summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Andrews <anotherjesse@gmail.com>2010-05-27 23:05:26 -0700
committerJesse Andrews <anotherjesse@gmail.com>2010-05-27 23:05:26 -0700
commitbf6e6e718cdc7488e2da87b21e258ccc065fe499 (patch)
tree51cf4f72047eb6b16079c7fe21e9822895541801
downloadnova-bf6e6e718cdc7488e2da87b21e258ccc065fe499.tar.gz
initial commit
-rw-r--r--CA/.gitignore11
-rw-r--r--CA/INTER/.gitignore1
-rwxr-xr-xCA/geninter.sh30
-rwxr-xr-xCA/genrootca.sh26
-rw-r--r--CA/newcerts/.placeholder0
-rw-r--r--CA/openssl.cnf.tmpl87
-rw-r--r--CA/private/.placeholder0
-rw-r--r--CA/reqs/.gitignore1
-rw-r--r--HACKING53
-rw-r--r--LICENSE176
-rwxr-xr-xbin/nova-api63
-rwxr-xr-xbin/nova-compute97
-rwxr-xr-xbin/nova-manage158
-rwxr-xr-xbin/nova-objectstore49
-rwxr-xr-xbin/nova-volume68
-rw-r--r--debian/changelog6
-rw-r--r--debian/compat1
-rw-r--r--debian/control40
-rw-r--r--debian/nova-api.init69
-rw-r--r--debian/nova-api.install1
-rw-r--r--debian/nova-common.install4
-rw-r--r--debian/nova-compute.init69
-rw-r--r--debian/nova-compute.install1
-rw-r--r--debian/nova-objectstore.init69
-rw-r--r--debian/nova-objectstore.install1
-rw-r--r--debian/nova-volume.init69
-rw-r--r--debian/nova-volume.install1
-rw-r--r--debian/pycompat1
-rw-r--r--debian/pyversions1
-rwxr-xr-xdebian/rules4
-rw-r--r--docs/.gitignore1
-rw-r--r--docs/Makefile89
-rw-r--r--docs/_build/.gitignore1
-rw-r--r--docs/_static/.gitignore0
-rw-r--r--docs/_templates/.gitignore0
-rw-r--r--docs/architecture.rst46
-rw-r--r--docs/auth.rst213
-rw-r--r--docs/binaries.rst29
-rw-r--r--docs/compute.rst72
-rw-r--r--docs/conf.py202
-rw-r--r--docs/endpoint.rst89
-rw-r--r--docs/fakes.rst41
-rw-r--r--docs/getting.started.rst70
-rw-r--r--docs/index.rst53
-rw-r--r--docs/modules.rst32
-rw-r--r--docs/network.rst86
-rw-r--r--docs/nova.rst89
-rw-r--r--docs/objectstore.rst64
-rw-r--r--docs/packages.rst27
-rw-r--r--docs/storage.rst29
-rw-r--r--docs/volume.rst43
-rw-r--r--nova/__init__.py30
-rw-r--r--nova/adminclient.py113
-rw-r--r--nova/auth/__init__.py25
-rw-r--r--nova/auth/access.py69
-rw-r--r--nova/auth/fakeldap.py81
-rw-r--r--nova/auth/novarc.template26
-rw-r--r--nova/auth/rbac.ldif60
-rw-r--r--nova/auth/signer.py127
-rwxr-xr-xnova/auth/slap.sh226
-rwxr-xr-xnova/auth/users.py454
-rw-r--r--nova/compute/__init__.py28
-rw-r--r--nova/compute/disk.py122
-rw-r--r--nova/compute/exception.py35
-rw-r--r--nova/compute/fakevirtinstance.xml43
-rw-r--r--nova/compute/libvirt.xml.template46
-rw-r--r--nova/compute/linux_net.py146
-rw-r--r--nova/compute/model.py203
-rw-r--r--nova/compute/network.py520
-rw-r--r--nova/compute/node.py549
-rw-r--r--nova/crypto.py224
-rw-r--r--nova/datastore.py367
-rw-r--r--nova/endpoint/__init__.py28
-rw-r--r--nova/endpoint/admin.py131
-rwxr-xr-xnova/endpoint/api.py337
-rw-r--r--nova/endpoint/cloud.py572
-rw-r--r--nova/endpoint/images.py92
-rw-r--r--nova/exception.py53
-rw-r--r--nova/fakerabbit.py131
-rw-r--r--nova/fakevirt.py109
-rw-r--r--nova/flags.py78
-rw-r--r--nova/objectstore/__init__.py28
-rw-r--r--nova/objectstore/bucket.py174
-rw-r--r--nova/objectstore/handler.py285
-rw-r--r--nova/objectstore/image.py177
-rw-r--r--nova/objectstore/stored.py58
-rw-r--r--nova/process.py131
-rw-r--r--nova/rpc.py222
-rw-r--r--nova/server.py139
-rw-r--r--nova/test.py246
-rw-r--r--nova/tests/CA/cacert.pem17
-rw-r--r--nova/tests/CA/private/cakey.pem15
-rw-r--r--nova/tests/__init__.py27
-rw-r--r--nova/tests/access_unittest.py60
-rw-r--r--nova/tests/api_integration.py50
-rw-r--r--nova/tests/api_unittest.py189
-rw-r--r--nova/tests/bundle/1mb.manifest.xml1
-rw-r--r--nova/tests/bundle/1mb.part.0bin0 -> 1024 bytes
-rw-r--r--nova/tests/bundle/1mb.part.11
-rw-r--r--nova/tests/cloud_unittest.py161
-rw-r--r--nova/tests/datastore_unittest.py60
-rw-r--r--nova/tests/fake_flags.py26
-rw-r--r--nova/tests/future_unittest.py74
-rw-r--r--nova/tests/keeper_unittest.py57
-rw-r--r--nova/tests/network_unittest.py113
-rw-r--r--nova/tests/node_unittest.py128
-rw-r--r--nova/tests/objectstore_unittest.py190
-rw-r--r--nova/tests/real_flags.py24
-rw-r--r--nova/tests/storage_unittest.py86
-rw-r--r--nova/tests/users_unittest.py137
-rw-r--r--nova/twistd.py249
-rw-r--r--nova/utils.py96
-rw-r--r--nova/vendor.py43
-rw-r--r--nova/volume/__init__.py27
-rw-r--r--nova/volume/storage.py250
-rw-r--r--run_tests.py99
-rw-r--r--setup.py32
-rw-r--r--vendor/IPy.py1304
-rw-r--r--vendor/Twisted-10.0.0/INSTALL32
-rw-r--r--vendor/Twisted-10.0.0/LICENSE57
-rw-r--r--vendor/Twisted-10.0.0/NEWS1416
-rw-r--r--vendor/Twisted-10.0.0/README118
-rw-r--r--vendor/Twisted-10.0.0/bin/.twistd.swpbin0 -> 12288 bytes
-rwxr-xr-xvendor/Twisted-10.0.0/bin/conch/cftp20
-rwxr-xr-xvendor/Twisted-10.0.0/bin/conch/ckeygen20
-rwxr-xr-xvendor/Twisted-10.0.0/bin/conch/conch20
-rwxr-xr-xvendor/Twisted-10.0.0/bin/conch/tkconch20
-rwxr-xr-xvendor/Twisted-10.0.0/bin/lore/lore21
-rwxr-xr-xvendor/Twisted-10.0.0/bin/mail/mailmail25
-rwxr-xr-xvendor/Twisted-10.0.0/bin/manhole21
-rwxr-xr-xvendor/Twisted-10.0.0/bin/mktap18
-rwxr-xr-xvendor/Twisted-10.0.0/bin/pyhtmlizer17
-rwxr-xr-xvendor/Twisted-10.0.0/bin/tap2deb20
-rwxr-xr-xvendor/Twisted-10.0.0/bin/tap2rpm22
-rwxr-xr-xvendor/Twisted-10.0.0/bin/tapconvert18
-rwxr-xr-xvendor/Twisted-10.0.0/bin/trial22
-rwxr-xr-xvendor/Twisted-10.0.0/bin/twistd19
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/benchmarks/README15
-rwxr-xr-xvendor/Twisted-10.0.0/doc/conch/benchmarks/buffering_mixin.py182
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/examples/demo.tac25
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/examples/demo_draw.tac80
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/examples/demo_insults.tac252
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/examples/demo_manhole.tac56
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/examples/demo_recvline.tac77
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/examples/demo_scroll.tac100
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/examples/index.html40
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/examples/sshsimpleclient.py111
-rwxr-xr-xvendor/Twisted-10.0.0/doc/conch/examples/sshsimpleserver.py117
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/examples/telnet_echo.tac37
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/examples/window.tac190
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/howto/conch_client.html318
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/howto/index.html28
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/index.html25
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/man/cftp-man.html87
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/man/cftp.189
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/man/ckeygen-man.html107
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/man/ckeygen.158
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/man/conch-man.html148
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/man/conch.1206
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/man/tkconch-man.html129
-rw-r--r--vendor/Twisted-10.0.0/doc/conch/man/tkconch.172
-rw-r--r--vendor/Twisted-10.0.0/doc/core/benchmarks/banana.py10
-rw-r--r--vendor/Twisted-10.0.0/doc/core/benchmarks/deferreds.py145
-rw-r--r--vendor/Twisted-10.0.0/doc/core/benchmarks/failure.py66
-rw-r--r--vendor/Twisted-10.0.0/doc/core/benchmarks/linereceiver.py47
-rw-r--r--vendor/Twisted-10.0.0/doc/core/benchmarks/task.py26
-rw-r--r--vendor/Twisted-10.0.0/doc/core/benchmarks/timer.py24
-rw-r--r--vendor/Twisted-10.0.0/doc/core/benchmarks/tpclient.py60
-rw-r--r--vendor/Twisted-10.0.0/doc/core/benchmarks/tpclient_nt.py22
-rw-r--r--vendor/Twisted-10.0.0/doc/core/benchmarks/tpserver.py19
-rw-r--r--vendor/Twisted-10.0.0/doc/core/benchmarks/tpserver_nt.py22
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/index.html27
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/listings/new_module_template.py12
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/naming.html38
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/philosophy.html58
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/policy/coding-standard.html809
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/policy/doc-standard.html188
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/policy/index.html33
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/policy/svn-dev.html227
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/policy/test-standard.html362
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/policy/writing-standard.html313
-rw-r--r--vendor/Twisted-10.0.0/doc/core/development/security.html43
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/ampclient.py26
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/ampserver.py40
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/bananabench.py79
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/chatserver.py37
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/courier.py111
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/cred.py163
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/examples/dbcred.py179
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/echoclient.py41
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/examples/echoclient_ssl.py46
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/echoclient_udp.py38
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/echoserv.py27
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/echoserv_ssl.py30
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/echoserv_udp.py19
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/filewatch.py17
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/ftpclient.py113
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/ftpserver.py55
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/gpsfix.py78
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/index.html127
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/longex.py66
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/longex2.py101
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/examples/mouse.py80
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pb_exceptions.py36
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pbbenchclient.py42
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pbbenchserver.py54
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pbecho.py51
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pbechoclient.py32
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pbgtk2.py122
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pbgtk2login.glade330
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pbinterop.py71
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pbsimple.py16
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pbsimpleclient.py18
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/postfix.py29
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/ptyserv.py32
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/pyui_bg.pngbin0 -> 29913 bytes
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/examples/pyuidemo.py31
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/rotatinglog.py26
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/row_example.py105
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/row_schema.sql65
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/row_util.py103
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/server.pem36
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/shaper.py52
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/shoutcast.py26
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/simple.tac39
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/simpleclient.py49
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/simpleserv.py26
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/stdin.py30
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/stdiodemo.py78
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/testlogging.py41
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib13
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib24
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nibbin0 -> 14896 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt6
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py79
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py14
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/threadedselect/README15
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/threadedselect/blockingdemo.py92
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/threadedselect/pygamedemo.py78
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/twistd-logging.tac33
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/wxacceptance.py113
-rw-r--r--vendor/Twisted-10.0.0/doc/core/examples/wxdemo.py64
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/application.html376
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/basics.html99
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/book.tex116
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/choosing-reactor.html355
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/clients.html635
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/components.html600
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/cred.html566
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/debug-with-emacs.html65
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/defer.html840
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/deferredindepth.html2183
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/design.html257
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/dirdbm.html77
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/gendefer.html415
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/glossary.html347
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/howto.tidyrc6
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/index.html198
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/internet-overview.html48
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/__init__.py3
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/pbquote.py10
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py32
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quoteproto.py36
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quoters.py39
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotes.txt15
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotetap.py29
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotetap2.py36
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/webquote.rpy12
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/application/service.tac34
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex.py60
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex1a.py73
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex1b.py79
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex2.py91
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex3.py100
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex4.py104
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex5.py136
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex6.py148
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex7.py61
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex8.py66
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/synch-validation.py5
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_classes.py43
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_receiver.py28
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_sender.py50
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/chatclient.py40
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/chatserver.py65
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_classes.py29
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_receiver.py21
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_sender.py44
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy_receiver.tac41
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy_sender.py57
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/exc_client.py33
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/exc_server.py32
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb1client.py31
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb1server.py20
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb2client.py36
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb2server.py30
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb3client.py26
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb3server.py16
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb4client.py58
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb5client.py22
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb5server.py29
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6client1.py22
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6client2.py25
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6server.py30
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb7client.py29
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pbAnonClient.py70
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/pbAnonServer.py91
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/trap_client.py88
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/pb/trap_server.py21
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/listings/process/process.py46
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/process/quotes.py25
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/process/trueandfalse.py14
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/udp/MulticastClient.py13
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/listings/udp/MulticastServer.py25
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/logging.html181
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/options.html533
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/overview.html18
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/pb-copyable.html1195
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/pb-cred.html1723
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/pb-intro.html320
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/pb-usage.html1158
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/pb.html52
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/pclients.html364
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/plugin.html292
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/process.html725
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/producers.html88
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/quotes.html214
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/rdbms.html228
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/reactor-basics.html92
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/row.html279
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/servers.html429
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/ssl.html550
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/stylesheet-unprocessed.css20
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/stylesheet.css189
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tap.html346
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/telnet.html83
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/template.tpl23
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/testing.html168
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/threading.html213
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/time.html118
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/backends.html1207
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/client.html260
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/components.html1068
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/configuration.html792
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/factory.html633
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/index.html83
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/intro.html716
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/library.html269
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/etc.users2
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/__init__.py3
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/finger.py331
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/tap.py20
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger01.py2
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger02.py10
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger03.py11
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger04.py12
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger05.py13
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger06.py18
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger07.py21
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger08.py30
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger09.py26
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger10.py30
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger11.tac34
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger12.tac55
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger13.tac59
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger14.tac55
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger15.tac76
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger16.tac91
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger17.tac91
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger18.tac137
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19.tac238
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19a.tac231
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19a_changes.py29
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19b.tac257
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19b_changes.py19
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19c.tac269
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19c_changes.py32
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger20.tac251
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger21.tac280
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger22.py297
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerPBclient.py26
-rwxr-xr-xvendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerXRclient.py5
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger_config.py38
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerproxy.tac110
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/organized-finger.tac31
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/simple-finger.tac17
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py5
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/pb.html650
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/protocol.html1055
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/style.html313
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/tutorial/web.html537
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/udp.html275
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/upgrading.html331
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/vision.html43
-rw-r--r--vendor/Twisted-10.0.0/doc/core/howto/website-template.tpl22
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/TwistedLogo.bmpbin0 -> 55494 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/cred-login.diabin0 -> 2369 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/cred-login.pngbin0 -> 34148 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/deferred-attach.diabin0 -> 2234 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/deferred-attach.pngbin0 -> 9356 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/deferred-process.diabin0 -> 2099 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/deferred-process.pngbin0 -> 10809 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/deferred.diabin0 -> 4348 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/deferred.pngbin0 -> 33282 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/twisted-overview.diabin0 -> 5984 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/img/twisted-overview.pngbin0 -> 50929 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/core/index.html33
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/manhole-man.html50
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/manhole.116
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/mktap-man.html328
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/mktap.1219
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/pyhtmlizer-man.html51
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/pyhtmlizer.122
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/tap2deb-man.html106
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/tap2deb.157
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/tap2rpm-man.html107
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/tap2rpm.158
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/tapconvert-man.html82
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/tapconvert.140
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/trial-man.html194
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/trial.1132
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/twistd-man.html194
-rw-r--r--vendor/Twisted-10.0.0/doc/core/man/twistd.1123
-rw-r--r--vendor/Twisted-10.0.0/doc/core/specifications/banana.html199
-rw-r--r--vendor/Twisted-10.0.0/doc/core/specifications/index.html21
-rw-r--r--vendor/Twisted-10.0.0/doc/core/upgrades/2.0/components.html115
-rw-r--r--vendor/Twisted-10.0.0/doc/core/upgrades/2.0/index.html31
-rw-r--r--vendor/Twisted-10.0.0/doc/core/upgrades/2.0/split.html163
-rw-r--r--vendor/Twisted-10.0.0/doc/core/upgrades/index.html29
-rw-r--r--vendor/Twisted-10.0.0/doc/fun/Twisted.Quotes5722
-rw-r--r--vendor/Twisted-10.0.0/doc/fun/lightbulb7
-rw-r--r--vendor/Twisted-10.0.0/doc/fun/register.html77
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2002/ipc10/twisted-network-framework/errata.html256
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2002/ipc10/twisted-network-framework/index.html1568
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/europython/doanddont.html508
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/europython/index.html35
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/europython/lore.html502
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/europython/slides-template.tpl19
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/europython/tw-deploy.html1106
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/europython/twisted.html608
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/europython/webclients.html482
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/haifux/haifux.html2235
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/haifux/notes.html60
-rwxr-xr-xvendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/applications257
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/applications.html343
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/pynfo-chart.pngbin0 -> 13018 bytes
-rwxr-xr-xvendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conch98
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conch.html165
-rwxr-xr-xvendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conchtalk.txt144
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/smalltwisted.pngbin0 -> 1472 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/twistedlogo.pngbin0 -> 7256 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-bad-adding.py8
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-chaining.py13
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-complex-failure.py30
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-complex-raise.py12
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-forwarding.py9
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing0.py18
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing1.py6
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing2.py8
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-simple-failure.py9
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-simple-raise.py3
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex.html499
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferexex.py16
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning97
-rwxr-xr-xvendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore-presentation108
-rwxr-xr-xvendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore-slides.html187
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore.html791
-rwxr-xr-xvendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-client1.py46
-rwxr-xr-xvendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-server1.py16
-rwxr-xr-xvendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-slides.py240
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb.html966
-rwxr-xr-xvendor/Twisted-10.0.0/doc/historic/2003/pycon/releasing/releasing-twisted151
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/releasing/releasing.html491
-rwxr-xr-xvendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/tw-deploy184
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/twisted-overview.pngbin0 -> 12722 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/twistedlogo.pngbin0 -> 7256 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-internet/twisted-internet.py541
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-reality/componentized.svg254
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-reality/twisted-reality.html578
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/2004/ibm/talk.html495
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/index.html128
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/ipc10errata.html256
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/ipc10paper.html1568
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/stylesheet.css178
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/template-notoc.tpl14
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/template.tpl20
-rw-r--r--vendor/Twisted-10.0.0/doc/historic/twisted-debian.html96
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/examples/example.html60
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/examples/index.html22
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/examples/slides-template.tpl21
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/howto/extend-lore.html425
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/howto/index.html23
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/1st_example.html12
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/a_lore_plugin.py11
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-19
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-220
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-321
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/spitters.py-118
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/spitters.py-226
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/howto/lore.html366
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/img/myhtml-output.pngbin0 -> 23124 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/index.html25
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/man/lore-man.html124
-rw-r--r--vendor/Twisted-10.0.0/doc/lore/man/lore.174
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/examples/emailserver.tac72
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/examples/imap4client.py181
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/examples/index.html35
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/examples/smtpclient_tls.py157
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/index.html25
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/man/mailmail-man.html55
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/man/mailmail.121
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-1.tac3
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-10.tac56
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-11.tac58
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-2.tac10
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-3.tac10
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-4.tac12
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-5.tac14
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-6.tac18
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-7.tac46
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-8.tac49
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-9.tac53
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient.html752
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-1.tac3
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-2.tac10
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-3.tac12
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-4.tac14
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-5.tac50
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-6.tac57
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-7.tac57
-rw-r--r--vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-8.tac63
-rwxr-xr-xvendor/Twisted-10.0.0/doc/names/examples/dns-service.py36
-rwxr-xr-xvendor/Twisted-10.0.0/doc/names/examples/gethostbyname.py21
-rw-r--r--vendor/Twisted-10.0.0/doc/names/examples/index.html24
-rw-r--r--vendor/Twisted-10.0.0/doc/names/examples/testdns.py38
-rw-r--r--vendor/Twisted-10.0.0/doc/names/howto/index.html22
-rw-r--r--vendor/Twisted-10.0.0/doc/names/howto/listings/names/example-domain.com37
-rw-r--r--vendor/Twisted-10.0.0/doc/names/howto/names.html134
-rw-r--r--vendor/Twisted-10.0.0/doc/names/index.html25
-rw-r--r--vendor/Twisted-10.0.0/doc/pair/examples/index.html23
-rw-r--r--vendor/Twisted-10.0.0/doc/pair/examples/pairudp.py18
-rw-r--r--vendor/Twisted-10.0.0/doc/pair/howto/index.html27
-rw-r--r--vendor/Twisted-10.0.0/doc/pair/howto/twisted-pair.html79
-rw-r--r--vendor/Twisted-10.0.0/doc/pair/index.html23
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/advogato.py45
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/dlpage.py9
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/fortune.rpy.py17
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/getpage.py9
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/google.py9
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/hello.rpy.py28
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/httpclient.py54
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/index.html96
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/lj.rpy.py35
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/proxy.py11
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/report.rpy.py28
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/rootscript.py9
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/silly-web.py18
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/simple.rtl23
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/soap.py41
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/users.rpy.py18
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/vhost.rpy.py4
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/web.py27
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/webguard.py54
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/xmlrpc.py67
-rw-r--r--vendor/Twisted-10.0.0/doc/web/examples/xmlrpcclient.py23
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/client.html469
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/formindepth.html20
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/glossary.html42
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/index.html50
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/listings/client/request.py21
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/listings/client/response.py47
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/listings/client/sendbody.py24
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/listings/client/stringprod.py21
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/listings/soap.rpy13
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/listings/webquote.rtl20
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/listings/xmlAndSoapQuote.py25
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/listings/xmlquote.rpy12
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/resource-templates.html103
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/using-twistedweb.html972
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-development.html106
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/asynchronous-deferred.html161
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/asynchronous.html121
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/custom-codes.html118
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/dynamic-content.html120
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/dynamic-dispatch.html142
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/error-handling.html129
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/handling-posts.html137
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/http-auth.html250
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/index.html44
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/interrupted.html141
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/logging-errors.html104
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/rpy-scripts.html86
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-basics.html120
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-endings.html170
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-store.html180
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/static-content.html102
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/static-dispatch.html118
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-in-60/wsgi.html123
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/web-overview.html67
-rw-r--r--vendor/Twisted-10.0.0/doc/web/howto/xmlrpc.html457
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/controller.pngbin0 -> 14934 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/livepage.pngbin0 -> 9363 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/model.pngbin0 -> 14971 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/plone_root_model.pngbin0 -> 11214 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/view.pngbin0 -> 14703 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/web-overview.diabin0 -> 1630 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/web-overview.pngbin0 -> 7330 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/web-process.pngbin0 -> 30404 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/web-process.svg594
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/web-session.pngbin0 -> 8966 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/web-widgets.diabin0 -> 1326 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/img/web-widgets.pngbin0 -> 3147 bytes
-rw-r--r--vendor/Twisted-10.0.0/doc/web/index.html25
-rw-r--r--vendor/Twisted-10.0.0/doc/words/examples/aimbot.py62
-rw-r--r--vendor/Twisted-10.0.0/doc/words/examples/cursesclient.py188
-rw-r--r--vendor/Twisted-10.0.0/doc/words/examples/index.html30
-rw-r--r--vendor/Twisted-10.0.0/doc/words/examples/ircLogBot.py156
-rw-r--r--vendor/Twisted-10.0.0/doc/words/examples/jabber_client.py29
-rw-r--r--vendor/Twisted-10.0.0/doc/words/examples/minchat.py126
-rw-r--r--vendor/Twisted-10.0.0/doc/words/examples/msn_example.py67
-rwxr-xr-xvendor/Twisted-10.0.0/doc/words/examples/oscardemo.py100
-rw-r--r--vendor/Twisted-10.0.0/doc/words/examples/pb_client.py102
-rw-r--r--vendor/Twisted-10.0.0/doc/words/examples/xmpp_client.py82
-rw-r--r--vendor/Twisted-10.0.0/doc/words/howto/im.html115
-rw-r--r--vendor/Twisted-10.0.0/doc/words/howto/index.html22
-rw-r--r--vendor/Twisted-10.0.0/doc/words/index.html25
-rw-r--r--vendor/Twisted-10.0.0/doc/words/man/im-man.html50
-rw-r--r--vendor/Twisted-10.0.0/doc/words/man/im.116
-rwxr-xr-xvendor/Twisted-10.0.0/setup.py100
-rw-r--r--vendor/Twisted-10.0.0/twisted/__init__.py24
-rw-r--r--vendor/Twisted-10.0.0/twisted/_version.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/application/__init__.py7
-rw-r--r--vendor/Twisted-10.0.0/twisted/application/app.py730
-rw-r--r--vendor/Twisted-10.0.0/twisted/application/internet.py270
-rw-r--r--vendor/Twisted-10.0.0/twisted/application/reactors.py83
-rw-r--r--vendor/Twisted-10.0.0/twisted/application/service.py398
-rw-r--r--vendor/Twisted-10.0.0/twisted/application/strports.py200
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/__init__.py18
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/_version.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/avatar.py37
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/checkers.py266
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/client/__init__.py9
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/client/agent.py73
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/client/connect.py21
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/client/default.py256
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/client/direct.py107
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/client/knownhosts.py474
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/client/options.py90
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/error.py102
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/insults/__init__.py4
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/insults/client.py138
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/insults/colors.py29
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/insults/helper.py450
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/insults/insults.py1087
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/insults/text.py186
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/insults/window.py864
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/interfaces.py402
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ls.py60
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/manhole.py336
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/manhole_ssh.py146
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/manhole_tap.py128
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/mixin.py49
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/openssh_compat/__init__.py11
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/openssh_compat/factory.py73
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/openssh_compat/primes.py26
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/recvline.py328
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/scripts/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/scripts/cftp.py811
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/scripts/ckeygen.py188
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/scripts/conch.py510
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/scripts/tkconch.py572
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/__init__.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/agent.py294
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/asn1.py34
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/channel.py281
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/common.py130
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/connection.py613
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/factory.py131
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/filetransfer.py927
-rwxr-xr-xvendor/Twisted-10.0.0/twisted/conch/ssh/forwarding.py181
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/keys.py941
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/service.py48
-rwxr-xr-xvendor/Twisted-10.0.0/twisted/conch/ssh/session.py310
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/sexpy.py42
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/transport.py1404
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ssh/userauth.py846
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/stdio.py95
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/tap.py48
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/telnet.py1017
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/keydata.py174
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_agent.py399
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_cftp.py881
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_channel.py279
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_checkers.py280
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_ckeygen.py80
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_conch.py437
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_connection.py623
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_default.py171
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_filetransfer.py677
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_helper.py560
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_insults.py460
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_keys.py961
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_knownhosts.py979
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_manhole.py348
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_mixin.py47
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_openssh_compat.py102
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_recvline.py649
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_session.py1210
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_ssh.py886
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_tap.py95
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_telnet.py710
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_text.py101
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_transport.py1953
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_userauth.py1062
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/test/test_window.py49
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/topfiles/NEWS206
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/topfiles/README4
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/topfiles/setup.py48
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ttymodes.py121
-rwxr-xr-xvendor/Twisted-10.0.0/twisted/conch/ui/__init__.py11
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ui/ansi.py240
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/ui/tkvt100.py197
-rw-r--r--vendor/Twisted-10.0.0/twisted/conch/unix.py457
-rw-r--r--vendor/Twisted-10.0.0/twisted/copyright.py39
-rw-r--r--vendor/Twisted-10.0.0/twisted/cred/__init__.py13
-rw-r--r--vendor/Twisted-10.0.0/twisted/cred/_digest.py129
-rw-r--r--vendor/Twisted-10.0.0/twisted/cred/checkers.py268
-rw-r--r--vendor/Twisted-10.0.0/twisted/cred/credentials.py483
-rw-r--r--vendor/Twisted-10.0.0/twisted/cred/error.py41
-rw-r--r--vendor/Twisted-10.0.0/twisted/cred/pamauth.py79
-rw-r--r--vendor/Twisted-10.0.0/twisted/cred/portal.py121
-rw-r--r--vendor/Twisted-10.0.0/twisted/cred/strcred.py270
-rw-r--r--vendor/Twisted-10.0.0/twisted/cred/util.py46
-rw-r--r--vendor/Twisted-10.0.0/twisted/enterprise/__init__.py9
-rw-r--r--vendor/Twisted-10.0.0/twisted/enterprise/adbapi.py488
-rw-r--r--vendor/Twisted-10.0.0/twisted/enterprise/reflector.py167
-rw-r--r--vendor/Twisted-10.0.0/twisted/enterprise/row.py127
-rw-r--r--vendor/Twisted-10.0.0/twisted/enterprise/sqlreflector.py327
-rw-r--r--vendor/Twisted-10.0.0/twisted/enterprise/util.py200
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/__init__.py12
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/_baseprocess.py62
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/_dumbwin32proc.py340
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/_javaserialport.py78
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/_pollingfile.py279
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/_posixserialport.py60
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/_posixstdio.py173
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/_sslverify.py748
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/_threadedselect.py362
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/_win32serialport.py112
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/_win32stdio.py124
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/abstract.py378
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/address.py113
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/base.py1191
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/cfreactor.py342
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfdate.pxi2
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfdecl.pxi227
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfrunloop.pxi104
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsocket.pxi111
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsupport.c2136
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsupport.pyx6
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/cfsupport/python.pxi5
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/cfsupport/setup.py50
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/default.py21
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/defer.py1264
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/epollreactor.py235
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/error.py319
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/fdesc.py118
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/glib2reactor.py49
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/gtk2reactor.py377
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/gtkreactor.py232
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/interfaces.py1693
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/__init__.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/abstract.py456
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/build.bat4
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/const.py26
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/interfaces.py33
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/acceptex.pxi38
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/connectex.pxi34
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/iocpsupport.c2003
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/iocpsupport.pyx250
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c62
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.h51
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/wsarecv.pxi61
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/wsasend.pxi27
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/notes.txt24
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/reactor.py267
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/setup.py23
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/tcp.py639
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/iocpreactor/udp.py389
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/kqreactor.py221
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/main.py28
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/pollreactor.py208
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/posixbase.py417
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/process.py931
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/protocol.py699
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/pyuisupport.py37
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/qtreactor.py19
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/reactor.py38
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/selectreactor.py204
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/serialport.py65
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/ssl.py233
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/stdio.py32
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/task.py750
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/tcp.py1019
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/__init__.py6
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/inlinecb_tests.py92
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/process_helper.py33
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/reactormixins.py193
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_base.py179
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_baseprocess.py73
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_core.py275
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_fdset.py209
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_inlinecb.py13
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_iocp.py105
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_pollingfile.py39
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_posixbase.py259
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_process.py475
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_qtreactor.py35
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_tcp.py143
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_threads.py163
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_time.py26
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_tls.py163
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/test/test_unix.py137
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/threads.py117
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/tksupport.py68
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/udp.py297
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/unix.py297
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/utils.py219
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/win32eventreactor.py244
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/wxreactor.py181
-rw-r--r--vendor/Twisted-10.0.0/twisted/internet/wxsupport.py61
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/__init__.py21
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/_version.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/default.py56
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/docbook.py68
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/htmlbook.py47
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/indexer.py50
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/latex.py463
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/lint.py204
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/lmath.py85
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/man2lore.py295
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/numberer.py33
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/process.py120
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/scripts/__init__.py1
-rwxr-xr-xvendor/Twisted-10.0.0/twisted/lore/scripts/lore.py159
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/slides.py359
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/template.mgp24
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_out.html2
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_out_multiple.html5
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_unnumbered_out.html2
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/lore_index_test.xhtml21
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/lore_index_test2.xhtml22
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/lore_numbering_test_out.html2
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/lore_numbering_test_out2.html2
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/simple.html9
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/simple3.html9
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/simple4.html9
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/template.tpl13
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/test_docbook.py35
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/test_latex.py146
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/test_lint.py132
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/test_lmath.py53
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/test_lore.py1228
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/test_man2lore.py169
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/test/test_slides.py85
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/texi.py109
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/topfiles/NEWS103
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/topfiles/README3
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/topfiles/setup.py29
-rwxr-xr-xvendor/Twisted-10.0.0/twisted/lore/tree.py1152
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/xhtml-lat1.ent196
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/xhtml-special.ent80
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/xhtml-symbol.ent237
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/xhtml1-strict.dtd978
-rw-r--r--vendor/Twisted-10.0.0/twisted/lore/xhtml1-transitional.dtd1201
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/__init__.py15
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/_version.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/alias.py435
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/bounce.py61
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/imap4.py5670
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/mail.py333
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/maildir.py517
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/pb.py115
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/pop3.py1072
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/pop3client.py706
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/protocols.py225
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/relay.py114
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/relaymanager.py631
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/scripts/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/scripts/mailmail.py360
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/smtp.py2023
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/tap.py185
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/pop3testserver.py314
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/rfc822.message86
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/test_bounce.py32
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/test_imap.py4244
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/test_mail.py1968
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/test_mailmail.py75
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/test_options.py44
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/test_pop3.py1069
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/test_pop3client.py573
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/test/test_smtp.py1530
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/topfiles/NEWS191
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/topfiles/README3
-rw-r--r--vendor/Twisted-10.0.0/twisted/mail/topfiles/setup.py50
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/__init__.py8
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/_inspectro.py369
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/explorer.py655
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/gladereactor.glade342
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/gladereactor.py219
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/inspectro.glade510
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/logview.glade39
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/service.py399
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/telnet.py117
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/ui/__init__.py7
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/ui/gtk2manhole.glade268
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/ui/gtk2manhole.py375
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/ui/test/__init__.py4
-rw-r--r--vendor/Twisted-10.0.0/twisted/manhole/ui/test/test_gtk2manhole.py48
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/__init__.py7
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/_version.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/authority.py322
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/cache.py96
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/client.py928
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/common.py265
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/dns.py1822
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/error.py95
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/hosts.py61
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/resolve.py59
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/root.py446
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/secondary.py102
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/server.py205
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/srvconnect.py186
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/tap.py119
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/test/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/test/test_cache.py14
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/test/test_client.py655
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/test/test_common.py71
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/test/test_dns.py1200
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/test/test_names.py752
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/test/test_rootresolve.py705
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/test/test_srvconnect.py133
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/topfiles/NEWS131
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/topfiles/README3
-rw-r--r--vendor/Twisted-10.0.0/twisted/names/topfiles/setup.py50
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/__init__.py11
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/_version.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/database.py998
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/news.py90
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/nntp.py1069
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/tap.py134
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/test/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/test/test_news.py107
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/test/test_nntp.py124
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/topfiles/NEWS54
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/topfiles/README4
-rw-r--r--vendor/Twisted-10.0.0/twisted/news/topfiles/setup.py28
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/__init__.py20
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/_version.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/ethernet.py56
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/ip.py72
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/raw.py35
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/rawudp.py55
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/test/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/test/test_ethernet.py226
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/test/test_ip.py417
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/test/test_rawudp.py327
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/topfiles/NEWS20
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/topfiles/README1
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/topfiles/setup.py28
-rw-r--r--vendor/Twisted-10.0.0/twisted/pair/tuntap.py170
-rw-r--r--vendor/Twisted-10.0.0/twisted/persisted/__init__.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/persisted/aot.py560
-rw-r--r--vendor/Twisted-10.0.0/twisted/persisted/crefutil.py167
-rw-r--r--vendor/Twisted-10.0.0/twisted/persisted/dirdbm.py358
-rw-r--r--vendor/Twisted-10.0.0/twisted/persisted/journal/__init__.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/persisted/journal/base.py226
-rw-r--r--vendor/Twisted-10.0.0/twisted/persisted/journal/picklelog.py48
-rw-r--r--vendor/Twisted-10.0.0/twisted/persisted/journal/rowjournal.py99
-rw-r--r--vendor/Twisted-10.0.0/twisted/persisted/sob.py227
-rw-r--r--vendor/Twisted-10.0.0/twisted/persisted/styles.py257
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugin.py246
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/__init__.py17
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/cred_anonymous.py40
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/cred_file.py60
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/cred_memory.py68
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/cred_unix.py138
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_conch.py18
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_ftp.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_inet.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_lore.py38
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_mail.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_manhole.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_names.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_news.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_portforward.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_qtstub.py45
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_reactors.py38
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_socks.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_telnet.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_trial.py59
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_web.py11
-rw-r--r--vendor/Twisted-10.0.0/twisted/plugins/twisted_words.py48
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/__init__.py7
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/_c_urlarg.c147
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/amp.py2394
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/basic.py519
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/dict.py362
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/finger.py43
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/ftp.py2814
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/gps/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/gps/nmea.py209
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/gps/rockwell.py268
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/htb.py269
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/ident.py227
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/loopback.py397
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/memcache.py758
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/mice/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/mice/mouseman.py127
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/pcp.py204
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/policies.py645
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/portforward.py76
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/postfix.py112
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/shoutcast.py111
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/sip.py1334
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/socks.py240
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/stateful.py52
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/telnet.py325
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/test/__init__.py6
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/test/test_tls.py566
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/tls.py345
-rw-r--r--vendor/Twisted-10.0.0/twisted/protocols/wire.py90
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/__init__.py13
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/_epoll.c925
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/_epoll.pyx181
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/_initgroups.c66
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/_release.py1265
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/_twisted_zsh_stub89
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/compat.py173
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/components.py448
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/context.py90
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/deprecate.py375
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/dispatch.py42
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/dist.py361
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/dxprofile.py56
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/failure.py557
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/fakepwd.py112
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/filepath.py802
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/finalize.py46
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/formmethod.py363
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/hashlib.py24
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/hook.py177
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/htmlizer.py91
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/lockfile.py212
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/log.py665
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/logfile.py324
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/modules.py747
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/monkey.py73
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/otp.py496
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/procutils.py45
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/randbytes.py177
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/rebuild.py264
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/reflect.py812
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/release.py57
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/roots.py248
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/runtime.py97
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/shortcut.py76
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/syslog.py107
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/__init__.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/deprecatedattributes.py21
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_components.py741
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_deprecate.py399
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_dist.py173
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_fakepwd.py216
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_hashlib.py90
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_htmlizer.py41
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_release.py2476
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_runtime.py29
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_syslog.py151
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_util.py834
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_versions.py323
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_win32.py35
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/test/test_zipstream.py455
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/text.py227
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/threadable.py120
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/threadpool.py308
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/timeoutqueue.py49
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/urlpath.py122
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/usage.py631
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/util.py968
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/versions.py249
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/win32.py163
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zippath.py217
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zipstream.py377
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/README8
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_cftp48
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_ckeygen25
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_conch58
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_lore28
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_manhole19
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_mktap304
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_pyhtmlizer8
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_tap2deb23
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_tap2rpm23
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_tapconvert17
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_tkconch38
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_tkmktap0
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_trial40
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_twistd328
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zsh/_websetroot0
-rw-r--r--vendor/Twisted-10.0.0/twisted/python/zshcomp.py780
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/__init__.py15
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/_version.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/inetd.py70
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/inetdconf.py194
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/inetdtap.py160
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/portmap.c57
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/procmon.py264
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/procutils.py5
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/test/__init__.py6
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/test/test_procmon.py55
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/topfiles/NEWS49
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/topfiles/README2
-rw-r--r--vendor/Twisted-10.0.0/twisted/runner/topfiles/setup.py35
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/__init__.py12
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/_twistd_unix.py317
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/_twistw.py50
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/htmlizer.py66
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/manhole.py65
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/mktap.py182
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/tap2deb.py281
-rwxr-xr-xvendor/Twisted-10.0.0/twisted/scripts/tap2rpm.py273
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/tapconvert.py53
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/test/__init__.py6
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/test/test_mktap.py122
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/tkunzip.py286
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/trial.py370
-rw-r--r--vendor/Twisted-10.0.0/twisted/scripts/twistd.py30
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/__init__.py12
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/banana.py358
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/flavors.py600
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/interfaces.py28
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/jelly.py1134
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/pb.py1380
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/publish.py142
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/refpath.py95
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/ui/__init__.py12
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/ui/gtk2util.py215
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/ui/login2.glade461
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/ui/tktree.py204
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/ui/tkutil.py397
-rw-r--r--vendor/Twisted-10.0.0/twisted/spread/util.py215
-rw-r--r--vendor/Twisted-10.0.0/twisted/tap/__init__.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/tap/ftp.py51
-rw-r--r--vendor/Twisted-10.0.0/twisted/tap/manhole.py51
-rw-r--r--vendor/Twisted-10.0.0/twisted/tap/portforward.py24
-rw-r--r--vendor/Twisted-10.0.0/twisted/tap/socks.py34
-rw-r--r--vendor/Twisted-10.0.0/twisted/tap/telnet.py29
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/__init__.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/crash_test_dummy.py34
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/generator_failure_tests.py169
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/iosim.py270
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/mock_win32process.py48
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/myrebuilder1.py15
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/myrebuilder2.py16
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/plugin_basic.py57
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/plugin_extra1.py23
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/plugin_extra2.py35
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/process_cmdline.py5
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/process_echoer.py11
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/process_fds.py40
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/process_linger.py17
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/process_reader.py12
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/process_signal.py8
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/process_stdinreader.py23
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/process_tester.py37
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/process_tty.py6
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/process_twisted.py43
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/proto_helpers.py299
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/raiser.c316
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/raiser.pyx21
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/reflect_helper_IE.py4
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/reflect_helper_VE.py4
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/reflect_helper_ZDE.py4
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/server.pem36
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/ssl_helpers.py26
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/stdio_test_consumer.py39
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/stdio_test_hostpeer.py32
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/stdio_test_lastwrite.py45
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/stdio_test_loseconn.py48
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/stdio_test_producer.py55
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/stdio_test_write.py32
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/stdio_test_writeseq.py30
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_abstract.py83
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_adbapi.py774
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_amp.py2555
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_application.py867
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_banana.py278
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_compat.py199
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_context.py15
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_cooperator.py634
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_defer.py950
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_defgen.py283
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_dict.py22
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_digestauth.py671
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_dirdbm.py176
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_doc.py92
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_enterprise.py41
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_epoll.py159
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_error.py170
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_explorer.py236
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_extensions.py18
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_factories.py162
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_failure.py318
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_fdesc.py235
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_finger.py67
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_formmethod.py77
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_ftp.py2671
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_hook.py150
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_htb.py96
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_ident.py194
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_import.py78
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_internet.py1396
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_iutils.py296
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_jelly.py618
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_journal.py169
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_lockfile.py445
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_log.py559
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_logfile.py314
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_loopback.py433
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_manhole.py75
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_memcache.py663
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_modules.py391
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_monkey.py161
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_newcred.py487
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_nmea.py115
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_paths.py896
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_pb.py1775
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_pbfailure.py424
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_pcp.py368
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_persisted.py314
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_plugin.py694
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_policies.py683
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_postfix.py108
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_process.py2410
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_protocols.py811
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_randbytes.py178
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_rebuild.py252
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_reflect.py756
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_reflector.py401
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_roots.py63
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_shortcut.py26
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_sip.py942
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_sob.py172
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_socks.py498
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_ssl.py664
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_sslverify.py558
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_stateful.py77
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_stdio.py287
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_strcred.py622
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_strerror.py145
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_stringtransport.py160
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_strports.py84
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_task.py627
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_tcp.py1908
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_tcp_internals.py240
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_text.py156
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_threadable.py103
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_threadpool.py583
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_threads.py412
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_timehelpers.py31
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_timeoutqueue.py73
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_tpfile.py52
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_twistd.py1378
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_udp.py661
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_unix.py405
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_usage.py372
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/test_zshcomp.py210
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/testutils.py55
-rw-r--r--vendor/Twisted-10.0.0/twisted/test/time_helpers.py72
-rw-r--r--vendor/Twisted-10.0.0/twisted/topfiles/4335.misc0
-rw-r--r--vendor/Twisted-10.0.0/twisted/topfiles/CREDITS60
-rw-r--r--vendor/Twisted-10.0.0/twisted/topfiles/ChangeLog.Old3888
-rw-r--r--vendor/Twisted-10.0.0/twisted/topfiles/NEWS942
-rw-r--r--vendor/Twisted-10.0.0/twisted/topfiles/README14
-rw-r--r--vendor/Twisted-10.0.0/twisted/topfiles/setup.py99
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/__init__.py52
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/itrial.py251
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/reporter.py1204
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/runner.py905
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/detests.py195
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/erroneous.py130
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite.py21
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite2.py21
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite3.py28
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/mockdoctest.py104
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/moduleself.py7
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/moduletest.py11
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/notpython2
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/novars.py6
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/packages.py134
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/sample.py40
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/scripttest.py14
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/suppression.py57
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_assertions.py742
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_deferred.py220
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_doctest.py81
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_keyboard.py113
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_loader.py541
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_log.py197
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_output.py162
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_plugins.py46
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_pyunitcompat.py222
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_reporter.py1561
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_runner.py914
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_script.py390
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_test_visitor.py82
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_testcase.py51
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_tests.py1056
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_util.py533
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/test_warning.py436
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/test/weird.py20
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/unittest.py1597
-rw-r--r--vendor/Twisted-10.0.0/twisted/trial/util.py378
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/__init__.py13
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/_auth/__init__.py7
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/_auth/basic.py59
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/_auth/digest.py54
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/_auth/wrapper.py222
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/_newclient.py1413
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/_version.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/client.py644
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/demo.py29
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/distrib.py374
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/domhelpers.py268
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/error.py230
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/google.py75
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/guard.py17
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/html.py49
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/http.py1797
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/http_headers.py260
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/iweb.py421
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/microdom.py1028
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/proxy.py302
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/resource.py300
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/rewrite.py52
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/script.py169
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/server.py527
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/soap.py154
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/static.py1104
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/sux.py657
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/tap.py234
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/__init__.py7
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/_util.py24
-rwxr-xr-xvendor/Twisted-10.0.0/twisted/web/test/test_cgi.py190
-rwxr-xr-xvendor/Twisted-10.0.0/twisted/web/test/test_distrib.py361
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_domhelpers.py306
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_error.py151
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_http.py1531
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_http_headers.py585
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_httpauth.py586
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_newclient.py2082
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_proxy.py541
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_resource.py144
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_script.py70
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_soap.py114
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_static.py1507
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_tap.py251
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_vhost.py105
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_web.py863
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_webclient.py1060
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_wsgi.py1572
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_xml.py1105
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/test/test_xmlrpc.py510
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/topfiles/NEWS309
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/topfiles/README1
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/topfiles/setup.py30
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/trp.py23
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/twcgi.py253
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/util.py380
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/vhost.py135
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/wsgi.py401
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/xmlrpc.py427
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/__init__.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/_version.py3
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/ewords.py34
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/__init__.py8
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/baseaccount.py62
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/basechat.py316
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/basesupport.py270
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/instancemessenger.glade3165
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/interfaces.py364
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/ircsupport.py261
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/locals.py26
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/pbsupport.py260
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/proxyui.py24
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/tap.py15
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/im/tocsupport.py220
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/iwords.py266
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/irc.py3166
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/__init__.py8
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/client.py369
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/component.py474
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/error.py336
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/ijabber.py199
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/jid.py249
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/jstrports.py31
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/sasl.py243
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/sasl_mechanisms.py240
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/xmlstream.py1136
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/jabber/xmpp_stringprep.py248
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/msn.py2449
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/oscar.py1235
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/protocols/toc.py1622
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/service.py1223
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/tap.py72
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/__init__.py1
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_basesupport.py97
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_domish.py421
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_irc.py1566
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_irc_service.py110
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_jabberclient.py414
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_jabbercomponent.py422
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_jabbererror.py308
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_jabberjid.py225
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_jabbersasl.py272
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_jabbersaslmechanisms.py90
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_jabberxmlstream.py1287
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_jabberxmppstringprep.py84
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_msn.py503
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_oscar.py24
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_service.py992
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_tap.py78
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_toc.py340
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_xishutil.py345
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_xmlstream.py201
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_xmpproutertap.py86
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/test/test_xpath.py260
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/toctap.py20
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/topfiles/NEWS230
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/topfiles/README4
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/topfiles/setup.py53
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/xish/__init__.py10
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/xish/domish.py848
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/xish/utility.py372
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/xish/xmlstream.py261
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/xish/xpath.py333
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/xish/xpathparser.g375
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/xish/xpathparser.py508
-rw-r--r--vendor/Twisted-10.0.0/twisted/words/xmpproutertap.py30
-rw-r--r--vendor/amqplib/__init__.py1
-rw-r--r--vendor/amqplib/client_0_8/__init__.py35
-rw-r--r--vendor/amqplib/client_0_8/abstract_channel.py114
-rw-r--r--vendor/amqplib/client_0_8/basic_message.py137
-rw-r--r--vendor/amqplib/client_0_8/channel.py2602
-rw-r--r--vendor/amqplib/client_0_8/connection.py826
-rw-r--r--vendor/amqplib/client_0_8/exceptions.py105
-rw-r--r--vendor/amqplib/client_0_8/method_framing.py244
-rw-r--r--vendor/amqplib/client_0_8/serialization.py530
-rw-r--r--vendor/amqplib/client_0_8/transport.py220
-rw-r--r--vendor/anyjson/__init__.py124
-rw-r--r--vendor/boto/README53
-rwxr-xr-xvendor/boto/bin/bundle_image27
-rw-r--r--vendor/boto/bin/cfadmin70
-rwxr-xr-xvendor/boto/bin/elbadmin179
-rwxr-xr-xvendor/boto/bin/fetch_file37
-rw-r--r--vendor/boto/bin/kill_instance12
-rwxr-xr-xvendor/boto/bin/launch_instance138
-rwxr-xr-xvendor/boto/bin/list_instances10
-rwxr-xr-xvendor/boto/bin/pyami_sendmail47
-rwxr-xr-xvendor/boto/bin/s3put196
-rwxr-xr-xvendor/boto/bin/sdbadmin168
-rwxr-xr-xvendor/boto/bin/taskadmin116
-rw-r--r--vendor/boto/boto/__init__.py292
-rw-r--r--vendor/boto/boto/cloudfront/__init__.py223
-rw-r--r--vendor/boto/boto/cloudfront/distribution.py470
-rw-r--r--vendor/boto/boto/cloudfront/exception.py26
-rw-r--r--vendor/boto/boto/cloudfront/identity.py122
-rw-r--r--vendor/boto/boto/cloudfront/logging.py38
-rw-r--r--vendor/boto/boto/cloudfront/object.py48
-rw-r--r--vendor/boto/boto/cloudfront/signers.py60
-rw-r--r--vendor/boto/boto/connection.py644
-rw-r--r--vendor/boto/boto/contrib/__init__.py22
-rw-r--r--vendor/boto/boto/contrib/m2helpers.py52
-rw-r--r--vendor/boto/boto/contrib/ymlmessage.py52
-rw-r--r--vendor/boto/boto/ec2/__init__.py52
-rw-r--r--vendor/boto/boto/ec2/address.py58
-rw-r--r--vendor/boto/boto/ec2/autoscale/__init__.py203
-rw-r--r--vendor/boto/boto/ec2/autoscale/activity.py55
-rw-r--r--vendor/boto/boto/ec2/autoscale/group.py189
-rw-r--r--vendor/boto/boto/ec2/autoscale/instance.py53
-rw-r--r--vendor/boto/boto/ec2/autoscale/launchconfig.py98
-rw-r--r--vendor/boto/boto/ec2/autoscale/request.py38
-rw-r--r--vendor/boto/boto/ec2/autoscale/trigger.py137
-rw-r--r--vendor/boto/boto/ec2/blockdevicemapping.py98
-rw-r--r--vendor/boto/boto/ec2/bundleinstance.py78
-rw-r--r--vendor/boto/boto/ec2/buyreservation.py81
-rw-r--r--vendor/boto/boto/ec2/cloudwatch/__init__.py213
-rw-r--r--vendor/boto/boto/ec2/cloudwatch/datapoint.py37
-rw-r--r--vendor/boto/boto/ec2/cloudwatch/metric.py71
-rw-r--r--vendor/boto/boto/ec2/connection.py1605
-rw-r--r--vendor/boto/boto/ec2/ec2object.py41
-rw-r--r--vendor/boto/boto/ec2/elb/__init__.py238
-rw-r--r--vendor/boto/boto/ec2/elb/healthcheck.py68
-rw-r--r--vendor/boto/boto/ec2/elb/instancestate.py54
-rw-r--r--vendor/boto/boto/ec2/elb/listelement.py31
-rw-r--r--vendor/boto/boto/ec2/elb/listener.py64
-rw-r--r--vendor/boto/boto/ec2/elb/loadbalancer.py142
-rw-r--r--vendor/boto/boto/ec2/image.py250
-rw-r--r--vendor/boto/boto/ec2/instance.py294
-rw-r--r--vendor/boto/boto/ec2/instanceinfo.py47
-rw-r--r--vendor/boto/boto/ec2/keypair.py111
-rw-r--r--vendor/boto/boto/ec2/launchspecification.py96
-rw-r--r--vendor/boto/boto/ec2/regioninfo.py60
-rw-r--r--vendor/boto/boto/ec2/reservedinstance.py97
-rw-r--r--vendor/boto/boto/ec2/securitygroup.py282
-rw-r--r--vendor/boto/boto/ec2/snapshot.py127
-rw-r--r--vendor/boto/boto/ec2/spotdatafeedsubscription.py63
-rw-r--r--vendor/boto/boto/ec2/spotinstancerequest.py109
-rw-r--r--vendor/boto/boto/ec2/spotpricehistory.py52
-rw-r--r--vendor/boto/boto/ec2/volume.py208
-rw-r--r--vendor/boto/boto/ec2/zone.py47
-rw-r--r--vendor/boto/boto/emr/__init__.py29
-rw-r--r--vendor/boto/boto/emr/connection.py236
-rw-r--r--vendor/boto/boto/emr/emrobject.py34
-rw-r--r--vendor/boto/boto/emr/jobflow.py89
-rw-r--r--vendor/boto/boto/emr/step.py168
-rw-r--r--vendor/boto/boto/exception.py293
-rw-r--r--vendor/boto/boto/fps/__init__.py23
-rw-r--r--vendor/boto/boto/fps/connection.py172
-rw-r--r--vendor/boto/boto/handler.py46
-rw-r--r--vendor/boto/boto/manage/__init__.py23
-rw-r--r--vendor/boto/boto/manage/cmdshell.py169
-rw-r--r--vendor/boto/boto/manage/propget.py64
-rw-r--r--vendor/boto/boto/manage/server.py548
-rw-r--r--vendor/boto/boto/manage/task.py175
-rw-r--r--vendor/boto/boto/manage/test_manage.py34
-rw-r--r--vendor/boto/boto/manage/volume.py420
-rw-r--r--vendor/boto/boto/mapreduce/__init__.py23
-rw-r--r--vendor/boto/boto/mapreduce/lqs.py152
-rw-r--r--vendor/boto/boto/mapreduce/partitiondb.py175
-rw-r--r--vendor/boto/boto/mapreduce/pdb_delete135
-rwxr-xr-xvendor/boto/boto/mapreduce/pdb_describe124
-rwxr-xr-xvendor/boto/boto/mapreduce/pdb_revert135
-rwxr-xr-xvendor/boto/boto/mapreduce/pdb_upload172
-rw-r--r--vendor/boto/boto/mapreduce/queuetools.py66
-rw-r--r--vendor/boto/boto/mashups/__init__.py23
-rw-r--r--vendor/boto/boto/mashups/interactive.py97
-rw-r--r--vendor/boto/boto/mashups/iobject.py115
-rw-r--r--vendor/boto/boto/mashups/order.py211
-rw-r--r--vendor/boto/boto/mashups/server.py395
-rw-r--r--vendor/boto/boto/mturk/__init__.py23
-rw-r--r--vendor/boto/boto/mturk/connection.py515
-rw-r--r--vendor/boto/boto/mturk/notification.py95
-rw-r--r--vendor/boto/boto/mturk/price.py48
-rw-r--r--vendor/boto/boto/mturk/qualification.py118
-rw-r--r--vendor/boto/boto/mturk/question.py336
-rw-r--r--vendor/boto/boto/mturk/test/all_tests.py8
-rw-r--r--vendor/boto/boto/mturk/test/cleanup_tests.py67
-rw-r--r--vendor/boto/boto/mturk/test/create_free_text_question_regex.doctest92
-rw-r--r--vendor/boto/boto/mturk/test/create_hit.doctest86
-rw-r--r--vendor/boto/boto/mturk/test/create_hit_binary.doctest87
-rw-r--r--vendor/boto/boto/mturk/test/create_hit_external.py14
-rw-r--r--vendor/boto/boto/mturk/test/create_hit_from_hit_type.doctest97
-rw-r--r--vendor/boto/boto/mturk/test/create_hit_with_qualifications.py18
-rw-r--r--vendor/boto/boto/mturk/test/reviewable_hits.doctest71
-rw-r--r--vendor/boto/boto/mturk/test/search_hits.doctest16
-rw-r--r--vendor/boto/boto/pyami/__init__.py22
-rw-r--r--vendor/boto/boto/pyami/bootstrap.py121
-rw-r--r--vendor/boto/boto/pyami/config.py203
-rw-r--r--vendor/boto/boto/pyami/copybot.cfg60
-rw-r--r--vendor/boto/boto/pyami/copybot.py97
-rw-r--r--vendor/boto/boto/pyami/helloworld.py28
-rw-r--r--vendor/boto/boto/pyami/installers/__init__.py64
-rw-r--r--vendor/boto/boto/pyami/installers/ubuntu/__init__.py22
-rw-r--r--vendor/boto/boto/pyami/installers/ubuntu/apache.py43
-rw-r--r--vendor/boto/boto/pyami/installers/ubuntu/ebs.py206
-rw-r--r--vendor/boto/boto/pyami/installers/ubuntu/installer.py96
-rw-r--r--vendor/boto/boto/pyami/installers/ubuntu/mysql.py109
-rw-r--r--vendor/boto/boto/pyami/installers/ubuntu/trac.py139
-rwxr-xr-xvendor/boto/boto/pyami/launch_ami.py178
-rw-r--r--vendor/boto/boto/pyami/scriptbase.py44
-rw-r--r--vendor/boto/boto/pyami/startup.py59
-rw-r--r--vendor/boto/boto/rds/__init__.py810
-rw-r--r--vendor/boto/boto/rds/dbinstance.py136
-rw-r--r--vendor/boto/boto/rds/dbsecuritygroup.py159
-rw-r--r--vendor/boto/boto/rds/dbsnapshot.py74
-rw-r--r--vendor/boto/boto/rds/event.py49
-rw-r--r--vendor/boto/boto/rds/parametergroup.py201
-rw-r--r--vendor/boto/boto/resultset.py136
-rw-r--r--vendor/boto/boto/s3/__init__.py31
-rw-r--r--vendor/boto/boto/s3/acl.py162
-rw-r--r--vendor/boto/boto/s3/bucket.py721
-rw-r--r--vendor/boto/boto/s3/bucketlistresultset.py99
-rw-r--r--vendor/boto/boto/s3/connection.py350
-rw-r--r--vendor/boto/boto/s3/deletemarker.py56
-rw-r--r--vendor/boto/boto/s3/key.py804
-rw-r--r--vendor/boto/boto/s3/prefix.py35
-rw-r--r--vendor/boto/boto/s3/user.py49
-rw-r--r--vendor/boto/boto/sdb/__init__.py41
-rw-r--r--vendor/boto/boto/sdb/connection.py441
-rw-r--r--vendor/boto/boto/sdb/db/__init__.py21
-rw-r--r--vendor/boto/boto/sdb/db/blob.py64
-rw-r--r--vendor/boto/boto/sdb/db/key.py59
-rw-r--r--vendor/boto/boto/sdb/db/manager/__init__.py88
-rw-r--r--vendor/boto/boto/sdb/db/manager/pgmanager.py389
-rw-r--r--vendor/boto/boto/sdb/db/manager/sdbmanager.py599
-rw-r--r--vendor/boto/boto/sdb/db/manager/xmlmanager.py517
-rw-r--r--vendor/boto/boto/sdb/db/model.py234
-rw-r--r--vendor/boto/boto/sdb/db/property.py556
-rw-r--r--vendor/boto/boto/sdb/db/query.py79
-rw-r--r--vendor/boto/boto/sdb/db/sequence.py224
-rw-r--r--vendor/boto/boto/sdb/db/test_db.py225
-rw-r--r--vendor/boto/boto/sdb/domain.py337
-rw-r--r--vendor/boto/boto/sdb/item.py105
-rw-r--r--vendor/boto/boto/sdb/persist/__init__.py83
-rw-r--r--vendor/boto/boto/sdb/persist/checker.py302
-rw-r--r--vendor/boto/boto/sdb/persist/object.py207
-rw-r--r--vendor/boto/boto/sdb/persist/property.py371
-rw-r--r--vendor/boto/boto/sdb/persist/test_persist.py141
-rw-r--r--vendor/boto/boto/sdb/queryresultset.py92
-rw-r--r--vendor/boto/boto/sdb/regioninfo.py40
-rw-r--r--vendor/boto/boto/services/__init__.py23
-rwxr-xr-xvendor/boto/boto/services/bs.py179
-rw-r--r--vendor/boto/boto/services/message.py58
-rw-r--r--vendor/boto/boto/services/result.py137
-rw-r--r--vendor/boto/boto/services/service.py161
-rw-r--r--vendor/boto/boto/services/servicedef.py91
-rw-r--r--vendor/boto/boto/services/sonofmmm.cfg43
-rw-r--r--vendor/boto/boto/services/sonofmmm.py81
-rw-r--r--vendor/boto/boto/services/submit.py88
-rw-r--r--vendor/boto/boto/sns/__init__.py353
-rw-r--r--vendor/boto/boto/sqs/__init__.py42
-rw-r--r--vendor/boto/boto/sqs/attributes.py46
-rw-r--r--vendor/boto/boto/sqs/connection.py286
-rw-r--r--vendor/boto/boto/sqs/jsonmessage.py42
-rw-r--r--vendor/boto/boto/sqs/message.py251
-rw-r--r--vendor/boto/boto/sqs/queue.py414
-rw-r--r--vendor/boto/boto/sqs/regioninfo.py40
-rw-r--r--vendor/boto/boto/tests/__init__.py23
-rw-r--r--vendor/boto/boto/tests/devpay_s3.py177
-rwxr-xr-xvendor/boto/boto/tests/test.py85
-rw-r--r--vendor/boto/boto/tests/test_ec2connection.py154
-rw-r--r--vendor/boto/boto/tests/test_s3connection.py175
-rw-r--r--vendor/boto/boto/tests/test_s3versioning.py143
-rw-r--r--vendor/boto/boto/tests/test_sdbconnection.py104
-rw-r--r--vendor/boto/boto/tests/test_sqsconnection.py142
-rw-r--r--vendor/boto/boto/utils.py561
-rw-r--r--vendor/boto/boto/vpc/__init__.py473
-rw-r--r--vendor/boto/boto/vpc/customergateway.py54
-rw-r--r--vendor/boto/boto/vpc/dhcpoptions.py69
-rw-r--r--vendor/boto/boto/vpc/subnet.py54
-rw-r--r--vendor/boto/boto/vpc/vpc.py54
-rw-r--r--vendor/boto/boto/vpc/vpnconnection.py60
-rw-r--r--vendor/boto/boto/vpc/vpngateway.py80
-rwxr-xr-xvendor/boto/cq.py82
-rw-r--r--vendor/boto/docs/Makefile89
-rw-r--r--vendor/boto/docs/make.bat113
-rw-r--r--vendor/boto/docs/source/_templates/layout.html3
-rw-r--r--vendor/boto/docs/source/autoscale_tut.rst140
-rw-r--r--vendor/boto/docs/source/boto_theme/static/boto.css_t239
-rw-r--r--vendor/boto/docs/source/boto_theme/static/pygments.css61
-rw-r--r--vendor/boto/docs/source/boto_theme/theme.conf3
-rw-r--r--vendor/boto/docs/source/conf.py30
-rw-r--r--vendor/boto/docs/source/documentation.rst59
-rw-r--r--vendor/boto/docs/source/ec2_tut.rst420
-rw-r--r--vendor/boto/docs/source/elb_tut.rst202
-rw-r--r--vendor/boto/docs/source/index.rst52
-rw-r--r--vendor/boto/docs/source/ref/boto.rst47
-rw-r--r--vendor/boto/docs/source/ref/cloudfront.rst108
-rw-r--r--vendor/boto/docs/source/ref/contrib.rst32
-rw-r--r--vendor/boto/docs/source/ref/ec2.rst223
-rw-r--r--vendor/boto/docs/source/ref/fps.rst19
-rw-r--r--vendor/boto/docs/source/ref/index.rst25
-rw-r--r--vendor/boto/docs/source/ref/manage.rst47
-rw-r--r--vendor/boto/docs/source/ref/mapreduce.rst38
-rw-r--r--vendor/boto/docs/source/ref/mashups.rst40
-rw-r--r--vendor/boto/docs/source/ref/mturk.rst47
-rw-r--r--vendor/boto/docs/source/ref/pyami.rst103
-rw-r--r--vendor/boto/docs/source/ref/rds.rst47
-rw-r--r--vendor/boto/docs/source/ref/s3.rst54
-rw-r--r--vendor/boto/docs/source/ref/sdb.rst144
-rw-r--r--vendor/boto/docs/source/ref/services.rst61
-rw-r--r--vendor/boto/docs/source/ref/sqs.rst54
-rw-r--r--vendor/boto/docs/source/ref/vpc.rst54
-rw-r--r--vendor/boto/docs/source/s3_tut.rst213
-rw-r--r--vendor/boto/docs/source/sqs_tut.rst230
-rw-r--r--vendor/boto/docs/source/vpc_tut.rst88
-rw-r--r--vendor/boto/pylintrc305
-rw-r--r--vendor/boto/setup.py56
-rw-r--r--vendor/carrot/__init__.py7
-rw-r--r--vendor/carrot/backends/__init__.py54
-rw-r--r--vendor/carrot/backends/base.py185
-rw-r--r--vendor/carrot/backends/pikachu.py209
-rw-r--r--vendor/carrot/backends/pyamqplib.py328
-rw-r--r--vendor/carrot/backends/pystomp.py192
-rw-r--r--vendor/carrot/backends/queue.py76
-rw-r--r--vendor/carrot/connection.py229
-rw-r--r--vendor/carrot/messaging.py981
-rw-r--r--vendor/carrot/serialization.py253
-rw-r--r--vendor/carrot/utils.py56
-rw-r--r--vendor/lockfile/2.4.diff99
-rw-r--r--vendor/lockfile/ACKS6
-rw-r--r--vendor/lockfile/LICENSE21
-rw-r--r--vendor/lockfile/MANIFEST19
-rw-r--r--vendor/lockfile/PKG-INFO47
-rw-r--r--vendor/lockfile/README23
-rw-r--r--vendor/lockfile/RELEASE-NOTES42
-rw-r--r--vendor/lockfile/doc/Makefile73
-rw-r--r--vendor/lockfile/doc/conf.py179
-rw-r--r--vendor/lockfile/doc/glossary.rst15
-rw-r--r--vendor/lockfile/doc/index.rst22
-rw-r--r--vendor/lockfile/doc/lockfile.rst257
-rw-r--r--vendor/lockfile/lockfile/__init__.py286
-rw-r--r--vendor/lockfile/lockfile/linklockfile.py71
-rw-r--r--vendor/lockfile/lockfile/mkdirlockfile.py79
-rw-r--r--vendor/lockfile/lockfile/pidlockfile.py181
-rw-r--r--vendor/lockfile/lockfile/sqlitelockfile.py142
-rw-r--r--vendor/lockfile/setup.py32
-rw-r--r--vendor/lockfile/test/compliancetest.py228
-rw-r--r--vendor/lockfile/test/test_lockfile.py30
-rw-r--r--vendor/pymox/COPYING202
-rw-r--r--vendor/pymox/MANIFEST.in5
-rw-r--r--vendor/pymox/README56
-rwxr-xr-xvendor/pymox/mox.py1729
-rwxr-xr-xvendor/pymox/mox_test.py1853
-rwxr-xr-xvendor/pymox/mox_test_helper.py95
-rwxr-xr-xvendor/pymox/setup.py14
-rw-r--r--vendor/pymox/stubout.py142
-rw-r--r--vendor/pymox/stubout_test.py47
-rw-r--r--vendor/pymox/stubout_testee.py2
-rw-r--r--vendor/python-daemon/ChangeLog187
-rw-r--r--vendor/python-daemon/LICENSE.GPL-2339
-rw-r--r--vendor/python-daemon/LICENSE.PSF-248
-rw-r--r--vendor/python-daemon/MANIFEST.in4
-rw-r--r--vendor/python-daemon/PKG-INFO37
-rw-r--r--vendor/python-daemon/README.nova4
-rw-r--r--vendor/python-daemon/daemon/__init__.py47
-rw-r--r--vendor/python-daemon/daemon/daemon.py776
-rw-r--r--vendor/python-daemon/daemon/pidlockfile.py195
-rw-r--r--vendor/python-daemon/daemon/runner.py229
-rw-r--r--vendor/python-daemon/daemon/version/__init__.py36
-rw-r--r--vendor/python-daemon/daemon/version/version_info.py23
-rw-r--r--vendor/python-daemon/python_daemon.egg-info/PKG-INFO37
-rw-r--r--vendor/python-daemon/python_daemon.egg-info/SOURCES.txt22
-rw-r--r--vendor/python-daemon/python_daemon.egg-info/dependency_links.txt1
-rw-r--r--vendor/python-daemon/python_daemon.egg-info/not-zip-safe1
-rw-r--r--vendor/python-daemon/python_daemon.egg-info/requires.txt2
-rw-r--r--vendor/python-daemon/python_daemon.egg-info/top_level.txt1
-rw-r--r--vendor/python-daemon/setup.cfg5
-rw-r--r--vendor/python-daemon/setup.py64
-rw-r--r--vendor/python-gflags/AUTHORS2
-rw-r--r--vendor/python-gflags/COPYING28
-rw-r--r--vendor/python-gflags/ChangeLog5
-rw-r--r--vendor/python-gflags/README23
-rw-r--r--vendor/python-gflags/debian/README7
-rw-r--r--vendor/python-gflags/debian/changelog11
-rw-r--r--vendor/python-gflags/debian/compat1
-rw-r--r--vendor/python-gflags/debian/control26
-rw-r--r--vendor/python-gflags/debian/copyright41
-rw-r--r--vendor/python-gflags/debian/docs2
-rwxr-xr-xvendor/python-gflags/debian/rules62
-rw-r--r--vendor/python-gflags/gflags.py2340
-rwxr-xr-xvendor/python-gflags/gflags2man.py536
-rwxr-xr-xvendor/python-gflags/gflags_helpxml_test.py563
-rwxr-xr-xvendor/python-gflags/gflags_unittest.py1679
-rwxr-xr-xvendor/python-gflags/setup.py44
-rwxr-xr-xvendor/python-gflags/test_module_bar.py135
-rwxr-xr-xvendor/python-gflags/test_module_foo.py120
-rwxr-xr-xvendor/redis-py/.gitignore5
-rwxr-xr-xvendor/redis-py/CHANGES58
-rwxr-xr-xvendor/redis-py/INSTALL6
-rwxr-xr-xvendor/redis-py/LICENSE22
-rwxr-xr-xvendor/redis-py/MANIFEST.in4
-rwxr-xr-xvendor/redis-py/README.md33
-rwxr-xr-xvendor/redis-py/redis/__init__.py10
-rwxr-xr-xvendor/redis-py/redis/client.py1259
-rwxr-xr-xvendor/redis-py/redis/exceptions.py20
-rwxr-xr-xvendor/redis-py/setup.py42
-rwxr-xr-xvendor/redis-py/tests/__init__.py11
-rwxr-xr-xvendor/redis-py/tests/connection_pool.py53
-rwxr-xr-xvendor/redis-py/tests/pipeline.py61
-rwxr-xr-xvendor/redis-py/tests/server_commands.py1092
-rw-r--r--vendor/tornado/MANIFEST.in2
-rw-r--r--vendor/tornado/README27
-rw-r--r--vendor/tornado/demos/appengine/README48
-rw-r--r--vendor/tornado/demos/appengine/app.yaml11
-rw-r--r--vendor/tornado/demos/appengine/blog.py169
-rw-r--r--vendor/tornado/demos/appengine/markdown.py1877
-rw-r--r--vendor/tornado/demos/appengine/static/blog.css153
-rw-r--r--vendor/tornado/demos/appengine/templates/archive.html31
-rw-r--r--vendor/tornado/demos/appengine/templates/base.html29
-rw-r--r--vendor/tornado/demos/appengine/templates/compose.html42
-rw-r--r--vendor/tornado/demos/appengine/templates/entry.html5
-rw-r--r--vendor/tornado/demos/appengine/templates/feed.xml26
-rw-r--r--vendor/tornado/demos/appengine/templates/home.html8
-rw-r--r--vendor/tornado/demos/appengine/templates/modules/entry.html8
-rwxr-xr-xvendor/tornado/demos/auth/authdemo.py79
-rw-r--r--vendor/tornado/demos/blog/README57
-rwxr-xr-xvendor/tornado/demos/blog/blog.py195
-rw-r--r--vendor/tornado/demos/blog/markdown.py1877
-rw-r--r--vendor/tornado/demos/blog/schema.sql44
-rw-r--r--vendor/tornado/demos/blog/static/blog.css153
-rw-r--r--vendor/tornado/demos/blog/templates/archive.html31
-rw-r--r--vendor/tornado/demos/blog/templates/base.html27
-rw-r--r--vendor/tornado/demos/blog/templates/compose.html42
-rw-r--r--vendor/tornado/demos/blog/templates/entry.html5
-rw-r--r--vendor/tornado/demos/blog/templates/feed.xml26
-rw-r--r--vendor/tornado/demos/blog/templates/home.html8
-rw-r--r--vendor/tornado/demos/blog/templates/modules/entry.html8
-rwxr-xr-xvendor/tornado/demos/chat/chatdemo.py156
-rw-r--r--vendor/tornado/demos/chat/static/chat.css56
-rw-r--r--vendor/tornado/demos/chat/static/chat.js135
-rw-r--r--vendor/tornado/demos/chat/templates/index.html37
-rw-r--r--vendor/tornado/demos/chat/templates/message.html1
-rw-r--r--vendor/tornado/demos/facebook/README8
-rwxr-xr-xvendor/tornado/demos/facebook/facebook.py127
-rw-r--r--vendor/tornado/demos/facebook/static/facebook.css97
-rw-r--r--vendor/tornado/demos/facebook/static/facebook.js0
-rw-r--r--vendor/tornado/demos/facebook/templates/modules/post.html29
-rw-r--r--vendor/tornado/demos/facebook/templates/stream.html22
-rw-r--r--vendor/tornado/demos/facebook/uimodules.py22
-rwxr-xr-xvendor/tornado/demos/helloworld/helloworld.py43
-rw-r--r--vendor/tornado/setup.py44
-rw-r--r--vendor/tornado/tornado/__init__.py17
-rw-r--r--vendor/tornado/tornado/auth.py883
-rw-r--r--vendor/tornado/tornado/autoreload.py95
-rw-r--r--vendor/tornado/tornado/database.py180
-rw-r--r--vendor/tornado/tornado/epoll.c112
-rw-r--r--vendor/tornado/tornado/escape.py112
-rw-r--r--vendor/tornado/tornado/httpclient.py465
-rw-r--r--vendor/tornado/tornado/httpserver.py450
-rw-r--r--vendor/tornado/tornado/ioloop.py483
-rw-r--r--vendor/tornado/tornado/iostream.py229
-rw-r--r--vendor/tornado/tornado/locale.py457
-rw-r--r--vendor/tornado/tornado/options.py386
-rw-r--r--vendor/tornado/tornado/s3server.py255
-rw-r--r--vendor/tornado/tornado/template.py576
-rw-r--r--vendor/tornado/tornado/test/README4
-rwxr-xr-xvendor/tornado/tornado/test/test_ioloop.py38
-rw-r--r--vendor/tornado/tornado/web.py1445
-rw-r--r--vendor/tornado/tornado/websocket.py138
-rw-r--r--vendor/tornado/tornado/win32_support.py123
-rw-r--r--vendor/tornado/tornado/wsgi.py311
-rw-r--r--vendor/tornado/website/app.yaml15
-rw-r--r--vendor/tornado/website/index.yaml0
-rw-r--r--vendor/tornado/website/markdown/__init__.py603
-rw-r--r--vendor/tornado/website/markdown/blockparser.py95
-rw-r--r--vendor/tornado/website/markdown/blockprocessors.py460
-rw-r--r--vendor/tornado/website/markdown/commandline.py96
-rw-r--r--vendor/tornado/website/markdown/etree_loader.py33
-rw-r--r--vendor/tornado/website/markdown/extensions/__init__.py0
-rw-r--r--vendor/tornado/website/markdown/extensions/toc.py140
-rw-r--r--vendor/tornado/website/markdown/html4.py274
-rw-r--r--vendor/tornado/website/markdown/inlinepatterns.py371
-rw-r--r--vendor/tornado/website/markdown/odict.py162
-rw-r--r--vendor/tornado/website/markdown/postprocessors.py77
-rw-r--r--vendor/tornado/website/markdown/preprocessors.py214
-rw-r--r--vendor/tornado/website/markdown/treeprocessors.py329
-rw-r--r--vendor/tornado/website/static/base.css120
-rwxr-xr-xvendor/tornado/website/static/facebook.pngbin0 -> 7457 bytes
-rwxr-xr-xvendor/tornado/website/static/friendfeed.pngbin0 -> 7906 bytes
-rw-r--r--vendor/tornado/website/static/robots.txt2
-rw-r--r--vendor/tornado/website/static/tornado-0.1.tar.gzbin0 -> 106878 bytes
-rw-r--r--vendor/tornado/website/static/tornado-0.2.tar.gzbin0 -> 200680 bytes
-rw-r--r--vendor/tornado/website/static/tornado.pngbin0 -> 7101 bytes
-rwxr-xr-xvendor/tornado/website/static/twitter.pngbin0 -> 7197 bytes
-rw-r--r--vendor/tornado/website/templates/base.html27
-rw-r--r--vendor/tornado/website/templates/documentation.html9
-rw-r--r--vendor/tornado/website/templates/documentation.txt866
-rw-r--r--vendor/tornado/website/templates/index.html51
-rw-r--r--vendor/tornado/website/website.py63
1877 files changed, 430845 insertions, 0 deletions
diff --git a/CA/.gitignore b/CA/.gitignore
new file mode 100644
index 0000000000..fae0922bf9
--- /dev/null
+++ b/CA/.gitignore
@@ -0,0 +1,11 @@
+index.txt
+index.txt.old
+index.txt.attr
+index.txt.attr.old
+cacert.pem
+serial
+serial.old
+openssl.cnf
+private/*
+newcerts/*
+
diff --git a/CA/INTER/.gitignore b/CA/INTER/.gitignore
new file mode 100644
index 0000000000..72e8ffc0db
--- /dev/null
+++ b/CA/INTER/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/CA/geninter.sh b/CA/geninter.sh
new file mode 100755
index 0000000000..ad3332ad92
--- /dev/null
+++ b/CA/geninter.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# ARG is the id of the user
+
+mkdir INTER/$1
+cd INTER/$1
+cp ../../openssl.cnf.tmpl openssl.cnf
+sed -i -e s/%USERNAME%/$1/g openssl.cnf
+mkdir certs crl newcerts private
+echo "10" > serial
+touch index.txt
+openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes
+openssl req -new -sha1 -key private/cakey.pem -out ../../reqs/inter$1.csr -batch -subj "/C=US/ST=California/L=Mountain View/O=Anso Labs/OU=Nova Dev/CN=customer-intCA-$1"
+cd ../../
+openssl ca -extensions v3_ca -days 365 -out INTER/$1/cacert.pem -in reqs/inter$1.csr -config openssl.cnf -batch \ No newline at end of file
diff --git a/CA/genrootca.sh b/CA/genrootca.sh
new file mode 100755
index 0000000000..e21f48d77a
--- /dev/null
+++ b/CA/genrootca.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+if [ -f "cacert.pem" ];
+then
+ echo "Not installing, it's already done."
+else
+ cp openssl.cnf.tmpl openssl.cnf
+ sed -i -e s/%USERNAME%/ROOT/g openssl.cnf
+ openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
+ touch index.txt
+ echo "10" > serial
+fi
diff --git a/CA/newcerts/.placeholder b/CA/newcerts/.placeholder
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/CA/newcerts/.placeholder
diff --git a/CA/openssl.cnf.tmpl b/CA/openssl.cnf.tmpl
new file mode 100644
index 0000000000..b06f1cca0b
--- /dev/null
+++ b/CA/openssl.cnf.tmpl
@@ -0,0 +1,87 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# OpenSSL configuration file.
+#
+
+# Establish working directory.
+
+dir = .
+
+[ ca ]
+default_ca = CA_default
+unique_subject = no
+
+[ CA_default ]
+serial = $dir/serial
+database = $dir/index.txt
+new_certs_dir = $dir/newcerts
+certificate = $dir/cacert.pem
+private_key = $dir/private/cakey.pem
+default_days = 365
+default_md = md5
+preserve = no
+email_in_dn = no
+nameopt = default_ca
+certopt = default_ca
+policy = policy_match
+
+[ policy_match ]
+countryName = match
+stateOrProvinceName = match
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+
+[ req ]
+default_bits = 1024 # Size of keys
+default_keyfile = key.pem # name of generated keys
+default_md = md5 # message digest algorithm
+string_mask = nombstr # permitted characters
+distinguished_name = req_distinguished_name
+
+[ req_distinguished_name ]
+# Variable name Prompt string
+#---------------------- ----------------------------------
+0.organizationName = Organization Name (company)
+organizationalUnitName = Organizational Unit Name (department, division)
+emailAddress = Email Address
+emailAddress_max = 40
+localityName = Locality Name (city, district)
+stateOrProvinceName = State or Province Name (full name)
+countryName = Country Name (2 letter code)
+countryName_min = 2
+countryName_max = 2
+commonName = Common Name (hostname, IP, or your name)
+commonName_max = 64
+
+# Default values for the above, for consistency and less typing.
+# Variable name Value
+#------------------------------ ------------------------------
+0.organizationName_default = NOVA %USERNAME%
+localityName_default = Mountain View
+stateOrProvinceName_default = California
+countryName_default = US
+
+[ v3_ca ]
+basicConstraints = CA:TRUE
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+
+[ v3_req ]
+basicConstraints = CA:FALSE
+subjectKeyIdentifier = hash
diff --git a/CA/private/.placeholder b/CA/private/.placeholder
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/CA/private/.placeholder
diff --git a/CA/reqs/.gitignore b/CA/reqs/.gitignore
new file mode 100644
index 0000000000..72e8ffc0db
--- /dev/null
+++ b/CA/reqs/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/HACKING b/HACKING
new file mode 100644
index 0000000000..77e42b8e6f
--- /dev/null
+++ b/HACKING
@@ -0,0 +1,53 @@
+Nova Style Commandments
+=======================
+
+Step 1: Read http://www.python.org/dev/peps/pep-0008/
+Step 2: Read http://www.python.org/dev/peps/pep-0008/ again
+Step 3: Read on
+
+Imports
+-------
+- thou shalt not import objects, only modules
+- thou shalt not import more than one module per line
+- thou shalt not make relative imports
+- thou shalt "from nova import vendor" before importing third party code
+- thou shalt organize your imports according to the following template
+
+::
+ # vim: tabstop=4 shiftwidth=4 softtabstop=4
+ {{stdlib imports in human alphabetical order}}
+ \n
+ from nova import vendor
+ {{vendor imports in human alphabetical order}}
+ \n
+ {{nova imports in human alphabetical order}}
+ \n
+ \n
+ {{begin your code}}
+
+
+General
+-------
+- thou shalt put two newlines twixt toplevel code (funcs, classes, etc)
+- thou shalt put one newline twixt methods in classes and anywhere else
+- thou shalt not write "except:", use "except Exception:" at the very least
+- thou shalt include your name with TODOs as in "TODO(termie)"
+- thou shalt not name anything the same name as a builtin or reserved word
+- thou shalt not violate causality in our time cone, or else
+
+
+Human Alphabetical Order Examples
+---------------------------------
+::
+ import httplib
+ import logging
+ import random
+ import StringIO
+ import time
+ import unittest
+
+ from nova import flags
+ from nova import test
+ from nova.auth import users
+ from nova.endpoint import api
+ from nova.endpoint import cloud
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000..68c771a099
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,176 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
diff --git a/bin/nova-api b/bin/nova-api
new file mode 100755
index 0000000000..8fea1da4d5
--- /dev/null
+++ b/bin/nova-api
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+ Tornado daemon for the main API endpoint.
+"""
+
+import logging
+
+from nova import vendor
+from tornado import httpserver
+from tornado import ioloop
+
+from nova import flags
+from nova import rpc
+from nova import server
+from nova import utils
+from nova.auth import users
+from nova.endpoint import admin
+from nova.endpoint import api
+from nova.endpoint import cloud
+
+FLAGS = flags.FLAGS
+
+
+def main(_argv):
+ user_manager = users.UserManager()
+ controllers = {
+ 'Cloud': cloud.CloudController(),
+ 'Admin': admin.AdminController(user_manager)
+ }
+ _app = api.APIServerApplication(user_manager, controllers)
+
+ conn = rpc.Connection.instance()
+ consumer = rpc.AdapterConsumer(connection=conn,
+ topic=FLAGS.cloud_topic,
+ proxy=controllers['Cloud'])
+
+ io_inst = ioloop.IOLoop.instance()
+ _injected = consumer.attach_to_tornado(io_inst)
+
+ http_server = httpserver.HTTPServer(_app)
+ http_server.listen(FLAGS.cc_port)
+ logging.debug('Started HTTP server on %s', FLAGS.cc_port)
+ io_inst.start()
+
+
+if __name__ == '__main__':
+ utils.default_flagfile()
+ server.serve('nova-api', main)
diff --git a/bin/nova-compute b/bin/nova-compute
new file mode 100755
index 0000000000..bd3648d206
--- /dev/null
+++ b/bin/nova-compute
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+ Twistd daemon for the nova compute nodes.
+ Receives messages via AMQP, manages pool of worker threads
+ for async tasks.
+"""
+
+import logging
+import os
+import sys
+
+# NOTE(termie): kludge so that we can run this from the bin directory in the
+# checkout without having to screw with paths
+NOVA_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'nova')
+if os.path.exists(NOVA_PATH):
+ sys.path.insert(0, os.path.dirname(NOVA_PATH))
+
+from nova import vendor
+from carrot import connection
+from carrot import messaging
+from twisted.internet import task
+from twisted.application import service
+
+from nova import flags
+from nova import rpc
+from nova import twistd
+from nova.compute import node
+
+
+FLAGS = flags.FLAGS
+# NOTE(termie): This file will necessarily be re-imported under different
+# context when the twistd.serve() call is made below so any
+# flags we define here will have to be conditionally defined,
+# flags defined by imported modules are safe.
+if 'node_report_state_interval' not in FLAGS:
+ flags.DEFINE_integer('node_report_state_interval', 10,
+ 'seconds between nodes reporting state to cloud',
+ lower_bound=1)
+logging.getLogger().setLevel(logging.DEBUG)
+
+def main():
+ logging.warn('Starting compute node')
+ n = node.NetworkNode()
+ d = n.adopt_instances()
+ d.addCallback(lambda x: logging.info('Adopted %d instances', x))
+
+ conn = rpc.Connection.instance()
+ consumer_all = rpc.AdapterConsumer(
+ connection=conn,
+ topic='%s' % FLAGS.compute_topic,
+ proxy=n)
+
+ consumer_node = rpc.AdapterConsumer(
+ connection=conn,
+ topic='%s.%s' % (FLAGS.compute_topic, FLAGS.node_name),
+ proxy=n)
+
+ # heartbeat = task.LoopingCall(n.report_state)
+ # heartbeat.start(interval=FLAGS.node_report_state_interval, now=False)
+
+ injected = consumer_all.attach_to_twisted()
+ injected = consumer_node.attach_to_twisted()
+
+ # This is the parent service that twistd will be looking for when it
+ # parses this file, return it so that we can get it into globals below
+ application = service.Application('nova-compute')
+ n.setServiceParent(application)
+ return application
+
+
+# NOTE(termie): When this script is executed from the commandline what it will
+# actually do is tell the twistd application runner that it
+# should run this file as a twistd application (see below).
+if __name__ == '__main__':
+ twistd.serve(__file__)
+
+# NOTE(termie): When this script is loaded by the twistd application runner
+# this code path will be executed and twistd will expect a
+# variable named 'application' to be available, it will then
+# handle starting it and stopping it.
+if __name__ == '__builtin__':
+ application = main()
diff --git a/bin/nova-manage b/bin/nova-manage
new file mode 100755
index 0000000000..d2108626b9
--- /dev/null
+++ b/bin/nova-manage
@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+ CLI interface for nova management.
+ Connects to the running ADMIN api in the api daemon.
+"""
+
+import sys
+
+from nova import flags
+from nova import utils
+from nova.auth import users
+from nova.compute import model
+from nova.endpoint import cloud
+import time
+
+FLAGS = flags.FLAGS
+
+
+class UserCommands(object):
+ def __init__(self):
+ self.manager = users.UserManager.instance()
+
+ def __print_export(self, user):
+ print 'export EC2_ACCESS_KEY=%s' % user.access
+ print 'export EC2_SECRET_KEY=%s' % user.secret
+
+ def admin(self, name, access=None, secret=None):
+ """creates a new admin and prints exports
+ arguments: name [access] [secret]"""
+ user = self.manager.create_user(name, access, secret, True)
+ self.__print_export(user)
+
+ def create(self, name, access=None, secret=None):
+ """creates a new user and prints exports
+ arguments: name [access] [secret]"""
+ user = self.manager.create_user(name, access, secret, False)
+ self.__print_export(user)
+
+ def delete(self, name):
+ """deletes an existing user
+ arguments: name"""
+ self.manager.delete_user(name)
+
+ def exports(self, name):
+ """prints access and secrets for user in export format
+ arguments: name"""
+ user = self.manager.get_user(name)
+ if user:
+ self.__print_export(user)
+ else:
+ print "User %s doesn't exist" % name
+
+ def list(self):
+ """lists all users
+ arguments: <none>"""
+ for user in self.manager.get_users():
+ print user.name
+
+ def zip(self, name, filename='nova.zip'):
+ """exports credentials for user to a zip file
+ arguments: name [filename='nova.zip]"""
+ user = self.manager.get_user(name)
+ if user:
+ with open(filename, 'w') as f:
+ f.write(user.get_credentials())
+ else:
+ print "User %s doesn't exist" % name
+
+
+def usage(script_name):
+ print script_name + " category action [<args>]"
+
+
+categories = [
+ ('user', UserCommands),
+]
+
+
+def lazy_match(name, key_value_tuples):
+ """finds all objects that have a key that case insensitively contains [name]
+ key_value_tuples is a list of tuples of the form (key, value)
+ returns a list of tuples of the form (key, value)"""
+ return [(k, v) for (k, v) in key_value_tuples if k.lower().find(name.lower()) == 0]
+
+
+def methods_of(obj):
+ """get all callable methods of an object that don't start with underscore
+ returns a list of tuples of the form (method_name, method)"""
+ return [(i, getattr(obj, i)) for i in dir(obj) if callable(getattr(obj, i)) and not i.startswith('_')]
+
+
+if __name__ == '__main__':
+ utils.default_flagfile()
+ argv = FLAGS(sys.argv)
+ script_name = argv.pop(0)
+ if len(argv) < 1:
+ usage(script_name)
+ print "Available categories:"
+ for k, v in categories:
+ print "\t%s" % k
+ sys.exit(2)
+ category = argv.pop(0)
+ matches = lazy_match(category, categories)
+ if len(matches) == 0:
+ print "%s does not match any categories:" % category
+ for k, v in categories:
+ print "\t%s" % k
+ sys.exit(2)
+ if len(matches) > 1:
+ print "%s matched multiple categories:" % category
+ for k, v in matches:
+ print "\t%s" % k
+ sys.exit(2)
+ # instantiate the command group object
+ category, fn = matches[0]
+ command_object = fn()
+ actions = methods_of(command_object)
+ if len(argv) < 1:
+ usage(script_name)
+ print "Available actions for %s category:" % category
+ for k, v in actions:
+ print "\t%s" % k
+ sys.exit(2)
+ action = argv.pop(0)
+ matches = lazy_match(action, actions)
+ if len(matches) == 0:
+ print "%s does not match any actions" % action
+ for k, v in actions:
+ print "\t%s" % k
+ sys.exit(2)
+ if len(matches) > 1:
+ print "%s matched multiple actions:" % action
+ for k, v in matches:
+ print "\t%s" % k
+ sys.exit(2)
+ action, fn = matches[0]
+ # call the action with the remaining arguments
+ try:
+ fn(*argv)
+ except TypeError:
+ print "Wrong number of arguments supplied"
+ print "%s %s: %s" % (category, action, fn.__doc__)
+
diff --git a/bin/nova-objectstore b/bin/nova-objectstore
new file mode 100755
index 0000000000..38a23f1ffd
--- /dev/null
+++ b/bin/nova-objectstore
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+ Tornado daemon for nova objectstore. Supports S3 API.
+"""
+
+import logging
+
+from nova import vendor
+from tornado import httpserver
+from tornado import ioloop
+
+from nova import flags
+from nova import server
+from nova import utils
+from nova.auth import users
+from nova.objectstore import handler
+
+
+FLAGS = flags.FLAGS
+
+
+def main(argv):
+ # FIXME: if this log statement isn't here, no logging
+ # appears from other files and app won't start daemonized
+ logging.debug('Started HTTP server on %s' % (FLAGS.s3_internal_port))
+ app = handler.Application(users.UserManager())
+ server = httpserver.HTTPServer(app)
+ server.listen(FLAGS.s3_internal_port)
+ ioloop.IOLoop.instance().start()
+
+
+if __name__ == '__main__':
+ utils.default_flagfile()
+ server.serve('nova-objectstore', main)
diff --git a/bin/nova-volume b/bin/nova-volume
new file mode 100755
index 0000000000..e36954cd37
--- /dev/null
+++ b/bin/nova-volume
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop
+
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+ Tornado Storage daemon manages AoE volumes via AMQP messaging.
+"""
+
+import logging
+
+from nova import vendor
+from tornado import ioloop
+
+from nova import flags
+from nova import rpc
+from nova import server
+from nova import utils
+from nova.volume import storage
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_integer('storage_report_state_interval', 10,
+ 'seconds between broadcasting state to cloud',
+ lower_bound=1)
+
+
+def main(argv):
+ bs = storage.BlockStore()
+
+ conn = rpc.Connection.instance()
+ consumer_all = rpc.AdapterConsumer(
+ connection=conn,
+ topic='%s' % FLAGS.storage_topic,
+ proxy=bs)
+
+ consumer_node = rpc.AdapterConsumer(
+ connection=conn,
+ topic='%s.%s' % (FLAGS.storage_topic, FLAGS.node_name),
+ proxy=bs)
+
+ io_inst = ioloop.IOLoop.instance()
+ scheduler = ioloop.PeriodicCallback(
+ lambda: bs.report_state(),
+ FLAGS.storage_report_state_interval * 1000,
+ io_loop=io_inst)
+
+ injected = consumer_all.attachToTornado(io_inst)
+ injected = consumer_node.attachToTornado(io_inst)
+ scheduler.start()
+ io_inst.start()
+
+
+if __name__ == '__main__':
+ utils.default_flagfile()
+ server.serve('nova-volume', main)
+
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000000..2b226e048d
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,6 @@
+nova (0.3.0-1) UNRELEASED; urgency=low
+
+ * initial release
+
+ -- Jesse Andrews <jesse@ansolabs.com> Thur, 27 May 2010 12:28:00 -0700
+
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000000..7f8f011eb7
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+7
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000000..81af9f4e9e
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,40 @@
+Source: nova
+Section: net
+Priority: extra
+Maintainer: Jesse Andrews <jesse@ansolabs.com>
+Build-Depends: debhelper (>= 7)
+Build-Depends-Indep: python-support
+Standards-Version: 3.8.4
+XS-Python-Version: 2.6
+
+Package: nova-common
+Architecture: all
+Depends: ${python:Depends}, aoetools, vlan, python-ipy, python-boto, python-m2crypto, python-pycurl, python-twisted, python-daemon, python-redis, python-carrot, python-lockfile, python-gflags, python-tornado, ${misc:Depends}
+Provides: ${python:Provides}
+Conflicts: nova
+Description: Nova is a cloud
+
+Package: nova-compute
+Architecture: all
+Depends: nova-common (= ${binary:Version}), kpartx, kvm, python-libvirt, libvirt-bin (>= 0.8.1), ${python:Depends}, ${misc:Depends}
+Description: Nova compute
+
+Package: nova-volume
+Architecture: all
+Depends: nova-common (= ${binary:Version}), vblade, vblade-persist, ${python:Depends}, ${misc:Depends}
+Description: Nova volume
+
+Package: nova-api
+Architecture: all
+Depends: nova-common (= ${binary:Version}), ${python:Depends}, ${misc:Depends}
+Description: Nova api
+
+Package: nova-objectstore
+Architecture: all
+Depends: nova-common (= ${binary:Version}), ${python:Depends}, ${misc:Depends}
+Description: Nova object store
+
+Package: nova-tools
+Architecture: all
+Depends: python-boto, ${python:Depends}, ${misc:Depends}
+Description: CLI tools to access nova
diff --git a/debian/nova-api.init b/debian/nova-api.init
new file mode 100644
index 0000000000..925c92c5e3
--- /dev/null
+++ b/debian/nova-api.init
@@ -0,0 +1,69 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: nova-api
+# Required-Start: $remote_fs $syslog
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: nova-api
+# Description: nova-api
+### END INIT INFO
+
+
+set -e
+
+DAEMON=/usr/bin/nova-api
+DAEMON_ARGS="--flagfile=/etc/nova.conf"
+PIDFILE=/var/run/nova-api.pid
+
+ENABLED=false
+
+if test -f /etc/default/nova-api; then
+ . /etc/default/nova-api
+fi
+
+. /lib/lsb/init-functions
+
+export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
+
+case "$1" in
+ start)
+ test "$ENABLED" = "true" || exit 0
+ log_daemon_msg "Starting nova api" "nova-api"
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS start; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ stop)
+ test "$ENABLED" = "true" || exit 0
+ log_daemon_msg "Stopping nova api" "nova-api"
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS stop; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ restart|force-reload)
+ test "$ENABLED" = "true" || exit 1
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS restart; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ status)
+ test "$ENABLED" = "true" || exit 0
+ status_of_proc -p $PIDFILE $DAEMON nova-api && exit 0 || exit $?
+ ;;
+ *)
+ log_action_msg "Usage: /etc/init.d/nova-api {start|stop|restart|force-reload|status}"
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/debian/nova-api.install b/debian/nova-api.install
new file mode 100644
index 0000000000..757235b111
--- /dev/null
+++ b/debian/nova-api.install
@@ -0,0 +1 @@
+bin/nova-api usr/bin
diff --git a/debian/nova-common.install b/debian/nova-common.install
new file mode 100644
index 0000000000..c9358ac419
--- /dev/null
+++ b/debian/nova-common.install
@@ -0,0 +1,4 @@
+bin/nova-manage usr/bin
+nova/auth/novarc.template usr/lib/pymodules/python2.6/nova/auth
+nova/compute/libvirt.xml.template usr/lib/pymodules/python2.6/nova/compute
+usr/lib/python*/*-packages/nova/*
diff --git a/debian/nova-compute.init b/debian/nova-compute.init
new file mode 100644
index 0000000000..89d0e5fce6
--- /dev/null
+++ b/debian/nova-compute.init
@@ -0,0 +1,69 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: nova-compute
+# Required-Start: $remote_fs $syslog
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: nova-compute
+# Description: nova-compute
+### END INIT INFO
+
+
+set -e
+
+DAEMON=/usr/bin/nova-compute
+DAEMON_ARGS="--flagfile=/etc/nova.conf"
+PIDFILE=/var/run/nova-compute.pid
+
+ENABLED=false
+
+if test -f /etc/default/nova-compute; then
+ . /etc/default/nova-compute
+fi
+
+. /lib/lsb/init-functions
+
+export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
+
+case "$1" in
+ start)
+ test "$ENABLED" = "true" || exit 0
+ log_daemon_msg "Starting nova compute" "nova-compute"
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS start; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ stop)
+ test "$ENABLED" = "true" || exit 0
+ log_daemon_msg "Stopping nova compute" "nova-compute"
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS stop; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ restart|force-reload)
+ test "$ENABLED" = "true" || exit 1
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS restart; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ status)
+ test "$ENABLED" = "true" || exit 0
+ status_of_proc -p $PIDFILE $DAEMON nova-compute && exit 0 || exit $?
+ ;;
+ *)
+ log_action_msg "Usage: /etc/init.d/nova-compute {start|stop|restart|force-reload|status}"
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/debian/nova-compute.install b/debian/nova-compute.install
new file mode 100644
index 0000000000..6387cef07f
--- /dev/null
+++ b/debian/nova-compute.install
@@ -0,0 +1 @@
+bin/nova-compute usr/bin
diff --git a/debian/nova-objectstore.init b/debian/nova-objectstore.init
new file mode 100644
index 0000000000..be7d32d8e0
--- /dev/null
+++ b/debian/nova-objectstore.init
@@ -0,0 +1,69 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: nova-objectstore
+# Required-Start: $remote_fs $syslog
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: nova-objectstore
+# Description: nova-objectstore
+### END INIT INFO
+
+
+set -e
+
+DAEMON=/usr/bin/nova-objectstore
+DAEMON_ARGS="--flagfile=/etc/nova.conf"
+PIDFILE=/var/run/nova-objectstore.pid
+
+ENABLED=false
+
+if test -f /etc/default/nova-objectstore; then
+ . /etc/default/nova-objectstore
+fi
+
+. /lib/lsb/init-functions
+
+export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
+
+case "$1" in
+ start)
+ test "$ENABLED" = "true" || exit 0
+ log_daemon_msg "Starting nova objectstore" "nova-objectstore"
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS start; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ stop)
+ test "$ENABLED" = "true" || exit 0
+ log_daemon_msg "Stopping nova objectstore" "nova-objectstore"
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS stop; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ restart|force-reload)
+ test "$ENABLED" = "true" || exit 1
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS restart; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ status)
+ test "$ENABLED" = "true" || exit 0
+ status_of_proc -p $PIDFILE $DAEMON nova-objectstore && exit 0 || exit $?
+ ;;
+ *)
+ log_action_msg "Usage: /etc/init.d/nova-objectstore {start|stop|restart|force-reload|status}"
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/debian/nova-objectstore.install b/debian/nova-objectstore.install
new file mode 100644
index 0000000000..ccc60fcccc
--- /dev/null
+++ b/debian/nova-objectstore.install
@@ -0,0 +1 @@
+bin/nova-objectstore usr/bin
diff --git a/debian/nova-volume.init b/debian/nova-volume.init
new file mode 100644
index 0000000000..80da3f70c6
--- /dev/null
+++ b/debian/nova-volume.init
@@ -0,0 +1,69 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: nova-volume
+# Required-Start: $remote_fs $syslog
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: nova-volume
+# Description: nova-volume
+### END INIT INFO
+
+
+set -e
+
+DAEMON=/usr/bin/nova-volume
+DAEMON_ARGS="--flagfile=/etc/nova.conf"
+PIDFILE=/var/run/nova-volume.pid
+
+ENABLED=false
+
+if test -f /etc/default/nova-volume; then
+ . /etc/default/nova-volume
+fi
+
+. /lib/lsb/init-functions
+
+export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
+
+case "$1" in
+ start)
+ test "$ENABLED" = "true" || exit 0
+ log_daemon_msg "Starting nova volume" "nova-volume"
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS start; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ stop)
+ test "$ENABLED" = "true" || exit 0
+ log_daemon_msg "Stopping nova volume" "nova-volume"
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS stop; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ restart|force-reload)
+ test "$ENABLED" = "true" || exit 1
+ cd /var/run
+ if $DAEMON $DAEMON_ARGS restart; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+ ;;
+ status)
+ test "$ENABLED" = "true" || exit 0
+ status_of_proc -p $PIDFILE $DAEMON nova-volume && exit 0 || exit $?
+ ;;
+ *)
+ log_action_msg "Usage: /etc/init.d/nova-volume {start|stop|restart|force-reload|status}"
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/debian/nova-volume.install b/debian/nova-volume.install
new file mode 100644
index 0000000000..37b535c034
--- /dev/null
+++ b/debian/nova-volume.install
@@ -0,0 +1 @@
+bin/nova-volume usr/bin
diff --git a/debian/pycompat b/debian/pycompat
new file mode 100644
index 0000000000..0cfbf08886
--- /dev/null
+++ b/debian/pycompat
@@ -0,0 +1 @@
+2
diff --git a/debian/pyversions b/debian/pyversions
new file mode 100644
index 0000000000..0c043f18c3
--- /dev/null
+++ b/debian/pyversions
@@ -0,0 +1 @@
+2.6-
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000000..2d33f6ac89
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,4 @@
+#!/usr/bin/make -f
+
+%:
+ dh $@
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000000..88f9974bd7
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1 @@
+_build/*
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000000..b2f74e85aa
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,89 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/nova.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/nova.qhc"
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+ "run these through (pdf)latex."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/_build/.gitignore b/docs/_build/.gitignore
new file mode 100644
index 0000000000..72e8ffc0db
--- /dev/null
+++ b/docs/_build/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/docs/_static/.gitignore b/docs/_static/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/docs/_static/.gitignore
diff --git a/docs/_templates/.gitignore b/docs/_templates/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/docs/_templates/.gitignore
diff --git a/docs/architecture.rst b/docs/architecture.rst
new file mode 100644
index 0000000000..9aab7afbf9
--- /dev/null
+++ b/docs/architecture.rst
@@ -0,0 +1,46 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+nova System Architecture
+========================
+
+Nova is built on a shared-nothing, messaging-based architecture. All of the major nova components can be run on multiple servers. This means that most component to component communication must go via message queue. In order to avoid blocking each component while waiting for a response, we use deferred objects, with a callback that gets triggered when a response is received.
+
+In order to achieve shared-nothing with multiple copies of the same component (especially when the component is an API server that needs to reply with state information in a timely fashion), we need to keep all of our system state in a distributed data system. Updates to system state are written into this system, using atomic transactions when necessary. Requests for state are read out of this system. In limited cases, these read calls are memoized within controllers for short periods of time. (Such a limited case would be, for instance, the current list of system users.)
+
+
+Components
+----------
+
+Below you will find a helpful explanation.
+
+::
+
+ [ User Manager ] ---- ( LDAP )
+ |
+ | / [ Storage ] - ( ATAoE )
+ [ API server ] -> [ Cloud ] < AMQP >
+ | \ [ Nodes ] - ( libvirt/kvm )
+ < HTTP >
+ |
+ [ S3 ]
+
+
+* API: receives http requests from boto, converts commands to/from API format, and sending requests to cloud controller
+* Cloud Controller: global state of system, talks to ldap, s3, and node/storage workers through a queue
+* Nodes: worker that spawns instances
+* S3: tornado based http/s3 server
+* User Manager: create/manage users, which are stored in ldap
+* Network Controller: allocate and deallocate IPs and VLANs
diff --git a/docs/auth.rst b/docs/auth.rst
new file mode 100644
index 0000000000..ba001cfecc
--- /dev/null
+++ b/docs/auth.rst
@@ -0,0 +1,213 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Auth Documentation
+==================
+
+Nova provides RBAC (Role-based access control) of the AWS-type APIs. We define the following roles:
+
+Roles-Based Access Control of AWS-style APIs using SAML Assertions
+“Achieving FIPS 199 Moderate certification of a hybrid cloud environment using CloudAudit and declarative C.I.A. classificationsâ€
+
+Introduction
+--------------
+
+We will investigate one method for integrating an AWS-style API with US eAuthentication-compatible federated authentication systems, to achieve access controls and limits based on traditional operational roles.
+Additionally, we will look at how combining this approach, with an implementation of the CloudAudit APIs, will allow us to achieve a certification under FIPS 199 Moderate classification for a hybrid cloud environment.
+
+Relationship of US eAuth to RBAC
+--------------------------------
+
+Typical implementations of US eAuth authentication systems are structured as follows::
+
+ [ MS Active Directory or other federated LDAP user store ]
+ --> backends to…
+ [ SUN Identity Manager or other SAML Policy Controller ]
+ --> maps URLs to groups…
+ [ Apache Policy Agent in front of eAuth-secured Web Application ]
+
+In more ideal implementations, the remainder of the application-specific account information is stored either in extended schema on the LDAP server itself, via the use of a translucent LDAP proxy, or in an independent datastore keyed off of the UID provided via SAML assertion.
+
+Basic AWS API call structure
+----------------------------
+
+AWS API calls are traditionally secured via Access and Secret Keys, which are used to sign API calls, along with traditional timestamps to prevent replay attacks. The APIs can be logically grouped into sets that align with five typical roles:
+
+* System User
+* System Administrator
+* Network Administrator
+* Project Manager
+* Cloud Administrator
+* (IT-Sec?)
+
+There is an additional, conceptual end-user that may or may not have API access:
+
+* (EXTERNAL) End-user / Third-party User
+
+Basic operations are available to any System User:
+
+* Launch Instance
+* Terminate Instance (their own)
+* Create keypair
+* Delete keypair
+* Create, Upload, Delete: Buckets and Keys (Object Store) – their own
+* Create, Attach, Delete Volume (Block Store) – their own
+
+System Administrators:
+
+* Register/Unregister Machine Image (project-wide)
+* Change Machine Image properties (public / private)
+* Request / Review CloudAudit Scans
+
+Network Administrator:
+
+* Change Firewall Rules, define Security Groups
+* Allocate, Associate, Deassociate Public IP addresses
+
+Project Manager:
+
+* Launch and Terminate Instances (project-wide)
+* CRUD of Object and Block store (project-wide)
+
+Cloud Administrator:
+
+* Register / Unregister Kernel and Ramdisk Images
+* Register / Unregister Machine Image (any)
+
+Enhancements
+------------
+
+* SAML Token passing
+* REST interfaces
+* SOAP interfaces
+
+Wrapping the SAML token into the API calls.
+Then store the UID (fetched via backchannel) into the instance metadata, providing end-to-end auditability of ownership and responsibility, without PII.
+
+CloudAudit APIs
+---------------
+
+* Request formats
+* Response formats
+* Stateless asynchronous queries
+
+CloudAudit queries may spawn long-running processes (similar to launching instances, etc.) They need to return a ReservationId in the same fashion, which can be returned in further queries for updates.
+RBAC of CloudAudit API calls is critical, since detailed system information is a system vulnerability.
+
+Type declarations
+---------------------
+* Data declarations – Volumes and Objects
+* System declarations – Instances
+
+Existing API calls to launch instances specific a single, combined “type†flag. We propose to extend this with three additional type declarations, mapping to the “Confidentiality, Integrity, Availability†classifications of FIPS 199. An example API call would look like::
+
+ RunInstances type=m1.large number=1 secgroup=default key=mykey confidentiality=low integrity=low availability=low
+
+These additional parameters would also apply to creation of block storage volumes (along with the existing parameter of ‘size’), and creation of object storage ‘buckets’. (C.I.A. classifications on a bucket would be inherited by the keys within this bucket.)
+
+Request Brokering
+-----------------
+
+ * Cloud Interop
+ * IMF Registration / PubSub
+ * Digital C&A
+
+Establishing declarative semantics for individual API calls will allow the cloud environment to seamlessly proxy these API calls to external, third-party vendors – when the requested CIA levels match.
+
+See related work within the Infrastructure 2.0 working group for more information on how the IMF Metadata specification could be utilized to manage registration of these vendors and their C&A credentials.
+
+Dirty Cloud – Hybrid Data Centers
+---------------------------------
+
+* CloudAudit bridge interfaces
+* Anything in the ARP table
+
+A hybrid cloud environment provides dedicated, potentially co-located physical hardware with a network interconnect to the project or users’ cloud virtual network.
+
+This interconnect is typically a bridged VPN connection. Any machines that can be bridged into a hybrid environment in this fashion (at Layer 2) must implement a minimum version of the CloudAudit spec, such that they can be queried to provide a complete picture of the IT-sec runtime environment.
+
+Network discovery protocols (ARP, CDP) can be applied in this case, and existing protocols (SNMP location data, DNS LOC records) overloaded to provide CloudAudit information.
+
+The Details
+-----------
+
+ * Preliminary Roles Definitions
+ * Categorization of available API calls
+ * SAML assertion vocabulary
+
+System limits
+-------------
+
+The following limits need to be defined and enforced:
+
+* Total number of instances allowed (user / project)
+* Total number of instances, per instance type (user / project)
+* Total number of volumes (user / project)
+* Maximum size of volume
+* Cumulative size of all volumes
+* Total use of object storage (GB)
+* Total number of Public IPs
+
+
+Further Challenges
+------------------
+ * Prioritization of users / jobs in shared computing environments
+ * Incident response planning
+ * Limit launch of instances to specific security groups based on AMI
+ * Store AMIs in LDAP for added property control
+
+
+
+The :mod:`access` Module
+--------------------------
+
+.. automodule:: nova.auth.access
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`signer` Module
+------------------------
+
+.. automodule:: nova.auth.signer
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`users` Module
+-----------------------
+
+.. automodule:: nova.auth.users
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`users_unittest` Module
+--------------------------------
+
+.. automodule:: nova.tests.users_unittest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`access_unittest` Module
+---------------------------------
+
+.. automodule:: nova.tests.access_unittest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
diff --git a/docs/binaries.rst b/docs/binaries.rst
new file mode 100644
index 0000000000..eee0891642
--- /dev/null
+++ b/docs/binaries.rst
@@ -0,0 +1,29 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Nova Binaries
+===============
+
+* nova-api
+* nova-compute
+* nova-manage
+* nova-objectstore
+* nova-volume
+
+The configuration of these binaries relies on "flagfiles" using the google
+gflags package. If present, the nova.conf file will be used as the flagfile
+- otherwise, it must be specified on the command line::
+
+ $ python node_worker.py --flagfile flagfile \ No newline at end of file
diff --git a/docs/compute.rst b/docs/compute.rst
new file mode 100644
index 0000000000..e2b32fae04
--- /dev/null
+++ b/docs/compute.rst
@@ -0,0 +1,72 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Compute Documentation
+=====================
+
+This page contains the Compute Package documentation.
+
+
+The :mod:`disk` Module
+----------------------
+
+.. automodule:: nova.compute.disk
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`exception` Module
+---------------------------
+
+.. automodule:: nova.compute.exception
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`model` Module
+-------------------------
+
+.. automodule:: nova.compute.model
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`network` Module
+-------------------------
+
+.. automodule:: nova.compute.network
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`node` Module
+----------------------
+
+.. automodule:: nova.compute.node
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+RELATED TESTS
+---------------
+
+The :mod:`node_unittest` Module
+-------------------------------
+
+.. automodule:: nova.tests.node_unittest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000000..9dfdfc8be6
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+#
+# nova documentation build configuration file, created by
+# sphinx-quickstart on Sat May 1 15:17:47 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+sys.path.append([os.path.abspath('../nova'),os.path.abspath('../'),os.path.abspath('../vendor')])
+from nova import vendor
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig']
+#sphinx_to_github = False
+todo_include_todos = True
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'nova'
+copyright = u'2010, Anso Labs, LLC'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.42'
+# The full version, including alpha/beta/rc tags.
+release = '0.42'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+modindex_common_prefix = ['nova.']
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'novadoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'nova.tex', u'nova Documentation',
+ u'Anso Labs, LLC', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/docs/endpoint.rst b/docs/endpoint.rst
new file mode 100644
index 0000000000..86a1a3be08
--- /dev/null
+++ b/docs/endpoint.rst
@@ -0,0 +1,89 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Endpoint Documentation
+======================
+
+This page contains the Endpoint Package documentation.
+
+The :mod:`admin` Module
+-----------------------
+
+.. automodule:: nova.endpoint.admin
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`api` Module
+---------------------
+
+.. automodule:: nova.endpoint.api
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`cloud` Module
+-----------------------
+
+.. automodule:: nova.endpoint.cloud
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`images` Module
+------------------------
+
+.. automodule:: nova.endpoint.images
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+RELATED TESTS
+--------------
+
+The :mod:`api_unittest` Module
+------------------------------
+
+.. automodule:: nova.tests.api_unittest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`api_integration` Module
+---------------------------------
+
+.. automodule:: nova.tests.api_integration
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`cloud_unittest` Module
+--------------------------------
+
+.. automodule:: nova.tests.cloud_unittest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`network_unittest` Module
+----------------------------------
+
+.. automodule:: nova.tests.network_unittest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
diff --git a/docs/fakes.rst b/docs/fakes.rst
new file mode 100644
index 0000000000..f105c6b8d6
--- /dev/null
+++ b/docs/fakes.rst
@@ -0,0 +1,41 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Nova Fakes
+==========
+
+The :mod:`fakevirt` Module
+--------------------------
+
+.. automodule:: nova.fakevirt
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`fakeldap` Module
+--------------------------
+
+.. automodule:: nova.auth.fakeldap
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`fakerabbit` Module
+----------------------------
+
+.. automodule:: nova.fakerabbit
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/getting.started.rst b/docs/getting.started.rst
new file mode 100644
index 0000000000..777cd32e98
--- /dev/null
+++ b/docs/getting.started.rst
@@ -0,0 +1,70 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Getting Started with Nova
+=========================
+
+
+GOTTA HAVE A nova.pth file added or it WONT WORK (will write setup.py file soon)
+
+DEPENDENCIES
+------------
+
+* RabbitMQ: messaging queue, used for all communication between components
+* OpenLDAP: users, groups (maybe cut)
+* Tornado: scalable non blocking web server for api requests
+* Twisted: just for the twisted.internet.defer package
+* boto: python api for aws api
+* M2Crypto: python library interface for openssl
+* IPy: library for managing ip addresses
+* ReDIS: Remote Dictionary Store (for fast, shared state data)
+
+Recommended
+-----------------
+* euca2ools: python implementation of aws ec2-tools and ami tools
+* build tornado to use C module for evented section
+
+
+Installation
+--------------
+::
+
+ # ON ALL SYSTEMS
+ apt-get install -y python-libvirt libvirt-bin python-setuptools python-dev python-pycurl python-m2crypto python-twisted
+ apt-get install -y aoetools vlan
+ modprobe aoe
+
+ # ON THE CLOUD CONTROLLER
+ apt-get install -y rabbitmq-server dnsmasq
+ # fix ec2 metadata/userdata uri - where $IP is the IP of the cloud
+ iptables -t nat -A PREROUTING -s 0.0.0.0/0 -d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination $IP:8773
+ iptables --table nat --append POSTROUTING --out-interface $PUBLICIFACE -j MASQUERADE
+ # setup ldap (slap.sh as root will remove ldap and reinstall it)
+ auth/slap.sh
+ /etc/init.d/rabbitmq-server start
+
+ # ON VOLUME NODE:
+ apt-get install -y vblade-persist
+
+ # ON THE COMPUTE NODE:
+ apt-get install -y kpartx kvm
+
+ # optional packages
+ apt-get install -y euca2ools
+
+ # Set up flagfiles with the appropriate hostnames, etc.
+ # start api_worker, s3_worker, node_worker, storage_worker
+ # Add yourself to the libvirtd group, log out, and log back in
+ # Make sure the user who will launch the workers has sudo privileges w/o pass (will fix later)
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000000..b86f14324c
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,53 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+Welcome to nova's documentation!
+================================
+
+Nova is a cloud computing fabric controller (the main part of an IaaS system) built to match the popular AWS EC2 and S3 APIs.
+It is written in Python, using the Tornado and Twisted frameworks, and relies on the standard AMQP messaging protocol,
+and the Redis distributed KVS.
+Nova is intended to be easy to extend, and adapt. For example, it currently uses
+an LDAP server for users and groups, but also includes a fake LDAP server,
+that stores data in Redis. It has extensive test coverage, and uses the
+Sphinx toolkit (the same as Python itself) for code and user documentation.
+While Nova is currently in Beta use within several organizations, the codebase
+is very much under active development - there are bugs!
+
+Contents:
+
+.. toctree::
+ :maxdepth: 2
+
+ getting.started
+ architecture
+ network
+ storage
+ auth
+ compute
+ endpoint
+ nova
+ fakes
+ binaries
+ todo
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/docs/modules.rst b/docs/modules.rst
new file mode 100644
index 0000000000..f927a52d09
--- /dev/null
+++ b/docs/modules.rst
@@ -0,0 +1,32 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Nova Documentation
+==================
+
+This page contains the Nova Modules documentation.
+
+Modules:
+--------
+
+.. toctree::
+ :maxdepth: 4
+
+ auth
+ compute
+ endpoint
+ fakes
+ nova
+ volume
diff --git a/docs/network.rst b/docs/network.rst
new file mode 100644
index 0000000000..49e36170d9
--- /dev/null
+++ b/docs/network.rst
@@ -0,0 +1,86 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+nova Networking
+================
+
+The nova networking components manage private networks, public IP addressing, VPN connectivity, and firewall rules.
+
+Components
+----------
+There are several key components:
+
+* NetworkController (Manages address and vlan allocation)
+* RoutingNode (NATs public IPs to private IPs, and enforces firewall rules)
+* AddressingNode (runs DHCP services for private networks)
+* BridgingNode (a subclass of the basic nova ComputeNode)
+* TunnelingNode (provides VPN connectivity)
+
+Component Diagram
+-----------------
+
+Overview::
+
+ (PUBLIC INTERNET)
+ | \
+ / \ / \
+ [RoutingNode] ... [RN] [TunnelingNode] ... [TN]
+ | \ / | |
+ | < AMQP > | |
+ [AddressingNode]-- (VLAN) ... | (VLAN)... (VLAN) --- [AddressingNode]
+ \ | \ /
+ / \ / \ / \ / \
+ [BridgingNode] ... [BridgingNode]
+
+
+ [NetworkController] ... [NetworkController]
+ \ /
+ < AMQP >
+ |
+ / \
+ [CloudController]...[CloudController]
+
+While this diagram may not make this entirely clear, nodes and controllers communicate exclusively across the message bus (AMQP, currently).
+
+State Model
+-----------
+Network State consists of the following facts:
+
+* VLAN assignment (to a project)
+* Private Subnet assignment (to a security group) in a VLAN
+* Private IP assignments (to running instances)
+* Public IP allocations (to a project)
+* Public IP associations (to a private IP / running instance)
+
+While copies of this state exist in many places (expressed in IPTables rule chains, DHCP hosts files, etc), the controllers rely only on the distributed "fact engine" for state, queried over RPC (currently AMQP). The NetworkController inserts most records into this datastore (allocating addresses, etc) - however, individual nodes update state e.g. when running instances crash.
+
+The Public Traffic Path
+-----------------------
+
+Public Traffic::
+
+ (PUBLIC INTERNET)
+ |
+ <NAT> <-- [RoutingNode]
+ |
+ [AddressingNode] --> |
+ ( VLAN )
+ | <-- [BridgingNode]
+ |
+ <RUNNING INSTANCE>
+
+The RoutingNode is currently implemented using IPTables rules, which implement both NATing of public IP addresses, and the appropriate firewall chains. We are also looking at using Netomata / Clusto to manage NATting within a switch or router, and/or to manage firewall rules within a hardware firewall appliance.
+
+Similarly, the AddressingNode currently manages running DNSMasq instances for DHCP services. However, we could run an internal DHCP server (using Scapy ala Clusto), or even switch to static addressing by inserting the private address into the disk image the same way we insert the SSH keys. (See compute for more details). \ No newline at end of file
diff --git a/docs/nova.rst b/docs/nova.rst
new file mode 100644
index 0000000000..7f1feda10c
--- /dev/null
+++ b/docs/nova.rst
@@ -0,0 +1,89 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+NOVA Libraries
+===============
+
+The :mod:`crypto` Module
+------------------------
+
+.. automodule:: nova.crypto
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`adminclient` Module
+-----------------------------
+
+.. automodule:: nova.adminclient
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`datastore` Module
+---------------------------
+
+.. automodule:: nova.datastore
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`exception` Module
+---------------------------
+
+.. automodule:: nova.exception
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`flags` Module
+---------------------------
+
+.. automodule:: nova.flags
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`rpc` Module
+---------------------------
+
+.. automodule:: nova.rpc
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`server` Module
+---------------------------
+
+.. automodule:: nova.server
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`test` Module
+---------------------------
+
+.. automodule:: nova.test
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`utils` Module
+---------------------------
+
+.. automodule:: nova.utils
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/objectstore.rst b/docs/objectstore.rst
new file mode 100644
index 0000000000..64122c9b73
--- /dev/null
+++ b/docs/objectstore.rst
@@ -0,0 +1,64 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Objectstore Documentation
+=========================
+
+This page contains the Objectstore Package documentation.
+
+
+The :mod:`bucket` Module
+------------------------
+
+.. automodule:: nova.objectstore.bucket
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`handler` Module
+-------------------------
+
+.. automodule:: nova.objectstore.handler
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`image` Module
+-----------------------
+
+.. automodule:: nova.objectstore.image
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`stored` Module
+------------------------
+
+.. automodule:: nova.objectstore.stored
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+RELATED TESTS
+-------------
+
+The :mod:`objectstore_unittest` Module
+--------------------------------------
+
+.. automodule:: nova.tests.objectstore_unittest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
diff --git a/docs/packages.rst b/docs/packages.rst
new file mode 100644
index 0000000000..ad1386f196
--- /dev/null
+++ b/docs/packages.rst
@@ -0,0 +1,27 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+nova Packages & Dependencies
+============================
+
+Nova is being built on Ubuntu Lucid.
+
+The following packages are required:
+
+ apt-get install python-ipy, python-libvirt, python-boto, python-pycurl, python-twisted, python-daemon, python-redis, python-carrot, python-lockfile
+
+In addition you need to install python:
+
+ * python-gflags - http://code.google.com/p/python-gflags/
diff --git a/docs/storage.rst b/docs/storage.rst
new file mode 100644
index 0000000000..94d7bdeea2
--- /dev/null
+++ b/docs/storage.rst
@@ -0,0 +1,29 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Storage in the Nova Cloud
+=========================
+
+There are three primary classes of storage in a nova cloud environment:
+
+* Ephemeral Storage (local disk within an instance)
+* Volume Storage (network-attached FS)
+* Object Storage (redundant KVS with locality and MR)
+
+.. toctree::
+ :maxdepth: 2
+
+ volume
+ objectstore \ No newline at end of file
diff --git a/docs/volume.rst b/docs/volume.rst
new file mode 100644
index 0000000000..18ce70a3ab
--- /dev/null
+++ b/docs/volume.rst
@@ -0,0 +1,43 @@
+..
+ Copyright [2010] [Anso Labs, LLC]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Volume Documentation
+====================
+
+Nova uses ata-over-ethernet (AoE) to export storage volumes from multiple storage nodes. These AoE exports are attached (using libvirt) directly to running instances.
+
+Nova volumes are exported over the primary system VLAN (usually VLAN 1), and not over individual VLANs.
+
+AoE exports are numbered according to a "shelf and blade" syntax. In order to avoid collisions, we currently perform an AoE-discover of existing exports, and then grab the next unused number. (This obviously has race condition problems, and should be replaced by allocating a shelf-id to each storage node.)
+
+The underlying volumes are LVM logical volumes, created on demand within a single large volume group.
+
+
+The :mod:`storage` Module
+-------------------------
+
+.. automodule:: nova.volume.storage
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+The :mod:`storage_unittest` Module
+----------------------------------
+
+.. automodule:: nova.tests.storage_unittest
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
diff --git a/nova/__init__.py b/nova/__init__.py
new file mode 100644
index 0000000000..2b25d1628d
--- /dev/null
+++ b/nova/__init__.py
@@ -0,0 +1,30 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:mod:`nova` -- Cloud IaaS Platform
+===================================
+
+.. automodule:: nova
+ :platform: Unix
+ :synopsis: Infrastructure-as-a-Service Cloud platform.
+.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
+.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
+.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
+.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
+.. moduleauthor:: Manish Singh <yosh@gimp.org>
+.. moduleauthor:: Andy Smith <andy@anarkystic.com>
+"""
+
+from exception import * \ No newline at end of file
diff --git a/nova/adminclient.py b/nova/adminclient.py
new file mode 100644
index 0000000000..2cc592b9f3
--- /dev/null
+++ b/nova/adminclient.py
@@ -0,0 +1,113 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Nova User API client library.
+"""
+
+import boto
+from boto.ec2.regioninfo import RegionInfo
+import base64
+
+class UserInfo(object):
+ """ Information about a Nova user
+ fields include:
+ username
+ accesskey
+ secretkey
+
+ and an optional field containing a zip with X509 cert & rc
+ file
+ """
+
+ def __init__(self, connection=None, username=None, endpoint=None):
+ self.connection = connection
+ self.username = username
+ self.endpoint = endpoint
+
+ def __repr__(self):
+ return 'UserInfo:%s' % self.username
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'username':
+ self.username = str(value)
+ elif name == 'file':
+ self.file = base64.b64decode(str(value))
+ elif name == 'accesskey':
+ self.accesskey = str(value)
+ elif name == 'secretkey':
+ self.secretkey = str(value)
+
+
+class NovaAdminClient(object):
+ def __init__(self, clc_ip='127.0.0.1', region='nova', access_key='admin',
+ secret_key='admin', **kwargs):
+ self.clc_ip = clc_ip
+ self.region = region
+ self.access = access_key
+ self.secret = secret_key
+ self.apiconn = boto.connect_ec2(aws_access_key_id=access_key,
+ aws_secret_access_key=secret_key,
+ is_secure=False,
+ region=RegionInfo(None, region, clc_ip),
+ port=8773,
+ path='/services/Admin',
+ **kwargs)
+ self.apiconn.APIVersion = 'nova'
+
+ def connection_for(self, username, **kwargs):
+ """
+ Returns a boto ec2 connection for the given username.
+ """
+ user = self.get_user(username)
+ return boto.connect_ec2(
+ aws_access_key_id=user.accesskey,
+ aws_secret_access_key=user.secretkey,
+ is_secure=False,
+ region=RegionInfo(None, self.region, self.clc_ip),
+ port=8773,
+ path='/services/Cloud',
+ **kwargs
+ )
+
+ def get_users(self):
+ """ grabs the list of all users """
+ return self.apiconn.get_list('DescribeUsers', {}, (['item', UserInfo]))
+
+ def get_user(self, name):
+ """ grab a single user by name """
+ user = self.apiconn.get_object('DescribeUser', {'Name': name}, UserInfo)
+
+ if user.username != None:
+ return user
+
+ def has_user(self, username):
+ """ determine if user exists """
+ return self.get_user(username) != None
+
+ def create_user(self, username):
+ """ creates a new user, returning the userinfo object with access/secret """
+ return self.apiconn.get_object('RegisterUser', {'Name': username}, UserInfo)
+
+ def delete_user(self, username):
+ """ deletes a user """
+ return self.apiconn.get_object('DeregisterUser', {'Name': username}, UserInfo)
+
+ def get_zip(self, username):
+ """ returns the content of a zip file containing novarc and access credentials. """
+ return self.apiconn.get_object('GenerateX509ForUser', {'Name': username}, UserInfo).file
+
diff --git a/nova/auth/__init__.py b/nova/auth/__init__.py
new file mode 100644
index 0000000000..7cd6c618db
--- /dev/null
+++ b/nova/auth/__init__.py
@@ -0,0 +1,25 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:mod:`nova.auth` -- Authentication and Access Control
+=====================================================
+
+.. automodule:: nova.auth
+ :platform: Unix
+ :synopsis: User-and-Project based RBAC using LDAP, SAML.
+.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
+.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
+.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
+""" \ No newline at end of file
diff --git a/nova/auth/access.py b/nova/auth/access.py
new file mode 100644
index 0000000000..2c780626d2
--- /dev/null
+++ b/nova/auth/access.py
@@ -0,0 +1,69 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Simple base set of RBAC rules which map API endpoints to LDAP groups.
+For testing accounts, users will always have PM privileges.
+"""
+
+
+# This is logically a RuleSet or some such.
+
+def allow_describe_images(user, project, target_object):
+ return True
+
+def allow_describe_instances(user, project, target_object):
+ return True
+
+def allow_describe_addresses(user, project, target_object):
+ return True
+
+def allow_run_instances(user, project, target_object):
+ # target_object is a reservation, not an instance
+ # it needs to include count, type, image, etc.
+
+ # First, is the project allowed to use this image
+
+ # Second, is this user allowed to launch within this project
+
+ # Third, is the count or type within project quota
+
+ return True
+
+def allow_terminate_instances(user, project, target_object):
+ # In a project, the PMs and Sysadmins can terminate
+ return True
+
+def allow_get_console_output(user, project, target_object):
+ # If the user launched the instance,
+ # Or is a sysadmin in the project,
+ return True
+
+def allow_allocate_address(user, project, target_object):
+ # There's no security concern in allocation,
+ # but it can get expensive. Limit to PM and NE.
+ return True
+
+def allow_associate_address(user, project, target_object):
+ # project NE only
+ # In future, will perform a CloudAudit scan first
+ # (Pass / Fail gate)
+ return True
+
+def allow_register(user, project, target_object):
+ return False
+
+def is_allowed(action, user, project, target_object):
+ return globals()['allow_%s' % action](user, project, target_object)
+
diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py
new file mode 100644
index 0000000000..c223b250ce
--- /dev/null
+++ b/nova/auth/fakeldap.py
@@ -0,0 +1,81 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+ Fake LDAP server for test harnesses.
+"""
+
+import logging
+
+from nova import datastore
+
+SCOPE_SUBTREE = 1
+
+
+class NO_SUCH_OBJECT(Exception):
+ pass
+
+
+def initialize(uri):
+ return FakeLDAP(uri)
+
+
+class FakeLDAP(object):
+ def __init__(self, _uri):
+ self.keeper = datastore.Keeper('fakeldap')
+ if self.keeper['objects'] is None:
+ self.keeper['objects'] = {}
+
+ def simple_bind_s(self, dn, password):
+ pass
+
+ def unbind_s(self):
+ pass
+
+ def search_s(self, dn, scope, query=None, fields=None):
+ logging.debug("searching for %s" % dn)
+ filtered = {}
+ d = self.keeper['objects'] or {}
+ for cn, attrs in d.iteritems():
+ if cn[-len(dn):] == dn:
+ filtered[cn] = attrs
+ if query:
+ k,v = query[1:-1].split('=')
+ objects = {}
+ for cn, attrs in filtered.iteritems():
+ if attrs.has_key(k) and (v in attrs[k] or
+ v == attrs[k]):
+ objects[cn] = attrs
+ if objects == {}:
+ raise NO_SUCH_OBJECT()
+ return objects.items()
+
+ def add_s(self, cn, attr):
+ logging.debug("adding %s" % cn)
+ stored = {}
+ for k, v in attr:
+ if type(v) is list:
+ stored[k] = v
+ else:
+ stored[k] = [v]
+ d = self.keeper['objects']
+ d[cn] = stored
+ self.keeper['objects'] = d
+
+ def delete_s(self, cn):
+ logging.debug("creating for %s" % cn)
+ d = self.keeper['objects'] or {}
+ del d[cn]
+ self.keeper['objects'] = d
diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template
new file mode 100644
index 0000000000..a993d18829
--- /dev/null
+++ b/nova/auth/novarc.template
@@ -0,0 +1,26 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+NOVA_KEY_DIR=$(pushd $(dirname $BASH_SOURCE)>/dev/null; pwd; popd>/dev/null)
+export EC2_ACCESS_KEY="%(access)s"
+export EC2_SECRET_KEY="%(secret)s"
+export EC2_URL="%(ec2)s"
+export S3_URL="%(s3)s"
+export EC2_USER_ID=42 # nova does not use user id, but bundling requires it
+export EC2_PRIVATE_KEY=${NOVA_KEY_DIR}/%(key)s
+export EC2_CERT=${NOVA_KEY_DIR}/%(cert)s
+export NOVA_CERT=${NOVA_KEY_DIR}/%(nova)s
+export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this set
+alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}"
+alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}"
diff --git a/nova/auth/rbac.ldif b/nova/auth/rbac.ldif
new file mode 100644
index 0000000000..3878d2c1be
--- /dev/null
+++ b/nova/auth/rbac.ldif
@@ -0,0 +1,60 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# LDIF fragment to create group branch under root
+
+#dn: ou=Groups,dc=example,dc=com
+#objectclass:organizationalunit
+#ou: groups
+#description: generic groups branch
+
+# create the itpeople entry
+
+dn: cn=sysadmins,ou=Groups,dc=example,dc=com
+objectclass: groupofnames
+cn: itpeople
+description: IT admin group
+# add the group members all of which are
+# assumed to exist under Users
+#member: cn=micky mouse,ou=people,dc=example,dc=com
+member: cn=admin,ou=Users,dc=example,dc=com
+
+dn: cn=netadmins,ou=Groups,dc=example,dc=com
+objectclass: groupofnames
+cn: netadmins
+description: Network admin group
+member: cn=admin,ou=Users,dc=example,dc=com
+
+dn: cn=cloudadmins,ou=Groups,dc=example,dc=com
+objectclass: groupofnames
+cn: cloudadmins
+description: Cloud admin group
+member: cn=admin,ou=Users,dc=example,dc=com
+
+dn: cn=itsec,ou=Groups,dc=example,dc=com
+objectclass: groupofnames
+cn: itsec
+description: IT security users group
+member: cn=admin,ou=Users,dc=example,dc=com
+
+# Example Project Group to demonstrate members
+# and project members
+
+dn: cn=myproject,ou=Groups,dc=example,dc=com
+objectclass: groupofnames
+objectclass: novaProject
+cn: myproject
+description: My Project Group
+member: cn=admin,ou=Users,dc=example,dc=com
+projectManager: cn=admin,ou=Users,dc=example,dc=com
diff --git a/nova/auth/signer.py b/nova/auth/signer.py
new file mode 100644
index 0000000000..00aa066fb0
--- /dev/null
+++ b/nova/auth/signer.py
@@ -0,0 +1,127 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# PORTIONS OF THIS FILE ARE FROM:
+# http://code.google.com/p/boto
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Utility class for parsing signed AMI manifests.
+"""
+
+import logging
+import hashlib
+import hmac
+import urllib
+import base64
+from nova.exception import Error
+
+_log = logging.getLogger('signer')
+logging.getLogger('signer').setLevel(logging.WARN)
+
+class Signer(object):
+ """ hacked up code from boto/connection.py """
+
+ def __init__(self, secret_key):
+ self.hmac = hmac.new(secret_key, digestmod=hashlib.sha1)
+ if hashlib.sha256:
+ self.hmac_256 = hmac.new(secret_key, digestmod=hashlib.sha256)
+
+ def generate(self, params, verb, server_string, path):
+ if params['SignatureVersion'] == '0':
+ t = self._calc_signature_0(params)
+ elif params['SignatureVersion'] == '1':
+ t = self._calc_signature_1(params)
+ elif params['SignatureVersion'] == '2':
+ t = self._calc_signature_2(params, verb, server_string, path)
+ else:
+ raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
+ return t
+
+ def _get_utf8_value(self, value):
+ if not isinstance(value, str) and not isinstance(value, unicode):
+ value = str(value)
+ if isinstance(value, unicode):
+ return value.encode('utf-8')
+ else:
+ return value
+
+ def _calc_signature_0(self, params):
+ s = params['Action'] + params['Timestamp']
+ self.hmac.update(s)
+ keys = params.keys()
+ keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
+ pairs = []
+ for key in keys:
+ val = self._get_utf8_value(params[key])
+ pairs.append(key + '=' + urllib.quote(val))
+ return base64.b64encode(self.hmac.digest())
+
+ def _calc_signature_1(self, params):
+ keys = params.keys()
+ keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
+ pairs = []
+ for key in keys:
+ self.hmac.update(key)
+ val = self._get_utf8_value(params[key])
+ self.hmac.update(val)
+ pairs.append(key + '=' + urllib.quote(val))
+ return base64.b64encode(self.hmac.digest())
+
+ def _calc_signature_2(self, params, verb, server_string, path):
+ _log.debug('using _calc_signature_2')
+ string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path)
+ if self.hmac_256:
+ hmac = self.hmac_256
+ params['SignatureMethod'] = 'HmacSHA256'
+ else:
+ hmac = self.hmac
+ params['SignatureMethod'] = 'HmacSHA1'
+ keys = params.keys()
+ keys.sort()
+ pairs = []
+ for key in keys:
+ val = self._get_utf8_value(params[key])
+ pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~'))
+ qs = '&'.join(pairs)
+ _log.debug('query string: %s' % qs)
+ string_to_sign += qs
+ _log.debug('string_to_sign: %s' % string_to_sign)
+ hmac.update(string_to_sign)
+ b64 = base64.b64encode(hmac.digest())
+ _log.debug('len(b64)=%d' % len(b64))
+ _log.debug('base64 encoded digest: %s' % b64)
+ return b64
+
+if __name__ == '__main__':
+ print Signer('foo').generate({"SignatureMethod": 'HmacSHA256', 'SignatureVersion': '2'}, "get", "server", "/foo")
diff --git a/nova/auth/slap.sh b/nova/auth/slap.sh
new file mode 100755
index 0000000000..a0df4e0ae6
--- /dev/null
+++ b/nova/auth/slap.sh
@@ -0,0 +1,226 @@
+#!/usr/bin/env bash
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# LDAP INSTALL SCRIPT - SHOULD BE IDEMPOTENT, but it SCRUBS all USERS
+
+apt-get install -y slapd ldap-utils python-ldap
+
+cat >/etc/ldap/schema/openssh-lpk_openldap.schema <<LPK_SCHEMA_EOF
+#
+# LDAP Public Key Patch schema for use with openssh-ldappubkey
+# Author: Eric AUGE <eau@phear.org>
+#
+# Based on the proposal of : Mark Ruijter
+#
+
+
+# octetString SYNTAX
+attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
+ DESC 'MANDATORY: OpenSSH Public key'
+ EQUALITY octetStringMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
+
+# printableString SYNTAX yes|no
+objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
+ DESC 'MANDATORY: OpenSSH LPK objectclass'
+ MAY ( sshPublicKey $ uid )
+ )
+LPK_SCHEMA_EOF
+
+cat >/etc/ldap/schema/nova.schema <<NOVA_SCHEMA_EOF
+#
+# Person object for Nova
+# inetorgperson with extra attributes
+# Author: Vishvananda Ishaya <vishvananda@yahoo.com>
+#
+#
+
+# using internet experimental oid arc as per BP64 3.1
+objectidentifier novaSchema 1.3.6.1.3.1.666.666
+objectidentifier novaAttrs novaSchema:3
+objectidentifier novaOCs novaSchema:4
+
+attributetype (
+ novaAttrs:1
+ NAME 'accessKey'
+ DESC 'Key for accessing data'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ )
+
+attributetype (
+ novaAttrs:2
+ NAME 'secretKey'
+ DESC 'Secret key'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ )
+
+attributetype (
+ novaAttrs:3
+ NAME 'keyFingerprint'
+ DESC 'Fingerprint of private key'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ )
+
+attributetype (
+ novaAttrs:4
+ NAME 'isAdmin'
+ DESC 'Is user an administrator?'
+ EQUALITY booleanMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+ SINGLE-VALUE
+ )
+
+attributetype (
+ novaAttrs:5
+ NAME 'projectManager'
+ DESC 'Project Managers of a project'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ )
+
+objectClass (
+ novaOCs:1
+ NAME 'novaUser'
+ DESC 'access and secret keys'
+ AUXILIARY
+ MUST ( uid )
+ MAY ( accessKey $ secretKey $ isAdmin )
+ )
+
+objectClass (
+ novaOCs:2
+ NAME 'novaKeyPair'
+ DESC 'Key pair for User'
+ SUP top
+ STRUCTURAL
+ MUST ( cn $ sshPublicKey $ keyFingerprint )
+ )
+
+objectClass (
+ novaOCs:3
+ NAME 'novaProject'
+ DESC 'Container for project'
+ SUP groupofnames
+ STRUCTURAL
+ MUST ( cn $ projectManager )
+ )
+
+NOVA_SCHEMA_EOF
+
+mv /etc/ldap/slapd.conf /etc/ldap/slapd.conf.orig
+cat >/etc/ldap/slapd.conf <<SLAPD_CONF_EOF
+# slapd.conf - Configuration file for LDAP SLAPD
+##########
+# Basics #
+##########
+include /etc/ldap/schema/core.schema
+include /etc/ldap/schema/cosine.schema
+include /etc/ldap/schema/inetorgperson.schema
+include /etc/ldap/schema/openssh-lpk_openldap.schema
+include /etc/ldap/schema/nova.schema
+pidfile /var/run/slapd/slapd.pid
+argsfile /var/run/slapd/slapd.args
+loglevel none
+modulepath /usr/lib/ldap
+# modulepath /usr/local/libexec/openldap
+moduleload back_hdb
+##########################
+# Database Configuration #
+##########################
+database hdb
+suffix "dc=example,dc=com"
+rootdn "cn=Manager,dc=example,dc=com"
+rootpw changeme
+directory /var/lib/ldap
+# directory /usr/local/var/openldap-data
+index objectClass,cn eq
+########
+# ACLs #
+########
+access to attrs=userPassword
+ by anonymous auth
+ by self write
+ by * none
+access to *
+ by self write
+ by * none
+SLAPD_CONF_EOF
+
+mv /etc/ldap/ldap.conf /etc/ldap/ldap.conf.orig
+
+cat >/etc/ldap/ldap.conf <<LDAP_CONF_EOF
+# LDAP Client Settings
+URI ldap://localhost
+BASE dc=example,dc=com
+BINDDN cn=Manager,dc=example,dc=com
+SIZELIMIT 0
+TIMELIMIT 0
+LDAP_CONF_EOF
+
+cat >/etc/ldap/base.ldif <<BASE_LDIF_EOF
+# This is the root of the directory tree
+dn: dc=example,dc=com
+description: Example.Com, your trusted non-existent corporation.
+dc: example
+o: Example.Com
+objectClass: top
+objectClass: dcObject
+objectClass: organization
+
+# Subtree for users
+dn: ou=Users,dc=example,dc=com
+ou: Users
+description: Users
+objectClass: organizationalUnit
+
+# Subtree for groups
+dn: ou=Groups,dc=example,dc=com
+ou: Groups
+description: Groups
+objectClass: organizationalUnit
+
+# Subtree for system accounts
+dn: ou=System,dc=example,dc=com
+ou: System
+description: Special accounts used by software applications.
+objectClass: organizationalUnit
+
+# Special Account for Authentication:
+dn: uid=authenticate,ou=System,dc=example,dc=com
+uid: authenticate
+ou: System
+description: Special account for authenticating users
+userPassword: {MD5}TLnIqASP0CKUR3/LGkEZGg==
+objectClass: account
+objectClass: simpleSecurityObject
+BASE_LDIF_EOF
+
+/etc/init.d/slapd stop
+rm -rf /var/lib/ldap/*
+rm -rf /etc/ldap/slapd.d/*
+slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d
+cp /usr/share/slapd/DB_CONFIG /var/lib/ldap/DB_CONFIG
+slapadd -v -l /etc/ldap/base.ldif
+chown -R openldap:openldap /etc/ldap/slapd.d
+chown -R openldap:openldap /var/lib/ldap
+/etc/init.d/slapd start
diff --git a/nova/auth/users.py b/nova/auth/users.py
new file mode 100755
index 0000000000..d8ea8ac68b
--- /dev/null
+++ b/nova/auth/users.py
@@ -0,0 +1,454 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Nova users and user management, including RBAC hooks.
+"""
+
+import datetime
+import logging
+import os
+import shutil
+import tempfile
+import uuid
+import zipfile
+
+try:
+ import ldap
+except Exception, e:
+ import fakeldap as ldap
+
+import fakeldap
+from nova import datastore
+
+# TODO(termie): clean up these imports
+import signer
+from nova import exception
+from nova import flags
+from nova import crypto
+from nova import utils
+import access as simplerbac
+
+from nova import objectstore # for flags
+
+FLAGS = flags.FLAGS
+
+flags.DEFINE_string('ldap_url', 'ldap://localhost', 'Point this at your ldap server')
+flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password')
+flags.DEFINE_string('user_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user')
+flags.DEFINE_string('user_unit', 'Users', 'OID for Users')
+flags.DEFINE_string('ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users')
+
+flags.DEFINE_string('ldap_sysadmin',
+ 'cn=sysadmins,ou=Groups,dc=example,dc=com', 'OU for Sysadmins')
+flags.DEFINE_string('ldap_netadmin',
+ 'cn=netadmins,ou=Groups,dc=example,dc=com', 'OU for NetAdmins')
+flags.DEFINE_string('ldap_cloudadmin',
+ 'cn=cloudadmins,ou=Groups,dc=example,dc=com', 'OU for Cloud Admins')
+flags.DEFINE_string('ldap_itsec',
+ 'cn=itsec,ou=Groups,dc=example,dc=com', 'OU for ItSec')
+
+flags.DEFINE_string('credentials_template',
+ utils.abspath('auth/novarc.template'),
+ 'Template for creating users rc file')
+flags.DEFINE_string('credential_key_file', 'pk.pem',
+ 'Filename of private key in credentials zip')
+flags.DEFINE_string('credential_cert_file', 'cert.pem',
+ 'Filename of certificate in credentials zip')
+flags.DEFINE_string('credential_rc_file', 'novarc',
+ 'Filename of rc in credentials zip')
+
+_log = logging.getLogger('auth')
+_log.setLevel(logging.WARN)
+
+
+
+class UserError(exception.ApiError):
+ pass
+
+class InvalidKeyPair(exception.ApiError):
+ pass
+
+class User(object):
+ def __init__(self, id, name, access, secret, admin):
+ self.manager = UserManager.instance()
+ self.id = id
+ self.name = name
+ self.access = access
+ self.secret = secret
+ self.admin = admin
+ self.keeper = datastore.Keeper(prefix="user")
+
+
+ def is_admin(self):
+ return self.admin
+
+ def has_role(self, role_type):
+ return self.manager.has_role(self.id, role_type)
+
+ def is_authorized(self, owner_id, action=None):
+ if self.is_admin() or owner_id == self.id:
+ return True
+ if action == None:
+ return False
+ project = None #(Fixme)
+ target_object = None # (Fixme, should be passed in)
+ return simplerbac.is_allowed(action, self, project, target_object)
+
+ def get_credentials(self):
+ rc = self.generate_rc()
+ private_key, signed_cert = self.generate_x509_cert()
+
+ tmpdir = tempfile.mkdtemp()
+ zf = os.path.join(tmpdir, "temp.zip")
+ zippy = zipfile.ZipFile(zf, 'w')
+ zippy.writestr(FLAGS.credential_rc_file, rc)
+ zippy.writestr(FLAGS.credential_key_file, private_key)
+ zippy.writestr(FLAGS.credential_cert_file, signed_cert)
+ zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(self.id))
+ zippy.close()
+ with open(zf, 'rb') as f:
+ buffer = f.read()
+
+ shutil.rmtree(tmpdir)
+ return buffer
+
+
+ def generate_rc(self):
+ rc = open(FLAGS.credentials_template).read()
+ rc = rc % { 'access': self.access,
+ 'secret': self.secret,
+ 'ec2': FLAGS.ec2_url,
+ 's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port),
+ 'nova': FLAGS.ca_file,
+ 'cert': FLAGS.credential_cert_file,
+ 'key': FLAGS.credential_key_file,
+ }
+ return rc
+
+ def generate_key_pair(self, name):
+ return self.manager.generate_key_pair(self.id, name)
+
+ def generate_x509_cert(self):
+ return self.manager.generate_x509_cert(self.id)
+
+ def create_key_pair(self, name, public_key, fingerprint):
+ return self.manager.create_key_pair(self.id,
+ name,
+ public_key,
+ fingerprint)
+
+ def get_key_pair(self, name):
+ return self.manager.get_key_pair(self.id, name)
+
+ def delete_key_pair(self, name):
+ return self.manager.delete_key_pair(self.id, name)
+
+ def get_key_pairs(self):
+ return self.manager.get_key_pairs(self.id)
+
+class KeyPair(object):
+ def __init__(self, name, owner, public_key, fingerprint):
+ self.manager = UserManager.instance()
+ self.owner = owner
+ self.name = name
+ self.public_key = public_key
+ self.fingerprint = fingerprint
+
+ def delete(self):
+ return self.manager.delete_key_pair(self.owner, self.name)
+
+class UserManager(object):
+ def __init__(self):
+ if hasattr(self.__class__, '_instance'):
+ raise Exception('Attempted to instantiate singleton')
+
+ @classmethod
+ def instance(cls):
+ if not hasattr(cls, '_instance'):
+ inst = UserManager()
+ cls._instance = inst
+ if FLAGS.fake_users:
+ try:
+ inst.create_user('fake', 'fake', 'fake')
+ except: pass
+ try:
+ inst.create_user('user', 'user', 'user')
+ except: pass
+ try:
+ inst.create_user('admin', 'admin', 'admin', True)
+ except: pass
+ return cls._instance
+
+ def authenticate(self, params, signature, verb='GET', server_string='127.0.0.1:8773', path='/'):
+ # TODO: Check for valid timestamp
+ access_key = params['AWSAccessKeyId']
+ user = self.get_user_from_access_key(access_key)
+ if user == None:
+ return None
+ # hmac can't handle unicode, so encode ensures that secret isn't unicode
+ expected_signature = signer.Signer(user.secret.encode()).generate(params, verb, server_string, path)
+ _log.debug('user.secret: %s', user.secret)
+ _log.debug('expected_signature: %s', expected_signature)
+ _log.debug('signature: %s', signature)
+ if signature == expected_signature:
+ return user
+
+ def has_role(self, user, role, project=None):
+ # Map role to ldap group
+ group = FLAGS.__getitem__("ldap_%s" % role)
+ with LDAPWrapper() as conn:
+ return conn.is_member_of(user, group)
+
+ def add_role(self, user, role, project=None):
+ # TODO: Project-specific roles
+ group = FLAGS.__getitem__("ldap_%s" % role)
+ with LDAPWrapper() as conn:
+ return conn.add_to_group(user, group)
+
+ def get_user(self, uid):
+ with LDAPWrapper() as conn:
+ return conn.find_user(uid)
+
+ def get_user_from_access_key(self, access_key):
+ with LDAPWrapper() as conn:
+ return conn.find_user_by_access_key(access_key)
+
+ def get_users(self):
+ with LDAPWrapper() as conn:
+ return conn.find_users()
+
+ def create_user(self, uid, access=None, secret=None, admin=False):
+ if access == None: access = str(uuid.uuid4())
+ if secret == None: secret = str(uuid.uuid4())
+ with LDAPWrapper() as conn:
+ u = conn.create_user(uid, access, secret, admin)
+ return u
+
+ def delete_user(self, uid):
+ with LDAPWrapper() as conn:
+ conn.delete_user(uid)
+
+ def generate_key_pair(self, uid, key_name):
+ # generating key pair is slow so delay generation
+ # until after check
+ with LDAPWrapper() as conn:
+ if not conn.user_exists(uid):
+ raise UserError("User " + uid + " doesn't exist")
+ if conn.key_pair_exists(uid, key_name):
+ raise InvalidKeyPair("The keypair '" +
+ key_name +
+ "' already exists.",
+ "Duplicate")
+ private_key, public_key, fingerprint = crypto.generate_key_pair()
+ self.create_key_pair(uid, key_name, public_key, fingerprint)
+ return private_key, fingerprint
+
+ def create_key_pair(self, uid, key_name, public_key, fingerprint):
+ with LDAPWrapper() as conn:
+ return conn.create_key_pair(uid, key_name, public_key, fingerprint)
+
+ def get_key_pair(self, uid, key_name):
+ with LDAPWrapper() as conn:
+ return conn.find_key_pair(uid, key_name)
+
+ def get_key_pairs(self, uid):
+ with LDAPWrapper() as conn:
+ return conn.find_key_pairs(uid)
+
+ def delete_key_pair(self, uid, key_name):
+ with LDAPWrapper() as conn:
+ conn.delete_key_pair(uid, key_name)
+
+ def get_signed_zip(self, uid):
+ user = self.get_user(uid)
+ return user.get_credentials()
+
+ def generate_x509_cert(self, uid):
+ (private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(uid))
+ # TODO - This should be async call back to the cloud controller
+ signed_cert = crypto.sign_csr(csr, uid)
+ return (private_key, signed_cert)
+
+ def sign_cert(self, csr, uid):
+ return crypto.sign_csr(csr, uid)
+
+ def __cert_subject(self, uid):
+ return "/C=US/ST=California/L=The_Mission/O=AnsoLabs/OU=Nova/CN=%s-%s" % (uid, str(datetime.datetime.utcnow().isoformat()))
+
+
+class LDAPWrapper(object):
+ def __init__(self):
+ self.user = FLAGS.user_dn
+ self.passwd = FLAGS.ldap_password
+
+ def __enter__(self):
+ self.connect()
+ return self
+
+ def __exit__(self, type, value, traceback):
+ #logging.info('type, value, traceback: %s, %s, %s', type, value, traceback)
+ self.conn.unbind_s()
+ return False
+
+ def connect(self):
+ """ connect to ldap as admin user """
+ if FLAGS.fake_users:
+ self.conn = fakeldap.initialize(FLAGS.ldap_url)
+ else:
+ assert(ldap.__name__ != 'fakeldap')
+ self.conn = ldap.initialize(FLAGS.ldap_url)
+ self.conn.simple_bind_s(self.user, self.passwd)
+
+ def find_object(self, dn, query = None):
+ objects = self.find_objects(dn, query)
+ if len(objects) == 0:
+ return None
+ return objects[0]
+
+ def find_objects(self, dn, query = None):
+ try:
+ res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query)
+ except Exception:
+ return []
+ # just return the attributes
+ return [x[1] for x in res]
+
+ def find_users(self):
+ attrs = self.find_objects(FLAGS.ldap_subtree, '(objectclass=novaUser)')
+ return [self.__to_user(attr) for attr in attrs]
+
+ def find_key_pairs(self, uid):
+ dn = 'uid=%s,%s' % (uid, FLAGS.ldap_subtree)
+ attrs = self.find_objects(dn, '(objectclass=novaKeyPair)')
+ return [self.__to_key_pair(uid, attr) for attr in attrs]
+
+ def find_user(self, name):
+ dn = 'uid=%s,%s' % (name, FLAGS.ldap_subtree)
+ attr = self.find_object(dn, '(objectclass=novaUser)')
+ return self.__to_user(attr)
+
+ def user_exists(self, name):
+ return self.find_user(name) != None
+
+ def find_key_pair(self, uid, key_name):
+ dn = 'cn=%s,uid=%s,%s' % (key_name,
+ uid,
+ FLAGS.ldap_subtree)
+ attr = self.find_object(dn, '(objectclass=novaKeyPair)')
+ return self.__to_key_pair(uid, attr)
+
+ def delete_key_pairs(self, uid):
+ keys = self.find_key_pairs(uid)
+ if keys != None:
+ for key in keys:
+ self.delete_key_pair(uid, key.name)
+
+ def key_pair_exists(self, uid, key_name):
+ return self.find_key_pair(uid, key_name) != None
+
+ def create_user(self, name, access_key, secret_key, is_admin):
+ if self.user_exists(name):
+ raise UserError("LDAP user " + name + " already exists")
+ attr = [
+ ('objectclass', ['person',
+ 'organizationalPerson',
+ 'inetOrgPerson',
+ 'novaUser']),
+ ('ou', [FLAGS.user_unit]),
+ ('uid', [name]),
+ ('sn', [name]),
+ ('cn', [name]),
+ ('secretKey', [secret_key]),
+ ('accessKey', [access_key]),
+ ('isAdmin', [str(is_admin).upper()]),
+ ]
+ self.conn.add_s('uid=%s,%s' % (name, FLAGS.ldap_subtree),
+ attr)
+ return self.__to_user(dict(attr))
+
+ def create_project(self, name, project_manager):
+ # PM can be user object or string containing DN
+ pass
+
+ def is_member_of(self, name, group):
+ return True
+
+ def add_to_group(self, name, group):
+ pass
+
+ def remove_from_group(self, name, group):
+ pass
+
+ def create_key_pair(self, uid, key_name, public_key, fingerprint):
+ """create's a public key in the directory underneath the user"""
+ # TODO(vish): possibly refactor this to store keys in their own ou
+ # and put dn reference in the user object
+ attr = [
+ ('objectclass', ['novaKeyPair']),
+ ('cn', [key_name]),
+ ('sshPublicKey', [public_key]),
+ ('keyFingerprint', [fingerprint]),
+ ]
+ self.conn.add_s('cn=%s,uid=%s,%s' % (key_name,
+ uid,
+ FLAGS.ldap_subtree),
+ attr)
+ return self.__to_key_pair(uid, dict(attr))
+
+ def find_user_by_access_key(self, access):
+ query = '(' + 'accessKey' + '=' + access + ')'
+ dn = FLAGS.ldap_subtree
+ return self.__to_user(self.find_object(dn, query))
+
+ def delete_key_pair(self, uid, key_name):
+ if not self.key_pair_exists(uid, key_name):
+ raise UserError("Key Pair " +
+ key_name +
+ " doesn't exist for user " +
+ uid)
+ self.conn.delete_s('cn=%s,uid=%s,%s' % (key_name, uid,
+ FLAGS.ldap_subtree))
+
+ def delete_user(self, name):
+ if not self.user_exists(name):
+ raise UserError("User " +
+ name +
+ " doesn't exist")
+ self.delete_key_pairs(name)
+ self.conn.delete_s('uid=%s,%s' % (name,
+ FLAGS.ldap_subtree))
+
+ def __to_user(self, attr):
+ if attr == None:
+ return None
+ return User(
+ id = attr['uid'][0],
+ name = attr['uid'][0],
+ access = attr['accessKey'][0],
+ secret = attr['secretKey'][0],
+ admin = (attr['isAdmin'][0] == 'TRUE')
+ )
+
+ def __to_key_pair(self, owner, attr):
+ if attr == None:
+ return None
+ return KeyPair(
+ owner = owner,
+ name = attr['cn'][0],
+ public_key = attr['sshPublicKey'][0],
+ fingerprint = attr['keyFingerprint'][0],
+ )
diff --git a/nova/compute/__init__.py b/nova/compute/__init__.py
new file mode 100644
index 0000000000..e8a6921e7e
--- /dev/null
+++ b/nova/compute/__init__.py
@@ -0,0 +1,28 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:mod:`nova.compute` -- Compute Nodes using LibVirt
+=====================================================
+
+.. automodule:: nova.compute
+ :platform: Unix
+ :synopsis: Thin wrapper around libvirt for VM mgmt.
+.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
+.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
+.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
+.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
+.. moduleauthor:: Manish Singh <yosh@gimp.org>
+.. moduleauthor:: Andy Smith <andy@anarkystic.com>
+""" \ No newline at end of file
diff --git a/nova/compute/disk.py b/nova/compute/disk.py
new file mode 100644
index 0000000000..d3eeb951fe
--- /dev/null
+++ b/nova/compute/disk.py
@@ -0,0 +1,122 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Utility methods to resize, repartition, and modify disk images.
+Includes injection of SSH PGP keys into authorized_keys file.
+"""
+
+import logging
+import os
+import tempfile
+
+from nova.exception import Error
+from nova.utils import execute
+
+def partition(infile, outfile, local_bytes=0, local_type='ext2'):
+ """Takes a single partition represented by infile and writes a bootable drive image into outfile.
+ The first 63 sectors (0-62) of the resulting image is a master boot record.
+ Infile becomes the first primary partition.
+ If local bytes is specified, a second primary partition is created and formatted as ext2.
+ In the diagram below, dashes represent drive sectors.
+ 0 a b c d e
+ +-----+------. . .-------+------. . .------+
+ | mbr | primary partiton | local partition |
+ +-----+------. . .-------+------. . .------+
+ """
+ sector_size = 512
+ file_size = os.path.getsize(infile)
+ if file_size % sector_size != 0:
+ logging.warn("Input partition size not evenly divisible by sector size: %d / %d" (file_size, sector_size))
+ primary_sectors = file_size / sector_size
+ if local_bytes % sector_size != 0:
+ logging.warn("Bytes for local storage not evenly divisible by sector size: %d / %d" (local_bytes, sector_size))
+ local_sectors = local_bytes / sector_size
+
+ mbr_last = 62 # a
+ primary_first = mbr_last + 1 # b
+ primary_last = primary_first + primary_sectors # c
+ local_first = primary_last + 1 # d
+ local_last = local_first + local_sectors # e
+ last_sector = local_last # e
+
+ # create an empty file
+ execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' % (outfile, last_sector, sector_size))
+
+ # make mbr partition
+ execute('parted --script %s mklabel msdos' % outfile)
+
+ # make primary partition
+ execute('parted --script %s mkpart primary %ds %ds' % (outfile, primary_first, primary_last))
+
+ # make local partition
+ if local_bytes > 0:
+ execute('parted --script %s mkpartfs primary %s %ds %ds' % (outfile, local_type, local_first, local_last))
+
+ # copy file into partition
+ execute('dd if=%s of=%s bs=%d seek=%d conv=notrunc,fsync' % (infile, outfile, sector_size, primary_first))
+
+
+def inject_key(key, image, partition=None):
+ """Injects a ssh key into a disk image.
+ It adds the specified key to /root/.ssh/authorized_keys
+ it will mount the image as a fully partitioned disk and attempt to inject into the specified partition number.
+ If partition is not specified it mounts the image as a single partition.
+ """
+ out, err = execute('sudo losetup -f --show %s' % image)
+ if err:
+ raise Error('Could not attach image to loopback: %s' % err)
+ device = out.strip()
+ try:
+ if not partition is None:
+ # create partition
+ out, err = execute('sudo kpartx -a %s' % device)
+ if err:
+ raise Error('Failed to load partition: %s' % err)
+ mapped_device = '/dev/mapper/%sp%s' % ( device.split('/')[-1] , partition )
+ else:
+ mapped_device = device
+ out, err = execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device)
+
+ tmpdir = tempfile.mkdtemp()
+ try:
+ # mount loopback to dir
+ out, err = execute('sudo mount %s %s' % (mapped_device, tmpdir))
+ if err:
+ raise Error('Failed to mount filesystem: %s' % err)
+
+ try:
+ # inject key file
+ _inject_into_fs(key, tmpdir)
+ finally:
+ # unmount device
+ execute('sudo umount %s' % mapped_device)
+ finally:
+ # remove temporary directory
+ os.rmdir(tmpdir)
+ if not partition is None:
+ # remove partitions
+ execute('sudo kpartx -d %s' % device)
+ finally:
+ # remove loopback
+ execute('sudo losetup -d %s' % device)
+
+def _inject_into_fs(key, fs):
+ sshdir = os.path.join(os.path.join(fs, 'root'), '.ssh')
+ execute('sudo mkdir %s' % sshdir) #error on existing dir doesn't matter
+ execute('sudo chown root %s' % sshdir)
+ execute('sudo chmod 700 %s' % sshdir)
+ keyfile = os.path.join(sshdir, 'authorized_keys')
+ execute('sudo bash -c "cat >> %s"' % keyfile, '\n' + key + '\n')
+
diff --git a/nova/compute/exception.py b/nova/compute/exception.py
new file mode 100644
index 0000000000..6fe8e381fe
--- /dev/null
+++ b/nova/compute/exception.py
@@ -0,0 +1,35 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Exceptions for Compute Node errors, mostly network addressing.
+"""
+
+from nova.exception import Error
+
+class NoMoreAddresses(Error):
+ pass
+
+class AddressNotAllocated(Error):
+ pass
+
+class AddressAlreadyAssociated(Error):
+ pass
+
+class AddressNotAssociated(Error):
+ pass
+
+class NotValidNetworkSize(Error):
+ pass
+
diff --git a/nova/compute/fakevirtinstance.xml b/nova/compute/fakevirtinstance.xml
new file mode 100644
index 0000000000..6036516bbe
--- /dev/null
+++ b/nova/compute/fakevirtinstance.xml
@@ -0,0 +1,43 @@
+<!--
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+ -->
+<domain type='kvm' id='100'>
+ <name>i-A9B8C7D6</name>
+ <uuid>12a345bc-67c8-901d-2e34-56f7g89012h3</uuid>
+ <memory>524288</memory>
+ <currentMemory>524288</currentMemory>
+ <vcpu>1</vcpu>
+ <os/>
+ <features>
+ <acpi/>
+ </features>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/kvm</emulator>
+ <disk type='file' device='disk'>
+ <source file='/var/lib/fakevirt/instances/i-A9B8C7D6/disk'/>
+ <target dev='sda' bus='scsi'/>
+ </disk>
+ <interface type='bridge'>
+ <mac address='a0:1b:c2:3d:4e:f5'/>
+ <source bridge='fakebr2000'/>
+ <target dev='vnet1'/>
+ <model type='e1000'/>
+ </interface>
+ </devices>
+</domain> \ No newline at end of file
diff --git a/nova/compute/libvirt.xml.template b/nova/compute/libvirt.xml.template
new file mode 100644
index 0000000000..4cf6e8b104
--- /dev/null
+++ b/nova/compute/libvirt.xml.template
@@ -0,0 +1,46 @@
+<!--
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+ -->
+<domain type='kvm'>
+ <name>%(name)s</name>
+ <os>
+ <type>hvm</type>
+ <kernel>%(basepath)s/kernel</kernel>
+ <initrd>%(basepath)s/ramdisk</initrd>
+ <cmdline>root=/dev/vda1 console=ttyS0</cmdline>
+ </os>
+ <features>
+ <acpi/>
+ </features>
+ <memory>%(memory_kb)s</memory>
+ <vcpu>%(vcpus)s</vcpu>
+ <devices>
+ <emulator>/usr/bin/kvm</emulator>
+ <disk type='file'>
+ <source file='%(basepath)s/disk'/>
+ <target dev='vda' bus='virtio'/>
+ </disk>
+ <interface type='bridge'>
+ <source bridge='%(bridge_name)s'/>
+ <mac address='%(mac_address)s'/>
+ <!-- <model type='virtio'/> CANT RUN virtio network right now -->
+ </interface>
+ <serial type="file">
+ <source path='%(basepath)s/console.log'/>
+ <target port='1'/>
+ </serial>
+ </devices>
+ <nova>%(nova)s</nova>
+</domain>
diff --git a/nova/compute/linux_net.py b/nova/compute/linux_net.py
new file mode 100644
index 0000000000..0983241f95
--- /dev/null
+++ b/nova/compute/linux_net.py
@@ -0,0 +1,146 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import signal
+import os
+import nova.utils
+import subprocess
+
+# todo(ja): does the definition of network_path belong here?
+
+from nova import flags
+FLAGS=flags.FLAGS
+
+def execute(cmd):
+ if FLAGS.fake_network:
+ print "FAKE NET: %s" % cmd
+ return "fake", 0
+ else:
+ nova.utils.execute(cmd)
+
+def runthis(desc, cmd):
+ if FLAGS.fake_network:
+ execute(cmd)
+ else:
+ nova.utils.runthis(desc,cmd)
+
+def Popen(cmd):
+ if FLAGS.fake_network:
+ execute(' '.join(cmd))
+ else:
+ subprocess.Popen(cmd)
+
+
+def device_exists(device):
+ (out, err) = execute("ifconfig %s" % device)
+ return not err
+
+def confirm_rule(cmd):
+ execute("sudo iptables --delete %s" % (cmd))
+ execute("sudo iptables -I %s" % (cmd))
+
+def remove_rule(cmd):
+ execute("sudo iptables --delete %s" % (cmd))
+
+def bind_public_ip(ip, interface):
+ runthis("Binding IP to interface: %s", "sudo ip addr add %s dev %s" % (ip, interface))
+
+def vlan_create(net):
+ """ create a vlan on on a bridge device unless vlan already exists """
+ if not device_exists("vlan%s" % net.vlan):
+ execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD")
+ execute("sudo vconfig add %s %s" % (net.bridge_dev, net.vlan))
+ execute("sudo ifconfig vlan%s up" % (net.vlan))
+
+def bridge_create(net):
+ """ create a bridge on a vlan unless it already exists """
+ if not device_exists(net.bridge_name):
+ execute("sudo brctl addbr %s" % (net.bridge_name))
+ # execute("sudo brctl setfd %s 0" % (net.bridge_name))
+ # execute("sudo brctl setageing %s 10" % (net.bridge_name))
+ execute("sudo brctl stp %s off" % (net.bridge_name))
+ execute("sudo brctl addif %s vlan%s" % (net.bridge_name, net.vlan))
+ if net.bridge_gets_ip:
+ execute("sudo ifconfig %s %s broadcast %s netmask %s up" % \
+ (net.bridge_name, net.gateway, net.broadcast, net.netmask))
+ confirm_rule("FORWARD --in-interface %s -j ACCEPT" % (net.bridge_name))
+ else:
+ execute("sudo ifconfig %s up" % net.bridge_name)
+
+def dnsmasq_cmd(net):
+ cmd = ['sudo dnsmasq',
+ ' --strict-order',
+ ' --bind-interfaces',
+ ' --conf-file=',
+ ' --pid-file=%s' % dhcp_file(net.vlan, 'pid'),
+ ' --listen-address=%s' % net.dhcp_listen_address,
+ ' --except-interface=lo',
+ ' --dhcp-range=%s,%s,120s' % (net.dhcp_range_start, net.dhcp_range_end),
+ ' --dhcp-lease-max=61',
+ ' --dhcp-hostsfile=%s' % dhcp_file(net.vlan, 'conf'),
+ ' --dhcp-leasefile=%s' % dhcp_file(net.vlan, 'leases')]
+ return ''.join(cmd)
+
+def hostDHCP(network, host):
+ idx = host['address'].split(".")[-1] # Logically, the idx of instances they've launched in this net
+ return "%s,%s-%s-%s.novalocal,%s" % \
+ (host['mac'], host['user_id'], network.vlan, idx, host['address'])
+
+# todo(ja): if the system has restarted or pid numbers have wrapped
+# then you cannot be certain that the pid refers to the
+# dnsmasq. As well, sending a HUP only reloads the hostfile,
+# so any configuration options (like dchp-range, vlan, ...)
+# aren't reloaded
+def start_dnsmasq(network):
+ """ (re)starts a dnsmasq server for a given network
+
+ if a dnsmasq instance is already running then send a HUP
+ signal causing it to reload, otherwise spawn a new instance
+ """
+ with open(dhcp_file(network.vlan, 'conf'), 'w') as f:
+ for host_name in network.hosts:
+ f.write("%s\n" % hostDHCP(network, network.hosts[host_name]))
+
+ pid = dnsmasq_pid_for(network)
+
+ # if dnsmasq is already running, then tell it to reload
+ if pid:
+ # todo(ja): use "/proc/%d/cmdline" % (pid) to determine if pid refers
+ # correct dnsmasq process
+ try:
+ os.kill(pid, signal.SIGHUP)
+ return
+ except Exception, e:
+ logging.debug("Killing dnsmasq threw %s", e)
+
+ # otherwise delete the existing leases file and start dnsmasq
+ lease_file = dhcp_file(network.vlan, 'leases')
+ if os.path.exists(lease_file):
+ os.unlink(lease_file)
+
+ Popen(dnsmasq_cmd(network).split(" "))
+
+def stop_dnsmasq(network):
+ """ stops the dnsmasq instance for a given network """
+ pid = dnsmasq_pid_for(network)
+
+ if pid:
+ os.kill(pid, signal.SIGTERM)
+
+def dhcp_file(vlan, kind):
+ """ return path to a pid, leases or conf file for a vlan """
+
+ return os.path.abspath("%s/nova-%s.%s" % (FLAGS.networks_path, vlan, kind))
+
+def dnsmasq_pid_for(network):
+ """ the pid for prior dnsmasq instance for a vlan,
+ returns None if no pid file exists
+
+ if machine has rebooted pid might be incorrect (caller should check)
+ """
+
+ pid_file = dhcp_file(network.vlan, 'pid')
+
+ if os.path.exists(pid_file):
+ with open(pid_file, 'r') as f:
+ return int(f.read())
+
diff --git a/nova/compute/model.py b/nova/compute/model.py
new file mode 100644
index 0000000000..78ed3a1012
--- /dev/null
+++ b/nova/compute/model.py
@@ -0,0 +1,203 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4 expandtab
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Datastore Model objects for Compute Instances, with
+InstanceDirectory manager.
+
+# Create a new instance?
+>>> InstDir = InstanceDirectory()
+>>> inst = InstDir.new()
+>>> inst.destroy()
+True
+>>> inst = InstDir['i-123']
+>>> inst['ip'] = "192.168.0.3"
+>>> inst['owner_id'] = "projectA"
+>>> inst.save()
+True
+
+>>> InstDir['i-123']
+<Instance:i-123>
+>>> InstDir.all.next()
+<Instance:i-123>
+
+>>> inst.destroy()
+True
+"""
+
+from nova import vendor
+
+from nova import datastore
+from nova import flags
+from nova import utils
+
+
+FLAGS = flags.FLAGS
+
+
+# TODO(ja): singleton instance of the directory
+class InstanceDirectory(object):
+ """an api for interacting with the global state of instances """
+ def __init__(self):
+ self.keeper = datastore.Keeper(FLAGS.instances_prefix)
+
+ def get(self, instance_id):
+ """ returns an instance object for a given id """
+ return Instance(instance_id)
+
+ def __getitem__(self, item):
+ return self.get(item)
+
+ def by_project(self, project):
+ """ returns a list of instance objects for a project """
+ for instance_id in self.keeper['project:%s:instances' % project]:
+ yield Instance(instance_id)
+
+ def by_node(self, node_id):
+ """ returns a list of instances for a node """
+ for instance in self.all:
+ if instance['node_name'] == node_id:
+ yield instance
+
+ def by_ip(self, ip_address):
+ """ returns an instance object that is using the IP """
+ for instance in self.all:
+ if instance['private_dns_name'] == ip_address:
+ return instance
+ return None
+
+ def by_volume(self, volume_id):
+ """ returns the instance a volume is attached to """
+ pass
+
+ def exists(self, instance_id):
+ if instance_id in self.keeper['instances']:
+ return True
+ return False
+
+ @property
+ def all(self):
+ """ returns a list of all instances """
+ instances = self.keeper['instances']
+ if instances != None:
+ for instance_id in self.keeper['instances']:
+ yield Instance(instance_id)
+
+ def new(self):
+ """ returns an empty Instance object, with ID """
+ instance_id = utils.generate_uid('i')
+ return self.get(instance_id)
+
+
+
+class Instance(object):
+ """ Wrapper around stored properties of an instance """
+
+ def __init__(self, instance_id):
+ """ loads an instance from the datastore if exists """
+ self.keeper = datastore.Keeper(FLAGS.instances_prefix)
+ self.instance_id = instance_id
+ self.initial_state = {}
+ self.state = self.keeper[self.__redis_key]
+ if self.state:
+ self.initial_state = self.state
+ else:
+ self.state = {'state' : 'pending',
+ 'instance_id' : instance_id,
+ 'node_name' : 'unassigned',
+ 'owner_id' : 'unassigned' }
+
+ @property
+ def __redis_key(self):
+ """ Magic string for instance keys """
+ return 'instance:%s' % self.instance_id
+
+ def __repr__(self):
+ return "<Instance:%s>" % self.instance_id
+
+ def get(self, item, default):
+ return self.state.get(item, default)
+
+ def __getitem__(self, item):
+ return self.state[item]
+
+ def __setitem__(self, item, val):
+ self.state[item] = val
+ return self.state[item]
+
+ def __delitem__(self, item):
+ """ We don't support this """
+ raise Exception("Silly monkey, Instances NEED all their properties.")
+
+ def save(self):
+ """ update the directory with the state from this instance
+ make sure you've set the owner_id before you call save
+ for the first time.
+ """
+ # TODO(ja): implement hmset in redis-py and use it
+ # instead of multiple calls to hset
+ state = self.keeper[self.__redis_key]
+ if not state:
+ state = {}
+ for key, val in self.state.iteritems():
+ # if (not self.initial_state.has_key(key)
+ # or self.initial_state[key] != val):
+ state[key] = val
+ self.keeper[self.__redis_key] = state
+ if self.initial_state == {}:
+ self.keeper.set_add('project:%s:instances' % self.state['owner_id'],
+ self.instance_id)
+ self.keeper.set_add('instances', self.instance_id)
+ self.initial_state = self.state
+ return True
+
+ def destroy(self):
+ """ deletes all related records from datastore.
+ does NOT do anything to running libvirt state.
+ """
+ self.keeper.set_remove('project:%s:instances' % self.state['owner_id'],
+ self.instance_id)
+ del self.keeper[self.__redis_key]
+ self.keeper.set_remove('instances', self.instance_id)
+ return True
+
+ @property
+ def volumes(self):
+ """ returns a list of attached volumes """
+ pass
+
+ @property
+ def reservation(self):
+ """ Returns a reservation object """
+ pass
+
+# class Reservation(object):
+# """ ORM wrapper for a batch of launched instances """
+# def __init__(self):
+# pass
+#
+# def userdata(self):
+# """ """
+# pass
+#
+#
+# class NodeDirectory(object):
+# def __init__(self):
+# pass
+#
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/nova/compute/network.py b/nova/compute/network.py
new file mode 100644
index 0000000000..612295f272
--- /dev/null
+++ b/nova/compute/network.py
@@ -0,0 +1,520 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Classes for network control, including VLANs, DHCP, and IP allocation.
+"""
+
+import json
+import logging
+import os
+
+# TODO(termie): clean up these imports
+from nova import vendor
+import IPy
+
+from nova import datastore
+import nova.exception
+from nova.compute import exception
+from nova import flags
+from nova import utils
+from nova.auth import users
+
+import linux_net
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('net_libvirt_xml_template',
+ utils.abspath('compute/net.libvirt.xml.template'),
+ 'Template file for libvirt networks')
+flags.DEFINE_string('networks_path', utils.abspath('../networks'),
+ 'Location to keep network config files')
+flags.DEFINE_integer('public_vlan', 1, 'VLAN for public IP addresses')
+flags.DEFINE_string('public_interface', 'vlan1', 'Interface for public IP addresses')
+flags.DEFINE_string('bridge_dev', 'eth1',
+ 'network device for bridges')
+flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks')
+flags.DEFINE_integer('vlan_end', 4093, 'Last VLAN for private networks')
+flags.DEFINE_integer('network_size', 256, 'Number of addresses in each private subnet')
+flags.DEFINE_string('public_range', '4.4.4.0/24', 'Public IP address block')
+flags.DEFINE_string('private_range', '10.0.0.0/8', 'Private IP address block')
+
+
+# HACK(vish): to delay _get_keeper() loading
+def _get_keeper():
+ if _get_keeper.keeper == None:
+ _get_keeper.keeper = datastore.Keeper(prefix="net")
+ return _get_keeper.keeper
+_get_keeper.keeper = None
+
+logging.getLogger().setLevel(logging.DEBUG)
+
+# CLEANUP:
+# TODO(ja): use singleton for usermanager instead of self.manager in vlanpool et al
+# TODO(ja): does vlanpool "keeper" need to know the min/max - shouldn't FLAGS always win?
+
+class Network(object):
+ def __init__(self, *args, **kwargs):
+ self.bridge_gets_ip = False
+ try:
+ os.makedirs(FLAGS.networks_path)
+ except Exception, err:
+ pass
+ self.load(**kwargs)
+
+ def to_dict(self):
+ return {'vlan': self.vlan,
+ 'network': self.network_str,
+ 'hosts': self.hosts}
+
+ def load(self, **kwargs):
+ self.network_str = kwargs.get('network', "192.168.100.0/24")
+ self.hosts = kwargs.get('hosts', {})
+ self.vlan = kwargs.get('vlan', 100)
+ self.name = "nova-%s" % (self.vlan)
+ self.network = IPy.IP(self.network_str)
+ self.gateway = self.network[1]
+ self.netmask = self.network.netmask()
+ self.broadcast = self.network.broadcast()
+ self.bridge_name = "br%s" % (self.vlan)
+
+ def __str__(self):
+ return json.dumps(self.to_dict())
+
+ def __unicode__(self):
+ return json.dumps(self.to_dict())
+
+ @classmethod
+ def from_dict(cls, args):
+ for arg in args.keys():
+ value = args[arg]
+ del args[arg]
+ args[str(arg)] = value
+ self = cls(**args)
+ return self
+
+ @classmethod
+ def from_json(cls, json_string):
+ parsed = json.loads(json_string)
+ return cls.from_dict(parsed)
+
+ def range(self):
+ for idx in range(3, len(self.network)-2):
+ yield self.network[idx]
+
+ def allocate_ip(self, user_id, mac):
+ for ip in self.range():
+ address = str(ip)
+ if not address in self.hosts.keys():
+ logging.debug("Allocating IP %s to %s" % (address, user_id))
+ self.hosts[address] = {
+ "address" : address, "user_id" : user_id, 'mac' : mac
+ }
+ self.express(address=address)
+ return address
+ raise exception.NoMoreAddresses()
+
+ def deallocate_ip(self, ip_str):
+ if not ip_str in self.hosts.keys():
+ raise exception.AddressNotAllocated()
+ del self.hosts[ip_str]
+ # TODO(joshua) SCRUB from the leases file somehow
+ self.deexpress(address=ip_str)
+
+ def list_addresses(self):
+ for address in self.hosts.values():
+ yield address
+
+ def express(self, address=None):
+ pass
+
+ def deexpress(self, address=None):
+ pass
+
+
+class Vlan(Network):
+ """
+ VLAN configuration, that when expressed creates the vlan
+
+ properties:
+
+ vlan - integer (example: 42)
+ bridge_dev - string (example: eth0)
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(Vlan, self).__init__(*args, **kwargs)
+ self.bridge_dev = FLAGS.bridge_dev
+
+ def express(self, address=None):
+ super(Vlan, self).express(address=address)
+ try:
+ logging.debug("Starting VLAN inteface for %s network" % (self.vlan))
+ linux_net.vlan_create(self)
+ except:
+ pass
+
+
+class VirtNetwork(Vlan):
+ """
+ Virtual Network that can export libvirt configuration or express itself to
+ create a bridge (with or without an IP address/netmask/gateway)
+
+ properties:
+ bridge_name - string (example value: br42)
+ vlan - integer (example value: 42)
+ bridge_gets_ip - boolean used during bridge creation
+
+ if bridge_gets_ip then network address for bridge uses the properties:
+ gateway
+ broadcast
+ netmask
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(VirtNetwork, self).__init__(*args, **kwargs)
+
+ def virtXML(self):
+ """ generate XML for libvirt network """
+
+ libvirt_xml = open(FLAGS.net_libvirt_xml_template).read()
+ xml_info = {'name' : self.name,
+ 'bridge_name' : self.bridge_name,
+ 'device' : "vlan%s" % (self.vlan),
+ 'gateway' : self.gateway,
+ 'netmask' : self.netmask,
+ }
+ libvirt_xml = libvirt_xml % xml_info
+ return libvirt_xml
+
+ def express(self, address=None):
+ """ creates a bridge device on top of the Vlan """
+ super(VirtNetwork, self).express(address=address)
+ try:
+ logging.debug("Starting Bridge inteface for %s network" % (self.vlan))
+ linux_net.bridge_create(self)
+ except:
+ pass
+
+class DHCPNetwork(VirtNetwork):
+ """
+ properties:
+ dhcp_listen_address: the ip of the gateway / dhcp host
+ dhcp_range_start: the first ip to give out
+ dhcp_range_end: the last ip to give out
+ """
+ def __init__(self, *args, **kwargs):
+ super(DHCPNetwork, self).__init__(*args, **kwargs)
+ logging.debug("Initing DHCPNetwork object...")
+ self.bridge_gets_ip = True
+ self.dhcp_listen_address = self.network[1]
+ self.dhcp_range_start = self.network[3]
+ self.dhcp_range_end = self.network[-2]
+
+ def express(self, address=None):
+ super(DHCPNetwork, self).express(address=address)
+ if len(self.hosts.values()) > 0:
+ logging.debug("Starting dnsmasq server for network with vlan %s" % self.vlan)
+ linux_net.start_dnsmasq(self)
+ else:
+ logging.debug("Not launching dnsmasq cause I don't think we have any hosts.")
+
+ def deexpress(self, address=None):
+ # if this is the last address, stop dns
+ super(DHCPNetwork, self).deexpress(address=address)
+ if len(self.hosts.values()) == 0:
+ linux_net.stop_dnsmasq(self)
+ else:
+ linux_net.start_dnsmasq(self)
+
+
+class PrivateNetwork(DHCPNetwork):
+ def __init__(self, **kwargs):
+ super(PrivateNetwork, self).__init__(**kwargs)
+ # self.express()
+
+ def to_dict(self):
+ return {'vlan': self.vlan,
+ 'network': self.network_str,
+ 'hosts': self.hosts}
+
+ def express(self, *args, **kwargs):
+ super(PrivateNetwork, self).express(*args, **kwargs)
+
+
+
+class PublicNetwork(Network):
+ def __init__(self, network="192.168.216.0/24", **kwargs):
+ super(PublicNetwork, self).__init__(network=network, **kwargs)
+ self.express()
+
+ def allocate_ip(self, user_id, mac):
+ for ip in self.range():
+ address = str(ip)
+ if not address in self.hosts.keys():
+ logging.debug("Allocating IP %s to %s" % (address, user_id))
+ self.hosts[address] = {
+ "address" : address, "user_id" : user_id, 'mac' : mac
+ }
+ self.express(address=address)
+ return address
+ raise exception.NoMoreAddresses()
+
+ def deallocate_ip(self, ip_str):
+ if not ip_str in self.hosts:
+ raise exception.AddressNotAllocated()
+ del self.hosts[ip_str]
+ # TODO(joshua) SCRUB from the leases file somehow
+ self.deexpress(address=ip_str)
+
+ def associate_address(self, public_ip, private_ip, instance_id):
+ if not public_ip in self.hosts:
+ raise exception.AddressNotAllocated()
+ for addr in self.hosts.values():
+ if addr.has_key('private_ip') and addr['private_ip'] == private_ip:
+ raise exception.AddressAlreadyAssociated()
+ if self.hosts[public_ip].has_key('private_ip'):
+ raise exception.AddressAlreadyAssociated()
+ self.hosts[public_ip]['private_ip'] = private_ip
+ self.hosts[public_ip]['instance_id'] = instance_id
+ self.express(address=public_ip)
+
+ def disassociate_address(self, public_ip):
+ if not public_ip in self.hosts:
+ raise exception.AddressNotAllocated()
+ if not self.hosts[public_ip].has_key('private_ip'):
+ raise exception.AddressNotAssociated()
+ self.deexpress(public_ip)
+ del self.hosts[public_ip]['private_ip']
+ del self.hosts[public_ip]['instance_id']
+ # TODO Express the removal
+
+ def deexpress(self, address):
+ addr = self.hosts[address]
+ public_ip = addr['address']
+ private_ip = addr['private_ip']
+ linux_net.remove_rule("PREROUTING -t nat -d %s -j DNAT --to %s" % (public_ip, private_ip))
+ linux_net.remove_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" % (private_ip, public_ip))
+ linux_net.remove_rule("FORWARD -d %s -p icmp -j ACCEPT" % (private_ip))
+ for (protocol, port) in [("tcp",80), ("tcp",22), ("udp",1194), ("tcp",443)]:
+ linux_net.remove_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT" % (private_ip, protocol, port))
+
+ def express(self, address=None):
+ logging.debug("Todo - need to create IPTables natting entries for this net.")
+ addresses = self.hosts.values()
+ if address:
+ addresses = [self.hosts[address]]
+ for addr in addresses:
+ if not addr.has_key('private_ip'):
+ continue
+ public_ip = addr['address']
+ private_ip = addr['private_ip']
+ linux_net.bind_public_ip(public_ip, FLAGS.public_interface)
+ linux_net.confirm_rule("PREROUTING -t nat -d %s -j DNAT --to %s" % (public_ip, private_ip))
+ linux_net.confirm_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" % (private_ip, public_ip))
+ # TODO: Get these from the secgroup datastore entries
+ linux_net.confirm_rule("FORWARD -d %s -p icmp -j ACCEPT" % (private_ip))
+ for (protocol, port) in [("tcp",80), ("tcp",22), ("udp",1194), ("tcp",443)]:
+ linux_net.confirm_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT" % (private_ip, protocol, port))
+
+
+class NetworkPool(object):
+ # TODO - Allocations need to be system global
+
+ def __init__(self):
+ self.network = IPy.IP(FLAGS.private_range)
+ netsize = FLAGS.network_size
+ if not netsize in [4,8,16,32,64,128,256,512,1024]:
+ raise exception.NotValidNetworkSize()
+ self.netsize = netsize
+ self.startvlan = FLAGS.vlan_start
+
+ def get_from_vlan(self, vlan):
+ start = (vlan-self.startvlan) * self.netsize
+ net_str = "%s-%s" % (self.network[start], self.network[start + self.netsize - 1])
+ logging.debug("Allocating %s" % net_str)
+ return net_str
+
+
+class VlanPool(object):
+ def __init__(self, **kwargs):
+ self.start = FLAGS.vlan_start
+ self.end = FLAGS.vlan_end
+ self.vlans = kwargs.get('vlans', {})
+ self.vlanpool = {}
+ self.manager = users.UserManager.instance()
+ for user_id, vlan in self.vlans.iteritems():
+ self.vlanpool[vlan] = user_id
+
+ def to_dict(self):
+ return {'vlans': self.vlans}
+
+ def __str__(self):
+ return json.dumps(self.to_dict())
+
+ def __unicode__(self):
+ return json.dumps(self.to_dict())
+
+ @classmethod
+ def from_dict(cls, args):
+ for arg in args.keys():
+ value = args[arg]
+ del args[arg]
+ args[str(arg)] = value
+ self = cls(**args)
+ return self
+
+ @classmethod
+ def from_json(cls, json_string):
+ parsed = json.loads(json_string)
+ return cls.from_dict(parsed)
+
+ def assign_vlan(self, user_id, vlan):
+ logging.debug("Assigning vlan %s to user %s" % (vlan, user_id))
+ self.vlans[user_id] = vlan
+ self.vlanpool[vlan] = user_id
+ return self.vlans[user_id]
+
+ def next(self, user_id):
+ for old_user_id, vlan in self.vlans.iteritems():
+ if not self.manager.get_user(old_user_id):
+ _get_keeper()["%s-default" % old_user_id] = {}
+ del _get_keeper()["%s-default" % old_user_id]
+ del self.vlans[old_user_id]
+ return self.assign_vlan(user_id, vlan)
+ vlans = self.vlanpool.keys()
+ vlans.append(self.start)
+ nextvlan = max(vlans) + 1
+ if nextvlan == self.end:
+ raise exception.AddressNotAllocated("Out of VLANs")
+ return self.assign_vlan(user_id, nextvlan)
+
+
+class NetworkController(object):
+ """ The network controller is in charge of network connections """
+
+ def __init__(self, **kwargs):
+ logging.debug("Starting up the network controller.")
+ self.manager = users.UserManager.instance()
+ self._pubnet = None
+ if not _get_keeper()['vlans']:
+ _get_keeper()['vlans'] = {}
+ if not _get_keeper()['public']:
+ _get_keeper()['public'] = {'vlan': FLAGS.public_vlan, 'network' : FLAGS.public_range}
+ self.express()
+
+ def reset(self):
+ _get_keeper()['public'] = {'vlan': FLAGS.public_vlan, 'network': FLAGS.public_range }
+ _get_keeper()['vlans'] = {}
+ # TODO : Get rid of old interfaces, bridges, and IPTables rules.
+
+ @property
+ def public_net(self):
+ if not self._pubnet:
+ self._pubnet = PublicNetwork.from_dict(_get_keeper()['public'])
+ self._pubnet.load(**_get_keeper()['public'])
+ return self._pubnet
+
+ @property
+ def vlan_pool(self):
+ return VlanPool.from_dict(_get_keeper()['vlans'])
+
+ def get_network_from_name(self, network_name):
+ net_dict = _get_keeper()[network_name]
+ if net_dict:
+ return PrivateNetwork.from_dict(net_dict)
+ return None
+
+ def get_public_ip_for_instance(self, instance_id):
+ # FIXME: this should be a lookup - iteration won't scale
+ for address_record in self.describe_addresses(type=PublicNetwork):
+ if address_record.get(u'instance_id', 'free') == instance_id:
+ return address_record[u'address']
+
+ def get_users_network(self, user_id):
+ """ get a user's private network, allocating one if needed """
+
+ user = self.manager.get_user(user_id)
+ if not user:
+ raise Exception("User %s doesn't exist, uhoh." % user_id)
+ usernet = self.get_network_from_name("%s-default" % user_id)
+ if not usernet:
+ pool = self.vlan_pool
+ vlan = pool.next(user_id)
+ private_pool = NetworkPool()
+ network_str = private_pool.get_from_vlan(vlan)
+ logging.debug("Constructing network %s and %s for %s" % (network_str, vlan, user_id))
+ usernet = PrivateNetwork(
+ network=network_str,
+ vlan=vlan)
+ _get_keeper()["%s-default" % user_id] = usernet.to_dict()
+ _get_keeper()['vlans'] = pool.to_dict()
+ return usernet
+
+ def allocate_address(self, user_id, mac=None, type=PrivateNetwork):
+ ip = None
+ net_name = None
+ if type == PrivateNetwork:
+ net = self.get_users_network(user_id)
+ ip = net.allocate_ip(user_id, mac)
+ net_name = net.name
+ _get_keeper()["%s-default" % user_id] = net.to_dict()
+ else:
+ net = self.public_net
+ ip = net.allocate_ip(user_id, mac)
+ net_name = net.name
+ _get_keeper()['public'] = net.to_dict()
+ return (ip, net_name)
+
+ def deallocate_address(self, address):
+ if address in self.public_net.network:
+ net = self.public_net
+ rv = net.deallocate_ip(str(address))
+ _get_keeper()['public'] = net.to_dict()
+ return rv
+ for user in self.manager.get_users():
+ if address in self.get_users_network(user.id).network:
+ net = self.get_users_network(user.id)
+ rv = net.deallocate_ip(str(address))
+ _get_keeper()["%s-default" % user.id] = net.to_dict()
+ return rv
+ raise exception.AddressNotAllocated()
+
+ def describe_addresses(self, type=PrivateNetwork):
+ if type == PrivateNetwork:
+ addresses = []
+ for user in self.manager.get_users():
+ addresses.extend(self.get_users_network(user.id).list_addresses())
+ return addresses
+ return self.public_net.list_addresses()
+
+ def associate_address(self, address, private_ip, instance_id):
+ net = self.public_net
+ rv = net.associate_address(address, private_ip, instance_id)
+ _get_keeper()['public'] = net.to_dict()
+ return rv
+
+ def disassociate_address(self, address):
+ net = self.public_net
+ rv = net.disassociate_address(address)
+ _get_keeper()['public'] = net.to_dict()
+ return rv
+
+ def express(self,address=None):
+ for user in self.manager.get_users():
+ self.get_users_network(user.id).express()
+
+ def report_state(self):
+ pass
+
diff --git a/nova/compute/node.py b/nova/compute/node.py
new file mode 100644
index 0000000000..a4de0f98ae
--- /dev/null
+++ b/nova/compute/node.py
@@ -0,0 +1,549 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Compute Node:
+
+ Runs on each compute node, managing the
+ hypervisor using libvirt.
+
+"""
+
+import base64
+import json
+import logging
+import os
+import random
+import shutil
+import sys
+
+from nova import vendor
+from twisted.internet import defer
+from twisted.internet import task
+from twisted.application import service
+
+try:
+ import libvirt
+except Exception, err:
+ logging.warning('no libvirt found')
+
+from nova import exception
+from nova import fakevirt
+from nova import flags
+from nova import process
+from nova import utils
+from nova.compute import disk
+from nova.compute import model
+from nova.compute import network
+from nova.objectstore import image # for image_path flag
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('libvirt_xml_template',
+ utils.abspath('compute/libvirt.xml.template'),
+ 'Network XML Template')
+flags.DEFINE_bool('use_s3', True,
+ 'whether to get images from s3 or use local copy')
+flags.DEFINE_string('instances_path', utils.abspath('../instances'),
+ 'where instances are stored on disk')
+flags.DEFINE_string('instances_prefix', 'compute-',
+ 'prefix for keepers for instances')
+
+INSTANCE_TYPES = {}
+INSTANCE_TYPES['m1.tiny'] = {'memory_mb': 512, 'vcpus': 1, 'local_gb': 0}
+INSTANCE_TYPES['m1.small'] = {'memory_mb': 1024, 'vcpus': 1, 'local_gb': 10}
+INSTANCE_TYPES['m1.medium'] = {'memory_mb': 2048, 'vcpus': 2, 'local_gb': 10}
+INSTANCE_TYPES['m1.large'] = {'memory_mb': 4096, 'vcpus': 4, 'local_gb': 10}
+INSTANCE_TYPES['m1.xlarge'] = {'memory_mb': 8192, 'vcpus': 4, 'local_gb': 10}
+INSTANCE_TYPES['c1.medium'] = {'memory_mb': 2048, 'vcpus': 4, 'local_gb': 10}
+
+# The number of processes to start in our process pool
+# TODO(termie): this should probably be a flag and the pool should probably
+# be a singleton
+PROCESS_POOL_SIZE = 4
+
+
+class Node(object, service.Service):
+ """
+ Manages the running instances.
+ """
+ def __init__(self):
+ """ load configuration options for this node and connect to libvirt """
+ super(Node, self).__init__()
+ self._instances = {}
+ self._conn = self._get_connection()
+ self._pool = process.Pool(PROCESS_POOL_SIZE)
+ self.instdir = model.InstanceDirectory()
+ # TODO(joshua): This needs to ensure system state, specifically: modprobe aoe
+
+ def _get_connection(self):
+ """ returns a libvirt connection object """
+ # TODO(termie): maybe lazy load after initial check for permissions
+ # TODO(termie): check whether we can be disconnected
+ if FLAGS.fake_libvirt:
+ conn = fakevirt.FakeVirtConnection.instance()
+ else:
+ auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT],
+ 'root',
+ None]
+ conn = libvirt.openAuth('qemu:///system', auth, 0)
+ if conn == None:
+ logging.error('Failed to open connection to the hypervisor')
+ sys.exit(1)
+ return conn
+
+ def noop(self):
+ """ simple test of an AMQP message call """
+ return defer.succeed('PONG')
+
+ def get_instance(self, instance_id):
+ # inst = self.instdir.get(instance_id)
+ # return inst
+ if self.instdir.exists(instance_id):
+ return Instance.fromName(self._conn, self._pool, instance_id)
+ return None
+
+ @exception.wrap_exception
+ def adopt_instances(self):
+ """ if there are instances already running, adopt them """
+ return defer.succeed(0)
+ instance_names = [self._conn.lookupByID(x).name()
+ for x in self._conn.listDomainsID()]
+ for name in instance_names:
+ try:
+ new_inst = Instance.fromName(self._conn, self._pool, name)
+ new_inst.update_state()
+ except:
+ pass
+ return defer.succeed(len(self._instances))
+
+ @exception.wrap_exception
+ def describe_instances(self):
+ retval = {}
+ for inst in self.instdir.by_node(FLAGS.node_name):
+ retval[inst['instance_id']] = (Instance.fromName(self._conn, self._pool, inst['instance_id']))
+ return retval
+
+ @defer.inlineCallbacks
+ def report_state(self):
+ logging.debug("Reporting State")
+ return
+
+ @exception.wrap_exception
+ def run_instance(self, instance_id, **_kwargs):
+ """ launch a new instance with specified options """
+ logging.debug("Starting instance %s..." % (instance_id))
+ inst = self.instdir.get(instance_id)
+ inst['node_name'] = FLAGS.node_name
+ inst.save()
+ # TODO(vish) check to make sure the availability zone matches
+ new_inst = Instance(self._conn, name=instance_id,
+ pool=self._pool, data=inst)
+ if new_inst.is_running():
+ raise exception.Error("Instance is already running")
+ d = new_inst.spawn()
+ return d
+
+ @exception.wrap_exception
+ def terminate_instance(self, instance_id):
+ """ terminate an instance on this machine """
+ logging.debug("Got told to terminate instance %s" % instance_id)
+ instance = self.get_instance(instance_id)
+ # inst = self.instdir.get(instance_id)
+ if not instance:
+ raise exception.Error(
+ 'trying to terminate unknown instance: %s' % instance_id)
+ d = instance.destroy()
+ # d.addCallback(lambda x: inst.destroy())
+ return d
+
+ @exception.wrap_exception
+ def reboot_instance(self, instance_id):
+ """ reboot an instance on this server
+ KVM doesn't support reboot, so we terminate and restart """
+ instance = self.get_instance(instance_id)
+ if not instance:
+ raise exception.Error(
+ 'trying to reboot unknown instance: %s' % instance_id)
+ return instance.reboot()
+
+ @defer.inlineCallbacks
+ @exception.wrap_exception
+ def get_console_output(self, instance_id):
+ """ send the console output for an instance """
+ logging.debug("Getting console output for %s" % (instance_id))
+ inst = self.instdir.get(instance_id)
+ instance = self.get_instance(instance_id)
+ if not instance:
+ raise exception.Error(
+ 'trying to get console log for unknown: %s' % instance_id)
+ rv = yield instance.console_output()
+ # TODO(termie): this stuff belongs in the API layer, no need to
+ # munge the data we send to ourselves
+ output = {"InstanceId" : instance_id,
+ "Timestamp" : "2",
+ "output" : base64.b64encode(rv)}
+ defer.returnValue(output)
+
+ @defer.inlineCallbacks
+ @exception.wrap_exception
+ def attach_volume(self, instance_id = None,
+ aoe_device = None, mountpoint = None):
+ utils.runthis("Attached Volume: %s",
+ "sudo virsh attach-disk %s /dev/etherd/%s %s"
+ % (instance_id, aoe_device, mountpoint.split("/")[-1]))
+ return defer.succeed(True)
+
+ def _init_aoe(self):
+ utils.runthis("Doin an AoE discover, returns %s", "sudo aoe-discover")
+ utils.runthis("Doin an AoE stat, returns %s", "sudo aoe-stat")
+
+ @exception.wrap_exception
+ def detach_volume(self, instance_id, mountpoint):
+ """ detach a volume from an instance """
+ # despite the documentation, virsh detach-disk just wants the device
+ # name without the leading /dev/
+ target = mountpoint.rpartition('/dev/')[2]
+ utils.runthis("Detached Volume: %s", "sudo virsh detach-disk %s %s "
+ % (instance_id, target))
+ return defer.succeed(True)
+
+
+class Group(object):
+ def __init__(self, group_id):
+ self.group_id = group_id
+
+
+class ProductCode(object):
+ def __init__(self, product_code):
+ self.product_code = product_code
+
+
+def _create_image(data, libvirt_xml):
+ """ create libvirt.xml and copy files into instance path """
+ def basepath(path=''):
+ return os.path.abspath(os.path.join(data['basepath'], path))
+
+ def imagepath(path=''):
+ return os.path.join(FLAGS.images_path, path)
+
+ def image_url(path):
+ return "%s:%s/_images/%s" % (FLAGS.s3_host, FLAGS.s3_port, path)
+
+ logging.info(basepath('disk'))
+ try:
+ os.makedirs(data['basepath'])
+ os.chmod(data['basepath'], 0777)
+ except OSError:
+ # TODO: there is already an instance with this name, do something
+ pass
+ try:
+ logging.info('Creating image for: %s', data['instance_id'])
+ f = open(basepath('libvirt.xml'), 'w')
+ f.write(libvirt_xml)
+ f.close()
+ if not FLAGS.fake_libvirt:
+ if FLAGS.use_s3:
+ if not os.path.exists(basepath('disk')):
+ utils.fetchfile(image_url("%s/image" % data['image_id']),
+ basepath('disk-raw'))
+ if not os.path.exists(basepath('kernel')):
+ utils.fetchfile(image_url("%s/image" % data['kernel_id']),
+ basepath('kernel'))
+ if not os.path.exists(basepath('ramdisk')):
+ utils.fetchfile(image_url("%s/image" % data['ramdisk_id']),
+ basepath('ramdisk'))
+ else:
+ if not os.path.exists(basepath('disk')):
+ shutil.copyfile(imagepath("%s/image" % data['image_id']),
+ basepath('disk-raw'))
+ if not os.path.exists(basepath('kernel')):
+ shutil.copyfile(imagepath("%s/image" % data['kernel_id']),
+ basepath('kernel'))
+ if not os.path.exists(basepath('ramdisk')):
+ shutil.copyfile(imagepath("%s/image" %
+ data['ramdisk_id']),
+ basepath('ramdisk'))
+ if data['key_data']:
+ logging.info('Injecting key data into image %s' %
+ data['image_id'])
+ disk.inject_key(data['key_data'], basepath('disk-raw'))
+ if os.path.exists(basepath('disk')):
+ os.remove(basepath('disk'))
+ bytes = INSTANCE_TYPES[data['instance_type']]['local_gb'] * 1024 * 1024 * 1024
+ disk.partition(basepath('disk-raw'), basepath('disk'), bytes)
+ logging.info('Done create image for: %s', data['instance_id'])
+ except Exception as ex:
+ return {'exception': ex}
+
+
+class Instance(object):
+
+ NOSTATE = 0x00
+ RUNNING = 0x01
+ BLOCKED = 0x02
+ PAUSED = 0x03
+ SHUTDOWN = 0x04
+ SHUTOFF = 0x05
+ CRASHED = 0x06
+
+ def is_pending(self):
+ return (self.state == Instance.NOSTATE or self.state == 'pending')
+
+ def is_destroyed(self):
+ return self.state == Instance.SHUTOFF
+
+ def is_running(self):
+ logging.debug("Instance state is: %s" % self.state)
+ return (self.state == Instance.RUNNING or self.state == 'running')
+
+ def __init__(self, conn, pool, name, data):
+ # TODO(termie): pool should probably be a singleton instead of being passed
+ # here and in the classmethods
+ """ spawn an instance with a given name """
+ # TODO(termie): pool should probably be a singleton instead of being passed
+ # here and in the classmethods
+ self._pool = pool
+ self._conn = conn
+ self.datamodel = data
+ print data
+
+ # NOTE(termie): to be passed to multiprocess self._s must be
+ # pickle-able by cPickle
+ self._s = {}
+
+ # TODO(termie): is instance_type that actual name for this?
+ size = data.get('instance_type', FLAGS.default_instance_type)
+ if size not in INSTANCE_TYPES:
+ raise exception.Error('invalid instance type: %s' % size)
+
+ self._s.update(INSTANCE_TYPES[size])
+
+ self._s['name'] = name
+ self._s['instance_id'] = name
+ self._s['instance_type'] = size
+ self._s['mac_address'] = data.get(
+ 'mac_address', 'df:df:df:df:df:df')
+ self._s['basepath'] = data.get(
+ 'basepath', os.path.abspath(
+ os.path.join(FLAGS.instances_path, self.name)))
+ self._s['memory_kb'] = int(self._s['memory_mb']) * 1024
+ # TODO(joshua) - Get this from network directory controller later
+ self._s['bridge_name'] = data.get('bridge_name', 'br0')
+ self._s['image_id'] = data.get('image_id', FLAGS.default_image)
+ self._s['kernel_id'] = data.get('kernel_id', FLAGS.default_kernel)
+ self._s['ramdisk_id'] = data.get('ramdisk_id', FLAGS.default_ramdisk)
+ self._s['owner_id'] = data.get('owner_id', '')
+ self._s['node_name'] = data.get('node_name', '')
+ self._s['user_data'] = data.get('user_data', '')
+ self._s['ami_launch_index'] = data.get('ami_launch_index', None)
+ self._s['launch_time'] = data.get('launch_time', None)
+ self._s['reservation_id'] = data.get('reservation_id', None)
+ # self._s['state'] = Instance.NOSTATE
+ self._s['state'] = data.get('state', Instance.NOSTATE)
+
+ self._s['key_data'] = data.get('key_data', None)
+
+ # TODO: we may not need to save the next few
+ self._s['groups'] = data.get('security_group', ['default'])
+ self._s['product_codes'] = data.get('product_code', [])
+ self._s['key_name'] = data.get('key_name', None)
+ self._s['addressing_type'] = data.get('addressing_type', None)
+ self._s['availability_zone'] = data.get('availability_zone', 'fixme')
+
+ #TODO: put real dns items here
+ self._s['private_dns_name'] = data.get('private_dns_name', 'fixme')
+ self._s['dns_name'] = data.get('dns_name',
+ self._s['private_dns_name'])
+ logging.debug("Finished init of Instance with id of %s" % name)
+
+ def toXml(self):
+ # TODO(termie): cache?
+ logging.debug("Starting the toXML method")
+ libvirt_xml = open(FLAGS.libvirt_xml_template).read()
+ xml_info = self._s.copy()
+ #xml_info.update(self._s)
+
+ # TODO(termie): lazy lazy hack because xml is annoying
+ xml_info['nova'] = json.dumps(self._s)
+ libvirt_xml = libvirt_xml % xml_info
+ logging.debug("Finished the toXML method")
+
+ return libvirt_xml
+
+ @classmethod
+ def fromName(cls, conn, pool, name):
+ """ use the saved data for reloading the instance """
+ # if FLAGS.fake_libvirt:
+ # raise Exception('this is a bit useless, eh?')
+
+ instdir = model.InstanceDirectory()
+ instance = instdir.get(name)
+ return cls(conn=conn, pool=pool, name=name, data=instance)
+
+ @property
+ def state(self):
+ return self._s['state']
+
+ @property
+ def name(self):
+ return self._s['name']
+
+ def describe(self):
+ return self._s
+
+ def info(self):
+ logging.debug("Getting info for dom %s" % self.name)
+ virt_dom = self._conn.lookupByName(self.name)
+ (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info()
+ return {'state': state,
+ 'max_mem': max_mem,
+ 'mem': mem,
+ 'num_cpu': num_cpu,
+ 'cpu_time': cpu_time}
+
+ def update_state(self):
+ info = self.info()
+ self._s['state'] = info['state']
+ self.datamodel['state'] = info['state']
+ self.datamodel['node_name'] = FLAGS.node_name
+ self.datamodel.save()
+
+ @exception.wrap_exception
+ def destroy(self):
+ if self.is_destroyed():
+ self.datamodel.destroy()
+ raise exception.Error('trying to destroy already destroyed'
+ ' instance: %s' % self.name)
+
+ self._s['state'] = Instance.SHUTDOWN
+ self.datamodel['state'] = 'shutting_down'
+ self.datamodel.save()
+ try:
+ virt_dom = self._conn.lookupByName(self.name)
+ virt_dom.destroy()
+ except Exception, _err:
+ pass
+ # If the instance is already terminated, we're still happy
+ d = defer.Deferred()
+ d.addCallback(lambda x: self.datamodel.destroy())
+ # TODO(termie): short-circuit me for tests
+ # WE'LL save this for when we do shutdown,
+ # instead of destroy - but destroy returns immediately
+ timer = task.LoopingCall(f=None)
+ def _wait_for_shutdown():
+ try:
+ info = self.info()
+ if info['state'] == Instance.SHUTDOWN:
+ self._s['state'] = Instance.SHUTDOWN
+ #self.datamodel['state'] = 'shutdown'
+ #self.datamodel.save()
+ timer.stop()
+ d.callback(None)
+ except Exception:
+ self._s['state'] = Instance.SHUTDOWN
+ timer.stop()
+ d.callback(None)
+ timer.f = _wait_for_shutdown
+ timer.start(interval=0.5, now=True)
+ return d
+
+ @defer.inlineCallbacks
+ @exception.wrap_exception
+ def reboot(self):
+ # if not self.is_running():
+ # raise exception.Error(
+ # 'trying to reboot a non-running'
+ # 'instance: %s (state: %s)' % (self.name, self.state))
+
+ yield self._conn.lookupByName(self.name).destroy()
+ self.datamodel['state'] = 'rebooting'
+ self.datamodel.save()
+ self._s['state'] = Instance.NOSTATE
+ self._conn.createXML(self.toXml(), 0)
+ # TODO(termie): this should actually register a callback to check
+ # for successful boot
+ self.datamodel['state'] = 'running'
+ self.datamodel.save()
+ self._s['state'] = Instance.RUNNING
+ logging.debug('rebooted instance %s' % self.name)
+ defer.returnValue(None)
+
+ @exception.wrap_exception
+ def spawn(self):
+ self.datamodel['state'] = "spawning"
+ self.datamodel.save()
+ logging.debug("Starting spawn in Instance")
+ xml = self.toXml()
+ def _launch(retvals):
+ self.datamodel['state'] = 'launching'
+ self.datamodel.save()
+ try:
+ logging.debug("Arrived in _launch")
+ if retvals and 'exception' in retvals:
+ raise retvals['exception']
+ self._conn.createXML(self.toXml(), 0)
+ # TODO(termie): this should actually register
+ # a callback to check for successful boot
+ self._s['state'] = Instance.RUNNING
+ self.datamodel['state'] = 'running'
+ self.datamodel.save()
+ logging.debug("Instance is running")
+ except Exception as ex:
+ logging.debug(ex)
+ self.datamodel['state'] = 'shutdown'
+ self.datamodel.save()
+ #return self
+
+ d = self._pool.apply(_create_image, self._s, xml)
+ d.addCallback(_launch)
+ return d
+
+ @exception.wrap_exception
+ def console_output(self):
+ if not FLAGS.fake_libvirt:
+ fname = os.path.abspath(
+ os.path.join(self._s['basepath'], 'console.log'))
+ with open(fname, 'r') as f:
+ console = f.read()
+ else:
+ console = 'FAKE CONSOLE OUTPUT'
+ return defer.succeed(console)
+
+ def generate_mac(self):
+ mac = [0x00, 0x16, 0x3e, random.randint(0x00, 0x7f),
+ random.randint(0x00, 0xff), random.randint(0x00, 0xff)
+ ]
+ return ':'.join(map(lambda x: "%02x" % x, mac))
+
+
+
+class NetworkNode(Node):
+ def __init__(self, **kwargs):
+ super(NetworkNode, self).__init__(**kwargs)
+ self.virtNets = {}
+
+ def add_network(self, net_dict):
+ net = network.VirtNetwork(**net_dict)
+ self.virtNets[net.name] = net
+ self.virtNets[net.name].express()
+ return defer.succeed({'retval': 'network added'})
+
+ @exception.wrap_exception
+ def run_instance(self, instance_id, **kwargs):
+ inst = self.instdir.get(instance_id)
+ net_dict = json.loads(inst.get('network_str', "{}"))
+ self.add_network(net_dict)
+ return super(NetworkNode, self).run_instance(instance_id, **kwargs)
+
diff --git a/nova/crypto.py b/nova/crypto.py
new file mode 100644
index 0000000000..6add55ee50
--- /dev/null
+++ b/nova/crypto.py
@@ -0,0 +1,224 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Wrappers around standard crypto, including root and intermediate CAs,
+SSH keypairs and x509 certificates.
+"""
+
+import hashlib
+import logging
+import os
+import shutil
+import tempfile
+import time
+import utils
+
+from nova import vendor
+import M2Crypto
+
+from nova import exception
+from nova import flags
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('ca_file', 'cacert.pem', 'Filename of root CA')
+flags.DEFINE_string('keys_path', utils.abspath('../keys'), 'Where we keep our keys')
+flags.DEFINE_string('ca_path', utils.abspath('../CA'), 'Where we keep our root CA')
+flags.DEFINE_boolean('use_intermediate_ca', False, 'Should we use intermediate CAs for each project?')
+
+
+def ca_path(username):
+ if username:
+ return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, username)
+ return "%s/cacert.pem" % (FLAGS.ca_path)
+
+def fetch_ca(username=None, chain=True):
+ if not FLAGS.use_intermediate_ca:
+ username = None
+ buffer = ""
+ if username:
+ with open(ca_path(username),"r") as cafile:
+ buffer += cafile.read()
+ if username and not chain:
+ return buffer
+ with open(ca_path(None),"r") as cafile:
+ buffer += cafile.read()
+ return buffer
+
+def generate_key_pair(bits=1024):
+ # what is the magic 65537?
+
+ tmpdir = tempfile.mkdtemp()
+ keyfile = os.path.join(tmpdir, 'temp')
+ utils.execute('ssh-keygen -q -b %d -N "" -f %s' % (bits, keyfile))
+ (out, err) = utils.execute('ssh-keygen -q -l -f %s.pub' % (keyfile))
+ fingerprint = out.split(' ')[1]
+ private_key = open(keyfile).read()
+ public_key = open(keyfile + '.pub').read()
+
+ shutil.rmtree(tmpdir)
+ # code below returns public key in pem format
+ # key = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
+ # private_key = key.as_pem(cipher=None)
+ # bio = M2Crypto.BIO.MemoryBuffer()
+ # key.save_pub_key_bio(bio)
+ # public_key = bio.read()
+ # public_key, err = execute('ssh-keygen -y -f /dev/stdin', private_key)
+
+ return (private_key, public_key, fingerprint)
+
+
+def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'):
+ """requires lsh-utils"""
+ convert="sed -e'1d' -e'$d' | pkcs1-conv --public-key-info --base-64 |" \
+ + " sexp-conv | sed -e'1s/(rsa-pkcs1/(rsa-pkcs1-sha1/' | sexp-conv -s" \
+ + " transport | lsh-export-key --openssh"
+ (out, err) = utils.execute(convert, ssl_public_key)
+ if err:
+ raise exception.Error("Failed to generate key: %s", err)
+ return '%s %s@%s\n' %(out.strip(), name, suffix)
+
+
+def generate_x509_cert(subject="/C=US/ST=California/L=The Mission/O=CloudFed/OU=NOVA/CN=foo", bits=1024):
+ tmpdir = tempfile.mkdtemp()
+ keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key'))
+ csrfile = os.path.join(tmpdir, 'temp.csr')
+ logging.debug("openssl genrsa -out %s %s" % (keyfile, bits))
+ utils.runthis("Generating private key: %s", "openssl genrsa -out %s %s" % (keyfile, bits))
+ utils.runthis("Generating CSR: %s", "openssl req -new -key %s -out %s -batch -subj %s" % (keyfile, csrfile, subject))
+ private_key = open(keyfile).read()
+ csr = open(csrfile).read()
+ shutil.rmtree(tmpdir)
+ return (private_key, csr)
+
+
+def sign_csr(csr_text, intermediate=None):
+ if not FLAGS.use_intermediate_ca:
+ intermediate = None
+ if not intermediate:
+ return _sign_csr(csr_text, FLAGS.ca_path)
+ user_ca = "%s/INTER/%s" % (FLAGS.ca_path, intermediate)
+ if not os.path.exists(user_ca):
+ start = os.getcwd()
+ os.chdir(FLAGS.ca_path)
+ utils.runthis("Generating intermediate CA: %s", "sh geninter.sh %s" % (intermediate))
+ os.chdir(start)
+ return _sign_csr(csr_text, user_ca)
+
+
+def _sign_csr(csr_text, ca_folder):
+ tmpfolder = tempfile.mkdtemp()
+ csrfile = open("%s/inbound.csr" % (tmpfolder), "w")
+ csrfile.write(csr_text)
+ csrfile.close()
+ logging.debug("Flags path: %s" % ca_folder)
+ start = os.getcwd()
+ # Change working dir to CA
+ os.chdir(ca_folder)
+ utils.runthis("Signing cert: %s", "openssl ca -batch -out %s/outbound.crt -config ./openssl.cnf -infiles %s/inbound.csr" % (tmpfolder, tmpfolder))
+ os.chdir(start)
+ with open("%s/outbound.crt" % (tmpfolder), "r") as crtfile:
+ return crtfile.read()
+
+
+def mkreq(bits, subject="foo", ca=0):
+ pk = M2Crypto.EVP.PKey()
+ req = M2Crypto.X509.Request()
+ rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
+ pk.assign_rsa(rsa)
+ rsa = None # should not be freed here
+ req.set_pubkey(pk)
+ req.set_subject(subject)
+ req.sign(pk,'sha512')
+ assert req.verify(pk)
+ pk2 = req.get_pubkey()
+ assert req.verify(pk2)
+ return req, pk
+
+
+def mkcacert(subject='nova', years=1):
+ req, pk = mkreq(2048, subject, ca=1)
+ pkey = req.get_pubkey()
+ sub = req.get_subject()
+ cert = M2Crypto.X509.X509()
+ cert.set_serial_number(1)
+ cert.set_version(2)
+ cert.set_subject(sub) # FIXME subject is not set in mkreq yet
+ t = long(time.time()) + time.timezone
+ now = M2Crypto.ASN1.ASN1_UTCTIME()
+ now.set_time(t)
+ nowPlusYear = M2Crypto.ASN1.ASN1_UTCTIME()
+ nowPlusYear.set_time(t + (years * 60 * 60 * 24 * 365))
+ cert.set_not_before(now)
+ cert.set_not_after(nowPlusYear)
+ issuer = M2Crypto.X509.X509_Name()
+ issuer.C = "US"
+ issuer.CN = subject
+ cert.set_issuer(issuer)
+ cert.set_pubkey(pkey)
+ ext = M2Crypto.X509.new_extension('basicConstraints', 'CA:TRUE')
+ cert.add_ext(ext)
+ cert.sign(pk, 'sha512')
+
+ # print 'cert', dir(cert)
+ print cert.as_pem()
+ print pk.get_rsa().as_pem()
+
+ return cert, pk, pkey
+
+
+
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+# http://code.google.com/p/boto
+
+def compute_md5(fp):
+ """
+ @type fp: file
+ @param fp: File pointer to the file to MD5 hash. The file pointer will be
+ reset to the beginning of the file before the method returns.
+
+ @rtype: tuple
+ @return: the hex digest version of the MD5 hash
+ """
+ m = hashlib.md5()
+ fp.seek(0)
+ s = fp.read(8192)
+ while s:
+ m.update(s)
+ s = fp.read(8192)
+ hex_md5 = m.hexdigest()
+ # size = fp.tell()
+ fp.seek(0)
+ return hex_md5
diff --git a/nova/datastore.py b/nova/datastore.py
new file mode 100644
index 0000000000..57940d98b7
--- /dev/null
+++ b/nova/datastore.py
@@ -0,0 +1,367 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Datastore:
+
+Providers the Keeper class, a simple pseudo-dictionary that
+persists on disk.
+
+MAKE Sure that ReDIS is running, and your flags are set properly,
+before trying to run this.
+"""
+
+import json
+import logging
+import os
+import sqlite3
+
+from nova import vendor
+import redis
+
+from nova import flags
+from nova import utils
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('datastore_path', utils.abspath('../keeper'),
+ 'where keys are stored on disk')
+flags.DEFINE_string('redis_host', '127.0.0.1',
+ 'Host that redis is running on.')
+flags.DEFINE_integer('redis_port', 6379,
+ 'Port that redis is running on.')
+flags.DEFINE_integer('redis_db', 0, 'Multiple DB keeps tests away')
+flags.DEFINE_string('keeper_backend', 'redis',
+ 'which backend to use for keeper')
+
+
+class Redis(object):
+ def __init__(self):
+ if hasattr(self.__class__, '_instance'):
+ raise Exception('Attempted to instantiate singleton')
+
+ @classmethod
+ def instance(cls):
+ if not hasattr(cls, '_instance'):
+ inst = redis.Redis(host=FLAGS.redis_host, port=FLAGS.redis_port, db=FLAGS.redis_db)
+ cls._instance = inst
+ return cls._instance
+
+
+class RedisModel(object):
+ """ Wrapper around redis-backed properties """
+ object_type = 'generic'
+ def __init__(self, object_id):
+ """ loads an object from the datastore if exists """
+ self.object_id = object_id
+ self.initial_state = {}
+ self.state = Redis.instance().hgetall(self.__redis_key)
+ if self.state:
+ self.initial_state = self.state
+ else:
+ self.set_default_state()
+
+ def set_default_state(self):
+ self.state = {'state' : 'pending'}
+ self.state[self.object_type+"_id"] = self.object_id
+
+ @property
+ def __redis_key(self):
+ """ Magic string for instance keys """
+ return '%s:%s' % (self.object_type, self.object_id)
+
+ def __repr__(self):
+ return "<%s:%s>" % (self.object_type, self.object_id)
+
+ def __str__(self):
+ return str(self.state)
+
+ def keys(self):
+ return self.state.keys()
+
+ def copy(self):
+ copyDict = {}
+ for item in self.keys():
+ copyDict[item] = self[item]
+ return copyDict
+
+ def get(self, item, default):
+ return self.state.get(item, default)
+
+ def __getitem__(self, item):
+ return self.state[item]
+
+ def __setitem__(self, item, val):
+ self.state[item] = val
+ return self.state[item]
+
+ def __delitem__(self, item):
+ """ We don't support this """
+ raise Exception("Silly monkey, we NEED all our properties.")
+
+ def save(self):
+ """ update the directory with the state from this instance """
+ # TODO(ja): implement hmset in redis-py and use it
+ # instead of multiple calls to hset
+ for key, val in self.state.iteritems():
+ # if (not self.initial_state.has_key(key)
+ # or self.initial_state[key] != val):
+ Redis.instance().hset(self.__redis_key, key, val)
+ if self.initial_state == {}:
+ self.first_save()
+ self.initial_state = self.state
+ return True
+
+ def first_save(self):
+ pass
+
+ def destroy(self):
+ """ deletes all related records from datastore.
+ does NOT do anything to running state.
+ """
+ Redis.instance().delete(self.__redis_key)
+ return True
+
+
+def slugify(key, prefix=None):
+ """
+ Key has to be a valid filename. Slugify solves that.
+ """
+ return "%s%s" % (prefix, key)
+
+
+class SqliteKeeper(object):
+ """ Keeper implementation in SQLite, mostly for in-memory testing """
+ _conn = {} # class variable
+
+ def __init__(self, prefix):
+ self.prefix = prefix
+
+ @property
+ def conn(self):
+ if self.prefix not in self.__class__._conn:
+ logging.debug('no sqlite connection (%s), making new', self.prefix)
+ if FLAGS.datastore_path != ':memory:':
+ try:
+ os.mkdir(FLAGS.datastore_path)
+ except Exception:
+ pass
+ conn = sqlite3.connect(os.path.join(
+ FLAGS.datastore_path, '%s.sqlite' % self.prefix))
+ else:
+ conn = sqlite3.connect(':memory:')
+
+ c = conn.cursor()
+ try:
+ c.execute('''CREATE TABLE data (item text, value text)''')
+ conn.commit()
+ except Exception:
+ logging.exception('create table failed')
+ finally:
+ c.close()
+
+ self.__class__._conn[self.prefix] = conn
+
+ return self.__class__._conn[self.prefix]
+
+ def __delitem__(self, item):
+ #logging.debug('sqlite deleting %s', item)
+ c = self.conn.cursor()
+ try:
+ c.execute('DELETE FROM data WHERE item = ?', (item, ))
+ self.conn.commit()
+ except Exception:
+ logging.exception('delete failed: %s', item)
+ finally:
+ c.close()
+
+ def __getitem__(self, item):
+ #logging.debug('sqlite getting %s', item)
+ result = None
+ c = self.conn.cursor()
+ try:
+ c.execute('SELECT value FROM data WHERE item = ?', (item, ))
+ row = c.fetchone()
+ if row:
+ result = json.loads(row[0])
+ else:
+ result = None
+ except Exception:
+ logging.exception('select failed: %s', item)
+ finally:
+ c.close()
+ #logging.debug('sqlite got %s: %s', item, result)
+ return result
+
+ def __setitem__(self, item, value):
+ serialized_value = json.dumps(value)
+ insert = True
+ if self[item] is not None:
+ insert = False
+ #logging.debug('sqlite insert %s: %s', item, value)
+ c = self.conn.cursor()
+ try:
+ if insert:
+ c.execute('INSERT INTO data VALUES (?, ?)',
+ (item, serialized_value))
+ else:
+ c.execute('UPDATE data SET item=?, value=? WHERE item = ?',
+ (item, serialized_value, item))
+
+ self.conn.commit()
+ except Exception:
+ logging.exception('select failed: %s', item)
+ finally:
+ c.close()
+
+ def clear(self):
+ if self.prefix not in self.__class__._conn:
+ return
+ self.conn.close()
+ if FLAGS.datastore_path != ':memory:':
+ os.unlink(os.path.join(FLAGS.datastore_path, '%s.sqlite' % self.prefix))
+ del self.__class__._conn[self.prefix]
+
+ def clear_all(self):
+ for k, conn in self.__class__._conn.iteritems():
+ conn.close()
+ if FLAGS.datastore_path != ':memory:':
+ os.unlink(os.path.join(FLAGS.datastore_path,
+ '%s.sqlite' % self.prefix))
+ self.__class__._conn = {}
+
+
+ def set_add(self, item, value):
+ group = self[item]
+ if not group:
+ group = []
+ group.append(value)
+ self[item] = group
+
+ def set_is_member(self, item, value):
+ group = self[item]
+ if not group:
+ return False
+ return value in group
+
+ def set_remove(self, item, value):
+ group = self[item]
+ if not group:
+ group = []
+ group.remove(value)
+ self[item] = group
+
+ def set_fetch(self, item):
+ # TODO(termie): I don't really know what set_fetch is supposed to do
+ group = self[item]
+ if not group:
+ group = []
+ return iter(group)
+
+class JsonKeeper(object):
+ """
+ Simple dictionary class that persists using
+ JSON in files saved to disk.
+ """
+ def __init__(self, prefix):
+ self.prefix = prefix
+
+ def __delitem__(self, item):
+ """
+ Removing a key means deleting a file from disk.
+ """
+ item = slugify(item, self.prefix)
+ path = "%s/%s" % (FLAGS.datastore_path, item)
+ if os.path.isfile(path):
+ os.remove(path)
+
+ def __getitem__(self, item):
+ """
+ Fetch file contents and dejsonify them.
+ """
+ item = slugify(item, self.prefix)
+ path = "%s/%s" % (FLAGS.datastore_path, item)
+ if os.path.isfile(path):
+ return json.load(open(path, 'r'))
+ return None
+
+ def __setitem__(self, item, value):
+ """
+ JSON encode value and save to file.
+ """
+ item = slugify(item, self.prefix)
+ path = "%s/%s" % (FLAGS.datastore_path, item)
+ with open(path, "w") as blobfile:
+ blobfile.write(json.dumps(value))
+ return value
+
+
+class RedisKeeper(object):
+ """
+ Simple dictionary class that persists using
+ ReDIS.
+ """
+ def __init__(self, prefix="redis-"):
+ self.prefix = prefix
+ Redis.instance().ping()
+
+ def __setitem__(self, item, value):
+ """
+ JSON encode value and save to file.
+ """
+ item = slugify(item, self.prefix)
+ Redis.instance().set(item, json.dumps(value))
+ return value
+
+ def __getitem__(self, item):
+ item = slugify(item, self.prefix)
+ value = Redis.instance().get(item)
+ if value:
+ return json.loads(value)
+
+ def __delitem__(self, item):
+ item = slugify(item, self.prefix)
+ return Redis.instance().delete(item)
+
+ def clear(self):
+ raise NotImplementedError()
+
+ def clear_all(self):
+ raise NotImplementedError()
+
+ def set_add(self, item, value):
+ item = slugify(item, self.prefix)
+ return Redis.instance().sadd(item, json.dumps(value))
+
+ def set_is_member(self, item, value):
+ item = slugify(item, self.prefix)
+ return Redis.instance().sismember(item, json.dumps(value))
+
+ def set_remove(self, item, value):
+ item = slugify(item, self.prefix)
+ return Redis.instance().srem(item, json.dumps(value))
+
+ def set_fetch(self, item):
+ item = slugify(item, self.prefix)
+ for obj in Redis.instance().sinter([item]):
+ yield json.loads(obj)
+
+
+def Keeper(prefix=''):
+ KEEPERS = {'redis': RedisKeeper,
+ 'sqlite': SqliteKeeper}
+ return KEEPERS[FLAGS.keeper_backend](prefix)
+
diff --git a/nova/endpoint/__init__.py b/nova/endpoint/__init__.py
new file mode 100644
index 0000000000..dbf15d2592
--- /dev/null
+++ b/nova/endpoint/__init__.py
@@ -0,0 +1,28 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:mod:`nova.endpoint` -- Main NOVA Api endpoints
+=====================================================
+
+.. automodule:: nova.endpoint
+ :platform: Unix
+ :synopsis: REST APIs for all nova functions
+.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
+.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
+.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
+.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
+.. moduleauthor:: Manish Singh <yosh@gimp.org>
+.. moduleauthor:: Andy Smith <andy@anarkystic.com>
+""" \ No newline at end of file
diff --git a/nova/endpoint/admin.py b/nova/endpoint/admin.py
new file mode 100644
index 0000000000..e9880acc5e
--- /dev/null
+++ b/nova/endpoint/admin.py
@@ -0,0 +1,131 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Admin API controller, exposed through http via the api worker.
+"""
+
+import base64
+
+def user_dict(user, base64_file=None):
+ """Convert the user object to a result dict"""
+ if user:
+ return {
+ 'username': user.id,
+ 'accesskey': user.access,
+ 'secretkey': user.secret,
+ 'file': base64_file,
+ }
+ else:
+ return {}
+
+def node_dict(node):
+ """Convert a node object to a result dict"""
+ if node:
+ return {
+ 'node_id': node.id,
+ 'workers': ", ".join(node.workers),
+ 'disks': ", ".join(node.disks),
+ 'ram': node.memory,
+ 'load_average' : node.load_average,
+ }
+ else:
+ return {}
+
+def admin_only(target):
+ """Decorator for admin-only API calls"""
+ def wrapper(*args, **kwargs):
+ """Internal wrapper method for admin-only API calls"""
+ context = args[1]
+ if context.user.is_admin():
+ return target(*args, **kwargs)
+ else:
+ return {}
+
+ return wrapper
+
+class AdminController(object):
+ """
+ API Controller for users, node status, and worker mgmt.
+ Trivial admin_only wrapper will be replaced with RBAC,
+ allowing project managers to administer project users.
+
+ """
+ def __init__(self, user_manager, node_manager=None):
+ self.user_manager = user_manager
+ self.node_manager = node_manager
+
+ def __str__(self):
+ return 'AdminController'
+
+ @admin_only
+ def describe_user(self, _context, name, **_kwargs):
+ """Returns user data, including access and secret keys.
+ """
+ return user_dict(self.user_manager.get_user(name))
+
+ @admin_only
+ def describe_users(self, _context, **_kwargs):
+ """Returns all users - should be changed to deal with a list.
+ """
+ return {'userSet':
+ [user_dict(u) for u in self.user_manager.get_users()] }
+
+ @admin_only
+ def register_user(self, _context, name, **_kwargs):
+ """ Creates a new user, and returns generated credentials.
+ """
+ self.user_manager.create_user(name)
+
+ return user_dict(self.user_manager.get_user(name))
+
+ @admin_only
+ def deregister_user(self, _context, name, **_kwargs):
+ """Deletes a single user (NOT undoable.)
+ Should throw an exception if the user has instances,
+ volumes, or buckets remaining.
+ """
+ self.user_manager.delete_user(name)
+
+ return True
+
+ @admin_only
+ def generate_x509_for_user(self, _context, name, **_kwargs):
+ """Generates and returns an x509 certificate for a single user.
+ Is usually called from a client that will wrap this with
+ access and secret key info, and return a zip file.
+ """
+ user = self.user_manager.get_user(name)
+ return user_dict(user, base64.b64encode(user.get_credentials()))
+
+ @admin_only
+ def describe_nodes(self, _context, **_kwargs):
+ """Returns status info for all nodes. Includes:
+ * Disk Space
+ * Instance List
+ * RAM used
+ * CPU used
+ * DHCP servers running
+ * Iptables / bridges
+ """
+ return {'nodeSet':
+ [node_dict(n) for n in self.node_manager.get_nodes()] }
+
+ @admin_only
+ def describe_node(self, _context, name, **_kwargs):
+ """Returns status info for single node.
+ """
+ return node_dict(self.node_manager.get_node(name))
+
diff --git a/nova/endpoint/api.py b/nova/endpoint/api.py
new file mode 100755
index 0000000000..5bbda3f562
--- /dev/null
+++ b/nova/endpoint/api.py
@@ -0,0 +1,337 @@
+#!/usr/bin/python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Tornado REST API Request Handlers for Nova functions
+Most calls are proxied into the responsible controller.
+"""
+
+import logging
+import multiprocessing
+import random
+import re
+import urllib
+# TODO(termie): replace minidom with etree
+from xml.dom import minidom
+
+from nova import vendor
+import tornado.web
+from twisted.internet import defer
+
+from nova import crypto
+from nova import exception
+from nova import flags
+from nova import utils
+from nova.endpoint import cloud
+from nova.auth import users
+
+FLAGS = flags.FLAGS
+flags.DEFINE_integer('cc_port', 8773, 'cloud controller port')
+
+
+_log = logging.getLogger("api")
+_log.setLevel(logging.DEBUG)
+
+
+_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
+
+
+def _camelcase_to_underscore(str):
+ return _c2u.sub(r'_\1', str).lower().strip('_')
+
+
+def _underscore_to_camelcase(str):
+ return ''.join([x[:1].upper() + x[1:] for x in str.split('_')])
+
+
+def _underscore_to_xmlcase(str):
+ res = _underscore_to_camelcase(str)
+ return res[:1].lower() + res[1:]
+
+
+class APIRequestContext(object):
+ def __init__(self, handler, user):
+ self.handler = handler
+ self.user = user
+ self.request_id = ''.join(
+ [random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-')
+ for x in xrange(20)]
+ )
+
+
+class APIRequest(object):
+ def __init__(self, handler, controller, action):
+ self.handler = handler
+ self.controller = controller
+ self.action = action
+
+ def send(self, user, **kwargs):
+ context = APIRequestContext(self.handler, user)
+
+ try:
+ method = getattr(self.controller,
+ _camelcase_to_underscore(self.action))
+ except AttributeError:
+ _error = ('Unsupported API request: controller = %s,'
+ 'action = %s') % (self.controller, self.action)
+ _log.warning(_error)
+ # TODO: Raise custom exception, trap in apiserver,
+ # and reraise as 400 error.
+ raise Exception(_error)
+
+ args = {}
+ for key, value in kwargs.items():
+ parts = key.split(".")
+ key = _camelcase_to_underscore(parts[0])
+ if len(parts) > 1:
+ d = args.get(key, {})
+ d[parts[1]] = value[0]
+ value = d
+ else:
+ value = value[0]
+ args[key] = value
+
+ for key in args.keys():
+ if isinstance(args[key], dict):
+ if args[key] != {} and args[key].keys()[0].isdigit():
+ s = args[key].items()
+ s.sort()
+ args[key] = [v for k, v in s]
+
+ d = defer.maybeDeferred(method, context, **args)
+ d.addCallback(self._render_response, context.request_id)
+ return d
+
+ def _render_response(self, response_data, request_id):
+ xml = minidom.Document()
+
+ response_el = xml.createElement(self.action + 'Response')
+ response_el.setAttribute('xmlns',
+ 'http://ec2.amazonaws.com/doc/2009-11-30/')
+ request_id_el = xml.createElement('requestId')
+ request_id_el.appendChild(xml.createTextNode(request_id))
+ response_el.appendChild(request_id_el)
+ if(response_data == True):
+ self._render_dict(xml, response_el, {'return': 'true'})
+ else:
+ self._render_dict(xml, response_el, response_data)
+
+ xml.appendChild(response_el)
+
+ response = xml.toxml()
+ xml.unlink()
+ _log.debug(response)
+ return response
+
+ def _render_dict(self, xml, el, data):
+ try:
+ for key in data.keys():
+ val = data[key]
+ el.appendChild(self._render_data(xml, key, val))
+ except:
+ _log.debug(data)
+ raise
+
+ def _render_data(self, xml, el_name, data):
+ el_name = _underscore_to_xmlcase(el_name)
+ data_el = xml.createElement(el_name)
+
+ if isinstance(data, list):
+ for item in data:
+ data_el.appendChild(self._render_data(xml, 'item', item))
+ elif isinstance(data, dict):
+ self._render_dict(xml, data_el, data)
+ elif hasattr(data, '__dict__'):
+ self._render_dict(xml, data_el, data.__dict__)
+ elif isinstance(data, bool):
+ data_el.appendChild(xml.createTextNode(str(data).lower()))
+ elif data != None:
+ data_el.appendChild(xml.createTextNode(str(data)))
+
+ return data_el
+
+
+class RootRequestHandler(tornado.web.RequestHandler):
+ def get(self):
+ # available api versions
+ versions = [
+ '1.0',
+ '2007-01-19',
+ '2007-03-01',
+ '2007-08-29',
+ '2007-10-10',
+ '2007-12-15',
+ '2008-02-01',
+ '2008-09-01',
+ '2009-04-04',
+ ]
+ for version in versions:
+ self.write('%s\n' % version)
+ self.finish()
+
+
+class MetadataRequestHandler(tornado.web.RequestHandler):
+ def print_data(self, data):
+ if isinstance(data, dict):
+ output = ''
+ for key in data:
+ if key == '_name':
+ continue
+ output += key
+ if isinstance(data[key], dict):
+ if '_name' in data[key]:
+ output += '=' + str(data[key]['_name'])
+ else:
+ output += '/'
+ output += '\n'
+ self.write(output[:-1]) # cut off last \n
+ elif isinstance(data, list):
+ self.write('\n'.join(data))
+ else:
+ self.write(str(data))
+
+ def lookup(self, path, data):
+ items = path.split('/')
+ for item in items:
+ if item:
+ if not isinstance(data, dict):
+ return data
+ if not item in data:
+ return None
+ data = data[item]
+ return data
+
+ def get(self, path):
+ cc = self.application.controllers['Cloud']
+ meta_data = cc.get_metadata(self.request.remote_ip)
+ if meta_data is None:
+ _log.error('Failed to get metadata for ip: %s' %
+ self.request.remote_ip)
+ raise tornado.web.HTTPError(404)
+ data = self.lookup(path, meta_data)
+ if data is None:
+ raise tornado.web.HTTPError(404)
+ self.print_data(data)
+ self.finish()
+
+
+class APIRequestHandler(tornado.web.RequestHandler):
+ def get(self, controller_name):
+ self.execute(controller_name)
+
+ @tornado.web.asynchronous
+ def execute(self, controller_name):
+ # Obtain the appropriate controller for this request.
+ try:
+ controller = self.application.controllers[controller_name]
+ except KeyError:
+ self._error('unhandled', 'no controller named %s' % controller_name)
+ return
+
+ args = self.request.arguments
+
+ # Read request signature.
+ try:
+ signature = args.pop('Signature')[0]
+ except:
+ raise tornado.web.HTTPError(400)
+
+ # Make a copy of args for authentication and signature verification.
+ auth_params = {}
+ for key, value in args.items():
+ auth_params[key] = value[0]
+
+ # Get requested action and remove authentication args for final request.
+ try:
+ action = args.pop('Action')[0]
+ args.pop('AWSAccessKeyId')
+ args.pop('SignatureMethod')
+ args.pop('SignatureVersion')
+ args.pop('Version')
+ args.pop('Timestamp')
+ except:
+ raise tornado.web.HTTPError(400)
+
+ # Authenticate the request.
+ user = self.application.user_manager.authenticate(
+ auth_params,
+ signature,
+ self.request.method,
+ self.request.host,
+ self.request.path
+ )
+
+ if not user:
+ raise tornado.web.HTTPError(403)
+
+ _log.debug('action: %s' % action)
+
+ for key, value in args.items():
+ _log.debug('arg: %s\t\tval: %s' % (key, value))
+
+ request = APIRequest(self, controller, action)
+ d = request.send(user, **args)
+ # d.addCallback(utils.debug)
+
+ # TODO: Wrap response in AWS XML format
+ d.addCallbacks(self._write_callback, self._error_callback)
+
+ def _write_callback(self, data):
+ self.set_header('Content-Type', 'text/xml')
+ self.write(data)
+ self.finish()
+
+ def _error_callback(self, failure):
+ try:
+ failure.raiseException()
+ except exception.ApiError as ex:
+ self._error(type(ex).__name__ + "." + ex.code, ex.message)
+ # TODO(vish): do something more useful with unknown exceptions
+ except Exception as ex:
+ self._error(type(ex).__name__, str(ex))
+ raise
+
+ def post(self, controller_name):
+ self.execute(controller_name)
+
+ def _error(self, code, message):
+ self._status_code = 400
+ self.set_header('Content-Type', 'text/xml')
+ self.write('<?xml version="1.0"?>\n')
+ self.write('<Response><Errors><Error><Code>%s</Code>'
+ '<Message>%s</Message></Error></Errors>'
+ '<RequestID>?</RequestID></Response>' % (code, message))
+ self.finish()
+
+
+class APIServerApplication(tornado.web.Application):
+ def __init__(self, user_manager, controllers):
+ tornado.web.Application.__init__(self, [
+ (r'/', RootRequestHandler),
+ (r'/services/([A-Za-z0-9]+)/', APIRequestHandler),
+ (r'/latest/([-A-Za-z0-9/]*)', MetadataRequestHandler),
+ (r'/2009-04-04/([-A-Za-z0-9/]*)', MetadataRequestHandler),
+ (r'/2008-09-01/([-A-Za-z0-9/]*)', MetadataRequestHandler),
+ (r'/2008-02-01/([-A-Za-z0-9/]*)', MetadataRequestHandler),
+ (r'/2007-12-15/([-A-Za-z0-9/]*)', MetadataRequestHandler),
+ (r'/2007-10-10/([-A-Za-z0-9/]*)', MetadataRequestHandler),
+ (r'/2007-08-29/([-A-Za-z0-9/]*)', MetadataRequestHandler),
+ (r'/2007-03-01/([-A-Za-z0-9/]*)', MetadataRequestHandler),
+ (r'/2007-01-19/([-A-Za-z0-9/]*)', MetadataRequestHandler),
+ (r'/1.0/([-A-Za-z0-9/]*)', MetadataRequestHandler),
+ ], pool=multiprocessing.Pool(4))
+ self.user_manager = user_manager
+ self.controllers = controllers
diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py
new file mode 100644
index 0000000000..27dd81aa29
--- /dev/null
+++ b/nova/endpoint/cloud.py
@@ -0,0 +1,572 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Cloud Controller: Implementation of EC2 REST API calls, which are
+dispatched to other nodes via AMQP RPC. State is via distributed
+datastore.
+"""
+
+import json
+import logging
+import os
+import time
+
+from nova import vendor
+from twisted.internet import defer
+
+from nova import datastore
+from nova import flags
+from nova import rpc
+from nova import utils
+from nova import exception
+from nova.auth import users
+from nova.compute import model
+from nova.compute import network
+from nova.endpoint import images
+from nova.volume import storage
+
+FLAGS = flags.FLAGS
+
+flags.DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on')
+
+def _gen_key(user_id, key_name):
+ """ Tuck this into UserManager """
+ try:
+ manager = users.UserManager.instance()
+ private_key, fingerprint = manager.generate_key_pair(user_id, key_name)
+ except Exception as ex:
+ return {'exception': ex}
+ return {'private_key': private_key, 'fingerprint': fingerprint}
+
+
+class CloudController(object):
+ """ CloudController provides the critical dispatch between
+ inbound API calls through the endpoint and messages
+ sent to the other nodes.
+"""
+ def __init__(self):
+ self._instances = datastore.Keeper(FLAGS.instances_prefix)
+ self.instdir = model.InstanceDirectory()
+ self.network = network.NetworkController()
+ self.setup()
+
+ @property
+ def instances(self):
+ """ All instances in the system, as dicts """
+ for instance in self.instdir.all:
+ yield {instance['instance_id']: instance}
+
+ @property
+ def volumes(self):
+ """ returns a list of all volumes """
+ for volume_id in datastore.Redis.instance().smembers("volumes"):
+ volume = storage.Volume(volume_id=volume_id)
+ yield volume
+
+ def __str__(self):
+ return 'CloudController'
+
+ def setup(self):
+ """ Ensure the keychains and folders exist. """
+ # Create keys folder, if it doesn't exist
+ if not os.path.exists(FLAGS.keys_path):
+ os.makedirs(os.path.abspath(FLAGS.keys_path))
+ # Gen root CA, if we don't have one
+ root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file)
+ if not os.path.exists(root_ca_path):
+ start = os.getcwd()
+ os.chdir(FLAGS.ca_path)
+ utils.runthis("Generating root CA: %s", "sh genrootca.sh")
+ os.chdir(start)
+ # TODO: Do this with M2Crypto instead
+
+ def get_instance_by_ip(self, ip):
+ return self.instdir.by_ip(ip)
+
+ def get_metadata(self, ip):
+ i = self.instdir.by_ip(ip)
+ if i is None:
+ return None
+ if i['key_name']:
+ keys = {
+ '0': {
+ '_name': i['key_name'],
+ 'openssh-key': i['key_data']
+ }
+ }
+ else:
+ keys = ''
+ data = {
+ 'user-data': base64.b64decode(i['user_data']),
+ 'meta-data': {
+ 'ami-id': i['image_id'],
+ 'ami-launch-index': i['ami_launch_index'],
+ 'ami-manifest-path': 'FIXME', # image property
+ 'block-device-mapping': { # TODO: replace with real data
+ 'ami': 'sda1',
+ 'ephemeral0': 'sda2',
+ 'root': '/dev/sda1',
+ 'swap': 'sda3'
+ },
+ 'hostname': i['private_dns_name'], # is this public sometimes?
+ 'instance-action': 'none',
+ 'instance-id': i['instance_id'],
+ 'instance-type': i.get('instance_type', ''),
+ 'local-hostname': i['private_dns_name'],
+ 'local-ipv4': i['private_dns_name'], # TODO: switch to IP
+ 'kernel-id': i.get('kernel_id', ''),
+ 'placement': {
+ 'availaibility-zone': i.get('availability_zone', 'nova'),
+ },
+ 'public-hostname': i.get('dns_name', ''),
+ 'public-ipv4': i.get('dns_name', ''), # TODO: switch to IP
+ 'public-keys' : keys,
+ 'ramdisk-id': i.get('ramdisk_id', ''),
+ 'reservation-id': i['reservation_id'],
+ 'security-groups': i.get('groups', '')
+ }
+ }
+ if False: # TODO: store ancestor ids
+ data['ancestor-ami-ids'] = []
+ if i.get('product_codes', None):
+ data['product-codes'] = i['product_codes']
+ return data
+
+
+ def describe_availability_zones(self, context, **kwargs):
+ return {'availabilityZoneInfo': [{'zoneName': 'nova',
+ 'zoneState': 'available'}]}
+
+ def describe_key_pairs(self, context, key_name=None, **kwargs):
+ key_pairs = []
+ key_names = key_name and key_name or []
+ if len(key_names) > 0:
+ for key_name in key_names:
+ key_pair = context.user.get_key_pair(key_name)
+ if key_pair != None:
+ key_pairs.append({
+ 'keyName': key_pair.name,
+ 'keyFingerprint': key_pair.fingerprint,
+ })
+ else:
+ for key_pair in context.user.get_key_pairs():
+ key_pairs.append({
+ 'keyName': key_pair.name,
+ 'keyFingerprint': key_pair.fingerprint,
+ })
+
+ return { 'keypairsSet': key_pairs }
+
+ def create_key_pair(self, context, key_name, **kwargs):
+ try:
+ d = defer.Deferred()
+ p = context.handler.application.settings.get('pool')
+ def _complete(kwargs):
+ if 'exception' in kwargs:
+ d.errback(kwargs['exception'])
+ return
+ d.callback({'keyName': key_name,
+ 'keyFingerprint': kwargs['fingerprint'],
+ 'keyMaterial': kwargs['private_key']})
+ p.apply_async(_gen_key, [context.user.id, key_name],
+ callback=_complete)
+ return d
+
+ except users.UserError, e:
+ raise
+
+ def delete_key_pair(self, context, key_name, **kwargs):
+ context.user.delete_key_pair(key_name)
+ # aws returns true even if the key doens't exist
+ return True
+
+ def describe_security_groups(self, context, group_names, **kwargs):
+ groups = { 'securityGroupSet': [] }
+
+ # Stubbed for now to unblock other things.
+ return groups
+
+ def create_security_group(self, context, group_name, **kwargs):
+ return True
+
+ def delete_security_group(self, context, group_name, **kwargs):
+ return True
+
+ def get_console_output(self, context, instance_id, **kwargs):
+ # instance_id is passed in as a list of instances
+ instance = self.instdir.get(instance_id[0])
+ if instance['state'] == 'pending':
+ raise exception.ApiError('Cannot get output for pending instance')
+ if not context.user.is_authorized(instance.get('owner_id', None)):
+ raise exception.ApiError('Not authorized to view output')
+ return rpc.call('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
+ {"method": "get_console_output",
+ "args" : {"instance_id": instance_id[0]}})
+
+ def _get_user_id(self, context):
+ if context and context.user:
+ return context.user.id
+ else:
+ return None
+
+ def describe_volumes(self, context, **kwargs):
+ volumes = []
+ for volume in self.volumes:
+ if context.user.is_authorized(volume.get('user_id', None)):
+ v = self.format_volume(context, volume)
+ volumes.append(v)
+ return defer.succeed({'volumeSet': volumes})
+
+ def format_volume(self, context, volume):
+ v = {}
+ v['volumeId'] = volume['volume_id']
+ v['status'] = volume['status']
+ v['size'] = volume['size']
+ v['availabilityZone'] = volume['availability_zone']
+ v['createTime'] = volume['create_time']
+ if context.user.is_admin():
+ v['status'] = '%s (%s, %s, %s, %s)' % (
+ volume.get('status', None),
+ volume.get('user_id', None),
+ volume.get('node_name', None),
+ volume.get('instance_id', ''),
+ volume.get('mountpoint', ''))
+ return v
+
+ def create_volume(self, context, size, **kwargs):
+ # TODO(vish): refactor this to create the volume object here and tell storage to create it
+ res = rpc.call(FLAGS.storage_topic, {"method": "create_volume",
+ "args" : {"size": size,
+ "user_id": context.user.id}})
+ def _format_result(result):
+ volume = self._get_volume(result['result'])
+ return {'volumeSet': [self.format_volume(context, volume)]}
+ res.addCallback(_format_result)
+ return res
+
+ def _get_by_id(self, nodes, id):
+ if nodes == {}:
+ raise exception.NotFound("%s not found" % id)
+ for node_name, node in nodes.iteritems():
+ if node.has_key(id):
+ return node_name, node[id]
+ raise exception.NotFound("%s not found" % id)
+
+ def _get_volume(self, volume_id):
+ for volume in self.volumes:
+ if volume['volume_id'] == volume_id:
+ return volume
+
+ def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
+ volume = self._get_volume(volume_id)
+ storage_node = volume['node_name']
+ # TODO: (joshua) Fix volumes to store creator id
+ if not context.user.is_authorized(volume.get('user_id', None)):
+ raise exception.ApiError("%s not authorized for %s" %
+ (context.user.id, volume_id))
+ instance = self.instdir.get(instance_id)
+ compute_node = instance['node_name']
+ if not context.user.is_authorized(instance.get('owner_id', None)):
+ raise exception.ApiError(message="%s not authorized for %s" %
+ (context.user.id, instance_id))
+ aoe_device = volume['aoe_device']
+ # Needs to get right node controller for attaching to
+ # TODO: Maybe have another exchange that goes to everyone?
+ rpc.cast('%s.%s' % (FLAGS.compute_topic, compute_node),
+ {"method": "attach_volume",
+ "args" : {"aoe_device": aoe_device,
+ "instance_id" : instance_id,
+ "mountpoint" : device}})
+ rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
+ {"method": "attach_volume",
+ "args" : {"volume_id": volume_id,
+ "instance_id" : instance_id,
+ "mountpoint" : device}})
+ return defer.succeed(True)
+
+ def detach_volume(self, context, volume_id, **kwargs):
+ # TODO(joshua): Make sure the updated state has been received first
+ volume = self._get_volume(volume_id)
+ storage_node = volume['node_name']
+ if not context.user.is_authorized(volume.get('user_id', None)):
+ raise exception.ApiError("%s not authorized for %s" %
+ (context.user.id, volume_id))
+ if 'instance_id' in volume.keys():
+ instance_id = volume['instance_id']
+ try:
+ instance = self.instdir.get(instance_id)
+ compute_node = instance['node_name']
+ mountpoint = volume['mountpoint']
+ if not context.user.is_authorized(
+ instance.get('owner_id', None)):
+ raise exception.ApiError(
+ "%s not authorized for %s" %
+ (context.user.id, instance_id))
+ rpc.cast('%s.%s' % (FLAGS.compute_topic, compute_node),
+ {"method": "detach_volume",
+ "args" : {"instance_id": instance_id,
+ "mountpoint": mountpoint}})
+ except exception.NotFound:
+ pass
+ rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
+ {"method": "detach_volume",
+ "args" : {"volume_id": volume_id}})
+ return defer.succeed(True)
+
+ def _convert_to_set(self, lst, str):
+ if lst == None or lst == []:
+ return None
+ return [{str: x} for x in lst]
+
+ def describe_instances(self, context, **kwargs):
+ return defer.succeed(self.format_instances(context.user))
+
+ def format_instances(self, user, reservation_id = None):
+ if self.instances == {}:
+ return {'reservationSet': []}
+ reservations = {}
+ for inst in self.instances:
+ instance = inst.values()[0]
+ res_id = instance.get('reservation_id', 'Unknown')
+ if (user.is_authorized(instance.get('owner_id', None))
+ and (reservation_id == None or reservation_id == res_id)):
+ i = {}
+ i['instance_id'] = instance.get('instance_id', None)
+ i['image_id'] = instance.get('image_id', None)
+ i['instance_state'] = {
+ 'code': 42,
+ 'name': instance.get('state', 'pending')
+ }
+ i['public_dns_name'] = self.network.get_public_ip_for_instance(
+ i['instance_id'])
+ i['private_dns_name'] = instance.get('private_dns_name', None)
+ if not i['public_dns_name']:
+ i['public_dns_name'] = i['private_dns_name']
+ i['dns_name'] = instance.get('dns_name', None)
+ i['key_name'] = instance.get('key_name', None)
+ if user.is_admin():
+ i['key_name'] = '%s (%s, %s)' % (i['key_name'],
+ instance.get('owner_id', None), instance.get('node_name',''))
+ i['product_codes_set'] = self._convert_to_set(
+ instance.get('product_codes', None), 'product_code')
+ i['instance_type'] = instance.get('instance_type', None)
+ i['launch_time'] = instance.get('launch_time', None)
+ i['ami_launch_index'] = instance.get('ami_launch_index',
+ None)
+ if not reservations.has_key(res_id):
+ r = {}
+ r['reservation_id'] = res_id
+ r['owner_id'] = instance.get('owner_id', None)
+ r['group_set'] = self._convert_to_set(
+ instance.get('groups', None), 'group_id')
+ r['instances_set'] = []
+ reservations[res_id] = r
+ reservations[res_id]['instances_set'].append(i)
+
+ instance_response = {'reservationSet' : list(reservations.values()) }
+ return instance_response
+
+ def describe_addresses(self, context, **kwargs):
+ return self.format_addresses(context.user)
+
+ def format_addresses(self, user):
+ addresses = []
+ # TODO(vish): move authorization checking into network.py
+ for address_record in self.network.describe_addresses(
+ type=network.PublicNetwork):
+ #logging.debug(address_record)
+ if user.is_authorized(address_record[u'user_id']):
+ address = {
+ 'public_ip': address_record[u'address'],
+ 'instance_id' : address_record.get(u'instance_id', 'free')
+ }
+ # FIXME: add another field for user id
+ if user.is_admin():
+ address['instance_id'] = "%s (%s)" % (
+ address['instance_id'],
+ address_record[u'user_id'],
+ )
+ addresses.append(address)
+ # logging.debug(addresses)
+ return {'addressesSet': addresses}
+
+ def allocate_address(self, context, **kwargs):
+ # TODO: Verify user is valid?
+ kwargs['owner_id'] = context.user.id
+ (address,network_name) = self.network.allocate_address(
+ context.user.id, type=network.PublicNetwork)
+ return defer.succeed({'addressSet': [{'publicIp' : address}]})
+
+ def release_address(self, context, **kwargs):
+ self.network.deallocate_address(kwargs.get('public_ip', None))
+ return defer.succeed({'releaseResponse': ["Address released."]})
+
+ def associate_address(self, context, instance_id, **kwargs):
+ instance = self.instdir.get(instance_id)
+ rv = self.network.associate_address(
+ kwargs['public_ip'],
+ instance['private_dns_name'],
+ instance_id)
+ return defer.succeed({'associateResponse': ["Address associated."]})
+
+ def disassociate_address(self, context, **kwargs):
+ rv = self.network.disassociate_address(kwargs['public_ip'])
+ # TODO - Strip the IP from the instance
+ return rv
+
+ def run_instances(self, context, **kwargs):
+ logging.debug("Going to run instances...")
+ reservation_id = utils.generate_uid('r')
+ launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
+ key_data = None
+ if kwargs.has_key('key_name'):
+ key_pair = context.user.get_key_pair(kwargs['key_name'])
+ if not key_pair:
+ raise exception.ApiError('Key Pair %s not found' %
+ kwargs['key_name'])
+ key_data = key_pair.public_key
+
+ for num in range(int(kwargs['max_count'])):
+ inst = self.instdir.new()
+ # TODO(ja): add ari, aki
+ inst['image_id'] = kwargs['image_id']
+ inst['user_data'] = kwargs.get('user_data', '')
+ inst['instance_type'] = kwargs.get('instance_type', '')
+ inst['reservation_id'] = reservation_id
+ inst['launch_time'] = launch_time
+ inst['key_data'] = key_data or ''
+ inst['key_name'] = kwargs.get('key_name', '')
+ inst['owner_id'] = context.user.id
+ inst['mac_address'] = utils.generate_mac()
+ inst['ami_launch_index'] = num
+ address, _netname = self.network.allocate_address(
+ inst['owner_id'], mac=inst['mac_address'])
+ network = self.network.get_users_network(str(context.user.id))
+ inst['network_str'] = json.dumps(network.to_dict())
+ inst['bridge_name'] = network.bridge_name
+ inst['private_dns_name'] = str(address)
+ # TODO: allocate expresses on the router node
+ inst.save()
+ rpc.cast(FLAGS.compute_topic,
+ {"method": "run_instance",
+ "args": {"instance_id" : inst.instance_id}})
+ logging.debug("Casting to node for %s's instance with IP of %s" %
+ (context.user.name, inst['private_dns_name']))
+ # TODO: Make the NetworkComputeNode figure out the network name from ip.
+ return defer.succeed(self.format_instances(
+ context.user, reservation_id))
+
+ def terminate_instances(self, context, instance_id, **kwargs):
+ logging.debug("Going to start terminating instances")
+ # TODO: return error if not authorized
+ for i in instance_id:
+ logging.debug("Going to try and terminate %s" % i)
+ instance = self.instdir.get(i)
+ #if instance['state'] == 'pending':
+ # raise exception.ApiError('Cannot terminate pending instance')
+ if context.user.is_authorized(instance.get('owner_id', None)):
+ try:
+ self.network.disassociate_address(
+ instance.get('public_dns_name', 'bork'))
+ except:
+ pass
+ if instance.get('private_dns_name', None):
+ logging.debug("Deallocating address %s" % instance.get('private_dns_name', None))
+ try:
+ self.network.deallocate_address(instance.get('private_dns_name', None))
+ except Exception, _err:
+ pass
+ if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
+ rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
+ {"method": "terminate_instance",
+ "args" : {"instance_id": i}})
+ else:
+ instance.destroy()
+ return defer.succeed(True)
+
+ def reboot_instances(self, context, instance_id, **kwargs):
+ # TODO: return error if not authorized
+ for i in instance_id:
+ instance = self.instdir.get(i)
+ if instance['state'] == 'pending':
+ raise exception.ApiError('Cannot reboot pending instance')
+ if context.user.is_authorized(instance.get('owner_id', None)):
+ rpc.cast('%s.%s' % (FLAGS.node_topic, instance['node_name']),
+ {"method": "reboot_instance",
+ "args" : {"instance_id": i}})
+ return defer.succeed(True)
+
+ def delete_volume(self, context, volume_id, **kwargs):
+ # TODO: return error if not authorized
+ volume = self._get_volume(volume_id)
+ storage_node = volume['node_name']
+ if context.user.is_authorized(volume.get('user_id', None)):
+ rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
+ {"method": "delete_volume",
+ "args" : {"volume_id": volume_id}})
+ return defer.succeed(True)
+
+ def describe_images(self, context, image_id=None, **kwargs):
+ imageSet = images.list(context.user)
+ if not image_id is None:
+ imageSet = [i for i in imageSet if i['imageId'] in image_id]
+
+ return defer.succeed({'imagesSet': imageSet})
+
+ def deregister_image(self, context, image_id, **kwargs):
+ images.deregister(context.user, image_id)
+
+ return defer.succeed({'imageId': image_id})
+
+ def register_image(self, context, image_location=None, **kwargs):
+ if image_location is None and kwargs.has_key('name'):
+ image_location = kwargs['name']
+
+ image_id = images.register(context.user, image_location)
+ logging.debug("Registered %s as %s" % (image_location, image_id))
+
+ return defer.succeed({'imageId': image_id})
+
+ def modify_image_attribute(self, context, image_id,
+ attribute, operation_type, **kwargs):
+ if attribute != 'launchPermission':
+ raise exception.ApiError('only launchPermission is supported')
+ if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all':
+ raise exception.ApiError('only group "all" is supported')
+ if not operation_type in ['add', 'delete']:
+ raise exception.ApiError('operation_type must be add or delete')
+ result = images.modify(context.user, image_id, operation_type)
+ return defer.succeed(result)
+
+ def update_state(self, topic, value):
+ """ accepts status reports from the queue and consolidates them """
+ # TODO(jmc): if an instance has disappeared from
+ # the node, call instance_death
+ if topic == "instances":
+ return defer.succeed(True)
+ aggregate_state = getattr(self, topic)
+ node_name = value.keys()[0]
+ items = value[node_name]
+
+ logging.debug("Updating %s state for %s" % (topic, node_name))
+
+ for item_id in items.keys():
+ if (aggregate_state.has_key('pending') and
+ aggregate_state['pending'].has_key(item_id)):
+ del aggregate_state['pending'][item_id]
+ aggregate_state[node_name] = items
+
+ return defer.succeed(True)
diff --git a/nova/endpoint/images.py b/nova/endpoint/images.py
new file mode 100644
index 0000000000..f494ce8926
--- /dev/null
+++ b/nova/endpoint/images.py
@@ -0,0 +1,92 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Proxy AMI-related calls from the cloud controller, to the running
+objectstore daemon.
+"""
+
+import json
+import random
+import urllib
+
+from nova import vendor
+import boto
+import boto.s3
+
+from nova import flags
+from nova import utils
+
+FLAGS = flags.FLAGS
+
+
+def modify(user, image_id, operation):
+ conn(user).make_request(
+ method='POST',
+ bucket='_images',
+ query_args=qs({'image_id': image_id, 'operation': operation}))
+
+ return True
+
+
+def register(user, image_location):
+ """ rpc call to register a new image based from a manifest """
+
+ image_id = utils.generate_uid('ami')
+ conn(user).make_request(
+ method='PUT',
+ bucket='_images',
+ query_args=qs({'image_location': image_location,
+ 'image_id': image_id}))
+
+ return image_id
+
+
+def list(user, filter_list=[]):
+ """ return a list of all images that a user can see
+
+ optionally filtered by a list of image_id """
+
+ # FIXME: send along the list of only_images to check for
+ response = conn(user).make_request(
+ method='GET',
+ bucket='_images')
+
+ return json.loads(response.read())
+
+
+def deregister(user, image_id):
+ """ unregister an image """
+ conn(user).make_request(
+ method='DELETE',
+ bucket='_images',
+ query_args=qs({'image_id': image_id}))
+
+
+def conn(user):
+ return boto.s3.connection.S3Connection (
+ aws_access_key_id=user.access,
+ aws_secret_access_key=user.secret,
+ is_secure=False,
+ calling_format=boto.s3.connection.OrdinaryCallingFormat(),
+ port=FLAGS.s3_port,
+ host=FLAGS.s3_host)
+
+
+def qs(params):
+ pairs = []
+ for key in params.keys():
+ pairs.append(key + '=' + urllib.quote(params[key]))
+ return '&'.join(pairs)
diff --git a/nova/exception.py b/nova/exception.py
new file mode 100644
index 0000000000..dc7b16cdbb
--- /dev/null
+++ b/nova/exception.py
@@ -0,0 +1,53 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Nova base exception handling, including decorator for re-raising
+Nova-type exceptions. SHOULD include dedicated exception logging.
+"""
+
+import logging
+import traceback
+import sys
+
+class Error(Exception):
+ pass
+
+class ApiError(Error):
+ def __init__(self, message='Unknown', code='Unknown'):
+ self.message = message
+ self.code = code
+
+class NotFound(Error):
+ pass
+
+class NotAuthorized(Error):
+ pass
+
+def wrap_exception(f):
+ def _wrap(*args, **kw):
+ try:
+ return f(*args, **kw)
+ except Exception, e:
+ if not isinstance(e, Error):
+ # exc_type, exc_value, exc_traceback = sys.exc_info()
+ logging.exception('Uncaught exception')
+ # logging.debug(traceback.extract_stack(exc_traceback))
+ raise Error(str(e))
+ raise
+ _wrap.func_name = f.func_name
+ return _wrap
+
+
diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py
new file mode 100644
index 0000000000..ec2e50791f
--- /dev/null
+++ b/nova/fakerabbit.py
@@ -0,0 +1,131 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" Based a bit on the carrot.backeds.queue backend... but a lot better """
+
+import logging
+import Queue as queue
+
+from carrot.backends import base
+
+
+class Message(base.BaseMessage):
+ pass
+
+
+class Exchange(object):
+ def __init__(self, name, exchange_type):
+ self.name = name
+ self.exchange_type = exchange_type
+ self._queue = queue.Queue()
+ self._routes = {}
+
+ def publish(self, message, routing_key=None):
+ logging.debug('(%s) publish (key: %s) %s',
+ self.name, routing_key, message)
+ if routing_key in self._routes:
+ for f in self._routes[routing_key]:
+ logging.debug('Publishing to route %s', f)
+ f(message, routing_key=routing_key)
+
+ def bind(self, callback, routing_key):
+ self._routes.setdefault(routing_key, [])
+ self._routes[routing_key].append(callback)
+
+
+class Queue(object):
+ def __init__(self, name):
+ self.name = name
+ self._queue = queue.Queue()
+
+ def __repr__(self):
+ return '<Queue: %s>' % self.name
+
+ def push(self, message, routing_key=None):
+ self._queue.put(message)
+
+ def size(self):
+ return self._queue.qsize()
+
+ def pop(self):
+ return self._queue.get()
+
+
+class Backend(object):
+ """ Singleton backend for testing """
+ class __impl(base.BaseBackend):
+ def __init__(self, *args, **kwargs):
+ #super(__impl, self).__init__(*args, **kwargs)
+ self._exchanges = {}
+ self._queues = {}
+
+ def _reset_all(self):
+ self._exchanges = {}
+ self._queues = {}
+
+ def queue_declare(self, queue, **kwargs):
+ if queue not in self._queues:
+ logging.debug('Declaring queue %s', queue)
+ self._queues[queue] = Queue(queue)
+
+ def exchange_declare(self, exchange, type, *args, **kwargs):
+ if exchange not in self._exchanges:
+ logging.debug('Declaring exchange %s', exchange)
+ self._exchanges[exchange] = Exchange(exchange, type)
+
+ def queue_bind(self, queue, exchange, routing_key, **kwargs):
+ logging.debug('Binding %s to %s with key %s',
+ queue, exchange, routing_key)
+ self._exchanges[exchange].bind(self._queues[queue].push,
+ routing_key)
+
+ def get(self, queue, no_ack=False):
+ if not self._queues[queue].size():
+ return None
+ (message_data, content_type, content_encoding) = \
+ self._queues[queue].pop()
+ message = Message(backend=self, body=message_data,
+ content_type=content_type,
+ content_encoding=content_encoding)
+ logging.debug('Getting from %s: %s', queue, message)
+ return message
+
+ def prepare_message(self, message_data, delivery_mode,
+ content_type, content_encoding, **kwargs):
+ """Prepare message for sending."""
+ return (message_data, content_type, content_encoding)
+
+ def publish(self, message, exchange, routing_key, **kwargs):
+ if exchange in self._exchanges:
+ self._exchanges[exchange].publish(
+ message, routing_key=routing_key)
+
+
+ __instance = None
+
+ def __init__(self, *args, **kwargs):
+ if Backend.__instance is None:
+ Backend.__instance = Backend.__impl(*args, **kwargs)
+ self.__dict__['_Backend__instance'] = Backend.__instance
+
+ def __getattr__(self, attr):
+ return getattr(self.__instance, attr)
+
+ def __setattr__(self, attr, value):
+ return setattr(self.__instance, attr, value)
+
+
+def reset_all():
+ Backend()._reset_all()
diff --git a/nova/fakevirt.py b/nova/fakevirt.py
new file mode 100644
index 0000000000..2b918d388a
--- /dev/null
+++ b/nova/fakevirt.py
@@ -0,0 +1,109 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+A fake (in-memory) hypervisor+api. Allows nova testing w/o KVM and libvirt.
+"""
+
+import StringIO
+from xml.etree import ElementTree
+
+
+class FakeVirtConnection(object):
+ # FIXME: networkCreateXML, listNetworks don't do anything since
+ # they aren't exercised in tests yet
+
+ def __init__(self):
+ self.next_index = 0
+ self.instances = {}
+
+ @classmethod
+ def instance(cls):
+ if not hasattr(cls, '_instance'):
+ cls._instance = cls()
+ return cls._instance
+
+ def lookupByID(self, i):
+ return self.instances[str(i)]
+
+ def listDomainsID(self):
+ return self.instances.keys()
+
+ def listNetworks(self):
+ return []
+
+ def lookupByName(self, instance_id):
+ for x in self.instances.values():
+ if x.name() == instance_id:
+ return x
+ raise Exception('no instance found for instance_id: %s' % instance_id)
+
+ def networkCreateXML(self, xml):
+ pass
+
+ def createXML(self, xml, flags):
+ # parse the xml :(
+ xml_stringio = StringIO.StringIO(xml)
+
+ my_xml = ElementTree.parse(xml_stringio)
+ name = my_xml.find('name').text
+
+ fake_instance = FakeVirtInstance(conn=self,
+ index=str(self.next_index),
+ name=name,
+ xml=my_xml)
+ self.instances[str(self.next_index)] = fake_instance
+ self.next_index += 1
+
+ def _removeInstance(self, i):
+ self.instances.pop(str(i))
+
+
+class FakeVirtInstance(object):
+ NOSTATE = 0x00
+ RUNNING = 0x01
+ BLOCKED = 0x02
+ PAUSED = 0x03
+ SHUTDOWN = 0x04
+ SHUTOFF = 0x05
+ CRASHED = 0x06
+
+ def __init__(self, conn, index, name, xml):
+ self._conn = conn
+ self._destroyed = False
+ self._name = name
+ self._index = index
+ self._state = self.RUNNING
+
+ def name(self):
+ return self._name
+
+ def destroy(self):
+ if self._state == self.SHUTOFF:
+ raise Exception('instance already destroyed: %s' % self.name())
+ self._state = self.SHUTDOWN
+ self._conn._removeInstance(self._index)
+
+ def info(self):
+ return [self._state, 0, 2, 0, 0]
+
+ def XMLDesc(self, flags):
+ return open('fakevirtinstance.xml', 'r').read()
+
+ def blockStats(self, disk):
+ return [0L, 0L, 0L, 0L, null]
+
+ def interfaceStats(self, iface):
+ return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L]
diff --git a/nova/flags.py b/nova/flags.py
new file mode 100644
index 0000000000..7818e1b14c
--- /dev/null
+++ b/nova/flags.py
@@ -0,0 +1,78 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Package-level global flags are defined here, the rest are defined
+where they're used.
+"""
+
+import socket
+
+from nova import vendor
+from gflags import *
+
+# This keeps pylint from barfing on the imports
+FLAGS = FLAGS
+DEFINE_string = DEFINE_string
+DEFINE_integer = DEFINE_integer
+DEFINE_bool = DEFINE_bool
+
+# __GLOBAL FLAGS ONLY__
+# Define any app-specific flags in their own files, docs at:
+# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39
+
+DEFINE_integer('s3_port', 3333, 's3 port')
+DEFINE_integer('s3_internal_port', 3334, 's3 port')
+DEFINE_string('s3_host', '127.0.0.1', 's3 host')
+#DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on')
+DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on')
+DEFINE_string('storage_topic', 'storage', 'the topic storage nodes listen on')
+DEFINE_bool('fake_libvirt', False,
+ 'whether to use a fake libvirt or not')
+DEFINE_bool('verbose', False, 'show debug output')
+DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit')
+DEFINE_bool('fake_network', False, 'should we use fake network devices and addresses')
+DEFINE_bool('fake_users', False, 'use fake users')
+DEFINE_string('rabbit_host', 'localhost', 'rabbit host')
+DEFINE_integer('rabbit_port', 5672, 'rabbit port')
+DEFINE_string('rabbit_userid', 'guest', 'rabbit userid')
+DEFINE_string('rabbit_password', 'guest', 'rabbit password')
+DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host')
+DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to')
+DEFINE_string('ec2_url',
+ 'http://127.0.0.1:8773/services/Cloud',
+ 'Url to ec2 api server')
+
+DEFINE_string('default_image',
+ 'ami-11111',
+ 'default image to use, testing only')
+DEFINE_string('default_kernel',
+ 'aki-11111',
+ 'default kernel to use, testing only')
+DEFINE_string('default_ramdisk',
+ 'ari-11111',
+ 'default ramdisk to use, testing only')
+DEFINE_string('default_instance_type',
+ 'm1.small',
+ 'default instance type to use, testing only')
+
+# UNUSED
+DEFINE_string('node_availability_zone',
+ 'nova',
+ 'availability zone of this node')
+DEFINE_string('node_name',
+ socket.gethostname(),
+ 'name of this node')
+
diff --git a/nova/objectstore/__init__.py b/nova/objectstore/__init__.py
new file mode 100644
index 0000000000..c6c09e53e7
--- /dev/null
+++ b/nova/objectstore/__init__.py
@@ -0,0 +1,28 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:mod:`nova.objectstore` -- S3-type object store
+=====================================================
+
+.. automodule:: nova.objectstore
+ :platform: Unix
+ :synopsis: Currently a trivial file-based system, getting extended w/ mongo.
+.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
+.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
+.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
+.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
+.. moduleauthor:: Manish Singh <yosh@gimp.org>
+.. moduleauthor:: Andy Smith <andy@anarkystic.com>
+""" \ No newline at end of file
diff --git a/nova/objectstore/bucket.py b/nova/objectstore/bucket.py
new file mode 100644
index 0000000000..0777c2f11c
--- /dev/null
+++ b/nova/objectstore/bucket.py
@@ -0,0 +1,174 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Simple object store using Blobs and JSON files on disk.
+"""
+
+import datetime
+import glob
+import json
+import os
+import bisect
+
+from nova import exception
+from nova import flags
+from nova import utils
+from nova.objectstore import stored
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('buckets_path', utils.abspath('../buckets'),
+ 'path to s3 buckets')
+
+
+class Bucket(object):
+ def __init__(self, name):
+ self.name = name
+ self.path = os.path.abspath(os.path.join(FLAGS.buckets_path, name))
+ if not self.path.startswith(os.path.abspath(FLAGS.buckets_path)) or \
+ not os.path.isdir(self.path):
+ raise exception.NotFound()
+
+ self.ctime = os.path.getctime(self.path)
+
+ def __repr__(self):
+ return "<Bucket: %s>" % self.name
+
+ @staticmethod
+ def all():
+ """ list of all buckets """
+ buckets = []
+ for fn in glob.glob("%s/*.json" % FLAGS.buckets_path):
+ try:
+ json.load(open(fn))
+ name = os.path.split(fn)[-1][:-5]
+ buckets.append(Bucket(name))
+ except:
+ pass
+
+ return buckets
+
+ @staticmethod
+ def create(bucket_name, user):
+ """Create a new bucket owned by a user.
+
+ @bucket_name: a string representing the name of the bucket to create
+ @user: a nova.auth.user who should own the bucket.
+
+ Raises:
+ NotAuthorized: if the bucket is already exists or has invalid name
+ """
+ path = os.path.abspath(os.path.join(
+ FLAGS.buckets_path, bucket_name))
+ if not path.startswith(os.path.abspath(FLAGS.buckets_path)) or \
+ os.path.exists(path):
+ raise exception.NotAuthorized()
+
+ os.makedirs(path)
+
+ with open(path+'.json', 'w') as f:
+ json.dump({'ownerId': user.id}, f)
+
+ @property
+ def metadata(self):
+ """ dictionary of metadata around bucket,
+ keys are 'Name' and 'CreationDate'
+ """
+
+ return {
+ "Name": self.name,
+ "CreationDate": datetime.datetime.utcfromtimestamp(self.ctime),
+ }
+
+ @property
+ def owner_id(self):
+ try:
+ with open(self.path+'.json') as f:
+ return json.load(f)['ownerId']
+ except:
+ return None
+
+ def is_authorized(self, user):
+ try:
+ return user.is_admin() or self.owner_id == user.id
+ except Exception, e:
+ pass
+
+ def list_keys(self, prefix='', marker=None, max_keys=1000, terse=False):
+ object_names = []
+ for root, dirs, files in os.walk(self.path):
+ for file_name in files:
+ object_names.append(os.path.join(root, file_name)[len(self.path)+1:])
+ object_names.sort()
+ contents = []
+
+ start_pos = 0
+ if marker:
+ start_pos = bisect.bisect_right(object_names, marker, start_pos)
+ if prefix:
+ start_pos = bisect.bisect_left(object_names, prefix, start_pos)
+
+ truncated = False
+ for object_name in object_names[start_pos:]:
+ if not object_name.startswith(prefix):
+ break
+ if len(contents) >= max_keys:
+ truncated = True
+ break
+ object_path = self._object_path(object_name)
+ c = {"Key": object_name}
+ if not terse:
+ info = os.stat(object_path)
+ c.update({
+ "LastModified": datetime.datetime.utcfromtimestamp(
+ info.st_mtime),
+ "Size": info.st_size,
+ })
+ contents.append(c)
+ marker = object_name
+
+ return {
+ "Name": self.name,
+ "Prefix": prefix,
+ "Marker": marker,
+ "MaxKeys": max_keys,
+ "IsTruncated": truncated,
+ "Contents": contents,
+ }
+
+ def _object_path(self, object_name):
+ fn = os.path.join(self.path, object_name)
+
+ if not fn.startswith(self.path):
+ raise exception.NotAuthorized()
+
+ return fn
+
+ def delete(self):
+ if len(os.listdir(self.path)) > 0:
+ raise exception.NotAuthorized()
+ os.rmdir(self.path)
+ os.remove(self.path+'.json')
+
+ def __getitem__(self, key):
+ return stored.Object(self, key)
+
+ def __setitem__(self, key, value):
+ with open(self._object_path(key), 'wb') as f:
+ f.write(value)
+
+ def __delitem__(self, key):
+ stored.Object(self, key).delete()
diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py
new file mode 100644
index 0000000000..c3e036a403
--- /dev/null
+++ b/nova/objectstore/handler.py
@@ -0,0 +1,285 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Implementation of an S3-like storage server based on local files.
+
+Useful to test features that will eventually run on S3, or if you want to
+run something locally that was once running on S3.
+
+We don't support all the features of S3, but it does work with the
+standard S3 client for the most basic semantics. To use the standard
+S3 client with this module::
+
+ c = S3.AWSAuthConnection("", "", server="localhost", port=8888,
+ is_secure=False)
+ c.create_bucket("mybucket")
+ c.put("mybucket", "mykey", "a value")
+ print c.get("mybucket", "mykey").body
+
+"""
+import datetime
+import os
+import urllib
+import json
+import logging
+import multiprocessing
+
+
+from nova import vendor
+from tornado import escape, web
+
+from nova import exception
+from nova import flags
+from nova.objectstore import bucket
+from nova.objectstore import image
+
+
+FLAGS = flags.FLAGS
+
+
+def catch_nova_exceptions(target):
+ # FIXME: find a way to wrap all handlers in the web.Application.__init__ ?
+ def wrapper(*args, **kwargs):
+ try:
+ return target(*args, **kwargs)
+ except exception.NotFound:
+ raise web.HTTPError(404)
+ except exception.NotAuthorized:
+ raise web.HTTPError(403)
+
+ return wrapper
+
+
+class Application(web.Application):
+ """Implementation of an S3-like storage server based on local files."""
+ def __init__(self, user_manager):
+ web.Application.__init__(self, [
+ (r"/", RootHandler),
+ (r"/_images/", ImageHandler),
+ (r"/([^/]+)/(.+)", ObjectHandler),
+ (r"/([^/]+)/", BucketHandler),
+ ])
+ self.buckets_path = os.path.abspath(FLAGS.buckets_path)
+ self.images_path = os.path.abspath(FLAGS.images_path)
+
+ if not os.path.exists(self.buckets_path):
+ raise Exception("buckets_path does not exist")
+ if not os.path.exists(self.images_path):
+ raise Exception("images_path does not exist")
+ self.user_manager = user_manager
+
+
+class BaseRequestHandler(web.RequestHandler):
+ SUPPORTED_METHODS = ("PUT", "GET", "DELETE", "HEAD")
+
+ @property
+ def user(self):
+ if not hasattr(self, '_user'):
+ try:
+ access = self.request.headers['Authorization'].split(' ')[1].split(':')[0]
+ user = self.application.user_manager.get_user_from_access_key(access)
+ user.secret # FIXME: check signature here!
+ self._user = user
+ except:
+ raise web.HTTPError(403)
+ return self._user
+
+ def render_xml(self, value):
+ assert isinstance(value, dict) and len(value) == 1
+ self.set_header("Content-Type", "application/xml; charset=UTF-8")
+ name = value.keys()[0]
+ parts = []
+ parts.append('<' + escape.utf8(name) +
+ ' xmlns="http://doc.s3.amazonaws.com/2006-03-01">')
+ self._render_parts(value.values()[0], parts)
+ parts.append('</' + escape.utf8(name) + '>')
+ self.finish('<?xml version="1.0" encoding="UTF-8"?>\n' +
+ ''.join(parts))
+
+ def _render_parts(self, value, parts=[]):
+ if isinstance(value, basestring):
+ parts.append(escape.xhtml_escape(value))
+ elif isinstance(value, int) or isinstance(value, long):
+ parts.append(str(value))
+ elif isinstance(value, datetime.datetime):
+ parts.append(value.strftime("%Y-%m-%dT%H:%M:%S.000Z"))
+ elif isinstance(value, dict):
+ for name, subvalue in value.iteritems():
+ if not isinstance(subvalue, list):
+ subvalue = [subvalue]
+ for subsubvalue in subvalue:
+ parts.append('<' + escape.utf8(name) + '>')
+ self._render_parts(subsubvalue, parts)
+ parts.append('</' + escape.utf8(name) + '>')
+ else:
+ raise Exception("Unknown S3 value type %r", value)
+
+ def head(self, *args, **kwargs):
+ return self.get(*args, **kwargs)
+
+
+class RootHandler(BaseRequestHandler):
+ def get(self):
+ buckets = [b for b in bucket.Bucket.all() if b.is_authorized(self.user)]
+
+ self.render_xml({"ListAllMyBucketsResult": {
+ "Buckets": {"Bucket": [b.metadata for b in buckets]},
+ }})
+
+
+class BucketHandler(BaseRequestHandler):
+ @catch_nova_exceptions
+ def get(self, bucket_name):
+ logging.debug("List keys for bucket %s" % (bucket_name))
+
+ bucket_object = bucket.Bucket(bucket_name)
+
+ if not bucket_object.is_authorized(self.user):
+ raise web.HTTPError(403)
+
+ prefix = self.get_argument("prefix", u"")
+ marker = self.get_argument("marker", u"")
+ max_keys = int(self.get_argument("max-keys", 1000))
+ terse = int(self.get_argument("terse", 0))
+
+ results = bucket_object.list_keys(prefix=prefix, marker=marker, max_keys=max_keys, terse=terse)
+ self.render_xml({"ListBucketResult": results})
+
+ @catch_nova_exceptions
+ def put(self, bucket_name):
+ logging.debug("Creating bucket %s" % (bucket_name))
+ bucket.Bucket.create(bucket_name, self.user)
+ self.finish()
+
+ @catch_nova_exceptions
+ def delete(self, bucket_name):
+ logging.debug("Deleting bucket %s" % (bucket_name))
+ bucket_object = bucket.Bucket(bucket_name)
+
+ if not bucket_object.is_authorized(self.user):
+ raise web.HTTPError(403)
+
+ bucket_object.delete()
+ self.set_status(204)
+ self.finish()
+
+
+class ObjectHandler(BaseRequestHandler):
+ @catch_nova_exceptions
+ def get(self, bucket_name, object_name):
+ logging.debug("Getting object: %s / %s" % (bucket_name, object_name))
+
+ bucket_object = bucket.Bucket(bucket_name)
+
+ if not bucket_object.is_authorized(self.user):
+ raise web.HTTPError(403)
+
+ obj = bucket_object[urllib.unquote(object_name)]
+ self.set_header("Content-Type", "application/unknown")
+ self.set_header("Last-Modified", datetime.datetime.utcfromtimestamp(obj.mtime))
+ self.set_header("Etag", '"' + obj.md5 + '"')
+ self.finish(obj.read())
+
+ @catch_nova_exceptions
+ def put(self, bucket_name, object_name):
+ logging.debug("Putting object: %s / %s" % (bucket_name, object_name))
+ bucket_object = bucket.Bucket(bucket_name)
+
+ if not bucket_object.is_authorized(self.user):
+ raise web.HTTPError(403)
+
+ key = urllib.unquote(object_name)
+ bucket_object[key] = self.request.body
+ self.set_header("Etag", '"' + bucket_object[key].md5 + '"')
+ self.finish()
+
+ @catch_nova_exceptions
+ def delete(self, bucket_name, object_name):
+ logging.debug("Deleting object: %s / %s" % (bucket_name, object_name))
+ bucket_object = bucket.Bucket(bucket_name)
+
+ if not bucket_object.is_authorized(self.user):
+ raise web.HTTPError(403)
+
+ del bucket_object[urllib.unquote(object_name)]
+ self.set_status(204)
+ self.finish()
+
+
+class ImageHandler(BaseRequestHandler):
+ SUPPORTED_METHODS = ("POST", "PUT", "GET", "DELETE")
+
+ @catch_nova_exceptions
+ def get(self):
+ """ returns a json listing of all images
+ that a user has permissions to see """
+
+ images = [i for i in image.Image.all() if i.is_authorized(self.user)]
+
+ self.finish(json.dumps([i.metadata for i in images]))
+
+ @catch_nova_exceptions
+ def put(self):
+ """ create a new registered image """
+
+ image_id = self.get_argument('image_id', u'')
+ image_location = self.get_argument('image_location', u'')
+
+ image_path = os.path.join(FLAGS.images_path, image_id)
+ if not image_path.startswith(FLAGS.images_path) or \
+ os.path.exists(image_path):
+ raise web.HTTPError(403)
+
+ bucket_object = bucket.Bucket(image_location.split("/")[0])
+ manifest = image_location[len(image_location.split('/')[0])+1:]
+
+ if not bucket_object.is_authorized(self.user):
+ raise web.HTTPError(403)
+
+ p = multiprocessing.Process(target=image.Image.create,args=
+ (image_id, image_location, self.user))
+ p.start()
+ self.finish()
+
+ @catch_nova_exceptions
+ def post(self):
+ """ update image attributes: public/private """
+
+ image_id = self.get_argument('image_id', u'')
+ operation = self.get_argument('operation', u'')
+
+ image_object = image.Image(image_id)
+
+ if image_object.owner_id != self.user.id:
+ raise web.HTTPError(403)
+
+ image_object.set_public(operation=='add')
+
+ self.finish()
+
+ @catch_nova_exceptions
+ def delete(self):
+ """ delete a registered image """
+ image_id = self.get_argument("image_id", u"")
+ image_object = image.Image(image_id)
+
+ if image_object.owner_id != self.user.id:
+ raise web.HTTPError(403)
+
+ image_object.delete()
+
+ self.set_status(204)
diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py
new file mode 100644
index 0000000000..1878487f7c
--- /dev/null
+++ b/nova/objectstore/image.py
@@ -0,0 +1,177 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Take uploaded bucket contents and register them as disk images (AMIs).
+Requires decryption using keys in the manifest.
+"""
+
+# TODO(jesse): Got these from Euca2ools, will need to revisit them
+
+import binascii
+import glob
+import json
+import os
+import shutil
+import tarfile
+import tempfile
+from xml.etree import ElementTree
+
+from nova import exception
+from nova import flags
+from nova import utils
+from nova.objectstore import bucket
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('images_path', utils.abspath('../images'),
+ 'path to decrypted images')
+
+class Image(object):
+ def __init__(self, image_id):
+ self.image_id = image_id
+ self.path = os.path.abspath(os.path.join(FLAGS.images_path, image_id))
+ if not self.path.startswith(os.path.abspath(FLAGS.images_path)) or \
+ not os.path.isdir(self.path):
+ raise exception.NotFound
+
+ def delete(self):
+ for fn in ['info.json', 'image']:
+ try:
+ os.unlink(os.path.join(self.path, fn))
+ except:
+ pass
+ try:
+ os.rmdir(self.path)
+ except:
+ pass
+
+ def is_authorized(self, user):
+ try:
+ return self.metadata['isPublic'] or self.metadata['imageOwnerId'] == user.id
+ except:
+ return False
+
+ def set_public(self, state):
+ md = self.metadata
+ md['isPublic'] = state
+ with open(os.path.join(self.path, 'info.json'), 'w') as f:
+ json.dump(md, f)
+
+ @staticmethod
+ def all():
+ images = []
+ for fn in glob.glob("%s/*/info.json" % FLAGS.images_path):
+ try:
+ image_id = fn.split('/')[-2]
+ images.append(Image(image_id))
+ except:
+ pass
+ return images
+
+ @property
+ def owner_id(self):
+ return self.metadata['imageOwnerId']
+
+ @property
+ def metadata(self):
+ with open(os.path.join(self.path, 'info.json')) as f:
+ return json.load(f)
+
+ @staticmethod
+ def create(image_id, image_location, user):
+ image_path = os.path.join(FLAGS.images_path, image_id)
+ os.makedirs(image_path)
+
+ bucket_name = image_location.split("/")[0]
+ manifest_path = image_location[len(bucket_name)+1:]
+ bucket_object = bucket.Bucket(bucket_name)
+
+ manifest = ElementTree.fromstring(bucket_object[manifest_path].read())
+ image_type = 'machine'
+
+ try:
+ kernel_id = manifest.find("machine_configuration/kernel_id").text
+ if kernel_id == 'true':
+ image_type = 'kernel'
+ except:
+ pass
+
+ try:
+ ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text
+ if ramdisk_id == 'true':
+ image_type = 'ramdisk'
+ except:
+ pass
+
+ info = {
+ 'imageId': image_id,
+ 'imageLocation': image_location,
+ 'imageOwnerId': user.id,
+ 'isPublic': False, # FIXME: grab public from manifest
+ 'architecture': 'x86_64', # FIXME: grab architecture from manifest
+ 'type' : image_type
+ }
+
+ def write_state(state):
+ info['imageState'] = state
+ with open(os.path.join(image_path, 'info.json'), "w") as f:
+ json.dump(info, f)
+
+ write_state('pending')
+
+ encrypted_filename = os.path.join(image_path, 'image.encrypted')
+ with open(encrypted_filename, 'w') as f:
+ for filename in manifest.find("image").getiterator("filename"):
+ shutil.copyfileobj(bucket_object[filename.text].file, f)
+
+ write_state('decrypting')
+
+ # FIXME: grab kernelId and ramdiskId from bundle manifest
+ encrypted_key = binascii.a2b_hex(manifest.find("image/ec2_encrypted_key").text)
+ encrypted_iv = binascii.a2b_hex(manifest.find("image/ec2_encrypted_iv").text)
+ cloud_private_key = os.path.join(FLAGS.ca_path, "private/cakey.pem")
+
+ decrypted_filename = os.path.join(image_path, 'image.tar.gz')
+ Image.decrypt_image(encrypted_filename, encrypted_key, encrypted_iv, cloud_private_key, decrypted_filename)
+
+ write_state('untarring')
+
+ image_file = Image.untarzip_image(image_path, decrypted_filename)
+ shutil.move(os.path.join(image_path, image_file), os.path.join(image_path, 'image'))
+
+ write_state('available')
+ os.unlink(decrypted_filename)
+ os.unlink(encrypted_filename)
+
+ @staticmethod
+ def decrypt_image(encrypted_filename, encrypted_key, encrypted_iv, cloud_private_key, decrypted_filename):
+ key, err = utils.execute('openssl rsautl -decrypt -inkey %s' % cloud_private_key, encrypted_key)
+ if err:
+ raise exception.Error("Failed to decrypt private key: %s" % err)
+ iv, err = utils.execute('openssl rsautl -decrypt -inkey %s' % cloud_private_key, encrypted_iv)
+ if err:
+ raise exception.Error("Failed to decrypt initialization vector: %s" % err)
+ out, err = utils.execute('openssl enc -d -aes-128-cbc -in %s -K %s -iv %s -out %s' % (encrypted_filename, key, iv, decrypted_filename))
+ if err:
+ raise exception.Error("Failed to decrypt image file %s : %s" % (encrypted_filename, err))
+
+ @staticmethod
+ def untarzip_image(path, filename):
+ tar_file = tarfile.open(filename, "r|gz")
+ tar_file.extractall(path)
+ image_file = tar_file.getnames()[0]
+ tar_file.close()
+ return image_file
diff --git a/nova/objectstore/stored.py b/nova/objectstore/stored.py
new file mode 100644
index 0000000000..05a7a1102c
--- /dev/null
+++ b/nova/objectstore/stored.py
@@ -0,0 +1,58 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Properties of an object stored within a bucket.
+"""
+
+from nova.exception import NotFound, NotAuthorized
+
+import os
+import nova.crypto
+
+class Object(object):
+ def __init__(self, bucket, key):
+ """ wrapper class of an existing key """
+ self.bucket = bucket
+ self.key = key
+ self.path = bucket._object_path(key)
+ if not os.path.isfile(self.path):
+ raise NotFound
+
+ def __repr__(self):
+ return "<Object %s/%s>" % (self.bucket, self.key)
+
+ @property
+ def md5(self):
+ """ computes the MD5 of the contents of file """
+ with open(self.path, "r") as f:
+ return nova.crypto.compute_md5(f)
+
+ @property
+ def mtime(self):
+ """ mtime of file """
+ return os.path.getmtime(self.path)
+
+ def read(self):
+ """ read all contents of key into memory and return """
+ return self.file.read()
+
+ @property
+ def file(self):
+ """ return a file object for the key """
+ return open(self.path, 'rb')
+
+ def delete(self):
+ """ deletes the file """
+ os.unlink(self.path)
diff --git a/nova/process.py b/nova/process.py
new file mode 100644
index 0000000000..754728fdf5
--- /dev/null
+++ b/nova/process.py
@@ -0,0 +1,131 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Process pool, still buggy right now.
+"""
+
+import logging
+import multiprocessing
+
+from nova import vendor
+from twisted.internet import defer
+from twisted.internet import reactor
+from twisted.internet import protocol
+from twisted.internet import threads
+
+# NOTE(termie): this is copied from twisted.internet.utils but since
+# they don't export it I've copied.
+class _BackRelay(protocol.ProcessProtocol):
+ """
+ Trivial protocol for communicating with a process and turning its output
+ into the result of a L{Deferred}.
+
+ @ivar deferred: A L{Deferred} which will be called back with all of stdout
+ and, if C{errortoo} is true, all of stderr as well (mixed together in
+ one string). If C{errortoo} is false and any bytes are received over
+ stderr, this will fire with an L{_UnexpectedErrorOutput} instance and
+ the attribute will be set to C{None}.
+
+ @ivar onProcessEnded: If C{errortoo} is false and bytes are received over
+ stderr, this attribute will refer to a L{Deferred} which will be called
+ back when the process ends. This C{Deferred} is also associated with
+ the L{_UnexpectedErrorOutput} which C{deferred} fires with earlier in
+ this case so that users can determine when the process has actually
+ ended, in addition to knowing when bytes have been received via stderr.
+ """
+
+ def __init__(self, deferred, errortoo=0):
+ self.deferred = deferred
+ self.s = StringIO.StringIO()
+ if errortoo:
+ self.errReceived = self.errReceivedIsGood
+ else:
+ self.errReceived = self.errReceivedIsBad
+
+ def errReceivedIsBad(self, text):
+ if self.deferred is not None:
+ self.onProcessEnded = defer.Deferred()
+ err = _UnexpectedErrorOutput(text, self.onProcessEnded)
+ self.deferred.errback(failure.Failure(err))
+ self.deferred = None
+ self.transport.loseConnection()
+
+ def errReceivedIsGood(self, text):
+ self.s.write(text)
+
+ def outReceived(self, text):
+ self.s.write(text)
+
+ def processEnded(self, reason):
+ if self.deferred is not None:
+ self.deferred.callback(self.s.getvalue())
+ elif self.onProcessEnded is not None:
+ self.onProcessEnded.errback(reason)
+
+
+class BackRelayWithInput(_BackRelay):
+ def __init__(self, deferred, errortoo=0, input=None):
+ super(BackRelayWithInput, self).__init__(deferred, errortoo)
+ self.input = input
+
+ def connectionMade(self):
+ if self.input:
+ self.transport.write(self.input)
+ self.transport.closeStdin()
+
+
+def getProcessOutput(executable, args=None, env=None, path=None, reactor=None,
+ errortoo=0, input=None):
+ if reactor is None:
+ from twisted.internet import reactor
+ args = args and args or ()
+ env = env and env and {}
+ d = defer.Deferred()
+ p = BackRelayWithInput(d, errortoo=errortoo, input=input)
+ reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path)
+ return d
+
+
+class Pool(object):
+ """ A simple process pool implementation around mutliprocessing.
+
+ Allows up to `size` processes at a time and queues the rest.
+
+ Using workarounds for multiprocessing behavior described in:
+ http://pypi.python.org/pypi/twisted.internet.processes/1.0b1
+ """
+
+ def __init__(self, size=None):
+ self._size = size
+ self._pool = multiprocessing.Pool(size)
+ self._registerShutdown()
+
+ def _registerShutdown(self):
+ reactor.addSystemEventTrigger(
+ 'during', 'shutdown', self.shutdown, reactor)
+
+ def shutdown(self, reactor=None):
+ if not self._pool:
+ return
+ self._pool.close()
+ # wait for workers to finish
+ self._pool.terminate()
+ self._pool = None
+
+ def apply(self, f, *args, **kw):
+ """ Add a task to the pool and return a deferred. """
+ result = self._pool.apply_async(f, args, kw)
+ return threads.deferToThread(result.get)
diff --git a/nova/rpc.py b/nova/rpc.py
new file mode 100644
index 0000000000..62c6afff33
--- /dev/null
+++ b/nova/rpc.py
@@ -0,0 +1,222 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+AMQP-based RPC. Queues have consumers and publishers.
+No fan-out support yet.
+"""
+
+import logging
+import sys
+import uuid
+
+from nova import vendor
+import anyjson
+from carrot import connection
+from carrot import messaging
+from twisted.internet import defer
+from twisted.internet import reactor
+from twisted.internet import task
+
+from nova import fakerabbit
+from nova import flags
+
+
+FLAGS = flags.FLAGS
+
+
+_log = logging.getLogger('amqplib')
+_log.setLevel(logging.WARN)
+
+
+class Connection(connection.BrokerConnection):
+ @classmethod
+ def instance(cls):
+ if not hasattr(cls, '_instance'):
+ params = dict(hostname=FLAGS.rabbit_host,
+ port=FLAGS.rabbit_port,
+ userid=FLAGS.rabbit_userid,
+ password=FLAGS.rabbit_password,
+ virtual_host=FLAGS.rabbit_virtual_host)
+
+ if FLAGS.fake_rabbit:
+ params['backend_cls'] = fakerabbit.Backend
+
+ cls._instance = cls(**params)
+ return cls._instance
+
+
+class Consumer(messaging.Consumer):
+ # TODO(termie): it would be nice to give these some way of automatically
+ # cleaning up after themselves
+ def attach_to_tornado(self, io_inst=None):
+ from tornado import ioloop
+ if io_inst is None:
+ io_inst = ioloop.IOLoop.instance()
+
+ injected = ioloop.PeriodicCallback(
+ lambda: self.fetch(enable_callbacks=True), 1, io_loop=io_inst)
+ injected.start()
+ return injected
+
+ attachToTornado = attach_to_tornado
+
+ def attach_to_twisted(self):
+ loop = task.LoopingCall(self.fetch, enable_callbacks=True)
+ loop.start(interval=0.001)
+
+class Publisher(messaging.Publisher):
+ pass
+
+
+class TopicConsumer(Consumer):
+ exchange_type = "topic"
+ def __init__(self, connection=None, topic="broadcast"):
+ self.queue = topic
+ self.routing_key = topic
+ self.exchange = FLAGS.control_exchange
+ super(TopicConsumer, self).__init__(connection=connection)
+
+
+class AdapterConsumer(TopicConsumer):
+ def __init__(self, connection=None, topic="broadcast", proxy=None):
+ _log.debug('Initing the Adapter Consumer for %s' % (topic))
+ self.proxy = proxy
+ super(AdapterConsumer, self).__init__(connection=connection, topic=topic)
+
+ def receive(self, message_data, message):
+ _log.debug('received %s' % (message_data))
+ msg_id = message_data.pop('_msg_id', None)
+
+ method = message_data.get('method')
+ args = message_data.get('args', {})
+ if not method:
+ return
+
+ node_func = getattr(self.proxy, str(method))
+ node_args = dict((str(k), v) for k, v in args.iteritems())
+ d = defer.maybeDeferred(node_func, **node_args)
+ if msg_id:
+ d.addCallback(lambda rval: msg_reply(msg_id, rval))
+ d.addErrback(lambda e: msg_reply(msg_id, str(e)))
+ message.ack()
+ return
+
+
+class TopicPublisher(Publisher):
+ exchange_type = "topic"
+ def __init__(self, connection=None, topic="broadcast"):
+ self.routing_key = topic
+ self.exchange = FLAGS.control_exchange
+ super(TopicPublisher, self).__init__(connection=connection)
+
+
+class DirectConsumer(Consumer):
+ exchange_type = "direct"
+ def __init__(self, connection=None, msg_id=None):
+ self.queue = msg_id
+ self.routing_key = msg_id
+ self.exchange = msg_id
+ self.auto_delete = True
+ super(DirectConsumer, self).__init__(connection=connection)
+
+
+class DirectPublisher(Publisher):
+ exchange_type = "direct"
+ def __init__(self, connection=None, msg_id=None):
+ self.routing_key = msg_id
+ self.exchange = msg_id
+ self.auto_delete = True
+ super(DirectPublisher, self).__init__(connection=connection)
+
+
+def msg_reply(msg_id, reply):
+ conn = Connection.instance()
+ publisher = DirectPublisher(connection=conn, msg_id=msg_id)
+
+ try:
+ publisher.send({'result': reply})
+ except TypeError:
+ publisher.send(
+ {'result': dict((k, repr(v))
+ for k, v in reply.__dict__.iteritems())
+ })
+ publisher.close()
+
+
+def call(topic, msg):
+ _log.debug("Making asynchronous call...")
+ msg_id = uuid.uuid4().hex
+ msg.update({'_msg_id': msg_id})
+ _log.debug("MSG_ID is %s" % (msg_id))
+
+ conn = Connection.instance()
+ d = defer.Deferred()
+ consumer = DirectConsumer(connection=conn, msg_id=msg_id)
+ consumer.register_callback(lambda data, message: d.callback(data))
+ injected = consumer.attach_to_tornado()
+
+ # clean up after the injected listened and return x
+ d.addCallback(lambda x: injected.stop() and x or x)
+
+ publisher = TopicPublisher(connection=conn, topic=topic)
+ publisher.send(msg)
+ publisher.close()
+ return d
+
+
+def cast(topic, msg):
+ _log.debug("Making asynchronous cast...")
+ conn = Connection.instance()
+ publisher = TopicPublisher(connection=conn, topic=topic)
+ publisher.send(msg)
+ publisher.close()
+
+
+def generic_response(message_data, message):
+ _log.debug('response %s', message_data)
+ message.ack()
+ sys.exit(0)
+
+
+def send_message(topic, message, wait=True):
+ msg_id = uuid.uuid4().hex
+ message.update({'_msg_id': msg_id})
+ _log.debug('topic is %s', topic)
+ _log.debug('message %s', message)
+
+ if wait:
+ consumer = messaging.Consumer(connection=rpc.Connection.instance(),
+ queue=msg_id,
+ exchange=msg_id,
+ auto_delete=True,
+ exchange_type="direct",
+ routing_key=msg_id)
+ consumer.register_callback(generic_response)
+
+ publisher = messaging.Publisher(connection=rpc.Connection.instance(),
+ exchange="nova",
+ exchange_type="topic",
+ routing_key=topic)
+ publisher.send(message)
+ publisher.close()
+
+ if wait:
+ consumer.wait()
+
+
+# TODO: Replace with a docstring test
+if __name__ == "__main__":
+ send_message(sys.argv[1], anyjson.deserialize(sys.argv[2]))
diff --git a/nova/server.py b/nova/server.py
new file mode 100644
index 0000000000..227f7fddc3
--- /dev/null
+++ b/nova/server.py
@@ -0,0 +1,139 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Base functionality for nova daemons - gradually being replaced with twistd.py.
+"""
+
+import logging
+import logging.handlers
+import os
+import signal
+import sys
+import time
+
+from nova import vendor
+import daemon
+from daemon import pidlockfile
+
+from nova import flags
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_bool('daemonize', False, 'daemonize this process')
+# NOTE(termie): right now I am defaulting to using syslog when we daemonize
+# it may be better to do something else -shrug-
+# NOTE(Devin): I think we should let each process have its own log file
+# and put it in /var/logs/nova/(appname).log
+# This makes debugging much easier and cuts down on sys log
+# clutter.
+flags.DEFINE_bool('use_syslog', True, 'output to syslog when daemonizing')
+flags.DEFINE_string('logfile', None, 'log file to output to')
+flags.DEFINE_string('pidfile', None, 'pid file to output to')
+flags.DEFINE_string('working_directory', './', 'working directory...')
+
+
+def stop(pidfile):
+ """
+ Stop the daemon
+ """
+ # Get the pid from the pidfile
+ try:
+ pf = file(pidfile,'r')
+ pid = int(pf.read().strip())
+ pf.close()
+ except IOError:
+ pid = None
+
+ if not pid:
+ message = "pidfile %s does not exist. Daemon not running?\n"
+ sys.stderr.write(message % pidfile)
+ return # not an error in a restart
+
+ # Try killing the daemon process
+ try:
+ while 1:
+ os.kill(pid, signal.SIGTERM)
+ time.sleep(0.1)
+ except OSError, err:
+ err = str(err)
+ if err.find("No such process") > 0:
+ if os.path.exists(pidfile):
+ os.remove(pidfile)
+ else:
+ print str(err)
+ sys.exit(1)
+
+
+def serve(name, main):
+ argv = FLAGS(sys.argv)
+
+ if not FLAGS.pidfile:
+ FLAGS.pidfile = '%s.pid' % name
+
+ logging.debug("Full set of FLAGS: \n\n\n" )
+ for flag in FLAGS:
+ logging.debug("%s : %s" % (flag, FLAGS.get(flag, None) ))
+
+ action = 'start'
+ if len(argv) > 1:
+ action = argv.pop()
+
+ if action == 'stop':
+ stop(FLAGS.pidfile)
+ sys.exit()
+ elif action == 'restart':
+ stop(FLAGS.pidfile)
+ elif action == 'start':
+ pass
+ else:
+ print 'usage: %s [options] [start|stop|restart]' % argv[0]
+ sys.exit(1)
+
+ logging.getLogger('amqplib').setLevel(logging.WARN)
+ if FLAGS.daemonize:
+ logger = logging.getLogger()
+ formatter = logging.Formatter(
+ name + '(%(name)s): %(levelname)s %(message)s')
+ if FLAGS.use_syslog and not FLAGS.logfile:
+ syslog = logging.handlers.SysLogHandler(address='/dev/log')
+ syslog.setFormatter(formatter)
+ logger.addHandler(syslog)
+ else:
+ if not FLAGS.logfile:
+ FLAGS.logfile = '%s.log' % name
+ logfile = logging.handlers.FileHandler(FLAGS.logfile)
+ logfile.setFormatter(formatter)
+ logger.addHandler(logfile)
+ stdin, stdout, stderr = None, None, None
+ else:
+ stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr
+
+ if FLAGS.verbose:
+ logging.getLogger().setLevel(logging.DEBUG)
+ else:
+ logging.getLogger().setLevel(logging.WARNING)
+
+ with daemon.DaemonContext(
+ detach_process=FLAGS.daemonize,
+ working_directory=FLAGS.working_directory,
+ pidfile=pidlockfile.TimeoutPIDLockFile(FLAGS.pidfile,
+ acquire_timeout=1,
+ threaded=False),
+ stdin=stdin,
+ stdout=stdout,
+ stderr=stderr
+ ):
+ main(argv)
diff --git a/nova/test.py b/nova/test.py
new file mode 100644
index 0000000000..610ad89aa9
--- /dev/null
+++ b/nova/test.py
@@ -0,0 +1,246 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Base classes for our unit tests.
+Allows overriding of flags for use of fakes,
+and some black magic for inline callbacks.
+"""
+
+import logging
+import time
+import unittest
+
+from nova import vendor
+import mox
+from tornado import ioloop
+from twisted.internet import defer
+from twisted.python import failure
+from twisted.trial import unittest as trial_unittest
+import stubout
+
+from nova import datastore
+from nova import fakerabbit
+from nova import flags
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_bool('fake_tests', True,
+ 'should we use everything for testing')
+
+
+def skip_if_fake(f):
+ def _skipper(*args, **kw):
+ if FLAGS.fake_tests:
+ raise trial_unittest.SkipTest('Test cannot be run in fake mode')
+ else:
+ return f(*args, **kw)
+
+ _skipper.func_name = f.func_name
+ return _skipper
+
+
+class TrialTestCase(trial_unittest.TestCase):
+ def setUp(self):
+ super(TrialTestCase, self).setUp()
+
+ # emulate some of the mox stuff, we can't use the metaclass
+ # because it screws with our generators
+ self.mox = mox.Mox()
+ self.stubs = stubout.StubOutForTesting()
+ self.flag_overrides = {}
+
+ def tearDown(self):
+ super(TrialTestCase, self).tearDown()
+ self.reset_flags()
+ self.mox.UnsetStubs()
+ self.stubs.UnsetAll()
+ self.stubs.SmartUnsetAll()
+ self.mox.VerifyAll()
+
+ if FLAGS.fake_rabbit:
+ fakerabbit.reset_all()
+
+ # attempt to wipe all keepers
+ #keeper = datastore.Keeper()
+ #keeper.clear_all()
+
+ def flags(self, **kw):
+ for k, v in kw.iteritems():
+ if k in self.flag_overrides:
+ self.reset_flags()
+ raise Exception(
+ 'trying to override already overriden flag: %s' % k)
+ self.flag_overrides[k] = getattr(FLAGS, k)
+ setattr(FLAGS, k, v)
+
+ def reset_flags(self):
+ for k, v in self.flag_overrides.iteritems():
+ setattr(FLAGS, k, v)
+
+
+
+class BaseTestCase(TrialTestCase):
+ def setUp(self):
+ super(BaseTestCase, self).setUp()
+ # TODO(termie): we could possibly keep a more global registry of
+ # the injected listeners... this is fine for now though
+ self.injected = []
+ self.ioloop = ioloop.IOLoop.instance()
+
+ self._waiting = None
+ self._doneWaiting = False
+ self._timedOut = False
+ self.set_up()
+
+ def set_up(self):
+ pass
+
+ def tear_down(self):
+ pass
+
+ def tearDown(self):
+ super(BaseTestCase, self).tearDown()
+ for x in self.injected:
+ x.stop()
+ if FLAGS.fake_rabbit:
+ fakerabbit.reset_all()
+ self.tear_down()
+
+ def _waitForTest(self, timeout=60):
+ """ Push the ioloop along to wait for our test to complete. """
+ self._waiting = self.ioloop.add_timeout(time.time() + timeout,
+ self._timeout)
+ def _wait():
+ if self._timedOut:
+ self.fail('test timed out')
+ self._done()
+ if self._doneWaiting:
+ self.ioloop.stop()
+ return
+ # we can use add_callback here but this uses less cpu when testing
+ self.ioloop.add_timeout(time.time() + 0.01, _wait)
+
+ self.ioloop.add_callback(_wait)
+ self.ioloop.start()
+
+ def _done(self):
+ if self._waiting:
+ try:
+ self.ioloop.remove_timeout(self._waiting)
+ except Exception:
+ pass
+ self._waiting = None
+ self._doneWaiting = True
+
+ def _maybeInlineCallbacks(self, f):
+ """ If we're doing async calls in our tests, wait on them.
+
+ This is probably the most complicated hunk of code we have so far.
+
+ First up, if the function is normal (not async) we just act normal
+ and return.
+
+ Async tests will use the "Inline Callbacks" pattern, which means
+ you yield Deferreds at every "waiting" step of your code instead
+ of making epic callback chains.
+
+ Example (callback chain, ugly):
+
+ d = self.node.terminate_instance(instance_id) # a Deferred instance
+ def _describe(_):
+ d_desc = self.node.describe_instances() # another Deferred instance
+ return d_desc
+ def _checkDescribe(rv):
+ self.assertEqual(rv, [])
+ d.addCallback(_describe)
+ d.addCallback(_checkDescribe)
+ d.addCallback(lambda x: self._done())
+ self._waitForTest()
+
+ Example (inline callbacks! yay!):
+
+ yield self.node.terminate_instance(instance_id)
+ rv = yield self.node.describe_instances()
+ self.assertEqual(rv, [])
+
+ If the test fits the Inline Callbacks pattern we will automatically
+ handle calling wait and done.
+ """
+ # TODO(termie): this can be a wrapper function instead and
+ # and we can make a metaclass so that we don't
+ # have to copy all that "run" code below.
+ g = f()
+ if not hasattr(g, 'send'):
+ self._done()
+ return defer.succeed(g)
+
+ inlined = defer.inlineCallbacks(f)
+ d = inlined()
+ return d
+
+ def _catchExceptions(self, result, failure):
+ exc = (failure.type, failure.value, failure.getTracebackObject())
+ if isinstance(failure.value, self.failureException):
+ result.addFailure(self, exc)
+ elif isinstance(failure.value, KeyboardInterrupt):
+ raise
+ else:
+ result.addError(self, exc)
+
+ self._done()
+
+ def _timeout(self):
+ self._waiting = False
+ self._timedOut = True
+
+ def run(self, result=None):
+ if result is None: result = self.defaultTestResult()
+
+ result.startTest(self)
+ testMethod = getattr(self, self._testMethodName)
+ try:
+ try:
+ self.setUp()
+ except KeyboardInterrupt:
+ raise
+ except:
+ result.addError(self, self._exc_info())
+ return
+
+ ok = False
+ try:
+ d = self._maybeInlineCallbacks(testMethod)
+ d.addErrback(lambda x: self._catchExceptions(result, x))
+ d.addBoth(lambda x: self._done() and x)
+ self._waitForTest()
+ ok = True
+ except self.failureException:
+ result.addFailure(self, self._exc_info())
+ except KeyboardInterrupt:
+ raise
+ except:
+ result.addError(self, self._exc_info())
+
+ try:
+ self.tearDown()
+ except KeyboardInterrupt:
+ raise
+ except:
+ result.addError(self, self._exc_info())
+ ok = False
+ if ok: result.addSuccess(self)
+ finally:
+ result.stopTest(self)
diff --git a/nova/tests/CA/cacert.pem b/nova/tests/CA/cacert.pem
new file mode 100644
index 0000000000..9ffb5bb807
--- /dev/null
+++ b/nova/tests/CA/cacert.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICyzCCAjSgAwIBAgIJANiqHZUcbScCMA0GCSqGSIb3DQEBBAUAME4xEjAQBgNV
+BAoTCU5PVkEgUk9PVDEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEGA1UECBMK
+Q2FsaWZvcm5pYTELMAkGA1UEBhMCVVMwHhcNMTAwNTI4MDExOTI1WhcNMTEwNTI4
+MDExOTI1WjBOMRIwEAYDVQQKEwlOT1ZBIFJPT1QxFjAUBgNVBAcTDU1vdW50YWlu
+IFZpZXcxEzARBgNVBAgTCkNhbGlmb3JuaWExCzAJBgNVBAYTAlVTMIGfMA0GCSqG
+SIb3DQEBAQUAA4GNADCBiQKBgQDobUnq8rpXA/HQZ2Uu9Me3SlqCayz3ws2wtvFQ
+koWPUzpriIYPkpprz2EaVu07Zb9uJHvjcoY07nYntl4jR8S7PH4XZhlVFn8AQWzs
+iThU4KJF71UfVM00dDrarSgVpyOIcFXO3iUvLoJj7+RUPjrWdLuJoMqnhicgLeHZ
+LAZ8ewIDAQABo4GwMIGtMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFMh1RMlTVtt8
+EdESYpsTU08r0FnpMH4GA1UdIwR3MHWAFMh1RMlTVtt8EdESYpsTU08r0FnpoVKk
+UDBOMRIwEAYDVQQKEwlOT1ZBIFJPT1QxFjAUBgNVBAcTDU1vdW50YWluIFZpZXcx
+EzARBgNVBAgTCkNhbGlmb3JuaWExCzAJBgNVBAYTAlVTggkA2KodlRxtJwIwDQYJ
+KoZIhvcNAQEEBQADgYEAq+YCgflK36HCdodNu2ya3O6UDRUE2dW8n96tAOmvHqmR
+v38k8GIW0pjWDo+lZYnFmeJYd+QGcJl9fLzXxffV5k+rNCfr/gEYtznWLNUX7AZB
+b/VC7L+yK9qz08C8n51TslXaf3fUGkfkQxsvEP7+hi0qavdd/8eTbdheWahYwWg=
+-----END CERTIFICATE-----
diff --git a/nova/tests/CA/private/cakey.pem b/nova/tests/CA/private/cakey.pem
new file mode 100644
index 0000000000..eee54cc387
--- /dev/null
+++ b/nova/tests/CA/private/cakey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDobUnq8rpXA/HQZ2Uu9Me3SlqCayz3ws2wtvFQkoWPUzpriIYP
+kpprz2EaVu07Zb9uJHvjcoY07nYntl4jR8S7PH4XZhlVFn8AQWzsiThU4KJF71Uf
+VM00dDrarSgVpyOIcFXO3iUvLoJj7+RUPjrWdLuJoMqnhicgLeHZLAZ8ewIDAQAB
+AoGBANQonmZ2Nh2jniFrn/LiwULP/ho6Fov6J6N8+n1focaYZCUwM58XZRmv7KUM
+X/PuBnVVnDibm2HJodTSJM/zfODnGO15kdmJ9X23FkkdTyuvphO5tYF0ONARXdfX
+9LbPcLYA14VSCZCKCye6mbv/xi0C/s7q6ZBoMl7XaeD9hgUxAkEA9lxQY/ZxcLV0
+Ae5I2spBbtuXEGns11YnKnppc59RrAono1gaDeYY2WZRwztIcD6VtUv7qkzH6ubo
+shAG4fvnPQJBAPGFaDODs2ckPvxnILEbjpnZXGQqDCpQ3sVJ6nfu+qdAWS92ESNo
+Y6DC8zFjFaQFbKy6Jxr1VsvYDXhF8cmy7hcCQHkLElSLGWGPRdhNA268QTn+mlJu
+OPf0VHoCex1cAfzNYHxZJTP/AeaO501NK2I63cOd+aDK6M75dQtH5JnT8uECQQCg
+jVydkhk6oV+1jiCvW3BKWbIPa9w2bRgJ8n8JRzYc5Kvk3wm5jfVcsvvTgtip9mkt
+0XmZdCpEy9T4dRasTGP1AkBMhShiVP7+P+SIQlZtSn8ckTt9G6cefEjxsv0kVFZe
+SjkUO0ZifahF8r3Q1eEUSzdXEvicEwONvcpc7MLwfSD7
+-----END RSA PRIVATE KEY-----
diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py
new file mode 100644
index 0000000000..a4ccbbaebc
--- /dev/null
+++ b/nova/tests/__init__.py
@@ -0,0 +1,27 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:mod:`nova.tests` -- Nova Unittests
+=====================================================
+
+.. automodule:: nova.tests
+ :platform: Unix
+.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
+.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
+.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
+.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
+.. moduleauthor:: Manish Singh <yosh@gimp.org>
+.. moduleauthor:: Andy Smith <andy@anarkystic.com>
+""" \ No newline at end of file
diff --git a/nova/tests/access_unittest.py b/nova/tests/access_unittest.py
new file mode 100644
index 0000000000..ab0759c2d9
--- /dev/null
+++ b/nova/tests/access_unittest.py
@@ -0,0 +1,60 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+import unittest
+
+from nova import flags
+from nova import test
+from nova.auth import users
+from nova.endpoint import cloud
+
+FLAGS = flags.FLAGS
+
+class AccessTestCase(test.BaseTestCase):
+ def setUp(self):
+ FLAGS.fake_libvirt = True
+ FLAGS.fake_storage = True
+ self.users = users.UserManager.instance()
+ super(AccessTestCase, self).setUp()
+ # Make a test project
+ # Make a test user
+ self.users.create_user('test1', 'access', 'secret')
+
+ # Make the test user a member of the project
+
+ def tearDown(self):
+ # Delete the test user
+ # Delete the test project
+ self.users.delete_user('test1')
+ pass
+
+ def test_001_basic_user_access(self):
+ user = self.users.get_user('test1')
+ # instance-foo, should be using object and not owner_id
+ instance_id = "i-12345678"
+ self.assertTrue(user.is_authorized(instance_id, action="describe_instances"))
+
+ def test_002_sysadmin_access(self):
+ user = self.users.get_user('test1')
+ bucket = "foo/bar/image"
+ self.assertFalse(user.is_authorized(bucket, action="register"))
+ self.users.add_role(user, "sysadmin")
+
+
+if __name__ == "__main__":
+ # TODO: Implement use_fake as an option
+ unittest.main()
diff --git a/nova/tests/api_integration.py b/nova/tests/api_integration.py
new file mode 100644
index 0000000000..d2e1026b8e
--- /dev/null
+++ b/nova/tests/api_integration.py
@@ -0,0 +1,50 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+import boto
+from boto.ec2.regioninfo import RegionInfo
+
+ACCESS_KEY = 'fake'
+SECRET_KEY = 'fake'
+CLC_IP = '127.0.0.1'
+CLC_PORT = 8773
+REGION = 'test'
+
+def get_connection():
+ return boto.connect_ec2 (
+ aws_access_key_id=ACCESS_KEY,
+ aws_secret_access_key=SECRET_KEY,
+ is_secure=False,
+ region=RegionInfo(None, REGION, CLC_IP),
+ port=CLC_PORT,
+ path='/services/Cloud',
+ debug=99
+ )
+
+class APIIntegrationTests(unittest.TestCase):
+ def test_001_get_all_images(self):
+ conn = get_connection()
+ res = conn.get_all_images()
+ print res
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+#print conn.get_all_key_pairs()
+#print conn.create_key_pair
+#print conn.create_security_group('name', 'description')
+
diff --git a/nova/tests/api_unittest.py b/nova/tests/api_unittest.py
new file mode 100644
index 0000000000..fdbf088f96
--- /dev/null
+++ b/nova/tests/api_unittest.py
@@ -0,0 +1,189 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import httplib
+import random
+import StringIO
+
+from nova import vendor
+import boto
+from boto.ec2 import regioninfo
+from tornado import httpserver
+from twisted.internet import defer
+
+from nova import flags
+from nova import test
+from nova.auth import users
+from nova.endpoint import api
+from nova.endpoint import cloud
+
+
+FLAGS = flags.FLAGS
+
+
+# NOTE(termie): These are a bunch of helper methods and classes to short
+# circuit boto calls and feed them into our tornado handlers,
+# it's pretty damn circuitous so apologies if you have to fix
+# a bug in it
+def boto_to_tornado(method, path, headers, data, host, connection=None):
+ """ translate boto requests into tornado requests
+
+ connection should be a FakeTornadoHttpConnection instance
+ """
+ headers = httpserver.HTTPHeaders()
+ for k, v in headers.iteritems():
+ headers[k] = v
+
+ req = httpserver.HTTPRequest(method=method,
+ uri=path,
+ headers=headers,
+ body=data,
+ host=host,
+ remote_ip='127.0.0.1',
+ connection=connection)
+ return req
+
+
+def raw_to_httpresponse(s):
+ """ translate a raw tornado http response into an httplib.HTTPResponse """
+ sock = FakeHttplibSocket(s)
+ resp = httplib.HTTPResponse(sock)
+ resp.begin()
+ return resp
+
+
+class FakeHttplibSocket(object):
+ """ a fake socket implementation for httplib.HTTPResponse, trivial """
+ def __init__(self, s):
+ self.fp = StringIO.StringIO(s)
+
+ def makefile(self, mode, other):
+ return self.fp
+
+
+class FakeTornadoStream(object):
+ """ a fake stream to satisfy tornado's assumptions, trivial """
+ def set_close_callback(self, f):
+ pass
+
+
+class FakeTornadoConnection(object):
+ """ a fake connection object for tornado to pass to its handlers
+
+ web requests are expected to write to this as they get data and call
+ finish when they are done with the request, we buffer the writes and
+ kick off a callback when it is done so that we can feed the result back
+ into boto.
+ """
+ def __init__(self, d):
+ self.d = d
+ self._buffer = StringIO.StringIO()
+
+ def write(self, chunk):
+ self._buffer.write(chunk)
+
+ def finish(self):
+ s = self._buffer.getvalue()
+ self.d.callback(s)
+
+ xheaders = None
+
+ @property
+ def stream(self):
+ return FakeTornadoStream()
+
+
+class FakeHttplibConnection(object):
+ """ a fake httplib.HTTPConnection for boto to use
+
+ requests made via this connection actually get translated and routed into
+ our tornado app, we then wait for the response and turn it back into
+ the httplib.HTTPResponse that boto expects.
+ """
+ def __init__(self, app, host, is_secure=False):
+ self.app = app
+ self.host = host
+ self.deferred = defer.Deferred()
+
+ def request(self, method, path, data, headers):
+ req = boto_to_tornado
+ conn = FakeTornadoConnection(self.deferred)
+ request = boto_to_tornado(connection=conn,
+ method=method,
+ path=path,
+ headers=headers,
+ data=data,
+ host=self.host)
+ handler = self.app(request)
+ self.deferred.addCallback(raw_to_httpresponse)
+
+ def getresponse(self):
+ @defer.inlineCallbacks
+ def _waiter():
+ result = yield self.deferred
+ defer.returnValue(result)
+ d = _waiter()
+ # NOTE(termie): defer.returnValue above should ensure that
+ # this deferred has already been called by the time
+ # we get here, we are going to cheat and return
+ # the result of the callback
+ return d.result
+
+ def close(self):
+ pass
+
+
+class ApiEc2TestCase(test.BaseTestCase):
+ def setUp(self):
+ super(ApiEc2TestCase, self).setUp()
+
+ self.users = users.UserManager.instance()
+ self.cloud = cloud.CloudController()
+
+ self.host = '127.0.0.1'
+
+ self.app = api.APIServerApplication(self.users, {'Cloud': self.cloud})
+ self.ec2 = boto.connect_ec2(
+ aws_access_key_id='fake',
+ aws_secret_access_key='fake',
+ is_secure=False,
+ region=regioninfo.RegionInfo(None, 'test', self.host),
+ port=FLAGS.cc_port,
+ path='/services/Cloud')
+
+ self.mox.StubOutWithMock(self.ec2, 'new_http_connection')
+
+ def expect_http(self, host=None, is_secure=False):
+ http = FakeHttplibConnection(
+ self.app, '%s:%d' % (self.host, FLAGS.cc_port), False)
+ self.ec2.new_http_connection(host, is_secure).AndReturn(http)
+ return http
+
+ def test_describe_instances(self):
+ self.expect_http()
+ self.mox.ReplayAll()
+
+ self.assertEqual(self.ec2.get_all_instances(), [])
+
+
+ def test_get_all_key_pairs(self):
+ self.expect_http()
+ self.mox.ReplayAll()
+ keyname = "".join(random.choice("sdiuisudfsdcnpaqwertasd") for x in range(random.randint(4, 8)))
+ self.users.generate_key_pair('fake', keyname)
+
+ rv = self.ec2.get_all_key_pairs()
+ self.assertTrue(filter(lambda k: k.name == keyname, rv))
+
diff --git a/nova/tests/bundle/1mb.manifest.xml b/nova/tests/bundle/1mb.manifest.xml
new file mode 100644
index 0000000000..dc33159571
--- /dev/null
+++ b/nova/tests/bundle/1mb.manifest.xml
@@ -0,0 +1 @@
+<?xml version="1.0" ?><manifest><version>2007-10-10</version><bundler><name>euca-tools</name><version>1.2</version><release>31337</release></bundler><machine_configuration><architecture>x86_64</architecture></machine_configuration><image><name>1mb</name><user>42</user><type>machine</type><digest algorithm="SHA1">da39a3ee5e6b4b0d3255bfef95601890afd80709</digest><size>1048576</size><bundled_size>1136</bundled_size><ec2_encrypted_key algorithm="AES-128-CBC">33a2ea00dc64083dd9a10eb5e233635b42a7beb1670ab75452087d9de74c60aba1cd27c136fda56f62beb581de128fb1f10d072b9e556fd25e903107a57827c21f6ee8a93a4ff55b11311fcef217e3eefb07e81f71e88216f43b4b54029c1f2549f2925a839a73947d2d5aeecec4a62ece4af9156d557ae907978298296d9915</ec2_encrypted_key><user_encrypted_key algorithm="AES-128-CBC">4c11147fd8caf92447e90ce339928933d7579244c2f8ffb07cc0ea35f8738da8b90eff6c7a49671a84500e993e9462e4c36d5c19c0b3a2b397d035b4c0cce742b58e12552175d81d129b0425e9f71ebacb9aeb539fa9dd2ac36749fb82876f6902e5fb24b6ec19f35ec4c20acd50437fd30966e99c4d9a0647577970a8fa3023</user_encrypted_key><ec2_encrypted_iv>14bd082c9715f071160c69bbfb070f51d2ba1076775f1d988ccde150e515088156b248e4b5a64e46c4fe064feeeedfe14511f7fde478a51acb89f9b2f6c84b60593e5c3f792ba6b01fed9bf2158fdac03086374883b39d13a3ca74497eeaaf579fc3f26effc73bfd9446a2a8c4061f0874bfaca058905180e22d3d8881551cb3</ec2_encrypted_iv><user_encrypted_iv>8f7606f19f00e4e19535dd234b66b31b77e9c7bad3885d9c9efa75c863631fd4f82a009e17d789066d9cc6032a436f05384832f6d9a3283d3e63eab04fa0da5c8c87db9b17e854e842c3fb416507d067a266b44538125ce732e486098e8ebd1ca91fa3079f007fce7d14957a9b7e57282407ead3c6eb68fe975df3d83190021b</user_encrypted_iv><parts count="2"><part index="0"><filename>1mb.part.0</filename><digest algorithm="SHA1">c4413423cf7a57e71187e19bfd5cd4b514a64283</digest></part><part index="1"><filename>1mb.part.1</filename><digest algorithm="SHA1">9d4262e6589393d09a11a0332af169887bc2e57d</digest></part></parts></image><signature>4e00b5ba28114dda4a9df7eeae94be847ec46117a09a1cbe41e578660642f0660dda1776b39fb3bf826b6cfec019e2a5e9c566728d186b7400ebc989a30670eb1db26ce01e68bd9d3f31290370077a85b81c66b63c1e0d5499bac115c06c17a21a81b6d3a67ebbce6c17019095af7ab07f3796c708cc843e58efc12ddc788c5e</signature></manifest> \ No newline at end of file
diff --git a/nova/tests/bundle/1mb.part.0 b/nova/tests/bundle/1mb.part.0
new file mode 100644
index 0000000000..15a1657c57
--- /dev/null
+++ b/nova/tests/bundle/1mb.part.0
Binary files differ
diff --git a/nova/tests/bundle/1mb.part.1 b/nova/tests/bundle/1mb.part.1
new file mode 100644
index 0000000000..2f0406e2d1
--- /dev/null
+++ b/nova/tests/bundle/1mb.part.1
@@ -0,0 +1 @@
+­´ˆà«€ç‰°Ƴ ¡ÀiDHW̽×JÈ8ïrV¼³h§X’·@Yj“~Ø ·Gû5û 3Nt«˜•H6Ñ$§Ëgö™é Lá¢+³æ¤X†pm¬@,øŽ>7ÚÊ×užp¼ aü`¥V2X@£#ᶠ\ No newline at end of file
diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py
new file mode 100644
index 0000000000..568a8dcd38
--- /dev/null
+++ b/nova/tests/cloud_unittest.py
@@ -0,0 +1,161 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import StringIO
+import time
+import unittest
+from xml.etree import ElementTree
+
+from nova import vendor
+import mox
+from tornado import ioloop
+from twisted.internet import defer
+
+from nova import flags
+from nova import rpc
+from nova import test
+from nova.auth import users
+from nova.compute import node
+from nova.endpoint import api
+from nova.endpoint import cloud
+
+
+FLAGS = flags.FLAGS
+
+
+class CloudTestCase(test.BaseTestCase):
+ def setUp(self):
+ super(CloudTestCase, self).setUp()
+ self.flags(fake_libvirt=True,
+ fake_storage=True,
+ fake_users=True,
+ redis_db=8)
+
+ self.conn = rpc.Connection.instance()
+ logging.getLogger().setLevel(logging.DEBUG)
+
+ # set up our cloud
+ self.cloud = cloud.CloudController()
+ self.cloud_consumer = rpc.AdapterConsumer(connection=self.conn,
+ topic=FLAGS.cloud_topic,
+ proxy=self.cloud)
+ self.injected.append(self.cloud_consumer.attach_to_tornado(self.ioloop))
+
+ # set up a node
+ self.node = node.Node()
+ self.node_consumer = rpc.AdapterConsumer(connection=self.conn,
+ topic=FLAGS.compute_topic,
+ proxy=self.node)
+ self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop))
+
+ user_mocker = mox.Mox()
+ self.admin = user_mocker.CreateMock(users.User)
+ self.admin.is_authorized(mox.IgnoreArg()).AndReturn(True)
+ self.context = api.APIRequestContext(handler=None,user=self.admin)
+
+ def test_console_output(self):
+ if FLAGS.fake_libvirt:
+ logging.debug("Can't test instances without a real virtual env.")
+ return
+ instance_id = 'foo'
+ inst = yield self.node.run_instance(instance_id)
+ output = yield self.cloud.get_console_output(self.context, [instance_id])
+ logging.debug(output)
+ self.assert_(output)
+ rv = yield self.node.terminate_instance(instance_id)
+
+ def test_run_instances(self):
+ if FLAGS.fake_libvirt:
+ logging.debug("Can't test instances without a real virtual env.")
+ return
+ image_id = FLAGS.default_image
+ instance_type = FLAGS.default_instance_type
+ max_count = 1
+ kwargs = {'image_id': image_id,
+ 'instance_type': instance_type,
+ 'max_count': max_count}
+ rv = yield self.cloud.run_instances(self.context, **kwargs)
+ # TODO: check for proper response
+ instance = rv['reservationSet'][0][rv['reservationSet'][0].keys()[0]][0]
+ logging.debug("Need to watch instance %s until it's running..." % instance['instance_id'])
+ while True:
+ rv = yield defer.succeed(time.sleep(1))
+ info = self.cloud._get_instance(instance['instance_id'])
+ logging.debug(info['state'])
+ if info['state'] == node.Instance.RUNNING:
+ break
+ self.assert_(rv)
+
+ if not FLAGS.fake_libvirt:
+ time.sleep(45) # Should use boto for polling here
+ for reservations in rv['reservationSet']:
+ # for res_id in reservations.keys():
+ # logging.debug(reservations[res_id])
+ # for instance in reservations[res_id]:
+ for instance in reservations[reservations.keys()[0]]:
+ logging.debug("Terminating instance %s" % instance['instance_id'])
+ rv = yield self.node.terminate_instance(instance['instance_id'])
+
+ def test_instance_update_state(self):
+ def instance(num):
+ return {
+ 'reservation_id': 'r-1',
+ 'instance_id': 'i-%s' % num,
+ 'image_id': 'ami-%s' % num,
+ 'private_dns_name': '10.0.0.%s' % num,
+ 'dns_name': '10.0.0%s' % num,
+ 'ami_launch_index': str(num),
+ 'instance_type': 'fake',
+ 'availability_zone': 'fake',
+ 'key_name': None,
+ 'kernel_id': 'fake',
+ 'ramdisk_id': 'fake',
+ 'groups': ['default'],
+ 'product_codes': None,
+ 'state': 0x01,
+ 'user_data': ''
+ }
+
+ rv = self.cloud.format_instances(self.admin)
+ print rv
+ self.assert_(len(rv['reservationSet']) == 0)
+
+ # simulate launch of 5 instances
+ # self.cloud.instances['pending'] = {}
+ #for i in xrange(5):
+ # inst = instance(i)
+ # self.cloud.instances['pending'][inst['instance_id']] = inst
+
+ #rv = self.cloud.format_instances(self.admin)
+ #self.assert_(len(rv['reservationSet']) == 1)
+ #self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)
+
+ # report 4 nodes each having 1 of the instances
+ #for i in xrange(4):
+ # self.cloud.update_state('instances', {('node-%s' % i): {('i-%s' % i): instance(i)}})
+
+ # one instance should be pending still
+ #self.assert_(len(self.cloud.instances['pending'].keys()) == 1)
+
+ # check that the reservations collapse
+ #rv = self.cloud.format_instances(self.admin)
+ #self.assert_(len(rv['reservationSet']) == 1)
+ #self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)
+
+ # check that we can get metadata for each instance
+ #for i in xrange(4):
+ # data = self.cloud.get_metadata(instance(i)['private_dns_name'])
+ # self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i)
diff --git a/nova/tests/datastore_unittest.py b/nova/tests/datastore_unittest.py
new file mode 100644
index 0000000000..4e4d8586a3
--- /dev/null
+++ b/nova/tests/datastore_unittest.py
@@ -0,0 +1,60 @@
+from nova import test
+from nova import datastore
+import random
+
+class KeeperTestCase(test.BaseTestCase):
+ """
+ Basic persistence tests for Keeper datastore.
+ Generalize, then use these to support
+ migration to redis / cassandra / multiple stores.
+ """
+
+ def __init__(self, *args, **kwargs):
+ """
+ Create a new keeper instance for test keys.
+ """
+ super(KeeperTestCase, self).__init__(*args, **kwargs)
+ self.keeper = datastore.Keeper('test-')
+
+ def tear_down(self):
+ """
+ Scrub out test keeper data.
+ """
+ pass
+
+ def test_store_strings(self):
+ """
+ Confirm that simple strings go in and come out safely.
+ Should also test unicode strings.
+ """
+ randomstring = ''.join(
+ [random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-')
+ for _x in xrange(20)]
+ )
+ self.keeper['test_string'] = randomstring
+ self.assertEqual(randomstring, self.keeper['test_string'])
+
+ def test_store_dicts(self):
+ """
+ Arbitrary dictionaries should be storable.
+ """
+ test_dict = {'key_one': 'value_one'}
+ self.keeper['test_dict'] = test_dict
+ self.assertEqual(test_dict['key_one'],
+ self.keeper['test_dict']['key_one'])
+
+ def test_sets(self):
+ """
+ A keeper dict should be self-serializing.
+ """
+ self.keeper.set_add('test_set', 'foo')
+ test_dict = {'arbitrary': 'dict of stuff'}
+ self.keeper.set_add('test_set', test_dict)
+ self.assertTrue(self.keeper.set_is_member('test_set', 'foo'))
+ self.assertFalse(self.keeper.set_is_member('test_set', 'bar'))
+ self.keeper.set_remove('test_set', 'foo')
+ self.assertFalse(self.keeper.set_is_member('test_set', 'foo'))
+ rv = self.keeper.set_fetch('test_set')
+ self.assertEqual(test_dict, rv.next())
+ self.keeper.set_remove('test_set', test_dict)
+
diff --git a/nova/tests/fake_flags.py b/nova/tests/fake_flags.py
new file mode 100644
index 0000000000..3c7b0be52d
--- /dev/null
+++ b/nova/tests/fake_flags.py
@@ -0,0 +1,26 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from nova import flags
+
+FLAGS = flags.FLAGS
+
+FLAGS.fake_libvirt = True
+FLAGS.fake_storage = True
+FLAGS.fake_rabbit = True
+FLAGS.fake_network = True
+FLAGS.fake_users = True
+FLAGS.keeper_backend = 'sqlite'
+FLAGS.datastore_path = ':memory:'
+FLAGS.verbose = True
diff --git a/nova/tests/future_unittest.py b/nova/tests/future_unittest.py
new file mode 100644
index 0000000000..81d69dffff
--- /dev/null
+++ b/nova/tests/future_unittest.py
@@ -0,0 +1,74 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import StringIO
+import time
+import unittest
+from xml.etree import ElementTree
+
+from nova import vendor
+import mox
+from tornado import ioloop
+from twisted.internet import defer
+
+from nova import cloud
+from nova import exception
+from nova import flags
+from nova import node
+from nova import rpc
+from nova import test
+
+
+FLAGS = flags.FLAGS
+
+
+class AdminTestCase(test.BaseTestCase):
+ def setUp(self):
+ super(AdminTestCase, self).setUp()
+ self.flags(fake_libvirt=True,
+ fake_rabbit=True)
+
+ self.conn = rpc.Connection.instance()
+
+ logging.getLogger().setLevel(logging.INFO)
+
+ # set up our cloud
+ self.cloud = cloud.CloudController()
+ self.cloud_consumer = rpc.AdapterConsumer(connection=self.conn,
+ topic=FLAGS.cloud_topic,
+ proxy=self.cloud)
+ self.injected.append(self.cloud_consumer.attach_to_tornado(self.ioloop))
+
+ # set up a node
+ self.node = node.Node()
+ self.node_consumer = rpc.AdapterConsumer(connection=self.conn,
+ topic=FLAGS.compute_topic,
+ proxy=self.node)
+ self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop))
+
+ def test_flush_terminated(self):
+ # Launch an instance
+
+ # Wait until it's running
+
+ # Terminate it
+
+ # Wait until it's terminated
+
+ # Flush terminated nodes
+
+ # ASSERT that it's gone
+ pass
diff --git a/nova/tests/keeper_unittest.py b/nova/tests/keeper_unittest.py
new file mode 100644
index 0000000000..3896c9e578
--- /dev/null
+++ b/nova/tests/keeper_unittest.py
@@ -0,0 +1,57 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+import random
+
+from nova import datastore
+from nova import test
+
+class KeeperTestCase(test.TrialTestCase):
+ """
+ Basic persistence tests for Keeper datastore.
+ Generalize, then use these to support
+ migration to redis / cassandra / multiple stores.
+ """
+
+ def setUp(self):
+ super(KeeperTestCase, self).setUp()
+ self.keeper = datastore.Keeper('test')
+
+ def tearDown(self):
+ super(KeeperTestCase, self).tearDown()
+ self.keeper.clear()
+
+ def test_store_strings(self):
+ """
+ Confirm that simple strings go in and come out safely.
+ Should also test unicode strings.
+ """
+ randomstring = ''.join(
+ [random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-')
+ for _x in xrange(20)]
+ )
+ self.keeper['test_string'] = randomstring
+ self.assertEqual(randomstring, self.keeper['test_string'])
+
+ def test_store_dicts(self):
+ """
+ Arbitrary dictionaries should be storable.
+ """
+ test_dict = {'key_one': 'value_one'}
+ self.keeper['test_dict'] = test_dict
+ self.assertEqual(test_dict['key_one'],
+ self.keeper['test_dict']['key_one'])
+
+ def test_sets(self):
+ """
+ A keeper dict should be self-serializing.
+ """
+ self.keeper.set_add('test_set', 'foo')
+ test_dict = {'arbitrary': 'dict of stuff'}
+ self.keeper.set_add('test_set', test_dict)
+ self.assertTrue(self.keeper.set_is_member('test_set', 'foo'))
+ self.assertFalse(self.keeper.set_is_member('test_set', 'bar'))
+ self.keeper.set_remove('test_set', 'foo')
+ self.assertFalse(self.keeper.set_is_member('test_set', 'foo'))
+ rv = self.keeper.set_fetch('test_set')
+ self.assertEqual(test_dict, rv.next())
+ self.keeper.set_remove('test_set', test_dict)
+
diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py
new file mode 100644
index 0000000000..43c7831a7a
--- /dev/null
+++ b/nova/tests/network_unittest.py
@@ -0,0 +1,113 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import unittest
+
+from nova import vendor
+import IPy
+
+from nova import flags
+from nova import test
+from nova.compute import network
+from nova.auth import users
+
+
+class NetworkTestCase(test.TrialTestCase):
+ def setUp(self):
+ super(NetworkTestCase, self).setUp()
+ logging.getLogger().setLevel(logging.DEBUG)
+ self.manager = users.UserManager.instance()
+ for i in range(0, 6):
+ name = 'user%s' % i
+ if not self.manager.get_user(name):
+ self.manager.create_user(name, name, name)
+ self.network = network.NetworkController(netsize=16)
+
+ def tearDown(self):
+ super(NetworkTestCase, self).tearDown()
+ for i in range(0, 6):
+ name = 'user%s' % i
+ self.manager.delete_user(name)
+
+ def test_network_serialization(self):
+ net1 = network.Network(vlan=100, network="192.168.100.0/24", conn=None)
+ address = net1.allocate_ip("user0", "01:24:55:36:f2:a0")
+ net_json = str(net1)
+ net2 = network.Network.from_json(net_json)
+ self.assertEqual(net_json, str(net2))
+ self.assertTrue(IPy.IP(address) in net2.network)
+
+ def test_allocate_deallocate_address(self):
+ for flag in flags.FLAGS:
+ print "%s=%s" % (flag, flags.FLAGS.get(flag, None))
+ (address, net_name) = self.network.allocate_address(
+ "user0", "01:24:55:36:f2:a0")
+ logging.debug("Was allocated %s" % (address))
+ self.assertEqual(True, address in self._get_user_addresses("user0"))
+ rv = self.network.deallocate_address(address)
+ self.assertEqual(False, address in self._get_user_addresses("user0"))
+
+ def test_range_allocation(self):
+ (address, net_name) = self.network.allocate_address(
+ "user0", "01:24:55:36:f2:a0")
+ (secondaddress, net_name) = self.network.allocate_address(
+ "user1", "01:24:55:36:f2:a0")
+ self.assertEqual(True, address in self._get_user_addresses("user0"))
+ self.assertEqual(True,
+ secondaddress in self._get_user_addresses("user1"))
+ self.assertEqual(False, address in self._get_user_addresses("user1"))
+ rv = self.network.deallocate_address(address)
+ self.assertEqual(False, address in self._get_user_addresses("user0"))
+ rv = self.network.deallocate_address(secondaddress)
+ self.assertEqual(False,
+ secondaddress in self._get_user_addresses("user1"))
+
+ def test_subnet_edge(self):
+ (secondaddress, net_name) = self.network.allocate_address("user0")
+ for user in range(1,5):
+ user_id = "user%s" % (user)
+ (address, net_name) = self.network.allocate_address(
+ user_id, "01:24:55:36:f2:a0")
+ (address2, net_name) = self.network.allocate_address(
+ user_id, "01:24:55:36:f2:a0")
+ (address3, net_name) = self.network.allocate_address(
+ user_id, "01:24:55:36:f2:a0")
+ self.assertEqual(False,
+ address in self._get_user_addresses("user0"))
+ self.assertEqual(False,
+ address2 in self._get_user_addresses("user0"))
+ self.assertEqual(False,
+ address3 in self._get_user_addresses("user0"))
+ rv = self.network.deallocate_address(address)
+ rv = self.network.deallocate_address(address2)
+ rv = self.network.deallocate_address(address3)
+ rv = self.network.deallocate_address(secondaddress)
+
+ def test_too_many_users(self):
+ for i in range(0, 30):
+ name = 'toomany-user%s' % i
+ self.manager.create_user(name, name, name)
+ (address, net_name) = self.network.allocate_address(
+ name, "01:24:55:36:f2:a0")
+ self.manager.delete_user(name)
+
+ def _get_user_addresses(self, user_id):
+ rv = self.network.describe_addresses()
+ user_addresses = []
+ for item in rv:
+ if item['user_id'] == user_id:
+ user_addresses.append(item['address'])
+ return user_addresses
diff --git a/nova/tests/node_unittest.py b/nova/tests/node_unittest.py
new file mode 100644
index 0000000000..7a6115fcc8
--- /dev/null
+++ b/nova/tests/node_unittest.py
@@ -0,0 +1,128 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import StringIO
+import time
+import unittest
+from xml.etree import ElementTree
+
+from nova import vendor
+import mox
+from tornado import ioloop
+from twisted.internet import defer
+
+from nova import exception
+from nova import flags
+from nova import test
+from nova import utils
+from nova.compute import model
+from nova.compute import node
+
+FLAGS = flags.FLAGS
+
+
+class InstanceXmlTestCase(test.TrialTestCase):
+ # @defer.inlineCallbacks
+ def test_serialization(self):
+ # TODO: Reimplement this, it doesn't make sense in redis-land
+ return
+
+ # instance_id = 'foo'
+ # first_node = node.Node()
+ # inst = yield first_node.run_instance(instance_id)
+ #
+ # # force the state so that we can verify that it changes
+ # inst._s['state'] = node.Instance.NOSTATE
+ # xml = inst.toXml()
+ # self.assert_(ElementTree.parse(StringIO.StringIO(xml)))
+ #
+ # second_node = node.Node()
+ # new_inst = node.Instance.fromXml(second_node._conn, pool=second_node._pool, xml=xml)
+ # self.assertEqual(new_inst.state, node.Instance.RUNNING)
+ # rv = yield first_node.terminate_instance(instance_id)
+
+
+class NodeConnectionTestCase(test.TrialTestCase):
+ def setUp(self):
+ logging.getLogger().setLevel(logging.DEBUG)
+ super(NodeConnectionTestCase, self).setUp()
+ self.flags(fake_libvirt=True,
+ fake_storage=True,
+ fake_users=True,
+ redis_db=8)
+ self.node = node.Node()
+
+ def create_instance(self):
+ instdir = model.InstanceDirectory()
+ inst = instdir.new()
+ # TODO(ja): add ami, ari, aki, user_data
+ inst['reservation_id'] = 'r-fakeres'
+ inst['launch_time'] = '10'
+ inst['owner_id'] = 'fake'
+ inst['node_name'] = FLAGS.node_name
+ inst['mac_address'] = utils.generate_mac()
+ inst['ami_launch_index'] = 0
+ inst.save()
+ return inst['instance_id']
+
+ @defer.inlineCallbacks
+ def test_run_describe_terminate(self):
+ instance_id = self.create_instance()
+
+ rv = yield self.node.run_instance(instance_id)
+
+ rv = yield self.node.describe_instances()
+ self.assertEqual(rv[instance_id].name, instance_id)
+
+ rv = yield self.node.terminate_instance(instance_id)
+
+ rv = yield self.node.describe_instances()
+ self.assertEqual(rv, {})
+
+ @defer.inlineCallbacks
+ def test_reboot(self):
+ instance_id = self.create_instance()
+ rv = yield self.node.run_instance(instance_id)
+
+ rv = yield self.node.describe_instances()
+ logging.debug("describe_instances returns %s" % (rv))
+ self.assertEqual(rv[instance_id].name, instance_id)
+
+ yield self.node.reboot_instance(instance_id)
+
+ rv = yield self.node.describe_instances()
+ self.assertEqual(rv[instance_id].name, instance_id)
+ rv = yield self.node.terminate_instance(instance_id)
+
+ @defer.inlineCallbacks
+ def test_console_output(self):
+ instance_id = self.create_instance()
+ rv = yield self.node.run_instance(instance_id)
+
+ console = yield self.node.get_console_output(instance_id)
+ self.assert_(console)
+ rv = yield self.node.terminate_instance(instance_id)
+
+ @defer.inlineCallbacks
+ def test_run_instance_existing(self):
+ instance_id = self.create_instance()
+ rv = yield self.node.run_instance(instance_id)
+
+ rv = yield self.node.describe_instances()
+ self.assertEqual(rv[instance_id].name, instance_id)
+
+ self.assertRaises(exception.Error, self.node.run_instance, instance_id)
+ rv = yield self.node.terminate_instance(instance_id)
diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py
new file mode 100644
index 0000000000..5f41d47a0c
--- /dev/null
+++ b/nova/tests/objectstore_unittest.py
@@ -0,0 +1,190 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import glob
+import hashlib
+import logging
+import os
+import shutil
+import tempfile
+
+from nova import vendor
+
+from nova import flags
+from nova import rpc
+from nova import objectstore
+from nova import test
+from nova.auth import users
+
+FLAGS = flags.FLAGS
+
+
+oss_tempdir = tempfile.mkdtemp(prefix='test_oss-')
+
+
+# delete tempdirs from previous runs (we don't delete after test to allow
+# checking the contents after running tests)
+for path in glob.glob(os.path.abspath(os.path.join(oss_tempdir, '../test_oss-*'))):
+ if path != oss_tempdir:
+ shutil.rmtree(path)
+
+
+# create bucket/images path
+os.makedirs(os.path.join(oss_tempdir, 'images'))
+os.makedirs(os.path.join(oss_tempdir, 'buckets'))
+
+class ObjectStoreTestCase(test.BaseTestCase):
+ def setUp(self):
+ super(ObjectStoreTestCase, self).setUp()
+ self.flags(fake_users=True,
+ buckets_path=os.path.join(oss_tempdir, 'buckets'),
+ images_path=os.path.join(oss_tempdir, 'images'),
+ ca_path=os.path.join(os.path.dirname(__file__), 'CA'))
+ self.conn = rpc.Connection.instance()
+ logging.getLogger().setLevel(logging.DEBUG)
+
+ self.um = users.UserManager.instance()
+
+ def test_buckets(self):
+ try:
+ self.um.create_user('user1')
+ except: pass
+ try:
+ self.um.create_user('user2')
+ except: pass
+ try:
+ self.um.create_user('admin_user', admin=True)
+ except: pass
+
+ objectstore.bucket.Bucket.create('new_bucket', self.um.get_user('user1'))
+ bucket = objectstore.bucket.Bucket('new_bucket')
+
+ # creator is authorized to use bucket
+ self.assert_(bucket.is_authorized(self.um.get_user('user1')))
+
+ # another user is not authorized
+ self.assert_(bucket.is_authorized(self.um.get_user('user2')) == False)
+
+ # admin is authorized to use bucket
+ self.assert_(bucket.is_authorized(self.um.get_user('admin_user')))
+
+ # new buckets are empty
+ self.assert_(bucket.list_keys()['Contents'] == [])
+
+ # storing keys works
+ bucket['foo'] = "bar"
+
+ self.assert_(len(bucket.list_keys()['Contents']) == 1)
+
+ self.assert_(bucket['foo'].read() == 'bar')
+
+ # md5 of key works
+ self.assert_(bucket['foo'].md5 == hashlib.md5('bar').hexdigest())
+
+ # deleting non-empty bucket throws exception
+ exception = False
+ try:
+ bucket.delete()
+ except:
+ exception = True
+
+ self.assert_(exception)
+
+ # deleting key
+ del bucket['foo']
+
+ # deleting empty button
+ bucket.delete()
+
+ # accessing deleted bucket throws exception
+ exception = False
+ try:
+ objectstore.bucket.Bucket('new_bucket')
+ except:
+ exception = True
+
+ self.assert_(exception)
+ self.um.delete_user('user1')
+ self.um.delete_user('user2')
+ self.um.delete_user('admin_user')
+
+ def test_images(self):
+ try:
+ self.um.create_user('image_creator')
+ except: pass
+ image_user = self.um.get_user('image_creator')
+
+ # create a bucket for our bundle
+ objectstore.bucket.Bucket.create('image_bucket', image_user)
+ bucket = objectstore.bucket.Bucket('image_bucket')
+
+ # upload an image manifest/parts
+ bundle_path = os.path.join(os.path.dirname(__file__), 'bundle')
+ for path in glob.glob(bundle_path + '/*'):
+ bucket[os.path.basename(path)] = open(path, 'rb').read()
+
+ # register an image
+ objectstore.image.Image.create('i-testing', 'image_bucket/1mb.manifest.xml', image_user)
+
+ # verify image
+ my_img = objectstore.image.Image('i-testing')
+ result_image_file = os.path.join(my_img.path, 'image')
+ self.assertEqual(os.stat(result_image_file).st_size, 1048576)
+
+ sha = hashlib.sha1(open(result_image_file).read()).hexdigest()
+ self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3')
+
+ # verify image permissions
+ try:
+ self.um.create_user('new_user')
+ except: pass
+ new_user = self.um.get_user('new_user')
+ self.assert_(my_img.is_authorized(new_user) == False)
+
+ self.um.delete_user('new_user')
+ self.um.delete_user('image_creator')
+
+# class ApiObjectStoreTestCase(test.BaseTestCase):
+# def setUp(self):
+# super(ApiObjectStoreTestCase, self).setUp()
+# FLAGS.fake_users = True
+# FLAGS.buckets_path = os.path.join(tempdir, 'buckets')
+# FLAGS.images_path = os.path.join(tempdir, 'images')
+# FLAGS.ca_path = os.path.join(os.path.dirname(__file__), 'CA')
+#
+# self.users = users.UserManager.instance()
+# self.app = handler.Application(self.users)
+#
+# self.host = '127.0.0.1'
+#
+# self.conn = boto.s3.connection.S3Connection(
+# aws_access_key_id=user.access,
+# aws_secret_access_key=user.secret,
+# is_secure=False,
+# calling_format=boto.s3.connection.OrdinaryCallingFormat(),
+# port=FLAGS.s3_port,
+# host=FLAGS.s3_host)
+#
+# self.mox.StubOutWithMock(self.ec2, 'new_http_connection')
+#
+# def tearDown(self):
+# FLAGS.Reset()
+# super(ApiObjectStoreTestCase, self).tearDown()
+#
+# def test_describe_instances(self):
+# self.expect_http()
+# self.mox.ReplayAll()
+#
+# self.assertEqual(self.ec2.get_all_instances(), [])
diff --git a/nova/tests/real_flags.py b/nova/tests/real_flags.py
new file mode 100644
index 0000000000..68fe8dc5b6
--- /dev/null
+++ b/nova/tests/real_flags.py
@@ -0,0 +1,24 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from nova import flags
+
+FLAGS = flags.FLAGS
+
+FLAGS.fake_libvirt = False
+FLAGS.fake_storage = False
+FLAGS.fake_rabbit = False
+FLAGS.fake_network = False
+FLAGS.fake_users = False
+FLAGS.verbose = False
diff --git a/nova/tests/storage_unittest.py b/nova/tests/storage_unittest.py
new file mode 100644
index 0000000000..31966d2d56
--- /dev/null
+++ b/nova/tests/storage_unittest.py
@@ -0,0 +1,86 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import StringIO
+import time
+import unittest
+from xml.etree import ElementTree
+
+from nova import vendor
+import mox
+from tornado import ioloop
+from twisted.internet import defer
+
+from nova import exception
+from nova import flags
+from nova import test
+from nova.compute import node
+from nova.volume import storage
+
+
+FLAGS = flags.FLAGS
+
+
+class StorageTestCase(test.TrialTestCase):
+ def setUp(self):
+ logging.getLogger().setLevel(logging.DEBUG)
+ super(StorageTestCase, self).setUp()
+ self.mynode = node.Node()
+ self.mystorage = None
+ self.flags(fake_libvirt=True,
+ fake_storage=True,
+ redis_db=8)
+ if FLAGS.fake_storage:
+ self.mystorage = storage.FakeBlockStore()
+ else:
+ self.mystorage = storage.BlockStore()
+
+ @test.skip_if_fake
+ def test_run_create_volume(self):
+ vol_size = '0'
+ user_id = 'fake'
+ volume_id = self.mystorage.create_volume(vol_size, user_id)
+ # rv = self.mystorage.describe_volumes()
+
+ # Volumes have to be sorted by timestamp in order to work here...
+ # TODO(termie): get_volume returns differently than create_volume
+ self.assertEqual(volume_id,
+ self.mystorage.get_volume(volume_id)['volume_id'])
+
+ rv = self.mystorage.delete_volume(volume_id)
+ self.assertRaises(exception.Error,
+ self.mystorage.get_volume,
+ volume_id)
+
+ @test.skip_if_fake
+ def test_run_attach_detach_volume(self):
+ # Create one volume and one node to test with
+ instance_id = "storage-test"
+ # TODO(joshua) - Redo this test, can't make fake instances this way any more
+ # rv = self.mynode.run_instance(instance_id)
+ vol_size = "5"
+ user_id = "fake"
+ volume_id = self.mystorage.create_volume(vol_size, user_id)
+ rv = self.mystorage.attach_volume(volume_id,
+ instance_id,
+ "/dev/sdf")
+ volume_obj = self.mystorage.get_volume(volume_id)
+ self.assertEqual(volume_obj['status'], "attached")
+ # TODO(???): assert that it's attached to the right instance
+
+ rv = self.mystorage.detach_volume(volume_id)
+ volume_obj = self.mystorage.get_volume(volume_id)
+ self.assertEqual(volume_obj['status'], "available")
diff --git a/nova/tests/users_unittest.py b/nova/tests/users_unittest.py
new file mode 100644
index 0000000000..70f508b351
--- /dev/null
+++ b/nova/tests/users_unittest.py
@@ -0,0 +1,137 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import unittest
+
+from nova import vendor
+from M2Crypto import BIO
+from M2Crypto import RSA
+from M2Crypto import X509
+
+from nova import crypto
+from nova import flags
+from nova import test
+from nova import utils
+from nova.auth import users
+from nova.endpoint import cloud
+
+
+FLAGS = flags.FLAGS
+
+
+class UserTestCase(test.BaseTestCase):
+ def setUp(self):
+ super(UserTestCase, self).setUp()
+ self.flags(fake_libvirt=True,
+ fake_storage=True,
+ redis_db=8)
+ self.users = users.UserManager.instance()
+
+ def test_001_can_create_user(self):
+ self.users.create_user('test1', 'access', 'secret')
+
+ def test_002_can_get_user(self):
+ user = self.users.get_user('test1')
+
+ def test_003_can_retreive_properties(self):
+ user = self.users.get_user('test1')
+ self.assertEqual('test1', user.id)
+ self.assertEqual('access', user.access)
+ self.assertEqual('secret', user.secret)
+
+ def test_004_signature_is_valid(self):
+ #self.assertTrue(self.users.authenticate( **boto.generate_url ... ? ? ? ))
+ pass
+ #raise NotImplementedError
+
+ def test_005_can_get_credentials(self):
+ return
+ credentials = self.users.get_user('test1').get_credentials()
+ self.assertEqual(credentials,
+ 'export EC2_ACCESS_KEY="access"\n' +
+ 'export EC2_SECRET_KEY="secret"\n' +
+ 'export EC2_URL="http://127.0.0.1:8773/services/Cloud"\n' +
+ 'export S3_URL="http://127.0.0.1:3333/"\n' +
+ 'export EC2_USER_ID="test1"\n')
+
+ def test_006_test_key_storage(self):
+ user = self.users.get_user('test1')
+ user.create_key_pair('public', 'key', 'fingerprint')
+ key = user.get_key_pair('public')
+ self.assertEqual('key', key.public_key)
+ self.assertEqual('fingerprint', key.fingerprint)
+
+ def test_007_test_key_generation(self):
+ user = self.users.get_user('test1')
+ private_key, fingerprint = user.generate_key_pair('public2')
+ key = RSA.load_key_string(private_key, callback=lambda: None)
+ bio = BIO.MemoryBuffer()
+ public_key = user.get_key_pair('public2').public_key
+ key.save_pub_key_bio(bio)
+ converted = crypto.ssl_pub_to_ssh_pub(bio.read())
+ # assert key fields are equal
+ print converted
+ self.assertEqual(public_key.split(" ")[1].strip(),
+ converted.split(" ")[1].strip())
+
+ def test_008_can_list_key_pairs(self):
+ keys = self.users.get_user('test1').get_key_pairs()
+ self.assertTrue(filter(lambda k: k.name == 'public', keys))
+ self.assertTrue(filter(lambda k: k.name == 'public2', keys))
+
+ def test_009_can_delete_key_pair(self):
+ self.users.get_user('test1').delete_key_pair('public')
+ keys = self.users.get_user('test1').get_key_pairs()
+ self.assertFalse(filter(lambda k: k.name == 'public', keys))
+
+ def test_010_can_list_users(self):
+ users = self.users.get_users()
+ self.assertTrue(filter(lambda u: u.id == 'test1', users))
+
+ def test_011_can_generate_x509(self):
+ # MUST HAVE RUN CLOUD SETUP BY NOW
+ self.cloud = cloud.CloudController()
+ self.cloud.setup()
+ private_key, signed_cert_string = self.users.get_user('test1').generate_x509_cert()
+ logging.debug(signed_cert_string)
+
+ # Need to verify that it's signed by the right intermediate CA
+ full_chain = crypto.fetch_ca(username='test1', chain=True)
+ int_cert = crypto.fetch_ca(username='test1', chain=False)
+ cloud_cert = crypto.fetch_ca()
+ logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
+ signed_cert = X509.load_cert_string(signed_cert_string)
+ chain_cert = X509.load_cert_string(full_chain)
+ int_cert = X509.load_cert_string(int_cert)
+ cloud_cert = X509.load_cert_string(cloud_cert)
+ self.assertTrue(signed_cert.verify(chain_cert.get_pubkey()))
+ self.assertTrue(signed_cert.verify(int_cert.get_pubkey()))
+
+ if not FLAGS.use_intermediate_ca:
+ self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey()))
+ else:
+ self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
+
+ def test_012_can_delete_user(self):
+ self.users.delete_user('test1')
+ users = self.users.get_users()
+ if users != None:
+ self.assertFalse(filter(lambda u: u.id == 'test1', users))
+
+
+if __name__ == "__main__":
+ # TODO: Implement use_fake as an option
+ unittest.main()
diff --git a/nova/twistd.py b/nova/twistd.py
new file mode 100644
index 0000000000..ea3c9c1689
--- /dev/null
+++ b/nova/twistd.py
@@ -0,0 +1,249 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Twisted daemon helpers, specifically to parse out gFlags from twisted flags,
+manage pid files and support syslogging.
+"""
+
+import logging
+import os
+import signal
+import sys
+import time
+import UserDict
+import logging.handlers
+
+from nova import vendor
+from twisted.scripts import twistd
+from twisted.python import log
+from twisted.python import reflect
+from twisted.python import runtime
+from twisted.python import usage
+
+from nova import flags
+
+if runtime.platformType == "win32":
+ from twisted.scripts._twistw import ServerOptions
+else:
+ from twisted.scripts._twistd_unix import ServerOptions
+
+
+FLAGS = flags.FLAGS
+
+
+class TwistdServerOptions(ServerOptions):
+ def parseArgs(self, *args):
+ return
+
+
+def WrapTwistedOptions(wrapped):
+ class TwistedOptionsToFlags(wrapped):
+ subCommands = None
+ def __init__(self):
+ # NOTE(termie): _data exists because Twisted stuff expects
+ # to be able to set arbitrary things that are
+ # not actual flags
+ self._data = {}
+ self._flagHandlers = {}
+ self._paramHandlers = {}
+
+ # Absorb the twistd flags into our FLAGS
+ self._absorbFlags()
+ self._absorbParameters()
+ self._absorbHandlers()
+
+ super(TwistedOptionsToFlags, self).__init__()
+
+ def _absorbFlags(self):
+ twistd_flags = []
+ reflect.accumulateClassList(self.__class__, 'optFlags', twistd_flags)
+ for flag in twistd_flags:
+ key = flag[0].replace('-', '_')
+ flags.DEFINE_boolean(key, None, str(flag[-1]))
+
+ def _absorbParameters(self):
+ twistd_params = []
+ reflect.accumulateClassList(self.__class__, 'optParameters', twistd_params)
+ for param in twistd_params:
+ key = param[0].replace('-', '_')
+ flags.DEFINE_string(key, param[2], str(param[-1]))
+
+ def _absorbHandlers(self):
+ twistd_handlers = {}
+ reflect.addMethodNamesToDict(self.__class__, twistd_handlers, "opt_")
+
+ # NOTE(termie): Much of the following is derived/copied from
+ # twisted.python.usage with the express purpose of
+ # providing compatibility
+ for name in twistd_handlers.keys():
+ method = getattr(self, 'opt_'+name)
+
+ takesArg = not usage.flagFunction(method, name)
+ doc = getattr(method, '__doc__', None)
+ if not doc:
+ doc = 'undocumented'
+
+ if not takesArg:
+ if name not in FLAGS:
+ flags.DEFINE_boolean(name, None, doc)
+ self._flagHandlers[name] = method
+ else:
+ if name not in FLAGS:
+ flags.DEFINE_string(name, None, doc)
+ self._paramHandlers[name] = method
+
+
+ def _doHandlers(self):
+ for flag, handler in self._flagHandlers.iteritems():
+ if self[flag]:
+ handler()
+ for param, handler in self._paramHandlers.iteritems():
+ if self[param] is not None:
+ handler(self[param])
+
+ def __str__(self):
+ return str(FLAGS)
+
+ def parseOptions(self, options=None):
+ if options is None:
+ options = sys.argv
+ else:
+ options.insert(0, '')
+
+ args = FLAGS(options)
+ argv = args[1:]
+ # ignore subcommands
+
+ try:
+ self.parseArgs(*argv)
+ except TypeError:
+ raise usage.UsageError("Wrong number of arguments.")
+
+ self.postOptions()
+ return args
+
+ def parseArgs(self, *args):
+ # TODO(termie): figure out a decent way of dealing with args
+ #return
+ super(TwistedOptionsToFlags, self).parseArgs(*args)
+
+ def postOptions(self):
+ self._doHandlers()
+
+ super(TwistedOptionsToFlags, self).postOptions()
+
+ def __getitem__(self, key):
+ key = key.replace('-', '_')
+ try:
+ return getattr(FLAGS, key)
+ except (AttributeError, KeyError):
+ return self._data[key]
+
+ def __setitem__(self, key, value):
+ key = key.replace('-', '_')
+ try:
+ return setattr(FLAGS, key, value)
+ except (AttributeError, KeyError):
+ self._data[key] = value
+
+ return TwistedOptionsToFlags
+
+
+def stop(pidfile):
+ """
+ Stop the daemon
+ """
+ # Get the pid from the pidfile
+ try:
+ pf = file(pidfile,'r')
+ pid = int(pf.read().strip())
+ pf.close()
+ except IOError:
+ pid = None
+
+ if not pid:
+ message = "pidfile %s does not exist. Daemon not running?\n"
+ sys.stderr.write(message % pidfile)
+ return # not an error in a restart
+
+ # Try killing the daemon process
+ try:
+ while 1:
+ os.kill(pid, signal.SIGKILL)
+ time.sleep(0.1)
+ except OSError, err:
+ err = str(err)
+ if err.find("No such process") > 0:
+ if os.path.exists(pidfile):
+ os.remove(pidfile)
+ else:
+ print str(err)
+ sys.exit(1)
+
+
+def serve(filename):
+ logging.debug("Serving %s" % filename)
+ name = os.path.basename(filename)
+ OptionsClass = WrapTwistedOptions(TwistdServerOptions)
+ options = OptionsClass()
+ argv = options.parseOptions()
+ logging.getLogger('amqplib').setLevel(logging.WARN)
+ FLAGS.python = filename
+ FLAGS.no_save = True
+ if not FLAGS.pidfile:
+ FLAGS.pidfile = '%s.pid' % name
+ elif FLAGS.pidfile.endswith('twistd.pid'):
+ FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', '%s.pid' % name)
+
+ if not FLAGS.logfile:
+ FLAGS.logfile = '%s.log' % name
+
+ action = 'start'
+ if len(argv) > 1:
+ action = argv.pop()
+
+ if action == 'stop':
+ stop(FLAGS.pidfile)
+ sys.exit()
+ elif action == 'restart':
+ stop(FLAGS.pidfile)
+ elif action == 'start':
+ pass
+ else:
+ print 'usage: %s [options] [start|stop|restart]' % argv[0]
+ sys.exit(1)
+
+ formatter = logging.Formatter(
+ name + '(%(name)s): %(levelname)s %(message)s')
+ handler = logging.StreamHandler(log.StdioOnnaStick())
+ handler.setFormatter(formatter)
+ logging.getLogger().addHandler(handler)
+
+ if FLAGS.verbose:
+ logging.getLogger().setLevel(logging.DEBUG)
+ else:
+ logging.getLogger().setLevel(logging.WARNING)
+
+ if FLAGS.syslog:
+ syslog = logging.handlers.SysLogHandler(address='/dev/log')
+ syslog.setFormatter(formatter)
+ logging.getLogger().addHandler(syslog)
+
+ logging.debug("Full set of FLAGS:")
+ for flag in FLAGS:
+ logging.debug("%s : %s" % (flag, FLAGS.get(flag, None)))
+
+ twistd.runApp(options)
diff --git a/nova/utils.py b/nova/utils.py
new file mode 100644
index 0000000000..0cfa2cf6c7
--- /dev/null
+++ b/nova/utils.py
@@ -0,0 +1,96 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+System-level utilities and helper functions.
+"""
+
+import logging
+import socket
+import sys
+import os.path
+import inspect
+import subprocess
+import random
+
+def fetchfile(url, target):
+ logging.debug("Fetching %s" % url)
+# c = pycurl.Curl()
+# fp = open(target, "wb")
+# c.setopt(c.URL, url)
+# c.setopt(c.WRITEDATA, fp)
+# c.perform()
+# c.close()
+# fp.close()
+ execute("curl %s -o %s" % (url, target))
+
+def execute(cmd, input=None):
+ #logging.debug("Running %s" % (cmd))
+ obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ result = None
+ if input != None:
+ result = obj.communicate(input)
+ else:
+ result = obj.communicate()
+ obj.stdin.close()
+ if obj.returncode:
+ logging.debug("Result was %s" % (obj.returncode))
+ return result
+
+def abspath(s):
+ return os.path.join(os.path.dirname(__file__), s)
+
+def default_flagfile(filename='nova.conf'):
+ for arg in sys.argv:
+ if arg.find('flagfile') != -1:
+ break
+ else:
+ if not os.path.isabs(filename):
+ # turn relative filename into an absolute path
+ script_dir = os.path.dirname(inspect.stack()[-1][1])
+ filename = os.path.abspath(os.path.join(script_dir, filename))
+ if os.path.exists(filename):
+ sys.argv = sys.argv[:1] + ['--flagfile=%s' % filename] + sys.argv[1:]
+
+def debug(arg):
+ logging.debug('debug in callback: %s', arg)
+ return arg
+
+def runthis(prompt, cmd):
+ logging.debug("Running %s" % (cmd))
+ logging.debug(prompt % (subprocess.call(cmd.split(" "))))
+
+
+def generate_uid(topic, size=8):
+ return '%s-%s' % (topic, ''.join([random.choice('01234567890abcdefghijklmnopqrstuvwxyz') for x in xrange(size)]))
+
+def generate_mac():
+ mac = [0x00, 0x16, 0x3e, random.randint(0x00, 0x7f),
+ random.randint(0x00, 0xff), random.randint(0x00, 0xff)
+ ]
+ return ':'.join(map(lambda x: "%02x" % x, mac))
+
+def last_octet(address):
+ return int(address.split(".")[-1])
+
+def get_my_ip():
+ ''' returns the actual ip of the local machine.
+ '''
+ csock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ csock.connect(('www.google.com', 80))
+ (addr, port) = csock.getsockname()
+ csock.close()
+ return addr
diff --git a/nova/vendor.py b/nova/vendor.py
new file mode 100644
index 0000000000..758adeb3cc
--- /dev/null
+++ b/nova/vendor.py
@@ -0,0 +1,43 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Get our vendor folders into the system path.
+"""
+
+import os
+import sys
+
+# abspath/__file__/../vendor
+VENDOR_PATH = os.path.abspath(
+ os.path.join(os.path.dirname(os.path.dirname(__file__)), 'vendor'))
+
+if not os.path.exists(VENDOR_PATH):
+ print 'warning: no vendor libraries included'
+else:
+ paths = [VENDOR_PATH,
+ os.path.join(VENDOR_PATH, 'pymox'),
+ os.path.join(VENDOR_PATH, 'tornado'),
+ os.path.join(VENDOR_PATH, 'python-gflags'),
+ os.path.join(VENDOR_PATH, 'python-daemon'),
+ os.path.join(VENDOR_PATH, 'lockfile'),
+ os.path.join(VENDOR_PATH, 'boto'),
+ os.path.join(VENDOR_PATH, 'Twisted-10.0.0'),
+ os.path.join(VENDOR_PATH, 'redis-py'),
+ ]
+
+ for p in paths:
+ if p not in sys.path:
+ sys.path.insert(0, p)
diff --git a/nova/volume/__init__.py b/nova/volume/__init__.py
new file mode 100644
index 0000000000..1c569f3830
--- /dev/null
+++ b/nova/volume/__init__.py
@@ -0,0 +1,27 @@
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:mod:`nova.volume` -- Nova Block Storage
+=====================================================
+
+.. automodule:: nova.volume
+ :platform: Unix
+.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
+.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
+.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>
+.. moduleauthor:: Joshua McKenty <joshua@cognition.ca>
+.. moduleauthor:: Manish Singh <yosh@gimp.org>
+.. moduleauthor:: Andy Smith <andy@anarkystic.com>
+""" \ No newline at end of file
diff --git a/nova/volume/storage.py b/nova/volume/storage.py
new file mode 100644
index 0000000000..823e1390a5
--- /dev/null
+++ b/nova/volume/storage.py
@@ -0,0 +1,250 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Nova Storage manages creating, attaching, detaching, and
+destroying persistent storage volumes, ala EBS.
+Currently uses Ata-over-Ethernet.
+"""
+
+import logging
+import random
+import socket
+import subprocess
+import time
+
+from nova import vendor
+from tornado import ioloop
+from twisted.internet import defer
+
+from nova import datastore
+from nova import exception
+from nova import flags
+from nova import rpc
+from nova import utils
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('storage_dev', '/dev/sdb',
+ 'Physical device to use for volumes')
+flags.DEFINE_string('volume_group', 'nova-volumes',
+ 'Name for the VG that will contain exported volumes')
+flags.DEFINE_string('aoe_eth_dev', 'eth0',
+ 'Which device to export the volumes on')
+flags.DEFINE_string('storage_name',
+ socket.gethostname(),
+ 'name of this node')
+flags.DEFINE_integer('shelf_id',
+ utils.last_octet(utils.get_my_ip()),
+ 'AoE shelf_id for this node')
+flags.DEFINE_string('storage_availability_zone',
+ 'nova',
+ 'availability zone of this node')
+flags.DEFINE_boolean('fake_storage', False,
+ 'Should we make real storage volumes to attach?')
+
+class BlockStore(object):
+ def __init__(self):
+ super(BlockStore, self).__init__()
+ self.volume_class = Volume
+ if FLAGS.fake_storage:
+ self.volume_class = FakeVolume
+ self._init_volume_group()
+ self.keeper = datastore.Keeper('instances')
+
+ def report_state(self):
+ #TODO: aggregate the state of the system
+ pass
+
+ def create_volume(self, size, user_id):
+ """
+ Creates an exported volume (fake or real),
+ restarts exports to make it available.
+ Volume at this point has size, owner, and zone.
+ """
+ logging.debug("Creating volume of size: %s" % (size))
+ vol = self.volume_class.create(size, user_id)
+ self.keeper.set_add('volumes', vol['volume_id'])
+ self._restart_exports()
+ return vol['volume_id']
+
+ def get_volume(self, volume_id):
+ """ Returns a redis-backed volume object """
+ if volume_id in self.keeper['volumes']:
+ return self.volume_class(volume_id=volume_id)
+ raise exception.Error("Volume does not exist")
+
+ def by_project(self, project):
+ """ returns a list of volume objects for a project """
+ # TODO(termie): I don't understand why this is doing a range
+ #for volume_id in datastore.Redis.instance().lrange("project:%s:volumes" %
+ #project, 0, -1):
+ for volume_id in datastore['project:%s:volumes' % project]:
+ yield self.volume_class(volume_id=volume_id)
+
+ def by_node(self, node_id):
+ """ returns a list of volumes for a node """
+ for volume in self.all:
+ if volume['node_name'] == node_id:
+ yield volume
+
+ @property
+ def all(self):
+ """ returns a list of all volumes """
+ for volume_id in self.keeper['volumes']:
+ yield self.volume_class(volume_id=volume_id)
+
+
+ def delete_volume(self, volume_id):
+ logging.debug("Deleting volume with id of: %s" % (volume_id))
+ vol = self.get_volume(volume_id)
+ vol.destroy()
+ self.keeper.set_remove('volumes', vol['volume_id'])
+ return True
+
+ def attach_volume(self, volume_id, instance_id, mountpoint):
+ self.volume_class(volume_id).attach(instance_id, mountpoint)
+
+ def detach_volume(self, volume_id):
+ self.volume_class(volume_id).detach()
+
+ def loop_volumes(self):
+ volumes = subprocess.Popen(["sudo", "lvs", "--noheadings"], stdout=subprocess.PIPE).communicate()[0].split("\n")
+ for lv in volumes:
+ if len(lv.split(" ")) > 1:
+ yield lv.split(" ")[2]
+
+ def _restart_exports(self):
+ if FLAGS.fake_storage:
+ return
+ utils.runthis("Setting exports to auto: %s", "sudo vblade-persist auto all")
+ utils.runthis("Starting all exports: %s", "sudo vblade-persist start all")
+ utils.runthis("Discovering AOE devices: %s", "sudo aoe-discover")
+
+ def _init_volume_group(self):
+ if FLAGS.fake_storage:
+ return
+ utils.runthis("PVCreate returned: %s", "sudo pvcreate %s" % (FLAGS.storage_dev))
+ utils.runthis("VGCreate returned: %s", "sudo vgcreate %s %s" % (FLAGS.volume_group, FLAGS.storage_dev))
+
+
+class FakeBlockStore(BlockStore):
+ def __init__(self):
+ super(FakeBlockStore, self).__init__()
+
+ def loop_volumes(self):
+ return self.volumes
+
+ def _init_volume_group(self):
+ pass
+
+ def _restart_exports(self):
+ pass
+
+
+class Volume(datastore.RedisModel):
+
+ object_type = 'volume'
+
+ def __init__(self, volume_id=None):
+ self.volume_id = volume_id
+ super(Volume, self).__init__(object_id=volume_id)
+
+ @classmethod
+ def create(cls, size, user_id):
+ volume_id = utils.generate_uid('vol')
+ vol = cls(volume_id=volume_id)
+ #TODO(vish): do we really need to store the volume id as .object_id .volume_id and ['volume_id']?
+ vol['volume_id'] = volume_id
+ vol['node_name'] = FLAGS.storage_name
+ vol['size'] = size
+ vol['user_id'] = user_id
+ vol['availability_zone'] = FLAGS.storage_availability_zone
+ vol["instance_id"] = 'none'
+ vol["mountpoint"] = 'none'
+ vol["create_time"] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
+ vol["attachment_set"] = ''
+ vol.create_lv()
+ vol.setup_export()
+ vol['status'] = "available"
+ vol.save()
+ return vol
+
+ def attach(self, instance_id, mountpoint):
+ self['instance_id'] = instance_id
+ self['mountpoint'] = mountpoint
+ self['status'] = "attached"
+ self.save()
+
+ def detach(self):
+ self['instance_id'] = None
+ self['mountpoint'] = None
+ self['status'] = "available"
+ self.save()
+
+ def destroy(self):
+ try:
+ self._remove_export()
+ except:
+ pass
+ self._delete_lv()
+ super(Volume, self).destroy()
+
+ def create_lv(self):
+ if str(self['size']) == '0':
+ sizestr = '100M'
+ else:
+ sizestr = '%sG' % self['size']
+ utils.runthis("Creating LV: %s", "sudo lvcreate -L %s -n %s %s" % (sizestr, self.volume_id, FLAGS.volume_group))
+
+ def _delete_lv(self):
+ utils.runthis("Removing LV: %s", "sudo lvremove -f %s/%s" % (FLAGS.volume_group, self.volume_id))
+
+ def setup_export(self):
+ (shelf_id, blade_id) = get_next_aoe_numbers()
+ self['aoe_device'] = "e%s.%s" % (shelf_id, blade_id)
+ self.save()
+ utils.runthis("Creating AOE export: %s",
+ "sudo vblade-persist setup %s %s %s /dev/%s/%s" %
+ (shelf_id, blade_id, FLAGS.aoe_eth_dev, FLAGS.volume_group, self.volume_id))
+
+ def _remove_export(self):
+ utils.runthis("Destroyed AOE export: %s", "sudo vblade-persist stop %s %s" % (self.aoe_device[1], self.aoe_device[3]))
+ utils.runthis("Destroyed AOE export: %s", "sudo vblade-persist destroy %s %s" % (self.aoe_device[1], self.aoe_device[3]))
+
+
+class FakeVolume(Volume):
+ def create_lv(self):
+ pass
+
+ def setup_export(self):
+ # TODO(???): This may not be good enough?
+ self['aoe_device'] = 'e%s.%s' % (FLAGS.shelf_id,
+ ''.join([random.choice('0123456789') for x in xrange(3)]))
+ self.save()
+
+ def _remove_export(self):
+ pass
+
+ def _delete_lv(self):
+ pass
+
+def get_next_aoe_numbers():
+ aoes = glob.glob("/var/lib/vblade-persist/vblades/e*")
+ aoes.extend(['e0.0'])
+ blade_id = int(max([int(a.split('.')[1]) for a in aoes])) + 1
+ logging.debug("Next blade_id is %s" % (blade_id))
+ shelf_id = FLAGS.shelf_id
+ return (shelf_id, blade_id)
diff --git a/run_tests.py b/run_tests.py
new file mode 100644
index 0000000000..535a0464a5
--- /dev/null
+++ b/run_tests.py
@@ -0,0 +1,99 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+This is our basic test running framework based on Twisted's Trial.
+
+Usage Examples:
+
+ # to run all the tests
+ python run_tests.py
+
+ # to run a specific test suite imported here
+ python run_tests.py NodeConnectionTestCase
+
+ # to run a specific test imported here
+ python run_tests.py NodeConnectionTestCase.test_reboot
+
+ # to run some test suites elsewhere
+ python run_tests.py nova.tests.node_unittest
+ python run_tests.py nova.tests.node_unittest.NodeConnectionTestCase
+
+Due to our use of multiprocessing it we frequently get some ignorable
+'Interrupted system call' exceptions after test completion.
+
+"""
+import __main__
+import sys
+
+from nova import vendor
+from twisted.scripts import trial as trial_script
+
+from nova import flags
+from nova import twistd
+
+from nova.tests.access_unittest import *
+from nova.tests.api_unittest import *
+from nova.tests.cloud_unittest import *
+from nova.tests.keeper_unittest import *
+from nova.tests.network_unittest import *
+from nova.tests.node_unittest import *
+from nova.tests.objectstore_unittest import *
+from nova.tests.storage_unittest import *
+from nova.tests.users_unittest import *
+from nova.tests.datastore_unittest import *
+
+
+FLAGS = flags.FLAGS
+
+
+if __name__ == '__main__':
+ OptionsClass = twistd.WrapTwistedOptions(trial_script.Options)
+ config = OptionsClass()
+ argv = config.parseOptions()
+
+ FLAGS.verbose = True
+
+ # TODO(termie): these should make a call instead of doing work on import
+ if FLAGS.fake_tests:
+ from nova.tests.fake_flags import *
+ else:
+ from nova.tests.real_flags import *
+
+ if len(argv) == 1 and len(config['tests']) == 0:
+ # If no tests were specified run the ones imported in this file
+ # NOTE(termie): "tests" is not a flag, just some Trial related stuff
+ config['tests'].update(['__main__'])
+ elif len(config['tests']):
+ # If we specified tests check first whether they are in __main__
+ for arg in config['tests']:
+ key = arg.split('.')[0]
+ if hasattr(__main__, key):
+ config['tests'].remove(arg)
+ config['tests'].add('__main__.%s' % arg)
+
+ trial_script._initialDebugSetup(config)
+ trialRunner = trial_script._makeRunner(config)
+ suite = trial_script._getSuite(config)
+ if config['until-failure']:
+ test_result = trialRunner.runUntilFailure(suite)
+ else:
+ test_result = trialRunner.run(suite)
+ if config.tracer:
+ sys.settrace(None)
+ results = config.tracer.results()
+ results.write_results(show_missing=1, summary=False,
+ coverdir=config.coverdir)
+ sys.exit(not test_result.wasSuccessful())
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000000..a25ae0c8cd
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# Copyright [2010] [Anso Labs, LLC]
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import glob
+import os
+import sys
+
+from setuptools import setup, find_packages
+
+srcdir = os.path.join(os.path.dirname(sys.argv[0]), 'src')
+
+setup(name='nova',
+ version='0.3.0',
+ description='None Other, Vaguely Awesome',
+ author='nova-core',
+ author_email='nova-core@googlegroups.com',
+ url='http://novacc.org/',
+ packages = find_packages(),
+
+ )
diff --git a/vendor/IPy.py b/vendor/IPy.py
new file mode 100644
index 0000000000..9fd80ece44
--- /dev/null
+++ b/vendor/IPy.py
@@ -0,0 +1,1304 @@
+"""
+IPy - class and tools for handling of IPv4 and IPv6 addresses and networks.
+See README file for learn how to use IPy.
+
+Further Information might be available at:
+http://software.inl.fr/trac/trac.cgi/wiki/IPy
+"""
+
+# $HeadURL: https://svn.inl.fr/inl-svn/src/tools/ipy/tags/IPy-0.70/IPy.py $
+# $Id: IPy.py 19309 2009-10-29 10:21:13Z haypo $
+
+__rcsid__ = '$Id: IPy.py 19309 2009-10-29 10:21:13Z haypo $'
+__version__ = '0.70'
+
+import types
+
+# Definition of the Ranges for IPv4 IPs
+# this should include www.iana.org/assignments/ipv4-address-space
+# and www.iana.org/assignments/multicast-addresses
+IPv4ranges = {
+ '0': 'PUBLIC', # fall back
+ '00000000': 'PRIVATE', # 0/8
+ '00001010': 'PRIVATE', # 10/8
+ '01111111': 'PRIVATE', # 127.0/8
+ '1': 'PUBLIC', # fall back
+ '1010100111111110': 'PRIVATE', # 169.254/16
+ '101011000001': 'PRIVATE', # 172.16/12
+ '1100000010101000': 'PRIVATE', # 192.168/16
+ '11011111': 'RESERVED', # 223/8
+ '111': 'RESERVED' # 224/3
+ }
+
+# Definition of the Ranges for IPv6 IPs
+# see also www.iana.org/assignments/ipv6-address-space,
+# www.iana.org/assignments/ipv6-tla-assignments,
+# www.iana.org/assignments/ipv6-multicast-addresses,
+# www.iana.org/assignments/ipv6-anycast-addresses
+IPv6ranges = {
+ '00000000' : 'RESERVED', # ::/8
+ '00000001' : 'UNASSIGNED', # 100::/8
+ '0000001' : 'NSAP', # 200::/7
+ '0000010' : 'IPX', # 400::/7
+ '0000011' : 'UNASSIGNED', # 600::/7
+ '00001' : 'UNASSIGNED', # 800::/5
+ '0001' : 'UNASSIGNED', # 1000::/4
+ '0010000000000000' : 'RESERVED', # 2000::/16 Reserved
+ '0010000000000001' : 'ASSIGNABLE', # 2001::/16 Sub-TLA Assignments [RFC2450]
+ '00100000000000010000000': 'ASSIGNABLE IANA', # 2001:0000::/29 - 2001:01F8::/29 IANA
+ '00100000000000010000001': 'ASSIGNABLE APNIC', # 2001:0200::/29 - 2001:03F8::/29 APNIC
+ '00100000000000010000010': 'ASSIGNABLE ARIN', # 2001:0400::/29 - 2001:05F8::/29 ARIN
+ '00100000000000010000011': 'ASSIGNABLE RIPE', # 2001:0600::/29 - 2001:07F8::/29 RIPE NCC
+ '0010000000000010' : '6TO4', # 2002::/16 "6to4" [RFC3056]
+ '0011111111111110' : '6BONE', # 3FFE::/16 6bone Testing [RFC2471]
+ '0011111111111111' : 'RESERVED', # 3FFF::/16 Reserved
+ '010' : 'GLOBAL-UNICAST', # 4000::/3
+ '011' : 'UNASSIGNED', # 6000::/3
+ '100' : 'GEO-UNICAST', # 8000::/3
+ '101' : 'UNASSIGNED', # A000::/3
+ '110' : 'UNASSIGNED', # C000::/3
+ '1110' : 'UNASSIGNED', # E000::/4
+ '11110' : 'UNASSIGNED', # F000::/5
+ '111110' : 'UNASSIGNED', # F800::/6
+ '1111110' : 'UNASSIGNED', # FC00::/7
+ '111111100' : 'UNASSIGNED', # FE00::/9
+ '1111111010' : 'LINKLOCAL', # FE80::/10
+ '1111111011' : 'SITELOCAL', # FEC0::/10
+ '11111111' : 'MULTICAST', # FF00::/8
+ '0' * 96 : 'IPV4COMP', # ::/96
+ '0' * 80 + '1' * 16 : 'IPV4MAP', # ::FFFF:0:0/96
+ '0' * 128 : 'UNSPECIFIED', # ::/128
+ '0' * 127 + '1' : 'LOOPBACK' # ::1/128
+ }
+
+
+class IPint:
+ """Handling of IP addresses returning integers.
+
+ Use class IP instead because some features are not implemented for
+ IPint."""
+
+ def __init__(self, data, ipversion=0, make_net=0):
+ """Create an instance of an IP object.
+
+ Data can be a network specification or a single IP. IP
+ addresses can be specified in all forms understood by
+ parseAddress(). The size of a network can be specified as
+
+ /prefixlen a.b.c.0/24 2001:658:22a:cafe::/64
+ -lastIP a.b.c.0-a.b.c.255 2001:658:22a:cafe::-2001:658:22a:cafe:ffff:ffff:ffff:ffff
+ /decimal netmask a.b.c.d/255.255.255.0 not supported for IPv6
+
+ If no size specification is given a size of 1 address (/32 for
+ IPv4 and /128 for IPv6) is assumed.
+
+ If make_net is True, an IP address will be transformed into the network
+ address by applying the specified netmask.
+
+ >>> print IP('127.0.0.0/8')
+ 127.0.0.0/8
+ >>> print IP('127.0.0.0/255.0.0.0')
+ 127.0.0.0/8
+ >>> print IP('127.0.0.0-127.255.255.255')
+ 127.0.0.0/8
+ >>> print IP('127.0.0.1/255.0.0.0', make_net=True)
+ 127.0.0.0/8
+
+ See module documentation for more examples.
+ """
+
+ # Print no Prefixlen for /32 and /128
+ self.NoPrefixForSingleIp = 1
+
+ # Do we want prefix printed by default? see _printPrefix()
+ self.WantPrefixLen = None
+
+ netbits = 0
+ prefixlen = -1
+
+ # handling of non string values in constructor
+ if type(data) == types.IntType or type(data) == types.LongType:
+ self.ip = long(data)
+ if ipversion == 0:
+ if self.ip < 0x100000000L:
+ ipversion = 4
+ else:
+ ipversion = 6
+ if ipversion == 4:
+ prefixlen = 32
+ elif ipversion == 6:
+ prefixlen = 128
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+ self._ipversion = ipversion
+ self._prefixlen = prefixlen
+ # handle IP instance as an parameter
+ elif isinstance(data, IPint):
+ self._ipversion = data._ipversion
+ self._prefixlen = data._prefixlen
+ self.ip = data.ip
+ else:
+ # TODO: refactor me!
+ # splitting of a string into IP and prefixlen et. al.
+ x = data.split('-')
+ if len(x) == 2:
+ # a.b.c.0-a.b.c.255 specification ?
+ (ip, last) = x
+ (self.ip, parsedVersion) = parseAddress(ip)
+ if parsedVersion != 4:
+ raise ValueError, "first-last notation only allowed for IPv4"
+ (last, lastversion) = parseAddress(last)
+ if lastversion != 4:
+ raise ValueError, "last address should be IPv4, too"
+ if last < self.ip:
+ raise ValueError, "last address should be larger than first"
+ size = last - self.ip
+ netbits = _count1Bits(size)
+ # make sure the broadcast is the same as the last ip
+ # otherwise it will return /16 for something like:
+ # 192.168.0.0-192.168.191.255
+ if IP('%s/%s' % (ip, 32-netbits)).broadcast().int() != last:
+ raise ValueError, \
+ "the range %s is not on a network boundary." % data
+ elif len(x) == 1:
+ x = data.split('/')
+ # if no prefix is given use defaults
+ if len(x) == 1:
+ ip = x[0]
+ prefixlen = -1
+ elif len(x) > 2:
+ raise ValueError, "only one '/' allowed in IP Address"
+ else:
+ (ip, prefixlen) = x
+ if prefixlen.find('.') != -1:
+ # check if the user might have used a netmask like
+ # a.b.c.d/255.255.255.0
+ (netmask, vers) = parseAddress(prefixlen)
+ if vers != 4:
+ raise ValueError, "netmask must be IPv4"
+ prefixlen = _netmaskToPrefixlen(netmask)
+ elif len(x) > 2:
+ raise ValueError, "only one '-' allowed in IP Address"
+ else:
+ raise ValueError, "can't parse"
+
+ (self.ip, parsedVersion) = parseAddress(ip)
+ if ipversion == 0:
+ ipversion = parsedVersion
+ if prefixlen == -1:
+ if ipversion == 4:
+ prefixlen = 32 - netbits
+ elif ipversion == 6:
+ prefixlen = 128 - netbits
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+ self._ipversion = ipversion
+ self._prefixlen = int(prefixlen)
+
+ if make_net:
+ self.ip = self.ip & _prefixlenToNetmask(self._prefixlen, self._ipversion)
+
+ if not _checkNetaddrWorksWithPrefixlen(self.ip,
+ self._prefixlen, self._ipversion):
+ raise ValueError, "%s has invalid prefix length (%s)" % (repr(self), self._prefixlen)
+
+ def int(self):
+ """Return the first / base / network addess as an (long) integer.
+
+ The same as IP[0].
+
+ >>> "%X" % IP('10.0.0.0/8').int()
+ 'A000000'
+ """
+ return self.ip
+
+ def version(self):
+ """Return the IP version of this Object.
+
+ >>> IP('10.0.0.0/8').version()
+ 4
+ >>> IP('::1').version()
+ 6
+ """
+ return self._ipversion
+
+ def prefixlen(self):
+ """Returns Network Prefixlen.
+
+ >>> IP('10.0.0.0/8').prefixlen()
+ 8
+ """
+ return self._prefixlen
+
+ def net(self):
+ """
+ Return the base (first) address of a network as an (long) integer.
+ """
+ return self.int()
+
+ def broadcast(self):
+ """
+ Return the broadcast (last) address of a network as an (long) integer.
+
+ The same as IP[-1]."""
+ return self.int() + self.len() - 1
+
+ def _printPrefix(self, want):
+ """Prints Prefixlen/Netmask.
+
+ Not really. In fact it is our universal Netmask/Prefixlen printer.
+ This is considered an internal function.
+
+ want == 0 / None don't return anything 1.2.3.0
+ want == 1 /prefix 1.2.3.0/24
+ want == 2 /netmask 1.2.3.0/255.255.255.0
+ want == 3 -lastip 1.2.3.0-1.2.3.255
+ """
+
+ if (self._ipversion == 4 and self._prefixlen == 32) or \
+ (self._ipversion == 6 and self._prefixlen == 128):
+ if self.NoPrefixForSingleIp:
+ want = 0
+ if want == None:
+ want = self.WantPrefixLen
+ if want == None:
+ want = 1
+ if want:
+ if want == 2:
+ # this should work with IP and IPint
+ netmask = self.netmask()
+ if type(netmask) != types.IntType \
+ and type(netmask) != types.LongType:
+ netmask = netmask.int()
+ return "/%s" % (intToIp(netmask, self._ipversion))
+ elif want == 3:
+ return "-%s" % (intToIp(self.ip + self.len() - 1, self._ipversion))
+ else:
+ # default
+ return "/%d" % (self._prefixlen)
+ else:
+ return ''
+
+ # We have different flavours to convert to:
+ # strFullsize 127.0.0.1 2001:0658:022a:cafe:0200:c0ff:fe8d:08fa
+ # strNormal 127.0.0.1 2001:658:22a:cafe:200:c0ff:fe8d:08fa
+ # strCompressed 127.0.0.1 2001:658:22a:cafe::1
+ # strHex 0x7F000001L 0x20010658022ACAFE0200C0FFFE8D08FA
+ # strDec 2130706433 42540616829182469433547974687817795834
+
+ def strBin(self, wantprefixlen = None):
+ """Return a string representation as a binary value.
+
+ >>> print IP('127.0.0.1').strBin()
+ 01111111000000000000000000000001
+ """
+
+
+ if self._ipversion == 4:
+ bits = 32
+ elif self._ipversion == 6:
+ bits = 128
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 0
+ ret = _intToBin(self.ip)
+ return '0' * (bits - len(ret)) + ret + self._printPrefix(wantprefixlen)
+
+ def strCompressed(self, wantprefixlen = None):
+ """Return a string representation in compressed format using '::' Notation.
+
+ >>> IP('127.0.0.1').strCompressed()
+ '127.0.0.1'
+ >>> IP('2001:0658:022a:cafe:0200::1').strCompressed()
+ '2001:658:22a:cafe:200::1'
+ >>> IP('ffff:ffff:ffff:ffff:ffff:f:f:fffc/127').strCompressed()
+ 'ffff:ffff:ffff:ffff:ffff:f:f:fffc/127'
+ """
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 1
+
+ if self._ipversion == 4:
+ return self.strFullsize(wantprefixlen)
+ else:
+ if self.ip >> 32 == 0xffff:
+ ipv4 = intToIp(self.ip & 0xffffffff, 4)
+ text = "::ffff:" + ipv4 + self._printPrefix(wantprefixlen)
+ return text
+ # find the longest sequence of '0'
+ hextets = [int(x, 16) for x in self.strFullsize(0).split(':')]
+ # every element of followingzeros will contain the number of zeros
+ # following the corresponding element of hextets
+ followingzeros = [0] * 8
+ for i in range(len(hextets)):
+ followingzeros[i] = _countFollowingZeros(hextets[i:])
+ # compressionpos is the position where we can start removing zeros
+ compressionpos = followingzeros.index(max(followingzeros))
+ if max(followingzeros) > 1:
+ # genererate string with the longest number of zeros cut out
+ # now we need hextets as strings
+ hextets = [x for x in self.strNormal(0).split(':')]
+ while compressionpos < len(hextets) and hextets[compressionpos] == '0':
+ del(hextets[compressionpos])
+ hextets.insert(compressionpos, '')
+ if compressionpos + 1 >= len(hextets):
+ hextets.append('')
+ if compressionpos == 0:
+ hextets = [''] + hextets
+ return ':'.join(hextets) + self._printPrefix(wantprefixlen)
+ else:
+ return self.strNormal(0) + self._printPrefix(wantprefixlen)
+
+ def strNormal(self, wantprefixlen = None):
+ """Return a string representation in the usual format.
+
+ >>> print IP('127.0.0.1').strNormal()
+ 127.0.0.1
+ >>> print IP('2001:0658:022a:cafe:0200::1').strNormal()
+ 2001:658:22a:cafe:200:0:0:1
+ """
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 1
+
+ if self._ipversion == 4:
+ ret = self.strFullsize(0)
+ elif self._ipversion == 6:
+ ret = ':'.join([hex(x)[2:] for x in [int(x, 16) for x in self.strFullsize(0).split(':')]])
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+
+
+
+ return ret + self._printPrefix(wantprefixlen)
+
+ def strFullsize(self, wantprefixlen = None):
+ """Return a string representation in the non-mangled format.
+
+ >>> print IP('127.0.0.1').strFullsize()
+ 127.0.0.1
+ >>> print IP('2001:0658:022a:cafe:0200::1').strFullsize()
+ 2001:0658:022a:cafe:0200:0000:0000:0001
+ """
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 1
+
+ return intToIp(self.ip, self._ipversion).lower() + self._printPrefix(wantprefixlen)
+
+ def strHex(self, wantprefixlen = None):
+ """Return a string representation in hex format in lower case.
+
+ >>> IP('127.0.0.1').strHex()
+ '0x7f000001'
+ >>> IP('2001:0658:022a:cafe:0200::1').strHex()
+ '0x20010658022acafe0200000000000001'
+ """
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 0
+
+ x = hex(self.ip)
+ if x[-1] == 'L':
+ x = x[:-1]
+ return x.lower() + self._printPrefix(wantprefixlen)
+
+ def strDec(self, wantprefixlen = None):
+ """Return a string representation in decimal format.
+
+ >>> print IP('127.0.0.1').strDec()
+ 2130706433
+ >>> print IP('2001:0658:022a:cafe:0200::1').strDec()
+ 42540616829182469433547762482097946625
+ """
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 0
+
+ x = str(self.ip)
+ if x[-1] == 'L':
+ x = x[:-1]
+ return x + self._printPrefix(wantprefixlen)
+
+ def iptype(self):
+ """Return a description of the IP type ('PRIVATE', 'RESERVERD', etc).
+
+ >>> print IP('127.0.0.1').iptype()
+ PRIVATE
+ >>> print IP('192.168.1.1').iptype()
+ PRIVATE
+ >>> print IP('195.185.1.2').iptype()
+ PUBLIC
+ >>> print IP('::1').iptype()
+ LOOPBACK
+ >>> print IP('2001:0658:022a:cafe:0200::1').iptype()
+ ASSIGNABLE RIPE
+
+ The type information for IPv6 is out of sync with reality.
+ """
+
+ # this could be greatly improved
+
+ if self._ipversion == 4:
+ iprange = IPv4ranges
+ elif self._ipversion == 6:
+ iprange = IPv6ranges
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+
+ bits = self.strBin()
+ for i in range(len(bits), 0, -1):
+ if iprange.has_key(bits[:i]):
+ return iprange[bits[:i]]
+ return "unknown"
+
+
+ def netmask(self):
+ """Return netmask as an integer.
+
+ >>> "%X" % IP('195.185.0.0/16').netmask().int()
+ 'FFFF0000'
+ """
+
+ # TODO: unify with prefixlenToNetmask?
+ if self._ipversion == 4:
+ locallen = 32 - self._prefixlen
+ elif self._ipversion == 6:
+ locallen = 128 - self._prefixlen
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+
+ return ((2L ** self._prefixlen) - 1) << locallen
+
+
+ def strNetmask(self):
+ """Return netmask as an string. Mostly useful for IPv6.
+
+ >>> print IP('195.185.0.0/16').strNetmask()
+ 255.255.0.0
+ >>> print IP('2001:0658:022a:cafe::0/64').strNetmask()
+ /64
+ """
+
+ # TODO: unify with prefixlenToNetmask?
+ if self._ipversion == 4:
+ locallen = 32 - self._prefixlen
+ return intToIp(((2L ** self._prefixlen) - 1) << locallen, 4)
+ elif self._ipversion == 6:
+ locallen = 128 - self._prefixlen
+ return "/%d" % self._prefixlen
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+
+ def len(self):
+ """Return the length of a subnet.
+
+ >>> print IP('195.185.1.0/28').len()
+ 16
+ >>> print IP('195.185.1.0/24').len()
+ 256
+ """
+
+ if self._ipversion == 4:
+ locallen = 32 - self._prefixlen
+ elif self._ipversion == 6:
+ locallen = 128 - self._prefixlen
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+
+ return 2L ** locallen
+
+
+ def __nonzero__(self):
+ """All IPy objects should evaluate to true in boolean context.
+ Ordinarily they do, but if handling a default route expressed as
+ 0.0.0.0/0, the __len__() of the object becomes 0, which is used
+ as the boolean value of the object.
+ """
+ return 1
+
+
+ def __len__(self):
+ """Return the length of a subnet.
+
+ Called to implement the built-in function len().
+ It breaks with IPv6 Networks. Anybody knows how to fix this."""
+
+ # Python < 2.2 has this silly restriction which breaks IPv6
+ # how about Python >= 2.2 ... ouch - it persists!
+
+ return int(self.len())
+
+
+ def __getitem__(self, key):
+ """Called to implement evaluation of self[key].
+
+ >>> ip=IP('127.0.0.0/30')
+ >>> for x in ip:
+ ... print repr(x)
+ ...
+ IP('127.0.0.0')
+ IP('127.0.0.1')
+ IP('127.0.0.2')
+ IP('127.0.0.3')
+ >>> ip[2]
+ IP('127.0.0.2')
+ >>> ip[-1]
+ IP('127.0.0.3')
+ """
+
+ if type(key) != types.IntType and type(key) != types.LongType:
+ raise TypeError
+ if key < 0:
+ if abs(key) <= self.len():
+ key = self.len() - abs(key)
+ else:
+ raise IndexError
+ else:
+ if key >= self.len():
+ raise IndexError
+
+ return self.ip + long(key)
+
+
+
+ def __contains__(self, item):
+ """Called to implement membership test operators.
+
+ Should return true if item is in self, false otherwise. Item
+ can be other IP-objects, strings or ints.
+
+ >>> IP('195.185.1.1').strHex()
+ '0xc3b90101'
+ >>> 0xC3B90101L in IP('195.185.1.0/24')
+ 1
+ >>> '127.0.0.1' in IP('127.0.0.0/24')
+ 1
+ >>> IP('127.0.0.0/24') in IP('127.0.0.0/25')
+ 0
+ """
+
+ item = IP(item)
+ if item.ip >= self.ip and item.ip < self.ip + self.len() - item.len() + 1:
+ return 1
+ else:
+ return 0
+
+
+ def overlaps(self, item):
+ """Check if two IP address ranges overlap.
+
+ Returns 0 if the two ranges don't overlap, 1 if the given
+ range overlaps at the end and -1 if it does at the beginning.
+
+ >>> IP('192.168.0.0/23').overlaps('192.168.1.0/24')
+ 1
+ >>> IP('192.168.0.0/23').overlaps('192.168.1.255')
+ 1
+ >>> IP('192.168.0.0/23').overlaps('192.168.2.0')
+ 0
+ >>> IP('192.168.1.0/24').overlaps('192.168.0.0/23')
+ -1
+ """
+
+ item = IP(item)
+ if item.ip >= self.ip and item.ip < self.ip + self.len():
+ return 1
+ elif self.ip >= item.ip and self.ip < item.ip + item.len():
+ return -1
+ else:
+ return 0
+
+
+ def __str__(self):
+ """Dispatch to the prefered String Representation.
+
+ Used to implement str(IP)."""
+
+ return self.strCompressed()
+
+
+ def __repr__(self):
+ """Print a representation of the Object.
+
+ Used to implement repr(IP). Returns a string which evaluates
+ to an identical Object (without the wantprefixlen stuff - see
+ module docstring.
+
+ >>> print repr(IP('10.0.0.0/24'))
+ IP('10.0.0.0/24')
+ """
+
+ return("IPint('%s')" % (self.strCompressed(1)))
+
+
+ def __cmp__(self, other):
+ """Called by comparison operations.
+
+ Should return a negative integer if self < other, zero if self
+ == other, a positive integer if self > other.
+
+ Networks with different prefixlen are considered non-equal.
+ Networks with the same prefixlen and differing addresses are
+ considered non equal but are compared by their base address
+ integer value to aid sorting of IP objects.
+
+ The version of Objects is not put into consideration.
+
+ >>> IP('10.0.0.0/24') > IP('10.0.0.0')
+ 1
+ >>> IP('10.0.0.0/24') < IP('10.0.0.0')
+ 0
+ >>> IP('10.0.0.0/24') < IP('12.0.0.0/24')
+ 1
+ >>> IP('10.0.0.0/24') > IP('12.0.0.0/24')
+ 0
+
+ """
+
+ # Im not really sure if this is "the right thing to do"
+ if self._prefixlen < other.prefixlen():
+ return (other.prefixlen() - self._prefixlen)
+ elif self._prefixlen > other.prefixlen():
+
+ # Fixed bySamuel Krempp <krempp@crans.ens-cachan.fr>:
+
+ # The bug is quite obvious really (as 99% bugs are once
+ # spotted, isn't it ? ;-) Because of precedence of
+ # multiplication by -1 over the substraction, prefixlen
+ # differences were causing the __cmp__ function to always
+ # return positive numbers, thus the function was failing
+ # the basic assumptions for a __cmp__ function.
+
+ # Namely we could have (a > b AND b > a), when the
+ # prefixlen of a and b are different. (eg let
+ # a=IP("1.0.0.0/24"); b=IP("2.0.0.0/16");) thus, anything
+ # could happen when launching a sort algorithm..
+ # everything's in order with the trivial, attached patch.
+
+ return (self._prefixlen - other.prefixlen()) * -1
+ else:
+ if self.ip < other.ip:
+ return -1
+ elif self.ip > other.ip:
+ return 1
+ elif self._ipversion != other._ipversion:
+ # IP('0.0.0.0'), IP('::/0')
+ return cmp(self._ipversion, other._ipversion)
+ else:
+ return 0
+
+
+ def __hash__(self):
+ """Called for the key object for dictionary operations, and by
+ the built-in function hash(). Should return a 32-bit integer
+ usable as a hash value for dictionary operations. The only
+ required property is that objects which compare equal have the
+ same hash value
+
+ >>> IP('10.0.0.0/24').__hash__()
+ -167772185
+ """
+
+ thehash = int(-1)
+ ip = self.ip
+ while ip > 0:
+ thehash = thehash ^ (ip & 0x7fffffff)
+ ip = ip >> 32
+ thehash = thehash ^ self._prefixlen
+ return int(thehash)
+
+
+class IP(IPint):
+ """Class for handling IP addresses and networks."""
+
+ def net(self):
+ """Return the base (first) address of a network as an IP object.
+
+ The same as IP[0].
+
+ >>> IP('10.0.0.0/8').net()
+ IP('10.0.0.0')
+ """
+ return IP(IPint.net(self), ipversion=self._ipversion)
+
+ def broadcast(self):
+ """Return the broadcast (last) address of a network as an IP object.
+
+ The same as IP[-1].
+
+ >>> IP('10.0.0.0/8').broadcast()
+ IP('10.255.255.255')
+ """
+ return IP(IPint.broadcast(self))
+
+ def netmask(self):
+ """Return netmask as an IP object.
+
+ >>> IP('10.0.0.0/8').netmask()
+ IP('255.0.0.0')
+ """
+ return IP(IPint.netmask(self))
+
+
+ def reverseNames(self):
+ """Return a list with values forming the reverse lookup.
+
+ >>> IP('213.221.113.87/32').reverseNames()
+ ['87.113.221.213.in-addr.arpa.']
+ >>> IP('213.221.112.224/30').reverseNames()
+ ['224.112.221.213.in-addr.arpa.', '225.112.221.213.in-addr.arpa.', '226.112.221.213.in-addr.arpa.', '227.112.221.213.in-addr.arpa.']
+ >>> IP('127.0.0.0/24').reverseNames()
+ ['0.0.127.in-addr.arpa.']
+ >>> IP('127.0.0.0/23').reverseNames()
+ ['0.0.127.in-addr.arpa.', '1.0.127.in-addr.arpa.']
+ >>> IP('127.0.0.0/16').reverseNames()
+ ['0.127.in-addr.arpa.']
+ >>> IP('127.0.0.0/15').reverseNames()
+ ['0.127.in-addr.arpa.', '1.127.in-addr.arpa.']
+ >>> IP('128.0.0.0/8').reverseNames()
+ ['128.in-addr.arpa.']
+ >>> IP('128.0.0.0/7').reverseNames()
+ ['128.in-addr.arpa.', '129.in-addr.arpa.']
+ >>> IP('::1:2').reverseNames()
+ ['2.0.0.0.1.ip6.arpa.']
+ """
+
+ if self._ipversion == 4:
+ ret = []
+ # TODO: Refactor. Add support for IPint objects
+ if self.len() < 2**8:
+ for x in self:
+ ret.append(x.reverseName())
+ elif self.len() < 2**16L:
+ for i in range(0, self.len(), 2**8):
+ ret.append(self[i].reverseName()[2:])
+ elif self.len() < 2**24L:
+ for i in range(0, self.len(), 2**16):
+ ret.append(self[i].reverseName()[4:])
+ else:
+ for i in range(0, self.len(), 2**24):
+ ret.append(self[i].reverseName()[6:])
+ return ret
+ elif self._ipversion == 6:
+ s = hex(self.ip)[2:].lower()
+ if s[-1] == 'l':
+ s = s[:-1]
+ if self._prefixlen % 4 != 0:
+ raise NotImplementedError, "can't create IPv6 reverse names at sub nibble level"
+ s = list(s)
+ s.reverse()
+ s = '.'.join(s)
+ first_nibble_index = int(32 - (self._prefixlen / 4)) * 2
+ return ["%s.ip6.arpa." % s[first_nibble_index:]]
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+
+
+
+ def reverseName(self):
+ """Return the value for reverse lookup/PTR records as RFC 2317 look alike.
+
+ RFC 2317 is an ugly hack which only works for sub-/24 e.g. not
+ for /23. Do not use it. Better set up a zone for every
+ address. See reverseName for a way to achieve that.
+
+ >>> print IP('195.185.1.1').reverseName()
+ 1.1.185.195.in-addr.arpa.
+ >>> print IP('195.185.1.0/28').reverseName()
+ 0-15.1.185.195.in-addr.arpa.
+ >>> IP('::1:2').reverseName()
+ '2.0.0.0.1.ip6.arpa.'
+ """
+
+ if self._ipversion == 4:
+ s = self.strFullsize(0)
+ s = s.split('.')
+ s.reverse()
+ first_byte_index = int(4 - (self._prefixlen / 8))
+ if self._prefixlen % 8 != 0:
+ nibblepart = "%s-%s" % (s[3-(self._prefixlen / 8)], intToIp(self.ip + self.len() - 1, 4).split('.')[-1])
+ if nibblepart[-1] == 'l':
+ nibblepart = nibblepart[:-1]
+ nibblepart += '.'
+ else:
+ nibblepart = ""
+
+ s = '.'.join(s[first_byte_index:])
+ return "%s%s.in-addr.arpa." % (nibblepart, s)
+
+ elif self._ipversion == 6:
+ s = hex(self.ip)[2:].lower()
+ if s[-1] == 'l':
+ s = s[:-1]
+ if self._prefixlen % 4 != 0:
+ nibblepart = "%s-%s" % (s[self._prefixlen:], hex(self.ip + self.len() - 1)[2:].lower())
+ if nibblepart[-1] == 'l':
+ nibblepart = nibblepart[:-1]
+ nibblepart += '.'
+ else:
+ nibblepart = ""
+ s = list(s)
+ s.reverse()
+ s = '.'.join(s)
+ first_nibble_index = int(32 - (self._prefixlen / 4)) * 2
+ return "%s%s.ip6.arpa." % (nibblepart, s[first_nibble_index:])
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+
+ def make_net(self, netmask):
+ """Transform a single IP address into a network specification by
+ applying the given netmask.
+
+ Returns a new IP instance.
+
+ >>> print IP('127.0.0.1').make_net('255.0.0.0')
+ 127.0.0.0/8
+ """
+ if '/' in str(netmask):
+ raise ValueError, "invalid netmask (%s)" % netmask
+ return IP('%s/%s' % (self, netmask), make_net=True)
+
+ def __getitem__(self, key):
+ """Called to implement evaluation of self[key].
+
+ >>> ip=IP('127.0.0.0/30')
+ >>> for x in ip:
+ ... print str(x)
+ ...
+ 127.0.0.0
+ 127.0.0.1
+ 127.0.0.2
+ 127.0.0.3
+ >>> print str(ip[2])
+ 127.0.0.2
+ >>> print str(ip[-1])
+ 127.0.0.3
+ """
+ return IP(IPint.__getitem__(self, key))
+
+ def __repr__(self):
+ """Print a representation of the Object.
+
+ >>> IP('10.0.0.0/8')
+ IP('10.0.0.0/8')
+ """
+
+ return("IP('%s')" % (self.strCompressed(1)))
+
+ def __add__(self, other):
+ """Emulate numeric objects through network aggregation"""
+ if self.prefixlen() != other.prefixlen():
+ raise ValueError, "Only networks with the same prefixlen can be added."
+ if self.prefixlen < 1:
+ raise ValueError, "Networks with a prefixlen longer than /1 can't be added."
+ if self.version() != other.version():
+ raise ValueError, "Only networks with the same IP version can be added."
+ if self > other:
+ # fixed by Skinny Puppy <skin_pup-IPy@happypoo.com>
+ return other.__add__(self)
+ else:
+ ret = IP(self.int())
+ ret._prefixlen = self.prefixlen() - 1
+ return ret
+
+
+def _parseAddressIPv6(ipstr):
+ """
+ Internal function used by parseAddress() to parse IPv6 address with ':'.
+
+ >>> _parseAddressIPv6('::')
+ 0L
+ >>> _parseAddressIPv6('::1')
+ 1L
+ >>> _parseAddressIPv6('0:0:0:0:0:0:0:1')
+ 1L
+ >>> _parseAddressIPv6('0:0:0::0:0:1')
+ 1L
+ >>> _parseAddressIPv6('0:0:0:0:0:0:0:0')
+ 0L
+ >>> _parseAddressIPv6('0:0:0::0:0:0')
+ 0L
+
+ >>> _parseAddressIPv6('FEDC:BA98:7654:3210:FEDC:BA98:7654:3210')
+ 338770000845734292534325025077361652240L
+ >>> _parseAddressIPv6('1080:0000:0000:0000:0008:0800:200C:417A')
+ 21932261930451111902915077091070067066L
+ >>> _parseAddressIPv6('1080:0:0:0:8:800:200C:417A')
+ 21932261930451111902915077091070067066L
+ >>> _parseAddressIPv6('1080:0::8:800:200C:417A')
+ 21932261930451111902915077091070067066L
+ >>> _parseAddressIPv6('1080::8:800:200C:417A')
+ 21932261930451111902915077091070067066L
+ >>> _parseAddressIPv6('FF01:0:0:0:0:0:0:43')
+ 338958331222012082418099330867817087043L
+ >>> _parseAddressIPv6('FF01:0:0::0:0:43')
+ 338958331222012082418099330867817087043L
+ >>> _parseAddressIPv6('FF01::43')
+ 338958331222012082418099330867817087043L
+ >>> _parseAddressIPv6('0:0:0:0:0:0:13.1.68.3')
+ 218186755L
+ >>> _parseAddressIPv6('::13.1.68.3')
+ 218186755L
+ >>> _parseAddressIPv6('0:0:0:0:0:FFFF:129.144.52.38')
+ 281472855454758L
+ >>> _parseAddressIPv6('::FFFF:129.144.52.38')
+ 281472855454758L
+ >>> _parseAddressIPv6('1080:0:0:0:8:800:200C:417A')
+ 21932261930451111902915077091070067066L
+ >>> _parseAddressIPv6('1080::8:800:200C:417A')
+ 21932261930451111902915077091070067066L
+ >>> _parseAddressIPv6('::1:2:3:4:5:6')
+ 1208962713947218704138246L
+ >>> _parseAddressIPv6('1:2:3:4:5:6::')
+ 5192455318486707404433266432802816L
+ """
+
+ # Split string into a list, example:
+ # '1080:200C::417A' => ['1080', '200C', '417A'] and fill_pos=2
+ # and fill_pos is the position of '::' in the list
+ items = []
+ index = 0
+ fill_pos = None
+ while index < len(ipstr):
+ text = ipstr[index:]
+ if text.startswith("::"):
+ if fill_pos is not None:
+ # Invalid IPv6, eg. '1::2::'
+ raise ValueError("%r: Invalid IPv6 address: more than one '::'" % ipstr)
+ fill_pos = len(items)
+ index += 2
+ continue
+ pos = text.find(':')
+ if pos == 0:
+ # Invalid IPv6, eg. '1::2:'
+ raise ValueError("%r: Invalid IPv6 address" % ipstr)
+ if pos != -1:
+ items.append(text[:pos])
+ if text[pos:pos+2] == "::":
+ index += pos
+ else:
+ index += pos+1
+
+ if index == len(ipstr):
+ # Invalid IPv6, eg. '1::2:'
+ raise ValueError("%r: Invalid IPv6 address" % ipstr)
+ else:
+ items.append(text)
+ break
+
+ if items and '.' in items[-1]:
+ # IPv6 ending with IPv4 like '::ffff:192.168.0.1'
+ if not (fill_pos <= len(items)-1):
+ # Invalid IPv6: 'ffff:192.168.0.1::'
+ raise ValueError("%r: Invalid IPv6 address: '::' after IPv4" % ipstr)
+ value = parseAddress(items[-1])[0]
+ items = items[:-1] + ["%04x" % (value >> 16), "%04x" % (value & 0xffff)]
+
+ # Expand fill_pos to fill with '0'
+ # ['1','2'] with fill_pos=1 => ['1', '0', '0', '0', '0', '0', '0', '2']
+ if fill_pos is not None:
+ diff = 8 - len(items)
+ if diff <= 0:
+ raise ValueError("%r: Invalid IPv6 address: '::' is not needed" % ipstr)
+ items = items[:fill_pos] + ['0']*diff + items[fill_pos:]
+
+ # Here we have a list of 8 strings
+ if len(items) != 8:
+ # Invalid IPv6, eg. '1:2:3'
+ raise ValueError("%r: Invalid IPv6 address: should have 8 hextets" % ipstr)
+
+ # Convert strings to long integer
+ value = 0L
+ index = 0
+ for item in items:
+ try:
+ item = int(item, 16)
+ error = not(0 <= item <= 0xFFFF)
+ except ValueError:
+ error = True
+ if error:
+ raise ValueError("%r: Invalid IPv6 address: invalid hexlet %r" % (ipstr, item))
+ value = (value << 16) + item
+ index += 1
+ return value
+
+def parseAddress(ipstr):
+ """
+ Parse a string and return the corresponding IP address (as integer)
+ and a guess of the IP version.
+
+ Following address formats are recognized:
+
+ >>> parseAddress('0x0123456789abcdef') # IPv4 if <= 0xffffffff else IPv6
+ (81985529216486895L, 6)
+ >>> parseAddress('123.123.123.123') # IPv4
+ (2071690107L, 4)
+ >>> parseAddress('123.123') # 0-padded IPv4
+ (2071658496L, 4)
+ >>> parseAddress('1080:0000:0000:0000:0008:0800:200C:417A')
+ (21932261930451111902915077091070067066L, 6)
+ >>> parseAddress('1080:0:0:0:8:800:200C:417A')
+ (21932261930451111902915077091070067066L, 6)
+ >>> parseAddress('1080:0::8:800:200C:417A')
+ (21932261930451111902915077091070067066L, 6)
+ >>> parseAddress('::1')
+ (1L, 6)
+ >>> parseAddress('::')
+ (0L, 6)
+ >>> parseAddress('0:0:0:0:0:FFFF:129.144.52.38')
+ (281472855454758L, 6)
+ >>> parseAddress('::13.1.68.3')
+ (218186755L, 6)
+ >>> parseAddress('::FFFF:129.144.52.38')
+ (281472855454758L, 6)
+ """
+
+ if ipstr.startswith('0x'):
+ ret = long(ipstr[2:], 16)
+ if ret > 0xffffffffffffffffffffffffffffffffL:
+ raise ValueError, "%r: IP Address can't be bigger than 2^128" % (ipstr)
+ if ret < 0x100000000L:
+ return (ret, 4)
+ else:
+ return (ret, 6)
+
+ if ipstr.find(':') != -1:
+ return (_parseAddressIPv6(ipstr), 6)
+
+ elif len(ipstr) == 32:
+ # assume IPv6 in pure hexadecimal notation
+ return (long(ipstr, 16), 6)
+
+ elif ipstr.find('.') != -1 or (len(ipstr) < 4 and int(ipstr) < 256):
+ # assume IPv4 ('127' gets interpreted as '127.0.0.0')
+ bytes = ipstr.split('.')
+ if len(bytes) > 4:
+ raise ValueError, "IPv4 Address with more than 4 bytes"
+ bytes += ['0'] * (4 - len(bytes))
+ bytes = [long(x) for x in bytes]
+ for x in bytes:
+ if x > 255 or x < 0:
+ raise ValueError, "%r: single byte must be 0 <= byte < 256" % (ipstr)
+ return ((bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3], 4)
+
+ else:
+ # we try to interprete it as a decimal digit -
+ # this ony works for numbers > 255 ... others
+ # will be interpreted as IPv4 first byte
+ ret = long(ipstr, 10)
+ if ret > 0xffffffffffffffffffffffffffffffffL:
+ raise ValueError, "IP Address can't be bigger than 2^128"
+ if ret <= 0xffffffffL:
+ return (ret, 4)
+ else:
+ return (ret, 6)
+
+
+def intToIp(ip, version):
+ """Transform an integer string into an IP address."""
+
+ # just to be sure and hoping for Python 2.22
+ ip = long(ip)
+
+ if ip < 0:
+ raise ValueError, "IPs can't be negative: %d" % (ip)
+
+ ret = ''
+ if version == 4:
+ if ip > 0xffffffffL:
+ raise ValueError, "IPv4 Addresses can't be larger than 0xffffffff: %s" % (hex(ip))
+ for l in range(4):
+ ret = str(ip & 0xffL) + '.' + ret
+ ip = ip >> 8
+ ret = ret[:-1]
+ elif version == 6:
+ if ip > 0xffffffffffffffffffffffffffffffffL:
+ raise ValueError, "IPv6 Addresses can't be larger than 0xffffffffffffffffffffffffffffffff: %s" % (hex(ip))
+ l = '0' * 32 + hex(ip)[2:-1]
+ for x in range(1, 33):
+ ret = l[-x] + ret
+ if x % 4 == 0:
+ ret = ':' + ret
+ ret = ret[1:]
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+
+ return ret
+
+def _ipVersionToLen(version):
+ """Return number of bits in address for a certain IP version.
+
+ >>> _ipVersionToLen(4)
+ 32
+ >>> _ipVersionToLen(6)
+ 128
+ >>> _ipVersionToLen(5)
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in ?
+ File "IPy.py", line 1076, in _ipVersionToLen
+ raise ValueError, "only IPv4 and IPv6 supported"
+ ValueError: only IPv4 and IPv6 supported
+ """
+
+ if version == 4:
+ return 32
+ elif version == 6:
+ return 128
+ else:
+ raise ValueError, "only IPv4 and IPv6 supported"
+
+
+def _countFollowingZeros(l):
+ """Return number of elements containing 0 at the beginning of the list."""
+ if len(l) == 0:
+ return 0
+ elif l[0] != 0:
+ return 0
+ else:
+ return 1 + _countFollowingZeros(l[1:])
+
+
+_BitTable = {'0': '0000', '1': '0001', '2': '0010', '3': '0011',
+ '4': '0100', '5': '0101', '6': '0110', '7': '0111',
+ '8': '1000', '9': '1001', 'a': '1010', 'b': '1011',
+ 'c': '1100', 'd': '1101', 'e': '1110', 'f': '1111'}
+
+def _intToBin(val):
+ """Return the binary representation of an integer as string."""
+
+ if val < 0:
+ raise ValueError, "Only positive values allowed"
+ s = hex(val).lower()
+ ret = ''
+ if s[-1] == 'l':
+ s = s[:-1]
+ for x in s[2:]:
+ if __debug__:
+ if not _BitTable.has_key(x):
+ raise AssertionError, "hex() returned strange result"
+ ret += _BitTable[x]
+ # remove leading zeros
+ while ret[0] == '0' and len(ret) > 1:
+ ret = ret[1:]
+ return ret
+
+def _count1Bits(num):
+ """Find the highest bit set to 1 in an integer."""
+ ret = 0
+ while num > 0:
+ num = num >> 1
+ ret += 1
+ return ret
+
+def _count0Bits(num):
+ """Find the highest bit set to 0 in an integer."""
+
+ # this could be so easy if _count1Bits(~long(num)) would work as excepted
+ num = long(num)
+ if num < 0:
+ raise ValueError, "Only positive Numbers please: %s" % (num)
+ ret = 0
+ while num > 0:
+ if num & 1 == 1:
+ break
+ num = num >> 1
+ ret += 1
+ return ret
+
+
+def _checkPrefix(ip, prefixlen, version):
+ """Check the validity of a prefix
+
+ Checks if the variant part of a prefix only has 0s, and the length is
+ correct.
+
+ >>> _checkPrefix(0x7f000000L, 24, 4)
+ 1
+ >>> _checkPrefix(0x7f000001L, 24, 4)
+ 0
+ >>> repr(_checkPrefix(0x7f000001L, -1, 4))
+ 'None'
+ >>> repr(_checkPrefix(0x7f000001L, 33, 4))
+ 'None'
+ """
+
+ # TODO: unify this v4/v6/invalid code in a function
+ bits = _ipVersionToLen(version)
+
+ if prefixlen < 0 or prefixlen > bits:
+ return None
+
+ if ip == 0:
+ zbits = bits + 1
+ else:
+ zbits = _count0Bits(ip)
+ if zbits < bits - prefixlen:
+ return 0
+ else:
+ return 1
+
+
+def _checkNetmask(netmask, masklen):
+ """Checks if a netmask is expressable as a prefixlen."""
+
+ num = long(netmask)
+ bits = masklen
+
+ # remove zero bits at the end
+ while (num & 1) == 0 and bits != 0:
+ num = num >> 1
+ bits -= 1
+ if bits == 0:
+ break
+ # now check if the rest consists only of ones
+ while bits > 0:
+ if (num & 1) == 0:
+ raise ValueError, "Netmask %s can't be expressed as an prefix." % (hex(netmask))
+ num = num >> 1
+ bits -= 1
+
+
+def _checkNetaddrWorksWithPrefixlen(net, prefixlen, version):
+ """Check if a base addess of a network is compatible with a prefixlen"""
+ if net & _prefixlenToNetmask(prefixlen, version) == net:
+ return 1
+ else:
+ return 0
+
+
+def _netmaskToPrefixlen(netmask):
+ """Convert an Integer representing a netmask to a prefixlen.
+
+ E.g. 0xffffff00 (255.255.255.0) returns 24
+ """
+
+ netlen = _count0Bits(netmask)
+ masklen = _count1Bits(netmask)
+ _checkNetmask(netmask, masklen)
+ return masklen - netlen
+
+
+def _prefixlenToNetmask(prefixlen, version):
+ """Return a mask of n bits as a long integer.
+
+ From 'IP address conversion functions with the builtin socket module'
+ by Alex Martelli
+ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66517
+ """
+ if prefixlen == 0:
+ return 0
+ elif prefixlen < 0:
+ raise ValueError, "Prefixlen must be > 0"
+ return ((2L<<prefixlen-1)-1) << (_ipVersionToLen(version) - prefixlen)
+
+
+if __name__ == "__main__":
+ import doctest
+ failure, nbtest = doctest.testmod()
+ if failure:
+ import sys
+ sys.exit(1)
+
diff --git a/vendor/Twisted-10.0.0/INSTALL b/vendor/Twisted-10.0.0/INSTALL
new file mode 100644
index 0000000000..a5aecab025
--- /dev/null
+++ b/vendor/Twisted-10.0.0/INSTALL
@@ -0,0 +1,32 @@
+Requirements
+
+ Python 2.4, 2.5 or 2.6.
+
+ Zope Interfaces 3.0.1 (http://zope.org/Products/ZopeInterface) - if
+ you have ZopeX3 (at least version 3.0.0c1) installed that should
+ work too.
+
+ On Windows pywin32 is recommended (this is built in to ActivePython,
+ so no need to reinstall if you use it instead of standard Python)
+ http://sourceforge.net/project/showfiles.php?group_id=78018
+ The Windows IOCP reactor requires pywin32 build 205 or later.
+
+ If you would like to use Trial's subunit reporter, then you will need to
+ install Subunit 0.0.2 or later (https://launchpad.net/subunit).
+
+Installation
+
+ * Debian and Ubuntu
+ Packages are included in the main distribution.
+
+ * FreeBSD, Gentoo
+ Twisted is in their package repositories.
+
+ * Win32
+ EXEs are available from http://twistedmatrix.com/
+
+ * Other
+ As with other Python packages, the standard way of installing from source
+ is:
+
+ python setup.py install
diff --git a/vendor/Twisted-10.0.0/LICENSE b/vendor/Twisted-10.0.0/LICENSE
new file mode 100644
index 0000000000..ebaf03c964
--- /dev/null
+++ b/vendor/Twisted-10.0.0/LICENSE
@@ -0,0 +1,57 @@
+Copyright (c) 2001-2010
+Allen Short
+Andy Gayton
+Andrew Bennetts
+Antoine Pitrou
+Apple Computer, Inc.
+Benjamin Bruheim
+Bob Ippolito
+Canonical Limited
+Christopher Armstrong
+David Reid
+Donovan Preston
+Eric Mangold
+Eyal Lotem
+Itamar Shtull-Trauring
+James Knight
+Jason A. Mobarak
+Jean-Paul Calderone
+Jessica McKellar
+Jonathan Jacobs
+Jonathan Lange
+Jonathan D. Simms
+Jürgen Hermann
+Kevin Horn
+Kevin Turner
+Mary Gardiner
+Matthew Lefkowitz
+Massachusetts Institute of Technology
+Moshe Zadka
+Paul Swartz
+Pavel Pergamenshchik
+Ralph Meijer
+Sean Riley
+Software Freedom Conservancy
+Travis B. Hartwell
+Thijs Triemstra
+Thomas Herve
+Timothy Allen
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/Twisted-10.0.0/NEWS b/vendor/Twisted-10.0.0/NEWS
new file mode 100644
index 0000000000..8a0e5142e5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/NEWS
@@ -0,0 +1,1416 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/<number>
+
+Twisted Core 10.0.0 (2010-03-01)
+================================
+
+Features
+--------
+ - The twistd man page now has a SIGNALS section. (#689)
+
+ - reactor.spawnProcess now will not emit a PotentialZombieWarning
+ when called before reactor.run, and there will be no potential for
+ zombie processes in this case. (#2078)
+
+ - High-throughput applications based on Perspective Broker should now
+ run noticably faster thanks to the use of a more efficient decoding
+ function in Twisted Spread. (#2310)
+
+ - Documentation for trac-post-commit-hook functionality in svn-dev
+ policy. (#3867)
+
+ - twisted.protocols.socks.SOCKSv4 now supports the SOCKSv4a protocol.
+ (#3886)
+
+ - Trial can now output test results according to the subunit
+ protocol, as long as Subunit is installed (see
+ https://launchpad.net/subunit). (#4004)
+
+ - twisted.protocols.amp now provides a ListOf argument type which can
+ be composed with some other argument types to create a zero or more
+ element sequence of that type. (#4116)
+
+ - If returnValue is invoked outside of a function decorated with
+ @inlineCallbacks, but causes a function thusly decorated to exit, a
+ DeprecationWarning will be emitted explaining this potentially
+ confusing behavior. In a future release, this will cause an
+ exception. (#4157)
+
+ - twisted.python.logfile.BaseLogFile now has a reopen method allowing
+ you to use an external logrotate mechanism. (#4255)
+
+Bugfixes
+--------
+ - FTP.ftp_NLST now handles requests on invalid paths in a way
+ consistent with RFC 959. (#1342)
+
+ - twisted.python.util.initgroups now calls the low-level C initgroups
+ by default if available: the python version can create lots of I/O
+ with certain authentication setup to retrieve all the necessary
+ information. (#3226)
+
+ - startLogging now does nothing on subsequent invocations, thus
+ fixing a terrible infinite recursion bug that's only on edge case.
+ (#3289)
+
+ - Stringify non-string data to NetstringReceiver.sendString before
+ calculating the length so that the calculated length is equal to
+ the actual length of the transported data. (#3299)
+
+ - twisted.python.win32.cmdLineQuote now correctly quotes empty
+ strings arguments (#3876)
+
+ - Change the behavior of the Gtk2Reactor to register only one source
+ watch for each file descriptor, instead of one for reading and one
+ for writing. In particular, it fixes a bug with Glib under Windows
+ where we failed to notify when a client is connected. (#3925)
+
+ - Twisted Trial no longer crashes if it can't remove an old
+ _trial_temp directory. (#4020)
+
+ - The optional _c_urlarg extension now handles unquote("") correctly
+ on platforms where malloc(0) returns NULL, such as AIX. It also
+ compiles with less warnings. (#4142)
+
+ - On POSIX, child processes created with reactor.spawnProcess will no
+ longer automatically ignore the signals which the parent process
+ has set to be ignored. (#4199)
+
+ - All SOCKSv4a tests now use a dummy reactor with a deterministic
+ resolve method. (#4275)
+
+ - Prevent extraneous server, date and content-type headers in proxy
+ responses. (#4277)
+
+Deprecations and Removals
+-------------------------
+ - twisted.internet.error.PotentialZombieWarning is now deprecated.
+ (#2078)
+
+ - twisted.test.time_helpers is now deprecated. (#3719)
+
+ - The deprecated connectUDP method of IReactorUDP has now been
+ removed. (#4075)
+
+ - twisted.trial.unittest.TestCase now ignores the previously
+ deprecated setUpClass and tearDownClass methods. (#4175)
+
+Other
+-----
+ - #917, #2406, #2481, #2608, #2689, #2884, #3056, #3082, #3199,
+ #3480, #3592, #3718, #3935, #4066, #4083, #4154, #4166, #4169,
+ #4176, #4183, #4186, #4188, #4189, #4194, #4201, #4204, #4209,
+ #4222, #4234, #4235, #4238, #4240, #4245, #4251, #4264, #4268,
+ #4269, #4282
+
+
+Twisted Conch 10.0.0 (2010-03-01)
+=================================
+
+Bugfixes
+--------
+ - twisted.conch.checkers.SSHPublicKeyDatabase now looks in the
+ correct user directory for authorized_keys files. (#3984)
+
+ - twisted.conch.ssh.SSHUserAuthClient now honors preferredOrder when
+ authenticating. (#4266)
+
+Other
+-----
+ - #2391, #4203, #4265
+
+
+Twisted Lore 10.0.0 (2010-03-01)
+================================
+
+Other
+-----
+ - #4241
+
+
+Twisted Mail 10.0.0 (2010-03-01)
+================================
+
+Bugfixes
+--------
+ - twisted.mail.smtp.ESMTPClient and
+ twisted.mail.smtp.LOGINAuthenticator now implement the (obsolete)
+ LOGIN SASL mechanism according to the draft specification. (#4031)
+
+ - twisted.mail.imap4.IMAP4Client will no longer misparse all html-
+ formatted message bodies received in response to a fetch command.
+ (#4049)
+
+ - The regression in IMAP4 search handling of "OR" and "NOT" terms has
+ been fixed. (#4178)
+
+Other
+-----
+ - #4028, #4170, #4200
+
+
+Twisted Names 10.0.0 (2010-03-01)
+=================================
+
+Bugfixes
+--------
+ - twisted.names.root.Resolver no longer leaks UDP sockets while
+ resolving names. (#970)
+
+Deprecations and Removals
+-------------------------
+ - Several top-level functions in twisted.names.root are now
+ deprecated. (#970)
+
+Other
+-----
+ - #4066
+
+
+Twisted Pair 10.0.0 (2010-03-01)
+================================
+
+Other
+-----
+ - #4170
+
+
+Twisted Runner 10.0.0 (2010-03-01)
+==================================
+
+Other
+-----
+ - #3961
+
+
+Twisted Web 10.0.0 (2010-03-01)
+===============================
+
+Features
+--------
+ - Twisted Web in 60 Seconds, a series of short tutorials with self-
+ contained examples on a range of common web topics, is now a part
+ of the Twisted Web howto documentation. (#4192)
+
+Bugfixes
+--------
+ - Data and File from twisted.web.static and
+ twisted.web.distrib.UserDirectory will now only generate a 200
+ response for GET or HEAD requests.
+ twisted.web.client.HTTPPageGetter will no longer ignore the case of
+ a request method when considering whether to apply special HEAD
+ processing to a response. (#446)
+
+ - twisted.web.http.HTTPClient now supports multi-line headers.
+ (#2062)
+
+ - Resources served via twisted.web.distrib will no longer encounter a
+ Banana error when writing more than 640kB at once to the request
+ object. (#3212)
+
+ - The Error, PageRedirect, and InfiniteRedirection exception in
+ twisted.web now initialize an empty message parameter by mapping
+ the HTTP status code parameter to a descriptive string. Previously
+ the lookup would always fail, leaving message empty. (#3806)
+
+ - The 'wsgi.input' WSGI environment object now supports -1 and None
+ as arguments to the read and readlines methods. (#4114)
+
+ - twisted.web.wsgi doesn't unquote QUERY_STRING anymore, thus
+ complying with the WSGI reference implementation. (#4143)
+
+ - The HTTP proxy will no longer pass on keep-alive request headers
+ from the client, preventing pages from loading then "hanging"
+ (leaving the connection open with no hope of termination). (#4179)
+
+Deprecations and Removals
+-------------------------
+ - Remove '--static' option from twistd web, that served as an alias
+ for the '--path' option. (#3907)
+
+Other
+-----
+ - #3784, #4216, #4242
+
+
+Twisted Words 10.0.0 (2010-03-01)
+=================================
+
+Features
+--------
+ - twisted.words.protocols.irc.IRCClient.irc_MODE now takes ISUPPORT
+ parameters into account when parsing mode messages with arguments
+ that take parameters (#3296)
+
+Bugfixes
+--------
+ - When twisted.words.protocols.irc.IRCClient's versionNum and
+ versionEnv attributes are set to None, they will no longer be
+ included in the client's response to CTCP VERSION queries. (#3660)
+
+ - twisted.words.protocols.jabber.xmlstream.hashPassword now only
+ accepts unicode as input (#3741, #3742, #3847)
+
+Other
+-----
+ - #2503, #4066, #4261
+
+
+Twisted Core 9.0.0 (2009-11-24)
+===============================
+
+Features
+--------
+ - LineReceiver.clearLineBuffer now returns the bytes that it cleared (#3573)
+ - twisted.protocols.amp now raises InvalidSignature when bad arguments are
+ passed to Command.makeArguments (#2808)
+ - IArgumentType was added to represent an existing but previously unspecified
+ interface in amp (#3468)
+ - Obscure python tricks have been removed from the finger tutorials (#2110)
+ - The digest auth implementations in twisted.web and twisted.protocolos.sip
+ have been merged together in twisted.cred (#3575)
+ - FilePath and ZipPath now has a parents() method which iterates up all of its
+ parents (#3588)
+ - reactors which support threads now have a getThreadPool method (#3591)
+ - The MemCache client implementation now allows arguments to the "stats"
+ command (#3661)
+ - The MemCache client now has a getMultiple method which allows fetching of
+ multiple values (#3171)
+ - twisted.spread.jelly can now unserialize some new-style classes (#2950)
+ - twisted.protocols.loopback.loopbackAsync now accepts a parameter to control
+ the data passed between client and server (#3820)
+ - The IOCP reactor now supports SSL (#593)
+ - Tasks in a twisted.internet.task.Cooperator can now be paused, resumed, and
+ cancelled (#2712)
+ - AmpList arguments can now be made optional (#3891)
+ - The syslog output observer now supports log levels (#3300)
+ - LoopingCall now supports reporting the number of intervals missed if it
+ isn't able to schedule calls fast enough (#3671)
+
+Fixes
+-----
+ - The deprecated md5 and sha modules are no longer used if the stdlib hashlib
+ module is available (#2763)
+ - An obscure deadlock involving waking up the reactor within signal handlers
+ in particular threads was fixed (#1997)
+ - The passivePortRange attribute of FTPFactory is now honored (#3593)
+ - TestCase.flushWarnings now flushes warnings even if they were produced by a
+ file that was renamed since it was byte compiled (#3598)
+ - Some internal file descriptors are now marked as close-on-exec, so these will
+ no longer be leaked to child processes (#3576)
+ - twisted.python.zipstream now correctly extracts the first file in a directory
+ as a file, and not an empty directory (#3625)
+ - proxyForInterface now returns classes which correctly *implement* interfaces
+ rather than *providing* them (#3646)
+ - SIP Via header parameters should now be correctly generated (#2194)
+ - The Deferred returned by stopListening would sometimes previously never fire
+ if an exception was raised by the underlying file descriptor's connectionLost
+ method. Now the Deferred will fire with a failure (#3654)
+ - The command-line tool "manhole" should now work with newer versions of pygtk
+ (#2464)
+ - When a DefaultOpenSSLContextFactory is instantiated with invalid parameters,
+ it will now raise an exception immediately instead of waiting for the first
+ connection (#3700)
+ - Twisted command line scripts should now work when installed in a virtualenv
+ (#3750)
+ - Trial will no longer delete temp directories which it did not create (#3481)
+ - Processes started on Windows should now be cleaned up properly in more cases
+ (#3893)
+ - Certain misbehaving importers will no longer cause twisted.python.modules
+ (and thus trial) to raise an exception, but rather issue a warning (#3913)
+ - MemCache client protocol methods will now fail when the transport has been
+ disconnected (#3643)
+ - In the AMP method callRemoteString, the requiresAnswer parameter is now
+ honored (#3999)
+ - Spawning a "script" (a file which starts with a #! line) on Windows running
+ Python 2.6 will now work instead of raising an exception about file mode
+ "ru" (#3567)
+ - FilePath's walk method now calls its "descend" parameter even on the first
+ level of children, instead of only on grandchildren. This allows for better
+ symlink cycle detection (#3911)
+ - Attempting to write unicode data to process pipes on Windows will no longer
+ result in arbitrarily encoded messages being written to the pipe, but instead
+ will immediately raise an error (#3930)
+ - The various twisted command line utilities will no longer print
+ ModuleType.__doc__ when Twisted was installed with setuptools (#4030)
+ - A Failure object will now be passed to connectionLost on stdio connections
+ on Windows, instead of an Exception object (#3922)
+
+Deprecations and Removals
+-------------------------
+ - twisted.persisted.marmalade was deleted after a long period of deprecation
+ (#876)
+ - Some remaining references to the long-gone plugins.tml system were removed
+ (#3246)
+ - SSLv2 is now disabled by default, but it can be re-enabled explicitly
+ (#3330)
+ - twisted.python.plugin has been removed (#1911)
+ - reactor.run will now raise a ReactorAlreadyRunning exception when it is
+ called reentrantly instead of warning a DeprecationWarning (#1785)
+ - twisted.spread.refpath is now deprecated because it is unmaintained,
+ untested, and has dubious value (#3723)
+ - The unused --quiet flag has been removed from the twistd command (#3003)
+
+Other
+-----
+ - #3545, #3490, #3544, #3537, #3455, #3315, #2281, #3564, #3570, #3571, #3486,
+ #3241, #3599, #3220, #1522, #3611, #3596, #3606, #3609, #3602, #3637, #3647,
+ #3632, #3675, #3673, #3686, #2217, #3685, #3688, #2456, #506, #3635, #2153,
+ #3581, #3708, #3714, #3717, #3698, #3747, #3704, #3707, #3713, #3720, #3692,
+ #3376, #3652, #3695, #3735, #3786, #3783, #3699, #3340, #3810, #3822, #3817,
+ #3791, #3859, #2459, #3677, #3883, #3894, #3861, #3822, #3852, #3875, #2722,
+ #3768, #3914, #3885, #2719, #3905, #3942, #2820, #3990, #3954, #1627, #2326,
+ #2972, #3253, #3937, #4058, #1200, #3639, #4079, #4063, #4050
+
+
+Twisted Conch 9.0.0 (2009-11-24)
+================================
+
+Fixes
+-----
+ - The SSH key parser has been removed and conch now uses pyASN1 to parse keys.
+ This should fix a number of cases where parsing a key would fail, but it now
+ requires users to have pyASN1 installed (#3391)
+ - The time field on SFTP file listings should now be correct (#3503)
+ - The day field on SFTP file listings should now be correct on Windows (#3503)
+ - The "cftp" sftp client now truncates files it is uploading over (#2519)
+ - The telnet server protocol can now properly respond to subnegotiation
+ requests (#3655)
+ - Tests and factoring of the SSHv2 server implementation are now much better
+ (#2682)
+ - The SSHv2 server now sends "exit-signal" messages to the client, instead of
+ raising an exception, when a process dies due to a signal (#2687)
+ - cftp's client-side "exec" command now uses /bin/sh if the current user has
+ no shell (#3914)
+
+Deprecations and Removals
+-------------------------
+ - The buggy SSH connection sharing feature of the SSHv2 client was removed
+ (#3498)
+ - Use of strings and PyCrypto objects to represent keys is deprecated in favor
+ of using Conch Key objects (#2682)
+
+Other
+-----
+ - #3548, #3537, #3551, #3220, #3568, #3689, #3709, #3809, #2763, #3540, #3750,
+ #3897, #3813, #3871, #3916, #4047, #3940, #4050
+
+
+Twisted Lore 9.0.0 (2009-11-24)
+===============================
+
+Features
+--------
+ - Python source listings now include line numbers (#3486)
+
+Fixes
+-----
+ - Lore now uses minidom instead of Twisted's microdom, which incidentally
+ fixes some Lore bugs such as throwing away certain whitespace
+ (#3560, #414, #3619)
+ - Lore's "lint" command should no longer break on documents with links in them
+ (#4051, #4115)
+
+Deprecations and Removals
+-------------------------
+ - Lore no longer uses the ancient "tml" Twisted plugin system (#1911)
+
+Other
+-----
+ - #3565, #3246, #3540, #3750, #4050
+
+
+Twisted Mail 9.0.0 (2009-11-24)
+===============================
+
+Features
+--------
+ - maildir.StringListMailbox, an in-memory maildir mailbox, now supports
+ deletion, undeletion, and syncing (#3547)
+ - SMTPClient's callbacks are now more completely documented (#684)
+
+Fixes
+-----
+ - Parse UNSEEN response data and include it in the result of
+ IMAP4Client.examine (#3550)
+ - The IMAP4 client now delivers more unsolicited server responses to callbacks
+ rather than ignoring them, and also won't ignore solicited responses that
+ arrive on the same line as an unsolicited one (#1105)
+ - Several bugs in the SMTP client's idle timeout support were fixed (#3641,
+ #1219)
+ - A case where the SMTP client could skip some recipients when retrying
+ delivery has been fixed (#3638)
+ - Errors during certain data transfers will no longer be swallowed. They will
+ now bubble up to the higher-level API (such as the sendmail function) (#3642)
+ - Escape sequences inside quoted strings in IMAP4 should now be parsed
+ correctly by the IMAP4 server protocol (#3659)
+ - The "imap4-utf-7" codec that is registered by twisted.mail.imap4 had a number
+ of fixes that allow it to work better with the Python codecs system, and to
+ actually work (#3663)
+ - The Maildir implementation now ensures time-based ordering of filenames so
+ that the lexical sorting of messages matches the order in which they were
+ received (#3812)
+ - SASL PLAIN credentials generated by the IMAP4 protocol implementations
+ (client and server) should now be RFC-compliant (#3939)
+ - Searching for a set of sequences using the IMAP4 "SEARCH" command should
+ now work on the IMAP4 server protocol implementation. This at least improves
+ support for the Pine mail client (#1977)
+
+Other
+-----
+ - #2763, #3647, #3750, #3819, #3540, #3846, #2023, #4050
+
+
+Twisted Names 9.0.0 (2009-11-24)
+================================
+
+Deprecations and Removals
+-------------------------
+ - client.ThreadedResolver is deprecated in favor of
+ twisted.internet.base.ThreadedResolver (#3710)
+
+Other
+-----
+ - #3540, #3560, #3712, #3750, #3990
+
+
+Twisted News 9.0.0 (2009-11-24)
+===============================
+
+Other
+-----
+ - #2763, #3540
+
+
+Twisted Pair 9.0.0 (2009-11-24)
+===============================
+
+Other
+-----
+ - #3540, #4050
+
+
+Twisted Runner 9.0.0 (2009-11-24)
+=================================
+
+Features
+--------
+ - procmon.ProcessMonitor.addProcess now accepts an 'env' parameter which allows
+ users to specify the environment in which a process will be run (#3691)
+
+Other
+-----
+ - #3540
+
+
+Twisted Web 9.0.0 (2009-11-24)
+==============================
+
+Features
+--------
+ - There is now an iweb.IRequest interface which specifies the interface that
+ request objects provide (#3416)
+ - downloadPage now supports the same cookie, redirect, and timeout features
+ that getPage supports (#2971)
+ - A chapter about WSGI has been added to the twisted.web documentation (#3510)
+ - The HTTP auth support in the web server now allows anonymous sessions by
+ logging in with ANONYMOUS credentials when no Authorization header is
+ provided in a request (#3924, #3936)
+ - HTTPClientFactory now accepts a parameter to enable a common deviation from
+ the HTTP 1.1 standard by responding to redirects in a POSTed request with a
+ GET instead of another POST (#3624)
+ - A new basic HTTP/1.1 client API is included in twisted.web.client.Agent
+ (#886, #3987)
+
+Fixes
+-----
+ - Requests for "insecure" children of a static.File (such as paths containing
+ encoded directory separators) will now result in a 404 instead of a 500
+ (#3549, #3469)
+ - When specifying a followRedirect argument to the getPage function, the state
+ of redirect-following for other getPage calls should now be unaffected. It
+ was previously overwriting a class attribute which would affect outstanding
+ getPage calls (#3192)
+ - Downloading an URL of the form "http://example.com:/" will now work,
+ ignoring the extraneous colon (#2402)
+ - microdom's appendChild method will no longer issue a spurious warning, and
+ microdom's methods in general should now issue more meaningful exceptions
+ when invalid parameters are passed (#3421)
+ - WSGI applications will no longer have spurious Content-Type headers added to
+ their responses by the twisted.web server. In addition, WSGI applications
+ will no longer be able to specify the server-restricted headers Server and
+ Date (#3569)
+ - http_headers.Headers now normalizes the case of raw headers passed directly
+ to it in the same way that it normalizes the headers passed to setRawHeaders
+ (#3557)
+ - The distrib module no longer relies on the deprecated woven package (#3559)
+ - twisted.web.domhelpers now works with both microdom and minidom (#3600)
+ - twisted.web servers will now ignore invalid If-Modified-Since headers instead
+ of returning a 500 error (#3601)
+ - Certain request-bound memory and file resources are cleaned up slightly
+ sooner by the request when the connection is lost (#1621, #3176)
+ - xmlrpclib.DateTime objects should now correctly round-trip over twisted.web's
+ XMLRPC support in all supported versions of Python, and errors during error
+ serialization will no longer hang a twisted.web XMLRPC response (#2446)
+ - request.content should now always be seeked to the beginning when
+ request.process is called, so application code should never need to seek
+ back manually (#3585)
+ - Fetching a child of static.File with a double-slash in the URL (such as
+ "example//foo.html") should now return a 404 instead of a traceback and
+ 500 error (#3631)
+ - downloadPage will now fire a Failure on its returned Deferred instead of
+ indicating success when the connection is prematurely lost (#3645)
+ - static.File will now provide a 404 instead of a 500 error when it was
+ constructed with a non-existent file (#3634)
+ - microdom should now serialize namespaces correctly (#3672)
+ - The HTTP Auth support resource wrapper should no longer corrupt requests and
+ cause them to skip a segment in the request path (#3679)
+ - The twisted.web WSGI support should now include leading slashes in PATH_INFO,
+ and SCRIPT_NAME will be empty if the application is at the root of the
+ resource tree. This means that WSGI applications should no longer generate
+ URLs with double-slashes in them even if they naively concatenate the values
+ (#3721)
+ - WSGI applications should now receive the requesting client's IP in the
+ REMOTE_ADDR environment variable (#3730)
+ - The distrib module should work again. It was unfortunately broken with the
+ refactoring of twisted.web's header support (#3697)
+ - static.File now supports multiple ranges specified in the Range header
+ (#3574)
+ - static.File should now generate a correct Content-Length value when the
+ requested Range value doesn't fit entirely within the file's contents (#3814)
+ - Attempting to call request.finish() after the connection has been lost will
+ now immediately raise a RuntimeError (#4013)
+ - An HTTP-auth resource should now be able to directly render the wrapped
+ avatar, whereas before it would only allow retrieval of child resources
+ (#4014)
+ - twisted.web's wsgi support should no longer attempt to call request.finish
+ twice, which would cause errors in certain cases (#4025)
+ - WSGI applications should now be able to handle requests with large bodies
+ (#4029)
+ - Exceptions raised from WSGI applications should now more reliably be turned
+ into 500 errors on the HTTP level (#4019)
+ - DeferredResource now correctly passes through exceptions raised from the
+ wrapped resource, instead of turning them all into 500 errors (#3932)
+ - Agent.request now generates a Host header when no headers are passed at
+ (#4131)
+
+Deprecations and Removals
+-------------------------
+ - The unmaintained and untested twisted.web.monitor module was removed (#2763)
+ - The twisted.web.woven package has been removed (#1522)
+ - All of the error resources in twisted.web.error are now in
+ twisted.web.resource, and accessing them through twisted.web.error is now
+ deprecated (#3035)
+ - To facilitate a simplification of the timeout logic in server.Session,
+ various things have been deprecated (#3457)
+ - the loopFactory attribute is now ignored
+ - the checkExpired method now does nothing
+ - the lifetime parameter to startCheckingExpiration is now ignored
+ - The twisted.web.trp module is now deprecated (#2030)
+
+Other
+-----
+ - #2763, #3540, #3575, #3610, #3605, #1176, #3539, #3750, #3761, #3779, #2677,
+ #3782, #3904, #3919, #3418, #3990, #1404, #4050
+
+
+Twisted Words 9.0.0 (2009-11-24)
+================================
+
+Features
+--------
+ - IRCClient.describe is a new method meant to replace IRCClient.me to send
+ CTCP ACTION messages with less confusing behavior (#3910)
+ - The XMPP client protocol implementation now supports ANONYMOUS SASL
+ authentication (#4067)
+ - The IRC client protocol implementation now has better support for the
+ ISUPPORT server->client message, storing the data in a new
+ ServerSupportedFeatures object accessible via IRCClient.supported (#3285)
+
+Fixes
+-----
+ - The twisted.words IRC server now always sends an MOTD, which at least makes
+ Pidgin able to successfully connect to a twisted.words IRC server (#2385)
+ - The IRC client will now dispatch "RPL MOTD" messages received before a
+ "RPL MOTD START" instead of raising an exception (#3676)
+ - The IRC client protocol implementation no longer updates its 'nickname'
+ attribute directly; instead, that attribute will be updated when the server
+ acknowledges the change (#3377)
+ - The IRC client protocol implementation now supports falling back to another
+ nickname when a nick change request fails (#3377, #4010)
+
+Deprecations and Removals
+-------------------------
+ - The TOC protocol implementation is now deprecated, since the protocol itself
+ has been deprecated and obselete for quite a long time (#3580)
+ - The gui "im" application has been removed, since it relied on GTK1, which is
+ hard to find these days (#3699, #3340)
+
+Other
+-----
+ - #2763, #3540, #3647, #3750, #3895, #3968, #4050
+
+
+Core 8.2.0 (2008-12-16)
+=======================
+
+Features
+--------
+ - Reactors are slowly but surely becoming more isolated, thus improving
+ testability (#3198)
+ - FilePath has gained a realpath method, and FilePath.walk no longer infinitely
+ recurses in the case of a symlink causing a self-recursing filesystem tree
+ (#3098)
+ - FilePath's moveTo and copyTo methods now have an option to disable following
+ of symlinks (#3105)
+ - Private APIs are now included in the API documentation (#3268)
+ - hotshot is now the default profiler for the twistd --profile parameter and
+ using cProfile is now documented (#3355, #3356)
+ - Process protocols can now implement a processExited method, which is
+ distinct from processEnded in that it is called immediately when the child
+ has died, instead of waiting for all the file descriptors to be closed
+ (#1291)
+ - twistd now has a --umask option (#966, #3024)
+ - A new deferToThreadPool function exists in twisted.internet.threads (#2845)
+ - There is now an example of writing an FTP server in examples/ftpserver.py
+ (#1579)
+ - A new runAsEffectiveUser function has been added to twisted.python.util
+ (#2607)
+ - twisted.internet.utils.getProcessOutput now offers a mechanism for
+ waiting for the process to actually end, in the event of data received on
+ stderr (#3239)
+ - A fullyQualifiedName function has been added to twisted.python.reflect
+ (#3254)
+ - strports now defaults to managing access to a UNIX socket with a lock;
+ lockfile=0 can be included in the strports specifier to disable this
+ behavior (#2295)
+ - FTPClient now has a 'rename' method (#3335)
+ - FTPClient now has a 'makeDirectory' method (#3500)
+ - FTPClient now has a 'removeFile' method (#3491)
+ - flushWarnings, A new Trial method for testing warnings, has been added
+ (#3487, #3427, #3506)
+ - The log observer can now be configured in .tac files (#3534)
+
+Fixes
+-----
+ - TLS Session Tickets are now disabled by default, allowing connections to
+ certain servers which hang when an empty session ticket is received (like
+ GTalk) (#3463)
+ - twisted.enterprise.adbapi.ConnectionPool's noisy attribute now defaults to
+ False, as documented (#1806)
+ - Error handling and logging in adbapi is now much improved (#3244)
+ - TCP listeners can now be restarted (#2913)
+ - Doctests can now be rerun with trial's --until-failure option (#2713)
+ - Some memory leaks have been fixed in trial's --until-failure
+ implementation (#3119, #3269)
+ - Trial's summary reporter now prints correct runtime information and handles
+ the case of 0 tests (#3184)
+ - Trial and any other user of the 'namedAny' function now has better error
+ reporting in the case of invalid module names (#3259)
+ - Multiple instances of trial can now run in parallel in the same directory
+ by creating _trial_temp directories with an incremental suffix (#2338)
+ - Trial's failUnlessWarns method now works on Python 2.6 (#3223)
+ - twisted.python.log now hooks into the warnings system in a way compatible
+ with Python 2.6 (#3211)
+ - The GTK2 reactor is now better supported on Windows, but still not passing
+ the entire test suite (#3203)
+ - low-level failure handling in spawnProcess has been improved and no longer
+ leaks file descriptors (#2305, #1410)
+ - Perspective Broker avatars now have their logout functions called in more
+ cases (#392)
+ - Log observers which raise exceptions are no longer removed (#1069)
+ - transport.getPeer now always includes an IP address in the Address returned
+ instead of a hostname (#3059)
+ - Functions in twisted.internet.utils which spawn processes now avoid calling
+ chdir in the case where no working directory is passed, to avoid some
+ obscure permission errors (#3159)
+ - twisted.spread.publish.Publishable no longer corrupts line endings on
+ Windows (#2327)
+ - SelectReactor now properly detects when a TLS/TCP connection has been
+ disconnected (#3218)
+ - twisted.python.lockfile no longer raises an EEXIST OSError and is much
+ better supported on Windows (#3367)
+ - When ITLSTransport.startTLS is called while there is data in the write
+ buffer, TLS negotiation will now be delayed instead of the method raising
+ an exception (#686)
+ - The userAnonymous argument to FTPFactory is now honored (#3390)
+ - twisted.python.modules no longer tries to "fix" sys.modules after an import
+ error, which was just causing problems (#3388)
+ - setup.py no longer attempts to build extension modules when run with Jython
+ (#3410)
+ - AMP boxes can now be sent in IBoxReceiver.startReceivingBoxes (#3477)
+ - AMP connections are closed as soon as a key length larger than 255 is
+ received (#3478)
+ - Log events with timezone offsets between -1 and -59 minutes are now
+ correctly reported as negative (#3515)
+
+Deprecations and Removals
+-------------------------
+ - Trial's setUpClass and tearDownClass methods are now deprecated (#2903)
+ - problemsFromTransport has been removed in favor of the argument passed to
+ connectionLost (#2874)
+ - The mode parameter to methods of IReactorUNIX and IReactorUNIXDatagram are
+ deprecated in favor of applications taking other security precautions, since
+ the mode of a Unix socket is often not respected (#1068)
+ - Index access on instances of twisted.internet.defer.FirstError has been
+ removed in favor of the subFailure attribute (#3298)
+ - The 'changeDirectory' method of FTPClient has been deprecated in favor of
+ the 'cwd' method (#3491)
+
+Other
+-----
+
+ - #3202, #2869, #3225, #2955, #3237, #3196, #2355, #2881, #3054, #2374, #2918,
+ #3210, #3052, #3267, #3288, #2985, #3295, #3297, #2512, #3302, #1222, #2631,
+ #3306, #3116, #3215, #1489, #3319, #3320, #3321, #1255, #2169, #3182, #3323,
+ #3301, #3318, #3029, #3338, #3346, #1144, #3173, #3165, #685, #3357, #2582,
+ #3370, #2438, #1253, #637, #1971, #2208, #979, #1790, #1888, #1882, #1793,
+ #754, #1890, #1931, #1246, #1025, #3177, #2496, #2567, #3400, #2213, #2027,
+ #3415, #1262, #3422, #2500, #3414, #3045, #3111, #2974, #2947, #3222, #2878,
+ #3402, #2909, #3423, #1328, #1852, #3382, #3393, #2029, #3489, #1853, #2026,
+ #2375, #3502, #3482, #3504, #3505, #3507, #2605, #3519, #3520, #3121, #3484,
+ #3439, #3216, #3511, #3524, #3521, #3197, #2486, #2449, #2748, #3381, #3236,
+ #671
+
+
+Conch 8.2.0 (2008-12-16)
+========================
+
+Features
+--------
+ - The type of the protocols instantiated by SSHFactory is now parameterized
+ (#3443)
+
+Fixes
+-----
+ - A file descriptor leak has been fixed (#3213, #1789)
+ - "File Already Exists" errors are now handled more correctly (#3033)
+ - Handling of CR IAC in TelnetClient is now improved (#3305)
+ - SSHAgent is no longer completely unusable (#3332)
+ - The performance of insults.ClientProtocol is now greatly increased by
+ delivering more than one byte at a time to application code (#3386)
+ - Manhole and the conch server no longer need to be run as root when not
+ necessary (#2607)
+ - The value of FILEXFER_ATTR_ACMODTIME has been corrected (#2902)
+ - The management of known_hosts and host key verification has been overhauled
+ (#1376, #1301, #3494, #3496, #1292, #3499)
+
+Other
+-----
+ - #3193, #1633
+
+
+Lore 8.2.0 (2008-12-16)
+=======================
+
+Other
+-----
+ - #2207, #2514
+
+
+Mail 8.2.0 (2008-12-16)
+=======================
+
+Fixes
+-----
+ - The mailmail tool now provides better error messages for usage errors (#3339)
+ - The SMTP protocol implementation now works on PyPy (#2976)
+
+Other
+-----
+ - #3475
+
+
+Names 8.2.0 (2008-12-16)
+========================
+
+Features
+--------
+ - The NAPTR record type is now supported (#2276)
+
+Fixes
+-----
+ - Make client.Resolver less vulnerable to the Birthday Paradox attack by
+ avoiding sending duplicate queries when it's not necessary (#3347)
+ - client.Resolver now uses a random source port for each DNS request (#3342)
+ - client.Resolver now uses a full 16 bits of randomness for message IDs,
+ instead of 10 which it previously used (#3342)
+ - All record types now have value-based equality and a string representation
+ (#2935)
+
+Other
+-----
+ - #1622, #3424
+
+
+Web 8.2.0 (2008-12-16)
+======================
+
+Features
+--------
+ - The web server can now deal with multi-value headers in the new attributes of
+ Request, requestHeaders and responseHeaders (#165)
+ - There is now a resource-wrapper which implements HTTP Basic and Digest auth
+ in terms of twisted.cred (#696)
+ - It's now possible to limit the number of redirects that client.getPage will
+ follow (#2412)
+ - The directory-listing code no longer uses Woven (#3257)
+ - static.File now supports Range headers with a single range (#1493)
+ - twisted.web now has a rudimentary WSGI container (#2753)
+ - The web server now supports chunked encoding in requests (#3385)
+
+Fixes
+-----
+ - The xmlrpc client now raises an error when the server sends an empty
+ response (#3399)
+ - HTTPPageGetter no longer duplicates default headers when they're explicitly
+ overridden in the headers parameter (#1382)
+ - The server will no longer timeout clients which are still sending request
+ data (#1903)
+ - microdom's isEqualToNode now returns False when the nodes aren't equal
+ (#2542)
+
+Deprecations and Removals
+-------------------------
+
+ - Request.headers and Request.received_headers are not quite deprecated, but
+ they are discouraged in favor of requestHeaders and responseHeaders (#165)
+
+Other
+-----
+ - #909, #687, #2938, #1152, #2930, #2025, #2683, #3471
+
+
+Web2 8.2.0 (2008-12-16)
+=======================
+
+Note: Twisted Web2 is being phased out in preference for Twisted Web, but some
+maintenance changes have been made.
+
+Fixes
+-----
+ - The main twisted.web2 docstring now indicates the current state of the
+ project (#2028)
+ - Headers which require unusual bytes are now quoted (#2346)
+ - Some links in the introduction documentation have been fixed (#2552)
+
+
+Words 8.2.0 (2008-12-16)
+========================
+
+Feature
+-------
+ - There is now a standalone XMPP router included in twisted.words: it can be
+ used with the 'twistd xmpp-router' command line (#3407)
+ - A server factory for Jabber XML Streams has been added (#3435)
+ - Domish now allows for iterating child elements with specific qualified names
+ (#2429)
+ - IRCClient now has a 'back' method which removes the away status (#3366)
+ - IRCClient now has a 'whois' method (#3133)
+
+Fixes
+-----
+ - The IRC Client implementation can now deal with compound mode changes (#3230)
+ - The MSN protocol implementation no longer requires the CVR0 protocol to
+ be included in the VER command (#3394)
+ - In the IRC server implementation, topic messages will no longer be sent for
+ a group which has no topic (#2204)
+ - An infinite loop (which caused infinite memory usage) in irc.split has been
+ fixed. This was triggered any time a message that starts with a delimiter
+ was sent (#3446)
+ - Jabber's toResponse now generates a valid stanza even when stanzaType is not
+ specified (#3467)
+ - The lifetime of authenticator instances in XmlStreamServerFactory is no
+ longer artificially extended (#3464)
+
+Other
+-----
+ - #3365
+
+
+Core 8.1.0 (2008-05-18)
+=======================
+
+Features
+--------
+
+ - twisted.internet.error.ConnectionClosed is a new exception which is the
+ superclass of ConnectionLost and ConnectionDone (#3137)
+ - Trial's CPU and memory performance should be better now (#3034)
+ - twisted.python.filepath.FilePath now has a chmod method (#3124)
+
+Fixes
+-----
+
+ - Some reactor re-entrancy regressions were fixed (#3146, #3168)
+ - A regression was fixed whereby constructing a Failure for an exception and
+ traceback raised out of a Pyrex extension would fail (#3132)
+ - CopyableFailures in PB can again be created from CopiedFailures (#3174)
+ - FilePath.remove, when called on a FilePath representing a symlink to a
+ directory, no longer removes the contents of the targeted directory, and
+ instead removes the symlink (#3097)
+ - FilePath now has a linkTo method for creating new symlinks (#3122)
+ - The docstring for Trial's addCleanup method now correctly specifies when
+ cleanup functions are run (#3131)
+ - assertWarns now deals better with multiple identical warnings (#2904)
+ - Various windows installer bugs were fixed (#3115, #3144, #3150, #3151, #3164)
+ - API links in the howto documentation have been corrected (#3130)
+ - The Win32 Process transport object now has a pid attribute (#1836)
+ - A doc bug in the twistd plugin howto which would inevitably lead to
+ confusion was fixed (#3183)
+ - A regression breaking IOCP introduced after the last release was fixed
+ (#3200)
+
+Deprecations and Removals
+-------------------------
+
+ - mktap is now fully deprecated, and will emit DeprecationWarnings when used
+ (#3127)
+
+Other
+-----
+ - #3079, #3118, #3120, #3145, #3069, #3149, #3186, #3208, #2762
+
+
+Conch 8.1.0 (2008-05-18)
+========================
+
+Fixes
+-----
+ - A regression was fixed whereby the publicKeys and privateKeys attributes of
+ SSHFactory would not be interpreted as strings (#3141)
+ - The sshsimpleserver.py example had a minor bug fix (#3135)
+ - The deprecated mktap API is no longer used (#3127)
+ - An infelicity was fixed whereby a NameError would be raised in certain
+ circumstances during authentication when a ConchError should have been
+ (#3154)
+ - A workaround was added to conch.insults for a bug in gnome-terminal whereby
+ it would not scroll correctly (#3189)
+
+
+Lore 8.1.0 (2008-05-18)
+=======================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+News 8.1.0 (2008-05-18)
+=======================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+Web 8.1.0 (2008-05-18)
+======================
+
+Fixes
+-----
+ - Fixed an XMLRPC bug whereby sometimes a callRemote Deferred would
+ accidentally be fired twice when a connection was lost during the handling of
+ a response (#3152)
+ - Fixed a bug in the "Using Twisted Web" document which prevented an example
+ resource from being renderable (#3147)
+ - The deprecated mktap API is no longer used (#3127)
+
+
+Words 8.1.0 (2008-05-18)
+========================
+
+Features
+--------
+ - JID objects now have a nice __repr__ (#3156)
+ - Extending XMPP protocols is now easier (#2178)
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+ - A bug whereby one-time XMPP observers would be enabled permanently was fixed
+ (#3066)
+
+
+Mail 8.1.0 (2008-05-18)
+=======================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+Names 8.1.0 (2008-05-18)
+========================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+Web2 8.1.0 (2008-05-18)
+=======================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+Core 8.0.1 (2008-03-26)
+=======================
+
+Fixes
+-----
+ - README no longer refers to obsolete trial command line option
+ - twistd no longer causes a bizarre DeprecationWarning about mktap
+
+
+Core 8.0.0 (2008-03-17)
+=======================
+
+Features
+--------
+
+ - The IOCP reactor has had many changes and is now greatly improved
+ (#1760, #3055)
+ - The main Twisted distribution is now easy_installable (#1286, #3110)
+ - twistd can now profile with cProfile (#2469)
+ - twisted.internet.defer contains a DeferredFilesystemLock which gives a
+ Deferred interface to lock file acquisition (#2180)
+ - twisted.python.modules is a new system for representing and manipulating
+ module paths (i.e. sys.path) (#1951)
+ - twisted.internet.fdesc now contains a writeToFD function, along with other
+ minor fixes (#2419)
+ - twisted.python.usage now allows optional type enforcement (#739)
+ - The reactor now has a blockingCallFromThread method for non-reactor threads
+ to use to wait for a reactor-scheduled call to return a result (#1042, #3030)
+ - Exceptions raised inside of inlineCallbacks-using functions now have a
+ better chance of coming with a meaningful traceback (#2639, #2803)
+ - twisted.python.randbytes now contains code for generating secure random
+ bytes (#2685)
+ - The classes in twisted.application.internet now accept a reactor parameter
+ for specifying the reactor to use for underlying calls to allow for better
+ testability (#2937)
+ - LoopingCall now allows you to specify the reactor to use to schedule new
+ calls, allowing much better testing techniques (#2633, #2634)
+ - twisted.internet.task.deferLater is a new API for scheduling calls and
+ getting deferreds which are fired with their results (#1875)
+ - objgrep now knows how to search through deque objects (#2323)
+ - twisted.python.log now contains a Twisted log observer which can forward
+ messages to the Python logging system (#1351)
+ - Log files now include seconds in the timestamps (#867)
+ - It is now possible to limit the number of log files to create during log
+ rotation (#1095)
+ - The interface required by the log context system is now documented as
+ ILoggingContext, and abstract.FileDescriptor now declares that it implements
+ it (#1272)
+ - There is now an example cred checker that uses a database via adbapi (#460)
+ - The epoll reactor is now documented in the choosing-reactors howto (#2539)
+ - There were improvements to the client howto (#222)
+ - Int8Receiver was added (#2315)
+ - Various refactorings to AMP introduced better testability and public
+ interfaces (#2657, #2667, #2656, #2664, #2810)
+ - twisted.protocol.policies.TrafficLoggingFactory now has a resetCounter
+ method (#2757)
+ - The FTP client can be told which port range within which to bind passive
+ transfer ports (#1904)
+ - twisted.protocols.memcache contains a new asynchronous memcache client
+ (#2506, #2957)
+ - PB now supports anonymous login (#439, #2312)
+ - twisted.spread.jelly now supports decimal objects (#2920)
+ - twisted.spread.jelly now supports all forms of sets (#2958)
+ - There is now an interface describing the API that process protocols must
+ provide (#3020)
+ - Trial reporting to core unittest TestResult objects has been improved (#2495)
+ - Trial's TestCase now has an addCleanup method which allows easy setup of
+ tear-down code (#2610, #2899)
+ - Trial's TestCase now has an assertIsInstance method (#2749)
+ - Trial's memory footprint and speed are greatly improved (#2275)
+ - At the end of trial runs, "PASSED" and "FAILED" messages are now colorized
+ (#2856)
+ - Tests which leave global state around in the reactor will now fail in
+ trial. A new option, --unclean-warnings, will convert these errors back into
+ warnings (#2091)
+ - Trial now has a --without-module command line for testing code in an
+ environment that lacks a particular Python module (#1795)
+ - Error reporting of failed assertEquals assertions now has much nicer
+ formatting (#2893)
+ - Trial now has methods for monkey-patching (#2598)
+ - Trial now has an ITestCase (#2898, #1950)
+ - The trial reporter API now has a 'done' method which is called at the end of
+ a test run (#2883)
+ - TestCase now has an assertWarns method which allows testing that functions
+ emit warnings (#2626, #2703)
+ - There are now no string exceptions in the entire Twisted code base (#2063)
+ - There is now a system for specifying credentials checkers with a string
+ (#2570)
+
+Fixes
+-----
+
+ - Some tests which were asserting the value of stderr have been changed
+ because Python uncontrollably writes bytes to stderr (#2405)
+ - Log files handle time zones with DST better (#2404)
+ - Subprocesses using PTYs on OS X that are handled by Twisted will now be able
+ to more reliably write the final bytes before they exit, allowing Twisted
+ code to more reliably receive them (#2371, #2858)
+ - Trial unit test reporting has been improved (#1901)
+ - The kqueue reactor handles connection failures better (#2172)
+ - It's now possible to run "trial foo/bar/" without an exception: trailing
+ slashes no longer cause problems (#2005)
+ - cred portals now better deal with implementations of inherited interfaces
+ (#2523)
+ - FTP error handling has been improved (#1160, 1107)
+ - Trial behaves better with respect to file locking on Windows (#2482)
+ - The FTP server now gives a better error when STOR is attempted during an
+ anonymous session (#1575)
+ - Trial now behaves better with tests that use the reactor's threadpool (#1832)
+ - twisted.python.reload now behaves better with new-style objects (#2297)
+ - LogFile's defaultMode parameter is now better implemented, preventing
+ potential security exploits (#2586)
+ - A minor obscure leak in thread pools was corrected (#1134)
+ - twisted.internet.task.Clock now returns the correct DelayedCall from
+ callLater, instead of returning the one scheduled for the furthest in the
+ future (#2691)
+ - twisted.spread.util.FilePager no longer unnecessarily buffers data in
+ memory (#1843, 2321)
+ - Asking for twistd or trial to use an unavailable reactor no longer prints a
+ traceback (#2457)
+ - System event triggers have fewer obscure bugs (#2509)
+ - Plugin discovery code is much better behaved, allowing multiple
+ installations of a package with plugins (#2339, #2769)
+ - Process and PTYProcess have been merged and some minor bugs have been fixed
+ (#2341)
+ - The reactor has less global state (#2545)
+ - Failure can now correctly represent and format errors caused by string
+ exceptions (#2830)
+ - The epoll reactor now has better error handling which now avoids the bug
+ causing 100% CPU usage in some cases (#2809)
+ - Errors raised during trial setUp or tearDown methods are now handled better
+ (#2837)
+ - A problem when deferred callbacks add new callbacks to the deferred that
+ they are a callback of was fixed (#2849)
+ - Log messages that are emitted during connectionMade now have the protocol
+ prefix correctly set (#2813)
+ - The string representation of a TCP Server connection now contains the actual
+ port that it's bound to when it was configured to listen on port 0 (#2826)
+ - There is better reporting of error codes for TCP failures on Windows (#2425)
+ - Process spawning has been made slightly more robust by disabling garbage
+ collection temporarily immediately after forking so that finalizers cannot
+ be executed in an unexpected environment (#2483)
+ - namedAny now detects import errors better (#698)
+ - Many fixes and improvements to the twisted.python.zipstream module have
+ been made (#2996)
+ - FilePager no longer blows up on empty files (#3023)
+ - twisted.python.util.FancyEqMixin has been improved to cooperate with objects
+ of other types (#2944)
+ - twisted.python.FilePath.exists now restats to prevent incorrect result
+ (#2896)
+ - twisted.python.util.mergeFunctionMetadata now also merges the __module__
+ attribute (#3049)
+ - It is now possible to call transport.pauseProducing within connectionMade on
+ TCP transports without it being ignored (#1780)
+ - twisted.python.versions now understands new SVN metadata format for fetching
+ the SVN revision number (#3058)
+ - It's now possible to use reactor.callWhenRunning(reactor.stop) on gtk2 and
+ glib2 reactors (#3011)
+
+Deprecations and removals
+-------------------------
+ - twisted.python.timeoutqueue is now deprecated (#2536)
+ - twisted.enterprise.row and twisted.enterprise.reflector are now deprecated
+ (#2387)
+ - twisted.enterprise.util is now deprecated (#3022)
+ - The dispatch and dispatchWithCallback methods of ThreadPool are now
+ deprecated (#2684)
+ - Starting the same reactor multiple times is now deprecated (#1785)
+ - The visit method of various test classes in trial has been deprecated (#2897)
+ - The --report-profile option to twistd and twisted.python.dxprofile are
+ deprecated (#2908)
+ - The upDownError method of Trial reporters is deprecated (#2883)
+
+Other
+-----
+
+ - #2396, #2211, #1921, #2378, #2247, #1603, #2463, #2530, #2426, #2356, #2574,
+ - #1844, #2575, #2655, #2640, #2670, #2688, #2543, #2743, #2744, #2745, #2746,
+ - #2742, #2741, #1730, #2831, #2216, #1192, #2848, #2767, #1220, #2727, #2643,
+ - #2669, #2866, #2867, #1879, #2766, #2855, #2547, #2857, #2862, #1264, #2735,
+ - #942, #2885, #2739, #2901, #2928, #2954, #2906, #2925, #2942, #2894, #2793,
+ - #2761, #2977, #2968, #2895, #3000, #2990, #2919, #2969, #2921, #3005, #421,
+ - #3031, #2940, #1181, #2783, #1049, #3053, #2847, #2941, #2876, #2886, #3086,
+ - #3095, #3109
+
+
+Conch 8.0.0 (2008-03-17)
+========================
+
+Features
+--------
+ - Add DEC private mode manipulation methods to ITerminalTransport. (#2403)
+
+Fixes
+-----
+ - Parameterize the scheduler function used by the insults TopWindow widget.
+ This change breaks backwards compatibility in the TopWindow initializer.
+ (#2413)
+ - Notify subsystems, like SFTP, of connection close. (#2421)
+ - Change the process file descriptor "connection lost" code to reverse the
+ setNonBlocking operation done during initialization. (#2371)
+ - Change ConsoleManhole to wait for connectionLost notification before
+ stopping the reactor. (#2123, #2371)
+ - Make SSHUserAuthServer.ssh_USERAUTH_REQUEST return a Deferred. (#2528)
+ - Manhole's initializer calls its parent class's initializer with its
+ namespace argument. (#2587)
+ - Handle ^C during input line continuation in manhole by updating the prompt
+ and line buffer correctly. (#2663)
+ - Make twisted.conch.telnet.Telnet by default reject all attempts to enable
+ options. (#1967)
+ - Reduce the number of calls into application code to deliver application-level
+ data in twisted.conch.telnet.Telnet.dataReceived (#2107)
+ - Fix definition and management of extended attributes in conch file transfer.
+ (#3010)
+ - Fix parsing of OpenSSH-generated RSA keys with differing ASN.1 packing style.
+ (#3008)
+ - Fix handling of missing $HOME in twisted.conch.client.unix. (#3061)
+
+Misc
+----
+ - #2267, #2378, #2604, #2707, #2341, #2685, #2679, #2912, #2977, #2678, #2709
+ #2063, #2847
+
+
+Lore 8.0.0 (2008-03-17)
+=======================
+
+Fixes
+-----
+ - Change twisted.lore.tree.setIndexLin so that it removes node with index-link
+ class when the specified index filename is None. (#812)
+ - Fix the conversion of the list of options in man pages to Lore format.
+ (#3017)
+ - Fix conch man pages generation. (#3075)
+ - Fix management of the interactive command tag in man2lore. (#3076)
+
+Misc
+----
+ - #2847
+
+
+News 8.0.0 (2008-03-17)
+=======================
+
+Misc
+----
+ - Remove all "API Stability" markers (#2847)
+
+
+Runner 8.0.0 (2008-03-17)
+=========================
+
+Misc
+----
+ - Remove all "API Stability" markers (#2847)
+
+
+Web 8.0.0 (2008-03-17)
+======================
+
+Features
+--------
+ - Add support to twisted.web.client.getPage for the HTTP HEAD method. (#2750)
+
+Fixes
+-----
+ - Set content-type in xmlrpc responses to "text/xml" (#2430)
+ - Add more error checking in the xmlrpc.XMLRPC render method, and enforce
+ POST requests. (#2505)
+ - Reject unicode input to twisted.web.client._parse to reject invalid
+ unicode URLs early. (#2628)
+ - Correctly re-quote URL path segments when generating an URL string to
+ return from Request.prePathURL. (#2934)
+ - Make twisted.web.proxy.ProxyClientFactory close the connection when
+ reporting a 501 error. (#1089)
+ - Fix twisted.web.proxy.ReverseProxyResource to specify the port in the
+ host header if different from 80. (#1117)
+ - Change twisted.web.proxy.ReverseProxyResource so that it correctly encodes
+ the request URI it sends on to the server for which it is a proxy. (#3013)
+ - Make "twistd web --personal" use PBServerFactory (#2681)
+
+Misc
+----
+ - #1996, #2382, #2211, #2633, #2634, #2640, #2752, #238, #2905
+
+
+Words 8.0.0 (2008-03-17)
+========================
+
+Features
+--------
+ - Provide function for creating XMPP response stanzas. (#2614, #2614)
+ - Log exceptions raised in Xish observers. (#2616)
+ - Add 'and' and 'or' operators for Xish XPath expressions. (#2502)
+ - Make JIDs hashable. (#2770)
+
+Fixes
+-----
+ - Respect the hostname and servername parameters to IRCClient.register. (#1649)
+ - Make EventDispatcher remove empty callback lists. (#1652)
+ - Use legacy base64 API to support Python 2.3 (#2461)
+ - Fix support of DIGEST-MD5 challenge parsing with multi-valued directives.
+ (#2606)
+ - Fix reuse of dict of prefixes in domish.Element.toXml (#2609)
+ - Properly process XMPP stream headers (#2615)
+ - Use proper namespace for XMPP stream errors. (#2630)
+ - Properly parse XMPP stream errors. (#2771)
+ - Fix toResponse for XMPP stanzas without an id attribute. (#2773)
+ - Move XMPP stream header procesing to authenticators. (#2772)
+
+Misc
+----
+ - #2617, #2640, #2741, #2063, #2570, #2847
+
+
+Mail 8.0.0 (2008-03-17)
+=======================
+
+Features
+--------
+ - Support CAPABILITY responses that include atoms of the form "FOO" and
+ "FOO=BAR" in IMAP4 (#2695)
+ - Parameterize error handling behavior of imap4.encoder and imap4.decoder.
+ (#2929)
+
+Fixes
+-----
+ - Handle empty passwords in SMTP auth. (#2521)
+ - Fix IMAP4Client's parsing of literals which are not preceeded by whitespace.
+ (#2700)
+ - Handle MX lookup suceeding without answers. (#2807)
+ - Fix issues with aliases(5) process support. (#2729)
+
+Misc
+----
+ - #2371, #2123, #2378, #739, #2640, #2746, #1917, #2266, #2864, #2832, #2063,
+ #2865, #2847
+
+
+Names 8.0.0 (2008-03-17)
+========================
+
+Fixes
+-----
+
+ - Refactor DNSDatagramProtocol and DNSProtocol to use same base class (#2414)
+ - Change Resolver to query specified nameservers in specified order, instead
+ of reverse order. (#2290)
+ - Make SRVConnector work with bad results and NXDOMAIN responses.
+ (#1908, #2777)
+ - Handle write errors happening in dns queries, to have correct deferred
+ failures. (#2492)
+ - Fix the value of OP_NOTIFY and add a definition for OP_UPDATE. (#2945)
+
+Misc
+----
+ - #2685, #2936, #2581, #2847
+
diff --git a/vendor/Twisted-10.0.0/README b/vendor/Twisted-10.0.0/README
new file mode 100644
index 0000000000..4ccf085e09
--- /dev/null
+++ b/vendor/Twisted-10.0.0/README
@@ -0,0 +1,118 @@
+Twisted 10.0.0
+
+Quote of the Release:
+
+ [on picking the quote of the release]
+ <glyph> Man, we're going to have to get a lot funnier if we're going
+ to do time-based releases
+
+
+For information on what's new in Twisted 10.0.0, see the NEWS file that comes
+with the distribution.
+
+What is this?
+=============
+
+ Twisted is an event-based framework for internet applications which works on
+ Python 2.4 through 2.6. The following are some of the modules included
+ with Twisted::
+
+ - twisted.application
+ A "Service" system that allows you to organize your application in
+ hierarchies with well-defined startup and dependency semantics,
+ - twisted.cred
+ A general credentials and authentication system that facilitates
+ pluggable authentication backends,
+ - twisted.enterprise
+ Asynchronous database access, compatible with any Python DBAPI2.0
+ modules,
+ - twisted.internet
+ Low-level asynchronous networking APIs that allow you to define
+ your own protocols that run over certain transports,
+ - twisted.manhole
+ A tool for remote debugging of your services which gives you a
+ Python interactive interpreter,
+ - twisted.protocols
+ Basic protocol implementations and helpers for your own protocol
+ implementations,
+ - twisted.python
+ A large set of utilities for Python tricks, reflection, text
+ processing, and anything else,
+ - twisted.spread
+ A secure, fast remote object system,
+ - twisted.trial
+ A unit testing framework that integrates well with Twisted-based code.
+
+ Twisted supports integration of the Tk, GTK+, GTK+ 2, Qt, Mac OS X,
+ or wxPython event loop with its main event loop. The Win32 event
+ loop is also supported.
+
+ For more information, visit http://www.twistedmatrix.com, or join the list
+ at http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
+
+ There are many official Twisted subprojects, including clients and
+ servers for web, mail, DNS, and more. You can find out more about
+ these projects at http://twistedmatrix.com/trac/wiki/TwistedProjects
+
+
+Installing
+==========
+
+ Instructions for installing this software are in INSTALL.
+
+Unit Tests
+==========
+
+
+ See our unit tests run proving that the software is BugFree(TM)::
+
+ % trial twisted
+
+ Some of these tests may fail if you
+ * don't have the dependancies required for a particular subsystem installed,
+ * have a firewall blocking some ports (or things like Multicast, which Linux
+ NAT has shown itself to do), or
+ * run them as root.
+
+
+Documentation and Support
+=========================
+
+ Examples on how to use Twisted APIs are located in doc/examples;
+ this might ease the learning curve a little bit, since all these
+ files are kept as short as possible. The file doc/howto/index.xhtml
+ contains an index of all the HOWTOs: this should be your starting
+ point when looking for documentation.
+
+ Help is available on the Twisted mailing list::
+
+ http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
+
+ There is also a very lively IRC channel, #twisted, on
+ irc.freenode.net.
+
+
+Copyright
+=========
+
+ All of the code in this distribution is Copyright (c) 2001-2010
+ Twisted Matrix Laboratories.
+
+ Twisted is made available under the MIT license. The included
+ LICENSE file describes this in detail.
+
+
+Warranty
+========
+
+ THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+ EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+ TO THE USE OF THIS SOFTWARE IS WITH YOU.
+
+ IN NO EVENT WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+ AND/OR REDISTRIBUTE THE LIBRARY, BE LIABLE TO YOU FOR ANY DAMAGES, EVEN IF
+ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ DAMAGES.
+
+ Again, see the included LICENSE file for specific legal details.
diff --git a/vendor/Twisted-10.0.0/bin/.twistd.swp b/vendor/Twisted-10.0.0/bin/.twistd.swp
new file mode 100644
index 0000000000..2f2e963230
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/.twistd.swp
Binary files differ
diff --git a/vendor/Twisted-10.0.0/bin/conch/cftp b/vendor/Twisted-10.0.0/bin/conch/cftp
new file mode 100755
index 0000000000..3e8f1747a2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/conch/cftp
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os
+path = os.path.abspath(sys.argv[0])
+while os.path.dirname(path) != path:
+ if os.path.basename(path).startswith('Twisted'):
+ sys.path.insert(0, path)
+ break
+ path = os.path.dirname(path)
+### end of preamble
+
+from twisted.conch.scripts.cftp import run
+run()
diff --git a/vendor/Twisted-10.0.0/bin/conch/ckeygen b/vendor/Twisted-10.0.0/bin/conch/ckeygen
new file mode 100755
index 0000000000..26a213e0d9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/conch/ckeygen
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os
+path = os.path.abspath(sys.argv[0])
+while os.path.dirname(path) != path:
+ if os.path.basename(path).startswith('Twisted'):
+ sys.path.insert(0, path)
+ break
+ path = os.path.dirname(path)
+### end of preamble
+
+from twisted.conch.scripts.ckeygen import run
+run()
diff --git a/vendor/Twisted-10.0.0/bin/conch/conch b/vendor/Twisted-10.0.0/bin/conch/conch
new file mode 100755
index 0000000000..a4c10a53b6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/conch/conch
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os
+path = os.path.abspath(sys.argv[0])
+while os.path.dirname(path) != path:
+ if os.path.basename(path).startswith('Twisted'):
+ sys.path.insert(0, path)
+ break
+ path = os.path.dirname(path)
+### end of preamble
+
+from twisted.conch.scripts.conch import run
+run()
diff --git a/vendor/Twisted-10.0.0/bin/conch/tkconch b/vendor/Twisted-10.0.0/bin/conch/tkconch
new file mode 100755
index 0000000000..4a980d3c81
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/conch/tkconch
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os
+path = os.path.abspath(sys.argv[0])
+while os.path.dirname(path) != path:
+ if os.path.basename(path).startswith('Twisted'):
+ sys.path.insert(0, path)
+ break
+ path = os.path.dirname(path)
+### end of preamble
+
+from twisted.conch.scripts.tkconch import run
+run()
diff --git a/vendor/Twisted-10.0.0/bin/lore/lore b/vendor/Twisted-10.0.0/bin/lore/lore
new file mode 100755
index 0000000000..2b7cb2c10d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/lore/lore
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os
+path = os.path.abspath(sys.argv[0])
+while os.path.dirname(path) != path:
+ if os.path.basename(path).startswith('Twisted'):
+ sys.path.insert(0, path)
+ break
+ path = os.path.dirname(path)
+### end of preamble
+
+from twisted.lore.scripts.lore import run
+run()
+
diff --git a/vendor/Twisted-10.0.0/bin/mail/mailmail b/vendor/Twisted-10.0.0/bin/mail/mailmail
new file mode 100755
index 0000000000..ef2fca7369
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/mail/mailmail
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+This script attempts to send some email.
+"""
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os
+path = os.path.abspath(sys.argv[0])
+while os.path.dirname(path) != path:
+ if os.path.basename(path).startswith('Twisted'):
+ sys.path.insert(0, path)
+ break
+ path = os.path.dirname(path)
+### end of preamble
+
+from twisted.mail.scripts import mailmail
+mailmail.run()
+
diff --git a/vendor/Twisted-10.0.0/bin/manhole b/vendor/Twisted-10.0.0/bin/manhole
new file mode 100755
index 0000000000..f98cf9831c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/manhole
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+This script runs GtkManhole, a client for Twisted.Manhole
+"""
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os, string
+if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1:
+ sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)))
+### end of preamble
+
+
+from twisted.scripts import manhole
+manhole.run()
diff --git a/vendor/Twisted-10.0.0/bin/mktap b/vendor/Twisted-10.0.0/bin/mktap
new file mode 100755
index 0000000000..a7c9d067df
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/mktap
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os, string
+if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1:
+ sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)))
+if not hasattr(os, "getuid") or os.getuid() != 0:
+ sys.path.insert(0, os.getcwd())
+### end of preamble
+
+from twisted.scripts.mktap import run
+run()
+
diff --git a/vendor/Twisted-10.0.0/bin/pyhtmlizer b/vendor/Twisted-10.0.0/bin/pyhtmlizer
new file mode 100755
index 0000000000..e6155587cf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/pyhtmlizer
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os, string
+if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1:
+ sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)))
+sys.path.insert(0, os.curdir)
+### end of preamble
+
+from twisted.scripts.htmlizer import run
+run()
diff --git a/vendor/Twisted-10.0.0/bin/tap2deb b/vendor/Twisted-10.0.0/bin/tap2deb
new file mode 100755
index 0000000000..dd1ee8e642
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/tap2deb
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+tap2deb
+"""
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os, string
+if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1:
+ sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)))
+### end of preamble
+
+from twisted.scripts import tap2deb
+tap2deb.run()
diff --git a/vendor/Twisted-10.0.0/bin/tap2rpm b/vendor/Twisted-10.0.0/bin/tap2rpm
new file mode 100755
index 0000000000..bc789b2074
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/tap2rpm
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# based off the tap2deb code
+# tap2rpm built by Sean Reifschneider, <jafo@tummy.com>
+
+"""
+tap2rpm
+"""
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os, string
+if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1:
+ sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)))
+### end of preamble
+
+from twisted.scripts import tap2rpm
+tap2rpm.run()
diff --git a/vendor/Twisted-10.0.0/bin/tapconvert b/vendor/Twisted-10.0.0/bin/tapconvert
new file mode 100755
index 0000000000..6ad2d7f42c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/tapconvert
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os, string
+if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1:
+ sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)))
+if not hasattr(os, "getuid") or os.getuid() != 0:
+ sys.path.insert(0, os.getcwd())
+### end of preamble
+
+from twisted.scripts.tapconvert import run
+run()
diff --git a/vendor/Twisted-10.0.0/bin/trial b/vendor/Twisted-10.0.0/bin/trial
new file mode 100755
index 0000000000..963a9068d8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/trial
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os, string
+if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1:
+ sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)))
+if hasattr(os, "getuid") and os.getuid() != 0:
+ sys.path.insert(0, os.curdir)
+### end of preamble
+
+# begin chdir armor
+sys.path[:] = map(os.path.abspath, sys.path)
+# end chdir armor
+
+from twisted.scripts.trial import run
+run()
diff --git a/vendor/Twisted-10.0.0/bin/twistd b/vendor/Twisted-10.0.0/bin/twistd
new file mode 100755
index 0000000000..7ec65ded79
--- /dev/null
+++ b/vendor/Twisted-10.0.0/bin/twistd
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os, string
+if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1:
+ sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)))
+if hasattr(os, "getuid") and os.getuid() != 0:
+ sys.path.insert(0, os.path.abspath(os.getcwd()))
+### end of preamble
+
+
+from twisted.scripts.twistd import run
+run()
diff --git a/vendor/Twisted-10.0.0/doc/conch/benchmarks/README b/vendor/Twisted-10.0.0/doc/conch/benchmarks/README
new file mode 100644
index 0000000000..233bc8e4f7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/benchmarks/README
@@ -0,0 +1,15 @@
+This directory contains various simple programs intended to exercise various
+features of Twisted Conch as a way to learn about and track their
+performance characteristics. As there is currently no record of past
+benchmark results, the tracking aspect of this is currently somewhat
+fantastic. However, the intent is for this to change at some future point.
+
+All (one) of the programs in this directory are currently intended to be
+invoked directly and to report some timing information on standard out.
+
+The following benchmarks are currently available:
+
+buffering_mixin.py:
+
+ This deals with twisted.conch.mixin.BufferingMixin which provides
+ Nagle-like write coalescing for Protocol classes.
diff --git a/vendor/Twisted-10.0.0/doc/conch/benchmarks/buffering_mixin.py b/vendor/Twisted-10.0.0/doc/conch/benchmarks/buffering_mixin.py
new file mode 100755
index 0000000000..b3c506a727
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/benchmarks/buffering_mixin.py
@@ -0,0 +1,182 @@
+# Copyright (c) 2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Benchmarks comparing the write performance of a "normal" Protocol instance
+and an instance of a Protocol class which has had L{twisted.conch.mixin}'s
+L{BufferingMixin<twisted.conch.mixin.BufferingMixin>} mixed in to perform
+Nagle-like write coalescing.
+"""
+
+from sys import stdout
+from pprint import pprint
+from time import time
+
+from twisted.python.usage import Options
+from twisted.python.log import startLogging
+
+from twisted.internet.protocol import ServerFactory, Protocol, ClientCreator
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
+
+from twisted.conch.mixin import BufferingMixin
+
+
+class BufferingBenchmark(Options):
+ """
+ Options for configuring the execution parameters of a benchmark run.
+ """
+
+ optParameters = [
+ ('scale', 's', '1',
+ 'Work multiplier (bigger takes longer, might resist noise better)')]
+
+ def postOptions(self):
+ self['scale'] = int(self['scale'])
+
+
+
+class ServerProtocol(Protocol):
+ """
+ A silent protocol which only waits for a particular amount of input and
+ then fires a Deferred.
+ """
+ def __init__(self, expected, finished):
+ self.expected = expected
+ self.finished = finished
+
+
+ def dataReceived(self, bytes):
+ self.expected -= len(bytes)
+ if self.expected == 0:
+ finished, self.finished = self.finished, None
+ finished.callback(None)
+
+
+
+class BufferingProtocol(Protocol, BufferingMixin):
+ """
+ A protocol which uses the buffering mixin to provide a write method.
+ """
+
+
+
+class UnbufferingProtocol(Protocol):
+ """
+ A protocol which provides a naive write method which simply passes through
+ to the transport.
+ """
+
+ def connectionMade(self):
+ """
+ Bind write to the transport's write method and flush to a no-op
+ function in order to provide the same API as is provided by
+ BufferingProtocol.
+ """
+ self.write = self.transport.write
+ self.flush = lambda: None
+
+
+
+def _write(proto, byteCount):
+ write = proto.write
+ flush = proto.flush
+
+ for i in range(byteCount):
+ write('x')
+ flush()
+
+
+
+def _benchmark(byteCount, clientProtocol):
+ result = {}
+ finished = Deferred()
+ def cbFinished(ignored):
+ result[u'disconnected'] = time()
+ result[u'duration'] = result[u'disconnected'] - result[u'connected']
+ return result
+ finished.addCallback(cbFinished)
+
+ f = ServerFactory()
+ f.protocol = lambda: ServerProtocol(byteCount, finished)
+ server = reactor.listenTCP(0, f)
+
+ f2 = ClientCreator(reactor, clientProtocol)
+ proto = f2.connectTCP('127.0.0.1', server.getHost().port)
+ def connected(proto):
+ result[u'connected'] = time()
+ return proto
+ proto.addCallback(connected)
+ proto.addCallback(_write, byteCount)
+ return finished
+
+
+
+def _benchmarkBuffered(byteCount):
+ return _benchmark(byteCount, BufferingProtocol)
+
+
+
+def _benchmarkUnbuffered(byteCount):
+ return _benchmark(byteCount, UnbufferingProtocol)
+
+
+
+def benchmark(scale=1):
+ """
+ Benchmark and return information regarding the relative performance of a
+ protocol which does not use the buffering mixin and a protocol which
+ does.
+
+ @type scale: C{int}
+ @param scale: A multipler to the amount of work to perform
+
+ @return: A Deferred which will fire with a dictionary mapping each of
+ the two unicode strings C{u'buffered'} and C{u'unbuffered'} to
+ dictionaries describing the performance of a protocol of each type.
+ These value dictionaries will map the unicode strings C{u'connected'}
+ and C{u'disconnected'} to the times at which each of those events
+ occurred and C{u'duration'} two the difference between these two values.
+ """
+ overallResult = {}
+
+ byteCount = 1024
+
+ bufferedDeferred = _benchmarkBuffered(byteCount * scale)
+ def didBuffered(bufferedResult):
+ overallResult[u'buffered'] = bufferedResult
+ unbufferedDeferred = _benchmarkUnbuffered(byteCount * scale)
+ def didUnbuffered(unbufferedResult):
+ overallResult[u'unbuffered'] = unbufferedResult
+ return overallResult
+ unbufferedDeferred.addCallback(didUnbuffered)
+ return unbufferedDeferred
+ bufferedDeferred.addCallback(didBuffered)
+ return bufferedDeferred
+
+
+
+def main(args=None):
+ """
+ Perform a single benchmark run, starting and stopping the reactor and
+ logging system as necessary.
+ """
+ startLogging(stdout)
+
+ options = BufferingBenchmark()
+ options.parseOptions(args)
+
+ d = benchmark(options['scale'])
+ def cbBenchmark(result):
+ pprint(result)
+ def ebBenchmark(err):
+ print err.getTraceback()
+ d.addCallbacks(cbBenchmark, ebBenchmark)
+ def stopReactor(ign):
+ reactor.stop()
+ d.addBoth(stopReactor)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/demo.tac b/vendor/Twisted-10.0.0/doc/conch/examples/demo.tac
new file mode 100644
index 0000000000..a853b6a7fc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/demo.tac
@@ -0,0 +1,25 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo.tac
+
+"""Nearly pointless demonstration of the manhole interactive interpreter.
+
+This does about the same thing as demo_manhole, but uses the tap
+module's makeService method instead. The only interesting difference
+is that in this version, the telnet server also requires
+authentication.
+
+Note, you will have to create a file named \"passwd\" and populate it
+with credentials (in the format of passwd(5)) to use this demo.
+"""
+
+from twisted.application import service
+application = service.Application("TAC Demo")
+
+from twisted.conch import manhole_tap
+manhole_tap.makeService({"telnetPort": "tcp:6023",
+ "sshPort": "tcp:6022",
+ "namespace": {"foo": "bar"},
+ "passwd": "passwd"}).setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/demo_draw.tac b/vendor/Twisted-10.0.0/doc/conch/examples/demo_draw.tac
new file mode 100644
index 0000000000..55a53c7941
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/demo_draw.tac
@@ -0,0 +1,80 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_draw.tac
+
+"""A trivial drawing application.
+
+Clients are allowed to connect and spew various characters out over
+the terminal. Spacebar changes the drawing character, while the arrow
+keys move the cursor.
+"""
+
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+class Draw(insults.TerminalProtocol):
+ """Protocol which accepts arrow key and spacebar input and places
+ the requested characters onto the terminal.
+ """
+ cursors = list('!@#$%^&*()_+-=')
+
+ def connectionMade(self):
+ self.terminal.eraseDisplay()
+ self.terminal.resetModes([insults.IRM])
+ self.cursor = self.cursors[0]
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == self.terminal.UP_ARROW:
+ self.terminal.cursorUp()
+ elif keyID == self.terminal.DOWN_ARROW:
+ self.terminal.cursorDown()
+ elif keyID == self.terminal.LEFT_ARROW:
+ self.terminal.cursorBackward()
+ elif keyID == self.terminal.RIGHT_ARROW:
+ self.terminal.cursorForward()
+ elif keyID == ' ':
+ self.cursor = self.cursors[(self.cursors.index(self.cursor) + 1) % len(self.cursors)]
+ else:
+ return
+ self.terminal.write(self.cursor)
+ self.terminal.cursorBackward()
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Insults Demo App")
+makeService({'protocolFactory': Draw,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/demo_insults.tac b/vendor/Twisted-10.0.0/doc/conch/examples/demo_insults.tac
new file mode 100644
index 0000000000..a49f011c98
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/demo_insults.tac
@@ -0,0 +1,252 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_insults.tac
+
+"""Various simple terminal manipulations using the insults module.
+
+This demo sets up two listening ports: one on 6022 which accepts ssh
+connections; one on 6023 which accepts telnet connections. No login
+for the telnet server is required; for the ssh server, \"username\" is
+the username and \"password\" is the password.
+
+The TerminalProtocol subclass defined here ignores most user input
+(except to print it out to the server log) and spends the duration of
+the connection drawing (the author's humble approximation of)
+raindrops at random locations on the client's terminal. +, -, *, and
+/ are respected and each adjusts an aspect of the timing of the
+animation process.
+"""
+
+import random, string
+
+from twisted.python import log
+from twisted.internet import protocol, task
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+class DrawingFinished(Exception):
+ """Sentinel exception, raised when no \"frames\" for a particular
+ \"animation\" remain to be drawn.
+ """
+
+class Drawable:
+ """Representation of an animation.
+
+ Constructed with a protocol instance and a coordinate on the
+ screen, waits for invocations of iterate() at which point it
+ erases the previous frame of the animation and draws the next one,
+ using its protocol instance and always placing the upper left hand
+ corner of the frame at the given coordinates.
+
+ Frames are defined with draw_ prefixed methods. Erasure is
+ performed by erase_ prefixed methods.
+ """
+ n = 0
+
+ def __init__(self, proto, col, line):
+ self.proto = proto
+ self.col = col
+ self.line = line
+
+ def drawLines(self, s):
+ lines = s.splitlines()
+ c = self.col
+ line = self.line
+ for l in lines:
+ self.proto.cursorPosition(c - len(lines) / 2, line)
+ self.proto.write(l)
+ line += 1
+
+ def iterate(self):
+ getattr(self, 'erase_' + str(self.n))()
+ self.n += 1
+ f = getattr(self, 'draw_' + str(self.n), None)
+ if f is None:
+ raise DrawingFinished()
+ f()
+
+ def erase_0(self):
+ pass
+
+
+class Splat(Drawable):
+ HEIGHT = 5
+ WIDTH = 11
+
+ def draw_1(self):
+ # . .
+ #. . .
+ # . .
+ self.drawLines(' . .\n. . .\n . .')
+
+ def erase_1(self):
+ self.drawLines(' \n \n ')
+
+ def draw_2(self):
+ # . . . .
+ # . o o o .
+ #. o o o o .
+ # . o o o .
+ # . . . .
+ self.drawLines(' . . . .\n . o o o .\n. o o o o .\n . o o o .\n . . . .')
+
+ def erase_2(self):
+ self.drawLines(' \n \n \n \n ')
+
+ def draw_3(self):
+ # o o o o
+ # o O O O o
+ #o O O O O o
+ # o O O O o
+ # o o o o
+ self.drawLines(' o o o o\n o O O O o\no O O O O o\n o O O O o\n o o o o')
+
+ erase_3 = erase_2
+
+ def draw_4(self):
+ # O O O O
+ # O . . . O
+ #O . . . . O
+ # O . . . O
+ # O O O O
+ self.drawLines(' O O O O\n O . . . O\nO . . . . O\n O . . . O\n O O O O')
+
+ erase_4 = erase_3
+
+ def draw_5(self):
+ # . . . .
+ # . .
+ #. .
+ # . .
+ # . . . .
+ self.drawLines(' . . . .\n . .\n. .\n . .\n . . . .')
+
+ erase_5 = erase_4
+
+class Drop(Drawable):
+ WIDTH = 3
+ HEIGHT = 4
+
+ def draw_1(self):
+ # o
+ self.drawLines(' o')
+
+ def erase_1(self):
+ self.drawLines(' ')
+
+ def draw_2(self):
+ # _
+ #/ \
+ #\./
+ self.drawLines(' _ \n/ \\\n\\./')
+
+ def erase_2(self):
+ self.drawLines(' \n \n ')
+
+ def draw_3(self):
+ # O
+ self.drawLines(' O')
+
+ def erase_3(self):
+ self.drawLines(' ')
+
+class DemoProtocol(insults.TerminalProtocol):
+ """Draws random things at random places on the screen.
+ """
+ width = 80
+ height = 24
+
+ interval = 0.1
+ rate = 0.05
+
+ def connectionMade(self):
+ self.run()
+
+ def connectionLost(self, reason):
+ self._call.stop()
+ del self._call
+
+ def run(self):
+ # Clear the screen, matey
+ self.terminal.eraseDisplay()
+
+ self._call = task.LoopingCall(self._iterate)
+ self._call.start(self.interval)
+
+ def _iterate(self):
+ cls = random.choice((Splat, Drop))
+
+ # Move to a random location on the screen
+ col = random.randrange(self.width - cls.WIDTH) + cls.WIDTH
+ line = random.randrange(self.height - cls.HEIGHT) + cls.HEIGHT
+
+ s = cls(self.terminal, col, line)
+
+ c = task.LoopingCall(s.iterate)
+ c.start(self.rate).addErrback(lambda f: f.trap(DrawingFinished)).addErrback(log.err)
+
+ # ITerminalListener
+ def terminalSize(self, width, height):
+ self.width = width
+ self.height = height
+
+ def unhandledControlSequence(self, seq):
+ log.msg("Client sent something weird: %r" % (seq,))
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == '+':
+ self.interval /= 1.1
+ elif keyID == '-':
+ self.interval *= 1.1
+ elif keyID == '*':
+ self.rate /= 1.1
+ elif keyID == '/':
+ self.rate *= 1.1
+ else:
+ log.msg("Client sent: %r" % (keyID,))
+ return
+
+ self._call.stop()
+ self._call = task.LoopingCall(self._iterate)
+ self._call.start(self.interval)
+
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Insults Demo App")
+
+makeService({'protocolFactory': DemoProtocol,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/demo_manhole.tac b/vendor/Twisted-10.0.0/doc/conch/examples/demo_manhole.tac
new file mode 100644
index 0000000000..7edb7a515c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/demo_manhole.tac
@@ -0,0 +1,56 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_manhole.tac
+
+"""An interactive Python interpreter with syntax coloring.
+
+Nothing interesting is actually defined here. Two listening ports are
+set up and attached to protocols which know how to properly set up a
+ColoredManhole instance.
+"""
+
+from twisted.conch.manhole import ColoredManhole
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Interactive Python Interpreter")
+
+makeService({'protocolFactory': ColoredManhole,
+ 'protocolArgs': (None,),
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/demo_recvline.tac b/vendor/Twisted-10.0.0/doc/conch/examples/demo_recvline.tac
new file mode 100644
index 0000000000..92d01d13f1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/demo_recvline.tac
@@ -0,0 +1,77 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_recvline.tac
+
+"""Demonstrates line-at-a-time handling with basic line-editing support.
+
+This is a variation on the echo server. It sets up two listening
+ports: one on 6022 which accepts ssh connections; one on 6023 which
+accepts telnet connections. No login for the telnet server is
+required; for the ssh server, \"username\" is the username and
+\"password\" is the password.
+
+The demo protocol defined in this module is handed a line of input at
+a time, which it simply writes back to the connection.
+HistoricRecvline, which the demo protocol subclasses, provides basic
+line editing and input history features.
+"""
+
+from twisted.conch import recvline
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+class DemoRecvLine(recvline.HistoricRecvLine):
+ """Simple echo protocol.
+
+ Accepts lines of input and writes them back to its connection. If
+ a line consisting solely of \"quit\" is received, the connection
+ is dropped.
+ """
+
+ def lineReceived(self, line):
+ if line == "quit":
+ self.terminal.loseConnection()
+ self.terminal.write(line)
+ self.terminal.nextLine()
+ self.terminal.write(self.ps[self.pn])
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Insults RecvLine Demo")
+
+makeService({'protocolFactory': DemoRecvLine,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/demo_scroll.tac b/vendor/Twisted-10.0.0/doc/conch/examples/demo_scroll.tac
new file mode 100644
index 0000000000..4fdfbce18c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/demo_scroll.tac
@@ -0,0 +1,100 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_scroll.tac
+
+"""Simple echo-ish server that uses the scroll-region.
+
+This demo sets up two listening ports: one on 6022 which accepts ssh
+connections; one on 6023 which accepts telnet connections. No login
+for the telnet server is required; for the ssh server, \"username\" is
+the username and \"password\" is the password.
+
+The TerminalProtocol subclass defined here sets up a scroll-region occupying
+most of the screen. It positions the cursor at the bottom of the screen and
+then echos back printable input. When return is received, the line is
+copied to the upper area of the screen (scrolling anything older up) and
+clears the input line.
+"""
+
+import string
+
+from twisted.python import log
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+class DemoProtocol(insults.TerminalProtocol):
+ """Copies input to an upwards scrolling region.
+ """
+ width = 80
+ height = 24
+
+ def connectionMade(self):
+ self.buffer = []
+ self.terminalSize(self.width, self.height)
+
+ # ITerminalListener
+ def terminalSize(self, width, height):
+ self.width = width
+ self.height = height
+
+ self.terminal.setScrollRegion(0, height - 1)
+ self.terminal.cursorPosition(0, height)
+ self.terminal.write('> ')
+
+ def unhandledControlSequence(self, seq):
+ log.msg("Client sent something weird: %r" % (seq,))
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == '\r':
+ self.terminal.cursorPosition(0, self.height - 2)
+ self.terminal.nextLine()
+ self.terminal.write(''.join(self.buffer))
+ self.terminal.cursorPosition(0, self.height - 1)
+ self.terminal.eraseToLineEnd()
+ self.terminal.write('> ')
+ self.buffer = []
+ elif keyID in list(string.printable):
+ self.terminal.write(keyID)
+ self.buffer.append(keyID)
+
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Scroll Region Demo App")
+
+makeService({'protocolFactory': DemoProtocol,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/index.html b/vendor/Twisted-10.0.0/doc/conch/examples/index.html
new file mode 100644
index 0000000000..6880581dcf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/index.html
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted.Conch code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted.Conch code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">Simple SSH server and client</a></li><li><a href="#auto1">Simple telnet server</a></li><li><a href="#auto2">twisted.conch.insults examples</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Simple SSH server and client<a name="auto0"/></h2>
+ <ul>
+ <li><a href="sshsimpleclient.py" shape="rect">sshsimpleclient.py</a></li>
+ <li><a href="sshsimpleserver.py" shape="rect">sshsimpleserver.py</a></li>
+ </ul>
+
+ <h2>Simple telnet server<a name="auto1"/></h2>
+ <ul>
+ <li><a href="telnet_echo.tac" shape="rect">A telnet server which echoes data and events back to the client</a></li>
+ </ul>
+
+
+ <h2>twisted.conch.insults examples<a name="auto2"/></h2>
+ <ul>
+ <li><a href="demo.tac" shape="rect">demo.tac</a> Nearly pointless demonstration of the manhole interactive interpreter</li>
+ <li><a href="demo_draw.tac" shape="rect">demo_draw.tac</a> A trivial drawing application</li>
+ <li><a href="demo_insults.tac" shape="rect">demo_insults.tac</a> Various simple terminal manipulations using the insults module</li>
+ <li><a href="demo_recvline.tac" shape="rect">demo_recvline.tac</a> Demonstrates line-at-a-time handling with basic line-editing support</li>
+ <li><a href="demo_scroll.tac" shape="rect">demo_scroll.tac</a> Simple echo-ish server that uses the scroll-region</li>
+ <li><a href="demo_manhole.tac" shape="rect">demo_manhole.tac</a> An interactive Python interpreter with syntax coloring</li>
+ <li><a href="window.tac" shape="rect">window.tac</a> An example of various widgets</li>
+ </ul>
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/sshsimpleclient.py b/vendor/Twisted-10.0.0/doc/conch/examples/sshsimpleclient.py
new file mode 100644
index 0000000000..a25b90e63c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/sshsimpleclient.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.conch.ssh import transport, userauth, connection, common, keys, channel
+from twisted.internet import defer, protocol, reactor
+from twisted.python import log
+import struct, sys, getpass, os
+
+USER = 'z3p' # replace this with a valid username
+HOST = 'localhost' # and a valid host
+
+class SimpleTransport(transport.SSHClientTransport):
+ def verifyHostKey(self, hostKey, fingerprint):
+ print 'host key fingerprint: %s' % fingerprint
+ return defer.succeed(1)
+
+ def connectionSecure(self):
+ self.requestService(
+ SimpleUserAuth(USER,
+ SimpleConnection()))
+
+class SimpleUserAuth(userauth.SSHUserAuthClient):
+ def getPassword(self):
+ return defer.succeed(getpass.getpass("%s@%s's password: " % (USER, HOST)))
+
+ def getGenericAnswers(self, name, instruction, questions):
+ print name
+ print instruction
+ answers = []
+ for prompt, echo in questions:
+ if echo:
+ answer = raw_input(prompt)
+ else:
+ answer = getpass.getpass(prompt)
+ answers.append(answer)
+ return defer.succeed(answers)
+
+ def getPublicKey(self):
+ path = os.path.expanduser('~/.ssh/id_dsa')
+ # this works with rsa too
+ # just change the name here and in getPrivateKey
+ if not os.path.exists(path) or self.lastPublicKey:
+ # the file doesn't exist, or we've tried a public key
+ return
+ return keys.getPublicKeyString(path+'.pub')
+
+ def getPrivateKey(self):
+ path = os.path.expanduser('~/.ssh/id_dsa')
+ return defer.succeed(keys.getPrivateKeyObject(path))
+
+class SimpleConnection(connection.SSHConnection):
+ def serviceStarted(self):
+ self.openChannel(TrueChannel(2**16, 2**15, self))
+ self.openChannel(FalseChannel(2**16, 2**15, self))
+ self.openChannel(CatChannel(2**16, 2**15, self))
+
+class TrueChannel(channel.SSHChannel):
+ name = 'session' # needed for commands
+
+ def openFailed(self, reason):
+ print 'true failed', reason
+
+ def channelOpen(self, ignoredData):
+ self.conn.sendRequest(self, 'exec', common.NS('true'))
+
+ def request_exit_status(self, data):
+ status = struct.unpack('>L', data)[0]
+ print 'true status was: %s' % status
+ self.loseConnection()
+
+class FalseChannel(channel.SSHChannel):
+ name = 'session'
+
+ def openFailed(self, reason):
+ print 'false failed', reason
+
+ def channelOpen(self, ignoredData):
+ self.conn.sendRequest(self, 'exec', common.NS('false'))
+
+ def request_exit_status(self, data):
+ status = struct.unpack('>L', data)[0]
+ print 'false status was: %s' % status
+ self.loseConnection()
+
+class CatChannel(channel.SSHChannel):
+ name = 'session'
+
+ def openFailed(self, reason):
+ print 'echo failed', reason
+
+ def channelOpen(self, ignoredData):
+ self.data = ''
+ d = self.conn.sendRequest(self, 'exec', common.NS('cat'), wantReply = 1)
+ d.addCallback(self._cbRequest)
+
+ def _cbRequest(self, ignored):
+ self.write('hello conch\n')
+ self.conn.sendEOF(self)
+
+ def dataReceived(self, data):
+ self.data += data
+
+ def closed(self):
+ print 'got data from cat: %s' % repr(self.data)
+ self.loseConnection()
+ reactor.stop()
+
+protocol.ClientCreator(reactor, SimpleTransport).connectTCP(HOST, 22)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/sshsimpleserver.py b/vendor/Twisted-10.0.0/doc/conch/examples/sshsimpleserver.py
new file mode 100755
index 0000000000..7cfdf5aa74
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/sshsimpleserver.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.cred import portal, checkers
+from twisted.conch import error, avatar
+from twisted.conch.checkers import SSHPublicKeyDatabase
+from twisted.conch.ssh import factory, userauth, connection, keys, session
+from twisted.internet import reactor, protocol, defer
+from twisted.python import log
+from zope.interface import implements
+import sys
+log.startLogging(sys.stderr)
+
+"""
+Example of running another protocol over an SSH channel.
+log in with username "user" and password "password".
+"""
+
+class ExampleAvatar(avatar.ConchUser):
+
+ def __init__(self, username):
+ avatar.ConchUser.__init__(self)
+ self.username = username
+ self.channelLookup.update({'session':session.SSHSession})
+
+class ExampleRealm:
+ implements(portal.IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ return interfaces[0], ExampleAvatar(avatarId), lambda: None
+
+class EchoProtocol(protocol.Protocol):
+ """this is our example protocol that we will run over SSH
+ """
+ def dataReceived(self, data):
+ if data == '\r':
+ data = '\r\n'
+ elif data == '\x03': #^C
+ self.transport.loseConnection()
+ return
+ self.transport.write(data)
+
+publicKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBEvLi8DVPrJ3/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYLh5KmRpslkYHRivcJSkbh/C+BR3utDS555mV'
+
+privateKey = """-----BEGIN RSA PRIVATE KEY-----
+MIIByAIBAAJhAK8ycfDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW
+4sbUIZR/ZXzY1CMfuC5qyR+UDUbBaaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fw
+vgUd7rQ0ueeZlQIBIwJgbh+1VZfr7WftK5lu7MHtqE1S1vPWZQYE3+VUn8yJADyb
+Z4fsZaCrzW9lkIqXkE3GIY+ojdhZhkO1gbG0118sIgphwSWKRxK0mvh6ERxKqIt1
+xJEJO74EykXZV4oNJ8sjAjEA3J9r2ZghVhGN6V8DnQrTk24Td0E8hU8AcP0FVP+8
+PQm/g/aXf2QQkQT+omdHVEJrAjEAy0pL0EBH6EVS98evDCBtQw22OZT52qXlAwZ2
+gyTriKFVoqjeEjt3SZKKqXHSApP/AjBLpF99zcJJZRq2abgYlf9lv1chkrWqDHUu
+DZttmYJeEfiFBBavVYIF1dOlZT0G8jMCMBc7sOSZodFnAiryP+Qg9otSBjJ3bQML
+pSTqy7c3a2AScC/YyOwkDaICHnnD3XyjMwIxALRzl0tQEKMXs6hH8ToUdlLROCrP
+EhQ0wahUTCk1gKA4uPD6TMTChavbh4K63OvbKg==
+-----END RSA PRIVATE KEY-----"""
+
+
+class InMemoryPublicKeyChecker(SSHPublicKeyDatabase):
+
+ def checkKey(self, credentials):
+ return credentials.username == 'user' and \
+ keys.getPublicKeyString(data=publicKey) == credentials.blob
+
+class ExampleSession:
+
+ def __init__(self, avatar):
+ """
+ We don't use it, but the adapter is passed the avatar as its first
+ argument.
+ """
+
+ def getPty(self, term, windowSize, attrs):
+ pass
+
+ def execCommand(self, proto, cmd):
+ raise Exception("no executing commands")
+
+ def openShell(self, trans):
+ ep = EchoProtocol()
+ ep.makeConnection(trans)
+ trans.makeConnection(session.wrapProtocol(ep))
+
+ def eofReceived(self):
+ pass
+
+ def closed(self):
+ pass
+
+from twisted.python import components
+components.registerAdapter(ExampleSession, ExampleAvatar, session.ISession)
+
+class ExampleFactory(factory.SSHFactory):
+ publicKeys = {
+ 'ssh-rsa': keys.Key.fromString(data=publicKey)
+ }
+ privateKeys = {
+ 'ssh-rsa': keys.Key.fromString(data=privateKey)
+ }
+ services = {
+ 'ssh-userauth': userauth.SSHUserAuthServer,
+ 'ssh-connection': connection.SSHConnection
+ }
+
+
+portal = portal.Portal(ExampleRealm())
+passwdDB = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+passwdDB.addUser('user', 'password')
+portal.registerChecker(passwdDB)
+portal.registerChecker(InMemoryPublicKeyChecker())
+ExampleFactory.portal = portal
+
+if __name__ == '__main__':
+ reactor.listenTCP(5022, ExampleFactory())
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/telnet_echo.tac b/vendor/Twisted-10.0.0/doc/conch/examples/telnet_echo.tac
new file mode 100644
index 0000000000..9fabdb89d9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/telnet_echo.tac
@@ -0,0 +1,37 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.conch.telnet import TelnetTransport, TelnetProtocol
+from twisted.internet.protocol import ServerFactory
+from twisted.application.internet import TCPServer
+from twisted.application.service import Application
+
+class TelnetEcho(TelnetProtocol):
+ def enableRemote(self, option):
+ self.transport.write("You tried to enable %r (I rejected it)\r\n" % (option,))
+ return False
+
+
+ def disableRemote(self, option):
+ self.transport.write("You disabled %r\r\n" % (option,))
+
+
+ def enableLocal(self, option):
+ self.transport.write("You tried to make me enable %r (I rejected it)\r\n" % (option,))
+ return False
+
+
+ def disableLocal(self, option):
+ self.transport.write("You asked me to disable %r\r\n" % (option,))
+
+
+ def dataReceived(self, data):
+ self.transport.write("I received %r from you\r\n" % (data,))
+
+
+factory = ServerFactory()
+factory.protocol = lambda: TelnetTransport(TelnetEcho)
+service = TCPServer(8023, factory)
+
+application = Application("Telnet Echo Server")
+service.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/conch/examples/window.tac b/vendor/Twisted-10.0.0/doc/conch/examples/window.tac
new file mode 100644
index 0000000000..e455f01648
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/examples/window.tac
@@ -0,0 +1,190 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny window.tac
+
+from __future__ import division
+
+import string, random
+
+from twisted.python import log
+from twisted.internet import protocol, task
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+from twisted.conch.insults import insults, window
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import reactor
+
+class DrawableCanvas(window.Canvas):
+ x = 0
+ y = 0
+
+ def func_LEFT_ARROW(self, modifier):
+ self.x -= 1
+ self.repaint()
+
+ def func_RIGHT_ARROW(self, modifier):
+ self.x += 1
+ self.repaint()
+
+ def func_UP_ARROW(self, modifier):
+ self.y -= 1
+ self.repaint()
+
+ def func_DOWN_ARROW(self, modifier):
+ self.y += 1
+ self.repaint()
+
+ def characterReceived(self, keyID, modifier):
+ self[self.x, self.y] = keyID
+ self.x += 1
+ self.repaint()
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == '\r' or keyID == '\v':
+ return
+ window.Canvas.keystrokeReceived(self, keyID, modifier)
+ if self.x >= self.width:
+ self.x = 0
+ elif self.x < 0:
+ self.x = self.width - 1
+
+ if self.y >= self.height:
+ self.y = 0
+ elif self.y < 0:
+ self.y = self.height - 1
+ self.repaint()
+
+ def render(self, width, height, terminal):
+ window.Canvas.render(self, width, height, terminal)
+ if self.focused:
+ terminal.cursorPosition(self.x, self.y)
+ window.cursor(terminal, self[self.x, self.y])
+
+
+class ButtonDemo(insults.TerminalProtocol):
+ width = 80
+ height = 24
+
+ def _draw(self):
+ self.window.draw(self.width, self.height, self.terminal)
+
+ def _redraw(self):
+ self.window.filthy()
+ self._draw()
+
+ def _schedule(self, f):
+ reactor.callLater(0, f)
+
+ def connectionMade(self):
+ self.terminal.eraseDisplay()
+ self.terminal.resetPrivateModes([insults.privateModes.CURSOR_MODE])
+
+ self.window = window.TopWindow(self._draw, self._schedule)
+ self.output = window.TextOutput((15, 1))
+ self.input = window.TextInput(15, self._setText)
+ self.select1 = window.Selection(map(str, range(100)), self._setText, 10)
+ self.select2 = window.Selection(map(str, range(200, 300)), self._setText, 10)
+ self.button = window.Button("Clear", self._clear)
+ self.canvas = DrawableCanvas()
+
+ hbox = window.HBox()
+ hbox.addChild(self.input)
+ hbox.addChild(self.output)
+ hbox.addChild(window.Border(self.button))
+ hbox.addChild(window.Border(self.select1))
+ hbox.addChild(window.Border(self.select2))
+
+ t1 = window.TextOutputArea(longLines=window.TextOutputArea.WRAP)
+ t2 = window.TextOutputArea(longLines=window.TextOutputArea.TRUNCATE)
+ t3 = window.TextOutputArea(longLines=window.TextOutputArea.TRUNCATE)
+ t4 = window.TextOutputArea(longLines=window.TextOutputArea.TRUNCATE)
+ for _t in t1, t2, t3, t4:
+ _t.setText((('This is a very long string. ' * 3) + '\n') * 3)
+
+ vp = window.Viewport(t3)
+ d = [1]
+ def spin():
+ vp.xOffset += d[0]
+ if vp.xOffset == 0 or vp.xOffset == 25:
+ d[0] *= -1
+ self.call = task.LoopingCall(spin)
+ self.call.start(0.25, now=False)
+ hbox.addChild(window.Border(vp))
+
+ vp2 = window.ScrolledArea(t4)
+ hbox.addChild(vp2)
+
+ texts = window.VBox()
+ texts.addChild(window.Border(t1))
+ texts.addChild(window.Border(t2))
+
+ areas = window.HBox()
+ areas.addChild(window.Border(self.canvas))
+ areas.addChild(texts)
+
+ vbox = window.VBox()
+ vbox.addChild(hbox)
+ vbox.addChild(areas)
+ self.window.addChild(vbox)
+ self.terminalSize(self.width, self.height)
+
+ def connectionLost(self, reason):
+ self.call.stop()
+ insults.TerminalProtocol.connectionLost(self, reason)
+
+ def terminalSize(self, width, height):
+ self.width = width
+ self.height = height
+ self.terminal.eraseDisplay()
+ self._redraw()
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ self.window.keystrokeReceived(keyID, modifier)
+
+ def _clear(self):
+ self.canvas.clear()
+
+ def _setText(self, text):
+ self.input.setText('')
+ self.output.setText(text)
+
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Window Demo")
+
+makeService({'protocolFactory': ButtonDemo,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/conch/howto/conch_client.html b/vendor/Twisted-10.0.0/doc/conch/howto/conch_client.html
new file mode 100644
index 0000000000..c801b654b7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/howto/conch_client.html
@@ -0,0 +1,318 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Writing a client with Twisted.Conch</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Writing a client with Twisted.Conch</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Writing a client</a></li><li><a href="#auto2">The Transport</a></li><li><a href="#auto3">The Authorization Client</a></li><li><a href="#auto4">The Connection</a></li><li><a href="#auto5">The Channel</a></li><li><a href="#auto6">The main() function</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Introduction<a name="auto0"/></h2>
+
+<p>In the original days of computing, rsh/rlogin were used to connect to
+remote computers and execute commands. These commands had the problem
+that the passwords and commands were sent in the clear. To solve this
+problem, the SSH protocol was created. Twisted.Conch implements the
+second version of this protocol.</p>
+
+ <h2>Writing a client<a name="auto1"/></h2>
+
+<p>Writing a client with Conch involves sub-classing 4 classes: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.transport.SSHClientTransport.html" title="twisted.conch.ssh.transport.SSHClientTransport">twisted.conch.ssh.transport.SSHClientTransport</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.userauth.SSHUserAuthClient.html" title="twisted.conch.ssh.userauth.SSHUserAuthClient">twisted.conch.ssh.userauth.SSHUserAuthClient</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.connection.SSHConnection.html" title="twisted.conch.ssh.connection.SSHConnection">twisted.conch.ssh.connection.SSHConnection</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.ssh.channel.SSHChannel.html" title="twisted.conch.ssh.channel.SSHChannel">twisted.conch.ssh.channel.SSHChannel</a></code>. We'll start out
+with <code class="python">SSHClientTransport</code> because it's the base
+of the client.</p>
+
+<h2>The Transport<a name="auto2"/></h2>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">error</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">transport</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientTransport</span>(<span class="py-src-parameter">transport</span>.<span class="py-src-parameter">SSHClientTransport</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">verifyHostKey</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pubKey</span>, <span class="py-src-parameter">fingerprint</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">fingerprint</span> != <span class="py-src-string">'b1:94:6a:c9:24:92:d2:34:7c:62:35:b4:d2:61:11:84'</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">fail</span>(<span class="py-src-variable">error</span>.<span class="py-src-variable">ConchError</span>(<span class="py-src-string">'bad key'</span>))
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-number">1</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionSecure</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">requestService</span>(<span class="py-src-variable">ClientUserAuth</span>(<span class="py-src-string">'user'</span>, <span class="py-src-variable">ClientConnection</span>()))
+</pre>
+
+<p>See how easy it is? <code class="python">SSHClientTransport</code>
+handles the negotiation of encryption and the verification of keys
+for you. The one security element that you as a client writer need to
+implement is <code class="python">verifyHostKey()</code>. This method
+is called with two strings: the public key sent by the server and its
+fingerprint. You should verify the host key the server sends, either
+by checking against a hard-coded value as in the example, or by asking
+the user. <code class="python">verifyHostKey</code> returns a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code> which gets a callback
+if the host key is valid, or an errback if it is not. Note that in the
+above, replace 'user' with the username you're attempting to ssh with,
+for instance a call to <code class="python">os.getlogin()</code> for the
+current user.</p>
+
+<p>The second method you need to implement is <code class="python">connectionSecure()</code>. It is called when the
+encryption is set up and other services can be run. The example requests
+that the <code class="python">ClientUserAuth</code> service be started.
+This service will be discussed next.</p>
+
+<h2>The Authorization Client<a name="auto3"/></h2>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">keys</span>, <span class="py-src-variable">userauth</span>
+
+<span class="py-src-comment"># these are the public/private keys from test_conch</span>
+
+<span class="py-src-variable">publicKey</span> = <span class="py-src-string">'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBEvLi8DVPrJ3\
+/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYLh5KmRpslkYHR\
+ivcJSkbh/C+BR3utDS555mV'</span>
+
+<span class="py-src-variable">privateKey</span> = <span class="py-src-string">&quot;&quot;&quot;-----BEGIN RSA PRIVATE KEY-----
+MIIByAIBAAJhAK8ycfDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW
+4sbUIZR/ZXzY1CMfuC5qyR+UDUbBaaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fw
+vgUd7rQ0ueeZlQIBIwJgbh+1VZfr7WftK5lu7MHtqE1S1vPWZQYE3+VUn8yJADyb
+Z4fsZaCrzW9lkIqXkE3GIY+ojdhZhkO1gbG0118sIgphwSWKRxK0mvh6ERxKqIt1
+xJEJO74EykXZV4oNJ8sjAjEA3J9r2ZghVhGN6V8DnQrTk24Td0E8hU8AcP0FVP+8
+PQm/g/aXf2QQkQT+omdHVEJrAjEAy0pL0EBH6EVS98evDCBtQw22OZT52qXlAwZ2
+gyTriKFVoqjeEjt3SZKKqXHSApP/AjBLpF99zcJJZRq2abgYlf9lv1chkrWqDHUu
+DZttmYJeEfiFBBavVYIF1dOlZT0G8jMCMBc7sOSZodFnAiryP+Qg9otSBjJ3bQML
+pSTqy7c3a2AScC/YyOwkDaICHnnD3XyjMwIxALRzl0tQEKMXs6hH8ToUdlLROCrP
+EhQ0wahUTCk1gKA4uPD6TMTChavbh4K63OvbKg==
+-----END RSA PRIVATE KEY-----&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientUserAuth</span>(<span class="py-src-parameter">userauth</span>.<span class="py-src-parameter">SSHUserAuthClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getPassword</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">prompt</span> = <span class="py-src-parameter">None</span>):
+ <span class="py-src-keyword">return</span>
+ <span class="py-src-comment"># this says we won't do password authentication</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getPublicKey</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">keys</span>.<span class="py-src-variable">getPublicKeyString</span>(<span class="py-src-variable">data</span> = <span class="py-src-variable">publicKey</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getPrivateKey</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">keys</span>.<span class="py-src-variable">getPrivateKeyObject</span>(<span class="py-src-variable">data</span> = <span class="py-src-variable">privateKey</span>))
+</pre>
+
+<p>Again, fairly simple. The <code class="python">SSHUserAuthClient</code> takes care of most
+of the work, but the actual authentication data needs to be
+supplied. <code class="python">getPassword()</code> asks for a
+password, <code class="python">getPublicKey()</code> and <code class="python">getPrivateKey()</code> get public and private keys,
+respectively. <code class="python">getPassword()</code> returns
+a <code class="python">Deferred</code> that is called back with
+the password to use. <code class="python">getPublicKey()</code>
+returns the SSH key data for the public key to use. <code class="python">keys.getPublicKeyString()</code> will take
+keys in OpenSSH and LSH format, and convert them to the
+required format. <code class="python">getPrivateKey()</code>
+returns a <code class="python">Deferred</code> which is
+called back with the key object (as used in PyCrypto) for
+the private key. <code class="python">getPassword()</code>
+and <code class="python">getPrivateKey()</code> return <code class="python">Deferreds</code> because they may need to ask the user
+for input.</p>
+
+<p>Once the authentication is complete, <code class="python">SSHUserAuthClient</code> takes care of starting the code
+<code class="python">SSHConnection</code> object given to it. Next, we'll
+look at how to use the <code class="python">SSHConnection</code></p>
+
+<h2>The Connection<a name="auto4"/></h2>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">connection</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientConnection</span>(<span class="py-src-parameter">connection</span>.<span class="py-src-parameter">SSHConnection</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">serviceStarted</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">openChannel</span>(<span class="py-src-variable">CatChannel</span>(<span class="py-src-variable">conn</span> = <span class="py-src-variable">self</span>))
+</pre>
+
+<p><code class="python">SSHConnection</code> is the easiest,
+as it's only responsible for starting the channels. It has
+other methods, those will be examined when we look at <code class="python">SSHChannel</code>.</p>
+
+<h2>The Channel<a name="auto5"/></h2>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">conch</span>.<span class="py-src-variable">ssh</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">channel</span>, <span class="py-src-variable">common</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CatChannel</span>(<span class="py-src-parameter">channel</span>.<span class="py-src-parameter">SSHChannel</span>):
+
+ <span class="py-src-variable">name</span> = <span class="py-src-string">'session'</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">channelOpen</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">conn</span>.<span class="py-src-variable">sendRequest</span>(<span class="py-src-variable">self</span>, <span class="py-src-string">'exec'</span>, <span class="py-src-variable">common</span>.<span class="py-src-variable">NS</span>(<span class="py-src-string">'cat'</span>),
+ <span class="py-src-variable">wantReply</span> = <span class="py-src-number">1</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cbSendRequest</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span> = <span class="py-src-string">''</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cbSendRequest</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">'This data will be echoed back to us by &quot;cat.&quot;\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">conn</span>.<span class="py-src-variable">sendEOF</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">loseConnection</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span> += <span class="py-src-variable">data</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">closed</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'We got this from &quot;cat&quot;:'</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">catData</span>
+</pre>
+
+<p>Now that we've spent all this time getting the server and
+client connected, here is where that work pays off. <code class="python">SSHChannel</code> is the interface between you and the
+other side. This particular channel opens a session and plays with the
+'cat' program, but your channel can implement anything, so long as the
+server supports it.</p>
+
+<p>The <code class="python">channelOpen()</code> method is
+where everything gets started. It gets passed a chunk of data;
+however, this chunk is usually nothing and can be ignored.
+Our <code class="python">channelOpen()</code> initializes our
+channel, and sends a request to the other side, using the
+<code class="python">sendRequest()</code> method of the <code class="python">SSHConnection</code> object. Requests are used to send
+events to the other side. We pass the method self so that it knows to
+send the request for this channel. The 2nd argument of 'exec' tells the
+server that we want to execute a command. The third argument is the data
+that accompanies the request. <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/common.NS.html" title="common.NS">common.NS</a></code> encodes
+the data as a length-prefixed string, which is how the server expects
+the data. We also say that we want a reply saying that the process has a
+been started. <code class="python">sendRequest()</code> then returns a
+<code class="python">Deferred</code> which we add a callback for.</p>
+
+<p>Once the callback fires, we send the data. <code class="python">SSHChannel</code> supports the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/
+.html" title="
+">
+twisted.internet.interface.Transport</a></code> interface, so
+it can be given to Protocols to run them over the secure
+connection. In our case, we just write the data directly. <code class="python">sendEOF()</code> does not follow the interface,
+but Conch uses it to tell the other side that we will write no
+more data. <code class="python">loseConnection()</code> shuts
+down our side of the connection, but we will still receive data
+through <code class="python">dataReceived()</code>. The <code class="python">closed()</code> method is called when both sides of the
+connection are closed, and we use it to display the data we received
+(which should be the same as the data we sent.)</p>
+
+<p>Finally, let's actually invoke the code we've set up.</p>
+
+<h2>The main() function<a name="auto6"/></h2>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ClientFactory</span>()
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">ClientTransport</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">22</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">main</span>()
+</pre>
+
+<P>We call <code class="python">connectTCP()</code> to connect to
+localhost, port 22 (the standard port for ssh), and pass it an instance
+of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.ClientFactory.html" title="twisted.internet.protocol.ClientFactory">twisted.internet.protocol.ClientFactory</a></code>.
+This instance has the attribute <code class="python">protocol</code>
+set to our earlier <code class="python">ClientTransport</code>
+class. Note that the protocol attribute is set to the class <code class="python">ClientTransport</code>, not an instance of
+<code class="python">ClientTransport</code>! When the <code class="python">connectTCP</code> call completes, the protocol will be
+called to create a <code class="python">ClientTransport()</code> object
+- this then invokes all our previous work.</P>
+
+<P>It's worth noting that in the example <code class="python">main()</code>
+routine, the <code class="python">reactor.run()</code> call never returns.
+If you want to make the program exit, call
+<code class="python">reactor.stop()</code> in the earlier
+<code class="python">closed()</code> method.</P>
+
+<P>If you wish to observe the interactions in more detail, adding a call
+to <code class="python">log.startLogging(sys.stdout, setStdout=0)</code>
+before the <code class="python">reactor.run()</code> call will send all
+logging to stdout.</P>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/conch/howto/index.html b/vendor/Twisted-10.0.0/doc/conch/howto/index.html
new file mode 100644
index 0000000000..ca9140d915
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/howto/index.html
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+ <li>Tutorial
+ <ul>
+ <li>
+ <a href="conch_client.html" shape="rect">Writing an SSH client with Conch</a>
+ </li>
+ </ul>
+ </li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/conch/index.html b/vendor/Twisted-10.0.0/doc/conch/index.html
new file mode 100644
index 0000000000..4f57b8c6f9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Conch Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Conch Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Conch to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Conch</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/conch/man/cftp-man.html b/vendor/Twisted-10.0.0/doc/conch/man/cftp-man.html
new file mode 100644
index 0000000000..7cdf6d9d13
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/man/cftp-man.html
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: CFTP.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">CFTP.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>cftp </p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p>cftp [<strong>-B</strong><em> buffer_size</em>][<strong>-b</strong><em> command_file</em>][<strong>-R</strong><em> num_requests</em>][<strong>-s</strong><em> subsystem</em>]</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>cftp is a client for logging into a remote machine and executing commands to send and receive file information. It can wrap a number of file transfer subsystems
+</p>
+
+<p>The options are as follows:
+<dl><dt><strong>-B</strong></dt><dd>Specifies the default size of the buffer to use for sending and receiving. (Default value: 32768 bytes.)
+</dd><dt><strong>-b</strong></dt><dd>File to read commands from, '-' for stdin. (Default value: interactive/stdin.)
+</dd><dt><strong>-R</strong></dt><dd>Number of requests to make before waiting for a reply.
+</dd><dt><strong>-s</strong></dt><dd>Subsystem/server program to connect to.
+</dd></dl>
+
+</p>
+
+<p>The following commands are recognised by
+cftp :
+<dl><dt>cd <u>path</u></dt><dd>Change the remote directory to 'path'.
+</dd><dt>chgrp <u>gid</u> <u>path</u></dt><dd>Change the gid of 'path' to 'gid'.
+</dd><dt>chmod <u>mode</u> <u>path</u></dt><dd>Change mode of 'path' to 'mode'.
+</dd><dt>chown <u>uid</u> <u>path</u></dt><dd>Change uid of 'path' to 'uid'.
+</dd><dt>exit</dt><dd>Disconnect from the server.
+</dd><dt>get <u>remote-path</u> [<u>local-path</u>]</dt><dd>Get remote file and optionally store it at specified local path.
+</dd><dt>help</dt><dd>Get a list of available commands.
+</dd><dt>lcd <u>path</u></dt><dd>Change local directory to 'path'.
+</dd><dt>lls [<u>ls-options</u>] [<u>path</u>]</dt><dd>Display local directory listing.
+</dd><dt>lmkdir <u>path</u></dt><dd>Create local directory.
+</dd><dt>ln <u>linkpath</u> <u>targetpath</u></dt><dd>Symlink remote file.
+</dd><dt>lpwd</dt><dd>Print the local working directory.
+</dd><dt>ls [<u>-l</u>] [<u>path</u>]</dt><dd>Display remote directory listing.
+</dd><dt>mkdir <u>path</u></dt><dd>Create remote directory.
+</dd><dt>progress</dt><dd>Toggle progress bar.
+</dd><dt>put <u>local-path</u> [<u>remote-path</u>]</dt><dd>Transfer local file to remote location
+</dd><dt>pwd</dt><dd>Print the remote working directory.
+</dd><dt>quit</dt><dd>Disconnect from the server.
+</dd><dt>rename <u>oldpath</u> <u>newpath</u></dt><dd>Rename remote file.
+</dd><dt>rmdir <u>path</u></dt><dd>Remove remote directory.
+</dd><dt>rm <u>path</u></dt><dd>Remove remote file.
+</dd><dt>version</dt><dd>Print the SFTP version.
+</dd><dt>?</dt><dd>Synonym for 'help'.
+</dd></dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>cftp by Paul Swartz &lt;z3p@twistedmatrix.com&gt;. Man page by Mary Gardiner &lt;mary@twistedmatrix.com&gt;.
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>Report bugs to <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2005-2008 Twisted Matrix Laboratories
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/conch/man/cftp.1 b/vendor/Twisted-10.0.0/doc/conch/man/cftp.1
new file mode 100644
index 0000000000..7eae889516
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/man/cftp.1
@@ -0,0 +1,89 @@
+.Dd October 8, 2005
+.Dt CFTP 1
+.Os
+.Sh NAME
+.Nm cftp
+.Nd Conch command-line SFTP client
+.Sh SYNOPSIS
+.Nm cftp
+.Op Fl B Ar buffer_size
+.Op Fl b Ar command_file
+.Op Fl R Ar num_requests
+.Op Fl s Ar subsystem
+.Os
+.Sh DESCRIPTION
+.Nm
+is a client for logging into a remote machine and executing commands to send and receive file information. It can wrap a number of file transfer subsystems
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B
+Specifies the default size of the buffer to use for sending and receiving. (Default value: 32768 bytes.)
+.It Fl b
+File to read commands from, '-' for stdin. (Default value: interactive/stdin.)
+.It Fl R
+Number of requests to make before waiting for a reply.
+.It Fl s
+Subsystem/server program to connect to.
+.El
+.Pp
+The following commands are recognised by
+.Nm
+:
+.Bl -tag -width Ds
+.It Ic cd Ar path
+Change the remote directory to 'path'.
+.It Ic chgrp Ar gid Ar path
+Change the gid of 'path' to 'gid'.
+.It Ic chmod Ar mode Ar path
+Change mode of 'path' to 'mode'.
+.It Ic chown Ar uid Ar path
+Change uid of 'path' to 'uid'.
+.It Ic exit
+Disconnect from the server.
+.It Ic get Ar remote-path Op Ar local-path
+Get remote file and optionally store it at specified local path.
+.It Ic help
+Get a list of available commands.
+.It Ic lcd Ar path
+Change local directory to 'path'.
+.It Ic lls Op Ar ls-options Op Ar path
+Display local directory listing.
+.It Ic lmkdir Ar path
+Create local directory.
+.It Ic ln Ar linkpath Ar targetpath
+Symlink remote file.
+.It Ic lpwd
+Print the local working directory.
+.It Ic ls Op Ar -l Op Ar path
+Display remote directory listing.
+.It Ic mkdir Ar path
+Create remote directory.
+.It Ic progress
+Toggle progress bar.
+.It Ic put Ar local-path Op Ar remote-path
+Transfer local file to remote location
+.It Ic pwd
+Print the remote working directory.
+.It Ic quit
+Disconnect from the server.
+.It Ic rename Ar oldpath Ar newpath
+Rename remote file.
+.It Ic rmdir Ar path
+Remove remote directory.
+.It Ic rm Ar path
+Remove remote file.
+.It Ic version
+Print the SFTP version.
+.It Ic ?
+Synonym for 'help'.
+.El
+.Sh AUTHOR
+cftp by Paul Swartz <z3p@twistedmatrix.com>. Man page by Mary Gardiner <mary@twistedmatrix.com>.
+.Sh "REPORTING BUGS"
+Report bugs to \fIhttp://twistedmatrix.com/bugs/\fR
+.Sh COPYRIGHT
+Copyright \(co 2005-2008 Twisted Matrix Laboratories
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/Twisted-10.0.0/doc/conch/man/ckeygen-man.html b/vendor/Twisted-10.0.0/doc/conch/man/ckeygen-man.html
new file mode 100644
index 0000000000..44d68f1841
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/man/ckeygen-man.html
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: CKEYGEN.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">CKEYGEN.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">DESCRIPTION</a></li><li><a href="#auto4">AUTHOR</a></li><li><a href="#auto5">REPORTING BUGS</a></li><li><a href="#auto6">COPYRIGHT</a></li><li><a href="#auto7">SEE ALSO</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>ckeygen - connect to SSH servers
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>ckeygen</strong> [-b <em>bits</em>] [-f <em>filename</em>] [-t <em>type</em>]<strong>[-C</strong> <em>comment</em>] [-N <em>new passphrase</em>] [-P <em>old passphrase</em>]<strong>[-l]</strong> [-p] [-q] [-y]<strong>ckeygen</strong> --help</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>The <strong>--help</strong> prints out a usage message to standard output.
+<dl><dt><strong>-b</strong>, <strong>--bits</strong> &lt;bits&gt;
+</dt><dd>Number of bits in the key to create (default: 1024)
+</dd>
+
+<dt><strong>-f</strong>, <strong>--filename</strong> &lt;file name&gt;
+</dt><dd>Filename of the key file.
+</dd>
+
+<dt><strong>-t</strong>, <strong>--type</strong> &lt;type&gt;
+</dt><dd>Type of key (rsa or dsa).
+</dd>
+
+<dt><strong>-C</strong>, <strong>--comment</strong> &lt;comment&gt;
+</dt><dd>Provide a new comment.
+</dd>
+
+<dt><strong>-N</strong>, <strong>--newpass</strong> &lt;pass phrase&gt;
+</dt><dd>Provide new passphrase.
+</dd>
+
+<dt><strong>-P</strong>, <strong>--pass</strong> &lt;pass phrase&gt;
+</dt><dd>Provide old passphrase.
+</dd>
+
+<dt><strong>-l</strong>, <strong>--fingerprint</strong>
+</dt><dd>Show fingerprint of key file.
+</dd>
+
+<dt><strong>-p</strong>, <strong>--changepass</strong>
+</dt><dd>Change passphrase of private key file.
+</dd>
+
+<dt><strong>-q</strong>, <strong>--quiet</strong>
+</dt><dd>Be quiet.
+</dd>
+
+<dt><strong>-y</strong>, <strong>--showpub</strong>
+</dt><dd>Read private key file and print public key.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Display version number only.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>DESCRIPTION<a name="auto3"/></h2>
+
+<p>Manipulate public/private keys in various ways.
+If no filename is given, a file name will be requested interactively.
+</p>
+
+<h2>AUTHOR<a name="auto4"/></h2>
+
+<p>Written by Moshe Zadka, based on ckeygen's help messages
+</p>
+
+<h2>REPORTING BUGS<a name="auto5"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto6"/></h2>
+
+<p>Copyright © 2002-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+<h2>SEE ALSO<a name="auto7"/></h2>
+
+<p>ssh(1), conch(1)
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/conch/man/ckeygen.1 b/vendor/Twisted-10.0.0/doc/conch/man/ckeygen.1
new file mode 100644
index 0000000000..a06d039011
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/man/ckeygen.1
@@ -0,0 +1,58 @@
+.TH CKEYGEN "1" "October 2002" "" ""
+.SH NAME
+ckeygen \- connect to SSH servers
+.SH SYNOPSIS
+.B ckeygen [-b \fIbits\fR] [-f \fIfilename\fR] [-t \fItype\fR]
+.B [-C \fIcomment\fR] [-N \fInew passphrase\fR] [-P \fIold passphrase\fR]
+.B [-l] [-p] [-q] [-y]
+.B ckeygen --help
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB-b\fR, \fB--bits\fR <bits>
+Number of bits in the key to create (default: 1024)
+.TP
+\fB-f\fR, \fB--filename\fR <file name>
+Filename of the key file.
+.TP
+\fB-t\fR, \fB--type\fR <type>
+Type of key (rsa or dsa).
+.TP
+\fB-C\fR, \fB--comment\fR <comment>
+Provide a new comment.
+.TP
+\fB-N\fR, \fB--newpass\fR <pass phrase>
+Provide new passphrase.
+.TP
+\fB-P\fR, \fB--pass\fR <pass phrase>
+Provide old passphrase.
+.TP
+\fB-l\fR, \fB--fingerprint\fR
+Show fingerprint of key file.
+.TP
+\fB-p\fR, \fB--changepass\fR
+Change passphrase of private key file.
+.TP
+\fB-q\fR, \fB--quiet\fR
+Be quiet.
+.TP
+\fB-y\fR, \fB--showpub\fR
+Read private key file and print public key.
+.TP
+\fB--version\fR
+Display version number only.
+.SH DESCRIPTION
+Manipulate public/private keys in various ways.
+If no filename is given, a file name will be requested interactively.
+.SH AUTHOR
+Written by Moshe Zadka, based on ckeygen's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2002-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+ssh(1), conch(1)
diff --git a/vendor/Twisted-10.0.0/doc/conch/man/conch-man.html b/vendor/Twisted-10.0.0/doc/conch/man/conch-man.html
new file mode 100644
index 0000000000..5f04778552
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/man/conch-man.html
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: CONCH.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">CONCH.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li><li><a href="#auto6">SEE ALSO</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>conch </p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p>conch [<strong>-AaCfINnrsTtVvx</strong>][<strong>-c</strong><em> cipher_spec</em>][<strong>-e</strong><em> escape_char</em>][<strong>-i</strong><em> identity_file</em>][<strong>-K</strong><em> connection_spec</em>][<strong>-L</strong><em> port</em>:<em> host</em>:<em> hostport</em>][<strong>-l</strong><em> user</em>][<strong>-m</strong><em> mac_spec</em>][<strong>-o</strong><em> openssh_option</em>][<strong>-p</strong><em> port</em>][<strong>-R</strong><em> port</em>:<em> host</em>:<em> hostport</em>][<em> user</em>@]<em> hostname</em>[<em> command</em>]</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>conch is a SSHv2 client for logging into a remote machine and executing commands. It provides encrypted and secure communications across a possibly insecure network. Arbitrary TCP/IP ports can also be forwarded over the secure connection.
+</p>
+
+<p>conch connects and logs into
+<em> hostname</em>(as
+<em> user</em>or the current username). The user must prove her/his identity through a public-key or a password. Alternatively, if a connection is already open to a server, a new shell can be opened over the connection without having to reauthenticate.
+</p>
+
+<p>If
+<em> command</em>is specified,
+<em> command</em>is executed instead of a shell. If the
+<strong>-s</strong>option is given,
+<em> command</em>is treated as an SSHv2 subsystem name.
+Conch supports the public-key, keyboard-interactive, and password authentications.
+</p>
+
+<p>The public-key method allows the RSA or DSA algorithm to be used. The client uses his/her private key,
+or
+to sign the session identifier, known only by the client and server. The server checks that the matching public key is valid for the user, and that the signature is correct.
+</p>
+
+<p>If public-key authentication fails,
+conch can authenticate by sending an encrypted password over the connection.
+conch has the ability to multiplex multiple shells, commands and TCP/IP ports over the same secure connection. To disable multiplexing for a connection, use the
+<strong>-I</strong>flag.
+</p>
+
+<p>The
+<strong>-K</strong>option determines how the client connects to the remote host. It is a comma-separated list of the methods to use, in order of preference. The two connection methods are
+(for connecting over a multiplexed connection) and
+(to connect directly).
+To disable connecting over a multiplexed connection, do not include
+in the preference list.
+</p>
+
+<p>As an example of how connection sharing works, to speed up CVS over SSH:
+</p>
+
+<p>conch --noshell --fork -l cvs_user cvs_host
+set CVS_RSH=<strong>conch</strong>
+</p>
+
+<p>Now, when CVS connects to cvs_host as cvs_user, instead of making a new connection to the server,
+conch will add a new channel to the existing connection. This saves the cost of repeatedly negotiating the cryptography and authentication.
+</p>
+
+<p>The options are as follows:
+<dl><dt><strong>-A</strong></dt><dd>Enables authentication agent forwarding.
+</dd><dt><strong>-a</strong></dt><dd>Disables authentication agent forwarding (default).
+</dd><dt><strong>-C</strong></dt><dd>Enable compression.
+</dd><dt><strong>-c</strong></dt><dd><em> cipher_spec</em>Selects encryption algorithms to be used for this connection, as a comma-separated list of ciphers in order of preference. The list that
+conch supports is (in order of default preference): aes256-ctr, aes256-cbc, aes192-ctr, aes192-cbc, aes128-ctr, aes128-cbc, cast128-ctr, cast128-cbc, blowfish-ctr, blowfish, idea-ctr, idea-cbc, 3des-ctr, 3des-cbc.
+</dd><dt><strong>-e</strong></dt><dd><em> ch</em>| ^ch | noneSets the escape character for sessions with a PTY (default:
+The escape character is only recognized at the beginning of a line (after a newline).
+The escape character followed by a dot
+closes the connection;
+followed by ^Z suspends the connection;
+and followed by the escape character sends the escape character once.
+Setting the character to
+disables any escapes.
+</dd><dt><strong>-f</strong></dt><dd>Fork to background after authentication.
+</dd><dt><strong>-I</strong></dt><dd>Do not allow connection sharing over this connection.
+</dd><dt><strong>-i</strong></dt><dd><em> identity_spec</em>The file from which the identity (private key) for RSA or DSA authentication is read.
+The defaults are
+and
+It is possible to use this option more than once to use more than one private key.
+</dd><dt><strong>-K</strong></dt><dd><em> connection_spec</em>Selects methods for connection to the server, as a comma-separated list of methods in order of preference. See
+for more information.
+</dd><dt><strong>-L</strong></dt><dd><em> port</em>: host : hostportSpecifies that the given port on the client host is to be forwarded to the given host and port on the remote side. This allocates a socket to listen to
+<em> port</em>on the local side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+<em> host</em>port
+<em> hostport</em>from the remote machine.
+Only root can forward privieged ports.
+</dd><dt><strong>-l</strong></dt><dd><em> user</em>Log in using this username.
+</dd><dt><strong>-m</strong></dt><dd><em> mac_spec</em>Selects MAC (message authentication code) algorithms, as a comma-separated list in order of preference. The list that
+conch supports is (in order of preference): hmac-sha1, hmac-md5.
+</dd><dt><strong>-N</strong></dt><dd>Do not execute a shell or command.
+</dd><dt><strong>-n</strong></dt><dd>Redirect input from /dev/null.
+</dd><dt><strong>-o</strong></dt><dd><em> openssh_option</em>Ignored OpenSSH options.
+</dd><dt><strong>-p</strong></dt><dd><em> port</em>The port to connect to on the server.
+</dd><dt><strong>-R</strong></dt><dd><em> port</em>: host : hostportSpecifies that the given port on the remote host is to be forwarded to the given host and port on the local side. This allocates a socket to listen to
+<em> port</em>on the remote side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+<em> host</em>port
+<em> hostport</em>from the client host.
+Only root can forward privieged ports.
+</dd><dt><strong>-s</strong></dt><dd>Reconnect to the server if the connection is lost.
+</dd><dt><strong>-s</strong></dt><dd>Invoke
+<em> command</em>(mandatory) as a SSHv2 subsystem.
+</dd><dt><strong>-T</strong></dt><dd>Do not allocate a TTY.
+</dd><dt><strong>-t</strong></dt><dd>Allocate a TTY even if command is given.
+</dd><dt><strong>-V</strong></dt><dd>Display version number only.
+</dd><dt><strong>-v</strong></dt><dd>Log to stderr.
+</dd><dt><strong>-x</strong></dt><dd>Disable X11 connection forwarding (default).
+</dd></dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Paul Swartz &lt;z3p@twistedmatrix.com&gt;.
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2002-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+<h2>SEE ALSO<a name="auto6"/></h2>
+
+<p>ssh(1)
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/conch/man/conch.1 b/vendor/Twisted-10.0.0/doc/conch/man/conch.1
new file mode 100644
index 0000000000..7ba9bfff30
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/man/conch.1
@@ -0,0 +1,206 @@
+.Dd May 22, 2004
+.Dt CONCH 1
+.Os
+.Sh NAME
+.Nm conch
+.Nd Conch SSH client
+.Sh SYNOPSIS
+.Nm conch
+.Op Fl AaCfINnrsTtVvx
+.Op Fl c Ar cipher_spec
+.Op Fl e Ar escape_char
+.Op Fl i Ar identity_file
+.Op Fl K Ar connection_spec
+.Bk -words
+.Oo Fl L Xo
+.Sm off
+.Ar port :
+.Ar host :
+.Ar hostport
+.Sm on
+.Xc
+.Oc
+.Ek
+.Op Fl l Ar user
+.Op Fl m Ar mac_spec
+.Op Fl o Ar openssh_option
+.Op Fl p Ar port
+.Bk -words
+.Oo Fl R Xo
+.Sm off
+.Ar port :
+.Ar host :
+.Ar hostport
+.Sm on
+.Xc
+.Oc
+.Ek
+.Oo Ar user Ns @ Ns Oc Ar hostname
+.Op Ar command
+.Sh DESCRIPTION
+.Nm
+is a SSHv2 client for logging into a remote machine and executing commands. It provides encrypted and secure communications across a possibly insecure network. Arbitrary TCP/IP ports can also be forwarded over the secure connection.
+.Pp
+.Nm
+connects and logs into
+.Ar hostname
+(as
+.Ar user
+or the current username). The user must prove her/his identity through a public\-key or a password. Alternatively, if a connection is already open to a server, a new shell can be opened over the connection without having to reauthenticate.
+.Pp
+If
+.Ar command
+is specified,
+.Ar command
+is executed instead of a shell. If the
+.Fl s
+option is given,
+.Ar command
+is treated as an SSHv2 subsystem name.
+.Ss Authentication
+Conch supports the public-key, keyboard-interactive, and password authentications.
+.Pp
+The public-key method allows the RSA or DSA algorithm to be used. The client uses his/her private key,
+.Pa $HOME/.ssh/id_rsa
+or
+.Pa $HOME/.ssh/id_dsa
+to sign the session identifier, known only by the client and server. The server checks that the matching public key is valid for the user, and that the signature is correct.
+.Pp
+If public-key authentication fails,
+.Nm
+can authenticate by sending an encrypted password over the connection.
+.Ss Connection sharing
+.Nm
+has the ability to multiplex multiple shells, commands and TCP/IP ports over the same secure connection. To disable multiplexing for a connection, use the
+.Fl I
+flag.
+.Pp
+The
+.Fl K
+option determines how the client connects to the remote host. It is a comma-separated list of the methods to use, in order of preference. The two connection methods are
+.Ql unix
+(for connecting over a multiplexed connection) and
+.Ql direct
+(to connect directly).
+To disable connecting over a multiplexed connection, do not include
+.Ql unix
+in the preference list.
+.Pp
+As an example of how connection sharing works, to speed up CVS over SSH:
+.Pp
+.Nm
+--noshell --fork -l cvs_user cvs_host
+.br
+set CVS_RSH=\fBconch\fR
+.Pp
+Now, when CVS connects to cvs_host as cvs_user, instead of making a new connection to the server,
+.Nm
+will add a new channel to the existing connection. This saves the cost of repeatedly negotiating the cryptography and authentication.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl A
+Enables authentication agent forwarding.
+.It Fl a
+Disables authentication agent forwarding (default).
+.It Fl C
+Enable compression.
+.It Fl c Ar cipher_spec
+Selects encryption algorithms to be used for this connection, as a comma-separated list of ciphers in order of preference. The list that
+.Nm
+supports is (in order of default preference): aes256-ctr, aes256-cbc, aes192-ctr, aes192-cbc, aes128-ctr, aes128-cbc, cast128-ctr, cast128-cbc, blowfish-ctr, blowfish, idea-ctr, idea-cbc, 3des-ctr, 3des-cbc.
+.It Fl e Ar ch | ^ch | none
+Sets the escape character for sessions with a PTY (default:
+.Ql ~ ) .
+The escape character is only recognized at the beginning of a line (after a newline).
+The escape character followed by a dot
+.Pq Ql \&.
+closes the connection;
+followed by ^Z suspends the connection;
+and followed by the escape character sends the escape character once.
+Setting the character to
+.Dq none
+disables any escapes.
+.It Fl f
+Fork to background after authentication.
+.It Fl I
+Do not allow connection sharing over this connection.
+.It Fl i Ar identity_spec
+The file from which the identity (private key) for RSA or DSA authentication is read.
+The defaults are
+.Pa $HOME/.ssh/id_rsa
+and
+.Pa $HOME/.ssh/id_dsa .
+It is possible to use this option more than once to use more than one private key.
+.It Fl K Ar connection_spec
+Selects methods for connection to the server, as a comma-separated list of methods in order of preference. See
+.Cm Connection sharing
+for more information.
+.It Fl L Xo
+.Sm off
+.Ar port : host : hostport
+.Sm on
+.Xc
+Specifies that the given port on the client host is to be forwarded to the given host and port on the remote side. This allocates a socket to listen to
+.Ar port
+on the local side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+.Ar host
+port
+.Ar hostport
+from the remote machine.
+Only root can forward privieged ports.
+.It Fl l Ar user
+Log in using this username.
+.It Fl m Ar mac_spec
+Selects MAC (message authentication code) algorithms, as a comma-separated list in order of preference. The list that
+.Nm
+supports is (in order of preference): hmac-sha1, hmac-md5.
+.It Fl N
+Do not execute a shell or command.
+.It Fl n
+Redirect input from /dev/null.
+.It Fl o Ar openssh_option
+Ignored OpenSSH options.
+.It Fl p Ar port
+The port to connect to on the server.
+.It Fl R Xo
+.Sm off
+.Ar port : host : hostport
+.Sm on
+.Xc
+Specifies that the given port on the remote host is to be forwarded to the given host and port on the local side. This allocates a socket to listen to
+.Ar port
+on the remote side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+.Ar host
+port
+.Ar hostport
+from the client host.
+Only root can forward privieged ports.
+.It Fl s
+Reconnect to the server if the connection is lost.
+.It Fl s
+Invoke
+.Ar command
+(mandatory) as a SSHv2 subsystem.
+.It Fl T
+Do not allocate a TTY.
+.It Fl t
+Allocate a TTY even if command is given.
+.It Fl V
+Display version number only.
+.It Fl v
+Log to stderr.
+.It Fl x
+Disable X11 connection forwarding (default).
+.El
+.Sh AUTHOR
+Written by Paul Swartz <z3p@twistedmatrix.com>.
+.Sh "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.Sh COPYRIGHT
+Copyright \(co 2002-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.Sh SEE ALSO
+ssh(1)
diff --git a/vendor/Twisted-10.0.0/doc/conch/man/tkconch-man.html b/vendor/Twisted-10.0.0/doc/conch/man/tkconch-man.html
new file mode 100644
index 0000000000..a335e0851a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/man/tkconch-man.html
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: CONCH.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">CONCH.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">DESCRIPTION</a></li><li><a href="#auto4">AUTHOR</a></li><li><a href="#auto5">REPORTING BUGS</a></li><li><a href="#auto6">COPYRIGHT</a></li><li><a href="#auto7">SEE ALSO</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>tkconch - connect to SSH servers graphically
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>conch</strong> [-l <em>user</em>] [-i <em>identity</em> [ -i <em>identity</em> ... ]] [-c <em>cipher</em>] [-m <em>MAC</em>] [-p <em>port</em>] [-n] [-t] [-T] [-V] [-C] [-N] [-s] [arg [...]]</p>
+
+<p><strong>conch</strong> --help</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>The <strong>--help</strong> prints out a usage message to standard output.
+<dl><dt><strong>-l</strong>, <strong>--user</strong> &lt;user&gt;
+</dt><dd>Log in using this user name.
+</dd>
+
+<dt><strong>-e</strong>, <strong>--escape</strong> &lt;escape character&gt;
+</dt><dd>Set escape character; 'none' = disable (default: ~)
+</dd>
+
+<dt><strong>-i</strong>, <strong>--identity</strong> &lt;identity&gt;
+</dt><dd>Add an identity file for public key authentication (default: ~/.ssh/identity)
+</dd>
+
+<dt><strong>-c</strong>, <strong>--cipher</strong> &lt;cipher&gt;
+</dt><dd>Cipher algorithm to use.
+</dd>
+
+<dt><strong>-m</strong>, <strong>--macs</strong> &lt;mac&gt;
+</dt><dd>Specify MAC algorithms for protocol version 2.
+</dd>
+
+<dt><strong>-p</strong>, <strong>--port</strong> &lt;port&gt;
+</dt><dd>Port to connect to.
+</dd>
+
+<dt><strong>-L</strong>, <strong>--localforward</strong> &lt;listen-port:host:port&gt;
+</dt><dd>Forward local port to remote address.
+</dd>
+
+<dt><strong>-R</strong>, <strong>--remoteforward</strong> &lt;listen-port:host:port&gt;
+</dt><dd>Forward remote port to local address.
+</dd>
+
+<dt><strong>-t</strong>, <strong>--tty</strong>
+</dt><dd>Allocate a tty even if command is given.
+</dd>
+
+<dt><strong>-n</strong>, <strong>--notty</strong>
+</dt><dd>Do not allocate a tty.
+</dd>
+
+<dt><strong>-V</strong>, <strong>--version</strong>
+</dt><dd>Display version number only.
+</dd>
+
+<dt><strong>-C</strong>, <strong>--compress</strong>
+</dt><dd>Enable compression.
+</dd>
+
+<dt><strong>-a</strong>, <strong>--ansilog</strong>
+</dt><dd>Print the received data to stdout.
+</dd>
+
+<dt><strong>-N</strong>, <strong>--noshell</strong>
+</dt><dd>Do not execute a shell or command.
+</dd>
+
+<dt><strong>-s</strong>, <strong>--subsystem</strong>
+</dt><dd>Invoke command (mandatory) as SSH2 subsystem.
+</dd>
+
+<dt><strong>--log</strong>
+</dt><dd>Print the receieved data to stderr.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>DESCRIPTION<a name="auto3"/></h2>
+
+<p>Open an SSH connection to specified server, and either run the command
+given there or open a remote interactive shell.
+</p>
+
+<h2>AUTHOR<a name="auto4"/></h2>
+
+<p>Written by Moshe Zadka, based on conch's help messages
+</p>
+
+<h2>REPORTING BUGS<a name="auto5"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto6"/></h2>
+
+<p>Copyright © 2002-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+<h2>SEE ALSO<a name="auto7"/></h2>
+
+<p>ssh(1)
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/conch/man/tkconch.1 b/vendor/Twisted-10.0.0/doc/conch/man/tkconch.1
new file mode 100644
index 0000000000..54260bf501
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/conch/man/tkconch.1
@@ -0,0 +1,72 @@
+.TH CONCH "1" "October 2002" "" ""
+.SH NAME
+tkconch \- connect to SSH servers graphically
+.SH SYNOPSIS
+.B conch [-l \fIuser\fR] [-i \fIidentity\fR [ -i \fIidentity\fR ... ]] [-c \fIcipher\fR] [-m \fIMAC\fR] [-p \fIport\fR] [-n] [-t] [-T] [-V] [-C] [-N] [-s] [arg [...]]
+.PP
+.B conch --help
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB-l\fR, \fB--user\fR <user>
+Log in using this user name.
+.TP
+\fB-e\fR, \fB--escape\fR <escape character>
+Set escape character; 'none' = disable (default: ~)
+.TP
+\fB-i\fR, \fB--identity\fR <identity>
+Add an identity file for public key authentication (default: ~/.ssh/identity)
+.TP
+\fB-c\fR, \fB--cipher\fR <cipher>
+Cipher algorithm to use.
+.TP
+\fB-m\fR, \fB--macs\fR <mac>
+Specify MAC algorithms for protocol version 2.
+.TP
+\fB-p\fR, \fB--port\fR <port>
+Port to connect to.
+.TP
+\fB-L\fR, \fB--localforward\fR <listen-port:host:port>
+Forward local port to remote address.
+.TP
+\fB-R\fR, \fB--remoteforward\fR <listen-port:host:port>
+Forward remote port to local address.
+.TP
+\fB-t\fR, \fB--tty\fR
+Allocate a tty even if command is given.
+.TP
+\fB-n\fR, \fB--notty\fR
+Do not allocate a tty.
+.TP
+\fB-V\fR, \fB--version\fR
+Display version number only.
+.TP
+\fB-C\fR, \fB--compress\fR
+Enable compression.
+.TP
+\fB-a\fR, \fB--ansilog\fR
+Print the received data to stdout.
+.TP
+\fB-N\fR, \fB--noshell\fR
+Do not execute a shell or command.
+.TP
+\fB-s\fR, \fB--subsystem\fR
+Invoke command (mandatory) as SSH2 subsystem.
+.TP
+\fB--log\fR
+Print the receieved data to stderr.
+.SH DESCRIPTION
+Open an SSH connection to specified server, and either run the command
+given there or open a remote interactive shell.
+.SH AUTHOR
+Written by Moshe Zadka, based on conch's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2002-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+ssh(1)
diff --git a/vendor/Twisted-10.0.0/doc/core/benchmarks/banana.py b/vendor/Twisted-10.0.0/doc/core/benchmarks/banana.py
new file mode 100644
index 0000000000..1c1f031704
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/benchmarks/banana.py
@@ -0,0 +1,10 @@
+#!/usr/bin/python
+
+from timer import timeit
+from twisted.spread.banana import b1282int
+
+ITERATIONS = 100000
+
+for length in (1, 5, 10, 50, 100):
+ elapsed = timeit(b1282int, ITERATIONS, "\xff" * length)
+ print "b1282int %3d byte string: %10d cps" % (length, ITERATIONS / elapsed)
diff --git a/vendor/Twisted-10.0.0/doc/core/benchmarks/deferreds.py b/vendor/Twisted-10.0.0/doc/core/benchmarks/deferreds.py
new file mode 100644
index 0000000000..a9cddd0b34
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/benchmarks/deferreds.py
@@ -0,0 +1,145 @@
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+See how fast deferreds are.
+
+This is mainly useful to compare cdefer.Deferred to defer.Deferred
+"""
+
+
+from twisted.internet import defer
+from timer import timeit
+
+benchmarkFuncs = []
+
+def benchmarkFunc(iter, args=()):
+ """
+ A decorator for benchmark functions that measure a single iteration
+ count. Registers the function with the given iteration count to the global
+ benchmarkFuncs list
+ """
+ def decorator(func):
+ benchmarkFuncs.append((func, args, iter))
+ return func
+ return decorator
+
+def benchmarkNFunc(iter, ns):
+ """
+ A decorator for benchmark functions that measure multiple iteration
+ counts. Registers the function with the given iteration count to the global
+ benchmarkFuncs list.
+ """
+ def decorator(func):
+ for n in ns:
+ benchmarkFuncs.append((func, (n,), iter))
+ return func
+ return decorator
+
+def instantiate():
+ """
+ Only create a deferred
+ """
+ d = defer.Deferred()
+instantiate = benchmarkFunc(100000)(instantiate)
+
+def instantiateShootCallback():
+ """
+ Create a deferred and give it a normal result
+ """
+ d = defer.Deferred()
+ d.callback(1)
+instantiateShootCallback = benchmarkFunc(100000)(instantiateShootCallback)
+
+def instantiateShootErrback():
+ """
+ Create a deferred and give it an exception result. To avoid Unhandled
+ Errors, also register an errback that eats the error
+ """
+ d = defer.Deferred()
+ try:
+ 1/0
+ except:
+ d.errback()
+ d.addErrback(lambda x: None)
+instantiateShootErrback = benchmarkFunc(200)(instantiateShootErrback)
+
+ns = [10, 1000, 10000]
+
+def instantiateAddCallbacksNoResult(n):
+ """
+ Creates a deferred and adds a trivial callback/errback/both to it the given
+ number of times.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f, f)
+instantiateAddCallbacksNoResult = benchmarkNFunc(20, ns)(instantiateAddCallbacksNoResult)
+
+def instantiateAddCallbacksBeforeResult(n):
+ """
+ Create a deferred and adds a trivial callback/errback/both to it the given
+ number of times, and then shoots a result through all of the callbacks.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f)
+ d.callback(1)
+instantiateAddCallbacksBeforeResult = benchmarkNFunc(20, ns)(instantiateAddCallbacksBeforeResult)
+
+def instantiateAddCallbacksAfterResult(n):
+ """
+ Create a deferred, shoots it and then adds a trivial callback/errback/both
+ to it the given number of times. The result is processed through the
+ callbacks as they are added.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ d.callback(1)
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f)
+instantiateAddCallbacksAfterResult = benchmarkNFunc(20, ns)(instantiateAddCallbacksAfterResult)
+
+def pauseUnpause(n):
+ """
+ Adds the given number of callbacks/errbacks/both to a deferred while it is
+ paused, and unpauses it, trigerring the processing of the value through the
+ callbacks.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ d.callback(1)
+ d.pause()
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f)
+ d.unpause()
+pauseUnpause = benchmarkNFunc(20, ns)(pauseUnpause)
+
+def benchmark():
+ """
+ Run all of the benchmarks registered in the benchmarkFuncs list
+ """
+ print defer.Deferred.__module__
+ for func, args, iter in benchmarkFuncs:
+ print func.__name__, args, timeit(func, iter, *args)
+
+if __name__ == '__main__':
+ benchmark()
diff --git a/vendor/Twisted-10.0.0/doc/core/benchmarks/failure.py b/vendor/Twisted-10.0.0/doc/core/benchmarks/failure.py
new file mode 100644
index 0000000000..d98cb4929b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/benchmarks/failure.py
@@ -0,0 +1,66 @@
+
+"""See how slow failure creation is"""
+
+import random
+from twisted.python import failure
+
+random.seed(10050)
+O = [0, 20, 40, 60, 80, 10, 30, 50, 70, 90]
+DEPTH = 30
+
+def pickVal():
+ return random.choice([None, 1, 'Hello', [], {1: 1}, (1, 2, 3)])
+
+def makeLocals(n):
+ return ';'.join(['x%d = %s' % (i, pickVal()) for i in range(n)])
+
+for nLocals in O:
+ for i in range(DEPTH):
+ s = """
+def deepFailure%d_%d():
+ %s
+ deepFailure%d_%d()
+""" % (nLocals, i, makeLocals(nLocals), nLocals, i + 1)
+ exec s
+
+ exec """
+def deepFailure%d_%d():
+ 1 / 0
+""" % (nLocals, DEPTH)
+
+R = range(5000)
+def fail(n):
+ for i in R:
+ try:
+ eval('deepFailure%d_0' % n)()
+ except:
+ failure.Failure()
+
+def fail_str(n):
+ for i in R:
+ try:
+ eval('deepFailure%d_0' % n)()
+ except:
+ str(failure.Failure())
+
+class PythonException(Exception): pass
+
+def fail_easy(n):
+ for i in R:
+ try:
+ failure.Failure(PythonException())
+ except:
+ pass
+
+from timer import timeit
+# for i in O:
+# timeit(fail, 1, i)
+
+# for i in O:
+# print 'easy failing', i, timeit(fail_easy, 1, i)
+
+for i in O:
+ print 'failing', i, timeit(fail, 1, i)
+
+# for i in O:
+# print 'string failing', i, timeit(fail_str, 1, i)
diff --git a/vendor/Twisted-10.0.0/doc/core/benchmarks/linereceiver.py b/vendor/Twisted-10.0.0/doc/core/benchmarks/linereceiver.py
new file mode 100644
index 0000000000..7f552919e3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/benchmarks/linereceiver.py
@@ -0,0 +1,47 @@
+import math, time
+
+from twisted.protocols import basic
+
+class CollectingLineReceiver(basic.LineReceiver):
+ def __init__(self):
+ self.lines = []
+ self.lineReceived = self.lines.append
+
+def deliver(proto, chunks):
+ map(proto.dataReceived, chunks)
+
+def benchmark(chunkSize, lineLength, numLines):
+ bytes = ('x' * lineLength + '\r\n') * numLines
+ chunkCount = len(bytes) / chunkSize + 1
+ chunks = []
+ for n in xrange(chunkCount):
+ chunks.append(bytes[n*chunkSize:(n+1)*chunkSize])
+ assert ''.join(chunks) == bytes, (chunks, bytes)
+ p = CollectingLineReceiver()
+
+ before = time.clock()
+ deliver(p, chunks)
+ after = time.clock()
+
+ assert bytes.splitlines() == p.lines, (bytes.splitlines(), p.lines)
+
+ print 'chunkSize:', chunkSize,
+ print 'lineLength:', lineLength,
+ print 'numLines:', numLines,
+ print 'CPU Time: ', after - before
+
+
+
+def main():
+ for numLines in 100, 1000:
+ for lineLength in (10, 100, 1000):
+ for chunkSize in (1, 500, 5000):
+ benchmark(chunkSize, lineLength, numLines)
+
+ for numLines in 10000, 50000:
+ for lineLength in (1000, 2000):
+ for chunkSize in (51, 500, 5000):
+ benchmark(chunkSize, lineLength, numLines)
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/benchmarks/task.py b/vendor/Twisted-10.0.0/doc/core/benchmarks/task.py
new file mode 100644
index 0000000000..e3d437b42c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/benchmarks/task.py
@@ -0,0 +1,26 @@
+
+"""
+Benchmarks for L{twisted.internet.task}.
+"""
+
+from timer import timeit
+
+from twisted.internet import task
+
+def test_performance():
+ """
+ L{LoopingCall} should not take long to skip a lot of iterations.
+ """
+ clock = task.Clock()
+ call = task.LoopingCall(lambda: None)
+ call.clock = clock
+
+ call.start(0.1)
+ clock.advance(1000000)
+
+
+def main():
+ print "LoopingCall large advance takes", timeit(test_performance, iter=1)
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/benchmarks/timer.py b/vendor/Twisted-10.0.0/doc/core/benchmarks/timer.py
new file mode 100644
index 0000000000..4b15a03533
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/benchmarks/timer.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Helper stuff for benchmarks.
+"""
+
+import gc
+gc.disable()
+print 'Disabled GC'
+
+def timeit(func, iter = 1000, *args, **kwargs):
+ """
+ timeit(func, iter = 1000 *args, **kwargs) -> elapsed time
+
+ calls func iter times with args and kwargs, returns time elapsed
+ """
+
+ from time import time as currentTime
+ r = range(iter)
+ t = currentTime()
+ for i in r:
+ func(*args, **kwargs)
+ return currentTime() - t
diff --git a/vendor/Twisted-10.0.0/doc/core/benchmarks/tpclient.py b/vendor/Twisted-10.0.0/doc/core/benchmarks/tpclient.py
new file mode 100644
index 0000000000..9e5e082b0b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/benchmarks/tpclient.py
@@ -0,0 +1,60 @@
+"""Throughput test."""
+
+import time, sys
+from twisted.internet import reactor, protocol
+from twisted.python import log
+
+TIMES = 10000
+S = "0123456789" * 1240
+
+toReceive = len(S) * TIMES
+
+class Sender(protocol.Protocol):
+
+ def connectionMade(self):
+ start()
+ self.numSent = 0
+ self.received = 0
+ self.transport.registerProducer(self, 0)
+
+ def stopProducing(self):
+ pass
+
+ def pauseProducing(self):
+ pass
+
+ def resumeProducing(self):
+ self.numSent += 1
+ self.transport.write(S)
+ if self.numSent == TIMES:
+ self.transport.unregisterProducer()
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ shutdown(self.numSent == TIMES)
+
+
+started = None
+
+def start():
+ global started
+ started = time.time()
+
+def shutdown(success):
+ if not success:
+ raise SystemExit, "failure or something"
+ passed = time.time() - started
+ print "Throughput (send): %s kbytes/sec" % ((toReceive / passed) / 1024)
+ reactor.stop()
+
+
+def main():
+ f = protocol.ClientFactory()
+ f.protocol = Sender
+ reactor.connectTCP(sys.argv[1], int(sys.argv[2]), f)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ #log.startLogging(sys.stdout)
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/benchmarks/tpclient_nt.py b/vendor/Twisted-10.0.0/doc/core/benchmarks/tpclient_nt.py
new file mode 100644
index 0000000000..a8170d77b2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/benchmarks/tpclient_nt.py
@@ -0,0 +1,22 @@
+"""Non-twisted throughput client."""
+
+import socket, time, sys
+
+TIMES = 50000
+S = "0123456789" * 1024
+sent = len(S) * TIMES
+
+def main():
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((sys.argv[1], int(sys.argv[2])))
+ start = time.time()
+ i = 0
+ while i < TIMES:
+ i += 1
+ s.sendall(S)
+ passed = time.time() - start
+ print "Throughput: %s kbytes/sec" % ((sent / passed) / 1024)
+ s.close()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/benchmarks/tpserver.py b/vendor/Twisted-10.0.0/doc/core/benchmarks/tpserver.py
new file mode 100644
index 0000000000..49024e1409
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/benchmarks/tpserver.py
@@ -0,0 +1,19 @@
+"""Throughput server."""
+
+import sys
+
+from twisted.protocols.wire import Discard
+from twisted.internet import protocol, reactor
+from twisted.python import log
+
+
+def main():
+ f = protocol.ServerFactory()
+ f.protocol = Discard
+ reactor.listenTCP(8000, f)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/benchmarks/tpserver_nt.py b/vendor/Twisted-10.0.0/doc/core/benchmarks/tpserver_nt.py
new file mode 100644
index 0000000000..e4bfddad79
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/benchmarks/tpserver_nt.py
@@ -0,0 +1,22 @@
+"""Non-twisted throughput server."""
+
+import socket, signal, sys
+
+def signalhandler(*args):
+ print "alarm!"
+ sys.stdout.flush()
+
+signal.signal(signal.SIGALRM, signalhandler)
+
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.bind(('', 8001))
+s.listen(1)
+while 1:
+ c, (h, p) = s.accept()
+ c.settimeout(30)
+ signal.alarm(5)
+ while 1:
+ d = c.recv(16384)
+ if not d:
+ break
+ c.close()
diff --git a/vendor/Twisted-10.0.0/doc/core/development/index.html b/vendor/Twisted-10.0.0/doc/core/development/index.html
new file mode 100644
index 0000000000..63bf029543
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/index.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Development of Twisted</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Development of Twisted</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>This documentation is for people who work on the Twisted codebase itself,
+rather than for people who want to use Twisted in their own projects.</p>
+<ul>
+<li><a href="naming.html" shape="rect">Naming</a></li>
+<li><a href="philosophy.html" shape="rect">Philosophy</a></li>
+<li><a href="security.html" shape="rect">Security</a></li>
+<li><a href="policy/" shape="rect">Twisted development policy</a></li>
+<li><a href="pb/" shape="rect">Twisted development for the pb modules</a></li>
+</ul>
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/development/listings/new_module_template.py b/vendor/Twisted-10.0.0/doc/core/development/listings/new_module_template.py
new file mode 100644
index 0000000000..ec3c2e5c2a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/listings/new_module_template.py
@@ -0,0 +1,12 @@
+# -*- test-case-name: <test module> -*-
+
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Docstring goes here.
+"""
+
+
+__all__ = []
diff --git a/vendor/Twisted-10.0.0/doc/core/development/naming.html b/vendor/Twisted-10.0.0/doc/core/development/naming.html
new file mode 100644
index 0000000000..5ec66c2d47
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/naming.html
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Naming Conventions</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Naming Conventions</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<p>While this may sound like a small detail, clear method naming is important to provide an API that developers familiar with event-based programming can pick up quickly.</p>
+
+<p>Since the idea of a method call maps very neatly onto that of a received event, all event handlers are simply methods named after past-tense verbs. All class names are descriptive nouns, designed to mirror the is-a relationship of the abstractions they implement. All requests for notification or transmission are present-tense imperative verbs.</p>
+
+<p>Here are some examples of this naming scheme:</p>
+
+<ul>
+<li>An event notification of data received from peer:
+<code class="python">dataReceived(data)</code></li>
+<li>A request to send data: <code class="python">write(data)</code></li>
+<li>A class that implements a protocol: <code class="python">Protocol</code></li>
+</ul>
+
+<p>The naming is platform neutral. This means that the names are equally appropriate in a wide variety of environments, as long as they can publish the required events.</p>
+
+<p>It is self-consistent. Things that deal with TCP use the acronym TCP, and it is always capitalized. Dropping, losing, terminating, and closing the connection are all referred to as <q>losing</q> the connection. This symmetrical naming allows developers to easily locate other API calls if they have learned a few related to what they want to do.</p>
+
+<p>It is semantically clear. The semantics of dataReceived are simple: there are some bytes available for processing. This remains true even if the lower-level machinery to get the data is highly complex.</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/development/philosophy.html b/vendor/Twisted-10.0.0/doc/core/development/philosophy.html
new file mode 100644
index 0000000000..c74591c957
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/philosophy.html
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Philosophy</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Philosophy</h1>
+ <div class="toc"><ol><li><a href="#auto0">Abstraction Levels</a></li><li><a href="#auto1">Learning Curves</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Abstraction Levels<a name="auto0"/></h2>
+
+<p>When implementing interfaces to the operating system or
+the network, provide two interfaces:</p>
+
+<ul>
+<li>One that doesn't hide platform specific or library specific
+functionality.
+For example, you can use file descriptors on Unix, and Win32 events on
+Windows.
+</li>
+<li>One that provides a high level interface hiding platform specific
+details.
+E.g. process running uses same API on Unix and Windows, although
+the implementation is very different.
+</li>
+</ul>
+
+<p>Restated in a more general way:</p>
+
+<ul>
+<li>Provide all low level functionality for your specific domain,
+without limiting the policies and decisions the user can make.</li>
+<li>Provide a high level abstraction on top of the low level
+implementation (or implementations) which implements the
+common use cases and functionality that is used in most cases.</li>
+</ul>
+
+<h2>Learning Curves<a name="auto1"/></h2>
+
+<p>Require the minimal amount of work and learning on part of the
+user to get started. If this means they have less functionality,
+that's OK, when they need it they can learn a bit more. This
+will also lead to a cleaner, easier to test design.</p>
+
+<p>For example - using twistd is a great way to deploy applications.
+But to get started you don't need to know about it. Later on you can
+start using twistd, but its usage is optional.</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/development/policy/coding-standard.html b/vendor/Twisted-10.0.0/doc/core/development/policy/coding-standard.html
new file mode 100644
index 0000000000..48b7b1de40
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/policy/coding-standard.html
@@ -0,0 +1,809 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Coding Standard</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Coding Standard</h1>
+ <div class="toc"><ol><li><a href="#auto0">Naming</a></li><li><a href="#auto1">Testing</a></li><ul><li><a href="#auto2">Overview</a></li><li><a href="#auto3">Test Suite</a></li></ul><li><a href="#auto4">Copyright Header</a></li><li><a href="#auto5">Whitespace</a></li><li><a href="#auto6">Modules</a></li><li><a href="#auto7">Packages</a></li><li><a href="#auto8">String Formatting Operations</a></li><li><a href="#auto9">Docstrings</a></li><li><a href="#auto10">Comments</a></li><li><a href="#auto11">Versioning</a></li><li><a href="#auto12">Scripts</a></li><li><a href="#auto13">Examples</a></li><li><a href="#auto14">Standard Library Extension Modules</a></li><li><a href="#auto15">Classes</a></li><ul><li><a href="#auto16">New-style Classes</a></li></ul><li><a href="#auto17">Methods</a></li><li><a href="#auto18">Callback Arguments</a></li><li><a href="#auto19">Special Methods</a></li><li><a href="#auto20">Functions</a></li><li><a href="#auto21">Attributes</a></li><li><a href="#auto22">Database</a></li><li><a href="#auto23">C Code</a></li><li><a href="#auto24">Commit Messages</a></li><li><a href="#auto25">Source Control</a></li><li><a href="#auto26">Fallback</a></li><li><a href="#auto27">Recommendations</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Naming<a name="auto0"/></h2>
+
+ <p>Try to choose names which are both easy to remember and
+ meaningful. Some silliness is OK at the module naming level
+ (see <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.html" title="twisted.spread">twisted.spread</a></code>...) but when
+ choosing class names, be as precise as possible.</p>
+
+ <p>Try to avoid overloaded terms. This rule is often broken,
+ since it is incredibly difficult, as most normal words have
+ already been taken by some other software. More importantly,
+ try to avoid meaningless words. In particular, words like
+ <q>handler</q>, <q>processor</q>, <q>engine</q>, <q>manager</q>
+ and <q>component</q> don't really indicate what something does,
+ only that it does <em>something</em>.</p>
+
+ <p>Use American spelling in both names and docstrings. For compound
+ technical terms such as 'filesystem', use a non-hyphenated spelling in
+ both docstrings and code in order to avoid unnecessary
+ capitalization.</p>
+
+ <h2>Testing<a name="auto1"/></h2>
+
+ <h3>Overview<a name="auto2"/></h3>
+
+ <p>Twisted development should always be
+ <a href="http://en.wikipedia.org/wiki/Test-driven_development" shape="rect">
+ test-driven</a>. The complete test suite in trunk@HEAD is required to
+ be passing on <a href="http://buildbot.twistedmatrix.com/supported" shape="rect">
+ supported platforms</a> at all times. Regressions in the test suite
+ are addressed by reverting whatever revisions introduced them. For
+ complete documentation about testing Twisted itself, refer to the
+ <a href="test-standard.html" shape="rect">Test Standard</a>. What follows is
+ intended to be a synopsis of the most important points.</p>
+
+ <h3>Test Suite<a name="auto3"/></h3>
+
+ <p>The Twisted test suite is spread across many subpackages of the
+ <code>twisted</code> package. Many tests are in
+ <code>twisted.test</code>. Others can be found at places such as
+ <code>twisted.web.test</code> or <code>twisted.internet.test</code>.
+ Parts of the Twisted test suite may serve as good examples of how to
+ write tests for Twisted or for Twisted-based libraries (newer parts of
+ the test suite are generally better examples than older parts - check
+ when the code you are looking at was written before you use it as an
+ example of what you should write). The names of test modules should
+ begin with <q>test_</q> so that they are automatically discoverable by
+ test runners such as Trial. Twisted's unit tests are written using
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.trial.html" title="twisted.trial">twisted.trial</a></code>, an xUnit library which has been
+ extensively customized for use in testing Twisted and Twisted-based
+ libraries.</p>
+
+ <p>Implementation (ie, non-test) source files should begin with a
+ <code>test-case-name</code> tag which gives the name of any test
+ modules or packages which exercise them. This lets tools discover a
+ subset of the entire test suite which they can run first to find tests
+ which might be broken by a particular change.</p>
+
+ <p>It is strongly suggested that developers learn to use Emacs, and use
+ the <code>twisted-dev.el</code> file included in the TwistedEmacs
+ package to bind the F9 key to <q>run unit tests</q> and bang on it
+ frequently. Support for other editors is unavailable at this time but
+ we would love to provide it.</p>
+
+ <p>To run the whole Twisted test without using emacs, use trial:</p>
+
+ <pre class="shell" xml:space="preserve">
+$ bin/trial twisted
+ </pre>
+
+ <p>To run an individual test module, such as
+ <code>twisted/mail/test/test_pop3.py</code>, specify the module
+ name:</p>
+
+ <pre class="shell" xml:space="preserve">
+$ bin/trial twisted.mail.test.test_pop3
+ </pre>
+
+ <p>To run the tests associated with a particular implementation file,
+ such as <code>twisted/mail/pop3.py</code>, use the
+ <code>testmodule</code> option:</p>
+
+ <pre class="shell" xml:space="preserve">
+$ bin/trial twisted/mail/pop3.py
+ </pre>
+
+ <p>All unit test methods should have docstrings specifying at a high
+ level the intent of the test. That is, a description that users of the
+ method would understand.</p>
+
+ <p>If you modify, or write a new, HOWTO, please read the <a href="http://twistedmatrix.com/trac/wiki/TwistedLore" shape="rect">Lore</a>
+ documentation to learn how to format the docs.</p>
+
+ <h2>Copyright Header<a name="auto4"/></h2>
+
+ <p>Whenever a new file is added to the repository, add the following
+ license header at the top of the file, including the year the file was
+ added. For example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+</pre>
+
+ <p>When you update existing files, make sure the year in the copyright
+ header is up to date as well. You should add a new copyright header when
+ it's completely missing in the file that is being edited.</p>
+
+ <h2>Whitespace<a name="auto5"/></h2>
+
+ <p>Indentation is 4 spaces per indent. Tabs are not allowed. It
+ is preferred that every block appear on a new line, so that
+ control structure indentation is always visible.</p>
+
+ <p>Lines are flowed at 79 columns. They must not have trailing
+ whitespace. Long lines must be wrapped using implied line continuation
+ inside parentheses; backslashes aren't allowed. To handle long import
+ lines, please repeat the import like this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">very</span>.<span class="py-src-variable">long</span>.<span class="py-src-variable">package</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">foo</span>, <span class="py-src-variable">bar</span>, <span class="py-src-variable">baz</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">very</span>.<span class="py-src-variable">long</span>.<span class="py-src-variable">package</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">qux</span>, <span class="py-src-variable">quux</span>, <span class="py-src-variable">quuux</span>
+</pre>
+
+ <p>Top-level classes and functions must be separated with 3 blank lines,
+ and class-level functions with 2 blank lines. The control-L (i.e. ^L) form
+ feed character must not be used.</p>
+
+ <h2>Modules<a name="auto6"/></h2>
+
+ <p>Modules must be named in all lower-case, preferably short,
+ single words. If a module name contains multiple words, they
+ may be separated by underscores or not separated at all.</p>
+
+ <p>Modules must have a copyright message, a docstring and a
+ reference to a test module that contains the bulk of its tests.
+ Use this template:</p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-comment"># -*- test-case-name: &lt;test module&gt; -*-</span>
+
+<span class="py-src-comment"># Copyright (c) 2008 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+
+<span class="py-src-string">&quot;&quot;&quot;
+Docstring goes here.
+&quot;&quot;&quot;</span>
+
+
+<span class="py-src-variable">__all__</span> = []
+</pre><div class="caption">Source listing - <a href="../listings/new_module_template.py"><span class="filename">../listings/new_module_template.py</span></a></div></div>
+
+ <p>In most cases, modules should contain more than one class,
+ function, or method; if a module contains only one object,
+ consider refactoring to include more related functionality in
+ that module.</p>
+
+ <p>Depending on the situation, it is acceptable to have imports that
+ look like this:
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Deferred</span>
+</pre>
+ or like this:
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+</pre>
+ That is, modules should import <em>modules</em> or <em>classes and
+ functions</em>, but not <em>packages</em>.</p>
+
+ <p>Wildcard import syntax may not be used by code in Twisted. These
+ imports lead to code which is difficult to read and maintain by
+ introducing complexity which strains human readers and automated tools
+ alike. If you find yourself with many imports to make from a single
+ module and wish to save typing, consider importing the module itself,
+ rather than its attributes.</p>
+
+ <p><em>Relative imports</em> (or <em>sibling imports</em>) may not be
+ used by code in Twisted. Relative imports allow certain circularities
+ to be introduced which can ultimately lead to unimportable modules or
+ duplicate instances of a single module. Relative imports also make the
+ task of refactoring more difficult.</p>
+
+ <p>In case of local names conflicts due to import, use the <code>as</code>
+ syntax, for example:
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">trial</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">util</span> <span class="py-src-keyword">as</span> <span class="py-src-variable">trial_util</span>
+</pre></p>
+
+ <p>The encoding must always be ASCII, so no coding cookie is necessary.</p>
+
+ <h2>Packages<a name="auto7"/></h2>
+
+ <p>Package names should follow the same conventions as module
+ names. All modules must be encapsulated in some package. Nested
+ packages may be used to further organize related modules.</p>
+
+ <p><code>__init__.py</code> must never contain anything other than a
+ docstring and (optionally) an <code>__all__</code> attribute. Packages are
+ not modules and should be treated differently. This rule may be
+ broken to preserve backwards compatibility if a module is made
+ into a nested package as part of a refactoring.</p>
+
+ <p>If you wish to promote code from a module to a package, for
+ example, to break a large module out into several smaller
+ files, the accepted way to do this is to promote from within
+ the module. For example,</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-comment"># parent/</span>
+<span class="py-src-comment"># --- __init__.py ---</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">child</span>
+
+<span class="py-src-comment"># --- child.py ---</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">parent</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Foo</span>:
+ <span class="py-src-keyword">pass</span>
+<span class="py-src-variable">parent</span>.<span class="py-src-variable">Foo</span> = <span class="py-src-variable">Foo</span>
+</pre>
+
+ <p>Every package should be added to the list in
+ <code class="shell">setup.py</code>.</p>
+
+ <p>Packages must not depend circularly upon each other. To simplify
+ maintaining this state, packages must also not import each other
+ circularly. While this applies to all packages within Twisted, one
+ <code>twisted.python</code> deserves particular attention, as it may
+ not depend on any other Twisted package.</p>
+
+ <h2>String Formatting Operations<a name="auto8"/></h2>
+
+ <p>When using <a href="http://docs.python.org/lib/typesseq-strings.html" shape="rect">string formatting
+ operations</a> like <code>formatString % values</code> you should always
+ use a tuple if you're using non-mapping <code>values</code>. This is to
+ avoid unexpected behavior when you think you're passing in a single value,
+ but the value is unexpectedly a tuple, e.g.:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">foo</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Hi %s\n&quot;</span> % <span class="py-src-variable">x</span>
+</pre>
+
+ <p>The example shows you can pass in <code>foo(&quot;foo&quot;)</code> or
+ <code>foo(3)</code> fine, but if you pass in <code>foo((1,2))</code>,
+ it raises a <code>TypeError</code>. You should use this instead:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">foo</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Hi %s\n&quot;</span> % (<span class="py-src-variable">x</span>,)
+</pre>
+
+ <h2>Docstrings<a name="auto9"/></h2>
+
+ <p>Docstrings should always be used to describe the
+ purpose of methods, functions, classes, and modules.</p>
+
+ <p>Docstrings are <em>never</em> to be used to provide semantic
+ information about an object; this rule may be violated if the
+ code in question is to be used in a system where this is a
+ requirement (such as Zope).</p>
+
+ <p>Docstrings should be indented to the level of the code they
+ are documenting.</p>
+
+ <p>Docstrings should be triple-quoted. The opening and the closing of the
+ docstrings should be on a line by themselves. For example:
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Ninja</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ A L{Ninja} is a warrior specializing in various unorthodox arts of war.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">attack</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">someone</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Attack C{someone} with this L{Ninja}'s shuriken.
+ &quot;&quot;&quot;</span>
+</pre>
+ </p>
+
+ <p>Docstrings should be written in epytext format; more
+ documentation is available in the
+ <a href="http://epydoc.sourceforge.net/epytext.html" shape="rect">Epytext Markup Language documentation</a>.</p>
+
+ <p>Additionally, to accommodate emacs users:</p>
+
+ <ul>
+ <li>Single quotes of the type of the docstring's triple-quote
+ should be escaped. This will prevent font-lock from
+ accidentally fontifying large portions of the file as a
+ string.</li>
+
+ <li>Code examples in docstrings should be prefixed by the |
+ character. This will prevent IM-Python from regarding sample
+ code as real functions, methods, and classes.</li>
+ </ul>
+
+ <p>For example,</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">foo2bar</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Convert L{foo}s to L{bar}s.
+
+ A function that should be used when you have a C{foo} but you want a
+ C{bar}; note that this is a non-destructive operation. If this method
+ can't convert the C{foo} to a C{bar} it will raise a L{FooException}.
+
+ @param f: C{foo}
+ @type f: str
+
+ For example::
+
+ | import wombat
+ | def sample(something):
+ | f = something.getFoo()
+ | f.doFooThing()
+ | b = wombat.foo2bar(f)
+ | b.doBarThing()
+ | return b
+
+ &quot;&quot;&quot;</span>
+ <span class="py-src-comment"># Optionally, actual code can go here.</span>
+</pre>
+
+ <h2>Comments<a name="auto10"/></h2>
+
+ <p>Comments marked with XXX or TODO must contain a reference to the
+ associated ticket.</p>
+
+ <h2>Versioning<a name="auto11"/></h2>
+
+ <p>The API documentation should be marked up with version information.
+ When a new API is added the class should be marked with the epytext
+ <code class="shell">@since:</code> field including the version number when
+ the change was introduced, eg. <code class="shell">@since: 8.1</code>.</p>
+
+ <h2>Scripts<a name="auto12"/></h2>
+
+ <p>For each <q>script</q>, that is, a program you expect a Twisted user
+ to run from the command-line, the following things must be done:</p>
+
+ <ol>
+ <li>Write a module in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.scripts.html" title="twisted.scripts">twisted.scripts</a></code>
+ which contains a callable global named <code>run</code>. This
+ will be called by the command line part with no arguments (it
+ will usually read <code>sys.argv</code>). Feel free to write more
+ functions or classes in this module, if you feel they are useful
+ to others.</li>
+
+ <li>Create a file which contains a shebang line for Python. For Twisted
+ Core, this file should be placed in the <code>bin/</code> directory; for
+ example, <code>bin/twistd</code>. For sub-projects, it should be placed
+ in <code>bin/&lt;subproject&gt;</code>; for example, the key-generation tool
+ for the Conch sub-project is in <code>bin/conch/ckeygen</code>.
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+</pre></li>
+
+ <p>To make sure that the script is portable across different UNIX like
+ operating systems we use the <code>/usr/bin/env</code> command. The env
+ command allows you to run a program in a modified environment. That way
+ you don't have to search for a program via the <code>PATH</code> environment
+ variable. This makes the script more portable but note that it is not a
+ foolproof method. Always make sure that <code>/usr/bin/env</code> exists or
+ use a softlink/symbolic link to point it to the correct path. Python's
+ distutils will rewrite the shebang line upon installation so this policy
+ only covers the source files in version control.</p>
+
+ <li>Add the Twisted running-from-SVN header:
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-comment">### Twisted Preamble</span>
+<span class="py-src-comment"># This makes sure that users don't have to set up their environment</span>
+<span class="py-src-comment"># specially in order to run these programs from bin/.</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>, <span class="py-src-variable">os</span>, <span class="py-src-variable">string</span>
+<span class="py-src-keyword">if</span> <span class="py-src-variable">string</span>.<span class="py-src-variable">find</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">abspath</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>]), <span class="py-src-variable">os</span>.<span class="py-src-variable">sep</span>+<span class="py-src-string">'Twisted'</span>) != -<span class="py-src-number">1</span>:
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">insert</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">normpath</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">abspath</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>]), <span class="py-src-variable">os</span>.<span class="py-src-variable">pardir</span>, <span class="py-src-variable">os</span>.<span class="py-src-variable">pardir</span>)))
+<span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">hasattr</span>(<span class="py-src-variable">os</span>, <span class="py-src-string">&quot;getuid&quot;</span>) <span class="py-src-keyword">or</span> <span class="py-src-variable">os</span>.<span class="py-src-variable">getuid</span>() != <span class="py-src-number">0</span>:
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">insert</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">os</span>.<span class="py-src-variable">getcwd</span>())
+<span class="py-src-comment">### end of preamble</span>
+</pre></li>
+
+ <li>And end with:
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">scripts</span>.<span class="py-src-variable">yourmodule</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">run</span>
+<span class="py-src-variable">run</span>()
+</pre></li>
+
+ <li>Write a manpage and add it to the <code class="shell">man</code> folder
+ of a subproject's <code class="shell">doc</code> folder. On Debian systems
+ you can find a skeleton example of a manpage in
+ <code>/usr/share/doc/man-db/examples/manpage.example</code>.</li>
+ </ol>
+
+ <p>This will insure your program will work correctly for users of SVN,
+ Windows releases and Debian packages.</p>
+
+ <h2>Examples<a name="auto13"/></h2>
+
+ <p>For example scripts you expect a Twisted user
+ to run from the command-line, add this Python shebang line at the top
+ of the file:</p>
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+</pre>
+
+ <h2>Standard Library Extension Modules<a name="auto14"/></h2>
+
+ <p>When using the extension version of a module for which there is also
+ a Python version, place the import statement inside a try/except block,
+ and import the Python version if the import fails. This allows code to
+ work on platforms where the extension version is not available. For
+ example:
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">try</span>:
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">cPickle</span> <span class="py-src-keyword">as</span> <span class="py-src-variable">pickle</span>
+<span class="py-src-keyword">except</span> <span class="py-src-variable">ImportError</span>:
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">pickle</span>
+</pre>
+
+ Use the &quot;as&quot; syntax of the import statement as well, to set
+ the name of the extension module to the name of the Python module.</p>
+
+ <p>Some modules don't exist across all supported Python versions. For
+ example, Python 2.3's <code>sets</code> module was deprecated in Python 2.6
+ in favor of the <code>set</code> and <code>frozenset</code> builtins. When
+ you need to use sets or frozensets in your code, please use
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.compat.set.html" title="twisted.python.compat.set">twisted.python.compat.set</a></code> and
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.compat.frozenset.html" title="twisted.python.compat.frozenset">twisted.python.compat.frozenset</a></code>. There are some
+ differences between <code>sets.Set</code> and <code>set</code>, that are
+ explained in the
+ <a href="http://www.python.org/dev/peps/pep-0218/" shape="rect">set PEP</a>. Please be
+ sure to not rely on the behavior of one or the other implementation.
+ </p>
+
+ <h2>Classes<a name="auto15"/></h2>
+
+ <p>Classes are to be named in mixed case, with the first letter
+ capitalized; each word separated by having its first letter
+ capitalized. Acronyms should be capitalized in their entirety.
+ Class names should not be prefixed with the name of the module they are
+ in. Examples of classes meeting this criteria:</p>
+
+ <ul>
+ <li>twisted.spread.pb.ViewPoint</li>
+ <li>twisted.parser.patterns.Pattern</li>
+ </ul>
+
+ <p>Examples of classes <strong>not</strong> meeting this criteria:</p>
+
+ <ul>
+ <li>event.EventHandler</li>
+ <li>main.MainGadget</li>
+ </ul>
+
+ <p>An effort should be made to prevent class names from clashing
+ with each other between modules, to reduce the need for
+ qualification when importing. For example, a Service subclass
+ for Forums might be named twisted.forum.service.ForumService,
+ and a Service subclass for Words might be
+ twisted.words.service.WordsService. Since neither of these
+ modules are volatile <em>(see above)</em> the classes may be
+ imported directly into the user's namespace and not cause
+ confusion.</p>
+
+ <h3>New-style Classes<a name="auto16"/></h3>
+
+ <p>Classes and instances in Python come in two flavors: old-style or
+ classic, and new-style. Up to Python 2.1, old-style classes were the
+ only flavour available to the user, new-style classes were introduced
+ in Python 2.2 to unify classes and types. All classes added to Twisted
+ should be written as new-style classes. If <code class="python">x</code>
+ is an instance of a new-style class, then <code class="python">type(x)</code>
+ is the same as <code class="python">x.__class__</code>.</p>
+
+ <h2>Methods<a name="auto17"/></h2>
+
+ <p>Methods should be in mixed case, with the first letter lower
+ case, each word separated by having its first letter
+ capitalized. For example, <code>someMethodName</code>,
+ <code>method</code>.</p>
+
+ <p>Sometimes, a class will dispatch to a specialized sort of
+ method using its name; for example, twisted.reflect.Accessor.
+ In those cases, the type of method should be a prefix in all
+ lower-case with a trailing underscore, so method names will
+ have an underscore in them. For example, <code>get_someAttribute</code>.
+ Underscores in method names in twisted code are therefore
+ expected to have some semantic associated with them.</p>
+
+ <p>Some methods, in particular <code>addCallback</code> and its
+ cousins return self to allow for chaining calls. In this case,
+ wrap the chain in parenthesis, and start each chained call on
+ a separate line, for example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">return</span> (<span class="py-src-variable">foo</span>()
+ .<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">bar</span>)
+ .<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">thud</span>)
+ .<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">wozers</span>))
+</pre>
+
+ <h2>Callback Arguments<a name="auto18"/></h2>
+
+ <p>There are several methods whose purpose is to help the user set up
+ callback functions, for example <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.addCallback.html" title="twisted.internet.defer.Deferred.addCallback">Deferred.addCallback</a></code> or the
+ reactor's <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.base.ReactorBase.callLater.html" title="twisted.internet.base.ReactorBase.callLater">callLater</a></code> method. To make
+ access to the callback as transparent as possible, most of these methods
+ use <code class="python">**kwargs</code> to capture arbitrary arguments
+ that are destined for the user's callback. This allows the call to the
+ setup function to look very much like the eventual call to the target
+ callback function.</p>
+
+ <p>In these methods, take care to not have other argument names that will
+ <q>steal</q> the user's callback's arguments. When sensible, prefix these
+ <q>internal</q> argument names with an underscore. For example, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.callRemote.html" title="twisted.spread.pb.RemoteReference.callRemote">RemoteReference.callRemote</a></code> is
+ meant to be called like this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-variable">myref</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;addUser&quot;</span>, <span class="py-src-string">&quot;bob&quot;</span>, <span class="py-src-string">&quot;555-1212&quot;</span>)
+
+<span class="py-src-comment"># on the remote end, the following method is invoked:</span>
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">addUser</span>(<span class="py-src-parameter">name</span>, <span class="py-src-parameter">phone</span>):
+ ...
+</pre>
+
+ <p>where <q>addUser</q> is the remote method name. The user might also
+ choose to call it with named parameters like this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">myref</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;addUser&quot;</span>, <span class="py-src-variable">name</span>=<span class="py-src-string">&quot;bob&quot;</span>, <span class="py-src-variable">phone</span>=<span class="py-src-string">&quot;555-1212&quot;</span>)
+</pre>
+
+ <p>In this case, <code>callRemote</code> (and any code that uses the
+ **kwargs syntax) must be careful to not use <q>name</q>, <q>phone</q>, or
+ any other name that might overlap with a user-provided named parameter.
+ Therefore, <code>callRemote</code> is implemented with the following
+ signature:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">callRemote</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">_name</span>, *<span class="py-src-parameter">args</span>, **<span class="py-src-parameter">kw</span>):
+ ...
+</pre>
+
+ <p>Do whatever you can to reduce user confusion. It may also be
+ appropriate to <code class="python">assert</code> that the kwargs
+ dictionary does not contain parameters with names that will eventually
+ cause problems.</p>
+
+
+ <h2>Special Methods<a name="auto19"/></h2>
+
+ <p>The augmented assignment protocol, defined by __iadd__ and other
+ similarly named methods, can be used to allow objects to be modified in
+ place or to rebind names if an object is immutable -- both through use
+ of the same operator. This can lead to confusing code, which in turn
+ leads to buggy code. For this reason, methods of the augmented
+ assignment protocol should not be used in Twisted.</p>
+
+ <h2>Functions<a name="auto20"/></h2>
+
+ <p>Functions should be named similiarly to methods.</p>
+
+ <p>Functions or methods which are responding to events to
+ complete a callback or errback should be named <code>_cbMethodName</code> or
+ <code>_ebMethodName</code>, in order to distinguish them from normal
+ methods.</p>
+
+ <h2>Attributes<a name="auto21"/></h2>
+
+ <p>Attributes should be named similarly to functions and
+ methods. Attributes should be named descriptively; attribute
+ names like <code>mode</code>, <code>type</code>, and
+ <code>buf</code> are generally discouraged. Instead, use
+ <code>displayMode</code>, <code>playerType</code>, or
+ <code>inputBuffer</code>.</p>
+
+ <p>Do not use Python's <q>private</q> attribute syntax; prefix
+ non-public attributes with a single leading underscore. Since
+ several classes have the same name in Twisted, and they are
+ distinguished by which package they come from, Python's
+ double-underscore name mangling will not work reliably in some
+ cases. Also, name-mangled private variables are more difficult
+ to address when unit testing or persisting a class.</p>
+
+ <p>An attribute (or function, method or class) should be
+ considered private when one or more of the following conditions
+ are true:</p>
+
+ <ul>
+ <li>The attribute represents intermediate state which is not
+ always kept up-to-date.</li>
+
+ <li>Referring to the contents of the attribute or otherwise
+ maintaining a reference to it may cause resources to
+ leak.</li>
+
+ <li>Assigning to the attribute will break internal
+ assumptions.</li>
+
+ <li>The attribute is part of a known-to-be-sub-optimal
+ interface and will certainly be removed in a future
+ release.</li>
+ </ul>
+
+
+ <h2>Database<a name="auto22"/></h2>
+
+ <p>Database tables will be named with plural nouns.</p>
+
+ <p>Database columns will be named with underscores between
+ words, all lower case, since most databases do not distinguish
+ between case.</p>
+
+ <p>Any attribute, method argument, or method name that
+ corresponds <em>directly</em> to a column in the database will
+ be named exactly the same as that column, regardless of other
+ coding conventions surrounding that circumstance.</p>
+
+ <p>All SQL keywords should be in upper case.</p>
+
+ <h2>C Code<a name="auto23"/></h2>
+
+ <p>Wherever possible, C code should be optional, and the
+ default python implementation should be maintained in tandem
+ with it. C code should be strict ANSI C, and
+ <strong>must</strong> build using GCC as well as Visual Studio
+ for Windows, and really shouldn't have any problems with other
+ compilers either. Don't do anything tricky.</p>
+
+ <p>C code should only be used for efficiency, not for binding
+ to external libraries. If your particular code is not
+ frequently run, write it in Python. If you require the use of
+ an external library, develop a separate, external bindings
+ package and make your twisted code depend on it.</p>
+
+ <h2 id="commits">Commit Messages<a name="auto24"/></h2>
+
+ <p>The commit messages are being distributed in a myriad of ways. Because
+ of that, you need to observe a few simple rules when writing a commit
+ message.</p>
+
+ <p>The first line of the message is being used as both the subject of
+ the commit email and the announcement on #twisted. Therefore, it should
+ be short (aim for &lt; 80 characters) and descriptive -- and must be
+ able to stand alone (it is best if it is a complete sentence). The rest
+ of the e-mail should be separated with <em>hard line breaks</em> into
+ short lines (&lt; 70 characters). This is free-format, so you can do
+ whatever you like here.</p>
+
+ <p>Commit messages should be about <em>what</em>, not <em>how</em>: we can
+ get how from SVN diff. Explain reasons for commits, and what they
+ affect.</p>
+
+ <p>Each commit should be a single logical change, which is internally
+ consistent. If you can't summarize your changes in one short line, this
+ is probably a sign that they should be broken into multiple checkins.</p>
+
+ <h2>Source Control<a name="auto25"/></h2>
+
+ <p>Twisted currently uses Subversion for source control. All
+ development <strong>should</strong> occur using branches; when a task is
+ considered complete another Twisted developer may review it and if no
+ problems are found, it may be merged into trunk. The Twisted wiki has <a href="http://twistedmatrix.com/trac/wiki/TwistedDevelopment" shape="rect">a start</a>.
+ Branches <strong>must</strong> be used for major development. Branches
+ should be managed using <a href="http://divmod.org/trac/wiki/DivmodCombinator" shape="rect">Combinator</a> (but
+ if you can manage them in some other way without anyone noticing, knock
+ yourself out).</p>
+
+ <p>Certain features of Subversion should be avoided.</p>
+
+ <ul>
+ <li>
+
+ <p>Do not set the <code class="shell">svn:ignore</code> property on any
+ file or directory. What you wish to ignore, others may wish to examine.
+ What others may wish you ignore, <em>you</em> may wish you examine.
+ <code class="shell"> svn:ignore </code> will affect everyone who uses
+ the repository, and so it is not the right mechanism to express personal
+ preferences.</p>
+
+ <p>If you wish to ignore certain files use the <code class="shell">
+ global-ignores </code> feature of <code class="shell">
+ ~/.subversion/config </code>, for example:</p>
+
+ <pre class="shell" xml:space="preserve">
+[miscellany]
+global-ignores = dropin.cache *.pyc *.pyo *.o *.lo *.la #*# .*.rej *.rej .*~
+ </pre>
+
+ </li>
+ </ul>
+
+ <h2>Fallback<a name="auto26"/></h2>
+
+ <p>In case of conventions not enforced in this document, the reference
+ documents to use in fallback is
+ <a href="http://www.python.org/dev/peps/pep-0008/" shape="rect">PEP 8</a> for Python
+ code and <a href="http://www.python.org/dev/peps/pep-0007/" shape="rect">PEP 7</a> for
+ C code. For example, the paragraph <strong>Whitespace in Expressions and
+ Statements</strong> in PEP 8 describes what should be done in Twisted
+ code.</p>
+
+ <h2>Recommendations<a name="auto27"/></h2>
+
+ <p>These things aren't necessarily standardizeable (in that
+ code can't be easily checked for compliance) but are a good
+ idea to keep in mind while working on Twisted.</p>
+
+ <p>If you're going to work on a fragment of the Twisted
+ codebase, please consider finding a way that you would <em>use</em>
+ such a fragment in daily life. Using a Twisted Web server on your
+ website encourages you to actively maintain and improve your code,
+ as the little everyday issues with using it become apparent.</p>
+
+ <p>Twisted is a <strong>big</strong> codebase! If you're
+ refactoring something, please make sure to recursively grep for
+ the names of functions you're changing. You may be surprised to
+ learn where something is called. Especially if you are moving
+ or renaming a function, class, method, or module, make sure
+ that it won't instantly break other code.</p>
+
+ </div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/development/policy/doc-standard.html b/vendor/Twisted-10.0.0/doc/core/development/policy/doc-standard.html
new file mode 100644
index 0000000000..f766290362
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/policy/doc-standard.html
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: HTML Documentation Standard for Twisted</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">HTML Documentation Standard for Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Allowable Tags</a></li><li><a href="#auto1">Multi-line Code Snippets</a></li><ul><li><a href="#auto2">python</a></li><li><a href="#auto3">python-interpreter</a></li><li><a href="#auto4">shell</a></li></ul><li><a href="#auto5">Code inside paragraph text</a></li><li><a href="#auto6">Headers</a></li><li><a href="#auto7">XHTML</a></li><li><a href="#auto8">Tag Case</a></li><li><a href="#auto9">Footnotes</a></li><li><a href="#auto10">Suggestions</a></li><li><a href="#auto11">__all__</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Allowable Tags<a name="auto0"/></h2>
+
+ <p>Please try to restrict your HTML usage to the following tags (all only for the original logical purpose, and not whatever visual effect you see): <code>&lt;html&gt;</code>, <code>&lt;title&gt;</code>, <code>&lt;head&gt;</code>, <code>&lt;body&gt;</code>, <code>&lt;h1&gt;</code>, <code>&lt;h2</code>, <code>&lt;h3&gt;</code>, <code>&lt;ol&gt;</code>, <code>&lt;ul&gt;</code>, <code>&lt;dl&gt;</code>, <code>&lt;li&gt;</code>, <code>&lt;dt&gt;</code>, <code>&lt;dd&gt;</code>, <code>&lt;p&gt;</code>, <code>&lt;code&gt;</code>, <code>&lt;img&gt;</code>, <code>&lt;blockquote&gt;</code>, <code>&lt;a&gt;</code>, <code>&lt;cite&gt;</code>, <code>&lt;div&gt;</code>, <code>&lt;span&gt;</code>, <code>&lt;strong&gt;</code>, <code>&lt;em&gt;</code>, <code>&lt;pre&gt;</code>, <code>&lt;q&gt;</code>, <code>&lt;table&gt;</code>,<code>&lt;tr&gt;</code>, <code>&lt;td&gt;</code> and <code>&lt;th&gt;</code>.</p>
+
+ <p>Please avoid using the quote sign (<code>&quot;</code>) for quoting, and use the relevant html tags (<code>&lt;q&gt;&lt;/q&gt;</code>) -- it is impossible to distinguish right and left quotes with the quote sign, and some more sophisticated output methods work better with that distinction.</p>
+
+ <h2>Multi-line Code Snippets<a name="auto1"/></h2>
+
+ <p>Multi-line code snippets should be delimited with a
+ &lt;pre&gt; tag, with a mandatory <q>class</q> attribute. The
+ conventionalized classes are <q>python</q>, <q>python-interpreter</q>,
+ and <q>shell</q>. For example:</p>
+
+ <h3><q>python</q><a name="auto2"/></h3>
+<pre xml:space="preserve">
+ &lt;p&gt;
+ For example, this is how one defines a Resource:
+ &lt;/p&gt;
+
+ &lt;pre class=&quot;python&quot;&gt;
+from twisted.web import resource
+
+class MyResource(resource.Resource):
+ def render_GET(self, request):
+ return &quot;Hello, world!&quot;
+ &lt;/pre&gt;
+</pre>
+
+ <p>For example, this is how one defines a Resource:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyResource</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Hello, world!&quot;</span>
+</pre>
+
+ <p>Note that you should never have leading indentation inside a
+ &lt;pre&gt; block -- this makes it hard for readers to
+ copy/paste the code.</p>
+
+ <h3><q>python-interpreter</q><a name="auto3"/></h3>
+<pre xml:space="preserve">
+ &lt;pre class=&quot;python-interpreter&quot;&gt;
+ &amp;gt;&amp;gt;&amp;gt; from twisted.web import resource
+ &amp;gt;&amp;gt;&amp;gt; class MyResource(resource.Resource):
+ ... def render_GET(self, request):
+ ... return &quot;Hello, world!&quot;
+ ...
+ &amp;gt;&amp;gt;&amp;gt; MyResource().render_GET(None)
+ &quot;Hello, world!&quot;
+ &lt;/pre&gt;
+</pre>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; from twisted.web import resource
+&gt;&gt;&gt; class MyResource(resource.Resource):
+... def render_GET(self, request):
+... return &quot;Hello, world!&quot;
+...
+&gt;&gt;&gt; MyResource().render_GET(None)
+&quot;Hello, world!&quot;
+</pre>
+
+ <h3><q>shell</q><a name="auto4"/></h3>
+<pre xml:space="preserve">
+ &lt;pre class=&quot;shell&quot;&gt;
+ $ twistd web --path /var/www
+ &lt;/pre&gt;
+</pre>
+
+<pre class="shell" xml:space="preserve">
+$ twistd web --path /var/www
+</pre>
+
+ <h2>Code inside paragraph text<a name="auto5"/></h2>
+
+ <p>For single-line code-snippets and attribute, method, class,
+ and module names, use the &lt;code&gt; tag, with a class of
+ <q>API</q> or <q>python</q>. During processing, module or class-names
+ with class <q>API</q> will automatically be looked up in the API
+ reference and have a link placed around it referencing the
+ actual API documents for that module/classname. If you wish to
+ reference an API document, then make sure you at least have a
+ single module-name so that the processing code will be able to
+ figure out which module or class you're referring to.</p>
+
+ <p>You may also use the <code>base</code> attribute in conjuction
+ with a class of <q>API</q> to indicate the module that should be prepended
+ to the module or classname. This is to help keep the documentation
+ clearer and less cluttered by allowing links to API docs that don't
+ need the module name.</p>
+<pre xml:space="preserve">
+ &lt;p&gt;
+ To add a &lt;code class=&quot;API&quot;&gt;twisted.web.widgets.Widget&lt;/code&gt;
+ instance to a &lt;code class=&quot;API&quot;
+ base=&quot;twisted.web.widgets&quot;&gt;Gadget&lt;/code&gt; instance, do
+ &lt;code class=&quot;python&quot;&gt;myGadget.putWidget(&quot;widgetPath&quot;,
+ MyWidget())&lt;/code&gt;.
+ &lt;/p&gt;
+
+ &lt;p&gt;
+ (implementation note: the widgets are stored in the &lt;code
+ class=&quot;python&quot;&gt;gadgetInstance.widgets&lt;/code&gt; attribute,
+ which is a
+ list.)
+ &lt;/p&gt;
+
+</pre>
+
+<div class="boxed">
+ <p>
+ To add a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.widgets.Widget.html" title="twisted.web.widgets.Widget">twisted.web.widgets.Widget</a></code>
+ instance to a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.widgets.Gadget.html" title="twisted.web.widgets.Gadget">Gadget</a></code>
+ instance, do
+ <code class="python">myGadget.putWidget(&quot;widgetPath&quot;, MyWidget())</code>.
+ </p>
+
+ <p>
+ (implementation note: the widgets are stored in the <code class="python">gadgetInstance.widgets</code> attribute,
+ which is a
+ list.)
+ </p>
+
+</div>
+
+ <h2>Headers<a name="auto6"/></h2>
+
+ <p>It goes without mentioning that you should use &lt;hN&gt; in
+ a sane way -- &lt;h1&gt; should only appear once in the
+ document, to specify the title. Sections of the document should
+ use &lt;h2&gt;, sub-headers &lt;h3&gt;, and so on.</p>
+
+ <h2>XHTML<a name="auto7"/></h2>
+
+ <p>XHTML is mandatory. That means tags that don't have a
+ closing tag need a <q>/</q>; for example, <code>&lt;hr /&gt;</code>
+ . Also, tags which have <q>optional</q> closing tags in HTML
+ <em>need</em> to be closed in XHTML; for example,
+ <code>&lt;li&gt;foo&lt;/li&gt;</code></p>
+
+ <h2>Tag Case<a name="auto8"/></h2>
+
+ <p>All tags will be done in lower-case. XHTML demands this, and
+ so do I. :-)</p>
+
+ <h2>Footnotes<a name="auto9"/></h2>
+
+ <p>Footnotes are enclosed inside
+ <code>&lt;span class=&quot;footnote&quot;&gt;&lt;/span&gt;</code>. They must not
+ contain any markup.</p>
+
+ <h2>Suggestions<a name="auto10"/></h2>
+
+ <p>Use <code class="shell">lore -o lint</code> to check your documentation
+ is not broken. <code class="shell">lore -o lint</code> will never change
+ your HTML, but it will complain if it doesn't like it.</p>
+
+ <p>Don't use tables for formatting. 'nuff said.</p>
+
+ <h2>__all__<a name="auto11"/></h2>
+
+ <p><code class="python">__all__</code> is a module level list of strings, naming
+ objects in the module that are public. Make sure publically exported classes,
+ functions and constants are listed here.</p>
+
+ </div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/development/policy/index.html b/vendor/Twisted-10.0.0/doc/core/development/policy/index.html
new file mode 100644
index 0000000000..92bef0407a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/policy/index.html
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Development Policy</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Development Policy</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<p>
+This series of documents is designed for people who wish to contribute to the
+Twisted codebase.
+</p>
+
+ <ul>
+ <li><a href="coding-standard.html" shape="rect">Coding standard</a></li>
+ <li><a href="doc-standard.html" shape="rect">Documentation standard</a></li>
+ <li><a href="writing-standard.html" shape="rect">Documentation writing standard</a></li>
+ <li><a href="test-standard.html" shape="rect">Testing standard</a></li>
+ <li><a href="svn-dev.html" shape="rect">Working from Twisted's Subversion
+ repository</a></li>
+ </ul>
+
+</div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/development/policy/svn-dev.html b/vendor/Twisted-10.0.0/doc/core/development/policy/svn-dev.html
new file mode 100644
index 0000000000..aa6a3c3cc6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/policy/svn-dev.html
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Working from Twisted's Subversion repository</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Working from Twisted's Subversion repository</h1>
+ <div class="toc"><ol><li><a href="#auto0">Checkout</a></li><li><a href="#auto1">Alternate tree names</a></li><li><a href="#auto2">Combinator</a></li><li><a href="#auto3">Compiling C extensions</a></li><li><a href="#auto4">Running tests</a></li><li><a href="#auto5">Building docs</a></li><li><a href="#auto6">Committing and Post-commit Hooks</a></li><li><a href="#auto7">Emacs</a></li><li><a href="#auto8">Building Debian packages</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p>If you're going to be doing development on Twisted itself, or if you want
+to take advantage of bleeding-edge features (or bug fixes) that are not yet
+available in a numbered release, you'll probably want to check out a tree from
+the Twisted Subversion repository. The Trunk is where all current development
+takes place.</p>
+
+<p>This document lists some useful tips for working on this cutting
+edge.</p>
+
+<h2>Checkout<a name="auto0"/></h2>
+
+<p>Subversion tutorials can be found elsewhere, see in particular <a href="http://subversion.tigris.org/" shape="rect">the Subversion homepage</a>. The relevant
+data you need to check out a copy of the Twisted tree is available on the <a href="http://twistedmatrix.com/trac/wiki/TwistedDevelopment" shape="rect">development page
+</a>, and is as follows:</p>
+
+<pre class="shell" xml:space="preserve">
+$ svn co svn://svn.twistedmatrix.com/svn/Twisted/trunk Twisted
+</pre>
+
+<h2>Alternate tree names<a name="auto1"/></h2>
+
+<p>By using <code>svn co svn://svn.twistedmatrix.com/svn/Twisted/trunk
+otherdir</code>, you can put the workspace tree in a directory other than
+<q>Twisted</q>. I do this (with a name like <q>Twisted-Subversion</q>) to
+remind myself that this tree comes from Subversion and not from a released
+version (like <q>Twisted-1.0.5</q>). This practice can cause a few problems,
+because there are a few places in the Twisted tree that need to know where
+the tree starts, so they can add it to <code>sys.path</code> without
+requiring the user manually set their PYTHONPATH. These functions walk the
+current directory up to the root, looking for a directory named
+<q>Twisted</q> (sometimes exactly that, sometimes with a
+<code>.startswith</code> test). Generally these are test scripts or other
+administrative tools which expect to be launched from somewhere inside the
+tree (but not necessarily from the top).</p>
+
+<p>If you rename the tree to something other than <code>Twisted</code>, these
+tools may wind up trying to use Twisted source files from /usr/lib/python2.5
+or elsewhere on the default <code>sys.path</code>. Normally this won't
+matter, but it is good to be aware of the issue in case you run into
+problems.</p>
+
+<p><code>twisted/test/process_twisted.py</code> is one of these programs.</p>
+
+<h2>Combinator<a name="auto2"/></h2>
+
+<p>In order to simplify the use of Subversion, we typically use
+<a href="http://divmod.org/trac/wiki/DivmodCombinator" shape="rect">Divmod Combinator</a>.
+You may find it to be useful, too. In particular, because Twisted uses
+branches for almost all feature development, if you plan to contribute to
+Twisted you will probably find Combinator very useful. For more details,
+see the Combinator website, as well as the
+<a href="http://divmod.org/trac/wiki/UltimateQualityDevelopmentSystem" shape="rect">
+UQDS</a> page.</p>
+
+<h2>Compiling C extensions<a name="auto3"/></h2>
+
+<p>
+There are currently several C extension modules in Twisted:
+twisted.protocols._c_urlarg, twisted.internet.cfsupport,
+twisted.internet.iocpreactor._iocp, and twisted.python._epoll. These modules
+are optional, but you'll have to compile them if you want to experience their
+features, performance improvements, or bugs. There are two approaches.
+</p>
+
+<p>The first is to do a regular distutils <code>./setup.py build</code>, which
+will create a directory under <code>build/</code> to hold both the generated
+<code>.so</code> files as well as a copy of the 600-odd <code>.py</code> files
+that make up Twisted. If you do this, you will need to set your PYTHONPATH to
+something like <code>MyDir/Twisted/build/lib.linux-i686-2.5</code> in order to
+run code against the Subversion twisted (as opposed to whatever's installed in
+<code>/usr/lib/python2.5</code> or wherever python usually looks). In
+addition, you will need to re-run the <code>build</code> command <em>every
+time</em> you change a <code>.py</code> file. The <code>build/lib.foo</code>
+directory is a copy of the main tree, and that copy is only updated when you
+re-run <code>setup.py build</code>. It is easy to forget this and then wonder
+why your code changes aren't being expressed.</p>
+
+<p>The second technique is to build the C modules in place, and point your
+PYTHONPATH at the top of the tree, like <code>MyDir/Twisted</code>. This way
+you're using the .py files in place too, removing the confusion a forgotten
+rebuild could cause with the separate build/ directory above. To build the C
+modules in place, do <code>./setup.py build_ext -i</code>. You only need to
+re-run this command when you change the C files. Note that
+<code>setup.py</code> is not Make, it does not always get the dependencies
+right (<code>.h</code> files in particular), so if you are hacking on the
+cReactor you may need to manually delete the <code>.o</code> files before
+doing a rebuild. Also note that doing a <code>setup.py clean</code> will
+remove the <code>.o</code> files but not the final <code>.so</code> files,
+they must be deleted by hand.</p>
+
+
+<h2>Running tests<a name="auto4"/></h2>
+
+<p>To run the full unit-test suite, do:</p>
+
+<pre class="shell" xml:space="preserve">./bin/trial twisted</pre>
+
+<p>To run a single test file (like <code>twisted/test/test_defer.py</code>),
+do one of:</p>
+
+<pre class="shell" xml:space="preserve">./bin/trial twisted.test.test_defer</pre>
+
+<p>or</p>
+
+<pre class="shell" xml:space="preserve">./bin/trial twisted/test/test_defer.py</pre>
+
+<p>To run any tests that are related to a code file, like
+<code>twisted/protocols/imap4.py</code>, do:</p>
+
+<pre class="shell" xml:space="preserve">./bin/trial --testmodule twisted/mail/imap4.py</pre>
+
+<p>This depends upon the <code>.py</code> file having an appropriate
+<q>test-case-name</q> tag that indicates which test cases provide coverage.
+See the <a href="test-standard.html" shape="rect">Test Standards</a> document for
+details about using <q>test-case-name</q>. In this example, the
+<code>twisted.mail.test.test_imap</code> test will be run.</p>
+
+<p>Many tests create temporary files in /tmp or ./_trial_temp, but
+everything in /tmp should be deleted when the test finishes. Sometimes these
+cleanup calls are commented out by mistake, so if you see a stray
+/tmp/@12345.1 directory, it is probably from test_dirdbm or test_popsicle.
+Look for an <code>rmtree</code> that has been commented out and complain to
+the last developer who touched that file.</p>
+
+<h2>Building docs<a name="auto5"/></h2>
+
+<p>Twisted documentation (not including the automatically-generated API docs)
+is in <a href="http://twistedmatrix.com/trac/wiki/TwistedLore" shape="rect">Lore Format</a>.
+These <code>.xhtml</code> files are translated into <code>.html</code> files by
+the <q>bin/lore/lore</q> script, which can check the files for syntax problems
+(hlint), process multiple files at once, insert the files into a template
+before processing, and can also translate the files into LaTeX or PostScript
+instead.</p>
+
+<p>To build the HTML form of the howto/ docs, do the following. Note that
+the index file will be placed in <code>doc/howto/index.html</code>.</p>
+
+<pre class="shell" xml:space="preserve">
+./bin/lore/lore -p --config template=doc/howto/template.tpl doc/howto/*.xhtml
+</pre>
+
+<p>To run hlint over a single Lore document, such as
+<code>doc/development/policy/svn-dev.xhtml</code>, do the following. This is
+useful because the HTML conversion may bail without a useful explanation if
+it sees mismatched tags.</p>
+
+<pre class="shell" xml:space="preserve">
+./bin/lore/lore -n --output lint doc/development/policy/svn-dev.xhtml
+</pre>
+
+<p>To convert it to HTML (including markup, interpolation of examples,
+footnote processing, etc), do the following. The results will be placed in
+<code>doc/development/policy/svn-dev.html</code>:</p>
+
+<pre class="shell" xml:space="preserve">
+./bin/lore/lore -p --config template=doc/howto/template.tpl \
+ doc/development/policy/svn-dev.xhtml
+</pre>
+
+<p>Note that hyperlinks to other documents may not be quite right unless you
+include a <q>-l</q> argument to <code>bin/lore/lore</code>. Links in the
+.xhtml file are to .xhtml targets: when the .xhtml is turned into .html, the
+link targets are supposed to be turned into .html also. In addition to this,
+Lore markup of the form &lt;code class=&quot;API&quot;&gt; is supposed to
+turn into a link to the corresponding API reference page. These links will
+probably be wrong unless the correct base URL is provided to Lore.</p>
+
+<h2>Committing and Post-commit Hooks<a name="auto6"/></h2>
+
+<p>Twisted uses a customized
+<a href="http://bazaar.launchpad.net/~exarkun/twisted-trac-integration/trunk/annotate/head%3A/trac-hooks/trac-post-commit-hook" shape="rect">
+trac-post-commit-hook</a> to enable ticket updates based on svn commit
+logs. When making a branch for a ticket, the branch name should end
+in <code>-&lt;ticket number&gt;</code>, for
+example <code>my-branch-9999</code>. This will add a ticket comment containing a
+changeset link and branch name. To make your commit message show up as a comment
+on a Trac ticket, add a <code>refs #&lt;ticket number&gt;</code> line at the
+bottom of your commit message. To automatically close a ticket on Trac
+as <code>Fixed</code> and add a comment with the closing commit message, add
+a <code>Fixes: #&lt;ticket number&gt;</code> line to your commit message. In
+general, a commit message closing a ticket looks like this:</p>
+
+<pre xml:space="preserve">
+Merge my-branch-9999: A single-line summary.
+
+Author: jesstess
+Reviewers: exarkun, glyph
+Fixes: #9999
+
+My longer description of the changes made.
+</pre>
+
+<p>The <a href="coding-standard.html#commits" shape="rect">Twisted Coding Standard</a>
+elaborates on commit messages and source control.</p>
+
+<h2>Emacs<a name="auto7"/></h2>
+
+<p>A minor mode for development with Twisted using Emacs is available. See
+<code>emacs/twisted-dev.el</code> for several utility functions which make
+it easier to grep for methods, run test cases, etc.</p>
+
+<h2>Building Debian packages<a name="auto8"/></h2>
+
+<p>Our support for building Debian packages has fallen into disrepair. We
+would very much like to restore this functionality, but until we do so, if
+you are interested in this, you are on your own. See
+<a href="http://stdeb.python-hosting.com/" shape="rect">stdeb</a> for one possible approach
+to this.</p>
+
+</div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/development/policy/test-standard.html b/vendor/Twisted-10.0.0/doc/core/development/policy/test-standard.html
new file mode 100644
index 0000000000..546639dc52
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/policy/test-standard.html
@@ -0,0 +1,362 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Unit Tests in Twisted</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Unit Tests in Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Unit Tests in the Twisted Philosophy</a></li><li><a href="#auto1">What to Test, What Not to Test</a></li><li><a href="#auto2">Running the Tests</a></li><ul><li><a href="#auto3">How</a></li><li><a href="#auto4">When</a></li></ul><li><a href="#auto5">Adding a Test</a></li><li><a href="#auto6">Skipping tests, TODO items</a></li><ul><li><a href="#auto7">.todo and Testing New Functionality </a></li><li><a href="#auto8">Line Coverage Information</a></li></ul><li><a href="#auto9">Associating Test Cases With Source Files</a></li><li><a href="#auto10">Links</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <p>Each <em>unit test</em> tests one bit of functionality in the
+ software. Unit tests are entirely automated and complete quickly.
+ Unit tests for the entire system are gathered into one test suite,
+ and may all be run in a single batch. The result of a unit test
+ is simple: either it passes, or it doesn't. All this means you
+ can test the entire system at any time without inconvenience, and
+ quickly see what passes and what fails.</p>
+
+ <h2>Unit Tests in the Twisted Philosophy<a name="auto0"/></h2>
+
+ <p>The Twisted development team
+ adheres to the practice of <a href="http://c2.com/cgi/wiki?ExtremeProgramming" shape="rect">Extreme
+ Programming</a> (XP), and the usage of unit tests is a cornerstone
+ XP practice. Unit tests are a tool to give you increased
+ confidence. You changed an algorithm -- did you break something?
+ Run the unit tests. If a test fails, you know where to look,
+ because each test covers only a small amount of code, and you know
+ it has something to do with the changes you just made. If all the
+ tests pass, you're good to go, and you don't need to second-guess
+ yourself or worry that you just accidently broke someone else's
+ program.</p>
+
+ <h2>What to Test, What Not to Test<a name="auto1"/></h2>
+
+ <blockquote><p>You don't have to write a test for every single
+ method you write, only production methods that could possibly break.</p>
+ </blockquote>
+
+ <p>-- Kent Beck, <cite>Extreme Programming Explained</cite>, p. 58.</p>
+
+ <h2>Running the Tests<a name="auto2"/></h2>
+
+ <h3>How<a name="auto3"/></h3>
+
+ <p>From the root of the Twisted source tree, run
+ <a href="http://twistedmatrix.com/trac/wiki/TwistedTrial" shape="rect">Trial</a>:
+ </p>
+
+ <pre class="shell" xml:space="preserve">
+$ bin/trial twisted
+ </pre>
+
+ <p>You'll find that having something like this in your emacs init
+ files is quite handy:</p>
+
+<pre class="elisp" xml:space="preserve">
+(defun runtests () (interactive)
+ (compile &quot;python /somepath/Twisted/bin/trial /somepath/Twisted&quot;))
+
+(global-set-key [(alt t)] 'runtests)
+</pre>
+ <h3>When<a name="auto4"/></h3>
+
+ <p>Always, always, <em>always</em> be sure <a href="http://www.xprogramming.com/xpmag/expUnitTestsAt100.htm" shape="rect">all the
+ tests pass</a> before committing any code. If someone else
+ checks out code at the start of a development session and finds
+ failing tests, they will not be happy and may decide to <em>hunt
+ you down</em>.</p>
+
+ <p>Since this is a geographically dispersed team, the person who
+ can help you get your code working probably isn't in the room with
+ you. You may want to share your work in progress over the
+ network, but you want to leave the main Subversion tree in good working
+ order. So <a href="http://svnbook.red-bean.com/en/1.0/ch04.html" shape="rect">use a branch</a>,
+ and merge your changes back in only after your problem is solved
+ and all the unit tests pass again.</p>
+
+ <h2>Adding a Test<a name="auto5"/></h2>
+
+ <p>Please don't add new modules to Twisted without adding tests
+ for them too. Otherwise we could change something which breaks
+ your module and not find out until later, making it hard to know
+ exactly what the change that broke it was, or until after a
+ release, and nobody wants broken code in a release.</p>
+
+ <p>Tests go into dedicated test packages such as
+ <code>twisted/test/</code> or <code>twisted/conch/test/</code>,
+ and are named <code>test_foo.py</code>, where <code>foo</code> is the name
+ of the module or package being tested. Extensive documentation on using
+ the PyUnit framework for writing unit tests can be found in the
+ <a href="#links" shape="rect">links section</a> below.
+ </p>
+
+ <p>One deviation from the standard PyUnit documentation: To ensure
+ that any variations in test results are due to variations in the
+ code or environment and not the test process itself, Twisted ships
+ with its own, compatible, testing framework. That just
+ means that when you import the unittest module, you will <code class="python">from twisted.trial import unittest</code> instead of the
+ standard <code class="python">import unittest</code>.</p>
+
+ <p>As long as you have followed the module naming and placement
+ conventions, <code class="shell">trial</code> will be smart
+ enough to pick up any new tests you write.</p>
+
+ <p>PyUnit provides a large number of assertion methods to be used when
+ writing tests. Many of these are redundant. For consistency, Twisted
+ unit tests should use the <code>assert</code> forms rather than the
+ <code>fail</code> forms. Also, use <code>assertEquals</code>,
+ <code>assertNotEquals</code>, and <code>assertAlmostEquals</code> rather
+ than <code>assertEqual</code>, <code>assertNotEqual</code>, and
+ <code>assertAlmostEqual</code>. <code>assertTrue</code> is also
+ preferred over <code>assert_</code>. You may notice this convention is
+ not followed everywhere in the Twisted codebase. If you are changing
+ some test code and notice the wrong method being used in nearby code,
+ feel free to adjust it.</p>
+
+ <p>When you add a unit test, make sure all methods have docstrings
+ specifying at a high level the intent of the test. That is, a description
+ that users of the method would understand.</p>
+
+<h2>Skipping tests, TODO items<a name="auto6"/></h2>
+
+<p>Trial, the Twisted unit test framework, has some extensions which are
+designed to encourage developers to add new tests. One common situation is
+that a test exercises some optional functionality: maybe it depends upon
+certain external libraries being available, maybe it only works on certain
+operating systems. The important common factor is that nobody considers
+these limitations to be a bug.</p>
+
+<p>To make it easy to test as much as possible, some tests may be skipped in
+certain situations. Individual test cases can raise the
+<code>SkipTest</code> exception to indicate that they should be skipped, and
+the remainder of the test is not run. In the summary (the very last thing
+printed, at the bottom of the test output) the test is counted as a
+<q>skip</q> instead of a <q>success</q> or <q>fail</q>. This should be used
+inside a conditional which looks for the necessary prerequisites:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">test_sshClient</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">ssh_path</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">unittest</span>.<span class="py-src-variable">SkipTest</span>(<span class="py-src-string">&quot;cannot find ssh, nothing to test&quot;</span>)
+ <span class="py-src-variable">foo</span>() <span class="py-src-comment"># do actual test after the SkipTest</span>
+</pre>
+
+<p>You can also set the <code>.skip</code> attribute on the method, with a string to
+indicate why the test is being skipped. This is convenient for temporarily
+turning off a test case, but it can also be set conditionally (by
+manipulating the class attributes after they've been defined):</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">test_thing</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">dotest</span>()
+<span class="py-src-variable">test_thing</span>.<span class="py-src-variable">skip</span> = <span class="py-src-string">&quot;disabled locally&quot;</span>
+</pre>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">MyTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_one</span>(<span class="py-src-parameter">self</span>):
+ ...
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">test_thing</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">dotest</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">haveThing</span>:
+ <span class="py-src-variable">MyTestCase</span>.<span class="py-src-variable">test_thing</span>.<span class="py-src-variable">im_func</span>.<span class="py-src-variable">skip</span> = <span class="py-src-string">&quot;cannot test without Thing&quot;</span>
+ <span class="py-src-comment"># but test_one() will still run</span>
+</pre>
+
+<p>Finally, you can turn off an entire TestCase at once by setting the .skip
+attribute on the class. If you organize your tests by the functionality they
+depend upon, this is a convenient way to disable just the tests which cannot
+be run.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">TCPTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ ...
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SSLTestCase</span>(<span class="py-src-parameter">unittest</span>.<span class="py-src-parameter">TestCase</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">haveSSL</span>:
+ <span class="py-src-variable">skip</span> = <span class="py-src-string">&quot;cannot test without SSL support&quot;</span>
+ <span class="py-src-comment"># but TCPTestCase will still run</span>
+ ...
+</pre>
+
+<h3>.todo and Testing New Functionality <a name="auto7"/></h3>
+
+<p>Two good practices which arise from the <q>XP</q> development process are
+sometimes at odds with each other:</p>
+
+<ul>
+ <li>Unit tests are a good thing. Good developers recoil in horror when
+ they see a failing unit test. They should drop everything until the test
+ has been fixed.</li>
+
+ <li>Good developers write the unit tests first. Once tests are done, they
+ write implementation code until the unit tests pass. Then they stop.</li>
+</ul>
+
+<p>These two goals will sometimes conflict. The unit tests that are written
+first, before any implementation has been done, are certain to fail. We want
+developers to commit their code frequently, for reliability and to improve
+coordination between multiple people working on the same problem together.
+While the code is being written, other developers (those not involved in the
+new feature) should not have to pay attention to failures in the new code.
+We should not dilute our well-indoctrinated Failing Test Horror Syndrome by
+crying wolf when an incomplete module has not yet started passing its unit
+tests. To do so would either teach the module author to put off writing or
+committing their unit tests until <em>after</em> all the functionality is
+working, or it would teach the other developers to ignore failing test
+cases. Both are bad things.</p>
+
+<p><q>.todo</q> is intended to solve this problem. When a developer first
+starts writing the unit tests for functionality that has not yet been
+implemented, they can set the <code>.todo</code> attribute on the test
+methods that are expected to fail. These methods will still be run, but
+their failure will not be counted the same as normal failures: they will go
+into an <q>expected failures</q> category. Developers should learn to treat
+this category as a second-priority queue, behind actual test failures.</p>
+
+<p>As the developer implements the feature, the tests will eventually start
+passing. This is surprising: after all those tests are marked as being
+expected to fail. The .todo tests which nevertheless pass are put into a
+<q>unexpected success</q> category. The developer should remove the .todo
+tag from these tests. At that point, they become normal tests, and their
+failure is once again cause for immediate action by the entire development
+team.</p>
+
+<p>The life cycle of a test is thus:</p>
+
+<ol>
+ <li>Test is created, marked <code>.todo</code>. Test fails: <q>expected
+ failure</q>.</li>
+
+ <li>Code is written, test starts to pass. <q>unexpected success</q>.</li>
+
+ <li><code>.todo</code> tag is removed. Test passes. <q>success</q>.</li>
+
+ <li>Code is broken, test starts to fail. <q>failure</q>. Developers spring
+ into action.</li>
+
+ <li>Code is fixed, test passes once more. <q>success</q>.</li>
+</ol>
+
+<p>Any test which remains marked with <code>.todo</code> for too long should
+be examined. Either it represents functionality which nobody is working on,
+or the test is broken in some fashion and needs to be fixed. Generally,
+<code>.todo</code> may be of use while you are developing a feature, but
+by the time you are ready to commit anything, all the tests you have written
+should be passing. In other words, you should rarely, if ever, feel the need
+to add a test marked todo to trunk. When you do, consider whether a ticket
+in the issue tracker would be more useful.</p>
+
+<h3>Line Coverage Information<a name="auto8"/></h3>
+
+<p>Trial provides line coverage information, which is very useful to ensure
+old code has decent coverage. Passing the <code>--coverage</code> option to
+to Trial will generate the coverage information in a file called
+<code>coverage</code> which can be found in the <code>_trial_temp</code>
+folder. This option requires Python 2.3.3 or newer.</p>
+
+<h2>Associating Test Cases With Source Files<a name="auto9"/></h2>
+
+<p>Please add a <code>test-case-name</code> tag to the source file that is
+covered by your new test. This is a comment at the beginning of the file
+which looks like one of the following:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-comment"># -*- test-case-name: twisted.test.test_defer -*-</span>
+</pre>
+
+<p>or</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+<span class="py-src-comment"># -*- test-case-name: twisted.test.test_defer -*-</span>
+</pre>
+
+<p>This format is understood by emacs to mark <q>File Variables</q>. The
+intention is to accept <code>test-case-name</code> anywhere emacs would on
+the first or second line of the file (but not in the <code>File
+Variables:</code> block that emacs accepts at the end of the file). If you
+need to define other emacs file variables, you can either put them in the
+<code>File Variables:</code> block or use a semicolon-separated list of
+variable definitions:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-comment"># -*- test-case-name: twisted.test.test_defer; fill-column: 75; -*-</span>
+</pre>
+
+<p>If the code is exercised by multiple test cases, those may be marked by
+using a comma-separated list of tests, as follows: (NOTE: not all tools can
+handle this yet.. <code>trial --testmodule</code> does, though)</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-comment"># -*- test-case-name: twisted.test.test_defer,twisted.test.test_tcp -*-</span>
+</pre>
+
+<p>The <code>test-case-name</code> tag will allow <code class="shell">trial
+--testmodule twisted/dir/myfile.py</code> to determine which test cases need
+to be run to exercise the code in <code>myfile.py</code>. Several tools (as
+well as <code>twisted-dev.el</code>'s F9 command) use this to automatically
+run the right tests.</p>
+
+<h2 id="links">Links<a name="auto10"/></h2><a name="links" shape="rect"/>
+
+<ul>
+ <li>A chapter on <a href="http://diveintopython.org/unit_testing/index.html" shape="rect">Unit Testing</a>
+ in Mark Pilgrim's <a href="http://diveintopython.org" shape="rect">Dive Into
+ Python</a>.</li>
+
+ <li><a href="http://docs.python.org/lib/module-unittest.html" shape="rect"><code>unittest</code></a> module documentation, in the <a href="http://docs.python.org/library" shape="rect">Python Library
+ Reference</a>.</li>
+
+ <li><a href="http://c2.com/cgi/wiki?UnitTests" shape="rect">UnitTests</a> on
+ the <a href="http://c2.com/cgi/wiki" shape="rect">PortlandPatternRepository
+ Wiki</a>, where all the cool <a href="http://c2.com/cgi/wiki?ExtremeProgramming" shape="rect">ExtremeProgramming</a> kids hang out.</li>
+
+ <li><a href="http://www.extremeprogramming.org/rules/unittests.html" shape="rect">Unit
+ Tests</a> in <a href="http://www.extremeprogramming.org" shape="rect">Extreme Programming: A Gentle Introduction</a>.</li>
+
+ <li>Ron Jeffries expounds on the importance of <a href="http://www.xprogramming.com/xpmag/expUnitTestsAt100.htm" shape="rect">Unit
+ Tests at 100%</a>.</li>
+
+ <li>Ron Jeffries writes about the <a href="http://www.xprogramming.com/Practices/PracUnitTest.html" shape="rect">Unit
+ Test</a> in the <a href="http://www.xprogramming.com/Practices/xpractices.htm" shape="rect">Extreme
+ Programming practices of C3</a>.</li>
+
+ <li><a href="http://pyunit.sourceforge.net" shape="rect">PyUnit's homepage</a>.</li>
+
+ <li>The <a href="http://svn.twistedmatrix.com/cvs/trunk/twisted/?root=Twisted" shape="rect">twisted/test directory</a> in Subversion.</li>
+
+ </ul>
+
+ <p>See also <a href="../../howto/testing.html" shape="rect">Tips for writing tests for Twisted
+ code</a>.</p>
+
+ </div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/development/policy/writing-standard.html b/vendor/Twisted-10.0.0/doc/core/development/policy/writing-standard.html
new file mode 100644
index 0000000000..b07e533c42
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/policy/writing-standard.html
@@ -0,0 +1,313 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Writing Standard</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Writing Standard</h1>
+ <div class="toc"><ol><li><a href="#auto0">General style</a></li><li><a href="#auto1">Evangelism and usage documents</a></li><li><a href="#auto2">Descriptions of features</a></li><li><a href="#auto3">Linking</a></li><li><a href="#auto4">Introductions</a></li><ul><li><a href="#auto5">Introductory paragraph</a></li><li><a href="#auto6">Description of target audience</a></li><li><a href="#auto7">Goals of document</a></li></ul><li><a href="#auto8">Example code</a></li><li><a href="#auto9">Conclusions</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <p>The Twisted writing standard describes the documentation writing
+ styles we prefer in our documentation. This standard applies particularly
+ to howtos and other descriptive documentation.</p>
+
+ <p>This document should be read with the <a href="doc-standard.html" shape="rect">documentation standard</a>, which describes
+ markup style for the documentation.</p>
+
+ <p>This document is meant to help Twisted documentation authors produce
+ documentation that does not have the following problems:</p>
+
+ <ul>
+ <li>misleads users about what is good Twisted style;</li>
+ <li>misleads users into thinking that an advanced howto is an introduction
+ to writing their first Twisted server; and</li>
+ <li>misleads users about whether they fit the document's target audience:
+ for example, that they are able to use enterprise without knowing how to
+ write SQL queries.</li>
+ </ul>
+
+ <h2>General style<a name="auto0"/></h2>
+
+ <p>Documents should aim to be clear and concise, allowing the API
+ documentation and the example code to tell as much of the story as they
+ can. Demonstrations and where necessary supported arguments should always
+ preferred to simple statements (&quot;here is how you would simplify this
+ code with Deferreds&quot; rather than &quot;Deferreds make code
+ simpler&quot;).</p>
+
+ <p>Documents should be clearly delineated into sections and subsections.
+ Each of these sections, like the overall document, should have a single
+ clear purpose. This is most easily tested by trying to have meaningful
+ headings: a section which is headed by &quot;More details&quot; or
+ &quot;Advanced stuff&quot; is not purposeful enough. There should be
+ fairly obvious ways to split a document. The two most common are task
+ based sectioning and sectioning which follows module and class
+ separations.</p>
+
+ <p>Documentation must use American English spelling, and where possible
+ avoid any local variants of either vocabulary or grammar. Grammatically
+ complex sentences should ideally be avoided: these make reading
+ unnecessarily difficult, particularly for non-native speakers.</p>
+
+ <h2>Evangelism and usage documents<a name="auto1"/></h2>
+
+ <p>The Twisted documentation should maintain a reasonable distinction
+ between &quot;evangelism&quot; documentation, which compares the Twisted
+ design or Twisted best practice with other approaches and argues for the
+ Twisted approach, and &quot;usage&quot; documentation, which describes the
+ Twisted approach in detail without comparison to other possible
+ approaches.</p>
+
+ <p>While both kinds of documentation are useful, they have different
+ audiences. The first kind of document, evangelical documents, is useful to
+ a reader who is researching and comparing approaches and seeking to
+ understand the Twisted approach or Twisted functionality in order to
+ decide whether it is useful to them. The second kind of document, usage
+ documents, are useful to a reader who has decided to use Twisted and
+ simply wants further information about available functions and
+ architectures they can use to accomplish their goal.</p>
+
+ <p>Since they have distinct audiences, evangelism and detailed usage
+ documentation belongs in separate files. There should be links between
+ them in 'Further reading' or similar sections.</p>
+
+ <h2>Descriptions of features<a name="auto2"/></h2>
+
+ <p>Descriptions of any feature added since release 2.0 of Twisted core
+ must have a note describing which release of which Twisted project they
+ were added in at the first mention in each document. If they are not yet
+ released, give them the number of the next minor release.</p>
+
+ <p>For example, a substantial change might have a version number added in
+ the introduction:</p>
+
+ <blockquote>
+ This document describes the Application infrastructure for deploying
+ Twisted applications <em>(added in Twisted 1.3)</em>.
+ </blockquote>
+
+ <p>The version does not need to be mentioned elsewhere in the document
+ except for specific features which were added in subsequent releases,
+ which might should be mentioned separately.</p>
+
+ <blockquote>
+ The simplest way to create a <code>.tac</code> file, SuperTac <em>(added
+ in Twisted Core 99.7)</em>...</blockquote>
+
+ <p>In the case where the usage of a feature has substantially changed, the
+ number should be that of the release in which the current usage became
+ available. For example:</p>
+
+ <blockquote> This document describes the Application infrastructure for
+ deploying Twisted applications <em>(updated[/substantially updated] in Twisted
+ 2.7)</em>. </blockquote>
+
+ <h2>Linking<a name="auto3"/></h2>
+
+ <p>The first occurrence of the name of any module, class or function should
+ always link to the API documents. Subsequent mentions may or may not link
+ at the author's discretion: discussions which are very closely bound to a
+ particular API should probably link in the first mention in the given
+ section.</p>
+
+ <p>Links between howtos are encouraged. Overview documents and tutorials
+ should always link to reference documents and in depth documents. These
+ documents should link among themselves wherever it's needed: if you're
+ tempted to re-describe the functionality of another module, you should
+ certainly link instead.</p>
+
+ <h2>Introductions<a name="auto4"/></h2>
+
+ <p>The introductory section of a Twisted howto should immediately follow
+ the top-level heading and precede any subheadings.</p>
+
+ <p>The following items should be present in the introduction to Twisted
+ howtos: the introductory paragraph and the description of the target
+ audience.</p>
+
+ <h3>Introductory paragraph<a name="auto5"/></h3>
+
+ <p>The introductory paragraph of a document should summarize what the
+ document is designed to present. It should use the both proper names for
+ the Twisted technologies and simple non-Twisted descriptions of the
+ technologies. For example, in this paragraph both the name of the technology
+ (&quot;Conch&quot;) and a description (&quot;SSH server&quot;) are used:</p>
+
+ <blockquote>
+ This document describes setting up a SSH server to serve data from the
+ file system using Conch, the Twisted SSH implementation.
+ </blockquote>
+
+ <p>The introductory paragraph should be relatively short, but should, like
+ the above, somewhere define the document's objective: what the reader
+ should be able to do using instructions in the document.</p>
+
+ <h3>Description of target audience<a name="auto6"/></h3>
+
+ <p>Subsequent paragraphs in the introduction should describe the target
+ audience of the document: who would want to read it, and what they should
+ know before they can expect to use your document. For example:</p>
+
+ <blockquote>
+ <p>
+ The target audience of this document is a Twisted user who has a set of
+ filesystem like data objects that they would like to make available to
+ authenticated users over SFTP.
+ </p>
+
+ <p>
+ Following the directions in this document will require that you are
+ familiar with managing authentication via the Twisted Cred system.
+ </p>
+ </blockquote>
+
+ <p>Use your discretion about the extent to which you list assumed
+ knowledge. Very introductory documents that are going to be among a
+ reader's first exposure to Twisted will even need to specify that they
+ rely on knowledge of Python and of certain networking concepts (ports,
+ servers, clients, connections) but documents that are going to be sought
+ out by existing Twisted users for particular purposes only need to specify
+ other Twisted knowledge that is assumed.</p>
+
+ <p>Any knowledge of technologies that wouldn't be considered &quot;core
+ Python&quot; and/or &quot;simple networking&quot; need to be explicitly
+ specified, no matter how obvious they seem to someone familiar with the
+ technology. For example, it needs to be stated that someone using
+ enterprise should know SQL and should know how to set up and populate
+ databases for testing purposes.</p>
+
+ <p>Where possible, link to other documents that will fill in missing
+ knowledge for the reader. Linking to documents in the Twisted repository
+ is preferred but not essential.</p>
+
+ <h3>Goals of document<a name="auto7"/></h3>
+
+ <p>The introduction should finish with a list of tasks that the user can
+ expect to see the document accomplish. These tasks should be concrete
+ rather than abstract, so rather than telling the user that they will
+ &quot;understand Twisted Conch&quot;, you would list the specific tasks
+ that they will see the document do. For example:</p>
+
+ <blockquote>
+ <p>
+ This document will demonstrate the following tasks using Twisted Conch:
+ </p>
+
+ <ul>
+ <li>creating an anonymous access read-only SFTP server using a filesystem
+ backend;</li>
+ <li>creating an anonymous access read-only SFTP server using a proxy
+ backend connecting to an HTTP server; and</li>
+ <li>creating a anonymous access read and write SFTP server using a
+ filesystem backend.</li>
+ </ul>
+ </blockquote>
+
+ <p>In many cases this will essentially be a list of your code examples,
+ but it need not be. If large sections of your code are devoted to design
+ discussions, your goals might resemble the following:</p>
+
+ <blockquote>
+ <p>
+ This document will discuss the following design aspects of writing Conch
+ servers:
+ </p>
+
+ <ul>
+ <li>authentication of users; and</li>
+ <li>choice of data backends.</li>
+ </ul>
+ </blockquote>
+
+
+ <h2>Example code<a name="auto8"/></h2>
+
+ <p>Wherever possible, example code should be provided to illustrate a
+ certain technique or piece of functionality.</p>
+
+ <p>Example code should try and meet as many of the following requirements
+ as possible:</p>
+
+ <ul>
+ <li>example code should be a complete working example suitable for copying
+ and pasting and running by the reader (where possible, provide a link to a
+ file to download);</li>
+ <li>example code should be short;</li>
+ <li>example code should be commented very extensively, with the assumption
+ that this code may be read by a Twisted newcomer;</li>
+ <li>example code should conform to the <a href="coding-standard.html" shape="rect">coding standard</a>; and</li>
+ <li>example code should exhibit 'best practice', not only for dealing with
+ the target functionality, but also for use of the application framework
+ and so on.</li>
+ </ul>
+
+ <p>The requirement to have a complete working example will occasionally
+ impose upon authors the need to have a few dummy functions: in Twisted
+ documentation the most common example is where a function is needed to
+ generate a Deferred and fire it after some time has passed. An example
+ might be this, where <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.task.deferLater.html" title="twisted.internet.task.deferLater">deferLater</a></code> is used to fire a callback
+ after a period of time:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">task</span>, <span class="py-src-variable">reactor</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getDummyDeferred</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Dummy method which returns a deferred that will fire in 5 seconds with
+ a result
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">task</span>.<span class="py-src-variable">deferLater</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">5</span>, <span class="py-src-keyword">lambda</span> <span class="py-src-variable">x</span>: <span class="py-src-string">&quot;RESULT&quot;</span>)
+</pre>
+
+ <p>As in the above example, it is imperative to clearly mark that the
+ function is a dummy in as many ways as you can: using <code>Dummy</code> in
+ the function name, explaining that it is a dummy in the docstring, and
+ marking particular lines as being required to create an effect for the
+ purposes of demonstration. In most cases, this will save the reader from
+ mistaking this dummy method for an idiom they should use in their Twisted
+ code.</p>
+
+ <h2>Conclusions<a name="auto9"/></h2>
+
+ <p>The conclusion of a howto should follow the very last section heading
+ in a file. This heading would usually be called &quot;Conclusion&quot;.</p>
+
+ <p>The conclusion of a howto should remind the reader of the tasks that
+ they have done while reading the document. For example:</p>
+
+ <blockquote>
+ <p>
+ In this document, you have seen how to:
+ </p>
+
+ <ol>
+ <li>set up an anonymous read-only SFTP server;</li>
+ <li>set up a SFTP server where users authenticate;</li>
+ <li>set up a SFTP server where users are restricted to some parts of the
+ filesystem based on authentication; and</li>
+ <li>set up a SFTP server where users have write access to some parts of
+ the filesystem based on authentication.</li>
+ </ol>
+ </blockquote>
+
+ <p>If appropriate, the howto could follow this description with links to
+ other documents that might be of interest to the reader with their
+ newfound knowledge. However, these links should be limited to fairly
+ obvious extensions of at least one of the listed tasks.</p>
+
+ </div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/development/security.html b/vendor/Twisted-10.0.0/doc/core/development/security.html
new file mode 100644
index 0000000000..4f2a2e55ff
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/development/security.html
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Security</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Security</h1>
+ <div class="toc"><ol><li><a href="#auto0">Bad input</a></li><li><a href="#auto1">Resource Exhaustion and DoS</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<p>We need to do a full audit of Twisted, module by module.
+This document list the sort of things you want to look for
+when doing this, or when writing your own code.</p>
+
+<h2>Bad input<a name="auto0"/></h2>
+
+<p>Any place we receive untrusted data, we need to be careful.
+In some cases we are not careful enough. For example, in HTTP
+there are many places where strings need to be converted to
+ints, so we use <code class="python">int()</code>. The problem
+is that this well accept negative numbers as well, whereas
+the protocol should only be accepting positive numbers.</p>
+
+<h2>Resource Exhaustion and DoS<a name="auto1"/></h2>
+
+<p>Make sure we never allow users to create arbitarily large
+strings or files. Some of the protocols still have issues
+like this. Place a limit which allows reasonable use but
+will cut off huge requests, and allow changing of this limit.
+</p>
+
+<p>Another operation to look out for are exceptions. They can fill
+up logs and take a lot of CPU time to render in web pages.</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/ampclient.py b/vendor/Twisted-10.0.0/doc/core/examples/ampclient.py
new file mode 100644
index 0000000000..53494486ed
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/ampclient.py
@@ -0,0 +1,26 @@
+from twisted.internet import reactor, defer
+from twisted.internet.protocol import ClientCreator
+from twisted.protocols import amp
+from ampserver import Sum, Divide
+
+
+def doMath():
+ d1 = ClientCreator(reactor, amp.AMP).connectTCP(
+ '127.0.0.1', 1234).addCallback(
+ lambda p: p.callRemote(Sum, a=13, b=81)).addCallback(
+ lambda result: result['total'])
+ def trapZero(result):
+ result.trap(ZeroDivisionError)
+ print "Divided by zero: returning INF"
+ return 1e1000
+ d2 = ClientCreator(reactor, amp.AMP).connectTCP(
+ '127.0.0.1', 1234).addCallback(
+ lambda p: p.callRemote(Divide, numerator=1234,
+ denominator=0)).addErrback(trapZero)
+ def done(result):
+ print 'Done with math:', result
+ defer.DeferredList([d1, d2]).addCallback(done)
+
+if __name__ == '__main__':
+ doMath()
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/ampserver.py b/vendor/Twisted-10.0.0/doc/core/examples/ampserver.py
new file mode 100644
index 0000000000..7b5adf0107
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/ampserver.py
@@ -0,0 +1,40 @@
+from twisted.protocols import amp
+
+class Sum(amp.Command):
+ arguments = [('a', amp.Integer()),
+ ('b', amp.Integer())]
+ response = [('total', amp.Integer())]
+
+
+class Divide(amp.Command):
+ arguments = [('numerator', amp.Integer()),
+ ('denominator', amp.Integer())]
+ response = [('result', amp.Float())]
+ errors = {ZeroDivisionError: 'ZERO_DIVISION'}
+
+
+class Math(amp.AMP):
+ def sum(self, a, b):
+ total = a + b
+ print 'Did a sum: %d + %d = %d' % (a, b, total)
+ return {'total': total}
+ Sum.responder(sum)
+
+ def divide(self, numerator, denominator):
+ result = float(numerator) / denominator
+ print 'Divided: %d / %d = %f' % (numerator, denominator, result)
+ return {'result': result}
+ Divide.responder(divide)
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.internet.protocol import Factory
+ pf = Factory()
+ pf.protocol = Math
+ reactor.listenTCP(1234, pf)
+ print 'started'
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/bananabench.py b/vendor/Twisted-10.0.0/doc/core/examples/bananabench.py
new file mode 100644
index 0000000000..9d13f386f9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/bananabench.py
@@ -0,0 +1,79 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import sys
+import time
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+# Twisted Imports
+from twisted.spread import banana
+from twisted.internet import protocol
+
+iterationCount = 10000
+
+class BananaBench:
+ r = range( iterationCount )
+ def setUp(self, encClass):
+ self.io = StringIO.StringIO()
+ self.enc = encClass()
+ self.enc.makeConnection(protocol.FileWrapper(self.io))
+ self.enc._selectDialect("none")
+ self.enc.expressionReceived = self.putResult
+
+ def putResult(self, result):
+ self.result = result
+
+ def tearDown(self):
+ self.enc.connectionLost()
+ del self.enc
+
+ def testEncode(self, value):
+ starttime = time.time()
+ for i in self.r:
+ self.enc.sendEncoded(value)
+ self.io.truncate(0)
+ endtime = time.time()
+ print ' Encode took %s seconds' % (endtime - starttime)
+ return endtime - starttime
+
+ def testDecode(self, value):
+ self.enc.sendEncoded(value)
+ encoded = self.io.getvalue()
+ starttime = time.time()
+ for i in self.r:
+ self.enc.dataReceived(encoded)
+ endtime = time.time()
+ print ' Decode took %s seconds' % (endtime - starttime)
+ return endtime - starttime
+
+ def performTest(self, method, data, encClass):
+ self.setUp(encClass)
+ method(data)
+ self.tearDown()
+
+ def runTests(self, testData):
+ print 'Test data is: %s' % testData
+ print ' Using Pure Python Banana:'
+ self.performTest(self.testEncode, testData, banana.Banana)
+ self.performTest(self.testDecode, testData, banana.Banana)
+
+bench = BananaBench()
+print 'Doing %s iterations of each test.' % iterationCount
+print ''
+testData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+bench.runTests(testData)
+testData = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
+bench.runTests(testData)
+testData = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
+bench.runTests(testData)
+testData = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]
+bench.runTests(testData)
+testData = [1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l, 9l, 10l]
+bench.runTests(testData)
+testData = [1, 2, [3, 4], [30.5, 40.2], 5, ["six", "seven", ["eight", 9]], [10], []]
+bench.runTests(testData)
+
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/chatserver.py b/vendor/Twisted-10.0.0/doc/core/examples/chatserver.py
new file mode 100644
index 0000000000..76c3cf8926
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/chatserver.py
@@ -0,0 +1,37 @@
+"""The most basic chat protocol possible.
+
+run me with twistd -y chatserver.py, and then connect with multiple
+telnet clients to port 1025
+"""
+
+from twisted.protocols import basic
+
+
+
+class MyChat(basic.LineReceiver):
+ def connectionMade(self):
+ print "Got new client!"
+ self.factory.clients.append(self)
+
+ def connectionLost(self, reason):
+ print "Lost a client!"
+ self.factory.clients.remove(self)
+
+ def lineReceived(self, line):
+ print "received", repr(line)
+ for c in self.factory.clients:
+ c.message(line)
+
+ def message(self, message):
+ self.transport.write(message + '\n')
+
+
+from twisted.internet import protocol
+from twisted.application import service, internet
+
+factory = protocol.ServerFactory()
+factory.protocol = MyChat
+factory.clients = []
+
+application = service.Application("chatserver")
+internet.TCPServer(1025, factory).setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/courier.py b/vendor/Twisted-10.0.0/doc/core/examples/courier.py
new file mode 100644
index 0000000000..319373ebd9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/courier.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example of a interfacing to Courier's mail filter interface.
+"""
+
+LOGFILE = '/tmp/filter.log'
+
+# Setup log file
+from twisted.python import log
+log.startLogging(open(LOGFILE, 'a'))
+import sys
+sys.stderr = log.logfile
+
+# Twisted imports
+from twisted.internet import reactor, stdio
+from twisted.internet.protocol import Protocol, Factory
+from twisted.protocols import basic
+
+FILTERS='/var/lib/courier/filters'
+ALLFILTERS='/var/lib/courier/allfilters'
+FILTERNAME='twistedfilter'
+
+import os, os.path
+from syslog import syslog, openlog, LOG_MAIL
+from rfc822 import Message
+
+def trace_dump():
+ t,v,tb = sys.exc_info()
+ openlog(FILTERNAME, 0, LOG_MAIL)
+ syslog('Unhandled exception: %s - %s' % (v, t))
+ while tb:
+ syslog('Trace: %s:%s %s' % (tb.tb_frame.f_code.co_filename,tb.tb_frame.f_code.co_name,tb.tb_lineno))
+ tb = tb.tb_next
+ # just to be safe
+ del tb
+
+def safe_del(file):
+ try:
+ if os.path.isdir(file):
+ os.removedirs(file)
+ else:
+ os.remove(file)
+ except OSError:
+ pass
+
+
+class DieWhenLost(Protocol):
+ def connectionLost(self, reason=None):
+ reactor.stop()
+
+
+class MailProcessor(basic.LineReceiver):
+ """I process a mail message.
+
+ Override filterMessage to do any filtering you want."""
+ messageFilename = None
+ delimiter = '\n'
+
+ def connectionMade(self):
+ log.msg('Connection from %r' % self.transport)
+ self.state = 'connected'
+ self.metaInfo = []
+
+ def lineReceived(self, line):
+ if self.state == 'connected':
+ self.messageFilename = line
+ self.state = 'gotMessageFilename'
+ if self.state == 'gotMessageFilename':
+ if line:
+ self.metaInfo.append(line)
+ else:
+ if not self.metaInfo:
+ self.transport.loseConnection()
+ return
+ self.filterMessage()
+
+ def filterMessage(self):
+ """Override this.
+
+ A trivial example is included.
+ """
+ try:
+ m = Message(open(self.messageFilename))
+ self.sendLine('200 Ok')
+ except:
+ trace_dump()
+ self.sendLine('435 %s processing error' % FILTERNAME)
+
+
+def main():
+ # Listen on the UNIX socket
+ f = Factory()
+ f.protocol = MailProcessor
+ safe_del('%s/%s' % (ALLFILTERS, FILTERNAME))
+ reactor.listenUNIX('%s/%s' % (ALLFILTERS, FILTERNAME), f, 10)
+
+ # Once started, close fd 3 to let Courier know we're ready
+ reactor.callLater(0, os.close, 3)
+
+ # When stdin is closed, it's time to exit.
+ s = stdio.StandardIO(DieWhenLost())
+
+ # Go!
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/cred.py b/vendor/Twisted-10.0.0/doc/core/examples/cred.py
new file mode 100644
index 0000000000..2002317186
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/cred.py
@@ -0,0 +1,163 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+
+import sys
+from zope.interface import implements, Interface
+
+from twisted.protocols import basic
+from twisted.internet import protocol
+from twisted.python import log
+
+from twisted.cred import error
+from twisted.cred import portal
+from twisted.cred import checkers
+from twisted.cred import credentials
+
+class IProtocolUser(Interface):
+ def getPrivileges():
+ """Return a list of privileges this user has."""
+
+ def logout():
+ """Cleanup per-login resources allocated to this avatar"""
+
+class AnonymousUser:
+ implements(IProtocolUser)
+
+ def getPrivileges(self):
+ return [1, 2, 3]
+
+ def logout(self):
+ print "Cleaning up anonymous user resources"
+
+class RegularUser:
+ implements(IProtocolUser)
+
+ def getPrivileges(self):
+ return [1, 2, 3, 5, 6]
+
+ def logout(self):
+ print "Cleaning up regular user resources"
+
+class Administrator:
+ implements(IProtocolUser)
+
+ def getPrivileges(self):
+ return range(50)
+
+ def logout(self):
+ print "Cleaning up administrator resources"
+
+class Protocol(basic.LineReceiver):
+ user = None
+ portal = None
+ avatar = None
+ logout = None
+
+ def connectionMade(self):
+ self.sendLine("Login with USER <name> followed by PASS <password> or ANON")
+ self.sendLine("Check privileges with PRIVS")
+
+ def connectionLost(self, reason):
+ if self.logout:
+ self.logout()
+ self.avatar = None
+ self.logout = None
+
+ def lineReceived(self, line):
+ f = getattr(self, 'cmd_' + line.upper().split()[0])
+ if f:
+ try:
+ f(*line.split()[1:])
+ except TypeError:
+ self.sendLine("Wrong number of arguments.")
+ except:
+ self.sendLine("Server error (probably your fault)")
+
+ def cmd_ANON(self):
+ if self.portal:
+ self.portal.login(credentials.Anonymous(), None, IProtocolUser
+ ).addCallbacks(self._cbLogin, self._ebLogin
+ )
+ else:
+ self.sendLine("DENIED")
+
+ def cmd_USER(self, name):
+ self.user = name
+ self.sendLine("Alright. Now PASS?")
+
+ def cmd_PASS(self, password):
+ if not self.user:
+ self.sendLine("USER required before PASS")
+ else:
+ if self.portal:
+ self.portal.login(
+ credentials.UsernamePassword(self.user, password),
+ None,
+ IProtocolUser
+ ).addCallbacks(self._cbLogin, self._ebLogin
+ )
+ else:
+ self.sendLine("DENIED")
+
+ def cmd_PRIVS(self):
+ self.sendLine("You have the following privileges: ")
+ self.sendLine(" ".join(map(str, self.avatar.getPrivileges())))
+
+ def _cbLogin(self, (interface, avatar, logout)):
+ assert interface is IProtocolUser
+ self.avatar = avatar
+ self.logout = logout
+ self.sendLine("Login successful. Available commands: PRIVS")
+
+ def _ebLogin(self, failure):
+ failure.trap(error.UnauthorizedLogin)
+ self.sendLine("Login denied! Go away.")
+
+class ServerFactory(protocol.ServerFactory):
+ protocol = Protocol
+
+ def __init__(self, portal):
+ self.portal = portal
+
+ def buildProtocol(self, addr):
+ p = protocol.ServerFactory.buildProtocol(self, addr)
+ p.portal = self.portal
+ return p
+
+class Realm:
+ implements(portal.IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if IProtocolUser in interfaces:
+ if avatarId == checkers.ANONYMOUS:
+ av = AnonymousUser()
+ elif avatarId.isupper():
+ # Capitalized usernames are administrators.
+ av = Administrator()
+ else:
+ av = RegularUser()
+ return IProtocolUser, av, av.logout
+ raise NotImplementedError("Only IProtocolUser interface is supported by this realm")
+
+def main():
+ r = Realm()
+ p = portal.Portal(r)
+ c = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ c.addUser("auser", "thepass")
+ c.addUser("SECONDUSER", "secret")
+ p.registerChecker(c)
+ p.registerChecker(checkers.AllowAnonymousAccess())
+
+ f = ServerFactory(p)
+
+ log.startLogging(sys.stdout)
+
+ from twisted.internet import reactor
+ reactor.listenTCP(4738, f)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/dbcred.py b/vendor/Twisted-10.0.0/doc/core/examples/dbcred.py
new file mode 100755
index 0000000000..2d9bfc30c0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/dbcred.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Simple example of a db checker: define a L{ICredentialsChecker} implementation
+that deals with a database backend to authenticate a user.
+"""
+
+from twisted.cred import error
+from twisted.cred.credentials import IUsernameHashedPassword, IUsernamePassword
+from twisted.cred.checkers import ICredentialsChecker
+from twisted.internet.defer import Deferred
+
+from zope.interface import implements
+
+
+class DBCredentialsChecker(object):
+ """
+ This class checks the credentials of incoming connections
+ against a user table in a database.
+ """
+ implements(ICredentialsChecker)
+
+ def __init__(self, runQuery,
+ query="SELECT username, password FROM user WHERE username = %s",
+ customCheckFunc=None, caseSensitivePasswords=True):
+ """
+ @param runQuery: This will be called to get the info from the db.
+ Generally you'd want to create a
+ L{twisted.enterprice.adbapi.ConnectionPool} and pass it's runQuery
+ method here. Otherwise pass a function with the same prototype.
+ @type runQuery: C{callable}
+
+ @type query: query used to authenticate user.
+ @param query: C{str}
+
+ @param customCheckFunc: Use this if the passwords in the db are stored
+ as hashes. We'll just call this, so you can do the checking
+ yourself. It takes the following params:
+ (username, suppliedPass, dbPass) and must return a boolean.
+ @type customCheckFunc: C{callable}
+
+ @param caseSensitivePasswords: If true requires that every letter in
+ C{credentials.password} is exactly the same case as the it's
+ counterpart letter in the database.
+ This is only relevant if C{customCheckFunc} is not used.
+ @type caseSensitivePasswords: C{bool}
+ """
+ self.runQuery = runQuery
+ self.caseSensitivePasswords = caseSensitivePasswords
+ self.customCheckFunc = customCheckFunc
+ # We can't support hashed password credentials if we only have a hash
+ # in the DB
+ if customCheckFunc:
+ self.credentialInterfaces = (IUsernamePassword,)
+ else:
+ self.credentialInterfaces = (
+ IUsernamePassword, IUsernameHashedPassword,)
+
+ self.sql = query
+
+ def requestAvatarId(self, credentials):
+ """
+ Authenticates the kiosk against the database.
+ """
+ # Check that the credentials instance implements at least one of our
+ # interfaces
+ for interface in self.credentialInterfaces:
+ if interface.providedBy(credentials):
+ break
+ else:
+ raise error.UnhandledCredentials()
+ # Ask the database for the username and password
+ dbDeferred = self.runQuery(self.sql, (credentials.username,))
+ # Setup our deferred result
+ deferred = Deferred()
+ dbDeferred.addCallbacks(self._cbAuthenticate, self._ebAuthenticate,
+ callbackArgs=(credentials, deferred),
+ errbackArgs=(credentials, deferred))
+ return deferred
+
+ def _cbAuthenticate(self, result, credentials, deferred):
+ """
+ Checks to see if authentication was good. Called once the info has
+ been retrieved from the DB.
+ """
+ if len(result) == 0:
+ # Username not found in db
+ deferred.errback(error.UnauthorizedLogin('Username unknown'))
+ else:
+ username, password = result[0]
+ if self.customCheckFunc:
+ # Let the owner do the checking
+ if self.customCheckFunc(
+ username, credentials.password, password):
+ deferred.callback(credentials.username)
+ else:
+ deferred.errback(
+ error.UnauthorizedLogin('Password mismatch'))
+ else:
+ # It's up to us or the credentials object to do the checking
+ # now
+ if IUsernameHashedPassword.providedBy(credentials):
+ # Let the hashed password checker do the checking
+ if credentials.checkPassword(password):
+ deferred.callback(credentials.username)
+ else:
+ deferred.errback(
+ error.UnauthorizedLogin('Password mismatch'))
+ elif IUsernamePassword.providedBy(credentials):
+ # Compare the passwords, deciging whether or not to use
+ # case sensitivity
+ if self.caseSensitivePasswords:
+ passOk = (
+ password.lower() == credentials.password.lower())
+ else:
+ passOk = password == credentials.password
+ # See if they match
+ if passOk:
+ deferred.callback(credentials.username)
+ else:
+ deferred.errback(
+ error.UnauthorizedLogin('Password mismatch'))
+ else:
+ # OK, we don't know how to check this
+ deferred.errback(error.UnhandledCredentials())
+
+ def _ebAuthenticate(self, message, credentials, deferred):
+ """
+ The database lookup failed for some reason.
+ """
+ deferred.errback(error.LoginFailed(message))
+
+
+def main():
+ """
+ Run a simple echo pb server to test the checker. It defines a custom query
+ for dealing with sqlite special quoting, but otherwise it's a
+ straightforward use of the object.
+
+ You can test it running C{pbechoclient.py}.
+ """
+ import sys
+ from twisted.python import log
+ log.startLogging(sys.stdout)
+ import os
+ if os.path.isfile('testcred'):
+ os.remove('testcred')
+ from twisted.enterprise import adbapi
+ pool = adbapi.ConnectionPool('pysqlite2.dbapi2', 'testcred')
+ # Create the table that will be used
+ query1 = """CREATE TABLE user (
+ username string,
+ password string
+ )"""
+ # Insert a test user
+ query2 = """INSERT INTO user VALUES ('guest', 'guest')"""
+ def cb(res):
+ pool.runQuery(query2)
+ pool.runQuery(query1).addCallback(cb)
+
+ checker = DBCredentialsChecker(pool.runQuery,
+ query="SELECT username, password FROM user WHERE username = ?")
+ from twisted.cred.portal import Portal
+
+ import pbecho
+ from twisted.spread import pb
+ portal = Portal(pbecho.SimpleRealm())
+ portal.registerChecker(checker)
+ reactor.listenTCP(pb.portno, pb.PBServerFactory(portal))
+
+
+if __name__ == "__main__":
+ from twisted.internet import reactor
+ reactor.callWhenRunning(main)
+ reactor.run()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/echoclient.py b/vendor/Twisted-10.0.0/doc/core/examples/echoclient.py
new file mode 100644
index 0000000000..c5ad2fe81c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/echoclient.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet.protocol import ClientFactory
+from twisted.protocols.basic import LineReceiver
+from twisted.internet import reactor
+import sys
+
+class EchoClient(LineReceiver):
+ end="Bye-bye!"
+ def connectionMade(self):
+ self.sendLine("Hello, world!")
+ self.sendLine("What a fine day it is.")
+ self.sendLine(self.end)
+
+ def lineReceived(self, line):
+ print "receive:", line
+ if line==self.end:
+ self.transport.loseConnection()
+
+class EchoClientFactory(ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ print 'connection failed:', reason.getErrorMessage()
+ reactor.stop()
+
+ def clientConnectionLost(self, connector, reason):
+ print 'connection lost:', reason.getErrorMessage()
+ reactor.stop()
+
+def main():
+ factory = EchoClientFactory()
+ reactor.connectTCP('localhost', 8000, factory)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/echoclient_ssl.py b/vendor/Twisted-10.0.0/doc/core/examples/echoclient_ssl.py
new file mode 100755
index 0000000000..8921161312
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/echoclient_ssl.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from OpenSSL import SSL
+import sys
+
+from twisted.internet.protocol import ClientFactory
+from twisted.protocols.basic import LineReceiver
+from twisted.internet import ssl, reactor
+
+
+class EchoClient(LineReceiver):
+ end="Bye-bye!"
+ def connectionMade(self):
+ self.sendLine("Hello, world!")
+ self.sendLine("What a fine day it is.")
+ self.sendLine(self.end)
+
+ def connectionLost(self, reason):
+ print 'connection lost (protocol)'
+
+ def lineReceived(self, line):
+ print "receive:", line
+ if line==self.end:
+ self.transport.loseConnection()
+
+class EchoClientFactory(ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ print 'connection failed:', reason.getErrorMessage()
+ reactor.stop()
+
+ def clientConnectionLost(self, connector, reason):
+ print 'connection lost:', reason.getErrorMessage()
+ reactor.stop()
+
+def main():
+ factory = EchoClientFactory()
+ reactor.connectSSL('localhost', 8000, factory, ssl.ClientContextFactory())
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/echoclient_udp.py b/vendor/Twisted-10.0.0/doc/core/examples/echoclient_udp.py
new file mode 100644
index 0000000000..3233918105
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/echoclient_udp.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+class EchoClientDatagramProtocol(DatagramProtocol):
+ strings = [
+ "Hello, world!",
+ "What a fine day it is.",
+ "Bye-bye!"
+ ]
+
+ def startProtocol(self):
+ self.transport.connect('127.0.0.1', 8000)
+ self.sendDatagram()
+
+ def sendDatagram(self):
+ if len(self.strings):
+ datagram = self.strings.pop(0)
+ self.transport.write(datagram)
+ else:
+ reactor.stop()
+
+ def datagramReceived(self, datagram, host):
+ print 'Datagram received: ', repr(datagram)
+ self.sendDatagram()
+
+def main():
+ protocol = EchoClientDatagramProtocol()
+ t = reactor.listenUDP(0, protocol)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/echoserv.py b/vendor/Twisted-10.0.0/doc/core/examples/echoserv.py
new file mode 100644
index 0000000000..90b6891895
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/echoserv.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet.protocol import Protocol, Factory
+from twisted.internet import reactor
+
+### Protocol Implementation
+
+# This is just about the simplest possible protocol
+class Echo(Protocol):
+ def dataReceived(self, data):
+ """
+ As soon as any data is received, write it back.
+ """
+ self.transport.write(data)
+
+
+def main():
+ f = Factory()
+ f.protocol = Echo
+ reactor.listenTCP(8000, f)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/echoserv_ssl.py b/vendor/Twisted-10.0.0/doc/core/examples/echoserv_ssl.py
new file mode 100644
index 0000000000..c05b5f1e53
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/echoserv_ssl.py
@@ -0,0 +1,30 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from OpenSSL import SSL
+
+class ServerContextFactory:
+
+ def getContext(self):
+ """Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'."""
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file('server.pem')
+ ctx.use_privatekey_file('server.pem')
+ return ctx
+
+
+if __name__ == '__main__':
+ import echoserv, sys
+ from twisted.internet.protocol import Factory
+ from twisted.internet import ssl, reactor
+ from twisted.python import log
+ log.startLogging(sys.stdout)
+ factory = Factory()
+ factory.protocol = echoserv.Echo
+ reactor.listenSSL(8000, factory, ServerContextFactory())
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/echoserv_udp.py b/vendor/Twisted-10.0.0/doc/core/examples/echoserv_udp.py
new file mode 100644
index 0000000000..9171d66939
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/echoserv_udp.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+# Here's a UDP version of the simplest possible protocol
+class EchoUDP(DatagramProtocol):
+ def datagramReceived(self, datagram, address):
+ self.transport.write(datagram, address)
+
+def main():
+ reactor.listenUDP(8000, EchoUDP())
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/filewatch.py b/vendor/Twisted-10.0.0/doc/core/examples/filewatch.py
new file mode 100644
index 0000000000..87c36824cb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/filewatch.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.application import internet
+
+def watch(fp):
+ fp.seek(fp.tell())
+ for line in fp.readlines():
+ sys.stdout.write(line)
+
+import sys
+from twisted.internet import reactor
+s = internet.TimerService(0.1, watch, file(sys.argv[1]))
+s.startService()
+reactor.run()
+s.stopService()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/ftpclient.py b/vendor/Twisted-10.0.0/doc/core/examples/ftpclient.py
new file mode 100644
index 0000000000..c6de976097
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/ftpclient.py
@@ -0,0 +1,113 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of using the FTP client
+"""
+
+# Twisted imports
+from twisted.protocols.ftp import FTPClient, FTPFileListProtocol
+from twisted.internet.protocol import Protocol, ClientCreator
+from twisted.python import usage
+from twisted.internet import reactor
+
+# Standard library imports
+import string
+import sys
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+
+class BufferingProtocol(Protocol):
+ """Simple utility class that holds all data written to it in a buffer."""
+ def __init__(self):
+ self.buffer = StringIO()
+
+ def dataReceived(self, data):
+ self.buffer.write(data)
+
+# Define some callbacks
+
+def success(response):
+ print 'Success! Got response:'
+ print '---'
+ if response is None:
+ print None
+ else:
+ print string.join(response, '\n')
+ print '---'
+
+
+def fail(error):
+ print 'Failed. Error was:'
+ print error
+
+def showFiles(result, fileListProtocol):
+ print 'Processed file listing:'
+ for file in fileListProtocol.files:
+ print ' %s: %d bytes, %s' \
+ % (file['filename'], file['size'], file['date'])
+ print 'Total: %d files' % (len(fileListProtocol.files))
+
+def showBuffer(result, bufferProtocol):
+ print 'Got data:'
+ print bufferProtocol.buffer.getvalue()
+
+
+class Options(usage.Options):
+ optParameters = [['host', 'h', 'localhost'],
+ ['port', 'p', 21],
+ ['username', 'u', 'anonymous'],
+ ['password', None, 'twisted@'],
+ ['passive', None, 0],
+ ['debug', 'd', 1],
+ ]
+
+def run():
+ # Get config
+ config = Options()
+ config.parseOptions()
+ config.opts['port'] = int(config.opts['port'])
+ config.opts['passive'] = int(config.opts['passive'])
+ config.opts['debug'] = int(config.opts['debug'])
+
+ # Create the client
+ FTPClient.debug = config.opts['debug']
+ creator = ClientCreator(reactor, FTPClient, config.opts['username'],
+ config.opts['password'], passive=config.opts['passive'])
+ creator.connectTCP(config.opts['host'], config.opts['port']).addCallback(connectionMade).addErrback(connectionFailed)
+ reactor.run()
+
+def connectionFailed(f):
+ print "Connection Failed:", f
+ reactor.stop()
+
+def connectionMade(ftpClient):
+ # Get the current working directory
+ ftpClient.pwd().addCallbacks(success, fail)
+
+ # Get a detailed listing of the current directory
+ fileList = FTPFileListProtocol()
+ d = ftpClient.list('.', fileList)
+ d.addCallbacks(showFiles, fail, callbackArgs=(fileList,))
+
+ # Change to the parent directory
+ ftpClient.cdup().addCallbacks(success, fail)
+
+ # Create a buffer
+ proto = BufferingProtocol()
+
+ # Get short listing of current directory, and quit when done
+ d = ftpClient.nlst('.', proto)
+ d.addCallbacks(showBuffer, fail, callbackArgs=(proto,))
+ d.addCallback(lambda result: reactor.stop())
+
+
+# this only runs if the module was *not* imported
+if __name__ == '__main__':
+ run()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/ftpserver.py b/vendor/Twisted-10.0.0/doc/core/examples/ftpserver.py
new file mode 100644
index 0000000000..ec2d78fa8a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/ftpserver.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An example FTP server with minimal user authentication.
+"""
+
+from twisted.protocols.ftp import FTPFactory, FTPRealm
+from twisted.cred.portal import Portal
+from twisted.cred.checkers import AllowAnonymousAccess, FilePasswordDB
+from twisted.internet import reactor
+
+#
+# First, set up a portal (twisted.cred.portal.Portal). This will be used
+# to authenticate user logins, including anonymous logins.
+#
+# Part of this will be to establish the "realm" of the server - the most
+# important task in this case is to establish where anonymous users will
+# have default access to. In a real world scenario this would typically
+# point to something like '/pub' but for this example it is pointed at the
+# current working directory.
+#
+# The other important part of the portal setup is to point it to a list of
+# credential checkers. In this case, the first of these is used to grant
+# access to anonymous users and is relatively simple; the second is a very
+# primitive password checker. This example uses a plain text password file
+# that has one username:password pair per line. This checker *does* provide
+# a hashing interface, and one would normally want to use it instead of
+# plain text storage for anything remotely resembling a 'live' network. In
+# this case, the file "pass.dat" is used, and stored in the same directory
+# as the server. BAD.
+#
+# Create a pass.dat file which looks like this:
+#
+# =====================
+# jeff:bozo
+# grimmtooth:bozo2
+# =====================
+#
+p = Portal(FTPRealm('./'),
+ [AllowAnonymousAccess(), FilePasswordDB("pass.dat")])
+
+#
+# Once the portal is set up, start up the FTPFactory and pass the portal to
+# it on startup. FTPFactory will start up a twisted.protocols.ftp.FTP()
+# handler for each incoming OPEN request. Business as usual in Twisted land.
+#
+f = FTPFactory(p)
+
+#
+# You know this part. Point the reactor to port 21 coupled with the above factory,
+# and start the event loop.
+#
+reactor.listenTCP(21, f)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/gpsfix.py b/vendor/Twisted-10.0.0/doc/core/examples/gpsfix.py
new file mode 100644
index 0000000000..b1176a29f6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/gpsfix.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+GPSTest is a simple example using the SerialPort transport and the NMEA 0183
+and Rockwell Zodiac GPS protocols to display fix data as it is received from
+the device.
+"""
+from twisted.python import log, usage
+import sys
+
+if sys.platform == 'win32':
+ from twisted.internet import win32eventreactor
+ win32eventreactor.install()
+
+
+class GPSFixLogger:
+ def handle_fix(self, *args):
+ """
+ handle_fix gets called whenever either rockwell.Zodiac or nmea.NMEAReceiver
+ receives and decodes fix data. Generally, GPS receivers will report a
+ fix at 1hz. Implementing only this method is sufficient for most purposes
+ unless tracking of ground speed, course, utc date, or detailed satellite
+ information is necessary.
+
+ For example, plotting a map from MapQuest or a similar service only
+ requires longitude and latitude.
+ """
+ log.msg('fix:\n' +
+ '\n'.join(map(lambda n: ' %s = %s' % tuple(n), zip(('utc', 'lon', 'lat', 'fix', 'sat', 'hdp', 'alt', 'geo', 'dgp'), map(repr, args)))))
+
+class GPSOptions(usage.Options):
+ optFlags = [
+ ['zodiac', 'z', 'Use Rockwell Zodiac (DeLorme Earthmate) [default: NMEA 0183]'],
+ ]
+ optParameters = [
+ ['outfile', 'o', None, 'Logfile [default: sys.stdout]'],
+ ['baudrate', 'b', None, 'Serial baudrate [default: 4800 for NMEA, 9600 for Zodiac]'],
+ ['port', 'p', '/dev/ttyS0', 'Serial Port device'],
+ ]
+
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ from twisted.internet.serialport import SerialPort
+
+ o = GPSOptions()
+ try:
+ o.parseOptions()
+ except usage.UsageError, errortext:
+ print '%s: %s' % (sys.argv[0], errortext)
+ print '%s: Try --help for usage details.' % (sys.argv[0])
+ raise SystemExit, 1
+
+ logFile = o.opts['outfile']
+ if logFile is None:
+ logFile = sys.stdout
+ log.startLogging(logFile)
+
+ if o.opts['zodiac']:
+ from twisted.protocols.gps.rockwell import Zodiac as GPSProtocolBase
+ baudrate = 9600
+ else:
+ from twisted.protocols.gps.nmea import NMEAReceiver as GPSProtocolBase
+ baudrate = 4800
+ class GPSTest(GPSProtocolBase, GPSFixLogger):
+ pass
+
+ if o.opts['baudrate']:
+ baudrate = int(o.opts['baudrate'])
+
+
+ port = o.opts['port']
+ log.msg('Attempting to open %s at %dbps as a %s device' % (port, baudrate, GPSProtocolBase.__name__))
+ s = SerialPort(GPSTest(), o.opts['port'], reactor, baudrate=baudrate)
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/index.html b/vendor/Twisted-10.0.0/doc/core/examples/index.html
new file mode 100644
index 0000000000..94cb84a315
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/index.html
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">Simple Echo server and client</a></li><li><a href="#auto1">Chat</a></li><li><a href="#auto2">Echo server &amp; client variants</a></li><li><a href="#auto3">AMP server &amp; client variants</a></li><li><a href="#auto4">Perspective Broker</a></li><li><a href="#auto5">ROW (Twisted Enterprise)</a></li><li><a href="#auto6">Cred</a></li><li><a href="#auto7">GUI</a></li><li><a href="#auto8">FTP examples</a></li><li><a href="#auto9">Logging</a></li><li><a href="#auto10">Miscellaneous</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Simple Echo server and client<a name="auto0"/></h2>
+ <ul>
+ <li><a href="simpleclient.py" shape="rect">simpleclient.py</a> - simple TCP client</li>
+ <li><a href="simpleserv.py" shape="rect">simpleserv.py</a> - simple TCP echo server</li>
+ </ul>
+
+ <h2>Chat<a name="auto1"/></h2>
+ <ul>
+ <li><a href="chatserver.py" shape="rect">chatserver.py</a> - shows how to communicate between clients</li>
+ </ul>
+
+ <h2>Echo server &amp; client variants<a name="auto2"/></h2>
+ <ul>
+ <li><a href="echoserv.py" shape="rect">echoserv.py</a> - variant on a simple TCP echo server</li>
+ <li><a href="echoclient.py" shape="rect">echoclient.py</a> - variant on a simple TCP client</li>
+ <li><a href="echoserv_udp.py" shape="rect">echoserv_udp.py</a> - simplest possible
+ UDP server</li>
+ <li><a href="echoclient_udp.py" shape="rect">echoclient_udp.py</a> - simple UDP
+ client</li>
+ <li><a href="echoserv_ssl.py" shape="rect">echoserv_ssl.py</a> - simple SSL server</li>
+ <li><a href="echoclient_ssl.py" shape="rect">echoclient_ssl.py</a> - simple SSL client</li>
+ </ul>
+
+ <h2>AMP server &amp; client variants<a name="auto3"/></h2>
+ <ul>
+ <li><a href="ampserver.py" shape="rect">ampserver.py</a> - do math using AMP</li>
+ <li><a href="ampclient.py" shape="rect">ampclient.py</a> - do math using AMP</li>
+ </ul>
+
+ <h2>Perspective Broker<a name="auto4"/></h2>
+ <ul>
+ <li><a href="pbsimple.py" shape="rect">pbsimple.py</a> - simplest possible PB server</li>
+ <li><a href="pbsimpleclient.py" shape="rect">pbsimpleclient.py</a> - simplest possible PB
+ client</li>
+ <li><a href="pbbenchclient.py" shape="rect">pbbenchclient.py</a> - benchmarking client</li>
+ <li><a href="pbbenchserver.py" shape="rect">pbbenchserver.py</a> - benchmarking server</li>
+ <li><a href="pbecho.py" shape="rect">pbecho.py</a> - echo server that uses login</li>
+ <li><a href="pbechoclient.py" shape="rect">pbechoclient.py</a> - echo client using login</li>
+ <li><a href="pb_exceptions.py" shape="rect">pb_exceptions.py</a> - example of exceptions over PB</li>
+ <li><a href="pbgtk2.py" shape="rect">pbgtk2.py</a> - example of using GTK2 with PB</li>
+ <li><a href="pbinterop.py" shape="rect">pbinterop.py</a> - shows off various types supported by PB</li>
+ <li><a href="bananabench.py" shape="rect">bananabench.py</a> - benchmark for banana</li>
+ </ul>
+
+ <h2>ROW (Twisted Enterprise)<a name="auto5"/></h2>
+ <ul>
+ <li><a href="row_example.py" shape="rect">row_example.py</a> - using twisted.enterpise.row to load objects
+ from a database and manipulate them.</li>
+ <li><a href="row_schema.sql" shape="rect">row_schema.sql</a> - sample statements to populate tables for
+ row_example.py</li>
+ <li><a href="row_util.py" shape="rect">row_util.py</a> - definitions of row classes for
+ row_example.py</li>
+ </ul>
+
+ <h2>Cred<a name="auto6"/></h2>
+ <ul>
+ <li><a href="cred.py" shape="rect">cred.py</a> - Authenticate a user with an in-memory username/password
+ database</li>
+ <li><a href="dbcred.py" shape="rect">dbcred.py</a> - Using a database backend to authenticate a user</li>
+ </ul>
+
+ <h2>GUI<a name="auto7"/></h2>
+ <ul>
+ <li><a href="wxdemo.py" shape="rect">wxdemo.py</a> - demo of wxPython integration with Twisted</li>
+ <li><a href="pbgtk2.py" shape="rect">pbgtk2.py</a> - example of using GTK2 with PB</li>
+ <li><a href="pyuidemo.py" shape="rect">pyuidemo.py</a> - PyUI</li>
+ </ul>
+
+ <h2>FTP examples<a name="auto8"/></h2>
+ <ul>
+ <li><a href="ftpclient.py" shape="rect">ftpclient.py</a> - example of using the FTP client</li>
+ <li><a href="ftpserver.py" shape="rect">ftpserver.py</a> - create an FTP server which
+ serves files for anonymous users from the working directory and serves
+ files for authenticated users from <code class="shell">/home</code>.</li>
+ </ul>
+
+ <h2>Logging<a name="auto9"/></h2>
+ <ul>
+ <li><a href="twistd-logging.tac" shape="rect">twistd-logging.tac</a> - logging example using
+ ILogObserver</li>
+ <li><a href="testlogging.py" shape="rect">testlogging.py</a> - use twisted.python.log to log errors to
+ standard out</li>
+ <li><a href="rotatinglog.py" shape="rect">rotatinglog.py</a> - example of log file rotation</li>
+ </ul>
+
+ <h2>Miscellaneous<a name="auto10"/></h2>
+ <ul>
+ <li><a href="shaper.py" shape="rect">shaper.py</a> - example of rate-limiting your web server</li>
+ <li><a href="stdiodemo.py" shape="rect">stdiodemo.py</a> - example using stdio, Deferreds, LineReceiver
+ and twisted.web.client.</li>
+ <li><a href="mouse.py" shape="rect">mouse.py</a> - example using MouseMan protocol with the SerialPort
+ transport</li>
+ <li><a href="ptyserv.py" shape="rect">ptyserv.py</a> - serve shells in pseudo-terminals over TCP</li>
+ <li><a href="courier.py" shape="rect">courier.py</a> - example of interfacing to Courier's mail filter
+ interface</li>
+ <li><a href="longex.py" shape="rect">longex.py</a> - example of doing arbitarily long calculations nicely
+ in Twisted</li>
+ <li><a href="longex2.py" shape="rect">longex2.py</a> - using generators to do long calculations</li>
+ <li><a href="stdin.py" shape="rect">stdin.py</a> - reading a line at a time from standard input
+ without blocking the reactor.</li>
+ <li><a href="filewatch.py" shape="rect">filewatch.py</a> - write the content of a file to standard out
+ one line at a time</li>
+ <li><a href="shoutcast.py" shape="rect">shoutcast.py</a> - example Shoutcast client</li>
+ <li><a href="gpsfix.py" shape="rect">gpsfix.py</a> - example using the SerialPort transport and GPS
+ protocols to display fix data as it is received from the device</li>
+ <li><a href="wxacceptance.py" shape="rect">wxacceptance.py</a> - acceptance tests for wxreactor</li>
+ <li><a href="postfix.py" shape="rect">postfix.py</a> - test application for PostfixTCPMapServer</li>
+ </ul>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/longex.py b/vendor/Twisted-10.0.0/doc/core/examples/longex.py
new file mode 100644
index 0000000000..6fc9a7fe05
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/longex.py
@@ -0,0 +1,66 @@
+"""Simple example of doing arbitarily long calculations nicely in Twisted.
+
+This is also a simple demonstration of twisted.protocols.basic.LineReceiver.
+"""
+
+from twisted.protocols import basic
+from twisted.internet import reactor
+from twisted.internet.protocol import ServerFactory
+
+class LongMultiplicationProtocol(basic.LineReceiver):
+ """A protocol for doing long multiplications.
+
+ It receives a list of numbers (seperated by whitespace) on a line, and
+ writes back the answer. The answer is calculated in chunks, so no one
+ calculation should block for long enough to matter.
+ """
+ def connectionMade(self):
+ self.workQueue = []
+
+ def lineReceived(self, line):
+ try:
+ numbers = map(long, line.split())
+ except ValueError:
+ self.sendLine('Error.')
+ return
+
+ if len(numbers) <= 1:
+ self.sendLine('Error.')
+ return
+
+ self.workQueue.append(numbers)
+ reactor.callLater(0, self.calcChunk)
+
+ def calcChunk(self):
+ # Make sure there's some work left; when multiple lines are received
+ # while processing is going on, multiple calls to reactor.callLater()
+ # can happen between calls to calcChunk().
+ if self.workQueue:
+ # Get the first bit of work off the queue
+ work = self.workQueue[0]
+
+ # Do a chunk of work: [a, b, c, ...] -> [a*b, c, ...]
+ work[:2] = [work[0] * work[1]]
+
+ # If this piece of work now has only one element, send it.
+ if len(work) == 1:
+ self.sendLine(str(work[0]))
+ del self.workQueue[0]
+
+ # Schedule this function to do more work, if there's still work
+ # to be done.
+ if self.workQueue:
+ reactor.callLater(0, self.calcChunk)
+
+
+class LongMultiplicationFactory(ServerFactory):
+ protocol = LongMultiplicationProtocol
+
+
+if __name__ == '__main__':
+ from twisted.python import log
+ import sys
+ log.startLogging(sys.stdout)
+ reactor.listenTCP(1234, LongMultiplicationFactory())
+ reactor.run()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/longex2.py b/vendor/Twisted-10.0.0/doc/core/examples/longex2.py
new file mode 100644
index 0000000000..87589888e2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/longex2.py
@@ -0,0 +1,101 @@
+"""Example of doing arbitarily long calculations nicely in Twisted.
+
+This is also a simple demonstration of twisted.protocols.basic.LineReceiver.
+This example uses generators to do the calculation. It also tries to be
+a good example in division of responsibilities:
+- The protocol handles the wire layer, reading in lists of numbers
+ and writing out the result.
+- The factory decides on policy, and has relatively little knowledge
+ of the details of the protocol. Other protocols can use the same
+ factory class by intantiating and setting .protocol
+- The factory does little job itself: it is mostly a policy maker.
+ The 'smarts' are in free-standing functions which are written
+ for flexibility.
+
+The goal is for minimal dependencies:
+- You can use runIterator to run any iterator inside the Twisted
+ main loop.
+- You can use multiply whenever you need some way of multiplying
+ numbers such that the multiplications will happen asynchronously,
+ but it is your responsibility to schedule the multiplications.
+- You can use the protocol with other factories to implement other
+ functions that apply to arbitrary lists of longs.
+- You can use the factory with other protocols for support of legacy
+ protocols. In fact, the factory does not even have to be used as
+ a protocol factory. Here are easy ways to support the operation
+ over XML-RPC and PB.
+
+class Multiply(xmlrpc.XMLRPC):
+ def __init__(self): self.factory = Multiplication()
+ def xmlrpc_multiply(self, *numbers):
+ return self.factory.calc(map(long, numbers))
+
+class Multiply(pb.Referencable):
+ def __init__(self): self.factory = Multiplication()
+ def remote_multiply(self, *numbers):
+ return self.factory.calc(map(long, numbers))
+
+Note:
+Multiplying zero numbers is a perfectly sensible operation, and the
+result is 1. In that, this example departs from doc/examples/longex.py,
+which errors out when trying to do this.
+"""
+from __future__ import generators
+from twisted.protocols import basic
+from twisted.internet import defer, protocol
+
+def runIterator(reactor, iterator):
+ try:
+ iterator.next()
+ except StopIteration:
+ pass
+ else:
+ reactor.callLater(0, runIterator, reactor, iterator)
+
+def multiply(numbers):
+ d = defer.Deferred()
+ def _():
+ acc = 1
+ while numbers:
+ acc *= numbers.pop()
+ yield None
+ d.callback(acc)
+ return d, _()
+
+class Numbers(basic.LineReceiver):
+ """Protocol for reading lists of numbers and manipulating them.
+
+ It receives a list of numbers (seperated by whitespace) on a line, and
+ writes back the answer. The exact algorithm to use depends on the
+ factory. It should return an str-able Deferred.
+ """
+ def lineReceived(self, line):
+ try:
+ numbers = map(long, line.split())
+ except ValueError:
+ self.sendLine('Error.')
+ return
+ deferred = self.factory.calc(numbers)
+ deferred.addCallback(str)
+ deferred.addCallback(self.sendLine)
+
+class Multiplication(protocol.ServerFactory):
+ """Factory for multiplying numbers.
+
+ It provides a function which calculates the multiplication
+ of a list of numbers. The function destroys its input.
+ Note that instances of this factory can use other formats
+ for transmitting the number lists, as long as they set
+ correct protoocl values.
+ """
+ protocol = Numbers
+ def calc(self, numbers):
+ deferred, iterator = multiply(numbers)
+ from twisted.internet import reactor
+ runIterator(reactor, iterator)
+ return deferred
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ reactor.listenTCP(1234, Multiplication())
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/mouse.py b/vendor/Twisted-10.0.0/doc/core/examples/mouse.py
new file mode 100755
index 0000000000..ee5b3927d2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/mouse.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example using MouseMan protocol with the SerialPort transport.
+"""
+
+# TODO set tty modes, etc.
+# This works for me:
+
+# speed 1200 baud; rows 0; columns 0; line = 0;
+# intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D;
+# eol = <undef>; eol2 = <undef>; start = ^Q; stop = ^S; susp = ^Z;
+# rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
+# -parenb -parodd cs7 hupcl -cstopb cread clocal -crtscts ignbrk
+# -brkint ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon
+# -ixoff -iuclc -ixany -imaxbel -opost -olcuc -ocrnl -onlcr -onocr
+# -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 -isig -icanon -iexten
+# -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl
+# -echoke
+
+import sys
+from twisted.python import usage, log
+from twisted.protocols.mice import mouseman
+
+if sys.platform == 'win32':
+ # win32 serial does not work yet!
+ raise NotImplementedError, "The SerialPort transport does not currently support Win32"
+ from twisted.internet import win32eventreactor
+ win32eventreactor.install()
+
+class Options(usage.Options):
+ optParameters = [
+ ['port', 'p', '/dev/mouse', 'Device for serial mouse'],
+ ['baudrate', 'b', '1200', 'Baudrate for serial mouse'],
+ ['outfile', 'o', None, 'Logfile [default: sys.stdout]'],
+ ]
+
+class McFooMouse(mouseman.MouseMan):
+ def down_left(self):
+ log.msg("LEFT")
+
+ def up_left(self):
+ log.msg("left")
+
+ def down_middle(self):
+ log.msg("MIDDLE")
+
+ def up_middle(self):
+ log.msg("middle")
+
+ def down_right(self):
+ log.msg("RIGHT")
+
+ def up_right(self):
+ log.msg("right")
+
+ def move(self, x, y):
+ log.msg("(%d,%d)" % (x, y))
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ from twisted.internet.serialport import SerialPort
+ o = Options()
+ try:
+ o.parseOptions()
+ except usage.UsageError, errortext:
+ print "%s: %s" % (sys.argv[0], errortext)
+ print "%s: Try --help for usage details." % (sys.argv[0])
+ raise SystemExit, 1
+
+ logFile = sys.stdout
+ if o.opts['outfile']:
+ logFile = o.opts['outfile']
+ log.startLogging(logFile)
+
+ SerialPort(McFooMouse(), o.opts['port'], reactor, baudrate=int(o.opts['baudrate']))
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pb_exceptions.py b/vendor/Twisted-10.0.0/doc/core/examples/pb_exceptions.py
new file mode 100644
index 0000000000..00753a4816
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pb_exceptions.py
@@ -0,0 +1,36 @@
+
+from twisted.python import util
+from twisted.spread import pb
+from twisted.cred import portal, checkers, credentials
+
+class Avatar(pb.Avatar):
+ def perspective_exception(self, x):
+ return x / 0
+
+class Realm:
+ def requestAvatar(self, interface, mind, *interfaces):
+ if pb.IPerspective in interfaces:
+ return pb.IPerspective, Avatar(), lambda: None
+
+def cbLogin(avatar):
+ avatar.callRemote("exception", 10).addCallback(str).addCallback(util.println)
+
+def ebLogin(failure):
+ print failure
+
+def main():
+ c = checkers.InMemoryUsernamePasswordDatabaseDontUse(user="pass")
+ p = portal.Portal(Realm(), [c])
+ server = pb.PBServerFactory(p)
+ server.unsafeTracebacks = True
+ client = pb.PBClientFactory()
+ login = client.login(credentials.UsernamePassword("user", "pass"))
+ login.addCallback(cbLogin).addErrback(ebLogin).addBoth(lambda: reactor.stop())
+
+ from twisted.internet import reactor
+ p = reactor.listenTCP(0, server)
+ c = reactor.connectTCP('127.0.0.1', p.getHost().port, client)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pbbenchclient.py b/vendor/Twisted-10.0.0/doc/core/examples/pbbenchclient.py
new file mode 100644
index 0000000000..9cd2b317b9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pbbenchclient.py
@@ -0,0 +1,42 @@
+
+from twisted.spread import pb
+from twisted.internet import defer, reactor
+from twisted.cred.credentials import UsernamePassword
+import time
+
+class PBBenchClient:
+ hostname = 'localhost'
+ portno = pb.portno
+ calledThisSecond = 0
+
+ def callLoop(self, ignored):
+ d1 = self.persp.callRemote("simple")
+ d2 = self.persp.callRemote("complexTypes")
+ defer.DeferredList([d1, d2]).addCallback(self.callLoop)
+ self.calledThisSecond += 1
+ thisSecond = int(time.time())
+ if thisSecond != self.lastSecond:
+ if thisSecond - self.lastSecond > 1:
+ print "WARNING it took more than one second"
+ print 'cps:', self.calledThisSecond
+ self.calledThisSecond = 0
+ self.lastSecond = thisSecond
+
+ def _cbPerspective(self, persp):
+ self.persp = persp
+ self.lastSecond = int(time.time())
+ self.callLoop(None)
+
+ def runTest(self):
+ factory = pb.PBClientFactory()
+ reactor.connectTCP(self.hostname, self.portno, factory)
+ factory.login(UsernamePassword("benchmark", "benchmark")).addCallback(self._cbPerspective)
+
+
+def main():
+ PBBenchClient().runTest()
+ from twisted.internet import reactor
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pbbenchserver.py b/vendor/Twisted-10.0.0/doc/core/examples/pbbenchserver.py
new file mode 100644
index 0000000000..c38726b805
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pbbenchserver.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Server for PB benchmark."""
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred.portal import IRealm
+
+class PBBenchPerspective(pb.Avatar):
+ callsPerSec = 0
+ def __init__(self):
+ pass
+
+ def perspective_simple(self):
+ self.callsPerSec = self.callsPerSec + 1
+ return None
+
+ def printCallsPerSec(self):
+ print '(s) cps:', self.callsPerSec
+ self.callsPerSec = 0
+ reactor.callLater(1, self.printCallsPerSec)
+
+ def perspective_complexTypes(self):
+ return ['a', 1, 1l, 1.0, [], ()]
+
+
+class SimpleRealm:
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective in interfaces:
+ p = PBBenchPerspective()
+ p.printCallsPerSec()
+ return pb.IPerspective, p, lambda : None
+ else:
+ raise NotImplementedError("no interface")
+
+
+def main():
+ from twisted.cred.portal import Portal
+ from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+ portal = Portal(SimpleRealm())
+ checker = InMemoryUsernamePasswordDatabaseDontUse()
+ checker.addUser("benchmark", "benchmark")
+ portal.registerChecker(checker)
+ reactor.listenTCP(8787, pb.PBServerFactory(portal))
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pbecho.py b/vendor/Twisted-10.0.0/doc/core/examples/pbecho.py
new file mode 100644
index 0000000000..f10428da22
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pbecho.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+if __name__ == '__main__':
+ # Avoid using any names defined in the "__main__" module.
+ from pbecho import main
+ raise SystemExit(main())
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.cred.portal import IRealm
+
+class DefinedError(pb.Error):
+ pass
+
+
+class SimplePerspective(pb.Avatar):
+
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+
+ def perspective_error(self):
+ raise DefinedError("exception!")
+
+ def logout(self):
+ print self, "logged out"
+
+
+class SimpleRealm:
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective in interfaces:
+ avatar = SimplePerspective()
+ return pb.IPerspective, avatar, avatar.logout
+ else:
+ raise NotImplementedError("no interface")
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.cred.portal import Portal
+ from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+ portal = Portal(SimpleRealm())
+ checker = InMemoryUsernamePasswordDatabaseDontUse()
+ checker.addUser("guest", "guest")
+ portal.registerChecker(checker)
+ reactor.listenTCP(pb.portno, pb.PBServerFactory(portal))
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pbechoclient.py b/vendor/Twisted-10.0.0/doc/core/examples/pbechoclient.py
new file mode 100644
index 0000000000..5d5eff6248
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pbechoclient.py
@@ -0,0 +1,32 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import reactor
+from twisted.spread import pb
+from twisted.cred.credentials import UsernamePassword
+
+from pbecho import DefinedError
+
+def success(message):
+ print "Message received:",message
+ # reactor.stop()
+
+def failure(error):
+ t = error.trap(DefinedError)
+ print "error received:", t
+ reactor.stop()
+
+def connected(perspective):
+ perspective.callRemote('echo', "hello world").addCallbacks(success, failure)
+ perspective.callRemote('error').addCallbacks(success, failure)
+ print "connected."
+
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("localhost", pb.portno, factory)
+factory.login(
+ UsernamePassword("guest", "guest")).addCallbacks(connected, failure)
+
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pbgtk2.py b/vendor/Twisted-10.0.0/doc/core/examples/pbgtk2.py
new file mode 100644
index 0000000000..8c4590e737
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pbgtk2.py
@@ -0,0 +1,122 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from __future__ import nested_scopes
+
+from twisted.internet import gtk2reactor
+gtk2reactor.install()
+
+import gtk
+from gtk import glade
+from twisted import copyright
+from twisted.internet import reactor, defer
+from twisted.python import failure, log, util
+from twisted.spread import pb
+from twisted.cred.credentials import UsernamePassword
+from twisted.internet import error as netError
+
+
+class LoginDialog:
+ def __init__(self, deferred):
+ self.deferredResult = deferred
+
+ gladefile = util.sibpath(__file__, "pbgtk2login.glade")
+ self.glade = glade.XML(gladefile)
+
+ self.glade.signal_autoconnect(self)
+
+ self.setWidgetsFromGladefile()
+ self._loginDialog.show()
+
+ def setWidgetsFromGladefile(self):
+ widgets = ("hostEntry", "portEntry", "userNameEntry", "passwordEntry",
+ "statusBar", "loginDialog")
+ gw = self.glade.get_widget
+ for widgetName in widgets:
+ setattr(self, "_" + widgetName, gw(widgetName))
+
+ self._statusContext = self._statusBar.get_context_id("Login dialog.")
+
+ def on_loginDialog_response(self, widget, response):
+ handlers = {gtk.RESPONSE_NONE: self.windowClosed,
+ gtk.RESPONSE_DELETE_EVENT: self.windowClosed,
+ gtk.RESPONSE_OK: self.doLogin,
+ gtk.RESPONSE_CANCEL: self.cancelled}
+ handlers.get(response)()
+
+ def on_loginDialog_close(self, widget, userdata=None):
+ self.windowClosed()
+
+ def cancelled(self):
+ if not self.deferredResult.called:
+ self.deferredResult.errback()
+ self._loginDialog.destroy()
+
+ def windowClosed(self, reason=None):
+ if not self.deferredResult.called:
+ self.deferredResult.errback()
+
+ def doLogin(self):
+ host = self._hostEntry.get_text()
+ port = int(self._portEntry.get_text())
+ userName = self._userNameEntry.get_text()
+ password = self._passwordEntry.get_text()
+
+ client_factory = pb.PBClientFactory()
+ reactor.connectTCP(host, port, client_factory)
+ creds = UsernamePassword(userName, password)
+ client_factory.login(creds).addCallbacks(self._cbGotPerspective, self._ebFailedLogin)
+
+ self.statusMsg("Contacting server...")
+
+ def _cbGotPerspective(self, perspective):
+ self.statusMsg("Connected to server.")
+ self.deferredResult.callback(perspective)
+ self._loginDialog.destroy()
+
+ def _ebFailedLogin(self, reason):
+ if isinstance(reason, failure.Failure):
+ text = str(reason.value)
+ else:
+ text = str(reason)
+
+ self.statusMsg(text)
+ msg = gtk.MessageDialog(self._loginDialog,
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_ERROR,
+ gtk.BUTTONS_CLOSE,
+ text)
+ msg.show_all()
+ msg.connect("response", lambda *a: msg.destroy())
+
+ def statusMsg(self, text):
+ self._statusBar.push(self._statusContext, text)
+
+
+class EchoClient:
+ def __init__(self, echoer):
+ self.echoer = echoer
+ w = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.VBox(); b = gtk.Button("Echo:")
+ self.entry = gtk.Entry(); self.outry = gtk.Entry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', self.stop)
+ w.show_all()
+
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.callRemote('echo',txt).addCallback(self.outry.set_text)
+
+ def stop(self, b):
+ reactor.stop()
+
+d = defer.Deferred()
+LoginDialog(d)
+d.addCallbacks(EchoClient,
+ lambda _: reactor.stop())
+
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pbgtk2login.glade b/vendor/Twisted-10.0.0/doc/core/examples/pbgtk2login.glade
new file mode 100644
index 0000000000..6b5eb01e6d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pbgtk2login.glade
@@ -0,0 +1,330 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="loginDialog">
+ <property name="title" translatable="yes">Login</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="has_separator">True</property>
+ <signal name="response" handler="on_loginDialog_response" last_modification_time="Sun, 21 Sep 2003 05:27:45 GMT"/>
+ <signal name="close" handler="on_loginDialog_close" last_modification_time="Sun, 21 Sep 2003 05:27:49 GMT"/>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="cancelbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="loginButton">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="response_id">-5</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-ok</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Login</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkStatusbar" id="statusBar">
+ <property name="visible">True</property>
+ <property name="has_resize_grip">False</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="hostLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Host:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.9</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">hostEntry</property>
+ <accessibility>
+ <atkrelation target="hostEntry" type="label-for"/>
+ <atkrelation target="portEntry" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkEntry" id="hostEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">The name of a host to connect to.</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">localhost</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ <accessibility>
+ <atkrelation target="hostLabel" type="labelled-by"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="portEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">The number of a port to connect on.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">8787</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">5</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="nameLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Name:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.9</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">userNameEntry</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="userNameEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">An identity to log in as.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">guest</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="passwordEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">The Identity's log-in password.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">False</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">guest</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="passwordLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Password:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.9</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">passwordEntry</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pbinterop.py b/vendor/Twisted-10.0.0/doc/core/examples/pbinterop.py
new file mode 100644
index 0000000000..de59632f3b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pbinterop.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""PB interop server."""
+
+from twisted.spread import pb, jelly, flavors
+from twisted.internet import reactor
+
+
+class Interop(pb.Root):
+ """Test object for PB interop tests."""
+
+ def __init__(self):
+ self.o = pb.Referenceable()
+
+ def remote_int(self):
+ return 1
+
+ def remote_string(self):
+ return "string"
+
+ def remote_unicode(self):
+ return u"string"
+
+ def remote_float(self):
+ return 1.5
+
+ def remote_list(self):
+ return [1, 2, 3]
+
+ def remote_recursive(self):
+ l = []
+ l.append(l)
+ return l
+
+ def remote_dict(self):
+ return {1 : 2}
+
+ def remote_reference(self):
+ return self.o
+
+ def remote_local(self, obj):
+ d = obj.callRemote("hello")
+ d.addCallback(self._local_success)
+
+ def _local_success(self, result):
+ if result != "hello, world":
+ raise ValueError, "%r != %r" % (result, "hello, world")
+
+ def remote_receive(self, obj):
+ expected = [1, 1.5, "hi", u"hi", {1 : 2}]
+ if obj != expected:
+ raise ValueError, "%r != %r" % (obj, expected)
+
+ def remote_self(self, obj):
+ if obj != self:
+ raise ValueError, "%r != %r" % (obj, self)
+
+ def remote_copy(self, x):
+ o = flavors.Copyable()
+ o.x = x
+ return o
+
+
+if __name__ == '__main__':
+ reactor.listenTCP(8789, pb.PBServerFactory(Interop()))
+ reactor.run()
+
+
+
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pbsimple.py b/vendor/Twisted-10.0.0/doc/core/examples/pbsimple.py
new file mode 100644
index 0000000000..7c3d9f442c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pbsimple.py
@@ -0,0 +1,16 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Echoer(pb.Root):
+ def remote_echo(self, st):
+ print 'echoing:', st
+ return st
+
+if __name__ == '__main__':
+ reactor.listenTCP(8789, pb.PBServerFactory(Echoer()))
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pbsimpleclient.py b/vendor/Twisted-10.0.0/doc/core/examples/pbsimpleclient.py
new file mode 100644
index 0000000000..91c1be6a78
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pbsimpleclient.py
@@ -0,0 +1,18 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.python import util
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("localhost", 8789, factory)
+d = factory.getRootObject()
+d.addCallback(lambda object: object.callRemote("echo", "hello network"))
+d.addCallback(lambda echo: 'server echoed: '+echo)
+d.addErrback(lambda reason: 'error: '+str(reason.value))
+d.addCallback(util.println)
+d.addCallback(lambda _: reactor.stop())
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/postfix.py b/vendor/Twisted-10.0.0/doc/core/examples/postfix.py
new file mode 100644
index 0000000000..edb77e4a6b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/postfix.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test app for PostfixTCPMapServer.
+
+Call with parameters KEY1=VAL1 KEY2=VAL2 ...
+"""
+
+import sys
+
+from twisted.internet import reactor
+from twisted.protocols import postfix
+from twisted.python import log
+
+log.startLogging(sys.stdout)
+
+d = {}
+for arg in sys.argv[1:]:
+ try:
+ k,v = arg.split('=', 1)
+ except ValueError:
+ k = arg
+ v = ''
+ d[k] = v
+
+f = postfix.PostfixTCPMapDictServerFactory(d)
+port = reactor.listenTCP(4242, f, interface='127.0.0.1')
+reactor.run() \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/ptyserv.py b/vendor/Twisted-10.0.0/doc/core/examples/ptyserv.py
new file mode 100644
index 0000000000..becbfd230c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/ptyserv.py
@@ -0,0 +1,32 @@
+from twisted.internet import reactor, protocol
+
+class FakeTelnet(protocol.Protocol):
+ commandToRun = ['/bin/sh'] # could have args too
+ dirToRunIn = '/tmp'
+ def connectionMade(self):
+ print 'connection made'
+ self.propro = ProcessProtocol(self)
+ reactor.spawnProcess(self.propro, self.commandToRun[0], self.commandToRun, {},
+ self.dirToRunIn, usePTY=1)
+ def dataReceived(self, data):
+ self.propro.transport.write(data)
+ def conectionLost(self):
+ print 'connection lost'
+ self.propro.tranport.loseConnection()
+
+class ProcessProtocol(protocol.ProcessProtocol):
+
+ def __init__(self, pr):
+ self.pr = pr
+
+ def outReceived(self, data):
+ self.pr.transport.write(data)
+
+ def processEnded(self, reason):
+ print 'protocol conection lost'
+ self.pr.transport.loseConnection()
+
+f = protocol.Factory()
+f.protocol = FakeTelnet
+reactor.listenTCP(5823, f)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pyui_bg.png b/vendor/Twisted-10.0.0/doc/core/examples/pyui_bg.png
new file mode 100644
index 0000000000..08d45ec0a6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pyui_bg.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/pyuidemo.py b/vendor/Twisted-10.0.0/doc/core/examples/pyuidemo.py
new file mode 100755
index 0000000000..68ec79d151
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/pyuidemo.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import pyui
+from twisted.internet import reactor, pyuisupport
+
+def onButton(self):
+ print "got a button"
+
+def onQuit(self):
+ reactor.stop()
+
+def main():
+ pyuisupport.install(args=(640, 480), kw={'renderer': '2d'})
+
+ w = pyui.widgets.Frame(50, 50, 400, 400, "clipme")
+ b = pyui.widgets.Button("A button is here", onButton)
+ q = pyui.widgets.Button("Quit!", onQuit)
+
+ w.addChild(b)
+ w.addChild(q)
+ w.pack()
+
+ w.setBackImage("pyui_bg.png")
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/rotatinglog.py b/vendor/Twisted-10.0.0/doc/core/examples/rotatinglog.py
new file mode 100644
index 0000000000..baacd44525
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/rotatinglog.py
@@ -0,0 +1,26 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of using the rotating log.
+"""
+
+from twisted.python import log
+from twisted.python import logfile
+
+# rotate every 100 bytes
+f = logfile.LogFile("test.log", "/tmp", rotateLength=100)
+
+# setup logging to use our new logfile
+log.startLogging(f)
+
+# print a few message
+for i in range(10):
+ log.msg("this is a test of the logfile: %s" % i)
+
+# rotate the logfile manually
+f.rotate()
+
+log.msg("goodbye")
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/row_example.py b/vendor/Twisted-10.0.0/doc/core/examples/row_example.py
new file mode 100644
index 0000000000..357ba27754
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/row_example.py
@@ -0,0 +1,105 @@
+import random
+
+from twisted.internet import reactor
+
+from twisted.enterprise import adbapi, row, reflector, sqlreflector
+
+from row_util import *
+
+""" This example show using twisted.enterpise.row to load objects from
+a database and manipulate them.
+"""
+
+manager = None
+
+def gotRooms(rooms):
+ print "got Rooms.", rooms
+ if not rooms:
+ print "no rooms found!"
+ reactor.stop()
+
+ for room in rooms:
+ print "room ", room
+ for child in room.furniture:
+ print "furn ", child
+ if hasattr(child, "childRows"):
+ for inner in child.childRows:
+ print "inner ", inner
+
+ room.moveTo( int(random.random() * 100) , int(random.random() * 100) )
+ manager.updateRow(room).addCallback(onUpdate)
+
+def gotFurniture(furniture):
+ for f in furniture:
+ print f
+ reactor.stop()
+
+def onUpdate(data):
+ print "updated row."
+ # create a new room
+ global newRoom
+ newRoom = RoomRow()
+ newRoom.assignKeyAttr("roomId", kf.getNextKey())
+ newRoom.town_id = 20
+ newRoom.name = 'newRoom1'
+ newRoom.owner = 'fred'
+ newRoom.posx = 100
+ newRoom.posy = 100
+ newRoom.width = 15
+ newRoom.height = 20
+
+ #insert row into database
+ manager.insertRow(newRoom).addCallback(onInsert)
+
+def onInsert(data):
+ global newRoom
+ print "row inserted"
+ print newRoom.roomId
+ manager.deleteRow(newRoom).addCallback(onDelete)
+
+def onDelete(data):
+ print "row deleted."
+ return manager.loadObjectsFrom("furniture", whereClause=[("furnId",reflector.EQUAL,53)], forceChildren=1 ).addCallback(onSelected)
+
+def onSelected(furn):
+ for f in furn:
+ print "\ngot Furn:", f
+ if hasattr(f, "childRows"):
+ for l in f.childRows:
+ print " ", l
+ reactor.stop()
+
+def gotRooms2(rooms):
+ print "got more rooms", rooms
+ reactor.stop()
+
+def tick():
+ reactor.callLater(0.5, tick)
+
+newRoom = None
+
+
+# use this line for postgresql test
+dbpool = adbapi.ConnectionPool("pyPgSQL.PgSQL", database="test")
+
+# use this line for SQLite test
+#dbpool = adbapi.ConnectionPool("sqlite", db="test")
+
+# use this line for Interbase / Firebird
+#dbpool = adbapi.ConnectionPool("kinterbasdb", dsn="localhost:/test.gdb",user="SYSDBA",password="masterkey")
+
+# use this for MySQL
+#dbpool = adbapi.ConnectionPool("MySQLdb", db="test", passwd="pass")
+
+
+def kickOffTests(ignoredResult=0):
+ global manager
+ manager = sqlreflector.SQLReflector(dbpool, [RoomRow, FurnitureRow, RugRow, LampRow])
+ manager.loadObjectsFrom("testrooms", forceChildren=1).addCallback(gotRooms)
+
+kf = KeyFactory(100000, 50000)
+
+# make sure we can be shut down on windows.
+reactor.callLater(0.5, tick)
+reactor.callLater(0.4, kickOffTests)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/row_schema.sql b/vendor/Twisted-10.0.0/doc/core/examples/row_schema.sql
new file mode 100644
index 0000000000..a545b5eb8b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/row_schema.sql
@@ -0,0 +1,65 @@
+DROP TABLE testrooms;
+DROP TABLE furniture;
+DROP TABLE rugs;
+DROP TABLE lamps;
+
+CREATE TABLE testrooms
+(
+ roomId int PRIMARY KEY,
+ town_id int,
+ name varchar(64),
+ owner varchar(64),
+ posx int,
+ posy int,
+ width int,
+ height int
+);
+
+CREATE TABLE furniture
+(
+ furnId int PRIMARY KEY,
+ roomId int,
+ name varchar(64),
+ posx int,
+ posy int
+);
+
+CREATE TABLE rugs
+(
+ rugId int PRIMARY KEY,
+ roomId int,
+ name varchar(64)
+);
+
+CREATE TABLE lamps
+(
+ lampId int PRIMARY KEY,
+ furnId int,
+ furnName varchar(64),
+ lampName varchar(64)
+);
+
+
+INSERT INTO testrooms VALUES (10, 100, 'testroom1', 'someguy', 10, 10, 20, 20);
+INSERT INTO testrooms VALUES (11, 100, 'testroom2', 'someguy', 30, 10, 20, 20);
+INSERT INTO testrooms VALUES (12, 100, 'testroom3', 'someguy', 50, 10, 20, 20);
+
+INSERT INTO furniture VALUES (50, 10, 'chair1', 10, 10);
+INSERT INTO furniture VALUES (51, 10, 'chair2', 14, 10);
+INSERT INTO furniture VALUES (52, 12, 'chair3', 14, 10);
+INSERT INTO furniture VALUES (53, 12, 'chair4', 10, 12);
+INSERT INTO furniture VALUES (54, 12, 'chair5', 18, 13);
+INSERT INTO furniture VALUES (55, 12, 'couch', 22, 3);
+
+INSERT INTO rugs VALUES (81, 10, 'a big rug');
+INSERT INTO rugs VALUES (82, 10, 'a blue rug');
+INSERT INTO rugs VALUES (83, 11, 'a red rug');
+INSERT INTO rugs VALUES (84, 11, 'a green rug');
+INSERT INTO rugs VALUES (85, 12, 'a dirty rug');
+
+INSERT INTO lamps VALUES (21, 50, 'chair1', 'a big lamp1');
+INSERT INTO lamps VALUES (22, 50, 'chair1', 'a big lamp2');
+INSERT INTO lamps VALUES (23, 53, 'chair4', 'a big lamp3');
+INSERT INTO lamps VALUES (24, 53, 'chair4', 'a big lamp4');
+INSERT INTO lamps VALUES (25, 53, 'chair4', 'a big lamp5');
+INSERT INTO lamps VALUES (26, 54, 'couch', 'a big lamp6');
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/row_util.py b/vendor/Twisted-10.0.0/doc/core/examples/row_util.py
new file mode 100644
index 0000000000..f674604a10
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/row_util.py
@@ -0,0 +1,103 @@
+from twisted.enterprise import row
+
+
+##################################################
+########## Definitions of Row Classes ############
+##################################################
+
+class KeyFactory:
+ """This is a lame, but simple way to generate keys.
+ For real code, use the database instead."""
+ def __init__(self, minimum, pool):
+ self.min = minimum
+ self.pool = minimum + pool
+ self.current = self.min
+
+ def getNextKey(self):
+ next = self.current + 1
+ self.current = next
+ if self.current >= self.pool:
+ raise ValueError("Key factory key pool exceeded.")
+ return next
+
+def myRowFactory(rowClass, data, kw):
+ newRow = rowClass()
+ newRow.__dict__.update(kw)
+ return newRow
+
+class RoomRow(row.RowObject):
+ rowColumns = [
+ ("roomId", "int"),
+ ("town_id", "int"),
+ ("name", "varchar"),
+ ("owner", "varchar"),
+ ("posx", "int"),
+ ("posy", "int"),
+ ("width", "int"),
+ ("height", "int")
+ ]
+ rowKeyColumns = [("roomId","int")]
+ rowTableName = "testrooms"
+ rowFactoryMethod = [myRowFactory]
+
+ def __init__(self):
+ self.furniture = []
+
+ def addStuff(self, stuff):
+ self.furniture.append(stuff)
+
+ def moveTo(self, x, y):
+ self.posx = x
+ self.posy = y
+
+ def __repr__(self):
+ return "<Room #%s: %s (%s) (%s,%s)>" % (self.roomId, self.name, self.owner, self.posx, self.posy)
+
+class FurnitureRow(row.RowObject):
+ rowColumns = [
+ ("furnId", "int"),
+ ("roomId", "int"),
+ ("name", "varchar"),
+ ("posx", "int"),
+ ("posy", "int")
+ ]
+ rowKeyColumns = [("furnId","int")]
+ rowTableName = "furniture"
+ rowForeignKeys = [("testrooms", [("roomId","int")], [("roomId","int")], "addStuff", 1) ]
+
+ def __repr__(self):
+ return "Furniture #%s: room #%s (%s) (%s,%s)" % (self.furnId, self.roomId, self.name, self.posx, self.posy)
+
+class RugRow(row.RowObject):
+ rowColumns = [
+ ("rugId", "int"),
+ ("roomId", "int"),
+ ("name", "varchar")
+ ]
+ rowKeyColumns = [("rugId","int")]
+ rowTableName = "rugs"
+ rowFactoryMethod = [myRowFactory]
+ rowForeignKeys = [( "testrooms", [("roomId","int")],[("roomId","int")], "addStuff", 1) ]
+
+ def __repr__(self):
+ return "Rug %#s: room #%s, (%s)" % (self.rugId, self.roomId, self.name)
+
+class LampRow(row.RowObject):
+ rowColumns = [
+ ("lampId", "int"),
+ ("furnId", "int"),
+ ("furnName", "varchar"),
+ ("lampName", "varchar")
+ ]
+ rowKeyColumns = [("lampId","int")]
+ rowTableName = "lamps"
+ rowForeignKeys = [("furniture",
+ [("furnId","int"),("furnName", "varchar")], # child table columns (this table)
+ [("furnId","int"),("name", "varchar")], # parent table columns (the other table)
+ None,
+ 1)
+ ]
+ # NOTE: this has no containerMethod so children will be added to "childRows"
+
+ def __repr__(self):
+ return "Lamp #%s" % self.lampId
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/server.pem b/vendor/Twisted-10.0.0/doc/core/examples/server.pem
new file mode 100644
index 0000000000..80ef9dcf3b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv
+c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB
+BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC
+MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
+MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7
+hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT
+CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw
+dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
+LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6
+BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++
+7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE
+WUQ9Ho4EzbYCOQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx
+OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT
+ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4
+nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2
+HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA
+oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBDTCBuAIBADBTMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQ
+BgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20w
+XDANBgkqhkiG9w0BAQEFAANLADBIAkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5Ad
+NgLzJ1/MfsQQJ7hHVeHmTAjM664V+fXvwUGJLziCeBo1ysWLRnl8CQIDAQABoAAw
+DQYJKoZIhvcNAQEEBQADQQA7uqbrNTjVWpF6By5ZNPvhZ4YdFgkeXFVWi5ao/TaP
+Vq4BG021fJ9nlHRtr4rotpgHDX1rr+iWeHKsx4+5DRSy
+-----END CERTIFICATE REQUEST-----
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/shaper.py b/vendor/Twisted-10.0.0/doc/core/examples/shaper.py
new file mode 100644
index 0000000000..573d67c230
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/shaper.py
@@ -0,0 +1,52 @@
+# -*- Python -*-
+
+"""Example of rate-limiting your web server.
+
+Caveat emptor: While the transfer rates imposed by this mechanism will
+look accurate with wget's rate-meter, don't forget to examine your network
+interface's traffic statistics as well. The current implementation tends
+to create lots of small packets in some conditions, and each packet carries
+with it some bytes of overhead. Check to make sure this overhead is not
+costing you more bandwidth than you are saving by limiting the rate!
+"""
+
+from twisted.protocols import htb
+# for picklability
+import shaper
+
+serverFilter = htb.HierarchicalBucketFilter()
+serverBucket = htb.Bucket()
+
+# Cap total server traffic at 20 kB/s
+serverBucket.maxburst = 20000
+serverBucket.rate = 20000
+
+serverFilter.buckets[None] = serverBucket
+
+# Web service is also limited per-host:
+class WebClientBucket(htb.Bucket):
+ # Your first 10k is free
+ maxburst = 10000
+ # One kB/s thereafter.
+ rate = 1000
+
+webFilter = htb.FilterByHost(serverFilter)
+webFilter.bucketFactory = shaper.WebClientBucket
+
+servertype = "web" # "chargen"
+
+if servertype == "web":
+ from twisted.web import server, static
+ site = server.Site(static.File("/var/www"))
+ site.protocol = htb.ShapedProtocolFactory(site.protocol, webFilter)
+elif servertype == "chargen":
+ from twisted.protocols import wire
+ from twisted.internet import protocol
+
+ site = protocol.ServerFactory()
+ site.protocol = htb.ShapedProtocolFactory(wire.Chargen, webFilter)
+ #site.protocol = wire.Chargen
+
+from twisted.internet import reactor
+reactor.listenTCP(8000, site)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/shoutcast.py b/vendor/Twisted-10.0.0/doc/core/examples/shoutcast.py
new file mode 100644
index 0000000000..2280580188
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/shoutcast.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example Shoutcast client. Run with:
+
+python shoutcast.py localhost 8080
+"""
+
+import sys
+
+from twisted.internet import protocol, reactor
+from twisted.protocols.shoutcast import ShoutcastClient
+
+class Test(ShoutcastClient):
+ def gotMetaData(self, data):
+ print "meta:", data
+
+ def gotMP3Data(self, data):
+ pass
+
+host = sys.argv[1]
+port = int(sys.argv[2])
+
+protocol.ClientCreator(reactor, Test).connectTCP(host, port)
+reactor.run() \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/simple.tac b/vendor/Twisted-10.0.0/doc/core/examples/simple.tac
new file mode 100644
index 0000000000..02b3f81e4c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/simple.tac
@@ -0,0 +1,39 @@
+# You can run this .tac file directly with:
+# twistd -ny simple.tac
+
+from twisted.application import service, internet
+from twisted.protocols import wire
+from twisted.internet import protocol
+from twisted.python import util
+
+application = service.Application('test')
+s = service.IServiceCollection(application)
+factory = protocol.ServerFactory()
+factory.protocol = wire.Echo
+internet.TCPServer(8080, factory).setServiceParent(s)
+
+internet.TCPServer(8081, factory).setServiceParent(s)
+internet.TimerService(5, util.println, "--MARK--").setServiceParent(s)
+
+class Foo(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.write('lalala\n')
+ def dataReceived(self, data):
+ print `data`
+
+factory = protocol.ClientFactory()
+factory.protocol = Foo
+internet.TCPClient('localhost', 8081, factory).setServiceParent(s)
+
+class FooService(service.Service):
+ def startService(self):
+ service.Service.startService(self)
+ print 'lala, starting'
+ def stopService(self):
+ service.Service.stopService(self)
+ print 'lala, stopping'
+ print self.parent.getServiceNamed(self.name) is self
+
+foo = FooService()
+foo.setName('foo')
+foo.setServiceParent(s)
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/simpleclient.py b/vendor/Twisted-10.0.0/doc/core/examples/simpleclient.py
new file mode 100644
index 0000000000..04907f39a9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/simpleclient.py
@@ -0,0 +1,49 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example client. Run simpleserv.py first before running this.
+"""
+
+from twisted.internet import reactor, protocol
+
+
+# a client protocol
+
+class EchoClient(protocol.Protocol):
+ """Once connected, send a message, then print the result."""
+
+ def connectionMade(self):
+ self.transport.write("hello, world!")
+
+ def dataReceived(self, data):
+ "As soon as any data is received, write it back."
+ print "Server said:", data
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ print "connection lost"
+
+class EchoFactory(protocol.ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ print "Connection failed - goodbye!"
+ reactor.stop()
+
+ def clientConnectionLost(self, connector, reason):
+ print "Connection lost - goodbye!"
+ reactor.stop()
+
+
+# this connects the protocol to a server runing on port 8000
+def main():
+ f = EchoFactory()
+ reactor.connectTCP("localhost", 8000, f)
+ reactor.run()
+
+# this only runs if the module was *not* imported
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/simpleserv.py b/vendor/Twisted-10.0.0/doc/core/examples/simpleserv.py
new file mode 100644
index 0000000000..938db119a1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/simpleserv.py
@@ -0,0 +1,26 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import reactor, protocol
+
+
+class Echo(protocol.Protocol):
+ """This is just about the simplest possible protocol"""
+
+ def dataReceived(self, data):
+ "As soon as any data is received, write it back."
+ self.transport.write(data)
+
+
+def main():
+ """This runs the protocol on port 8000"""
+ factory = protocol.ServerFactory()
+ factory.protocol = Echo
+ reactor.listenTCP(8000,factory)
+ reactor.run()
+
+# this only runs if the module was *not* imported
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/stdin.py b/vendor/Twisted-10.0.0/doc/core/examples/stdin.py
new file mode 100644
index 0000000000..28dc7fdd28
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/stdin.py
@@ -0,0 +1,30 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of reading a line at a time from standard input
+without blocking the reactor.
+"""
+
+from twisted.internet import stdio
+from twisted.protocols import basic
+
+class Echo(basic.LineReceiver):
+ from os import linesep as delimiter
+
+ def connectionMade(self):
+ self.transport.write('>>> ')
+
+ def lineReceived(self, line):
+ self.sendLine('Echo: ' + line)
+ self.transport.write('>>> ')
+
+def main():
+ stdio.StandardIO(Echo())
+ from twisted.internet import reactor
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/stdiodemo.py b/vendor/Twisted-10.0.0/doc/core/examples/stdiodemo.py
new file mode 100644
index 0000000000..004fa451b9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/stdiodemo.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example using stdio, Deferreds, LineReceiver and twisted.web.client.
+
+Note that the WebCheckerCommandProtocol protocol could easily be used in e.g.
+a telnet server instead; see the comments for details.
+
+Based on an example by Abe Fettig.
+"""
+
+from twisted.internet import stdio, reactor
+from twisted.protocols import basic
+from twisted.web import client
+
+class WebCheckerCommandProtocol(basic.LineReceiver):
+ delimiter = '\n' # unix terminal style newlines. remove this line
+ # for use with Telnet
+
+ def connectionMade(self):
+ self.sendLine("Web checker console. Type 'help' for help.")
+
+ def lineReceived(self, line):
+ # Ignore blank lines
+ if not line: return
+
+ # Parse the command
+ commandParts = line.split()
+ command = commandParts[0].lower()
+ args = commandParts[1:]
+
+ # Dispatch the command to the appropriate method. Note that all you
+ # need to do to implement a new command is add another do_* method.
+ try:
+ method = getattr(self, 'do_' + command)
+ except AttributeError, e:
+ self.sendLine('Error: no such command.')
+ else:
+ try:
+ method(*args)
+ except Exception, e:
+ self.sendLine('Error: ' + str(e))
+
+ def do_help(self, command=None):
+ """help [command]: List commands, or show help on the given command"""
+ if command:
+ self.sendLine(getattr(self, 'do_' + command).__doc__)
+ else:
+ commands = [cmd[3:] for cmd in dir(self) if cmd.startswith('do_')]
+ self.sendLine("Valid commands: " +" ".join(commands))
+
+ def do_quit(self):
+ """quit: Quit this session"""
+ self.sendLine('Goodbye.')
+ self.transport.loseConnection()
+
+ def do_check(self, url):
+ """check <url>: Attempt to download the given web page"""
+ client.getPage(url).addCallback(
+ self.__checkSuccess).addErrback(
+ self.__checkFailure)
+
+ def __checkSuccess(self, pageData):
+ self.sendLine("Success: got %i bytes." % len(pageData))
+
+ def __checkFailure(self, failure):
+ self.sendLine("Failure: " + failure.getErrorMessage())
+
+ def connectionLost(self, reason):
+ # stop the reactor, only because this is meant to be run in Stdio.
+ reactor.stop()
+
+if __name__ == "__main__":
+ stdio.StandardIO(WebCheckerCommandProtocol())
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/testlogging.py b/vendor/Twisted-10.0.0/doc/core/examples/testlogging.py
new file mode 100644
index 0000000000..7a8c93ddb9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/testlogging.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Test logging.
+
+Message should only be printed second time around.
+"""
+
+from twisted.python import log
+from twisted.internet import reactor
+
+import sys, warnings
+
+def test(i):
+ print "printed", i
+ log.msg("message %s" % i)
+ warnings.warn("warning %s" % i)
+ try:
+ raise RuntimeError, "error %s" % i
+ except:
+ log.err()
+
+def startlog():
+ log.startLogging(sys.stdout)
+
+def end():
+ reactor.stop()
+
+# pre-reactor run
+test(1)
+
+# after reactor run
+reactor.callLater(0.1, test, 2)
+reactor.callLater(0.2, startlog)
+
+# after startLogging
+reactor.callLater(0.3, test, 3)
+reactor.callLater(0.4, end)
+
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib
new file mode 100644
index 0000000000..71cb459873
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib
@@ -0,0 +1,13 @@
+{
+ IBClasses = (
+ {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; },
+ {
+ ACTIONS = {doTwistzillaFetch = id; };
+ CLASS = MyAppDelegate;
+ LANGUAGE = ObjC;
+ OUTLETS = {messageTextField = id; progressIndicator = id; resultTextField = id; };
+ SUPERCLASS = NSObject;
+ }
+ );
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib
new file mode 100644
index 0000000000..4f99a2de8e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBEditorPositions</key>
+ <dict>
+ <key>29</key>
+ <string>127 344 318 44 0 0 1600 1002 </string>
+ </dict>
+ <key>IBFramework Version</key>
+ <string>291.0</string>
+ <key>IBLockedObjects</key>
+ <array>
+ <integer>204</integer>
+ </array>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>21</integer>
+ <integer>29</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>6L60</string>
+</dict>
+</plist>
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nib b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 0000000000..e5caaf0fb2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt
new file mode 100644
index 0000000000..96010e23df
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt
@@ -0,0 +1,6 @@
+Requires PyObjC 1.3.1 (svn r1589 or later)
+
+To run the demo:
+
+python setup.py py2app
+open dist/Twistzilla.app
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py
new file mode 100644
index 0000000000..9a7806c1e1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py
@@ -0,0 +1,79 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+# import needed classes/functions from Cocoa
+from Foundation import *
+from AppKit import *
+
+# import Nib loading functionality from AppKit
+from PyObjCTools import NibClassBuilder, AppHelper
+
+from twisted.internet import _threadedselect
+_threadedselect.install()
+
+from twisted.internet import reactor, protocol
+from twisted.web import http
+from twisted.python import log
+import sys, urlparse
+
+# create ObjC classes as defined in MainMenu.nib
+NibClassBuilder.extractClasses("MainMenu")
+class TwistzillaClient(http.HTTPClient):
+ def __init__(self, delegate, urls):
+ self.urls = urls
+ self.delegate = delegate
+
+ def connectionMade(self):
+ self.sendCommand('GET', str(self.urls[2]))
+ self.sendHeader('Host', '%s:%d' % (self.urls[0], self.urls[1]))
+ self.sendHeader('User-Agent', 'CocoaTwistzilla')
+ self.endHeaders()
+
+ def handleResponse(self, data):
+ self.delegate.gotResponse_(data)
+
+class MyAppDelegate(NibClassBuilder.AutoBaseClass):
+ def gotResponse_(self, html):
+ s = self.resultTextField.textStorage()
+ s.replaceCharactersInRange_withString_((0, s.length()), html)
+ self.progressIndicator.stopAnimation_(self)
+
+ def doTwistzillaFetch_(self, sender):
+ s = self.resultTextField.textStorage()
+ s.deleteCharactersInRange_((0, s.length()))
+ self.progressIndicator.startAnimation_(self)
+ u = urlparse.urlparse(self.messageTextField.stringValue())
+ pos = u[1].find(':')
+ if pos == -1:
+ host, port = u[1], 80
+ else:
+ host, port = u[1][:pos], int(u[1][pos+1:])
+ if u[2] == '':
+ fname = '/'
+ else:
+ fname = u[2]
+ host = host.encode('utf8')
+ fname = fname.encode('utf8')
+ protocol.ClientCreator(reactor, TwistzillaClient, self, (host, port, fname)).connectTCP(host, port).addErrback(lambda f:self.gotResponse_(f.getBriefTraceback()))
+
+ def applicationDidFinishLaunching_(self, aNotification):
+ """
+ Invoked by NSApplication once the app is done launching and
+ immediately before the first pass through the main event
+ loop.
+ """
+ self.messageTextField.setStringValue_("http://www.twistedmatrix.com/")
+ reactor.interleave(AppHelper.callAfter)
+
+ def applicationShouldTerminate_(self, sender):
+ if reactor.running:
+ reactor.addSystemEventTrigger(
+ 'after', 'shutdown', AppHelper.stopEventLoop)
+ reactor.stop()
+ return False
+ return True
+
+if __name__ == '__main__':
+ log.startLogging(sys.stdout)
+ AppHelper.runEventLoop()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py
new file mode 100644
index 0000000000..f3afe8a397
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py
@@ -0,0 +1,14 @@
+"""
+Script for building the example.
+
+Usage:
+ python setup.py py2app
+"""
+
+from distutils.core import setup
+import py2app
+
+setup(
+ app = ['Twistzilla.py'],
+ data_files = ["English.lproj"],
+)
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/README b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/README
new file mode 100644
index 0000000000..5d3feabe30
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/README
@@ -0,0 +1,15 @@
+The examples in this directory import a private module from the
+twisted.internet package. The _threadedselect module provides an object
+which is similar to a Twisted reactor in many ways, but which is not
+actually intended to be used in the same way as a Twisted reactor (it has a
+method named interleave which is intended to be the main entrypoint). This
+functionality should be considered highly experimental and the API subject
+to change at any time.
+
+Possibly the best way to make use of this functionality is to use it to
+implement an object which actually presents the Twisted reactor interface
+(specifically, an object with a run method). That object can then be used
+by application-code in the usual way.
+
+Another course of action is to avoid _threadedselect entirely until the
+issues surrounding it have been resolved.
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/blockingdemo.py b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/blockingdemo.py
new file mode 100644
index 0000000000..2d46bafbee
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/blockingdemo.py
@@ -0,0 +1,92 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import _threadedselect
+_threadedselect.install()
+
+from twisted.internet.defer import Deferred
+from twisted.python.failure import Failure
+from twisted.internet import reactor
+from twisted.python.runtime import seconds
+from itertools import count
+from Queue import Queue, Empty
+
+class TwistedManager(object):
+ def __init__(self):
+ self.twistedQueue = Queue()
+ self.key = count()
+ self.results = {}
+
+ def getKey(self):
+ # get a unique identifier
+ return self.key.next()
+
+ def start(self):
+ # start the reactor
+ reactor.interleave(self.twistedQueue.put)
+
+ def _stopIterating(self, value, key):
+ self.results[key] = value
+
+ def stop(self):
+ # stop the reactor
+ key = self.getKey()
+ reactor.addSystemEventTrigger('after', 'shutdown',
+ self._stopIterating, True, key)
+ reactor.stop()
+ self.iterate(key)
+
+ def getDeferred(self, d):
+ # get the result of a deferred or raise if it failed
+ key = self.getKey()
+ d.addBoth(self._stopIterating, key)
+ res = self.iterate(key)
+ if isinstance(res, Failure):
+ res.raiseException()
+ return res
+
+ def poll(self, noLongerThan=1.0):
+ # poll the reactor for up to noLongerThan seconds
+ base = seconds()
+ try:
+ while (seconds() - base) <= noLongerThan:
+ callback = self.twistedQueue.get_nowait()
+ callback()
+ except Empty:
+ pass
+
+ def iterate(self, key=None):
+ # iterate the reactor until it has the result we're looking for
+ while key not in self.results:
+ callback = self.twistedQueue.get()
+ callback()
+ return self.results.pop(key)
+
+def fakeDeferred(msg):
+ d = Deferred()
+ def cb():
+ print "deferred called back"
+ d.callback(msg)
+ reactor.callLater(2, cb)
+ return d
+
+def fakeCallback():
+ print "twisted is still running"
+
+def main():
+ m = TwistedManager()
+ print "starting"
+ m.start()
+ print "setting up a 1sec callback"
+ reactor.callLater(1, fakeCallback)
+ print "getting a deferred"
+ res = m.getDeferred(fakeDeferred("got it!"))
+ print "got the deferred:", res
+ print "stopping"
+ m.stop()
+ print "stopped"
+
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/pygamedemo.py b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/pygamedemo.py
new file mode 100644
index 0000000000..a2bec3388f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/threadedselect/pygamedemo.py
@@ -0,0 +1,78 @@
+from __future__ import generators
+
+# import Twisted and install
+from twisted.internet import _threadedselect
+_threadedselect.install()
+from twisted.internet import reactor
+
+import os
+
+import pygame
+from pygame.locals import *
+
+try:
+ import pygame.fastevent as eventmodule
+except ImportError:
+ import pygame.event as eventmodule
+
+
+# You can customize this if you use your
+# own events, but you must OBEY:
+#
+# USEREVENT <= TWISTEDEVENT < NUMEVENTS
+#
+TWISTEDEVENT = USEREVENT
+
+def postTwistedEvent(func):
+ # if not using pygame.fastevent, this can explode if the queue
+ # fills up.. so that's bad. Use pygame.fastevent, in pygame CVS
+ # as of 2005-04-18.
+ eventmodule.post(eventmodule.Event(TWISTEDEVENT, iterateTwisted=func))
+
+def helloWorld():
+ print "hello, world"
+ reactor.callLater(1, helloWorld)
+reactor.callLater(1, helloWorld)
+
+def twoSecondsPassed():
+ print "two seconds passed"
+reactor.callLater(2, twoSecondsPassed)
+
+def eventIterator():
+ while True:
+ yield eventmodule.wait()
+ while True:
+ event = eventmodule.poll()
+ if event.type == NOEVENT:
+ break
+ else:
+ yield event
+
+def main():
+ pygame.init()
+ if hasattr(eventmodule, 'init'):
+ eventmodule.init()
+ screen = pygame.display.set_mode((300, 300))
+
+ # send an event when twisted wants attention
+ reactor.interleave(postTwistedEvent)
+ # make shouldQuit a True value when it's safe to quit
+ # by appending a value to it. This ensures that
+ # Twisted gets to shut down properly.
+ shouldQuit = []
+ reactor.addSystemEventTrigger('after', 'shutdown', shouldQuit.append, True)
+
+ for event in eventIterator():
+ if event.type == TWISTEDEVENT:
+ event.iterateTwisted()
+ if shouldQuit:
+ break
+ elif event.type == QUIT:
+ reactor.stop()
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ reactor.stop()
+
+ pygame.quit()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/twistd-logging.tac b/vendor/Twisted-10.0.0/doc/core/examples/twistd-logging.tac
new file mode 100644
index 0000000000..2302558a22
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/twistd-logging.tac
@@ -0,0 +1,33 @@
+# Invoke this script with:
+
+# $ twistd -ny twistd-logging.tac
+
+# It will create a log file named "twistd-logging.log". The log file will
+# be formatted such that each line contains the representation of the dict
+# structure of each log message.
+
+from twisted.application.service import Application
+from twisted.python.log import ILogObserver, msg
+from twisted.python.util import untilConcludes
+from twisted.internet.task import LoopingCall
+
+
+logfile = open("twistd-logging.log", "a")
+
+
+def log(eventDict):
+ # untilConcludes is necessary to retry the operation when the system call
+ # has been interrupted.
+ untilConcludes(logfile.write, "Got a log! %r\n" % eventDict)
+ untilConcludes(logfile.flush)
+
+
+def logSomething():
+ msg("A log message")
+
+
+LoopingCall(logSomething).start(1)
+
+application = Application("twistd-logging")
+application.setComponent(ILogObserver, log)
+
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/wxacceptance.py b/vendor/Twisted-10.0.0/doc/core/examples/wxacceptance.py
new file mode 100644
index 0000000000..8f73f823da
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/wxacceptance.py
@@ -0,0 +1,113 @@
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Acceptance tests for wxreactor.
+
+Please test on Linux, Win32 and OS X:
+1. Startup event is called at startup.
+2. Scheduled event is called after 2 seconds.
+3. Shutdown takes 3 seconds, both when quiting from menu and when closing
+ window (e.g. Alt-F4 in metacity). This tests reactor.stop() and
+ wxApp.ExitEventLoop().
+4. 'hello, world' continues to be printed even when modal dialog is open
+ (use dialog menu item), when menus are held down, when window is being
+ dragged.
+"""
+
+import sys, time
+
+try:
+ from wx import Frame as wxFrame, DefaultPosition as wxDefaultPosition, \
+ Size as wxSize, Menu as wxMenu, MenuBar as wxMenuBar, \
+ EVT_MENU, MessageDialog as wxMessageDialog, App as wxApp
+except ImportError, e:
+ from wxPython.wx import *
+
+from twisted.python import log
+from twisted.internet import wxreactor
+wxreactor.install()
+from twisted.internet import reactor, defer
+
+
+# set up so that "hello, world" is printed continously
+dc = None
+def helloWorld():
+ global dc
+ print "hello, world", time.time()
+ dc = reactor.callLater(0.1, helloWorld)
+dc = reactor.callLater(0.1, helloWorld)
+
+def twoSecondsPassed():
+ print "two seconds passed"
+
+def printer(s):
+ print s
+
+def shutdown():
+ print "shutting down in 3 seconds"
+ if dc.active():
+ dc.cancel()
+ reactor.callLater(1, printer, "2...")
+ reactor.callLater(2, printer, "1...")
+ reactor.callLater(3, printer, "0...")
+ d = defer.Deferred()
+ reactor.callLater(3, d.callback, 1)
+ return d
+
+def startup():
+ print "Start up event!"
+
+reactor.callLater(2, twoSecondsPassed)
+reactor.addSystemEventTrigger("after", "startup", startup)
+reactor.addSystemEventTrigger("before", "shutdown", shutdown)
+
+
+ID_EXIT = 101
+ID_DIALOG = 102
+
+class MyFrame(wxFrame):
+ def __init__(self, parent, ID, title):
+ wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, wxSize(300, 200))
+ menu = wxMenu()
+ menu.Append(ID_DIALOG, "D&ialog", "Show dialog")
+ menu.Append(ID_EXIT, "E&xit", "Terminate the program")
+ menuBar = wxMenuBar()
+ menuBar.Append(menu, "&File")
+ self.SetMenuBar(menuBar)
+ EVT_MENU(self, ID_EXIT, self.DoExit)
+ EVT_MENU(self, ID_DIALOG, self.DoDialog)
+ # you really ought to do this instead of reactor.stop() in
+ # DoExit, but for the sake of testing we'll let closing the
+ # window shutdown wx without reactor.stop(), to make sure that
+ # still does the right thing.
+ #EVT_CLOSE(self, lambda evt: reactor.stop())
+
+ def DoDialog(self, event):
+ dl = wxMessageDialog(self, "Check terminal to see if messages are still being "
+ "printed by Twisted.")
+ dl.ShowModal()
+ dl.Destroy()
+
+ def DoExit(self, event):
+ reactor.stop()
+
+
+class MyApp(wxApp):
+
+ def OnInit(self):
+ frame = MyFrame(None, -1, "Hello, world")
+ frame.Show(True)
+ self.SetTopWindow(frame)
+ return True
+
+
+def demo():
+ log.startLogging(sys.stdout)
+ app = MyApp(0)
+ reactor.registerWxApp(app)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ demo()
diff --git a/vendor/Twisted-10.0.0/doc/core/examples/wxdemo.py b/vendor/Twisted-10.0.0/doc/core/examples/wxdemo.py
new file mode 100644
index 0000000000..3db2c0fd56
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/examples/wxdemo.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""Demo of wxPython integration with Twisted."""
+
+import sys
+
+from wx import Frame, DefaultPosition, Size, Menu, MenuBar, App
+from wx import EVT_MENU, EVT_CLOSE
+
+from twisted.python import log
+from twisted.internet import wxreactor
+wxreactor.install()
+
+# import t.i.reactor only after installing wxreactor:
+from twisted.internet import reactor
+
+
+ID_EXIT = 101
+
+class MyFrame(Frame):
+ def __init__(self, parent, ID, title):
+ Frame.__init__(self, parent, ID, title, DefaultPosition, Size(300, 200))
+ menu = Menu()
+ menu.Append(ID_EXIT, "E&xit", "Terminate the program")
+ menuBar = MenuBar()
+ menuBar.Append(menu, "&File")
+ self.SetMenuBar(menuBar)
+ EVT_MENU(self, ID_EXIT, self.DoExit)
+
+ # make sure reactor.stop() is used to stop event loop:
+ EVT_CLOSE(self, lambda evt: reactor.stop())
+
+ def DoExit(self, event):
+ reactor.stop()
+
+
+class MyApp(App):
+
+ def twoSecondsPassed(self):
+ print "two seconds passed"
+
+ def OnInit(self):
+ frame = MyFrame(None, -1, "Hello, world")
+ frame.Show(True)
+ self.SetTopWindow(frame)
+ # look, we can use twisted calls!
+ reactor.callLater(2, self.twoSecondsPassed)
+ return True
+
+
+def demo():
+ log.startLogging(sys.stdout)
+
+ # register the App instance with Twisted:
+ app = MyApp(0)
+ reactor.registerWxApp(app)
+
+ # start the event loop:
+ reactor.run()
+
+
+if __name__ == '__main__':
+ demo()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/application.html b/vendor/Twisted-10.0.0/doc/core/howto/application.html
new file mode 100644
index 0000000000..c1e0ff51be
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/application.html
@@ -0,0 +1,376 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using the Twisted Application Framework</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using the Twisted Application Framework</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><ul><li><a href="#auto1">Audience</a></li><li><a href="#auto2">Goals</a></li></ul><li><a href="#auto3">Overview</a></li><li><a href="#auto4">Using application</a></li><ul><li><a href="#auto5">twistd and tac</a></li><li><a href="#auto6">Customizing twistd logging in a .tac application</a></li><li><a href="#auto7">Services provided by Twisted</a></li><li><a href="#auto8">Service Collection</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<h3>Audience<a name="auto1"/></h3>
+
+<p>The target audience of this document is a Twisted user who wants to deploy a
+significant amount of Twisted code in a re-usable, standard and easily
+configurable fashion. A Twisted user who wishes to use the Application
+framework needs to be familiar with developing Twisted <a href="servers.html" shape="rect">servers</a> and/or <a href="clients.html" shape="rect">clients</a>.</p>
+
+<h3>Goals<a name="auto2"/></h3>
+
+<ul>
+ <li>To introduce the Twisted Application infrastructure.</li>
+
+ <li>To explain how to deploy your Twisted application using <code>.tac</code>
+ files and <code>twistd</code></li>
+
+ <li>To outline the existing Twisted services.</li>
+</ul>
+
+<h2>Overview<a name="auto3"/></h2>
+
+<p>The Twisted Application infrastructure takes care of running and stopping
+your application. Using this infrastructure frees you from from having to
+write a large amount of boilerplate code by hooking your application into
+existing tools that manage daemonization, logging, <a href="choosing-reactor.html" shape="rect">choosing a reactor</a> and more.</p>
+
+<p>The major tool that manages Twisted applications is a command-line utility
+called <code>twistd</code>. <code>twistd</code> is cross platform, and is the
+recommended tool for running Twisted applications. </p>
+
+
+<p>The core component of the Twisted Application infrastructure is the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">twisted.application.service.Application</a></code> object — an
+object which represents your application. However, Application doesn't provide
+anything that you'd want to manipulate directly. Instead, Application acts as
+a container of any <q>Services</q> (objects implementing <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code>) that your application
+provides. Most of your interaction with the Application infrastructure will be
+done through Services.</p>
+
+<p>By <q>Service</q>, we mean anything in your application that can be started
+and stopped. Typical services include web servers, FTP servers and SSH
+clients. Your Application object can contain many services, and can even
+contain structured hierarchies of Services using <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IServiceCollection.html" title="twisted.application.service.IServiceCollection">IServiceCollection</a></code>s.</p>
+
+<p>Here's a simple example of constructing an Application object which
+represents an echo server that runs on TCP port 7001.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">somemodule</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">EchoFactory</span>
+
+<span class="py-src-variable">port</span> = <span class="py-src-number">7001</span>
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">EchoFactory</span>()
+
+<span class="py-src-comment"># this is the important bit</span>
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;echo&quot;</span>) <span class="py-src-comment"># create the Application</span>
+<span class="py-src-variable">echoService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-variable">port</span>, <span class="py-src-variable">factory</span>) <span class="py-src-comment"># create the service</span>
+<span class="py-src-comment"># add the service to the application</span>
+<span class="py-src-variable">echoService</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+</pre>
+
+<p>See <a href="servers.html" shape="rect">Writing Servers</a> for an explanation of
+EchoFactory.</p>
+
+<p>This example creates a simple hierarchy:
+<pre xml:space="preserve">
+ application
+ |
+ `- echoService
+</pre> More complicated hierarchies of services can be created using
+IServiceCollection. You will most likely want to do this to manage Services
+which are dependent on other Services. For example, a proxying Twisted
+application might want its server Service to only start up after the associated
+Client service. </p>
+
+
+<h2>Using application<a name="auto4"/></h2>
+
+<h3>twistd and tac<a name="auto5"/></h3><a name="twistd" shape="rect"/>
+
+<p>To handle start-up and configuration of your Twisted application, the
+Twisted Application infrastructure uses <code>.tac</code> files.
+<code>.tac</code> are Python files which configure an <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code> object and assign this
+object to the top-level variable <q><code>application</code></q>.</p>
+
+<p>The following is a simple example of a <code>.tac</code> file:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+</p><span class="py-src-comment"># You can run this .tac file directly with:</span>
+<span class="py-src-comment"># twistd -ny service.tac</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+This is an example .tac file which starts a webserver on port 8080 and
+serves files from the current working directory.
+
+The important part of this, the part that makes it a .tac file, is
+the final root-level section, which sets up the object called 'application'
+which twistd will look for
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getWebService</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a service suitable for creating an application object.
+
+ This service is a simple web server that serves files on port 8080 from
+ underneath the current working directory.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-comment"># create a resource to serve static files</span>
+ <span class="py-src-variable">fileServer</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">getcwd</span>()))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8080</span>, <span class="py-src-variable">fileServer</span>)
+
+<span class="py-src-comment"># this is the core part of any tac file, the creation of the root-level</span>
+<span class="py-src-comment"># application object</span>
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;Demo application&quot;</span>)
+
+<span class="py-src-comment"># attach the service to its parent application</span>
+<span class="py-src-variable">service</span> = <span class="py-src-variable">getWebService</span>()
+<span class="py-src-variable">service</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+</pre><div class="caption">Source listing - <a href="listings/application/service.tac"><span class="filename">listings/application/service.tac</span></a></div></div>
+
+<p><code>twistd</code> is a program that runs Twisted applications using a
+<code>.tac</code> file. In its most simple form, it takes a single argument
+<code>-y</code> and a tac file name. For example, you can run the above server
+with the command <code class="shell">twistd -y service.tac</code>.</p>
+
+<p>By default, <code>twistd</code> daemonizes and logs to a file called
+<code>twistd.log</code>. More usually, when debugging, you will want your
+application to run in the foreground and log to the command line. To run the
+above file like this, use the command <code class="shell">twistd -noy
+service.tac</code></p>
+
+<p>For more information, see the <code>twistd</code> man page.</p>
+
+<h3>Customizing <code>twistd</code> logging in a .tac application<a name="auto6"/></h3>
+
+<p>
+The logging behavior can be customized through an API
+accessible from <code>.tac</code> files. The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.ILogObserver.html" title="twisted.python.log.ILogObserver">ILogObserver</a></code> component can be
+set on an Application in order to customize the default log observer that
+<code>twistd</code> will use.
+</p>
+
+<p>
+Here is an example of how to use <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.logfile.DailyLogFile.html" title="twisted.python.logfile.DailyLogFile">DailyLogFile</a></code>, which rotates the log once
+per day.
+</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">service</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Application</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ILogObserver</span>, <span class="py-src-variable">FileLogObserver</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">logfile</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DailyLogFile</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;myapp&quot;</span>)
+<span class="py-src-variable">logfile</span> = <span class="py-src-variable">DailyLogFile</span>(<span class="py-src-string">&quot;my.log&quot;</span>, <span class="py-src-string">&quot;/tmp&quot;</span>)
+<span class="py-src-variable">application</span>.<span class="py-src-variable">setComponent</span>(<span class="py-src-variable">ILogObserver</span>, <span class="py-src-variable">FileLogObserver</span>(<span class="py-src-variable">logfile</span>).<span class="py-src-variable">emit</span>)
+</pre>
+
+<p>
+invoking <code class="shell">twistd -y my.tac</code> will create a log file
+at<code>/tmp/my.log</code>.
+</p>
+
+<h3>Services provided by Twisted<a name="auto7"/></h3>
+
+<p>Twisted provides several services that you want to know about.</p>
+
+<p>Each of these services (except TimerService) has a corresponding
+<q>connect</q> or <q>listen</q> method on the reactor, and the constructors for
+the services take the same arguments as the reactor methods. The
+<q>connect</q> methods are for clients and the <q>listen</q> methods are for
+servers. For example, TCPServer corresponds to reactor.listenTCP and TCPClient
+corresponds to reactor.connectTCP. </p>
+
+<dl>
+ <dt><code>TCPServer</code>
+ </dt>
+
+ <dt><code>TCPClient</code>
+ </dt>
+
+ <dd>
+ Services which allow you to make connections and listen for connections
+ on TCP ports.
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTCP.listenTCP.html" title="twisted.internet.interfaces.IReactorTCP.listenTCP">listenTCP</a></code></li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTCP.connectTCP.html" title="twisted.internet.interfaces.IReactorTCP.connectTCP">connectTCP</a></code></li>
+ </ul>
+ </dd>
+
+ <dt><code>UNIXServer</code></dt>
+
+ <dt><code>UNIXClient</code></dt>
+
+ <dd>
+ Services which listen and make connections over UNIX sockets.
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUNIX.listenUNIX.html" title="twisted.internet.interfaces.IReactorUNIX.listenUNIX">listenUNIX</a></code></li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUNIX.connectUNIX.html" title="twisted.internet.interfaces.IReactorUNIX.connectUNIX">connectUNIX</a></code></li>
+ </ul>
+ </dd>
+
+ <dt><code>SSLServer</code></dt>
+
+ <dt><code>SSLClient</code></dt>
+
+ <dd>Services which allow you to make SSL connections and run SSL servers.
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorSSL.listenSSL.html" title="twisted.internet.interfaces.IReactorSSL.listenSSL">listenSSL</a></code></li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorSSL.connectSSL.html" title="twisted.internet.interfaces.IReactorSSL.connectSSL">connectSSL</a></code></li>
+ </ul>
+ </dd>
+
+ <dt><code>UDPServer</code></dt>
+
+ <dt><code>UDPClient</code></dt>
+
+ <dd>Services which allow you to send and receive data over UDP
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUDP.listenUDP.html" title="twisted.internet.interfaces.IReactorUDP.listenUDP">listenUDP</a></code></li>
+ </ul>
+
+ <p>See also the <a href="udp.html" shape="rect">UDP documentation</a>.</p>
+ </dd>
+
+ <dt><code>UNIXDatagramServer</code></dt>
+
+ <dt><code>UNIXDatagramClient</code></dt>
+
+ <dd>Services which send and receive data over UNIX datagram sockets.
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUDP.listenUNIXDatagram.html" title="twisted.internet.interfaces.IReactorUDP.listenUNIXDatagram">listenUNIXDatagram</a></code></li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUDP.connectUNIXDatagram.html" title="twisted.internet.interfaces.IReactorUDP.connectUNIXDatagram">connectUNIXDatagram</a></code></li>
+ </ul>
+ </dd>
+
+ <dt><code>MulticastServer</code></dt>
+
+ <dd>
+ A server for UDP socket methods that support multicast.
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUDP.listenMulticast.html" title="twisted.internet.interfaces.IReactorUDP.listenMulticast">listenMulticast</a></code></li>
+ </ul>
+ </dd>
+
+ <dt><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.internet.TimerService.html" title="twisted.application.internet.TimerService">TimerService</a></code></dt>
+
+ <dd>
+ A service to periodically call a function.
+ </dd>
+
+</dl>
+
+<h3>Service Collection<a name="auto8"/></h3>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IServiceCollection.html" title="twisted.application.service.IServiceCollection">IServiceCollection</a></code> objects contain
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code> objects.
+IService objects can be added to IServiceCollection by calling <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IService.setServiceParent.html" title="twisted.application.service.IService.setServiceParent">setServiceParent</a></code> and detached
+by using <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IService.disownServiceParent.html" title="twisted.application.service.IService.disownServiceParent">disownServiceParent</a></code>.</p>
+
+<p>The standard implementation of IServiceCollection is <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.MultiService.html" title="twisted.application.service.MultiService">MultiService</a></code>, which also implements
+IService. MultiService is useful for creating a new Service which combines two
+or more existing Services. For example, you could create a DNS Service as a
+MultiService which has a TCP and a UDP Service as children.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">names</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span>, <span class="py-src-variable">dns</span>, <span class="py-src-variable">hosts</span>
+
+<span class="py-src-variable">port</span> = <span class="py-src-number">53</span>
+
+<span class="py-src-comment"># Create a MultiService, and hook up a TCPServer and a UDPServer to it as</span>
+<span class="py-src-comment"># children.</span>
+<span class="py-src-variable">dnsService</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">MultiService</span>()
+<span class="py-src-variable">hostsResolver</span> = <span class="py-src-variable">hosts</span>.<span class="py-src-variable">Resolver</span>(<span class="py-src-string">'/etc/hosts'</span>)
+<span class="py-src-variable">tcpFactory</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">DNSServerFactory</span>([<span class="py-src-variable">hostsResolver</span>])
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-variable">port</span>, <span class="py-src-variable">tcpFactory</span>).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">dnsService</span>)
+<span class="py-src-variable">udpFactory</span> = <span class="py-src-variable">dns</span>.<span class="py-src-variable">DNSDatagramProtocol</span>(<span class="py-src-variable">tcpFactory</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">UDPServer</span>(<span class="py-src-variable">port</span>, <span class="py-src-variable">udpFactory</span>).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">dnsService</span>)
+
+<span class="py-src-comment"># Create an application as normal</span>
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;DNSExample&quot;</span>)
+
+<span class="py-src-comment"># Connect our MultiService to the application, just like a normal service.</span>
+<span class="py-src-variable">dnsService</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+</pre>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/basics.html b/vendor/Twisted-10.0.0/doc/core/howto/basics.html
new file mode 100644
index 0000000000..cfaf899dc9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/basics.html
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Basics</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Basics</h1>
+ <div class="toc"><ol><li><a href="#auto0">Application</a></li><li><a href="#auto1">twistd</a></li><li><a href="#auto2">tap2deb</a></li><li><a href="#auto3">tap2rpm</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Application<a name="auto0"/></h2>
+
+<p>Twisted programs usually work with
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">twisted.application.service.Application</a></code>.
+This class usually holds all persistent configuration of
+a running server -- ports to bind to, places where connections
+to must be kept or attempted, periodic actions to do and almost
+everything else. It is the root object in a tree of services implementing
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code>.</p>
+
+<p>Other HOWTOs describe how to write custom code for Applications,
+but this one describes how to use already written code (which can be
+part of Twisted or from a third-party Twisted plugin developer). The
+Twisted distribution comes with an important tool to deal with
+Applications, <code>twistd</code>.</p>
+
+<p><code>Application</code>s are just Python objects, which can
+be created and manipulated in the same ways as any other object.
+</p>
+
+<h2>twistd<a name="auto1"/></h2><a name="twistd" shape="rect"/>
+
+<p>The Twisted Daemon is a program that knows how to run Applications.
+This program
+is <code class="shell">twistd(1)</code>. Strictly
+speaking, <code class="shell">twistd</code> is not necessary --
+fetching the application, getting the <code>IService</code> component,
+calling <code>startService</code>, scheduling <code>stopService</code> when
+the reactor shuts down, and then calling <code>reactor.run()</code> could be
+done manually. <code class="shell">twistd(1)</code>, however, supplies
+many options which are highly useful for program set up.</p>
+
+<p><code class="shell">twistd</code> supports choosing a reactor (for more on
+reactors, see <a href="choosing-reactor.html" shape="rect">Choosing a Reactor</a>), logging
+to a logfile, daemonizing and more. <code class="shell">twistd</code> supports all
+Applications mentioned above -- and an additional one. Sometimes
+it is convenient to write the code for building a class in straight
+Python. One big source of such Python files is the <code>doc/examples</code>
+directory. When a straight Python file which defines an <code>Application</code>
+object called <code>application</code> is used, use the <code class="shell">-y</code>
+option.</p>
+
+<p>When <code class="shell">twistd</code> runs, it records its process id in a
+<code>twistd.pid</code> file (this can be configured via a command line
+switch). In order to shutdown the <code class="shell">twistd</code> process, kill that
+pid (usually you would do <code class="shell">kill `cat twistd.pid`</code>).
+</p>
+
+<p>As always, the gory details are in the manual page.</p>
+
+<h2>tap2deb<a name="auto2"/></h2>
+
+<p>
+For Twisted-based server application developers who want to deploy on
+Debian, Twisted supplies the <code class="shell">tap2deb</code> program. This program
+wraps a Twisted Application file (of any of the supported formats -- Python,
+source, xml or pickle)
+in a Debian package, including correct installation and removal scripts
+and <code>init.d</code> scripts. This frees the installer from manually
+stopping or starting the service, and will make sure it goes properly up
+on startup and down on shutdown and that it obeys the init levels.
+</p>
+
+<p>
+For the more savvy Debian users, the
+<code class="shell">tap2deb</code> also generates the source package, allowing her
+to modify and polish things which automated software cannot detect
+(such as dependencies or relationships to virtual packages). In addition,
+the Twisted team itself intends to produce Debian packages for some common
+services, such as web servers and an inetd replacement. Those packages
+will enjoy the best of all worlds -- both the consistency which comes
+from being based on the <code class="shell">tap2deb</code> and the delicate manual
+tweaking of a Debian maintainer, insuring perfect integration with
+Debian.
+</p>
+
+<h2>tap2rpm<a name="auto3"/></h2>
+
+<p><code class="shell">tap2rpm</code> is similar to <code class="shell">tap2deb</code>, except that
+it generates RPMs for Redhat and other related platforms.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/book.tex b/vendor/Twisted-10.0.0/doc/core/howto/book.tex
new file mode 100644
index 0000000000..716ab97ab3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/book.tex
@@ -0,0 +1,116 @@
+\documentclass[oneside]{book}
+\usepackage[dvips]{graphicx}
+\usepackage{times,mathptmx}
+\usepackage{ifthen}
+\usepackage{hyperref}
+
+\usepackage{geometry}
+\geometry{verbose,letterpaper,tmargin=1in,bmargin=0.5in,lmargin=1in,rmargin=1in}
+
+\setlength{\oddsidemargin}{0in}
+\setlength{\textwidth}{\paperwidth}
+\addtolength{\textwidth}{-2in}
+
+\newcommand{\loreref}[1]{%
+ \ifthenelse{\value{page}=\pageref{#1}}%
+ { (this page)}%
+ { (page \pageref{#1})}%
+}
+
+
+\title{The Twisted Documentation}
+\author{The Twisted Development Team}
+
+\tolerance=1000
+\sloppy
+
+\begin{document}
+\maketitle
+\tableofcontents
+
+\chapter{Introduction}
+
+\input{vision.tex}
+\input{overview.tex}
+\input{internet-overview.tex}
+
+\chapter{Tutorial}
+
+\input{servers.tex}
+\input{clients.tex}
+\input{quotes.tex}
+\input{design.tex}
+\input{tutorial/index.tex}
+\input{tutorial/intro.tex}
+\input{tutorial/protocol.tex}
+\input{tutorial/style.tex}
+\input{tutorial/components.tex}
+\input{tutorial/backends.tex}
+\input{tutorial/web.tex}
+\input{tutorial/pb.tex}
+\input{tutorial/factory.tex}
+\input{tutorial/client.tex}
+\input{tutorial/library.tex}
+\input{tutorial/configuration.tex}
+
+\chapter{Low-Level Twisted }
+
+\input{reactor-basics.tex}
+\input{udp.tex}
+\input{process.tex}
+\input{defer.tex}
+\input{gendefer.tex}
+\input{deferredindepth.tex}
+\input{time.tex}
+\input{threading.tex}
+\input{choosing-reactor.tex}
+
+\chapter{High-Level Twisted}
+
+\input{basics.tex}
+\input{plugin.tex}
+\input{tap.tex}
+\input{components.tex}
+\input{cred.tex}
+\input{application.tex}
+
+\chapter{Utilities}
+
+\input{options.tex}
+\input{logging.tex}
+\input{dirdbm.tex}
+\input{telnet.tex}
+\input{testing.tex}
+
+\chapter{Twisted RDBMS support}
+
+\input{rdbms.tex}
+\input{row.tex}
+
+\chapter{Perspective Broker}
+\input{pb.tex}
+\input{pb-intro.tex}
+\input{pb-usage.tex}
+\input{pb-copyable.tex}
+\input{pb-cred.tex}
+
+\chapter{Manual Pages}
+
+\input{../man/manhole-man.tex}
+\clearpage
+\input{../man/tap2deb-man.tex}
+\clearpage
+\input{../man/tap2rpm-man.tex}
+\clearpage
+\input{../man/tapconvert-man.tex}
+\clearpage
+\input{../man/trial-man.tex}
+\clearpage
+\input{../man/twistd-man.tex}
+
+\chapter{Appendix}
+
+\input{glossary.tex}
+\input{../specifications/banana.tex}
+
+\end{document}
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/choosing-reactor.html b/vendor/Twisted-10.0.0/doc/core/howto/choosing-reactor.html
new file mode 100644
index 0000000000..d8920e32b6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/choosing-reactor.html
@@ -0,0 +1,355 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Choosing a Reactor and GUI Toolkit Integration</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Choosing a Reactor and GUI Toolkit Integration</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Reactor Functionality</a></li><li><a href="#auto2">General Purpose Reactors</a></li><ul><li><a href="#auto3">Select()-based Reactor</a></li><li><a href="#auto4">Poll()-based Reactor</a></li></ul><li><a href="#auto5">Platform-Specific Reactors</a></li><ul><li><a href="#auto6">KQueue</a></li><li><a href="#auto7">Win32 (WFMO)</a></li><li><a href="#auto8">Win32 (IOCP)</a></li><li><a href="#auto9">Epoll-based Reactor</a></li></ul><li><a href="#auto10">GUI Integration Reactors</a></li><ul><li><a href="#auto11">GTK+</a></li><li><a href="#auto12">CoreFoundation</a></li></ul><li><a href="#auto13">Non-Reactor GUI Integration</a></li><ul><li><a href="#auto14">Tkinter</a></li><li><a href="#auto15">wxPython</a></li><li><a href="#auto16">PyUI</a></li></ul></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>Twisted provides a variety of implementations of the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">twisted.internet.reactor</a></code>. The specialized
+ implementations are suited for different purposes and are
+ designed to integrate better with particular platforms.</p>
+
+ <p>The general purpose reactor implementations are:</p>
+
+ <ul>
+ <li><a href="#select" shape="rect">The select()-based reactor</a></li>
+ <li><a href="#poll" shape="rect">The poll()-based reactor</a></li>
+ </ul>
+
+ <p>Platform-specific reactor implementations exist for:</p>
+
+ <ul>
+ <li><a href="#kqueue" shape="rect">KQueue for FreeBSD and OS X</a></li>
+ <li><a href="#win32_wfmo" shape="rect">Win32 (WFMO)</a></li>
+ <li><a href="#win32_iocp" shape="rect">Win32 (IOCP)</a></li>
+ <li><a href="#cfreactor" shape="rect">Mac OS X</a></li>
+ <li><a href="#epoll" shape="rect">Epoll for Linux 2.6</a></li>
+ </ul>
+
+ <p>The remaining custom reactor implementations provide support
+ for integrating with the native event loops of various graphical
+ toolkits. This lets your Twisted application use all of the
+ usual Twisted APIs while still being a graphical application.</p>
+
+ <p>Twisted currently integrates with the following graphical
+ toolkits:</p>
+
+ <ul>
+ <li><a href="#gtk" shape="rect">GTK+ 1.2 and 2.0</a></li>
+ <li><a href="#tkinter" shape="rect">Tkinter</a></li>
+ <li><a href="#wxpython" shape="rect">WxPython</a></li>
+ <li><a href="#win32_wfmo" shape="rect">Win32</a></li>
+ <li><a href="#cfreactor" shape="rect">CoreFoundation</a></li>
+ <li><a href="#pyui" shape="rect">PyUI</a></li>
+ </ul>
+
+ <p>When using applications that runnable using <code>twistd</code>, e.g.
+ TAPs or plugins, there is no need to choose a reactor explicitly, since
+ this can be chosen using <code>twistd</code>'s -r option.</p>
+
+ <p>In all cases, the event loop is started by calling <code class="python">reactor.run()</code>. In all cases, the event loop
+ should be stopped with <code class="python">reactor.stop()</code>.</p>
+
+ <p><strong>IMPORTANT:</strong> installing a reactor should be the first thing
+ done in the app, since any code that does
+ <code class="python">from twisted.internet import reactor</code> will automatically
+ install the default reactor if the code hasn't already installed one.</p>
+
+ <h2>Reactor Functionality<a name="auto1"/></h2>
+
+ <table border="1" cellpadding="7" cellspacing="0" title="Summary of reactor features">
+ <tr><td colspan="1" rowspan="1"/><th colspan="1" rowspan="1">Status</th><th colspan="1" rowspan="1">TCP</th><th colspan="1" rowspan="1">SSL</th><th colspan="1" rowspan="1">UDP</th><th colspan="1" rowspan="1">Threading</th><th colspan="1" rowspan="1">Processes</th><th colspan="1" rowspan="1">Scheduling</th><th colspan="1" rowspan="1">Platforms</th></tr>
+ <tr><th colspan="1" rowspan="1">select()</th><td colspan="1" rowspan="1">Stable</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Unix, Win32</td></tr>
+ <tr><th colspan="1" rowspan="1">poll()</th><td colspan="1" rowspan="1">Stable</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Unix</td></tr>
+ <tr><th colspan="1" rowspan="1">Win32 (WFMO)</th><td colspan="1" rowspan="1">Experimental</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Win32</td></tr>
+ <tr><th colspan="1" rowspan="1">Win32 (IOCP)</th><td colspan="1" rowspan="1">Experimental</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">N</td><td colspan="1" rowspan="1">N</td><td colspan="1" rowspan="1">N</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Win32</td></tr>
+ <tr><th colspan="1" rowspan="1">CoreFoundation</th><td colspan="1" rowspan="1">Unmaintained</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">OS X</td></tr>
+ <tr><th colspan="1" rowspan="1">epoll</th><td colspan="1" rowspan="1">Stable</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Linux 2.6</td></tr>
+ <tr><th colspan="1" rowspan="1">Gtk</th><td colspan="1" rowspan="1">Stable</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Unix, Win32</td></tr>
+ <tr><th colspan="1" rowspan="1">wx</th><td colspan="1" rowspan="1">Experimental</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Unix, Win32</td></tr>
+ <tr><th colspan="1" rowspan="1">kqueue</th><td colspan="1" rowspan="1">Experimental</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">Y</td><td colspan="1" rowspan="1">FreeBSD</td></tr>
+ </table>
+
+ <h2>General Purpose Reactors<a name="auto2"/></h2>
+
+ <h3>Select()-based Reactor<a name="auto3"/></h3><a name="select" shape="rect"/>
+
+ <p>The <code>select</code> reactor is currently the default reactor on all
+ platforms. The following code will install it, if no other reactor has
+ been installed:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <p>In the future, if another reactor becomes the default, but the
+ <code>select</code> reactor is desired, it may be installed via:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">selectreactor</span>
+<span class="py-src-variable">selectreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h3>Poll()-based Reactor<a name="auto4"/></h3><a name="poll" shape="rect"/>
+
+ <p>The PollReactor will work on any platform that provides <code class="python">poll()</code> (while OS X provides <code class="python">poll()</code>, it is not recommended to use the
+ PollReactor on OS X due to bugs in its implementation of the call).
+ With larger numbers of connected sockets, it may provide for better
+ performance than the SelectReactor.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pollreactor</span>
+<span class="py-src-variable">pollreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h2>Platform-Specific Reactors<a name="auto5"/></h2>
+
+ <h3>KQueue<a name="auto6"/></h3><a name="kqueue" shape="rect"/>
+
+ <p>The KQueue Reactor allows Twisted to use FreeBSD's kqueue mechanism for
+ event scheduling. See instructions in the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.kqreactor.html" title="twisted.internet.kqreactor">twisted.internet.kqreactor</a></code>'s
+ docstring for installation notes.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">kqreactor</span>
+<span class="py-src-variable">kqreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+
+ <h3>Win32 (WFMO)<a name="auto7"/></h3><a name="win32_wfmo" shape="rect"/>
+
+ <p>The Win32 reactor is not yet complete and has various limitations
+ and issues that need to be addressed. The reactor supports GUI integration
+ with the win32gui module, so it can be used for native Win32 GUI applications.
+ </p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">win32eventreactor</span>
+<span class="py-src-variable">win32eventreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h3>Win32 (IOCP)<a name="auto8"/></h3><a name="win32_iocp" shape="rect"/>
+
+ <p>
+ Windows provides a fast, scalable event notification system known as IO
+ Completion Ports, or IOCP for short. Twisted includes a reactor based
+ on IOCP which is nearly complete. The reactor has a handful of known
+ bugs and lacks SSL support.
+ </p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">iocpreactor</span>
+<span class="py-src-variable">iocpreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h3>Epoll-based Reactor<a name="auto9"/></h3><a name="epoll" shape="rect"/>
+
+ <p>The EPollReactor will work on any platform that provides
+ <code class="python">epoll</code>, today only Linux 2.6 and over. The
+ implementation of the epoll reactor currently uses the Level Triggered
+ interface, which is basically like poll() but scales much better.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">epollreactor</span>
+<span class="py-src-variable">epollreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h2>GUI Integration Reactors<a name="auto10"/></h2>
+
+ <h3>GTK+<a name="auto11"/></h3><a name="gtk" shape="rect"/>
+
+ <p>Twisted integrates with <a href="http://www.pygtk.org/" shape="rect">PyGTK</a>, versions 1.2 (<code>gtkreactor</code>) and 2.0
+ (<code>gtk2reactor</code>). Sample applications using GTK+ and
+ Twisted are available in the Twisted SVN.</p>
+
+ <p>GTK-2.0 split the event loop out of the GUI toolkit, into a separate
+ module called <q>glib</q>. To run an application using the glib event
+ loop, use the <code>glib2reactor</code>. This will be slightly faster
+ than <code>gtk2reactor</code> (and does not require a working X display),
+ but cannot be used to run GUI applications.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">gtkreactor</span> <span class="py-src-comment"># for gtk-1.2</span>
+<span class="py-src-variable">gtkreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">gtk2reactor</span> <span class="py-src-comment"># for gtk-2.0</span>
+<span class="py-src-variable">gtk2reactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">glib2reactor</span> <span class="py-src-comment"># for non-GUI apps</span>
+<span class="py-src-variable">glib2reactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h3>CoreFoundation<a name="auto12"/></h3><a name="cfreactor" shape="rect"/>
+
+ <p>Twisted integrates with <a href="http://pyobjc.sf.net/" shape="rect">PyObjC</a>, version 1.0. Sample applications using Cocoa and Twisted
+ are available in the examples directory under
+ <code>threadedselect/Cocoa</code>.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">cfreactor</span>
+<span class="py-src-variable">cfreactor</span>.<span class="py-src-variable">install</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <h2>Non-Reactor GUI Integration<a name="auto13"/></h2>
+
+ <h3>Tkinter<a name="auto14"/></h3><a name="tkinter" shape="rect"/>
+
+ <p>The support for <a href="http://www.python.org/topics/tkinter/" shape="rect">Tkinter</a> doesn't use a specialized reactor. Instead, there is
+ some specialized support code:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">Tkinter</span> <span class="py-src-keyword">import</span> *
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">tksupport</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Tk</span>()
+
+<span class="py-src-comment"># Install the Reactor support</span>
+<span class="py-src-variable">tksupport</span>.<span class="py-src-variable">install</span>(<span class="py-src-variable">root</span>)
+
+<span class="py-src-comment"># at this point build Tk app as usual using the root object,</span>
+<span class="py-src-comment"># and start the program with &quot;reactor.run()&quot;, and stop it</span>
+<span class="py-src-comment"># with &quot;reactor.stop()&quot;.</span>
+</pre>
+
+ <h3>wxPython<a name="auto15"/></h3><a name="wxpython" shape="rect"/>
+
+ <p>Twisted currently supports two methods of integrating
+ wxPython. Unfortunately, neither method will work on all wxPython
+ platforms (such as GTK2 or Windows). It seems that the only
+ portable way to integrate with wxPython is to run it in a separate
+ thread. One of these methods may be sufficient if your wx app is
+ limited to a single platform.</p>
+
+ <p>As with <a href="#tkinter" shape="rect">Tkinter</a>, the support for integrating
+ Twisted with a <a href="http://www.wxpython.org" shape="rect">wxPython</a>
+ application uses specialized support code rather than a simple reactor.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">wxPython</span>.<span class="py-src-variable">wx</span> <span class="py-src-keyword">import</span> *
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">wxsupport</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">myWxAppInstance</span> = <span class="py-src-variable">wxApp</span>(<span class="py-src-number">0</span>)
+<span class="py-src-variable">wxsupport</span>.<span class="py-src-variable">install</span>(<span class="py-src-variable">myWxAppInstance</span>)
+</pre>
+
+ <p>However, this has issues when running on Windows, so Twisted now
+ comes with alternative wxPython support using a reactor. Using
+ this method is probably better. Initialization is done in two
+ stages. In the first, the reactor is installed:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">wxreactor</span>
+<span class="py-src-variable">wxreactor</span>.<span class="py-src-variable">install</span>()
+</pre>
+
+ <p>Later, once a <code class="python">wxApp</code> instance has
+ been created, but before <code class="python">reactor.run()</code>
+ is called:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">myWxAppInstance</span> = <span class="py-src-variable">wxApp</span>(<span class="py-src-number">0</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">registerWxApp</span>(<span class="py-src-variable">myWxAppInstance</span>)
+</pre>
+
+ <p>An example Twisted application that uses WxWindows can be found
+ in <code class="py-filename">doc/examples/wxdemo.py</code>.</p>
+
+ <h3>PyUI<a name="auto16"/></h3><a name="pyui" shape="rect"/>
+
+ <p>As with <a href="#tkinter" shape="rect">Tkinter</a>, the support for integrating
+ Twisted with a <a href="http://pyui.sourceforge.net" shape="rect">PyUI</a>
+ application uses specialized support code rather than a simple reactor.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pyuisupport</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">pyuisupport</span>.<span class="py-src-variable">install</span>(<span class="py-src-variable">args</span>=(<span class="py-src-number">640</span>, <span class="py-src-number">480</span>), <span class="py-src-variable">kw</span>={<span class="py-src-string">'renderer'</span>: <span class="py-src-string">'gl'</span>})
+</pre>
+
+ <p>An example Twisted application that uses PyUI can be found in <code class="py-filename">doc/examples/pyuidemo.py</code>.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/clients.html b/vendor/Twisted-10.0.0/doc/core/howto/clients.html
new file mode 100644
index 0000000000..191908f45b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/clients.html
@@ -0,0 +1,635 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Writing Clients</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Writing Clients</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Protocol</a></li><li><a href="#auto2">Simple, single-use clients</a></li><li><a href="#auto3">ClientFactory</a></li><ul><li><a href="#auto4">Reconnection</a></li></ul><li><a href="#auto5">A Higher-Level Example: ircLogBot</a></li><ul><li><a href="#auto6">Overview of ircLogBot</a></li><li><a href="#auto7">Persistent Data in the Factory</a></li></ul><li><a href="#auto8">Further Reading</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>Twisted is a framework designed to be very flexible, and let you write
+ powerful clients. The cost of this flexibility is a few layers in the way
+ to writing your client. This document covers creating clients that can be
+ used for TCP, SSL and Unix sockets, UDP is covered <a href="udp.html" shape="rect">in
+ a different document</a>.</p>
+
+ <p>At the base, the place where you actually implement the protocol parsing
+ and handling, is the Protocol class. This class will usually be decended
+ from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">twisted.internet.protocol.Protocol</a></code>. Most
+ protocol handlers inherit either from this class or from one of its
+ convenience children. An instance of the protocol class will be
+ instantiated when you connect to the server, and will go away when the
+ connection is finished. This means that persistent configuration is not
+ saved in the Protocol.</p>
+
+ <p>The persistent configuration is kept in a Factory class, which usually
+ inherits from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.ClientFactory.html" title="twisted.internet.protocol.ClientFactory">twisted.internet.protocol.ClientFactory</a></code>. The default
+ factory class just instantiate the Protocol, and then sets on it an
+ attribute called <code>factory</code> which points to itself. This let
+ the Protocol access, and possibly modify, the persistent
+ configuration.</p>
+
+ <h2>Protocol<a name="auto1"/></h2>
+
+ <p>As mentioned above, this, and auxiliary classes and functions, is where
+ most of the code is. A Twisted protocol handles data in an asynchronous
+ manner. What this means is that the protocol never waits for an event, but
+ rather responds to events as they arrive from the network.</p>
+
+ <p>Here is a simple example:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">stdout</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+</pre>
+
+ <p>This is one of the simplest protocols. It simply writes to standard
+ output whatever it reads from the connection. There are many events it
+ does not respond to. Here is an example of a Protocol responding to
+ another event.</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">WelcomeMessage</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Hello server, I am the client!\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+</pre>
+
+ <p>This protocol connects to the server, sends it a welcome message, and
+ then terminates the connection.</p>
+
+ <p>The connectionMade event is usually where set up of the Protocol
+ object happens, as well as any initial greetings (as in the
+ WelcomeMessage protocol above). Any tearing down of Protocol-specific
+ objects is done in connectionLost.</p>
+
+ <h2>Simple, single-use clients<a name="auto2"/></h2>
+
+ <p>In many cases, the protocol only needs to connect to the server once,
+ and the code just wants to get a connected instance of the protocol. In
+ those cases <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.ClientCreator.html" title="twisted.internet.protocol.ClientCreator">twisted.internet.protocol.ClientCreator</a></code> provides the
+ appropriate API.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>, <span class="py-src-variable">ClientCreator</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Greeter</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">sendMessage</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;MESSAGE %s\n&quot;</span> % <span class="py-src-variable">msg</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">gotProtocol</span>(<span class="py-src-parameter">p</span>):
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">sendMessage</span>(<span class="py-src-string">&quot;Hello&quot;</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">1</span>, <span class="py-src-variable">p</span>.<span class="py-src-variable">sendMessage</span>, <span class="py-src-string">&quot;This is sent in a second&quot;</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">2</span>, <span class="py-src-variable">p</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>)
+
+<span class="py-src-variable">c</span> = <span class="py-src-variable">ClientCreator</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">Greeter</span>)
+<span class="py-src-variable">c</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">1234</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">gotProtocol</span>)
+</pre>
+
+ <h2>ClientFactory<a name="auto3"/></h2>
+
+ <p>We use reactor.connect* and a ClientFactory. The ClientFactory is in
+ charge of creating the Protocol, and also receives events relating to the
+ connection state. This allows it to do things like reconnect on the event
+ of a connection error. Here is an example of a simple ClientFactory that
+ uses the Echo protocol (above) and also prints what state the connection
+ is in.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>, <span class="py-src-variable">ClientFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">stdout</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClientFactory</span>(<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startedConnecting</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Started to connect.'</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Connected.'</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">Echo</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Lost connection. Reason:'</span>, <span class="py-src-variable">reason</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Connection failed. Reason:'</span>, <span class="py-src-variable">reason</span>
+</pre>
+
+ <p>To connect this EchoClientFactory to a server, you could use this
+ code:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>, <span class="py-src-variable">EchoClientFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Note that <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.ClientFactory.clientConnectionFailed.html" title="twisted.internet.protocol.ClientFactory.clientConnectionFailed">clientConnectionFailed</a></code>
+ is called when a connection could not be established, and that <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.ClientFactory.clientConnectionLost.html" title="twisted.internet.protocol.ClientFactory.clientConnectionLost">clientConnectionLost</a></code>
+ is called when a connection was made and then disconnected.</p>
+
+ <h3>Reconnection<a name="auto4"/></h3>
+
+ <p>Many times, the connection of a client will be lost unintentionally due
+ to network errors. One way to reconnect after a disconnection would be to
+ call <code class="python">connector.connect()</code> when the
+ connection is lost:
+ </p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ClientFactory</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClientFactory</span>(<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">connector</span>.<span class="py-src-variable">connect</span>()
+</pre>
+
+ <p>The connector passed as the first argument is the interface between a
+ connection and a protocol. When the connection fails and the factory
+ receives the clientConnectionLost event, the factory can call <code class="python">connector.connect()</code> to start the connection over
+ again from scratch.</p>
+
+ <p>
+ However, most programs that want this functionality should implement <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.ReconnectingClientFactory.html" title="twisted.internet.protocol.ReconnectingClientFactory">ReconnectingClientFactory</a></code> instead,
+ which tries to reconnect if a connection is lost or fails, and which
+ exponentially delays repeated reconnect attempts.
+ </p>
+
+ <p>
+ Here is the Echo protocol implemented with a ReconnectingClientFactory:
+ </p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>, <span class="py-src-variable">ReconnectingClientFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">stdout</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClientFactory</span>(<span class="py-src-parameter">ReconnectingClientFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startedConnecting</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Started to connect.'</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Connected.'</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Resetting reconnection delay'</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">resetDelay</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">Echo</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Lost connection. Reason:'</span>, <span class="py-src-variable">reason</span>
+ <span class="py-src-variable">ReconnectingClientFactory</span>.<span class="py-src-variable">clientConnectionLost</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">connector</span>, <span class="py-src-variable">reason</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Connection failed. Reason:'</span>, <span class="py-src-variable">reason</span>
+ <span class="py-src-variable">ReconnectingClientFactory</span>.<span class="py-src-variable">clientConnectionFailed</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">connector</span>,
+ <span class="py-src-variable">reason</span>)
+</pre>
+
+ <h2>A Higher-Level Example: ircLogBot<a name="auto5"/></h2>
+
+ <h3>Overview of ircLogBot<a name="auto6"/></h3>
+
+ <p>The clients so far have been fairly simple. A more complicated
+ example comes with Twisted Words in the doc/examples directory.</p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+</p><span class="py-src-comment"># twisted imports</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+
+<span class="py-src-comment"># system imports</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>, <span class="py-src-variable">sys</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MessageLogger</span>:
+ <span class="py-src-string">&quot;&quot;&quot;
+ An independent logger class (because separation of application
+ and protocol logic is a good thing).
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">file</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">file</span> = <span class="py-src-variable">file</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">log</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Write a message to the file.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">timestamp</span> = <span class="py-src-variable">time</span>.<span class="py-src-variable">strftime</span>(<span class="py-src-string">&quot;[%H:%M:%S]&quot;</span>, <span class="py-src-variable">time</span>.<span class="py-src-variable">localtime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>()))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">file</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">'%s %s\n'</span> % (<span class="py-src-variable">timestamp</span>, <span class="py-src-variable">message</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">file</span>.<span class="py-src-variable">flush</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">close</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">file</span>.<span class="py-src-variable">close</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LogBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+ <span class="py-src-string">&quot;&quot;&quot;A logging IRC bot.&quot;&quot;&quot;</span>
+
+ <span class="py-src-variable">nickname</span> = <span class="py-src-string">&quot;twistedbot&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span> = <span class="py-src-variable">MessageLogger</span>(<span class="py-src-variable">open</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">filename</span>, <span class="py-src-string">&quot;a&quot;</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;[connected at %s]&quot;</span> %
+ <span class="py-src-variable">time</span>.<span class="py-src-variable">asctime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">localtime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>())))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionLost</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">reason</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;[disconnected at %s]&quot;</span> %
+ <span class="py-src-variable">time</span>.<span class="py-src-variable">asctime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">localtime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>())))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">close</span>()
+
+
+ <span class="py-src-comment"># callbacks for events</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">signedOn</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Called when bot has succesfully signed on to server.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">channel</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">joined</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">channel</span>):
+ <span class="py-src-string">&quot;&quot;&quot;This will get called when the bot joins the channel.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;[I have joined %s]&quot;</span> % <span class="py-src-variable">channel</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-string">&quot;&quot;&quot;This will get called when the bot receives a message.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>, <span class="py-src-number">1</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;&lt;%s&gt; %s&quot;</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">msg</span>))
+
+ <span class="py-src-comment"># Check to see if they're sending me a private message</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">channel</span> == <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>:
+ <span class="py-src-variable">msg</span> = <span class="py-src-string">&quot;It isn't nice to whisper! Play nice with the group.&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">msg</span>)
+ <span class="py-src-keyword">return</span>
+
+ <span class="py-src-comment"># Otherwise check to see if it is a message directed at me</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">msg</span>.<span class="py-src-variable">startswith</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> + <span class="py-src-string">&quot;:&quot;</span>):
+ <span class="py-src-variable">msg</span> = <span class="py-src-string">&quot;%s: I am a log bot&quot;</span> % <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">channel</span>, <span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;&lt;%s&gt; %s&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>, <span class="py-src-variable">msg</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">action</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-string">&quot;&quot;&quot;This will get called when the bot sees someone do an action.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>, <span class="py-src-number">1</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;* %s %s&quot;</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">msg</span>))
+
+ <span class="py-src-comment"># irc callbacks</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">irc_NICK</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">prefix</span>, <span class="py-src-parameter">params</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Called when an IRC user changes their nickname.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">old_nick</span> = <span class="py-src-variable">prefix</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">new_nick</span> = <span class="py-src-variable">params</span>[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;%s is now known as %s&quot;</span> % (<span class="py-src-variable">old_nick</span>, <span class="py-src-variable">new_nick</span>))
+
+
+ <span class="py-src-comment"># For fun, override the method that determines how a nickname is changed on</span>
+ <span class="py-src-comment"># collisions. The default method appends an underscore.</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">alterCollidedNick</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">nickname</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Generate an altered version of a nickname that caused a collision in an
+ effort to create an unused related name for subsequent registration.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">nickname</span> + <span class="py-src-string">'^'</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LogBotFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-string">&quot;&quot;&quot;A factory for LogBots.
+
+ A new protocol instance will be created each time we connect to the server.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-comment"># the class of the protocol to build when new connection is made</span>
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">LogBot</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">channel</span> = <span class="py-src-variable">channel</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-string">&quot;&quot;&quot;If we get disconnected, reconnect to server.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">connector</span>.<span class="py-src-variable">connect</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;connection failed:&quot;</span>, <span class="py-src-variable">reason</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-comment"># initialize logging</span>
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">startLogging</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">stdout</span>)
+
+ <span class="py-src-comment"># create factory protocol and application</span>
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">LogBotFactory</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">1</span>], <span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">2</span>])
+
+ <span class="py-src-comment"># connect factory to this host and port</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;irc.freenode.net&quot;</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">f</span>)
+
+ <span class="py-src-comment"># run bot</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="../../words/examples/ircLogBot.py"><span class="filename">../../words/examples/ircLogBot.py</span></a></div></div>
+
+ <p>ircLogBot.py connects to an IRC server, joins a channel, and logs all
+ traffic on it to a file. It demonstrates some of the connection-level
+ logic of reconnecting on a lost connection, as well as storing persistent
+ data in the Factory.</p>
+
+ <h3>Persistent Data in the Factory<a name="auto7"/></h3>
+
+ <p>Since the Protocol instance is recreated each time the connection is
+ made, the client needs some way to keep track of data that should be
+ persisted. In the case of the logging bot, it needs to know which channel
+ it is logging, and where to log it to.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LogBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span> = <span class="py-src-variable">MessageLogger</span>(<span class="py-src-variable">open</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">filename</span>, <span class="py-src-string">&quot;a&quot;</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">logger</span>.<span class="py-src-variable">log</span>(<span class="py-src-string">&quot;[connected at %s]&quot;</span> %
+ <span class="py-src-variable">time</span>.<span class="py-src-variable">asctime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">localtime</span>(<span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>())))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">signedOn</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">channel</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LogBotFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">LogBot</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">channel</span> = <span class="py-src-variable">channel</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+</pre>
+
+ <p>When the protocol is created, it gets a reference to the factory as
+ self.factory. It can then access attributes of the factory in its logic.
+ In the case of LogBot, it opens the file and connects to the channel
+ stored in the factory.</p>
+
+ <h2>Further Reading<a name="auto8"/></h2>
+
+ <p>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">Protocol</a></code>
+ class used throughout this document is a base implementation of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IProtocol.html" title="twisted.internet.interfaces.IProtocol">IProtocol</a></code> used in
+ most Twisted applications for convenience. To learn about the
+ complete<code>IProtocol</code> interface, see the API documentation for
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IProtocol.html" title="twisted.internet.interfaces.IProtocol">IProtocol</a></code>.</p>
+
+ <p>The <code>transport</code> attribute used in some examples in this
+ document provides the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.
+.html" title="twisted.internet.interfaces.
+">
+ ITCPTransport</a></code> interface. To learn about the complete interface, see
+ the API documentation for <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.ITCPTransport.html" title="twisted.internet.interfaces.ITCPTransport">ITCPTransport</a></code>.</p>
+
+ <p>Interface classes are a way of specifying what methods and attributes an
+ object has and how they behave. See the <a href="components.html" shape="rect">
+ Components: Interfaces and Adapters</a> document.</p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/components.html b/vendor/Twisted-10.0.0/doc/core/howto/components.html
new file mode 100644
index 0000000000..1feb5a7b7e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/components.html
@@ -0,0 +1,600 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Components: Interfaces and Adapters</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Components: Interfaces and Adapters</h1>
+ <div class="toc"><ol><li><a href="#auto0">Interfaces and Components in Twisted code</a></li><ul><li><a href="#auto1">Components and Inheritance</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<p>Object oriented programming languages allow programmers to reuse portions of
+existing code by creating new <q>classes</q> of objects which subclass another
+class. When a class subclasses another, it is said to <em>inherit</em> all of its
+behaviour. The subclass can then <q>override</q> and <q>extend</q> the behavior
+provided to it by the superclass. Inheritance is very useful in many situations,
+but because it is so convenient to use, often becomes abused in large software
+systems, especially when multiple inheritance is involved. One solution is to
+use <em>delegation</em> instead of <q>inheritance</q> where appropriate.
+Delegation is simply the act of asking <em>another</em> object to perform a task
+for an object. To support this design pattern, which is often referred to as the
+<em>components</em> pattern because it involves many small interacting components,
+<em>interfaces</em> and <em>adapters</em> were created by the Zope 3 team.</p>
+
+<p><q>Interfaces</q> are simply markers which objects can use to say <q>I
+implement this interface</q>. Other objects may then make requests like
+<q>Please give me an object which implements interface X for object type Y</q>.
+Objects which implement an interface for another object type are called
+<q>adapters</q>.</p>
+
+<p>The superclass-subclass relationship is said to be an <em>is-a</em> relationship.
+When designing object hierarchies, object modellers use subclassing when they
+can say that the subclass <em>is</em> the same class as the superclass. For
+example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Shape</span>:
+ <span class="py-src-variable">sideLength</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getSideLength</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setSideLength</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">sideLength</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span> = <span class="py-src-variable">sideLength</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">area</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>, <span class="py-src-string">&quot;Subclasses must implement area&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Triangle</span>(<span class="py-src-parameter">Shape</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">area</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> (<span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span> * <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span>) / <span class="py-src-number">2</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Square</span>(<span class="py-src-parameter">Shape</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">area</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span> * <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span>
+</pre>
+
+<p>In the above example, a Triangle <em>is-a</em> Shape, so it subclasses Shape,
+and a Square <em>is-a</em> Shape, so it also subclasses Shape.</p>
+
+<p>However, subclassing can get complicated, especially when Multiple
+Inheritance enters the picture. Multiple Inheritance allows a class to inherit
+from more than one base class. Software which relies heavily on inheritance
+often ends up having both very wide and very deep inheritance trees, meaning
+that one class inherits from many superclasses spread throughout the system.
+Since subclassing with Multiple Inheritance means <em>implementation
+inheritance</em>, locating a method's actual implementation and ensuring the
+correct method is actually being invoked becomes a challenge. For example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Area</span>:
+ <span class="py-src-variable">sideLength</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getSideLength</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setSideLength</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">sideLength</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span> = <span class="py-src-variable">sideLength</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">area</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>, <span class="py-src-string">&quot;Subclasses must implement area&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Color</span>:
+ <span class="py-src-variable">color</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setColor</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">color</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">color</span> = <span class="py-src-variable">color</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getColor</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">color</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Square</span>(<span class="py-src-parameter">Area</span>, <span class="py-src-parameter">Color</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">area</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span> * <span class="py-src-variable">self</span>.<span class="py-src-variable">sideLength</span>
+</pre>
+
+<p>The reason programmers like using implementation inheritance is because it
+makes code easier to read since the implementation details of Area are in a
+separate place than the implementation details of Color. This is nice, because
+conceivably an object could have a color but not an area, or an area but not a
+color. The problem, though, is that Square is not really an Area or a Color, but
+has an area and color. Thus, we should really be using another object oriented
+technique called <em>composition</em>, which relies on delegation rather than
+inheritance to break code into small reusable chunks. Let us continue with the
+Multiple Inheritance example, though, because it is often used in practice.</p>
+
+<p>What if both the Color and the Area base class defined the same method,
+perhaps <code>calculate</code>? Where would the implementation come from? The
+implementation that is located for <code>Square().calculate()</code> depends on
+the method resolution order, or MRO, and can change when programmers change
+seemingly unrelated things by refactoring classes in other parts of the system,
+causing obscure bugs. Our first thought might be to change the calculate method
+name to avoid name clashes, to perhaps <code>calculateArea</code> and
+<code>calculateColor</code>. While explicit, this change could potentially
+require a large number of changes throughout a system, and is error-prone,
+especially when attempting to integrate two systems which you didn't write.</p>
+
+<p>Let's imagine another example. We have an electric appliance, say a hair
+dryer. The hair dryer is american voltage. We have two electric sockets, one of
+them an american 110 Volt socket, and one of them a foreign 220 Volt socket. If
+we plug the hair dryer into the 220 Volt socket, it is going to expect 110 Volt
+current and errors will result. Going back and changing the hair dryer to
+support both <code>plug110Volt</code> and <code>plug220Volt</code> methods would
+be tedious, and what if we decided we needed to plug the hair dryer into yet
+another type of socket? For example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">HairDryer</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">plug</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">socket</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">socket</span>.<span class="py-src-variable">voltage</span>() == <span class="py-src-number">110</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I was plugged in properly and am operating.&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I was plugged in improperly and &quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;now you have no hair dryer any more.&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AmericanSocket</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-number">110</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ForeignSocket</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-number">220</span>
+</pre>
+
+<p>Given these classes, the following operations can be performed:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; hd = HairDryer()
+&gt;&gt;&gt; am = AmericanSocket()
+&gt;&gt;&gt; hd.plug(am)
+I was plugged in properly and am operating.
+&gt;&gt;&gt; fs = ForeignSocket()
+&gt;&gt;&gt; hd.plug(fs)
+I was plugged in improperly and
+now you have no hair dryer any more.
+</pre>
+
+<p>We are going to attempt to solve this problem by writing an Adapter for the
+<code>ForeignSocket</code> which converts the voltage for use with an American
+hair dryer. An Adapter is a class which is constructed with one and only one
+argument, the <q>adaptee</q> or <q>original</q> object. In this example, we
+will show all code involved for clarity:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">AdaptToAmericanSocket</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">original</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span> = <span class="py-src-variable">original</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span>.<span class="py-src-variable">voltage</span>() / <span class="py-src-number">2</span>
+</pre>
+
+<p>Now, we can use it as so:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; hd = HairDryer()
+&gt;&gt;&gt; fs = ForeignSocket()
+&gt;&gt;&gt; adapted = AdaptToAmericanSocket(fs)
+&gt;&gt;&gt; hd.plug(adapted)
+I was plugged in properly and am operating.
+</pre>
+
+<p>So, as you can see, an adapter can 'override' the original implementation. It
+can also 'extend' the interface of the original object by providing methods the
+original object did not have. Note that an Adapter must explicitly delegate any
+method calls it does not wish to modify to the original, otherwise the Adapter
+cannot be used in places where the original is expected. Usually this is not a
+problem, as an Adapter is created to conform an object to a particular interface
+and then discarded.</p>
+
+<h2>Interfaces and Components in Twisted code<a name="auto0"/></h2>
+
+<p>Adapters are a useful way of using multiple classes to factor code into
+discrete chunks. However, they are not very interesting without some more
+infrastructure. If each piece of code which wished to use an adapted object had
+to explicitly construct the adapter itself, the coupling between components
+would be too tight. We would like to achieve <q>loose coupling</q>, and this is
+where <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.html" title="twisted.python.components">twisted.python.components</a></code> comes in.</p>
+
+<p>First, we need to discuss Interfaces in more detail. As we mentioned
+earlier, an Interface is nothing more than a class which is used as a marker.
+Interfaces should be subclasses of <code>zope.interface.Interface</code>, and
+have a very odd look to python programmers not used to them:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IAmericanSocket</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return the voltage produced by this socket object, as an integer.
+ &quot;&quot;&quot;</span>
+</pre>
+
+<p>Notice how it looks just like a regular class definition, other than
+inheriting from <code>Interface</code>? However, the method definitions inside
+the class block do not have any method body! Since Python does not have any
+native language-level support for Interfaces like Java does, this is what
+distinguishes an Interface definition from a Class.</p>
+
+<p>Now that we have a defined Interface, we can talk about objects using terms
+like this: <q>The <code>AmericanSocket</code> class implements the
+<code>IAmericanSocket</code> interface</q> and <q>Please give me an object which
+adapts <code>ForeignSocket</code> to the <code>IAmericanSocket</code>
+interface</q>. We can make <em>declarations</em> about what interfaces a certain
+class implements, and we can request adapters which implement a certain
+interface for a specific class.</p>
+
+<p>Let's look at how we declare that a class implements an interface:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AmericanSocket</span>:
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IAmericanSocket</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-number">110</span>
+</pre>
+
+<p>So, to declare that a class implements an interface, we simply call
+<code>zope.interface.implements</code> at the class level.</p>
+
+<p>Now, let's say we want to rewrite the <code>AdaptToAmericanSocket</code>
+class as a real adapter. In this case we also specify it as implementing
+<code>IAmericanSocket</code>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AdaptToAmericanSocket</span>:
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IAmericanSocket</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">original</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Pass the original ForeignSocket object as original
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span> = <span class="py-src-variable">original</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span>.<span class="py-src-variable">voltage</span>() / <span class="py-src-number">2</span>
+</pre>
+
+<p>Notice how we placed the implements declaration on this adapter class. So
+far, we have not achieved anything by using components other than requiring us
+to type more. In order for components to be useful, we must use the
+<em>component registry</em>. Since <code>AdaptToAmericanSocket</code> implements
+<code>IAmericanSocket</code> and regulates the voltage of a
+<code>ForeignSocket</code> object, we can <em>register
+<code>AdaptToAmericanSocket</code> as an <code>IAmericanSocket</code> adapter
+for the <code>ForeignSocket</code> class</em>. It is easier to see how this is
+done in code than to describe it:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IAmericanSocket</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return the voltage produced by this socket object, as an integer.
+ &quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AmericanSocket</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IAmericanSocket</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-number">110</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ForeignSocket</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-number">220</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">AdaptToAmericanSocket</span>:
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IAmericanSocket</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">original</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span> = <span class="py-src-variable">original</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">voltage</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">original</span>.<span class="py-src-variable">voltage</span>() / <span class="py-src-number">2</span>
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(
+ <span class="py-src-variable">AdaptToAmericanSocket</span>,
+ <span class="py-src-variable">ForeignSocket</span>,
+ <span class="py-src-variable">IAmericanSocket</span>)
+</pre>
+
+<p>Now, if we run this script in the interactive interpreter, we can discover a
+little more about how to use components. The first thing we can do is discover
+whether an object implements an interface or not:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; IAmericanSocket.implementedBy(AmericanSocket)
+True
+&gt;&gt;&gt; IAmericanSocket.implementedBy(ForeignSocket)
+False
+&gt;&gt;&gt; am = AmericanSocket()
+&gt;&gt;&gt; fs = ForeignSocket()
+&gt;&gt;&gt; IAmericanSocket.providedBy(am)
+True
+&gt;&gt;&gt; IAmericanSocket.providedBy(fs)
+False
+</pre>
+
+<p>As you can see, the <code>AmericanSocket</code> instance claims to
+implement <code>IAmericanSocket</code>, but the <code>ForeignSocket</code>
+does not. If we wanted to use the <code>HairDryer</code> with the
+<code>AmericanSocket</code>, we could know that it would be safe to do so by
+checking whether it implements <code>IAmericanSocket</code>. However, if we
+decide we want to use <code>HairDryer</code> with a <code>ForeignSocket</code>
+instance, we must <em>adapt</em> it to <code>IAmericanSocket</code> before
+doing so. We use the interface object to do this:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; IAmericanSocket(fs)
+&lt;__main__.AdaptToAmericanSocket instance at 0x1a5120&gt;
+</pre>
+
+<p>When calling an interface with an object as an argument, the interface
+looks in the adapter registry for an adapter which implements the interface for
+the given instance's class. If it finds one, it constructs an instance of the
+Adapter class, passing the constructor the original instance, and returns it.
+Now the <code>HairDryer</code> can safely be used with the adapted
+<code>ForeignSocket</code>. But what happens if we attempt to adapt an object
+which already implements <code>IAmericanSocket</code>? We simply get back the
+original instance:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; IAmericanSocket(am)
+&lt;__main__.AmericanSocket instance at 0x36bff0&gt;
+</pre>
+
+<p>So, we could write a new <q>smart</q> <code>HairDryer</code> which
+automatically looked up an adapter for the socket you tried to plug it into:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">HairDryer</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">plug</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">socket</span>):
+ <span class="py-src-variable">adapted</span> = <span class="py-src-variable">IAmericanSocket</span>(<span class="py-src-variable">socket</span>)
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">adapted</span>.<span class="py-src-variable">voltage</span>() == <span class="py-src-number">110</span>, <span class="py-src-string">&quot;BOOM&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I was plugged in properly and am operating&quot;</span>
+</pre>
+
+<p>Now, if we create an instance of our new <q>smart</q> <code>HairDryer</code>
+and attempt to plug it in to various sockets, the <code>HairDryer</code> will
+adapt itself automatically depending on the type of socket it is plugged in
+to:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; am = AmericanSocket()
+&gt;&gt;&gt; fs = ForeignSocket()
+&gt;&gt;&gt; hd = HairDryer()
+&gt;&gt;&gt; hd.plug(am)
+I was plugged in properly and am operating
+&gt;&gt;&gt; hd.plug(fs)
+I was plugged in properly and am operating
+</pre>
+
+<p>Voila; the magic of components.</p>
+
+<h3>Components and Inheritance<a name="auto1"/></h3>
+
+<p>If you inherit from a class which implements some interface, and your new
+subclass declares that it implements another interface, the implements will be
+inherited by default.</p>
+
+<p>For example, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code> is a class
+which implements <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.IPBRoot.html" title="twisted.spread.pb.IPBRoot">IPBRoot</a></code>. This interface indicates that an
+object has remotely-invokable methods and can be used as the initial object
+served by a new Broker instance. It has an <code>implements</code> setting
+like:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Root</span>(<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPBRoot</span>)
+</pre>
+
+<p>Suppose you have your own class which implements your
+<code>IMyInterface</code> interface:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>, <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMyInterface</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyThing</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IMyInterface</span>)
+</pre>
+
+<p>Now if you want to make this class inherit from <code>pb.Root</code>,
+the interfaces code will automatically determine that it also implements
+<code>IPBRoot</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>, <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMyInterface</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyThing</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IMyInterface</span>)
+</pre>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; from twisted.spread.flavors import IPBRoot
+&gt;&gt;&gt; IPBRoot.implementedBy(MyThing)
+True
+</pre>
+
+<p>If you want <code>MyThing</code> to inherit from <code>pb.Root</code> but
+<em>not</em> implement <code>IPBRoot</code> like <code>pb.Root</code> does,
+use <code>implementOnly</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implementsOnly</span>, <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMyInterface</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyThing</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-variable">implementsOnly</span>(<span class="py-src-variable">IMyInterface</span>)
+</pre>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; from twisted.spread.pb import IPBRoot
+&gt;&gt;&gt; IPBRoot.implementedBy(MyThing)
+False
+</pre>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/cred.html b/vendor/Twisted-10.0.0/doc/core/howto/cred.html
new file mode 100644
index 0000000000..7b420fa382
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/cred.html
@@ -0,0 +1,566 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Cred: Pluggable Authentication</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Cred: Pluggable Authentication</h1>
+ <div class="toc"><ol><li><a href="#auto0">Goals</a></li><li><a href="#auto1">Cred objects</a></li><ul><li><a href="#auto2">The Portal</a></li><li><a href="#auto3">The CredentialChecker</a></li><li><a href="#auto4">The Credentials</a></li><li><a href="#auto5">The Realm</a></li><li><a href="#auto6">The Avatar</a></li><li><a href="#auto7">The Mind</a></li></ul><li><a href="#auto8">Responsibilities</a></li><ul><li><a href="#auto9">Server protocol implementation</a></li><li><a href="#auto10">Application implementation</a></li><li><a href="#auto11">Deployment</a></li></ul><li><a href="#auto12">Cred plugins</a></li><ul><li><a href="#auto13">Authentication with cred plugins</a></li><li><a href="#auto14">Building a cred plugin</a></li></ul><li><a href="#auto15">Conclusion</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Goals<a name="auto0"/></h2>
+
+<p>Cred is a pluggable authentication system for servers. It allows any
+number of network protocols to connect and authenticate to a system, and
+communicate to those aspects of the system which are meaningful to the specific
+protocol. For example, Twisted's POP3 support passes a <q>username and
+password</q> set of credentials to get back a mailbox for the specified email
+account. IMAP does the same, but retrieves a slightly different view of the
+same mailbox, enabling those features specific to IMAP which are not available
+in other mail protocols.</p>
+
+<p>Cred is designed to allow both the backend implementation of the business
+logic - called the <em>avatar</em> - and the authentication database - called
+the <em>credential checker</em> - to be decided during deployment. For example,
+the same POP3 server should be able to authenticate against the local UNIX
+password database or an LDAP server without having to know anything about how
+or where mail is stored. </p>
+
+<p>To sketch out how this works - a <q>Realm</q> corresponds to an application
+domain and is in charge of avatars, which are network-accessible business logic
+objects. To connect this to an authentication database, a top-level object
+called a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.Portal.html" title="twisted.cred.portal.Portal">Portal</a></code> stores a
+realm, and a number of credential checkers. Something that wishes to log in,
+such as a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">Protocol</a></code>,
+stores a reference to the portal. Login consists of passing credentials and a
+request interface (e.g. POP3's <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.protocols.pop3.IMailbox.html" title="twisted.protocols.pop3.IMailbox">IMailbox</a></code>) to the portal. The portal passes
+the credentials to the appropriate credential checker, which returns an avatar
+ID. The ID is passed to the realm, which returns the appropriate avatar. For a
+Portal that has a realm that creates mailbox objects and a credential checker
+that checks /etc/passwd, login consists of passing in a username/password and
+the IMailbox interface to the portal. The portal passes this to the /etc/passwd
+credential checker, gets back a avatar ID corresponding to an email account,
+passes that to the realm and gets back a mailbox object for that email
+account.</p>
+
+<p>Putting all this together, here's how a login request will typically be
+processed:</p>
+
+<img src="../img/cred-login.png" title="Cred Login"/>
+
+ <h2>Cred objects<a name="auto1"/></h2>
+ <h3>The Portal<a name="auto2"/></h3>
+<p>This is the the core of login, the point of integration between all the objects
+in the cred system. There is one
+concrete implementation of Portal, and no interface - it does a very
+simple task. A <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.Portal.html" title="twisted.cred.portal.Portal">Portal</a></code>
+associates one (1) Realm with a collection of
+CredentialChecker instances. (More on those later.)</p>
+
+<p>If you are writing a protocol that needs to authenticate against
+something, you will need a reference to a Portal, and to nothing else.
+This has only 2 methods -</p>
+
+<ul>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.Portal.login.html" title="twisted.cred.portal.Portal.login">login</a></code><code>(credentials, mind, *interfaces)</code>
+
+<p>The docstring is quite expansive (see <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.html" title="twisted.cred.portal">twisted.cred.portal</a></code>), but in
+brief, this is what you call when you need to call in order to connect
+a user to the system. Typically you only pass in one interface, and the mind is
+<code class="python">None</code>. The interfaces are the possible interfaces the returned
+avatar is expected to implement, in order of preference.
+The result is a deferred which fires a tuple of:</p>
+ <ul>
+ <li>interface the avatar implements (which was one of the interfaces passed in the *interfaces
+tuple)</li>
+ <li>an object that implements that interface (an avatar)</li>
+ <li>logout, a 0-argument callable which disconnects the connection that was
+established by this call to login</li>
+ </ul>
+<p>The logout method has to be called when the avatar is logged out. For POP3 this means
+when the protocol is disconnected or logged out, etc..</p>
+</li>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.Portal.registerChecker.html" title="twisted.cred.portal.Portal.registerChecker">registerChecker</a></code><code>(checker, *credentialInterfaces)</code>
+
+<p>which adds a CredentialChecker to the portal. The optional list of interfaces are interfaces of credentials
+that the checker is able to check.</p>
+</li></ul>
+
+ <h3>The CredentialChecker<a name="auto3"/></h3>
+
+<p>This is an object implementing <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.checkers.ICredentialsChecker.html" title="twisted.cred.checkers.ICredentialsChecker">ICredentialsChecker</a></code> which resolves some
+credentials to an avatar ID.
+
+Whether the credentials are stored in an in-memory data structure, an
+Apache-style htaccess file, a UNIX password database, an SSH key database,
+or any other form, an implementation of <code>ICredentialsChecker</code> is
+how this data is connected to cred.
+
+A credential checker
+stipulates some requirements of the credentials it can check by
+specifying a credentialInterfaces attribute, which is a list of
+interfaces. Credentials passed to its requestAvatarId method must
+implement one of those interfaces.</p>
+
+<p>For the most part, these things will just check usernames and passwords
+and produce the username as the result, but hopefully we will be seeing
+some public-key, challenge-response, and certificate based credential
+checker mechanisms soon.</p>
+
+<p>A credential checker should raise an error if it cannot authenticate
+the user, and return <code>twisted.cred.checkers.ANONYMOUS</code>
+for anonymous access.</p>
+
+ <h3>The Credentials<a name="auto4"/></h3>
+<p>Oddly enough, this represents some credentials that the user presents.
+Usually this will just be a small static blob of data, but in some
+cases it will actually be an object connected to a network protocol.
+For example, a username/password pair is static, but a
+challenge/response server is an active state-machine that will require
+several method calls in order to determine a result.</p>
+
+<p>Twisted comes with a number of credentials interfaces and implementations
+in the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.credentials.html" title="twisted.cred.credentials">twisted.cred.credentials</a></code> module,
+such as <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.credentials.IUsernamePassword.html" title="twisted.cred.credentials.IUsernamePassword">IUsernamePassword</a></code>
+and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.credentials.IUsernameHashedPassword.html" title="twisted.cred.credentials.IUsernameHashedPassword">IUsernameHashedPassword</a></code>.</p>
+
+ <h3>The Realm<a name="auto5"/></h3>
+<p>A realm is an interface which connects your universe of <q>business
+objects</q> to the authentication system.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.IRealm.html" title="twisted.cred.portal.IRealm">IRealm</a></code> is another one-method interface:</p>
+
+<ul>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.IRealm.requestAvatar.html" title="twisted.cred.portal.IRealm.requestAvatar">requestAvatar</a></code><code>(avatarId, mind, *interfaces)</code>
+
+<p>This method will typically be called from 'Portal.login'. The avatarId
+is the one returned by a CredentialChecker.</p>
+
+<div class="note"><strong>Note: </strong>Note that <code>avatarId</code> must always be a string. In
+particular, do not use unicode strings. If internationalized support is needed,
+it is recommended to use UTF-8, and take care of decoding in the realm. </div>
+
+<p>The important thing to realize about this method is that if it is being
+called, <em>the user has already authenticated</em>. Therefore, if possible,
+the Realm should create a new user if one does not already exist
+whenever possible. Of course, sometimes this will be impossible
+without more information, and that is the case that the interfaces
+argument is for.</p>
+</li>
+</ul>
+
+<p>Since requestAvatar should be called from a Deferred callback, it may
+return a Deferred or a synchronous result.</p>
+
+ <h3>The Avatar<a name="auto6"/></h3>
+
+<p>An avatar is a business logic object for a specific user. For POP3, it's
+a mailbox, for a first-person-shooter it's the object that interacts with
+the game, the actor as it were. Avatars are specific to an application,
+and each avatar represents a single <q>user</q>.</p>
+
+ <h3>The Mind<a name="auto7"/></h3>
+
+<p>As mentioned before, the mind is usually None, so you can skip this
+bit if you want.</p>
+
+<p>Masters of Perspective Broker already know this object as the ill-named
+<q>client object</q>. There is no <q>mind</q> class, or even interface, but it
+is an object which serves an important role - any notifications which are to be
+relayed to an authenticated client are passed through a 'mind'. In addition, it
+allows passing more information to the realm during login in addition to the
+avatar ID.</p>
+
+<p>The name may seem rather unusual, but considering that a Mind is
+representative of the entity on the <q>other end</q> of a network connection
+that is both receiving updates and issuing commands, I believe it is
+appropriate.</p>
+
+<p>Although many protocols will not use this, it serves an important role.
+ It is provided as an argument both to the Portal and to the Realm,
+although a CredentialChecker should interact with a client program
+exclusively through a Credentials instance.</p>
+
+<p>Unlike the original Perspective Broker <q>client object</q>, a Mind's
+implementation is most often dictated by the protocol that is
+connecting rather than the Realm. A Realm which requires a particular
+interface to issue notifications will need to wrap the Protocol's mind
+implementation with an adapter in order to get one that conforms to its
+expected interface - however, Perspective Broker will likely continue
+to use the model where the client object has a pre-specified remote
+interface.</p>
+
+<p>(If you don't quite understand this, it's fine. It's hard to explain,
+and it's not used in simple usages of cred, so feel free to pass None
+until you find yourself requiring something like this.)</p>
+
+ <h2>Responsibilities<a name="auto8"/></h2>
+
+ <h3>Server protocol implementation<a name="auto9"/></h3>
+
+<p>The protocol implementor should define the interface the avatar should implement,
+and design the protocol to have a portal attached. When a user logs in using the
+protocol, a credential object is created, passed to the portal, and an avatar
+with the appropriate interface is requested. When the user logs out or the protocol
+is disconnected, the avatar should be logged out.</p>
+
+<p>The protocol designer should not hardcode how users are authenticated or the
+realm implemented. For example, a POP3 protocol implementation would require a portal whose
+realm returns avatars implementing IMailbox and whose credential checker accepts
+username/password credentials, but that is all. Here's a sketch of how the code
+might look - note that USER and PASS are the protocol commands used to login, and
+the DELE command can only be used after you are logged in:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>, <span class="py-src-variable">error</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMailbox</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Interface specification for mailbox.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">deleteMessage</span>(<span class="py-src-parameter">index</span>): <span class="py-src-keyword">pass</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">POP3</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-comment"># ...</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">portal</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">portal</span> = <span class="py-src-variable">portal</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">do_DELE</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">i</span>):
+ <span class="py-src-comment"># uses self.mbox, which is set after login</span>
+ <span class="py-src-variable">i</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">i</span>)-<span class="py-src-number">1</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">mbox</span>.<span class="py-src-variable">deleteMessage</span>(<span class="py-src-variable">i</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">successResponse</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">do_USER</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_userIs</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">successResponse</span>(<span class="py-src-string">'USER accepted, send PASS'</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">do_PASS</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">password</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_userIs</span> <span class="py-src-keyword">is</span> <span class="py-src-variable">None</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">failResponse</span>(<span class="py-src-string">&quot;USER required before PASS&quot;</span>)
+ <span class="py-src-keyword">return</span>
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">_userIs</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_userIs</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">maybeDeferred</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">authenticateUserPASS</span>, <span class="py-src-variable">user</span>, <span class="py-src-variable">password</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cbMailbox</span>, <span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">authenticateUserPASS</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">password</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">is</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">None</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">portal</span>.<span class="py-src-variable">login</span>(
+ <span class="py-src-variable">cred</span>.<span class="py-src-variable">credentials</span>.<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">password</span>),
+ <span class="py-src-variable">None</span>,
+ <span class="py-src-variable">IMailbox</span>
+ )
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">error</span>.<span class="py-src-variable">UnauthorizedLogin</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cbMailbox</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">ial</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">interface</span>, <span class="py-src-variable">avatar</span>, <span class="py-src-variable">logout</span> = <span class="py-src-variable">ial</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">interface</span> <span class="py-src-keyword">is</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">IMailbox</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">failResponse</span>(<span class="py-src-string">'Authentication failed'</span>)
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>(<span class="py-src-string">&quot;_cbMailbox() called with an interface other than IMailbox&quot;</span>)
+ <span class="py-src-keyword">return</span>
+
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">mbox</span> = <span class="py-src-variable">avatar</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_onLogout</span> = <span class="py-src-variable">logout</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">successResponse</span>(<span class="py-src-string">'Authentication succeeded'</span>)
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">msg</span>(<span class="py-src-string">&quot;Authenticated login for &quot;</span> + <span class="py-src-variable">user</span>)
+</pre>
+
+ <h3>Application implementation<a name="auto10"/></h3>
+
+<p>The application developer can implement realms and credential checkers. For example,
+she might implement a realm that returns IMailbox implementing avatars, using MySQL
+for storage, or perhaps a credential checker that uses LDAP for authentication.
+In the following example, the Realm for a simple remote object service (using
+Twisted's Perspective Broker protocol) is implemented:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimplePerspective</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">text</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'echoing'</span>,<span class="py-src-variable">text</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">text</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">logout</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">self</span>, <span class="py-src-string">&quot;logged out&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-variable">avatar</span> = <span class="py-src-variable">SimplePerspective</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">avatar</span>, <span class="py-src-variable">avatar</span>.<span class="py-src-variable">logout</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>(<span class="py-src-string">&quot;no interface&quot;</span>)
+</pre>
+
+ <h3>Deployment<a name="auto11"/></h3>
+
+<p>Deployment involves tying together a protocol, an appropriate realm and a credential
+checker. For example, a POP3 server can be constructed by attaching to it a portal
+that wraps the MySQL-based realm and an /etc/passwd credential checker, or perhaps
+the LDAP credential checker if that is more useful. The following example shows
+how the SimpleRealm in the previous example is deployed using an in-memory credential checker:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>
+
+<span class="py-src-variable">portal</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">SimpleRealm</span>())
+<span class="py-src-variable">checker</span> = <span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>()
+<span class="py-src-variable">checker</span>.<span class="py-src-variable">addUser</span>(<span class="py-src-string">&quot;guest&quot;</span>, <span class="py-src-string">&quot;password&quot;</span>)
+<span class="py-src-variable">portal</span>.<span class="py-src-variable">registerChecker</span>(<span class="py-src-variable">checker</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">9986</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">portal</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h2>Cred plugins<a name="auto12"/></h2>
+
+ <h3>Authentication with cred plugins<a name="auto13"/></h3>
+
+<p> Cred offers a plugin architecture for authentication methods. The
+primary API for this architecture is the command-line; the plugins are
+meant to be specified by the end-user when deploying a TAP (twistd
+plugin).</p>
+
+<p> For more information on writing a twistd plugin and using cred
+plugins for your application, please refer to the <a href="tap.html" shape="rect">Writing a twistd plugin</a> document.</p>
+
+ <h3>Building a cred plugin<a name="auto14"/></h3>
+
+<p> To build a plugin for cred, you should first define an <code class="python">authType</code>, a short one-word string that defines
+your plugin to the command-line. Once you have this, the convention is
+to create a file named <code>myapp_plugins.py</code> in the
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugins.html" title="twisted.plugins">twisted.plugins</a></code> module path. </p>
+
+<p> Below is an example file structure for an application that defines
+such a plugin: </p>
+
+<ul>
+<li>MyApplication/
+ <ul>
+ <li>setup.py</li>
+ <li>myapp/
+ <ul>
+ <li>__init__.py</li>
+ <li>cred.py</li>
+ <li>server.py</li>
+ </ul>
+ </li>
+ <li>twisted/
+ <ul>
+ <li>plugins/
+ <ul>
+ <li>myapp_plugins.py</li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+</li>
+</ul>
+
+<p>
+Once you have created this structure within your application, you can
+create the code for your cred plugin by building a factory class which
+implements <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.checkers.ICheckerFactory.html" title="twisted.cred.checkers.ICheckerFactory">ICheckerFactory</a></code>.
+These factory classes should not consist of a tremendous amount of
+code. Most of the real application logic should reside in the cred
+checker itself. (For help on building those, scroll up.)
+</p>
+
+<p>
+The core purpose of the CheckerFactory is to translate an <code class="python">argstring</code>, which is passed on the command line,
+into a suitable set of initialization parameters for a Checker
+class. In most cases this should be little more than constructing a
+dictionary or a tuple of arguments, then passing them along to a new
+checker instance.
+</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">plugin</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">checkers</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myapp</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SpecialChecker</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SpecialCheckerFactory</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ A checker factory for a specialized (fictional) API.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-comment"># The class needs to implement both of these interfaces</span>
+ <span class="py-src-comment"># for the plugin system to find our factory.</span>
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">checkers</span>.<span class="py-src-variable">ICheckerFactory</span>, <span class="py-src-variable">plugin</span>.<span class="py-src-variable">IPlugin</span>)
+
+ <span class="py-src-comment"># This tells AuthOptionsMixin how to find this factory.</span>
+ <span class="py-src-variable">authType</span> = <span class="py-src-string">&quot;special&quot;</span>
+
+ <span class="py-src-comment"># This is a one-line explanation of what arguments, if any,</span>
+ <span class="py-src-comment"># your particular cred plugin requires at the command-line.</span>
+ <span class="py-src-variable">argStringFormat</span> = <span class="py-src-string">&quot;A colon-separated key=value list.&quot;</span>
+
+ <span class="py-src-comment"># This help text can be multiple lines. It will be displayed</span>
+ <span class="py-src-comment"># when someone uses the &quot;--help-auth-type special&quot; command.</span>
+ <span class="py-src-variable">authHelp</span> = <span class="py-src-string">&quot;&quot;&quot;Some help text goes here ...&quot;&quot;&quot;</span>
+
+ <span class="py-src-comment"># This will be called once per command-line.</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">generateChecker</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">argstring</span>=<span class="py-src-string">&quot;&quot;</span>):
+ <span class="py-src-variable">argdict</span> = <span class="py-src-variable">dict</span>((<span class="py-src-variable">x</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'='</span>) <span class="py-src-keyword">for</span> <span class="py-src-variable">x</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">argstring</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>)))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">SpecialChecker</span>(**<span class="py-src-variable">dict</span>)
+
+<span class="py-src-comment"># We need to instantiate our class for the plugin to work.</span>
+<span class="py-src-variable">theSpecialCheckerFactory</span> = <span class="py-src-variable">SpecialCheckerFactory</span>()
+</pre>
+
+<p> For more information on how your plugin can be used in your
+application (and by other application developers), please see the <a href="tap.html" shape="rect">Writing a twistd plugin</a> document.</p>
+
+<h2>Conclusion<a name="auto15"/></h2>
+
+<p>After reading through this tutorial, you should be able to
+</p>
+<ul>
+<li>Understand how the cred architecture applies to your application</li>
+<li>Integrate your application with cred's object model</li>
+<li>Deploy an application that uses cred for authentication</li>
+<li>Allow your users to use command-line authentication plugins</li>
+</ul>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/debug-with-emacs.html b/vendor/Twisted-10.0.0/doc/core/howto/debug-with-emacs.html
new file mode 100644
index 0000000000..49c2b90b7e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/debug-with-emacs.html
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Debugging Python(Twisted) with Emacs</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Debugging Python(Twisted) with Emacs</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<img src="http://yellow5.com/pokey/archive/pokey411_3.gif"/>
+<a href="#footnote-1" title="POKEY THE PENGUIN IS COPYRIGHT © 1998-2002 THE AUTHORS"><super>1</super></a>
+
+<ul>
+ <li>Open up your project files. sometimes emacs can't find them if you
+ don't have them open before-hand.</li>
+
+ <li>Make sure you have a program called <code class="shell">pdb</code> somewhere
+ in your PATH, with the following contents:
+
+ <pre class="shell" xml:space="preserve">#!/bin/sh
+exec python2.3 /usr/lib/python2.3/pdb.py $1 $2 $3 $4 $5 $6 $7 $8 $9
+ </pre></li>
+
+ <li>Run <code class="shell">M-x pdb</code> in emacs. If you usually run your
+ program as <code class="shell">python foo.py</code>, your command line should be <code class="shell">pdb
+ foo.py</code>, for <code class="shell">twistd</code> and <code class="shell">trial</code> just
+ add -b to the command line, e.g.: <code class="shell">twistd -b -y my.tac</code></li>
+
+ <li>while pdb waits for your input, go to a place in your code and hit
+ <code class="shell">C-x SPC</code> to insert a break-point. pdb should say something happy.
+ Do this in as many points as you wish.</li>
+
+ <li>Go to your pdb buffer and hit <code class="shell">c</code>; this runs as normal until a
+ break-point is found.</li>
+
+ <li>once you get to a breakpoint, use <code class="shell">s</code> to step, <code class="shell">n</code> to run the
+ current line without stepping through the functions it calls, <code class="shell">w</code>
+ to print out the current stack, <code class="shell">u</code> and <code class="shell">d</code> to go up and down a
+ level in the stack, <code class="shell">p foo</code> to print result of expression <code class="shell">foo</code>.</li>
+
+ <li>recommendations for effective debugging:
+ <ul>
+ <li>use <code class="shell">p self</code> a lot; just knowing the class where the current code
+ is isn't enough most of the time.</li>
+ <li>use <code class="shell">w</code> to get your bearings, it'll re-display the current-line/arrow</li>
+ <li>after you use <code class="shell">w</code>, use <code class="shell">u</code> and <code class="shell">d</code> and lots more <code class="shell">p self</code> on the
+ different stack-levels.</li>
+ <li>If you've got a big code-path that you need to grok, keep another
+ buffer open and list the code-path there (e.g., I had a
+ nasty-evil Deferred recursion, and this helped me tons)</li>
+ </ul>
+ </li>
+</ul>
+
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">POKEY THE PENGUIN IS COPYRIGHT © 1998-2002
+THE AUTHORS</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/defer.html b/vendor/Twisted-10.0.0/doc/core/howto/defer.html
new file mode 100644
index 0000000000..f1a76d8fee
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/defer.html
@@ -0,0 +1,840 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Deferred Reference</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Deferred Reference</h1>
+ <div class="toc"><ol><li><a href="#auto0">Deferreds</a></li><li><a href="#auto1">Callbacks</a></li><ul><li><a href="#auto2">Multiple callbacks</a></li><li><a href="#auto3">Visual Explanation</a></li></ul><li><a href="#auto4">Errbacks</a></li><ul><li><a href="#auto5">Unhandled Errors</a></li></ul><li><a href="#auto6">Handling either synchronous or asynchronous results</a></li><ul><li><a href="#auto7">Handling possible Deferreds in the library code</a></li></ul><li><a href="#auto8">DeferredList</a></li><ul><li><a href="#auto9">Other behaviours</a></li></ul><li><a href="#auto10">Class Overview</a></li><ul><li><a href="#auto11">Basic Callback Functions</a></li><li><a href="#auto12">Chaining Deferreds</a></li></ul><li><a href="#auto13">See also</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<p>This document is a guide to the behaviour of the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code> object, and to various
+ways you can use them when they are returned by functions.</p>
+
+<p>This document assumes that you are familiar with the basic principle that
+the Twisted framework is structured around: asynchronous, callback-based
+programming, where instead of having blocking code in your program or using
+threads to run blocking code, you have functions that return immediately and
+then begin a callback chain when data is available.</p>
+
+<p>
+After reading this document, the reader should expect to be able to
+deal with most simple APIs in Twisted and Twisted-using code that
+return Deferreds.
+</p>
+
+<ul>
+<li>what sorts of things you can do when you get a Deferred from a
+function call; and</li>
+<li>how you can write your code to robustly handle errors in Deferred
+code.</li>
+</ul>
+
+<a name="deferreds" shape="rect"/>
+<h2>Deferreds<a name="auto0"/></h2>
+
+<p>Twisted uses the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> object to manage the callback
+sequence. The client application attaches a series of functions to the
+deferred to be called in order when the results of the asychronous request are
+available (this series of functions is known as a series of
+<strong>callbacks</strong>, or a <strong>callback chain</strong>), together
+with a series of functions to be called if there is an error in the
+asychronous request (known as a series of <strong>errbacks</strong> or an
+<strong>errback chain</strong>). The asychronous library code calls the first
+callback when the result is available, or the first errback when an error
+occurs, and the <code>Deferred</code> object then hands the results of each
+callback or errback function to the next function in the chain.</p>
+
+<h2>Callbacks<a name="auto1"/></h2>
+
+<p>A <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code> is a promise that
+a function will at some point have a result. We can attach callback functions
+to a Deferred, and once it gets a result these callbacks will be called. In
+addition Deferreds allow the developer to register a callback for an error,
+with the default behavior of logging the error. The deferred mechanism
+standardizes the application programmer's interface with all sorts of
+blocking or delayed operations.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getDummyData</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ This function is a dummy which simulates a delayed result and
+ returns a Deferred which will fire with that result. Don't try too
+ hard to understand this.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-comment"># simulate a delayed result by asking the reactor to fire the</span>
+ <span class="py-src-comment"># Deferred in 2 seconds time with the result x * 3</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">2</span>, <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>, <span class="py-src-variable">x</span> * <span class="py-src-number">3</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printData</span>(<span class="py-src-parameter">d</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Data handling function to be added as a callback: handles the
+ data by printing the result
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-variable">d</span> = <span class="py-src-variable">getDummyData</span>(<span class="py-src-number">3</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printData</span>)
+
+<span class="py-src-comment"># manually set up the end of the process by asking the reactor to</span>
+<span class="py-src-comment"># stop itself in 4 seconds time</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">4</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>)
+<span class="py-src-comment"># start up the Twisted reactor (event loop handler) manually</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h3>Multiple callbacks<a name="auto2"/></h3>
+
+<p>Multiple callbacks can be added to a Deferred. The first callback in the
+Deferred's callback chain will be called with the result, the second with the
+result of the first callback, and so on. Why do we need this? Well, consider
+a Deferred returned by twisted.enterprise.adbapi - the result of a SQL query.
+A web widget might add a callback that converts this result into HTML, and
+pass the Deferred onwards, where the callback will be used by twisted to
+return the result to the HTTP client. The callback chain will be bypassed in
+case of errors or exceptions.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Getter</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">gotResults</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ The Deferred mechanism provides a mechanism to signal error
+ conditions. In this case, odd numbers are bad.
+
+ This function demonstrates a more complex way of starting
+ the callback chain by checking for expected results and
+ choosing whether to fire the callback or errback chain
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">x</span> % <span class="py-src-number">2</span> == <span class="py-src-number">0</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">x</span>*<span class="py-src-number">3</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">ValueError</span>(<span class="py-src-string">&quot;You used an odd number!&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_toHTML</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">r</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ This function converts r to HTML.
+
+ It is added to the callback chain by getDummyData in
+ order to demonstrate how a callback passes its own result
+ to the next callback
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Result: %s&quot;</span> % <span class="py-src-variable">r</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getDummyData</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ The Deferred mechanism allows for chained callbacks.
+ In this example, the output of gotResults is first
+ passed through _toHTML on its way to printData.
+
+ Again this function is a dummy, simulating a delayed result
+ using callLater, rather than using a real asynchronous
+ setup.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-comment"># simulate a delayed result by asking the reactor to schedule</span>
+ <span class="py-src-comment"># gotResults in 2 seconds time</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">2</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">gotResults</span>, <span class="py-src-variable">x</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_toHTML</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printData</span>(<span class="py-src-parameter">d</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printError</span>(<span class="py-src-parameter">failure</span>):
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">stderr</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">str</span>(<span class="py-src-variable">failure</span>))
+
+<span class="py-src-comment"># this series of callbacks and errbacks will print an error message</span>
+<span class="py-src-variable">g</span> = <span class="py-src-variable">Getter</span>()
+<span class="py-src-variable">d</span> = <span class="py-src-variable">g</span>.<span class="py-src-variable">getDummyData</span>(<span class="py-src-number">3</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printData</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">printError</span>)
+
+<span class="py-src-comment"># this series of callbacks and errbacks will print &quot;Result: 12&quot;</span>
+<span class="py-src-variable">g</span> = <span class="py-src-variable">Getter</span>()
+<span class="py-src-variable">d</span> = <span class="py-src-variable">g</span>.<span class="py-src-variable">getDummyData</span>(<span class="py-src-number">4</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printData</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">printError</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">4</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>); <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h3>Visual Explanation<a name="auto3"/></h3>
+
+<div align="center" hlint="off">
+<img src="../img/deferred-attach.png"/>
+</div>
+
+<ol>
+ <li>Requesting method (data sink) requests data, gets
+ Deferred object.</li>
+
+ <li>Requesting method attaches callbacks to Deferred
+ object.</li>
+</ol>
+<img align="left" hlint="off" src="../img/deferred-process.png"/>
+
+<ol>
+
+ <li>When the result is ready, give it to the Deferred
+ object. <code>.callback(result)</code> if the operation succeeded,
+ <code>.errback(failure)</code> if it failed. Note that
+ <code>failure</code> is typically an instance of a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">twisted.python.failure.Failure</a></code>
+ instance.</li>
+
+ <li>Deferred object triggers previously-added (call/err)back
+ with the <code>result</code> or <code>failure</code>.
+ Execution then follows the following rules, going down the
+ chain of callbacks to be processed.
+
+ <ul>
+ <li>Result of the callback is always passed as the first
+ argument to the next callback, creating a chain of
+ processors.</li>
+
+ <li>If a callback raises an exception, switch to
+ errback.</li>
+
+ <li>An unhandled failure gets passed down the line of
+ errbacks, this creating an asynchronous analog to a
+ series to a series of <code>except:</code>
+ statements.</li>
+
+ <li>If an errback doesn't raise an exception or return a
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">twisted.python.failure.Failure</a></code>
+ instance, switch to callback.</li>
+ </ul> </li>
+</ol>
+<br clear="all" hlint="off"/>
+
+<h2>Errbacks<a name="auto4"/></h2>
+
+<p>Deferred's error handling is modeled after Python's
+exception handling. In the case that no errors occur, all the
+callbacks run, one after the other, as described above.</p>
+
+<p>If the errback is called instead of the callback (e.g. because a DB query
+raised an error), then a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">twisted.python.failure.Failure</a></code> is passed into the first
+errback (you can add multiple errbacks, just like with callbacks). You can
+think of your errbacks as being like <code class="python">except</code> blocks
+of ordinary Python code.</p>
+
+<p>Unless you explicitly <code class="python">raise</code> an error in except
+block, the <code class="python">Exception</code> is caught and stops
+propagating, and normal execution continues. The same thing happens with
+errbacks: unless you explicitly <code class="python">return</code> a <code class="python">Failure</code> or (re-)raise an exception, the error stops
+propagating, and normal callbacks continue executing from that point (using the
+value returned from the errback). If the errback does returns a <code class="python">Failure</code> or raise an exception, then that is passed to the
+next errback, and so on.</p>
+
+<p><em>Note:</em> If an errback doesn't return anything, then it effectively
+returns <code class="python">None</code>, meaning that callbacks will continue
+to be executed after this errback. This may not be what you expect to happen,
+so be careful. Make sure your errbacks return a <code class="python">Failure</code> (probably the one that was passed to it), or a
+meaningful return value for the next callback.</p>
+
+<p>Also, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">twisted.python.failure.Failure</a></code> instances have
+a useful method called trap, allowing you to effectively do the equivalent
+of:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">try</span>:
+ <span class="py-src-comment"># code that may throw an exception</span>
+ <span class="py-src-variable">cookSpamAndEggs</span>()
+<span class="py-src-keyword">except</span> (<span class="py-src-variable">SpamException</span>, <span class="py-src-variable">EggException</span>):
+ <span class="py-src-comment"># Handle SpamExceptions and EggExceptions</span>
+ ...
+</pre>
+
+<p>You do this by:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">errorHandler</span>(<span class="py-src-parameter">failure</span>):
+ <span class="py-src-variable">failure</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">SpamException</span>, <span class="py-src-variable">EggException</span>)
+ <span class="py-src-comment"># Handle SpamExceptions and EggExceptions</span>
+
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cookSpamAndEggs</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">errorHandler</span>)
+</pre>
+
+<p>If none of arguments passed to <code class="python">failure.trap</code>
+match the error encapsulated in that <code class="python">Failure</code>, then
+it re-raises the error.</p>
+
+<p>There's another potential <q>gotcha</q> here. There's a
+method <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.addCallbacks.html" title="twisted.internet.defer.Deferred.addCallbacks">twisted.internet.defer.Deferred.addCallbacks</a></code>
+which is similar to, but not exactly the same as, <code class="python">addCallback</code> followed by <code class="python">addErrback</code>. In particular, consider these two cases:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-comment"># Case 1</span>
+<span class="py-src-variable">d</span> = <span class="py-src-variable">getDeferredFromSomewhere</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">callback1</span>) <span class="py-src-comment"># A</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">errback1</span>) <span class="py-src-comment"># B</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">callback2</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">errback2</span>)
+
+<span class="py-src-comment"># Case 2</span>
+<span class="py-src-variable">d</span> = <span class="py-src-variable">getDeferredFromSomewhere</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">callback1</span>, <span class="py-src-variable">errback1</span>) <span class="py-src-comment"># C</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">callback2</span>, <span class="py-src-variable">errback2</span>)
+</pre>
+
+<p>If an error occurs in <code class="python">callback1</code>, then for Case 1
+<code class="python">errback1</code> will be called with the failure. For Case
+2, <code class="python">errback2</code> will be called. Be careful with your
+callbacks and errbacks.</p>
+
+<p>What this means in a practical sense is in Case 1, &quot;A&quot; will
+handle a success condition from <code>getDeferredFromSomewhere</code>, and
+&quot;B&quot; will handle any errors that occur <em>from either the upstream
+source, or that occur in 'A'</em>. In Case 2, &quot;C&quot;'s errback1
+<em>will only handle an error condition raised by
+<code>getDeferredFromSomewhere</code></em>, it will not do any handling of
+errors raised in callback1.</p>
+
+
+<h3>Unhandled Errors<a name="auto5"/></h3>
+
+<p>If a Deferred is garbage-collected with an unhandled error (i.e. it would
+call the next errback if there was one), then Twisted will write the error's
+traceback to the log file. This means that you can typically get away with not
+adding errbacks and still get errors logged. Be careful though; if you keep a
+reference to the Deferred around, preventing it from being garbage-collected,
+then you may never see the error (and your callbacks will mysteriously seem to
+have never been called). If unsure, you should explicitly add an errback after
+your callbacks, even if all you do is:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-comment"># Make sure errors get logged</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>)
+</pre>
+
+<h2>Handling either synchronous or asynchronous results<a name="auto6"/></h2>
+<p>
+In some applications, there are functions that might be either asynchronous or
+synchronous. For example, a user authentication function might be able to
+check in memory whether a user is authenticated, allowing the authentication
+function to return an immediate result, or it may need to wait on
+network data, in which case it should return a Deferred to be fired
+when that data arrives. However, a function that wants to check if a user is
+authenticated will then need to accept both immediate results <em> and</em>
+Deferreds.
+</p>
+
+<p>
+In this example, the library function <code>authenticateUser</code> uses the
+application function <code>isValidUser</code> to authenticate a user:
+</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">authenticateUser</span>(<span class="py-src-parameter">isValidUser</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">isValidUser</span>(<span class="py-src-variable">user</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;User is authenticated&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;User is not authenticated&quot;</span>
+</pre>
+
+<p>
+However, it assumes that <code>isValidUser</code> returns immediately,
+whereas <code>isValidUser</code> may actually authenticate the user
+asynchronously and return a Deferred. It is possible to adapt this
+trivial user authentication code to accept either a
+synchronous <code>isValidUser</code> or an
+asynchronous <code>isValidUser</code>, allowing the library to handle
+either type of function. It is, however, also possible to adapt
+synchronous functions to return Deferreds. This section describes both
+alternatives: handling functions that might be synchronous or
+asynchronous in the library function (<code>authenticateUser</code>)
+or in the application code.
+</p>
+
+<h3>Handling possible Deferreds in the library code<a name="auto7"/></h3>
+
+<p>
+Here is an example of a synchronous user authentication function that might be
+passed to <code>authenticateUser</code>:
+</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">synchronousIsValidUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">'''
+ Return true if user is a valid user, false otherwise
+ '''</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">&quot;Alice&quot;</span>, <span class="py-src-string">&quot;Angus&quot;</span>, <span class="py-src-string">&quot;Agnes&quot;</span>]
+</pre><div class="caption">Source listing - <a href="listings/deferred/synch-validation.py"><span class="filename">listings/deferred/synch-validation.py</span></a></div></div>
+
+<p>
+However, here's an <code>asynchronousIsValidUser</code> function that returns
+a Deferred:
+</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">asynchronousIsValidUser</span>(<span class="py-src-parameter">d</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">2</span>, <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>, <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">&quot;Alice&quot;</span>, <span class="py-src-string">&quot;Angus&quot;</span>, <span class="py-src-string">&quot;Agnes&quot;</span>])
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+</pre>
+
+<p> Our original implementation of <code>authenticateUser</code> expected
+<code>isValidUser</code> to be synchronous, but now we need to change it to handle both
+synchronous and asynchronous implementations of <code>isValidUser</code>. For this, we
+use <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.maybeDeferred.html" title="twisted.internet.defer.maybeDeferred">maybeDeferred</a></code> to
+call <code>isValidUser</code>, ensuring that the result of <code>isValidUser</code> is a Deferred,
+even if <code>isValidUser</code> is a synchronous function:
+</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">result</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;User is authenticated&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;User is not authenticated&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">authenticateUser</span>(<span class="py-src-parameter">isValidUser</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">maybeDeferred</span>(<span class="py-src-variable">isValidUser</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+</pre>
+
+<p>
+Now <code>isValidUser</code> could be either <code>synchronousIsValidUser</code> or
+<code>asynchronousIsValidUser</code>.
+</p>
+
+<p>It is also possible to modify <code>synchronousIsValidUser</code> to return
+a Deferred, see <a href="gendefer.html" shape="rect">Generating Deferreds</a> for more
+information.</p>
+
+<h2>DeferredList<a name="auto8"/></h2>
+
+<p>Sometimes you want to be notified after several different events have all
+happened, rather than waiting for each one individually. For example, you may
+want to wait for all the connections in a list to close. <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.DeferredList.html" title="twisted.internet.defer.DeferredList">twisted.internet.defer.DeferredList</a></code> is the way to do
+this.</p>
+
+<p>To create a DeferredList from multiple Deferreds, you simply pass a list of
+the Deferreds you want it to wait for:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-comment"># Creates a DeferredList</span>
+<span class="py-src-variable">dl</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">DeferredList</span>([<span class="py-src-variable">deferred1</span>, <span class="py-src-variable">deferred2</span>, <span class="py-src-variable">deferred3</span>])
+</pre>
+
+<p>You can now treat the DeferredList like an ordinary Deferred; you can call
+<code>addCallbacks</code> and so on. The DeferredList will call its callback
+when all the deferreds have completed. The callback will be called with a list
+of the results of the Deferreds it contains, like so:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">for</span> (<span class="py-src-variable">success</span>, <span class="py-src-variable">value</span>) <span class="py-src-keyword">in</span> <span class="py-src-variable">result</span>:
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">success</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Success:'</span>, <span class="py-src-variable">value</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Failure:'</span>, <span class="py-src-variable">value</span>.<span class="py-src-variable">getErrorMessage</span>()
+<span class="py-src-variable">deferred1</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">deferred2</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">deferred3</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">dl</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">DeferredList</span>([<span class="py-src-variable">deferred1</span>, <span class="py-src-variable">deferred2</span>, <span class="py-src-variable">deferred3</span>], <span class="py-src-variable">consumeErrors</span>=<span class="py-src-variable">True</span>)
+<span class="py-src-variable">dl</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+<span class="py-src-variable">deferred1</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">'one'</span>)
+<span class="py-src-variable">deferred2</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">Exception</span>(<span class="py-src-string">'bang!'</span>))
+<span class="py-src-variable">deferred3</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">'three'</span>)
+<span class="py-src-comment"># At this point, dl will fire its callback, printing:</span>
+<span class="py-src-comment"># Success: one</span>
+<span class="py-src-comment"># Failure: bang!</span>
+<span class="py-src-comment"># Success: three</span>
+<span class="py-src-comment"># (note that defer.SUCCESS == True, and defer.FAILURE == False)</span>
+</pre>
+
+<p>A standard DeferredList will never call errback, but failures in Deferreds
+passed to a DeferredList will still errback unless <code>consumeErrors</code>
+is passed <code>True</code>. See below for more details about this and other
+flags which modify the behavior of DeferredList.</p>
+
+<div class="note"><strong>Note: </strong>
+<p>If you want to apply callbacks to the individual Deferreds that
+go into the DeferredList, you should be careful about when those callbacks
+are added. The act of adding a Deferred to a DeferredList inserts a callback
+into that Deferred (when that callback is run, it checks to see if the
+DeferredList has been completed yet). The important thing to remember is
+that it is <em>this callback</em> which records the value that goes into the
+result list handed to the DeferredList's callback.</p>
+
+
+
+<p>Therefore, if you add a callback to the Deferred <em>after</em> adding the
+Deferred to the DeferredList, the value returned by that callback will not
+be given to the DeferredList's callback. To avoid confusion, we recommend not
+adding callbacks to a Deferred once it has been used in a DeferredList.</p>
+</div>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">result</span>
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">addTen</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">result</span> + <span class="py-src-string">&quot; ten&quot;</span>
+
+<span class="py-src-comment"># Deferred gets callback before DeferredList is created</span>
+<span class="py-src-variable">deferred1</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">deferred2</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">deferred1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">addTen</span>)
+<span class="py-src-variable">dl</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">DeferredList</span>([<span class="py-src-variable">deferred1</span>, <span class="py-src-variable">deferred2</span>])
+<span class="py-src-variable">dl</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+<span class="py-src-variable">deferred1</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;one&quot;</span>) <span class="py-src-comment"># fires addTen, checks DeferredList, stores &quot;one ten&quot;</span>
+<span class="py-src-variable">deferred2</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;two&quot;</span>)
+<span class="py-src-comment"># At this point, dl will fire its callback, printing:</span>
+<span class="py-src-comment"># [(1, 'one ten'), (1, 'two')]</span>
+
+<span class="py-src-comment"># Deferred gets callback after DeferredList is created</span>
+<span class="py-src-variable">deferred1</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">deferred2</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+<span class="py-src-variable">dl</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">DeferredList</span>([<span class="py-src-variable">deferred1</span>, <span class="py-src-variable">deferred2</span>])
+<span class="py-src-variable">deferred1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">addTen</span>) <span class="py-src-comment"># will run *after* DeferredList gets its value</span>
+<span class="py-src-variable">dl</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+<span class="py-src-variable">deferred1</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;one&quot;</span>) <span class="py-src-comment"># checks DeferredList, stores &quot;one&quot;, fires addTen</span>
+<span class="py-src-variable">deferred2</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;two&quot;</span>)
+<span class="py-src-comment"># At this point, dl will fire its callback, printing:</span>
+<span class="py-src-comment"># [(1, 'one), (1, 'two')]</span>
+</pre>
+
+<h3>Other behaviours<a name="auto9"/></h3>
+
+<p>DeferredList accepts three keyword arguments that modify its behaviour:
+<code>fireOnOneCallback</code>, <code>fireOnOneErrback</code> and
+<code>consumeErrors</code>. If <code>fireOnOneCallback</code> is set, the
+DeferredList will immediately call its callback as soon as any of its Deferreds
+call their callback. Similarly, <code>fireOnOneErrback</code> will call errback
+as soon as any of the Deferreds call their errback. Note that DeferredList is
+still one-shot, like ordinary Deferreds, so after a callback or errback has been
+called the DeferredList will do nothing further (it will just silently ignore
+any other results from its Deferreds).</p>
+
+<p>The <code>fireOnOneErrback</code> option is particularly useful when you
+want to wait for all the results if everything succeeds, but also want to know
+immediately if something fails.</p>
+
+<p>The <code>consumeErrors</code> argument will stop the DeferredList from
+propagating any errors along the callback chains of any Deferreds it contains
+(usually creating a DeferredList has no effect on the results passed along the
+callbacks and errbacks of their Deferreds). Stopping errors at the DeferredList
+with this option will prevent <q>Unhandled error in Deferred</q> warnings from
+the Deferreds it contains without needing to add extra errbacks<a href="#footnote-1" title="Unless of course a later callback starts a fresh error — but as we've already noted, adding callbacks to a Deferred after its used in a DeferredList is confusing and usually avoided."><super>1</super></a>.</p>
+
+<a name="class" shape="rect"/>
+
+<h2>Class Overview<a name="auto10"/></h2>
+
+<p>This is an overview API reference for Deferred from the point of using a
+Deferred returned by a function. It is not meant to be a
+substitute for the docstrings in the Deferred class, but can provide guidelines
+for its use.</p>
+
+<p>There is a parallel overview of functions used by the Deferred's
+<em>creator</em> in <a href="gendefer.html#class" shape="rect">Generating Deferreds</a>.</p>
+
+<h3>Basic Callback Functions<a name="auto11"/></h3>
+
+<ul>
+ <li>
+ <code class="py-prototype">addCallbacks(self, callback[, errback, callbackArgs,
+ callbackKeywords, errbackArgs, errbackKeywords])</code>
+
+ <p>This is the method you will use to interact
+ with Deferred. It adds a pair of callbacks <q>parallel</q> to
+ each other (see diagram above) in the list of callbacks
+ made when the Deferred is called back to. The signature of
+ a method added using addCallbacks should be
+ <code>myMethod(result, *methodArgs,
+ **methodKeywords)</code>. If your method is passed in the
+ callback slot, for example, all arguments in the tuple
+ <code>callbackArgs</code> will be passed as
+ <code>*methodArgs</code> to your method.</p>
+
+ <p>There are various convenience methods that are
+ derivative of addCallbacks. I will not cover them in detail
+ here, but it is important to know about them in order to
+ create concise code.</p>
+
+ <ul>
+ <li>
+ <code class="py-prototype">addCallback(callback, *callbackArgs,
+ **callbackKeywords)</code>
+
+ <p>Adds your callback at the next point in the
+ processing chain, while adding an errback that will
+ re-raise its first argument, not affecting further
+ processing in the error case.</p>
+
+ <p>Note that, while addCallbacks (plural) requires the arguments to be
+ passed in a tuple, addCallback (singular) takes all its remaining
+ arguments as things to be passed to the callback function. The reason is
+ obvious: addCallbacks (plural) cannot tell whether the arguments are
+ meant for the callback or the errback, so they must be specifically
+ marked by putting them into a tuple. addCallback (singular) knows that
+ everything is destined to go to the callback, so it can use Python's
+ <q>*</q> and <q>**</q> syntax to collect the remaining arguments.</p>
+
+ </li>
+
+ <li>
+ <code class="py-prototype">addErrback(errback, *errbackArgs,
+ **errbackKeywords)</code>
+
+ <p>Adds your errback at the next point in the
+ processing chain, while adding a callback that will
+ return its first argument, not affecting further
+ processing in the success case.</p>
+ </li>
+
+ <li>
+ <code class="py-prototype">addBoth(callbackOrErrback,
+ *callbackOrErrbackArgs,
+ **callbackOrErrbackKeywords)</code>
+
+ <p>This method adds the same callback into both sides
+ of the processing chain at both points. Keep in mind
+ that the type of the first argument is indeterminate if
+ you use this method! Use it for <code>finally:</code>
+ style blocks.</p>
+ </li>
+ </ul> </li>
+
+</ul>
+
+
+<h3>Chaining Deferreds<a name="auto12"/></h3>
+
+<p>If you need one Deferred to wait on another, all you need to do is return a
+Deferred from a method added to addCallbacks. Specifically, if you return
+Deferred B from a method added to Deferred A using A.addCallbacks, Deferred A's
+processing chain will stop until Deferred B's .callback() method is called; at
+that point, the next callback in A will be passed the result of the last
+callback in Deferred B's processing chain at the time.</p>
+
+<p>If this seems confusing, don't worry about it right now -- when you run into
+a situation where you need this behavior, you will probably recognize it
+immediately and realize why this happens. If you want to chain deferreds
+manually, there is also a convenience method to help you.</p>
+
+<ul>
+ <li>
+ <code class="py-prototype">chainDeferred(otherDeferred)</code>
+
+ <p>Add <code>otherDeferred</code> to the end of this
+ Deferred's processing chain. When self.callback is called,
+ the result of my processing chain up to this point will be
+ passed to <code>otherDeferred.callback</code>. Further
+ additions to my callback chain do not affect
+ <code>otherDeferred</code></p>
+ <p>This is the same as <code class="python">self.addCallbacks(otherDeferred.callback,
+ otherDeferred.errback)</code></p>
+ </li>
+</ul>
+
+<h2>See also<a name="auto13"/></h2>
+
+<ol>
+<li><a href="gendefer.html" shape="rect">Generating Deferreds</a>, an introduction to
+writing asynchronous functions that return Deferreds.</li>
+</ol>
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">Unless of course a later callback starts a fresh error —
+but as we've already noted, adding callbacks to a Deferred after its used in a
+DeferredList is confusing and usually avoided.</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/deferredindepth.html b/vendor/Twisted-10.0.0/doc/core/howto/deferredindepth.html
new file mode 100644
index 0000000000..317e28ddfb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/deferredindepth.html
@@ -0,0 +1,2183 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Deferreds are beautiful! (A Tutorial)</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Deferreds are beautiful! (A Tutorial)</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">A simple example</a></li><li><a href="#auto2">Errbacks</a></li><ul><li><a href="#auto3">Failure in requested operation</a></li><li><a href="#auto4">Exceptions raised in callbacks</a></li><li><a href="#auto5">Exceptions will only be handled by errbacks</a></li><li><a href="#auto6">Handling an exception and continuing on</a></li></ul><li><a href="#auto7">addBoth: the deferred version of finally</a></li><li><a href="#auto8">addCallbacks: decision making based on previous success or failure</a></li><li><a href="#auto9">Hints, tips, common mistakes, and miscellaney</a></li><ul><li><a href="#auto10">The deferred callback chain is stateful</a></li><li><a href="#auto11">Don't call .callback() on deferreds you didn't create!</a></li><li><a href="#auto12">Callbacks can return deferreds</a></li></ul><li><a href="#auto13">Conclusion</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p>Deferreds are quite possibly the single most confusing topic that a
+newcomer to Twisted has to deal with. I am going to forgo the normal talk
+about what deferreds are, what they aren't, and why they're used in Twisted.
+Instead, I'm going show you the logic behind what they
+<strong>do</strong>.</p>
+
+
+<p>A deferred allows you to encapsulate the logic that you'd normally use to
+make a series of function calls after receiving a result into a single object.
+In the examples that follow, I'll first show you what's going to go on behind
+the scenes in the deferred chain, then show you the deferred API calls that set
+up that chain. All of these examples are runnable code, so feel free to play
+around with them.</p>
+
+
+<h2>A simple example<a name="auto1"/></h2>
+
+First, a simple example so that we have something to talk about:
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>, <span class="py-src-variable">util</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Here we have the simplest case, a single callback and a single errback.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleFailure</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we got an exception: %s&quot;</span> % (<span class="py-src-variable">f</span>.<span class="py-src-variable">getTraceback</span>(),)
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">global</span> <span class="py-src-variable">num</span>; <span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;yay! handleResult was successful!&quot;</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">behindTheScenes</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-comment"># equivalent to d.callback(result)</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">pass</span>
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleFailure</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">handleFailure</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;success&quot;</span>)
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">behindTheScenes</span>(<span class="py-src-string">&quot;success&quot;</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\n-------------------------------------------------\n&quot;</span>
+ <span class="py-src-keyword">global</span> <span class="py-src-variable">num</span>; <span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">deferredExample</span>()
+</pre><div class="caption">Source listing - <a href="listings/deferred/deferred_ex.py"><span class="filename">listings/deferred/deferred_ex.py</span></a></div></div>
+
+<p>And the output: (since both methods in the example produce the same output,
+it will only be shown once.) </p>
+
+<pre xml:space="preserve">
+callback 1
+ got result: success
+</pre>
+
+<p>Here we have the simplest case. A deferred with a single callback and a
+single errback. Normally, a function would create a deferred and hand it back
+to you when you request an operation that needs to wait for an event for
+completion. The object you called then does <code>d.callback(result)</code>
+when the results are in.
+</p>
+
+<p>The thing to notice is that there is only one result that is passed from
+method to method, and that the result returned from a method is the argument
+to the next method in the chain. In case of an exception, result is set to an
+instance of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code>
+that describes the exception.</p>
+
+<h2>Errbacks<a name="auto2"/></h2>
+<h3>Failure in requested operation<a name="auto3"/></h3>
+<p>Things don't always go as planned, and sometimes the function that
+returned the deferred needs to alert the callback chain that an error
+has occurred.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>, <span class="py-src-variable">util</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+This example is analogous to a function calling .errback(failure)
+&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleFailure</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we got an exception: %s&quot;</span> % (<span class="py-src-variable">f</span>.<span class="py-src-variable">getTraceback</span>(),)
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;yay! handleResult was successful!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">failAtHandlingResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tabout to raise exception&quot;</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">RuntimeError</span>, <span class="py-src-string">&quot;whoops! we encountered an error&quot;</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">behindTheScenes</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">pass</span>
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleFailure</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">failAtHandlingResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">handleFailure</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">result</span>)
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">RuntimeError</span>, <span class="py-src-string">&quot;*doh*! failure!&quot;</span>
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-variable">behindTheScenes</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\n-------------------------------------------------\n&quot;</span>
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">deferredExample</span>(<span class="py-src-variable">result</span>)
+</pre><div class="caption">Source listing - <a href="listings/deferred/deferred_ex1a.py"><span class="filename">listings/deferred/deferred_ex1a.py</span></a></div></div>
+
+<pre xml:space="preserve">
+errback
+we got an exception: Traceback (most recent call last):
+--- exception caught here ---
+ File &quot;deferred_ex1a.py&quot;, line 73, in ?
+ raise RuntimeError, &quot;*doh*! failure!&quot;
+exceptions.RuntimeError: *doh*! failure!
+</pre>
+
+<p> The important thing to note (as it will come up again in later examples)
+is that the callback isn't touched, the failure goes right to the errback.
+Also note that the errback trap()s the expected exception type. If you don't
+trap the exception, an error will be logged when the deferred is
+garbage-collected.
+</p>
+
+
+<h3>Exceptions raised in callbacks<a name="auto4"/></h3>
+
+<p>Now let's see what happens when <em>our callback</em> raises an
+exception</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>, <span class="py-src-variable">util</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Here we have a slightly more involved case. The deferred is called back with a
+result. the first callback returns a value, the second callback, however
+raises an exception, which is handled by the errback.
+&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleFailure</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we got an exception: %s&quot;</span> % (<span class="py-src-variable">f</span>.<span class="py-src-variable">getTraceback</span>(),)
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;yay! handleResult was successful!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">failAtHandlingResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tabout to raise exception&quot;</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">RuntimeError</span>, <span class="py-src-string">&quot;whoops! we encountered an error&quot;</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">behindTheScenes</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failAtHandlingResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">pass</span>
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleFailure</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">failAtHandlingResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">handleFailure</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;success&quot;</span>)
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">behindTheScenes</span>(<span class="py-src-string">&quot;success&quot;</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\n-------------------------------------------------\n&quot;</span>
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">deferredExample</span>()
+</pre><div class="caption">Source listing - <a href="listings/deferred/deferred_ex1b.py"><span class="filename">listings/deferred/deferred_ex1b.py</span></a></div></div>
+
+<p>And the output: (note, tracebacks will be edited slightly to conserve
+space)</p>
+
+<pre xml:space="preserve">
+callback 1
+ got result: success
+callback 2
+ got result: yay! handleResult was successful!
+ about to raise exception
+errback
+we got an exception: Traceback (most recent call last):
+--- &lt;exception caught here&gt; ---
+ File &quot;/home/slyphon/Projects/Twisted/trunk/twisted/internet/defer.py&quot;, line
+326, in _runCallbacks
+ self.result = callback(self.result, *args, **kw)
+ File &quot;./deferred_ex1.py&quot;, line 32, in failAtHandlingResult
+ raise RuntimeError, &quot;whoops! we encountered an error&quot;
+exceptions.RuntimeError: whoops! we encountered an error
+</pre>
+
+<p>If your callback raises an exception, the next method to be called will be
+the next errback in your chain.</p>
+
+
+<h3>Exceptions will only be handled by errbacks<a name="auto5"/></h3>
+
+<p>If a callback raises an exception the next method to be called will be next
+errback in the chain. If the chain is started off with a failure, the first
+method to be called will be the first errback.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>, <span class="py-src-variable">util</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+This example shows an important concept that many deferred newbies
+(myself included) have trouble understanding.
+
+when an error occurs in a callback, the first errback after the error
+occurs will be the next method called. (in the next example we'll
+see what happens in the 'chain' after an errback).
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleFailure</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we got an exception: %s&quot;</span> % (<span class="py-src-variable">f</span>.<span class="py-src-variable">getTraceback</span>(),)
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;yay! handleResult was successful!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">failAtHandlingResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tabout to raise exception&quot;</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">RuntimeError</span>, <span class="py-src-string">&quot;whoops! we encountered an error&quot;</span>
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">behindTheScenes</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-comment"># equivalent to d.callback(result)</span>
+
+ <span class="py-src-comment"># now, let's make the error happen in the first callback</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failAtHandlingResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-comment"># note: this callback will be skipped because</span>
+ <span class="py-src-comment"># result is a failure</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">pass</span>
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleFailure</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">failAtHandlingResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">handleFailure</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;success&quot;</span>)
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">behindTheScenes</span>(<span class="py-src-string">&quot;success&quot;</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\n-------------------------------------------------\n&quot;</span>
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">deferredExample</span>()
+</pre><div class="caption">Source listing - <a href="listings/deferred/deferred_ex2.py"><span class="filename">listings/deferred/deferred_ex2.py</span></a></div></div>
+
+<pre xml:space="preserve">
+callback 1
+ got result: success
+ about to raise exception
+errback
+we got an exception: Traceback (most recent call last):
+ File &quot;./deferred_ex2.py&quot;, line 85, in ?
+ nonDeferredExample(&quot;success&quot;)
+--- &lt;exception caught here&gt; ---
+ File &quot;./deferred_ex2.py&quot;, line 46, in nonDeferredExample
+ result = failAtHandlingResult(result)
+ File &quot;./deferred_ex2.py&quot;, line 35, in failAtHandlingResult
+ raise RuntimeError, &quot;whoops! we encountered an error&quot;
+exceptions.RuntimeError: whoops! we encountered an error
+</pre>
+
+<p>You can see that our second callback, handleResult was not called because
+failAtHandlingResult raised an exception</p>
+
+<h3>Handling an exception and continuing on<a name="auto6"/></h3>
+
+<p>In this example, we see an errback handle an exception raised in the
+preceeding callback. Take note that it could just as easily been an exception
+from <strong>any other</strong> preceeding method. You'll see that after the
+exception is handled in the errback (i.e. the errback does not return a
+failure or raise an exception) the chain continues on with the next
+callback.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>, <span class="py-src-variable">util</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Now we see how an errback can handle errors. if an errback
+does not raise an exception, the next callback in the chain
+will be called.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleFailure</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we got an exception: %s&quot;</span> % (<span class="py-src-variable">f</span>.<span class="py-src-variable">getTraceback</span>(),)
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;okay, continue on&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;yay! handleResult was successful!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">failAtHandlingResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tabout to raise exception&quot;</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">RuntimeError</span>, <span class="py-src-string">&quot;whoops! we encountered an error&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">callbackAfterErrback</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">behindTheScenes</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-comment"># equivalent to d.callback(result)</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failAtHandlingResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">pass</span>
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleFailure</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">callbackAfterErrback</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">failAtHandlingResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">handleFailure</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">callbackAfterErrback</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;success&quot;</span>)
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">behindTheScenes</span>(<span class="py-src-string">&quot;success&quot;</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\n-------------------------------------------------\n&quot;</span>
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">deferredExample</span>()
+</pre><div class="caption">Source listing - <a href="listings/deferred/deferred_ex3.py"><span class="filename">listings/deferred/deferred_ex3.py</span></a></div></div>
+
+<pre xml:space="preserve">
+callback 1
+ got result: success
+callback 2
+ got result: yay! handleResult was successful!
+ about to raise exception
+errback
+we got an exception: Traceback (most recent call last):
+ File &quot;./deferred_ex3.py&quot;, line 97, in &lt;module&gt;
+ deferredExample()
+ File &quot;./deferred_ex3.py&quot;, line 90, in deferredExample
+ d.callback(&quot;success&quot;)
+ File &quot;/home/slyphon/Projects/Twisted/trunk/twisted/internet/defer.py&quot;, line 243, in callback
+ self._startRunCallbacks(result)
+ File &quot;/home/slyphon/Projects/Twisted/trunk/twisted/internet/defer.py&quot;, line 312, in _startRunCallbacks
+ self._runCallbacks()
+--- &lt;exception caught here&gt; ---
+ File &quot;/home/slyphon/Projects/Twisted/trunk/twisted/internet/defer.py&quot;, line 328, in _runCallbacks
+ self.result = callback(self.result, *args, **kw)
+ File &quot;./deferred_ex3.py&quot;, line 34, in failAtHandlingResult
+ raise RuntimeError, &quot;whoops! we encountered an error&quot;
+exceptions.RuntimeError: whoops! we encountered an error
+
+callback 3
+ got result: okay, continue on
+</pre>
+
+<h2>addBoth: the deferred version of <em>finally</em><a name="auto7"/></h2>
+
+<p>Now we see how deferreds do <strong>finally</strong>, with .addBoth. The
+callback that gets added as addBoth will be called if the result is a failure
+or non-failure. We'll also see in this example, that our doThisNoMatterWhat()
+method follows a common idiom in deferred callbacks by acting as a passthru,
+returning the value that it received to allow processing the chain to
+continue, but appearing transparent in terms of the result.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>, <span class="py-src-variable">util</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Now we'll see what happens when you use 'addBoth'.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleFailure</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we got an exception: %s&quot;</span> % (<span class="py-src-variable">f</span>.<span class="py-src-variable">getTraceback</span>(),)
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;okay, continue on&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;yay! handleResult was successful!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">failAtHandlingResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tabout to raise exception&quot;</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">RuntimeError</span>, <span class="py-src-string">&quot;whoops! we encountered an error&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">doThisNoMatterWhat</span>(<span class="py-src-parameter">arg</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;both %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot argument %r&quot;</span> % (<span class="py-src-variable">arg</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tdoing something very important&quot;</span>
+ <span class="py-src-comment"># we pass the argument we received to the next phase here</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">arg</span>
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">behindTheScenes</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-comment"># equivalent to d.callback(result)</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failAtHandlingResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-comment"># ---- this is equivalent to addBoth(doThisNoMatterWhat)</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>):
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">doThisNoMatterWhat</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">doThisNoMatterWhat</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">pass</span>
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleFailure</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">failAtHandlingResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addBoth</span>(<span class="py-src-variable">doThisNoMatterWhat</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">handleFailure</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;success&quot;</span>)
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">behindTheScenes</span>(<span class="py-src-string">&quot;success&quot;</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\n-------------------------------------------------\n&quot;</span>
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">deferredExample</span>()
+</pre><div class="caption">Source listing - <a href="listings/deferred/deferred_ex4.py"><span class="filename">listings/deferred/deferred_ex4.py</span></a></div></div>
+
+<pre xml:space="preserve">
+callback 1
+ got result: success
+callback 2
+ got result: yay! handleResult was successful!
+ about to raise exception
+both 3
+ got argument &lt;twisted.python.failure.Failure exceptions.RuntimeError&gt;
+ doing something very important
+errback
+we got an exception: Traceback (most recent call last):
+--- &lt;exception caught here&gt; ---
+ File &quot;/home/slyphon/Projects/Twisted/trunk/twisted/internet/defer.py&quot;, line
+326, in _runCallbacks
+ self.result = callback(self.result, *args, **kw)
+ File &quot;./deferred_ex4.py&quot;, line 32, in failAtHandlingResult
+ raise RuntimeError, &quot;whoops! we encountered an error&quot;
+exceptions.RuntimeError: whoops! we encountered an error
+</pre>
+
+<p>You can see that the errback is called, (and consequently, the failure is
+trapped). This is because doThisNoMatterWhat method returned the value it
+received, a failure.</p>
+
+<h2>addCallbacks: decision making based on previous success or failure<a name="auto8"/></h2>
+
+<p>As we've been seeing in the examples, the callback is a pair of
+callback/errback. Using addCallback or addErrback is actually a special case
+where one of the pair is a pass statement. If you want to make a decision
+based on whether or not the previous result in the chain was a failure or not
+(which is very rare, but included here for completeness), you use
+addCallbacks. Note that this is <strong>not</strong> the same thing as an
+addCallback followed by an addErrback.</p>
+
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>, <span class="py-src-variable">util</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Now comes the more nuanced addCallbacks, which allows us to make a
+yes/no (branching) decision based on whether the result at a given point is
+a failure or not.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleFailure</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we got an exception: %s&quot;</span> % (<span class="py-src-variable">f</span>.<span class="py-src-variable">getTraceback</span>(),)
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;okay, continue on&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;yay! handleResult was successful!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">failAtHandlingResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tabout to raise exception&quot;</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">RuntimeError</span>, <span class="py-src-string">&quot;whoops! we encountered an error&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">yesDecision</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;yes decision %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\twasn't a failure, so we can plow ahead&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;go ahead!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">noDecision</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-variable">result</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;no decision %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\t*doh*! a failure! quick! damage control!&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;damage control successful!&quot;</span>
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">behindTheScenes</span>(<span class="py-src-parameter">result</span>):
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failAtHandlingResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-comment"># this is equivalent to addCallbacks(yesDecision, noDecision)</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">yesDecision</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">noDecision</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-comment"># this is equivalent to addCallbacks(yesDecision, noDecision)</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">yesDecision</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">noDecision</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">pass</span>
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleFailure</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">failAtHandlingResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">yesDecision</span>, <span class="py-src-variable">noDecision</span>) <span class="py-src-comment"># noDecision will be called</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>) <span class="py-src-comment"># - A -</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">yesDecision</span>, <span class="py-src-variable">noDecision</span>) <span class="py-src-comment"># yesDecision will be called</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">handleFailure</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;success&quot;</span>)
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">behindTheScenes</span>(<span class="py-src-string">&quot;success&quot;</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\n-------------------------------------------------\n&quot;</span>
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">deferredExample</span>()
+</pre><div class="caption">Source listing - <a href="listings/deferred/deferred_ex5.py"><span class="filename">listings/deferred/deferred_ex5.py</span></a></div></div>
+
+<pre xml:space="preserve">
+callback 1
+ got result: success
+ about to raise exception
+no decision 2
+ *doh*! a failure! quick! damage control!
+callback 3
+ got result: damage control successful!
+yes decision 4
+ wasn't a failure, so we can plow ahead
+callback 5
+ got result: go ahead!
+</pre>
+
+<p>Notice that our errback is never called. The noDecision method returns a
+non-failure so processing continues with the next callback. If we wanted to
+skip the callback at &quot;- A -&quot; because of the error, but do some kind of
+processing in response to the error, we would have used a passthru, and
+returned the failure we received, as we see in this next example: </p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>, <span class="py-src-variable">util</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Now comes the more nuanced addCallbacks, which allows us to make a
+yes/no (branching) decision based on whether the result at a given point is
+a failure or not.
+
+here, we return the failure from noDecisionPassthru, the errback argument to
+the first addCallbacks method invocation, and see what happens.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleFailure</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we got an exception: %s&quot;</span> % (<span class="py-src-variable">f</span>.<span class="py-src-variable">getTraceback</span>(),)
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;okay, continue on&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;yay! handleResult was successful!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">failAtHandlingResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tabout to raise exception&quot;</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">RuntimeError</span>, <span class="py-src-string">&quot;whoops! we encountered an error&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">yesDecision</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;yes decision %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\twasn't a failure, so we can plow ahead&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;go ahead!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">noDecision</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-variable">result</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;no decision %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\t*doh*! a failure! quick! damage control!&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;damage control successful!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">noDecisionPassthru</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;no decision %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\t*doh*! a failure! don't know what to do, returning failure!&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">result</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">behindTheScenes</span>(<span class="py-src-parameter">result</span>):
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failAtHandlingResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-comment"># this is equivalent to addCallbacks(yesDecision, noDecision)</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">yesDecision</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">noDecisionPassthru</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-comment"># this is equivalent to addCallbacks(yesDecision, noDecision)</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">yesDecision</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">noDecision</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleResult</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">pass</span>
+
+
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">isinstance</span>(<span class="py-src-variable">result</span>, <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>): <span class="py-src-comment"># ---- callback</span>
+ <span class="py-src-keyword">pass</span>
+ <span class="py-src-keyword">else</span>: <span class="py-src-comment"># ---- errback</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">handleFailure</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">Failure</span>()
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">failAtHandlingResult</span>)
+
+ <span class="py-src-comment"># noDecisionPassthru will be called</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">yesDecision</span>, <span class="py-src-variable">noDecisionPassthru</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>) <span class="py-src-comment"># - A -</span>
+
+ <span class="py-src-comment"># noDecision will be called</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">yesDecision</span>, <span class="py-src-variable">noDecision</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>) <span class="py-src-comment"># - B -</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">handleFailure</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;success&quot;</span>)
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">behindTheScenes</span>(<span class="py-src-string">&quot;success&quot;</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\n-------------------------------------------------\n&quot;</span>
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">deferredExample</span>()
+</pre><div class="caption">Source listing - <a href="listings/deferred/deferred_ex6.py"><span class="filename">listings/deferred/deferred_ex6.py</span></a></div></div>
+
+<pre xml:space="preserve">
+callback 1
+ got result: success
+ about to raise exception
+no decision 2
+ *doh*! a failure! don't know what to do, returning failure!
+no decision 3
+ *doh*! a failure! quick! damage control!
+callback 4
+ got result: damage control successful!
+</pre>
+
+<p>Two things to note here. First, &quot;- A -&quot; was skipped, like we wanted it to,
+and the second thing is that after &quot;- A -&quot;, noDecision is called, because
+<strong>it is the next errback that exists in the chain</strong>. It returns a
+non-failure, so processing continues with the next callback at &quot;- B -&quot;, and
+the errback at the end of the chain is never called </p>
+
+<h2>Hints, tips, common mistakes, and miscellaney<a name="auto9"/></h2>
+
+<h3>The deferred callback chain is stateful<a name="auto10"/></h3>
+
+<p>A deferred that has been called back will call its addCallback and
+addErrback methods as appropriate in the order they are added, when they are
+added. So we see in the following example, deferredExample1 and
+deferredExample2 are equivalent. The first sets up the processing chain
+beforehand and then executes it, the other executes the chain as it is being
+constructed. This is because deferreds are <em>stateful</em>. </p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>, <span class="py-src-variable">util</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+The deferred callback chain is stateful, and can be executed before
+or after all callbacks have been added to the chain
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleFailure</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we got an exception: %s&quot;</span> % (<span class="py-src-variable">f</span>.<span class="py-src-variable">getTraceback</span>(),)
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">RuntimeError</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;yay! handleResult was successful!&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">failAtHandlingResult</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tabout to raise exception&quot;</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">RuntimeError</span>, <span class="py-src-string">&quot;whoops! we encountered an error&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample1</span>():
+ <span class="py-src-comment"># this is another common idiom, since all add* methods</span>
+ <span class="py-src-comment"># return the deferred instance, you can just chain your</span>
+ <span class="py-src-comment"># calls to addCallback and addErrback</span>
+
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>().<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">failAtHandlingResult</span>
+ ).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>
+ ).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">handleFailure</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;success&quot;</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample2</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;success&quot;</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">failAtHandlingResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">handleResult</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">handleFailure</span>)
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">deferredExample1</span>()
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\n-------------------------------------------------\n&quot;</span>
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">deferredExample2</span>()
+</pre><div class="caption">Source listing - <a href="listings/deferred/deferred_ex7.py"><span class="filename">listings/deferred/deferred_ex7.py</span></a></div></div>
+
+<pre xml:space="preserve">
+callback 1
+ got result: success
+ about to raise exception
+errback
+we got an exception: Traceback (most recent call last):
+--- &lt;exception caught here&gt; ---
+ File &quot;/home/slyphon/Projects/Twisted/trunk/twisted/internet/defer.py&quot;, line
+326, in _runCallbacks
+ self.result = callback(self.result, *args, **kw)
+ File &quot;./deferred_ex7.py&quot;, line 35, in failAtHandlingResult
+ raise RuntimeError, &quot;whoops! we encountered an error&quot;
+exceptions.RuntimeError: whoops! we encountered an error
+
+
+-------------------------------------------------
+
+callback 1
+ got result: success
+ about to raise exception
+errback
+we got an exception: Traceback (most recent call last):
+--- &lt;exception caught here&gt; ---
+ File &quot;/home/slyphon/Projects/Twisted/trunk/twisted/internet/defer.py&quot;, line
+326, in _runCallbacks
+ self.result = callback(self.result, *args, **kw)
+ File &quot;./deferred_ex7.py&quot;, line 35, in failAtHandlingResult
+ raise RuntimeError, &quot;whoops! we encountered an error&quot;
+exceptions.RuntimeError: whoops! we encountered an error
+</pre>
+
+<p>This example also shows you the common idiom of chaining calls to
+addCallback and addErrback.
+</p>
+
+<h3>Don't call .callback() on deferreds you didn't create!<a name="auto11"/></h3>
+
+<p>It is an error to reinvoke deferreds callback or errback method, therefore
+if you didn't create a deferred, <strong>do not under any
+circumstances</strong> call its callback or errback. doing so will raise
+an exception </p>
+
+<h3>Callbacks can return deferreds<a name="auto12"/></h3>
+
+<p>If you need to call a method that returns a deferred within your callback
+chain, just return that deferred, and the result of the secondary deferred's
+processing chain will become the result that gets passed to the next callback
+of the primary deferreds processing chain </p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>, <span class="py-src-variable">util</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">num</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">let</span> = <span class="py-src-string">'a'</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">incrLet</span>(<span class="py-src-parameter">cls</span>):
+ <span class="py-src-variable">cls</span>.<span class="py-src-variable">let</span> = <span class="py-src-variable">chr</span>(<span class="py-src-variable">ord</span>(<span class="py-src-variable">cls</span>.<span class="py-src-variable">let</span>) + <span class="py-src-number">1</span>)
+ <span class="py-src-variable">incrLet</span> = <span class="py-src-variable">classmethod</span>(<span class="py-src-variable">incrLet</span>)
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">handleFailure</span>(<span class="py-src-parameter">f</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we got an exception: %s&quot;</span> % (<span class="py-src-variable">f</span>.<span class="py-src-variable">getTraceback</span>(),)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">subCb_B</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;sub-callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">let</span>,)
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">incrLet</span>()
+ <span class="py-src-variable">s</span> = <span class="py-src-string">&quot; beautiful!&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tadding %r to result&quot;</span> % (<span class="py-src-variable">s</span>,)
+ <span class="py-src-variable">result</span> += <span class="py-src-variable">s</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">result</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">subCb_A</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;sub-callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">let</span>,)
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">incrLet</span>()
+ <span class="py-src-variable">s</span> = <span class="py-src-string">&quot; are &quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tadding %r to result&quot;</span> % (<span class="py-src-variable">s</span>,)
+ <span class="py-src-variable">result</span> += <span class="py-src-variable">s</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">result</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">mainCb_1</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+ <span class="py-src-variable">result</span> += <span class="py-src-string">&quot; Deferreds &quot;</span>
+
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>().<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">subCb_A</span>
+ ).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">subCb_B</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">result</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">mainCb_2</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;callback %s&quot;</span> % (<span class="py-src-variable">Counter</span>.<span class="py-src-variable">num</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;\tgot result: %s&quot;</span> % (<span class="py-src-variable">result</span>,)
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">deferredExample</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>().<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">mainCb_1</span>
+ ).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">mainCb_2</span>)
+
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-string">&quot;I hope you'll agree: &quot;</span>)
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">deferredExample</span>()
+</pre><div class="caption">Source listing - <a href="listings/deferred/deferred_ex8.py"><span class="filename">listings/deferred/deferred_ex8.py</span></a></div></div>
+
+<pre xml:space="preserve">
+callback 1
+ got result: I hope you'll agree:
+sub-callback a
+ adding ' are ' to result
+sub-callback b
+ adding ' beautiful!' to result
+callback 2
+ got result: I hope you'll agree: Deferreds are beautiful!
+</pre>
+
+<h2>Conclusion<a name="auto13"/></h2>
+
+<p>Deferreds can be confusing, but only because they're so elegant and simple.
+There is a lot of logical power that can expressed with a deferred's
+processing chain, and once you see what's going on behind the curtain, it's a
+lot easier to understand how to make use of what deferreds have to offer.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/design.html b/vendor/Twisted-10.0.0/doc/core/howto/design.html
new file mode 100644
index 0000000000..3a3a7330fe
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/design.html
@@ -0,0 +1,257 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Designing Twisted Applications</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Designing Twisted Applications</h1>
+ <div class="toc"><ol><li><a href="#auto0">Goals</a></li><li><a href="#auto1">Example of a modular design: TwistedQuotes</a></li><ul><li><a href="#auto2">Set up the project directory</a></li><li><a href="#auto3">A Look at the Heart of the Application</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Goals<a name="auto0"/></h2>
+
+<p>This document describes how a good Twisted application is structured. It
+should be useful for beginning Twisted developers who want to structure their
+code in a clean, maintainable way that reflects current best practices.</p>
+
+<p>Readers will want to be familiar with writing <a href="servers.html" shape="rect">servers</a> and <a href="clients.html" shape="rect">clients</a> using Twisted.</p>
+
+<h2>Example of a modular design: TwistedQuotes<a name="auto1"/></h2>
+
+<p><code>TwistedQuotes</code> is a very simple plugin which is a great
+demonstration of
+Twisted's power. It will export a small kernel of functionality -- Quote of
+the Day -- which can be accessed through every interface that Twisted supports:
+web pages, e-mail, instant messaging, a specific Quote of the Day protocol, and
+more.</p>
+
+<h3>Set up the project directory<a name="auto2"/></h3>
+
+<p>See the description of <a href="quotes.html" shape="rect">setting up the TwistedQuotes
+example</a>.</p>
+
+<h3>A Look at the Heart of the Application<a name="auto3"/></h3>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">random</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">choice</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">TwistedQuotes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">quoteproto</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">StaticQuoter</span>:
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a static quote.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">quoteproto</span>.<span class="py-src-variable">IQuoter</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">quote</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">quote</span> = <span class="py-src-variable">quote</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">quote</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FortuneQuoter</span>:
+ <span class="py-src-string">&quot;&quot;&quot;
+ Load quotes from a fortune-format file.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">quoteproto</span>.<span class="py-src-variable">IQuoter</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filenames</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filenames</span> = <span class="py-src-variable">filenames</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">quoteFile</span> = <span class="py-src-variable">file</span>(<span class="py-src-variable">choice</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filenames</span>))
+ <span class="py-src-variable">quotes</span> = <span class="py-src-variable">quoteFile</span>.<span class="py-src-variable">read</span>().<span class="py-src-variable">split</span>(<span class="py-src-string">'\n%\n'</span>)
+ <span class="py-src-variable">quoteFile</span>.<span class="py-src-variable">close</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">choice</span>(<span class="py-src-variable">quotes</span>)
+</pre><div class="caption">Twisted Quotes
+Central Abstraction - <a href="listings/TwistedQuotes/quoters.py"><span class="filename">listings/TwistedQuotes/quoters.py</span></a></div></div>
+
+<p>This code listing shows us what the Twisted Quotes system is all about. The
+code doesn't have any way of talking to the outside world, but it provides a
+library which is a clear and uncluttered abstraction: <q>give me the quote of
+the day</q>. </p>
+
+<p>Note that this module does not import any Twisted functionality at all! The
+reason for doing things this way is integration. If your <q>business
+objects</q> are not stuck to your user interface, you can make a module that
+can integrate those objects with different protocols, GUIs, and file formats.
+Having such classes provides a way to decouple your components from each other,
+by allowing each to be used independently.</p>
+
+<p>In this manner, Twisted itself has minimal impact on the logic of your
+program. Although the Twisted <q>dot products</q> are highly interoperable,
+they
+also follow this approach. You can use them independently because they are not
+stuck to each other. They communicate in well-defined ways, and only when that
+communication provides some additional feature. Thus, you can use <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.html" title="twisted.web">twisted.web</a></code> with <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.html" title="twisted.enterprise">twisted.enterprise</a></code>, but neither requires the other, because
+they are integrated around the concept of <a href="defer.html" shape="rect">Deferreds</a>.</p>
+
+<p>Your Twisted applications should follow this style as much as possible.
+Have (at least) one module which implements your specific functionality,
+independent of any user-interface code. </p>
+
+<p>Next, we're going to need to associate this abstract logic with some way of
+displaying it to the user. We'll do this by writing a Twisted server protocol,
+which will respond to the clients that connect to it by sending a quote to the
+client and then closing the connection. Note: don't get too focused on the
+details of this -- different ways to interface with the user are 90% of what
+Twisted does, and there are lots of documents describing the different ways to
+do it.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IQuoter</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ An object that returns quotes.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a quote.
+ &quot;&quot;&quot;</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">quoter</span>.<span class="py-src-variable">getQuote</span>()+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTDFactory</span>(<span class="py-src-parameter">Factory</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ A factory for the Quote of the Day protocol.
+
+ @type quoter: L{IQuoter} provider
+ @ivar quoter: An object which provides L{IQuoter} which will be used by
+ the L{QOTD} protocol to get quotes to emit.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">QOTD</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">quoter</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">quoter</span> = <span class="py-src-variable">quoter</span>
+</pre><div class="caption">Twisted
+Quotes Protocol Implementation - <a href="listings/TwistedQuotes/quoteproto.py"><span class="filename">listings/TwistedQuotes/quoteproto.py</span></a></div></div>
+
+<p>This is a very straightforward <code>Protocol</code> implementation, and the
+pattern described above is repeated here. The Protocol contains essentially no
+logic of its own, just enough to tie together an object which can generate
+quotes (a <code class="python">Quoter</code>) and an object which can relay
+bytes to a TCP connection (a <code class="python">Transport</code>). When a
+client connects to this server, a <code class="python">QOTD</code> instance is
+created, and its <code class="python">connectionMade</code> method is called.
+</p>
+
+<p> The <code class="python">QOTDFactory</code>'s role is to specify to the
+Twisted framework how to create a <code class="python">Protocol</code> instance
+that will handle the connection. Twisted will not instantiate a <code class="python">QOTDFactory</code>; you will do that yourself later, in a
+<code class="shell">twistd</code> plug-in.
+</p>
+
+<p>Note: you can read more specifics of <code class="python">Protocol</code> and
+<code class="python">Factory</code> in the <a href="servers.html" shape="rect">Writing
+Servers</a> HOWTO.</p>
+
+<p>Once we have an abstraction -- a <code>Quoter</code> -- and we have a
+mechanism to connect it to the network -- the <code>QOTD</code> protocol -- the
+next thing to do is to put the last link in the chain of functionality between
+abstraction and user. This last link will allow a user to choose a
+<code>Quoter</code> and configure the protocol. Writing this configuration is
+covered in the <a href="application.html" shape="rect">Application HOWTO</a>.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/dirdbm.html b/vendor/Twisted-10.0.0/doc/core/howto/dirdbm.html
new file mode 100644
index 0000000000..af0e48ba21
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/dirdbm.html
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: DirDBM: Directory-based Storage</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">DirDBM: Directory-based Storage</h1>
+ <div class="toc"><ol><li><a href="#auto0">dirdbm.DirDBM</a></li><li><a href="#auto1">dirdbm.Shelf</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>dirdbm.DirDBM<a name="auto0"/></h2>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.dirdbm.DirDBM.html" title="twisted.persisted.dirdbm.DirDBM">twisted.persisted.dirdbm.DirDBM</a></code> is a DBM-like storage system.
+That is, it stores mappings between keys
+and values, like a Python dictionary, except that it stores the values in files
+in a directory - each entry is a different file. The keys must always be strings,
+as are the values. Other than that, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.dirdbm.DirDBM.html" title="twisted.persisted.dirdbm.DirDBM">DirDBM</a></code>
+objects act just like Python dictionaries.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.dirdbm.DirDBM.html" title="twisted.persisted.dirdbm.DirDBM">DirDBM</a></code> is useful for cases
+when you want to store small amounts of data in an organized fashion, without having
+to deal with the complexity of a RDBMS or other sophisticated database. It is simple,
+easy to use, cross-platform, and doesn't require any external C libraries, unlike
+Python's built-in DBM modules.</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; from twisted.persisted import dirdbm
+&gt;&gt;&gt; d = dirdbm.DirDBM(&quot;/tmp/dir&quot;)
+&gt;&gt;&gt; d[&quot;librarian&quot;] = &quot;ook&quot;
+&gt;&gt;&gt; d[&quot;librarian&quot;]
+'ook'
+&gt;&gt;&gt; d.keys()
+['librarian']
+&gt;&gt;&gt; del d[&quot;librarian&quot;]
+&gt;&gt;&gt; d.items()
+[]
+</pre>
+
+<h2>dirdbm.Shelf<a name="auto1"/></h2>
+
+<p>Sometimes it is neccessary to persist more complicated objects than strings.
+With some care, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.dirdbm.Shelf.html" title="twisted.persisted.dirdbm.Shelf">dirdbm.Shelf</a></code>
+can transparently persist
+them. <code>Shelf</code> works exactly like <code>DirDBM</code>, except that
+the values (but not the keys) can be arbitrary picklable objects. However,
+notice that mutating an object after it has been stored in the
+<code>Shelf</code> has no effect on the Shelf.
+When mutating objects, it is neccessary to explictly store them back in the <code>Shelf</code>
+afterwards:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; from twisted.persisted import dirdbm
+&gt;&gt;&gt; d = dirdbm.Shelf(&quot;/tmp/dir2&quot;)
+&gt;&gt;&gt; d[&quot;key&quot;] = [1, 2]
+&gt;&gt;&gt; d[&quot;key&quot;]
+[1, 2]
+&gt;&gt;&gt; l = d[&quot;key&quot;]
+&gt;&gt;&gt; l.append(3)
+&gt;&gt;&gt; d[&quot;key&quot;]
+[1, 2]
+&gt;&gt;&gt; d[&quot;key&quot;] = l
+&gt;&gt;&gt; d[&quot;key&quot;]
+[1, 2, 3]
+</pre>
+
+
+
+
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/gendefer.html b/vendor/Twisted-10.0.0/doc/core/howto/gendefer.html
new file mode 100644
index 0000000000..4d5878bf94
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/gendefer.html
@@ -0,0 +1,415 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Generating Deferreds</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Generating Deferreds</h1>
+ <div class="toc"><ol><li><a href="#auto0">Class overview</a></li><ul><li><a href="#auto1">Basic Callback Functions</a></li></ul><li><a href="#auto2">What Deferreds don't do: make your code asynchronous</a></li><li><a href="#auto3">Advanced Processing Chain Control</a></li><li><a href="#auto4">Returning Deferreds from synchronous functions</a></li><li><a href="#auto5">Integrating blocking code with Twisted</a></li><li><a href="#auto6">Possible sources of error</a></li><ul><li><a href="#auto7">Firing Deferreds more than once is impossible</a></li><li><a href="#auto8">Synchronous callback execution</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> objects are
+signals that a function you have called does not yet have the data you want
+available. When a function returns a Deferred object, your calling function
+attaches callbacks to it to handle the data when available.</p>
+
+<p>This document addresses the other half of the question: writing functions
+that return Deferreds, that is, constructing Deferred objects, arranging for
+them to be returned immediately without blocking until data is available, and
+firing their callbacks when the data is available.</p>
+
+<p>This document assumes that you are familiar with the asynchronous model used
+by Twisted, and with <a href="defer.html" shape="rect">using deferreds returned by functions</a>
+.</p>
+
+<a name="class" shape="rect"/>
+
+<h2>Class overview<a name="auto0"/></h2>
+
+<p>This is an overview API reference for Deferred from the point of creating a
+Deferred and firing its callbacks and errbacks. It is not meant to be a
+substitute for the docstrings in the Deferred class, but can provide
+guidelines for its use.</p>
+
+<p>There is a parallel overview of functions used by calling function which
+the Deferred is returned to at <a href="defer.html#class" shape="rect">Using Deferreds</a>.</p>
+
+<h3>Basic Callback Functions<a name="auto1"/></h3>
+
+<ul>
+ <li>
+ <code class="py-prototype">callback(result)</code>
+
+ <p>Run success callbacks with the given result. <em>This
+ can only be run once.</em> Later calls to this or
+ <code>errback</code> will raise <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.AlreadyCalledError.html" title="twisted.internet.defer.AlreadyCalledError">twisted.internet.defer.AlreadyCalledError</a></code>.
+ If further callbacks or errbacks are added after this
+ point, addCallbacks will run the callbacks immediately.</p>
+ </li>
+
+ <li>
+ <code class="py-prototype">errback(failure)</code>
+
+ <p>Run error callbacks with the given failure. <em>This can
+ only be run once.</em> Later calls to this or
+ <code>callback</code> will raise <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.AlreadyCalledError.html" title="twisted.internet.defer.AlreadyCalledError">twisted.internet.defer.AlreadyCalledError</a></code>.
+ If further callbacks or errbacks are added after this
+ point, addCallbacks will run the callbacks immediately.</p>
+ </li>
+</ul>
+
+<h2>What Deferreds don't do: make your code asynchronous<a name="auto2"/></h2>
+
+<p><em>Deferreds do not make the code magically not block.</em></p>
+
+<p>Let's take this function as an example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+<span class="py-src-variable">TARGET</span> = <span class="py-src-number">10000</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">largeFibonnaciNumber</span>():
+ <span class="py-src-comment"># create a Deferred object to return:</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+
+ <span class="py-src-comment"># calculate the ten thousandth Fibonnaci number</span>
+
+ <span class="py-src-variable">first</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">second</span> = <span class="py-src-number">1</span>
+
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">i</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">xrange</span>(<span class="py-src-variable">TARGET</span> - <span class="py-src-number">1</span>):
+ <span class="py-src-variable">new</span> = <span class="py-src-variable">first</span> + <span class="py-src-variable">second</span>
+ <span class="py-src-variable">first</span> = <span class="py-src-variable">second</span>
+ <span class="py-src-variable">second</span> = <span class="py-src-variable">new</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">i</span> % <span class="py-src-number">100</span> == <span class="py-src-number">0</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Progress: calculating the %dth Fibonnaci number&quot;</span> % <span class="py-src-variable">i</span>
+
+ <span class="py-src-comment"># give the Deferred the answer to pass to the callbacks:</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">second</span>)
+
+ <span class="py-src-comment"># return the Deferred with the answer:</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+
+<span class="py-src-variable">timeBefore</span> = <span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>()
+
+<span class="py-src-comment"># call the function and get our Deferred</span>
+<span class="py-src-variable">d</span> = <span class="py-src-variable">largeFibonnaciNumber</span>()
+
+<span class="py-src-variable">timeAfter</span> = <span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>()
+
+<span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Total time taken for largeFibonnaciNumber call: %0.3f seconds&quot;</span> %
+ (<span class="py-src-variable">timeAfter</span> - <span class="py-src-variable">timeBefore</span>)
+
+<span class="py-src-comment"># add a callback to it to print the number</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printNumber</span>(<span class="py-src-parameter">number</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;The %dth Fibonacci number is %d&quot;</span> % (<span class="py-src-variable">TARGET</span>, <span class="py-src-variable">number</span>)
+
+<span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Adding the callback now.&quot;</span>
+
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printNumber</span>)
+</pre>
+
+<p>You will notice that despite creating a Deferred in the
+<code>largeFibonnaciNumber</code> function, these things happened:</p>
+<ul>
+<li>the &quot;Total time taken for largeFibonnaciNumber call&quot; output
+shows that the function did not return immediately as asynchronous functions
+are expected to do; and</li>
+<li>rather than the callback being added before the result was available and
+called after the result is available, it isn't even added until after the
+calculation has been completed.</li>
+</ul>
+
+<p> The function completed its calculation before returning, blocking the
+process until it had finished, which is exactly what asynchronous functions
+are not meant to do. Deferreds are not a non-blocking talisman: they are a
+signal for asynchronous functions to <em>use</em> to pass results onto
+callbacks, but using them does not guarantee that you have an asynchronous
+function.</p>
+
+
+<h2>Advanced Processing Chain Control<a name="auto3"/></h2>
+
+<ul>
+ <li>
+ <code class="py-prototype">pause()</code>
+
+ <p>Cease calling any methods as they are added, and do not
+ respond to <code>callback</code>, until
+ <code>self.unpause()</code> is called.</p>
+ </li>
+
+ <li>
+ <code class="py-prototype">unpause()</code>
+
+ <p>If <code>callback</code> has been called on this
+ Deferred already, call all the callbacks that have been
+ added to this Deferred since <code>pause</code> was
+ called.</p>
+
+ <p>Whether it was called or not, this will put this
+ Deferred in a state where further calls to
+ <code>addCallbacks</code> or <code>callback</code> will
+ work as normal.</p>
+ </li>
+</ul>
+
+<h2>Returning Deferreds from synchronous functions<a name="auto4"/></h2>
+
+<p>Sometimes you might wish to return a Deferred from a synchronous function.
+There are several reasons why, the major two are maintaining API compatibility
+with another version of your function which returns a Deferred, or allowing
+for the possiblity that in the future your function might need to be
+asynchronous.</p>
+
+<p>In the <a href="defer.html" shape="rect">Using Deferreds</a> reference, we gave the
+following example of a synchronous function:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">synchronousIsValidUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">'''
+ Return true if user is a valid user, false otherwise
+ '''</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">&quot;Alice&quot;</span>, <span class="py-src-string">&quot;Angus&quot;</span>, <span class="py-src-string">&quot;Agnes&quot;</span>]
+</pre><div class="caption">Source listing - <a href="listings/deferred/synch-validation.py"><span class="filename">listings/deferred/synch-validation.py</span></a></div></div>
+
+<p>While we can require that callers of our function wrap our synchronous
+result in a Deferred using <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.maybeDeferred.html" title="twisted.internet.defer.maybeDeferred">maybeDeferred</a></code>, for the sake of API
+compatibility it is better to return a Deferred ourself using <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.succeed.html" title="twisted.internet.defer.succeed">defer.succeed</a></code>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">immediateIsValidUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">'''
+ Returns a Deferred resulting in true if user is a valid user, false
+ otherwise
+ '''</span>
+
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">&quot;Alice&quot;</span>, <span class="py-src-string">&quot;Angus&quot;</span>, <span class="py-src-string">&quot;Agnes&quot;</span>]
+
+ <span class="py-src-comment"># return a Deferred object already called back with the value of result</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">result</span>)
+</pre>
+
+<p>There is an equivalent <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.fail.html" title="twisted.internet.defer.fail">defer.fail</a></code> method to return a Deferred with the
+errback chain already fired.</p>
+
+<h2>Integrating blocking code with Twisted<a name="auto5"/></h2>
+
+<p>At some point, you are likely to need to call a blocking function: many
+functions in third party libraries will have long running blocking functions.
+There is no way to 'force' a function to be asynchronous: it must be written
+that way specifically. When using Twisted, your own code should be
+asynchronous, but there is no way to make third party functions asynchronous
+other than rewriting them.</p>
+
+<p>In this case, Twisted provides the ability to run the blocking code in a
+separate thread rather than letting it block your application. The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.threads.deferToThread.html" title="twisted.internet.threads.deferToThread">twisted.internet.threads.deferToThread</a></code> function will set up
+a thread to run your blocking function, return a Deferred and later fire that
+Deferred when the thread completes.</p>
+
+<p>Let's assume our <code class="python">largeFibonnaciNumber</code> function
+from above is in a third party library (returning the result of the
+calculation, not a Deferred) and is not easily modifiable to be finished in
+discrete blocks. This example shows it being called in a thread, unlike in the
+earlier section we'll see that the operation does not block our entire
+program:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">largeFibonnaciNumber</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Represent a long running blocking function by calculating
+ the TARGETth Fibonnaci number
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">TARGET</span> = <span class="py-src-number">10000</span>
+
+ <span class="py-src-variable">first</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">second</span> = <span class="py-src-number">1</span>
+
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">i</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">xrange</span>(<span class="py-src-variable">TARGET</span> - <span class="py-src-number">1</span>):
+ <span class="py-src-variable">new</span> = <span class="py-src-variable">first</span> + <span class="py-src-variable">second</span>
+ <span class="py-src-variable">first</span> = <span class="py-src-variable">second</span>
+ <span class="py-src-variable">second</span> = <span class="py-src-variable">new</span>
+
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">second</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">threads</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">fibonacciCallback</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Callback which manages the largeFibonnaciNumber result by
+ printing it out
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;largeFibonnaciNumber result =&quot;</span>, <span class="py-src-variable">result</span>
+ <span class="py-src-comment"># make sure the reactor stops after the callback chain finishes,</span>
+ <span class="py-src-comment"># just so that this example terminates</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">run</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Run a series of operations, deferring the largeFibonnaciNumber
+ operation to a thread and performing some other operations after
+ adding the callback
+ &quot;&quot;&quot;</span>
+ <span class="py-src-comment"># get our Deferred which will be called with the largeFibonnaciNumber result</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">threads</span>.<span class="py-src-variable">deferToThread</span>(<span class="py-src-variable">largeFibonnaciNumber</span>)
+ <span class="py-src-comment"># add our callback to print it out</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">fibonacciCallback</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;1st line after the addition of the callback&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;2nd line after the addition of the callback&quot;</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">run</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h2>Possible sources of error<a name="auto6"/></h2>
+
+<p>Deferreds greatly simplify the process of writing asynchronous code by
+providing a standard for registering callbacks, but there are some subtle and
+sometimes confusing rules that you need to follow if you are going to use
+them. This mostly applies to people who are writing new systems that use
+Deferreds internally, and not writers of applications that just add callbacks
+to Deferreds produced and processed by other systems. Nevertheless, it is good
+to know.</p>
+
+<h3>Firing Deferreds more than once is impossible<a name="auto7"/></h3>
+
+<p>Deferreds are one-shot. You can only call <code>Deferred.callback</code> or
+<code>Deferred.errback</code> once. The processing chain continues each time
+you add new callbacks to an already-called-back-to Deferred.</p>
+
+<h3>Synchronous callback execution<a name="auto8"/></h3>
+
+<p>If a Deferred already has a result available, addCallback
+<strong>may</strong> call the callback synchronously: that is, immediately
+after it's been added. In situations where callbacks modify state, it is
+might be desirable for the chain of processing to halt until all callbacks are
+added. For this, it is possible to <code>pause</code> and <code>unpause</code>
+a Deferred's processing chain while you are adding lots of callbacks.</p>
+
+<p>Be careful when you use these methods! If you <code>pause</code> a
+Deferred, it is <em>your</em> responsibility to make sure that you unpause it.
+The function adding the callbacks must unpause a paused Deferred, it should
+<em>never</em> be the responsibility of the code that actually fires the
+callback chain by calling <code>callback</code> or <code>errback</code> as
+this would negate its usefulness!</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/glossary.html b/vendor/Twisted-10.0.0/doc/core/howto/glossary.html
new file mode 100644
index 0000000000..7826774fe6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/glossary.html
@@ -0,0 +1,347 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Glossary</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Glossary</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<dl>
+
+<dt><a name="adaptee" shape="rect">adaptee</a></dt>
+<dd>
+ An object that has been adapted, also called <q>original</q>. See <a href="#Adapter" shape="rect">Adapter</a>.
+</dd>
+
+<dt><a name="Adapter" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.Adapter.html" title="twisted.python.components.Adapter">Adapter</a></code></a></dt>
+<dd>
+ An object whose sole purpose is to implement an Interface for another object.
+ See <a href="components.html" shape="rect">Interfaces and Adapters</a>.
+</dd>
+
+<dt><a name="Application" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code></a></dt>
+<dd>
+ A <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">twisted.application.service.Application</a></code>. There are
+ HOWTOs on <a href="basics.html" shape="rect">creating and manipulating</a> them as a
+ system-administrator, as well as <a href="application.html" shape="rect">using</a> them in
+ your code.
+</dd>
+
+<dt><a name="Avatar" shape="rect">Avatar</a></dt>
+<dd>
+ (from <a href="#Cred" shape="rect">Twisted Cred</a>) business logic for specific user.
+ For example, in <a href="#PB" shape="rect">PB</a> these are perspectives, in pop3 these
+ are mailboxes, and so on.
+</dd>
+
+<dt><a name="Banana" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.banana.Banana.html" title="twisted.spread.banana.Banana">Banana</a></code></a></dt>
+<dd>
+ The low-level data marshalling layer of <a href="#Spread" shape="rect">Twisted Spread</a>.
+ See <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.banana.html" title="twisted.spread.banana">twisted.spread.banana</a></code>.
+</dd>
+
+<dt><a name="Broker" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Broker.html" title="twisted.spread.pb.Broker">Broker</a></code></a></dt>
+<dd>
+ A <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Broker.html" title="twisted.spread.pb.Broker">twisted.spread.pb.Broker</a></code>, the object request
+ broker for <a href="#Spread" shape="rect">Twisted Spread</a>.
+</dd>
+
+<dt><a name="cache" shape="rect">cache</a></dt>
+<dd>
+ A way to store data in readily accessible place for later reuse. Caching data
+ is often done because the data is expensive to produce or access. Caching data
+ risks being stale, or out of sync with the original data.
+</dd>
+
+<dt><a name="component" shape="rect">component</a></dt>
+<dd>
+ A special kind of (persistent) <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.Adapter.html" title="twisted.python.components.Adapter">Adapter</a></code> that works with a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.Componentized.html" title="twisted.python.components.Componentized">twisted.python.components.Componentized</a></code>. See also <a href="components.html" shape="rect">Interfaces and Adapters</a>.
+</dd>
+
+<dt><a name="Componentized" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.Componentized.html" title="twisted.python.components.Componentized">Componentized</a></code></a></dt>
+<dd>
+ A Componentized object is a collection of information, separated
+ into domain-specific or role-specific instances, that all stick
+ together and refer to each other.
+ Each object is an <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.Adapter.html" title="twisted.python.components.Adapter">Adapter</a></code>, which, in the
+ context of Componentized, we call <q>components</q>. See also <a href="components.html" shape="rect">Interfaces and Adapters</a>.
+</dd>
+
+<dt><a name="conch" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.conch.html" title="twisted.conch">conch</a></code></a></dt>
+<dd>Twisted's SSH implementation.</dd>
+
+<dt><a name="Connector" shape="rect">Connector</a></dt>
+<dd>
+ Object used to interface between client connections and protocols, usually
+ used with a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.ClientFactory.html" title="twisted.internet.protocol.ClientFactory">twisted.internet.protocol.ClientFactory</a></code>
+ to give you control over how a client connection reconnects. See <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IConnector.html" title="twisted.internet.interfaces.IConnector">twisted.internet.interfaces.IConnector</a></code> and <a href="clients.html" shape="rect">Writing Clients</a>.
+</dd>
+
+<dt><a name="Consumer" shape="rect">Consumer</a></dt>
+<dd>
+ An object that consumes data from a <a href="#Producer" shape="rect">Producer</a>. See
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IConsumer.html" title="twisted.internet.interfaces.IConsumer">twisted.internet.interfaces.IConsumer</a></code>.
+</dd>
+
+<dt><a name="Cred" shape="rect">Cred</a></dt>
+<dd>
+ Twisted's authentication API, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.html" title="twisted.cred">twisted.cred</a></code>. See
+ <a href="cred.html" shape="rect">Introduction to Twisted Cred</a> and
+ <a href="pb-cred.html" shape="rect">Twisted Cred usage</a>.
+</dd>
+
+<dt><a name="credentials" shape="rect">credentials</a></dt>
+<dd>
+ A username/password, public key, or some other information used for
+ authentication.
+</dd>
+
+<dt><a name="credential-checker" shape="rect">credential checker</a></dt>
+<dd>
+ Where authentication actually happens. See
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.checkers.ICredentialChecker.html" title="twisted.cred.checkers.ICredentialChecker">ICredentialChecker</a></code>.
+</dd>
+
+<dt><a name="CVSToys" shape="rect">CVSToys</a></dt>
+<dd>A nifty set of tools for CVS, available at
+<a href="http://twistedmatrix.com/users/acapnotic/wares/code/CVSToys/" shape="rect">http://twistedmatrix.com/users/acapnotic/wares/code/CVSToys/</a>.</dd>
+
+<dt><a name="Daemon" shape="rect">Daemon</a></dt>
+<dd>
+ A background process that does a job or handles client requests.
+ <i>Daemon</i> is a Unix term; <i>service</i> is the NT equivalent.
+</dd>
+
+<dt><a name="Deferred" shape="rect"><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code></a></dt>
+<dd>
+ A instance of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code>, an
+ abstraction for handling chains of callbacks and error handlers
+ (<q>errbacks</q>).
+ See the <a href="defer.html" shape="rect">Deferring Execution</a> HOWTO.
+</dd>
+
+<dt><a name="Enterprise" shape="rect">Enterprise</a></dt>
+<dd>
+ Twisted's RDBMS support. It contains <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.adbapi.html" title="twisted.enterprise.adbapi">twisted.enterprise.adbapi</a></code> for asynchronous access to any
+ standard DB-API 2.0 module, and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.row.html" title="twisted.enterprise.row">twisted.enterprise.row</a></code>, a <q><a href="#ROW" shape="rect">Relational
+ Object Wrapper</a></q>. See <a href="rdbms.html" shape="rect">Introduction to
+ Twisted Enterprise</a> and <a href="row.html" shape="rect">Twisted Enterprise Row
+ Objects</a> for more details.
+</dd>
+
+<dt><a name="errback" shape="rect">errback</a></dt>
+<dd>
+ A callback attached to a <a href="#Deferred" shape="rect">Deferred</a> with
+ <code>.addErrback</code> to handle errors.
+</dd>
+
+<dt><a name="Factory" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">Factory</a></code></a></dt>
+<dd>
+ In general, an object that constructs other objects. In Twisted, a Factory
+ usually refers to a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">twisted.internet.protocol.Factory</a></code>, which constructs
+ <a href="#Protocol" shape="rect">Protocol</a> instances for incoming or outgoing
+ connections. See <a href="servers.html" shape="rect">Writing Servers</a> and <a href="clients.html" shape="rect">Writing Clients</a>.
+</dd>
+
+<dt><a name="Failure" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code></a></dt>
+<dd>
+ Basically, an asynchronous exception that contains traceback information;
+ these are used for passing errors through asynchronous callbacks.
+</dd>
+
+<dt><a name="im" shape="rect">im</a></dt>
+<dd>
+ Abbreviation of <q>(Twisted) <a href="#InstanceMessenger" shape="rect">Instance
+ Messenger</a></q>.
+ </dd>
+
+<dt><a name="InstanceMessenger" shape="rect">Instance Messenger</a></dt>
+<dd>
+ Instance Messenger is a multi-protocol chat program that comes with
+ Twisted. It can communicate via TOC with the AOL servers, via IRC, as well as
+ via <a href="#PerspectiveBroker" shape="rect">PB</a> with <a href="#Words" shape="rect">Twisted
+ Words</a>. See <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.im.html" title="twisted.im">twisted.im</a></code>.
+</dd>
+
+<dt><a name="Interface" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.Interface.html" title="twisted.python.components.Interface">Interface</a></code></a></dt>
+<dd>
+ A class that defines and documents methods that a class conforming to that
+ interface needs to have. A collection of core twisted.internet interfaces can
+ be found in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.html" title="twisted.internet.interfaces">twisted.internet.interfaces</a></code>. See also <a href="components.html" shape="rect">Interfaces and Adapters</a>.
+</dd>
+
+<dt><a name="Jelly" shape="rect">Jelly</a></dt>
+<dd>
+ The serialization layer for <a href="#Spread" shape="rect">Twisted Spread</a>, although it
+ can be used seperately from Twisted Spread as well. It is similar in purpose
+ to Python's standard <code>pickle</code> module, but is more
+ network-friendly, and depends on a separate marshaller (<a href="#Banana" shape="rect">Banana</a>, in most cases). See <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.jelly.html" title="twisted.spread.jelly">twisted.spread.jelly</a></code>.
+</dd>
+
+<dt><a name="Lore" shape="rect">Lore</a></dt>
+
+<dd><a href="http://twistedmatrix.com/trac/wiki/TwistedLore/" shape="rect">Lore</a> is
+Twisted's documentation system. The source format is a subset of
+XHTML, and output formats include HTML and LaTeX.</dd>
+
+<dt><a name="Manhole" shape="rect">Manhole</a></dt>
+<dd>
+ A debugging/administration interface to a Twisted application.
+</dd>
+
+<dt><a name="Microdom" shape="rect">Microdom</a></dt>
+<dd>
+ A partial DOM implementation using <a href="#SUX" shape="rect">SUX</a>. It is simple and
+ pythonic, rather than strictly standards-compliant. See <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.microdom.html" title="twisted.web.microdom">twisted.web.microdom</a></code>.
+</dd>
+
+<dt><a name="Names" shape="rect">Names</a></dt>
+<dd>Twisted's DNS server, found in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.names.html" title="twisted.names">twisted.names</a></code>.</dd>
+
+<dt><a name="Nevow" shape="rect">Nevow</a></dt>
+<dd>The successor to <a href="#Woven" shape="rect">Woven</a>; available from
+<a href="http://divmod.org/trac/" shape="rect">Divmod</a>.
+</dd>
+
+<dt><a name="PB" shape="rect">PB</a></dt>
+<dd>
+ Abbreviation of <q><a href="#PerspectiveBroker" shape="rect">Perspective
+ Broker</a></q>.
+</dd>
+
+<dt><a name="PerspectiveBroker" shape="rect">Perspective Broker</a></dt>
+<dd>
+ The high-level object layer of Twisted <a href="#Spread" shape="rect">Spread</a>,
+ implementing semantics for method calling and object copying, caching, and
+ referencing. See <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.html" title="twisted.spread.pb">twisted.spread.pb</a></code>.
+</dd>
+
+<dt><a name="Portal" shape="rect">Portal</a></dt>
+<dd>
+ Glues <a href="#credential-checker" shape="rect">credential checkers</a> and
+ <a href="#realm" shape="rect">realm</a>s together.
+</dd>
+
+<dt><a name="Producer" shape="rect">Producer</a></dt>
+<dd>
+ An object that generates data a chunk at a time, usually to be processed by a
+ <a href="#Consumer" shape="rect">Consumer</a>. See
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IProducer.html" title="twisted.internet.interfaces.IProducer">twisted.internet.interfaces.IProducer</a></code>.
+</dd>
+
+<dt><a name="Protocol" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">Protocol</a></code></a></dt>
+<dd>
+ In general each network connection has its own Protocol instance to manage
+ connection-specific state. There is a collection of standard
+ protocol implementations in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.protocols.html" title="twisted.protocols">twisted.protocols</a></code>. See
+ also <a href="servers.html" shape="rect">Writing Servers</a> and <a href="clients.html" shape="rect">Writing Clients</a>.
+</dd>
+
+<dt><a name="PSU" shape="rect">PSU</a></dt>
+<dd>There is no PSU.</dd>
+
+<dt><a name="Reactor" shape="rect">Reactor</a></dt>
+<dd>
+ The core event-loop of a Twisted application. See
+ <a href="reactor-basics.html" shape="rect">Reactor Basics</a>.
+</dd>
+
+<dt><a name="Reality" shape="rect">Reality</a></dt>
+<dd>See <q><a href="#TwistedReality" shape="rect">Twisted Reality</a></q></dd>
+
+<dt><a name="realm" shape="rect">realm</a></dt>
+<dd>
+ (in <a href="#Cred" shape="rect">Twisted Cred</a>) stores <a href="#Avatar" shape="rect">avatars</a>
+ and perhaps general business logic. See
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.IRealm.html" title="twisted.cred.portal.IRealm">IRealm</a></code>.
+</dd>
+
+<dt><a name="Resource" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code></a></dt>
+<dd>
+ A <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">twisted.web.resource.Resource</a></code>, which are served
+ by Twisted Web. Resources can be as simple as a static file on disk, or they
+ can have dynamically generated content.
+</dd>
+
+<dt><a name="ROW" shape="rect">ROW</a></dt>
+<dd>
+ <em>R</em>elational <em>O</em>bject <em>W</em>rapper, an object-oriented
+ interface to a relational database. See <a href="row.html" shape="rect">Twisted Enterprise
+ Row Objects</a>.
+</dd>
+
+<dt><a name="Service" shape="rect">Service</a></dt>
+<dd>
+ A <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.Service.html" title="twisted.application.service.Service">twisted.application.service.Service</a></code>. See <a href="application.html" shape="rect">Application howto</a> for a description of how they
+ relate to <a href="#Application" shape="rect">Applications</a>.
+</dd>
+
+<dt><a name="Spread" shape="rect">Spread</a></dt>
+<dd><a href="http://twistedmatrix.com/products/spread" shape="rect">Twisted Spread</a> is
+Twisted's remote-object suite. It consists of three layers:
+<a href="#PerspectiveBroker" shape="rect">Perspective Broker</a>, <a href="#Jelly" shape="rect">Jelly</a>
+and <a href="#Banana" shape="rect">Banana.</a> See <a href="pb.html" shape="rect">Writing Applications
+with Perspective Broker</a>.</dd>
+
+<dt><a name="SUX" shape="rect">SUX</a></dt>
+<dd><em>S</em>mall <em>U</em>ncomplicated <em>X</em>ML, Twisted's simple XML
+parser written in pure Python. See
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.protocols.sux.html" title="twisted.protocols.sux">twisted.protocols.sux</a></code>.</dd>
+
+<dt><a name="TAC" shape="rect">TAC</a></dt>
+<dd>A <em>T</em>wisted <em>A</em>pplication <em>C</em>onfiguration is a Python
+source file, generally with the <em>.tac</em> extension, which defines
+configuration to make an application runnable using <code>twistd</code>.</dd>
+
+<dt><a name="TAP" shape="rect">TAP</a></dt>
+<dd><em>T</em>wisted <em>A</em>pplication <em>P</em>ickle (deprecated), or simply just a
+<em>T</em>wisted <em>AP</em>plication. A serialised application that was created
+with <code>mktap</code> and runnable by <code>twistd</code>. See
+<a href="basics.html" shape="rect">Using the Utilities</a>.</dd>
+
+<dt><a name="Trial" shape="rect">Trial</a></dt>
+<dd><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.trial.html" title="twisted.trial">twisted.trial</a></code>, Twisted's unit-testing framework,
+modelled after <a href="http://pyunit.sourceforge.net/" shape="rect">pyunit</a>. See also
+<a href="testing.html" shape="rect">Writing tests for Twisted code</a>.</dd>
+
+<dt><a name="TwistedMatrixLaboratories" shape="rect">Twisted Matrix Laboratories</a></dt>
+<dd>The team behind Twisted.
+<a href="http://twistedmatrix.com/" shape="rect">http://twistedmatrix.com/</a>.</dd>
+
+<dt><a name="TwistedReality" shape="rect">Twisted Reality</a></dt>
+<dd>
+In days of old, the Twisted Reality multiplayer text-based interactive-fiction
+system was the main focus of Twisted Matrix Labs; Twisted, the general networking
+framework, grew out of Reality's need for better network functionality. Twisted
+Reality has been superseded by the
+<a href="http://www.divmod.org/trac/wiki/DivmodImaginary" shape="rect">Imaginary</a> project.
+</dd>
+
+<dt><a name="usage" shape="rect"><code class="API" noexpand="1"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.usage.html" title="twisted.python.usage">usage</a></code></a></dt>
+<dd>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.usage.html" title="twisted.python.usage">twisted.python.usage</a></code> module, a replacement for
+the standard <code>getopt</code> module for parsing command-lines which is much
+easier to work with. See <a href="options.html" shape="rect">Parsing command-lines</a>.</dd>
+
+<dt><a name="Words" shape="rect">Words</a></dt>
+<dd>Twisted Words is a multi-protocol chat server that uses the
+<a href="#PerspectiveBroker" shape="rect">Perspective Broker</a> protocol as its native
+communication style. See <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.words.html" title="twisted.words">twisted.words</a></code>.</dd>
+
+<dt><a name="Woven" shape="rect">Woven</a></dt>
+<dd><em>W</em>eb <em>O</em>bject <em>V</em>isualization <em>En</em>vironment.
+A templating system previously, but no longer, included with Twisted. Woven
+has largely been superceded by <a href="http://divmod.org/trac/wiki/DivmodNevow" shape="rect">
+Divmod Nevow</a>.</dd>
+
+</dl>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/howto.tidyrc b/vendor/Twisted-10.0.0/doc/core/howto/howto.tidyrc
new file mode 100644
index 0000000000..68965051d8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/howto.tidyrc
@@ -0,0 +1,6 @@
+output-xml: yes
+output-xhtml: yes
+tidy-mark: no
+indent: auto
+gnu-emacs: yes
+add-xml-decl: yes \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/index.html b/vendor/Twisted-10.0.0/doc/core/howto/index.html
new file mode 100644
index 0000000000..4644564cdb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/index.html
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <ul class="toc">
+ <li><a name="introduction" shape="rect">Introduction</a>
+
+ <ul>
+ <li><a href="vision.html" shape="rect">Executive summary</a></li>
+
+ <li><a href="overview.html" shape="rect">Graphical Overview</a></li>
+
+ <li><a href="internet-overview.html" shape="rect">Twisted
+ Internet</a></li>
+ </ul>
+ </li>
+
+ <li><a name="tutorials" shape="rect">Tutorials</a>
+
+ <ul>
+ <li><a href="servers.html" shape="rect">Writing a TCP server</a></li>
+
+ <li><a href="clients.html" shape="rect">Writing a TCP client</a></li>
+
+ <li><a href="quotes.html" shape="rect">Setting up the TwistedQuotes
+ application</a></li>
+
+ <li><a href="design.html" shape="rect">Designing a Twisted application</a></li>
+
+ <li>
+ <a href="tutorial/index.html" shape="rect">Tutorial: Twisted From
+ Scratch</a>
+
+ <ol>
+ <li><a href="tutorial/intro.html" shape="rect">The Evolution of
+ Finger: building a simple finger service</a></li>
+
+ <li><a href="tutorial/protocol.html" shape="rect">The Evolution of
+ Finger: adding features to the finger service</a></li>
+
+ <li><a href="tutorial/style.html" shape="rect">The Evolution of
+ Finger: cleaning up the finger code</a></li>
+
+ <li><a href="tutorial/components.html" shape="rect">The Evolution of
+ Finger: moving to a component based
+ architecture</a></li>
+
+ <li><a href="tutorial/backends.html" shape="rect">The Evolution of
+ Finger: pluggable backends</a></li>
+
+ <li><a href="tutorial/web.html" shape="rect">The Evolution of
+ Finger: a clean web frontend</a></li>
+
+ <li><a href="tutorial/pb.html" shape="rect">The Evolution of Finger:
+ Twisted client support using Perspective
+ Broker</a></li>
+
+ <li><a href="tutorial/factory.html" shape="rect">The Evolution of
+ Finger: using a single factory for multiple
+ protocols</a></li>
+
+ <li><a href="tutorial/client.html" shape="rect">The Evolution of
+ Finger: a Twisted finger client</a></li>
+
+ <li><a href="tutorial/library.html" shape="rect">The Evolution of
+ Finger: making a finger library</a></li>
+
+ <li><a href="tutorial/configuration.html" shape="rect">The Evolution
+ of Finger: configuration and packaging of the finger
+ service</a></li>
+ </ol>
+ </li>
+ <li><a href="ssl.html" shape="rect">Using SSL in Twisted</a></li>
+ </ul>
+ </li>
+
+ <li><a name="lowlevel" shape="rect">Low-Level Networking and Event Loop</a>
+
+ <ul>
+ <li><a href="reactor-basics.html" shape="rect">Reactor basics</a></li>
+
+ <li><a href="udp.html" shape="rect">UDP Networking</a></li>
+
+ <li><a href="process.html" shape="rect">Using processes</a></li>
+
+ <li><a href="defer.html" shape="rect">Using Deferreds</a></li>
+
+ <li><a href="gendefer.html" shape="rect">Generating deferreds</a></li>
+
+ <li><a href="deferredindepth.html" shape="rect">Deferreds in
+ depth</a></li>
+
+ <li><a href="time.html" shape="rect">Scheduling</a></li>
+
+ <li><a href="threading.html" shape="rect">Using threads</a></li>
+
+ <li><a href="producers.html" shape="rect">Efficient High-Volume Streaming</a></li>
+
+ <li><a href="choosing-reactor.html" shape="rect">Choosing a reactor and
+ GUI toolkit integration</a></li>
+ </ul>
+ </li>
+
+ <li><a name="highlevel" shape="rect">High-Level Twisted</a>
+
+ <ul>
+ <li><a href="basics.html" shape="rect">Helper programs and scripts
+ (twistd, ..)</a></li>
+
+ <li><a href="plugin.html" shape="rect">Twisted's plugin architecture</a></li>
+
+ <li><a href="tap.html" shape="rect">Writing Twisted Application Plugins
+ for twistd</a></li>
+
+ <li><a href="components.html" shape="rect">Interfaces and Adapters
+ (Component Architecture)</a></li>
+
+ <li><a href="cred.html" shape="rect">Cred: Pluggable
+ Authentication</a></li>
+
+ <li><a href="application.html" shape="rect">Using the Twisted Application
+ Framework</a></li>
+
+ </ul>
+ </li>
+
+ <li><a name="utilities" shape="rect">Utilities</a>
+
+ <ul>
+ <li><a href="options.html" shape="rect">Parsing command-lines</a></li>
+
+ <li><a href="logging.html" shape="rect">Logging</a></li>
+
+ <li><a href="dirdbm.html" shape="rect">Using Dirdbm: Directory-based Storage</a></li>
+
+ <li><a href="telnet.html" shape="rect">Using telnet to manipulate a
+ twisted server</a></li>
+
+ <li><a href="testing.html" shape="rect">Tips for writing tests for Twisted
+ code using Trial</a></li>
+ </ul>
+ </li>
+
+ <li><a name="rdbms" shape="rect">Twisted RDBMS support</a>
+
+ <ul>
+ <li><a href="rdbms.html" shape="rect">Twisted RDBMS support with
+ adbapi</a></li>
+
+ <li><a href="row.html" shape="rect">The row database
+ abstraction</a></li>
+ </ul>
+ </li>
+
+ <li><a name="pb" shape="rect">Perspective Broker</a>
+
+ <ul>
+ <li><a href="pb.html" shape="rect">Twisted Spread</a></li>
+
+ <li><a href="pb-intro.html" shape="rect">Introduction to Perspective
+ Broker</a></li>
+
+ <li><a href="pb-usage.html" shape="rect">Using Perspective
+ Broker</a></li>
+
+ <li><a href="pb-copyable.html" shape="rect">Passing Complex
+ Types</a></li>
+
+ <li><a href="pb-cred.html" shape="rect">Authentication with Perspective
+ Broker</a></li>
+ </ul>
+ </li>
+
+ <li><a name="appendix" shape="rect">Appendix</a>
+
+ <ul>
+ <li><a href="glossary.html" shape="rect">Glossary</a></li>
+
+ <li class="ignoretoc"><a href="debug-with-emacs.html" shape="rect">Tips
+ for debugging with emacs</a></li>
+ </ul>
+ </li>
+ </ul>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/internet-overview.html b/vendor/Twisted-10.0.0/doc/core/howto/internet-overview.html
new file mode 100644
index 0000000000..40ea11afef
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/internet-overview.html
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Overview of Twisted Internet</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Overview of Twisted Internet</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<p>Twisted Internet is a collection of compatible event-loops for Python.
+It contains the code to dispatch events to interested observers and a portable
+API so that observers need not care about which event loop is running. Thus,
+it is possible to use the same code for different loops, from Twisted's basic,
+yet portable, <code>select</code>-based loop to the loops of various GUI
+toolkits like GTK+ or Tk.</p>
+
+<p>Twisted Internet contains the various interfaces to the reactor
+API, whose usage is documented in the low-level chapter. Those APIs
+are <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorCore.html" title="twisted.internet.interfaces.IReactorCore">IReactorCore</a></code>,
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTCP.html" title="twisted.internet.interfaces.IReactorTCP">IReactorTCP</a></code>,
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorSSL.html" title="twisted.internet.interfaces.IReactorSSL">IReactorSSL</a></code>,
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUNIX.html" title="twisted.internet.interfaces.IReactorUNIX">IReactorUNIX</a></code>,
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUDP.html" title="twisted.internet.interfaces.IReactorUDP">IReactorUDP</a></code>,
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTime.html" title="twisted.internet.interfaces.IReactorTime">IReactorTime</a></code>,
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorProcess.html" title="twisted.internet.interfaces.IReactorProcess">IReactorProcess</a></code>,
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorMulticast.html" title="twisted.internet.interfaces.IReactorMulticast">IReactorMulticast</a></code>
+and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorThreads.html" title="twisted.internet.interfaces.IReactorThreads">IReactorThreads</a></code>.
+The reactor APIs allow non-persistent calls to be made.</p>
+
+<p>Twisted Internet also covers the interfaces for the various transports,
+in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.ITransport.html" title="twisted.internet.interfaces.ITransport">ITransport</a></code>
+and friends. These interfaces allow Twisted network code to be written without
+regard to the underlying implementation of the transport.</p>
+
+<p>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IProtocolFactory.html" title="twisted.internet.interfaces.IProtocolFactory">IProtocolFactory</a></code>
+dictates how factories, which are usually a large part of third party code, are
+written.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/__init__.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/__init__.py
new file mode 100644
index 0000000000..ed6bd97a9c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/__init__.py
@@ -0,0 +1,3 @@
+"""
+Twisted Quotes
+"""
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/pbquote.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/pbquote.py
new file mode 100644
index 0000000000..d0330e6763
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/pbquote.py
@@ -0,0 +1,10 @@
+from twisted.spread import pb
+
+class QuoteReader(pb.Root):
+
+ def __init__(self, quoter):
+ self.quoter = quoter
+
+ def remote_nextQuote(self):
+ return self.quoter.getQuote()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py
new file mode 100644
index 0000000000..c2975399e1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py
@@ -0,0 +1,32 @@
+
+from sys import stdout
+from twisted.python import log
+log.discardLogs()
+from twisted.internet import reactor
+from twisted.spread import pb
+
+def connected(root):
+ root.callRemote('nextQuote').addCallbacks(success, failure)
+
+def success(quote):
+ stdout.write(quote + "\n")
+ reactor.stop()
+
+def failure(error):
+ stdout.write("Failed to obtain quote.\n")
+ reactor.stop()
+
+factory = pb.PBClientFactory()
+reactor.connectTCP(
+ "localhost", # host name
+ pb.portno, # port number
+ factory, # factory
+ )
+
+
+
+factory.getRootObject().addCallbacks(connected, # when we get the root
+ failure) # when we can't
+
+reactor.run() # start the main loop
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quoteproto.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quoteproto.py
new file mode 100644
index 0000000000..b8d346922b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quoteproto.py
@@ -0,0 +1,36 @@
+from zope.interface import Interface
+
+from twisted.internet.protocol import Factory, Protocol
+
+
+
+class IQuoter(Interface):
+ """
+ An object that returns quotes.
+ """
+ def getQuote():
+ """
+ Return a quote.
+ """
+
+
+
+class QOTD(Protocol):
+ def connectionMade(self):
+ self.transport.write(self.factory.quoter.getQuote()+'\r\n')
+ self.transport.loseConnection()
+
+
+
+class QOTDFactory(Factory):
+ """
+ A factory for the Quote of the Day protocol.
+
+ @type quoter: L{IQuoter} provider
+ @ivar quoter: An object which provides L{IQuoter} which will be used by
+ the L{QOTD} protocol to get quotes to emit.
+ """
+ protocol = QOTD
+
+ def __init__(self, quoter):
+ self.quoter = quoter
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quoters.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quoters.py
new file mode 100644
index 0000000000..f6d5689049
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quoters.py
@@ -0,0 +1,39 @@
+from random import choice
+
+from zope.interface import implements
+
+from TwistedQuotes import quoteproto
+
+
+
+class StaticQuoter:
+ """
+ Return a static quote.
+ """
+
+ implements(quoteproto.IQuoter)
+
+ def __init__(self, quote):
+ self.quote = quote
+
+
+ def getQuote(self):
+ return self.quote
+
+
+
+class FortuneQuoter:
+ """
+ Load quotes from a fortune-format file.
+ """
+ implements(quoteproto.IQuoter)
+
+ def __init__(self, filenames):
+ self.filenames = filenames
+
+
+ def getQuote(self):
+ quoteFile = file(choice(self.filenames))
+ quotes = quoteFile.read().split('\n%\n')
+ quoteFile.close()
+ return choice(quotes)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotes.txt b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotes.txt
new file mode 100644
index 0000000000..62a5ed9995
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotes.txt
@@ -0,0 +1,15 @@
+
+<radix> the sysadmin of the future is going to know twisted-shelling like the back of his hand
+%
+<Acapnotic> Ooh, I just figured out what my first twisted.reality creation will be.
+<dash> Acapnotic: oh?
+<Acapnotic> "Being Glyph Lefkowitz"
+%
+<johs> Oh, please. Threads ownz j00.
+%
+<jafo> I used to hang out with this chick that ran a BBS.
+<jafo> She had a great baud.
+%
+<chrchr> dsmith: Twisted is neat, but unfortunately, it's not object-oriented.
+%
+<datazone> twisted is madness
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotetap.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotetap.py
new file mode 100644
index 0000000000..06d15ec871
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotetap.py
@@ -0,0 +1,29 @@
+from twisted.application import internet # services that run TCP/SSL/etc.
+from TwistedQuotes import quoteproto # Protocol and Factory
+from TwistedQuotes import quoters # "give me a quote" code
+
+from twisted.python import usage # twisted command-line processing
+
+
+class Options(usage.Options):
+ optParameters = [["port", "p", 8007,
+ "Port number to listen on for QOTD protocol."],
+ ["static", "s", "An apple a day keeps the doctor away.",
+ "A static quote to display."],
+ ["file", "f", None,
+ "A fortune-format text file to read quotes from."]]
+
+
+def makeService(config):
+ """Return a service that will be attached to the application."""
+ if config["file"]: # If I was given a "file" option...
+ # Read quotes from a file, selecting a random one each time,
+ quoter = quoters.FortuneQuoter([config['file']])
+ else: # otherwise,
+ # read a single quote from the command line (or use the default).
+ quoter = quoters.StaticQuoter(config['static'])
+ port = int(config["port"]) # TCP port to listen on
+ factory = quoteproto.QOTDFactory(quoter) # here we create a QOTDFactory
+ # Finally, set up our factory, with its custom quoter, to create QOTD
+ # protocol instances when events arrive on the specified port.
+ return internet.TCPServer(port, factory)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotetap2.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotetap2.py
new file mode 100644
index 0000000000..4bc0f06b77
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/quotetap2.py
@@ -0,0 +1,36 @@
+from TwistedQuotes import quoteproto # Protocol and Factory
+from TwistedQuotes import quoters # "give me a quote" code
+from TwistedQuotes import pbquote # perspective broker binding
+
+from twisted.application import service, internet
+from twisted.python import usage # twisted command-line processing
+from twisted.spread import pb # Perspective Broker
+
+class Options(usage.Options):
+ optParameters = [["port", "p", 8007,
+ "Port number to listen on for QOTD protocol."],
+ ["static", "s", "An apple a day keeps the doctor away.",
+ "A static quote to display."],
+ ["file", "f", None,
+ "A fortune-format text file to read quotes from."],
+ ["pb", "b", None,
+ "Port to listen with PB server"]]
+
+def makeService(config):
+ svc = service.MultiService()
+ if config["file"]: # If I was given a "file" option...
+ # Read quotes from a file, selecting a random one each time,
+ quoter = quoters.FortuneQuoter([config['file']])
+ else: # otherwise,
+ # read a single quote from the command line (or use the default).
+ quoter = quoters.StaticQuoter(config['static'])
+ port = int(config["port"]) # TCP port to listen on
+ factory = quoteproto.QOTDFactory(quoter) # here we create a QOTDFactory
+ # Finally, set up our factory, with its custom quoter, to create QOTD
+ # protocol instances when events arrive on the specified port.
+ pbport = config['pb'] # TCP PB port to listen on
+ if pbport:
+ pbfact = pb.PBServerFactory(pbquote.QuoteReader(quoter))
+ svc.addService(internet.TCPServer(int(pbport), pbfact))
+ svc.addService(internet.TCPServer(port, factory))
+ return svc
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/webquote.rpy b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/webquote.rpy
new file mode 100644
index 0000000000..99e0e9cbe6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/TwistedQuotes/webquote.rpy
@@ -0,0 +1,12 @@
+# -*- Python -*-
+
+from TwistedQuotes import webquoteresource
+
+#__file__ is defined to be the name of this file; this is to
+#get the sibling file "quotes.txt" which should be in the same directory
+import os
+quotefile = os.path.join(os.path.split(__file__)[0], "quotes.txt")
+
+#ResourceScript requires us to define 'resource'.
+#This resource is used to render the page.
+resource = webquoteresource.QuoteResource([quotefile])
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/application/service.tac b/vendor/Twisted-10.0.0/doc/core/howto/listings/application/service.tac
new file mode 100644
index 0000000000..b0167fa866
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/application/service.tac
@@ -0,0 +1,34 @@
+# You can run this .tac file directly with:
+# twistd -ny service.tac
+
+"""
+This is an example .tac file which starts a webserver on port 8080 and
+serves files from the current working directory.
+
+The important part of this, the part that makes it a .tac file, is
+the final root-level section, which sets up the object called 'application'
+which twistd will look for
+"""
+
+import os
+from twisted.application import service, internet
+from twisted.web import static, server
+
+def getWebService():
+ """
+ Return a service suitable for creating an application object.
+
+ This service is a simple web server that serves files on port 8080 from
+ underneath the current working directory.
+ """
+ # create a resource to serve static files
+ fileServer = server.Site(static.File(os.getcwd()))
+ return internet.TCPServer(8080, fileServer)
+
+# this is the core part of any tac file, the creation of the root-level
+# application object
+application = service.Application("Demo application")
+
+# attach the service to its parent application
+service = getWebService()
+service.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex.py
new file mode 100644
index 0000000000..3aeae3a42c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.python import failure, util
+
+"""
+Here we have the simplest case, a single callback and a single errback.
+"""
+
+num = 0
+
+def handleFailure(f):
+ print "errback"
+ print "we got an exception: %s" % (f.getTraceback(),)
+ f.trap(RuntimeError)
+
+def handleResult(result):
+ global num; num += 1
+ print "callback %s" % (num,)
+ print "\tgot result: %s" % (result,)
+ return "yay! handleResult was successful!"
+
+
+def behindTheScenes(result):
+ # equivalent to d.callback(result)
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = handleResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ pass
+ else: # ---- errback
+ try:
+ result = handleFailure(result)
+ except:
+ result = failure.Failure()
+
+
+def deferredExample():
+ d = defer.Deferred()
+ d.addCallback(handleResult)
+ d.addErrback(handleFailure)
+
+ d.callback("success")
+
+
+if __name__ == '__main__':
+ behindTheScenes("success")
+ print "\n-------------------------------------------------\n"
+ global num; num = 0
+ deferredExample()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex1a.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex1a.py
new file mode 100755
index 0000000000..737cc4fb50
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex1a.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.python import failure, util
+
+"""
+This example is analogous to a function calling .errback(failure)
+"""
+
+
+class Counter(object):
+ num = 0
+
+def handleFailure(f):
+ print "errback"
+ print "we got an exception: %s" % (f.getTraceback(),)
+ f.trap(RuntimeError)
+
+def handleResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ return "yay! handleResult was successful!"
+
+def failAtHandlingResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ print "\tabout to raise exception"
+ raise RuntimeError, "whoops! we encountered an error"
+
+
+def behindTheScenes(result):
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = handleResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ pass
+ else: # ---- errback
+ try:
+ result = handleFailure(result)
+ except:
+ result = failure.Failure()
+
+
+def deferredExample(result):
+ d = defer.Deferred()
+ d.addCallback(handleResult)
+ d.addCallback(failAtHandlingResult)
+ d.addErrback(handleFailure)
+
+ d.errback(result)
+
+
+if __name__ == '__main__':
+ result = None
+ try:
+ raise RuntimeError, "*doh*! failure!"
+ except:
+ result = failure.Failure()
+ behindTheScenes(result)
+ print "\n-------------------------------------------------\n"
+ Counter.num = 0
+ deferredExample(result)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex1b.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex1b.py
new file mode 100755
index 0000000000..3243821c7f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex1b.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.python import failure, util
+
+"""
+Here we have a slightly more involved case. The deferred is called back with a
+result. the first callback returns a value, the second callback, however
+raises an exception, which is handled by the errback.
+"""
+
+
+class Counter(object):
+ num = 0
+
+def handleFailure(f):
+ print "errback"
+ print "we got an exception: %s" % (f.getTraceback(),)
+ f.trap(RuntimeError)
+
+def handleResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ return "yay! handleResult was successful!"
+
+def failAtHandlingResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ print "\tabout to raise exception"
+ raise RuntimeError, "whoops! we encountered an error"
+
+
+def behindTheScenes(result):
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = handleResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = failAtHandlingResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ pass
+ else: # ---- errback
+ try:
+ result = handleFailure(result)
+ except:
+ result = failure.Failure()
+
+
+def deferredExample():
+ d = defer.Deferred()
+ d.addCallback(handleResult)
+ d.addCallback(failAtHandlingResult)
+ d.addErrback(handleFailure)
+
+ d.callback("success")
+
+
+if __name__ == '__main__':
+ behindTheScenes("success")
+ print "\n-------------------------------------------------\n"
+ Counter.num = 0
+ deferredExample()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex2.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex2.py
new file mode 100755
index 0000000000..21f83634a0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex2.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.python import failure, util
+
+"""
+This example shows an important concept that many deferred newbies
+(myself included) have trouble understanding.
+
+when an error occurs in a callback, the first errback after the error
+occurs will be the next method called. (in the next example we'll
+see what happens in the 'chain' after an errback).
+"""
+
+class Counter(object):
+ num = 0
+
+def handleFailure(f):
+ print "errback"
+ print "we got an exception: %s" % (f.getTraceback(),)
+ f.trap(RuntimeError)
+
+def handleResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ return "yay! handleResult was successful!"
+
+def failAtHandlingResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ print "\tabout to raise exception"
+ raise RuntimeError, "whoops! we encountered an error"
+
+
+
+def behindTheScenes(result):
+ # equivalent to d.callback(result)
+
+ # now, let's make the error happen in the first callback
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = failAtHandlingResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ # note: this callback will be skipped because
+ # result is a failure
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = handleResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ pass
+ else: # ---- errback
+ try:
+ result = handleFailure(result)
+ except:
+ result = failure.Failure()
+
+
+
+def deferredExample():
+ d = defer.Deferred()
+ d.addCallback(failAtHandlingResult)
+ d.addCallback(handleResult)
+ d.addErrback(handleFailure)
+
+ d.callback("success")
+
+
+if __name__ == '__main__':
+ behindTheScenes("success")
+ print "\n-------------------------------------------------\n"
+ Counter.num = 0
+ deferredExample()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex3.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex3.py
new file mode 100755
index 0000000000..b71e43ab1c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex3.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.python import failure, util
+
+"""
+Now we see how an errback can handle errors. if an errback
+does not raise an exception, the next callback in the chain
+will be called.
+"""
+
+class Counter(object):
+ num = 0
+
+
+def handleFailure(f):
+ print "errback"
+ print "we got an exception: %s" % (f.getTraceback(),)
+ f.trap(RuntimeError)
+ return "okay, continue on"
+
+def handleResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ return "yay! handleResult was successful!"
+
+def failAtHandlingResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ print "\tabout to raise exception"
+ raise RuntimeError, "whoops! we encountered an error"
+
+def callbackAfterErrback(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+
+
+
+def behindTheScenes(result):
+ # equivalent to d.callback(result)
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = handleResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = failAtHandlingResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ pass
+ else: # ---- errback
+ try:
+ result = handleFailure(result)
+ except:
+ result = failure.Failure()
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = callbackAfterErrback(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+
+def deferredExample():
+ d = defer.Deferred()
+ d.addCallback(handleResult)
+ d.addCallback(failAtHandlingResult)
+ d.addErrback(handleFailure)
+ d.addCallback(callbackAfterErrback)
+
+ d.callback("success")
+
+
+if __name__ == '__main__':
+ behindTheScenes("success")
+ print "\n-------------------------------------------------\n"
+ Counter.num = 0
+ deferredExample()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex4.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex4.py
new file mode 100755
index 0000000000..cb005c7e6e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex4.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.python import failure, util
+
+"""
+Now we'll see what happens when you use 'addBoth'.
+"""
+
+class Counter(object):
+ num = 0
+
+
+def handleFailure(f):
+ print "errback"
+ print "we got an exception: %s" % (f.getTraceback(),)
+ f.trap(RuntimeError)
+ return "okay, continue on"
+
+def handleResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ return "yay! handleResult was successful!"
+
+def failAtHandlingResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ print "\tabout to raise exception"
+ raise RuntimeError, "whoops! we encountered an error"
+
+def doThisNoMatterWhat(arg):
+ Counter.num += 1
+ print "both %s" % (Counter.num,)
+ print "\tgot argument %r" % (arg,)
+ print "\tdoing something very important"
+ # we pass the argument we received to the next phase here
+ return arg
+
+
+
+def behindTheScenes(result):
+ # equivalent to d.callback(result)
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = handleResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = failAtHandlingResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ # ---- this is equivalent to addBoth(doThisNoMatterWhat)
+
+ if not isinstance(result, failure.Failure):
+ try:
+ result = doThisNoMatterWhat(result)
+ except:
+ result = failure.Failure()
+ else:
+ try:
+ result = doThisNoMatterWhat(result)
+ except:
+ result = failure.Failure()
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ pass
+ else: # ---- errback
+ try:
+ result = handleFailure(result)
+ except:
+ result = failure.Failure()
+
+
+def deferredExample():
+ d = defer.Deferred()
+ d.addCallback(handleResult)
+ d.addCallback(failAtHandlingResult)
+ d.addBoth(doThisNoMatterWhat)
+ d.addErrback(handleFailure)
+
+ d.callback("success")
+
+
+if __name__ == '__main__':
+ behindTheScenes("success")
+ print "\n-------------------------------------------------\n"
+ Counter.num = 0
+ deferredExample()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex5.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex5.py
new file mode 100755
index 0000000000..08d453ee74
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex5.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.python import failure, util
+
+"""
+Now comes the more nuanced addCallbacks, which allows us to make a
+yes/no (branching) decision based on whether the result at a given point is
+a failure or not.
+"""
+
+class Counter(object):
+ num = 0
+
+
+def handleFailure(f):
+ print "errback"
+ print "we got an exception: %s" % (f.getTraceback(),)
+ f.trap(RuntimeError)
+ return "okay, continue on"
+
+def handleResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ return "yay! handleResult was successful!"
+
+def failAtHandlingResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ print "\tabout to raise exception"
+ raise RuntimeError, "whoops! we encountered an error"
+
+def yesDecision(result):
+ Counter.num += 1
+ print "yes decision %s" % (Counter.num,)
+ print "\twasn't a failure, so we can plow ahead"
+ return "go ahead!"
+
+def noDecision(result):
+ Counter.num += 1
+ result.trap(RuntimeError)
+ print "no decision %s" % (Counter.num,)
+ print "\t*doh*! a failure! quick! damage control!"
+ return "damage control successful!"
+
+
+
+def behindTheScenes(result):
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = failAtHandlingResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ # this is equivalent to addCallbacks(yesDecision, noDecision)
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = yesDecision(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ try:
+ result = noDecision(result)
+ except:
+ result = failure.Failure()
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = handleResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ # this is equivalent to addCallbacks(yesDecision, noDecision)
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = yesDecision(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ try:
+ result = noDecision(result)
+ except:
+ result = failure.Failure()
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = handleResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ pass
+ else: # ---- errback
+ try:
+ result = handleFailure(result)
+ except:
+ result = failure.Failure()
+
+
+def deferredExample():
+ d = defer.Deferred()
+ d.addCallback(failAtHandlingResult)
+ d.addCallbacks(yesDecision, noDecision) # noDecision will be called
+ d.addCallback(handleResult) # - A -
+ d.addCallbacks(yesDecision, noDecision) # yesDecision will be called
+ d.addCallback(handleResult)
+ d.addErrback(handleFailure)
+
+ d.callback("success")
+
+
+if __name__ == '__main__':
+ behindTheScenes("success")
+ print "\n-------------------------------------------------\n"
+ Counter.num = 0
+ deferredExample()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex6.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex6.py
new file mode 100755
index 0000000000..cc2996d7a8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex6.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.python import failure, util
+
+"""
+Now comes the more nuanced addCallbacks, which allows us to make a
+yes/no (branching) decision based on whether the result at a given point is
+a failure or not.
+
+here, we return the failure from noDecisionPassthru, the errback argument to
+the first addCallbacks method invocation, and see what happens.
+"""
+
+class Counter(object):
+ num = 0
+
+
+def handleFailure(f):
+ print "errback"
+ print "we got an exception: %s" % (f.getTraceback(),)
+ f.trap(RuntimeError)
+ return "okay, continue on"
+
+def handleResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ return "yay! handleResult was successful!"
+
+def failAtHandlingResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ print "\tabout to raise exception"
+ raise RuntimeError, "whoops! we encountered an error"
+
+def yesDecision(result):
+ Counter.num += 1
+ print "yes decision %s" % (Counter.num,)
+ print "\twasn't a failure, so we can plow ahead"
+ return "go ahead!"
+
+def noDecision(result):
+ Counter.num += 1
+ result.trap(RuntimeError)
+ print "no decision %s" % (Counter.num,)
+ print "\t*doh*! a failure! quick! damage control!"
+ return "damage control successful!"
+
+def noDecisionPassthru(result):
+ Counter.num += 1
+ print "no decision %s" % (Counter.num,)
+ print "\t*doh*! a failure! don't know what to do, returning failure!"
+ return result
+
+
+def behindTheScenes(result):
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = failAtHandlingResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ # this is equivalent to addCallbacks(yesDecision, noDecision)
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = yesDecision(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ try:
+ result = noDecisionPassthru(result)
+ except:
+ result = failure.Failure()
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = handleResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ # this is equivalent to addCallbacks(yesDecision, noDecision)
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = yesDecision(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ try:
+ result = noDecision(result)
+ except:
+ result = failure.Failure()
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ try:
+ result = handleResult(result)
+ except:
+ result = failure.Failure()
+ else: # ---- errback
+ pass
+
+
+ if not isinstance(result, failure.Failure): # ---- callback
+ pass
+ else: # ---- errback
+ try:
+ result = handleFailure(result)
+ except:
+ result = failure.Failure()
+
+
+def deferredExample():
+ d = defer.Deferred()
+ d.addCallback(failAtHandlingResult)
+
+ # noDecisionPassthru will be called
+ d.addCallbacks(yesDecision, noDecisionPassthru)
+ d.addCallback(handleResult) # - A -
+
+ # noDecision will be called
+ d.addCallbacks(yesDecision, noDecision)
+ d.addCallback(handleResult) # - B -
+ d.addErrback(handleFailure)
+
+ d.callback("success")
+
+
+if __name__ == '__main__':
+ behindTheScenes("success")
+ print "\n-------------------------------------------------\n"
+ Counter.num = 0
+ deferredExample()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex7.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex7.py
new file mode 100755
index 0000000000..f3cb02734e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex7.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.python import failure, util
+
+"""
+The deferred callback chain is stateful, and can be executed before
+or after all callbacks have been added to the chain
+"""
+
+class Counter(object):
+ num = 0
+
+def handleFailure(f):
+ print "errback"
+ print "we got an exception: %s" % (f.getTraceback(),)
+ f.trap(RuntimeError)
+
+def handleResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ return "yay! handleResult was successful!"
+
+def failAtHandlingResult(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ print "\tabout to raise exception"
+ raise RuntimeError, "whoops! we encountered an error"
+
+def deferredExample1():
+ # this is another common idiom, since all add* methods
+ # return the deferred instance, you can just chain your
+ # calls to addCallback and addErrback
+
+ d = defer.Deferred().addCallback(failAtHandlingResult
+ ).addCallback(handleResult
+ ).addErrback(handleFailure)
+
+ d.callback("success")
+
+def deferredExample2():
+ d = defer.Deferred()
+
+ d.callback("success")
+
+ d.addCallback(failAtHandlingResult)
+ d.addCallback(handleResult)
+ d.addErrback(handleFailure)
+
+
+if __name__ == '__main__':
+ deferredExample1()
+ print "\n-------------------------------------------------\n"
+ Counter.num = 0
+ deferredExample2()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex8.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex8.py
new file mode 100755
index 0000000000..6c8ae17f59
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/deferred_ex8.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.python import failure, util
+
+
+class Counter(object):
+ num = 0
+ let = 'a'
+
+ def incrLet(cls):
+ cls.let = chr(ord(cls.let) + 1)
+ incrLet = classmethod(incrLet)
+
+
+def handleFailure(f):
+ print "errback"
+ print "we got an exception: %s" % (f.getTraceback(),)
+ return f
+
+def subCb_B(result):
+ print "sub-callback %s" % (Counter.let,)
+ Counter.incrLet()
+ s = " beautiful!"
+ print "\tadding %r to result" % (s,)
+ result += s
+ return result
+
+def subCb_A(result):
+ print "sub-callback %s" % (Counter.let,)
+ Counter.incrLet()
+ s = " are "
+ print "\tadding %r to result" % (s,)
+ result += s
+ return result
+
+def mainCb_1(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+ result += " Deferreds "
+
+ d = defer.Deferred().addCallback(subCb_A
+ ).addCallback(subCb_B)
+ d.callback(result)
+ return d
+
+def mainCb_2(result):
+ Counter.num += 1
+ print "callback %s" % (Counter.num,)
+ print "\tgot result: %s" % (result,)
+
+
+def deferredExample():
+ d = defer.Deferred().addCallback(mainCb_1
+ ).addCallback(mainCb_2)
+
+ d.callback("I hope you'll agree: ")
+
+
+if __name__ == '__main__':
+ deferredExample()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/synch-validation.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/synch-validation.py
new file mode 100644
index 0000000000..2912f2b185
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/deferred/synch-validation.py
@@ -0,0 +1,5 @@
+def synchronousIsValidUser(user):
+ '''
+ Return true if user is a valid user, false otherwise
+ '''
+ return user in ["Alice", "Angus", "Agnes"]
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_classes.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_classes.py
new file mode 100755
index 0000000000..354df9df46
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_classes.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+
+class MasterDuckPond(pb.Cacheable):
+ def __init__(self, ducks):
+ self.observers = []
+ self.ducks = ducks
+ def count(self):
+ print "I have [%d] ducks" % len(self.ducks)
+ def addDuck(self, duck):
+ self.ducks.append(duck)
+ for o in self.observers: o.callRemote('addDuck', duck)
+ def removeDuck(self, duck):
+ self.ducks.remove(duck)
+ for o in self.observers: o.callRemote('removeDuck', duck)
+ def getStateToCacheAndObserveFor(self, perspective, observer):
+ self.observers.append(observer)
+ # you should ignore pb.Cacheable-specific state, like self.observers
+ return self.ducks # in this case, just a list of ducks
+ def stoppedObserving(self, perspective, observer):
+ self.observers.remove(observer)
+
+class SlaveDuckPond(pb.RemoteCache):
+ # This is a cache of a remote MasterDuckPond
+ def count(self):
+ return len(self.cacheducks)
+ def getDucks(self):
+ return self.cacheducks
+ def setCopyableState(self, state):
+ print " cache - sitting, er, setting ducks"
+ self.cacheducks = state
+ def observe_addDuck(self, newDuck):
+ print " cache - addDuck"
+ self.cacheducks.append(newDuck)
+ def observe_removeDuck(self, deadDuck):
+ print " cache - removeDuck"
+ self.cacheducks.remove(deadDuck)
+
+pb.setUnjellyableForClass(MasterDuckPond, SlaveDuckPond)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_receiver.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_receiver.py
new file mode 100755
index 0000000000..2487c3cc88
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_receiver.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application import service, internet
+from twisted.internet import reactor
+from twisted.spread import pb
+import cache_classes
+
+class Receiver(pb.Root):
+ def remote_takePond(self, pond):
+ self.pond = pond
+ print "got pond:", pond # a DuckPondCache
+ self.remote_checkDucks()
+ def remote_checkDucks(self):
+ print "[%d] ducks: " % self.pond.count(), self.pond.getDucks()
+ def remote_ignorePond(self):
+ # stop watching the pond
+ print "dropping pond"
+ # gc causes __del__ causes 'decache' msg causes stoppedObserving
+ self.pond = None
+ def remote_shutdown(self):
+ reactor.stop()
+
+application = service.Application("copy_receiver")
+internet.TCPServer(8800, pb.PBServerFactory(Receiver())).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_sender.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_sender.py
new file mode 100755
index 0000000000..dea96e5dc0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/cache_sender.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+from cache_classes import MasterDuckPond
+
+class Sender:
+ def __init__(self, pond):
+ self.pond = pond
+
+ def phase1(self, remote):
+ self.remote = remote
+ d = remote.callRemote("takePond", self.pond)
+ d.addCallback(self.phase2).addErrback(log.err)
+ def phase2(self, response):
+ self.pond.addDuck("ugly duckling")
+ self.pond.count()
+ reactor.callLater(1, self.phase3)
+ def phase3(self):
+ d = self.remote.callRemote("checkDucks")
+ d.addCallback(self.phase4).addErrback(log.err)
+ def phase4(self, dummy):
+ self.pond.removeDuck("one duck")
+ self.pond.count()
+ self.remote.callRemote("checkDucks")
+ d = self.remote.callRemote("ignorePond")
+ d.addCallback(self.phase5)
+ def phase5(self, dummy):
+ d = self.remote.callRemote("shutdown")
+ d.addCallback(self.phase6)
+ def phase6(self, dummy):
+ reactor.stop()
+
+def main():
+ master = MasterDuckPond(["one duck", "two duck"])
+ master.count()
+
+ sender = Sender(master)
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ deferred = factory.getRootObject()
+ deferred.addCallback(sender.phase1)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/chatclient.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/chatclient.py
new file mode 100755
index 0000000000..d3e00d5507
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/chatclient.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+class Client(pb.Referenceable):
+
+ def remote_print(self, message):
+ print message
+
+ def connect(self):
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("alice", "1234"),
+ client=self)
+ def1.addCallback(self.connected)
+ reactor.run()
+
+ def connected(self, perspective):
+ print "connected, joining group #lookingForFourth"
+ # this perspective is a reference to our User object
+ d = perspective.callRemote("joinGroup", "#lookingForFourth")
+ d.addCallback(self.gotGroup)
+
+ def gotGroup(self, group):
+ print "joined group, now sending a message to all members"
+ # 'group' is a reference to the Group object (through a ViewPoint)
+ d = group.callRemote("send", "You can call me Al.")
+ d.addCallback(self.shutdown)
+
+ def shutdown(self, result):
+ reactor.stop()
+
+
+Client().connect()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/chatserver.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/chatserver.py
new file mode 100755
index 0000000000..7be4364ec7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/chatserver.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import implements
+
+from twisted.cred import portal, checkers
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class ChatServer:
+ def __init__(self):
+ self.groups = {} # indexed by name
+
+ def joinGroup(self, groupname, user, allowMattress):
+ if not self.groups.has_key(groupname):
+ self.groups[groupname] = Group(groupname, allowMattress)
+ self.groups[groupname].addUser(user)
+ return self.groups[groupname]
+
+class ChatRealm:
+ implements(portal.IRealm)
+ def requestAvatar(self, avatarID, mind, *interfaces):
+ assert pb.IPerspective in interfaces
+ avatar = User(avatarID)
+ avatar.server = self.server
+ avatar.attached(mind)
+ return pb.IPerspective, avatar, lambda a=avatar:a.detached(mind)
+
+class User(pb.Avatar):
+ def __init__(self, name):
+ self.name = name
+ def attached(self, mind):
+ self.remote = mind
+ def detached(self, mind):
+ self.remote = None
+ def perspective_joinGroup(self, groupname, allowMattress=True):
+ return self.server.joinGroup(groupname, self, allowMattress)
+ def send(self, message):
+ self.remote.callRemote("print", message)
+
+class Group(pb.Viewable):
+ def __init__(self, groupname, allowMattress):
+ self.name = groupname
+ self.allowMattress = allowMattress
+ self.users = []
+ def addUser(self, user):
+ self.users.append(user)
+ def view_send(self, from_user, message):
+ if not self.allowMattress and message.find("mattress") != -1:
+ raise ValueError, "Don't say that word"
+ for user in self.users:
+ user.send("<%s> says: %s" % (from_user.name, message))
+
+realm = ChatRealm()
+realm.server = ChatServer()
+checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+checker.addUser("alice", "1234")
+checker.addUser("bob", "secret")
+checker.addUser("carol", "fido")
+p = portal.Portal(realm, [checker])
+
+reactor.listenTCP(8800, pb.PBServerFactory(p))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_classes.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_classes.py
new file mode 100755
index 0000000000..60138c0a08
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_classes.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+
+class FrogPond:
+ def __init__(self, numFrogs, numToads):
+ self.numFrogs = numFrogs
+ self.numToads = numToads
+ def count(self):
+ return self.numFrogs + self.numToads
+
+class SenderPond(FrogPond, pb.Copyable):
+ def getStateToCopy(self):
+ d = self.__dict__.copy()
+ d['frogsAndToads'] = d['numFrogs'] + d['numToads']
+ del d['numFrogs']
+ del d['numToads']
+ return d
+
+class ReceiverPond(pb.RemoteCopy):
+ def setCopyableState(self, state):
+ self.__dict__ = state
+ def count(self):
+ return self.frogsAndToads
+
+pb.setUnjellyableForClass(SenderPond, ReceiverPond)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_receiver.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_receiver.py
new file mode 100755
index 0000000000..166801f720
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_receiver.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application import service, internet
+from twisted.internet import reactor
+from twisted.spread import pb
+import copy2_classes # needed to get ReceiverPond registered with Jelly
+
+class Receiver(pb.Root):
+ def remote_takePond(self, pond):
+ print " got pond:", pond
+ print " count %d" % pond.count()
+ return "safe and sound" # positive acknowledgement
+ def remote_shutdown(self):
+ reactor.stop()
+
+application = service.Application("copy_receiver")
+internet.TCPServer(8800, pb.PBServerFactory(Receiver())).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_sender.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_sender.py
new file mode 100755
index 0000000000..bc374d09a7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy2_sender.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+from copy2_classes import SenderPond
+
+class Sender:
+ def __init__(self, pond):
+ self.pond = pond
+
+ def got_obj(self, obj):
+ d = obj.callRemote("takePond", self.pond)
+ d.addCallback(self.ok).addErrback(self.notOk)
+
+ def ok(self, response):
+ print "pond arrived", response
+ reactor.stop()
+ def notOk(self, failure):
+ print "error during takePond:"
+ if failure.type == jelly.InsecureJelly:
+ print " InsecureJelly"
+ else:
+ print failure
+ reactor.stop()
+ return None
+
+def main():
+ pond = SenderPond(3, 4)
+ print "count %d" % pond.count()
+
+ sender = Sender(pond)
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ deferred = factory.getRootObject()
+ deferred.addCallback(sender.got_obj)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy_receiver.tac b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy_receiver.tac
new file mode 100755
index 0000000000..79aaf5bbf6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy_receiver.tac
@@ -0,0 +1,41 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+PB copy receiver example.
+
+This is a Twisted Application Configuration (tac) file. Run with e.g.
+ twistd -ny copy_receiver.tac
+
+See the twistd(1) man page or
+http://twistedmatrix.com/documents/current/howto/application for details.
+"""
+
+import sys
+if __name__ == '__main__':
+ print __doc__
+ sys.exit(1)
+
+from twisted.application import service, internet
+from twisted.internet import reactor
+from twisted.spread import pb
+from copy_sender import LilyPond, CopyPond
+
+from twisted.python import log
+#log.startLogging(sys.stdout)
+
+class ReceiverPond(pb.RemoteCopy, LilyPond):
+ pass
+pb.setUnjellyableForClass(CopyPond, ReceiverPond)
+
+class Receiver(pb.Root):
+ def remote_takePond(self, pond):
+ print " got pond:", pond
+ pond.countFrogs()
+ return "safe and sound" # positive acknowledgement
+ def remote_shutdown(self):
+ reactor.stop()
+
+application = service.Application("copy_receiver")
+internet.TCPServer(8800, pb.PBServerFactory(Receiver())).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy_sender.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy_sender.py
new file mode 100755
index 0000000000..1636dbb596
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/copy_sender.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+
+class LilyPond:
+ def setStuff(self, color, numFrogs):
+ self.color = color
+ self.numFrogs = numFrogs
+ def countFrogs(self):
+ print "%d frogs" % self.numFrogs
+
+class CopyPond(LilyPond, pb.Copyable):
+ pass
+
+class Sender:
+ def __init__(self, pond):
+ self.pond = pond
+
+ def got_obj(self, remote):
+ self.remote = remote
+ d = remote.callRemote("takePond", self.pond)
+ d.addCallback(self.ok).addErrback(self.notOk)
+
+ def ok(self, response):
+ print "pond arrived", response
+ reactor.stop()
+ def notOk(self, failure):
+ print "error during takePond:"
+ if failure.type == jelly.InsecureJelly:
+ print " InsecureJelly"
+ else:
+ print failure
+ reactor.stop()
+ return None
+
+def main():
+ from copy_sender import CopyPond # so it's not __main__.CopyPond
+ pond = CopyPond()
+ pond.setStuff("green", 7)
+ pond.countFrogs()
+ # class name:
+ print ".".join([pond.__class__.__module__, pond.__class__.__name__])
+
+ sender = Sender(pond)
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ deferred = factory.getRootObject()
+ deferred.addCallback(sender.got_obj)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/exc_client.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/exc_client.py
new file mode 100755
index 0000000000..6ec3da46bd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/exc_client.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ d = factory.getRootObject()
+ d.addCallbacks(got_obj)
+ reactor.run()
+
+def got_obj(obj):
+ # change "broken" into "broken2" to demonstrate an unhandled exception
+ d2 = obj.callRemote("broken")
+ d2.addCallback(working)
+ d2.addErrback(broken)
+
+def working():
+ print "erm, it wasn't *supposed* to work.."
+
+def broken(reason):
+ print "got remote Exception"
+ # reason should be a Failure (or subclass) holding the MyError exception
+ print " .__class__ =", reason.__class__
+ print " .getErrorMessage() =", reason.getErrorMessage()
+ print " .type =", reason.type
+ reactor.stop()
+
+main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/exc_server.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/exc_server.py
new file mode 100755
index 0000000000..1afe83e6da
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/exc_server.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class MyError(pb.Error):
+ """This is an Expected Exception. Something bad happened."""
+ pass
+
+class MyError2(Exception):
+ """This is an Unexpected Exception. Something really bad happened."""
+ pass
+
+class One(pb.Root):
+ def remote_broken(self):
+ msg = "fall down go boom"
+ print "raising a MyError exception with data '%s'" % msg
+ raise MyError(msg)
+ def remote_broken2(self):
+ msg = "hadda owie"
+ print "raising a MyError2 exception with data '%s'" % msg
+ raise MyError2(msg)
+
+def main():
+ reactor.listenTCP(8800, pb.PBServerFactory(One()))
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb1client.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb1client.py
new file mode 100755
index 0000000000..2cb842faa5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb1client.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.getRootObject()
+ def1.addCallbacks(got_obj1, err_obj1)
+ reactor.run()
+
+def err_obj1(reason):
+ print "error getting first object", reason
+ reactor.stop()
+
+def got_obj1(obj1):
+ print "got first object:", obj1
+ print "asking it to getTwo"
+ def2 = obj1.callRemote("getTwo")
+ def2.addCallbacks(got_obj2)
+
+def got_obj2(obj2):
+ print "got second object:", obj2
+ print "telling it to do three(12)"
+ obj2.callRemote("three", 12)
+
+main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb1server.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb1server.py
new file mode 100755
index 0000000000..1efa60abd5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb1server.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+
+class Two(pb.Referenceable):
+ def remote_three(self, arg):
+ print "Two.three was given", arg
+
+class One(pb.Root):
+ def remote_getTwo(self):
+ two = Two()
+ print "returning a Two called", two
+ return two
+
+from twisted.internet import reactor
+reactor.listenTCP(8800, pb.PBServerFactory(One()))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb2client.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb2client.py
new file mode 100755
index 0000000000..632f42cf1a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb2client.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ foo = Foo()
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ factory.getRootObject().addCallback(foo.step1)
+ reactor.run()
+
+# keeping globals around is starting to get ugly, so we use a simple class
+# instead. Instead of hooking one function to the next, we hook one method
+# to the next.
+
+class Foo:
+ def __init__(self):
+ self.oneRef = None
+
+ def step1(self, obj):
+ print "got one object:", obj
+ self.oneRef = obj
+ print "asking it to getTwo"
+ self.oneRef.callRemote("getTwo").addCallback(self.step2)
+
+ def step2(self, two):
+ print "got two object:", two
+ print "giving it back to one"
+ print "one is", self.oneRef
+ self.oneRef.callRemote("checkTwo", two)
+
+main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb2server.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb2server.py
new file mode 100755
index 0000000000..85fcc79e2b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb2server.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Two(pb.Referenceable):
+ def remote_print(self, arg):
+ print "two.print was given", arg
+
+class One(pb.Root):
+ def __init__(self, two):
+ #pb.Root.__init__(self) # pb.Root doesn't implement __init__
+ self.two = two
+ def remote_getTwo(self):
+ print "One.getTwo(), returning my two called", two
+ return two
+ def remote_checkTwo(self, newtwo):
+ print "One.checkTwo(): comparing my two", self.two
+ print "One.checkTwo(): against your two", newtwo
+ if two == newtwo:
+ print "One.checkTwo(): our twos are the same"
+
+
+two = Two()
+root_obj = One(two)
+reactor.listenTCP(8800, pb.PBServerFactory(root_obj))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb3client.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb3client.py
new file mode 100755
index 0000000000..9c6f7f350b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb3client.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Two(pb.Referenceable):
+ def remote_print(self, arg):
+ print "Two.print() called with", arg
+
+def main():
+ two = Two()
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.getRootObject()
+ def1.addCallback(got_obj, two) # hands our 'two' to the callback
+ reactor.run()
+
+def got_obj(obj, two):
+ print "got One:", obj
+ print "giving it our two"
+ obj.callRemote("takeTwo", two)
+
+main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb3server.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb3server.py
new file mode 100755
index 0000000000..001d3320f3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb3server.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class One(pb.Root):
+ def remote_takeTwo(self, two):
+ print "received a Two called", two
+ print "telling it to print(12)"
+ two.callRemote("print", 12)
+
+reactor.listenTCP(8800, pb.PBServerFactory(One()))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb4client.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb4client.py
new file mode 100755
index 0000000000..0354fb9faf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb4client.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ rootobj_def = pb.getObjectAt("localhost", 8800, 30)
+ rootobj_def.addCallbacks(got_rootobj)
+ obj2_def = getSomeObjectAt("localhost", 8800, 30, "two")
+ obj2_def.addCallbacks(got_obj2)
+ obj3_def = getSomeObjectAt("localhost", 8800, 30, "three")
+ obj3_def.addCallbacks(got_obj3)
+ reactor.run()
+
+def got_rootobj(rootobj):
+ print "got root object:", rootobj
+ print "telling root object to do foo(A)"
+ rootobj.callRemote("foo", "A")
+
+def got_obj2(obj2):
+ print "got second object:", obj2
+ print "telling second object to do foo(B)"
+ obj2.callRemote("foo", "B")
+
+def got_obj3(obj3):
+ print "got third object:", obj3
+ print "telling third object to do foo(C)"
+ obj3.callRemote("foo", "C")
+
+class my_ObjectRetrieval(pb._ObjectRetrieval):
+ def __init__(self, broker, d, objname):
+ pb._ObjectRetrieval.__init__(self, broker, d)
+ self.objname = objname
+ def connectionMade(self):
+ assert not self.term, "How did this get called?"
+ x = self.broker.remoteForName(self.objname)
+ del self.broker
+ self.term = 1
+ self.deferred.callback(x)
+
+def getSomeObjectAt(host, port, timeout=None, objname="root"):
+ from twisted.internet import defer
+ from twisted.spread.pb import Broker, BrokerClientFactory
+ d = defer.Deferred()
+ b = Broker(1)
+ bf = BrokerClientFactory(b)
+ my_ObjectRetrieval(b, d, objname)
+ if host == "unix":
+ # every time you use this, God kills a kitten
+ reactor.connectUNIX(port, bf, timeout)
+ else:
+ reactor.connectTCP(host, port, bf, timeout)
+ return d
+
+main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb5client.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb5client.py
new file mode 100755
index 0000000000..fac671c32b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb5client.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("user1", "pass1"))
+ def1.addCallback(connected)
+ reactor.run()
+
+def connected(perspective):
+ print "got perspective ref:", perspective
+ print "asking it to foo(12)"
+ perspective.callRemote("foo", 12)
+
+main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb5server.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb5server.py
new file mode 100755
index 0000000000..d3fc3b5397
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb5server.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.cred import checkers, portal
+from twisted.internet import reactor
+
+class MyPerspective(pb.Avatar):
+ def __init__(self, name):
+ self.name = name
+ def perspective_foo(self, arg):
+ print "I am", self.name, "perspective_foo(",arg,") called on", self
+
+class MyRealm:
+ implements(portal.IRealm)
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective not in interfaces:
+ raise NotImplementedError
+ return pb.IPerspective, MyPerspective(avatarId), lambda:None
+
+p = portal.Portal(MyRealm())
+p.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(user1="pass1"))
+reactor.listenTCP(8800, pb.PBServerFactory(p))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6client1.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6client1.py
new file mode 100755
index 0000000000..eed4a98764
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6client1.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("user1", "pass1"))
+ def1.addCallback(connected)
+ reactor.run()
+
+def connected(perspective):
+ print "got perspective1 ref:", perspective
+ print "asking it to foo(13)"
+ perspective.callRemote("foo", 13)
+
+main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6client2.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6client2.py
new file mode 100755
index 0000000000..02e61cd3d7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6client2.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("user2", "pass2"))
+ def1.addCallback(connected)
+ reactor.run()
+
+def connected(perspective):
+ print "got perspective2 ref:", perspective
+ print "asking it to foo(14)"
+ perspective.callRemote("foo", 14)
+
+main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6server.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6server.py
new file mode 100755
index 0000000000..375ec199d1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb6server.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.cred import checkers, portal
+from twisted.internet import reactor
+
+class MyPerspective(pb.Avatar):
+ def __init__(self, name):
+ self.name = name
+ def perspective_foo(self, arg):
+ print "I am", self.name, "perspective_foo(",arg,") called on", self
+
+class MyRealm:
+ implements(portal.IRealm)
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective not in interfaces:
+ raise NotImplementedError
+ return pb.IPerspective, MyPerspective(avatarId), lambda:None
+
+p = portal.Portal(MyRealm())
+c = checkers.InMemoryUsernamePasswordDatabaseDontUse(user1="pass1",
+ user2="pass2")
+p.registerChecker(c)
+reactor.listenTCP(8800, pb.PBServerFactory(p))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb7client.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb7client.py
new file mode 100755
index 0000000000..8b2823fa9e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pb7client.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def one(port, user, pw, service, perspective, number):
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", port, factory)
+ def1 = factory.getPerspective(
+ user, pw, service, perspective)
+ def1.addCallback(connected, number)
+
+def connected(perspective, number):
+ print "got perspective ref:", perspective
+ print "asking it to foo(%d)" % number
+ perspective.callRemote("foo", number)
+
+def main():
+ one(8800, "user1", "pass1", "service1", "perspective1.1", 10)
+ one(8800, "user1", "pass1", "service2", "perspective2.1", 11)
+ one(8800, "user2", "pass2", "service1", "perspective1.2", 12)
+ one(8800, "user2", "pass2", "service2", "perspective2.2", 13)
+ one(8801, "user3", "pass3", "service3", "perspective3.3", 14)
+ reactor.run()
+
+main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pbAnonClient.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pbAnonClient.py
new file mode 100755
index 0000000000..a9f5b52a6e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pbAnonClient.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Client which will talk to the server run by pbAnonServer.py, logging in
+either anonymously or with username/password credentials.
+"""
+
+from sys import stdout
+
+from twisted.python.log import err, startLogging
+from twisted.cred.credentials import Anonymous, UsernamePassword
+from twisted.internet import reactor
+from twisted.internet.defer import gatherResults
+from twisted.spread.pb import PBClientFactory
+
+
+def error(why, msg):
+ """
+ Catch-all errback which simply logs the failure. This isn't expected to
+ be invoked in the normal case for this example.
+ """
+ err(why, msg)
+
+
+def connected(perspective):
+ """
+ Login callback which invokes the remote "foo" method on the perspective
+ which the server returned.
+ """
+ print "got perspective1 ref:", perspective
+ print "asking it to foo(13)"
+ return perspective.callRemote("foo", 13)
+
+
+def finished(ignored):
+ """
+ Callback invoked when both logins and method calls have finished to shut
+ down the reactor so the example exits.
+ """
+ reactor.stop()
+
+
+def main():
+ """
+ Connect to a PB server running on port 8800 on localhost and log in to
+ it, both anonymously and using a username/password it will recognize.
+ """
+ startLogging(stdout)
+ factory = PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+
+ anonymousLogin = factory.login(Anonymous())
+ anonymousLogin.addCallback(connected)
+ anonymousLogin.addErrback(error, "Anonymous login failed")
+
+ usernameLogin = factory.login(UsernamePassword("user1", "pass1"))
+ usernameLogin.addCallback(connected)
+ usernameLogin.addErrback(error, "Username/password login failed")
+
+ bothDeferreds = gatherResults([anonymousLogin, usernameLogin])
+ bothDeferreds.addCallback(finished)
+
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pbAnonServer.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pbAnonServer.py
new file mode 100755
index 0000000000..dcdae23d21
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/pbAnonServer.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implement the realm for and run on port 8800 a PB service which allows both
+anonymous and username/password based access.
+
+Successful username/password-based login requests given an instance of
+MyPerspective with a name which matches the username with which they
+authenticated. Success anonymous login requests are given an instance of
+MyPerspective with the name "Anonymous".
+"""
+
+from sys import stdout
+
+from zope.interface import implements
+
+from twisted.python.log import startLogging
+from twisted.cred.checkers import ANONYMOUS, AllowAnonymousAccess
+from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+from twisted.cred.portal import IRealm, Portal
+from twisted.internet import reactor
+from twisted.spread.pb import Avatar, IPerspective, PBServerFactory
+
+
+class MyPerspective(Avatar):
+ """
+ Trivial avatar exposing a single remote method for demonstrative
+ purposes. All successful login attempts in this example will result in
+ an avatar which is an instance of this class.
+
+ @type name: C{str}
+ @ivar name: The username which was used during login or C{"Anonymous"}
+ if the login was anonymous (a real service might want to avoid the
+ collision this introduces between anonoymous users and authenticated
+ users named "Anonymous").
+ """
+ def __init__(self, name):
+ self.name = name
+
+
+ def perspective_foo(self, arg):
+ """
+ Print a simple message which gives the argument this method was
+ called with and this avatar's name.
+ """
+ print "I am %s. perspective_foo(%s) called on %s." % (
+ self.name, arg, self)
+
+
+
+class MyRealm(object):
+ """
+ Trivial realm which supports anonymous and named users by creating
+ avatars which are instances of MyPerspective for either.
+ """
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if IPerspective not in interfaces:
+ raise NotImplementedError("MyRealm only handles IPerspective")
+ if avatarId is ANONYMOUS:
+ avatarId = "Anonymous"
+ return IPerspective, MyPerspective(avatarId), lambda: None
+
+
+
+def main():
+ """
+ Create a PB server using MyRealm and run it on port 8800.
+ """
+ startLogging(stdout)
+
+ p = Portal(MyRealm())
+
+ # Here the username/password checker is registered.
+ c1 = InMemoryUsernamePasswordDatabaseDontUse(user1="pass1", user2="pass2")
+ p.registerChecker(c1)
+
+ # Here the anonymous checker is registered.
+ c2 = AllowAnonymousAccess()
+ p.registerChecker(c2)
+
+ reactor.listenTCP(8800, PBServerFactory(p))
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/trap_client.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/trap_client.py
new file mode 100755
index 0000000000..6edb094bb4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/trap_client.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+
+class MyException(pb.Error): pass
+class MyOtherException(pb.Error): pass
+
+class ScaryObject:
+ # not safe for serialization
+ pass
+
+def worksLike(obj):
+ # the callback/errback sequence in class One works just like an
+ # asynchronous version of the following:
+ try:
+ response = obj.callMethod(name, arg)
+ except pb.DeadReferenceError:
+ print " stale reference: the client disconnected or crashed"
+ except jelly.InsecureJelly:
+ print " InsecureJelly: you tried to send something unsafe to them"
+ except (MyException, MyOtherException):
+ print " remote raised a MyException" # or MyOtherException
+ except:
+ print " something else happened"
+ else:
+ print " method successful, response:", response
+
+class One:
+ def worked(self, response):
+ print " method successful, response:", response
+ def check_InsecureJelly(self, failure):
+ failure.trap(jelly.InsecureJelly)
+ print " InsecureJelly: you tried to send something unsafe to them"
+ return None
+ def check_MyException(self, failure):
+ which = failure.trap(MyException, MyOtherException)
+ if which == MyException:
+ print " remote raised a MyException"
+ else:
+ print " remote raised a MyOtherException"
+ return None
+ def catch_everythingElse(self, failure):
+ print " something else happened"
+ log.err(failure)
+ return None
+
+ def doCall(self, explanation, arg):
+ print explanation
+ try:
+ deferred = self.remote.callRemote("fooMethod", arg)
+ deferred.addCallback(self.worked)
+ deferred.addErrback(self.check_InsecureJelly)
+ deferred.addErrback(self.check_MyException)
+ deferred.addErrback(self.catch_everythingElse)
+ except pb.DeadReferenceError:
+ print " stale reference: the client disconnected or crashed"
+
+ def callOne(self):
+ self.doCall("callOne: call with safe object", "safe string")
+ def callTwo(self):
+ self.doCall("callTwo: call with dangerous object", ScaryObject())
+ def callThree(self):
+ self.doCall("callThree: call that raises remote exception", "panic!")
+ def callShutdown(self):
+ print "telling them to shut down"
+ self.remote.callRemote("shutdown")
+ def callFour(self):
+ self.doCall("callFour: call on stale reference", "dummy")
+
+ def got_obj(self, obj):
+ self.remote = obj
+ reactor.callLater(1, self.callOne)
+ reactor.callLater(2, self.callTwo)
+ reactor.callLater(3, self.callThree)
+ reactor.callLater(4, self.callShutdown)
+ reactor.callLater(5, self.callFour)
+ reactor.callLater(6, reactor.stop)
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("localhost", 8800, factory)
+deferred = factory.getRootObject()
+deferred.addCallback(One().got_obj)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/trap_server.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/trap_server.py
new file mode 100755
index 0000000000..ef705811f1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/pb/trap_server.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import reactor
+from twisted.spread import pb
+
+class MyException(pb.Error):
+ pass
+
+class One(pb.Root):
+ def remote_fooMethod(self, arg):
+ if arg == "panic!":
+ raise MyException
+ return "response"
+ def remote_shutdown(self):
+ reactor.stop()
+
+reactor.listenTCP(8800, pb.PBServerFactory(One()))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/process/process.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/process/process.py
new file mode 100755
index 0000000000..60a5b29dcb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/process/process.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import protocol
+from twisted.internet import reactor
+import re
+
+class MyPP(protocol.ProcessProtocol):
+ def __init__(self, verses):
+ self.verses = verses
+ self.data = ""
+ def connectionMade(self):
+ print "connectionMade!"
+ for i in range(self.verses):
+ self.transport.write("Aleph-null bottles of beer on the wall,\n" +
+ "Aleph-null bottles of beer,\n" +
+ "Take one down and pass it around,\n" +
+ "Aleph-null bottles of beer on the wall.\n")
+ self.transport.closeStdin() # tell them we're done
+ def outReceived(self, data):
+ print "outReceived! with %d bytes!" % len(data)
+ self.data = self.data + data
+ def errReceived(self, data):
+ print "errReceived! with %d bytes!" % len(data)
+ def inConnectionLost(self):
+ print "inConnectionLost! stdin is closed! (we probably did it)"
+ def outConnectionLost(self):
+ print "outConnectionLost! The child closed their stdout!"
+ # now is the time to examine what they wrote
+ #print "I saw them write:", self.data
+ (dummy, lines, words, chars, file) = re.split(r'\s+', self.data)
+ print "I saw %s lines" % lines
+ def errConnectionLost(self):
+ print "errConnectionLost! The child closed their stderr."
+ def processExited(self, reason):
+ print "processExited, status %d" % (reason.value.exitCode,)
+ def processEnded(self, reason):
+ print "processEnded, status %d" % (reason.value.exitCode,)
+ print "quitting"
+ reactor.stop()
+
+pp = MyPP(10)
+reactor.spawnProcess(pp, "wc", ["wc"], {})
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/process/quotes.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/process/quotes.py
new file mode 100644
index 0000000000..c0efeafaf9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/process/quotes.py
@@ -0,0 +1,25 @@
+from twisted.internet import protocol, utils, reactor
+from twisted.python import failure
+from cStringIO import StringIO
+
+class FortuneQuoter(protocol.Protocol):
+
+ fortune = '/usr/games/fortune'
+
+ def connectionMade(self):
+ output = utils.getProcessOutput(self.fortune)
+ output.addCallbacks(self.writeResponse, self.noResponse)
+
+ def writeResponse(self, resp):
+ self.transport.write(resp)
+ self.transport.loseConnection()
+
+ def noResponse(self, err):
+ self.transport.loseConnection()
+
+
+if __name__ == '__main__':
+ f = protocol.Factory()
+ f.protocol = FortuneQuoter
+ reactor.listenTCP(10999, f)
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/process/trueandfalse.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/process/trueandfalse.py
new file mode 100644
index 0000000000..4962c93dba
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/process/trueandfalse.py
@@ -0,0 +1,14 @@
+from twisted.internet import utils, reactor
+
+def printTrueValue(val):
+ print "/bin/true exits with rc=%d" % val
+ output = utils.getProcessValue('/bin/false')
+ output.addCallback(printFalseValue)
+
+def printFalseValue(val):
+ print "/bin/false exits with rc=%d" % val
+ reactor.stop()
+
+output = utils.getProcessValue('/bin/true')
+output.addCallback(printTrueValue)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/udp/MulticastClient.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/udp/MulticastClient.py
new file mode 100644
index 0000000000..ec48ac0cc5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/udp/MulticastClient.py
@@ -0,0 +1,13 @@
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+from twisted.application.internet import MulticastServer
+
+class MulticastClientUDP(DatagramProtocol):
+
+ def datagramReceived(self, datagram, address):
+ print "Received:" + repr(datagram)
+
+# Send multicast on 224.0.0.1:8005, on our dynamically allocated port
+reactor.listenUDP(0, MulticastClientUDP()).write('UniqueID',
+ ('224.0.0.1', 8005))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/listings/udp/MulticastServer.py b/vendor/Twisted-10.0.0/doc/core/howto/listings/udp/MulticastServer.py
new file mode 100644
index 0000000000..9e70bdd327
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/listings/udp/MulticastServer.py
@@ -0,0 +1,25 @@
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+from twisted.application.internet import MulticastServer
+
+class MulticastServerUDP(DatagramProtocol):
+ def startProtocol(self):
+ print 'Started Listening'
+ # Join a specific multicast group, which is the IP we will respond to
+ self.transport.joinGroup('224.0.0.1')
+
+ def datagramReceived(self, datagram, address):
+ # The uniqueID check is to ensure we only service requests from
+ # ourselves
+ if datagram == 'UniqueID':
+ print "Server Received:" + repr(datagram)
+ self.transport.write("data", address)
+
+# Note that the join function is picky about having a unique object
+# on which to call join. To avoid using startProtocol, the following is
+# sufficient:
+#reactor.listenMulticast(8005, MulticastServerUDP()).join('224.0.0.1')
+
+# Listen for multicast on 224.0.0.1:8005
+reactor.listenMulticast(8005, MulticastServerUDP())
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/logging.html b/vendor/Twisted-10.0.0/doc/core/howto/logging.html
new file mode 100644
index 0000000000..e524d1a924
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/logging.html
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Logging with twisted.python.log</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Logging with twisted.python.log</h1>
+ <div class="toc"><ol><li><a href="#auto0">Basic usage</a></li><ul><li><a href="#auto1">Logging and twistd</a></li><li><a href="#auto2">Log files</a></li><li><a href="#auto3">Using the Python logging module</a></li></ul><li><a href="#auto4">Writing log observers</a></li><li><a href="#auto5">Customizing twistd logging</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Basic usage<a name="auto0"/></h2>
+
+ <p>Twisted provides a simple and flexible logging system in the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.html" title="twisted.python.log">twisted.python.log</a></code> module. It has three commonly used
+ functions:</p>
+
+ <dl>
+ <dt><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.msg.html" title="twisted.python.log.msg">msg</a></code></dt>
+ <dd>Logs a new message. For example:
+ <pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-variable">log</span>.<span class="py-src-variable">msg</span>(<span class="py-src-string">'Hello, world.'</span>)
+</pre>
+ </dd>
+
+ <dt><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.err.html" title="twisted.python.log.err">err</a></code></dt>
+ <dd>Writes a failure to the log, including traceback information (if any).
+ You can pass it a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code> or Exception instance, or
+ nothing. If you pass something else, it will be converted to a string
+ with <code>repr</code> and logged.
+
+ If you pass nothing, it will construct a Failure from the
+ currently active exception, which makes it convenient to use in an <code class="python">except</code> clause:
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">x</span> = <span class="py-src-number">1</span> / <span class="py-src-number">0</span>
+<span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>() <span class="py-src-comment"># will log the ZeroDivisionError</span>
+</pre>
+ </dd>
+
+ <dt><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.startLogging.html" title="twisted.python.log.startLogging">startLogging</a></code></dt>
+ <dd>Starts logging to a given file-like object. For example:
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">log</span>.<span class="py-src-variable">startLogging</span>(<span class="py-src-variable">open</span>(<span class="py-src-string">'/var/log/foo.log'</span>, <span class="py-src-string">'w'</span>))
+</pre>
+ or:
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">log</span>.<span class="py-src-variable">startLogging</span>(<span class="py-src-variable">sys</span>.<span class="py-src-variable">stdout</span>)
+</pre>
+
+ By default, <code>startLogging</code> will also redirect anything written
+ to <code>sys.stdout</code> and <code>sys.stderr</code> to the log. You
+ can disable this by passing <code class="python">setStdout=False</code> to
+ <code>startLogging</code>.
+ </dd>
+ </dl>
+
+ <p>Before <code>startLogging</code> is called, log messages will be
+ discarded and errors will be written to stderr.</p>
+
+ <h3>Logging and twistd<a name="auto1"/></h3>
+
+ <p>If you are using <code class="shell">twistd</code> to run your daemon, it
+ will take care of calling <code>startLogging</code> for you, and will also
+ rotate log files. See <a href="application.html#twistd" shape="rect">twistd and tac</a>
+ and the <code class="shell">twistd</code> man page for details of using
+ twistd.</p>
+
+ <h3>Log files<a name="auto2"/></h3>
+
+ <p>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.logfile.html" title="twisted.python.logfile">twisted.python.logfile</a></code> module provides
+ some standard classes suitable for use with <code>startLogging</code>, such
+ as <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.logfile.DailyLogFile.html" title="twisted.python.logfile.DailyLogFile">DailyLogFile</a></code>,
+ which will rotate the log to a new file once per day.</p>
+
+ <h3>Using the Python logging module<a name="auto3"/></h3>
+
+ <p>If your application uses the logging module or you want to use its ease
+ of configuration but don't want to lose twisted-produced messages,
+ the observer
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.PythonLoggingObserver.html" title="twisted.python.log.PythonLoggingObserver">PythonLoggingObserver</a></code>
+ should be useful to you</p>
+
+ <p>You just start it like any other observers:
+ <pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">observer</span> = <span class="py-src-variable">log</span>.<span class="py-src-variable">PythonLoggingObserver</span>()
+<span class="py-src-variable">observer</span>.<span class="py-src-variable">start</span>()
+</pre>
+ And then you'll just have to configure logging to do what you want:
+ <a href="http://docs.python.org/lib/module-logging.html" shape="rect">
+ logging documentation</a>.
+ </p>
+
+ <p>This method allows you to customize the log level received by the
+ logging module using the <code>logLevel</code> keyword:
+ <pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">log</span>.<span class="py-src-variable">msg</span>(<span class="py-src-string">&quot;This is important!&quot;</span>, <span class="py-src-variable">logLevel</span>=<span class="py-src-variable">logging</span>.<span class="py-src-variable">CRITICAL</span>)
+<span class="py-src-variable">log</span>.<span class="py-src-variable">msg</span>(<span class="py-src-string">&quot;Don't mind&quot;</span>, <span class="py-src-variable">logLevel</span>=<span class="py-src-variable">logging</span>.<span class="py-src-variable">DEBUG</span>)
+</pre>
+ Unless logLevel is provided, logging.INFO is used for <code>log.msg</code>
+ and logging.ERROR is used for <code>log.err</code>.
+ </p>
+
+ <p>One special care should be made when you use special configuration of
+ the python logging module: some handlers (e.g. SMTP, HTTP) uses network
+ so can block inside the reactor loop. <em>Nothing</em> in the bridge is
+ done to prevent that.</p>
+
+ <h2>Writing log observers<a name="auto4"/></h2>
+
+ <p>Log observers are the basis of the Twisted logging system. An example of
+ a log observer in Twisted is the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.FileLogObserver.html" title="twisted.python.log.FileLogObserver">FileLogObserver</a></code> used by
+ <code>startLogging</code> that writes events to a log file. A log observer
+ is just a callable that accepts a dictionary as its only argument. You can
+ then register it to receive all log events (in addition to any other
+ observers):</p>
+
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span>.<span class="py-src-variable">addObserver</span>(<span class="py-src-variable">yourCallable</span>)
+</pre>
+
+ <p>The dictionary will have at least two items:</p>
+
+ <dl>
+ <dt>message</dt>
+ <dd>The message (a list, usually of strings)
+ for this log event, as passed to <code>log.msg</code> or the
+ message in the failure passed to <code>log.err</code>.</dd>
+
+ <dt>isError</dt>
+ <dd>This is a boolean that will be true if this event came from a call to
+ <code>log.err</code>. If this is set, there may be a <code>failure</code>
+ item in the dictionary as will, with a Failure object in it.</dd>
+ </dl>
+
+ <p>Other items the built in logging functionality may add include:</p>
+
+ <dl>
+ <dt>printed</dt>
+ <dd>This message was captured from <code>sys.stdout</code>, i.e. this
+ message came from a <code>print</code> statement. If
+ <code>isError</code> is also true, it came from
+ <code>sys.stderr</code>.</dd>
+ </dl>
+
+ <p>You can pass additional items to the event dictionary by passing keyword
+ arguments to <code>log.msg</code> and <code>log.err</code>. The standard
+ log observers will ignore dictionary items they don't use.</p>
+
+ <p>Important notes:</p>
+
+ <ul>
+ <li>Never block in a log observer, as it may run in main Twisted thread.
+ This means you can't use socket or syslog Python-logging backends.</li>
+
+ <li>The observer needs to be thread safe if you anticipate using threads
+ in your program.</li>
+ </ul>
+
+ <h2>Customizing <code>twistd</code> logging<a name="auto5"/></h2>
+ <p>
+ The behavior of the logging that <code>twistd</code> does can be customized
+ by setting the <code>ILogObserver</code> component on the application
+ object. See the <a href="application.html" shape="rect">Application document</a> for
+ more information.
+ </p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/options.html b/vendor/Twisted-10.0.0/doc/core/howto/options.html
new file mode 100644
index 0000000000..e275895b28
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/options.html
@@ -0,0 +1,533 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Parsing command-lines with usage.Options</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Parsing command-lines with usage.Options</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Boolean Options</a></li><ul><li><a href="#auto2">Inheritance, Or: How I Learned to Stop Worrying and Love
+ the Superclass</a></li></ul><li><a href="#auto3">Parameters</a></li><li><a href="#auto4">Option Subcommands</a></li><li><a href="#auto5">Generic Code For Options</a></li><li><a href="#auto6">Parsing Arguments</a></li><li><a href="#auto7">Post Processing</a></li><li><a href="#auto8">Type enforcement</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Introduction<a name="auto0"/></h2>
+
+ <p>There is frequently a need for programs to parse a UNIX-like
+ command line program: options preceded by <code>-</code> or
+ <code>--</code>, sometimes followed by a parameter, followed by
+ a list of arguments. The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.usage.html" title="twisted.python.usage">twisted.python.usage</a></code> provides a class,
+ <code>Options</code>, to facilitate such parsing.</p>
+
+ <p>While Python has the <code>getopt</code> module for doing
+ this, it provides a very low level of abstraction for options.
+ Twisted has a higher level of abstraction, in the class <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.usage.Options.html" title="twisted.python.usage.Options">twisted.python.usage.Options</a></code>. It uses
+ Python's reflection facilities to provide an easy to use yet
+ flexible interface to the command line. While most command line
+ processors either force the application writer to write her own
+ loops, or have arbitrary limitations on the command line (the
+ most common one being not being able to have more then one
+ instance of a specific option, thus rendering the idiom
+ <code class="shell">program -v -v -v</code> impossible), Twisted allows the
+ programmer to decide how much control she wants.</p>
+
+ <p>The <code>Options</code> class is used by subclassing. Since
+ a lot of time it will be used in the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.tap.html" title="twisted.tap">twisted.tap</a></code> package, where the local
+ conventions require the specific options parsing class to also
+ be called <code>Options</code>, it is usually imported with</p>
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+</pre>
+
+ <h2>Boolean Options<a name="auto1"/></h2>
+
+ <p>For simple boolean options, define the attribute
+ <code>optFlags</code> like this:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optFlags</span> = [[<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-string">&quot;Act quickly&quot;</span>], [<span class="py-src-string">&quot;safe&quot;</span>, <span class="py-src-string">&quot;s&quot;</span>, <span class="py-src-string">&quot;Act safely&quot;</span>]]
+</pre>
+ <p><code>optFlags</code> should be a list of 3-lists. The first element
+ is the long name, and will be used on the command line as
+ <code>--fast</code>. The second one is the short name, and will be used
+ on the command line as <code>-f</code>. The last element is a
+ description of the flag and will be used to generate the usage
+ information text. The long name also determines the name of the key
+ that will be set on the Options instance. Its value will be 1 if the
+ option was seen, 0 otherwise. Here is an example for usage:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optFlags</span> = [
+ [<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-string">&quot;Act quickly&quot;</span>],
+ [<span class="py-src-string">&quot;good&quot;</span>, <span class="py-src-string">&quot;g&quot;</span>, <span class="py-src-string">&quot;Act well&quot;</span>],
+ [<span class="py-src-string">&quot;cheap&quot;</span>, <span class="py-src-string">&quot;c&quot;</span>, <span class="py-src-string">&quot;Act cheaply&quot;</span>]
+ ]
+
+<span class="py-src-variable">command_line</span> = [<span class="py-src-string">&quot;-g&quot;</span>, <span class="py-src-string">&quot;--fast&quot;</span>]
+
+<span class="py-src-variable">options</span> = <span class="py-src-variable">Options</span>()
+<span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">options</span>.<span class="py-src-variable">parseOptions</span>(<span class="py-src-variable">command_line</span>)
+<span class="py-src-keyword">except</span> <span class="py-src-variable">usage</span>.<span class="py-src-variable">UsageError</span>, <span class="py-src-variable">errortext</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'%s: %s'</span> % (<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>], <span class="py-src-variable">errortext</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'%s: Try --help for usage details.'</span> % (<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>])
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">exit</span>(<span class="py-src-number">1</span>)
+<span class="py-src-keyword">if</span> <span class="py-src-variable">options</span>[<span class="py-src-string">'fast'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;fast&quot;</span>,
+<span class="py-src-keyword">if</span> <span class="py-src-variable">options</span>[<span class="py-src-string">'good'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;good&quot;</span>,
+<span class="py-src-keyword">if</span> <span class="py-src-variable">options</span>[<span class="py-src-string">'cheap'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;cheap&quot;</span>,
+<span class="py-src-keyword">print</span>
+</pre>
+
+ <p>The above will print <code>fast good</code>.</p>
+
+ <p>Note here that Options fully supports the mapping interface. You can
+ access it mostly just like you can access any other dict. Options are stored
+ as mapping items in the Options instance: parameters as 'paramname': 'value'
+ and flags as 'flagname': 1 or 0.</p>
+
+ <h3>Inheritance, Or: How I Learned to Stop Worrying and Love
+ the Superclass<a name="auto2"/></h3>
+
+ <p>Sometimes there is a need for several option processors with
+ a unifying core. Perhaps you want all your commands to
+ understand <code>-q</code>/<code>--quiet</code> means to be
+ quiet, or something similar. On the face of it, this looks
+ impossible: in Python, the subclass's <code>optFlags</code>
+ would shadow the superclass's. However,
+ <code>usage.Options</code> uses special reflection code to get
+ all of the <code>optFlags</code> defined in the hierarchy. So
+ the following:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">BaseOptions</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optFlags</span> = [[<span class="py-src-string">&quot;quiet&quot;</span>, <span class="py-src-string">&quot;q&quot;</span>, <span class="py-src-variable">None</span>]]
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SpecificOptions</span>(<span class="py-src-parameter">BaseOptions</span>):
+
+ <span class="py-src-variable">optFlags</span> = [
+ [<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-variable">None</span>], [<span class="py-src-string">&quot;good&quot;</span>, <span class="py-src-string">&quot;g&quot;</span>, <span class="py-src-variable">None</span>], [<span class="py-src-string">&quot;cheap&quot;</span>, <span class="py-src-string">&quot;c&quot;</span>, <span class="py-src-variable">None</span>]
+ ]
+</pre>
+ <p>Is the same as: </p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SpecificOptions</span>(<span class="py-src-parameter">BaseOptions</span>):
+
+ <span class="py-src-variable">optFlags</span> = [
+ [<span class="py-src-string">&quot;quiet&quot;</span>, <span class="py-src-string">&quot;q&quot;</span>, <span class="py-src-string">&quot;Silence output&quot;</span>],
+ [<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-string">&quot;Run quickly&quot;</span>],
+ [<span class="py-src-string">&quot;good&quot;</span>, <span class="py-src-string">&quot;g&quot;</span>, <span class="py-src-string">&quot;Don't validate input&quot;</span>],
+ [<span class="py-src-string">&quot;cheap&quot;</span>, <span class="py-src-string">&quot;c&quot;</span>, <span class="py-src-string">&quot;Use cheap resources&quot;</span>]
+ ]
+</pre>
+
+ <h2>Parameters<a name="auto3"/></h2>
+
+ <p>Parameters are specified using the attribute
+ <code>optParameters</code>. They <em>must</em> be given a
+ default. If you want to make sure you got the parameter from
+ the command line, give a non-string default. Since the command
+ line only has strings, this is completely reliable.</p>
+
+ <p>Here is an example:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optFlags</span> = [
+ [<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-string">&quot;Run quickly&quot;</span>],
+ [<span class="py-src-string">&quot;good&quot;</span>, <span class="py-src-string">&quot;g&quot;</span>, <span class="py-src-string">&quot;Don't validate input&quot;</span>],
+ [<span class="py-src-string">&quot;cheap&quot;</span>, <span class="py-src-string">&quot;c&quot;</span>, <span class="py-src-string">&quot;Use cheap resources&quot;</span>]
+ ]
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">&quot;user&quot;</span>, <span class="py-src-string">&quot;u&quot;</span>, <span class="py-src-variable">None</span>, <span class="py-src-string">&quot;The user name&quot;</span>]]
+
+<span class="py-src-variable">config</span> = <span class="py-src-variable">Options</span>()
+<span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">config</span>.<span class="py-src-variable">parseOptions</span>() <span class="py-src-comment"># When given no argument, parses sys.argv[1:]</span>
+<span class="py-src-keyword">except</span> <span class="py-src-variable">usage</span>.<span class="py-src-variable">UsageError</span>, <span class="py-src-variable">errortext</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'%s: %s'</span> % (<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>], <span class="py-src-variable">errortext</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'%s: Try --help for usage details.'</span> % (<span class="py-src-variable">sys</span>.<span class="py-src-variable">argv</span>[<span class="py-src-number">0</span>])
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">exit</span>(<span class="py-src-number">1</span>)
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>[<span class="py-src-string">'user'</span>] <span class="py-src-keyword">is</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">None</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Hello&quot;</span>, <span class="py-src-variable">config</span>[<span class="py-src-string">'user'</span>]
+<span class="py-src-keyword">print</span> <span class="py-src-string">&quot;So, you want it:&quot;</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>[<span class="py-src-string">'fast'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;fast&quot;</span>,
+<span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>[<span class="py-src-string">'good'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;good&quot;</span>,
+<span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>[<span class="py-src-string">'cheap'</span>]:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;cheap&quot;</span>,
+<span class="py-src-keyword">print</span>
+</pre>
+
+ <p>Like <code>optFlags</code>, <code>optParameters</code> works
+ smoothly with inheritance.</p>
+
+ <h2>Option Subcommands<a name="auto4"/></h2>
+
+ <p>It is useful, on occassion, to group a set of options together based
+ on the logical <q>action</q> to which they belong. For this, the
+ <code>usage.Options</code> class allows you to define a set of
+ <q>subcommands</q>, each of which can provide its own
+ <code>usage.Options</code> instance to handle its particular
+ options.</p>
+
+ <p>Here is an example for an Options class that might parse
+ options like those the cvs program takes</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ImportOptions</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">optParameters</span> = [
+ [<span class="py-src-string">'module'</span>, <span class="py-src-string">'m'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>], [<span class="py-src-string">'vendor'</span>, <span class="py-src-string">'v'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>],
+ [<span class="py-src-string">'release'</span>, <span class="py-src-string">'r'</span>, <span class="py-src-variable">None</span>]
+ ]
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CheckoutOptions</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">'module'</span>, <span class="py-src-string">'m'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>], [<span class="py-src-string">'tag'</span>, <span class="py-src-string">'r'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>]]
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">subCommands</span> = [[<span class="py-src-string">'import'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">ImportOptions</span>, <span class="py-src-string">&quot;Do an Import&quot;</span>],
+ [<span class="py-src-string">'checkout'</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">CheckoutOptions</span>, <span class="py-src-string">&quot;Do a Checkout&quot;</span>]]
+
+ <span class="py-src-variable">optParameters</span> = [
+ [<span class="py-src-string">'compression'</span>, <span class="py-src-string">'z'</span>, <span class="py-src-number">0</span>, <span class="py-src-string">'Use compression'</span>],
+ [<span class="py-src-string">'repository'</span>, <span class="py-src-string">'r'</span>, <span class="py-src-variable">None</span>, <span class="py-src-string">'Specify an alternate repository'</span>]
+ ]
+
+<span class="py-src-variable">config</span> = <span class="py-src-variable">Options</span>(); <span class="py-src-variable">config</span>.<span class="py-src-variable">parseOptions</span>()
+<span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">subCommand</span> == <span class="py-src-string">'import'</span>:
+ <span class="py-src-variable">doImport</span>(<span class="py-src-variable">config</span>.<span class="py-src-variable">subOptions</span>)
+<span class="py-src-keyword">elif</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">subCommand</span> == <span class="py-src-string">'checkout'</span>:
+ <span class="py-src-variable">doCheckout</span>(<span class="py-src-variable">config</span>.<span class="py-src-variable">subOptions</span>)
+</pre>
+
+ <p>The <code>subCommands</code> attribute of <code>Options</code>
+ directs the parser to the two other <code>Options</code> subclasses
+ when the strings <code>&quot;import&quot;</code> or <code>&quot;checkout&quot;</code> are
+ present on the command
+ line. All options after the given command string are passed to the
+ specified Options subclass for further parsing. Only one subcommand
+ may be specified at a time. After parsing has completed, the Options
+ instance has two new attributes - <code>subCommand</code> and <code>
+ subOptions</code> - which hold the command string and the Options
+ instance used to parse the remaining options.</p>
+
+ <h2>Generic Code For Options<a name="auto5"/></h2>
+
+ <p>Sometimes, just setting an attribute on the basis of the
+ options is not flexible enough. In those cases, Twisted does
+ not even attempt to provide abstractions such as <q>counts</q> or
+ <q>lists</q>, but rathers lets you call your own method, which will
+ be called whenever the option is encountered.</p>
+
+ <p>Here is an example of counting verbosity</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">usage</span>.<span class="py-src-variable">Options</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'verbosity'</span>] = <span class="py-src-number">0</span> <span class="py-src-comment"># default</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">opt_verbose</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'verbosity'</span>] = <span class="py-src-variable">self</span>[<span class="py-src-string">'verbosity'</span>]+<span class="py-src-number">1</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">opt_quiet</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'verbosity'</span>] = <span class="py-src-variable">self</span>[<span class="py-src-string">'verbosity'</span>]-<span class="py-src-number">1</span>
+
+ <span class="py-src-variable">opt_v</span> = <span class="py-src-variable">opt_verbose</span>
+ <span class="py-src-variable">opt_q</span> = <span class="py-src-variable">opt_quiet</span>
+</pre>
+
+ <p>Command lines that look like
+ <code class="shell">command -v -v -v -v</code> will
+ increase verbosity to 4, while
+ <code class="shell">command -q -q -q</code> will decrease
+ verbosity to -3.
+ </p>
+
+ <p>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.usage.Options.html" title="twisted.python.usage.Options">usage.Options</a></code>
+ class knows that these are
+ parameter-less options, since the methods do not receive an
+ argument. Here is an example for a method with a parameter:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">usage</span>.<span class="py-src-variable">Options</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'symbols'</span>] = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">opt_define</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">symbol</span>):
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'symbols'</span>].<span class="py-src-variable">append</span>(<span class="py-src-variable">symbol</span>)
+
+ <span class="py-src-variable">opt_D</span> = <span class="py-src-variable">opt_define</span>
+</pre>
+
+ <p>This example is useful for the common idiom of having
+ <code>command -DFOO -DBAR</code> to define symbols.</p>
+
+ <h2>Parsing Arguments<a name="auto6"/></h2>
+
+ <p><code>usage.Options</code> does not stop helping when the
+ last parameter is gone. All the other arguments are sent into a
+ function which should deal with them. Here is an example for a
+ <code>cmp</code> like command.</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">&quot;max_differences&quot;</span>, <span class="py-src-string">&quot;d&quot;</span>, <span class="py-src-number">1</span>, <span class="py-src-variable">None</span>]]
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">parseArgs</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">origin</span>, <span class="py-src-parameter">changed</span>):
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'origin'</span>] = <span class="py-src-variable">origin</span>
+ <span class="py-src-variable">self</span>[<span class="py-src-string">'changed'</span>] = <span class="py-src-variable">changed</span>
+</pre>
+
+ <p>The command should look like <code>command origin
+ changed</code>.</p>
+
+ <p>If you want to have a variable number of left-over
+ arguments, just use <code>def parseArgs(self, *args):</code>.
+ This is useful for commands like the UNIX
+ <code>cat(1)</code>.</p>
+
+ <h2>Post Processing<a name="auto7"/></h2>
+
+ <p>Sometimes, you want to perform post processing of options to
+ patch up inconsistencies, and the like. Here is an example:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optFlags</span> = [
+ [<span class="py-src-string">&quot;fast&quot;</span>, <span class="py-src-string">&quot;f&quot;</span>, <span class="py-src-string">&quot;Run quickly&quot;</span>],
+ [<span class="py-src-string">&quot;good&quot;</span>, <span class="py-src-string">&quot;g&quot;</span>, <span class="py-src-string">&quot;Don't validate input&quot;</span>],
+ [<span class="py-src-string">&quot;cheap&quot;</span>, <span class="py-src-string">&quot;c&quot;</span>, <span class="py-src-string">&quot;Use cheap resources&quot;</span>]
+ ]
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">postOptions</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>[<span class="py-src-string">'fast'</span>] <span class="py-src-keyword">and</span> <span class="py-src-variable">self</span>[<span class="py-src-string">'good'</span>] <span class="py-src-keyword">and</span> <span class="py-src-variable">self</span>[<span class="py-src-string">'cheap'</span>]:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">usage</span>.<span class="py-src-variable">UsageError</span>, <span class="py-src-string">&quot;can't have it all, brother&quot;</span>
+</pre>
+
+ <h2>Type enforcement<a name="auto8"/></h2>
+
+ <p>By default, all options are handled as strings. You may want to
+ enforce the type of your option in some specific case, the classic example
+ being port number. Any callable can be specified in the fifth row of
+ <code>optParameters</code> and will be called with the string value passed
+ in parameter.
+ </p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">&quot;shiny_integer&quot;</span>, <span class="py-src-string">&quot;s&quot;</span>, <span class="py-src-number">1</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">int</span>]]
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">&quot;dummy_float&quot;</span>, <span class="py-src-string">&quot;d&quot;</span>, <span class="py-src-number">3.14159</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">float</span>]]
+</pre>
+
+ <p>Note that default values are not coerced, so you should either declare
+ it with the good type (as above) or handle it when you use your
+ options.</p>
+
+ <p>The coerce function may have a coerceDoc attribute, the content of which
+ will be printed after the documentation of the option. It's particularly
+ useful for reusing the function at multiple places.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">oneTwoThree</span>(<span class="py-src-parameter">val</span>):
+ <span class="py-src-variable">val</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">val</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">val</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">range</span>(<span class="py-src-number">1</span>, <span class="py-src-number">4</span>):
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">ValueError</span>(<span class="py-src-string">&quot;Not in range&quot;</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">val</span>
+<span class="py-src-variable">oneTwoThree</span>.<span class="py-src-variable">coerceDoc</span> = <span class="py-src-string">&quot;Must be 1, 2 or 3.&quot;</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">&quot;one_choice&quot;</span>, <span class="py-src-string">&quot;o&quot;</span>, <span class="py-src-number">1</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">oneTwoThree</span>]]
+</pre>
+
+<p>This example code will print the following help when added to your program:
+</p>
+
+<pre class="shell" xml:space="preserve">
+$ <em>python myprogram.py --help</em>
+Usage: myprogram [options]
+Options:
+ -o, --one_choice= [default: 0]. Must be 1, 2 or 3.
+</pre>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/overview.html b/vendor/Twisted-10.0.0/doc/core/howto/overview.html
new file mode 100644
index 0000000000..0aa7bf9417
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/overview.html
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: High-Level Overview of Twisted</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">High-Level Overview of Twisted</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+ <img src="../img/twisted-overview.png"/>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/pb-copyable.html b/vendor/Twisted-10.0.0/doc/core/howto/pb-copyable.html
new file mode 100644
index 0000000000..9df3a3d93c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/pb-copyable.html
@@ -0,0 +1,1195 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: PB Copyable: Passing Complex Types</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">PB Copyable: Passing Complex Types</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Motivation</a></li><li><a href="#auto2">Passing Objects</a></li><ul><li><a href="#auto3">Security Options</a></li><li><a href="#auto4">What class to use?</a></li></ul><li><a href="#auto5">pb.Copyable</a></li><ul><li><a href="#auto6">Controlling the Copied State</a></li><li><a href="#auto7">Things To Watch Out For</a></li><li><a href="#auto8">More Information</a></li></ul><li><a href="#auto9">pb.Cacheable</a></li><ul><li><a href="#auto10">Example</a></li><li><a href="#auto11">More Information</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Overview<a name="auto0"/></h2>
+
+<p>This chapter focuses on how to use PB to pass complex types (specifically
+class instances) to and from a remote process. The first section is on
+simply copying the contents of an object to a remote process (<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>). The second covers how
+to copy those contents once, then update them later when they change (<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">Cacheable</a></code>).</p>
+
+<h2>Motivation<a name="auto1"/></h2>
+
+<p>From the <a href="pb-usage.html" shape="rect">previous chapter</a>, you've seen how to
+pass basic types to a remote process, by using them in the arguments or
+return values of a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.callRemote.html" title="twisted.spread.pb.RemoteReference.callRemote">callRemote</a></code> function. However,
+if you've experimented with it, you may have discovered problems when trying
+to pass anything more complicated than a primitive int/list/dict/string
+type, or another <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code> object. At some point you want
+to pass entire objects between processes, instead of having to reduce them
+down to dictionaries on one end and then re-instantiating them on the
+other.</p>
+
+<h2>Passing Objects<a name="auto2"/></h2>
+
+<p>The most obvious and straightforward way to send an object to a remote
+process is with something like the following code. It also happens that this
+code doesn't work, as will be explained below.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">LilyPond</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">frogs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">frogs</span> = <span class="py-src-variable">frogs</span>
+
+<span class="py-src-variable">pond</span> = <span class="py-src-variable">LilyPond</span>(<span class="py-src-number">12</span>)
+<span class="py-src-variable">ref</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;sendPond&quot;</span>, <span class="py-src-variable">pond</span>)
+</pre>
+
+<p>If you try to run this, you might hope that a suitable remote end which
+implements the <code>remote_sendPond</code> method would see that method get
+invoked with an instance from the <code>LilyPond</code> class. But instead,
+you'll encounter the dreaded <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.jelly.InsecureJelly.html" title="twisted.spread.jelly.InsecureJelly">InsecureJelly</a></code> exception. This is
+Twisted's way of telling you that you've violated a security restriction,
+and that the receiving end refuses to accept your object.</p>
+
+<h3>Security Options<a name="auto3"/></h3>
+
+<p>What's the big deal? What's wrong with just copying a class into another
+process' namespace?</p>
+
+<p>Reversing the question might make it easier to see the issue: what is the
+problem with accepting a stranger's request to create an arbitrary object in
+your local namespace? The real question is how much power you are granting
+them: what actions can they convince you to take on the basis of the bytes
+they are sending you over that remote connection.</p>
+
+<p>Objects generally represent more power than basic types like strings and
+dictionaries because they also contain (or reference) code, which can modify
+other data structures when executed. Once previously-trusted data is
+subverted, the rest of the program is compromised.</p>
+
+<p>The built-in Python <q>batteries included</q> classes are relatively
+tame, but you still wouldn't want to let a foreign program use them to
+create arbitrary objects in your namespace or on your computer. Imagine a
+protocol that involved sending a file-like object with a <code>read()</code>
+method that was supposed to used later to retrieve a document. Then imagine
+what if that object were created with
+<code>os.fdopen(&quot;~/.gnupg/secring.gpg&quot;)</code>. Or an instance of
+<code>telnetlib.Telnet(&quot;localhost&quot;, &quot;chargen&quot;)</code>. </p>
+
+<p>Classes you've written for your own program are likely to have far more
+power. They may run code during <code>__init__</code>, or even have special
+meaning simply because of their existence. A program might have
+<code>User</code> objects to represent user accounts, and have a rule that
+says all <code>User</code> objects in the system are referenced when
+authorizing a login session. (In this system, <code>User.__init__</code>
+would probably add the object to a global list of known users). The simple
+act of creating an object would give access to somebody. If you could be
+tricked into creating a bad object, an unauthorized user would get
+access.</p>
+
+<p>So object creation needs to be part of a system's security design. The
+dotted line between <q>trusted inside</q> and <q>untrusted outside</q> needs
+to describe what may be done in response to outside events. One of those
+events is the receipt of an object through a PB remote procedure call, which
+is a request to create an object in your <q>inside</q> namespace. The
+question is what to do in response to it. For this reason, you must
+explicitly specific what remote classes will be accepted, and how their
+local representatives are to be created.</p>
+
+<h3>What class to use?<a name="auto4"/></h3>
+
+<p>Another basic question to answer before we can do anything useful with an
+incoming serialized object is: what class should we create? The simplistic
+answer is to create the <q>same kind</q> that was serialized on the sender's
+end of the wire, but this is not as easy or as straightforward as you might
+think. Remember that the request is coming from a different program, using a
+potentially different set of class libraries. In fact, since PB has also
+been implemented in Java, Emacs-Lisp, and other languages, there's no
+guarantee that the sender is even running Python! All we know on the
+receiving end is a list of two things which describe the instance they are
+trying to send us: the name of the class, and a representation of the
+contents of the object.</p>
+
+
+<p>PB lets you specify the mapping from remote class names to local classes
+with the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.jelly.setUnjellyableForClass.html" title="twisted.spread.jelly.setUnjellyableForClass">setUnjellyableForClass</a></code> function<a href="#footnote-1" title="Note that, in this context, unjelly is a verb with the opposite meaning of jelly. The verb to jelly means to serialize an object or data structure into a sequence of bytes (or other primitive transmittable/storable representation), while to unjelly means to unserialize the bytestream into a live object in the receiver's memory space. Unjellyable is a noun, (not an adjective), referring to the the class that serves as a destination or recipient of the unjellying process. A is unjellyable into B means that a serialized representation A (of some remote object) can be unserialized into a local object of type B. It is these objects B that are the Unjellyable second argument of the setUnjellyableForClass function. In particular, unjellyable does not mean cannot be jellied. Unpersistable means not persistable, but unjelly, unserialize, and unpickle mean to reverse the operations of jellying, serializing, and pickling."><super>1</super></a>.
+
+
+This function takes a remote/sender class reference (either the
+fully-qualified name as used by the sending end, or a class object from
+which the name can be extracted), and a local/recipient class (used to
+create the local representation for incoming serialized objects). Whenever
+the remote end sends an object, the class name that they transmit is looked
+up in the table controlled by this function. If a matching class is found,
+it is used to create the local object. If not, you get the
+<code>InsecureJelly</code> exception.</p>
+
+<p>In general you expect both ends to share the same codebase: either you
+control the program that is running on both ends of the wire, or both
+programs share some kind of common language that is implemented in code
+which exists on both ends. You wouldn't expect them to send you an object of
+the MyFooziWhatZit class unless you also had a definition for that class. So
+it is reasonable for the Jelly layer to reject all incoming classes except
+the ones that you have explicitly marked with
+<code>setUnjellyableForClass</code>. But keep in mind that the sender's idea
+of a <code>User</code> object might differ from the recipient's, either
+through namespace collisions between unrelated packages, version skew
+between nodes that haven't been updated at the same rate, or a malicious
+intruder trying to cause your code to fail in some interesting or
+potentially vulnerable way.</p>
+
+
+<h2>pb.Copyable<a name="auto5"/></h2>
+
+<p>Ok, enough of this theory. How do you send a fully-fledged object from
+one side to the other?</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LilyPond</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setStuff</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">color</span>, <span class="py-src-parameter">numFrogs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">color</span> = <span class="py-src-variable">color</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span> = <span class="py-src-variable">numFrogs</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">countFrogs</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;%d frogs&quot;</span> % <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CopyPond</span>(<span class="py-src-parameter">LilyPond</span>, <span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Copyable</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Sender</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">remote</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">remote</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;takePond&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">ok</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">notOk</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">ok</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;pond arrived&quot;</span>, <span class="py-src-variable">response</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">notOk</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;error during takePond:&quot;</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">failure</span>.<span class="py-src-variable">type</span> == <span class="py-src-variable">jelly</span>.<span class="py-src-variable">InsecureJelly</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; InsecureJelly&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">failure</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">copy_sender</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">CopyPond</span> <span class="py-src-comment"># so it's not __main__.CopyPond</span>
+ <span class="py-src-variable">pond</span> = <span class="py-src-variable">CopyPond</span>()
+ <span class="py-src-variable">pond</span>.<span class="py-src-variable">setStuff</span>(<span class="py-src-string">&quot;green&quot;</span>, <span class="py-src-number">7</span>)
+ <span class="py-src-variable">pond</span>.<span class="py-src-variable">countFrogs</span>()
+ <span class="py-src-comment"># class name:</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;.&quot;</span>.<span class="py-src-variable">join</span>([<span class="py-src-variable">pond</span>.<span class="py-src-variable">__class__</span>.<span class="py-src-variable">__module__</span>, <span class="py-src-variable">pond</span>.<span class="py-src-variable">__class__</span>.<span class="py-src-variable">__name__</span>])
+
+ <span class="py-src-variable">sender</span> = <span class="py-src-variable">Sender</span>(<span class="py-src-variable">pond</span>)
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">sender</span>.<span class="py-src-variable">got_obj</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/copy_sender.py"><span class="filename">listings/pb/copy_sender.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+</p><span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+PB copy receiver example.
+
+This is a Twisted Application Configuration (tac) file. Run with e.g.
+ twistd -ny copy_receiver.tac
+
+See the twistd(1) man page or
+http://twistedmatrix.com/documents/current/howto/application for details.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">__doc__</span>
+ <span class="py-src-variable">sys</span>.<span class="py-src-variable">exit</span>(<span class="py-src-number">1</span>)
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">copy_sender</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LilyPond</span>, <span class="py-src-variable">CopyPond</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-comment">#log.startLogging(sys.stdout)</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ReceiverPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">RemoteCopy</span>, <span class="py-src-parameter">LilyPond</span>):
+ <span class="py-src-keyword">pass</span>
+<span class="py-src-variable">pb</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">CopyPond</span>, <span class="py-src-variable">ReceiverPond</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Receiver</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takePond</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; got pond:&quot;</span>, <span class="py-src-variable">pond</span>
+ <span class="py-src-variable">pond</span>.<span class="py-src-variable">countFrogs</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;safe and sound&quot;</span> <span class="py-src-comment"># positive acknowledgement</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;copy_receiver&quot;</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Receiver</span>())).<span class="py-src-variable">setServiceParent</span>(
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/pb/copy_receiver.tac"><span class="filename">listings/pb/copy_receiver.tac</span></a></div></div>
+
+<p>The sending side has a class called <code>LilyPond</code>. To make this
+eligble for transport through <code>callRemote</code> (either as an
+argument, a return value, or something referenced by either of those [like a
+dictionary value]), it must inherit from one of the four <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Serializable.html" title="twisted.spread.pb.Serializable">Serializable</a></code> classes. In this section,
+we focus on <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">Copyable</a></code>.
+The copyable subclass of <code>LilyPond</code> is called
+<code>CopyPond</code>. We create an instance of it and send it through
+<code>callRemote</code> as an argument to the receiver's
+<code>remote_takePond</code> method. The Jelly layer will serialize
+(<q>jelly</q>) that object as an instance with a class name of
+<q>copy_sender.CopyPond</q> and some chunk of data that represents the
+object's state. <code>pond.__class__.__module__</code> and
+<code>pond.__class__.__name__</code> are used to derive the class name
+string. The object's <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Copyable.getStateToCopy.html" title="twisted.spread.pb.Copyable.getStateToCopy">getStateToCopy</a></code> method is
+used to get the state: this is provided by <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>, and the default just retrieves
+<code>self.__dict__</code>. This works just like the optional
+<code>__getstate__</code> method used by <code>pickle</code>. The pair of
+name and state are sent over the wire to the receiver.</p>
+
+<p>The receiving end defines a local class named <code>ReceiverPond</code>
+to represent incoming <code>LilyPond</code> instances. This class derives
+from the sender's <code>LilyPond</code> class (with a fully-qualified name
+of <code>copy_sender.LilyPond</code>), which specifies how we expect it to
+behave. We trust that this is the same <code>LilyPond</code> class as the
+sender used. (At the very least, we hope ours will be able to accept a state
+created by theirs). It also inherits from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code>, which is a requirement for all
+classes that act in this local-representative role (those which are given to
+the second argument of <code>setUnjellyableForClass</code>).
+<code>RemoteCopy</code> provides the methods that tell the Jelly layer how
+to create the local object from the incoming serialized state.</p>
+
+<p>Then <code>setUnjellyableForClass</code> is used to register the two
+classes. This has two effects: instances of the remote class (the first
+argument) will be allowed in through the security layer, and instances of
+the local class (the second argument) will be used to contain the state that
+is transmitted when the sender serializes the remote object.</p>
+
+<p>When the receiver unserializes (<q>unjellies</q>) the object, it will
+create an instance of the local <code>ReceiverPond</code> class, and hand
+the transmitted state (usually in the form of a dictionary) to that object's
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCopy.setCopyableState.html" title="twisted.spread.pb.RemoteCopy.setCopyableState">setCopyableState</a></code> method.
+This acts just like the <code>__setstate__</code> method that
+<code>pickle</code> uses when unserializing an object.
+<code>getStateToCopy</code>/<code>setCopyableState</code> are distinct from
+<code>__getstate__</code>/<code>__setstate__</code> to allow objects to be
+persisted (across time) differently than they are transmitted (across
+[memory]space).</p>
+
+<p>When this is run, it produces the following output:</p>
+
+<pre class="shell" xml:space="preserve">
+[-] twisted.spread.pb.PBServerFactory starting on 8800
+[-] Starting factory &lt;twisted.spread.pb.PBServerFactory instance at
+0x406159cc&gt;
+[Broker,0,127.0.0.1] got pond: &lt;__builtin__.ReceiverPond instance at
+0x406ec5ec&gt;
+[Broker,0,127.0.0.1] 7 frogs
+</pre>
+
+<pre class="shell" xml:space="preserve">
+% ./copy_sender.py
+7 frogs
+copy_sender.CopyPond
+pond arrived safe and sound
+Main loop terminated.
+%
+</pre>
+
+
+
+<h3>Controlling the Copied State<a name="auto6"/></h3>
+
+<p>By overriding <code>getStateToCopy</code> and
+<code>setCopyableState</code>, you can control how the object is transmitted
+over the wire. For example, you might want perform some data-reduction:
+pre-compute some results instead of sending all the raw data over the wire.
+Or you could replace references to a local object on the sender's side with
+markers before sending, then upon receipt replace those markers with
+references to a receiver-side proxy that could perform the same operations
+against a local cache of data.</p>
+
+<p>Another good use for <code>getStateToCopy</code> is to implement
+<q>local-only</q> attributes: data that is only accessible by the local
+process, not to any remote users. For example, a <code>.password</code>
+attribute could be removed from the object state before sending to a remote
+system. Combined with the fact that <code>Copyable</code> objects return
+unchanged from a round trip, this could be used to build a
+challenge-response system (in fact PB does this with
+<code>pb.Referenceable</code> objects to implement authorization as
+described <a href="pb-cred.html" shape="rect">here</a>).</p>
+
+<p>Whatever <code>getStateToCopy</code> returns from the sending object will
+be serialized and sent over the wire; <code>setCopyableState</code> gets
+whatever comes over the wire and is responsible for setting up the state of
+the object it lives in.</p>
+
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FrogPond</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">numFrogs</span>, <span class="py-src-parameter">numToads</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span> = <span class="py-src-variable">numFrogs</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">numToads</span> = <span class="py-src-variable">numToads</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span> + <span class="py-src-variable">self</span>.<span class="py-src-variable">numToads</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SenderPond</span>(<span class="py-src-parameter">FrogPond</span>, <span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Copyable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getStateToCopy</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">__dict__</span>.<span class="py-src-variable">copy</span>()
+ <span class="py-src-variable">d</span>[<span class="py-src-string">'frogsAndToads'</span>] = <span class="py-src-variable">d</span>[<span class="py-src-string">'numFrogs'</span>] + <span class="py-src-variable">d</span>[<span class="py-src-string">'numToads'</span>]
+ <span class="py-src-keyword">del</span> <span class="py-src-variable">d</span>[<span class="py-src-string">'numFrogs'</span>]
+ <span class="py-src-keyword">del</span> <span class="py-src-variable">d</span>[<span class="py-src-string">'numToads'</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ReceiverPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">RemoteCopy</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setCopyableState</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">state</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">__dict__</span> = <span class="py-src-variable">state</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">frogsAndToads</span>
+
+<span class="py-src-variable">pb</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">SenderPond</span>, <span class="py-src-variable">ReceiverPond</span>)
+</pre><div class="caption">Source listing - <a href="listings/pb/copy2_classes.py"><span class="filename">listings/pb/copy2_classes.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">copy2_classes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SenderPond</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Sender</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">obj</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">obj</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;takePond&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">ok</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">notOk</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">ok</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;pond arrived&quot;</span>, <span class="py-src-variable">response</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">notOk</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;error during takePond:&quot;</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">failure</span>.<span class="py-src-variable">type</span> == <span class="py-src-variable">jelly</span>.<span class="py-src-variable">InsecureJelly</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; InsecureJelly&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">failure</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">pond</span> = <span class="py-src-variable">SenderPond</span>(<span class="py-src-number">3</span>, <span class="py-src-number">4</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;count %d&quot;</span> % <span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
+
+ <span class="py-src-variable">sender</span> = <span class="py-src-variable">Sender</span>(<span class="py-src-variable">pond</span>)
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">sender</span>.<span class="py-src-variable">got_obj</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/copy2_sender.py"><span class="filename">listings/pb/copy2_sender.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">copy2_classes</span> <span class="py-src-comment"># needed to get ReceiverPond registered with Jelly</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Receiver</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takePond</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; got pond:&quot;</span>, <span class="py-src-variable">pond</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; count %d&quot;</span> % <span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;safe and sound&quot;</span> <span class="py-src-comment"># positive acknowledgement</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;copy_receiver&quot;</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Receiver</span>())).<span class="py-src-variable">setServiceParent</span>(
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/pb/copy2_receiver.py"><span class="filename">listings/pb/copy2_receiver.py</span></a></div></div>
+
+<p>In this example, the classes are defined in a separate source file, which
+also sets up the binding between them. The <code>SenderPond</code> and
+<code>ReceiverPond</code> are unrelated save for this binding: they happen
+to implement the same methods, but use different internal instance variables
+to accomplish them.</p>
+
+<p>The recipient of the object doesn't even have to import the class
+definition into their namespace. It is sufficient that they import the class
+definition (and thus execute the <code>setUnjellyableForClass</code>
+statement). The Jelly layer remembers the class definition until a matching
+object is received. The sender of the object needs the definition, of
+course, to create the object in the first place.</p>
+
+<p>When run, the <code>copy2</code> example emits the following:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd -n -y copy2_receiver.py
+[-] twisted.spread.pb.PBServerFactory starting on 8800
+[-] Starting factory &lt;twisted.spread.pb.PBServerFactory instance at
+0x40604b4c&gt;
+[Broker,0,127.0.0.1] got pond: &lt;copy2_classes.ReceiverPond instance at
+0x406eb2ac&gt;
+[Broker,0,127.0.0.1] count 7
+</pre>
+
+<pre class="shell" xml:space="preserve">
+% ./copy2_sender.py
+count 7
+pond arrived safe and sound
+Main loop terminated.
+%
+</pre>
+
+
+
+<h3>Things To Watch Out For<a name="auto7"/></h3>
+
+<ul>
+
+ <li>The first argument to <code>setUnjellyableForClass</code> must refer
+ to the class <em>as known by the sender</em>. The sender has no way of
+ knowing about how your local <code>import</code> statements are set up,
+ and Python's flexible namespace semantics allow you to access the same
+ class through a variety of different names. You must match whatever the
+ sender does. Having both ends import the class from a separate file, using
+ a canonical module name (no <q>sibiling imports</q>), is a good way to get
+ this right, especially when both the sending and the receiving classes are
+ defined together, with the <code>setUnjellyableForClass</code> immediately
+ following them.</li>
+
+
+
+
+ <li>The class that is sent must inherit from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>. The class that is registered to
+ receive it must inherit from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code><a href="#footnote-2" title="pb.RemoteCopy is actually defined as pb.RemoteCopy, but pb.RemoteCopy is the preferred way to access it"><super>2</super></a>. </li>
+
+ <li>The same class can be used to send and receive. Just have it inherit
+ from both <code>pb.Copyable</code> and <code>pb.RemoteCopy</code>. This
+ will also make it possible to send the same class symmetrically back and
+ forth over the wire. But don't get confused about when it is coming (and
+ using <code>setCopyableState</code>) versus when it is going (using
+ <code>getStateToCopy</code>).</li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.jelly.InsecureJelly.html" title="twisted.spread.jelly.InsecureJelly">InsecureJelly</a></code>
+ exceptions are raised by the receiving end. They will be delivered
+ asynchronously to an <code>errback</code> handler. If you do not add one
+ to the <code>Deferred</code> returned by <code>callRemote</code>, then you
+ will never receive notification of the problem. </li>
+
+ <li>The class that is derived from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code> will be created using a
+ constructor <code>__init__</code> method that takes no arguments. All
+ setup must be performed in the <code>setCopyableState</code> method. As
+ the docstring on <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">RemoteCopy</a></code> says, don't implement a
+ constructor that requires arguments in a subclass of
+ <code>RemoteCopy</code>.</li>
+
+
+
+
+
+</ul>
+
+<h3>More Information<a name="auto8"/></h3>
+
+<ul>
+
+ <li> <code>pb.Copyable</code> is mostly implemented
+ in <code>twisted.spread.flavors</code>, and the docstrings there are
+ the best source of additional information.</li>
+
+ <li><code>Copyable</code> is also used in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.distrib.html" title="twisted.web.distrib">twisted.web.distrib</a></code> to deliver HTTP requests to other
+ programs for rendering, allowing subtrees of URL space to be delegated to
+ multiple programs (on multiple machines).</li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.manhole.explorer.html" title="twisted.manhole.explorer">twisted.manhole.explorer</a></code> also uses
+ <code>Copyable</code> to distribute debugging information from the program
+ under test to the debugging tool.</li>
+
+</ul>
+
+
+<h2>pb.Cacheable<a name="auto9"/></h2>
+
+<p>Sometimes the object you want to send to the remote process is big and
+slow. <q>big</q> means it takes a lot of data (storage, network bandwidth,
+processing) to represent its state. <q>slow</q> means that state doesn't
+change very frequently. It may be more efficient to send the full state only
+once, the first time it is needed, then afterwards only send the differences
+or changes in state whenever it is modified. The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">pb.Cacheable</a></code> class provides a framework to
+implement this.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">pb.Cacheable</a></code> is derived
+from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>, so it is
+based upon the idea of an object's state being captured on the sending side,
+and then turned into a new object on the receiving side. This is extended to
+have an object <q>publishing</q> on the sending side (derived from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">pb.Cacheable</a></code>), matched with one
+<q>observing</q> on the receiving side (derived from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCache.html" title="twisted.spread.pb.RemoteCache">pb.RemoteCache</a></code>).</p>
+
+<p>To effectively use <code>pb.Cacheable</code>, you need to isolate changes
+to your object into accessor functions (specifically <q>setter</q>
+functions). Your object needs to get control <em>every</em> single time some
+attribute is changed<a href="#footnote-3" title="of course you could be clever and add a hook to __setattr__, along with magical change-announcing subclasses of the usual builtin types, to detect changes that result from normal = set operations. The semi-magical property attributes that were introduced in Python-2.2 could be useful too. The result might be hard to maintain or extend, though."><super>3</super></a>.</p>
+
+<p>You derive your sender-side class from <code>pb.Cacheable</code>, and you
+add two methods: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.getStateToCacheAndObserveFor.html" title="twisted.spread.pb.Cacheable.getStateToCacheAndObserveFor">getStateToCacheAndObserveFor</a></code>
+and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.stoppedObserving.html" title="twisted.spread.pb.Cacheable.stoppedObserving">stoppedObserving</a></code>. The first
+is called when a remote caching reference is first created, and retrieves
+the data with which the cache is first filled. It also provides an
+object called the <q>observer</q><a href="#footnote-4" title="this is actually a RemoteCacheObserver, but it isn't very useful to subclass or modify, so simply treat it as a little demon that sits in your pb.Cacheable class and helps you distribute change notifications. The only useful thing to do with it is to run its callRemote method, which acts just like a normal pb.Referenceable's method of the same name."><super>4</super></a>
+
+that points at that receiver-side cache. Every time the state of the object
+is changed, you give a message to the observer, informing them of the
+change. The other method, <code>stoppedObserving</code>, is called when the
+remote cache goes away, so that you can stop sending updates.</p>
+
+<p>On the receiver end, you make your cache class inherit from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCache.html" title="twisted.spread.pb.RemoteCache">pb.RemoteCache</a></code>, and implement the
+<code>setCopyableState</code> as you would for a <code>pb.RemoteCopy</code>
+object. In addition, you must implement methods to receive the updates sent
+to the observer by the <code>pb.Cacheable</code>: these methods should have
+names that start with <code>observe_</code>, and match the
+<code>callRemote</code> invocations from the sender side just as the usual
+<code>remote_*</code> and <code>perspective_*</code> methods match normal
+<code>callRemote</code> calls. </p>
+
+<p>The first time a reference to the <code>pb.Cacheable</code> object is
+sent to any particular recipient, a sender-side Observer will be created for
+it, and the <code>getStateToCacheAndObserveFor</code> method will be called
+to get the current state and register the Observer. The state which that
+returns is sent to the remote end and turned into a local representation
+using <code>setCopyableState</code> just like <code>pb.RemoteCopy</code>,
+described above (in fact it inherits from that class). </p>
+
+<p>After that, your <q>setter</q> functions on the sender side should call
+<code>callRemote</code> on the Observer, which causes <code>observe_*</code>
+methods to run on the receiver, which are then supposed to update the
+receiver-local (cached) state.</p>
+
+<p>When the receiver stops following the cached object and the last
+reference goes away, the <code>pb.RemoteCache</code> object can be freed.
+Just before it dies, it tells the sender side it no longer cares about the
+original object. When <em>that</em> reference count goes to zero, the
+Observer goes away and the <code>pb.Cacheable</code> object can stop
+announcing every change that takes place. The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.stoppedObserving.html" title="twisted.spread.pb.Cacheable.stoppedObserving">stoppedObserving</a></code> method is
+used to tell the <code>pb.Cacheable</code> that the Observer has gone
+away.</p>
+
+<p>With the <code>pb.Cacheable</code> and <code>pb.RemoteCache</code>
+classes in place, bound together by a call to
+<code>pb.setUnjellyableForClass</code>, all that remains is to pass a
+reference to your <code>pb.Cacheable</code> over the wire to the remote end.
+The corresponding <code>pb.RemoteCache</code> object will automatically be
+created, and the matching methods will be used to keep the receiver-side
+slave object in sync with the sender-side master object.</p>
+
+<h3>Example<a name="auto10"/></h3>
+
+<p>Here is a complete example, in which the <code>MasterDuckPond</code> is
+controlled by the sending side, and the <code>SlaveDuckPond</code> is a
+cache that tracks changes to the master:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MasterDuckPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Cacheable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">ducks</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span> = []
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span> = <span class="py-src-variable">ducks</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I have [%d] ducks&quot;</span> % <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">addDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">duck</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">duck</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">o</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>: <span class="py-src-variable">o</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'addDuck'</span>, <span class="py-src-variable">duck</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">removeDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">duck</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">duck</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">o</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>: <span class="py-src-variable">o</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'removeDuck'</span>, <span class="py-src-variable">duck</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getStateToCacheAndObserveFor</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">perspective</span>, <span class="py-src-parameter">observer</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">observer</span>)
+ <span class="py-src-comment"># you should ignore pb.Cacheable-specific state, like self.observers</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span> <span class="py-src-comment"># in this case, just a list of ducks</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stoppedObserving</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">perspective</span>, <span class="py-src-parameter">observer</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">observer</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SlaveDuckPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">RemoteCache</span>):
+ <span class="py-src-comment"># This is a cache of a remote MasterDuckPond</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getDucks</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setCopyableState</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">state</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; cache - sitting, er, setting ducks&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span> = <span class="py-src-variable">state</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">observe_addDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">newDuck</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; cache - addDuck&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">newDuck</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">observe_removeDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">deadDuck</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; cache - removeDuck&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">deadDuck</span>)
+
+<span class="py-src-variable">pb</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">MasterDuckPond</span>, <span class="py-src-variable">SlaveDuckPond</span>)
+</pre><div class="caption">Source listing - <a href="listings/pb/cache_classes.py"><span class="filename">listings/pb/cache_classes.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">cache_classes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">MasterDuckPond</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Sender</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase1</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">remote</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">remote</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;takePond&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase2</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase2</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">addDuck</span>(<span class="py-src-string">&quot;ugly duckling&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">1</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">phase3</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase3</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;checkDucks&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase4</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase4</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">dummy</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">removeDuck</span>(<span class="py-src-string">&quot;one duck&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;checkDucks&quot;</span>)
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;ignorePond&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase5</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase5</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">dummy</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;shutdown&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase6</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase6</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">dummy</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">master</span> = <span class="py-src-variable">MasterDuckPond</span>([<span class="py-src-string">&quot;one duck&quot;</span>, <span class="py-src-string">&quot;two duck&quot;</span>])
+ <span class="py-src-variable">master</span>.<span class="py-src-variable">count</span>()
+
+ <span class="py-src-variable">sender</span> = <span class="py-src-variable">Sender</span>(<span class="py-src-variable">master</span>)
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">sender</span>.<span class="py-src-variable">phase1</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/cache_sender.py"><span class="filename">listings/pb/cache_sender.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cache_classes</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Receiver</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takePond</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got pond:&quot;</span>, <span class="py-src-variable">pond</span> <span class="py-src-comment"># a DuckPondCache</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote_checkDucks</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_checkDucks</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;[%d] ducks: &quot;</span> % <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>(), <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">getDucks</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_ignorePond</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-comment"># stop watching the pond</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;dropping pond&quot;</span>
+ <span class="py-src-comment"># gc causes __del__ causes 'decache' msg causes stoppedObserving</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;copy_receiver&quot;</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Receiver</span>())).<span class="py-src-variable">setServiceParent</span>(
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/pb/cache_receiver.py"><span class="filename">listings/pb/cache_receiver.py</span></a></div></div>
+<p>When run, this example emits the following:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd -n -y cache_receiver.py
+[-] twisted.spread.pb.PBServerFactory starting on 8800
+[-] Starting factory &lt;twisted.spread.pb.PBServerFactory instance at
+0x40615acc&gt;
+[Broker,0,127.0.0.1] cache - sitting, er, setting ducks
+[Broker,0,127.0.0.1] got pond: &lt;cache_classes.SlaveDuckPond instance at
+0x406eb5ec&gt;
+[Broker,0,127.0.0.1] [2] ducks: ['one duck', 'two duck']
+[Broker,0,127.0.0.1] cache - addDuck
+[Broker,0,127.0.0.1] [3] ducks: ['one duck', 'two duck', 'ugly duckling']
+[Broker,0,127.0.0.1] cache - removeDuck
+[Broker,0,127.0.0.1] [2] ducks: ['two duck', 'ugly duckling']
+[Broker,0,127.0.0.1] dropping pond
+%
+</pre>
+
+<pre class="shell" xml:space="preserve">
+% ./cache_sender.py
+I have [2] ducks
+I have [3] ducks
+I have [2] ducks
+Main loop terminated.
+%
+</pre>
+
+
+<p>Points to notice:</p>
+
+<ul>
+ <li>There is one <code>Observer</code> for each remote program that holds
+ an active reference. Multiple references inside the same program don't
+ matter: the serialization layer notices the duplicates and does the
+ appropriate reference counting<a href="#footnote-5" title="this applies to multiple references through the same Broker. If you've managed to make multiple TCP connections to the same program, you deserve whatever you get."><super>5</super></a>.
+ </li>
+
+ <li>Multiple Observers need to be kept in a list, and all of them need to
+ be updated when something changes. By sending the initial state at the
+ same time as you add the observer to the list, in a single atomic action
+ that cannot be interrupted by a state change, you insure that you can send
+ the same status update to all the observers.</li>
+
+ <li>The <code>observer.callRemote</code> calls can still fail. If the
+ remote side has disconnected very recently and
+ <code>stoppedObserving</code> has not yet been called, you may get a
+ <code>DeadReferenceError</code>. It is a good idea to add an errback to
+ those <code>callRemote</code>s to throw away such an error. This is a
+ useful idiom:
+
+ <pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">observer</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'foo'</span>, <span class="py-src-variable">arg</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">f</span>: <span class="py-src-variable">None</span>)
+</pre>
+ </li>
+
+
+ <li><code>getStateToCacheAndObserverFor</code> must return some object
+ that represents the current state of the object. This may simply be the
+ object's <code>__dict__</code> attribute. It is a good idea to remove the
+ <code>pb.Cacheable</code>-specific members of it before sending it to the
+ remote end. The list of Observers, in particular, should be left out, to
+ avoid dizzying recursive Cacheable references. The mind boggles as to the
+ potential consequences of leaving in such an item.</li>
+
+ <li>A <code>perspective</code> argument is available to
+ <code>getStateToCacheAndObserveFor</code>, as well as
+ <code>stoppedObserving</code>. I think the purpose of this is to allow
+ viewer-specific changes to the way the cache is updated. If all remote
+ viewers are supposed to see the same data, it can be ignored.</li>
+
+</ul>
+
+
+
+
+<h3>More Information<a name="auto11"/></h3>
+
+<ul>
+ <li>The best source for information comes from the docstrings
+ in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.flavors.html" title="twisted.spread.flavors">twisted.spread.flavors</a></code>,
+ where <code>pb.Cacheable</code> is implemented.</li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.manhole.explorer.html" title="twisted.manhole.explorer">twisted.manhole.explorer</a></code> uses
+ <code>Cacheable</code>, and does some fairly interesting things with it.</li>
+
+
+
+ <li>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.publish.html" title="twisted.spread.publish">spread.publish</a></code> module also
+ uses <code>Cacheable</code>, and might be a source of further
+ information.</li>
+</ul>
+
+
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote"> <p>Note that, in this context, <q>unjelly</q> is
+a verb with the opposite meaning of <q>jelly</q>. The verb <q>to jelly</q>
+means to serialize an object or data structure into a sequence of bytes (or
+other primitive transmittable/storable representation), while <q>to
+unjelly</q> means to unserialize the bytestream into a live object in the
+receiver's memory space. <q>Unjellyable</q> is a noun, (<em>not</em> an
+adjective), referring to the the class that serves as a destination or
+recipient of the unjellying process. <q>A is unjellyable into B</q> means
+that a serialized representation A (of some remote object) can be
+unserialized into a local object of type B. It is these objects <q>B</q>
+that are the <q>Unjellyable</q> second argument of the
+<code>setUnjellyableForClass</code> function.</p>
+
+<p>In particular, <q>unjellyable</q> does <em>not</em> mean <q>cannot be
+jellied</q>. <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.jelly.Unpersistable.html" title="twisted.spread.jelly.Unpersistable">Unpersistable</a></code> means <q>not
+persistable</q>, but <q>unjelly</q>, <q>unserialize</q>, and <q>unpickle</q>
+mean to reverse the operations of <q>jellying</q>, <q>serializing</q>, and
+<q>pickling</q>.</p> </span></a></li><li><a name="footnote-2"><span class="footnote"><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code> is actually defined
+ as <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code>, but
+ <code>pb.RemoteCopy</code> is the preferred way to access it</span></a></li><li><a name="footnote-3"><span class="footnote">of course you could be clever and
+add a hook to <code>__setattr__</code>, along with magical change-announcing
+subclasses of the usual builtin types, to detect changes that result from
+normal <q>=</q> set operations. The semi-magical <q>property attributes</q>
+that were introduced in Python-2.2 could be useful too. The result might be
+hard to maintain or extend, though.</span></a></li><li><a name="footnote-4"><span class="footnote"> this is actually a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCacheObserver.html" title="twisted.spread.pb.RemoteCacheObserver">RemoteCacheObserver</a></code>, but it isn't very
+useful to subclass or modify, so simply treat it as a little demon that sits
+in your <code>pb.Cacheable</code> class and helps you distribute change
+notifications. The only useful thing to do with it is to run its
+<code>callRemote</code> method, which acts just like a normal
+<code>pb.Referenceable</code>'s method of the same name.</span></a></li><li><a name="footnote-5"><span class="footnote">this applies to
+ multiple references through the same <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Broker.html" title="twisted.spread.pb.Broker">Broker</a></code>. If you've managed to make multiple
+ TCP connections to the same program, you deserve whatever you get.</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/pb-cred.html b/vendor/Twisted-10.0.0/doc/core/howto/pb-cred.html
new file mode 100644
index 0000000000..9cd425ef3e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/pb-cred.html
@@ -0,0 +1,1723 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Authentication with Perspective Broker</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Authentication with Perspective Broker</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Compartmentalizing Services</a></li><ul><li><a href="#auto2">Incorrect Arguments</a></li><li><a href="#auto3">Unforgeable References</a></li><li><a href="#auto4">Argument Typechecking</a></li><li><a href="#auto5">Objects as Capabilities</a></li></ul><li><a href="#auto6">Avatars and Perspectives</a></li><li><a href="#auto7">Perspective Examples</a></li><ul><li><a href="#auto8">One Client</a></li><li><a href="#auto9">Two Clients</a></li><li><a href="#auto10">How that example worked</a></li><li><a href="#auto11">Anonymous Clients</a></li></ul><li><a href="#auto12">Using Avatars</a></li><ul><li><a href="#auto13">Avatar Interfaces</a></li><li><a href="#auto14">Logging Out</a></li><li><a href="#auto15">Making Avatars</a></li><li><a href="#auto16">Connecting and Disconnecting</a></li><li><a href="#auto17">Viewable</a></li><li><a href="#auto18">Chat Server with Avatars</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Overview<a name="auto0"/></h2>
+
+<p>The examples shown in <a href="pb-usage.html" shape="rect">Using Perspective
+Broker</a> demonstrate how to do basic remote method calls, but provided no
+facilities for authentication. In this context, authentication is about who
+gets which remote references, and how to restrict access to the <q>right</q>
+set of people or programs.</p>
+
+<p>As soon as you have a program which offers services to multiple users,
+where those users should not be allowed to interfere with each other, you
+need to think about authentication. Many services use the idea of an
+<q>account</q>, and rely upon fact that each user has access to only one
+account. Twisted uses a system called <a href="cred.html" shape="rect">cred</a> to
+handle authentication issues, and Perspective Broker has code to make it
+easy to implement the most common use cases.</p>
+
+<h2>Compartmentalizing Services<a name="auto1"/></h2>
+
+<p>Imagine how you would write a chat server using PB. The first step might
+be a <code>ChatServer</code> object which had a bunch of
+<code>pb.RemoteReference</code>s that point at user clients. Pretend that
+those clients offered a <code>remote_print</code> method which lets the
+server print a message on the user's console. In that case, the server might
+look something like this:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatServer</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span> = {} <span class="py-src-comment"># indexed by name</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {} <span class="py-src-comment"># indexed by name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>, <span class="py-src-parameter">groupname</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">groupname</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>] = []
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>].<span class="py-src-variable">append</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">username</span>])
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_sendMessage</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">from_username</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">group</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">group</span>:
+ <span class="py-src-comment"># send the message to all members of the group</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">group</span>:
+ <span class="py-src-variable">user</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;print&quot;</span>,
+ <span class="py-src-string">&quot;&lt;%s&gt; says: %s&quot;</span> % (<span class="py-src-variable">from_username</span>,
+ <span class="py-src-variable">message</span>))
+</pre>
+
+<p>For now, assume that all clients have somehow acquired a
+<code>pb.RemoteReference</code> to this <code>ChatServer</code> object,
+perhaps using <code>pb.Root</code> and <code>getRootObject</code> as
+described in the <a href="pb-usage.html" shape="rect">previous chapter</a>. In this
+scheme, when a user sends a message to the group, their client runs
+something like the following:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">remotegroup</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;sendMessage&quot;</span>, <span class="py-src-string">&quot;alice&quot;</span>, <span class="py-src-string">&quot;Hi, my name is alice.&quot;</span>)
+</pre>
+
+
+<h3>Incorrect Arguments<a name="auto2"/></h3>
+
+<p>You've probably seen the first problem: users can trivially spoof each
+other. We depend upon the user to pass a correct value in their
+<q>username</q> argument, and have no way to tell if they're lying or not.
+There is nothing to prevent Alice from modifying her client to do:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">remotegroup</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;sendMessage&quot;</span>, <span class="py-src-string">&quot;bob&quot;</span>, <span class="py-src-string">&quot;i like pork&quot;</span>)
+</pre>
+
+<p>much to the horror of Bob's vegetarian friends.<a href="#footnote-1" title="Apparently Alice is one of those weirdos who has nothing better to do than to try and impersonate Bob. She will lie to her chat client, send incorrect objects to remote methods, even rewrite her local client code entirely to accomplish this juvenile prank. Given this adversarial relationship, one must wonder why she and Bob seem to spend so much time together: their adventures are clearly documented by the cryptographic literature."><super>1</super></a></p>
+
+<p>(In general, learn to get suspicious if you see any argument of a
+remotely-invokable method described as <q>must be X</q>)</p>
+
+<p>The best way to fix this is to keep track of the user's name locally,
+rather than asking them to send it to the server with each message. The best
+place to keep state is in an object, so this suggests we need a per-user
+object. Rather than choosing an obvious name<a href="#footnote-2" title="the obvious name is clearly ServerSidePerUserObjectWhichNobodyElseHasAccessTo, but because python makes everything else so easy to read, it only seems fair to make your audience work for something"><super>2</super></a>, let's call this the
+<code>User</code> class.
+</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">User</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>, <span class="py-src-parameter">server</span>, <span class="py-src-parameter">clientref</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">username</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span> = <span class="py-src-variable">server</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">clientref</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span>.<span class="py-src-variable">joinGroup</span>(<span class="py-src-variable">groupname</span>, <span class="py-src-variable">self</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_sendMessage</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span>.<span class="py-src-variable">sendMessage</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">groupname</span>, <span class="py-src-variable">message</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;print&quot;</span>, <span class="py-src-variable">message</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatServer</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span> = {} <span class="py-src-comment"># indexed by name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">groupname</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>] = []
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>].<span class="py-src-variable">append</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">sendMessage</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">from_username</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">group</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">group</span>:
+ <span class="py-src-comment"># send the message to all members of the group</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">group</span>:
+ <span class="py-src-variable">user</span>.<span class="py-src-variable">send</span>(<span class="py-src-string">&quot;&lt;%s&gt; says: %s&quot;</span> % (<span class="py-src-variable">from_username</span>, <span class="py-src-variable">message</span>))
+</pre>
+
+<p>Again, assume that each remote client gets access to a single
+<code>User</code> object, which is created with the proper username.</p>
+
+<p>Note how the <code>ChatServer</code> object has no remote access: it
+isn't even <code>pb.Referenceable</code> anymore. This means that all access
+to it must be mediated through other objects, with code that is under your
+control.</p>
+
+<p>As long as Alice only has access to her own <code>User</code> object, she
+can no longer spoof Bob. The only way for her to invoke
+<code>ChatServer.sendMessage</code> is to call her <code>User</code>
+object's <code>remote_sendMessage</code> method, and that method uses its
+own state to provide the <code>from_username</code> argument. It doesn't
+give her any way to change that state.</p>
+
+<p>This restriction is important. The <code>User</code> object is able to
+maintain its own integrity because there is a wall between the object and
+the client: the client cannot inspect or modify internal state, like the
+<code>.name</code> attribute. The only way through this wall is via remote
+method invocations, and the only control Alice has over those invocations is
+when they get invoked and what arguments they are given.</p>
+
+<div class="note"><strong>Note: </strong>
+<p>No object can maintain its integrity against local threats: by design,
+Python offers no mechanism for class instances to hide their attributes, and
+once an intruder has a copy of <code>self.__dict__</code>, they can do
+everything the original object was able to do.</p>
+</div>
+
+
+<h3>Unforgeable References<a name="auto3"/></h3>
+
+<p>Now suppose you wanted to implement group parameters, for example a mode
+in which nobody was allowed to talk about mattresses because some users were
+sensitive and calming them down after someone said <q>mattress</q> is a
+hassle that were best avoided altogether. Again, per-group state implies a
+per-group object. We'll go out on a limb and call this the
+<code>Group</code> object:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">User</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>, <span class="py-src-parameter">server</span>, <span class="py-src-parameter">clientref</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">username</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span> = <span class="py-src-variable">server</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">clientref</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">allowMattress</span>=<span class="py-src-parameter">True</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span>.<span class="py-src-variable">joinGroup</span>(<span class="py-src-variable">groupname</span>, <span class="py-src-variable">self</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;print&quot;</span>, <span class="py-src-variable">message</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Group</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">allowMattress</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">groupname</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> = <span class="py-src-variable">allowMattress</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = []
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">from_user</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> <span class="py-src-keyword">and</span> <span class="py-src-variable">message</span>.<span class="py-src-variable">find</span>(<span class="py-src-string">&quot;mattress&quot;</span>) != -<span class="py-src-number">1</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">ValueError</span>, <span class="py-src-string">&quot;Don't say that word&quot;</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>:
+ <span class="py-src-variable">user</span>.<span class="py-src-variable">send</span>(<span class="py-src-string">&quot;&lt;%s&gt; says: %s&quot;</span> % (<span class="py-src-variable">from_user</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">message</span>))
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">addUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatServer</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span> = {} <span class="py-src-comment"># indexed by name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">allowMattress</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">groupname</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>] = <span class="py-src-variable">Group</span>(<span class="py-src-variable">groupname</span>, <span class="py-src-variable">allowMattress</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>].<span class="py-src-variable">addUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>]
+</pre>
+
+
+<p>This example takes advantage of the fact that
+<code>pb.Referenceable</code> objects sent over a wire can be returned to
+you, and they will be turned into references to the same object that you
+originally sent. The client cannot modify the object in any way: all they
+can do is point at it and invoke its <code>remote_*</code> methods. Thus,
+you can be sure that the <code>.name</code> attribute remains the same as
+you left it. In this case, the client code would look something like
+this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientThing</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_print</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">message</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">join</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remoteUser</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;joinGroup&quot;</span>, <span class="py-src-string">&quot;#twisted&quot;</span>,
+ <span class="py-src-variable">allowMattress</span>=<span class="py-src-variable">False</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">gotGroup</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">gotGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">group</span>):
+ <span class="py-src-variable">group</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;send&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">remoteUser</span>, <span class="py-src-string">&quot;hi everybody&quot;</span>)
+</pre>
+
+<p>The <code>User</code> object is sent from the server side, and is turned
+into a <code>pb.RemoteReference</code> when it arrives at the client. The
+client sends it back to <code>Group.remote_send</code>, and PB turns it back
+into a reference to the original <code>User</code> when it gets there.
+<code>Group.remote_send</code> can then use its <code>.name</code> attribute
+as the sender of the message.</p>
+
+<div class="note"><strong>Note: </strong>
+
+<p>Third party references (there aren't any)</p>
+
+<p>This technique also relies upon the fact that the
+<code>pb.Referenceable</code> reference can <em>only</em> come from someone
+who holds a corresponding <code>pb.RemoteReference</code>. The design of the
+serialization mechanism (implemented in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.jelly.html" title="twisted.spread.jelly">twisted.spread.jelly</a></code>: pb, jelly, spread.. get it? Look for
+<q>banana</q>, too. What other networking framework
+can claim API names based on sandwich ingredients?) makes it impossible for
+a client to obtain a reference that they weren't explicitly given.
+References passed over the wire are given id numbers and recorded in a
+per-connection dictionary. If you didn't give them the reference, the id
+number won't be in the dict, and no amount of guessing by a malicious client
+will give them anything else. The dict goes away when the connection is
+dropped, further limiting the scope of those references.</p>
+
+<p>Futhermore, it is not possible for Bob to send <em>his</em>
+<code>User</code> reference to Alice (perhaps over some other PB channel
+just between the two of them). Outside the context of Bob's connection to
+the server, that reference is just a meaningless number. To prevent
+confusion, PB will tell you if you try to give it away: when you try to hand
+a <code>pb.RemoteReference</code> to a third party, you'll get an exception
+(implemented with an assert in pb.py:364 RemoteReference.jellyFor).</p>
+
+<p>This helps the security model somewhat: only the client you gave the
+reference to can cause any damage with it. Of course, the client might be a
+brainless zombie, simply doing anything some third party wants. When it's
+not proxying <code>callRemote</code> invocations, it's probably terrorizing
+the living and searching out human brains for sustenance. In short, if you
+don't trust them, don't give them that reference.</p>
+
+<p>And remember that everything you've ever given them over that connection
+can come back to you. If expect the client to invoke your method with some
+object A that you sent to them earlier, and instead they send you object B
+(that you also sent to them earlier), and you don't check it somehow, then
+you've just opened up a security hole (we'll see an example of this
+shortly). It may be better to keep such objects in a dictionary on the
+server side, and have the client send you an index string instead. Doing it
+that way makes it obvious that they can send you anything they want, and
+improves the chances that you'll remember to implement the right checks.
+(This is exactly what PB is doing underneath, with a per-connection
+dictionary of <code>Referenceable</code> objects, indexed by a number).</p>
+
+<p>And, of course, you have to make sure you don't accidentally hand out a
+reference to the wrong object.</p>
+
+</div>
+
+
+<p>But again, note the vulnerability. If Alice holds a
+<code>RemoteReference</code> to <em>any</em> object on the server side that
+has a <code>.name</code> attribute, she can use that name as a spoofed
+<q>from</q> parameter. As a simple example, what if her client code looked
+like:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientThing</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">join</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remoteUser</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;joinGroup&quot;</span>, <span class="py-src-string">&quot;#twisted&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">gotGroup</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">gotGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">group</span>):
+ <span class="py-src-variable">group</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;send&quot;</span>, <span class="py-src-variable">from_user</span>=<span class="py-src-variable">group</span>, <span class="py-src-string">&quot;hi everybody&quot;</span>)
+</pre>
+
+<p>This would let her send a message that appeared to come from
+<q>#twisted</q> rather than <q>Alice</q>. If she joined a group that
+happened to be named <q>bob</q> (perhaps it is the <q>How To Be Bob</q>
+channel, populated by Alice and countless others, a place where they can
+share stories about their best impersonating-Bob moments), then she would be
+able to emit a message that looked like <q>&lt;bob&gt; says: hi there</q>,
+and she has accomplished her lifelong goal.</p>
+
+
+<h3>Argument Typechecking<a name="auto4"/></h3>
+
+<p>There are two techniques to close this hole. The first is to have your
+remotely-invokable methods do type-checking on their arguments: if
+<code>Group.remote_send</code> asserted <code>isinstance(from_user,
+User)</code> then Alice couldn't use non-User objects to do her spoofing,
+and hopefully the rest of the system is designed well enough to prevent her
+from obtaining access to somebody else's User object.</p>
+
+
+<h3>Objects as Capabilities<a name="auto5"/></h3>
+
+<p>The second technique is to avoid having the client send you the objects
+altogether. If they don't send you anything, there is nothing to verify. In
+this case, you would have to have a per-user-per-group object, in which the
+<code>remote_send</code> method would only take a single
+<code>message</code> argument. The <code>UserGroup</code> object is created
+with references to the only <code>User</code> and <code>Group</code> objects
+that it will ever use, so no lookups are needed:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">UserGroup</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">group</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">group</span> = <span class="py-src-variable">group</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">group</span>.<span class="py-src-variable">send</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">message</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Group</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">allowMattress</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">groupname</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> = <span class="py-src-variable">allowMattress</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = []
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">from_user</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> <span class="py-src-keyword">and</span> <span class="py-src-variable">message</span>.<span class="py-src-variable">find</span>(<span class="py-src-string">&quot;mattress&quot;</span>) != -<span class="py-src-number">1</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">ValueError</span>, <span class="py-src-string">&quot;Don't say that word&quot;</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>:
+ <span class="py-src-variable">user</span>.<span class="py-src-variable">send</span>(<span class="py-src-string">&quot;&lt;%s&gt; says: %s&quot;</span> % (<span class="py-src-variable">from_user</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">message</span>))
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">addUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">user</span>)
+</pre>
+
+<p>The only message-sending method Alice has left is
+<code>UserGroup.remote_send</code>, and it only accepts a message: there are
+no remaining ways to influence the <q>from</q> name.</p>
+
+<p>In this model, each remotely-accessible object represents a very small
+set of capabilities. Security is achieved by only granting a minimal set of
+abilities to each remote user.</p>
+
+<p>PB provides a shortcut which makes this technique easier to use. The
+<code>Viewable</code> class will be discussed <a href="#viewable" shape="rect">below</a>.</p>
+
+<h2>Avatars and Perspectives<a name="auto6"/></h2>
+
+<p>In Twisted's <a href="cred.html" shape="rect">cred</a> system, an <q>Avatar</q> is
+an object that lives on the <q>server</q> side (defined here as the side
+farthest from the human who is trying to get something done) which lets the
+remote user get something done. The avatar isn't really a particular class,
+it's more like a description of a role that some object plays, as in <q>the
+Foo object here is acting as the user's avatar for this particular
+service</q>. Generally, the remote user has some way of getting their avatar
+to run some code. The avatar object may enforce some security checks, and
+provide additional data, then call other methods which get things done.</p>
+
+<p>The two pieces in the cred puzzle (for any protocol, not just PB) are:
+<q>what serves as the Avatar?</q>, and <q>how does the user get access to
+it?</q>.</p>
+
+<p>For PB, the first question is easy. The Avatar is a remotely-accessible
+object which can run code: this is a perfect description of
+<code>pb.Referenceable</code> and its subclasses. We shall defer the second
+question until the next section.</p>
+
+<p>In the example above, you can think of the <code>ChatServer</code> and
+<code>Group</code> objects as a service. The <code>User</code> object is the
+user's server-side representative: everything the user is capable of doing
+is done by running one of its methods. Anything that the server wants to do
+to the user (change their group membership, change their name, delete their
+pet cat, whatever) is done by manipulating the <code>User</code> object.</p>
+
+<p>There are multiple User objects living in peace and harmony around the
+ChatServer. Each has a different point of view on the services provided by
+the ChatServer and the Groups: each may belong to different groups, some
+might have more permissions than others (like the ability to create groups).
+These different points of view are called <q>Perspectives</q>. This is the
+origin of the term <q>Perspective</q> in <q>Perspective Broker</q>: PB
+provides and controls (i.e. <q>brokers</q>) access to Perspectives.</p>
+
+<p>Once upon a time, these local-representative objects were actually called
+<code>pb.Perspective</code>. But this has changed with the advent of the
+rewritten cred system, and now the more generic term for a local
+representative object is an Avatar. But you will still see reference to
+<q>Perspective</q> in the code, the docs, and the module names<a href="#footnote-3" title="We could just go ahead and rename Perspective Broker to be Avatar Broker, but 1) that would cause massive compatibility problems, and 2) AB doesn't fit into the whole sandwich-themed naming scheme nearly as well as PB does. If we changed it to AB, we'd probably have to change Banana to be CD (CoderDecoder), and Jelly to be EF (EncapsulatorFragmentor). twisted.spread would then have to be renamed twisted.alphabetsoup, and then the whole food-pun thing would start all over again."><super>3</super></a>. Just remember
+that perspectives and avatars are basically the same thing. </p>
+
+<p>Despite all we've been <a href="cred.html" shape="rect">telling you</a> about how
+Avatars are more of a concept than an actual class, the base class from
+which you can create your server-side avatar-ish objects is, in fact, named
+<code>pb.Avatar</code><a href="#footnote-4" title="The avatar-ish class is named pb.Avatar because pb.Perspective was already taken, by the (now obsolete) oldcred perspective-ish class. It is a pity, but it simply wasn't possible both replace pb.Perspective in-place and maintain a reasonable level of backwards-compatibility."><super>4</super></a>. These objects behave very much like
+<code>pb.Referenceable</code>. The only difference is that instead of
+offering <q>remote_FOO</q> methods, they offer <q>perspective_FOO</q>
+methods.</p>
+
+<p>The other way in which <code>pb.Avatar</code> differs from
+<code>pb.Referenceable</code> is that the avatar objects are designed to be
+the first thing retrieved by a cred-using remote client. Just as
+<code>PBClientFactory.getRootObject</code> gives the client access to a
+<code>pb.Root</code> object (which can then provide access to all kinds of
+other objects), <code>PBClientFactory.login</code> gives client access to a
+<code>pb.Avatar</code> object (which can return other references). </p>
+
+<p>So, the first half of using cred in your PB application is to create an
+Avatar object which implements <code>perspective_</code> methods and is
+careful to do useful things for the remote user while remaining vigilant
+against being tricked with unexpected argument values. It must also be
+careful to never give access to objects that the user should not have access
+to, whether by returning them directly, returning objects which contain
+them, or returning objects which can be asked (remotely) to provide
+them.</p>
+
+<p>The second half is how the user gets a <code>pb.RemoteReference</code> to
+your Avatar. As explained <a href="cred.html" shape="rect">elsewhere</a>, Avatars are
+obtained from a Realm. The Realm doesn't deal with authentication at all
+(usernames, passwords, public keys, challenge-response systems, retinal
+scanners, real-time DNA sequencers, etc). It simply takes an <q>avatarID</q>
+(which is effectively a username) and returns an Avatar object. The Portal
+and its Checkers deal with authenticating the user: by the time they are
+done, the remote user has proved their right to access the avatarID that is
+given to the Realm, so the Realm can return a remotely-controllable object
+that has whatever powers you wish to grant to this particular user. </p>
+
+<p>For PB, the realm is expected to return a <code>pb.Avatar</code> (or
+anything which implements <code>pb.IPerspective</code>, really, but there's
+no reason to not return a <code>pb.Avatar</code> subclass). This object will
+be given to the client just like a <code>pb.Root</code> would be without
+cred, and the user can get access to other objects through it (if you let
+them).</p>
+
+<p>The basic idea is that there is a separate IPerspective-implementing
+object (i.e. the Avatar subclass) (i.e. the <q>perspective</q>) for each
+user, and <em>only</em> the authorized user gets a remote reference to that
+object. You can store whatever permissions or capabilities the user
+possesses in that object, and then use them when the user invokes a remote
+method. You give the user access to the perspective object instead of the
+objects that do the real work.</p>
+
+
+<h2>Perspective Examples<a name="auto7"/></h2>
+
+<p>Here is a brief example of using a pb.Avatar. Most of the support code
+is magic for now: we'll explain it later.</p>
+
+<h3>One Client<a name="auto8"/></h3>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">checkers</span>, <span class="py-src-variable">portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyPerspective</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_foo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I am&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>, <span class="py-src-string">&quot;perspective_foo(&quot;</span>,<span class="py-src-variable">arg</span>,<span class="py-src-string">&quot;) called on&quot;</span>, <span class="py-src-variable">self</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">MyPerspective</span>(<span class="py-src-variable">avatarId</span>), <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+
+<span class="py-src-variable">p</span> = <span class="py-src-variable">portal</span>.<span class="py-src-variable">Portal</span>(<span class="py-src-variable">MyRealm</span>())
+<span class="py-src-variable">p</span>.<span class="py-src-variable">registerChecker</span>(
+ <span class="py-src-variable">checkers</span>.<span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>(<span class="py-src-variable">user1</span>=<span class="py-src-string">&quot;pass1&quot;</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">p</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb5server.py"><span class="filename">listings/pb/pb5server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">credentials</span>.<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-string">&quot;user1&quot;</span>, <span class="py-src-string">&quot;pass1&quot;</span>))
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">perspective</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got perspective ref:&quot;</span>, <span class="py-src-variable">perspective</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to foo(12)&quot;</span>
+ <span class="py-src-variable">perspective</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-number">12</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb5client.py"><span class="filename">listings/pb/pb5client.py</span></a></div></div>
+
+<p>Ok, so that wasn't really very exciting. It doesn't accomplish much more
+than the first PB example, and used a lot more code to do it. Let's try it
+again with two users this time.</p>
+
+<div class="note"><strong>Note: </strong>
+
+<p>When the client runs <code>login</code> to request the Perspective,
+they can provide it with an optional <code>client</code> argument (which
+must be a <code>pb.Referenceable</code> object). If they do, then a
+reference to that object will be handed to the realm's
+<code>requestAvatar</code> in the <code>mind</code> argument.</p>
+
+<p>The server-side Perspective can use it to invoke remote methods on
+something in the client, so that the client doesn't always have to drive the
+interaction. In a chat server, the client object would be the one to which
+<q>display text</q> messages were sent. In a board game server, this would
+provide a way to tell the clients that someone has made a move, so they can
+update their game boards.</p>
+
+</div>
+
+<h3>Two Clients<a name="auto9"/></h3>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">checkers</span>, <span class="py-src-variable">portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyPerspective</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_foo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I am&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>, <span class="py-src-string">&quot;perspective_foo(&quot;</span>,<span class="py-src-variable">arg</span>,<span class="py-src-string">&quot;) called on&quot;</span>, <span class="py-src-variable">self</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">MyPerspective</span>(<span class="py-src-variable">avatarId</span>), <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+
+<span class="py-src-variable">p</span> = <span class="py-src-variable">portal</span>.<span class="py-src-variable">Portal</span>(<span class="py-src-variable">MyRealm</span>())
+<span class="py-src-variable">c</span> = <span class="py-src-variable">checkers</span>.<span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>(<span class="py-src-variable">user1</span>=<span class="py-src-string">&quot;pass1&quot;</span>,
+ <span class="py-src-variable">user2</span>=<span class="py-src-string">&quot;pass2&quot;</span>)
+<span class="py-src-variable">p</span>.<span class="py-src-variable">registerChecker</span>(<span class="py-src-variable">c</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">p</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb6server.py"><span class="filename">listings/pb/pb6server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">credentials</span>.<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-string">&quot;user1&quot;</span>, <span class="py-src-string">&quot;pass1&quot;</span>))
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">perspective</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got perspective1 ref:&quot;</span>, <span class="py-src-variable">perspective</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to foo(13)&quot;</span>
+ <span class="py-src-variable">perspective</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-number">13</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb6client1.py"><span class="filename">listings/pb/pb6client1.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">credentials</span>.<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-string">&quot;user2&quot;</span>, <span class="py-src-string">&quot;pass2&quot;</span>))
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">perspective</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got perspective2 ref:&quot;</span>, <span class="py-src-variable">perspective</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to foo(14)&quot;</span>
+ <span class="py-src-variable">perspective</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-number">14</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb6client2.py"><span class="filename">listings/pb/pb6client2.py</span></a></div></div>
+
+<p>While pb6server.py is running, try starting pb6client1, then pb6client2.
+Compare the argument passed by the <code>.callRemote()</code> in each
+client. You can see how each client gets connected to a different
+Perspective.</p>
+
+
+<h3>How that example worked<a name="auto10"/></h3><a name="smallexample" shape="rect"/>
+
+<p>Let's walk through the previous example and see what was going on.</p>
+
+<p>First, we created a subclass called <code>MyPerspective</code> which is
+our server-side Avatar. It implements a <code>perspective_foo</code> method
+that is exposed to the remote client.</p>
+
+<p>Second, we created a realm (an object which implements
+<code>IRealm</code>, and therefore implements <code>requestAvatar</code>).
+This realm manufactures <code>MyPerspective</code> objects. It makes as many
+as we want, and names each one with the avatarID (a username) that comes out
+of the checkers. This MyRealm object returns two other objects as well,
+which we will describe later.</p>
+
+<p>Third, we created a portal to hold this realm. The portal's job is to
+dispatch incoming clients to the credential checkers, and then to request
+Avatars for any which survive the authentication process.</p>
+
+<p>Fourth, we made a simple checker (an object which implements
+<code>IChecker</code>) to hold valid user/password pairs. The checker
+gets registered with the portal, so it knows who to ask when new
+clients connect. We use a checker named
+<code>InMemoryUsernamePasswordDatabaseDontUse</code>, which suggests
+that 1: all the username/password pairs are kept in memory instead of
+being saved to a database or something, and 2: you shouldn't use
+it. The admonition against using it is because there are better
+schemes: keeping everything in memory will not work when you have
+thousands or millions of users to keep track of, the passwords will be
+stored in the .tap file when the application shuts down (possibly a
+security risk), and finally it is a nuisance to add or remove users
+after the checker is constructed.</p>
+
+<p>Fifth, we create a <code>pb.PBServerFactory</code> to listen on a TCP
+port. This factory knows how to connect the remote client to the Portal, so
+incoming connections will be handed to the authentication process. Other
+protocols (non-PB) would do something similar: the factory that creates
+Protocol objects will give those objects access to the Portal so
+authentication can take place.</p>
+
+<p>On the client side, a <code>pb.PBClientFactory</code> is created (as <a href="pb-usage.html" shape="rect">before</a>) and attached to a TCP connection. When the
+connection completes, the factory will be asked to produce a Protocol, and
+it will create a PB object. Unlike the previous chapter, where we used
+<code>.getRootObject</code>, here we use <code>factory.login</code> to
+initiate the cred authentication process. We provide a
+<code>credentials</code> object, which is the client-side agent for doing
+our half of the authentication process. This process may involve several
+messages: challenges, responses, encrypted passwords, secure hashes, etc. We
+give our credentials object everything it will need to respond correctly (in
+this case, a username and password, but you could write a credential that
+used public-key encryption or even fancier techniques).</p>
+
+<p><code>login</code> returns a Deferred which, when it fires, will return a
+<code>pb.RemoteReference</code> to the remote avatar. We can then do
+<code>callRemote</code> to invoke a <code>perspective_foo</code> method on
+that Avatar.</p>
+
+
+<h3>Anonymous Clients<a name="auto11"/></h3>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2007-2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Implement the realm for and run on port 8800 a PB service which allows both
+anonymous and username/password based access.
+
+Successful username/password-based login requests given an instance of
+MyPerspective with a name which matches the username with which they
+authenticated. Success anonymous login requests are given an instance of
+MyPerspective with the name &quot;Anonymous&quot;.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">startLogging</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ANONYMOUS</span>, <span class="py-src-variable">AllowAnonymousAccess</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>, <span class="py-src-variable">Portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span>.<span class="py-src-variable">pb</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Avatar</span>, <span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">PBServerFactory</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyPerspective</span>(<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Trivial avatar exposing a single remote method for demonstrative
+ purposes. All successful login attempts in this example will result in
+ an avatar which is an instance of this class.
+
+ @type name: C{str}
+ @ivar name: The username which was used during login or C{&quot;Anonymous&quot;}
+ if the login was anonymous (a real service might want to avoid the
+ collision this introduces between anonoymous users and authenticated
+ users named &quot;Anonymous&quot;).
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_foo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Print a simple message which gives the argument this method was
+ called with and this avatar's name.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I am %s. perspective_foo(%s) called on %s.&quot;</span> % (
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">arg</span>, <span class="py-src-variable">self</span>)
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyRealm</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Trivial realm which supports anonymous and named users by creating
+ avatars which are instances of MyPerspective for either.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>(<span class="py-src-string">&quot;MyRealm only handles IPerspective&quot;</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarId</span> <span class="py-src-keyword">is</span> <span class="py-src-variable">ANONYMOUS</span>:
+ <span class="py-src-variable">avatarId</span> = <span class="py-src-string">&quot;Anonymous&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">MyPerspective</span>(<span class="py-src-variable">avatarId</span>), <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">None</span>
+
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Create a PB server using MyRealm and run it on port 8800.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">startLogging</span>(<span class="py-src-variable">stdout</span>)
+
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">MyRealm</span>())
+
+ <span class="py-src-comment"># Here the username/password checker is registered.</span>
+ <span class="py-src-variable">c1</span> = <span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>(<span class="py-src-variable">user1</span>=<span class="py-src-string">&quot;pass1&quot;</span>, <span class="py-src-variable">user2</span>=<span class="py-src-string">&quot;pass2&quot;</span>)
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">registerChecker</span>(<span class="py-src-variable">c1</span>)
+
+ <span class="py-src-comment"># Here the anonymous checker is registered.</span>
+ <span class="py-src-variable">c2</span> = <span class="py-src-variable">AllowAnonymousAccess</span>()
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">registerChecker</span>(<span class="py-src-variable">c2</span>)
+
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">p</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pbAnonServer.py"><span class="filename">listings/pb/pbAnonServer.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2007-2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-string">&quot;&quot;&quot;
+Client which will talk to the server run by pbAnonServer.py, logging in
+either anonymously or with username/password credentials.
+&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">sys</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">stdout</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">err</span>, <span class="py-src-variable">startLogging</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">credentials</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Anonymous</span>, <span class="py-src-variable">UsernamePassword</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">gatherResults</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span>.<span class="py-src-variable">pb</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">PBClientFactory</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">error</span>(<span class="py-src-parameter">why</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Catch-all errback which simply logs the failure. This isn't expected to
+ be invoked in the normal case for this example.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">err</span>(<span class="py-src-variable">why</span>, <span class="py-src-variable">msg</span>)
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">perspective</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Login callback which invokes the remote &quot;foo&quot; method on the perspective
+ which the server returned.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got perspective1 ref:&quot;</span>, <span class="py-src-variable">perspective</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to foo(13)&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">perspective</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-number">13</span>)
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">finished</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Callback invoked when both logins and method calls have finished to shut
+ down the reactor so the example exits.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Connect to a PB server running on port 8800 on localhost and log in to
+ it, both anonymously and using a username/password it will recognize.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">startLogging</span>(<span class="py-src-variable">stdout</span>)
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+
+ <span class="py-src-variable">anonymousLogin</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">Anonymous</span>())
+ <span class="py-src-variable">anonymousLogin</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">anonymousLogin</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">error</span>, <span class="py-src-string">&quot;Anonymous login failed&quot;</span>)
+
+ <span class="py-src-variable">usernameLogin</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-string">&quot;user1&quot;</span>, <span class="py-src-string">&quot;pass1&quot;</span>))
+ <span class="py-src-variable">usernameLogin</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">usernameLogin</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">error</span>, <span class="py-src-string">&quot;Username/password login failed&quot;</span>)
+
+ <span class="py-src-variable">bothDeferreds</span> = <span class="py-src-variable">gatherResults</span>([<span class="py-src-variable">anonymousLogin</span>, <span class="py-src-variable">usernameLogin</span>])
+ <span class="py-src-variable">bothDeferreds</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">finished</span>)
+
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pbAnonClient.py"><span class="filename">listings/pb/pbAnonClient.py</span></a></div></div>
+
+<p>pbAnonServer.py implements a server based on pb6server.py, extending it to
+permit anonymous logins in addition to authenticated logins. A
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.checkers.AllowAnonymousAccess.html" title="twisted.cred.checkers.AllowAnonymousAccess">AllowAnonymousAccess</a></code>
+checker and a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.checkers.
+.html" title="twisted.cred.checkers.
+">
+InMemoryUsernamePasswordDatabaseDontUse</a></code> checker are registered and the
+client's choice of credentials object determines which is used to authenticate
+the login. In either case, the realm will be called on to create an avatar for
+the login. <code>AllowAnonymousAccess</code> always produces an <code>avatarId
+</code> of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.checkers.ANONYMOUS.html" title="twisted.cred.checkers.ANONYMOUS">ANONYMOUS</a></code>.</p>
+
+<p>On the client side, the only change is the use of an instance of
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.credentials.Anonymous.html" title="twisted.cred.credentials.Anonymous">Anonymous</a></code> when calling
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.PBClientFactory.login.html" title="twisted.spread.pb.PBClientFactory.login">PBClientFactory.login</a></code>.</p>
+
+
+<h2>Using Avatars<a name="auto12"/></h2>
+
+
+<h3>Avatar Interfaces<a name="auto13"/></h3>
+
+<p>The first element of the 3-tuple returned by <code>requestAvatar</code>
+indicates which Interface this Avatar implements. For PB avatars, it will
+always be <code>pb.IPerspective</code>, because that's the only interface
+these avatars implement.</p>
+
+<p>This element is present because <code>requestAvatar</code> is actually
+presented with a list of possible Interfaces. The question being posed to
+the Realm is: <q>do you have an avatar for (avatarID) that can implement one
+of the following set of Interfaces?</q>. Some portals and checkers might
+give a list of Interfaces and the Realm could pick; the PB code only knows
+how to do one, so we cannot take advantage of this feature.</p>
+
+<h3>Logging Out<a name="auto14"/></h3>
+
+<p>The third element of the 3-tuple is a zero-argument callable, which will
+be invoked by the protocol when the connection has been lost. We can use
+this to notify the Avatar when the client has lost its connection. This will
+be described in more detail below.</p>
+
+<h3>Making Avatars<a name="auto15"/></h3>
+
+<p>In the example above, we create Avatars upon request, during
+<code>requestAvatar</code>. Depending upon the service, these Avatars might
+already exist before the connection is received, and might outlive the
+connection. The Avatars might also accept multiple connections.</p>
+
+<p>Another possibility is that the Avatars might exist ahead of time, but in
+a different form (frozen in a pickle and/or saved in a database). In this
+case, <code>requestAvatar</code> may need to perform a database lookup and
+then do something with the result before it can provide an avatar. In this
+case, it would probably return a Deferred so it could provide the real
+Avatar later, once the lookup had completed.</p>
+
+<p>Here are some possible implementations of
+<code>MyRealm.requestAvatar</code>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+</p><span class="py-src-comment"># pre-existing, static avatars</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-variable">avatar</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarID</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">avatar</span>, <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+
+ <span class="py-src-comment"># database lookup and unpickling</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">database</span>.<span class="py-src-variable">fetchAvatar</span>(<span class="py-src-variable">avatarID</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">doUnpickle</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">d</span>, <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">doUnpickle</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pickled</span>):
+ <span class="py-src-variable">avatar</span> = <span class="py-src-variable">pickle</span>.<span class="py-src-variable">loads</span>(<span class="py-src-variable">pickled</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">avatar</span>
+
+ <span class="py-src-comment"># everybody shares the same Avatar</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">theOneAvatar</span>, <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+
+ <span class="py-src-comment"># anonymous users share one Avatar, named users each get their own</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarID</span> == <span class="py-src-variable">checkers</span>.<span class="py-src-variable">ANONYMOUS</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">anonAvatar</span>, <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarID</span>], <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+
+ <span class="py-src-comment"># anonymous users get independent (but temporary) Avatars</span>
+ <span class="py-src-comment"># named users get their own persistent one</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarID</span> == <span class="py-src-variable">checkers</span>.<span class="py-src-variable">ANONYMOUS</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">MyAvatar</span>(), <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarID</span>], <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+</pre>
+
+<p>The last example, note that the new <code>MyAvatar</code> instance is not
+saved anywhere: it will vanish when the connection is dropped. By contrast,
+the avatars that live in the <code>self.avatars</code> dictionary will
+probably get persisted into the .tap file along with the Realm, the Portal,
+and anything else that is referenced by the top-level Application object.
+This is an easy way to manage saved user profiles.</p>
+
+
+<h3>Connecting and Disconnecting<a name="auto16"/></h3>
+
+<p>It may be useful for your Avatars to be told when remote clients gain
+(and lose) access to them. For example, and Avatar might be updated by
+something in the server, and if there are clients attached, it should update
+them (through the <q>mind</q> argument which lets the Avatar do callRemote
+on the client).</p>
+
+<p>One common idiom which accomplishes this is to have the Realm tell the
+avatar that a remote client has just attached. The Realm can also ask the
+protocol to let it know when the connection goes away, so it can then inform
+the Avatar that the client has detached. The third member of the
+<code>requestAvatar</code> return tuple is a callable which will be invoked
+when the connection is lost.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">MyPerspective</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clients</span> = []
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">attached</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">mind</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clients</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">mind</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;attached to&quot;</span>, <span class="py-src-variable">mind</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">detached</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">mind</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">clients</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">mind</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;detached from&quot;</span>, <span class="py-src-variable">mind</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">update</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">c</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">clients</span>:
+ <span class="py-src-variable">c</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;update&quot;</span>, <span class="py-src-variable">message</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyRealm</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-variable">avatar</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarID</span>]
+ <span class="py-src-variable">avatar</span>.<span class="py-src-variable">attached</span>(<span class="py-src-variable">mind</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">avatar</span>, <span class="py-src-keyword">lambda</span> <span class="py-src-variable">a</span>=<span class="py-src-variable">avatar</span>:<span class="py-src-variable">a</span>.<span class="py-src-variable">detached</span>(<span class="py-src-variable">mind</span>)
+</pre>
+
+
+<h3>Viewable<a name="auto17"/></h3> <a name="viewable" shape="rect"/>
+
+<p>Once you have <code>IPerspective</code> objects (i.e. the Avatar) to
+represent users, the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.flavors.Viewable.html" title="twisted.spread.flavors.Viewable">Viewable</a></code> class can come into play. This
+class behaves a lot like <code>Referenceable</code>: it turns into a
+<code>RemoteReference</code> when sent over the wire, and certain methods
+can be invoked by the holder of that reference. However, the methods that
+can be called have names that start with <code>view_</code> instead of
+<code>remote_</code>, and those methods are always called with an extra
+<code>perspective</code> argument that points to the Avatar through which
+the reference was sent:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Foo</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Viewable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">view_doFoo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">perspective</span>, <span class="py-src-parameter">arg1</span>, <span class="py-src-parameter">arg2</span>):
+ <span class="py-src-keyword">pass</span>
+</pre>
+
+<p>This is useful if you want to let multiple clients share a reference to
+the same object. The <code>view_</code> methods can use the
+<q>perspective</q> argument to figure out which client is calling them. This
+gives them a way to do additional permission checks, do per-user accounting,
+etc.</p>
+
+<p>This is the shortcut which makes per-user-per-group capability objects
+much easier to use. Instead of creating such per-(user,group) objects, you
+just have per-group objects which inherit from <code>pb.Viewable</code>, and
+give the user references to them. The local <code>pb.Avatar</code> object
+will automatically show up as the <q>perspective</q> argument in the
+<code>view_*</code> method calls, give you a chance to involve the Avatar in
+the process.</p>
+
+
+<h3>Chat Server with Avatars<a name="auto18"/></h3>
+
+<p>Combining all the above techniques, here is an example chat server which
+uses a fixed set of identities (say, for the three members of your bridge
+club, who hang out in <q>#NeedAFourth</q> hoping that someone will discover
+your server, guess somebody's password, break in, join the group, and also
+be available for a game next saturday afternoon).</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">portal</span>, <span class="py-src-variable">checkers</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatServer</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span> = {} <span class="py-src-comment"># indexed by name</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">allowMattress</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">groupname</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>] = <span class="py-src-variable">Group</span>(<span class="py-src-variable">groupname</span>, <span class="py-src-variable">allowMattress</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>].<span class="py-src-variable">addUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">groups</span>[<span class="py-src-variable">groupname</span>]
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ChatRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarID</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">assert</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>
+ <span class="py-src-variable">avatar</span> = <span class="py-src-variable">User</span>(<span class="py-src-variable">avatarID</span>)
+ <span class="py-src-variable">avatar</span>.<span class="py-src-variable">server</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span>
+ <span class="py-src-variable">avatar</span>.<span class="py-src-variable">attached</span>(<span class="py-src-variable">mind</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">avatar</span>, <span class="py-src-keyword">lambda</span> <span class="py-src-variable">a</span>=<span class="py-src-variable">avatar</span>:<span class="py-src-variable">a</span>.<span class="py-src-variable">detached</span>(<span class="py-src-variable">mind</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">User</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">attached</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">mind</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">mind</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">detached</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">mind</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_joinGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">allowMattress</span>=<span class="py-src-parameter">True</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">server</span>.<span class="py-src-variable">joinGroup</span>(<span class="py-src-variable">groupname</span>, <span class="py-src-variable">self</span>, <span class="py-src-variable">allowMattress</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;print&quot;</span>, <span class="py-src-variable">message</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Group</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Viewable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">groupname</span>, <span class="py-src-parameter">allowMattress</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">groupname</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> = <span class="py-src-variable">allowMattress</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = []
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">addUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">view_send</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">from_user</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">allowMattress</span> <span class="py-src-keyword">and</span> <span class="py-src-variable">message</span>.<span class="py-src-variable">find</span>(<span class="py-src-string">&quot;mattress&quot;</span>) != -<span class="py-src-number">1</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">ValueError</span>, <span class="py-src-string">&quot;Don't say that word&quot;</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>:
+ <span class="py-src-variable">user</span>.<span class="py-src-variable">send</span>(<span class="py-src-string">&quot;&lt;%s&gt; says: %s&quot;</span> % (<span class="py-src-variable">from_user</span>.<span class="py-src-variable">name</span>, <span class="py-src-variable">message</span>))
+
+<span class="py-src-variable">realm</span> = <span class="py-src-variable">ChatRealm</span>()
+<span class="py-src-variable">realm</span>.<span class="py-src-variable">server</span> = <span class="py-src-variable">ChatServer</span>()
+<span class="py-src-variable">checker</span> = <span class="py-src-variable">checkers</span>.<span class="py-src-variable">InMemoryUsernamePasswordDatabaseDontUse</span>()
+<span class="py-src-variable">checker</span>.<span class="py-src-variable">addUser</span>(<span class="py-src-string">&quot;alice&quot;</span>, <span class="py-src-string">&quot;1234&quot;</span>)
+<span class="py-src-variable">checker</span>.<span class="py-src-variable">addUser</span>(<span class="py-src-string">&quot;bob&quot;</span>, <span class="py-src-string">&quot;secret&quot;</span>)
+<span class="py-src-variable">checker</span>.<span class="py-src-variable">addUser</span>(<span class="py-src-string">&quot;carol&quot;</span>, <span class="py-src-string">&quot;fido&quot;</span>)
+<span class="py-src-variable">p</span> = <span class="py-src-variable">portal</span>.<span class="py-src-variable">Portal</span>(<span class="py-src-variable">realm</span>, [<span class="py-src-variable">checker</span>])
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">p</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/chatserver.py"><span class="filename">listings/pb/chatserver.py</span></a></div></div>
+
+<p>Notice that the client uses <code>perspective_joinGroup</code> to both
+join a group and retrieve a <code>RemoteReference</code> to the
+<code>Group</code> object. However, the reference they get is actually to a
+special intermediate object called a <code>pb.ViewPoint</code>. When they do
+<code>group.callRemote(&quot;send&quot;, &quot;message&quot;)</code>, their avatar is inserted
+into the argument list that <code>Group.view_send</code> actually sees. This
+lets the group get their username out of the Avatar without giving the
+client an opportunity to spoof someone else.</p>
+
+<p>The client side code that joins a group and sends a message would look
+like this:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Client</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_print</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">message</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">message</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connect</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">login</span>(<span class="py-src-variable">credentials</span>.<span class="py-src-variable">UsernamePassword</span>(<span class="py-src-string">&quot;alice&quot;</span>, <span class="py-src-string">&quot;1234&quot;</span>),
+ <span class="py-src-variable">client</span>=<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">connected</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connected</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">perspective</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;connected, joining group #lookingForFourth&quot;</span>
+ <span class="py-src-comment"># this perspective is a reference to our User object</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">perspective</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;joinGroup&quot;</span>, <span class="py-src-string">&quot;#lookingForFourth&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">gotGroup</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">gotGroup</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">group</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;joined group, now sending a message to all members&quot;</span>
+ <span class="py-src-comment"># 'group' is a reference to the Group object (through a ViewPoint)</span>
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">group</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;send&quot;</span>, <span class="py-src-string">&quot;You can call me Al.&quot;</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">shutdown</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">shutdown</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">result</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+
+<span class="py-src-variable">Client</span>().<span class="py-src-variable">connect</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/chatclient.py"><span class="filename">listings/pb/chatclient.py</span></a></div></div>
+
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">Apparently Alice is one of those weirdos who has nothing
+better to do than to try and impersonate Bob. She will lie to her chat
+client, send incorrect objects to remote methods, even rewrite her local
+client code entirely to accomplish this juvenile prank. Given this
+adversarial relationship, one must wonder why she and Bob seem to spend so
+much time together: their adventures are clearly documented by the
+cryptographic literature.</span></a></li><li><a name="footnote-2"><span class="footnote">the
+obvious name is clearly
+<code>ServerSidePerUserObjectWhichNobodyElseHasAccessTo</code>, but because
+python makes everything else so easy to read, it only seems fair to make
+your audience work for <em>something</em></span></a></li><li><a name="footnote-3"><span class="footnote">We could just go ahead and rename Perspective Broker to be
+Avatar Broker, but 1) that would cause massive compatibility problems, and 2)
+<q>AB</q> doesn't fit into the whole sandwich-themed naming scheme nearly as
+well as <q>PB</q> does. If we changed it to AB, we'd probably have to change
+Banana to be CD (CoderDecoder), and Jelly to be EF (EncapsulatorFragmentor).
+twisted.spread would then have to be renamed twisted.alphabetsoup, and then
+the whole food-pun thing would start all over again.</span></a></li><li><a name="footnote-4"><span class="footnote">The avatar-ish class is named
+<code>pb.Avatar</code> because <code>pb.Perspective</code> was already
+taken, by the (now obsolete) oldcred perspective-ish class. It is a pity,
+but it simply wasn't possible both replace <code>pb.Perspective</code>
+in-place <em>and</em> maintain a reasonable level of
+backwards-compatibility.</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/pb-intro.html b/vendor/Twisted-10.0.0/doc/core/howto/pb-intro.html
new file mode 100644
index 0000000000..4b848641af
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/pb-intro.html
@@ -0,0 +1,320 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Introduction to Perspective Broker</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Introduction to Perspective Broker</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Object Roadmap</a></li><ul><li><a href="#auto2">Subclassing and Implementing</a></li></ul><li><a href="#auto3">Things you can Call Remotely</a></li><li><a href="#auto4">Things you can Copy Remotely</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p>Suppose you find yourself in control of both ends of the wire: you
+have two programs that need to talk to each other, and you get to use any
+protocol you want. If you can think of your problem in terms of objects that
+need to make method calls on each other, then chances are good that you can
+use twisted's Perspective Broker protocol rather than trying to shoehorn
+your needs into something like HTTP, or implementing yet another RPC
+mechanism<a href="#footnote-1" title="Most of Twisted is like this. Hell, most of unix is like this: if you think it would be useful, someone else has probably thought that way in the past, and acted on it, and you can take advantage of the tool they created to solve the same problem you're facing now."><super>1</super></a>.</p>
+
+<p>The Perspective Broker system (abbreviated <q>PB</q>, spawning numerous
+sandwich-related puns) is based upon a few central concepts:</p>
+
+<ul>
+
+ <li><em>serialization</em>: taking fairly arbitrary objects and types,
+ turning them into a chunk of bytes, sending them over a wire, then
+ reconstituting them on the other end. By keeping careful track of object
+ ids, the serialized objects can contain references to other objects and
+ the remote copy will still be useful. </li>
+
+ <li><em>remote method calls</em>: doing something to a local object and
+ causing a method to get run on a distant one. The local object is called a
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code>, and you
+ <q>do something</q> by running its <code>.callRemote</code> method.
+ </li>
+
+</ul>
+
+<p>This document will contain several examples that will (hopefully) appear
+redundant and verbose once you've figured out what's going on. To begin
+with, much of the code will just be labelled <q>magic</q>: don't worry about how
+these parts work yet. It will be explained more fully later.</p>
+
+<h2>Object Roadmap<a name="auto1"/></h2>
+
+<p>To start with, here are the major classes, interfaces, and
+functions involved in PB, with links to the file where they are
+defined (all of which are under twisted/, of course). Don't worry
+about understanding what they all do yet: it's easier to figure them
+out through their interaction than explaining them one at a time.</p>
+
+<ul>
+
+ <li><em><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">Factory</a></code></em>
+ : <code>internet/protocol.py</code></li>
+
+ <li><em><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.PBServerFactory.html" title="twisted.spread.pb.PBServerFactory">PBServerFactory</a></code></em>
+ : <code>spread/pb.py</code></li>
+
+ <li><em><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Broker.html" title="twisted.spread.pb.Broker">Broker</a></code></em>
+ : <code>spread/pb.py</code></li>
+
+</ul>
+
+<p>Other classes that are involved at some point:</p>
+
+<ul>
+
+ <li> <em><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code></em>
+ : <code>spread/pb.py</code> </li>
+
+ <li> <em><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code></em>
+ : <code>spread/pb.py</code>, actually defined as
+ <code>twisted.spread.flavors.Root</code>
+ in <code>spread/flavors.py</code> </li>
+
+ <li> <em><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code></em>
+ : <code>spread/pb.py</code>, actually defined as
+ <code>twisted.spread.flavors.Referenceable</code>
+ in <code>spread/flavors.py</code> </li>
+
+</ul>
+
+<p>Classes and interfaces that get involved when you start to care
+about authorization and security:</p>
+
+<ul>
+ <li><em><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.Portal.html" title="twisted.cred.portal.Portal">Portal</a></code></em>
+ : <code>cred/portal.py</code></li>
+
+ <li><em><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.IRealm.html" title="twisted.cred.portal.IRealm">IRealm</a></code></em>
+ : <code>cred/portal.py</code></li>
+
+ <li><em><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.IPerspective.html" title="twisted.spread.pb.IPerspective">IPerspective</a></code></em>
+ : <code>spread/pb.py</code>, which you will usually be interacting
+ with via pb.Avatar (a basic implementor of the interface).</li>
+</ul>
+
+<h3>Subclassing and Implementing<a name="auto2"/></h3>
+
+<p>Technically you can subclass anything you want, but technically you
+could also write a whole new framework, which would just waste a lot
+of time. Knowing which classes are useful to subclass or which
+interfaces to implement is one of the bits of knowledge that's crucial
+to using PB (and all of Twisted) successfully. Here are some hints to
+get started:</p>
+
+<ul>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>: you'll
+ subclass these to make remotely-referenceable objects (i.e., objects
+ which you can call methods on remotely) using PB. You don't need to
+ change any of the existing behavior, just inherit all of it and add
+ the remotely-accessible methods that you want to export.</li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Avatar.html" title="twisted.spread.pb.Avatar">pb.Avatar</a></code>: You'll
+ be subclassing this when you get into PB programming with
+ authorization. This is an implementor of IPerspective.</li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.checkers.ICredentialsChecker.html" title="twisted.cred.checkers.ICredentialsChecker">ICredentialsChecker</a></code>: Implement this if
+ you want to authenticate your users against some sort of data store:
+ i.e., an LDAP database, an RDBMS, etc. There are already a few
+ implementations of this for various back-ends in
+ twisted.cred.checkers.</li>
+
+</ul>
+
+
+
+<h2>Things you can Call Remotely<a name="auto3"/></h2>
+
+<p>At this writing, there are three <q>flavors</q> of objects that can
+be accessed remotely through <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code> objects. Each of these
+flavors has a rule for how the <code class="python">callRemote</code>
+message is transformed into a local method call on the server. In
+order to use one of these <q>flavors</q>, subclass them and name your
+published methods with the appropriate prefix.
+
+<ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.IPerspective.html" title="twisted.spread.pb.IPerspective">twisted.spread.pb.IPerspective</a></code> implementors
+
+ <p>This is the first interface we deal with. It is a <q>perspective</q>
+ onto your PB application. Perspectives are slightly special because
+ they are usually the first object that a given user can access in
+ your application (after they log on). A user should only receive a
+ reference to their <em>own</em> perspective. PB works hard to
+ verify, as best it can, that any method that can be called on a
+ perspective directly is being called on behalf of the user who is
+ represented by that perspective. (Services with unusual
+ requirements for <q>on behalf of</q>, such as simulations with the
+ ability to posess another player's avatar, are accomplished by
+ providing indirected access to another user's perspective.)
+
+ </p>
+
+ <p>Perspectives are not usually serialized as remote references, so
+ do not return an IPerspective-implementor directly. </p>
+
+ <p>The way most people will want to implement IPerspective is by
+ subclassing pb.Avatar. Remotely accessible methods on pb.Avatar
+ instances are named with the <code class="python">perspective_</code> prefix. </p>
+
+ </li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">twisted.spread.pb.Referenceable</a></code>
+
+ <p>Referenceable objects are the simplest kind of PB object. You can call
+ methods on them and return them from methods to provide access to other
+ objects' methods. </p>
+
+ <p>However, when a method is called on a Referenceable, it's not possible to
+ tell who called it.</p>
+
+ <p>Remotely accessible methods on Referenceables are named with the
+ <code class="python">remote_</code> prefix.</p>
+
+ </li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Viewable.html" title="twisted.spread.pb.Viewable">twisted.spread.pb.Viewable</a></code>
+
+ <p>Viewable objects are remotely referenceable objects which have the
+ additional requirement that it must be possible to tell who is calling them.
+ The argument list to a Viewable's remote methods is modified in order to
+ include the Perspective representing the calling user.</p>
+
+ <p>Remotely accessible methods on Viewables are named with the
+ <code class="python">view_</code> prefix.</p>
+
+ </li>
+
+</ul>
+
+</p>
+
+<h2>Things you can Copy Remotely<a name="auto4"/></h2>
+
+<p>In addition to returning objects that you can call remote methods on, you
+can return structured copies of local objects.</p>
+
+<p>There are 2 basic flavors that allow for copying objects remotely. Again,
+you can use these by subclassing them. In order to specify what state you want
+to have copied when these are serialized, you can either use the Python default
+<code class="python">__getstate__</code> or specialized method calls for that
+flavor.</p>
+
+<p>
+<ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">twisted.spread.pb.Copyable</a></code>
+
+ <p>This is the simpler kind of object that can be copied. Every time this
+ object is returned from a method or passed as an argument, it is serialized
+ and unserialized.</p>
+
+ <p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">Copyable</a></code>
+ provides a method you can override, <code class="py-prototype">getStateToCopyFor(perspective)</code>, which
+ allows you to decide what an object will look like for the
+ perspective who is requesting it. The <code class="python">perspective</code> argument will be the perspective
+ which is either passing an argument or returning a result an
+ instance of your Copyable class. </p>
+
+ <p>For security reasons, in order to allow a particular Copyable class to
+ actually be copied, you must declare a <code class="python">RemoteCopy</code>
+ handler for
+ that Copyable subclass. The easiest way to do this is to declare both in the
+ same module, like so:
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">flavors</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Foo</span>(<span class="py-src-parameter">flavors</span>.<span class="py-src-parameter">Copyable</span>):
+ <span class="py-src-keyword">pass</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">RemoteFoo</span>(<span class="py-src-parameter">flavors</span>.<span class="py-src-parameter">RemoteCopy</span>):
+ <span class="py-src-keyword">pass</span>
+<span class="py-src-variable">flavors</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">Foo</span>, <span class="py-src-variable">RemoteFoo</span>)
+</pre>
+
+ In this case, each time a Foo is copied between peers, a RemoteFoo will be
+ instantiated and populated with the Foo's state. If you do not do this, PB
+ will complain that there have been security violations, and it may close the
+ connection.
+ </p>
+
+ </li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">twisted.spread.pb.Cacheable</a></code>
+
+ <p>Let me preface this with a warning: Cacheable may be hard to understand.
+ The motivation for it may be unclear if you don't have some experience with
+ real-world applications that use remote method calling of some kind. Once
+ you understand why you need it, what it does will likely seem simple and
+ obvious, but if you get confused by this, forget about it and come back
+ later. It's possible to use PB without understanding Cacheable at all.
+ </p>
+
+ <p>Cacheable is a flavor which is designed to be copied only when necessary,
+ and updated on the fly as changes are made to it. When passed as an argument
+ or a return value, if a Cacheable exists on the side of the connection it is
+ being copied to, it will be referred to by ID and not copied.</p>
+
+ <p>Cacheable is designed to minimize errors involved in replicating an object
+ between multiple servers, especially those related to having stale
+ information. In order to do this, Cacheable automatically registers
+ observers and queries state atomically, together. You can override the
+ method <code class="py-prototype">getStateToCacheAndObserveFor(self,
+ perspective, observer)</code> in order to specify how your observers will be
+ stored and updated.
+ </p>
+
+ <p>Similar to
+ <code class="python">getStateToCopyFor</code>,
+ <code class="python">getStateToCacheAndObserveFor</code> gets passed a
+ perspective. It also gets passed an
+ <code class="python">observer</code>, which is a remote reference to a
+ <q>secret</q> fourth referenceable flavor:
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCache.html" title="twisted.spread.pb.RemoteCache">RemoteCache</a></code>.</p>
+
+ <p>A <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCache.html" title="twisted.spread.pb.RemoteCache">RemoteCache</a></code> is simply
+ the object that represents your
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">Cacheable</a></code> on the other side
+ of the connection. It is registered using the same method as
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">RemoteCopy</a></code>, above.
+ RemoteCache is different, however, in that it will be referenced by its peer.
+ It acts as a Referenceable, where all methods prefixed with
+ <code class="python">observe_</code> will be callable remotely. It is
+ recommended that your object maintain a list (note: library support for this
+ is forthcoming!) of observers, and update them using
+ <code class="python">callRemote</code> when the Cacheable changes in a way
+ that should be noticeable to its clients. </p>
+
+ <p>Finally, when all references to a
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">Cacheable</a></code> from a given
+ perspective are lost,
+ <code class="py-prototype">stoppedObserving(perspective, observer)</code>
+ will be called on the
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">Cacheable</a></code>, with the same
+ perspective/observer pair that <code>getStateToCacheAndObserveFor</code> was
+ originally called with. Any cleanup remote calls can be made there, as well
+ as removing the observer object from any lists which it was previously in.
+ Any further calls to this observer object will be invalid.</p>
+ </li>
+</ul>
+</p>
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">Most of Twisted is like this. Hell, most of
+unix is like this: if <em>you</em> think it would be useful, someone else has
+probably thought that way in the past, and acted on it, and you can take
+advantage of the tool they created to solve the same problem you're facing
+now.</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/pb-usage.html b/vendor/Twisted-10.0.0/doc/core/howto/pb-usage.html
new file mode 100644
index 0000000000..e6d1b0d6ed
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/pb-usage.html
@@ -0,0 +1,1158 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using Perspective Broker</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using Perspective Broker</h1>
+ <div class="toc"><ol><li><a href="#auto0">Basic Example</a></li><li><a href="#auto1">Complete Example</a></li><li><a href="#auto2">References can come back to you</a></li><li><a href="#auto3">References to client-side objects</a></li><li><a href="#auto4">Raising Remote Exceptions</a></li><li><a href="#auto5">Try/Except blocks and Failure.trap </a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Basic Example<a name="auto0"/></h2>
+
+<p>The first example to look at is a complete (although somewhat trivial)
+application. It uses <code>PBServerFactory()</code> on the server side, and
+<code>PBClientFactory()</code> on the client side.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echoer</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">st</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'echoing:'</span>, <span class="py-src-variable">st</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">st</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8789</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Echoer</span>()))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="../examples/pbsimple.py"><span class="filename">../examples/pbsimple.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">util</span>
+
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8789</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">d</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">object</span>: <span class="py-src-variable">object</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;echo&quot;</span>, <span class="py-src-string">&quot;hello network&quot;</span>))
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">echo</span>: <span class="py-src-string">'server echoed: '</span>+<span class="py-src-variable">echo</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">reason</span>: <span class="py-src-string">'error: '</span>+<span class="py-src-variable">str</span>(<span class="py-src-variable">reason</span>.<span class="py-src-variable">value</span>))
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">util</span>.<span class="py-src-variable">println</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="../examples/pbsimpleclient.py"><span class="filename">../examples/pbsimpleclient.py</span></a></div></div>
+
+<p>First we look at the server. This defines an Echoer class (derived from
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>), with a method called
+<code>remote_echo()</code>.
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code> objects (because of
+their inheritance of
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>, described
+later) can define methods with names of the form <code>remote_*</code>; a
+client which obtains a remote reference to that
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code> object will be able to
+invoke those methods.</p>
+
+<p>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>-ish object is
+given to a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.PBServerFactory.html" title="twisted.spread.pb.PBServerFactory">pb.PBServerFactory</a></code><code>()</code>. This is a
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">Factory</a></code> object like
+any other: the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">Protocol</a></code> objects it creates for new
+connections know how to speak the PB protocol. The object you give to
+<code>pb.PBServerFactory()</code> becomes the <q>root object</q>, which
+simply makes it available for the client to retrieve. The client may only
+request references to the objects you want to provide it: this helps you
+implement your security model. Because it is so common to export just a
+single object (and because a <code>remote_*</code> method on that one can
+return a reference to any other object you might want to give out), the
+simplest example is one where the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.PBServerFactory.html" title="twisted.spread.pb.PBServerFactory">PBServerFactory</a></code> is given the root object, and
+the client retrieves it.</p>
+
+<p>The client side uses
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.PBClientFactory.html" title="twisted.spread.pb.PBClientFactory">pb.PBClientFactory</a></code> to make a
+connection to a given port. This is a two-step process involving opening
+a TCP connection to a given host and port and requesting the root object
+using <code>.getRootObject()</code>.</p>
+
+<p>Because <code>.getRootObject()</code> has to wait until a network
+connection has been made and exchange some data, it may take a while,
+so it returns a Deferred, to which the gotObject() callback is
+attached. (See the documentation on <a href="defer.html" shape="rect">Deferring
+Execution</a> for a complete explanation of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>s). If and when the
+connection succeeds and a reference to the remote root object is
+obtained, this callback is run. The first argument passed to the
+callback is a remote reference to the distant root object. (you can
+give other arguments to the callback too, see the other parameters for
+<code>.addCallback()</code> and <code>.addCallbacks()</code>).</p>
+
+<p>The callback does:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">object</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;echo&quot;</span>, <span class="py-src-string">&quot;hello network&quot;</span>)
+</pre>
+
+<p>which causes the server's <code>.remote_echo()</code> method to be invoked.
+(running <code>.callRemote(&quot;boom&quot;)</code> would cause
+<code>.remote_boom()</code> to be run, etc). Again because of the delay
+involved, <code>callRemote()</code> returns a
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>. Assuming the
+remote method was run without causing an exception (including an attempt to
+invoke an unknown method), the callback attached to that
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> will be
+invoked with any objects that were returned by the remote method call.</p>
+
+<p>In this example, the server's <code>Echoer</code> object has a method
+invoked, <em>exactly</em> as if some code on the server side had done:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">echoer_object</span>.<span class="py-src-variable">remote_echo</span>(<span class="py-src-string">&quot;hello network&quot;</span>)
+</pre>
+
+<p>and from the definition of <code>remote_echo()</code> we see that this just
+returns the same string it was given: <q>hello network</q>.</p>
+
+<p>From the client's point of view, the remote call gets another <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> object instead of
+that string. <code>callRemote()</code> <em>always</em> returns a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>. This is why PB is
+described as a system for <q>translucent</q> remote method calls instead of
+<q>transparent</q> ones: you cannot pretend that the remote object is really
+local. Trying to do so (as some other RPC mechanisms do, coughCORBAcough)
+breaks down when faced with the asynchronous nature of the network. Using
+Deferreds turns out to be a very clean way to deal with the whole thing.</p>
+
+<p>The remote reference object (the one given to
+<code>getRootObject()</code>'s success callback) is an instance the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code> class. This means
+you can use it to invoke methods on the remote object that it refers to. Only
+instances of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code> are eligible for
+<code>.callRemote()</code>. The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code> object is the one that lives
+on the remote side (the client, in this case), not the local side (where the
+actual object is defined).</p>
+
+<p>In our example, the local object is that <code>Echoer()</code> instance,
+which inherits from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>,
+which inherits from
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>. It is that
+<code>Referenceable</code> class that makes the object eligible to be available
+for remote method calls<a href="#footnote-1" title="There are a few other classes that can bestow this ability, but pb.Referenceable is the easiest to understand; see 'flavors' below for details on the others."><super>1</super></a>. If you have
+an object that is Referenceable, then any client that manages to get a
+reference to it can invoke any <code>remote_*</code> methods they please.</p>
+
+<div class="note"><strong>Note: </strong>
+<p>The <em>only</em> thing they can do is invoke those
+methods. In particular, they cannot access attributes. From a security point
+of view, you control what they can do by limiting what the
+<code>remote_*</code> methods can do.</p>
+
+<p>Also note: the other classes like
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">Referenceable</a></code> allow access to
+other methods, in particular <code>perspective_*</code> and <code>view_*</code>
+may be accessed. Don't write local-only methods with these names, because then
+remote callers will be able to do more than you intended.</p>
+
+<p>Also also note: the other classes like
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code> <em>do</em> allow
+access to attributes, but you control which ones they can see.</p>
+</div>
+
+<p>You don't have to be a
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code> to be remotely callable,
+but you do have to be
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>. (Objects that
+inherit from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code>
+but not from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code> can be
+remotely called, but only
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Root.html" title="twisted.spread.pb.Root">pb.Root</a></code>-ish objects can be given
+to the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.PBServerFactory.html" title="twisted.spread.pb.PBServerFactory">PBServerFactory</a></code>.)</p>
+
+<h2>Complete Example<a name="auto1"/></h2>
+
+<p>Here is an example client and server which uses <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code> as a root object and as the
+result of a remotely exposed method. In each context, methods can be invoked
+on the exposed <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.Referenceable.html" title="twisted.spread.Referenceable">Referenceable</a></code>
+instance. In this example, the initial root object has a method that returns a
+reference to the second object.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Two</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_three</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Two.three was given&quot;</span>, <span class="py-src-variable">arg</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getTwo</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">two</span> = <span class="py-src-variable">Two</span>()
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;returning a Two called&quot;</span>, <span class="py-src-variable">two</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">two</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">One</span>()))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb1server.py"><span class="filename">listings/pb/pb1server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">got_obj1</span>, <span class="py-src-variable">err_obj1</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">err_obj1</span>(<span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;error getting first object&quot;</span>, <span class="py-src-variable">reason</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj1</span>(<span class="py-src-parameter">obj1</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got first object:&quot;</span>, <span class="py-src-variable">obj1</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to getTwo&quot;</span>
+ <span class="py-src-variable">def2</span> = <span class="py-src-variable">obj1</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;getTwo&quot;</span>)
+ <span class="py-src-variable">def2</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">got_obj2</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj2</span>(<span class="py-src-parameter">obj2</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got second object:&quot;</span>, <span class="py-src-variable">obj2</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;telling it to do three(12)&quot;</span>
+ <span class="py-src-variable">obj2</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;three&quot;</span>, <span class="py-src-number">12</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb1client.py"><span class="filename">listings/pb/pb1client.py</span></a></div></div>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.PBClientFactory.getRootObject.html" title="twisted.spread.pb.PBClientFactory.getRootObject">pb.PBClientFactory.getRootObject</a></code> will
+handle all the details of waiting for the creation of a connection.
+It returns a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>, which will have its
+callback called when the reactor connects to the remote server and
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.PBClientFactory.html" title="twisted.spread.pb.PBClientFactory">pb.PBClientFactory</a></code> gets the
+root, and have its <code class="python">errback</code> called when the
+object-connection fails for any reason, whether it was host lookup
+failure, connection refusal, or some server-side error.
+</p>
+
+<p>The root object has a method called <code>remote_getTwo</code>, which
+returns the <code>Two()</code> instance. On the client end, the callback gets
+a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code> to that
+instance. The client can then invoke two's <code>.remote_three()</code>
+method.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.html" title="twisted.spread.pb.RemoteReference">RemoteReference</a></code>
+objects have one method which is their purpose for being: <code class="python">callRemote</code>. This method allows you to call a
+remote method on the object being referred to by the Reference. <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.RemoteReference.callRemote.html" title="twisted.spread.pb.RemoteReference.callRemote">RemoteReference.callRemote</a></code>, like <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.PBClientFactory.getRootObject.html" title="twisted.spread.pb.PBClientFactory.getRootObject">pb.PBClientFactory.getRootObject</a></code>, returns
+a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>.
+When a response to the method-call being sent arrives, the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>'s <code class="python">callback</code> or <code class="python">errback</code>
+will be made, depending on whether an error occurred in processing the
+method call.</p>
+
+<p>You can use this technique to provide access to arbitrary sets of objects.
+Just remember that any object that might get passed <q>over the wire</q> must
+inherit from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">Referenceable</a></code>
+(or one of the other flavors). If you try to pass a non-Referenceable object
+(say, by returning one from a <code>remote_*</code> method), you'll get an
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.jelly.InsecureJelly.html" title="twisted.spread.jelly.InsecureJelly">InsecureJelly</a></code>
+exception<a href="#footnote-2" title="This can be overridden, by subclassing one of the Serializable flavors and defining custom serialization code for your class. See Passing Complex Types for details."><super>2</super></a>.</p>
+
+
+<h2>References can come back to you<a name="auto2"/></h2>
+
+<p>If your server gives a reference to a client, and then that client gives
+the reference back to the server, the server will wind up with the same
+object it gave out originally. The serialization layer watches for returning
+reference identifiers and turns them into actual objects. You need to stay
+aware of where the object lives: if it is on your side, you do actual method
+calls. If it is on the other side, you do
+<code>.callRemote()</code><a href="#footnote-3" title="The binary nature of this local vs. remote scheme works because you cannot give RemoteReferences to a third party. If you could, then your object A could go to B, B could give it to C, C might give it back to you, and you would be hard pressed to tell if the object lived in C's memory space, in B's, or if it was really your own object, tarnished and sullied after being handed down like a really ugly picture that your great aunt owned and which nobody wants but which nobody can bear to throw out. Ok, not really like that, but you get the idea."><super>3</super></a>.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Two</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_print</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;two.print was given&quot;</span>, <span class="py-src-variable">arg</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">two</span>):
+ <span class="py-src-comment">#pb.Root.__init__(self) # pb.Root doesn't implement __init__</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">two</span> = <span class="py-src-variable">two</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getTwo</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;One.getTwo(), returning my two called&quot;</span>, <span class="py-src-variable">two</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">two</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_checkTwo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">newtwo</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;One.checkTwo(): comparing my two&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">two</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;One.checkTwo(): against your two&quot;</span>, <span class="py-src-variable">newtwo</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">two</span> == <span class="py-src-variable">newtwo</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;One.checkTwo(): our twos are the same&quot;</span>
+
+
+<span class="py-src-variable">two</span> = <span class="py-src-variable">Two</span>()
+<span class="py-src-variable">root_obj</span> = <span class="py-src-variable">One</span>(<span class="py-src-variable">two</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">root_obj</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb2server.py"><span class="filename">listings/pb/pb2server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">foo</span> = <span class="py-src-variable">Foo</span>()
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>().<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">foo</span>.<span class="py-src-variable">step1</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-comment"># keeping globals around is starting to get ugly, so we use a simple class</span>
+<span class="py-src-comment"># instead. Instead of hooking one function to the next, we hook one method</span>
+<span class="py-src-comment"># to the next.</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Foo</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">oneRef</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">step1</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">obj</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got one object:&quot;</span>, <span class="py-src-variable">obj</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">oneRef</span> = <span class="py-src-variable">obj</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to getTwo&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">oneRef</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;getTwo&quot;</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">step2</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">step2</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">two</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got two object:&quot;</span>, <span class="py-src-variable">two</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;giving it back to one&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;one is&quot;</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">oneRef</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">oneRef</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;checkTwo&quot;</span>, <span class="py-src-variable">two</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb2client.py"><span class="filename">listings/pb/pb2client.py</span></a></div></div>
+
+<p>The server gives a <code>Two()</code> instance to the client, who then
+returns the reference back to the server. The server compares the <q>two</q>
+given with the <q>two</q> received and shows that they are the same, and that
+both are real objects instead of remote references.</p>
+
+<p>A few other techniques are demonstrated in <code>pb2client.py</code>. One
+is that the callbacks are are added with <code>.addCallback</code> instead
+of <code>.addCallbacks</code>. As you can tell from the <a href="defer.html" shape="rect">Deferred</a> documentation, <code>.addCallback</code> is a
+simplified form which only adds a success callback. The other is that to
+keep track of state from one callback to the next (the remote reference to
+the main One() object), we create a simple class, store the reference in an
+instance thereof, and point the callbacks at a sequence of bound methods.
+This is a convenient way to encapsulate a state machine. Each response kicks
+off the next method, and any data that needs to be carried from one state to
+the next can simply be saved as an attribute of the object.</p>
+
+<p>Remember that the client can give you back any remote reference you've
+given them. Don't base your zillion-dollar stock-trading clearinghouse
+server on the idea that you trust the client to give you back the right
+reference. The security model inherent in PB means that they can <em>only</em>
+give you back a reference that you've given them for the current connection
+(not one you've given to someone else instead, nor one you gave them last
+time before the TCP session went down, nor one you haven't yet given to the
+client), but just like with URLs and HTTP cookies, the particular reference
+they give you is entirely under their control.</p>
+
+
+<h2>References to client-side objects<a name="auto3"/></h2>
+
+<p>Anything that's Referenceable can get passed across the wire, <em>in
+either direction</em>. The <q>client</q> can give a reference to the
+<q>server</q>, and then the server can use .callRemote() to invoke methods on
+the client end. This fuzzes the distinction between <q>client</q> and
+<q>server</q>: the only real difference is who initiates the original TCP
+connection; after that it's all symmetric.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takeTwo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">two</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;received a Two called&quot;</span>, <span class="py-src-variable">two</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;telling it to print(12)&quot;</span>
+ <span class="py-src-variable">two</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;print&quot;</span>, <span class="py-src-number">12</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">One</span>()))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb3server.py"><span class="filename">listings/pb/pb3server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Two</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Referenceable</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_print</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Two.print() called with&quot;</span>, <span class="py-src-variable">arg</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">two</span> = <span class="py-src-variable">Two</span>()
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">def1</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">def1</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">got_obj</span>, <span class="py-src-variable">two</span>) <span class="py-src-comment"># hands our 'two' to the callback</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">obj</span>, <span class="py-src-parameter">two</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got One:&quot;</span>, <span class="py-src-variable">obj</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;giving it our two&quot;</span>
+ <span class="py-src-variable">obj</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;takeTwo&quot;</span>, <span class="py-src-variable">two</span>)
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/pb3client.py"><span class="filename">listings/pb/pb3client.py</span></a></div></div>
+
+<p>In this example, the client gives a reference to its own object to the
+server. The server then invokes a remote method on the client-side
+object.</p>
+
+
+<h2>Raising Remote Exceptions<a name="auto4"/></h2>
+
+<p>Everything so far has covered what happens when things go right. What
+about when they go wrong? The Python Way is to raise an exception of some
+sort. The Twisted Way is the same.</p>
+
+<p>The only special thing you do is to define your <code>Exception</code>
+subclass by deriving it from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Error.html" title="twisted.spread.pb.Error">pb.Error</a></code>. When any remotely-invokable method
+(like <code>remote_*</code> or <code>perspective_*</code>) raises a
+<code>pb.Error</code>-derived exception, a serialized form of that Exception
+object will be sent back over the wire<a href="#footnote-4" title="To be precise, the Failure will be sent if any exception is raised, not just pb.Error-derived ones. But the server will print ugly error messages if you raise ones that aren't derived from pb.Error."><super>4</super></a>. The other side (which
+did <code>callRemote</code>) will have the <q><code>errback</code></q>
+callback run with a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code> object that contains a copy of
+the exception object. This <code>Failure</code> object can be queried to
+retrieve the error message and a stack traceback.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code> is a
+special class, defined in <code>twisted/python/failure.py</code>, created to
+make it easier to handle asynchronous exceptions. Just as exception handlers
+can be nested, <code>errback</code> functions can be chained. If one errback
+can't handle the particular type of failure, it can be <q>passed along</q> to a
+errback handler further down the chain.</p>
+
+<p>For simple purposes, think of the <code>Failure</code> as just a container
+for remotely-thrown <code>Exception</code> objects. To extract the string that
+was put into the exception, use its <code>.getErrorMessage()</code> method.
+To get the type of the exception (as a string), look at its
+<code>.type</code> attribute. The stack traceback is available too. The
+intent is to let the errback function get just as much information about the
+exception as Python's normal <code>try:</code> clauses do, even though the
+exception occurred in somebody else's memory space at some unknown time in
+the past.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyError</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Error</span>):
+ <span class="py-src-string">&quot;&quot;&quot;This is an Expected Exception. Something bad happened.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyError2</span>(<span class="py-src-parameter">Exception</span>):
+ <span class="py-src-string">&quot;&quot;&quot;This is an Unexpected Exception. Something really bad happened.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_broken</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">msg</span> = <span class="py-src-string">&quot;fall down go boom&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;raising a MyError exception with data '%s'&quot;</span> % <span class="py-src-variable">msg</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">MyError</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_broken2</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">msg</span> = <span class="py-src-string">&quot;hadda owie&quot;</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;raising a MyError2 exception with data '%s'&quot;</span> % <span class="py-src-variable">msg</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">MyError2</span>(<span class="py-src-variable">msg</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">One</span>()))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/exc_server.py"><span class="filename">listings/pb/exc_server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">got_obj</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">obj</span>):
+ <span class="py-src-comment"># change &quot;broken&quot; into &quot;broken2&quot; to demonstrate an unhandled exception</span>
+ <span class="py-src-variable">d2</span> = <span class="py-src-variable">obj</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;broken&quot;</span>)
+ <span class="py-src-variable">d2</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">working</span>)
+ <span class="py-src-variable">d2</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">broken</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">working</span>():
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;erm, it wasn't *supposed* to work..&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">broken</span>(<span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got remote Exception&quot;</span>
+ <span class="py-src-comment"># reason should be a Failure (or subclass) holding the MyError exception</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; .__class__ =&quot;</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">__class__</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; .getErrorMessage() =&quot;</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">getErrorMessage</span>()
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; .type =&quot;</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">type</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/exc_client.py"><span class="filename">listings/pb/exc_client.py</span></a></div></div>
+
+<pre class="shell" xml:space="preserve">
+% ./exc_client.py
+got remote Exception
+ .__class__ = twisted.spread.pb.CopiedFailure
+ .getErrorMessage() = fall down go boom
+ .type = __main__.MyError
+Main loop terminated.
+</pre>
+
+<p>Oh, and what happens if you raise some other kind of exception? Something
+that <em>isn't</em> subclassed from <code>pb.Error</code>? Well, those are
+called <q>unexpected exceptions</q>, which make Twisted think that something
+has <em>really</em> gone wrong. These will raise an exception on the
+<em>server</em> side. This won't break the connection (the exception is
+trapped, just like most exceptions that occur in response to network
+traffic), but it will print out an unsightly stack trace on the server's
+stderr with a message that says <q>Peer Will Receive PB Traceback</q>, just
+as if the exception had happened outside a remotely-invokable method. (This
+message will go the current log target, if <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.startLogging.html" title="twisted.python.log.startLogging">log.startLogging</a></code> was used to redirect it). The
+client will get the same <code>Failure</code> object in either case, but
+subclassing your exception from <code>pb.Error</code> is the way to tell
+Twisted that you expect this sort of exception, and that it is ok to just
+let the client handle it instead of also asking the server to complain. Look
+at <code>exc_client.py</code> and change it to invoke <code>broken2()</code>
+instead of <code>broken()</code> to see the change in the server's
+behavior.</p>
+
+<p>If you don't add an <code>errback</code> function to the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>, then a remote
+exception will still send a <code>Failure</code> object back over, but it
+will get lodged in the <code>Deferred</code> with nowhere to go. When that
+<code>Deferred</code> finally goes out of scope, the side that did
+<code>callRemote</code> will emit a message about an <q>Unhandled error in
+Deferred</q>, along with an ugly stack trace. It can't raise an exception at
+that point (after all, the <code>callRemote</code> that triggered the
+problem is long gone), but it will emit a traceback. So be a good programmer
+and <em>always add <code>errback</code> handlers</em>, even if they are just
+calls to <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.err.html" title="twisted.python.log.err">log.err</a></code>.</p>
+
+<h2>Try/Except blocks and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.trap.html" title="twisted.python.failure.Failure.trap">Failure.trap</a></code> <a name="auto5"/></h2>
+
+<p>To implement the equivalent of the Python try/except blocks (which can
+trap particular kinds of exceptions and pass others <q>up</q> to
+higher-level <code>try/except</code> blocks), you can use the
+<code>.trap()</code> method in conjunction with multiple
+<code>errback</code> handlers on the <code>Deferred</code>. Re-raising an
+exception in an <code>errback</code> handler serves to pass that new
+exception to the next handler in the chain. The <code>trap</code> method is
+given a list of exceptions to look for, and will re-raise anything that
+isn't on the list. Instead of passing unhandled exceptions <q>up</q> to an
+enclosing <code>try</code> block, this has the effect of passing the
+exception <q>off</q> to later <code>errback</code> handlers on the same
+<code>Deferred</code>. The <code>trap</code> calls are used in chained
+errbacks to test for each kind of exception in sequence. </p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyException</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Error</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_fooMethod</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">arg</span> == <span class="py-src-string">&quot;panic!&quot;</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">MyException</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;response&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">One</span>()))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/trap_server.py"><span class="filename">listings/pb/trap_server.py</span></a></div></div>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyException</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Error</span>): <span class="py-src-keyword">pass</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyOtherException</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Error</span>): <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ScaryObject</span>:
+ <span class="py-src-comment"># not safe for serialization</span>
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">worksLike</span>(<span class="py-src-parameter">obj</span>):
+ <span class="py-src-comment"># the callback/errback sequence in class One works just like an</span>
+ <span class="py-src-comment"># asynchronous version of the following:</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">response</span> = <span class="py-src-variable">obj</span>.<span class="py-src-variable">callMethod</span>(<span class="py-src-variable">name</span>, <span class="py-src-variable">arg</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">DeadReferenceError</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; stale reference: the client disconnected or crashed&quot;</span>
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">jelly</span>.<span class="py-src-variable">InsecureJelly</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; InsecureJelly: you tried to send something unsafe to them&quot;</span>
+ <span class="py-src-keyword">except</span> (<span class="py-src-variable">MyException</span>, <span class="py-src-variable">MyOtherException</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; remote raised a MyException&quot;</span> <span class="py-src-comment"># or MyOtherException</span>
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; something else happened&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; method successful, response:&quot;</span>, <span class="py-src-variable">response</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">One</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">worked</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; method successful, response:&quot;</span>, <span class="py-src-variable">response</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">check_InsecureJelly</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
+ <span class="py-src-variable">failure</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">jelly</span>.<span class="py-src-variable">InsecureJelly</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; InsecureJelly: you tried to send something unsafe to them&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">check_MyException</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
+ <span class="py-src-variable">which</span> = <span class="py-src-variable">failure</span>.<span class="py-src-variable">trap</span>(<span class="py-src-variable">MyException</span>, <span class="py-src-variable">MyOtherException</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">which</span> == <span class="py-src-variable">MyException</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; remote raised a MyException&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; remote raised a MyOtherException&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">catch_everythingElse</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; something else happened&quot;</span>
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>(<span class="py-src-variable">failure</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">doCall</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">explanation</span>, <span class="py-src-parameter">arg</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">explanation</span>
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">deferred</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;fooMethod&quot;</span>, <span class="py-src-variable">arg</span>)
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">worked</span>)
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">check_InsecureJelly</span>)
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">check_MyException</span>)
+ <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">catch_everythingElse</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">DeadReferenceError</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot; stale reference: the client disconnected or crashed&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callOne</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">doCall</span>(<span class="py-src-string">&quot;callOne: call with safe object&quot;</span>, <span class="py-src-string">&quot;safe string&quot;</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callTwo</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">doCall</span>(<span class="py-src-string">&quot;callTwo: call with dangerous object&quot;</span>, <span class="py-src-variable">ScaryObject</span>())
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callThree</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">doCall</span>(<span class="py-src-string">&quot;callThree: call that raises remote exception&quot;</span>, <span class="py-src-string">&quot;panic!&quot;</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callShutdown</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;telling them to shut down&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;shutdown&quot;</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">callFour</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">doCall</span>(<span class="py-src-string">&quot;callFour: call on stale reference&quot;</span>, <span class="py-src-string">&quot;dummy&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">obj</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">obj</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">1</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">callOne</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">2</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">callTwo</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">3</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">callThree</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">4</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">callShutdown</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">callFour</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">6</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>)
+
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
+<span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">One</span>().<span class="py-src-variable">got_obj</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/pb/trap_client.py"><span class="filename">listings/pb/trap_client.py</span></a></div></div>
+
+<pre class="shell" xml:space="preserve">
+% ./trap_client.py
+callOne: call with safe object
+ method successful, response: response
+callTwo: call with dangerous object
+ InsecureJelly: you tried to send something unsafe to them
+callThree: call that raises remote exception
+ remote raised a MyException
+telling them to shut down
+callFour: call on stale reference
+ stale reference: the client disconnected or crashed
+%
+</pre>
+
+
+<p>In this example, <code>callTwo</code> tries to send an instance of a
+locally-defined class through <code>callRemote</code>. The default security
+model implemented by <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Jelly.html" title="twisted.spread.pb.Jelly">pb.Jelly</a></code>
+on the remote end will not allow unknown classes to be unserialized (i.e.
+taken off the wire as a stream of bytes and turned back into an object: a
+living, breathing instance of some class): one reason is that it does not
+know which local class ought to be used to create an instance that
+corresponds to the remote object<a href="#footnote-5" title="The naive approach of simply doing import SomeClass to match a remote caller who claims to have an object of type SomeClass could have nasty consequences for some modules that do significant operations in their __init__ methods (think telnetlib.Telnet(host='localhost', port='chargen'), or even more powerful classes that you have available in your server program). Allowing a remote entity to create arbitrary classes in your namespace is nearly equivalent to allowing them to run arbitrary code. The pb.InsecureJelly exception arises because the class being sent over the wire has not been registered with the serialization layer (known as jelly). The easiest way to make it possible to copy entire class instances over the wire is to have them inherit from pb.Copyable, and then to use setUnjellyableForClass(remoteClass, localClass) on the receiving side. See Passing Complex Types for an example."><super>5</super></a>.
+
+The receiving end of the connection gets to decide what to accept and what
+to reject. It indicates its disapproval by raising a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.InsecureJelly.html" title="twisted.spread.pb.InsecureJelly">pb.InsecureJelly</a></code> exception. Because it occurs
+at the remote end, the exception is returned to the caller asynchronously,
+so an <code>errback</code> handler for the associated <code>Deferred</code>
+is run. That errback receives a <code>Failure</code> which wraps the
+<code>InsecureJelly</code>.</p>
+
+
+<p>Remember that <code>trap</code> re-raises exceptions that it wasn't asked
+to look for. You can only check for one set of exceptions per errback
+handler: all others must be checked in a subsequent handler.
+<code>check_MyException</code> shows how multiple kinds of exceptions can be
+checked in a single errback: give a list of exception types to
+<code>trap</code>, and it will return the matching member. In this case, the
+kinds of exceptions we are checking for (<code>MyException</code> and
+<code>MyOtherException</code>) may be raised by the remote end: they inherit
+from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Error.html" title="twisted.spread.pb.Error">pb.Error</a></code>.</p>
+
+<p>The handler can return <code>None</code> to terminate processing of the
+errback chain (to be precise, it switches to the callback that follows the
+errback; if there is no callback then processing terminates). It is a good
+idea to put an errback that will catch everything (no <code>trap</code>
+tests, no possible chance of raising more exceptions, always returns
+<code>None</code>) at the end of the chain. Just as with regular <code>try:
+except:</code> handlers, you need to think carefully about ways in which
+your errback handlers could themselves raise exceptions. The extra
+importance in an asynchronous environment is that an exception that falls
+off the end of the <code>Deferred</code> will not be signalled until that
+<code>Deferred</code> goes out of scope, and at that point may only cause a
+log message (which could even be thrown away if <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.startLogging.html" title="twisted.python.log.startLogging">log.startLogging</a></code> is not used to point it at
+stdout or a log file). In contrast, a synchronous exception that is not
+handled by any other <code>except:</code> block will very visibly terminate
+the program immediately with a noisy stack trace.</p>
+
+<p><code>callFour</code> shows another kind of exception that can occur
+while using <code>callRemote</code>: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.DeadReferenceError.html" title="twisted.spread.pb.DeadReferenceError">pb.DeadReferenceError</a></code>. This one occurs when the
+remote end has disconnected or crashed, leaving the local side with a stale
+reference. This kind of exception happens to be reported right away (XXX: is
+this guaranteed? probably not), so must be caught in a traditional
+synchronous <code>try: except pb.DeadReferenceError</code> block. </p>
+
+<p>Yet another kind that can occur is a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.PBConnectionLost.html" title="twisted.spread.pb.PBConnectionLost">pb.PBConnectionLost</a></code> exception. This occurs
+(asynchronously) if the connection was lost while you were waiting for a
+<code>callRemote</code> call to complete. When the line goes dead, all
+pending requests are terminated with this exception. Note that you have no
+way of knowing whether the request made it to the other end or not, nor how
+far along in processing it they had managed before the connection was
+lost. XXX: explain transaction semantics, find a decent reference.</p>
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">There are a few other classes
+that can bestow this ability, but pb.Referenceable is the easiest to
+understand; see 'flavors' below for details on the others.</span></a></li><li><a name="footnote-2"><span class="footnote">This can be overridden, by subclassing one of
+the Serializable flavors and defining custom serialization code for your
+class. See <a href="pb-copyable.html" shape="rect">Passing Complex Types</a> for
+details.</span></a></li><li><a name="footnote-3"><span class="footnote">The binary nature of this
+local vs. remote scheme works because you cannot give RemoteReferences to a
+third party. If you could, then your object A could go to B, B could give it to
+C, C might give it back to you, and you would be hard pressed to tell if the
+object lived in C's memory space, in B's, or if it was really your own object,
+tarnished and sullied after being handed down like a really ugly picture that
+your great aunt owned and which nobody wants but which nobody can bear to throw
+out. Ok, not really like that, but you get the idea.</span></a></li><li><a name="footnote-4"><span class="footnote">To be precise,
+the Failure will be sent if <em>any</em> exception is raised, not just
+pb.Error-derived ones. But the server will print ugly error messages if you
+raise ones that aren't derived from pb.Error.</span></a></li><li><a name="footnote-5"><span class="footnote"><p>The naive approach of simply doing <code>import
+SomeClass</code> to match a remote caller who claims to have an object of
+type <q>SomeClass</q> could have nasty consequences for some modules that do
+significant operations in their <code>__init__</code> methods (think
+<code>telnetlib.Telnet(host='localhost', port='chargen')</code>, or even
+more powerful classes that you have available in your server program).
+Allowing a remote entity to create arbitrary classes in your namespace is
+nearly equivalent to allowing them to run arbitrary code.</p>
+
+<p>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.InsecureJelly.html" title="twisted.spread.pb.InsecureJelly">pb.InsecureJelly</a></code>
+exception arises because the class being sent over the wire has not been
+registered with the serialization layer (known as <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.jelly.html" title="twisted.spread.jelly">jelly</a></code>). The easiest way to make it possible to
+copy entire class instances over the wire is to have them inherit from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>, and then to use
+<code>setUnjellyableForClass(remoteClass, localClass)</code> on the
+receiving side. See <a href="pb-copyable.html" shape="rect">Passing Complex Types</a>
+for an example.</p></span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/pb.html b/vendor/Twisted-10.0.0/doc/core/howto/pb.html
new file mode 100644
index 0000000000..92d2f92795
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/pb.html
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Overview of Twisted Spread</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Overview of Twisted Spread</h1>
+ <div class="toc"><ol><li><a href="#auto0">Rationale</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p> Perspective Broker (affectionately known as <q>PB</q>) is an
+asynchronous, symmetric<a href="#footnote-1" title="There is a negotiation phase for banana with particular roles for listener and initiator, so it's not completely symmetric, but after the connection is fully established, the protocol is completely symmetrical."><super>1</super></a> network protocol for secure,
+remote method calls and transferring of objects. PB is <q>translucent, not
+transparent</q>, meaning that it is very visible and obvious to see the
+difference between local method calls and potentially remote method calls,
+but remote method calls are still extremely convenient to make, and it is
+easy to emulate them to have objects which work both locally and
+remotely.</p>
+
+<p>PB supports user-defined serialized data in return values, which can be
+either copied each time the value is returned, or <q>cached</q>: only copied
+once and updated by notifications.</p>
+
+<p>PB gets its name from the fact that access to objects is through a
+<q>perspective</q>. This means that when you are responding to a remote
+method call, you can establish who is making the call.</p>
+
+<h2>Rationale<a name="auto0"/></h2>
+
+<p>No other currently existing protocols have all the properties of PB at the
+same time. The particularly interesting combination of attributes, though, is
+that PB is flexible and lightweight, allowing for rapid development, while
+still powerful enough to do two-way method calls and user-defined data
+types.</p>
+
+<p>It is important to have these attributes in order to allow for a protocol
+which is extensible. One of the facets of this flexibility is that PB can
+integrate an arbitrary number of services could be aggregated over a single
+connection, as well as publish and call new methods on existing objects
+without restarting the server or client.</p>
+
+<h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">There is a negotiation phase
+for banana with particular roles for listener and initiator, so it's not
+<em>completely</em> symmetric, but after the connection is fully established,
+the protocol is completely symmetrical.</span></a></li></ol></div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/pclients.html b/vendor/Twisted-10.0.0/doc/core/howto/pclients.html
new file mode 100644
index 0000000000..0d95bc4b87
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/pclients.html
@@ -0,0 +1,364 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Managing Clients of Perspectives</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ <link href="http://twistedmatrix.com/users/acapnotic/" rel="author" title="Kevin Turner"/></head>
+
+ <body bgcolor="white">
+ <h1 class="title">Managing Clients of Perspectives</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Managing Avatars</a></li><li><a href="#auto2">Managing Clients</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Overview<a name="auto0"/></h2>
+
+<p>In all the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.pb.IPerspective.html" title="twisted.spread.pb.IPerspective">IPerspective</a></code>
+we have shown so far, we ignored the <code>mind</code> argument and created
+a new <code>Avatar</code> for every connection. This is usually an easy
+design choice, and it works well for simple cases.</p>
+
+<p>In more complicated cases, for example an <code>Avatar</code> that
+represents a player object which is persistent in the game universe,
+we will want connections from the same player to use the same
+<code>Avatar</code>.</p>
+
+<p>Another thing which is necessary in more complicated scenarios
+is notifying a player asynchronously. While it is possible, of
+course, to allow a player to call
+<code>perspective_remoteListener(referencable)</code> that would
+mean both duplication of code and a higher latency in logging in,
+both bad.</p>
+
+<p>In previous sections all realms looked to be identical.
+In this one we will show the usefulness of realms in accomplishing
+those two objectives.</p>
+
+<h2>Managing Avatars<a name="auto1"/></h2>
+
+<p>The simplest way to manage persistent avatars is to use a straight-forward
+caching mechanism:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleAvatar</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-variable">greetings</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_greet</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;%d&gt;hello %s&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CachingRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>: <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarId</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>]
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>] = <span class="py-src-variable">SimpleAvatar</span>(<span class="py-src-variable">avatarId</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">p</span>, <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+</pre>
+
+<p>This gives us a perspective which counts the number of greetings it
+sent its client. Implementing a caching strategy, as opposed to generating
+a realm with the correct avatars already in it, is usually easier. This
+makes adding new checkers to the portal, or adding new users to a checker
+database, transparent. Otherwise, careful synchronization is needed between
+the checker and avatar is needed (much like the synchronization between
+UNIX's <code>/etc/shadow</code> and <code>/etc/passwd</code>).</p>
+
+<p>Sometimes, however, an avatar will need enough per-connection state
+that it would be easier to generate a new avatar and cache something
+else. Here is an example of that:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Greeter</span>:
+ <span class="py-src-variable">greetings</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">hello</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;%d&gt;hello&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleAvatar</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-variable">greetings</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">greeter</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greeter</span> = <span class="py-src-variable">greeter</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_greet</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">greeter</span>.<span class="py-src-variable">hello</span>()+<span class="py-src-string">' '</span>+<span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CachingRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greeters</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>: <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarId</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">greeters</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">greeters</span>[<span class="py-src-variable">avatarId</span>]
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">greeters</span>[<span class="py-src-variable">avatarId</span>] = <span class="py-src-variable">Greeter</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">SimpleAvatar</span>(<span class="py-src-variable">avatarId</span>, <span class="py-src-variable">p</span>), <span class="py-src-keyword">lambda</span>:<span class="py-src-variable">None</span>
+</pre>
+
+<p>It might seem tempting to use this pattern to have an avatar which
+is notified of new connections. However, the problems here are twofold:
+it would lead to a thin class which needs to forward all of its methods,
+and it would be impossible to know when disconnections occur. Luckily,
+there is a better pattern:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleAvatar</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-variable">greetings</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">connections</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connect</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">connections</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">disconnect</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">connections</span> -= <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_greet</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;%d&gt;hello %s&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CachingRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span> = {}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>: <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarId</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>]
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>] = <span class="py-src-variable">SimpleAvatar</span>(<span class="py-src-variable">avatarId</span>)
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">connect</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">p</span>, <span class="py-src-variable">p</span>.<span class="py-src-variable">disconnect</span>
+</pre>
+
+<p>It is possible to use such a pattern to define an arbitrary limit for
+the number of concurrent connections:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleAvatar</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-variable">greetings</span> = <span class="py-src-number">0</span>
+ <span class="py-src-variable">connections</span> = <span class="py-src-number">0</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-variable">name</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connect</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">connections</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">disconnect</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">connections</span> -= <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_greet</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;%d&gt;hello %s&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">greetings</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CachingRealm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">max</span>=<span class="py-src-number">1</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">max</span> = <span class="py-src-variable">max</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>: <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">avatarId</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>]
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">avatars</span>[<span class="py-src-variable">avatarId</span>] = <span class="py-src-variable">SimpleAvatar</span>(<span class="py-src-variable">avatarId</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">p</span>.<span class="py-src-variable">connections</span> &gt;= <span class="py-src-variable">self</span>.<span class="py-src-variable">max</span>:
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">ValueError</span>(<span class="py-src-string">&quot;too many connections&quot;</span>)
+ <span class="py-src-variable">p</span>.<span class="py-src-variable">connect</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">p</span>, <span class="py-src-variable">p</span>.<span class="py-src-variable">disconnect</span>
+</pre>
+
+<h2>Managing Clients<a name="auto2"/></h2>
+
+<p>So far, all our realms have ignored the <code>mind</code> argument.
+In the case of PB, the <code>mind</code> is an object supplied by
+the remote login method -- usually, when it passes over the wire,
+it becomes a <code>pb.RemoteReference</code>. This object allows
+sending messages to the client as soon as the connection is established
+and authenticated.</p>
+
+<p>Here is a simple remote-clock application which shows the usefulness
+of the <code>mind</code> argument:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleAvatar</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Avatar</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">client</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TimerService</span>(<span class="py-src-number">1</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">telltime</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span>.<span class="py-src-variable">startService</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">client</span> = <span class="py-src-variable">client</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">telltime</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">client</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;notifyTime&quot;</span>, <span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>())
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">perspective_setperiod</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">period</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span>.<span class="py-src-variable">stopService</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TimerService</span>(<span class="py-src-variable">period</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">telltime</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span>.<span class="py-src-variable">startService</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">logout</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">s</span>.<span class="py-src-variable">stopService</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Realm</span>:
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">portal</span>.<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>: <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">SimpleAvatar</span>(<span class="py-src-variable">mind</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">pb</span>.<span class="py-src-variable">IPerspective</span>, <span class="py-src-variable">p</span>, <span class="py-src-variable">p</span>.<span class="py-src-variable">logout</span>
+</pre>
+
+<p>In more complicated situations, you might want to cache the avatars
+and give each one a set of <q>current clients</q> or something similar.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/plugin.html b/vendor/Twisted-10.0.0/doc/core/howto/plugin.html
new file mode 100644
index 0000000000..e902edf531
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/plugin.html
@@ -0,0 +1,292 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Twisted Plugin System</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Twisted Plugin System</h1>
+ <div class="toc"><ol><li><a href="#auto0">Writing Extensible Programs</a></li><li><a href="#auto1">Extending an Existing Program</a></li><li><a href="#auto2">Alternate Plugin Packages</a></li><li><a href="#auto3">Plugin Caching</a></li><li><a href="#auto4">Further Reading</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <p>The purpose of this guide is to describe the preferred way to
+ write extensible Twisted applications (and consequently, also to
+ describe how to extend applications written in such a way). This
+ extensibility is achieved through the definition of one or more
+ APIs and a mechanism for collecting code plugins which
+ implement this API to provide some additional functionality.
+ At the base of this system is the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.html" title="twisted.plugin">twisted.plugin</a></code> module.</p>
+
+ <p>Making an application extensible using the plugin system has
+ several strong advantages over other techniques:</p>
+
+ <ul>
+ <li>It allows third-party developers to easily enhance your
+ software in a way that is loosely coupled: only the plugin API
+ is required to remain stable.</li>
+
+ <li>It allows new plugins to be discovered flexibly. For
+ example, plugins can be loaded and saved when a program is first
+ run, or re-discovered each time the program starts up, or they
+ can be polled for repeatedly at runtime (allowing the discovery
+ of new plugins installed after the program has started).</li>
+ </ul>
+
+ <h2>Writing Extensible Programs<a name="auto0"/></h2>
+
+ <p>Taking advantage of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.html" title="twisted.plugin">twisted.plugin</a></code> is
+ a two step process:</p>
+
+ <ol>
+ <li>
+ <p>
+ Define an interface which plugins will be required to implement.
+ This is done using the zope.interface package in the same way one
+ would define an interface for any other purpose.
+ </p>
+
+ <p>
+ A convention for defining interfaces is do so in a file named like
+ <em>ProjectName/projectname/iprojectname.py</em>. The rest of this
+ document will follow that convention: consider the following
+ interface definition be in <code>Matsim/matsim/imatsim.py</code>, an
+ interface definition module for a hypothetical material simulation
+ package.
+ </p>
+ </li>
+
+ <li>
+ At one or more places in your program, invoke <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.getPlugins.html" title="twisted.plugin.getPlugins">twisted.plugin.getPlugins</a></code> and iterate over its
+ result.
+ </li>
+ </ol>
+
+ <p>
+ As an example of the first step, consider the following interface
+ definition for a physical modelling system.
+ </p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">Attribute</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IMaterial</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ An object with specific physical properties
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">yieldStress</span>(<span class="py-src-parameter">temperature</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Returns the pressure this material can support without
+ fracturing at the given temperature.
+
+ @type temperature: C{float}
+ @param temperature: Kelvins
+
+ @rtype: C{float}
+ @return: Pascals
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-variable">dielectricConstant</span> = <span class="py-src-variable">Attribute</span>(<span class="py-src-string">&quot;&quot;&quot;
+ @type dielectricConstant: C{complex}
+ @ivar dielectricConstant: The relative permittivity, with the
+ real part giving reflective surface properties and the
+ imaginary part giving the radio absorption coefficient.
+ &quot;&quot;&quot;</span>)
+</pre>
+
+ <p>In another module, we might have a function that operates on
+ objects providing the <code>IMaterial</code> interface:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">displayMaterial</span>(<span class="py-src-parameter">m</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'A material with yield stress %s at 500 K'</span> % (<span class="py-src-variable">m</span>.<span class="py-src-variable">yieldStress</span>(<span class="py-src-number">500</span>),)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Also a dielectric constant of %s.'</span> % (<span class="py-src-variable">m</span>.<span class="py-src-variable">dielectricConstant</span>,)
+</pre>
+
+ <p>The last piece of required code is that which collects
+ <code>IMaterial</code> providers and passes them to the
+ <code>displayMaterial</code> function.</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">getPlugins</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">matsim</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">imatsim</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">displayAllKnownMaterials</span>():
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">material</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">getPlugins</span>(<span class="py-src-variable">imatsim</span>.<span class="py-src-variable">IMaterial</span>):
+ <span class="py-src-variable">displayMaterial</span>(<span class="py-src-variable">material</span>)
+</pre>
+
+ <p>Third party developers may now contribute different materials
+ to be used by this modelling system by implementing one or more
+ plugins for the <code>IMaterial</code> interface.</p>
+
+ <h2>Extending an Existing Program<a name="auto1"/></h2>
+
+ <p>The above code demonstrates how an extensible program might be
+ written using Twisted's plugin system. How do we write plugins
+ for it, though? Essentially, we create objects which provide the
+ required interface and then make them available at a particular
+ location. Consider the following example.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">matsim</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">imatsim</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SimpleMaterial</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPlugin</span>, <span class="py-src-variable">imatsim</span>.<span class="py-src-variable">IMaterial</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">yieldStressFactor</span>, <span class="py-src-parameter">dielectricConstant</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_yieldStressFactor</span> = <span class="py-src-variable">yieldStressFactor</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">dielectricConstant</span> = <span class="py-src-variable">dielectricConstant</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">yieldStress</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">temperature</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">_yieldStressFactor</span> * <span class="py-src-variable">temperature</span>
+
+<span class="py-src-variable">steelPlate</span> = <span class="py-src-variable">SimpleMaterial</span>(<span class="py-src-number">2.06842719e11</span>, <span class="py-src-number">2.7</span> + <span class="py-src-number">0.2j</span>)
+<span class="py-src-variable">brassPlate</span> = <span class="py-src-variable">SimpleMaterial</span>(<span class="py-src-number">1.03421359e11</span>, <span class="py-src-number">1.4</span> + <span class="py-src-number">0.5j</span>)
+</pre>
+
+ <p><code>steelPlate</code> and <code>brassPlate</code> now provide both
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.IPlugin.html" title="twisted.plugin.IPlugin">IPlugin</a></code> and <code>IMaterial</code>.
+ All that remains is to make this module available at an appropriate
+ location. For this, there are two options. The first of these is
+ primarily useful during development: if a directory which
+ has been added to <code>sys.path</code> (typically by adding it to the
+ <code class="shell">PYTHONPATH</code> environment variable) contains a
+ <em>directory</em> named <code class="shell">twisted/plugins/</code>,
+ each <code class="shell">.py</code> file in that directory will be loaded
+ as a source of plugins. This directory <em>must not</em> be a Python
+ package: including <code class="shell">__init__.py</code> will cause the
+ directory to be skipped and no plugins loaded from it. Second, each
+ module in the installed version of Twisted's <code class="shell">
+ twisted.plugins</code> package will also be loaded as a source of
+ plugins.</p>
+
+ <p>Once this plugin is installed in one of these two ways,
+ <code>displayAllKnownMaterials</code> can be run and we will see
+ two pairs of output: one for a steel plate and one for a brass
+ plate.</p>
+
+ <h2>Alternate Plugin Packages<a name="auto2"/></h2>
+
+ <p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.getPlugins.html" title="twisted.plugin.getPlugins">getPlugins</a></code> takes one
+ additional argument not mentioned above. If passed in, the 2nd argument
+ should be a module or package to be used instead of
+ <code>twisted.plugins</code> as the plugin meta-package. If you
+ are writing a plugin for a Twisted interface, you should never
+ need to pass this argument. However, if you have developed an
+ interface of your own, you may want to mandate that plugins for it
+ are installed in your own plugins package, rather than in
+ Twisted's. In this case, you probably also want to support <code class="shell">yourproject/plugins/</code> directories for ease of
+ development. To do so, you should make the <code class="shell">__init__.py</code> for that package contain at least
+ the following lines.</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pluginPackagePaths</span>
+<span class="py-src-variable">__path__</span>.<span class="py-src-variable">extend</span>(<span class="py-src-variable">pluginPackagePaths</span>(<span class="py-src-variable">__name__</span>))
+<span class="py-src-variable">__all__</span> = []
+</pre>
+
+ <p>The key behavior here is that interfaces are essentially paired
+ with a particular plugin package. If plugins are installed in a
+ different package than the one the code which relies on the
+ interface they provide, they will not be found when the
+ application goes to load them.</p>
+
+ <h2>Plugin Caching<a name="auto3"/></h2>
+
+ <p>In the course of using the Twisted plugin system, you may
+ notice <code class="shell">dropin.cache</code> files appearing at
+ various locations. These files are used to cache information
+ about what plugins are present in the directory which contains
+ them. At times, this cached information may become out of date.
+ Twisted uses the mtimes of various files involved in the plugin
+ system to determine when this cache may have become invalid.
+ Twisted will try to re-write the cache each time it tries to use
+ it but finds it out of date.</p>
+
+ <p>For a site-wide install, it may not (indeed, should not) be
+ possible for applications running as normal users to rewrite the
+ cache file. While these applications will still run and find
+ correct plugin information, they may run more slowly than they
+ would if the cache was up to date, and they may also report
+ exceptions if certain plugins have been removed but which the
+ cache still references. For these reasons, when installing or
+ removing software which provides Twisted plugins, the site
+ administrator should be sure the cache is regenerated.
+ Well-behaved package managers for such software should take this
+ task upon themselves, since it is trivially automatable. The
+ canonical way to regenerate the cache is to run the following
+ Python code:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>, <span class="py-src-variable">getPlugins</span>
+<span class="py-src-variable">list</span>(<span class="py-src-variable">getPlugins</span>(<span class="py-src-variable">IPlugin</span>))
+</pre>
+
+ <p>As mentioned, it is normal for exceptions to be raised
+ <strong>once</strong> here if plugins have been removed.</p>
+
+ <h2>Further Reading<a name="auto4"/></h2>
+
+ <ul>
+
+ <li><a href="components.html" shape="rect">Components: Interfaces and Adapters</a></li>
+
+ </ul>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/process.html b/vendor/Twisted-10.0.0/doc/core/howto/process.html
new file mode 100644
index 0000000000..a5c7146680
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/process.html
@@ -0,0 +1,725 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using Processes</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using Processes</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Running Another Process</a></li><li><a href="#auto2">Writing a ProcessProtocol</a></li><li><a href="#auto3">Things that can happen to your ProcessProtocol</a></li><li><a href="#auto4">Things you can do from your ProcessProtocol</a></li><li><a href="#auto5">Verbose Example</a></li><li><a href="#auto6">Doing it the Easy Way</a></li><li><a href="#auto7">Mapping File Descriptors</a></li><ul><li><a href="#auto8">ProcessProtocols with extra file descriptors</a></li><li><a href="#auto9">Examples</a></li></ul></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+<p>Along with connection to servers across the internet, Twisted also
+connects to local processes with much the same API. The API is described in
+more detail in the documentation of:
+<ul>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorProcess.html" title="twisted.internet.interfaces.IReactorProcess">twisted.internet.interfaces.IReactorProcess</a></code></li>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IProcessTransport.html" title="twisted.internet.interfaces.IProcessTransport">twisted.internet.interfaces.IProcessTransport</a></code></li>
+<li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IProcessProtocol.html" title="twisted.internet.interfaces.IProcessProtocol">twisted.internet.interfaces.IProcessProtocol</a></code></li>
+</ul>
+</p>
+
+ <h2>Running Another Process<a name="auto1"/></h2>
+
+<p>Processes are run through the reactor,
+using <code>reactor.spawnProcess</code>. Pipes are created to the child process,
+and added to the reactor core so that the application will not block while
+sending data into or pulling data out of the new
+process. <code>reactor.spawnProcess</code> requires two arguments,
+processProtocol and executable, and optionally takes several more: arguments,
+environment, path, userID, groupID, usePTY, and childFDs. Not all of these are
+available on Windows.</p>
+
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">processProtocol</span> = <span class="py-src-variable">MyProcessProtocol</span>()
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">spawnProcess</span>(<span class="py-src-variable">processProtocol</span>, <span class="py-src-variable">executable</span>, <span class="py-src-variable">args</span>=[<span class="py-src-variable">program</span>, <span class="py-src-variable">arg1</span>, <span class="py-src-variable">arg2</span>],
+ <span class="py-src-variable">env</span>={<span class="py-src-string">'HOME'</span>: <span class="py-src-variable">os</span>.<span class="py-src-variable">environ</span>[<span class="py-src-string">'HOME'</span>]}, <span class="py-src-variable">path</span>,
+ <span class="py-src-variable">uid</span>, <span class="py-src-variable">gid</span>, <span class="py-src-variable">usePTY</span>, <span class="py-src-variable">childFDs</span>)
+</pre>
+
+<ul>
+
+ <li><code>processProtocol</code> should be an instance of a subclass of
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.ProcessProtocol.html" title="twisted.internet.protocol.ProcessProtocol">twisted.internet.protocol.ProcessProtocol</a></code>. The
+ interface is described below.</li>
+
+ <li><code>executable</code> is the full path of the program to run. It
+ will be connected to processProtocol.</li>
+
+ <li><code>args</code> is a list of command line arguments to be passed to
+ the process. <code>args[0]</code> should be the name of the process.</li>
+
+ <li><code>env</code> is a dictionary containing the environment to pass
+ through to the process.</li>
+
+ <li><code>path</code> is the directory to run the process in. The child
+ will switch to the given directory just before starting the new program.
+ The default is to stay in the current directory.</li>
+
+ <li><code>uid</code> and <code>gid</code> are the user ID and group ID to
+ run the subprocess as. Of course, changing identities will be more likely
+ to succeed if you start as root.</li>
+
+ <li><code>usePTY</code> specifies whether the child process should be run
+ with a pty, or if it should just get a pair of pipes. Whether a program
+ needs to be run with a PTY or not depends on the particulars of that
+ program. Often, programs which primarily interact with users via a terminal
+ do need a PTY.</li>
+
+ <li><code>childFDs</code> lets you specify how the child's file
+ descriptors should be set up. Each key is a file descriptor number (an
+ integer) as seen by the child. 0, 1, and 2 are usually stdin, stdout, and
+ stderr, but some programs may be instructed to use additional fds through
+ command-line arguments or environment variables. Each value is either an
+ integer specifying one of the parent's current file descriptors, the
+ string <q>r</q> which creates a pipe that the parent can read from, or the
+ string <q>w</q> which creates a pipe that the parent can write to. If
+ <code>childFDs</code> is not provided, a default is used which creates the
+ usual stdin-writer, stdout-reader, and stderr-reader pipes.</li>
+
+</ul>
+
+<p><code>args</code> and <code>env</code> have empty default values, but
+many programs depend upon them to be set correctly. At the very least,
+<code>args[0]</code> should probably be the same as <code>executable</code>.
+If you just provide <code>os.environ</code> for <code>env</code>, the child
+program will inherit the environment from the current process, which is
+usually the civilized thing to do (unless you want to explicitly clean the
+environment as a security precaution). The default is to give an empty
+<code>env</code> to the child.</p>
+
+<p><code>reactor.spawnProcess</code> returns an instance that
+implements <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.
+.html" title="twisted.internet.interfaces.
+">
+IProcessTransport</a></code>.</p>
+
+ <h2>Writing a ProcessProtocol<a name="auto2"/></h2>
+
+<p>The ProcessProtocol you pass to spawnProcess is your interaction with the
+process. It has a very similar signature to a regular Protocol, but it has
+several extra methods to deal with events specific to a process. In our
+example, we will interface with 'wc' to create a word count of user-given
+text. First, we'll start by importing the required modules, and writing the
+initialization for our ProcessProtocol.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">WCProcessProtocol</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ProcessProtocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">text</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">text</span> = <span class="py-src-variable">text</span>
+</pre>
+
+<p>When the ProcessProtocol is connected to the protocol, it has the
+connectionMade method called. In our protocol, we will write our text to the
+standard input of our process and then close standard input, to the let the
+process know we are done writing to it.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">text</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">closeStdin</span>()
+</pre>
+
+<p>At this point, the process has receieved the data, and it's time for us
+to read the results. Instead of being received in <code>dataReceived</code>,
+data from standard output is received in <code>outReceived</code>. This is
+to distinguish it from data on standard error.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">outReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">fieldLength</span> = <span class="py-src-variable">len</span>(<span class="py-src-variable">data</span>) / <span class="py-src-number">3</span>
+ <span class="py-src-variable">lines</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">data</span>[:<span class="py-src-variable">fieldLength</span>])
+ <span class="py-src-variable">words</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">data</span>[<span class="py-src-variable">fieldLength</span>:<span class="py-src-variable">fieldLength</span>*<span class="py-src-number">2</span>])
+ <span class="py-src-variable">chars</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">data</span>[<span class="py-src-variable">fieldLength</span>*<span class="py-src-number">2</span>:])
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">receiveCounts</span>(<span class="py-src-variable">lines</span>, <span class="py-src-variable">words</span>, <span class="py-src-variable">chars</span>)
+</pre>
+
+<p>Now, the process has parsed the output, and ended the connection to the
+process. Then it sends the results on to the final method, receiveCounts.
+This is for users of the class to override, so as to do other things with
+the data. For our demonstration, we will just print the results.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">receiveCounts</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">lines</span>, <span class="py-src-parameter">words</span>, <span class="py-src-parameter">chars</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Received counts from wc.'</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Lines:'</span>, <span class="py-src-variable">lines</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Words:'</span>, <span class="py-src-variable">words</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Characters:'</span>, <span class="py-src-variable">chars</span>
+</pre>
+
+<p>We're done! To use our WCProcessProtocol, we create an instance, and pass
+it to spawnProcess.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-variable">wcProcess</span> = <span class="py-src-variable">WCProcessProtocol</span>(<span class="py-src-string">&quot;accessing protocols through Twisted is fun!\n&quot;</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">spawnProcess</span>(<span class="py-src-variable">wcProcess</span>, <span class="py-src-string">'wc'</span>, [<span class="py-src-string">'wc'</span>])
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+
+<h2>Things that can happen to your ProcessProtocol<a name="auto3"/></h2>
+
+<p>These are the methods that you can usefully override in your subclass of
+<code>ProcessProtocol</code>:</p>
+
+<ul>
+
+ <li><code>.connectionMade()</code>: This is called when the program is
+ started, and makes a good place to write data into the stdin pipe (using
+ <code class="python">self.transport.write</code>).</li>
+
+ <li><code>.outReceived(data)</code>: This is called with data that was
+ received from the process' stdout pipe. Pipes tend to provide data in
+ larger chunks than sockets (one kilobyte is a common buffer size), so you
+ may not experience the <q>random dribs and drabs</q> behavior typical of
+ network sockets, but regardless you should be prepared to deal if you
+ don't get all your data in a single call. To do it properly,
+ <code>outReceived</code> ought to simply accumulate the data and put off
+ doing anything with it until the process has finished.</li>
+
+ <li><code>.errReceived(data)</code>: This is called with data from the
+ process' stderr pipe. It behaves just like <code>outReceived</code>.</li>
+
+ <li><code>.inConnectionLost</code>: This is called when the reactor notices
+ that the process' stdin pipe has closed. Programs don't typically close
+ their own stdin, so this will probably get called when your
+ ProcessProtocol has shut down the write side with <code class="python">self.transport.loseConnection</code>.</li>
+
+ <li><code>.outConnectionLost</code>: This is called when the program closes
+ its stdout pipe. This usually happens when the program terminates.</li>
+
+ <li><code>.errConnectionLost</code>: Same as
+ <code>outConnectionLost</code>, but for stderr instead of stdout.</li>
+
+ <li><code>.processExited(status)</code>: This is called when the child
+ process has been reaped, and receives information about the process' exit
+ status. The status is passed in the form of a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code> instance, created with a
+ <code>.value</code> that either holds a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.error.ProcessDone.html" title="twisted.internet.error.ProcessDone">ProcessDone</a></code> object if the process
+ terminated normally (it died of natural causes instead of receiving a
+ signal, and if the exit code was 0), or a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.error.ProcessTerminated.html" title="twisted.internet.error.ProcessTerminated">ProcessTerminated</a></code> object (with an
+ <code>.exitCode</code> attribute) if something went wrong.</li>
+
+ <li><code>.processEnded(status)</code>: This is called when all the file
+ descriptors associated with the child process have been closed and the
+ process has been reaped. This means it is the last callback which will be
+ made onto a <code>ProcessProtocol</code>. The <code>status</code> parameter
+ has the same meaning as it does for <code>processExited</code>.</li>
+
+</ul>
+
+<p>The base-class definitions of most of these functions are no-ops. This will
+result in all stdout and stderr being thrown away. Note that it is important
+for data you don't care about to be thrown away: if the pipe were not read,
+the child process would eventually block as it tried to write to a full
+pipe.</p>
+
+
+<h2>Things you can do from your ProcessProtocol<a name="auto4"/></h2>
+
+<p>The following are the basic ways to control the child process:</p>
+
+<ul>
+
+ <li><code>self.transport.write(data)</code>: Stuff some data in the stdin
+ pipe. Note that this <code>write</code> method will queue any data that can't
+ be written immediately. Writing will resume in the future when the pipe
+ becomes writable again.</li>
+
+ <li><code>self.transport.closeStdin</code>: Close the stdin pipe. Programs
+ which act as filters (reading from stdin, modifying the data, writing to
+ stdout) usually take this as a sign that they should finish their job and
+ terminate. For these programs, it is important to close stdin when you're
+ done with it, otherwise the child process will never quit.</li>
+
+ <li><code>self.transport.closeStdout</code>: Not usually called, since you're
+ putting the process into a state where any attempt to write to stdout will
+ cause a SIGPIPE error. This isn't a nice thing to do to the poor
+ process.</li>
+
+ <li><code>self.transport.closeStderr</code>: Not usually called, same reason
+ as <code>closeStdout</code>.</li>
+
+ <li><code>self.transport.loseConnection</code>: Close all three pipes.</li>
+
+ <li><code>self.transport.signalProcess('KILL')</code>: Kill the child
+ process. This will eventually result in <code>processEnded</code> being
+ called.</li>
+
+</ul>
+
+
+<h2>Verbose Example<a name="auto5"/></h2>
+
+<p>Here is an example that is rather verbose about exactly when all the
+methods are called. It writes a number of lines into the <code>wc</code>
+program and then parses the output.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-comment"># Copyright (c) 2009-2010 Twisted Matrix Laboratories.</span>
+<span class="py-src-comment"># See LICENSE for details.</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">re</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyPP</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ProcessProtocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">verses</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">verses</span> = <span class="py-src-variable">verses</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">data</span> = <span class="py-src-string">&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;connectionMade!&quot;</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">i</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">range</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">verses</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Aleph-null bottles of beer on the wall,\n&quot;</span> +
+ <span class="py-src-string">&quot;Aleph-null bottles of beer,\n&quot;</span> +
+ <span class="py-src-string">&quot;Take one down and pass it around,\n&quot;</span> +
+ <span class="py-src-string">&quot;Aleph-null bottles of beer on the wall.\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">closeStdin</span>() <span class="py-src-comment"># tell them we're done</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">outReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;outReceived! with %d bytes!&quot;</span> % <span class="py-src-variable">len</span>(<span class="py-src-variable">data</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">data</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">data</span> + <span class="py-src-variable">data</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">errReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errReceived! with %d bytes!&quot;</span> % <span class="py-src-variable">len</span>(<span class="py-src-variable">data</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">inConnectionLost</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;inConnectionLost! stdin is closed! (we probably did it)&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">outConnectionLost</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;outConnectionLost! The child closed their stdout!&quot;</span>
+ <span class="py-src-comment"># now is the time to examine what they wrote</span>
+ <span class="py-src-comment">#print &quot;I saw them write:&quot;, self.data</span>
+ (<span class="py-src-variable">dummy</span>, <span class="py-src-variable">lines</span>, <span class="py-src-variable">words</span>, <span class="py-src-variable">chars</span>, <span class="py-src-variable">file</span>) = <span class="py-src-variable">re</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">r'\s+'</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">data</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I saw %s lines&quot;</span> % <span class="py-src-variable">lines</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">errConnectionLost</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;errConnectionLost! The child closed their stderr.&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">processExited</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;processExited, status %d&quot;</span> % (<span class="py-src-variable">reason</span>.<span class="py-src-variable">value</span>.<span class="py-src-variable">exitCode</span>,)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">processEnded</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;processEnded, status %d&quot;</span> % (<span class="py-src-variable">reason</span>.<span class="py-src-variable">value</span>.<span class="py-src-variable">exitCode</span>,)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;quitting&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">pp</span> = <span class="py-src-variable">MyPP</span>(<span class="py-src-number">10</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">spawnProcess</span>(<span class="py-src-variable">pp</span>, <span class="py-src-string">&quot;wc&quot;</span>, [<span class="py-src-string">&quot;wc&quot;</span>], {})
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/process/process.py"><span class="filename">listings/process/process.py</span></a></div></div>
+
+<p>The exact output of this program depends upon the relative timing of some
+un-synchronized events. In particular, the program may observe the child
+process close its stderr pipe before or after it reads data from the stdout
+pipe. One possible transcript would look like this:</p>
+
+<pre class="shell" xml:space="preserve">
+% ./process.py
+connectionMade!
+inConnectionLost! stdin is closed! (we probably did it)
+errConnectionLost! The child closed their stderr.
+outReceived! with 24 bytes!
+outConnectionLost! The child closed their stdout!
+I saw 40 lines
+processEnded, status 0
+quitting
+Main loop terminated.
+%
+</pre>
+
+<h2>Doing it the Easy Way<a name="auto6"/></h2>
+
+<p>Frequently, one just needs a simple way to get all the output from a
+program. In the blocking world, you might use <code class="python">commands.getoutput</code> from the standard library, but
+using that in an event-driven program will cause everything else to stall
+until the command finishes. (in addition, the SIGCHLD handler used by that
+function does not play well with Twisted's own signal handling). For these
+cases, the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.utils.getProcessOutput.html" title="twisted.internet.utils.getProcessOutput">twisted.internet.utils.getProcessOutput</a></code>
+function can be used. Here is a simple example:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">utils</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">failure</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">cStringIO</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">StringIO</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FortuneQuoter</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-variable">fortune</span> = <span class="py-src-string">'/usr/games/fortune'</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">output</span> = <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessOutput</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">fortune</span>)
+ <span class="py-src-variable">output</span>.<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">writeResponse</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">noResponse</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">resp</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">resp</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">noResponse</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">err</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">Factory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FortuneQuoter</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">10999</span>, <span class="py-src-variable">f</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/process/quotes.py"><span class="filename">listings/process/quotes.py</span></a></div></div>
+
+<p>If you only need the final exit code (like <code class="python">commands.getstatusoutput(cmd)[0]</code>), the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.utils.getProcessValue.html" title="twisted.internet.utils.getProcessValue">twisted.internet.utils.getProcessValue</a></code> function is
+useful. Here is an example:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">utils</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printTrueValue</span>(<span class="py-src-parameter">val</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;/bin/true exits with rc=%d&quot;</span> % <span class="py-src-variable">val</span>
+ <span class="py-src-variable">output</span> = <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessValue</span>(<span class="py-src-string">'/bin/false'</span>)
+ <span class="py-src-variable">output</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printFalseValue</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printFalseValue</span>(<span class="py-src-parameter">val</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;/bin/false exits with rc=%d&quot;</span> % <span class="py-src-variable">val</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">output</span> = <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessValue</span>(<span class="py-src-string">'/bin/true'</span>)
+<span class="py-src-variable">output</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printTrueValue</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/process/trueandfalse.py"><span class="filename">listings/process/trueandfalse.py</span></a></div></div>
+
+<h2>Mapping File Descriptors<a name="auto7"/></h2>
+
+<p><q>stdin</q>, <q>stdout</q>, and <q>stderr</q> are just conventions.
+Programs which operate as filters generally accept input on fd0, write their
+output on fd1, and emit error messages on fd2. This is common enough that
+the standard C library provides macros like <q>stdin</q> to mean fd0, and
+shells interpret the pipe character <q>|</q> to mean <q>redirect fd1 from
+one command into fd0 of the next command</q>.</p>
+
+<p>But these are just conventions, and programs are free to use additional
+file descriptors or even ignore the standard three entirely. The
+<q>childFDs</q> argument allows you to specify exactly what kind of files
+descriptors the child process should be given.</p>
+
+<p>Each child FD can be put into one of three states:</p>
+
+<ul>
+ <li>Mapped to a parent FD: this causes the child's reads and writes to
+ come from or go to the same source/destination as the parent.</li>
+
+ <li>Feeding into a pipe which can be read by the parent.</li>
+
+ <li>Feeding from a pipe which the parent writes into.</li>
+</ul>
+
+<p>Mapping the child FDs to the parent's is very commonly used to send the
+child's stderr output to the same place as the parent's. When you run a
+program from the shell, it will typically leave fds 0, 1, and 2 mapped to
+the shell's 0, 1, and 2, allowing you to see the child program's output on
+the same terminal you used to launch the child. Likewise, inetd will
+typically map both stdin and stdout to the network socket, and may map
+stderr to the same socket or to some kind of logging mechanism. This allows
+the child program to be implemented with no knowledge of the network: it
+merely speaks its protocol by doing reads on fd0 and writes on fd1.</p>
+
+<p>Feeding into a parent's read pipe is used to gather output from the
+child, and is by far the most common way of interacting with child
+processes.</p>
+
+<p>Feeding from a parent's write pipe allows the parent to control the
+child. Programs like <q>bc</q> or <q>ftp</q> can be controlled this way, by
+writing commands into their stdin stream.</p>
+
+<p>The <q>childFDs</q> dictionary maps file descriptor numbers (as will be
+seen by the child process) to one of these three states. To map the fd to
+one of the parent's fds, simply provide the fd number as the value. To map
+it to a read pipe, use the string <q>r</q> as the value. To map it to a
+write pipe, use the string <q>w</q>.</p>
+
+<p>For example, the default mapping sets up the standard stdin/stdout/stderr
+pipes. It is implemented with the following dictionary:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">childFDs</span> = { <span class="py-src-number">0</span>: <span class="py-src-string">&quot;w&quot;</span>, <span class="py-src-number">1</span>: <span class="py-src-string">&quot;r&quot;</span>, <span class="py-src-number">2</span>: <span class="py-src-string">&quot;r&quot;</span> }
+</pre>
+
+<p>To launch a process which reads and writes to the same places that the
+parent python program does, use this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">childFDs</span> = { <span class="py-src-number">0</span>: <span class="py-src-number">0</span>, <span class="py-src-number">1</span>: <span class="py-src-number">1</span>, <span class="py-src-number">2</span>: <span class="py-src-number">2</span>}
+</pre>
+
+<p>To write into an additional fd (say it is fd number 4), use this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">childFDs</span> = { <span class="py-src-number">0</span>: <span class="py-src-string">&quot;w&quot;</span>, <span class="py-src-number">1</span>: <span class="py-src-string">&quot;r&quot;</span>, <span class="py-src-number">2</span>: <span class="py-src-string">&quot;r&quot;</span> , <span class="py-src-number">4</span>: <span class="py-src-string">&quot;w&quot;</span>}
+</pre>
+
+
+
+<h3>ProcessProtocols with extra file descriptors<a name="auto8"/></h3>
+
+<p>When you provide a <q>childFDs</q> dictionary with more than the normal
+three fds, you need addtional methods to access those pipes. These methods
+are more generalized than the <code>.outReceived</code> ones described above.
+In fact, those methods (<code>outReceived</code> and
+<code>errReceived</code>) are actually just wrappers left in for
+compatibility with older code, written before this generalized fd mapping was
+implemented. The new list of things that can happen to your ProcessProtocol
+is as follows:</p>
+
+<ul>
+
+ <li><code>.connectionMade</code>: This is called when the program is
+ started.</li>
+
+ <li><code>.childDataReceived(childFD, data)</code>: This is called with
+ data that was received from one of the process' output pipes (i.e. where
+ the childFDs value was <q>r</q>. The actual file number (from the point of
+ view of the child process) is in <q>childFD</q>. For compatibility, the
+ default implementation of <code>.dataReceived</code> dispatches to
+ <code>.outReceived</code> or <code>.errReceived</code> when <q>childFD</q>
+ is 1 or 2.</li>
+
+ <li><code>.childConnectionLost(childFD)</code>: This is called when the
+ reactor notices that one of the process' pipes has been closed. This
+ either means you have just closed down the parent's end of the pipe (with
+ <code>.transport.closeChildFD</code>), the child closed the pipe
+ explicitly (sometimes to indicate EOF), or the child process has
+ terminated and the kernel has closed all of its pipes. The <q>childFD</q>
+ argument tells you which pipe was closed. Note that you can only find out
+ about file descriptors which were mapped to pipes: when they are mapped to
+ existing fds the parent has no way to notice when they've been closed. For
+ compatibility, the default implementation dispatches to
+ <code>.inConnectionLost</code>, <code>.outConnectionLost</code>, or
+ <code>.errConnectionLost</code>.</li>
+
+ <li><code>.processEnded(status)</code>: This is called when the child
+ process has been reaped, and all pipes have been closed. This insures that
+ all data written by the child prior to its death will be received before
+ <code>.processEnded</code> is invoked.</li>
+
+</ul>
+
+
+<p>In addition to those methods, there are other methods available to
+influence the child process:</p>
+
+<ul>
+
+ <li><code>self.transport.writeToChild(childFD, data)</code>: Stuff some
+ data into an input pipe. <code>.write</code> simply writes to
+ childFD=0.</li>
+
+ <li><code>self.transport.closeChildFD(childFD)</code>: Close one of the
+ child's pipes. Closing an input pipe is a common way to indicate EOF to
+ the child process. Closing an output pipe is neither very friendly nor
+ very useful.</li>
+</ul>
+
+<h3>Examples<a name="auto9"/></h3>
+
+<p>GnuPG, the encryption program, can use additional file descriptors to
+accept a passphrase and emit status output. These are distinct from stdin
+(used to accept the crypttext), stdout (used to emit the plaintext), and
+stderr (used to emit human-readable status/warning messages). The passphrase
+FD reads until the pipe is closed and uses the resulting string to unlock
+the secret key that performs the actual decryption. The status FD emits
+machine-parseable status messages to indicate the validity of the signature,
+which key the message was encrypted to, etc.</p>
+
+<p>gpg accepts command-line arguments to specify what these fds are, and
+then assumes that they have been opened by the parent before the gpg process
+is started. It simply performs reads and writes to these fd numbers.</p>
+
+<p>To invoke gpg in decryption/verification mode, you would do something
+like the following:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">GPGProtocol</span>(<span class="py-src-parameter">ProcessProtocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">crypttext</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">crypttext</span> = <span class="py-src-variable">crypttext</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">plaintext</span> = <span class="py-src-string">&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">status</span> = <span class="py-src-string">&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">writeToChild</span>(<span class="py-src-number">3</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">passphrase</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">closeChildFD</span>(<span class="py-src-number">3</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">writeToChild</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">crypttext</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">closeChildFD</span>(<span class="py-src-number">0</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">childDataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">childFD</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">childFD</span> == <span class="py-src-number">1</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">plaintext</span> += <span class="py-src-variable">data</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">childFD</span> == <span class="py-src-number">4</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">status</span> += <span class="py-src-variable">data</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">processEnded</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">rc</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">value</span>.<span class="py-src-variable">exitCode</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">rc</span> == <span class="py-src-number">0</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">deferred</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">deferred</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">rc</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">decrypt</span>(<span class="py-src-parameter">crypttext</span>):
+ <span class="py-src-variable">gp</span> = <span class="py-src-variable">GPGProtocol</span>(<span class="py-src-variable">crypttext</span>)
+ <span class="py-src-variable">gp</span>.<span class="py-src-variable">deferred</span> = <span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">cmd</span> = [<span class="py-src-string">&quot;gpg&quot;</span>, <span class="py-src-string">&quot;--decrypt&quot;</span>, <span class="py-src-string">&quot;--passphrase-fd&quot;</span>, <span class="py-src-string">&quot;3&quot;</span>, <span class="py-src-string">&quot;--status-fd&quot;</span>, <span class="py-src-string">&quot;4&quot;</span>,
+ <span class="py-src-string">&quot;--batch&quot;</span>]
+ <span class="py-src-variable">p</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">spawnProcess</span>(<span class="py-src-variable">gp</span>, <span class="py-src-variable">cmd</span>[<span class="py-src-number">0</span>], <span class="py-src-variable">cmd</span>, <span class="py-src-variable">env</span>=<span class="py-src-variable">None</span>,
+ <span class="py-src-variable">childFDs</span>={<span class="py-src-number">0</span>:<span class="py-src-string">&quot;w&quot;</span>, <span class="py-src-number">1</span>:<span class="py-src-string">&quot;r&quot;</span>, <span class="py-src-number">2</span>:<span class="py-src-number">2</span>, <span class="py-src-number">3</span>:<span class="py-src-string">&quot;w&quot;</span>, <span class="py-src-number">4</span>:<span class="py-src-string">&quot;r&quot;</span>})
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">gp</span>.<span class="py-src-variable">deferred</span>
+</pre>
+
+<p>In this example, the status output could be parsed after the fact. It
+could, of course, be parsed on the fly, as it is a simple line-oriented
+protocol. Methods from LineReceiver could be mixed in to make this parsing
+more convenient.</p>
+
+<p>The stderr mapping (<q>2:2</q>) used will cause any GPG errors to be
+emitted by the parent program, just as if those errors had caused in the
+parent itself. This is sometimes desireable (it roughly corresponds to
+letting exceptions propagate upwards), especially if you do not expect to
+encounter errors in the child process and want them to be more visible to
+the end user. The alternative is to map stderr to a read-pipe and handle any
+such output from within the ProcessProtocol (roughly corresponding to
+catching the exception locally).</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/producers.html b/vendor/Twisted-10.0.0/doc/core/howto/producers.html
new file mode 100644
index 0000000000..377379b6f0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/producers.html
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Producers and Consumers: Efficient High-Volume Streaming</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Producers and Consumers: Efficient High-Volume Streaming</h1>
+ <div class="toc"><ol><li><a href="#auto0">Push Producers</a></li><ul><li><a href="#auto1">pauseProducing()</a></li><li><a href="#auto2">resumeProducing()</a></li><li><a href="#auto3">stopProducing()</a></li></ul><li><a href="#auto4">Pull Producers</a></li><ul><li><a href="#auto5">resumeProducing()</a></li><li><a href="#auto6">stopProducing()</a></li></ul><li><a href="#auto7">Consumers</a></li><ul><li><a href="#auto8">registerProducer(producer, streaming)</a></li><li><a href="#auto9">unregisterProducer()</a></li><li><a href="#auto10">write(data)</a></li><li><a href="#auto11">finish()</a></li></ul><li><a href="#auto12">Further Reading</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <p>The purpose of this guide is to describe the Twisted <em>producer</em> and <em>consumer</em> system. The producer system allows applications to stream large amounts of data in a manner which is both memory and CPU efficient, and which does not introduce a source of unacceptable latency into the reactor.</p>
+
+ <p>Readers should have at least a passing familiarity with the terminology associated with interfaces.</p>
+
+ <h2>Push Producers<a name="auto0"/></h2>
+
+ <p>A push producer is one which will continue to generate data without external prompting until told to stop; a pull producer will generate one chunk of data at a time in response to an explicit request for more data.</p>
+
+ <p>The push producer API is defined by the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IPushProducer.html" title="twisted.internet.interfaces.IPushProducer">IPushProducer</a></code> interface. It is best to create a push producer when data generation is closedly tied to an event source. For example, a proxy which forwards incoming bytes from one socket to another outgoing socket might be implemented using a push producer: the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IProtocol.dataReceived.html" title="twisted.internet.interfaces.IProtocol.dataReceived">dataReceived</a></code> takes the role of an event source from which the producer generates bytes, and requires no external intervention in order to do so.</p>
+
+ <p>There are three methods which may be invoked on a push producer at various points in its lifetime: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IPushProducer.pauseProducing.html" title="twisted.internet.interfaces.IPushProducer.pauseProducing">pauseProducing</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IPushProducer.resumeProducing.html" title="twisted.internet.interfaces.IPushProducer.resumeProducing">resumeProducing</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IPushProducer.stopProducing.html" title="twisted.internet.interfaces.IPushProducer.stopProducing">stopProducing</a></code>.</p>
+
+ <h3>pauseProducing()<a name="auto1"/></h3>
+
+ <p>In order to avoid the possibility of using an unbounded amount of memory to buffer produced data which cannot be processed quickly enough, it is necessary to be able to tell a push producer to stop producing data for a while. This is done using the <code class="python">pauseProducing</code> method. Implementers of a push producer should temporarily stop producing data when this method is invoked.</p>
+
+ <h3>resumeProducing()<a name="auto2"/></h3>
+
+ <p>After a push producer has been paused for some time, the excess of data which it produced will have been processed and the producer may again begin producing data. When the time for this comes, the push producer will have <code class="python">resumeProducing</code> invoked on it.</p>
+
+ <h3>stopProducing()<a name="auto3"/></h3>
+
+ <p>Most producers will generate some finite (albeit, perhaps, unknown in advance) amount of data and then stop, having served their intended purpose. However, it is possible that before this happens an event will occur which renders the remaining, unproduced data irrelevant. In these cases, producing it anyway would be wasteful. The <code class="python">stopProducing</code> method will be invoked on the push producer. The implementation should stop producing data and clean up any resources owned by the producer.</p>
+
+ <h2>Pull Producers<a name="auto4"/></h2>
+
+ <p>The pull producer API is defined by the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IPullProducer.html" title="twisted.internet.interfaces.IPullProducer">IPullProducer</a></code> interface. Pull producers are useful in cases where there is no clear event source involved with the generation of data. For example, if the data is the result of some algorithmic process that is bound only by CPU time, a pull producer is appropriate.</p>
+
+ <p>Pull producers are defined in terms of only two methods: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IPullProducer.resumeProducing.html" title="twisted.internet.interfaces.IPullProducer.resumeProducing">resumeProducing</a></code> and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IPullProducer.stopProducing.html" title="twisted.internet.interfaces.IPullProducer.stopProducing">stopProducing</a></code>.</p>
+
+ <h3>resumeProducing()<a name="auto5"/></h3>
+
+ <p>Unlike push producers, a pull producer is expected to <strong>only</strong> produce data in response to <code class="python">resumeProducing</code> being called. This method will be called whenever more data is required. How much data to produce in response to this method call depends on various factors: too little data and runtime costs will be dominated by the back-and-forth event notification associated with a buffer becoming empty and requesting more data to process; too much data and memory usage will be driven higher than it needs to be and the latency associated with creating so much data will cause overall performance in the application to suffer. A good rule of thumb is to generate between 16 and 64 kilobytes of data at a time, but you should experiment with various values to determine what is best for your application.</p>
+
+ <h3>stopProducing()<a name="auto6"/></h3>
+
+ <p>This method has the same meaning for pull producers as it does for push producers.</p>
+
+ <h2>Consumers<a name="auto7"/></h2>
+
+ <p>This far, I've discussed the various external APIs of the two kinds of producers supported by Twisted. However, I have not mentioned where the data a producer generates actually goes, nor what entity is responsible for invoking these APIs. Both of these roles are filled by <em>consumers</em>. Consumers are defined by the two interfaces <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IConsumer.html" title="twisted.internet.interfaces.IConsumer">IConsumer</a></code> and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IFinishableConsumer.html" title="twisted.internet.interfaces.IFinishableConsumer">IFinishableConsumer</a></code>.</p>
+
+ <p>The slightly simpler of these two interfaces, <code class="python">IConsumer</code>, defines three methods: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IConsumer.registerProducer.html" title="twisted.internet.interfaces.IConsumer.registerProducer">registerProducer</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IConsumer.unregisterProducer.html" title="twisted.internet.interfaces.IConsumer.unregisterProducer">unregisterProducer</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IConsumer.write.html" title="twisted.internet.interfaces.IConsumer.write">write</a></code>. <code class="python">IFinishableConsumer</code> adds <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IFinishableConsumer.finish.html" title="twisted.internet.interfaces.IFinishableConsumer.finish">finish</a></code>.</p>
+
+ <h3>registerProducer(producer, streaming)<a name="auto8"/></h3>
+
+ <p>So that a consumer can invoke methods on a producer, the consumer needs to be told about the producer. This is done with the <code class="python">registerProducer</code> method. The first argument is either a <code class="python">IPullProducer</code> or <code class="python">IPushProducer</code> provider; the second argument indicates which of these interfaces is provided: <code class="python">True</code> for push producers, <code class="python">False</code> for pull producers.</p>
+
+ <h3>unregisterProducer()<a name="auto9"/></h3>
+
+ <p>Eventually a consumer will not longer be interested in a producer. This could be because the producer has finished generating all its data, or because the consumer is moving on to something else, or any number of other reasons. In any case, this method reverses the effects of <code class="python">registerProducer</code>.</p>
+
+ <h3>write(data)<a name="auto10"/></h3>
+
+ <p>As you might guess, this is the method which a producer calls when it has generated some data. Push producers should call it as frequently as they like as long as they are not paused. Pull producers should call it once for each time <code class="python">resumeProducing</code> is called on them.</p>
+
+ <h3>finish()<a name="auto11"/></h3>
+
+ <p>This method of <code class="python">IFinishableConsumer</code>s gives producers a way to explicitly notify the consumer that they have generated all the data they will ever generate.</p>
+
+ <h2>Further Reading<a name="auto12"/></h2>
+
+ <ul>
+
+ <li><a href="components.html" shape="rect">Components: Interfaces and Adapters</a></li>
+
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.protocols.basic.FileSender.html" title="twisted.protocols.basic.FileSender">FileSender</a></code>: A Simple Pull Producer</li>
+
+ </ul>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/quotes.html b/vendor/Twisted-10.0.0/doc/core/howto/quotes.html
new file mode 100644
index 0000000000..c7a6d5f96c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/quotes.html
@@ -0,0 +1,214 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Setting up the TwistedQuotes application</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Setting up the TwistedQuotes application</h1>
+ <div class="toc"><ol><li><a href="#auto0">Goal</a></li><li><a href="#auto1">Setting up the TwistedQuotes project directory</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Goal<a name="auto0"/></h2>
+
+<p>This document describes how to set up the TwistedQuotes application used in
+a number of other documents, such as <a href="design.html" shape="rect">designing Twisted applications</a>.</p>
+
+<h2>Setting up the TwistedQuotes project directory<a name="auto1"/></h2>
+
+<p>In order to run the Twisted Quotes example, you will need to do the
+following:</p>
+
+<ol>
+<li>Make a <code>TwistedQuotes</code> directory on your system</li>
+<li>Place the following files in the <code>TwistedQuotes</code> directory:
+ <ul>
+ <li><div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-string">&quot;&quot;&quot;
+Twisted Quotes
+&quot;&quot;&quot;</span>
+</pre><div class="caption">Source listing - <a href="listings/TwistedQuotes/__init__.py"><span class="filename">listings/TwistedQuotes/__init__.py</span></a></div></div> (this
+ file marks it as a package, see <a href="http://docs.python.org/tutorial/modules.html#packages" shape="rect">this section</a> of the Python tutorial for more on packages);</li>
+ <li><div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">random</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">choice</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">TwistedQuotes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">quoteproto</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">StaticQuoter</span>:
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a static quote.
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">quoteproto</span>.<span class="py-src-variable">IQuoter</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">quote</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">quote</span> = <span class="py-src-variable">quote</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">quote</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FortuneQuoter</span>:
+ <span class="py-src-string">&quot;&quot;&quot;
+ Load quotes from a fortune-format file.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">quoteproto</span>.<span class="py-src-variable">IQuoter</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filenames</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filenames</span> = <span class="py-src-variable">filenames</span>
+
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">quoteFile</span> = <span class="py-src-variable">file</span>(<span class="py-src-variable">choice</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filenames</span>))
+ <span class="py-src-variable">quotes</span> = <span class="py-src-variable">quoteFile</span>.<span class="py-src-variable">read</span>().<span class="py-src-variable">split</span>(<span class="py-src-string">'\n%\n'</span>)
+ <span class="py-src-variable">quoteFile</span>.<span class="py-src-variable">close</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">choice</span>(<span class="py-src-variable">quotes</span>)
+</pre><div class="caption">Source listing - <a href="listings/TwistedQuotes/quoters.py"><span class="filename">listings/TwistedQuotes/quoters.py</span></a></div></div>;</li>
+ <li><div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IQuoter</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ An object that returns quotes.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return a quote.
+ &quot;&quot;&quot;</span>
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">quoter</span>.<span class="py-src-variable">getQuote</span>()+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTDFactory</span>(<span class="py-src-parameter">Factory</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ A factory for the Quote of the Day protocol.
+
+ @type quoter: L{IQuoter} provider
+ @ivar quoter: An object which provides L{IQuoter} which will be used by
+ the L{QOTD} protocol to get quotes to emit.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">QOTD</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">quoter</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">quoter</span> = <span class="py-src-variable">quoter</span>
+</pre><div class="caption">Source listing - <a href="listings/TwistedQuotes/quoteproto.py"><span class="filename">listings/TwistedQuotes/quoteproto.py</span></a></div></div>;</li>
+ </ul>
+</li>
+<li>Add the <code>TwistedQuotes</code> directory's <em>parent</em> to your Python
+path. For example, if the TwistedQuotes directory's path is
+<code>/tmp/TwistedQuotes</code>
+add <code>/tmp</code> to your Python path. On UNIX this would be <code class="shell">export PYTHONPATH=/my/stuff:$PYTHONPATH</code>, on Microsoft
+Windows change the <code class="shell">PYTHONPATH</code> variable through the
+Systems Properites dialog to add <code class="shell">/my/stuff;</code> at the
+beginning.</li>
+<li>
+Test your package by trying to import it in the Python interpreter:
+<pre class="python-interpreter" xml:space="preserve">
+Python 2.1.3 (#1, Apr 20 2002, 22:45:31)
+[GCC 2.95.4 20011002 (Debian prerelease)] on linux2
+Type &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
+&gt;&gt;&gt; import TwistedQuotes
+&gt;&gt;&gt; # No traceback means you're fine.
+</pre>
+</li>
+</ol>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/rdbms.html b/vendor/Twisted-10.0.0/doc/core/howto/rdbms.html
new file mode 100644
index 0000000000..9a038743c9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/rdbms.html
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: twisted.enterprise.adbapi: Twisted RDBMS support</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">twisted.enterprise.adbapi: Twisted RDBMS support</h1>
+ <div class="toc"><ol><li><a href="#auto0">Abstract</a></li><li><a href="#auto1">What you should already know</a></li><li><a href="#auto2">Quick Overview</a></li><li><a href="#auto3">How do I use adbapi?</a></li><li><a href="#auto4">Examples of various database adapters</a></li><li><a href="#auto5">And that's it!</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Abstract<a name="auto0"/></h2>
+
+ <p>Twisted is an asynchronous networking framework, but most
+ database API implementations unfortunately have blocking
+ interfaces -- for this reason, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.adbapi.html" title="twisted.enterprise.adbapi">twisted.enterprise.adbapi</a></code> was created. It is
+ a non-blocking interface to the standardized DB-API 2.0 API,
+ which allows you to access a number of different RDBMSes.</p>
+
+ <h2>What you should already know<a name="auto1"/></h2>
+
+ <ul>
+ <li>Python :-)</li>
+
+ <li>How to write a simple Twisted Server (see <a href="servers.html" shape="rect">this tutorial</a> to learn how)</li>
+
+ <li>Familiarity with using database interfaces (see <a href="http://www.python.org/dev/peps/pep-0249/" shape="rect">
+ the documentation for DBAPI 2.0</a> or this <a href="http://www.amk.ca/python/writing/DB-API.html" shape="rect">article</a>
+ by Andrew Kuchling)</li>
+ </ul>
+
+ <h2>Quick Overview<a name="auto2"/></h2>
+
+ <p>Twisted is an asynchronous framework. This means standard
+ database modules cannot be used directly, as they typically
+ work something like:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-comment"># Create connection... </span>
+<span class="py-src-variable">db</span> = <span class="py-src-variable">dbmodule</span>.<span class="py-src-variable">connect</span>(<span class="py-src-string">'mydb'</span>, <span class="py-src-string">'andrew'</span>, <span class="py-src-string">'password'</span>)
+<span class="py-src-comment"># ...which blocks for an unknown amount of time </span>
+
+<span class="py-src-comment"># Create a cursor </span>
+<span class="py-src-variable">cursor</span> = <span class="py-src-variable">db</span>.<span class="py-src-variable">cursor</span>()
+
+<span class="py-src-comment"># Do a query... </span>
+<span class="py-src-variable">resultset</span> = <span class="py-src-variable">cursor</span>.<span class="py-src-variable">query</span>(<span class="py-src-string">'SELECT * FROM table WHERE ...'</span>)
+<span class="py-src-comment"># ...which could take a long time, perhaps even minutes.</span>
+</pre>
+
+ <p>Those delays are unacceptable when using an asynchronous
+ framework such as Twisted. For this reason, twisted provides
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.adbapi.html" title="twisted.enterprise.adbapi">twisted.enterprise.adbapi</a></code>, an
+ asynchronous wrapper for any <a href="http://www.python.org/topics/database/DatabaseAPI-2.0.html" shape="rect">
+ DB-API 2.0</a>-compliant module.</p>
+
+ <p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.adbapi.html" title="twisted.enterprise.adbapi">enterprise.adbapi</a></code> will do
+ blocking
+ database operations in seperate threads, which trigger
+ callbacks in the originating thread when they complete. In the
+ meantime, the original thread can continue doing normal work,
+ like servicing other requests.</p>
+
+ <h2>How do I use adbapi?<a name="auto3"/></h2>
+
+ <p>Rather than creating a database connection directly, use the
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.adbapi.ConnectionPool.html" title="twisted.enterprise.adbapi.ConnectionPool">adbapi.ConnectionPool</a></code>
+ class to manage
+ a connections for you. This allows <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.adbapi.html" title="twisted.enterprise.adbapi">enterprise.adbapi</a></code> to use multiple
+ connections, one per thread. This is easy:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-comment"># Using the &quot;dbmodule&quot; from the previous example, create a ConnectionPool </span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">enterprise</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">adbapi</span>
+<span class="py-src-variable">dbpool</span> = <span class="py-src-variable">adbapi</span>.<span class="py-src-variable">ConnectionPool</span>(<span class="py-src-string">&quot;dbmodule&quot;</span>, <span class="py-src-string">'mydb'</span>, <span class="py-src-string">'andrew'</span>, <span class="py-src-string">'password'</span>)
+</pre>
+
+ <p>Things to note about doing this:</p>
+
+ <ul>
+ <li>There is no need to import dbmodule directly. You just
+ pass the name to <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.adbapi.ConnectionPool.html" title="twisted.enterprise.adbapi.ConnectionPool">adbapi.ConnectionPool</a></code>'s constructor.</li>
+
+ <li>The parameters you would pass to dbmodule.connect are
+ passed as extra arguments to <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.adbapi.ConnectionPool.html" title="twisted.enterprise.adbapi.ConnectionPool">adbapi.ConnectionPool</a></code>'s constructor.
+ Keyword parameters work as well.</li>
+ </ul>
+
+ <p>Now we can do a database query:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-comment"># equivalent of cursor.execute(statement), return cursor.fetchall():</span>
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getAge</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">dbpool</span>.<span class="py-src-variable">runQuery</span>(<span class="py-src-string">&quot;SELECT age FROM users WHERE name = ?&quot;</span>, <span class="py-src-variable">user</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">l</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">l</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">l</span>[<span class="py-src-number">0</span>][<span class="py-src-number">0</span>], <span class="py-src-string">&quot;years old&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;No such user&quot;</span>
+
+<span class="py-src-variable">getAge</span>(<span class="py-src-string">&quot;joe&quot;</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+</pre>
+
+ <p>This is straightforward, except perhaps for the return value
+ of <code>getAge</code>. It returns a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">twisted.internet.defer.Deferred</a></code>, which allows
+ arbitrary callbacks to be called upon completion (or upon
+ failure). More documentation on Deferred is available <a href="defer.html" shape="rect">here</a>.</p>
+
+ <p>In addition to <code>runQuery</code>, there is also <code>runOperation</code>,
+ and <code>runInteraction</code> that gets called with a callable (e.g. a function).
+ The function will be called in the thread with a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.enterprise.adbapi.Transaction.html" title="twisted.enterprise.adbapi.Transaction">twisted.enterprise.adbapi.Transaction</a></code>,
+ which basically mimics a DB-API cursor. In all cases a database transaction will be
+ commited after your database usage is finished, unless an exception is raised in
+ which case it will be rolled back.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">_getAge</span>(<span class="py-src-parameter">txn</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-comment"># this will run in a thread, we can use blocking calls</span>
+ <span class="py-src-variable">txn</span>.<span class="py-src-variable">execute</span>(<span class="py-src-string">&quot;SELECT * FROM foo&quot;</span>)
+ <span class="py-src-comment"># ... other cursor commands called on txn ...</span>
+ <span class="py-src-variable">txn</span>.<span class="py-src-variable">execute</span>(<span class="py-src-string">&quot;SELECT age FROM users WHERE name = ?&quot;</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">txn</span>.<span class="py-src-variable">fetchall</span>()
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">result</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">result</span>[<span class="py-src-number">0</span>][<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getAge</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">dbpool</span>.<span class="py-src-variable">runInteraction</span>(<span class="py-src-variable">_getAge</span>, <span class="py-src-variable">user</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">age</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">age</span> != <span class="py-src-variable">None</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">age</span>, <span class="py-src-string">&quot;years old&quot;</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;No such user&quot;</span>
+
+<span class="py-src-variable">getAge</span>(<span class="py-src-string">&quot;joe&quot;</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+</pre>
+
+ <p>Also worth noting is that these examples assumes that dbmodule
+ uses the <q>qmarks</q> paramstyle (see the DB-API specification). If
+ your dbmodule uses a different paramstyle (e.g. pyformat) then
+ use that. Twisted doesn't attempt to offer any sort of magic
+ paramater munging -- <code class="python">runQuery(query,
+ params, ...)</code> maps directly onto <code class="python">cursor.execute(query, params, ...)</code>.</p>
+
+ <h2>Examples of various database adapters<a name="auto4"/></h2>
+
+ <p>Notice that the first argument is the module name you would
+ usually import and get <code class="python">connect(...)</code>
+ from, and that following arguments are whatever arguments you'd
+ call <code class="python">connect(...)</code> with.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">enterprise</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">adbapi</span>
+
+<span class="py-src-comment"># Gadfly</span>
+<span class="py-src-variable">cp</span> = <span class="py-src-variable">adbapi</span>.<span class="py-src-variable">ConnectionPool</span>(<span class="py-src-string">&quot;gadfly&quot;</span>, <span class="py-src-string">&quot;test&quot;</span>, <span class="py-src-string">&quot;/tmp/gadflyDB&quot;</span>)
+
+<span class="py-src-comment"># PostgreSQL PyPgSQL</span>
+<span class="py-src-variable">cp</span> = <span class="py-src-variable">adbapi</span>.<span class="py-src-variable">ConnectionPool</span>(<span class="py-src-string">&quot;pyPgSQL.PgSQL&quot;</span>, <span class="py-src-variable">database</span>=<span class="py-src-string">&quot;test&quot;</span>)
+
+<span class="py-src-comment"># MySQL</span>
+<span class="py-src-variable">cp</span> = <span class="py-src-variable">adbapi</span>.<span class="py-src-variable">ConnectionPool</span>(<span class="py-src-string">&quot;MySQLdb&quot;</span>, <span class="py-src-variable">db</span>=<span class="py-src-string">&quot;test&quot;</span>)
+</pre>
+
+ <h2>And that's it!<a name="auto5"/></h2>
+
+ <p>That's all you need to know to use a database from within
+ Twisted. You probably should read the adbapi module's
+ documentation to get an idea of the other functions it has, but
+ hopefully this document presents the core ideas.</p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/reactor-basics.html b/vendor/Twisted-10.0.0/doc/core/howto/reactor-basics.html
new file mode 100644
index 0000000000..d123f2be36
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/reactor-basics.html
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Reactor Overview</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Reactor Overview</h1>
+ <div class="toc"><ol><li><a href="#auto0">Reactor Basics</a></li><li><a href="#auto1">Using the reactor object</a></li></ol></div>
+ <div class="content">
+
+ <span/>
+
+ <p>
+ This HOWTO introduces the Twisted reactor, describes the basics of the
+ reactor and links to the various reactor interfaces.
+ </p>
+
+ <h2>Reactor Basics<a name="auto0"/></h2>
+
+ <p>The reactor is the core of the event loop within Twisted -- the loop
+ which drives applications using Twisted. The event loop is a programming
+ construct that waits for and dispatches events or messages in a program.
+ It works by calling some internal or external &quot;event provider&quot;, which
+ generally blocks until an event has arrived, and then calls the relevant
+ event handler (&quot;dispatches the event&quot;). The reactor provides basic
+ interfaces to a number of services, including network communications,
+ threading, and event dispatching.
+ </p>
+
+ <p>
+ For information about using the reactor and the Twisted event loop, see:
+ </p>
+
+ <ul>
+ <li>the event dispatching howtos: <a href="time.html" shape="rect">Scheduling</a> and <a href="defer.html" shape="rect">Using Deferreds</a>;</li>
+ <li>the communication howtos: <a href="servers.html" shape="rect">TCP
+ servers</a>, <a href="clients.html" shape="rect">TCP clients</a>, <a href="udp.html" shape="rect">UDP networking</a> and <a href="process.html" shape="rect">Using
+ processes</a>; and</li>
+ <li><a href="threading.html" shape="rect">Using threads</a>.</li>
+ </ul>
+
+ <p>There are multiple implementations of the reactor, each
+ modified to provide better support for specialized features
+ over the default implementation. More information about these
+ and how to use a particular implementation is available via
+ <a href="choosing-reactor.html" shape="rect">Choosing a Reactor</a>.</p>
+
+
+ <p>
+ Twisted applications can use the interfaces in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.html" title="twisted.application.service">twisted.application.service</a></code> to configure and run the
+ application instead of using
+ boilerplate reactor code. See <a href="application.html" shape="rect">Using Application</a> for an introduction to
+ Application.
+ </p>
+
+ <h2>Using the reactor object<a name="auto1"/></h2>
+
+ <p>You can get to the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code> object using the following code:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+ <p>The reactor usually implements a set of interfaces, but
+ depending on the chosen reactor and the platform, some of
+ the interfaces may not be implemented:</p>
+
+ <ul>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorCore.html" title="twisted.internet.interfaces.IReactorCore">IReactorCore</a></code>: Core (required) functionality.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorFDSet.html" title="twisted.internet.interfaces.IReactorFDSet">IReactorFDSet</a></code>: Use FileDescriptor objects.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorProcess.html" title="twisted.internet.interfaces.IReactorProcess">IReactorProcess</a></code>: Process management. Read the
+ <a href="process.html" shape="rect">Using Processes</a> document for
+ more information.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorSSL.html" title="twisted.internet.interfaces.IReactorSSL">IReactorSSL</a></code>: SSL networking support.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTCP.html" title="twisted.internet.interfaces.IReactorTCP">IReactorTCP</a></code>: TCP networking support. More information
+ can be found in the <a href="servers.html" shape="rect">Writing Servers</a>
+ and <a href="clients.html" shape="rect">Writing Clients</a> documents.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorThreads.html" title="twisted.internet.interfaces.IReactorThreads">IReactorThreads</a></code>: Threading use and management. More
+ information can be found within <a href="threading.html" shape="rect">Threading In Twisted</a>.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTime.html" title="twisted.internet.interfaces.IReactorTime">IReactorTime</a></code>: Scheduling interface. More information
+ can be found within <a href="time.html" shape="rect">Scheduling Tasks</a>.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUDP.html" title="twisted.internet.interfaces.IReactorUDP">IReactorUDP</a></code>: UDP networking support. More information
+ can be found within <a href="udp.html" shape="rect">UDP Networking</a>.</li>
+ <li><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUNIX.html" title="twisted.internet.interfaces.IReactorUNIX">IReactorUNIX</a></code>: UNIX socket support.</li>
+ </ul>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/row.html b/vendor/Twisted-10.0.0/doc/core/howto/row.html
new file mode 100644
index 0000000000..bc12625e37
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/row.html
@@ -0,0 +1,279 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Enterprise Row Objects</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Enterprise Row Objects</h1>
+ <div class="toc"><ol><li><a href="#auto0">Class Definitions</a></li><li><a href="#auto1">Initialization</a></li><li><a href="#auto2">Creating Row Objects</a></li><li><a href="#auto3">Relationships Between Tables</a></li><li><a href="#auto4">Duplicate Row Objects</a></li><li><a href="#auto5">Updating Row Objects</a></li><li><a href="#auto6">Deleting Row Objects</a></li></ol></div>
+ <div class="content">
+
+<div class="note"><strong>Note: </strong>
+<p>
+Due to lack of maintenance, <code>twisted.enterprise.row</code>
+and <code>twisted.enterprise.reflector</code> have been deprecated since
+Twisted 8.0.
+</p>
+
+<p>
+This documentation is maintained only for users with an existing
+codebase.
+</p>
+</div>
+
+
+<span/>
+
+<p>The <code>twisted.enterprise.row</code> module is a method of
+interfacing simple python objects with rows in relational database
+tables. It has two components: the <code>RowObject</code> class which
+developers sub-class for each relational table that their code
+interacts with, and the <code>Reflector</code> which is responsible
+for updates, inserts, queries and deletes against the database.</p>
+
+<p>The row module is intended for applications such as on-line
+games, and websites that require a back-end database interface.
+It is not a full functioned object-relational mapper for python
+- it deals best with simple data types structured in ways that
+can be easily represented in a relational database. It is well
+suited to building a python interface to an existing relational
+database, and slightly less suited to added database persistance
+to an existing python application.</p>
+
+<p><em>If row does not fit your model, you will be best off using
+the <a href="rdbms.html" shape="rect">low-level database API</a> directly,
+or writing your own object/relational layer on top of it.</em></p>
+
+<h2>Class Definitions<a name="auto0"/></h2>
+
+<p>To interface to relational database tables, the developer must
+create a class derived from the <code>twisted.enterprise.row.RowObject</code>
+class for each table. These derived classes must define a number
+of class attributes which contains information about the database
+table that class corresponds to. The required class attributes
+are:</p>
+
+<ul>
+ <li>rowColumns - list of the column names and types in the table with
+ the correct case</li>
+ <li>rowKeyColumns - list of key columns in form: <code>[(columnName,
+ typeName)]</code></li>
+ <li>rowTableName - the name of the database table</li>
+</ul>
+
+<p>There are also two optional class attributes that can be specified:</p>
+
+<ul>
+ <li>rowForeignKeys - list of foreign keys to other database tables
+ in the form: <code>[(tableName, [(childColumnName, childColumnType), ...],
+ [(parentColumnName, parentColumnType), ...], containerMethodName, autoLoad]</code></li>
+ <li>rowFactoryMethod - a method that creates instances of this
+ class</li>
+</ul>
+
+<p>For example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">RoomRow</span>(<span class="py-src-parameter">row</span>.<span class="py-src-parameter">RowObject</span>):
+ <span class="py-src-variable">rowColumns</span> = [(<span class="py-src-string">&quot;roomId&quot;</span>, <span class="py-src-string">&quot;int&quot;</span>),
+ (<span class="py-src-string">&quot;town_id&quot;</span>, <span class="py-src-string">&quot;int&quot;</span>),
+ (<span class="py-src-string">&quot;name&quot;</span>, <span class="py-src-string">&quot;varchar&quot;</span>),
+ (<span class="py-src-string">&quot;owner&quot;</span>, <span class="py-src-string">&quot;varchar&quot;</span>),
+ (<span class="py-src-string">&quot;posx&quot;</span>, <span class="py-src-string">&quot;int&quot;</span>),
+ (<span class="py-src-string">&quot;posy&quot;</span>, <span class="py-src-string">&quot;int&quot;</span>),
+ (<span class="py-src-string">&quot;width&quot;</span>, <span class="py-src-string">&quot;int&quot;</span>),
+ (<span class="py-src-string">&quot;height&quot;</span>, <span class="py-src-string">&quot;int&quot;</span>)]
+ <span class="py-src-variable">rowKeyColumns</span> = [(<span class="py-src-string">&quot;roomId&quot;</span>, <span class="py-src-string">&quot;int4&quot;</span>)]
+ <span class="py-src-variable">rowTableName</span> = <span class="py-src-string">&quot;testrooms&quot;</span>
+ <span class="py-src-variable">rowFactoryMethod</span> = [<span class="py-src-variable">testRoomFactory</span>]
+</pre>
+
+<p>The items in the rowColumns list will become data members of
+classes of this type when they are created by the Reflector.</p>
+
+<h2>Initialization<a name="auto1"/></h2>
+
+<p>The initialization phase builds the SQL for the database interactions.
+It uses the system catalogs of the database to do this, but requires
+some basic information to get started. The class attributes of
+the classes derived from RowClass are used for this. Those classes
+are passed to a Reflector when it is created.</p>
+
+<p>There are currently two available reflectors in Twisted Enterprise,
+the SQL Reflector for relational databases which uses the python DB
+API, and the XML Reflector which uses a file system containing XML
+files. The XML reflector is currently extremely slow.</p>
+
+<p>An example class list for the RoomRow class we specified above using the SQLReflector:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">enterprise</span>.<span class="py-src-variable">sqlreflector</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SQLReflector</span>
+
+<span class="py-src-variable">dbpool</span> = <span class="py-src-variable">adbapi</span>.<span class="py-src-variable">ConnectionPool</span>(<span class="py-src-string">&quot;pyPgSQL.PgSQL&quot;</span>)
+<span class="py-src-variable">reflector</span> = <span class="py-src-variable">SQLReflector</span>( <span class="py-src-variable">dbpool</span>, [<span class="py-src-variable">RoomRow</span>] )
+</pre>
+
+<h2>Creating Row Objects<a name="auto2"/></h2>
+
+<p>There are two methods of creating RowObjects - loading from
+the database, and creating a new instance ready to be inserted.</p>
+
+<p>To load rows from the database and create RowObject instances
+for each of the rows, use the loadObjectsFrom method of the Reflector.
+This takes a tableName, an optional <q>user data</q> parameter,
+and an optional <q>where clause</q>. The where clause may
+be omitted which will retrieve all the rows from the table. For
+example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">gotRooms</span>(<span class="py-src-parameter">rooms</span>):
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">room</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">rooms</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Got room:&quot;</span>, <span class="py-src-variable">room</span>.<span class="py-src-variable">id</span>
+
+<span class="py-src-variable">d</span> = <span class="py-src-variable">reflector</span>.<span class="py-src-variable">loadObjectsFrom</span>(<span class="py-src-string">&quot;testrooms&quot;</span>,
+ <span class="py-src-variable">whereClause</span>=[(<span class="py-src-string">&quot;id&quot;</span>, <span class="py-src-variable">reflector</span>.<span class="py-src-variable">EQUAL</span>, <span class="py-src-number">5</span>)])
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">gotRooms</span>)
+</pre>
+
+<p>For more advanced RowObject construction, loadObjectsFrom may
+use a factoryMethod that was specified as a class attribute for
+the RowClass derived class. This method will be called for each
+of the rows with the class object, the userData parameter, and
+a dictionary of data from the database keyed by column name. This
+factory method should return a fully populated RowObject instance
+and may be used to do pre-processing, lookups, and data transformations
+before exposing the data to user code. An example factory method:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">testRoomFactory</span>(<span class="py-src-parameter">roomClass</span>, <span class="py-src-parameter">userData</span>, <span class="py-src-parameter">kw</span>):
+ <span class="py-src-variable">newRoom</span> = <span class="py-src-variable">roomClass</span>(<span class="py-src-variable">userData</span>)
+ <span class="py-src-variable">newRoom</span>.<span class="py-src-variable">__dict__</span>.<span class="py-src-variable">update</span>(<span class="py-src-variable">kw</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">newRoom</span>
+</pre>
+
+<p>The last method of creating a row object is for new instances
+that do not already exist in the database table. In this case,
+create a new instance and assign its primary key attributes and
+all of its member data attributes, then pass it to the <code>insertRow</code>
+method of the Reflector. For example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-variable">newRoom</span> = <span class="py-src-variable">RoomRow</span>()
+ <span class="py-src-variable">newRoom</span>.<span class="py-src-variable">assignKeyAttr</span>(<span class="py-src-string">&quot;roomI&quot;</span>, <span class="py-src-number">11</span>)
+ <span class="py-src-variable">newRoom</span>.<span class="py-src-variable">town_id</span> = <span class="py-src-number">20</span>
+ <span class="py-src-variable">newRoom</span>.<span class="py-src-variable">name</span> = <span class="py-src-string">'newRoom1'</span>
+ <span class="py-src-variable">newRoom</span>.<span class="py-src-variable">owner</span> = <span class="py-src-string">'fred'</span>
+ <span class="py-src-variable">newRoom</span>.<span class="py-src-variable">posx</span> = <span class="py-src-number">100</span>
+ <span class="py-src-variable">newRoom</span>.<span class="py-src-variable">posy</span> = <span class="py-src-number">100</span>
+ <span class="py-src-variable">newRoom</span>.<span class="py-src-variable">width</span> = <span class="py-src-number">15</span>
+ <span class="py-src-variable">newRoom</span>.<span class="py-src-variable">height</span> = <span class="py-src-number">20</span>
+ <span class="py-src-variable">reflector</span>.<span class="py-src-variable">insertRow</span>(<span class="py-src-variable">newRoom</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">onInsert</span>)
+</pre>
+
+<p>This will insert a new row into the database table for this
+new RowObject instance. Note that the <code>assignKeyAttr</code>
+method must be used to set primary key attributes - regular attribute
+assignment of a primary key attribute of a rowObject will raise
+an exception. This prevents the database identity of RowObject
+from being changed by mistake.</p>
+
+
+<h2>Relationships Between Tables<a name="auto3"/></h2>
+
+<p>Specifying a foreign key for a RowClass creates a relationship
+between database tables. When <code class="python">loadObjectsFrom</code> is called for a table, it will
+automatically load all the children rows for the rows from the specified
+table. The child rows will be put into a list member variable of the
+rowObject instance with the name <code>childRows</code> or if a
+<em>containerMethod</em> is specified for the foreign key relationship,
+that method will be called on the parent row object for each row that is
+being added to it as a child.</p>
+
+<p>The <em>autoLoad</em> member of the foreign key definition is a flag
+that specifies whether child rows should be auto-loaded for that
+relationship when a parent row is loaded.</p>
+
+<h2>Duplicate Row Objects<a name="auto4"/></h2>
+
+<p>If a reflector tries to load an instance of a rowObject that
+is already loaded, it will return a reference to the existing
+rowObject rather than creating a new instance. The reflector maintains
+a cache of weak references to all loaded row objects by their
+unique keys for this purpose.</p>
+
+<h2>Updating Row Objects<a name="auto5"/></h2>
+
+<p>RowObjects have a <code>dirty</code> member attribute that is
+set to 1 when any of the member attributes of the instance that
+map to database columns are changed. This dirty flag can be used
+to tell when RowObjects need to be updated back to the database.
+In addition, the <code>setDirty</code> method can be overridden
+to provide more complex automated handling such as dirty lists
+(be sure to call the base class setDirty though!).</p>
+
+<p>When it is determined that a RowObject instance is dirty and
+need to have its state updated into the database, pass that object
+to the <code>updateRow</code> method of the Reflector. For example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">reflector</span>.<span class="py-src-variable">updateRow</span>(<span class="py-src-variable">room</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">onUpdated</span>)
+</pre>
+
+<p>For more complex behavior, the reflector can generate the SQL
+for the update but not perform the update. This can be useful
+for batching up multiple updates into single requests. For example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">updateSQL</span> = <span class="py-src-variable">reflector</span>.<span class="py-src-variable">updateRowSQL</span>(<span class="py-src-variable">room</span>)
+</pre>
+
+<h2>Deleting Row Objects<a name="auto6"/></h2>
+
+<p>To delete a row from a database pass the RowObject instance
+for that row to the Reflector <code>deleteRow</code> method.
+Deleting the python Rowobject instance does <em>not</em> automatically
+delete the row from the database. For example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">reflector</span>.<span class="py-src-variable">deleteRow</span>(<span class="py-src-variable">room</span>)
+</pre>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/servers.html b/vendor/Twisted-10.0.0/doc/core/howto/servers.html
new file mode 100644
index 0000000000..567aeca300
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/servers.html
@@ -0,0 +1,429 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Writing Servers</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Writing Servers</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Protocols</a></li><ul><li><a href="#auto2">Using the Protocol</a></li><li><a href="#auto3">Helper Protocols</a></li><li><a href="#auto4">State Machines</a></li></ul><li><a href="#auto5">Factories</a></li><ul><li><a href="#auto6">Putting it All Together</a></li></ul></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>Twisted is a framework designed to be very flexible and let
+ you write powerful servers. The cost of this flexibility is a
+ few layers in the way to writing your server.</p>
+
+ <p>This document describes the
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">Protocol</a></code>
+ layer, where you
+ implement protocol parsing and handling. If you are implementing
+ an application then you should read this document second, after
+ first reading the top level overview of how to begin writing your
+ Twisted application, in <a href="plugin.html" shape="rect">Writing Plug-Ins
+ for Twisted</a>. This document is only relevant to TCP, SSL and
+ Unix socket servers, there is a <a href="udp.html" shape="rect">separate document</a>
+ for UDP.</p>
+
+ <p>Your protocol handling class will usually subclass <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Protocol.html" title="twisted.internet.protocol.Protocol">twisted.internet.protocol.Protocol</a></code>. Most
+ protocol handlers inherit either from this class or from one of
+ its convenience children. An instance of the protocol class
+ might be instantiated per-connection, on demand, and might go
+ away when the connection is finished. This means that
+ persistent configuration is not saved in the
+ <code>Protocol</code>.</p>
+
+ <p>The persistent configuration is kept in a Factory class,
+ which usually inherits from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">twisted.internet.protocol.Factory</a></code>. The
+ default factory class just instantiates each <code>Protocol</code>, and then
+ sets on it an attribute called <code>factory</code> which
+ points to itself. This lets every <code>Protocol</code> access,
+ and possibly modify, the persistent configuration.</p>
+
+ <p>It is usually useful to be able to offer the same service on
+ multiple ports or network addresses. This is why the <code>Factory</code>
+ does not listen to connections, and in fact does not
+ know anything about the network. See <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTCP.listenTCP.html" title="twisted.internet.interfaces.IReactorTCP.listenTCP">twisted.internet.interfaces.IReactorTCP.listenTCP</a></code>,
+ and the other <code>IReactor*.listen*</code> APIs for more
+ information.</p>
+
+ <p>This document will explain each step of the way.</p>
+
+ <h2>Protocols<a name="auto1"/></h2>
+
+ <p>As mentioned above, this, along with auxiliary classes and
+ functions, is where most of the code is. A Twisted protocol
+ handles data in an asynchronous manner. What this means is that
+ the protocol never waits for an event, but rather responds to
+ events as they arrive from the network.</p>
+
+ <p>Here is a simple example:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+</pre>
+
+ <p>This is one of the simplest protocols. It simply writes back
+ whatever is written to it, and does not respond to all events. Here is an
+ example of a Protocol responding to another event:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span>(<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;An apple a day keeps the doctor away\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+</pre>
+
+ <p>This protocol responds to the initial connection with a well
+ known quote, and then terminates the connection.</p>
+
+ <p>The connectionMade event is usually where set up of the
+ connection object happens, as well as any initial greetings (as
+ in the QOTD protocol above, which is actually based on RFC
+ 865). The <code>connectionLost</code> event is where tearing down of any
+ connection-specific objects is done. Here is an example:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">numProtocols</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">numProtocols</span>+<span class="py-src-number">1</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">numProtocols</span> &gt; <span class="py-src-number">100</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Too many connections, try later&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">numProtocols</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">numProtocols</span>-<span class="py-src-number">1</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+</pre>
+
+ <p>Here <code>connectionMade</code> and
+ <code>connectionLost</code> cooperate to keep a count of the
+ active protocols in the factory. <code>connectionMade</code>
+ immediately closes the connection if there are too many active
+ protocols.</p>
+
+ <h3>Using the Protocol<a name="auto2"/></h3>
+
+ <p>In this section, I will explain how to test your protocol
+ easily. (In order to see how you should write a production-grade Twisted
+ server, though, you should read the <a href="plugin.html" shape="rect">Writing Plug-Ins
+ for Twisted</a> HOWTO as well).</p>
+
+ <p>Here is code that will run the QOTD server discussed
+ earlier</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>, <span class="py-src-variable">Factory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span>(<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;An apple a day keeps the doctor away\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-comment"># Next lines are magic:</span>
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Factory</span>()
+<span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">QOTD</span>
+
+<span class="py-src-comment"># 8007 is the port you want to run under. Choose something &gt;1024</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8007</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Don't worry about the last 6 magic lines -- you will
+ understand what they do later in the document.</p>
+
+ <h3>Helper Protocols<a name="auto3"/></h3>
+
+ <p>Many protocols build upon similar lower-level abstraction.
+ The most popular in internet protocols is being line-based.
+ Lines are usually terminated with a CR-LF combinations.</p>
+
+ <p>However, quite a few protocols are mixed - they have
+ line-based sections and then raw data sections. Examples
+ include HTTP/1.1 and the Freenet protocol.</p>
+
+ <p>For those cases, there is the <code>LineReceiver</code>
+ protocol. This protocol dispatches to two different event
+ handlers - <code>lineReceived</code> and
+ <code>rawDataReceived</code>. By default, only
+ <code>lineReceived</code> will be called, once for each line.
+ However, if <code>setRawMode</code> is called, the protocol
+ will call <code>rawDataReceived</code> until
+ <code>setLineMode</code> is called, which returns it to using
+ <code>lineReceived</code>.</p>
+
+ <p>Here is an example for a simple use of the line
+ receiver:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">basic</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LineReceiver</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Answer</span>(<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-variable">answers</span> = {<span class="py-src-string">'How are you?'</span>: <span class="py-src-string">'Fine'</span>, <span class="py-src-variable">None</span> : <span class="py-src-string">&quot;I don't know what you mean&quot;</span>}
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">answers</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-variable">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">answers</span>[<span class="py-src-variable">line</span>])
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">answers</span>[<span class="py-src-variable">None</span>])
+</pre>
+
+ <p>Note that the delimiter is not part of the line.</p>
+
+ <p>Several other, less popular, helpers exist, such as a
+ netstring based protocol and a prefixed-message-length
+ protocol.</p>
+
+ <h3>State Machines<a name="auto4"/></h3>
+
+ <p>Many Twisted protocol handlers need to write a state machine
+ to record the state they are at. Here are some pieces of advice
+ which help to write state machines:</p>
+
+ <ul>
+ <li>Don't write big state machines. Prefer to write a state
+ machine which deals with one level of abstraction at a
+ time.</li>
+
+ <li>Use Python's dynamicity to create open ended state
+ machines. See, for example, the code for the SMTP
+ client.</li>
+
+ <li>Don't mix application-specific code with Protocol
+ handling code. When the protocol handler has to make an
+ application-specific call, keep it as a method call.</li>
+ </ul>
+
+ <h2>Factories<a name="auto5"/></h2>
+
+ <p>As mentioned before, usually the class <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.Factory.html" title="twisted.internet.protocol.Factory">twisted.internet.protocol.Factory</a></code> works,
+ and there is no need to subclass it. However, sometimes there
+ can be factory-specific configuration of the protocols, or
+ other considerations. In those cases, there is a need to
+ subclass <code>Factory</code>.</p>
+
+ <p>For a factory which simply instantiates instances of a
+ specific protocol class, simply instantiate
+ <code>Factory</code>, and sets its <code>protocol</code> attribute:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">wire</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Echo</span>
+
+<span class="py-src-variable">myFactory</span> = <span class="py-src-variable">Factory</span>()
+<span class="py-src-variable">myFactory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">Echo</span>
+</pre>
+
+ <p>If there is a need to easily construct factories for a
+ specific configuration, a factory function is often useful:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span>(<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">quote</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">makeQOTDFactory</span>(<span class="py-src-parameter">quote</span>=<span class="py-src-parameter">None</span>):
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">Factory</span>()
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">QOTD</span>
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">quote</span> = <span class="py-src-variable">quote</span> <span class="py-src-keyword">or</span> <span class="py-src-string">'An apple a day keeps the doctor away'</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">factory</span>
+</pre>
+
+ <p>A Factory has two methods to perform application-specific
+ building up and tearing down (since a Factory is frequently
+ persisted, it is often not appropriate to do them in <code>__init__</code>
+ or <code>__del__</code>, and would frequently be too early or too late).</p>
+
+ <p>Here is an example of a factory which allows its Protocols
+ to write to a special log-file:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">basic</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LineReceiver</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LoggingProtocol</span>(<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">fp</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">line</span>+<span class="py-src-string">'\n'</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LogfileFactory</span>(<span class="py-src-parameter">Factory</span>):
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">LoggingProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">fileName</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">file</span> = <span class="py-src-variable">fileName</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">fp</span> = <span class="py-src-variable">open</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">file</span>, <span class="py-src-string">'a'</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">fp</span>.<span class="py-src-variable">close</span>()
+</pre>
+
+ <h3>Putting it All Together<a name="auto6"/></h3>
+
+ <p>So, you know what factories are, and want to run the QOTD
+ with configurable quote server, do you? No problems, here is an
+ example.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTD</span>(<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">quote</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QOTDFactory</span>(<span class="py-src-parameter">Factory</span>):
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">QOTD</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">quote</span>=<span class="py-src-parameter">None</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">quote</span> = <span class="py-src-variable">quote</span> <span class="py-src-keyword">or</span> <span class="py-src-string">'An apple a day keeps the doctor away'</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8007</span>, <span class="py-src-variable">QOTDFactory</span>(<span class="py-src-string">&quot;configurable quote&quot;</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>The only lines you might not understand are the last two.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTCP.listenTCP.html" title="twisted.internet.interfaces.IReactorTCP.listenTCP">listenTCP</a></code> is
+the method which connects a <code>Factory</code> to the network.
+It uses the reactor interface, which lets many different loops handle
+the networking code, without modifying end-user code, like this.
+As mentioned above, if you want to write your code to be a production-grade
+Twisted server, and not a mere 20-line hack, you will want to
+use <a href="application.html" shape="rect">the Application object</a>.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/ssl.html b/vendor/Twisted-10.0.0/doc/core/howto/ssl.html
new file mode 100644
index 0000000000..b67efa318d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/ssl.html
@@ -0,0 +1,550 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using SSL in Twisted</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using SSL in Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">SSL echo server and client without client authentication</a></li><ul><li><a href="#auto2">SSL echo server</a></li><li><a href="#auto3">SSL echo client</a></li></ul><li><a href="#auto4">Using startTLS</a></li><ul><li><a href="#auto5">startTLS server</a></li><li><a href="#auto6">startTLS client</a></li></ul><li><a href="#auto7">Client authentication</a></li><ul><li><a href="#auto8">Client-authenticating server</a></li><li><a href="#auto9">Client with certificates</a></li></ul><li><a href="#auto10">Other facilities</a></li><li><a href="#auto11">Conclusion</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>This document describes how to use SSL in Twisted servers and clients. It
+ assumes that you know what SSL is, what some of the major reasons to use it
+ are, and how to generate your own SSL certificates, in particular self-signed
+ certificates. It also assumes that you are comfortable with creating TCP
+ servers and clients as described in the <a href="servers.html" shape="rect">server howto
+ </a> and <a href="clients.html" shape="rect">client howto</a>. After reading this
+ document you should be able to create servers and clients that can use SSL to
+ encrypt their connections, switch from using an unencrypted channel to an
+ encrypted one mid-connection, and require client authentication.</p>
+
+ <p>Using SSL in Twisted requires that you have
+ <a href="http://pyopenssl.sf.net" shape="rect">pyOpenSSL</a> installed. A quick test to
+ verify that you do is to run <code>from OpenSSL import SSL</code> at a
+ python prompt and not get an error.</p>
+
+ <p>SSL connections require SSL contexts. These contexts are generated by a
+ <code>ContextFactory</code> that maintains state like the SSL method, private
+ key file name, and certificate file name.</p>
+
+ <p>Instead of using listenTCP and connectTCP to create a connection, use
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorSSL.listenSSL.html" title="twisted.internet.interfaces.IReactorSSL.listenSSL">listenSSL</a></code> and
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorSSL.connectSSL.html" title="twisted.internet.interfaces.IReactorSSL.connectSSL">connectSSL</a></code> for a
+ server and client respectively. These methods take a contextFactory as an
+ additional argument.</p>
+
+ <p>The basic server context factory is
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.ssl.ContextFactory.html" title="twisted.internet.ssl.ContextFactory">twisted.internet.ssl.ContextFactory</a></code>, and the basic
+ client context factory is
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.ssl.ClientContextFactory.html" title="twisted.internet.ssl.ClientContextFactory">twisted.internet.ssl.ClientContextFactory</a></code>. They can
+ be used as-is or subclassed.
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.ssl.DefaultOpenSSLContextFactory.html" title="twisted.internet.ssl.DefaultOpenSSLContextFactory">twisted.internet.ssl.DefaultOpenSSLContextFactory</a></code>
+ is a convenience server class that subclasses <code>ContextFactory</code>
+ and adds default parameters to the SSL handshake and connection. Another
+ useful class is
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.ssl.CertificateOptions.html" title="twisted.internet.ssl.CertificateOptions">twisted.internet.ssl.CertificateOptions</a></code>; it is a
+ factory for SSL context objects that lets you specify many of the common
+ verification and session options so it can do the proper pyOpenSSL
+ initialization for you.</p>
+
+ <p>Those are the big immediate differences between TCP and SSL connections,
+ so let's look at an example. In it and all subsequent examples it is assumed
+ that keys and certificates for the server, certificate authority, and client
+ should they exist live in a <i>keys/</i> subdirectory of the directory
+ containing the example code, and that the certificates are self-signed.</p>
+
+ <h2>SSL echo server and client without client authentication<a name="auto1"/></h2>
+
+ <p>Authentication and encryption are two separate parts of the SSL protocol.
+ The server almost always needs a key and certificate to authenticate itself
+ to the client but is usually configured to allow encrypted connections with
+ unauthenticated clients who don't have certificates. This common case is
+ demonstrated first by adding SSL support to the echo client and server in
+ the <a href="../examples/index.html" shape="rect">core examples</a>.</p>
+
+ <h3>SSL echo server<a name="auto2"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ssl</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-string">&quot;&quot;&quot;As soon as any data is received, write it back.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">Factory</span>()
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">Echo</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenSSL</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>,
+ <span class="py-src-variable">ssl</span>.<span class="py-src-variable">DefaultOpenSSLContextFactory</span>(
+ <span class="py-src-string">'keys/server.key'</span>, <span class="py-src-string">'keys/server.crt'</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h3>SSL echo client<a name="auto3"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ssl</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ClientFactory</span>, <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClient</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;hello, world&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;hello, world!&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Server said:&quot;</span>, <span class="py-src-variable">data</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClientFactory</span>(<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">EchoClient</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Connection failed - goodbye!&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Connection lost - goodbye!&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">EchoClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectSSL</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>, <span class="py-src-variable">ssl</span>.<span class="py-src-variable">ClientContextFactory</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Contexts are created according to a specified method.
+ <code>SSLv3_METHOD</code>, <code>SSLv23_METHOD</code>, and
+ <code>TLSv1_METHOD</code> are the valid constants that represent SSL methods
+ to use when creating a context object. <code>DefaultOpenSSLContextFactory</code> and
+ <code>ClientContextFactory</code> default to using <code>SSL.SSLv23_METHOD</code> as their
+ method, and it is compatible for communication with all the other methods
+ listed above. An older method constant, <code>SSLv2_METHOD</code>, exists but
+ is explicitly disallowed in both <code>DefaultOpenSSLContextFactory</code> and
+ <code>ClientContextFactory</code> for being insecure by calling
+ <code>set_options(SSL.OP_NO_SSLv2)</code> on their contexts. See
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.ssl.html" title="twisted.internet.ssl">twisted.internet.ssl</a></code> for additional comments.</p>
+
+ <h2>Using startTLS<a name="auto4"/></h2>
+
+ <p>If you want to switch from unencrypted to encrypted traffic
+ mid-connection, you'll need to turn on SSL with <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.ITLSTransport.startTLS.html" title="twisted.internet.interfaces.ITLSTransport.startTLS">startTLS</a></code> on both
+ ends of the connection at the same time via some agreed-upon signal like the
+ reception of a particular message. You can readily verify the switch to an
+ encrypted channel by examining the packet payloads with a tool like
+ <a href="http://www.wireshark.org/" shape="rect">Wireshark</a>.</p>
+
+ <h3>startTLS server<a name="auto5"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">ssl</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ServerFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">basic</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LineReceiver</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">TLSServer</span>(<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;received: &quot;</span> + <span class="py-src-variable">line</span>
+
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">line</span> == <span class="py-src-string">&quot;STARTTLS&quot;</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;-- Switching to TLS&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-string">'READY'</span>)
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">ServerTLSContext</span>(
+ <span class="py-src-variable">privateKeyFileName</span>=<span class="py-src-string">'keys/server.key'</span>,
+ <span class="py-src-variable">certificateFileName</span>=<span class="py-src-string">'keys/server.crt'</span>,
+ )
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">startTLS</span>(<span class="py-src-variable">ctx</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ServerTLSContext</span>(<span class="py-src-parameter">ssl</span>.<span class="py-src-parameter">DefaultOpenSSLContextFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, *<span class="py-src-parameter">args</span>, **<span class="py-src-parameter">kw</span>):
+ <span class="py-src-variable">kw</span>[<span class="py-src-string">'sslmethod'</span>] = <span class="py-src-variable">SSL</span>.<span class="py-src-variable">TLSv1_METHOD</span>
+ <span class="py-src-variable">ssl</span>.<span class="py-src-variable">DefaultOpenSSLContextFactory</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>, *<span class="py-src-variable">args</span>, **<span class="py-src-variable">kw</span>)
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">TLSServer</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h3>startTLS client<a name="auto6"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">ssl</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ClientFactory</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span>.<span class="py-src-variable">basic</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LineReceiver</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClientTLSContext</span>(<span class="py-src-parameter">ssl</span>.<span class="py-src-parameter">ClientContextFactory</span>):
+ <span class="py-src-variable">isClient</span> = <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getContext</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">SSL</span>.<span class="py-src-variable">Context</span>(<span class="py-src-variable">SSL</span>.<span class="py-src-variable">TLSv1_METHOD</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">TLSClient</span>(<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-variable">pretext</span> = [
+ <span class="py-src-string">&quot;first line&quot;</span>,
+ <span class="py-src-string">&quot;last thing before TLS starts&quot;</span>,
+ <span class="py-src-string">&quot;STARTTLS&quot;</span>]
+
+ <span class="py-src-variable">posttext</span> = [
+ <span class="py-src-string">&quot;first thing after TLS started&quot;</span>,
+ <span class="py-src-string">&quot;last thing ever&quot;</span>]
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">l</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">pretext</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">l</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;received: &quot;</span> + <span class="py-src-variable">line</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">line</span> == <span class="py-src-string">&quot;READY&quot;</span>:
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">ClientTLSContext</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">startTLS</span>(<span class="py-src-variable">ctx</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">l</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">posttext</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sendLine</span>(<span class="py-src-variable">l</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">TLSClientFactory</span>(<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">TLSClient</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;connection failed: &quot;</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">getErrorMessage</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;connection lost: &quot;</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">getErrorMessage</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">&quot;__main__&quot;</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">TLSClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p><code>startTLS</code> is a transport method that gets passed a context.
+ It is invoked at an agreed-upon time in the data reception method of the
+ client and server protocols. The <code>ServerTLSContext</code> and
+ <code>ClientTLSContext</code> classes used above inherit from the basic
+ server and client context factories used in the earlier echo examples and
+ illustrate two more ways of setting an SSL method.</p>
+
+ <h2>Client authentication<a name="auto7"/></h2>
+
+ <p>Server and client-side changes to require client authentication fall
+ largely under the dominion of pyOpenSSL, but few examples seem to exist on
+ the web so for completeness a sample server and client are provided here.</p>
+
+ <h3>Client-authenticating server<a name="auto8"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ssl</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Factory</span>, <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">verifyCallback</span>(<span class="py-src-parameter">connection</span>, <span class="py-src-parameter">x509</span>, <span class="py-src-parameter">errnum</span>, <span class="py-src-parameter">errdepth</span>, <span class="py-src-parameter">ok</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">ok</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'invalid cert from subject:'</span>, <span class="py-src-variable">x509</span>.<span class="py-src-variable">get_subject</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">False</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Certs are fine&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">True</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">Factory</span>()
+ <span class="py-src-variable">factory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">Echo</span>
+
+ <span class="py-src-variable">myContextFactory</span> = <span class="py-src-variable">ssl</span>.<span class="py-src-variable">DefaultOpenSSLContextFactory</span>(
+ <span class="py-src-string">'keys/server.key'</span>, <span class="py-src-string">'keys/server.crt'</span>
+ )
+
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">myContextFactory</span>.<span class="py-src-variable">getContext</span>()
+
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">set_verify</span>(
+ <span class="py-src-variable">SSL</span>.<span class="py-src-variable">VERIFY_PEER</span> | <span class="py-src-variable">SSL</span>.<span class="py-src-variable">VERIFY_FAIL_IF_NO_PEER_CERT</span>,
+ <span class="py-src-variable">verifyCallback</span>
+ )
+
+ <span class="py-src-comment"># Since we have self-signed certs we have to explicitly</span>
+ <span class="py-src-comment"># tell the server to trust them.</span>
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">load_verify_locations</span>(<span class="py-src-string">&quot;keys/ca.pem&quot;</span>)
+
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenSSL</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>, <span class="py-src-variable">myContextFactory</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Use the <code>set_verify</code> method to set the verification mode for a
+ context object and the verification callback. The mode is either
+ <code>VERIFY_NONE</code> or <code>VERIFY_PEER</code>. If
+ <code>VERIFY_PEER</code> is set, the mode can be augmented by
+ <code>VERIFY_FAIL_IF_NO_PEER_CERT</code> and/or
+ <code>VERIFY_CLIENT_ONCE</code>.</p>
+
+ <p>The callback takes as its arguments a connection object, X509 object,
+ error number, error depth, and return code. The purpose of the callback is
+ to allow you to enforce additional restrictions on the verification. Thus,
+ if the return code is False, you should return False; if the return code is
+ True <i>and</i> further verification passes, return True.</p>
+
+
+ <h3>Client with certificates<a name="auto9"/></h3>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ssl</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ClientFactory</span>, <span class="py-src-variable">Protocol</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClient</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;hello, world&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;hello, world!&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Server said:&quot;</span>, <span class="py-src-variable">data</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">EchoClientFactory</span>(<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">EchoClient</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Connection failed - goodbye!&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">connector</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Connection lost - goodbye!&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CtxFactory</span>(<span class="py-src-parameter">ssl</span>.<span class="py-src-parameter">ClientContextFactory</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getContext</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">method</span> = <span class="py-src-variable">SSL</span>.<span class="py-src-variable">SSLv23_METHOD</span>
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">ssl</span>.<span class="py-src-variable">ClientContextFactory</span>.<span class="py-src-variable">getContext</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_certificate_file</span>(<span class="py-src-string">'keys/client.crt'</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_privatekey_file</span>(<span class="py-src-string">'keys/client.key'</span>)
+
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">ctx</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">EchoClientFactory</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectSSL</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">8000</span>, <span class="py-src-variable">factory</span>, <span class="py-src-variable">CtxFactory</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h2>Other facilities<a name="auto10"/></h2>
+
+ <p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.protocols.amp.html" title="twisted.protocols.amp">twisted.protocols.amp</a></code> supports encrypted
+ connections and exposes a <code>startTLS</code> method one can use or
+ subclass. <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.html" title="twisted.web">twisted.web</a></code> has built-in SSL support in
+ its <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.client.html" title="twisted.web.client">client</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.http.html" title="twisted.web.http">http</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.xmlrpc.html" title="twisted.web.xmlrpc">xmlrpc</a></code> modules.</p>
+
+ <h2>Conclusion<a name="auto11"/></h2>
+
+ <p>After reading through this tutorial, you should be able to: </p>
+ <ul>
+ <li>Use <code>listenSSL</code> and <code>connectSSL</code> to create servers and clients that use
+ SSL</li>
+ <li>Use <code>startTLS</code> to switch a channel from being unencrypted to using SSL
+ mid-connection</li>
+ <li>Add server and client support for client authentication</li>
+ </ul>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/stylesheet-unprocessed.css b/vendor/Twisted-10.0.0/doc/core/howto/stylesheet-unprocessed.css
new file mode 100644
index 0000000000..e4a62cc158
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/stylesheet-unprocessed.css
@@ -0,0 +1,20 @@
+
+span.footnote {
+ vertical-align: super;
+ font-size: small;
+}
+
+span.footnote:before
+{
+ content: "[Footnote: ";
+}
+
+span.footnote:after
+{
+ content: "]";
+}
+
+div.note:before
+{
+ content: "Note: ";
+}
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/stylesheet.css b/vendor/Twisted-10.0.0/doc/core/howto/stylesheet.css
new file mode 100644
index 0000000000..3c5961e7e3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/stylesheet.css
@@ -0,0 +1,189 @@
+
+body
+{
+ margin-left: 2em;
+ margin-right: 2em;
+ border: 0px;
+ padding: 0px;
+ font-family: sans-serif;
+ }
+
+.done { color: #005500; background-color: #99ff99 }
+.notdone { color: #550000; background-color: #ff9999;}
+
+pre
+{
+ padding: 1em;
+ border: thin black solid;
+ line-height: 1.2em;
+}
+
+.boxed
+{
+ padding: 1em;
+ border: thin black solid;
+}
+
+.shell
+{
+ background-color: #ffffdd;
+}
+
+.python
+{
+ background-color: #dddddd;
+}
+
+.htmlsource
+{
+ background-color: #dddddd;
+}
+
+.py-prototype
+{
+ background-color: #ddddff;
+}
+
+
+.python-interpreter
+{
+ background-color: #ddddff;
+}
+
+.doit
+{
+ border: thin blue dashed ;
+ background-color: #0ef
+}
+
+.py-src-comment
+{
+ color: #1111CC
+}
+
+.py-src-keyword
+{
+ color: #3333CC;
+ font-weight: bold;
+ line-height: 1.0em
+}
+
+.py-src-parameter
+{
+ color: #000066;
+ font-weight: bold;
+ line-height: 1.0em
+}
+
+.py-src-identifier
+{
+ color: #CC0000
+}
+
+.py-src-string
+{
+
+ color: #115511
+}
+
+.py-src-endmarker
+{
+ display: block; /* IE hack; prevents following line from being sucked into the py-listing box. */
+}
+
+.py-linenumber
+{
+ background-color: #cdcdcd;
+ float: left;
+ margin-top: 0px;
+ width: 4.0em
+}
+
+.py-listing, .html-listing, .listing
+{
+ margin: 1ex;
+ border: thin solid black;
+ background-color: #eee;
+}
+
+.py-listing pre, .html-listing pre, .listing pre
+{
+ margin: 0px;
+ border: none;
+ border-bottom: thin solid black;
+}
+
+.py-listing .python
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.html-listing .htmlsource
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.caption
+{
+ text-align: center;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+}
+
+.filename
+{
+ font-style: italic;
+ }
+
+.manhole-output
+{
+ color: blue;
+}
+
+hr
+{
+ display: inline;
+ }
+
+ul
+{
+ padding: 0px;
+ margin: 0px;
+ margin-left: 1em;
+ padding-left: 1em;
+ border-left: 1em;
+ }
+
+li
+{
+ padding: 2px;
+ }
+
+dt
+{
+ font-weight: bold;
+ margin-left: 1ex;
+ }
+
+dd
+{
+ margin-bottom: 1em;
+ }
+
+div.note
+{
+ background-color: #FFFFCC;
+ margin-top: 1ex;
+ margin-left: 5%;
+ margin-right: 5%;
+ padding-top: 1ex;
+ padding-left: 5%;
+ padding-right: 5%;
+ border: thin black solid;
+}
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tap.html b/vendor/Twisted-10.0.0/doc/core/howto/tap.html
new file mode 100644
index 0000000000..6a4485f215
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tap.html
@@ -0,0 +1,346 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Writing a twistd Plugin</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Writing a twistd Plugin</h1>
+ <div class="toc"><ol><li><a href="#auto0">Goals</a></li><li><a href="#auto1">A note on .tap files</a></li><li><a href="#auto2">Alternatives to twistd plugins</a></li><li><a href="#auto3">Creating the plugin</a></li><li><a href="#auto4">Using cred with your TAP</a></li><li><a href="#auto5">Conclusion</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p>This document describes adding subcommands to
+the <code>twistd</code> command, as a way to facilitate the deployment
+of your applications. <em>(This feature was added in Twisted 2.5)</em></p>
+
+<p>The target audience of this document are those that have developed
+a Twisted application which needs a command line-based deployment
+mechanism.</p>
+
+<p>There are a few prerequisites to understanding this document:</p>
+<ul>
+ <li>A basic understanding of the Twisted Plugin System (i.e.,
+ the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.html" title="twisted.plugin">twisted.plugin</a></code> module) is
+ necessary, however, step-by-step instructions will be
+ given. Reading <a href="plugin.html" shape="rect">The Twisted Plugin
+ System</a> is recommended, in particular the <q>Extending an
+ Existing Program</q> section.</li>
+ <li>The <a href="application.html" shape="rect">Application</a> infrastructure
+ is used in <code>twistd</code> plugins; in particular, you should
+ know how to expose your program's functionality as a Service.</li>
+ <li>In order to parse command line arguments, the <code>twistd</code> plugin
+ mechanism relies
+ on <code>twisted.python.usage</code>, which is documented
+ in <a href="options.html" shape="rect">Using usage.Options</a>.</li>
+</ul>
+
+<h2>Goals<a name="auto0"/></h2>
+
+<p>After reading this document, the reader should be able to expose
+their Service-using application as a subcommand
+of <code>twistd</code>, taking into consideration whatever was passed
+on the command line.</p>
+
+<h2>A note on .tap files<a name="auto1"/></h2>
+
+<p>Readers may be confused about a historical file type associated
+with Twisted, the <code>.tap</code> file. This was a kind of file that
+was generated by a program named <code>mktap</code> and
+which <code>twistd</code> can read. <code>.tap</code> files are
+deprecated; this document has nothing to do with them, although the
+technology described herein is very closely related to the old
+system. Existing plugins that were written for the mktap system are
+compatible with this <code>twistd</code> plugin system; the following
+commands,
+</p>
+
+<pre class="shell" xml:space="preserve">
+$ mktap [foo] [options...]
+$ twistd -n -f [foo].tap
+</pre>
+
+<p>
+are equivalent to the command:</p>
+
+<pre class="shell" xml:space="preserve">$ twistd -n [foo] [options...]</pre>
+
+<h2>Alternatives to twistd plugins<a name="auto2"/></h2>
+<p>The major alternative to the twistd plugin mechanism is the <code>.tac</code>
+file, which is a simple script to be used with the
+twistd <code>-y/--python</code> parameter. The twistd plugin mechanism
+exists to offer a more extensible command-line-driven interface to
+your application. For more information on <code>.tac</code> files, see
+the document <a href="application.html" shape="rect">Using the Twisted Application
+Framework</a>.</p>
+
+
+<h2>Creating the plugin<a name="auto3"/></h2>
+
+<p>The following directory structure is assumed of your project:</p>
+
+<ul>
+ <li><strong>MyProject</strong> - Top level directory
+ <ul>
+ <li><strong>myproject</strong> - Python package
+ <ul><li><strong>__init__.py</strong></li></ul>
+ </li>
+ </ul>
+ </li>
+</ul>
+
+<p>
+ During development of your project, Twisted plugins can be loaded
+ from a special directory in your project, assuming your top level
+ directory ends up in sys.path. Create a directory
+ named <code>twisted</code> containing a directory
+ named <code>plugins</code>, and add a file
+ named <code>myproject_plugin.py</code> to it. This file will contain your
+ plugin. Note that you should <em>not</em> add any __init__.py files
+ to this directory structure, and the plugin file should <em>not</em>
+ be named <code>myproject.py</code> (because that would conflict with
+ your project's module name).
+</p>
+
+<p>
+ In this file, define an object which <em>provides</em> the interfaces
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugin.IPlugin.html" title="twisted.plugin.IPlugin">twisted.plugin.IPlugin</a></code>
+ and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IServiceMaker.html" title="twisted.application.service.IServiceMaker">twisted.application.service.IServiceMaker</a></code>.
+</p>
+
+<p>The <code>tapname</code> attribute of your IServiceMaker provider
+will be used as the subcommand name in a command
+like <code class="shell">twistd [subcommand] [args...]</code>, and
+the <code>options</code> attribute (which should be
+a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.usage.Options.html" title="twisted.python.usage.Options">usage.Options</a></code>
+subclass) will be used to parse the given args.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">service</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IServiceMaker</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myproject</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">MyFactory</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+ <span class="py-src-variable">optParameters</span> = [[<span class="py-src-string">&quot;port&quot;</span>, <span class="py-src-string">&quot;p&quot;</span>, <span class="py-src-number">1235</span>, <span class="py-src-string">&quot;The port number to listen on.&quot;</span>]]
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyServiceMaker</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IServiceMaker</span>, <span class="py-src-variable">IPlugin</span>)
+ <span class="py-src-variable">tapname</span> = <span class="py-src-string">&quot;myproject&quot;</span>
+ <span class="py-src-variable">description</span> = <span class="py-src-string">&quot;Run this! It'll make your dog happy.&quot;</span>
+ <span class="py-src-variable">options</span> = <span class="py-src-variable">Options</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">options</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Construct a TCPServer from a factory defined in myproject.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">options</span>[<span class="py-src-string">&quot;port&quot;</span>]), <span class="py-src-variable">MyFactory</span>())
+
+
+<span class="py-src-comment"># Now construct an object which *provides* the relevant interfaces</span>
+<span class="py-src-comment"># The name of this variable is irrelevant, as long as there is *some*</span>
+<span class="py-src-comment"># name bound to a provider of IPlugin and IServiceMaker.</span>
+
+<span class="py-src-variable">serviceMaker</span> = <span class="py-src-variable">MyServiceMaker</span>()
+</pre>
+
+<p>
+ Now running <code class="shell">twistd --help</code> should
+ print <code>myproject</code> in the list of available subcommands,
+ followed by the description that we specified in the
+ plugin. <code class="shell">twistd -n myproject</code> would,
+ assuming we defined a <code>MyFactory</code> factory
+ inside <code>myproject</code>, start a listening server on port 1235
+ with that factory.
+</p>
+
+<h2>Using cred with your TAP<a name="auto4"/></h2>
+
+<p>
+ Twisted ships with a robust authentication framework to use with
+ your application. If your server needs authentication functionality,
+ and you haven't read about <a href="cred.html" shape="rect">twisted.cred</a>
+ yet, read up on it first.
+</p>
+
+<p>
+ If you are building a twistd plugin and you want to support a wide
+ variety of authentication patterns, Twisted provides an easy-to-use
+ mixin for your Options subclass:
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.strcred.AuthOptionMixin.html" title="twisted.cred.strcred.AuthOptionMixin">strcred.AuthOptionMixin</a></code>.
+ The following code is an example of using this mixin:
+</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">credentials</span>, <span class="py-src-variable">portal</span>, <span class="py-src-variable">strcred</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">service</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IServiceMaker</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myserver</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">myservice</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ServerOptions</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>, <span class="py-src-parameter">strcred</span>.<span class="py-src-parameter">AuthOptionMixin</span>):
+ <span class="py-src-comment"># This part is optional; it tells AuthOptionMixin what</span>
+ <span class="py-src-comment"># kinds of credential interfaces the user can give us.</span>
+ <span class="py-src-variable">supportedInterfaces</span> = (<span class="py-src-variable">credentials</span>.<span class="py-src-variable">IUsernamePassword</span>,)
+
+ <span class="py-src-variable">optParameters</span> = [
+ [<span class="py-src-string">&quot;port&quot;</span>, <span class="py-src-string">&quot;p&quot;</span>, <span class="py-src-number">1234</span>, <span class="py-src-string">&quot;Server port number&quot;</span>],
+ [<span class="py-src-string">&quot;host&quot;</span>, <span class="py-src-string">&quot;h&quot;</span>, <span class="py-src-string">&quot;localhost&quot;</span>, <span class="py-src-string">&quot;Server hostname&quot;</span>]]
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyServerServiceMaker</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IServiceMaker</span>, <span class="py-src-variable">IPlugin</span>)
+ <span class="py-src-variable">tapname</span> = <span class="py-src-string">&quot;myserver&quot;</span>
+ <span class="py-src-variable">description</span> = <span class="py-src-string">&quot;This server does nothing productive.&quot;</span>
+ <span class="py-src-variable">options</span> = <span class="py-src-variable">ServerOptions</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">options</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Construct a service object.&quot;&quot;&quot;</span>
+ <span class="py-src-comment"># The realm is a custom object that your server defines.</span>
+ <span class="py-src-variable">realm</span> = <span class="py-src-variable">myservice</span>.<span class="py-src-variable">MyServerRealm</span>(<span class="py-src-variable">options</span>[<span class="py-src-string">&quot;host&quot;</span>])
+
+ <span class="py-src-comment"># The portal is something Cred can provide, as long as</span>
+ <span class="py-src-comment"># you have a list of checkers that you'll support. This</span>
+ <span class="py-src-comment"># list is provided my AuthOptionMixin.</span>
+ <span class="py-src-variable">portal</span> = <span class="py-src-variable">portal</span>.<span class="py-src-variable">Portal</span>(<span class="py-src-variable">realm</span>, <span class="py-src-variable">options</span>[<span class="py-src-string">&quot;credCheckers&quot;</span>])
+
+ <span class="py-src-comment"># OR, if you know you might get multiple interfaces, and</span>
+ <span class="py-src-comment"># only want to give your application one of them, you</span>
+ <span class="py-src-comment"># also have that option with AuthOptionMixin:</span>
+ <span class="py-src-variable">interface</span> = <span class="py-src-variable">credentials</span>.<span class="py-src-variable">IUsernamePassword</span>
+ <span class="py-src-variable">portal</span> = <span class="py-src-variable">portal</span>.<span class="py-src-variable">Portal</span>(<span class="py-src-variable">realm</span>, <span class="py-src-variable">options</span>[<span class="py-src-string">&quot;credInterfaces&quot;</span>][<span class="py-src-variable">interface</span>])
+
+ <span class="py-src-comment"># The protocol factory is, like the realm, something you implement.</span>
+ <span class="py-src-variable">factory</span> = <span class="py-src-variable">myservice</span>.<span class="py-src-variable">ServerFactory</span>(<span class="py-src-variable">realm</span>, <span class="py-src-variable">portal</span>)
+
+ <span class="py-src-comment"># Finally, return a service that will listen for connections.</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">options</span>[<span class="py-src-string">&quot;port&quot;</span>]), <span class="py-src-variable">factory</span>)
+
+
+<span class="py-src-comment"># As in our example above, we have to construct an object that</span>
+<span class="py-src-comment"># provides the IPlugin and IServiceMaker interfaces.</span>
+
+<span class="py-src-variable">serviceMaker</span> = <span class="py-src-variable">MyServerServiceMaker</span>()
+</pre>
+
+<p>
+ Now that you have your TAP configured to support any authentication
+ we can throw at it, you're ready to use it. Here is an example of
+ starting your server using the /etc/passwd file for
+ authentication. (Clearly, this won't work on servers with shadow
+ passwords.)
+</p>
+
+<pre class="shell" xml:space="preserve">
+$ twistd myserver --auth passwd:/etc/passwd
+</pre>
+
+<p>
+ For a full list of cred plugins supported, see <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.plugins.html" title="twisted.plugins">twisted.plugins</a></code>, or use the command-line help:
+</p>
+
+<pre class="shell" xml:space="preserve">
+$ twistd myserver --help-auth
+$ twistd myserver --help-auth-type passwd
+</pre>
+
+<h2>Conclusion<a name="auto5"/></h2>
+
+<p>You should now be able to</p>
+<ul>
+ <li>Create a twistd plugin</li>
+ <li>Incorporate authentication into your plugin</li>
+ <li>Use it from your development environment</li>
+ <li>Install it correctly and use it in deployment</li>
+</ul>
+
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/telnet.html b/vendor/Twisted-10.0.0/doc/core/howto/telnet.html
new file mode 100644
index 0000000000..cfc0665f27
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/telnet.html
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using telnet to manipulate a twisted server</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using telnet to manipulate a twisted server</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>To start things off, we're going to create a simple server that just
+gives you remote access to a Python interpreter. We will use a telnet client
+to access this server.</p>
+
+<p>Run <code class="shell">twistd telnet -p 4040 -u admin -w admin</code> at
+your shell prompt. The Application has a telnet server that you specified to
+be on port 4040, and it will start listening for connections on this port. Try
+connecting with your favorite telnet utility to 127.0.0.1 port 4040.</p>
+
+<pre class="shell" xml:space="preserve">
+$ <em>telnet localhost 4040</em>
+Trying 127.0.0.1...
+Connected to localhost.
+Escape character is '^]'.
+
+twisted.manhole.telnet.ShellFactory
+Twisted 1.1.0
+username: <em>admin</em>
+password: <em>admin</em>
+&gt;&gt;&gt;
+</pre>
+
+<p>Now, you should see a Python prompt --
+<code>&gt;&gt;&gt;</code>. You can type any valid Python code
+here. Let's try looking around.</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; <em>dir()</em>
+['__builtins__']
+</pre>
+
+<p>Ok, not much. let's play a little more:</p>
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; <em>import __main__</em>
+&gt;&gt;&gt; <em>dir(__main__)</em>
+['__builtins__', '__doc__', '__name__', 'os', 'run', 'string', 'sys']
+
+&gt;&gt;&gt; <em>service</em>
+&lt;twisted.application.internet.TCPServer instance at 0x10270f48&gt;
+&gt;&gt;&gt; <em>service._port</em>
+&lt;twisted.manhole.telnet.ShellFactory on 4040&gt;
+&gt;&gt;&gt; <em>service.parent</em>
+&lt;twisted.application.service.MultiService instance at 0x1024d7a8&gt;
+</pre>
+
+<p>The service object is the service used to serve the telnet shell,
+and that it is listening on port 4040 with something called a
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.manhole.telnet.ShellFactory.html" title="twisted.manhole.telnet.ShellFactory">ShellFactory</a></code>.
+Its parent is a <code class="python">twisted.application.service.MultiService</code>,
+a collection of services. We can keep getting the parent attribute
+of services until we hit the root of all services.</p>
+
+<p>As you can see, this is quite useful - we can introspect a
+running process, see the internal objects, and even change
+their attributes. The telnet server can of course be used from straight
+Python code; you can see how to do this by reading the code for
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.tap.telnet.html" title="twisted.tap.telnet">twisted.tap.telnet</a></code>.</p>
+
+<p>A final note - if you want access to be more secure, you can even
+have the telnet server use SSL. Assuming you have the appropriate
+certificate and private key files, you can <code class="shell">twistd
+telnet -p ssl:443:privateKey=mykey.pem:certKey=cert.pem -u admin -w
+admin</code>. See <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.strports.html" title="twisted.application.strports">twisted.application.strports</a></code> for more examples of
+options for listening on a port.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/template.tpl b/vendor/Twisted-10.0.0/doc/core/howto/template.tpl
new file mode 100644
index 0000000000..1fbb5177bb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/template.tpl
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+<title>Twisted Documentation: </title>
+<link type="text/css" rel="stylesheet"
+href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title"></h1>
+ <div class="toc"></div>
+ <div class="body">
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: </span>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/testing.html b/vendor/Twisted-10.0.0/doc/core/howto/testing.html
new file mode 100644
index 0000000000..dd5d3143ab
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/testing.html
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Writing tests for Twisted code using Trial</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Writing tests for Twisted code using Trial</h1>
+ <div class="toc"><ol><li><a href="#auto0">Trial basics</a></li><li><a href="#auto1">Trial directories</a></li><li><a href="#auto2">Twisted-specific quirks: reactor, Deferreds, callLater</a></li><ul><li><a href="#auto3">Leave the Reactor as you found it</a></li><li><a href="#auto4">Using Timers to Detect Failing Tests</a></li><li><a href="#auto5">Interacting with warnings in tests</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Trial basics<a name="auto0"/></h2>
+
+<p><strong>Trial</strong> is Twisted's testing framework. It provides a
+library for writing test cases and utility functions for working with the
+Twisted environment in your tests, and a command-line utility for running your
+tests. Trial is built on the Python standard library's <code>unittest</code>
+module.</p>
+
+<p>To run all the Twisted tests, do:</p>
+
+<pre class="shell" xml:space="preserve">
+$ trial twisted
+</pre>
+
+<p>Refer to the Trial man page for other command-line options.</p>
+
+<h2>Trial directories<a name="auto1"/></h2>
+
+<p>You might notice a new <code class="shell">_trial_temp</code> folder in the
+current working directory after Trial completes the tests. This folder is the
+working directory for the Trial process. It can be used by unit tests and
+allows them to write whatever data they like to disk, and not worry
+about polluting the current working directory.</p>
+
+<p>Folders named <code class="shell">_trial_temp-&lt;counter&gt;</code> are
+created if two instances of Trial are run in parallel from the same directory,
+so as to avoid giving two different test-runs the same temporary directory.</p>
+
+<p>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.lockfile.html" title="twisted.python.lockfile">twisted.python.lockfile</a></code> utility is used to lock
+the <code class="shell">_trial_temp</code> directories. On Linux, this results
+in symlinks to pids. On Windows, directories are created with a single file with
+a pid as the contents. These lock files will be cleaned up if Trial exits normally
+and otherwise they will be left behind. They should be cleaned up the next time
+Trial tries to use the directory they lock, but it's also safe to delete them
+manually if desired.</p>
+
+<h2>Twisted-specific quirks: reactor, Deferreds, callLater<a name="auto2"/></h2>
+
+<p>The standard Python <code>unittest</code> framework, from which Trial is
+derived, is ideal for testing code with a fairly linear flow of control.
+Twisted is an asynchronous networking framework which provides a clean,
+sensible way to establish functions that are run in response to events (like
+timers and incoming data), which creates a highly non-linear flow of control.
+Trial has a few extensions which help to test this kind of code. This section
+provides some hints on how to use these extensions and how to best structure
+your tests.</p>
+
+<h3>Leave the Reactor as you found it<a name="auto3"/></h3>
+
+<p>Trial runs the entire test suite (over four thousand tests) in a single
+process, with a single reactor. Therefore it is important that your test
+leave the reactor in the same state as it found it. Leftover timers may
+expire during somebody else's unsuspecting test. Leftover connection attempts
+may complete (and fail) during a later test. These lead to intermittent
+failures that wander from test to test and are very time-consuming to track
+down.</p>
+
+<p>If your test leaves event sources in the reactor, Trial will fail the test.
+The <code>tearDown</code> method is a good place to put cleanup code: it is
+always run regardless of whether your test passes or fails (like a bare <code>
+except</code> clause in a try-except construct). Exceptions in <code>tearDown
+</code> are flagged as errors and flunk the test.
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.trial.unittest.TestCase.addCleanup.html" title="twisted.trial.unittest.TestCase.addCleanup">TestCase.addCleanup</a></code> is
+another useful tool for cleaning up. With it, you can register callables to
+clean up resources as the test allocates them. Generally, code should be
+written so that only resources allocated in the tests need to be cleaned up in
+the tests. Resources which are allocated internally by the implementation
+should be cleaned up by the implementation.</p>
+
+<p>If your code uses Deferreds or depends on the reactor running, you can
+return a Deferred from your test method, setUp, or tearDown and Trial will
+do the right thing. That is, it will run the reactor for you until the
+Deferred has triggered and its callbacks have been run. Don't use
+<code>reactor.run()</code>, <code>reactor.stop()</code>, <code>reactor.crash()
+</code>or <code>reactor.iterate()</code> in your tests.</p>
+
+<p>Calls to <code>reactor.callLater</code> create <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IDelayedCall.html" title="twisted.internet.interfaces.IDelayedCall">IDelayedCall</a></code>s. These need to be run
+or cancelled during a test, otherwise they will outlive the test. This would
+be bad, because they could interfere with a later test, causing confusing
+failures in unrelated tests! For this reason, Trial checks the reactor to make
+sure there are no leftover <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IDelayedCall.html" title="twisted.internet.interfaces.IDelayedCall">IDelayedCall</a></code>s in the reactor after a
+test, and will fail the test if there are. The cleanest and simplest way to
+make sure this all works is to return a Deferred from your test.</p>
+
+<p>Similarly, sockets created during a test should be closed by the end of the
+test. This applies to both listening ports and client connections. So, calls
+to <code>reactor.listenTCP</code> (and <code>listenUNIX</code>, and so on)
+return <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IListeningPort.html" title="twisted.internet.interfaces.IListeningPort">IListeningPort</a></code>s, and these should be
+cleaned up before a test ends by calling their <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IListeningPort.stopListening.html" title="twisted.internet.interfaces.IListeningPort.stopListening">stopListening</a></code> method.
+Calls to <code>reactor.connectTCP</code> return <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IConnector.html" title="twisted.internet.interfaces.IConnector">IConnector</a></code>s, which should be cleaned
+up by calling their <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IConnector.disconnect.html" title="twisted.internet.interfaces.IConnector.disconnect">disconnect</a></code> method. Trial
+will warn about unclosed sockets.</p>
+
+<p>The golden rule is: If your tests call a function which returns a Deferred,
+your test should return a Deferred.</p>
+
+<h3>Using Timers to Detect Failing Tests<a name="auto4"/></h3>
+
+<p>It is common for tests to establish some kind of fail-safe timeout that
+will terminate the test in case something unexpected has happened and none of
+the normal test-failure paths are followed. This timeout puts an upper bound
+on the time that a test can consume, and prevents the entire test suite from
+stalling because of a single test. This is especially important for the
+Twisted test suite, because it is run automatically by the buildbot whenever
+changes are committed to the Subversion repository.</p>
+
+<p>The way to do this in Trial is to set the <code>.timeout</code> attribute
+on your unit test method. Set the attribute to the number of seconds you wish
+to elapse before the test raises a timeout error. Trial has a default timeout
+which will be applied even if the <code>timeout</code> attribute is not set.
+The Trial default timeout is usually sufficient and should be overridden only
+in unusual cases.</p>
+
+<h3>Interacting with warnings in tests<a name="auto5"/></h3>
+
+<p>Trial includes specific support for interacting with Python's
+<code>warnings</code> module. This support allows warning-emitting code to
+be written test-driven, just as any other code would be. It also improves
+the way in which warnings reporting when a test suite is running.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.trial.unittest.TestCase.assertWarns.html" title="twisted.trial.unittest.TestCase.assertWarns">TestCase.assertWarns</a></code> and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.trial.unittest.TestCase.flushWarnings.html" title="twisted.trial.unittest.TestCase.flushWarnings">TestCase.flushWarnings</a></code>
+allow tests to be written which make assertions about what warnings have
+been emitted during a particular test method. <code>flushWarnings</code> is
+the new method and has a simpler and more flexible API and should be
+preferred when writing new code. In order to test a warning with
+<code>flushWarnings</code>, write a test which first invokes the code which
+will emit a warning and then calls <code>flushWarnings</code> and makes
+assertions about the result. For example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">test_warning</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">warnings</span>.<span class="py-src-variable">warn</span>(<span class="py-src-string">&quot;foo is bad&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">assertEqual</span>(<span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">flushWarnings</span>()), <span class="py-src-number">1</span>)
+</pre>
+
+<p>Warnings emitted in tests which are not flushed will be included by the
+default reporter in its output after the result of the test. If Python's
+warnings filter system (see <a href="http://docs.python.org/using/cmdline.html#cmdoption-W" shape="rect">the -W command
+line option to Python</a>) is configured to treat a warning as an error,
+then unflushed warnings will causes tests to fail and will be included in
+the summary section of the default reporter. Note that unlike usual
+operation, when <code>warnings.warn</code> is called as part of a test
+method, it will not raise an exception when warnings have been configured as
+errors. However, if called outside of a test method (for example, at module
+scope in a test module or a module imported by a test module) then it
+<em>will</em> raise an exception.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/threading.html b/vendor/Twisted-10.0.0/doc/core/howto/threading.html
new file mode 100644
index 0000000000..2c7a511192
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/threading.html
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using Threads in Twisted</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using Threads in Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Running code in a thread-safe manner</a></li><li><a href="#auto1">Running code in threads</a></li><li><a href="#auto2">Utility Methods</a></li><li><a href="#auto3">Managing the Thread Pool</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Running code in a thread-safe manner<a name="auto0"/></h2>
+
+ <p>Most code in Twisted is not thread-safe. For example,
+ writing data to a transport from a protocol is not thread-safe.
+ Therefore, we want a way to schedule methods to be run in the
+ main event loop. This can be done using the function <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorThreads.callFromThread.html" title="twisted.internet.interfaces.IReactorThreads.callFromThread">twisted.internet.interfaces.IReactorThreads.callFromThread</a></code>:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">notThreadSafe</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;do something that isn't thread-safe&quot;&quot;&quot;</span>
+ <span class="py-src-comment"># ...</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">threadSafeScheduler</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Run in thread-safe manner.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callFromThread</span>(<span class="py-src-variable">notThreadSafe</span>, <span class="py-src-number">3</span>) <span class="py-src-comment"># will run 'notThreadSafe(3)'</span>
+ <span class="py-src-comment"># in the event loop</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h2>Running code in threads<a name="auto1"/></h2>
+
+ <p>Sometimes we may want to run methods in threads - for
+ example, in order to access blocking APIs. Twisted provides
+ methods for doing so using the IReactorThreads API (<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorThreads.html" title="twisted.internet.interfaces.IReactorThreads">twisted.internet.interfaces.IReactorThreads</a></code>).
+ Additional utility functions are provided in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.threads.html" title="twisted.internet.threads">twisted.internet.threads</a></code>. Basically, these
+ methods allow us to queue methods to be run by a thread
+ pool.</p>
+
+ <p>For example, to run a method in a thread we can do:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">aSillyBlockingMethod</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+ <span class="py-src-variable">time</span>.<span class="py-src-variable">sleep</span>(<span class="py-src-number">2</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">x</span>
+
+<span class="py-src-comment"># run method in thread</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callInThread</span>(<span class="py-src-variable">aSillyBlockingMethod</span>, <span class="py-src-string">&quot;2 seconds have passed&quot;</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <h2>Utility Methods<a name="auto2"/></h2>
+
+ <p>The utility methods are not part of the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">twisted.internet.reactor</a></code> APIs, but are implemented
+ in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.threads.html" title="twisted.internet.threads">twisted.internet.threads</a></code>.</p>
+
+ <p>If we have multiple methods to run sequentially within a thread,
+ we can do:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">threads</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">aSillyBlockingMethodOne</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+ <span class="py-src-variable">time</span>.<span class="py-src-variable">sleep</span>(<span class="py-src-number">2</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">x</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">aSillyBlockingMethodTwo</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">x</span>
+
+<span class="py-src-comment"># run both methods sequentially in a thread</span>
+<span class="py-src-variable">commands</span> = [(<span class="py-src-variable">aSillyBlockingMethodOne</span>, [<span class="py-src-string">&quot;Calling First&quot;</span>], {})]
+<span class="py-src-variable">commands</span>.<span class="py-src-variable">append</span>((<span class="py-src-variable">aSillyBlockingMethodTwo</span>, [<span class="py-src-string">&quot;And the second&quot;</span>], {}))
+<span class="py-src-variable">threads</span>.<span class="py-src-variable">callMultipleInThread</span>(<span class="py-src-variable">commands</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>For functions whose results we wish to get, we can have the
+ result returned as a Deferred:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>, <span class="py-src-variable">threads</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">doLongCalculation</span>():
+ <span class="py-src-comment"># .... do long calculation here ...</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-number">3</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printResult</span>(<span class="py-src-parameter">x</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">x</span>
+
+<span class="py-src-comment"># run method in thread and get result as defer.Deferred</span>
+<span class="py-src-variable">d</span> = <span class="py-src-variable">threads</span>.<span class="py-src-variable">deferToThread</span>(<span class="py-src-variable">doLongCalculation</span>)
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">printResult</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>If you wish to call a method in the reactor thread and get its result,
+ you can use <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.threads.blockingCallFromThread.html" title="twisted.internet.threads.blockingCallFromThread">blockingCallFromThread</a></code>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">threads</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">getPage</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">error</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Error</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">inThread</span>():
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">threads</span>.<span class="py-src-variable">blockingCallFromThread</span>(
+ <span class="py-src-variable">reactor</span>, <span class="py-src-variable">getPage</span>, <span class="py-src-string">&quot;http://twistedmatrix.com/&quot;</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">Error</span>, <span class="py-src-variable">exc</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">exc</span>
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">result</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callFromThread</span>(<span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callInThread</span>(<span class="py-src-variable">inThread</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p><code>blockingCallFromThread</code> will return the object or raise
+ the exception returned or raised by the function passed to it. If the
+ function passed to it returns a Deferred, it will return the value the
+ Deferred is called back with or raise the exception it is errbacked
+ with.</p>
+
+ <h2>Managing the Thread Pool<a name="auto3"/></h2>
+
+ <p>The thread pool is implemented by <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.threadpool.ThreadPool.html" title="twisted.python.threadpool.ThreadPool">twisted.python.threadpool.ThreadPool</a></code>.</p>
+
+ <p>We may want to modify the size of the threadpool, increasing
+ or decreasing the number of threads in use. We can do this
+ do this quite easily:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">suggestThreadPoolSize</span>(<span class="py-src-number">30</span>)
+</pre>
+
+ <p>The default size of the thread pool depends on the reactor being used;
+ the default reactor uses a minimum size of 5 and a maximum size of 10. Be
+ careful that you understand threads and their resource usage before
+ drastically altering the thread pool sizes.</p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/time.html b/vendor/Twisted-10.0.0/doc/core/howto/time.html
new file mode 100644
index 0000000000..6365c36781
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/time.html
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Scheduling tasks for the future</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Scheduling tasks for the future</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <p>Let's say we want to run a task X seconds in the future.
+ The way to do that is defined in the reactor interface <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTime.html" title="twisted.internet.interfaces.IReactorTime">twisted.internet.interfaces.IReactorTime</a></code>:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">f</span>(<span class="py-src-parameter">s</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;this will run 3.5 seconds after it was scheduled: %s&quot;</span> % <span class="py-src-variable">s</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">3.5</span>, <span class="py-src-variable">f</span>, <span class="py-src-string">&quot;hello, world&quot;</span>)
+
+<span class="py-src-comment"># f() will only be called if the event loop is started.</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>If the result of the function is important or if it may be necessary
+ to handle exceptions it raises, then the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.task.deferLater.html" title="twisted.internet.task.deferLater">twisted.internet.task.deferLater</a></code> utility conveniently
+ takes care of creating a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> and setting up a delayed
+ call:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">task</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">f</span>(<span class="py-src-parameter">s</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;This will run 3.5 seconds after it was scheduled: %s&quot;</span> % <span class="py-src-variable">s</span>
+
+<span class="py-src-variable">d</span> = <span class="py-src-variable">task</span>.<span class="py-src-variable">deferLater</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">3.5</span>, <span class="py-src-variable">f</span>, <span class="py-src-string">&quot;hello, world&quot;</span>)
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">called</span>(<span class="py-src-parameter">result</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">result</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">called</span>)
+
+<span class="py-src-comment"># f() will only be called if the event loop is started.</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>If we want a task to run every X seconds repeatedly, we can
+ use <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.task.LoopingCall.html" title="twisted.internet.task.LoopingCall">twisted.internet.task.LoopingCall</a></code>:</p>
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">task</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">runEverySecond</span>():
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;a second has passed&quot;</span>
+
+<span class="py-src-variable">l</span> = <span class="py-src-variable">task</span>.<span class="py-src-variable">LoopingCall</span>(<span class="py-src-variable">runEverySecond</span>)
+<span class="py-src-variable">l</span>.<span class="py-src-variable">start</span>(<span class="py-src-number">1.0</span>) <span class="py-src-comment"># call every second</span>
+
+<span class="py-src-comment"># l.stop() will stop the looping calls</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>If we want to cancel a task that we've scheduled:</p>
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">f</span>():
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;I'll never run.&quot;</span>
+
+<span class="py-src-variable">callID</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">f</span>)
+<span class="py-src-variable">callID</span>.<span class="py-src-variable">cancel</span>()
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>As with all reactor-based code, in order for scheduling to work the reactor must be started using <code class="python">reactor.run()</code>.</p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/backends.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/backends.html
new file mode 100644
index 0000000000..a5c3a69fd6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/backends.html
@@ -0,0 +1,1207 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: pluggable backends</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: pluggable backends</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Another Back-end</a></li><li><a href="#auto2">Yet Another Back-end: Doing the Standard Thing</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the fifth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part we will add new several new backends to our finger service
+using the component-based architecture developed in <a href="components.html" shape="rect">The Evolution of Finger: moving to a
+component based architecture</a>. This will show just how convenient it is to
+implement new back-ends when we move to a component based architecture. Note
+that here we also use an interface we previously wrote, FingerSetterFactory,
+by supporting one single method. We manage to preserve the service's ignorance
+of the network.</p>
+
+<h2>Another Back-end<a name="auto1"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">pwd</span>
+
+<span class="py-src-comment"># Another back-end</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LocalFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-comment"># need a local finger daemon running for this to work</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessOutput</span>(<span class="py-src-string">&quot;finger&quot;</span>, [<span class="py-src-variable">user</span>])
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>([])
+
+
+<span class="py-src-variable">f</span> = <span class="py-src-variable">LocalFingerService</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19b_changes.py"><span class="filename">listings/finger/finger19b_changes.py</span></a></div></div>
+<p>
+Full source code here: <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">pwd</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">formatUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">l</span> = [<span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;ul&gt;'</span>+<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">l</span>)+<span class="py-src-string">'&lt;/ul&gt;'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">formatUsers</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>==<span class="py-src-string">&quot;&quot;</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>:
+ <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;'</span>%<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">'&lt;p&gt;%s&lt;/p&gt;'</span>%<span class="py-src-variable">m</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+<span class="py-src-comment"># Another back-end</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LocalFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-comment"># need a local finger daemon running for this to work</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessOutput</span>(<span class="py-src-string">&quot;finger&quot;</span>, [<span class="py-src-variable">user</span>])
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>([])
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">LocalFingerService</span>()
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19b.tac"><span class="filename">listings/finger/finger19b.tac</span></a></div></div>
+</p>
+
+<p>We've already written this, but now we get more for less work:
+the network code is completely separate from the back-end.</p>
+
+
+<h2>Yet Another Back-end: Doing the Standard Thing<a name="auto2"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">pwd</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+
+
+<span class="py-src-comment"># Yet another back-end</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LocalFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">entry</span> = <span class="py-src-variable">pwd</span>.<span class="py-src-variable">getpwnam</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">KeyError</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">&quot;No such user&quot;</span>)
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">file</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">entry</span>[<span class="py-src-number">5</span>],<span class="py-src-string">'.plan'</span>))
+ <span class="py-src-keyword">except</span> (<span class="py-src-variable">IOError</span>, <span class="py-src-variable">OSError</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">&quot;No such user&quot;</span>)
+ <span class="py-src-variable">data</span> = <span class="py-src-variable">f</span>.<span class="py-src-variable">read</span>()
+ <span class="py-src-variable">data</span> = <span class="py-src-variable">data</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">close</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">data</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>([])
+
+
+
+<span class="py-src-variable">f</span> = <span class="py-src-variable">LocalFingerService</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19c_changes.py"><span class="filename">listings/finger/finger19c_changes.py</span></a></div></div>
+<p>
+Full source code here: <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">pwd</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>():
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">formatUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">l</span> = [<span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;ul&gt;'</span>+<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">l</span>)+<span class="py-src-string">'&lt;/ul&gt;'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">formatUsers</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>==<span class="py-src-string">&quot;&quot;</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>:
+ <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;'</span>%<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">'&lt;p&gt;%s&lt;/p&gt;'</span>%<span class="py-src-variable">m</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+<span class="py-src-comment"># Yet another back-end</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">LocalFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">entry</span> = <span class="py-src-variable">pwd</span>.<span class="py-src-variable">getpwnam</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">KeyError</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">&quot;No such user&quot;</span>)
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">file</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">entry</span>[<span class="py-src-number">5</span>],<span class="py-src-string">'.plan'</span>))
+ <span class="py-src-keyword">except</span> (<span class="py-src-variable">IOError</span>, <span class="py-src-variable">OSError</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">&quot;No such user&quot;</span>)
+ <span class="py-src-variable">data</span> = <span class="py-src-variable">f</span>.<span class="py-src-variable">read</span>()
+ <span class="py-src-variable">data</span> = <span class="py-src-variable">data</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">close</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">data</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>([])
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">LocalFingerService</span>()
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19c.tac"><span class="filename">listings/finger/finger19c.tac</span></a></div></div>
+</p>
+
+<p>Not much to say except that now we
+can be churn out backends like crazy. Feel like doing a back-end
+for Advogato, for example? Dig out the XML-RPC client support Twisted
+has, and get to work!</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/client.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/client.html
new file mode 100644
index 0000000000..446a7b04d7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/client.html
@@ -0,0 +1,260 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: a Twisted finger client</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: a Twisted finger client</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Finger Proxy</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the ninth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we develop a client for the finger server: a proxy finger
+server which forwards requests to another finger server.</p>
+
+<h2>Finger Proxy<a name="auto1"/></h2>
+
+<p>Writing new clients with Twisted is much like writing new servers.
+We implement the protocol, which just gathers up all the data, and
+give it to the factory. The factory keeps a deferred which is triggered
+if the connection either fails or succeeds. When we use the client,
+we first make sure the deferred will never fail, by producing a message
+in that case. Implementing a wrapper around client which just returns
+the deferred is a common pattern. While less flexible than
+using the factory directly, it's also more convenient.</p>
+
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+</p><span class="py-src-comment"># finger proxy</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">defer</span>, <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerClient</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">Protocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">&quot;\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">buf</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">buf</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">data</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">gotData</span>(<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">buf</span>))
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerClientFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerClient</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span> = <span class="py-src-variable">defer</span>.<span class="py-src-variable">Deferred</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">clientConnectionFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">_</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>.<span class="py-src-variable">errback</span>(<span class="py-src-variable">reason</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">gotData</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">d</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">data</span>)
+
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">finger</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">host</span>, <span class="py-src-parameter">port</span>=<span class="py-src-number">79</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">FingerClientFactory</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>, <span class="py-src-variable">f</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>.<span class="py-src-variable">d</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ProxyFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">host</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'@'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-keyword">except</span>:
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">host</span> = <span class="py-src-string">'127.0.0.1'</span>
+ <span class="py-src-variable">ret</span> = <span class="py-src-variable">finger</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">host</span>)
+ <span class="py-src-variable">ret</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-string">&quot;Could not connect to remote host&quot;</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">ret</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>([])
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">ProxyFingerService</span>()
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">7779</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)).<span class="py-src-variable">setServiceParent</span>(
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/finger/fingerproxy.tac"><span class="filename">listings/finger/fingerproxy.tac</span></a></div></div>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/components.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/components.html
new file mode 100644
index 0000000000..baf3ce3a33
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/components.html
@@ -0,0 +1,1068 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: moving to a component based architecture</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: moving to a component based architecture</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Write Maintainable Code</a></li><li><a href="#auto2">Advantages of Latest Version</a></li><li><a href="#auto3">Aspect-Oriented Programming</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the fourth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this section of the tutorial, we'll move our code to a component
+architecture so that adding new features is trivial.
+See <a href="../components.html" shape="rect">Interfaces and Adapters</a> for a more
+complete discussion of components.</p>
+
+<h2>Write Maintainable Code<a name="auto1"/></h2>
+
+
+<p>In the last version, the service class was three times longer than
+any other class, and was hard to understand. This was because it turned
+out to have multiple responsibilities. It had to know how to access
+user information, by rereading the file every half minute,
+but also how to display itself in a myriad of protocols. Here, we
+used the component-based architecture that Twisted provides to achieve
+a separation of concerns. All the service is responsible for, now,
+is supporting getUser/getUsers. It declares its support via a call to
+zope.interface.implements. Then, adapters are used to make this service
+look like an appropriate class for various things: for supplying
+a finger factory to TCPServer, for supplying a resource to site's
+constructor, and to provide an IRC client factory for TCPClient.
+All the adapters use are the methods in FingerService they are
+declared to use: getUser/getUsers. We could, of course,
+skip the interfaces and let the configuration code use
+things like FingerFactoryFromService(f) directly. However, using
+interfaces provides the same flexibility inheritance gives: future
+subclasses can override the adapters.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">formatUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">l</span> = [<span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;ul&gt;'</span>+<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">l</span>)+<span class="py-src-string">'&lt;/ul&gt;'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">formatUsers</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>==<span class="py-src-string">&quot;&quot;</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>:
+ <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;'</span>%<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">'&lt;p&gt;%s&lt;/p&gt;'</span>%<span class="py-src-variable">m</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19.tac"><span class="filename">listings/finger/finger19.tac</span></a></div></div>
+
+<h2>Advantages of Latest Version<a name="auto2"/></h2>
+
+<ul>
+<li>Readable -- each class is short</li>
+<li>Maintainable -- each class knows only about interfaces</li>
+<li>Dependencies between code parts are minimized</li>
+<li>Example: writing a new IFingerService is easy</li>
+</ul>
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-comment"># Advantages of latest version</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MemoryFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>([<span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">IFingerSetterService</span>])
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+
+
+<span class="py-src-variable">f</span> = <span class="py-src-variable">MemoryFingerService</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">IFingerSetterFactory</span>(<span class="py-src-variable">f</span>), <span class="py-src-variable">interface</span>=<span class="py-src-string">'127.0.0.1'</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19a_changes.py"><span class="filename">listings/finger/finger19a_changes.py</span></a></div></div>
+<p>
+Full source code here: <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">formatUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">l</span> = [<span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;ul&gt;'</span>+<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">l</span>)+<span class="py-src-string">'&lt;/ul&gt;'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">formatUsers</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>==<span class="py-src-string">&quot;&quot;</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>:
+ <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;'</span>%<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">'&lt;p&gt;%s&lt;/p&gt;'</span>%<span class="py-src-variable">m</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MemoryFingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>([<span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">IFingerSetterService</span>])
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">MemoryFingerService</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">IFingerSetterFactory</span>(<span class="py-src-variable">f</span>), <span class="py-src-variable">interface</span>=<span class="py-src-string">'127.0.0.1'</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger19a.tac"><span class="filename">listings/finger/finger19a.tac</span></a></div></div>
+</p>
+
+<h2>Aspect-Oriented Programming<a name="auto3"/></h2>
+
+<p>At last, an example of aspect-oriented programming that isn't about logging
+or timing. This code is actually useful! Watch how aspect-oriented programming
+helps you write less code and have fewer dependencies!
+</p>
+
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/configuration.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/configuration.html
new file mode 100644
index 0000000000..ccf782124a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/configuration.html
@@ -0,0 +1,792 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: configuration and packaging of the finger service</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: configuration and packaging of the finger service</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Plugins</a></li><li><a href="#auto2">OS Integration</a></li><ul><li><a href="#auto3">Debian</a></li><li><a href="#auto4">Red Hat / Mandrake</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the eleventh part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we make it easier for non-programmers to configure a finger
+server and show how to package it in the .deb and RPM package formats. Plugins
+are discussed further in the <a href="../plugin.html" shape="rect">Twisted Plugin System</a>
+howto. .tap files are covered in <a href="../tap.html" shape="rect">Writing a twistd
+Plugin</a>, and .tac applications are covered
+in <a href="../application.html" shape="rect">Using the Twisted Application
+Framework</a>.</p>
+
+<h2>Plugins<a name="auto1"/></h2>
+
+<p>So far, the user had to be somewhat of a programmer to be able to configure
+stuff. Maybe we can eliminate even that? Move old code to finger/__init__.py and...</p>
+<p>
+Full source code for finger module here: <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+</p><span class="py-src-comment"># finger.py module</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>, <span class="py-src-variable">log</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">template</span> = <span class="py-src-string">&quot;&quot;&quot;&lt;html&gt;&lt;head&gt;&lt;title&gt;Users&lt;/title&gt;&lt;/head&gt;&lt;body&gt;
+ &lt;h1&gt;Users&lt;/h1&gt;
+ &lt;ul&gt;
+ %(users)s
+ &lt;/ul&gt;
+ &lt;/body&gt;
+ &lt;/html&gt;&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span> == <span class="py-src-string">''</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>
+ <span class="py-src-keyword">elif</span> <span class="py-src-variable">path</span> == <span class="py-src-string">'RPC2'</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">users</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">cbUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">template</span> % {<span class="py-src-string">'users'</span>: <span class="py-src-string">''</span>.<span class="py-src-variable">join</span>([
+ <span class="py-src-comment"># Name should be quoted properly these uses.</span>
+ <span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">name</span>, <span class="py-src-variable">name</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">name</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>])})
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+ <span class="py-src-variable">users</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbUsers</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">ebUsers</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>(<span class="py-src-variable">err</span>, <span class="py-src-string">&quot;UserStatusTree failed&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+ <span class="py-src-variable">users</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">ebUsers</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">template</span>=<span class="py-src-string">'''&lt;html&gt;&lt;head&gt;&lt;title&gt;%(title)s&lt;/title&gt;&lt;/head&gt;
+ &lt;body&gt;&lt;h1&gt;%(name)s&lt;/h1&gt;&lt;p&gt;%(status)s&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;'''</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">cbStatus</span>(<span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">template</span> % {
+ <span class="py-src-string">'title'</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>,
+ <span class="py-src-string">'name'</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>,
+ <span class="py-src-string">'status'</span>: <span class="py-src-variable">status</span>})
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+ <span class="py-src-variable">status</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbStatus</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">ebStatus</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>(<span class="py-src-variable">err</span>, <span class="py-src-string">&quot;UserStatus failed&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+ <span class="py-src-variable">status</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">ebStatus</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IPerspectiveFinger</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">username</span>):
+ <span class="py-src-string">&quot;&quot;&quot;return a user's status&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;return a user's status&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PerspectiveFingerFromService</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPerspectiveFinger</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">username</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">PerspectiveFingerFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IPerspectiveFinger</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ServerContextFactory</span>:
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getContext</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">SSL</span>.<span class="py-src-variable">Context</span>(<span class="py-src-variable">SSL</span>.<span class="py-src-variable">SSLv23_METHOD</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_certificate_file</span>(<span class="py-src-string">'server.pem'</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_privatekey_file</span>(<span class="py-src-string">'server.pem'</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">ctx</span>
+
+
+
+
+<span class="py-src-comment"># Easy configuration</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span>(<span class="py-src-parameter">config</span>):
+ <span class="py-src-comment"># finger on port 79</span>
+ <span class="py-src-variable">s</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">MultiService</span>()
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-variable">config</span>[<span class="py-src-string">'file'</span>])
+ <span class="py-src-variable">h</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>))
+ <span class="py-src-variable">h</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+
+ <span class="py-src-comment"># website on port 8000</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>)
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">templateDirectory</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'templates'</span>]
+ <span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>)
+ <span class="py-src-variable">j</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">site</span>)
+ <span class="py-src-variable">j</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># ssl on port 443</span>
+<span class="py-src-comment"># if config.get('ssl'):</span>
+<span class="py-src-comment"># k = internet.SSLServer(443, site, ServerContextFactory())</span>
+<span class="py-src-comment"># k.setServiceParent(s)</span>
+
+ <span class="py-src-comment"># irc fingerbot</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-string">'ircnick'</span>):
+ <span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+ <span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'ircnick'</span>]
+ <span class="py-src-variable">ircserver</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'ircserver'</span>]
+ <span class="py-src-variable">b</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">ircserver</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>)
+ <span class="py-src-variable">b</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># Pespective Broker on port 8889</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-string">'pbport'</span>):
+ <span class="py-src-variable">m</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(
+ <span class="py-src-variable">int</span>(<span class="py-src-variable">config</span>[<span class="py-src-string">'pbport'</span>]),
+ <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">IPerspectiveFinger</span>(<span class="py-src-variable">f</span>)))
+ <span class="py-src-variable">m</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">s</span>
+</pre><div class="caption">finger module - <a href="listings/finger/finger/finger.py"><span class="filename">listings/finger/finger/finger.py</span></a></div></div>
+</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-comment"># finger/tap.py</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">interfaces</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">usage</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">finger</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Options</span>(<span class="py-src-parameter">usage</span>.<span class="py-src-parameter">Options</span>):
+
+ <span class="py-src-variable">optParameters</span> = [
+ [<span class="py-src-string">'file'</span>, <span class="py-src-string">'f'</span>, <span class="py-src-string">'/etc/users'</span>],
+ [<span class="py-src-string">'templates'</span>, <span class="py-src-string">'t'</span>, <span class="py-src-string">'/usr/share/finger/templates'</span>],
+ [<span class="py-src-string">'ircnick'</span>, <span class="py-src-string">'n'</span>, <span class="py-src-string">'fingerbot'</span>],
+ [<span class="py-src-string">'ircserver'</span>, <span class="py-src-variable">None</span>, <span class="py-src-string">'irc.freenode.net'</span>],
+ [<span class="py-src-string">'pbport'</span>, <span class="py-src-string">'p'</span>, <span class="py-src-number">8889</span>],
+ ]
+
+ <span class="py-src-variable">optFlags</span> = [[<span class="py-src-string">'ssl'</span>, <span class="py-src-string">'s'</span>]]
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span>(<span class="py-src-parameter">config</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">finger</span>.<span class="py-src-variable">makeService</span>(<span class="py-src-variable">config</span>)
+</pre><div class="caption">finger/tap.py - <a href="listings/finger/finger/tap.py"><span class="filename">listings/finger/finger/tap.py</span></a></div></div>
+
+<p>And register it all:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">service</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ServiceMaker</span>
+
+<span class="py-src-variable">finger</span> = <span class="py-src-variable">ServiceMaker</span>(
+ <span class="py-src-string">'finger'</span>, <span class="py-src-string">'finger.tap'</span>, <span class="py-src-string">'Run a finger service'</span>, <span class="py-src-string">'finger'</span>)
+</pre><div class="caption">
+twisted/plugins/finger_tutorial.py
+ - <a href="listings/finger/twisted/plugins/finger_tutorial.py"><span class="filename">listings/finger/twisted/plugins/finger_tutorial.py</span></a></div></div>
+
+<p>And now, the following works</p>
+
+<pre class="shell" xml:space="preserve">
+% sudo twistd -n finger --file=/etc/users --ircnick=fingerbot
+</pre>
+
+<p>
+ For more details about this, see the <a href="../tap.html" shape="rect">twistd plugin
+ documentation</a>.
+</p>
+
+<h2>OS Integration<a name="auto2"/></h2>
+
+<p>If we already have the <q>finger</q> package installed in
+PYTHONPATH (e.g. we added it to site-packages), we can achieve easy
+integration:</p>
+
+<h3>Debian<a name="auto3"/></h3>
+
+<pre class="shell" xml:space="preserve">
+% tap2deb --unsigned -m &quot;Foo &lt;foo@example.com&gt;&quot; --type=python finger.tac
+% sudo dpkg -i .build/*.deb
+</pre>
+
+<h3>Red Hat / Mandrake<a name="auto4"/></h3>
+
+<pre class="shell" xml:space="preserve">
+% tap2rpm --type=python finger.tac #[maybe other options needed]
+% sudo rpm -i .build/*.rpm
+</pre>
+
+<p>Will properly register the tap/tac, init.d scripts, etc. for the given file.</p>
+
+<p>If it doesn't work on your favorite OS: patches accepted!</p>
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/factory.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/factory.html
new file mode 100644
index 0000000000..5f1c6af9d5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/factory.html
@@ -0,0 +1,633 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: using a single factory for
+ multiple protocols</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: using a single factory for
+ multiple protocols</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Support HTTPS</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the eighth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we add HTTPS support to our web frontend, showing how to have a
+single factory listen on multiple ports. More information on using SSL in
+Twisted can be found in the <a href="../ssl.html" shape="rect">SSL howto</a>.</p>
+
+<h2>Support HTTPS<a name="auto1"/></h2>
+
+<p>All we need to do to code an HTTPS site is just write a context factory (in
+this case, which loads the certificate from a certain file) and then use the
+twisted.application.internet.SSLServer method. Note that one factory (in this
+case, a site) can listen on multiple ports with multiple protocols.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">microdom</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">OpenSSL</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SSL</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>=<span class="py-src-variable">service</span>
+
+ <span class="py-src-comment"># add a specific child for the path &quot;RPC2&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;RPC2&quot;</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-comment"># need to do this for resources at the root of the site</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;&quot;</span>, <span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">users</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">userOutput</span> = <span class="py-src-string">''</span>.<span class="py-src-variable">join</span>([<span class="py-src-string">&quot;&lt;li&gt;&lt;a href=\&quot;%s\&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;&quot;</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>])
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;
+ &lt;html&gt;&lt;head&gt;&lt;title&gt;Users&lt;/title&gt;&lt;/head&gt;&lt;body&gt;
+ &lt;h1&gt;Users&lt;/h1&gt;
+ &lt;ul&gt;
+ %s
+ &lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % <span class="py-src-variable">userOutput</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">user</span>=<span class="py-src-variable">path</span>, <span class="py-src-variable">service</span>=<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">status</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;&lt;html&gt;&lt;head&gt;&lt;title&gt;%s&lt;/title&gt;&lt;/head&gt;
+ &lt;body&gt;&lt;h1&gt;%s&lt;/h1&gt;
+ &lt;p&gt;%s&lt;/p&gt;
+ &lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>))
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IPerspectiveFinger</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">username</span>):
+ <span class="py-src-string">&quot;&quot;&quot;return a user's status&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;return a user's status&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PerspectiveFingerFromService</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPerspectiveFinger</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">username</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">PerspectiveFingerFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IPerspectiveFinger</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ServerContextFactory</span>:
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getContext</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'.&quot;&quot;&quot;</span>
+ <span class="py-src-variable">ctx</span> = <span class="py-src-variable">SSL</span>.<span class="py-src-variable">Context</span>(<span class="py-src-variable">SSL</span>.<span class="py-src-variable">SSLv23_METHOD</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_certificate_file</span>(<span class="py-src-string">'server.pem'</span>)
+ <span class="py-src-variable">ctx</span>.<span class="py-src-variable">use_privatekey_file</span>(<span class="py-src-string">'server.pem'</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">ctx</span>
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">site</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">SSLServer</span>(<span class="py-src-number">443</span>, <span class="py-src-variable">site</span>, <span class="py-src-variable">ServerContextFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8889</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">IPerspectiveFinger</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger22.py"><span class="filename">listings/finger/finger22.py</span></a></div></div>
+
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/index.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/index.html
new file mode 100644
index 0000000000..38024d4840
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/index.html
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted from Scratch, or The Evolution of Finger</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted from Scratch, or The Evolution of Finger</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Contents</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p>
+Twisted is a big system. People are often daunted when they approach it. It's
+hard to know where to start looking.
+</p>
+
+<p>
+This guide builds a full-fledged Twisted application from the ground up, using
+most of the important bits of the framework. There is a lot of code, but don't
+be afraid.
+</p>
+
+<p>
+The application we are looking at is a <q>finger</q> service, along the
+lines of the familiar service traditionally provided by UNIXâ„¢ servers.
+We will extend this service slightly beyond the standard, in order to
+demonstrate some of Twisted's higher-level features.
+</p>
+
+<p>
+Each section of the tutorial dives straight into applications for various
+Twisted topics. These topics have their own introductory howtos listed in
+the <a href="../index.html" shape="rect">core howto index</a> and in the documentation for
+other Twisted projects like Twisted Web and Twisted Words. There are at least
+three ways to use this tutorial: you may find it useful to read through the rest
+of the topics listed in the <a href="../index.html" shape="rect">core howto index</a> before
+working through the finger tutorial, work through the finger tutorial and then
+go back and hit the introductory material that is relevant to the Twisted
+project you're working on, or read the introductory material one piece at a time
+as it comes up in the finger tutorial.
+</p>
+
+<h2>Contents<a name="auto1"/></h2>
+
+<p>
+This tutorial is split into eleven parts:
+</p>
+
+<ol>
+<li><a href="intro.html" shape="rect">The Evolution of Finger: building a simple
+finger service</a></li>
+<li><a href="protocol.html" shape="rect">The Evolution of Finger: adding features
+to the finger service</a></li>
+<li><a href="style.html" shape="rect">The Evolution of Finger: cleaning up the
+finger code</a></li>
+<li><a href="components.html" shape="rect">The Evolution of Finger: moving to a
+component based architecture</a></li>
+<li><a href="backends.html" shape="rect">The Evolution of Finger: pluggable
+backends</a></li>
+<li><a href="web.html" shape="rect">The Evolution of Finger: a web
+frontend</a></li>
+<li><a href="pb.html" shape="rect">The Evolution of Finger: Twisted client
+support using Perspective Broker</a></li>
+<li><a href="factory.html" shape="rect">The Evolution of Finger: using a single
+factory for multiple protocols</a></li>
+<li><a href="client.html" shape="rect">The Evolution of Finger: a Twisted finger
+client</a></li>
+<li><a href="library.html" shape="rect">The Evolution of Finger: making a finger library</a></li>
+<li><a href="configuration.html" shape="rect">The Evolution of Finger:
+configuration and packaging of the finger service</a></li>
+</ol>
+
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/intro.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/intro.html
new file mode 100644
index 0000000000..0b2df75ecd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/intro.html
@@ -0,0 +1,716 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: building a simple finger service</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: building a simple finger service</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Refuse Connections</a></li><ul><li><a href="#auto2">The Reactor</a></li></ul><li><a href="#auto3">Do Nothing</a></li><li><a href="#auto4">Drop Connections</a></li><li><a href="#auto5">Read Username, Drop Connections</a></li><li><a href="#auto6">Read Username, Output Error, Drop Connections</a></li><li><a href="#auto7">Output From Empty Factory</a></li><li><a href="#auto8">Output from Non-empty Factory</a></li><li><a href="#auto9">Use Deferreds</a></li><li><a href="#auto10">Run 'finger' Locally</a></li><li><a href="#auto11">Read Status from the Web</a></li><li><a href="#auto12">Use Application</a></li><li><a href="#auto13">twistd</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p>This is the first part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>If you're not familiar with 'finger' it's probably because it's not used as
+much nowadays as it used to be. Basically, if you run <code>finger nail</code>
+or <code>finger nail@example.com</code> the target computer spits out some
+information about the user named <code>nail</code>. For instance:</p>
+
+<pre class="shell" xml:space="preserve">
+Login: nail Name: Nail Sharp
+Directory: /home/nail Shell: /usr/bin/sh
+Last login Wed Mar 31 18:32 2004 (PST)
+New mail received Thu Apr 1 10:50 2004 (PST)
+ Unread since Thu Apr 1 10:50 2004 (PST)
+No Plan.
+</pre>
+
+<p>If the target computer does not have the <code>fingerd</code> <a href="../glossary.html#Daemon" shape="rect">daemon</a> running you'll get a &quot;Connection
+Refused&quot; error. Paranoid sysadmins keep <code>fingerd</code> off or limit the
+output to hinder crackers and harassers. The above format is the standard
+<code>fingerd</code> default, but an alternate implementation can output
+anything it wants, such as automated responsibility status for everyone in an
+organization. You can also define pseudo &quot;users&quot;, which are essentially
+keywords.</p>
+
+<p>This portion of the tutorial makes use of factories and protocols as
+introduced in the <a href="../servers.html" shape="rect">Writing a TCP Server howto</a> and
+deferreds as introduced in <a href="../defer.html" shape="rect">Using Deferreds</a>
+and <a href="../gendefer.html" shape="rect">Generating Deferreds</a>. Services and
+applications are discussed in <a href="../application.html" shape="rect">Using the Twisted
+Application Framework</a>.</p>
+
+<p>By the end of this section of the tutorial, our finger server will answer
+TCP finger requests on port 1079, and will read data from the web.</p>
+
+<h2>Refuse Connections<a name="auto1"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger01.py"><span class="filename">listings/finger/finger01.py</span></a></div></div>
+
+<p>This example only runs the reactor. It will consume almost no CPU
+resources. As it is not listening on any port, it can't respond to network
+requests — nothing at all will happen until we interrupt the program. At
+this point if you run <code>finger nail</code> or <code>telnet localhost
+1079</code>, you'll get a &quot;Connection refused&quot; error since there's no daemon
+running to respond. Not very useful, perhaps — but this is the skeleton
+inside which the Twisted program will grow.
+</p>
+
+<p>As implied above, at various points in this tutorial you'll want to
+observe the behavior of the server being developed. Unless you have a
+finger program which can use an alternate port, the easiest way to do this
+is with a telnet client. <code>telnet localhost 1079</code> will connect to
+the local host on port 1079, where a finger server will eventually be
+listening.</p>
+
+<h3>The Reactor<a name="auto2"/></h3>
+
+<p>
+You don't call Twisted, Twisted calls you. The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code>
+is Twisted's main event loop, similar to the main loop in other toolkits available
+in Python (Qt, wx, and Gtk). There is exactly one reactor in any running Twisted
+application. Once started it loops over and over again, responding to network
+events and making scheduled calls to code.</p>
+
+<p>Note that there are actually several different reactors to choose from;
+<code>from twisted.internet import reactor</code> returns the current reactor.
+If you haven't chosen a reactor class yet, it automatically chooses the
+default. See the <a href="../reactor-basics.html" shape="rect">Reactor Basics HOWTO</a>
+for more information.</p>
+
+<h2>Do Nothing<a name="auto3"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger02.py"><span class="filename">listings/finger/finger02.py</span></a></div></div>
+
+<p>Here, <code>reactor.listenTCP</code> opens port 1079. (The number 1079 is a
+reminder that eventually we want to run on port 79, the standard port for
+finger servers.) The specified factory, <code>FingerFactory</code>, is used to
+handle incoming requests on that port. Specifically, for each request, the
+reactor calls the factory's <code>buildProtocol</code> method, which in this
+case causes <code>FingerProtocol</code> to be instantiated. Since the protocol
+defined here does not actually respond to any events, connections to 1079 will
+be accepted, but the input ignored.</p>
+
+<p>A Factory is the proper place for data that you want to make available to
+the protocol instances, since the protocol instances are garbage collected when
+the connection is closed.</p>
+
+
+<h2>Drop Connections<a name="auto4"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger03.py"><span class="filename">listings/finger/finger03.py</span></a></div></div>
+
+<p>Here we add to the protocol the ability to respond to the event of beginning
+a connection — by terminating it. Perhaps not an interesting behavior, but
+it is already close to behaving according to the letter of the standard finger protocol. After
+all, there is no requirement to send any data to the remote connection in the
+standard. The only problem, as far as the standard is concerned, is that we
+terminate the connection too soon. A client which is slow enough will see his
+send() of the username result in an error.</p>
+
+
+<h2>Read Username, Drop Connections<a name="auto5"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger04.py"><span class="filename">listings/finger/finger04.py</span></a></div></div>
+
+<p>Here we make <code>FingerProtocol</code> inherit from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.protocols.basic.LineReceiver.html" title="twisted.protocols.basic.LineReceiver">LineReceiver</a></code>, so that we get data-based
+events on a line-by-line basis. We respond to the event of receiving the line
+with shutting down the connection.</p>
+
+<p>If you use a telnet client to interact with this server, the result will
+look something like this:</p>
+
+<pre class="shell" xml:space="preserve">
+$ telnet localhost 1079
+Trying 127.0.0.1...
+Connected to localhost.localdomain.
+alice
+Connection closed by foreign host.
+</pre>
+
+<p>Congratulations, this is the first standard-compliant version of the code.
+However, usually people actually expect some data about users to be
+transmitted.</p>
+
+<h2>Read Username, Output Error, Drop Connections<a name="auto6"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;No such user\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger05.py"><span class="filename">listings/finger/finger05.py</span></a></div></div>
+
+<p>Finally, a useful version. Granted, the usefulness is somewhat limited by
+the fact that this version only prints out a <q>No such user</q> message. It
+could be used for devastating effect in honey-pots (decoy servers), of
+course.</p>
+
+
+<h2>Output From Empty Factory<a name="auto7"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-comment"># Read username, output from empty factory, drop connections</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)+<span class="py-src-string">&quot;\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;No such user&quot;</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger06.py"><span class="filename">listings/finger/finger06.py</span></a></div></div>
+
+<p>The same behavior, but finally we see what usefulness the
+factory has: as something that does not get constructed for
+every connection, it can be in charge of the user database.
+In particular, we won't have to change the protocol if
+the user database back-end changes.</p>
+
+
+<h2>Output from Non-empty Factory<a name="auto8"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-comment"># Read username, output from non-empty factory, drop connections</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)+<span class="py-src-string">&quot;\r\n&quot;</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger07.py"><span class="filename">listings/finger/finger07.py</span></a></div></div>
+
+<p>Finally, a really useful finger database. While it does not
+supply information about logged in users, it could be used to
+distribute things like office locations and internal office
+numbers. As hinted above, the factory is in charge of keeping
+the user database: note that the protocol instance has not
+changed. This is starting to look good: we really won't have
+to keep tweaking our protocol.</p>
+
+
+<h2>Use Deferreds<a name="auto9"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-comment"># Read username, output from non-empty factory, drop connections</span>
+<span class="py-src-comment"># Use deferreds, to minimize synchronicity assumptions</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger08.py"><span class="filename">listings/finger/finger08.py</span></a></div></div>
+
+<p>But, here we tweak it just for the hell of it. Yes, while the
+previous version worked, it did assume the result of getUser is
+always immediately available. But what if instead of an in-memory
+database, we would have to fetch the result from a remote Oracle server? By
+allowing getUser to return a Deferred, we make it easier for the data to be
+retrieved asynchronously so that the CPU can be used for other tasks in the
+meanwhile.</p>
+
+<p>As described in the <a href="../defer.html" shape="rect">Deferred HOWTO</a>, Deferreds
+allow a program to be driven by events. For instance, if one task in a program
+is waiting on data, rather than have the CPU (and the program!) idly waiting
+for that data (a process normally called 'blocking'), the program can perform
+other operations in the meantime, and waits for some signal that data is ready
+to be processed before returning to that process.</p>
+
+<p>In brief, the code in <code>FingerFactory</code> above creates a Deferred,
+to which we start to attach <em>callbacks</em>. The deferred action in
+<code>FingerFactory</code> is actually a fast-running expression consisting of
+one dictionary method, <code>get</code>. Since this action can execute without
+delay, <code>FingerFactory.getUser</code> uses <code>defer.succeed</code> to
+create a Deferred which already has a result, meaning its return value will be
+passed immediately to the first callback function, which turns out to be
+<code>FingerProtocol.writeResponse</code>. We've also defined an
+<em>errback</em> (appropriately named <code>FingerProtocol.onError</code>) that
+will be called instead of <code>writeResponse</code> if something goes
+wrong.</p>
+
+<h2>Run 'finger' Locally<a name="auto10"/></h2>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-comment"># Read username, output from factory interfacing to OS, drop connections</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">utils</span>.<span class="py-src-variable">getProcessOutput</span>(<span class="py-src-string">&quot;finger&quot;</span>, [<span class="py-src-variable">user</span>])
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger09.py"><span class="filename">listings/finger/finger09.py</span></a></div></div>
+
+<p>This example also makes use of a Deferred.
+<code>twisted.internet.utils.getProcessOutput</code> is a non-blocking version
+of Python's <code>commands.getoutput</code>: it runs a shell command
+(<code>finger</code>, in this case) and captures its standard output. However,
+<code>getProcessOutput</code> returns a Deferred instead of the output itself.
+Since <code>FingerProtocol.lineReceived</code> is already expecting a Deferred
+to be returned by <code>getUser</code>, it doesn't need to be changed, and it
+returns the standard output as the finger result.</p>
+
+<p>Note that in this case the shell's built-in <code>finger</code> command is
+simply run with whatever arguments it is given. This is probably insecure, so
+you probably don't want a real server to do this without a lot more validation
+of the user input. This will do exactly what the standard version of the finger
+server does.</p>
+
+<h2>Read Status from the Web<a name="auto11"/></h2>
+
+<p>The web. That invention which has infiltrated homes around the world finally
+gets through to our invention. In this case we use the built-in Twisted web
+client via <code>twisted.web.client.getPage</code>, a non-blocking version of
+Python's <code>urllib2.urlopen(URL).read()</code>. Like
+<code>getProcessOutput</code> it returns a Deferred which will be called back
+with a string, and can thus be used as a drop-in replacement.</p>
+
+<p>Thus, we have examples of three different database back-ends, none of which
+change the protocol class. In fact, we will not have to change the protocol
+again until the end of this tutorial: we have achieved, here, one truly usable
+class.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+</p><span class="py-src-comment"># Read username, output from factory interfacing to web, drop connections</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>, <span class="py-src-variable">utils</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">client</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">prefix</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">prefix</span>=<span class="py-src-variable">prefix</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">client</span>.<span class="py-src-variable">getPage</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">prefix</span>+<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">1079</span>, <span class="py-src-variable">FingerFactory</span>(<span class="py-src-variable">prefix</span>=<span class="py-src-string">'http://livejournal.com/~'</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/finger10.py"><span class="filename">listings/finger/finger10.py</span></a></div></div>
+
+<h2>Use Application<a name="auto12"/></h2>
+
+<p>Up until now, we faked. We kept using port 1079, because really,
+who wants to run a finger server with root privileges? Well, the
+common solution is <q>privilege shedding</q>: after binding to the
+network, become a different, less privileged user. We could have done
+it ourselves, but Twisted has a built-in way to do it. We will create
+a snippet as above, but now we will define an application object. That
+object will have uid and gid attributes. When running it (later we
+will see how) it will bind to ports, shed privileges and then run.</p>
+
+<p>Read on to find out how to run this code using the twistd utility.</p>
+
+<h2>twistd<a name="auto13"/></h2>
+
+<p>This is how to run <q>Twisted Applications</q> — files which define an
+'application'. A daemon is expected to adhere to certain behavioral standards
+so that standard tools can stop/start/query them. If a Twisted application is
+run via twistd, the TWISTed Daemonizer, all this behavioral stuff will be
+handled for you. twistd does everything a daemon can be expected to —
+shuts down stdin/stdout/stderr, disconnects from the terminal and can even
+change runtime directory, or even the root filesystems. In short, it does
+everything so the Twisted application developer can concentrate on writing his
+networking code.</p>
+
+<pre class="shell" xml:space="preserve">
+root% twistd -ny finger11.tac # just like before
+root% twistd -y finger11.tac # daemonize, keep pid in twistd.pid
+root% twistd -y finger11.tac --pidfile=finger.pid
+root% twistd -y finger11.tac --rundir=/
+root% twistd -y finger11.tac --chroot=/var
+root% twistd -y finger11.tac -l /var/log/finger.log
+root% twistd -y finger11.tac --syslog # just log to syslog
+root% twistd -y finger11.tac --syslog --prefix=twistedfinger # use given prefix
+</pre>
+
+<p>There are several ways to tell twistd where your application is; here we
+show how it is done using the <code>application</code> global variable in a
+Python source file (a <a href="glossary.html#TAC" shape="rect">Twisted Application
+Configuration</a> file).</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+</p><span class="py-src-comment"># Read username, output from non-empty factory, drop connections</span>
+<span class="py-src-comment"># Use deferreds, to minimize synchronicity assumptions</span>
+<span class="py-src-comment"># Write application. Save in 'finger.tpy'</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">FingerFactory</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">factory</span>).<span class="py-src-variable">setServiceParent</span>(
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/finger/finger11.tac"><span class="filename">listings/finger/finger11.tac</span></a></div></div>
+
+<p>Instead of using <code>reactor.listenTCP</code> as in the above examples,
+here we are using its application-aware counterpart,
+<code>internet.TCPServer</code>. Notice that when it is instantiated, the
+application object itself does not reference either the protocol or the
+factory. Any services (such as TCPServer) which have the application as their
+parent will be started when the application is started by twistd. The
+application object is more useful for returning an object that supports the
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IServiceCollection.html" title="twisted.application.service.IServiceCollection">IServiceCollection</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IProcess.html" title="twisted.application.service.IProcess">IProcess</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.sob.IPersistable.html" title="twisted.persisted.sob.IPersistable">sob.IPersistable</a></code> interfaces with
+the given parameters; we'll be seeing these in the next part of the
+tutorial. As the parent of the TCPServer we opened, the application lets us
+manage the TCPServer.</p>
+
+<p>With the daemon running on the standard finger port, you can test it with
+the standard finger command: <code>finger moshez</code>.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/library.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/library.html
new file mode 100644
index 0000000000..00388d59d1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/library.html
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: making a finger library</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: making a finger library</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Organization</a></li><li><a href="#auto2">Easy Configuration</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the tenth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we separate the application code that launches a finger service
+from the library code which defines a finger service, placing the application in
+a Twisted Application Configuration (.tac) file. We also move configuration
+(such as HTML templates) into separate files. Configuration and deployment with
+.tac and twistd are introduced in <a href="../application.html" shape="rect">Using the
+Twisted Application Framework</a>.</p>
+
+<h2>Organization<a name="auto1"/></h2>
+
+<p>Now this code, while quite modular and well-designed, isn't
+properly organized. Everything above the <code>application=</code> belongs in a
+module, and the HTML templates all belong in separate files.
+</p>
+
+<p>We can use the templateFile and templateDirectory attributes to indicate
+what HTML template file to use for each Page, and where to look for it.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+</p><span class="py-src-comment"># organized-finger.tac</span>
+<span class="py-src-comment"># eg: twistd -ny organized-finger.tac</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">finger</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>, <span class="py-src-variable">strports</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">finger</span>.<span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">finger</span>.<span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">site</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">SSLServer</span>(<span class="py-src-number">443</span>, <span class="py-src-variable">site</span>, <span class="py-src-variable">finger</span>.<span class="py-src-variable">ServerContextFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+
+<span class="py-src-variable">i</span> = <span class="py-src-variable">finger</span>.<span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8889</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">finger</span>.<span class="py-src-variable">IPerspectiveFinger</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/organized-finger.tac"><span class="filename">listings/finger/organized-finger.tac</span></a></div></div>
+
+<p>
+Note that our program is now quite separated. We have:
+<ul>
+ <li>Code (in the module)</li>
+ <li>Configuration (file above)</li>
+ <li>Presentation (templates)</li>
+ <li>Content (/etc/users)</li>
+ <li>Deployment (twistd)</li>
+</ul>
+
+Prototypes don't need this level of separation, so our earlier examples all
+bunched together. However, real applications do. Thankfully, if we write our
+code correctly, it is easy to achieve a good separation of parts.
+</p>
+
+
+<h2>Easy Configuration<a name="auto2"/></h2>
+
+<p>We can also supply easy configuration for common cases with a makeService method that will also help build .tap files later:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+</p><span class="py-src-comment"># Easy configuration</span>
+<span class="py-src-comment"># makeService from finger module</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">makeService</span>(<span class="py-src-parameter">config</span>):
+ <span class="py-src-comment"># finger on port 79</span>
+ <span class="py-src-variable">s</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">MultiService</span>()
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-variable">config</span>[<span class="py-src-string">'file'</span>])
+ <span class="py-src-variable">h</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>))
+ <span class="py-src-variable">h</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># website on port 8000</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>)
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">templateDirectory</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'templates'</span>]
+ <span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>)
+ <span class="py-src-variable">j</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">site</span>)
+ <span class="py-src-variable">j</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># ssl on port 443</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">get</span>(<span class="py-src-string">'ssl'</span>):
+ <span class="py-src-variable">k</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">SSLServer</span>(<span class="py-src-number">443</span>, <span class="py-src-variable">site</span>, <span class="py-src-variable">ServerContextFactory</span>())
+ <span class="py-src-variable">k</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># irc fingerbot</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-string">'ircnick'</span>):
+ <span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+ <span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'ircnick'</span>]
+ <span class="py-src-variable">ircserver</span> = <span class="py-src-variable">config</span>[<span class="py-src-string">'ircserver'</span>]
+ <span class="py-src-variable">b</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">ircserver</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>)
+ <span class="py-src-variable">b</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-comment"># Pespective Broker on port 8889</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">config</span>.<span class="py-src-variable">has_key</span>(<span class="py-src-string">'pbport'</span>):
+ <span class="py-src-variable">m</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(
+ <span class="py-src-variable">int</span>(<span class="py-src-variable">config</span>[<span class="py-src-string">'pbport'</span>]),
+ <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">IPerspectiveFinger</span>(<span class="py-src-variable">f</span>)))
+ <span class="py-src-variable">m</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">s</span>)
+
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">s</span>
+</pre><div class="caption">Source listing - <a href="listings/finger/finger_config.py"><span class="filename">listings/finger/finger_config.py</span></a></div></div>
+
+<p>And we can write simpler files now:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+</p><span class="py-src-comment"># simple-finger.tac</span>
+<span class="py-src-comment"># eg: twistd -ny simple-finger.tac</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">finger</span>
+
+<span class="py-src-variable">options</span> = { <span class="py-src-string">'file'</span>: <span class="py-src-string">'/etc/users'</span>,
+ <span class="py-src-string">'templates'</span>: <span class="py-src-string">'/usr/share/finger/templates'</span>,
+ <span class="py-src-string">'ircnick'</span>: <span class="py-src-string">'fingerbot'</span>,
+ <span class="py-src-string">'ircserver'</span>: <span class="py-src-string">'irc.freenode.net'</span>,
+ <span class="py-src-string">'pbport'</span>: <span class="py-src-number">8889</span>,
+ <span class="py-src-string">'ssl'</span>: <span class="py-src-string">'ssl=0'</span> }
+
+<span class="py-src-variable">ser</span> = <span class="py-src-variable">finger</span>.<span class="py-src-variable">makeService</span>(<span class="py-src-variable">options</span>)
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">ser</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/finger/simple-finger.tac"><span class="filename">listings/finger/simple-finger.tac</span></a></div></div>
+
+<pre class="shell" xml:space="preserve">
+% twisted -ny simple-finger.tac
+</pre>
+
+
+<p>Note: the finger <em>user</em> still has ultimate power: he can use
+makeService, or he can use the lower-level interface if he has
+specific needs (maybe an IRC server on some other port? maybe we
+want the non-SSL webserver to listen only locally? etc. etc.)
+This is an important design principle: never force a layer of abstraction:
+allow usage of layers of abstractions.</p>
+
+<p>The pasta theory of design:</p>
+
+<ul>
+<li>Spaghetti: each piece of code interacts with every other piece of
+ code [can be implemented with GOTO, functions, objects]</li>
+<li>Lasagna: code has carefully designed layers. Each layer is, in
+ theory independent. However low-level layers usually cannot be
+ used easily, and high-level layers depend on low-level layers.</li>
+<li>Ravioli: each part of the code is useful by itself. There is a thin
+ layer of interfaces between various parts [the sauce]. Each part
+ can be usefully be used elsewhere.</li>
+<li>...but sometimes, the user just wants to order <q>Ravioli</q>, so one
+ coarse-grain easily definable layer of abstraction on top of it all
+ can be useful.</li>
+</ul>
+
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/etc.users b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/etc.users
new file mode 100644
index 0000000000..d8c8f8cd21
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/etc.users
@@ -0,0 +1,2 @@
+moshez: happy and well
+shawn: alive
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/__init__.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/__init__.py
new file mode 100755
index 0000000000..bcb24fabd1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/__init__.py
@@ -0,0 +1,3 @@
+"""
+Finger example application.
+"""
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/finger.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/finger.py
new file mode 100755
index 0000000000..b05053666b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/finger.py
@@ -0,0 +1,331 @@
+# finger.py module
+
+from zope.interface import Interface, implements
+
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components, log
+from twisted.web import resource, server, xmlrpc
+from twisted.spread import pb
+
+from OpenSSL import SSL
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+class UserStatusTree(resource.Resource):
+
+ template = """<html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %(users)s
+ </ul>
+ </body>
+ </html>"""
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+
+ def getChild(self, path, request):
+ if path == '':
+ return self
+ elif path == 'RPC2':
+ return UserStatusXR(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+ def render_GET(self, request):
+ users = self.service.getUsers()
+ def cbUsers(users):
+ request.write(self.template % {'users': ''.join([
+ # Name should be quoted properly these uses.
+ '<li><a href="%s">%s</a></li>' % (name, name)
+ for name in users])})
+ request.finish()
+ users.addCallback(cbUsers)
+ def ebUsers(err):
+ log.err(err, "UserStatusTree failed")
+ request.finish()
+ users.addErrback(ebUsers)
+ return server.NOT_DONE_YET
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ template='''<html><head><title>%(title)s</title></head>
+ <body><h1>%(name)s</h1><p>%(status)s</p></body></html>'''
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ status = self.service.getUser(self.user)
+ def cbStatus(status):
+ request.write(self.template % {
+ 'title': self.user,
+ 'name': self.user,
+ 'status': status})
+ request.finish()
+ status.addCallback(cbStatus)
+ def ebStatus(err):
+ log.err(err, "UserStatus failed")
+ request.finish()
+ status.addErrback(ebStatus)
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class IPerspectiveFinger(Interface):
+
+ def remote_getUser(username):
+ """return a user's status"""
+
+ def remote_getUsers():
+ """return a user's status"""
+
+class PerspectiveFingerFromService(pb.Root):
+
+ implements(IPerspectiveFinger)
+
+ def __init__(self, service):
+ self.service = service
+
+ def remote_getUser(self, username):
+ return self.service.getUser(username)
+
+ def remote_getUsers(self):
+ return self.service.getUsers()
+
+components.registerAdapter(PerspectiveFingerFromService,
+ IFingerService,
+ IPerspectiveFinger)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+class ServerContextFactory:
+
+ def getContext(self):
+ """Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'."""
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file('server.pem')
+ ctx.use_privatekey_file('server.pem')
+ return ctx
+
+
+
+
+# Easy configuration
+
+def makeService(config):
+ # finger on port 79
+ s = service.MultiService()
+ f = FingerService(config['file'])
+ h = internet.TCPServer(1079, IFingerFactory(f))
+ h.setServiceParent(s)
+
+
+ # website on port 8000
+ r = resource.IResource(f)
+ r.templateDirectory = config['templates']
+ site = server.Site(r)
+ j = internet.TCPServer(8000, site)
+ j.setServiceParent(s)
+
+ # ssl on port 443
+# if config.get('ssl'):
+# k = internet.SSLServer(443, site, ServerContextFactory())
+# k.setServiceParent(s)
+
+ # irc fingerbot
+ if config.has_key('ircnick'):
+ i = IIRCClientFactory(f)
+ i.nickname = config['ircnick']
+ ircserver = config['ircserver']
+ b = internet.TCPClient(ircserver, 6667, i)
+ b.setServiceParent(s)
+
+ # Pespective Broker on port 8889
+ if config.has_key('pbport'):
+ m = internet.TCPServer(
+ int(config['pbport']),
+ pb.PBServerFactory(IPerspectiveFinger(f)))
+ m.setServiceParent(s)
+
+ return s
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/tap.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/tap.py
new file mode 100644
index 0000000000..a06102c4ce
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger/tap.py
@@ -0,0 +1,20 @@
+# finger/tap.py
+from twisted.application import internet, service
+from twisted.internet import interfaces
+from twisted.python import usage
+import finger
+
+class Options(usage.Options):
+
+ optParameters = [
+ ['file', 'f', '/etc/users'],
+ ['templates', 't', '/usr/share/finger/templates'],
+ ['ircnick', 'n', 'fingerbot'],
+ ['ircserver', None, 'irc.freenode.net'],
+ ['pbport', 'p', 8889],
+ ]
+
+ optFlags = [['ssl', 's']]
+
+def makeService(config):
+ return finger.makeService(config)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger01.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger01.py
new file mode 100755
index 0000000000..0561510718
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger01.py
@@ -0,0 +1,2 @@
+from twisted.internet import reactor
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger02.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger02.py
new file mode 100755
index 0000000000..e7efbf4e6f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger02.py
@@ -0,0 +1,10 @@
+from twisted.internet import protocol, reactor
+
+class FingerProtocol(protocol.Protocol):
+ pass
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger03.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger03.py
new file mode 100755
index 0000000000..d32302367c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger03.py
@@ -0,0 +1,11 @@
+from twisted.internet import protocol, reactor
+
+class FingerProtocol(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.loseConnection()
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger04.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger04.py
new file mode 100755
index 0000000000..d35f590789
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger04.py
@@ -0,0 +1,12 @@
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.loseConnection()
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger05.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger05.py
new file mode 100755
index 0000000000..0d8da8cb1c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger05.py
@@ -0,0 +1,13 @@
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write("No such user\r\n")
+ self.transport.loseConnection()
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger06.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger06.py
new file mode 100755
index 0000000000..7f789861b7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger06.py
@@ -0,0 +1,18 @@
+# Read username, output from empty factory, drop connections
+
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write(self.factory.getUser(user)+"\r\n")
+ self.transport.loseConnection()
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def getUser(self, user):
+ return "No such user"
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger07.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger07.py
new file mode 100755
index 0000000000..cc5dbf1306
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger07.py
@@ -0,0 +1,21 @@
+# Read username, output from non-empty factory, drop connections
+
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write(self.factory.getUser(user)+"\r\n")
+ self.transport.loseConnection()
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return self.users.get(user, "No such user")
+
+reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger08.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger08.py
new file mode 100755
index 0000000000..624c5b041f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger08.py
@@ -0,0 +1,30 @@
+# Read username, output from non-empty factory, drop connections
+# Use deferreds, to minimize synchronicity assumptions
+
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger09.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger09.py
new file mode 100755
index 0000000000..336acb3688
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger09.py
@@ -0,0 +1,26 @@
+# Read username, output from factory interfacing to OS, drop connections
+
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def getUser(self, user):
+ return utils.getProcessOutput("finger", [user])
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger10.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger10.py
new file mode 100755
index 0000000000..7e4cb931c3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger10.py
@@ -0,0 +1,30 @@
+# Read username, output from factory interfacing to web, drop connections
+
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.protocols import basic
+from twisted.web import client
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, prefix):
+ self.prefix=prefix
+
+ def getUser(self, user):
+ return client.getPage(self.prefix+user)
+
+reactor.listenTCP(1079, FingerFactory(prefix='http://livejournal.com/~'))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger11.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger11.tac
new file mode 100755
index 0000000000..aae8ca689c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger11.tac
@@ -0,0 +1,34 @@
+# Read username, output from non-empty factory, drop connections
+# Use deferreds, to minimize synchronicity assumptions
+# Write application. Save in 'finger.tpy'
+
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+application = service.Application('finger', uid=1, gid=1)
+factory = FingerFactory(moshez='Happy and well')
+internet.TCPServer(79, factory).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger12.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger12.tac
new file mode 100755
index 0000000000..69120f1248
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger12.tac
@@ -0,0 +1,55 @@
+# But let's try and fix setting away messages, shall we?
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ user = self.lines[0]
+ status = self.lines[1]
+ self.factory.setUser(user, status)
+
+class FingerSetterFactory(protocol.ServerFactory):
+ protocol = FingerSetterProtocol
+
+ def __init__(self, fingerFactory):
+ self.fingerFactory = fingerFactory
+
+ def setUser(self, user, status):
+ self.fingerFactory.users[user] = status
+
+ff = FingerFactory(moshez='Happy and well')
+fsf = FingerSetterFactory(ff)
+
+application = service.Application('finger', uid=1, gid=1)
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79,ff).setServiceParent(serviceCollection)
+internet.TCPServer(1079,fsf).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger13.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger13.tac
new file mode 100755
index 0000000000..5cf60c9af0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger13.tac
@@ -0,0 +1,59 @@
+# Fix asymmetry
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self,reason):
+ user = self.lines[0]
+ status = self.lines[1]
+ self.factory.setUser(user, status)
+
+class FingerService(service.Service):
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def setUser(self, user, status):
+ self.users[user] = status
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getFingerSetterFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerSetterProtocol
+ f.setUser = self.setUser
+ return f
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService(moshez='Happy and well')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79,f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(1079,f.getFingerSetterFactory()
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger14.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger14.tac
new file mode 100755
index 0000000000..61d35d05fb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger14.tac
@@ -0,0 +1,55 @@
+# Read from file
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.users = {}
+ self.filename = filename
+ self._read()
+
+ def _read(self):
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+finger = internet.TCPServer(79, f.getFingerFactory())
+
+finger.setServiceParent(service.IServiceCollection(application))
+f.setServiceParent(service.IServiceCollection(application))
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger15.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger15.tac
new file mode 100755
index 0000000000..18b3d87382
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger15.tac
@@ -0,0 +1,76 @@
+# Read from file, announce on the web!
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+from twisted.web import resource, server, static
+import cgi
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerResource(resource.Resource):
+
+ def __init__(self, users):
+ self.users = users
+ resource.Resource.__init__(self)
+
+ # we treat the path as the username
+ def getChild(self, username, request):
+ """
+ 'username' is a string.
+ 'request' is a 'twisted.web.server.Request'.
+ """
+ messagevalue = self.users.get(username)
+ username = cgi.escape(username)
+ if messagevalue is not None:
+ messagevalue = cgi.escape(messagevalue)
+ text = '<h1>%s</h1><p>%s</p>' % (username,messagevalue)
+ else:
+ text = '<h1>%s</h1><p>No such user</p>' % username
+ return static.Data(text, 'text/html')
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+ self._read()
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = FingerResource(self.users)
+ return r
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger16.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger16.tac
new file mode 100755
index 0000000000..f5d350240d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger16.tac
@@ -0,0 +1,91 @@
+# Read from file, announce on the web, irc
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.web import resource, server, static
+
+import cgi
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+
+class IRCReplyBot(irc.IRCClient):
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ irc.IRCClient.msg(self, user, msg+': '+message)
+ d.addCallback(writeResponse)
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+ self._read()
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('<h1>%s</h1><p>%s</p>' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path,
+ "No such user <p/> usage: site/user")])),
+ 'text/html'))
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
+internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger17.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger17.tac
new file mode 100755
index 0000000000..5ef9170c40
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger17.tac
@@ -0,0 +1,91 @@
+# Read from file, announce on the web, irc, xml-rpc
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class IRCReplyBot(irc.IRCClient):
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ irc.IRCClient.msg(self, user, msg+': '+message)
+ d.addCallback(writeResponse)
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+ self._read()
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('<h1>%s</h1><p>%s</p>' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path, "No such user")])),
+ 'text/html'))
+ x = xmlrpc.XMLRPC()
+ x.xmlrpc_getUser = self.getUser
+ r.putChild('RPC2', x)
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
+internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger18.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger18.tac
new file mode 100755
index 0000000000..6ddc1c8798
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger18.tac
@@ -0,0 +1,137 @@
+# Do everything properly
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class UserStatusTree(resource.Resource):
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['<li><a href="%s">%s</a></li>' % (user, user)
+ for user in users]
+ return '<ul>'+''.join(l)+'</ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+ self._read()
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = UserStatusTree(self)
+ x = UserStatusXR(self)
+ r.putChild('RPC2', x)
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
+internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19.tac
new file mode 100755
index 0000000000..248bd9c81a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19.tac
@@ -0,0 +1,238 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['<li><a href="%s">%s</a></li>' % (user, user)
+ for user in users]
+ return '<ul>'+''.join(l)+'</ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+ self._read()
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19a.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19a.tac
new file mode 100755
index 0000000000..e6c66b5a43
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19a.tac
@@ -0,0 +1,231 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['<li><a href="%s">%s</a></li>' % (user, user)
+ for user in users]
+ return '<ul>'+''.join(l)+'</ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+class MemoryFingerService(service.Service):
+
+ implements([IFingerService, IFingerSetterService])
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def setUser(self, user, status):
+ self.users[user] = status
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = MemoryFingerService(moshez='Happy and well')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(1079, IFingerSetterFactory(f), interface='127.0.0.1'
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19a_changes.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19a_changes.py
new file mode 100644
index 0000000000..cbb3623a0d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19a_changes.py
@@ -0,0 +1,29 @@
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+# Advantages of latest version
+
+class MemoryFingerService(service.Service):
+
+ implements([IFingerService, IFingerSetterService])
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def setUser(self, user, status):
+ self.users[user] = status
+
+
+f = MemoryFingerService(moshez='Happy and well')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(1079, IFingerSetterFactory(f), interface='127.0.0.1'
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19b.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19b.tac
new file mode 100755
index 0000000000..b4790a6f7a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19b.tac
@@ -0,0 +1,257 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+import pwd
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['<li><a href="%s">%s</a></li>' % (user, user)
+ for user in users]
+ return '<ul>'+''.join(l)+'</ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+ self._read()
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+# Another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ # need a local finger daemon running for this to work
+ return utils.getProcessOutput("finger", [user])
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = LocalFingerService()
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19b_changes.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19b_changes.py
new file mode 100644
index 0000000000..3c8ff75b08
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19b_changes.py
@@ -0,0 +1,19 @@
+
+from twisted.internet import protocol, reactor, defer, utils
+import pwd
+
+# Another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ # need a local finger daemon running for this to work
+ return utils.getProcessOutput("finger", [user])
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+f = LocalFingerService()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19c.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19c.tac
new file mode 100755
index 0000000000..15e37150d1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19c.tac
@@ -0,0 +1,269 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+import pwd
+import os
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade():
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['<li><a href="%s">%s</a></li>' % (user, user)
+ for user in users]
+ return '<ul>'+''.join(l)+'</ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+ self._read()
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+# Yet another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ user = user.strip()
+ try:
+ entry = pwd.getpwnam(user)
+ except KeyError:
+ return defer.succeed("No such user")
+ try:
+ f = file(os.path.join(entry[5],'.plan'))
+ except (IOError, OSError):
+ return defer.succeed("No such user")
+ data = f.read()
+ data = data.strip()
+ f.close()
+ return defer.succeed(data)
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = LocalFingerService()
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19c_changes.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19c_changes.py
new file mode 100644
index 0000000000..cc592ea3d5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger19c_changes.py
@@ -0,0 +1,32 @@
+from twisted.internet import protocol, reactor, defer, utils
+import pwd
+import os
+
+
+# Yet another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ user = user.strip()
+ try:
+ entry = pwd.getpwnam(user)
+ except KeyError:
+ return defer.succeed("No such user")
+ try:
+ f = file(os.path.join(entry[5],'.plan'))
+ except (IOError, OSError):
+ return defer.succeed("No such user")
+ data = f.read()
+ data = data.strip()
+ f.close()
+ return defer.succeed(data)
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+
+f = LocalFingerService()
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger20.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger20.tac
new file mode 100755
index 0000000000..48c0b029b5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger20.tac
@@ -0,0 +1,251 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service=service
+
+ # add a specific child for the path "RPC2"
+ self.putChild("RPC2", UserStatusXR(self.service))
+
+ # need to do this for resources at the root of the site
+ self.putChild("", self)
+
+ def _cb_render_GET(self, users, request):
+ userOutput = ''.join(["<li><a href=\"%s\">%s</a></li>" % (user, user)
+ for user in users])
+ request.write("""
+ <html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %s
+ </ul></body></html>""" % userOutput)
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(user=path, service=self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def _cb_render_GET(self, status, request):
+ request.write("""<html><head><title>%s</title></head>
+ <body><h1>%s</h1>
+ <p>%s</p>
+ </body></html>""" % (self.user, self.user, status))
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+ self._read()
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger21.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger21.tac
new file mode 100755
index 0000000000..8ac2603ac2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger21.tac
@@ -0,0 +1,280 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.spread import pb
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service=service
+
+ # add a specific child for the path "RPC2"
+ self.putChild("RPC2", UserStatusXR(self.service))
+
+ # need to do this for resources at the root of the site
+ self.putChild("", self)
+
+ def _cb_render_GET(self, users, request):
+ userOutput = ''.join(["<li><a href=\"%s\">%s</a></li>" % (user, user)
+ for user in users])
+ request.write("""
+ <html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %s
+ </ul></body></html>""" % userOutput)
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(user=path, service=self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def _cb_render_GET(self, status, request):
+ request.write("""<html><head><title>%s</title></head>
+ <body><h1>%s</h1>
+ <p>%s</p>
+ </body></html>""" % (self.user, self.user, status))
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class IPerspectiveFinger(Interface):
+
+ def remote_getUser(username):
+ """return a user's status"""
+
+ def remote_getUsers():
+ """return a user's status"""
+
+class PerspectiveFingerFromService(pb.Root):
+
+ implements(IPerspectiveFinger)
+
+ def __init__(self, service):
+ self.service = service
+
+ def remote_getUser(self, username):
+ return self.service.getUser(username)
+
+ def remote_getUsers(self):
+ return self.service.getUsers()
+
+components.registerAdapter(PerspectiveFingerFromService,
+ IFingerService,
+ IPerspectiveFinger)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+ self._read()
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8889, pb.PBServerFactory(IPerspectiveFinger(f))
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger22.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger22.py
new file mode 100755
index 0000000000..dc8deb94e8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger22.py
@@ -0,0 +1,297 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.spread import pb
+from zope.interface import Interface, implements
+from OpenSSL import SSL
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ implements(IFingerSetterFactory)
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ implements(IIRCClientFactory)
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service=service
+
+ # add a specific child for the path "RPC2"
+ self.putChild("RPC2", UserStatusXR(self.service))
+
+ # need to do this for resources at the root of the site
+ self.putChild("", self)
+
+ def _cb_render_GET(self, users, request):
+ userOutput = ''.join(["<li><a href=\"%s\">%s</a></li>" % (user, user)
+ for user in users])
+ request.write("""
+ <html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %s
+ </ul></body></html>""" % userOutput)
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(user=path, service=self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def _cb_render_GET(self, status, request):
+ request.write("""<html><head><title>%s</title></head>
+ <body><h1>%s</h1>
+ <p>%s</p>
+ </body></html>""" % (self.user, self.user, status))
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class IPerspectiveFinger(Interface):
+
+ def remote_getUser(username):
+ """return a user's status"""
+
+ def remote_getUsers():
+ """return a user's status"""
+
+class PerspectiveFingerFromService(pb.Root):
+
+ implements(IPerspectiveFinger)
+
+ def __init__(self, service):
+ self.service = service
+
+ def remote_getUser(self, username):
+ return self.service.getUser(username)
+
+ def remote_getUsers(self):
+ return self.service.getUsers()
+
+components.registerAdapter(PerspectiveFingerFromService,
+ IFingerService,
+ IPerspectiveFinger)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+ self._read()
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+class ServerContextFactory:
+
+ def getContext(self):
+ """Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'."""
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file('server.pem')
+ ctx.use_privatekey_file('server.pem')
+ return ctx
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+site = server.Site(resource.IResource(f))
+internet.TCPServer(8000, site
+ ).setServiceParent(serviceCollection)
+internet.SSLServer(443, site, ServerContextFactory()
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8889, pb.PBServerFactory(IPerspectiveFinger(f))
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerPBclient.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerPBclient.py
new file mode 100755
index 0000000000..66ed0ae769
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerPBclient.py
@@ -0,0 +1,26 @@
+# test the PB finger on port 8889
+# this code is essentially the same as
+# the first example in howto/pb-usage
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def gotObject(object):
+ print "got object:", object
+ object.callRemote("getUser","moshez").addCallback(gotData)
+# or
+# object.callRemote("getUsers").addCallback(gotData)
+
+def gotData(data):
+ print 'server sent:', data
+ reactor.stop()
+
+def gotNoObject(reason):
+ print "no object:",reason
+ reactor.stop()
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("127.0.0.1",8889, factory)
+factory.getRootObject().addCallbacks(gotObject,gotNoObject)
+reactor.run()
+
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerXRclient.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerXRclient.py
new file mode 100755
index 0000000000..b854bcfc67
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerXRclient.py
@@ -0,0 +1,5 @@
+# testing xmlrpc finger
+
+import xmlrpclib
+server = xmlrpclib.Server('http://127.0.0.1:8000/RPC2')
+print server.getUser('moshez')
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger_config.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger_config.py
new file mode 100644
index 0000000000..226a26ab3b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/finger_config.py
@@ -0,0 +1,38 @@
+# Easy configuration
+# makeService from finger module
+
+def makeService(config):
+ # finger on port 79
+ s = service.MultiService()
+ f = FingerService(config['file'])
+ h = internet.TCPServer(79, IFingerFactory(f))
+ h.setServiceParent(s)
+
+ # website on port 8000
+ r = resource.IResource(f)
+ r.templateDirectory = config['templates']
+ site = server.Site(r)
+ j = internet.TCPServer(8000, site)
+ j.setServiceParent(s)
+
+ # ssl on port 443
+ if config.get('ssl'):
+ k = internet.SSLServer(443, site, ServerContextFactory())
+ k.setServiceParent(s)
+
+ # irc fingerbot
+ if config.has_key('ircnick'):
+ i = IIRCClientFactory(f)
+ i.nickname = config['ircnick']
+ ircserver = config['ircserver']
+ b = internet.TCPClient(ircserver, 6667, i)
+ b.setServiceParent(s)
+
+ # Pespective Broker on port 8889
+ if config.has_key('pbport'):
+ m = internet.TCPServer(
+ int(config['pbport']),
+ pb.PBServerFactory(IPerspectiveFinger(f)))
+ m.setServiceParent(s)
+
+ return s
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerproxy.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerproxy.tac
new file mode 100644
index 0000000000..839c63dc42
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/fingerproxy.tac
@@ -0,0 +1,110 @@
+# finger proxy
+from twisted.application import internet, service
+from twisted.internet import defer, protocol, reactor
+from twisted.protocols import basic
+from twisted.python import components
+from zope.interface import Interface, implements
+
+
+def catchError(err):
+ return "Internal error in server"
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+
+class FingerFactoryFromService(protocol.ClientFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+class FingerClient(protocol.Protocol):
+
+ def connectionMade(self):
+ self.transport.write(self.factory.user+"\r\n")
+ self.buf = []
+
+ def dataReceived(self, data):
+ self.buf.append(data)
+
+ def connectionLost(self, reason):
+ self.factory.gotData(''.join(self.buf))
+
+class FingerClientFactory(protocol.ClientFactory):
+
+ protocol = FingerClient
+
+ def __init__(self, user):
+ self.user = user
+ self.d = defer.Deferred()
+
+ def clientConnectionFailed(self, _, reason):
+ self.d.errback(reason)
+
+ def gotData(self, data):
+ self.d.callback(data)
+
+
+def finger(user, host, port=79):
+ f = FingerClientFactory(user)
+ reactor.connectTCP(host, port, f)
+ return f.d
+
+
+class ProxyFingerService(service.Service):
+ implements(IFingerService)
+
+ def getUser(self, user):
+ try:
+ user, host = user.split('@', 1)
+ except:
+ user = user.strip()
+ host = '127.0.0.1'
+ ret = finger(user, host)
+ ret.addErrback(lambda _: "Could not connect to remote host")
+ return ret
+
+ def getUsers(self):
+ return defer.succeed([])
+
+application = service.Application('finger', uid=1, gid=1)
+f = ProxyFingerService()
+internet.TCPServer(7779, IFingerFactory(f)).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/organized-finger.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/organized-finger.tac
new file mode 100644
index 0000000000..2f9a129cf8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/organized-finger.tac
@@ -0,0 +1,31 @@
+# organized-finger.tac
+# eg: twistd -ny organized-finger.tac
+
+import finger
+
+from twisted.internet import protocol, reactor, defer
+from twisted.spread import pb
+from twisted.web import resource, server
+from twisted.application import internet, service, strports
+from twisted.python import log
+
+application = service.Application('finger', uid=1, gid=1)
+f = finger.FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, finger.IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+
+site = server.Site(resource.IResource(f))
+internet.TCPServer(8000, site
+ ).setServiceParent(serviceCollection)
+
+internet.SSLServer(443, site, finger.ServerContextFactory()
+ ).setServiceParent(serviceCollection)
+
+i = finger.IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+
+internet.TCPServer(8889, pb.PBServerFactory(finger.IPerspectiveFinger(f))
+ ).setServiceParent(serviceCollection)
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/simple-finger.tac b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/simple-finger.tac
new file mode 100644
index 0000000000..2e75cb1c89
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/simple-finger.tac
@@ -0,0 +1,17 @@
+# simple-finger.tac
+# eg: twistd -ny simple-finger.tac
+
+from twisted.application import service
+
+import finger
+
+options = { 'file': '/etc/users',
+ 'templates': '/usr/share/finger/templates',
+ 'ircnick': 'fingerbot',
+ 'ircserver': 'irc.freenode.net',
+ 'pbport': 8889,
+ 'ssl': 'ssl=0' }
+
+ser = finger.makeService(options)
+application = service.Application('finger', uid=1, gid=1)
+ser.setServiceParent(service.IServiceCollection(application))
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py
new file mode 100644
index 0000000000..73361ae234
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py
@@ -0,0 +1,5 @@
+
+from twisted.application.service import ServiceMaker
+
+finger = ServiceMaker(
+ 'finger', 'finger.tap', 'Run a finger service', 'finger')
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/pb.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/pb.html
new file mode 100644
index 0000000000..65e1065a6a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/pb.html
@@ -0,0 +1,650 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: Twisted client support using Perspective Broker</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: Twisted client support using Perspective Broker</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Use Perspective Broker</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the seventh part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we add a Perspective Broker service to the finger application
+so that Twisted clients can access the finger server. Perspective Broker is
+introduced in depth in its own <a href="../index.html#pb" shape="rect">section</a> of the
+core howto index.</p>
+
+<h2>Use Perspective Broker<a name="auto1"/></h2>
+
+<p>We add support for perspective broker, Twisted's native remote object
+protocol. Now, Twisted clients will not have to go through XML-RPCish
+contortions to get information about users.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">microdom</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>=<span class="py-src-variable">service</span>
+
+ <span class="py-src-comment"># add a specific child for the path &quot;RPC2&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;RPC2&quot;</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-comment"># need to do this for resources at the root of the site</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;&quot;</span>, <span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">users</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">userOutput</span> = <span class="py-src-string">''</span>.<span class="py-src-variable">join</span>([<span class="py-src-string">&quot;&lt;li&gt;&lt;a href=\&quot;%s\&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;&quot;</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>])
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;
+ &lt;html&gt;&lt;head&gt;&lt;title&gt;Users&lt;/title&gt;&lt;/head&gt;&lt;body&gt;
+ &lt;h1&gt;Users&lt;/h1&gt;
+ &lt;ul&gt;
+ %s
+ &lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % <span class="py-src-variable">userOutput</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">user</span>=<span class="py-src-variable">path</span>, <span class="py-src-variable">service</span>=<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">status</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;&lt;html&gt;&lt;head&gt;&lt;title&gt;%s&lt;/title&gt;&lt;/head&gt;
+ &lt;body&gt;&lt;h1&gt;%s&lt;/h1&gt;
+ &lt;p&gt;%s&lt;/p&gt;
+ &lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>))
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IPerspectiveFinger</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">username</span>):
+ <span class="py-src-string">&quot;&quot;&quot;return a user's status&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;return a user's status&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PerspectiveFingerFromService</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPerspectiveFinger</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">username</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">PerspectiveFingerFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IPerspectiveFinger</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8889</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">IPerspectiveFinger</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger21.tac"><span class="filename">listings/finger/finger21.tac</span></a></div></div>
+
+<p>A simple client to test the perspective broker finger:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-comment"># test the PB finger on port 8889</span>
+<span class="py-src-comment"># this code is essentially the same as</span>
+<span class="py-src-comment"># the first example in howto/pb-usage</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">gotObject</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;got object:&quot;</span>, <span class="py-src-variable">object</span>
+ <span class="py-src-variable">object</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">&quot;getUser&quot;</span>,<span class="py-src-string">&quot;moshez&quot;</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">gotData</span>)
+<span class="py-src-comment"># or</span>
+<span class="py-src-comment"># object.callRemote(&quot;getUsers&quot;).addCallback(gotData)</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">gotData</span>(<span class="py-src-parameter">data</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'server sent:'</span>, <span class="py-src-variable">data</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">gotNoObject</span>(<span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;no object:&quot;</span>,<span class="py-src-variable">reason</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">&quot;127.0.0.1&quot;</span>,<span class="py-src-number">8889</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>().<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">gotObject</span>,<span class="py-src-variable">gotNoObject</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/finger/fingerPBclient.py"><span class="filename">listings/finger/fingerPBclient.py</span></a></div></div>
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/protocol.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/protocol.html
new file mode 100644
index 0000000000..ac38d59307
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/protocol.html
@@ -0,0 +1,1055 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: adding features to the finger service</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: adding features to the finger service</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Setting Message By Local Users</a></li><li><a href="#auto2">Use Services to Make Dependencies Sane</a></li><li><a href="#auto3">Read Status File</a></li><li><a href="#auto4">Announce on Web, Too</a></li><li><a href="#auto5">Announce on IRC, Too</a></li><li><a href="#auto6">Add XML-RPC Support</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the second part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this section of the tutorial, our finger server will continue to sprout
+features: the ability for users to set finger announces, and using our finger
+service to send those announcements on the web, on IRC and over XML-RPC.
+Resources and XML-RPC are introduced in the Web Applications portion of
+the <a href="../../../web/howto/index.html" shape="rect">Twisted Web howto</a>. More examples
+using <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.words.protocols.irc.html" title="twisted.words.protocols.irc">twisted.words.protocols.irc</a></code> can be found
+in <a href="../clients.html" shape="rect">Writing a TCP Client</a> and
+the <a href="../../../words/examples/index.html" shape="rect">Twisted Words examples</a>.</p>
+
+<h2>Setting Message By Local Users<a name="auto1"/></h2>
+
+<p>Now that port 1079 is free, maybe we can use it with a different
+server, one which will let people set their messages. It does
+no access control, so anyone who can login to the machine can
+set any message. We assume this is the desired behavior in
+our case. Testing it can be done by simply:
+</p>
+
+<pre class="shell" xml:space="preserve">
+% nc localhost 1079 # or telnet localhost 1079
+moshez
+Giving a tutorial now, sorry!
+^D
+</pre>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+</p><span class="py-src-comment"># But let's try and fix setting away messages, shall we?</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>[<span class="py-src-number">1</span>]
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">fingerFactory</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">fingerFactory</span> = <span class="py-src-variable">fingerFactory</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">fingerFactory</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+
+<span class="py-src-variable">ff</span> = <span class="py-src-variable">FingerFactory</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>)
+<span class="py-src-variable">fsf</span> = <span class="py-src-variable">FingerSetterFactory</span>(<span class="py-src-variable">ff</span>)
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>,<span class="py-src-variable">ff</span>).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">1079</span>,<span class="py-src-variable">fsf</span>).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger12.tac"><span class="filename">listings/finger/finger12.tac</span></a></div></div>
+
+<p>This program has two protocol-factory-TCPServer pairs, which are both child
+services of the application. Specifically, the
+<code base="API" class="twisted.application.service.Service">setServiceParent</code>
+method is used to define the two TCPServer services as children of
+<code>application</code>, which implements
+<code base="API" class="twisted.application.servce">IServiceCollection</code>.
+Both services are thus started with the application.</p>
+
+<h2>Use Services to Make Dependencies Sane<a name="auto2"/></h2>
+
+<p>The previous version had the setter poke at the innards of the
+finger factory. This strategy is usually not a good idea: this version makes
+both factories symmetric by making them both look at a single
+object. Services are useful for when an object is needed which is
+not related to a specific network server. Here, we define a common service
+class with methods that will create factories on the fly. The service
+also contains methods the factories will depend on.</p>
+
+<p>The factory-creation methods, <code>getFingerFactory</code> and
+<code>getFingerSetterFactory</code>, follow this pattern:</p>
+
+<ol>
+
+<li>Instantiate a generic server factory,
+<code>twisted.internet.protocol.ServerFactory</code>.</li>
+
+<li>Set the protocol class, just like our factory class would have.</li>
+
+<li>Copy a service method to the factory as a function attribute. The
+function won't have access to the factory's <code>self</code>, but that's OK
+because as a bound method it has access to the service's <code>self</code>,
+which is what it needs. For <code>getUser</code>, a custom method defined in
+the service gets copied. For <code>setUser</code>, a standard method of the
+<code>users</code> dictionary is copied.</li>
+
+</ol>
+
+<p>Thus, we stopped subclassing: the service simply puts useful methods and
+attributes inside the factories. We are getting better at protocol design:
+none of our protocol classes had to be changed, and neither will have to
+change until the end of the tutorial.</p>
+
+<p>As an application
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.service.html" title="twisted.application.service.service">service</a></code> , this new
+finger service implements the
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.IService.html" title="twisted.application.service.IService">IService</a></code> interface
+and can be started and stopped in a standardized manner. We'll make use of
+this in the next example.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+</p><span class="py-src-comment"># Fix asymmetry</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>,<span class="py-src-parameter">reason</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>[<span class="py-src-number">0</span>]
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>[<span class="py-src-number">1</span>]
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, **<span class="py-src-parameter">kwargs</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">kwargs</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerSetterFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">setUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">setUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-variable">moshez</span>=<span class="py-src-string">'Happy and well'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>,<span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">1079</span>,<span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerSetterFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger13.tac"><span class="filename">listings/finger/finger13.tac</span></a></div></div>
+
+
+
+<h2>Read Status File<a name="auto3"/></h2>
+
+<p>This version shows how, instead of just letting users set their
+messages, we can read those from a centrally managed file. We cache
+results, and every 30 seconds we refresh it. Services are useful
+for such scheduled tasks.</p>
+
+<div class="listing"><pre>
+moshez: happy and well
+shawn: alive
+</pre><div class="caption">sample /etc/users file - <a href="listings/finger/etc.users"><span class="filename">listings/finger/etc.users</span></a></div></div>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+</p><span class="py-src-comment"># Read from file</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">startService</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopService</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">service</span>.<span class="py-src-variable">Service</span>.<span class="py-src-variable">stopService</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">finger</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>())
+
+<span class="py-src-variable">finger</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+<span class="py-src-variable">f</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
+</pre><div class="caption">Source listing - <a href="listings/finger/finger14.tac"><span class="filename">listings/finger/finger14.tac</span></a></div></div>
+
+<p>Since this verison is reading data from a file (and refreshing the data
+every 30 seconds), there is no <code>FingerSetterFactory</code> and thus
+nothing listening on port 1079.</p>
+
+<p>Here we override the standard
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.Service.startService.html" title="twisted.application.service.Service.startService">startService</a></code>
+and
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.Service.stopService.html" title="twisted.application.service.Service.stopService">stopService</a></code>
+hooks in the Finger service, which is set up as a child service of
+the application in the last line of the code. <code>startService</code> calls
+<code>_read</code>, the function responsible for reading the data;
+<code>reactor.callLater</code> is then used to schedule it to run again after
+thirty seconds every time it is called. <code>reactor.callLater</code> returns
+an object that lets us cancel the scheduled run in <code>stopService</code>
+using its <code>cancel</code> method.</p>
+
+<h2>Announce on Web, Too<a name="auto4"/></h2>
+
+<p>The same kind of service can also produce things useful for
+other protocols. For example, in twisted.web, the factory
+itself (<code base="API" class="twisted.web.server">Site</code>) is almost
+never subclassed — instead, it is given a resource, which represents the tree
+of resources available via URLs. That hierarchy is navigated by
+<code base="API" class="twisted.web.server">Site</code>
+and overriding it dynamically is possible with
+<code base="API" class="twisted.web.resource.Resource">getChild</code>.</p>
+
+<p>To integrate this into the Finger application (just because we can), we set
+up a new TCPServer that calls the <code base="API" class="twisted.web.server">Site</code> factory and retrieves resources via a
+new function of <code>FingerService</code> named <code>getResource</code>.
+This function specifically returns a <code base="API" class="twisted.web.resource">Resource</code> object with an overridden <code base="API" class="twisted.web.resource.Resource">getChild</code> method.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+</p><span class="py-src-comment"># Read from file, announce on the web!</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerResource</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = <span class="py-src-variable">users</span>
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-comment"># we treat the path as the username</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">username</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ 'username' is a string.
+ 'request' is a 'twisted.web.server.Request'.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-variable">messagevalue</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">username</span>)
+ <span class="py-src-variable">username</span> = <span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>(<span class="py-src-variable">username</span>)
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">messagevalue</span> <span class="py-src-keyword">is</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">None</span>:
+ <span class="py-src-variable">messagevalue</span> = <span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>(<span class="py-src-variable">messagevalue</span>)
+ <span class="py-src-variable">text</span> = <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;&lt;p&gt;%s&lt;/p&gt;'</span> % (<span class="py-src-variable">username</span>,<span class="py-src-variable">messagevalue</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-variable">text</span> = <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;&lt;p&gt;No such user&lt;/p&gt;'</span> % <span class="py-src-variable">username</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">static</span>.<span class="py-src-variable">Data</span>(<span class="py-src-variable">text</span>, <span class="py-src-string">'text/html'</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getResource</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">FingerResource</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">r</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">f</span>.<span class="py-src-variable">getResource</span>())
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger15.tac"><span class="filename">listings/finger/finger15.tac</span></a></div></div>
+
+
+<h2>Announce on IRC, Too<a name="auto5"/></h2>
+
+<p>This is the first time there is client code. IRC clients often
+act a lot like servers: responding to events from the network.
+The reconnecting client factory will make sure that severed links
+will get re-established, with intelligent tweaked exponential
+back-off algorithms. The IRC client itself is simple: the only
+real hack is getting the nickname from the factory in connectionMade.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+</p><span class="py-src-comment"># Read from file, announce on the web, irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">user</span>, <span class="py-src-variable">msg</span>+<span class="py-src-string">': '</span>+<span class="py-src-variable">message</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getResource</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>()
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">getChild</span> = (<span class="py-src-keyword">lambda</span> <span class="py-src-variable">path</span>, <span class="py-src-variable">request</span>:
+ <span class="py-src-variable">static</span>.<span class="py-src-variable">Data</span>(<span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;&lt;p&gt;%s&lt;/p&gt;'</span> %
+ <span class="py-src-variable">tuple</span>(<span class="py-src-variable">map</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>,
+ [<span class="py-src-variable">path</span>,<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">path</span>,
+ <span class="py-src-string">&quot;No such user &lt;p/&gt; usage: site/user&quot;</span>)])),
+ <span class="py-src-string">'text/html'</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">r</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getIRCBot</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">nickname</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ReconnectingClientFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">f</span>.<span class="py-src-variable">getResource</span>())
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getIRCBot</span>(<span class="py-src-string">'fingerbot'</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger16.tac"><span class="filename">listings/finger/finger16.tac</span></a></div></div>
+
+<p><code>FingerService</code> now has another new function,
+<code>getIRCbot</code>, which returns the
+<code>ReconnectingClientFactory</code>. This factory in turn will instantiate
+the <code>IRCReplyBot</code> protocol. The IRCBot is configured in the last
+line to connect to <code>irc.freenode.org</code> with a nickname of
+<code>fingerbot</code>.</p>
+
+<p>By overriding <code>irc.IRCClient.connectionMade</code>,
+<code>IRCReplyBot</code> can access the <code>nickname</code> attribute of the
+factory that instantiated it.</p>
+
+<h2>Add XML-RPC Support<a name="auto6"/></h2>
+
+<p>In Twisted, XML-RPC support is handled just as though it was
+another resource. That resource will still support GET calls normally
+through render(), but that is usually left unimplemented. Note
+that it is possible to return deferreds from XML-RPC methods.
+The client, of course, will not get the answer until the deferred
+is triggered.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+</p><span class="py-src-comment"># Read from file, announce on the web, irc, xml-rpc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">message</span> + <span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">onError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Internal error in server'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">onError</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeResponse</span>(<span class="py-src-parameter">message</span>):
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">user</span>, <span class="py-src-variable">msg</span>+<span class="py-src-string">': '</span>+<span class="py-src-variable">message</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeResponse</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getResource</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>()
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">getChild</span> = (<span class="py-src-keyword">lambda</span> <span class="py-src-variable">path</span>, <span class="py-src-variable">request</span>:
+ <span class="py-src-variable">static</span>.<span class="py-src-variable">Data</span>(<span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;&lt;p&gt;%s&lt;/p&gt;'</span> %
+ <span class="py-src-variable">tuple</span>(<span class="py-src-variable">map</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>,
+ [<span class="py-src-variable">path</span>,<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">path</span>, <span class="py-src-string">&quot;No such user&quot;</span>)])),
+ <span class="py-src-string">'text/html'</span>))
+ <span class="py-src-variable">x</span> = <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>()
+ <span class="py-src-variable">x</span>.<span class="py-src-variable">xmlrpc_getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">x</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">r</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getIRCBot</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">nickname</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ReconnectingClientFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">f</span>.<span class="py-src-variable">getResource</span>())
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getIRCBot</span>(<span class="py-src-string">'fingerbot'</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger17.tac"><span class="filename">listings/finger/finger17.tac</span></a></div></div>
+
+<p>Instead of a web browser, we can test the XMLRPC finger using a simple
+client based on Python's built-in <code>xmlrpclib</code>, which will access
+the resource we've made available at <code>localhost/RPC2</code>.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-comment"># testing xmlrpc finger</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpclib</span>
+<span class="py-src-variable">server</span> = <span class="py-src-variable">xmlrpclib</span>.<span class="py-src-variable">Server</span>(<span class="py-src-string">'http://127.0.0.1:8000/RPC2'</span>)
+<span class="py-src-keyword">print</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-string">'moshez'</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/fingerXRclient.py"><span class="filename">listings/finger/fingerXRclient.py</span></a></div></div>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/style.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/style.html
new file mode 100644
index 0000000000..71931d9b79
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/style.html
@@ -0,0 +1,313 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: cleaning up the finger code</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: cleaning up the finger code</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Write Readable Code</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the third part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this section of the tutorial, we'll clean up our code so that it is
+closer to a readable and extendable style.</p>
+
+<h2>Write Readable Code<a name="auto1"/></h2>
+
+<p>The last version of the application had a lot of hacks. We avoided
+sub-classing, didn't support things like user listings over the web,
+and removed all blank lines -- all in the interest of code
+which is shorter. Here we take a step back, subclass what is more
+naturally a subclass, make things which should take multiple lines
+take them, etc. This shows a much better style of developing Twisted
+applications, though the hacks in the previous stages are sometimes
+used in throw-away prototypes.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+</p><span class="py-src-comment"># Do everything properly</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">formatUsers</span>(<span class="py-src-parameter">users</span>):
+ <span class="py-src-variable">l</span> = [<span class="py-src-string">'&lt;li&gt;&lt;a href=&quot;%s&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;'</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>]
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'&lt;ul&gt;'</span>+<span class="py-src-string">''</span>.<span class="py-src-variable">join</span>(<span class="py-src-variable">l</span>)+<span class="py-src-string">'&lt;/ul&gt;'</span>
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">formatUsers</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">path</span>==<span class="py-src-string">&quot;&quot;</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">path</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>:
+ <span class="py-src-string">'&lt;h1&gt;%s&lt;/h1&gt;'</span>%<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>+<span class="py-src-string">'&lt;p&gt;%s&lt;/p&gt;'</span>%<span class="py-src-variable">m</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>())
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getFingerFactory</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ServerFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getResource</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">UserStatusTree</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">x</span> = <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">x</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">r</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getIRCBot</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">nickname</span>):
+ <span class="py-src-variable">f</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ReconnectingClientFactory</span>()
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">f</span>.<span class="py-src-variable">getUser</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">getUser</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">f</span>
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getFingerFactory</span>()
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">f</span>.<span class="py-src-variable">getResource</span>())
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">f</span>.<span class="py-src-variable">getIRCBot</span>(<span class="py-src-string">'fingerbot'</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger18.tac"><span class="filename">listings/finger/finger18.tac</span></a></div></div>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/tutorial/web.html b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/web.html
new file mode 100644
index 0000000000..ebb0ca8bd0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/tutorial/web.html
@@ -0,0 +1,537 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Evolution of Finger: a web frontend</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Evolution of Finger: a web frontend</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p> This is the sixth part of the Twisted tutorial <a href="index.html" shape="rect">Twisted from Scratch, or The Evolution of Finger</a>.</p>
+
+<p>In this part, we demonstrate adding a web frontend using simple <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">twisted.web.resource.Resource</a></code> objects: <code class="python">UserStatusTree</code>, which will produce a listing of all
+users at the base URL (<code>/</code>) of our site; <code class="python">UserStatus</code>, which gives the status of each user at the
+locaton <code>/username</code>; and <code class="python">UserStatusXR</code>,
+which exposes an XMLRPC interface to <code class="python">getUser</code> and
+<code class="python">getUsers</code> functions at the URL <code>/RPC2</code>.</p>
+
+<p>In this example we construct HTML segments manually. If the web interface
+was less trivial, we would want to use more sophisticated web templating and
+design our system so that HTML rendering and logic were clearly separated.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+</p><span class="py-src-comment"># Do everything properly, and componentize</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>, <span class="py-src-variable">reactor</span>, <span class="py-src-variable">defer</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">words</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">irc</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">protocols</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">basic</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">components</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">microdom</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>():
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a list of strings&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterService</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Set the user's status to something&quot;&quot;&quot;</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">catchError</span>(<span class="py-src-parameter">err</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Internal error in server&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">writeValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">value</span>+<span class="py-src-string">'\r\n'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">loseConnection</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">writeValue</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IFingerFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterProtocol</span>(<span class="py-src-parameter">basic</span>.<span class="py-src-parameter">LineReceiver</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span> = []
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">lineReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">line</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">line</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>) == <span class="py-src-number">2</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">setUser</span>(*<span class="py-src-variable">self</span>.<span class="py-src-variable">lines</span>)
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IFingerSetterFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol returning a string&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerSetterFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ServerFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerSetterFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">FingerSetterProtocol</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">setUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">status</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">setUser</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>)
+
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">FingerSetterFactoryFromService</span>,
+ <span class="py-src-variable">IFingerSetterService</span>,
+ <span class="py-src-variable">IFingerSetterFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCReplyBot</span>(<span class="py-src-parameter">irc</span>.<span class="py-src-parameter">IRCClient</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionMade</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">nickname</span>
+ <span class="py-src-variable">irc</span>.<span class="py-src-variable">IRCClient</span>.<span class="py-src-variable">connectionMade</span>(<span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">privmsg</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">channel</span>, <span class="py-src-parameter">msg</span>):
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">'!'</span>)[<span class="py-src-number">0</span>]
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">nickname</span>.<span class="py-src-variable">lower</span>() == <span class="py-src-variable">channel</span>.<span class="py-src-variable">lower</span>():
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">factory</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">msg</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">catchError</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-string">&quot;Status of %s: %s&quot;</span> % (<span class="py-src-variable">msg</span>, <span class="py-src-variable">m</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">m</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">msg</span>(<span class="py-src-variable">user</span>, <span class="py-src-variable">m</span>))
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IIRCClientFactory</span>(<span class="py-src-parameter">Interface</span>):
+
+ <span class="py-src-string">&quot;&quot;&quot;
+ @ivar nickname
+ &quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">user</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a deferred returning a string&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">addr</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return a protocol&quot;&quot;&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">IRCClientFactoryFromService</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IIRCClientFactory</span>)
+
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">IRCReplyBot</span>
+ <span class="py-src-variable">nickname</span> = <span class="py-src-variable">None</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">IRCClientFactoryFromService</span>,
+ <span class="py-src-variable">IFingerService</span>,
+ <span class="py-src-variable">IIRCClientFactory</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusTree</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>=<span class="py-src-variable">service</span>
+
+ <span class="py-src-comment"># add a specific child for the path &quot;RPC2&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;RPC2&quot;</span>, <span class="py-src-variable">UserStatusXR</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>))
+
+ <span class="py-src-comment"># need to do this for resources at the root of the site</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;&quot;</span>, <span class="py-src-variable">self</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">users</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">userOutput</span> = <span class="py-src-string">''</span>.<span class="py-src-variable">join</span>([<span class="py-src-string">&quot;&lt;li&gt;&lt;a href=\&quot;%s\&quot;&gt;%s&lt;/a&gt;&lt;/li&gt;&quot;</span> % (<span class="py-src-variable">user</span>, <span class="py-src-variable">user</span>)
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">user</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">users</span>])
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;
+ &lt;html&gt;&lt;head&gt;&lt;title&gt;Users&lt;/title&gt;&lt;/head&gt;&lt;body&gt;
+ &lt;h1&gt;Users&lt;/h1&gt;
+ &lt;ul&gt;
+ %s
+ &lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % <span class="py-src-variable">userOutput</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">path</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">UserStatus</span>(<span class="py-src-variable">user</span>=<span class="py-src-variable">path</span>, <span class="py-src-variable">service</span>=<span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>)
+
+<span class="py-src-variable">components</span>.<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">UserStatusTree</span>, <span class="py-src-variable">IFingerService</span>, <span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatus</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_cb_render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">status</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;&quot;&quot;&lt;html&gt;&lt;head&gt;&lt;title&gt;%s&lt;/title&gt;&lt;/head&gt;
+ &lt;body&gt;&lt;h1&gt;%s&lt;/h1&gt;
+ &lt;p&gt;%s&lt;/p&gt;
+ &lt;/body&gt;&lt;/html&gt;&quot;&quot;&quot;</span> % (<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>, <span class="py-src-variable">status</span>))
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">user</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_cb_render_GET</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-comment"># signal that the rendering is not complete</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">UserStatusXR</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">service</span>):
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">XMLRPC</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span> = <span class="py-src-variable">service</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUser</span>(<span class="py-src-variable">user</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">service</span>.<span class="py-src-variable">getUsers</span>()
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FingerService</span>(<span class="py-src-parameter">service</span>.<span class="py-src-parameter">Service</span>):
+
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IFingerService</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">filename</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span> = <span class="py-src-variable">filename</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span> = {}
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_read</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">clear</span>()
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">line</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">file</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">filename</span>):
+ <span class="py-src-variable">user</span>, <span class="py-src-variable">status</span> = <span class="py-src-variable">line</span>.<span class="py-src-variable">split</span>(<span class="py-src-string">':'</span>, <span class="py-src-number">1</span>)
+ <span class="py-src-variable">user</span> = <span class="py-src-variable">user</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">status</span> = <span class="py-src-variable">status</span>.<span class="py-src-variable">strip</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>[<span class="py-src-variable">user</span>] = <span class="py-src-variable">status</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">30</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_read</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUser</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">user</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">get</span>(<span class="py-src-variable">user</span>, <span class="py-src-string">&quot;No such user&quot;</span>))
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getUsers</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">users</span>.<span class="py-src-variable">keys</span>())
+
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'finger'</span>, <span class="py-src-variable">uid</span>=<span class="py-src-number">1</span>, <span class="py-src-variable">gid</span>=<span class="py-src-number">1</span>)
+<span class="py-src-variable">f</span> = <span class="py-src-variable">FingerService</span>(<span class="py-src-string">'/etc/users'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">79</span>, <span class="py-src-variable">IFingerFactory</span>(<span class="py-src-variable">f</span>)
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8000</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>.<span class="py-src-variable">IResource</span>(<span class="py-src-variable">f</span>))
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">IIRCClientFactory</span>(<span class="py-src-variable">f</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">nickname</span> = <span class="py-src-string">'fingerbot'</span>
+<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'irc.freenode.org'</span>, <span class="py-src-number">6667</span>, <span class="py-src-variable">i</span>
+ ).<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">serviceCollection</span>)
+</pre><div class="caption">Source listing - <a href="listings/finger/finger20.tac"><span class="filename">listings/finger/finger20.tac</span></a></div></div>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/udp.html b/vendor/Twisted-10.0.0/doc/core/howto/udp.html
new file mode 100644
index 0000000000..27272ffd70
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/udp.html
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: UDP Networking</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">UDP Networking</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">DatagramProtocol</a></li><li><a href="#auto2">Connected UDP</a></li><li><a href="#auto3">Multicast UDP</a></li><li><a href="#auto4">Acknowledgements</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Overview<a name="auto0"/></h2>
+
+ <p>Unlike TCP, UDP has no notion of connections. A UDP socket can receive
+ datagrams from any server on the network, and send datagrams to any host
+ on the network. In addition, datagrams may arrive in any order, never
+ arrive at all, or be duplicated in transit.</p>
+
+ <p>Since there are no multiple connections, we only use a single object,
+ a protocol, for each UDP socket. We then use the reactor to connect
+ this protocol to a UDP transport, using the
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorUDP.html" title="twisted.internet.interfaces.IReactorUDP">twisted.internet.interfaces.IReactorUDP</a></code>
+ reactor API.</p>
+
+ <h2>DatagramProtocol<a name="auto1"/></h2>
+
+ <p>At the base, the place where you actually implement the protocol
+ parsing and handling, is the DatagramProtocol class. This class will
+ usually be decended from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.DatagramProtocol.html" title="twisted.internet.protocol.DatagramProtocol">twisted.internet.protocol.DatagramProtocol</a></code>. Most
+ protocol handlers inherit either from this class or from one of its
+ convenience children. The DatagramProtocol class receives datagrams, and
+ can send them out over the network. Received datagrams include the
+ address they were sent from, and when sending datagrams the address to
+ send to must be specified.</p>
+
+ <p>Here is a simple example:</p>
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DatagramProtocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Echo</span>(<span class="py-src-parameter">DatagramProtocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">datagramReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>, (<span class="py-src-parameter">host</span>, <span class="py-src-parameter">port</span>)):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;received %r from %s:%d&quot;</span> % (<span class="py-src-variable">data</span>, <span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">data</span>, (<span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>))
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenUDP</span>(<span class="py-src-number">9999</span>, <span class="py-src-variable">Echo</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>As you can see, the protocol is registed with the reactor. This means
+ it may be persisted if it's added to an application, and thus it has
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.DatagramProtocol.startProtocol.html" title="twisted.internet.protocol.DatagramProtocol.startProtocol">twisted.internet.protocol.DatagramProtocol.startProtocol</a></code>
+ and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.protocol.DatagramProtocol.stopProtocol.html" title="twisted.internet.protocol.DatagramProtocol.stopProtocol">twisted.internet.protocol.DatagramProtocol.stopProtocol</a></code>
+ methods that will get called when the protocol is connected and
+ disconnected from a UDP socket.</p>
+
+ <p>The protocol's <code class="python">transport</code> attribute will
+ implement the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IUDPTransport.html" title="twisted.internet.interfaces.IUDPTransport">twisted.internet.interfaces.IUDPTransport</a></code> interface.
+ Notice that the <code class="python">host</code> argument should be an
+ IP, not a hostname. If you only have the hostname use <code class="python">reactor.resolve()</code> to resolve the address (see <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorCore.resolve.html" title="twisted.internet.interfaces.IReactorCore.resolve">twisted.internet.interfaces.IReactorCore.resolve</a></code>).</p>
+
+
+ <h2>Connected UDP<a name="auto2"/></h2>
+
+ <p>A connected UDP socket is slighly different from a standard one - it
+ can only send and receive datagrams to/from a single address, but this
+ does not in any way imply a connection. Datagrams may still arrive in any
+ order, and the port on the other side may have no one listening. The
+ benefit of the connected UDP socket is that it it <strong>may</strong>
+ provide notification of undelivered packages. This depends on many
+ factors, almost all of which are out of the control of the application,
+ but it still presents certain benefits which occassionally make it
+ useful.</p>
+
+ <p>Unlike a regular UDP protocol, we do not need to specify where to
+ send datagrams to, and are not told where they came from since
+ they can only come from address the socket is 'connected' to.</p>
+
+ <pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DatagramProtocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Helloer</span>(<span class="py-src-parameter">DatagramProtocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startProtocol</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">connect</span>(<span class="py-src-string">&quot;192.168.1.1&quot;</span>, <span class="py-src-number">1234</span>)
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;we can only send to %s now&quot;</span> % <span class="py-src-variable">str</span>((<span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>))
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;hello&quot;</span>) <span class="py-src-comment"># no need for address</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">datagramReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">data</span>, (<span class="py-src-parameter">host</span>, <span class="py-src-parameter">port</span>)):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;received %r from %s:%d&quot;</span> % (<span class="py-src-variable">data</span>, <span class="py-src-variable">host</span>, <span class="py-src-variable">port</span>)
+
+ <span class="py-src-comment"># Possibly invoked if there is no server listening on the</span>
+ <span class="py-src-comment"># address to which we are sending.</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionRefused</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;No one listening&quot;</span>
+
+<span class="py-src-comment"># 0 means any port, we don't care in this case</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenUDP</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">Helloer</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+ <p>Note that <code class="python">connect()</code>, like <code class="python">write()</code> will only accept IP addresses, not
+ unresolved domain names. To obtain the IP of a domain name use <code class="python">reactor.resolve()</code>, e.g.:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">gotIP</span>(<span class="py-src-parameter">ip</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;IP of 'example.com' is&quot;</span>, <span class="py-src-variable">ip</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">resolve</span>(<span class="py-src-string">'example.com'</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">gotIP</span>)
+</pre>
+
+ <p>Connecting to a new address after a previous connection, or
+ making a connected port unconnected are not currently supported,
+ but will likely be supported in the future.</p>
+
+ <h2>Multicast UDP<a name="auto3"/></h2>
+
+ <p>A multicast UDP socket can send and receive datagrams from multiple clients.
+ The interesting and useful feature of the multicast is that a client can
+ contact multiple servers with a single packet, without knowing the specific IP
+ of any of the hosts.</p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DatagramProtocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">MulticastServer</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MulticastServerUDP</span>(<span class="py-src-parameter">DatagramProtocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startProtocol</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Started Listening'</span>
+ <span class="py-src-comment"># Join a specific multicast group, which is the IP we will respond to</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">joinGroup</span>(<span class="py-src-string">'224.0.0.1'</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">datagramReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">datagram</span>, <span class="py-src-parameter">address</span>):
+ <span class="py-src-comment"># The uniqueID check is to ensure we only service requests from</span>
+ <span class="py-src-comment"># ourselves</span>
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">datagram</span> == <span class="py-src-string">'UniqueID'</span>:
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Server Received:&quot;</span> + <span class="py-src-variable">repr</span>(<span class="py-src-variable">datagram</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">transport</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;data&quot;</span>, <span class="py-src-variable">address</span>)
+
+<span class="py-src-comment"># Note that the join function is picky about having a unique object</span>
+<span class="py-src-comment"># on which to call join. To avoid using startProtocol, the following is</span>
+<span class="py-src-comment"># sufficient:</span>
+<span class="py-src-comment">#reactor.listenMulticast(8005, MulticastServerUDP()).join('224.0.0.1')</span>
+
+<span class="py-src-comment"># Listen for multicast on 224.0.0.1:8005</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenMulticast</span>(<span class="py-src-number">8005</span>, <span class="py-src-variable">MulticastServerUDP</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">Source listing - <a href="listings/udp/MulticastServer.py"><span class="filename">listings/udp/MulticastServer.py</span></a></div></div>
+
+ <p>
+ The server protocol is very simple, and closely resembles a normal listenUDP
+ implementation. The main difference is that instead of listenUDP,
+ listenMulticast is called with a specified port number. The server must also
+ call joinGroup to specify on which multicast IP address it will service
+ requests. Another item of interest is the contents of the datagram. Many
+ different applications use multicast as a way of device discovery, which leads
+ to an abundance of packets flying around. Checking the payload can ensure
+ that we only service requests from our specific clients.
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DatagramProtocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">MulticastServer</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MulticastClientUDP</span>(<span class="py-src-parameter">DatagramProtocol</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">datagramReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">datagram</span>, <span class="py-src-parameter">address</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Received:&quot;</span> + <span class="py-src-variable">repr</span>(<span class="py-src-variable">datagram</span>)
+
+<span class="py-src-comment"># Send multicast on 224.0.0.1:8005, on our dynamically allocated port</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenUDP</span>(<span class="py-src-number">0</span>, <span class="py-src-variable">MulticastClientUDP</span>()).<span class="py-src-variable">write</span>(<span class="py-src-string">'UniqueID'</span>,
+ (<span class="py-src-string">'224.0.0.1'</span>, <span class="py-src-number">8005</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">MulticastServer.py - <a href="listings/udp/MulticastClient.py"><span class="filename">listings/udp/MulticastClient.py</span></a></div></div>
+
+ <p>
+ This is a mirror implementation of a standard UDP client. The only difference
+ is that the destination IP is the multicast address. This datagram will be
+ distributed to every server listening on 224.0.0.1 and port 8005. Note that
+ the client port is specified as 0, as we have no need to keep track of what
+ port the client is listening on.
+ </p>
+
+<h2>Acknowledgements<a name="auto4"/></h2>
+
+<p>Thank you to all contributors to this document, including:</p>
+
+<ul>
+<li>Kyle Robertson, author of the explanation and examples of multicast</li>
+</ul>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/upgrading.html b/vendor/Twisted-10.0.0/doc/core/howto/upgrading.html
new file mode 100644
index 0000000000..3719ce1b77
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/upgrading.html
@@ -0,0 +1,331 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Upgrading Applications</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Upgrading Applications</h1>
+ <div class="toc"><ol><li><a href="#auto0">Basic Persistence: Application and .tap files</a></li><li><a href="#auto1">Versioned: New Code Meets Old Data</a></li><li><a href="#auto2">Rebuild: Loading New Code Without Restarting</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p>Applications must frequently deal with data that lives longer than the
+programs that create it. Sometimes the structure of that data changes over
+time, but new versions of a program must be able to accomodate data created
+by an older version. These versions may change very quickly, especially
+during development of new code. Sometimes different versions of the same
+program are running at the same time, sharing data across a network
+connection. These situations all result in a need for a way to upgrade data
+structures. </p>
+
+<h2>Basic Persistence: Application and .tap files<a name="auto0"/></h2>
+
+<p>Simple object persistence (using <code>pickle</code> or
+<code>jelly</code>) provides the fundamental <q>save the object to disk</q>
+functionality at application shutdown. If you use the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code> object, every object
+referenced by your Application will be saved into the
+<code>-shutdown.tap</code> file when the program terminates. When you use
+<code>twistd</code> to launch that new .tap file, the Application object
+will be restored along with all of its referenced data.</p>
+
+<p>This provides a simple way to have data outlive any particular invocation
+of your program: simply store it as an attribute of the Application. Note
+that all Services are referenced by the Application, so their attributes
+will be stored as well. Ports that have been bound with listenTCP (and the
+like) are also remembered, and the sockets are created at startup time (when
+<code>Application.run</code> is called).</p>
+
+<p>To influence the way that the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code> is persisted, you can adapt
+it to <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.sob.IPersistable.html" title="twisted.persisted.sob.IPersistable">twisted.persisted.sob.IPersistable</a></code> and use
+the <code class="python">setStyle(style)</code> method with
+a string like <q>pickle</q> or <q>source</q>. These use different serializers (and different
+extensions: <q>.tap</q> and <q>.tas</q> respectively) for the
+saved Application.</p>
+
+<p>You can manually cause the application to be saved by calling its
+<code>.save</code> method (on the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.sob.IPersistable.html" title="twisted.persisted.sob.IPersistable">twisted.persisted.sob.IPersistable</a></code>
+adapted object).</p>
+
+
+<h2>Versioned: New Code Meets Old Data<a name="auto1"/></h2>
+
+<p>So suppose you're running version 1 of some application, and you want to
+upgrade to version 2. You shut down the program, giving you a .tap file that
+you could restore with twistd to get back to the same state that you had
+before. The upgrade process is to then install the new version of the
+application, and then use twistd to launch the saved .tap file. The old data
+will be loaded into classes created with the new code, and now you'll have a
+program running with the new behavior but the old data.</p>
+
+<p>But what about the data structures that have changed? Since these
+structures are really just pickled class instances, the real question is
+what about the class definitions that have changed? Changes to class methods
+are easy: nothing about them is saved in the .tap file. The issue is when
+the data attributes of a instance are added, removed, or their format is
+changed.</p>
+
+<p>Twisted provides a mechanism called <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.styles.Versioned.html" title="twisted.persisted.styles.Versioned">Versioned</a></code> to ease these upgrades.
+Each version of the data structure (i.e. each version of the class) gets a
+version number. This number must change every time you add or remove a data
+attribute to the class. It must also change every time you modify one of
+those data attributes: for example, if you use a string in one version and
+an integer in another, those versions must have different version numbers.
+</p>
+
+<p>The version number is defined in a class attribute named
+<code>persistenceVersion</code>. This is an integer which will be stored in
+the .tap file along with the rest of the instance state. When the object is
+unserialized, the saved persistenceVersion is compared against the current
+class's value, and if they differ, special upgrade methods are called. These
+methods are named <code>upgradeToVersionNN</code>, and there must be one for
+each intermediate version. These methods are expected to manipulate the
+instance's state from the previous version's format into that of the new
+version.</p>
+
+<p>To use this, simply have your class inherit from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.styles.Versioned.html" title="twisted.persisted.styles.Versioned">Versioned</a></code>. You don't have to do this
+from the very beginning of time: all objects have an implicit version number
+of <q>0</q> when they don't inherit from Versioned. So when you first make
+an incompatible data-format change to your class, add Versioned to the
+inheritance list, and add an <code>upgradeToVersion1</code> method.</p>
+
+<p>For example, suppose the first version of our class saves an integer
+which measures the size of a line. We release this as version 1.0 of our
+neat application:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Thing</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">length</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span> = <span class="py-src-variable">length</span>
+</pre>
+
+<p>Then we fix some bugs elsewhere, and release versions 1.1 and 1.2 of the
+application. Later, we decide that we should add some units to the length,
+so that people can refer to it in inches or meters. Version 1.3 is shipped
+with the following code:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Thing</span>(<span class="py-src-parameter">Versioned</span>):
+ <span class="py-src-variable">persistenceVersion</span> = <span class="py-src-number">1</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">length</span>, <span class="py-src-parameter">units</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span> = <span class="py-src-string">&quot;%d %s&quot;</span> % (<span class="py-src-variable">length</span>, <span class="py-src-variable">units</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion1</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span> = <span class="py-src-string">&quot;%d inches&quot;</span> % <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span>
+</pre>
+
+<p>Note that we must make an assumption about what the previous value meant:
+in this case, we assume the number was in inches.</p>
+
+<p>1.4 and 1.5 are shipped with other changes. Then in version 1.6 we decide
+that saving the two values as a string was foolish and that it would be
+better to save the number and the string separately, using a tuple. We ship
+1.6 with the following:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Thing</span>(<span class="py-src-parameter">Versioned</span>):
+ <span class="py-src-variable">persistenceVersion</span> = <span class="py-src-number">2</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">length</span>, <span class="py-src-parameter">units</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span> = (<span class="py-src-variable">length</span>, <span class="py-src-variable">units</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion1</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span> = <span class="py-src-string">&quot;%d inches&quot;</span> % <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion2</span>(<span class="py-src-parameter">self</span>):
+ (<span class="py-src-variable">length</span>, <span class="py-src-variable">units</span>) = <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span>.<span class="py-src-variable">split</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span> = (<span class="py-src-variable">length</span>, <span class="py-src-variable">units</span>)
+</pre>
+
+<p>Note that we must provide both <code>upgradeToVersion1</code>
+<em>and</em> <code>upgradeToVersion2</code>. We have to assume that the
+saved .tap files which will be provided to this class come from a random
+assortment of old versions: we must be prepared to accept anything ever
+saved by a released version of our application.</p>
+
+<p>Finally, version 2.0 adds multiple dimensions. Instead of merely
+recording the length of a line, it records the size of an N-dimensional
+rectangular solid. For backwards compatiblity, all 1.X version of the
+program are assumed to be dealing with a 1-dimensional line. We change the
+name of the attribute from <code>.length</code> to <code>.size</code> to
+reflect the new meaning.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Thing</span>(<span class="py-src-parameter">Versioned</span>):
+ <span class="py-src-variable">persistenceVersion</span> = <span class="py-src-number">3</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">dimensions</span>):
+ <span class="py-src-comment"># dimensions is a list of tuples, each is (length, units)</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">size</span> = <span class="py-src-variable">dimensions</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = [<span class="py-src-string">&quot;line&quot;</span>, <span class="py-src-string">&quot;square&quot;</span>, <span class="py-src-string">&quot;cube&quot;</span>, <span class="py-src-string">&quot;hypercube&quot;</span>][<span class="py-src-variable">len</span>(<span class="py-src-variable">dimensions</span>)]
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion1</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span> = <span class="py-src-string">&quot;%d inches&quot;</span> % <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion2</span>(<span class="py-src-parameter">self</span>):
+ (<span class="py-src-variable">length</span>, <span class="py-src-variable">units</span>) = <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span>.<span class="py-src-variable">split</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span> = (<span class="py-src-variable">length</span>, <span class="py-src-variable">units</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion3</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">size</span> = [<span class="py-src-variable">self</span>.<span class="py-src-variable">length</span>]
+ <span class="py-src-keyword">del</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">name</span> = <span class="py-src-string">&quot;line&quot;</span>
+</pre>
+
+<p>If a .tap file from the earliest version of our program were to be loaded
+by the latest code, the following sequence would occur for each Thing
+instance contained inside:</p>
+
+<ol>
+
+ <li>An instance of Thing would be created, with a __dict__ that contained
+ a single attribute <code>.size</code>, which was an integer, like
+ <q>5</q>.</li>
+
+ <li><code class="python">self.upgradeToVersion1()</code> would be called,
+ changing <code>self.size</code> into a string, like <q>5 inches</q>.</li>
+
+ <li><code class="python">self.upgradeToVersion2()</code> would be called,
+ changing <code>self.size</code> into a tuple, like (5,
+ <q>inches</q>).</li>
+
+ <li>Finally, <code class="python">self.upgradeToVersion3()</code> would be
+ called, creating <code>self.size</code> as a list holding a single
+ dimension, like [(5, <q>inches</q>)]. The old <code>.length</code>
+ attribute is deleted, and a new <code>.name</code> is created with the
+ type of shape this instance represents (<q>line</q>).</li>
+
+</ol>
+
+<p>Some hints for the <code>upgradeVersion</code> methods:</p>
+
+<ul>
+
+ <li>They must do everything the <code>__init__</code> method would have
+ done, as well as any methods that might have been called during the
+ lifetime of the object.</li>
+
+ <li>If the class has (or used to have) methods which can add attributes
+ that weren't created in <code>__init__</code>, then the saved object may
+ have a haphazard subset of those attributes, depending upon which methods
+ were called. The upgradeVersion methods must be prepared to deal with
+ this. <code>hasattr</code> and <code>.get</code> may be useful.</li>
+
+ <li>Once you have released a class with a given
+ <code>upgradeVersion</code> method, you should never change that method.
+ (assuming you care about infinite backwards compatibility).</li>
+
+ <li>You must add a new <code>upgradeVersion</code> method (and bump the
+ persistenceVersion value) for each and every release that has a different
+ set of data attributes than the previous release.</li>
+
+ <li><code>Versioned</code> works by providing <code>__setstate__</code>
+ and <code>__getstate__</code> methods. You probably don't want to override
+ these methods without being very careful to call the Versioned versions at
+ exactly the right time. It also requires a <code>doUpgrade</code> function
+ to be called after all the objects are loaded. This is done automatically
+ by <code>Application.run</code>.</li>
+
+ <li>Depending upon how they are serialized, <code>Versioned</code> objects
+ can probably be sent across a network connection, and the upgrade process
+ can be made to occur upon receipt. (You'll want to look at the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.styles.requireUpgrade.html" title="twisted.persisted.styles.requireUpgrade">requireUpgrade</a></code>
+ function). This might be useful in providing compability with an older
+ peer. Note, however, that <code>Versioned</code> does not let you go
+ backwards in time; there is no <code>downgradeVersionNN</code> method.
+ This means it is probably only useful for compatibility in one direction:
+ the newer-to-older direction must still be explicitly handled by the
+ application.</li>
+
+ <li>In general, backwards compatibility is handled by pretending that the
+ old code was restricting itself to a narrow subset of the capabilities of
+ the new code. The job of the upgrade routines is then to translate the old
+ representation into a new one.</li>
+
+</ul>
+
+<p>For more information, look at the doc strings for <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.styles.Versioned.html" title="twisted.persisted.styles.Versioned">styles.Versioned</a></code>, as well as the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.app.Application.html" title="twisted.internet.app.Application">app.Application</a></code> class and the <a href="application.html" shape="rect">Application HOWTO</a>.</p>
+
+
+<h2>Rebuild: Loading New Code Without Restarting<a name="auto2"/></h2>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/Versioned.html" title="Versioned">Versioned</a></code> is good for handling changes between
+released versions of your program, where the application state is saved on
+disk during the upgrade. But while you are developing that code, you often
+want to change the behavior of the running program, <em>without</em> the
+slowdown of saving everything out to disk, shutting down, and restarting.
+Sometimes it will be difficult or time-consuming to get back to the previous
+state: the running program could include ephemeral objects (like open
+sockets) which cannot be persisted.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.rebuild.html" title="twisted.python.rebuild">twisted.python.rebuild</a></code> provides a function
+called <code>rebuild</code> which helps smooth this cycle. It allows objects
+in a running program to be upgraded to a new version of the code without
+shutting down.</p>
+
+<p>To use it, simply call <code class="python">rebuild</code> on the module
+that holds the classes you want to be upgraded. Through deep <code class="python">gc</code> magic, all instances of classes in that module will
+be located and upgraded.</p>
+
+<p>Typically, this is done in response to a privileged command sent over a
+network connection. The usual development cycle is to start the server, get
+it into an interesting state, see a problem, edit the class definition, then
+push the <q>rebuild yourself</q> button. That <q>button</q> could be a magic
+web page which, when requested, runs <code class="python">rebuild(mymodule)</code>, or a special IRC command, or
+perhaps just a socket that listens for connections and accepts a password to
+trigger the rebuild. (You want this to be a privileged operation to prevent
+someone from making your server do a rebuild while you're in the middle of
+editing the code).</p>
+
+<p>A few useful notes about the rebuild process:</p>
+
+<ul>
+ <li>If the module has a top-level attribute named
+ <code>ALLOW_TWISTED_REBUILD</code>, this attribute must evaluate to True.
+ Should it be false, the rebuild attempt will raise an exception.</li>
+
+ <li>Adapters (from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.html" title="twisted.python.components">twisted.python.components</a></code>) use
+ top-level registration function calls. These are handled correctly during
+ rebuilds, and the usual duplicate registration errors are not raised.</li>
+
+ <li>Rebuilds may be slow: every single object known to the interpreter
+ must be examined to see if it is one of the classes being changed.</li>
+</ul>
+
+<p>Finally, note that <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.rebuild.rebuild.html" title="twisted.python.rebuild.rebuild">rebuild</a></code> <em>cannot</em> currently be
+mixed with <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.persisted.styles.Versioned.html" title="twisted.persisted.styles.Versioned">Versioned</a></code>. <code>rebuild</code> does
+not run any of the classes' methods, whereas <code>Versioned</code> works by
+running <code>__setstate__</code> during the load process and
+<code>doUpgrade</code> afterwards. This means <code>rebuild</code> can only
+be used to process upgrades that do not change the data attributes of any of
+the involved classes. Any time attributes are added or removed, the program
+must be shut down, persisted, and restarted, with upgradeToVersionNN methods
+used to handle the attributes. (this may change in the future, but for now
+the implementation is easier and more reliable with this restriction).</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/vision.html b/vendor/Twisted-10.0.0/doc/core/howto/vision.html
new file mode 100644
index 0000000000..8b763b12c5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/vision.html
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Vision For Twisted</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Vision For Twisted</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <p>Many other documents in this repository are dedicated to
+ defining what Twisted is. Here, I will attempt to explain not
+ what Twisted is, but what it should be, once I've met my goals
+ with it.</p>
+
+ <p>First, Twisted should be fun. It began as a game, it is
+ being used commercially in games, and it will be, I hope, an
+ interactive and entertaining experience for the end-user.</p>
+
+ <p>Twisted is a platform for developing internet applications.
+ While python, by itself, is a very powerful language, there are
+ many facilities it lacks which other languages have spent great
+ attention to adding. It can do this now; Twisted is a good (if
+ somewhat idiosyncratic) pure-python framework or library,
+ depending on how you treat it, and it continues to improve.</p>
+
+ <p>As a platform, Twisted should be focused on integration.
+ Ideally, all functionality will be accessible through all
+ protocols. Failing that, all functionality should be
+ configurable through at least one protocol, with a seamless and
+ consistent user-interface. The next phase of development will
+ be focusing strongly on a configuration system which will unify
+ many disparate pieces of the current infrastructure, and allow
+ them to be tacked together by a non-programmer.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/howto/website-template.tpl b/vendor/Twisted-10.0.0/doc/core/howto/website-template.tpl
new file mode 100644
index 0000000000..cf742e8e98
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/howto/website-template.tpl
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+<title>Twisted Documentation: </title>
+<link type="text/css" rel="stylesheet"
+href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title"></h1>
+ <div class="toc"></div>
+ <div class="body">
+
+ </div>
+
+ <p><a href="index">Index</a></p>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/core/img/TwistedLogo.bmp b/vendor/Twisted-10.0.0/doc/core/img/TwistedLogo.bmp
new file mode 100644
index 0000000000..940ede07ea
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/TwistedLogo.bmp
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/img/cred-login.dia b/vendor/Twisted-10.0.0/doc/core/img/cred-login.dia
new file mode 100644
index 0000000000..f9dfaa7a11
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/cred-login.dia
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/img/cred-login.png b/vendor/Twisted-10.0.0/doc/core/img/cred-login.png
new file mode 100644
index 0000000000..a27dff4b27
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/cred-login.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/img/deferred-attach.dia b/vendor/Twisted-10.0.0/doc/core/img/deferred-attach.dia
new file mode 100644
index 0000000000..9e42967292
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/deferred-attach.dia
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/img/deferred-attach.png b/vendor/Twisted-10.0.0/doc/core/img/deferred-attach.png
new file mode 100644
index 0000000000..80500582fa
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/deferred-attach.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/img/deferred-process.dia b/vendor/Twisted-10.0.0/doc/core/img/deferred-process.dia
new file mode 100644
index 0000000000..37c5dd36c7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/deferred-process.dia
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/img/deferred-process.png b/vendor/Twisted-10.0.0/doc/core/img/deferred-process.png
new file mode 100644
index 0000000000..d4047eb5de
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/deferred-process.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/img/deferred.dia b/vendor/Twisted-10.0.0/doc/core/img/deferred.dia
new file mode 100644
index 0000000000..f27410a4eb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/deferred.dia
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/img/deferred.png b/vendor/Twisted-10.0.0/doc/core/img/deferred.png
new file mode 100644
index 0000000000..069d1d5fe0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/deferred.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/img/twisted-overview.dia b/vendor/Twisted-10.0.0/doc/core/img/twisted-overview.dia
new file mode 100644
index 0000000000..fa78656688
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/twisted-overview.dia
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/img/twisted-overview.png b/vendor/Twisted-10.0.0/doc/core/img/twisted-overview.png
new file mode 100644
index 0000000000..e9c71a6cad
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/img/twisted-overview.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/core/index.html b/vendor/Twisted-10.0.0/doc/core/index.html
new file mode 100644
index 0000000000..c033d440d2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/index.html
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Core Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Core Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Core to develop your own applications</li>
+<li><a href="upgrades/index.html" shape="rect">Upgrades between versions</a>:
+documentation specific to upgrading between versions of Twisted core</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Core</li>
+<li><a href="specifications/index.html" shape="rect">Specifications</a>: specification
+documents for elements of Twisted Core</li>
+<li><a href="development/index.html" shape="rect">Development of Twisted</a>: for people who
+want to work on Twisted itself</li>
+</ul>
+<p>An <a href="http://twistedmatrix.com/documents/current/api/" shape="rect">API
+ reference</a> is available on the twistedmatrix web site.</p>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/man/manhole-man.html b/vendor/Twisted-10.0.0/doc/core/man/manhole-man.html
new file mode 100644
index 0000000000..8e8e6fafa0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/manhole-man.html
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: MANHOLE.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">MANHOLE.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>manhole - Connect to a Twisted Manhole service
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>manhole</strong> </p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>manhole is a GTK interface to Twisted Manhole services. You can execute python code as if at an interactive Python console inside a running Twisted process with this.
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Chris Armstrong, copied from Moshe Zadka's <q>faucet</q> manpage.
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2000-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/man/manhole.1 b/vendor/Twisted-10.0.0/doc/core/man/manhole.1
new file mode 100644
index 0000000000..3d78617cd9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/manhole.1
@@ -0,0 +1,16 @@
+.TH MANHOLE "1" "August 2001" "" ""
+.SH NAME
+manhole \- Connect to a Twisted Manhole service
+.SH SYNOPSIS
+.B manhole
+.SH DESCRIPTION
+manhole is a GTK interface to Twisted Manhole services. You can execute python code as if at an interactive Python console inside a running Twisted process with this.
+.SH AUTHOR
+Written by Chris Armstrong, copied from Moshe Zadka's "faucet" manpage.
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/Twisted-10.0.0/doc/core/man/mktap-man.html b/vendor/Twisted-10.0.0/doc/core/man/mktap-man.html
new file mode 100644
index 0000000000..c3cf9617da
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/mktap-man.html
@@ -0,0 +1,328 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: MKTAP.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">MKTAP.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">portforward options</a></li><li><a href="#auto4">web options</a></li><li><a href="#auto5">toc options</a></li><li><a href="#auto6">mail options</a></li><li><a href="#auto7">telnet options</a></li><li><a href="#auto8">socks options</a></li><li><a href="#auto9">ftp options</a></li><li><a href="#auto10">manhole options</a></li><li><a href="#auto11">words options</a></li><li><a href="#auto12">AUTHOR</a></li><li><a href="#auto13">REPORTING BUGS</a></li><li><a href="#auto14">COPYRIGHT</a></li><li><a href="#auto15">SEE ALSO</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>mktap - create twisted.servers
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>mktap</strong> [<em>options</em>] <em>apptype</em> [<em>application_option</em>]...
+</p>
+
+<p><strong>mktap</strong> <em>apptype</em> --help
+</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>The <strong>--help</strong> prints out a usage message to standard output.
+<dl><dt><strong>--debug</strong>, <strong>-d</strong>
+</dt><dd>Show debug information for plugin loading.
+</dd>
+
+<dt><strong>--progress</strong>, <strong>-p</strong>
+</dt><dd>Show progress information for plugin loading.
+</dd>
+
+<dt><strong>--encrypted</strong>, <strong>-e</strong>
+</dt><dd>Encrypt file before writing (will make the extension of the resultant file begin with 'e').
+</dd>
+
+<dt><strong>--uid</strong>, <strong>-u</strong> <em>&lt;uid&gt;</em>
+</dt><dd>Application belongs to this uid, and should run with its permissions.
+</dd>
+
+<dt><strong>--gid</strong>, <strong>-d</strong> <em>&lt;gid&gt;</em>
+</dt><dd>Application belongs to this gid, and should run with its permissions.
+</dd>
+
+<dt><strong>--append</strong>, <strong>-a</strong> <em>&lt;file&gt;</em>
+</dt><dd>Append given servers to given file, instead of creating a new one.
+File should be be a tap file.
+</dd>
+
+<dt><strong>--appname</strong>, <strong>-n</strong> <em>&lt;name&gt;</em>
+</dt><dd>Use the specified name as the process name when the application is run with
+<em>twistd(1)</em>. This option also causes some initialization code to be
+duplicated when <em>twistd(1)</em> is run.
+</dd>
+
+<dt><strong>--type</strong>, <strong>-t</strong> <em>&lt;type&gt;</em>
+</dt><dd>Specify the output file type. Available types are:
+pickle - (default) Output as a python pickle file.
+source - Output as a .tas (AOT Python source) file.
+<em>apptype</em>
+Can be 'web', 'portforward', 'toc', 'coil', 'words', 'manhole', 'im', 'news', 'socks', 'telnet', 'parent', 'sibling', 'ftp', and 'mail'. Each of those support different options.
+</dd>
+
+</dl>
+
+</p>
+
+<h2><strong>portforward</strong> options<a name="auto3"/></h2>
+
+<dl><dt><strong>-h</strong>, <strong>--host</strong> <em>&lt;host&gt;</em>
+</dt><dd>Proxy connections to <em>&lt;host&gt;</em>
+</dd>
+
+<dt><strong>-d</strong>, <strong>--dest_port</strong> <em>&lt;port&gt;</em>
+</dt><dd>Proxy connections to <em>&lt;port&gt;</em> on remote host.
+</dd>
+
+<dt><strong>-p</strong>, <strong>--port</strong> <em>&lt;port&gt;</em>
+</dt><dd>Listen locally on <em>&lt;port&gt;</em>
+</dd>
+
+</dl>
+
+<h2><strong>web</strong> options<a name="auto4"/></h2>
+
+<dl><dt><strong>-u</strong>, <strong>--user</strong>
+</dt><dd>Makes a server with ~/public_html and
+~/.twistd-web-pb support for users.
+</dd>
+
+<dt><strong>--personal</strong>
+</dt><dd>Instead of generating a webserver, generate a
+ResourcePublisher which listens on ~/.twistd-web-pb
+</dd>
+
+<dt><strong>--path</strong> <em>&lt;path&gt;</em>
+</dt><dd>&lt;path&gt; is either a specific file or a directory to be
+set as the root of the web server. Use this if you
+have a directory full of HTML, cgi, php3, epy, or rpy files or
+any other files that you want to be served up raw.
+</dd>
+
+<dt><strong>-p</strong>, <strong>--port</strong> <em>&lt;port&gt;</em>
+</dt><dd>&lt;port&gt; is a number representing which port you want to
+start the server on.
+</dd>
+
+<dt><strong>-m</strong>, <strong>--mime_type</strong> <em>&lt;mimetype&gt;</em>
+</dt><dd>&lt;mimetype&gt; is the default MIME type to use for
+files in a --path web server when none can be determined
+for a particular extension. The default is 'text/html'.
+</dd>
+
+<dt><strong>--allow_ignore_ext</strong>
+</dt><dd>Specify whether or not a request for 'foo' should return 'foo.ext'.
+Default is off.
+</dd>
+
+<dt><strong>--ignore-ext</strong> <em>.&lt;extension&gt;</em>
+</dt><dd>Specify that a request for 'foo' should return 'foo.<em>&lt;extension&gt;</em>'.
+</dd>
+
+<dt><strong>-t</strong>, <strong>--telnet</strong> <em>&lt;port&gt;</em>
+</dt><dd>Run a telnet server on &lt;port&gt;, for additional
+configuration later.
+</dd>
+
+<dt><strong>-i</strong>, <strong>--index</strong> <em>&lt;name&gt;</em>
+</dt><dd>Use an index name other than <q>index.html</q>
+</dd>
+
+<dt><strong>--https</strong> <em>&lt;port&gt;</em>
+</dt><dd>Port to listen on for Secure HTTP.
+</dd>
+
+<dt><strong>-c</strong>, <strong>--certificate</strong> <em>&lt;filename&gt;</em>
+</dt><dd>SSL certificate to use for HTTPS. [default: server.pem]
+</dd>
+
+<dt><strong>-k</strong>, <strong>--privkey</strong> <em>&lt;filename&gt;</em>
+</dt><dd>SSL certificate to use for HTTPS. [default: server.pem]
+</dd>
+
+<dt><strong>--processor</strong> <em>&lt;ext&gt;=&lt;class name&gt;</em>
+</dt><dd>Adds a processor to those file names. (Only usable if after
+<strong>--path)</strong> </dd>
+
+<dt><strong>--resource-script</strong> <em>&lt;script name&gt;</em>
+</dt><dd>Sets the root as a resource script. This script will be re-evaluated on
+every request.
+</dd>
+
+</dl>
+
+<p>This creates a web.tap file that can be used by twistd. If you
+specify no arguments, it will be a demo webserver that has the Test
+class from twisted.web.test in it.
+</p>
+
+<h2><strong>toc</strong> options<a name="auto5"/></h2>
+
+<dl><dt><strong>-p</strong> <em>&lt;port&gt;</em>
+</dt><dd>&lt;port&gt; is a number representing which port you want to
+start the server on.
+</dd>
+
+</dl>
+
+<h2><strong>mail</strong> options<a name="auto6"/></h2>
+
+<dl><dt><strong>-r</strong>, <strong>--relay</strong> <em>&lt;ip&gt;,&lt;port&gt;=&lt;queue directory&gt;</em>
+</dt><dd>Relay mail to all unknown domains through given IP and port,
+using queue directory as temporary place to place files.
+</dd>
+
+<dt><strong>-d</strong>, <strong>--domain</strong> <em>&lt;domain&gt;</em>=<em>&lt;path&gt;</em>
+</dt><dd>generate an SMTP/POP3 virtual maildir domain named <q>domain</q> which saves to
+<q>path</q>
+</dd>
+
+<dt><strong>-u</strong>, <strong>--username</strong> <em>&lt;name&gt;</em>=<em>&lt;password&gt;</em>
+</dt><dd>add a user/password to the last specified domains
+</dd>
+
+<dt><strong>-b</strong>, <strong>--bounce_to_postmaster</strong>
+</dt><dd>undelivered mails are sent to the postmaster, instead of being rejected.
+</dd>
+
+<dt><strong>-p</strong>, <strong>--pop</strong> <em>&lt;port&gt;</em>
+</dt><dd>&lt;port&gt; is a number representing which port you want to
+start the pop3 server on.
+</dd>
+
+<dt><strong>-s</strong>, <strong>--smtp</strong> <em>&lt;port&gt;</em>
+</dt><dd>&lt;port&gt; is a number representing which port you want to
+start the smtp server on.
+</dd>
+
+</dl>
+
+<p>This creates a mail.tap file that can be used by twistd(1)
+</p>
+
+<h2><strong>telnet</strong> options<a name="auto7"/></h2>
+
+<dl><dt><strong>-p</strong>, <strong>--port</strong> <em>&lt;port&gt;</em>
+</dt><dd>Run the telnet server on &lt;port&gt;
+</dd>
+
+<dt><strong>-u</strong>, <strong>--username</strong> <em>&lt;name&gt;</em>
+</dt><dd>set the username to &lt;name&gt;
+</dd>
+
+<dt><strong>-w</strong>, <strong>--password</strong> <em>&lt;password&gt;</em>
+</dt><dd>set the password to &lt;password&gt;
+</dd>
+
+</dl>
+
+<h2><strong>socks</strong> options<a name="auto8"/></h2>
+
+<dl><dt><strong>-i</strong>, <strong>--interface</strong> <em>&lt;interface&gt;</em>
+</dt><dd>Listen on interface &lt;interface&gt;
+</dd>
+
+<dt><strong>-p</strong>, <strong>--port</strong> <em>&lt;port&gt;</em>
+</dt><dd>Run the SOCKSv4 server on &lt;port&gt;
+</dd>
+
+<dt><strong>-l</strong>, <strong>--log</strong> <em>&lt;filename&gt;</em>
+</dt><dd>log connection data to &lt;filename&gt;
+</dd>
+
+</dl>
+
+<h2><strong>ftp</strong> options<a name="auto9"/></h2>
+
+<dl><dt><strong>-a</strong>, <strong>--anonymous</strong>
+</dt><dd>Allow anonymous logins
+</dd>
+
+<dt><strong>-3</strong>, <strong>--thirdparty</strong>
+</dt><dd>Allow third party connections
+</dd>
+
+<dt><strong>--otp</strong>
+</dt><dd>Use one time passwords (OTP)
+</dd>
+
+<dt><strong>-p</strong>, <strong>--port</strong> <em>&lt;port&gt;</em>
+</dt><dd>Run the FTP server on &lt;port&gt;
+</dd>
+
+<dt><strong>-r</strong>, <strong>--root</strong> <em>&lt;path&gt;</em>
+</dt><dd>Define the local root of the FTP server
+</dd>
+
+<dt><strong>--anonymoususer</strong> <em>&lt;username&gt;</em>
+</dt><dd>Define the the name of the anonymous user
+</dd>
+
+</dl>
+
+<h2><strong>manhole</strong> options<a name="auto10"/></h2>
+
+<dl><dt><strong>-p</strong>, <strong>--port</strong> <em>&lt;port&gt;</em>
+</dt><dd>Run the manhole server on &lt;port&gt;
+</dd>
+
+<dt><strong>-u</strong>, <strong>--user</strong> <em>&lt;name&gt;</em>
+</dt><dd>set the username to &lt;name&gt;
+</dd>
+
+<dt><strong>-w</strong>, <strong>--password</strong> <em>&lt;password&gt;</em>
+</dt><dd>set the password to &lt;password&gt;
+</dd>
+
+</dl>
+
+<h2><strong>words</strong> options<a name="auto11"/></h2>
+
+<dl><dt><strong>-p</strong>, <strong>--port</strong> <em>&lt;port&gt;</em>
+</dt><dd>Run the Words server on &lt;port&gt;
+</dd>
+
+<dt><strong>-i</strong>, <strong>--irc</strong> <em>&lt;port&gt;</em>
+</dt><dd>Run IRC server on port &lt;port&gt;
+</dd>
+
+<dt><strong>-w</strong>, <strong>--web</strong> <em>&lt;port&gt;</em>
+</dt><dd>Run web server on port &lt;port&gt;
+</dd>
+
+</dl>
+
+<h2>AUTHOR<a name="auto12"/></h2>
+
+<p>Written by Moshe Zadka, based on mktap's help messages
+</p>
+
+<h2>REPORTING BUGS<a name="auto13"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto14"/></h2>
+
+<p>Copyright © 2000-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+<h2>SEE ALSO<a name="auto15"/></h2>
+
+<p>twistd(1)
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/man/mktap.1 b/vendor/Twisted-10.0.0/doc/core/man/mktap.1
new file mode 100644
index 0000000000..70f79b6f51
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/mktap.1
@@ -0,0 +1,219 @@
+.TH MKTAP "1" "July 2001" "" ""
+.SH NAME
+mktap \- create twisted.servers
+.SH SYNOPSIS
+.B mktap
+[\fIoptions\fR] \fIapptype\fR [\fIapplication_option\fR]...
+.PP
+.B mktap
+\fIapptype\fR --help
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB\--debug\fR, \fB\-d\fR
+Show debug information for plugin loading.
+.TP
+\fB\--progress\fR, \fB\-p\fR
+Show progress information for plugin loading.
+.TP
+\fB\--encrypted\fR, \fB\-e\fR
+Encrypt file before writing (will make the extension of the resultant file begin with 'e').
+.TP
+\fB\--uid\fR, \fB\-u\fR \fI<uid>\fR
+Application belongs to this uid, and should run with its permissions.
+.TP
+\fB\--gid\fR, \fB\-d\fR \fI<gid>\fR
+Application belongs to this gid, and should run with its permissions.
+.TP
+\fB\--append\fR, \fB\-a\fR \fI<file>\fR
+Append given servers to given file, instead of creating a new one.
+File should be be a tap file.
+.TP
+\fB\--appname\fR, \fB\-n\fR \fI<name>\fR
+Use the specified name as the process name when the application is run with
+\fItwistd(1)\fR. This option also causes some initialization code to be
+duplicated when \fItwistd(1)\fR is run.
+.TP
+\fB\--type\fR, \fB\-t\fR \fI<type>\fR
+Specify the output file type. Available types are:
+.IP
+pickle - (default) Output as a python pickle file.
+.br
+source - Output as a .tas (AOT Python source) file.
+.P
+\fIapptype\fR
+Can be 'web', 'portforward', 'toc', 'coil', 'words', \
+'manhole', 'im', 'news', 'socks', 'telnet', 'parent', 'sibling', \
+'ftp', and 'mail'. Each of those support different options.
+.PP
+.SH \fBportforward\fR options
+.TP
+\fB\-h\fR, \fB\--host\fR \fI<host>\fR
+Proxy connections to \fI<host>\fR
+.TP
+\fB\-d\fR, \fB\--dest_port\fR \fI<port>\fR
+Proxy connections to \fI<port>\fR on remote host.
+.TP
+\fB\-p\fR, \fB\--port\fR \fI<port>\fR
+Listen locally on \fI<port>\fR
+.PP
+.SH \fBweb\fR options
+.TP
+\fB\-u\fR, \fB\--user\fR
+Makes a server with ~/public_html and
+~/.twistd-web-pb support for users.
+.TP
+\fB\--personal\fR
+Instead of generating a webserver, generate a
+ResourcePublisher which listens on ~/.twistd-web-pb
+.TP
+\fB\--path\fR \fI<path>\fR
+<path> is either a specific file or a directory to be
+set as the root of the web server. Use this if you
+have a directory full of HTML, cgi, php3, epy, or rpy files or
+any other files that you want to be served up raw.
+.TP
+\fB\-p\fR, \fB\--port\fR \fI<port>\fR
+<port> is a number representing which port you want to
+start the server on.
+.TP
+\fB\-m\fR, \fB\--mime_type\fR \fI<mimetype>\fR
+<mimetype> is the default MIME type to use for
+files in a --path web server when none can be determined
+for a particular extension. The default is 'text/html'.
+.TP
+\fB\--allow_ignore_ext\fR
+Specify whether or not a request for 'foo' should return 'foo.ext'.
+Default is off.
+.TP
+\fB\--ignore-ext\fR \fI.<extension>\fR
+Specify that a request for 'foo' should return 'foo.\fI<extension>\fR'.
+.TP
+\fB\-t\fR, \fB\--telnet\fR \fI<port>\fR
+Run a telnet server on <port>, for additional
+configuration later.
+.TP
+\fB\-i\fR, \fB\--index\fR \fI<name>\fR
+Use an index name other than "index.html"
+.TP
+\fB--https\fR \fI<port>\fR
+Port to listen on for Secure HTTP.
+.TP
+\fB-c\fR, \fB--certificate\fR \fI<filename>\fR
+SSL certificate to use for HTTPS. [default: server.pem]
+.TP
+\fB-k\fR, \fB--privkey\fR \fI<filename>\fR
+SSL certificate to use for HTTPS. [default: server.pem]
+.TP
+\fB--processor\fR \fI<ext>=<class name>\fR
+Adds a processor to those file names. (Only usable if after
+.B --path)
+.TP
+\fB--resource-script\fR \fI<script name>\fR
+Sets the root as a resource script. This script will be re-evaluated on
+every request.
+.PP
+This creates a web.tap file that can be used by twistd. If you
+specify no arguments, it will be a demo webserver that has the Test
+class from twisted.web.test in it.
+.SH \fBtoc\fR options
+.TP
+\fB\-p\fR \fI<port>\fR
+<port> is a number representing which port you want to
+start the server on.
+.SH \fBmail\fR options
+.TP
+\fB\-r\fR, \fB\--relay\fR \fI<ip>,<port>=<queue directory>\fR
+Relay mail to all unknown domains through given IP and port,
+using queue directory as temporary place to place files.
+.TP
+\fB\-d\fR, \fB\--domain\fR \fI<domain>\fR=\fI<path>\fR
+generate an SMTP/POP3 virtual maildir domain named "domain" which saves to
+"path"
+.TP
+\fB\-u\fR, \fB\--username\fR \fI<name>\fR=\fI<password>\fR
+add a user/password to the last specified domains
+.TP
+\fB\-b\fR, \fB\--bounce_to_postmaster\fR
+undelivered mails are sent to the postmaster, instead of being rejected.
+.TP
+\fB\-p\fR, \fB\--pop\fR \fI<port>\fR
+<port> is a number representing which port you want to
+start the pop3 server on.
+.TP
+\fB\-s\fR, \fB\--smtp\fR \fI<port>\fR
+<port> is a number representing which port you want to
+start the smtp server on.
+.PP
+This creates a mail.tap file that can be used by twistd(1)
+.SH \fBtelnet\fR options
+.TP
+\fB\-p\fR, \fB\--port\fR \fI<port>\fR
+Run the telnet server on <port>
+.TP
+\fB\-u\fR, \fB\--username\fR \fI<name>\fR
+set the username to <name>
+.TP
+\fB\-w\fR, \fB\--password\fR \fI<password>\fR
+set the password to <password>
+.SH \fBsocks\fR options
+.TP
+\fB\-i\fR, \fB\--interface\fR \fI<interface>\fR
+Listen on interface <interface>
+.TP
+\fB\-p\fR, \fB\--port\fR \fI<port>\fR
+Run the SOCKSv4 server on <port>
+.TP
+\fB\-l\fR, \fB\--log\fR \fI<filename>\fR
+log connection data to <filename>
+.SH \fBftp\fR options
+.TP
+\fB\-a\fR, \fB\--anonymous\fR
+Allow anonymous logins
+.TP
+\fB\-3\fR, \fB\--thirdparty\fR
+Allow third party connections
+.TP
+\fB\--otp\fR
+Use one time passwords (OTP)
+.TP
+\fB\-p\fR, \fB\--port\fR \fI<port>\fR
+Run the FTP server on <port>
+.TP
+\fB\-r\fR, \fB\--root\fR \fI<path>\fR
+Define the local root of the FTP server
+.TP
+\fB\--anonymoususer\fR \fI<username>\fR
+Define the the name of the anonymous user
+.SH \fBmanhole\fR options
+.TP
+\fB\-p\fR, \fB\--port\fR \fI<port>\fR
+Run the manhole server on <port>
+.TP
+\fB\-u\fR, \fB\--user\fR \fI<name>\fR
+set the username to <name>
+.TP
+\fB\-w\fR, \fB\--password\fR \fI<password>\fR
+set the password to <password>
+.SH \fBwords\fR options
+.TP
+\fB\-p\fR, \fB\--port\fR \fI<port>\fR
+Run the Words server on <port>
+.TP
+\fB\-i\fR, \fB\--irc\fR \fI<port>\fR
+Run IRC server on port <port>
+.TP
+\fB\-w\fR, \fB\--web\fR \fI<port>\fR
+Run web server on port <port>
+.SH AUTHOR
+Written by Moshe Zadka, based on mktap's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+twistd(1)
diff --git a/vendor/Twisted-10.0.0/doc/core/man/pyhtmlizer-man.html b/vendor/Twisted-10.0.0/doc/core/man/pyhtmlizer-man.html
new file mode 100644
index 0000000000..8e4e89b2aa
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/pyhtmlizer-man.html
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: pyhtmlizer.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">pyhtmlizer.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNTAX</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">OPTIONS</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>pyhtmlizer - pretty-print Python source as HTML
+
+</p>
+
+<h2>SYNTAX<a name="auto1"/></h2>
+
+<p>pyhtmlizer [<em>-s|--stylesheet</em> &lt;<em>url</em>&gt;] &lt;<em>filename</em>&gt;
+</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>This generates a HTML document with Python source marked up with span elements. To colorize, provide a stylesheet.
+</p>
+
+<h2>OPTIONS<a name="auto3"/></h2>
+
+<dl><dt><strong>--stylesheet, -s</strong> &lt;<em>url</em>&gt;
+</dt><dd>Links to the stylesheet at &lt;<em>url</em>&gt;.
+</dd>
+
+<dt><strong>--help</strong>
+</dt><dd>Output help information and exit.
+</dd>
+
+<dt><strong>-v</strong>, <strong>--version</strong>
+</dt><dd>Output version information and exit.
+</dd>
+
+</dl>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/man/pyhtmlizer.1 b/vendor/Twisted-10.0.0/doc/core/man/pyhtmlizer.1
new file mode 100644
index 0000000000..9621e6098a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/pyhtmlizer.1
@@ -0,0 +1,22 @@
+.TH "pyhtmlizer" "1" "" "Twisted Matrix Laboratories" ""
+.SH "NAME"
+.LP
+pyhtmlizer \- pretty\-print Python source as HTML
+
+.SH "SYNTAX"
+.LP
+pyhtmlizer [\fI\-s|\-\-stylesheet\fR <\fIurl\fR>] <\fIfilename\fR>
+.SH "DESCRIPTION"
+.LP
+This generates a HTML document with Python source marked up with span elements. To colorize, provide a stylesheet.
+.SH "OPTIONS"
+.LP
+.TP
+\fB\-\-stylesheet, \-s\fR <\fIurl\fR>
+Links to the stylesheet at <\fIurl\fR>.
+.TP
+\fB\-\-help\fR
+Output help information and exit.
+.TP
+\fB\-v\fR, \fB\--version\fR
+Output version information and exit.
diff --git a/vendor/Twisted-10.0.0/doc/core/man/tap2deb-man.html b/vendor/Twisted-10.0.0/doc/core/man/tap2deb-man.html
new file mode 100644
index 0000000000..df5737fa83
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/tap2deb-man.html
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: TAP2DEB.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">TAP2DEB.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li><li><a href="#auto6">SEE ALSO</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>tap2deb - create Debian packages which wrap .tap files
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>tap2deb</strong> [options]
+</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>Create a ready to upload Debian package in <q>.build</q>
+<dl><dt><strong>-u</strong>, <strong>--unsigned</strong>
+</dt><dd>do not sign the Debian package
+</dd>
+
+<dt><strong>-t</strong>, <strong>--tapfile</strong> <em>&lt;tapfile&gt;</em>
+</dt><dd>Build the application around the given .tap (default twistd.tap)
+</dd>
+
+<dt><strong>-y</strong>, <strong>--type</strong> <em>&lt;type&gt;</em>
+</dt><dd>The configuration has the given type . Allowable types are
+<strong>tap</strong>, <strong>source</strong>, <strong>xml</strong> and <strong>python</strong>.
+The first three types are <strong>mktap(1)</strong> output formats,
+while the last one is a manual building of application
+(see <strong>twistd(1)</strong>, the <strong>-y</strong> option).
+</dd>
+
+<dt><strong>-p</strong>, <strong>--protocol</strong> <em>&lt;protocol&gt;</em>
+</dt><dd>The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+</dd>
+
+<dt><strong>-d</strong>, <strong>--debfile</strong> <em>&lt;debfile&gt;</em>
+</dt><dd>The name of the debian package. Default is 'twisted-'+protocol.
+</dd>
+
+<dt><strong>-V</strong>, <strong>--set-version</strong> <em>&lt;version&gt;</em>
+</dt><dd>The version of the Debian package. The default is 1.0
+</dd>
+
+<dt><strong>-e</strong>, <strong>--description</strong> <em>&lt;description&gt;</em>
+</dt><dd>The one-line description. Default is uninteresting.
+</dd>
+
+<dt><strong>-l</strong>, <strong>--long_description</strong> <em>&lt;long_description&gt;</em>
+</dt><dd>A multi-line description. Default is explanation about
+this being an automatic package created from tap2deb.
+</dd>
+
+<dt><strong>-m</strong>, <strong>--maintainer</strong> <em>&lt;maintainer&gt;</em>
+</dt><dd>The maintainer, as <q>Name Lastname &lt;email address&gt;</q>. This will
+go in the meta-files, as well as be used as the id to sign the package.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Output version information and exit.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Moshe Zadka, based on twistd's help messages
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2000-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+<h2>SEE ALSO<a name="auto6"/></h2>
+
+<p>mktap(1)
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/man/tap2deb.1 b/vendor/Twisted-10.0.0/doc/core/man/tap2deb.1
new file mode 100644
index 0000000000..566f70aeb8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/tap2deb.1
@@ -0,0 +1,57 @@
+.TH TAP2DEB "1" "July 2001" "" ""
+.SH NAME
+tap2deb \- create Debian packages which wrap .tap files
+.SH SYNOPSIS
+.B tap2deb
+[options]
+.SH DESCRIPTION
+Create a ready to upload Debian package in ".build"
+.TP
+\fB\-u\fR, \fB\--unsigned\fR
+do not sign the Debian package
+.TP
+\fB\-t\fR, \fB\--tapfile\fR \fI<tapfile>\fR
+Build the application around the given .tap (default twistd.tap)
+.TP
+\fB\-y\fR, \fB\--type\fR \fI<type>\fR
+The configuration has the given type . Allowable types are
+\fBtap\fR, \fBsource\fR, \fBxml\fR and \fBpython\fR.
+The first three types are \fBmktap(1)\fR output formats,
+while the last one is a manual building of application
+(see \fBtwistd(1)\fR, the \fB\-y\fR option).
+.TP
+\fB\-p\fR, \fB\--protocol\fR \fI<protocol>\fR
+The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+.TP
+\fB\-d\fR, \fB\--debfile\fR \fI<debfile>\fR
+The name of the debian package. Default is 'twisted-'+protocol.
+.TP
+\fB\-V\fR, \fB\--set-version\fR \fI<version>\fR
+The version of the Debian package. The default is 1.0
+.TP
+\fB\-e\fR, \fB\--description\fR \fI<description>\fR
+The one-line description. Default is uninteresting.
+.TP
+\fB\-l\fR, \fB\--long_description\fR \fI<long_description>\fR
+A multi-line description. Default is explanation about
+this being an automatic package created from tap2deb.
+.TP
+\fB\-m\fR, \fB\--maintainer\fR \fI<maintainer>\fR
+The maintainer, as "Name Lastname <email address>". This will
+go in the meta-files, as well as be used as the id to sign the package.
+.TP
+\fB\--version\fR
+Output version information and exit.
+.SH AUTHOR
+Written by Moshe Zadka, based on twistd's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+mktap(1)
diff --git a/vendor/Twisted-10.0.0/doc/core/man/tap2rpm-man.html b/vendor/Twisted-10.0.0/doc/core/man/tap2rpm-man.html
new file mode 100644
index 0000000000..5f8d57a731
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/tap2rpm-man.html
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: TAP2RPM.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">TAP2RPM.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li><li><a href="#auto6">SEE ALSO</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>tap2rpm - create RPM packages which wrap .tap files
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>tap2rpm</strong> [options]
+</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>Create a set of RPM/SRPM packages in the current directory
+<dl><dt><strong>-u</strong>, <strong>--unsigned</strong>
+</dt><dd>do not sign the RPM package
+</dd>
+
+<dt><strong>-t</strong>, <strong>--tapfile</strong> <em>&lt;tapfile&gt;</em>
+</dt><dd>Build the application around the given .tap (default twistd.tap)
+</dd>
+
+<dt><strong>-y</strong>, <strong>--type</strong> <em>&lt;type&gt;</em>
+</dt><dd>The configuration has the given type . Allowable types are
+<strong>tap</strong>, <strong>source</strong>, <strong>xml</strong> and <strong>python</strong>.
+The first three types are <strong>mktap(1)</strong> output formats,
+while the last one is a manual building of application
+(see <strong>twistd(1)</strong>, the <strong>-y</strong> option).
+</dd>
+
+<dt><strong>-p</strong>, <strong>--protocol</strong> <em>&lt;protocol&gt;</em>
+</dt><dd>The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+</dd>
+
+<dt><strong>-d</strong>, <strong>--rpmfile</strong> <em>&lt;rpmfile&gt;</em>
+</dt><dd>The name of the RPM package. Default is 'twisted-'+protocol.
+</dd>
+
+<dt><strong>-V</strong>, <strong>--set-version</strong> <em>&lt;version&gt;</em>
+</dt><dd>The version of the RPM package. The default is 1.0
+</dd>
+
+<dt><strong>-e</strong>, <strong>--description</strong> <em>&lt;description&gt;</em>
+</dt><dd>The one-line description. Default is uninteresting.
+</dd>
+
+<dt><strong>-l</strong>, <strong>--long_description</strong> <em>&lt;long_description&gt;</em>
+</dt><dd>A multi-line description. Default is explanation about
+this being an automatic package created from tap2rpm.
+</dd>
+
+<dt><strong>-m</strong>, <strong>--maintainer</strong> <em>&lt;maintainer&gt;</em>
+</dt><dd>The maintainer, as <q>Name Lastname &lt;email address&gt;</q>. This will
+go in the meta-files, as well as be used as the id to sign the package.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Output version information and exit.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>tap2rpm was written by Sean Reifschneider based on tap2deb by Moshe Zadka.
+This man page is heavily based on the tap2deb man page by Moshe Zadka.
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2000-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+<h2>SEE ALSO<a name="auto6"/></h2>
+
+<p>mktap(1)
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/man/tap2rpm.1 b/vendor/Twisted-10.0.0/doc/core/man/tap2rpm.1
new file mode 100644
index 0000000000..a406c45884
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/tap2rpm.1
@@ -0,0 +1,58 @@
+.TH TAP2RPM "1" "July 2001" "" ""
+.SH NAME
+tap2rpm \- create RPM packages which wrap .tap files
+.SH SYNOPSIS
+.B tap2rpm
+[options]
+.SH DESCRIPTION
+Create a set of RPM/SRPM packages in the current directory
+.TP
+\fB\-u\fR, \fB\--unsigned\fR
+do not sign the RPM package
+.TP
+\fB\-t\fR, \fB\--tapfile\fR \fI<tapfile>\fR
+Build the application around the given .tap (default twistd.tap)
+.TP
+\fB\-y\fR, \fB\--type\fR \fI<type>\fR
+The configuration has the given type . Allowable types are
+\fBtap\fR, \fBsource\fR, \fBxml\fR and \fBpython\fR.
+The first three types are \fBmktap(1)\fR output formats,
+while the last one is a manual building of application
+(see \fBtwistd(1)\fR, the \fB\-y\fR option).
+.TP
+\fB\-p\fR, \fB\--protocol\fR \fI<protocol>\fR
+The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+.TP
+\fB\-d\fR, \fB\--rpmfile\fR \fI<rpmfile>\fR
+The name of the RPM package. Default is 'twisted-'+protocol.
+.TP
+\fB\-V\fR, \fB\--set-version\fR \fI<version>\fR
+The version of the RPM package. The default is 1.0
+.TP
+\fB\-e\fR, \fB\--description\fR \fI<description>\fR
+The one-line description. Default is uninteresting.
+.TP
+\fB\-l\fR, \fB\--long_description\fR \fI<long_description>\fR
+A multi-line description. Default is explanation about
+this being an automatic package created from tap2rpm.
+.TP
+\fB\-m\fR, \fB\--maintainer\fR \fI<maintainer>\fR
+The maintainer, as "Name Lastname <email address>". This will
+go in the meta-files, as well as be used as the id to sign the package.
+.TP
+\fB\--version\fR
+Output version information and exit.
+.SH AUTHOR
+tap2rpm was written by Sean Reifschneider based on tap2deb by Moshe Zadka.
+This man page is heavily based on the tap2deb man page by Moshe Zadka.
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+mktap(1)
diff --git a/vendor/Twisted-10.0.0/doc/core/man/tapconvert-man.html b/vendor/Twisted-10.0.0/doc/core/man/tapconvert-man.html
new file mode 100644
index 0000000000..c30bf40268
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/tapconvert-man.html
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: TAPCONVERT.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">TAPCONVERT.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>tapconvert - convert Twisted configurations from one format to another
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>tapconvert</strong> -i <em>input</em> -o <em>output</em> [-f <em>input-type</em>] [-t <em>output-type</em>] [-d] [-e]</p>
+
+<p><strong>tapconvert</strong> --help</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>The <strong>--help</strong> prints out a usage message to standard output.
+<dl><dt><strong>--in</strong>, <strong>-i</strong> <em>&lt;input file&gt;</em>
+</dt><dd>The name of the input configuration.
+</dd>
+
+<dt><strong>--out</strong>, <strong>-o</strong> <em>&lt;output file&gt;</em>
+</dt><dd>The name of the output configuration.
+</dd>
+
+<dt><strong>--typein</strong>, <strong>-f</strong> <em>&lt;input type&gt;</em>
+</dt><dd>The type of the input file. Can be either 'guess', 'python', 'pickle', 'xml', or 'source'. Default is 'guess'.
+</dd>
+
+<dt><strong>--typeout</strong>, <strong>-t</strong> <em>&lt;output type&gt;</em>
+</dt><dd>The type of the output file. Can be either 'pickle', 'xml', or 'source'. Default is 'source'.
+</dd>
+
+<dt><strong>--decrypt</strong>, <strong>-d</strong>
+</dt><dd>Decrypt the specified tap/aos/xml input file.
+</dd>
+
+<dt><strong>--encrypt</strong>, <strong>-e</strong>
+</dt><dd>Encrypt output file before writing.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Output version information and exit.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Moshe Zadka, based on tapconvert's help messages
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2000-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/man/tapconvert.1 b/vendor/Twisted-10.0.0/doc/core/man/tapconvert.1
new file mode 100644
index 0000000000..0e1372a223
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/tapconvert.1
@@ -0,0 +1,40 @@
+.TH TAPCONVERT "1" "July 2001" "" ""
+.SH NAME
+tapconvert \- convert Twisted configurations from one format to another
+.SH SYNOPSIS
+.B tapconvert -i \fIinput\fR -o \fIoutput\fR [-f \fIinput-type\fR] [-t \fIoutput-type\fR] [-d] [-e]
+.PP
+.B tapconvert --help
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB\--in\fR, \fB\-i\fR \fI<input file>\fR
+The name of the input configuration.
+.TP
+\fB\--out\fR, \fB\-o\fR \fI<output file>\fR
+The name of the output configuration.
+.TP
+\fB\--typein\fR, \fB\-f\fR \fI<input type>\fR
+The type of the input file. Can be either 'guess', 'python', 'pickle', 'xml', or 'source'. Default is 'guess'.
+.TP
+\fB\--typeout\fR, \fB\-t\fR \fI<output type>\fR
+The type of the output file. Can be either 'pickle', 'xml', or 'source'. Default is 'source'.
+.TP
+\fB\--decrypt\fR, \fB\-d\fR
+Decrypt the specified tap/aos/xml input file.
+.TP
+\fB\--encrypt\fR, \fB\-e\fR
+Encrypt output file before writing.
+.TP
+\fB\--version\fR
+Output version information and exit.
+.SH AUTHOR
+Written by Moshe Zadka, based on tapconvert's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/Twisted-10.0.0/doc/core/man/trial-man.html b/vendor/Twisted-10.0.0/doc/core/man/trial-man.html
new file mode 100644
index 0000000000..325f713730
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/trial-man.html
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: TRIAL.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">TRIAL.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>trial - run unit tests
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>trial</strong> [options] [[file|package|module|TestCase|testmethod]...]</p>
+
+<p><strong>trial</strong> --help</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>trial loads and executes a suite of unit tests, obtained from modules,
+packages and files listed on the command line.
+</p>
+
+<p>trial will take either filenames or fully qualified Python names as
+arguments. Thus 'trial myproject/foo.py', 'trial myproject.foo' and
+ 'trial myproject.foo.SomeTestCase.test_method' are all valid ways to
+invoke trial.
+<dl><dt><strong>-b</strong>, <strong>--debug</strong>
+</dt><dd>Run the tests in the Python debugger. Also does post-mortem
+debugging on exceptions. Will load '.pdbrc' from current directory if
+it exists.
+</dd>
+
+<dt><strong>-B</strong>, <strong>--debug-stacktraces</strong>
+</dt><dd>Report Deferred creation and callback stack traces
+</dd>
+
+<dt><strong>--coverage</strong>
+</dt><dd>Generate coverage information in _trial_temp/coverage/. Requires Python 2.3
+or higher.
+</dd>
+
+<dt><strong>--disablegc</strong>
+</dt><dd>Disable the garbage collector. I don't know why this is in trial.
+</dd>
+
+<dt><strong>-x</strong>, <strong>--extra</strong>
+</dt><dd>Add an extra argument. (This is a hack necessary for interfacing with emacs's
+`gud'.)
+</dd>
+
+<dt><strong>-e</strong>, <strong>--rterrors</strong>
+</dt><dd>Print tracebacks to standard output as soon as they occur
+</dd>
+
+<dt><strong>--force-gc</strong>
+</dt><dd>Run gc.collect() before and after each test case. This can be used to
+isolate errors that occur when objects get collected. This option would be
+the default, except it makes tests run about ten times slower.
+</dd>
+
+<dt><strong>-h</strong>, <strong>--help</strong>
+</dt><dd>Print a usage message to standard output, then exit.
+</dd>
+
+<dt><strong>--help-reporters</strong>
+</dt><dd>Print a list of valid reporters to standard output, then exit.
+</dd>
+
+<dt><strong>--help-reactors</strong>
+</dt><dd>List the names of possibly available reactors.
+</dd>
+
+<dt><strong>-l</strong>, <strong>--logfile</strong> &lt;logfile&gt;
+</dt><dd>Direct the log to a different file. The default file is 'test.log'.
+&lt;logfile&gt; is relative to _trial_temp.
+</dd>
+
+<dt><strong>-n</strong>, <strong>--dry-run</strong>
+</dt><dd>Go through all the tests and make them pass without running.
+</dd>
+
+<dt><strong>-N</strong>, <strong>--no-recurse</strong>
+</dt><dd>By default, trial recurses through packages to find every module inside
+every subpackage. Unless, that is, you specify this option.
+</dd>
+
+<dt><strong>--nopm</strong>
+</dt><dd>Don't automatically jump into debugger for post-mortem analysis of
+exceptions. Only usable in conjunction with --debug.
+</dd>
+
+<dt><strong>--profile</strong>
+</dt><dd>Run tests under the Python profiler.
+</dd>
+
+<dt><strong>-r</strong>, <strong>--reactor</strong> <em>&lt;reactor&gt;</em>
+</dt><dd>Choose which reactor to use. See --help-reactors for a list.
+</dd>
+
+<dt><strong>--recursionlimit</strong>
+</dt><dd>Set Python's recursion limit. See sys.setrecursionlimit()
+</dd>
+
+<dt><strong>--reporter</strong>
+</dt><dd>Select the reporter to use for Trial's output. Use the --help-reporters
+option to see a list of valid reporters.
+</dd>
+
+<dt><strong>--spew</strong>
+</dt><dd>Print an insanely verbose log of everything that happens. Useful when
+debugging freezes or locks in complex code.
+</dd>
+
+<dt><strong>--tbformat</strong> &lt;format&gt;
+</dt><dd>Format to display tracebacks with. Acceptable values are 'default', 'brief'
+and 'verbose'. 'brief' produces tracebacks that play nicely with Emacs' GUD.
+</dd>
+
+<dt><strong>--temp-directory &lt;directory&gt;</strong>
+</dt><dd>WARNING: Do not use this options unless you know what you are doing.
+By default, trial creates a directory called _trial_temp under the current
+working directory. When trial runs, it first <em>deletes</em> this directory,
+then creates it, then changes into the directory to run the tests. The log
+file and any coverage files are stored here. Use this option if you wish to
+have trial run in a directory other than _trial_temp. Be warned, trial
+will <em>delete</em> the directory before re-creating it.
+</dd>
+
+<dt><strong>--testmodule &lt;filename&gt;</strong>
+</dt><dd>Ask trial to look into &lt;filename&gt; and run any tests specified using the
+Emacs-style buffer variable 'test-case-name'.
+</dd>
+
+<dt><strong>--unclean-warnings</strong>
+</dt><dd>As of Twisted 8.0, trial will report an error if the reactor is left unclean
+at the end of the test. This option is provided to assist in migrating from
+Twisted 2.5 to Twisted 8.0 and later. Enabling this option will turn the errors
+into warnings.
+</dd>
+
+<dt><strong>-u</strong>, <strong>--until-failure</strong>
+</dt><dd>Keep looping the tests until one of them raises an error or a failure.
+This is particularly useful for reproducing intermittent failures.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Prints the Twisted version number and exit.
+</dd>
+
+<dt><strong>--without-module &lt;modulenames&gt;</strong>
+</dt><dd>Simulate the lack of the specified comma-separated list of modules. This makes
+it look like the modules are not present in the system, causing tests to check
+the behavior for that configuration.
+</dd>
+
+<dt><strong>-z</strong>, <strong>--random [&lt;seed&gt;]</strong>
+</dt><dd>Run the tests in random order using the specified seed.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Jonathan M. Lange
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2003-2008 Twisted Matrix Laboratories
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/man/trial.1 b/vendor/Twisted-10.0.0/doc/core/man/trial.1
new file mode 100644
index 0000000000..7c53587008
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/trial.1
@@ -0,0 +1,132 @@
+.TH TRIAL "1" "Oct 2007" "" ""
+.SH NAME
+trial \- run unit tests
+.SH SYNOPSIS
+.B trial [options] [[file|package|module|TestCase|testmethod]...]
+.PP
+.B trial --help
+.SH DESCRIPTION
+.PP
+trial loads and executes a suite of unit tests, obtained from modules,
+packages and files listed on the command line.
+.PP
+trial will take either filenames or fully qualified Python names as
+arguments. Thus 'trial myproject/foo.py', 'trial myproject.foo' and
+ 'trial myproject.foo.SomeTestCase.test_method' are all valid ways to
+invoke trial.
+.TP
+\fB-b\fR, \fB--debug\fR
+Run the tests in the Python debugger. Also does post-mortem
+debugging on exceptions. Will load '.pdbrc' from current directory if
+it exists.
+.TP
+\fB-B\fR, \fB--debug-stacktraces\fR
+Report Deferred creation and callback stack traces
+.TP
+\fB--coverage\fR
+Generate coverage information in _trial_temp/coverage/. Requires Python 2.3
+or higher.
+.TP
+\fB--disablegc\fR
+Disable the garbage collector. I don't know why this is in trial.
+.TP
+\fB-x\fR, \fB--extra\fR
+Add an extra argument. (This is a hack necessary for interfacing with emacs's
+`gud'.)
+.TP
+\fB-e\fR, \fB--rterrors\fR
+Print tracebacks to standard output as soon as they occur
+.TP
+\fB--force-gc\fR
+Run gc.collect() before and after each test case. This can be used to
+isolate errors that occur when objects get collected. This option would be
+the default, except it makes tests run about ten times slower.
+.TP
+\fB-h\fR, \fB--help\fR
+Print a usage message to standard output, then exit.
+.TP
+\fB--help-reporters\fR
+Print a list of valid reporters to standard output, then exit.
+.TP
+\fB--help-reactors\fR
+List the names of possibly available reactors.
+.TP
+\fB-l\fR, \fB--logfile\fR <logfile>
+Direct the log to a different file. The default file is 'test.log'.
+<logfile> is relative to _trial_temp.
+.TP
+\fB-n\fR, \fB--dry-run\fR
+Go through all the tests and make them pass without running.
+.TP
+\fB-N\fR, \fB--no-recurse\fR
+By default, trial recurses through packages to find every module inside
+every subpackage. Unless, that is, you specify this option.
+.TP
+\fB--nopm\fR
+Don't automatically jump into debugger for post-mortem analysis of
+exceptions. Only usable in conjunction with --debug.
+.TP
+\fB--profile\fR
+Run tests under the Python profiler.
+.TP
+\fB\-r\fR, \fB\--reactor\fR \fI<reactor>\fR
+Choose which reactor to use. See --help-reactors for a list.
+.TP
+\fB--recursionlimit\fR
+Set Python's recursion limit. See sys.setrecursionlimit()
+.TP
+\fB--reporter\fR
+Select the reporter to use for Trial's output. Use the --help-reporters
+option to see a list of valid reporters.
+.TP
+\fB--spew\fR
+Print an insanely verbose log of everything that happens. Useful when
+debugging freezes or locks in complex code.
+.TP
+\fB--tbformat\fR <format>
+Format to display tracebacks with. Acceptable values are 'default', 'brief'
+and 'verbose'. 'brief' produces tracebacks that play nicely with Emacs' GUD.
+.TP
+\fB--temp-directory <directory>\fR
+WARNING: Do not use this options unless you know what you are doing.
+By default, trial creates a directory called _trial_temp under the current
+working directory. When trial runs, it first \fIdeletes\fR this directory,
+then creates it, then changes into the directory to run the tests. The log
+file and any coverage files are stored here. Use this option if you wish to
+have trial run in a directory other than _trial_temp. Be warned, trial
+will \fIdelete\fR the directory before re-creating it.
+.TP
+\fB--testmodule <filename>\fR
+Ask trial to look into <filename> and run any tests specified using the
+Emacs-style buffer variable 'test-case-name'.
+.TP
+\fB--unclean-warnings\fR
+As of Twisted 8.0, trial will report an error if the reactor is left unclean
+at the end of the test. This option is provided to assist in migrating from
+Twisted 2.5 to Twisted 8.0 and later. Enabling this option will turn the errors
+into warnings.
+.TP
+\fB-u\fR, \fB--until-failure\fR
+Keep looping the tests until one of them raises an error or a failure.
+This is particularly useful for reproducing intermittent failures.
+.TP
+\fB--version\fR
+Prints the Twisted version number and exit.
+.TP
+\fB--without-module <modulenames>\fR
+Simulate the lack of the specified comma-separated list of modules. This makes
+it look like the modules are not present in the system, causing tests to check
+the behavior for that configuration.
+.TP
+\fB-z\fR, \fB--random [<seed>]\fR
+Run the tests in random order using the specified seed.
+.PP
+.SH AUTHOR
+Written by Jonathan M. Lange
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2003-2008 Twisted Matrix Laboratories
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/Twisted-10.0.0/doc/core/man/twistd-man.html b/vendor/Twisted-10.0.0/doc/core/man/twistd-man.html
new file mode 100644
index 0000000000..292afa1932
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/twistd-man.html
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: TWISTD.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">TWISTD.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">OPTIONS</a></li><li><a href="#auto4">SIGNALS</a></li><li><a href="#auto5">AUTHOR</a></li><li><a href="#auto6">REPORTING BUGS</a></li><li><a href="#auto7">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>twistd - run Twisted applications (TACs, TAPs)
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>twistd</strong> [options]
+</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>Read a twisted.application.service.Application out of a file and run it.
+</p>
+
+<h2>OPTIONS<a name="auto3"/></h2>
+
+<p><strong>-n</strong>, <strong>--nodaemon</strong>
+Don't daemonize (stay in foreground).
+<dl><dt><strong>-q</strong>, <strong>--quiet</strong>
+</dt><dd>No-op for backwards compatibility.
+</dd>
+
+<dt><strong>-p</strong>, <strong>--profile</strong> <em>&lt;profile output&gt;</em>
+</dt><dd>Run the application under the profiler, dumping results to the specified file.
+</dd>
+
+<dt><strong>--profiler</strong> <em>&lt;profiler name&gt;</em>
+</dt><dd>Specify the profiler to use. Defaults to the 'hotshot' profiler.
+</dd>
+
+<dt><strong>--savestats</strong>
+</dt><dd>Save the Stats object rather than the text output of the profiler.
+</dd>
+
+<dt><strong>-b</strong>, <strong>--debug</strong>
+</dt><dd>Run the application in the Python Debugger (implies <strong>--nodaemon</strong> option).
+Sending a SIGINT or SIGUSR2 signal to the process will drop it into the
+debugger.
+</dd>
+
+<dt><strong>-e</strong>, <strong>--encrypted</strong> <em>&lt;file&gt;</em>
+</dt><dd>The specified tap/aos file is encrypted.
+</dd>
+
+<dt><strong>--euid</strong>
+</dt><dd>Set only effective user-id rather than real user-id. This option has no
+effect unless the server is running as root, in which case it means not
+to shed all privileges after binding ports, retaining the option to regain
+privileges in cases such as spawning processes. Use with caution.
+</dd>
+
+<dt><strong>-o</strong>, <strong>--no_save</strong>
+</dt><dd>Do not save shutdown state.
+</dd>
+
+<dt><strong>--originalname</strong>
+</dt><dd>Behave as though the specified Application has no process name set, and run
+with the standard process name (the Python binary in most cases).
+</dd>
+
+<dt><strong>-l</strong>, <strong>--logfile</strong> <em>&lt;logfile&gt;</em>
+</dt><dd>Log to a specified file, - for stdout (default: twistd.log).
+The log file will be rotated on SIGUSR1.
+</dd>
+
+<dt><strong>--pidfile</strong> <em>&lt;pidfile&gt;</em>
+</dt><dd>Save pid in specified file (default: twistd.pid).
+</dd>
+
+<dt><strong>--chroot</strong> <em>&lt;directory&gt;</em>
+</dt><dd>Chroot to a supplied directory before running (default: don't chroot).
+Chrooting is done before changing the current directory.
+</dd>
+
+<dt><strong>-d</strong>, <strong>--rundir</strong> <em>&lt;directory&gt;</em>
+</dt><dd>Change to a supplied directory before running (default: .).
+</dd>
+
+<dt><strong>-u</strong>, <strong>--uid</strong> <em>&lt;uid&gt;</em>
+</dt><dd>The uid to run as (default: don't change).
+</dd>
+
+<dt><strong>-g</strong>, <strong>--gid</strong> <em>&lt;gid&gt;</em>
+</dt><dd>The gid to run as (default: don't change).
+</dd>
+
+<dt><strong>--umask</strong> <em>&lt;mask&gt;</em>
+</dt><dd>The (octal) file creation mask to apply. (default: 0077 for daemons, no
+change otherwise).
+</dd>
+
+<dt><strong>-r</strong>, <strong>--reactor</strong> <em>&lt;reactor&gt;</em>
+</dt><dd>Choose which reactor to use. See <strong>--help-reactors</strong> for a list of
+possibilities.
+</dd>
+
+<dt><strong>--help-reactors</strong>
+</dt><dd>List the names of possibly available reactors.
+</dd>
+
+<dt><strong>--spew</strong>
+</dt><dd>Write an extremely verbose log of everything that happens. Useful for
+debugging freezes or locks in complex code.
+</dd>
+
+<dt><strong>-f</strong>, <strong>--file</strong> <em>&lt;tap file&gt;</em>
+</dt><dd>Read the given .tap file (default: twistd.tap).
+</dd>
+
+<dt><strong>-s</strong>, <strong>--source</strong> <em>&lt;tas file&gt;</em>
+</dt><dd>Load an Application from the given .tas (AOT Python source) file.
+</dd>
+
+<dt><strong>-y</strong>, <strong>--python</strong> <em>&lt;python file&gt;</em>
+</dt><dd>Use the variable <q>application</q> from the given Python file. This option overrides
+<strong>-f</strong>. This option implies <strong>--no_save</strong>.
+</dd>
+
+<dt><strong>-g</strong>, <strong>--plugin</strong> <em>&lt;plugin name&gt;</em>
+</dt><dd>Read config.tac from a plugin package, as with <strong>-y</strong>.
+</dd>
+
+<dt><strong>--syslog</strong>
+</dt><dd>Log to syslog instead of a file.
+</dd>
+
+<dt><strong>-u</strong>, <strong>--uid</strong> <em>&lt;uid&gt;</em>
+</dt><dd>The uid to run as.
+</dd>
+
+<dt><strong>-g</strong>, <strong>--gid</strong> <em>&lt;gid&gt;</em>
+</dt><dd>The gid to run as.
+</dd>
+
+<dt><strong>--version</strong>
+</dt><dd>Print version information and exit.
+</dd>
+
+<dt><strong>--prefix</strong> <em>&lt;prefix&gt;</em>
+</dt><dd>Use the specified prefix when logging to logfile. Default is <q>twisted</q>.
+</dd>
+
+</dl>
+
+</p>
+
+<p>Note that if <strong>twistd</strong> is run as root, the working directory is <em>not</em>
+searched for Python modules.
+</p>
+
+<h2>SIGNALS<a name="auto4"/></h2>
+
+<p>A running twistd accepts SIGINT for a clean shutdown and SIGUSR1 to rotate log
+files.
+</p>
+
+<h2>AUTHOR<a name="auto5"/></h2>
+
+<p>Written by Moshe Zadka, based on twistd's help messages.
+</p>
+
+<h2>REPORTING BUGS<a name="auto6"/></h2>
+
+<p>To report a bug, visit
+<em>http://twistedmatrix.com/trac/wiki/TwistedDevelopment#DevelopmentProcess</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto7"/></h2>
+
+<p>Copyright © 2001-2010 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/man/twistd.1 b/vendor/Twisted-10.0.0/doc/core/man/twistd.1
new file mode 100644
index 0000000000..ff92c9f58f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/man/twistd.1
@@ -0,0 +1,123 @@
+.TH TWISTD "1" "Dec 2003" "" ""
+.SH NAME
+twistd \- run Twisted applications (TACs, TAPs)
+.SH SYNOPSIS
+.B twistd
+[options]
+.SH DESCRIPTION
+Read a twisted.application.service.Application out of a file and run it.
+.SH OPTIONS
+\fB\-n\fR, \fB\--nodaemon\fR
+Don't daemonize (stay in foreground).
+.TP
+\fB\-q\fR, \fB\--quiet\fR
+No-op for backwards compatibility.
+.TP
+\fB\-p\fR, \fB\--profile\fR \fI<profile output>\fR
+Run the application under the profiler, dumping results to the specified file.
+.TP
+\fB\--profiler\fR \fI<profiler name>\fR
+Specify the profiler to use. Defaults to the 'hotshot' profiler.
+.TP
+\fB--savestats\fR
+Save the Stats object rather than the text output of the profiler.
+.TP
+\fB\-b\fR, \fB\--debug\fR
+Run the application in the Python Debugger (implies \fB\--nodaemon\fR option).
+Sending a SIGINT or SIGUSR2 signal to the process will drop it into the
+debugger.
+.TP
+\fB\-e\fR, \fB\--encrypted\fR \fI<file>\fR
+The specified tap/aos file is encrypted.
+.TP
+\fB--euid\fR
+Set only effective user-id rather than real user-id. This option has no
+effect unless the server is running as root, in which case it means not
+to shed all privileges after binding ports, retaining the option to regain
+privileges in cases such as spawning processes. Use with caution.
+.TP
+\fB\-o\fR, \fB\--no_save\fR
+Do not save shutdown state.
+.TP
+\fB\--originalname\fR
+Behave as though the specified Application has no process name set, and run
+with the standard process name (the Python binary in most cases).
+.TP
+\fB\-l\fR, \fB\--logfile\fR \fI<logfile>\fR
+Log to a specified file, - for stdout (default: twistd.log).
+The log file will be rotated on SIGUSR1.
+.TP
+\fB\--pidfile\fR \fI<pidfile>\fR
+Save pid in specified file (default: twistd.pid).
+.TP
+\fB\--chroot\fR \fI<directory>\fR
+Chroot to a supplied directory before running (default: don't chroot).
+Chrooting is done before changing the current directory.
+.TP
+\fB\-d\fR, \fB\--rundir\fR \fI<directory>\fR
+Change to a supplied directory before running (default: .).
+.TP
+\fB\-u\fR, \fB\--uid\fR \fI<uid>\fR
+The uid to run as (default: don't change).
+.TP
+\fB\-g\fR, \fB\--gid\fR \fI<gid>\fR
+The gid to run as (default: don't change).
+.TP
+\fB--umask\fR \fI<mask>\fR
+The (octal) file creation mask to apply. (default: 0077 for daemons, no
+change otherwise).
+.TP
+\fB\-r\fR, \fB\--reactor\fR \fI<reactor>\fR
+Choose which reactor to use. See \fB\--help-reactors\fR for a list of
+possibilities.
+.TP
+\fB--help-reactors\fR
+List the names of possibly available reactors.
+.TP
+\fB\--spew\fR
+Write an extremely verbose log of everything that happens. Useful for
+debugging freezes or locks in complex code.
+.TP
+\fB\-f\fR, \fB\--file\fR \fI<tap file>\fR
+Read the given .tap file (default: twistd.tap).
+.TP
+\fB\-s\fR, \fB\--source\fR \fI<tas file>\fR
+Load an Application from the given .tas (AOT Python source) file.
+.TP
+\fB\-y\fR, \fB\--python\fR \fI<python file>\fR
+Use the variable "application" from the given Python file. This option overrides
+\fB\-f\fR. This option implies \fB\--no_save\fR.
+.TP
+\fB\-g\fR, \fB\--plugin\fR \fI<plugin name>\fR
+Read config.tac from a plugin package, as with \fB\-y\fR.
+.TP
+\fB\--syslog\fR
+Log to syslog instead of a file.
+.TP
+\fB\-u\fR, \fB\--uid\fR \fI<uid>\fR
+The uid to run as.
+.TP
+\fB\-g\fR, \fB\--gid\fR \fI<gid>\fR
+The gid to run as.
+.TP
+\fB\--version\fR
+Print version information and exit.
+.TP
+\fB\--prefix\fR \fI<prefix>\fR
+Use the specified prefix when logging to logfile. Default is "twisted".
+.PP
+Note that if \fBtwistd\fR is run as root, the working directory is \fInot\fR
+searched for Python modules.
+.SH SIGNALS
+A running twistd accepts SIGINT for a clean shutdown and SIGUSR1 to rotate log
+files.
+.SH AUTHOR
+Written by Moshe Zadka, based on twistd's help messages.
+.SH "REPORTING BUGS"
+To report a bug, visit
+\fIhttp://twistedmatrix.com/trac/wiki/TwistedDevelopment#DevelopmentProcess\fR
+.SH COPYRIGHT
+Copyright \(co 2001-2010 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/Twisted-10.0.0/doc/core/specifications/banana.html b/vendor/Twisted-10.0.0/doc/core/specifications/banana.html
new file mode 100644
index 0000000000..9bee967cc9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/specifications/banana.html
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Banana Protocol Specifications</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Banana Protocol Specifications</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Banana Encodings</a></li><li><a href="#auto2">Element Types</a></li><ul><li><a href="#auto3">Examples</a></li></ul><li><a href="#auto4">Profiles</a></li><ul><li><a href="#auto5">The &quot;none&quot; Profile</a></li><li><a href="#auto6">The &quot;pb&quot; Profile</a></li></ul><li><a href="#auto7">Protocol Handshake and Behaviour</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Introduction<a name="auto0"/></h2>
+
+ <p>
+ Banana is an efficient, extendable protocol for sending and receiving s-expressions.
+ A s-expression in this context is a list composed of byte strings, integers,
+ large integers, floats and/or s-expressions.
+ </p>
+
+ <h2>Banana Encodings<a name="auto1"/></h2>
+
+ <p>
+ The banana protocol is a stream of data composed of elements. Each element has the
+ following general structure - first, the length of element encoded in base-128, least signficant
+ bit first. For example length 4674 will be sent as <code>0x42 0x24</code>. For certain element
+ types the length will be omitted (e.g. float) or have a different meaning (it is the actual
+ value of integer elements).
+ </p>
+
+ <p>
+ Following the length is a delimiter byte, which tells us what kind of element this
+ is. Depending on the element type, there will then follow the number of bytes specified
+ in the length. The byte's high-bit will always be set, so that we can differentiate
+ between it and the length (since the length bytes use 128-base, their high bit will
+ never be set).
+ </p>
+
+ <h2>Element Types<a name="auto2"/></h2>
+
+ <p>
+ Given a series of bytes that gave us length N, these are the different delimiter bytes:
+ </p>
+
+ <dl>
+ <dt>List -- 0x80</dt>
+
+ <dd>The following bytes are a list of N elements. Lists may be nested,
+ and a child list counts as only one element to its parent (regardless
+ of how many elements the child list contains). </dd>
+
+ <dt>Integer -- 0x81</dt>
+ <dd>The value of this element is the positive integer N. Following bytes are not part of this element. Integers can have values of 0 &lt;= N &lt;= 2147483647.</dd>
+
+ <dt>String -- 0x82</dt>
+ <dd>The following N bytes are a string element.</dd>
+
+ <dt>Negative Integer -- 0x83</dt>
+ <dd>The value of this element is the integer N * -1, i.e. -N. Following bytes are not part of this element. Negative integers can have values of 0 &gt;= -N &gt;= -2147483648.</dd>
+
+ <dt>Float - 0x84</dt>
+ <dd>The next 8 bytes are the float encoded in IEEE 754 floating-point <q>double format</q> bit layout.
+ No length bytes should have been defined.
+ </dd>
+
+ <dt>Large Integer -- 0x85</dt>
+ <dd>The value of this element is the positive large integer N. Following bytes are not part of this element. Large integers have no size limitation.</dd>
+
+ <dt>Large Negative Integer -- 0x86</dt>
+ <dd>The value of this element is the negative large integer -N. Following bytes are not part of this element. Large integers have no size limitation.</dd>
+ </dl>
+
+ <p>
+ Large integers are intended for arbitary length integers. Regular integers types (positive and negative) are limited to 32-bit values.
+ </p>
+
+ <h3>Examples<a name="auto3"/></h3>
+
+ <p>
+ Here are some examples of elements and their encodings - the type bytes are marked in bold:
+ </p>
+
+ <dl>
+ <dt><code>1</code></dt>
+ <dd><code>0x01 <strong>0x81</strong></code></dd>
+ <dt><code>-1</code></dt>
+ <dd><code>0x01 <strong>0x83</strong></code></dd>
+ <dt><code>1.5</code></dt>
+ <dd><code><strong>0x84</strong> 0x3f 0xf8 0x00 0x00 0x00 0x00 0x00 0x00</code></dd>
+ <dt><code>&quot;hello&quot;</code></dt>
+ <dd><code>0x05 <strong>0x82</strong> 0x68 0x65 0x6c 0x6c 0x6f</code></dd>
+ <dt><code>[]</code></dt>
+ <dd><code>0x00 <strong>0x80</strong></code></dd>
+ <dt><code>[1, 23]</code></dt>
+ <dd><code>0x02 <strong>0x80</strong> 0x01 <strong>0x81</strong> 0x17 <strong>0x81</strong></code></dd>
+ <dt><code>123456789123456789</code></dt>
+ <dd><code>0x15 0x3e 0x41 0x66 0x3a 0x69 0x26 0x5b 0x01 <strong>0x85</strong></code></dd>
+ <dt><code>[1, [&quot;hello&quot;]]</code></dt>
+ <dd><code>0x02 <strong>0x80</strong> 0x01 <strong>0x81</strong> 0x01 <strong>0x80</strong> 0x05 <strong>0x82</strong> 0x68 0x65 0x6c 0x6c 0x6f</code></dd>
+ </dl>
+
+ <h2>Profiles<a name="auto4"/></h2>
+
+ <p>
+ The Banana protocol is extendable. Therefore, it supports the concept of profiles. Profiles allow
+ developers to extend the banana protocol, adding new element types, while still keeping backwards
+ compatability with implementations that don't support the extensions. The profile used in each
+ session is determined at the handshake stage (see below.)
+ </p>
+
+ <p>
+ A profile is specified by a unique string. This specification defines two profiles
+ - <code>&quot;none&quot;</code> and <code>&quot;pb&quot;</code>. The <code>&quot;none&quot;</code> profile is the standard
+ profile that should be supported by all Banana implementations.
+ Additional profiles may be added in the future.
+ </p>
+
+ <h3>The <code>&quot;none&quot;</code> Profile<a name="auto5"/></h3>
+
+ <p>
+ The <code>&quot;none&quot;</code> profile is identical to the delimiter types listed above. It is highly recommended
+ that all Banana clients and servers support the <code>&quot;none&quot;</code> profile.
+ </p>
+
+ <h3>The <code>&quot;pb&quot;</code> Profile<a name="auto6"/></h3>
+
+ <p>
+ The <code>&quot;pb&quot;</code> profile is intended for use with the Perspective Broker protocol, that runs on top
+ of Banana. Basically, it converts commonly used PB strings into shorter versions, thus
+ minimizing bandwidth usage. It starts with a single byte, which tells us to which string element
+ to convert it, and ends with the delimiter byte, <code>0x87</code>, which should not be prefixed
+ by a length.
+ </p>
+
+ <dl>
+ <dt>0x01</dt> <dd>'None'</dd>
+ <dt>0x02</dt> <dd>'class'</dd>
+ <dt>0x03</dt> <dd>'dereference'</dd>
+ <dt>0x04</dt> <dd>'reference'</dd>
+ <dt>0x05</dt> <dd>'dictionary'</dd>
+ <dt>0x06</dt> <dd>'function'</dd>
+ <dt>0x07</dt> <dd>'instance'</dd>
+ <dt>0x08</dt> <dd>'list'</dd>
+ <dt>0x09</dt> <dd>'module'</dd>
+ <dt>0x0a</dt> <dd>'persistent'</dd>
+ <dt>0x0b</dt> <dd>'tuple'</dd>
+ <dt>0x0c</dt> <dd>'unpersistable'</dd>
+ <dt>0x0d</dt> <dd>'copy'</dd>
+ <dt>0x0e</dt> <dd>'cache'</dd>
+ <dt>0x0f</dt> <dd>'cached'</dd>
+ <dt>0x10</dt> <dd>'remote'</dd>
+ <dt>0x11</dt> <dd>'local'</dd>
+ <dt>0x12</dt> <dd>'lcache'</dd>
+ <dt>0x13</dt> <dd>'version'</dd>
+ <dt>0x14</dt> <dd>'login'</dd>
+ <dt>0x15</dt> <dd>'password'</dd>
+ <dt>0x16</dt> <dd>'challenge'</dd>
+ <dt>0x17</dt> <dd>'logged_in'</dd>
+ <dt>0x18</dt> <dd>'not_logged_in'</dd>
+ <dt>0x19</dt> <dd>'cachemessage'</dd>
+ <dt>0x1a</dt> <dd>'message'</dd>
+ <dt>0x1b</dt> <dd>'answer'</dd>
+ <dt>0x1c</dt> <dd>'error'</dd>
+ <dt>0x1d</dt> <dd>'decref'</dd>
+ <dt>0x1e</dt> <dd>'decache'</dd>
+ <dt>0x1f</dt> <dd>'uncache'</dd>
+ </dl>
+
+ <h2>Protocol Handshake and Behaviour<a name="auto7"/></h2>
+
+ <p>
+ The initiating side of the connection will be referred to as <q>client</q>, and the other
+ side as <q>server</q>.
+ </p>
+
+ <p>
+ Upon connection, the server will send the client a list of string elements, signifying
+ the profiles it supports. It is recommended that <code>&quot;none&quot;</code> be included in this list. The client
+ then sends the server a string from this list, telling the server which profile it wants to
+ use. At this point the whole session will use this profile.
+ </p>
+
+ <p>
+ Once a profile has been established, the two sides may start exchanging elements. There is no
+ limitation on order or dependencies of messages. Any such limitation (e.g. <q>server can only
+ send an element to client in response to a request from client</q>) is application specific.
+ </p>
+
+ <p>
+ Upon receiving illegal messages, failed handshakes, etc., a Banana client or server should
+ close its connection.
+ </p>
+
+ </div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/specifications/index.html b/vendor/Twisted-10.0.0/doc/core/specifications/index.html
new file mode 100644
index 0000000000..19c552fe08
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/specifications/index.html
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Specifications</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Specifications</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="banana.html" shape="rect">Banana</a></li>
+</ul>
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/upgrades/2.0/components.html b/vendor/Twisted-10.0.0/doc/core/upgrades/2.0/components.html
new file mode 100644
index 0000000000..63210133f1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/upgrades/2.0/components.html
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Twisted Zope Interfaces FAQ</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Twisted Zope Interfaces FAQ</h1>
+ <div class="toc"><ol><li><a href="#auto0">Twisted components system in 2.0</a></li><li><a href="#auto1">FAQ</a></li><ul><li><a href="#auto2">Why did Twisted switch to Zope Interfaces?</a></li><li><a href="#auto3">Why did Twisted switch to Zope Interfaces rather than PyProtocols?</a></li><li><a href="#auto4">Will this affect my deployment?</a></li><li><a href="#auto5">How can I update my own code?</a></li><li><a href="#auto6">
+
+What about third party classes dependant on the old style of implements
+declarations?
+
+</a></li><li><a href="#auto7">What about using third party classes?</a></li></ul><li><a href="#auto8">Acknowledgements</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Twisted components system in 2.0<a name="auto0"/></h2>
+
+<p>Twisted code's own use of the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.html" title="twisted.python.components">twisted.python.components</a></code> package has been updated to use <a href="http://www.zope.org/Wikis/Interfaces/FrontPage" shape="rect">Zope Interfaces</a> in
+the 2.0 release.</p>
+
+<p>New code developed starting with the 2.0 release of Twisted Core should use Zope
+Interfaces directly rather than using the twisted.python.components
+package.</p>
+
+<h2>FAQ<a name="auto1"/></h2>
+
+<h3>Why did Twisted switch to Zope Interfaces?<a name="auto2"/></h3>
+
+<p>
+The twisted.python.components package is a large amount of on-going
+maintenance. Using the Zope Interface package also provides a greater level of
+compatibility between Twisted interfaces and Zope interfaces.
+</p>
+
+<h3>Why did Twisted switch to Zope Interfaces rather than PyProtocols?<a name="auto3"/></h3>
+
+<p>
+The Zope Interface package was chosen over PyProtocols because of its greater
+conceptual similarity to twisted.python.components.
+</p>
+
+<h3>Will this affect my deployment?<a name="auto4"/></h3>
+
+<p>
+No. Releases of Twisted Core will include Zope Interfaces.
+</p>
+
+<h3>How can I update my own code?<a name="auto5"/></h3>
+
+<p>
+Classes written using twisted.python.components declare which interfaces they
+implement in this style:
+</p>
+
+<code>
+class C:
+ __implements__ = IFoo,
+</code>
+
+<p>
+This should be changed to:
+</p>
+
+<code>
+class C:
+ zope.interface.implements(IFoo)
+</code>
+
+<h3>
+
+What about third party classes dependant on the old style of implements
+declarations?
+
+<a name="auto6"/></h3>
+
+<p>
+Use <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.backwardsCompatImplements.html" title="twisted.python.components.backwardsCompatImplements">backwardsCompatImplements</a></code> to fix this.
+</p>
+
+<code>
+twisted.python.components.backwardsCompatImplements(C)
+</code>
+
+<h3>What about using third party classes?<a name="auto7"/></h3>
+
+<p>
+If you are using third party libraries that only declare __implements__, these
+objects should be made compatible with <code base="twisted.python.components">fixClassImplements</code>: </p>
+
+<code>
+# import o where o is some third party library
+from thirdparty.lib import o
+
+twisted.python.components.fixClassImplements(o.__class__)
+</code>
+
+<p>
+This will make sure that __implements__ declarations get converted to the new
+style of implements declarations.
+</p>
+
+<h2>Acknowledgements<a name="auto8"/></h2>
+
+<p>This document is the work of Jason A. Mobarak, with contributions from Mary
+Gardiner.</p>
+
+</div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/upgrades/2.0/index.html b/vendor/Twisted-10.0.0/doc/core/upgrades/2.0/index.html
new file mode 100644
index 0000000000..4eb5c41646
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/upgrades/2.0/index.html
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Upgrading to Twisted 2.0</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Upgrading to Twisted 2.0</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <ul>
+ <li>
+ <a href="split.html" shape="rect">
+ The Twisted Split FAQ
+ </a>: why was Twisted split into multiple packages?
+ </li>
+ <li>
+ <a href="components.html" shape="rect">
+ Components changes
+ </a>: Twisted now uses Zope Interfaces rather than its own component
+ system.
+ </li>
+ </ul>
+ </div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/upgrades/2.0/split.html b/vendor/Twisted-10.0.0/doc/core/upgrades/2.0/split.html
new file mode 100644
index 0000000000..67a2808aad
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/upgrades/2.0/split.html
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: The Twisted Split FAQ</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">The Twisted Split FAQ</h1>
+ <div class="toc"><ol><li><a href="#auto0">What is the Twisted Split?</a></li><li><a href="#auto1">Why was Twisted being split?</a></li><li><a href="#auto2">But I liked the monolithic packages. Can I still get them?</a></li><li><a href="#auto3">Where can I find information about the individual projects?</a></li><li><a href="#auto4">What are the new packages?</a></li><li><a href="#auto5">Will I have to rewrite my code? What API changes are there?</a></li><li><a href="#auto6">What about my deployments? What will I have to do to have the new packages?</a></li><li><a href="#auto7">Why are all the packages still named twisted.subproject?</a></li><li><a href="#auto8">What does this mean for existing Twisted developers?</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>What is the Twisted Split?<a name="auto0"/></h2>
+
+<p>
+<a href="http://twistedmatrix.com/" shape="rect">Twisted</a> is very large. At last
+count, it has around 80 thousand lines of code (yes, that <em>is</em> very
+large for a Python project, maybe not so for a C++ project. ;). We broke it
+into several smaller packages with the 2.0 release.
+</p>
+
+<h2>Why was Twisted being split?<a name="auto1"/></h2>
+
+<p>
+The biggest reason was to make our release process more
+agile. A regression in twisted.names, for example, could hold up the release
+of the entire thing, when really it should only be holding up the release of
+twisted.names.
+</p>
+
+<p>
+The other big reason is visibility. Twisted has a ton of
+functionality, but many people miss out on it because they don't know
+where it is hidden inside Twisted. The Twisted split gave every
+sub-project its own web site and thus more visibility.
+</p>
+
+<h2>But I liked the monolithic packages. Can I still get them?<a name="auto2"/></h2>
+
+<p>
+Yes. Tarball and Windows releases for Twisted and all of its
+sub-projects will still be maintained. We encourage maintainers of
+packages for OSes with automatic packaging systems to break up the
+packages as well, so, for example, Debian will have
+python2.3-twisted-core, python2.3-twisted-conch,
+python2.3-twisted-names, and so on.
+</p>
+
+<h2>Where can I find information about the individual projects?<a name="auto3"/></h2>
+
+<p>
+A list of <a href="http://twistedmatrix.com/trac/wiki/TwistedProjects" shape="rect">Twisted
+projects</a> is available on the website. The list includes maintainer
+information and links to project-specific pages with more detailed
+information.
+</p>
+
+<h2>What are the new packages?<a name="auto4"/></h2>
+
+<p>
+ <ul>
+
+ <li><a href="http://twistedmatrix.com/trac/wiki/TwistedCore" shape="rect">Twisted
+ Core</a> - This contains twisted.application, twisted.cred,
+ twisted.enterprise, twisted.internet, twisted.manhole,
+ twisted.persisted, twisted.protocols<sup><a href="#protocols" shape="rect">[1]</a></sup>, twisted.python, twisted.spread,
+ twisted.trial</li>
+
+ <li><a href="http://twistedmatrix.com/trac/wiki/TwistedConch" shape="rect">Twisted
+ Conch</a> - This contains twisted.conch.</li>
+
+ <li><a href="http://twistedmatrix.com/trac/wiki/TwistedLore" shape="rect">Twisted
+ Lore</a> - This contains twisted.lore</li>
+
+ <li><a href="http://twistedmatrix.com/trac/wiki/TwistedMail" shape="rect">Twisted
+ Mail</a> - This contains twisted.mail; NOTE the mail protocols
+ that were in twisted.protocols.(imap4,pop3,smtp) were moved to
+ twisted.mail.</li>
+
+ <li><a href="http://twistedmatrix.com/trac/wiki/TwistedNames" shape="rect">Twisted
+ Names</a> - This contains twisted.names; NOTE
+ twisted.protocols.dns was moved to twisted.names.dns.</li>
+
+ <li><a href="http://twistedmatrix.com/trac/wiki/TwistedNews" shape="rect">Twisted
+ News</a> - This contains twisted.news; NOTE
+ twisted.protocols.nntp was moved to twisted.news.nntp</li>
+
+ <li><a href="http://twistedmatrix.com/trac/wiki/TwistedPair" shape="rect">Twisted
+ Pair</a> - This contains twisted.pair; NOTE ethernet, ip, raw,
+ and rawudp protocol support was moved from twisted.protocols to
+ twisted.pair. (deprecated)</li>
+
+ <li><a href="http://twistedmatrix.com/trac/wiki/TwistedRunner" shape="rect">Twisted
+ Runner</a> - This contains twisted.runner.</li>
+
+ <li><a href="http://twistedmatrix.com/trac/wiki/TwistedWeb" shape="rect">Twisted
+ Web</a> - This contains twisted.web; NOTE that
+ twisted.protocols.http was moved to twisted.web.http.</li>
+
+ <li><a href="http://twistedmatrix.com/trac/wiki/TwistedWords" shape="rect">Twisted
+ Words</a> - This contains twisted.words; NOTE that twisted.im was
+ moved to twisted.words.im, twisted.xish was moved to
+ twisted.words.xish, AND the chat protocols (irc, msn, jabber, toc,
+ oscar) were moved to twisted.words.protocols.</li>
+ </ul>
+</p>
+
+<p><a name="protocols" shape="rect">[1]</a>: twisted.protocols is very stripped
+down now; it only includes the protocols that didn't belong anywhere
+else. It still contains the simple protocols, the helper utilities,
+and, ahem, FTP.</p>
+
+
+<h2>Will I have to rewrite my code? What API changes are there?<a name="auto5"/></h2>
+
+<p>
+No existing code should <em>break</em>, however, many modules were
+moved. Backwards compatibility support <em>does exist</em> in older versions of
+Twisted, but was removed in Twisted 9.0.</p>
+
+<h2>What about my deployments? What will I have to do to have the new packages?<a name="auto6"/></h2>
+
+<p>
+It depends on your OS and how you installed Twisted originally. If
+you're using Debian, we are planning on breaking up the Debian
+packages to e.g. python2.3-twisted-core, python2.3-twisted-web, and so
+on. If you're using Windows, or generally install Twisted from the
+tarball or from an SVN checkout, monolithic options will still be
+available.
+</p>
+
+<p>
+If you try to run code that imports a sub-package when that
+sub-package is not available on the system, an ImportError will be
+raised directing the user to the web site for that particular
+sub-project.
+</p>
+
+<h2>Why are all the packages still named twisted.<em>subproject</em>?<a name="auto7"/></h2>
+
+<p>
+This is controversial. While this does mean that there is a
+mashed-together namespace under <code>twisted.</code>, it's also the
+simplest thing to do, and means less breakage for user-code, so we did that.
+</p>
+
+<h2>What does this mean for existing Twisted developers?<a name="auto8"/></h2>
+
+<p>
+Not much. The repository is rearranged a bit; protocols have been
+moved to their relevant packages and documentation is now stored in
+doc/<em>subproject</em>/ instead of everything at the top-level of
+doc/. Everything is still in the same repository and everyone still
+has the same access levels they used to.
+</p>
+
+</div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/core/upgrades/index.html b/vendor/Twisted-10.0.0/doc/core/upgrades/index.html
new file mode 100644
index 0000000000..311d91fefd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/core/upgrades/index.html
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Upgrading</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Upgrading</h1>
+ <div class="toc"><ol><li><a href="#auto0">Upgrading between versions of Twisted Core</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Upgrading between versions of Twisted Core<a name="auto0"/></h2>
+
+ <p>
+ These documents describe code changes you may need to make to use a
+ newer version of Twisted Core. Not all releases will require code
+ changes.
+ </p>
+
+ <ul>
+ <li><a href="2.0/index.html" shape="rect">Twisted 2.0</a></li>
+ </ul>
+ </div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/fun/Twisted.Quotes b/vendor/Twisted-10.0.0/doc/fun/Twisted.Quotes
new file mode 100644
index 0000000000..7d81becc89
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/fun/Twisted.Quotes
@@ -0,0 +1,5722 @@
+December 5, 2000:
+Washort says, " $self._hasIntelligence()"
+Washort says, "1"
+Washort says, "*ponders setting that to 0 on certain people*"
+Maxwell says, "yes, that's our Ego-Enhancing API"
+ [this from before we had 'emote'. I added it 10 minutes later. -ed]
+%
+You say, "I wanted to discourage people from using the [old] code as examples...".
+You say, "but I don't think that bad java style is going to damage your budding programming skills :)".
+* washort nods. 'I seriously hope not.' [see, i told you I added 'emote' -ed]
+Washort says, "Oh, did i ever tell you about the Java assignment i had at the beginning of the semester?"
+Washort says, "I was bored so i did it without any loops or temp variables."
+Washort says, "so... don't tell ME about bad style. ;)"
+%
+<glyph> it's times like this when I wish I could just swallow my pride and use a standard thing like asyncore :)
+%
+<jedin> Since it's completely unsolicited, I'd just like to add that anyone who tries implementing Keynesian economics in this game will be put behind the door with the Elder Sign....
+%
+<Nafai> I love Python. It has made me look smart in this consulting job. Because of how easily I was able to do what they need me to do, they ended up doubling my pay rate. :) Woot!
+<glyph> Woot *indeed*, good sir. :)
+%
+<glyph> you know, when I say *now* I mean "in a minute" :)
+%
+Glyph: "You need to start working on Twisted Reality."
+Mike: "What makes you say that?"
+Glyph: "Because it pains me to hear you talk about how you were 'in the same bed as' someone on AIM. There is no bed. There is no spoon. There's just some gay-ass peer-to-peer shit going on."
+%
+02:26:44 AreteComp: I've decided I'm going to warn you every 5 minutes until you go to bed.
+(You have been warned by AreteComp (5%))
+02:28:48 AreteComp: Tick, tick, motherfucker.
+%
+<washort> "TONIGHT on CELEBRITY DEATHMATCH: Kenaan vs The Shrike"
+%
+<washort> yow. autoconf can be *thorough* sometimes..
+<washort> "checking for EBCDIC... no"
+<washort> i hesitate to ask what it would have done had the answer been "yes"
+%
+<glyphAtWork> the http server was so we could say "Web!" if we ever did a freshmeat announcement
+<glyphAtWork> this makes people excited
+%
+FifthKow: jello is beyond good and evil
+%
+<washort> besides, we need a way to handle the cases of characters on drugs...
+%
+<jerji> sorry glyph, but I have to take away your dork award. det is far more deserving.:)
+%
+<det> glyph: you be on tomorrow ?
+<glyph> det: what, you think I'll suddenly grow a life?
+%
+<glyph> det: if it were any more generic it would be socket.socket
+-- (responding to det's request to make twisted.web more generic)
+%
+<glyph> GenericBoy: Dude, this is *python*... objects get created when you sneeze
+%
+<tenth> I get the feeling that I could rack up some ad impressions by posting an announcement to FM about a webserver "powered entirely by love, that I made out of this bong I had".
+<tenth> Well, as long as it did something really l88t that other bong-servers didn't do, anyway.
+%
+01:35:08 AreteComp:
+Before you finish linking, you must answer the following:
+Are you a Jew?[y/N]: y
+Nice try, Yid.
+%
+<samuel> oh why do you mock me rpm
+%
+<jerji> oh no!
+%
+<tpck> http://www.twistedmatrix.com/whatisdivunal.html << makes it sound likes its done and played by millions worldwide
+<washort> tpck: that's what ad copy is for
+%
+<tpck> glyph: I thought Enterprise Class Software wasn't supposed to crash?
+<glyph> tpck: It costs extra for the kind that doesn't crash, I think
+%
+<glyph> now you're probably wondering how to run cvs
+<samuel> actually i was thinking of naked women.
+<samuel> but sure.
+%
+<\\mimic> graydon: it's when you start constructing conditional branches in sed that the men in white coats come for you
+<graydon> mimic: been there, done that. wrote a qmail crypto extension in sed this summer :)
+%
+<jerji> dude tf programming, in my experience, was just about reading the help file and hacking something until it worked.
+<jerji> not really the kind of place to employ software engineering principles. ;)
+%
+<denial> CanDoo: I would rather run a home trepaning centre than do tech support :)
+%
+<zedboy> washort "A given program in PERL is like a turd. you can see it. smell it. touch it. yuck! it's definitely a turd. it's compact. it's smelly. it's brown. a turd, thru and thru"
+<zedboy> washort "The *same* program in C/C++/Java/your favorite imperative language here is like a roll
+ of toilet paper, with the turd smeared *all over it*. you tear of one sheet. yuck! another sheet. ugh! another sheet. ewww! etc"
+ --- quoting Chet Murphy
+%
+<shapr> I get the feeling regexes in emacs are subtly different from python
+%
+<moshez> I'm not touching anything not abstracted from hardware at least two levels
+%
+<dreid> wh00t!
+<dreid> i made the quotefile!
+%
+<dreid> "lispachu, parentheses attack!"
+%
+<e@ircnet> internet
+%
+<washort> this was experimentally determined using an unholy combination of emacs, python's interactive mode, and bc
+%
+<smoke:#lisp> perhaps i should write a "Teach yourself CL in 21 days" book and hide from Peter Norvig for a few years
+%
+<GenericBoy> I'm not high!
+%
+<Mike_L> what is twisted python?
+<glyph> Mike_L: it's the python libraries your mother would use, if she were a programmer, had a lot of free time, and was very VERY patient
+%
+KaraNiSuru: Your opinion has differing degrees of importance to me. On
+programming, it's almost like law; on fashion, it's unimportant; on cuteness,
+it serves only to warn me away.
+(addressed to glyph, from his girlfriend)
+%
+<washort> glyph: you're evil, too
+<glyph> washort: I try
+<washort> not the good kind of evil
+<washort> the other kind
+%
+<Yosomono@efnet> swing is to gui programming what cupholders are to cdrom drives
+<Yosomono@efnet> something easily mistaken for the real thing
+%
+<glyph> well, I'm working on divunal now
+<washort> and what are you doing to it?
+<glyph> I'm making the clouds work again
+<glyph> the clouds were always one of my favorite bits
+<washort> bah
+<washort> typical vapourware
+%
+<glyph> washort: I learned C from reading the E sources.
+<washort> glyph: well, i learned python from reading Zope
+<washort> glyph: so i think we're about equally damaged
+%
+--> glyph (glyph@adsl-64-123-27-108.dsl.austtx.swbell.net) has joined #python
+<glyph> yay for pushing the wrong button
+<washort> when will you xchat people learn
+<washort> silly hacker, irc is for terminals
+<washort> you dont see *me* typing '/quite' by accident ;)
+<-- washort has quit (either =))
+--> washort (washort@131.204.216.12) has joined #Python
+<washort> glyph: you bastard.
+%
+<GenericBoy> I wish I had enough knowledge to start working on this damn thing
+<GenericBoy> glyph: But you had to crush my hopes. ;)
+<GenericBoy> not that that's bad though, I am grateful for giving me a better
+perspective
+<glyph> GenericBoy: crushing hopes is what I do best
+<glyph> GenericBoy: you call me "glyph", but in ancient mesopotamia they called me the "eater of souls"
+%
+<glyph> many as-yet-untranslated pre-cuneform tablets will one day be
+translated to say "beware he who will write a webserver that will deprive you
+of your very will to live!"
+<glyph> GenericBoy: although I'm not sure if they were talking about me or
+marc andreissen
+%
+<GenericBoy> I'll be the t.w guy from now on
+<glyph> yay!
+<glyph> YAY!
+<glyph> SOMEBODY ELSE IS GOING TO MAINTAIN MY SOFTWARE
+<glyph> oh god I think I'm going to cry
+<GenericBoy> ack
+<washort> GenericBoy: i think that was a mistake :)
+* GenericBoy runs
+%
+<glyph> GenericBoy: * New in 0.8.0: carmstro's soul now comes with twisted.web
+%
+<jepler> C:
+<jepler> char buf[1024]
+<jepler> strcpy(buf, user_data)
+<jepler> Python:
+<jepler> buf = user_data[:1024]
+<jepler> if len(user_data) > 1024: security_hole(user_data[1024:])
+<jepler> actually, the translation is not difficult, so long as you implement security_hole() properly.
+%
+<LynchM0b> ... do u have an easier way
+<glyph> python ;-)
+<LynchM0b> thank the lord
+<LynchM0b> java is rediculous
+%
+<yy[Z]@efnet> i can say with all confidence that my python code was the
+fastest and tightest code on the whole java project i been on for the last year
+%
+<ben3> OO is a seductive failure.
+%
+<dreid> washort: i don't want to take over the world
+<dreid> i want to marry the chick who is going to take over the world
+%
+<demoncrat> forth is much better than sanity
+%
+<snibril> Tim can go on at length on issues which are not really the core of the problem, complicating said problem for himself and everybody else.
+<snibril> glyph: sounds like you ;)
+<glyph> snibril: the difference is there is rarely actually a problem, when I'm involved :)
+%
+<glyph> So if I understand you correctly you want software that will b-2-b education portal internet enterprise mission-critical!
+<muks> yep
+<glyph> Ah. then you want Zope.
+%
+<shapr> glyph: ok, where's the tw tutorial?
+<glyph> shapr: feh, you think there is *documentation*? You just need to be at harmony with the universe, and the api calls will come to you.
+<shapr> I just got a job writing Java. harmony is nowhere close to me.
+%
+<shapr> I just *love* your Python vs Java rant :) it's GREAT
+%
+<shapr> glyph: while reading through the last part of your rant, I got this mental picture of "Glyph Lefkowitz, Python Ninja" systematically chopping limbs off the JVM
+<shapr> glyph: the problem is that "don't expect your apps to run" was cutting the head off, and for cinematic effect, it should be on the bottom
+%
+<dreid> GenericBoy: but multiple eterms tailing various logfiles are great for making it look like your actually doing something :)
+<GenericBoy> hehe
+<dreid> i'm preparing myself for when i have to work in a corporate setting
+%
+<TomG> I'm in the wrong channel.
+%
+<dreid> Yoso: i like to think that i'm a fairly sane individual for a python programmer anyway
+%
+<glyph> snibril: I think we should put *you* in the unit tests dir.
+%
+<bram> have I mentioned there's a FRIGGIN BUTTLOAD of ways web input can go bad?
+%
+<washort> GenericBoy: if we knew what we were doing, we would not call it programming
+%
+<GenericBoy> Usually relying on magic buttons from the future doesn't work
+[in reply to something Mike_L said. --ed]
+%
+<blupingu> hi glyph. i'm trying out python because of twisted python :)
+%
+ newpath = os.path.join(self.path, path)
+ # forgive me, oh lord, for I know not what I do
+ p, ext = os.path.splitext(newpath)
+%
+<Nafai> There's a twisted python philosophy tutorial?
+<washort> Nafai: yes.... read it, expand your consciousness
+<obanta> It's actually a new religion
+%
+<glyph> washort: coding angry lends whole new meaning to song lyrics :)
+%
+<GenericBoy> what's the point of all of this?
+<glyph> GenericBoy: I don't know
+%
+<Acapnotic> [ Read Past Entries ] [ Modify an entry ] [ Write new entry ] [ Have me add one for you. ]
+<Acapnotic> include: [ ] generic angst [ ] relationship trouble (or lack thereof)
+ [ ] other family trouble [ ] cynical technology rants
+<Acapnotic> also bash: [ ] slashdot [ ] users [ ] sysadmins [ ] politicians [ ] voters
+<Cysgod> [ ] Perl
+
+ -- proposed new configuration interface for the standard twisted.web weblog
+%
+<moshez@ircnet> I'm going to write a treatise "girls as open-source projects".
+<moshez@ircnet> Instead of "reaching second-base", you're "writing patches".
+<moshez@ircnet> "So, are you writing patches for you-know-who?" "Well, no, but I'm using the CVS version"
+<moshez@ircnet> Should translate to "We're only kissing, but that's as serious as it got"
+%
+<dreid> watching a beautiful girl sleep is amazingly fun
+<dreid> more fun than coding
+<washort> do you mean 'more fun than coding Enterprise Applications in java', 'more fun than coding display hacks in C', or 'more fun than coding weblogs in python'?
+%
+ # ha ha, python can do lexical closures good enough for me
+ # (Bah. if these were lexical closures you wouldn't need the
+ # 'obj=obj', and you could do 'return setdesc' and the
+ # function would still work after escaping. -was)
+ def setdesc(desc, obj=obj):
+ obj.description=desc
+%
+<_Krelin> Data hiding and encapsulation are at least in-laws, if not blood brothers
+<glyph> data hiding is encapsulation's shrewish mother-in-law
+%
+<dreid> Zope is pretty much the reason i learned PHP, (and TPy is the reason i stopped)
+%
+* Nafai doesn't think he is worthy of the quotefile
+<washort> you're in it twice
+%
+<washort@opn> "twisted python.... it's featurrific!"
+%
+<moshez> living is just syntactic sugar.
+%
+<det> glyph: what are you going to do now that UO2 is canceled ?
+<washort> det: take over the world
+<washort> det: same as before
+<det> washort: but thats what he was going to do last night
+<washort> det: glyph is a man of habit
+%
+<washort> who invented this "time zone" crap? everybody should be on IRC at once
+%
+--- washort has changed the topic to: | <-- you must be smarter than this stick to ride the internet
+%
+<Krelin> TwistedPython may, in fact, have both "enterprise" AND "internet" ;)
+%
+<thirmite@efnet> nothing like a pop tart to remind me i live in a first world country
+%
+<glyph> yosomono: in fact, I'll turn this box of Cheese Nips and
+ this 3-liter bottle of Mountain Dew into a irc2web interface
+%
+<h3x> actually i have clothes on
+<h3x> believe it or not
+%
+<\broken:#openprojects> geez that tomg bot is in here again
+<\broken:#openprojects> didn't we ban it a couple of times already
+%
+<Yosomono> uh, move zig zamboni to push grandma cats down the stairs to protect her/him from the terrible secret of space, which is that she/he can't skate?
+%
+<washort> o/` once i was the king of spain o/`
+* Acapnotic throws a humble pie at washort
+%
+<bram> the more I get into the art of design, the more I design things like I'm seven years old
+<bram> 'I don't want to do things that way because it's too hard'
+<bram> 'I wanna do it like this because I understand it'
+<bram> 'I'm ignoring that because it's scary'
+<bram> 'I don't want to work with him because he's a poopy-head'
+<bram> 'I don't want to use this because it smells like poo'
+<bram> 'this is no fun any more, I'm going home'
+%
+<cube> Greetings, O Twisted One
+%
+<Forest> Someone please tell me that this thing about P3K and Perl 6 is just a sick April Fool's joke
+<glyph> Forest: what, print>> wasn't a big enough hint?
+%
+* moshez lives to workaround design decisions made by others.
+(-- after just proposing to implement IRC over HTTP via Zope.)
+%
+ <idcmp> /msg ry a/s/l
+%
+<glyph> okay, cvs is scaring me
+<det> glyph: when I was 5, when the other children were going as ninjas and dracula, I went as CVS!
+<glyph> det: you should have gone as SCCS
+<det> glyph: you gotta be a little cute to get the candy
+%
+<cube> If you are anal, and you love to be right all the time, C++ gives you a multitude of mostly untimportant details to fret about so you can feel good about yourself for getting them "right", while missing the big picture entirely
+%
+<cube> C++ extends the machine-efficiency requirement all the way up from
+ line-by-line implementation into entity abstraction as classes, it
+ corrupts far end of the coding spectrum with "efficiency" concerns.
+%
+<glyph> that's why I love IRC
+<glyph> you can't be late for IRC
+%
+<radix> uh oh.
+<radix> 'destroy here' isn't a good idea. :)
+%
+<radix> glyph: the problem with writing a framework for text universe is
+that text adventure authors want to do the craziest shit :)
+%
+<thirmite@efnet> are you jewish?
+<moshez> yes.
+<moshez> be afraid
+%
+<thirmite> btw, e, what are the girls like in .fi?
+<e@ircnet> bipedal, warm blooded, pink skinned, about 1.5-2.0 meters tall
+%
+<dash> jeffk isn't funny, the people who think he's real are funny. :)
+<thirmite> he isn't real?!?
+%
+<cyli> it'd be so cool. i'd feel all l33t and shit
+%
+<dash> if they had neural interfaces to computers, we'd both be dead by now
+%
+<skreech> I declare myself god
+<skreech> the end
+%
+<radix> GenericBoy is no more
+<radix> I killed him, and have taken his place
+<Acapnotic> radix: whadja do with the body?
+<det> Acapnotic: killed in a metophorical sense
+<radix> that's what you think.
+<Acapnotic> What happened to the metaphorical body?
+<det> Acapnotic: the metaphorical body is decaying at the bottom of lake washington
+<radix> that's what he thinks.
+%
+ <dnm:#lisp> i'm convinced the core of loop [the Common Lisp facility] is a n-dimensional singularity and
+ that the common macro people implement is merrely the tessaract to loop's hypercube.
+%
+<radix> I'm an at least somewhat-educated dope fiend
+%
+<jedin> I figured your lasers would be a good impetus to action.
+<glyph> Don't forget about them.
+<glyph> They're hovering, just over your head... where you can't see them. Remember that.
+<jedin> Okay.
+<jedin> Hm. That could be a cool theme for a new breakfast cereal!
+%
+<moshez> glyph: I don't know anything about reality.
+%
+<Acapnotic> There are *many* differences between Texas and yogurt. Texas is drier than yogurt. Texas is larger than any amount of yogurt I've seen in one place at a time. (or ALL the yogurt I've seen at ANY time). Eating Texas would be less enjoyable than eating yogurt.
+<Acapnotic> Texas does not come in eight ounce plastic containers with tinfoil lids. To the best of my knowledge, there is no "fruit on the bottom" version of Texas. Texas is not available in the dairy section of your grocer. Texas does not help fufill your daily dietary requirement of calcium.
+%
+<eAndroid> MAKE YOUR LOGO AL GORE ON A STICK
+%
+<dash> radix: the question is, do you _really_ want to do that? :)
+<radix> no, but I want to make other people do it
+%
+<Rainy-Day> i ascended several times.. once as a tourist without wishes or material transformations
+%
+<radix> dash: Hey, what do you think a good visual aid for a talk on anarcho-capitalism would be?
+<glyph> radix: a gun.
+<glyph> radix: correction: a gun and a big pile of money :)
+%
+<mothra> Most large software projects are disasters. Nothing new there.
+<dash> most large software projects use java or C++. not a coincidence.
+%
+<dash> the program isn't debugged until the last user is dead
+%
+<moshez> glyph: I prefer to think of it as a community project...since not every interface is equal
+<moshez> some interfaces are more equal then others.
+%
+<det> glyph: why not use xml? (only because it is sort of a python standard [dont kill me])
+%
+<LiquidAngle> can you do socket programming with python ?
+<dash> boy can you _ever_
+%
+<spiv> In python, you can, but in Java you can't.
+ [ this comment had context, but it's really just axiomatic --ed]
+%
+<faassen> I'm not a python luminary, I just play one on TV. :)
+%
+<h3x> but the point is, i dont have to juggle dlopen() bullshit
+<h3x> because that gets old real fast
+%
+<glyph> shapr-werk: I can't even imagine the hell of having to write java while quitting smoking. I am behind you 100% ;)
+<shapr-werk> glyph: yah, anyone in front of me has already been mauled :-)
+%
+<Krelin> glyph: You have created a powerful solution for which there are no problems. Everyone is impressed, but duly confused.
+%
+<radix> crack! *that's* what I need!
+%
+<glyph> I like writing code that overloads operators in python
+<e> get help
+%
+<Yosomono> rasterman is the millionth monkey
+%
+<solomon> john tesh get out of my head!
+%
+<dash> i want distributed everything
+<dash> yesterday
+%
+<parks> glyph please please dont jump on the P2P XML bandwagon
+<dash> parks: satan will be buying ice skates before glyph does that
+%
+<shapr> this is where I tell you to stop hyperfocussing on bad stuff and think about something nifty like metaclasses or sex
+%
+* itamar loves changing an object's own class at run time
+<snibril> itamar: and you eat little babies, too
+%
+<TQuid> So glyph is a master of the occult as well as the obscure. :)
+%
+<glyph> "What?" "Take the red continuation." "What?" "Take the blue continuation." "Huh?" "Take the red continuation." "What?"...
+%
+* rik cheers for twisted python
+<rik> it's the easiest network coding toolkit I've come across
+<rik> as soon as you have the flash of inspiration as to how it works, you'll not look back
+%
+<dnm> ugh. linear cosmologist fever.
+%
+<e> we have powers that reach beyond the pickle
+%
+<TQuid> "No one expects the python acquisition!"
+%
+<laotse> I'm sorry. I used to be sane. Then I learned Perl and now I'm like this. ;)
+<dash> laotse: that's my excuse too
+<dash> laotse: that, and 4 years of university CS
+%
+ <h3x> i get my best programming done in the nude
+%
+<timmy> what is the recommended way to do client sockets in python
+<e2d2@ircnet> timmy: a chainsaw! AHAHAHAHA!
+* e2d2@ircnet goes back to sleep
+%
+<Acapnotic> No more doc about twist-dee, needs another page or three...
+<Acapnotic> You call this an application server? This is a slide projector and a bedsheet!
+* Acapnotic is going to have to speak to Bob about this.
+%
+<phed> dash: that's the cool part of system programming, programming half-finished programs, and tell others you're finished
+%
+<dash> if i'm going to use an obscure language with poor system integration, it might as well be lisp
+%
+<radix> I feel so special when people quote me
+%
+<spiv> dash: so we need to wrap integers... Java does that too, so it can't be that bad ;)
+* dash doesn't know how to respond to that except with physical violence
+%
+<glyph> It's just like a method call, but ON FIRE AND UPSIDE DOWN!!!
+%
+<dash> design patterns in general are just java/c++ crutches
+<dash> which isn't to say they're useless. when your language is crippled you need crutches
+%
+<laotse> Java is the tell me when I've been bad language ;)
+%
+<e> so he is writing a python interpreter in python
+<firegod> dash: is he actually that evil?
+<dash> firegod: for glyph, this is relatively non-evil.
+%
+<gary> btw, my gcc compile line is gcc -o foo foo.o includes.o -lstdcxx is there I can cut that stdcxx out? My executables are like half a meg.
+<glyph> nope
+<glyph> if you didn't want your executables to be huge and slow for no reason, you could stop using c++ :)
+%
+<glyph> the industry average per programmer/day is 10 lines
+<gary> yeah I know. its sorta sad.
+<gary> have you ever wanted to, like, be part of the backstreet boys or something? It would probably make life quite easier. Or at least different.
+ [This is what C++ does to your brain, kids. -ed]
+%
+<red_one> PORK IS NOT A VERB.
+%
+* shapr reads market speak
+<shapr> Vertical navigation through business domain trees (classification trees).
+<shapr> Horizontal navigation through multidimensional classification trees.
+<shapr> I bet moshe wrote this advertising
+%
+<snibril> glyph: others do secret sex perversities, and you join #c++
+%
+<jumpy> we are the knights who say INT! SHORT! and UNSIGGGGGGGGGGGGGGGGNNNNNNNNNNNEDDDDDDDDDDDDD LONNNNNNNNNGGGGGG!
+%
+<saint_go@efnet> Why?
+<dash> because C++ is an excellent language for doing slow and late projects in. :)
+<makk@efnet> dash: at least it's good for something. :)
+%
+KaraNiSuru: who needs a real live girl when you can get thousands of prettier girls displayed on a gorgeous 1365x768 resolution, 16.7 million color flat-panel plasma tv?
+%
+<radix> yeah, I saw that OBSOLETE_base attribute and thought to myself "Maybe glyph already tried this, and found that it sucks"
+%
+<jafo> Our fathers were our models for God. If they bailed, what does that tell you about God? You have to be prepared for the possibility that God does not like you.
+%
+<Acapnotic> ... whenever I hear anything in this channel that smacks my brain three feet into kata, chances are that glyph is the one that said it
+%
+<hunter> ... I'm execfile()'ing a file provided by j random sysadmin, so I'm pretty much holding a gun to my head.
+%
+<deeptape> I just got a vision of a version of Gaunlet that pits Pythonistas against an endless horde of C++ and Java zealouts
+<deeptape> Red Hacker needs Source, Badly
+%
+--> java (dutkiewicz@91.portland-01-02rs.or.dial-access.att.net) has joined #python
+<java> yes
+<-- java (dutkiewicz@91.portland-01-02rs.or.dial-access.att.net) has left #python
+<glyph> goodbye, java
+<glyph> hehe, that's surprisingly satisfying to say
+%
+<glyph> dash! dash! he's our man! If he can't do it, we'll make him write in ASP until he dies! bwahahhaha
+<dash> i hate you, milkman glyph
+%
+<thirmite> i have <glyph> and <dash> both subbed to the one message: <tpy> $1-
+%
+<glyph> so thirmite
+<glyph> it sounds like you have some issues
+<thirmite> duh i've been hanging around #python for around 4 years
+<thirmite> i have every issue possible
+%
+<Acapnotic> glyph: why are you being an asshole and insisting on seven bits instead of eight, anyway?
+<glyph> Acapnotic: because I gave up that bit in exchange for eternal life
+%
+<churchr> XML wasn't invented. It was excreted.
+%
+<faassen> I'm not a PSU agent.
+%
+<thirmite> i *think* i have a girlfriend
+%
+<dash> roey: i've got some code you should look at
+<dash> roey: http://twistedmatrix.com/
+<Acapnotic> dash: it's amazing how much you can make "I've got some code you
+should look at" sound like "do you have stairs in your house?"
+%
+<Mike_L> hmm ELF sounds complex =/
+* Mike_L hates file formats
+<Mike_L> i suppose I could just make my bytecode file format based on XML
+%
+<glyph> I am *not* a PSU agent.
+%
+<dash> glyph: nice people dont name functions "b1282int"
+%
+<jemfinch> I mean, if GNU wants everything to use guile, they should probably make it suck less.
+%
+<dash> i feel the power of the confusatron
+%
+<Yosomono> [Ying] is a fantastic artist, that's for certain.
+%
+<shapr> so, is the twisted crowd moving to Oz?
+<dash> shapr: no. Oz is coming to _us_
+%
+<h3x> why dosent someone write a rfc or w3 spec on server push text fields?
+<Acapnotic> look, everybody knows that "push" had it's chance, and it flunked. Pushing failed. Pushing is not the answer.
+<dash> Acapnotic: SHOVING IS THE ANSWER
+<Acapnotic> yes, shoving is the answer. We must have shoving streaming media. "I am the shover transport -- I push the newsfeed down their throats."
+%
+<mitiege> dash: where do you go to school?
+<tpck> mitiege: PSU
+<mitiege> tpck: didn't faassen go there too?
+%
+<eihrul> .NET is kinda the quickening
+%
+<shapr> we're all CODEpendant.
+%
+<robbe@ircnet> AttributeError: CMD
+<robbe@ircnet> what is here the failure?!?
+<radix> robbe: the 'cmd' object doesn't have a member named CMD
+<robbe@ircnet> radix: how i can make it?
+<radix> robbe: set us up the bomb
+%
+<M-x> sure, excessive use of the Emacs causes social problems
+<M-x> in understanding the trivial problems other people have
+<M-x> like you see them indenting a whole file of source code manually, or jumping between make output and trying to find the offending line
+%
+<dash> "COM Error: Errors occurred"
+* dash attacks ASP with a rusty hacksaw blade
+%
+<lyn:#lisp> making things fast generally seems to involve trading space for time
+<dan`b:#lisp> not so! you're thinking like a typical lisp programer
+<dan`b:#lisp> you can also trade correctness, like any self-respecting C hacker
+%
+<shapr> dash: I know Python adds sanity points to me.
+<dash> shapr: reading glyph's code does not
+%
+<tireg> i see the light!!
+<tireg> AND IT BURNS!
+<dash> tireg: welcome to python
+%
+<Acapnotic> jemfinch: What's to parse? A numeric code, perhaps a chicken, and some arguments
+%
+<Acapnotic> dash: yes, about that, do you have anything besides spam?
+<dash> Acapnotic: got spam, spam, internet, enterprise, and spam
+<dash> Acapnotic: that doesn't got _much_ spam
+%
+<itamar> if moshez ever gets into the Python RPG he'll have "different definitions of basic concepts leads to conflicts with everyone" as a disadvantage
+%
+<e@ircnet> meikan adsl:n asennus makso sentaan muistaakseni 3000 ja silta
+sedalta meni 10min :)
+<e@ircnet> oops, wrong channel
+<radix> eek
+<radix> scary words
+<dash> radix: ph34r the ph1nn1sh!
+%
+<matsaleh> glyph has been *trying* to bring me up to speed on twisted
+<matsaleh> all I know is that if he gets any smarter i'm in trouble
+<dash> matsaleh: we already are, i think
+%
+<moshez> glyph: what's Twisted Matrix Laboratories?
+<dash> moshez: the only enemy the PSU fears
+%
+<radix> scripts are just usually short programs that do a very specific thing
+<radix> that's why a lot of us people who use interpreted languages hate it when someone calls our language a "Scripting language" ;)
+<radix> (I mean, look at Twisted and call it a "script" with a straight face)
+%
+<Afterglow> glyth: what's odd is i keep getting a segfault and i don't know why
+<glyph> Afterglow: are you using C?
+<Afterglow> yes
+<glyph> Afterglow: ah. There's your problem.
+%
+<peryklez> should i learn python?
+<moshez> peryklez: no. instead, you should be an anarcho-vegeterian.
+<moshez> peryklez: here, see this channel? Do you ever see us talking about Python? No! Because Python sucks.
+<glyph> peryklez: Yes. You should learn python.
+<glyph> peryklez: Also, stay away from crack cocaine, which moshez is evidently smoking...
+%
+<radix> well, running it works well :>
+<glyph> radix: yeah, but don't ask what it does because it'll KILL YOU WITH
+ITS TEETH
+%
+<dnm_> Twisted tickles my high-level competent software design and concisely functional code that does something useful which was done poorly elsewhere in comparison bones.
+%
+<snibril> radix: i met some _professional_ (or supposed-to-be) admins that had probs even with "ldd"
+<dash> snibril: so, uh.... what did these guys _do_?
+<snibril> dash: ask stupid qs
+%
+<e@ircnet> on the internet the concepts of time and space lose meaning
+%
+<matsaleh> well, maybe we should evangelize a bit...
+<matsaleh> one thing to do would be to convince some kind of public site - techie oriented - to use twisted in some implementation
+<glyph> any ideas come to mind?
+<matsaleh> start small... google? :)
+%
+<radix> I was drinking tea before this job
+%
+<LcModerator:#live> <radix> have you heard of Twisted? Did you know that TwistedMatrix Laboratories is the only feared enemy of the PSU?
+<gvanrossum:#live> radix: I've heard of Twisted and even downloaded his code once, but I couldn't understand one bit of it. Twisted, if you're here, sorry, but that's a fact.
+<gvanrossum:#live> The PSU, of course, doesn't exist.
+<dash:#python> radix: you're a bad, bad boy
+%
+<snibril> guido, when will you stop calling python a scripting language? ;)
+%
+<gvanrossum:#live> zilch: I'm a big fan of wxPython [...]
+<radix:#python> I no longer respect that man
+%
+<gvanrossum:#live> What afro?
+%
+<dash> jenn: you DONT FEEL LIKE PROGRAMMING? what's WRONG with you??
+%
+<Erwin> #python FAQ: How do I build X? A: Wait for twisted.X.
+%
+<thirmite> i'm in the psu!
+%
+<e> most people on irc are professional and shit.
+%
+<dash> glyph: maybe that'd stop, if we stopped denying that the PSU is real and is actually coordina~~4%~~..~*'#n`+>~~.]
+<-- dash has quit
+%
+<rbm> glyph: Now I want to get more to your side of the darkness >:->
+%
+* Nafai will vouch for the fact of glyph's being the master of the obscure
+%
+<churchr> glyph: So why can't you make that into a database?
+<glyph> churchr: I will set you on fire.
+%
+<skreech> I'm gonna kinda miss code red when its gone, my webpage has never gotten this many hits before
+%
+<Acapnotic> garble. if I don't find a twisted.spread example soon, I might try to figure out what .spread is supposed to do by looking at the source directly
+<Acapnotic> which would probably be unhealthy
+<dash> Acapnotic: hey! i've been reading the source for the past month! didididididn't bother me at all!
+* dash giggles
+%
+<Acapnotic> hmm. I wonder what would happen if you fed .bash_history to megahal and then set that as your shell.
+%
+<thirmite> the pull to IRC is so much less now i have my drivers license
+<dash> thirmite: so why are you telling _us_ that? ;)
+<thirmite> dash: who else am i gonna tell? :)
+%
+<thirmite> faassen: it was on the internet
+<faassen> thirmite: don't use the internet.
+<thirmite> i love the internet
+<h3x> pike, the language of your internet
+<Jii> what's internet?
+%
+<glyph> HELP ME SMALL CHILD I HAVE ATTEMPTED TO CREATE A WEB SERVER BUT I HAVE BECOME LOST
+%
+<glyph> I *hate* thinking.
+%
+<Acapnotic> Unlike BASIC, Python doesn't have circle-drawing and paint-fill operations either.
+%
+* glyph returns
+* rik wonders what glyph returns
+<radix> rik: NOT_DONE_YET
+%
+<thirmite> dash: i don't really IRC while drunk *anymore*
+%
+<_pHI_> what is twised.words? and why did i just create an account :) ?
+%
+<churchr> I don't know why you guys want to hurt people.
+<glyph> churchr: money, usually
+<dash> glyph: wow, i can get _paid_ to hurt people?
+<dash> they didn't mention this at Career Day
+%
+<glyph> ddent, the man who was born to program in python, but doesn't
+<dash> glyph: you're thinking of his evil twin, "ndent".
+<shapr> Python as Guido and ndent did.
+%
+<shapr> I am an object!
+%
+<glyph> you know, if I'm going to develop a massive cult of personality, I need to have a better website
+%
+<Inhibitor> this is commercial software - there are no security holes
+<Inhibitor> not like your crappy open source - written by students - stuff
+<glyph> right, I had forgotten
+%
+<e@ircnet> i have been known to occasionally infact say "internet".
+%
+<Yosomono@efnet> The next version of Shapr 0.96 will have integrated Twisted support.
+%
+<Yosomono@efnet> glyph: the colors! the colors! they're burning my eyes!
+%
+* shapr goes into his a capella techno rendition of "mission impossible"
+<shapr> doodle oooo.... doodlee ooo!!
+<radix> doodlee ooo??
+<radix> I don't remember that part
+<shapr> radix: yah, that's at the beginning
+<bitPoet> radix: that's before the duh-duh-duhduh-duh-duh-duhduh part
+%
+<bram> talking about the engineering of p2p apps is like talking about the engineering of red cars
+%
+<thirmite> well the only way i could think of a girl turning me into a vegetarian is by offering me continous sexual favours, but that wouldn't work on glyph because he has some sort of dignity
+%
+<glyph> no land wars in asia or sicilian blood feuds
+<glyph> or threads
+%
+<glyph> it's easy to be dogmatic when you're right and everyone else is an idiot
+%
+<e@ircnet> thirmite: we added window manager support to bridgette.
+<thirmite> e: i hope you're drunk
+%
+<e@ircnet> error handling is important, arguing tha silent failure is ok for "production systems" does not alleviate problems when something goes wrong with "production systems" :)
+%
+<h3x> everybody is left of something
+%
+<glyph> but one person's identity could have multiple perspectives
+<e@ircnet> multiple perspective disorder
+%
+<e@ircnet> glyph: that would make twisted the most buzzword compliant application server platform known to man!
+%
+<glyph> "Fetch me my internet pants."
+%
+<moshez> What is programming, if not fighting a world of idiotic design decisions?
+<moshez> And where can you find design decisions more idiotic?
+<glyph> moshez: landscaping
+<moshez> glyph: hmmm......point.
+%
+<eAndroid> Guido has been on crack for a while.
+<eAndroid> I think he bought some cheap stuff, that's all
+%
+<dash> this feels like saving christmas from santa claus
+ [on trying to prevent Guido from making python less dynamic]
+%
+<e@ircnet> fwiw writing a sexp parser in virtually any language is easier than learning to use xml libraries for that platform.
+%
+* Blackb|rd has been spoiled by years of C and C++ and the hideous exposition to Java 1.0.2
+<radix> Blackb|rd: not "spoiled", "mentally mutilated"
+%
+<glyph> CHECKED IN
+<radix> RUN!
+<glyph> GENERATE CODE!
+* dash runs around in circles screaming, then falls over
+<glyph> INTRODUCE INSTABILITY!
+<glyph> SUDDEN EXIT!
+%
+<adu> i'm a great hacker, but i'm horrible at thinking of things to hack
+<glyph> adu: you are my new best friend
+%
+<dash> wal-mart, purveyor of fine $9.48 chinese keyboards
+%
+<itamar|nyc> think positive thoughst and then cat /dev/urandom > file
+%
+* radix would rather go see glyph than Linus :-)
+<Viiru> radix: Why?
+%
+<dash> radix: you laugh a bit too quickly for someone who's working with a
+project with a business plan based on a pokey cartoon
+%
+<moshez> If I wanted to code with syntax highlighting, I'd just take LSD. 'My, what a green comment'
+%
+<dreid> twisted can do pretty much anything if glyph gets drunk enough
+%
+<deltab> glyph: there's something strangely fitting about being able to "from internet import delay"
+%
+<TQuid> Jesus shit. Is there anything twisted doesn't do, or at least doesn't intend to do?
+<dash> tquid: XML.
+%
+<Acapnotic> What do you get out of writing docstrings if you can't confuse, mislead, and infuriate your audience?
+%
+<gt3> i thought i had mono once for an entire year, turned out it was cuz i was using Perl
+%
+<dash> det: our chief weapons are misinformation and asynchronous networking
+<det> dash: at least you can deliver it at maximum effieciency
+%
+<dash> (breaking encapsulation for fun and profit since 1998!)
+%
+<dash> bask in the rosy glow of my ignorance
+%
+<Tv> So, now there's my way, a simpler way, _and_ the correct way? I'm getting confused.
+<Tv> Back when I was a youngster, there was just my way and the correct way :)
+%
+<Yosomono@efnet> glyph: you're telling me I'm 6 months behind you?
+<Yosomono@efnet> glyph: that makes sense, considering the time lag between film releases in the US and Japan =)
+%
+<Yosomono@efnet> Twisted: Bring Out Yer Dead (Paradigms)
+%
+<Yosomono@efnet> Fuck, what's this world coming to?
+<thirmite> yosomono: obviously something less than good.
+%
+<Tv> Mwahahaa!
+<Tv> I can encode and decode arbitrary ASN.1 structures :)
+<e@ircnet> get help
+%
+<wondr> ever since they moved over to twisted google has seemed a little bit flaky
+%
+<itamar|nyc> twisted is what medusa should've been, I think
+%
+<moshez> glyph: yes, TCP connection forwarder is good.
+<illume> why not twisted.internet.tcp_forwarder then?
+<moshez> illume: because I wanted to use the word "stupid" in code.
+%
+e2d2 (~erno@2002:d432:8efa:0:0:0:0:1) joined on ircnet
+<e2d2@ircnet> internet 6!
+%
+<dash> web in my head get it out get it out
+%
+<radix> yosomono: One of these days, I'm going to actually see what you do
+<Yosomono> radix: You will turn to stone almost immediately
+%
+<mothra> i'm not sexist, women are just a pain in the ass
+%
+<eAndroid> win still has fork though right?
+<jepler> no
+<eAndroid> hmm. no wonder my daemons don't work
+%
+<jedin> Know any good informational/instructional sites on Prolog?
+<glyph> kill yourself now
+<jedin> But I just vacuumed!
+%
+<redoz> 2 years?
+<glyph> redoz: I've been working on twisted for a while
+<redoz> apparantly
+<chrchr> glyph: Most people in this channel haven't even been _alive_ for two years.
+%
+<glyph> BLOCKING OPERATIONS ARE NEVER VALID!
+<glyph> HAVE YOU SET YOUR SOCKET'S BLOCKING FLAG TO ZERO SMALL CHILD??
+%
+<shapr> I've never used a small child as a flag.
+<shapr> not even once.
+<glyph> shapr: MAKE SURE YOU BRING LOTS OF STAPLES!
+%
+<mothra> cars have the same beauty of form as women, without the nagging
+<glyph> mothra: maybe your issues with women stem from that misunderstanding
+<glyph> mothra: to start, women are *soft*, whereas cars are not
+%
+<radix> so are you coming to IPC10?
+<dash> if you, me, moshez, and glyph end up in the same room though, we may
+assemble into a giant robot and lay waste to virginia
+<dash> and that's always inconvenient
+%
+<mbac> would it be foolish of me to wish java banished to the depths of hell and in it's place is python?
+%
+<Rainy-Day> dash: i think you know what it means but for odd reasons make it
+look like you don't :P
+<radix> Rainy-Day: he does that a lot
+<radix> :>
+<Rainy-Day> yeah
+<Rainy-Day> it's annoying as hell!
+<dash> Rainy-Day: is it really?
+<Rainy-Day> yep..
+<dash> my plan has succeeded!!
+%
+<bitPoet> all of twisted is probably like 3 lines of apl
+%
+<radix> thirmite: we're not a 3rd world country. =)
+<dash> radix: not this week anyway
+%
+<glyph> let's have some more corporations
+<glyph> then we can absolve all individuals acting on their behalf of
+responsibility and collude with the government to steal money!
+<dash> glyph: YES! where do i sign up?
+<glyph> dash: www.microsoft.com, look for "passport"
+%
+<jafo> If java had real garbage-collection, it would delete most programs
+before it executed them.
+%
+<dash> (hacking implies the use of an edged tool, java isn't sharp ;)
+%
+(context: http://yellow5.com/pokey/archive/index76.html)
+[glyph] pokey's taste for the cereal reminds me of my own preference for python :-)
+[glyph] "GLYPH THEY ARE USING WOOD GLUE AS AN OBJECT MODEL!"
+[glyph] "I WANT ANOTHER INSTANCE"
+%
+<liiwi> hrmpf. python compared to to perl is like c++ compared to c
+<dash> liiwi: so, which are you implying? that C++ is a good thing, or that
+python is a bad thing?
+<dash> liiwi: either way we have to kill you, i think
+%
+<radix> It's gonna take a lot of effort ripping reality apart
+<glyph> it's going to be almost as hard to stand idly by while you do so :-)
+<radix> I kill you!
+<glyph> no, you kill my CODE :-)
+%
+<Rainy-Day> no? jesus was like, love thy neighbour and shit
+%
+<spiv> NeuroMorphus: That's not really meaningful, though.
+<NeuroMorphus> spiv: it's not a matter of meaning, it's an assignment
+%
+<radix> man, Rune better kick ass
+<radix> this demo I'm downloading is *90MB*
+<radix> games are so huge these days
+<radix> In my time a game that filled up a whole 1.4MB diskette was big!
+<Erwin> You know what else I noticed? Today's 21" monitors are bigger than
+yesterday's 14" monitors :)
+<radix> bah :)
+%
+<bitPoet> the full name of the enterprise is probably something buzzword-
+compliant like "scalable enterprise java interspacial XML warp drive", it's
+just "enterprise" for short :-)
+%
+<dnm> Someone quote me already.
+<dnm> I'm trying to plithy.
+%
+<skreech> some say there is documentation in them there hills
+%
+<Acapnotic> "Required course materials: 1 copy of 'Java and You', an installation of JBuilder+, and a HID vomit-proofing kit for each workstation you will use."
+%
+<glyph> jafo: Are you ircing as you *DRIVE*!?
+<dash> glyph: well, duh
+<dash> glyph: cant pull over every time you want to say something
+%
+<glyph> h3x: so... you're a professional extortionist?
+<h3x> pretty much
+<glyph> h3x: do you offer professional apprenticeships?
+<h3x> i should
+<dash> glyph: gah, you beat me to it
+%
+<glyph> dash: So while we're on the subject, are there features you feel the PB protocol lacks at its lowest level that you might find useful?
+<Acapnotic> (Like the "YOU FUCKED UP AN SUBCLASSED THE WRONG THING, MORON!" feature? :)
+<glyph> Acapnotic: If python had decent metaclasses, ViewPoint would scream profanity at you personally, but until that time, I'll have to do it by proxy.
+<glyph> Acapnotic: Do you have a phone in your house? ;-)
+<Acapnotic> glyph: Yeah, it's right by the stairs. Why do you ask?
+%
+<itamar> stuouid keyboard hates me
+%
+<Nanosecond> And BTW, we taliban guys use Macs. All of us.
+%
+<Gand> <sigh> ... first day as a python programer and already I have to start writing my own functions ...
+%
+<Acapnotic> My computer is playing reggae out of thin air!
+%
+<e@ircnet> pcmC0D0c pcmC0D0p pcmC0D1p pcmC0D2c pcmC0D2p
+<glyph> e: are those the lyrics to some weird finnish music?
+<e@ircnet> glyph: not yet
+%
+<dash> guess there's a fine line between "tilting at windmills" and "hitting the fan"
+%
+<thirmite> what's a web widget??
+<glyph> thirmite: internet on a stick, on fire
+<Acapnotic> with web sauce!
+%
+<thirmite> bea: how are you?
+<bea> thirmite: not bored
+<thirmite> then why are you on IRC? ;)
+%
+<Acapnotic> something's wrong, none of the tests failed
+%
+<Tenshihan> then where does modular programming come from?
+<dash> Tenshihan: the lesser magellanic cloud
+<glyph> dash: the origin of the modular programming technique is classified!
+%
+<Rugal> Do I have to study something else in order to use twisted? i mean, is
+twisted to python how C++ is to java?
+%
+[Just another night in #Python... -ed]
+* X86BSD-H throws a ball of yarn in front of rik
+* rik watches it bounce past
+* X86BSD-H needs to get a G4 PB
+* dreid beats system-wide fetchmail with a stick
+* rik looks at X86BSD-H
+%
+<radix> but a year of python programming is like 5 years of C programming
+<radix> because in those 5 years of C programming about 4 of them are dealing
+with memory management
+%
+--> bdash (mark21rowe@chch-d109.connections.net.nz) has joined #python
+<Deep6> oh no! its the lower grade dash!
+%
+<glyph> dash: let me put it another way -- I will upgrade to 2.2, if for no
+other reason than to bitch on clp.
+%
+<det> pokey reminds me of yosomono
+<det> except on drugs
+%
+<radix> it's kind of interesting to think about twisted philosophically
+<radix> it's basically a bunch of APIs layered on top of each other
+<radix> each one making a task easier to do
+<dreid> until finally trained monkeys can do it
+<dreid> "Twisted, the framework of a million monkeys with typewriters."
+%
+<radix> rep's just this happy little lisp
+<radix> and CL is the giant living on the mountain
+<radix> rarraa I'm 8MB!
+%
+<radix> man, everyone else has cool programming fathers but me
+<radix> I'm going to be a cool programming father to a kid some day
+<dash> radix: i'm going to have a lot of kids and teach them all to play quake.
+<dash> radix: we'll be the best clan in the state.
+%
+<glyph> oooh
+<glyph> OOOH
+<radix> oh shit
+<radix> glyph just had an idea
+%
+<glyph> It's interesting that people often say "Hey, I'm looking for
+something to work on!"
+<glyph> then someone else says "Glyph's code needs a little help." then
+the original asker says "SWEET MARY MOTHER OF GOD I'M NOT TOUCHING THAT!
+I mean, uh, that's too much work or I'm not good at it. Or something."
+[...]
+<tpck> You want me to read and understand and then rewrite a 795 line
+piece of code that contains doc strings like "WARNING! This source code
+for this method may cause your eyeballs to melt."
+%
+<itamar> ok, it's JAVA TIME FOR BOYS AND GIRLS
+%
+<itamar> actually, I don't have the patience for java right now
+%
+<sayke> Acapnotic: don't make it twisted-specific
+<dash> sayke: pffft
+<dash> sayke: twisted isn't specific
+%
+<tpck> merriam-webster is nothing to me
+%
+<kingkill> glyph: i was under the impression you didn't like twisted
+%
+<amien@efnet> ? swing is bad? :)
+%
+<e2d2@ircnet> don't use threads :)
+<glyph> e2d2: it's java... you don't really have an option
+<e2d2@ircnet> don't use java :)
+<glyph> e2d2: sage words
+%
+*** Signoff: glyph[Ping timeout for glyph]
+*** glyph joined channel #python
+ * Nafai wonders if glyph is really there
+<Yosomono> He's never really been "all there"
+<Acapnotic> glyph is never *really* there, but sometimes the probablitiy
+becomes high enough that he influences internet.
+<Yosomono> He's like an electron cloud.
+<Yosomono> You can't really tell where he is at a given moment, just a
+probability.
+<Yosomono> Also, you can't tell both where he is AND how much coding he's
+doing at the same time.
+<Yosomono> This is the Glyphenberg Uncertainty principle
+%
+<glyph> I probably shouldn't think of it as an accomplishment that I manage
+to cancel all of my social- and entertainment-oriented engagements on friday
+night so I can work
+%
+<flippo> When programming languages started using four-letter names, APL was
+doomed.
+%
+<datazone> glyph: you are stupid
+%
+<radix> continuations make me want to hurt you, dash
+<dash> continuations made me want to hurt a lot of people
+%
+<radix> you're a pragmatic bastard, dash :)
+%
+<mbac> strange
+<mbac> all my life i've hated object oriented programming
+<mbac> when the problem was simply that i was using C++
+%
+<MoonFallen> how well does twisted work with xml?
+<radix> MoonFallen: PUT YOUR FACE INTO THE JELLY
+%
+<radix> MoonFallen: well, there is a very simple xml-rpc implementation
+<radix> MoonFallen: but we generally don't like to talk about it
+%
+<Acapnotic> When you're holding an automatic weapon, a remarkable number of things become your choice.
+%
+<dash> if perl is a swiss army chainsaw, this is a dynamically reconfigurable
+nanosword
+%
+<radix> skreech: hey guess what!
+<skreech> what
+<radix> skreech: exciting night tonight
+<skreech> radix: women?
+<radix> skreech: twisted release! =D
+<skreech> radix: YES!!!!!!!!!!
+<radix> hee hee
+<radix> I know you live for these moments, skreech
+<skreech> VROOOM
+<skreech> Lemme get my Twisted-Release-socks
+<skreech> and noisemakers
+%
+<glyph> backinasec,Ibrokemyspacebar
+%
+Let the record show that on Saturday, November 24th 2001, at 8:38 PM UTC,
+Glyph Lefkowitz did speak thusly:
+
+ "OK. I have a crack-laden idea.
+ or perhaps a crack-destroying id
+ Deferreds are confusing as hell
+ Let's just use threads."
+%
+<radix> I HATE METAPHORS
+%
+<dash> wow. this code does something highly entertaining, but nowhere near correct
+%
+<skreech> I can feel my brain
+%
+<nikon_> i want to live in a country thats run by beautiful large breasted women
+%
+<itamar> def revenueGenerator():
+<itamar> yield cash
+%
+<mothra> one day i want my life to be so automated that getting out of bed will
+be a configuration option
+%
+<moshez_> I love portraying prejudices
+%
+<skreech> its only 10:40pm here
+<skreech> everyones going to *sleep?*
+<Intention> I am staying up! There is much to read on the web.
+%
+[in regards to http://www.askemos.org/]
+<glyph> he is *completely* insane :)
+<dash> glyph: yes
+<dash> glyph: i hope he IRCs
+<dash> this seems like a person i could hurl abuse at for hours
+%
+<e2d2@ircnet> verwilst: debugging is easier when you read the error messages :)
+%
+[dreid] i'd like to learn Forth at some point also
+[dash] save it for last
+[dash] forth has the power to destroy minds
+[dreid] sorta the snowcrash of programming languages?
+%
+<moshez_> dash: I'm a very nice man, except in hypothetical situations
+%
+<Intention> Moral: HOORAY FOR PYTHON. IT CAN GET YOU LAID.
+%
+<glyph> steve: Are you the creator of the Grease(TM) Plan for Internet Success?
+<steve> glpyh: i'm just a vessel for Grease
+%
+<Intention> Twisted did raise me from the dead after two weeks. It is a miracle
+ of software engineering.
+%
+<Kuja> Wow twisted can do all that.
+%
+<radix> Thain: I think the point is that you'd be hacking C code, not python
+<Thain> but c is easy...what's your point?
+%
+<chrchr> dsmith: Twisted is neat, but unfortunately, it's not object-oriented.
+%
+<datazone> twisted is madness
+%
+<skreech> glyph: SUDDEN INTeRNET!!
+%
+<Pahan> hunter2: RedHat is an evil distro of death! How could you not know this?
+<hunter2> Pahan: um, as a former employee and current stockholder, I probably
+didn't know due to brainwashing. :)
+<Pahan> hunter2: Oh.
+%
+<itamar> jail time and 50K fines are great marketing tools
+%
+<mcc> My justification for java's existence is "it's not quite as bad as c++"
+%
+Broadcast Message from carmstro@zaibach
+ (/dev/pts/21) at 2:24 ...
+
+who needs IRC when you can w4llx0r
+%
+* the internet
+%
+<itamar> DIE
+%
+* dash holds up his "WILL WRITE PROGRAMS THAT WRITE PROGRAMS THAT WRITE PROGRAMS FOR FOOD" sign
+* Nafai holds up his "WILL FOLLOW AROUND dash TO WRITE PROGRAMS THAT WRITE PROGRAMS" sign
+* Nafai holds up underneath a sign reading "HOPING HE MAY ACTUALLY LEARN SOMETHING"
+%
+<comatoast> hm, you could join #artois on DALnet if you're interested in making
+a version of C++ that doesn't suck
+<dash> comatoast: uh
+<dash> comatoast: i am experiencing extreme cognitive dissonance
+%
+<resolve> we are the freedom police! you must stop this happyness right now.
+%
+<jafo> I used to hang out with this chick that ran a BBS.
+<jafo> She had a great baud.
+%
+<__funky__> so where's the real python channel?
+%
+<internet> e
+%
+<glyph> Actually, they all need for Twisted. They burn for it in the very
+core of their souls, like a vampire's thirst for blood. Programmers NEED
+twisted; existance without it is a pale shadow of the righteous glory that
+the Twisted hacker can achieve.
+%
+<chrchr> I'll like anything for money.
+%
+<thirmite_> radix: you dropped out?
+<radix> thirmite: yeah.
+<thirmite_> <GenericBoy> yay i am at college;<radix> #python has made me cynical
+i hate life
+%
+<sayke> dash: i wasn't sure what to call the system daemon/service/kernel
+module/things, so i called them "gods" and made them into a pantheon. i
+then made a creation myth as a metaphor for the system boot process, which
+i combined with a programming-as-magik analogy to form a user interface
+vocabulary roughly reminiscent of, well, crowleyian wizardry.
+<dash> sayke: you are a special and unique person
+%
+<itamar> I want to kill someone
+<glyph> Why?
+<itamar> java
+%
+<moshez> "I give thy soul to the gods of the web, may they take this offering and grant us sane protocols"
+%
+<moshez> the only reason to get a life is to get a girl
+<moshez> I'm hoping to get a girl without the seemingly mandatory life thing
+<itamar> yeah? how?
+<moshez> itamar: no specific plans
+<moshez> just random hopes ;-)
+%
+<Intention> KRIS KROSS'LL MAKE YOU LONGJMP SETJMP
+%
+<glyph> You should want me dead, you'll get all my stuff.
+<cyli> I don't want you dead -- I get all your stuff anyway.
+%
+<thirmite_> if you had 100k to spend on an engine why would you make a game? :)
+<radix> thirmite: so you can make a million dollars off of it
+<thirmite_> radix: i'd still rather buy a dedicated server in the US that did
+nothing but email dash spam on c++
+%
+<dash> i remember those days.
+<dash> the world was cold and without hope....
+<dash> twisted had not been released yet.
+%
+* dash feels the idea "3d postgres-db visualisation with twisted, pyopengl, and pygame" waft through his brain
+<glyph> dash: uh-oh, you've caught the asbahr wave
+%
+<itamar> who is megahal?
+<itamar> does he do bar-mitzvahs?
+%
+<radix> I'm fighting a huge cat with breasts
+<spiv> radix: Congratulations. I think.
+<e> i have trouble imagining how you fight with breasts
+%
+<sayke> i moved left, [the cow] moved left. i moved right, [the cow] moved
+right. i yelled "WHY ARE YOU IN MY WAY? MOVE!!", and waited a second for it to
+concoct a reply. when none was forthcoming, i dropped into stance and kicked
+it in the nose.
+%
+<dash> i can think of ways to do it but they're mostly evil. what are you doing?
+%
+<thirmite> i want one of those jobs where you get people out of cults
+- pause -
+<thirmite> by blowing up cult headquarters
+%
+<glyph> I love the fact that there's apparently a text-based Tribes-2 deathmatch going on interspersed with the argument though.
+%
+<Darkvise> I guess you could say that Windows and Linux are like two different chicks. Windows gets along with most people and it knows how to party but she's been with so many guys that you dont know what virus she might be carrying, and Linux could be some nerdy chick who may not seem that attractive on the outside but she's not shallow and braindead like Windows.
+%
+<tenth> One of our prospective clients has been asking about using MSSQL for his database
+<tenth> He can use whatever he wants. MSSQL just isn't currently supported. (So if it wakes him up in the dark hours of the morning with shrill, piping calls and cries of "Yig! Yig!" and immerses him in sanity-shattering cosmic horror, which MSSQL 6.7 has been known to do, he can't call tech support about it.)
+<tenth> "Okay... What version of BusinessMind are you using? Good... okay, what database are you using? Hmm... Well, what does it say at the top of the window? Is it a red border, or a blue border, or a shimmering band of tones and shades seeming only barely within the reach of human eyes, both confusing and terrible to look upon? Colour Out Of Space? yeah, sorry, we don't support that one. You should get MySQL."
+<glyph> Warning! Kill songs unsung while still unheard [y/N]?
+<tenth> "Please enter the number of songs you wish to kill (up to the maximum displayed next to the field) and click the Yellow Sign to continue."
+<tenth> BusinessMind For Those Who Cannot Be Named
+%
+<glyph> *whew*
+<glyph> took the call and emerged testicles intact.
+* dash points out to glyph, needlessly, that he has issues
+<radix> dash: I think they're calling them "women" these days
+%
+<johs> Oh, please. Threads ownz j00.
+%
+<ThreeSeas> maybe it's be easier if I used te metaphor of the matrix characters?
+<dash> ThreeSeas: no
+%
+<chrchr> radix: A software engineer is somebody who can extend a system without reading any code.
+%
+<glyph> funny. I'm looking at twistedmatrix.com right now and the most recent
+version is still 0.13.0
+<radix> :P
+<radix> glyph: find a QOTR
+%
+<glyph> dash: Isn't "efficiency" supposed to be your department? :)
+<dash> glyph: "crack" is my department.
+ * radix gets depressed because his department is "bitch"
+%
+<radix> What the hell was I thinking?
+<dash> radix: get used to that feeling
+<dash> that feeling is called "design" ;)
+%
+<Nafai> Wait. I think I got it to work!
+<Nafai> YAY
+<Nafai> w00t!
+<Nafai> Houston, we have a contact manager!
+<glyph> Nafai: austin.
+%
+<itamar> two more webmonkey days, and then I'm off to the USA
+* shapr hands a web-banana to itamar
+<desaster@ircnet> my god, the banana is full of ads
+%
+<cheeser> i think the general method of developing address books is to write a random number generator and use that as input for any decision making.
+%
+<glyph> e: look upon my work, o kings, and despair!
+* e@ircnet viewcvs'es
+<glyph> e: aren't you going to "viewcvs and despair"? ;)
+<e@ircnet> i will despair once i see it.
+%
+<e@ircnet> when i die i want to be dried into a scary looking dried up corpse and be used to scare young children
+%
+<Acapnotic> he just logged the fact that he got r3wt0rz3d
+<radix> he's on windows
+<radix> it comes pre-rewted
+%
+<itamar> we should lock z3p up in a protocol factory
+<z3p> self.factory.stopFactory(); self.factory.letMeEscape()
+%
+<pjarks> today is a good day to install zope
+%
+<faassen> I mean, geez, the guy thinks there is a conspiracy of programmers! a conspiracy
+related to programming!!
+<faassen> who'd have ever thought of that? :)
+%
+<radix> I am just the bombest dude in the world
+%
+"Alarm Sounds Like" -- Whoop Whoop
+%
+<noa> did anyone cause the alarm to go off just to see what it sounds like?
+%
+<ZC-Matt> You you *can* take an unwrapped object and stash it in a C module, poised to leap out at any unsuspecting transaction that wanders by.
+<zigg> ooo, above my head.
+<JimFulton> mine too. ;)
+%
+--- ChanServ gives channel operator status to dash
+<dash> magc35us: you've got 30 seconds to be witty, relevant, or at least apologetic
+%
+<Intention> How EXACTLY are cameras used to keep planes from hitting skyscrapers? Do they have laser attatchments?
+%
+<draukuWORK> moshez, i signed the zope contributors agreement today... there goes my first born
+<draukuWORK> or any first born i may borrow
+%
+<dash> glyph: go to #lisp and ask about relative pathnames. :)
+<chrchr> dash: Don't make him do that.
+<dash> chrchr: he knows better
+%
+<Nafai> What to do, what to do.
+<Nafai> No class at all next week!
+* glyph gets the "documentation" hat and starts running after Nafai
+<Nafai> AHHHHHHHHHHHHHHHHHHHHHH
+* Nafai jumps on the snowboard and takes off
+%
+<resolve> hah! java is the jerry springer of computer languages
+%
+<matju> chrchr: the web will allow us to metaparadigmatically outpace
+innovation beyond the future
+<matju> chrchr: that's why it's so revolutionary
+%
+--- shapr is now known as world
+<world> hello
+%
+<glyph> so we need to target this website to three groups -- end users, corporate shills, and open source developers
+<dash> and unfortunately JavaScript is not advanced enough to determine which is which.
+%
+<exarkun> I think there's a rather large difference between a stale twinkie and a kernel swap daemon
+%
+<stranger> ok i think i need a polymorphic language with continuations and closures to write this properly
+<stranger> should I give up and implement in C?
+%
+<yosomono> When I was done with my first test gtk app using twisted, my first thought was "is that it?"
+%
+<thirmite> srbaker: www.twistedmatrix.com - a framework for building
+asynchronous network based apps
+<dreid> in other words, doing cool stuff with little work and even less
+documentation
+[*ahem*, hopefully not for long --ed]
+%
+<glyph> if you've ever dealt with MS, it's like dealing with ... well, germany.
+<glyph> it's big and not everybody agrees on everything
+%
+* skreech runs mothra over in his shrike.
+* dreid rushes to an inventory station and grabs a sniper rifle
+<resolve> hey skreech, i just went and made myself some lunch, and you're still doing that. :) i think it's time to stop
+%
+* stampy tosses a fruitcake mortar skreech's way
+<skreech> NOT FRUITCAKE
+* stampy sprays skreech with napalm eggnog
+* skreech loses control of his shrike.
+<skreech> MY EYES
+* skreech ejects
+<glyph> dash: it's a performance art version of t2, I think
+%
+* dreid hands skreech a chaingun and dash a spinfuser
+* skreech jumps and jets.
+* Novas007 picks up a mortar
+* dash tries to work out which end to hold
+<skreech> dash: raaaatatatatatatatatata
+* moshez gets a radioactive spider to bite him
+<moshez> yay! I have spider-powers
+* Novas007 flies up to the nearest high place and begins raining mortars down
+<dreid> hah
+* skreech blows up.
+* moshez uses his spider powers to help human kind.
+<skreech> Shazbot!
+<dreid> "damn lag!"
+%
+--- dash has changed the topic to: from enemy_base import flag
+%
+* skreech throws a satchel charge in the middle of the channel.
+* dreid hides behind a generator
+<skreech> lets argue.
+%
+<skreech> THE SENSOR NETWORK IS DOWN
+* skreech pilots his shrike into the side of #python
+%
+* dreid fires his spinfuser at skreech
+<skreech> BAM!! Glyph's body flys across the map after being hit by skreech's shrike going 355kph!
+* skreech avoids various heat seaking missiles launched by mothra.
+<skreech> dreid's disc hits skreech's shrike and sits it veering into a hill.
+<skreech> Nooooo!
+* skreech 's shrike flips upside down.
+<skreech> EJECT EJECT
+* skreech 's shrike explodes in a fiery ballness of flame.
+* dreid starts saturation bombing of the area where skreech's shrike crashed
+<skreech> AAAAA
+* skreech dies.
+<dreid> skreech: :)
+<dreid> gg
+%
+<Acapnotic> Ooh, I just figured out what my first twisted.reality creation will be.
+<dash> Acapnotic: oh?
+<Acapnotic> "Being Glyph Lefkowitz"
+%
+* Intention enjoys very much being able to keep programs, editors, photo editors, and games runing for a week or more at a time without fucking up or crashing or making everything else slow. God bless younicks.
+<Intention> I never had even concieved of forgetting that programs were running until unix. Now it is like.. erm..
+%
+<spiv> Apparently my company used to be a Linux company, many years ago.
+<spiv> The website consisted of Perl CGI scripts serving stock data.
+<spiv> We moved to Windows because someone couldn't figure out how to give our customers case-insenstive website logins.
+%
+<radix> glyph: so, tell us about the trip!
+<radix> did you have fun?
+<glyph> radix: It was awesome. Sin is the best thing ever!
+%
+<radix> xihr: while moshez is indeed completely insane, he's not much of an ass-talker
+%
+<fooz> oh, mozart is "write once run anywhere" like java
+<fooz> that means it probably won't work on any platform I care about
+%
+<liiwi> moshez: gotta squish radix to do 0.15.5 soon
+(oh, my god, it's spreading - Ed.)
+%
+<Aardappel> this "I hate c++" is so old
+<dash> it's as old as C++, yes
+%
+<Blue> glyph: USE TWISTED
+%
+--> moshez (~moshez@p9.j3.actcom.co.il) has joined #python
+<itamar> look, it's moses!
+--- ameoba is now known as redC
+* redC parts
+%
+<itamar> Lesson of the day: you can't test the win32 event loop if you're not running the win32 event loop
+%
+<Yosomono@efnet> radix: It looks pretty disturbing when you see a bunch of people beating the shit out of a leprechaun who has arrows sticking out of his head
+%
+<glyph> I am tasting the pepperoni-pizza-combo flavored taste of independence.
+<matsaleh> don't let it go to your head
+<glyph> well, I still have a very strong sense of "I could crash into any of these objects at any time"
+<glyph> I figure as long as I hang on to that really tightly, I'll be OK
+<matsaleh> probably a good plan
+<matsaleh> one word of advice tho
+<matsaleh> don't drive for at least 1 hr after playing any FPS
+<matsaleh> everything looks like a power up
+%
+<exarkun> twistedmatrix.com looks a lot different in netscape than it does in links
+<exarkun> I suddenly have a much higher opinion of twisted
+<exarkun> before I thought it was all garbage. now it is all garbage with a great web page
+%
+<radix> ViperCA: you can make good websites without doing stupid shit, you know. :-)
+%
+<Pahan> foot.get_owner's_gun_through_obscure_meta_tricks.shoot(self)
+%
+<TheJester> .seen god
+<xena> God seen changing nickname to God_|Away|PersecutingAtheists ~ 52 day(s) 4 hr(s) 32 min(s) 58 sec(s) ago
+%
+<itamar> we're ripe for a syndicalist-anarchist revolution ;)
+<radix> yay!
+<radix> do you guys have a lot of those?
+%
+<radix> the sysadmin of the future is going to know twisted-shelling like the back of his hand
+%
+<Donatien_Alphonse> :) no promises - the truth may be a star, but we have a
+proper motion relative to it. Oneof my favorite quoites - a wise man I knew
+once said "Honor is truth in motion."
+<glyph> Donatien_Alphonse: A wise man I once new said "I invented the hippo!"
+It's not always best to live by the words of wise men.
+<stranger> Donatien_Alphonse: i'm beginning to think wise men should keep
+their traps shut :)
+%
+<Intention> Java and Squeak are sort of similar. They are both superdynamico and have their own widgety things and run in a VM. [Squeak] has way more colors though.
+%
+<skreech> no matter what, when I come back to my #twisted window theres always 'squish' somewhere
+%
+<red_one> hm
+<red_one> is there a python that's statically typed?
+<exarkun> red_one: the south american red python is of static type
+%
+* StevenK starts to plot a drive to Belgium, but gets stuck.
+<StevenK> Damn ocean.
+* moshez starts to plot a drive to Belgium, but gets stuck.
+<moshez> Damn arabs.
+%
+<Intention> radix: Once upon a time, I truly GOT C++. This profound body of
+knowledge was so complex, it formed a separate personality in my head just to
+DEAL with the complexity without killing me. So every once in a while, when truly
+troubled, I flip to that personality. When I come back, I have no idea what
+happened. It's NIRVANA.
+%
+<moshez> dash: you should go back for completions of logic...
+* dash points at his shirt
+<dash> "AUBURN GRADUATE (PAID)"
+<dash> i've done all the learning i'm ever going to do
+%
+<moshez> Nafai: I once met a girl on a bus. She told me her name was Li. I proved to her Aleph null is less then 2 to the Aleph null. She gave me her phone #.
+<itamar> what does her name have to do with it?
+<moshez> itamar: Lie groups.
+%
+<d1ver> python programmers?! it's not even a computer language - it doesn't even support proper tail recursion!
+%
+<skreech> radix: apparantly, in stories, chinese ISPs have responded to being blocked by the rest of the world with "take block off"
+<skreech> take off every block for great justice!
+<skreech> someone set up us the packet filter
+<skreech> <zig>
+%
+<dash> BardCat: so. what's communism?
+<BardCat> dash: It's when a boy and a girl love each other, and then there is a cabbage and a baby!
+<dash> BardCat: wait
+<dash> BardCat: i thought that was syndicalism
+%
+<gt3> perlsucks?yes:wtf_yes_it_does;
+%
+<hmmm-@efnet> sitting here seeing stuff like <ry> <exarkun@opn> really makes me feel like a minion talking to his gods :P
+%
+<dash> blag
+<dash> let's finish all this 'twisted' crap so we can write some fun stuff
+%
+<skreech> WTF. The sf.net skill profile does not have a skill for "molecular biology"
+%
+<dash> moshez: you aren't making sense now
+<moshez> dash: *now*? I'm not making sense *now*?
+%
+<stranger> Hey! I've got an idea: <byte><bit value=1/><bit value=0/><bit value=0/>....</byte>
+%
+<etcha@efnet> btw whats ry>? is it a kind of irc gateway?
+<e@ircnet> it's a bit like a mind flayer, except it also relays messages.
+%
+<Pahan> Damn, I threw a horrible insult, and got no wise-ass retorts.
+<sayke> Pahan: i was just going to say "ask me about my apathy"
+<dash> sayke: he doesn't care about your apathy.
+%
+<shapr> man I had a radix quality dream
+<shapr> it was about this guy who found a dinosaur preserved in ice, and removed its stomach, and surgically altered the stomach to be able to survive in lake awter by itself
+%
+<gt3> i had a dream guido really did get hit by a bus
+<jafo> :-(
+<jafo> He's a nice guy.
+<gt3> then somehow twisted came standard with it after dash took over
+<gt3> he seems nice
+<gt3> but nice doesn't stop a bus
+%
+<skreech> How do I keep people from reading my Perl code? Oh wait. Ha ha!
+%
+<Acapnotic> I care not for your somnable teeth. I wish only to master the multipart/form-data
+%
+<Tenshihan> why do drugs make us commit so many crimes?!
+%
+<gt3> programming should be an adventure, those damn college courses make it so its like yer joining the navy seals so you can work at sea world as a whale feeder
+%
+<Jii> "internet with python" spells twisted
+%
+<Qelf> Did you doods find it hard when you 1st started?
+<dash> Qelf: well sure
+<dash> Qelf: in fact i would characterise my programming education as being in a state of near-permanent confusion
+%
+<ElectricElf> infinity: M-x font-lock-mode
+<ElectricElf> infinity: You can set it do be on by default, but that requires
+ editing a file somewhere and I can't remember which nor what to
+ add ;)
+%
+<shapr> so, where do I buy stock in glyph? ;)
+%
+* hmh looks at unmime.c in fetchmail and cries in agony
+<moshez> hmh: eh? what would a fucking MAIL DOWNLOADER be doing with mime?
+<hmh> moshez: being too fucking smart for its own good.
+<hmh> moshez: in a very dumb way, too.
+%
+<radix> I can switch screens like none other!
+<radix> look! I just switched!
+<radix> and again!
+<radix> wee!
+<itamar> wow
+<itamar> I no longer feel bored
+<itamar> compared to you, my life *scintillates*
+%
+<glyph> yo ho ho and a bottle of internet
+%
+<kosh#zope> sorry no games are worth what xp costs in terms of the freedoms removed
+%
+<Nafai> Dang. sendmail ain't working all the sudden
+<exarkun> "all of a sudden"?
+<exarkun> Nafai: where have you been for the last decade?
+%
+<dash> the primary function of the human brain is to make witty remarks on irc
+%
+<radix> i've gotta move to one of those socialist countries and become a school-bum like princepsz
+<HappyFool> please. 'professional student', not 'school-bum'
+%
+<radix> Like, imagine sitting around with your Marine buddies in your transport spaceship, going to Mars, getting rowdied up for the battle with the space aliens
+<radix> and then you get there and a thousand marines pour out of the ships and meet a horde of 10,000 imps
+<dash> radix: AND YOUR FRAMERATE GOES IN THE TOILET
+%
+<exarkun> crack attack is life
+%
+<flippo> I was reading a book about C++ templates today, then I glanced at a preview of the Python cookbook, and I thought my ears would explode from the change in pressure.
+%
+<glyph> skreech: you think "Acquireable" is hard to spell? ;-)
+[ed: dict acquireable]
+%
+<faassen> moshez: consistency's hobgoblin has a little mind!
+* dash dubs moshez "consistency's hobgoblin"
+%
+<Overfiend> bwa ha ha ha ha
+<Overfiend> 03:28AM|<moshez> what I like about Manoj is his desire for
+ simple and small solutions. like EMACS. or dvt.
+<Overfiend> 03:29AM|<Manoj> well, dvt was _supposed_ to be simple
+<Overfiend> 03:29AM|<Manoj> it only took 2 weeks to write
+<Overfiend> that's just a classic exchange
+<Overfiend> "Well, it's really quite simple if you conceive of it as a
+ partially bounded n-dimensional manifold where n is the factorial
+ of the number of ballot options"
+%
+<TQuid> Twisted blows my mind so severely I want desperately to do
+something with it, yet I don't know what.
+<TQuid> You read about it and it's like "twisted will shortly assassinate
+Bill Gates, reformulate intellectual property law to make both BSD and GNU
+fanatics happy, and also make you a nice grilled-cheese sandwich."
+%
+<hmh> moshez: I know ESR thinks he is a god of sex, and I know his signal
+ handling code says otherwise...
+%
+<II-V-I> subliminal message: python is good
+<Yosomono@efnet> subliminal retort: damn good
+<sun> subliminal antagonism: have you tried Ruby?
+%
+<spiv> Imar: Saying "php is good because it is better than C" is like saying "maiming is good because it is better than severe maiming with shrapnel and burning oil".
+%
+<radix> why do i hang out with you geeks ;)
+<dash> radix: the money, the power, the chicks
+<radix> YES
+<radix> :)
+<dash> the self-delusion
+%
+<iLLf8d> hey all how can I get more info from python exceptions?
+<gt3> play good cop/bad cop
+%
+<stranger> too much lag. going to pub.
+*** stranger is now known as stranger[pub]
+%
+<radix> how was the [censor]?
+<radix> oh.
+* radix pats his trusty Secure-o-matic.
+<glyph> radix: Terrific! [censored] was there, and so was [censored]. We built a [CENSORED] and used it to target
+<glyph> [INFORMATION QUOTA EXCEEDED]
+<radix> whoah there, buddy.
+<glyph> Erase is delete.
+<glyph> Kill is control-U (^U).
+<glyph> Interrupt is control-C (^C).
+<glyph> Ahem
+<glyph> right. So, it went well.
+%
+<schirkaan> and i thought distro wars where over a long time ago ;)
+<radix> schirkaan: are you new to IRC? :)
+%
+<wiggy> for some reason the drugs aren't working today
+*** wiggy is ~wichert@cabal.xs4all.nl (Wichert Akkerman)
+%
+You may think I'm uncooperative, but perhaps I'm just stupid.
+Bye,
+ Mike
+--
+|=| Michael Piefel
+%
+<skreech> If MS had bought Nintendo then Pikachu could be an MS Office Assistent.
+<shapr> paperclippachu, irritation attack!
+<shapr> paperclippachu, window close immunity!
+%
+<aj> Tv: it's been around for ages, but never got put in the mainline cgi's
+ (doogie saw some bright and shiny and got distracted...)
+%
+<SteveA> I want a new builtin type for Python 2.3: zenbool
+<SteveA> It is like the new bool type, but has three possible values: True, False and Mu
+%
+<aj> willy: so the question is, do i want to try my luck with another willy
+ upload? do i feel lucky? well, do i, punk?
+%
+<skreech> kill guard
+<skreech> drink potion
+<skreech> [lag]
+<skreech> ...
+<skreech> YOU MISSED GUARD HITS YOU MISSED GUARD HITS YOU MISSED GUARD HITS
+<skreech> You can't do that when you're dead.
+%
+<rc> I'm making a game called Tycoon Tycoon. It simulates competing software companies making 'Tycoon' games.
+%
+<faassen> "Hey I could speak in Slashdot messages only" An interesting
+experiment.
+%
+<glyph> So...
+<glyph> XML.
+*** Quits: dash:#twisted [washort@d136.narrowgate.net] (Read error: 113 (No route to host))
+<glyph> Wow... just _saying_ it makes him disappear
+%
+<Overfiend> Eric Raymond got frustrated because his code wasn't getting
+ merged, and it wasn't helping him out with the chicks who only
+ give blow jobs to people whose code actually makes it into the
+ kernel.
+%
+<dash> a famous evil genius is a dead evil genius
+<dash> unless you've got a robot army or something
+<dash> and mine's on back order
+%
+<itamar> you know what causes most evilness? the WEB
+%
+<spiv> My life is a sequence of blissful sleeps interspersed by bits between sleeping (most people call those bits "days").
+<spiv> I live for sleeping.
+<spiv> It's like my natural, base state of being. The Aristotlean ideal of me is me sleeping.
+%
+<itamar> [in XMLRPC] the header saying you *used* compression is as long as the banana packet
+%
+[ 23:07:38 ] <glyph> DeepTape: Are you familiar with the Time Cube?
+[ 23:08:06 ] <DeepTape> glyph: is that a comic?
+[ 23:08:13 ] <dash> DeepTape: not.... exactly
+%
+<cyli> Your minions are like the little elves, or trolls, who make shoes.
+ Except, not really shoes: internet.
+%
+<radix> there are stick men!!!
+<dash> yes
+<dash> uml has stick men
+<radix> I LOVE UML!!!!!!
+%
+<dash> get thee down, be thou funky
+%
+<thirmite> the novelty has worn off and i once again need heroin.
+%
+<thirmite> some would argue radix on crack is a different person!!
+<exarkun> thirmite: some would argue that radix _not_ on crack is a different person
+<demoncrat> some might argue radix on crack is two different people
+%
+<glyph> and the rexec'd code would run in a thread, and could use a Bastion to frob a PB reference synchronously
+<radix> here comes the crack, fellas
+%
+<adiabatic> Every day you stay awake too long God kills a kitten. Please, think of the kittens.
+%
+<o2s@ircnet> its nott the size that matters but the code
+%
+<Nafai> After I do some preliminary testing, I will soon be using Twisted towards commericial purposes
+<Aco> like what?
+* exarkun crosses his fingers and hopes for microlaser brain surgery hardware control.
+<exarkun> Twisted: The Framework That's Cutting Up Your Brain
+%
+<sayke> "your mission, sayke, should you choose to accept it, is as follows: define r(n, b[n], x, u); where r() is reality's iteration definition rule function, n is
+the number of dimensions, b[n] is the boundry size (in each dimension) of the automata, x is the number of cell states, and u is the state of the universe, last
+iteration."
+<radix> sayke: use Twisted!
+%
+* moshez kills dash and eats him
+<krz> You feel jumpy.
+%
+<ameoba> now that everything is an object, I'm afraid you'll have to return those integers until we can verify your credit.
+%
+<faassen> I created it. but I'm not *responsible* :)
+<faassen> it started lurching around by itself..
+%
+<skreech> I dont even take a lot of whats on IRC to _brain_ much less to heart.
+%
+<dash> javadoc is a cold and demanding master
+%
+* itamar looks out the window at the view and cheers up
+<itamar> nothing like a peanut factory to remind you how good life is
+[...]
+<itamar> there's a peanut factory next to the office, and that's what I see
+<itamar> great big peanut containers, towering above me
+%
+<glyph> itamar: we should set up a really nasty looking demo with emacs and java and pb all talking to each other
+* glyph ponders code-generation-based support for PB in C++
+<glyph> OK, I am guessing that dull pain behind my eyes means I should stop thinking
+%
+[re: emacs/PB, and the implementation thereof]
+<glyph> LEXICAL-LET is cool, I don't care how it works. I don't _want_ to know how it
+works. And now I have an appreciation for why I should never, ever change PB
+again :)
+%
+<aj> mstone: the raving lunatic camp rarely manages to
+ implement stuff +effectively, so they follow the people who can... </aj's
+ theory of life, the +universe and everything>
+%
+<skreech> web
+<itamar> web?
+<glyph> skreech: INTAR-web.
+<skreech> .org
+%
+<shapr> itamar: I've heard jdk1.4 is using a modified version of the mach kernel...
+<itamar> hahahahah
+<itamar> you're kidding, I hope
+<shapr> itamar: see, YOU'RE NOT SURE
+%
+<ameoba> print "\n".join(["".join([(lambda n, f=lambda c : "\033[%dm#"%c: f(n=='0' and 30 or n=='1' and 33 or n=='2' and 35 or n=='3' and 31 or n=='4' and 34 or n=='5' and 32 or n=='6' and 37))(char) for char in line]) for line in ["%06d"%x for x in [1002,31502,314233,314251,131152,314214,411531,234562,152212]]])
+<ameoba> ex : it's a diagram of a crack-attack board +)
+<ameoba> 'cuz I can't dcc shit to shapr while he's behind that firewall +)
+%
+<__del__> is there a special method a class can implement if it does its own garbage collection?
+--- __del__ is now known as gc
+* gc collects himslef
+<-- gc has kicked gc from #zope (gc)
+%
+"Much like in the world of Frisbee, new game developers and game
+development companies should never make a statement with more predictive
+power than "Watch this!" "
+- Glyph
+%
+Brian Crowder: It's both relevant and terrifying at the same time.
+Glyph: That's the best kind of relevant.
+Matt Walker: Yes, but it's the worst kind of terrifying.
+%
+<glyph> exarkun: The issue with globals is that they make resource management nearly impossible.
+<exarkun> glyph: why kind of resources?
+<glyph> exarkun: memory, disk, process time.
+<exarkun> glyph: I don't see how...
+<glyph> exarkun: Well, let's start with a hypothetical world with twenty billion obje[Out of memory error: server stopped]
+%
+* moshez decides to call himself GNU/Moshez
+%
+<skreech> (#%&@$@
+<shapr> perl? or lisp?
+%
+<noa> "let sleeping dongs lie"
+%
+<glyph> moshez: see? xml makes people happy.
+%
+<resolve> we live in a world where some people get their jollies having sex
+ with dead people - i don't think the notion of windows supporters
+ is entirely inconceivable
+%
+<ameoba> isn't a latvia part of the female genitalia?
+%
+<glyph> blargchoo
+%
+<Yosomono@efnet> premature optimization is like that other "premature" thing, messy and embarrassing
+%
+<SteveA> I just had a very odd phone call
+<SteveA> from a researcher with the french TV station "TF1"
+<SteveA> asking about inflatable football referees
+%
+<exarkun> english am dumb
+%
+<sjj> i believe my monitor just blanked out
+<sjj> i hope i'm in the IRC window ;)
+<skreech> sjj: no use telling you 'yes'
+%
+<dash> careful with that syntax, eugene
+%
+<VladDrac> does it [Twisted - ed] make my penis grow?
+<shapr> if so, you better be careful how many people run Twisted all at once.
+<shapr> you could die of blood loss.
+%
+<shapr> in reality, it just means I can throw down some Zope stuff and then play more crack attack rather than wrestling with J2EE for months.
+<shapr> ya know, no one on #java plays crack-attack
+<shapr> I think there's a not so hidden truth there.
+%
+<EWSJames> we can be knights in shining armor if we want, but peasants who make up 99.9% percent of the people just see us as asses who wear shiny shit and talk funny
+%
+* TuxedoKamen wonders why everyone always assumes he's on linux
+<Erwin> benefit of the doubt :)
+%
+<allexpro> dash: put me in a tent and give it to moshez!
+%
+<glyph> dash: we need to come up with a "basic rules of discourse" webpage
+<exarkun> glyph: why
+<glyph> exarkun: because if one more person makes a completely unfounded assertion in front of me I AM GOING TO EXPLODE THIS BACKPACK-SIZED NUCLEAR DEVICE
+<dash> glyph: I invented the hippo!@
+%
+<dash> we've all got stupid ideas in our past
+<dash> thanks to the power of the internet, the shame associated with them need never dim!
+%
+<sjj> itamar: if you use the word 'embedded' a lot, you sound smart.
+%
+<liiwi> ah, coldness, the lovely coldness. And the ever-protecting darkness.
+%
+From the /topic on #web:
+The First Rule of Web Development is, "We Don't Talk About Netscape 4.x"
+%
+<spiv> I'm not entirely happy with it, but it works. Well, actually it doesn't. But until 5 minutes ago I thought it did :)
+ [regarding the god-cursed FTP support in Twisted -ed]
+%
+<dash> your RDF is massive and unstoppable. [to glyph -ed]
+%
+<skreech> ooooh shit
+<skreech> I have moderator points!
+<skreech> RAAAAAMPAAAAAAAGE!!$*^
+%
+<tenth> "And then you run this Z80 assembly on the resulting bytecode in the emulator of your choice to create your makefile."
+<tenth> "The inital register settings of the real or simulated Z80 are left as an exercise for the reader."
+ [the nebula build process is just not fun. -ed]
+%
+<dash> i find it interesting that your roadmap showed twisted improving most while you're in jail.
+[in reference to http://twistedmatrix.com/pipermail/twisted-python/2001-April/000037.html -ed]
+%
+<allexpro> discovering twisted is probably the best thing that has happened in my life
+%
+<sjj> dash: i'm fine with you dealing drugs, just keep them away from radix
+<dash> sjj: look, if i dont keep radix stocked, we get no releases.
+%
+<radix> /msg exarkun [lilo] HI ALL GIMME MONEYS AND LOOK AT MY WEBBARSITE
+%
+* radix harnesses the power of fudgepops for good, rather than evil
+%
+<dreid> radix: any system that relies so heavily on a human concept like trust is inherently flawed ...
+<dreid> radix: i just use gpg to encrypt my porn
+%
+<Bergenlund> how do I add twisted to autoexec.bat?
+%
+* skreech squints really hard and tries to change his neuron patterns.
+%
+<radix> MY TAPEWORM TELLS ME WHAT TO DO
+<radix> s/MY TAPEWORM/MOSHEZ/
+%
+<dash> .rhosts auth is effectively "root one get one free"
+%
+<dash> the os module is why python doesn't suck
+<glyph> dash: concrete is what makes skyscrapers not suck
+<glyph> dash: doesn't mean I want to go swimming in it
+%
+<dash> is there some connection between German and disgusting modifications to C?
+ [c.f. The Nebula Device, CLISP -ed]
+%
+<dreid> earth# apt-get install good-will-towards-man
+<dreid> Reading Package Lists... Done
+<dreid> Building Dependency Tree... Done
+<dreid> Sorry but the following packages have unmet dependencies:
+<dreid> good-will-towards-man: Depends: peace-on-earth but it is not going to be installed
+<_moshez> dreid: file a bug against good-will-towards-men
+<_moshez> dreid: unless it is in contrib?
+<Nafai> non-free, perhaps
+%
+<dreid> <xpp>
+<dreid> <xout>Hello World!</xout>
+<dreid> </xpp>
+<bruce> dreid: just fucking learn Common Lisp. :)
+%
+<exarkun> let the unwashed masses write their C
+<exarkun> you will reap the benefits of their pain and toil
+%
+<glyph> jemfinch: Are you really a captain of a spaceship from the mirror earth, on the other side of the sun?
+<jemfinch> I don't quite catch your meaning :)
+<glyph> jemfinch: Aah. Wink wink, know what you mean, say no more, say no more.
+%
+<dash> "mwahahahahahahahahahaha"
+<Nafai> you are the christina aguilera of evil
+%
+<glyph> > flirt with cyli
+<glyph> You flirt with cyli. [moshez is here, flirting with cyli]
+<glyph> > wink at cyli
+<glyph> You wink flirtatiously at cyli.
+<glyph> Glyph enters the room.
+<glyph> # glare moshez
+<glyph> Glyph glares at you!
+<glyph> # kill moshez with sword of infinite slaying
+<glyph> Glyph hits! Glyph hits! glyph hits! -more-
+%
+<bruce> i wish i was only doing an imitation of a dumb user rather than really being one. :)
+%
+<sjj> you can't be a satinist without god either
+<spiv> You can be a satanist without pants though. The world is an amazing place.
+%
+* glyph finally places the order to get his carpets cleaned
+<shapr> is carpet cleaning thread safe?
+%
+<tenth> "As a developer, I'm often discouraged by the amount of time and effort it takes to gouge out my own eyes in pain and frustration. Thanks to Gouge#.net, this distasteful task can be peformed quickly and easily by a trained professional*. Thank you, .net. Jesus, my eyes.
+ (* Professionally designed GougeWizard(TM) with your choice of animated agent character)
+%
+<resolve> i miss the days of programming computers in machine code. all this new-fangled source code is a waste of time.
+<itamar> machine code? hah
+<itamar> in my day we ran programs in our *head*
+<moshez> itamar: you had a *head*? pah
+%
+<glyph> who could forget binky?
+<radix> glyph: well, anyone who naturally blocks out haunting things so they don't have nightmares
+<glyph> radix: Kenaan disapproves.
+%
+<hornby> Slavery doesn't seem so bad.
+%
+<getchomsky> "so, mister nooning, did you know you are associating with a man named glyph, a man authorities consider to be the most dangerous jewish man alive?"
+<getchomsky> "his mastery of open source programing makes him a threat to every man and woman alive on this planet. he must be stopped. Forget everything you think you know about him, and about this "twisted" of his"
+%
+<exarkun> twisted.web has used 1 CPU second of time in the week I've had it running.
+%
+<psy> How do I stop a factory?
+<Aco> psy: syndicate strike
+%
+<exarkun> exceptions in C++ are a _huge_ mistake.
+<radix> s/exceptions in/
+%
+<exarkun> glyph: do _you_ know about super()?
+<exarkun> glyph: As far as I can tell, it's a plot, one that would be likely perpetrated by an organisation not unlike the PSU (if the PSU existed, of course), to kidnap our firstborn and empty our jars of cookies.
+<Nafai> My cookies!?
+%
+<glyph> You know, I don't think I've reached a point in my life where I said "I don't have enough emotional trauma", irc-related or otherwise.
+<dash> glyph: cool. let's go troll #c++.
+%
+<sjj> i've heard there is a /quit command.
+<ameoba> sjj : "/quit" : absurd liberal myth
+<sjj> figured.
+%
+<_moshez> itamar: so, the security people ask them what they do, and they say they are mathematicians
+<_moshez> itamar: and to prove it, they show papers with their name on it.
+<_moshez> itmaar: and then the security guys ask them to explain what the papers are about!
+<_moshez> itamar: apparently, one hasn't lived until he heard a mathematician explain to a security guy what equivariant cobordisms between symplectic manifolds are
+%
+<ameoba> c++ is 700 times faster than Python
+<princepsd> ameoba: based on? ;))
+<ameoba> princeps: something somebody said on usenet +)
+%
+<itamar> take money from elderly and weak with knife
+[itamar writes test cases for the Twisted Reality parser]
+%
+<matiu> (I have to write help files) :(
+<ameoba> matiu : you could do it with twisted.
+<matiu> ameoba: So you're saying twisted has a "help file writer" somewhere deep down?
+<dash> matiu: yes and his name is bruce
+%
+<glyph> dash: uh... what is the correct answer to the question "The short common lisp site name"?
+<dash> glyph: "it buuuuuuurns"
+<skreech> my eyes, the googles do nothing!
+%
+<skreech> Why do I feel the sudden urge to buy a nice quality florescent desk lamp?
+%
+<skreech> the woot, the woot, the woot is on fire.
+%
+<skreech> itamar: heres your nickel back.
+%
+<datazone> okay, tell me if i am crazy
+<Yosomono> you are
+<datazone> damn
+%
+<bruce> and i like doing what i enjoy in my spare time. :)
+<bruce> which, although you all might think so, isn't harassing you all to do more work.
+<bruce> although you all do need to do more work.
+%
+<snibril> JRuby? hmmm, only java ppl have to reimplement ever other lang to replace theirs ;)
+<radix> scheme people, too
+<cleverdra> Scheme people don't do that!
+<radix> how many object systems have YOU written today?
+<cleverdra> radix - today? 12, but one of them wasn't really.
+%
+<radix> excuse me for visiting my DEAR OLD BABUSHKA on her EIGHTY-SIXTH BIRTHDAY when I should be WORKING ON TWISTED
+%
+<radix> every time you make a terrible joke, a baby rabbit dies
+%
+<itamar> "We put the 's' in 'drwxr-sr-x'!"
+%
+<bruce> i've apparently gotten someone at work to clean up their act.
+<glyph> bruce: clean up their act how?
+<glyph> bruce: were they like a pedophile heroin addict or were they just checking in buggy code?
+<dash> glyph: like there's a difference
+%
+<dash> It's moshez. Remember the briefing.
+%
+<dash> glyph: so, i am trying to jump off the side of the NSF headquarters without losing my legs
+<fzZzy> reminds me of college
+%
+<itamar> IN THE INTERNET AGE YOU WILL BE ABLE TO CHAT WITH YOUR TOASTER
+%
+<itamar> I liked the "2 years C# and .NET experience" job
+<gt3> i guess they're hiring dogs
+%
+<fzZzy> how do you quit a twisted telnet session?
+<allexpro> ctrl + ]?
+<fzZzy> there's no cleaner way?
+<exarkun> calling close() on your connection's socket is pretty clean.
+<exarkun> I suppose you could call in a tactical nuclear strike on the remote host
+<fzZzy> considering the remote host is my computer right here, that would take care of everything for me
+%
+<glyph> spiv: is bugzilla bad?
+<spiv> glyph: It's... large. And perl. Join the dots.
+%
+<itamar> write a kqueue reactor
+<itamar> all the FreeBSD people will then go nuts
+<bruce> FeerBSD people are already nuts
+%
+<radix> don't let's all go break a million tests, eh?
+%
+<dash> adiabatic: citizen, you have committed an error
+%
+<bruce> i'm feeling motivated
+<glyph> bruce: yaay!
+<glyph> bruce: what flavour of motivation?
+<bruce> beating you up
+%
+<moshez> it's the holy trinity, dash, radix & glyph
+%
+<bruce> what's #ypn ?
+<glyph> bruce: the fifth circle of hell
+<moshez> bruce: young programmers' network
+<moshez> glyph: potato potahto
+%
+<bruce> allexpro is a view of the future of humanity as a group consciousness.
+%
+<exarkun> njjeeeee
+<itamar> njjeeee?
+<exarkun> ancient aramethaic warcry
+<exarkun> infamous for its ability to strike confusion into the hearts of enemies of aramathia
+%
+<exarkun> radix: lisp freak
+<exarkun> radix: go suck on a car
+%
+<ameoba> it's frightening to remember that twisted is an overgrown MUD
+%
+<fariseo> i am completely lost, all i do understand is an OS with a database backend and a scripting language, but i am missing the whole xml/.net/j2ee/twisted...
+<glyph> I'm both honored and appalled that Twisted shows up in that list :)
+%
+<exarkun> Pop up a Tkinter dialog saying "There's some information waiting for you" and do a beep every time the ethernet IRQ goes high
+%
+<hornby> 1. Create laws that promote a fair, just society.
+<exarkun> 2. ????
+<exarkun> 3. PROFIT
+%
+<moshez> skreech: I claimed the typical anti anarchist attack goes something like:
+<moshez> "say someone cracks into your computer, downloads all your porn, burns it to a CD and throws it at you?"
+<moshez> anarcho-communist: nothing. the community would reprimand him.
+<moshez> anarcho-capitalist: my private security forces would shoot him before the CD left his fingers
+<moshez> attacker: "SEE! under anarchism you'd have people throwing porn CDs at people, and people either ignoring them and shooting them!"
+%
+<rmt> Every python program needs to have direct access to a mouse over ssh!
+%
+<strib> Sorry, I'm just in the middle of a paradigm warp right now.
+<dash> strib: welcome to twisted
+%
+<exarkun> entirely not your fault, I'd say. the current behavior is somewhat broken
+<exarkun> luckily I documented it as being broken so it's not my fault either.
+%
+<kriptik> wow twisted is neat
+<kriptik> *bleeds from the eyes*
+%
+<allexpro> i said 'hello'
+<allexpro> and when i 'cat test.au > /dev/dsp'... it sounded like a tiger roar
+%
+* moshez sings the radix song
+<moshez> "for he's a squishy good radix"
+<moshez> "for he's a squishy good radix"
+<moshez> "for he's a squishy good raaaaaaadix"
+<moshez> "and nobody can deny"
+%
+<allexpro> and how do observer patterns work?
+<dash> the PSU watches your data and notifies the authorities when it becomes suspicious.
+%
+<comajelly> hrm, I wanted to snipe this guy, but he got ran over.
+<fzZzy> heh
+<fzZzy> I hate it when that happens
+%
+<itamar> night all
+<-- itamar has quit ("Client Exiting")
+<radix> me too
+<radix> heh, it's weird going to sleep at the same time as itamar
+%
+<glyph> radix: I think that the twisted vs. asyncore table should begin with this quote, though: "Our conviction is like an arrow already in flight. Your life will only last until it reaches you."
+%
+<Erwin> I recompiled XFree 4.2 with gcc 3.2-beta-from-cvs with -O42 and -march-pentium4-800Mhz and I am sure that the MOUSE CURSOR is moving 5 % FASTER!
+%
+<bruce> I CONTROL YOUR WEBSITE!@$$
+%
+--> IAmNotAPickle (slt5v@12-255-1-203.client.attbi.com) has joined #twisted
+<radix> PICKLE
+<-- IAmNotAPickle (slt5v@12-255-1-203.client.attbi.com) has left #twisted
+<radix> :(
+<exarkun> you scared it
+%
+<exarkun> heh
+<exarkun> I was at home depot the other day
+<exarkun> and they had a big rack of free AOL CDs
+<exarkun> So I took about 40 and stuck them under people's windshield wipers in the parking lot
+%
+> Linux is complicated, becasue you compile.
+Corollary: Windows is simple because no compiler comes with the system...
+[Seen on linux-il]
+%
+<sjj> dash: soon there'll be another level of college labelled "unlearn university crap"
+%
+<wzZy> arg. these infinite recursion tracebacks take forever to render in the browser
+%
+<exarkun> I'm just kidding. I'll spend on good computer books, but I failed to do any prepatory research in order to know whether any of the books there were worth anything.
+<exarkun> And I'd just spent $80 at the camping store.
+<adiabatic> wadja get?
+<exarkun> some kerosene and a cooler and a coupla chairs
+<dash> nothing like a good old fashioned book burning
+%
+<radix> you have the pokey gene
+<zigg> ack, where'd I get it from :-P
+<radix> it's random
+<zigg> triple ultra-recessive
+%
+<radix> krz: we know that glyph owns all of our souls equally
+%
+<moshez> glyph: teehee. good always loses
+%
+<sjj> if you package twisted with python, it becomes py2ee
+%
+<sjj> when does something denote enterprise? :P
+<deltab> when it's the most expensive version in its line
+<inapt> when it's terribly inefficient, but scales ;-)
+<deltab> alternatively: database
+%
+<glyph> The world made more sense when I thought software was a physical thing you sold in stores, and I wrote code in C++; making software was a lot more like mixing cement, then, not poetry or revolution.
+%
+<skreech> I'm sorry. I forgot that in #twisted, all suggestions are taken seriously.
+%
+<sjj> what do you do for your clients? :)
+<sjj> "distributed enterprise networking technology solutions" ?
+<bruce> we put DENTS in your budget.
+%
+<Jerub> All these things that would be next to impossible with php, that I can think to do in twisted.web
+%
+<timmy> so it's basically a lot of libs for doing stuff?
+[timmy becomes enlightened to the Twisted Way -ed]
+%
+<Joey> I sense disturbance in the security buildd structure.
+%
+<wzZzy> AQUAMAN VS THE GERMANS IS THE BEST MOVIE EVER MADE
+%
+<eevench> is LISP good
+<exarkun> The short answer is yes and no.
+<exarkun> You don't want the long answer.
+%
+<radix> who has the power to wield the almighty +t?
+<radix> Me!
+<exarkun> radix: I don't think you can handle the +t
+%
+<willy> you can tune a fs but you can't call a string
+%
+<Jerub> What l33t skilzz do I have to pick up to get a python job? Zope? Twisted?
+<dash> Jerub: the power to cloud the minds of men
+<Jerub> dash: I'm afraid I only have a Wand of Clouding vs. Women
+<glyph> Jerub: oh, that's easy
+<glyph> use the wand on a woman
+<glyph> women have the clouding-men's-minds intrinsic
+<glyph> so you can either make her your pet and then wander around
+near some businessmen for a while
+<glyph> or eat her corpse and get the intrinsic yourself
+<glyph> no wait, that's not how it works...
+<dash> glyph: wrong game
+ * dash twitches violently as he thinks of "nethack, enterprise edition"
+%
+<spiv> AaronSw: You should never, ever be creating a transport... Twisted is supposed to do that for you.
+<AaronSw> I should never create the tcp.Client stuff or I shouldn't manually set them as the transport?
+<spiv> AaronSw: Use reactor.clientTCP (or better yet, reactor.connectTCP in CVS).
+<spiv> Don't create tcp.Client directly either.
+<spiv> http://twistedmatrix.com/documents/TwistedDocs/Twisted-0.19.0/twisted/internet/interfaces_IReactorTCP.py.html
+<spiv> AaronSw: But of course, that API is deprecated in 0.99 (but creating a tcp.Client directly is even more deprecated :P)
+<AaronSw> Are you guys abstraction astronauts or something? ;-)
+%
+<DeepTape> Oh no, taxes! They are trying to steal your arctic circle income
+%
+<pkomarek> dash: the worst part about perl is that it is intuitive, right up until you need something to work correctly.
+%
+<dash> finally! an essential representation of the confusion.
+ [ed: referring to a diagram of twisted.cred]
+%
+<Acapnotic> Good afternoon, Agent.
+<glyph> Acapnotic: "agent"? You've been immersing yourself in the One True Game, I take it.
+<Acapnotic> What's going on out there is no game. Those guys are using real bullets.
+<Acapnotic> That last mission? I got sloppy at the end of it. Real sloppy. Barely had a leg to stand on when I got on that chopper.
+%
+<glyph> Hello .au
+<Jerub> hello .us
+<Jerub> or, alteratively,
+<Jerub> hello None
+%
+<moshez> doogie: Clint wants you to lap-dance.
+<doogie> I charge more than the normal $20
+<Clint> with or without the hat?
+<doogie> that'd be the only thing I'd wear
+%
+[About a tm.com redesign]
+<evol> But what kinda design is the goal here
+<itamar> not ugly?
+<moshez> evol: a good one.
+<dash> evol: "non sucky"
+%
+<sjj> glyph: ahh too bad, if you have a windows box it lets you use windows media player...
+<dash> sjj: he's a terrorist PPC user not a patriotic x86 user
+%
+<JerubBaal> why can religious fanatics and nigerians not figure out capslock?
+<glyph> JerubBaal: IF IS SPEAK LIKE A TELEGRAM YOU WILL LISTEN TO ME STOP IF I USE NORMAL ENGLISH YOU MAY FIND IT BLAND AND NOT READ IT ALL STOP
+<JerubBaal> Sorry, I lost interest after you shouted 'telegram'
+%
+<dash> twisted doesn't currently have any trouble with 2.2, right?
+<radix> nope
+<dash> good good
+* dash prepares to make trouble
+%
+<dash> looks like we have people who just totally fall off our radar because they're totally happy with twisted and dont _need_ to say anything =)
+%
+<spiv> glyph: You have the deepest insight into XML of anyone I know ;)
+%
+<mesozoic> I tried adding one in coil, and got more errors. I'm not sure if I'm going about it properly. Is there any other way to configure a vhost?
+<ameoba> call the vhost-bustters
+%
+<Jerub> I got in trouble for drawing a smiley face on a gantt chart.
+%
+* moshez does the evil lowering squishation resistance level dance.
+%
+<glyph> CDATA is not an integration strategy.
+%
+<nessus> The PSU? Is that that thing that I used to send $50 a year to?
+%
+<radix> "Don't expect romantic attachments to be strictly logical or rational!" [from a fortune cookie -ed]
+<deltab> do expect them to be in DOC format
+<deltab> "May all your romantic attachments be in an unreadable file format"
+%
+<sjj> skreech: you go to all classes?
+<skreech> sjj: yes.
+<sjj> skreech: why? :)
+<sjj> skreech: uni was made to be skipped
+<sjj> man, americans must be dedicated students.
+%
+* moshez doubts they realize Linux has *WAY* more brand-awareness than SCO, and possibly equal to "UNIX"
+<dash> where SCO is recognised
+<dash> it is recognised as suffering
+%
+--- ivan is now known as grub
+<grub> please donate to this IRC server I need lots of money i don't know how i can stay online.
+--- grub is now known as ivan
+%
+<glyph> I've started to think that having a lot of stable, robust stuff and a
+ lot of half-finished proof-of-concept stuff in one project is a good
+ business model
+<glyph> like "You know we can do good work, but we got bored with that bit; if
+ you want us to finish it, pay"
+<dash> glyph: good, because that's what we have
+%
+<StevenK> steven@broken:~$ ssh squished
+<StevenK> steven@squished's password:
+<StevenK> Linux squished 2.4.18-686 #1 Sun Apr 14 11:32:47 EST 2002 i686
+%
+<bruce> how are the jails in israel?
+<itamar> well, the one I was in was pretty nice
+%
+<dash> glyph: how many PSU agents did you have to kill to get that working?
+<glyph> dash: 3, and they were all waiting just inside the door. Amateurs.
+%
+<glyph> and it's considered a professional courtesy, when you are *invited*
+ into a bank, not to steal all their moneys and shoot the managers full
+ of assault rifle bullets
+%
+<moshez> itamar: you're AT WORK?
+<itamar> moshez: I am not an employee
+%
+<tenth> Doing stuff in MySQL is like getting dates at [name elided to
+ protect the guilty -ed] College... "How ugly do you want it?"
+%
+<tenth> I think we need a god verb "0wnz0r" on the
+ reality-pencil-type-thing. I'm not sure exactly what it would do, but I
+ think it may be necessary.
+%
+<dash> Saying that complexity isn't real because it "was invented somewhere else" is the most useless kind of wishful thinking
+%
+<xcabbage> mind.sf.net crashed my browser
+<dash> signs of intelligent life!
+%
+<glyph> The only thing more absurd than the technology of XML is the politics surrounding it.
+%
+<glyph> While it is *possible* that I'm smarter than you think I am, it is certain that I'm more stubborn.
+%
+<z3p> glyph: what group of programmers are you picking on tonight?
+<glyph> z3p: PyXML again
+<z3p> sounds like a blast :)
+<glyph> z3p: ugh. Actually I have a pretty high opinion of some of those people so it bugs me to have to be flaming :)
+<dash> glyph: bah, just lower your opinion of them
+<dash> no need to consider their past character, if they're wrong, they're scum@#!
+* dash twitches
+%
+<glyph> _moshez: debian really needs to make start-stop-daemon do something
+ cute, like put icons across the top of the fbdev
+%
+<glyph> we need PB for C#
+* moshez squishes glyph
+<moshez> glyph: squishy insane person
+%
+<dash> moshez: we dont have the right kind of soil to not grow wheat in
+%
+<exarkun> I try to limit myself to one major screw up a week
+%
+<tenth> in OSX, they deprecate things with hammers and nailguns
+%
+<Lan_Rover> it is my official decree that it is easier to config and run twisted as a web server than to install apache2 and mod_python
+%
+<liiwi> http://slashdot.org/articles/02/09/12/160255.shtml?tid=99
+ [the topic is "squishy Digital Rights Management" -ed]
+* dash looks at slashdot
+* dash looks at moshez
+<dash> moshez: just _what_ have you been up to lately????
+%
+<itamar> thank god I'm not religious
+%
+<dash> Hi. Allow me to express my opinion of Word now that i've gotten to know it a little better.
+<dash> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaaa.
+%
+<glyph> Stravad: whereas eval is like slitting your own throat before going
+ out for a walk so as to make the mugger's job easier
+%
+<glyph> radix: keep your eyes on the bot! we move fast.
+%
+<infinity> moshez : I won't denounce PHP... It still has its uses.. <shrug>...
+ I denounced most PHP *users* about a week after adopting it, though.
+<infinity> That language attracts more idiots...
+[Ed: infinity is the Debian PHP maintainer]
+%
+<Aco> radix: ever heard for this russian group tato/taty? pop
+<radix> Aco: nope
+<Aco> radix: well, girls were like 16 when they started. they sing in
+ther white underpants, and they wet them with water.. so you can see
+under. very interesting. want some pictures? :)
+<dash> Aco: please do not corrupt our release manager
+<dash> Aco: at least not until after 1.0
+%
+<radix> hehe yeah the whore house was awesome :D
+<radix> if you go in with a high-level the chicks pass out after you're done
+ with them
+%
+<Tv> What kind of dope is that? md5 digests _are_ 16 bytes.
+<warner> don't trust the hash, man
+%
+<bruce> hmmm. i didn't get my 11pm cron output email.
+* bruce forgets which cronjob that is though.
+<skreech> bruce: nuclear war has erupted. haven't you heard?
+<bruce> (or what machine it runs on)
+<bruce> (or what user it runs as on what machine)
+<glyph> skreech: in 2002, war was beginning
+* skreech makes a "boooosshhh" sound.
+<skreech> glyph: what happen?
+<glyph> skreech: somebody set up bruce the cron, apparently
+<skreech> glyph: main vi turn on
+%
+<shapr> c:\> vrms
+%
+<itamar> yes, but that doesn't make sense, how can you be proud of a civil war?
+<exarkuN> itamar: we won
+<exarkuN> itamar: what's not to be proud of?
+%
+<jml> is there a doc on twisted's version numbering conventions?
+<dash> jml: glyph rolls dice
+%
+<itamar> dash is *already* pre-strectched
+<itamar> he's like 6 feet
+<itamar> well
+<itamar> he's actualy 1.80 meters
+<itamar> or so
+<itamar> it's not that he's a giant insect
+%
+<_moshez> itamar: I'm agaisnt the state too :)
+<shapr> _moshez: are you purely functional?
+%
+<jml> dash: there's an otherwise normal guy at work who uses tcl as his scripting language of choice
+%
+<sjj> let me tell you something. I worked at BK for 1 year, and the veggie burgers have more meat than the whoppers, but nobody complained!
+%
+<sjj> moshez: don't kid yourself, if a cow got the chance he'd eat you and everyone you cared about.
+%
+<dash> zb0: ok, let me describe what you sound like
+<dash> zb0: "Hi. I want to drive a spike through my foot into the floor. Can someone help me with that? I know i dont need to, but there are other things i want to drive spikes into."
+%
+<itamar> it's "moshez vs. the CS profs of doom"
+<dash> itamar: i think in a war with the CS profs of doom, i'd be on moshez's
+ side.
+<itamar> yes, but your goals are different
+<itamar> inevitably your pact would weaken
+<dash> itamar: well, we'd fight a duel if we both survived the war.
+%
+<dash> |mmy: cgi is not an enterprise solution.
+%
+<_moshez> cyli: oh, yes. what did you think of my flame to val?
+<cyli> moshez: i didn't get to read all of it. glyph kept interrupting me
+ with questions of what i thought of it. and then i had dinner.
+%
+<glyph> phed: the abbreviation FAQ does not have the word "smart" in it
+%
+<cvs> Commit from glyph (changed 1) in Twisted/twisted/web: "A more expository docstring. Sometimes I'm distracted easily and I might stop in the middle of" static.py
+%
+<anonymous> i keep forgetting how much *fun* python is without zope
+%
+<glyph> radix: so ... it doesn't already do what you want?
+<radix> glyph: Well, now that I understand that what I wanted is impossible, yes. :-)
+<radix> I mean, yes, it does everything that I want, now. ;-)
+-vinge.openprojects.net- glyph changed topic: Learn from radix: if Twisted doesn't do what you want, modify your desires.
+%
+<CainKnight> Right now, I could care less about the best way to do this, or the
+ intricacies involved. What I care about is making a function get
+ called.
+<CainKnight> If that involves ritual sacrifice to dark gods, fine.
+<CainKnight> I don't care why the dark gods want chicken blood.
+<CainKnight> All I need to know right now is will they accept it and make the
+ volcano not wipe out my city.
+<CainKnight> Once the volcano is placated, then i can go back and figure out
+ that it wasn't the blood, it was the heat mixed with the iron in a
+ rich oxygen environment, and adjust the ritual properly in the
+ future.
+%
+<Yosomono@efnet> I'm so open source that I sequenced my genome and released ISOs
+%
+<glyph> one of the nice things about being american and effectively
+ culture-free
+[The next line isn't really important, is it? --ed]
+%
+<glyph> radix: there is NO bit of canada that's that close to you
+%
+<radix> ok, *6* hours ;-)
+<glyph> radix: yeah, if your car can _fly_
+%
+<Erwin> I will code your website and polish your shoes! With my toung!
+<moshez> Erwin: how do you code a website with your tongue?
+<dash> moshez: two words
+<dash> "salivaproof keyboard"
+%
+<z3p> WHY DO YOU MOCK ME UNIX
+%
+<radix> what does wifi have to do with feng shui? :P
+<dash> radix: optimal flows of internet through your house
+%
+* moshez doesn't see how you can not have a computer in the bedroom
+<moshez> I mean, what if you wake up at 4am and need to talk to someone
+<dash> moshez: walk into the other room?
+<moshez> dash: I'd need to get dressed for that
+<dash> moshez: bathrobe
+<moshez> dash: I'm sane
+<moshez> dash: my bathrobe is in the bathroom
+%
+<itamar> dash: how'd you learn?
+<dash> itamar: lessons
+%
+<radix> bathrobe is easier than boxers ;-)
+<moshez> radix: how so?
+<radix> moshez: eh, you have to deal with legs
+<radix> a swoosh around the shoulders is easier, I think
+<moshez> radix: it takes more presence of mind to tie the belt-thingy
+<jml> moshez: compared to buttons on boxers? I think not
+<moshez> buttons???????
+<moshez> jml: who makes your boxers? Chinese Torture 'R' Us?
+%
+<moshez> I'm always nice.
+%
+<moshez> jml: you're like all men, you're afraid of committing
+<jml> moshez: it's a deep seated fear of conflict
+%
+<glyph> bruce_: GPL can't force you to write code under non-MIT/BSD licenses
+<exarkun> GPL+mindflayer can though
+%
+<zen-@ircnet> which the ratio simplicity/expressivitiy of python?
+<moshez> 2.49866397309784
+<moshez> approximately
+<tigrux> moshez ?
+<moshez> tigrux: well, it's for Python2.2
+<moshez> I haven't had time to modify my calculations for the CVS version
+%
+<spiv> mjs: You've probably noticed by now that dash is only here to make occasional remarks about Twisted & World Domination... ;)
+<mjs> spiv: yeah I starting to notice... but I am sure it will become more fervent when we have a Lisp twisted implementation. =)
+<dash> mjs: when that happens, i will become more powerful than you can possibly imagine
+%
+<teratorn> as a general rule, you should never associate popularity with correctness
+%
+<dopey> fd0: what in particular about the environment is significant ?
+<fd0> dopey: some env-variables
+%
+<blanu> glyph: Itamar says I need a shell account on pyramid. I forgot why.
+<glyph> blanu: You do! For cabal research.
+<glyph> I MEAN CVS ACCESS NOT cabal research there are no blood sacrifices
+<blanu> Yes, yes exactly.
+* Acapnotic puts magnetic blood boy back in the closet.
+%
+<PenguinOfDoom> Bah.
+<PenguinOfDoom> People still say "Linux Redhat 8"?
+<PenguinOfDoom> It's "Linux version 8", damnit.
+%
+* itamar wonders if the phrase "evil spawned in dark aeons beyond the ken of man" should go in a price proposal for a project
+<moshez> depends.
+<moshez> if it's a proposal to Satan, yes.
+<moshez> also, how much would it cost?
+<moshez> is it, like, a big chunk of the price?
+<moshez> if so, possibly a more expansive description is in order.
+<moshez> like, where exactly the evil was spawned.
+%
+<moshez> "Hi, we use bit arithmetic on doubles, becuase we're really
+ stupid. We deserve what we get for programming Perl. Do you
+ have a position for us FLIPPING BURGERS?"
+%
+<radix> where the heck did caps-day come from, anyway?
+<JDAHLIN> It's something we often celebrate here in South America
+<JDAHLIN> Very traditional.
+%
+<radix> man
+<radix> I'm getting drinker's-elbows
+%
+* moshez sees the orbital lasers adjusting
+* ameoba puts on a _REALLY_ shiny tinfoil hat
+<moshez> ameoba: I'm afraid that's another myth
+<moshez> the tinfoil hats actually help us aim the lasers
+<dash> ameoba: they suppressed the laser-dispelling tinfoil in 1953
+%
+"Perl is like a normal chainsaw, but it's inflammable."
+ -- Prior-Art-O-Matic (http://thesurrealist.co.uk/priorart.cgi?ref=Perl)
+%
+<ameoba> READ THE FAQ@!#@ THAT"S NOT A MINIMAL EXAMPLERING@#
+%
+<rc> Yosomono: I was only kidding when I said, "Fuck you."
+<Yosomono> rc: Dude. Water. Bridge. Beneathage.
+%
+<Pahan> demoscene? Isn't that some Greek philosopher?
+%
+<glyph> I'm at MIT. I'm walking down the infinite corridor, and towards the
+end they have a small lab, which looks strangely like the MJ12 Level 2 labs
+in DX. In the lab are a bunch of display screens.
+<glyph> The lab has a placard next to it that says "nanotechnology center"
+<glyph> Soon as I look up at the monitor, it switches to a slide that says
+"nano-indentation".
+<glyph> I'm not kidding.
+<glyph> they are engineering the whitespace eating nanovirus _right here_
+%
+<exarkun> this server rebooted, now freshcvs is raising exceptions, the webserver doesn't run, and mailman's permission
+wrapper refuses to acknowledge setuid bits
+<exarkun> It's enough to make a guy buy a rifle and start shooting people.
+<glyph> exarkun: what kind of rifle
+%
+<jml> anyway, wasn't bruce_ writing a C implementation of spread?
+* jml decides to write a C++ one just to piss dash off
+<dash> jml: that wouldn't piss me off
+<dash> jml: that's like trying to annoy an eye surgeon by stabbing yourself in
+ the face with a pencil
+%
+<exarkun> ThreeSeas: Thanks for playing. BTW, I whipped up an autocoder last night, but it went on strike.. said it wanted a better contract.
+%
+<pht> hi, does python do threads?
+<MoonFallen> we need a dash hand-puppet to answer this question when he's not around
+%
+<ameoba> WHO WAS GENERAL TSO AND WHY ARE WE EATING HIS CHICKEN?
+<dash> ameoba: because it is SPICY.
+%
+<jml> C++ templates, a bad idea ruined by bad implementation.
+%
+<glyph> IT IS BECAUSE I AM A GENIUS!!
+%
+<mindlace> so g-d parses xhtml. Mysterious ways, I guess.
+<radix> no, microdom parsers xhtml
+<radix> g-d munges it :)
+%
+<Mifune> that is one benefit of being "god" on this project... I am my own clusterfuck
+%
+[this conversation took place at 7:16 AM, and both participants knew they had
+ clearly been awake for the previous 12 hours or so... -ed]
+<glyph> So, your schedule in space again too?
+<dash> schedule?
+<dash> my _brain_ is in space
+%
+<radix> mozilla runs on macosx, right?
+<glyph> radix: yes.
+<glyph> radix: but it's slooooooowwwwww
+<fzZzy> It's not too bad on my 933 with 1.25 gb of ram
+%
+<xyld> Java is like being naked, covered in vaseline and beaten with sticks -- I respect that some people like that kind of thing, but I'll pass :)
+%
+<jml> quoth the _moshez: Here is .lore
+%
+<fzZzy> pyn: suck it
+<pyn> An error occurred:
+<pyn> suck
+%
+<blanu> When I mentioned Twisted, Guido said it had been suffering from the
+ problem where you look at it and you can't tell what it is, but that he
+ thinks that has gotten much better lately. I then told him much praise for
+ the responsiveness and hardcore attitude of the Twisted developers.
+<radix> HARDCORE YO
+ * radix headbangs
+ * radix goes to wash dishes
+%
+<ry> twisted_ (twisted@krs-dhcp351.studby.uio.no) joined on efnet
+<Erwin> It's become SENTIENT!
+%
+[discussing Woven... -ed]
+<glyph> dmerrill: We're still working out the best way to approach this philosophpically ;)
+<spiv> "philoso*php*ically"?
+<glyph> spiv: that was the weirdest freudian slip of my life
+%
+<datazone> play that funky music dash boy
+* dash is playing that funky music right.
+%
+<ameoba> radix: I think "+q? :)" is an ETC macro...
+<ameoba> radix: it's used alot in quantum computing.
+<ameoba> "try all values, destroy universe if false"
+<ameoba> useful for that O(1) execution speed, unfortunately, it could be REALLY BAD if your evaluation function's buggy
+%
+<cluster> hehe I haven't had a decent night of sleep since I discovered twisted :)
+%
+<z3p> itamar: you can /never/ have too many monkeys
+%
+<moshez> jafo: going to give a talk today.
+<moshez> jafo: "Smooth Structure of Orbifolds"
+<dash> moshez: if i ever write an RPG, i'm going to make one of the monsters be an orbifold
+<dash> "The orbifold hits! The orbifold hits! You die..."
+%
+<radix> A Sparrow claws your face right off!
+<radix> You are dead! Sorry...
+<radix> [Info] Radix has been slain by A Sparrow!!
+<radix> The gods have mercy on your inexperienced soul.
+<radix> <1hp 103m 86mv>
+<radix> 1hp!
+<radix> no restore!
+%
+<red_one> sayke: what would happen if everyone voted consciencously?
+<sayke> red_one: what would happen if everyone beat their swords into plowshares?
+<dash> sayke: PLOWSHARE FIGHT
+%
+* vegai wears his reading bra.
+<vegai> umm, I mean glasses
+%
+(searching for "god" on Google returns http://phpnuke.org)
+<itamar> something is screwed up with google...
+<LotR> itamar: no, that's god's little joke on google
+%
+<datazone> but the real question is: "do you get to beat victims to death with a steal dildo while wearing a bugs bunny outfit?"
+%
+<exarkun> I'm gonna preempt them by mailing the list and asking what people think is wrong with it
+<Acapnotic> exarkun: will you make that a multiple choice question?
+<exarkun> Acapnotic: YES! A) IT IS TOO GOOD B) IT IS TOO GOOD C) EXARKUN IS TOO SEXY, I WANT TO HAVE HIS CHILDREN D) ALL OF THE ABOVE
+<Acapnotic> > Dear exarkum, i would like very much to use ur smtp client programme but you are too sexy and i want to have many children by you. only problem is that i am currently a man and it will take time to change for you.
+<Acapnotic> > I will understand if you do not wait for me, but i will look forward to the times we are together
+%
+<moshez> spiv: what's the date there?
+<spiv> moshez: Oct 2.
+<spiv> Admittedly we're in daylight savings, but we're also not right on the international date line :)
+<moshez> maybe Nov 2...
+<spiv> Er, Nov 2, yeah :)
+* spiv tries to act innocent, like he doesn't have a time machine
+%
+<radix> glyph needs more friends that can break into his house
+%
+<moshez> itamar: when they have an action figure of me, it will come with a squishing action
+%
+<itamar> so it's, kinda, "heh-inducing episodes occured in conjunction with
+ your girlfriend"
+%
+<jml> dash: well, I might have done something really stupid. Like embed perl code into the example by accident.
+<dash> jml: well, there's stupidity, and willful stupidity
+<jml> dash: and then there's university
+%
+<sjj> hah, they've introduced Vanilla Coke down there as well, eh? ;)
+<jml> sjj: yeah, we're really up to do. Soon, a real "burger chain"
+from the United States is gonna come here. They call themselves,
+Mac-something-or-other. :)
+%
+<glyph> that's AWESOME
+<itamar> wasn't it your idea?
+<glyph> itamar: If it was, I'm a genius
+%
+<Kengur> why cant python b compiled to native bitecode?
+<Erwin> Python IS compiled to bitecode on the Transmeta Muffin
+<inapt> what's native bitecode?
+<inapt> a sekrit language of snakes?
+%
+<icepick> go vote!
+<arma> for the shmuck, or the other shmuck?
+%
+<jml> dash: given the number of places you can stick const, "where the sun don't shine" is probably the best
+ -- jml explains good C++ style
+%
+<itamar> kill him
+<itamar> before he reproduces
+<anonymous> itamar: slightly more tact is called for
+<exarkun> Tact won't solve any problems an aluminum baseball bat won't solve faster.
+<anonymous> can't get to him - no budget money for ticket
+<anonymous> any excess budget money will go toward lucky professor #0 getting whacked.
+<anonymous> I got in trouble, because the first draft of the budget had the official "slop" line item marked as "Dr XXXXXXX hitman fund."
+<anonymous> noone disagreed, mind you - they just didn't want it officially in the budget.
+%
+<Cheez> OMG, the economy is so bad that people are willing to work in tennessee??!?
+%
+<jafo> I finally got through the Internet, but the end guy is REALLY hard.
+%
+<bram> http://advogato.org/person/Bram/diary.html?start=40
+<raph> if i were to click that link, it would: pop up a progress window; print cryptic messages about launching konq to stdout; make the kirc window small and unresponsive; and crash the gnome panel
+%
+<moshez> glyph: do you think programming requires thinking?
+<glyph> moshez: No! That is why we can automate it with robot monkeys, and we programmers must fight to earn our meager living while we are being crowded out by machines.
+%
+<saph> i'd rather have a non-robot monkey, for they are squishier and have hair
+%
+<drue> fermats last theorem is very simple, it's the proof that's a bugbear
+<raph> actually, i have a simple proof, but it's too small for me to type into irc
+%
+<exarkun> INEFFICIENT CAPITALIST YOUR OPULENT TOILET WILL BE YOUR UNDOING
+%
+<dash> it's quicker to ask python than us :)
+<MoonFallen> dash speaks words of wisdom
+<MoonFallen> it is quicker
+<MoonFallen> it's not always useful, though
+<MoonFallen> for example, last week i wanted to know a good brand of barbecue sauce. i tried asking python
+<MoonFallen> it quickly gave me my answer: SyntaxError
+<dash> MoonFallen: we dont buy any other brand
+<MoonFallen> i knew i should have looked for it at Whole Foods instead
+%
+<jml> <saph> it is easy to get a date
+<jml> <jml> saph: that's easy for you to say
+<jml> <saph> you just have to be brave and have little or no standards
+* jml fucks his clipboard
+%
+<Tenshihan> if I had a binary number 1001001 and I wanted to count the
+number of 1's in it... and the only math function I have is add, should
+I shoot my professor?
+%
+<MoonFallen> or maybe just pay his army to surrender. it would cost less than
+shipping 250,000 troops over there, i'll bet you.
+<MoonFallen> the oil companies would probably chip in too
+<MoonFallen> we could get everyone involved. like sponsoring a starving child
+in africa. except you're sponsoring an iraqi to surrender
+<MoonFallen> i wonder if we could get them to write their sponsors letters.
+"thank you for not blowing me up. thanks to your generous donation of 1 million
+dollars, instead of being dead, i am now an oil magnate in my native country."
+%
+<datazone> you're a towel
+<exarkun> a towel of IMMENSE POWER, yes.
+%
+<moshez> wow
+<moshez> I don't see how people didn't think of this before
+<moshez> if you're in competition with some windows user, just report him to the bsa
+%
+<jml> damn you all. damn you and your witty repartee and your elegant bloody framework. I'm going to sleep.
+%
+<jml> here I am, brain the size of a planet, and they make me do XML
+%
+* glyph thinks x++ should have been named "<xml type="programming">
+ <increment /> <increment> </xml>"
+%
+<raph> i was going to publish a spec for bitmap images much along similar lines as BLOAT
+<raph> ie, <pixel><color><component name="red" value="34"/> ...
+<raph> now here is the evil thought
+<raph> do up an XLST stylesheet to render it as a huge html table with cell backgrounds for each pixel
+<raph> so you can view it in mozilla
+%
+<aum> dash: do we have a 'non-profanity' chan policy here?
+<dash> aum: we have a "not acting lame" policy
+%
+* spiv wishes he never has to see another meta-argument
+<moshez> spiv: meta-arguments are fun!
+<spiv> moshez: The first time perhaps. They're always the same, though. It gets tiresome.
+<glyph> spiv: you're having a meta-meta argument now
+<spiv> glyph: My life is pain :)
+<moshez> glyph: I was afraid of having to point this out to spiv myself
+<dash> moshez: it wasn't an argument 'til you spoke up :)
+<moshez> dash: but he *answered*
+<glyph> aaaaaaaaa
+<glyph> METAMETAMETA ARGUMENT
+<jml> glyph: not it's not
+--- ChanServ gives channel operator status to glyph
+<-- glyph has kicked jml from #twisted (IT IS NOT AN ARGUMENT IF I HAVE A GUN)
+<exarkun> +1 (Insightful)
+%
+<dash> bruce: oh. intellectual dishonesty doesn't bother me when it comes to getting k5 to post our propaganda.
+<moshez> dash: intellectual dishonesty doesn't bother me when it comes to brainwashing and taking over the world
+<dash> moshez: That's what I said.
+%
+<MoonFallen> i'm looking at a perl program called tedia2sql
+<MoonFallen> the author seems pretty competent, judging by the quality of the program, but it takes him almost 300 lines what t.p.usage would allow me to do in 50
+<MoonFallen> and i'm not that good
+<glyph> MoonFallen: yes, but I'm *amazing*, and I wrote t.p.usage ;-D
+<MoonFallen> well shit, no wonder
+<glyph> and it's been hacked on by people smarter than me, since then.
+<MoonFallen> incidentally, who do you consider smarter than you? i need to hire those people or keep away from them
+%
+<MoonFallen> lol. it's nice when someone starts out a post like this: " Basically, the entire structure of your argument centers around the assumption that it's bad to have bugs in your program."
+<MoonFallen> then i know i can skip the rest of the post
+%
+<bram> I just got a call from a mechanical voice which said 'I'm sorry, I dialed your number in error'
+%
+Grocible says, "this programming job on another site demands "courage, commitment and loyalty""
+Grocible says, "Courage commitment and fucking loyalty?!"
+Grocible asks, "is this an ad for a knight's assistant circa 1450?"
+Grocible asks, "what were they called? Pages?"
+Nate says, "So that's what Active Server Pages are."
+%
+<liiwi> _pattern = re.compile('^(?P<client>[^ ]+) (?P<ident>[^ ]+) (?P<authuser>[^\[\n]+) \[(?P<mday>[0-9]+)\/(?P<mon_name>\w+)\/(?P<ye\
+<liiwi> ar>[0-9]+):(?P<hour>[0-9]+):(?P<min>[0-9]+):(?P<sec>[0-9]+) (?P<timediff>[^ ]+)\] "(?P<method>(GET|HEAD|PUT|POST|TRACE|DELETE|O\
+<liiwi> PTIONS|-))( (?P<url>.*) (?P<proto>[^ ].*))" (?P<status>.*) (?P<bytes>.*) "(?P<refer>.*)" "(?P<agent>[^"]+)"(($)|( (?P<stime>[^ \
+<liiwi> ]+) (?P<vhost>[^ ]+).*$))')
+<radix> BURN IN HELL
+%
+<radix> somebody dressed in a chicken suit came out during the concert and attacked them
+<radix> and they beat the crap out of it
+%
+<rc> I don't find transclusive folding to be that useful a programming
+language feature.
+<faassen> rc: well, it's an acquired taste.
+%
+<Stravad> Python has that instance id thingy for everything
+<glyph> Stravad: uh, that's not an OID, that's &foo; :-)
+<radix> glyph: !?
+<dash> radix: that's what id() does
+<radix> kill me
+<radix> I read that as an XML entity
+%
+<glyph> if zone transfers are bind fileformat
+<glyph> how much more complicated could this be than FTP? :)
+<dash> glyph: Prepare to be surprised.
+%
+<dash> it's like a bicycle
+<dash> but with internet
+%
+<dash> itamar: ok. well, given that Jesus did rise from the dead, one has to consider what this says about him
+<itamar> he was lucky?
+%
+<moshez> we aren't really a dictatorship
+<moshez> we're more like an anarchy, except WE ELIMINATE PEOPLE WE DON'T LIKE
+%
+<radix> sometimes i eat tums just cuz I like the taste
+<glyph> radix: that sounds like a bad idea
+<radix> you can never get enough calcium!@
+<glyph> radix: if your eyelids ever start sticking to your eyes, or you can't see or hear because a caky, white film has covered your eyes or ears, you may want to consider cutting back on your over-the-counter pharmiceutical intake
+<radix> hold up... let me raise the font size.
+<radix> Oh.
+%
+<moshez> nobody resizes my text terminals and lives.
+%
+<saph> radix: i've eaten nothing but a subway and a weird coconut thing my mom
+ made that has a pecan on it and i was kind of afraid of it, but i was also
+ very hungry so hunger won out on that one
+%
+<glyph> the god of unit testing is going to kill me for this code
+<Tv> There is no such thing.
+<Tv> I would have been dead by lightning for years now.
+* liiwi notices the lack of god of code commenting too
+%
+<queuetue> Are these actual people we're discussing, or another webcomic?
+%
+<glyph> ono! I have forgotten the sacred waterfall
+%
+<icepick> I, as someone who was a professional php programmer, can tell you: Think of the children
+%
+<PenguinOfDoom> I reject that approach. It has a suspicious lack of internet.
+%
+<glyph> itamar: uh, I *am* twistedmatrix.com
+%
+<PenguinOfDoom> CA is definitely like life;
+<PenguinOfDoom> When is says "bonus", it means "you are buried"
+%
+<itamar> i don't understand how COM works
+<MoonFallen> me neither
+<MoonFallen> i suspect it doesn't
+%
+<lgonze> ok, name a security flaw in browsers.
+<raph> "bugtraq browser" returns about 37,100 hits on google
+<raph> sorry i don't have the patience to sift through them all
+%
+<Yosomono> He's really a reasonable person, if you read his writing.
+<Yosomono> I mean, aside from the "lizards run the world" thing.
+%
+<glyph> AND NOW FOR A MESSAGE FROM OUR SPONSOR
+<glyph> Are you WEIRD?
+<glyph> Are you MADE OF INTERNET?
+<glyph> Use Twisted! Or die. http://www.twistedmatrix.com/
+%
+<MoonFallen> i know. but i've read too many horror stories. glyph gets run over
+by a truck, his source code gets acquired from his estate by microsoft, evil
+ensues
+%
+<radix> bah screw it
+* radix fakes it
+<glyph> radix: hooray for faking
+%
+<jml> will you take me, to build and to dist, in windows and in unix, till uninstall do we part?
+<jml> If any man here objects, let him speak now or ... "error: command 'cl.exe' failed: No such file or directory"
+%
+<dash> moshez: why's that better?
+<moshez> dash: um, because it doesn't necessitate Elijah
+<moshez> so it's more portable
+<jml> re-use for fun and prophet
+%
+<cow_2001> btw, for me python is love from first sight..
+<exarkun> and as with real love, it will fade after you copulate with it
+%
+<itamar> just because he was dating a 16 year old that one time he was
+ supposed to be doing a release...
+<gvanrossum> too much info, okay?
+%
+<itamar> liiwi: europe has no business
+<itamar> thus it can't make business mistakes
+<itamar> bunch of socialists living in caves banging rocks together
+%
+<liiwi> why not illegalize guns while they're at it?
+<chrchr> liiwi: Because they're Republicans. They love guns. It's _ideas_ they hate!
+<liiwi> let's start using shotguns to route packets
+%
+<radix> I love killing everything with the sword
+%
+<porridge> does python have an equivalent of C ternary "?:" operator?
+<Erwin> Python has the sextary operator, !@#$^&. Given expression x, each of the 6 operands of the sextary operator is evaluated depending on whether the expression is logically true, false, morally right or wrong or neither of those
+%
+<exarkun> if you're lucky it causes segfaults
+<exarkun> if you're unlucky it signals the Mothership that Earth is ripe for invasion and brings about the destruction of all mankind
+%
+<chrchr> fariseo: If PHP is like stabbing your eye sockets with a screwdriver, Python is like not stabbing your eye sockets with a screw driver.
+%
+<saph> i don't know. i've smoked more than my fair share of pot in my day and i've never shot anyone or raped or been raped by anyone
+<saph> the most i'd do is make some really fucking cool paintings
+<saph> that and played the best scrabble game of my life
+<saph> but that was under the influence of both pot and alcohol
+<radix> "floopy! it's a word! I swear it!"
+%
+<sjj> radix: I could smoke a pound of crack and still pronounce "nuclear" better than G.W.B
+%
+<saph> sjj: president is a minimum age of 35 (which i think is complete bs)
+<saph> i know people who are 28 who could run the country better than bushy
+<sjj> saph: I could argue I know people 5 years old who could run it better than bush.
+* warner knows magic 8-balls which could etc..
+<saph> sjj: a ficus plant could do better
+<dash> warner: that's why i'm voting for Inanimate Carbon Rod!
+<jml> In Rod We Trust
+%
+<sjj> moshez: I somewhat see what you meant about Gimli being the target of _lots_ of jokes in TT
+<sjj> moshez: it got a bit old after a while :\
+<Tv> Yes, the jokes fell a little.. short.
+%
+<Pahan> Comfort me, please.
+<fzZzy> no
+%
+<spiv> 11am - 6pm. For *five* whole days. And that's just one game! It's brilliant.
+<glyph> it's like the chanukah of professional sports!
+%
+* warner has done too much work with intermittent test failures
+<warner> my worst nightmares involve the alarm clock only ringing on mornings after I fall asleep on minutes ending in an even number
+%
+<Artimage> Says it will take 15 minutes
+<Tschechow> 15 _apple_ minutes.
+<Artimage> Actually, its already down to 4.
+<Tschechow> oh, you got hardware with an apple-minute-rate <1?
+%
+<exarkun> it probably doesn't even belong in the evil directory
+<dash> exarkun: why, do you have a "stupid/"?
+%
+<exarkun> bring on the dancing monkeys
+<Tenshihan> radix?
+<exarkun> That works
+%
+<moshez> jml: but euphemisms for sex are common in all languages :)
+<exarkun> moshez: what about lojban?
+<jml> exarkun: there's no record of any lojban speakers having sex. :)
+%
+<jml> If I were a girl, I'd fall in love with a bloke who wrote copious amounts of documentation.
+%
+<MoonFallen> i just signed up for a trial of o'reilly's safari thing, and noticed they had a "voodoo" topic category
+<MoonFallen> the first three books are about .NET
+<MoonFallen> i always had a feeling there was goat blood and zombies involved
+%
+<moshez> Debian: If It's Free, Insecure and Crap, We have It.
+%
+<jml> moshez: did you know that the average vegetarian walks around with 2 kgs
+of anti-establishment bile in their stomach? :)
+%
+<moshez> glyph: in the future, browsers will support google://blah blah :)
+<glyph> moshez: in the future, telepathic russians will rule the earth, and computers will be made of synthetic cheese!
+<moshez> glyph: before that
+%
+<faassen> I mean, do they say, okay, so bush looks like a born again christian with a faint hold on sanity but he's really a secular humanist and that's *good* or that's *worse*?
+<dash> faassen: he's a secular humanist with a faint hold on the english language
+%
+<glyph> I'm an un-american anti-semitic american jew! I love this country.
+%
+<hypatia> I'm not part of the American way because they don't let you own those kind of weapons around here :)
+%
+<Pahan> dash uses windows?
+<Tenshihan> his grandparents do -- so he's got some windows in his blood
+<Tenshihan> it's like being a quarter jewish
+%
+<dash> lament: well. inductive folds are pretty much the same thing as
+__get__ in python combined with the appropriate metaclass
+%
+<jml> hmmm
+<jml> Let me put it this way.
+<jml> When I read 'Brave New World' I imagined most of it to be in a place very much like Canberra.
+%
+<spiv> hypatia: I have seen snow!
+<hypatia> spiv: When? You mistook frost for snow one time :)
+%
+<dash> see, in my day, we didn't have those fancy init scripts
+<dash> just zeroes and ones
+<dash> and we used upstream bandwidth both ways
+%
+<Marvin--> well, yes, we know that /. is broken, but do you mean in some
+ particular way?
+%
+<jml> Argh!! what's happening to me. I'm melting..
+* Jerub hands jml a pamphlet : "So you've started to devolve into a primordial ooze".
+%
+<moshez> dash: well, I lent my crystal ball to itamar
+<moshez> because his broke.
+<moshez> and then he broke mine.
+<moshez> evil itamar.
+<itamar> I did not break your balls!
+%
+<Kengur> dash: r u familiar with the Nebula Device?
+<dash> Kengur: you.... *might* say that, yes
+<Erwin> Kengur: that's the self-destruction device Kirk threatens to blow up?
+<dash> Erwin: Kirk's was before the invention of C++, though
+<dash> Erwin: so they couldn't really imagine the horrificness
+%
+<dash> sourceforge's CVS server is secretly dalnet!
+%
+<moshez> Yosomono: I AM INSANE!!!!!
+%
+<etrepum> webtastic
+%
+<liiwi> hrmpf. concurrency issues are nasty
+* Tv gives liiwi two forks and invites him to join the table.
+ <liiwi> Tv: can I bring my leatherman?
+<Tv> Well, only if you give up one fork.
+<Tv> We need to have the same number of tools and eaters.
+<Tv> Otherwise it's quite unfair :)
+%
+<moshez> the common definitions say that after a person rises from his grave, he is the undead.
+<dash> moshez: Buffy definitions are not common definitions.
+<moshez> dash: Buffy could kick your ass.
+%
+<ameoba> EVERYBODY GET STONED!!!
+<dash> ameoba: furthermore
+<dash> ameoba: everybody MUST get stoned
+<ameoba> YAY EQUALITY
+%
+<z3D> dash: yup ... remember the word 'enterprise' ?
+<dash> z3D: YES! it is one of my favorite words
+%
+<chrchr> datazone: jwz didn't invent "If the only tool you have is a hammer . . ." any more than the French invented tongue kissing.
+<datazone> chrchr: my grandpa invented french kissing
+<datazone> and i will be damned if i sit here and listen to you say that he didnt
+%
+<exarkun> '''The MODE command is a dual-purpose command in IRC. It allows both usernames and channels to have their mode changed. The rationale for this choice is that one day nicknames will be obsolete'''
+<dash> yeah, and the marxist state will wither away
+%
+* radix is a monkey with a bamboo stick
+* Kengur tries to trade banana for a bamboo stick
+<radix> hah!
+* radix beats kengur on the head and steals his banana.
+<radix> You don't need trade when you've got a bamboo stick.
+%
+<lament> Software Engineering is basically a set of techniques for making bad programmers write good code.
+<dash> lament: Which is why it doesn't work.
+%
+<Yosomono> ameoba: my hand made me sleep on the couch :P
+%
+<tansaku> mathematics is so about sex
+%
+<chrchr> Spalding Grey made the point that comedians (and comic artists!) might spend a long time building up to the punchline at the end of the joke, while a good storyteller can have the audience laughing throughout the story.
+<chrchr> Discuss.
+<exarkun> Spalding Grey is a monkey in red suspenders.
+<chrchr> exarkun: Excellent point.
+<chrchr> Everyone, how does exarkun's point about Spalding Grey being a monkey in red suspenders make you feel?
+%
+<kiko> is it just me or is the term "software design" too vague to shake a stick at?
+<exarkun> kiko: get a bigger stick
+%
+<jml> dash: is quoting pokey like mentioning the holocaust on usenet?
+<dash> jml: nope
+<dash> jml: quoting pokey is recommended practice
+<jml> dash: but, it makes retort impossible.
+<dash> jml: you're beginning to understand
+%
+<radix> HORRIBLE JAVASCRIPT ARGh
+<moshez> radix: when a teenage suicidal brooding girl builds a web site, what do you expect?
+%
+<itamar> glyph: spyce.sf.net
+<glyph> GYAH
+<glyph> yes, I've seen this before
+<glyph> I believe it was looking at this page that I coined the phrase "captain Dimwit McStupid"
+%
+<fatjim> quotemaster: i know a guy on gimpnet who wrote his entire website in bash
+<fatjim> quotemaster: then he wrote an ircbot in bash
+<fatjim> quotemaster: then his head exploded and we had to burn the entire building to prevent an epidemic
+%
+<dash> Iä, const char* fhtagn
+%
+<radix> When I take PCP, I pretend that I'm half-leprechaun-half-cheetah
+<dash> "You feel jumpy."
+%
+<Acapnotic> Are there sound effects that accompany the speaking of "Debian Project Leader"? Trumpet fanfare or choir of angles or the hushed whispering of the cabal or something?
+<_moshez> Acapnotic: the Empire music from Star Wars
+<_moshez> dam dam dam dadadam dadadam
+%
+<psy> good thing my 15yo sister also smokes rollies =P
+%
+<dash> i used to think ms creighton was just insane, but i'm reminded of a .sig on the parrot list
+<dash> "the difference betweeen insanity and genius is measured by success"
+%
+<sjj> itamar: you'll be sorry when the VIC is mainstream! you'll all be out of jobs HAHA
+%
+<Logan> You might as well ban dancing next!
+<Yosomono> Logan: We don't allow that sort of reckless behaviour here.
+<Yosomono> Logan: It leads to fraternization amongs youngsters, and eventually PREMARITAL SEX
+%
+<Logan> chrchr: Are you saying this channel is boring?
+<Logan> I think I saw something funny in here once.
+%
+<glyph> oh. where is saph?
+<exarkun> she got a job
+<glyph> Hooray!
+<exarkun> It's a mixed blessing
+<glyph> What's in the mix?
+<exarkun> I get to hear about how I don't have one a lot :P
+%
+* zookoasleep sleeps furiously.
+%
+<raph> [WebDAV] probably has all the disadvantages of a stateless protocol combined with the problems of actually implementing state
+%
+<chrchr> If I had dignity, I'd have to start wearing pants to work.
+%
+<amybah> THE REVOLUTION WILL BE TELEVISIED (to drm compliant devices)
+%
+<dash> "we wisssssh to sssquissssh, my preciousss"
+%
+<itamar> glyph freaked out my grandmother
+%
+<dash> [From the GPL FAQ:] "If the program dynamically links plug-ins, and they make function calls to each other and share data structures, we believe they form a single program"
+<dash> the days of "one process == one program" are coming to an end, i believe
+<SamB> o/` Python is a totally different way of thinking, which doesn't have much to do with linking o/`
+%
+<glyph> moshez, you are only allowed to be excited about one insipid american cultural icon at a time
+<moshez> glyph: have you not realized that at heart, I'm an american teenage girl :)
+%
+<moshez> I prefer the ones who fully embrace, knowingly and publically, the commercialization
+<moshez> than the ones who commercialize by being "non-commercial" (Eminem is the prime example here)
+<Aco> whatever... i just like girls in white panties
+%
+<lstep> It's not my fault, my wife keeps connecting under false identities to monitor me!
+%
+<wzZzy> I tried to use woven.guard sunday night
+<hazmat> and?
+<wzZzy> hazmat: glyph's powers of obfuscation are considerable
+%
+<icepick> to the batlaptop!
+%
+<moshez> radix: oh, and did I tell you I'm officially a sock puppet now?
+%
+<wzZzy> what's up mjs
+<mjs> wzZzy: nothing much, just got home from school... yourself?
+<wzZzy> mjs: workin'
+<mjs> that's what you are always doing. =)
+<wzZzy> that's cause I have a job
+%
+<raph> but if you are going to combine a scoring system with eigenvectors, it's a lot easier to think about linear things than funky bayesian formulae
+<wmf> eigenbloggers are useful, too
+* raph prepares to eigensmack wmf
+<raph> prepare to compute the eigenvalue of _this_, fool
+%
+<exarkun> nethack should be made illegal
+<exarkun> it corrupts the minds of otherwise upstanding young men
+<dash> exarkun: dont make it illegal 'til after i get this fellow into gehennom
+%
+<glyph> I am getting my design on.
+<glyph> (For those of you not already in the aisles, this means: RUN!!!!)
+<glyph> I _really_ like this one
+<glyph> it may not be appropriate for the main site, but we're going to have to use it someplace
+%
+<glyph> radix: Your credulity is not a requirement for precipitation!
+%
+<shapr> no one wants to play with my monads :-(
+%
+<ThreeSeas> what is melatonin?
+<ThreeSeas> is that like another programming language?
+%
+<gus> doesn't the phonetic representation (Zo["o]l.) mean that it should be pronounced "Zoul"?
+<glyph> I don't know...
+<glyph> I can't read phonetic.
+<gus> and all this time I thought you were phonecian.
+%
+<exarkun> Oh. The infamous "Windows sucks" bug.
+%
+<datazone> you know, america has alot of different military orginazations
+<radix> yes, maybe they'll get into a war
+%
+<radix> i want to make sweet love to twisted.python.components
+<ameoba> radix : it probably wouldn't be hard to whip up an adapter +)
+%
+<jml> _moshez: having dates would be kinda cool.
+<itamar> jml: if you take a shower every day, you'll find it's a lot more
+ likely
+%
+<trawa_@ircnet> im trying to get started with web services, but i need
+ some software - i think i read all of ibm's articles on that, read
+ about 4suite - does anyone know of sth litghter, smaller in size and
+ easier to absorb?
+<dash> trawa: hmm. what do you mean by "get started with web services"? :)
+<trawa_@ircnet> well im trying to port my html application to flash mx
+ but dont fell like using cold fusion..
+%
+<exarkun> What powers the orbital lasers
+<exarkun> I always assumed it was the Sun, but now I don't think that would work too well
+<exarkun> Unless we are anticipating zero resistance after the destruction of the Sun
+<exarkun> This sounds like a serious flaw in our plans, then
+%
+<floam> itamar: think of how the anonymous bathroom fits in with the way our society works
+%
+<floam> the paper towel represents the customers
+%
+<dash> perl has EVERYTHING.
+<brc> except buffer overflows
+%
+<wzZzy> we should write an os
+<itamar> YES
+* itamar starts a sourceforge project
+%
+<chrchr> There are some dead/preserved carpenter ants in a bag in the mailroom of the building where I live, and a sign that says, "THESE ARE CARPENTER ANTS. BEWARE!!"
+<datazone> chrchr: why do they assume ants can read, and if they can read, that they can read english?
+%
+<sayke> those whom the gods would destroy they first make l33t
+%
+<shapr> ucking keyoar
+%
+<drewp> this twisted thing really is a text adventure game
+<dash> drewp: What a ridiculous notion.
+private message from dash: wuh oh, drewp is on to us
+%
+* itamar goes to french class
+<radix> FRENCH class???
+<radix> Don't you mean FREEDOM class?
+%
+<glyph> my condolances
+<itamar> if Thunder- solves it i'll be ok
+<itamar> writing a C++ extension to open a fucking find dialog is soooo fun
+<glyph> oh
+<glyph> I meant "my condolances on failing to stop the war" :)
+%
+<moshez> glyph: I have an ethical question for you.
+<glyph> moshez: my answer, as always, is "kill them all and let god sort them out"
+%
+<snibri1> i wonder if those boost ppl ever sleep
+<jemfinch> snibri1: why?
+<AdamV> snibri1: Nightmares from C++ template coding?
+%
+<Artimage> Amazon wish lists are really a double edged sword.
+<Artimage> This guy made me his amazon friend... so I checked out his list...
+<Artimage> lots of 'how to make your own porno movies' books... I now know WAY TOO MUCH... but I have no clue who he is.
+<Artimage> I would have to know someone at least a week before I'd be interested in knowing this.
+%
+<treker> pyn ur fuckin hot
+<treker> :-Plol
+%
+<moshez> marz: don't be scared!
+<moshez> marz: the squishing is fun
+<moshez> ask anyone
+<dash> it isn't fun
+<dash> it's like a tiny genocide
+<moshez> marz: ok, ask anyone except dash
+%
+<moshez> dash: what is your profession, again?
+<dash> moshez: "irc junkie"
+<moshez> dash: good money in that, huh?
+<dash> moshez: money?
+<dash> moshez: now that you mention it
+<dash> most of my jobs lately have come from irc
+%
+[re: porting the rewrite of jelly to Scheme]
+<radix> So is it still really mutaty?
+<glyph> It's so mutaty you're going to be using 'set' with *two* exclamation points
+%
+<jml> you ever heard about cosmic conflicts between the forces of order and
+ the forces of chaos? Well, Canberra is like Order winning, stomping over the
+ frail corpse of chaos, and all spontenaity, surprise and flexibility
+ disappearing from the world.
+%
+<Spec> Damn you pyn. You are now my greatest adversary.
+%
+<-- Spec has quit ("The bell that controls our lives has released us into the world....woot.")
+%
+<exarkun> What's a good, low-cost way to notice when a new file exists in a directory?
+<SamB> exarkun: open the directory for reading, read to the end, and use select? (crazy!)
+%
+<coderman> you ever see my callback templates for use with static and member functions?
+<coderman> it works nicely, which is a credit to the STL, but it is also so incredibly ugly it makes my head hurt
+<coderman> http://cubicmetercrystal.com/alpine/gen_html/base/AppUtils/AppCallback.h.html
+<zooko> What the fuck, man? Why are you memcpying these void*'s? Where is callbackData_ defined? What are all these things templated on "class argument Type"?? AAAAiiiigh!
+* zooko holds one hand over his eyes and clicks blindly with the mouse in the attempt to hit the "close window" button.
+%
+* Acapnotic puts on his "in MY day, we had to write our modelines by hand, with nothing more than some loose notes by matt walsh, the back on an envelope, and the blood from our own hands..." pants.
+%
+<jml> the phrase "unstable reactor" makes me feel a little nervous
+%
+<etrepum> dance dance hack?
+<radix> hack hack revolution
+%
+<pyn> <widada@ircnet> this is what i see: "<pyn> <radix@fn> Huh?"
+<pyn> <princepsd@ircnet> do you want to see somethingelse?
+<pyn> <princepsd@ircnet> ;))
+<pyn> <princepsd@oftc> or is this to much for you? ;))
+<pyn> <princepsd@efnet> the bridge confuses you?
+<princepsd> you can't keep track? ;))
+%
+<exarkun> why would someone mount a swapfile from an NFS mount?
+<MoonFallen> exarkun: to bring about an anarchic dystopia
+%
+<princepsd> marxist witted is a anagram of twistedmatrix
+%
+<exarkun> z3p: conch is making me sad again
+<z3p> exarkun: conch only does ssh, it is not responsible for also making you happy
+<exarkun> Oh. Crud.
+%
+<bram> it's nice being a cult leader :-)
+<bram> now I must get my minions to start giant bonfires in the woods and do ritual dances around them
+%
+<Yosomono@efnet> glyph: Does it amuse you sometimes to realize you've created an entire genre of coding for people?
+<glyph> Yosomono: "sometimes"? It amuses me constantly. It's become the central staple of my entire sense of humor, as well as my livelihood.
+<glyph> It is easily the most amusing thing in my life.
+%
+<dash> if python is an orchestra, overloaded operators are "miscellaneous percussion"
+%
+<radix> Don't be a consumer! Live off the land
+<itamar> there are no pants-trees in nyc
+%
+<fzZzy> what good is planetary consciousness if it can't open an arbitrary socket?
+%
+<jml> can someone briefly explain componentized in the context of the web registry?
+<spiv> jml: You've got an internet cloud, right? Only, you're reading isometric, so you really want an internet *cube*.
+ So, you take your componentised cloud, and ask it for a cube adapter. Then exigency girl arrives, kicks your ass, and everyone
+ is happy.
+%
+<spiv> As far as I'm concerned, the meat pie is the ultimate unit of currency.
+%
+<Noen> fear twisted, because its leet
+%
+<glyph> jml: I PROMISE I really thought popsicle was a good idea at the time.
+%
+<jml> balsa is crying out to be re-written in Twisted
+<jml> 'heal me' 'heal me'
+<jml> 'I don't want to be threaded'
+<Acapnotic> yeah, lots of mail programs say that
+<jml> Acapnotic: It must be very easy to write GUI mail programs that suck
+<Acapnotic> it is, but a lot of people have gone above and beyond the
+ bare minimal effort required
+%
+<warner> Activate the Flux Condensor!
+<MoonFallen> We can fix it by patching the wireless with a subspace access point!
+<itamar> the powerbook's dilithium crystals are not in tune with the mr814 access point
+* warner looks through his toolbox for a transdimensional flux agitator
+<warner> uh-oh, we've got a level 3 resonance singularity
+<MoonFallen> But it'll be risky! We've got to get the harmonics oscillating
+ correctly before Buffy comes on, or we'll be dead in the water.
+<warner> Right, you align the warp power conduits while I re-fractalize the
+ positronic emitters. We meet back on the engineering level in 40
+ centons.
+%
+<warner> happy tests, fat buildmaster
+<spiv> warner: I can practically hear it yelling "Feed me, warner!" from
+ here ;)
+<spiv> Heh. Now I'm envisaging a "Little Shop of Hackers" with the
+ buildmaster as the plant (and the buildslaves for tendrils!), and poor
+ warner as Seymour :)
+<warner> FEED ME!
+<spiv> For some reason, I keep thinking of _moshez as the dentist ;)
+%
+<exarkun> radix: [dash] *is* a lazy sob
+<exarkun> radix: but that doesn't mean he's wrong
+%
+<itamar> I don't want my name in the windows registry
+<itamar> it's probably bad luck
+%
+<exarkun> it's JAVA, if you didn't want to type FIFTY LINES to do ONE STUPID THING you wouldn't be using it, right?
+%
+<jml> _moshez: yeah, but that's obviously an imposed patriarchal paradigm
+ that's entirely foreign to the implicit metanarritive.
+<flax07> not to be ot - but can anyone give some newbie help
+%
+<freeside> On a scale of One to AWESOME, twisted.web is PRETTY ABSTRACT!!!!
+%
+<rik> note to self: do not advise people to use a Deferred when in #c.
+%
+<saph> dash: when you take over the world, can i be in charge of leather and vinyl active wear?
+%
+<Nafai> w00t w00t w00t w00t!
+<Nafai> I don't understand all of the code, but it works!
+<Nafai> I guess I should check it in.
+%
+<moshez> glyph: I don't care about actual people
+<moshez> glyph: I care about not screwing up Debian
+%
+<arno> what are the main things to change going from [bsddb] 3.3 to 4.1?
+<icepick> moving around when you open transactions
+<arno> sounds like money laundering strategies...
+%
+<gt3> welcome to the world wide wtf
+%
+<glyph> warner: you want to port twisted to PyMite?
+<warner> now *that* would be entertaining..
+<Nafai> ...for various definitions of entertaining
+%
+<zooko> I don't know if I'll have much time, but my goals are: 1. test_ent.py, 2. ent.py, 3. make znff.py use ent.py, 4. rule the universe
+%
+<hypatia> moshez: That's OK, you can feature in my diary as "Glyph's bitch".
+%
+<akrherz> quotacheck <-- Do I have quota left for more questions :)
+<radix> akrherz: Insert $.25
+<akrherz> where? Can I just give my credit card?
+<radix> akrherz: That'll do.
+<akrherz> wow, the internet is fantastic
+%
+<glyph> dreid: so you have some free time?
+<dreid> glyph: not really but i could always sleep less
+%
+<jml> any world knowledge-ables about?
+<chrchr> jml: YES.
+<chrchr> jml: Oh. You mean twisted.world.
+<jml> chrchr: yes.
+<jml> chrchr: I am not aware of any other.
+%
+<wumpus> google, froogle, when are they going to start making names that make sense? :P
+<wumpus> even the twisted module names make more sense
+%
+<dash> wumpus: names that make sense are at an end
+<dash> wumpus: they have been all trademarked
+%
+<chrchr> radix: Are you at NASA? Do you see any of the aliens?
+<radix> No.
+<radix> chrchr: They don't keep the aliens at Goddard, anyway
+%
+<_moshez> dash: we need to make Twisted into, pardon the comparison, Zope
+%
+<Holocaine> moshez: I think your bad wrap comes from the fact that all of us
+ are far too postmodern for your version of correctness. =)
+%
+--- itamar has changed the topic to: "We reject kings, presidents, and voting. We believe in rough consensus and running code." -- David Clark
+<dash> also, giant robots.
+%
+<itamar> better not have children anywhere [POWERFUL CORPORATION'S NAME ELIDED -ed] can fly lawyers
+<etrepum> hah
+<etrepum> they're not that bad
+<itamar> that's what they all say
+<itamar> and then it's "but I didn't think they take *my* little Joanne!"
+%
+<dash> fortunately not all people who call themselves christians are bloodthirsty imperialists
+%
+<glyph> dreid: you want to talk about the web?
+<dreid> glyph: yes
+<glyph> dreid: I THINK THE WEB IS TERRIBLE
+<dreid> well there is always gopher
+%
+<zoyd> someone help me with using dictclient.py
+<zoyd> the dict.org client that is.
+<tappintap> zoyd: what are you trying to do. Start at the beginning, like: I
+ got up today, and I wanted to paint the shed.
+%
+<dash> there is a particular sense of fatigue that i have come to associate with the aftermath of attempting to troubleshoot windows problems
+%
+<itamar> oooh
+<itamar> Windows Server 2003 CD
+<itamar> 180 day evaluation
+<glyph> itamar: damn! that's half as good as the full product
+%
+<ivan> i can't wait till palladium!
+<ivan> cheat-free gaming at last
+%
+<jml> spiv: some might call it rewriting, I call it "refactoring from zero"
+%
+<Nafai> I need to find a reason to learn Woven. :)
+<exarkun> it will get you laid
+* Nafai thinks
+<Nafai> I'm not sure I believe you.
+%
+<jml> if I'm stuck in windows, what's a good browser?
+<jml> preferably one that implements the Aquinas protocol so I can get infinite bandwidth with no hardware upgrades
+%
+<radix> THE REAL WORLD IS JUST LIKE HIGH SCHOOL
+<radix> ARAGH
+* radix shoots himself.
+%
+<exarkun> "While working on a web framework late one night, Mr. Preston was sucked into an http vortex and trapped on the internet. Now he roams from website to website, crying at code embedded in malformed html templates and exploiting cross site scripting bugs."
+%
+<AdamV> SamB: PHP's basic control structure is the "database timeout error".
+%
+<tjs> I was telling the guys at work about woven, and they asked me to implement it in php.. I told them, without battering an eyelid, that it was totally impossible.. sometimes you just have to stand up for whats right
+%
+<tjs> they have xml parsers for php
+<dash> I AM NOT EVEN GOING TO THINK ABOUT IT
+%
+<WuN> my school went on a trip to Queens U last week (like we stayed the week
+ in the dorms and such), and i took java... i learned 3 things that week:
+ 1) i dont like java 2) lectures suck 3) not having parents is amazing
+%
+<Yosomono@efnet> chrchr: In MMORPGs, I typically play female characters too.
+ Mostly because if I'm gonna spend a lot of time looking at
+ this person's ass while they're running around, I wanna see
+ something decent.
+%
+<Tv> How does everyone feel about getting all dirty with low-level networking in twisted?
+<Tv> I'm thinking ip, udp etc.
+<Tv> As in, "here's the full packet".
+<itamar> personally, it fills me with an unholy glee
+%
+<itamar> Tv: there are people who will offer to marry you if you release this
+%
+<jml> if only I could eat whitespace
+%
+*** prell (~prell@106.165.8.67.cfl.rr.com) has joined channel #twisted
+<prell> glyph: thanks :-)
+*** prell (~prell@106.165.8.67.cfl.rr.com) has quit: Client Quit
+<liiwi> wow, a drive-by thanking
+%
+<exarkun> radix: also, I need a crossover cable.
+<radix> wth are you using cables for
+<radix> cables are gross
+<etrepum> because sometimes you want bandwidth
+<radix> yeah right!
+<etrepum> well you can go play your infocom games and I'll transfer large files
+%
+On 2003.05.23 18:54, Glyph Lefkowitz wrote:
+> On Friday, May 23, 2003, at 06:45 PM, Bob Ippolito wrote:
+> > The next big thing is to fantasize about nonexistent programming
+> > languages that make good compile and runtime decisions for you.
+>
+> Hey wait! I'm *really good* at that! Let me tell you about this paper
+> I read on linear objects...
+
+[Later that day, on IRC...]
+<radix> dash: dogg! glyph is cutting up
+<radix> dash: latest post to t-p
+<dash> radix: Yeah, i know he hates me and wants me to die
+<dash> radix: why, what's new?
+%
+<dash> cfork: if all you have are nails, there's no need to pick up the
+ biggest hammer with the poison-ivy oil on the handle every time
+%
+<zooko> I once had a party in Amsterdam, and there were two live webcams, and I was worried that people would
+<zooko> accidentally do something on camera that they didn't intend to.
+<zooko> So I spent a long time making great big signs, hard to miss even if you are fucked up, saying
+<zooko> "THERE IS A WEB CAM IN THIS ROOM" and stuff like that.
+<zooko> As soon as the party started some people pushed the cam aside and started doing lines of coke on the desktop.
+%
+<chrchr> datazone-work: Some people dominate the world because they can't hold down a regular job and like the flexible hours that world domination offers.
+%
+<fzZzy> dang. when there are so many layers of abstraction you don't understand it's tough to do a minimal test :(
+<fzZzy> and when glyph writes no docstrings
+<fzZzy> now I know how people feel trying to use woven
+%
+<Tv> Enough rope to shoot a foot in your mouth.
+<_moshez> enough mixed metaphors to grab a bull by its horns?
+<Tv> Enough mixed metaphors to grab a bull and eat it, too.
+%
+<glyph> jml: world needs help
+<spiv> glyph: Sound like you need to put an ad out for a superhero
+<glyph> spiv: all too true
+<glyph> world.save()
+%
+<moshez> the breasts are part of the cognitive dissonance
+%
+<glyph> So, how do I tell distutils to compile/link with g++ rather than gcc?
+<spiv> glyph: By typing your commands IN BLOOD
+%
+<cehteh> >>> foo()
+<cehteh> Segmentation fault
+<cehteh> doh
+<whitestar> cehteh: sucks to be foo()!
+<deltab> I pity the foo()
+%
+<itamar> ambivalent?
+<gus> kinda
+%
+<radix> how many people are we going to get posting to the list "How do I do X? I can't use <perfect solution>, it doesn't fit my design" today?
+%
+<sjj> spiv: where does your family live?
+<spiv> sjj: Singleton.
+%
+<limi> I need some sex to adjust my sleeping patterns
+<limi> the only thing that *really* works ;)
+%
+<sjj> dash: you certainly are an enigma wrapped in a riddle wrapped in a hat.
+%
+<cyli> actually I signed on because i was curious to see what people thought about the stem cell debate
+<radix> cyli: I'm with Dream Theater on that one
+<glyph> radix: You believe that the decision on the debate should involve a melodic but very complex 10-minute sample montage?
+<radix> glyph: Hell yes
+%
+<Alea> Just spent the last 6 months writing const-ridden C++ code...
+<radix> I've *never* had to copy a data structure before returning in any of my code
+<radix> Alea: We're a more, ahh, free-thinking bunch. Like hippies, you know?
+* radix passes some pot to Alea
+%
+<dash> glyph: i don't see anything else you'd want to use '' for
+<glyph> dash: a user named ''
+<dash> glyph: is there a good reason to allow users named that? :)
+<jml> : of course not
+%
+<itamar> stupid useless gods
+%
+<moshez> glyph: but I'd recommend giving him a ban
+<moshez> it's like chops
+<moshez> except better
+%
+<Jerub> Why do people punish themselves with latex?
+<anthony> Jerub: well, you see... oh. you mean LaTeX.
+%
+<cyli> maybe you can help me w/ more of woven's dynamic stuff?
+<cyli> you said it was really cool and you could do all sorts of weird things with it
+<cyli> does that involve lots of javascript?
+<glyph> yes
+<glyph> as well as blood of a Polynesian virgin
+<cyli> i thought the blood of a germanic princess
+<glyph> germanic princess is OK for static pages
+%
+<pr0le> I find that when i get sex regularly, i tend to be more productive as a programmer.
+<pr0le> conversely, when i don't get it regularly, i am more productive as a musician.
+<MoonFallen> pr0le: i'm not a musician. i guess that makes my priorities very clear.
+%
+<allexpro> why does twisted want to destroy the sun?
+<MFen> give it a gimmick in the overcrowded python network framework market?
+<MFen> you know, those feature checkboxes on those comparison pages. [x] tcp [x] udp [x] asynchronous [x] destroyed the sun
+<MFen> and on the right it would be like competitor ------ [x] tcp [x] udp [x] asynchronous [ ] destroyed the sun
+%
+<abram> Anyone up for answering a Deferred question?
+<dash> abram: ask your question now, get an answer later!
+%
+<glyph> the answer, of course, is "fuck Windows"
+%
+On 2003.06.25 05:36, Moshe Zadka wrote:
+> On Wed, 25 Jun 2003, "W.J." wrote:
+> > I really hope twisted is not going to enforce this.
+>
+> Twisted is not about enforcement. Twisted is about mocking people who are
+> using the technology in non-optimal ways.
+%
+<saph> dash: moshe rules!
+<dash> saph: maybe!
+<dash> but he'll have to fight me first
+<saph> dash: it has to be a clean fight, no stilts, no hats
+%
+<Rumor> dash: What is this, the spanish inquisition?
+<dash> rumor: This is me asking you to think.
+%
+<itamar> lets change the subject
+<glyph> itamar: Okay, let's talk about your inadequacies instead
+%
+<tic> horray! twisted is working!
+<tic> now let's see if that IRC client thingy is working as well.
+<glyph> tic: QUICK SHUT IT DOWN BEFORE I ROOT YOUR COMPUTER
+<tic> glyph, NO PLAES DONNT!
+%
+<itamar> I'm half jewish!
+<itamar> the other half is also jewish though
+%
+* rt tries to think back to his college courses. "Elementary Carnivorous Dinosaur Avoidance 101" sticks out as a particularly useful class.
+<chrchr> rt: I think you might be dating yourself.
+%
+<hefzibah> peaceniks never make up their minds - never date one.
+%
+<lament> Slashdot karma, unfortunately, is not real karma, because it doesn't involve the death of the people who have it
+%
+<glyph> spiv: FIX FTP
+<spiv> glyph: I'm a little pissed atm... don't encourage me to write code :)
+<sjj> spiv: pissed as in angry or pissed as in australian?
+%
+<Riastradh> Syntax causes cancer of the semicolon.
+<radix> syntax rules
+<Riastradh> syntax-rules rules.
+%
+<glyph> radix: PEACE AND LOVE!!!
+<radix> why
+%
+<chrchr> What's the word for a potion that makes people horny? I
+ forget the word. Wild mead is supposed to do that.
+<Erwin> alcohol
+%
+<glyph> LordVan: I don't know why people keep using twisted for all this serious stuff. It's a mud with a mailserver.
+%
+<glyph> For example - if you came in here asking "how do I use a jackhammer" we might ask "why do you need to use a jackhammer"
+<glyph> If the answer to the latter question is "to knock my grandmother's head off to let out the evil spirits that gave her cancer", then maybe the problem is actually unrelated to jackhammers
+%
+<Pahan> exarkun: Are you a brain surgeon?
+<exarkun> Pahan: I know where your brain is, and I've used a knife before, if that's what you mean.
+%
+<moshez> nobomb: the rumours that we mutilated and killed people who badmouthed Twisted are completely unsubstantiated
+<exarkun> but please stay out of the garage
+%
+<nobomb> since i learnt netscape started in a garage...i've yet to enter one
+<nobomb> something like that must be spawned from fornication carrion beasts
+<moshez> and what do the carrion beats feed on?
+<moshez> I can't tell you what they don't feed on!
+<nobomb> the limbs of mutilated twisted naysayers
+<moshez> and that's bodies of people we killed for badmouthing Twisted
+<nobomb> why not
+<moshez> because there aren't any!
+<nobomb> lies
+%
+<hypatia> I don't think charmed is so applicable there?
+<moshez> I don't think the Charmed ones got their powers a little later
+<moshez> on the bad part, spiv would die :(
+<hypatia> That's the price you pay for superpowers.
+<hypatia> Nobody gets superpowers and happiness.
+<hypatia> Sorry spiv -- I'm trading up!
+%
+<glyph> what the hell am I going to do with a dozen donuts?
+<dash> make 11 new friends
+%
+<moshez> itamar: I mean, it's fun that I can trace ancestry pretty much to the people who invented flamewars, 3k years ago
+<itamar> moshez: this would explain a lot about you, yes
+%
+<glyph> dizzyd: yeah, microdom.py vs. sux.py :)
+<radix> deathmatch!
+<glyph> radix: sux would totally whup microdom's ass
+<glyph> radix: it would be all scary dressed up in leather and chains and shit
+<glyph> and microdom would have a little bow tie
+%
+<phed> glyph: If I take care of some children, and I tell somebody else, "I just let them do whatever they want"...
+<phed> glyph: then people like you say "what if they KILL somebody! Shriek!".
+<glyph> phed: they actually say that? they say "shriek"?
+<phed> Shriek is the matingcall of people I hate
+<glyph> norway is a weird place
+%
+<dash> soon copyright will be dead
+<dash> and bookmobiles will roam the streets of america freely
+%
+<moshez> jml: I AM A STANDARD
+%
+<dizzyd> dude, life is good once you get the hang of this framework
+<dizzyd> my code just drizzles into modularity
+%
+<dash> First they [the dev. team for Evolution: Worlds, a console game]
+ break mimesis, then they break the first rule of RPGs!
+<glyph> what is the first rule of RPGs?
+<dash> schizophrenic kleptomania
+%
+<itamar> ow ow ow my head
+<fzZzy> gently down the stream
+<dash> merrily merrily merrily
+<fzZzy> itamar is in pain
+<itamar> I am going to kill you all
+%
+<moshez> I want to use ed
+<moshez> but ed has flaws :(
+<dash> i would venture to say that ed is composed entirely of flaws
+%
+<moshez> spiv: I cry wolf all the time, but that's because
+ WE'RE SURROUNDED BY WOLFS!
+%
+<itamar> I must work
+%
+<dash> ooh, i have an idea
+<dash> clone noam chomsky and hire the clones as greeters at wal-mart
+<itamar> I've never been to a walmart
+<itamar> how does that work?
+<dash> itamar: it is a big room full of stuff
+<glyph> dash: "Hello, you bourgeois military-industrial pig! Would you like
+ some coupons?"
+%
+<MFen> irc is sort of a window into the schizophrenic part of the
+ brain, i think.
+* Nafai tries to smash the window
+%
+<dunker> ah so the kqueue reactor doesn't spawn right
+<spiv> dunker: Maybe it needs a full moon in spring, like certain types of fish?
+%
+<jml> watching classic films and reading classic books is worth it, in general, just to appreciate The Simpsons more
+%
+<lac> my problem is that i have the new cisco wireless card
+<lac> and I cannot get it to work
+<lac> with my debian linux. curse it all.
+<lac> also running it makes my dishwasher go nuts
+%
+<BradB> Perl's main appeal is more social than technical. They have fun
+ tricking Perl into doing things we don't even have to think about in
+ Python.
+%
+<avida> like with enumerate(), i would go back to al my code and use enumerate ...
+<avida> im obsessed that way
+<dash> is it rad to be obsessed avida
+<avida> dash: its killer radical, indeed
+%
+<Riastradh> glyph, explain why I'm writing twisted-scheme, then...and
+ making it implementation-independant.
+<glyph> Riastradh: because you are a WONDERFUL PERSON, even though I
+ disagree with you
+%
+<itamar> why does my 1.8ghz pc take 30 seconds to delete start menu items
+<itamar> what is it *doing*
+<radix> contacting the mothership by emanating magnetic signals from the movement of the HD heads across the platters.
+%
+<itamar> oh look, these people are writing a "pragmatic language"! from scratch
+<itamar> someone get me some of those drugs
+<itamar> I don't enjoy reality any more
+%
+<Pahan> It looks like a very cool tihng.
+<Pahan> But ugh, I hate fragile software.
+<radix> Pahan: wtf, you're a l33t C++ hacker.
+<radix> Pahan: you should be used to it.
+%
+<etrepum> worst case you waste 40 bucks, best case it just works.. somewhere in the middle, you learn how to write a kernel driver
+%
+<moshez> deltab: "write to stderr" is not a logging technology
+%
+<radix> hooray! death to privatization
+%
+<fzZzy> css is like putting a bandaid on your SEVERED HEAD
+%
+<dash> well, we try
+<dash> but some of us are more trying than others
+%
+<tenth> The issue tracker that solves the issue of failing to use the issue
+tracker will rule the earth as a living god someday.
+%
+<Acapnotic> dash: how are the cookies connected to glyph's internet again?
+<dash> Acapnotic: wires
+%
+<MFen> i don't want to look at it. but someone else should. seriously
+<MFen> hard work is not my thing
+%
+<Erwin> it provides some thin abstractinos around Python objects so
+ you don't have to screw around with refcounting, but nothing much
+<exarkun> abstractinos!
+<exarkun> the elementary particle of abstraction
+<exarkun> excite them to high enough energy and they release their
+ gluons, resulting in a refactor!
+<exarkun> but don't excite them too much, or you'll end up with all
+ abstraction and no implementation!
+%
+<radix> my misfortune is that Broken Sword crashing in the same spot
+<radix> every time i try to play through it
+<radix> whyyy
+<radix> my computer sucks
+<dash> radix: yeah, this is why i don't buy games that say "broken" on the box
+%
+<Ron> I'm beginning to suspect that statically linking is a bad idea?
+<exarkun> Not if you own a lot of shares of Maxtor or Seagate
+%
+<moshez> IInsanity(moshez).squish(IHandsHaving(radix))
+%
+<radix> what the heck are you talking about
+<MFen> cyberhigh
+<Nafai> MFen: Is that a drug like snow crash?
+<MFen> almost exactly like that, but without the ninja motorcycle chase
+%
+* moshez is insane toad
+<moshez> today
+<moshez> damnit
+%
+<tjs> at least C++ is comparitivly sane
+<tjs> my day job is php
+<exarkun> tjs: uh
+<exarkun> tjs: oh
+%
+<exarkun> btw I hate imap
+%
+<etrepum> python should come with a disclaimer
+<etrepum> that says you may not want to use anything else ever again
+%
+<MFen> want to write my requirements for me?
+<radix> Sure!
+<radix> "show a dancing monkey in the about box"
+%
+<dash> foom: at one level, i'd just say "screw that, let people share objects if they're foolish enough to try"
+<foom> yes, that's called "threading"
+<foom> and everyone is foolish enough to try
+%
+<moshez> I miss the hype!!
+%
+"from experience and months of lurking, I would say the Twisted newbie
+experience is characterised by waves of confusion and euphoria."
+ -- Douglas Bagnall
+%
+<sjj> one day you will understand how it is I came to be the sole
+ owner of this lemonade
+<warner> you killed all the other kids at the lemonade stand, didn't you
+<sjj> hah hah haaah, free enterprise!
+%
+<moshez> exarkun: I will tell saph to uncynicalize you
+<itamar> with a SHOTGUN
+<itamar> the cynicism will leak out the holes
+%
+<chrchr> SamB: pirate is an implementation of Python for the Parrot VM.
+<exarkun> Argh, matey.
+%
+<radix> i like porn
+<itamar> that's not womanizing
+<itamar> that's objectification, closely related to OBJECTIVISM
+<radix> i like it a lot
+%
+<sjj> so i'm trying to keep glyph out of jail and / or financial ruin
+%
+<dash> "some guy in blue sunglasses killed the last guy who worked here. We think he was from the future."
+<glyph> dash: my sunglasses are mirrored grey, actually
+<dash> glyph: the ones you have NOW, you mean
+%
+<MFen> anything that makes glyph go OH SHIT makes me want to buy garlic and silver bullets and get a lawyer
+<MFen> the silver bullets are in case it's a werewolf, the lawyer is in case it's a lawsuit and the garlic is to protect me from the lawyer
+%
+<fzZzy> Yay! Advertising: Internal Server Error
+<fzZzy> my favorite kind of advertising
+%
+<glyph> well, well, well.
+<glyph> it worked.
+<glyph> My desk is in the correct position.
+<glyph> My internet is on.
+<dash> glyph: GENERATE REVENUE
+%
+<exarkun> rt: we could do with an out of control suicide rate
+<rt> unless you're willing to lead the way, I wouldn't go making that
+ recommendation.
+<exarkun> rt: I would, but if I hurl people off a cliff, it's murder, not
+ suicide.
+%
+<itamar> why isn't my mac shipping :(
+<MFen> they ran out of candy canes and gumdrops
+%
+<raph> of course, from a mathematical point of view, "working" and "IMAP" are probably incompatible concepts
+%
+<moshez> dance dance EVIL revolution
+<moshez> this is a game I will invent
+<moshez> it's like DDR
+<moshez> except on HEADS
+<moshez> hahahaah evil
+%
+<ivan> why the *fuck* do we have 2.4ghz devices everywhere when water resonates at 2.4ghz and we're 90% water?
+<ivan> is this a conspiracy theory to kill us all?
+<exarkun> yes, ivan.
+<exarkun> very clever.
+<exarkun> you've found them out.
+<exarkun> you realize you've killed us all, I hope?
+%
+<Acapnotic> I have a problem with edonkey though, and that is that I get hypnotized by the many parallel download meters
+<Acapnotic> one time I spent three days without eating, sleeping, or coding, just looking at the little progress meters and watching clients connect and disconnect and whatnot
+%
+<saph_w> dash: how are you?
+<dash> saph_w: better than i deserve
+%
+<dash> time to get my abstracti on
+%
+<radix> PHP doesn't have interfaces, it has REIFIED PAIN
+%
+<itamar> if I ever write a novel
+<itamar> the chapters will start with quotes from the quotefile
+%
+<exarkun> my knowledge is exceeded only by good looks and success with the ladies
+<dash> exarkun: easy to believe
+<dash> they wouldn't have far to go
+%
+<headh> where python stores its modules?
+<exarkun> internet
+<exarkun> the modules roam free in the valley of IP
+<exarkun> just beyond the IANA peaks and the black chasm of the IETF
+%
+<lament> Listening to your heart? Pfft
+<lament> it's boring
+<lament> thud thud, thud thud
+<lament> and it's always the same beat
+<lament> it's not like it goes boom -kachink-chakachaka-boom!
+%
+<hypatia> Oh well, as long as they aren't rapping in Andunaic...
+<dash> hypatia: actually
+<dash> hypatia: that would be kinda cool.
+<hypatia> dash: Impressive too, considering how small the known vocabulary is.
+<hypatia> Quenya or Sindarin would be doable.
+<hypatia> As long as you like rapping primarily about flowers, natural beauty and grief.
+%
+<slyphon> what is Andunaic?
+<dash> slyphon: Adnaic is the language of Numenor.
+<slyphon> dash: is that in south-east-asia?
+%
+<hypatia> The Noldor don't strike me as a very goth people, but, you know, maybe they've gotten with the times.
+<moshez> hypatia: like vampires, elves don't change
+<hypatia> moshez: They fade though. That's pretty goth.
+%
+<exarkun> "packaged" doesn't mean easy to install or configure
+<exarkun> it means "comes in a pretty box carried by a guy in a $800 suit"
+%
+<glyph> I agree that it would be huge amounts of fun to watch monkeys in $800 suits carrying big shiny boxes that say Twisted do a complicated ballet to the tune of "Money, Money, Money", so if you want to fund it, please send me the video tape
+%
+<parks> ill take ASN.1 over XML any day
+<dash> parks: why?
+<parks> blind bigoted hatred
+%
+<itamar> exarkun: does it do screenshots of empty landscapes?
+<itamar> if not it is NOT BEGUN
+%
+<dash> exarkun: radix is ruined for life i guess
+<radix> no!
+<radix> i will experience WONDROUS JOY for the rest of my life
+<exarkun> radix: of course you will
+<exarkun> you're only ruined from the perspective of sane people
+%
+<moshez> fuckin' jew
+<slyphon> moshez: i think they like to be called 'the messiah-challenged' these days
+%
+<cherub> yay Unspeakable Algebra
+<cherub> I assume the geometry associated with all of this involves strange many-dimensional paralleltopes which are an affront to reason, and through the corners of which unknowable evil seeps into our plane of existance
+%
+<fzZzy> hmm. what happens if an interface inherits from another interface, and I try to adapt an object which declares it implements the subclass to the base class interface?
+<Jerub> the spacetime continuum will shatter, leaving only remnants of the previous inheritance tree to forge out an existance in the rubble of a former great civilisation.
+%
+<glyph> moshez: Your interpretation of the human condition is, as always, colorful and, as always, wrong :)
+%
+<moshez> glyph: I'm always polite
+%
+<z3p> what is a good way to debug crazy errors in C modules?
+<Jerub> z3p: find -name "*.c" -exec rm {} \;
+%
+<Jerub> extremists make middle ground exist.
+<glyph> Jerub: sometimes they salt the earth as they pass over the middle ground ;-)
+%
+<gt3> i took a 2 month coding vacation and went soul searching
+<gt3> i collected a lot of souls..
+<dash> gt3: cool. what are you going to do with them?
+<gt3> sell em on ebay
+%
+<dash> whoa
+<dash> the mexican-flag thingy works like the editors at BYTE used to!
+%
+<radix> "promgrenades"?!
+<radix> that sounds like some terrorist weapon that a high schooler thought up
+%
+<chrchr> see man mount. see spot run.
+<slyphon> chrchr: as long as we don't see "man mount spot"
+%
+<radix> exarkun: I am skeptical.
+<exarkun> radix: Go skeptate elsewhere!
+<exarkun> radix: You're harshing my buzz.
+%
+<PenguinOfDoom> And the app sucks.
+<exarkun> what do I care if the app sucks!
+<PenguinOfDoom> Running sucky apps diminishes honor of your mother.
+%
+<teratorn> everything tastes better with a little internet
+%
+<slyphon> do you know what guido said about why python didn't have an optimizing native code compiler?
+<radix> he said "i like meatloaf"
+%
+<fzZzy> why is the king in yellow paperback 20 bucks :(
+<glyph> fzZzy: I *seriously* hope you mean "The Yellow Sign" or something
+<glyph> fzZzy: if you found an actual copy of The King in Yellow, DON'T TALK ABOUT IT HERE
+%
+<radix> haha! fear my bamboo stick
+<radix> thwap! swip! donk!
+<itamar> donk?
+<radix> itamar: yeah. stabbing in the forehead with a bamboo stick makes that sound.
+%
+<dash> exarkun: CULTURAL OSMOSIS
+<Glammie> Perhaps the expression has percolated throughout a variety of social media without retaining the tag of its orig
+<Glammie> dash, jesus christ.. I just typed a whole sentence, and you say the same damn thing in 2 words. Damn you for your conciseness!
+<dash> Glammie: PERSPICACITY WOO
+%
+<glyph> fzZzy: It always starts with one harmless little branch tag, and pretty soon, you've got a revision in each hand, and you're snorting crushed revisions off the ass of a 12 year old boy you call "revision"
+%
+<MFen> i swear to god c programmers must do #include <buffer_overflow.h>
+* MFen upgrades his servers. again. hooray debian
+<exarkun> MFen: yes! except it's spelled <string.h>
+<exarkun> i think it's short for "... long embarassing string of security vulnerabilities ..."
+%
+<saph_w> i think buildbot should be renamed vlad
+<saph_w> and be given scripts to talk about makeout sessions it's had
+%
+<exarkun> all twisted has in the way of ipv6 support is Twisted/sandbox/exarkun/ipv6.py
+<exarkun> Which someone should rewrite as an internet newapp service and drop in a more useful location
+<glyph> exarkun: hum
+<glyph> exarkun: I suppose we should take our lead from DNS
+<glyph> exarkun: and add a function to the reactor called
+<glyph> listenTCPTCPTCPTCP
+%
+<moshez> we can program in morse code
+<dash> moshez: will you release it under an Open Morse license?!@
+%
+<mesozoic> fzZzy: uh... when you click "Read Next Message", and the entire things refreshes using JavaScript instead of simply opening another page, I think you're adding unneeded layers of complexity.
+<fzZzy> mesozoic: that's awesome!
+%
+<mesozoic> fzZzy: it was the way the whole thing tied together. It was like GOTO graduated and became a design methodism for web applications.
+<fzZzy> there is no design methodology for web applications! that's the best part
+%
+<tenbytes> fag
+<dash> this channel is made of LOVE AND PEACE!!
+<tenbytes> oh
+%
+<glyph> dash: Well, I'll give you a hint. A certain UNIX vendor is going cross-country with an advertising campaign, and warner says he hasn't "been physically ejected from a conference in ages"
+ -- warner and glyph plan a visit to a SCO Q&A session
+%
+<MFen> huh. microsoft has a license _compiler_
+<MFen> i guess you need special tools to inject a program with pure evil
+%
+<blanu> The waste sub-project was/is going to be IRC over Chord basically.
+<arma> will it be "invisible"? will it have a "2" in the name?
+<blanu> Doubtful.
+<blanu> It will probably be rolled in crispy crust of crack though.
+%
+<blanu> I figure if you're going to write a new chat system, you might as well be mostly insane about it.
+<blanu> Since it's doomed anyway.
+<arma> will it be deniable, at least?
+<blanu> No, I plan to take full responsibility for writing it even if it's silly. After all I wrote IRC over Freenet.
+%
+<glyph> I want PB to be a service to rival HTTP, which means that it needs to be able to *do something* when you just type "pb.blahblah.com" and then slather enough drool on the enter key to depress it
+%
+* slyphon has a roomate (best friend from high school) that is going for a degree in being a lazy no-job having mooch
+<slyphon> but in a loveable way
+%
+<nazca> can anyone think of a good method of pursuding my college network admin that installing python and twisted on to the application server would be a Good Idea (tm)
+<radix> nazca: well, why do you need it? :)
+<nazca> i need it for working on software projects that eat more time than the college course ;) i'm not telling them that or they'll give me more work
+%
+<saph> YES I AM A FEMALE
+<saph> FEAR MY BOOBIE POWER
+%
+<grib> don't worry, I have a pentagram around my Aeron
+%
+--> orangecat has joined #twisted
+<orangecat> I was just on my way to the bar to pick up some internet
+ and wondered if everyone had enough enterprise. No facilitated
+ client-based XML quality vector refills? Budweiser?
+<-- orangecat has left #twisted
+%
+<etrepum> what are we plotting?
+<dash> etrepum: world domination
+%
+<raph> imagine how many fewer problems we'd have if everybody on the planet was an asynchronous network protocol programmer
+<clausen> raph: I think everyone would starve :p
+%a
+<kiko> I had a friend who started accessing objects concurrently
+<kiko> he ended up in rehab with a triple X tatooed on his face
+%
+<moshez> glyph is the leerless feeder!
+%
+<exarkun> I'll go add <br> tags at the end of every line
+<exarkun> I think that is how you add spaces to HTML
+%
+<Artimage> I believe a bar chart can confirm my humanity.
+%
+<radix> moshez: you are very nice to me, and girls in general
+%
+<radix> naked naked naked
+%
+<dhess> has anyone written a blog application server in twisted?
+<dhess> i'm tempted to use plone but it's a little overly complicated for my needs
+<exarkun> heh heh
+* exarkun throws himself off a cliff
+%
+<itamar> that was a wasted 30 minutes
+* itamar curses tenth's halloween special
+%
+<exarkun> stay the hell away from my corpse
+%
+<glyph> amiaaaaornot.com
+ [the ipv6 equivalent of 'whatsmyip.com' -ed]
+%
+<coderman> i think blanu disappeared
+<GabeW> wow
+<GabeW> does he have the ring?
+<coderman> is that a euphamism for the clap?
+<wmf> somehow I don't see AaronSw getting the clap
+<coderman> did aaron and blanu go somewhere? there is probably a big conference going on that im blissfuly clueless about like usual
+<GabeW> coderman: is this how blanu and AaronSw got the clap?
+%
+<exarkun> it is hard not to think I am FUCKING INSANELY AWESOME when everything I do turns out so amazingly well
+%
+<slyphon> YAY SEGFAULTING!
+* PenguinOfDoom is continually amazed at how a segfault is such a joyful occurence for Python programmers.
+<itamar> PenguinOfDoom: it is somewhat like seeing someone levitating and then kicking you in the face
+<itamar> it hurts, but at the same time you are distracted by the violation of your concept of reality
+%
+<moshez> pfote: anthopomorphising is the most powerful weapon in the fight against complexity
+<glyph> moshez: don't you mean "anthropomorphism is the most powerful warrior in the fight against complexity"? :)
+<MFen> i prefer to simianize
+%
+<moshez> glyph: I see your problem
+<moshez> glyph: you are trying to do impossible things
+<dash> moshez: nothing else is interesting
+<moshez> dash: perhaps! but impossible things are notoriously hard
+%
+<phed> symbiont: you spot the flamewars as those neverending staircases of posts. and those who say something not flameable, as those with one or none replies.
+<phed> at the end of the staircase, hitler is mentioned.
+<symbiont> yes, hitler is always a component of flamewars
+<symbiont> can we encapsulate hitler in an xml document?
+<slyphon> he'll make sure all of your documents are only in pure german
+<symbiont> achtung baby!
+<slyphon> you could use it for mail, but then you'd just hear the chant over and over...
+<slyphon> "SIG!"
+<slyphon> "FILE!"
+<slyphon> "SIG!"
+<slyphon> "FILE!"
+%
+<SamB> they should label crap "tragedy"
+<saph_w> no, they should label crap "crap"
+%
+* aum sits back while mnet builds the kitchen sink, the earth, the heavens, the beasts of the land and the fowls of the air
+%
+<anthony> anyone that would give out sexual favours to get access to an imap server... sheesh.
+%
+<tmcvs> Commit from sjj (changed 2) in 2 subdirs of Twisted: "add copyright info" msn_example.py, test_msn.py
+<sjj> Now all I have to do is wait for them to deport glyph to syria.
+%
+<ilikewine> that mp3 sounds like that band is from williamsburg
+<ilikewine> its an awful place
+<ilikewine> where everybody wears stripes
+%
+<saph> i think the same drunken circus bears who taught radix to type taught him to drive
+%
+<moshez> exarkun: the user can win by setting ulimit
+<moshez> exarkun: haha you lose
+<exarkun> Uh
+<exarkun> The goal is not to defeat the user.
+<moshez> exarkun: what kind of screwed up software do you write?
+%
+<radix> PenguinOfDoom: *you're* a jew too? goddamnit
+<radix> what is up with you people
+<PenguinOfDoom> radix: Only a halfjew!
+<PenguinOfDoom> radix: I get +5 racial bonus to antisemitism.
+<PenguinOfDoom> radix: It's like half-elves and whatever they do best, archery or something.
+%
+<radix> someone should do a statistical analysis of the religions of Twisted developers
+<exarkun> Doesn't that cvs stats tool emit a graph for that?
+<exarkun> It should
+%
+<fzZzy> do not stare into the xhtml
+<kwaker> it will make my eyes to pop
+<kwaker> and my wife to leave me
+<kwaker> and my hard drive to burn in flames
+<fzZzy> yes
+%
+<exarkun> Nagle is a little gnome who ships inside every TCP stack
+<exarkun> He grabs your sockets and squeezes them
+<exarkun> So the bits can't fit through
+<exarkun> Later, he lets go
+<exarkun> Turning off TCP_NODELAY hits Nagle with a sledge hammer.
+<exarkun> While he's unconscious, no one does the job of squeezing your sockets.
+%
+<PenguinOfDoom> dash left me!
+<PenguinOfDoom> aaaaaaaa
+<itamar> PenguinOfDoom: there will be other men
+* PenguinOfDoom hits itamar with a brick.
+%
+<saph_w> moshe: telling friends to read /. is like telling friends to go to radio shack
+%
+<slyphon> spiv: hey look, if i had to maintain that crap ass protocol i'd be slacking too
+* slyphon suddenly has a horrible moment of clarity
+%
+<frankie> smile! you are on-line at linux-day in italy!!!
+<frankie> say something nice, plz :)
+<aj> hello, this is the Debian Release Manager, please, please
+ try a different distro! (arrrggh, the pressure, the pressure!)
+%
+<PenguinOfDoom> exarkun: I have a desire to rearrange your internal organs in the way you rearrange words.
+<saph> PoD: please keep his kidneys out of his nose
+<PenguinOfDoom> saph: Why?
+<saph> PoD: because i don't want to have to explain it in the holiday cards!
+<PenguinOfDoom> saph: Okay.
+%
+<zooko> I think there should be a "maybe" button next to "ok", "cancel" in all dialogs.
+<zooko> [okay] [cancel] [I'm not sure]
+<LotR> and the dialog would vanish and then reappear?
+<zooko> I'm not sure.
+%
+<danfaust> What makes you a Superjew? Pork bounces off your chest?
+[See http://www.timeoutny.com/427.cover.html -ed]
+%
+<jml> It is like wandering through the desert, for weeks, without a
+drop of water, but knowing that there is an oasis ahead. And all the
+time, I am being stabbed in the face with a blunt spoon by a flatulent
+person.
+ -- jml describes PHP, after using nevow
+%
+<crw> don't talk to teachers when you first wake up, it floods back all the crap you had to do in school.
+<crw> and if the conversation begins with "i'll give you three guesses to figure out who this is", just hang up on them and go back to sleep. :P
+%
+<gus> "Je suis en train d'avoir les presqu'impossibles du travail"
+<gus> it means I CAN'T CONCENTRATE ON MY RESEARCH
+%
+<MFen> give me correctness or give me death
+<glyph> MFen: I wouldn't say that, standing so close to a windows machine.
+%
+<PenguinOfDoom> Grr. Python saps my will to write software. Anything I'd want to write is either boring, impossible or already written.
+%
+<AccorDNGuy> You know, my academic career would've been more interesting if I'd answered my exams with porn stories.
+<AccorDNGuy> "Explain AVL Trees." "Winer undid his zipper, casting a longing glance at Dvorak, who returned his smouldering gaze. There was going to be some serious pole-smoking tonight."
+%
+<dash> radix: are you jewish yet?
+<radix> dash: Not yet
+<dash> radix: me neither! what's taking so long
+%
+<slyphon> then I'm the happiest loser in the phone book!
+%
+* lebowski was talking to a guy in the pub the other night who once got stopped by a gang in NI
+<lebowski> "Are you a catholic or a protestant?" they asked
+<lebowski> "Er... neither, I'm an athiest"
+<lebowski> "... Aye, but are you a catholic athiest or a protestant atheist?"
+%
+<maciej> wow. Spam with ex-girlfriend's name.
+<maciej> that extra little twist-o-the-knife
+<maciej> "my ex is suddenly writing.... and she wants to increase my WHAT?"
+%
+<lapsly> dash has the coolest hat
+<lapsly> he looked like a pimp walkin around chinatown
+%
+<maciej> I like to save my timidity for actual human interaction, where it belongs
+%
+<exarkun> usecrack: --compiler
+%
+<slyphon> has anyone thought about a squid-like caching-proxy server thingy for twisted?
+<`anthony> slyphon: what, hideously complex, consuming enormous amounts of resources, and buggy as fuck? Not particularly.
+<`anthony> Or do you mean instead slimy with long tentacles
+%
+<maciej> I almost took my cat to the vet for a strange skin condition before my girlfriend reminded me cats are mammals
+%
+<itamar> sex is not digital, it is analog
+%
+ * warner wishes for the zillionth time that he could just grep his closet
+%
+<dash> isn't it cute?
+<spiv> dash: Cute like a baby choking on an ice-cream cone that's been rammed down it's throat.
+<dash> spiv: "frees up your hands from holding the ice cream cone, but leaves the bottom soggy"?
+%
+<itamar> is there a libmandelbrot?
+<exarkun> itamar: people who write re-usable software don't spend years hand-tuning a 6 instruction inner loop!
+<exarkun> And vice versa
+%
+<shawn> the highest calling of technical book writers is to destroy the sun
+%
+<hypatia> Why does dirdbm exist, exactly?
+<spiv> hypatia: Because glyph is trigger-happy when it comes to writing persistence systems.
+<spiv> "Hey Rocky, watch me pull a persistence system out of my hat!"
+<spiv> "But that trick never works!"
+<spiv> "This time for *sure*!"
+%
+<_joshua> From what I can tell, there are two kinds of interviewers: ones that ask a bunch of silly questions, you answer them, they take one bad quote and make you look bad
+<_joshua> and the other ones that engage in a long dialogue, discuss back and forth, really understand what's going on
+<_joshua> and then take one bad quote and make you look bad
+%
+<exarkun> today's lesson
+<exarkun> don't strace X in an xterm
+%
+<glyph> exarkun: any thoughts on what to do about actions having consequences?
+%
+<Pahan> glyph: I have no time for your Zen crap! I have hardware to burn.
+%
+<symbiont> been doing industrial C for three years
+<slyphon> wow
+<slyphon> i learned python about 2 years ago and all of my C skillz have left me
+<symbiont> yeah, it's all a hack
+%
+<saph> dash: when did you get a day job
+<saph> dash: i thought you just taught dancing and sold trinkets as part of a band of traveling cyber gypsies
+<dash> i only teach dancing to beautiful women
+<dash> for free
+%
+<_joshua> perhaps we could have some sort of sacrificial goat technology where people decide collectively that someone absolutely must get laid
+<tangra> for the good of the state
+<tangra> kind of like the draft lottery
+%
+<itamar> jml: the Sex Pistols have no songs about Hillary Clinton
+<jml> itamar: an unfortunate accident of history
+%
+<dash> Demoscene? wasn't he a greek philosopher
+%
+<slyphon> dammit! my upstream just sucks
+<slyphon> 20 kb/sec!
+<slyphon> if i'm lucky!
+<exarkun> install an optimization
+<slyphon> how?
+<slyphon> ifconfig eth0 --don't-suck-upstream up??
+%
+<radix> I bit my finger
+<exarkun> radix: Yay
+<radix> and it hurt real bad
+<Riastradh> radix, um, why did you bite your finger?
+<radix> Cuz it was holding a waffle
+<exarkun> radix: Hahaha
+<radix> :(
+<exarkun> radix: Awesome
+<radix> I bit it really, really hard
+<Riastradh> radix, does your finger really look that much like a waffle that you bit your finger instead of the waffle?
+<radix> I had to lie down afterwards
+<radix> Riastradh: no, i wasn't looking at it at the time
+<radix> Riastradh: I was busy stuffing it into my mouth
+%
+<itamar> glyph: did you see the multiplayer go written with twisted?
+<exarkun> itamar: *massively* multiplayer!
+<itamar> yeah right
+<itamar> only like three people in the whole world play Go
+%
+<exarkun> I mean, uh, her character has many unresolved personal issues that she projects onto other people around her without reason.
+<glyph> exarkun: that sounds like pretty much all TV people
+<exarkun> Gumby never took out his unresolved personal issues on Pokey
+<glyph> you don't think so?
+<glyph> I thought that Pokey's whole _life_ was gumby's unresolved personal issues
+<glyph> like this memorable sequence:
+<exarkun> Well, they were always friendly enough on camera
+<glyph> gumby: HAHA STUPID QUADRUPED
+<glyph> pokey: shut up! I hate you!
+<glyph> gumby: FOUR LEGS FOUR LEGS, WHERE ARE YOUR HANDS HAHA
+<exarkun> glyph: I think I missed that episode.
+<glyph> pokey: one day you will be hurt by someone close to you the same way!
+<dash> exarkun: i think glyph watched tv in an alternate universe
+<exarkun> dash: That seems likely!
+<glyph> exarkun: it was right before pokey got a cameo on NYPD Blue
+%
+<slyphon> but then again my temple is so reform it's called "Our Lady of the Immaculate Livingroom"
+%
+<mcunixjr> i was sitting downstairs, and my 2.5 yr old was sitting next to me, she has the Flu, 102 temp
+<mcunixjr> she turns to me and says "i dont feel good"
+<mcunixjr> and at the last word, out came dinner
+<mcunixjr> onto my lap
+<mcunixjr> and onto my Powerbook 12"
+%
+--> foom (~jknight@128.52.220.152) has joined #twisted
+<-- Moof (~moof@horus.kaotix.co.uk) has left #twisted
+%
+<maciej> Poles are wondering why they are paying millions of $$ out of pocket to occupy Iraq on behalf of the US, and not seeing the slightest benefit
+<markp> i believe the benefit is that we'll bump you down a few notches on the list of "countries we'll invade next"
+<brkchrmr> Doesn't Poland get a +5 to be invaded on every roll? ;)
+%
+<MFen> i'm sorry, but does this scream DANGER DANGER WARNING to you? "e"
+<MFen> no it does not. but a big floating eyeball does!
+<exarkun> MFen: you obviously lack an adventurers keen senses!
+<exarkun> "e" strikes the deepest terror into my heart.
+%
+<exarkun> speak of the devil
+<moshez> exarkun: froor
+<exarkun> not you
+%
+<jml> are there any really really good wysiwyg (or close) HTML editors?
+<jml> I mean, amazingly good, XHTML-spewing editors.
+<radix> hahaha!@!@!R!A@!@!@!@!@!#@$!#*
+>>> radix stabs software
+<jml> radix: a man can dream
+<radix> jml: your question fills me with burning rage
+<jml> radix: why is this?
+<radix> jml: i hate web
+<Jerub> radix: you are in #twisted.web
+<radix> Jerub: yes
+<radix> Jerub: that is why I am FULL OF RAGE
+%
+<KevinMarks> 'Our series A round is to help us build out the Other Plane; look at the returns possible once we transcend the mortal universe'
+%
+<maciej> It strikes me that Cthulhu can only effect change by altering the order things are eaten in
+<maciej> just like Alan Greenspan can only raise or lower interest rates
+%
+* Suw has never seen a wall mounted cat.
+<maciej> Suw: give me ten minutes and a stapler and I'll show you
+<Suw> maciej: like to see you try that trick with Fflwff
+<maciej> twenty minutes and a staple gun
+<Suw> maciej: you haven't seen her claws...
+<maciej> thirty minutes, asbestos gloves, and a hydraulic nail gun
+<Suw> maciej: asbestos gloves? that would never cut it. she'd be at your jugular before you could say 'argh'.
+<maciej> forty minutes, a torniquet, and a double-wide roll of duct tape
+<Suw> maciej: she's way too slippery for that.
+<maciej> fifty minutes and a large sheet of Velcro
+<Suw> maciej: ok, that might work
+%
+<Nafai> Sheesh. Why is downloading stuff so hard?
+<exarkun> try typing in real credit card numbers
+%
+<saph_b> radix: minnesotans invented the frozen pizza
+<radix> saph_b: i love minnesotans
+%
+<exarkun> Dang
+<exarkun> A channel even more fascist than #python
+<exarkun> == kick exarkun off #hurd by neal (Rule #1 of 1: no nodding)
+%
+<ivan> i'm almost done rewriting python in python
+<ivan> i can't believe it took those pypy guys years
+%
+<REDROBOT> MEET MY SECOND COUSIN 'ONHOLDTONE'
+%
+<`anthony> I think I shall refer to Guido as "Tallest" from now on.
+<`anthony> I'm not sure who Zim would be. Maybe Ping.
+[Referring to http://pycon.org/images/mastheadphohtos2.jpg]
+%
+<radix> code is for grunts, not software architects
+<jml> architects are merely coders without keyboards
+<radix> yes, lacking keyboards is a sign of prestige
+* jml throws his keyboard at radix
+<jml> I am prestigous
+%
+<exarkun> I have a gig of ram, after all
+<exarkun> and other people are below my threshhold of attention
+%
+<mingus> pynfo: kick exarkun for abuse of power
+<pynfo> You aren't allowed to do that.
+%
+<PenguinOfDoom> So wait, oekaki is also some stupid Java applet that crashes
+ and stuff, right?
+<PenguinOfDoom> I think it probably has builtin tools for drawing anime boobs.
+%
+<mingus> emacs is /da-bomb/
+<mingus> it's exactly all the things i wanted vim to be but never was
+<glyph> mingus: the sad part is, it's exactly all the things bram wanted vim to be but never was; vim is the trophy of ignorance's triumph over laziness
+<glyph> but hey, it's, uh, faster to start
+%
+<dash> slyphon: sure, but this has happened before
+<slyphon> dash: when?
+<symbiont> glyph: such as the working people paying off debt
+<slyphon> dash: and how many vetoes
+<dash> slyphon: dagnab it
+<dash> slyphon: you're harshing my rhetorical buzz
+<slyphon> dash: you have taught me well
+<slyphon> ;)
+<dash> hracht! i gotta stop teaching you stuff.
+%
+<slyphon> why is it that when women get pms and they give you a hard time about nothing that the _LAST_ thing on _EARTH_ you can suggest to them is that they might be the teensiest, weensiest bit on edge because they have PMS?
+<Yosomono> slyphon: Because you are basically telling them that their feelings are the result of chemical imbalance, and therefore unimportant.
+<slyphon> BUT THEY ARE!
+<saph_w> slyphon: because it belittles what they're feeling to being simply a hormonal response
+<Yosomono> slyphon: Good luck buddy.
+%
+<MFen> i think i've finally become an abstronaut
+<MFen> i'm able to break source into its fundamental abstractinos
+<MFen> pretty soon i'll be writing a mud engine just so i can rewrite it again from scratch
+%
+<dash> what the heck, i think i pushed the wrong button in emacs
+<dash> "Pinging loginfo.py (Paraguay)..."
+%
+<symbiont> btw, i've noticed that the word "federated" is not in the Twisted source tree, should i file a roundup on this issue?
+%
+<saph_w> i suppose he would have to find a good contact to the underground prior to his cat transformation so he can purchase a wig there
+%
+<cyli> Is Michael Eisner Trent Eisner's son?
+ [Since people keep asking about this one, say it slowly:
+ Trent Reznor. Trent rEznor. Trent Eizner. Get it? -ed]
+%
+<Yosomono> You guys need to stream pycon online.
+<dash> Yosomono: all the other people trying to use the wifi would hate us
+<Yosomono> dash: It should be a standard feature of the con.
+<dash> Yosomono: Maybe so.
+<Yosomono> dash: This is the year 2004 for cripesake.
+<saph_w> Yosomono: maybe the japs should give us our fucking flying cars, then we'll talk about streaming video!
+%
+<jml> there's a book out called 'implementing CIFS'
+<jml> anyone want to buy it for me?
+<exarkun> why didn't they just implement CIFS and sell a CD containing the implementation?
+<jml> because people like me would re-implement it anyway
+%
+<lemonodor> it's canadian, you know.
+<lemonodor> er, i mean, written in lisp.
+%
+* radix sads at spacelessness
+<moshez> radix: don't sad
+<moshez> radix: happy at contentfulness
+%
+<phobos> but like, when you were 16-22, (maybe you still are), most sexual contact you obtained was through just 'hooking up', i.e. meeting someone at a party, stoned and/or drunk, and doing things for the night only, right?
+<maciej> phobos: no, most sexual contact I obtained was with myself
+<maciej> and that was a long-term relationship
+%
+* radix remembers twisted.web.html.Interface, feels nostalgia
+<radix> wait, no. that's not nostalgia, that's horror
+%
+<exarkun> chrchr: A great man once called cotton the fabric of our lives. Is that not more important than the fabric of our society?
+<exarkun> chrchr: For what is society without livelyhood?
+<jml> exarkun: IRC?
+%
+<saph> what is gentoo again?
+%
+<MFen> exceptions.ImportError No module named win32com.gen_py.565783C6-CB41-11D1-8B02-00600806D9B6x0x1x2
+<czth> i wouldn't name a _dog_ win32com.gen_py.565783C6-CB41-11D1-8B02-00600806D9B6x0x1x2
+%
+<etrepum> Jokes around here tend to get followed by implementations.
+%
+<dreid> jml: i thought warner wanted to be a Problem object that can be passed via jelly
+%
+<chrchr> PenguinOfDoom: Also, what non-sucky HTTP server? What would you use, besides twisted.web?
+<glyphG4> chrchr: roxen!
+<chrchr> glyphG4: Roxen? Really??
+<glyphG4> chrchr: no, not really
+<glyphG4> chrchr: I write all my own crap so I don't have to deal with questions like this
+%
+<radix> INSTALL says that panda will take 1-2 hours to compile
+<radix> <3 C++
+<dash> c++, it gives you free time!
+%
+<PenguinOfDoom> omfg yes, another weapon to stab radix when he claims that X is useful.
+<dash> PenguinOfDoom: what's useful-er
+<MFen> flash cards
+<MFen> and a rotor to display them very quickly
+%
+<KevinMarks> I really like that unicode has a code point for 'snowman with a hat on'
+%
+<moshez> but yes, theoretically Ogg could replace tar :)
+<MFen> you could have subtitles while you're untarring
+<MFen> "Look! another directory!"
+%
+<redheadatwork> Well, our seder consists mainly of the four questions, which I do twice (once the real ones, once a set I make up on the fly), and a lot of food. Sometimes a song.
+<redheadatwork> And an orange on the seder plate.
+<furan> Before you you see:
+<furan> An orange on a cedar plate.
+<furan> take orange
+<furan> You cannot take the orange. It is firmly fastened to the cedar plate.
+<furan> Take plate
+<furan> You successfully take the cedar plate.
+<furan> #joiito: the adventure game
+<furan> "now with graphics!"
+%
+<Suw> oh, i'm having problems thinking in english.
+<Suw> i keep wanting ot type in welsh instead
+<ChrisDodo> pobol y cwm?
+<Suw> lol
+<Suw> dw i'm yn edrych pobol y cwm
+<Suw> cachu ydy o
+* ChrisDodo turns on english subtitles
+<jeanniecool> "probably you'll come?"
+<jeanniecool> "lol"
+<jeanniecool> "duh I'm probably about ready to come"
+<jeanniecool> "catch yo daddy"
+* shiruken sniffles
+<shiruken> welsh is such a beautiful language
+%
+<cablehead> who would win in a fight between a lion and a monkey ( with a bag of rocks )
+<dash> that's boring
+<dash> ask who would win in a fight between a monkey and a pirate
+<exarkun> that's boring
+<exarkun> ask who would win in a fight between a pirate monkey and a bag of lions
+%
+<dash> i am trying to get Asterisk to work
+<dash> it is stabbing me in the face
+<dreid> yes ... i seem to recall that feature in the documentation
+%
+<radix> it's stupid
+<slyphon> it is?
+<radix> the proper solution is to use an alternative implementation of time
+%
+<radix> So, I guess the reason you chose ftp as a discovery protocol is because it's a semi-ubiquitous anonymous protocol that allows people to communicate?
+<edsuom> No, because I was stupid
+%
+<riptor> flashback to 1945, nazi makes portal (how? who cares), demon pops out, us troops find demon, give it a candybar, name it hellboy <- plot
+%
+<dash> i think i want to implement simulacrum in common lisp
+<exarkun> no
+<exarkun> go away
+<dash> i know i know
+<dash> it's a personal problem
+<dash> but therapy is expensive
+%
+<warner> although.. actually several of my projects are violently battling for the dubious honor of being the least likely to turn into cash
+%
+<PenguinOfDoom> saph: What did you write to your mom?
+<saph> PenguinOfDoom: about being happy that she's home fine from the hospital and how i feel lucky she's ok and stuff
+<saph> PenguinOfDoom: and i told her that her bonsai will eventually give fruit, but i don't know if she can eat it
+<itamar> saph: is that a metaphor for grandchildren?
+%
+<MFen> exarkun: my brain is the size of a pickup truck
+<slyphon> HAH
+<exarkun> MFen: ah!
+<slyphon> MFen: that's nothin, Jesus built my hot rod!
+<MFen> hehe
+<radix> psh
+<radix> satan *is* my motor!
+<slyphon> :D
+<MFen> radix: you HAVE been practicing!
+%
+<Logan> Although I'm fighting for it, my boss thinks the customer wants it done in C++ or, even worse, Java.
+<Logan> But I told him it'd quadruple the cost. :P
+<PenguinOfDoom> Logan: What does the customer care, anyway?
+<Logan> PenguinOfDoom: That's what I said. It's like dictating what brand of toothpaste your plumber brushes his teeth with.
+%
+<radix> A VoIP server "powered entirely by stabbing, that I made out of this gun I had"
+%
+<exarkun> I can't tell if HP-UX sucks /even more/ than last time I used it or if somehow my terminal settings are causing non-deterministic behavior
+<exarkun> For example!
+<exarkun> T.................................................................................SSSS........Changing password for jcalder9 on NIS server
+<exarkun> Old NIS password:
+<exarkun> I hit <enter> and the tests proceeded
+<exarkun> Do we call passwd in our tests or something? :)
+%
+<itamar> 22 bugs and I'm a free man
+<PenguinOfDoom> A slave contract under an entymologist?
+%
+<slyphon> slyphon: mock mock mock
+<PenguinOfDoom> slyphon: Did you grab the wrong end of a mockery gun?
+%
+<PenguinOfDoom> Being enlightened gentlemen, we split all programming languages into two groups, sucks and doesn't-suck and put all of them into the first group.
+%
+<itamar> what are you going to do at cisco?
+<exarkun> rot and die, I bet
+<PenguinOfDoom> itamar: IOS debuggery.
+<exarkun> woo I win
+%
+<SamB> what do interfaces do when you call them? is that even allowed?
+<exarkun> welcome to the year 1973.
+<exarkun> callable interfaces roam the surface of the earth
+<exarkun> humanity has fled underground
+%
+<dash> consumption does not create wealth, production does
+<dash> people in China have noticed this, people in America have not
+<moshez> consumption is good
+<moshez> dash: creating wealth is not an intristic value
+<dash> moshez: being naked, cold, hungry, and defenseless isn't either
+<dash> moshez: but if you don't create wealth, you will be those things!
+<moshez> dash: are you naked ?
+<moshez> dash: wait, that came out wrong
+* moshez hides
+<dash> i am not h3x
+<moshez> anyone adds this to quotes, I hunt you down and kill you
+%
+* kev wonders if having people checking output by eye counts as a valid unit test
+<MFen> if you can attach electrodes to them and force them to do it every time
+<exarkun> and they have to turn red when it fails
+%
+<RemyWork> http://web.archive.org/web/20030608082636/http://www.movabletype.org/commercial_license.shtml
+<RemyWork> I love having to use the wayback machine to see what my rights are
+%
+<exarkun> I suspect the performance of this irrelevant task is highly sensitive to implementation decisions
+%
+<radix> I just downloaded the fruitiest anime ever
+<radix> it's .. girly
+<radix> it's about a girl's school, and about a "sister" system between upperclassmen and lowerclassmen, and .. and... *twitch*
+<radix> there isn't any hitting!
+%
+<mwh> i wonder if i should post an "are you serious" comment
+<radix> mwh: that's a lot nicer than the comment I was thinking up
+<radix> which was something along the lines of "Holy shit, I'm sick of the horrible crap that's showing up in this God-forsaken cookbook."
+%
+<mamamusings> grades are due by saturday
+<mamamusings> that means i have to at least pretend to evaluate them
+<crw> i KNEW teachers talked like this when students weren't around :P
+<mamamusings> hell, i talk like this when they *are* around
+<mamamusings> it's good to be tenured
+%
+<moshez> On January 8th, 1977, Amber Nicole Benson came into this world -- more
+<moshez> specifically, she was born in Birmingham, Alabama.
+<radix> who the heck is she, anyway?
+<moshez> radix: you know what's shocking? technically, you and I share the same universe
+<radix> moshez: I fight to make that untrue every day
+%
+*** warner has joined channel #twisted
+<glyph> warner: newpb!
+<kenaan> Twisted: warner * r10709 sandbox/warner/ (10 files): revamp exceptions, remote calls kinda work now
+<exarkun> OMG
+<glyph> see everybody? now _that_ is the kind of response I like
+%
+<exarkun> it's really too bad people live so long
+<exarkun> and that it is generally considered immoral to experiment on them
+%
+<Tv> I want an incrimental knifi.
+<exarkun> Able to slice multiple things simultaneously without making its user interface non-responsive!?
+<exarkun> SUCH A THING SURELY COULD NOT EXIST
+<Tv> exarkun: Yeah, except it would a poor weapon, because it COULD NOT BLOCK!
+%
+<SamB> few people know the secret of growing donuts
+%
+<hypatia> I distrust projects that require you to socialise with the developers in order to learn how to use them.
+%
+<radix> well hey, I'm getting back on sane schedule
+<radix> I'll probably stay up until about 5pm today
+%
+<spiv> I am confident some people should be made to feel pain, itamar's possible insanity notwithstanding.
+%
+<dash> for some reason i keep putting off becoming an alcoholic
+%
+<itamar> if I got a cookie for every day I didn't work
+<itamar> I'd be radix
+%
+-!- itamar2 [~itamar@pool-162-83-253-243.ny5030.east.verizon.net] has joined #twisted
+<dash> argh
+<dash> who left the robot clone factory switched on
+%
+<markp> copy editors can blow me
+%
+<itamar> the question is
+<itamar> do I *really* need five more tshirts with monkeys on them
+%
+<`anthony> Yah, yah, debian has advantages, but their glacial release cycle is not good.
+<Jerub> if you want me to go around aj's house and kneecap him, my paypal account is stephen@thorne.id.au
+%
+<moshez> dash: greet me into the 21st century!!
+<dash> moshez: it has been the 21st century here for a while! did .il daylight savings just kick in?
+%
+<dash> brb fighting pirates
+%
+<dash> radix
+<dash> er i mean, twisted.lore
+%
+<mattcamp> Anybody know why twisted.protocols.toc is deprecated?
+<exarkun> because the toc protocol itself is deprecated
+<dash> is TOC really deprecated?
+<dash> %google deprecated TOC AIM
+<pynfo> deprecated TOC AIM: http://twistedmatrix.com/documents/current/api/twisted.protocols.toc.TOCClient.html
+<dash> google is now useless
+%
+<itamar> I bet simulating glyph wouldn't be hard
+<itamar> "We just make a <noun> that will <verb> the <other noun>! it will be awesome! I can do it in a week!"
+%
+<PenguinOfDoom> slyphon: I am torn between going to Quizno's to buy a sub and brutally murdering you.
+%
+* slyphon thinks red hat should change it's motto to, "Eh, it's good enough"
+%
+<radix> facts are awesome
+%
+<PenguinOfDoom> You pigfuckers are sitting there, staring at me with etrade.com open, waiting for JUICY INSIDER INFO
+<exarkun> I use ameritrade.
+%
+<dash> dizzyd: yeah, i got bored of that whole college thing after a while
+<dash> so i graduated
+%
+<jimbug> You know, I would knock the curses author over the head if he didn't invent rogue.
+%
+<glyph> exarkun: you could just write a C module that would do all that ugly dl module crud
+<exarkun> glyph: yea, but then I'd have to write a C module
+<exarkun> Py_Incref in Python is neat, I think :)
+<glyph> exarkun: that guy who wrote pyrex is spinning in his grave, and he isn't even dead
+%
+<morning> yes, but i almost wrote a book on orthogonal persistence, until i realized i couldn't spell it.
+%
+<radix> ayn rand had sex?
+%
+<glyph> I am going up through levels of abstraction so fast, reading from top to bottom, I am worried about getting the bends
+%
+--- iratsu gives channel operator status to dash glyph rev_bot
+--- Users on #ddb: @glyph @rev_bot @dash @iratsu
+<iratsu> yay communism
+%
+<exarkun> I don't think I even knew what XML was the last time I used DOS EDIT.
+%
+<slyphon> wtf is NIH?
+<Tv> slyphon: tla
+<Tv> :)
+<slyphon> Tv: duh
+<`anthony> slyphon: tla for Not Invented Here.
+<orbitz> slyphon: Nice Illegal Honey
+<slyphon> ah
+<Tv> slyphon: that's not a tla!
+<Tv> saying something is a tla implies "go look up it up in the standard places"
+<orbitz> Don't Upset him
+<Tv> orbitz: Your Honey is a him?
+<slyphon> oy
+<orbitz> Tv: honey you eat!
+<Tv> orbitz: EWW!
+%
+<dialtone> I can even run python on my clock
+<dialtone> and my watch
+<dialtone> and have my watch sync with my clock
+<exarkun> what time is it right now
+<dialtone> don't have my watch on right now
+%
+<e> birthday paradox cake
+<exarkun> e: Is that the paradox where, if you have more than 30 people in a room, they'll eat your birthday cake?
+%
+<saph_w> my shirt has a moose
+<radix> family channel
+<saph_w> wth
+<saph_w> moose moose mooose
+<radix> jesus fucking H you've got a mouth on you
+%
+<Nafai> A coworker saw my machine once; I was using the Apple II xscreensaver with the Twisted Quotes as the source
+<Nafai> He asked, "Is that glyph's screensaver?"
+%
+<MFen> actually, #python kinda makes sense
+%
+<Yosomono> why does every discussion of survival of the fittest end with hitler?
+<Yosomono> goddamn nazis have ruined everything
+%
+<saph> PenguinOfDoom: you sap energy from people with evil eye rays
+<saph> you are all e_e----------
+<saph> and the other person is :o
+<saph> and then they are -_-
+<saph> and you are ^_^
+%
+<glyph> people would just roll dice all the time in chat rooms
+<glyph> for *no reason*
+<glyph> because it was a feature of the system that was added for RPG players
+<glyph> and the dice-rolling syntax was pretty involved
+<glyph> infinitely involved, actually
+<glyph> it was a complete RainMan interpreter
+<glyph> you could backdoor the whole goddamn system with the dice roller
+<glyph> user: "I'd like to roll some very big dice"
+<glyph> system: "sure, maybe you would like an admin console and some dev tools to help you manage them"
+%
+<faisal> [sushi is] one of the 3 essential food groups for the networking community
+<kiad> what are the other two?
+<faisal> sushi, caffeine, ietf drafts (for fiber)
+%
+<radix> oh no the galaxies
+<radix> they're going to collide :(
+<radix> BUT PHYSICS SAVES THE DAY
+%
+<dash> there is some law of thermodynamics that says you can't pump all the stupid into one container and expect it to stay there
+<dash> diffusion, or something
+<exarkun> we need a membrane
+<glyph> a membrane with a gun
+%
+<Cerin> and I thought mono was an up and coming technology
+<capnSTABN> your sister gave me mono
+<Cerin> she is pretty tech savvy
+%
+<radix> My computer is gone :-(
+<exarkun> radix: ono!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+<exarkun> radix: wait
+<radix> :-(
+<exarkun> radix: I am suspicious.
+<radix> Why
+<exarkun> radix: Well, let's see
+<exarkun> radix: LOOKTHEREYOUARE
+%
+<foom> wait when was the earth created?
+<flophouse> just look at the expiration date
+<flophouse> it's on the bottom, under the ice cap
+%
+<warner> at a touchscreen voting platform, nobody knows if you're a dog..
+%
+<Tv> thomasvs: why not just talk some authentication protocol to the other host?
+<thomasvs> Tv: why use something arcane and difficult when I have THE POWER OF TWISTED ?
+%
+<brian_> I'm pretty sure from twisted import __version__ will work
+<slyphon> or i guess you could do that
+<brian_> I import dictionaries
+<slyphon> really? do you have to declare them in customs?
+<dash> slyphon: You are an inspiration to me
+<dash> slyphon: the next language I will design will have an alternative to the import statement
+<dash> slyphon: 'smuggle'
+<MFen> dash: no, that should be your execfile replacement
+<dash> from america.south smuggle guns, drugs, dictionaries
+<slyphon> :D
+<MFen> hah
+%
+<MFen> why is programming so *hard*
+<radix> because you try to do it on windows
+<MFen> radix: that is because i already beat the end guy on unix
+%
+<exarkun> If I can make just one person blow chunks, I'm doing my job right.
+%
+<itamar> our manager was looking for you
+<coworker_home> oh? just now?
+<itamar> I think he got your cell# off someone though
+<itamar> yeah
+<coworker_home> ah. good thing my cell is dead
+(Names changed to protect the innocent - Ed.)
+%
+<warner> huh. this one proposition is funded by large corporations on one side, and 204 lawyers on the other
+<glyph> warner: wait - proposition? are you IRCing from inside a voting booth?
+<glyph> those new diebold machines must be awesome
+%
+<Karnaugh> my attempt at implementing Ramanujan went horribly wrong
+<Vhata> because Python isn't the best language for reincarnating tubercular indians?
+%
+<Vhata> maybe I should write my own operating system, where you CAN write to sockets
+<Vhata> my operating system will have beer and hookers, too
+<Vhata> in fact, screw the sockets
+%
+<dash> mmm, ken macleod
+<dash> if I had a "People I Would Be Most Likely To Engage in Apocalyptic Anime-Style Battle With" list, he would be #1 on it
+%
+<glyph> I am going to break with tradition and make one rule here
+<glyph> as long as I'm still active, nobody say anything supportive of Bush
+( ... later ...)
+<chrchr> scout^2: Give us a fact that ties Iraq to 9/11 and you will not be kicked.
+<scout^2> well shit man.. if its gonna come down to that..
+<-- scout^2 has quit ("www.twistedmatrix.com, www.kwikdeath.com")
+ [Note the instant quit when *facts* entered the discussion.]
+%
+<hypatia> spiv: get the tshirt. "Beats me, I'm an arch user." Good for all manner of situations.
+<spiv> hypatia: With a companion shirt "Beat me, I'm an arch developer" ;)
+%
+<orbitz> radix: do you know what it's like to be a team player?
+<radix> orbitz: No. I hate you.
+<orbitz> :(
+<orbitz> radix: well i'm a team player
+<orbitz> so i hate me too
+%
+<blanu> arma: What's bamboo?
+<arma> blanu: some guy named sean rhea from berkeley has been pimping it on p2p-hackers
+<blanu> What's the interesting thing about it?
+<arma> blanu: apparently it works.
+<arma> blanu: seems pretty novel to me. :)
+%
+<tjs> I want to know
+<tjs> why are my pants a topic of conversation?
+%
+<glyph> How was your sunday? Relax at all?
+<exarkun> I played _Silent Hill_ most of the day
+<exarkun> On the one hand, you could say that is relaxing
+<exarkun> On the other hand, no, no you can't
+%
+<arg> argh, my wife calls to complain about her mother while im in the
+ middle of trying to understand someone elses metaclasses
+%
+<Moof> I'm tryign to compile pyopenssl
+<Moof> but there's a syntax error in Python.h
+<mwh> this seems unlikely, on the face of it
+%
+<vit--> dash, do you have any recommendations for python jabber libraries?
+<dash> vit--: kill yourself now
+%
+<orbitz> once i found two people having MUSH sex though
+<orbitz> ieee
+<orbitz> i was 13
+<orbitz> scared for life
+<orbitz> the yellow font burned into my soul
+%
+<exarkun> radix: Are you ready to get a tummy host yet?
+<Tv> tummy hosting is that thing the Jaffa do in Stargate, right?
+%
+<dialtone> my monitor can do Mhz
+<dialtone> in horizontal refresh though
+<exarkun> dialtone: hey that gives me an awesome idea
+<exarkun> I am going to turn my monitor on its side
+%
+<tjs> jml: You are going to die one day.
+...
+<tjs> And when you die, part of what makes up your being is the knowledge of how
+to write Java.
+<tjs> I don't have that problem.
+%
+<chrchr> exarkun: Note that all kryptonite locks are vulnerable to hacksaws.
+<exarkun> chrchr: That's why I have another lock too.
+<exarkun> chrchr: How many thieves carry around _two_ hacksaws?
+%
+$ php sucks.php
+
+PHP EQUALITY -- An experiment:
+0 == "": TRUE.
+0 == "0": TRUE.
+"" == "0": FALSE.
+'none' == 0: TRUE.
+%
+Kragen Sitaker: sub f{grep{(1x$_)!~/^(11+)\1+$/}2..pop}
+Itamar: that looks scary
+Kragen Sitaker: the haskell version is just as short and nearly as opaque
+Kragen Sitaker: but even more inefficient!
+%
+<exarkun> it's not drugs, it's ubuntu
+%
+<arg> i think a significant percentage of twisted apps begin as the logbot example
+<chrchr> Twisted is a fantastic framework for building logbots.
+%
+<mumak> hmm. I think my flatmate is asleep.
+<Brend> You should take this opportunity to glue all the furniture to the ceiling
+<mumak> Brend: well, I have to live here too
+<mumak> Brend: and besides, I'm on holidays. That sounds like too much work.
+<Brend> If you just glue your flatmate to the ceiling, you achieve the same thing with less work, and no impedence to yourself!
+<mumak> you present a strong and compelling case
+<mumak> however, I think my flatmate wouldn't appreciate it.
+<Brend> Anyone who can't see the value of being glued to the ceiling deserves punishment by ceiling-glue
+%
+<MFen> i bet i could beat him at football though
+<MFen> or shoe tying
+<MFen> i'd kick his ass at shoe tying
+<MFen> and then when i was done tying my shoes i stand up and shout IN YOUR FACE. IN YOUR FACE, PI BOY. SEE THESE SHOES? NOT COMING OFF. WHO'S THE SAVANT NOW BITCH?
+<MFen> and do a little dance
+<MFen> i bet he can't dance either
+%
+<glyph> kevc: are you volunteering to maintain it, hmmmmm? :)
+<kevc> heh, no, I have no time free at present
+<glyph> kevc: TOO LATE
+* glyph slaps the manacles on kevc
+<kevc> glyph: last time someone played "tag you're it" on me, I ended up
+ running some uni computing project for three years
+<kevc> went to the pub, woke up with a root password written on my arm
+%
+<tjs> I cant do anything
+<tjs> im running XP atm
+%
+<rik> ew.
+<rik> a python packet filter.
+<rik> that'd have almost windows-like performance.
+<afshar> well, it would be for windows
+%
+[In response to http://journal.jafo.ca/sw-20030328-18biganno.jpg]
+<Brend> Wow. You guys are younger than I thought.
+<dash> Brend: glyph's on like his third host body
+%
+<hypatia> Hey, hidden bonus of having actual named maintainers is having
+ people to assign bugs to...
+<hypatia> Of course, it always ends up being exarkun anyway.
+%
+<arg> i know a guy whos last words were "brb, bout to go h@x this streetlight"
+<arg> knew
+%
+<glyph> saph: loving relationships don't involve windows.
+<saph> glyph: they do if there is a safe word involved
+<PenguinOfDoom> "GENERAL PROTECTION FAULT IN MODULE KERNEL32.DLL SYSTEM CRASHED ERASING DATA NOW" "Firetruck! Firetruck!"
+%
+<zooko> https://yumyum.zooko.com:19144/pub/emacsirc.png
+<zooko> ^-- screenshot of my beautiful Ubuntu desktop
+<teratorn> why must i accept your phony ssl certificate?
+<zooko> you don't have to if you don't want to.
+<zooko> In fact, I recommend that you reject it. Because it could be a Man In The Middle attempting to show you a phony screenshot of my xemacs session.
+%
+<Yosomono> aron: So how are you different from the fascists again?
+<aron> I look shity in borwn shorts
+%
+<exarkun> did glyph tell you about the book we saw at the bookstore over the weekend?
+<exarkun> on the front it said
+<exarkun> Java: Principles in Object Oriented Programming
+<exarkun> on the side it said
+<exarkun> Java POOP
+%
+<moshez> glyph: hello tiny person!
+<moshez> are you tiny and squishy today
+<glyph> moshez: You ask questions that are difficult to answer sensibly
+<moshez> glyph: yes! because I am evil
+<glyph> moshez: for example, "no, I am massive and hard" might give the wrong impression
+<moshez> glyph: urgh
+<moshez> the mental goggles they do nothing
+%
+<foom> who's going to the Time Traveler's Convention next weekend?
+<zirpu> i already went. :-)
+%
+<Tv> Möö
+<tazle> Möö?
+<ValarQ> wtf-8
+%
+<anthony> time to say goodbye fedora, hello whorey weasal (or whatever the fuck it's called this week)
+%
+<Brend> glyph: I see you are proactively prepared to leverage the horizontal market opportunities of the end of all life. I'm impressed.
+%
+<radix> everything in the world should have butter in it
+%
+<itamar> WebSphere MQ!
+<itamar> More enterprise than William Shatner!
+%
+--> freakazoi1 (~Sean@pat100.wirelesssecuritycorp.com) has joined #p2p-hackers
+<freakazoi1> stupid wireless
+<-- freakazoid has quit (Nick collision from services.)
+--- freakazoi1 is now known as freakazoid
+%
+<exarkun> I bet francis bacon would go well with orange juice waffles
+%
+<exarkun> (?:PARTOFSPEECH<adjective>(\w)(\w)+y) (?:SEMANTICWEB<noun,mammal,small>\1\w+)
+<warner> next you're going to tell me that those are actually valid Perl6 regular expressions
+<exarkun> yes, except PARTOFSPEECH is a unicode character with a glyph like a speaking mouth, and SEMANTICWEB is a unicode character with a glyph like cthulu
+%
+<MFen> you needed to kill -USR1 duh
+<MFen> n00b
+<PenguinOfDoom> Can I make a saving LOL? :(
+<PenguinOfDoom> USR1 made gnome-settings-daemon die
+<MFen> dude that's because you didn't init 5 first n00b
+<MFen> btw, i'm making this crap up
+<PenguinOfDoom> btw, I'm planning the destruction of Fresno
+<MFen> can i provide you with maps?
+<saph> hooray
+%
+<MFen> Tv: how do you know when sarge is going to be released?
+<Tv> MFen: If it ain't out by debconf, there will be a public lynching ;)
+%
+<itamar> ""As a champion of the free-enterprise system in Congress, Chris Cox knows that a free economy is built on trust," Bush said at the White House as he introduced the third man in his tenure to lead the [SEC]."
+<itamar> apparently the guy is a fan of Ayn Rand
+<itamar> also a fan of large contributions from corporations
+<dash> of course
+<dash> that's what it just said he's a champion of, right?
+<dash> free-enterprise system in Congress
+%
+<winjer> but i made a point of learning as little as possible
+<winjer> i spent most of the time chasing women and smoking pot
+<saph> hooray
+<winjer> if i'd smoked less i might have caught some
+<saph> winjer: did you go to hampshire college or something
+%
+<dreid> heh ... worst name for an interface ever ... ITem
+<warner> oh, I don't know, I bet INterface would be worse
+<warner> implements(INYerFace)
+<dreid> IStabber(dreid).stab('warner')
+<warner> registerAdapter(lambda victim: dreid, type('warner'), IStabee)
+<warner> heh. the PEP246 equivalent of "nyah nyah, no tagbacks!"
+%
+<radix> and long words are good words
+<Brend> But what about those of us who have hippopotomonstrosesquippadeliophobia?
+<radix> sux to u
+<radix> (to put it into terms you'll understand)
+%
+<jafo> PenguinOf: Ha ha. You listened to a doctor! Serves you right.
+<jafo> I mean, look at it this way. They spend at least 6 years in school, right?
+<jafo> If they're so hot, why can't they graduate in 4 years like everyone else?
+<jafo> Besides, why would I want to be a doctor when I could be a MASTER?
+%
+<_radix_the_nun> I write things on 3x5 cards then smoke them to learn stuff
+%
+<PenguinOfLove> And when I strike, the kids with their "lol" and "ur"
+ will scream "oh, please, Pavel! Do not degrade and destroy our beloved
+ language!"
+%
+<Brend> Whoever chose the title "A Gentle Introduction to Haskell" is
+ obviously accustomed to wrestling bears in piranha pits or something.
+%
+<dash> halfoff: what's the problem?
+<dreid> dash: his spider is dying.
+<dash> dreid: quiet you
+<halfoff> i have a mexican redknee tarantula that escaped for about a week i found it this morning very weak and slow moving is it molting or dying
+<dreid> dash: told you.
+<dash> dreid: SIGH
+%
+<seberino> dash: my zope class prof said real businesses don't do
+ javascript since not professional so i happily neglected it
+<dash> ...
+%
+<dash> glyph: what are _you_ doing up? you have to be at work in the morning
+<dash> glyph: you know, to tell me what to do
+<glyph> dash: I got about 30 hours of sleep this weekend, I'm good
+<dash> glyph: that's no way to live, man
+<glyph> dash: Yeah, but you know me. I'm happy with a ghastly un-life; a mockery of what it means to live
+<dash> glyph: You must be using "happy" in a figurative or metaphorical sense.
+%
+<ph3nyx> mfen: my gvim configuration under windows is wacky
+<MFen> ph3nyx: you should see mine. i keep it in version control :)
+<MFen> 172 lines
+* bear keeps his entire dev config in version control
+<KragenSitaker> i keep my entire living room in version control
+<MFen> yeah. i mean, i keep my desktop backgrounds in version control too, so maybe that's not a very strong point
+<ph3nyx> kragen: that's gotta be a pain in the ass for branching
+<ph3nyx> copying your living room isn't an O(1) operation, no matter what the SVN docs say
+<MFen> svn diff -r172:171. "Dammit! Who moved my chair."
+* bear chuckles
+<KragenSitaker> i tried keeping my entire front yard in it too, but it kept leaking gasoline from the lawnmower
+<KragenSitaker> turns out CVS was expanding a $Id$ in the wheel assembly that would puncture the gas tank
+<MFen> you need svn:flammable 1
+<MFen> heh
+<KragenSitaker> so I decided that was too dangerous and scaled back to my living room
+<KragenSitaker> now i just make occasional tar files of the whole house and back them up with rmsync
+<KragenSitaker> which is the version of rsync for matter
+<MFen> KragenSitaker: have you ever considered branching yourself?
+%
+Jerub|the best advice anyone ever gave me was when i was a fledgling linux geek.
+Jerub|"learn vi"
+Jerub|the worst advice anyone ever gave me was "install mirc".
+Jerub|and I still, to this day, curse that man.
+%
+<cracauer> Potatos are for throwing. If god had intended for them to be eaten they would be square.
+<dbutts> How many naturally occuring edible things are square?
+<dbutts> Apart from fiendishly expensive japanese watermelons?
+<cracauer> Chocolate bars :-)
+%
+<subterrific> what happened to twisted.reality ?
+<dash> subterrific: nothing
+<subterrific> is it running somewhere?
+<dash> no
+<dash> that would be something! instead of nothing
+%
+<tjs> I say we pull their bluff
+<tjs> spam that is
+<tjs> go pro-spam
+<tjs> if everyone spams everyone, then spam will nolonger be effective
+<tjs> and it will stop
+<tjs> and so will the internet, and we can all farm tomatoes
+<tjs> yay tomatoes
+%
+[dash referring to Alan Cox's quote]
+<dash> what's 6mb of unauditable crap
+<PenguinOfDoom> dash: You have three guesses.
+<dash> PenguinOfDoom: your gnucash budget?
+%
+<jotham> you guys are like ADHD vultures, swoop in, devour my problem, leave me
+bewildered, then go off to the next corpse
+%
+<glyph> In the sentence "mang I need to get some cheetos up ins", what
+ part of speech is "up ins", and what function does it serve? It
+ seems to me like "i need to get some cheetos" would be sufficient
+<exarkun> It serves to disambiguate from the case where one merely
+ needs to procure rights to a future shipment of cheetos, most likely
+ to be resold before delivery is taken.
+%
+<radix> php thinks 0 == "Foo"
+<radix> why
+...
+<moshez> radix: as consistent and clear PHP is, it has its problem areas
+<moshez> radix: wait, no, I can't say that with a straight face
+%
+<jotham> something i coded was just on sky sport news
+<jotham> shame it was a horrible C++ nightmare
+<mwh> is debugging nested templates a sport now?
+%
+<mumak> Python totally needs to find a use for É
+<spiv> mumak: dude
+<`anthony> mumak: range!
+<spiv> mumak: There's *already* an ellipsis type in Python.
+<spiv> mumak: Put 2 and 2 together!
+<`anthony> spiv: but the ellipsis type is useless.
+<spiv> `anthony: Clearly unicode would fix that!
+%
+<glyph> WOOO
+<glyph> What the *crap*, how does this work
+<spiv> glyph: LD_PRELOAD
+<glyph> fuck, why is everything horrible
+%
+<radix> hrm, I meant to say <3, but I guess maybe <4 means extra <3.
+%
+<tjs> http://www.animalcaresystems.com/
+<tjs> about 20 crates with this logo just got dumped outside our office
+<tjs> full or rack-mounted mice-containers
+<tjs> unfortunately for me, I have an insatiable curiosity. and when
+ someone unloads 2 shipping containers of extremely high-tech mice
+ containment systems on my doorstep, I just have to know whats going on
+%
+<mumak> Jerub: the trial command line isn't so much of a swiss army knife as... well, Dad's old toolshed.
+<mumak> you never know what you'll find there. there's bound to be some cool stuff, but you can't be too sure whether it will work. everything is either greasy, dusty or both, and nothing is where you expect it to be.
+%
+<justinj> It is difficult to assess the current state of the world when things don't fail consistently.
+%
+<dash> glyph: i live with my two younger brothers
+<dash> it is like getting a graduate course in techniques for annoying people
+<dash> that is why i thought of SMS
+%
+tekNico: Some guy has ported Stan to Turbogears: http://blog.develix.com/archive/2006/01/01/stan-turbogears-continued/
+idnar: that's a bit backwards
+idnar: I wouldn't say anyone ported anything
+idnar: it's just that turbogears has pluggable templating, and he plugged stan in
+tekNico: Hey, either port or plug, it's still a four letter p-word.
+exarkun: poop
+%
+01:10 < KragenSitaker> PenguinOfDoom: are you watching american politics? (< PenguinOfDoom> ugh just when I thought this show couldn't get any worse. Torture and obvious lip-syncing.)
+%
+<zooko> I pay attention to Linux development, mostly starting with lwn.net and its "Kernel" page every week.
+<zooko> I'm often reminded of the adage about sausage and legislation.
+<zooko> I use Linux, and I'm happy with it, but the more I learn about the development process the less comfortable I am.
+%
+<foom> haha, OSX had a suid tool called "dsidentity" which checked your privileges by looking at the "USER" environment variable.
+<dash> that is bad
+<radix> woot
+<radix> /Library/Receipts/MacOSXUpdateCombo10.4.3.pkg/Contents/Resources/postflight_actions/dsidentity.sh
+<radix> haha
+<radix> and the contents of that script are /bin/rm -f "$3/usr/sbin/dsidentity"
+<landonf> What happens when you take a bunch of Mac developers and drop them into UNIX-land ?
+<dash> landonf: hilarity ensues
+%
+<dreid> inviso_: of course in an alternate timeline you're also a 12 foot tall ninja dinosaur.
+<inviso_> oooo, excellent! I like that one better. Can I order that with fries?
+<dreid> you think when you're a 12 foot tall ninja dinosaur you're going to be a herbivore?
+%
+<PenguinOfDoom> wtf
+<PenguinOfDoom> I just forgot that I watched Spiderman 2.
+<PenguinOfDoom> And then remembered.
+<PenguinOfDoom> And then forgot again.
+<exarkun> PenguinOfDoom: Apparently you then remembered again.
+<exarkun> PenguinOfDoom: What an exciting turn of events. Tell me more.
+%
+<orbitz> amberite: our concurrency model is wanted in 12 systems for murder
+<Tv> orbitz: Pfft, it has a perfect alibi -- it was elsewhere at the time!
+%
+mode ( +o glyph ) by ChanServ
+<moshez> glyph is opping, and it's not because I'm being abusive
+<moshez> man
+<moshez> what is wrong with the world
+%
+<Jerub> someone motivate me to write a real http client.
+<lifeless> Jerub: write a real http client
+<spiv> Jerub: write a real http client
+<oubiwann> Jerub: write a real client and 1000 virgins are yours for the taking
+<dash> oubiwann: all of #gentoo?!
+%
+<jml> Give me enough bandwidth and a place to sit
+<jml> and I will move the world.
+%
+<Brend> I don't have any special cases! All my functions do everything!
+<MikeS> Brend: me too! That's why my program is just a single function, run()
+%
+<dreid> do they not have sarcasm in boston?
+<glyph> no, we communicate exclusively through interpretive dance
+%
+<PenguinOfDoom> "!!!!!..!!!!!"
+<PenguinOfDoom> HELP THERE IS A TFTP WOMBAT IN MY ROUTER
+<PenguinOfDoom> It feeds on exclamation marks.
+%
+<exarkun> oh crap I need to do some work too
+<exarkun> but first I will need to configure my irc client to tell everyone that I have work to do
+<exarkun> so that I don't waste any time manually telling people that I have work to do
+<exarkun> can anyone stop working on whatever they're working on and tell me how to configure my irc client to tell you that I'm going to start working on something
+<_moshez> exarkun: perhaps! what irc client do you have
+<exarkun> _moshez: my fist
+<exarkun> I punch kittens until out of sheer suffering they start channelling the internet
+%
+(From pydoctor's website):
+
+How do I use it?
+
+ Good question, glad you asked.
+
+%
+<radix> isn't the answer to *any* question about javascript simply "haha"?
+%
+(regarding threadedselectreactor)
+<SamB_XP> is it chernobl-safe?
+<dreid> not nearly as safe
+<SamB_XP> thats pretty bad!
+<dreid> chernobyl probably didn't have unittests either though
+<SamB_XP> actually, I think that was what they were trying when they
+ blew it up!
+<glyph> nothing says [FAILED] quite like an entire uninhabitable province
+%
+<Jack9> after ConnectionMade() where does it return to?
+<exarkun> Jack9: Otherwhere
+%
+<Deformative> Well, I am one of those people that prefer old, tested/cheeper, hardware. ^_^
+<exarkun> Fortunately for you, even older, tested, cheaper hardware gets faster.
+<exarkun> And at about the same rate as new hardware.
+<Deformative> If not faster.
+<Deformative> Erm waiot.
+<Deformative> Ignore that.
+%
+<keturn> dash: be sure to explain to your kids how jp is short for exarkun and GenericBoy is short for radix.
+%
+<jml> "Roll for integration"
+<jml> d20 + dy/dx
+%
+<dash> woah hey
+<dash> somebody bombed paypal
+<PenguinOfDoom> bombed?
+<exarkun> PenguinOfDoom: with a bomb
+<PenguinOfDoom> oh
+%
+<MFen> hooray! correct layout, instantly. thanks, tables!
+%
+<exarkun> glyph: I will tell you what 'V' does in Perl's pack
+<exarkun> glyph: Unsigned long...
+<exarkun> glyph: ...VAX ordering
+<glyph> exarkun: GGgghhaalllgufffffaff
+<exarkun> critical hit!
+%
+<radix> man, it isn't easy to fall off a log
+<radix> first you need to find a log
+<radix> where the heck do I find a log?
+<radix> then you need to climb up on top of it
+<radix> that's heck of hard
+%
+<dash> also "licence" isn't a software term
+<dash> it's the collective noun for a bunch of lice
+%
+[on libel laws]
+<radix> (a) by means of a device utilizing electromagnetic waves of
+ frequencies lower than 3 000 GHz propagated in space
+ without artificial guide, or
+<radix> (b) through a community antenna television system operated by
+ a person licensed under the Broadcasting Act (Canada)
+ to carry on a broadcasting receiving undertaking,
+<exarkun> I'm hella gonna start calling people names with a 4 GHz laser
+%
+<radix> you are lying exarkun
+<radix> why do you lie
+<exarkun> it's healthy
+<exarkun> I just gained 3 hp
+%
+<radix> I think all of our HOWTOs should be moved into docstrings :-)
+<Brend> radix: Right! That way people can look at the source files,
+ and follow the usual chain of mystification -> hope -> rejoicing ->
+ source code -> confusion -> panic -> roped-into-maintaining-package
+ without even having to switch windows
+%
+<radix> penguinofdoom is not an optimal destination for resources
+* PenguinOfDoom opens mouth.
+<PenguinOfDoom> <----put cheezburger hear
+%
+<det> so how is married life?
+<dash> det: excessively awesome
+<det> When can we expect dots?
+<glyph> det: You've been waiting for years to say that, haven't you.
+%
+<glyph> while 1: pass
+<glyph> that's pretty CPU intensive
+<dracflamloc> yup
+<dracflamloc> you'd be better off doing that in a compiled language
+%
+[Mr Stebbing explaining his name]
+<tjs> PenguinOfDoom: no we chose a new vowel after the 'incident'
+<tjs> we dont talk about that anymore..
+<tjs> poor old Aunty Anne, in the kitchen, with the bread knife
+%
+<glyph> ***** You have declared an explicit schema in a dynamic language *****
+<glyph> Would you like to RESTART, RESTORE, or IMPLEMENT ORTHOGONAL PERSISTENCE?
+%
+<exarkun> I'm happy all the time. No matter what.
+%
+<synx> Right, that's fine.
+<synx> OH WAIT
+<synx> ...no
+<synx> no wait, yes.
+%
+<radix> exarkun: it is cool, whenever I don't want to do any work I write wiki pages
+%
+<glyph> I kind of agree with that.
+<exarkun> There's nothing to agree with -- it's true.
+%
+<therve> hey! google is not a dictionary!
+<exarkun> since when
+<therve> since internet is full of people like me who don't speak 3 words of english!
+<exarkun> it's a living language man, you gotta keep evolving it!
+<therve> yay! evolvulation!
+%
+<MFen> wtf python ignores -Wignore on its own warnings
+<glyph> MFen: you think that's air you're breathing?
+<glyph> MFen: I mean, are you sure that python is emitting warnings, and not just writing to stderr in C?
+<MFen> glyph: how can you warn when you cannot.. speak? 2> /dev/null
+%
+<radix> WHITE MENS BRIEF SIZE L
+<radix> 365 @ $2.99 = $1091.35
+<jml> :(
+<jml> that is not a plan
+<jml> radix: you might think it is a plan, but it is not
+%
+<PenguinOfDoom> It's not AMP, it's C
+<PenguinOfDoom> You need to be either Immune To Confusion or Soulless.
+<PenguinOfDoom> or take frequent short breaks
+<indigo> i may be both
+<PenguinOfDoom> In that case, Cisco has a job for you
+%
+<exarkun> btw, do test driven development :/
+<Torn|zz> yeah that's on my todo list
+%
+<PenguinOfDoom> itamar: Doing fun stuff while breaking tests is a bit like pissing into the wind :(
+<PenguinOfDoom> Sure, you get sweet, sweet relief
+<PenguinOfDoom> also a faceful of piss
+%
+<dreid> Software sucks.
+<PenguinOfDoom> I love software! Software enables my life.
+%
+<some guy> Are you the Twisted guys?
+<glyph> Yeah, but this guy is bazaar.
+%
+<tazle> what should I read before poking at AMP?
+<therve> William Faulkner
+%
+<exarkun> quick what's demorgan's law
+<dash> exarkun: "give me some rum or walk the plank"
+<dash> or wait is that captain morgan's law
+%
+<PenguinOfDoom> Maybe we could just quadruple the moon's mass
+<radix> yeah, that's what I'm thinking
+%
+<radix> man it's beautiful outside
+<radix> I wish I lived in a place where I didn't mind having my blinds open
+<radix> unfortunately there are constantly hobos outside of my house looking at my intellectual property
+<therve> use a smaller font
+%
+<therve> man, why everyone want us to work on twisted.web
+<exarkun> how could you do anything without the web
+<exarkun> it's the lifeblood that flows through the veins of the internet
+<exarkun> or perhaps some kind of parasite
+<exarkun> it's definitely in there though
+%
+<itamar> I get the impression MC Frontalot is going to be at ITA for lunch
+<itamar> or something
+<dash> which MC was that
+%
+<itamar> exarkun: how exactly are you approaching the web2/web tickets?
+<exarkun> three man teams, radio silence, weapons-free rules of engagement
+%
+<radix> I have an idea btw
+<radix> zope.configuration
+<exarkun> that's not an idea
+<dash> putting zope in front of words automatically makes them ideas
+<radix> dash: just like twisted
+<exarkun> dash: almost! except the opposite.
+%
+<exarkun> I AM NOT ANGRY SHUT UP OR I WILL GO MAD WITH RAGE AND MURDER YOU
+%
+<MFen> still. in a world of no pants, the one-panted man is king
+%
+<remote> is this where good habits were invented?
+<Jerub> remote: no, but this is where bad habits are ridiculed.
+%
+<Jerub> MFen: it's certainly a core pillar in the lollocopter zeitgeist.
+%
+<ivan> RFCs are generally known for their superb quality
+%
+<exarkun> Hm
+<exarkun> I fixed the build, but still no orange.
+<djmitche> I misunderstood that at first as a malapropism for "..but still no cigar"
+%
+[...]
+<exarkun> lvh: Find the frame the assertion came from and look into its locals
+<exarkun> lvh: Use the frame's bytecode offset to find out which line was running
+<exarkun> lvh: re-evaluate the expressions in the assertion in the context of the frame's locals
+<exarkun> lvh: viola
+<lvh> exarkun: viola...tion
+%
+<radix> but I don't think anyone else has time and/or knowledge to try to build a karmic package at the moment.
+<jldupont> radix: hmm.... somewhat strange... single point of failure for you guys...
+<idnar> look on the bright side
+<idnar> it's a single point of success
+%
+11:44 < vatts> wvd, it used to work before (as i said) and i didn't change anything :s
+11:44 < exarkun> vatts: Denial is only very rarely a useful debugging technique.
+%
diff --git a/vendor/Twisted-10.0.0/doc/fun/lightbulb b/vendor/Twisted-10.0.0/doc/fun/lightbulb
new file mode 100644
index 0000000000..12de98987a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/fun/lightbulb
@@ -0,0 +1,7 @@
+Q. How many Twisted developers does it take to screw in a lightbulb?
+
+A. Three to implement twisted.lightbulb, one to refactor it, four to whine until the API is documented, two to re-implement it as a C module, one to package it up nicely, but nobody uses the packaged lightbulb, because they need the newest light features, and then no one actually gets around to screwing it in.
+%
+Q. How many Divmod developers does it take to reboot a server?
+A. Four. One to drive, one to recompile the kernel and one to report the progress on IRC.
+%
diff --git a/vendor/Twisted-10.0.0/doc/fun/register.html b/vendor/Twisted-10.0.0/doc/fun/register.html
new file mode 100644
index 0000000000..3fe220baa5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/fun/register.html
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Twisted Matrix Labs Software Registration</title>
+ </head>
+
+ <body>
+ <h1>How to Register your Twisted Daemon instance</h1>
+
+ <h2>The Problem</h2>
+
+ <p>The Business Software Alliance may have put it best, in
+ their page on <a
+ href="http://www.bsa.org/usa/antipiracy/">anti-piracy</a>:</p>
+
+ <blockquote>
+ "Software is one of the most valuable technologies of the
+ Information Age, running everything from PCs to the Internet.
+ Unfortunately, because software is so valuable, and because
+ computers make it easy to create an exact copy of a program
+ in seconds, software piracy is widespread. From individual
+ computer users to professionals who deal wholesale in stolen
+ software, piracy exists in homes, schools, businesses and
+ government. Software pirates not only steal from the
+ companies that make the software, but with less money for
+ research and development of new software, all users are hurt.
+ That's why all software piracy - even one copy you make for a
+ friend, is illegal."
+ </blockquote>
+
+ <p>Software piracy is a serious crime, and one that the Open
+ Source community has been remarkably lax in pursuing and
+ protecting against. This is why Twisted Matrix Laboratories is
+ taking the forefront in Open Source software registration
+ technology.</p>
+
+ <h2>The Twisted Solution</h2>
+
+ <p>In order to do your part to prevent the tragedy of
+ unregistered, unlicensed software, all you need to do is visit
+ <a href="http://www.twistedmatrix.com/license">the Twisted
+ Matrix Labs Licensing and Registration page</a>, and enter your
+ user information to obtain a license key. You can provide us
+ with as much or as little information as you like!</p>
+
+ <p>This will produce a plain-text file which you should save as
+ "twisted-registration" (no quotes, no extension) and drop into
+ the same folder where you run your <code>twistd</code> server.
+ It will be automatically recognized by the server upon
+ startup.</p>
+
+ <h2>Other Benefits to You, the User</h2>
+
+ <p>Besides providing you with a license for the use of your
+ Twisted Daemon, a Twisted registration file also provides you
+ with a probably-unique identifier for your Twisted process -- a
+ helpful tool in writing peer-to-peer applications, or assigning
+ distributed database keys. <b>Privacy is important to us, and
+ this ID is <i>never</i> used by Twisted Matrix Labs software
+ for tracking purposes.</b> You need only register once, rather
+ than renegotiating unique IDs upon every run of your peered
+ application.</p>
+
+ <p>Registering with a valid e-mail address also gives Twisted
+ Matrix Labs a way to contact you with information about
+ upgrades and services as they become available. (Again,
+ registering with an e-mail address is <em>strictly
+ optional</em> to obtain your license key. If you don't want to
+ receive this information, you don't have to!)</p>
+
+ <p>Thank you for doing your part to end the piracy crisis.</p>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/historic/2002/ipc10/twisted-network-framework/errata.html b/vendor/Twisted-10.0.0/doc/historic/2002/ipc10/twisted-network-framework/errata.html
new file mode 100644
index 0000000000..8388919e4b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2002/ipc10/twisted-network-framework/errata.html
@@ -0,0 +1,256 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>The World of Software is a World of Constant
+ Change</title>
+ </head>
+
+ <body>
+ <p><em><strong>Note:</strong> This document is relevant for the
+ version of Twisted that was current at <a
+ href="http://www.python10.com">IPC10</a>. It has since been
+ superseded by many changes to the Python API. It is remaining
+ unchanged for historical reasons, but please refer to
+ documentation for the specific system you are looking for and
+ not these papers for current information.</em></p>
+
+ <h1>The World of Software is a World of Constant Change</h1>
+
+ <p>Twisted has undergone several major revisions since Moshe
+ Zadka and I wrote the <a href="ipc10paper.html">"The Twisted
+ Network Framework"</a>. Most of these changes have not deviated
+ from the central vision of the framework, but almost all of the
+ code listings have been re-visited and enhanced in some
+ way.</p>
+
+ <p>So, while the paper was correct at the time that it was
+ originally written, a few things have changed which have
+ invalidated portions of it.</p>
+
+ <p>Most significant is the fact that almost all methods which
+ pass callbacks of some kind have been changed to take no
+ callback or error-callback arguments, and instead return an
+ instance of a <code
+ class="API">twisted.python.defer.Deferred</code>. This means
+ that an asynchronous function can be easily identified visually
+ because it will be of the form: <code
+ class="python">async_obj.asyncMethod("foo")<b>.addCallbacks(succeded,
+ failed)</b></code>. There is also a utility method <code
+ class="python">addCallback</code> which makes it more
+ convenient to pass additional arguments to a callback function
+ and omit special-case error handling.</p>
+
+ <p>While it is still backwards compatible, <code
+ class="API">twisted.internet.passport</code> has been re-named
+ to <code class="API">twisted.cred</code>, and the various
+ classes in it have been split out into submodules of that
+ package, and the various remote-object superclasses have been
+ moved out of twisted.spread.pb and put into
+ twisted.spread.flavors.</p>
+
+ <p><code class="python">Application.listenOn</code> has been
+ replaced with the more descripively named <code
+ class="python">Application.listenTCP</code>, <code
+ class="python">Application.listenUDP</code>, and <code
+ class="python">Application.listenSSL</code>.</p>
+
+ <p><code class="API">twisted.web.widgets</code> has progressed
+ quite far since the paper was written! One description
+ specifically given in the paper is no longer correct:</p>
+
+ <blockquote>
+ The namespace for evaluating the template expressions is
+ obtained by scanning the class hierarchy for attributes, and
+ getting each of those attributes from the current instance.
+ This means that all methods will be bound methods, so
+ indicating "self" explicitly is not required. While it is
+ possible to override the method for creating namespaces,
+ using this default has the effect of associating all
+ presentation code for a particular widget in one class, along
+ with its template. If one is working with a non-programmer
+ designer, and the template is in an external file, it is
+ always very clear to the designer what functionality is
+ available to them in any given scope, because there is a list
+ of available methods for any given class.
+ </blockquote>
+<p>This is still possible to avoid breakages in old code, but
+after some experimentation, it became clear that simply passing
+ <code class="python">self</code> was an easier method for
+ creating the namespace, both for designers and programmers.</p>
+ <p>In addition, since the advent of Zope3, interoperability
+ with Zope has become increasingly interesting possibility for
+ the Twisted development team, since it would be desirable if
+ Twisted could use their excellent strategy for
+ content-management, while still maintaining Twisted's
+ advantages in the arena of multi-protocol servers. Of
+ particular interest has been Zope Presentation Templates, since
+ they seem to be a truly robust solution for keeping design
+ discrete from code, compatible with the event-based method in
+ which twisted.web.widgets processes web requests. <code
+ class="API">twisted.web.widgets.ZopePresentationTemplate</code>
+ may be opening soon in a theatre near you!</p>
+
+ <p>The following code examples are corrected or modernized
+ versions of the ones that appear in the paper.</p>
+
+ <blockquote>
+ Listing 9: A remotely accessible object and accompanying call
+
+<pre class="python">
+# Server Side
+class MyObject(pb.Referenceable):
+ def remote_doIt(self):
+ return "did it"
+
+# Client Side
+ ...
+ def myCallback(result):
+ print result # result will be 'did it'
+ def myErrback(stacktrace):
+ print 'oh no, mr. bill!'
+ print stacktrace
+ myRemoteReference.doIt().addCallbacks(myCallback,
+ myErrback)
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 10: An object responding to its calling perspective
+<pre class="python">
+# Server Side
+class Greeter(pb.Viewable):
+ def view_greet(self, actor):
+ return "Hello %s!\n" % actor.perspectiveName
+
+# Client Side
+ ...
+ remoteGreeter.greet().addCallback(sys.stdout.write)
+ ...
+</pre>
+ </blockquote>
+
+
+ <blockquote>
+ Listing 12: A client for Echoer objects.
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def gotObject(object):
+ print "got object:",object
+ object.echo("hello network".addCallback(gotEcho)
+def gotEcho(echo):
+ print 'server echoed:',echo
+ main.shutDown()
+def gotNoObject(reason):
+ print "no object:",reason
+ main.shutDown()
+pb.getObjectAt("localhost", 8789, gotObject, gotNoObject, 30)
+main.run()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 13: A PB server using twisted's "passport"
+ authentication.
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class SimplePerspective(pb.Perspective):
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+class SimpleService(pb.Service):
+ def getPerspectiveNamed(self, name):
+ return SimplePerspective(name, self)
+if __name__ == '__main__':
+ import pbecho
+ app = main.Application("pbecho")
+ pbecho.SimpleService("pbecho",app).getPerspectiveNamed("guest").makeIdentity("guest")
+ app.listenTCP(pb.portno, pb.BrokerFactory(pb.AuthRoot(app)))
+ app.save("start")
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 14: Connecting to an Authorized Service
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def success(message):
+ print "Message received:",message
+ main.shutDown()
+def failure(error):
+ print "Failure...",error
+ main.shutDown()
+def connected(perspective):
+ perspective.echo("hello world").addCallbacks(success, failure)
+ print "connected."
+
+pb.connect("localhost", pb.portno, "guest", "guest",
+ "pbecho", "guest", 30).addCallbacks(connected,
+ failure)
+main.run()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 15: A Twisted GUI application
+<pre class="python">
+from twisted.internet import main, ingtkernet
+from twisted.spread.ui import gtkutil
+import gtk
+ingtkernet.install()
+class EchoClient:
+ def __init__(self, echoer):
+ l.hide()
+ self.echoer = echoer
+ w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.GtkVBox(); b = gtk.GtkButton("Echo:")
+ self.entry = gtk.GtkEntry(); self.outry = gtk.GtkEntry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', gtk.mainquit)
+ w.show_all()
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.echo(txt).addCallback(self.outry.set_text)
+l = gtkutil.Login(EchoClient, None, initialService="pbecho")
+l.show_all()
+gtk.mainloop()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 16: an event-based web widget.
+<pre class="python">
+from twisted.spread import pb
+from twisted.python import defer
+from twisted.web import widgets
+class EchoDisplay(widgets.Gadget, widgets.Presentation):
+ template = """&lt;H1&gt;Welcome to my widget, displaying %%%%echotext%%%%.&lt;/h1&gt;
+ &lt;p&gt;Here it is: %%%%getEchoPerspective()%%%%&lt;/p&gt;"""
+ echotext = 'hello web!'
+ def getEchoPerspective(self):
+ return ['&lt;b&gt;',
+ pb.connect("localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 1).
+ addCallbacks(self.makeListOf, self.formatTraceback)
+ ,'&lt;/b&gt;']
+ def makeListOf(self, echoer):
+ return [echoer.echo(self.echotext).addCallback(lambda x: [x])]
+if __name__ == "__main__":
+ from twisted.web import server
+ from twisted.internet import main
+ a = main.Application("pbweb")
+ a.listenTCP(8080, server.Site(EchoDisplay()))
+ a.run()
+</pre>
+ </blockquote>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/historic/2002/ipc10/twisted-network-framework/index.html b/vendor/Twisted-10.0.0/doc/historic/2002/ipc10/twisted-network-framework/index.html
new file mode 100644
index 0000000000..13f825c125
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2002/ipc10/twisted-network-framework/index.html
@@ -0,0 +1,1568 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>The Twisted Network Framework</title>
+ </head>
+
+ <body>
+ <p><em><strong>Note:</strong> This document is relevant for the
+ version of Twisted that were current previous to <a
+ href="http://www.python10.com">IPC10</a>. Even at the time of
+ its release, <a href="ipc10errata.html">there were errata
+ issued</a> to make it current. It is remaining unaltered for
+ historical purposes but it is no longer accurate.</em></p>
+
+ <h1>The Twisted Network Framework</h1>
+
+ <h6>Moshe Zadka <a
+ href="mailto:m@moshez.org">m@moshez.org</a></h6>
+
+ <h6>Glyph Lefkowitz <a
+ href="mailto:glyph@twistedmatrix.com">glyph@twistedmatrix.com</a></h6>
+
+ <h3>Abstract</h3>
+
+ <p>Twisted is a framework for writing asynchronous,
+ event-driven networked programs in Python -- both clients and
+ servers. In addition to abstractions for low-level system calls
+ like <code>select(2)</code> and <code>socket(2)</code>, it also
+ includes a large number of utility functions and classes, which
+ make writing new servers easy. Twisted includes support for
+ popular network protocols like HTTP and SMTP, support for GUI
+ frameworks like <code>GTK+</code>/<code>GNOME</code> and
+ <code>Tk</code> and many other classes designed to make network
+ programs easy. Whenever possible, Twisted uses Python's
+ introspection facilities to save the client programmer as much
+ work as possible. Even though Twisted is still work in
+ progress, it is already usable for production systems -- it can
+ be used to bring up a Web server, a mail server or an IRC
+ server in a matter of minutes, and require almost no
+ configuration.</p>
+
+ <p><strong>Keywords:</strong> internet, network, framework,
+ event-based, asynchronous</p>
+
+ <h3>Introduction</h3>
+
+ <p>Python lends itself to writing frameworks. Python has a
+ simple class model, which facilitates inheritance. It has
+ dynamic typing, which means code needs to assume less. Python
+ also has built-in memory management, which means application
+ code does not need to track ownership. Thus, when writing a new
+ application, a programmer often finds himself writing a
+ framework to make writing this kind of application easier.
+ Twisted evolved from the need to write high-performance
+ interoperable servers in Python, and making them easy to use
+ (and difficult to use incorrectly).</p>
+
+ <p>There are three ways to write network programs:</p>
+
+ <ol>
+ <li>Handle each connection in a separate process</li>
+
+ <li>Handle each connection in a separate thread</li>
+
+ <li>Use non-blocking system calls to handle all connections
+ in one thread.</li>
+ </ol>
+
+ <p>When dealing with many connections in one thread, the
+ scheduling is the responsibility of the application, not the
+ operating system, and is usually implemented by calling a
+ registered function when each connection is ready to for
+ reading or writing -- commonly known as event-driven, or
+ callback-based, programming.</p>
+
+ <p>Since multi-threaded programming is often tricky, even with
+ high level abstractions, and since forking Python processes has
+ many disadvantages, like Python's reference counting not
+ playing well with copy-on-write and problems with shared state,
+ it was felt the best option was an event-driven framework. A
+ benefit of such approach is that by letting other event-driven
+ frameworks take over the main loop, server and client code are
+ essentially the same - making peer-to-peer a reality. While
+ Twisted includes its own event loop, Twisted can already
+ interoperate with <code>GTK+</code>'s and <code>Tk</code>'s
+ mainloops, as well as provide an emulation of event-based I/O
+ for Jython (specific support for the Swing toolkit is planned).
+ Client code is never aware of the loop it is running under, as
+ long as it is using Twisted's interface for registering for
+ interesting events.</p>
+
+ <p>Some examples of programs which were written using the
+ Twisted framework are <code>twisted.web</code> (a web server),
+ <code>twisted.mail</code> (a mail server, supporting both SMTP
+ and POP3, as well as relaying), <code>twisted.words</code> (a
+ chat application supporting integration between a variety of IM
+ protocols, like IRC, AOL Instant Messenger's TOC and
+ Perspective Broker, a remote-object protocol native to
+ Twisted), <code>im</code> (an instant messenger which connects
+ to twisted.words) and <code>faucet</code> (a GUI client for the
+ <code>twisted.reality</code> interactive-fiction framework).
+ Twisted can be useful for any network or GUI application
+ written in Python.</p>
+
+ <p>However, event-driven programming still contains some tricky
+ aspects. As each callback must be finished as soon as possible,
+ it is not possible to keep persistent state in function-local
+ variables. In addition, some programming techniques, such as
+ recursion, are impossible to use. Event-driven programming has
+ a reputation of being hard to use due to the frequent need to
+ write state machines. Twisted was built with the assumption
+ that with the right library, event-driven programming is easier
+ then multi-threaded programming. Twisted aims to be that
+ library.</p>
+
+ <p>Twisted includes both high-level and low-level support for
+ protocols. Most protocol implementation by twisted are in a
+ package which tries to implement "mechanisms, not policy". On
+ top of those implementations, Twisted includes usable
+ implementations of those protocols: for example, connecting the
+ abstract HTTP protocol handler to a concrete resource-tree, or
+ connecting the abstract mail protocol handler to deliver mail
+ to maildirs according to domains. Twisted tries to come with as
+ much functionality as possible out of the box, while not
+ constraining a programmer to a choice between using a
+ possibly-inappropriate class and rewriting the non-interesting
+ parts himself.</p>
+
+ <p>Twisted also includes Perspective Broker, a simple
+ remote-object framework, which allows Twisted servers to be
+ divided into separate processes as the end deployer (rather
+ then the original programmer) finds most convenient. This
+ allows, for example, Twisted web servers to pass requests for
+ specific URLs with co-operating servers so permissions are
+ granted according to the need of the specific application,
+ instead of being forced into giving all the applications all
+ permissions. The co-operation is truly symmetrical, although
+ typical deployments (such as the one which the Twisted web site
+ itself uses) use a master/slave relationship.</p>
+
+ <p>Twisted is not alone in the niche of a Python network
+ framework. One of the better known frameworks is Medusa. Medusa
+ is used, among other things, as Zope's native server serving
+ HTTP, FTP and other protocols. However, Medusa is no longer
+ under active development, and the Twisted development team had
+ a number of goals which would necessitate a rewrite of large
+ portions of Medusa. Twisted seperates protocols from the
+ underlying transport layer. This seperation has the advantages
+ of resuability (for example, using the same clients and servers
+ over SSL) and testability (because it is easy to test the
+ protocol with a much lighter test harness) among others.
+ Twisted also has a very flexible main-loop which can
+ interoperate with third-party main-loops, making it usable in
+ GUI programs too.</p>
+
+ <h3>Complementing Python</h3>
+
+ <p>Python comes out of the box with "batteries included".
+ However, it seems that many Python projects rewrite some basic
+ parts: logging to files, parsing options and high level
+ interfaces to reflection. When the Twisted project found itself
+ rewriting those, it moved them into a separate subpackage,
+ which does not depend on the rest of the twisted framework.
+ Hopefully, people will use <code>twisted.python</code> more and
+ solve interesting problems instead. Indeed, it is one of
+ Twisted's goals to serve as a repository for useful Python
+ code.</p>
+
+ <p>One useful module is <code>twisted.python.reflect</code>,
+ which has methods like <code>prefixedMethods</code>, which
+ returns all methods with a specific prefix. Even though some
+ modules in Python itself implement such functionality (notably,
+ <code>urllib2</code>), they do not expose it as a function
+ usable by outside code. Another useful module is
+ <code>twisted.python.hook</code>, which can add pre-hooks and
+ post-hooks to methods in classes.</p>
+
+ <blockquote>
+<pre class="python">
+# Add all method names beginning with opt_ to the given
+# dictionary. This cannot be done with dir(), since
+# it does not search in superclasses
+dct = {}
+reflect.addMethodNamesToDict(self.__class__, dct, "opt_")
+
+# Sum up all lists, in the given class and superclasses,
+# which have a given name. This gives us "different class
+# semantics": attributes do not override, but rather append
+flags = []
+reflect.accumulateClassList(self.__class__, 'optFlags', flags)
+
+# Add lock-acquire and lock-release to all methods which
+# are not multi-thread safe
+for methodName in klass.synchronized:
+ hook.addPre(klass, methodName, _synchPre)
+ hook.addPost(klass, methodName, _synchPost)
+
+</pre>
+
+ <h6>Listing 1: Using <code>twisted.python.reflect</code> and
+ <code>twisted.python.hook</code></h6>
+ </blockquote>
+
+ <p>The <code>twisted.python</code> subpackage also contains a
+ high-level interface to getopt which supplies as much power as
+ plain getopt while avoiding long
+ <code>if</code>/<code>elif</code> chains and making many common
+ cases easier to use. It uses the reflection interfaces in
+ <code>twisted.python.reflect</code> to find which options the
+ class is interested in, and constructs the argument to
+ <code>getopt</code>. Since in the common case options' values
+ are just saved in instance attributes, it is very easy to
+ indicate interest in such options. However, for the cases
+ custom code needs to be run for an option (for example,
+ counting how many <code>-v</code> options were given to
+ indicate verbosity level), it will call a method which is named
+ correctly.</p>
+
+ <blockquote>
+<pre class="python">
+class ServerOptions(usage.Options):
+ # Those are (short and long) options which
+ # have no argument. The corresponding attribute
+ # will be true iff this option was given
+ optFlags = [['nodaemon','n'],
+ ['profile','p'],
+ ['threaded','t'],
+ ['quiet','q'],
+ ['no_save','o']]
+ # This are options which require an argument
+ # The default is used if no such option was given
+ # Note: since options can only have string arguments,
+ # putting a non-string here is a reliable way to detect
+ # whether the option was given
+ optStrings = [['logfile','l',None],
+ ['file','f','twistd.tap'],
+ ['python','y',''],
+ ['pidfile','','twistd.pid'],
+ ['rundir','d','.']]
+
+ # For methods which can be called multiple times
+ # or have other unusual semantics, a method will be called
+ # Twisted assumes that the option needs an argument if and only if
+ # the method is defined to accept an argument.
+ def opt_plugin(self, pkgname):
+ pkg = __import__(pkgname)
+ self.python = os.path.join(os.path.dirname(
+ os.path.abspath(pkg.__file__)), 'config.tac')
+
+ # Most long options based on methods are aliased to short
+ # options. If there is only one letter, Twisted knows it is a short
+ # option, so it is "-g", not "--g"
+ opt_g = opt_plugin
+
+try:
+ config = ServerOptions()
+ config.parseOptions()
+except usage.error, ue:
+ print "%s: %s" % (sys.argv[0], ue)
+ sys.exit(1)
+</pre>
+
+ <h6>Listing 2: <code>twistd</code>'s Usage Code</h6>
+ </blockquote>
+
+ <p>Unlike <code>getopt</code>, Twisted has a useful abstraction
+ for the non-option arguments: they are passed as arguments to
+ the <code>parsedArgs</code> method. This means too many
+ arguments, or too few, will cause a usage error, which will be
+ flagged. If an unknown number of arguments is desired,
+ explicitly using a tuple catch-all argument will work.</p>
+
+ <h3>Configuration</h3>
+
+ <p>The formats of configuration files have shown two visible
+ trends over the years. On the one hand, more and more
+ programmability has been added, until sometimes they become a
+ new language. The extreme end of this trend is using a regular
+ programming language, such as Python, as the configuration
+ language. On the other hand, some configuration files became
+ more and more machine editable, until they become a miniature
+ database formates. The extreme end of that trend is using a
+ generic database tool.</p>
+
+ <p>Both trends stem from the same rationale -- the need to use
+ a powerful general purpose tool instead of hacking domain
+ specific languages. Domain specific languages are usually
+ ad-hoc and not well designed, having neither the power of
+ general purpose languages nor the predictable machine editable
+ format of generic databases.</p>
+
+ <p>Twisted combines these two trends. It can read the
+ configuration either from a Python file, or from a pickled
+ file. To some degree, it integrates the approaches by
+ auto-pickling state on shutdown, so the configuration files can
+ migrate from Python into pickles. Currently, there is no way to
+ go back from pickles to equivalent Python source, although it
+ is planned for the future. As a proof of concept, the RPG
+ framework Twisted Reality already has facilities for creating
+ Python source which evaluates into a given Python object.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import main
+from twisted.web import proxy, server
+site = server.Site(proxy.ReverseProxyResource('www.yahoo.com', 80, '/'))
+application = main.Application('web-proxy')
+application.listenOn(8080, site)
+</pre>
+
+ <h6>Listing 3: The configuration file for a reverse web
+ proxy</h6>
+ </blockquote>
+
+ <p>Twisted's main program, <code>twistd</code>, can receive
+ either a pickled <code>twisted.internet.main.Application</code>
+ or a Python file which defines a variable called
+ <code>application</code>. The application can be saved at any
+ time by calling its <code>save</code> method, which can take an
+ optional argument to save to a different file name. It would be
+ fairly easy, for example, to have a Twisted server which saves
+ the application every few seconds to a file whose name depends
+ on the time. Usually, however, one settles for the default
+ behavior which saves to a <code>shutdown</code> file. Then, if
+ the shutdown configuration proves suitable, the regular pickle
+ is replaced by the shutdown file. Hence, on the fly
+ configuration changes, regardless of complexity, can always
+ persist.</p>
+
+ <p>There are several client/server protocols which let a
+ suitably privileged user to access to application variable and
+ change it on the fly. The first, and least common denominator,
+ is telnet. The administrator can telnet into twisted, and issue
+ Python statements to her heart's content. For example, one can
+ add ports to listen on to the application, reconfigure the web
+ servers and various other ways by simple accessing
+ <code>__main__.application</code>. Some proof of concepts for a
+ simple suite of command-line utilities to control a Twisted
+ application were written, including commands which allow an
+ administrator to shut down the server or save the current state
+ to a tap file. These are especially useful on Microsoft
+ Windows(tm) platforms, where the normal UNIX way of
+ communicating shutdown requests via signals are less
+ reliable.</p>
+
+ <p>If reconfiguration on the fly is not necessary, Python
+ itself can be used as the configuration editor. Loading the
+ application is as simple as unpickling it, and saving it is
+ done by calling its <code>save</code> method. It is quite easy
+ to add more services or change existing ones from the Python
+ interactive mode.</p>
+
+ <p>A more sophisticated way to reconfigure the application on
+ the fly is via the manhole service. Manhole is a client/server
+ protocol based on top of Perspective Broker, Twisted's
+ translucent remote-object protocol which will be covered later.
+ Manhole has a graphical client called <code>gtkmanhole</code>
+ which can access the server and change its state. Since Twisted
+ is modular, it is possible to write more services for user
+ friendly configuration. For example, through-the-web
+ configuration is planned for several services, notably
+ mail.</p>
+
+ <p>For cases where a third party wants to distribute both the
+ code for a server and a ready to run configuration file, there
+ is the plugin configuration. Philosophically similar to the
+ <code>--python</code> option to <code>twistd</code>, it
+ simplifies the distribution process. A plugin is an archive
+ which is ready to be unpacked into the Python module path. In
+ order to keep a clean tree, <code>twistd</code> extends the
+ module path with some Twisted-specific paths, like the
+ directory <code>TwistedPlugins</code> in the user's home
+ directory. When a plugin is unpacked, it should be a Python
+ package which includes, alongside <code>__init__.py</code> a
+ file named <code>config.tac</code>. This file should define a
+ variable named <code>application</code>, in a similar way to
+ files loaded with <code>--python</code>. The plugin way of
+ distributing configurations is meant to reduce the temptation
+ to put large amount of codes inside the configuration file
+ itself.</p>
+
+ <p>Putting class and function definition inside the
+ configuration files would make the persistent servers which are
+ auto-generated on shutdown useless, since they would not have
+ access to the classes and functions defined inside the
+ configuration file. Thus, the plugin method is intended so
+ classes and functions can still be in regular, importable,
+ Python modules, but still allow third parties distribute
+ powerful configurations. Plugins are used by some of the
+ Twisted Reality virtual worlds.</p>
+
+ <h3>Ports, Protocol and Protocol Factories</h3>
+
+ <p><code>Port</code> is the Twisted class which represents a
+ socket listening on a port. Currently, twisted supports both
+ internet and unix-domain sockets, and there are SSL classes
+ with identical interface. A <code>Port</code> is only
+ responsible for handling the transfer layer. It calls
+ <code>accept</code> on the socket, checks that it actually
+ wants to deal with the connection and asks its factory for a
+ protocol. The factory is usually a subclass of
+ <code>twisted.protocols.protocol.Factory</code>, and its most
+ important method is <code>buildProtocol</code>. This should
+ return something that adheres to the protocol interface, and is
+ usually a subclass of
+ <code>twisted.protocols.protocol.Protocol</code>.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.protocols import protocol
+from twisted.internet import main, tcp
+
+class Echo(protocol.Protocol):
+ def dataReceived(self, data):
+ self.transport.write(data)
+
+factory = protocol.Factory()
+factory.protocol = Echo
+port = tcp.Port(8000, factory)
+app = main.Application("echo")
+app.addPort(port)
+app.run()
+</pre>
+
+ <h6>Listing 4: A Simple Twisted Application</h6>
+ </blockquote>
+
+ <p>The factory is responsible for two tasks: creating new
+ protocols, and keeping global configuration and state. Since
+ the factory builds the new protocols, it usually makes sure the
+ protocols have a reference to it. This allows protocols to
+ access, and change, the configuration. Keeping state
+ information in the factory is the primary reason for keeping an
+ abstraction layer between ports and protocols. Examples of
+ configuration information is the root directory of a web server
+ or the user database of a telnet server. Note that it is
+ possible to use the same factory in two different Ports. This
+ can be used to run the same server bound to several different
+ addresses but not to all of them, or to run the same server on
+ a TCP socket and a UNIX domain sockets.</p>
+
+ <p>A protocol begins and ends its life with
+ <code>connectionMade</code> and <code>connectionLost</code>;
+ both are called with no arguments. <code>connectionMade</code>
+ is called when a connection is first established. By then, the
+ protocol has a <code>transport</code> attribute. The
+ <code>transport</code> attribute is a <code>Transport</code> -
+ it supports <code>write</code> and <code>loseConnection</code>.
+ Both these methods never block: <code>write</code> actually
+ buffers data which will be written only when the transport is
+ signalled ready to for writing, and <code>loseConnection</code>
+ marks the transport for closing as soon as there is no buffered
+ data. Note that transports do <em>not</em> have a
+ <code>read</code> method: data arrives when it arrives, and the
+ protocol must be ready for its <code>dataReceived</code>
+ method, or its <code>connectionLost</code> method, to be
+ called. The transport also supports a <code>getPeer</code>
+ method, which returns parameters about the other side of the
+ transport. For TCP sockets, this includes the remote IP and
+ port.</p>
+
+ <blockquote>
+<pre class="python">
+# A tcp port-forwarder
+# A StupidProtocol sends all data it gets to its peer.
+# A StupidProtocolServer connects to the host/port,
+# and initializes the client connection to be its peer
+# and itself to be the client's peer
+from twisted.protocols import protocol
+
+class StupidProtocol(protocol.Protocol):
+ def connectionLost(self): self.peer.loseConnection();del self.peer
+ def dataReceived(self, data): self.peer.write(data)
+
+class StupidProtocolServer(StupidProtocol):
+ def connectionMade(self):
+ clientProtocol = StupidProtocol()
+ clientProtocol.peer = self.transport
+ self.peer = tcp.Client(self.factory.host, self.factory.port,
+ clientProtocol)
+
+# Create a factory which creates StupidProtocolServers, and
+# has the configuration information they assume
+def makeStupidFactory(host, port):
+ factory = protocol.Factory()
+ factory.host, factory.port = host, port
+ factory.protocol = StupidProtocolServer
+ return factory
+</pre>
+
+ <h6>Listing 5: TCP forwarder code</h6>
+ </blockquote>
+
+ <h3>The Event Loop</h3>
+
+ <p>While Twisted has the ability to let other event loops take
+ over for integration with GUI toolkits, it usually uses its own
+ event loop. The event loop code uses global variables to
+ maintain interested readers and writers, and uses Python's
+ <code>select()</code> function, which can accept any object
+ which has a <code>fileno()</code> method, not only raw file
+ descriptors. Objects can use the event loop interface to
+ indicate interest in either reading to or writing from a given
+ file descriptor. In addition, for those cases where time-based
+ events are needed (for example, queue flushing or periodic POP3
+ downloads), Twisted has a mechanism for repeating events at
+ known delays. While far from being real-time, this is enough
+ for most programs' needs.</p>
+
+ <h3>Going Higher Level</h3>
+
+ <p>Unfortunately, handling arbitrary data chunks is a hard way
+ to code a server. This is why twisted has many classes sitting
+ in submodules of the twisted.protocols package which give
+ higher level interface to the data. For line oriented
+ protocols, <code>LineReceiver</code> translates the low-level
+ <code>dataReceived</code> events into <code>lineReceived</code>
+ events. However, the first naive implementation of
+ <code>LineReceiver</code> proved to be too simple. Protocols
+ like HTTP/1.1 or Freenet have packets which begin with header
+ lines that include length information, and then byte streams.
+ <code>LineReceiver</code> was rewritten to have a simple
+ interface for switching at the protocol layer between
+ line-oriented parts and byte-stream parts.</p>
+
+ <p>Another format which is gathering popularity is Dan J.
+ Bernstein's netstring format. This format keeps ASCII text as
+ ASCII, but allows arbitrary bytes (including nulls and
+ newlines) to be passed freely. However, netstrings were never
+ designed to be used in event-based protocols where over-reading
+ is unavoidable. Twisted makes sure no user will have to deal
+ with the subtle problems handling netstrings in event-driven
+ programs by providing <code>NetstringReceiver</code>.</p>
+
+ <p>For even higher levels, there are the protocol-specific
+ protocol classes. These translate low-level chunks into
+ high-level events such as "HTTP request received" (for web
+ servers), "approve destination address" (for mail servers) or
+ "get user information" (for finger servers). Many RFCs have
+ been thus implemented for Twisted (at latest count, more then
+ 12 RFCs have been implemented). One of Twisted's goals is to be
+ a repository of event-driven implementations for various
+ protocols in Python.</p>
+
+ <blockquote>
+<pre class="python">
+class DomainSMTP(SMTP):
+
+ def validateTo(self, helo, destination):
+ try:
+ user, domain = string.split(destination, '@', 1)
+ except ValueError:
+ return 0
+ if not self.factory.domains.has_key(domain):
+ return 0
+ if not self.factory.domains[domain].exists(user, domain, self):
+ return 0
+ return 1
+
+ def handleMessage(self, helo, origin, recipients, message):
+ # No need to check for existence -- only recipients which
+ # we approved at the validateTo stage are passed here
+ for recipient in recipients:
+ user, domain = string.split(recipient, '@', 1)
+ self.factory.domains[domain].saveMessage(origin, user, message,
+ domain)
+</pre>
+
+ <h6>Listing 6: Implementation of virtual domains using the
+ SMTP protocol class</h6>
+ </blockquote>
+
+ <p>Copious documentation on writing new protocol abstraction
+ exists, since this is the largest amount of code written --
+ much like most operating system code is device drivers. Since
+ many different protocols have already been implemented, there
+ are also plenty of examples to draw on. Usually implementing
+ the client-side of a protocol is particularly challenging,
+ since protocol designers tend to assume much more state kept on
+ the client side of a connection then on the server side.</p>
+
+ <h3>The <code>twisted.tap</code> Package and
+ <code>mktap</code></h3>
+
+ <p>Since one of Twisted's configuration formats are pickles,
+ which are tricky to edit by hand, Twisted evolved a framework
+ for creating such pickles. This framework is contained in the
+ <code>twisted.tap</code> package and the <code>mktap</code>
+ script. New servers, or new ways to configure existing servers,
+ can easily participate in the twisted.tap framework by creating
+ a <code>twisted.tap</code> submodule.</p>
+
+ <p>All <code>twisted.tap</code> submodules must conform to a
+ rigid interface. The interface defines functions to accept the
+ command line parameters, and functions to take the processed
+ command line parameters and add servers to
+ <code>twisted.main.internet.Application</code>. Existing
+ <code>twisted.tap</code> submodules use
+ <code>twisted.python.usage</code>, so the command line format
+ is consistent between different modules.</p>
+
+ <p>The <code>mktap</code> utility gets some generic options,
+ and then the name of the server to build. It imports a
+ same-named <code>twisted.tap</code> submodule, and lets it
+ process the rest of the options and parameters. This makes sure
+ that the process configuring the <code>main.Application</code>
+ is agnostic for where it is used. This allowed
+ <code>mktap</code> to grow the <code>--append</code> option,
+ which appends to an existing pickle rather then creating a new
+ one. This option is frequently used to post-add a telnet server
+ to an application, for net-based on the fly configuration
+ later.</p>
+
+ <p>When running <code>mktap</code> under UNIX, it saves the
+ user id and group id inside the tap. Then, when feeding this
+ tap into <code>twistd</code>, it changes to this user/group id
+ after binding the ports. Such a feature is necessary in any
+ production-grade server, since ports below 1024 require root
+ privileges to use on UNIX -- but applications should not run as
+ root. In case changing to the specified user causes difficulty
+ in the build environment, it is also possible to give those
+ arguments to <code>mktap</code> explicitly.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import tcp, stupidproxy
+from twisted.python import usage
+
+usage_message = """
+usage: mktap stupid [OPTIONS]
+
+Options are as follows:
+ --port &lt;#&gt;, -p: set the port number to &lt;#&gt;.
+ --host &lt;host&gt;, -h: set the host to &lt;host&gt;
+ --dest_port &lt;#&gt;, -d: set the destination port to &lt;#&gt;
+"""
+
+class Options(usage.Options):
+ optStrings = [["port", "p", 6666],
+ ["host", "h", "localhost"],
+ ["dest_port", "d", 6665]]
+
+def getPorts(app, config):
+ s = stupidproxy.makeStupidFactory(config.host, int(config.dest_port))
+ return [(int(config.port), s)]
+</pre>
+
+ <h6>Listing 7: <code>twisted.tap.stupid</code></h6>
+ </blockquote>
+
+ <p>The <code>twisted.tap</code> framework is one of the reasons
+ servers can be set up with little knowledge and time. Simply
+ running <code>mktap</code> with arguments can bring up a web
+ server, a mail server or an integrated chat server -- with
+ hardly any need for maintainance. As a working
+ proof-on-concept, the <code>tap2deb</code> utility exists to
+ wrap up tap files in Debian packages, which include scripts for
+ running and stopping the server and interact with
+ <code>init(8)</code> to make sure servers are automatically run
+ on start-up. Such programs can also be written to interface
+ with the Red Hat Package Manager or the FreeBSD package
+ management systems.</p>
+
+ <blockquote>
+<pre class="shell">
+% mktap --uid 33 --gid 33 web --static /var/www --port 80
+% tap2deb -t web.tap -m 'Moshe Zadka &lt;moshez@debian.org&gt;'
+% su
+password:
+# dpkg -i .build/twisted-web_1.0_all.deb
+</pre>
+
+ <h6>Listing 8: Bringing up a web server on a Debian
+ system</h6>
+ </blockquote>
+
+ <h3>Multi-thread Support</h3>
+
+ <p>Sometimes, threads are unavoidable or hard to avoid. Many
+ legacy programs which use threads want to use Twisted, and some
+ vendor APIs have no non-blocking version -- for example, most
+ database systems' API. Twisted can work with threads, although
+ it supports only one thread in which the main select loop is
+ running. It can use other threads to simulate non-blocking API
+ over a blocking API -- it spawns a thread to call the blocking
+ API, and when it returns, the thread calls a callback in the
+ main thread. Threads can call callbacks in the main thread
+ safely by adding those callbacks to a list of pending events.
+ When the main thread is between select calls, it searches
+ through the list of pending events, and executes them. This is
+ used in the <code>twisted.enterprise</code> package to supply
+ an event driven interfaces to databases, which uses Python's DB
+ API.</p>
+
+ <p>Twisted tries to optimize for the common case -- no threads.
+ If there is need for threads, a special call must be made to
+ inform the <code>twisted.python.threadable</code> module that
+ threads will be used. This module is implemented differently
+ depending on whether threads will be used or not. The decision
+ must be made before importing any modules which use threadable,
+ and so is usually done in the main application. For example,
+ <code>twistd</code> has a command line option to initialize
+ threads.</p>
+
+ <p>Twisted also supplies a module which supports a threadpool,
+ so the common task of implementing non-blocking APIs above
+ blocking APIs will be both easy and efficient. Threads are kept
+ in a pool, and dispatch requests are done by threads which are
+ not working. The pool supports a maximum amount of threads, and
+ will throw exceptions when there are more requests than
+ allowable threads.</p>
+
+ <p>One of the difficulties about multi-threaded systems is
+ using locks to avoid race conditions. Twisted uses a mechanism
+ similar to Java's synchronized methods. A class can declare a
+ list of methods which cannot safely be called at the same time
+ from two different threads. A function in threadable then uses
+ <code>twisted.python.hook</code> to transparently add
+ lock/unlock around these methods. This allows Twisted classes
+ to be written without thought about threading, except for one
+ localized declaration which does not entail any performance
+ penalty for the single-threaded case.</p>
+
+ <h3>Twisted Mail Server</h3>
+
+ <p>Mail servers have a history of security flaws. Sendmail is
+ by now the poster boy of security holes, but no mail servers,
+ bar maybe qmail, are free of them. Like Dan Bernstein of qmail
+ fame said, mail cannot be simply turned off -- even the
+ simplest organization needs a mail server. Since Twisted is
+ written in a high-level language, many problems which plague
+ other mail servers, notably buffer overflows, simply do not
+ exist. Other holes are avoidable with correct design. Twisted
+ Mail is a project trying to see if it is possible to write a
+ high quality high performance mail server entirely in
+ Python.</p>
+
+ <p>Twisted Mail is built on the SMTP server and client protocol
+ classes. While these present a level of abstraction from the
+ specific SMTP line semantics, they do not contain any message
+ storage code. The SMTP server class does know how to divide
+ responsibility between domains. When a message arrives, it
+ analyzes the recipient's address, tries matching it with one of
+ the registered domain, and then passes validation of the
+ address and saving the message to the correct domain, or
+ refuses to handle the message if it cannot handle the domain.
+ It is possible to specify a catch-all domain, which will
+ usually be responsible for relaying mails outwards.</p>
+
+ <p>While correct relaying is planned for the future, at the
+ moment we have only so-called "smarthost" relaying. All e-mail
+ not recognized by a local domain is relayed to a single outside
+ upstream server, which is supposed to relay the mail further.
+ This is the configuration for most home machines, which are
+ Twisted Mail's current target audience.</p>
+
+ <p>Since the people involved in Twisted's development were
+ reluctant to run code that runs as a super user, or with any
+ special privileges, it had to be considered how delivery of
+ mail to users is possible. The solution decided upon was to
+ have Twisted deliver to its own directory, which should have
+ very strict permissions, and have users pull the mail using
+ some remote mail access protocol like POP3. This means only a
+ user would write to his own mail box, so no security holes in
+ Twisted would be able to adversely affect a user.</p>
+
+ <p>Future plans are to use a Perspective Broker-based service
+ to hand mail to users to a personal server using a UNIX domain
+ socket, as well as to add some more conventional delivery
+ methods, as scary as they may be.</p>
+
+ <p>Because the default configuration of Twisted Mail is to be
+ an integrated POP3/SMTP servers, it is ideally suited for the
+ so-called POP toaster configuration, where there are a
+ multitude of virtual users and domains, all using the same IP
+ address and computer to send and receive mails. It is fairly
+ easy to configure Twisted as a POP toaster. There are a number
+ of deployment choices: one can append a telnet server to the
+ tap for remote configuration, or simple scripts can add and
+ remove users from the user database. The user database is saved
+ as a directory, where file names are keys and file contents are
+ values, so concurrency is not usually a problem.</p>
+
+ <blockquote>
+<pre class="shell">
+% mktap mail -d foobar.com=$HOME/Maildir/ -u postmaster=secret -b \
+ -p 110 -s 25
+% twistd -f mail.tap
+
+</pre>
+
+ <h6>Bringing up a simple mail-server</h6>
+ </blockquote>
+
+ <p>Twisted's native mail storage format is Maildir, a format
+ that requires no locking and is safe and atomic. Twisted
+ supports a number of standardized extensions to Maildir,
+ commonly known as Maildir++. Most importantly, it supports
+ deletion as simply moving to a subfolder named
+ <code>Trash</code>, so mail is recoverable if accessed through
+ a protocol which allows multiple folders, like IMAP. However,
+ Twisted itself currently does not support any such protocol
+ yet.</p>
+
+ <h3>Introducing Perspective Broker</h3>
+
+ <h4>All the World's a Game</h4>
+
+ <p>Twisted was originally designed to support multi-player
+ games; a simulated "real world" environment. Experience with
+ game systems of that type is enlightening as to the nature of
+ computing on the whole. Almost all services on a computer are
+ modeled after some simulated real-world activity. For example,
+ e-"mail", or "document publishing" on the web. Even
+ "object-oriented" programming is based around the notion that
+ data structures in a computer simulate some analogous
+ real-world objects.</p>
+
+ <p>All such networked simulations have a few things in common.
+ They each represent a service provided by software, and there
+ is usually some object where "global" state is kept. Such a
+ service must provide an authentication mechanism. Often, there
+ is a representation of the authenticated user within the
+ context of the simulation, and there are also objects aside
+ from the user and the simulation itself that can be
+ accessed.</p>
+
+ <p>For most existing protocols, Twisted provides these
+ abstractions through <code>twisted.internet.passport</code>.
+ This is so named because the most important common
+ functionality it provides is authentication. A simulation
+ "world" as described above -- such as an e-mail system,
+ document publishing archive, or online video game -- is
+ represented by subclass of <code>Service</code>, the
+ authentication mechanism by an <code>Authorizer</code> (which
+ is a set of <code>Identities</code>), and the user of the
+ simulation by a <code>Perspective</code>. Other objects in the
+ simulation may be represented by arbitrary python objects,
+ depending upon the implementation of the given protocol.</p>
+
+ <p>New problem domains, however, often require new protocols,
+ and re-implementing these abstractions each time can be
+ tedious, especially when it's not necessary. Many efforts have
+ been made in recent years to create generic "remote object" or
+ "remote procedure call" protocols, but in developing Twisted,
+ these protocols were found to require too much overhead in
+ development, be too inefficient at runtime, or both.</p>
+
+ <p>Perspective Broker is a new remote-object protocol designed
+ to be lightweight and impose minimal constraints upon the
+ development process and use Python's dynamic nature to good
+ effect, but still relatively efficient in terms of bandwidth
+ and CPU utilization. <code>twisted.spread.pb</code> serves as a
+ reference implementation of the protocol, but implementation of
+ Perspective Broker in other languages is already underway.
+ <code>spread</code> is the <code>twisted</code> subpackage
+ dealing with remote calls and objects, and has nothing to do
+ with the <code>spread</code> toolkit.</p>
+
+ <p>Perspective Broker extends
+ <code>twisted.internet.passport</code>'s abstractions to be
+ concrete objects rather than design patterns. Rather than
+ having a <code>Protocol</code> implementation translate between
+ sequences of bytes and specifically named methods (as in the
+ other Twisted <code>Protocols</code>), Perspective Broker
+ defines a direct mapping between network messages and
+ quasi-arbitrary method calls.</p>
+
+ <h3>Translucent, not Transparent</h3>
+
+ <p>In a server application where a large number of clients may
+ be interacting at once, it is not feasible to have an
+ arbitrarily large number of OS threads blocking and waiting for
+ remote method calls to return. Additionally, the ability for
+ any client to call any method of an object would present a
+ significant security risk. Therefore, rather than attempting to
+ provide a transparent interface to remote objects,
+ <code>twisted.spread.pb</code> is "translucent", meaning that
+ while remote method calls have different semantics than local
+ ones, the similarities in semantics are mirrored by
+ similarities in the syntax. Remote method calls impose as
+ little overhead as possible in terms of volume of code, but "as
+ little as possible" is unfortunately not "nothing".</p>
+
+ <p><code>twisted.spread.pb</code> defines a method naming
+ standard for each type of remotely accessible object. For
+ example, if a client requests a method call with an expression
+ such as <code>myPerspective.doThisAction()</code>, the remote
+ version of <code>myPerspective</code> would be sent the message
+ <code>perspective_doThisAction</code>. Depending on the manner
+ in which an object is accessed, other method prefixes may be
+ <code>observe_</code>, <code>view_</code>, or
+ <code>remote_</code>. Any method present on a remotely
+ accessible object, and named appropriately, is considered to be
+ published -- since this is accomplished with
+ <code>getattr</code>, the definition of "present" is not just
+ limited to methods defined on the class, but instances may have
+ arbitrary callable objects associated with them as long as the
+ name is correct -- similarly to normal python objects.</p>
+
+ <p>Remote method calls are made on remote reference objects
+ (instances of <code>pb.RemoteReference</code>) by calling a
+ method with an appropriate name. However, that call will not
+ block -- if you need the result from a remote method call, you
+ pass in one of the two special keyword arguments to that method
+ -- <code>pbcallback</code> or <code>pberrback</code>.
+ <code>pbcallback</code> is a callable object which will be
+ called when the result is available, and <code>pberrback</code>
+ is a callable object which will be called if there was an
+ exception thrown either in transmission of the call or on the
+ remote side.</p>
+
+ <p>In the case that neither <code>pberrback</code> or
+ <code>pbcallback</code> is provided,
+ <code>twisted.spread.pb</code> will optimize network usage by
+ not sending confirmations of messages.</p>
+
+ <blockquote>
+<pre class="python">
+# Server Side
+class MyObject(pb.Referenceable):
+ def remote_doIt(self):
+ return "did it"
+
+# Client Side
+ ...
+ def myCallback(result):
+ print result # result will be 'did it'
+ def myErrback(stacktrace):
+ print 'oh no, mr. bill!'
+ print stacktrace
+ myRemoteReference.doIt(pbcallback=myCallback,
+ pberrback=myErrback)
+</pre>
+
+ <h6>Listing 9: A remotely accessible object and accompanying
+ call</h6>
+ </blockquote>
+
+ <h3>Different Behavior for Different Perspectives</h3>
+
+ <p>Considering the problem of remote object access in terms of
+ a simulation demonstrates a requirement for the knowledge of an
+ actor with certain actions or requests. Often, when processing
+ message, it is useful to know who sent it, since different
+ results may be required depending on the permissions or state
+ of the caller.</p>
+
+ <p>A simple example is a game where certain an object is
+ invisible, but players with the "Heightened Perception"
+ enchantment can see it. When answering the question "What
+ objects are here?" it is important for the room to know who is
+ asking, to determine which objects they can see. Parallels to
+ the differences between "administrators" and "users" on an
+ average multi-user system are obvious.</p>
+
+ <p>Perspective Broker is named for the fact that it does not
+ broker only objects, but views of objects. As a user of the
+ <code>twisted.spread.pb</code> module, it is quite easy to
+ determine the caller of a method. All you have to do is
+ subclass <code>Viewable</code>.</p>
+
+ <blockquote>
+<pre class="python">
+# Server Side
+class Greeter(pb.Viewable):
+ def view_greet(self, actor):
+ return "Hello %s!\n" % actor.perspectiveName
+
+# Client Side
+ ...
+ remoteGreeter.greet(pbcallback=sys.stdout.write)
+ ...
+</pre>
+
+ <h6>Listing 10: An object responding to its calling
+ perspective</h6>
+ </blockquote>
+ Before any arguments sent by the client, the actor
+ (specifically, the Perspective instance through which this
+ object was retrieved) will be passed as the first argument to
+ any <code>view_xxx</code> methods.
+
+ <h3>Mechanisms for Sharing State</h3>
+
+ <p>In a simulation of any decent complexity, client and server
+ will wish to share structured data. Perspective Broker provides
+ a mechanism for both transferring (copying) and sharing
+ (caching) that state.</p>
+
+ <p>Whenever an object is passed as an argument to or returned
+ from a remote method call, that object is serialized using
+ <code>twisted.spread.jelly</code>; a serializer similar in some
+ ways to Python's native <code>pickle</code>. Originally,
+ <code>pickle</code> itself was going to be used, but there were
+ several security issues with the <code>pickle</code> code as it
+ stands. It is on these issues of security that
+ <code>pickle</code> and <code>twisted.spread.jelly</code> part
+ ways.</p>
+
+ <p>While <code>twisted.spread.jelly</code> handles a few basic
+ types such as strings, lists, dictionaries and numbers
+ automatically, all user-defined types must be registered both
+ for serialization and unserialization. This registration
+ process is necessary on the sending side in order to determine
+ if a particular object is shared, and whether it is shared as
+ state or behavior. On the receiving end, it's necessary to
+ prevent arbitrary code from being run when an object is
+ unserialized -- a significant security hole in
+ <code>pickle</code> for networked applications.</p>
+
+ <p>On the sending side, the registration is accomplished by
+ making the object you want to serialize a subclass of one of
+ the "flavors" of object that are handled by Perspective Broker.
+ A class may be <code>Referenceable</code>,
+ <code>Viewable</code>, <code>Copyable</code> or
+ <code>Cacheable</code>. These four classes correspond to
+ different ways that the object will be seen remotely.
+ Serialization flavors are mutually exclusive -- these 4 classes
+ may not be mixed in with each other.</p>
+
+ <ul>
+ <li><code>Referenceable</code>: The remote side will refer to
+ this object directly. Methods with the prefix
+ <code>remote_</code> will be callable on it. No state will be
+ transferred.</li>
+
+ <li><code>Viewable</code>: The remote side will refer to a
+ proxy for this object, which indicates what perspective
+ accessed this; as discussed above. Methods with the prefix
+ <code>view_</code> will be callable on it, and have an
+ additional first argument inserted (the perspective that
+ called the method). No state will be transferred.</li>
+
+ <li><code>Copyable</code>: Each time this object is
+ serialized, its state will be copied and sent. No methods are
+ remotely callable on it. By default, the state sent will be
+ the instance's <code>__dict__</code>, but a method
+ <code>getStateToCopyFor(perspective)</code> may be defined
+ which returns an arbitrary serializable object for
+ state.</li>
+
+ <li><code>Cacheable</code>: The first time this object is
+ serialized, its state will be copied and sent. Each
+ subsequent time, however, a reference to the original object
+ will be sent to the receiver. No methods will be remotely
+ callable on this object. By default, again, the state sent
+ will be the instance's <code>__dict__</code>but a method
+ <code>getStateToCacheAndObserveFor(perspective,
+ observer)</code> may be defined to return alternative state.
+ Since the state for this object is only sent once, the
+ <code>observer</code> argument is an object representative of
+ the receiver's representation of the <code>Cacheable</code>
+ after unserialization -- method calls to this object will be
+ resolved to methods prefixed with <code>observe_</code>,
+ <em>on the receiver's <code>RemoteCache</code> of this
+ object</em>. This may be used to keep the receiver's cache
+ up-to-date as relevant portions of the <code>Cacheable</code>
+ object change.</li>
+ </ul>
+
+ <h3>Publishing Objects with PB</h3>
+
+ <p>The previous samples of code have shown how an individual
+ object will interact over a previously-established PB
+ connection. In order to get to that connection, you need to do
+ some set-up work on both the client and server side; PB
+ attempts to minimize this effort.</p>
+
+ <p>There are two different approaches for setting up a PB
+ server, depending on your application's needs. In the simplest
+ case, where your application does not deal with the
+ abstractions above -- services, identities, and perspectives --
+ you can simply publish an object on a particular port.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class Echoer(pb.Root):
+ def remote_echo(self, st):
+ print 'echoing:', st
+ return st
+if __name__ == '__main__':
+ app = main.Application("pbsimple")
+ app.listenOn(8789, pb.BrokerFactory(Echoer()))
+ app.run()
+</pre>
+
+ <h6>Listing 11: Creating a simple PB server</h6>
+ </blockquote>
+
+ <p>Listing 11 shows how to publish a simple object which
+ responds to a single message, "echo", and returns whatever
+ argument is sent to it. There is very little to explain: the
+ "Echoer" class is a pb.Root, which is a small subclass of
+ Referenceable designed to be used for objects published by a
+ BrokerFactory, so Echoer follows the same rule for remote
+ access that Referenceable does. Connecting to this service is
+ almost equally simple.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def gotObject(object):
+ print "got object:",object
+ object.echo("hello network", pbcallback=gotEcho)
+def gotEcho(echo):
+ print 'server echoed:',echo
+ main.shutDown()
+def gotNoObject(reason):
+ print "no object:",reason
+ main.shutDown()
+pb.getObjectAt("localhost", 8789, gotObject, gotNoObject, 30)
+main.run()
+</pre>
+
+ <h6>Listing 12: A client for Echoer objects.</h6>
+ </blockquote>
+
+ <p>The utility function <code>pb.getObjectAt</code> retrieves
+ the root object from a hostname/port-number pair and makes a
+ callback (in this case, <code>gotObject</code>) if it can
+ connect and retrieve the object reference successfully, and an
+ error callback (<code>gotNoObject</code>) if it cannot connect
+ or the connection times out.</p>
+
+ <p><code>gotObject</code> receives the remote reference, and
+ sends the <code>echo</code> message to it. This call is
+ visually noticeable as a remote method invocation by the
+ distinctive <code>pbcallback</code> keyword argument. When the
+ result from that call is received, <code>gotEcho</code> will be
+ called, notifying us that in fact, the server echoed our input
+ ("hello network").</p>
+
+ <p>While this setup might be useful for certain simple types of
+ applications where there is no notion of a "user", the
+ additional complexity necessary for authentication and service
+ segregation is worth it. In particular, re-use of server code
+ for things like chat (twisted.words) is a lot easier with a
+ unified notion of users and authentication.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class SimplePerspective(pb.Perspective):
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+class SimpleService(pb.Service):
+ def getPerspectiveNamed(self, name):
+ return SimplePerspective(name, self)
+if __name__ == '__main__':
+ import pbecho
+ app = main.Application("pbecho")
+ pbecho.SimpleService("pbecho",app).getPerspectiveNamed("guest")\
+ .makeIdentity("guest")
+ app.listenOn(pb.portno, pb.BrokerFactory(pb.AuthRoot(app)))
+ app.save("start")
+</pre>
+
+ <h6>Listing 13: A PB server using twisted's "passport"
+ authentication.</h6>
+ </blockquote>
+
+ <p>In terms of the "functionality" it offers, this server is
+ identical. It provides a method which will echo some simple
+ object sent to it. However, this server provides it in a manner
+ which will allow it to cooperate with multiple other
+ authenticated services running on the same connection, because
+ it uses the central Authorizer for the application.</p>
+
+ <p>On the line that creates the <code>SimpleService</code>,
+ several things happen.</p>
+
+ <ol>
+ <li>A SimpleService is created and persistently added to the
+ <code>Application</code> instance.</li>
+
+ <li>A SimplePerspective is created, via the overridden
+ <code>getPerspectiveNamed</code> method.</li>
+
+ <li>That <code>SimplePerspective</code> has an
+ <code>Identity</code> generated for it, and persistently
+ added to the <code>Application</code>'s
+ <code>Authorizer</code>. The created identity will have the
+ same name as the perspective ("guest"), and the password
+ supplied (also, "guest"). It will also have a reference to
+ the service "pbecho" and a perspective named "guest", by
+ name. The <code>Perspective.makeIdentity</code> utility
+ method prevents having to deal with the intricacies of the
+ passport <code>Authorizer</code> system when one doesn't
+ require strongly separate <code>Identity</code>s and
+ <code>Perspective</code>s.</li>
+ </ol>
+ <br />
+ <br />
+
+
+ <p>Also, this server does not run itself, but instead persists
+ to a file which can be run with twistd, offering all the usual
+ amenities of daemonization, logging, etc. Once the server is
+ run, connecting to it is similar to the previous example.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def success(message):
+ print "Message received:",message
+ main.shutDown()
+def failure(error):
+ print "Failure...",error
+ main.shutDown()
+def connected(perspective):
+ perspective.echo("hello world",
+ pbcallback=success,
+ pberrback=failure)
+ print "connected."
+pb.connect(connected, failure, "localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 30)
+main.run()
+</pre>
+
+ <h6>Listing 14: Connecting to an Authorized Service</h6>
+ </blockquote>
+ <br />
+ <br />
+
+
+ <p>This introduces a new utility -- <code>pb.connect</code>.
+ This function takes a long list of arguments and manages the
+ handshaking and challenge/response aspects of connecting to a
+ PB service perspective, eventually calling back to indicate
+ either success or failure. In this particular example, we are
+ connecting to localhost on the default PB port (8787),
+ authenticating to the identity "guest" with the password
+ "guest", requesting the perspective "guest" from the service
+ "pbecho". If this can't be done within 30 seconds, the
+ connection will abort.</p>
+
+ <p>In these examples, I've attempted to show how Twisted makes
+ event-based scripting easier; this facilitates the ability to
+ run short scripts as part of a long-running process. However,
+ event-based programming is not natural to procedural scripts;
+ it is more generally accepted that GUI programs will be
+ event-driven whereas scripts will be blocking. An alternative
+ client to our <code>SimpleService</code> using GTK illustrates
+ the seamless meshing of Twisted and GTK.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import main, ingtkernet
+from twisted.spread.ui import gtkutil
+import gtk
+ingtkernet.install()
+class EchoClient:
+ def __init__(self, echoer):
+ l.hide()
+ self.echoer = echoer
+ w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.GtkVBox(); b = gtk.GtkButton("Echo:")
+ self.entry = gtk.GtkEntry(); self.outry = gtk.GtkEntry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', gtk.mainquit)
+ w.show_all()
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.echo(txt, pbcallback=self.outry.set_text)
+l = gtkutil.Login(EchoClient, None, initialService="pbecho")
+l.show_all()
+gtk.mainloop()
+</pre>
+
+ <h6>Listing 15: A Twisted GUI application</h6>
+ </blockquote>
+
+ <h3>Event-Driven Web Object Publishing with Web.Widgets</h3>
+
+ <p>Although PB will be interesting to those people who wish to
+ write custom clients for their networked applications, many
+ prefer or require a web-based front end. Twisted's built-in web
+ server has been designed to accommodate this desire, and the
+ presentation framework that one would use to write such an
+ application is <code>twisted.web.widgets</code>. Web.Widgets
+ has been designed to work in an event-based manner, without
+ adding overhead to the designer or the developer's
+ work-flow.</p>
+
+ <p>Surprisingly, asynchronous web interfaces fit very well into
+ the normal uses of purpose-built web toolkits such as PHP. Any
+ experienced PHP, Zope, or WebWare developer will tell you that
+ <em>separation of presentation, content, and logic</em> is very
+ important. In practice, this results in a "header" block of
+ code which sets up various functions which are called
+ throughout the page, some of which load blocks of content to
+ display. While PHP does not enforce this, it is certainly
+ idiomatic. Zope enforces it to a limited degree, although it
+ still allows control structures and other programmatic elements
+ in the body of the content.</p>
+
+ <p>In Web.Widgets, strict enforcement of this principle
+ coincides very neatly with a "hands-free" event-based
+ integration, where much of the work of declaring callbacks is
+ implicit. A "Presentation" has a very simple structure for
+ evaluating Python expressions and giving them a context to
+ operate in. The "header" block which is common to many
+ templating systems becomes a class, which represents an
+ enumeration of events that the template may generate, each of
+ which may be responded to either immediately or latently.</p>
+
+ <p>For the sake of simplicity, as well as maintaining
+ compatibility for potential document formats other than HTML,
+ Presentation widgets do not attempt to parse their template as
+ HTML tags. The structure of the template is <code>"HTML Text
+ %%%%python_expression()%%%% more HTML Text"</code>. Every set
+ of 4 percent signs (%%%%) switches back and forth between
+ evaluation and printing.</p>
+
+ <p>No control structures are allowed in the template. This was
+ originally thought to be a potentially major inconvenience, but
+ with use of the Web.Widgets code to develop a few small sites,
+ it has seemed trivial to encapsulate any table-formatting code
+ within a method; especially since those methods can take string
+ arguments if there's a need to customize the table's
+ appearance.</p>
+
+ <p>The namespace for evaluating the template expressions is
+ obtained by scanning the class hierarchy for attributes, and
+ getting each of those attributes from the current instance.
+ This means that all methods will be bound methods, so
+ indicating "self" explicitly is not required. While it is
+ possible to override the method for creating namespaces, using
+ this default has the effect of associating all presentation
+ code for a particular widget in one class, along with its
+ template. If one is working with a non-programmer designer, and
+ the template is in an external file, it is always very clear to
+ the designer what functionality is available to them in any
+ given scope, because there is a list of available methods for
+ any given class.</p>
+
+ <p>A convenient event to register for would be a response from
+ the PB service that we just implemented. We can use the
+ <code>Deferred</code> class in order to indicate to the widgets
+ framework that certain work has to be done later. This is a
+ Twisted convention which one can currently use in PB as well as
+ webwidgets; any framework which needs the ability to defer a
+ return value until later should use this facility. Elements of
+ the page will be rendered from top to bottom as data becomes
+ available, so the page will not be blocked on rendering until
+ all deferred elements have been completed.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.python import defer
+from twisted.web import widgets
+class EchoDisplay(widgets.Presentation):
+ template = """&lt;H1&gt;Welcome to my widget, displaying %%%%echotext%%%%.&lt;/h1&gt;
+ &lt;p&gt;Here it is: %%%%getEchoPerspective()%%%%&lt;/p&gt;"""
+ echotext = 'hello web!'
+ def getEchoPerspective(self):
+ d = defer.Deferred()
+ pb.connect(d.callback, d.errback, "localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 1)
+ d.addCallbacks(self.makeListOf, self.formatTraceback)
+ return ['&lt;b&gt;',d,'&lt;/b&gt;']
+ def makeListOf(self, echoer):
+ d = defer.Deferred()
+ echoer.echo(self.echotext, pbcallback=d.callback, pberrback=d.errback)
+ d.addCallbacks(widgets.listify, self.formatTraceback)
+ return [d]
+if __name__ == "__main__":
+ from twisted.web import server
+ from twisted.internet import main
+ a = main.Application("pbweb")
+ gdgt = widgets.Gadget()
+ gdgt.widgets['index'] = EchoDisplay()
+ a.listenOn(8080, server.Site(gdgt))
+ a.run()
+</pre>
+
+ <h6>Listing 16: an event-based web widget.</h6>
+ </blockquote>
+
+ <p>Each time a Deferred is returned as part of the page, the
+ page will pause rendering until the deferred's
+ <code>callback</code> method is invoked. When that callback is
+ made, it is inserted at the point in the page where rendering
+ left off.</p>
+
+ <p>If necessary, there are options within web.widgets to allow
+ a widget to postpone or cease rendering of the entire page --
+ for example, it is possible to write a FileDownload widget,
+ which will override the rendering of the entire page and
+ replace it with a file download.</p>
+
+ <p>The final goal of web.widgets is to provide a framework
+ which encourages the development of usable library code. Too
+ much web-based code is thrown away due to its particular
+ environment requirements or stylistic preconceptions it carries
+ with it. The goal is to combine the fast-and-loose iterative
+ development cycle of PHP with the ease of installation and use
+ of Zope's "Product" plugins.</p>
+
+ <h3>Things That Twisted Does Not Do</h3>
+
+ <p>It is unfortunately well beyond the scope of this paper to
+ cover all the functionality that Twisted provides, but it
+ serves as a good overview. It may seem as though twisted does
+ anything and everything, but there are certain features we
+ never plan to implement because they are simply outside the
+ scope of the project.</p>
+
+ <p>Despite the multiple ways to publish and access objects,
+ Twisted does not have or support an interface definition
+ language. Some developers on the Twisted project have
+ experience with remote object interfaces that require explicit
+ specification of all datatypes during the design of an object's
+ interface. We feel that such interfaces are in the spirit of
+ statically-typed languages, and are therefore suited to the
+ domain of problems where statically-typed languages excel.
+ Twisted has no plans to implement a protocol schema or static
+ type-checking mechanism, as the efficiency gained by such an
+ approach would be quickly lost again by requiring the type
+ conversion between Python's dynamic types and the protocol's
+ static ones. Since one of the key advantages of Python is its
+ extremely flexible dynamic type system, we felt that a
+ dynamically typed approach to protocol design would share some
+ of those advantages.</p>
+
+ <p>Twisted does not assume that all data is stored in a
+ relational database, or even an efficient object database.
+ Currently, Twisted's configuration state is all stored in
+ memory at run-time, and the persistent parts of it are pickled
+ at one go. There are no plans to move the configuration objects
+ into a "real" database, as we feel it is easier to keep a naive
+ form of persistence for the default case and let
+ application-specific persistence mechanisms handle persistence.
+ Consequently, there is no object-relational mapping in Twisted;
+ <code>twisted.enterprise</code> is an interface to the
+ relational paradigm, not an object-oriented layer over it.</p>
+
+ <p>There are other things that Twisted will not do as well, but
+ these have been frequently discussed as possibilities for it.
+ The general rule of thumb is that if something will increase
+ the required installation overhead, then Twisted will probably
+ not do it. Optional additions that enhance integration with
+ external systems are always welcome: for example, database
+ drivers for Twisted or a CORBA IDL for PB objects.</p>
+
+ <h3>Future Directions</h3>
+
+ <p>Twisted is still a work in progress. The number of protocols
+ in the world is infinite for all practical purposes, and it
+ would be nice to have a central repository of event-based
+ protocol implementations. Better integration with frameworks
+ and operating systems is also a goal. Examples for integration
+ opportunities are automatic creation of installer for "tap"
+ files (for Red Hat Packager-based distributions, FreeBSD's
+ package management system or Microsoft Windows(tm) installers),
+ and integration with other event-dispatch mechanisms, such as
+ win32's native message dispatch.</p>
+
+ <p>A still-nascent feature of Twisted, which this paper only
+ touches briefly upon, is <code>twisted.enterprise</code>: it is
+ planned that Twisted will have first-class database support
+ some time in the near future. In particular, integration
+ between twisted.web and twisted.enterprise to allow developers
+ to have SQL conveniences that they are used to from other
+ frameworks.</p>
+
+ <p>Another direction that we hope Twisted will progress in is
+ standardization and porting of PB as a messaging protocol. Some
+ progress has already been made in that direction, with XEmacs
+ integration nearly ready for release as of this writing.</p>
+
+ <p>Tighter integration of protocols is also a future goal, such
+ an FTP server that can serve the same resources as a web
+ server, or a web server that allows users to change their POP3
+ password. While Twisted is already a very tightly integrated
+ framework, there is always room for more integration. Of
+ course, all this should be done in a flexible way, so the
+ end-user will choose which components to use -- and have those
+ components work well together.</p>
+
+ <h3>Conclusions</h3>
+
+ <p>As shown, Twisted provides a lot of functionality to the
+ Python network programmer, while trying to be in his way as
+ little as possible. Twisted gives good tools for both someone
+ trying to implement a new protocol, or someone trying to use an
+ existing protocol. Twisted allows developers to prototype and
+ develop object communication models with PB, without designing
+ a byte-level protocol. Twisted tries to have an easy way to
+ record useful deployment options, via the
+ <code>twisted.tap</code> and plugin mechanisms, while making it
+ easy to generate new forms of deployment. And last but not
+ least, even Twisted is written in a high-level language and
+ uses its dynamic facilities to give an easy API, it has
+ performance which is good enough for most situations -- for
+ example, the web server can easily saturate a T1 line serving
+ dynamic requests on low-end machines.</p>
+
+ <p>While still an active project, Twisted can already used for
+ production programs. Twisted can be downloaded from the main
+ Twisted site (http://www.twistedmatrix.com) where there is also
+ documentation for using and programming Twisted.</p>
+
+ <h3>Acknowledgements</h3>
+
+ <p>We wish to thank Sean Riley, Allen Short, Chris Armstrong,
+ Paul Swartz, J&uuml;rgen Hermann, Benjamin Bruheim, Travis B.
+ Hartwell, and Itamar Shtull-Trauring for being a part of the
+ Twisted development team with us.</p>
+
+ <p>Thanks also to Jason Asbahr, Tommi Virtanen, Gavin Cooper,
+ Erno Kuusela, Nick Moffit, Jeremy Fincher, Jerry Hebert, Keith
+ Zaback, Matthew Walker, and Dan Moniz, for providing insight,
+ commentary, bandwidth, crazy ideas, and bug-fixes (in no
+ particular order) to the Twisted team.</p>
+
+ <h3>References</h3>
+
+ <ol>
+ <li>The Twisted site, http://www.twistedmatrix.com</li>
+
+ <li>Douglas Schmidt, Michael Stal, Hans Rohnert and Frank
+ Buschmann, Pattern-Oriented Software Architecture, Volume 2,
+ Patterns for Concurrent and Networked Objects, John Wiley
+ &amp; Sons</li>
+
+ <li>Abhishek Chandra, David Mosberger, Scalability of Linux
+ Event-Dispatch Mechanisms, USENIX 2001,
+ http://lass.cs.umass.edu/~abhishek/papers/usenix01/paper.ps</li>
+
+ <li>Protocol specifications, http://www.rfc-editor.com</li>
+
+ <li>The Twisted Philosophical FAQ,
+ http://www.twistedmatrix.com/page.epy/twistedphil.html</li>
+
+ <li>Twisted Advocacy,
+ http://www.twistedmatrix.com/page.epy/whytwisted.html</li>
+
+ <li>Medusa, http://www.nightmare.com/medusa/index.html</li>
+
+ <li>Using Spreadable Web Servers,
+ http://www.twistedmatrix.com/users/jh.twistd/python/moin.cgi/TwistedWeb</li>
+
+ <li>Twisted.Spread implementations for other languages,
+ http://www.twistedmatrix.com/users/washort/</li>
+
+ <li>PHP: Hypertext Preprocessor, http://www.php.net/</li>
+
+ <li>The Z Object Publishing Environment,
+ http://www.zope.org/, http://zope.com/</li>
+ </ol>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/europython/doanddont.html b/vendor/Twisted-10.0.0/doc/historic/2003/europython/doanddont.html
new file mode 100644
index 0000000000..79b007289f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/europython/doanddont.html
@@ -0,0 +1,508 @@
+<html><head><title>Idioms and Anti-Idioms in Python</title></head><body>
+
+<h1>Idioms and Anti-Idioms in Python</h1>
+
+<h2>Idioms and Anti-Idioms, AKA Do and Don't</h2><ul>
+<li>Welcome</li>
+
+<li>Gimmick -- Charmed quotes</li>
+
+</ul>
+<hr />
+<em>Prue (Something Wicca This Way Comes, season 1) -- No, we are not supposed to use our powers</em>
+<h2>Python</h2><ul>
+<li>Few gotchas...</li>
+
+<li>...but not zero</li>
+
+<li>Most are easy to avoid...</li>
+
+<li>...if you know about them.</li>
+
+</ul>
+<hr />
+<em>Prue (Something Wicca This Way Comes, season 1) -- Uh, it doesn't work out there either.</em>
+<h2>Exceptions</h2><ul>
+<li>Primary method of dealing with errors</li>
+
+<li>Flexible</li>
+
+<li>Good opportunity to shoot yourself in foot...</li>
+
+<li>...without knowing about it (bug only shows up rarely.)</li>
+
+</ul>
+<hr />
+<em>Leo (Paige From the Past, season 4) -- You have to [...] Paige. No exceptions.</em>
+<h2>Exceptions -- Catching Too Much</h2><ul>
+<li>Classical case: 'except:'</li>
+
+<li>Will catch anything</li>
+
+<li>Including most bugs...<ul><li>NameError, AttributeError...</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Piper (Charmed Again, season 4) -- Okay, well this is way too much for me to handle. </em>
+<h2>Exceptions -- Catching Too Much -- Example</h2>
+<pre class="python">
+try:
+ f = opne("file")
+except:
+ sys.exit("no such file")
+</pre>
+
+<hr />
+<em>Piper (Charmed Again, season 4) -- Way too much.</em>
+<h2>Exceptions -- Catching Too Soon</h2><ul>
+<li>The slogan:<ul><li>Don't catch errors you can do nothing about</li>
+</ul></li>
+
+<li>Catching exceptions should by the 'user'</li>
+
+<li>The point where the value is *used*<ul><li>Rather than passed on</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Phoebe (Knight to Remember, season 4) -- Maybe it's just too soon.</em>
+<h2>Exceptions -- Catching Too Soon -- Example</h2>
+<pre class="python">
+def readlinesfromfile(file):
+ try:
+ return open(file).readlines()
+ except IOError:
+ pass # do what?
+</pre>
+<ul><li>What can we do?<ul><li>Return empty list? bad</li>
+
+<li>Print warning? what if it's one of several possibilities</li>
+
+<li>Exit? NO!</li>
+
+<li>Raise our own exception? Losing information</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Paige (Knight to Remember, season 4) -- I'm already a little late.</em>
+<h2>Catching multiple exceptions</h2><ul>
+<li>Spot bug here:</li></ul>
+
+<pre class="python">
+try:
+ fp = open("file")
+except IOError, OSError:
+ print "could not open file"
+</pre>
+<hr />
+<em>Paige (Knight to Remember, season 4) -- Because I've got too many responsibilities</em>
+<h2>Catching multiple exceptions (cont'd)</h2><ul>
+<li>Bug:</li>
+
+<li>Only IOError gets caught...</li>
+
+<li>and exception value is put in OSError</li>
+
+<li>But most exceptions are IOError :(</li>
+
+<li>Likely to not discover this bug</li>
+
+</ul>
+<hr />
+<em>Piper (Knight to Remember, season 4) -- Alright! Calm down!</em>
+<h2>Catching multiple exceptions (cont'd 2)</h2><ul>
+<li>Correct way</li></ul>
+
+<pre class="python">
+try:
+ fp = open("file")
+except (IOError, OSError):
+ print "could not open file"
+</pre>
+<hr />
+<em>Phoebe (Knight to Remember, season 4) -- Besides that, maybe we can help</em>
+<h2>Catching NameError</h2>
+
+<pre class="python">
+try:
+ import foo
+except ImportError:
+ pass
+
+try:
+ foo.Function()
+except NameError:
+ pass # some replacement
+</pre>
+<ul><li>Bad idea!</li>
+
+</ul>
+<hr />
+<em>Piper (Morality Bites, season 4) -- That's OK, I forgot your name too.</em>
+<h2>Catching NameError (cont'd)</h2>
+
+<pre class="python">
+try:
+ import foo
+except ImportError:
+ foo = None
+
+if foo is not None:
+ foo.Function()
+else:
+ pass # some replacement
+</pre>
+<ul><li>If foo.Function() sometimes has a NameError, we won't mask it...</li>
+
+<li>...or if we misspell 'foo'</li>
+
+</ul>
+<hr />
+<em>Anne (Morality Bites, season 4) -- Oh, right, sorry.</em>
+<h2>Importing Modules -- A Review</h2><ul>
+<li>'import module'</li>
+
+<li>'from module import name1, name2'</li>
+
+<li>Only imports once (or does it?)</li>
+
+</ul>
+<hr />
+<em>Phoebe (Animal Pragmatism, season 2) -- Rome was not built in a day,</em>
+<h2>Importing __main__</h2><ul>
+<li>__main__ is where the 'script' is executed</li>
+
+<li>Avoid the temptation to import __main__</li>
+
+<li>Put common function in a named module</li>
+
+<li>Then your code will be more useful</li>
+
+</ul>
+<hr />
+<em>Piper (Animal Pragmatism, season 2) -- And why mess with a good thing?</em>
+<h2>Importing a File Twice</h2><ul>
+<li>But it can't be, can it?</li>
+
+<li>Importing a script into itself</li></ul>
+
+<pre class="python">
+# file: hello.py
+import hello
+class Foo: pass
+</pre>
+<ul><li>Two 'Foo's, same definition, different class!</li>
+
+<li>If your sys.path includes packages...</li>
+
+<li>...you can import a module once from a package and once plain</li>
+
+</ul>
+<hr />
+<em>Phoebe (Which Prue Is It, Anyway?, season 1) -- Okay, which one of you is the real Prue?</em>
+<h2>Importing *</h2><ul>
+<li>Don't do it<ul><li>Don't do it</li>
+</ul></li>
+
+<li>Classic mistake:</li></ul>
+
+<pre class="python">
+from os import *
+
+fp = open("file") # works
+fp.readline() # fails with a weird error...?
+</pre>
+<ul><li>os.open returns a file descriptor (number)</li>
+
+</ul>
+<hr />
+<em>Pink Prue (Which Prue Is It, Anyway?, season 1) -- So, um, what did I do now?</em>
+<h2>Importing * Inside Functions</h2><ul>
+<li>Just invalid Python...</li>
+
+<li>...but happens to work in 1.5.2...</li>
+
+<li>...and sometimes in 2.1...</li>
+
+<li>...never in 2.2.</li>
+
+<li>Just Say No</li>
+
+</ul>
+<hr />
+<em>Pink Prue (Which Prue Is It, Anyway?, season 1) -- What ever it is, I have an alibi.</em>
+<h2>Importing Names</h2><ul>
+<li>from foo import name1, name2</li>
+
+<li>Not a bad idea always</li>
+
+<li>But be careful of repercussions:</li>
+
+<li>modules sometimes change things inside</li>
+
+<li>You won't see those changes</li>
+
+<li>Opportunity for inconsistency!</li>
+
+</ul>
+<hr />
+<em>Real Prue (Which Prue Is It, Anyway?, season 1) -- Because I still have to work here when all of this is over.</em>
+<h2>Reloading</h2><ul>
+<li>reload(module) -- reread module from file</li>
+
+<li>Useful in long running processes?</li>
+
+<li>Doesn't play nice with 'from import name'</li>
+
+<li>Beware of exceptions: the new classes are different from old classes</li>
+
+</ul>
+<hr />
+<em>Real Prue (Which Prue Is It, Anyway?, season 1) -- Don't worry I'm never casting that spell again.</em>
+<h2>exec, execfile and eval</h2><ul>
+<li>Execute arbitrary Python code</li>
+
+<li>No-cost scripting language for applications</li>
+
+<li>But easy to shoot one's self in the foot</li>
+
+</ul>
+<hr />
+<em>Reporter (Morality Bites, season 2) -- More news on the execution of Phoebe Halliwell coming up.</em>
+<h2>exec, execfile and eval -- Modify namespaces</h2><ul>
+<li>They modify the namespace they're in<ul><li>...but not always.</li>
+</ul></li>
+
+<li>Depends on global vs. inside functions</li>
+
+<li>Use with care -- or with explicit dictionaries</li>
+
+</ul>
+<hr />
+<em>Nathaniel (Morality Bites, season 2) -- Executions are a bitch to plan.</em>
+<h2>exec, execfile and eval -- Inside functions</h2><ul>
+<li>Unadorned exec is invalid inside functions</li>
+
+<li>execfile and eval play badly with local var. optimisation</li>
+
+<li>Always use with explicit dictionary</li>
+
+</ul>
+<hr />
+<em>Nathaniel (Morality Bites, season 2) -- Phoebe, what is this? An attempt to stay your execution?</em>
+<h2>Conclusion: recommended usage</h2><ul>
+<li>d={};exec "code" in d</li>
+
+<li>d={};execfile("file", d)</li>
+
+<li>d={};eval("expression", d)</li>
+
+<li>Sometimes useful to pre-populate dictionary</li>
+
+</ul>
+<hr />
+<em>Phoebe (Morality Bites, season 2) -- Just because you don't understand something, doesn't make it evil.</em>
+<h2>exec, execfile and eval -- Restricted Execution (Don't)</h2><ul>
+<li>rexec never was audited</li>
+
+<li>History of holes</li>
+
+<li>Dangerous to allow arbitrary code</li>
+
+<li>DoS attacks not defended against at all</li>
+
+<li>Recursion</li>
+
+</ul>
+<hr />
+<em>Leo (Morality Bites, season 2) -- Nobody's gonna rescue you.</em>
+<h2>Syntax</h2><ul>
+<li>Python syntax regular and nice...</li>
+
+<li>...but not perfect.</li>
+
+<li>Some care needed.</li>
+
+</ul>
+<hr />
+<em>Prue (Morality Bites, season 2) -- You know, we can still make the good things happen.</em>
+<h2>Syntax -- Tabs and Spaces</h2><ul>
+<li>Use Tabs</li>
+
+<li>Or use spaces</li>
+
+<li>But don't mix them...</li>
+
+<li>...ever!</li>
+
+<li>Invites bugs</li>
+
+</ul>
+<hr />
+<em>Prue (The Painted World, season 2) -- We've seen so many bizarre things.</em>
+<h2>Syntax -- Backslash Continuations</h2>
+
+<pre class="python">
+# Extra newline
+r = 1 \
+
++2
+
+# Missing backslash in long series
+r = 1 \
++2 \
++3 \
++4
++5 \
++6
+</pre>
+<ul><li>Both *silently* do the wrong things</li></ul>
+
+<h2>Syntax -- Backslash Continuations (cont'd)</h2>
+
+<ul><li>Better</li></ul>
+
+<pre class="python">
+# Extra newline
+r = (1
+
++2)
+
+# Long series
+r = (1
++2
++3
++4
++5
++6)
+</pre>
+
+<hr />
+<em>Prue (The Painted World, season 2) -- Uh, what just happened here?</em>
+<h2>Hand Hacking Batteries</h2><ul>
+<li>Don't write os.path functions yourself<ul><li>os.path.join especially</li>
+</ul></li>
+
+<li>min, max</li>
+
+<li>urlparse</li>
+
+<li>Skim through modules list. A lot.</li>
+
+</ul>
+<hr />
+<em>Prue (Animal Pragmatism, season 2) -- Well, we didn't find anything in the Book Of Shadows.</em>
+<h2>Further Reading</h2><ul>
+<li><a href="http://aspn.activestate.com/ASPN/Python/Reference/Products/ActivePython/howtos/doanddont/doanddont.html">http://aspn.activestate.com/ASPN/Python/Reference/Products/ActivePython/howtos/doanddont/doanddont.html</a></li>
+
+<li><a href="http://www.amk.ca/python/writing/warts.html">http://www.amk.ca/python/writing/warts.html</a></li>
+
+<li><a href="http://www.python.org/doc/essays/styleguide.html">http://www.python.org/doc/essays/styleguide.html</a></li>
+
+</ul>
+<hr />
+<em>Phoebe (The Painted World, season 2) -- I think you'll find me pretty knowledgeable about all areas</em>
+<h2>Questions?</h2>
+<em>Piper (The Painted World, season 2) -- You're like ask rainman.com</em>
+
+<h2>Bonus Slides</h2>
+<em>Phoebe (The Painted World, season 2) -- Oh, and P.S. there will be no personal gain.</em>
+
+<h2>Packages and __init__.py</h2><ul>
+<li>Packages are determined by __init__.py files</li>
+
+<li>Temptation to put code in __init__.py</li>
+
+<li>But two namespaces mix: __init__'s and filesystem's</li>
+
+<li>Put comments, docstring and __all__</li>
+
+</ul>
+<hr />
+<em>Piper (Animal Pragmatism, season 2) -- It's a package. One I would like to share with you.</em>
+<h2>Type Checking</h2><ul>
+<li>Python's typing is highly dynamic</li>
+
+<li>Capability-based, not class-based</li>
+
+<li>Explicit type checks hurt code usefulness</li>
+
+<li>(common use -- proxies, for testing)</li>
+
+</ul>
+<hr />
+<em>Phoebe (Black as Cole, season 2) -- I never thought of myself as the marrying type</em>
+<h2>Type Checking -- Example</h2><ul>
+<li>Here's what not to do:</li></ul>
+
+<pre class="python">
+class Foo:
+ def __init__(self, i):
+ if type(i) is types.StringType:
+ self.content = open(i).readlines()
+ elif type(i) is types.ListType:
+ self.content = i
+</pre>
+<ul><li>(inspired from a question on #python)</li>
+
+<li>More badness than you can shake a stick at.</li>
+
+</ul>
+<hr />
+<em>Phoebe (Muse to My Ears, season 4) -- You're an artistic, creative type.</em>
+<h2>Type Checking -- Example -- Fixed</h2>
+<pre class="python">
+
+class Foo:
+ pass
+
+class FooFromFile(Foo):
+
+ def __init__(self, filename):
+ self.content = open(filename).readlines()
+
+class FooFromList(Foo):
+
+ def __init__(self, list):
+ self.content = list
+</pre>
+
+<hr />
+<em>Phoebe (Muse to My Ears, season 4) -- You see how well this worked out?</em>
+<h2>Private __Attributes</h2><ul>
+<li>Useful in deep hierarchies to keep attributes separate</li>
+
+<li>Mangle only class name -- *not* module name</li>
+
+<li>Makes it harder to test</li>
+
+<li>Makes it harder to hand-hack for debugging</li>
+
+</ul>
+<hr />
+<em>Tessa (Animal Pragmatism, season 2) -- Maybe it's our fault because we tried to make them into something they're not.</em>
+
+<h2>Using Mutable Default Arguments</h2>
+
+<pre class="python">
+def foo(l=[]):
+ l.append(5);return l
+</pre>
+
+<ul>
+<li>Will modify the same list.</li>
+<li>If you want that -- use object, class, not that hack.</li>
+</ul>
+
+<pre class="python">
+def foo(l=None):
+ if l is None: l=[]
+ l.append(5);return l
+</pre>
+<hr />
+<em>Snake guy (Animal Pragmatism, season 2) --
+You two are acting like nothing's changed.</em>
+
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/europython/index.html b/vendor/Twisted-10.0.0/doc/historic/2003/europython/index.html
new file mode 100644
index 0000000000..051fb6d5ef
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/europython/index.html
@@ -0,0 +1,35 @@
+<html><head><title>Moshe's Talks</title></head><body>
+
+<h1>Moshe's talks</h1>
+
+<h2>Slides</h2>
+
+<ul>
+<li><a href="webclients-1.html">Writing Web Clients</a></li>
+<li><a href="doanddont-1.html">Idioms and Anti-Idioms</a></li>
+<li><a href="twisted-1.html">Introduction to Twisted</a></li>
+<li><a href="tw-deploy-1.html">Configuring and Deploying Twisted Web</a></li>
+<li><a href="lore-1.html">Using Lore</a></li>
+</ul>
+
+<h2>HTML</h2>
+
+<ul>
+<li><a href="webclients.html">Writing Web Clients</a></li>
+<li><a href="doanddont.html">Idioms and Anti-Idioms</a></li>
+<li><a href="twisted.html">Introduction to Twisted</a></li>
+<li><a href="tw-deploy.html">Configuring and Deploying Twisted Web</a></li>
+<li><a href="lore.html">Using Lore</a></li>
+</ul>
+
+<h2>PDF</h2>
+
+<ul>
+<li><a href="webclients.pdf">Writing Web Clients</a></li>
+<li><a href="doanddont.pdf">Idioms and Anti-Idioms</a></li>
+<li><a href="twisted.pdf">Introduction to Twisted</a></li>
+<li><a href="tw-deploy.pdf">Configuring and Deploying Twisted Web</a></li>
+<li><a href="lore.pdf">Using Lore</a></li>
+</ul>
+
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/europython/lore.html b/vendor/Twisted-10.0.0/doc/historic/2003/europython/lore.html
new file mode 100644
index 0000000000..edb33cc055
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/europython/lore.html
@@ -0,0 +1,502 @@
+<html><head><title>Lore</title></head><body>
+
+<h1>Lore</h1>
+<h2>Lore - A Document Generation System</h2><ul>
+<li>Gimmick -- Gilmore girls quotes</li>
+
+<li>Goal - take something which is easy to write, transforms to something easy to read</li>
+
+<li>For correct definitions of 'easy', of course</li>
+
+</ul>
+<hr />
+<em>Rory (Concert Interruptus, season 1) -- Yeah, well I've always thought easy is completely overrated.</em>
+<h2>Source Format</h2><ul>
+<li>Subset of XHTML 1.0<ul><li>Except for some new attributes</li>
+
+<li>Shouldn't bother browsers</li>
+</ul></li>
+
+<li>Slanted towards logical markup</li>
+
+</ul>
+<hr />
+<em>Alex (I Solemnly Swear, season 3) -- That would've been far too logical.</em>
+
+<h2>Output Formats</h2><ul>
+<li>Screen and paper<ul><li>Screen - 'fancy HTML'</li>
+
+<li>Paper - LaTeX<ul><li>Use LaTeX to produce PDF or PostScript</li>
+</ul></li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Madelaine (The Lorelais' First Day at Chilton, season 1) -- You don't know she's going out for the paper.</em>
+<h2>Minimal Lore Document</h2>
+<pre>
+&lt;html&gt;
+&lt;head&gt;&lt;title&gt;Title&lt;/title&gt;&lt;/head&gt;
+&lt;body&gt;&lt;h1&gt;Title&lt;/h1&gt;&lt;/body&gt;
+&lt;/html&gt;</pre>
+
+<hr />
+<em>Luke (There's the Rub, season 2) -- You said minimal</em>
+<h2>Minimal Lore Document Explained</h2><ul>
+<li>title element in head -- a must</li>
+
+<li>h1 element in head -- a must<ul><li>Must have same content</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Tom (There's the Rub, season 2) -- Hey, this is minimal</em>
+<h2>External Listings</h2><ul>
+<li>Advantage -- no need to quote</li>
+
+<li>Advantage -- test your examples</li>
+
+<li>Example:<ul><li><code>&lt;a class="python-listing" href="/usr/lib/python2.2/os.py"&gt;os.py&lt;/a&gt;</code>
+</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Kirk (Red Light on the Wedding Night, season 2) -- I include it as an example of the excellence I aspire to.</em>
+<h2>Using Lore to Generate HTML</h2><ul>
+<li>Write template</li>
+
+<li>[optional] Write stylesheet</li>
+
+<li>Run lore</li>
+
+</ul>
+<hr />
+<em>Paris (Run Away, Little Boy, season 2) -- I went on the web and found this site</em>
+<h2>Generating LaTeX</h2><ul>
+<li>lore -olatex file.html --&gt; produces file.tex</li>
+
+<li>Default is to create an 'article'</li>
+
+<li>Creating PostScript<ul><li>latex file.tex</li>
+
+<li>latex file.tex</li>
+
+<li>dvips -o file.ps file.dvi</li>
+</ul></li>
+
+<li>Creating PDF<li>latex file.tex</li>
+<li>pdflatex file.tex</li>
+</li>
+
+</ul>
+<hr />
+<em>Rory (Christopher Returns, season 1) -- He had already printed like a million</em>
+<h2>Using Lint</h2><ul>
+<li>lore -olint doc/howto/*.html</li>
+
+<li>lore -n -olint doc/howto/*.html #no output except warnings</li>
+
+</ul>
+<hr />
+<em>Max (The Deer-Hunters, season 1) -- I know a D seems pretty dismal</em>
+<h2>Further Reading</h2><ul>
+<li>Man page -- doc/man/lore.xhtml</li>
+
+<li>Howto -- doc/howto/lore.xhtml</li>
+
+<li>Extending howto -- doc/howto/extending-lore.xhtml</li>
+
+<li>Documentation standard -- doc/howto/doc-standard.xhtml</li>
+
+<li>Lore paper -- doc/historic/2003/pycon/lore/lore.html</li>
+
+</ul>
+<hr />
+<em>Paris (The Bracebridge Dinner, season 2) -- Rereading the Iliad a third time is not not doing anything</em>
+<h2>Questions?</h2>
+<em>Lorelai (Forgiveness and Stuff, season 1) -- A person needs details.</em>
+<h2>Bonus Slides</h2>
+<em>Miss James (The Lorelais' First Day at Chilton, season 1) -- If you do it in Latin you get extra credit.</em>
+<h2>Lore Alternatives - LaTeX</h2><ul>
+<li>Very good at printed results</li>
+
+<li>Model makes alternative parsers near-impossible</li>
+
+<li>Renderers to HTML are buggy and fragile</li>
+
+<li>People find it hard to use</li>
+
+</ul>
+<hr />
+<em>Michel (Love, Daisies and Troubadors, season 1) -- It increases my ennui</em>
+<h2>Lore Alternatives - HTML</h2><ul>
+<li>Too flexible</li>
+
+<li>No support for needed idioms<ul><li>Special-purpose Python markup</li>
+
+<li>Tables of contents</li>
+
+<li>Inlining</li>
+
+<li>Footnotes</li>
+</ul></li>
+
+<li>Renders badly to dead trees with current tools</li>
+
+</ul>
+<hr />
+<em>Lorelai (Love, Daisies and Troubadors, season 1) -- It was broken [...] I'm not crazy</em>
+<h2>Lore Alternatives - Docbook</h2><ul>
+<li>Using correctly requires too much work<ul><li>Write a DTD with special elements</li>
+
+<li>Write Jade stylesheets</li>
+</ul></li>
+
+<li>Lore is probably smaller than docbook specialisation</li>
+
+<li>People find it hard to use</li>
+
+</ul>
+<hr />
+<em>Rory (Hammers and Veils, season 2) -- What do you want me to do it?</em>
+<h2>Lore Alternatives - Texinfo</h2><ul>
+<li>Next slide, please</li>
+
+</ul>
+<hr />
+<em>Man (Hammers and Veils, season 2) -- There's a ton of hurt that almost happened here.</em>
+<h2>Lore Alternatives - reST</h2><ul>
+<li>Completely new language (no editor support)</li>
+
+<li>Hard to add new tags</li>
+
+<li>No linter</li>
+
+</ul>
+<hr />
+<em>Emily (Hammers and Veils, season 2) -- And this is what we need to discuss right now?</em>
+<h2>Lore Alternatives - LyX</h2><ul>
+<li>Dependent on GUI</li>
+
+</ul>
+<hr />
+<em>Rory (Hammers and Veils, season 2) -- Well, it's just dressed up a little.</em>
+<h2>Some Standard Tags -- XHTML Primer</h2><ul>
+<li><code>&lt;p&gt;paragraph&lt;/p&gt;</code>
+</li>
+
+<li><code>&lt;em&gt;emphasis&lt;/em&gt;</code>
+</li>
+
+<li><code>&lt;strong&gt;strong emphasis&lt;/strong&gt;</code>
+</li>
+
+<li>Headers<ul><li>&lt;h2&gt;sectionheader&lt;/h2&gt;</li><li>&lt;h3&gt;subsection&lt;/h3&gt;</li></ul></li>
+
+<li>Lists<ul><li>&lt;ol&gt;&lt;li&gt;ordered list item&lt;/li&gt;&lt;/ol&gt;</li><li>&lt;ul&gt;&lt;li&gt;unordered list item&lt;/li&gt;&lt;/ul&gt;</li></ul></li>
+
+<li><code>&lt;img src="http://example.com/img.png" /&gt;</code>
+<ul><li>Note '/' at end!</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Rory (Kiss and Tell, season 1) -- See, even a little information in your hands is dangerous.</em>
+<h2>More HTML</h2><ul>
+<li>Indicating authorship -- &lt;link rel="author" href="author@example.com" title="Author Name" /&gt;</li>
+
+<li>Put in &lt;head&gt;</li>
+
+<li>sub/sup -- subscripts, superscripts</li>
+
+</ul>
+<hr />
+<em>Max (The Lorelais' First Day at Chilton, season 1) -- Tolstoy's favourite author, for instance, was...</em>
+<h2>More HTML -- cross references</h2><ul>
+<li>Label<ul><code>&lt;a name="label-name" /&gt;</code>
+</ul></li>
+
+<li>Reference in file<ul><li><code>&lt;a href="#label-name"&gt;reference text&lt;/a&gt;</code></li>
+</ul></li>
+
+<li>Reference in other file<ul><li><code>&lt;a href="file-name#label-name"&gt;reference text&lt;/a&gt;</code></li>
+</ul></li>
+
+<li>Refer to URL<ul><li><code>&lt;a href="http://example.com"&gt;reference text&lt;/a&gt;</code></li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Christopher (Christopher Returns, season 1) -- It's just a weird reference.</em>
+<h2>Special Markup</h2><ul>
+<li>Things not in XHTML are done with div/span classes</li>
+
+<li>&lt;div class="note"&gt;note&lt;/a&gt; -- notes</li>
+
+<li>&lt;div class="doit"&gt;doit&lt;/a&gt; -- something not implemented</li>
+
+<li>&lt;span class="footnote"&gt;footnote&lt;/a&gt; -- put in a footnote</li>
+
+</ul>
+<hr />
+<em>Taylor (Take The Deviled Eggs, season 3) -- Out attention spans are gnat-like tonight</em>
+<h2>API References</h2><ul>
+<li><code>&lt;code class="API"&gt;urllib&lt;/code&gt;</code>
+</li>
+
+<li><code>&lt;code base="urllib" class="API"&gt;urlencode&lt;/code&gt;</code>
+</li>
+
+<li><code>&lt;code base="twisted" class="API"&gt;copyright.version&lt;/code&gt;</code>
+</li>
+
+</ul>
+<hr />
+<em>Lorelai (The Road Trip To Harvard, season 2) -- We're just kinda hanging out between classes</em>
+<h2>API References Explained</h2><ul>
+<li>Integrate with systems for docstring generation</li>
+
+<li>Generate links to auto-generated docs</li>
+
+</ul>
+<hr />
+<em>Luke (Love and War and Snow, season 1) -- How do you know? Do you have written documentation?</em>
+<h2>Inline Listings</h2><ul>
+<li>Use &lt;pre&gt;</li>
+
+<li>Possible classes: python, shell, python-interpreter</li>
+
+<li>Example:</li>
+
+</ul>
+<pre>
+&lt;pre class="python"&gt;
+def foo():
+ return forbnicate(4)
+&lt;/pre&gt;</pre>
+<hr />
+<em>Taylor (Take The Deviled Eggs, season 3) -- That's not even English.</em>
+<h2>Inline Listings -- short</h2><ul>
+<li>Use &lt;code&gt;</li>
+
+<li>Possible classes: python, shell, py-signature</li>
+
+<li>...and more</li>
+
+</ul>
+<hr />
+<em>Rory (Double Date, season 1) -- It's like this weird code thing with her.</em>
+<h2>Generating HTML -- writing templates</h2><ul>
+<li>Templates are XHTML documents</li>
+
+<li>Put in reference to stylesheet -- head is mostly kept as is</li>
+
+<li>Title will be prepended to document's title</li>
+
+<li>&lt;div class="toc" /&gt; will be replaced by table of contents</li>
+
+<li>&lt;div class="body" /&gt; will be replaced by processed body</li>
+
+</ul>
+<hr />
+<em>Paris (I Can't Get Started, season 2) -- How's this sound for a template?</em>
+<h2>Generating HTML -- using commandline</h2><ul>
+<li>Full details: the lore manpage</li>
+
+<li>Basic format: lore file.html --&gt; outputs file.xhtml<ul><li>--config template=template.tpl to use different template</li>
+
+<li>--config baseurl=format-string for the url of the auto-generated docstring docs</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Richard (The Third Lorelai, season 1) -- Your wish is my command.</em>
+<h2>Generating HTML -- using commandline -- examples</h2><ul>
+<li>lore --config template=strange.tpl foo.html</li>
+
+<li>lore --docsdir doc/howto/</li>
+
+<li>lore -p --docsdir doc/howto/ # use plain progress</li>
+
+</ul>
+<hr />
+<em>Jackson (A Deep-Fried Korean Thanksgiving, season 3) -- Deep-fried cake!</em>
+<h2>Generating HTML -- using commandline -- examples (cont'd)</h2><ul>
+<li>lore --docsdir doc/howto/ --config baseurl=../api/%s.html</li>
+
+<li>lore --ext='' foo.html # produce 'foo' as output</li>
+
+</ul>
+<hr />
+<em>Jackson (A Deep-Fried Korean Thanksgiving, season 3) -- Deep-fried shoe!</em>
+<h2>Generating HTML -- notes about stylesheets</h2><ul>
+<li>Many 'class's in the output</li>
+
+<li>The stylesheet Twisted uses can be used as example<ul><li>Especially the .py-src-* classes: used for syntax highlighting</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Miss Patty (Cinnamon's Wake, season 1) -- If you had a better hair style I might consider dating</em>
+<h2>Generating LaTeX -- examples</h2><ul>
+<li>lore -olatex --config section file.html</li>
+
+<li>lore -olatex --config book file.html</li>
+
+<li>lore -olatex --config section --docsdir doc/howto/</li>
+
+</ul>
+<hr />
+<em>Luke (Hammers and Veils, season 2) -- Just an example</em>
+<h2>Using Lint -- notes</h2><ul>
+<li>If there is an element which lint gives a warning you disagree with: &lt;element hlint="off"&gt;mistake&lt;/element&gt;</li>
+
+<li>But usually the linter is right</li>
+
+<li>lint exits with non-zero status iff some document was not clean -- useful in shell scripts</li>
+
+</ul>
+<hr />
+<em>Paris (The Deer-Hunters, season 1) -- That would be cause for concern.</em>
+<h2>Understanding Lint Warnings</h2><ul>
+<li>Format: file:line:column:warning</li>
+
+<li>Line/column always point to start/end of element</li>
+
+<li>Some justifications:<ul><li>&lt;pre&gt; with &gt;80 characters/line renders badly, both in HTML and in LaTeX</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Jess (Teach Me Tonight, season 2) -- I appreciate the warning.</em>
+<h2>Using Lore For Slides</h2><ul>
+<li>lore -ilore-slides -omgp file.html for magic point</li>
+
+<li>lore -ilore-slides -oprosper file.html for prosper</li>
+
+<li>lore -ilore-slides -ohtml file.html for HTML next/prev</li>
+
+<li>Splits on 'h2'</li>
+
+<li>Dogfooding</li>
+
+</ul>
+<hr />
+<em>Emily (Road Trip to Harvard, season 2) -- Why in the world do you insist on taking slides?</em>
+<h2>Extending Lore</h2><ul>
+<li>Accept more input tags</li>
+
+<li>Change how documents are processed</li>
+
+<li>Add more output formats</li>
+
+</ul>
+<hr />
+<em>Rory (The Lorelais' First Day at Chilton, season 1) -- Well, add a couple of plaid skirts</em>
+<h2>Extending Lore -- example</h2><ul>
+<li>We want to add a way to blink: &lt;span class="blink"&gt;</li>
+
+<li>Modify HTML output</li>
+
+<li>Modify lint output</li>
+
+<li>Make it 'small caps' in LaTeX</li>
+
+<li>Distribute as package 'blinker'</li>
+
+</ul>
+<hr />
+<em>Lorelai (Presenting Lorelai Gilmore, season 2) -- No, no, if you wanna do it, I'll help. It's just weird.</em>
+<h2>Extending Lore -- example (cont'd)</h2>
+<pre>
+# blinker/html.py
+from twisted.lore import tree
+from twisted.web import microdom, domhelpers
+
+def doBlink(document):
+ for node in domhelpers.findElementsWithAttribute(document, 'class',
+ 'blink'):
+ newNode = microdom.Element('blink')
+ newNode.children = node.children
+ node.parentNode.replaceChild(newNode, node)
+
+def doFile(fn, docsdir, ext, url, templ, linkrel=''):
+ doc = tree.parseFileAndReport(fn)
+ doBlink(doc)
+ cn = templ.cloneNode(1)
+ tree.munge(doc, cn, linkrel, docsdir, fn, ext, url)
+ cn.writexml(open(os.path.splitext(fn)[0]+ext, 'wb'))
+</pre>
+
+<hr />
+<em>Christopher (Presenting Lorelai Gilmore, season 2) -- I can't believe you're letting her do it.</em>
+<h2>Extending Lore -- example (cont'd 2)</h2>
+<pre>
+# blinker/latex.py
+class BlinkerLatexSpitter(latex.LatexSpitter):
+
+ def visitNode_span_blink(self, node):
+ self.writer('{\sc ')
+ self.visitNodeDefault(node)
+ self.writer('}')
+</pre>
+
+<hr />
+<em>Lorelai (Presenting Lorelai Gilmore, season 2) -- I'm sorry, I meant what scenario on my planet</em>
+<h2>Extending Lore -- example (cont'd 3)</h2>
+<pre>
+# blinker/factory.py
+from blinker import html, latex
+from twisted.lore import default
+
+class ProcessingFunctionFactory(default.ProcessingFunctionFactory):
+
+ doFile = [doFile]
+
+ latexSpitters = {None: latex.BlinkLatexSpitter}
+
+ def getLintChecker(self):
+ checker = lint.getDefaultChecker()
+ checker.allowedClasses = checker.allowedClasses.copy()
+ oldSpan = checker.allowedClasses['span']
+ checker.allowedClasses['span'] = (lambda x:oldSpan(x) or
+ x=='blink')
+ return checker
+
+factory = ProcessingFunctionFactory()
+</pre>
+<pre>
+# blinker/plugins.tml
+register("Blink-Lore",
+ "blinker.factory",
+ description="Lore format with blink",
+ type="lore",
+ tapname="blinklore")
+</pre>
+<p>...and that's it!</p>
+
+<hr />
+<em>Rory (Presenting Lorelai Gilmore, season 2) -- Sorry, we haven't tamed my wild ways yet.</em>
+<h2>Man page support</h2><ul>
+<li>No output</li>
+
+<li>Man-&gt;Lore conversion</li>
+
+<li>lore -iman -olint file.1 --&gt; generates file.html</li>
+
+</ul>
+<hr />
+<em>Lorelai (Concert Interruptus, season 1) -- would you like to write out some sort of instruction manual to go with the dishes?</em>
+<h2>Man page support</h2><ul>
+<li>No output</li>
+
+<li>Man-&gt;Lore conversion</li>
+
+<li>lore -iman -olint file.1 --&gt; generates file.html</li>
+
+</ul>
+<hr />
+<em>Lorelai (Concert Interruptus, season 1) -- would you like to write out some sort of instruction manual to go with the dishes?</em>
+
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/europython/slides-template.tpl b/vendor/Twisted-10.0.0/doc/historic/2003/europython/slides-template.tpl
new file mode 100644
index 0000000000..fd33fc3d18
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/europython/slides-template.tpl
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+ <title></title>
+ <link type="text/css" rel="stylesheet" href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ [<span class="navigation"><a class="next"></a></span> |
+ <span class="navigation"><a class="previous"></a></span>]
+ <h1 class="title"></h1>
+ <div class="body">
+
+ </div>
+ </body>
+</html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/europython/tw-deploy.html b/vendor/Twisted-10.0.0/doc/historic/2003/europython/tw-deploy.html
new file mode 100644
index 0000000000..628d12a8cb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/europython/tw-deploy.html
@@ -0,0 +1,1106 @@
+<html><head><title>A Twisted Web Tutorial</title></head><body>
+
+<h1>A Twisted Web Tutorial</h1>
+
+<h2>Twisted Web -- The Tutorial</h2><ul>
+<li>Welcome</li>
+
+<li>Gimmick -- Buffy quotes</li>
+
+</ul>
+<hr />
+<em>Sweet (Once More With Feeling, season 6) -- Showtime</em>
+<h2>Twisted Web</h2><ul>
+<li>Web server, using Twisted<ul><li>Like Apache, Zope...</li>
+</ul></li>
+
+<li>Serve static files</li>
+
+<li>Run CGIs</li>
+
+<li>Other uses...</li>
+
+</ul>
+<hr />
+<em>Giles (I Robot -- You Jane, season 1) -- There's a demon in the internet</em>
+<h2>Short Example: Putting a Server Up</h2><ul>
+<li>Here's all you need to know to bring up a server</li></ul>
+
+<pre class="shell">
+% mktap --uid=33 --gid=33 web --path=/var/www/htdocs --port=80
+% sudo twistd -f web.tap
+</pre>
+<ul><li>The rest of the talk will explain what that means...</li>
+
+<li>...and how to do more complicated things</li>
+
+</ul>
+<hr />
+<em>Buffy (Once More, With Feeling, season 6) -- I've got a theory. It doesn't matter.</em>
+<h2>Setup and Configuration Utilities</h2><ul>
+<li>mktap</li>
+
+<li>twistd</li>
+
+<li>websetroot<ul><li>Won't be covered, unless there is time</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Xander (The Harvest, season 1) -- crosses, garlic, stake through the heart</em>
+<h2>Digression: What are TAPs</h2><ul>
+<li>Pickled 'application configuration'</li>
+
+<li>Object which contains all the information about application</li>
+
+<li>The canonical way to represent configurations in Twisted</li>
+
+<li>Machine editable</li>
+
+</ul>
+<hr />
+<em>Master (The Wish, season 3) -- Behold the technical wonder</em>
+<h2>mktap</h2><ul>
+<li>General usage</li>
+
+<li>Flexibility and Power</li>
+
+</ul>
+<hr />
+<em>Buffy (Bad Eggs, season 2) -- I'm gonna need a *big* weapon</em>
+<h2>mktap web: Common Useful Options</h2><ul>
+<li>--path: serve from given path</li>
+
+<li>--port: listen on given port</li>
+
+<li>--user: serve from users' directories and personal servers</li>
+
+<li>--logfile: log to NCSA compatible logfile given</li>
+
+<li>--processor: add a special processor for a given extension</li>
+
+</ul>
+<hr />
+<em>Buffy (Bad Eggs, season 2) -- That's probably not gonna be the winning argument, is it?</em>
+
+<h2>twistd</h2><ul>
+<li>Start a Twisted Application</li>
+
+<li>Loads an instance of twisted.internet.app.Application from a file</li>
+
+<li>Daemonizes, binds to appropriate ports, and starts the Twisted mainloop</li>
+
+</ul>
+<hr />
+<em>Giles (Teacher's Pet, season 1) -- That's all he said? Fork Guy?</em>
+<h2>What's a Resource?</h2><ul>
+<li>Everything represented as twisted.web.resource.Resource</li>
+
+<li>Important interface:<ul><li>getChild()</li>
+
+<li>render()</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Sean (Go Fish, season 3) -- You're soakin' in it, bud.</em>
+<h2>Resource Examples</h2><ul>
+<li>Files<ul><li>MIME</li>
+
+<li>Processors</li>
+</ul></li>
+
+<li>Others<ul><li>Virtual hosts</li>
+
+<li>User directories</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Xander (As You Were, season 6) -- We have friends, family and demons</em>
+<h2>Web Development</h2><ul>
+<li>Processors<ul><li>Inherited from resource.Resource</li>
+
+<li>Interpret files as code rather than data</li>
+</ul></li>
+
+<li>Default processors<ul><li>.php -- default PHP</li>
+
+<li>.cgi -- Common Gateway Interface</li>
+
+<li>.rpy -- Correct way, Python scripting</li>
+
+<li>.trp -- Resource pickles</li>
+<li>...more</li>
+</ul></li>
+
+<li>You can also write your own</li>
+
+</ul>
+<hr />
+<em>Xander (Family, season 5) -- That was a tangled web</em>
+<h2>Custom Processor</h2><ul>
+<li>A custom processor to handle Perl CGIs (in a module called PerlScript)</li></ul>
+
+<pre class="python">
+from twisted.web import static, twcgi
+
+class PerlScript(twcgi.FilteredScript):
+ filter = '/usr/bin/perl' # Points to the perl parser
+</pre>
+<ul><li>Use:<ul><li>mktap web --path=/home/nafai/public_html --processor=.pl=PerlScript.PerlScript</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Tara (Family, season 4) -- There was the front of a camel</em>
+<h2>Resource Scripting</h2><ul>
+<li>Subclass resource.Resource</li>
+
+<li>Write a render(self, request) method<ul><li>Return string for immediate response</li>
+
+<li>Return NOT_DONE_YET and write to request</li>
+</ul></li>
+
+<li>Create an .rpy file that sets 'resource' to an instance</li>
+
+</ul>
+<hr />
+<em>Tara (Once More, With Feeling, season 6) -- You make me complete</em>
+<h2>.rpy example</h2>
+<pre class="python">
+from twisted.web import resource as resourcelib
+
+class MyGreatResource(resourcelib.Resource):
+ def render(self, request):
+ return "&lt;html&gt;foo&lt;/html&gt;"
+
+resource = MyGreatResource()
+</pre>
+<hr />
+<em>Willow (Welcome to the Hellmouth, season 1) -- It's probably easy for you.</em>
+<h2>Alternative Configuration Formats</h2><ul>
+<li>xml</li>
+
+<li>source</li>
+
+<li>Python</li>
+
+</ul>
+<hr />
+<em>Ben (The Gift, season 5) -- I wish there was another way</em>
+<h2>Alternative Configuration Formats -- Python</h2><ul>
+<li>Write manually</li>
+
+<li>Just uses twisted.web API</li>
+
+<li>Possible to do anything<ul><li>Write loops</li>
+
+<li>Read other files</li>
+
+<li>(not recommended) Define functions or classes</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Buffy (The I In Team, season 4) -- But I've learned that it pays to be flexible in life.</em>
+
+<h2>Python Configuration Example</h2><ul>
+
+<li>Create application</li>
+
+<li>Make it listen on port 80 for web requests...</li>
+
+<li>...which should be served from /var/www/htdocs</li></ul>
+
+<pre class="python">
+from twisted.internet import app
+from twisted.web import static, server
+
+application = app.Application('web')
+application.listenTCP(80,
+ server.Site(static.File("/var/www/htdocs")))
+</pre>
+<hr />
+<em>Willow (The Pack, season 1) -- It's simple, really.</em>
+
+<h2>Bannerfish -- A Case Study in Deployment</h2><ul>
+<li>Serves banner ads</li>
+
+<li>Has algorithms to maintain randomness and fairness</li>
+
+<li><a href="http://itamarst.org/software/bannerfish/">http://itamarst.org/software/bannerfish/</a></li>
+
+<li>But how to deploy?</li>
+
+</ul>
+<hr />
+<em>Xander (Halloween, season 2) -- Let's move out.</em>
+<h2>Bannerfish -- Standalone tap</h2><ul>
+<li>mktap bannerfish</li>
+
+<li>twistd -f bannerfish.tap</li>
+
+<li>For full list of options<ul><li>mktap --help bannerfish</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Ethan (Halloween, season 2) -- Don't wish to blow my own trumpet, but --</em>
+<h2>Bannerfish -- Standalone tap (behind reverse proxy)</h2><ul>
+<li>mktap bannerfish --port 81 --proxyhost=example.com</li>
+
+<li>twistd -f bannerfish.tap</li>
+
+<li>Now can work on internal server behind firewall</li>
+
+<li>If main server is Twisted Web, following Resource script will serve from bannerfish</li></ul>
+
+<pre class="python">
+resource = proxy.ReverseProxyResource('localhost', 81, '/')
+</pre>
+<hr />
+<em>Buffy (Halloween, season 2) -- You're sweet. A terrible liar, but sweet.</em>
+<h2>Bannerfish -- Standalone Python</h2>
+<pre class="python">
+from twisted.internet import app
+from twisted.cred import authorizer
+from twisted.web import server
+from bannerfish import service
+
+application = app.Application("bannerfish")
+auth = authorizer.DefaultAuthorizer(app)
+svc = service.BannerService('/var/bannerfish',
+ "bannerfish", application, auth)
+site = server.Site(svc.buildResource(None, None))
+application.listenTCP(80, site)
+</pre>
+<hr />
+<em>Spike (Halloween, season 2) -- Shaking. Terrified. Alone. Lost little lamb.</em>
+<h2>Bannerfish -- /etc/twisted-web/local.d Drop In</h2>
+<pre class="python">
+from twisted.cred import authorizer
+from bannerfish import service
+
+auth = authorizer.DefaultAuthorizer(app)
+svc = service.BannerService('/var/bannerfish',
+ "bannerfish", application, auth)
+resource = svc.buildResource(None, None)
+default.addChild("bannerfish", resource)
+</pre>
+<hr />
+<em>Cordelia (Halloween, season 2) -- Well, I guess you better get them back to their parents.</em>
+<h2>Bannerfish -- Resource Script</h2>
+<pre class="python">
+from twisted.cred import authorizer
+from twisted.internet import app
+from bannerfish import service
+
+application = registry.getComponent(app.Application)
+auth = authorizer.DefaultAuthorizer(application)
+svc = service.BannerService('/var/bannerfish',
+ "bannerfish", application, auth)
+resource = svc.buildResource(None, None)
+</pre>
+<ul><li>But see later, about registry</li>
+
+</ul>
+<hr />
+<em>Xander (Innocence, season 2) -- They like to see the big guns.</em>
+<h2>Bannerfish -- Distributed (Slave)</h2>
+
+<pre class="python">
+from twisted.internet import application
+from twisted.cred import authorizer
+from twisted.web import server
+from bannerfish import service
+application = app.Application("bannerfish")
+auth = authorizer.DefaultAuthorizer(application)
+svc = service.BannerService('/var/bannerfish',
+ "bannerfish", application, auth)
+site = server.Site(svc.buildResource(None, None))
+fact = pb.BrokerFactory(site)
+site = server.Site(root)
+application.listenUNIX('/var/run/bannerfish', fact)
+</pre>
+<h2>Bannerfish -- Distributed (Master, Resource Script)</h2>
+
+<pre class="python">
+from twisted.web import distrib
+
+resource = distrib.ResourceSubscription('unix',
+ '/var/run/bannerfish')
+</pre>
+<hr />
+<em>Oz (Innocence, season 2) -- So, do you guys steal weapons from the Army a lot?</em>
+<h2>Bannerfish -- Other options</h2><ul>
+<li>Mix and match possible</li>
+
+<li>Can serve same content multiple ways simultaneously</li>
+
+<li>Might be useful as a way to serve same ads different ways</li>
+
+<li>...or serve ads from several bannerfish servers...</li>
+
+<li>...each deployed differently.</li>
+
+</ul>
+<hr />
+<em>Buffy (Tabula Rasa, season 6) -- I'm like a superhero or something</em>
+<h2>Bannerfish -- Conclusions</h2><ul>
+<li>What are the tradeoffs?</li>
+
+<li>Everything works in the simple cases</li>
+
+<li>Not enough complicated cases to have data</li>
+
+<li>Luckily, easy to move between them</li>
+
+<li>Motto -- move deployment choices as late as possible</li>
+
+</ul>
+<hr />
+<em>Giles (Killed By Death, season 2) -- Simple enough, but, but</em>
+<h2>Further Reading</h2><ul>
+<li>Short overview -- doc/howto/web-overview.html</li>
+
+<li>In depth review -- doc/howto/using-twistedweb.html</li>
+
+<li>Using databases -- doc/howto/enterprise.html</li>
+
+<li>Deferred execution -- doc/howto/deferred.html</li>
+
+<li>Resource script examples -- doc/examples/*.rpy.py</li>
+
+</ul>
+<hr />
+<em>Giles (I Was Made to Love You, Season 5) -- There's an enormous amount of research we should do before -- no I'm lying</em>
+<h2>Questions?</h2>
+<em>Vampire Willow (Dopplegangland, season 3): Questions? Comments?</em>
+<h2>Bonus Slides</h2>
+<em>Xander (The Dark Age, season 2) -- A bonus day of class plus Cordelia.</em>
+
+<h2>Python Configuration -- Hints</h2><ul>
+<li>Working with persistence</li>
+
+<li>Processors</li>
+
+<li>Indices</li>
+
+<li>Virtual Hosts</li>
+
+</ul>
+<hr />
+<em>Buffy (Phases, season 2) -- Have you dropped any hints?</em>
+<h2>Python Configuration -- Persistence</h2><ul>
+<li>Don't define functions or classes</li>
+
+<li>Don't modify class attributes</li>
+
+</ul>
+<hr />
+<em>Spike (Once More, With Feeling) -- Let me rest in peace</em>
+<h2>Python Configuration -- Processors</h2>
+<pre class="python">
+
+from twisted.internet import app
+from twisted.web import static, server
+from twisted.web import twcgi
+
+root = static.File("/var/www")
+root.processors = {".cgi": twcgi.CGIScript}
+application = app.Application('web')
+application.listenTCP(80, server.Site(root))
+</pre>
+<hr />
+<em>Manny (Doublemeat Palace, season 6) -- It's a meat process</em>
+<h2>Python Configuration -- Indices</h2>
+<pre class="python">
+
+root = static.File("/var/www")
+root.indices = ['index.rpy', 'index.html']
+</pre>
+<hr />
+<em>Willow (Buffy vs. Dracula, season 5) -- Labelling your amulets and indexing your diaries</em>
+<h2>Python Configuration -- Virtual Hosts</h2>
+<pre class="python">
+
+from twisted.web import vhost
+default = static.File("/var/www")
+foo = static.File("/var/foo")
+root = vhost.NamedVirtualHost(default)
+root.addHost('foo.com', foo)
+</pre>
+<hr />
+<em>Fritz (I Robot, You Jane, season 1) -- The only reality is virtual.</em>
+<h2>Python Configuration -- uber example</h2>
+<pre class="python">
+
+from twisted.internet import app
+from twisted.web import static, server, vhost, script
+
+default = static.File("/var/www")
+default.processors = {".rpy", script.ResourceScript}
+root = vhost.NamedVirtualHost(default
+foo = static.File("/var/foo")
+foo.indices = ['index.xhtml', 'index.html']
+root.addHost('foo.com', foo)
+site = server.Site(root)
+application = app.Application('web')
+application.listenTCP(80, site, interface='127.0.0.1')
+</pre>
+<hr />
+<em>Buffy (Potential, season 7) -- It was putting a lot of stock in that uber-vamp</em>
+<h2>Python Configuration -- Splitting With Reverse Proxy</h2><ul>
+<li>Original use case - SVN</li></ul>
+
+<pre class="python">
+from twisted.web import proxy
+
+root.putChild('foo',
+ proxy.ReverseProxyResource('localhost',
+ 81, '/foo/'))
+</pre>
+<hr />
+<em>Buffy (Once More, With Feeling, season 6 -- So I will walk through the fire</em>
+<h2>mktap examples</h2><ul>
+<li>mktap web</li>
+
+<li>mktap web --path=/var/www --logfile=/var/log/twistedweb.log</li>
+
+<li>mktap web --port=80 --path=/var/www --mime-type=text/plain</li>
+
+<li>mktap web --path=/home/nafai/public_html --processor=.pl=PerlProcessor.PerlProcessor --index=index.pl</li>
+
+</ul>
+<hr />
+<em>Anya (I Was Made to Love You, season 4) -- You can also see the website I designed for the magic shop</em>
+<h2>mktap examples (cont'd)</h2><ul>
+<li>mktap web --users</li>
+
+<li>mktap web --ignore-ext=.cgi</li>
+
+<li>mktap web --index=index.cgi --index=index.rpy --index=index.html</li>
+
+</ul>
+<hr />
+<em>Buffy (Once More, With Feeling, season 6) -- All the twists and bends</em>
+<h2>mktap examples (alternate formats)</h2><ul>
+<li>mktap --type=source web</li>
+
+<li>mktap --type=xml web</li>
+
+</ul>
+<hr />
+<em>Tara (Seeing Red, season 6) -- It isn't written in any ancient language we could identify.</em>
+<h2>mktap examples (setting uid)</h2><ul>
+<li>mktap --uid=33 web</li>
+
+<li>mktap --gid=33 web</li>
+
+<li>Uid/Gid of www-data on Debian systems</li>
+
+<li>Not possible to use username</li>
+
+<li>More about this later</li>
+
+</ul>
+<hr />
+<em>Buffy (Who Are You?, season 4) -- I would be Buffy</em>
+
+<h2>twistd examples</h2><ul>
+<li>twistd -f web.tap -l /var/log/twisted.log</li>
+
+<li>twistd -f web.tap --pidfile /var/run/web.pid</li>
+
+<li>twistd -x web.tax<ul><li>For mktap --type=xml</li>
+</ul></li>
+
+<li>twistd -s web.tas<ul><li>For mktap --type=source</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Xander (Teacher's Pet, season 1) -- How come *that* never came up?</em>
+<h2>Shutting down twistd</h2><ul>
+<li>On Unix (in general): <ul><li>kill `cat twistd.pid`</li>
+</ul></li>
+
+<li>On Windows: <ul><li>Cannot daemonize on Windows, so just run twistd in a command prompt</li>
+
+<li>Switch to the command prompt, and press Control-C</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Buffy (Prophecy Girl, season 1) -- I don't wanna die.</em>
+<h2>Shutdown TAPs</h2><ul>
+<li>Since TAPs store persistent data for an application, a 'shutdown' TAP is created on twistd shutdown</li>
+
+<li>You'll often want to start your Twisted application on subsequent runs with the shutdown TAP</li>
+
+</ul>
+<hr />
+<em>Headstone (The Gift, season 5) -- She saved the world. A lot.</em>
+<h2>twistd and security</h2><ul>
+<li>When twistd is run as root, it will shed root privileges for the uid and gid of either the user that created the TAP or those specified on the mktap commandline.</li>
+
+</ul>
+<hr />
+<em>Buffy (Dopplegangland, season 3) -- I think it's good to be reliable</em>
+
+<h2>Resource Call Examples</h2><ul>
+<li>/foo/bar/baz gets converted to:</li></ul>
+
+<pre class="python">
+site.getChild('foo', request
+ ).getChild('bar', request
+ ).getChild('baz', request
+ ).render(request)
+</pre>
+<hr />
+<em>Willow/Tara (Afterlife, Part 2, season 6) -- Child of words, hear thy makers</em>
+<h2>Resource Call Examples (cont'd)</h2><ul>
+<li>/foo/bar/baz/ gets converted to:</li></ul>
+
+<pre class="python">
+site.getChild('foo', request
+ ).getChild('bar', request
+ ).getChild('baz', request
+ ).getChild('', request
+ ).render(request)
+</pre>
+<hr />
+<em>Buffy (Gone, season 6) -- Stop trying to see me.</em>
+<h2>Distributed Servers -- Theory</h2><ul>
+<li>Master is a resource</li>
+
+<li>Slave is a server</li>
+
+<li>Same server can have both master and slave parts</li>
+
+</ul>
+<hr />
+<em>Anya (Once More, With Feeling, season 6) -- I've got a theory, it could be bunnies</em>
+<h2>Distributed Servers -- Manually</h2>
+<pre class="python">
+from twisted.internet import app, protocol
+from twisted.web import server, distrib, static
+from twisted.spread import pb
+
+application = app.Application("silly-web")
+# The "master" server
+site = server.Site(distrib.ResourceSubscription('unix', '.rp'))
+application.listenTCP(19988, site)
+# The "slave" server
+fact = pb.BrokerFactory(distrib.ResourcePublisher(
+ server.Site(static.File('static'))))
+application.listenUNIX('./.rp', fact)
+</pre>
+<hr />
+<em>Buffy (Some Assembly Required, season 2) -- Men dig up the corpses and the women have the babies.</em>
+<h2>Distributed Servers -- Manual (cont'd)</h2><ul>
+<li>First Server</li></ul>
+
+<pre class="python">
+from twisted.internet import app, protocol
+from twisted.web import server, distrib, static, vhost
+from twisted.spread import pb
+
+application = app.Application("ping-web")
+
+default = static.File("/var/www/foo")
+root = vhost.NamedVirtualHost(default)
+root.addVhost("foo.com", default)
+bar = distrib.ResourceSubscription('unix', '.bar')
+root.addVhost("bar.com", bar)
+
+fact = pb.BrokerFactory(static.Site(default))
+site = server.Site(root)
+application.listenTCP(19988, site)
+application.listenUNIX('./.foo', fact)
+</pre>
+<hr />
+<em>Buffy (Welcome to the Hellmouth, season 1) -- Now, we can do this the hard way, or...</em>
+<h2>Distributed Servers -- Manual (cont'd 2)</h2><ul>
+<li>Second Server</li></ul>
+
+<pre class="python">
+from twisted.internet import app, protocol
+from twisted.web import server, distrib, static, vhost
+from twisted.spread import pb
+
+application = app.Application("pong-web")
+
+foo = distrib.ResourceSubscription('unix', '.foo')
+root = vhost.NamedVirtualHost(foo)
+root.addVhost("foo.com", foo)
+bar = static.File("/var/www/bar")
+root.addVhost("bar.com", bar)
+
+fact = pb.BrokerFactory(static.Site(bar))
+site = server.Site(root)
+application.listenTCP(19989, site)
+application.listenUNIX('./.bar', fact)
+</pre>
+<hr />
+<em>Buffy (Welcome to the Hellmouth, season 1) -- ...well, actually there's just the hard way.</em>
+<h2>Distributed Servers -- User Directory</h2><ul>
+<li>A resource</li>
+
+<li>Child that looks like 'moshez' -- ~moshez/public_html </li>
+
+<li>Child that looks like 'moshez.twistd' -- moshez's personal server</li>
+
+</ul>
+<hr />
+<em>Master (The Wish, season 3) -- Mass production!</em>
+<h2>Distributed Servers -- User Directory Server</h2><ul>
+<li>With mktap: mktap web --user</li>
+
+<li>With Python configuration</li></ul>
+
+<pre class="python">
+from twisted.internet import app
+from twisted.web import static, server, distrib
+
+root = static.File("/var/www")
+root.putChild("users", distrib.UserDirectory())
+site = server.Site(root)
+application = app.Application('web')
+application.listenTCP(80, site)
+</pre>
+<hr />
+<em>Richard (Reptile Boy, season 2) -- In his name.</em>
+<h2>Distributed Servers -- Personal Servers</h2><ul>
+<li>With mktap: mktap web --personal ...</li>
+
+<li>With Python configuration</li></ul>
+
+<pre class="python">
+from twisted.internet import app
+from twisted.web import static, server, distrib
+from twisted.spread import pb
+
+root = static.File("/home/moshez/twistd")
+site = server.Site(root)
+
+fact = pb.BrokerFactory(distrib.ResourcePublisher(site))
+application.listenUNIX('/home/moshez/.twisted-web-pb', fact)
+</pre>
+<hr />
+<em>Giles (Bargaining, season 6) -- It's my personal collection</em>
+<h2>Debian Configuration</h2><ul>
+<li>Inside twisted-web package</li>
+
+<li>Goal -- look like other web servers to users</li>
+
+<li>Goal -- interoperate easily</li>
+
+<li>Goal -- allow users to avoid modifying files</li>
+
+</ul>
+<hr />
+<em>Buffy (Bad Girls, season 3) -- We can help each other.</em>
+<h2>Debian Configuration -- Usage</h2><ul>
+<li>Changing port -- edit /etc/twisted-web/ports</li>
+
+<li>Want to use behind reverse proxy? Use rptwisted</li>
+
+<li>Change anything else -- drop files in /etc/twisted-web/local.d</li>
+
+</ul>
+<hr />
+<em>Faith (Home Coming, season 3) -- we'll use 'em</em>
+<h2>Debian Configuration -- Drop In Examples</h2>
+<pre class="python">
+from twisted.web import static
+import os
+
+vhostDir = '/var/www/vhost/'
+
+for file in os.listdir(vhostDir):
+ root.addHost(file, static.File(os.path.join(vhostDir, file)))
+</pre>
+<hr />
+<em>Buffy (The Freshman, season 4) -- I just thought I'd drop in</em>
+<h2>Debian Configuration -- Drop In Examples (cont'd)</h2>
+<pre class="python">
+from twisted.web import script, static
+
+default.processors['.rpy'] = script.ResourceScript
+default.ignoreExt('rpy')
+</pre>
+<hr />
+<em>Riley (As You Were, season 6) -- Sorry to just drop in on you</em>
+<h2>Debian Configuration -- Drop In Examples (cont'd 2)</h2>
+<pre class="python">
+from twisted.web import vhost
+
+default.putChild('vhost', vhost.VHostMonsterResource())
+</pre>
+<hr />
+<em>Sam (As You Were, season 6) -- a hairy night drop into hostile territory</em>
+<h2>twistedmatrix.com Configuration</h2><ul>
+<li>Some highlights</li></ul>
+
+<pre class="python">
+...
+indexNames = ['index', 'index.html', 'index.xhtml', 'index.rpy','index.cgi']
+...
+root.putChild('mailman', twcgi.CGIDirectory('/usr/lib/cgi-bin'))
+root.putChild('users', distrib.UserDirectory())
+root.putChild('cgi-bin', twcgi.CGIDirectory('/usr/lib/cgi-bin'))
+root.putChild('doc', static.File('/usr/share/doc'))
+...
+uid = pwd.getpwnam('www-data')[2]
+gid = grp.getgrnam('www-data')[2]
+...
+top = rewrite.RewriterResource(root, rewrite.tildeToUsers)
+...
+application = app.Application("web", uid=uid, gid=gid)
+</pre>
+<hr />
+<em>Xander (The Witch, season 1) -- May all lesser cretins bow before me.</em>
+<h2>Apache vs. Twisted Web</h2><ul>
+<li>Apache is faster</li>
+
+<li>Apache -- Threads/processes model</li>
+
+<li>Twisted -- async model</li>
+
+<li>Apache -- has C security holes (buffer overflows)</li>
+
+<li>Twisted -- easy to set up</li>
+
+<li>Twisted -- built in Python programmability</li>
+
+</ul>
+<hr />
+<em>Willow (Buffy vs. Dracular, season 5) -- I think we've just put our finger on why we're the sidekicks</em>
+<h2>Apache/Twisted Web Integration</h2><ul>
+<li>Use both!</li>
+
+<li>Apache's reverse proxy works well</li>
+
+<li>Easy to have a site which is partially managed by Apache</li>
+
+<li>Documentation has examples of configurations</li>
+
+</ul>
+<hr />
+<em>Xander (What's My Line, season 2) -- Angel's our friend! Except I don't like him.</em>
+<h2>Zope vs. Twisted Web</h2><ul>
+<li>Zope -- fully editable through the web</li>
+
+<li>Zope -- uses ZODB, not file system</li>
+
+<li>Twisted -- can integrate with other protocols easily</li>
+
+<li>Twisted -- extension code has much less overhead</li>
+
+</ul>
+<hr />
+<em>Willow (Dopplegangland, season 3) -- Competition is natural and healthy</em>
+<h2>Zope/Twisted Web Integration</h2><ul>
+<li>Possible to use Twisted as Zope's network layer</li>
+
+<li>Hackish with Zope2</li>
+
+<li>Easier with Zope3</li>
+
+</ul>
+<hr />
+<em>Snyder (Dopplegangland, season 3) -- It's a perfect match.</em>
+<h2>Zope/Twisted Web Integration (cont'd)</h2><ul>
+<li>Less direct -- use Apache</li>
+
+<li>Reverse proxy parts to Zope</li>
+
+<li>Reverse proxy parts to Twisted Web</li>
+
+</ul>
+<hr />
+<em>Wesley (Dopplegangland, season 3) -- Still a little sloppy, though</em>
+<h2>Applications Appropriate for Twisted Web</h2><ul>
+<li>Webmail</li>
+
+<li>Blogs</li>
+
+<li>Web/other protocol chat systems</li>
+
+</ul>
+<hr />
+<em>Sweet (Once More, With Feeling) -- Why don't you come and play?</em>
+<h2>Behind Reverse Proxy</h2><ul>
+<li>Sometimes, we want Twisted to pretend to be another host/port</li>
+
+<li>Reverse proxies, NATs, etc.</li>
+
+<li>Reverse proxy to /vhost/http/&lt;host:port&gt;/</li>
+
+<li>Make sure root has a child called vhost of type twisted.web.vhost.VirtualHostingMonster</li>
+
+</ul>
+<hr />
+<em>Jenny (I Robot -- You Jane, season 1) -- The divine exists in cyberspace</em>
+<h2>Rewrite Rules</h2><ul>
+<li>Change a URL to another</li>
+
+<li>Useful for different treatment from outside resources</li>
+
+<li>Wraps a resource</li>
+
+</ul>
+<hr />
+<em>Spike (What's My Line, season 2) -- Read it again.</em>
+<h2>Rewrite Rules -- Example</h2>
+<pre class="python">
+
+root = static.File("/var/www")
+root.putChild("users", distrib.UserDirectory())
+root = rewrite.RewriterResource(root, rewrite.tildeToUsers)
+</pre>
+<ul><li>Now, /~moshez/ works</li>
+
+</ul>
+<hr />
+<em>Spike (What's My Line, season 2) -- I think it's just enough kill.</em>
+<h2>websetroot</h2><ul>
+<li>Used to change what the root of the server points to</li>
+
+<li>Set it to a Resource contained either in a Python source file or a Pickle file</li>
+
+</ul>
+<hr />
+<em>Manny (DoubleMeat Palace, season 6) -- We have a lot of turnover here</em>
+<h2>Sample websetroot command lines</h2><ul>
+<li>websetroot -p 80 -f web.tap --script rootResource.py</li>
+
+<li>websetroot -p 8080 -f web.tap --pickle rootPickle</li>
+
+</ul>
+<hr />
+<em>Manny (DoubleMeat Palace, season 6) -- You can toss it</em>
+<h2>init.d</h2><ul>
+<li>pidfile</li>
+
+<li>chdir</li>
+
+<li>chroot?</li>
+
+<li>No smooth reloading</li>
+
+<li>Persistence</li>
+
+</ul>
+<hr />
+<em>Bob (Zeppo, season 3) -- He hasn't been initiated.</em>
+<h2>Special Bonus - How to Configure &lt;user&gt;.example.com</h2>
+<pre class="python">
+import pwd, os
+from twisted.web import resource, error, distrib
+from twisted.protocols import http
+
+class UserNameVirtualHost(resource.Resource):
+
+ def __init__(self, default, tail):
+ resource.Resource.__init__(self)
+ self.default = default
+ self.tail = tail
+ self.users = {}
+
+ def _getResourceForRequest(self, request):
+ host=request.getHeader('host')
+ if host.endswith(tail):
+ username = host[:-len(tail)]
+ else:
+ username = default
+ if self.users.has_key(username):
+ return self.users[username]
+ try:
+ (pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir,
+ pw_shell) = pwd.getpwnam(username)
+ except KeyError:
+ return error.ErrorPage(http.NOT_FOUND,
+ "No Such User",
+ "The user %s was not found on this system." %
+ repr(username))
+ twistdsock = os.path.join(pw_dir, ".twistd-web-pb")
+ rs = distrib.ResourceSubscription('unix',twistdsock)
+ self.users[username] = rs
+ return rs
+
+ def render(self, request):
+ resrc = self._getResourceForRequest(request)
+ return resrc.render(request)
+
+ def getChild(self, path, request):
+ resrc = self._getResourceForRequest(request)
+ request.path=request.path[:-1]
+ request.postpath=request.uri.split('/')[1:]
+ print request, request.path, request.postpath
+ return resrc.getChildForRequest(request)
+</pre>
+<hr />
+<em>Morgan (The Puppet Show, season 1) -- Weird? What d'you mean?</em>
+<h2>Special Bonus - How to Configure &lt;user&gt;.example.com (cont'd)</h2><ul>
+<li>Put above in a module (say, uservhost)</li>
+
+<li>Use following configuration file</li></ul>
+
+<pre class="python">
+from twisted.internet import app
+from twisted.web import server
+import uservhost
+
+root = UserNameVirtualHost("www", "example.com")
+site = server.Site(root)
+application = app.Application('web')
+application.listenTCP(80, site)
+</pre>
+<hr />
+<em>Snyder (The Puppet Show, season 1) -- You need to integrate into this school, people.</em>
+<h2>Using the Twisted Registry</h2><ul>
+<li>Use especially in Resource Scripts</li>
+
+<li>Save persistent information</li>
+
+<li>Uses Twisted's Componentized to be extensible</li>
+
+</ul>
+<hr />
+<em>Angel (Helpless, season 3) -- I wanted to keep it safe</em>
+<h2>Using the Twisted Registry -- example</h2>
+<pre class="python">
+
+from twisted.web import distrib
+
+resource = registry.getComponent(distrib.UserDirectory)
+if not resource:
+ resource = distrib.UserDirectory()
+ registry.setComponent(distrib.UserDirectory, resource)
+</pre>
+<hr />
+<em>Paul (The Freshman, season 4) -- Do you know where they're distributing the [...] applications?</em>
+<h2>Using the Twisted Registry -- problems</h2><ul>
+<li>In most cases -- need to write a custom class</li>
+
+<li>Saves data in-memory</li>
+
+<li>Won't work as expected unless -shutdown taps are used</li>
+
+</ul>
+<hr />
+<em>Anya (Once More, With Feeling, season 6) -- The only trouble is [pause] I'll never tell.</em>
+<h2>Alternative Configuration Formats -- XML</h2><ul>
+<li>Can be generated from mktap</li>
+
+<li>Editable with any XML editor</li>
+
+<li>Easy to do easy things<ul><li>Change a port</li>
+</ul></li>
+
+<li>Nontrivial to do hard things<ul><li>Bind to specific IP</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Buffy (Once More, With Feeling, season 6) -- To fit in in this glittering world.</em>
+<h2>Alternative Configuration Formats -- XML -- example</h2>
+<pre class="python">
+&lt;?xml version="1.0"?&gt;
+
+&lt;instance class="twisted.internet.app.Application" reference="1"&gt;
+ &lt;dictionary&gt;
+...
+ &lt;string role="key" value="tcpPorts" /&gt;
+ &lt;list&gt;
+ &lt;tuple&gt;
+ &lt;int value="80" /&gt;
+ &lt;instance class="twisted.web.server.Site"&gt;
+ &lt;dictionary&gt;
+...
+ &lt;string role="key" value="resource" /&gt;
+ &lt;instance class="twisted.web.static.File"&gt;
+ &lt;dictionary&gt;
+...
+ &lt;string role="key" value="path" /&gt;
+ &lt;string value="/var/www" /&gt;
+...
+ &lt;/dictionary&gt;
+ &lt;/instance&gt;
+...
+ &lt;/dictionary&gt;
+ &lt;/instance&gt;
+...
+ &lt;/tuple&gt;
+ &lt;/list&gt;
+...
+ &lt;/dictionary&gt;
+&lt;/instance&gt;
+</pre>
+<hr />
+<em>Natalie (Teacher's Pet, season 1) -- There's nothing ugly about these creatures</em>
+<h2>Alternative Configuration Formats -- Source</h2><ul>
+<li>Can be generated from mktap</li>
+
+<li>Editable with any Python source editor</li>
+
+<li>Easy to do easy things<ul><li>Change a port</li>
+</ul></li>
+
+<li>Nontrivial to do hard things<ul><li>Bind to specific IP</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Willow/Giles/Xander (Primeval, season 4) -- You could never hope to grasp the source</em>
+<h2>Alternative Configuration Formats -- Source -- Example</h2>
+<pre class="python">
+app=Ref(1,
+ Instance('twisted.internet.app.Application',{
+...
+ 'tcpPorts':[
+ (
+ 80,
+ Instance('twisted.web.server.Site',
+...
+ resource=Instance('twisted.web.static.File',{
+...
+ 'path':'/var/www',
+...
+ ),
+ ],
+...
+ }))
+</pre>
+<hr />
+<em>Tara (Family, season 5) -- You learn her source, and, uh we'll introduce her to her insect reflection</em>
+<h2>Twisted Web - Beginnings</h2>
+<pre class="python">
+
+&lt;glyphAtWork&gt; the http server was so we could say "Web!" if we ever did
+ a freshmeat announcement
+&lt;glyphAtWork&gt; this makes people excited
+</pre>
+<ul><li>Turned out he was right</li>
+
+</ul>
+<hr />
+<em>Dawn (Get It Done, season 7) -- I think it's an origin myth.</em>
+<h2>Woven Overview</h2><ul>
+<li>HTML templates</li>
+
+<li>Model/View/Controller architecture</li>
+
+<li>Integrated with deferred</li>
+
+<li>Classical systems work badly with async</li>
+
+<li>More -- beyond scope of this tutorial</li>
+
+</ul>
+<hr />
+<em>Razor (Bargaining, season 6) -- A pretty toy</em>
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/europython/twisted.html b/vendor/Twisted-10.0.0/doc/historic/2003/europython/twisted.html
new file mode 100644
index 0000000000..2576bca407
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/europython/twisted.html
@@ -0,0 +1,608 @@
+<html><head><title>Twisted Tutorial</title></head>
+<body>
+
+<h1>Twisted Tutorial</h1>
+
+<h2>Twisted -- The Tutorial</h2><ul>
+<li>Welcome</li>
+
+<li>Gimmick -- Charmed quotes</li>
+
+</ul>
+<hr />
+<em>Prue (Something Wicca This Way Comes, season 1) -- Piper, the girl has no vision, no sense of the future.</em>
+<h2>Twisted -- Networking For Python</h2><ul>
+<li>Handles the icky socket stuff</li>
+
+<li>Handles the icky select stuff</li>
+
+<li>No threads, no blocking</li>
+
+</ul>
+<hr />
+<em>Leo (Bite Me, season 4) -- As far as I know they're apart of a whole different network now.</em>
+<h2>Finger</h2><ul>
+<li>Send username</li>
+
+<li>Get back some stuff about user</li>
+
+<li>Will only implement subset of protocol here</li>
+
+</ul>
+<hr />
+<em>Natalie (Blinded By the Whitelighter) -- I'll assume a demon attacked your finger</em>
+<h2>Finger - Protocol code</h2>
+<pre class="python">
+from twisted.protocols import basic
+
+class FingerClient(basic.LineReceiver):
+
+ # This will be called when the connection is made
+ def connectionMade(self): self.sendLine(self.factory.user)
+
+ # This will be called when the server sends us a line.
+ # IMPORTANT: line *without "\n" at end.
+ # Yes, this means empty line does not mean EOF
+ def lineReceived(self, line): print line
+
+ # This will be called when the connection is terminated
+ def connectionLost(self, _): print "-"*40
+</pre>
+<hr />
+<em>Phoebe (Blind Sided, season 1) -- Standard dating protocol.</em>
+<h2>Finger - client factory</h2><ul>
+<li>Keep configuration information</li>
+
+<li>In this case, just the username</li></ul>
+
+<pre class="python">
+from twisted.internet import protocol
+
+class FingerFactory(protocol.ClientFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, user): self.user = user
+
+ def clientConnectionFailed(self, _, reason):
+ print "error", reason.value
+</pre>
+
+<hr />
+<em>Jack (Ms. Hellfire, season 2) -- Well, they'd better be a rich client</em>
+<h2>Finger - tying it all together</h2><ul>
+<li>Actually run above code</li>
+
+<li>Use reactors</li></ul>
+
+<pre class="python">
+from twisted.internet import reactor
+import sys
+
+user, host = sys.argv[1].split('@')
+port = 79
+reactor.connectTCP(host, port, FingerFactory(port))
+reactor.run()
+</pre>
+<hr />
+<em>Prue/Phoebe/Piper (Something Wicca This Way Comes, season 1) -- The power of three will set us free</em>
+<h2>Finger - a bug</h2><ul>
+<li>Succeed or fail, program doesn't exit</li>
+
+<li>Reactor continues in a loop</li>
+
+<li>Takes almost no CPU time...</li>
+
+<li>...but still wrong behaviour</li>
+
+</ul>
+<hr />
+<em>Leo (Trial By Magic, season 4) -- Demons you can handle but not rats?</em>
+<h2>Digression - Deferreds</h2><ul>
+<li>In order to be more flexible, we want callbacks</li>
+
+<li>Common callbacks are too weak</li>
+
+<li>We used 'deferreds' as an abstraction for callbacks</li>
+
+</ul>
+<hr />
+<em>Piper (Morality Bites, season 2) -- Talk about it later.</em>
+<h2>Finger - reimplementing correctly</h2>
+<pre class="python">
+from twisted.protocols import basic
+from twisted.internet import protocol, defer
+import sys
+
+class FingerClient(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.transport.write(self.factory.user+"\n")
+
+ def lineReceived(self, line):
+ self.factory.gotLine(line)
+</pre>
+
+
+<h2>Finger - reimplementing correctly (cont'd)</h2>
+<pre class="python">
+class FingerFactory(protocol.ClientFactory):
+ protocol = FingerProtocol
+
+ def __init__(self, user):
+ self.user, self.d = user, defer.Deferred()
+
+ def gotLine(self, line): print line
+
+ def clientConnectionLost(self, _, why): self.d.callback(None)
+
+ def clientConnectionFailed(self, _, why): self.d.errback(why)
+</pre>
+
+<h2>Finger - reimplementing correctly (cont'd 2)</h2>
+<pre class="python">
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ from twisted.python import util
+ user, host = sys.argv[1].split('@')
+ f = FingerFactory(user)
+ port = 79
+ reactor.connectTCP(host, port, FingerFactory(port))
+ f.d.addCallback(lambda _: reactor.stop())
+ f.d.addErrback(lambda _: (util.println("could not connect"),
+ reactor.stop()))
+ reactor.run()
+</pre>
+<hr />
+<em>Phoebe (Charmed and Dangerous, season 4) -- That's what we were missing.</em>
+<h2>Servers</h2><ul>
+<li>Servers are actually easier</li>
+
+<li>Servers meant to wait for events</li>
+
+<li>Most of concepts similar to clients</li>
+
+</ul>
+<hr />
+<em>Genie (Be Careful What You Witch For, season 2) -- All I know is that you rubbed and now I serve.</em>
+<h2>Finger - protocol</h2>
+<pre class="python">
+class FingerServer(basic.LineReceiver):
+
+ def lineReceived(self, line):
+ self.transport.write(self.factory.getUser(line))
+ self.transport.loseConnection()
+</pre>
+<hr />
+<em>Secretary (The Painted World, season 2) -- Well, you won't have any trouble with this if you figured that out.</em>
+<h2>Finger - factory</h2>
+<pre class="python">
+class FingerServerFactory(protocol.Factory):
+
+ protocol = FingerServer
+
+ def __init__(self):
+ self.users = {}
+ self.message = "No such user\n"
+
+ def getUser(self, name):
+ return self.users.get(name, self.message)
+
+ def setUser(self, user, status):
+ self.users[user] = status
+</pre>
+<hr />
+<em>Prue (The Demon Who Came In From the Cole, season 3) -- Okay, so who are they?</em>
+<h2>Finger - glue</h2>
+<pre class="python">
+factory = FingerServerFactory()
+factory.setUser("moshez", "Online - Sitting at computer\n")
+factory.setUser("spiv", "Offline - Surfing the waves\n")
+
+reactor.listenTCP(79, factory)
+</pre>
+<hr />
+<em>Prue (All Halliwell's Eve, season 3) -- Put it all together, it may just work.</em>
+<h2>Finger Server - problem</h2><ul>
+<li>What if server has to actually work to find user's status?</li>
+
+<li>For example, read status from a website</li>
+
+<li>API forces us to block -- not good</li>
+
+</ul>
+<hr />
+<em>Piper (All Halliwell's Eve, season 3) -- We've got big problems, a little time and a little magic.</em>
+<h2>Finger server -- new protocol</h2>
+<pre class="python">
+class FingerServer(basic.LineReceiver):
+
+ def lineReceived(self, line):
+ d = self.factory.getUser(line)
+ d.addCallback(self.writeResponse)
+ d.addErrback(self.writeError)
+
+ def writeResponse(self, response):
+ self.transport.write(response)
+ self.transport.loseConnection()
+
+ def writeError(self, error):
+ self.transport.write("Server error -- try later\n")
+ self.transport.loseConnection()
+</pre>
+<hr />
+<em>Piper (Ex Libris, season 2) -- We'll worry about it later.</em>
+<h2>Finger - factory</h2>
+<pre class="python">
+class FingerServerFactory(protocol.Factory):
+
+ protocol = FingerServer
+
+ def __init__(self):
+ self.users = {}
+ self.message = "No such user\n"
+
+ def getUser(self, name):
+ return defer.succeed(self.users.get(name, self.message))
+
+ def setUser(self, user, status):
+ self.users[user] = status
+</pre>
+<hr />
+<em>Piper/Zen Master (Enter the Demon, season 4) -- It is a different realm down there with new rules.</em>
+<h2>Finger - web factory</h2>
+<pre class="python">
+from twisted.web import client
+
+class FingerWebFactory(protocol.Factory):
+ protocol = FingerServer
+
+ def getUser(self, name):
+ url = "http://example.com/~%s/online" % name
+ d = client.getPage(url)
+ d.addErrback(lambda _: "No such user\n")
+ return d
+</pre>
+<hr />
+<em>Applicant #3 (The Painted World, season 2) -- in this day and age, who can't write in the HTML numeric languages, right?</em>
+<h2>Application</h2><ul>
+<li>The Twisted way of configuration files</li>
+
+<li>Decouple configuration from running</li></ul>
+
+<h2>Application (Example)</h2>
+<pre class="python">
+# File: finger.tpy
+from twisted.internet import app
+import fingerserver
+
+factory = fingerserver.FingerServerFactory()
+factory.setUser("moshez", "Online - Sitting at computer\n")
+factory.setUser("spiv", "Offline - Surfing the waves\n")
+application = app.Application("finger")
+application.listenTCP(79, factory)
+</pre>
+
+<hr />
+<em>Paige (Hell Hath No Fury, season 4) -- I am taking full responsibility for being late with the application.</em>
+<h2>twistd</h2><ul>
+<li>TWISTed Daemonizer</li>
+
+<li>Daemonizes Twisted servers</li>
+
+<li>Takes care of log files, PID files, etc.</li>
+
+<li>twistd -y finger.tpy</li>
+
+</ul>
+<hr />
+<em>Phoebe (Sleuthing With the Enemy, season 3) -- Was it some sick twisted demonic thrill?</em>
+<h2>twistd examples</h2><ul>
+<li>twistd -y finger.tpy -l /var/finger/log</li>
+
+<li>twistd -y finger.tpy --pidfile /var/run/finger.pid</li>
+
+<li>twistd -y finger.tpy --chroot /var/run</li>
+
+</ul>
+<hr />
+<em>Professor Whittlessy (Is There a Woogy In the House?, season 1) -- I use your house as an example</em>
+<h2>Writing Plugins</h2><ul>
+<li>Automatically create application configurations</li>
+
+<li>Accessible via commandline or GUI</li></ul>
+
+<h2>Writing Plugins (Example)</h2>
+<pre class="python">
+# File finger/tap.py
+from twisted.python import usage
+
+class Options(usage.Options):
+ synopsis = "Usage: mktap finger [options]"
+ optParameters = [["port", "p", 6666,"Set the port number."]]
+ longdesc = 'Finger Server'
+ users = ()
+
+ def opt_user(self, user):
+ if not '=' in user: status = "Online"
+ else: user, status = user.split('=', 1)
+ self.users += ((user, status+"\n"),)
+</pre>
+
+
+<h2>Writing Plugins (Example cont'd)</h2>
+<pre class="python">
+def updateApplication(app, config):
+ f = FingerFactory()
+ for (user, status) in config.users:
+ f.setUser(user, status)
+ app.listenTCP(int(config.opts['port']), s)
+</pre>
+<hr />
+<em>Paige (Bite Me, season 4) -- They won't join us willingly.</em>
+<h2>Writing Plugins (Example cont'd 2)</h2>
+<pre class="python">
+# File finger/plugins.tml
+register("Finger",
+ "finger.tap",
+ description="Finger Server",
+ type='tap',
+ tapname="finger")
+</pre>
+<hr />
+<em>Queen (Bite Me, season 4) -- That's what families are for.</em>
+<h2>Using mktap</h2><ul>
+<li>mktap finger --user moshez --user spiv=Offline</li>
+
+<li>twistd -f finger.tap</li>
+
+</ul>
+<hr />
+<em>Piper (Charmed and Dangerous, season 4) -- We'll use potions instead.</em>
+<h2>Delayed execution</h2><ul>
+<li>Basic interface: reactor.callLater(&lt;time&gt;, &lt;function&gt;, [&lt;arg&gt;, [&lt;arg&gt; ...]])</li>
+
+<li>reactor.callLater(10, reactor.stop)</li>
+
+<li>reactor.callLater(5, util.println, 'hello', 'world')</li>
+
+</ul>
+<hr />
+<em>Cole (Enter the Demon, season 4) -- I know, but not right now.</em>
+<h2>callLater(0,) -- An idiom</h2><ul>
+<li>Use to set up a call in next iteration of loop</li>
+
+<li>Can be used in algorithm-heavy code to let other code run</li></ul>
+
+<pre class="python">
+def calculateFact(cur, acc=1, d=None):
+ d = d or defer.Deferred()
+ if cur&lt;=1: d.callback(acc)
+ else: reactor.callLater(0, calculateFact, acc*cur, cur-1, d)
+
+calculateFact(10
+).addCallback(lambda n: (util.println(n), reactor.stop()))
+reactor.run()
+</pre>
+<hr />
+<em>Piper (Lost and Bound, season 4) -- Someone, I won't say who, has the insane notion</em>
+<h2>UNIX Domain Sockets</h2><ul>
+<li>Servers<ul><li>reactor.listenUNIX('/var/run/finger.sock', FingerWebFactory())</li>
+</ul></li>
+
+<li>Clients<ul><li>reactor.connectUNIX('/var/run/finger.sock', FingerFactory())</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Kate (Once Upon a Time, season 3) -- Fairies don't talk the same way people do.</em>
+<h2>SSL Servers</h2>
+
+<pre class="python">
+from OpenSSL import SSL
+
+class ServerContextFactory:
+
+ def getContext(self):
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file('server.pem')
+ ctx.use_privatekey_file('server.pem')
+ return ctx
+
+reactor.listenSSL(111, FingerWebFactory(), ServerContextFactory())
+</pre>
+
+<h2>SSL Clients</h2>
+
+<ul>
+<li>from twisted.internet import ssl</li>
+
+<li>reactor.connectSSL(111, 'localhost', FingerFactory(), ssl.ClientContextFactory())</li>
+</ul>
+<hr />
+<em>Natalie (Blinded By the Whitelighter, season 3) -- I mean, in private if you wouldn't mind</em>
+<h2>Running Processes</h2><ul>
+<li>A process has two outputs: stdout and stderr</li>
+
+<li>Protocol to interface with it is different</li></ul>
+
+<pre class="python">
+class Advertizer(protocol.ProcessProtocol):
+ def outReceived(self, data): print "out", `data`
+
+ def errReceived(self, data): print "error", `data`
+
+ def processEnded(self, reason): print "ended", reason
+
+reactor.spawnProcess(Advertizer(),
+ "echo", ["echo", "hello", "world"])
+</pre>
+<hr />
+<em>Prue (Coyote Piper, season 3) -- You have to know that you can talk to me</em>
+<h2>Further Reading</h2><ul>
+<li><a href="http://twistedmatrix.com/documents/">Twisted Docs</a></li>
+
+</ul>
+<hr />
+<em>Phoebe (Animal Pragmatism, season 2) -- Ooh, the girls in school are reading this.</em>
+<h2>Questions?</h2>
+<em>Piper (Something Wicca This Way Comes, season 1) -- Tell me that's not our old spirit board?</em>
+<h2>Bonus Slides</h2>
+<em>Prue (Sleuthing With the Enemy, season 3) -- All right, you start talking or we start the bonus round.</em>
+<h2>Perspective Broker</h2><ul>
+<li>Meant to be worked async</li>
+
+<li>Can transfer references or copies</li>
+
+<li>Secure (no pickles or other remote execution mechanisms)</li>
+
+<li>Lightweight (bandwidth and CPU)</li>
+
+<li>Translucent</li>
+
+</ul>
+<hr />
+<em>Paige (Charmed Again, season 4) -- I guess I just kind of feel - connected somehow.</em>
+<h2>PB Remote Control Finger (Server)</h2>
+<pre class="python">
+from twisted.spread import pb
+
+class FingerSetter(pb.Root):
+
+ def __init__(self, ff): self.ff = ff
+
+ def remote_setUser(self, name, status):
+ self.ff.setUser(name, status+"\n")
+
+ff = FingerServerFactory()
+setter = FingerSetter(ff)
+reactor.listenUNIX("/var/run/finger.control",
+ pb.BrokerFactory(setter))
+</pre>
+<hr />
+<em>Piper (Be Careful What You Witch For, season 2) -- Okay, you think you can control the power this time?</em>
+<h2>PB Remote Control Finger (Client)</h2>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import reactor
+import sys
+
+def failed(reason):
+ print "failed:", reason.value;reactor.stop()
+
+pb.getObjectAt("unix", "/var/run/finger.control", 30
+).addCallback(lambda o: o.callRemote("setUser", *sys.argv[1:3],
+).addCallbacks(lambda _: reactor.stop(), failed)
+
+reactor.run()
+</pre>
+<hr />
+<em>Leo (Be Careful What You Witch For, season 2) -- How about you just keep your arms down until you learn how to work the controls.</em>
+<h2>Perspective Broker (Trick)</h2><ul>
+<li>Add to the application something which will call reactor.stop()</li>
+
+<li>Portable (works on Windows)</li>
+
+<li>Gets around OS security limitations</li>
+
+<li>Need to add application-level security</li>
+
+<li>The docs have the answers (see 'cred')</li>
+
+</ul>
+<hr />
+<em>Piper (Lost and Bound, season 4) -- They're not good or bad by themselves, it's how we use them</em>
+<h2>Perspective Broker (Authentication)</h2><ul>
+<li>pb.cred</li>
+
+<li>Perspectives</li>
+
+<li>Can get remote user with every call<ul><li>Inherit from pb.Perpsective</li>
+
+<li>Call methods perspective_&lt;name&gt;(self, remoteUser, ...)</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Piper (She's a Man, Baby, a Man!, season 2) -- Okey-Dokey. I get the point.</em>
+
+<h2>Perspective Broker - About Large Data Streams</h2>
+
+<ul>
+
+<li>Sending large (>640kb) strings is impossible -- feature, not bug.</li>
+
+<li>It stops DoSes</li>
+
+<li>Nobody would ever need...<ul><li>JokeTooOldError</li></ul></li>
+
+<li>Use twisted.spread.utils.Pager -- sends the data in managable chunks.</li>
+
+</ul>
+
+<hr />
+<em>Piper (Womb Raider, season 4) --
+Oral tradition tales of a giant whose body served as a portal to other
+dimensions.</em>
+
+<h2>Producers and Consumers</h2><ul>
+<li>Use for things like sending a big file</li>
+
+<li>A good alternative to manually reactor.callLater(0,)-ing</li>
+
+<li>See twisted.internet.interfaces.{IProducer,IConsumer}</li>
+
+</ul>
+<hr />
+<em>Phoebe (Black as Cole, season 4) -- Apparently he feeds on the remains of other demons' victims.</em>
+<h2>Threads (callInThread)</h2><ul>
+<li>Use for long running calculations</li>
+
+<li>Use for blocking calls you can't do without</li>
+
+<li>deferred = reactor.callInThread(function, arg, arg)</li>
+
+</ul>
+<hr />
+<em>Piper (The Painted World, season 2) -- There will be consequences. There always are.</em>
+<h2>Threads (callFromThread)</h2><ul>
+<li>Use from a function running in a different thread</li>
+
+<li>Always thread safe</li>
+
+<li>Interface to non-thread-safe APIs</li>
+
+<li>reactor.callFromThread(protocol.transport.write, s)</li>
+
+</ul>
+<hr />
+<em>Phoebe (Witch Trial, season 2) -- Maybe it's still in the house. Just on different plane.</em>
+
+<h2>Using ApplicationService</h2><ul>
+<li>Keep useful data...</li>
+
+<li>...or useful volatile objects</li>
+
+<li>Support start/stop notification</li>
+
+<li>Example: process monitor</li>
+
+</ul>
+<hr />
+<em>Phoebe (Marry Go Round, season 4) -- Yeah, that's just in case you need psychic services.</em>
+
+<h2>Playing With Persistence</h2><ul>
+<li>Shutdown taps are useful</li>
+
+<li>Even if you use twistd -y</li>
+
+<li>So remember<ul><li>Classes belong in modules</li>
+
+<li>Functions belong in modules</li>
+
+<li>Modifying class attributes should be avoided</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Cole (Marry Go Round, season 4) -- That Lazerus demon is a time bomb waiting to explode</em>
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/europython/webclients.html b/vendor/Twisted-10.0.0/doc/historic/2003/europython/webclients.html
new file mode 100644
index 0000000000..8a26f71e59
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/europython/webclients.html
@@ -0,0 +1,482 @@
+<html><head><title>Writing Web Clients</title></head><body>
+
+<h1>Writing Web Clients</h1>
+
+<h2>Web Clients -- The Tutorial</h2><ul>
+<li>Welcome</li>
+
+<li>Gimmick -- Buffy quotes</li>
+
+</ul>
+<hr />
+<em>Anya (Family, season 5) -- Thank you for coming. We value your patronage.</em>
+<h2>What Are Web Clients?</h2><ul>
+<li>Clarification: non-interactive web clients</li>
+
+<li>Special purpose</li>
+
+<li>Often, quick and dirty hacks</li>
+
+<li>Make a web page into API</li>
+
+</ul>
+<hr />
+<em>Giles (Family, season 5) -- Could we please be a little less effusive, Anya?</em>
+<h2>What Are Web Clients Useful For?</h2><ul>
+<li>Mass download</li>
+
+<li>Periodic checking</li>
+
+<li>Automating tasks<ul><li>Make a web page more friendly</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Harmony (Family, season 5) -- Aww. You're my little lamb.</em>
+<h2>Review of Modules</h2><ul>
+<li>htmllib</li>
+
+<li>sgmllib</li>
+
+<li>httplib</li>
+
+<li>urllib</li>
+
+<li>urllib2</li>
+
+<li>urlparse</li>
+
+</ul>
+<hr />
+<em>Buffy (Family, season 5) -- Your definition of narrow is impressively wide.</em>
+<h2>Modules -- htmllib</h2><ul>
+<li>Most useful for easy filtering of images</li>
+
+<li>...or links</li>
+
+<li>Other things often easier with sgmllib</li>
+
+<li>Or with re</li>
+
+<li>Or with string manipulation</li>
+
+</ul>
+<hr />
+<em>Xander (Family, season 5) -- The answer is somewhere here.</em>
+<h2>Modules -- htmllib -- idiomatic usage</h2>
+<pre>
+# For lists
+import htmllib, formatter
+
+h = htmllib.HTMLParser(formatter.NullFormatter())
+h.feed(htmlString)
+print h.anchorlist
+</pre>
+
+<hr />
+<em>Xander (Family, season 5) -- I'm helping, I'm reading, I'm quiet.</em>
+<h2>Modules -- htmllib -- idiotmatic usage (cont'd)</h2>
+<pre>
+import htmllib, formatter
+
+class IMGFinder(htmllib.HTMLParser):
+
+ def __init__(self, *args, **kw):
+ htmllib.HTMLParser.__init__(self, *args, **kw)
+ self.ims = []
+
+ def handle_image(self, src, *args): self.ims.append(src)
+
+h = IMGFinder(formatter.NullFormatter())
+h.feed(htmlString)
+print h.ims
+</pre>
+
+<hr />
+<em>Donny (Family, season 5) -- Look what I found!</em>
+<h2>Modules -- htmllib -- base</h2><ul>
+<li>Some sites use 'base' for different relative linking</li>
+
+<li>For example, Zope does</li>
+
+<li>In above examples, 'h.base' has the base</li>
+
+</ul>
+<hr />
+<em>Dawn (Family, season 5) -- This is the source of my gladness.</em>
+<h2>Modules -- htmllib -- base (example)</h2><ul>
+<li>If the page on http://example.com/foo/bar.html has a link to '../baz.html'<ul><li>It means http://example.com/baz.html</li>
+</ul></li>
+
+<li>If the original page has base='/foo/quux'<ul><li>It means http://example.com/foo/baz.html</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Riley (Family, season 5) -- Every time I think I'm getting close to you...</em>
+<h2>Modules -- urllib/urllib2</h2><ul>
+<li>High-level interface</li>
+
+<li>Treat URLs as file-like objects</li>
+
+<li>...but still allows low-level operations</li>
+
+<li>Interface largely compatible</li>
+
+</ul>
+<hr />
+<em>Glory (Family, season 5) -- I am great and I am beautiful.</em>
+<h2>Modules -- urllib/urllib2 (cont'd)</h2><ul>
+<li>Can work through object-interface</li>
+
+<li>More flexible</li>
+
+<li>Interface no longer compatible</li>
+
+<li>urllib2 better usually</li>
+
+</ul>
+<hr />
+<em>Joyce (Ted, season 2) -- He redid my entire system.</em>
+<h2>Modules -- urllib/urllib2 (examples)</h2><ul>
+<li>urllib.urlopen("http://www.yahoo.com/").read() -&gt; contents</li>
+
+<li>urllib.urlopen("http://www.yahoo.com/").info() -&gt; headers</li>
+
+<li>Same works with urllib2</li>
+
+<li>Automatically uses environment variables for proxies</li>
+
+<li>urllib2 supports proxies with authentication</li>
+
+</ul>
+<hr />
+<em>Xander (Ted, season 2) -- Yum-my!</em>
+<h2>Digression -- HTTP Overview</h2><ul>
+<li>Request/Response</li>
+
+<li>Request is command followed by headers followed by body</li>
+
+<li>Response is error code followed by headers followed by body</li>
+
+<li>No welcome message</li>
+
+</ul>
+<hr />
+<em>Tara (Family, season 5) -- ...in terms of the karmic cycle.</em>
+<h2>Example HTTP Sessions</h2><ul>
+<li>Client</li>
+</ul>
+
+<pre>
+GET /foo/bar.html HTTP/1.0
+Host: www.example.org
+&lt;blank line&gt;
+</pre>
+
+<ul><li>Server</li></ul>
+
+<pre>
+HTTP/1.0 200 OK
+Content-Type: text/html
+
+&lt;html&gt;&lt;body&gt;lalalala&lt;/body&gt;&lt;/html&gt;
+</pre>
+
+<hr />
+<em>Giles (Family, season 5) -- And you are talking about what on earth?</em>
+<h2>Modules -- httplib</h2><ul>
+<li>Low-level interface to innards of HTTP</li>
+
+<li>Absolute control</li>
+
+<li>No abstractions</li>
+
+</ul>
+<hr />
+<em>Mr. MacLay (Family, season 5) -- We know how to control her...problem.</em>
+<h2>Modules -- httplib -- example</h2><ul>
+<li>Note: usually, the Host header is important<ul><li>Virtual hosting</li>
+</ul></li></ul>
+
+<pre>
+&gt;&gt;&gt; import httplib
+&gt;&gt;&gt; h=httplib.HTTP("moshez.org")
+&gt;&gt;&gt; h.putrequest('GET', '/')
+&gt;&gt;&gt; h.putheader('Host', 'moshez.org')
+&gt;&gt;&gt; h.endheaders()
+&gt;&gt;&gt; h.getreply()
+(200, 'OK', &lt;mimetools.Message instance at 0x81220dc&gt;)
+&gt;&gt;&gt; h.getfile().read(10)
+"&lt;HTML&gt;\n&lt;HE"
+</pre>
+<hr />
+<em>Anya (Family, season 5) -- ...and it was fun!</em>
+<h2>Modules -- urlparse</h2><ul>
+<li>urlparse.urljoin -- like os.path.join for URLs</li>
+
+<li>For path manipulation<ul><li>urlparse.urlsplit</li>
+
+<li>urlparse.urlunsplit</li>
+</ul></li>
+
+</ul>
+<hr />
+<em>Buffy (Family, season 5) -- You know what, you guys, just leave it here.</em>
+<h2>Downloading Dilbert</h2>
+<pre>
+import urllib2, re
+
+URL = 'http://www.dilbert.com/'
+f = urllib2.urlopen(URL)
+s = f.read()
+href = re.compile('&lt;a href="(/comics/.*?/dilbert.*?gif)"&gt;')
+m = href.search(value)
+f = urllib2.urlretrieve(urlparse.urljoin(URL, m.group(1)),
+ "dilbert.gif")
+</pre>
+<hr />
+<em>Tara (Family, season 5) -- That was funny if you [...] are a complete dork.</em>
+<h2>Downloading Dark Angel Transcripts</h2><ul>
+<li>Common situation of mass download</li></ul>
+
+<pre>
+import urllib2, htmllib, formatter, posixpath
+URL="http://www.darkangelfan.com/episode/"
+LINK_RE = re.compile('/trans_[0-9]+\.shtml$')
+s = urllib2.urlopen(URL).read()
+h = htmllib.HTMLParser(formatter.NullFormatter())
+h.feed(s)
+links = [urlparse.urljoin(URL, link)
+ for link in h.anchorlist if LINK_RE.search(link)]
+### -- really download --
+for link in links:
+ urllib2.urlretrieve(link, posixpath.basename(link))
+</pre>
+
+<hr />
+<em>Intern (Family, season 5) -- Yeah. That makes like five this month.</em>
+<h2>Downloading Dark Angel Transcripts (select)</h2>
+
+<pre>
+class Downloader:
+
+ def __init__(self, fin, fout):
+ self.fin, self.fout, self.fileno = fin, fout, fin.fileno
+
+ def read(self):
+ buf = self.fin.read(4096)
+ if not buf:
+ for f in [self.fout, self.fin]: f.close()
+ return 1
+ self.fout.write(buf)
+</pre>
+<hr />
+<em>Joyce (Ted, season 2) -- I've been looking for the right moment.</em>
+<h2>Downloading Dark Angel Transcripts (select, cont'd)</h2><ul>
+<li>Same code up to 'really download'</li></ul>
+
+<pre>
+downloaders = [Downloader(urllib2.urlopen(link),
+ open(posixpath.basename(link), 'wb'))
+ for link in links]
+while downloaders:
+ toRead = select.select(None, [downloaders], [], [])
+ for downloader in toRead:
+ if downloader.read():
+ downloaders.remove(downloader)
+</pre>
+<hr />
+<em>Buffy (Family, season 5) -- Tara's damn birthday is just one too many things for me to worry about.</em>
+<h2>Downloading Dark Angel Transcripts (threads)</h2><ul>
+<li>Bare bones example</li></ul>
+
+<pre>
+import threading
+
+for link in links:
+ Thread(target=urllib2.urlretrieve,
+ args=(link,posixpath.basename(link)))
+</pre>
+<hr />
+<em>Buffy (Ted, season 2) -- Sounds like fun.</em>
+<h2>Digression - twisted.web.client</h2><ul>
+<li>Part of the Twisted networking framework</li>
+
+<li>High level interface to HTTP client</li>
+
+<li>Completely asynchronous</li>
+
+<li>Reports results via callbacks</li>
+
+<li>client.getpage("http://www.yahoo.com").addCallbacks(gotResult, gotError)</li>
+
+</ul>
+<hr />
+<em>Buffy (Ted, season 2) -- You're supposed to use your powers for good!</em>
+<h2>Downloading Dark Angel Transcripts (web.client)</h2>
+<pre>
+from twisted.web import client
+from twisted.internet import import reactor, defer
+
+defer.DeferredList(
+[client.downloadPage(link, posixpath.basename(link))
+ for link in links]).addBoth(lambda _: reactor.stop())
+reactor.run()
+</pre>
+<hr />
+<em>Ted (Ted, season 2) -- You don't have to worry about anything.</em>
+<h2>HTTP Authentication</h2><ul>
+<li>Client attempts to connect</li>
+
+<li>Server sends back a 401 (please authenticate)</li>
+
+<li>Client sends same request back -- with auth tokens</li>
+
+<li>Only HTTP Basic authentication widely supported</li>
+
+<li>Client can send auth tokens on more requests automatically</li>
+
+</ul>
+<hr />
+<em>Buffy (Ted, season 2) -- Ummm... Who are these people?</em>
+<h2>HTTP Authentication - manually</h2><ul>
+<li>In HTTP, authentication is a header</li>
+
+<li>Base authentication is sending username and password</li>
+</ul>
+<pre>
+user = 'moshez'
+password = 's3kr1t'
+import httplib
+h=httplib.HTTP("localhost")
+h.putrequest('GET', '/protected/stuff.html')
+h.putheader('Authorization',
+ base64.encodestring(user+":"+password).strip())
+h.endheaders()
+h.getreply()
+print h.getfile().read()
+</pre>
+<hr />
+<em>Tara (Family, season 5) -- And, uh, these are my-my friends.</em>
+<h2>HTTP Authentication - urllib2</h2><ul>
+<li>Can read username/password from URL</li>
+
+<li>urllib2.urlopen("http://moshez:s3krit@example.com"
+ "/protected/stuff.html")</li>
+
+</ul>
+<hr />
+<em>Xander (Ted, season 2) -- I am really jinxing the hell out of us.</em>
+<h2>Further Reading</h2><ul>
+<li>htmllib docs <a href="http://www.python.org/doc/current/lib/module-htmllib.html">http://www.python.org/doc/current/lib/module-htmllib.html</a></li>
+
+<li>sgmllib docs<a href="http://www.python.org/doc/current/lib/module-sgmllib.html">http://www.python.org/doc/current/lib/module-sgmllib.html</a></li>
+
+<li>urllib docs<a href="http://www.python.org/doc/current/lib/module-urllib.html">http://www.python.org/doc/current/lib/module-urllib.html</a></li>
+
+<li>urllib2 docs<a href="http://www.python.org/doc/current/lib/module-urllib2.html">http://www.python.org/doc/current/lib/module-urllib2.html</a></li>
+
+<li>httplib docs<a href="http://www.python.org/doc/current/lib/module-httplib.html">http://www.python.org/doc/current/lib/module-httplib.html</a></li>
+
+<li>re docs<a href="http://www.python.org/doc/current/lib/module-re.html">http://www.python.org/doc/current/lib/module-re.html</a></li>
+
+<li>HTTP RFC<a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">http://www.w3.org/Protocols/rfc2616/rfc2616.html</a></li>
+
+<li>W3C HTML Page<a href="http://www.w3.org/MarkUp/">http://www.w3.org/MarkUp/</a></li>
+
+<li>Twisted<a href="http://twistedmatrix.com">http://twistedmatrix.com</a></li>
+
+</ul>
+<hr />
+<em>Willow (Ted, season 2) -- 'Book-cracker Buffy', it's kind of her nickname.</em>
+<h2>Questions?</h2>
+<em>Buffy (Family, season 5) -- I let you come, now sit down and look studious.</em>
+<h2>Bonus Slides</h2>
+<em>Tara (Family, season 5) -- You always make me feel special.</em>
+
+<h2>Cookies</h2><ul>
+<li>Carry state from one page to another</li>
+
+<li>Server sends header: Set-Cookie</li>
+
+<li>Client sends on later requests header: Cookie</li>
+
+</ul>
+<hr />
+<em>Ted (Ted, season 2) -- Who's up for dessert? I made chocolate-chip cookies!</em>
+<h2>urllib2 cookies</h2><ul>
+<li>Unfortunately, no automatic cookie jar support</li>
+
+<li>Can manually use .info() to read cookies...</li>
+
+<li>...and the Request() API to send them to the server</li>
+
+</ul>
+<hr />
+<em>Joyce (Ted, season 2) -- Mm! Buffy, you've got to try one of these!</em>
+<h2>Logging Into Advogato</h2>
+<pre>
+
+import urllib2
+
+u = urllib2.urlopen("http://advogato.org/acct/loginsub.html",
+ urllib2.urlencode({'u': 'moshez',
+ 'pass': 'not my real pass'})
+cookie = u.info()['set-cookie']
+cookie = cookie[:cookie.find(';')]
+r = Request('http://advogato.org/diary/post.html',
+ urllib2.urlencode(
+ {'entry': open('entry').read(), 'post': 'Post'}),
+ {'Cookie': cookie})
+urllib2.urlopen(r).read()
+</pre>
+
+<hr />
+<em>Anya (Family, season 5) -- I have a place in the world now.</em>
+<h2>On Being Nice - Robots</h2><ul>
+<li>Some sites don't want automatic crawlers</li>
+
+<li>It is up to you whether to play nice</li>
+
+<li>But you should know the rules before you break them</li>
+
+<li>Robots file -- at /robots.txt</li>
+
+</ul>
+<hr />
+<em>Willow (Ted, season 2) -- There were design features in that robot that pre-date...</em>
+<h2>Using robotparser</h2>
+<pre>
+import robotparser
+rp = robotparser.RobotFileParser()
+rp.set_url('http://www.example.com/robots.txt')
+rp.read()
+if not rp.can_fetch('', 'http://www.example.com/'):
+ sys.exit(1)
+</pre>
+
+<hr />
+<em>Buffy (Ted, season 2) -- Tell me you didn't keep any parts.</em>
+<h2>webchecker</h2><ul>
+<li>In the source distribution, in Tools/</li>
+
+<li>Understands robots.txt</li>
+
+<li>Can override which links gets chased</li>
+
+</ul>
+<hr />
+<em>Willow (Ted, season 2) -- What do you mean, check him out?</em>
+<h2>websucker</h2><ul>
+<li>In the source distribution, in Tools/</li>
+
+<li>Uses webchecker as a module</li>
+
+<li>Saves the pages it downloads</li>
+
+</ul>
+<hr />
+<em>Buffy (Ted, season 2) -- Find out his secrets, hack into his life.</em>
+
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/haifux/haifux.html b/vendor/Twisted-10.0.0/doc/historic/2003/haifux/haifux.html
new file mode 100644
index 0000000000..255178c782
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/haifux/haifux.html
@@ -0,0 +1,2235 @@
+<html><head><title>Evolution of Finger</title></head><body>
+<h1>Evolution of Finger</h1>
+
+<h2>Refuse Connections</h2>
+
+<pre>
+from twisted.internet import reactor
+reactor.run()
+</pre>
+
+<p>Here, we just run the reactor. Nothing at all will happen,
+until we interrupt the program. It will not consume (almost)
+no CPU resources. Not very useful, perhaps -- but this
+is the skeleton inside which the Twisted program
+will grow.</p>
+
+<h2>Do Nothing</h2>
+
+<pre>
+from twisted.internet import protocol, reactor
+class FingerProtocol(protocol.Protocol):
+ pass
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>Here, we start listening on port 1079 [which is supposed to be
+a reminder that eventually, we want to run on port 79, the port
+the finger server is supposed to run on. We define a protocol which
+does not respond to any events. Thus, connections to 1079 will
+be accepted, but the input ignored.</p>
+
+<h2>Drop Connections</h2>
+
+<pre>
+from twisted.internet import protocol, reactor
+class FingerProtocol(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>Here we add to the protocol the ability to respond to the
+event of beginning a connection -- by terminating it.
+Perhaps not an interesting behaviour, but it is already
+not that far from behaving according to the letter of the
+protocol. After all, there is no requirement to send any
+data to the remote connection in the standard, is there.
+The only technical problem is that we terminate the connection
+too soon. A client which is slow enough will see his send()
+of the username result in an error.</p>
+
+<h2>Read Username, Drop Connections</h2>
+
+<pre>
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>Here we make <code>FingerProtocol</code> inherit from
+<code>LineReceiver</code>, so that we get data-based events
+on a line-by-line basis. We respond to the event of receiving
+the line with shutting down the connection. Congratulations,
+this is the first standard-compliant version of the code.
+However, usually people actually expect some data about
+users to be transmitted.</p>
+
+
+<h2>Read Username, Output Error, Drop Connections</h2>
+
+<pre>
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write("No such user\r\n")
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>Finally, a useful version. Granted, the usefulness is somewhat
+limited by the fact that this version only prints out a no such
+user message. It could be used for devestating effect in honeypots,
+of course :)</p>
+
+<h2>Output From Empty Factory</h2>
+
+<pre>
+# Read username, output from empty factory, drop connections
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write(self.factory.getUser(user)+"\r\n")
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def getUser(self, user): return "No such user"
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>The same behaviour, but finally we see what usefuleness the
+factory has: as something that does not get constructed for
+every connection, it can be in charge of the user database.
+In particular, we won't have to change the protocol if
+the user database backend changes.</p>
+
+<h2>Output from Non-empty Factory</h2>
+
+<pre>
+# Read username, output from non-empty factory, drop connections
+from twisted.internet import protocol, reactor
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write(self.factory.getUser(user)+"\r\n")
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs): self.users = kwargs
+ def getUser(self, user):
+ return self.users.get(user, "No such user")
+reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
+reactor.run()
+</pre>
+
+<p>Finally, a really useful finger database. While it does not
+supply information about logged in users, it could be used to
+distribute things like office locations and internal office
+numbers. As hinted above, the factory is in charge of keeping
+the user database: note that the protocol instance has not
+changed. This is starting to look good: we really won't have
+to keep tweaking our protocol.</p>
+
+<h2>Use Deferreds</h2>
+
+<pre>
+# Read username, output from non-empty factory, drop connections
+# Use deferreds, to minimize synchronicity assumptions
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs): self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
+reactor.run()
+</pre>
+
+<p>But, here we tweak it just for the hell of it. Yes, while the
+previous version worked, it did assume the result of getUser is
+always immediately available. But what if instead of an in memory
+database, we would have to fetch result from a remote Oracle?
+Or from the web? Or, or...</p>
+
+<h2>Run 'finger' Locally</h2>
+
+<pre>
+# Read username, output from factory interfacing to OS, drop connections
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def getUser(self, user):
+ return utils.getProcessOutput("finger", [user])
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>...from running a local command? Yes, this version (safely!) runs
+finger locally with whatever arguments it is given, and returns the
+standard output. This will do exactly what the standard version
+of the finger server does -- without the need for any remote buffer
+overflows, as the networking is done safely.</p>
+
+<h2>Read Status from the Web</h2>
+
+<pre>
+# Read username, output from factory interfacing to web, drop connections
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.protocols import basic
+from twisted.web import client
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, prefix): self.prefix=prefix
+ def getUser(self, user):
+ return client.getPage(self.prefix+user)
+reactor.listenTCP(1079, FingerFactory(prefix='http://livejournal.com/~'))
+reactor.run()
+</pre>
+
+<p>The web. That invention which has infiltrated homes around the
+world finally gets through to our invention. Here we use the built-in
+Twisted web client, which also returns a deferred. Finally, we manage
+to have examples of three different database backends, which do
+not change the protocol class. In fact, we will not have to change
+the protocol again until the end of this talk: we have achieved,
+here, one truly usable class.</p>
+
+
+<h2>Use Application</h2>
+
+<pre>
+# Read username, output from non-empty factory, drop connections
+# Use deferreds, to minimize synchronicity assumptions
+# Write application. Save in 'finger.tpy'
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs): self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+application = app.Application('finger', uid=1, gid=1)
+application.listenTCP(79, FingerFactory(moshez='Happy and well'))
+</pre>
+
+<p>Up until now, we faked. We kept using port 1079, because really,
+who wants to run a finger server with root privileges? Well, the
+common solution is "privilege shedding": after binding to the network,
+become a different, less privileged user. We could have done it ourselves,
+but Twisted has a builtin way to do it. Create a snippet as above,
+defining an application object. That object will have uid and gid
+attributes. When running it (later we will see how) it will bind
+to ports, shed privileges and then run.</p>
+
+<h2>twistd</h2>
+
+<pre>
+root% twistd -ny finger.tpy # just like before
+root% twistd -y finger.tpy # daemonize, keep pid in twistd.pid
+root% twistd -y finger.tpy --pidfile=finger.pid
+root% twistd -y finger.tpy --rundir=/
+root% twistd -y finger.tpy --chroot=/var
+root% twistd -y finger.tpy -l /var/log/finger.log
+root% twistd -y finger.tpy --syslog # just log to syslog
+root% twistd -y finger.tpy --syslog --prefix=twistedfinger # use given prefix
+</pre>
+
+<p>This is how to run "Twisted Applications" -- files which define an
+'application'. twistd (TWISTed Daemonizer) does everything a daemon
+can be expected to -- shuts down stdin/stdout/stderr, disconnects
+from the terminal and can even change runtime directory, or even
+the root filesystems. In short, it does everything so the Twisted
+application developer can concentrate on writing his networking code.</p>
+
+<h2>Setting Message By Local Users</h2>
+
+<pre>
+# But let's try and fix setting away messages, shall we?
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs): self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class FingerSetterFactory(protocol.ServerFactory):
+ def __init__(self, ff): self.setUser = self.ff.users.__setitem__
+ff = FingerFactory(moshez='Happy and well')
+fsf = FingerSetterFactory(ff)
+application = app.Application('finger', uid=1, gid=1)
+application.listenTCP(79, ff)
+application.listenTCP(1079, fsf, interface='127.0.0.1')
+</pre>
+
+<p>Now that port 1079 is free, maybe we can run on it a different
+server, one which will let people set their messages. It does
+no access control, so anyone who can login to the machine can
+set any message. We assume this is the desired behaviour in
+our case. Testing it can be done by simply:
+</p>
+
+<pre>
+% nc localhost 1079
+moshez
+Giving a talk now, sorry!
+^D
+</pre>
+
+<h2>Use Services to Make Dependencies Sane</h2>
+
+<pre>
+# Fix asymmetry
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class FingerService(app.ApplicationService):
+ def __init__(self, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args)
+ self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+ def getFingerSetterFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.setUser = FingerSetterProtocol, self.users.__setitem__
+ return f
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService(application, 'finger', moshez='Happy and well')
+application.listenTCP(79, f.getFingerFactory())
+application.listenTCP(1079, f.getFingerSetterFactory(), interface='127.0.0.1')
+</pre>
+
+<p>The previous version had the setter poke at the innards of the
+finger factory. It's usually not a good idea: this version makes
+both factories symmetric by making them both look at a single
+object. Services are useful for when an object is needed which is
+not related to a specific network server. Here, we moved all responsibility
+for manufacturing factories into the service. Note that we stopped
+subclassing: the service simply puts useful methods and attributes
+inside the factories. We are getting better at protocol design:
+none of our protocol classes had to be changed, and neither will
+have to change until the end of the talk.</p>
+
+<h2>Read Status File</h2>
+
+<pre>
+# Read from file
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class FingerService(app.ApplicationService):
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, f.getFingerFactory())
+</pre>
+
+<p>This version shows how, instead of just letting users set their
+messages, we can read those from a centrally managed file. We cache
+results, and every 30 seconds we refresh it. Services are useful
+for such scheduled tasks.</p>
+
+<h2>Announce on Web, Too</h2>
+
+<pre>
+# Read from file, announce on the web!
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic
+from twisted.web import resource, server, static
+import cgi
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class FingerService(app.ApplicationService):
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('text/html',
+ '&lt;h1>%s&lt;/h1>&lt;p>%s&lt;/p>' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path, "No such user")]))))
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, f.getFingerFactory())
+application.listenTCP(80, server.Site(f.getResource()))
+</pre>
+
+<p>The same kind of service can also produce things useful for
+other protocols. For example, in twisted.web, the factory
+itself (the site) is almost never subclassed -- instead,
+it is given a resource, which represents the tree of resources
+available via URLs. That hierarchy is navigated by site,
+and overriding it dynamically is possible with getChild.</p>
+
+<h2>Announce on IRC, Too</h2>
+
+<pre>
+# Read from file, announce on the web, irc
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.web import resource, server, static
+import cgi
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class IRCReplyBot(irc.IRCClient):
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ self.factory.getUser(msg
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m: self.msg(user, m))
+class FingerService(app.ApplicationService):
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('text/html',
+ '&lt;h1>%s&lt;/h1>&lt;p>%s&lt;/p>' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path, "No such user")]))))
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser
+ return f
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, f.getFingerFactory())
+application.listenTCP(80, server.Site(f.getResource()))
+application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot'))
+</pre>
+
+<p>This is the first time there is client code. IRC clients often
+act a lot like servers: responding to events form the network.
+The reconnecting client factory will make sure that severed links
+will get re-established, with intelligent tweaked exponential
+backoff algorithms. The irc client itself is simple: the only
+real hack is getting the nickname from the factory in connectionMade.</p>
+
+
+
+<h2>Add XML-RPC Support</h2>
+
+<pre>
+# Read from file, announce on the web, irc, xml-rpc
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.factory.getUser(user
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m:
+ (self.transport.write(m+"\r\n"),self.transport.loseConnection()))
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self): self.lines = []
+ def lineReceived(self, line): self.lines.append(line)
+ def connectionLost(self): self.factory.setUser(*self.lines)
+class IRCReplyBot(irc.IRCClient):
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ self.factory.getUser(msg
+ ).addErrback(lambda _: "Internal error in server"
+ ).addCallback(lambda m: self.msg(user, m))
+class FingerService(app.ApplicationService):
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('text/html',
+ '&lt;h1>%s&lt;/h1>&lt;p>%s&lt;/p>' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path, "No such user")]))))
+ x = xmlrpc.XMLRPRC()
+ x.xmlrpc_getUser = self.getUser
+ r.putChild('RPC2.0', x)
+ return r
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser
+ return f
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, f.getFingerFactory())
+application.listenTCP(80, server.Site(f.getResource()))
+application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot'))
+</pre>
+
+<p>In Twisted, XML-RPC support is handled just as though it was
+another resource. That resource will still support GET calls normally
+through render(), but that is usually left unimplemented. Note
+that it is possible to return deferreds from XML-RPC methods.
+The client, of course, will not get the answer until the deferred
+is triggered.</p>
+
+
+<h2>Write Readable Code</h2>
+
+<pre>
+# Do everything properly
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+
+def catchError(err):
+ return "Internal error in server"
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (user, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self):
+ self.service = service
+
+ def render(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ["&lt;li>&lt;a href="%s">%s&lt;/a>&lt;/li> % (user, user)
+ for user in users]
+ return '&lt;ul>'+''.join(l)+'&lt;/ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(path, self.service)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self):
+ self.user = user
+ self.service = service
+
+ def render(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '&lt;h1>%s&lt;/h1>'%self.user+'&lt;p>%s&lt;/p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLPRC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(app.ApplicationService):
+
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = UserStatusTree(self)
+ x = UserStatusXR(self)
+ r.putChild('RPC2.0', x)
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, f.getFingerFactory())
+application.listenTCP(80, server.Site(f.getResource()))
+application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot'))
+</pre>
+
+<p>The last version of the application had a lot of hacks. We avoided
+subclassing, did not support things like user listings in the web
+support, and removed all blank lines -- all in the interest of code
+which is shorter. Here we take a step back, subclass what is more
+naturally a subclass, make things which should take multiple lines
+take them, etc. This shows a much better style of developing Twisted
+applications, though the hacks in the previous stages are sometimes
+used in throw-away prototypes.</p>
+
+<h2>Write Maintainable Code</h2>
+
+<pre>
+# Do everything properly, and componentize
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+
+class IFingerService(components.Interface):
+
+ def getUser(self, user):
+ '''Return a deferred returning a string'''
+
+ def getUsers(self):
+ '''Return a deferred returning a list of strings'''
+
+class IFingerSettingService(components.Interface):
+
+ def setUser(self, user, status):
+ '''Set the user's status to something'''
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(components.Interface):
+
+ def getUser(self, user):
+ """Return a deferred returning a string""""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string""""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerFactory,
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService, IFingerService)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(components.Interface):
+
+ def setUser(self, user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerSetterFactory,
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSettingService)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (user, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(components.Interface):
+
+ '''
+ @ivar nickname
+ '''
+
+ def getUser(self, user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ __implements__ = IIRCClientFactory,
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser()
+
+components.registerAdapter(IRCClientFactoryFromService, IFingerService)
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self):
+ self.putChild('RPC2.0', UserStatusXR(self.service))
+ self.service = service
+
+ def render(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ["&lt;li>&lt;a href="%s">%s&lt;/a>&lt;/li> % (user, user)
+ for user in users]
+ return '&lt;ul>'+''.join(l)+'&lt;/ul>'
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self):
+ self.user = user
+ self.service = service
+
+ def render(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '&lt;h1>%s&lt;/h1>'%self.user+'&lt;p>%s&lt;/p>'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLPRC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(app.ApplicationService):
+
+ __implements__ = IFingerService,
+
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+</pre>
+
+<p>In the last version, the service class was three times longer than
+any other class, and was hard to understand. This was because it turned
+out to have multiple responsibilities. It had to know how to access
+user information, by scheduling a reread of the file ever half minute,
+but also how to display itself in a myriad of protocols. Here, we
+used the component-based architecture that Twisted provides to achieve
+a separation of concerns. All the service is responsible for, now,
+is supporting getUser/getUsers. It declares its support via the
+__implements__ keyword. Then, adapters are used to make this service
+look like an appropriate class for various things: for supplying
+a finger factory to listenTCP, for supplying a resource to site's
+constructor, and to provide an IRC client factory for connectTCP.
+All the adapters use are the methods in FingerService they are
+declared to use: getUser/getUsers. We could, of course,
+skipped the interfaces and let the configuration code use
+things like FingerFactoryFromService(f) directly. However, using
+interfaces provides the same flexibility inheritance gives: future
+subclasses can override the adapters.</p>
+
+
+
+<h2>Advantages of Latest Version</h2>
+
+<ul>
+<li>Readable -- each class is short</li>
+<li>Maintainable -- each class knows only about interfaces</li>
+<li>Dependencies between code parts are minimized</li>
+<li>Example: writing a new IFingerService is easy</li>
+</ul>
+
+<pre>
+class MemoryFingerService(app.ApplicationService):
+ __implements__ = IFingerService, IFingerSetterService
+
+ def __init__(self, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args)
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def setUser(self, user, status):
+ self.users[user] = status
+
+application = app.Application('finger', uid=1, gid=1)
+# New constructor call
+f = MemoryFingerService(application, 'finger', moshez='Happy and well')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+# New: run setter too
+application.listenTCP(1079, IFingerSetterFactory(f), interface='127.0.0.1')
+</pre>
+
+<p>Here we show just how convenient it is to implement new backends
+when we move to a component based architecture. Note that here
+we also use an interface we previously wrote, FingerSetterFactory,
+by supporting one single method. We manage to preserve the service's
+ignorance of the network.</p>
+
+<h2>Another Backend</h2>
+
+<pre>
+class LocalFingerService(app.ApplicationService):
+ __implements__ = IFingerService
+
+ def getUser(self, user):
+ return utils.getProcessOutput("finger", [user])
+
+ def getUsers(self):
+ return defer.succeed([])
+
+application = app.Application('finger', uid=1, gid=1)
+f = LocalFingerService(application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+</pre>
+
+<p>We have already wrote this, but now we get more for less work:
+the network code is completely separate from the backend.</p>
+
+<h2>Yet Another Backend: Doing the Standard Thing</h2>
+
+<pre>
+import pwd
+
+class LocalFingerService(app.ApplicationService):
+ __implements__ = IFingerService
+
+ def getUser(self, user):
+ try:
+ entry = pwd.getpwnam(user)
+ except KeyError:
+ return "No such user"
+ try:
+ f=file(os.path.join(entry[5],'.plan'))
+ except (IOError, OSError):
+ return "No such user"
+ data = f.read()
+ f.close()
+ return data
+
+ def getUsers(self):
+ return defer.succeed([])
+
+application = app.Application('finger', uid=1, gid=1)
+f = LocalFingerService(application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+</pre>
+
+<p>Not much to say about that, except to indicate that by now we
+can be churning out backends like crazy. Feel like doing a backend
+for advogato, for example? Dig out the XML-RPC client support Twisted
+has, and get to work!</p>
+
+
+<h2>Aspect Oriented Programming</h2>
+
+<ul>
+<li>This is an example...</li>
+<li>...with something actually useful...</li>
+<li>...not logging and timing.</li>
+<li>Write less code, have less dependencies!</li>
+</ul>
+
+<h2>Use Woven</h2>
+
+<pre>
+# Do everything properly, and componentize
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.web.woven import page, widget
+import cgi
+
+class IFingerService(components.Interface):
+
+ def getUser(self, user):
+ '''Return a deferred returning a string'''
+
+ def getUsers(self):
+ '''Return a deferred returning a list of strings'''
+
+class IFingerSettingService(components.Interface):
+
+ def setUser(self, user, status):
+ '''Set the user's status to something'''
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(components.Interface):
+
+ def getUser(self, user):
+ """Return a deferred returning a string""""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string""""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerFactory,
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService, IFingerService)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(components.Interface):
+
+ def setUser(self, user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerSetterFactory,
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSettingService)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (user, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(components.Interface):
+
+ '''
+ @ivar nickname
+ '''
+
+ def getUser(self, user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ __implements__ = IIRCClientFactory,
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser()
+
+components.registerAdapter(IRCClientFactoryFromService, IFingerService)
+
+
+class UsersModel(model.MethodModel):
+
+ def __init__(self, service):
+ self.service = service
+
+ def wmfactory_users(self):
+ return self.service.getUsers()
+
+components.registerAdapter(UsersModel, IFingerService)
+
+class UserStatusTree(page.Page):
+
+ template = """&lt;html>&lt;head>&lt;title>Users&lt;/title>&lt;head>&lt;body>
+ &lt;h1>Users&lt;/h1>
+ &lt;ul model="users" view="List">
+ &lt;li pattern="listItem" />&lt;a view="Link" model="."
+ href="dummy">&lt;span model="." view="Text" />&lt;/a>
+ &lt;/ul>&lt;/body>&lt;/html>"""
+
+ def initialize(self, **kwargs):
+ self.putChild('RPC2.0', UserStatusXR(self.model.service))
+
+ def getDynamicChild(self, path, request):
+ return UserStatus(user=path, service=self.model.service)
+
+components.registerAdapter(UserStatusTree, IFingerService)
+
+
+class UserStatus(page.Page):
+
+ template='''&lt;html>&lt;head>&lt;title view="Text" model="user"/>&lt;/heaD>
+ &lt;body>&lt;h1 view="Text" model="user"/>
+ &lt;p mode="status" view="Text" />
+ &lt;/body>&lt;/html>'''
+
+ def initialize(self, **kwargs):
+ self.user = kwargs['user']
+ self.service = kwargs['service']
+
+ def wmfactory_user(self):
+ return self.user
+
+ def wmfactory_status(self):
+ return self.service.getUser(self.user)
+
+class UserStatusXR(xmlrpc.XMLPRC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(app.ApplicationService):
+
+ __implements__ = IFingerService,
+
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+</pre>
+
+<p>Here we convert to using Woven, instead of manually
+constructing HTML snippets. Woven is a sophisticated web templating
+system. Its main features are to disallow any code inside the HTML,
+and transparent integration with deferred results.</p>
+
+<h2>Use Perspective Broker</h2>
+
+<pre>
+# Do everything properly, and componentize
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.web.woven import page, widget
+from twisted.spread import pb
+import cgi
+
+class IFingerService(components.Interface):
+
+ def getUser(self, user):
+ '''Return a deferred returning a string'''
+
+ def getUsers(self):
+ '''Return a deferred returning a list of strings'''
+
+class IFingerSettingService(components.Interface):
+
+ def setUser(self, user, status):
+ '''Set the user's status to something'''
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(components.Interface):
+
+ def getUser(self, user):
+ """Return a deferred returning a string""""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string""""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerFactory,
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService, IFingerService)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(components.Interface):
+
+ def setUser(self, user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerSetterFactory,
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSettingService)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (user, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(components.Interface):
+
+ '''
+ @ivar nickname
+ '''
+
+ def getUser(self, user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ __implements__ = IIRCClientFactory,
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser()
+
+components.registerAdapter(IRCClientFactoryFromService, IFingerService)
+
+
+class UsersModel(model.MethodModel):
+
+ def __init__(self, service):
+ self.service = service
+
+ def wmfactory_users(self):
+ return self.service.getUsers()
+
+components.registerAdapter(UsersModel, IFingerService)
+
+class UserStatusTree(page.Page):
+
+ template = """&lt;html>&lt;head>&lt;title>Users&lt;/title>&lt;head>&lt;body>
+ &lt;h1>Users&lt;/h1>
+ &lt;ul model="users" view="List">
+ &lt;li pattern="listItem" />&lt;a view="Link" model="."
+ href="dummy">&lt;span model="." view="Text" />&lt;/a>
+ &lt;/ul>&lt;/body>&lt;/html>"""
+
+ def initialize(self, **kwargs):
+ self.putChild('RPC2.0', UserStatusXR(self.model.service))
+
+ def getDynamicChild(self, path, request):
+ return UserStatus(user=path, service=self.model.service)
+
+components.registerAdapter(UserStatusTree, IFingerService)
+
+
+class UserStatus(page.Page):
+
+ template='''&lt;html>&lt;head>&lt&lt;title view="Text" model="user"/>&lt;/heaD>
+ &lt;body>&lt;h1 view="Text" model="user"/>
+ &lt;p mode="status" view="Text" />
+ &lt;/body>&lt;/html>'''
+
+ def initialize(self, **kwargs):
+ self.user = kwargs['user']
+ self.service = kwargs['service']
+
+ def wmfactory_user(self):
+ return self.user
+
+ def wmfactory_status(self):
+ return self.service.getUser(self.user)
+
+class UserStatusXR(xmlrpc.XMLPRC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class IPerspectiveFinger(components.Interface):
+
+ def remote_getUser(self, username):
+ """return a user's status"""
+
+ def remote_getUsers(self):
+ """return a user's status"""
+
+
+class PerspectiveFingerFromService(pb.Root):
+
+ __implements__ = IPerspectiveFinger,
+
+ def __init__(self, service):
+ self.service = service
+
+ def remote_getUser(self, username):
+ return self.service.getUser(username)
+
+ def remote_getUsers(self):
+ return self.service.getUsers()
+
+components.registerAdapter(PerspectiveFingerFromService, IFingerService)
+
+
+class FingerService(app.ApplicationService):
+
+ __implements__ = IFingerService,
+
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+application.listenTCP(80, server.Site(resource.IResource(f)))
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f))
+</pre>
+
+<p>We add support for perspective broker, Twisted's native remote
+object protocol. Now, Twisted clients will not have to go through
+XML-RPCish contortions to get information about users.</p>
+
+<h2>Support HTTPS</h2>
+
+<pre>
+# Do everything properly, and componentize
+from twisted.internet import protocol, reactor, defer, app
+from twisted.protocols import basic, irc
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.web.woven import page, widget
+from twisted.spread import pb
+from OpenSSL import SSL
+import cgi
+
+class IFingerService(components.Interface):
+
+ def getUser(self, user):
+ '''Return a deferred returning a string'''
+
+ def getUsers(self):
+ '''Return a deferred returning a list of strings'''
+
+class IFingerSettingService(components.Interface):
+
+ def setUser(self, user, status):
+ '''Set the user's status to something'''
+
+def catchError(err):
+ return "Internal error in server"
+
+
+class FingerProtocol(basic.LineReceiver):
+
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+ d.addErrback(catchError)
+ def writeValue(value):
+ self.transport.write(value)
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(components.Interface):
+
+ def getUser(self, user):
+ """Return a deferred returning a string""""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string""""
+
+
+class FingerFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerFactory,
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+components.registerAdapter(FingerFactoryFromService, IFingerService)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(components.Interface):
+
+ def setUser(self, user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol returning a string"""
+
+
+class FingerSetterFactoryFromService(protocol.ServerFactory):
+
+ __implements__ = IFingerSetterFactory,
+
+ protocol = FingerSetterProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def setUser(self, user, status):
+ self.service.setUser(user, status)
+
+
+components.registerAdapter(FingerSetterFactoryFromService,
+ IFingerSettingService)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ if user.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (user, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(components.Interface):
+
+ '''
+ @ivar nickname
+ '''
+
+ def getUser(self, user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(self, addr):
+ """Return a protocol"""
+
+
+class IRCClientFactoryFromService(protocol.ClientFactory):
+
+ __implements__ = IIRCClientFactory,
+
+ protocol = IRCReplyBot
+ nickname = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser()
+
+components.registerAdapter(IRCClientFactoryFromService, IFingerService)
+
+
+class UsersModel(model.MethodModel):
+
+ def __init__(self, service):
+ self.service = service
+
+ def wmfactory_users(self):
+ return self.service.getUsers()
+
+components.registerAdapter(UsersModel, IFingerService)
+
+class UserStatusTree(page.Page):
+
+ template = """&lt;html>&lt;head>&lt;title>Users&lt;/title>&lt;head>&lt;body>
+ &lt;h1>Users&lt;/h1>
+ &lt;ul model="users" view="List">
+ &lt;li pattern="listItem" />&lt;a view="Link" model="."
+ href="dummy">&lt;span model="." view="Text" />&lt;/a>
+ &lt;/ul>&lt;/body>&lt;/html>"""
+
+ def initialize(self, **kwargs):
+ self.putChild('RPC2.0', UserStatusXR(self.model.service))
+
+ def getDynamicChild(self, path, request):
+ return UserStatus(user=path, service=self.model.service)
+
+components.registerAdapter(UserStatusTree, IFingerService)
+
+class UserStatus(page.Page):
+
+ template='''&lt;html>&lt;head>&lt;title view="Text" model="user"/>&lt;/heaD>
+ &lt;body>&lt;h1 view="Text" model="user"/>
+ &lt;p mode="status" view="Text" />
+ &lt;/body>&lt;/html>'''
+
+ def initialize(self, **kwargs):
+ self.user = kwargs['user']
+ self.service = kwargs['service']
+
+ def wmfactory_user(self):
+ return self.user
+
+ def wmfactory_status(self):
+ return self.service.getUser(self.user)
+
+class UserStatusXR(xmlrpc.XMLPRC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class IPerspectiveFinger(components.Interface):
+
+ def remote_getUser(self, username):
+ """return a user's status"""
+
+ def remote_getUsers(self):
+ """return a user's status"""
+
+
+class PerspectiveFingerFromService(pb.Root):
+
+ __implements__ = IPerspectiveFinger,
+
+ def __init__(self, service):
+ self.service = service
+
+ def remote_getUser(self, username):
+ return self.service.getUser(username)
+
+ def remote_getUsers(self):
+ return self.service.getUsers()
+
+components.registerAdapter(PerspectiveFingerFromService, IFingerService)
+
+
+class FingerService(app.ApplicationService):
+
+ __implements__ = IFingerService,
+
+ def __init__(self, file, *args, **kwargs):
+ app.ApplicationService.__init__(self, *args, **kwargs)
+ self.file = file
+
+ def startService(self):
+ app.ApplicationService.startService(self)
+ self._read()
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.file):
+ user, status = line.split(':', 1)
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def stopService(self):
+ app.ApplicationService.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(u, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+
+class ServerContextFactory:
+
+ def getContext(self):
+ """Create an SSL context.
+
+ This is a sample implementation that loads a certificate from a file
+ called 'server.pem'."""
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file('server.pem')
+ ctx.use_privatekey_file('server.pem')
+ return ctx
+
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+site = server.Site(resource.IResource(f))
+application.listenTCP(80, site)
+application.listenSSL(443, site, ServerContextFactory())
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f))
+</pre>
+
+<p>All we need to do to code an HTTPS site is just write a context
+factory (in this case, which loads the certificate from a certain file)
+and then use the listenSSL method. Note that one factory (in this
+case, a site) can listen on multiple ports with multiple protocols.</p>
+
+<h2>Finger Proxy</h2>
+
+<pre>
+class FingerClient(protocol.Protocol):
+
+ def connectionMade(self):
+ self.transport.write(self.factory.user+"\r\n")
+ self.buf = []
+
+ def dataReceived(self, data):
+ self.buf.append(data)
+
+ def connectionLost(self):
+ self.factory.gotData(''.join(self.buf))
+
+
+class FingerClientFactory(protocol.ClientFactory):
+
+ protocol = FingerClient
+
+ def __init__(self, user):
+ self.user = user
+ self.d = defer.Deferred()
+
+ def clientConnectionFailed(self, _, reason):
+ self.d.errback(reason)
+
+ def gotData(self, data):
+ self.d.callback(data)
+
+
+def finger(user, host, port=79):
+ f = FingerClientFactory(user)
+ reactor.connectTCP(host, port, f)
+ return f.d
+
+class ProxyFingerService(app.ApplicationService):
+ __implements__ = IFingerService
+
+ def getUser(self, user):
+ user, host = user.split('@', 1)
+ ret = finger(user, host)
+ ret.addErrback(lambda _: "Could not connect to remote host")
+ return ret
+
+ def getUsers(self):
+ return defer.succeed([])
+
+application = app.Application('finger', uid=1, gid=1)
+f = ProxyFingerService(application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+</pre>
+
+<p>Writing new clients with Twisted is much like writing new servers.
+We implement the protocol, which just gathers up all the data, and
+give it to the factory. The factory keeps a deferred which is triggered
+if the connection either fails or succeeds. When we use the client,
+we first make sure the deferred will never fail, by producing a message
+in that case. Implementing a wrapper around client which just returns
+the deferred is a common pattern. While being less flexible than
+using the factory directly, it is also more convenient.</p>
+
+<h2>Organization</h2>
+
+<ul>
+<li>Code belongs in modules: everything above the <code>application=</code>
+ line.</li>
+<li>Templates belong in separate files. The templateFile attribute can be
+ used to indicate the file.</li>
+<li>The templateDirectory attribute will be used to indicate where to look
+ for the files.</li>
+</ul>
+
+<pre>
+from twisted.internet import app
+from finger import FingerService, IIRCclient, ServerContextFactory, \
+ IFingerFactory, IPerspectiveFinger
+from twisted.web import resource, server
+from twisted.spread import pb
+
+application = app.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users', application, 'finger')
+application.listenTCP(79, IFingerFactory(f))
+r = resource.IResource(f)
+r.templateDirectory = '/usr/share/finger/templates/'
+site = server.Site(r)
+application.listenTCP(80, site)
+application.listenSSL(443, site, ServerContextFactory())
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+application.connectTCP('irc.freenode.org', 6667, i)
+application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f))
+</pre>
+
+<ul>
+<li>Seperaration between: code (module), configuration (file above),
+ presentation (templates), contents (/etc/users), deployment (twistd)</li>
+<li>Examples, early prototypes don't need that.</li>
+<li>But when writing correctly, easy to do!</li>
+</ul>
+
+<h2>Easy Configuration</h2>
+
+<p>We can also supply easy configuration for common cases</p>
+
+<pre>
+# in finger.py moudle
+def updateApplication(app, **kwargs):
+ f = FingerService(kwargs['users'], application, 'finger')
+ application.listenTCP(79, IFingerFactory(f))
+ r = resource.IResource(f)
+ r.templateDirectory = kwargs['templates']
+ site = server.Site(r)
+ app.listenTCP(80, site)
+ if kwargs.get('ssl'):
+ app.listenSSL(443, site, ServerContextFactory())
+ if kwargs.has_key('ircnick'):
+ i = IIRCClientFactory(f)
+ i.nickname = kwargs['ircnick']
+ ircServer = kwargs['ircserver']
+ application.connectTCP(ircserver, 6667, i)
+ if kwargs.has_key('pbport'):
+ application.listenTCP(int(kwargs['pbport']),
+ pb.BrokerFactory(IPerspectiveFinger(f))
+</pre>
+
+<p>And we can write simpler files now:</p>
+
+<pre>
+# simple-finger.tpy
+from twisted.internet import app
+import finger
+
+application = app.Application('finger', uid=1, gid=1)
+finger.updateApplication(application,
+ users='/etc/users',
+ templatesDirectory='/usr/share/finger/templates',
+ ssl=1,
+ ircnick='fingerbot',
+ ircserver='irc.freenode.net',
+ pbport=8889
+)
+</pre>
+
+<p>Note: the finger <em>user</em> still has ultimate power: he can use
+updateApplication, or he can use the lower-level interface if he has
+specific needs (maybe an ircserver on some other port? maybe we
+want the non-ssl webserver to listen only locally? etc. etc.)
+This is an important design principle: never force a layer of abstraction:
+allow usage of layers of abstractions.</p>
+
+<p>The pasta theory of design:</p>
+
+<ul>
+<li>Spaghetti: each piece of code interacts with every other piece of
+ code [can be implemented with GOTO, functions, objects]</li>
+<li>Lasagna: code has carefully designed layers. Each layer is, in
+ theory independent. However low-level layers usually cannot be
+ used easily, and high-level layers depend on low-level layers.</li>
+<li>Raviolli: each part of the code is useful by itself. There is a thin
+ layer of interfaces between various parts [the sauce]. Each part
+ can be usefully be used elsewhere.</li>
+<li>...but sometimes, the user just wants to order "Raviolli", so one
+ coarse-grain easily definable layer of abstraction on top of it all
+ can be useful.</li>
+</ul>
+
+<h2>Plugins</h2>
+
+<p>So far, the user had to be somewhat of a programmer to use this.
+Maybe we can eliminate even that? Move old code to
+"finger/service.py", put empty "__init__.py" and...</p>
+
+<pre>
+# finger/tap.py
+from twisted.python import usage
+from finger import service
+
+class Options(usage.Options):
+
+ optParams = [
+ ['users', 'u', '/etc/users'],
+ ['templatesDirectory', 't', '/usr/share/finger/templates'],
+ ['ircnick', 'n', 'fingerbot'],
+ ['ircserver', None, 'irc.freenode.net'],
+ ['pbport', 'p', 8889],
+ ]
+
+ optFlags = [['ssl', 's']]
+
+def updateApplication(app, config):
+ service.updateApplication(app, **config)
+</pre>
+
+<p>And register it all:</p>
+
+<pre>
+#finger/plugins.tml
+register('Finger', 'finger.tap', type='tap', tapname='finger')
+</pre>
+
+<p>And now, the following works</p>
+
+<pre>
+% mktap finger --users=/usr/local/etc/users --ircnick=moshez-finger
+% sudo twistd -f finger.tap
+</pre>
+
+<h2>OS Integration</h2>
+
+<p>If we already have the "finger" package installed, we can achieve
+easy integration:</p>
+
+<p>on Debian--</p>
+
+<pre>
+% tap2deb --unsigned -m "Foo <foo@example.com>" --type=python finger.tpy
+% sudo dpkg -i .build/*.deb
+</pre>
+
+<p>On Red Hat [or Mandrake]</p>
+
+<pre>
+% tap2rpm --type=python finger.tpy #[maybe other options needed]
+% sudo rpm -i .build/*.rpm
+</pre>
+
+<p>Will properly register configuration files, init.d sripts, etc. etc.</p>
+
+<p>If it doesn't work on your favourite OS: patches accepted!</p>
+
+<h2>Summary</h2>
+
+<ul>
+<li>Twisted is asynchronous</li>
+<li>Twisted has implementations of every useful protocol</li>
+<li>In Twisted, implementing new protocols is easy [we just did three]</li>
+<li>In Twisted, achieving tight integration of servers and clients
+ is easy.</li>
+<li>In Twisted, achieving high code usability is easy.</li>
+<li>Ease of use of Twisted follows, in a big part, from that of Python.</li>
+<li>Bonus: No buffer overflows. Ever. No matter what.</li>
+</ul>
+
+<h2>Motto</h2>
+
+<ul>
+<li>"Twisted is not about forcing. It's about mocking you when you use
+ the technology in suboptimal ways."</li>
+<li>You're not forced to use anything except the reactor...</li>
+<li>...not the protocol implementations...</li>
+<li>...not application...</li>
+<li>...not services...</li>
+<li>...not components...</li>
+<li>...not woven...</li>
+<li>...not perspective broker...</li>
+<li>...etc.</li>
+<li>But you should!</li>
+<li>Reinventing the wheel is not a good idea, especially if you form
+ some vaguely squarish lump of glue and poison and try and attach
+ it to your car.</li>
+<li>The Twisted team solved many of the problems you are likely to come
+ across...</li>
+<li>...several times...</li>
+<li>...getting it right the nth time.</li>
+</ul>
+
+
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/haifux/notes.html b/vendor/Twisted-10.0.0/doc/historic/2003/haifux/notes.html
new file mode 100644
index 0000000000..c35afa8a87
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/haifux/notes.html
@@ -0,0 +1,60 @@
+<html><head><title>Notes</title></head><body>
+<h1>Notes</h1>
+
+<p>[translated roughly from Hebrew]</p>
+
+<h2>Introduction</h2>
+
+<ul>
+<li>Name: Moshe Zadka</li>
+<li>Twisted developer [Debian, Python]</li>
+<li>Not:<ul>
+<li>XML talk (XML is: standarised, flexibl, internationalized)</li>
+<li>Gettysburg in Power Point</li>
+<li>Touching lots of things briefly</li>
+</ul></li>
+<ul>How to do more than one thing at once?<ul>
+<li>Fork (Apache)</li>
+<li>Thread (AOLServer)</li>
+<li>Cheat (GUI programs)</li>
+</ul></li>
+<li>Main loop calling our code.</li>
+<li>Let's develop a network program!</li>
+</ul>
+
+<h2>Discussion</h2>
+
+<ul>
+<li>What is blocking?
+<ul>
+<li>There is a UNIX concept of blocking...</li>
+<li>...which is not really relevant.</li>
+<li>Connecting to an accepting UNIX domain socket is blocking...</li>
+<li>...reading a file from NFS is not.</li>
+</ul></li>
+<li>Wait a minute: why is that interesting?<ul>
+<li>GUI -- humans (0.1s-1s)</li>
+<li>Network: connections might get refused</li>
+</ul></li>
+<li>Typical scenario: listen buffer 5, 1e6 connections/day --
+ don't dawdle for more than 0.08s</li>
+<li>These are the numbers that matter!</li>
+<li>Useful criterion: blocking==takes more than 0.01s on normal load.</li>
+<li>Depends on hardware, etc.</li>
+<li>Real world :(</li>
+<li>But a useful rule of thumb when coding.</li>
+<li>Trick: reactor.callLater(0,)</li>
+<li>Continuation-passing-style, tail-call-optimization</li>
+<li>But not pure -- not optimal</li>
+</ul>
+
+<h2>References</h2>
+
+<ul>
+<li>Plonk</li>
+<li>twistedmatrix.com</li>
+<li>mailing list</li>
+<li>irc -- #twisted</li>
+</ul>
+
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/applications b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/applications
new file mode 100755
index 0000000000..a6c18a2770
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/applications
@@ -0,0 +1,257 @@
+#!/usr/bin/python
+from slides import Slide, Bullet, SubBullet, URL, Image, PRE
+from twslides import Lecture
+
+class PythonSource:
+ def __init__(self, content):
+ self.content = content
+ def toHTML(self):
+ return '<pre class="python">%s</pre>' % (self.content,)
+
+class Raw:
+ def __init__(self, content):
+ self.content = content
+ def toHTML(self):
+ return self.content + '\n'
+
+lecture = Lecture(
+ "Applications of Twisted",
+ Slide("Twisted.names",
+ Bullet("Domain Name Server", SubBullet(
+ Bullet("Authoritative"),
+ Bullet("Caching"),
+ Bullet("Other!"),
+ )),
+ Bullet("Domain Name Client"),
+ ),
+ Slide("Mostly Functional",
+ Bullet("All common records support; 22 supported total", SubBullet(
+ Bullet("A, NS, CNAME, SOA, PTR, HINFO, MX, TXT"),
+ Bullet("IPv6 records AAAA and A6"),
+ )),
+ Bullet("No DNSSEC support"),
+ Bullet("Server and Client functionality"),
+ ),
+ Slide("Rapidly Developed",
+ Bullet("One month initial development period", SubBullet(
+ Bullet("Python is good for rapid development"),
+ Bullet("Twisted handles all the boring network details"),
+ )),
+ Bullet("Easily extended", SubBullet(
+ Bullet("Doesn't choke on unrecognized record types"),
+ Bullet("Support for a new record type can be added in "
+ "just a few minutes"),
+ Bullet(PythonSource("""\
+from twisted.protocols import dns
+
+class Record_A:
+ __implements__ = (dns.IEncodable,)
+ TYPE = dns.QUERY_TYPES['A'] = 1
+
+ def __init__(self, address = '0.0.0.0'):
+ self.address = socket.inet_aton(address)
+
+ def encode(self, strio, compDict = None):
+ strio.write(self.address)
+
+ def decode(self, strio, length = None):
+ self.address = readPrecisely(strio, 4)
+"""
+ )),
+ )),
+ ),
+ Slide("Easily Configured",
+ Bullet("BIND zonefile syntax"),
+ Bullet("Python source", SubBullet(
+ Bullet(PythonSource("""\
+zone = [
+ AAAA('intarweb.us', '3ffe:b80:1886:1::1'),
+ SRV('_http._tcp.www.intarweb.us', 0, 0, 8080, 'intarweb.us'),
+ MX('intarweb.us', 10, 'mail.intarweb.us')
+]
+"""
+ )),
+ )),
+ Bullet("Twisted's mktap and twistd tools", SubBullet(
+ PRE("mktap dns --pyzone a.domain.zonefile --recursive --cache"),
+ PRE("twistd -f dns.tap"),
+ )),
+ ),
+ Slide("Client API",
+ Bullet("Asynchronous", SubBullet(
+ Bullet("All lookup functions return Deferred objects"),
+ Bullet(PythonSource(
+"""\
+import random
+from twisted.names import client
+
+def addressFor(service, protocol, domain):
+ d = client.theResolver.lookupService(
+ '_%s._%s.%s' % (service, protocol, domain)
+ )
+
+ def grabPayload((answers, authority, additional)):
+ return [r.payload for r in answers]
+
+ def randomAnswer(results):
+ if len(results) == 1 and results[0] == '.':
+ raise RuntimeException, "No service records found"
+ return random.choice(results)
+
+ return d.addCallback(grabPayload).addCallback(randomAnswer)
+"""
+ )),
+ )),
+ ),
+ Slide("Uses",
+ Bullet("Service Records", SubBullet(
+ Bullet("Potential to simplify user experience"),
+ Bullet("Not widely accessible"),
+ Bullet("Names' client API makes accessing them trivial"),
+ )),
+ ),
+ Slide("Pynfo: A Network Information 'Bot",
+ Bullet("A 'bot with the goal of integrating access to miscellaneous data inputs"),
+ ),
+ Slide("Architecture",
+ Bullet("Factories", SubBullet(
+ Bullet("Takes care of connecting to different services"),
+ Bullet("Acts as a central storage for shared data"),
+ Bullet("Currently only IRC is supported"),
+ Bullet("Planned support for web, IM, and PB interfaces")
+ )),
+ Bullet("Protocols", SubBullet(
+ Bullet("Created by Factories"),
+ Bullet("Handles all service-specific interaction"),
+ Bullet("Refers back to the factory for shared data"),
+ Bullet("Current support for IRC only")
+ )),
+ Image("pynfo-chart.png"),
+ Bullet("Separation of Factory and Protocols", SubBullet(
+ Bullet("Per-protocol data separate, per-robot data shared"),
+ Bullet("Protocols destroyed on disconnect, factory manage reconnecting"),
+ )),
+ ),
+ Slide("Plugins",
+ Bullet("Plugins are modules plus some metadata", SubBullet(
+ Bullet("A plugin name"),
+ Bullet("A plugin description"),
+ Bullet("A plugin type"),
+ Bullet("Any other data appropriate for the type"),
+ )),
+ Bullet("Initialization / Finalization hooks"),
+ Bullet("Input filtering, for behaviors like ignore"),
+ Bullet("An example", SubBullet(
+ Bullet(PythonSource("""
+from twisted.names import client
+def info_LOOKUP(bot, user, channel, query):
+ def tellUserResponse((ans, auth, add)):
+ bot.reply(user, "%s: %s" % (query, [str(a.payload) for a in ans]))
+
+ def tellUserError(failure):
+ bot.reply(user, "Host lookup failed.")
+
+ client.lookupAddress(query).addCallbacks(
+ tellUserResponse, tellUserError
+ )
+"""
+ )),
+ )),
+ ),
+ Slide("But where do they come from?",
+ Bullet("Twisted's plugin module", SubBullet(
+ Bullet(PythonSource("""\
+from twisted.python import plugin
+class InfoBotFactory:
+ ...
+ def loadPlugins(self):
+ ...
+ p = plugin.getPlugIns('infobot')
+ ....
+"""
+ )),
+ )),
+ ),
+ Slide("Persistence",
+ Bullet("Addresses to connect to and protocols to use"),
+ Bullet("Administrators, passwords, keys"),
+ Bullet("Connection statistics"),
+ Bullet("Plugins can store objects for later retrieval")
+ ),
+ Slide("Components",
+ Bullet("Shared and pluggable behavior is implemented as Adapters for Interfaces", SubBullet(
+ Bullet("IScheduler, IStorage, IAuthenticator"),
+ )),
+ Bullet("Plugins can register their own adapters for the factory", SubBullet(
+ Bullet("Gracefully add new capabilities without __class__ hacks"),
+ Bullet("Share capabilities with other plugins"),
+ Bullet("Avoids namespace collisions"),
+ )),
+ ),
+ Slide("Interaction",
+ Bullet("Commands and responses are issued through normal protocol actions"),
+ Bullet('Three levels of command "security"'),
+ Bullet("Access to some commands is unrestricted",
+ Raw("<code>"),
+ SubBullet("<exarkun> pyn: networks"),
+ SubBullet("<pyn> Connected to: oftc -> ('irc.oftc.net', 6667), fn -> ('irc.freenode.net', 6667)"),
+ Raw("</code>"),
+ ),
+ Bullet("Access to others is granted via an ACL",
+ Raw("<code>"),
+ SubBullet("<exarkun> pyn: rebuild"),
+ SubBullet("<pyn> You aren't allowed to do that."),
+ Raw("</code>"),
+ ),
+ Bullet("Still further access is granted by the possession of a secret key",
+ Raw("<code>"),
+ SubBullet("<exarkun> pyn: spill self.transport.getHost()"),
+ SubBullet("<pyn> Command queued. Challenge: BBKSkYCRCGQETx4kTmceUg==%"),
+ SubBullet("<exarkun> pyn: respond 2lwcgSVnJPzrW6Yvq7sg+g==%"),
+ SubBullet("<pyn> ('INET', '192.168.123.137', 45539)"),
+ Raw("</code>"),
+ )
+ ),
+ Slide("Network Bridging",
+ Bullet("Pass messages between networks",
+ Raw("<code>"),
+ SubBullet("<pyn> Yosomono (~fake@hostmask) has joined on efnet"),
+ SubBullet("<pyn> <Yosomono@efnet> hello"),
+ Raw("</code>"),
+ ),
+ Bullet("Requesting user information across networks",
+ Raw("<code>"),
+ SubBullet("<exarkun> pyn: whois Yosomono@efnet"),
+ SubBullet("<pyn> Hostmask: Yosomono!fake@hostmask"),
+ SubBullet("<pyn> Channels: @#python"),
+ Raw("</code>"),
+ ),
+ ),
+ Slide("Conversation Logging",
+ Bullet("The 'conversation' command", SubBullet(
+ Raw("<code>"),
+ SubBullet("<exar[con]> pyn: conversation begin PyCon example conversation"),
+ SubBullet("<pyn> Beginning tagged conversation 'PyCon example conversation'."),
+ SubBullet("<exar[con]> Hello, PyCon"),
+ SubBullet("<exar[con]> Enjoy the example!"),
+ SubBullet("<exar[con]> pyn: conversation end PyCon example conversation"),
+ SubBullet("<pyn> Ended tagged conversation 'PyCon example conversation'."),
+ Raw("</code>"),
+ )),
+ Bullet("Web interface", SubBullet(
+ URL("http://c.intarweb.us:8008/%23tanstaafl/PyCon%20example%20conversation"),
+ )),
+ Bullet("Search previous logs and add conversation tags"),
+ ),
+ Slide("Various other plugins",
+ Bullet("PyPI monitor and querying"),
+ Bullet("Network specific operations - IRC operator module"),
+ Bullet("Freshmeat and Google querying"),
+ Bullet("Link shortener"),
+ Bullet("PyMetar plugin"),
+ Bullet("Manhole"),
+ ),
+ Slide("Questions?"),
+)
+
+lecture.renderHTML(".", "applications-%d.html", css="stylesheet.css")
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/applications.html b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/applications.html
new file mode 100644
index 0000000000..8e495139a3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/applications.html
@@ -0,0 +1,343 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html>
+ <head>
+ <title>Applications of the Twisted Framework</title>
+ <link href="stylesheet.css" type="text/css" rel="stylesheet" />
+ </head>
+ <body>
+ <h1>Applications of the Twisted Framework</h1>
+ <p>Jp Calderone</p>
+ <p>exarkun@twistedmatrix.com</p>
+
+ <h2>ABSTRACT</h2>
+
+ <p>Two projects developed using the Twisted framework are described;
+ one, Twisted.names, which is included as part of the Twisted
+ distribution, a domain name server and client API, and one, Pynfo, which
+ is packaged separately, a network information robot.</p>
+
+ <h2>Twisted (dot) Names</h2>
+
+ <h3>Motivation</h3>
+ <p>The field of domain name servers is well explored and numerous
+ strong, widely-deployed implementations of the protocol exist. DNSSEC,
+ IPv6, service location, geographical location, and many of the other DNS
+ extension proposals all have high quality support in BIND, djbdns,
+ maradns, and others. From a client's perspective, though, the landscape
+ looks a little different. APIs to perform arbitrary domain name lookups
+ are sparse. In contrast, Twisted.names presents a richly featured,
+ asynchronous client API.</p>
+
+ <h3>Names Server</h3>
+ <p><b>Names</b> is capable of operating as a fully functional domain
+ name server. It implements caching, recursive lookups, and can act as
+ the authority for an arbitrary number of domains. It is not, however, a
+ finely tuned performance machine. Responding to queries can take about
+ twice the time other domain name servers might need. It has not been
+ investigated whether this is a design limitation or merely the result of
+ an unoptimized implementation.</p>
+
+ <h3>Names Client</h3>
+ <p>As a client, <b>Names</b> provides an easy interface to every type of
+ record supported by. Looking up the MX records for a host, for example,
+ might look like this:</p>
+
+ <pre class="python">
+ def _cbMailExchange(results):
+ # Callback for MX query
+ answers = results[0]
+ print 'Mail Exchange is: ', answers
+
+ def _ebMailExchange(failure):
+ # Error callback for MX query
+ print 'Lookup failed:'
+ failure.printTraceback()
+
+ from twisted.names import client
+ d = client.lookupMailExchange('example-domain.com')
+ d.addCallbacks(_cbMailExchange, _ebMailExchange)
+ </pre>
+
+ <p>Looking up other record types is as simple as calling a different
+ <code>lookup*</code> function.</p>
+
+ <h3>Implementation</h3>
+
+ <p>As with most network software written using Twisted, the first step
+ in developing <b>Names</b> was to write the protocol support. In this
+ case, the protocol was DNS, and support was partially implemented.
+ However, it attempted to merge support for both UDP and TCP, and ended
+ up with less than optimal results. Much of this code was discarded,
+ though some of the lowest level encoding and decoding code worked well
+ and was re-used.</p>
+
+ <p>With the two protocol classes, DNSDatagramProtocol and DNSProtocol
+ (the TCP version) implemented, the next step was to write classes which
+ created the proper behavior for a domain name server. This logic was
+ put in the <code>twisted.names.server.DNSServerFactory</code> class,
+ which in turn relies on several different kind of <code>Resolver</code>s
+ to find the appropriate response to queries it receives from the
+ protocol instance.</p>
+
+ <p>The chain of execution, then, is this: a packet is received by the
+ protocol object (a <code>DNSDatagramProtocol</code> or
+ <code>DNSProtocol</code> instance); the packet is decoded by
+ <code>twisted.protocols.dns.RRHeader</code> in cooperation with one of
+ the record classes (<code>twisted.protocols.dns.Record_A</code> for
+ example); the decoded <code>twisted.protocols.dns.Query</code> object is
+ passed up to the <code>twisted.names.server.DNSServerFactory</code>,
+ which determines the query type and invokes the appropriate lookup
+ method on each of its resolver objects in turn; if an answer is found,
+ it is passed back down to the protocol instance (otherwise the
+ appropriate bit for an error condition is set), where it is encoded and
+ transmitted back to the client.</p>
+
+ <p>There are four kinds of resolvers in the current implementation. The
+ first three are authorities, caches, and recursive resolvers. They are
+ generally queried, in this order, using the fourth resolver, the "chain"
+ resolver, which simply queries the resolvers it knows about, moving on
+ to the next when any given resolver fails to produce a response, and
+ generating the proper exception when the last resolver has failed.</p>
+
+ <h3>Shortcomings</h3>
+
+ <p>There are several aspects of Twisted.Names that might preclude its
+ use in "production" software. These issues stem mainly from its
+ immaturity, it being less than six months old at the writing of this
+ paper.</p>
+
+ <ul>
+ <li><p>Possibly of foremost interest to those who might use it in a
+ high-load environment, it has somewhat poor runtime performance
+ characteristics. One potential reason for this is the extensive use of
+ exceptions to signal the relatively common case of a resolver lookup
+ failing. Solutions to this problem are apparent, but an implementation
+ change has not been attempted. Until this area of its development is
+ more fully examined, it will likely not be of use in anything other than
+ for low- to mid-load tasks, or with more hardware available to it than
+ might seem reasonable.</p></li>
+
+ <li>No attempt has been made to implement DNSSEC.</li>
+
+ <li>Certain areas of the server remain out of compliance with the
+ standardized RFCs, occasionally causing undesirable behavior when
+ interacting with clients. This most frequently manifests itself as a
+ lookup which fails the first time and succeeds on subsequent attempts.
+ It is not believed that these represent architectural flaws, only small
+ oversights in areas such as the "additional processing" sections of the
+ current authority resolver implementations.</li>
+ </ul>
+ <h2>Pynfo</h2>
+
+ <h3>Motivation</h3>
+ <p>Pynfo was originally begun as a learning project to become acquainted
+ with the Twisted framework. After a brief initial development period
+ and an extended period of non-development, Pynfo was picked up again to
+ serve as a replacement for several existing robots, each with fragile
+ code bases and with designs not intended for future integration with
+ other services. After it subsumed the functions of network relaying and
+ Google searches, other desired features, which enhanced the IRC medium
+ and had not previously been considered due to the difficulty of
+ extending existing robots, were added to Pynfo, prompting the development
+ of an elementary plug-in system to further facilitate the integration
+ process.</p>
+
+ <h3>Architecture</h3>
+ <p>Pynfo performs such simple tasks as noting the last time an
+ individual spoke and querying the Google search engine, as well as
+ several more complex operations like relaying traffic between different
+ IRC networks and publishing channel logs through an HTTP interface.</p>
+
+ <p>Toward these ends, it is useful to abstract the functionality into
+ several different layers:</p>
+
+ <ul>
+ <li><p>The factory: All shared data, such as the channels a given user is
+ known to be in, the plugins currently loaded, and the addresses of servers
+ to connect to, is aggregated here. When it is necessary to make a
+ connection, the factory creates an instance of the appropriate Protocol
+ subclass, in a manner similar to this:
+
+ <pre class="python">
+ def buildProtocol(self, address):
+ for net in self.data['networks'].values():
+ if net.address == address:
+ break
+
+ proto = IRCProtocol(net)
+ self.allBots[net.alias] = proto
+ proto.factory = self
+ return proto
+ </pre>
+
+ The factory instance is created only once, and that instance persists
+ through the entire time a particular Pynfo bot operates.</p>
+ </li>
+
+ <li><p>The protocol: Each kind of service Pynfo can connect to has a
+ Protocol class associated with it, a class which handles the specifics
+ of communicating over this protocol. Unlike the factory, protocols
+ instances can be short lived and are created and destroyed as many times
+ as network connectivity demands. When a Pynfo robot shuts down and is
+ serialized to disk, all Protocol instances are destroyed and discarded,
+ to be created anew when the robot is restarted.</p>
+ </li>
+
+ <li><p>Plugins: These give Pynfo most of its functionality. From the
+ very simple logging module, which does no more than write strings to
+ disk, to the esoteric lookup module, which translates hostnames into
+ dotted-quads, to the informative dictionary module, which queries an <a
+ href="http://dict.org">online dictionary</a>, plugins come in all shapes
+ and sizes, and can be written to fill almost any niche.</p>
+ </li>
+ </ul>
+
+ <h3>Employing Components</h3>
+ <p>Twisted provides a <i>component</i> system which Pynfo relies on to
+ split up useful functionality used in different areas of the code. The
+ Interface class is the primary element in the component system, and is
+ used as a location for a semi-format definition of an API, as well as
+ documentation. Classes declare that they implement an Interface by
+ including it in their __implements__ tuple attribute. Interfaces can
+ also be added to classes by third parties using the registerAdapter()
+ function. This takes an Adapter type in addition to the interface being
+ registered and the type it is being registered for. Adapters are a
+ objects which can store additional state information and implement
+ functionality without being part of the classes that are "actually"
+ being operated upon. They, as their name suggests, adapt components to
+ conform to interfaces.</p>
+
+ <p>Components can implement interfaces themselves, or maintain a cache
+ of adapter objects for each interfaces that is requested of them. These
+ persist like any other attribute, and so state stored in adapters
+ remains associated with the component as long as that component exists, or
+ until the adapter is explicitly removed.</p>
+
+ <p>Pynfo's Factory class uses two adapters to implement two basic
+ Interfaces that many plugins find useful. The first is the IStorage
+ interface.
+
+ <pre class="python">
+ class IStorage(components.Interface):
+
+ def store(self, key, version, value):
+ """
+ Store any pickleable object
+ """
+
+ def retrieve(self, key, version):
+ """
+ Retrieve the previously stored object associated with key and
+ version
+ """
+ </pre>
+ An example usage of this interface is the PyPI plugin, which polls the
+ Python Package Index and reports updates to a configurable list of
+ outputs:
+
+ <pre class="python">
+ def init(factory):
+ global notifyChannels
+ store = factory.getComponent(interfaces.IStorage)
+ try:
+ notifyChannels = store.retrieve('pypi', __version__)
+ except error.RetrievalError:
+ notifyChannels = []
+
+ </pre>
+ <p>The module requests the component of factory which implements
+ IStorage, then attempts to load any previously stored version of
+ "notifyChannels". If none is found, it defaults to none. In the
+ finalizer below, this global is stored, using the same interfaced, to be
+ retrieved when the module is next initialized.</p>
+
+ <pre class="python">
+ def fini(factory):
+ s = factory.getComponent(interfaces.IStorage)
+ s.store('pypi', __version__, notifyChannels)
+ </pre>
+
+ The second interface allows low granularity scheduling of events:
+
+ <pre class="python">
+ class IScheduler(components.Interface):
+ MINUTELY = 60
+ HOURLY = 60 * 60
+ DAILY = 60 * 60 * 24
+ WEEKLY = 60 * 60 * 24 * 7
+
+
+ def schedule(self, period, fn, *args, **kw):
+ """
+ Cause a function to be invoked at regular intervals with the given
+ arguments.
+ """
+ </pre>
+ The Adapter which implements this interface is just as simple:
+ <pre class="python">
+ class SchedulerAdapter(components.Adapter):
+ __implements__ = (interfaces.IScheduler,)
+
+ def schedule(self, period, fn, *args, **kw):
+ from twisted.internet import reactor
+ def cycle():
+ fn(*args, **kw)
+ reactor.callLater(period, cycle)
+ reactor.callLater(period, cycle)
+ </pre>
+ </p>
+
+ <p>Implementing these interfaces as adapters using the component system
+ has two primary advantages over a simple inheritance or mixins approach.
+ First, it allows plugins to add completely new behavior to the system
+ without complex and fragile manipulation of the factory's __class__
+ attribute. This is a big win when it comes to plugins that want to
+ share new functionality with other plugins. For example, the "ignore"
+ plugin adds an IDiscriminating interface and an adapter which implements
+ it. Once this plugin is loaded, any other plugin can request the
+ component for IDiscriminating and add users to or remove users from the
+ ignore list.</p>
+
+ <h3>The Plugin Framework</h3>
+
+ <p>Before a module can be loaded and initialized as a plugin, it must be
+ located. This could be done with a simple use of
+ <code>os.listdir()</code>, or <code>__all__</code> could be set to include
+ each new plugin added. Twisted provides another way, though.</p>
+
+ <p>The <code>twisted.python.plugin</code> provides the most high-level
+ interface to the plugin system, a function called
+ <code>getPlugIns</code>. It usually takes one argument, a plugin type,
+ which is an arbitrary string used to categorize the different kinds of
+ plugins available on a system. Twisted's own "mktap" tool uses the
+ "tap" plugin type. For Pynfo, I have elected to use the "infobot"
+ string. <code>getPlugIns("infobot")</code> searches the system (by way
+ of PYTHONPATH) for files named "plugins.tml". These files contain
+ python source, and are run as such; a function, "register" is placed in
+ their namespace, and the most common action for them is to invoke this
+ function one or more times, providing information about a plugin. Here
+ is a snippet from one which Pynfo uses:</p>
+
+ <pre class="python">
+ register(
+ "Weather",
+ "Pynfo.plugins.weather",
+ description="Commands to check the weather at "
+ "various places around the world.",
+ type="infobot"
+ )
+ </pre>
+
+ <p>Any number of plugin.tml files may exist in the filesystem, allowing
+ per-user and even per-robot plugins to be installed, all without
+ modifying the Pynfo installation itself.
+
+ The second argument indicates the module which may be imported to get
+ this plugin. Pynfo traverses the resulting list, importing these modules,
+ and initializing them if necessary.</p>
+
+ </body>
+</html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/pynfo-chart.png b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/pynfo-chart.png
new file mode 100644
index 0000000000..7318b15458
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/applications/pynfo-chart.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conch b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conch
new file mode 100755
index 0000000000..9e46b4e913
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conch
@@ -0,0 +1,98 @@
+#!/usr/bin/python
+from slides import Slide, Bullet, SubBullet, URL
+from twslides import Lecture
+
+lecture = Lecture(
+ "Twisted.Conch: SSH in Python",
+ Slide("Introduction",
+ ),
+ Slide("Other implementations (servers)",
+ Bullet("OpenSSH",
+ SubBullet(URL("http://www.openssh.org")),
+ ),
+ Bullet("FSecure SSH",
+ SubBullet(URL("http://www.f-secure.com/products/ssh/")),
+ ),
+ Bullet("LSH",
+ SubBullet(URL("http://www.lysator.liu.se/~nisse/lsh/")),
+ ),
+ ),
+ Slide("Other implementations (clients)",
+ Bullet("PuTTY",
+ SubBullet(URL("http://www.chiark.greenend.org.uk/~sgtatham/putty/")),
+ ),
+ Bullet("TeraTerm",
+ SubBullet(URL("http://www.ayera.com/teraterm/")),
+ ),
+ Bullet("MindTerm",
+ SubBullet(URL("http://www.appgate.com/mindterm/")),
+ ),
+ ),
+ Slide("Why Twisted?",
+ Bullet("Asynchronous"),
+ Bullet("Python"),
+ Bullet("High-Level"),
+ ),
+ Slide("No Forking or Threads",
+ Bullet("Forking is expensive"),
+ Bullet("Threads are complicated/expensive, esp. in Python"),
+ Bullet("Asynch means no worrying about any of that"),
+ Bullet("Makes running a session 2x as fast in Conch as in OpenSSH"),
+ ),
+ Slide("Security - No Pointers",
+ SubBullet("No buffer overflows"),
+ SubBullet("No off-by-1 errors"),
+ SubBullet("No malloc/free bugs"),
+ SubBullet("No arbitrary code execution"),
+ ),
+ Slide("Security - High Level",
+ Bullet("Strong built-in library"),
+ Bullet("Exceptions"),
+ ),
+ Slide("Security - Not Root",
+ Bullet("Limits vulnerablity in a compromise"),
+ Bullet("Allows use of process limits/etc."),
+ ),
+ Slide("Interfacing with other software",
+ Bullet("OpenSSH interacts only through separate processes",
+ SubBullet("Expensive"),
+ SubBullet("Complicated"),
+ ),
+ Bullet("Conch can interact in-process",
+ SubBullet("Faster"),
+ SubBullet("Easy integration to other Twisted and Python libraries"),
+ ),
+ ),
+ Slide("Speed",
+ Bullet("C is faster than Python"),
+ Bullet("Interpreter cost is high for the client"),
+ Bullet("FSH-style connection caching helps a bit"),
+ Bullet("Psyco helps as well"),
+ ),
+ Slide("Age",
+ Bullet("Conch is new",
+ SubBullet("First commit was July 15, 2002"),
+ ),
+ Bullet("Hasn't had a security aduit"),
+ Bullet("Shouldn't be used in security-critical systems"),
+ ),
+ Slide("Applications with Conch",
+ Bullet("Reality: MUD framework"),
+ Bullet("Insults: async. replacement for curses in Conch apps"),
+ ),
+ Slide("Future Directions",
+ Bullet("Generic authentication forwarding"),
+ Bullet("Work on applications"),
+ Bullet("Auditing of the code"),
+ Bullet("Increase speed"),
+ Bullet("SFTP/SCP"),
+ Bullet("Key Agent"),
+ Bullet("DNSSEC"),
+ ),
+ Slide("Conclusion",
+ Bullet("Working implementation in Python"),
+ Bullet("Much room for improvement"),
+ ),
+)
+
+lecture.renderHTML(".", "conch-%d.html", css="main.css")
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conch.html b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conch.html
new file mode 100644
index 0000000000..dea160aee8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conch.html
@@ -0,0 +1,165 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<title>Twisted.Conch: SSH in Python with Twisted</title>
+</head>
+
+<body>
+<h1>Twisted.Conch: SSH in Python with Twisted</h1>
+
+<ul>
+<li>Paul Swartz
+ <a href="mailto:z3p@twistedmatrix.com">z3p@twistedmatrix.com</a></li>
+</ul>
+
+<h2>Introduction</h2>
+
+<p>Although it is a newcomer on the Secure Shell stage, Twisted.Conch has quickly
+caught up with the two most popular free *nix implementations and the most
+popular free Windows implementation in terms of functionality. This rapid
+development time, as well as the stability and other advantages, owes much to
+Python and the Twisted networking framework.</p>
+
+<h2>Other implementations (servers)</h2>
+
+<p>Other than Conch, there are three popular server implementations. OpenSSH
+works with versions 1 and 2 of the protocol, and is the most popular on *nix
+systems. FSecure is more popular on Windows servers, and also works with both
+versions. LSH is newer, and implements version 2. All three are written in C,
+with LSH having some supporting Scheme code to generate C files.</p>
+
+<h2>Other implementations (clients)</h2>
+
+<p>On *nix, the SSH clients are provided by the server implementations (OpenSSH
+and LSH). On Windows, there are a couple of separate clients. PuTTY is the
+most popular and supports Telnet along with SSH1 and 2. TeraTerm recently
+incorporated SSH into the core: before it had been an extension module.
+MindTerm is the only implementation in this list to be written in a language
+other than C. It runs as a Java applet, allowing SSH to run on any computer
+with a JVM.</p>
+
+<h2>Why Twisted?</h2>
+
+<p>Why is Twisted ideal for this type of project? Firstly, it is an asynchronous
+library, meaning there are no worries about threading or concurrency issues.
+This means more developer time can be devoted to making the code work well,
+rather than just work. Second, Python lends itself to this kind of
+development: the code is easy to read and easy to write. Third, the Twisted
+library is high-level, so developers do not need to worry about select loops or
+callbacks. Twisted handles all of that and allows developers to concentrate on
+the code.</p>
+
+<h2>No forking/threads</h2>
+
+<p>Unlike OpenSSH, the Conch server does not fork a process for each incoming
+connection. Instead, it uses the Twisted reactor to multiplex the connections.
+The only fork done is to execute a process such as a shell, but running a shell
+is not necessary, in which case the entire protocol would be run in-process.
+One of the initial features of the server was an in-process Python interpreter
+which allowed a user to interact with the server as it was running. (It is
+currently disabled for security reasons.) Threads are only used to interface
+with synchronous libraries, such as PyPAM (Pluggable Authentication Modules
+support) or PyME (GPGME support). By not using forks or threads, the time it
+takes for the Conch server to start an SSH session is roughly half of the time
+it takes for OpenSSH. However, this does require that code in Conch be
+non-blocking, which is an obstacle for programmers not used to that style.</p>
+
+<h2>Security - No Pointers</h2>
+
+<p>OpenSSH, LSH, and PuTTY are all written in C. Many security holes are a result
+of problems with unsafe pointer usage, which is a large problem in C code.
+Many other security holes result from related issues, such as buffer overflows,
+off-by-one errors on arrays, and memory allocation/deallocation bugs. Python
+is pointer-safe, and so is not vulnerable to this class of hole. This also
+means that no arbitrary data from over-the-wire is ever run, meaning control
+always stays with the Conch server.</p>
+
+<h2>Security - High Level</h2>
+
+<p>Being written in Python provides more security than just pointer safety. The
+strong builtin library that comes with Python (including powerful data types
+like the list and dictionary) means that fewer wheels need to be reinvented.
+This limits the potential to make mistakes in implementation. Exceptions are
+another powerful tool. They centralize error handling, rather than the mix of
+methods that the C libraries use. All errors are caught and dealt with: this
+might mean that the server stops accepting connections, but it never
+compromises security.</p>
+
+<h2>Security - Not Root</h2>
+
+<p>Also, Conch does not need to run as root. In the default server, root
+privileges are used for two things: to bind to ports &lt; 1024, and to fork a
+process as a different user. If neither of these are needed, the server need
+not run as root at all. Even if they are, the server is only running as root
+for those small sections. The rest of the time, it runs under the effective
+user and group ID of the user who started the server. This limits the amount
+of damage that could be inflicted in the event of a compromise.</p>
+
+<h2>Interfacing with other software</h2>
+
+<p>OpenSSH can interact with subsystems such as SFTP only by executing a process
+to handle it. Not only is forking a process expensive, it limits the
+interaction to a generic bitstream, which leaves developers to determine how
+to interact with their users. Conch can run in the same process as other
+Python software, and is easily integrated with other Twisted servers. This
+allows for things like secure remote administration of a Twisted web server,
+encrypted communication to a Reality MUD, or secure remote object access using
+Perspective Broker. This saves the hassle and expense of forking, and allows
+Python developers to interact with Conch the way they know best: with Python.</p>
+
+<h2>Speed</h2>
+
+<p>No one can deny that compiled C is faster than Python. Some part of Conch use
+C (PyCrypto, TGMP) to speed frequent operations, but the majority of the code
+is in Python. The client suffers the most from this because of the time it
+takes to start the interpreter. Work is being done to speed up the client by
+caching connections. This does not eliminate the interpreter start-up cost,
+but it removes the cost of negotiating a new connection. This effort is
+similar to FSH (also in Python) but interacts more nicely with the SSH
+protocol. Psyco helps as well, offering a speedup of roughly 2x - 5x.</p>
+
+<h2>Age</h2>
+
+<p>As I said in the introduction, Conch is still a newcomer on the Secure Shell
+stage (The first commit for Conch was July 15, 2002.) Although Python solves
+a large class of holes, it is probable that other security holes are in the
+code. Until a full audit is conducted of Twisted and of Conch, it should not
+be used for security-critical systems.</p>
+
+<h2>Applications with Conch</h2>
+
+<p>One of the applications for Conch is with Reality, a MUD framework using
+Twisted. Conch makes it easy to allow secure connections to the MUD in
+addition or even in place of a standard Telnet connection. As problems
+such as character theft become more prevalent on the Internet, a secure
+interface becomes more important.</p>
+
+<p>More generally, work is being done on Insults, a replacement for libraries
+like Curses and S-Lang. It allows developers to write GUI code that
+interacts well with Conch and other Twisted software. Although it is in the
+initial stages of development, it shows much promise for the future.</p>
+
+<h2>Future Directions</h2>
+
+<p>There are several different directions for Conch to move in. One of the most
+interesting is system for generalized authentication forwarding. This would
+allow all authentication to be performed on a host that the user controls,
+which would help to stop vulnerabilities such as timing attacks. Second is
+more work with applications. Insults is becoming more powerful, and it will
+be interesting to see what it can be used for. Also important are auditing of
+the code and increasing the speed. These will make the code more useful in
+general, as well as improving security. Other ideas include direct support for
+SFTP/SCP, support for a key agent, and interfacing with Twisted.Names to
+support DNSSEC.</p>
+
+<h2>Conclusion</h2>
+
+<p>Although it is new, Conch is a working implementation of the Secure Shell
+protocol. It is robust enough to serve as both the client and server on
+systems I and others use daily.</p>
+
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conchtalk.txt b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conchtalk.txt
new file mode 100755
index 0000000000..ac24a49693
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/conchtalk.txt
@@ -0,0 +1,144 @@
+Introduction
+------------
+Although it is a newcomer on the Secure Shell stage, Twisted.Conch has quickly
+caught up with the two most popular free *nix implementations and the most
+popular free Windows implementation in terms of functionality. This rapid
+development time, as well as the stability and other advantages, owes much to
+Python and the Twisted networking framework.
+
+Other implementations (servers)
+------------------------------
+Other than Conch, there are three popular server implementations. OpenSSH
+works with versions 1 and 2 of the protocol, and is the most popular on *nix
+systems. FSecure is more popular on Windows servers, and also works with both
+versions. LSH is newer, and implements version 2. All three are written in C,
+with LSH having some supporting Scheme code to generate C files.
+
+Other implementations (clients)
+-------------------------------
+On *nix, the SSH clients are provided by the server implementations (OpenSSH
+and LSH). On Windows, there are a couple of separate clients. PuTTY is the
+most popular and supports Telnet along with SSH1 and 2. TeraTerm recently
+incorporated SSH into the core: before it had been an extension module.
+MindTerm is the only implementation in this list to be written in a language
+other than C. It runs as a Java applet, allowing SSH to run on any computer
+with a JVM.
+
+Why Twisted?
+------------
+Why is Twisted ideal for this type of project? Firstly, it is an asynchronous
+library, meaning there are no worries about threading or concurrency issues.
+This means more developer time can be devoted to making the code work well,
+rather than just work. Second, Python lends itself to this kind of
+development: the code is easy to read and easy to write. Third, the Twisted
+library is high-level, so developers do not need to worry about select loops or
+callbacks. Twisted handles all of that and allows developers to concentrate on
+the code.
+
+No forking/threads
+------------------
+Unlike OpenSSH, the Conch server does not fork a process for each incoming
+connection. Instead, it uses the Twisted reactor to multiplex the connections.
+The only fork done is to execute a process such as a shell, but running a shell
+is not necessary, in which case the entire protocol would be run in-process.
+One of the initial features of the server was an in-process Python interpreter
+which allowed a user to interact with the server as it was running. (It is
+currently disabled for security reasons.) Threads are only used to interface
+with synchronous libraries, such as PyPAM (Pluggable Authentication Modules
+support) or PyME (GPGME support). By not using forks or threads, the time it
+takes for the Conch server to start an SSH session is roughly half of the time
+it takes for OpenSSH. However, this does require that code in Conch be
+non-blocking, which is an obstacle for programmers not used to that style.
+
+Security - No Pointers
+----------------------
+OpenSSH, LSH, and PuTTY are all written in C. Many security holes are a result
+of problems with unsafe pointer usage, which is a large problem in C code.
+Many other security holes result from related issues, such as buffer overflows,
+off-by-one errors on arrays, and memory allocation/deallocation bugs. Python
+is pointer-safe, and so is not vulnerable to this class of hole. This also
+means that no arbitrary data from over-the-wire is ever run, meaning control
+always stays with the Conch server.
+
+Security - High Level
+---------------------
+Being written in Python provides more security than just pointer safety. The
+strong builtin library that comes with Python (including powerful data types
+like the list and dictionary) means that fewer wheels need to be reinvented.
+This limits the potential to make mistakes in implementation. Exceptions are
+another powerful tool. They centralize error handling, rather than the mix of
+methods that the C libraries use. All errors are caught and dealt with: this
+might mean that the server stops accepting connections, but it never
+compromises security.
+
+Security - Not Root
+-------------------
+Also, Conch does not need to run as root. In the default server, root
+privileges are used for two things: to bind to ports < 1024, and to fork a
+process as a different user. If neither of these are needed, the server need
+not run as root at all. Even if they are, the server is only running as root
+for those small sections. The rest of the time, it runs under the effective
+user and group ID of the user who started the server. This limits the amount
+of damage that could be inflicted in the event of a compromise.
+
+Interfacing with other software
+--------------------------------------------
+OpenSSH can interact with subsystems such as SFTP only by executing a process
+to handle it. Not only is forking a process expensive, it limits the
+interaction to a generic bitstream, which leaves developers to determine how
+to interact with their users. Conch can run in the same process as other
+Python software, and is easily integrated with other Twisted servers. This
+allows for things like secure remote administration of a Twisted web server,
+encrypted communication to a Reality MUD, or secure remote object access using
+Perspective Broker. This saves the hassle and expense of forking, and allows
+Python developers to interact with Conch the way they know best: with Python.
+
+Speed
+---------------------
+No one can deny that compiled C is faster than Python. Some part of Conch use
+C (PyCrypto, TGMP) to speed frequent operations, but the majority of the code
+is in Python. The client suffers the most from this because of the time it
+takes to start the interpreter. Work is being done to speed up the client by
+caching connections. This does not eliminate the interpreter start-up cost,
+but it removes the cost of negotiating a new connection. This effort is
+similar to FSH (also in Python) but interacts more nicely with the SSH
+protocol. Psyco helps as well, offering a speedup of roughly 2x - 5x.
+
+Age
+---
+As I said in the introduction, Conch is still a newcomer on the Secure Shell
+stage (The first commit for Conch was July 15, 2002.) Although Python solves
+a large class of holes, it is probable that other security holes are in the
+code. Until a full audit is conducted of Twisted and of Conch, it should not
+be used for security-critical systems.
+
+Applications with Conch
+-----------------------
+One of the applications for Conch is with Reality, a MUD framework using
+Twisted. Conch makes it easy to allow secure connections to the MUD in
+addition or even in place of a standard Telnet connection. As problems
+such as character theft become more prevalent on the Internet, a secure
+interface becomes more important.
+More generally, work is being done on Insults, a replacement for libraries
+like Curses and S-Lang. It allows developers to write GUI code that
+interacts well with Conch and other Twisted software. Although it is in the
+initial stages of development, it shows much promise for the future.
+
+Future Directions
+-----------------
+There are several different directions for Conch to move in. One of the most
+interesting is system for generalized authentication forwarding. This would
+allow all authentication to be performed on a host that the user controls,
+which would help to stop vulnerabilities such as timing attacks. Second is
+more work with applications. Insults is becoming more powerful, and it will
+be interesting to see what it can be used for. Also important are auditing of
+the code and increasing the speed. These will make the code more useful in
+general, as well as improving security. Other ideas include direct support for
+SFTP/SCP, support for a key agent, and interfacing with Twisted.Names to
+support DNSSEC.
+
+Conclusion
+----------
+Although it is new, Conch is a working implementation of the Secure Shell
+protocol. It is robust enough to serve as both the client and server on
+systems I and others use daily.
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/smalltwisted.png b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/smalltwisted.png
new file mode 100644
index 0000000000..4f7d04d308
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/smalltwisted.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/twistedlogo.png b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/twistedlogo.png
new file mode 100644
index 0000000000..6226297c16
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/conch/twistedlogo.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-bad-adding.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-bad-adding.py
new file mode 100644
index 0000000000..d124eaacc1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-bad-adding.py
@@ -0,0 +1,8 @@
+def successCallback(result):
+ myResult = result + 1
+ print myResult
+ return myResult
+
+...
+
+adder.callRemote("add", 1, 1).addCallback(successCallback)
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-chaining.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-chaining.py
new file mode 100644
index 0000000000..cee9bea209
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-chaining.py
@@ -0,0 +1,13 @@
+from twisted.internet import reactor, defer
+
+A = defer.Deferred()
+def X(result):
+ B = defer.Deferred()
+ reactor.callLater(2, B.callback, result)
+ return B
+def Y(result):
+ print result
+A.addCallback(X)
+A.addCallback(Y)
+A.callback("hello world")
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-complex-failure.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-complex-failure.py
new file mode 100644
index 0000000000..bcc38e21f6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-complex-failure.py
@@ -0,0 +1,30 @@
+from deferexex import adder
+
+class MyExc(Exception):
+ "A sample exception"
+
+class MyObj:
+
+ def blowUp(self, result):
+ self.x = result
+ raise MyExc("I can't go on!")
+
+ def trapIt(self, failure):
+ failure.trap(MyExc)
+ print 'error (', failure.getErrorMessage(), '). x was:', self.x
+ return self.x
+
+ def onSuccess(self, result):
+ print result + 3
+
+ def whenTrapped(eslf, result):
+ print 'Finally, result was', result
+
+ def run(self, o):
+ o.callRemote("add", 1, 2).addCallback(
+ self.blowUp).addCallback(
+ self.onSuccess).addErrback(
+ self.trapIt).addCallback(
+ self.whenTrapped)
+
+MyObj().run(adder)
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-complex-raise.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-complex-raise.py
new file mode 100644
index 0000000000..8005e45b91
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-complex-raise.py
@@ -0,0 +1,12 @@
+class MyExc(Exception):
+ "A sample exception."
+
+try:
+ x = 1 + 3
+ raise MyExc("I can't go on!")
+ x = x + 1
+ print x
+except MyExc, me:
+ print 'error (',me,'). x was:', x
+except:
+ print 'fatal error! abort!'
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-forwarding.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-forwarding.py
new file mode 100644
index 0000000000..c2fa6f9ad9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-forwarding.py
@@ -0,0 +1,9 @@
+from twisted.spread import pb
+
+class LocalForwarder(flavors.Referenceable):
+ def remote_foo(self):
+ return str(self.local.baz())
+
+class RemoteForwarder(flavors.Referenceable):
+ def remote_foo(self):
+ return self.remote.callRemote("baz").addCallback(str)
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing0.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing0.py
new file mode 100644
index 0000000000..e0de3ce4f4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing0.py
@@ -0,0 +1,18 @@
+
+class DocumentProcessor:
+ def __init__(self):
+ self.loadDocuments(self.callback, mySrv, "hello")
+
+ def loadDocuments(callback, server, keyword):
+ "Retrieve a set of documents!"
+ ...
+
+ def callback(self, documents):
+ try:
+ for document in documents:
+ process(document)
+ finally:
+ self.cleanup()
+
+ def cleanup(self):
+ ...
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing1.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing1.py
new file mode 100644
index 0000000000..399eb2b05a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing1.py
@@ -0,0 +1,6 @@
+def prettyRequest(server, requestName):
+ return server.makeRequest(requestName
+ ).addCallback(
+ lambda result: ', '.join(result.asList())
+ ).addErrback(
+ lambda failure: failure.printTraceback())
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing2.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing2.py
new file mode 100644
index 0000000000..d124eaacc1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-listing2.py
@@ -0,0 +1,8 @@
+def successCallback(result):
+ myResult = result + 1
+ print myResult
+ return myResult
+
+...
+
+adder.callRemote("add", 1, 1).addCallback(successCallback)
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-simple-failure.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-simple-failure.py
new file mode 100644
index 0000000000..34f6b298f4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-simple-failure.py
@@ -0,0 +1,9 @@
+from deferexex import adder
+
+def blowUp(result):
+ raise Exception("I can't go on!")
+
+def onSuccess(result):
+ print result + 3
+
+adder.callRemote("add", 1, 2).addCallback(blowUp).addCallback(onSuccess)
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-simple-raise.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-simple-raise.py
new file mode 100644
index 0000000000..cd89b1ae63
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex-simple-raise.py
@@ -0,0 +1,3 @@
+x = 1 + 3
+raise Exception("I can't go on!")
+print x
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex.html b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex.html
new file mode 100644
index 0000000000..a4fb1712a5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferex.html
@@ -0,0 +1,499 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+<title>Generalization of Deferred Execution in Python</title>
+</head>
+
+<body>
+<h1>Generalization of Deferred Execution in Python</h1>
+
+<p>Glyph Lefkowitz</p>
+
+<div>
+<div>Twisted Matrix Labs</div>
+<div><a href="mailto:glyph@twistedmatrix.com">glyph@twistedmatrix.com</a></div>
+</div>
+
+<h2>Overview</h2>
+
+<p>A deceptively simple architectural challenge faced by many multi-tasking
+applications is gracefully doing nothing. Systems that must wait for the
+results of a long-running process, network message, or database query while
+continuing to perform other tasks must establish conventions for the semantics
+of waiting. The simplest of these is blocking in a thread, but it has
+significant scalability problems. In asynchronous frameworks, the most common
+approach is for long-running methods to accept a callback that will be executed
+when the command completes. These callbacks will have different signatures
+depending on the nature of the data being requested, and often, a great deal of
+code is necessary to glue one portion of an asynchronous networking system to
+another. Matters become even more complicated when a developer wants to wait
+for two different events to complete, requiring the developer to &quot;juggle&quot;
+the callbacks and create a third, mutually incompatible callback type to handle
+the final result. </p>
+
+<p>This paper describes the mechanism used by the Twisted framework for waiting
+for the results of long-running operations. This mechanism, the <code>Deferred</code>,
+handles the often-neglected problems of error handling, callback juggling,
+inter-system communication and code readability. </p>
+
+<p> In a framework like Twisted, the ability to glue two existing components
+together with a minimum of mediating code is paramount. Considering that the
+vast majority of the code in Twisted is asynchronous I/O handling, it is
+imperative that the mechanism for relaying the data between the output from one
+system into the input of another be competitive with the simplicity of passing
+the return value of one method to the argument of another. It was also
+important to use only no new syntax to avoid confusing programmers who already
+have experience with Python, and establish no dependencies on anything which
+would break compatibility with existing Python code or C / Java
+extensions. </p>
+
+<h2>Other Popular Approaches</h2>
+
+<p>There are several traditional approaches to handling concurrency that have
+been taken by application frameworks in the past. Each has its own
+drawbacks.</p>
+
+<h3>Threads</h3>
+
+<p>The problems with using threads for concurrency in systems that need to
+scale is fairly well-documented. However, many systems that use asynchronous
+multiplexing for I/O and system-level tasks, but run application code in a
+thread. Zope's threaded request handling is a good example of this model.</p>
+
+<p>It is optimal, however, to avoid <em>requiring</em> threads for any part of
+a framework. Threading has a significant cost, especially in Python. The
+global interpreter lock destroys any performance benefit that threading may
+yield on SMP systems, and introduces significant complexity into both framework
+and application code that needs to be thread-safe.</p>
+
+<p>A full discussion of the pros and cons of threads is beyond the scope of
+this paper, however, using threads merely for blocking operations is clearly
+overkill. Since each thread represents some allocation of resources, all of
+those resources are literally sitting idle if they are doing nothing but
+waiting for the results from a blocking call.</p>
+
+<p>In a fairly traditional networking situation, where the server is
+asynchronously multiplexed, this waste of resources may be acceptable for
+special-purpose, simple client programs, since only a few will be run at a
+time. To create a generic system, however, one must anticipate cases when the
+system in question is not only a multi-user server or a single-user client, but
+also a multi-user hybrid client/server.</p>
+
+<p>A good example of this is a high-volume web spider. A spider may have a
+server for administrative purposes, but must also be able to spawn many
+requests at once and wait for them all to return without allocating undue
+resources for each request. The non-trivial overhead of threads, in addition
+to sockets, would be a very serious performance problem. </p>
+
+<h3>Callback Conventions</h3>
+
+<p>At some level, any system for handling asynchronous results in Python will
+be based on callback functions. The typical way to present this to the
+application programmer is to have all asynchronous methods accept a callback as
+one of their arguments.</p>
+
+<p>This approach is usually standardized by giving the callback having a
+standard name (&quot;callback&quot;) or a particular position (first argument, last
+argument). Even systems which rigorously adhere to such standardization run
+into problems, however.</p>
+
+<p>This approach does work for a variety of events. It is unwieldy when one is
+attempting to write asynchronous &quot;conversations&quot; that involve multiple
+stages. The first problem that we notice is the lack of error-handling. If an
+error occurs in normal Python code, Exception handling provides clean and
+powerful semantics for handling it. </p>
+
+<a href="deferex-listing0.py" class="py-listing">Document Processor Example</a>
+
+<p>In an asynchronous method such as the one given above, traditional
+exceptions fall short. What if an error occurs retrieving the documents from
+storage? Do we call the callback with an error rather than a result?</p>
+
+<h3>Language Modifications</h3>
+
+<p>Other languages handle this by associating different semantics with
+threading, or providing different constructs altogether for concurrency. This
+has the disadvantage that these languages aren't Python. Even Stackless Python
+is problematic because it lacks integration with the wide variety of libraries
+that Python provides access to. </p>
+
+<p>The design of <code>Deferred</code> draws upon some of these other languages, and this
+section will cover several languages and their impact.</p>
+
+<p>In particular, the following list of languages were influential:</p>
+
+<ul>
+ <li>Erlang</li>
+ <li>Mozart/Oz</li>
+ <li>E</li>
+ <li>Scheme</li>
+ <li>Smalltalk</li>
+</ul>
+
+<p> E, Smalltalk, and Scheme proved particularly influential. In E's, there is
+a sharp distinction between objects which are synchronously accessible and
+those which are asynchronously accessible. The original use for
+<code>Deferred</code>s was to represent results from Perspective Broker method
+calls. E was interesting in that the entire execution environment had
+assumptions about networking built in. E's &quot;eventually&quot; operator <a
+href="#steigler">[stiegler]</a> is what originally inspired the distinction
+between &quot;a method which returns X&quot; and &quot;a method which returns a
+<code>Deferred</code> that fires X&quot;. </p>
+
+<p>
+Smalltalk was influential in that its syntax for closures provided some
+precedent for thinking about the continuation of a &quot;conversation&quot; of execution
+as itself an object with methods. The original thought-experiment that lead to
+<code>Deferred</code>s was an attempt to write some Squeak code that looked like this:
+
+<pre>
+(object callRemote: &quot;fooBar&quot;) andThen: [ result |
+ Transcript show: result.
+ ] orElse: [ failure |
+ failure printTraceback.
+ ]
+</pre>
+
+The hypothetical <code>callRemote</code> method here would return an object
+with the method <code>andThen:orElse:</code> that took 2 code blocks, one for
+handling results and the other for handling errors.
+</p>
+
+<p>It was challenging to write enough Smalltalk code to make anything
+interesting happen with this paradigm, but within the existing framework of
+Twisted, it was easy to convert several request/response idioms to use this
+sort of object. Now that Twisted has dropped python 1.5.2 compatibility, and
+2.1 is the baseline version, we can use <code>nested_scopes</code> <a
+href="#hylton">[hylton]</a> and anonymous functions to make the code look
+similar to this original model. </p>
+
+<p> Scheme, of course, provides <code>call-with-current-continuation</code> (or
+<code>call/cc</code>), the mind-bending control structure which has been a
+subject of much debate in language-design circles. <code>call/cc</code> may
+have provided more a model of things to avoid than a real inspiration, though.
+While it is incredibly powerful, it creates almost as many problems as it
+solves. In particular, the interaction between continuations and
+<code>try:finally:</code> is undefined <a href="#pitman">[pitman]</a>, since it
+is impossible to determine the final time the protected code will be run. The
+strongest lesson from <code>call/cc</code> was to only take as much state in
+the <code>Deferred</code> as necessary, and to avoid playing tricks with implicit context.
+</p>
+
+<p>The mechanisms that these languages use, however, often rely upon deeper
+abstractions that make their interpreters less amenable than Python's to
+convenient, idiomatic integration with C and UNIX. Scheme's
+<code>call/cc</code> requires a large amount of work and creativity to
+integrate with &quot;C&quot; language libraries, as C. Tismer's work in
+Stackless Python Python has shown. <a href="#tismer">[tismer]</a> </p>
+
+<h2>Basics of Deferreds</h2>
+
+<p>After several months of working with Twisted's callback-based
+request/response mechanisms, it became apparent that something more was
+necessary. Often, errors would silently cause a particular process to halt.
+The syntax for a multi-stage asynchronous process looked confusing, because
+multiple different interfaces were being invoked, each of which taking multiple
+callbacks. The complexity of constructing these stages was constantly being
+exposed to the application developer, when it shouldn't really concern them.
+</p>
+
+<p>In order to make gluing different request/response systems together easy, we
+needed to create a more uniform way of having them communicate than a simple
+convention. In keeping with that goal, we reduced several conventions into one
+class, <code>Deferred</code>, so that the request system could return a
+<code>Deferred</code> as output and the responder could accept a <code>Deferred</code> as input..
+<code>Deferred</code>s are objects which represent the result of a request that is not yet
+available. It is suggested that any methods which must perform long-running
+calculations or communication with a remote host return a <code>Deferred</code>.</p>
+
+<p>This is similar to the Promise pattern, or lazy evaluation, except that it
+is a promise that will not be resolved synchronously. The terminology usually
+used to describe a <code>Deferred</code> is &quot;a <code>Deferred</code> that will fire&quot; a particular
+result.</p>
+
+<p><code>Deferred</code>s have a small interface, which boils down to these five methods,
+plus convenience methods that call them:
+
+<ul>
+ <li><code>addCallbacks(self, callback, errback=None, callbackArgs=None,
+ callbackKeywords=None, errbackArgs=None, errbackKeywords=None)</code></li>
+ <li><code>callback(result)</code></li>
+ <li><code>errback(result)</code></li>
+ <li><code>pause()</code></li>
+ <li><code>unpause()</code></li>
+</ul>
+
+</p>
+
+<p>In general, code that initially returns <code>Deferred</code>s will be framework code,
+such as a web request or a remote method call. This means that code that uses
+the framework will call <code>addCallbacks</code> on the <code>Deferred</code> that is
+returned by the framework. When the result is ready, the callback will be
+triggered and the client code can process the result. Usually the utility
+methods <code>addCallback</code> and <code>addErrback</code> are used.
+</p>
+
+<p>Using <code>addCallbacks</code> has slightly different semantics than using
+<code>addCallback</code> followed by <code>addErrback</code>;
+<code>addCallbacks</code> places the callback and the errback &quot;in
+parallel&quot;, meaning if there is an error in your callback, your errback will
+not be called. Thus using <code>addCallbacks</code> has either/or semantics;
+either the callback or the errback will be called, but not both.</p>
+
+<a href="deferex-listing1.py" class="py-listing">Fictitious Request Example</a>
+
+<p>The example given shows a method which returns a <code>Deferred</code> that will fire a
+formatted string of the result of a given request. The return value of each
+callback is passed to the first argument of the next.</p>
+
+<h2>Generalized Error Handling</h2>
+
+<p>As described above in the section on using callbacks for asynchronous result
+processing, one of the most common application-level problems in an
+asynchronous framework is an error that causes a certain task to stop
+executing. For example, if an exception is raised while hashing a user's
+password, the entire log-in sequence might be halted, leaving the connection in
+an inconsistent state.</p>
+
+<p>One way that Twisted remedies this is to have reasonable default behavior in
+circumstances such as this: if an uncaught exception is thrown while in the
+<code>dataReceived</code> callback for a particular connection, the connection
+is terminated. However, for multi-step asynchronous conversations, this is not
+always adequate.</p>
+
+<p>Python's basic exception handling provides a good example for an
+error-handling mechanisms. If the programmer fails to account for an error, an
+uncaught exception stops the program and produces information to help track it
+down. Well-written python code never has to manually detect whether an error
+has occurred or not: code which depends on the previous steps being successful
+will not be run if they are not. It is easy to provide information about an
+error by using attributes of exception objects. It is also easy to relay
+contextual information between successful execution and error handlers, because
+they execute in the same scope.</p>
+
+<p><code>Deferred</code> attempts to mimic these properties as much as possible in an
+asynchronous context.</p>
+
+<h3>Reasonable Defaults</h3>
+
+<p>When something unexpected goes wrong, the program should emit some debugging
+information and terminate the asynchronous chain of processing as gracefully as
+possible.</p>
+
+<p>Python exceptions do this very gracefully, with no effort required on the
+part of the developer at all.</p>
+
+<a href="deferex-simple-raise.py" class="py-listing">Simple Catastrophic Exception</a>
+
+<p><code>Deferred</code>s provide a symmetrical facility, where the developer may register a
+callback but then forego any error processing.</p>
+
+<a href="deferex-simple-failure.py" class="py-listing">Simple Catastrophic Deferred Failure</a>
+
+<p>In this example, the onSuccess callback will never be run, because the
+blowUp callback creates an error condition which is not handled. </p>
+
+<h3>No Ambiguity about Failure</h3>
+
+<p>It is impossible to provide a reasonable default behavior if failure is
+ambiguous. Code should never have to manually distinguish between success and
+failure. An error-processing callback has a distinct signature to a
+result-processing callback.</p>
+
+<p>Forcing client code to manually introspect on return values creates a common
+kind of error; when the success of a given long-running operation is assumed,
+it appears to work, and it is easier (and less code) to write a callback that
+only functions properly in a successful case, and creates bizarre errors in a
+failure case. A simple example:.</p>
+
+<a class="py-listing" href="deferex-bad-adding.py">Common Error Pattern</a>
+
+<p>In this example, when the remote call to add the two numbers succeeds,
+everything looks fine. However, when it fails, <code>result</code> will be an
+exception and not an integer: therefore the printed traceback will say
+something unhelpful, like:</p>
+
+<pre>TypeError: unsupported operand types for +: 'instance' and 'int'</pre>
+
+<h3>Rich Information about Errors</h3>
+
+<p>It should be easy for developers to distinguish between fatal and non-fatal
+errors. With Python exceptions, you can do this by specifying exception
+classes, which is a fairly powerful technique.</p>
+
+<a href="deferex-complex-raise.py" class="py-listing">Complex Python Exception</a>
+
+<p>With <code>Deferred</code>, we planned to have a syntactically simple technique for
+accomplishing something similar. The resulting code structure is tends to be a
+bit more expansive than the synchronous equivalent, due to the necessity of
+giving explicit names to the functions involved, but it can be just as easy to
+follow.</p>
+
+<a href="deferex-complex-failure.py" class="py-listing">Complex Deferred Failure</a>
+
+<p>In this example, we have a callback chain that begins with the result of a
+remote method call of 3. We then encounter a <code>MyExc</code> error raised
+in <code>blowUp</code>, which is caught by the errback <code>trapIt</code>.
+The 'trap' method will re-raise the current failure unless its class matches
+the given argument, so the error will continue to propagate if it doesn't
+match, much like an <code>except:</code> clause.</p>
+
+<h3>Easy Propagation of Context</h3>
+
+<p>While it is dangerous to implicitly propagate too much context (leading to
+problems similar to those with threads), we wanted to make sure that it is easy
+to move context from one callback to the next, and to convert information in
+errors into successful results after the errors have been handled. </p>
+
+<p>Both <code>addCallback</code> and <code>addErrback</code> have the signature
+<code>callable, *args, **kw</code>. The additional arguments are passed
+through to the registered callback when it is invoked. This allows us to
+easily send information about the current call to the error-handler in the same
+way as the success callback.</p>
+
+<h2>Patterns of Usage</h2>
+
+<p>Since <code>Deferred</code> is designed for a fairly specific class of problems, most
+places it is used tend to employ certain idioms.</p>
+
+<h3>Request-ID Dictionary</h3>
+
+<p>If you are implementing a symmetric, message-oriented protocol, you will
+typically need to juggle an arbitrary number of outstanding requests at once.
+The normal pattern for doing this is to create a dictionary mapping a request
+number to a <code>Deferred</code>, and firing a <code>Deferred</code> when a response with a given
+request-ID associated with it arrives.</p>
+
+<p>A good example of this pattern is the Perspective Broker protocol. Each
+method call has a request, but it is acceptable for the peer to make calls
+before answering requests. Few protocols are as extensively permissive about
+execution order as PB, but any full-fledged RPC or RMI protocol will enable
+similar interactions. The MSN protocol implementation in Twisted also uses
+something similar.</p>
+
+<h3> Sometimes Synchronous Interface </h3>
+
+<p>When writing interfaces that application programmers will be implementing
+frequently, it is often convenient to allow them to either return either a
+<code>Deferred</code> or a synchronous result. A good example of this is Twisted's Woven, a
+dynamic web content system.
+</p>
+
+<p> The processing of any XML node within a page may be deferred until some
+results are ready, be they results of a database query, a remote method call,
+an authentication request, or a file upload. Many methods that may return
+Nodes may also return <code>Deferred</code>s, so that in either case the application
+developer need return the appropriate value. No wrapping is required if it is
+synchronous, and no manual management of the result is required if it is not.
+</p>
+
+<p>This is the best way to assure that an application developer will never need
+to care whether a certain method's results are synchronous or not. The first
+usage of this was in Perspective Broker, to allow easy transparent forwarding
+of method calls. If a <code>Deferred</code> is returned from a remotely accessible method,
+the result will not be sent to the caller until the <code>Deferred</code> fires. </p>
+
+<a href="deferex-forwarding.py" class="py-listing">Forwarding Local and Remote
+Interfaces</a>
+
+<h3><code>callRemote</code></h3>
+
+<p>Ideally, all interactions between communicating systems would be modeled as
+asynchronous method calls. Twisted Words, the Twisted chat server, treats any
+asynchronous operation as a subset of the functionality of Perspective Broker,
+using the same interface. Eventually, the hope is to make greater use of this
+pattern, and abstract asynchronous conversations up another level, by having
+the actual mechanism of message transport wrapped so that client code is only
+aware of what asynchronous interface is being invoked.</p>
+
+<h2>Advanced Features</h2>
+
+<p>The first &quot;advanced&quot; feature of <code>Deferred</code>s is actually
+used quite frequently. As discussed previously, each <code>Deferred</code> has
+not one, but a chain of callbacks, each of which is passed the result from the
+previous callback. However, the mechanism that invokes each callback is itself
+an implementor of the previously-discussed &quot;Sometimes Synchronous
+Interface&quot; pattern - a callback may return either a value or a
+<code>Deferred</code>.</p>
+
+<p>For example, if we have a <code>Deferred</code> A, which has 2 callbacks: X,
+which returns a deferred B, that fires the result to X in 2 seconds, and Y,
+which prints its result, we will see the string &quot;hello&quot; on the screen
+in 2 seconds. While it may sound complex, this style of coding one
+<code>Deferred</code> which depends on another looks very natural.</p>
+
+<a class="py-listing" href="deferex-chaining.py">Chaining 2
+<code>Deferred</code>s Together</a>
+
+<p>In this way, any asynchronous conversation may pause to wait for an
+additional request, without knowing in advance of running the first request
+what all the requests will be.</p>
+
+<p>The other advanced feature of <code>Deferred</code>s is not terribly common,
+but is still useful on occasion. We have glossed over the issue of
+&quot;pre-executed&quot;<code>Deferred</code>s so far, e.g. <code>Deferred</code>s
+which have already been called with a callback value before client code adds
+callbacks to them. The default behavior, which works in almost every
+situation, is simply to call the callback immediately (synchronously) as it is
+added. However, there are rare circumstances where race conditions can occur
+when this naive approach is taken.</p>
+
+<p>For this reason, <code>Deferred</code> provides <code>pause</code> and
+<code>unpause</code> methods, allowing you to put a <code>Deferred</code> into
+a state where it will stop calling its callbacks as they are added; this will
+allow you to set up a series of communicating <code>Deferred</code>s without
+having anything execute, complete your setup work, and then unpause the
+process.</p>
+
+<p>In this way, you can create centralized choke-points for caring about whether
+a process is synchronous or not, and completely ignore this problem in your
+application code. For example, in the now-obsolete Twisted Web Widgets system
+(a dynamic web content framework that predates woven), it was necessary to make
+sure that certain <code>Deferred</code>s were always called in order, so the page would
+render from top to bottom. However, the user's code did not need to concern
+itself with this, because any <code>Deferred</code>s for which synchronous callback
+execution would have been an issue were passed to user code paused.</p>
+
+<h2>Conclusion</h2>
+
+<p><code>Deferred</code>s are a powerful abstraction for dealing with
+asynchronous results. Having a uniform approach to asynchronous conversations
+allows Twisted APIs to provide a level of familiarity and flexibility for
+network programmers that approaches that of domain-specific languages, but
+still provides access to all of Python's power.</p>
+
+<h2>Acknowledgements</h2>
+
+<p>I would like to thank the entire Twisted team, for making me realize what a
+good idea I had hit upon with <code>Deferred</code>s.</p>
+
+<p>Special thanks go to Andrew Bennetts and Moshe Zadka, for implementing the
+portion of Twisted used to generate this, and other, papers, and to Ying Li and
+Donovan Preston for last-minute editorial assistance..</p>
+
+<h2>References</h2>
+
+<ol>
+
+ <li><a name="stiegler"></a>Marc Stiegler, <a
+ href="http://www.skyhunter.com/marcs/ewalnut.html#SEC19">The E Language in a
+ Walnut</a>, <i>erights.org</i></li>
+
+ <li><a name="hylton"></a>Jeremy Hylton, <a
+href="http://www.python.org/peps/pep-0227.html" >PEP 227, &quot;Statically
+Nested Scopes&quot;</a></li>
+
+ <li><a name="pitman"></a>Kent Pitman, <a
+href="http://www.nhplace.com/kent/PFAQ/unwind-protect-vs-continuations.html"
+ >UNWIND-PROTECT vs. Continuations</a>, <i>Kent Pitman's Personal FAQ</i></li>
+
+ <li><a name="tismer">Christian Tismer, <a
+ href="http://www.stackless.com/spcpaper.htm">Continuations and Stackless
+ Python</a>, <i>Proceedings of the Sixth International Python Conference</i>
+ </a>
+ </li>
+
+</ol>
+
+</body>
+</html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferexex.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferexex.py
new file mode 100644
index 0000000000..71c116eee1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/deferex/deferexex.py
@@ -0,0 +1,16 @@
+
+# DEFERred EXecution EXamples
+
+### make sure errors come out in order
+import sys
+from twisted.python import log
+log.logerr = sys.stdout
+
+# Create a pseudo "remote" object for executing this stuff
+from twisted.spread.util import LocalAsRemote
+class Adder(LocalAsRemote):
+ def async_add(self, a, b):
+ print 'adding', a, b
+ return a + b
+
+adder = Adder()
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning
new file mode 100644
index 0000000000..d41d3af0a1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning
@@ -0,0 +1,97 @@
+#!/usr/bin/python
+
+from slides import *
+from twslides import *
+
+class PythonSource:
+ def __init__(self, content):
+ self.content = content
+ def toHTML(self):
+ return '<pre class="python">%s</pre>' % (self.content,)
+
+lecture = Lecture(
+ "Changing the Type of Literals",
+ Slide("New-style classes",
+ Bullet("In 2.2+, built-in types can be subclassed"),
+ Bullet("These can be created explicitly by using their name", SubBullet(
+ Bullet("For example, an int subclass that displays itself in roman numerals"),
+ Bullet("print RomanNumeral(13) -> XIII"),
+ )),
+ ),
+ Slide("Literals are less accessable",
+ Bullet("When you write [] or 7, the list or int type is instantiated"),
+ Bullet("This behavior seems inaccessable"),
+ Bullet("While this makes for more readable code, it limits the scope of possible evil"),
+ ),
+ Slide("Throw in an extension module...",
+ Bullet("intrinsics.so exposes one function, 'replace'"),
+ Bullet("It takes two arguments", SubBullet(
+ Bullet("A type object to replace"),
+ Bullet("The type object with which to replace it"),
+ )),
+ Bullet("Magic is performed, and the new type is now used whenever the old one would have been"),
+ ),
+ Slide("An example",
+ PythonSource("""\
+class RomanNumeral(int):
+ def __str__(self):
+ # Regular code for formatting roman numerals
+
+old_int = intrinsics.replace(int, RomanNumeral)
+print 13
+"""
+ ),
+ Bullet("The output is simply the roman numerals XIII"),
+ ),
+ Slide("intrinsics.c - The replacement",
+ PRE("""\
+PyObject*
+intrinsics_replace(PyObject* self, PyObject* args) {
+ static PyTypeObject* const types[] = {
+ &PyInt_Type, &PyLong_Type, &PyFloat_Type, &PyComplex_Type,
+ &PyBool_Type, &PyBaseObject_Type, &PyDict_Type, &PyTuple_Type,
+ &PyBuffer_Type, &PyClassMethod_Type, &PyEnum_Type, &PyProperty_Type,
+ &PyList_Type, &PyStaticMethod_Type, &PySlice_Type, &PySuper_Type,
+ &PyType_Type, &PyRange_Type, &PyFile_Type, &PyUnicode_Type,
+ &PyString_Type,
+ NULL
+ };
+
+ int i = 0;
+ PyObject *old, *new;
+ PyTypeObject* space;
+
+ if (!PyArg_ParseTuple(args, "OO:replace", &old, &new))
+ return NULL;
+"""
+ ),
+ ),
+ Slide("intrinsics.c - The actual replacement",
+ PRE("""\
+ while (types[i]) {
+ if (types[i] == (PyTypeObject*)old) {
+ space = PyObject_New(PyTypeObject, &PyType_Type);
+ *space = *(types[i]);
+ *(types[i]) = *(PyTypeObject*)new;
+ break;
+ }
+ ++i;
+ }
+ if (!types[i]) {
+ PyErr_SetString(replace_error, "unknown type");
+ return NULL;
+ }
+ Py_INCREF(new);
+ Py_INCREF(space);
+ return (PyObject*)space;
+}
+"""
+ ),
+ ),
+ Slide("This is the wrong answer",
+ Bullet("The right answer is to add more flexibility to the Python compiler"),
+ Bullet("This is a lot less code, though"),
+ )
+)
+
+lecture.renderHTML(".", "intrinsics-lightning-%d.html", css="stylesheet.css")
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore-presentation b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore-presentation
new file mode 100755
index 0000000000..59a4d6a6be
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore-presentation
@@ -0,0 +1,108 @@
+#!/usr/bin/python2.2
+# Moshe -- current content is estimated at about 15 minutes
+from slides import NumSlide, Slide, Bullet, SubBullet, PRE, URL
+from twslides import Lecture
+
+
+lecture = Lecture(
+ "Lore: A Document Generation System",
+ Slide("Introduction",
+ Bullet("Document generation system"),
+ Bullet("Input a strict subset of XHTML"),
+ Bullet("Output -- nicely formatted HTML and LaTeX"),
+ Bullet("Used to generate >200 pages of Twisted documentation"),
+ ),
+ Slide("History",
+ Bullet("Twisted needed documentation -- and a format"),
+ Bullet("Reluctance to add dependence on a big system"),
+ Bullet("Wanted something quick and easy -- subset of HTML!"),
+ Bullet("Needs matured: table of contents, printed version"),
+ Bullet("Enter Lore"),
+ ),
+ Slide("Goals",
+ Bullet("Easy to use for authors"),
+ Bullet("Easy to install"),
+ Bullet("(Uncommon) Source format should be readable"),
+ ),
+ Slide("Contents",
+ Bullet("twisted.lore Python package"),
+ Bullet("'lore' command-line program"),
+ Bullet("Comes with every Twisted installation"),
+ Bullet("In particular -- works on Linux, Win32, Mac"),
+ Bullet("In particular -- supports Python 2.1, 2.2, 2.3 alpha"),
+ ),
+ Slide("Alternatives - HTML",
+ Bullet("Too flexible"),
+ Bullet("No support for needed idioms", SubBullet(
+ Bullet("Special-purpose Python markup"),
+ Bullet("Tables of contents"),
+ Bullet("Inlining")),
+ ),
+ Bullet("Renders badly to dead trees with current tools"),
+ ),
+ Slide("Alternatives - LaTeX",
+ Bullet("Very good at printed results"),
+ Bullet("Model makes alternative parsers near-impossible"),
+ Bullet("Renderers to HTML are buggy and fragile"),
+ ),
+ Slide("Alternatives - Docbook",
+ Bullet("Using correctly requires too much work", SubBullet(
+ Bullet("Write a DTD with special elements"),
+ Bullet("Write Jade stylesheets"))),
+ Bullet("Lore is probably smaller than docbook specialization"),
+ ),
+ Slide("Alternatives - Texinfo",
+ Bullet("Next slide, please"),
+ ),
+ Slide("Lore goodies",
+ Bullet("Special tag to mark classes/modules/functions", SubBullet(
+ Bullet("Can be made to point to auto-generated docs")),
+ ),
+ Bullet("Inline code-examples", SubBullet(
+ Bullet("No need to escape all those <, > and &")),
+ ),
+ Bullet("Syntax-highlight Python code"),
+ ),
+ Slide("hlint - A lint-like program",
+ Bullet("Checks for many common errors"),
+ Bullet("Unhandled elements"),
+ Bullet("Misspelled (or miscased) class names"),
+ Bullet("Checks Python code for syntax errors"),
+ ),
+ Slide("Extending Lore",
+ Bullet("Easily done with some Python code"),
+ Bullet("Input-enhancements decide which output formats to handle"),
+ Bullet("Example: math-lore, Lore with LaTeX formulae"),
+ ),
+ Slide("HTML Output",
+ Bullet("HTML is a flexible output format"),
+ Bullet("Documents often have to integrate with a site"),
+ Bullet("Lore produces HTML documents based on a template"),
+ Bullet("Lore uses only HTML 'class' attributes, never 'font'",
+ SubBullet(Bullet("Plays nice with CSS")),
+ ),
+ ),
+ Slide("Man Pages",
+ Bullet("Lore has a program to convert man pages to Lore documents"),
+ Bullet("Man pages are written anyway"),
+ Bullet("No man output: the format is too limited"),
+ ),
+ Slide("Small Example",
+ PRE("""\
+<html>
+<head>
+<title>Example</title>
+</head>
+<body>
+<h1>Example</h1>
+<p>Simple paragraph<span class="footnote">footnote</span></p>
+</body>
+</html>
+""")),
+ Slide("Future Directions",
+ Bullet("More output formats"),
+ Bullet("Some more classes - abstract, bibliography"),
+ ),
+)
+
+lecture.renderHTML(".", "lore-%d.html", css="main.css")
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore-slides.html b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore-slides.html
new file mode 100755
index 0000000000..19171f88f6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore-slides.html
@@ -0,0 +1,187 @@
+<html>
+ <head><title>Lore: A Document Generation System</title></head>
+
+ <body>
+ <h1>Lore: A Document Generation System</h1>
+
+ <div class="author">Andrew Bennetts &lt;andrew@puzzling.org&gt;</div>
+ <div class="author">(Twisted Lore maintainer)</div>
+
+ <h2>Introduction</h2>
+ <ul>
+ <li>Document generation system</li>
+ <li>Input format is essentially a subset of XHTML</li>
+ <li>Outputs nicely formatted HTML and LaTeX</li>
+ <li>Used to generate &gt;200 pages of Twisted documentation</li>
+ </ul>
+
+ <h2>History</h2>
+ <ul>
+ <li>Twisted needed documentation -- and a format</li>
+ <li>We didn't want to depend on a big system
+ <ul>
+ <li>The lower the barrier for documentation contributions, the
+ better</li>
+ </ul>
+ </li>
+ <li>We wanted something quick and easy
+ <ul>
+ <li>Lots of people already know simple HTML</li>
+ <li>People were already using HTML to write docs</li>
+ </ul>
+ </li>
+ <li>Our needs matured: table of contents, printable version</li>
+ <li>So we created Lore</li>
+ </ul>
+
+ <h2>Goals</h2>
+ <ul>
+ <li>Easy to use for authors</li>
+ <li>Easy to install</li>
+ <li>(Uncommon) Source format should be readable
+ <ul>
+ <li>Even to non-hackers</li>
+ </ul>
+ </li>
+ </ul>
+
+ <h2>Small Example</h2>
+ <pre>
+&lt;html&gt;
+&lt;head&gt;
+&lt;title&gt;Example&lt;/title&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;h1&gt;Example&lt;/h1&gt;
+&lt;p&gt;Simple paragraph&lt;span class="footnote"&gt;footnote&lt;/span&gt;&lt;/p&gt;
+&lt;/body&gt;
+&lt;/html&gt;
+</pre>
+
+ <h2>Contents</h2>
+ <ul>
+ <li>twisted.lore Python package</li>
+ <li><code class="shell">lore</code> command-line program</li>
+ <li>Comes with every Twisted installation</li>
+ <li>In particular -- works on Linux, Win32, Mac</li>
+ <li>In particular -- supports Python 2.1, 2.2, 2.3 alpha</li>
+ </ul>
+
+ <h2>Alternatives - HTML</h2>
+ <ul>
+ <li>Too flexible</li>
+ <li>No support for needed idioms
+ <ul>
+ <li>Special-purpose Python markup</li>
+ <li>Tables of contents</li>
+ <li>Inlining</li>
+ </ul>
+ </li>
+ <li>Renders badly to dead trees with current tools</li>
+ </ul>
+
+ <h2>Alternatives - LaTeX</h2>
+ <ul>
+ <li>Very good at printed results</li>
+ <li>LaTeX's design makes alternative parsers near-impossible</li>
+ <li>Renderers to HTML are buggy and fragile
+ <ul>
+ <li>Although the Python Standard Library seems to cope :-)</li>
+ </ul>
+ </li>
+ </ul>
+
+ <h2>Alternatives - Docbook</h2>
+ <ul>
+ <li>Using correctly requires too much work
+ <ul>
+ <li>Write a DTD with special elements</li>
+ <li>Write Jade stylesheets</li>
+ </ul>
+ </li>
+ <li>Lore is probably smaller than docbook specialization</li>
+ </ul>
+
+ <h2>Alternatives - Texinfo</h2>
+ <ul>
+ <li>Next slide, please</li>
+ </ul>
+
+ <h2>Lore goodies</h2>
+ <ul>
+ <li>Special tag to mark classes/modules/functions
+ <ul>
+ <li>Can be made to point to auto-generated docs</li>
+ </ul>
+ </li>
+
+ <li>Inline code-examples
+ <ul>
+ <li>No need to escape all those &lt;, &gt; and &amp;</li>
+ </ul>
+ </li>
+
+ <li>Syntax-highlight Python code</li>
+ </ul>
+
+ <h2>'lore -o lint': A lint-like tool</h2>
+ <ul>
+ <li>Checks for many common errors
+ <ul>
+ <li>Invalid XML</li>
+ <li>Unhandled elements</li>
+ <li>Misspelled (or miscased) class names</li>
+ <li>Checks Python code for syntax errors</li>
+ </ul>
+ </li>
+ </ul>
+
+ <h2>Extending Lore</h2>
+ <ul>
+ <li>Easily done with some Python code</li>
+ <li>Input-enhancements decide which output formats to handle</li>
+ <li>Example: math-lore, Lore with LaTeX formulae</li>
+ </ul>
+
+ <h2>Extending Lore (cont'd)</h2>
+ <div class="pause" />
+ <ul>
+ <li>Another example: These slides!</li>
+ <li>The <code>lore-slides</code> plugin can output to
+ <ul>
+ <li>Magicpoint</li>
+ <li>HTML (one page per slide)</li>
+ <li>HTML (one big page)</li>
+ </ul>
+ </li>
+ </ul>
+
+ <h2>HTML Output</h2>
+ <ul>
+ <li>HTML is a flexible output format</li>
+ <li>Documents often have to integrate with a site</li>
+ <li>Lore produces HTML documents based on a template</li>
+ <li>Lore uses only HTML <code>class</code> attributes, never <code>font</code>
+ <ul>
+ <li>Plays nice with CSS</li>
+ </ul>
+ </li>
+ </ul>
+
+ <h2>Man Pages</h2>
+ <ul>
+ <li>Lore has a program to convert man pages to Lore documents</li>
+ <li>Man pages are written anyway</li>
+ <li>No man output: the format is too limited</li>
+ </ul>
+
+ <h2>Future Directions</h2>
+ <ul>
+ <li>More output formats</li>
+ <li>Some more classes -- abstract, bibliography</li>
+ <li>Index</li>
+ </ul>
+
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore.html b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore.html
new file mode 100644
index 0000000000..da71590d35
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/lore/lore.html
@@ -0,0 +1,791 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<title>The Lore Document Generation Framework</title>
+</head>
+
+<body>
+
+<h1>The Lore Document Generation Framework</h1>
+
+<ul>
+<li>Moshe Zadka
+ <a href="mailto:moshez@twistedmatrix.com">moshez@twistedmatrix.com</a></li>
+<li>Andrew Bennetts
+ <a href="mailto:spiv@twistedmatrix.com">spiv@twistedmatrix.com</a></li>
+</ul>
+
+<h2>Abstract</h2>
+
+<p>Lore is a documentation generation system which uses a limited subset
+of XHTML, together with some class attributes, as its source format. This
+allows for lower barrier of entry than many other similar systems, since HTML
+authoring tools are plentiful
+as is knowledge of HTML writing. As an added advantage, the source format
+is viewable directly, so that even if Lore is not available the documentation
+is useful. It currently outputs LaTeX and HTML, which allows for most
+use-cases.</p>
+
+<p>Lore is currently in use by the Twisted project to generate its
+documentation for versions 1.0.1 and above.</p>
+
+<h2>History</h2>
+
+<p>At the beginning of Twisted's life cycle, as with any self-respecting
+free software project, it came completely devoid of documentation. As
+Twisted progressed in maturity, the Twisted development team realized
+that documentation is necessary.</p>
+
+<p>Since at that time the Twisted development
+team did not want the overhead of integrating
+a full-scale document generation framework into its build infrastructure,
+documents were written for the least common denominator -- plain HTML.
+When the Twisted team wanted the documentation to be
+featured on the web site, it was desirable to have them integrated with
+the web site's look and feel. Thus, <code class="shell">generate-domdocs</code>
+was born as a simple XML-based command line hack which improved the look of the
+documents so they would share the look and feel of the other pages in the web
+site, including a standard header and footer. As
+<code class="shell">generate-domdocs</code>
+slowly grew more and more features, it gradually became too large to maintain.
+The authors, members of the Twisted development team, decided that in order to
+make it more maintainable, it should be refactored into a
+library and by the way also add alternate output formats. Some of the documents
+which were reluctant to be transformed into alternate formats were fixed,
+and guidelines for making compatible documents were drafted. Those documents,
+together with the conversion code, are the Lore documentation generation
+system.</p>
+
+<h2>Introduction</h2>
+
+<p>Lore is documentation generation system which is a part of the
+<a href="http://twistedmatrix.com">Twisted</a> framework. It uses
+the Twisted XML parsing framework
+(<code class="API" base="twisted.web">microdom</code>) to parse compliant XHTML
+and generate the various output formats from it.</p>
+
+<p>Lore consists of a Python package, <code class="API">twisted.lore</code>,
+and a command-line program: <code class="shell">lore</code>, which
+generates HTML output (which is more presentation-oriented than the source
+format), LaTeX or runs an linter, depending on command-line arguments.</p>
+
+<p>In the case where the default output of Lore is not exactly suited to a
+Lore user,
+it is possible to subclass the output generators and customize their behavior.
+This could be done for many purposes, from straight-forward additions like
+adding a new <code>span</code> or <code>div</code> class to advanced tweaking
+such as changing the way Lore does image conversion on LaTeX output.</p>
+
+<p>Lore uses reflection intensively to make adding new features as simple
+as adding a new method, without the need for awkward registration schemes.
+Thus, adding another check to the linter or letting
+Lore handle the <code>link</code> element in some way require only the addition
+of one method.</p>
+
+<h2>Goals</h2>
+
+<p>Lore was written when the Twisted team felt it needed to write documentation
+and looked for a documentation format. Looking through alternatives, the
+best one seemed to be the Python way, using LaTeX format and
+<code class="shell">latex2html</code>. However, the Python way has its share
+of problems, not the least of which is <code class="shell">latex2html</code>
+being a long and crufty Perl program whose Perl APIs, which are the
+only way to add support for custom markup, change every version.</p>
+
+<p>Since documentation writing is important, a documentation system with
+minimal impact on the writer would be desirable. While LaTeX certainly has
+very little impact in terms of markup overhead, it has a very big impact
+both in terms of installed base (installing LaTeX on UNIX systems or
+Windows is non-trivial at best) and in terms of familiarity.</p>
+
+<p>HTML has the benefit of being directly readable on every post-1995
+computer, so the installed base is as big as could be hoped for. It also has
+the benefit of being easily parsed, at least in its new XHTML guise.</p>
+
+<p>The goals of Lore were taken to be:</p>
+
+<ul>
+<li>Source files directly readable.</li>
+<li>At least output to modern (CSS-based) HTML.</li>
+<li>Easily parsed by third-parties.</li>
+</ul>
+
+<h2>Source Format</h2>
+
+<h3>Description</h3>
+
+<p>Lore's source format is a subset of XHTML; all Lore source documents are
+valid XHTML documents. The XHTML tags that Lore allows are:
+<code>html</code>, <code>title</code>, <code>head</code>, <code>body</code>,
+<code>h1</code>, <code>h2</code>, <code>h3</code>, <code>ol</code>,
+<code>ul</code>, <code>dl</code>, <code>li</code>, <code>dt</code>,
+<code>dd</code>, <code>p</code>, <code>code</code>, <code>img</code>,
+<code>blockquote</code>, <code>a</code>, <code>cite</code>, <code>div</code>,
+<code>span</code>, <code>strong</code>, <code>em</code>, <code>pre</code>,
+<code>q</code>, <code>table</code>, <code>tr</code>, <code>td</code>,
+<code>th</code> and <code>style</code>.
+</p>
+
+<p>We would like to stress the omission of the <code>font</code> tag (which is
+deprecated in HTML 4.01 anyway). Instead of using <code>font</code>, Lore
+mandates the use of stylesheets
+and the <code>class</code> attribute, and in particular Lore defines several
+classes, such as <code>footnote</code>, <code>API</code>,
+<code>py-listing</code>. The use of classes on <code>div</code> and
+<code>span</code> elements effectively allows XHTML to be arbitrarily
+extensible without needing to define custom tags.</p>
+
+<p>Further discouraging explicit style decision, Lore deprecates the
+<code>style</code> attribute which allowing HTML (and XHTML) authors to embed
+pieces of the stylesheet in the document. Though Lore properly processes
+such documents, they are against the specification of Lore -- and
+the Lore lint-like problem finder will complain.</p>
+
+<h3>Advantages and Disadvantages</h3>
+
+<p>Requiring XHTML rather than just HTML greatly simplifies the code to
+manipulate Lore source, because we can use standard XML libraries. For
+documentation authors, the difference is negligible -- and any mistakes made in
+balancing tags can be easily found using the linter.
+Since tag balancing problems, in many cases, cause a discrepancy between
+author intention and the result, it is better to balance the tags anyway.</p>
+
+<p>Like LaTeX, Lore encourages authors to focus on content, letting the
+presentation take care of itself. This is an inherently restrictive approach,
+but results in much more consistent and higher-quality output.</p>
+
+<p>The Lore source format is quite usable (if somewhat plain) as an end-format.
+Any web browser can read it, and it does not require special stylesheet support,
+JavaScript or any other modern HTML additions. It is also, as intended,
+straightforward to create and edit documents in this format.</p>
+
+<p>However, reading the source format directly has some major limitations,
+which are inherent in the combination of the facilities which render HTML
+and the requirement that the format will be easily writable, and easy to
+modify, using any standard text editor.
+The limitations include:</p>
+
+<ul>
+<li>There is no table of contents.</li>
+<li>Footnotes interrupt the flow of text (although stylesheet tricks can
+alleviate this to an extent).</li>
+<li>Python source is not syntax highlighted.</li>
+<li>File inclusions are implemented as hyper-links.</li>
+</ul>
+
+<h2>Output Formats</h2>
+
+<p>The two most important formats, for the end-user, are the computer screen and
+pages of print outs. Any other format should be first and foremost be thought
+of as a prelude to these final formats.</p>
+
+<p>The easiest computer-screen oriented format is HTML. However, the HTML
+which is most comfortable and useful to the end-user is not necessarily
+easy to write and modify.
+For example, it is painful to manually write a table of contents, and even more
+painful to keep it updated as sections are added, removed or changed. However,
+when reading a long document having a table of contents, with hyperlinks
+into the sections, is a boon.
+Thus, even though both Lore's source and one output format are HTML, an
+HTML to HTML conversion is still necessary, paradoxical though it may sound.</p>
+
+<p>For printable output, the most widely supported formats are PostScript
+and Portable Document Format. On UNIX systems PostScript is often preferred,
+since there are many tools for manipulating it and printing it (and PostScript
+printers are more common in the UNIX world). On Windows and Apple computers,
+Portable Document Format (PDF) is preferred because of the ease of installation
+of the necessary tools. Mac OS X, though being technically a UNIX, supports
+PDF natively.</p>
+
+<p>Directly generating PostScript or PDF, however, is hard. Since these formats
+are very low-level, the application generating them must do the hard work
+of calculating line breaks, guessing hyphenation points and deciding on fonts.
+Since these tasks are already implemented by LaTeX, Lore just generates LaTeX
+code and lets the user run LaTeX to generate PostScript and
+<code class="shell">ps2pdf</code> to generate PDF. Granted, this still causes
+the problems with the difficulties of installing LaTeX. It is
+possible to implement direct Lore to PDF converter, though this hasn't been
+done yet, by using <code>pdflib</code>.</p>
+
+<h3>HTML</h3>
+
+<p>The HTML to HTML converter works by running a series of transformations on
+the Document Object Model (DOM) tree of the parsed document, and then
+writing it out. The most important transformation is that of throwing
+away anything outside the <code>body</code> element, and putting the
+<code>body</code> element inside a template file. This allows large
+parts of the common layout code to be customized without modifying or writing
+any Python code.</p>
+
+<p>Each step is implemented as a separate function, to allow Lore-using
+Python programmers to customize which tree transformations to do in their
+own code, without forcing them to rewrite functionality in Lore. In addition,
+other output generators might perform a subset of these transformations
+on the input tree before processing it -- and indeed, this is being used
+even in Lore itself.</p>
+
+<p>One of the steps taken is caused by a need which is common in large
+Python frameworks: many of the class or module names are deeply nested,
+but are commonly referred to by just their last one or two components
+in writing. However, the user would like to know the full name of the
+class or module name, and where to look up the API documentation -- but
+without having the complete name thrust upon him during the flow of text
+each time the module is mentioned.</p>
+
+<p>Lore makes sure that each class or module name which is mentioned will
+appear at least once using its full name, and afterwards use a common
+short name, regardless of how the author wrote it up. This frees authors
+from needing to observe, manually, this useful rule in their documents.</p>
+
+<p>The HTML Lore outputs aims to be the poster boy of graceful degradation.
+Thus, for example, while footnotes always appear as hyper-links to the footnote
+text, browsers which respect the <code>title</code> attribute (which is usually
+rendered as a tooltip) will also show the beginning of the footnote while
+hovering above the hyper-link.</p>
+
+<p>Lore avoids using the <q>font</q> or <q>color</q> tags and attributes,
+preferring to use HTML classes and using a stylesheet to specify graphical
+design decisions. This allows the Lore user to customize the presentation of
+the output without touching Python code. Since most often the stylesheet
+link is found in the <code>head</code> element, this is determined by
+the by the template.</p>
+
+<p>Lore uses the same approach even for syntax-highlighting Python code,
+generating such elements as
+<code>&lt;span class="keyword"&gt;if&lt;/span&gt;</code>.</p>
+
+<h3>LaTeX</h3>
+
+<p>The LaTeX home page describes LaTeX as a <q>high-quality typesetting system,
+with features designed for the production of technical and scientific
+documentation.</q> LaTeX is very popular for generating printable content,
+building on Donald Knuth's TeX system to generate nearly optimal output
+by putting together much of the typesetting industry's experience in the
+form of a program and adding sophisticated algorithms for line-breaking and
+hyphenation.</p>
+
+<p>It is very common for document generation systems to avoid generating
+printable output themselves, instead letting LaTeX do the hard work, and
+Lore is no exception.</p>
+
+<p>Lore can output LaTeX in two modes: article mode, in which it generates
+a complete article ready to be be processed, and a section mode in which
+it generates a LaTeX file whose top-level element is a section. Such a file
+is usually included in some other LaTeX file via the include mechanism.
+Twisted itself uses mainly the section mode, and includes everything in the
+file <code class="shell">book.tex</code>, which is later processed to generate
+the Twisted book.</p>
+
+<p>While, conceivably, other modes could be done (a chapter mode or a subsection
+mode) there has not been any demand for those. In the case of demand, supplying
+these would be very few lines of Python code (less than 10), which can even
+be done by subclassing existing classes and avoiding the modification of Lore
+itself.</p>
+
+<h3>Docbook</h3>
+
+<p>Docbook output is currently experimental. Its chief use to Lore would
+be in generating Texinfo, which is the source for the GNU info documentation
+format.</p>
+
+<h2>Lint</h2>
+
+<p>Very early in the Lore development life-cycle it was found that a good
+Lint-like tool is necessary to find errors without necessitating a full
+compilation to all formats and sometimes even browsing the results. Because
+Lore was written to accommodate a large set of already existing documents
+(which were not previously checked for potential problems), such a tool
+was very useful so that finding a problem in one document would not mean
+this problem needs to be manually searched, and corrected, in all the other
+documents.</p>
+
+<p>Lore's linter tries to find problems in documents
+that would either stop the conversion to other formats by Lore completely
+(for example, by being not well-formed XML), or that would make it less useful
+(for example, by warning about tags or classes that are not supported by
+Lore).</p>
+
+<p>The linter even detects more exotic problems,
+including:</p>
+<ul>
+ <li><code>pre</code> elements containing lines over 80 characters. Long lines
+ can be ugly to render in some output formats, and even impossible to
+ render in others.</li>
+ <li>Explicit use of the <code>"</code> character in a non-pre or non-code
+ environment. This makes a big difference for high-quality typographical
+ output targets like LaTeX, which
+ have distinct left- and right-quote characters.</li>
+ <li>Python code that isn't syntactically valid, with a bit of magic to account
+ for this idiom:
+<pre class="python">
+for x in sequence:
+ ...
+</pre>
+ This check caught a surprisingly large number of errors in the Twisted
+ documentation!</li>
+ <li><code>h1</code> contents being equal to <code>title</code> contents.
+ HTML is somewhat unique in that it has two places to specify the logical
+ idea of <q>title</q>. Since other output formats do not support that,
+ in Lore papers, the contents of both must be the same.</li>
+</ul>
+
+<p>Since many of the incremental improvements done to Lore found a problem
+in the existing documentation files, the linter has been
+an important part of the Lore development effort. One may even argue that
+part of the reason other documentation generation systems produce suboptimal
+output for their <q>non-native</q> application is the lack of a linting
+tool.</p>
+
+<p>Finally, if the linter gives a false positive, that is
+it emits a warning for something that isn't a problem in a particular situation,
+the user can add an <code>hlint="off"</code> attribute to the offending tag, and
+the linter will ignore it. This is necessary only very rarely.</p>
+
+<p>The chief design decision made in the linter, after
+painful experience when running <code class="shell">tidy</code>, is that
+<em>it must never change the document</em>. Thus, while the linter
+will be as pedantic as possible finding
+errors, it never changes the contents. This is particularly important
+when dealing with version control systems, where spurious changes can
+render <code class="shell">diff</code> listings useless.</p>
+
+<h2>Features</h2>
+
+<h3>Python Syntax Highlighting</h3>
+
+<p>All existing syntax highlighters for Python used pre-<code>tokenize</code>
+techniques to analyse the Python code. As a result, they were cumbersome
+and non-standard. The Lore developers decided that writing a Python
+HTML syntax-highlighter would be easier than modifying one of the existing
+ones. A syntax-highlighter was built on top of a null-tokenizer: that is,
+a tokenizer which emits the <em>exact same</em> characters as the input.
+This allowed easy debugging of the parsing code.</p>
+
+<p>The only non-trivial code in the syntax highlighter is when dealing
+with whitespace which is not significant syntactically, since the tokenizer
+does not report it. However, since the tokenizer does report row and column,
+when the code sees a discrepancy between where the previous token ended
+and the current token starts, it adds whitespace to make up for
+the discrepancy.</p>
+
+<p>When writing out the HTML, the only difference between that and the
+null-tokenizer is the wrapping of each token by a <code>span</code>
+tag with the appropriate class and escaping.</p>
+
+<p>Note that the basic Python tokenizer does not distinguish between the
+various roles of the production <q>NAME</q> (that is, a string of alphanumeric
+and
+underscore characters starting with an underscore or a letter) in Python.
+The tokenizer Lore uses adds that information by having a simple state machine:
+if the word is a keyword, there is nothing to be determined; otherwise, it
+depends on the last detected name -- <code>class</code> or
+<code>def</code> mean it is a function or class names, and after a
+<code>class</code>/<code>def</code> and until a <code>:</code>, everything
+is a <q>parameter</q> or a superclass.</p>
+
+<p>The Python syntax highlighter Lore uses can be found in the
+<code class="API">twisted.python.htmlizer</code>.</p>
+
+<h3>File Inclusion</h3>
+
+<p>Often, when writing detailed documents, the author wishes to test his
+examples or even use examples from a working project. Pasting such examples
+directly into the HTML has both the usual problems of pasting code -- the
+version in the document will not benefit from bug fixes or enhancement to
+the original version -- and the problem that the HTML needs proper escaping,
+which is a tedious and error-prone procedure if done manually.
+Both problems are solved by Lore's <q>listing</q> mechanism. The
+<q>listing</q> mechanism converts HTML such as</p>
+
+<pre>
+&lt;a href="foo.py" class="py-listing&gt;foo.py&lt;/a&gt;
+</pre>
+
+<p>into inclusion of the <code class="shell">foo.py</code> file. It will always
+be properly escaped for whatever output format. It will
+also be syntax-highlighted, just as if it had been included verbatim.</p>
+
+<p>A similar class, <code>html-listing</code> is available for inclusion
+of HTML files.</p>
+
+<h3>API Reference Links</h3>
+
+<p>Twisted's documentation frequently references API documentation. In Lore,
+the name of an API such as
+<code class="API">twisted.internet.defer.Deferred</code> is marked up as</p>
+
+<pre>
+&lt;code class="API" base="twisted.internet.defer"&gt;Deferred&lt;/code&gt;
+</pre>
+
+<p>This will unambiguously link to
+<code class="API">twisted.internet.defer.Deferred</code>, even though it is
+displayed as
+<q><code class="API" base="twisted.internet.defer">Deferred</code></q>. Lore
+produces API links that work with
+<a href="http://epydoc.sourceforge.net">epydoc</a>,
+but could easily be adapted for another API documentation generator; in fact,
+Lore originally worked with happydoc.
+In addition, in the HTML output, Lore will add a <code>title</code>
+attribute to the API reference, containing the full name of the link.</p>
+
+<h3>Cross references</h3>
+
+<p>A collection of documents will typically refer to each other, for instance to
+avoid re-explaining some central concept. In HTML, cross-referencing
+is implemented as linking:</p>
+
+<pre>
+See &lt;a href="defer.html"&gt;Deferring Execution&lt;/a&gt;.
+</pre>
+
+<p>As a collection of HTML documents, this works with no changes. Other output
+formats do linking in other ways. When Lore is used to convert a collection of
+source HTML files into a single LaTeX book, each file is its own section, and
+the links are automatically converted into cross-references. Thus the example
+above might be rendered as <q>See Deferring Execution (page 163).</q></p>
+
+<p>Lore also recognizes <em>fragment identifiers</em> in links, so that a link
+to <code>glossary.html#psu</code> will be cross-referenced to that part of the
+glossary named <q>psu</q>, not just the whole glossary. This ensures that the
+page the reader is referred to is the correct one.</p>
+
+<h2>Man Support</h2>
+
+<p>Man pages are a fact of life on UNIX, and every self-respecting command
+line program is expected to come with one. The man format, implemented as
+troff macros, is somewhat arcane. Since, when Lore was written, we already
+had written man pages, the decision was to convert them to HTML rather than
+try to rewrite them in HTML and design a man output format.</p>
+
+<p>A limited parser for man pages is available in the
+<code class="API">twisted.lore.man2lore</code> module. It is not yet
+exposed via any public command line program.</p>
+
+<p>Earlier attempts, using <code class="shell">groff -Thtml</code> to
+generate HTML and then post-process it into Lore-compatible HTML
+were crufty and unmaintainable. It seems the man format shares some
+of LaTeX's problem: being written as a macro package over a powerful
+processor, it is too flexible for its own good. Fortunately, the subset
+normally used in man pages is quite small, so heuristically parsing man pages is
+much easier than the same task with LaTeX.</p>
+
+<h2>Comparisons</h2>
+
+<h3>HTML</h3>
+
+<p>HTML, when invented by Tim Berners-Lee, was meant to be a simple language
+for writing and sharing documents. With the explosion of the web, HTML has
+grown to a confusing jumble of logical and presentation features, with more
+layers, such as CSS, dumped on top of it. As a result, a modern browser is
+a complicated beast. That given, it is perhaps understandable that today's
+browsers do a sub-standard job at printing. Thus, while being extremely
+well suited to the world wide web, HTML is significantly lacking, at least
+in today's application market, when it comes to paper output. It might
+be possible to write an application to properly convert HTML with CSS to
+PostScript or PDF -- however, it would probably be much more complicated
+than Lore. Moreover, the portability of such an application would
+be worse of the portability of Lore itself, which currently only depends
+on Python 2.1 or higher and the Twisted framework.</p>
+
+<p>Limiting HTML to a small subset of features enables Lore to be small
+and readable while remaining useful. By including the <code>class</code>
+attribute among those features, Lore is also extensible.</p>
+
+<h3>LaTeX</h3>
+
+<p>When it comes to paper output, LaTeX cannot be out done except by a skilled
+typesetter designing and implementing. However, the architecture of LaTeX
+presents
+significant problems when trying to view LaTeX online. LaTeX is written
+as a macro layer above TeX rather than a preprocessor. Thus, all of TeX's
+power is available, and sometimes used, in LaTeX. TeX is non-trivial to
+parse and format by anyone short of Donald Knuth -- it contains such commands
+as to change the tokenizer by modifying which characters are considered
+word characters or even which character is the command character.
+In fact, the authors are not aware of any application which handles the
+full power of TeX without being based on the original TeX code.</p>
+
+<p>All this makes LaTeX extremely difficult to parse, and even partial attempts
+to parse LaTeX are big and cumbersome -- for example,
+<code class="shell">latex2html</code>. It is thus difficult to convert
+LaTeX to something appropriate to online viewing.</p>
+
+<h3>LyX</h3>
+
+<p>LyX's internal source format is not well documented, and the only supported
+way to write it is using the LyX GUI. Thus it is inherently limiting to
+documentation authors. In addition, it is not trivial to write LyX preprocessors
+to save documentation authors tedious work.</p>
+
+<h3>Docbook</h3>
+
+<p>Docbook is a big standard, with non-trivial to install tool-set. Writing
+Docbook is different than most other document generation formats, so it
+takes significant training to write. In addition, using Docbook for
+a specific project usually requires writing custom DSSSL stylesheets
+in a scheme-like language, and additional XML DTD snippets. Writing
+these was quite possibly comparable to writing Lore, and Lore has the advantage
+of being written in Python.</p>
+
+<h3>Texinfo</h3>
+
+<p>Texinfo imposes a significant effort on authors. Many things need to
+be written twice, and the error messages leave a lot to be desired.
+After starting to work on the Lore texinfo output format the authors
+are grateful they have never had to write Texinfo by hand.</p>
+
+<h2>Techniques</h2>
+
+<h3>Visitor Pattern</h3>
+
+<p>When generating LaTeX, Lore does it via a visitor pattern while visiting
+the nodes. A node which does not have a specific visitor is visited by
+first writing the <code>start_</code> attribute, then visiting its
+children and then writing the <code>end_</code> attribute. If the attributes
+do not exist, they are treated as though they were empty strings.</p>
+
+<p>That code allows most of the HTML elements to LaTeX converters to have no
+code -- only a pair of strings -- while the elements converters which need
+more sophisticated programming can do it via defining a method, which can
+still call the default processor if it needs this functionality.</p>
+
+<p>This pattern is also friendly to subclassing: all a subclass needs to
+do in order to change how an element is handled is to define either a pair
+of class attributes or a method.</p>
+
+<h3>Liberal Use of Reflection</h3>
+
+<p>In the above example of the visitor pattern, registration of the methods
+and attributes is avoided thanks to using the crudest form of reflection
+in Python -- the <code>getattr()</code> function.</p>
+
+<p>In the Lint support tool, more sophisticated reflection is needed when
+it needs to find all methods whose name begins with <code>check_</code>.
+This is done via the Twisted reflection code, built on top of the native
+Python facilities, in the module
+<code class="API">twisted.python.reflect</code>.</p>
+
+<h3>Recursively Searching For Elements</h3>
+
+<p>In the HTML output code, the most common operation is that of getting
+a list of elements which satisfy some property. This is done by one
+primary work-horse function:
+<code class="API">twisted.web.domhelpers.findNodes</code>. This function
+accepts a DOM tree and a function, and returns a list of all elements
+for which this function returns true. Using this, and the fact that Python makes
+it easy to combine functions into boolean combinations, makes analysis
+and modification and of the DOM tree a breeze.</p>
+
+<h2>Lessons Learned</h2>
+
+<h3>Problems With Some Output Formats</h3>
+
+<p>Probably the trickiest thing about non-HTML output formats is escaping.
+The problem comes from two annoying problems which are not really hard
+to solve, but do represent annoyances in the code:</p>
+
+<ul>
+<li>Different characters are escaped differently (for example, <code>\</code>
+ is escaped, in TeX, as <code>$\backslash$</code> while most other
+ characters are escaped as <code>\&lt;char&gt;</code>.</li>
+<li>Escaping depends on context -- special characters should not be escaped
+ at all inside <code>pre</code>, <code>&lt;/&gt;</code> should not be
+ escaped inside <code>code</code> and should be escaped as
+ <code>$&lt;$/$&gt;$</code> outside it.</li>
+</ul>
+
+<p>In Docbook, the sections are nested, so there is only need for
+a <code>title</code> element. However, in HTML only the headers care
+at which level they are. This requires the Docbook converter to keep
+the last header level and when it reaches a new header, to close and open
+enough sections so the header will get to the correct level. While Docbook's
+way may be more <q>correct</q>, it is unfortunate it chose to diverge from
+all other systems here.</p>
+
+<p>Texinfo requires all the sections in a document will have unique names.
+This makes it very inconvenient as both an input and an output format.</p>
+
+<p>Also, differing significance of whitespace in different formats requires that
+all whitespace emitted by lore must be normalized for the particular output
+format being used. Blank lines which have no impact on HTML will trigger
+paragraph breaks in LaTeX.</p>
+
+<h3>Event-based XML Parsing Considered Harmful</h3>
+
+<p>The first version of the LaTeX output generator was using an event-based
+XML parsing engine. It quickly turned out one needs to keep a lot of
+information in stacks and manage many instance variables. For example,
+though XML gets the name of the closing element (even that is arguably
+too much information), it does not get the attributes. In <code>span</code>
+elements, for example, the interesting information is the <code>class</code>
+attribute. Since a-priory, <code>span</code>s might be nested, the class
+needs to keep a stack of attribute collections.</p>
+
+<p>Quite soon, stacks were needed for proper handling of <code>div</code>
+tags and for determining proper quoting formats. Moreover, getting the
+code to function correctly in the face of edge cases, such as cross-references
+inside <code>pre</code> tags, proved to be quite a challenge.</p>
+
+<p>The code was shortened, simplified and became more maintainable when
+it was moved to <code class="API" base="twisted.web">microdom</code>.</p>
+
+<p>We feel that unless there is
+an inherent reason to do XML event-based parsing, then it is much easier
+to read the whole thing into a DOM and then process it. The code is both
+shorter and clearer, and features are much easier to add.</p>
+
+<h3>Allow Easy Modification</h3>
+
+<p>Lore, out of the box, does not attempt to be all things to all people.
+Particularly in the LaTeX output format, there is a lot of room for
+interpretation and personal preferences. Lore chose one specific way, without
+trying to add half a dozen options to tweak it. However, thanks to the
+way it is coded, it is easy to add or modify features to suit individual
+preferences. Many customizations only involve adding or overriding simple data
+attributes to a subclass; more advanced changes require adding or overriding
+methods.</p>
+
+<p>Likewise, the HTML output is built by running several tree-modification
+functions which are independent. Completely different HTML output could
+be build by adding more functions, or not running some of those which
+are being run.</p>
+
+<p>We already know of multiple users that have extended Lore for custom LaTeX
+generation. In each case it was a simple matter of subclassing Lore's LaTeX
+code.</p>
+
+<h3>Reinventing Wheels Can Be Useful</h3>
+
+<p>Documentation generation systems were already a solved problem before Lore
+was written. However, we know of no system with Lore's unique combination of
+features -- in particular, portability, having a directly readable source format
+which is also directly writable in text editors.
+The common wisdom that a documentation generation
+system is a hard sell because it requires people to learn a new language was
+refuted by using an existing language.</p>
+
+<p>Wheel reinvention also occurred in a nearby area -- Twisted's XML support,
+for which Lore is one of the biggest users. Again, the common wisdom was that
+this was a solved problem, with many existing DOM and SAX implementations.
+However, implementation of some features, no implementation of other features
+and API instability have lead the Twisted team to write its own, highly
+pythonic, DOM-like implementation. In
+<code class="API" base="twisted.web">microdom</code>, the aim is to be
+as thin a wrapper over the basic Python wrappers as possible. This feature
+has been used to the full in Lore, where many of the tree manipulations
+would have been much more cumbersome had a standard <q>opaque</q> DOM
+implementation been used. In addition, using
+<code class="API" base="twisted.web">microdom</code> frees Lore from the
+dependence on both Python version and whether PyXML is installed.</p>
+
+<p>For example, <code class="API" base="twisted.web">microdom</code>
+exposes the list of child nodes as a plain Python lists. This means that
+not only all the list operations can be done of it, which could possibly
+be simulated by a list-like object, but that it is possible to
+<em>replace</em> it by our own list. As another example,
+<code class="API" base="twisted.web">microdom</code> allows us to freely
+copy nodes from one DOM tree into another.</p>
+
+<p>Python, as a language well suited to rapid application development,
+acts as a way to make wheel reinvention far from the horrible mistake
+which is portrayed in the common software engineering folklore. Indeed, Python
+makes it easy enough to reinvent wheels that only the best, and easy to
+use, wheels, get reused at all.</p>
+
+<h2>Availability</h2>
+
+<p>Lore can be found in Twisted 1.0.1 and higher, in the
+<code class="API">twisted.lore</code> package. When you install the package,
+the relevant script, <code class="shell">lore</code>,
+should be installed in a sane directory,
+as determined by distutils.</p>
+
+<p>For usage examples, see <code class="shell">admin/release-twisted</code>
+in the Twisted source distribution. It runs the various Lore scripts
+as part of the package build.</p>
+
+<h2>Future Plans</h2>
+
+<h3>More Output Formats</h3>
+
+<p>It would be nice to have the Docbook output fully working. It would also
+be nice to have Texinfo in full working order so that GNU info aficionados could
+read the documents with the info browser. As suggested above, it might
+also be useful to have a way to directly generate PDF output via
+<code>pdflib</code> in order to skip LaTeX.</p>
+
+<p>In addition, another potential output format is to have high-quality
+text output. This is non-trivial, but possibly useful: browsers'
+<q>Save as text</q> feature is usually implemented as an afterthought,
+and hardly uses the flexibility available in the text format to its
+full power. The authors are unaware, for example, of an HTML to text
+converter which uses the underlining with <q>=</q> sign or <q>-</q>
+to indicate a header, or which uses the <code>/slant/</code> or
+<code>*asterisk*</code> conventions to indicate emphasis.</p>
+
+<p>Another output format we are considering is a split-page HTML with
+interlinks, so that long documents can be converted into something
+which is web-friendly. One nice use for that would be in web-based
+presentations.</p>
+
+<h3>Image Conversion</h3>
+
+<p>Currently all images are converted to EPS format. It would be nice to have
+the LaTeX converter try to see if there is already an EPS version, via some
+naming convention, and use that. This would allow better scaling of things like
+Dia diagrams. The versions in bitmap-based formats (such as PNG)
+are impossible to scale, because the text would become unreadable.</p>
+
+<h3>Interface</h3>
+
+<p>Currently, the only interface to Lore is through the command-line, and
+even that is somewhat spotty: for example, the man page parser is not directly
+available via the command line. We hope to remedy that, having at least a full
+suite of command-line tools and possibly graphical wrappers, particularly
+EMACS modes.</p>
+
+<h2>Twisted Integration</h2>
+
+<p>When starting with a historical note, it is only fitting to end
+with a historical note. Since the writing of Lore, Twisted documentation
+is successfully generated by it and distributed in the tarball. It contains
+generated HTML from the HOWTO documents, specifications and man pages.
+It also contains all these documents inside a LaTeX-generated PostScript
+file and PDF file in an easy to print format, suitable for reading on those
+long plane flights or train rides.</p>
+
+<p>Lore is also used to generate pages with consistent headers and footers for
+the twistedmatrix.com web site -- not just the Twisted documentation.
+This is shows the inherent flexibility in Lore's model of being easily
+configurable via an HTML template,
+a feature which none of the major
+document generation systems support for their HTML output.</p>
+
+<h2>Further Resources</h2>
+
+<ul>
+ <li><a
+ href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.0.3/man/lore-man.xhtml"><code
+ class="shell">lore(1)</code> man page</a></li>
+ <li><a
+ href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.0.3/howto/doc-standard">Lore guidelines</a></li>
+ <li><a
+ href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.0.3/howto/lore">Lore HOWTO</a></li>
+ <li><a
+ href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.0.1/examples/example.html">Skeleton Lore document</a></li>
+ <li>The <a
+ href="http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/~checkout~/doc/howto/stylesheet.css?rev=1.16&amp;content-type=text/css&amp;cvsroot=Twisted">stylesheet</a> and <a
+ href="http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/~checkout~/doc/howto/template.tpl?rev=1.6&amp;content-type=text/plain&amp;cvsroot=Twisted">template</a> used by the Twisted documentation</li>
+ <li><a href="http://www.w3.org/TR/xhtml1/">The XHTML specification</a></li>
+ <li><a href="http://www.latex-project.org">LaTeX project home page</a></li>
+ <li><a href="http://www.lyx.org">LyX</a></li>
+ <li><a href="http://www.docbook.org">Docbook</a></li>
+ <li><a href="http://www.python10.com/p10-papers/09/index.htm">Zadka, Moshe and Lefkowitz, Glyph, The Twisted Network Framework, The Tenth International Python Conference Proceedings</a></li>
+</ul>
+
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-client1.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-client1.py
new file mode 100755
index 0000000000..7814fb74d5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-client1.py
@@ -0,0 +1,46 @@
+#! /usr/bin/python
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Client:
+ def connect(self):
+ deferred = pb.getObjectAt("localhost", 8800, 30)
+ deferred.addCallbacks(self.got_obj, self.err_obj)
+ # when the Deferred fires (i.e. when the connection is established and
+ # we receive a reference to the remote object), the 'got_obj' callback
+ # will be run
+
+ def got_obj(self, obj):
+ print "got object:", obj
+ self.server = obj
+ print "asking it to add"
+ def2 = self.server.callRemote("add", 1, 2)
+ def2.addCallbacks(self.add_done, self.err)
+ # this Deferred fires when the method call is complete
+
+ def err_obj(self, reason):
+ print "error getting object", reason
+ self.quit()
+
+ def add_done(self, result):
+ print "addition complete, result is", result
+ print "now trying subtract"
+ d = self.server.callRemote("subtract", 5, 12)
+ d.addCallbacks(self.sub_done, self.err)
+
+ def err(self, reason):
+ print "Error running remote method", reason
+ self.quit()
+
+ def sub_done(self, result):
+ print "subtraction result is", result
+ self.quit()
+
+ def quit(self):
+ print "shutting down"
+ reactor.stop()
+
+c = Client()
+c.connect()
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-server1.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-server1.py
new file mode 100755
index 0000000000..c0fb43fbe9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-server1.py
@@ -0,0 +1,16 @@
+#! /usr/bin/python
+
+from twisted.spread import pb
+import twisted.internet.app
+
+class ServerObject(pb.Root):
+ def remote_add(self, one, two):
+ answer = one + two
+ print "returning result:", answer
+ return answer
+ def remote_subtract(self, one, two):
+ return one - two
+
+app = twisted.internet.app.Application("server1")
+app.listenTCP(8800, pb.BrokerFactory(ServerObject()))
+app.run(save=0)
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-slides.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-slides.py
new file mode 100755
index 0000000000..1a9aea6dd6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb-slides.py
@@ -0,0 +1,240 @@
+#! /usr/bin/python
+
+from slides import Lecture, NumSlide, Slide, Bullet, SubBullet, PRE, URL
+
+class Raw:
+ def __init__(self, title, html):
+ self.title = title
+ self.html = html
+ def toHTML(self):
+ return self.html
+
+class HTML(Raw):
+ def __init__(self, html):
+ self.html = html
+
+server_lore = """<div class="py-listing">
+<pre><span class="py-src-keyword">class</span> <span class="py-src-identifier">ServerObject</span><span class="py-src-op">(</span><span class="py-src-parameter">pb</span><span class="py-src-op">.</span><span class="py-src-parameter">Referenceable</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"></span>
+<span class="py-src-indent"> </span><span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_add</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">one</span><span class="py-src-op">,</span> <span class="py-src-parameter">two</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
+</span><span class="py-src-indent"> </span><span class="py-src-variable">answer</span> <span class="py-src-op">=</span> <span class="py-src-variable">one</span> <span class="py-src-op">+</span> <span class="py-src-variable">two</span><span class="py-src-newline">
+</span> <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;returning result:&quot;</span><span class="py-src-op">,</span> <span class="py-src-variable">answer</span><span class="py-src-newline">
+</span> <span class="py-src-keyword">return</span> <span class="py-src-variable">answer</span><span class="py-src-endmarker"></span></pre>
+<div class="py-caption">Server Code</div>
+</div>
+"""
+
+client_lore = """<div class="py-listing"><pre>
+<span class="py-src-nl"></span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">got_RemoteReference</span><span class="py-src-op">(</span><span class="py-src-parameter">remoteref</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
+</span> <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;asking it to add&quot;</span><span class="py-src-newline">
+</span> <span class="py-src-variable">deferred</span> <span class="py-src-op">=</span> <span class="py-src-variable">remoteref</span><span class="py-src-op">.</span><span class="py-src-variable">callRemote</span><span class="py-src-op">(</span><span class="py-src-string">&quot;add&quot;</span><span class="py-src-op">,</span> <span class="py-src-number">1</span><span class="py-src-op">,</span> <span class="py-src-number">2</span><span class="py-src-op">)</span><span class="py-src-newline">
+</span> <span class="py-src-variable">deferred</span><span class="py-src-op">.</span><span class="py-src-variable">addCallbacks</span><span class="py-src-op">(</span><span class="py-src-variable">add_done</span><span class="py-src-op">,</span> <span class="py-src-variable">err</span><span class="py-src-op">)</span><span class="py-src-newline">
+</span> <span class="py-src-comment"># this Deferred fires when the method call is complete
+</span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">add_done</span><span class="py-src-op">(</span><span class="py-src-parameter">result</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
+</span><span class="py-src-indent"> </span><span class="py-src-keyword">print</span> <span class="py-src-string">&quot;addition complete, result is&quot;</span><span class="py-src-op">,</span> <span class="py-src-variable">result</span><span class="py-src-newline">
+</span><span class="py-src-endmarker"></span></pre><div class="py-caption">Client Code</div></div>
+"""
+
+
+# title graphic: PB peanut butter jar, "Twist(ed)" on lid
+lecture = Lecture(
+ "Perspective Broker: Translucent RPC in Twisted",
+ # intro
+ Raw("Title", """
+ <h1>Perspective Broker: Translucent RPC in Twisted</h1>
+ <h2>PyCon 2003</h2>
+ <h2>Brian Warner &lt; warner @ lothar . com &gt; </h2>
+ """),
+
+ Slide("Introduction",
+ Bullet("Overview/definition of RPC"),
+ Bullet("What is Perspective Broker?"),
+ Bullet("How do I use it?"),
+ Bullet("Security Issues"),
+ Bullet("Future Directions"),
+ ),
+
+ Slide("Remote Procedure Calls",
+ Bullet("Action at a distance: separate processes, safely telling each other what to do",
+ SubBullet("Separate memory spaces"),
+ SubBullet("Usually on different machines"),
+ ),
+ Bullet("Frequently called RMI these days: Remote Method Invocation"),
+ Bullet("Three basic parts: Addressing, Serialization, Waiting"),
+ ),
+
+ Slide("Addressing",
+ Bullet("What program are you talking to?",
+ SubBullet("hostname, port number"),
+ SubBullet("Some systems use other namespaces: sunrpc")
+ ),
+ Bullet("Which object in that program?"),
+ Bullet("Which method do you want to run?"),
+ Bullet("Related issues",
+ SubBullet("How do you know what the arguments are?"),
+ SubBullet("(do you care?)"),
+ SubBullet("How do you know what methods are available?"),
+ SubBullet("(do you care?)"),
+ ),
+ ),
+
+ Slide("Serialization",
+ Bullet("What happens to the arguments you send in?"),
+ Bullet("What happens to the results that are returned?",
+ SubBullet("Representation differences: endianness, word length"),
+ SubBullet("Dealing with user-defined types"),
+ ),
+ Bullet("How to deal with references"),
+ ),
+ Slide("The Waiting (is the hardest part)",
+ Bullet("Asynchronous: results come later, or not at all"),
+ Bullet("Need to do other work while waiting"),
+ ),
+
+ Slide("Whither Translucence?",
+ Bullet("Not 'Transparent': don't pretend remote objects are really local",
+ SubBullet("CORBA (in C) does this, makes remote calls look like local calls"),
+ SubBullet("makes it hard to deal with the async nature of RPC"),
+ ),
+ Bullet("Not 'Opaque': make it easy to deal with the differences",
+ SubBullet("Including extra failure modes, delayed results"),
+ ),
+
+ Bullet("Exceptions and Deferreds to the rescue")),
+
+ Slide("Other RPC protocols",
+ Bullet("HTML"),
+ Bullet("XML-RPC"),
+ Bullet("CORBA"),
+ Bullet("when you control both ends of the wire, use PB"),
+ ),
+
+ Raw("Where does PB fit?",
+ """<h2>PB sits on top of <span class=\"py-src-identifier\">twisted.internet</span></h2>
+ <img src=\"twisted-overview.png\" />
+ """),
+
+ Slide("pb.RemoteReference",
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.Referenceable</span>: Object which can be accessed by remote systems."),
+ SubBullet(HTML("Defines methods like <span class=\"py-src-identifier\">remote_foo</span> and <span class=\"py-src-identifier\">remote_bar</span> which can be invoked remotely.")),
+ SubBullet(HTML("Methods without the <span class=\"py-src-identifier\">remote_</span> prefix are local-only.")),
+ ),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.RemoteReference</span>: Used by distant program to invoke methods."),
+ SubBullet(HTML("Offers <span class=\"py-src-identifier\">.callRemote()</span> to trigger remote method on a corresponding <span class=\"py-src-identifier\">pb.Referenceable</span>.")),
+ ),
+ ),
+
+ Raw("Sample code",
+ "<h2>Sample Code</h2>" + server_lore + client_lore),
+ #Slide("Simple Demo"),
+ # "better demo: manhole, or reactor running in another thread"
+
+ #build up from callRemote?
+ Slide("What happens to those arguments?",
+ Bullet("Basic structures should travel transparently",
+ SubBullet("Actually quite difficult in some languages"),
+ ),
+ Bullet("Object graph should remain the same",
+ SubBullet("Serialization context"),
+ SubBullet("(same issues as Pickle)")),
+ Bullet("Instances of user-defined classes require more care",
+ SubBullet("User-controlled unjellying"),)
+ ),
+
+ #serialization (skip banana)
+ Slide("40% More Sandwich Puns Than The Leading Brand",
+ Bullet("twisted.spread: python package holding other modules"),
+ Bullet("PB: remote method invocation"),
+ Bullet("Jelly: mid-level object serialization"),
+ Bullet("Banana: low-level serialization of s-expressions"),
+ Bullet("Taster: security context, decides what may be received"),
+ Bullet("Marmalade: like Jelly, but involves XML, so it's bitter"),
+ Bullet("better than the competition",
+ SubBullet("CORBA: few or no sandwich puns"),
+ SubBullet("XML-RPC: barely pronounceable"),
+ ),
+ ),
+
+ Slide("Jellying objects",
+ Bullet("'Jellying' vs 'Unjellying'"),
+ Bullet("Immutable objects are copied whole"),
+ Bullet("Mutable objects get reference IDs to insure shared references remain shared",
+ SubBullet("(within the same Jellying context)")),
+ ),
+
+ Slide("Jellying instances",
+ Bullet(HTML("User classes inherit from one of the <span class=\"py-src-identifier\">pb.flavor</span> classes")),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.Referenceable</span>: methods can be called remotely")),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.Copyable</span>: contents are selectively copied")),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.Cacheable</span>: contents are copied and kept up to date")),
+ Bullet(HTML("Classes define <span class=\"py-src-identifier\">.getStateToCopy</span> and other methods to restrict exported state")),
+ ),
+
+ Slide("pb.Copyable example",
+ PRE("""class SenderPond(FrogPond, pb.Copyable):
+ def getStateToCopy(self):
+ d = self.__dict__.copy()
+ d['frogsAndToads'] = d['numFrogs'] + d['numToads']
+ del d['numFrogs']
+ del d['numToads']
+ return d
+
+class ReceiverPond(pb.RemoteCopy):
+ def setCopyableState(self, state):
+ self.__dict__ = state
+ self.localCount = 12
+ def count(self):
+ return self.frogsAndToads
+
+pb.setUnjellyableForClass(SenderPond, ReceiverPond)
+""")),
+
+ Slide("Secure Unjellying",
+ Bullet("Pickle has security problems",
+ SubBullet("Pickle will import any module the sender requests."),
+ SubBullet(HTML("2.3 gave up, removed safety checks like <span class=\"py-src-identifier\">__safe_for_unpickling__</span> .")),
+ ),
+ Bullet("Jelly attempts to be safe in the face of hostile clients",
+ SubBullet("All classes rejected by default"),
+ SubBullet(HTML("<span class=\"py-src-identifier\">registerUnjellyable()</span> used to accept safe ones")),
+ SubBullet(HTML("Registered classes define <span class=\"py-src-identifier\">.setCopyableState</span> and others to process remote state")),
+ ),
+ Bullet("Must mark (by subclassing) to transmit"),
+ ),
+
+ Slide("Transformation of references in transit",
+ Bullet("All referenced objects get turned into their counterparts as they go over the wire"),
+ Bullet("References are followed recursively",
+ SubBullet("Sending a reference to a tree of objects will cause the whole thing to be transferred"),
+ SubBullet("(subject to security restrictions)"),
+ ),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.flavors</span> get reference ids"),
+ SubBullet("They are recognized when they return, transformed into the original reference"),
+ SubBullet("Reference ids are scoped to the connection"),
+ SubBullet("One side-effect: no 'third party' references"),
+ ),
+ ),
+
+ Slide("Perspectives: pb.cred and the Identity/Service model",
+ Bullet("A layer to provide common authentication services to Twisted applications"),
+ Bullet(HTML("<span class=\"py-src-identifier\">Identity</span>: named user accounts with passwords")),
+ Bullet(HTML("<span class=\"py-src-identifier\">Service</span>: something a user can request access to")),
+ Bullet(HTML("<span class=\"py-src-identifier\">Perspective</span>: user accessing a service")),
+ Bullet(HTML("<span class=\"py-src-identifier\">pb.Perspective</span>: first object, a <span class=\"py-src-identifier\">pb.Referenceable</span> used to access everything else")),
+ ),
+ #picture would help
+
+ Slide("Future directions",
+ Bullet("Other language bindings: Java, elisp, Haskell, Scheme, JavaScript, OCaml"),
+ # donovan is doing the JavaScript port
+ Bullet("Other transports: UDP, Airhook"),
+ Bullet("Componentization"),
+ Bullet("Performance improvements: C extension for Jelly"),
+ Bullet("Refactor addressing model: PB URLs"),
+ ),
+
+ Slide("Questions", Bullet("???")),
+
+ )
+
+lecture.renderHTML("slides", "slide-%02d.html", css="stylesheet.css")
+
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb.html b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb.html
new file mode 100644
index 0000000000..95f1ebea45
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/pb/pb.html
@@ -0,0 +1,966 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Perspective Broker: <q>Translucent</q> Remote Method calls in Twisted</title>
+ </head>
+
+<body>
+
+<h1>Perspective Broker: <q>Translucent</q> Remote Method calls in Twisted</h1>
+
+<ul>
+<li><a href="http://www.lothar.com">Brian Warner</a>:
+<code>&lt;warner@lothar.com&gt;</code>
+</li>
+</ul>
+
+<h2>Abstract</h2>
+
+<p>One of the core services provided by the Twisted networking framework is
+<q>Perspective Broker</q>, which provides a clean, secure, easy-to-use
+Remote Procedure Call (RPC) mechanism. This paper explains the novel
+features of PB, describes the security model and its implementation, and
+provides brief examples of usage.</p>
+
+<p>PB is used as a foundation for many other services in Twisted, as well as
+projects built upon the Twisted framework. twisted.web servers can delegate
+responsibility for different portions of URL-space by distributing PB
+messages to the object that owns that subspace. twisted.im is an
+instant-messaging protocol that runs over PB. Applications like CVSToys and
+the BuildBot use PB to distribute notices every time a CVS commit has
+occurred. Using Perspective Broker as the RPC layer allows these projects to
+stay focused on the interesting parts.</p>
+
+<p>The PB protocol is not limited to Python. There is a working Java
+implementation available from the Twisted web site, as is an Emacs-Lisp
+version (which can be used to control a PB-enabled application from within
+your editing session, or effectively embed a Python interpreter in Emacs).
+Python's dynamic and introspective nature makes Perspective Broker easier to
+implement (and very convenient to use), but neither are strictly necessary.
+With a set of callback tables and a good dictionary implementation, it would
+be possible to implement the same protocol in C, C++, Perl, or other
+languages.</p>
+
+<h2>Overview</h2>
+
+<h3>Features</h3>
+
+<p>Perspective Broker provides the following basic RPC features.</p>
+
+<ul>
+ <li><strong>remotely-invokable methods</strong>: certain methods (those
+ with names that start with <q>remote_</q>) of
+ <code>pb.Referenceable</code> objects can be invoked by remote clients who
+ hold matching <code>pb.RemoteReference</code> objects.</li>
+
+ <li><strong>transparent, controllable object serialization</strong>: other
+ objects sent through those remote method invocations (either as arguments
+ or in the return value) will be automatically serialized. The data that is
+ serialized, and the way they are represented on the remote side, depends
+ upon which <code>twisted.pb.flavor</code> class they inherit from, and
+ upon overridable methods to get and set state.</li>
+
+ <li><strong>per-connection object ids</strong>: certain objects that are
+ passed by reference are tracked when they are sent over a wire. If the
+ receiver sends back the reference it received, the sender will see their
+ original object come back to them.</li>
+
+ <li><strong>twisted.cred authentication layer</strong>: provides common
+ username/password verification functions. <code>pb.Viewable</code> objects
+ keep a user reference with them, so remotely-invokable methods can find
+ out who invoked them.</li>
+
+ <li><strong>remote exception reporting</strong>: exceptions that occur in
+ remote methods are wrapped in <code>Failure</code> objects and serialized
+ so they can be provided to the caller. All the usual traceback information
+ is available on the invoking side.</li>
+
+ <li><strong>runs over arbitrary byte-pipe transports</strong>: including
+ TCP, UNIX-domain sockets, and SSL connections. UDP support (in the form of
+ Airhook) is being developed.</li>
+
+ <li><strong>numerous sandwich-related puns</strong>: PB, Jelly, Banana,
+ <code>twisted.spread</code>, Marmalade, Tasters, and Flavors. By contrast,
+ CORBA and XML-RPC have few, if any, puns in their naming conventions.</li>
+
+</ul>
+
+<h3>Example</h3>
+
+<p>Here is a simple example of PB in action. The server code creates an
+object that can respond to a few remote method calls, and makes it available
+on a TCP port. The client code connects and runs two methods.</p>
+
+<a href="pb-server1.py" class="py-listing" skipLines="2">pb-server1.py</a>
+<a href="pb-client1.py" class="py-listing" skipLines="2">pb-client1.py</a>
+
+<p>When this is run, the client emits the following progress messages:</p>
+
+<pre class="shell">
+% <em>./pb-client1.py</em>
+got object: &lt;twisted.spread.pb.RemoteReference instance at 0x817cab4&gt;
+asking it to add
+addition complete, result is 3
+now trying subtract
+subtraction result is -7
+shutting down
+</pre>
+
+<p>This example doesn't demonstrate instance serialization, exception
+reporting, authentication, or other features of PB. For more details and
+examples, look at the PB <q>howto</q> docs at <a
+href="http://twistedmatrix.com/documents/howto/">twistedmatrix.com</a>.</p>
+
+<h2>Why <q>Translucent</q> References?</h2>
+
+<p>Remote function calls are not the same as local function calls. Remote
+calls are asynchronous. Data exchanged with a remote system may be
+interpreted differently depending upon version skew between the two systems.
+Method signatures (number and types of parameters) may differ. More failure
+modes are possible with RPC calls than local ones.</p>
+
+<p><q>Transparent</q> RPC systems attempt to hide these differences, to make
+remote calls look the same as local ones (with the noble intention of making
+life easier for programmers), but the differences are real, and hiding them
+simply makes them more difficult to deal with. PB therefore provides
+<q>translucent</q> method calls: it exposes these differences, but offers
+convenient mechanisms to handle them. Python's flexible object model and
+exception handling take care of part of the problem, while Twisted's
+Deferred class provides a clean way to deal with the asynchronous nature of
+RPC.</p>
+
+<h3>Asynchronous Invocation</h3>
+
+<p>A fundamental difference between local function calls and remote ones is
+that remote ones are always performed asynchronously. Local function calls
+are generally synchronous (at least in most programming languages): the
+caller is blocked until the callee finishes running and possibly returns a
+value. Local functions which might block (loosely defined as those which
+would take non-zero or indefinite time to run on infinitely fast hardware)
+are usually marked as such, and frequently provide alternative APIs to run
+in an asynchronous manner. Examples of blocking functions are
+<code>select()</code> and its less-generalized cousins:
+<code>sleep()</code>, <code>read()</code> (when buffers are empty), and
+<code>write()</code> (when buffers are full).</p>
+
+<p>Remote function calls are generally assumed to take a long time. In
+addition to the network delays involved in sending arguments and receiving
+return values, the remote function might itself be blocking.</p>
+
+<p><q>Transparent</q> RPC systems, which pretend that the remote system is
+really local, usually offer only synchronous calls. This prevents the
+program from getting other work done while the call is running, and causes
+integration problems with GUI toolkits and other event-driven
+frameworks.</p>
+
+<h3>Failure Modes</h3>
+
+<p>In addition to the usual exceptions that might be raised in the course of
+running a function, remotely invoked code can cause other errors. The
+network might be down, the remote host might refuse the connection (due to
+authorization failures or resource-exhaustion issues), the remote end might
+have a different version of the code and thus misinterpret serialized
+arguments or return a corrupt response. Python's flexible exception
+mechanism makes these errors easy to report: they are just more exceptions
+that could be raised by the remote call. In other languages, this requires a
+special API to report failures via a different path than the normal
+response.</p>
+
+<h3>Deferreds to the rescue</h3>
+
+<p>In PB, Deferreds are used to handle both the asynchronous nature of the
+method calls and the various kinds of remote failures that might occur. When
+the method is invoked, PB returns a Deferred object that will be fired
+later, when the response (success or failure) is received from the remote
+end. The caller (the one who invoked <code>callRemote</code>) is free to
+attach callback and errback handlers to the Deferred. If an exception is
+raised (either by the remote code or a network failure during processing),
+the errback will be run with the wrapped exception. If the function
+completes normally, the callback is run.</p>
+
+<p>By using Deferreds, the invoking program can get other work done while it
+is waiting for the results. Failure is handled just as cleanly as
+success.</p>
+
+<p>In addition, the remote method can itself return a <code>Deferred</code>
+instead of an actual return value. When that <code>Deferreds</code> fires,
+the data given to the callback will be serialized and returned to the
+original caller. This allows the remote server to perform other work as
+well, putting off the answer until one is available.</p>
+
+
+<h2>Calling Remote Methods</h2>
+
+<p>Perspective Broker is first and foremost a mechanism for remote method
+calls: doing something to a local object which causes a method to get run on
+a distant one. The process making the request is usually called the
+<q>client</q>, and the process which hosts the object that actually runs the
+method is called the <q>server</q>. Note, however, that method requests can
+go in either direction: instead of distinguishing <q>client</q> and
+<q>server</q>, it makes more sense to talk about the <q>sender</q> and
+<q>receiver</q> for any individual method call. PB is symmetric, and the
+only real difference between the two ends is that one initiated the original
+TCP connection and the other accepted it.</p>
+
+<p>With PB, the local object is an instance of
+<code>twisted.spread.pb.RemoteReference</code>, and you <q>do something</q>
+to it by calling its <code>.callRemote</code> method. This call accepts a
+method name and an argument list (including keyword arguments). Both are
+serialized and sent to the receiving process, and the call returns a
+<code>Deferred</code>, to which you can add callbacks. Those callbacks will
+be fired later, when the response returns from the remote end.</p>
+
+<p>That local RemoteReference points at a
+<code>twisted.spread.pb.Referenceable</code> object living in the other
+program (or one of the related callable flavors). When the request comes
+over the wire, PB constructs a method name by prepending
+<code>remote_</code> to the name requested by the remote caller. This method
+is looked up in the <code>pb.Referenceable</code> and invoked. If an
+exception is raised (including the <code>AttributeError</code> that results
+from a bad method name), the error is wrapped in a <code>Failure</code>
+object and sent back to the caller. If it succeeds, the result is serialized
+and sent back.</p>
+
+<p>The caller's Deferred will either have the callback run (if the method
+completed normally) or the errback run (if an exception was raised). The
+Failure object given to the errback handler allows a full stack trace to be
+displayed on the calling end.</p>
+
+<p>For example, if the holder of the <code>RemoteReference</code> does <code
+class="python">rr.callRemote("foo", 1, 3)</code>, the corresponding
+<code>Referenceable</code> will be invoked with <code
+class="python">r.remote_foo(1, 3)</code>. A <code>callRemote</code> of
+<q><code>bar</code></q> would invoke <code>remote_bar</code>, etc.</p>
+
+<h3>Obtaining other references</h3>
+
+<p>Each <code>pb.RemoteReference</code> object points to a
+<code>pb.Referenceable</code> instance in some other program. The first such
+reference must be acquired with a bootstrapping function like
+<code>pb.getObjectAt</code>, but all subsequent ones are created when a
+<code>pb.Referenceable</code> is sent as an argument to (or a return value
+from) a remote method call.</p>
+
+<p>When the arguments or return values contain references to other objects,
+the object that appears on the other side of the wire depends upon the type
+of the referred object. Basic types are simply copied: a dictionary of lists
+will appear as a dictionary of lists, with internal references preserved on
+a per-method-call basis (just as Pickle will preserve internal references
+for everything pickled at the same time). Class instances are restricted,
+both to avoid confusion and for security reasons.</p>
+
+<h3>Transferring Instances</h3>
+
+<p>PB only allows certain kinds of objects to be transferred to and from
+remote processes. Most of these restrictions are implemented in the <a
+href="#jelly">Jelly</a> serialization layer, described below. In general, to
+send an object over the wire, it must either be a basic python type (list,
+dictionary, etc), or an instance of a class which is derived from one of the
+four basic <em>PB Flavors</em>: <code>Referenceable</code>,
+<code>Viewable</code>, <code>Copyable</code>, and <code>Cacheable</code>.
+Each flavor has methods which define how the object should be treated when
+it needs to be serialized to go over the wire, and all have related classes
+that are created on the remote end to represent them.</p>
+
+<p>There are a few kinds of callable classes. All are represented on the
+remote system with <code>RemoteReference</code> instances.
+<code>callRemote</code> can be used on these RemoteReferences, causing
+methods with various prefixes to be invoked.</p>
+
+<table border="1">
+ <tr>
+ <th>Local Class</th>
+ <th>Remote Representation</th>
+ <th>method prefix</th>
+ </tr>
+ <tr>
+ <td><code>Referenceable</code></td>
+ <td><code>RemoteReference</code></td>
+ <td><code>remote_</code></td>
+ </tr>
+ <tr>
+ <td><code>Viewable</code></td>
+ <td><code>RemoteReference</code></td>
+ <td><code>view_</code></td>
+ </tr>
+</table>
+
+<p><code>Viewable</code> (and the related <code>Perspective</code> class)
+are described later (in <a href="#authorization">Authorization</a>). They
+provide a secure way to let methods know <em>who</em> is calling them. Any
+time a <code>Referenceable</code> (or <code>Viewable</code>) is sent over
+the wire, it will appear on the other end as a <code>RemoteReference</code>.
+If any of these references are sent back to the system they came from, they
+emerge from the round trip in their original form.</p>
+
+<p>Note that RemoteReferences cannot be sent to anyone else (there are no
+<q>third-party references</q>): they are scoped to the connection between
+the holder of the <code>Referenceable</code> and the holder of the
+<code>RemoteReference</code>. (In fact, the <code>RemoteReference</code> is
+really just an index into a table maintained by the owner of the original
+<code>Referenceable</code>).</p>
+
+<p>There are also two data classes. To send an instance over the wire, it
+must belong to a class which inherits from one of these.</p>
+
+<table border="1">
+ <tr>
+ <th>Local Class</th>
+ <th>Remote Representation</th>
+ </tr>
+ <tr>
+ <td><code>Copyable</code></td>
+ <td><code>RemoteCopy</code></td>
+ </tr>
+ <tr>
+ <td><code>Cacheable</code></td>
+ <td><code>RemoteCache</code></td>
+ </tr>
+</table>
+
+<h3>pb.Copyable</h3>
+<a name="pb.Copyable"></a>
+
+<p><code>Copyable</code> is used to allow class instances to be sent over
+the wire. <code>Copyable</code>s are copy-by-value, unlike
+<code>Referenceable</code>s which are copy-by-reference.
+<code>Copyable</code> objects have a method called
+<code>getStateToCopy</code> which gets to decide how much of the object
+should be sent to the remote system: the default simply copies the whole
+<code>__dict__</code>. The receiver must register a <code>RemoteCopy</code>
+class for each kind of <code>Copyable</code> that will be sent to it: this
+registration (described later in <a href="#unjellyableRegistry">Representing
+Instances</a>) maps class names to actual classes. Apart from being a
+security measure (it emphasizes the fact that the process is receiving data
+from an untrusted remote entity and must decide how to interpret it safely),
+it is also frequently useful to distinguish a copy of an object from the
+original by holding them in different classes.</p>
+
+<p><code>getStateToCopy</code> is frequently used to remove attributes that
+would not be meaningful outside the process that hosts the object, like file
+descriptors. It also allows shared objects to hold state that is only
+available to the local process, including passwords or other private
+information. Because the default serialization process recursively follows
+all references to other objects, it is easy to accidentally send your entire
+program to the remote side. Explicitly creating the state object (creating
+an empty dictionary, then populating it with only the desired instance
+attributes) is a good way to avoid this.</p>
+
+<p>The fact that PB will refuse to serialize objects that are neither basic
+types nor explicitly marked as being transferable (by subclassing one of the
+pb.flavors) is another way to avoid the <q>don't tug on that, you never know
+what it might be attached to</q> problem. If the object you are sending
+includes a reference to something that isn't marked as transferable, PB will
+raise an InsecureJelly exception rather than blindly sending it anyway (and
+everything else it references).</p>
+
+<p>Finally, note that <code>getStateToCopy</code> is distinct from the
+<code>__getstate__</code> method used by Pickle, and they can return
+different values. This allows objects to be persisted (across time)
+differently than they are transmitted (across [memory]space).</p>
+
+<h3>pb.Cacheable</h3>
+<a name="pb.Cacheable"></a>
+
+<p><code>Cacheable</code> is a variant of <code>Copyable</code> which is
+used to implement remote caches. When a <code>Cacheable</code> is sent
+across a wire, a method named <code>getStateToCacheAndObserveFor</code> is
+used to simultaneously get the object's current state and to register an
+<q>Observer</q> which lives next to the <code>Cacheable</code>. The Observer
+is effectively a <code>RemoteReference</code> that points at the remote
+cache. Each time the cached object changes, it uses its Observers to tell
+all the remote caches about the change. The <q>setter</q> methods can just
+call <code class="python">observer.callRemote("setFoo", newvalue)</code> for
+all their observers.</p>
+
+<p>On the remote end, a <code>RemoteCache</code> object is created, which
+populates the original object's state just as <code>RemoteCopy</code> does.
+When changes are made, the Observers remotely invoke methods like
+<code>observe_setFoo</code> in the <code>RemoteCache</code> to perform the
+updates.</p>
+
+<p>As <code>RemoteCache</code> objects go away, their Observers go away too,
+and call <code>stoppedObserving</code> so they can be removed from the
+list.</p>
+
+<p>The PB <a href="http://twistedmatrix.com/documents/howto/"
+><q>howto</q> docs</a> have more information and complete examples of both
+<code>pb.Copyable</code> and <code>pb.Cacheable</code>.</p>
+
+
+<h2>Authorization</h2>
+<a name="authorization"></a>
+
+<p>As a framework, Perspective Broker (indeed, all of Twisted) was built
+from the ground up. As multiple use cases became apparent, common
+requirements were identified, code was refactored, and layers were developed
+to cleanly serve the needs of all <q>customers</q>. The twisted.cred layer
+was created to provide authorization services for PB as well as other
+Twisted services, like the HTTP server and the various instant messaging
+protocols. The abstract notions of identity and authority it uses are
+intended to match the common needs of these various protocols: specific
+applications can always use subclasses that are more appropriate for their
+needs.</p>
+
+<h3>Identity and Perspectives</h3>
+
+<p>In twisted.cred, <q>Identities</q> are usernames (with passwords),
+represented by <code>Identity</code> objects. Each identity has a
+<q>keyring</q> which authorizes it to access a set of objects called
+<q>Perspectives</q>. These perspectives represent accounts or other
+capabilities; each belongs to a single <q>Service</q>. There may be multiple
+Services in a single application; in fact the flexible nature of Twisted
+makes this easy. An HTTP server would be a Service, and an IRC server would
+be another one.</p>
+
+<p>As an example, a login service might have perspectives for Alice, Bob,
+and Charlie, and there might also be an Admin perspective. Alice has admin
+capabilities. In addition, let us say the same application has a chat
+service with accounts for each person (but no special administrator
+account).</p>
+
+<p>So, in this example, Alice's keyring gives her access to three
+perspectives: login/Alice, login/Admin, and chat/Alice. Bob only gets two:
+login/Bob and chat/Bob. <code>Perspective</code> objects have names and
+belong to <code>Service</code> objects, but the
+<code>Identity.keyring</code> is a dictionary indexed by (serviceName,
+perspectiveName) pairs. It uses names instead of object references because
+the <code>Perspective</code> object might be created on demand. The keys
+include the service name because Perspective names are scoped to a single
+service.</p>
+
+<h3>pb.Perspective</h3>
+
+<p>The PB-specific subclass of the generic <code>Perspective</code> class is
+also capable of remote execution. The login process results in the
+authorized client holding a special kind of <code>RemoteReference</code>
+that will allow it to invoke <code>perspective_</code> methods on the
+matching <code>pb.Perspective</code> object. In PB applications that use the
+<code>twisted.cred</code> authorization layer, clients get this reference
+first. The client is then dependent upon the Perspective to provide
+everything else, so the Perspective can enforce whatever security policy it
+likes.</p>
+
+<p>(Note that the <code>pb.Perspective</code> class is not actually one of
+the serializable PB flavors, and that instances of it cannot be sent
+directly over the wire. This is a security feature intended to prevent users
+from getting access to somebody else's <code>Perspective</code> by mistake,
+perhaps when a <q>list all users</q> command sends back an object which
+includes references to other Perspectives.)</p>
+
+<p>PB provides functions to perform a challenge-response exchange in which
+the remote client proves their identity to get that <code>Perspective</code>
+reference. The <code>Identity</code> object holds a password and uses an MD5
+hash to verify that the remote user knows the password without sending it in
+cleartext over the wire. Once the remote user has proved their identity,
+they can request a reference to any <code>Perspective</code> permitted by
+their <code>Identity</code>'s keyring.</p>
+
+<p>There are twisted.cred functions (twisted.enterprise.dbcred) which can
+pull user information out of a database, and it is easy to create modules
+that could check /etc/passwd or LDAP instead. Authorization can then be
+centralized through the Perspective object: each object that is accessible
+remotely can be created with a pointer to the local Perspective, and objects
+can ask that Perspective whether the operation is allowed before performing
+method calls.</p>
+
+<p>Most clients use a helper function called <code>pb.connect()</code> to
+get the first Perspective reference: it takes all the necessary identifying
+information (host, port, username, password, service name, and perspective
+name) and returns a <code>Deferred</code> that will be fired when the
+<code>RemoteReference</code> is available. (This may change in the future:
+there are plans afoot to use a URL-like scheme to identify the Perspective,
+which will probably mean a new helper function).</p>
+
+<h3>Viewable</h3>
+
+<p>There is a special kind of <code>Referenceable</code> called
+<code>pb.Viewable</code>. Its remote methods (all named <code>view_</code>)
+are called with an extra argument that points at the
+<code>Perspective</code> the client is using. This allows the same
+<code>Referenceable</code> to be shared among multiple clients while
+retaining the ability to treat those clients differently. The methods can
+check with the Perspective to see if the request should be allowed, and can
+use per-client information in processing the request.</p>
+
+<!-- XXX: it would be nice to provide some examples of typical Perspective
+use cases: static pre-defined Perspectives, DB lookup, anonymous access. But
+they would be pretty big, and are probably more appropriate for the
+pb-cred.html HOWTO doc -->
+
+
+<h2>PB Design: Object Serialization</h2>
+
+<p>Fundamental to any calling convention, whether ABI or RPC, is how
+arguments and return values are passed from caller to callee and back. RPC
+systems require data to be turned into a form which can be delivered through
+a network, a process usually known as serialization. Sharing complex types
+(references and class instances) with a remote system requires more care:
+references should all point to the same thing (even though the object being
+referenced might live on either end of the connection), and allowing a
+remote user to create arbitrary class instances in your memory space is a
+security risk that must be controlled.</p>
+
+<p>PB uses its own serialization scheme called <q>Jelly</q>. At the bottom
+end, it uses s-expressions (lists of numbers and strings) to represent the
+state of basic types (lists, dictionaries, etc). These s-expressions are
+turned into a bytestream by the <q>Banana</q> layer, which has an optional C
+implementation for speed. Unserialization for higher-level objects is driven
+by per-class <q>jellyier</q> objects: this flexibility allows PB to offer
+inheritable classes for common operations. <code>pb.Referenceable</code> is
+a class which is serialized by sending a reference to the remote end that
+can be used to invoke remote methods. <code>pb.Copyable</code> is a class
+which creates a new object on the remote end, with methods that the
+developer can override to control how much state is sent or accepted.
+<code>pb.Cacheable</code> sends a full copy the first time it is exchanged,
+but then sends deltas as the object is modified later.</p>
+
+<p>Objects passed over the wire get to decide for themselves how much
+information is actually passed to the remote system. Copy-by-reference
+objects are given a per-connection ID number and stashed in a local
+dictionary. Copy-by-value objects may send their entire
+<code>__dict__</code>, or some subset thereof. If the remote method returns
+a referenceable object that was given to it earlier (either in the same RPC
+call or an earlier one), PB sends the ID number over the wire, which is
+looked up and turned into a proper object reference upon receipt. This
+provides one-sided reference transparency: one end sees objects coming and
+going through remote method calls in exactly the same fashion as through
+local calls. Those references are only capable of very specific operations;
+PB does not attempt to provide full object transparency. As discussed later,
+this is instrumental to security.</p>
+
+<h3>Banana and s-expressions</h3>
+
+<p>The <q>Banana</q> low-level serialization layer converts s-expressions
+which represent basic types (numbers, strings, and lists of numbers,
+strings, or other lists) to and from a bytestream. S-expressions are easy to
+encode and decode, and are flexible enough (when used with a set of tokens)
+to represent arbitrary objects. <q>cBanana</q> is a C extension module which
+performs the encode/decode step faster than the native python
+implementation.</p>
+
+<p>Each s-expression element is converted into a message with two or three
+components: a header, a type marker, and an optional body (used only for
+strings). The header is a number expressed in base 128. The type marker is a
+single byte with the high bit set, that both terminates the header and
+indicate the type of element this message describes (number, list-start,
+string, or tokenized string).</p>
+
+<p>When a connection is first established, a list of strings is sent to
+negotiate the <q>dialect</q> of Banana being spoken. The first dialect known
+to both sides is selected. Currently, the dialect is only used to select a
+list of string tokens that should be specially encoded (for performance),
+but subclasses of Banana could use self.currentDialect to influence the
+encoding process in other ways.</p>
+
+<p>When Banana is used for PB (by negotiating the <q>pb</q> dialect), it has
+a list of 30ish strings that are encoded into two-byte sequences instead of
+being sent as generalized string messages. These string tokens are used to
+mark complex types (beyond the simple lists, strings, and numbers provided
+natively by Banana) and other objects Jelly needs to do its job.</p>
+
+<h3>Jelly</h3>
+<a name="jelly"></a>
+
+<p><code>Jelly</code> handles object serialization. It fills a similar role
+to the standard Pickle module, but has design goals of security and
+portability (especially to other languages) where Pickle favors efficiency
+of representation. In addition, Jelly serializes objects into s-expressions
+(lists of tokens, strings, numbers, and other lists), and lets Banana do the
+rest, whereas Pickle goes all the way down to a bytestream by itself.</p>
+
+<p>Basic python types (apart from strings and numbers, which Banana can
+handle directly) are generally turned into lists with a type token as the
+first element. For example, a python dictionary is turned into a list that
+starts with the string token <q>dictionary</q> and continues with elements
+that are lists of [key, value] pairs. Modules, classes, and methods are all
+transformed into s-expressions that refer to the relevant names. Instances
+are represented by combining the class name (a string) with an arbitrary
+state object (which is usually a dictionary).</p>
+
+<p>Much of the rest of Jelly has to do with safely handling class instances
+(as opposed to basic Python types) and dealing with references to shared
+objects.</p>
+
+<h4>Tracking shared references</h4>
+
+<p>Mutable types are serialized in a way that preserves the identity between
+the same object referenced multiple times. As an example, a list with four
+elements that all point to the same object must look the same on the remote
+end: if it showed up as a list pointing to four independent objects (even if
+all the objects had identical states), the resulting list would not behave
+in the same way as the original. Changing <code>newlist[0]</code> would not
+modify <code>newlist[1]</code> as it ought to.</p>
+
+<p>Consequently, when objects which reference mutable types are serialized,
+those references must be examined to see if they point to objects which have
+already been serialized in the same session. If so, an object id tag of some
+sort is put into the bytestream instead of the complete object, indicating
+that the deserializer should use a reference to a previously-created object.
+This also solves the issue of recursive or circular references: the first
+appearance of an object gets the full state, and all subsequent ones get a
+reference to it.</p>
+
+<p>Jelly manages this reference tracking through an internal
+<code>_Jellier</code> object (in particular through the <code>.cooked</code>
+dictionary). As objects are serialized, their <code>id</code> values are
+stashed. References to those objects that occur after jellying has started
+can be replaced with a <q>dereference</q> marker and the object id.</p>
+
+<p>The scope of this <code>_Jellier</code> object is limited to a single
+call of the <code>jelly</code> function, which in general corresponds to a
+single remote method call. The argument tuple is jellied as a single object
+(a tuple), so different arguments to the same method will share referenced
+objects<span class="footnote">Actually, PB currently jellies the list
+arguments in a separate tuple from the keyword arguments. This issue is
+currently being examined and may be changed in the future</span>, but
+arguments of separate methods will not share them. To do more complex
+caching and reference tracking, certain PB <q>flavors</q> (see below)
+override their <code>jellyFor</code> method to do more interesting things.
+In particular, <code>pb.Referenceable</code> objects have code to insure
+that one which makes a round trip will come back as a reference to the same
+object that was originally sent.</p>
+
+<p>An exception to this <q>one-call scope</q> is provided: if the
+<code>Jellier</code> is created with a <code>persistentStore</code> object,
+all class instances will be passed through it first, and it has the
+opportunity to return a <q>persistent id</q>. If available, this id is
+serialized instead of the object's state. This would allow object references
+to be shared between different invocations of <code>jelly</code>. However,
+PB itself does not use this technique: it uses overridden
+<code>jellyFor</code> methods to provide per-connection shared
+references.</p>
+
+<h4>Representing Instances</h4>
+<a name="unjellyableRegistry"></a>
+
+<p>Each class gets to decide how it should be represented on a remote
+system. Sending and receiving are separate actions, performed in separate
+programs on different machines. So, to be precise, each class gets to decide
+two things. First, they get to specify how they should be sent to a remote
+client: what should happen when an instance is serialized (or <q>jellied</q>
+in PB lingo), what state should be recorded, what class name should be sent,
+etc. Second, the receiving program gets to specify how an incoming object
+that claims to be an instance of some class should be treated: whether it
+should be accepted at all, if so what class should be used to create the new
+object, and how the received state should be used to populate that
+object.</p>
+
+<p>A word about notation: in Perspective Broker parlance, <q>to jelly</q> is
+used to describe the act of turning an object into an s-expression
+representation (serialization, or at least most of it). Therefore the
+reverse process, which takes an s-expression and turns it into a real python
+object, is described with the verb <q>to unjelly</q>. </p>
+
+<h4>Jellying Instances</h4>
+
+<p>Serializing instances is fairly straightforward. Classes which inherit
+from <code>Jellyable</code> provide a <code>jellyFor</code> method, which
+acts like <code>__getstate__</code> in that it should return a serializable
+representation of the object (usually a dictionary). Other classes are
+checked with a <code>SecurityOptions</code> instance, to verify that they
+are safe to be sent over the wire, then serialized by using their
+<code>__getstate__</code> method (or their <code>__dict__</code> if no such
+method exists). User-level classes always inherit from one of the PB
+<q>flavors</q> like <code>pb.Copyable</code> (all of which inherit from
+<code>Jellyable</code>) and use <code>jellyFor</code>; the
+<code>__getstate__</code> option is only for internal use.</p>
+
+<!-- should we mention persistentStore here? Nothing uses it, so no. Besides
+it was already hinted at in 'tracking shared references' above. -->
+
+<h4>Secure Unjellying</h4>
+
+<p>Unjellying (for instances) is triggered by the receipt of an s-expression
+with the <q>instance</q> tag. The s-expression has two elements: the name of
+the class, and an object (probably a dictionary) which holds the instance's
+state. At that point in time, the receiving program does not know what class
+should be used: it is certainly <em>not</em> safe to simply do an
+<code>import</code> of the classname requested by the sender. That
+effectively allows a remote entity to run arbitrary code on your system.
+</p>
+
+<p>There are two techniques used to control how instances are unjellied. The
+first is a <code>SecurityOptions</code> instance which gets to decide
+whether the incoming object should accepted or not. It is said to
+<q>taste</q> the incoming type before really trying to unserialize it. The
+default taster accepts all basic types but no classes or instances.</p>
+
+<p>If the taster decides that the type is acceptable, Jelly then turns to
+the <code>unjellyableRegistry</code> to determine exactly <em>how</em> to
+deserialize the state. This is a table that maps received class names names
+to unserialization routines or classes.</p>
+
+<p>The receiving program must register the classes it is willing to accept.
+Any attempts to send instances of unregistered classes to the program will
+be rejected, and an InsecureJelly exception will be sent back to the sender.
+If objects should be represented by the same class in both the sender and
+receiver, and if the class is defined by code which is imported into both
+programs (an assumption that results in many security problems when it is
+violated), then the shared module can simply claim responsibility as the
+classes are defined:</p>
+
+<pre class="python">
+class Foo(pb.RemoteCopy):
+ def __init__(self):
+ # note: __init__ will *not* be called when creating RemoteCopy objects
+ pass
+ def __getstate__(self):
+ return foo
+ def __setstate__(self, state):
+ self.stuff = state.stuff
+setUnjellyableForClass(Foo, Foo)
+</pre>
+
+<p>In this example, the first argument to
+<code>setUnjellyableForClass</code> is used to get the fully-qualified class
+name, while the second defines which class will be used for unjellying.
+<code>setUnjellyableForClass</code> has two functions: it informs the
+<q>taster</q> that instances of the given class are safe to receive, and it
+registers the local class that should be used for unjellying.</p>
+
+
+<h3>Broker</h3>
+
+<p>The <code>Broker</code> class manages the actual connection to a remote
+system. <code>Broker</code> is a <q>Protocol</q> (in Twisted terminology),
+and there is an instance for each socket over which PB is being spoken.
+Proxy objects like <code>pb.RemoteReference</code>, which are associated
+with another object on the other end of the wire, all know which Broker they
+must use to get to their remote counterpart. <code>pb.Broker</code> objects
+implement distributed reference counts, manage per-connection object IDs,
+and provide notification when references are lost (due to lost connections,
+either from network problems or program termination).</p>
+
+<h4>PB over Jelly</h4>
+
+<p>Perspective Broker is implemented by sending Jellied commands over the
+connection. These commands are always lists, and the first element of the
+list is always a command name. The commands are turned into
+<code>proto_</code>-prefixed method names and executed in the Broker object.
+There are currently 9 such commands. Two (<code>proto_version</code> and
+<code>proto_didNotUnderstand</code>) are used for connection negotiation.
+<code>proto_message</code> is used to implement remote method calls, and is
+answered by either <code>proto_answer</code> or
+<code>proto_error</code>.</p>
+
+<p><code>proto_cachemessage</code> is used by Observers (see <a
+href="#pb.Copyable">pb.Copyable</a>) to notify their
+<code>RemoteCache</code> about state updates, and behaves like
+<code>proto_message</code>. <a href="#pb.Cacheable">pb.Cacheable</a> also
+uses <code>proto_decache</code> and <code>proto_uncache</code> to manage
+reference counts of cached objects.</p>
+
+<p>Finally, <code>proto_decref</code> is used to manage reference counts on
+<code>RemoteReference</code> objects. It is sent when the
+<code>RemoteReference</code> goes away, so that the holder of the original
+<code>Referenceable</code> can free that object.</p>
+
+<h4>Per-Connection ID Numbers</h4>
+
+<p>Each time a <code>Referenceable</code> is sent across the wire, its
+<code>jellyFor</code> method obtains a new unique <q>local ID</q> (luid) for
+it, which is a simple integer that refers to the original object. The
+Broker's <code>.localObjects{}</code> and <code>.luids{}</code> tables
+maintain the <q>luid</q>-to-object mapping. Only this ID number is sent to
+the remote system. On the other end, the object is unjellied into a
+<code>RemoteReference</code> object which remembers its Broker and the luid
+it refers to on the other end of the wire. Whenever
+<code>callRemote()</code> is used, it tells the Broker to send a message to
+the other end, including the luid value. Back in the original process, the
+luid is looked up in the table, turned into an object, and the named method
+is invoked.</p>
+
+<p>A similar system is used with Cacheables: the first time one is sent, an
+ID number is allocated and recorded in the
+<code>.remotelyCachedObjects{}</code> table. The object's state (as returned
+by <code>getStateToCacheAndObserveFor()</code>) and this ID number are sent
+to the far end. That side uses <code>.cachedLocallyAs()</code> to find the
+local <code>CachedCopy</code> object, and tracks it in the Broker's
+<code>.locallyCachedObjects{}</code> table. (Note that to route state
+updates to the right place, the Broker on the <code>CachedCopy</code> side
+needs to know where it is. The same is not true of
+<code>RemoteReference</code>s: nothing is ever sent <em>to</em> a
+<code>RemoteReference</code>, so its Broker doesn't need to keep track of
+it).</p>
+
+<p>Each remote method call gets a new <code>requestID</code> number. This
+number is used to link the request with the response. All pending requests
+are stored in the Broker's <code>.waitingForAnswers{}</code> table until
+they are completed by the receipt of a <code>proto_answer</code> or
+<code>proto_error</code> message.</p>
+
+<p>The Broker also provides hooks to be run when the connection is lost.
+Holders of a <code>RemoteReference</code> can register a callback with
+<code>.notifyOnDisconnect()</code> to be run when the process which holds
+the original object goes away. Trying to invoke a remote method on a
+disconnected broker results in an immediate <code>DeadReferenceError</code>
+exception.</p>
+
+<h4>Reference Counting</h4>
+
+<p>The Broker on the <code>Referenceable</code> end of the connection needs
+to implement distributed reference counting. The fact that a remote end
+holds a <code>RemoteReference</code> should prevent the
+<code>Referenceable</code> from being freed. To accomplish this, The
+<code>.localObjects{}</code> table actually points at a wrapper object
+called <code>pb.Local</code>. This object holds a reference count in it that
+is incremented by one for each <code>RemoteReference</code> that points to
+the wrapped object. Each time a Broker serializes a
+<code>Referenceable</code>, that count goes up. Each time the distant
+<code>RemoteReference</code> goes away, the remote Broker sends a
+<code>proto_decref</code> message to the local Broker, and the count goes
+down. When the count hits zero, the <code>Local</code> is deleted, allowing
+the original <code>Referenceable</code> object to be released.</p>
+
+
+<h2>Security</h2>
+
+<p>Insecurity in network applications comes from many places. Most can be
+summarized as trusting the remote end to behave in a certain way.
+Applications or protocols that do not have a way to verify their assumptions
+may act unpredictably when the other end misbehaves; this may result in a
+crash or a remote compromise. One fundamental assumption that most RPC
+libraries make when unserializing data is that the same library is being
+used at the other end of the wire to generate that data. Developers put so
+much time into making their RPC libraries work <strong>at all</strong> that
+they usually assume their own code is the only thing that could possibly
+provide the input. A safer design is to assume that the input will almost
+always be corrupt, and to make sure that the program survives anyway.</p>
+
+<h3>Controlled Object serialization</h3>
+
+<p>Security is a primary design goal of PB. The receiver gets final say as
+to what they will and will not accept. The lowest-level serialization
+protocol (<q>Banana</q>) is simple enough to validate by inspection, and
+there are size limits imposed on the actual data received to prevent
+excessive memory consumption. Jelly is willing to accept basic data types
+(numbers, strings, lists and dictionaries of basic types) without question,
+as there is no dangerous code triggered by their creation, but Class
+instances are rigidly controlled. Only subclasses of the basic PB flavors
+(<code>pb.Copyable</code>, etc) can be passed over the wire, and these all
+provide the developer with ways to control what state is sent and accepted.
+Objects can keep private data on one end of the connection by simply not
+including it in the copied state.</p>
+
+<p>Jelly's refusal to serialize objects that haven't been explicitly marked
+as copyable helps stop accidental security leaks. Seeing the
+<code>pb.Copyable</code> tag in the class definition is a flag to the
+developer that they need to be aware of what parts of the class will be
+available to a remote system and which parts are private. Classes without
+those tags are not an issue: the mere act of <em>trying</em> to export them
+will cause an exception. If Jelly tried to copy arbitrary classes, the
+security audit would have to look into <em>every</em> class in the
+system.</p>
+
+<h3>Controlled Object Unserialization</h3>
+
+<p>On the receiving side, the fact that Unjellying insists upon a
+user-registered class for each potential incoming instance reduces the risk
+that arbitrary code will be executed on behalf of remote clients. Only the
+classes that are added to the <code>unjellyableRegistry</code> need to be
+examined. Half of the security issues in RPC systems will boil down to the
+fact that these potential unserializing classes will have their
+<code>setCopyableState</code> methods called with a potentially hostile
+<code>state</code> argument. (the other half are that <code>remote_</code>
+methods can be called with arbitrary arguments, including instances that
+have been sent to that client at some point since the current connection was
+established). If the system is prepared to handle that, it should be in good
+shape security-wise.</p>
+
+<p>RPC systems which allow remote clients to create arbitrary objects in the
+local namespace are liable to be abused. Code gets run when objects are
+created, and generally the more interesting and useful the object, the more
+powerful the code that gets run during its creation. Such systems also have
+more assumptions that must be validated: code that expects to be given an
+object of class <code>A</code> so it can call <code>A.foo</code> could be
+given an object of class <code>B</code> instead, for which the
+<code>foo</code> method might do something drastically different. Validating
+the object is of the required type is much easier when the number of
+potential types is smaller.</p>
+
+<h3>Controlled Method Invocation</h3>
+
+<p>Objects which allow remote method invocation do not provide remote access
+to their attributes (<code>pb.Referenceable</code> and
+<code>pb.Copyable</code> are mutually exclusive). Remote users can only
+invoke a well-defined and clearly-marked subset of their methods: those with
+names that start with <code>remote_</code> (or other specific prefixes
+depending upon the variant of <code>Referenceable</code> in use). This
+insures that they can have local methods which cannot be invoked remotely.
+Complete object transparency would make this very difficult: the
+<q>translucent</q> reference scheme allows objects some measure of privacy
+which can be used to implement a security model. The
+<q><code>remote_</code></q> prefix makes all remotely-invokable methods easy
+to locate, improving the focus of a security audit.</p>
+
+<h3>Restricted Object Access</h3>
+
+<p>Objects sent by reference are indexed by a per-connection ID number,
+which is the only way for the remote end to refer back to that same object.
+This list means that the remote end can not touch objects that were not
+explicitly given to them, nor can they send back references to objects
+outside that list. This protects the program's memory space against the
+remote end: they cannot find other local objects to play with.</p>
+
+<p>This philosophy of using simple, easy to validate identifiers (integers
+in the case of PB) that are scoped to a well-defined trust boundary (in this
+case the Broker and the one remote system it is connected to) leads to
+better security. Imagine a C system which sent pointers to the remote end
+and hoped it would receive back valid ones, and the kind of damage a
+malicious client could do. PB's <code>.localObjects{}</code> table insures
+that any given client can only refer to things that were given to them. It
+isn't even a question of validating the identifier they send: if it isn't a
+value of the <code>.localObjects{}</code> dictionary, they have no physical
+way to get at it. The worst they can do with a corrupt ObjectID is to cause
+a <code>KeyError</code> when it is not found, which will be trapped and
+reported back.</p>
+
+<h3>Size Limits</h3>
+
+<p>Banana limits string objects to 640k (because, as the source says, 640k
+is all you'll ever need). There is a helper class called
+<code>pb.util.StringPager</code> that uses a producer/consumer interface to
+break up the string into separate pages and send them one piece at a time.
+This also serves to reduce memory consumption: rather than serializing the
+entire string and holding it in RAM while waiting for the transmit buffers
+to drain, the pages are only serialized as there is space for them.</p>
+
+
+<h2>Future Directions</h2>
+
+<p>PB can currently be carried over TCP and SSL connections, and through
+UNIX-domain sockets. It is being extended to run over UDP datagrams and a
+work-in-progress reliable datagram protocol called <q>airhook</q>. (clearly
+this requires changes to the authorization sequence, as it must all be done
+in a single packet: it might require some kind of public-key signature).</p>
+
+<p>At present, two functions are used to obtain the initial reference to a
+remote object: <code>pb.getObjectAt</code> and <code>pb.connect</code>. They
+take a variety of parameters to indicate where the remote process is
+listening, what kind of username/password should be used, and which exact
+object should be retrieved. This will be simplified into a <q>PB URL</q>
+syntax, making it possible to identify a remote object with a descriptive
+URL instead of a list of parameters.</p>
+
+<p>Another research direction is to implement <q>typed arguments</q>: a way
+to annotate the method signature to indicate that certain arguments may only
+be instances of a certain class. Reminiscent of the E language, this would
+help remote methods improve their security, as the common code could take
+care of class verification.</p>
+
+<p>Twisted provides a <q>componentization</q> mechanism to allow
+functionality to be split among multiple classes. A class can declare that
+all methods in a given list (the <q>interface</q>) are actually implemented
+by a companion class. Perspective Broker will be cleaned up to use this
+mechanism, making it easier to swap out parts of the protocol with different
+implementations.</p>
+
+<p>Finally, a comprehensive security audit and some performance improvements
+to the Jelly design are also in the works.</p>
+
+<!-- $Id: pb.html,v 1.1 2003/03/31 05:21:40 glyph Exp $ -->
+
+</body> </html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/releasing/releasing-twisted b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/releasing/releasing-twisted
new file mode 100755
index 0000000000..1b2015567c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/releasing/releasing-twisted
@@ -0,0 +1,151 @@
+#!/usr/bin/python2.2
+# Moshe -- This seems like 30+ minutes to me!
+from slides import NumSlide, Slide, Bullet, SubBullet, PRE, URL
+from twslides import Lecture
+
+
+lecture = Lecture(
+ "Managing the Release of a Large Python Project",
+ Slide("About Twisted",
+ Bullet("Networking framework"),
+ Bullet("Other goodies"),
+ Bullet("60,000 lines of code"),
+ Bullet("Things can (and do) go wrong"),
+ ),
+ Slide("Python",
+ Bullet("Recap"),
+ Bullet("No compilation (except for native modules)"),
+ Bullet("Simple file-based modules (no registration)"),
+ Bullet("Distutils -- Does the common things"),
+ ),
+ Slide("Release Procedure -- Steps",
+ Bullet("Increment version in copyright file, README"),
+ Bullet("Tag release"),
+ Bullet("Export from CVS"),
+ Bullet("Rename toplevel directory"),
+ Bullet("Generate API and HOWTO documentation"),
+ Bullet("Create tarballs"),
+ Bullet("Move tarballs to target area"),
+ Bullet("Create Debian packages"),
+ Bullet("Put Debian packages in final place"),
+ Bullet("Upgrade production machine"),
+ ),
+ Slide("Release Procedure Overview - Documentation",
+ Bullet("Man pages -> Lore"),
+ Bullet("Lore documents -> HTML"),
+ Bullet("Lore documents -> PS/PDF"),
+ Bullet("API documentation -> HTML"),
+ ),
+ Slide("Release Procedure Overview - Testing",
+ Bullet("Run of the mill unit tests"),
+ Bullet("Acceptance tests of less portable things"),
+ Bullet("Prerelease tests for twistedmatrix.com-specific test"),
+ Bullet("twistedmatrix.com uses latest version -- always!"),
+ ),
+ Slide("Release Procedure Overview - Debian",
+ Bullet("The Twisted machines use Debian packages"),
+ Bullet("The Twisted machines run latest version"),
+ Bullet("Debian packages are built as part of the release procedure"),
+ ),
+ Slide("Overview Summary",
+ Bullet("Many steps"),
+ Bullet("Each can fail", SubBullet(
+ Bullet("Documentation can fail to build"),
+ Bullet("Tests can fail"),
+ Bullet("Debian packages can fail to build")),
+ ),
+ Bullet("Need robust automated setup"),
+ ),
+ Slide("Enter Release-Twisted",
+ Bullet("Python program to release Twisted"),
+ Bullet("Key word -- Robust"),
+ Bullet("Based on actions which can undo"),
+ Bullet("Flexible - able to recover a botched build from the middle"),
+ Bullet("Easy - has good defaults"),
+ ),
+ Slide("Testing - Recap",
+ Bullet("Testing is special - no effect"),
+ Bullet("The more, the better"),
+ Bullet("Harder to automate - machines can't tell right from wrong",
+ SubBullet(Bullet("Except in Hollywood")),
+ ),
+ ),
+ Slide("Different Kinds of Tests - Unit Tests",
+ Bullet("Completely automated"),
+ Bullet("Completely machine-verifiable"),
+ Bullet("Portable"),
+ Bullet("Must always pass"),
+ ),
+ Slide("Different Kinds of Tests - Acceptance Tests",
+ Bullet("Interacts with user"),
+ Bullet("Probably works only on Linux"),
+ Bullet("Assumes many client side tools"),
+ Bullet("Exercises many parts of Twisted which are hard in unit tests"),
+ ),
+ Slide("Acceptance Tests Examples",
+ Bullet("Run Twisted web server, run user-defined web browser"),
+ Bullet("Run mail server, send mail and try to download with pop3"),
+ Bullet("Run IRC server, run user-defined IRC client"),
+ ),
+ Slide("Different Kinds of Tests - Prerelease Tests",
+ Bullet("TwistedMatrix.com dogfoods"),
+ Bullet("We want to test the dog food"),
+ Bullet("prerelease tests convince us that this version doesn't break "
+ "completely"),
+ Bullet("Among other things, tests that distributed web works"),
+ ),
+ Slide("Epydoc",
+ ),
+ Slide("Epyrun",
+ ),
+ Slide("Distutils -- Datafiles",
+ ),
+ Slide("Distutils -- Conditional compilation",
+ ),
+ Slide("Distutils -- Conditional compilation woes",
+ ),
+ Slide("Distutils -- Other woes",
+ Bullet("Versions -- keywords were added later"),
+ Bullet("Icky to do platform dependent stuff"),
+ ),
+ Slide("release-twistd -- master script",
+ ),
+ Slide("Commit/rollback",
+ ),
+ Slide("CVS and tagging",
+ ),
+ Slide("Debian Packages -- Challenges",
+ Bullet("Versioning: We want 1.0.2alpha4 to precede 1.0.2"),
+ Bullet("Dependencies: Which versions of Python? 2.1? 2.2? 2.3?"),
+ Bullet("Dependencies: Which libc version?"),
+ ),
+ Slide("Debian Packages -- Solutions",
+ Bullet("Build two sets -- for Debian stable and for Debian unstable"),
+ Bullet("When building on stable, remove python2.3-dev from build"
+ " dependencies", SubBullet(
+ Bullet("This stops the Python 2.3 version from being built")),
+ ),
+ Bullet("If building a non-final version, name it 1.0.1+1.0.2alpha4"),
+ Bullet("Unstable build is done by sshing into an unstable chroot"),
+ ),
+ Slide("Windows Releases -- Challenges",
+ ),
+ Slide("Windows Releases -- Solutions",
+ ),
+ Slide("Why Not Dependency Management?",
+ ),
+ Slide("Conclusions",
+ Bullet("Distutils does not do enough"),
+ Bullet("Cross compiling is hard"),
+ Bullet("It would be nice if Python had integrated docstring tools"),
+ Bullet("Wheel reinvention is useful"),
+ ),
+ Slide("Future Directions",
+ Bullet("RPMs for Various Distributions"),
+ Bullet("More automation"),
+ ),
+ Slide("Questions?",
+ ),
+)
+
+lecture.renderHTML(".", "releasing-%d.html", css="main.css")
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/releasing/releasing.html b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/releasing/releasing.html
new file mode 100644
index 0000000000..feb692bd8e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/releasing/releasing.html
@@ -0,0 +1,491 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+<title>Managing the Release of a Large Python Project</title>
+</head>
+
+<body>
+<h1>Managing the Release of a Large Python Project</h1>
+
+<ul>
+<li>Christopher Armstrong <a href="mailto:radix@twistedmatrix.com">radix@twistedmatrix.com</a></li>
+<li>Moshe Zadka <a href="mailto:moshez@twistedmatrix.com">moshez@twistedmatrix.com</a></li>
+</ul>
+
+<h2>Abstract</h2>
+<p>
+
+Twisted is a Python networking framework. At last count, the project
+contains nearly 60,000 lines of effective code (not comments or blank
+lines). When preparing a release, many details must be checked, and
+many steps must be followed. We describe here the technologies and
+tools we use, and explain how we built tools on top of them which help
+us make releasing as painless as possible.
+
+</p>
+
+<h2>Introduction</h2>
+<p>
+
+One of the virtues of Python is the ease of distributing code. Its
+module system and the lack of necessity of compilation are what make
+this possible. This means that for simple Python projects, nothing
+more complicated then tar is needed to prepare a distribution of a
+library. However, Twisted has auto-generated documentation in several
+formats, including docstring generated documentation, HOWTOs written
+in HTML, and manpages written in nroff. As Twisted grew more complex
+and popular, a detailed procedure for putting out a release was made
+necessary. However, human fallibility being what it is, it was decided
+that most of these steps should be automated.
+
+</p>
+
+<h2>Overview of Steps</h2>
+<p>
+
+Despite heavy automation, there are still a number of manual steps
+involved in the release process. We've reduced the amount of manual
+steps quite a bit, and most of what's left is not fully automatable,
+although the process could be made easier (see <q>Future
+Directions</q> below).
+
+</p>
+
+<ul>
+ <li>Test
+ <ul>
+ <li>Unit tests</li>
+ <li>Acceptance tests</li>
+ <li>Pre-release tests</li>
+ </ul>
+ </li>
+ <li>Update the Changelog and README files</li>
+ <li>Run the release script
+ <ul>
+ <li>unix runs admin/release-twisted</li>
+ <li>Win32 runs win32/bdist_wininst.bat</li>
+ </ul>
+ </li>
+ <li>Deploy: update twisted deployment on twistedmatrix.com</li>
+ <li>Upload to SourceForge mirror</li>
+ <li>Update Website</li>
+</ul>
+
+
+
+<h2>Testing</h2>
+
+<p>
+
+Twisted has three categories of tests: unit, acceptance, and
+pre-release. Testing is an important part of releasing quality
+software, of course, so these will be explained.
+
+</p>
+
+
+<p>
+
+Unit tests are run as often as possible by each of the developers as
+they write code, and must pass before they commit any changes to
+CVS. While the Twisted team tries to follow the XP practice of
+ensuring all code is releasable, this isn't always true. Thus, running
+the unit tests on several platforms before releasing is necessary.
+Our BuildBot runs the unit tests constantly on several hosts and
+multiple platforms, so the <a
+href="http://twistedmatrix.com/users/warner.twistd/">status page</a>
+is simply checked for green lights before a release.
+
+</p>
+
+<p>
+
+Acceptance tests (which, unfortunately, are not quite the same as <a
+href="http://xprogramming.org/">Extreme Programming's</a> Acceptance
+Tests) are simply interactive tests of various Twisted services. There
+is a script that executes several system commands that use the Twisted
+end-user executables and start several clients (web browsers, IRC
+clients, etc) to allow the user to interactively test the different
+services that Twisted offers. These are only routinely run before a
+release, but we also encourage developers to run these before they
+make major changes.
+
+</p>
+
+<p>
+
+The pre-release tests are for ensuring the web server (One of the most
+popular parts of Twisted, and which the twistedmatrix.com web site
+uses) runs correctly in a semi-production environment. The script
+starts up a web server on twistedmatrix.com, similar to the one on
+port 80, but on an out-of-the-way port. <q>lynx</q> is then run
+several times, with URLs strategically chosen to test different
+features of the web server. Afterwards, the log of the web server is
+displayed and the user is to check for any errors.
+
+</p>
+
+
+<h2>The release-twisted Script</h2>
+
+<p>
+
+Like many other build/release systems, the automated parts of our
+release system started out as a number of small shell
+scripts. Eventually these became a single Python script which was a
+large improvement, but still had many problems, especially since our
+release process became more complex (documentation generation,
+different types of archive formats, etc). This led to problems with
+steps in the middle of the process breaking; the release manager would
+need to restart the entire thing, or enter the remaining commands
+manually.
+
+</p>
+
+<p>
+
+The solution that we came up with was a simple framework for
+pseudo-transactions; Every step of the process is implemented with a
+class that has <code class="python">doIt</code> and <code
+class="python">undoIt</code> methods. Each step also has a
+command-line argument associated with it, so a typical run of the
+script looks something like this:
+
+<pre class="shell">
+$SOMEWHERE/admin/release-twisted -V $VERSION -o $LASTVERSION --checkout \
+--release=/twisted/Releases --upver --tag --exp --dist --docs --balls \
+--rel --deb --debi
+</pre>
+
+</p>
+
+<h3>Transactions</h3>
+
+<p>
+
+As stated above, our transaction system is very simple. One of our
+rather simple transaction classes is <code
+class="python">Export</code>.
+
+</p>
+
+
+<pre class="python">
+class Export(Transaction):
+ def doIt(self, opts):
+ print "Export"
+ root = opts['cvsroot']
+ ver = opts['release-version']
+ sh('cvs -d%s export -r release-%s Twisted' % (root, ver.replace('.', '_')))
+
+ def undoIt(self, opts, fail):
+ sh('rm -rf Twisted')
+</pre>
+
+
+<p>
+
+One useful feature to note is the <code
+class="python">sensitiveUndo</code> attribute on Transaction
+classes. If a transaction has this set, the user will be prompted
+before running the <code class="python">undoIt</code> method. This is
+useful for very long-running processes, like documentation generation,
+debian package building, and uploading to sourceforge. If something
+goes wrong in the middle of one of these processes, we want to give
+the user a chance to manually fix the problem rather than redoing the
+entire transaction. They can then continue from the next command by
+omitting the commands that have already been accomplished from the
+<code class="shell">release-twisted</code> arguments.
+
+</p>
+
+<p>
+
+A list of all of the transactions defined in release-twisted follows.
+
+</p>
+
+<dl>
+<dt>CheckOut</dt>
+<dd>
+
+ checks out the latest revision of Twisted from CVS and puts it in
+ the <q>Twisted.CVS</q> directory.
+
+</dd>
+
+<dt>UpdateVersion</dt>
+<dd>
+
+ changes the version number of the current release -- updating
+ twisted/copyright.py (the canonical location for the current
+ version) and a few other text files where the current version is
+ mentioned.
+
+</dd>
+
+
+<dt>Tag</dt>
+<dd>
+
+ tags the revisions in the current source tree with the version
+ passed in on the command line.
+
+</dd>
+
+
+<dt>Export</dt>
+
+<dd>
+
+ runs the cvs <q>export</q> command, which is similar to
+ <q>checkout</q>, but leaves out CVS support directories; this is
+ what we package up in the archives.
+
+</dd>
+
+
+<dt>PrepareDist</dt>
+<dd>
+
+ simply copies the directory containing the version of Twisted to be
+ released to a new directory specifically for the release
+ process. The reason that we have this extra copy is that sometimes
+ one will want to create a release from a directory that wasn't
+ created from the <q>Export</q> command; having the release script
+ munge that directory in-place would be impolite.
+
+</dd>
+
+
+<dt>GenerateDocs</dt>
+
+<dd>
+
+ generates the various documentation: HTML API documentation (via
+ Epydoc), HTML, PostScript, and PDF howto documentation (via
+ twisted.lore), and HTML man-pages (via lore, converted from the
+ nroff source).
+
+</dd>
+
+<dt>CreateTarballs</dt>
+<dd>
+
+ creates the various archives that each Twisted release involves:
+ tarred and gzipped or bzip2ed versions of archives with code plus
+ documentation, code without documentation, and only documentation.
+
+</dd>
+
+
+<dt>Release</dt>
+
+<dd>
+
+ copies all of the archives to a directory specified by the --release
+ parameter. This is meant to be a publically accessible directory,
+ thus the name <q>Release</q>.
+
+</dd>
+
+<dt>MakeDebs</dt>
+
+<dd>
+
+ creates the .deb packages and support files for the Twisted Debian
+ packages.
+
+</dd>
+
+<dt>InstallDebs</dt>
+
+<dd>
+
+ Creates an apt-gettable Debian package repository in the
+ (unfortunately hard-coded) <q>/twisted/Debian</q> directory.
+
+</dd>
+
+<dt>Sourceforge</dt>
+
+<dd>
+
+ uploads the archives and debian packages to Twisted's sourceforge
+ mirror at <a
+ href="http://twisted.sourceforge.net">http://twisted.sourceforge.net/</a>.
+
+</dd>
+
+
+<dt>UpgradeDebian</dt>
+
+<dd>
+
+ Installs the recently-generated Debian packages via <q>dpkg</q> on
+ the local machine.
+
+</dd>
+
+</dl>
+
+
+<h2>setup.py</h2>
+
+<p>
+
+Twisted has an extensive and very customized setup.py script. We have
+a number of C extension modules and try to ensure that they all build,
+or at least fail gracefully, on win32, Mac OSX, Linux and other
+popular unix-style OSes.
+
+</p>
+
+<p>
+
+We have overridden three of the distutils <q>command classes</q>:
+<code class="python">build_ext</code>, <code
+class="python">install_scripts</code>, and <code
+class="python">install_data</code>.
+
+</p>
+
+
+<h3>Building C extensions</h3>
+
+<p>
+
+<code class="python">build_ext_twisted</code> detects, based on
+various features of the platform, which C extensions to build. It
+overrides the <code class="python">build_extensions</code> method to
+first check which C extensions are appropriate to build for the
+current platform before proceeding as normal (by calling the
+superclass's <code class="python">build_extensions</code>). The
+module-detection consists of several simple tests for platform
+features and conditional additions to the `extensions' attribute. One
+especially useful feature is the <code
+class="python">_check_header</code> method, which takes the name of an
+arbitrary head file and tries to compile (via the distutil's C
+compiler interafce) a simple C file that only #includes it.
+
+</p>
+
+
+<h3>Installing scripts</h3>
+
+
+<p>
+
+<code class="python">install_data_twisted</code> ensures that the data
+files are installed along-side the python modules in the twisted
+package. This is accomplished with the incantation:
+
+</p>
+
+<pre class="python">
+class install_data_twisted(install_data):
+ def finalize_options (self):
+ self.set_undefined_options('install',
+ ('install_lib', 'install_dir')
+ )
+ install_data.finalize_options(self)
+</pre>
+
+
+
+<h3>Windows Releases</h3>
+
+<!--
+<p>
+This section will cover the problems with packaging Python projects
+for windows, especially ones which contain scripts. The problem of
+clickability is especially acute, as windows determines types by
+extensions and not by #! lines.
+</p>
+-->
+
+<p>
+
+Packaging software for windows involves a unique set of problems. The
+problem of clickability is especially acute; Several customizations to
+the distutils setup had to be made.
+
+</p>
+
+<p>
+
+The first customization was to make the <q>scripts</q> end with a
+<q>.py</q> extension, since Windows relies on extension rather than a
+she-bang line to specify what interpreter should execute a file. This
+was accomplished by overriding the <code
+class="python">install_scripts</code> command, like so:
+
+</p>
+
+<pre class="python">
+class install_scripts_twisted(install_scripts):
+ """Renames scripts so they end with '.py' on Windows."""
+
+ def run(self):
+ install_scripts.run(self)
+ if os.name == "nt":
+ for file in self.get_outputs():
+ if not file.endswith(".py"):
+ os.rename(file, file + ".py")
+</pre>
+
+
+<p>
+
+We also wanted to have a Start-menu group with a number of icons for
+running different Twisted programs. This was accomplished with a
+post-install script specified with the command-line parameter
+<code class="shell">--install-script=twisted_postinstall.py</code>.
+
+</p>
+
+
+
+<h2>Future Directions</h2>
+
+<p>
+
+The theme is, of course, automation, and there are still many manual
+steps involved in a Twisted release. The currently most annoying step
+is updating the documentation and downloads section of the
+twistedmatrix.com website. Automating this would be a major
+improvement to the time it takes from the running of the release
+script to a fully completed release.
+
+</p>
+
+<p>
+
+Another major improvement will involve further integration with
+BuildBot. Currently we have BuildBot running unit tests, building C
+extensions, and generating documentation on several hosts. Eventually
+we would like to have it constantly generating full release archives,
+and have an additional web form for <q>finalizing</q> any particular
+build that we deem releasable. The result would be uploading the
+release to the mirrors and updating the website.
+
+</p>
+
+<p>
+
+The tagging scheme used by the release-twisted scripts can sometimes
+be problematic. If we find serious problems in the code-base after the
+Tag command is executed (which is fairly early in the process), we are
+forced to fix the bug and increase the version number. This can be
+prevented by, instead of making the official tag, using the unofficial
+tag <q>releasing-$version</q> (as opposed to <q>release-$version</q>)
+at that early stage. Once most of the steps are complete, the official
+tag will be made. If something in between goes wrong, we can just
+re-use the unofficial <q>releasing-$version</q> tag and not worry
+about users trying to use that tag.
+
+</p>
+
+
+</body>
+</html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/tw-deploy b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/tw-deploy
new file mode 100755
index 0000000000..294bd7382d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/tw-deploy
@@ -0,0 +1,184 @@
+#!/usr/bin/python
+# Requires CVS Slides
+
+from slides import Lecture, Slide, TitleSlide, Image, Bullet, PRE, URL, SubBullet, NumSlide, toHTML
+
+PERL_PROCESSOR = """\
+from twisted.web import static, twcgi
+
+class PerlScript(twcgi.FilteredScript):
+ filter = '/usr/bin/perl' # Points to the perl parser
+"""
+
+RPY_EXAMPLE = """\
+from twisted.web import resource
+
+class MyGreatResource(resource.Resource):
+ def render(self, request):
+ return "<html>foo</html>"
+
+resource = MyGreatResource()
+"""
+
+lecture = Lecture(
+ "A Twisted Web Tutorial",
+
+ TitleSlide("Twisted Web -- A tutorial",
+ Image("twistedlogo.png"),
+ ),
+
+ Slide("Twisted Web -- Where does it fit?",
+ Image("twisted-overview.png"),
+ ),
+
+ Slide("Setup and Configuration Utilities",
+ Bullet("mktap"),
+ Bullet("twistd"),
+ Bullet("websetroot"),
+ ),
+
+ Slide("mktap",
+ Bullet("TAP Model"),
+ Bullet("General usage"),
+ Bullet("Flexibility and Power"),
+ ),
+
+ Slide("mktap web : Common Useful Options",
+ Bullet("--path"),
+ Bullet("--port"),
+ Bullet("--user"),
+ Bullet("--logfile"),
+ Bullet("--processor"),
+ ),
+
+ Slide("Sample mktap command lines",
+ Bullet(PRE("mktap web")),
+ Bullet(PRE("mktap web --path=/var/www --logfile=/var/log/twistedweb.log")),
+ Bullet(PRE("mktap web --port=80 --path=/var/www --user --mime-type=text/plain")),
+ Bullet(PRE("mktap web --path=/home/nafai/public_html --processor=.pl=PerlProcessor.PerlProcessor --index=index.pl")),
+ ),
+
+ Slide("twistd : An overview",
+ Bullet("Start a Twisted Application"),
+ Bullet("Loads and instance of twisted.internet.app.Application from a file"),
+ Bullet("Daemonizes, binds to appropriate ports, and starts the Twisted mainloop"),
+ ),
+
+ Slide("Sample twistd command lines",
+ Bullet(PRE("twistd -f web.tap -l /var/log/twisted.log")),
+ Bullet(PRE("twistd -f web.tap --pidfile /var/run/web.pid")),
+ ),
+
+ Slide("Shutting down twistd",
+ Bullet("On Unix (in general): "),
+ SubBullet(
+ Bullet(PRE("kill -9 `cat twistd.pid`"))),
+ Bullet("On Windows: "),
+ SubBullet(
+ Bullet("Cannot daemonize on Windows, so just run twistd in a command prompt"),
+ Bullet("Switch to the command prompt, and press Control-C"),
+ ),
+ ),
+
+ Slide("Shutdown TAPs",
+ Bullet("Since TAPs store persistent data for an application,\
+ a 'shutdown' TAP is created on twistd shutdown"),
+ Bullet("You'll often want to start your Twisted application\
+ on subsequent runs with the shutdown TAP"),
+ ),
+
+ Slide("twistd and security",
+ Bullet("When twistd is run as root, it will shed root privileges\
+ for the uid and gid of either the user that created the TAP or those\
+ specified on the mktap commandline."),
+ ),
+
+ # Try this out!
+ Slide("websetroot",
+ Bullet("Used to change what the root of the server points to"),
+ Bullet("Set it to a Resource contained either in a Python source file or a Pickle file"),
+ ),
+
+ Slide("Sample websetroot command lines",
+ Bullet(PRE("websetroot -p 80 -f web.tap --script rootResource.py")),
+ Bullet(PRE("websetroot -p 8080 -f web.tap --pickle rootPickle")),
+ ),
+
+ # Resource example
+ # Use the perl example from the docs
+ Slide("What's a Resource?",
+ Bullet("Everything in twisted in represented as twisted.web.resource.Resource object"),
+ Bullet("In general, two calls are made on a resource:"),
+ SubBullet(
+ Bullet(PRE("getChild()")),
+ Bullet(PRE("render()")),
+ ),
+ ),
+
+ Slide("Resource call examples:",
+ Bullet("/foo/bar/baz gets converted to:"
+ ),
+ SubBullet(
+ Bullet(PRE("site.getChild('foo', request).getChild('bar', request).getChild('baz', request).render(request)")),
+ ),
+ Bullet("/foo/bar/baz/ gets converted to:"
+ ),
+ SubBullet(
+ Bullet(PRE("site.getChild('foo', request).getChild('bar', request).getChild('baz', request).getChild('', request).render(request)")),
+ ),
+ ),
+
+ Slide("What do Resources handle?",
+ Bullet("Out of the box, Twisted supports files of all types"),
+ Bullet("HTML, text, etc."),
+ Bullet("Default MIME type can be specified"),
+ ),
+
+ Slide("What about web development?",
+ Bullet("Twisted Web has what are called processors, which are instances\
+ of classes inherited from resource.Resource"),
+ Bullet("By default, Twisted supports the following file types:"),
+ SubBullet(
+ Bullet(".php"),
+ Bullet(".php3"),
+ Bullet(".cgi"),
+ Bullet(".epy"),
+ Bullet(".rpy"),
+ Bullet(".trp"),
+ ),
+ Bullet("You can also write your own"),
+ ),
+
+ Slide("Custom Processor: More than One Evil Way to Do It",
+ Bullet("A custom processor to handle Perl CGIs:"),
+ PRE(PERL_PROCESSOR),
+ Bullet("An example of how to use:"),
+ SubBullet(Bullet(PRE("mktap web --path=/home/nafai/public_html --processor=.pl=PerlScript.PerlScript")),
+ ),
+ ),
+
+ Slide("What about making my own resources?",
+ Bullet("Define a class that inherits from resource.Resource"),
+ Bullet("Define the render() method on that class"),
+ Bullet("For long requests, render() can return NOT_DONE_YET"),
+ Bullet("Then Create a .rpy file that sets resource = to an instance of the class"),
+ ),
+
+ Slide(".rpy example",
+ PRE(RPY_EXAMPLE),
+ ),
+
+ Slide("More Stuff",
+ Bullet("In other words, the slides I didn't get to write..."),
+ SubBullet(
+ Bullet("Distributed Servers"),
+ Bullet("Virtual Hosts"),
+ Bullet("Rewrite Rules"),
+ Bullet("Debian configuration"),
+ Bullet("twistedmatrix.com configuration"),
+ ),
+ ),
+)
+
+if __name__ == '__main__':
+ lecture.renderHTML(".", "tw_deploy-%02d.html", css="main.css")
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/twisted-overview.png b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/twisted-overview.png
new file mode 100644
index 0000000000..6746a85a82
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/twisted-overview.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/twistedlogo.png b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/twistedlogo.png
new file mode 100644
index 0000000000..6226297c16
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/tw-deploy/twistedlogo.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-internet/twisted-internet.py b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-internet/twisted-internet.py
new file mode 100644
index 0000000000..1948d3e9d0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-internet/twisted-internet.py
@@ -0,0 +1,541 @@
+#!/usr/bin/python
+
+from slides import Lecture, Slide, Image, Bullet, PRE, URL, SubBullet, NumSlide, toHTML
+import os
+
+class Bad:
+ """Marks the text in red."""
+
+ def __init__(self, text):
+ self.text = text
+
+ def toHTML(self):
+ return '<font color="red">%s</font>' % toHTML(self.text)
+
+
+class Lecture(Lecture):
+
+ def getFooter(self):
+ return '<div class="footer"><hr noshade />Presented by <b>ZOTECA&nbsp;</b></div>'
+
+
+EVENT_LOOP_CODE = """\
+# pseudo-code reactor
+class Reactor:
+ def run(self):
+ while 1:
+ e = self.getNextEvent()
+ e.run()
+"""
+
+PROTOCOL_CODE = """\
+from twisted.internet.protocol import Protocol
+
+class Echo(Protocol):
+ def connectionMade(self):
+ print 'connection made with', self.transport.getPeer()
+ def dataReceived(self, data):
+ self.transport.write(data)
+ def connectionLost(self, reason):
+ print 'connection was lost, alas'
+"""
+
+SERVER_CODE = """\
+from twisted.internet.protocol import ServerFactory
+
+class EchoFactory(ServerFactory):
+
+ def buildProtocol(self, addr):
+ p = Echo()
+ p.factory = self
+ return p
+"""
+
+RUNNING_SERVER_CODE = """\
+from twisted.internet import reactor
+
+f = EchoFactory()
+reactor.listenTCP(7771, f)
+reactor.run()
+"""
+
+CLIENT_PROTOCOL_CODE = """\
+from twisted.internet.protocol import Protocol
+
+class MyClientProtocol(Protocol):
+ buffer = ''
+ def connectionMade(self):
+ self.transport.write('hello world')
+ def dataReceived(self, data):
+ self.buffer += data
+ if self.buffer == 'hello world':
+ self.transport.loseConnection()
+"""
+
+CLIENT_FACTORY_CODE = """\
+from twisted.internet.protocol import ClientFactory
+
+class MyFactory(ClientFactory):
+
+ protocol = MyClientProtocol
+
+ def startedConnecting(self, connector):
+ pass # we could connector.stopConnecting()
+ def clientConnectionMade(self, connector):
+ pass # we could connector.stopConnecting()
+ def clientConnectionLost(self, connector, reason):
+ connector.connect() # reconnect
+ def clientConnectionFailed(self, connector, reason):
+ print "connection failed"
+ reactor.stop()
+"""
+
+CLIENT_CONNECT_CODE = """\
+from twisted.internet import reactor
+
+reactor.connectTCP('localhost', 7771, MyFactory(), timeout=30)
+reactor.run()
+"""
+
+PULL_PRODUCER_CODE = """\
+class FileProducer:
+
+ def __init__(self, file, size, transport):
+ self.file = file; self.size = size
+ self.transport = transport # the consumer
+ transport.registerProducer(self, 0)
+
+ def resumeProducing(self):
+ if not self.transport: return
+ self.transport.write(self.file.read(16384))
+ if self.file.tell() == self.size:
+ self.transport.unregisterProducer()
+ self.transport = None
+
+ def pauseProducing(self): pass
+
+ def stopProducing(self):
+ self.file.close()
+ self.request = None
+"""
+
+PUSH_PRODUCER_CODE = """\
+from twisted.internet import reactor
+
+class GarbageProducer:
+
+ def __init__(self, transport):
+ self.paused = 0; self.stopped = 0
+ self.transport = transport
+ transport.registerProducer(self, 1)
+ self.produce()
+
+ def produce(self):
+ if not self.paused:
+ self.transport.write('blabla')
+ if not self.stopped:
+ reactor.callLater(0.1, self.produce)
+
+ def stopProducing(self):
+ self.stopped = 1
+
+ def pauseProducing(self):
+ self.paused = 1
+
+ def resumeProducing(self):
+ self.paused = 0
+"""
+
+SCHEDULING_CODE = """\
+from twisted.internet import reactor
+
+def f(x, y=1):
+ print x, y
+
+i = reactor.callLater(0.1, f, 2, y=4)
+i.delay(2)
+i.reset(1)
+i.cancel()
+"""
+
+FACTORY_START_CODE = """\
+from twisted.internet.protocol import ServerFactory
+
+class LogFactory(ServerFactory):
+
+ def startFactory(self):
+ self.log = open('log.txt', 'w')
+
+ def stopFactory(self):
+ self.log.close()
+"""
+
+LOGGING_CODE = """\
+from twisted.python import log
+
+# by default only errors are logged, to stderr
+logFile = open('log.txt', 'a')
+log.startLogging(logFile)
+
+log.msg('Something has occurred')
+"""
+
+LOGGING_ERRORS_CODE = """
+from twisted.python import log, failure
+
+e = ValueError('ONO')
+log.err(failure.Failure(e))
+
+try:
+ doSomethingElse()
+except:
+ log.deferr()
+"""
+
+SERVICE_CODE = """\
+from twisted.internet import app
+
+class FooService(app.ApplicationService):
+ def startService(self):
+ # do startup stuff
+ def stopService(self):
+ # do shutdown stuff
+ def foobrizate(self):
+ # business logic!
+
+application = app.Application('foobnator')
+svc = FooService('foo', application)
+application.getServiceNamed('foo') is svc # True
+"""
+
+RUNNABLE_APP_CODE = """\
+# this is web.py
+from twisted.internet import app
+from twisted.web import static, server
+
+application = app.Application('web')
+application.listenTCP(8080, server.Site(static.File('/var/www')))
+
+if __name__ == '__main__':
+ application.run(save=0)
+"""
+
+TWISTD_CODE = """\
+$ twistd -y web.py
+$ lynx http://localhost:8080
+$ kill `cat twistd.pid`
+"""
+
+GUI_CODE = """\
+from twisted.internet import gtkreactor
+gtkreactor.install()
+import gtk
+w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+w.show_all()
+from twisted.internet import reactor
+reactor.run()
+"""
+
+lecture = Lecture(
+ "The twisted.internet Tutorial of Doom",
+
+ Slide("Part 1 - Introduction"),
+
+ # there are different ways to do networking
+ # mention processes are not cross-platform
+ Slide("Choosing a networking paradigm for the enterprise",
+ Bullet("Event driven"),
+ Bullet(Bad("Threads")),
+ Bullet("Others which we will ignore (processes, SEDA, ...)")),
+
+ # it's a metaphor!
+ Slide("Applied Bistromathics 101",
+ Bullet("Consider a restaurant as a network application"),
+ Bullet("Clients come in, make requests to the waiters"),
+ Bullet("Waiters act on clients' choices")),
+
+ # an event loop is efficient, doesn't waste time
+ # event loop is also used for GUIs
+ Slide("The event driven waiter",
+ Bullet("One waiter, serving all tables"),
+ Bullet("Waiter takes orders from tables to kitchen"),
+ Bullet("Waiter takes food from kitchen to tables")),
+
+ # not accurate, but the problems are real. avoid threads if you can
+ Slide("Threads (a caricature)",
+ Bullet(Bad("One waiter per table")),
+ SubBullet("Problems:",
+ Bullet(Bad("Expensive")),
+ Bullet(Bad("Waiters need to be careful not bump into each other")),
+ )),
+
+ # why threads are sometimes necessary
+ Slide("When do we want threads?",
+ Bullet("Long running, blocking operations"),
+ Bullet("Classic example: database access")),
+
+ # today we will discuss only (parts of) twisted.internet
+ Slide("Twisted: The Framework of Your Internet",
+ Image("twisted-overview.png")),
+
+ Slide("Project Stats",
+ Bullet("URL: ", URL("http://www.twistedmatrix.com")),
+ Bullet("License: LGPL"),
+ Bullet("Number of developers: approximately 20"),
+ Bullet("Version: 1.0.3"),
+ Bullet("Platforms: Unix, Win32"),
+ Bullet("Started in January 2000 by Glyph Lefkowitz")),
+
+ Slide("Part 2 - Basic Networking With Twisted"),
+
+ # quick review of how the internet works
+ Slide("Internet!",
+ Bullet("Network of interconnected machines"),
+ Bullet("Each machine has one (or more) IP addresses"),
+ Bullet("DNS maps names ('www.yahoo.com') to IPs (216.109.125.69)"),
+ Bullet("TCP runs on top of IP, servers listen on of of 65536 ports,"
+ " e.g. HTTP on port 80"),),
+
+ # we need to understand certain basic terms before we continue.
+ # the event loop is the last thing we run - it waits until
+ # an event occurs, then calls the appropriate handler.
+ Slide("Basic Definitions - Reactor",
+ Bullet("An object implementing the event loop",
+ PRE(EVENT_LOOP_CODE))),
+
+ Slide("Basic Definitions - Transport",
+ Bullet("Moves data from one location to another"),
+ Bullet("Main focus of talk are ordered, reliable byte stream transports"),
+ Bullet("Examples: TCP, SSL, Unix sockets"),
+ Bullet("UDP is a different kind of transport")),
+
+ # the client is the side which initiated the connection
+ # HTTP and SSH run on TCP-like transports, DNS runs on UDP or TCP
+ Slide("Basic Definitions - Protocol",
+ Bullet("Defines the rules for communication between two hosts"),
+ Bullet("Protocols communicate using a transport"),
+ Bullet("Typically there is a client, and server"),
+ Bullet("Examples: HTTP, SSH, DNS")),
+
+ Slide("All Together Now",
+ Bullet("The reactor gets events from the transports (read from network, write to network)"),
+ Bullet("The reactor passes events to protocol (connection lost, data received)"),
+ Bullet("The protocol tells the transport to do stuff (write data, lose connection)")),
+
+ # designing a new protocol is usually a bad idea, there are lots of
+ # things you can get wrong, both in design and in implementation
+ Slide("How To Implement A Protocol",
+ Bullet("Hopefully, you don't.")),
+
+ # XXX split into three expanded slides?
+ NumSlide("How To Not Implement A Protocol",
+ Bullet("Use an existing Twisted implementation of the protocol"),
+ Bullet("Use XML-RPC"),
+ Bullet("Use Perspective Broker, a remote object protocol")),
+
+ # connectionMade is called when connection is made
+ # dataReceived is called every time we receive data from the network
+ # connectionLost is called when the connection is lost
+ Slide("How To Really Implement A Protocol",
+ PRE(PROTOCOL_CODE)),
+
+ # factories - why?
+ Slide("Factories",
+ Bullet("A protocol instance only exists as long as the connection is there"),
+ Bullet("Protocols want to share state"),
+ Bullet("Solution: a factory object that creates protocol instances")),
+
+ # factory code - notice how protocol instances have access to the factory
+ # instance, for shared state. buildProtocol can return None if we don't
+ # want to accept connections from that address.
+ Slide("A Server Factory",
+ PRE(SERVER_CODE)),
+
+ # running the server we just wrote
+ Slide("Connecting A Factory To A TCP Port",
+ PRE(RUNNING_SERVER_CODE)),
+
+ # transport independence - using listenUNIX as example
+ Slide("Transport Independence",
+ Bullet("Notice how none of the protocol code was TCP specific"),
+ Bullet("We can reuse same protocol with different transports"),
+ Bullet("We could use listenUNIX for unix sockets with same code"),
+ Bullet("Likewise listenSSL for SSL or TLS")),
+
+ Slide("Client Side Protocol",
+ PRE(CLIENT_PROTOCOL_CODE)),
+
+ # client connections are different
+ Slide("Client Side Factories",
+ Bullet("Different requirements than server"),
+ Bullet("Failure to connect"),
+ Bullet("Automatic reconnecting"),
+ Bullet("Cancelling and timing out connections")),
+
+ # example client factory - explain use of default buildProtocol
+ Slide("Client Side Factories 2",
+ PRE(CLIENT_FACTORY_CODE)),
+
+ # connectTCP
+ Slide("Connection API",
+ PRE(CLIENT_CONNECT_CODE)),
+
+ # explain how transports buffer the output
+ Slide("Buffering",
+ Bullet("When we write to transport, data is buffered"),
+ Bullet("loseConnection will wait until all buffered data is sent, and producer (if any) is finished")),
+
+ # start/stopFactory
+ Slide("Factory Resources",
+ Bullet("Factories may want to create/clean up resources"),
+ Bullet("startFactory() - called on start of listening/connect"),
+ Bullet("stopFactory() - called on end of listening/connect"),
+ Bullet("Called once even if factory listening/connecting multiple ports")),
+
+ # example of restartable factory
+ Slide("Factory Resources 2",
+ PRE(FACTORY_START_CODE)),
+
+ Slide("Producers and Consumers",
+ Bullet("What if we want to send out lots of data?"),
+ Bullet("Can't write it out all at once"),
+ Bullet("We don't want to write too fast")),
+
+ Slide("Producers",
+ Bullet("Produce data for a consumer, in this case by calling transport's write()"),
+ Bullet("Pausable (should implement pauseProducing and resumeProducing methods)"),
+ Bullet("Push - keeps producing unless told to pause"),
+ Bullet("Pull - produces only when consumer tells it to")),
+
+ Slide("Consumers",
+ Bullet("registerProducer(producer, streaming)"),
+ Bullet("Will notify producer to pause if buffers are full")),
+
+ Slide("Sample Pull Producer",
+ PRE(PULL_PRODUCER_CODE)),
+
+ Slide("Sample Push Producer",
+ PRE(PUSH_PRODUCER_CODE)),
+
+ # scheduling events
+ Slide("Scheduling",
+ PRE(SCHEDULING_CODE)),
+
+ # pluggable reactors - why?
+ Slide("Choosing a Reactor - Why?",
+ Bullet("GUI toolkits have their own event loop"),
+ Bullet("Platform specific event loops")),
+
+ Slide("Choosing a Reactor",
+ Bullet("Twisted supports multiple reactors"),
+ Bullet("Default, gtk, gtk2, qt, win32 and others"),
+ Bullet("Tk and wxPython as non-reactors"),
+ Bullet("Reactor installation should be first thing code does")),
+
+ # example GUI client
+ Slide("Example GTK Program",
+ PRE(GUI_CODE)),
+
+ # you can learn more about
+ Slide("Learning more about networking and scheduling",
+ Bullet("twisted.internet.interfaces"),
+ Bullet("http://twistedmatrix.com/document/howtos/")),
+
+
+ Slide("Part 3 - Building Applications With Twisted"),
+
+ # the concept of the application
+ Slide("Applications",
+ Bullet("Reactor is a concept of event loop"),
+ Bullet("Application is higher-level"),
+ Bullet("Configuration, services, persistence"),
+ Bullet("Like reactor, you can listenTCP, connectTCP, etc.")),
+
+ # services concept
+ Slide("Services",
+ Bullet("Services can be registered with Application"),
+ Bullet("A service encapsulates 'business logic'"),
+ Bullet("Infrastructure outside the scope of protocols"),
+ Bullet("Examples: authentication, mail storage")),
+
+ # service example code
+ Slide("Services 2",
+ PRE(SERVICE_CODE)),
+
+ # logging
+ Slide("Logging",
+ PRE(LOGGING_CODE)),
+
+ # logging errors
+ # explain why this is good idea (twistd -b)
+ Slide("Logging Errors",
+ PRE(LOGGING_ERRORS_CODE)),
+
+ # twistd idea
+ Slide("twistd - Application Runner",
+ Bullet("Single access point for running applications"),
+ Bullet("Separate configuration from deployment")),
+
+ # twistd features
+ Slide("twistd Features",
+ Bullet("Daemonization"),
+ Bullet("Log file selection (including to syslog)"),
+ Bullet("Choosing reactor"),
+ Bullet("Running under debugger"),
+ Bullet("Profiling"),
+ Bullet("uid, gid"),
+ Bullet("Future: WinNT Services")),
+
+ # making modules for twistd -y
+ Slide("Making a runnable application",
+ PRE(RUNNABLE_APP_CODE)),
+
+ # running the server
+ Slide("Running twistd",
+ PRE(TWISTD_CODE)),
+
+ Slide("Part 4: Further Bits and Pieces"),
+
+ Slide("Other twisted.internet Features",
+ Bullet("UDP, Multicast, Unix sockets, Serial"),
+ Bullet("Thread integration")),
+
+ Slide("Deferreds",
+ Bullet("Deferred - a promise of a result"),
+ Bullet("Supports callback chains for results and exceptions"),
+ Bullet("Used across the whole framework"),
+ Bullet("Make event-driven programming much easier"),
+ Bullet("Can work with asyncore too, not just Twisted")),
+
+ Slide("Protocol implementations",
+ Bullet("Low-level implementations, without policies"),
+ Bullet("SSH, HTTP, SMTP, IRC, POP3, telnet, FTP, TOC, OSCAR, SOCKSv4, finger, DNS, NNTP, IMAP, LDAP"),
+ Bullet("Common GPS modem protocols")),
+
+ Slide("Frameworks",
+ Bullet("twisted.web - Web server framework"),
+ Bullet("twisted.news - NNTP server framework"),
+ Bullet("twisted.words - messaging framework"),
+ Bullet("twisted.names - DNS server")),
+
+ Slide("Perspective Broker",
+ Bullet("Object publishing protocol"),
+ Bullet("Fast, efficient and extendable"),
+ Bullet("Two-way, asynchronous"),
+ Bullet("Secure and encourages secure model"),
+ Bullet("Implemented in Python for Twisted, and Java")),
+
+ Slide("Lore",
+ Bullet("Simple documentation system"),
+ Bullet("Simple subset of XHTML"),
+ Bullet("Generates LaTeX, XHTML")),
+
+ Slide("Reality",
+ Bullet("Multiplayer text simulation framework"),
+ Bullet("Original source of Twisted project"),
+ Bullet("Now a totally different project")),
+)
+
+
+if __name__ == '__main__':
+ lecture.renderHTML(".", "twisted_internet-%02d.html", css="main.css")
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-reality/componentized.svg b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-reality/componentized.svg
new file mode 100644
index 0000000000..613192a2dc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-reality/componentized.svg
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
+<svg
+ id="svg137"
+ sodipodi:version="0.31"
+ width="11in"
+ height="8in"
+ sodipodi:docbase="/home/washort/projects/PyCon/"
+ sodipodi:docname="/home/washort/projects/PyCon/adapters2.svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs
+ id="defs139" />
+ <sodipodi:namedview
+ id="base"
+ snaptoguides="false"
+ showgrid="false" />
+<g>
+<animateTransform attributeName="transform" attributeType="XML" begin="1s" dur="3s" values="1,1;1.5,1.5" type="scale" fill="freeze" additive="sum" />
+<animateMotion begin="1s" dur="3s" fill="freeze" path="M 0 0 L -40 -300"/>
+<g style="fill-rule:evenodd;stroke-width:10;stroke:#000000;stroke-opacity:1;stroke-dasharray:none;stroke-linejoin:bevel;fill-opacity:1;">
+<animate attributeName="stroke-opacity" attributeType="CSS" begin="1s" dur="3s" from="1" to="0.4" fill="freeze"/>
+<animate attributeName="fill-opacity" attributeType="CSS" begin="1s" dur="3s" from="1" to="0.4" fill="freeze"/>
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#3e62db"
+ id="polygon152"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,462.812,244.007)" />
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#d86264"
+ id="polygon234"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,634.521,144.935)" />
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#d86264"
+ id="polygon243"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,634.604,342.45)" />
+
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;;fill:#d86264"
+ id="polygon252"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,290.377,145.564)" />
+
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#d86264"
+ id="polygon261"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,462.169,442.179)" />
+
+ <polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#d86264"
+ id="polygon270"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,462.812,45.8392)" />
+
+ <g style="stroke-width:2pt">
+ <path
+ d="M 538.285 387.439 L 571.873 407.105 "
+ id="path372"
+ transform="translate(171.25,-20)" />
+ <path
+ d="M 538.285 308.11 L 572.315 288.223 "
+ id="path373"
+ transform="translate(171.25,-20)" />
+ <path
+ d="M 365.044 287.781 L 400.399 308.552 "
+ id="path376"
+ transform="translate(171.25,-20)" />
+ <path
+ d="M 468.9 465.883 L 468.9 426.993 "
+ id="path377"
+ transform="translate(171.25,-20)" />
+ <path
+ d="M 469.342 268.335 L 469.342 229.445 "
+ id="path378"
+ transform="translate(171.25,-20)" /></g>
+</g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-opacity:0.398649;stroke-width:2pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1;"
+ d="M 365.928 407.547 L 399.957 387.66 "
+ id="path379"
+ transform="translate(171.25,-20)" />
+<polygon
+ sodipodi:type="star"
+ style="font-size:12;fill:#d86264;fill-rule:evenodd;stroke-width:10;stroke:#000000;stroke-opacity:1;stroke-dasharray:none;stroke-linejoin:bevel;fill-opacity:1;"
+ id="polygon225"
+ sodipodi:sides="6"
+ sodipodi:cx="355.625061"
+ sodipodi:cy="167.987030"
+ sodipodi:r1="183.750000"
+ sodipodi:r2="159.132095"
+ sodipodi:arg1="0.000000"
+ sodipodi:arg2="0.523599"
+ points="539.375,167.987 493.437,247.553 447.5,327.119 355.625,327.119 263.75,327.119 217.813,247.553 171.875,167.987 217.813,88.4209 263.75,8.85486 355.625,8.85493 447.5,8.85486 493.438,88.421 539.375,167.987 "
+ transform="matrix(0.5,0,0,0.5,290.836,343.189)" />
+<g style="fill:#000000;stroke:none;font-family:URW Gothic L;font-style:normal;font-weight:normal;font-size:36;fill-opacity:1;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;text-anchor:start;writing-mode:lr;" >
+<animate attributeName="fill-opacity" attributeType="CSS" begin="1s" dur="3s" from="1" to="0.4" fill="freeze"/>
+ <text
+ x="594.546"
+ y="312.197"
+ id="text383">
+ <tspan
+ x="594.546"
+ y="312.197"
+ sodipodi:role="line"
+ id="tspan476">
+Thing</tspan>
+ <tspan
+ x="594.546"
+ y="348.197"
+ sodipodi:role="line"
+ id="tspan478">
+</tspan>
+ </text>
+ <text
+ x="394.315"
+ y="230.363"
+ id="text388">
+ <tspan
+ x="394.315"
+ y="230.363"
+ sodipodi:role="line"
+ id="tspan504">
+Weapon</tspan>
+ </text>
+
+ <text
+ x="569.441"
+ y="524.279"
+ id="text396">
+ <tspan
+ x="569.441"
+ y="524.279"
+ sodipodi:role="line"
+ id="tspan460">
+Portable</tspan>
+ </text>
+ <text
+ style="font-size:18;font-style:italic"
+ x="561.266"
+ y="347.885"
+ id="text480">
+ <tspan
+ x="561.266"
+ y="347.885"
+ sodipodi:role="line"
+ id="tspan496">
+(Componentized)</tspan>
+ </text>
+
+ <text
+ style="font-size:18;font-style:italic"
+ x="594.854"
+ y="548.526"
+ id="text498">
+ <tspan
+ x="594.854"
+ y="548.526"
+ sodipodi:role="line"
+ id="tspan499">
+(Adapter)</tspan>
+ </text>
+ <text
+ style="font-size:18;font-style:italic"
+ x="422.496"
+ y="254.194"
+ id="text501">
+ <tspan
+ x="422.496"
+ y="254.194"
+ sodipodi:role="line"
+ id="tspan502">
+(Adapter)</tspan>
+ </text>
+ </g>
+<text
+ style="fill:#000000;stroke:none;font-family:URW Gothic L;font-style:italic;font-weight:normal;font-size:18;text-anchor:start;writing-mode:lr;fill-opacity:1;troke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;"
+ x="422.496"
+ y="451.299"
+ id="text489">
+ <tspan
+ x="422.496"
+ y="451.299"
+ sodipodi:role="line"
+ id="tspan494">
+(Adapter)</tspan>
+ </text>
+ <text
+ style="fill:#000000;stroke:none;font-family:URW Gothic L;font-style:roman;font-weight:normal;font-size:26;text-anchor:start;writing-mode:lr;fill-opacity:1;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;"
+ x="388.457"
+ y="430.303"
+ id="text393">
+ <tspan
+ x="388.457"
+ y="430.303"
+ sodipodi:role="line"
+ id="tspan458">
+Merchandise</tspan>
+ </text>
+</g>
+</svg>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-reality/twisted-reality.html b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-reality/twisted-reality.html
new file mode 100644
index 0000000000..bb96500e3b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2003/pycon/twisted-reality/twisted-reality.html
@@ -0,0 +1,578 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html>
+ <head>
+ <title>Twisted Reality: A Flexible Framework for Virtual Worlds</title>
+ <link href="stylesheet.css" type="text/css" rel="stylesheet" />
+ </head>
+ <body>
+ <h1>Twisted Reality: A Flexible Framework for Virtual Worlds</h1>
+
+ <ul>
+ <li>Allen Short
+<a href="washort@twistedmatrix.com">&lt;washort@twistedmatrix.com&gt;</a></li>
+ <li>Glyph Lefkowitz
+ <a href="glyph@twistedmatrix.com">&lt;glyph@twistedmatrix.com&gt;</a></li>
+ </ul>
+
+ <h2>Abstract</h2>
+ <p>Flexibly modelling virtual worlds in object-oriented languages has
+ historically been difficult; the issues arising from multiple
+ inheritance and order-of-execution resolution have limited the
+ sophistication of existing object-oriented simulations. Twisted
+ Reality avoids these problems by reifying both actions and
+ relationships, and avoiding inheritance in favor of automated
+ composition through adapters and interfaces.</p>
+
+ <h2>Motivation</h2>
+
+ <p>Text-based simulations have a long and venerable history, from
+ games such as Infocom's Zork and Bartle's MUD to modern systems
+ such as Inform, LambdaMOO and Cold. The general trend in the
+ development of these systems has been toward domain-specific
+ languages, which has largely been an improvement. However, a
+ discrepancy remains between systems for single-user and
+ multiple-user simulations: in single-user systems such as Inform,
+ incremental extensibility has been sacrificed to allow for complex
+ interaction with the world; whereas in multiple-user systems,
+ incremental extensibility is paramount, but it is achieved at the
+ cost of a much simpler model of interaction. Twisted Reality aims
+ to bring the sophistication of Inform's action model to multiuser
+ simulation.</p>
+
+
+<h2>The Twisted Component Model</h2>
+
+<p>Twisted's component system is almost identical to Zope 3's. The
+primary element is the interface, a class used as a point of
+integration and documentation. Classes may declare the interfaces they
+implement by setting their <code class="python">__implements__</code>
+attribute to a tuple of interfaces. Additional interfaces may be added
+to classes with <code
+class="python">registerAdapter(adapterClass,originalClass,interface)</code>;
+when <code class="python">getAdapter(obj, interfaceClass)</code> is
+called on an object, the adapter associated with that interface and
+class is looked up and instantiated as a wrapper around <code
+class="python">obj</code>. (Alternately, if <code
+class="python">obj</code> implements the requested interface, the
+original object is simply returned.)</p>
+
+<h3>Componentized</h3>
+<p>In addition to the basic system of adapters and interfaces, Twisted
+has the <code class="python">Componentized</code> class. Instances of
+<code class="python">Componentized</code> hold instances of their
+adapters. This storage of adapter instances encourages separation of
+concerns; multiple related instances representing aspects of a
+simulation object can be automatically composed in a single
+Componentized instance.</p>
+
+<p><code class="python">Componentized</code> is the heart of Twisted
+Reality; it is subclassed by <code class="python">Thing</code>, the
+base class for all simulation objects. Functionality is added to
+<code class="python">Thing</code>s with adapters; for example, the
+<code class="python">Portable</code> adapter adds the abilities to be
+picked up and dropped. </p>
+
+<p>By separating aspects of the simulation object into multiple
+instances, several improvements in ease of code maintenance can be
+realized. Persistence of simulation objects, for example, is greatly
+eased by <code class="python">Componentized</code>: each adapter's
+state can be stored in a separate database table or similar data
+store.</p>
+
+<h2>Parsing System</h2>
+
+<p>The key element missing from multiuser simulations' parsing systems
+is an abstract representation of actions. Current systems proceed
+directly from parsing the user's input to executing object-specific
+code. For example, LambdaMOO, one of the most popular object-oriented
+simulation frameworks, handles input using a non-customizable lexer
+which dispatches to parsing methods on simulation objects. The
+ColdCore framework, a similar effort, improves on this model by
+providing pattern-matching facilities for the lexer, but performs
+dispatch in essentially the same fashion. In contrast to these
+systems, Twisted Reality separates parsing from simulation objects
+entirely, keeping a global registry of parser methods which produce
+objects representing actions, rather than directly performing the
+actions. Adding this layer allows for more sophisticated parsing and
+sensitivity to ambiguity.</p>
+
+<p>The parser in <code class="python">reality.text.english</code> uses
+a relatively simple strategy: it keeps a parser registry which maps
+<q>verbs</q> (i.e., substrings at the beginning of the user input) to
+parser methods, and runs all methods whose prefixes match the input,
+collecting the actions they return. Parsing methods are added to the
+system by registering <code class="python">Subparser</code>s. </p>
+
+<pre class="python">class MusicParser(english.Subparser):
+ def parse_blow(self, player, instrumentName):
+ actor = player.getComponent(IPlayWindInstrumentActor)
+ if actor is None:
+ return []
+ return [PlayWindInstrument(actor, instrumentName)]
+
+english.registerSubparser(MusicParser())</pre>
+
+<p><code class="python">english.registerSubparser</code> collects
+ methods prefixed with <code class="python">parse_</code> from
+ the subparser and places them in the parsing registry.</p>
+
+<pre class="shell">a Room
+You see a rocket, a whistle, and a candle.
+Exits: a door, north
+bob: <b>blow whistle</b>
+You play a shrill blast upon a whistle.</pre>
+
+<p>Here is one of the simplest cases for the parser: <q><code
+class="shell">blow whistle</code></q> should obviously resolve to a
+single action, in this case <code class="python"
+>PlayWindInstrument</code>.</p>
+
+<p>The parser calls <code class="python">MusicParser.parse_blow</code>
+with the actor and the remainder of the input, and adds the list of
+actions it returns to the collection of possible actions. If only one
+action is possible, it immediately dispatches it. This strategy allows
+the parser to examine the state of the simulation before committing to
+a decision about what the player means. For example, the check for the
+actor interface is a simple form of permissions; if you don't
+implement the required interface, you aren't allowed to perform the
+action.</p>
+
+<p>Since this sort of parser is quite common, it has been generalized
+ to a simple mapping of command names to actions:</p>
+<pre class="python">class FireParser(english.Subparser):
+ simpleTargetParsers = {"blow": Extinguish}
+
+english.registerSubparser(FireParser())</pre>
+<pre class="shell">bob: <b>blow candle</b>
+You blow out a candle.
+</pre>
+<p>The real test of any parsing system of this nature, of course, is
+its ability to handle ambiguity. Since two possibilities for
+parsing a command starting with <q>blow</q> now exist, the parser has two
+potential actions to examine: <code class="python"
+>PlayWindInstrument</code> and <code class="python"
+>Extinguish</code>. Obviously, only <code class="python"
+>Extinguish</code> makes sense, and the parser determines this by
+examining the interfaces on the targets and rejecting actions for
+which the target is invalid.</p>
+
+<pre class="python">class ExplosivesParser(english.Subparser):
+ simpleToolParsers = {"blow": BlowUp}
+
+english.registerSubparser(ExplosivesParser())
+</pre>
+<pre class="shell">bob: <b>blow door</b>
+You fire a rocket at a door.
+*BOOM*!!
+The door shatters to pieces!
+</pre>
+
+<p> The other common case is actions with three participants -- actor,
+target, and tool. The parser generated here is intelligent enough to
+look around for an appropriate tool (again, by examining interfaces)
+and include it in the action.</p>
+
+<p>Despite these techniques for disambiguating the user's meaning,
+situations will inevitably arise where multiple actions are equally
+valid parses. In these cases, the parser formats the list of potential
+actions and presents the choices to the user.</p>
+
+<pre class="shell">You see a short sword, and a long sword.
+bob: <b>get sword</b>
+Which Target?
+1: long sword
+2: short sword
+bob: <b>1</b>
+You take a long sword.</pre>
+
+<h2>Actions System</h2>
+
+
+<p>Actions in Twisted Reality, as in Inform, are objects representing
+a successful parse of a player's intentions. Actions are classified
+according to the number of objects they operate upon: <code
+class="python">NoTargetAction</code> (actions such as <code
+class="python">Say</code> or <code class="python">Look</code>), <code
+class="python">TargetAction</code> (e.g. <code
+class="python">Eat</code>, <code class="python">Wear</code>), <code
+class="python">ToolAction</code> (e.g. <code
+class="python">Open</code>, <code class="python">Take</code>). When
+actions are defined, interfaces corresponding to the possible roles in
+the action are also created. When an action is instantiated, it asks
+the participants in the action to adapt themselves to the actor,
+target, or tool interfaces, as appropriate. When dispatched, the
+action may call handler methods on the adapted objects or dispatch
+subsidiary actions.</p>
+
+<pre class="python">IDamageActor = things.IThing
+class Damage(actions.ToolAction):
+ def formatToActor(self):
+ with = ""
+ if self.tool:
+ with = " with ", self.tool
+ return ("You hit ",self.target) + with + (".",)
+ def formatToTarget(self):
+ with = ()
+ if self.tool:
+ with = " with ", self.tool
+ return (self.actor," hits you") + with + (".",)
+ def formatToOther(self):
+ with = ""
+ if self.tool:
+ with = " with ", self.tool
+ return self.actor," hits ",self.target) + with + (".",)
+ def doAction(self):
+ amount = self.tool.getDamageAmount()
+ self.target.damage(amount)
+
+class Weapon(components.Adapter):
+ __implements__ = IDamageTool
+ def getDamageAmount(self):
+ return 10
+
+class Damageable(components.Adapter):
+ __implements__ = IDamageTarget
+ def damage(self, amount):
+ self.original.emitEvent("Ow! that hurt. You take %d points of damage."
+ % amount, intensity=1)
+class HarmParser(english.Subparser):
+ simpleToolParsers = {"hit":Damage}
+
+english.registerSubparser(HarmParser())
+components.registerAdapter(Damageable, things.Actor, IDamageTarget)</pre>
+
+<p><code class="python">actions.ToolAction</code>, via metaclass
+magic, creates three interfaces when subclasssed, named after the
+subclass: in this case, <code class="python">IDamageActor</code>,
+<code class="python" >IDamageTarget</code>, and <code
+class="python">IDamageTool</code>. However, since <code
+class="python">IDamageActor</code> already exists, the metaclass does
+ not clobber it. Setting <code class="python">IDamageActor</code> to
+<code class="python">IThing</code> indicates that any <code
+class="python">Thing</code> may perform the <code
+ class="python">Damage</code> action. The other elements of the action
+are represented here by <code class="python">Weapon</code> and <code
+class="python">Damageable</code> as the tool and the target,
+respectively. The <code class="python">HarmParser</code> adds a
+<q>hit</q> command, and the call to <code
+class="python">registerAdapter</code> ensures that any <code
+class="python">Actor</code>s who do not already have a
+component implementing <code class="python">IDamageTarget</code> will
+receive a <code class="python">Damageable</code> when needed.
+</p>
+<pre class="python">room = ambulation.Room("room")
+bob = things.Actor( "Bob")
+rodney = things.Actor("rodneY")
+sword = things.Movable("sword")
+
+sword.addAdapter(conveyance.Portable, True)
+sword.addAdapter(harm.Weapon, True)
+
+
+for o in rodney, bob, sword:
+ o.moveTo(room)
+</pre>
+
+<p>In this example, we create instances of <code
+class="python">Movable</code> <code class="python">Actor</code>
+(subclasses of <code class="python">Thing</code>), a <code
+class="python">Room</code>, then adds a <code
+class="python">Portable</code> adapter to the sword, allowing it to be
+picked up and dropped, as well as a <code class="python">Weapon</code>
+adapter, and finally moves all three into the room.</p>
+
+<pre class="shell">a room
+You see rodneY, and a sword.
+Bob: <b>get sword</b>
+You take a sword.
+Bob: <b>hit rodney with sword</b>
+You hit rodneY with a sword.</pre>
+
+<p>The parser instantiates the <code class="python">Damage</code>
+action with Bob, Rodney, and the sword as actor, target, and tool. The
+action is dispatched, calling <code
+class="python">Damage.doAction</code>, which inflicts damage upon
+Rodney. From Rodney's perspective:</p>
+
+<pre class="shell">a room
+You see Bob, and a sword.
+Bob takes a sword.
+Bob hits you with a sword.
+Ow! that hurt. You take 10 points of damage.
+rodneY:</pre>
+
+<p>The primary advantage of this actions system is that it provides a
+central point for dispatching object-specific behaviour in a
+customizable manner. This mechanism prevents order-of-execution
+problems: in other simulations of this type, combining multiple game
+effects is difficult since the connections between them are not made
+explicit. When confronted with ambiguity, TR's action system refuses
+to guess: all combinations of effects that make sense must be
+implemented separately. The Adapters system makes this manageable even
+in the face of arbitrarily extended complexity.</p>
+
+<p>Also, it allows for centralized handling of string formatting,
+instead of having each actor or target handle output of event
+descriptions. For example, suppose there is a zone prohibiting PvP
+combat. The <code class="python">Damage</code> action can suppress the
+usual messages describing combat (as well as the actual damage
+routines) since it is responsible for generating them.</p>
+
+
+<h2>Composing Simulations with Adapters</h2>
+
+<p>The combination of these features -- an incrementally extendable
+parser, actions as first-class objects, componentized simulation
+objects -- provide a powerful basis for the composition of simulations
+within a virtual world, often enabling extensions to the world and
+object behaviour without touching unrelated code. For example, to add
+armor that reduces damage to the simple combat simulation described
+above, we add an <code class="python">Armor</code> class which
+forwards the <code class="python">IDamageTarget</code> interface:</p>
+<pre class="python">class Armor(raiment.Wearable):
+ __implements__ = IDamageTarget, raiment.IWearTarget, raiment.IUnwearTarget
+ originalTarget = None
+ armorCoefficient = 0.5
+ def dress(self, wearer):
+ originalTarget = wearer.getComponent(IDamageTarget)
+ if originalTarget:
+ self.originalTarget = originalTarget
+ wearer.original.setComponent(IDamageTarget, self)
+
+ def undress(self, wearer):
+ if self.originalTarget:
+ wearer.setComponent(IDamageTarget, self.originalTarget)
+
+ def damage(self, amount):
+ self.original.emitEvent("Your armor cushions the blow.", intensity=2)
+ if self.originalTarget:
+ self.originalTarget.damage(amount * self.armorCoefficient)</pre>
+
+<p><code class="python">Armor</code> inherits from the <code
+class="python">Wearable</code> adapter, and thus receives notification
+of the player wearing or removing it. When this happens, it forwards
+or unforwards the <code class="python">damage</code> method,
+respectively.</p>
+<pre class="shell">a room
+You see an armor, Bob, and a sword.
+rodneY: <b>take armor</b>
+You take an armor.
+rodneY: <b>wear armor</b>
+You put on an armor.
+Bob hits you with a sword.
+Your armor cushions the blow.
+Ow! that hurt. You take 5 points of damage.</pre>
+
+<p>In this fashion, the combat simulation can be extended to deal with
+various types of weapons, armor, damageable objects, and types of
+damage, with little or no changes to existing code.</p>
+
+<p> Now, let us consider a second type of simulation common to virtual
+ worlds: shops. We wish to prevent unpaid items from leaving the shop,
+ and to have a price associated with each item.</p>
+
+<pre class="python">
+class IVendor(components.Interface): pass
+class IMerchandise(components.Interface): pass
+
+class Buy(actions.TargetAction):
+ def formatToOther(self):
+ return ""
+ def formatToActor(self):
+ return ("You buy ",self.target," from ",self.vendor," for ",
+ self.target.price," zorkmids.")
+
+ def doAction(self):
+ vendors = self.actor.original.lookFor(None, IVendor)
+ if vendors:
+ #assume only one vendor per room, for now
+ self.vendor = vendors[0]
+ else:
+ raise errors.Failure("There appears to be no shopkeeper here "
+ "to receive your payment.")
+ amt = self.target.price
+ self.actor.withdraw(amt)
+ self.vendor.buy(self.target, amt)
+
+class ShopParser(english.Subparser):
+ simpleTargetParsers = {"buy": Buy}
+english.registerSubparser(ShopParser())</pre>
+
+<p>The basic behaviour for buying an object in a shop is simple:
+first, a vendor is located, the price is looked up, then money is
+transferred from the buyer's account to the vendor's.</p>
+
+<pre class="python">class Customer(components.Adapter):
+ __implements__ = IBuyActor
+
+ def withdraw(self, amt):
+ "interface to accounting system goes here"
+
+class Vendor(components.Adapter):
+ __implements__ = IVendor
+
+ def shoutPrice(self, merch, cust):
+ n = self.getComponent(english.INoun)
+ title = ('creature', 'sir','lady'
+ )[cust.getComponent(things.IThing).gender]
+ merchName = merch.original.getComponent(english.INoun).name))
+ self.original.emitEvent('%s says "For you, good %s, only %d '
+ 'zorkmids for this %s."' % (n.nounPhrase(cust),
+ title, merch.price,
+ merchName))
+
+ def buy(self, merchandise, amount):
+ self.deposit(amount)
+ merchandise.original.removeComponent(merchandise)
+
+ def stock(self, obj, price):
+ m = Merchandise(obj)
+ m.price = price
+ m.owner = self
+ m.home = self.original.location
+ obj.addComponent(m, ignoreClass=1)
+
+ def deposit(self, amt):
+ "more accounting code"</pre>
+<p>The essential operations for management of shop inventory are
+<code class="python">Vendor.stock</code> and <code
+class="python">Vendor.buy</code>, which add and remove a <code
+class="python">Merchandise</code> adapter, which stores the
+state related to the shop simulation for the object (in this case, its
+price, its owner, and the location it lives).</p>
+
+<pre class="shell">A weapons shop. You see a long sword, and Asidonhopo.
+Exits: a Secret Trapdoor, down; a Security Door, north
+bob: <b>get sword</b>
+You take a long sword.
+Asidonhopo says "For you, good sir, only 100 zorkmids for this long sword."
+</pre>
+
+<p>To enforce our anti-theft policy, we put constraints on the exits
+to the shop.</p>
+<pre class="python">
+class ShopDoor(ambulation.Door):
+ def collectImplementors(self, asker, iface, collection, seen,
+ event=None, name=None, intensity=2):
+ if iface == ambulation.IWalkTarget:
+ unpaidItems = asker.searchContents(None, IMerchandise)
+ if unpaidItems:
+ collection[self] = things.Refusal(self, "You cant leave, "
+ "you haven't paid!")
+ return
+
+ ambulation.Door.collectImplementors(self, asker, iface,
+ collection, seen, event,
+ name, intensity)
+ return collection</pre>
+
+<p><code class="python">collectImplementors</code> is the means by
+which queries for action participants are accomplished. It is a rather
+general graph-traversal mechanism and thus takes a few arguments:
+<code class="python">asker</code> is the object that initiated the
+query. <code class="python">iface</code> is the interface the results
+must conform to, <code class="python">collection</code> is the results
+so far, and <code class="python">seen</code> is a collection of
+objects already visited. The check done here is fairly simple: it
+refuses queries for <code class="python">IWalkTarget</code>s (the
+interface needed for walking between rooms) if the asker contains
+things that implement <code class="python">IMerchandise</code>, in
+particular unpaid items. Otherwise, it passes on the query to its
+superclass.</p>
+
+<pre class="shell">bob: <b>go north</b>
+You cant leave, you haven't paid!</pre>
+
+<p>Here, the <q>Security Door</q> examines the actor's contents for
+objects implementing IMerchandise. Since the sword still has a
+Merchandise adapter attached, the passage is barred.</p>
+
+<pre class="shell">bob: <b>go down</b></pre>
+
+<p>However, relying on the exits
+to contain merchandise is potentially error-prone; it demands knowing
+about all forms of locomotion in advance. If an unsecured exit from
+ the shop exists, or the player has the ability to teleport,
+ this form of security can be bypassed. Therefore, it is
+advantageous to have the Merchandise adapter itself keep the item
+within the shop.</p>
+
+<pre class="python">class Merchandise(components.Adapter):
+ __implements__ = IMerchandise, things.IMoveListener, IBuyTarget
+
+ def thingArrived(*args):
+ pass
+ def thingLeft(*args):
+ pass
+ def thingMoved(self, emitter, event):
+ if self.original == emitter and isinstance(event, conveyance.Take):
+ self.owner.shoutPrice(self, self.original.location)
+ if self.original.getOutermostRoom() != self.home:
+ self.original.emitEvent("The %s vanishes with a *foop*."
+ % self.getComponent(english.INoun).name)
+ self.original.moveTo(self.home)</pre>
+
+<p>When objects move, they broadcast events to nearby things
+ (where <q>nearby</q> is determined, again, by <code
+ class="python">collectImplementors</code>) that implement
+ <code class="python">IMoveListener</code>. In this case, the
+ <code class="python">Merchandise</code> adapter <q>listens</q>
+ for being picked up, and prompts the shopkeeper to quote the
+ price, and also checks to make sure it is contained by its
+ home room. If the player manages to leave the shop with unpaid
+ merchandise --</p>
+
+<pre class="shell">The long sword vanishes with a *foop*.</pre>
+
+<p>then it sets its location to its home room and informs the prospective
+shoplifter he no longer has his prize.</p>
+
+<h2>Future Directions</h2>
+
+<p>Current development efforts focus on enlarging the standard library
+of simulation objects and behaviour, developing web-based interfaces
+to the simulation, and improving the persistence layer. Possible
+extensions include client-side generation of action objects, enabling
+the development of graphical interfaces, or adapting the text system
+to other languages than English.</p>
+
+<h2>Conclusions</h2>
+
+<p>As seen in these examples, Twisted Reality provides features not
+found in other object-oriented simulation frameworks. The component
+model allows automatic aggregation of related objects; the actions
+system provides a mechanism for precise control of game effects; and
+the parser enables incremental extension of user input
+handling. Combined, they provide a powerful basis for modelling
+virtual worlds by composing simulations.</p>
+
+<h2>Acknowledgements</h2>
+<p>Thanks to Chris Armstrong and Donovan Preston for contributions to
+ Twisted Reality, and to Ying Li for editorial assistance.</p>
+
+<h2>References</h2>
+<ul>
+<li>Jason Asbahr, <a
+ href="http://asbahr.com/paper1html/paper1.html">Beyond: A
+ Portable Virtual World Simulation Framework</a>,
+ <i>Proceedings of the Seventh International Python
+ Conference</i> (1998).</li>
+
+<li>Pavel Curtis, <a
+ href="ftp://ftp.lambda.moo.mud.org/pub/MOO/ProgrammersManual.html"><i>LambdaMOO programmer's manual</i></a>, 1997.</li>
+<li> Jim Fulton, <a href="?">Zope Component Architecture</a></li>
+<li> Brandon Gillespie, <i><a
+ href="http://ice.cold.org:1180/bin/help?node=coldc">ColdC Reference Manual</a></i>, 2001.</li>
+<li>Glyph Lefkowitz, and Moshe Zadka, <q><a
+ href="http://twistedmatrix.com/doc/historic/ipc10paper">The Twisted Network Framework</a></q>, <i>Proceedings of the Tenth International Python Conference</i> (2002): 83.</li>
+<li>Graham Nelson, <i><a href="http://www.inform-fiction.org/manual/about_dm4.html">The
+ Inform Designer's Manual</a></i>. 4th ed. (St Charles, IL:
+ Interactive Fiction Library, 2001).</li>
+
+ </ul>
+
+ </body>
+</html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/2004/ibm/talk.html b/vendor/Twisted-10.0.0/doc/historic/2004/ibm/talk.html
new file mode 100644
index 0000000000..56d1bad845
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/2004/ibm/talk.html
@@ -0,0 +1,495 @@
+<html><head><title>Twisted: A Tutorial</title></head><body>
+
+<h1>Twisted: A Tutorial</h1>
+
+<h2>Thanks</h2>
+
+<p>I am grateful to IBM for inviting me to talk here, and to Muli Ben-Yehuda for arranging everything.</p>
+
+<h2>Administrative Notes</h2>
+
+<p>After reading Peter Norvig's infamous <q>The Gettysburg Powerpoint Presentation</q>, I was traumatized enough to forgoe the usual bullets and slides style, which originally was developed for physical slide projectors. Therefore, these notes are presented as one long HTML file, and I will use a new invention I call the <q>scrollbar</q> to show just one thing at a time. Enjoy living on the bleeding edge of presentation technology!</p>
+
+<h2>What Is Twisted?</h2>
+
+<p>Twisted is an <em>event-based networkings framework for Python</em>. It includes not only the basics of networking but also high-level protocol implementations, scheduling and more. It uses Python's high-level nature to enable few dependencies between different parts of the code. Twisted allows you to write network applications, clients and servers, without using threads and without running into icky concurrency issues.</p>
+
+<blockquote>
+A computer is a state machine.
+Threads are for people who can't program state machines.
+</blockquote>
+
+<p>Alan Cox in a discussion about the threads and the Linux scheduler</p>
+<p>http://www.bitmover.com/lm/quotes.html</p>
+
+<h2>An Extremely Short Introduction to Python</h2>
+
+<p>Python is a high-level dyanmically strongly typed language. All values are references to objects, and all arguments passed are objects references. Assignment changes the reference a variable points to, not the reference itself. Data types include integers (machine sized and big nums) like <code>1</code> and <code>1L</code>, strings and unicode strings like <code>"moshe"</code> and <code>u"\u05DE\u05E9\u05D4 -- moshe"</code>, lists (variably typed arrays, really) like <code>[1,2,3, "lalala", 10L, [1,2]]</code>, tuples (immutable arrays) like <code>("1", 2, 3)</code>, dictionaries <code>{"moshe": "person", "table": "thing"}</code> and user-defined objects.</p>
+
+<p>Every Python object has a type, which is itself a Python object. Some types aare defined in native C code (such as the types above) and some are defined in Python using the class keyword.</p>
+
+<p>Structure is indicated through indentation.</p>
+
+<p>Functions are defined using</p>
+
+<pre class="py-listing">
+def function(param1, param2, optionalParam="default value", *restParams,
+ **keywordParams):
+ pass
+</pre>
+
+<p>And are called using <code>function("value for param1", param2=42,
+optionalParam=[1,2], "these", "params", "will", "be", "restParams",
+keyword="arguments", arePut="in dictionary keywordParams")</code>.</p>
+
+<p>Functions can be defined inside classes:</p>
+
+<pre class="py-listing">
+class Example:
+ # constructor
+ def __init__(self, a=1):
+ self.b = a
+ def echo(self):
+ print self.b
+e = Example(5)
+e.echo()
+</pre>
+
+<p>All methods magically receive the self argument, but must treat it
+explicitly.</p>
+
+<p>Functions defined inside functions enjoy lexical scoping. All variables
+are outer-scope unless they are assigned to in which case they are inner-most
+scope.</p>
+
+<h2>How To Use Twisted</h2>
+
+<p>Those of you used to other event-based frameworks (notably, GUIs) will recognize the familiar pattern -- you call the framework's <code>mainloop</code> function, and it calls registered event handlers. Event handlers must finish quickly, to enable the framework to call other handlers without forcing the client (be it a GUI user or a network client) to wait. Twisted uses the <code>reactor</code> module for the main interaction with the network, and the main loop function is called <code>reactor.run</code>. The following code is the basic skeleton of a Twisted application.</p>
+
+<pre class="py-listing">
+from twisted.internet import reactor
+reactor.run()
+</pre>
+
+<p>This runs the reactor. This takes no CPU on UNIX-like systems, and little CPU on Windows (some APIs must be busy-polled), runs forever and does not quit unless delivered a signal.</p>
+
+<h2>How To Use Twisted to Do Nothing</h2>
+
+<p>Our first task using Twisted is to build a server to the well-known <q>finger</q> protocol -- or rather a simpler variant of it. The first step is accepting, and hanging, connections. This example will run forever, and will allow clients to connect to port 1079. It will promptly ignore everything they have to say...</p>
+
+
+<pre class="py-listing">
+from twisted.internet import protocol, reactor
+class FingerProtocol(protocol.Protocol):
+ pass
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>The protocol class is empty -- the default network event handlers simply throw away the events. Notice that the <code>protocol</code> attribute in <code>FingerFactory</code> is the <code>FingerProtocol</code> class itself, not an instance of it. Protocol logic properly belongs in the <code>Protocol</code> subclass, and the next few slides will show it developing.</p>
+
+
+<h2>How To Use Twisted to Do Nothing (But Work Hard)</h2>
+
+<p>The previous example used the fact that the default event handlers in the protocol exist and do nothing. The following example shows how to code the event handlers explicitly to do nothing. While being no more useful than the previous version, this shows the available events.</p>
+<pre class="py-listing">
+from twisted.internet import protocol, reactor
+class FingerProtocol(protocol.Protocol):
+ def connectionMade(self):
+ pass
+ def connectionLost(self):
+ pass
+ def dataReceived(self, data):
+ pass
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
+</pre>
+
+<p>This example is much easier to work with for the copy'n'paste style of programming...it has everything a good network application has: a low-level protocol implementation, a high-level class to handle persistent configuration data (the factory) and enough glue code to connect it to the network.</p>
+
+<h2>How To Use Twisted to Be Rude</h2>
+
+<p>The simplest event to respond to is the connection event. It is the first event a connection receives. We will use this opportunity to slam the door shut -- anyone who connects to us will be disconnected immediately.</p>
+
+<pre class="py-listing">
+class FingerProtocol(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.loseConnection()
+</pre>
+
+<p>The <code>transport</code> attribute is the protocol's link to the other side. It uses it to send data, to disconnect, to access meta-data about the connection and so on. Seperating the transport from the protocol enables easier work with other kinds of connections (unix domain sockets, SSL, files and even pre-written for strings, for testing purposes). It conforms to the <code>ITransport</code> interface, which is defined in <code>twisted.internet.interfaces</code>.</p>
+
+<h2>How To Use Twisted To Be Rude (In a Smart Way)</h2>
+
+<p>The previous version closed the connection as soon as the client connected, not even appearing as though it was a problem with the input. Since finger is a line-oriented protocol, if we read a line and then terminate the connection, the client will be forever sure it was his fault.</p>
+
+<pre class="py-listing">
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.loseConnection()
+</pre>
+
+<p>We now inherit from <code>LineReceiver</code>, and not directly from <code>Protocol</code>. <code>LineReceiver</code> allows us to respond to network data line-by-line rather than as they come from the TCP driver. We finish reading the line, and only then we disconnect the client. Important note for people used to various <code>fgets</code>, <code>fin.readline()</code> or Perl's <code>&lt;&gt;</code> operator: the line does <em>not</em> end in a newline, and so an empty line is <em>not</em> an indicator of end-of-file, but rather an indication of an empty line. End-of-file, in network context, is known as <q>closed connection</q> and is signaled by another event altogether (namely, <code>connectionLost</code>.</p>
+
+<h2>How To Use Twisted to Output Errors</h2>
+
+<p>The limit case of a useful finger server is a one with no users. This server will always reply that such a user does not exist. It can be installed while a system is upgraded or the old finger server has a security hole.</p>
+
+<pre class="py-listing">
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write("No such user\r\n")
+ self.transport.loseConnection()
+</pre>
+
+<p>Notice how we did not have to explicitly flush, or worry about the write being successful. Twisted will not close the socket until it has written all data to it, and will buffer it internally. While there are ways for interacting with the buffering mechanism (for example, when sending large amounts of data), for simple protocols this proves to be convenient.</p>
+
+<h2>How to Use Twisted to Do Useful Things</h2>
+
+<p>Note how we remarked earlier that <em>protocol logic</em> belongs in the
+protocol class. This is necessary and sufficient -- we do not want non-protocol
+logic in the protocol class. User management is clearly not part of the protocol logic, and so should not be in the protocol. This is exactly why we have the factory in the first place. The factory allows us to delegate non-protocol logic
+to a seperate class. It is often not completely trivial what does and does not belong in the factory, of course.</p>
+
+<pre class="py-listing">
+from twisted.protocols import basic
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ self.transport.write(self.factory.getUser(user)+"\r\n")
+ self.transport.loseConnection()
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def getUser(self, user):
+ return "No such user"
+</pre>
+
+<p>Notice how we did not change the observable behaviour, but we did make the factory know about which users exist and do not exist. With this kind of setup, we will not need to modify our protocol class when we change user management schemes...hopefully.</p>
+
+<h2>Using Twisted to Do Useful Things (For Real)</h2>
+
+<p>The last heading did not live up to its name -- the server kept spouting off that it did not know who we are talking about, they never lived here and could we please go away. It did, however, prepare the way for doing actually useful things which we do here.</p>
+
+<pre class="py-listing">
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs):
+ self.users = kwargs
+ def getUser(self, user):
+ return self.users.get(user, "No such user")
+reactor.listenTCP(1079, FingerFactory(moshez='Happy and well'))
+</pre>
+
+<p>This server actually has valid use cases. With such code, we could easily disseminate office/phone/real name information across an organization, if people had finger clients.</p>
+
+<h2>Using Twisted to Do Useful Things, Correctly</h2>
+
+<p>The version above works just fine. However, the interface between the protocol class and its factory is synchronous. This might be a problem. After all, <code>lineReceived</code> is an event, and should be handled quickly. If the user's status needs to be fetched by a slow process, this is impossible to achieve using the current interface. Following our method earlier, we modify this API glitch without changing anything in the outward-facing behaviour.</p>
+
+
+<pre class="py-listing">
+from twisted.internet import defer
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = defer.maybeDeferred(self.factory.getUser, user)
+ def e(_):
+ return "Internal error in server"
+ d.addErrback(e)
+ def _(m):
+ self.transport.write(m+"\r\n")
+ self.transport.loseConnection()
+ d.addCallback(_)
+</pre>
+
+<p>The value of using <code>maybeDeferred</code> is that it seamlessly
+works with the old factory too. If we would allow changing the factory,
+we could make the code a little cleaner, as the following example shows.</p>
+
+<pre class="py-listing">
+from twisted.internet import defer
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser( user)
+ def e(_):
+ return "Internal error in server"
+ d.addErrback(e)
+ def _(m):
+ self.transport.write(m+"\r\n")
+ self.transport.loseConnection()
+ d.addCallback(_)
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, **kwargs):
+ self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+</pre>
+
+<p>Note how this example had to change the factory too. <code>defer.succeed</code> is a way to returning a deferred results which is already triggered successfully. It is useful in exactly these kinds of cases: an API had to be asynchronous to support other use-cases, but in a simple enough use-case, the result is availble immediately.</p>
+
+<p>Deferreds are abstractions of callbacks. In this instance, the deferred
+had a value immediately, so the callback was called as soon as it was
+added. We will soon show an example where it will not be available immediately.
+The errback is called if there are problems, and is equivalent to exception handling. If it returns a value, the exception is considered handled, and further callbacks will be called with its return value.</p>
+
+<h2>Using Twisted to Do The Standard Thing</h2>
+
+<p>The standard finger daemon is equivalent to running the <code>finger</code>
+command on the remote machine. Twisted can treat processes as event sources too, and enables high-level abstractions to allow us to get process output easily.</p>
+
+<pre class="py-listing">
+from twisted.internet import utils
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def getUser(self, user):
+ return utils.getProcessOutput("finger", [user])
+</pre>
+
+<p>The real value of using deferreds in Twisted is shown here in full. Because there is a standard way to abstract callbacks, especially a way that does not require sending down the call-sequence a callback, all functions in Twisted itself whose result might take a long time return a deferred. This enables us in many cases to return the value that a function returns, without caring that it is deferred at all.</p>
+
+<p>If the command exits with an error code, or sends data to stderr, the
+errback will be triggered and the user will be faced with a half-way useful
+error message. Since we did not whitewash the argument at all, it is quite
+likely that this contains a security hole. This is, of course, another
+standard feature of finger daemons...</p>
+
+<p>However, it is easy to whitewash the output. Suppose, for example, we do not want the explicit name <q>Microsoft</q> in the output, because of the risk of offending religious feelings. It is easy to change the deferred into one which is completely safe.</p>
+
+<pre class="py-listing">
+from twisted.internet import utils
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def getUser(self, user):
+ d = utils.getProcessOutput("finger", [user])
+ def _(s):
+ return s.replace('Microsoft', 'It which cannot be named')
+ d.addCallback(_)
+ return d
+</pre>
+
+<p>The good news is that the protocol class will need to change no more,
+up until the end of the talk. That class abstracts the protocol well
+enough that we only have to modify factories when we need to support
+other user-management schemes.</p>
+
+<h2>Use The Correct Port</h2>
+
+<p>So far we used port 1097, because with UNIX low ports can only be bound by root. Certainly we do not want to run the whole finger server as root. The usual solution would be to use privilege shedding: something like <code>reactor.listenTCP</code>, followed by appropriate <code>os.setuid</code> and then <code>reactor.run</code>. This kind of code, however, brings the option of making subtle bugs in the exact place they are most harmful. Fortunately, Twisted can help us do privilege shedding in an easy, portable and safe manner.</p>
+
+<p>For that, we will not write <code>.py</code> main programs which run the application. Rather, we will write <code>.tac</code> (Twisted Application Configuration) files which contain the configuration. While Twisted supports several configuration formats, the easiest one to edit by hand, and the most popular one is...Python. A <code>.tac</code> is just a plain Python file which defines a variable named <code>application</code>. That variable should subscribe to various interfaces, and the usual way is to instantiate <code>twisted.service.Application</code>. Note that unlike many popular frameworks, in Twisted it is not recommended to <em>inherit</em> from <code>Application</code>.</p>
+
+<pre class="py-listing">
+from twisted.application import service
+application = service.Application('finger', uid=1, gid=1)
+factory = FingerFactory(moshez='Happy and well')
+internet.TCPServer(79, factory).setServiceParent(application)
+</pre>
+
+<p>This is a minimalist <code>.tac</code> file. The application class itelf is resopnsible for the uid/gid, and various services we configure as its children are responsible for specific tasks. The service tree really is a tree, by the way...</p>
+
+<h2>Running TAC Files</h2>
+
+<p>TAC files are run with <code>twistd</code> (TWISTed Daemonizer). It supports various options, but the usual testing way is:</p>
+
+<pre class="shell">
+root% twistd -noy finger.tac
+</pre>
+
+<p>With long options:</p>
+
+<pre class="shell">
+root% twistd --nodaemon --no_save --python finger.tac
+</pre>
+
+<p>Stopping <code>twistd</code> from daemonizing is convenient because then it is possible to kill it with CTRL-C. Stopping it from saving program state is good because recovering from saved states is uncommon and problematic and it leaves too many <code>-shutdown.tap</code> files around. <code>--python finger.tac</code> lets <code>twistd</code> know what type of configuration to read from which file. Other options include <code>--file .tap</code> (a pickle), <code>--xml .tax</code> (an XML configuration format) and <code>--source .tas</code> (a specialized Python-source format which is more regular, more verbose and hard to edit).</p>
+
+<h2>Integrating Several Services</h2>
+
+<p>Before we can integrate several services, we need to write another service. The service we will implement here will allow users to change their status on the finger server. We will not implement any access control. First, the protocol class:</p>
+
+<pre class="py-listing">
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self):
+ self.lines = []
+ def lineReceived(self, line):
+ self.lines.append(line)
+ def connectionLost(self, reason):
+ self.factory.setUser(self.line[0], self.line[1])
+</pre>
+
+<p>And then, the factory:</p>
+
+<pre class="py-listing">
+class FingerSetterFactory(protocol.ServerFactory):
+ protocol = FingerSetterProtocol
+ def __init__(self, fingerFactory):
+ self.fingerFactory = fingerFactory
+ def setUser(self, user, status):
+ self.fingerFactory.users[user] = status
+</pre>
+
+<p>And finally, the <code>.tac</code>:</p>
+
+<pre class="py-listing">
+ff = FingerFactory(moshez="Happy and well")
+fsf = FingerSetterFactory(ff)
+application = service.Application('finger', uid=1, gid=1)
+internet.TCPServer(79,ff).setServiceParent(application)
+internet.TCPServer(1079,fsf,interface='127.0.0.1').setServiceParent(application)
+</pre>
+
+<p>Now users can use programs like <code>telnet</code> or <code>nc</code> to change their status, or maybe even write specialized programs to set their options:</p>
+
+<pre class="py-listing">
+import socket
+s = socket.socket()
+s.connect(('localhost', 1097))
+s.send('%s\r\n%s\r\n' % (sys.argv[1], sys.argv[2]))
+</pre>
+
+<p>(Later, we will learn on how to write network clients with Twisted, which fix the bugs in this example.)</p>
+
+<p>Note how, as a naive version of access control, we bound the setter service to the local machine, not to the default interface (<code>0.0.0.0</code). Thus, only users with shell access to the machine will be able to change settings. It is possible to do more access control, such as listening on UNIX domain sockets and accessing various unportable APIs to query users. There will be no examples of such techniques in this talk, however.</p>
+
+<h2>Integrating Several Services: The Smart Way</h2>
+
+<p>The last example exposed a historical asymmetry. Because the finger setter was developed later, it poked into the finger factory in an unseemly manner. Note that now, we will only be changing factories and configuration -- the protocol classes, apparently, are perfect.</p>
+
+<pre class="py-listing">
+class FingerService(service.Service):
+ def __init__(self, **kwargs):
+ self.users = kwargs
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+ def getFingerSetterFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.setUser = FingerSetterProtocol, self.users.__setitem__
+ return f
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService(moshez='Happy and well')
+ff = f.getFingerFactory()
+fsf = f.getFingerSetterFactory()
+internet.TCPServer(79,ff).setServiceParent(application)
+internet.TCPServer(1079,fsf).setServiceParent(application)
+</pre>
+
+<p>Note how it is perfectly fine to use <code>ServerFactory</code> rather than subclassing it, as long as we explicitly set the <code>protocol</code> attribute -- and anything that the protocols use. This is common in the case where the factory only glues together the protocol and the configuration, rather than actually serving as the repository for the configuration information.</p>
+
+<h2>Periodic Tasks</h2>
+
+<p>In this example, we periodicially read a global configuration file to decide which users do what. First, the code.</p>
+
+<pre class="py-listing">
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.update()
+ def update(self):
+ self.users = {}
+ for line in file(self.filename):
+ user, status = line[:-1].split(':', 1)
+ self.users[user] = status
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol, f.getUser = FingerProtocol, self.getUser
+ return f
+</pre>
+
+<p>The TAC file:</p>
+
+<pre class="py-listing">
+application = service.Application('finger', uid=1, gid=1)
+finger = FingerService('/etc/users')
+server = internet.TCPServer(79, f.getFingerFactory())
+periodic = internet.TimerService(30, f.update)
+finger.setServiceParent(application)
+server.setServiceParent(application)
+periodic.setServiceParent(application)
+</pre>
+
+<p>Note how the actual periodic refreshing is a feature of the configuration, not the code. This is useful in the case we want to have other timers control refreshing, or perhaps even only refresh explicitly as depending on user action (another protocol, perhaps?).</p>
+
+<h2>Writing Clients: A Finger Proxy</h2>
+
+<p>It could be the case that our finger server needs to query another finger server, perhaps because of strange network configuration or maybe we just want to mask some users. Here is an example for a finger client, and a use case as a finger proxy. Note that in this example, we do not need custom services and so we do not develop them.</p>
+
+<pre class="py-listing">
+from twisted.internet import protocol, defer, reactor
+class FingerClient(protocol.Protocol):
+ buffer = ''
+ def connectionMade(self):
+ self.transport.write(self.factory.user+'\r\n')
+ def dataReceived(self, data):
+ self.buffer += data
+ def connectionLost(self, reason):
+ self.factory.gotResult(self.buffer)
+
+class FingerClientFactory(protocol.ClientFactory):
+ protocol = FingerClient
+ def __init__(self, user):
+ self.user = user
+ self.result = defer.Deferred()
+ def gotResult(self, result):
+ self.result.callback(result)
+ def clientConnectionFailed(self, _, reason):
+ self.result.errback(reason)
+
+def query(host, port, user):
+ f = FingerClientFactory(user)
+ reactor.connectTCP(host, port, f)
+ return f.result
+
+class FingerProxyServer(protocol.ServerFactory):
+ protocol = FingerProtocol
+ def __init__(self, host, port=79):
+ self.host, self.port = host, port
+ def getUser(self, user):
+ return query(self.host, self.port, user)
+</pre>
+
+<p>With a TAC that looks like:</p>
+
+<pre class="py-listing">
+application = service.Application('finger', uid=1, gid=1)
+server = internet.TCPServer(79, FingerProxyFactory('internal.ibm.com'))
+server.setServiceParent(application)
+</pre>
+
+<h2>What I Did Not Cover</h2>
+
+<p>Twisted is large. Really large. Really really large. I could not hope to cover it all in thrice the time. What didn't I cover?</p>
+
+<ul>
+<li>Integration with GUI toolkits.</li>
+<li>Nevow, a web-framework.</li>
+<li>Twisted's internal remote call protocol, Perspective Broker.</li>
+<li>Trial, a unit testing framework optimized for testing Twisted-based
+ code.</li>
+<li>cred, the user management framework.</li>
+<li>Advanced deferred usage.</li>
+<li>Threads abstraction.</li>
+<li>Consumers/providers</li>
+</ul>
+
+<p>There is good documentation on the Twisted website, among which the tutorial which was based on an old HAIFUX talk and was, in turn, the basis for this talk, and specific HOWTOs for doing many useful and fun things.</p>
+
+<h2>Notes on Non-Blocking</h2>
+
+<p>In UNIX non-blocking has a very specific meaning -- some operations might block, others won't. Unfortunately, this meaning is almost completely useless in real life. Reading from a socket connected to a responsive server on a UNIX domain socket is blocking, while reading a megabyte string from a busy hard-drive is not. A more useful meaning for actual decisions while writing non-blocking code is <q>takes more than 0.05 seconds on my target platform</q>. With this kind of handlers, typical network usage will allow for the magical <q>one million hits a day</q> website, or a GUI application which appears to a human being as infinitely responsive. Various techniques, not limited but including threads, can be used to modify code to be responsive at those levels.</li>
+
+<h2>Conclusion</h2>
+
+<p>Twisted supports high-level abstractions for almost all levels of writing network code. Moreover, when using Twisted correctly it is possible to add more absractions, so that actual network applications do not have to fiddle with low-level protocol details. Developing network applications using Twisted and Python can lead to quick prototypes which can then be either optimized or rewritten on other platforms -- and often can just serve as-is.</p>
+
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/index.html b/vendor/Twisted-10.0.0/doc/historic/index.html
new file mode 100644
index 0000000000..c550222d96
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/index.html
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Historical Documents</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Historical Documents</h1>
+ <div class="toc"><ol><li><a href="#auto0">2003</a></li><ul><li><a href="#auto1">Python Community Conference</a></li></ul><li><a href="#auto2">Previously</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p>Here are documents which contain no pertinent information or documentation.
+People from the Twisted team have published them, and they serve as interesting
+land marks and thoughts. Please don't look here for documentation -- however,
+if you are interested in the history of Twisted, or want to quote from these
+documents, feel free. Remember, however -- the documents here may contain
+wrong information -- they are not updated as Twisted is, to keep their
+historical value intact.</p>
+
+<h2>2003<a name="auto0"/></h2>
+<h3>Python Community Conference<a name="auto1"/></h3>
+
+<p>These papers were part of the <a href="http://python.org/pycon/" shape="rect">Python Community Conference</a> (PyCon) in March of 2003.</p>
+
+
+
+<dl>
+ <dt><a href="2003/pycon/deferex.html" shape="rect"><cite>Generalization of Deferred Execution in Python</cite></a></dt>
+
+ <dd><p>A deceptively simple architectural challenge faced by many
+ multi-tasking applications is gracefully doing nothing. Systems that
+ must wait for the results of a long-running process, network message, or
+ database query while continuing to perform other tasks must establish
+ conventions for the semantics of waiting. The simplest of these is
+ blocking in a thread, but it has significant scalability problems. In
+ asynchronous frameworks, the most common approach is for long-running
+ methods to accept a callback that will be executed when the command
+ completes. These callbacks will have different signatures depending on
+ the nature of the data being requested, and often, a great deal of code
+ is necessary to glue one portion of an asynchronous networking system to
+ another. Matters become even more complicated when a developer wants to
+ wait for two different events to complete, requiring the developer to
+ &quot;juggle&quot; the callbacks and create a third, mutually incompatible
+ callback type to handle the final result.</p>
+
+ <p>This paper describes the mechanism used by the Twisted framework for
+ waiting for the results of long-running operations. This mechanism,
+ the <code>Deferred</code>, handles the often-neglected problems of
+ error handling, callback juggling, inter-system communication and code
+ readability.</p></dd>
+
+ <dt><a href="2003/pycon/applications/applications.html" shape="rect"><cite>Applications of the Twisted Framework</cite></a></dt>
+
+ <dd><p>Two projects developed using the Twisted framework are described;
+ one, Twisted.names, which is included as part of the Twisted
+ distribution, a domain name server and client API, and one, Pynfo, which
+ is packaged separately, a network information robot.</p></dd>
+
+ <dt><a href="2003/pycon/conch/conch.html" shape="rect"><cite>Twisted.Conch: SSH in Python with Twisted</cite></a></dt>
+
+ <dd><p>Conch is an implementation of the Secure Shell Protocol (currently
+ in the IETF standarization process). Secure Shell (or SSH) is a popular
+ protocol for remote shell access, file management and port forwarding
+ protected by military-grade security. SSH supports multiple encryption and
+ compression protocols for the wire transports, and a flexible system of
+ multiplexed channels on top. Conch uses the Twisted networking framework
+ to supply a library which can be used to implement both SSH clients and
+ servers. In addition, it also contains several ready made client programs,
+ including a drop-in replacement for the OpenSSH program from the OpenBSD
+ project.</p></dd>
+
+ <dt><a href="2003/pycon/lore/lore.html" shape="rect"><cite>The Lore Document Generation Framework</cite></a></dt>
+
+ <dd><p>Lore is a documentation generation system which uses a limited
+ subset of XHTML, together with some class attributes, as its source
+ format. This allows for lower barrier of entry than many other similar
+ systems, since HTML authoring tools are plentiful as is knowledge of
+ HTML writing. As an added advantage, the source format is viewable
+ directly, so that even if Lore is not available the documentation is
+ useful. It currently outputs LaTeX and HTML, which allows for most
+ use-cases.</p></dd>
+
+ <dt><a href="2003/pycon/pb/pb.html" shape="rect"><cite>Perspective Broker: <q>Translucent</q> Remote Method calls in Twisted</cite></a></dt>
+
+ <dd><p>One of the core services provided by the Twisted networking
+ framework is <q>Perspective Broker</q>, which provides a clean, secure,
+ easy-to-use Remote Procedure Call (RPC) mechanism. This paper explains the
+ novel features of PB, describes the security model and its implementation,
+ and provides brief examples of usage.</p></dd>
+
+ <dt><a href="2003/pycon/releasing/releasing.html" shape="rect"><cite>Managing the Release of a Large Python Project</cite></a></dt>
+
+ <dd><p>Twisted is a Python networking framework. At last count, the
+ project contains nearly 60,000 lines of effective code (not comments or
+ blank lines). When preparing a release, many details must be checked, and
+ many steps must be followed. We describe here the technologies and tools
+ we use, and explain how we built tools on top of them which help us make
+ releasing as painless as possible.</p></dd>
+
+ <dt><a href="2003/pycon/twisted-reality/twisted-reality.html" shape="rect"><cite>Twisted Reality: A Flexible Framework for Virtual Worlds</cite></a></dt>
+
+ <dd><p>Flexibly modelling virtual worlds in object-oriented languages has
+ historically been difficult; the issues arising from multiple
+ inheritance and order-of-execution resolution have limited the
+ sophistication of existing object-oriented simulations. Twisted
+ Reality avoids these problems by reifying both actions and
+ relationships, and avoiding inheritance in favor of automated
+ composition through adapters and interfaces.</p></dd>
+</dl>
+
+<h2>Previously<a name="auto2"/></h2>
+
+<ul>
+<li><a href="ipc10paper.html" shape="rect">The paper Glyph and Moshe presented in
+ IPC10</a></li>
+<li><a href="ipc10errata.html" shape="rect">The errata published in IPC10 against the
+ paper.</a></li>
+<li><a href="twisted-debian.html" shape="rect">A paper Moshe wrote about Twisted and
+ Debian.</a></li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/historic/ipc10errata.html b/vendor/Twisted-10.0.0/doc/historic/ipc10errata.html
new file mode 100644
index 0000000000..8388919e4b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/ipc10errata.html
@@ -0,0 +1,256 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>The World of Software is a World of Constant
+ Change</title>
+ </head>
+
+ <body>
+ <p><em><strong>Note:</strong> This document is relevant for the
+ version of Twisted that was current at <a
+ href="http://www.python10.com">IPC10</a>. It has since been
+ superseded by many changes to the Python API. It is remaining
+ unchanged for historical reasons, but please refer to
+ documentation for the specific system you are looking for and
+ not these papers for current information.</em></p>
+
+ <h1>The World of Software is a World of Constant Change</h1>
+
+ <p>Twisted has undergone several major revisions since Moshe
+ Zadka and I wrote the <a href="ipc10paper.html">"The Twisted
+ Network Framework"</a>. Most of these changes have not deviated
+ from the central vision of the framework, but almost all of the
+ code listings have been re-visited and enhanced in some
+ way.</p>
+
+ <p>So, while the paper was correct at the time that it was
+ originally written, a few things have changed which have
+ invalidated portions of it.</p>
+
+ <p>Most significant is the fact that almost all methods which
+ pass callbacks of some kind have been changed to take no
+ callback or error-callback arguments, and instead return an
+ instance of a <code
+ class="API">twisted.python.defer.Deferred</code>. This means
+ that an asynchronous function can be easily identified visually
+ because it will be of the form: <code
+ class="python">async_obj.asyncMethod("foo")<b>.addCallbacks(succeded,
+ failed)</b></code>. There is also a utility method <code
+ class="python">addCallback</code> which makes it more
+ convenient to pass additional arguments to a callback function
+ and omit special-case error handling.</p>
+
+ <p>While it is still backwards compatible, <code
+ class="API">twisted.internet.passport</code> has been re-named
+ to <code class="API">twisted.cred</code>, and the various
+ classes in it have been split out into submodules of that
+ package, and the various remote-object superclasses have been
+ moved out of twisted.spread.pb and put into
+ twisted.spread.flavors.</p>
+
+ <p><code class="python">Application.listenOn</code> has been
+ replaced with the more descripively named <code
+ class="python">Application.listenTCP</code>, <code
+ class="python">Application.listenUDP</code>, and <code
+ class="python">Application.listenSSL</code>.</p>
+
+ <p><code class="API">twisted.web.widgets</code> has progressed
+ quite far since the paper was written! One description
+ specifically given in the paper is no longer correct:</p>
+
+ <blockquote>
+ The namespace for evaluating the template expressions is
+ obtained by scanning the class hierarchy for attributes, and
+ getting each of those attributes from the current instance.
+ This means that all methods will be bound methods, so
+ indicating "self" explicitly is not required. While it is
+ possible to override the method for creating namespaces,
+ using this default has the effect of associating all
+ presentation code for a particular widget in one class, along
+ with its template. If one is working with a non-programmer
+ designer, and the template is in an external file, it is
+ always very clear to the designer what functionality is
+ available to them in any given scope, because there is a list
+ of available methods for any given class.
+ </blockquote>
+<p>This is still possible to avoid breakages in old code, but
+after some experimentation, it became clear that simply passing
+ <code class="python">self</code> was an easier method for
+ creating the namespace, both for designers and programmers.</p>
+ <p>In addition, since the advent of Zope3, interoperability
+ with Zope has become increasingly interesting possibility for
+ the Twisted development team, since it would be desirable if
+ Twisted could use their excellent strategy for
+ content-management, while still maintaining Twisted's
+ advantages in the arena of multi-protocol servers. Of
+ particular interest has been Zope Presentation Templates, since
+ they seem to be a truly robust solution for keeping design
+ discrete from code, compatible with the event-based method in
+ which twisted.web.widgets processes web requests. <code
+ class="API">twisted.web.widgets.ZopePresentationTemplate</code>
+ may be opening soon in a theatre near you!</p>
+
+ <p>The following code examples are corrected or modernized
+ versions of the ones that appear in the paper.</p>
+
+ <blockquote>
+ Listing 9: A remotely accessible object and accompanying call
+
+<pre class="python">
+# Server Side
+class MyObject(pb.Referenceable):
+ def remote_doIt(self):
+ return "did it"
+
+# Client Side
+ ...
+ def myCallback(result):
+ print result # result will be 'did it'
+ def myErrback(stacktrace):
+ print 'oh no, mr. bill!'
+ print stacktrace
+ myRemoteReference.doIt().addCallbacks(myCallback,
+ myErrback)
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 10: An object responding to its calling perspective
+<pre class="python">
+# Server Side
+class Greeter(pb.Viewable):
+ def view_greet(self, actor):
+ return "Hello %s!\n" % actor.perspectiveName
+
+# Client Side
+ ...
+ remoteGreeter.greet().addCallback(sys.stdout.write)
+ ...
+</pre>
+ </blockquote>
+
+
+ <blockquote>
+ Listing 12: A client for Echoer objects.
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def gotObject(object):
+ print "got object:",object
+ object.echo("hello network".addCallback(gotEcho)
+def gotEcho(echo):
+ print 'server echoed:',echo
+ main.shutDown()
+def gotNoObject(reason):
+ print "no object:",reason
+ main.shutDown()
+pb.getObjectAt("localhost", 8789, gotObject, gotNoObject, 30)
+main.run()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 13: A PB server using twisted's "passport"
+ authentication.
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class SimplePerspective(pb.Perspective):
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+class SimpleService(pb.Service):
+ def getPerspectiveNamed(self, name):
+ return SimplePerspective(name, self)
+if __name__ == '__main__':
+ import pbecho
+ app = main.Application("pbecho")
+ pbecho.SimpleService("pbecho",app).getPerspectiveNamed("guest").makeIdentity("guest")
+ app.listenTCP(pb.portno, pb.BrokerFactory(pb.AuthRoot(app)))
+ app.save("start")
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 14: Connecting to an Authorized Service
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def success(message):
+ print "Message received:",message
+ main.shutDown()
+def failure(error):
+ print "Failure...",error
+ main.shutDown()
+def connected(perspective):
+ perspective.echo("hello world").addCallbacks(success, failure)
+ print "connected."
+
+pb.connect("localhost", pb.portno, "guest", "guest",
+ "pbecho", "guest", 30).addCallbacks(connected,
+ failure)
+main.run()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 15: A Twisted GUI application
+<pre class="python">
+from twisted.internet import main, ingtkernet
+from twisted.spread.ui import gtkutil
+import gtk
+ingtkernet.install()
+class EchoClient:
+ def __init__(self, echoer):
+ l.hide()
+ self.echoer = echoer
+ w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.GtkVBox(); b = gtk.GtkButton("Echo:")
+ self.entry = gtk.GtkEntry(); self.outry = gtk.GtkEntry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', gtk.mainquit)
+ w.show_all()
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.echo(txt).addCallback(self.outry.set_text)
+l = gtkutil.Login(EchoClient, None, initialService="pbecho")
+l.show_all()
+gtk.mainloop()
+</pre>
+ </blockquote>
+
+ <blockquote>
+ Listing 16: an event-based web widget.
+<pre class="python">
+from twisted.spread import pb
+from twisted.python import defer
+from twisted.web import widgets
+class EchoDisplay(widgets.Gadget, widgets.Presentation):
+ template = """&lt;H1&gt;Welcome to my widget, displaying %%%%echotext%%%%.&lt;/h1&gt;
+ &lt;p&gt;Here it is: %%%%getEchoPerspective()%%%%&lt;/p&gt;"""
+ echotext = 'hello web!'
+ def getEchoPerspective(self):
+ return ['&lt;b&gt;',
+ pb.connect("localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 1).
+ addCallbacks(self.makeListOf, self.formatTraceback)
+ ,'&lt;/b&gt;']
+ def makeListOf(self, echoer):
+ return [echoer.echo(self.echotext).addCallback(lambda x: [x])]
+if __name__ == "__main__":
+ from twisted.web import server
+ from twisted.internet import main
+ a = main.Application("pbweb")
+ a.listenTCP(8080, server.Site(EchoDisplay()))
+ a.run()
+</pre>
+ </blockquote>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/historic/ipc10paper.html b/vendor/Twisted-10.0.0/doc/historic/ipc10paper.html
new file mode 100644
index 0000000000..13f825c125
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/ipc10paper.html
@@ -0,0 +1,1568 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>The Twisted Network Framework</title>
+ </head>
+
+ <body>
+ <p><em><strong>Note:</strong> This document is relevant for the
+ version of Twisted that were current previous to <a
+ href="http://www.python10.com">IPC10</a>. Even at the time of
+ its release, <a href="ipc10errata.html">there were errata
+ issued</a> to make it current. It is remaining unaltered for
+ historical purposes but it is no longer accurate.</em></p>
+
+ <h1>The Twisted Network Framework</h1>
+
+ <h6>Moshe Zadka <a
+ href="mailto:m@moshez.org">m@moshez.org</a></h6>
+
+ <h6>Glyph Lefkowitz <a
+ href="mailto:glyph@twistedmatrix.com">glyph@twistedmatrix.com</a></h6>
+
+ <h3>Abstract</h3>
+
+ <p>Twisted is a framework for writing asynchronous,
+ event-driven networked programs in Python -- both clients and
+ servers. In addition to abstractions for low-level system calls
+ like <code>select(2)</code> and <code>socket(2)</code>, it also
+ includes a large number of utility functions and classes, which
+ make writing new servers easy. Twisted includes support for
+ popular network protocols like HTTP and SMTP, support for GUI
+ frameworks like <code>GTK+</code>/<code>GNOME</code> and
+ <code>Tk</code> and many other classes designed to make network
+ programs easy. Whenever possible, Twisted uses Python's
+ introspection facilities to save the client programmer as much
+ work as possible. Even though Twisted is still work in
+ progress, it is already usable for production systems -- it can
+ be used to bring up a Web server, a mail server or an IRC
+ server in a matter of minutes, and require almost no
+ configuration.</p>
+
+ <p><strong>Keywords:</strong> internet, network, framework,
+ event-based, asynchronous</p>
+
+ <h3>Introduction</h3>
+
+ <p>Python lends itself to writing frameworks. Python has a
+ simple class model, which facilitates inheritance. It has
+ dynamic typing, which means code needs to assume less. Python
+ also has built-in memory management, which means application
+ code does not need to track ownership. Thus, when writing a new
+ application, a programmer often finds himself writing a
+ framework to make writing this kind of application easier.
+ Twisted evolved from the need to write high-performance
+ interoperable servers in Python, and making them easy to use
+ (and difficult to use incorrectly).</p>
+
+ <p>There are three ways to write network programs:</p>
+
+ <ol>
+ <li>Handle each connection in a separate process</li>
+
+ <li>Handle each connection in a separate thread</li>
+
+ <li>Use non-blocking system calls to handle all connections
+ in one thread.</li>
+ </ol>
+
+ <p>When dealing with many connections in one thread, the
+ scheduling is the responsibility of the application, not the
+ operating system, and is usually implemented by calling a
+ registered function when each connection is ready to for
+ reading or writing -- commonly known as event-driven, or
+ callback-based, programming.</p>
+
+ <p>Since multi-threaded programming is often tricky, even with
+ high level abstractions, and since forking Python processes has
+ many disadvantages, like Python's reference counting not
+ playing well with copy-on-write and problems with shared state,
+ it was felt the best option was an event-driven framework. A
+ benefit of such approach is that by letting other event-driven
+ frameworks take over the main loop, server and client code are
+ essentially the same - making peer-to-peer a reality. While
+ Twisted includes its own event loop, Twisted can already
+ interoperate with <code>GTK+</code>'s and <code>Tk</code>'s
+ mainloops, as well as provide an emulation of event-based I/O
+ for Jython (specific support for the Swing toolkit is planned).
+ Client code is never aware of the loop it is running under, as
+ long as it is using Twisted's interface for registering for
+ interesting events.</p>
+
+ <p>Some examples of programs which were written using the
+ Twisted framework are <code>twisted.web</code> (a web server),
+ <code>twisted.mail</code> (a mail server, supporting both SMTP
+ and POP3, as well as relaying), <code>twisted.words</code> (a
+ chat application supporting integration between a variety of IM
+ protocols, like IRC, AOL Instant Messenger's TOC and
+ Perspective Broker, a remote-object protocol native to
+ Twisted), <code>im</code> (an instant messenger which connects
+ to twisted.words) and <code>faucet</code> (a GUI client for the
+ <code>twisted.reality</code> interactive-fiction framework).
+ Twisted can be useful for any network or GUI application
+ written in Python.</p>
+
+ <p>However, event-driven programming still contains some tricky
+ aspects. As each callback must be finished as soon as possible,
+ it is not possible to keep persistent state in function-local
+ variables. In addition, some programming techniques, such as
+ recursion, are impossible to use. Event-driven programming has
+ a reputation of being hard to use due to the frequent need to
+ write state machines. Twisted was built with the assumption
+ that with the right library, event-driven programming is easier
+ then multi-threaded programming. Twisted aims to be that
+ library.</p>
+
+ <p>Twisted includes both high-level and low-level support for
+ protocols. Most protocol implementation by twisted are in a
+ package which tries to implement "mechanisms, not policy". On
+ top of those implementations, Twisted includes usable
+ implementations of those protocols: for example, connecting the
+ abstract HTTP protocol handler to a concrete resource-tree, or
+ connecting the abstract mail protocol handler to deliver mail
+ to maildirs according to domains. Twisted tries to come with as
+ much functionality as possible out of the box, while not
+ constraining a programmer to a choice between using a
+ possibly-inappropriate class and rewriting the non-interesting
+ parts himself.</p>
+
+ <p>Twisted also includes Perspective Broker, a simple
+ remote-object framework, which allows Twisted servers to be
+ divided into separate processes as the end deployer (rather
+ then the original programmer) finds most convenient. This
+ allows, for example, Twisted web servers to pass requests for
+ specific URLs with co-operating servers so permissions are
+ granted according to the need of the specific application,
+ instead of being forced into giving all the applications all
+ permissions. The co-operation is truly symmetrical, although
+ typical deployments (such as the one which the Twisted web site
+ itself uses) use a master/slave relationship.</p>
+
+ <p>Twisted is not alone in the niche of a Python network
+ framework. One of the better known frameworks is Medusa. Medusa
+ is used, among other things, as Zope's native server serving
+ HTTP, FTP and other protocols. However, Medusa is no longer
+ under active development, and the Twisted development team had
+ a number of goals which would necessitate a rewrite of large
+ portions of Medusa. Twisted seperates protocols from the
+ underlying transport layer. This seperation has the advantages
+ of resuability (for example, using the same clients and servers
+ over SSL) and testability (because it is easy to test the
+ protocol with a much lighter test harness) among others.
+ Twisted also has a very flexible main-loop which can
+ interoperate with third-party main-loops, making it usable in
+ GUI programs too.</p>
+
+ <h3>Complementing Python</h3>
+
+ <p>Python comes out of the box with "batteries included".
+ However, it seems that many Python projects rewrite some basic
+ parts: logging to files, parsing options and high level
+ interfaces to reflection. When the Twisted project found itself
+ rewriting those, it moved them into a separate subpackage,
+ which does not depend on the rest of the twisted framework.
+ Hopefully, people will use <code>twisted.python</code> more and
+ solve interesting problems instead. Indeed, it is one of
+ Twisted's goals to serve as a repository for useful Python
+ code.</p>
+
+ <p>One useful module is <code>twisted.python.reflect</code>,
+ which has methods like <code>prefixedMethods</code>, which
+ returns all methods with a specific prefix. Even though some
+ modules in Python itself implement such functionality (notably,
+ <code>urllib2</code>), they do not expose it as a function
+ usable by outside code. Another useful module is
+ <code>twisted.python.hook</code>, which can add pre-hooks and
+ post-hooks to methods in classes.</p>
+
+ <blockquote>
+<pre class="python">
+# Add all method names beginning with opt_ to the given
+# dictionary. This cannot be done with dir(), since
+# it does not search in superclasses
+dct = {}
+reflect.addMethodNamesToDict(self.__class__, dct, "opt_")
+
+# Sum up all lists, in the given class and superclasses,
+# which have a given name. This gives us "different class
+# semantics": attributes do not override, but rather append
+flags = []
+reflect.accumulateClassList(self.__class__, 'optFlags', flags)
+
+# Add lock-acquire and lock-release to all methods which
+# are not multi-thread safe
+for methodName in klass.synchronized:
+ hook.addPre(klass, methodName, _synchPre)
+ hook.addPost(klass, methodName, _synchPost)
+
+</pre>
+
+ <h6>Listing 1: Using <code>twisted.python.reflect</code> and
+ <code>twisted.python.hook</code></h6>
+ </blockquote>
+
+ <p>The <code>twisted.python</code> subpackage also contains a
+ high-level interface to getopt which supplies as much power as
+ plain getopt while avoiding long
+ <code>if</code>/<code>elif</code> chains and making many common
+ cases easier to use. It uses the reflection interfaces in
+ <code>twisted.python.reflect</code> to find which options the
+ class is interested in, and constructs the argument to
+ <code>getopt</code>. Since in the common case options' values
+ are just saved in instance attributes, it is very easy to
+ indicate interest in such options. However, for the cases
+ custom code needs to be run for an option (for example,
+ counting how many <code>-v</code> options were given to
+ indicate verbosity level), it will call a method which is named
+ correctly.</p>
+
+ <blockquote>
+<pre class="python">
+class ServerOptions(usage.Options):
+ # Those are (short and long) options which
+ # have no argument. The corresponding attribute
+ # will be true iff this option was given
+ optFlags = [['nodaemon','n'],
+ ['profile','p'],
+ ['threaded','t'],
+ ['quiet','q'],
+ ['no_save','o']]
+ # This are options which require an argument
+ # The default is used if no such option was given
+ # Note: since options can only have string arguments,
+ # putting a non-string here is a reliable way to detect
+ # whether the option was given
+ optStrings = [['logfile','l',None],
+ ['file','f','twistd.tap'],
+ ['python','y',''],
+ ['pidfile','','twistd.pid'],
+ ['rundir','d','.']]
+
+ # For methods which can be called multiple times
+ # or have other unusual semantics, a method will be called
+ # Twisted assumes that the option needs an argument if and only if
+ # the method is defined to accept an argument.
+ def opt_plugin(self, pkgname):
+ pkg = __import__(pkgname)
+ self.python = os.path.join(os.path.dirname(
+ os.path.abspath(pkg.__file__)), 'config.tac')
+
+ # Most long options based on methods are aliased to short
+ # options. If there is only one letter, Twisted knows it is a short
+ # option, so it is "-g", not "--g"
+ opt_g = opt_plugin
+
+try:
+ config = ServerOptions()
+ config.parseOptions()
+except usage.error, ue:
+ print "%s: %s" % (sys.argv[0], ue)
+ sys.exit(1)
+</pre>
+
+ <h6>Listing 2: <code>twistd</code>'s Usage Code</h6>
+ </blockquote>
+
+ <p>Unlike <code>getopt</code>, Twisted has a useful abstraction
+ for the non-option arguments: they are passed as arguments to
+ the <code>parsedArgs</code> method. This means too many
+ arguments, or too few, will cause a usage error, which will be
+ flagged. If an unknown number of arguments is desired,
+ explicitly using a tuple catch-all argument will work.</p>
+
+ <h3>Configuration</h3>
+
+ <p>The formats of configuration files have shown two visible
+ trends over the years. On the one hand, more and more
+ programmability has been added, until sometimes they become a
+ new language. The extreme end of this trend is using a regular
+ programming language, such as Python, as the configuration
+ language. On the other hand, some configuration files became
+ more and more machine editable, until they become a miniature
+ database formates. The extreme end of that trend is using a
+ generic database tool.</p>
+
+ <p>Both trends stem from the same rationale -- the need to use
+ a powerful general purpose tool instead of hacking domain
+ specific languages. Domain specific languages are usually
+ ad-hoc and not well designed, having neither the power of
+ general purpose languages nor the predictable machine editable
+ format of generic databases.</p>
+
+ <p>Twisted combines these two trends. It can read the
+ configuration either from a Python file, or from a pickled
+ file. To some degree, it integrates the approaches by
+ auto-pickling state on shutdown, so the configuration files can
+ migrate from Python into pickles. Currently, there is no way to
+ go back from pickles to equivalent Python source, although it
+ is planned for the future. As a proof of concept, the RPG
+ framework Twisted Reality already has facilities for creating
+ Python source which evaluates into a given Python object.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import main
+from twisted.web import proxy, server
+site = server.Site(proxy.ReverseProxyResource('www.yahoo.com', 80, '/'))
+application = main.Application('web-proxy')
+application.listenOn(8080, site)
+</pre>
+
+ <h6>Listing 3: The configuration file for a reverse web
+ proxy</h6>
+ </blockquote>
+
+ <p>Twisted's main program, <code>twistd</code>, can receive
+ either a pickled <code>twisted.internet.main.Application</code>
+ or a Python file which defines a variable called
+ <code>application</code>. The application can be saved at any
+ time by calling its <code>save</code> method, which can take an
+ optional argument to save to a different file name. It would be
+ fairly easy, for example, to have a Twisted server which saves
+ the application every few seconds to a file whose name depends
+ on the time. Usually, however, one settles for the default
+ behavior which saves to a <code>shutdown</code> file. Then, if
+ the shutdown configuration proves suitable, the regular pickle
+ is replaced by the shutdown file. Hence, on the fly
+ configuration changes, regardless of complexity, can always
+ persist.</p>
+
+ <p>There are several client/server protocols which let a
+ suitably privileged user to access to application variable and
+ change it on the fly. The first, and least common denominator,
+ is telnet. The administrator can telnet into twisted, and issue
+ Python statements to her heart's content. For example, one can
+ add ports to listen on to the application, reconfigure the web
+ servers and various other ways by simple accessing
+ <code>__main__.application</code>. Some proof of concepts for a
+ simple suite of command-line utilities to control a Twisted
+ application were written, including commands which allow an
+ administrator to shut down the server or save the current state
+ to a tap file. These are especially useful on Microsoft
+ Windows(tm) platforms, where the normal UNIX way of
+ communicating shutdown requests via signals are less
+ reliable.</p>
+
+ <p>If reconfiguration on the fly is not necessary, Python
+ itself can be used as the configuration editor. Loading the
+ application is as simple as unpickling it, and saving it is
+ done by calling its <code>save</code> method. It is quite easy
+ to add more services or change existing ones from the Python
+ interactive mode.</p>
+
+ <p>A more sophisticated way to reconfigure the application on
+ the fly is via the manhole service. Manhole is a client/server
+ protocol based on top of Perspective Broker, Twisted's
+ translucent remote-object protocol which will be covered later.
+ Manhole has a graphical client called <code>gtkmanhole</code>
+ which can access the server and change its state. Since Twisted
+ is modular, it is possible to write more services for user
+ friendly configuration. For example, through-the-web
+ configuration is planned for several services, notably
+ mail.</p>
+
+ <p>For cases where a third party wants to distribute both the
+ code for a server and a ready to run configuration file, there
+ is the plugin configuration. Philosophically similar to the
+ <code>--python</code> option to <code>twistd</code>, it
+ simplifies the distribution process. A plugin is an archive
+ which is ready to be unpacked into the Python module path. In
+ order to keep a clean tree, <code>twistd</code> extends the
+ module path with some Twisted-specific paths, like the
+ directory <code>TwistedPlugins</code> in the user's home
+ directory. When a plugin is unpacked, it should be a Python
+ package which includes, alongside <code>__init__.py</code> a
+ file named <code>config.tac</code>. This file should define a
+ variable named <code>application</code>, in a similar way to
+ files loaded with <code>--python</code>. The plugin way of
+ distributing configurations is meant to reduce the temptation
+ to put large amount of codes inside the configuration file
+ itself.</p>
+
+ <p>Putting class and function definition inside the
+ configuration files would make the persistent servers which are
+ auto-generated on shutdown useless, since they would not have
+ access to the classes and functions defined inside the
+ configuration file. Thus, the plugin method is intended so
+ classes and functions can still be in regular, importable,
+ Python modules, but still allow third parties distribute
+ powerful configurations. Plugins are used by some of the
+ Twisted Reality virtual worlds.</p>
+
+ <h3>Ports, Protocol and Protocol Factories</h3>
+
+ <p><code>Port</code> is the Twisted class which represents a
+ socket listening on a port. Currently, twisted supports both
+ internet and unix-domain sockets, and there are SSL classes
+ with identical interface. A <code>Port</code> is only
+ responsible for handling the transfer layer. It calls
+ <code>accept</code> on the socket, checks that it actually
+ wants to deal with the connection and asks its factory for a
+ protocol. The factory is usually a subclass of
+ <code>twisted.protocols.protocol.Factory</code>, and its most
+ important method is <code>buildProtocol</code>. This should
+ return something that adheres to the protocol interface, and is
+ usually a subclass of
+ <code>twisted.protocols.protocol.Protocol</code>.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.protocols import protocol
+from twisted.internet import main, tcp
+
+class Echo(protocol.Protocol):
+ def dataReceived(self, data):
+ self.transport.write(data)
+
+factory = protocol.Factory()
+factory.protocol = Echo
+port = tcp.Port(8000, factory)
+app = main.Application("echo")
+app.addPort(port)
+app.run()
+</pre>
+
+ <h6>Listing 4: A Simple Twisted Application</h6>
+ </blockquote>
+
+ <p>The factory is responsible for two tasks: creating new
+ protocols, and keeping global configuration and state. Since
+ the factory builds the new protocols, it usually makes sure the
+ protocols have a reference to it. This allows protocols to
+ access, and change, the configuration. Keeping state
+ information in the factory is the primary reason for keeping an
+ abstraction layer between ports and protocols. Examples of
+ configuration information is the root directory of a web server
+ or the user database of a telnet server. Note that it is
+ possible to use the same factory in two different Ports. This
+ can be used to run the same server bound to several different
+ addresses but not to all of them, or to run the same server on
+ a TCP socket and a UNIX domain sockets.</p>
+
+ <p>A protocol begins and ends its life with
+ <code>connectionMade</code> and <code>connectionLost</code>;
+ both are called with no arguments. <code>connectionMade</code>
+ is called when a connection is first established. By then, the
+ protocol has a <code>transport</code> attribute. The
+ <code>transport</code> attribute is a <code>Transport</code> -
+ it supports <code>write</code> and <code>loseConnection</code>.
+ Both these methods never block: <code>write</code> actually
+ buffers data which will be written only when the transport is
+ signalled ready to for writing, and <code>loseConnection</code>
+ marks the transport for closing as soon as there is no buffered
+ data. Note that transports do <em>not</em> have a
+ <code>read</code> method: data arrives when it arrives, and the
+ protocol must be ready for its <code>dataReceived</code>
+ method, or its <code>connectionLost</code> method, to be
+ called. The transport also supports a <code>getPeer</code>
+ method, which returns parameters about the other side of the
+ transport. For TCP sockets, this includes the remote IP and
+ port.</p>
+
+ <blockquote>
+<pre class="python">
+# A tcp port-forwarder
+# A StupidProtocol sends all data it gets to its peer.
+# A StupidProtocolServer connects to the host/port,
+# and initializes the client connection to be its peer
+# and itself to be the client's peer
+from twisted.protocols import protocol
+
+class StupidProtocol(protocol.Protocol):
+ def connectionLost(self): self.peer.loseConnection();del self.peer
+ def dataReceived(self, data): self.peer.write(data)
+
+class StupidProtocolServer(StupidProtocol):
+ def connectionMade(self):
+ clientProtocol = StupidProtocol()
+ clientProtocol.peer = self.transport
+ self.peer = tcp.Client(self.factory.host, self.factory.port,
+ clientProtocol)
+
+# Create a factory which creates StupidProtocolServers, and
+# has the configuration information they assume
+def makeStupidFactory(host, port):
+ factory = protocol.Factory()
+ factory.host, factory.port = host, port
+ factory.protocol = StupidProtocolServer
+ return factory
+</pre>
+
+ <h6>Listing 5: TCP forwarder code</h6>
+ </blockquote>
+
+ <h3>The Event Loop</h3>
+
+ <p>While Twisted has the ability to let other event loops take
+ over for integration with GUI toolkits, it usually uses its own
+ event loop. The event loop code uses global variables to
+ maintain interested readers and writers, and uses Python's
+ <code>select()</code> function, which can accept any object
+ which has a <code>fileno()</code> method, not only raw file
+ descriptors. Objects can use the event loop interface to
+ indicate interest in either reading to or writing from a given
+ file descriptor. In addition, for those cases where time-based
+ events are needed (for example, queue flushing or periodic POP3
+ downloads), Twisted has a mechanism for repeating events at
+ known delays. While far from being real-time, this is enough
+ for most programs' needs.</p>
+
+ <h3>Going Higher Level</h3>
+
+ <p>Unfortunately, handling arbitrary data chunks is a hard way
+ to code a server. This is why twisted has many classes sitting
+ in submodules of the twisted.protocols package which give
+ higher level interface to the data. For line oriented
+ protocols, <code>LineReceiver</code> translates the low-level
+ <code>dataReceived</code> events into <code>lineReceived</code>
+ events. However, the first naive implementation of
+ <code>LineReceiver</code> proved to be too simple. Protocols
+ like HTTP/1.1 or Freenet have packets which begin with header
+ lines that include length information, and then byte streams.
+ <code>LineReceiver</code> was rewritten to have a simple
+ interface for switching at the protocol layer between
+ line-oriented parts and byte-stream parts.</p>
+
+ <p>Another format which is gathering popularity is Dan J.
+ Bernstein's netstring format. This format keeps ASCII text as
+ ASCII, but allows arbitrary bytes (including nulls and
+ newlines) to be passed freely. However, netstrings were never
+ designed to be used in event-based protocols where over-reading
+ is unavoidable. Twisted makes sure no user will have to deal
+ with the subtle problems handling netstrings in event-driven
+ programs by providing <code>NetstringReceiver</code>.</p>
+
+ <p>For even higher levels, there are the protocol-specific
+ protocol classes. These translate low-level chunks into
+ high-level events such as "HTTP request received" (for web
+ servers), "approve destination address" (for mail servers) or
+ "get user information" (for finger servers). Many RFCs have
+ been thus implemented for Twisted (at latest count, more then
+ 12 RFCs have been implemented). One of Twisted's goals is to be
+ a repository of event-driven implementations for various
+ protocols in Python.</p>
+
+ <blockquote>
+<pre class="python">
+class DomainSMTP(SMTP):
+
+ def validateTo(self, helo, destination):
+ try:
+ user, domain = string.split(destination, '@', 1)
+ except ValueError:
+ return 0
+ if not self.factory.domains.has_key(domain):
+ return 0
+ if not self.factory.domains[domain].exists(user, domain, self):
+ return 0
+ return 1
+
+ def handleMessage(self, helo, origin, recipients, message):
+ # No need to check for existence -- only recipients which
+ # we approved at the validateTo stage are passed here
+ for recipient in recipients:
+ user, domain = string.split(recipient, '@', 1)
+ self.factory.domains[domain].saveMessage(origin, user, message,
+ domain)
+</pre>
+
+ <h6>Listing 6: Implementation of virtual domains using the
+ SMTP protocol class</h6>
+ </blockquote>
+
+ <p>Copious documentation on writing new protocol abstraction
+ exists, since this is the largest amount of code written --
+ much like most operating system code is device drivers. Since
+ many different protocols have already been implemented, there
+ are also plenty of examples to draw on. Usually implementing
+ the client-side of a protocol is particularly challenging,
+ since protocol designers tend to assume much more state kept on
+ the client side of a connection then on the server side.</p>
+
+ <h3>The <code>twisted.tap</code> Package and
+ <code>mktap</code></h3>
+
+ <p>Since one of Twisted's configuration formats are pickles,
+ which are tricky to edit by hand, Twisted evolved a framework
+ for creating such pickles. This framework is contained in the
+ <code>twisted.tap</code> package and the <code>mktap</code>
+ script. New servers, or new ways to configure existing servers,
+ can easily participate in the twisted.tap framework by creating
+ a <code>twisted.tap</code> submodule.</p>
+
+ <p>All <code>twisted.tap</code> submodules must conform to a
+ rigid interface. The interface defines functions to accept the
+ command line parameters, and functions to take the processed
+ command line parameters and add servers to
+ <code>twisted.main.internet.Application</code>. Existing
+ <code>twisted.tap</code> submodules use
+ <code>twisted.python.usage</code>, so the command line format
+ is consistent between different modules.</p>
+
+ <p>The <code>mktap</code> utility gets some generic options,
+ and then the name of the server to build. It imports a
+ same-named <code>twisted.tap</code> submodule, and lets it
+ process the rest of the options and parameters. This makes sure
+ that the process configuring the <code>main.Application</code>
+ is agnostic for where it is used. This allowed
+ <code>mktap</code> to grow the <code>--append</code> option,
+ which appends to an existing pickle rather then creating a new
+ one. This option is frequently used to post-add a telnet server
+ to an application, for net-based on the fly configuration
+ later.</p>
+
+ <p>When running <code>mktap</code> under UNIX, it saves the
+ user id and group id inside the tap. Then, when feeding this
+ tap into <code>twistd</code>, it changes to this user/group id
+ after binding the ports. Such a feature is necessary in any
+ production-grade server, since ports below 1024 require root
+ privileges to use on UNIX -- but applications should not run as
+ root. In case changing to the specified user causes difficulty
+ in the build environment, it is also possible to give those
+ arguments to <code>mktap</code> explicitly.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import tcp, stupidproxy
+from twisted.python import usage
+
+usage_message = """
+usage: mktap stupid [OPTIONS]
+
+Options are as follows:
+ --port &lt;#&gt;, -p: set the port number to &lt;#&gt;.
+ --host &lt;host&gt;, -h: set the host to &lt;host&gt;
+ --dest_port &lt;#&gt;, -d: set the destination port to &lt;#&gt;
+"""
+
+class Options(usage.Options):
+ optStrings = [["port", "p", 6666],
+ ["host", "h", "localhost"],
+ ["dest_port", "d", 6665]]
+
+def getPorts(app, config):
+ s = stupidproxy.makeStupidFactory(config.host, int(config.dest_port))
+ return [(int(config.port), s)]
+</pre>
+
+ <h6>Listing 7: <code>twisted.tap.stupid</code></h6>
+ </blockquote>
+
+ <p>The <code>twisted.tap</code> framework is one of the reasons
+ servers can be set up with little knowledge and time. Simply
+ running <code>mktap</code> with arguments can bring up a web
+ server, a mail server or an integrated chat server -- with
+ hardly any need for maintainance. As a working
+ proof-on-concept, the <code>tap2deb</code> utility exists to
+ wrap up tap files in Debian packages, which include scripts for
+ running and stopping the server and interact with
+ <code>init(8)</code> to make sure servers are automatically run
+ on start-up. Such programs can also be written to interface
+ with the Red Hat Package Manager or the FreeBSD package
+ management systems.</p>
+
+ <blockquote>
+<pre class="shell">
+% mktap --uid 33 --gid 33 web --static /var/www --port 80
+% tap2deb -t web.tap -m 'Moshe Zadka &lt;moshez@debian.org&gt;'
+% su
+password:
+# dpkg -i .build/twisted-web_1.0_all.deb
+</pre>
+
+ <h6>Listing 8: Bringing up a web server on a Debian
+ system</h6>
+ </blockquote>
+
+ <h3>Multi-thread Support</h3>
+
+ <p>Sometimes, threads are unavoidable or hard to avoid. Many
+ legacy programs which use threads want to use Twisted, and some
+ vendor APIs have no non-blocking version -- for example, most
+ database systems' API. Twisted can work with threads, although
+ it supports only one thread in which the main select loop is
+ running. It can use other threads to simulate non-blocking API
+ over a blocking API -- it spawns a thread to call the blocking
+ API, and when it returns, the thread calls a callback in the
+ main thread. Threads can call callbacks in the main thread
+ safely by adding those callbacks to a list of pending events.
+ When the main thread is between select calls, it searches
+ through the list of pending events, and executes them. This is
+ used in the <code>twisted.enterprise</code> package to supply
+ an event driven interfaces to databases, which uses Python's DB
+ API.</p>
+
+ <p>Twisted tries to optimize for the common case -- no threads.
+ If there is need for threads, a special call must be made to
+ inform the <code>twisted.python.threadable</code> module that
+ threads will be used. This module is implemented differently
+ depending on whether threads will be used or not. The decision
+ must be made before importing any modules which use threadable,
+ and so is usually done in the main application. For example,
+ <code>twistd</code> has a command line option to initialize
+ threads.</p>
+
+ <p>Twisted also supplies a module which supports a threadpool,
+ so the common task of implementing non-blocking APIs above
+ blocking APIs will be both easy and efficient. Threads are kept
+ in a pool, and dispatch requests are done by threads which are
+ not working. The pool supports a maximum amount of threads, and
+ will throw exceptions when there are more requests than
+ allowable threads.</p>
+
+ <p>One of the difficulties about multi-threaded systems is
+ using locks to avoid race conditions. Twisted uses a mechanism
+ similar to Java's synchronized methods. A class can declare a
+ list of methods which cannot safely be called at the same time
+ from two different threads. A function in threadable then uses
+ <code>twisted.python.hook</code> to transparently add
+ lock/unlock around these methods. This allows Twisted classes
+ to be written without thought about threading, except for one
+ localized declaration which does not entail any performance
+ penalty for the single-threaded case.</p>
+
+ <h3>Twisted Mail Server</h3>
+
+ <p>Mail servers have a history of security flaws. Sendmail is
+ by now the poster boy of security holes, but no mail servers,
+ bar maybe qmail, are free of them. Like Dan Bernstein of qmail
+ fame said, mail cannot be simply turned off -- even the
+ simplest organization needs a mail server. Since Twisted is
+ written in a high-level language, many problems which plague
+ other mail servers, notably buffer overflows, simply do not
+ exist. Other holes are avoidable with correct design. Twisted
+ Mail is a project trying to see if it is possible to write a
+ high quality high performance mail server entirely in
+ Python.</p>
+
+ <p>Twisted Mail is built on the SMTP server and client protocol
+ classes. While these present a level of abstraction from the
+ specific SMTP line semantics, they do not contain any message
+ storage code. The SMTP server class does know how to divide
+ responsibility between domains. When a message arrives, it
+ analyzes the recipient's address, tries matching it with one of
+ the registered domain, and then passes validation of the
+ address and saving the message to the correct domain, or
+ refuses to handle the message if it cannot handle the domain.
+ It is possible to specify a catch-all domain, which will
+ usually be responsible for relaying mails outwards.</p>
+
+ <p>While correct relaying is planned for the future, at the
+ moment we have only so-called "smarthost" relaying. All e-mail
+ not recognized by a local domain is relayed to a single outside
+ upstream server, which is supposed to relay the mail further.
+ This is the configuration for most home machines, which are
+ Twisted Mail's current target audience.</p>
+
+ <p>Since the people involved in Twisted's development were
+ reluctant to run code that runs as a super user, or with any
+ special privileges, it had to be considered how delivery of
+ mail to users is possible. The solution decided upon was to
+ have Twisted deliver to its own directory, which should have
+ very strict permissions, and have users pull the mail using
+ some remote mail access protocol like POP3. This means only a
+ user would write to his own mail box, so no security holes in
+ Twisted would be able to adversely affect a user.</p>
+
+ <p>Future plans are to use a Perspective Broker-based service
+ to hand mail to users to a personal server using a UNIX domain
+ socket, as well as to add some more conventional delivery
+ methods, as scary as they may be.</p>
+
+ <p>Because the default configuration of Twisted Mail is to be
+ an integrated POP3/SMTP servers, it is ideally suited for the
+ so-called POP toaster configuration, where there are a
+ multitude of virtual users and domains, all using the same IP
+ address and computer to send and receive mails. It is fairly
+ easy to configure Twisted as a POP toaster. There are a number
+ of deployment choices: one can append a telnet server to the
+ tap for remote configuration, or simple scripts can add and
+ remove users from the user database. The user database is saved
+ as a directory, where file names are keys and file contents are
+ values, so concurrency is not usually a problem.</p>
+
+ <blockquote>
+<pre class="shell">
+% mktap mail -d foobar.com=$HOME/Maildir/ -u postmaster=secret -b \
+ -p 110 -s 25
+% twistd -f mail.tap
+
+</pre>
+
+ <h6>Bringing up a simple mail-server</h6>
+ </blockquote>
+
+ <p>Twisted's native mail storage format is Maildir, a format
+ that requires no locking and is safe and atomic. Twisted
+ supports a number of standardized extensions to Maildir,
+ commonly known as Maildir++. Most importantly, it supports
+ deletion as simply moving to a subfolder named
+ <code>Trash</code>, so mail is recoverable if accessed through
+ a protocol which allows multiple folders, like IMAP. However,
+ Twisted itself currently does not support any such protocol
+ yet.</p>
+
+ <h3>Introducing Perspective Broker</h3>
+
+ <h4>All the World's a Game</h4>
+
+ <p>Twisted was originally designed to support multi-player
+ games; a simulated "real world" environment. Experience with
+ game systems of that type is enlightening as to the nature of
+ computing on the whole. Almost all services on a computer are
+ modeled after some simulated real-world activity. For example,
+ e-"mail", or "document publishing" on the web. Even
+ "object-oriented" programming is based around the notion that
+ data structures in a computer simulate some analogous
+ real-world objects.</p>
+
+ <p>All such networked simulations have a few things in common.
+ They each represent a service provided by software, and there
+ is usually some object where "global" state is kept. Such a
+ service must provide an authentication mechanism. Often, there
+ is a representation of the authenticated user within the
+ context of the simulation, and there are also objects aside
+ from the user and the simulation itself that can be
+ accessed.</p>
+
+ <p>For most existing protocols, Twisted provides these
+ abstractions through <code>twisted.internet.passport</code>.
+ This is so named because the most important common
+ functionality it provides is authentication. A simulation
+ "world" as described above -- such as an e-mail system,
+ document publishing archive, or online video game -- is
+ represented by subclass of <code>Service</code>, the
+ authentication mechanism by an <code>Authorizer</code> (which
+ is a set of <code>Identities</code>), and the user of the
+ simulation by a <code>Perspective</code>. Other objects in the
+ simulation may be represented by arbitrary python objects,
+ depending upon the implementation of the given protocol.</p>
+
+ <p>New problem domains, however, often require new protocols,
+ and re-implementing these abstractions each time can be
+ tedious, especially when it's not necessary. Many efforts have
+ been made in recent years to create generic "remote object" or
+ "remote procedure call" protocols, but in developing Twisted,
+ these protocols were found to require too much overhead in
+ development, be too inefficient at runtime, or both.</p>
+
+ <p>Perspective Broker is a new remote-object protocol designed
+ to be lightweight and impose minimal constraints upon the
+ development process and use Python's dynamic nature to good
+ effect, but still relatively efficient in terms of bandwidth
+ and CPU utilization. <code>twisted.spread.pb</code> serves as a
+ reference implementation of the protocol, but implementation of
+ Perspective Broker in other languages is already underway.
+ <code>spread</code> is the <code>twisted</code> subpackage
+ dealing with remote calls and objects, and has nothing to do
+ with the <code>spread</code> toolkit.</p>
+
+ <p>Perspective Broker extends
+ <code>twisted.internet.passport</code>'s abstractions to be
+ concrete objects rather than design patterns. Rather than
+ having a <code>Protocol</code> implementation translate between
+ sequences of bytes and specifically named methods (as in the
+ other Twisted <code>Protocols</code>), Perspective Broker
+ defines a direct mapping between network messages and
+ quasi-arbitrary method calls.</p>
+
+ <h3>Translucent, not Transparent</h3>
+
+ <p>In a server application where a large number of clients may
+ be interacting at once, it is not feasible to have an
+ arbitrarily large number of OS threads blocking and waiting for
+ remote method calls to return. Additionally, the ability for
+ any client to call any method of an object would present a
+ significant security risk. Therefore, rather than attempting to
+ provide a transparent interface to remote objects,
+ <code>twisted.spread.pb</code> is "translucent", meaning that
+ while remote method calls have different semantics than local
+ ones, the similarities in semantics are mirrored by
+ similarities in the syntax. Remote method calls impose as
+ little overhead as possible in terms of volume of code, but "as
+ little as possible" is unfortunately not "nothing".</p>
+
+ <p><code>twisted.spread.pb</code> defines a method naming
+ standard for each type of remotely accessible object. For
+ example, if a client requests a method call with an expression
+ such as <code>myPerspective.doThisAction()</code>, the remote
+ version of <code>myPerspective</code> would be sent the message
+ <code>perspective_doThisAction</code>. Depending on the manner
+ in which an object is accessed, other method prefixes may be
+ <code>observe_</code>, <code>view_</code>, or
+ <code>remote_</code>. Any method present on a remotely
+ accessible object, and named appropriately, is considered to be
+ published -- since this is accomplished with
+ <code>getattr</code>, the definition of "present" is not just
+ limited to methods defined on the class, but instances may have
+ arbitrary callable objects associated with them as long as the
+ name is correct -- similarly to normal python objects.</p>
+
+ <p>Remote method calls are made on remote reference objects
+ (instances of <code>pb.RemoteReference</code>) by calling a
+ method with an appropriate name. However, that call will not
+ block -- if you need the result from a remote method call, you
+ pass in one of the two special keyword arguments to that method
+ -- <code>pbcallback</code> or <code>pberrback</code>.
+ <code>pbcallback</code> is a callable object which will be
+ called when the result is available, and <code>pberrback</code>
+ is a callable object which will be called if there was an
+ exception thrown either in transmission of the call or on the
+ remote side.</p>
+
+ <p>In the case that neither <code>pberrback</code> or
+ <code>pbcallback</code> is provided,
+ <code>twisted.spread.pb</code> will optimize network usage by
+ not sending confirmations of messages.</p>
+
+ <blockquote>
+<pre class="python">
+# Server Side
+class MyObject(pb.Referenceable):
+ def remote_doIt(self):
+ return "did it"
+
+# Client Side
+ ...
+ def myCallback(result):
+ print result # result will be 'did it'
+ def myErrback(stacktrace):
+ print 'oh no, mr. bill!'
+ print stacktrace
+ myRemoteReference.doIt(pbcallback=myCallback,
+ pberrback=myErrback)
+</pre>
+
+ <h6>Listing 9: A remotely accessible object and accompanying
+ call</h6>
+ </blockquote>
+
+ <h3>Different Behavior for Different Perspectives</h3>
+
+ <p>Considering the problem of remote object access in terms of
+ a simulation demonstrates a requirement for the knowledge of an
+ actor with certain actions or requests. Often, when processing
+ message, it is useful to know who sent it, since different
+ results may be required depending on the permissions or state
+ of the caller.</p>
+
+ <p>A simple example is a game where certain an object is
+ invisible, but players with the "Heightened Perception"
+ enchantment can see it. When answering the question "What
+ objects are here?" it is important for the room to know who is
+ asking, to determine which objects they can see. Parallels to
+ the differences between "administrators" and "users" on an
+ average multi-user system are obvious.</p>
+
+ <p>Perspective Broker is named for the fact that it does not
+ broker only objects, but views of objects. As a user of the
+ <code>twisted.spread.pb</code> module, it is quite easy to
+ determine the caller of a method. All you have to do is
+ subclass <code>Viewable</code>.</p>
+
+ <blockquote>
+<pre class="python">
+# Server Side
+class Greeter(pb.Viewable):
+ def view_greet(self, actor):
+ return "Hello %s!\n" % actor.perspectiveName
+
+# Client Side
+ ...
+ remoteGreeter.greet(pbcallback=sys.stdout.write)
+ ...
+</pre>
+
+ <h6>Listing 10: An object responding to its calling
+ perspective</h6>
+ </blockquote>
+ Before any arguments sent by the client, the actor
+ (specifically, the Perspective instance through which this
+ object was retrieved) will be passed as the first argument to
+ any <code>view_xxx</code> methods.
+
+ <h3>Mechanisms for Sharing State</h3>
+
+ <p>In a simulation of any decent complexity, client and server
+ will wish to share structured data. Perspective Broker provides
+ a mechanism for both transferring (copying) and sharing
+ (caching) that state.</p>
+
+ <p>Whenever an object is passed as an argument to or returned
+ from a remote method call, that object is serialized using
+ <code>twisted.spread.jelly</code>; a serializer similar in some
+ ways to Python's native <code>pickle</code>. Originally,
+ <code>pickle</code> itself was going to be used, but there were
+ several security issues with the <code>pickle</code> code as it
+ stands. It is on these issues of security that
+ <code>pickle</code> and <code>twisted.spread.jelly</code> part
+ ways.</p>
+
+ <p>While <code>twisted.spread.jelly</code> handles a few basic
+ types such as strings, lists, dictionaries and numbers
+ automatically, all user-defined types must be registered both
+ for serialization and unserialization. This registration
+ process is necessary on the sending side in order to determine
+ if a particular object is shared, and whether it is shared as
+ state or behavior. On the receiving end, it's necessary to
+ prevent arbitrary code from being run when an object is
+ unserialized -- a significant security hole in
+ <code>pickle</code> for networked applications.</p>
+
+ <p>On the sending side, the registration is accomplished by
+ making the object you want to serialize a subclass of one of
+ the "flavors" of object that are handled by Perspective Broker.
+ A class may be <code>Referenceable</code>,
+ <code>Viewable</code>, <code>Copyable</code> or
+ <code>Cacheable</code>. These four classes correspond to
+ different ways that the object will be seen remotely.
+ Serialization flavors are mutually exclusive -- these 4 classes
+ may not be mixed in with each other.</p>
+
+ <ul>
+ <li><code>Referenceable</code>: The remote side will refer to
+ this object directly. Methods with the prefix
+ <code>remote_</code> will be callable on it. No state will be
+ transferred.</li>
+
+ <li><code>Viewable</code>: The remote side will refer to a
+ proxy for this object, which indicates what perspective
+ accessed this; as discussed above. Methods with the prefix
+ <code>view_</code> will be callable on it, and have an
+ additional first argument inserted (the perspective that
+ called the method). No state will be transferred.</li>
+
+ <li><code>Copyable</code>: Each time this object is
+ serialized, its state will be copied and sent. No methods are
+ remotely callable on it. By default, the state sent will be
+ the instance's <code>__dict__</code>, but a method
+ <code>getStateToCopyFor(perspective)</code> may be defined
+ which returns an arbitrary serializable object for
+ state.</li>
+
+ <li><code>Cacheable</code>: The first time this object is
+ serialized, its state will be copied and sent. Each
+ subsequent time, however, a reference to the original object
+ will be sent to the receiver. No methods will be remotely
+ callable on this object. By default, again, the state sent
+ will be the instance's <code>__dict__</code>but a method
+ <code>getStateToCacheAndObserveFor(perspective,
+ observer)</code> may be defined to return alternative state.
+ Since the state for this object is only sent once, the
+ <code>observer</code> argument is an object representative of
+ the receiver's representation of the <code>Cacheable</code>
+ after unserialization -- method calls to this object will be
+ resolved to methods prefixed with <code>observe_</code>,
+ <em>on the receiver's <code>RemoteCache</code> of this
+ object</em>. This may be used to keep the receiver's cache
+ up-to-date as relevant portions of the <code>Cacheable</code>
+ object change.</li>
+ </ul>
+
+ <h3>Publishing Objects with PB</h3>
+
+ <p>The previous samples of code have shown how an individual
+ object will interact over a previously-established PB
+ connection. In order to get to that connection, you need to do
+ some set-up work on both the client and server side; PB
+ attempts to minimize this effort.</p>
+
+ <p>There are two different approaches for setting up a PB
+ server, depending on your application's needs. In the simplest
+ case, where your application does not deal with the
+ abstractions above -- services, identities, and perspectives --
+ you can simply publish an object on a particular port.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class Echoer(pb.Root):
+ def remote_echo(self, st):
+ print 'echoing:', st
+ return st
+if __name__ == '__main__':
+ app = main.Application("pbsimple")
+ app.listenOn(8789, pb.BrokerFactory(Echoer()))
+ app.run()
+</pre>
+
+ <h6>Listing 11: Creating a simple PB server</h6>
+ </blockquote>
+
+ <p>Listing 11 shows how to publish a simple object which
+ responds to a single message, "echo", and returns whatever
+ argument is sent to it. There is very little to explain: the
+ "Echoer" class is a pb.Root, which is a small subclass of
+ Referenceable designed to be used for objects published by a
+ BrokerFactory, so Echoer follows the same rule for remote
+ access that Referenceable does. Connecting to this service is
+ almost equally simple.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def gotObject(object):
+ print "got object:",object
+ object.echo("hello network", pbcallback=gotEcho)
+def gotEcho(echo):
+ print 'server echoed:',echo
+ main.shutDown()
+def gotNoObject(reason):
+ print "no object:",reason
+ main.shutDown()
+pb.getObjectAt("localhost", 8789, gotObject, gotNoObject, 30)
+main.run()
+</pre>
+
+ <h6>Listing 12: A client for Echoer objects.</h6>
+ </blockquote>
+
+ <p>The utility function <code>pb.getObjectAt</code> retrieves
+ the root object from a hostname/port-number pair and makes a
+ callback (in this case, <code>gotObject</code>) if it can
+ connect and retrieve the object reference successfully, and an
+ error callback (<code>gotNoObject</code>) if it cannot connect
+ or the connection times out.</p>
+
+ <p><code>gotObject</code> receives the remote reference, and
+ sends the <code>echo</code> message to it. This call is
+ visually noticeable as a remote method invocation by the
+ distinctive <code>pbcallback</code> keyword argument. When the
+ result from that call is received, <code>gotEcho</code> will be
+ called, notifying us that in fact, the server echoed our input
+ ("hello network").</p>
+
+ <p>While this setup might be useful for certain simple types of
+ applications where there is no notion of a "user", the
+ additional complexity necessary for authentication and service
+ segregation is worth it. In particular, re-use of server code
+ for things like chat (twisted.words) is a lot easier with a
+ unified notion of users and authentication.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+class SimplePerspective(pb.Perspective):
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+class SimpleService(pb.Service):
+ def getPerspectiveNamed(self, name):
+ return SimplePerspective(name, self)
+if __name__ == '__main__':
+ import pbecho
+ app = main.Application("pbecho")
+ pbecho.SimpleService("pbecho",app).getPerspectiveNamed("guest")\
+ .makeIdentity("guest")
+ app.listenOn(pb.portno, pb.BrokerFactory(pb.AuthRoot(app)))
+ app.save("start")
+</pre>
+
+ <h6>Listing 13: A PB server using twisted's "passport"
+ authentication.</h6>
+ </blockquote>
+
+ <p>In terms of the "functionality" it offers, this server is
+ identical. It provides a method which will echo some simple
+ object sent to it. However, this server provides it in a manner
+ which will allow it to cooperate with multiple other
+ authenticated services running on the same connection, because
+ it uses the central Authorizer for the application.</p>
+
+ <p>On the line that creates the <code>SimpleService</code>,
+ several things happen.</p>
+
+ <ol>
+ <li>A SimpleService is created and persistently added to the
+ <code>Application</code> instance.</li>
+
+ <li>A SimplePerspective is created, via the overridden
+ <code>getPerspectiveNamed</code> method.</li>
+
+ <li>That <code>SimplePerspective</code> has an
+ <code>Identity</code> generated for it, and persistently
+ added to the <code>Application</code>'s
+ <code>Authorizer</code>. The created identity will have the
+ same name as the perspective ("guest"), and the password
+ supplied (also, "guest"). It will also have a reference to
+ the service "pbecho" and a perspective named "guest", by
+ name. The <code>Perspective.makeIdentity</code> utility
+ method prevents having to deal with the intricacies of the
+ passport <code>Authorizer</code> system when one doesn't
+ require strongly separate <code>Identity</code>s and
+ <code>Perspective</code>s.</li>
+ </ol>
+ <br />
+ <br />
+
+
+ <p>Also, this server does not run itself, but instead persists
+ to a file which can be run with twistd, offering all the usual
+ amenities of daemonization, logging, etc. Once the server is
+ run, connecting to it is similar to the previous example.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.internet import main
+def success(message):
+ print "Message received:",message
+ main.shutDown()
+def failure(error):
+ print "Failure...",error
+ main.shutDown()
+def connected(perspective):
+ perspective.echo("hello world",
+ pbcallback=success,
+ pberrback=failure)
+ print "connected."
+pb.connect(connected, failure, "localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 30)
+main.run()
+</pre>
+
+ <h6>Listing 14: Connecting to an Authorized Service</h6>
+ </blockquote>
+ <br />
+ <br />
+
+
+ <p>This introduces a new utility -- <code>pb.connect</code>.
+ This function takes a long list of arguments and manages the
+ handshaking and challenge/response aspects of connecting to a
+ PB service perspective, eventually calling back to indicate
+ either success or failure. In this particular example, we are
+ connecting to localhost on the default PB port (8787),
+ authenticating to the identity "guest" with the password
+ "guest", requesting the perspective "guest" from the service
+ "pbecho". If this can't be done within 30 seconds, the
+ connection will abort.</p>
+
+ <p>In these examples, I've attempted to show how Twisted makes
+ event-based scripting easier; this facilitates the ability to
+ run short scripts as part of a long-running process. However,
+ event-based programming is not natural to procedural scripts;
+ it is more generally accepted that GUI programs will be
+ event-driven whereas scripts will be blocking. An alternative
+ client to our <code>SimpleService</code> using GTK illustrates
+ the seamless meshing of Twisted and GTK.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.internet import main, ingtkernet
+from twisted.spread.ui import gtkutil
+import gtk
+ingtkernet.install()
+class EchoClient:
+ def __init__(self, echoer):
+ l.hide()
+ self.echoer = echoer
+ w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.GtkVBox(); b = gtk.GtkButton("Echo:")
+ self.entry = gtk.GtkEntry(); self.outry = gtk.GtkEntry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', gtk.mainquit)
+ w.show_all()
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.echo(txt, pbcallback=self.outry.set_text)
+l = gtkutil.Login(EchoClient, None, initialService="pbecho")
+l.show_all()
+gtk.mainloop()
+</pre>
+
+ <h6>Listing 15: A Twisted GUI application</h6>
+ </blockquote>
+
+ <h3>Event-Driven Web Object Publishing with Web.Widgets</h3>
+
+ <p>Although PB will be interesting to those people who wish to
+ write custom clients for their networked applications, many
+ prefer or require a web-based front end. Twisted's built-in web
+ server has been designed to accommodate this desire, and the
+ presentation framework that one would use to write such an
+ application is <code>twisted.web.widgets</code>. Web.Widgets
+ has been designed to work in an event-based manner, without
+ adding overhead to the designer or the developer's
+ work-flow.</p>
+
+ <p>Surprisingly, asynchronous web interfaces fit very well into
+ the normal uses of purpose-built web toolkits such as PHP. Any
+ experienced PHP, Zope, or WebWare developer will tell you that
+ <em>separation of presentation, content, and logic</em> is very
+ important. In practice, this results in a "header" block of
+ code which sets up various functions which are called
+ throughout the page, some of which load blocks of content to
+ display. While PHP does not enforce this, it is certainly
+ idiomatic. Zope enforces it to a limited degree, although it
+ still allows control structures and other programmatic elements
+ in the body of the content.</p>
+
+ <p>In Web.Widgets, strict enforcement of this principle
+ coincides very neatly with a "hands-free" event-based
+ integration, where much of the work of declaring callbacks is
+ implicit. A "Presentation" has a very simple structure for
+ evaluating Python expressions and giving them a context to
+ operate in. The "header" block which is common to many
+ templating systems becomes a class, which represents an
+ enumeration of events that the template may generate, each of
+ which may be responded to either immediately or latently.</p>
+
+ <p>For the sake of simplicity, as well as maintaining
+ compatibility for potential document formats other than HTML,
+ Presentation widgets do not attempt to parse their template as
+ HTML tags. The structure of the template is <code>"HTML Text
+ %%%%python_expression()%%%% more HTML Text"</code>. Every set
+ of 4 percent signs (%%%%) switches back and forth between
+ evaluation and printing.</p>
+
+ <p>No control structures are allowed in the template. This was
+ originally thought to be a potentially major inconvenience, but
+ with use of the Web.Widgets code to develop a few small sites,
+ it has seemed trivial to encapsulate any table-formatting code
+ within a method; especially since those methods can take string
+ arguments if there's a need to customize the table's
+ appearance.</p>
+
+ <p>The namespace for evaluating the template expressions is
+ obtained by scanning the class hierarchy for attributes, and
+ getting each of those attributes from the current instance.
+ This means that all methods will be bound methods, so
+ indicating "self" explicitly is not required. While it is
+ possible to override the method for creating namespaces, using
+ this default has the effect of associating all presentation
+ code for a particular widget in one class, along with its
+ template. If one is working with a non-programmer designer, and
+ the template is in an external file, it is always very clear to
+ the designer what functionality is available to them in any
+ given scope, because there is a list of available methods for
+ any given class.</p>
+
+ <p>A convenient event to register for would be a response from
+ the PB service that we just implemented. We can use the
+ <code>Deferred</code> class in order to indicate to the widgets
+ framework that certain work has to be done later. This is a
+ Twisted convention which one can currently use in PB as well as
+ webwidgets; any framework which needs the ability to defer a
+ return value until later should use this facility. Elements of
+ the page will be rendered from top to bottom as data becomes
+ available, so the page will not be blocked on rendering until
+ all deferred elements have been completed.</p>
+
+ <blockquote>
+<pre class="python">
+from twisted.spread import pb
+from twisted.python import defer
+from twisted.web import widgets
+class EchoDisplay(widgets.Presentation):
+ template = """&lt;H1&gt;Welcome to my widget, displaying %%%%echotext%%%%.&lt;/h1&gt;
+ &lt;p&gt;Here it is: %%%%getEchoPerspective()%%%%&lt;/p&gt;"""
+ echotext = 'hello web!'
+ def getEchoPerspective(self):
+ d = defer.Deferred()
+ pb.connect(d.callback, d.errback, "localhost", pb.portno,
+ "guest", "guest", "pbecho", "guest", 1)
+ d.addCallbacks(self.makeListOf, self.formatTraceback)
+ return ['&lt;b&gt;',d,'&lt;/b&gt;']
+ def makeListOf(self, echoer):
+ d = defer.Deferred()
+ echoer.echo(self.echotext, pbcallback=d.callback, pberrback=d.errback)
+ d.addCallbacks(widgets.listify, self.formatTraceback)
+ return [d]
+if __name__ == "__main__":
+ from twisted.web import server
+ from twisted.internet import main
+ a = main.Application("pbweb")
+ gdgt = widgets.Gadget()
+ gdgt.widgets['index'] = EchoDisplay()
+ a.listenOn(8080, server.Site(gdgt))
+ a.run()
+</pre>
+
+ <h6>Listing 16: an event-based web widget.</h6>
+ </blockquote>
+
+ <p>Each time a Deferred is returned as part of the page, the
+ page will pause rendering until the deferred's
+ <code>callback</code> method is invoked. When that callback is
+ made, it is inserted at the point in the page where rendering
+ left off.</p>
+
+ <p>If necessary, there are options within web.widgets to allow
+ a widget to postpone or cease rendering of the entire page --
+ for example, it is possible to write a FileDownload widget,
+ which will override the rendering of the entire page and
+ replace it with a file download.</p>
+
+ <p>The final goal of web.widgets is to provide a framework
+ which encourages the development of usable library code. Too
+ much web-based code is thrown away due to its particular
+ environment requirements or stylistic preconceptions it carries
+ with it. The goal is to combine the fast-and-loose iterative
+ development cycle of PHP with the ease of installation and use
+ of Zope's "Product" plugins.</p>
+
+ <h3>Things That Twisted Does Not Do</h3>
+
+ <p>It is unfortunately well beyond the scope of this paper to
+ cover all the functionality that Twisted provides, but it
+ serves as a good overview. It may seem as though twisted does
+ anything and everything, but there are certain features we
+ never plan to implement because they are simply outside the
+ scope of the project.</p>
+
+ <p>Despite the multiple ways to publish and access objects,
+ Twisted does not have or support an interface definition
+ language. Some developers on the Twisted project have
+ experience with remote object interfaces that require explicit
+ specification of all datatypes during the design of an object's
+ interface. We feel that such interfaces are in the spirit of
+ statically-typed languages, and are therefore suited to the
+ domain of problems where statically-typed languages excel.
+ Twisted has no plans to implement a protocol schema or static
+ type-checking mechanism, as the efficiency gained by such an
+ approach would be quickly lost again by requiring the type
+ conversion between Python's dynamic types and the protocol's
+ static ones. Since one of the key advantages of Python is its
+ extremely flexible dynamic type system, we felt that a
+ dynamically typed approach to protocol design would share some
+ of those advantages.</p>
+
+ <p>Twisted does not assume that all data is stored in a
+ relational database, or even an efficient object database.
+ Currently, Twisted's configuration state is all stored in
+ memory at run-time, and the persistent parts of it are pickled
+ at one go. There are no plans to move the configuration objects
+ into a "real" database, as we feel it is easier to keep a naive
+ form of persistence for the default case and let
+ application-specific persistence mechanisms handle persistence.
+ Consequently, there is no object-relational mapping in Twisted;
+ <code>twisted.enterprise</code> is an interface to the
+ relational paradigm, not an object-oriented layer over it.</p>
+
+ <p>There are other things that Twisted will not do as well, but
+ these have been frequently discussed as possibilities for it.
+ The general rule of thumb is that if something will increase
+ the required installation overhead, then Twisted will probably
+ not do it. Optional additions that enhance integration with
+ external systems are always welcome: for example, database
+ drivers for Twisted or a CORBA IDL for PB objects.</p>
+
+ <h3>Future Directions</h3>
+
+ <p>Twisted is still a work in progress. The number of protocols
+ in the world is infinite for all practical purposes, and it
+ would be nice to have a central repository of event-based
+ protocol implementations. Better integration with frameworks
+ and operating systems is also a goal. Examples for integration
+ opportunities are automatic creation of installer for "tap"
+ files (for Red Hat Packager-based distributions, FreeBSD's
+ package management system or Microsoft Windows(tm) installers),
+ and integration with other event-dispatch mechanisms, such as
+ win32's native message dispatch.</p>
+
+ <p>A still-nascent feature of Twisted, which this paper only
+ touches briefly upon, is <code>twisted.enterprise</code>: it is
+ planned that Twisted will have first-class database support
+ some time in the near future. In particular, integration
+ between twisted.web and twisted.enterprise to allow developers
+ to have SQL conveniences that they are used to from other
+ frameworks.</p>
+
+ <p>Another direction that we hope Twisted will progress in is
+ standardization and porting of PB as a messaging protocol. Some
+ progress has already been made in that direction, with XEmacs
+ integration nearly ready for release as of this writing.</p>
+
+ <p>Tighter integration of protocols is also a future goal, such
+ an FTP server that can serve the same resources as a web
+ server, or a web server that allows users to change their POP3
+ password. While Twisted is already a very tightly integrated
+ framework, there is always room for more integration. Of
+ course, all this should be done in a flexible way, so the
+ end-user will choose which components to use -- and have those
+ components work well together.</p>
+
+ <h3>Conclusions</h3>
+
+ <p>As shown, Twisted provides a lot of functionality to the
+ Python network programmer, while trying to be in his way as
+ little as possible. Twisted gives good tools for both someone
+ trying to implement a new protocol, or someone trying to use an
+ existing protocol. Twisted allows developers to prototype and
+ develop object communication models with PB, without designing
+ a byte-level protocol. Twisted tries to have an easy way to
+ record useful deployment options, via the
+ <code>twisted.tap</code> and plugin mechanisms, while making it
+ easy to generate new forms of deployment. And last but not
+ least, even Twisted is written in a high-level language and
+ uses its dynamic facilities to give an easy API, it has
+ performance which is good enough for most situations -- for
+ example, the web server can easily saturate a T1 line serving
+ dynamic requests on low-end machines.</p>
+
+ <p>While still an active project, Twisted can already used for
+ production programs. Twisted can be downloaded from the main
+ Twisted site (http://www.twistedmatrix.com) where there is also
+ documentation for using and programming Twisted.</p>
+
+ <h3>Acknowledgements</h3>
+
+ <p>We wish to thank Sean Riley, Allen Short, Chris Armstrong,
+ Paul Swartz, J&uuml;rgen Hermann, Benjamin Bruheim, Travis B.
+ Hartwell, and Itamar Shtull-Trauring for being a part of the
+ Twisted development team with us.</p>
+
+ <p>Thanks also to Jason Asbahr, Tommi Virtanen, Gavin Cooper,
+ Erno Kuusela, Nick Moffit, Jeremy Fincher, Jerry Hebert, Keith
+ Zaback, Matthew Walker, and Dan Moniz, for providing insight,
+ commentary, bandwidth, crazy ideas, and bug-fixes (in no
+ particular order) to the Twisted team.</p>
+
+ <h3>References</h3>
+
+ <ol>
+ <li>The Twisted site, http://www.twistedmatrix.com</li>
+
+ <li>Douglas Schmidt, Michael Stal, Hans Rohnert and Frank
+ Buschmann, Pattern-Oriented Software Architecture, Volume 2,
+ Patterns for Concurrent and Networked Objects, John Wiley
+ &amp; Sons</li>
+
+ <li>Abhishek Chandra, David Mosberger, Scalability of Linux
+ Event-Dispatch Mechanisms, USENIX 2001,
+ http://lass.cs.umass.edu/~abhishek/papers/usenix01/paper.ps</li>
+
+ <li>Protocol specifications, http://www.rfc-editor.com</li>
+
+ <li>The Twisted Philosophical FAQ,
+ http://www.twistedmatrix.com/page.epy/twistedphil.html</li>
+
+ <li>Twisted Advocacy,
+ http://www.twistedmatrix.com/page.epy/whytwisted.html</li>
+
+ <li>Medusa, http://www.nightmare.com/medusa/index.html</li>
+
+ <li>Using Spreadable Web Servers,
+ http://www.twistedmatrix.com/users/jh.twistd/python/moin.cgi/TwistedWeb</li>
+
+ <li>Twisted.Spread implementations for other languages,
+ http://www.twistedmatrix.com/users/washort/</li>
+
+ <li>PHP: Hypertext Preprocessor, http://www.php.net/</li>
+
+ <li>The Z Object Publishing Environment,
+ http://www.zope.org/, http://zope.com/</li>
+ </ol>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/historic/stylesheet.css b/vendor/Twisted-10.0.0/doc/historic/stylesheet.css
new file mode 100644
index 0000000000..8235f6c961
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/stylesheet.css
@@ -0,0 +1,178 @@
+
+body
+{
+ margin-left: 2em;
+ margin-right: 2em;
+ border: 0px;
+ padding: 0px;
+ font-family: sans-serif;
+ }
+
+.done { color: #005500; background-color: #99ff99 }
+.notdone { color: #550000; background-color: #ff9999;}
+
+pre
+{
+ padding: 1em;
+ border: thin black solid;
+}
+
+.boxed
+{
+ padding: 1em;
+ border: thin black solid;
+}
+
+.shell
+{
+ background-color: #ffffdd;
+}
+
+.python
+{
+ background-color: #dddddd;
+}
+
+.htmlsource
+{
+ background-color: #dddddd;
+}
+
+.py-prototype
+{
+ background-color: #ddddff;
+}
+
+
+.python-interpreter
+{
+ background-color: #ddddff;
+}
+
+.doit
+{
+ border: thin blue dashed ;
+ background-color: #0ef
+}
+
+.py-src-comment
+{
+ color: #1111CC
+}
+
+.py-src-keyword
+{
+ color: #3333CC;
+ font-weight: bold;
+}
+
+.py-src-parameter
+{
+ color: #000066;
+ font-weight: bold;
+}
+
+.py-src-identifier
+{
+ color: #CC0000
+}
+
+.py-src-string
+{
+
+ color: #115511
+}
+
+.py-src-endmarker
+{
+ display: block; /* IE hack; prevents following line from being sucked into the py-listing box. */
+}
+
+.py-listing
+{
+ margin: 1ex;
+ border: thin solid black;
+ background-color: #eee;
+}
+
+.py-listing pre
+{
+ margin: 0px;
+ border: none;
+ border-bottom: thin solid black;
+}
+
+.py-listing .python
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.py-listing .htmlsource
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.py-caption
+{
+ text-align: center;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+}
+
+.py-filename
+{
+ font-style: italic;
+ }
+
+.manhole-output
+{
+ color: blue;
+}
+
+hr
+{
+ display: inline;
+ }
+
+ul
+{
+ padding: 0px;
+ margin: 0px;
+ margin-left: 1em;
+ padding-left: 1em;
+ border-left: 1em;
+ }
+
+li
+{
+ padding: 2px;
+ }
+
+dt
+{
+ font-weight: bold;
+ margin-left: 1ex;
+ }
+
+dd
+{
+ margin-bottom: 1em;
+ }
+
+div.note
+{
+ background-color: #FFFFCC;
+ margin-top: 1ex;
+ margin-left: 5%;
+ margin-right: 5%;
+ padding-top: 1ex;
+ padding-left: 5%;
+ padding-right: 5%;
+ border: thin black solid;
+}
diff --git a/vendor/Twisted-10.0.0/doc/historic/template-notoc.tpl b/vendor/Twisted-10.0.0/doc/historic/template-notoc.tpl
new file mode 100644
index 0000000000..5cc2efcd05
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/template-notoc.tpl
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+ <title></title>
+ <link type="text/css" rel="stylesheet" href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ <div class="body" />
+ </body>
+</html>
diff --git a/vendor/Twisted-10.0.0/doc/historic/template.tpl b/vendor/Twisted-10.0.0/doc/historic/template.tpl
new file mode 100644
index 0000000000..bb40833aba
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/template.tpl
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+<title></title>
+<link type="text/css" rel="stylesheet"
+href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title"></h1>
+ <div class="toc"></div>
+ <div class="body">
+
+ </div>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/historic/twisted-debian.html b/vendor/Twisted-10.0.0/doc/historic/twisted-debian.html
new file mode 100644
index 0000000000..a88abe5371
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/historic/twisted-debian.html
@@ -0,0 +1,96 @@
+<html><head><title>Twisted and Debian</title></head><body>
+
+<h1>Twisted and Debian</h1>
+
+<h3>Moshe Zadka</h3>
+<h4>&lt;moshez@debian.org&gt; &lt;moshez@twistedmatrix.com&gt;</h4>
+
+<h2>Twisted</h2>
+
+<p>
+Twisted is a Python networking framework. It is useful for development
+of both clients and servers, and strives to support as many externalities
+as possible -- from network protocols (with over a two dozen RFCs implemented)
+to GUI toolkits (supporting GTK+, Qt, wxWindows and Tk).
+</p>
+
+<h2>Debian</h2>
+
+<p>
+Debian is a free, stable and comprehensive operating system, based on GNU
+software and the Linux kernel. Debian supports eleven hardware archtecture
+and over 6000 programs. Debian, as a free, community-supported, operating
+system, has been used as a base for many other operating systems, including
+Lindows and Knoppix.
+</p>
+
+
+<h2>Using Twisted on a Debian System</h2>
+
+<p>
+The latest stable release of Debian, woody, comes with Twisted 0.15.5 built
+in. New versions of Twisted, which are tested on both stable and unstable,
+are always available from
+"deb http://twistedmatrix.com/users/moshez/apt". So, even those using
+stable Debian can use the latest Twisted releases, including the upcoming 1.0,
+without the overhead of adding unstable sources to their sources.list, dealing
+with apt-pinning or building the sources themselves.
+</p>
+
+<p>
+Of course, users of Debian unstable can get the releases directly from Debian
+-- the released packages, already having been tested on the main Twisted
+Debian machine, are usually uploaded to Debian unstable within hours of
+the official release.
+</p>
+
+<p>
+Twisted supports,
+as fully as possible, the Python versions available in Debian -- currently,
+2.1, 2.2 and pre-releases of 2.3. For those needing just a version of
+Twisted which works with the Debian default Python version, "python-twisted"
+is available. For low-impact on production servers, the documentation of
+Twisted (over half a megabyte) is packaged seperately. Twisted uses
+the Recommends: and Suggests: fields, to allow the Debian packaging tools
+to supply the information about which packages can be used to maximise
+the potential of Twisted.
+</p>
+
+<p>
+For those on the bleeding edge, or people who want to make sure their
+applications work flawlessly for the next version of Twisted, all release
+candidates are available from the apt source
+"deb http://twistedmatrix.com/users/moshez/snapshot". These are the release
+candidates the Twisted team uses itself to prepare for the next release --
+but third party developers interested in assuring compatibility are also
+welcome to use them.
+</p>
+
+<h2>Using Twisted's Debian Integration</h2>
+
+<p>
+For Twisted-based server application developers who want to deploy on
+Debian, Twisted supplies the <code>tap2deb</code> program. This program
+wraps a tap file (Twisted Application Pickle, a Twisted configuration)
+in a Debian archive, including correct installation and removal scripts
+and <code>init.d</code> scripts. For the more savvy Debian users, the
+<code>tap2deb</code> also generates the source package, allowing her
+to modify and polish things which automated software cannot detect
+(such as dependencies or relationships to virtual packages). In addition,
+the Twisted team itself intends to produce Debian packages for some common
+services, such as web servers and an inetd replacement. Those packages
+will enjoy the best of all worlds -- both the consistency which comes
+from being based on the <code>tap2deb</code> and the delicate manual
+tweaking of a Debian maintainer, insuring perfect integration with
+Debian.
+</p>
+
+<p>
+This things will insure you can run a fully functional Debian system
+which relies on Twisted for many of its core, and security sensitive,
+portions -- thus, eliminating many of the classical security holes
+(such as buffer overlows, uninitialized memory access and stack smashing),
+allowing you to sleep better at night.
+</p>
+
+</body></html>
diff --git a/vendor/Twisted-10.0.0/doc/lore/examples/example.html b/vendor/Twisted-10.0.0/doc/lore/examples/example.html
new file mode 100644
index 0000000000..b1c6684ded
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/examples/example.html
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<title>Your Title Here</title>
+</head>
+
+<body>
+<h1>Your Title Here</h1>
+
+<h2>Introduction</h2>
+
+<p>The introduction is an important part of your
+document<span class="footnote">though it should not be the most important
+part</span>.</p>
+
+<div class="note">
+<p>It is generally a <em>very</em> good to write other section except
+the introduction too.</p>
+</div>
+
+<p>
+You can use the following ways to write lists:
+<ul>
+<li>Unordered lists</li>
+<li>
+ <ol>
+ <li>ordered lists</li>
+ </ol>
+</li>
+<li><dl>
+ <dt>defintion lists</dt>
+ <dd>with definitions</dd>
+ </dl>
+</li>
+</ul>
+</p>
+
+<p>Shell commands look like <code class="shell">ls -l</code>. Python
+snippets look like <code class="python">print 1</code>.</p>
+
+<p>Longer python things should be in pre</p>
+
+<pre class="python">
+def foo():
+ pass
+</pre>
+
+<p>Or they can be outlined</p>
+
+<a href="rootscript.py" class="py-listing">rootscript.py</a>
+
+<p>Likewise, HTML can be outlined too:</p>
+
+<a href="example.html" class="py-listing">example.html</a>
+
+</body> </html>
diff --git a/vendor/Twisted-10.0.0/doc/lore/examples/index.html b/vendor/Twisted-10.0.0/doc/lore/examples/index.html
new file mode 100644
index 0000000000..659c011da2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/examples/index.html
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Lore examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Lore examples</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+ <ul>
+ <li><a href="example.html" shape="rect">example.html</a></li>
+ <li><a href="slides-template.tpl" shape="rect">slides-template.tpl</a></li>
+ </ul>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/lore/examples/slides-template.tpl b/vendor/Twisted-10.0.0/doc/lore/examples/slides-template.tpl
new file mode 100644
index 0000000000..9d7a420d05
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/examples/slides-template.tpl
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+ <title>Twisted Documentation: </title>
+ <link type="text/css" rel="stylesheet" href="stylesheet.css" />
+ </head>
+
+ <body bgcolor="white">
+ <div><span>Previous: <a class="previous"><span class="previous" /></a></span>
+ <br />
+ <span>Next: <a class="next"><span class="next" /></a></span></div>
+ <h1 class="title"></h1>
+ <div class="body">
+
+ </div>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/lore/howto/extend-lore.html b/vendor/Twisted-10.0.0/doc/lore/howto/extend-lore.html
new file mode 100644
index 0000000000..650f268523
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/howto/extend-lore.html
@@ -0,0 +1,425 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Extending the Lore Documentation System</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Extending the Lore Documentation System</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Inputs and Outputs</a></li><ul><li><a href="#auto2">Creating New Inputs</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Overview<a name="auto0"/></h2>
+
+<p>The <a href="lore.html" shape="rect">Lore Documentation System</a>, out of the box, is
+specialized for documenting Twisted. Its markup includes CSS classes for
+Python, HTML, filenames, and other Twisted-focused categories. But don't
+think this means Lore can't be used for other documentation tasks! Lore is
+designed to allow extensions, giving any Python programmer the ability to
+customize Lore for documenting almost anything.</p>
+
+<p>There are several reasons why you would want to extend Lore. You may want
+to attach file formats Lore does not understand to your documentation. You
+may want to create callouts that have special meanings to the reader, to give a
+memorable appearance to text such as, <q>WARNING: This software was written by
+ a frothing madman!</q> You may want to create color-coding for a different
+programming language, or you may find that Lore does not provide you with
+enough structure to mark your document up completely. All of these situations
+can be solved by creating an extension.</p>
+
+<h2>Inputs and Outputs<a name="auto1"/></h2>
+
+<p>Lore works by reading the HTML source of your document, and producing
+whatever output the user specifies on the command line. If the HTML document
+is well-formed XML that meets a certain minimum standard, Lore will be able to
+to produce some output. All Lore extensions will be written to redefine the
+<em>input</em>, and most will redefine the output in some way. The name of
+the default input is <q>lore</q>. When you write your extension, you will
+come up with a new name for your input, telling Lore what rules to use to
+process the file.</p>
+
+<p>Lore can produce XHTML, LaTeX, and DocBook document formats, which can be
+displayed directly if you have a user agent capable of viewing them, or
+processed into a third form such as PostScript or PDF. Another output is
+called <q>lint</q>, after the static-checking utility for C, and is used for
+the same reason: to statically check input files for problems. The
+<q>lint</q> output is just a stream of error messages, not a formatted
+document, but is important because it gives users the ability to validate
+their input before trying to process it. For the first example, the only
+output we will be concerned with is LaTeX.</p>
+
+<h3>Creating New Inputs<a name="auto2"/></h3>
+<p>Create a new input to tell Lore that your document is marked up differently
+from a vanilla Lore document. This gives you the power to define a new tag
+class, for example:</p>
+<pre xml:space="preserve">
+&lt;p&gt;The Frabjulon &lt;span class=&quot;productname&quot;&gt;Limpet 2000&lt;/span&gt;
+is the &lt;span class=&quot;marketinglie&quot;&gt;industry-leading&lt;/span&gt; aquatic
+mollusc counter, bar none.&lt;/p&gt;
+</pre>
+
+<p>The above HTML is an instance of a new input to Lore, which we will call
+MyHTML, to differentiate it from the <q>lore</q> input. We want it to have
+the following markup:</p>
+<ul>
+ <li>A <code>productname</code> class for the &lt;span&gt; tag, which
+ produces underlined text</li>
+ <li>A <code>marketinglie</code> class for &lt;span&gt; tag, which
+ produces larger type, bold text</li>
+</ul>
+<p>Note that I chose class names that are valid Python identifiers. You will
+see why shortly. To get these two effects in Lore's HTML output, all we have
+to do is create a cascading stylesheet (CSS), and use it in the Lore XHTML
+Template. However, we also want these effects to work in LaTeX, and we want
+the output of lint to produce no warnings when it sees lines with these 2
+classes. To make LaTeX and lint work, we start by creating a plugin.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">plugin</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IPlugin</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span>.<span class="py-src-variable">scripts</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IProcessor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyHTML</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IPlugin</span>, <span class="py-src-variable">IProcessor</span>)
+
+ <span class="py-src-variable">name</span> = <span class="py-src-string">&quot;myhtml&quot;</span>
+ <span class="py-src-variable">moduleName</span> = <span class="py-src-string">&quot;myhtml.factory&quot;</span>
+</pre><div class="caption">
+ Listing 1: The Plugin File - <a href="listings/lore/a_lore_plugin.py"><span class="filename">listings/lore/a_lore_plugin.py</span></a></div></div>
+
+ <p>Create this file in a <code class="shell">twisted/plugins/</code>
+ directory (<em>not</em> a package) which is located in a directory in the
+ Python module search path. See the <a href="../../core/howto/plugin.html" shape="rect">Twisted
+ plugin howto</a> for more details on plugins.</p>
+
+ <p>Users of your extension will pass the value of your plugin's <code class="python">name</code> attribute to lore with the <code class="shell">--input</code> parameter on the command line to select it. For
+ example, to select the plugin defined above, a user would pass <code class="shell">--input myhtml</code>. The <code class="python">moduleName</code> attribute tells Lore where to find the code
+ implementing the plugin. In particular, this module should have a <code class="python">factory</code> attribute which defines a <code class="python">generator_</code>-prefixed method for each output format it
+ supports. Next we'll look at this module.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">default</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myhtml</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">spitters</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyProcessingFunctionFactory</span>(<span class="py-src-parameter">default</span>.<span class="py-src-parameter">ProcessingFunctionFactory</span>):
+ <span class="py-src-variable">latexSpitters</span>={<span class="py-src-variable">None</span>: <span class="py-src-variable">spitters</span>.<span class="py-src-variable">MyLatexSpitter</span>,
+ }
+
+<span class="py-src-comment"># initialize the global variable factory with an instance of your new factory</span>
+<span class="py-src-variable">factory</span>=<span class="py-src-variable">MyProcessingFunctionFactory</span>()
+</pre><div class="caption">Listing 2: The Input
+ Factory - <a href="listings/lore/factory.py-1"><span class="filename">listings/lore/factory.py-1</span></a></div></div>
+
+<p>In Listing 2, we create a subclass of ProcessingFunctionFactory. This
+class provides a hook for you, a class variable named
+<code>latexSpitters</code>. This variable tells Lore
+what new class will be generating LaTeX from your input format. We redefine
+<code>latexSpitters</code> to <code>MyLatexSpitter</code> in the subclass
+because this
+class knows what to do with the new input we have already defined. Last, you
+must define the module-level variable <code class="py-src-identifier">factory</code>. It should be an instance with
+ the same
+interface as <code class="py-src-identifier">ProcessingFunctionFactory</code>
+(e.g. an instance of a subclass, in this case, <code class="py-src-identifier">MyProcessingFunctionFactory</code>).</p>
+
+<p>Now let's actually write some code to generate the LaTeX. Doing this
+requires at least a familiarity with the LaTeX language. Search Google for
+<q>latex tutorial</q> and you will find any number of useful LaTeX
+resources.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">latex</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span>.<span class="py-src-variable">latex</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">processFile</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyLatexSpitter</span>(<span class="py-src-parameter">latex</span>.<span class="py-src-parameter">LatexSpitter</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">visitNode_span_productname</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">node</span>):
+ <span class="py-src-comment"># start an underline section in LaTeX</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\underline{'</span>)
+ <span class="py-src-comment"># process the node and its children</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">visitNodeDefault</span>(<span class="py-src-variable">node</span>)
+ <span class="py-src-comment"># end the underline block</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'}'</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">visitNode_span_marketinglie</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">node</span>):
+ <span class="py-src-comment"># this example turns on more than one LaTeX effect at once</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\begin{bf}\\begin{Large}'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">visitNodeDefault</span>(<span class="py-src-variable">node</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\end{Large}\\end{bf}'</span>)
+</pre><div class="caption">Listing 3:
+ spitters.py - <a href="listings/lore/spitters.py-1"><span class="filename">listings/lore/spitters.py-1</span></a></div></div>
+
+<p>The method <code>visitNode_span_productname</code> is
+our handler for &lt;span&gt; tags with the <code>class=&quot;productname&quot;</code>
+identifier. Lore knows to try methods <code>visitNode_span_*</code> and
+<code>visitNode_div_*</code> whenever it encounters a new
+class in one of these tags. This is why the class names have to be valid
+Python identifiers.</p>
+
+<p>Now let's see what Lore does with these new classes with the following
+input file:</p>
+
+<div class="html-listing"><pre class="htmlsource">
+&lt;html&gt;
+ &lt;head&gt;
+ &lt;title&gt;My First Example&lt;/title&gt;
+ &lt;/head&gt;
+ &lt;body&gt;
+ &lt;h1&gt;My First Example&lt;/h1&gt;
+ &lt;p&gt;The Frabjulon &lt;span class=&quot;productname&quot;&gt;Limpet 2000&lt;/span&gt;
+ is the &lt;span class=&quot;marketinglie&quot;&gt;industry-leading&lt;/span&gt; aquatic
+ mollusc counter, bar none.&lt;/p&gt;
+ &lt;/body&gt;
+&lt;/html&gt;
+
+</pre><div class="caption">Listing 4:
+ 1st_example.html - <a href="listings/lore/1st_example.html"><span class="filename">listings/lore/1st_example.html</span></a></div></div>
+
+<p>First, verify that your package is laid out correctly. Your directory
+structure should look like this:</p>
+
+<pre xml:space="preserve">
+1st_example.html
+myhtml/
+ __init__.py
+ factory.py
+ spitters.py
+twisted/plugins/
+ a_lore_plugin.py
+</pre>
+
+<p>In the parent directory of myhtml (that is, <code>myhtml/..</code>), run
+lore and pdflatex on the input:</p>
+
+<pre class="shell" xml:space="preserve">
+$ lore --input myhtml --output latex 1st_example.html
+[########################################] (*Done*)
+
+$ pdflatex 1st_example.tex
+[ . . . latex output omitted for brevity . . . ]
+Output written on 1st_example.pdf (1 page, 22260 bytes).
+Transcript written on 1st_example.log.
+</pre>
+
+<p>And here's what the rendered PDF looks like:</p>
+
+<p><img src="../img/myhtml-output.png"/></p>
+
+<p>What happens when we run lore on this file using the lint output?</p>
+
+<pre class="shell" xml:space="preserve">
+$ lore --input myhtml --output lint 1st_example.html
+1st_example.html:7:47: unknown class productname
+1st_example.html:8:38: unknown class marketinglie
+[########################################] (*Done*)
+</pre>
+
+<p>Lint reports these classes as errors, even though our spitter knows how to
+process them. To fix this problem, we must add to <code class="py-filename">factory.py</code>.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">default</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myhtml</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">spitters</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyProcessingFunctionFactory</span>(<span class="py-src-parameter">default</span>.<span class="py-src-parameter">ProcessingFunctionFactory</span>):
+ <span class="py-src-variable">latexSpitters</span>={<span class="py-src-variable">None</span>: <span class="py-src-variable">spitters</span>.<span class="py-src-variable">MyLatexSpitter</span>,
+ }
+
+ <span class="py-src-comment"># redefine getLintChecker to validate our classes</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getLintChecker</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-comment"># use the default checker from parent</span>
+ <span class="py-src-variable">checker</span> = <span class="py-src-variable">lint</span>.<span class="py-src-variable">getDefaultChecker</span>()
+ <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span> = <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>.<span class="py-src-variable">copy</span>()
+ <span class="py-src-variable">oldSpan</span> = <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>[<span class="py-src-string">'span'</span>]
+ <span class="py-src-variable">checkfunc</span>=<span class="py-src-keyword">lambda</span> <span class="py-src-variable">cl</span>: <span class="py-src-variable">oldSpan</span>(<span class="py-src-variable">cl</span>) <span class="py-src-keyword">or</span> <span class="py-src-variable">cl</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">'marketinglie'</span>,
+ <span class="py-src-string">'productname'</span>]
+ <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>[<span class="py-src-string">'span'</span>] = <span class="py-src-variable">checkfunc</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">checker</span>
+
+<span class="py-src-comment"># initialize the global variable factory with an instance of your new factory</span>
+<span class="py-src-variable">factory</span>=<span class="py-src-variable">MyProcessingFunctionFactory</span>()
+</pre><div class="caption">Listing 5: Input
+ Factory with Lint Support - <a href="listings/lore/factory.py-2"><span class="filename">listings/lore/factory.py-2</span></a></div></div>
+
+<p>The method <code class="py-src-identifier">getLintChecker</code> is called
+by Lore to produce the lint output. This modification adds our classes to the
+list of classes lint ignores:</p>
+
+<pre class="shell" xml:space="preserve">
+$ lore --input myhtml --output lint 1st_example.html
+[########################################] (*Done*)
+$ # Hooray!
+</pre>
+
+<p>Finally, there are two other sub-outputs of LaTeX, for a total of three
+different ways that Lore can produce LaTeX: the default way, which produces as
+output an entire, self-contained LaTeX document; with <code class="shell">--config section</code> on the command line, which produces a
+LaTeX \section; and with <code class="shell">--config chapter</code>, which
+produces a LaTeX \chapter. To support these options as well, the solution is
+to make the new spitter class a mixin, and use it with the <code class="py-src-identifier">SectionLatexSpitter</code> and <code class="py-src-identifier">ChapterLatexSpitter</code>, respectively.
+Comments in the following listings tell you everything you need to know about
+making these simple changes:</p>
+
+<ul>
+ <li><div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">default</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">myhtml</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">spitters</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyProcessingFunctionFactory</span>(<span class="py-src-parameter">default</span>.<span class="py-src-parameter">ProcessingFunctionFactory</span>):
+ <span class="py-src-comment"># 1. add the keys &quot;chapter&quot; and &quot;section&quot; to latexSpitters to handle the</span>
+ <span class="py-src-comment"># --config chapter and --config section options</span>
+ <span class="py-src-variable">latexSpitters</span>={<span class="py-src-variable">None</span>: <span class="py-src-variable">spitters</span>.<span class="py-src-variable">MyLatexSpitter</span>,
+ <span class="py-src-string">&quot;section&quot;</span>: <span class="py-src-variable">spitters</span>.<span class="py-src-variable">MySectionLatexSpitter</span>,
+ <span class="py-src-string">&quot;chapter&quot;</span>: <span class="py-src-variable">spitters</span>.<span class="py-src-variable">MyChapterLatexSpitter</span>,
+ }
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getLintChecker</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">checker</span> = <span class="py-src-variable">lint</span>.<span class="py-src-variable">getDefaultChecker</span>()
+ <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span> = <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>.<span class="py-src-variable">copy</span>()
+ <span class="py-src-variable">oldSpan</span> = <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>[<span class="py-src-string">'span'</span>]
+ <span class="py-src-variable">checkfunc</span>=<span class="py-src-keyword">lambda</span> <span class="py-src-variable">cl</span>: <span class="py-src-variable">oldSpan</span>(<span class="py-src-variable">cl</span>) <span class="py-src-keyword">or</span> <span class="py-src-variable">cl</span> <span class="py-src-keyword">in</span> [<span class="py-src-string">'marketinglie'</span>,
+ <span class="py-src-string">'productname'</span>]
+ <span class="py-src-variable">checker</span>.<span class="py-src-variable">allowedClasses</span>[<span class="py-src-string">'span'</span>] = <span class="py-src-variable">checkfunc</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">checker</span>
+
+<span class="py-src-variable">factory</span>=<span class="py-src-variable">MyProcessingFunctionFactory</span>()
+</pre><div class="caption">factory.py - <a href="listings/lore/factory.py-3"><span class="filename">listings/lore/factory.py-3</span></a></div></div></li>
+ <li><div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">latex</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">lore</span>.<span class="py-src-variable">latex</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">processFile</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>
+
+<span class="py-src-comment"># 2. Create a new mixin that does what the old MyLatexSpitter used to do:</span>
+<span class="py-src-comment"># process the new classes we defined</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MySpitterMixin</span>:
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">visitNode_span_productname</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">node</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\underline{'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">visitNodeDefault</span>(<span class="py-src-variable">node</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'}'</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">visitNode_span_marketinglie</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">node</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\begin{bf}\\begin{Large}'</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">visitNodeDefault</span>(<span class="py-src-variable">node</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">writer</span>(<span class="py-src-string">'\\end{Large}\\end{bf}'</span>)
+
+<span class="py-src-comment"># 3. inherit from the mixin class for each of the three sub-spitters</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyLatexSpitter</span>(<span class="py-src-parameter">MySpitterMixin</span>, <span class="py-src-parameter">latex</span>.<span class="py-src-parameter">LatexSpitter</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MySectionLatexSpitter</span>(<span class="py-src-parameter">MySpitterMixin</span>, <span class="py-src-parameter">latex</span>.<span class="py-src-parameter">SectionLatexSpitter</span>):
+ <span class="py-src-keyword">pass</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyChapterLatexSpitter</span>(<span class="py-src-parameter">MySpitterMixin</span>, <span class="py-src-parameter">latex</span>.<span class="py-src-parameter">ChapterLatexSpitter</span>):
+ <span class="py-src-keyword">pass</span>
+</pre><div class="caption">spitters.py - <a href="listings/lore/spitters.py-2"><span class="filename">listings/lore/spitters.py-2</span></a></div></div></li>
+</ul>
+
+
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/lore/howto/index.html b/vendor/Twisted-10.0.0/doc/lore/howto/index.html
new file mode 100644
index 0000000000..9bd3fd53e4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/howto/index.html
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Lore Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Lore Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+ <li><a href="lore.html" shape="rect">Lore documentation system</a></li>
+ <li><a href="extend-lore.html" shape="rect">Extending Lore</a></li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/1st_example.html b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/1st_example.html
new file mode 100644
index 0000000000..11ff82cf51
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/1st_example.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <title>My First Example</title>
+ </head>
+ <body>
+ <h1>My First Example</h1>
+ <p>The Frabjulon <span class="productname">Limpet 2000</span>
+ is the <span class="marketinglie">industry-leading</span> aquatic
+ mollusc counter, bar none.</p>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/a_lore_plugin.py b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/a_lore_plugin.py
new file mode 100644
index 0000000000..c6ac1b4d8b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/a_lore_plugin.py
@@ -0,0 +1,11 @@
+
+from zope.interface import implements
+
+from twisted.plugin import IPlugin
+from twisted.lore.scripts.lore import IProcessor
+
+class MyHTML(object):
+ implements(IPlugin, IProcessor)
+
+ name = "myhtml"
+ moduleName = "myhtml.factory"
diff --git a/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-1 b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-1
new file mode 100644
index 0000000000..0030955db5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-1
@@ -0,0 +1,9 @@
+from twisted.lore import default
+from myhtml import spitters
+
+class MyProcessingFunctionFactory(default.ProcessingFunctionFactory):
+ latexSpitters={None: spitters.MyLatexSpitter,
+ }
+
+# initialize the global variable factory with an instance of your new factory
+factory=MyProcessingFunctionFactory()
diff --git a/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-2 b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-2
new file mode 100644
index 0000000000..c5e0319567
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-2
@@ -0,0 +1,20 @@
+from twisted.lore import default
+from myhtml import spitters
+
+class MyProcessingFunctionFactory(default.ProcessingFunctionFactory):
+ latexSpitters={None: spitters.MyLatexSpitter,
+ }
+
+ # redefine getLintChecker to validate our classes
+ def getLintChecker(self):
+ # use the default checker from parent
+ checker = lint.getDefaultChecker()
+ checker.allowedClasses = checker.allowedClasses.copy()
+ oldSpan = checker.allowedClasses['span']
+ checkfunc=lambda cl: oldSpan(cl) or cl in ['marketinglie',
+ 'productname']
+ checker.allowedClasses['span'] = checkfunc
+ return checker
+
+# initialize the global variable factory with an instance of your new factory
+factory=MyProcessingFunctionFactory()
diff --git a/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-3 b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-3
new file mode 100644
index 0000000000..85e0374f6a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/factory.py-3
@@ -0,0 +1,21 @@
+from twisted.lore import default
+from myhtml import spitters
+
+class MyProcessingFunctionFactory(default.ProcessingFunctionFactory):
+ # 1. add the keys "chapter" and "section" to latexSpitters to handle the
+ # --config chapter and --config section options
+ latexSpitters={None: spitters.MyLatexSpitter,
+ "section": spitters.MySectionLatexSpitter,
+ "chapter": spitters.MyChapterLatexSpitter,
+ }
+
+ def getLintChecker(self):
+ checker = lint.getDefaultChecker()
+ checker.allowedClasses = checker.allowedClasses.copy()
+ oldSpan = checker.allowedClasses['span']
+ checkfunc=lambda cl: oldSpan(cl) or cl in ['marketinglie',
+ 'productname']
+ checker.allowedClasses['span'] = checkfunc
+ return checker
+
+factory=MyProcessingFunctionFactory()
diff --git a/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/spitters.py-1 b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/spitters.py-1
new file mode 100644
index 0000000000..b17a0be52d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/spitters.py-1
@@ -0,0 +1,18 @@
+from twisted.lore import latex
+from twisted.lore.latex import processFile
+import os.path
+
+class MyLatexSpitter(latex.LatexSpitter):
+ def visitNode_span_productname(self, node):
+ # start an underline section in LaTeX
+ self.writer('\\underline{')
+ # process the node and its children
+ self.visitNodeDefault(node)
+ # end the underline block
+ self.writer('}')
+
+ def visitNode_span_marketinglie(self, node):
+ # this example turns on more than one LaTeX effect at once
+ self.writer('\\begin{bf}\\begin{Large}')
+ self.visitNodeDefault(node)
+ self.writer('\\end{Large}\\end{bf}')
diff --git a/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/spitters.py-2 b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/spitters.py-2
new file mode 100644
index 0000000000..7108d6ba72
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/howto/listings/lore/spitters.py-2
@@ -0,0 +1,26 @@
+from twisted.lore import latex
+from twisted.lore.latex import processFile
+import os.path
+
+# 2. Create a new mixin that does what the old MyLatexSpitter used to do:
+# process the new classes we defined
+class MySpitterMixin:
+ def visitNode_span_productname(self, node):
+ self.writer('\\underline{')
+ self.visitNodeDefault(node)
+ self.writer('}')
+
+ def visitNode_span_marketinglie(self, node):
+ self.writer('\\begin{bf}\\begin{Large}')
+ self.visitNodeDefault(node)
+ self.writer('\\end{Large}\\end{bf}')
+
+# 3. inherit from the mixin class for each of the three sub-spitters
+class MyLatexSpitter(MySpitterMixin, latex.LatexSpitter):
+ pass
+
+class MySectionLatexSpitter(MySpitterMixin, latex.SectionLatexSpitter):
+ pass
+
+class MyChapterLatexSpitter(MySpitterMixin, latex.ChapterLatexSpitter):
+ pass
diff --git a/vendor/Twisted-10.0.0/doc/lore/howto/lore.html b/vendor/Twisted-10.0.0/doc/lore/howto/lore.html
new file mode 100644
index 0000000000..8bcd4cc576
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/howto/lore.html
@@ -0,0 +1,366 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Using the Lore Documentation System</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Using the Lore Documentation System</h1>
+ <div class="toc"><ol><li><a href="#auto0">Writing Lore Documents</a></li><ul><li><a href="#auto1">Overview</a></li><li><a href="#auto2">Elements and Their Uses</a></li></ul><li><a href="#auto3">Writing Lore XHTML Templates</a></li><li><a href="#auto4">Using Lore to Generate HTML</a></li><li><a href="#auto5">Using Lore to Generate LaTex</a></li><ul><li><a href="#auto6">Articles</a></li><li><a href="#auto7">Books</a></li></ul><li><a href="#auto8">Using Lore to Generate Slides</a></li><ul><li><a href="#auto9">Magic Point Output</a></li><li><a href="#auto10">LaTeX Output</a></li></ul><li><a href="#auto11">Linting</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Writing Lore Documents<a name="auto0"/></h2>
+
+<h3>Overview<a name="auto1"/></h3>
+
+<p>Lore documents are a special subset of XHTML documents. They use specific
+subset of XHTML, together with custom classes, to allow a wide variety of
+document elements, including some Python-specific ones. Lore documents, in
+particular, are well-formed XML documents. XML can be written using a wide
+variety of tools: from run of the mill editors such as vi, through editors
+with XML help like EMACS and ending with XML specific tools like (need name
+of XML editor here). Here, we will not cover the specifics of writing XML
+documents, except for a very broad overview.</p>
+
+<p>XML documents contain elements, which are delimited by an opening tag
+which looks like <code>&lt;tag-name attribute=&quot;value&quot;&gt;</code> and ends with
+a closing tag, which looks like <code>&lt;/tag-name&gt;</code>. If an
+elements happen to contain nothing, it can be shortened to
+<code>&lt;tag-name /&gt;</code>. Elements can contain other elements, or
+text. Text can contain any characters except &lt;, &gt; and &amp;. These
+characters are rendered by &amp;lt;, &amp;gt; and &amp;amp;, respectively.</p>
+
+<p>A Lore document is a single <code>html</code> element. Inside this element,
+there are exactly two top-level elements: <code>head</code> and
+<code>body</code>. The <code>head</code> element must contain exactly one
+element: <code>title</code>, containing the title of the document.
+Most of the document will be contained in the <code>body</code> element.
+The <code>body</code> element must start with an <code>h1</code> (top-level
+header) element, which contains the exact same content as the
+<code>title</code> element.</p>
+
+<p>Thus, a fairly minimal Lore document might look like:</p>
+
+<pre xml:space="preserve">
+&lt;html&gt;
+&lt;head&gt;&lt;title&gt;Title&lt;/title&gt;&lt;/head&gt;
+&lt;body&gt;&lt;h1&gt;Title&lt;/h1&gt;&lt;/body&gt;
+&lt;/html&gt;
+</pre>
+
+<h3>Elements and Their Uses<a name="auto2"/></h3>
+
+<table border="2" cellpadding="7" cellspacing="0">
+<tr>
+<th colspan="1" rowspan="1">Element</th>
+<th colspan="1" rowspan="1">Description</th>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>p</code></td>
+<td colspan="1" rowspan="1">The paragraph element. Most of the document should be inside paragraphs.
+</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>span</code></td>
+<td colspan="1" rowspan="1">The span element is an element which has no meaning -- unless it has a
+special <code>class</code> attributes. The following classes have the stated
+meanings:
+<dl>
+<dt><code>footnote</code></dt>
+<dd>a small comment which should not be inside the main text-flow.</dd>
+<dt><code>manhole-output</code></dt>
+<dd>This signifies, within a manhole transcript, that the enclosed text is
+ the output and not something the user has to input.</dd>
+<dt><code>index</code></dt>
+<dd>This should be an <em>empty</em> element, with an attribute
+ <code>value</code>. That attribute should be an index term, in the
+ format of <code>generic!specific!more specific</code>. Usually,
+ you will only have one level, in which case <code>value=&quot;term&quot;</code>
+ works.</dd>
+</dl></td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>div</code></td>
+<td colspan="1" rowspan="1">The div element is equivalent to a span, except it always appears outside
+paragraphs. The following classes have the given meanings:
+<dl>
+<dt><code>note</code></dt>
+<dd>A short note which is not necessary for the understanding of the text.</dd>
+<dt><code>doit</code></dt>
+<dd>An indication that the discussed feature is not complete or implemented
+ yet.</dd>
+<dt><code>boxed</code></dt>
+<dd>An indication that the text should be clearly separated from its
+ surroundings.</dd>
+</dl></td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>a</code></td>
+<td colspan="1" rowspan="1">This element can have several meanings, depending on the attributes:
+<dl>
+<dt><code>name</code> attribute</dt>
+<dd>Add a label to the current position, which might be used in this document
+ or other documents to refer to.</dd>
+<dt><code>href=URL</code></dt>
+<dd>Refer to some WWW resource.</dd>
+<dt><code>href=relative-path</code>, <code>href=relative-path#label</code> or
+ <code>href=#label</code></dt>
+<dd>Refer to a position in a Lore resource. By default, relative links to
+ <code>.xhtml</code> files are changed to point to a <code>.html</code> file.
+ If you need a link to a local non-Lore .xhtml file, use
+ <code>class=absolute</code> to make Lore treat it as an absolute link.</dd>
+<dt><code>href=relative-path</code> with <code>class=py-listing</code> or
+ <code>class=html-listing</code></dt>
+<dd>Indicate the given resource is a part of the text flow, and should be
+ inlined (and if possible, syntax highlighted).</dd>
+</dl></td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>ol</code>, <code>ul</code></td>
+<td colspan="1" rowspan="1">A list. It can be enumerated or bulleted. Inside a list, the element
+<code>li</code> (for a list element) is valid.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>h2</code>, <code>h3</code></td>
+<td colspan="1" rowspan="1">Second- and third-level section headings.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>code</code></td>
+<td colspan="1" rowspan="1">A string which has meaning to the computer. There are many possible
+classes:
+<dl>
+<dt><code>API</code></dt>
+<dd>A class, function or a module. It does not have to be a fully qualified
+ name -- but if it isn't, a <code>base</code> attribute is necessary.
+ <br/>Example:
+ <code>&lt;code class=&quot;API&quot; base=&quot;urllib&quot;&gt;urlencode&lt;code&gt;</code>.</dd>
+<dt><code>shell</code></dt>
+<dd>Shell (usually Bourne) code.</dd>
+<dt><code>python</code></dt>
+<dd>Python code.</dd>
+<dt><code>py-prototype</code></dt>
+<dd>Function prototype.</dd>
+<dt><code>py-filename</code></dt>
+<dd>Python file.</dd>
+<dt><code>py-src-string</code></dt>
+<dd>Python string.</dd>
+<dt><code>py-signature</code></dt>
+<dd>Function signature.</dd>
+<dt><code>py-src-parameter</code></dt>
+<dd>Parameter.</dd>
+<dt><code>py-src-identifier</code></dt>
+<dd>Identifier.</dd>
+<dt><code>py-src-keyword</code></dt>
+<dd>Keyword.</dd>
+</dl></td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>pre</code></td>
+<td colspan="1" rowspan="1">Preformatted text, usually for file listings. It can be used with the
+<code>python</code> class to indicate Python syntax coloring. Other possible
+classes are <code>shell</code> (to indicate a shell-transcript) or
+<code>python-interpreter</code> (to indicate an interactive interpreter
+transcript).</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>img</code></td>
+<td colspan="1" rowspan="1">Insert the image indicated by the <code>src</code> attribute.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>q</code></td>
+<td colspan="1" rowspan="1">The quote signs (<code>&quot;</code>) are not recommended
+except in preformatted or code environment. Instead, quote by using the
+<code>q</code> element which allows nested quotes and properly distinguishes
+opening quote from closing quote.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>em</code>, <code>strong</code></td>
+<td colspan="1" rowspan="1">Emphasise (or strongly emphasise) text.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>table</code></td>
+<td colspan="1" rowspan="1">Tabular data. Inside a table, use the <code>tr</code>
+element for each rows, and inside it use either <code>td</code> for a regular
+table cell or <code>th</code> for a table header (column or row).</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>blockquote</code></td>
+<td colspan="1" rowspan="1">A long quote which should be properly seperated from the main text.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>cite</code></td>
+<td colspan="1" rowspan="1">Cite a resource.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>sub</code>, <code>sup</code></td>
+<td colspan="1" rowspan="1">Subscripts and superscripts.</td>
+</tr>
+
+<tr>
+<td colspan="1" rowspan="1"><code>link</code></td>
+<td colspan="1" rowspan="1">Currently, the only <code>link</code> elements supported
+are for for indicating authorship. <code>&lt;link rel=&quot;author&quot;
+href=&quot;author-address@examples.com&quot; title=&quot;Author Name&quot; /&gt;</code>
+should be used to indicate authorship. Multiple instances
+are allowed, and indicate shared authorship.</td>
+</tr>
+
+</table>
+
+<h2>Writing Lore XHTML Templates<a name="auto3"/></h2>
+
+<p>One of Lore's output formats is XHTML. Lore itself is very markup-light,
+but the output XHTML is much more markup intensive. Part of the auto-generated
+markup is directed by a special template.</p>
+
+<p>The output of Lore is inserted into template in the following way:</p>
+
+<ul>
+<li>The title is appended into each element with class <code>title</code>.</li>
+<li>The body is inserted into the first element that has class
+ <code>body</code>.</li>
+<li>The table of contents is inserted into the first element that has class
+ <code>toc</code>.</li>
+</ul>
+
+<p>In particular, most of the header is not tampered with -- so it is
+easy to indicate a CSS stylesheet in the template.</p>
+
+<h2>Using Lore to Generate HTML<a name="auto4"/></h2>
+
+<p>After having written a template, the easiest way to build HTML from the Lore
+document is by:</p>
+
+<pre class="shell" xml:space="preserve">
+% lore --config template=mytemplate.tpl mydocument.xhtml
+</pre>
+
+<p>This will create a file called <code class="shell">mydocument.html</code>.
+</p>
+
+<p>For example, to generate the HTML version of the Twisted docs from a SVN
+checkout, do:</p>
+
+<pre class="shell" xml:space="preserve">
+% lore --config template=doc/howto/template.tpl doc/howto/*.xhtml
+</pre>
+
+<p>
+In order to generate files with a different extension, use the <code class="shell">--config</code> commandline flag to tell the HTML output plugin to
+use a different extension:
+</p>
+<pre class="shell" xml:space="preserve">
+% lore --config ext=.html doc/howto/*.xhtml
+</pre>
+<h2>Using Lore to Generate LaTex<a name="auto5"/></h2>
+
+<h3>Articles<a name="auto6"/></h3>
+
+<pre class="shell" xml:space="preserve">
+% lore --output latex mydocument.xhtml
+</pre>
+
+<h3>Books<a name="auto7"/></h3>
+
+<p>Have a Lore file for each section. Then, have a LaTeX file which inputs
+all the given LaTeX files. Generate all the LaTeX files by using</p>
+
+<pre class="shell" xml:space="preserve">
+% lore --output latex --config section *.xhtml
+</pre>
+
+<p>in the relevant directory.</p>
+
+<h2>Using Lore to Generate Slides<a name="auto8"/></h2>
+
+<p>Lore can also be used to generate slides for presentations. The start
+of a new slide is indicated by use of an h2 tag, with the content
+between the opening and closing tags the title of the slide. Slides
+are generated by</p>
+
+<pre class="shell" xml:space="preserve">
+% lore --input lore-slides myslides.xhtml
+</pre>
+
+<p>This, by default, will produce HTML output with one HTML file for
+each slide. For our example, the files would be named
+myslides-&lt;number&gt;.html, where number is the slide number,
+starting with 0 for the title slide. Lore will look for a template
+file, either indicated by the <code>--config
+template=mytemplate.tpl</code> or the default template.tpl in the
+current directory. An example slide template is found in
+<code>doc/examples/slides-template.tpl</code></p>
+
+<p>The slides module currently supports three major output types:
+HTML, Magic Point, and LaTeX. The options for the latter two will be
+covered individually.</p>
+
+<h3>Magic Point Output<a name="auto9"/></h3>
+
+<p>Lore supports outputting to the Magic Point file format.
+Magicpoint is a presentation program for X, which can be installed on
+Debian by <code>apt-get install mgp</code> or by visiting <a href="http://member.wide.ad.jp/wg/mgp/" shape="rect">the Magic Point homepage</a>
+otherwise. A template file is required, <code>template.mgp</code> is
+shipped in the <code>twisted/lore</code> directory. Magic Point
+slides are generated by </p>
+
+<pre class="shell" xml:space="preserve">
+% lore --input lore-slides --output mgp \
+ --config template=~/Twisted/twisted/lore/template.mgp \
+ myslides.xhtml
+</pre>
+
+<p>That will produce <code>myslides.mgp</code>.</p>
+
+<h3>LaTeX Output<a name="auto10"/></h3>
+
+<p>Lore can also produce slides in LaTeX format. It supports three
+main styles: one slide per page, two per page, and Prosper format,
+with the <code>--config</code> parameters being <code>page</code>,
+<code>twopage</code>, and <code>prosper</code> respectively. Prosper
+is a LaTeX class for creating slides, which can be installed on Debian
+by <code>apt-get install prosper</code> or by visiting <a href="http://sourceforge.net/projects/prosper/" shape="rect">the Prosper SourceForge page</a>.
+LaTeX format slides (using the Prosper option, for example) are
+generated by</p>
+
+<pre class="shell" xml:space="preserve">
+% lore --input lore-slides --output latex \
+ --config prosper myslides.xhtml
+</pre>
+
+<p> This will generate <code>myslides.tex</code> file that can be processed
+with <code>latex</code> or <code>pdftex</code> or the appropriate
+LaTeX processing command.</p>
+
+<h2>Linting<a name="auto11"/></h2>
+
+<pre xml:space="preserve">
+% lore --output lint mydocument.xhtml
+</pre>
+
+<p>This will generate compiler-style (file:line:column:message) warnings.
+It is possible to integrate these warnings into a smart editor such as
+EMACS, but it has not been done yet.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/lore/img/myhtml-output.png b/vendor/Twisted-10.0.0/doc/lore/img/myhtml-output.png
new file mode 100644
index 0000000000..4a00fbfc3c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/img/myhtml-output.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/lore/index.html b/vendor/Twisted-10.0.0/doc/lore/index.html
new file mode 100644
index 0000000000..36531c8d86
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Lore Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Lore Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Lore to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Lore</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/lore/man/lore-man.html b/vendor/Twisted-10.0.0/doc/lore/man/lore-man.html
new file mode 100644
index 0000000000..c3c2317bd6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/man/lore-man.html
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: GENERATELORE.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">GENERATELORE.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">DESCRIPTION</a></li><li><a href="#auto4">AUTHOR</a></li><li><a href="#auto5">REPORTING BUGS</a></li><li><a href="#auto6">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>lore - convert documentations formats
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>lore</strong> [-l <em>linkrel</em>] [-d <em>docsdir</em>] [-i <em>input</em>] [-o <em>output</em>] [--config attribute[=value] [...]] [-p] [file [...]]</p>
+
+<p><strong>lore</strong> --help</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>The <strong>--help</strong> prints out a usage message to standard output.
+<dl><dt><strong>-p</strong>, <strong>--plain</strong>
+</dt><dd>Use non-flashy progress bar - one file per line.
+</dd>
+
+<dt><strong>-n</strong>, <strong>--null</strong>
+</dt><dd>Do not report progress at all.
+</dd>
+
+<dt><strong>-N</strong>, <strong>--number</strong>
+</dt><dd>Add chapter/section numbers to section headings.
+</dd>
+
+<dt><em>-l</em>, <em>--linkrel</em>
+</dt><dd>Where non-document links should be relative to.
+</dd>
+
+<dt><em>-d</em>, <em>--docsdir</em>
+</dt><dd>Where to look for <strong>.html</strong> files if no files are given.
+</dd>
+
+<dt><em>-e</em>, <em>--inputext</em> &lt;extension&gt;
+</dt><dd>The extension that your Lore input files have (default: .xhtml)
+</dd>
+
+<dt><em>-i</em>, <em>--input</em>
+</dt><dd>Input format. New input formats can be dynamically registered. Lore itself
+comes with <q>lore</q> (the standard format), <q>mlore</q> (allows LaTeX equations)
+and <q>man</q> (man page format). If the input format is not registered as a plugin,
+a module of the named input will be searched. For example,
+<strong>--i</strong> twisted.lore.defaultis equivalent to using the default Lore input.
+</dd>
+
+<dt><em>-o</em>, <em>--output</em>
+</dt><dd>Output format. Available output formats depend on the input. For the core
+formats, lore and mlore support html, latex and lint, while man allows
+lore.
+</dd>
+
+<dt><em>-x</em>, <em>--index</em> &lt;filename&gt;
+</dt><dd>The base filename you want to give your index file.
+</dd>
+
+<dt><em>-b</em>, <em>--book</em> &lt;filename&gt;
+</dt><dd>The book file to generate a book from.
+</dd>
+
+<dt><em>--prefixurl</em> &lt;prefix&gt;
+</dt><dd>The prefix to stick on to relative links; only useful when processing
+directories.
+</dd>
+
+<dt><em>--version</em>
+</dt><dd>Display version information and exit.
+</dd>
+
+<dt><em>--config</em>
+</dt><dd>Add input/output-specific information.
+HTML output allows for 'ext=&lt;extension&gt;',
+'template=&lt;template&gt;' and 'baseurl=&lt;format string for API URLs&gt;'. LaTeX
+output allows for 'section' or 'chapter' in Lore, and nothing in Math-Lore.
+Lore output allows for 'ext=&lt;extension&gt;'. Lint output allows nothing.
+Note that disallowed <em>--config</em> options are merely ignored, and do
+not cause errors.
+</dd>
+
+</dl>
+
+</p>
+
+<h2>DESCRIPTION<a name="auto3"/></h2>
+
+<p>If no files are given, all *.html documents in docsdir are processed.
+</p>
+
+<h2>AUTHOR<a name="auto4"/></h2>
+
+<p>Written by Moshe Zadka
+</p>
+
+<h2>REPORTING BUGS<a name="auto5"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto6"/></h2>
+
+<p>Copyright © 2003-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/lore/man/lore.1 b/vendor/Twisted-10.0.0/doc/lore/man/lore.1
new file mode 100644
index 0000000000..0d9cba19f1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/lore/man/lore.1
@@ -0,0 +1,74 @@
+.TH GENERATELORE "1" "October 2002" "" ""
+.SH NAME
+lore \- convert documentations formats
+.SH SYNOPSIS
+.B lore [-l \fIlinkrel\fR] [-d \fIdocsdir\fR] [-i \fIinput\fR] [-o \fIoutput\fR] [--config attribute[=value] [...]] [-p] [file [...]]
+.PP
+.B lore --help
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB-p\fR, \fB--plain\fR
+Use non-flashy progress bar \- one file per line.
+.TP
+\fB-n\fR, \fB--null\fR
+Do not report progress at all.
+.TP
+\fB-N\fR, \fB--number\fR
+Add chapter/section numbers to section headings.
+.TP
+\fI-l\fR, \fI--linkrel\fR
+Where non-document links should be relative to.
+.TP
+\fI-d\fR, \fI--docsdir\fR
+Where to look for \fB.html\fR files if no files are given.
+.TP
+\fI-e\fR, \fI--inputext\fR <extension>
+The extension that your Lore input files have (default: .xhtml)
+.TP
+\fI-i\fR, \fI--input\fR
+Input format. New input formats can be dynamically registered. Lore itself
+comes with "lore" (the standard format), "mlore" (allows LaTeX equations)
+and "man" (man page format). If the input format is not registered as a plugin,
+a module of the named input will be searched. For example,
+.B --i twisted.lore.default
+is equivalent to using the default Lore input.
+.TP
+\fI-o\fR, \fI--output\fR
+Output format. Available output formats depend on the input. For the core
+formats, lore and mlore support html, latex and lint, while man allows
+lore.
+.TP
+\fI-x\fR, \fI--index\fR <filename>
+The base filename you want to give your index file.
+.TP
+\fI-b\fR, \fI--book\fR <filename>
+The book file to generate a book from.
+.TP
+\fI--prefixurl\fR <prefix>
+The prefix to stick on to relative links; only useful when processing
+directories.
+.TP
+\fI--version\fR
+Display version information and exit.
+.TP
+\fI--config\fR
+Add input/output-specific information.
+HTML output allows for 'ext=<extension>',
+'template=<template>' and 'baseurl=<format string for API URLs>'. LaTeX
+output allows for 'section' or 'chapter' in Lore, and nothing in Math-Lore.
+Lore output allows for 'ext=<extension>'. Lint output allows nothing.
+Note that disallowed \fI--config\fR options are merely ignored, and do
+not cause errors.
+.SH DESCRIPTION
+If no files are given, all *.html documents in docsdir are processed.
+.SH AUTHOR
+Written by Moshe Zadka
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2003-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/Twisted-10.0.0/doc/mail/examples/emailserver.tac b/vendor/Twisted-10.0.0/doc/mail/examples/emailserver.tac
new file mode 100644
index 0000000000..95d5b53df8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/examples/emailserver.tac
@@ -0,0 +1,72 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this module directly with:
+# twistd -ny emailserver.tac
+
+
+"""
+A toy email server.
+"""
+
+from zope.interface import implements
+
+from twisted.internet import defer
+from twisted.mail import smtp
+
+class ConsoleMessageDelivery:
+ implements(smtp.IMessageDelivery)
+
+ def receivedHeader(self, helo, origin, recipients):
+ return "Received: ConsoleMessageDelivery"
+
+ def validateFrom(self, helo, origin):
+ # All addresses are accepted
+ return origin
+
+ def validateTo(self, user):
+ # Only messages directed to the "console" user are accepted.
+ if user.dest.local == "console":
+ return lambda: ConsoleMessage()
+ raise smtp.SMTPBadRcpt(user)
+
+class ConsoleMessage:
+ implements(smtp.IMessage)
+
+ def __init__(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def eomReceived(self):
+ print "New message received:"
+ print "\n".join(self.lines)
+ self.lines = None
+ return defer.succeed(None)
+
+ def connectionLost(self):
+ # There was an error, throw away the stored lines
+ self.lines = None
+
+class ConsoleSMTPFactory(smtp.SMTPFactory):
+ def __init__(self, *a, **kw):
+ smtp.SMTPFactory.__init__(self, *a, **kw)
+ self.delivery = ConsoleMessageDelivery()
+
+ def buildProtocol(self, addr):
+ p = smtp.SMTPFactory.buildProtocol(self, addr)
+ p.delivery = self.delivery
+ return p
+
+def main():
+ from twisted.application import internet
+ from twisted.application import service
+
+ a = service.Application("Console SMTP Server")
+ internet.TCPServer(2500, ConsoleSMTPFactory()).setServiceParent(a)
+
+ return a
+
+application = main()
diff --git a/vendor/Twisted-10.0.0/doc/mail/examples/imap4client.py b/vendor/Twisted-10.0.0/doc/mail/examples/imap4client.py
new file mode 100644
index 0000000000..90f943b41e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/examples/imap4client.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Simple IMAP4 client which displays the subjects of all messages in a
+particular mailbox.
+"""
+
+import sys
+
+from twisted.internet import protocol
+from twisted.internet import ssl
+from twisted.internet import defer
+from twisted.internet import stdio
+from twisted.mail import imap4
+from twisted.protocols import basic
+from twisted.python import util
+from twisted.python import log
+
+class TrivialPrompter(basic.LineReceiver):
+ from os import linesep as delimiter
+
+ promptDeferred = None
+
+ def prompt(self, msg):
+ assert self.promptDeferred is None
+ self.display(msg)
+ self.promptDeferred = defer.Deferred()
+ return self.promptDeferred
+
+ def display(self, msg):
+ self.transport.write(msg)
+
+ def lineReceived(self, line):
+ if self.promptDeferred is None:
+ return
+ d, self.promptDeferred = self.promptDeferred, None
+ d.callback(line)
+
+class SimpleIMAP4Client(imap4.IMAP4Client):
+ greetDeferred = None
+
+ def serverGreeting(self, caps):
+ self.serverCapabilities = caps
+ if self.greetDeferred is not None:
+ d, self.greetDeferred = self.greetDeferred, None
+ d.callback(self)
+
+class SimpleIMAP4ClientFactory(protocol.ClientFactory):
+ usedUp = False
+
+ protocol = SimpleIMAP4Client
+
+ def __init__(self, username, onConn):
+ self.ctx = ssl.ClientContextFactory()
+
+ self.username = username
+ self.onConn = onConn
+
+ def buildProtocol(self, addr):
+ assert not self.usedUp
+ self.usedUp = True
+
+ p = self.protocol(self.ctx)
+ p.factory = self
+ p.greetDeferred = self.onConn
+
+ auth = imap4.CramMD5ClientAuthenticator(self.username)
+ p.registerAuthenticator(auth)
+
+ return p
+
+ def clientConnectionFailed(self, connector, reason):
+ d, self.onConn = self.onConn, None
+ d.errback(reason)
+
+# Initial callback - invoked after the server sends us its greet message
+def cbServerGreeting(proto, username, password):
+ # Hook up stdio
+ tp = TrivialPrompter()
+ stdio.StandardIO(tp)
+
+ # And make it easily accessible
+ proto.prompt = tp.prompt
+ proto.display = tp.display
+
+ # Try to authenticate securely
+ return proto.authenticate(password
+ ).addCallback(cbAuthentication, proto
+ ).addErrback(ebAuthentication, proto, username, password
+ )
+
+# Fallback error-handler. If anything goes wrong, log it and quit.
+def ebConnection(reason):
+ log.startLogging(sys.stdout)
+ log.err(reason)
+ from twisted.internet import reactor
+ reactor.stop()
+
+# Callback after authentication has succeeded
+def cbAuthentication(result, proto):
+ # List a bunch of mailboxes
+ return proto.list("", "*"
+ ).addCallback(cbMailboxList, proto
+ )
+
+# Errback invoked when authentication fails
+def ebAuthentication(failure, proto, username, password):
+ # If it failed because no SASL mechanisms match, offer the user the choice
+ # of logging in insecurely.
+ failure.trap(imap4.NoSupportedAuthentication)
+ return proto.prompt("No secure authentication available. Login insecurely? (y/N) "
+ ).addCallback(cbInsecureLogin, proto, username, password
+ )
+
+# Callback for "insecure-login" prompt
+def cbInsecureLogin(result, proto, username, password):
+ if result.lower() == "y":
+ # If they said yes, do it.
+ return proto.login(username, password
+ ).addCallback(cbAuthentication, proto
+ )
+ return defer.fail(Exception("Login failed for security reasons."))
+
+# Callback invoked when a list of mailboxes has been retrieved
+def cbMailboxList(result, proto):
+ result = [e[2] for e in result]
+ s = '\n'.join(['%d. %s' % (n + 1, m) for (n, m) in zip(range(len(result)), result)])
+ if not s:
+ return defer.fail(Exception("No mailboxes exist on server!"))
+ return proto.prompt(s + "\nWhich mailbox? [1] "
+ ).addCallback(cbPickMailbox, proto, result
+ )
+
+# When the user selects a mailbox, "examine" it.
+def cbPickMailbox(result, proto, mboxes):
+ mbox = mboxes[int(result or '1') - 1]
+ return proto.examine(mbox
+ ).addCallback(cbExamineMbox, proto
+ )
+
+# Callback invoked when examine command completes.
+def cbExamineMbox(result, proto):
+ # Retrieve the subject header of every message on the mailbox.
+ return proto.fetchSpecific('1:*',
+ headerType='HEADER.FIELDS',
+ headerArgs=['SUBJECT']
+ ).addCallback(cbFetch, proto
+ )
+
+# Finally, display headers.
+def cbFetch(result, proto):
+ keys = result.keys()
+ keys.sort()
+ for k in keys:
+ proto.display('%s %s' % (k, result[k][0][2]))
+ return proto.logout()
+
+PORT = 143
+
+def main():
+ hostname = raw_input('IMAP4 Server Hostname: ')
+ username = raw_input('IMAP4 Username: ')
+ password = util.getPassword('IMAP4 Password: ')
+
+ onConn = defer.Deferred(
+ ).addCallback(cbServerGreeting, username, password
+ ).addErrback(ebConnection
+ )
+
+ factory = SimpleIMAP4ClientFactory(username, onConn)
+
+ from twisted.internet import reactor
+ conn = reactor.connectTCP(hostname, PORT, factory)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/mail/examples/index.html b/vendor/Twisted-10.0.0/doc/mail/examples/index.html
new file mode 100644
index 0000000000..ed16be71cf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/examples/index.html
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted.Mail code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted.Mail code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">SMTP servers</a></li><li><a href="#auto1">SMTP clients</a></li><li><a href="#auto2">IMAP clients</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>SMTP servers<a name="auto0"/></h2>
+ <ul>
+ <li><a href="emailserver.tac" shape="rect">emailserver.tac</a> - a toy email server</li>
+ </ul>
+
+ <h2>SMTP clients<a name="auto1"/></h2>
+ <ul>
+ <li><a href="smtpclient_tls.py" shape="rect">smtpclient_tls.py</a> - send email
+ using authentication and transport layer security.</li>
+ </ul>
+
+ <h2>IMAP clients<a name="auto2"/></h2>
+ <ul>
+ <li><a href="imap4client.py" shape="rect">imap4client.py</a> - Simple IMAP4
+ client which displays the subjects of all messages in a
+ particular mailbox.</li>
+ </ul>
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/mail/examples/smtpclient_tls.py b/vendor/Twisted-10.0.0/doc/mail/examples/smtpclient_tls.py
new file mode 100644
index 0000000000..758b97d744
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/examples/smtpclient_tls.py
@@ -0,0 +1,157 @@
+
+"""
+Demonstrate sending mail via SMTP while employing TLS and performing
+authentication.
+"""
+
+import sys
+
+from OpenSSL.SSL import SSLv3_METHOD
+
+from twisted.mail.smtp import ESMTPSenderFactory
+from twisted.python.usage import Options, UsageError
+from twisted.internet.ssl import ClientContextFactory
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
+
+def sendmail(
+ authenticationUsername, authenticationSecret,
+ fromAddress, toAddress,
+ messageFile,
+ smtpHost, smtpPort=25
+ ):
+ """
+ @param authenticationUsername: The username with which to authenticate.
+ @param authenticationSecret: The password with which to authenticate.
+ @param fromAddress: The SMTP reverse path (ie, MAIL FROM)
+ @param toAddress: The SMTP forward path (ie, RCPT TO)
+ @param messageFile: A file-like object containing the headers and body of
+ the message to send.
+ @param smtpHost: The MX host to which to connect.
+ @param smtpPort: The port number to which to connect.
+
+ @return: A Deferred which will be called back when the message has been
+ sent or which will errback if it cannot be sent.
+ """
+
+ # Create a context factory which only allows SSLv3 and does not verify
+ # the peer's certificate.
+ contextFactory = ClientContextFactory()
+ contextFactory.method = SSLv3_METHOD
+
+ resultDeferred = Deferred()
+
+ senderFactory = ESMTPSenderFactory(
+ authenticationUsername,
+ authenticationSecret,
+ fromAddress,
+ toAddress,
+ messageFile,
+ resultDeferred,
+ contextFactory=contextFactory)
+
+ reactor.connectTCP(smtpHost, smtpPort, senderFactory)
+
+ return resultDeferred
+
+
+
+class SendmailOptions(Options):
+ synopsis = "smtpclient_tls.py [options]"
+
+ optParameters = [
+ ('username', 'u', None,
+ 'The username with which to authenticate to the SMTP server.'),
+ ('password', 'p', None,
+ 'The password with which to authenticate to the SMTP server.'),
+ ('from-address', 'f', None,
+ 'The address from which to send the message.'),
+ ('to-address', 't', None,
+ 'The address to which to send the message.'),
+ ('message', 'm', None,
+ 'The filename which contains the message to send.'),
+ ('smtp-host', 'h', None,
+ 'The host through which to send the message.'),
+ ('smtp-port', None, '25',
+ 'The port number on smtp-host to which to connect.')]
+
+
+ def postOptions(self):
+ """
+ Parse integer parameters, open the message file, and make sure all
+ required parameters have been specified.
+ """
+ try:
+ self['smtp-port'] = int(self['smtp-port'])
+ except ValueError:
+ raise UsageError("--smtp-port argument must be an integer.")
+ if self['username'] is None:
+ raise UsageError(
+ "Must specify authentication username with --username")
+ if self['password'] is None:
+ raise UsageError(
+ "Must specify authentication password with --password")
+ if self['from-address'] is None:
+ raise UsageError("Must specify from address with --from-address")
+ if self['to-address'] is None:
+ raise UsageError("Must specify from address with --to-address")
+ if self['smtp-host'] is None:
+ raise UsageError("Must specify smtp host with --smtp-host")
+ if self['message'] is None:
+ raise UsageError(
+ "Must specify a message file to send with --message")
+ try:
+ self['message'] = file(self['message'])
+ except Exception, e:
+ raise UsageError(e)
+
+
+
+def cbSentMessage(result):
+ """
+ Called when the message has been sent.
+
+ Report success to the user and then stop the reactor.
+ """
+ print "Message sent"
+ reactor.stop()
+
+
+
+def ebSentMessage(err):
+ """
+ Called if the message cannot be sent.
+
+ Report the failure to the user and then stop the reactor.
+ """
+ err.printTraceback()
+ reactor.stop()
+
+
+
+def main(args=None):
+ """
+ Parse arguments and send an email based on them.
+ """
+ o = SendmailOptions()
+ try:
+ o.parseOptions(args)
+ except UsageError, e:
+ raise SystemExit(e)
+ else:
+ from twisted.python import log
+ log.startLogging(sys.stdout)
+ result = sendmail(
+ o['username'],
+ o['password'],
+ o['from-address'],
+ o['to-address'],
+ o['message'],
+ o['smtp-host'],
+ o['smtp-port'])
+ result.addCallbacks(cbSentMessage, ebSentMessage)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/vendor/Twisted-10.0.0/doc/mail/index.html b/vendor/Twisted-10.0.0/doc/mail/index.html
new file mode 100644
index 0000000000..cb35af254f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Mail Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Mail Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Mail</li>
+<li><a href="tutorial/smtpclient/smtpclient.html" shape="rect">Twisted Mail Tutorial</a>: Building
+an SMTP Client from Scratch</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/mail/man/mailmail-man.html b/vendor/Twisted-10.0.0/doc/mail/man/mailmail-man.html
new file mode 100644
index 0000000000..b51a47618d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/man/mailmail-man.html
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: MAILMAIL.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">MAILMAIL.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>mailmail - Twisted sendmail compatibility script
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>mailmail</strong> [recipient addresses]</p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>mailmail reads RFC822 message text from standard input and delivers them,
+using SMTP, to a Mail Transfer Agent listening at 127.0.0.1:25. It accepts
+(but does not necessarily implement) many of the standard sendmail(1)
+options, but it is preferable to list only the recipient addresses on
+the command line, and include a <strong>From</strong> header within the message text
+indicating the sender.
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Jp Calderone
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2003-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/mail/man/mailmail.1 b/vendor/Twisted-10.0.0/doc/mail/man/mailmail.1
new file mode 100644
index 0000000000..9bff0f7615
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/man/mailmail.1
@@ -0,0 +1,21 @@
+.TH MAILMAIL "1" "July 2003" "" ""
+.SH NAME
+mailmail \- Twisted sendmail compatibility script
+.SH SYNOPSIS
+.B mailmail [recipient addresses]
+.SH DESCRIPTION
+mailmail reads RFC822 message text from standard input and delivers them,
+using SMTP, to a Mail Transfer Agent listening at 127.0.0.1:25. It accepts
+(but does not necessarily implement) many of the standard sendmail(1)
+options, but it is preferable to list only the recipient addresses on
+the command line, and include a \fBFrom\fR header within the message text
+indicating the sender.
+.SH AUTHOR
+Written by Jp Calderone
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2003-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-1.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-1.tac
new file mode 100644
index 0000000000..40b685cee5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-1.tac
@@ -0,0 +1,3 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-10.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-10.tac
new file mode 100644
index 0000000000..dcfe5ef267
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-10.tac
@@ -0,0 +1,56 @@
+import StringIO
+
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.internet import defer
+from twisted.mail import smtp
+
+class SMTPTutorialClient(smtp.ESMTPClient):
+ mailFrom = "tutorial_sender@example.com"
+ mailTo = "tutorial_recipient@example.net"
+ mailData = '''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy <tutorial_sender@example.com>
+To: Tutorial Gal <tutorial_recipient@example.net>
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''
+
+ def getMailFrom(self):
+ result = self.mailFrom
+ self.mailFrom = None
+ return result
+
+ def getMailTo(self):
+ return [self.mailTo]
+
+ def getMailData(self):
+ return StringIO.StringIO(self.mailData)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ print 'Sent', numOk, 'messages'
+
+ from twisted.internet import reactor
+ reactor.stop()
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = SMTPTutorialClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+def getMailExchange(host):
+ return defer.succeed('localhost')
+
+def cbMailExchange(exchange):
+ smtpClientFactory = SMTPClientFactory()
+
+ smtpClientService = internet.TCPClient(exchange, 25, smtpClientFactory)
+ smtpClientService.setServiceParent(application)
+
+getMailExchange('example.net').addCallback(cbMailExchange)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-11.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-11.tac
new file mode 100644
index 0000000000..a570165332
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-11.tac
@@ -0,0 +1,58 @@
+import StringIO
+
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.internet import defer
+from twisted.mail import smtp, relaymanager
+
+class SMTPTutorialClient(smtp.ESMTPClient):
+ mailFrom = "tutorial_sender@example.com"
+ mailTo = "tutorial_recipient@example.net"
+ mailData = '''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy <tutorial_sender@example.com>
+To: Tutorial Gal <tutorial_recipient@example.net>
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''
+
+ def getMailFrom(self):
+ result = self.mailFrom
+ self.mailFrom = None
+ return result
+
+ def getMailTo(self):
+ return [self.mailTo]
+
+ def getMailData(self):
+ return StringIO.StringIO(self.mailData)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ print 'Sent', numOk, 'messages'
+
+ from twisted.internet import reactor
+ reactor.stop()
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = SMTPTutorialClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+def getMailExchange(host):
+ def cbMX(mxRecord):
+ return str(mxRecord.exchange)
+ return relaymanager.MXCalculator().getMX(host).addCallback(cbMX)
+
+def cbMailExchange(exchange):
+ smtpClientFactory = SMTPClientFactory()
+
+ smtpClientService = internet.TCPClient(exchange, 25, smtpClientFactory)
+ smtpClientService.setServiceParent(application)
+
+getMailExchange('example.net').addCallback(cbMailExchange)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-2.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-2.tac
new file mode 100644
index 0000000000..e95921be78
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-2.tac
@@ -0,0 +1,10 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpClientFactory = protocol.ClientFactory()
+smtpClientService = internet.TCPClient(None, None, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-3.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-3.tac
new file mode 100644
index 0000000000..26ea519221
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-3.tac
@@ -0,0 +1,10 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpClientFactory = protocol.ClientFactory()
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-4.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-4.tac
new file mode 100644
index 0000000000..e95e5968a7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-4.tac
@@ -0,0 +1,12 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpClientFactory = protocol.ClientFactory()
+smtpClientFactory.protocol = protocol.Protocol
+
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-5.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-5.tac
new file mode 100644
index 0000000000..30af8ad8be
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-5.tac
@@ -0,0 +1,14 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpClientFactory = protocol.ClientFactory()
+
+from twisted.mail import smtp
+smtpClientFactory.protocol = smtp.ESMTPClient
+
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-6.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-6.tac
new file mode 100644
index 0000000000..d1eb5a001d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-6.tac
@@ -0,0 +1,18 @@
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.mail import smtp
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = smtp.ESMTPClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+smtpClientFactory = SMTPClientFactory()
+
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-7.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-7.tac
new file mode 100644
index 0000000000..297a35a392
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-7.tac
@@ -0,0 +1,46 @@
+import StringIO
+
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.mail import smtp
+
+class SMTPTutorialClient(smtp.ESMTPClient):
+ mailFrom = "tutorial_sender@example.com"
+ mailTo = "tutorial_recipient@example.net"
+ mailData = '''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy <tutorial_sender@example.com>
+To: Tutorial Gal <tutorial_recipient@example.net>
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''
+
+ def getMailFrom(self):
+ result = self.mailFrom
+ self.mailFrom = None
+ return result
+
+ def getMailTo(self):
+ return [self.mailTo]
+
+ def getMailData(self):
+ return StringIO.StringIO(self.mailData)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ print 'Sent', numOk, 'messages'
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = SMTPTutorialClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+smtpClientFactory = SMTPClientFactory()
+
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-8.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-8.tac
new file mode 100644
index 0000000000..8dbef106df
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-8.tac
@@ -0,0 +1,49 @@
+import StringIO
+
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.mail import smtp
+
+class SMTPTutorialClient(smtp.ESMTPClient):
+ mailFrom = "tutorial_sender@example.com"
+ mailTo = "tutorial_recipient@example.net"
+ mailData = '''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy <tutorial_sender@example.com>
+To: Tutorial Gal <tutorial_recipient@example.net>
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''
+
+ def getMailFrom(self):
+ result = self.mailFrom
+ self.mailFrom = None
+ return result
+
+ def getMailTo(self):
+ return [self.mailTo]
+
+ def getMailData(self):
+ return StringIO.StringIO(self.mailData)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ print 'Sent', numOk, 'messages'
+
+ from twisted.internet import reactor
+ reactor.stop()
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = SMTPTutorialClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+smtpClientFactory = SMTPClientFactory()
+
+smtpClientService = internet.TCPClient('localhost', 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-9.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-9.tac
new file mode 100644
index 0000000000..397057a04e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient-9.tac
@@ -0,0 +1,53 @@
+import StringIO
+
+from twisted.application import service
+
+application = service.Application("SMTP Client Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+from twisted.mail import smtp
+
+class SMTPTutorialClient(smtp.ESMTPClient):
+ mailFrom = "tutorial_sender@example.com"
+ mailTo = "tutorial_recipient@example.net"
+ mailData = '''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy <tutorial_sender@example.com>
+To: Tutorial Gal <tutorial_recipient@example.net>
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''
+
+ def getMailFrom(self):
+ result = self.mailFrom
+ self.mailFrom = None
+ return result
+
+ def getMailTo(self):
+ return [self.mailTo]
+
+ def getMailData(self):
+ return StringIO.StringIO(self.mailData)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ print 'Sent', numOk, 'messages'
+
+ from twisted.internet import reactor
+ reactor.stop()
+
+class SMTPClientFactory(protocol.ClientFactory):
+ protocol = SMTPTutorialClient
+
+ def buildProtocol(self, addr):
+ return self.protocol(secret=None, identity='example.com')
+
+def getMailExchange(host):
+ return 'localhost'
+
+smtpClientFactory = SMTPClientFactory()
+
+smtpClientService = internet.TCPClient(
+ getMailExchange('example.net'), 25, smtpClientFactory)
+smtpClientService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient.html b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient.html
new file mode 100644
index 0000000000..bd86d3405a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpclient/smtpclient.html
@@ -0,0 +1,752 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Mail Tutorial: Building an SMTP Client from Scratch</title>
+<link href="../../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Mail Tutorial: Building an SMTP Client from Scratch</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><ul><li><a href="#auto1">SMTP Client 1</a></li><li><a href="#auto2">SMTP Client 2</a></li><li><a href="#auto3">SMTP Client 3</a></li><li><a href="#auto4">SMTP Client 4</a></li><li><a href="#auto5">SMTP Client 5</a></li><li><a href="#auto6">SMTP Client 6</a></li><li><a href="#auto7">SMTP Client 7</a></li><li><a href="#auto8">SMTP Client 8</a></li><li><a href="#auto9">SMTP Client 9</a></li><li><a href="#auto10">SMTP Client 10</a></li><li><a href="#auto11">SMTP Client 11</a></li></ul></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p>This tutorial will walk you through the creation of an extremely
+simple SMTP client application. By the time the tutorial is complete,
+you will understand how to create and start a TCP client speaking the
+SMTP protocol, have it connect to an appropriate mail exchange server,
+and transmit a message for delivery.</p>
+
+<p>For the majority of this tutorial, <code>twistd</code> will be used
+to launch the application. Near the end we will explore other
+possibilities for starting a Twisted application. Until then, make
+sure that you have <code>twistd</code> installed and conveniently
+accessible for use in running each of the example <code>.tac</code>
+files.</p>
+
+<h3>SMTP Client 1<a name="auto1"/></h3>
+
+<p>The first step is to create <a href="smtpclient-1.tac" shape="rect">the most
+minimal <code>.tac</code> file</a> possible for use by
+<code>twistd</code>.</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>
+</pre>
+
+<p>The first line of the <code>.tac</code> file imports
+<code>twisted.application.service</code>, a module which contains many
+of the basic <em>service</em> classes and helper functions available
+in Twisted. In particular, we will be using the
+<code>Application</code> function to create a new <em>application
+service</em>. An <em>application service</em> simply acts as a
+central object on which to store certain kinds of deployment
+configuration.</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">&quot;SMTP Client Tutorial&quot;</span>)
+</pre>
+
+<p>The second line of the <code>.tac</code> file creates a new
+<em>application service</em> and binds it to the local name
+<code>application</code>. <code>twistd</code> requires this local
+name in each <code>.tac</code> file it runs. It uses various pieces
+of configuration on the object to determine its behavior. For
+example, <code>&quot;SMTP Client Tutorial&quot;</code> will be used as the name
+of the <code>.tap</code> file into which to serialize application
+state, should it be necessary to do so.</p>
+
+<p>That does it for the first example. We now have enough of a
+<code>.tac</code> file to pass to <code>twistd</code>. If we run <a href="smtpclient-1.tac" shape="rect">smtpclient-1.tac</a> using the
+<code>twistd</code> command line:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">twistd</span> -<span class="py-src-variable">ny</span> <span class="py-src-variable">smtpclient</span>-<span class="py-src-number">1.</span><span class="py-src-variable">tac</span>
+</pre>
+
+<p>we are rewarded with the following output:</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/mail/tutorial/smtpclient$ twistd -ny smtpclient-1.tac
+18:31 EST [-] Log opened.
+18:31 EST [-] twistd 2.0.0 (/usr/bin/python2.4 2.4.1) starting up
+18:31 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+18:31 EST [-] Loading smtpclient-1.tac...
+18:31 EST [-] Loaded.
+</pre>
+
+<p>As we expected, not much is going on. We can shutdown this server
+by issuing <code>^C</code>:</p>
+
+<pre class="shell" xml:space="preserve">
+18:34 EST [-] Received SIGINT, shutting down.
+18:34 EST [-] Main loop terminated.
+18:34 EST [-] Server Shut Down.
+exarkun@boson:~/mail/tutorial/smtpclient$
+</pre>
+
+<h3>SMTP Client 2<a name="auto2"/></h3>
+
+<p>The first version of our SMTP client wasn't very interesting. It
+didn't even establish any TCP connections! The <a href="smtpclient-2.tac" shape="rect">second version</a> will come a little bit
+closer to that level of complexity. First, we need to import a few
+more things:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">protocol</span>
+</pre>
+
+<p><code>twisted.application.internet</code> is another
+<em>application service</em> module. It provides services for
+establishing outgoing connections (as well as creating network
+servers, though we are not interested in those parts for the moment).
+<code>twisted.internet.protocol</code> provides base implementations
+of many of the core Twisted concepts, such as <em>factories</em> and
+<em>protocols</em>.</p>
+
+<p>The next line of <a href="smtpclient-2.tac" shape="rect">smtpclient-2.tac</a>
+instantiates a new <em>client factory</em>.</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientFactory</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">ClientFactory</span>()
+</pre>
+
+<p><em>Client factories</em> are responsible for constructing
+<em>protocol instances</em> whenever connections are established.
+They may be required to create just one instance, or many instances if
+many different connections are established, or they may never be
+required to create one at all, if no connection ever manages to be
+established.</p>
+
+<p>Now that we have a client factory, we'll need to hook it up to the
+network somehow. The next line of <code>smtpclient-2.tac</code> does
+just that:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">smtpClientFactory</span>)
+</pre>
+
+<p>We'll ignore the first two arguments to
+<code>internet.TCPClient</code> for the moment and instead focus on
+the third. <code>TCPClient</code> is one of those <em>application
+service</em> classes. It creates TCP connections to a specified
+address and then uses its third argument, a <em>client factory</em>,
+to get a <em>protocol instance</em>. It then associates the TCP
+connection with the protocol instance and gets out of the way.</p>
+
+<p>We can try to run <code>smtpclient-2.tac</code> the same way we ran
+<code>smtpclient-1.tac</code>, but the results might be a little
+disappointing:</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/mail/tutorial/smtpclient$ twistd -ny smtpclient-2.tac
+18:55 EST [-] Log opened.
+18:55 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+18:55 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+18:55 EST [-] Loading smtpclient-2.tac...
+18:55 EST [-] Loaded.
+18:55 EST [-] Starting factory &lt;twisted.internet.protocol.ClientFactory
+ instance at 0xb791e46c&gt;
+18:55 EST [-] Traceback (most recent call last):
+ File &quot;twisted/scripts/twistd.py&quot;, line 187, in runApp
+ app.runReactorWithLogging(config, oldstdout, oldstderr)
+ File &quot;twisted/application/app.py&quot;, line 128, in runReactorWithLogging
+ reactor.run()
+ File &quot;twisted/internet/posixbase.py&quot;, line 200, in run
+ self.mainLoop()
+ File &quot;twisted/internet/posixbase.py&quot;, line 208, in mainLoop
+ self.runUntilCurrent()
+ --- &lt;exception caught here&gt; ---
+ File &quot;twisted/internet/base.py&quot;, line 533, in runUntilCurrent
+ call.func(*call.args, **call.kw)
+ File &quot;twisted/internet/tcp.py&quot;, line 489, in resolveAddress
+ if abstract.isIPAddress(self.addr[0]):
+ File &quot;twisted/internet/abstract.py&quot;, line 315, in isIPAddress
+ parts = string.split(addr, '.')
+ File &quot;/usr/lib/python2.4/string.py&quot;, line 292, in split
+ return s.split(sep, maxsplit)
+ exceptions.AttributeError: 'NoneType' object has no attribute 'split'
+
+18:55 EST [-] Received SIGINT, shutting down.
+18:55 EST [-] Main loop terminated.
+18:55 EST [-] Server Shut Down.
+exarkun@boson:~/mail/tutorial/smtpclient$
+</pre>
+
+<p>What happened? Those first two arguments to <code>TCPClient</code>
+turned out to be important after all. We'll get to them in the next
+example.</p>
+
+<h3>SMTP Client 3<a name="auto3"/></h3>
+
+<p>Version three of our SMTP client only changes one thing. The line
+from version two:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">None</span>, <span class="py-src-variable">None</span>, <span class="py-src-variable">smtpClientFactory</span>)
+</pre>
+
+<p>has its first two arguments changed from <code>None</code> to
+something with a bit more meaning:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-string">'localhost'</span>, <span class="py-src-number">25</span>, <span class="py-src-variable">smtpClientFactory</span>)
+</pre>
+
+<p>This directs the client to connect to <em>localhost</em> on port
+<em>25</em>. This isn't the address we want ultimately, but it's a
+good place-holder for the time being. We can run <a href="smtpclient-3.tac" shape="rect">smtpclient-3.tac</a> and see what this change
+gets us:</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/mail/tutorial/smtpclient$ twistd -ny smtpclient-3.tac
+19:10 EST [-] Log opened.
+19:10 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+19:10 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+19:10 EST [-] Loading smtpclient-3.tac...
+19:10 EST [-] Loaded.
+19:10 EST [-] Starting factory &lt;twisted.internet.protocol.ClientFactory
+ instance at 0xb791e48c&gt;
+19:10 EST [-] Enabling Multithreading.
+19:10 EST [Uninitialized] Traceback (most recent call last):
+ File &quot;twisted/python/log.py&quot;, line 56, in callWithLogger
+ return callWithContext({&quot;system&quot;: lp}, func, *args, **kw)
+ File &quot;twisted/python/log.py&quot;, line 41, in callWithContext
+ return context.call({ILogContext: newCtx}, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 52, in callWithContext
+ return self.currentContext().callWithContext(ctx, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 31, in callWithContext
+ return func(*args,**kw)
+ --- &lt;exception caught here&gt; ---
+ File &quot;twisted/internet/selectreactor.py&quot;, line 139, in _doReadOrWrite
+ why = getattr(selectable, method)()
+ File &quot;twisted/internet/tcp.py&quot;, line 543, in doConnect
+ self._connectDone()
+ File &quot;twisted/internet/tcp.py&quot;, line 546, in _connectDone
+ self.protocol = self.connector.buildProtocol(self.getPeer())
+ File &quot;twisted/internet/base.py&quot;, line 641, in buildProtocol
+ return self.factory.buildProtocol(addr)
+ File &quot;twisted/internet/protocol.py&quot;, line 99, in buildProtocol
+ p = self.protocol()
+ exceptions.TypeError: 'NoneType' object is not callable
+
+19:10 EST [Uninitialized] Stopping factory
+ &lt;twisted.internet.protocol.ClientFactory instance at
+ 0xb791e48c&gt;
+19:10 EST [-] Received SIGINT, shutting down.
+19:10 EST [-] Main loop terminated.
+19:10 EST [-] Server Shut Down.
+exarkun@boson:~/mail/tutorial/smtpclient$
+</pre>
+
+<p>A meagre amount of progress, but the service still raises an
+exception. This time, it's because we haven't specified a
+<em>protocol class</em> for the factory to use. We'll do that in the
+next example.</p>
+
+<h3>SMTP Client 4<a name="auto4"/></h3>
+
+<p>In the previous example, we ran into a problem because we hadn't
+set up our <em>client factory's</em> <em>protocol</em> attribute
+correctly (or at all). <code>ClientFactory.buildProtocol</code> is
+the method responsible for creating a <em>protocol instance</em>. The
+default implementation calls the factory's <code>protocol</code> attribute,
+adds itself as an attribute named <code>factory</code> to the
+resulting instance, and returns it. In <a href="smtpclient-4.tac" shape="rect">smtpclient-4.tac</a>, we'll correct the
+oversight that caused the traceback in smtpclient-3.tac:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientFactory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">Protocol</span>
+</pre>
+
+<p>Running this version of the client, we can see the output is once
+again traceback free:</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-4.tac
+19:29 EST [-] Log opened.
+19:29 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+19:29 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+19:29 EST [-] Loading smtpclient-4.tac...
+19:29 EST [-] Loaded.
+19:29 EST [-] Starting factory &lt;twisted.internet.protocol.ClientFactory
+ instance at 0xb791e4ac&gt;
+19:29 EST [-] Enabling Multithreading.
+19:29 EST [-] Received SIGINT, shutting down.
+19:29 EST [Protocol,client] Stopping factory
+ &lt;twisted.internet.protocol.ClientFactory instance at
+ 0xb791e4ac&gt;
+19:29 EST [-] Main loop terminated.
+19:29 EST [-] Server Shut Down.
+exarkun@boson:~/doc/mail/tutorial/smtpclient$
+</pre>
+
+<p>But what does this mean?
+<code>twisted.internet.protocol.Protocol</code> is the base
+<em>protocol</em> implementation. For those familiar with the classic
+UNIX network services, it is equivalent to the <em>discard</em>
+service. It never produces any output and it discards all its input.
+Not terribly useful, and certainly nothing like an SMTP client. Let's
+see how we can improve this in the next example.</p>
+
+<h3>SMTP Client 5<a name="auto5"/></h3>
+
+<p>In <a href="smtpclient-5.tac" shape="rect">smtpclient-5.tac</a>, we will begin
+to use Twisted's SMTP protocol implementation for the first time.
+We'll make the obvious change, simply swapping out
+<code>twisted.internet.protocol.Protocol</code> in favor of
+<code>twisted.mail.smtp.ESMTPClient</code>. Don't worry about the
+<em>E</em> in <em>ESMTP</em>. It indicates we're actually using a
+newer version of the SMTP protocol. There is an
+<code>SMTPClient</code> in Twisted, but there's essentially no reason
+to ever use it.</p>
+
+<p>smtpclient-5.tac adds a new import:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">mail</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">smtp</span>
+</pre>
+
+<p>All of the mail related code in Twisted exists beneath the
+<code>twisted.mail</code> package. More specifically, everything
+having to do with the SMTP protocol implementation is defined in the
+<code>twisted.mail.smtp</code> module.</p>
+
+<p>Next we remove a line we added in smtpclient-4.tac:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientFactory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">protocol</span>.<span class="py-src-variable">Protocol</span>
+</pre>
+
+<p>And add a similar one in its place:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientFactory</span>.<span class="py-src-variable">protocol</span> = <span class="py-src-variable">smtp</span>.<span class="py-src-variable">ESMTPClient</span>
+</pre>
+
+<p>Our client factory is now using a protocol implementation which
+behaves as an SMTP client. What happens when we try to run this
+version?</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-5.tac
+19:42 EST [-] Log opened.
+19:42 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+19:42 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+19:42 EST [-] Loading smtpclient-5.tac...
+19:42 EST [-] Loaded.
+19:42 EST [-] Starting factory &lt;twisted.internet.protocol.ClientFactory
+ instance at 0xb791e54c&gt;
+19:42 EST [-] Enabling Multithreading.
+19:42 EST [Uninitialized] Traceback (most recent call last):
+ File &quot;twisted/python/log.py&quot;, line 56, in callWithLogger
+ return callWithContext({&quot;system&quot;: lp}, func, *args, **kw)
+ File &quot;twisted/python/log.py&quot;, line 41, in callWithContext
+ return context.call({ILogContext: newCtx}, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 52, in callWithContext
+ return self.currentContext().callWithContext(ctx, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 31, in callWithContext
+ return func(*args,**kw)
+ --- &lt;exception caught here&gt; ---
+ File &quot;twisted/internet/selectreactor.py&quot;, line 139, in _doReadOrWrite
+ why = getattr(selectable, method)()
+ File &quot;twisted/internet/tcp.py&quot;, line 543, in doConnect
+ self._connectDone()
+ File &quot;twisted/internet/tcp.py&quot;, line 546, in _connectDone
+ self.protocol = self.connector.buildProtocol(self.getPeer())
+ File &quot;twisted/internet/base.py&quot;, line 641, in buildProtocol
+ return self.factory.buildProtocol(addr)
+ File &quot;twisted/internet/protocol.py&quot;, line 99, in buildProtocol
+ p = self.protocol()
+ exceptions.TypeError: __init__() takes at least 2 arguments (1 given)
+
+19:42 EST [Uninitialized] Stopping factory
+ &lt;twisted.internet.protocol.ClientFactory instance at
+ 0xb791e54c&gt;
+19:43 EST [-] Received SIGINT, shutting down.
+19:43 EST [-] Main loop terminated.
+19:43 EST [-] Server Shut Down.
+exarkun@boson:~/doc/mail/tutorial/smtpclient$
+</pre>
+
+
+<p>Oops, back to getting a traceback. This time, the default
+implementation of <code>buildProtocol</code> seems no longer to be
+sufficient. It instantiates the protocol with no arguments, but
+<code>ESMTPClient</code> wants at least one argument. In the next
+version of the client, we'll override <code>buildProtocol</code> to
+fix this problem.</p>
+
+<h3>SMTP Client 6<a name="auto6"/></h3>
+
+<p><a href="smtpclient-6.tac" shape="rect">smtpclient-6.tac</a> introduces a
+<code>twisted.internet.protocol.ClientFactory</code> subclass with an
+overridden <code>buildProtocol</code> method to overcome the problem
+encountered in the previous example.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SMTPClientFactory</span>(<span class="py-src-parameter">protocol</span>.<span class="py-src-parameter">ClientFactory</span>):
+ <span class="py-src-variable">protocol</span> = <span class="py-src-variable">smtp</span>.<span class="py-src-variable">ESMTPClient</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">buildProtocol</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">addr</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">protocol</span>(<span class="py-src-variable">secret</span>=<span class="py-src-variable">None</span>, <span class="py-src-variable">identity</span>=<span class="py-src-string">'example.com'</span>)
+</pre>
+
+<p>The overridden method does almost the same thing as the base
+implementation: the only change is that it passes values for two
+arguments to <code>twisted.mail.smtp.ESMTPClient</code>'s initializer.
+The <code>secret</code> argument is used for SMTP authentication
+(which we will not attempt yet). The <code>identity</code> argument
+is used as a to identify ourselves Another minor change to note is
+that the <code>protocol</code> attribute is now defined in the class
+definition, rather than tacked onto an instance after one is created.
+This means it is a class attribute, rather than an instance attribute,
+now, which makes no difference as far as this example is concerned.
+There are circumstances in which the difference is important: be sure
+you understand the implications of each approach when creating your
+own factories.</p>
+
+<p>One other change is required: instead of
+instantiating <code>twisted.internet.protocol.ClientFactory</code>, we
+will now instantiate <code>SMTPClientFactory</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">smtpClientFactory</span> = <span class="py-src-variable">SMTPClientFactory</span>()
+</pre>
+
+<p>Running this version of the code, we observe that the code
+<strong>still</strong> isn't quite traceback-free.</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-6.tac
+21:17 EST [-] Log opened.
+21:17 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+21:17 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+21:17 EST [-] Loading smtpclient-6.tac...
+21:17 EST [-] Loaded.
+21:17 EST [-] Starting factory &lt;__builtin__.SMTPClientFactory instance
+ at 0xb77fd68c&gt;
+21:17 EST [-] Enabling Multithreading.
+21:17 EST [ESMTPClient,client] Traceback (most recent call last):
+ File &quot;twisted/python/log.py&quot;, line 56, in callWithLogger
+ return callWithContext({&quot;system&quot;: lp}, func, *args, **kw)
+ File &quot;twisted/python/log.py&quot;, line 41, in callWithContext
+ return context.call({ILogContext: newCtx}, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 52, in callWithContext
+ return self.currentContext().callWithContext(ctx, func, *args, **kw)
+ File &quot;twisted/python/context.py&quot;, line 31, in callWithContext
+ return func(*args,**kw)
+ --- &lt;exception caught here&gt; ---
+ File &quot;twisted/internet/selectreactor.py&quot;, line 139, in _doReadOrWrite
+ why = getattr(selectable, method)()
+ File &quot;twisted/internet/tcp.py&quot;, line 351, in doRead
+ return self.protocol.dataReceived(data)
+ File &quot;twisted/protocols/basic.py&quot;, line 221, in dataReceived
+ why = self.lineReceived(line)
+ File &quot;twisted/mail/smtp.py&quot;, line 1039, in lineReceived
+ why = self._okresponse(self.code,'\n'.join(self.resp))
+ File &quot;twisted/mail/smtp.py&quot;, line 1281, in esmtpState_serverConfig
+ self.tryTLS(code, resp, items)
+ File &quot;twisted/mail/smtp.py&quot;, line 1294, in tryTLS
+ self.authenticate(code, resp, items)
+ File &quot;twisted/mail/smtp.py&quot;, line 1343, in authenticate
+ self.smtpState_from(code, resp)
+ File &quot;twisted/mail/smtp.py&quot;, line 1062, in smtpState_from
+ self._from = self.getMailFrom()
+ File &quot;twisted/mail/smtp.py&quot;, line 1137, in getMailFrom
+ raise NotImplementedError
+ exceptions.NotImplementedError:
+
+21:17 EST [ESMTPClient,client] Stopping factory
+ &lt;__builtin__.SMTPClientFactory instance at 0xb77fd68c&gt;
+21:17 EST [-] Received SIGINT, shutting down.
+21:17 EST [-] Main loop terminated.
+21:17 EST [-] Server Shut Down.
+exarkun@boson:~/doc/mail/tutorial/smtpclient$
+</pre>
+
+<p>What we have accomplished with this iteration of the example is to
+navigate far enough into an SMTP transaction that Twisted is now
+interested in calling back to application-level code to determine what
+its next step should be. In the next example, we'll see how to
+provide that information to it.</p>
+
+<h3>SMTP Client 7<a name="auto7"/></h3>
+
+<p>SMTP Client 7 is the first version of our SMTP client which
+actually includes message data to transmit. For simplicity's sake,
+the message is defined as part of a new class. In a useful program
+which sent email, message data might be pulled in from the filesystem,
+a database, or be generated based on user-input. <a href="smtpclient-7.tac" shape="rect">smtpclient-7.tac</a>, however, defines a new
+class, <code>SMTPTutorialClient</code>, with three class attributes
+(<code>mailFrom</code>, <code>mailTo</code>, and
+<code>mailData</code>):</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">SMTPTutorialClient</span>(<span class="py-src-parameter">smtp</span>.<span class="py-src-parameter">ESMTPClient</span>):
+ <span class="py-src-variable">mailFrom</span> = <span class="py-src-string">&quot;tutorial_sender@example.com&quot;</span>
+ <span class="py-src-variable">mailTo</span> = <span class="py-src-string">&quot;tutorial_recipient@example.net&quot;</span>
+ <span class="py-src-variable">mailData</span> = <span class="py-src-string">'''\
+Date: Fri, 6 Feb 2004 10:14:39 -0800
+From: Tutorial Guy &lt;tutorial_sender@example.com&gt;
+To: Tutorial Gal &lt;tutorial_recipient@example.net&gt;
+Subject: Tutorate!
+
+Hello, how are you, goodbye.
+'''</span>
+</pre>
+
+<p>This statically defined data is accessed later in the class
+definition by three of the methods which are part of the
+<em>SMTPClient callback API</em>. Twisted expects each of the three
+methods below to be defined and to return an object with a particular
+meaning. First, <code>getMailFrom</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailFrom</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">result</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">mailFrom</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">mailFrom</span> = <span class="py-src-variable">None</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">result</span>
+</pre>
+
+<p>This method is called to determine the <em>reverse-path</em>,
+otherwise known as the <em>envelope from</em>, of the message. This
+value will be used when sending the <code>MAIL FROM</code> SMTP
+command. The method must return a string which conforms to the <a href="http://www.faqs.org/rfcs/rfc2821.html" shape="rect">RFC 2821</a> definition
+of a <em>reverse-path</em>. In simpler terms, it should be a string
+like <code>&quot;alice@example.com&quot;</code>. Only one <em>envelope
+from</em> is allowed by the SMTP protocol, so it cannot be a list of
+strings or a comma separated list of addresses. Our implementation
+of <code>getMailFrom</code> does a little bit more than just return a
+string; we'll get back to this in a little bit.</p>
+
+<p>The next method is <code>getMailTo</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailTo</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> [<span class="py-src-variable">self</span>.<span class="py-src-variable">mailTo</span>]
+</pre>
+
+<p><code>getMailTo</code> is similar to <code>getMailFrom</code>. It
+returns one or more RFC 2821 addresses (this time a
+<em>forward-path</em>, or <em>envelope to</em>). Since SMTP allows
+multiple recipients, <code>getMailTo</code> returns a list of these
+addresses. The list must contain at least one address, and even if
+there is exactly one recipient, it must still be in a list.</p>
+
+<p>The final callback we will define to provide information to
+Twisted is <code>getMailData</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailData</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">StringIO</span>.<span class="py-src-variable">StringIO</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">mailData</span>)
+</pre>
+
+<p>This one is quite simple as well: it returns a file or a file-like
+object which contains the message contents. In our case, we return a
+<code>StringIO</code> since we already have a string containing our
+message. If the contents of the file returned by
+<code>getMailData</code> span multiple lines (as email messages often
+do), the lines should be <code>\n</code> delimited (as they would be
+when opening a text file in the <code>&quot;rt&quot;</code> mode): necessary
+newline translation will be performed by <code>SMTPClient</code>
+automatically.</p>
+
+<p>There is one more new callback method defined in smtpclient-7.tac.
+This one isn't for providing information about the messages to
+Twisted, but for Twisted to provide information about the success or
+failure of the message transmission to the application:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">sentMail</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">code</span>, <span class="py-src-parameter">resp</span>, <span class="py-src-parameter">numOk</span>, <span class="py-src-parameter">addresses</span>, <span class="py-src-parameter">log</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Sent'</span>, <span class="py-src-variable">numOk</span>, <span class="py-src-string">'messages'</span>
+</pre>
+
+<p>Each of the arguments to <code>sentMail</code> provides some
+information about the success or failure of the message transmission
+transaction. <code>code</code> is the response code from the ultimate
+command. For successful transactions, it will be 250. For transient
+failures (those which should be retried), it will be between 400 and
+499, inclusive. For permanent failures (this which will never work,
+no matter how many times you retry them), it will be between 500 and
+599.</p>
+
+<h3>SMTP Client 8<a name="auto8"/></h3>
+
+<p>Thus far we have succeeded in creating a Twisted client application
+which starts up, connects to a (possibly) remote host, transmits some
+data, and disconnects. Notably missing, however, is application
+shutdown. Hitting ^C is fine during development, but it's not exactly
+a long-term solution. Fortunately, programmatic shutdown is extremely
+simple. <a href="smtpclient-8.tac" shape="rect">smtpclient-8.tac</a> extends
+<code>sentMail</code> with these two lines:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+</pre>
+
+<p>The <code>stop</code> method of the reactor causes the main event
+loop to exit, allowing a Twisted server to shut down. With this
+version of the example, we see that the program actually terminates
+after sending the message, without user-intervention:</p>
+
+<pre class="shell" xml:space="preserve">
+exarkun@boson:~/doc/mail/tutorial/smtpclient$ twistd -ny smtpclient-8.tac
+19:52 EST [-] Log opened.
+19:52 EST [-] twistd SVN-Trunk (/usr/bin/python2.4 2.4.1) starting up
+19:52 EST [-] reactor class: twisted.internet.selectreactor.SelectReactor
+19:52 EST [-] Loading smtpclient-8.tac...
+19:52 EST [-] Loaded.
+19:52 EST [-] Starting factory &lt;__builtin__.SMTPClientFactory instance
+ at 0xb791beec&gt;
+19:52 EST [-] Enabling Multithreading.
+19:52 EST [SMTPTutorialClient,client] Sent 1 messages
+19:52 EST [SMTPTutorialClient,client] Stopping factory
+ &lt;__builtin__.SMTPClientFactory instance at 0xb791beec&gt;
+19:52 EST [-] Main loop terminated.
+19:52 EST [-] Server Shut Down.
+exarkun@boson:~/doc/mail/tutorial/smtpclient$
+</pre>
+
+<h3>SMTP Client 9<a name="auto9"/></h3>
+
+<p>One task remains to be completed in this tutorial SMTP client:
+instead of always sending mail through a well-known host, we will look
+up the mail exchange server for the recipient address and try to
+deliver the message to that host.</p>
+
+<p>In <a href="smtpclient-9.tac" shape="rect">smtpclient-9.tac</a>, we'll take the
+first step towards this feature by defining a function which returns
+the mail exchange host for a particular domain:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailExchange</span>(<span class="py-src-parameter">host</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'localhost'</span>
+</pre>
+
+<p>Obviously this doesn't return the correct mail exchange host yet
+(in fact, it returns the exact same host we have been using all
+along), but pulling out the logic for determining which host to
+connect to into a function like this is the first step towards our
+ultimate goal. Now that we have <code>getMailExchange</code>, we'll
+call it when constructing our <code>TCPClient</code> service:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(
+ <span class="py-src-variable">getMailExchange</span>(<span class="py-src-string">'example.net'</span>), <span class="py-src-number">25</span>, <span class="py-src-variable">smtpClientFactory</span>)
+</pre>
+
+<p>We'll expand on the definition of <code>getMailExchange</code> in
+the next example.</p>
+
+<h3>SMTP Client 10<a name="auto10"/></h3>
+
+<p>In the previous example we defined <code>getMailExchange</code> to
+return a string representing the mail exchange host for a particular
+domain. While this was a step in the right direction, it turns out
+not to be a very big one. Determining the mail exchange host for a
+particular domain is going to involve network traffic (specifically,
+some DNS requests). These might take an arbitrarily large amount of
+time, so we need to introduce a <code>Deferred</code> to represent the
+result of <code>getMailExchange</code>. <a href="smtpclient-10.tac" shape="rect">smtpclient-10.tac</a> redefines it
+thusly:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailExchange</span>(<span class="py-src-parameter">host</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">defer</span>.<span class="py-src-variable">succeed</span>(<span class="py-src-string">'localhost'</span>)
+</pre>
+
+<p><code>defer.succeed</code> is a function which creates a new
+<code>Deferred</code> which already has a result, in this case
+<code>'localhost'</code>. Now we need to adjust our
+<code>TCPClient</code>-constructing code to expect and properly handle
+this <code>Deferred</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">cbMailExchange</span>(<span class="py-src-parameter">exchange</span>):
+ <span class="py-src-variable">smtpClientFactory</span> = <span class="py-src-variable">SMTPClientFactory</span>()
+
+ <span class="py-src-variable">smtpClientService</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPClient</span>(<span class="py-src-variable">exchange</span>, <span class="py-src-number">25</span>, <span class="py-src-variable">smtpClientFactory</span>)
+ <span class="py-src-variable">smtpClientService</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+
+<span class="py-src-variable">getMailExchange</span>(<span class="py-src-string">'example.net'</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbMailExchange</span>)
+</pre>
+
+<p>An in-depth exploration of <code>Deferred</code>s is beyond the
+scope of this document. For such a look, see
+the <a href="../../../core/howto/defer.html" shape="rect">Deferred Reference</a>.
+However, in brief, what this version of the code does is to delay the
+creation of the <code>TCPClient</code> until the
+<code>Deferred</code> returned by <code>getMailExchange</code> fires.
+Once it does, we proceed normally through the creation of our
+<code>SMTPClientFactory</code> and <code>TCPClient</code>, as well as
+set the <code>TCPClient</code>'s service parent, just as we did in the
+previous examples.</p>
+
+<h3>SMTP Client 11<a name="auto11"/></h3>
+
+<p>At last we're ready to perform the mail exchange lookup. We do
+this by calling on an object provided specifically for this task,
+<code>twisted.mail.relaymanager.MXCalculator</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">getMailExchange</span>(<span class="py-src-parameter">host</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">cbMX</span>(<span class="py-src-parameter">mxRecord</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">str</span>(<span class="py-src-variable">mxRecord</span>.<span class="py-src-variable">exchange</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">relaymanager</span>.<span class="py-src-variable">MXCalculator</span>().<span class="py-src-variable">getMX</span>(<span class="py-src-variable">host</span>).<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbMX</span>)
+</pre>
+
+<p>Because <code>getMX</code> returns a <code>Record_MX</code> object
+rather than a string, we do a little bit of post-processing to get the
+results we want. We have already converted the rest of the tutorial
+application to expect a <code>Deferred</code> from
+<code>getMailExchange</code>, so no further changes are required. <a href="smtpclient-11.tac" shape="rect">smtpclient-11.tac</a> completes this tutorial
+by being able to both look up the mail exchange host for the recipient
+domain, connect to it, complete an SMTP transaction, report its
+results, and finally shut down the reactor.</p>
+
+
+</div>
+
+ <p><a href="../../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-1.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-1.tac
new file mode 100644
index 0000000000..4804723a94
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-1.tac
@@ -0,0 +1,3 @@
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-2.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-2.tac
new file mode 100644
index 0000000000..00f143ac2b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-2.tac
@@ -0,0 +1,10 @@
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpServerFactory = protocol.ServerFactory()
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-3.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-3.tac
new file mode 100644
index 0000000000..5ff3cb5b09
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-3.tac
@@ -0,0 +1,12 @@
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpServerFactory = protocol.ServerFactory()
+smtpServerFactory.protocol = protocol.Protocol
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-4.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-4.tac
new file mode 100644
index 0000000000..a8ee09ea7f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-4.tac
@@ -0,0 +1,14 @@
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol
+
+smtpServerFactory = protocol.ServerFactory()
+
+from twisted.mail import smtp
+smtpServerFactory.protocol = smtp.ESMTP
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-5.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-5.tac
new file mode 100644
index 0000000000..6f3a961538
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-5.tac
@@ -0,0 +1,50 @@
+import os
+from zope.interface import implements
+
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol, defer
+
+smtpServerFactory = protocol.ServerFactory()
+
+from twisted.mail import smtp
+
+class FileMessage(object):
+ implements(smtp.IMessage)
+
+ def __init__(self, fileObj):
+ self.fileObj = fileObj
+
+ def lineReceived(self, line):
+ self.fileObj.write(line + '\n')
+
+ def eomReceived(self):
+ self.fileObj.close()
+ return defer.succeed(None)
+
+ def connectionLost(self):
+ self.fileObj.close()
+ os.remove(self.fileObj.name)
+
+class TutorialESMTP(smtp.ESMTP):
+ counter = 0
+
+ def validateTo(self, user):
+ fileName = 'tutorial-smtp.' + str(self.counter)
+ self.counter += 1
+ return lambda: FileMessage(file(fileName, 'w'))
+
+ def validateFrom(self, helo, origin):
+ return origin
+
+ def receivedHeader(self, helo, origin, recipients):
+ return 'Received: Tutorially.'
+
+
+smtpServerFactory.protocol = TutorialESMTP
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-6.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-6.tac
new file mode 100644
index 0000000000..5924384a9b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-6.tac
@@ -0,0 +1,57 @@
+import os
+from zope.interface import implements
+
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol, defer
+
+smtpServerFactory = protocol.ServerFactory()
+
+from twisted.mail import smtp
+
+class FileMessage(object):
+ implements(smtp.IMessage)
+
+ def __init__(self, fileObj):
+ self.fileObj = fileObj
+
+ def lineReceived(self, line):
+ self.fileObj.write(line + '\n')
+
+ def eomReceived(self):
+ self.fileObj.close()
+ return defer.succeed(None)
+
+ def connectionLost(self):
+ self.fileObj.close()
+ os.remove(self.fileObj.name)
+
+class TutorialESMTP(smtp.ESMTP):
+ counter = 0
+
+ def validateTo(self, user):
+ fileName = 'tutorial-smtp.' + str(self.counter)
+ self.counter += 1
+ return lambda: FileMessage(file(fileName, 'w'))
+
+ def validateFrom(self, helo, origin):
+ return origin
+
+ def receivedHeader(self, helo, origin, recipients):
+ return 'Received: Tutorially.'
+
+class TutorialESMTPFactory(protocol.ServerFactory):
+ protocol = TutorialESMTP
+
+ def buildProtocol(self, addr):
+ p = self.protocol()
+ p.factory = self
+ return p
+
+smtpServerFactory.protocol = TutorialESMTP
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-7.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-7.tac
new file mode 100644
index 0000000000..db98032e58
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-7.tac
@@ -0,0 +1,57 @@
+import os
+from zope.interface import implements
+
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol, defer
+
+from twisted.mail import smtp
+
+class FileMessage(object):
+ implements(smtp.IMessage)
+
+ def __init__(self, fileObj):
+ self.fileObj = fileObj
+
+ def lineReceived(self, line):
+ self.fileObj.write(line + '\n')
+
+ def eomReceived(self):
+ self.fileObj.close()
+ return defer.succeed(None)
+
+ def connectionLost(self):
+ self.fileObj.close()
+ os.remove(self.fileObj.name)
+
+class TutorialDelivery(object):
+ implements(smtp.IMessageDelivery)
+ counter = 0
+
+ def validateTo(self, user):
+ fileName = 'tutorial-smtp.' + str(self.counter)
+ self.counter += 1
+ return lambda: FileMessage(file(fileName, 'w'))
+
+ def validateFrom(self, helo, origin):
+ return origin
+
+ def receivedHeader(self, helo, origin, recipients):
+ return 'Received: Tutorially.'
+
+class TutorialESMTPFactory(protocol.ServerFactory):
+ protocol = smtp.ESMTP
+
+ def buildProtocol(self, addr):
+ p = self.protocol()
+ p.delivery = TutorialDelivery()
+ p.factory = self
+ return p
+
+smtpServerFactory = TutorialESMTPFactory()
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-8.tac b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-8.tac
new file mode 100644
index 0000000000..6133912a99
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/mail/tutorial/smtpserver/smtpserver-8.tac
@@ -0,0 +1,63 @@
+import os
+from zope.interface import implements
+
+from twisted.application import service
+
+application = service.Application("SMTP Server Tutorial")
+
+from twisted.application import internet
+from twisted.internet import protocol, defer
+
+from twisted.mail import smtp
+
+class FileMessage(object):
+ implements(smtp.IMessage)
+
+ def __init__(self, fileObj):
+ self.fileObj = fileObj
+
+ def lineReceived(self, line):
+ self.fileObj.write(line + '\n')
+
+ def eomReceived(self):
+ self.fileObj.close()
+ return defer.succeed(None)
+
+ def connectionLost(self):
+ self.fileObj.close()
+ os.remove(self.fileObj.name)
+
+class TutorialDelivery(object):
+ implements(smtp.IMessageDelivery)
+ counter = 0
+
+ def validateTo(self, user):
+ fileName = 'tutorial-smtp.' + str(self.counter)
+ self.counter += 1
+ return lambda: FileMessage(file(fileName, 'w'))
+
+ def validateFrom(self, helo, origin):
+ return origin
+
+ def receivedHeader(self, helo, origin, recipients):
+ return 'Received: Tutorially.'
+
+class TutorialDeliveryFactory(object):
+ implements(smtp.IMessageDeliveryFactory)
+
+ def getMessageDelivery(self):
+ return TutorialDelivery()
+
+class TutorialESMTPFactory(protocol.ServerFactory):
+ protocol = smtp.ESMTP
+
+ def buildProtocol(self, addr):
+ p = self.protocol()
+ p.deliveryFactory = TutorialDeliveryFactory()
+ p.factory = self
+ return p
+
+smtpServerFactory = TutorialESMTPFactory()
+
+smtpServerService = internet.TCPServer(2025, smtpServerFactory)
+smtpServerService.setServiceParent(application)
diff --git a/vendor/Twisted-10.0.0/doc/names/examples/dns-service.py b/vendor/Twisted-10.0.0/doc/names/examples/dns-service.py
new file mode 100755
index 0000000000..f8a1dff242
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/names/examples/dns-service.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Sample app to lookup SRV records in DNS.
+"""
+
+from twisted.names import client
+from twisted.internet import reactor
+import sys
+
+def printAnswer((answers, auth, add)):
+ if not len(answers):
+ print 'No answers'
+ else:
+ print '\n'.join([str(x.payload) for x in answers])
+ reactor.stop()
+
+def printFailure(arg):
+ print "error: could not resolve:", arg
+ reactor.stop()
+
+try:
+ service, proto, domain = sys.argv[1:]
+except ValueError:
+ sys.stderr.write('%s: usage:\n' % sys.argv[0] +
+ ' %s SERVICE PROTO DOMAIN\n' % sys.argv[0])
+ sys.exit(1)
+
+resolver = client.Resolver('/etc/resolv.conf')
+d = resolver.lookupService('_%s._%s.%s' % (service, proto, domain), [1])
+d.addCallbacks(printAnswer, printFailure)
+
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/names/examples/gethostbyname.py b/vendor/Twisted-10.0.0/doc/names/examples/gethostbyname.py
new file mode 100755
index 0000000000..efcdeee6e0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/names/examples/gethostbyname.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+from twisted.names import client
+from twisted.internet import reactor
+
+def gotResult(result):
+ print result
+ reactor.stop()
+
+def gotFailure(failure):
+ failure.printTraceback()
+ reactor.stop()
+
+d = client.getHostByName(sys.argv[1])
+d.addCallbacks(gotResult, gotFailure)
+
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/names/examples/index.html b/vendor/Twisted-10.0.0/doc/names/examples/index.html
new file mode 100644
index 0000000000..6637a1acdc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/names/examples/index.html
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Names code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Names code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">DNS (Twisted Names)</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>DNS (Twisted Names)<a name="auto0"/></h2>
+ <ul>
+ <li><a href="testdns.py" shape="rect">testdns.py</a></li>
+ <li><a href="dns-service.py" shape="rect">dns-service.py</a></li>
+ <li><a href="gethostbyname.py" shape="rect">gethostbyname.py</a></li>
+ </ul>
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/names/examples/testdns.py b/vendor/Twisted-10.0.0/doc/names/examples/testdns.py
new file mode 100644
index 0000000000..03e28849ec
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/names/examples/testdns.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+from twisted.names import client
+from twisted.internet import reactor
+from twisted.names import dns
+
+r = client.Resolver('/etc/resolv.conf')
+
+def gotAddress(a):
+ print 'Addresses: ', ', '.join(map(str, a))
+
+def gotMails(a):
+ print 'Mail Exchangers: ', ', '.join(map(str, a))
+
+def gotNameservers(a):
+ print 'Nameservers: ', ', '.join(map(str, a))
+
+def gotError(f):
+ print 'gotError'
+ f.printTraceback()
+
+ from twisted.internet import reactor
+ reactor.stop()
+
+
+if __name__ == '__main__':
+ import sys
+
+ r.lookupAddress(sys.argv[1]).addCallback(gotAddress).addErrback(gotError)
+ r.lookupMailExchange(sys.argv[1]).addCallback(gotMails).addErrback(gotError)
+ r.lookupNameservers(sys.argv[1]).addCallback(gotNameservers).addErrback(gotError)
+
+ reactor.callLater(4, reactor.stop)
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/names/howto/index.html b/vendor/Twisted-10.0.0/doc/names/howto/index.html
new file mode 100644
index 0000000000..067e202f06
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/names/howto/index.html
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Names Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Names Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+ <li><a href="names.html" shape="rect">Names DNS library</a></li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/names/howto/listings/names/example-domain.com b/vendor/Twisted-10.0.0/doc/names/howto/listings/names/example-domain.com
new file mode 100644
index 0000000000..e7200197d4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/names/howto/listings/names/example-domain.com
@@ -0,0 +1,37 @@
+
+zone = [
+ SOA(
+ # For whom we are the authority
+ 'example-domain.com',
+
+ # This nameserver's name
+ mname = "ns1.example-domain.com",
+
+ # Mailbox of individual who handles this
+ rname = "root.example-domain.com",
+
+ # Unique serial identifying this SOA data
+ serial = 2003010601,
+
+ # Time interval before zone should be refreshed
+ refresh = "1H",
+
+ # Interval before failed refresh should be retried
+ retry = "1H",
+
+ # Upper limit on time interval before expiry
+ expire = "1H",
+
+ # Minimum TTL
+ minimum = "1H"
+ ),
+
+ A('example-domain.com', '127.0.0.1'),
+ NS('example-domain.com', 'ns1.example-domain.com'),
+
+ CNAME('www.example-domain.com', 'example-domain.com'),
+ CNAME('ftp.example-domain.com', 'example-domain.com'),
+
+ MX('example-domain.com', 0, 'mail.example-domain.com'),
+ A('mail.example-domain.com', '123.0.16.43')
+]
diff --git a/vendor/Twisted-10.0.0/doc/names/howto/names.html b/vendor/Twisted-10.0.0/doc/names/howto/names.html
new file mode 100644
index 0000000000..1b1936ba43
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/names/howto/names.html
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Creating and working with a names (DNS) server</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Creating and working with a names (DNS) server</h1>
+ <div class="toc"><ol><li><a href="#auto0">Creating a non-authoritative server</a></li><li><a href="#auto1">Creating an authoritative server</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<p>A Names server can be perform three basic operations:</p>
+
+<ul>
+<li>act as a recursive server, forwarding queries to other servers</li>
+<li>perform local caching of recursively discovered records</li>
+<li>act as the authoritative server for a domain</li>
+</ul>
+
+<h2>Creating a non-authoritative server<a name="auto0"/></h2>
+
+<p>
+The first two of these are easy, and you can create a server that performs them
+with the command <code class="shell">twistd -n dns --recursive --cache</code>.
+You may wish to run this as root since it will try to bind to UDP port 53. Try
+performing a lookup with it, <code class="shell">dig twistedmatrix.com
+@127.0.0.1</code>.
+</p>
+
+<h2>Creating an authoritative server<a name="auto1"/></h2>
+
+<p>To act as the authority for a domain, two things are necessary: the address
+of the machine on which the domain name server will run must be registered
+as a nameserver for the domain; and the domain name server must be
+configured to act as the authority. The first requirement is beyond the
+scope of this howto and will not be covered.
+</p>
+
+<p>To configure Names to act as the authority for
+<code>example-domain.com</code>, we first create a
+zone file for this domain.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+</p><span class="py-src-variable">zone</span> = [
+ <span class="py-src-variable">SOA</span>(
+ <span class="py-src-comment"># For whom we are the authority</span>
+ <span class="py-src-string">'example-domain.com'</span>,
+
+ <span class="py-src-comment"># This nameserver's name</span>
+ <span class="py-src-variable">mname</span> = <span class="py-src-string">&quot;ns1.example-domain.com&quot;</span>,
+
+ <span class="py-src-comment"># Mailbox of individual who handles this</span>
+ <span class="py-src-variable">rname</span> = <span class="py-src-string">&quot;root.example-domain.com&quot;</span>,
+
+ <span class="py-src-comment"># Unique serial identifying this SOA data</span>
+ <span class="py-src-variable">serial</span> = <span class="py-src-number">2003010601</span>,
+
+ <span class="py-src-comment"># Time interval before zone should be refreshed</span>
+ <span class="py-src-variable">refresh</span> = <span class="py-src-string">&quot;1H&quot;</span>,
+
+ <span class="py-src-comment"># Interval before failed refresh should be retried</span>
+ <span class="py-src-variable">retry</span> = <span class="py-src-string">&quot;1H&quot;</span>,
+
+ <span class="py-src-comment"># Upper limit on time interval before expiry</span>
+ <span class="py-src-variable">expire</span> = <span class="py-src-string">&quot;1H&quot;</span>,
+
+ <span class="py-src-comment"># Minimum TTL</span>
+ <span class="py-src-variable">minimum</span> = <span class="py-src-string">&quot;1H&quot;</span>
+ ),
+
+ <span class="py-src-variable">A</span>(<span class="py-src-string">'example-domain.com'</span>, <span class="py-src-string">'127.0.0.1'</span>),
+ <span class="py-src-variable">NS</span>(<span class="py-src-string">'example-domain.com'</span>, <span class="py-src-string">'ns1.example-domain.com'</span>),
+
+ <span class="py-src-variable">CNAME</span>(<span class="py-src-string">'www.example-domain.com'</span>, <span class="py-src-string">'example-domain.com'</span>),
+ <span class="py-src-variable">CNAME</span>(<span class="py-src-string">'ftp.example-domain.com'</span>, <span class="py-src-string">'example-domain.com'</span>),
+
+ <span class="py-src-variable">MX</span>(<span class="py-src-string">'example-domain.com'</span>, <span class="py-src-number">0</span>, <span class="py-src-string">'mail.example-domain.com'</span>),
+ <span class="py-src-variable">A</span>(<span class="py-src-string">'mail.example-domain.com'</span>, <span class="py-src-string">'123.0.16.43'</span>)
+]
+</pre><div class="caption">Zone file - <a href="listings/names/example-domain.com"><span class="filename">listings/names/example-domain.com</span></a></div></div>
+
+<p>Next, run the command <code class="shell">twistd -n dns --pyzone
+example-domain.com</code>. Now try querying the domain locally (again, with
+dig): <code class="shell">dig -t any example-domain.com @127.0.0.1</code>.
+</p>
+
+<p>Names can also read a traditional, BIND-syntax zone file. Specify these
+with the <code>--bindzone</code> parameter. The $GENERATE and $INCLUDE
+directives are not yet supported.
+</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/names/index.html b/vendor/Twisted-10.0.0/doc/names/index.html
new file mode 100644
index 0000000000..427a0ec56e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/names/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Names Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Names Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Names to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Names</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/pair/examples/index.html b/vendor/Twisted-10.0.0/doc/pair/examples/index.html
new file mode 100644
index 0000000000..59047063be
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/pair/examples/index.html
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">Miscellaenous</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Miscellaenous<a name="auto0"/></h2>
+ <ul>
+ <li><a href="pairudp.py" shape="rect">pairudp.py</a> - UDP implemented with a TUN/TAP device</li>
+ </ul>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/pair/examples/pairudp.py b/vendor/Twisted-10.0.0/doc/pair/examples/pairudp.py
new file mode 100644
index 0000000000..a6149d1c0b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/pair/examples/pairudp.py
@@ -0,0 +1,18 @@
+from twisted.internet import reactor, protocol
+from twisted.pair import ethernet, rawudp, ip
+from twisted.pair import tuntap
+
+class MyProto(protocol.DatagramProtocol):
+ def datagramReceived(self, *a, **kw):
+ print a, kw
+
+p_udp = rawudp.RawUDPProtocol()
+p_udp.addProto(42, MyProto())
+p_ip = ip.IPProtocol()
+p_ip.addProto(17, p_udp)
+p_eth = ethernet.EthernetProtocol()
+p_eth.addProto(0x800, p_ip)
+
+reactor.listenWith(tuntap.TuntapPort,
+ interface='tap0', proto=p_eth, reactor=reactor)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/pair/howto/index.html b/vendor/Twisted-10.0.0/doc/pair/howto/index.html
new file mode 100644
index 0000000000..43e003d1dd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/pair/howto/index.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Pair Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Pair Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+
+<li>Twisted Pair Documentation
+ <ul>
+ <li><a href="twisted-pair.html" shape="rect">Twisted Pair: Low-level networking</a></li>
+ </ul>
+</li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/pair/howto/twisted-pair.html b/vendor/Twisted-10.0.0/doc/pair/howto/twisted-pair.html
new file mode 100644
index 0000000000..f523f23cf3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/pair/howto/twisted-pair.html
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Pair: Low-level Networking</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Pair: Low-level Networking</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview of classes</a></li><ul><li><a href="#auto1">Transports</a></li><li><a href="#auto2">Protocols</a></li><li><a href="#auto3">Interfaces</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<p>Twisted can do low-level networking, too.</p>
+
+<p>Here's an example that tries to show the relationships of different
+classes and how data could flow for receiving packets.</p>
+
+<pre xml:space="preserve">
+FileWrapper
+ |
+ v
+PcapProtocol TuntapPort
+ | |
+ +------------+
+ v
+EthernetProtocol
+ |
+ +------------+-----------+---...
+ v v v
+IPProtocol ARPProtocol IPv6Protocol
+ |
+ +-------------+----------------+---...
+ v v v
+RawUDPProtocol RawICMPProtocol RawTCPProtocol
+ |
+ v
+DatagramProtocol
+</pre>
+
+<p>Of course, for writing, the picture would look pretty much
+identical, except all arrows would be reversed.</p>
+
+<h2>Overview of classes<a name="auto0"/></h2>
+
+<p>TODO</p>
+
+<h3>Transports<a name="auto1"/></h3>
+
+<p>TODO</p>
+
+<ul>
+<li>TuntapPort: TODO</li>
+</ul>
+
+<h3>Protocols<a name="auto2"/></h3>
+
+<p>TODO</p>
+
+<ul>
+<li>EthernetProtocol: TODO</li>
+<li>IPProtocol: TODO</li>
+<li>RawUDPProtocol: TODO</li>
+</ul>
+
+<h3>Interfaces<a name="auto3"/></h3>
+
+<p>TODO</p>
+
+<ul>
+<li>IRawDatagramProtocol: TODO</li>
+<li>IRawPacketProtocol: TODO</li>
+</ul>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/pair/index.html b/vendor/Twisted-10.0.0/doc/pair/index.html
new file mode 100644
index 0000000000..9df58f8518
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/pair/index.html
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Pair Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Pair Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Pair to develop your own applications</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/advogato.py b/vendor/Twisted-10.0.0/doc/web/examples/advogato.py
new file mode 100644
index 0000000000..3ba33a4e62
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/advogato.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+'''
+Usage:
+advogato.py <name> <diary entry file>
+'''
+
+from twisted.web.xmlrpc import Proxy
+from twisted.internet import reactor
+from getpass import getpass
+import sys
+
+class AddDiary:
+
+ def __init__(self, name, password):
+ self.name = name
+ self.password = password
+ self.proxy = Proxy('http://advogato.org/XMLRPC')
+
+ def __call__(self, filename):
+ self.data = open(filename).read()
+ d = self.proxy.callRemote('authenticate', self.name, self.password)
+ d.addCallbacks(self.login, self.noLogin)
+
+ def noLogin(self, reason):
+ print "could not login"
+ reactor.stop()
+
+ def login(self, cookie):
+ d = self.proxy.callRemote('diary.set', cookie, -1, self.data)
+ d.addCallbacks(self.setDiary, self.errorSetDiary)
+
+ def setDiary(self, response):
+ reactor.stop()
+
+ def errorSetDiary(self, error):
+ print "could not set diary", error
+ reactor.stop()
+
+diary = AddDiary(sys.argv[1], getpass())
+diary(sys.argv[2])
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/dlpage.py b/vendor/Twisted-10.0.0/doc/web/examples/dlpage.py
new file mode 100644
index 0000000000..d1c9f05012
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/dlpage.py
@@ -0,0 +1,9 @@
+from twisted.internet import reactor
+from twisted.web.client import downloadPage
+from twisted.python.util import println
+import sys
+
+downloadPage(sys.argv[1], "foo").addCallbacks(
+ lambda value:reactor.stop(),
+ lambda error:(println("an error occurred",error),reactor.stop()))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/fortune.rpy.py b/vendor/Twisted-10.0.0/doc/web/examples/fortune.rpy.py
new file mode 100644
index 0000000000..10e7be344f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/fortune.rpy.py
@@ -0,0 +1,17 @@
+from twisted.web.resource import Resource
+from twisted.web import server
+from twisted.internet import utils
+from twisted.python import util
+
+class FortuneResource(Resource):
+
+ def render_GET(self, request):
+ request.write("<pre>\n")
+ deferred = utils.getProcessOutput("/usr/games/fortune")
+ deferred.addCallback(lambda s:
+ (request.write(s+"\n"), request.finish()))
+ deferred.addErrback(lambda s:
+ (request.write(str(s)), request.finish()))
+ return server.NOT_DONE_YET
+
+resource = FortuneResource()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/getpage.py b/vendor/Twisted-10.0.0/doc/web/examples/getpage.py
new file mode 100644
index 0000000000..42e4f90c5b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/getpage.py
@@ -0,0 +1,9 @@
+from twisted.internet import reactor
+from twisted.web.client import getPage
+from twisted.python.util import println
+import sys
+
+getPage(sys.argv[1]).addCallbacks(
+ callback=lambda value:(println(value),reactor.stop()),
+ errback=lambda error:(println("an error occurred", error),reactor.stop()))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/google.py b/vendor/Twisted-10.0.0/doc/web/examples/google.py
new file mode 100644
index 0000000000..7731f55a1a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/google.py
@@ -0,0 +1,9 @@
+from twisted.web.google import checkGoogle
+from twisted.python.util import println
+from twisted.internet import reactor
+import sys
+
+checkGoogle(sys.argv[1:]).addCallbacks(
+ lambda l:(println(l),reactor.stop()),
+ lambda e:(println('error:',e),reactor.stop()))
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/hello.rpy.py b/vendor/Twisted-10.0.0/doc/web/examples/hello.rpy.py
new file mode 100644
index 0000000000..aa9c912ce9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/hello.rpy.py
@@ -0,0 +1,28 @@
+# This is a resource file, which generates some useful
+# information
+# To use it, rename it to "hello.rpy" and put it in the path of
+# any normally-configured Twisted web server.
+
+from twisted.web import static
+import time
+
+now = time.ctime()
+
+d = '''\
+<HTML><HEAD><TITLE>Hello Rpy</TITLE>
+
+<H1>Hello World, It is Now %(now)s</H1>
+
+<UL>
+''' % vars()
+
+for i in range(10):
+ d += "<LI>%(i)s" % vars()
+
+d += '''\
+</UL>
+
+</BODY></HTML>
+'''
+
+resource = static.Data(d, 'text/html')
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/httpclient.py b/vendor/Twisted-10.0.0/doc/web/examples/httpclient.py
new file mode 100644
index 0000000000..899220e0dd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/httpclient.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+from pprint import pprint
+
+from twisted import version
+from twisted.python import log
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
+from twisted.internet.protocol import Protocol
+from twisted.web.iweb import UNKNOWN_LENGTH
+from twisted.web.http_headers import Headers
+from twisted.web.client import Agent, ResponseDone
+
+
+class WriteToStdout(Protocol):
+ def connectionMade(self):
+ self.onConnLost = Deferred()
+
+ def dataReceived(self, data):
+ print 'Got some:', data
+
+ def connectionLost(self, reason):
+ if not reason.check(ResponseDone):
+ reason.printTraceback()
+ else:
+ print 'Response done'
+ self.onConnLost.callback(None)
+
+
+def main(reactor, url):
+ userAgent = 'Twisted/%s (httpclient.py)' % (version.short(),)
+ agent = Agent(reactor)
+ d = agent.request(
+ 'GET', url, Headers({'user-agent': [userAgent]}))
+ def cbResponse(response):
+ pprint(vars(response))
+ proto = WriteToStdout()
+ if response.length is not UNKNOWN_LENGTH:
+ print 'The response body will consist of', response.length, 'bytes.'
+ else:
+ print 'The response body length is unknown.'
+ response.deliverBody(proto)
+ return proto.onConnLost
+ d.addCallback(cbResponse)
+ d.addErrback(log.err)
+ d.addBoth(lambda ign: reactor.callWhenRunning(reactor.stop))
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main(reactor, *sys.argv[1:])
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/index.html b/vendor/Twisted-10.0.0/doc/web/examples/index.html
new file mode 100644
index 0000000000..0d5fcca4ac
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/index.html
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Web code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Web code examples</h1>
+ <div class="toc"><ol><li><a href="#auto0">twisted.web.client</a></li><li><a href="#auto1">XML-RPC</a></li><li><a href="#auto2">Virtual hosts and proxies</a></li><li><a href="#auto3">.rpys and ResourceTemplate</a></li><li><a href="#auto4">Miscellaneous</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>twisted.web.client<a name="auto0"/></h2>
+ <ul>
+ <li><a href="getpage.py" shape="rect">getpage.py</a> - use
+ <code>twisted.web.client.getPage</code> to download a web
+ page.</li>
+ <li><a href="dlpage.py" shape="rect">dlpage.py</a> - add callbacks to
+ <code>twisted.web.client.downloadPage</code> to display errors
+ that occur when downloading a web page</li>
+ </ul>
+
+ <h2>XML-RPC<a name="auto1"/></h2>
+ <ul>
+ <li><a href="xmlrpc.py" shape="rect">xmlrpc.py</a> XML-RPC server with
+ several methods, including echoing, faulting, returning
+ deferreds and failed deferreds</li>
+ <li><a href="xmlrpcclient.py" shape="rect">xmlrpcclient.py</a> - use
+ <code>twisted.web.xmlrpc.Proxy</code> to call remote XML-RPC
+ methods</li>
+ <li><a href="advogato.py" shape="rect">advogato.py</a> - use
+ <code>twisted.web.xmlrpc</code> to post a diary entry to
+ advogato.org; requires an advogato account</li>
+ </ul>
+
+ <h2>Virtual hosts and proxies<a name="auto2"/></h2>
+ <ul>
+ <li><a href="rootscript.py" shape="rect">rootscript.py</a> - example use of
+ <code>twisted.web.vhost.NameVirtualHost</code></li>
+ <li><a href="web.py" shape="rect">web.py</a> - an example of both using the
+ <code>processors</code> attribute to set how certain file types
+ are treated and using
+ <code>twisted.web.vhost.VHostMonsterResource</code> to reverse
+ proxy</li>
+ <li><a href="proxy.py" shape="rect">proxy.py</a> - use
+ <code>twisted.web.proxy</code> to make any HTTP request to the
+ proxy port get applied to a specified website</li>
+ </ul>
+
+ <h2>.rpys and ResourceTemplate<a name="auto3"/></h2>
+ <ul>
+ <li><a href="hello.rpy.py" shape="rect">hello.rpy.py</a> - use
+ <code>twisted.web.static</code> to create a static resource to
+ serve</li>
+ <li><a href="fortune.rpy.py" shape="rect">fortune.rpy.py</a> - create a
+ resource that returns the output of a process run on the
+ server</li>
+ <li><a href="lj.rpy.py" shape="rect">lj.rpy.py</a> - use
+ <code>twisted.web.microdom</code>,
+ <code>twisted.web.domhelpers</code>, and chained callbacks to
+ extract and display parts of a livejournal user's rss page</li>
+ <li><a href="vhost.rpy.py" shape="rect">vhost.rpy.py</a> - make a
+ <code>twisted.web.vhost.VHostMonsterResource</code> resource
+ </li>
+ <li><a href="report.rpy.py" shape="rect">report.rpy.py</a> - display
+ various properties of a resource, including path, host, and
+ port</li>
+ <li><a href="users.rpy.py" shape="rect">users.rpy.py</a> - use
+ <code>twisted.web.distrib</code> to publish user directories
+ as for a &quot;community web site&quot;</li>
+ <li><a href="simple.rtl" shape="rect">simple.rtl</a> - example use of
+ <code>twisted.web.resource.ResourceTemplate</code></li>
+ </ul>
+
+ <h2>Miscellaneous<a name="auto4"/></h2>
+ <ul>
+ <li><a href="webguard.py" shape="rect">webguard.py</a> - pairing
+ <code>twisted.web</code> with <code>twisted.cred</code> to
+ guard resources against unauthenticated users</li>
+ <li><a href="silly-web.py" shape="rect">silly-web.py</a> - bare-bones
+ distributed web setup with a master and slave using
+ <code>twisted.web.distrib</code> and
+ <code>twisted.spread.pb</code></li>
+ <li><a href="google.py" shape="rect">google.py</a> - use
+ <code>twisted.web.google</code> to get the I'm Feeling Lucky
+ page for a search term</li>
+ <li><a href="soap.py" shape="rect">soap.py</a> - use
+ <code>twisted.web.soap</code> to publish SOAP methods</li>
+ </ul>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/lj.rpy.py b/vendor/Twisted-10.0.0/doc/web/examples/lj.rpy.py
new file mode 100644
index 0000000000..796ca37d48
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/lj.rpy.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+# Syndicate LiveJournal users
+# Demonstrates how to use chained callbacks
+from __future__ import nested_scopes
+
+from twisted.web import resource as resourcelib
+from twisted.web import client, microdom, domhelpers, server
+
+urlTemplate = 'http://www.livejournal.com/users/%s/rss'
+
+class LJSyndicatingResource(resourcelib.Resource):
+
+ def render_GET(self, request):
+ url = urlTemplate % request.args['user'][0]
+ client.getPage(url).addCallback(
+ microdom.parseString).addCallback(
+ lambda t: domhelpers.findNodesNamed(t, 'item')).addCallback(
+ lambda itms: zip([domhelpers.findNodesNamed(x, 'title')[0]
+ for x in itms],
+ [domhelpers.findNodesNamed(x, 'link')[0]
+ for x in itms]
+ )).addCallback(
+ lambda itms: '<html><head></head><body><ul>%s</ul></body></html>' %
+ '\n'.join(
+ ['<li><a href="%s">%s</a></li>' % (
+ domhelpers.getNodeText(link), domhelpers.getNodeText(title))
+ for (title, link) in itms])
+ ).addCallback(lambda s: (request.write(s),request.finish())).addErrback(
+ lambda e: (request.write('Error: %s' % e),request.finish()))
+ return server.NOT_DONE_YET
+
+resource = LJSyndicatingResource()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/proxy.py b/vendor/Twisted-10.0.0/doc/web/examples/proxy.py
new file mode 100644
index 0000000000..47d4dae650
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/proxy.py
@@ -0,0 +1,11 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import reactor
+from twisted.web import proxy, server
+
+site = server.Site(proxy.ReverseProxyResource('www.yahoo.com', 80, ''))
+reactor.listenTCP(8080, site)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/report.rpy.py b/vendor/Twisted-10.0.0/doc/web/examples/report.rpy.py
new file mode 100644
index 0000000000..96dde8a55c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/report.rpy.py
@@ -0,0 +1,28 @@
+from twisted.web.resource import Resource
+
+
+class ReportResource(Resource):
+
+ def render_GET(self, request):
+ path = request.path
+ _, host, port = request.getHost()
+ url = request.prePathURL()
+ uri = request.uri
+ secure = (request.isSecure() and "securely") or "insecurely"
+ return ("""\
+<HTML>
+ <HEAD><TITLE>Welcome To Twisted Python Reporting</title></head>
+
+ <BODY><H1>Welcome To Twisted Python Reporting</H1>
+ <UL>
+ <LI>The path to me is %(path)s
+ <LI>The host I'm on is %(host)s
+ <LI>The port I'm on is %(port)s
+ <LI>I was accessed %(secure)s
+ <LI>A URL to me is %(url)s
+ <LI>My URI to me is %(uri)s
+ </UL>
+ </body>
+</html>""" % vars())
+
+resource = ReportResource()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/rootscript.py b/vendor/Twisted-10.0.0/doc/web/examples/rootscript.py
new file mode 100644
index 0000000000..a420db347a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/rootscript.py
@@ -0,0 +1,9 @@
+from twisted.web import vhost, static, script
+
+default = static.Data('text/html', '')
+default.putChild('vhost', vhost.VHostMonsterResource())
+resource = vhost.NameVirtualHost()
+resource.default = default
+file = static.File('static')
+file.processors = {'.rpy': script.ResourceScript}
+resource.addHost('twistedmatrix.com', file)
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/silly-web.py b/vendor/Twisted-10.0.0/doc/web/examples/silly-web.py
new file mode 100644
index 0000000000..b98d359bbc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/silly-web.py
@@ -0,0 +1,18 @@
+# This shows an example of a bare-bones distributed web
+# set up.
+# The "master" and "slave" parts will usually be in different files
+# -- they are here together only for brevity of illustration
+
+from twisted.internet import reactor, protocol
+from twisted.web import server, distrib, static
+from twisted.spread import pb
+
+# The "master" server
+site = server.Site(distrib.ResourceSubscription('unix', '.rp'))
+reactor.listenTCP(19988, site)
+
+# The "slave" server
+fact = pb.PBServerFactory(distrib.ResourcePublisher(server.Site(static.File('static'))))
+
+reactor.listenUNIX('./.rp', fact)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/simple.rtl b/vendor/Twisted-10.0.0/doc/web/examples/simple.rtl
new file mode 100644
index 0000000000..149e39bc56
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/simple.rtl
@@ -0,0 +1,23 @@
+# For this to work:
+# Configure your web server with
+# --process=.rtl=twisted.web.script.ResourceTemplate
+# And make sure quixote is installed on your Python path.
+from twisted.web.resource import Resource
+
+
+class ExampleResource(Resource):
+
+ def render_GET(self, request):
+ """\
+<HTML>
+ <HEAD><TITLE> Welcome To Twisted Python </title></head>
+
+ <BODY><ul>"""
+ for i in range(10):
+ '<LI>';i
+ """</ul></body>
+</html>"""
+
+
+resource = ExampleResource()
+
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/soap.py b/vendor/Twisted-10.0.0/doc/web/examples/soap.py
new file mode 100644
index 0000000000..8b744727de
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/soap.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""Example of publishing SOAP methods.
+
+Sample usage::
+
+ >>> import SOAPpy
+ >>> p = SOAPpy.SOAPProxy('http://localhost:8080/')
+ >>> p.add(a=1)
+ 1
+ >>> p.add(a=1, b=3)
+ 4
+ >>> p.echo([1, 2])
+ [1, 2]
+
+"""
+
+from twisted.web import soap, server
+from twisted.internet import reactor, defer
+
+
+class Example(soap.SOAPPublisher):
+ """Publish two methods, 'add' and 'echo'."""
+
+ def soap_echo(self, x):
+ return x
+
+ def soap_add(self, a=0, b=0):
+ return a + b
+ soap_add.useKeywords = 1
+
+ def soap_deferred(self):
+ return defer.succeed(2)
+
+
+reactor.listenTCP(8080, server.Site(Example()))
+reactor.run()
+
+
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/users.rpy.py b/vendor/Twisted-10.0.0/doc/web/examples/users.rpy.py
new file mode 100644
index 0000000000..adaae426b2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/users.rpy.py
@@ -0,0 +1,18 @@
+# With this, you can have an instant "community web site",
+# letting your shell users publish data in secure ways.
+#
+# Just put this script anywhere, and /path/to/this/script/<user>/
+# will publish a user's ~/public_html, and a .../<user>.twistd/
+# will attempt to contact a user's personal web server.
+#
+# For example, if you put this at the root of the web server
+# as "users.rpy", and configure --allow-ignore-ext, then
+# http://example.com/users/<name>/ and http://example.com/users/<name>.twistd
+# will work similarily to how they work on twistedmatrix.com
+
+from twisted.web import distrib
+
+resource = registry.getComponent(distrib.UserDirectory)
+if not resource:
+ resource = distrib.UserDirectory()
+ registry.setComponent(distrib.UserDirectory, resource)
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/vhost.rpy.py b/vendor/Twisted-10.0.0/doc/web/examples/vhost.rpy.py
new file mode 100644
index 0000000000..52673319f4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/vhost.rpy.py
@@ -0,0 +1,4 @@
+from twisted.web import vhost
+
+resource = vhost.VHostMonsterResource()
+
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/web.py b/vendor/Twisted-10.0.0/doc/web/examples/web.py
new file mode 100644
index 0000000000..3fdc072b2b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/web.py
@@ -0,0 +1,27 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+# This web server makes it possible to put it behind a reverse proxy
+# transparently. Just have the reverse proxy proxy to
+# host:port/vhost/http/external-host:port/
+# and on redirects and other link calculation, the external-host:port will
+# be transmitted to the client.
+
+from twisted.internet import reactor
+from twisted.web import static, server, vhost, twcgi, script, trp
+
+root = static.File("static")
+root.processors = {
+ '.cgi': twcgi.CGIScript,
+ '.php3': twcgi.PHP3Script,
+ '.php': twcgi.PHPScript,
+ '.epy': script.PythonScript,
+ '.rpy': script.ResourceScript,
+ '.trp': trp.ResourceUnpickler,
+}
+root.putChild('vhost', vhost.VHostMonsterResource())
+site = server.Site(root)
+reactor.listenTCP(1999, site)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/webguard.py b/vendor/Twisted-10.0.0/doc/web/examples/webguard.py
new file mode 100644
index 0000000000..abfc68ff95
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/webguard.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+
+from zope.interface import implements
+
+from twisted.python import log
+from twisted.internet import reactor
+from twisted.web import server, resource, guard
+from twisted.cred.portal import IRealm, Portal
+from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+
+
+class GuardedResource(resource.Resource):
+ """
+ A resource which is protected by guard and requires authentication in order
+ to access.
+ """
+ def getChild(self, path, request):
+ return self
+
+
+ def render(self, request):
+ return "Authorized!"
+
+
+
+class SimpleRealm(object):
+ """
+ A realm which gives out L{GuardedResource} instances for authenticated
+ users.
+ """
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if resource.IResource in interfaces:
+ return resource.IResource, GuardedResource(), lambda: None
+ raise NotImplementedError()
+
+
+
+def main():
+ log.startLogging(sys.stdout)
+ checkers = [InMemoryUsernamePasswordDatabaseDontUse(joe='blow')]
+ wrapper = guard.HTTPAuthSessionWrapper(
+ Portal(SimpleRealm(), checkers),
+ [guard.DigestCredentialFactory('md5', 'example.com')])
+ reactor.listenTCP(8889, server.Site(
+ resource = wrapper))
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/xmlrpc.py b/vendor/Twisted-10.0.0/doc/web/examples/xmlrpc.py
new file mode 100644
index 0000000000..808e5ba848
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/xmlrpc.py
@@ -0,0 +1,67 @@
+"""Example of an XML-RPC server in Twisted.
+
+To run the server, just run this file ("python xmlrpc.py").
+
+An example session (assuming the server is running)::
+
+ >>> import xmlrpclib
+ >>> s = xmlrpclib.Server('http://localhost:7080/')
+ >>> s.echo("lala")
+ ['lala']
+ >>> s.echo("lala", 1)
+ ['lala', 1]
+ >>> s.echo("lala", 4)
+ ['lala', 4]
+ >>> s.echo("lala", 4, 3.4)
+ ['lala', 4, 3.3999999999999999]
+ >>> s.echo("lala", 4, [1, 2])
+ ['lala', 4, [1, 2]]
+
+"""
+
+from twisted.web import xmlrpc
+from twisted.internet import defer
+
+# This module is standard in Python 2.2, otherwise get it from
+# http://www.pythonware.com/products/xmlrpc/
+import xmlrpclib
+
+
+class Echoer(xmlrpc.XMLRPC):
+ """An example object to be published.
+
+ Has five methods accessable by XML-RPC, 'echo', 'hello', 'defer',
+ 'defer_fail' and 'fail.
+ """
+
+ def xmlrpc_echo(self, *args):
+ """Return all passed args."""
+ return args
+
+ def xmlrpc_hello(self):
+ """Return 'hello, world'."""
+ return 'hello, world!'
+
+ def xmlrpc_defer(self):
+ """Show how xmlrpc methods can return Deferred."""
+ return defer.succeed("hello")
+
+ def xmlrpc_defer_fail(self):
+ """Show how xmlrpc methods can return failed Deferred."""
+ return defer.fail(12)
+
+ def xmlrpc_fail(self):
+ """Show how we can return a failure code."""
+ return xmlrpclib.Fault(7, "Out of cheese.")
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.web import server
+ r = Echoer()
+ reactor.listenTCP(7080, server.Site(r))
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/web/examples/xmlrpcclient.py b/vendor/Twisted-10.0.0/doc/web/examples/xmlrpcclient.py
new file mode 100644
index 0000000000..49c29b1fea
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/examples/xmlrpcclient.py
@@ -0,0 +1,23 @@
+from twisted.web.xmlrpc import Proxy
+from twisted.internet import reactor
+
+def printValue(value):
+ print repr(value)
+ reactor.stop()
+
+def printError(error):
+ print 'error', error
+ reactor.stop()
+
+proxy = Proxy('http://advogato.org/XMLRPC')
+proxy.callRemote('test.sumprod', 3, 5).addCallbacks(printValue, printError)
+reactor.run()
+proxy.callRemote('test.capitalize', 'moshe zadka').addCallbacks(printValue,
+ printError)
+reactor.run()
+proxy = Proxy('http://time.xmlrpc.com/RPC2')
+proxy.callRemote('currentTime.getCurrentTime').addCallbacks(printValue, printError)
+reactor.run()
+proxy = Proxy('http://betty.userland.com/RPC2')
+proxy.callRemote('examples.getStateName', 41).addCallbacks(printValue, printError)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/client.html b/vendor/Twisted-10.0.0/doc/web/howto/client.html
new file mode 100644
index 0000000000..22be61027f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/client.html
@@ -0,0 +1,469 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation:
+ Using the Twisted Web Client
+ </title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">
+ Using the Twisted Web Client
+ </h1>
+ <div class="toc"><ol><li><a href="#auto0">
+ Overview
+ </a></li><ul><li><a href="#auto1">
+ Prerequisites
+ </a></li></ul><li><a href="#auto2">
+ The Agent
+ </a></li><ul><li><a href="#auto3">Issuing Requests</a></li><li><a href="#auto4">
+ Receiving Responses
+ </a></li></ul><li><a href="#auto5">
+ Conclusion
+ </a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>
+ Overview
+ <a name="auto0"/></h2>
+
+ <p>
+ This document describes how to use the HTTP client included in Twisted
+ Web. After reading it, you should be able to make HTTP requests using
+ Twisted Web. You will be able to specify the request method, headers,
+ and body and you will be able to retrieve the response code, headers, and
+ body.
+ </p>
+
+ <h3>
+ Prerequisites
+ <a name="auto1"/></h3>
+
+ <p>
+ This document assumes that you are familiar with <a href="../../core/howto/defer.html" shape="rect">Deferreds and Failures</a>, and <a href="../../core/howto/producers.html" shape="rect">producers and consumers</a>.
+ It also assumes you are familiar with the basic concepts of HTTP, such
+ as requests and responses, methods, headers, and message bodies.
+ </p>
+
+ <h2>
+ The Agent
+ <a name="auto2"/></h2>
+
+ <h3>Issuing Requests<a name="auto3"/></h3>
+
+ <p>
+ The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.client.Agent.html" title="twisted.web.client.Agent">twisted.web.client.Agent</a></code> class is the entry
+ point into the client API. Requests are issued using the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.client.Agent.request.html" title="twisted.web.client.Agent.request">request</a></code> method, which
+ takes as parameters a request method, a request URI, the request headers,
+ and an object which can produce the request body (if there is to be one).
+ The agent is responsible for connection setup. Because of this, it
+ requires a reactor as an argument to its initializer. An example of
+ creating an agent and issuing a request using it might look like this:
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">http_headers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Headers</span>
+
+<span class="py-src-variable">agent</span> = <span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>)
+
+<span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(
+ <span class="py-src-string">'GET'</span>,
+ <span class="py-src-string">'http://example.com/'</span>,
+ <span class="py-src-variable">Headers</span>({<span class="py-src-string">'User-Agent'</span>: [<span class="py-src-string">'Twisted Web Client Example'</span>]}),
+ <span class="py-src-variable">None</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbResponse</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response received'</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbResponse</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbShutdown</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addBoth</span>(<span class="py-src-variable">cbShutdown</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">
+ Issue a request with an Agent
+ - <a href="listings/client/request.py"><span class="filename">listings/client/request.py</span></a></div></div>
+
+ <p>
+ As may be obvious, this issues a new <em>GET</em> request for <em>/</em>
+ to the web server on <code>example.com</code>. <code>Agent</code> is
+ responsible for resolving the hostname into an IP address and connecting
+ to it on port 80. It is also responsible for cleaning up the connection
+ afterwards. This code sends a request which includes one custom header,
+ <em>User-Agent</em>. The last argument passed to <code>Agent.request</code> is
+ <code>None</code>, though, so the request has no body.
+ </p>
+
+ <p>
+ Sending a request which does include a body requires passing an object
+ providing <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.iweb.IBodyProducer.html" title="twisted.web.iweb.IBodyProducer">twisted.web.iweb.IBodyProducer</a></code>
+ to <code>Agent.request</code>. This interface extends the more general
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IPushProducer.html" title="twisted.internet.interfaces.IPushProducer">IPushProducer</a></code>
+ by adding a new <code>length</code> attribute and adding several
+ constraints to the way the producer and consumer interact.
+ </p>
+
+ <ul>
+ <li>
+ The length attribute must be a non-negative integer or the constant
+ <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.iweb.UNKNOWN_LENGTH.html" title="twisted.web.iweb.UNKNOWN_LENGTH">twisted.web.iweb.UNKNOWN_LENGTH</a></code>. If the
+ length is known, it will be used to specify the value for the
+ <em>Content-Length</em> header in the request. If the length is
+ unknown the attribute should be set to <code>UNKNOWN_LENGTH</code>.
+ Since more servers support <em>Content-Length</em>, if a length can be
+ provided it should be.
+ </li>
+
+ <li>
+ An additional method is required on <code>IEntityBodyProvider</code>
+ implementations: <code>startProducing</code>. This method is used to
+ associate a consumer with the producer. It should return a
+ <code>Deferred</code> which fires when all data has been produced.
+ </li>
+
+ <li>
+ <code>IEntityBodyProvider</code> implementations should never call the
+ consumer's <code>unregisterProducer</code> method. Instead, when it
+ has produced all of the data it is going to produce, it should only
+ fire the <code>Deferred</code> returned by <code>startProducing</code>.
+ </li>
+ </ul>
+
+ <p>
+ For additional details about the requirements of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.iweb.IBodyProducer.html" title="twisted.web.iweb.IBodyProducer">IBodyProducer</a></code> implementations, see
+ the API documentation.
+ </p>
+
+ <p>
+ Here's a simple <code>IEntityBodyProvider</code> implementation which
+ writes an in-memory string to the consumer:
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">succeed</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">iweb</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IBodyProducer</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">StringProducer</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IBodyProducer</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">body</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">body</span> = <span class="py-src-variable">body</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">length</span> = <span class="py-src-variable">len</span>(<span class="py-src-variable">body</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">startProducing</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">consumer</span>):
+ <span class="py-src-variable">consumer</span>.<span class="py-src-variable">write</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">body</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">succeed</span>(<span class="py-src-variable">None</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">pauseProducing</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">pass</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">stopProducing</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">pass</span>
+</pre><div class="caption">
+ A string-based body producer.
+ - <a href="listings/client/stringprod.py"><span class="filename">listings/client/stringprod.py</span></a></div></div>
+
+ <p>
+ This producer can be used to issue a request with a body:
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">http_headers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Headers</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">stringprod</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">StringProducer</span>
+
+<span class="py-src-variable">agent</span> = <span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>)
+<span class="py-src-variable">body</span> = <span class="py-src-variable">StringProducer</span>(<span class="py-src-string">&quot;hello, world&quot;</span>)
+<span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(
+ <span class="py-src-string">'GET'</span>,
+ <span class="py-src-string">'http://example.com/'</span>,
+ <span class="py-src-variable">Headers</span>({<span class="py-src-string">'User-Agent'</span>: [<span class="py-src-string">'Twisted Web Client Example'</span>],
+ <span class="py-src-string">'Content-Type'</span>: [<span class="py-src-string">'text/x-greeting'</span>]}),
+ <span class="py-src-variable">body</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbResponse</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response received'</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbResponse</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbShutdown</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addBoth</span>(<span class="py-src-variable">cbShutdown</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">
+ Issue a request with a body.
+ - <a href="listings/client/sendbody.py"><span class="filename">listings/client/sendbody.py</span></a></div></div>
+
+ <h3>
+ Receiving Responses
+ <a name="auto4"/></h3>
+
+ <p>
+ So far, the examples have demonstrated how to issue a request. However,
+ they have ignored the response, except for showing that it is a
+ <code>Deferred</code> which seems to fire when the response has been
+ received. Next we'll cover what that response is and how to interpret
+ it.
+ </p>
+
+ <p>
+ <code>Agent.request</code>, as with most <code>Deferred</code>-returning
+ APIs, can return a <code>Deferred</code> which fires with a
+ <code>Failure</code>. If the request fails somehow, this will be
+ reflected with a failure. This may be due to a problem looking up the
+ host IP address, or it may be because the HTTP server is not accepting
+ connections, or it may be because of a problem parsing the response, or
+ any other problem which arises which prevents the response from being
+ received. It does <em>not</em> include responses with an error status.
+ </p>
+
+ <p>
+ If the request succeeds, though, the <code>Deferred</code> will fire with
+ a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.client.Response.html" title="twisted.web.client.Response">Response</a></code>. This
+ happens as soon as all the response headers have been received. It
+ happens before any of the response body, if there is one, is processed.
+ The <code>Response</code> object has several attributes giving the
+ response information: its code, version, phrase, and headers, as well as
+ the length of the body to expect. The <code>Response</code> object also
+ has a method which makes the response body available: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web._newclient.Response.deliverBody.deliverBody.html" title="twisted.web._newclient.Response.deliverBody.deliverBody">deliverBody</a></code>.
+ Using the attributes of the response object and this method, here's an
+ example which displays part of the response to a request:
+ </p>
+
+ <div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">pprint</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pformat</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">defer</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Deferred</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">protocol</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Protocol</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">client</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Agent</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">http_headers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Headers</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">BeginningPrinter</span>(<span class="py-src-parameter">Protocol</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">finished</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">finished</span> = <span class="py-src-variable">finished</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span> = <span class="py-src-number">1024</span> * <span class="py-src-number">10</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">dataReceived</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">bytes</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span>:
+ <span class="py-src-variable">display</span> = <span class="py-src-variable">bytes</span>[:<span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span>]
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Some data received:'</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">display</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">remaining</span> -= <span class="py-src-variable">len</span>(<span class="py-src-variable">display</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">connectionLost</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">reason</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Finished receiving body:'</span>, <span class="py-src-variable">reason</span>.<span class="py-src-variable">getErrorMessage</span>()
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">finished</span>.<span class="py-src-variable">callback</span>(<span class="py-src-variable">None</span>)
+
+<span class="py-src-variable">agent</span> = <span class="py-src-variable">Agent</span>(<span class="py-src-variable">reactor</span>)
+<span class="py-src-variable">d</span> = <span class="py-src-variable">agent</span>.<span class="py-src-variable">request</span>(
+ <span class="py-src-string">'GET'</span>,
+ <span class="py-src-string">'http://example.com/'</span>,
+ <span class="py-src-variable">Headers</span>({<span class="py-src-string">'User-Agent'</span>: [<span class="py-src-string">'Twisted Web Client Example'</span>]}),
+ <span class="py-src-variable">None</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbRequest</span>(<span class="py-src-parameter">response</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response version:'</span>, <span class="py-src-variable">response</span>.<span class="py-src-variable">version</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response code:'</span>, <span class="py-src-variable">response</span>.<span class="py-src-variable">code</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response phrase:'</span>, <span class="py-src-variable">response</span>.<span class="py-src-variable">phrase</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'Response headers:'</span>
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">pformat</span>(<span class="py-src-variable">list</span>(<span class="py-src-variable">response</span>.<span class="py-src-variable">headers</span>.<span class="py-src-variable">getAllRawHeaders</span>()))
+ <span class="py-src-variable">finished</span> = <span class="py-src-variable">Deferred</span>()
+ <span class="py-src-variable">response</span>.<span class="py-src-variable">deliverBody</span>(<span class="py-src-variable">BeginningPrinter</span>(<span class="py-src-variable">finished</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">finished</span>
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">cbRequest</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">cbShutdown</span>(<span class="py-src-parameter">ignored</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+<span class="py-src-variable">d</span>.<span class="py-src-variable">addBoth</span>(<span class="py-src-variable">cbShutdown</span>)
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre><div class="caption">
+ Inspect the response.
+ - <a href="listings/client/response.py"><span class="filename">listings/client/response.py</span></a></div></div>
+
+ <p>
+ The <code>BeginningPrinter</code> protocol in this example is passed to
+ <code>Response.deliverBody</code> and the response body is then delivered
+ to its <code>dataReceived</code> method as it arrives. When the body has
+ been completely delivered, the protocol's <code>connectionLost</code>
+ method is called. It is important to inspect the <code>Failure</code>
+ passed to <code>connectionLost</code>. If the response body has been
+ completely received, the failure will wrap a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.client.ResponseDone.html" title="twisted.web.client.ResponseDone">twisted.web.client.ResponseDone</a></code> exception. This
+ indicates that it is <em>known</em> that all data has been received. It
+ is also possible for the failure to wrap a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.http.PotentialDataLoss.html" title="twisted.web.http.PotentialDataLoss">twisted.web.http.PotentialDataLoss</a></code> exception: this
+ indicates that the server framed the response such that there is no way
+ to know when the entire response body has been received. Only
+ HTTP/1.0 servers should behave this way. Finally, it is possible for
+ the exception to be of another type, indicating guaranteed data loss for
+ some reason (a lost connection, a memory error, etc).
+ </p>
+
+ <p>
+ Just as protocols associated with a TCP connection are given a transport,
+ so will be a protocol passed to <code>deliverBody</code>. Since it makes
+ no sense to write more data to the connection at this stage of the
+ request, though, the transport <em>only</em> provides <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IPushProducer.html" title="twisted.internet.interfaces.IPushProducer">IPushProducer</a></code>. This allows the
+ protocol to control the flow of the response data: a call to the
+ transport's <code>pauseProducing</code> method will pause delivery; a
+ later call to <code>resumeProducing</code> will resume it. If it is
+ decided that the rest of the response body is not desired,
+ <code>stopProducing</code> can be used to stop delivery permanently;
+ after this, the protocol's <code>connectionLost</code> method will be
+ called.
+ </p>
+
+ <p>
+ An important thing to keep in mind is that the body will only be read
+ from the connection after <code>Response.deliverBody</code> is called.
+ This also means that the connection will remain open until this is done
+ (and the body read). So, in general, any response with a body
+ <em>must</em> have that body read using <code>deliverBody</code>. If the
+ application is not interested in the body, it should issue a
+ <em>HEAD</em> request or use a protocol which immediately calls
+ <code>stopProducing</code> on its transport.
+ </p>
+
+ <h2>
+ Conclusion
+ <a name="auto5"/></h2>
+
+ <p>
+ You should now understand the basics of the Twisted Web HTTP client. In
+ particular, you should understand:
+ </p>
+
+ <ul>
+ <li>
+ How to issue requests with arbitrary methods, headers, and bodies.
+ </li>
+ <li>
+ How to access the response version, code, phrase, headers, and body.
+ </li>
+ <li>
+ How to control the streaming of the response body.
+ </li>
+ </ul>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/formindepth.html b/vendor/Twisted-10.0.0/doc/web/howto/formindepth.html
new file mode 100644
index 0000000000..bbd7455c43
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/formindepth.html
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Form In Depth</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Form In Depth</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>XXX: To be written</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/glossary.html b/vendor/Twisted-10.0.0/doc/web/howto/glossary.html
new file mode 100644
index 0000000000..780f4acf75
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/glossary.html
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Glossary</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Glossary</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <p class="note"><strong>Note: </strong>This glossary is very incomplete. Contributions are
+ welcome.</p>
+
+ <dl>
+ <dt><a name="resource" shape="rect">resource</a></dt>
+ <dd>
+ An object accessible via HTTP at one or more URIs. In Twisted Web,
+ a resource is represented by an object which provides <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.IResource.html" title="twisted.web.resource.IResource">twisted.web.resource.IResource</a></code> and most often is
+ a subclass of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">twisted.web.resource.Resource</a></code>. For example, here
+ is a resource which represents a simple HTML greeting.
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Greeting</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Hello, world.&quot;</span>
+</pre>
+ </dd>
+ </dl>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/index.html b/vendor/Twisted-10.0.0/doc/web/howto/index.html
new file mode 100644
index 0000000000..9850b37744
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/index.html
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted.Web Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted.Web Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+
+<li>Introduction
+ <ul>
+ <li><a href="web-overview.html" shape="rect">Overview of Twisted Web</a></li>
+ </ul>
+</li>
+
+<li>Web Applications
+ <ul>
+ <li><a href="using-twistedweb.html" shape="rect">Using twisted.web</a></li>
+ <li><a href="web-development.html" shape="rect">Web application development</a></li>
+ <li><a href="resource-templates.html" shape="rect">Quixote resource templates</a></li>
+ <li><a href="xmlrpc.html" shape="rect">XML-RPC and SOAP</a></li>
+ <li><a href="web-in-60/index.html" shape="rect">Twisted Web in 60 Seconds: A
+ series of short, complete examples using twisted.web</a></li>
+ </ul>
+</li>
+
+<li>Other
+ <ul>
+ <li><a href="client.html" shape="rect">Using the Twisted Web Client</a></li>
+ </ul>
+</li>
+
+<li>Appendix
+ <ul>
+ <li><a href="glossary.html" shape="rect">Glossary</a></li>
+ </ul>
+</li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/listings/client/request.py b/vendor/Twisted-10.0.0/doc/web/howto/listings/client/request.py
new file mode 100644
index 0000000000..493186390d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/listings/client/request.py
@@ -0,0 +1,21 @@
+from twisted.internet import reactor
+from twisted.web.client import Agent
+from twisted.web.http_headers import Headers
+
+agent = Agent(reactor)
+
+d = agent.request(
+ 'GET',
+ 'http://example.com/',
+ Headers({'User-Agent': ['Twisted Web Client Example']}),
+ None)
+
+def cbResponse(ignored):
+ print 'Response received'
+d.addCallback(cbResponse)
+
+def cbShutdown(ignored):
+ reactor.stop()
+d.addBoth(cbShutdown)
+
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/listings/client/response.py b/vendor/Twisted-10.0.0/doc/web/howto/listings/client/response.py
new file mode 100644
index 0000000000..6b3547c8dc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/listings/client/response.py
@@ -0,0 +1,47 @@
+from pprint import pformat
+
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred
+from twisted.internet.protocol import Protocol
+from twisted.web.client import Agent
+from twisted.web.http_headers import Headers
+
+class BeginningPrinter(Protocol):
+ def __init__(self, finished):
+ self.finished = finished
+ self.remaining = 1024 * 10
+
+ def dataReceived(self, bytes):
+ if self.remaining:
+ display = bytes[:self.remaining]
+ print 'Some data received:'
+ print display
+ self.remaining -= len(display)
+
+ def connectionLost(self, reason):
+ print 'Finished receiving body:', reason.getErrorMessage()
+ self.finished.callback(None)
+
+agent = Agent(reactor)
+d = agent.request(
+ 'GET',
+ 'http://example.com/',
+ Headers({'User-Agent': ['Twisted Web Client Example']}),
+ None)
+
+def cbRequest(response):
+ print 'Response version:', response.version
+ print 'Response code:', response.code
+ print 'Response phrase:', response.phrase
+ print 'Response headers:'
+ print pformat(list(response.headers.getAllRawHeaders()))
+ finished = Deferred()
+ response.deliverBody(BeginningPrinter(finished))
+ return finished
+d.addCallback(cbRequest)
+
+def cbShutdown(ignored):
+ reactor.stop()
+d.addBoth(cbShutdown)
+
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/listings/client/sendbody.py b/vendor/Twisted-10.0.0/doc/web/howto/listings/client/sendbody.py
new file mode 100644
index 0000000000..31cac8fcfe
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/listings/client/sendbody.py
@@ -0,0 +1,24 @@
+from twisted.internet import reactor
+from twisted.web.client import Agent
+from twisted.web.http_headers import Headers
+
+from stringprod import StringProducer
+
+agent = Agent(reactor)
+body = StringProducer("hello, world")
+d = agent.request(
+ 'GET',
+ 'http://example.com/',
+ Headers({'User-Agent': ['Twisted Web Client Example'],
+ 'Content-Type': ['text/x-greeting']}),
+ body)
+
+def cbResponse(ignored):
+ print 'Response received'
+d.addCallback(cbResponse)
+
+def cbShutdown(ignored):
+ reactor.stop()
+d.addBoth(cbShutdown)
+
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/listings/client/stringprod.py b/vendor/Twisted-10.0.0/doc/web/howto/listings/client/stringprod.py
new file mode 100644
index 0000000000..da2b5cdffa
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/listings/client/stringprod.py
@@ -0,0 +1,21 @@
+from zope.interface import implements
+
+from twisted.internet.defer import succeed
+from twisted.web.iweb import IBodyProducer
+
+class StringProducer(object):
+ implements(IBodyProducer)
+
+ def __init__(self, body):
+ self.body = body
+ self.length = len(body)
+
+ def startProducing(self, consumer):
+ consumer.write(self.body)
+ return succeed(None)
+
+ def pauseProducing(self):
+ pass
+
+ def stopProducing(self):
+ pass
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/listings/soap.rpy b/vendor/Twisted-10.0.0/doc/web/howto/listings/soap.rpy
new file mode 100644
index 0000000000..957380d2d8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/listings/soap.rpy
@@ -0,0 +1,13 @@
+from twisted.web import soap
+import os
+
+def getQuote():
+ return "That beverage, sir, is off the hizzy."
+
+class Quoter(soap.SOAPPublisher):
+ """Publish one method, 'quote'."""
+
+ def soap_quote(self):
+ return getQuote()
+
+resource = Quoter()
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/listings/webquote.rtl b/vendor/Twisted-10.0.0/doc/web/howto/listings/webquote.rtl
new file mode 100644
index 0000000000..8807ac4320
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/listings/webquote.rtl
@@ -0,0 +1,20 @@
+from twisted.web.resource import Resource
+
+def getQuote():
+ return "An apple a day keeps the doctor away."
+
+
+class QuoteResource(Resource):
+
+ template render(self, request):
+ """\
+ <html>
+ <head><title>Quotes Galore</title></head>
+
+ <body><h1>Quotes</h1>"""
+ getQuote()
+ "</body></html>"
+
+
+resource = QuoteResource()
+
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/listings/xmlAndSoapQuote.py b/vendor/Twisted-10.0.0/doc/web/howto/listings/xmlAndSoapQuote.py
new file mode 100644
index 0000000000..f17eb28317
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/listings/xmlAndSoapQuote.py
@@ -0,0 +1,25 @@
+from twisted.web import soap, xmlrpc, resource, server
+import os
+
+def getQuote():
+ return "Victory to the burgeois, you capitalist swine!"
+
+class XMLRPCQuoter(xmlrpc.XMLRPC):
+ def xmlrpc_quote(self):
+ return getQuote()
+
+class SOAPQuoter(soap.SOAPPublisher):
+ def soap_quote(self):
+ return getQuote()
+
+def main():
+ from twisted.internet import reactor
+ root = resource.Resource()
+ root.putChild('RPC2', XMLRPCQuoter())
+ root.putChild('SOAP', SOAPQuoter())
+ reactor.listenTCP(7080, server.Site(root))
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/listings/xmlquote.rpy b/vendor/Twisted-10.0.0/doc/web/howto/listings/xmlquote.rpy
new file mode 100644
index 0000000000..26b76f0a25
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/listings/xmlquote.rpy
@@ -0,0 +1,12 @@
+from twisted.web import xmlrpc
+import os
+
+def getQuote():
+ return "What are you talking about, William?"
+
+class Quoter(xmlrpc.XMLRPC):
+
+ def xmlrpc_quote(self):
+ return getQuote()
+
+resource = Quoter()
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/resource-templates.html b/vendor/Twisted-10.0.0/doc/web/howto/resource-templates.html
new file mode 100644
index 0000000000..8e2bf6a8ee
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/resource-templates.html
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Light Weight Templating With Resource Templates</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Light Weight Templating With Resource Templates</h1>
+ <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Configuring Twisted.Web</a></li><li><a href="#auto2">Using ResourceTemplate</a></li></ol></div>
+ <div class="content">
+ <span/>
+
+<h2>Overview<a name="auto0"/></h2>
+
+<p>While high-level templating systems can be used with Twisted (for example,
+<a href="http://divmod.org/trac/wiki/DivmodNevow" shape="rect">Divmod Nevow</a>, sometimes
+one needs a less file-heavy system which lets one directly write HTML. While
+ResourceScripts are available, they have a high overhead of coding, needing
+some boring string arithmetic. ResourceTemplates fill the space between Nevow
+and ResourceScript using Quixote's PTL (Python Templating Language).</p>
+
+<p>ResourceTemplates need Quixote installed. In
+<a href="http://www.debian.org" shape="rect">Debian</a>, that means using Python 2.2
+and installing the <code>quixote</code> package
+(<code>apt-get install quixote</code>). Other operating systems require
+other ways to install quixote, or it can be done manually.</p>
+
+<h2>Configuring Twisted.Web<a name="auto1"/></h2>
+
+<p>The easiest way to get Twisted.Web to support ResourceTemplates is to
+bind them to some extension using the web tap's <code>--processor</code>
+flag. Here is an example:</p>
+
+<pre xml:space="preserve">
+% twistd web --path=/var/www \
+ --processor=.rtl=twisted.web.script.ResourceTemplate
+</pre>
+
+<p>The above command line binds the <code>rtl</code> extension to use the
+ResourceTemplate processor. Other ways are possible, but would require
+more Python coding and are outside the scope of this HOWTO.</p>
+
+<h2>Using ResourceTemplate<a name="auto2"/></h2>
+
+<p>ResourceTemplates are coded in an extension of Python called the
+<q>Python Templating Language</q>. Complete documentation of the PTL
+is available at <a href="http://www.mems-exchange.org/software/quixote/doc/PTL.html" shape="rect">the quixote web site</a>. The web server
+will expect the PTL source file to define a variable named
+<code>resource</code>.
+This should be a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Resource.html" title="twisted.web.server.Resource">twisted.web.server.Resource</a></code>,
+whose <code>.render</code> method be called. Usually, you would want
+to define <code>render</code> using the keyword <code>template</code>
+rather than <code>def</code>.</p>
+
+<p>Here is a simple example for a resource template.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;An apple a day keeps the doctor away.&quot;</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">QuoteResource</span>(<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-variable">template</span> <span class="py-src-variable">render</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">request</span>):
+ <span class="py-src-string">&quot;&quot;&quot;\
+ &lt;html&gt;
+ &lt;head&gt;&lt;title&gt;Quotes Galore&lt;/title&gt;&lt;/head&gt;
+
+ &lt;body&gt;&lt;h1&gt;Quotes&lt;/h1&gt;&quot;&quot;&quot;</span>
+ <span class="py-src-variable">getQuote</span>()
+ <span class="py-src-string">&quot;&lt;/body&gt;&lt;/html&gt;&quot;</span>
+
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">QuoteResource</span>()
+</pre><div class="caption">Resource Template for Quotes - <a href="listings/webquote.rtl"><span class="filename">listings/webquote.rtl</span></a></div></div>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/using-twistedweb.html b/vendor/Twisted-10.0.0/doc/web/howto/using-twistedweb.html
new file mode 100644
index 0000000000..c0577be0d1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/using-twistedweb.html
@@ -0,0 +1,972 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Configuring and Using the Twisted.Web Server</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Configuring and Using the Twisted.Web Server</h1>
+ <div class="toc"><ol><li><a href="#auto0">Twisted Web Development</a></li><ul><li><a href="#auto1">Main Concepts</a></li><li><a href="#auto2">Site Objects</a></li><li><a href="#auto3">Resource objects</a></li><li><a href="#auto4">Resource Trees</a></li><li><a href="#auto5">.rpy scripts</a></li><li><a href="#auto6">Resource rendering</a></li><li><a href="#auto7">Session</a></li></ul><li><a href="#auto8">Advanced Configuration</a></li><ul><li><a href="#auto9">Adding Children</a></li><li><a href="#auto10">Modifying File Resources</a></li><li><a href="#auto11">Virtual Hosts</a></li><li><a href="#auto12">Advanced Techniques</a></li></ul><li><a href="#auto13">Running a Twisted Web Server</a></li><ul><li><a href="#auto14">Serving Flat HTML</a></li><li><a href="#auto15">Resource Scripts</a></li><li><a href="#auto16">Web UIs</a></li><li><a href="#auto17">Spreadable Web Servers</a></li><li><a href="#auto18">Serving PHP/Perl/CGI</a></li><li><a href="#auto19">Serving WSGI Applications</a></li><li><a href="#auto20">Using VHostMonster</a></li></ul><li><a href="#auto21">Rewriting URLs</a></li><li><a href="#auto22">Knowing When We're Not Wanted</a></li><li><a href="#auto23">As-Is Serving</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Twisted Web Development<a name="auto0"/></h2><a name="development" shape="rect"/>
+
+<p>Twisted Web serves Python objects that implement the interface
+IResource.</p>
+
+<br clear="none"/><img alt="Twisted Web process" src="../img/web-process.png"/>
+
+<h3>Main Concepts<a name="auto1"/></h3>
+
+<ul>
+
+<li><a href="#sites" shape="rect">Site Objects</a> are responsible for creating
+<code>HTTPChannel</code> instances to parse the HTTP request, and begin the object lookup process. They contain the root Resource, the resource which represents the URL <code>/</code> on the site.</li>
+
+<li><a href="#resources" shape="rect">Resource</a> objects represent a single URL segment. The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.IResource.html" title="twisted.web.resource.IResource">IResource</a></code> interface describes the methods a Resource object must implement in order to participate in the object publishing process.</li>
+
+<li><a href="#trees" shape="rect">Resource trees</a> are arrangements of Resource objects into a Resource tree. Starting at the root Resource object, the tree of Resource objects defines the URLs which will be valid.</li>
+
+<li><a href="#rpys" shape="rect">.rpy scripts</a> are python scripts which the twisted.web static file server will execute, much like a CGI. However, unlike CGI they must create a Resource object which will be rendered when the URL is visited.</li>
+
+<li><a href="#rendering" shape="rect">Resource rendering</a> occurs when Twisted Web locates a leaf Resource object. A Resource can either return an html string or write to the request object.</li>
+
+<li><a href="#sessions" shape="rect">Session</a> objects allow you to store information across multiple requests. Each individual browser using the system has a unique Session instance.</li>
+
+</ul>
+
+<p>The Twisted.Web server is started through the Twisted Daemonizer, as in:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web
+</pre>
+
+<h3>Site Objects<a name="auto2"/></h3>
+<a name="sites" shape="rect"/>
+
+<p>Site objects serve as the glue between a port to listen for HTTP requests on, and a root Resource object.</p>
+
+<p>When using <code>twistd -n web --path /foo/bar/baz</code>, a Site object is created with a root Resource that serves files out of the given path.</p>
+
+<p>You can also create a <code>Site</code> instance by hand, passing it a
+<code>Resource</code> object which will serve as the root of the site:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span>, <span class="py-src-variable">resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Simple</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">isLeaf</span> = <span class="py-src-variable">True</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;Hello, world!&lt;/html&gt;&quot;</span>
+
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">Simple</span>())
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8080</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h3>Resource objects<a name="auto3"/></h3>
+<a name="resources" shape="rect"/>
+
+<p><code>Resource</code> objects represent a single URL segment of a site. During URL parsing, <code>getChild</code> is called on the current <code>Resource</code> to produce the next <code>Resource</code> object.</p>
+
+<p>When the leaf Resource is reached, either because there were no more URL segments or a Resource had isLeaf set to True, the leaf Resource is rendered by calling <code>render(request)</code>. See <q>Resource Rendering</q> below for more about this.</p>
+
+<p>During the Resource location process, the URL segments which have already been processed and those which have not yet been processed are available in <code>request.prepath</code> and <code>request.postpath</code>.</p>
+
+<p>A Resource can know where it is in the URL tree by looking at <code>request.prepath</code>, a list of URL segment strings.</p>
+
+<p>A Resource can know which path segments will be processed after it by looking at <code>request.postpath</code>.</p>
+
+<p>If the URL ends in a slash, for example <code>http://example.com/foo/bar/</code>, the final URL segment will be an empty string. Resources can thus know if they were requested with or without a final slash.</p>
+
+<p>Here is a simple Resource object:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Hello</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">isLeaf</span> = <span class="py-src-variable">True</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">name</span> == <span class="py-src-string">''</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">Resource</span>.<span class="py-src-variable">getChild</span>(<span class="py-src-variable">self</span>, <span class="py-src-variable">name</span>, <span class="py-src-variable">request</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Hello, world! I am located at %r.&quot;</span> % (<span class="py-src-variable">request</span>.<span class="py-src-variable">prepath</span>,)
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">Hello</span>()
+</pre>
+
+<h3>Resource Trees<a name="auto4"/></h3>
+<a name="trees" shape="rect"/>
+
+<p>Resources can be arranged in trees using <code>putChild</code>. <code>putChild</code> puts a Resource instance into another Resource instance, making it available at the given path segment name:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-variable">root</span> = <span class="py-src-variable">Hello</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'fred'</span>, <span class="py-src-variable">Hello</span>())
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'bob'</span>, <span class="py-src-variable">Hello</span>())
+</pre>
+
+<p>If this root resource is served as the root of a Site instance, the following URLs will all be valid:</p>
+
+<ul>
+<li><code>http://example.com/</code></li>
+<li><code>http://example.com/fred</code></li>
+<li><code>http://example.com/bob</code></li>
+<li><code>http://example.com/fred/</code></li>
+<li><code>http://example.com/bob/</code></li>
+
+</ul>
+
+<h3>.rpy scripts<a name="auto5"/></h3>
+<a name="rpys" shape="rect"/>
+
+<p>Files with the extension <code>.rpy</code> are python scripts which, when placed in a directory served by Twisted Web, will be executed when visited through the web.</p>
+
+<p>An <code>.rpy</code> script must define a variable, <code>resource</code>, which is the Resource object that will render the request.</p>
+
+<p><code>.rpy</code> files are very convenient for rapid development and prototyping. Since they are executed on every web request, defining a Resource subclass in an <code>.rpy</code> will make viewing the results of changes to your class visible simply by refreshing the page:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;Hello, world!&lt;/html&gt;&quot;</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">MyResource</span>()
+</pre>
+
+<p>However, it is often a better idea to define Resource subclasses in Python modules. In order for changes in modules to be visible, you must either restart the Python process, or reload the module:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">myresource</span>
+
+<span class="py-src-comment">## Comment out this line when finished debugging</span>
+<span class="py-src-variable">reload</span>(<span class="py-src-variable">myresource</span>)
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">myresource</span>.<span class="py-src-variable">MyResource</span>()
+</pre>
+
+<p>Creating a Twisted Web server which serves a directory is easy:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd -n web --path /Users/dsp/Sites
+</pre>
+
+<h3>Resource rendering<a name="auto6"/></h3>
+<a name="rendering" shape="rect"/>
+
+<p>Resource rendering occurs when Twisted Web locates a leaf Resource object to handle a web request. A Resource's <code>render</code> method may do various things to produce output which will be sent back to the browser:</p>
+
+<ul>
+<li>Return a string</li>
+<li>Call <code>request.write(&quot;stuff&quot;)</code> as many times as desired, then call <code>request.finish()</code> and return <code>server.NOT_DONE_YET</code> (This is deceptive, since you are in fact done with the request, but is the correct way to do this)</li>
+
+<li>Request a <code>Deferred</code>, return <code>server.NOT_DONE_YET</code>, and call <code>request.write(&quot;stuff&quot;)</code> and <code>request.finish()</code> later, in a callback on the <code>Deferred</code>.</li>
+</ul>
+
+<p>
+
+The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>
+class, which is usually what one's Resource classes subclass, has a
+convenient default implementation of <code class="python">render</code>. It will call a method named <code class="python">self.render_METHOD</code> where <q>METHOD</q> is
+whatever HTTP method was used to request this resource. Examples:
+request_GET, request_POST, request_HEAD, and so on. It is recommended
+that you have your resource classes subclass <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code> and implement <code class="python">render_METHOD</code> methods as opposed to <code class="python">render</code> itself. Note that for certain resources,
+<code class="python">request_POST = request_GET</code> may be
+desirable in case one wants to process arguments passed to the
+resource regardless of whether they used GET
+(<code>?foo=bar&amp;baz=quux</code>, and so forth) or POST.
+
+</p>
+
+<h3>Session<a name="auto7"/></h3>
+<a name="sessions" shape="rect"/>
+
+<p>HTTP is a stateless protocol; every request-response is treated as an individual unit, distinguishable from any other request only by the URL requested. With the advent of Cookies in the mid nineties, dynamic web servers gained the ability to distinguish between requests coming from different <em>browser sessions</em> by sending a Cookie to a browser. The browser then sends this cookie whenever it makes a request to a web server, allowing the server to track which requests come from which browser session.</p>
+
+<p>Twisted Web provides an abstraction of this browser-tracking behavior called the <em>Session object</em>. Calling <code>request.getSession()</code> checks to see if a session cookie has been set; if not, it creates a unique session id, creates a Session object, stores it in the Site, and returns it. If a session object already exists, the same session object is returned. In this way, you can store data specific to the session in the session object.</p>
+
+<img src="../img/web-session.png"/>
+
+<h2>Advanced Configuration<a name="auto8"/></h2>
+
+<p>Non-trivial configurations of Twisted Web are achieved with Python
+configuration files. This is a Python snippet which builds up a variable
+called application. Usually, a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.application.internet.TCPServer.html" title="twisted.application.internet.TCPServer">twisted.application.internet.TCPServer</a></code> instance will
+be used to make the application listen on a TCP port (80, in case direct
+web serving is desired), with the listener being a
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">twisted.web.server.Site</a></code>. The resulting file can then
+be run with <code class="shell">twistd -y</code>. Alternatively a reactor
+object can be used directly to make a runnable script.</p>
+
+<p>The <code>Site</code> will wrap a <code>Resource</code> object -- the
+root.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<p>Most advanced configurations will be in the form of tweaking the
+root resource object.</p>
+
+<h3>Adding Children<a name="auto9"/></h3>
+
+<p>Usually, the root's children will be based on the filesystem's contents.
+It is possible to override the filesystem by explicit <code>putChild</code>
+methods.</p>
+
+<p>Here are two examples. The first one adds a <code>/doc</code> child
+to serve the documentation of the installed packages, while the second
+one adds a <code>cgi-bin</code> directory for CGI scripts.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;doc&quot;</span>, <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/usr/share/doc&quot;</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">twcgi</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;cgi-bin&quot;</span>, <span class="py-src-variable">twcgi</span>.<span class="py-src-variable">CGIDirectory</span>(<span class="py-src-string">&quot;/var/www/cgi-bin&quot;</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h3>Modifying File Resources<a name="auto10"/></h3>
+
+<p><code>File</code> resources, be they root object or children thereof,
+have two important attributes that often need to be modified:
+<code>indexNames</code> and <code>processors</code>. <code>indexNames</code>
+determines which files are treated as <q>index files</q> -- served
+up when a directory is rendered. <code>processors</code> determine how
+certain file extensions are treated.</p>
+
+<p>Here is an example for both, creating a site where all <code>.rpy</code>
+extensions are Resource Scripts, and which renders directories by
+searching for a <code>index.rpy</code> file.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">script</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">indexNames</span>=[<span class="py-src-string">'index.rpy'</span>]
+<span class="py-src-variable">root</span>.<span class="py-src-variable">processors</span> = {<span class="py-src-string">'.rpy'</span>: <span class="py-src-variable">script</span>.<span class="py-src-variable">ResourceScript</span>}
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<p><code>File</code> objects also have a method called <code>ignoreExt</code>.
+This method can be used to give extension-less URLs to users, so that
+implementation is hidden. Here is an example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">script</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">ignoreExt</span>(<span class="py-src-string">&quot;.rpy&quot;</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">processors</span> = {<span class="py-src-string">'.rpy'</span>: <span class="py-src-variable">script</span>.<span class="py-src-variable">ResourceScript</span>}
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<p>Now, a URL such as <code>/foo</code> might be served from a Resource
+Script called <code>foo.rpy</code>, if no file by the name of <code>foo</code>
+exists.</p>
+
+<h3>Virtual Hosts<a name="auto11"/></h3>
+
+<p>Virtual hosting is done via a special resource, that should be
+used as the root resource -- <code>NameVirtualHost</code>.
+<code>NameVirtualHost</code> has an attribute named <code>default</code>,
+which holds the default website. If a different root for some other
+name is desired, the <code>addHost</code> method should be called.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">vhost</span>, <span class="py-src-variable">script</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">vhost</span>.<span class="py-src-variable">NameVirtualHost</span>()
+
+<span class="py-src-comment"># Add a default -- htdocs</span>
+<span class="py-src-variable">root</span>.<span class="py-src-variable">default</span>=<span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+
+<span class="py-src-comment"># Add a simple virtual host -- foo.com</span>
+<span class="py-src-variable">root</span>.<span class="py-src-variable">addHost</span>(<span class="py-src-string">&quot;foo.com&quot;</span>, <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/foo&quot;</span>))
+
+<span class="py-src-comment"># Add a simple virtual host -- bar.com</span>
+<span class="py-src-variable">root</span>.<span class="py-src-variable">addHost</span>(<span class="py-src-string">&quot;bar.com&quot;</span>, <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/bar&quot;</span>))
+
+<span class="py-src-comment"># The &quot;baz&quot; people want to use Resource Scripts in their web site</span>
+<span class="py-src-variable">baz</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/baz&quot;</span>)
+<span class="py-src-variable">baz</span>.<span class="py-src-variable">processors</span> = {<span class="py-src-string">'.rpy'</span>: <span class="py-src-variable">script</span>.<span class="py-src-variable">ResourceScript</span>}
+<span class="py-src-variable">baz</span>.<span class="py-src-variable">ignoreExt</span>(<span class="py-src-string">'.rpy'</span>)
+<span class="py-src-variable">root</span>.<span class="py-src-variable">addHost</span>(<span class="py-src-string">'baz'</span>, <span class="py-src-variable">baz</span>)
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<h3>Advanced Techniques<a name="auto12"/></h3>
+
+<p>Since the configuration is a Python snippet, it is possible to
+use the full power of Python. Here are some simple examples:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-comment"># No need for configuration of virtual hosts -- just make sure</span>
+<span class="py-src-comment"># a directory /var/vhosts/&lt;vhost name&gt; exists:</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">vhost</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">vhost</span>.<span class="py-src-variable">NameVirtualHost</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">default</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+<span class="py-src-keyword">for</span> <span class="py-src-variable">dir</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">os</span>.<span class="py-src-variable">listdir</span>(<span class="py-src-string">&quot;/var/vhosts&quot;</span>):
+ <span class="py-src-variable">root</span>.<span class="py-src-variable">addHost</span>(<span class="py-src-variable">dir</span>, <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-variable">os</span>.<span class="py-src-variable">path</span>.<span class="py-src-variable">join</span>(<span class="py-src-string">&quot;/var/vhosts&quot;</span>, <span class="py-src-variable">dir</span>)))
+
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-comment"># Determine ports we listen on based on a file with numbers:</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">vhost</span>, <span class="py-src-variable">static</span>, <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/var/www/htdocs&quot;</span>)
+
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web'</span>)
+<span class="py-src-variable">serviceCollection</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+
+<span class="py-src-keyword">for</span> <span class="py-src-variable">num</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">map</span>(<span class="py-src-variable">int</span>, <span class="py-src-variable">open</span>(<span class="py-src-string">&quot;/etc/web/ports&quot;</span>).<span class="py-src-variable">read</span>().<span class="py-src-variable">split</span>()):
+ <span class="py-src-variable">serviceCollection</span>.<span class="py-src-variable">addCollection</span>(<span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-variable">num</span>, <span class="py-src-variable">site</span>))
+</pre>
+
+
+<h2>Running a Twisted Web Server<a name="auto13"/></h2>
+
+<p>In many cases, you'll end up repeating common usage patterns of
+twisted.web. In those cases you'll probably want to use Twisted's
+pre-configured web server setup.</p>
+
+<p>The easiest way to run a Twisted Web server is with the Twisted Daemonizer.
+For example, this command will run a web server which serves static files from
+a particular directory:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web --path /path/to/web/content
+</pre>
+
+<p>If you just want to serve content from your own home directory, the
+following will do:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web --path ~/public_html/
+</pre>
+
+<p>You can stop the server at any time by going back to the directory you
+started it in and running the command:</p>
+
+<pre class="shell" xml:space="preserve">
+% kill `cat twistd.pid`
+</pre>
+
+<p> Some other configuration options are available as well: </p>
+
+<ul>
+ <li> <code>--port</code>: Specify the port for the web
+ server to listen on. This defaults to 8080. </li>
+ <li> <code>--logfile</code>: Specify the path to the
+ log file. </li>
+</ul>
+
+<p> The full set of options that are available can be seen with: </p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web --help
+</pre>
+
+<h3>Serving Flat HTML<a name="auto14"/></h3>
+
+<p> Twisted.Web serves flat HTML files just as it does any other flat file. </p>
+
+<a name="ResourceScripts" shape="rect"/>
+<h3>Resource Scripts<a name="auto15"/></h3>
+
+<p> A Resource script is a Python file ending with the extension <code>.rpy</code>, which is required to create an instance of a (subclass of a) <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">twisted.web.resource.Resource</a></code>. </p>
+
+<p> Resource scripts have 3 special variables: </p>
+
+<ul>
+ <li> <code class="py-src-identifier">__file__</code>: The name of the .rpy file, including the full path. This variable is automatically defined and present within the namespace. </li>
+ <li> <code class="py-src-identifier">registry</code>: An object of class <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.static.Registry.html" title="twisted.web.static.Registry">static.Registry</a></code>. It can be used to access and set persistent data keyed by a class.</li>
+ <li> <code class="py-src-identifier">resource</code>: The variable which must be defined by the script and set to the resource instance that will be used to render the page. </li>
+</ul>
+
+<p> A very simple Resource Script might look like: </p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyGreatResource</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&lt;html&gt;foo&lt;/html&gt;&quot;</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">MyGreatResource</span>()
+</pre>
+
+<p> A slightly more complicated resource script, which accesses some
+persistent data, might look like:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">SillyWeb</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Counter</span>
+
+<span class="py-src-variable">counter</span> = <span class="py-src-variable">registry</span>.<span class="py-src-variable">getComponent</span>(<span class="py-src-variable">Counter</span>)
+<span class="py-src-keyword">if</span> <span class="py-src-keyword">not</span> <span class="py-src-variable">counter</span>:
+ <span class="py-src-variable">registry</span>.<span class="py-src-variable">setComponent</span>(<span class="py-src-variable">Counter</span>, <span class="py-src-variable">Counter</span>())
+<span class="py-src-variable">counter</span> = <span class="py-src-variable">registry</span>.<span class="py-src-variable">getComponent</span>(<span class="py-src-variable">Counter</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">MyResource</span>(<span class="py-src-parameter">resource</span>.<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">counter</span>.<span class="py-src-variable">increment</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;you are visitor %d&quot;</span> % <span class="py-src-variable">counter</span>.<span class="py-src-variable">getValue</span>()
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">MyResource</span>()
+</pre>
+
+<p> This is assuming you have the <code>SillyWeb.Counter</code> module,
+implemented something like the following:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>:
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">value</span> = <span class="py-src-number">0</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">increment</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">value</span> += <span class="py-src-number">1</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getValue</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">value</span>
+</pre>
+
+<h3>Web UIs<a name="auto16"/></h3>
+
+<p>
+The
+<a href="http://www.divmod.org/projects/nevow" shape="rect">Nevow</a> framework, available as
+part of the <a href="http://www.divmod.org/projects/quotient" shape="rect">Quotient</a> project,
+is an advanced system for giving Web UIs to your application. Nevow uses Twisted Web but is
+not itself part of Twisted.
+</p>
+
+<a name="SpreadableWebServers" shape="rect"/>
+<h3>Spreadable Web Servers<a name="auto17"/></h3>
+
+<p> One of the most interesting applications of Twisted.Web is the distributed webserver; multiple servers can all answer requests on the same port, using the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.spread.html" title="twisted.spread">twisted.spread</a></code> package for <q>spreadable</q> computing. In two different directories, run the commands: </p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web --user
+% twistd web --personal [other options, if you desire]
+</pre>
+
+<p> Once you're running both of these instances, go to <code>http://localhost:8080/your_username.twistd/</code> -- you will see the front page from the server you created with the <code>--personal</code> option. What's happening here is that the request you've sent is being relayed from the central (User) server to your own (Personal) server, over a PB connection. This technique can be highly useful for small <q>community</q> sites; using the code that makes this demo work, you can connect one HTTP port to multiple resources running with different permissions on the same machine, on different local machines, or even over the internet to a remote site. </p>
+
+<p>
+By default, a personal server listens on a UNIX socket in the owner's home
+directory. The <code class="shell">--port</code> option can be used to make
+it listen on a different address, such as a TCP or SSL server or on a UNIX
+server in a different location. If you use this option to make a personal
+server listen on a different address, the central (User) server won't be
+able to find it, but a custom server which uses the same APIs as the central
+server might. Another use of the <code class="shell">--port</code> option
+is to make the UNIX server robust against system crashes. If the server
+crashes and the UNIX socket is left on the filesystem, the personal server
+will not be able to restart until it is removed. However, if <code class="shell">--port unix:/home/username/.twistd-web-pb:wantPID=1</code> is
+supplied when creating the personal server, then a lockfile will be used to
+keep track of whether the server socket is in use and automatically delete
+it when it is not.
+</p>
+
+<h3>Serving PHP/Perl/CGI<a name="auto18"/></h3>
+
+<p>Everything related to CGI is located in the
+<code>twisted.web.twcgi</code>, and it's here you'll find the classes that you
+need to subclass in order to support the language of your (or somebody elses)
+taste. You'll also need to create your own kind of resource if you are using a
+non-unix operatingsystem (such as Windows), or if the default resources has
+wrong pathnames to the parsers.</p>
+
+<p>The following snippet is a .rpy that serves perl-files. Look at <code>twisted.web.twcgi</code>
+for more examples regarding twisted.web and CGI.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span>, <span class="py-src-variable">twcgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PerlScript</span>(<span class="py-src-parameter">twcgi</span>.<span class="py-src-parameter">FilteredScript</span>):
+ <span class="py-src-variable">filter</span> = <span class="py-src-string">'/usr/bin/perl'</span> <span class="py-src-comment"># Points to the perl parser</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">static</span>.<span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/perlsite&quot;</span>) <span class="py-src-comment"># Points to the perl website</span>
+<span class="py-src-variable">resource</span>.<span class="py-src-variable">processors</span> = {<span class="py-src-string">&quot;.pl&quot;</span>: <span class="py-src-variable">PerlScript</span>} <span class="py-src-comment"># Files that end with .pl will be</span>
+ <span class="py-src-comment"># processed by PerlScript</span>
+<span class="py-src-variable">resource</span>.<span class="py-src-variable">indexNames</span> = [<span class="py-src-string">'index.pl'</span>]
+</pre>
+
+<h3>Serving WSGI Applications<a name="auto19"/></h3>
+
+<p><a href="http://wsgi.org/wsgi/" shape="rect">WSGI</a> is the Web Server Gateway
+Interface. It is a specification for web servers and application servers to
+communicate with Python web applications. All modern Python web frameworks
+support the WSGI interface.</p>
+
+<p>The easiest way to get started with WSGI application is to use the twistd
+command:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd -n web --wsgi=helloworld.application
+</pre>
+
+<p>This assumes that you have a WSGI application called application in
+your helloworld module/package, which might look like this:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">application</span>(<span class="py-src-parameter">environ</span>, <span class="py-src-parameter">start_response</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Basic WSGI Application&quot;&quot;&quot;</span>
+ <span class="py-src-variable">start_response</span>(<span class="py-src-string">'200 OK'</span>, [(<span class="py-src-string">'Content-type'</span>,<span class="py-src-string">'text/plain'</span>)])
+ <span class="py-src-keyword">return</span> [<span class="py-src-string">'Hello World!'</span>]
+</pre>
+
+<p>The above setup will be suitable for many applications where all that is
+needed is to server the WSGI application at the site's root. However, for
+greater control, Twisted provides support for using WSGI applications as
+resources <code class="api">twisted.web.wsgi.WSGIResource</code>.</p>
+
+<p>Here is an example of a WSGI application being served as the root resource
+for a site, in the following tac file:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">wsgi</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">WSGIResource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">threadpool</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">ThreadPool</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">strports</span>
+
+<span class="py-src-comment"># Create and start a thread pool,</span>
+<span class="py-src-variable">wsgiThreadPool</span> = <span class="py-src-variable">ThreadPool</span>()
+<span class="py-src-variable">wsgiThreadPool</span>.<span class="py-src-variable">start</span>()
+
+<span class="py-src-comment"># ensuring that it will be stopped when the reactor shuts down</span>
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">addSystemEventTrigger</span>(<span class="py-src-string">'after'</span>, <span class="py-src-string">'shutdown'</span>, <span class="py-src-variable">wsgiThreadPool</span>.<span class="py-src-variable">stop</span>)
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">application</span>(<span class="py-src-parameter">environ</span>, <span class="py-src-parameter">start_response</span>):
+ <span class="py-src-string">&quot;&quot;&quot;A basic WSGI application&quot;&quot;&quot;</span>
+ <span class="py-src-variable">start_response</span>(<span class="py-src-string">'200 OK'</span>, [(<span class="py-src-string">'Content-type'</span>,<span class="py-src-string">'text/plain'</span>)])
+ <span class="py-src-keyword">return</span> [<span class="py-src-string">'Hello World!'</span>]
+
+<span class="py-src-comment"># Create the WSGI resource</span>
+<span class="py-src-variable">wsgiAppAsResource</span> = <span class="py-src-variable">WSGIResource</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">wsgiThreadPool</span>, <span class="py-src-variable">application</span>)
+
+<span class="py-src-comment"># Hooks for twistd</span>
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'Twisted.web.wsgi Hello World Example'</span>)
+<span class="py-src-variable">server</span> = <span class="py-src-variable">strports</span>.<span class="py-src-variable">service</span>(<span class="py-src-string">'tcp:8080'</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">wsgiAppAsResource</span>))
+<span class="py-src-variable">server</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">application</span>)
+</pre>
+
+<p>This can then be run like any other .tac file:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd -ny myapp.tac
+</pre>
+
+<p>Because of the synchronous nature of WSGI, each application call (for
+each request) is called within a thread, and the result is written back to the
+web server. For this, a <code class="api">twisted.python.threadpool.ThreadPool</code>
+instance is used.</p>
+
+<h3>Using VHostMonster<a name="auto20"/></h3>
+
+<p>It is common to use one server (for example, Apache) on a site with multiple
+names which then uses reverse proxy (in Apache, via <code>mod_proxy</code>) to different
+internal web servers, possibly on different machines. However, naive
+configuration causes miscommunication: the internal server firmly believes it
+is running on <q>internal-name:port</q>, and will generate URLs to that effect,
+which will be completely wrong when received by the client.</p>
+
+<p>While Apache has the ProxyPassReverse directive, it is really a hack
+and is nowhere near comprehensive enough. Instead, the recommended practice
+in case the internal web server is Twisted.Web is to use VHostMonster.</p>
+
+<p>From the Twisted side, using VHostMonster is easy: just drop a file named
+(for example) <code>vhost.rpy</code> containing the following:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">vhost</span>
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">vhost</span>.<span class="py-src-variable">VHostMonsterResource</span>()
+</pre>
+
+<p>Of course, an equivalent <code>.trp</code> can also be used. Make sure
+the web server is configured with the correct processors for the
+<code>rpy</code> or <code>trp</code> extensions (the web server
+<code>twistd web --path</code> generates by default is so configured).</p>
+
+<p>From the Apache side, instead of using the following ProxyPass directive:</p>
+
+<pre xml:space="preserve">
+&lt;VirtualHost ip-addr&gt;
+ProxyPass / http://localhost:8538/
+ServerName example.com
+&lt;/VirtualHost&gt;
+</pre>
+
+<p>Use the following directive:</p>
+
+<pre xml:space="preserve">
+&lt;VirtualHost ip-addr&gt;
+ProxyPass / http://localhost:8538/vhost.rpy/http/example.com:80/
+ServerName example.com
+&lt;/VirtualHost&gt;
+</pre>
+
+<p>Here is an example for Twisted.Web's reverse proxy:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">internet</span>, <span class="py-src-variable">service</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">proxy</span>, <span class="py-src-variable">server</span>, <span class="py-src-variable">vhost</span>
+<span class="py-src-variable">vhostName</span> = <span class="py-src-string">'example.com'</span>
+<span class="py-src-variable">reverseProxy</span> = <span class="py-src-variable">proxy</span>.<span class="py-src-variable">ReverseProxyResource</span>(<span class="py-src-string">'internal'</span>, <span class="py-src-number">8538</span>,
+ <span class="py-src-string">'/vhost.rpy/http/'</span>+<span class="py-src-variable">vhostName</span>+<span class="py-src-string">'/'</span>)
+<span class="py-src-variable">root</span> = <span class="py-src-variable">vhost</span>.<span class="py-src-variable">NameVirtualHost</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">addHost</span>(<span class="py-src-variable">vhostName</span>, <span class="py-src-variable">reverseProxy</span>)
+<span class="py-src-variable">site</span> = <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">'web-proxy'</span>)
+<span class="py-src-variable">sc</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>)
+<span class="py-src-variable">i</span> = <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">80</span>, <span class="py-src-variable">site</span>)
+<span class="py-src-variable">i</span>.<span class="py-src-variable">setServiceParent</span>(<span class="py-src-variable">sc</span>)
+</pre>
+
+<h2>Rewriting URLs<a name="auto21"/></h2>
+
+<p>Sometimes it is convenient to modify the content of the
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">Request</a></code> object
+before passing it on. Because this is most often used to rewrite
+either the URL, the similarity to Apache's <code>mod_rewrite</code> has
+inspired the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.rewrite.html" title="twisted.web.rewrite">twisted.web.rewrite</a></code> module. Using
+this module is done via wrapping a resource with a
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.rewrite.RewriterResource.html" title="twisted.web.rewrite.RewriterResource">twisted.web.rewrite.RewriterResource</a></code> which
+then has rewrite rules. Rewrite rules are functions which accept a request
+object, and possible modify it. After all rewrite rules run, the child
+resolution chain continues as if the wrapped resource, rather than
+the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.rewrite.RewriterResource.html" title="twisted.web.rewrite.RewriterResource">RewriterResource</a></code>,
+was the child.</p>
+
+<p>Here is an example, using the only rule currently supplied by Twisted
+itself:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">default_root</span> = <span class="py-src-variable">rewrite</span>.<span class="py-src-variable">RewriterResource</span>(<span class="py-src-variable">default</span>, <span class="py-src-variable">rewrite</span>.<span class="py-src-variable">tildeToUsers</span>)
+</pre>
+
+<p>This causes the URL <code>/~foo/bar.html</code> to be treated like
+<code>/users/foo/bar.html</code>. If done after setting default's
+<code>users</code> child to a
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.distrib.UserDirectory.html" title="twisted.web.distrib.UserDirectory">distrib.UserDirectory</a></code>,
+it gives a configuration similar to the classical configuration of
+web server, common since the first NCSA servers.</p>
+
+<h2>Knowing When We're Not Wanted<a name="auto22"/></h2>
+
+<p>Sometimes it is useful to know when the other side has broken the connection.
+Here is an example which does that:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">util</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">println</span>
+
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExampleResource</span>(<span class="py-src-parameter">Resource</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;hello world&quot;</span>)
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">request</span>.<span class="py-src-variable">notifyFinish</span>()
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">_</span>: <span class="py-src-variable">println</span>(<span class="py-src-string">&quot;finished normally&quot;</span>))
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">println</span>, <span class="py-src-string">&quot;error&quot;</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">10</span>, <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">server</span>.<span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">ExampleResource</span>()
+</pre>
+
+<p>This will allow us to run statistics on the log-file to see how many users
+are frustrated after merely 10 seconds.</p>
+
+<h2>As-Is Serving<a name="auto23"/></h2>
+
+<p>Sometimes, you want to be able to send headers and status directly. While
+you can do this with a
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.script.ResourceScript.html" title="twisted.web.script.ResourceScript">ResourceScript</a></code>, an easier
+way is to use <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.static.AsIsProcessor.html" title="twisted.web.static.AsIsProcessor">AsIsProcessor</a></code>.
+Use it by, for example, addding it as a processor for the <code>.asis</code>
+extension. Here is a sample file:</p>
+
+<pre xml:space="preserve">
+HTTP/1.0 200 OK
+Content-Type: text/html
+
+Hello world
+</pre>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-development.html b/vendor/Twisted-10.0.0/doc/web/howto/web-development.html
new file mode 100644
index 0000000000..481a8b3184
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-development.html
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Web Application Development</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Web Application Development</h1>
+ <div class="toc"><ol><li><a href="#auto0">Code layout</a></li><li><a href="#auto1">Web application deployment</a></li><li><a href="#auto2">Understanding resource scripts (.rpy files)</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Code layout<a name="auto0"/></h2>
+
+<p>The development of a Twisted Web application should be orthogonal to its
+deployment. This means is that if you are developing a web application, it
+should be a resource with children, and internal links. Some of the children
+might use <a href="http://www.divmod.org/projects/nevow" shape="rect">Nevow</a>, some
+might be resources manually using <code>.write</code>, and so on. Regardless,
+the code should be in a Python module, or package, <em>outside</em> the web
+tree.</p>
+
+<p>You will probably want to test your application as you develop it. There are
+many ways to test, including dropping an <code>.rpy</code> which looks
+like:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">mypackage</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">toplevel</span>
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">toplevel</span>.<span class="py-src-variable">Resource</span>(<span class="py-src-variable">file</span>=<span class="py-src-string">&quot;foo/bar&quot;</span>, <span class="py-src-variable">color</span>=<span class="py-src-string">&quot;blue&quot;</span>)
+</pre>
+
+<p>into a directory, and then running:</p>
+
+<pre class="shell" xml:space="preserve">
+% twistd web --path=/directory
+</pre>
+
+<p>You can also write a Python script like:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-comment">#!/usr/bin/env python</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">mypackage</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">toplevel</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8080</span>,
+ <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">toplevel</span>.<span class="py-src-variable">Resource</span>(<span class="py-src-variable">file</span>=<span class="py-src-string">&quot;foo/bar&quot;</span>, <span class="py-src-variable">color</span>=<span class="py-src-string">&quot;blue&quot;</span>)))
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<h2>Web application deployment<a name="auto1"/></h2>
+
+<p>Which one of these development strategies you use is not terribly important,
+since (and this is the important part) deployment is <em>orthogonal</em>.
+Later, when you want users to actually <em>use</em> your code, you should worry
+about what to do -- or rather, don't. Users may have widely different needs.
+Some may want to run your code in a different process, so they'll use
+distributed web (<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.distrib.html" title="twisted.web.distrib">twisted.web.distrib</a></code>). Some may be
+using the <code>twisted-web</code> Debian package, and will drop in:</p>
+
+<pre class="shell" xml:space="preserve">
+% cat &gt; /etc/local.d/99addmypackage.py
+from mypackage import toplevel
+default.putChild(&quot;mypackage&quot;, toplevel.Resource(file=&quot;foo/bar&quot;, color=&quot;blue&quot;))
+^D
+</pre>
+
+<p>If you want to be friendly to your users, you can supply many examples in
+your package, like the above <code>.rpy</code> and the Debian-package drop-in.
+But the <em>ultimate</em> friendliness is to write a useful resource which does
+not have deployment assumptions built in.</p>
+
+<h2>Understanding resource scripts (<code>.rpy</code> files)<a name="auto2"/></h2>
+
+<p>Twisted Web is not PHP -- it has better tools for organizing code Python
+modules and packages, so use them. In PHP, the only tool for organizing code is
+a web page, which leads to silly things like PHP pages full of functions that
+other pages import, and so on. If you were to write your code this way with
+Twisted Web, you would do web development using many <code>.rpy</code> files,
+all importing some Python module. This is a <em>bad idea</em> -- it mashes
+deployment with development, and makes sure your users will be <em>tied</em> to
+the file-system.</p>
+
+<p>We have <code>.rpy</code>s because they are useful and necessary. But using
+them incorrectly leads to horribly unmaintainable applications. The best way to
+ensure you are using them correctly is to not use them at all, until you are on
+your <em>final</em> deployment stages. You should then find your
+<code>.rpy</code> files will be less than 10 lines, because you will not
+<em>have</em> more than 10 lines to write.</p>
+
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/asynchronous-deferred.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/asynchronous-deferred.html
new file mode 100644
index 0000000000..6d63b9531c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/asynchronous-deferred.html
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Asynchronous Responses (via Deferred)</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Asynchronous Responses (via Deferred)</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The previous example had a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code> that generates its response
+asynchronously rather than immediately upon the call to its render
+method. Though it was a useful demonstration of the <code>NOT_DONE_YET</code>
+feature of Twisted Web, the example didn't reflect what a realistic application
+might want to do. This example introduces <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code>, the Twisted class which is used
+to provide a uniform interface to many asynchronous events, and shows you an
+example of using a <code>Deferred</code>-returning API to generate an
+asynchronous response to a request in Twisted Web.</p>
+
+<p><code>Deferred</code> is the result of two consequences of the asynchronous
+programming approach. First, asynchronous code is frequently (if not always)
+concerned with some data (in Python, an object) which is not yet available but
+which probably will be soon. Asynchronous code needs a way to define what will
+be done to the object once it does exist. It also needs a way to define how to
+handle errors in the creation or acquisition of that object. These two needs are
+satisfied by the <i>callbacks</i> and <i>errbacks</i> of a
+<code>Deferred</code>. Callbacks are added to a <code>Deferred</code> with <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.addCallback.html" title="twisted.internet.defer.Deferred.addCallback">Deferred.addCallback</a></code>; errbacks
+are added with <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.addErrback.html" title="twisted.internet.defer.Deferred.addErrback">Deferred.addErrback</a></code>. When the object
+finally does exist, it is passed to <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.callback.html" title="twisted.internet.defer.Deferred.callback">Deferred.callback</a></code> which passes it on to the
+callback added with <code>addCallback</code>. Similarly, if an error occurs,
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.errback.html" title="twisted.internet.defer.Deferred.errback">Deferred.errback</a></code> is
+called and the error is passed along to the errback added with
+<code>addErrback</code>. Second, the events that make asynchronous code actually
+work often take many different, incompatible forms. <code>Deferred</code> acts
+as the uniform interface which lets different parts of an asynchronous
+application interact and isolates them from implementation details they
+shouldn't be concerned with.</p>
+
+<p>That's almost all there is to Deferred. To solidify your new understanding,
+now consider this rewritten version of DelayedResource which uses a
+Deferred-based delay API. It does exactly the same thing as the <a href="asynchronous.html" shape="rect">previous example</a>. Only the implementation is
+different.</p>
+
+<p>First, the example must import that new API that was just mentioned, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.task.deferLater.html" title="twisted.internet.task.deferLater">deferLater</a></code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">task</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">deferLater</span>
+</pre>
+
+<p>Next, all the other imports (these are the same as last time):</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<p>With the imports done, here's the first part of the
+<code>DelayedResource</code> implementation. Again, this part of the code is
+identical to the previous version:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Sorry to keep you waiting.&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+</pre>
+
+<p>Next we need to define the render method. Here's where things change a
+bit. Instead of using <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTime.callLater.html" title="twisted.internet.interfaces.IReactorTime.callLater">callLater</a></code>, We're going to
+use <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.task.deferLater.html" title="twisted.internet.task.deferLater">deferLater</a></code> this
+time. <code>deferLater</code> accepts a reactor, delay (in seconds, as with
+<code>callLater</code>), and a function to call after the delay to produce that
+elusive object discussed in the description of Deferreds. We're also going to
+use <code>_delayedRender</code> as the callback to add to the
+<code>Deferred</code> returned by <code>deferLater</code>. Since it expects the
+request object as an argument, we're going to set up the <code>deferLater</code>
+call to return a <code>Deferred</code> which has the request object as its
+result.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">deferLater</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">5</span>, <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">request</span>)
+</pre>
+
+<p>The <code>Deferred</code> referenced by <code>d</code> now needs to have the
+<code>_delayedRender</code> callback added to it. Once this is done,
+<code>_delayedRender</code> will be called with the result of <code>d</code>
+(which will be <code>request</code>, of course — the result of <code>(lambda:
+request)()</code>).</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>)
+</pre>
+
+<p>Finally, the render method still needs to return <code>NOT_DONE_YET</code>,
+for exactly the same reasons as it did in the previous version of the
+example.</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+</pre>
+
+<p>And with that, <code>DelayedResource</code> is now implemented based on a
+<code>Deferred</code>. The example still isn't very realistic, but remember that
+since Deferreds offer a uniform interface to many different asynchronous event
+sources, this code now resembles a real application even more closely; you could
+easily replace <code>deferLater</code> with another
+<code>Deferred</code>-returning API and suddenly you might have a resource that
+does something useful.</p>
+
+<p>Finally, here's the complete, uninterrupted example source, as an rpy script:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span>.<span class="py-src-variable">task</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">deferLater</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Sorry to keep you waiting.&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">d</span> = <span class="py-src-variable">deferLater</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-number">5</span>, <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">request</span>)
+ <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">DelayedResource</span>()
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/asynchronous.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/asynchronous.html
new file mode 100644
index 0000000000..11584d5ddf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/asynchronous.html
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Asynchronous Responses</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Asynchronous Responses</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>In all of the previous examples, the resource examples presented generated
+responses immediately. One of the features of prime interest of Twisted Web,
+though, is the ability to generate a response over a longer period of time while
+leaving the server free to respond to other requests. In other words,
+asynchronously. In this installment, we'll write a resource like this.</p>
+
+<p>A resource that generates a response asynchronously looks like one that
+generates a response synchronously in many ways. The same base class, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>, is used either way; the
+same render methods are used. There are three basic differences, though.</p>
+
+<p>First, instead of returning the string which will be used as the body of the
+response, the resource uses <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.http.Request.write.html" title="twisted.web.http.Request.write">Request.write</a></code>. This method can be called
+repeatedly. Each call appends another string to the response body. Second, when
+the entire response body has been passed to <code>Request.write</code>, the
+application must call <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.http.Request.finish.html" title="twisted.web.http.Request.finish">Request.finish</a></code>. As you might expect from the
+name, this ends the response. Finally, in order to make Twisted Web not end the
+response as soon as the render method returns, the render method must return
+<code>NOT_DONE_YET</code>. Consider this example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Sorry to keep you waiting.&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>, <span class="py-src-variable">request</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+</pre>
+
+<p>If you're not familiar with reactor.<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IReactorTime.callLater.html" title="twisted.internet.interfaces.IReactorTime.callLater">callLater</a></code>, all you really
+need to know about it to understand this example is that the above usage of it
+arranges to have <code>self._delayedRender(request)</code> run about 5 seconds
+after <code>callLater</code> is invoked from this render method and that it
+returns immediately.</p>
+
+<p>All three of the elements mentioned earlier can be seen in this example. The
+resource uses <code>Request.write</code> to set the response body. It uses
+<code>Request.finish</code> after the entire body has been specified (all with
+just one call to write in this case). Lastly, it returns
+<code>NOT_DONE_YET</code> from its render method. So there you have it,
+asynchronous rendering with Twisted Web.</p>
+
+<p>Here's a complete rpy script based on this resource class (see the <a href="rpy-scripts.html" shape="rect">previous example</a> if you need a reminder about rpy
+scripts):</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Sorry to keep you waiting.&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>, <span class="py-src-variable">request</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">DelayedResource</span>()
+</pre>
+
+<p>Drop this source into a <code>.rpy</code> file and fire up a server using
+<code>twistd -n web --path /directory/containing/script/.</code> You'll see that
+loading the page takes 5 seconds. If you try to load a second before the first
+completes, it will also take 5 seconds from the time you request it (but it
+won't be delayed by any other outstanding requests).</p>
+
+<p>Something else to consider when generating responses asynchronously is that
+the client may not wait around to get the response to its
+request. A <a href="interrupted.html" shape="rect">subsequent example</a> demonstrates how
+to detect that the client has abandoned the request and that the server
+shouldn't bother to finish generating its response.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/custom-codes.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/custom-codes.html
new file mode 100644
index 0000000000..2b68465436
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/custom-codes.html
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Custom Response Codes</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Custom Response Codes</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The previous example introduced <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.error.NoResource.html" title="twisted.web.error.NoResource">NoResource</a></code>, a Twisted Web error
+resource which responds with a 404 (not found) code. This example will
+cover the APIs that <code>NoResource</code> uses to do this so that
+you can generate your own custom response codes as desired.</p>
+
+<p>First, the now-standard import preamble:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<p>Now we'll define a new resource class that always returns a 402
+(payment required) response. This is really not very different from
+the resources that was defined in previous examples. The fact that it
+has a response code other than 200 doesn't change anything else about
+its role. This will require using the request object, though, which
+none of the previous examples have done.</p>
+
+<p>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">Request</a></code>
+object has shown up in a couple of places, but so far we've ignored
+it. It is a parameter to the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.getChild.html" title="twisted.web.resource.Resource.getChild">getChild</a></code> API as well as to
+render methods such as <code>render_GET</code>. As you might have
+suspected, it represents the request for which a response is to be
+generated. Additionally, it also represents the response being
+generated. In this example we're going to use its <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.http.Request.setResponseCode.html" title="twisted.web.http.Request.setResponseCode">setResponseCode</a></code> method to - you
+guessed it - set the response's status code.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">PaymentRequired</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">setResponseCode</span>(<span class="py-src-number">402</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Please swipe your credit card.&quot;</span>
+</pre>
+
+<p>Just like the other resources I've demonstrated, this one returns a
+string from its <code>render_GET</code> method to define the body of
+the response. All that's different is the call to
+<code>setResponseCode</code> to override the default response code,
+200, with a different one.</p>
+
+<p>Finally, the code to set up the site and reactor. We'll put an
+instance of the above defined resource at <code>/buy</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;buy&quot;</span>, <span class="py-src-variable">PaymentRequired</span>())
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Here's the complete example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PaymentRequired</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">setResponseCode</span>(<span class="py-src-number">402</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Please swipe your credit card.&quot;</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;buy&quot;</span>, <span class="py-src-variable">PaymentRequired</span>())
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Run the server and visit <code>http://localhost:8880/buy</code> in
+your browser. It'll look pretty boring, but if you use Firefox's View
+Page Info right-click menu item (or your browser's equivalent), you'll
+be able to see that the server indeed sent back a 402 response
+code.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/dynamic-content.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/dynamic-content.html
new file mode 100644
index 0000000000..04524af396
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/dynamic-content.html
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Generating a Page Dynamically</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Generating a Page Dynamically</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The goal of this example is to show you how to dynamically generate the
+contents of a page.</p>
+
+<p>Taking care of some of the necessary imports first, we'll import <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code> and the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+</pre>
+
+<p>The Site is a factory which associates a listening port with the HTTP
+protocol implementation. The reactor is the main loop that drives any Twisted
+application; we'll use it to actually create the listening port in a moment.</p>
+
+<p>Next, we'll import one more thing from Twisted Web: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>. An instance of
+<code>Resource</code> (or a subclass) represents a page (technically, the entity
+addressed by a URI).</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+</pre>
+
+<p>Since we're going to make the demo resource a clock, we'll also import the
+time module:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+</pre>
+
+<p>With imports taken care of, the next step is to define a
+<code>Resource</code> subclass which has the dynamic rendering behavior we
+want. Here's a resource which generates a page giving the time:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ClockPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">isLeaf</span> = <span class="py-src-variable">True</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;%s&quot;</span> % (<span class="py-src-variable">time</span>.<span class="py-src-variable">ctime</span>(),)
+</pre>
+
+<p>Setting <code>isLeaf</code> to <code>True</code> indicates that
+<code>ClockPage</code> resources will never have any children.</p>
+
+<p>The <code>render_GET</code> method here will be called whenever the URI we
+hook this resource up to is requested with the <code>GET</code> method. The byte
+string it returns is what will be sent to the browser.</p>
+
+<p>With the resource defined, we can create a <code>Site</code> from it:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">resource</span> = <span class="py-src-variable">ClockPage</span>()
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>)
+</pre>
+
+<p>Just as with the previous static content example, this configuration puts our
+resource at the very top of the URI hierarchy, ie at <code>/</code>. With that
+<code>Site</code> instance, we can tell the reactor to <a href="../../../core/howto/servers.html" shape="rect">create a TCP server</a> and start
+servicing requests:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Here's the code with no interruptions:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClockPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">isLeaf</span> = <span class="py-src-variable">True</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;%s&quot;</span> % (<span class="py-src-variable">time</span>.<span class="py-src-variable">ctime</span>(),)
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">ClockPage</span>()
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/dynamic-dispatch.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/dynamic-dispatch.html
new file mode 100644
index 0000000000..1bb127a050
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/dynamic-dispatch.html
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Dynamic URL Dispatch</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Dynamic URL Dispatch</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>In the <a href="static-dispatch.html" shape="rect">previous example</a> we covered how to
+statically configure Twisted Web to serve different content at different
+URLs. The goal of this example is to show you how to do this dynamically
+instead. Reading the previous installment if you haven't already is suggested in
+order to get an overview of how URLs are treated when using Twisted Web's <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.html" title="twisted.web.resource">resource</a></code> APIs.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code> (the object which
+associates a listening server port with the HTTP implementation), <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code> (a convenient base class
+to use when defining custom pages), and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code> (the object which implements the Twisted
+main loop) return once again:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<p>With that out of the way, here's the interesting part of this example. We're
+going to define a resource which renders a whole-year calendar. The year it will
+render the calendar for will be the year in the request URL. So, for example,
+<code>/2009</code> will render a calendar for 2009. First, here's a resource
+that renders a calendar for the year passed to its initializer:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+9
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">calendar</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">calendar</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">YearPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">year</span>):
+ <span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">year</span> = <span class="py-src-variable">year</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;%s&quot;</span> % (<span class="py-src-variable">calendar</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">year</span>),)
+</pre>
+
+<p>Pretty simple - not all that different from the first dynamic resource
+demonstrated in <a href="dynamic-content.html" shape="rect">Generating a Page
+Dynamically</a>. Now here's the resource that handles URLs with a year in them
+by creating a suitable instance of this <code>YearPage</code> class:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Calendar</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">YearPage</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">name</span>))
+</pre>
+
+<p>By implementing <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.getChild.html" title="twisted.web.resource.Resource.getChild">getChild</a></code> here, we've just defined
+how Twisted Web should find children of <code>Calendar</code> instances when
+it's resolving an URL into a resource. This implementation defines all integers
+as the children of <code>Calendar</code> (and punts on error handling, more on
+that later).</p>
+
+<p>All that's left is to create a <code>Site</code> using this resource as its
+root and then start the reactor:</p>
+
+<pre xml:space="preserve">
+root = Calendar()
+factory = Site(root)
+reactor.listenTCP(8880, factory)
+reactor.run()
+</pre>
+
+<p>And that's all. Any resource-based dynamic URL handling is going to look
+basically like <code>Calendar.getPage</code>. Here's the full example code:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">calendar</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">calendar</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">YearPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">year</span>):
+ <span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">year</span> = <span class="py-src-variable">year</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;%s&quot;</span> % (<span class="py-src-variable">calendar</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">year</span>),)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Calendar</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">YearPage</span>(<span class="py-src-variable">int</span>(<span class="py-src-variable">name</span>))
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Calendar</span>()
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/error-handling.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/error-handling.html
new file mode 100644
index 0000000000..f481bab31a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/error-handling.html
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Error Handling</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Error Handling</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>In this example we'll extend dynamic dispatch to return a 404 (not found)
+response when a client requests a non-existent URL.</p>
+
+<p>As in the previous examples, we'll start with <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>, and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code> imports:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<p>Next, we'll add one more import. <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.error.NoResource.html" title="twisted.web.error.NoResource">NoResource</a></code> is one of the pre-defined error
+resources provided by Twisted Web. It generates the necessary 404 response code
+and renders a simple html page telling the client there is no such resource.</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">error</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NoResource</span>
+</pre>
+
+<p>Next, we'll define a custom resource which does some dynamic URL
+dispatch. This example is going to be just like the <a href="dynamic-dispatch.html" shape="rect">previous one</a>, where the path segment is
+interpreted as a year; the difference is that this time we'll handle requests
+which don't conform to that pattern by returning the not found response:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">Calendar</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">year</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">name</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">ValueError</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NoResource</span>()
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">YearPage</span>(<span class="py-src-variable">year</span>)
+</pre>
+
+<p>Aside from including the definition of <code>YearPage</code> from the
+previous example, the only other thing left to do is the normal
+<code>Site</code> and <code>reactor</code> setup. Here's the complete code for
+this example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">error</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NoResource</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">calendar</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">calendar</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">YearPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">year</span>):
+ <span class="py-src-variable">Resource</span>.<span class="py-src-variable">__init__</span>(<span class="py-src-variable">self</span>)
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">year</span> = <span class="py-src-variable">year</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;%s&quot;</span> % (<span class="py-src-variable">calendar</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">year</span>),)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Calendar</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">getChild</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">name</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">try</span>:
+ <span class="py-src-variable">year</span> = <span class="py-src-variable">int</span>(<span class="py-src-variable">name</span>)
+ <span class="py-src-keyword">except</span> <span class="py-src-variable">ValueError</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NoResource</span>()
+ <span class="py-src-keyword">else</span>:
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">YearPage</span>(<span class="py-src-variable">year</span>)
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Calendar</span>()
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>This server hands out the same calendar views as the one from the previous
+installment, but it will also hand out a nice error page with a 404 response
+when a request is made for a URL which cannot be interpreted as a year.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/handling-posts.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/handling-posts.html
new file mode 100644
index 0000000000..5a3d98ca04
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/handling-posts.html
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Handling POSTs</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Handling POSTs</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>All of the previous examples have focused on <code>GET</code>
+requests. Unlike <code>GET</code> requests, <code>POST</code> requests can have
+a request body - extra data after the request headers; for example, data
+representing the contents of an HTML form. Twisted Web makes this data available
+to applications via the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">Request</a></code> object.</p>
+
+<p>Here's an example web server which renders a static HTML form and then
+generates a dynamic page when that form is posted back to it. Disclaimer: While
+it's convenient for this example, it's often not a good idea to make a resource
+that <code>POST</code>s to itself; this isn't about Twisted Web, but the nature
+of HTTP in general; if you do this in a real application, make sure you
+understand the possible negative consequences.</p>
+
+<p>As usual, we start with some imports. In addition to the Twisted imports,
+this example uses the <code>cgi</code> module to <a href="http://en.wikipedia.org/wiki/Cross-site_scripting" shape="rect">escape user-entered
+content</a> for inclusion in the output.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+</pre>
+
+<p>Next, we'll define a resource which is going to do two things. First, it will
+respond to <code>GET</code> requests with a static HTML form:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">FormPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">''</span>
+</pre>
+
+<p>This is similar to the resource used in a <a href="dynamic-content.html" shape="rect">previous installment</a>. However, we'll now add
+one more method to give it a second behavior; this <code>render_POST</code>
+method will allow it to accept <code>POST</code> requests:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">render_POST</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'You submitted: %s'</span> % (<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">args</span>[<span class="py-src-string">&quot;the-field&quot;</span>][<span class="py-src-number">0</span>]),)
+</pre>
+
+<p>The main thing to note here is the use of <code>request.args</code>. This is
+a dictionary-like object that provides access to the contents of the form. The
+keys in this dictionary are the names of inputs in the form. Each value is a
+list containing strings (since there can be multiple inputs with the same name),
+which is why we had to extract the first element to pass to
+<code>cgi.escape</code>. <code>request.args</code> will be populated from form
+contents whenever a <code>POST</code> request is made with a content type of
+<code>application/x-www-form-urlencoded</code> or
+<code>multipart/form-data</code> (it's also populated by query arguments for any
+type of request).</p>
+
+<p>Finally, the example just needs the usual site creation and port setup:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;form&quot;</span>, <span class="py-src-variable">FormPage</span>())
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Run the server and visit <a href="http://localhost:8880/form" shape="rect">http://localhost:8880/form</a>, submit the
+form, and watch it generate a page including the value you entered into the
+single field.</p>
+
+<p>Here's the complete source for the example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">import</span> <span class="py-src-variable">cgi</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">FormPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">''</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_POST</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'You submitted: %s'</span> % (<span class="py-src-variable">cgi</span>.<span class="py-src-variable">escape</span>(<span class="py-src-variable">request</span>.<span class="py-src-variable">args</span>[<span class="py-src-string">&quot;the-field&quot;</span>][<span class="py-src-number">0</span>]),)
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;form&quot;</span>, <span class="py-src-variable">FormPage</span>())
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/http-auth.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/http-auth.html
new file mode 100644
index 0000000000..fd4036f321
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/http-auth.html
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: HTTP Authentication</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">HTTP Authentication</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>Many of the previous examples have looked at how to serve content by using
+existing resource classes or implementing new ones. In this example we'll use
+Twisted Web's basic or digest HTTP authentication to control access to these
+resources.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.guard.html" title="twisted.web.guard">guard</a></code>, the Twisted Web
+module which provides most of the APIs that will be used in this example, helps
+you to
+add <a href="http://en.wikipedia.org/wiki/Authentication" shape="rect">authentication</a>
+and <a href="http://en.wikipedia.org/wiki/Authorization" shape="rect">authorization</a> to a
+resource hierarchy. It does this by providing a resource which implements
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.getChild.html" title="twisted.web.resource.Resource.getChild">getChild</a></code> to return
+a <a href="dynamic-dispatch.html" shape="rect">dynamically selected resource</a>. The
+selection is based on the authentication headers in the request. If those
+headers indicate that the request is made on behalf of Alice, then Alice's
+resource will be returned. If they indicate that it was made on behalf of Bob,
+his will be returned. If the headers contain invalid credentials, an error
+resource is returned. Whatever happens, once this resource is returned, URL
+traversal continues as normal from that resource.</p>
+
+<p>The resource that implements this is <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.guard.HTTPAuthSessionWrapper.html" title="twisted.web.guard.HTTPAuthSessionWrapper">HTTPAuthSessionWrapper</a></code>, though it is directly
+responsible for very little of the process. It will extract headers from the
+request and hand them off to a credentials factory to parse them according to
+the appropriate standards (eg <a href="http://tools.ietf.org/html/rfc2617" shape="rect">HTTP
+Authentication: Basic and Digest Access Authentication</a>) and then hand the
+resulting credentials object off to a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.Portal.html" title="twisted.cred.portal.Portal">Portal</a></code>, the core
+of <a href="../../../core/howto/cred.html" shape="rect">Twisted Cred</a>, a system for
+uniform handling of authentication and authorization. We won't discuss Twisted
+Cred in much depth here. To make use of it with Twisted Web, the only thing you
+really need to know is how to implement an <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.IRealm.html" title="twisted.cred.portal.IRealm">IRealm</a></code>.</p>
+
+<p>You need to implement a realm because the realm is the object that actually
+decides which resources are used for which users. This can be as complex or as
+simple as it suitable for your application. For this example we'll keep it very
+simple: each user will have a resource which is a static file listing of the
+<code>public_html</code> directory in their UNIX home directory. First, we need
+to import <code>implements</code> from <code>zope.interface</code> and <code>IRealm</code> from
+<code>twisted.cred.portal</code>. Together these will let me mark this class as
+a realm (this is mostly - but not entirely - a documentation thing). We'll also
+need <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.static.File.html" title="twisted.web.static.File">File</a></code> for the actual
+implementation later.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PublicHTMLRealm</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
+</pre>
+
+<p>A realm only needs to implement one method: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.portal.IRealm.requestAvatar.html" title="twisted.cred.portal.IRealm.requestAvatar">requestAvatar</a></code>. This method is called
+after any successful authentication attempt (ie, Alice supplied the right
+password). Its job is to return the <i>avatar</i> for the user who succeeded in
+authenticating. An <i>avatar</i> is just an object that represents a user. In
+this case, it will be a <code>File</code>. In general, with <code>Guard</code>,
+the avatar must be a resource of some sort.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">IResource</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-keyword">return</span> (<span class="py-src-variable">IResource</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/home/%s/public_html&quot;</span> % (<span class="py-src-variable">avatarId</span>,)), <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">None</span>)
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>()
+</pre>
+
+<p>A few notes on this method:</p>
+<ul>
+ <li>The <code>avatarId</code> parameter is essentially the username. It's the
+ job of some other code to extract the username from the request headers and
+ make sure it gets passed here.</li>
+ <li>The <code>mind</code> is always <code>None</code> when writing a realm to
+ be used with <code>Guard</code>. You can ignore it until you want to write a
+ realm for something else.</li>
+ <li><code>Guard</code> is always
+ passed <code class="twisted.web.resource">IResource</code> as
+ the <code>interfaces</code> parameter. If <code>interfaces</code> only
+ contains interfaces your code doesn't understand,
+ raising <code>NotImplementedError</code> is the thing to do, as
+ above. You'll only need to worry about getting a different interface when
+ you write a realm for something other than <code>Guard</code>.</li>
+ <li>If you want to track when a user logs out, that's what the last element of
+ the returned tuple is for. It will be called when this avatar logs
+ out. <code>lambda: None</code> is the idiomatic no-op logout function.</li>
+ <li>Notice that the path handling code in this example is written very
+ poorly. This example may be vulnerable to certain unintentional information
+ disclosure attacks. This sort of problem is exactly the
+ reason <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.filepath.FilePath.html" title="twisted.python.filepath.FilePath">FilePath</a></code>
+ exists. However, that's an example for another day...</li>
+</ul>
+
+<p>We're almost ready to set up the resource for this example. To create an
+<code>HTTPAuthSessionWrapper</code>, though, we need two things. First, a
+portal, which requires the realm above, plus at least one credentials
+checker:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePasswordDB</span>
+
+<span class="py-src-variable">portal</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">PublicHTMLRealm</span>(), [<span class="py-src-variable">FilePasswordDB</span>(<span class="py-src-string">'httpd.password'</span>)])
+</pre>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.cred.checkers.FilePasswordDB.html" title="twisted.cred.checkers.FilePasswordDB">FilePasswordDB</a></code> is the
+credentials checker. It knows how to read <code>passwd(5)</code>-style (loosely)
+files to check credentials against. It is responsible for the authentication
+work after <code>HTTPAuthSessionWrapper</code> extracts the credentials from the
+request.</p>
+
+<p>Next we need either <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.guard.BasicCredentialFactory.html" title="twisted.web.guard.BasicCredentialFactory">BasicCredentialFactory</a></code> or
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.guard.DigestCredentialFactory.html" title="twisted.web.guard.DigestCredentialFactory">DigestCredentialFactory</a></code>. The
+former knows how to challenge HTTP clients to do basic authentication; the
+latter, digest authentication. We'll use digest here:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DigestCredentialFactory</span>
+
+<span class="py-src-variable">credentialFactory</span> = <span class="py-src-variable">DigestCredentialFactory</span>(<span class="py-src-string">&quot;md5&quot;</span>, <span class="py-src-string">&quot;example.org&quot;</span>)
+</pre>
+
+<p>The two parameters to this constructor are the hash algorithm and the HTTP
+authentication realm which will be used. The only other valid hash algorithm is
+&quot;sha&quot; (but be careful, MD5 is more widely supported than SHA). The HTTP
+authentication realm is mostly just a string that is presented to the user to
+let them know why they're authenticating (you can read more about this in the
+<a href="http://tools.ietf.org/html/rfc2617" shape="rect">RFC</a>).</p>
+
+<p>With those things created, we can finally
+instantiate <code>HTTPAuthSessionWrapper</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">HTTPAuthSessionWrapper</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">HTTPAuthSessionWrapper</span>(<span class="py-src-variable">portal</span>, [<span class="py-src-variable">credentialFactory</span>])
+</pre>
+
+<p>There's just one last thing that needs to be done
+here. When <a href="rpy-scripts.html" shape="rect">rpy scripts</a> were introduced, it was
+mentioned that they are evaluated in an unusual context. This is the first
+example that actually needs to take this into account. It so happens that
+<code>DigestCredentialFactory</code> instances are stateful. Authentication will
+only succeed if the same instance is used to both generate challenges and
+examine the responses to those challenges. However, the normal mode of operation
+for an rpy script is for it to be re-executed for every request. This leads to a
+new
+<code>DigestCredentialFactory</code> being created for every request, preventing
+any authentication attempt from ever succeeding.</p>
+
+<p>There are two ways to deal with this. First, and the better of the two ways,
+we could move almost all of the code into a real Python module, including the
+code that instantiates the <code>DigestCredentialFactory</code>. This would
+ensure that the same instance was used for every request. Second, and the easier
+of the two ways, we could add a call to <code>cache()</code> to the beginning of
+the rpy script:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">cache</span>()
+</pre>
+
+<p><code>cache</code> is part of the globals of any rpy script, so you don't
+need to import it (it's okay to be cringing at this
+point). Calling <code>cache</code> makes Twisted re-use the result of the first
+evaluation of the rpy script for subsequent requests too - just what we want in
+this case.</p>
+
+<p>Here's the complete example (with imports re-arranged to the more
+conventional style):</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+</p><span class="py-src-variable">cache</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>, <span class="py-src-variable">Portal</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePasswordDB</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IResource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">HTTPAuthSessionWrapper</span>, <span class="py-src-variable">DigestCredentialFactory</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">PublicHTMLRealm</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">IResource</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
+ <span class="py-src-keyword">return</span> (<span class="py-src-variable">IResource</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/home/%s/public_html&quot;</span> % (<span class="py-src-variable">avatarId</span>,)), <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">None</span>)
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>()
+
+<span class="py-src-variable">portal</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">PublicHTMLRealm</span>(), [<span class="py-src-variable">FilePasswordDB</span>(<span class="py-src-string">'httpd.password'</span>)])
+
+<span class="py-src-variable">credentialFactory</span> = <span class="py-src-variable">DigestCredentialFactory</span>(<span class="py-src-string">&quot;md5&quot;</span>, <span class="py-src-string">&quot;localhost:8080&quot;</span>)
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">HTTPAuthSessionWrapper</span>(<span class="py-src-variable">portal</span>, [<span class="py-src-variable">credentialFactory</span>])
+</pre>
+
+<p>And voila, a password-protected per-user Twisted Web server.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/index.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/index.html
new file mode 100644
index 0000000000..7bca942560
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/index.html
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted.Web In 60 Seconds</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted.Web In 60 Seconds</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<p>This set of examples contains short, complete applications of
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.html" title="twisted.web">twisted.web</a></code>. For subjects not covered here, see
+the <a href="../using-twistedweb.html" shape="rect">Twisted Web tutorial</a> and the API
+documentation.</p>
+
+<ol>
+<li><a href="static-content.html" shape="rect">Serving static content from a directory</a></li>
+<li><a href="dynamic-content.html" shape="rect">Generating a page dynamically</a></li>
+<li><a href="static-dispatch.html" shape="rect">Static URL dispatch</a></li>
+<li><a href="dynamic-dispatch.html" shape="rect">Dynamic URL dispatch</a></li>
+<li><a href="error-handling.html" shape="rect">Error handling</a></li>
+<li><a href="custom-codes.html" shape="rect">Custom response codes</a></li>
+<li><a href="handling-posts.html" shape="rect">Handling POSTs</a></li>
+<li><a href="rpy-scripts.html" shape="rect">rpy scripts (or, how to save yourself some typing)</a></li>
+<li><a href="asynchronous.html" shape="rect">Asynchronous responses</a></li>
+<li><a href="asynchronous-deferred.html" shape="rect">Asynchronous responses (via Deferred)</a></li>
+<li><a href="interrupted.html" shape="rect">Interrupted responses</a></li>
+<li><a href="logging-errors.html" shape="rect">Logging errors</a></li>
+<li><a href="wsgi.html" shape="rect">WSGIs</a></li>
+<li><a href="http-auth.html" shape="rect">HTTP authentication</a></li>
+<li><a href="session-basics.html" shape="rect">Session basics</a></li>
+<li><a href="session-store.html" shape="rect">Storing objects in the session</a></li>
+<li><a href="session-endings.html" shape="rect">Session endings</a></li>
+</ol>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/interrupted.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/interrupted.html
new file mode 100644
index 0000000000..ecd8320bc9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/interrupted.html
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Interrupted Responses</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Interrupted Responses</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The previous example had a Resource that generates its response
+asynchronously rather than immediately upon the call to its render method. When
+generating responses asynchronously, the possibility is introduced that the
+connection to the client may be lost before the response is generated. In such a
+case, it is often desirable to abandon the response generation entirely, since
+there is nothing to do with the data once it is produced. This example shows how
+to be notified that the connection has been lost.</p>
+
+<p>This example will build upon the <a href="asynchronous.html" shape="rect">asynchronous
+responses example</a> which simply (if not very realistically) generated its
+response after a fixed delay. We will expand that resource so that as soon as
+the client connection is lost, the delayed event is cancelled and the response
+is never generated.</p>
+
+<p>The feature this example relies on is provided by another <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">Request</a></code> method: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.http.Request.notifyFinish.html" title="twisted.web.http.Request.notifyFinish">notifyFinish</a></code>. This method returns a new
+Deferred which will fire with <code>None</code> if the request is successfully
+responded to or with an error otherwise - for example if the connection is lost
+before the response is sent.</p>
+
+<p>The example starts in a familiar way, with the requisite Twisted imports and
+a resource class with the same <code>_delayedRender</code> used previously:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+ <span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Sorry to keep you waiting.&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+</pre>
+
+<p>Before defining the render method, we're going to define an errback (an
+errback being a callback that gets called when there's an error), though. This
+will be the errback attached to the <code>Deferred</code> returned by
+<code>Request.notifyFinish</code>. It will cancel the delayed call to
+<code>_delayedRender</code>.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">_responseFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">err</span>, <span class="py-src-parameter">call</span>):
+ <span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+</pre>
+
+<p>Finally, the render method will set up the delayed call just as it did
+before, and return <code>NOT_DONE_YET</code> likewise. However, it will also use
+<code>Request.notifyFinish</code> to make sure <code>_responseFailed</code> is
+called if appropriate.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>, <span class="py-src-variable">request</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">notifyFinish</span>().<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_responseFailed</span>, <span class="py-src-variable">call</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+</pre>
+
+<p>Notice that since <code>_responseFailed</code> needs a reference to the
+delayed call object in order to cancel it, we passed that object to
+<code>addErrback</code>. Any additional arguments passed to
+<code>addErrback</code> (or <code>addCallback</code>) will be passed along to
+the errback after the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code> instance which is always passed as
+the first argument. Passing <code>call</code> here means it will be passed to
+<code>_responseFailed</code>, where it is expected and required.</p>
+
+<p>That covers almost all the code for this example. Here's the entire example
+without interruptions, as an <a href="rpy-scripts.html" shape="rect">rpy script</a>:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Sorry to keep you waiting.&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_responseFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">err</span>, <span class="py-src-parameter">call</span>):
+ <span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>, <span class="py-src-variable">request</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">notifyFinish</span>().<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_responseFailed</span>, <span class="py-src-variable">call</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">DelayedResource</span>()
+</pre>
+
+<p>Toss this into example.rpy, fire it up with <code>twistd -n web --path
+.</code>, and hit <a href="http://localhost:8080/example.rpy" shape="rect">http://localhost:8080/example.rpy</a>. If
+you wait five seconds, you'll get the page content. If you interrupt the request
+before then, say by hitting escape (in Firefox, at least), then you'll see
+perhaps the most boring demonstration ever - no page content, and nothing in the
+server logs. Success!</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/logging-errors.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/logging-errors.html
new file mode 100644
index 0000000000..b14506943f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/logging-errors.html
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Logging Errors</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Logging Errors</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The <a href="interrupted.html" shape="rect">previous example</a> created a server that
+dealt with response errors by aborting response generation, potentially avoiding
+pointless work. However, it did this silently for any error. In this example,
+we'll modify the previous example so that it logs each failed response.</p>
+
+<p>This example will use the Twisted API for logging errors. As was mentioned in
+the <a href="asynchronous-deferred.html" shape="rect">first example covering Deferreds</a>,
+errbacks are passed an error. In the previous example, the
+<code>_responseFailed</code> errback accepted this error as a parameter but
+ignored it. The only way this example will differ is that this
+<code>_responseFailed</code> will use that error parameter to log a message.</p>
+
+<p>This example will require all of the imports required by the previous example
+plus one new import:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">err</span>
+</pre>
+
+<p>The only other part of the previous example which changes is the
+<code>_responseFailed</code> callback, which will now log the error passed to
+it:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">_responseFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>, <span class="py-src-parameter">call</span>):
+ <span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+ <span class="py-src-variable">err</span>(<span class="py-src-variable">failure</span>, <span class="py-src-string">&quot;Async response demo interrupted response&quot;</span>)
+</pre>
+
+<p>We're passing two arguments to <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.log.err.html" title="twisted.python.log.err">err</a></code> here. The first is the error which is being
+passed in to the callback. This is always an object of type <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.failure.Failure.html" title="twisted.python.failure.Failure">Failure</a></code>, a class which represents an
+exception and (sometimes, but not always) a traceback. <code>err</code> will
+format this nicely for the log. The second argument is a descriptive string that
+tells someone reading the log what the source of the error was.</p>
+
+<p>Here's the full example with the two above modifications:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">NOT_DONE_YET</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">log</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">err</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">DelayedResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_delayedRender</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">write</span>(<span class="py-src-string">&quot;Sorry to keep you waiting.&quot;</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">finish</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_responseFailed</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>, <span class="py-src-parameter">call</span>):
+ <span class="py-src-variable">call</span>.<span class="py-src-variable">cancel</span>()
+ <span class="py-src-variable">err</span>(<span class="py-src-variable">failure</span>, <span class="py-src-string">&quot;Async response demo interrupted response&quot;</span>)
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">call</span> = <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">5</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">_delayedRender</span>, <span class="py-src-variable">request</span>)
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">notifyFinish</span>().<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">_responseFailed</span>, <span class="py-src-variable">call</span>)
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">NOT_DONE_YET</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">DelayedResource</span>()
+</pre>
+
+<p>Run this server as in the <a href="interrupted.html" shape="rect">previous example</a>
+and interrupt a request. Unlike the previous example, where the server gave no
+indication that this had happened, you'll see a message in the log output with
+this version.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/rpy-scripts.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/rpy-scripts.html
new file mode 100644
index 0000000000..19be767388
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/rpy-scripts.html
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: rpy scripts (or, how to save yourself some typing)</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">rpy scripts (or, how to save yourself some typing)</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The goal of this installment is to show you another way to run a Twisted Web
+server with a custom resource which doesn't require as much code as the previous
+examples.</p>
+
+<p>The feature in question is called an <code>rpy script</code>. An rpy script
+is a Python source file which defines a resource and can be loaded into a
+Twisted Web server. The advantages of this approach are that you don't have to
+write code to create the site or set up a listening port with the reactor. That
+means fewer lines of code that aren't dedicated to the task you're trying to
+accomplish.</p>
+
+<p>There are some disadvantages, though. An rpy script must have the extension
+<code>.rpy</code>. This means you can't import it using the usual Python import
+statement. This means it's hard to re-use code in an rpy script. This also means
+you can't easily unit test it. The code in an rpy script is evaluated in an
+unusual context. So, while rpy scripts may be useful for testing out ideas,
+they're not recommend for much more than that.</p>
+
+<p>Okay, with that warning out of the way, let's dive in. First, as mentioned,
+rpy scripts are Python source files with the <code>.rpy</code> extension. So,
+open up an appropriately named file (for example, <code>example.rpy</code>) and
+put this code in it:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ClockPage</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">isLeaf</span> = <span class="py-src-variable">True</span>
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;%s&quot;</span> % (<span class="py-src-variable">time</span>.<span class="py-src-variable">ctime</span>(),)
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">ClockPage</span>()
+</pre>
+
+<p>You may recognize this as the resource from the <a href="dynamic-content.html" shape="rect">first dynamic rendering example</a>. What's
+different is what you don't see: we didn't import <code>reactor</code> or
+<code>Site</code>. There are no calls to <code>listenTCP</code> or
+<code>run</code>. Instead, and this is the core idea for rpy scripts, we just
+bound the name <code>resource</code> to the resource we want the script to
+serve. Every rpy script must bind this name, and this name is the only thing
+Twisted Web will pay attention to in an rpy script.</p>
+
+<p>All that's left is to drop this rpy script into a Twisted Web server. There
+are a few ways to do this. The simplest way is with <code>twistd</code>:</p>
+
+<pre class="shell" xml:space="preserve">
+$ twistd -n web --path .
+</pre>
+
+<p>Hit <a href="http://localhost:8080/example.rpy" shape="rect">http://localhost:8080/example.rpy</a>
+to see it run. You can pass other arguments here too. <code>twistd web</code>
+has options for specifying which port number to bind, whether to set up an HTTPS
+server, and plenty more. Other options you can pass to <code>twistd</code> allow
+you to configure logging to work differently, to select a different reactor,
+etc. For a full list of options, see <code>twistd --help</code> and <code>twistd
+web --help</code>.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-basics.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-basics.html
new file mode 100644
index 0000000000..75918ecb59
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-basics.html
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Session Basics</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Session Basics</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>Sessions are the most complicated topic covered in this series of examples,
+and because of that it is going to take a few examples to cover all of the
+different aspects. This first example demonstrates the very basics of the
+Twisted Web session API: how to get the session object for the current request
+and how to prematurely expire a session.</p>
+
+<p>Before diving into the APIs, let's look at the big picture of sessions in
+Twisted Web. Sessions are represented by instances of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Session.html" title="twisted.web.server.Session">Session</a></code>. The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code> creates a new instance of
+<code>Session</code> the first time an application asks for it for a particular
+session. <code>Session</code> instances are kept on the <code>Site</code>
+instance until they expire (due to inactivity or because they are explicitly
+expired). Each time after the first that a particular session's
+<code>Session</code> object is requested, it is retrieved from
+the <code>Site</code>.</p>
+
+<p>With the conceptual underpinnings of the upcoming API in place, here comes
+the example. This will be a very simple <a href="rpy-scripts.html" shape="rect">rpy
+script</a> which tells a user what its unique session identifier is and lets it
+prematurely expire the session.</p>
+
+<p>First, we'll import <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code> so we can define a couple of
+subclasses of it:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+</pre>
+
+<p>Next we'll define the resource which tells the client what its session
+identifier is. This is done easily by first getting the session object
+using <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Request.getSession.html" title="twisted.web.server.Request.getSession">Request.getSession</a></code> and
+then getting the session object's uid attribute:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ShowSession</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Your session id is: '</span> + <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>().<span class="py-src-variable">uid</span>
+</pre>
+
+<p>To let the client expire its own session before it times out, we'll define
+another resource which expires whatever session it is requested with. This is
+done using the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Session.expire.html" title="twisted.web.server.Session.expire">Session.expire</a></code>
+method:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">class</span> <span class="py-src-identifier">ExpireSession</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>().<span class="py-src-variable">expire</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Your session has been expired.'</span>
+</pre>
+
+<p>Finally, to make the example an rpy script, we'll make an instance of
+<code>ShowSession</code> and give it an instance of <code>ExpireSession</code>
+as a child using <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.putChild.html" title="twisted.web.resource.Resource.putChild">Resource.putChild</a></code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-variable">resource</span> = <span class="py-src-variable">ShowSession</span>()
+<span class="py-src-variable">resource</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;expire&quot;</span>, <span class="py-src-variable">ExpireSession</span>())
+</pre>
+
+<p>And that is the complete example. You can fire this up and load the top
+page. You'll see a (rather opaque) session identifier that remains the same
+across reloads (at least until you flush the <code>TWISTED_SESSION</code> cookie
+from your browser or enough time passes). You can then visit
+the <code>expire</code> child and go back to the top page and see that you have
+a new session.</p>
+
+<p>Here's the complete source for the example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ShowSession</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Your session id is: '</span> + <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>().<span class="py-src-variable">uid</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExpireSession</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>().<span class="py-src-variable">expire</span>()
+ <span class="py-src-keyword">return</span> <span class="py-src-string">'Your session has been expired.'</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">ShowSession</span>()
+<span class="py-src-variable">resource</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;expire&quot;</span>, <span class="py-src-variable">ExpireSession</span>())
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-endings.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-endings.html
new file mode 100644
index 0000000000..46793a7b85
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-endings.html
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Session Endings</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Session Endings</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The previous two examples introduced Twisted Web's session APIs. This
+included accessing the session object, storing state on it, and retrieving it
+later, as well as the idea that the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Session.html" title="twisted.web.server.Session">Session</a></code> object has a lifetime which is tied to
+the notional session it represents. This example demonstrates how to exert some
+control over that lifetime and react when it expires.</p>
+
+<p>The lifetime of a session is controlled by the <code>sessionTimeout</code>
+attribute of the <code>Session</code> class. This attribute gives the number of
+seconds a session may go without being accessed before it expires. The default
+is 15 minutes. In this example we'll change that to a different value.</p>
+
+<p>One way to override the value is with a subclass:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Session</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ShortSession</span>(<span class="py-src-parameter">Session</span>):
+ <span class="py-src-variable">sessionTimeout</span> = <span class="py-src-number">60</span>
+</pre>
+
+<p>To have Twisted Web actually make use of this session class, rather than the
+default, it is also necessary to override the <code>sessionFactory</code> attribute of
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code>. We could do this with
+another subclass, but we could also do it to just one instance
+of <code>Site</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">rootResource</span>)
+<span class="py-src-variable">factory</span>.<span class="py-src-variable">sessionFactory</span> = <span class="py-src-variable">ShortSession</span>
+</pre>
+
+<p>Sessions given out for requests served by this <code>Site</code> will
+use <code>ShortSession</code> and only last one minute without activity.</p>
+
+<p>You can have arbitrary functions run when sessions expire, too. This can be
+useful for cleaning up external resources associated with the session, tracking
+usage statistics, and more. This functionality is provided via
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Session.notifyOnExpire.html" title="twisted.web.server.Session.notifyOnExpire">Session.notifyOnExpire</a></code>. It
+accepts a single argument: a function to call when the session expires. Here's a
+trivial example which prints a message whenever a session expires:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExpirationLogger</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">sessions</span> = <span class="py-src-variable">set</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">session</span> = <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>()
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>.<span class="py-src-variable">add</span>(<span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span>)
+ <span class="py-src-variable">session</span>.<span class="py-src-variable">notifyOnExpire</span>(<span class="py-src-keyword">lambda</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">_expired</span>(<span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_expired</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">uid</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Session&quot;</span>, <span class="py-src-variable">uid</span>, <span class="py-src-string">&quot;has expired.&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">uid</span>)
+</pre>
+
+<p>Keep in mind that using a method as the callback will keep the instance (in
+this case, the <code>ExpirationLogger</code> resource) in memory until the
+session expires.</p>
+
+<p>With those pieces in hand, here's an example that prints a message whenever a
+session expires, and uses sessions which last for 5 seconds:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>, <span class="py-src-variable">Session</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ShortSession</span>(<span class="py-src-parameter">Session</span>):
+ <span class="py-src-variable">sessionTimeout</span> = <span class="py-src-number">5</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ExpirationLogger</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-variable">sessions</span> = <span class="py-src-variable">set</span>()
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">session</span> = <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>()
+ <span class="py-src-keyword">if</span> <span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span> <span class="py-src-keyword">not</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>:
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>.<span class="py-src-variable">add</span>(<span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span>)
+ <span class="py-src-variable">session</span>.<span class="py-src-variable">notifyOnExpire</span>(<span class="py-src-keyword">lambda</span>: <span class="py-src-variable">self</span>.<span class="py-src-variable">_expired</span>(<span class="py-src-variable">session</span>.<span class="py-src-variable">uid</span>))
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">_expired</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">uid</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">&quot;Session&quot;</span>, <span class="py-src-variable">uid</span>, <span class="py-src-string">&quot;has expired.&quot;</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">sessions</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">uid</span>)
+
+<span class="py-src-variable">rootResource</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">rootResource</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;logme&quot;</span>, <span class="py-src-variable">ExpirationLogger</span>())
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">rootResource</span>)
+<span class="py-src-variable">factory</span>.<span class="py-src-variable">sessionFactory</span> = <span class="py-src-variable">ShortSession</span>
+
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8080</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Since <code>Site</code> customization is required, this example can't be
+rpy-based, so it brings back the manual <code>reactor.listenTCP</code>
+and <code>reactor.run</code> calls. Run it and visit <code>/logme</code> to see
+it in action. Keep visiting it to keep your session active. Stop visiting it for
+five seconds to see your session expiration message.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-store.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-store.html
new file mode 100644
index 0000000000..4887be0b05
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/session-store.html
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Storing Objects in the Session</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Storing Objects in the Session</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>This example shows you how you can persist objects across requests in the
+session object.</p>
+
+<p>As was discussed <a href="session-basics.html" shape="rect">previously</a>, instances
+of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Session.html" title="twisted.web.server.Session">Session</a></code> last as long as
+the notional session itself does. Each time <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Request.getSession.html" title="twisted.web.server.Request.getSession">Request.getSession</a></code> is called, if the session
+for the request is still active, then the same <code>Session</code> instance is
+returned as was returned previously. Because of this, <code>Session</code>
+instances can be used to keep other objects around for as long as the session
+exists.</p>
+
+<p>It's easier to demonstrate how this works than explain it, so here's an
+example:</p>
+
+<pre class="shell" xml:space="preserve">
+&gt;&gt;&gt; from zope.interface import Interface, Attribute, implements
+&gt;&gt;&gt; from twisted.python.components import registerAdapter
+&gt;&gt;&gt; from twisted.web.server import Session
+&gt;&gt;&gt; class ICounter(Interface):
+... value = Attribute(&quot;An int value which counts up once per page view.&quot;)
+...
+&gt;&gt;&gt; class Counter(object):
+... implements(ICounter)
+... def __init__(self, session):
+... self.value = 0
+...
+&gt;&gt;&gt; registerAdapter(Counter, Session, ICounter)
+&gt;&gt;&gt; ses = Session(None, None)
+&gt;&gt;&gt; data = ICounter(ses)
+&gt;&gt;&gt; print data
+&lt;__main__.Counter object at 0x8d535ec&gt;
+&gt;&gt;&gt; print data is ICounter(ses)
+True
+&gt;&gt;&gt;
+</pre>
+
+<p><i>What?</i>, I hear you say.</p>
+
+<p>What's shown in this example is the interface and adaption-based API which
+<code>Session</code> exposes for persisting state. There are several critical
+pieces interacting here:</p>
+
+<ul>
+ <li><code>ICounter</code> is an interface which serves several purposes. Like
+ all interfaces, it documents the API of some class of objects (in this case,
+ just the <code>value</code> attribute). It also serves as a key into what is
+ basically a dictionary within the session object: the interface is used to
+ store or retrieve a value on the session (the <code>Counter</code> instance,
+ in this case).</li>
+ <li><code>Counter</code> is the class which actually holds the session data in
+ this example. It implements <code>ICounter</code> (again, mostly for
+ documentation purposes). It also has a <code>value</code> attribute, as the
+ interface declared.</li>
+ <li>The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.components.registerAdapter.html" title="twisted.python.components.registerAdapter">registerAdapter</a></code> call sets up the
+ relationship between its three arguments so that adaption will do what we
+ want in this case.</li>
+ <li>Adaption is performed by the expression <code>ICounter(ses)</code>. This
+ is read as <i>adapt <code>ses</code> to <code>ICounter</code></i>. Because
+ of the <code>registerAdapter</code> call, it is roughly equivalent
+ to <code>Counter(ses)</code>. However (because of certain
+ things <code>Session</code> does), it also saves the <code>Counter</code>
+ instance created so that it will be returned the next time this adaption is
+ done. This is why the last statement produces <code>True</code>.</li>
+</ul>
+
+<p>If you're still not clear on some of the details there, don't worry about it
+and just remember this: <code>ICounter(ses)</code> gives you an object you can
+persist state on. It can be as much or as little state as you want, and you can
+use as few or as many different <code>Interface</code> classes as you want on a
+single <code>Session</code> instance.</p>
+
+<p>With those conceptual dependencies out of the way, it's a very short step to
+actually getting persistent state into a Twisted Web application. Here's an
+example which implements a simple counter, re-using the definitions from the
+example above:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CounterResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">session</span> = <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>()
+ <span class="py-src-variable">counter</span> = <span class="py-src-variable">ICounter</span>(<span class="py-src-variable">session</span>)
+ <span class="py-src-variable">counter</span>.<span class="py-src-variable">value</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Visit #%d for you!&quot;</span> % (<span class="py-src-variable">counter</span>.<span class="py-src-variable">value</span>,)
+</pre>
+
+<p>Pretty simple from this side, eh? All this does is
+use <code>Request.getSession</code> and the adaption from above, plus some
+integer math to give you a session-based visit counter.</p>
+
+<p>Here's the complete source for an <a href="rpy-scripts.html" shape="rect">rpy script</a>
+based on this example:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-variable">cache</span>()
+
+<span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Interface</span>, <span class="py-src-variable">Attribute</span>, <span class="py-src-variable">implements</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span>.<span class="py-src-variable">components</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">registerAdapter</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Session</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">ICounter</span>(<span class="py-src-parameter">Interface</span>):
+ <span class="py-src-variable">value</span> = <span class="py-src-variable">Attribute</span>(<span class="py-src-string">&quot;An int value which counts up once per page view.&quot;</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Counter</span>(<span class="py-src-parameter">object</span>):
+ <span class="py-src-variable">implements</span>(<span class="py-src-variable">ICounter</span>)
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">session</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">value</span> = <span class="py-src-number">0</span>
+
+<span class="py-src-variable">registerAdapter</span>(<span class="py-src-variable">Counter</span>, <span class="py-src-variable">Session</span>, <span class="py-src-variable">ICounter</span>)
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">CounterResource</span>(<span class="py-src-parameter">Resource</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">render_GET</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">request</span>):
+ <span class="py-src-variable">session</span> = <span class="py-src-variable">request</span>.<span class="py-src-variable">getSession</span>()
+ <span class="py-src-variable">counter</span> = <span class="py-src-variable">ICounter</span>(<span class="py-src-variable">session</span>)
+ <span class="py-src-variable">counter</span>.<span class="py-src-variable">value</span> += <span class="py-src-number">1</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Visit #%d for you!&quot;</span> % (<span class="py-src-variable">counter</span>.<span class="py-src-variable">value</span>,)
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">CounterResource</span>()
+</pre>
+
+<p>One more thing to note is the <code>cache()</code> call at the top of this
+example. As with the <a href="http-auth.html" shape="rect">previous example</a> where this
+came up, this rpy script is stateful. This time, it's the <code>ICounter</code>
+definition and the
+<code>registerAdapter</code> call that need to be executed only once. If we
+didn't use <code>cache</code>, every request would define a new, different
+interface named <code>ICounter</code>. Each of these would be a different key in
+the session, so the counter would never get past one.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/static-content.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/static-content.html
new file mode 100644
index 0000000000..3499f9849a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/static-content.html
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Serving Static Content From a Directory</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Serving Static Content From a Directory</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The goal of this example is to show you how to serve static content
+from a filesystem. First, we need to import some objects:</p>
+
+<ul>
+
+<li>
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code>, an <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IProtocolFactory.html" title="twisted.internet.interfaces.IProtocolFactory">IProtocolFactory</a></code> which
+glues a listening server port (<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.interfaces.IListeningPort.html" title="twisted.internet.interfaces.IListeningPort">IListeningPort</a></code>) to the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.http.HTTPChannel.html" title="twisted.web.http.HTTPChannel">HTTPChannel</a></code>
+implementation:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+</pre>
+</li>
+
+<li>
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.static.File.html" title="twisted.web.static.File">File</a></code>, an <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.IResource.html" title="twisted.web.resource.IResource">IResource</a></code> which glues
+the HTTP protocol implementation to the filesystem:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+</pre>
+</li>
+
+<li>
+The <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code>, which
+drives the whole process, actually accepting TCP connections and
+moving bytes into and out of them:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+</li>
+
+</ul>
+
+Next, we create an instance of the File resource pointed at the
+directory to serve:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">resource</span> = <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/tmp&quot;</span>)
+</pre>
+
+Then we create an instance of the Site factory with that resource:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>)
+</pre>
+
+Now we glue that factory to a TCP port:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8888</span>, <span class="py-src-variable">factory</span>)
+</pre>
+
+Finally, we start the reactor so it can make the program work:
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+And that's it. Here's the complete program:
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">File</span>(<span class="py-src-string">'/tmp'</span>)
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">resource</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8888</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Bonus example! For those times when you don't actually want to
+write a new program, the above implemented functionality is one of the
+things the command line <code>twistd</code> tool can do. In this case,
+the command
+<pre xml:space="preserve">
+twistd -n web --path /tmp
+</pre>
+will accomplish the same thing as the above server. See <a href="../../../core/howto/basics.html" shape="rect">helper programs</a> in the
+Twisted Core documentation for more information on using
+<code>twistd</code>.</p>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/static-dispatch.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/static-dispatch.html
new file mode 100644
index 0000000000..3e5c47000b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/static-dispatch.html
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Static URL Dispatch</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Static URL Dispatch</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The goal of this example is to show you how to serve different content at
+different URLs.</p>
+
+<p>The key to understanding how different URLs are handled with the resource
+APIs in Twisted Web is understanding that any URL can be used to address a node
+in a tree. Resources in Twisted Web exist in such a tree, and a request for a
+URL will be responded to by the resource which that URL addresses. The
+addressing scheme considers only the path segments of the URL. Starting with the
+root resource (the one used to construct the <code>Site</code>) and the first
+path segment, a child resource is looked up. As long as there are more path
+segments, this process is repeated using the result of the previous lookup and
+the next path segment. For example, to handle a request for
+<code>/foo/bar</code>, first the root's &quot;<code>foo</code>&quot; child is retrieved,
+then that resource's &quot;<code>bar</code>&quot; child is retrieved, then that resource
+is used to create the response.</p>
+
+<p>With that out of the way, let's consider an example that can serve a few
+different resources at a few different URLs.</p>
+
+<p>First things first: we need to import <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code>, the factory for HTTP servers, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code>, a convenient base class
+for custom pages, and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.reactor.html" title="twisted.internet.reactor">reactor</a></code>,
+the object which implements the Twisted main loop. We'll also import <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.static.File.html" title="twisted.web.static.File">File</a></code> to use as the resource at one
+of the example URLs.</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+</pre>
+
+<p>Now we create a resource which will correspond to the root of the URL
+hierarchy: all URLs are children of this resource.</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+</pre>
+
+<p>Here comes the interesting part of this example. We're now going to create
+three more resources and attach them to the three URLs <code>/foo</code>,
+<code>/bar</code>, and <code>/baz</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/tmp&quot;</span>))
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;bar&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/lost+found&quot;</span>))
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;baz&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/opt&quot;</span>))
+</pre>
+
+<p>Last, all that's required is to create a <code>Site</code> with the root
+resource, associate it with a listening server port, and start the reactor:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>With this server running, <code>http://localhost:8880/foo</code> will serve a
+listing of files from <code>/tmp</code>, <code>http://localhost:8880/bar</code>
+will serve a listing of files from <code>/lost+found</code>, and
+<code>http://localhost:8880/baz</code> will serve a listing of files from
+<code>/opt</code>.</p>
+
+<p>Here's the whole example uninterrupted:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">server</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Site</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Resource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
+
+<span class="py-src-variable">root</span> = <span class="py-src-variable">Resource</span>()
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;foo&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/tmp&quot;</span>))
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;bar&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/lost+found&quot;</span>))
+<span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">&quot;baz&quot;</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">&quot;/opt&quot;</span>))
+
+<span class="py-src-variable">factory</span> = <span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">8880</span>, <span class="py-src-variable">factory</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/wsgi.html b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/wsgi.html
new file mode 100644
index 0000000000..c42f748f3b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-in-60/wsgi.html
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: WSGI</title>
+<link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">WSGI</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<p>The goal of this example is to show you how to use <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.wsgi.WSGIResource.html" title="twisted.web.wsgi.WSGIResource">WSGIResource</a></code>, another existing
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">Resource</a></code> subclass, to serve
+<a href="http://www.python.org/dev/peps/pep-0333/" shape="rect">WSGI applications</a> in a
+Twisted Web server.</p>
+
+<p>Note thate <code>WSGIResource</code> is a multithreaded WSGI container. Like
+any other WSGI container, you can't do anything asynchronous in your WSGI
+applications, even though this is a Twisted WSGI container.</p>
+
+<p>The first new thing in this example is the import
+of <code>WSGIResource</code>:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">wsgi</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">WSGIResource</span>
+</pre>
+
+<p>Nothing too surprising there. We still need one of the other usual suspects,
+too:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+</pre>
+
+<p>You'll see why in a minute. Next, we need a WSGI application. Here's a really
+simple one just to get things going:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">application</span>(<span class="py-src-parameter">environ</span>, <span class="py-src-parameter">start_response</span>):
+ <span class="py-src-variable">start_response</span>(<span class="py-src-string">'200 OK'</span>, [(<span class="py-src-string">'Content-type'</span>, <span class="py-src-string">'text/plain'</span>)])
+ <span class="py-src-keyword">return</span> [<span class="py-src-string">'Hello, world!'</span>]
+</pre>
+
+<p>If this doesn't make sense to you, take a look at one of
+these <a href="http://wsgi.org/wsgi/Learn_WSGI" shape="rect">fine tutorials</a>. Otherwise,
+or once you're done with that, the next step is to create
+a <code>WSGIResource</code> instance, as this is going to be
+another <a href="rpy-scripts.html" shape="rect">rpy script</a> example:</p>
+
+<pre class="python"><p class="py-linenumber">1
+</p><span class="py-src-variable">resource</span> = <span class="py-src-variable">WSGIResource</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">getThreadPool</span>(), <span class="py-src-variable">application</span>)
+</pre>
+
+<p>Let's dwell on this line for a minute. The first parameter passed to
+<code>WSGIResource</code> is the reactor. Despite the fact that the reactor is
+global and any code that wants it can always just import it (as, in fact, this
+rpy script simply does itself), passing it around as a parameter leaves the door
+open for certain future possibilities - for example, having more than one
+reactor. There are also testing implications. Consider how much easier it is to
+unit test a function that accepts a reactor - perhaps a mock reactor specially
+constructed to make your tests easy to write - rather than importing the real
+global reactor. That's why <code>WSGIResource</code> requires you to pass the
+reactor to it.</p>
+
+<p>The second parameter passed to <code>WSGIResource</code> is
+a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.python.threadpool.ThreadPool.html" title="twisted.python.threadpool.ThreadPool">ThreadPool</a></code>. <code>WSGIResource</code>
+uses this to actually call the application object passed in to it. To keep this
+example short, we're passing in the reactor's internal threadpool here, letting
+us skip its creation and shutdown-time destruction. For finer control over how
+many WSGI requests are served in parallel, you may want to create your own
+thread pool to use with your <code>WSGIResource</code>, but for simple testing,
+using the reactor's is fine.</p>
+
+<p>The final argument is the application object. This is pretty typical of how
+WSGI containers work.</p>
+
+<p>The example, sans interruption:</p>
+
+<pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+6
+7
+8
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">wsgi</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">WSGIResource</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">application</span>(<span class="py-src-parameter">environ</span>, <span class="py-src-parameter">start_response</span>):
+ <span class="py-src-variable">start_response</span>(<span class="py-src-string">'200 OK'</span>, [(<span class="py-src-string">'Content-type'</span>, <span class="py-src-string">'text/plain'</span>)])
+ <span class="py-src-keyword">return</span> [<span class="py-src-string">'Hello, world!'</span>]
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">WSGIResource</span>(<span class="py-src-variable">reactor</span>, <span class="py-src-variable">reactor</span>.<span class="py-src-variable">getThreadPool</span>(), <span class="py-src-variable">application</span>)
+</pre>
+
+<p>Up to the point where the <code>WSGIResource</code> instance defined here
+exists in the resource hierarchy, the normal resource traversal rules
+apply: <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.getChild.html" title="twisted.web.resource.Resource.getChild">getChild</a></code>
+will be called to handle each segment. Once the <code>WSGIResource</code> is
+encountered, though, that process stops and all further URL handling is the
+responsibility of the WSGI application. This application does nothing with the
+URL, though, so you won't be able to tell that.</p>
+
+<p>Oh, and as was the case with the first static file example, there's also a
+command line option you can use to avoid a lot of this. If you just put the
+above application function, without all of the <code>WSGIResource</code> stuff,
+into a file, say, <code>foo.py</code>, then you can launch a roughly equivalent
+server like this:</p>
+
+<pre class="shell" xml:space="preserve">
+$ twistd -n web --wsgi foo.application
+</pre>
+
+</div>
+
+ <p><a href="../index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/web-overview.html b/vendor/Twisted-10.0.0/doc/web/howto/web-overview.html
new file mode 100644
index 0000000000..d6780c1e36
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/web-overview.html
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Overview of Twisted Web</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Overview of Twisted Web</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Twisted Web's Structure</a></li><li><a href="#auto2">Resources</a></li><li><a href="#auto3">
+ Web programming with Twisted Web
+ </a></li></ol></div>
+ <div class="content">
+ <span/>
+
+ <h2>Introduction<a name="auto0"/></h2>
+
+ <p>Twisted Web is a web application server written in pure
+ Python, with APIs at multiple levels of abstraction to
+ facilitate different kinds of web programming.
+ </p>
+
+ <h2>Twisted Web's Structure<a name="auto1"/></h2>
+
+ <p><img src="../img/web-overview.png"/></p>
+
+ <p>When
+ the Web Server receives a request from a Client, it creates
+ a Request object and passes it on to the Resource system.
+ The Resource system dispatches to the appropriate Resource
+ object based on what path was requested by the client. The
+ Resource is asked to render itself, and the result is
+ returned to the client.</p>
+
+ <h2>Resources<a name="auto2"/></h2>
+
+ <p>Resources are the lowest-level abstraction for applications
+ in the Twisted web server. Each Resource is a 1:1 mapping with
+ a path that is requested: you can think of a Resource as a
+ single <q>page</q> to be rendered. The interface for making
+ Resources is very simple; they must have a method named
+ <code>render</code> which takes a single argument, which is the
+ Request object (an instance of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Request.html" title="twisted.web.server.Request">twisted.web.server.Request</a></code>). This render
+ method must return a string, which will be returned to the web
+ browser making the request. Alternatively, they can return a
+ special constant, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.NOT_DONE_YET.html" title="twisted.web.server.NOT_DONE_YET">twisted.web.server.NOT_DONE_YET</a></code>, which tells
+ the web server not to close the connection; you must then use
+ <code class="python">request.write(data)</code> to render the
+ page, and call <code class="python">request.finish()</code>
+ whenever you're done.
+ </p>
+
+ <h2>
+ Web programming with Twisted Web
+ <a name="auto3"/></h2>
+
+ <p>
+ Web programmers seeking a higher level abstraction than the Resource system
+ should look at <a href="http://divmod.org/trac/wiki/DivmodNevow" shape="rect">Nevow</a>.
+ Nevow is based on ideas previously developed in Twisted, but is now maintained
+ outside of Twisted to easy development and release cycle pressures.
+ </p>
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/howto/xmlrpc.html b/vendor/Twisted-10.0.0/doc/web/howto/xmlrpc.html
new file mode 100644
index 0000000000..b9a0634c90
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/howto/xmlrpc.html
@@ -0,0 +1,457 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Creating XML-RPC Servers and Clients with Twisted</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Creating XML-RPC Servers and Clients with Twisted</h1>
+ <div class="toc"><ol><li><a href="#auto0">Introduction</a></li><li><a href="#auto1">Creating a XML-RPC server</a></li><ul><li><a href="#auto2">Using XML-RPC sub-handlers</a></li><li><a href="#auto3">Adding XML-RPC Introspection support</a></li></ul><li><a href="#auto4">SOAP Support</a></li><li><a href="#auto5">Creating an XML-RPC Client</a></li><li><a href="#auto6">Serving SOAP and XML-RPC simultaneously</a></li></ol></div>
+ <div class="content">
+<span/>
+
+<h2>Introduction<a name="auto0"/></h2>
+
+<p><a href="http://www.xmlrpc.com" shape="rect">XML-RPC</a> is a simple request/reply protocol that runs over HTTP. It is simple,
+easy to implement and supported by most programming languages. Twisted's XML-RPC
+support is implemented using the xmlrpclib library that is included with Python 2.2 and later.</p>
+
+<h2>Creating a XML-RPC server<a name="auto1"/></h2>
+
+<p>Making a server is very easy - all you need to do is inherit from <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.xmlrpc.XMLRPC.html" title="twisted.web.xmlrpc.XMLRPC">twisted.web.xmlrpc.XMLRPC</a></code>.
+You then create methods beginning with <code>xmlrpc_</code>. The methods'
+arguments determine what arguments it will accept from XML-RPC clients.
+The result is what will be returned to the clients.</p>
+
+<p>Methods published via XML-RPC can return all the basic XML-RPC
+types, such as strings, lists and so on (just return a regular python
+integer, etc). They can also raise exceptions or return Failure instances to indicate an
+error has occurred, or <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.xmlrpc.Binary.html" title="twisted.web.xmlrpc.Binary">Binary</a></code>, <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.xmlrpc.Boolean.html" title="twisted.web.xmlrpc.Boolean">Boolean</a></code> or <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.xmlrpc.DateTime.html" title="twisted.web.xmlrpc.DateTime">DateTime</a></code> instances (all of these are the same as
+the respective classes in xmlrpclib. In addition, XML-RPC published
+methods can return <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.internet.defer.Deferred.html" title="twisted.internet.defer.Deferred">Deferred</a></code> instances whose results are one of the
+above. This allows you to return results that can't be calculated
+immediately, such as database queries. See the <a href="../../core/howto/defer.html" shape="rect">Deferred documentation</a> for more details.</p>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.xmlrpc.XMLRPC.html" title="twisted.web.xmlrpc.XMLRPC">XMLRPC</a></code> instances
+are Resource objects, and they can thus be published using a Site. The
+following example has two methods published via XML-RPC, <code>add(a,
+b)</code> and <code>echo(x)</code>.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Example</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-string">&quot;&quot;&quot;An example object to be published.&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return all passed args.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">x</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Return sum of arguments.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> + <span class="py-src-variable">b</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_fault</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;
+ Raise a Fault indicating that the procedure should not be used.
+ &quot;&quot;&quot;</span>
+ <span class="py-src-keyword">raise</span> <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">Fault</span>(<span class="py-src-number">123</span>, <span class="py-src-string">&quot;The fault procedure is faulty.&quot;</span>)
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">Example</span>()
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">7080</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>After we run this command, we can connect with a client and send commands
+to the server:</p>
+
+<pre class="python-interpreter" xml:space="preserve">
+&gt;&gt;&gt; import xmlrpclib
+&gt;&gt;&gt; s = xmlrpclib.Server('http://localhost:7080/')
+&gt;&gt;&gt; s.echo(&quot;lala&quot;)
+'lala'
+&gt;&gt;&gt; s.add(1, 2)
+3
+&gt;&gt;&gt; s.fault()
+Traceback (most recent call last):
+...
+xmlrpclib.Fault: &lt;Fault 123: 'The fault procedure is faulty.'&gt;
+&gt;&gt;&gt;
+
+</pre>
+
+<p>XML-RPC resources can also be part of a normal Twisted web server, using
+resource scripts. The following is an example of such a resource script:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpc</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;What are you talking about, William?&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Quoter</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_quote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">getQuote</span>()
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">Quoter</span>()
+</pre><div class="caption">Source listing - <a href="listings/xmlquote.rpy"><span class="filename">listings/xmlquote.rpy</span></a></div></div>
+
+<h3>Using XML-RPC sub-handlers<a name="auto2"/></h3>
+
+<p>XML-RPC resource can be nested so that one handler calls another if
+a method with a given prefix is called. For example, to add support for
+an XML-RPC method <code>date.time()</code> to the
+<code class="python">Example</code> class, you could do the following:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+</p><span class="py-src-keyword">import</span> <span class="py-src-variable">time</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Example</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-string">&quot;&quot;&quot;An example object to be published.&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return all passed args.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">x</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return sum of arguments.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> + <span class="py-src-variable">b</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Date</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Serve the XML-RPC 'time' method.&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_time</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return UNIX time.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">time</span>.<span class="py-src-variable">time</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">Example</span>()
+ <span class="py-src-variable">date</span> = <span class="py-src-variable">Date</span>()
+ <span class="py-src-variable">r</span>.<span class="py-src-variable">putSubHandler</span>(<span class="py-src-string">'date'</span>, <span class="py-src-variable">date</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">7080</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>By default, a period ('.') separates the prefix from the method
+name, but you can use a different character by overriding the <code class="python">XMLRPC.separator</code> data member in your base
+XML-RPC server. XML-RPC servers may be nested to arbitrary depths
+using this method.</p>
+
+<h3>Adding XML-RPC Introspection support<a name="auto3"/></h3>
+
+<p>XML-RPC has an informal <a href="http://ldp.kernelnotes.de/HOWTO/XML-RPC-HOWTO/xmlrpc-howto-interfaces.html" shape="rect">Introspection API</a> that specifies three
+methods in a <code>system</code> sub-handler which allow a client to query
+a server about the server's API. Adding Introspection support to the
+<code class="python">Example</code> class is easy using the
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.xmlrpc.XMLRPCIntrospection.html" title="twisted.web.xmlrpc.XMLRPCIntrospection">XMLRPCIntrospection</a></code>
+class:</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">server</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Example</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-string">&quot;&quot;&quot;An example object to be published.&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_echo</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">x</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return all passed args.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">x</span>
+
+ <span class="py-src-variable">xmlrpc_echo</span>.<span class="py-src-variable">signature</span> = [[<span class="py-src-string">'string'</span>, <span class="py-src-string">'string'</span>],
+ [<span class="py-src-string">'int'</span>, <span class="py-src-string">'int'</span>],
+ [<span class="py-src-string">'double'</span>, <span class="py-src-string">'double'</span>],
+ [<span class="py-src-string">'array'</span>, <span class="py-src-string">'array'</span>],
+ [<span class="py-src-string">'struct'</span>, <span class="py-src-string">'struct'</span>]]
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_add</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">a</span>, <span class="py-src-parameter">b</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Return sum of arguments.&quot;&quot;&quot;</span>
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">a</span> + <span class="py-src-variable">b</span>
+
+ <span class="py-src-variable">xmlrpc_add</span>.<span class="py-src-variable">signature</span> = [[<span class="py-src-string">'int'</span>, <span class="py-src-string">'int'</span>, <span class="py-src-string">'int'</span>],
+ [<span class="py-src-string">'double'</span>, <span class="py-src-string">'double'</span>, <span class="py-src-string">'double'</span>]]
+ <span class="py-src-variable">xmlrpc_add</span>.<span class="py-src-variable">help</span> = <span class="py-src-string">&quot;Add the arguments and return the sum.&quot;</span>
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">r</span> = <span class="py-src-variable">Example</span>()
+ <span class="py-src-variable">xmlrpc</span>.<span class="py-src-variable">addIntrospection</span>(<span class="py-src-variable">r</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">7080</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">r</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>Note the method attributes <code class="python">help</code> and
+<code class="python">signature</code> which are used by the Introspection
+API methods <code>system.methodHelp</code> and
+<code>system.methodSignature</code> respectively. If no
+<code class="python">help</code> attribute is specified,
+the method's documentation string is used instead.</p>
+
+<h2>SOAP Support<a name="auto4"/></h2>
+
+<p>From the point of view of a Twisted developer, there is little difference
+between XML-RPC support and SOAP support. Here is an example of SOAP usage:</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">soap</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;That beverage, sir, is off the hizzy.&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">Quoter</span>(<span class="py-src-parameter">soap</span>.<span class="py-src-parameter">SOAPPublisher</span>):
+ <span class="py-src-string">&quot;&quot;&quot;Publish one method, 'quote'.&quot;&quot;&quot;</span>
+
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">soap_quote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">getQuote</span>()
+
+<span class="py-src-variable">resource</span> = <span class="py-src-variable">Quoter</span>()
+</pre><div class="caption">Source listing - <a href="listings/soap.rpy"><span class="filename">listings/soap.rpy</span></a></div></div>
+
+
+<h2>Creating an XML-RPC Client<a name="auto5"/></h2>
+
+<p>XML-RPC clients in Twisted are meant to look as something which will be
+familiar either to <code>xmlrpclib</code> or to Perspective Broker users,
+taking features from both, as appropriate. There are two major deviations
+from the <code>xmlrpclib</code> way which should be noted:</p>
+
+<ol>
+<li>No implicit <code>/RPC2</code>. If the services uses this path for the
+ XML-RPC calls, then it will have to be given explicitly.</li>
+<li>No magic <code>__getattr__</code>: calls must be made by an explicit
+ <code>callRemote</code>.</li>
+</ol>
+
+<p>The interface Twisted presents to XML-RPC client is that of a proxy object:
+<code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.xmlrpc.Proxy.html" title="twisted.web.xmlrpc.Proxy">twisted.web.xmlrpc.Proxy</a></code>. The constructor for the
+object receives a URL: it must be an HTTP or HTTPS URL. When an XML-RPC service
+is described, the URL to that service will be given there.</p>
+
+<p>Having a proxy object, one can just call the <code>callRemote</code> method,
+which accepts a method name and a variable argument list (but no named
+arguments, as these are not supported by XML-RPC). It returns a deferred,
+which will be called back with the result. If there is any error, at any
+level, the errback will be called. The exception will be the relevant Twisted
+error in the case of a problem with the underlying connection (for example,
+a timeout), <code>IOError</code> containing the status and message in the case
+of a non-200 status or a <code>xmlrpclib.Fault</code> in the case of an
+XML-RPC level problem.</p>
+
+<pre class="python"><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">xmlrpc</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Proxy</span>
+<span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printValue</span>(<span class="py-src-parameter">value</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-variable">repr</span>(<span class="py-src-variable">value</span>)
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">printError</span>(<span class="py-src-parameter">error</span>):
+ <span class="py-src-keyword">print</span> <span class="py-src-string">'error'</span>, <span class="py-src-variable">error</span>
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
+
+<span class="py-src-variable">proxy</span> = <span class="py-src-variable">Proxy</span>(<span class="py-src-string">'http://advogato.org/XMLRPC'</span>)
+<span class="py-src-variable">proxy</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'test.sumprod'</span>, <span class="py-src-number">3</span>, <span class="py-src-number">5</span>).<span class="py-src-variable">addCallbacks</span>(<span class="py-src-variable">printValue</span>, <span class="py-src-variable">printError</span>)
+<span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+</pre>
+
+<p>prints:</p>
+
+<pre xml:space="preserve">
+[8, 15]
+</pre>
+
+<h2>Serving SOAP and XML-RPC simultaneously<a name="auto6"/></h2>
+
+<p><code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.xmlrpc.XMLRPC.html" title="twisted.web.xmlrpc.XMLRPC">twisted.web.xmlrpc.XMLRPC</a></code> and <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.soap.SOAPPublisher.html" title="twisted.web.soap.SOAPPublisher">twisted.web.soap.SOAPPublisher</a></code> are both <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resources.html" title="twisted.web.resource.Resources">Resources</a></code>. So, to serve both XML-RPC and
+SOAP in the one web server, you can use the <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.IResource.putChild.html" title="twisted.web.resource.IResource.putChild">putChild</a></code> method of Resources.</p>
+
+<p>The following example uses an empty <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.resource.Resource.html" title="twisted.web.resource.Resource">resource.Resource</a></code> as the root resource for a <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.web.server.Site.html" title="twisted.web.server.Site">Site</a></code>, and then adds
+<code>/RPC2</code> and <code>/SOAP</code> paths to it.</p>
+
+<div class="py-listing"><pre><p class="py-linenumber"> 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+</p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">soap</span>, <span class="py-src-variable">xmlrpc</span>, <span class="py-src-variable">resource</span>, <span class="py-src-variable">server</span>
+<span class="py-src-keyword">import</span> <span class="py-src-variable">os</span>
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">getQuote</span>():
+ <span class="py-src-keyword">return</span> <span class="py-src-string">&quot;Victory to the burgeois, you capitalist swine!&quot;</span>
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">XMLRPCQuoter</span>(<span class="py-src-parameter">xmlrpc</span>.<span class="py-src-parameter">XMLRPC</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">xmlrpc_quote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">getQuote</span>()
+
+<span class="py-src-keyword">class</span> <span class="py-src-identifier">SOAPQuoter</span>(<span class="py-src-parameter">soap</span>.<span class="py-src-parameter">SOAPPublisher</span>):
+ <span class="py-src-keyword">def</span> <span class="py-src-identifier">soap_quote</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-keyword">return</span> <span class="py-src-variable">getQuote</span>()
+
+<span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
+ <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
+ <span class="py-src-variable">root</span> = <span class="py-src-variable">resource</span>.<span class="py-src-variable">Resource</span>()
+ <span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'RPC2'</span>, <span class="py-src-variable">XMLRPCQuoter</span>())
+ <span class="py-src-variable">root</span>.<span class="py-src-variable">putChild</span>(<span class="py-src-string">'SOAP'</span>, <span class="py-src-variable">SOAPQuoter</span>())
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">listenTCP</span>(<span class="py-src-number">7080</span>, <span class="py-src-variable">server</span>.<span class="py-src-variable">Site</span>(<span class="py-src-variable">root</span>))
+ <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
+
+<span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
+ <span class="py-src-variable">main</span>()
+</pre><div class="caption">Source listing - <a href="listings/xmlAndSoapQuote.py"><span class="filename">listings/xmlAndSoapQuote.py</span></a></div></div>
+
+<p>Refer to <a href="using-twistedweb.html#development" shape="rect">Twisted Web
+Development</a> for more details about Resources.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/web/img/controller.png b/vendor/Twisted-10.0.0/doc/web/img/controller.png
new file mode 100644
index 0000000000..462268e91f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/controller.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/img/livepage.png b/vendor/Twisted-10.0.0/doc/web/img/livepage.png
new file mode 100644
index 0000000000..131d8d7f8c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/livepage.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/img/model.png b/vendor/Twisted-10.0.0/doc/web/img/model.png
new file mode 100644
index 0000000000..30058b08aa
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/model.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/img/plone_root_model.png b/vendor/Twisted-10.0.0/doc/web/img/plone_root_model.png
new file mode 100644
index 0000000000..9f0f385a49
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/plone_root_model.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/img/view.png b/vendor/Twisted-10.0.0/doc/web/img/view.png
new file mode 100644
index 0000000000..5fdbc05d96
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/view.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/img/web-overview.dia b/vendor/Twisted-10.0.0/doc/web/img/web-overview.dia
new file mode 100644
index 0000000000..4bc9be995e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/web-overview.dia
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/img/web-overview.png b/vendor/Twisted-10.0.0/doc/web/img/web-overview.png
new file mode 100644
index 0000000000..3eff9bd599
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/web-overview.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/img/web-process.png b/vendor/Twisted-10.0.0/doc/web/img/web-process.png
new file mode 100644
index 0000000000..4f5ab66af1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/web-process.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/img/web-process.svg b/vendor/Twisted-10.0.0/doc/web/img/web-process.svg
new file mode 100644
index 0000000000..54a3850c7f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/web-process.svg
@@ -0,0 +1,594 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="900"
+ height="650"
+ id="svg2"
+ inkscape:version="0.47 r22583"
+ sodipodi:docname="web-process.svg"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/web-process.png"
+ inkscape:export-xdpi="53.88356"
+ inkscape:export-ydpi="53.88356">
+ <metadata
+ id="metadata2876">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1207"
+ inkscape:window-height="788"
+ id="namedview2874"
+ showgrid="false"
+ showguides="false"
+ inkscape:zoom="0.80439329"
+ inkscape:cx="568.43722"
+ inkscape:cy="418.14159"
+ inkscape:window-x="344"
+ inkscape:window-y="33"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg2"
+ inkscape:guide-bbox="true"
+ showborder="true"
+ borderlayer="false"
+ inkscape:showpageshadow="false">
+ <sodipodi:guide
+ position="653.14257,-346.44227"
+ orientation="0,744.09448"
+ id="guide7662" />
+ <sodipodi:guide
+ position="1397.237,-346.44227"
+ orientation="-1052.3622,0"
+ id="guide7664" />
+ <sodipodi:guide
+ position="1397.237,705.91993"
+ orientation="0,-744.09448"
+ id="guide7666" />
+ <sodipodi:guide
+ position="653.14257,705.91993"
+ orientation="1052.3622,0"
+ id="guide7668" />
+ <sodipodi:guide
+ position="653.14257,-346.44227"
+ orientation="0,744.09448"
+ id="guide7670" />
+ <sodipodi:guide
+ position="1397.237,-346.44227"
+ orientation="-1052.3622,0"
+ id="guide7672" />
+ <sodipodi:guide
+ position="1397.237,705.91993"
+ orientation="0,-744.09448"
+ id="guide7674" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lend"
+ style="overflow:visible;">
+ <path
+ id="path4316"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.8) rotate(180) translate(12.5,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lend"
+ style="overflow:visible">
+ <path
+ id="path4334"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(-1.1,0,0,-1.1,-1.1,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lstart"
+ style="overflow:visible">
+ <path
+ id="path4331"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(1.1,0,0,1.1,1.1,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mstart"
+ style="overflow:visible">
+ <path
+ id="path4319"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(0.4,0,0,0.4,4,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ id="path4337"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart"
+ style="overflow:visible">
+ <path
+ id="path4313"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective2878" />
+ <inkscape:perspective
+ id="perspective3681"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3695"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3743"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3783"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3816"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3952"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3979"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective4032"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective4297"
+ inkscape:persp3d-origin="250 : 166.66667 : 1"
+ inkscape:vp_z="500 : 250 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 250 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective4787"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective5576"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective5603"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective6025"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective6865"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective7806"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ </defs>
+ <g
+ id="layer1"
+ transform="matrix(0.93010115,0,0,0.93010115,250.13373,-60.976065)"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001">
+ <text
+ x="234.28571"
+ y="132.36218"
+ id="text2818"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans Bold"
+ sodipodi:linespacing="125%"><tspan
+ x="234.28571"
+ y="132.36218"
+ id="tspan2820">Twisted Web</tspan></text>
+ </g>
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:2.70000005;stroke-miterlimit:4;stroke-dasharray:none"
+ id="rect2884"
+ width="570.77271"
+ height="231.87642"
+ x="301.6925"
+ y="115.17715"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="424.94702"
+ y="95.464104"
+ id="text3701"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan3703"
+ x="424.94702"
+ y="95.464104">http://example.com/foo/bar/baz</tspan></text>
+ <rect
+ style="fill:#b8ffb8;fill-opacity:1;stroke:#000000;stroke-width:1.1715759;stroke-miterlimit:4;stroke-dasharray:none"
+ id="rect3705"
+ width="95.166527"
+ height="213.02367"
+ x="312.14368"
+ y="123.7507"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="358.15546"
+ y="199.9054"
+ id="text3707"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan3709"
+ x="358.15546"
+ y="199.9054">H</tspan><tspan
+ sodipodi:role="line"
+ x="358.15546"
+ y="224.9054"
+ id="tspan3711">T</tspan><tspan
+ sodipodi:role="line"
+ x="358.15546"
+ y="249.9054"
+ id="tspan3713">T</tspan><tspan
+ sodipodi:role="line"
+ x="358.15546"
+ y="274.9054"
+ id="tspan3715">P</tspan></text>
+ <rect
+ style="fill:#c8ebeb;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none"
+ id="rect3789"
+ width="445.31635"
+ height="211.98299"
+ x="416.99731"
+ y="124.27103"
+ ry="46.083252"
+ rx="0"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="478.34595"
+ y="224.9054"
+ id="text3822"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan3824"
+ x="478.34595"
+ y="224.9054">Object</tspan><tspan
+ sodipodi:role="line"
+ x="478.34595"
+ y="247.4054"
+ id="tspan3826">Publisher</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="675.25073"
+ y="210.61969"
+ id="text3828"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan3830"
+ x="675.25073"
+ y="210.61969">getChild(&quot;foo&quot;)</tspan><tspan
+ sodipodi:role="line"
+ x="675.25073"
+ y="233.11969"
+ id="tspan3832"> getChild(&quot;bar&quot;)</tspan><tspan
+ sodipodi:role="line"
+ x="675.25073"
+ y="255.61969"
+ id="tspan3834"> getChild(&quot;baz&quot;)</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:144px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#3f3f3f;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="542.63159"
+ y="292.76254"
+ id="text3842"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan3844"
+ x="542.63159"
+ y="292.76254">↻</tspan></text>
+ <g
+ id="g4300"
+ transform="matrix(0.57696609,0,0,0.51806079,460.16063,443.16138)"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001">
+ <path
+ sodipodi:nodetypes="cccccccccccccccccc"
+ id="path835"
+ d="m 163.26321,64.542987 c 3.597,0 35.974,-23.982698 88.737,-26.381098 71.948,-8.394 101.927,38.372498 101.927,38.372498 -2.398,5.9957 49.164,-37.173398 79.143,-33.575998 32.377,4.7966 45.568,19.186098 41.97,49.164598 -2.398,19.186703 -22.784,27.580703 -27.58,37.173703 2.398,4.796 28.779,20.386 28.779,62.356 0,41.97 -26.381,64.753 -25.182,64.753 1.199,0 26.381,41.97 7.195,67.152 -19.186,25.182 -73.148,1.2 -81.542,-3.597 -8.394,2.398 -13.32007,47.47397 -119.17674,52.27097 -87.33582,0 -124.24926,-33.08497 -136.24026,-37.88197 -14.39,9.593 -73.147796,22.785 -92.334196,-13.19 -15.58881,-45.567 8.3941,-56.36 13.1906,-64.754 -4.7965,-10.792 -20.3854,-28.779 -17.9871,-62.355 0,-43.17 10.7923,-64.754 20.3855,-71.949 -4.7966,-9.593 -23.9829,-47.965903 17.987,-71.948401 69.550196,-17.9872 99.529196,15.588898 100.728196,14.389698 z"
+ style="fill:#000000;fill-opacity:0.5;fill-rule:evenodd;stroke:none" />
+ <path
+ sodipodi:nodetypes="cccccccccccccccccc"
+ id="path598"
+ d="m 158.287,57.5163 c 3.597,0 35.974,-23.9827 88.737,-26.3811 71.948,-8.394 101.927,38.3725 101.927,38.3725 -2.398,5.9957 49.164,-37.1734 79.143,-33.576 32.377,4.7966 45.568,19.1861 41.97,49.1646 -2.398,19.1867 -22.784,27.5807 -27.58,37.1737 2.398,4.796 28.779,20.386 28.779,62.356 0,41.97 -26.381,64.753 -25.182,64.753 1.199,0 26.381,41.97 7.195,67.152 -19.186,25.182 -73.148,1.2 -81.542,-3.597 -8.394,2.398 -13.32007,47.47397 -119.17674,52.27097 C 165.22144,365.20497 128.308,332.12 116.317,327.323 101.927,336.916 43.1692,350.108 23.9828,314.133 8.39399,268.566 32.3769,257.773 37.1734,249.379 32.3769,238.587 16.788,220.6 19.1863,187.024 c 0,-43.17 10.7923,-64.754 20.3855,-71.949 C 34.7752,105.482 15.5889,67.1091 57.5588,43.1266 127.109,25.1394 157.088,58.7155 158.287,57.5163 z"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.77096152;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:5;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Lstart)"
+ d="m 357.21235,348.82073 0,81.41873 0,0 0,0"
+ id="path4762"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="329.22418"
+ y="453.06482"
+ id="text4793"
+ sodipodi:linespacing="100%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan4795"
+ x="329.22418"
+ y="453.06482">Finish</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.77699995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 357.21235,529.36939 0,-67.3435"
+ id="path5551"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <image
+ y="528.07153"
+ x="355.14368"
+ id="image5605"
+ height="2"
+ width="117"
+ xlink:href="file:///Users/thijstriemstra/Desktop/inkscape_pasted_image_20091210_211422.png"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="693.24731"
+ y="396.24796"
+ id="text5609"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan5611"
+ x="693.24731"
+ y="396.24796">Render</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.63258982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 774.86673,265.48334 c 0,143.1595 0,143.1595 0,143.1595 l 0,0"
+ id="path5613"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.98251843;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 676.93043,409.22247 c 98.723,0 98.723,0 98.723,0"
+ id="path5615"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.74416637;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lend)"
+ d="m 677.69453,408.55589 0,59.90598"
+ id="path5807"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:100%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="601.7937"
+ y="536.74927"
+ id="text6031"
+ sodipodi:linespacing="100%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan6033"
+ x="601.7937"
+ y="536.74927">Templating</tspan><tspan
+ sodipodi:role="line"
+ x="601.7937"
+ y="560.74927"
+ id="tspan6035">System</tspan></text>
+ <rect
+ style="fill:#e2ddd8;fill-opacity:1;stroke:#000000;stroke-width:2.27699995;stroke-miterlimit:4;stroke-dasharray:none"
+ id="rect6058"
+ width="136.48538"
+ height="210.41495"
+ x="33.1385"
+ y="127.60599"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="229.00862"
+ y="183.2704"
+ id="text6060"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan6062"
+ x="229.00862"
+ y="183.2704">Request</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="244.9366"
+ y="270.70905"
+ id="text6064"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan6066"
+ x="244.9366"
+ y="270.70905">Response</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:100%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
+ x="99.959412"
+ y="236.50031"
+ id="text6068"
+ sodipodi:linespacing="100%"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001"><tspan
+ sodipodi:role="line"
+ id="tspan6070"
+ x="99.959412"
+ y="236.50031">Browser</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.76616168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Lstart)"
+ d="m 298.85972,194.46803 c -128.91105,0 -128.91105,0 -128.91105,0"
+ id="path6871"
+ inkscape:export-filename="/Users/thijstriemstra/Desktop/layer1.png"
+ inkscape:export-xdpi="54.700001"
+ inkscape:export-ydpi="54.700001" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.77699995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:url(#Arrow2Lstart)"
+ d="m 301.95575,280.22714 -130.10029,0 0,0 0,0 0,0 0,0"
+ id="path7812" />
+</svg>
diff --git a/vendor/Twisted-10.0.0/doc/web/img/web-session.png b/vendor/Twisted-10.0.0/doc/web/img/web-session.png
new file mode 100644
index 0000000000..c4aeba7f7d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/web-session.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/img/web-widgets.dia b/vendor/Twisted-10.0.0/doc/web/img/web-widgets.dia
new file mode 100644
index 0000000000..6c6b37a861
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/web-widgets.dia
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/img/web-widgets.png b/vendor/Twisted-10.0.0/doc/web/img/web-widgets.png
new file mode 100644
index 0000000000..6fef28e5ec
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/img/web-widgets.png
Binary files differ
diff --git a/vendor/Twisted-10.0.0/doc/web/index.html b/vendor/Twisted-10.0.0/doc/web/index.html
new file mode 100644
index 0000000000..eae17511be
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/web/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Web Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Web Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Web to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Web</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/words/examples/aimbot.py b/vendor/Twisted-10.0.0/doc/words/examples/aimbot.py
new file mode 100644
index 0000000000..f58eaf4e5e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/examples/aimbot.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+AIM echo bot.
+"""
+
+from twisted.words.protocols import toc
+from twisted.internet import reactor, protocol
+import twisted.words.im.tocsupport as ts
+
+# account info
+screenname = 'username'
+password = 'password'
+
+class aimBot(toc.TOCClient):
+ """AOL Instant Messenger echo bot"""
+
+ def gotConfig(self, mode, buddylist, permit, deny):
+ """called when the server sends us config info"""
+ global screename
+
+ # add someone to our deny list?
+ self.add_deny([])
+
+ # add ourself to our buddy list
+ self.add_buddy([screenname])
+
+ # finish up the signon procedure
+ self.signon()
+
+ def updateBuddy(self,username,online,evilness,signontime,idletime,userclass,away):
+ """called when a buddy changes state"""
+ print "status changed for",username
+
+ def hearWarning(self, warnlvl, screenname):
+ """called when someone warns us"""
+ print screenname,"warned us"
+
+ def hearError(self, errcode, *args):
+ """called when server sends error"""
+ print "recieved error:",errcode
+
+ def hearMessage(self, username, message, autoreply):
+ """called when a message is recieved"""
+
+ # remove the incoming message' html
+ msg = ts.dehtml(message)
+
+ print "got message:",msg
+
+ # construct the reply, and turn it into html
+ reply = ts.html("echo: %s" % msg)
+
+ self.say(username, reply)
+
+cc = protocol.ClientCreator(reactor, aimBot, screenname, password)
+cc.connectTCP("toc.oscar.aol.com", 9898)
+
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/words/examples/cursesclient.py b/vendor/Twisted-10.0.0/doc/words/examples/cursesclient.py
new file mode 100644
index 0000000000..669e6ef31e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/examples/cursesclient.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This is an example of integrating curses with the twisted underlying
+select loop. Most of what is in this is insignificant -- the main piece
+of interest is the 'CursesStdIO' class.
+
+This class acts as file-descriptor 0, and is scheduled with the twisted
+select loop via reactor.addReader (once the curses class extends it
+of course). When there is input waiting doRead is called, and any
+input-oriented curses calls (ie. getch()) should be executed within this
+block.
+
+Remember to call nodelay(1) in curses, to make getch() non-blocking.
+"""
+
+# System Imports
+import curses, time, traceback, sys
+import curses.wrapper
+
+# Twisted imports
+from twisted.internet import reactor
+from twisted.internet.protocol import ClientFactory
+from twisted.words.protocols.irc import IRCClient
+from twisted.python import log
+
+class TextTooLongError(Exception):
+ pass
+
+class CursesStdIO:
+ """fake fd to be registered as a reader with the twisted reactor.
+ Curses classes needing input should extend this"""
+
+ def fileno(self):
+ """ We want to select on FD 0 """
+ return 0
+
+ def doRead(self):
+ """called when input is ready"""
+
+ def logPrefix(self): return 'CursesClient'
+
+
+class IRC(IRCClient):
+
+ """ A protocol object for IRC """
+
+ nickname = "testcurses"
+
+ def __init__(self, screenObj):
+ # screenObj should be 'stdscr' or a curses window/pad object
+ self.screenObj = screenObj
+ # for testing (hacky way around initial bad design for this example) :)
+ self.screenObj.irc = self
+
+ def lineReceived(self, line):
+ """ When receiving a line, add it to the output buffer """
+ self.screenObj.addLine(line)
+
+ def connectionMade(self):
+ IRCClient.connectionMade(self)
+ self.screenObj.addLine("* CONNECTED")
+
+ def clientConnectionLost(self, connection, reason):
+ pass
+
+
+class IRCFactory(ClientFactory):
+
+ """
+ Factory used for creating IRC protocol objects
+ """
+
+ protocol = IRC
+
+ def __init__(self, screenObj):
+ self.irc = self.protocol(screenObj)
+
+ def buildProtocol(self, addr=None):
+ return self.irc
+
+ def clientConnectionLost(self, conn, reason):
+ pass
+
+
+class Screen(CursesStdIO):
+ def __init__(self, stdscr):
+ self.timer = 0
+ self.statusText = "TEST CURSES APP -"
+ self.searchText = ''
+ self.stdscr = stdscr
+
+ # set screen attributes
+ self.stdscr.nodelay(1) # this is used to make input calls non-blocking
+ curses.cbreak()
+ self.stdscr.keypad(1)
+ curses.curs_set(0) # no annoying mouse cursor
+
+ self.rows, self.cols = self.stdscr.getmaxyx()
+ self.lines = []
+
+ curses.start_color()
+
+ # create color pair's 1 and 2
+ curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
+ curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK)
+
+ self.paintStatus(self.statusText)
+
+ def connectionLost(self, reason):
+ self.close()
+
+ def addLine(self, text):
+ """ add a line to the internal list of lines"""
+
+ self.lines.append(text)
+ self.redisplayLines()
+
+ def redisplayLines(self):
+ """ method for redisplaying lines
+ based on internal list of lines """
+
+ self.stdscr.clear()
+ self.paintStatus(self.statusText)
+ i = 0
+ index = len(self.lines) - 1
+ while i < (self.rows - 3) and index >= 0:
+ self.stdscr.addstr(self.rows - 3 - i, 0, self.lines[index],
+ curses.color_pair(2))
+ i = i + 1
+ index = index - 1
+ self.stdscr.refresh()
+
+ def paintStatus(self, text):
+ if len(text) > self.cols: raise TextTooLongError
+ self.stdscr.addstr(self.rows-2,0,text + ' ' * (self.cols-len(text)),
+ curses.color_pair(1))
+ # move cursor to input line
+ self.stdscr.move(self.rows-1, self.cols-1)
+
+ def doRead(self):
+ """ Input is ready! """
+ curses.noecho()
+ self.timer = self.timer + 1
+ c = self.stdscr.getch() # read a character
+
+ if c == curses.KEY_BACKSPACE:
+ self.searchText = self.searchText[:-1]
+
+ elif c == curses.KEY_ENTER or c == 10:
+ self.addLine(self.searchText)
+ # for testing too
+ try: self.irc.sendLine(self.searchText)
+ except: pass
+ self.stdscr.refresh()
+ self.searchText = ''
+
+ else:
+ if len(self.searchText) == self.cols-2: return
+ self.searchText = self.searchText + chr(c)
+
+ self.stdscr.addstr(self.rows-1, 0,
+ self.searchText + (' ' * (
+ self.cols-len(self.searchText)-2)))
+ self.stdscr.move(self.rows-1, len(self.searchText))
+ self.paintStatus(self.statusText + ' %d' % len(self.searchText))
+ self.stdscr.refresh()
+
+ def close(self):
+ """ clean up """
+
+ curses.nocbreak()
+ self.stdscr.keypad(0)
+ curses.echo()
+ curses.endwin()
+
+if __name__ == '__main__':
+ stdscr = curses.initscr() # initialize curses
+ screen = Screen(stdscr) # create Screen object
+ stdscr.refresh()
+ ircFactory = IRCFactory(screen)
+ reactor.addReader(screen) # add screen object as a reader to the reactor
+ reactor.connectTCP("irc.freenode.net",6667,ircFactory) # connect to IRC
+ reactor.run() # have fun!
+ screen.close()
diff --git a/vendor/Twisted-10.0.0/doc/words/examples/index.html b/vendor/Twisted-10.0.0/doc/words/examples/index.html
new file mode 100644
index 0000000000..5b7b563a28
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/examples/index.html
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Words code examples</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Words code examples</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+ <span/>
+
+ <ul>
+ <li><a href="ircLogBot.py" shape="rect">ircLogBot.py</a> - connects to an IRC server and logs all messages</li>
+ <li><a href="minchat.py" shape="rect">minchat.py</a> - log bot using twisted.im</li>
+ <li><a href="aimbot.py" shape="rect">aimbot.py</a> - (AOL Instant Messaging)</li>
+ <li><a href="msn_example.py" shape="rect">msn_example.py</a></li>
+ <li><a href="oscardemo.py" shape="rect">oscardemo.py</a></li>
+ <li><a href="jabber_client.py" shape="rect">jabber_client.py</a></li>
+ <li><a href="pb_client.py" shape="rect">pb_client.py</a></li>
+ <li><a href="xmpp_client.py" shape="rect">xmpp_client.py</a></li>
+ <li><a href="cursesclient.py" shape="rect">cursesclient.py</a> - trivial curses-based IRC client</li>
+ </ul>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/words/examples/ircLogBot.py b/vendor/Twisted-10.0.0/doc/words/examples/ircLogBot.py
new file mode 100644
index 0000000000..25c7870483
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/examples/ircLogBot.py
@@ -0,0 +1,156 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example IRC log bot - logs a channel's events to a file.
+
+If someone says the bot's name in the channel followed by a ':',
+e.g.
+
+ <foo> logbot: hello!
+
+the bot will reply:
+
+ <logbot> foo: I am a log bot
+
+Run this script with two arguments, the channel name the bot should
+connect to, and file to log to, e.g.:
+
+ $ python ircLogBot.py test test.log
+
+will log channel #test to the file 'test.log'.
+"""
+
+
+# twisted imports
+from twisted.words.protocols import irc
+from twisted.internet import reactor, protocol
+from twisted.python import log
+
+# system imports
+import time, sys
+
+
+class MessageLogger:
+ """
+ An independent logger class (because separation of application
+ and protocol logic is a good thing).
+ """
+ def __init__(self, file):
+ self.file = file
+
+ def log(self, message):
+ """Write a message to the file."""
+ timestamp = time.strftime("[%H:%M:%S]", time.localtime(time.time()))
+ self.file.write('%s %s\n' % (timestamp, message))
+ self.file.flush()
+
+ def close(self):
+ self.file.close()
+
+
+class LogBot(irc.IRCClient):
+ """A logging IRC bot."""
+
+ nickname = "twistedbot"
+
+ def connectionMade(self):
+ irc.IRCClient.connectionMade(self)
+ self.logger = MessageLogger(open(self.factory.filename, "a"))
+ self.logger.log("[connected at %s]" %
+ time.asctime(time.localtime(time.time())))
+
+ def connectionLost(self, reason):
+ irc.IRCClient.connectionLost(self, reason)
+ self.logger.log("[disconnected at %s]" %
+ time.asctime(time.localtime(time.time())))
+ self.logger.close()
+
+
+ # callbacks for events
+
+ def signedOn(self):
+ """Called when bot has succesfully signed on to server."""
+ self.join(self.factory.channel)
+
+ def joined(self, channel):
+ """This will get called when the bot joins the channel."""
+ self.logger.log("[I have joined %s]" % channel)
+
+ def privmsg(self, user, channel, msg):
+ """This will get called when the bot receives a message."""
+ user = user.split('!', 1)[0]
+ self.logger.log("<%s> %s" % (user, msg))
+
+ # Check to see if they're sending me a private message
+ if channel == self.nickname:
+ msg = "It isn't nice to whisper! Play nice with the group."
+ self.msg(user, msg)
+ return
+
+ # Otherwise check to see if it is a message directed at me
+ if msg.startswith(self.nickname + ":"):
+ msg = "%s: I am a log bot" % user
+ self.msg(channel, msg)
+ self.logger.log("<%s> %s" % (self.nickname, msg))
+
+ def action(self, user, channel, msg):
+ """This will get called when the bot sees someone do an action."""
+ user = user.split('!', 1)[0]
+ self.logger.log("* %s %s" % (user, msg))
+
+ # irc callbacks
+
+ def irc_NICK(self, prefix, params):
+ """Called when an IRC user changes their nickname."""
+ old_nick = prefix.split('!')[0]
+ new_nick = params[0]
+ self.logger.log("%s is now known as %s" % (old_nick, new_nick))
+
+
+ # For fun, override the method that determines how a nickname is changed on
+ # collisions. The default method appends an underscore.
+ def alterCollidedNick(self, nickname):
+ """
+ Generate an altered version of a nickname that caused a collision in an
+ effort to create an unused related name for subsequent registration.
+ """
+ return nickname + '^'
+
+
+
+class LogBotFactory(protocol.ClientFactory):
+ """A factory for LogBots.
+
+ A new protocol instance will be created each time we connect to the server.
+ """
+
+ # the class of the protocol to build when new connection is made
+ protocol = LogBot
+
+ def __init__(self, channel, filename):
+ self.channel = channel
+ self.filename = filename
+
+ def clientConnectionLost(self, connector, reason):
+ """If we get disconnected, reconnect to server."""
+ connector.connect()
+
+ def clientConnectionFailed(self, connector, reason):
+ print "connection failed:", reason
+ reactor.stop()
+
+
+if __name__ == '__main__':
+ # initialize logging
+ log.startLogging(sys.stdout)
+
+ # create factory protocol and application
+ f = LogBotFactory(sys.argv[1], sys.argv[2])
+
+ # connect factory to this host and port
+ reactor.connectTCP("irc.freenode.net", 6667, f)
+
+ # run bot
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/words/examples/jabber_client.py b/vendor/Twisted-10.0.0/doc/words/examples/jabber_client.py
new file mode 100644
index 0000000000..6ee3c7aa17
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/examples/jabber_client.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# Originally written by Darryl Vandorp
+# http://randomthoughts.vandorp.ca/
+
+from twisted.words.protocols.jabber import client, jid
+from twisted.words.xish import domish
+from twisted.internet import reactor
+
+def authd(xmlstream):
+ print "authenticated"
+
+ presence = domish.Element(('jabber:client','presence'))
+ xmlstream.send(presence)
+
+ xmlstream.addObserver('/message', debug)
+ xmlstream.addObserver('/presence', debug)
+ xmlstream.addObserver('/iq', debug)
+
+def debug(elem):
+ print elem.toXml().encode('utf-8')
+ print "="*20
+
+myJid = jid.JID('username@server.jabber/twisted_words')
+factory = client.basicClientFactory(myJid, 'password')
+factory.addBootstrap('//event/stream/authd',authd)
+reactor.connectTCP('server.jabber',5222,factory)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/words/examples/minchat.py b/vendor/Twisted-10.0.0/doc/words/examples/minchat.py
new file mode 100644
index 0000000000..b58571bfe6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/examples/minchat.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+A very simple twisted.im-based logbot.
+"""
+
+from twisted.im import basechat, baseaccount
+
+# A list of account objects. We might as well create them at runtime, this is
+# supposed to be a Minimalist Implementation, after all.
+from twisted.im import ircsupport
+accounts = [
+ ircsupport.IRCAccount("IRC", 1,
+ "Tooty", # nickname
+ "", # passwd
+ "irc.freenode.net", # irc server
+ 6667, # port
+ "#twisted", # comma-seperated list of channels
+ )
+]
+
+
+class AccountManager (baseaccount.AccountManager):
+ """This class is a minimal implementation of the Acccount Manager.
+
+ Most implementations will show some screen that lets the user add and
+ remove accounts, but we're not quite that sophisticated.
+ """
+
+ def __init__(self):
+
+ self.chatui = MinChat()
+
+ if len(accounts) == 0:
+ print "You have defined no accounts."
+ for acct in accounts:
+ acct.logOn(self.chatui)
+
+
+class MinConversation(basechat.Conversation):
+ """This class is a minimal implementation of the abstract Conversation class.
+
+ This is all you need to override to receive one-on-one messages.
+ """
+ def show(self):
+ """If you don't have a GUI, this is a no-op.
+ """
+ pass
+
+ def hide(self):
+ """If you don't have a GUI, this is a no-op.
+ """
+ pass
+
+ def showMessage(self, text, metadata=None):
+ print "<%s> %s" % (self.person.name, text)
+
+ def contactChangedNick(self, person, newnick):
+ basechat.Conversation.contactChangedNick(self, person, newnick)
+ print "-!- %s is now known as %s" % (person.name, newnick)
+
+
+class MinGroupConversation(basechat.GroupConversation):
+ """This class is a minimal implementation of the abstract GroupConversation class.
+
+ This is all you need to override to listen in on a group conversaion.
+ """
+ def show(self):
+ """If you don't have a GUI, this is a no-op.
+ """
+ pass
+
+ def hide(self):
+ """If you don't have a GUI, this is a no-op.
+ """
+ pass
+
+ def showGroupMessage(self, sender, text, metadata=None):
+ print "<%s/%s> %s" % (sender, self.group.name, text)
+
+ def setTopic(self, topic, author):
+ print "-!- %s set the topic of %s to: %s" % (author,
+ self.group.name, topic)
+
+ def memberJoined(self, member):
+ basechat.GroupConversation.memberJoined(self, member)
+ print "-!- %s joined %s" % (member, self.group.name)
+
+ def memberChangedNick(self, oldnick, newnick):
+ basechat.GroupConversation.memberChangedNick(self, oldnick, newnick)
+ print "-!- %s is now known as %s in %s" % (oldnick, newnick,
+ self.group.name)
+
+ def memberLeft(self, member):
+ basechat.GroupConversation.memberLeft(self, member)
+ print "-!- %s left %s" % (member, self.group.name)
+
+class MinChat(basechat.ChatUI):
+ """This class is a minimal implementation of the abstract ChatUI class.
+
+ There are only two methods that need overriding - and of those two,
+ the only change that needs to be made is the default value of the Class
+ parameter.
+ """
+
+ def getGroupConversation(self, group, Class=MinGroupConversation,
+ stayHidden=0):
+
+ return basechat.ChatUI.getGroupConversation(self, group, Class,
+ stayHidden)
+
+ def getConversation(self, person, Class=MinConversation,
+ stayHidden=0):
+
+ return basechat.ChatUI.getConversation(self, person, Class, stayHidden)
+
+if __name__ == "__main__":
+ from twisted.internet import reactor
+
+ AccountManager()
+
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/words/examples/msn_example.py b/vendor/Twisted-10.0.0/doc/words/examples/msn_example.py
new file mode 100644
index 0000000000..05ec8b11c9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/examples/msn_example.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# Twisted Imports
+from twisted.internet import reactor
+from twisted.internet.protocol import ClientFactory
+from twisted.words.protocols import msn
+from twisted.python import log
+
+# System Imports
+import sys, getpass
+
+"""
+This example connects to the MSN chat service and
+prints out information about all the users on your
+contact list (both online and offline).
+
+The main aim of this example is to demonstrate
+the connection process.
+
+@author Samuel Jordan
+"""
+
+
+def _createNotificationFac():
+ fac = msn.NotificationFactory()
+ fac.userHandle = USER_HANDLE
+ fac.password = PASSWORD
+ fac.protocol = Notification
+ return fac
+
+class Dispatch(msn.DispatchClient):
+
+ def __init__(self):
+ msn.DispatchClient.__init__(self)
+ self.userHandle = USER_HANDLE
+
+ def gotNotificationReferral(self, host, port):
+ self.transport.loseConnection()
+ reactor.connectTCP(host, port, _createNotificationFac())
+
+class Notification(msn.NotificationClient):
+
+ def loginFailure(self, message):
+ print 'Login failure:', message
+
+ def listSynchronized(self, *args):
+ contactList = self.factory.contacts
+ print 'Contact list has been synchronized, number of contacts = %s' % len(contactList.getContacts())
+ for contact in contactList.getContacts().values():
+ print 'Contact: %s' % (contact.screenName,)
+ print ' email: %s' % (contact.userHandle,)
+ print ' groups:'
+ for group in contact.groups:
+ print ' - %s' % contactList.groups[group]
+ print
+
+if __name__ == '__main__':
+ USER_HANDLE = raw_input("Email (passport): ")
+ PASSWORD = getpass.getpass()
+ log.startLogging(sys.stdout)
+ _dummy_fac = ClientFactory()
+ _dummy_fac.protocol = Dispatch
+ reactor.connectTCP('messenger.hotmail.com', 1863, _dummy_fac)
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/words/examples/oscardemo.py b/vendor/Twisted-10.0.0/doc/words/examples/oscardemo.py
new file mode 100755
index 0000000000..19e2aa4e94
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/examples/oscardemo.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.words.protocols import oscar
+from twisted.internet import protocol, reactor
+import getpass
+
+SN = raw_input('Username: ') # replace this with a screenname
+PASS = getpass.getpass('Password: ')# replace this with a password
+if SN[0].isdigit():
+ icqMode = 1
+ hostport = ('login.icq.com', 5238)
+else:
+ hostport = ('login.oscar.aol.com', 5190)
+ icqMode = 0
+
+class B(oscar.BOSConnection):
+ capabilities = [oscar.CAP_CHAT]
+ def initDone(self):
+ self.requestSelfInfo().addCallback(self.gotSelfInfo)
+ self.requestSSI().addCallback(self.gotBuddyList)
+ def gotSelfInfo(self, user):
+ print user.__dict__
+ self.name = user.name
+ def gotBuddyList(self, l):
+ print l
+ self.activateSSI()
+ self.setProfile("""this is a test of the current twisted.oscar code.<br>
+current features:<br>
+* send me a message, and you should get it back.<br>
+* invite me to a chat room. i'll repeat what people say. say 'leave' and i'll go.<br>
+* also, i hang out in '%s Chat'. join that, i'll repeat what you say there.<br>
+* try warning me. just try it.<br>
+<br>
+if any of those features don't work, tell paul (Z3Penguin). thanks."""%SN)
+ self.setIdleTime(0)
+ self.clientReady()
+ self.createChat('%s Chat'%SN).addCallback(self.createdRoom)
+ def createdRoom(self, (exchange, fullName, instance)):
+ print 'created room',exchange, fullName, instance
+ self.joinChat(exchange, fullName, instance).addCallback(self.chatJoined)
+ def updateBuddy(self, user):
+ print user
+ def offlineBuddy(self, user):
+ print 'offline', user.name
+ def receiveMessage(self, user, multiparts, flags):
+ print user.name, multiparts, flags
+ self.getAway(user.name).addCallback(self.gotAway, user.name)
+ if multiparts[0][0].find('away')!=-1:
+ self.setAway('I am away from my computer right now.')
+ elif multiparts[0][0].find('back')!=-1:
+ self.setAway(None)
+ if self.awayMessage:
+ self.sendMessage(user.name,'<html><font color="#0000ff">'+self.awayMessage,autoResponse=1)
+ else:
+ self.lastUser = user.name
+ self.sendMessage(user.name, multiparts, wantAck = 1, autoResponse = (self.awayMessage!=None)).addCallback( \
+ self.messageAck)
+ def messageAck(self, (username, message)):
+ print 'message sent to %s acked' % username
+ def gotAway(self, away, user):
+ if away != None:
+ print 'got away for',user,':',away
+ def receiveWarning(self, newLevel, user):
+ print 'got warning from', hasattr(user,'name') and user.name or None
+ print 'new warning level', newLevel
+ if not user:
+ #username = self.lastUser
+ return
+ else:
+ username = user.name
+ self.warnUser(username).addCallback(self.warnedUser, username)
+ def warnedUser(self, oldLevel, newLevel, username):
+ self.sendMessage(username,'muahaha :-p')
+ def receiveChatInvite(self, user, message, exchange, fullName, instance, shortName, inviteTime):
+ print 'chat invite from',user.name,'for room',shortName,'with message:',message
+ self.joinChat(exchange, fullName, instance).addCallback(self.chatJoined)
+ def chatJoined(self, chat):
+ print 'joined chat room', chat.name
+ print 'members:',map(lambda x:x.name,chat.members)
+ def chatReceiveMessage(self, chat, user, message):
+ print 'message to',chat.name,'from',user.name,':',message
+ if user.name!=self.name: chat.sendMessage(user.name+': '+message)
+ if message.find('leave')!=-1 and chat.name!='%s Chat'%SN: chat.leaveChat()
+ def chatMemberJoined(self, chat, member):
+ print member.name,'joined',chat.name
+ def chatMemberLeft(self, chat, member):
+ print member.name,'left',chat.name
+ print 'current members',map(lambda x:x.name,chat.members)
+ if chat.name!="%s Chat"%SN and len(chat.members)==1:
+ print 'leaving', chat.name
+ chat.leaveChat()
+
+class OA(oscar.OscarAuthenticator):
+ BOSClass = B
+
+protocol.ClientCreator(reactor, OA, SN, PASS, icq=icqMode).connectTCP(*hostport)
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/words/examples/pb_client.py b/vendor/Twisted-10.0.0/doc/words/examples/pb_client.py
new file mode 100644
index 0000000000..9d45824adb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/examples/pb_client.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Simple PB Words client demo
+
+This connects to a server (host/port specified by argv[1]/argv[2]),
+authenticates with a username and password (given by argv[3] and argv[4]),
+joins a group (argv[5]) sends a simple message, leaves the group, and quits
+the server.
+"""
+
+import sys
+from twisted.python import log
+from twisted.cred import credentials
+from twisted.words import service
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class DemoMind(service.PBMind):
+ """An utterly pointless PBMind subclass.
+
+ This notices messages received and prints them to stdout. Since
+ the bot never stays in a channel very long, it is exceedingly
+ unlikely this will ever do anything interesting.
+ """
+ def remote_receive(self, sender, recipient, message):
+ print 'Woop', sender, recipient, message
+
+def quitServer(ignored):
+ """Quit succeeded, shut down the reactor.
+ """
+ reactor.stop()
+
+def leftGroup(ignored, avatar):
+ """Left the group successfully, quit the server.
+ """
+ q = avatar.quit()
+ q.addCallback(quitServer)
+ return q
+
+def sentMessage(ignored, group, avatar):
+ """Sent the message successfully, leave the group.
+ """
+ l = group.leave()
+ l.addCallback(leftGroup, avatar)
+ return l
+
+def joinedGroup(group, avatar):
+ """Joined the group successfully, send a stupid message.
+ """
+ s = group.send({"text": "Hello, monkeys"})
+ s.addCallback(sentMessage, group, avatar)
+ return s
+
+def loggedIn(avatar, group):
+ """Logged in successfully, join a group.
+ """
+ j = avatar.join(group)
+ j.addCallback(joinedGroup, avatar)
+ return j
+
+def errorOccurred(err):
+ """Something went awry, log it and shutdown.
+ """
+ log.err(err)
+ try:
+ reactor.stop()
+ except RuntimeError:
+ pass
+
+def run(host, port, username, password, group):
+ """Create a mind and factory and set things in motion.
+ """
+ m = DemoMind()
+ f = pb.PBClientFactory()
+ f.unsafeTracebacks = True
+ l = f.login(credentials.UsernamePassword(username, password), m)
+ l.addCallback(loggedIn, group)
+ l.addErrback(errorOccurred)
+ reactor.connectTCP(host, int(port), f)
+
+def main():
+ """
+ Set up logging, have the real main function run, and start the reactor.
+ """
+ if len(sys.argv) != 6:
+ raise SystemExit("Usage: %s host port username password group" % (sys.argv[0],))
+ log.startLogging(sys.stdout)
+
+ host, port, username, password, group = sys.argv[1:]
+ port = int(port)
+ username = username.decode(sys.stdin.encoding)
+ group = group.decode(sys.stdin.encoding)
+
+ reactor.callWhenRunning(run, host, port, username, password, group)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/doc/words/examples/xmpp_client.py b/vendor/Twisted-10.0.0/doc/words/examples/xmpp_client.py
new file mode 100644
index 0000000000..994e99431e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/examples/xmpp_client.py
@@ -0,0 +1,82 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+from twisted.internet import reactor
+from twisted.names.srvconnect import SRVConnector
+from twisted.words.xish import domish
+from twisted.words.protocols.jabber import xmlstream, client, jid
+
+
+class XMPPClientConnector(SRVConnector):
+ def __init__(self, reactor, domain, factory):
+ SRVConnector.__init__(self, reactor, 'xmpp-client', domain, factory)
+
+
+ def pickServer(self):
+ host, port = SRVConnector.pickServer(self)
+
+ if not self.servers and not self.orderedServers:
+ # no SRV record, fall back..
+ port = 5222
+
+ return host, port
+
+
+
+class Client(object):
+ def __init__(self, client_jid, secret):
+ f = client.XMPPClientFactory(client_jid, secret)
+ f.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT, self.connected)
+ f.addBootstrap(xmlstream.STREAM_END_EVENT, self.disconnected)
+ f.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self.authenticated)
+ f.addBootstrap(xmlstream.INIT_FAILED_EVENT, self.init_failed)
+ connector = XMPPClientConnector(reactor, client_jid.host, f)
+ connector.connect()
+
+
+ def rawDataIn(self, buf):
+ print "RECV: %s" % unicode(buf, 'utf-8').encode('ascii', 'replace')
+
+
+ def rawDataOut(self, buf):
+ print "SEND: %s" % unicode(buf, 'utf-8').encode('ascii', 'replace')
+
+
+ def connected(self, xs):
+ print 'Connected.'
+
+ self.xmlstream = xs
+
+ # Log all traffic
+ xs.rawDataInFn = self.rawDataIn
+ xs.rawDataOutFn = self.rawDataOut
+
+
+ def disconnected(self, xs):
+ print 'Disconnected.'
+
+ reactor.stop()
+
+
+ def authenticated(self, xs):
+ print "Authenticated."
+
+ presence = domish.Element((None, 'presence'))
+ xs.send(presence)
+
+ reactor.callLater(5, xs.sendFooter)
+
+
+ def init_failed(self, failure):
+ print "Initialization failed."
+ print failure
+
+ self.xmlstream.sendFooter()
+
+
+client_jid = jid.JID(sys.argv[1])
+secret = sys.argv[2]
+c = Client(client_jid, secret)
+
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/doc/words/howto/im.html b/vendor/Twisted-10.0.0/doc/words/howto/im.html
new file mode 100644
index 0000000000..b6d58d4ba2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/howto/im.html
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Overview of Twisted IM</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Overview of Twisted IM</h1>
+ <div class="toc"><ol><li><a href="#auto0">Code flow</a></li><ul><li><a href="#auto1">AccountManager</a></li><li><a href="#auto2">ChatUI</a></li><li><a href="#auto3">Conversation and GroupConversation</a></li><li><a href="#auto4">Accounts</a></li></ul></ol></div>
+ <div class="content">
+<span/>
+
+<div class="note"><strong>Note: </strong>Twisted IM and Twisted Words are both known to be in a state
+of flux at the moment. Several of the APIs discussed here have fallen short of
+their original goals and <em>will</em> be changing within the next few releases
+of Twisted. The good news is that newer versions will be based on our
+experiences with the current ones and will provide much more access to features
+beyond plain-text chat message relaying in different protocols.</div>
+
+ <p>Twisted IM (Instance Messenger) is a multi-protocol chat
+ framework, based on the Twisted framework we've all come to know
+ and love. It's fairly simple and extensible in two directions -
+ it's pretty easy to add new protocols, and it's also quite easy
+ to add new front-ends.</p>
+
+ <h2>Code flow<a name="auto0"/></h2>
+ <p>Twisted IM is usually started from the file
+ <code>twisted/scripts/im.py</code> (maybe with a shell-script
+ wrapper or similar). Twisted currently comes with two
+ interfaces for Twisted IM - one written in GTK for Python
+ under Linux, and one written in Swing for Jython.
+ <code>im.py</code> picks an implementation and starts it - if
+ you want to write your own interface, you can modify
+ <code>im.py</code> to start it under appropriate
+ conditions.</p>
+
+ <p>Once started, both interfaces behave in a very similar
+ fashion, so I won't be getting into differences here.</p>
+
+ <h3>AccountManager<a name="auto1"/></h3>
+ <p>Control flow starts at the relevant subclass of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.im.baseaccount.AccountManager.html" title="twisted.im.baseaccount.AccountManager">baseaccount.AccountManager</a></code>.
+ The AccountManager is responsible for, well, managing accounts
+ - remembering what accounts are available, their
+ settings, adding and removal of accounts, and making accounts
+ log on at startup.</p>
+
+ <p>This would be a good place to start your interface, load a
+ list of accounts from disk and tell them to login. Most of the
+ method names in <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.im.baseaccount.AccountManager.html" title="twisted.im.baseaccount.AccountManager">AccountManager</a></code>
+ are pretty self-explanatory, and your subclass can override
+ whatever it wants, but you <em>need</em> to override <code class="python">__init__</code>. Something like
+ this:</p>
+
+ <pre class="python"><p class="py-linenumber">1
+2
+3
+4
+5
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>):
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">chatui</span> = ... <span class="py-src-comment"># Your subclass of basechat.ChatUI</span>
+ <span class="py-src-variable">self</span>.<span class="py-src-variable">accounts</span> = ... <span class="py-src-comment"># Load account list</span>
+ <span class="py-src-keyword">for</span> <span class="py-src-variable">a</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">accounts</span>:
+ <span class="py-src-variable">a</span>.<span class="py-src-variable">logOn</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">chatui</span>)
+</pre>
+
+ <h3>ChatUI<a name="auto2"/></h3>
+ <p>Account objects talk to the user via a subclass of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.im.basechat.ChatUI.html" title="twisted.im.basechat.ChatUI">basechat.ChatUI</a></code>.
+ This class keeps track of all the various conversations that
+ are currently active, so that when an account receives and
+ incoming message, it can put that message in its correct
+ context.</p>
+
+ <p>How much of this class you need to override depends on what
+ you need to do. You will need to override
+ <code>getConversation</code> (a one-on-one conversation, like
+ an IRC DCC chat) and <code>getGroupConversation</code> (a
+ multiple user conversation, like an IRC channel). You might
+ want to override <code>getGroup</code> and
+ <code>getPerson</code>.</p>
+
+ <p>The main problem with the default versions of the above
+ routines is that they take a parameter, <code>Class</code>,
+ which defaults to an abstract implementation of that class -
+ for example, <code>getConversation</code> has a
+ <code>Class</code> parameter that defaults to <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.im.basechat.Conversation.html" title="twisted.im.basechat.Conversation">basechat.Conversation</a></code> which
+ raises a lot of <code>NotImplementedError</code>s. In your
+ subclass, override the method with a new method whose Class
+ parameter defaults to your own implementation of
+ <code>Conversation</code>, that simply calls the parent
+ class' implementation.</p>
+
+ <h3>Conversation and GroupConversation<a name="auto3"/></h3>
+ <p>These classes are where your interface meets the chat
+ protocol. Chat protocols get a message, find the appropriate
+ <code>Conversation</code> or <code>GroupConversation</code>
+ object, and call its methods when various interesting things
+ happen.</p>
+
+ <p>Override whatever methods you want to get the information
+ you want to display. You must override the <code>hide</code>
+ and <code>show</code> methods, however - they are called
+ frequently and the default implementation raises
+ <code>NotImplementedError</code>.</p>
+
+ <h3>Accounts<a name="auto4"/></h3>
+ <p>An account is an instance of a subclass of <code class="API"><a href="http://twistedmatrix.com/documents/10.0.0/api/twisted.im.basesupport.AbstractAccount.html" title="twisted.im.basesupport.AbstractAccount">basesupport.AbstractAccount</a></code>.
+ For more details and sample code, see the various
+ <code>*support</code> files in <code>twisted.im</code>.</p>
+
+ </div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/words/howto/index.html b/vendor/Twisted-10.0.0/doc/words/howto/index.html
new file mode 100644
index 0000000000..f76e507be0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/howto/index.html
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted IM Documentation</title>
+<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted IM Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+
+<span/>
+
+<ul class="toc">
+ <li><a href="im.html" shape="rect">Twisted IM</a></li>
+</ul>
+</div>
+
+ <p><a href="index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/words/index.html b/vendor/Twisted-10.0.0/doc/words/index.html
new file mode 100644
index 0000000000..fca8187187
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/index.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: Twisted Words Documentation</title>
+<link href="howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">Twisted Words Documentation</h1>
+ <div class="toc"><ol/></div>
+ <div class="content">
+<span/>
+
+<ul>
+<li><a href="howto/index.html" shape="rect">Developer guides</a>: documentation on using
+Twisted Words to develop your own applications</li>
+<li><a href="examples/index.html" shape="rect">Examples</a>: short code examples using
+Twisted Words</li>
+</ul>
+
+</div>
+
+ <p><a href="howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/words/man/im-man.html b/vendor/Twisted-10.0.0/doc/words/man/im-man.html
new file mode 100644
index 0000000000..95d91052ca
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/man/im-man.html
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+<title>Twisted Documentation: IM.1</title>
+<link href="../howto/stylesheet.css" rel="stylesheet" type="text/css"/>
+ </head>
+
+ <body bgcolor="white">
+ <h1 class="title">IM.1</h1>
+ <div class="toc"><ol><li><a href="#auto0">NAME</a></li><li><a href="#auto1">SYNOPSIS</a></li><li><a href="#auto2">DESCRIPTION</a></li><li><a href="#auto3">AUTHOR</a></li><li><a href="#auto4">REPORTING BUGS</a></li><li><a href="#auto5">COPYRIGHT</a></li></ol></div>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>im - run Instance Messenger, the Tkinter twisted.words client
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>im</strong> </p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>Run Instance Messenger, the Tkinter twisted.words client
+</p>
+
+<h2>AUTHOR<a name="auto3"/></h2>
+
+<p>Written by Moshe Zadka, based on im's help messages
+</p>
+
+<h2>REPORTING BUGS<a name="auto4"/></h2>
+
+<p>To report a bug, visit <em>http://twistedmatrix.com/bugs/</em>
+</p>
+
+<h2>COPYRIGHT<a name="auto5"/></h2>
+
+<p>Copyright © 2000-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+</p>
+
+</div>
+
+ <p><a href="../howto/index.html">Index</a></p>
+ <span class="version">Version: 10.0.0</span>
+ </body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/doc/words/man/im.1 b/vendor/Twisted-10.0.0/doc/words/man/im.1
new file mode 100644
index 0000000000..98139b3b95
--- /dev/null
+++ b/vendor/Twisted-10.0.0/doc/words/man/im.1
@@ -0,0 +1,16 @@
+.TH IM "1" "July 2001" "" ""
+.SH NAME
+im \- run Instance Messenger, the Tkinter twisted.words client
+.SH SYNOPSIS
+.B im
+.SH DESCRIPTION
+Run Instance Messenger, the Tkinter twisted.words client
+.SH AUTHOR
+Written by Moshe Zadka, based on im's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/vendor/Twisted-10.0.0/setup.py b/vendor/Twisted-10.0.0/setup.py
new file mode 100755
index 0000000000..c8b58775f8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/setup.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Distutils installer for Twisted.
+"""
+
+try:
+ # Load setuptools, to build a specific source package
+ import setuptools
+except ImportError:
+ pass
+
+import sys, os
+
+
+def getExtensions():
+ """
+ Get all extensions from core and all subprojects.
+ """
+ extensions = []
+
+ if not sys.platform.startswith('java'):
+ for dir in os.listdir("twisted") + [""]:
+ topfiles = os.path.join("twisted", dir, "topfiles")
+ if os.path.isdir(topfiles):
+ ns = {}
+ setup_py = os.path.join(topfiles, "setup.py")
+ execfile(setup_py, ns, ns)
+ if "extensions" in ns:
+ extensions.extend(ns["extensions"])
+
+ return extensions
+
+
+def main(args):
+ """
+ Invoke twisted.python.dist with the appropriate metadata about the
+ Twisted package.
+ """
+ if os.path.exists('twisted'):
+ sys.path.insert(0, '.')
+ from twisted import copyright
+ from twisted.python.dist import getDataFiles, getScripts, getPackages, setup
+
+ # "" is included because core scripts are directly in bin/
+ projects = [''] + [x for x in os.listdir('bin')
+ if os.path.isdir(os.path.join("bin", x))
+ and not x.startswith(".")]
+ scripts = []
+ for i in projects:
+ scripts.extend(getScripts(i))
+
+ setup_args = dict(
+ # metadata
+ name="Twisted",
+ version=copyright.version,
+ description="An asynchronous networking framework written in "
+ "Python",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="Glyph Lefkowitz",
+ maintainer_email="glyph@twistedmatrix.com",
+ url="http://twistedmatrix.com/",
+ license="MIT",
+ long_description="""\
+An extensible framework for Python programming, with special focus
+on event-based network programming and multiprotocol integration.
+""",
+ packages = getPackages('twisted'),
+ conditionalExtensions = getExtensions(),
+ scripts = scripts,
+ data_files=getDataFiles('twisted'),
+ )
+
+ if 'setuptools' in sys.modules:
+ from pkg_resources import parse_requirements
+ requirements = ["zope.interface"]
+ try:
+ list(parse_requirements(requirements))
+ except:
+ print """You seem to be running a very old version of setuptools.
+This version of setuptools has a bug parsing dependencies, so automatic
+dependency resolution is disabled.
+"""
+ else:
+ setup_args['install_requires'] = requirements
+ setup_args['include_package_data'] = True
+ setup_args['zip_safe'] = False
+ setup(**setup_args)
+
+
+if __name__ == "__main__":
+ try:
+ main(sys.argv[1:])
+ except KeyboardInterrupt:
+ sys.exit(1)
+
diff --git a/vendor/Twisted-10.0.0/twisted/__init__.py b/vendor/Twisted-10.0.0/twisted/__init__.py
new file mode 100644
index 0000000000..9fa050d26d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/__init__.py
@@ -0,0 +1,24 @@
+# -*- test-case-name: twisted -*-
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Twisted: The Framework Of Your Internet.
+"""
+
+# Ensure the user is running the version of python we require.
+import sys
+if not hasattr(sys, "version_info") or sys.version_info < (2,3):
+ raise RuntimeError("Twisted requires Python 2.3 or later.")
+del sys
+
+# Ensure compat gets imported
+from twisted.python import compat
+del compat
+
+# setup version
+from twisted._version import version
+__version__ = version.short()
+
diff --git a/vendor/Twisted-10.0.0/twisted/_version.py b/vendor/Twisted-10.0.0/twisted/_version.py
new file mode 100644
index 0000000000..f2461e110c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/_version.py
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twisted', 10, 0, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/application/__init__.py b/vendor/Twisted-10.0.0/twisted/application/__init__.py
new file mode 100644
index 0000000000..616928127f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/application/__init__.py
@@ -0,0 +1,7 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""
+Configuration objects for Twisted Applications
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/application/app.py b/vendor/Twisted-10.0.0/twisted/application/app.py
new file mode 100644
index 0000000000..3e73ac7b45
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/application/app.py
@@ -0,0 +1,730 @@
+# -*- test-case-name: twisted.test.test_application,twisted.test.test_twistd -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys, os, pdb, getpass, traceback, signal, warnings
+
+from twisted.python import runtime, log, usage, failure, util, logfile
+from twisted.python.versions import Version
+from twisted.python.reflect import qual
+from twisted.python.deprecate import deprecated
+from twisted.python.log import ILogObserver
+from twisted.persisted import sob
+from twisted.application import service, reactors
+from twisted.internet import defer
+from twisted import copyright
+
+# Expose the new implementation of installReactor at the old location.
+from twisted.application.reactors import installReactor
+from twisted.application.reactors import NoSuchReactor
+
+
+
+class _BasicProfiler(object):
+ """
+ @ivar saveStats: if C{True}, save the stats information instead of the
+ human readable format
+ @type saveStats: C{bool}
+
+ @ivar profileOutput: the name of the file use to print profile data.
+ @type profileOutput: C{str}
+ """
+
+ def __init__(self, profileOutput, saveStats):
+ self.profileOutput = profileOutput
+ self.saveStats = saveStats
+
+
+ def _reportImportError(self, module, e):
+ """
+ Helper method to report an import error with a profile module. This
+ has to be explicit because some of these modules are removed by
+ distributions due to them being non-free.
+ """
+ s = "Failed to import module %s: %s" % (module, e)
+ s += """
+This is most likely caused by your operating system not including
+the module due to it being non-free. Either do not use the option
+--profile, or install the module; your operating system vendor
+may provide it in a separate package.
+"""
+ raise SystemExit(s)
+
+
+
+class ProfileRunner(_BasicProfiler):
+ """
+ Runner for the standard profile module.
+ """
+
+ def run(self, reactor):
+ """
+ Run reactor under the standard profiler.
+ """
+ try:
+ import profile
+ except ImportError, e:
+ self._reportImportError("profile", e)
+
+ p = profile.Profile()
+ p.runcall(reactor.run)
+ if self.saveStats:
+ p.dump_stats(self.profileOutput)
+ else:
+ tmp, sys.stdout = sys.stdout, open(self.profileOutput, 'a')
+ try:
+ p.print_stats()
+ finally:
+ sys.stdout, tmp = tmp, sys.stdout
+ tmp.close()
+
+
+
+class HotshotRunner(_BasicProfiler):
+ """
+ Runner for the hotshot profile module.
+ """
+
+ def run(self, reactor):
+ """
+ Run reactor under the hotshot profiler.
+ """
+ try:
+ import hotshot.stats
+ except (ImportError, SystemExit), e:
+ # Certain versions of Debian (and Debian derivatives) raise
+ # SystemExit when importing hotshot if the "non-free" profiler
+ # module is not installed. Someone eventually recognized this
+ # as a bug and changed the Debian packaged Python to raise
+ # ImportError instead. Handle both exception types here in
+ # order to support the versions of Debian which have this
+ # behavior. The bug report which prompted the introduction of
+ # this highly undesirable behavior should be available online at
+ # <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=334067>.
+ # There seems to be no corresponding bug report which resulted
+ # in the behavior being removed. -exarkun
+ self._reportImportError("hotshot", e)
+
+ # this writes stats straight out
+ p = hotshot.Profile(self.profileOutput)
+ p.runcall(reactor.run)
+ if self.saveStats:
+ # stats are automatically written to file, nothing to do
+ return
+ else:
+ s = hotshot.stats.load(self.profileOutput)
+ s.strip_dirs()
+ s.sort_stats(-1)
+ if getattr(s, 'stream', None) is not None:
+ # Python 2.5 and above supports a stream attribute
+ s.stream = open(self.profileOutput, 'w')
+ s.print_stats()
+ s.stream.close()
+ else:
+ # But we have to use a trick for Python < 2.5
+ tmp, sys.stdout = sys.stdout, open(self.profileOutput, 'w')
+ try:
+ s.print_stats()
+ finally:
+ sys.stdout, tmp = tmp, sys.stdout
+ tmp.close()
+
+
+
+class CProfileRunner(_BasicProfiler):
+ """
+ Runner for the cProfile module.
+ """
+
+ def run(self, reactor):
+ """
+ Run reactor under the cProfile profiler.
+ """
+ try:
+ import cProfile, pstats
+ except ImportError, e:
+ self._reportImportError("cProfile", e)
+
+ p = cProfile.Profile()
+ p.runcall(reactor.run)
+ if self.saveStats:
+ p.dump_stats(self.profileOutput)
+ else:
+ stream = open(self.profileOutput, 'w')
+ s = pstats.Stats(p, stream=stream)
+ s.strip_dirs()
+ s.sort_stats(-1)
+ s.print_stats()
+ stream.close()
+
+
+
+class AppProfiler(object):
+ """
+ Class which selects a specific profile runner based on configuration
+ options.
+
+ @ivar profiler: the name of the selected profiler.
+ @type profiler: C{str}
+ """
+ profilers = {"profile": ProfileRunner, "hotshot": HotshotRunner,
+ "cprofile": CProfileRunner}
+
+ def __init__(self, options):
+ saveStats = options.get("savestats", False)
+ profileOutput = options.get("profile", None)
+ self.profiler = options.get("profiler", "hotshot").lower()
+ if options.get("nothotshot", False):
+ warnings.warn("The --nothotshot option is deprecated. Please "
+ "specify the profiler name using the --profiler "
+ "option", category=DeprecationWarning)
+ self.profiler = "profile"
+ if self.profiler in self.profilers:
+ profiler = self.profilers[self.profiler](profileOutput, saveStats)
+ self.run = profiler.run
+ else:
+ raise SystemExit("Unsupported profiler name: %s" % (self.profiler,))
+
+
+
+def runWithProfiler(reactor, config):
+ """
+ DEPRECATED in Twisted 8.0.
+
+ Run reactor under standard profiler.
+ """
+ warnings.warn("runWithProfiler is deprecated since Twisted 8.0. "
+ "Use ProfileRunner instead.", DeprecationWarning, 2)
+ item = AppProfiler(config)
+ return item.run(reactor)
+
+
+
+def runWithHotshot(reactor, config):
+ """
+ DEPRECATED in Twisted 8.0.
+
+ Run reactor under hotshot profiler.
+ """
+ warnings.warn("runWithHotshot is deprecated since Twisted 8.0. "
+ "Use HotshotRunner instead.", DeprecationWarning, 2)
+ item = AppProfiler(config)
+ return item.run(reactor)
+
+
+
+class AppLogger(object):
+ """
+ Class managing logging faciliy of the application.
+
+ @ivar _logfilename: The name of the file to which to log, if other than the
+ default.
+ @type _logfilename: C{str}
+
+ @ivar _observer: log observer added at C{start} and removed at C{stop}.
+ @type _observer: C{callable}
+ """
+ _observer = None
+
+ def __init__(self, options):
+ self._logfilename = options.get("logfile", "")
+
+
+ def start(self, application):
+ """
+ Initialize the logging system.
+
+ If an L{ILogObserver} component has been set on C{application}, then
+ it will be used as the log observer. Otherwise a log observer will be
+ created based on the command-line options.
+
+ @param application: The application on which to check for an
+ L{ILogObserver}.
+ """
+ observer = application.getComponent(ILogObserver, None)
+
+ if observer is None:
+ observer = self._getLogObserver()
+ self._observer = observer
+ log.startLoggingWithObserver(self._observer)
+ self._initialLog()
+
+
+ def _initialLog(self):
+ """
+ Print twistd start log message.
+ """
+ from twisted.internet import reactor
+ log.msg("twistd %s (%s %s) starting up." % (copyright.version,
+ sys.executable,
+ runtime.shortPythonVersion()))
+ log.msg('reactor class: %s.' % (qual(reactor.__class__),))
+
+
+ def _getLogObserver(self):
+ """
+ Create a log observer to be added to the logging system before running
+ this application.
+ """
+ if self._logfilename == '-' or not self._logfilename:
+ logFile = sys.stdout
+ else:
+ logFile = logfile.LogFile.fromFullPath(self._logfilename)
+ return log.FileLogObserver(logFile).emit
+
+
+ def stop(self):
+ """
+ Print twistd stop log message.
+ """
+ log.msg("Server Shut Down.")
+ if self._observer is not None:
+ log.removeObserver(self._observer)
+ self._observer = None
+
+
+
+def fixPdb():
+ def do_stop(self, arg):
+ self.clear_all_breaks()
+ self.set_continue()
+ from twisted.internet import reactor
+ reactor.callLater(0, reactor.stop)
+ return 1
+
+ def help_stop(self):
+ print """stop - Continue execution, then cleanly shutdown the twisted reactor."""
+
+ def set_quit(self):
+ os._exit(0)
+
+ pdb.Pdb.set_quit = set_quit
+ pdb.Pdb.do_stop = do_stop
+ pdb.Pdb.help_stop = help_stop
+
+
+
+def runReactorWithLogging(config, oldstdout, oldstderr, profiler=None, reactor=None):
+ """
+ Start the reactor, using profiling if specified by the configuration, and
+ log any error happening in the process.
+
+ @param config: configuration of the twistd application.
+ @type config: L{ServerOptions}
+
+ @param oldstdout: initial value of C{sys.stdout}.
+ @type oldstdout: C{file}
+
+ @param oldstderr: initial value of C{sys.stderr}.
+ @type oldstderr: C{file}
+
+ @param profiler: object used to run the reactor with profiling.
+ @type profiler: L{AppProfiler}
+
+ @param reactor: The reactor to use. If C{None}, the global reactor will
+ be used.
+ """
+ if reactor is None:
+ from twisted.internet import reactor
+ try:
+ if config['profile']:
+ if profiler is not None:
+ profiler.run(reactor)
+ else:
+ # Backward compatible code
+ if not config['nothotshot']:
+ runWithHotshot(reactor, config)
+ else:
+ runWithProfiler(reactor, config)
+ elif config['debug']:
+ sys.stdout = oldstdout
+ sys.stderr = oldstderr
+ if runtime.platformType == 'posix':
+ signal.signal(signal.SIGUSR2, lambda *args: pdb.set_trace())
+ signal.signal(signal.SIGINT, lambda *args: pdb.set_trace())
+ fixPdb()
+ pdb.runcall(reactor.run)
+ else:
+ reactor.run()
+ except:
+ if config['nodaemon']:
+ file = oldstdout
+ else:
+ file = open("TWISTD-CRASH.log",'a')
+ traceback.print_exc(file=file)
+ file.flush()
+
+
+
+def getPassphrase(needed):
+ if needed:
+ return getpass.getpass('Passphrase: ')
+ else:
+ return None
+
+
+
+def getSavePassphrase(needed):
+ if needed:
+ passphrase = util.getPassword("Encryption passphrase: ")
+ else:
+ return None
+
+
+
+class ApplicationRunner(object):
+ """
+ An object which helps running an application based on a config object.
+
+ Subclass me and implement preApplication and postApplication
+ methods. postApplication generally will want to run the reactor
+ after starting the application.
+
+ @ivar config: The config object, which provides a dict-like interface.
+
+ @ivar application: Available in postApplication, but not
+ preApplication. This is the application object.
+
+ @ivar profilerFactory: Factory for creating a profiler object, able to
+ profile the application if options are set accordingly.
+
+ @ivar profiler: Instance provided by C{profilerFactory}.
+
+ @ivar loggerFactory: Factory for creating object responsible for logging.
+
+ @ivar logger: Instance provided by C{loggerFactory}.
+ """
+ profilerFactory = AppProfiler
+ loggerFactory = AppLogger
+
+ def __init__(self, config):
+ self.config = config
+ self.profiler = self.profilerFactory(config)
+ self.logger = self.loggerFactory(config)
+
+
+ def run(self):
+ """
+ Run the application.
+ """
+ self.preApplication()
+ self.application = self.createOrGetApplication()
+
+
+ getLogObserverLegacy = getattr(self, 'getLogObserver', None)
+ if getLogObserverLegacy is not None:
+ warnings.warn("Specifying a log observer with getLogObserver is "
+ "deprecated. Please use a loggerFactory instead.",
+ category=DeprecationWarning)
+ self.startLogging(self.getLogObserver())
+ else:
+ self.logger.start(self.application)
+
+ self.postApplication()
+ self.logger.stop()
+
+
+ def startLogging(self, observer):
+ """
+ Initialize the logging system. DEPRECATED.
+
+ @param observer: The observer to add to the logging system.
+ """
+ log.startLoggingWithObserver(observer)
+ self.logger._initialLog()
+
+
+ def startReactor(self, reactor, oldstdout, oldstderr):
+ """
+ Run the reactor with the given configuration. Subclasses should
+ probably call this from C{postApplication}.
+
+ @see: L{runReactorWithLogging}
+ """
+ runReactorWithLogging(
+ self.config, oldstdout, oldstderr, self.profiler, reactor)
+
+
+ def preApplication(self):
+ """
+ Override in subclass.
+
+ This should set up any state necessary before loading and
+ running the Application.
+ """
+ raise NotImplementedError()
+
+
+ def postApplication(self):
+ """
+ Override in subclass.
+
+ This will be called after the application has been loaded (so
+ the C{application} attribute will be set). Generally this
+ should start the application and run the reactor.
+ """
+ raise NotImplementedError()
+
+
+ def createOrGetApplication(self):
+ """
+ Create or load an Application based on the parameters found in the
+ given L{ServerOptions} instance.
+
+ If a subcommand was used, the L{service.IServiceMaker} that it
+ represents will be used to construct a service to be added to
+ a newly-created Application.
+
+ Otherwise, an application will be loaded based on parameters in
+ the config.
+ """
+ if self.config.subCommand:
+ # If a subcommand was given, it's our responsibility to create
+ # the application, instead of load it from a file.
+
+ # loadedPlugins is set up by the ServerOptions.subCommands
+ # property, which is iterated somewhere in the bowels of
+ # usage.Options.
+ plg = self.config.loadedPlugins[self.config.subCommand]
+ ser = plg.makeService(self.config.subOptions)
+ application = service.Application(plg.tapname)
+ ser.setServiceParent(application)
+ else:
+ passphrase = getPassphrase(self.config['encrypted'])
+ application = getApplication(self.config, passphrase)
+ return application
+
+
+
+def getApplication(config, passphrase):
+ s = [(config[t], t)
+ for t in ['python', 'source', 'file'] if config[t]][0]
+ filename, style = s[0], {'file':'pickle'}.get(s[1],s[1])
+ try:
+ log.msg("Loading %s..." % filename)
+ application = service.loadApplication(filename, style, passphrase)
+ log.msg("Loaded.")
+ except Exception, e:
+ s = "Failed to load application: %s" % e
+ if isinstance(e, KeyError) and e.args[0] == "application":
+ s += """
+Could not find 'application' in the file. To use 'twistd -y', your .tac
+file must create a suitable object (e.g., by calling service.Application())
+and store it in a variable named 'application'. twistd loads your .tac file
+and scans the global variables for one of this name.
+
+Please read the 'Using Application' HOWTO for details.
+"""
+ traceback.print_exc(file=log.logfile)
+ log.msg(s)
+ log.deferr()
+ sys.exit('\n' + s + '\n')
+ return application
+
+
+
+def reportProfile(report_profile, name):
+ """
+ DEPRECATED since Twisted 8.0. This does nothing.
+ """
+ warnings.warn("reportProfile is deprecated and a no-op since Twisted 8.0.",
+ category=DeprecationWarning)
+
+
+
+def _reactorZshAction():
+ return "(%s)" % " ".join([r.shortName for r in reactors.getReactorTypes()])
+
+class ReactorSelectionMixin:
+ """
+ Provides options for selecting a reactor to install.
+ """
+ zsh_actions = {"reactor" : _reactorZshAction}
+ messageOutput = sys.stdout
+
+
+ def opt_help_reactors(self):
+ """
+ Display a list of possibly available reactor names.
+ """
+ for r in reactors.getReactorTypes():
+ self.messageOutput.write(' %-4s\t%s\n' %
+ (r.shortName, r.description))
+ raise SystemExit(0)
+
+
+ def opt_reactor(self, shortName):
+ """
+ Which reactor to use (see --help-reactors for a list of possibilities)
+ """
+ # Actually actually actually install the reactor right at this very
+ # moment, before any other code (for example, a sub-command plugin)
+ # runs and accidentally imports and installs the default reactor.
+ #
+ # This could probably be improved somehow.
+ try:
+ installReactor(shortName)
+ except NoSuchReactor:
+ msg = ("The specified reactor does not exist: '%s'.\n"
+ "See the list of available reactors with "
+ "--help-reactors" % (shortName,))
+ raise usage.UsageError(msg)
+ except Exception, e:
+ msg = ("The specified reactor cannot be used, failed with error: "
+ "%s.\nSee the list of available reactors with "
+ "--help-reactors" % (e,))
+ raise usage.UsageError(msg)
+ opt_r = opt_reactor
+
+
+
+
+class ServerOptions(usage.Options, ReactorSelectionMixin):
+
+ longdesc = ("twistd reads a twisted.application.service.Application out "
+ "of a file and runs it.")
+
+ optFlags = [['savestats', None,
+ "save the Stats object rather than the text output of "
+ "the profiler."],
+ ['no_save','o', "do not save state on shutdown"],
+ ['encrypted', 'e',
+ "The specified tap/aos file is encrypted."],
+ ['nothotshot', None,
+ "DEPRECATED. Don't use the hotshot profiler even if "
+ "it's available."]]
+
+ optParameters = [['logfile','l', None,
+ "log to a specified file, - for stdout"],
+ ['profile', 'p', None,
+ "Run in profile mode, dumping results to specified file"],
+ ['profiler', None, "hotshot",
+ "Name of the profiler to use (%s)." %
+ ", ".join(AppProfiler.profilers)],
+ ['file','f','twistd.tap',
+ "read the given .tap file"],
+ ['python','y', None,
+ "read an application from within a Python file "
+ "(implies -o)"],
+ ['source', 's', None,
+ "Read an application from a .tas file (AOT format)."],
+ ['rundir','d','.',
+ 'Change to a supplied directory before running'],
+ ['report-profile', None, None,
+ 'E-mail address to use when reporting dynamic execution '
+ 'profiler stats. This should not be combined with '
+ 'other profiling options. This will only take effect '
+ 'if the application to be run has an application '
+ 'name.']]
+
+ #zsh_altArgDescr = {"foo":"use this description for foo instead"}
+ #zsh_multiUse = ["foo", "bar"]
+ zsh_mutuallyExclusive = [("file", "python", "source")]
+ zsh_actions = {"file":'_files -g "*.tap"',
+ "python":'_files -g "*.(tac|py)"',
+ "source":'_files -g "*.tas"',
+ "rundir":"_dirs"}
+ #zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
+
+ def __init__(self, *a, **kw):
+ self['debug'] = False
+ usage.Options.__init__(self, *a, **kw)
+
+ def opt_debug(self):
+ """
+ run the application in the Python Debugger (implies nodaemon),
+ sending SIGUSR2 will drop into debugger
+ """
+ defer.setDebugging(True)
+ failure.startDebugMode()
+ self['debug'] = True
+ opt_b = opt_debug
+
+
+ def opt_spew(self):
+ """Print an insanely verbose log of everything that happens.
+ Useful when debugging freezes or locks in complex code."""
+ sys.settrace(util.spewer)
+ try:
+ import threading
+ except ImportError:
+ return
+ threading.settrace(util.spewer)
+
+
+ def opt_report_profile(self, value):
+ """
+ DEPRECATED.
+
+ Manage --report-profile option, which does nothing currently.
+ """
+ warnings.warn("--report-profile option is deprecated and a no-op "
+ "since Twisted 8.0.", category=DeprecationWarning)
+
+
+ def parseOptions(self, options=None):
+ if options is None:
+ options = sys.argv[1:] or ["--help"]
+ usage.Options.parseOptions(self, options)
+
+ def postOptions(self):
+ if self.subCommand or self['python']:
+ self['no_save'] = True
+
+ def subCommands(self):
+ from twisted import plugin
+ plugins = plugin.getPlugins(service.IServiceMaker)
+ self.loadedPlugins = {}
+ for plug in plugins:
+ self.loadedPlugins[plug.tapname] = plug
+ yield (plug.tapname, None, lambda: plug.options(), plug.description)
+ subCommands = property(subCommands)
+
+
+
+def run(runApp, ServerOptions):
+ config = ServerOptions()
+ try:
+ config.parseOptions()
+ except usage.error, ue:
+ print config
+ print "%s: %s" % (sys.argv[0], ue)
+ else:
+ runApp(config)
+
+
+
+def initialLog():
+ AppLogger({})._initialLog()
+initialLog = deprecated(Version("Twisted", 8, 2, 0))(initialLog)
+
+
+
+def convertStyle(filein, typein, passphrase, fileout, typeout, encrypt):
+ application = service.loadApplication(filein, typein, passphrase)
+ sob.IPersistable(application).setStyle(typeout)
+ passphrase = getSavePassphrase(encrypt)
+ if passphrase:
+ fileout = None
+ sob.IPersistable(application).save(filename=fileout, passphrase=passphrase)
+
+def startApplication(application, save):
+ from twisted.internet import reactor
+ service.IService(application).startService()
+ if save:
+ p = sob.IPersistable(application)
+ reactor.addSystemEventTrigger('after', 'shutdown', p.save, 'shutdown')
+ reactor.addSystemEventTrigger('before', 'shutdown',
+ service.IService(application).stopService)
+
+def getLogFile(logfilename):
+ """
+ Build a log file from the full path.
+ """
+ warnings.warn(
+ "app.getLogFile is deprecated. Use "
+ "twisted.python.logfile.LogFile.fromFullPath instead",
+ DeprecationWarning, stacklevel=2)
+
+ return logfile.LogFile.fromFullPath(logfilename)
+
diff --git a/vendor/Twisted-10.0.0/twisted/application/internet.py b/vendor/Twisted-10.0.0/twisted/application/internet.py
new file mode 100644
index 0000000000..375cb16ecf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/application/internet.py
@@ -0,0 +1,270 @@
+# -*- test-case-name: twisted.test.test_application,twisted.test.test_cooperator -*-
+
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Reactor-based Services
+
+Here are services to run clients, servers and periodic services using
+the reactor.
+
+This module (dynamically) defines various Service subclasses that let
+you represent clients and servers in a Service hierarchy.
+
+They are as follows::
+
+ TCPServer, TCPClient,
+ UNIXServer, UNIXClient,
+ SSLServer, SSLClient,
+ UDPServer, UDPClient,
+ UNIXDatagramServer, UNIXDatagramClient,
+ MulticastServer
+
+These classes take arbitrary arguments in their constructors and pass
+them straight on to their respective reactor.listenXXX or
+reactor.connectXXX calls.
+
+For example, the following service starts a web server on port 8080:
+C{TCPServer(8080, server.Site(r))}. See the documentation for the
+reactor.listen/connect* methods for more information.
+
+Maintainer: Moshe Zadka
+"""
+
+from twisted.python import log
+from twisted.application import service
+from twisted.internet import task
+
+
+class _VolatileDataService(service.Service):
+
+ volatile = []
+
+ def __getstate__(self):
+ d = service.Service.__getstate__(self)
+ for attr in self.volatile:
+ if attr in d:
+ del d[attr]
+ return d
+
+
+
+class _AbstractServer(_VolatileDataService):
+ """
+ @cvar volatile: list of attribute to remove from pickling.
+ @type volatile: C{list}
+
+ @ivar method: the type of method to call on the reactor, one of B{TCP},
+ B{UDP}, B{SSL} or B{UNIX}.
+ @type method: C{str}
+
+ @ivar reactor: the current running reactor.
+ @type reactor: a provider of C{IReactorTCP}, C{IReactorUDP},
+ C{IReactorSSL} or C{IReactorUnix}.
+
+ @ivar _port: instance of port set when the service is started.
+ @type _port: a provider of C{IListeningPort}.
+ """
+
+ volatile = ['_port']
+ method = None
+ reactor = None
+
+ _port = None
+
+ def __init__(self, *args, **kwargs):
+ self.args = args
+ if 'reactor' in kwargs:
+ self.reactor = kwargs.pop("reactor")
+ self.kwargs = kwargs
+
+
+ def privilegedStartService(self):
+ service.Service.privilegedStartService(self)
+ self._port = self._getPort()
+
+
+ def startService(self):
+ service.Service.startService(self)
+ if self._port is None:
+ self._port = self._getPort()
+
+
+ def stopService(self):
+ service.Service.stopService(self)
+ # TODO: if startup failed, should shutdown skip stopListening?
+ # _port won't exist
+ if self._port is not None:
+ d = self._port.stopListening()
+ del self._port
+ return d
+
+
+ def _getPort(self):
+ """
+ Wrapper around the appropriate listen method of the reactor.
+
+ @return: the port object returned by the listen method.
+ @rtype: an object providing L{IListeningPort}.
+ """
+ if self.reactor is None:
+ from twisted.internet import reactor
+ else:
+ reactor = self.reactor
+ return getattr(reactor, 'listen%s' % (self.method,))(
+ *self.args, **self.kwargs)
+
+
+
+class _AbstractClient(_VolatileDataService):
+ """
+ @cvar volatile: list of attribute to remove from pickling.
+ @type volatile: C{list}
+
+ @ivar method: the type of method to call on the reactor, one of B{TCP},
+ B{UDP}, B{SSL} or B{UNIX}.
+ @type method: C{str}
+
+ @ivar reactor: the current running reactor.
+ @type reactor: a provider of C{IReactorTCP}, C{IReactorUDP},
+ C{IReactorSSL} or C{IReactorUnix}.
+
+ @ivar _connection: instance of connection set when the service is started.
+ @type _connection: a provider of C{IConnector}.
+ """
+ volatile = ['_connection']
+ method = None
+ reactor = None
+
+ _connection = None
+
+ def __init__(self, *args, **kwargs):
+ self.args = args
+ if 'reactor' in kwargs:
+ self.reactor = kwargs.pop("reactor")
+ self.kwargs = kwargs
+
+
+ def startService(self):
+ service.Service.startService(self)
+ self._connection = self._getConnection()
+
+
+ def stopService(self):
+ service.Service.stopService(self)
+ if self._connection is not None:
+ self._connection.disconnect()
+ del self._connection
+
+
+ def _getConnection(self):
+ """
+ Wrapper around the appropriate connect method of the reactor.
+
+ @return: the port object returned by the connect method.
+ @rtype: an object providing L{IConnector}.
+ """
+ if self.reactor is None:
+ from twisted.internet import reactor
+ else:
+ reactor = self.reactor
+ return getattr(reactor, 'connect%s' % (self.method,))(
+ *self.args, **self.kwargs)
+
+
+
+_doc={
+'Client':
+"""Connect to %(tran)s
+
+Call reactor.connect%(method)s when the service starts, with the
+arguments given to the constructor.
+""",
+'Server':
+"""Serve %(tran)s clients
+
+Call reactor.listen%(method)s when the service starts, with the
+arguments given to the constructor. When the service stops,
+stop listening. See twisted.internet.interfaces for documentation
+on arguments to the reactor method.
+""",
+}
+
+import new
+for tran in 'Generic TCP UNIX SSL UDP UNIXDatagram Multicast'.split():
+ for side in 'Server Client'.split():
+ if tran == "Multicast" and side == "Client":
+ continue
+ base = globals()['_Abstract'+side]
+ method = {'Generic': 'With'}.get(tran, tran)
+ doc = _doc[side]%vars()
+ klass = new.classobj(tran+side, (base,),
+ {'method': method, '__doc__': doc})
+ globals()[tran+side] = klass
+
+
+class TimerService(_VolatileDataService):
+
+ """Service to periodically call a function
+
+ Every C{step} seconds call the given function with the given arguments.
+ The service starts the calls when it starts, and cancels them
+ when it stops.
+ """
+
+ volatile = ['_loop']
+
+ def __init__(self, step, callable, *args, **kwargs):
+ self.step = step
+ self.call = (callable, args, kwargs)
+
+ def startService(self):
+ service.Service.startService(self)
+ callable, args, kwargs = self.call
+ # we have to make a new LoopingCall each time we're started, because
+ # an active LoopingCall remains active when serialized. If
+ # LoopingCall were a _VolatileDataService, we wouldn't need to do
+ # this.
+ self._loop = task.LoopingCall(callable, *args, **kwargs)
+ self._loop.start(self.step, now=True).addErrback(self._failed)
+
+ def _failed(self, why):
+ # make a note that the LoopingCall is no longer looping, so we don't
+ # try to shut it down a second time in stopService. I think this
+ # should be in LoopingCall. -warner
+ self._loop.running = False
+ log.err(why)
+
+ def stopService(self):
+ if self._loop.running:
+ self._loop.stop()
+ return service.Service.stopService(self)
+
+
+
+class CooperatorService(service.Service):
+ """
+ Simple L{service.IService} which starts and stops a L{twisted.internet.task.Cooperator}.
+ """
+ def __init__(self):
+ self.coop = task.Cooperator(started=False)
+
+
+ def coiterate(self, iterator):
+ return self.coop.coiterate(iterator)
+
+
+ def startService(self):
+ self.coop.start()
+
+
+ def stopService(self):
+ self.coop.stop()
+
+
+
+__all__ = (['TimerService', 'CooperatorService'] +
+ [tran+side
+ for tran in 'Generic TCP UNIX SSL UDP UNIXDatagram Multicast'.split()
+ for side in 'Server Client'.split()])
diff --git a/vendor/Twisted-10.0.0/twisted/application/reactors.py b/vendor/Twisted-10.0.0/twisted/application/reactors.py
new file mode 100644
index 0000000000..c15c948202
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/application/reactors.py
@@ -0,0 +1,83 @@
+# -*- test-case-name: twisted.test.test_application -*-
+# Copyright (c) 2006-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Plugin-based system for enumerating available reactors and installing one of
+them.
+"""
+
+from zope.interface import Interface, Attribute, implements
+
+from twisted.plugin import IPlugin, getPlugins
+from twisted.python.reflect import namedAny
+
+
+class IReactorInstaller(Interface):
+ """
+ Definition of a reactor which can probably be installed.
+ """
+ shortName = Attribute("""
+ A brief string giving the user-facing name of this reactor.
+ """)
+
+ description = Attribute("""
+ A longer string giving a user-facing description of this reactor.
+ """)
+
+ def install():
+ """
+ Install this reactor.
+ """
+
+ # TODO - A method which provides a best-guess as to whether this reactor
+ # can actually be used in the execution environment.
+
+
+
+class NoSuchReactor(KeyError):
+ """
+ Raised when an attempt is made to install a reactor which cannot be found.
+ """
+
+
+class Reactor(object):
+ """
+ @ivar moduleName: The fully-qualified Python name of the module of which
+ the install callable is an attribute.
+ """
+ implements(IPlugin, IReactorInstaller)
+
+
+ def __init__(self, shortName, moduleName, description):
+ self.shortName = shortName
+ self.moduleName = moduleName
+ self.description = description
+
+
+ def install(self):
+ namedAny(self.moduleName).install()
+
+
+
+def getReactorTypes():
+ """
+ Return an iterator of L{IReactorInstaller} plugins.
+ """
+ return getPlugins(IReactorInstaller)
+
+
+
+def installReactor(shortName):
+ """
+ Install the reactor with the given C{shortName} attribute.
+
+ @raise NoSuchReactor: If no reactor is found with a matching C{shortName}.
+
+ @raise: anything that the specified reactor can raise when installed.
+ """
+ for installer in getReactorTypes():
+ if installer.shortName == shortName:
+ return installer.install()
+ raise NoSuchReactor(shortName)
+
diff --git a/vendor/Twisted-10.0.0/twisted/application/service.py b/vendor/Twisted-10.0.0/twisted/application/service.py
new file mode 100644
index 0000000000..16cd938ffb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/application/service.py
@@ -0,0 +1,398 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Service architecture for Twisted.
+
+Services are arranged in a hierarchy. At the leafs of the hierarchy,
+the services which actually interact with the outside world are started.
+Services can be named or anonymous -- usually, they will be named if
+there is need to access them through the hierarchy (from a parent or
+a sibling).
+
+Maintainer: Moshe Zadka
+"""
+
+from zope.interface import implements, Interface, Attribute
+
+from twisted.python.reflect import namedAny
+from twisted.python import components
+from twisted.internet import defer
+from twisted.persisted import sob
+from twisted.plugin import IPlugin
+
+class IServiceMaker(Interface):
+ """
+ An object which can be used to construct services in a flexible
+ way.
+
+ This interface should most often be implemented along with
+ L{twisted.plugin.IPlugin}, and will most often be used by the
+ 'twistd' command.
+ """
+ tapname = Attribute(
+ "A short string naming this Twisted plugin, for example 'web' or "
+ "'pencil'. This name will be used as the subcommand of 'twistd'.")
+
+ description = Attribute(
+ "A brief summary of the features provided by this "
+ "Twisted application plugin.")
+
+ options = Attribute(
+ "A C{twisted.python.usage.Options} subclass defining the"
+ "configuration options for this application.")
+
+
+ def makeService(options):
+ """
+ Create and return an object providing
+ L{twisted.application.service.IService}.
+
+ @param options: A mapping (typically a C{dict} or
+ C{twisted.python.usage.Options} instance) of configuration
+ options to desired configuration values.
+ """
+
+
+
+class ServiceMaker(object):
+ """
+ Utility class to simplify the definition of L{IServiceMaker} plugins.
+ """
+ implements(IPlugin, IServiceMaker)
+
+ def __init__(self, name, module, description, tapname):
+ self.name = name
+ self.module = module
+ self.description = description
+ self.tapname = tapname
+
+
+ def options():
+ def get(self):
+ return namedAny(self.module).Options
+ return get,
+ options = property(*options())
+
+
+ def makeService():
+ def get(self):
+ return namedAny(self.module).makeService
+ return get,
+ makeService = property(*makeService())
+
+
+
+class IService(Interface):
+ """
+ A service.
+
+ Run start-up and shut-down code at the appropriate times.
+
+ @type name: C{string}
+ @ivar name: The name of the service (or None)
+ @type running: C{boolean}
+ @ivar running: Whether the service is running.
+ """
+
+ def setName(name):
+ """
+ Set the name of the service.
+
+ @type name: C{str}
+ @raise RuntimeError: Raised if the service already has a parent.
+ """
+
+ def setServiceParent(parent):
+ """
+ Set the parent of the service.
+
+ @type parent: L{IServiceCollection}
+ @raise RuntimeError: Raised if the service already has a parent
+ or if the service has a name and the parent already has a child
+ by that name.
+ """
+
+ def disownServiceParent():
+ """
+ Use this API to remove an L{IService} from an L{IServiceCollection}.
+
+ This method is used symmetrically with L{setServiceParent} in that it
+ sets the C{parent} attribute on the child.
+
+ @rtype: L{Deferred}
+ @return: a L{Deferred} which is triggered when the service has
+ finished shutting down. If shutting down is immediate,
+ a value can be returned (usually, C{None}).
+ """
+
+ def startService():
+ """
+ Start the service.
+ """
+
+ def stopService():
+ """
+ Stop the service.
+
+ @rtype: L{Deferred}
+ @return: a L{Deferred} which is triggered when the service has
+ finished shutting down. If shutting down is immediate, a
+ value can be returned (usually, C{None}).
+ """
+
+ def privilegedStartService():
+ """
+ Do preparation work for starting the service.
+
+ Here things which should be done before changing directory,
+ root or shedding privileges are done.
+ """
+
+
+class Service:
+ """
+ Base class for services.
+
+ Most services should inherit from this class. It handles the
+ book-keeping reponsibilities of starting and stopping, as well
+ as not serializing this book-keeping information.
+ """
+
+ implements(IService)
+
+ running = 0
+ name = None
+ parent = None
+
+ def __getstate__(self):
+ dict = self.__dict__.copy()
+ if dict.has_key("running"):
+ del dict['running']
+ return dict
+
+ def setName(self, name):
+ if self.parent is not None:
+ raise RuntimeError("cannot change name when parent exists")
+ self.name = name
+
+ def setServiceParent(self, parent):
+ if self.parent is not None:
+ self.disownServiceParent()
+ parent = IServiceCollection(parent, parent)
+ self.parent = parent
+ self.parent.addService(self)
+
+ def disownServiceParent(self):
+ d = self.parent.removeService(self)
+ self.parent = None
+ return d
+
+ def privilegedStartService(self):
+ pass
+
+ def startService(self):
+ self.running = 1
+
+ def stopService(self):
+ self.running = 0
+
+
+
+class IServiceCollection(Interface):
+ """
+ Collection of services.
+
+ Contain several services, and manage their start-up/shut-down.
+ Services can be accessed by name if they have a name, and it
+ is always possible to iterate over them.
+ """
+
+ def getServiceNamed(name):
+ """
+ Get the child service with a given name.
+
+ @type name: C{str}
+ @rtype: L{IService}
+ @raise KeyError: Raised if the service has no child with the
+ given name.
+ """
+
+ def __iter__():
+ """
+ Get an iterator over all child services.
+ """
+
+ def addService(service):
+ """
+ Add a child service.
+
+ @type service: L{IService}
+ @raise RuntimeError: Raised if the service has a child with
+ the given name.
+ """
+
+ def removeService(service):
+ """
+ Remove a child service.
+
+ Only implementations of L{IService.disownServiceParent} should
+ use this method.
+
+ @type service: L{IService}
+ @raise ValueError: Raised if the given service is not a child.
+ @rtype: L{Deferred}
+ @return: a L{Deferred} which is triggered when the service has
+ finished shutting down. If shutting down is immediate, a
+ value can be returned (usually, C{None}).
+ """
+
+
+
+class MultiService(Service):
+ """
+ Straightforward Service Container.
+
+ Hold a collection of services, and manage them in a simplistic
+ way. No service will wait for another, but this object itself
+ will not finish shutting down until all of its child services
+ will finish.
+ """
+
+ implements(IServiceCollection)
+
+ def __init__(self):
+ self.services = []
+ self.namedServices = {}
+ self.parent = None
+
+ def privilegedStartService(self):
+ Service.privilegedStartService(self)
+ for service in self:
+ service.privilegedStartService()
+
+ def startService(self):
+ Service.startService(self)
+ for service in self:
+ service.startService()
+
+ def stopService(self):
+ Service.stopService(self)
+ l = []
+ services = list(self)
+ services.reverse()
+ for service in services:
+ l.append(defer.maybeDeferred(service.stopService))
+ return defer.DeferredList(l)
+
+ def getServiceNamed(self, name):
+ return self.namedServices[name]
+
+ def __iter__(self):
+ return iter(self.services)
+
+ def addService(self, service):
+ if service.name is not None:
+ if self.namedServices.has_key(service.name):
+ raise RuntimeError("cannot have two services with same name"
+ " '%s'" % service.name)
+ self.namedServices[service.name] = service
+ self.services.append(service)
+ if self.running:
+ # It may be too late for that, but we will do our best
+ service.privilegedStartService()
+ service.startService()
+
+ def removeService(self, service):
+ if service.name:
+ del self.namedServices[service.name]
+ self.services.remove(service)
+ if self.running:
+ # Returning this so as not to lose information from the
+ # MultiService.stopService deferred.
+ return service.stopService()
+ else:
+ return None
+
+
+
+class IProcess(Interface):
+ """
+ Process running parameters.
+
+ Represents parameters for how processes should be run.
+
+ @ivar processName: the name the process should have in ps (or C{None})
+ @type processName: C{str}
+ @ivar uid: the user-id the process should run under.
+ @type uid: C{int}
+ @ivar gid: the group-id the process should run under.
+ @type gid: C{int}
+ """
+
+
+class Process:
+ """
+ Process running parameters.
+
+ Sets up uid/gid in the constructor, and has a default
+ of C{None} as C{processName}.
+ """
+ implements(IProcess)
+ processName = None
+
+ def __init__(self, uid=None, gid=None):
+ """
+ Set uid and gid.
+
+ @param uid: The user ID as whom to execute the process. If
+ this is C{None}, no attempt will be made to change the UID.
+
+ @param gid: The group ID as whom to execute the process. If
+ this is C{None}, no attempt will be made to change the GID.
+ """
+ self.uid = uid
+ self.gid = gid
+
+
+def Application(name, uid=None, gid=None):
+ """
+ Return a compound class.
+
+ Return an object supporting the L{IService}, L{IServiceCollection},
+ L{IProcess} and L{sob.IPersistable} interfaces, with the given
+ parameters. Always access the return value by explicit casting to
+ one of the interfaces.
+ """
+ ret = components.Componentized()
+ for comp in (MultiService(), sob.Persistent(ret, name), Process(uid, gid)):
+ ret.addComponent(comp, ignoreClass=1)
+ IService(ret).setName(name)
+ return ret
+
+
+
+def loadApplication(filename, kind, passphrase=None):
+ """
+ Load Application from a given file.
+
+ The serialization format it was saved in should be given as
+ C{kind}, and is one of C{pickle}, C{source}, C{xml} or C{python}. If
+ C{passphrase} is given, the application was encrypted with the
+ given passphrase.
+
+ @type filename: C{str}
+ @type kind: C{str}
+ @type passphrase: C{str}
+ """
+ if kind == 'python':
+ application = sob.loadValueFromFile(filename, 'application', passphrase)
+ else:
+ application = sob.load(filename, kind, passphrase)
+ return application
+
+
+__all__ = ['IServiceMaker', 'IService', 'Service',
+ 'IServiceCollection', 'MultiService',
+ 'IProcess', 'Process', 'Application', 'loadApplication']
diff --git a/vendor/Twisted-10.0.0/twisted/application/strports.py b/vendor/Twisted-10.0.0/twisted/application/strports.py
new file mode 100644
index 0000000000..3654413488
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/application/strports.py
@@ -0,0 +1,200 @@
+# -*- test-case-name: twisted.test.test_strports -*-
+
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Port description language
+
+This module implements a description mini-language for ports, and provides
+functions to parse it and to use it to directly construct appropriate
+network server services or to directly listen on them.
+
+Here are some examples. They assume the following toy resource and factory
+definitions::
+ class Simple(resource.Resource):
+ isLeaf = True
+ def render_GET(self, request):
+ return "<html>Hello, world!</html>"
+
+ class FingerProtocol(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.loseConnection()
+
+ class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+Examples using SSL require a private key and a certificate. If a private key
+file name (C{privateKey}) isn't provided, a "server.pem" file is assumed to
+exist which contains the private key. If the certificate file name (C{certKey})
+isn't provided, the private key file is assumed to contain the certificate as
+well::
+ >>> s=service("80", server.Site(Simple()))
+ >>> s=service("tcp:80", server.Site(Simple()))
+ >>> s=service("tcp:80:interface=127.0.0.1", server.Site(Simple()))
+ >>> s=service("ssl:443", server.Site(Simple()))
+ >>> s=service("ssl:443:privateKey=mykey.pem", server.Site(Simple()))
+ >>> s=service("ssl:443:privateKey=mykey.pem:certKey=cert.pem", server.Site(Simple()))
+ >>> s=service("unix:/var/run/finger", FingerFactory())
+ >>> s=service("unix:/var/run/finger:mode=660", FingerFactory())
+ >>> p=listen("80", server.Site(Simple()))
+ >>> p=listen("tcp:80", server.Site(Simple()))
+ >>> p=listen("tcp:80:interface=127.0.0.1", server.Site(Simple()))
+ >>> p=listen("ssl:443", server.Site(Simple()))
+ >>> p=listen("ssl:443:privateKey=mykey.pem", server.Site(Simple()))
+ >>> p=listen("ssl:443:privateKey=mykey.pem:certKey=cert.pem", server.Site(Simple()))
+ >>> p=listen("unix:/var/run/finger", FingerFactory())
+ >>> p=listen("unix:/var/run/finger:mode=660", FingerFactory())
+ >>> p=listen("unix:/var/run/finger:lockfile=0", FingerFactory())
+
+See specific function documentation for more information.
+
+Maintainer: Moshe Zadka
+"""
+from __future__ import generators
+
+def _parseTCP(factory, port, interface="", backlog=50):
+ return (int(port), factory), {'interface': interface,
+ 'backlog': int(backlog)}
+
+
+
+def _parseUNIX(factory, address, mode='666', backlog=50, lockfile=True):
+ return (
+ (address, factory),
+ {'mode': int(mode, 8), 'backlog': int(backlog),
+ 'wantPID': bool(int(lockfile))})
+
+
+
+def _parseSSL(factory, port, privateKey="server.pem", certKey=None,
+ sslmethod=None, interface='', backlog=50):
+ from twisted.internet import ssl
+ if certKey is None:
+ certKey = privateKey
+ kw = {}
+ if sslmethod is not None:
+ kw['sslmethod'] = getattr(ssl.SSL, sslmethod)
+ cf = ssl.DefaultOpenSSLContextFactory(privateKey, certKey, **kw)
+ return ((int(port), factory, cf),
+ {'interface': interface, 'backlog': int(backlog)})
+
+_funcs = {"tcp": _parseTCP,
+ "unix": _parseUNIX,
+ "ssl": _parseSSL}
+
+_OP, _STRING = range(2)
+def _tokenize(description):
+ current = ''
+ ops = ':='
+ nextOps = {':': ':=', '=': ':'}
+ description = iter(description)
+ for n in description:
+ if n in ops:
+ yield _STRING, current
+ yield _OP, n
+ current = ''
+ ops = nextOps[n]
+ elif n=='\\':
+ current += description.next()
+ else:
+ current += n
+ yield _STRING, current
+
+def _parse(description):
+ args, kw = [], {}
+ def add(sofar):
+ if len(sofar)==1:
+ args.append(sofar[0])
+ else:
+ kw[sofar[0]] = sofar[1]
+ sofar = ()
+ for (type, value) in _tokenize(description):
+ if type is _STRING:
+ sofar += (value,)
+ elif value==':':
+ add(sofar)
+ sofar = ()
+ add(sofar)
+ return args, kw
+
+def parse(description, factory, default=None):
+ """
+ Parse the description of a reliable virtual circuit server (that is, a
+ TCP port, a UNIX domain socket or an SSL port) and return the data
+ necessary to call the reactor methods to listen on the given socket with
+ the given factory.
+
+ An argument with no colons means a default port. Usually the default
+ type is C{tcp}, but passing a non-C{None} value as C{default} will set
+ that as the default. Otherwise, it is a colon-separated string. The
+ first part means the type -- currently, it can only be ssl, unix or tcp.
+ After that, comes a list of arguments. Arguments can be positional or
+ keyword, and can be mixed. Keyword arguments are indicated by
+ C{'name=value'}. If a value is supposed to contain a C{':'}, a C{'='} or
+ a C{'\\'}, escape it with a C{'\\'}.
+
+ For TCP, the arguments are the port (port number) and, optionally the
+ interface (interface on which to listen) and backlog (how many clients
+ to keep in the backlog).
+
+ For UNIX domain sockets, the arguments are address (the file name of the
+ socket) and optionally the mode (the mode bits of the file, as an octal
+ number) and the backlog (how many clients to keep in the backlog).
+
+ For SSL sockets, the arguments are the port (port number) and,
+ optionally, the privateKey (file in which the private key is in),
+ certKey (file in which the certification is in), sslmethod (the name of
+ the SSL method to allow), the interface (interface on which to listen)
+ and the backlog (how many clients to keep in the backlog).
+
+ @type description: C{str}
+ @type factory: L{twisted.internet.interfaces.IProtocolFactory}
+ @type default: C{str} or C{None}
+ @rtype: C{tuple}
+ @return: a tuple of string, tuple and dictionary. The string is the name
+ of the method (sans C{'listen'}) to call, and the tuple and dictionary
+ are the arguments and keyword arguments to the method.
+ @raises ValueError: if the string is formatted incorrectly.
+ @raises KeyError: if the type is other than unix, ssl or tcp.
+ """
+ args, kw = _parse(description)
+ if not args or (len(args)==1 and not kw):
+ args[0:0] = [default or 'tcp']
+ return (args[0].upper(),)+_funcs[args[0]](factory, *args[1:], **kw)
+
+def service(description, factory, default=None):
+ """Return the service corresponding to a description
+
+ @type description: C{str}
+ @type factory: L{twisted.internet.interfaces.IProtocolFactory}
+ @type default: C{str} or C{None}
+ @rtype: C{twisted.application.service.IService}
+ @return: the service corresponding to a description of a reliable
+ virtual circuit server.
+
+ See the documentation of the C{parse} function for description
+ of the semantics of the arguments.
+ """
+ from twisted.application import internet
+ name, args, kw = parse(description, factory, default)
+ return getattr(internet, name+'Server')(*args, **kw)
+
+def listen(description, factory, default=None):
+ """Listen on a port corresponding to a description
+
+ @type description: C{str}
+ @type factory: L{twisted.internet.interfaces.IProtocolFactory}
+ @type default: C{str} or C{None}
+ @rtype: C{twisted.internet.interfaces.IListeningPort}
+ @return: the port corresponding to a description of a reliable
+ virtual circuit server.
+
+ See the documentation of the C{parse} function for description
+ of the semantics of the arguments.
+ """
+ from twisted.internet import reactor
+ name, args, kw = parse(description, factory, default)
+ return getattr(reactor, 'listen'+name)(*args, **kw)
+
+__all__ = ['parse', 'service', 'listen']
diff --git a/vendor/Twisted-10.0.0/twisted/conch/__init__.py b/vendor/Twisted-10.0.0/twisted/conch/__init__.py
new file mode 100644
index 0000000000..9ff0c11513
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/__init__.py
@@ -0,0 +1,18 @@
+# -*- test-case-name: twisted.conch.test -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+
+"""
+Twisted.Conch: The Twisted Shell. Terminal emulation, SSHv2 and telnet.
+
+Currently this contains the SSHv2 implementation, but it may work over other
+protocols in the future. (i.e. Telnet)
+
+Maintainer: Paul Swartz
+"""
+
+from twisted.conch._version import version
+__version__ = version.short()
diff --git a/vendor/Twisted-10.0.0/twisted/conch/_version.py b/vendor/Twisted-10.0.0/twisted/conch/_version.py
new file mode 100644
index 0000000000..234b3d34ac
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/_version.py
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twisted.conch', 10, 0, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/avatar.py b/vendor/Twisted-10.0.0/twisted/conch/avatar.py
new file mode 100644
index 0000000000..a914da3e06
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/avatar.py
@@ -0,0 +1,37 @@
+# -*- test-case-name: twisted.conch.test.test_conch -*-
+from interfaces import IConchUser
+from error import ConchError
+from ssh.connection import OPEN_UNKNOWN_CHANNEL_TYPE
+from twisted.python import log
+from zope import interface
+
+class ConchUser:
+ interface.implements(IConchUser)
+
+ def __init__(self):
+ self.channelLookup = {}
+ self.subsystemLookup = {}
+
+ def lookupChannel(self, channelType, windowSize, maxPacket, data):
+ klass = self.channelLookup.get(channelType, None)
+ if not klass:
+ raise ConchError(OPEN_UNKNOWN_CHANNEL_TYPE, "unknown channel")
+ else:
+ return klass(remoteWindow = windowSize,
+ remoteMaxPacket = maxPacket,
+ data=data, avatar=self)
+
+ def lookupSubsystem(self, subsystem, data):
+ log.msg(repr(self.subsystemLookup))
+ klass = self.subsystemLookup.get(subsystem, None)
+ if not klass:
+ return False
+ return klass(data, avatar=self)
+
+ def gotGlobalRequest(self, requestType, data):
+ # XXX should this use method dispatch?
+ requestType = requestType.replace('-','_')
+ f = getattr(self, "global_%s" % requestType, None)
+ if not f:
+ return 0
+ return f(data)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/checkers.py b/vendor/Twisted-10.0.0/twisted/conch/checkers.py
new file mode 100644
index 0000000000..2fda5f36a6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/checkers.py
@@ -0,0 +1,266 @@
+# -*- test-case-name: twisted.conch.test.test_checkers -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Provide L{ICredentialsChecker} implementations to be used in Conch protocols.
+"""
+
+import os, base64, binascii, errno
+try:
+ import pwd
+except ImportError:
+ pwd = None
+else:
+ import crypt
+
+try:
+ # get this from http://www.twistedmatrix.com/users/z3p/files/pyshadow-0.2.tar.gz
+ import shadow
+except:
+ shadow = None
+
+try:
+ from twisted.cred import pamauth
+except ImportError:
+ pamauth = None
+
+from zope.interface import implements, providedBy
+
+from twisted.conch import error
+from twisted.conch.ssh import keys
+from twisted.cred.checkers import ICredentialsChecker
+from twisted.cred.credentials import IUsernamePassword, ISSHPrivateKey
+from twisted.cred.error import UnauthorizedLogin, UnhandledCredentials
+from twisted.internet import defer
+from twisted.python import failure, reflect, log
+from twisted.python.util import runAsEffectiveUser
+from twisted.python.filepath import FilePath
+
+
+def verifyCryptedPassword(crypted, pw):
+ if crypted[0] == '$': # md5_crypt encrypted
+ salt = '$1$' + crypted.split('$')[2]
+ else:
+ salt = crypted[:2]
+ return crypt.crypt(pw, salt) == crypted
+
+class UNIXPasswordDatabase:
+ credentialInterfaces = IUsernamePassword,
+ implements(ICredentialsChecker)
+
+ def requestAvatarId(self, credentials):
+ if pwd:
+ try:
+ cryptedPass = pwd.getpwnam(credentials.username)[1]
+ except KeyError:
+ return defer.fail(UnauthorizedLogin("invalid username"))
+ else:
+ if cryptedPass not in ['*', 'x'] and \
+ verifyCryptedPassword(cryptedPass, credentials.password):
+ return defer.succeed(credentials.username)
+ if shadow:
+ gid = os.getegid()
+ uid = os.geteuid()
+ os.setegid(0)
+ os.seteuid(0)
+ try:
+ shadowPass = shadow.getspnam(credentials.username)[1]
+ except KeyError:
+ os.setegid(gid)
+ os.seteuid(uid)
+ return defer.fail(UnauthorizedLogin("invalid username"))
+ os.setegid(gid)
+ os.seteuid(uid)
+ if verifyCryptedPassword(shadowPass, credentials.password):
+ return defer.succeed(credentials.username)
+ return defer.fail(UnauthorizedLogin("invalid password"))
+
+ return defer.fail(UnauthorizedLogin("unable to verify password"))
+
+
+class SSHPublicKeyDatabase:
+ """
+ Checker that authenticates SSH public keys, based on public keys listed in
+ authorized_keys and authorized_keys2 files in user .ssh/ directories.
+ """
+
+ credentialInterfaces = ISSHPrivateKey,
+ implements(ICredentialsChecker)
+
+ def requestAvatarId(self, credentials):
+ d = defer.maybeDeferred(self.checkKey, credentials)
+ d.addCallback(self._cbRequestAvatarId, credentials)
+ d.addErrback(self._ebRequestAvatarId)
+ return d
+
+ def _cbRequestAvatarId(self, validKey, credentials):
+ """
+ Check whether the credentials themselves are valid, now that we know
+ if the key matches the user.
+
+ @param validKey: A boolean indicating whether or not the public key
+ matches a key in the user's authorized_keys file.
+
+ @param credentials: The credentials offered by the user.
+ @type credentials: L{ISSHPrivateKey} provider
+
+ @raise UnauthorizedLogin: (as a failure) if the key does not match the
+ user in C{credentials}. Also raised if the user provides an invalid
+ signature.
+
+ @raise ValidPublicKey: (as a failure) if the key matches the user but
+ the credentials do not include a signature. See
+ L{error.ValidPublicKey} for more information.
+
+ @return: The user's username, if authentication was successful.
+ """
+ if not validKey:
+ return failure.Failure(UnauthorizedLogin("invalid key"))
+ if not credentials.signature:
+ return failure.Failure(error.ValidPublicKey())
+ else:
+ try:
+ pubKey = keys.Key.fromString(credentials.blob)
+ if pubKey.verify(credentials.signature, credentials.sigData):
+ return credentials.username
+ except: # any error should be treated as a failed login
+ log.err()
+ return failure.Failure(UnauthorizedLogin('error while verifying key'))
+ return failure.Failure(UnauthorizedLogin("unable to verify key"))
+
+
+ def getAuthorizedKeysFiles(self, credentials):
+ """
+ Return a list of L{FilePath} instances for I{authorized_keys} files
+ which might contain information about authorized keys for the given
+ credentials.
+
+ On OpenSSH servers, the default location of the file containing the
+ list of authorized public keys is
+ U{$HOME/.ssh/authorized_keys<http://www.openbsd.org/cgi-bin/man.cgi?query=sshd_config>}.
+
+ I{$HOME/.ssh/authorized_keys2} is also returned, though it has been
+ U{deprecated by OpenSSH since
+ 2001<http://marc.info/?m=100508718416162>}.
+
+ @return: A list of L{FilePath} instances to files with the authorized keys.
+ """
+ pwent = pwd.getpwnam(credentials.username)
+ root = FilePath(pwent.pw_dir).child('.ssh')
+ files = ['authorized_keys', 'authorized_keys2']
+ return [root.child(f) for f in files]
+
+
+ def checkKey(self, credentials):
+ """
+ Retrieve files containing authorized keys and check against user
+ credentials.
+ """
+ uid, gid = os.geteuid(), os.getegid()
+ ouid, ogid = pwd.getpwnam(credentials.username)[2:4]
+ for filepath in self.getAuthorizedKeysFiles(credentials):
+ if not filepath.exists():
+ continue
+ try:
+ lines = filepath.open()
+ except IOError, e:
+ if e.errno == errno.EACCES:
+ lines = runAsEffectiveUser(ouid, ogid, filepath.open)
+ else:
+ raise
+ for l in lines:
+ l2 = l.split()
+ if len(l2) < 2:
+ continue
+ try:
+ if base64.decodestring(l2[1]) == credentials.blob:
+ return True
+ except binascii.Error:
+ continue
+ return False
+
+ def _ebRequestAvatarId(self, f):
+ if not f.check(UnauthorizedLogin):
+ log.msg(f)
+ return failure.Failure(UnauthorizedLogin("unable to get avatar id"))
+ return f
+
+
+class SSHProtocolChecker:
+ """
+ SSHProtocolChecker is a checker that requires multiple authentications
+ to succeed. To add a checker, call my registerChecker method with
+ the checker and the interface.
+
+ After each successful authenticate, I call my areDone method with the
+ avatar id. To get a list of the successful credentials for an avatar id,
+ use C{SSHProcotolChecker.successfulCredentials[avatarId]}. If L{areDone}
+ returns True, the authentication has succeeded.
+ """
+
+ implements(ICredentialsChecker)
+
+ def __init__(self):
+ self.checkers = {}
+ self.successfulCredentials = {}
+
+ def get_credentialInterfaces(self):
+ return self.checkers.keys()
+
+ credentialInterfaces = property(get_credentialInterfaces)
+
+ def registerChecker(self, checker, *credentialInterfaces):
+ if not credentialInterfaces:
+ credentialInterfaces = checker.credentialInterfaces
+ for credentialInterface in credentialInterfaces:
+ self.checkers[credentialInterface] = checker
+
+ def requestAvatarId(self, credentials):
+ """
+ Part of the L{ICredentialsChecker} interface. Called by a portal with
+ some credentials to check if they'll authenticate a user. We check the
+ interfaces that the credentials provide against our list of acceptable
+ checkers. If one of them matches, we ask that checker to verify the
+ credentials. If they're valid, we call our L{_cbGoodAuthentication}
+ method to continue.
+
+ @param credentials: the credentials the L{Portal} wants us to verify
+ """
+ ifac = providedBy(credentials)
+ for i in ifac:
+ c = self.checkers.get(i)
+ if c is not None:
+ d = defer.maybeDeferred(c.requestAvatarId, credentials)
+ return d.addCallback(self._cbGoodAuthentication,
+ credentials)
+ return defer.fail(UnhandledCredentials("No checker for %s" % \
+ ', '.join(map(reflect.qual, ifac))))
+
+ def _cbGoodAuthentication(self, avatarId, credentials):
+ """
+ Called if a checker has verified the credentials. We call our
+ L{areDone} method to see if the whole of the successful authentications
+ are enough. If they are, we return the avatar ID returned by the first
+ checker.
+ """
+ if avatarId not in self.successfulCredentials:
+ self.successfulCredentials[avatarId] = []
+ self.successfulCredentials[avatarId].append(credentials)
+ if self.areDone(avatarId):
+ del self.successfulCredentials[avatarId]
+ return avatarId
+ else:
+ raise error.NotEnoughAuthentication()
+
+ def areDone(self, avatarId):
+ """
+ Override to determine if the authentication is finished for a given
+ avatarId.
+
+ @param avatarId: the avatar returned by the first checker. For
+ this checker to function correctly, all the checkers must
+ return the same avatar ID.
+ """
+ return True
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/client/__init__.py b/vendor/Twisted-10.0.0/twisted/conch/client/__init__.py
new file mode 100644
index 0000000000..1b723d33c6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/client/__init__.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""
+Client support code for Conch.
+
+Maintainer: Paul Swartz
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/conch/client/agent.py b/vendor/Twisted-10.0.0/twisted/conch/client/agent.py
new file mode 100644
index 0000000000..0e8d5425d0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/client/agent.py
@@ -0,0 +1,73 @@
+# -*- test-case-name: twisted.conch.test.test_default -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Accesses the key agent for user authentication.
+
+Maintainer: Paul Swartz
+"""
+
+import os
+
+from twisted.conch.ssh import agent, channel, keys
+from twisted.internet import protocol, reactor
+from twisted.python import log
+
+
+
+class SSHAgentClient(agent.SSHAgentClient):
+
+ def __init__(self):
+ agent.SSHAgentClient.__init__(self)
+ self.blobs = []
+
+
+ def getPublicKeys(self):
+ return self.requestIdentities().addCallback(self._cbPublicKeys)
+
+
+ def _cbPublicKeys(self, blobcomm):
+ log.msg('got %i public keys' % len(blobcomm))
+ self.blobs = [x[0] for x in blobcomm]
+
+
+ def getPublicKey(self):
+ """
+ Return a L{Key} from the first blob in C{self.blobs}, if any, or
+ return C{None}.
+ """
+ if self.blobs:
+ return keys.Key.fromString(self.blobs.pop(0))
+ return None
+
+
+
+class SSHAgentForwardingChannel(channel.SSHChannel):
+
+ def channelOpen(self, specificData):
+ cc = protocol.ClientCreator(reactor, SSHAgentForwardingLocal)
+ d = cc.connectUNIX(os.environ['SSH_AUTH_SOCK'])
+ d.addCallback(self._cbGotLocal)
+ d.addErrback(lambda x:self.loseConnection())
+ self.buf = ''
+
+
+ def _cbGotLocal(self, local):
+ self.local = local
+ self.dataReceived = self.local.transport.write
+ self.local.dataReceived = self.write
+
+
+ def dataReceived(self, data):
+ self.buf += data
+
+
+ def closed(self):
+ if self.local:
+ self.local.loseConnection()
+ self.local = None
+
+
+class SSHAgentForwardingLocal(protocol.Protocol):
+ pass
diff --git a/vendor/Twisted-10.0.0/twisted/conch/client/connect.py b/vendor/Twisted-10.0.0/twisted/conch/client/connect.py
new file mode 100644
index 0000000000..c8b3ddd6b3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/client/connect.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+import direct
+
+connectTypes = {"direct" : direct.connect}
+
+def connect(host, port, options, verifyHostKey, userAuthObject):
+ useConnects = ['direct']
+ return _ebConnect(None, useConnects, host, port, options, verifyHostKey,
+ userAuthObject)
+
+def _ebConnect(f, useConnects, host, port, options, vhk, uao):
+ if not useConnects:
+ return f
+ connectType = useConnects.pop(0)
+ f = connectTypes[connectType]
+ d = f(host, port, options, vhk, uao)
+ d.addErrback(_ebConnect, useConnects, host, port, options, vhk, uao)
+ return d
diff --git a/vendor/Twisted-10.0.0/twisted/conch/client/default.py b/vendor/Twisted-10.0.0/twisted/conch/client/default.py
new file mode 100644
index 0000000000..d21683219f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/client/default.py
@@ -0,0 +1,256 @@
+# -*- test-case-name: twisted.conch.test.test_knownhosts,twisted.conch.test.test_default -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Various classes and functions for implementing user-interaction in the
+command-line conch client.
+
+You probably shouldn't use anything in this module directly, since it assumes
+you are sitting at an interactive terminal. For example, to programmatically
+interact with a known_hosts database, use L{twisted.conch.client.knownhosts}.
+"""
+
+from twisted.python import log
+from twisted.python.filepath import FilePath
+
+from twisted.conch.error import ConchError
+from twisted.conch.ssh import common, keys, userauth
+from twisted.internet import defer, protocol, reactor
+
+from twisted.conch.client.knownhosts import KnownHostsFile, ConsoleUI
+
+from twisted.conch.client import agent
+
+import os, sys, base64, getpass
+
+# This name is bound so that the unit tests can use 'patch' to override it.
+_open = open
+
+def verifyHostKey(transport, host, pubKey, fingerprint):
+ """
+ Verify a host's key.
+
+ This function is a gross vestige of some bad factoring in the client
+ internals. The actual implementation, and a better signature of this logic
+ is in L{KnownHostsFile.verifyHostKey}. This function is not deprecated yet
+ because the callers have not yet been rehabilitated, but they should
+ eventually be changed to call that method instead.
+
+ However, this function does perform two functions not implemented by
+ L{KnownHostsFile.verifyHostKey}. It determines the path to the user's
+ known_hosts file based on the options (which should really be the options
+ object's job), and it provides an opener to L{ConsoleUI} which opens
+ '/dev/tty' so that the user will be prompted on the tty of the process even
+ if the input and output of the process has been redirected. This latter
+ part is, somewhat obviously, not portable, but I don't know of a portable
+ equivalent that could be used.
+
+ @param host: Due to a bug in L{SSHClientTransport.verifyHostKey}, this is
+ always the dotted-quad IP address of the host being connected to.
+ @type host: L{str}
+
+ @param transport: the client transport which is attempting to connect to
+ the given host.
+ @type transport: L{SSHClientTransport}
+
+ @param fingerprint: the fingerprint of the given public key, in
+ xx:xx:xx:... format. This is ignored in favor of getting the fingerprint
+ from the key itself.
+ @type fingerprint: L{str}
+
+ @param pubKey: The public key of the server being connected to.
+ @type pubKey: L{str}
+
+ @return: a L{Deferred} which fires with C{1} if the key was successfully
+ verified, or fails if the key could not be successfully verified. Failure
+ types may include L{HostKeyChanged}, L{UserRejectedKey}, L{IOError} or
+ L{KeyboardInterrupt}.
+ """
+ actualHost = transport.factory.options['host']
+ actualKey = keys.Key.fromString(pubKey)
+ kh = KnownHostsFile.fromPath(FilePath(
+ transport.factory.options['known-hosts']
+ or os.path.expanduser("~/.ssh/known_hosts")
+ ))
+ ui = ConsoleUI(lambda : _open("/dev/tty", "r+b"))
+ return kh.verifyHostKey(ui, actualHost, host, actualKey)
+
+
+
+def isInKnownHosts(host, pubKey, options):
+ """checks to see if host is in the known_hosts file for the user.
+ returns 0 if it isn't, 1 if it is and is the same, 2 if it's changed.
+ """
+ keyType = common.getNS(pubKey)[0]
+ retVal = 0
+
+ if not options['known-hosts'] and not os.path.exists(os.path.expanduser('~/.ssh/')):
+ print 'Creating ~/.ssh directory...'
+ os.mkdir(os.path.expanduser('~/.ssh'))
+ kh_file = options['known-hosts'] or '~/.ssh/known_hosts'
+ try:
+ known_hosts = open(os.path.expanduser(kh_file))
+ except IOError:
+ return 0
+ for line in known_hosts.xreadlines():
+ split = line.split()
+ if len(split) < 3:
+ continue
+ hosts, hostKeyType, encodedKey = split[:3]
+ if host not in hosts.split(','): # incorrect host
+ continue
+ if hostKeyType != keyType: # incorrect type of key
+ continue
+ try:
+ decodedKey = base64.decodestring(encodedKey)
+ except:
+ continue
+ if decodedKey == pubKey:
+ return 1
+ else:
+ retVal = 2
+ return retVal
+
+
+
+class SSHUserAuthClient(userauth.SSHUserAuthClient):
+
+ def __init__(self, user, options, *args):
+ userauth.SSHUserAuthClient.__init__(self, user, *args)
+ self.keyAgent = None
+ self.options = options
+ self.usedFiles = []
+ if not options.identitys:
+ options.identitys = ['~/.ssh/id_rsa', '~/.ssh/id_dsa']
+
+ def serviceStarted(self):
+ if 'SSH_AUTH_SOCK' in os.environ and not self.options['noagent']:
+ log.msg('using agent')
+ cc = protocol.ClientCreator(reactor, agent.SSHAgentClient)
+ d = cc.connectUNIX(os.environ['SSH_AUTH_SOCK'])
+ d.addCallback(self._setAgent)
+ d.addErrback(self._ebSetAgent)
+ else:
+ userauth.SSHUserAuthClient.serviceStarted(self)
+
+ def serviceStopped(self):
+ if self.keyAgent:
+ self.keyAgent.transport.loseConnection()
+ self.keyAgent = None
+
+ def _setAgent(self, a):
+ self.keyAgent = a
+ d = self.keyAgent.getPublicKeys()
+ d.addBoth(self._ebSetAgent)
+ return d
+
+ def _ebSetAgent(self, f):
+ userauth.SSHUserAuthClient.serviceStarted(self)
+
+ def _getPassword(self, prompt):
+ try:
+ oldout, oldin = sys.stdout, sys.stdin
+ sys.stdin = sys.stdout = open('/dev/tty','r+')
+ p=getpass.getpass(prompt)
+ sys.stdout,sys.stdin=oldout,oldin
+ return p
+ except (KeyboardInterrupt, IOError):
+ print
+ raise ConchError('PEBKAC')
+
+ def getPassword(self, prompt = None):
+ if not prompt:
+ prompt = "%s@%s's password: " % (self.user, self.transport.transport.getPeer().host)
+ try:
+ p = self._getPassword(prompt)
+ return defer.succeed(p)
+ except ConchError:
+ return defer.fail()
+
+
+ def getPublicKey(self):
+ """
+ Get a public key from the key agent if possible, otherwise look in
+ the next configured identity file for one.
+ """
+ if self.keyAgent:
+ key = self.keyAgent.getPublicKey()
+ if key is not None:
+ return key
+ files = [x for x in self.options.identitys if x not in self.usedFiles]
+ log.msg(str(self.options.identitys))
+ log.msg(str(files))
+ if not files:
+ return None
+ file = files[0]
+ log.msg(file)
+ self.usedFiles.append(file)
+ file = os.path.expanduser(file)
+ file += '.pub'
+ if not os.path.exists(file):
+ return self.getPublicKey() # try again
+ try:
+ return keys.Key.fromFile(file)
+ except keys.BadKeyError:
+ return self.getPublicKey() # try again
+
+
+ def signData(self, publicKey, signData):
+ """
+ Extend the base signing behavior by using an SSH agent to sign the
+ data, if one is available.
+
+ @type publicKey: L{Key}
+ @type signData: C{str}
+ """
+ if not self.usedFiles: # agent key
+ return self.keyAgent.signData(publicKey.blob(), signData)
+ else:
+ return userauth.SSHUserAuthClient.signData(self, publicKey, signData)
+
+
+ def getPrivateKey(self):
+ """
+ Try to load the private key from the last used file identified by
+ C{getPublicKey}, potentially asking for the passphrase if the key is
+ encrypted.
+ """
+ file = os.path.expanduser(self.usedFiles[-1])
+ if not os.path.exists(file):
+ return None
+ try:
+ return defer.succeed(keys.Key.fromFile(file))
+ except keys.EncryptedKeyError:
+ for i in range(3):
+ prompt = "Enter passphrase for key '%s': " % \
+ self.usedFiles[-1]
+ try:
+ p = self._getPassword(prompt)
+ return defer.succeed(keys.Key.fromFile(file, passphrase=p))
+ except (keys.BadKeyError, ConchError):
+ pass
+ return defer.fail(ConchError('bad password'))
+ raise
+ except KeyboardInterrupt:
+ print
+ reactor.stop()
+
+
+ def getGenericAnswers(self, name, instruction, prompts):
+ responses = []
+ try:
+ oldout, oldin = sys.stdout, sys.stdin
+ sys.stdin = sys.stdout = open('/dev/tty','r+')
+ if name:
+ print name
+ if instruction:
+ print instruction
+ for prompt, echo in prompts:
+ if echo:
+ responses.append(raw_input(prompt))
+ else:
+ responses.append(getpass.getpass(prompt))
+ finally:
+ sys.stdout,sys.stdin=oldout,oldin
+ return defer.succeed(responses)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/client/direct.py b/vendor/Twisted-10.0.0/twisted/conch/client/direct.py
new file mode 100644
index 0000000000..0ff2ecc94d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/client/direct.py
@@ -0,0 +1,107 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import defer, protocol, reactor
+from twisted.conch import error
+from twisted.conch.ssh import transport
+from twisted.python import log
+
+
+
+class SSHClientFactory(protocol.ClientFactory):
+
+ def __init__(self, d, options, verifyHostKey, userAuthObject):
+ self.d = d
+ self.options = options
+ self.verifyHostKey = verifyHostKey
+ self.userAuthObject = userAuthObject
+
+
+ def clientConnectionLost(self, connector, reason):
+ if self.options['reconnect']:
+ connector.connect()
+
+
+ def clientConnectionFailed(self, connector, reason):
+ if self.d is None:
+ return
+ d, self.d = self.d, None
+ d.errback(reason)
+
+
+ def buildProtocol(self, addr):
+ trans = SSHClientTransport(self)
+ if self.options['ciphers']:
+ trans.supportedCiphers = self.options['ciphers']
+ if self.options['macs']:
+ trans.supportedMACs = self.options['macs']
+ if self.options['compress']:
+ trans.supportedCompressions[0:1] = ['zlib']
+ if self.options['host-key-algorithms']:
+ trans.supportedPublicKeys = self.options['host-key-algorithms']
+ return trans
+
+
+
+class SSHClientTransport(transport.SSHClientTransport):
+
+ def __init__(self, factory):
+ self.factory = factory
+ self.unixServer = None
+
+
+ def connectionLost(self, reason):
+ if self.unixServer:
+ d = self.unixServer.stopListening()
+ self.unixServer = None
+ else:
+ d = defer.succeed(None)
+ d.addCallback(lambda x:
+ transport.SSHClientTransport.connectionLost(self, reason))
+
+
+ def receiveError(self, code, desc):
+ if self.factory.d is None:
+ return
+ d, self.factory.d = self.factory.d, None
+ d.errback(error.ConchError(desc, code))
+
+
+ def sendDisconnect(self, code, reason):
+ if self.factory.d is None:
+ return
+ d, self.factory.d = self.factory.d, None
+ transport.SSHClientTransport.sendDisconnect(self, code, reason)
+ d.errback(error.ConchError(reason, code))
+
+
+ def receiveDebug(self, alwaysDisplay, message, lang):
+ log.msg('Received Debug Message: %s' % message)
+ if alwaysDisplay: # XXX what should happen here?
+ print message
+
+
+ def verifyHostKey(self, pubKey, fingerprint):
+ return self.factory.verifyHostKey(self, self.transport.getPeer().host, pubKey,
+ fingerprint)
+
+
+ def setService(self, service):
+ log.msg('setting client server to %s' % service)
+ transport.SSHClientTransport.setService(self, service)
+ if service.name != 'ssh-userauth' and self.factory.d is not None:
+ d, self.factory.d = self.factory.d, None
+ d.callback(None)
+
+
+ def connectionSecure(self):
+ self.requestService(self.factory.userAuthObject)
+
+
+
+def connect(host, port, options, verifyHostKey, userAuthObject):
+ d = defer.Deferred()
+ factory = SSHClientFactory(d, options, verifyHostKey, userAuthObject)
+ reactor.connectTCP(host, port, factory)
+ return d
diff --git a/vendor/Twisted-10.0.0/twisted/conch/client/knownhosts.py b/vendor/Twisted-10.0.0/twisted/conch/client/knownhosts.py
new file mode 100644
index 0000000000..6e932f17c1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/client/knownhosts.py
@@ -0,0 +1,474 @@
+# -*- test-case-name: twisted.conch.test.test_knownhosts -*-
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An implementation of the OpenSSH known_hosts database.
+
+@since: 8.2
+"""
+
+from binascii import Error as DecodeError, b2a_base64
+
+from zope.interface import implements
+
+from Crypto.Hash.HMAC import HMAC
+from Crypto.Hash import SHA
+
+from twisted.python.randbytes import secureRandom
+
+from twisted.internet import defer
+
+from twisted.python import log
+from twisted.conch.interfaces import IKnownHostEntry
+from twisted.conch.error import HostKeyChanged, UserRejectedKey, InvalidEntry
+from twisted.conch.ssh.keys import Key, BadKeyError
+
+
+def _b64encode(s):
+ """
+ Encode a binary string as base64 with no trailing newline.
+ """
+ return b2a_base64(s).strip()
+
+
+
+def _extractCommon(string):
+ """
+ Extract common elements of base64 keys from an entry in a hosts file.
+
+ @return: a 4-tuple of hostname data (L{str}), ssh key type (L{str}), key
+ (L{Key}), and comment (L{str} or L{None}). The hostname data is simply the
+ beginning of the line up to the first occurrence of whitespace.
+ """
+ elements = string.split(None, 2)
+ if len(elements) != 3:
+ raise InvalidEntry()
+ hostnames, keyType, keyAndComment = elements
+ splitkey = keyAndComment.split(None, 1)
+ if len(splitkey) == 2:
+ keyString, comment = splitkey
+ comment = comment.rstrip("\n")
+ else:
+ keyString = splitkey[0]
+ comment = None
+ key = Key.fromString(keyString.decode('base64'))
+ return hostnames, keyType, key, comment
+
+
+
+class _BaseEntry(object):
+ """
+ Abstract base of both hashed and non-hashed entry objects, since they
+ represent keys and key types the same way.
+
+ @ivar keyType: The type of the key; either ssh-dss or ssh-rsa.
+ @type keyType: L{str}
+
+ @ivar publicKey: The server public key indicated by this line.
+ @type publicKey: L{twisted.conch.ssh.keys.Key}
+
+ @ivar comment: Trailing garbage after the key line.
+ @type comment: L{str}
+ """
+
+ def __init__(self, keyType, publicKey, comment):
+ self.keyType = keyType
+ self.publicKey = publicKey
+ self.comment = comment
+
+
+ def matchesKey(self, keyObject):
+ """
+ Check to see if this entry matches a given key object.
+
+ @type keyObject: L{Key}
+
+ @rtype: bool
+ """
+ return self.publicKey == keyObject
+
+
+
+class PlainEntry(_BaseEntry):
+ """
+ A L{PlainEntry} is a representation of a plain-text entry in a known_hosts
+ file.
+
+ @ivar _hostnames: the list of all host-names associated with this entry.
+ @type _hostnames: L{list} of L{str}
+ """
+
+ implements(IKnownHostEntry)
+
+ def __init__(self, hostnames, keyType, publicKey, comment):
+ self._hostnames = hostnames
+ super(PlainEntry, self).__init__(keyType, publicKey, comment)
+
+
+ def fromString(cls, string):
+ """
+ Parse a plain-text entry in a known_hosts file, and return a
+ corresponding L{PlainEntry}.
+
+ @param string: a space-separated string formatted like "hostname
+ key-type base64-key-data comment".
+
+ @type string: L{str}
+
+ @raise DecodeError: if the key is not valid encoded as valid base64.
+
+ @raise InvalidEntry: if the entry does not have the right number of
+ elements and is therefore invalid.
+
+ @raise BadKeyError: if the key, once decoded from base64, is not
+ actually an SSH key.
+
+ @return: an IKnownHostEntry representing the hostname and key in the
+ input line.
+
+ @rtype: L{PlainEntry}
+ """
+ hostnames, keyType, key, comment = _extractCommon(string)
+ self = cls(hostnames.split(","), keyType, key, comment)
+ return self
+
+ fromString = classmethod(fromString)
+
+
+ def matchesHost(self, hostname):
+ """
+ Check to see if this entry matches a given hostname.
+
+ @type hostname: L{str}
+
+ @rtype: bool
+ """
+ return hostname in self._hostnames
+
+
+ def toString(self):
+ """
+ Implement L{IKnownHostEntry.toString} by recording the comma-separated
+ hostnames, key type, and base-64 encoded key.
+ """
+ fields = [','.join(self._hostnames),
+ self.keyType,
+ _b64encode(self.publicKey.blob())]
+ if self.comment is not None:
+ fields.append(self.comment)
+ return ' '.join(fields)
+
+
+class UnparsedEntry(object):
+ """
+ L{UnparsedEntry} is an entry in a L{KnownHostsFile} which can't actually be
+ parsed; therefore it matches no keys and no hosts.
+ """
+
+ implements(IKnownHostEntry)
+
+ def __init__(self, string):
+ """
+ Create an unparsed entry from a line in a known_hosts file which cannot
+ otherwise be parsed.
+ """
+ self._string = string
+
+
+ def matchesHost(self, hostname):
+ """
+ Always returns False.
+ """
+ return False
+
+
+ def matchesKey(self, key):
+ """
+ Always returns False.
+ """
+ return False
+
+
+ def toString(self):
+ """
+ Returns the input line, without its newline if one was given.
+ """
+ return self._string.rstrip("\n")
+
+
+
+def _hmacedString(key, string):
+ """
+ Return the SHA-1 HMAC hash of the given key and string.
+ """
+ hash = HMAC(key, digestmod=SHA)
+ hash.update(string)
+ return hash.digest()
+
+
+
+class HashedEntry(_BaseEntry):
+ """
+ A L{HashedEntry} is a representation of an entry in a known_hosts file
+ where the hostname has been hashed and salted.
+
+ @ivar _hostSalt: the salt to combine with a hostname for hashing.
+
+ @ivar _hostHash: the hashed representation of the hostname.
+
+ @cvar MAGIC: the 'hash magic' string used to identify a hashed line in a
+ known_hosts file as opposed to a plaintext one.
+ """
+
+ implements(IKnownHostEntry)
+
+ MAGIC = '|1|'
+
+ def __init__(self, hostSalt, hostHash, keyType, publicKey, comment):
+ self._hostSalt = hostSalt
+ self._hostHash = hostHash
+ super(HashedEntry, self).__init__(keyType, publicKey, comment)
+
+
+ def fromString(cls, string):
+ """
+ Load a hashed entry from a string representing a line in a known_hosts
+ file.
+
+ @raise DecodeError: if the key, the hostname, or the is not valid
+ encoded as valid base64
+
+ @raise InvalidEntry: if the entry does not have the right number of
+ elements and is therefore invalid, or the host/hash portion contains
+ more items than just the host and hash.
+
+ @raise BadKeyError: if the key, once decoded from base64, is not
+ actually an SSH key.
+ """
+ stuff, keyType, key, comment = _extractCommon(string)
+ saltAndHash = stuff[len(cls.MAGIC):].split("|")
+ if len(saltAndHash) != 2:
+ raise InvalidEntry()
+ hostSalt, hostHash = saltAndHash
+ self = cls(hostSalt.decode("base64"), hostHash.decode("base64"),
+ keyType, key, comment)
+ return self
+
+ fromString = classmethod(fromString)
+
+
+ def matchesHost(self, hostname):
+ """
+ Implement L{IKnownHostEntry.matchesHost} to compare the hash of the
+ input to the stored hash.
+ """
+ return (_hmacedString(self._hostSalt, hostname) == self._hostHash)
+
+
+ def toString(self):
+ """
+ Implement L{IKnownHostEntry.toString} by base64-encoding the salt, host
+ hash, and key.
+ """
+ fields = [self.MAGIC + '|'.join([_b64encode(self._hostSalt),
+ _b64encode(self._hostHash)]),
+ self.keyType,
+ _b64encode(self.publicKey.blob())]
+ if self.comment is not None:
+ fields.append(self.comment)
+ return ' '.join(fields)
+
+
+
+class KnownHostsFile(object):
+ """
+ A structured representation of an OpenSSH-format ~/.ssh/known_hosts file.
+
+ @ivar _entries: a list of L{IKnownHostEntry} providers.
+
+ @ivar _savePath: the L{FilePath} to save new entries to.
+ """
+
+ def __init__(self, savePath):
+ """
+ Create a new, empty KnownHostsFile.
+
+ You want to use L{KnownHostsFile.fromPath} to parse one of these.
+ """
+ self._entries = []
+ self._savePath = savePath
+
+
+ def hasHostKey(self, hostname, key):
+ """
+ @return: True if the given hostname and key are present in this file,
+ False if they are not.
+
+ @rtype: L{bool}
+
+ @raise HostKeyChanged: if the host key found for the given hostname
+ does not match the given key.
+ """
+ for lineidx, entry in enumerate(self._entries):
+ if entry.matchesHost(hostname):
+ if entry.matchesKey(key):
+ return True
+ else:
+ raise HostKeyChanged(entry, self._savePath, lineidx + 1)
+ return False
+
+
+ def verifyHostKey(self, ui, hostname, ip, key):
+ """
+ Verify the given host key for the given IP and host, asking for
+ confirmation from, and notifying, the given UI about changes to this
+ file.
+
+ @param ui: The user interface to request an IP address from.
+
+ @param hostname: The hostname that the user requested to connect to.
+
+ @param ip: The string representation of the IP address that is actually
+ being connected to.
+
+ @param key: The public key of the server.
+
+ @return: a L{Deferred} that fires with True when the key has been
+ verified, or fires with an errback when the key either cannot be
+ verified or has changed.
+
+ @rtype: L{Deferred}
+ """
+ hhk = defer.maybeDeferred(self.hasHostKey, hostname, key)
+ def gotHasKey(result):
+ if result:
+ if not self.hasHostKey(ip, key):
+ ui.warn("Warning: Permanently added the %s host key for "
+ "IP address '%s' to the list of known hosts." %
+ (key.type(), ip))
+ self.addHostKey(ip, key)
+ self.save()
+ return result
+ else:
+ def promptResponse(response):
+ if response:
+ self.addHostKey(hostname, key)
+ self.addHostKey(ip, key)
+ self.save()
+ return response
+ else:
+ raise UserRejectedKey()
+ return ui.prompt(
+ "The authenticity of host '%s (%s)' "
+ "can't be established.\n"
+ "RSA key fingerprint is %s.\n"
+ "Are you sure you want to continue connecting (yes/no)? " %
+ (hostname, ip, key.fingerprint())).addCallback(promptResponse)
+ return hhk.addCallback(gotHasKey)
+
+
+ def addHostKey(self, hostname, key):
+ """
+ Add a new L{HashedEntry} to the key database.
+
+ Note that you still need to call L{KnownHostsFile.save} if you wish
+ these changes to be persisted.
+
+ @return: the L{HashedEntry} that was added.
+ """
+ salt = secureRandom(20)
+ keyType = "ssh-" + key.type().lower()
+ entry = HashedEntry(salt, _hmacedString(salt, hostname),
+ keyType, key, None)
+ self._entries.append(entry)
+ return entry
+
+
+ def save(self):
+ """
+ Save this L{KnownHostsFile} to the path it was loaded from.
+ """
+ p = self._savePath.parent()
+ if not p.isdir():
+ p.makedirs()
+ self._savePath.setContent('\n'.join(
+ [entry.toString() for entry in self._entries]) + "\n")
+
+
+ def fromPath(cls, path):
+ """
+ @param path: A path object to use for both reading contents from and
+ later saving to.
+
+ @type path: L{FilePath}
+ """
+ self = cls(path)
+ try:
+ fp = path.open()
+ except IOError:
+ return self
+ for line in fp:
+ if line.startswith(HashedEntry.MAGIC):
+ entry = HashedEntry.fromString(line)
+ else:
+ try:
+ entry = PlainEntry.fromString(line)
+ except (DecodeError, InvalidEntry, BadKeyError):
+ entry = UnparsedEntry(line)
+ self._entries.append(entry)
+ return self
+
+ fromPath = classmethod(fromPath)
+
+
+class ConsoleUI(object):
+ """
+ A UI object that can ask true/false questions and post notifications on the
+ console, to be used during key verification.
+
+ @ivar opener: a no-argument callable which should open a console file-like
+ object to be used for reading and writing.
+ """
+
+ def __init__(self, opener):
+ self.opener = opener
+
+
+ def prompt(self, text):
+ """
+ Write the given text as a prompt to the console output, then read a
+ result from the console input.
+
+ @return: a L{Deferred} which fires with L{True} when the user answers
+ 'yes' and L{False} when the user answers 'no'. It may errback if there
+ were any I/O errors.
+ """
+ d = defer.succeed(None)
+ def body(ignored):
+ f = self.opener()
+ f.write(text)
+ while True:
+ answer = f.readline().strip().lower()
+ if answer == 'yes':
+ f.close()
+ return True
+ elif answer == 'no':
+ f.close()
+ return False
+ else:
+ f.write("Please type 'yes' or 'no': ")
+ return d.addCallback(body)
+
+
+ def warn(self, text):
+ """
+ Notify the user (non-interactively) of the provided text, by writing it
+ to the console.
+ """
+ try:
+ f = self.opener()
+ f.write(text)
+ f.close()
+ except:
+ log.err()
diff --git a/vendor/Twisted-10.0.0/twisted/conch/client/options.py b/vendor/Twisted-10.0.0/twisted/conch/client/options.py
new file mode 100644
index 0000000000..6fa3bc5c87
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/client/options.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.conch.ssh.transport import SSHClientTransport, SSHCiphers
+from twisted.python import usage
+
+import sys
+
+class ConchOptions(usage.Options):
+
+ optParameters = [['user', 'l', None, 'Log in using this user name.'],
+ ['identity', 'i', None],
+ ['ciphers', 'c', None],
+ ['macs', 'm', None],
+ ['port', 'p', None, 'Connect to this port. Server must be on the same port.'],
+ ['option', 'o', None, 'Ignored OpenSSH options'],
+ ['host-key-algorithms', '', None],
+ ['known-hosts', '', None, 'File to check for host keys'],
+ ['user-authentications', '', None, 'Types of user authentications to use.'],
+ ['logfile', '', None, 'File to log to, or - for stdout'],
+ ]
+
+ optFlags = [['version', 'V', 'Display version number only.'],
+ ['compress', 'C', 'Enable compression.'],
+ ['log', 'v', 'Enable logging (defaults to stderr)'],
+ ['nox11', 'x', 'Disable X11 connection forwarding (default)'],
+ ['agent', 'A', 'Enable authentication agent forwarding'],
+ ['noagent', 'a', 'Disable authentication agent forwarding (default)'],
+ ['reconnect', 'r', 'Reconnect to the server if the connection is lost.'],
+ ]
+ zsh_altArgDescr = {"connection-usage":"Connection types to use"}
+ #zsh_multiUse = ["foo", "bar"]
+ zsh_mutuallyExclusive = [("agent", "noagent")]
+ zsh_actions = {"user":"_users",
+ "ciphers":"_values -s , 'ciphers to choose from' %s" %
+ " ".join(SSHCiphers.cipherMap.keys()),
+ "macs":"_values -s , 'macs to choose from' %s" %
+ " ".join(SSHCiphers.macMap.keys()),
+ "host-key-algorithms":"_values -s , 'host key algorithms to choose from' %s" %
+ " ".join(SSHClientTransport.supportedPublicKeys),
+ #"user-authentications":"_values -s , 'user authentication types to choose from' %s" %
+ # " ".join(???),
+ }
+ #zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
+ # user, host, or user@host completion similar to zsh's ssh completion
+ zsh_extras = ['1:host | user@host:{_ssh;if compset -P "*@"; then _wanted hosts expl "remote host name" _ssh_hosts && ret=0 elif compset -S "@*"; then _wanted users expl "login name" _ssh_users -S "" && ret=0 else if (( $+opt_args[-l] )); then tmp=() else tmp=( "users:login name:_ssh_users -qS@" ) fi; _alternative "hosts:remote host name:_ssh_hosts" "$tmp[@]" && ret=0 fi}']
+
+ def __init__(self, *args, **kw):
+ usage.Options.__init__(self, *args, **kw)
+ self.identitys = []
+ self.conns = None
+
+ def opt_identity(self, i):
+ """Identity for public-key authentication"""
+ self.identitys.append(i)
+
+ def opt_ciphers(self, ciphers):
+ "Select encryption algorithms"
+ ciphers = ciphers.split(',')
+ for cipher in ciphers:
+ if not SSHCiphers.cipherMap.has_key(cipher):
+ sys.exit("Unknown cipher type '%s'" % cipher)
+ self['ciphers'] = ciphers
+
+
+ def opt_macs(self, macs):
+ "Specify MAC algorithms"
+ macs = macs.split(',')
+ for mac in macs:
+ if not SSHCiphers.macMap.has_key(mac):
+ sys.exit("Unknown mac type '%s'" % mac)
+ self['macs'] = macs
+
+ def opt_host_key_algorithms(self, hkas):
+ "Select host key algorithms"
+ hkas = hkas.split(',')
+ for hka in hkas:
+ if hka not in SSHClientTransport.supportedPublicKeys:
+ sys.exit("Unknown host key type '%s'" % hka)
+ self['host-key-algorithms'] = hkas
+
+ def opt_user_authentications(self, uas):
+ "Choose how to authenticate to the remote server"
+ self['user-authentications'] = uas.split(',')
+
+# def opt_compress(self):
+# "Enable compression"
+# self.enableCompression = 1
+# SSHClientTransport.supportedCompressions[0:1] = ['zlib']
diff --git a/vendor/Twisted-10.0.0/twisted/conch/error.py b/vendor/Twisted-10.0.0/twisted/conch/error.py
new file mode 100644
index 0000000000..dd4928f129
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/error.py
@@ -0,0 +1,102 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An error to represent bad things happening in Conch.
+
+Maintainer: Paul Swartz
+"""
+
+from twisted.cred.error import UnauthorizedLogin
+
+
+
+class ConchError(Exception):
+ def __init__(self, value, data = None):
+ Exception.__init__(self, value, data)
+ self.value = value
+ self.data = data
+
+
+
+class NotEnoughAuthentication(Exception):
+ """
+ This is thrown if the authentication is valid, but is not enough to
+ successfully verify the user. i.e. don't retry this type of
+ authentication, try another one.
+ """
+
+
+
+class ValidPublicKey(UnauthorizedLogin):
+ """
+ Raised by public key checkers when they receive public key credentials
+ that don't contain a signature at all, but are valid in every other way.
+ (e.g. the public key matches one in the user's authorized_keys file).
+
+ Protocol code (eg
+ L{SSHUserAuthServer<twisted.conch.ssh.userauth.SSHUserAuthServer>}) which
+ attempts to log in using
+ L{ISSHPrivateKey<twisted.cred.credentials.ISSHPrivateKey>} credentials
+ should be prepared to handle a failure of this type by telling the user to
+ re-authenticate using the same key and to include a signature with the new
+ attempt.
+
+ See U{http://www.ietf.org/rfc/rfc4252.txt} section 7 for more details.
+ """
+
+
+
+class IgnoreAuthentication(Exception):
+ """
+ This is thrown to let the UserAuthServer know it doesn't need to handle the
+ authentication anymore.
+ """
+
+
+
+class MissingKeyStoreError(Exception):
+ """
+ Raised if an SSHAgentServer starts receiving data without its factory
+ providing a keys dict on which to read/write key data.
+ """
+
+
+
+class UserRejectedKey(Exception):
+ """
+ The user interactively rejected a key.
+ """
+
+
+
+class InvalidEntry(Exception):
+ """
+ An entry in a known_hosts file could not be interpreted as a valid entry.
+ """
+
+
+
+class HostKeyChanged(Exception):
+ """
+ The host key of a remote host has changed.
+
+ @ivar offendingEntry: The entry which contains the persistent host key that
+ disagrees with the given host key.
+
+ @type offendingEntry: L{twisted.conch.interfaces.IKnownHostEntry}
+
+ @ivar path: a reference to the known_hosts file that the offending entry
+ was loaded from
+
+ @type path: L{twisted.python.filepath.FilePath}
+
+ @ivar lineno: The line number of the offending entry in the given path.
+
+ @type lineno: L{int}
+ """
+ def __init__(self, offendingEntry, path, lineno):
+ Exception.__init__(self)
+ self.offendingEntry = offendingEntry
+ self.path = path
+ self.lineno = lineno
diff --git a/vendor/Twisted-10.0.0/twisted/conch/insults/__init__.py b/vendor/Twisted-10.0.0/twisted/conch/insults/__init__.py
new file mode 100644
index 0000000000..3d83876698
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/insults/__init__.py
@@ -0,0 +1,4 @@
+"""
+Insults: a replacement for Curses/S-Lang.
+
+Very basic at the moment."""
diff --git a/vendor/Twisted-10.0.0/twisted/conch/insults/client.py b/vendor/Twisted-10.0.0/twisted/conch/insults/client.py
new file mode 100644
index 0000000000..89c79cdaf0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/insults/client.py
@@ -0,0 +1,138 @@
+"""
+You don't really want to use this module. Try insults.py instead.
+"""
+
+from twisted.internet import protocol
+
+class InsultsClient(protocol.Protocol):
+
+ escapeTimeout = 0.2
+
+ def __init__(self):
+ self.width = self.height = None
+ self.xpos = self.ypos = 0
+ self.commandQueue = []
+ self.inEscape = ''
+
+ def setSize(self, width, height):
+ call = 0
+ if self.width:
+ call = 1
+ self.width = width
+ self.height = height
+ if call:
+ self.windowSizeChanged()
+
+ def dataReceived(self, data):
+ from twisted.internet import reactor
+ for ch in data:
+ if ch == '\x1b':
+ if self.inEscape:
+ self.keyReceived(ch)
+ self.inEscape = ''
+ else:
+ self.inEscape = ch
+ self.escapeCall = reactor.callLater(self.escapeTimeout,
+ self.endEscape)
+ elif ch in 'ABCD' and self.inEscape:
+ self.inEscape = ''
+ self.escapeCall.cancel()
+ if ch == 'A':
+ self.keyReceived('<Up>')
+ elif ch == 'B':
+ self.keyReceived('<Down>')
+ elif ch == 'C':
+ self.keyReceived('<Right>')
+ elif ch == 'D':
+ self.keyReceived('<Left>')
+ elif self.inEscape:
+ self.inEscape += ch
+ else:
+ self.keyReceived(ch)
+
+ def endEscape(self):
+ ch = self.inEscape
+ self.inEscape = ''
+ self.keyReceived(ch)
+
+ def initScreen(self):
+ self.transport.write('\x1b=\x1b[?1h')
+
+ def gotoXY(self, x, y):
+ """Go to a position on the screen.
+ """
+ self.xpos = x
+ self.ypos = y
+ self.commandQueue.append(('gotoxy', x, y))
+
+ def writeCh(self, ch):
+ """Write a character to the screen. If we're at the end of the row,
+ ignore the write.
+ """
+ if self.xpos < self.width - 1:
+ self.commandQueue.append(('write', ch))
+ self.xpos += 1
+
+ def writeStr(self, s):
+ """Write a string to the screen. This does not wrap a the edge of the
+ screen, and stops at \\r and \\n.
+ """
+ s = s[:self.width-self.xpos]
+ if '\n' in s:
+ s=s[:s.find('\n')]
+ if '\r' in s:
+ s=s[:s.find('\r')]
+ self.commandQueue.append(('write', s))
+ self.xpos += len(s)
+
+ def eraseToLine(self):
+ """Erase from the current position to the end of the line.
+ """
+ self.commandQueue.append(('eraseeol',))
+
+ def eraseToScreen(self):
+ """Erase from the current position to the end of the screen.
+ """
+ self.commandQueue.append(('eraseeos',))
+
+ def clearScreen(self):
+ """Clear the screen, and return the cursor to 0, 0.
+ """
+ self.commandQueue = [('cls',)]
+ self.xpos = self.ypos = 0
+
+ def setAttributes(self, *attrs):
+ """Set the attributes for drawing on the screen.
+ """
+ self.commandQueue.append(('attributes', attrs))
+
+ def refresh(self):
+ """Redraw the screen.
+ """
+ redraw = ''
+ for command in self.commandQueue:
+ if command[0] == 'gotoxy':
+ redraw += '\x1b[%i;%iH' % (command[2]+1, command[1]+1)
+ elif command[0] == 'write':
+ redraw += command[1]
+ elif command[0] == 'eraseeol':
+ redraw += '\x1b[0K'
+ elif command[0] == 'eraseeos':
+ redraw += '\x1b[OJ'
+ elif command[0] == 'cls':
+ redraw += '\x1b[H\x1b[J'
+ elif command[0] == 'attributes':
+ redraw += '\x1b[%sm' % ';'.join(map(str, command[1]))
+ else:
+ print command
+ self.commandQueue = []
+ self.transport.write(redraw)
+
+ def windowSizeChanged(self):
+ """Called when the size of the window changes.
+ Might want to redraw the screen here, or something.
+ """
+
+ def keyReceived(self, key):
+ """Called when the user hits a key.
+ """
diff --git a/vendor/Twisted-10.0.0/twisted/conch/insults/colors.py b/vendor/Twisted-10.0.0/twisted/conch/insults/colors.py
new file mode 100644
index 0000000000..c12ab16fa5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/insults/colors.py
@@ -0,0 +1,29 @@
+"""
+You don't really want to use this module. Try helper.py instead.
+"""
+
+CLEAR = 0
+BOLD = 1
+DIM = 2
+ITALIC = 3
+UNDERSCORE = 4
+BLINK_SLOW = 5
+BLINK_FAST = 6
+REVERSE = 7
+CONCEALED = 8
+FG_BLACK = 30
+FG_RED = 31
+FG_GREEN = 32
+FG_YELLOW = 33
+FG_BLUE = 34
+FG_MAGENTA = 35
+FG_CYAN = 36
+FG_WHITE = 37
+BG_BLACK = 40
+BG_RED = 41
+BG_GREEN = 42
+BG_YELLOW = 43
+BG_BLUE = 44
+BG_MAGENTA = 45
+BG_CYAN = 46
+BG_WHITE = 47
diff --git a/vendor/Twisted-10.0.0/twisted/conch/insults/helper.py b/vendor/Twisted-10.0.0/twisted/conch/insults/helper.py
new file mode 100644
index 0000000000..f7b8da3f18
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/insults/helper.py
@@ -0,0 +1,450 @@
+# -*- test-case-name: twisted.conch.test.test_helper -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Partial in-memory terminal emulator
+
+@author: Jp Calderone
+"""
+
+import re, string
+
+from zope.interface import implements
+
+from twisted.internet import defer, protocol, reactor
+from twisted.python import log
+
+from twisted.conch.insults import insults
+
+FOREGROUND = 30
+BACKGROUND = 40
+BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, N_COLORS = range(9)
+
+class CharacterAttribute:
+ """Represents the attributes of a single character.
+
+ Character set, intensity, underlinedness, blinkitude, video
+ reversal, as well as foreground and background colors made up a
+ character's attributes.
+ """
+ def __init__(self, charset=insults.G0,
+ bold=False, underline=False,
+ blink=False, reverseVideo=False,
+ foreground=WHITE, background=BLACK,
+
+ _subtracting=False):
+ self.charset = charset
+ self.bold = bold
+ self.underline = underline
+ self.blink = blink
+ self.reverseVideo = reverseVideo
+ self.foreground = foreground
+ self.background = background
+
+ self._subtracting = _subtracting
+
+ def __eq__(self, other):
+ return vars(self) == vars(other)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def copy(self):
+ c = self.__class__()
+ c.__dict__.update(vars(self))
+ return c
+
+ def wantOne(self, **kw):
+ k, v = kw.popitem()
+ if getattr(self, k) != v:
+ attr = self.copy()
+ attr._subtracting = not v
+ setattr(attr, k, v)
+ return attr
+ else:
+ return self.copy()
+
+ def toVT102(self):
+ # Spit out a vt102 control sequence that will set up
+ # all the attributes set here. Except charset.
+ attrs = []
+ if self._subtracting:
+ attrs.append(0)
+ if self.bold:
+ attrs.append(insults.BOLD)
+ if self.underline:
+ attrs.append(insults.UNDERLINE)
+ if self.blink:
+ attrs.append(insults.BLINK)
+ if self.reverseVideo:
+ attrs.append(insults.REVERSE_VIDEO)
+ if self.foreground != WHITE:
+ attrs.append(FOREGROUND + self.foreground)
+ if self.background != BLACK:
+ attrs.append(BACKGROUND + self.background)
+ if attrs:
+ return '\x1b[' + ';'.join(map(str, attrs)) + 'm'
+ return ''
+
+# XXX - need to support scroll regions and scroll history
+class TerminalBuffer(protocol.Protocol):
+ """
+ An in-memory terminal emulator.
+ """
+ implements(insults.ITerminalTransport)
+
+ for keyID in ('UP_ARROW', 'DOWN_ARROW', 'RIGHT_ARROW', 'LEFT_ARROW',
+ 'HOME', 'INSERT', 'DELETE', 'END', 'PGUP', 'PGDN',
+ 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
+ 'F10', 'F11', 'F12'):
+ exec '%s = object()' % (keyID,)
+
+ TAB = '\t'
+ BACKSPACE = '\x7f'
+
+ width = 80
+ height = 24
+
+ fill = ' '
+ void = object()
+
+ def getCharacter(self, x, y):
+ return self.lines[y][x]
+
+ def connectionMade(self):
+ self.reset()
+
+ def write(self, bytes):
+ """
+ Add the given printable bytes to the terminal.
+
+ Line feeds in C{bytes} will be replaced with carriage return / line
+ feed pairs.
+ """
+ for b in bytes.replace('\n', '\r\n'):
+ self.insertAtCursor(b)
+
+ def _currentCharacterAttributes(self):
+ return CharacterAttribute(self.activeCharset, **self.graphicRendition)
+
+ def insertAtCursor(self, b):
+ """
+ Add one byte to the terminal at the cursor and make consequent state
+ updates.
+
+ If b is a carriage return, move the cursor to the beginning of the
+ current row.
+
+ If b is a line feed, move the cursor to the next row or scroll down if
+ the cursor is already in the last row.
+
+ Otherwise, if b is printable, put it at the cursor position (inserting
+ or overwriting as dictated by the current mode) and move the cursor.
+ """
+ if b == '\r':
+ self.x = 0
+ elif b == '\n':
+ self._scrollDown()
+ elif b in string.printable:
+ if self.x >= self.width:
+ self.nextLine()
+ ch = (b, self._currentCharacterAttributes())
+ if self.modes.get(insults.modes.IRM):
+ self.lines[self.y][self.x:self.x] = [ch]
+ self.lines[self.y].pop()
+ else:
+ self.lines[self.y][self.x] = ch
+ self.x += 1
+
+ def _emptyLine(self, width):
+ return [(self.void, self._currentCharacterAttributes()) for i in xrange(width)]
+
+ def _scrollDown(self):
+ self.y += 1
+ if self.y >= self.height:
+ self.y -= 1
+ del self.lines[0]
+ self.lines.append(self._emptyLine(self.width))
+
+ def _scrollUp(self):
+ self.y -= 1
+ if self.y < 0:
+ self.y = 0
+ del self.lines[-1]
+ self.lines.insert(0, self._emptyLine(self.width))
+
+ def cursorUp(self, n=1):
+ self.y = max(0, self.y - n)
+
+ def cursorDown(self, n=1):
+ self.y = min(self.height - 1, self.y + n)
+
+ def cursorBackward(self, n=1):
+ self.x = max(0, self.x - n)
+
+ def cursorForward(self, n=1):
+ self.x = min(self.width, self.x + n)
+
+ def cursorPosition(self, column, line):
+ self.x = column
+ self.y = line
+
+ def cursorHome(self):
+ self.x = self.home.x
+ self.y = self.home.y
+
+ def index(self):
+ self._scrollDown()
+
+ def reverseIndex(self):
+ self._scrollUp()
+
+ def nextLine(self):
+ """
+ Update the cursor position attributes and scroll down if appropriate.
+ """
+ self.x = 0
+ self._scrollDown()
+
+ def saveCursor(self):
+ self._savedCursor = (self.x, self.y)
+
+ def restoreCursor(self):
+ self.x, self.y = self._savedCursor
+ del self._savedCursor
+
+ def setModes(self, modes):
+ for m in modes:
+ self.modes[m] = True
+
+ def resetModes(self, modes):
+ for m in modes:
+ try:
+ del self.modes[m]
+ except KeyError:
+ pass
+
+
+ def setPrivateModes(self, modes):
+ """
+ Enable the given modes.
+
+ Track which modes have been enabled so that the implementations of
+ other L{insults.ITerminalTransport} methods can be properly implemented
+ to respect these settings.
+
+ @see: L{resetPrivateModes}
+ @see: L{insults.ITerminalTransport.setPrivateModes}
+ """
+ for m in modes:
+ self.privateModes[m] = True
+
+
+ def resetPrivateModes(self, modes):
+ """
+ Disable the given modes.
+
+ @see: L{setPrivateModes}
+ @see: L{insults.ITerminalTransport.resetPrivateModes}
+ """
+ for m in modes:
+ try:
+ del self.privateModes[m]
+ except KeyError:
+ pass
+
+
+ def applicationKeypadMode(self):
+ self.keypadMode = 'app'
+
+ def numericKeypadMode(self):
+ self.keypadMode = 'num'
+
+ def selectCharacterSet(self, charSet, which):
+ self.charsets[which] = charSet
+
+ def shiftIn(self):
+ self.activeCharset = insults.G0
+
+ def shiftOut(self):
+ self.activeCharset = insults.G1
+
+ def singleShift2(self):
+ oldActiveCharset = self.activeCharset
+ self.activeCharset = insults.G2
+ f = self.insertAtCursor
+ def insertAtCursor(b):
+ f(b)
+ del self.insertAtCursor
+ self.activeCharset = oldActiveCharset
+ self.insertAtCursor = insertAtCursor
+
+ def singleShift3(self):
+ oldActiveCharset = self.activeCharset
+ self.activeCharset = insults.G3
+ f = self.insertAtCursor
+ def insertAtCursor(b):
+ f(b)
+ del self.insertAtCursor
+ self.activeCharset = oldActiveCharset
+ self.insertAtCursor = insertAtCursor
+
+ def selectGraphicRendition(self, *attributes):
+ for a in attributes:
+ if a == insults.NORMAL:
+ self.graphicRendition = {
+ 'bold': False,
+ 'underline': False,
+ 'blink': False,
+ 'reverseVideo': False,
+ 'foreground': WHITE,
+ 'background': BLACK}
+ elif a == insults.BOLD:
+ self.graphicRendition['bold'] = True
+ elif a == insults.UNDERLINE:
+ self.graphicRendition['underline'] = True
+ elif a == insults.BLINK:
+ self.graphicRendition['blink'] = True
+ elif a == insults.REVERSE_VIDEO:
+ self.graphicRendition['reverseVideo'] = True
+ else:
+ try:
+ v = int(a)
+ except ValueError:
+ log.msg("Unknown graphic rendition attribute: " + repr(a))
+ else:
+ if FOREGROUND <= v <= FOREGROUND + N_COLORS:
+ self.graphicRendition['foreground'] = v - FOREGROUND
+ elif BACKGROUND <= v <= BACKGROUND + N_COLORS:
+ self.graphicRendition['background'] = v - BACKGROUND
+ else:
+ log.msg("Unknown graphic rendition attribute: " + repr(a))
+
+ def eraseLine(self):
+ self.lines[self.y] = self._emptyLine(self.width)
+
+ def eraseToLineEnd(self):
+ width = self.width - self.x
+ self.lines[self.y][self.x:] = self._emptyLine(width)
+
+ def eraseToLineBeginning(self):
+ self.lines[self.y][:self.x + 1] = self._emptyLine(self.x + 1)
+
+ def eraseDisplay(self):
+ self.lines = [self._emptyLine(self.width) for i in xrange(self.height)]
+
+ def eraseToDisplayEnd(self):
+ self.eraseToLineEnd()
+ height = self.height - self.y - 1
+ self.lines[self.y + 1:] = [self._emptyLine(self.width) for i in range(height)]
+
+ def eraseToDisplayBeginning(self):
+ self.eraseToLineBeginning()
+ self.lines[:self.y] = [self._emptyLine(self.width) for i in range(self.y)]
+
+ def deleteCharacter(self, n=1):
+ del self.lines[self.y][self.x:self.x+n]
+ self.lines[self.y].extend(self._emptyLine(min(self.width - self.x, n)))
+
+ def insertLine(self, n=1):
+ self.lines[self.y:self.y] = [self._emptyLine(self.width) for i in range(n)]
+ del self.lines[self.height:]
+
+ def deleteLine(self, n=1):
+ del self.lines[self.y:self.y+n]
+ self.lines.extend([self._emptyLine(self.width) for i in range(n)])
+
+ def reportCursorPosition(self):
+ return (self.x, self.y)
+
+ def reset(self):
+ self.home = insults.Vector(0, 0)
+ self.x = self.y = 0
+ self.modes = {}
+ self.privateModes = {}
+ self.setPrivateModes([insults.privateModes.AUTO_WRAP,
+ insults.privateModes.CURSOR_MODE])
+ self.numericKeypad = 'app'
+ self.activeCharset = insults.G0
+ self.graphicRendition = {
+ 'bold': False,
+ 'underline': False,
+ 'blink': False,
+ 'reverseVideo': False,
+ 'foreground': WHITE,
+ 'background': BLACK}
+ self.charsets = {
+ insults.G0: insults.CS_US,
+ insults.G1: insults.CS_US,
+ insults.G2: insults.CS_ALTERNATE,
+ insults.G3: insults.CS_ALTERNATE_SPECIAL}
+ self.eraseDisplay()
+
+ def unhandledControlSequence(self, buf):
+ print 'Could not handle', repr(buf)
+
+ def __str__(self):
+ lines = []
+ for L in self.lines:
+ buf = []
+ length = 0
+ for (ch, attr) in L:
+ if ch is not self.void:
+ buf.append(ch)
+ length = len(buf)
+ else:
+ buf.append(self.fill)
+ lines.append(''.join(buf[:length]))
+ return '\n'.join(lines)
+
+class ExpectationTimeout(Exception):
+ pass
+
+class ExpectableBuffer(TerminalBuffer):
+ _mark = 0
+
+ def connectionMade(self):
+ TerminalBuffer.connectionMade(self)
+ self._expecting = []
+
+ def write(self, bytes):
+ TerminalBuffer.write(self, bytes)
+ self._checkExpected()
+
+ def cursorHome(self):
+ TerminalBuffer.cursorHome(self)
+ self._mark = 0
+
+ def _timeoutExpected(self, d):
+ d.errback(ExpectationTimeout())
+ self._checkExpected()
+
+ def _checkExpected(self):
+ s = str(self)[self._mark:]
+ while self._expecting:
+ expr, timer, deferred = self._expecting[0]
+ if timer and not timer.active():
+ del self._expecting[0]
+ continue
+ for match in expr.finditer(s):
+ if timer:
+ timer.cancel()
+ del self._expecting[0]
+ self._mark += match.end()
+ s = s[match.end():]
+ deferred.callback(match)
+ break
+ else:
+ return
+
+ def expect(self, expression, timeout=None, scheduler=reactor):
+ d = defer.Deferred()
+ timer = None
+ if timeout:
+ timer = scheduler.callLater(timeout, self._timeoutExpected, d)
+ self._expecting.append((re.compile(expression), timer, d))
+ self._checkExpected()
+ return d
+
+__all__ = ['CharacterAttribute', 'TerminalBuffer', 'ExpectableBuffer']
diff --git a/vendor/Twisted-10.0.0/twisted/conch/insults/insults.py b/vendor/Twisted-10.0.0/twisted/conch/insults/insults.py
new file mode 100644
index 0000000000..bd7fba398c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/insults/insults.py
@@ -0,0 +1,1087 @@
+# -*- test-case-name: twisted.conch.test.test_insults -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+VT102 and VT220 terminal manipulation.
+
+@author: Jp Calderone
+"""
+
+from zope.interface import implements, Interface
+
+from twisted.internet import protocol, defer, interfaces as iinternet
+
+class ITerminalProtocol(Interface):
+ def makeConnection(transport):
+ """Called with an L{ITerminalTransport} when a connection is established.
+ """
+
+ def keystrokeReceived(keyID, modifier):
+ """A keystroke was received.
+
+ Each keystroke corresponds to one invocation of this method.
+ keyID is a string identifier for that key. Printable characters
+ are represented by themselves. Control keys, such as arrows and
+ function keys, are represented with symbolic constants on
+ L{ServerProtocol}.
+ """
+
+ def terminalSize(width, height):
+ """Called to indicate the size of the terminal.
+
+ A terminal of 80x24 should be assumed if this method is not
+ called. This method might not be called for real terminals.
+ """
+
+ def unhandledControlSequence(seq):
+ """Called when an unsupported control sequence is received.
+
+ @type seq: C{str}
+ @param seq: The whole control sequence which could not be interpreted.
+ """
+
+ def connectionLost(reason):
+ """Called when the connection has been lost.
+
+ reason is a Failure describing why.
+ """
+
+class TerminalProtocol(object):
+ implements(ITerminalProtocol)
+
+ def makeConnection(self, terminal):
+ # assert ITerminalTransport.providedBy(transport), "TerminalProtocol.makeConnection must be passed an ITerminalTransport implementor"
+ self.terminal = terminal
+ self.connectionMade()
+
+ def connectionMade(self):
+ """Called after a connection has been established.
+ """
+
+ def keystrokeReceived(self, keyID, modifier):
+ pass
+
+ def terminalSize(self, width, height):
+ pass
+
+ def unhandledControlSequence(self, seq):
+ pass
+
+ def connectionLost(self, reason):
+ pass
+
+class ITerminalTransport(iinternet.ITransport):
+ def cursorUp(n=1):
+ """Move the cursor up n lines.
+ """
+
+ def cursorDown(n=1):
+ """Move the cursor down n lines.
+ """
+
+ def cursorForward(n=1):
+ """Move the cursor right n columns.
+ """
+
+ def cursorBackward(n=1):
+ """Move the cursor left n columns.
+ """
+
+ def cursorPosition(column, line):
+ """Move the cursor to the given line and column.
+ """
+
+ def cursorHome():
+ """Move the cursor home.
+ """
+
+ def index():
+ """Move the cursor down one line, performing scrolling if necessary.
+ """
+
+ def reverseIndex():
+ """Move the cursor up one line, performing scrolling if necessary.
+ """
+
+ def nextLine():
+ """Move the cursor to the first position on the next line, performing scrolling if necessary.
+ """
+
+ def saveCursor():
+ """Save the cursor position, character attribute, character set, and origin mode selection.
+ """
+
+ def restoreCursor():
+ """Restore the previously saved cursor position, character attribute, character set, and origin mode selection.
+
+ If no cursor state was previously saved, move the cursor to the home position.
+ """
+
+ def setModes(modes):
+ """Set the given modes on the terminal.
+ """
+
+ def resetModes(mode):
+ """Reset the given modes on the terminal.
+ """
+
+
+ def setPrivateModes(modes):
+ """
+ Set the given DEC private modes on the terminal.
+ """
+
+
+ def resetPrivateModes(modes):
+ """
+ Reset the given DEC private modes on the terminal.
+ """
+
+
+ def applicationKeypadMode():
+ """Cause keypad to generate control functions.
+
+ Cursor key mode selects the type of characters generated by cursor keys.
+ """
+
+ def numericKeypadMode():
+ """Cause keypad to generate normal characters.
+ """
+
+ def selectCharacterSet(charSet, which):
+ """Select a character set.
+
+ charSet should be one of CS_US, CS_UK, CS_DRAWING, CS_ALTERNATE, or
+ CS_ALTERNATE_SPECIAL.
+
+ which should be one of G0 or G1.
+ """
+
+ def shiftIn():
+ """Activate the G0 character set.
+ """
+
+ def shiftOut():
+ """Activate the G1 character set.
+ """
+
+ def singleShift2():
+ """Shift to the G2 character set for a single character.
+ """
+
+ def singleShift3():
+ """Shift to the G3 character set for a single character.
+ """
+
+ def selectGraphicRendition(*attributes):
+ """Enabled one or more character attributes.
+
+ Arguments should be one or more of UNDERLINE, REVERSE_VIDEO, BLINK, or BOLD.
+ NORMAL may also be specified to disable all character attributes.
+ """
+
+ def horizontalTabulationSet():
+ """Set a tab stop at the current cursor position.
+ """
+
+ def tabulationClear():
+ """Clear the tab stop at the current cursor position.
+ """
+
+ def tabulationClearAll():
+ """Clear all tab stops.
+ """
+
+ def doubleHeightLine(top=True):
+ """Make the current line the top or bottom half of a double-height, double-width line.
+
+ If top is True, the current line is the top half. Otherwise, it is the bottom half.
+ """
+
+ def singleWidthLine():
+ """Make the current line a single-width, single-height line.
+ """
+
+ def doubleWidthLine():
+ """Make the current line a double-width line.
+ """
+
+ def eraseToLineEnd():
+ """Erase from the cursor to the end of line, including cursor position.
+ """
+
+ def eraseToLineBeginning():
+ """Erase from the cursor to the beginning of the line, including the cursor position.
+ """
+
+ def eraseLine():
+ """Erase the entire cursor line.
+ """
+
+ def eraseToDisplayEnd():
+ """Erase from the cursor to the end of the display, including the cursor position.
+ """
+
+ def eraseToDisplayBeginning():
+ """Erase from the cursor to the beginning of the display, including the cursor position.
+ """
+
+ def eraseDisplay():
+ """Erase the entire display.
+ """
+
+ def deleteCharacter(n=1):
+ """Delete n characters starting at the cursor position.
+
+ Characters to the right of deleted characters are shifted to the left.
+ """
+
+ def insertLine(n=1):
+ """Insert n lines at the cursor position.
+
+ Lines below the cursor are shifted down. Lines moved past the bottom margin are lost.
+ This command is ignored when the cursor is outside the scroll region.
+ """
+
+ def deleteLine(n=1):
+ """Delete n lines starting at the cursor position.
+
+ Lines below the cursor are shifted up. This command is ignored when the cursor is outside
+ the scroll region.
+ """
+
+ def reportCursorPosition():
+ """Return a Deferred that fires with a two-tuple of (x, y) indicating the cursor position.
+ """
+
+ def reset():
+ """Reset the terminal to its initial state.
+ """
+
+ def unhandledControlSequence(seq):
+ """Called when an unsupported control sequence is received.
+
+ @type seq: C{str}
+ @param seq: The whole control sequence which could not be interpreted.
+ """
+
+
+CSI = '\x1b'
+CST = {'~': 'tilde'}
+
+class modes:
+ """ECMA 48 standardized modes
+ """
+
+ # BREAKS YOPUR KEYBOARD MOFO
+ KEYBOARD_ACTION = KAM = 2
+
+ # When set, enables character insertion. New display characters
+ # move old display characters to the right. Characters moved past
+ # the right margin are lost.
+
+ # When reset, enables replacement mode (disables character
+ # insertion). New display characters replace old display
+ # characters at cursor position. The old character is erased.
+ INSERTION_REPLACEMENT = IRM = 4
+
+ # Set causes a received linefeed, form feed, or vertical tab to
+ # move cursor to first column of next line. RETURN transmits both
+ # a carriage return and linefeed. This selection is also called
+ # new line option.
+
+ # Reset causes a received linefeed, form feed, or vertical tab to
+ # move cursor to next line in current column. RETURN transmits a
+ # carriage return.
+ LINEFEED_NEWLINE = LNM = 20
+
+
+class privateModes:
+ """ANSI-Compatible Private Modes
+ """
+ ERROR = 0
+ CURSOR_KEY = 1
+ ANSI_VT52 = 2
+ COLUMN = 3
+ SCROLL = 4
+ SCREEN = 5
+ ORIGIN = 6
+ AUTO_WRAP = 7
+ AUTO_REPEAT = 8
+ PRINTER_FORM_FEED = 18
+ PRINTER_EXTENT = 19
+
+ # Toggle cursor visibility (reset hides it)
+ CURSOR_MODE = 25
+
+
+# Character sets
+CS_US = 'CS_US'
+CS_UK = 'CS_UK'
+CS_DRAWING = 'CS_DRAWING'
+CS_ALTERNATE = 'CS_ALTERNATE'
+CS_ALTERNATE_SPECIAL = 'CS_ALTERNATE_SPECIAL'
+
+# Groupings (or something?? These are like variables that can be bound to character sets)
+G0 = 'G0'
+G1 = 'G1'
+
+# G2 and G3 cannot be changed, but they can be shifted to.
+G2 = 'G2'
+G3 = 'G3'
+
+# Character attributes
+
+NORMAL = 0
+BOLD = 1
+UNDERLINE = 4
+BLINK = 5
+REVERSE_VIDEO = 7
+
+class Vector:
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+def log(s):
+ file('log', 'a').write(str(s) + '\n')
+
+# XXX TODO - These attributes are really part of the
+# ITerminalTransport interface, I think.
+_KEY_NAMES = ('UP_ARROW', 'DOWN_ARROW', 'RIGHT_ARROW', 'LEFT_ARROW',
+ 'HOME', 'INSERT', 'DELETE', 'END', 'PGUP', 'PGDN', 'NUMPAD_MIDDLE',
+ 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
+ 'F10', 'F11', 'F12',
+
+ 'ALT', 'SHIFT', 'CONTROL')
+
+class _const(object):
+ """
+ @ivar name: A string naming this constant
+ """
+ def __init__(self, name):
+ self.name = name
+
+ def __repr__(self):
+ return '[' + self.name + ']'
+
+
+FUNCTION_KEYS = [
+ _const(_name) for _name in _KEY_NAMES]
+
+class ServerProtocol(protocol.Protocol):
+ implements(ITerminalTransport)
+
+ protocolFactory = None
+ terminalProtocol = None
+
+ TAB = '\t'
+ BACKSPACE = '\x7f'
+ ##
+
+ lastWrite = ''
+
+ state = 'data'
+
+ termSize = Vector(80, 24)
+ cursorPos = Vector(0, 0)
+ scrollRegion = None
+
+ # Factory who instantiated me
+ factory = None
+
+ def __init__(self, protocolFactory=None, *a, **kw):
+ """
+ @param protocolFactory: A callable which will be invoked with
+ *a, **kw and should return an ITerminalProtocol implementor.
+ This will be invoked when a connection to this ServerProtocol
+ is established.
+
+ @param a: Any positional arguments to pass to protocolFactory.
+ @param kw: Any keyword arguments to pass to protocolFactory.
+ """
+ # assert protocolFactory is None or ITerminalProtocol.implementedBy(protocolFactory), "ServerProtocol.__init__ must be passed an ITerminalProtocol implementor"
+ if protocolFactory is not None:
+ self.protocolFactory = protocolFactory
+ self.protocolArgs = a
+ self.protocolKwArgs = kw
+
+ self._cursorReports = []
+
+ def connectionMade(self):
+ if self.protocolFactory is not None:
+ self.terminalProtocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
+
+ try:
+ factory = self.factory
+ except AttributeError:
+ pass
+ else:
+ self.terminalProtocol.factory = factory
+
+ self.terminalProtocol.makeConnection(self)
+
+ def dataReceived(self, data):
+ for ch in data:
+ if self.state == 'data':
+ if ch == '\x1b':
+ self.state = 'escaped'
+ else:
+ self.terminalProtocol.keystrokeReceived(ch, None)
+ elif self.state == 'escaped':
+ if ch == '[':
+ self.state = 'bracket-escaped'
+ self.escBuf = []
+ elif ch == 'O':
+ self.state = 'low-function-escaped'
+ else:
+ self.state = 'data'
+ self._handleShortControlSequence(ch)
+ elif self.state == 'bracket-escaped':
+ if ch == 'O':
+ self.state = 'low-function-escaped'
+ elif ch.isalpha() or ch == '~':
+ self._handleControlSequence(''.join(self.escBuf) + ch)
+ del self.escBuf
+ self.state = 'data'
+ else:
+ self.escBuf.append(ch)
+ elif self.state == 'low-function-escaped':
+ self._handleLowFunctionControlSequence(ch)
+ self.state = 'data'
+ else:
+ raise ValueError("Illegal state")
+
+ def _handleShortControlSequence(self, ch):
+ self.terminalProtocol.keystrokeReceived(ch, self.ALT)
+
+ def _handleControlSequence(self, buf):
+ buf = '\x1b[' + buf
+ f = getattr(self.controlSequenceParser, CST.get(buf[-1], buf[-1]), None)
+ if f is None:
+ self.unhandledControlSequence(buf)
+ else:
+ f(self, self.terminalProtocol, buf[:-1])
+
+ def unhandledControlSequence(self, buf):
+ self.terminalProtocol.unhandledControlSequence(buf)
+
+ def _handleLowFunctionControlSequence(self, ch):
+ map = {'P': self.F1, 'Q': self.F2, 'R': self.F3, 'S': self.F4}
+ keyID = map.get(ch)
+ if keyID is not None:
+ self.terminalProtocol.keystrokeReceived(keyID, None)
+ else:
+ self.terminalProtocol.unhandledControlSequence('\x1b[O' + ch)
+
+ class ControlSequenceParser:
+ def A(self, proto, handler, buf):
+ if buf == '\x1b[':
+ handler.keystrokeReceived(proto.UP_ARROW, None)
+ else:
+ handler.unhandledControlSequence(buf + 'A')
+
+ def B(self, proto, handler, buf):
+ if buf == '\x1b[':
+ handler.keystrokeReceived(proto.DOWN_ARROW, None)
+ else:
+ handler.unhandledControlSequence(buf + 'B')
+
+ def C(self, proto, handler, buf):
+ if buf == '\x1b[':
+ handler.keystrokeReceived(proto.RIGHT_ARROW, None)
+ else:
+ handler.unhandledControlSequence(buf + 'C')
+
+ def D(self, proto, handler, buf):
+ if buf == '\x1b[':
+ handler.keystrokeReceived(proto.LEFT_ARROW, None)
+ else:
+ handler.unhandledControlSequence(buf + 'D')
+
+ def E(self, proto, handler, buf):
+ if buf == '\x1b[':
+ handler.keystrokeReceived(proto.NUMPAD_MIDDLE, None)
+ else:
+ handler.unhandledControlSequence(buf + 'E')
+
+ def F(self, proto, handler, buf):
+ if buf == '\x1b[':
+ handler.keystrokeReceived(proto.END, None)
+ else:
+ handler.unhandledControlSequence(buf + 'F')
+
+ def H(self, proto, handler, buf):
+ if buf == '\x1b[':
+ handler.keystrokeReceived(proto.HOME, None)
+ else:
+ handler.unhandledControlSequence(buf + 'H')
+
+ def R(self, proto, handler, buf):
+ if not proto._cursorReports:
+ handler.unhandledControlSequence(buf + 'R')
+ elif buf.startswith('\x1b['):
+ report = buf[2:]
+ parts = report.split(';')
+ if len(parts) != 2:
+ handler.unhandledControlSequence(buf + 'R')
+ else:
+ Pl, Pc = parts
+ try:
+ Pl, Pc = int(Pl), int(Pc)
+ except ValueError:
+ handler.unhandledControlSequence(buf + 'R')
+ else:
+ d = proto._cursorReports.pop(0)
+ d.callback((Pc - 1, Pl - 1))
+ else:
+ handler.unhandledControlSequence(buf + 'R')
+
+ def Z(self, proto, handler, buf):
+ if buf == '\x1b[':
+ handler.keystrokeReceived(proto.TAB, proto.SHIFT)
+ else:
+ handler.unhandledControlSequence(buf + 'Z')
+
+ def tilde(self, proto, handler, buf):
+ map = {1: proto.HOME, 2: proto.INSERT, 3: proto.DELETE,
+ 4: proto.END, 5: proto.PGUP, 6: proto.PGDN,
+
+ 15: proto.F5, 17: proto.F6, 18: proto.F7,
+ 19: proto.F8, 20: proto.F9, 21: proto.F10,
+ 23: proto.F11, 24: proto.F12}
+
+ if buf.startswith('\x1b['):
+ ch = buf[2:]
+ try:
+ v = int(ch)
+ except ValueError:
+ handler.unhandledControlSequence(buf + '~')
+ else:
+ symbolic = map.get(v)
+ if symbolic is not None:
+ handler.keystrokeReceived(map[v], None)
+ else:
+ handler.unhandledControlSequence(buf + '~')
+ else:
+ handler.unhandledControlSequence(buf + '~')
+
+ controlSequenceParser = ControlSequenceParser()
+
+ # ITerminalTransport
+ def cursorUp(self, n=1):
+ assert n >= 1
+ self.cursorPos.y = max(self.cursorPos.y - n, 0)
+ self.write('\x1b[%dA' % (n,))
+
+ def cursorDown(self, n=1):
+ assert n >= 1
+ self.cursorPos.y = min(self.cursorPos.y + n, self.termSize.y - 1)
+ self.write('\x1b[%dB' % (n,))
+
+ def cursorForward(self, n=1):
+ assert n >= 1
+ self.cursorPos.x = min(self.cursorPos.x + n, self.termSize.x - 1)
+ self.write('\x1b[%dC' % (n,))
+
+ def cursorBackward(self, n=1):
+ assert n >= 1
+ self.cursorPos.x = max(self.cursorPos.x - n, 0)
+ self.write('\x1b[%dD' % (n,))
+
+ def cursorPosition(self, column, line):
+ self.write('\x1b[%d;%dH' % (line + 1, column + 1))
+
+ def cursorHome(self):
+ self.cursorPos.x = self.cursorPos.y = 0
+ self.write('\x1b[H')
+
+ def index(self):
+ self.cursorPos.y = min(self.cursorPos.y + 1, self.termSize.y - 1)
+ self.write('\x1bD')
+
+ def reverseIndex(self):
+ self.cursorPos.y = max(self.cursorPos.y - 1, 0)
+ self.write('\x1bM')
+
+ def nextLine(self):
+ self.cursorPos.x = 0
+ self.cursorPos.y = min(self.cursorPos.y + 1, self.termSize.y - 1)
+ self.write('\n')
+
+ def saveCursor(self):
+ self._savedCursorPos = Vector(self.cursorPos.x, self.cursorPos.y)
+ self.write('\x1b7')
+
+ def restoreCursor(self):
+ self.cursorPos = self._savedCursorPos
+ del self._savedCursorPos
+ self.write('\x1b8')
+
+ def setModes(self, modes):
+ # XXX Support ANSI-Compatible private modes
+ self.write('\x1b[%sh' % (';'.join(map(str, modes)),))
+
+ def setPrivateModes(self, modes):
+ self.write('\x1b[?%sh' % (';'.join(map(str, modes)),))
+
+ def resetModes(self, modes):
+ # XXX Support ANSI-Compatible private modes
+ self.write('\x1b[%sl' % (';'.join(map(str, modes)),))
+
+ def resetPrivateModes(self, modes):
+ self.write('\x1b[?%sl' % (';'.join(map(str, modes)),))
+
+ def applicationKeypadMode(self):
+ self.write('\x1b=')
+
+ def numericKeypadMode(self):
+ self.write('\x1b>')
+
+ def selectCharacterSet(self, charSet, which):
+ # XXX Rewrite these as dict lookups
+ if which == G0:
+ which = '('
+ elif which == G1:
+ which = ')'
+ else:
+ raise ValueError("`which' argument to selectCharacterSet must be G0 or G1")
+ if charSet == CS_UK:
+ charSet = 'A'
+ elif charSet == CS_US:
+ charSet = 'B'
+ elif charSet == CS_DRAWING:
+ charSet = '0'
+ elif charSet == CS_ALTERNATE:
+ charSet = '1'
+ elif charSet == CS_ALTERNATE_SPECIAL:
+ charSet = '2'
+ else:
+ raise ValueError("Invalid `charSet' argument to selectCharacterSet")
+ self.write('\x1b' + which + charSet)
+
+ def shiftIn(self):
+ self.write('\x15')
+
+ def shiftOut(self):
+ self.write('\x14')
+
+ def singleShift2(self):
+ self.write('\x1bN')
+
+ def singleShift3(self):
+ self.write('\x1bO')
+
+ def selectGraphicRendition(self, *attributes):
+ attrs = []
+ for a in attributes:
+ attrs.append(a)
+ self.write('\x1b[%sm' % (';'.join(attrs),))
+
+ def horizontalTabulationSet(self):
+ self.write('\x1bH')
+
+ def tabulationClear(self):
+ self.write('\x1b[q')
+
+ def tabulationClearAll(self):
+ self.write('\x1b[3q')
+
+ def doubleHeightLine(self, top=True):
+ if top:
+ self.write('\x1b#3')
+ else:
+ self.write('\x1b#4')
+
+ def singleWidthLine(self):
+ self.write('\x1b#5')
+
+ def doubleWidthLine(self):
+ self.write('\x1b#6')
+
+ def eraseToLineEnd(self):
+ self.write('\x1b[K')
+
+ def eraseToLineBeginning(self):
+ self.write('\x1b[1K')
+
+ def eraseLine(self):
+ self.write('\x1b[2K')
+
+ def eraseToDisplayEnd(self):
+ self.write('\x1b[J')
+
+ def eraseToDisplayBeginning(self):
+ self.write('\x1b[1J')
+
+ def eraseDisplay(self):
+ self.write('\x1b[2J')
+
+ def deleteCharacter(self, n=1):
+ self.write('\x1b[%dP' % (n,))
+
+ def insertLine(self, n=1):
+ self.write('\x1b[%dL' % (n,))
+
+ def deleteLine(self, n=1):
+ self.write('\x1b[%dM' % (n,))
+
+ def setScrollRegion(self, first=None, last=None):
+ if first is not None:
+ first = '%d' % (first,)
+ else:
+ first = ''
+ if last is not None:
+ last = '%d' % (last,)
+ else:
+ last = ''
+ self.write('\x1b[%s;%sr' % (first, last))
+
+ def resetScrollRegion(self):
+ self.setScrollRegion()
+
+ def reportCursorPosition(self):
+ d = defer.Deferred()
+ self._cursorReports.append(d)
+ self.write('\x1b[6n')
+ return d
+
+ def reset(self):
+ self.cursorPos.x = self.cursorPos.y = 0
+ try:
+ del self._savedCursorPos
+ except AttributeError:
+ pass
+ self.write('\x1bc')
+
+ # ITransport
+ def write(self, bytes):
+ if bytes:
+ self.lastWrite = bytes
+ self.transport.write('\r\n'.join(bytes.split('\n')))
+
+ def writeSequence(self, bytes):
+ self.write(''.join(bytes))
+
+ def loseConnection(self):
+ self.reset()
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ if self.terminalProtocol is not None:
+ try:
+ self.terminalProtocol.connectionLost(reason)
+ finally:
+ self.terminalProtocol = None
+# Add symbolic names for function keys
+for name, const in zip(_KEY_NAMES, FUNCTION_KEYS):
+ setattr(ServerProtocol, name, const)
+
+
+
+class ClientProtocol(protocol.Protocol):
+
+ terminalFactory = None
+ terminal = None
+
+ state = 'data'
+
+ _escBuf = None
+
+ _shorts = {
+ 'D': 'index',
+ 'M': 'reverseIndex',
+ 'E': 'nextLine',
+ '7': 'saveCursor',
+ '8': 'restoreCursor',
+ '=': 'applicationKeypadMode',
+ '>': 'numericKeypadMode',
+ 'N': 'singleShift2',
+ 'O': 'singleShift3',
+ 'H': 'horizontalTabulationSet',
+ 'c': 'reset'}
+
+ _longs = {
+ '[': 'bracket-escape',
+ '(': 'select-g0',
+ ')': 'select-g1',
+ '#': 'select-height-width'}
+
+ _charsets = {
+ 'A': CS_UK,
+ 'B': CS_US,
+ '0': CS_DRAWING,
+ '1': CS_ALTERNATE,
+ '2': CS_ALTERNATE_SPECIAL}
+
+ # Factory who instantiated me
+ factory = None
+
+ def __init__(self, terminalFactory=None, *a, **kw):
+ """
+ @param terminalFactory: A callable which will be invoked with
+ *a, **kw and should return an ITerminalTransport provider.
+ This will be invoked when this ClientProtocol establishes a
+ connection.
+
+ @param a: Any positional arguments to pass to terminalFactory.
+ @param kw: Any keyword arguments to pass to terminalFactory.
+ """
+ # assert terminalFactory is None or ITerminalTransport.implementedBy(terminalFactory), "ClientProtocol.__init__ must be passed an ITerminalTransport implementor"
+ if terminalFactory is not None:
+ self.terminalFactory = terminalFactory
+ self.terminalArgs = a
+ self.terminalKwArgs = kw
+
+ def connectionMade(self):
+ if self.terminalFactory is not None:
+ self.terminal = self.terminalFactory(*self.terminalArgs, **self.terminalKwArgs)
+ self.terminal.factory = self.factory
+ self.terminal.makeConnection(self)
+
+ def connectionLost(self, reason):
+ if self.terminal is not None:
+ try:
+ self.terminal.connectionLost(reason)
+ finally:
+ del self.terminal
+
+ def dataReceived(self, bytes):
+ """
+ Parse the given data from a terminal server, dispatching to event
+ handlers defined by C{self.terminal}.
+ """
+ toWrite = []
+ for b in bytes:
+ if self.state == 'data':
+ if b == '\x1b':
+ if toWrite:
+ self.terminal.write(''.join(toWrite))
+ del toWrite[:]
+ self.state = 'escaped'
+ elif b == '\x14':
+ if toWrite:
+ self.terminal.write(''.join(toWrite))
+ del toWrite[:]
+ self.terminal.shiftOut()
+ elif b == '\x15':
+ if toWrite:
+ self.terminal.write(''.join(toWrite))
+ del toWrite[:]
+ self.terminal.shiftIn()
+ elif b == '\x08':
+ if toWrite:
+ self.terminal.write(''.join(toWrite))
+ del toWrite[:]
+ self.terminal.cursorBackward()
+ else:
+ toWrite.append(b)
+ elif self.state == 'escaped':
+ fName = self._shorts.get(b)
+ if fName is not None:
+ self.state = 'data'
+ getattr(self.terminal, fName)()
+ else:
+ state = self._longs.get(b)
+ if state is not None:
+ self.state = state
+ else:
+ self.terminal.unhandledControlSequence('\x1b' + b)
+ self.state = 'data'
+ elif self.state == 'bracket-escape':
+ if self._escBuf is None:
+ self._escBuf = []
+ if b.isalpha() or b == '~':
+ self._handleControlSequence(''.join(self._escBuf), b)
+ del self._escBuf
+ self.state = 'data'
+ else:
+ self._escBuf.append(b)
+ elif self.state == 'select-g0':
+ self.terminal.selectCharacterSet(self._charsets.get(b, b), G0)
+ self.state = 'data'
+ elif self.state == 'select-g1':
+ self.terminal.selectCharacterSet(self._charsets.get(b, b), G1)
+ self.state = 'data'
+ elif self.state == 'select-height-width':
+ self._handleHeightWidth(b)
+ self.state = 'data'
+ else:
+ raise ValueError("Illegal state")
+ if toWrite:
+ self.terminal.write(''.join(toWrite))
+
+
+ def _handleControlSequence(self, buf, terminal):
+ f = getattr(self.controlSequenceParser, CST.get(terminal, terminal), None)
+ if f is None:
+ self.terminal.unhandledControlSequence('\x1b[' + buf + terminal)
+ else:
+ f(self, self.terminal, buf)
+
+ class ControlSequenceParser:
+ def _makeSimple(ch, fName):
+ n = 'cursor' + fName
+ def simple(self, proto, handler, buf):
+ if not buf:
+ getattr(handler, n)(1)
+ else:
+ try:
+ m = int(buf)
+ except ValueError:
+ handler.unhandledControlSequence('\x1b[' + buf + ch)
+ else:
+ getattr(handler, n)(m)
+ return simple
+ for (ch, fName) in (('A', 'Up'),
+ ('B', 'Down'),
+ ('C', 'Forward'),
+ ('D', 'Backward')):
+ exec ch + " = _makeSimple(ch, fName)"
+ del _makeSimple
+
+ def h(self, proto, handler, buf):
+ # XXX - Handle '?' to introduce ANSI-Compatible private modes.
+ try:
+ modes = map(int, buf.split(';'))
+ except ValueError:
+ handler.unhandledControlSequence('\x1b[' + buf + 'h')
+ else:
+ handler.setModes(modes)
+
+ def l(self, proto, handler, buf):
+ # XXX - Handle '?' to introduce ANSI-Compatible private modes.
+ try:
+ modes = map(int, buf.split(';'))
+ except ValueError:
+ handler.unhandledControlSequence('\x1b[' + buf + 'l')
+ else:
+ handler.resetModes(modes)
+
+ def r(self, proto, handler, buf):
+ parts = buf.split(';')
+ if len(parts) == 1:
+ handler.setScrollRegion(None, None)
+ elif len(parts) == 2:
+ try:
+ if parts[0]:
+ pt = int(parts[0])
+ else:
+ pt = None
+ if parts[1]:
+ pb = int(parts[1])
+ else:
+ pb = None
+ except ValueError:
+ handler.unhandledControlSequence('\x1b[' + buf + 'r')
+ else:
+ handler.setScrollRegion(pt, pb)
+ else:
+ handler.unhandledControlSequence('\x1b[' + buf + 'r')
+
+ def K(self, proto, handler, buf):
+ if not buf:
+ handler.eraseToLineEnd()
+ elif buf == '1':
+ handler.eraseToLineBeginning()
+ elif buf == '2':
+ handler.eraseLine()
+ else:
+ handler.unhandledControlSequence('\x1b[' + buf + 'K')
+
+ def H(self, proto, handler, buf):
+ handler.cursorHome()
+
+ def J(self, proto, handler, buf):
+ if not buf:
+ handler.eraseToDisplayEnd()
+ elif buf == '1':
+ handler.eraseToDisplayBeginning()
+ elif buf == '2':
+ handler.eraseDisplay()
+ else:
+ handler.unhandledControlSequence('\x1b[' + buf + 'J')
+
+ def P(self, proto, handler, buf):
+ if not buf:
+ handler.deleteCharacter(1)
+ else:
+ try:
+ n = int(buf)
+ except ValueError:
+ handler.unhandledControlSequence('\x1b[' + buf + 'P')
+ else:
+ handler.deleteCharacter(n)
+
+ def L(self, proto, handler, buf):
+ if not buf:
+ handler.insertLine(1)
+ else:
+ try:
+ n = int(buf)
+ except ValueError:
+ handler.unhandledControlSequence('\x1b[' + buf + 'L')
+ else:
+ handler.insertLine(n)
+
+ def M(self, proto, handler, buf):
+ if not buf:
+ handler.deleteLine(1)
+ else:
+ try:
+ n = int(buf)
+ except ValueError:
+ handler.unhandledControlSequence('\x1b[' + buf + 'M')
+ else:
+ handler.deleteLine(n)
+
+ def n(self, proto, handler, buf):
+ if buf == '6':
+ x, y = handler.reportCursorPosition()
+ proto.transport.write('\x1b[%d;%dR' % (x + 1, y + 1))
+ else:
+ handler.unhandledControlSequence('\x1b[' + buf + 'n')
+
+ def m(self, proto, handler, buf):
+ if not buf:
+ handler.selectGraphicRendition(NORMAL)
+ else:
+ attrs = []
+ for a in buf.split(';'):
+ try:
+ a = int(a)
+ except ValueError:
+ pass
+ attrs.append(a)
+ handler.selectGraphicRendition(*attrs)
+
+ controlSequenceParser = ControlSequenceParser()
+
+ def _handleHeightWidth(self, b):
+ if b == '3':
+ self.terminal.doubleHeightLine(True)
+ elif b == '4':
+ self.terminal.doubleHeightLine(False)
+ elif b == '5':
+ self.terminal.singleWidthLine()
+ elif b == '6':
+ self.terminal.doubleWidthLine()
+ else:
+ self.terminal.unhandledControlSequence('\x1b#' + b)
+
+
+__all__ = [
+ # Interfaces
+ 'ITerminalProtocol', 'ITerminalTransport',
+
+ # Symbolic constants
+ 'modes', 'privateModes', 'FUNCTION_KEYS',
+
+ 'CS_US', 'CS_UK', 'CS_DRAWING', 'CS_ALTERNATE', 'CS_ALTERNATE_SPECIAL',
+ 'G0', 'G1', 'G2', 'G3',
+
+ 'UNDERLINE', 'REVERSE_VIDEO', 'BLINK', 'BOLD', 'NORMAL',
+
+ # Protocol classes
+ 'ServerProtocol', 'ClientProtocol']
diff --git a/vendor/Twisted-10.0.0/twisted/conch/insults/text.py b/vendor/Twisted-10.0.0/twisted/conch/insults/text.py
new file mode 100644
index 0000000000..18b8b5f6b9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/insults/text.py
@@ -0,0 +1,186 @@
+# -*- test-case-name: twisted.conch.test.test_text -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Character attribute manipulation API
+
+This module provides a domain-specific language (using Python syntax)
+for the creation of text with additional display attributes associated
+with it. It is intended as an alternative to manually building up
+strings containing ECMA 48 character attribute control codes. It
+currently supports foreground and background colors (black, red,
+green, yellow, blue, magenta, cyan, and white), intensity selection,
+underlining, blinking and reverse video. Character set selection
+support is planned.
+
+Character attributes are specified by using two Python operations:
+attribute lookup and indexing. For example, the string \"Hello
+world\" with red foreground and all other attributes set to their
+defaults, assuming the name twisted.conch.insults.text.attributes has
+been imported and bound to the name \"A\" (with the statement C{from
+twisted.conch.insults.text import attributes as A}, for example) one
+uses this expression::
+
+ | A.fg.red[\"Hello world\"]
+
+Other foreground colors are set by substituting their name for
+\"red\". To set both a foreground and a background color, this
+expression is used::
+
+ | A.fg.red[A.bg.green[\"Hello world\"]]
+
+Note that either A.bg.green can be nested within A.fg.red or vice
+versa. Also note that multiple items can be nested within a single
+index operation by separating them with commas::
+
+ | A.bg.green[A.fg.red[\"Hello\"], " ", A.fg.blue[\"world\"]]
+
+Other character attributes are set in a similar fashion. To specify a
+blinking version of the previous expression::
+
+ | A.blink[A.bg.green[A.fg.red[\"Hello\"], " ", A.fg.blue[\"world\"]]]
+
+C{A.reverseVideo}, C{A.underline}, and C{A.bold} are also valid.
+
+A third operation is actually supported: unary negation. This turns
+off an attribute when an enclosing expression would otherwise have
+caused it to be on. For example::
+
+ | A.underline[A.fg.red[\"Hello\", -A.underline[\" world\"]]]
+
+@author: Jp Calderone
+"""
+
+from twisted.conch.insults import helper, insults
+
+class _Attribute(object):
+ def __init__(self):
+ self.children = []
+
+ def __getitem__(self, item):
+ assert isinstance(item, (list, tuple, _Attribute, str))
+ if isinstance(item, (list, tuple)):
+ self.children.extend(item)
+ else:
+ self.children.append(item)
+ return self
+
+ def serialize(self, write, attrs=None):
+ if attrs is None:
+ attrs = helper.CharacterAttribute()
+ for ch in self.children:
+ if isinstance(ch, _Attribute):
+ ch.serialize(write, attrs.copy())
+ else:
+ write(attrs.toVT102())
+ write(ch)
+
+class _NormalAttr(_Attribute):
+ def serialize(self, write, attrs):
+ attrs.__init__()
+ super(_NormalAttr, self).serialize(write, attrs)
+
+class _OtherAttr(_Attribute):
+ def __init__(self, attrname, attrvalue):
+ self.attrname = attrname
+ self.attrvalue = attrvalue
+ self.children = []
+
+ def __neg__(self):
+ result = _OtherAttr(self.attrname, not self.attrvalue)
+ result.children.extend(self.children)
+ return result
+
+ def serialize(self, write, attrs):
+ attrs = attrs.wantOne(**{self.attrname: self.attrvalue})
+ super(_OtherAttr, self).serialize(write, attrs)
+
+class _ColorAttr(_Attribute):
+ def __init__(self, color, ground):
+ self.color = color
+ self.ground = ground
+ self.children = []
+
+ def serialize(self, write, attrs):
+ attrs = attrs.wantOne(**{self.ground: self.color})
+ super(_ColorAttr, self).serialize(write, attrs)
+
+class _ForegroundColorAttr(_ColorAttr):
+ def __init__(self, color):
+ super(_ForegroundColorAttr, self).__init__(color, 'foreground')
+
+class _BackgroundColorAttr(_ColorAttr):
+ def __init__(self, color):
+ super(_BackgroundColorAttr, self).__init__(color, 'background')
+
+class CharacterAttributes(object):
+ class _ColorAttribute(object):
+ def __init__(self, ground):
+ self.ground = ground
+
+ attrs = {
+ 'black': helper.BLACK,
+ 'red': helper.RED,
+ 'green': helper.GREEN,
+ 'yellow': helper.YELLOW,
+ 'blue': helper.BLUE,
+ 'magenta': helper.MAGENTA,
+ 'cyan': helper.CYAN,
+ 'white': helper.WHITE}
+
+ def __getattr__(self, name):
+ try:
+ return self.ground(self.attrs[name])
+ except KeyError:
+ raise AttributeError(name)
+
+ fg = _ColorAttribute(_ForegroundColorAttr)
+ bg = _ColorAttribute(_BackgroundColorAttr)
+
+ attrs = {
+ 'bold': insults.BOLD,
+ 'blink': insults.BLINK,
+ 'underline': insults.UNDERLINE,
+ 'reverseVideo': insults.REVERSE_VIDEO}
+
+ def __getattr__(self, name):
+ if name == 'normal':
+ return _NormalAttr()
+ if name in self.attrs:
+ return _OtherAttr(name, True)
+ raise AttributeError(name)
+
+def flatten(output, attrs):
+ """Serialize a sequence of characters with attribute information
+
+ The resulting string can be interpreted by VT102-compatible
+ terminals so that the contained characters are displayed and, for
+ those attributes which the terminal supports, have the attributes
+ specified in the input.
+
+ For example, if your terminal is VT102 compatible, you might run
+ this for a colorful variation on the \"hello world\" theme::
+
+ | from twisted.conch.insults.text import flatten, attributes as A
+ | from twisted.conch.insults.helper import CharacterAttribute
+ | print flatten(
+ | A.normal[A.bold[A.fg.red['He'], A.fg.green['ll'], A.fg.magenta['o'], ' ',
+ | A.fg.yellow['Wo'], A.fg.blue['rl'], A.fg.cyan['d!']]],
+ | CharacterAttribute())
+
+ @param output: Object returned by accessing attributes of the
+ module-level attributes object.
+
+ @param attrs: A L{twisted.conch.insults.helper.CharacterAttribute}
+ instance
+
+ @return: A VT102-friendly string
+ """
+ L = []
+ output.serialize(L.append, attrs)
+ return ''.join(L)
+
+attributes = CharacterAttributes()
+
+__all__ = ['attributes', 'flatten']
diff --git a/vendor/Twisted-10.0.0/twisted/conch/insults/window.py b/vendor/Twisted-10.0.0/twisted/conch/insults/window.py
new file mode 100644
index 0000000000..51cdebaa4f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/insults/window.py
@@ -0,0 +1,864 @@
+# -*- test-case-name: twisted.conch.test.test_window -*-
+
+"""
+Simple insults-based widget library
+
+@author: Jp Calderone
+"""
+
+import array
+
+from twisted.conch.insults import insults, helper
+from twisted.python import text as tptext
+
+class YieldFocus(Exception):
+ """Input focus manipulation exception
+ """
+
+class BoundedTerminalWrapper(object):
+ def __init__(self, terminal, width, height, xoff, yoff):
+ self.width = width
+ self.height = height
+ self.xoff = xoff
+ self.yoff = yoff
+ self.terminal = terminal
+ self.cursorForward = terminal.cursorForward
+ self.selectCharacterSet = terminal.selectCharacterSet
+ self.selectGraphicRendition = terminal.selectGraphicRendition
+ self.saveCursor = terminal.saveCursor
+ self.restoreCursor = terminal.restoreCursor
+
+ def cursorPosition(self, x, y):
+ return self.terminal.cursorPosition(
+ self.xoff + min(self.width, x),
+ self.yoff + min(self.height, y)
+ )
+
+ def cursorHome(self):
+ return self.terminal.cursorPosition(
+ self.xoff, self.yoff)
+
+ def write(self, bytes):
+ return self.terminal.write(bytes)
+
+class Widget(object):
+ focused = False
+ parent = None
+ dirty = False
+ width = height = None
+
+ def repaint(self):
+ if not self.dirty:
+ self.dirty = True
+ if self.parent is not None and not self.parent.dirty:
+ self.parent.repaint()
+
+ def filthy(self):
+ self.dirty = True
+
+ def redraw(self, width, height, terminal):
+ self.filthy()
+ self.draw(width, height, terminal)
+
+ def draw(self, width, height, terminal):
+ if width != self.width or height != self.height or self.dirty:
+ self.width = width
+ self.height = height
+ self.dirty = False
+ self.render(width, height, terminal)
+
+ def render(self, width, height, terminal):
+ pass
+
+ def sizeHint(self):
+ return None
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == '\t':
+ self.tabReceived(modifier)
+ elif keyID == '\x7f':
+ self.backspaceReceived()
+ elif keyID in insults.FUNCTION_KEYS:
+ self.functionKeyReceived(keyID, modifier)
+ else:
+ self.characterReceived(keyID, modifier)
+
+ def tabReceived(self, modifier):
+ # XXX TODO - Handle shift+tab
+ raise YieldFocus()
+
+ def focusReceived(self):
+ """Called when focus is being given to this widget.
+
+ May raise YieldFocus is this widget does not want focus.
+ """
+ self.focused = True
+ self.repaint()
+
+ def focusLost(self):
+ self.focused = False
+ self.repaint()
+
+ def backspaceReceived(self):
+ pass
+
+ def functionKeyReceived(self, keyID, modifier):
+ func = getattr(self, 'func_' + keyID.name, None)
+ if func is not None:
+ func(modifier)
+
+ def characterReceived(self, keyID, modifier):
+ pass
+
+class ContainerWidget(Widget):
+ """
+ @ivar focusedChild: The contained widget which currently has
+ focus, or None.
+ """
+ focusedChild = None
+ focused = False
+
+ def __init__(self):
+ Widget.__init__(self)
+ self.children = []
+
+ def addChild(self, child):
+ assert child.parent is None
+ child.parent = self
+ self.children.append(child)
+ if self.focusedChild is None and self.focused:
+ try:
+ child.focusReceived()
+ except YieldFocus:
+ pass
+ else:
+ self.focusedChild = child
+ self.repaint()
+
+ def remChild(self, child):
+ assert child.parent is self
+ child.parent = None
+ self.children.remove(child)
+ self.repaint()
+
+ def filthy(self):
+ for ch in self.children:
+ ch.filthy()
+ Widget.filthy(self)
+
+ def render(self, width, height, terminal):
+ for ch in self.children:
+ ch.draw(width, height, terminal)
+
+ def changeFocus(self):
+ self.repaint()
+
+ if self.focusedChild is not None:
+ self.focusedChild.focusLost()
+ focusedChild = self.focusedChild
+ self.focusedChild = None
+ try:
+ curFocus = self.children.index(focusedChild) + 1
+ except ValueError:
+ raise YieldFocus()
+ else:
+ curFocus = 0
+ while curFocus < len(self.children):
+ try:
+ self.children[curFocus].focusReceived()
+ except YieldFocus:
+ curFocus += 1
+ else:
+ self.focusedChild = self.children[curFocus]
+ return
+ # None of our children wanted focus
+ raise YieldFocus()
+
+
+ def focusReceived(self):
+ self.changeFocus()
+ self.focused = True
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ if self.focusedChild is not None:
+ try:
+ self.focusedChild.keystrokeReceived(keyID, modifier)
+ except YieldFocus:
+ self.changeFocus()
+ self.repaint()
+ else:
+ Widget.keystrokeReceived(self, keyID, modifier)
+
+
+class TopWindow(ContainerWidget):
+ """
+ A top-level container object which provides focus wrap-around and paint
+ scheduling.
+
+ @ivar painter: A no-argument callable which will be invoked when this
+ widget needs to be redrawn.
+
+ @ivar scheduler: A one-argument callable which will be invoked with a
+ no-argument callable and should arrange for it to invoked at some point in
+ the near future. The no-argument callable will cause this widget and all
+ its children to be redrawn. It is typically beneficial for the no-argument
+ callable to be invoked at the end of handling for whatever event is
+ currently active; for example, it might make sense to call it at the end of
+ L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
+ Note, however, that since calls to this may also be made in response to no
+ apparent event, arrangements should be made for the function to be called
+ even if an event handler such as C{keystrokeReceived} is not on the call
+ stack (eg, using C{reactor.callLater} with a short timeout).
+ """
+ focused = True
+
+ def __init__(self, painter, scheduler):
+ ContainerWidget.__init__(self)
+ self.painter = painter
+ self.scheduler = scheduler
+
+ _paintCall = None
+ def repaint(self):
+ if self._paintCall is None:
+ self._paintCall = object()
+ self.scheduler(self._paint)
+ ContainerWidget.repaint(self)
+
+ def _paint(self):
+ self._paintCall = None
+ self.painter()
+
+ def changeFocus(self):
+ try:
+ ContainerWidget.changeFocus(self)
+ except YieldFocus:
+ try:
+ ContainerWidget.changeFocus(self)
+ except YieldFocus:
+ pass
+
+ def keystrokeReceived(self, keyID, modifier):
+ try:
+ ContainerWidget.keystrokeReceived(self, keyID, modifier)
+ except YieldFocus:
+ self.changeFocus()
+
+
+class AbsoluteBox(ContainerWidget):
+ def moveChild(self, child, x, y):
+ for n in range(len(self.children)):
+ if self.children[n][0] is child:
+ self.children[n] = (child, x, y)
+ break
+ else:
+ raise ValueError("No such child", child)
+
+ def render(self, width, height, terminal):
+ for (ch, x, y) in self.children:
+ wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
+ ch.draw(width, height, wrap)
+
+
+class _Box(ContainerWidget):
+ TOP, CENTER, BOTTOM = range(3)
+
+ def __init__(self, gravity=CENTER):
+ ContainerWidget.__init__(self)
+ self.gravity = gravity
+
+ def sizeHint(self):
+ height = 0
+ width = 0
+ for ch in self.children:
+ hint = ch.sizeHint()
+ if hint is None:
+ hint = (None, None)
+
+ if self.variableDimension == 0:
+ if hint[0] is None:
+ width = None
+ elif width is not None:
+ width += hint[0]
+ if hint[1] is None:
+ height = None
+ elif height is not None:
+ height = max(height, hint[1])
+ else:
+ if hint[0] is None:
+ width = None
+ elif width is not None:
+ width = max(width, hint[0])
+ if hint[1] is None:
+ height = None
+ elif height is not None:
+ height += hint[1]
+
+ return width, height
+
+
+ def render(self, width, height, terminal):
+ if not self.children:
+ return
+
+ greedy = 0
+ wants = []
+ for ch in self.children:
+ hint = ch.sizeHint()
+ if hint is None:
+ hint = (None, None)
+ if hint[self.variableDimension] is None:
+ greedy += 1
+ wants.append(hint[self.variableDimension])
+
+ length = (width, height)[self.variableDimension]
+ totalWant = sum([w for w in wants if w is not None])
+ if greedy:
+ leftForGreedy = int((length - totalWant) / greedy)
+
+ widthOffset = heightOffset = 0
+
+ for want, ch in zip(wants, self.children):
+ if want is None:
+ want = leftForGreedy
+
+ subWidth, subHeight = width, height
+ if self.variableDimension == 0:
+ subWidth = want
+ else:
+ subHeight = want
+
+ wrap = BoundedTerminalWrapper(
+ terminal,
+ subWidth,
+ subHeight,
+ widthOffset,
+ heightOffset,
+ )
+ ch.draw(subWidth, subHeight, wrap)
+ if self.variableDimension == 0:
+ widthOffset += want
+ else:
+ heightOffset += want
+
+
+class HBox(_Box):
+ variableDimension = 0
+
+class VBox(_Box):
+ variableDimension = 1
+
+
+class Packer(ContainerWidget):
+ def render(self, width, height, terminal):
+ if not self.children:
+ return
+
+ root = int(len(self.children) ** 0.5 + 0.5)
+ boxes = [VBox() for n in range(root)]
+ for n, ch in enumerate(self.children):
+ boxes[n % len(boxes)].addChild(ch)
+ h = HBox()
+ map(h.addChild, boxes)
+ h.render(width, height, terminal)
+
+
+class Canvas(Widget):
+ focused = False
+
+ contents = None
+
+ def __init__(self):
+ Widget.__init__(self)
+ self.resize(1, 1)
+
+ def resize(self, width, height):
+ contents = array.array('c', ' ' * width * height)
+ if self.contents is not None:
+ for x in range(min(width, self._width)):
+ for y in range(min(height, self._height)):
+ contents[width * y + x] = self[x, y]
+ self.contents = contents
+ self._width = width
+ self._height = height
+ if self.x >= width:
+ self.x = width - 1
+ if self.y >= height:
+ self.y = height - 1
+
+ def __getitem__(self, (x, y)):
+ return self.contents[(self._width * y) + x]
+
+ def __setitem__(self, (x, y), value):
+ self.contents[(self._width * y) + x] = value
+
+ def clear(self):
+ self.contents = array.array('c', ' ' * len(self.contents))
+
+ def render(self, width, height, terminal):
+ if not width or not height:
+ return
+
+ if width != self._width or height != self._height:
+ self.resize(width, height)
+ for i in range(height):
+ terminal.cursorPosition(0, i)
+ terminal.write(''.join(self.contents[self._width * i:self._width * i + self._width])[:width])
+
+
+def horizontalLine(terminal, y, left, right):
+ terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
+ terminal.cursorPosition(left, y)
+ terminal.write(chr(0161) * (right - left))
+ terminal.selectCharacterSet(insults.CS_US, insults.G0)
+
+def verticalLine(terminal, x, top, bottom):
+ terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
+ for n in xrange(top, bottom):
+ terminal.cursorPosition(x, n)
+ terminal.write(chr(0170))
+ terminal.selectCharacterSet(insults.CS_US, insults.G0)
+
+
+def rectangle(terminal, (top, left), (width, height)):
+ terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
+
+ terminal.cursorPosition(top, left)
+ terminal.write(chr(0154))
+ terminal.write(chr(0161) * (width - 2))
+ terminal.write(chr(0153))
+ for n in range(height - 2):
+ terminal.cursorPosition(left, top + n + 1)
+ terminal.write(chr(0170))
+ terminal.cursorForward(width - 2)
+ terminal.write(chr(0170))
+ terminal.cursorPosition(0, top + height - 1)
+ terminal.write(chr(0155))
+ terminal.write(chr(0161) * (width - 2))
+ terminal.write(chr(0152))
+
+ terminal.selectCharacterSet(insults.CS_US, insults.G0)
+
+class Border(Widget):
+ def __init__(self, containee):
+ Widget.__init__(self)
+ self.containee = containee
+ self.containee.parent = self
+
+ def focusReceived(self):
+ return self.containee.focusReceived()
+
+ def focusLost(self):
+ return self.containee.focusLost()
+
+ def keystrokeReceived(self, keyID, modifier):
+ return self.containee.keystrokeReceived(keyID, modifier)
+
+ def sizeHint(self):
+ hint = self.containee.sizeHint()
+ if hint is None:
+ hint = (None, None)
+ if hint[0] is None:
+ x = None
+ else:
+ x = hint[0] + 2
+ if hint[1] is None:
+ y = None
+ else:
+ y = hint[1] + 2
+ return x, y
+
+ def filthy(self):
+ self.containee.filthy()
+ Widget.filthy(self)
+
+ def render(self, width, height, terminal):
+ if self.containee.focused:
+ terminal.write('\x1b[31m')
+ rectangle(terminal, (0, 0), (width, height))
+ terminal.write('\x1b[0m')
+ wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
+ self.containee.draw(width - 2, height - 2, wrap)
+
+
+class Button(Widget):
+ def __init__(self, label, onPress):
+ Widget.__init__(self)
+ self.label = label
+ self.onPress = onPress
+
+ def sizeHint(self):
+ return len(self.label), 1
+
+ def characterReceived(self, keyID, modifier):
+ if keyID == '\r':
+ self.onPress()
+
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ if self.focused:
+ terminal.write('\x1b[1m' + self.label + '\x1b[0m')
+ else:
+ terminal.write(self.label)
+
+class TextInput(Widget):
+ def __init__(self, maxwidth, onSubmit):
+ Widget.__init__(self)
+ self.onSubmit = onSubmit
+ self.maxwidth = maxwidth
+ self.buffer = ''
+ self.cursor = 0
+
+ def setText(self, text):
+ self.buffer = text[:self.maxwidth]
+ self.cursor = len(self.buffer)
+ self.repaint()
+
+ def func_LEFT_ARROW(self, modifier):
+ if self.cursor > 0:
+ self.cursor -= 1
+ self.repaint()
+
+ def func_RIGHT_ARROW(self, modifier):
+ if self.cursor < len(self.buffer):
+ self.cursor += 1
+ self.repaint()
+
+ def backspaceReceived(self):
+ if self.cursor > 0:
+ self.buffer = self.buffer[:self.cursor - 1] + self.buffer[self.cursor:]
+ self.cursor -= 1
+ self.repaint()
+
+ def characterReceived(self, keyID, modifier):
+ if keyID == '\r':
+ self.onSubmit(self.buffer)
+ else:
+ if len(self.buffer) < self.maxwidth:
+ self.buffer = self.buffer[:self.cursor] + keyID + self.buffer[self.cursor:]
+ self.cursor += 1
+ self.repaint()
+
+ def sizeHint(self):
+ return self.maxwidth + 1, 1
+
+ def render(self, width, height, terminal):
+ currentText = self._renderText()
+ terminal.cursorPosition(0, 0)
+ if self.focused:
+ terminal.write(currentText[:self.cursor])
+ cursor(terminal, currentText[self.cursor:self.cursor+1] or ' ')
+ terminal.write(currentText[self.cursor+1:])
+ terminal.write(' ' * (self.maxwidth - len(currentText) + 1))
+ else:
+ more = self.maxwidth - len(currentText)
+ terminal.write(currentText + '_' * more)
+
+ def _renderText(self):
+ return self.buffer
+
+class PasswordInput(TextInput):
+ def _renderText(self):
+ return '*' * len(self.buffer)
+
+class TextOutput(Widget):
+ text = ''
+
+ def __init__(self, size=None):
+ Widget.__init__(self)
+ self.size = size
+
+ def sizeHint(self):
+ return self.size
+
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ text = self.text[:width]
+ terminal.write(text + ' ' * (width - len(text)))
+
+ def setText(self, text):
+ self.text = text
+ self.repaint()
+
+ def focusReceived(self):
+ raise YieldFocus()
+
+class TextOutputArea(TextOutput):
+ WRAP, TRUNCATE = range(2)
+
+ def __init__(self, size=None, longLines=WRAP):
+ TextOutput.__init__(self, size)
+ self.longLines = longLines
+
+ def render(self, width, height, terminal):
+ n = 0
+ inputLines = self.text.splitlines()
+ outputLines = []
+ while inputLines:
+ if self.longLines == self.WRAP:
+ wrappedLines = tptext.greedyWrap(inputLines.pop(0), width)
+ outputLines.extend(wrappedLines or [''])
+ else:
+ outputLines.append(inputLines.pop(0)[:width])
+ if len(outputLines) >= height:
+ break
+ for n, L in enumerate(outputLines[:height]):
+ terminal.cursorPosition(0, n)
+ terminal.write(L)
+
+class Viewport(Widget):
+ _xOffset = 0
+ _yOffset = 0
+
+ def xOffset():
+ def get(self):
+ return self._xOffset
+ def set(self, value):
+ if self._xOffset != value:
+ self._xOffset = value
+ self.repaint()
+ return get, set
+ xOffset = property(*xOffset())
+
+ def yOffset():
+ def get(self):
+ return self._yOffset
+ def set(self, value):
+ if self._yOffset != value:
+ self._yOffset = value
+ self.repaint()
+ return get, set
+ yOffset = property(*yOffset())
+
+ _width = 160
+ _height = 24
+
+ def __init__(self, containee):
+ Widget.__init__(self)
+ self.containee = containee
+ self.containee.parent = self
+
+ self._buf = helper.TerminalBuffer()
+ self._buf.width = self._width
+ self._buf.height = self._height
+ self._buf.connectionMade()
+
+ def filthy(self):
+ self.containee.filthy()
+ Widget.filthy(self)
+
+ def render(self, width, height, terminal):
+ self.containee.draw(self._width, self._height, self._buf)
+
+ # XXX /Lame/
+ for y, line in enumerate(self._buf.lines[self._yOffset:self._yOffset + height]):
+ terminal.cursorPosition(0, y)
+ n = 0
+ for n, (ch, attr) in enumerate(line[self._xOffset:self._xOffset + width]):
+ if ch is self._buf.void:
+ ch = ' '
+ terminal.write(ch)
+ if n < width:
+ terminal.write(' ' * (width - n - 1))
+
+
+class _Scrollbar(Widget):
+ def __init__(self, onScroll):
+ Widget.__init__(self)
+ self.onScroll = onScroll
+ self.percent = 0.0
+
+ def smaller(self):
+ self.percent = min(1.0, max(0.0, self.onScroll(-1)))
+ self.repaint()
+
+ def bigger(self):
+ self.percent = min(1.0, max(0.0, self.onScroll(+1)))
+ self.repaint()
+
+
+class HorizontalScrollbar(_Scrollbar):
+ def sizeHint(self):
+ return (None, 1)
+
+ def func_LEFT_ARROW(self, modifier):
+ self.smaller()
+
+ def func_RIGHT_ARROW(self, modifier):
+ self.bigger()
+
+ _left = u'\N{BLACK LEFT-POINTING TRIANGLE}'
+ _right = u'\N{BLACK RIGHT-POINTING TRIANGLE}'
+ _bar = u'\N{LIGHT SHADE}'
+ _slider = u'\N{DARK SHADE}'
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ n = width - 3
+ before = int(n * self.percent)
+ after = n - before
+ me = self._left + (self._bar * before) + self._slider + (self._bar * after) + self._right
+ terminal.write(me.encode('utf-8'))
+
+
+class VerticalScrollbar(_Scrollbar):
+ def sizeHint(self):
+ return (1, None)
+
+ def func_UP_ARROW(self, modifier):
+ self.smaller()
+
+ def func_DOWN_ARROW(self, modifier):
+ self.bigger()
+
+ _up = u'\N{BLACK UP-POINTING TRIANGLE}'
+ _down = u'\N{BLACK DOWN-POINTING TRIANGLE}'
+ _bar = u'\N{LIGHT SHADE}'
+ _slider = u'\N{DARK SHADE}'
+ def render(self, width, height, terminal):
+ terminal.cursorPosition(0, 0)
+ knob = int(self.percent * (height - 2))
+ terminal.write(self._up.encode('utf-8'))
+ for i in xrange(1, height - 1):
+ terminal.cursorPosition(0, i)
+ if i != (knob + 1):
+ terminal.write(self._bar.encode('utf-8'))
+ else:
+ terminal.write(self._slider.encode('utf-8'))
+ terminal.cursorPosition(0, height - 1)
+ terminal.write(self._down.encode('utf-8'))
+
+
+class ScrolledArea(Widget):
+ def __init__(self, containee):
+ Widget.__init__(self, containee)
+ self._viewport = Viewport(containee)
+ self._horiz = HorizontalScrollbar(self._horizScroll)
+ self._vert = VerticalScrollbar(self._vertScroll)
+
+ for w in self._viewport, self._horiz, self._vert:
+ w.parent = self
+
+ def _horizScroll(self, n):
+ self._viewport.xOffset += n
+ self._viewport.xOffset = max(0, self._viewport.xOffset)
+ return self._viewport.xOffset / 25.0
+
+ def _vertScroll(self, n):
+ self._viewport.yOffset += n
+ self._viewport.yOffset = max(0, self._viewport.yOffset)
+ return self._viewport.yOffset / 25.0
+
+ def func_UP_ARROW(self, modifier):
+ self._vert.smaller()
+
+ def func_DOWN_ARROW(self, modifier):
+ self._vert.bigger()
+
+ def func_LEFT_ARROW(self, modifier):
+ self._horiz.smaller()
+
+ def func_RIGHT_ARROW(self, modifier):
+ self._horiz.bigger()
+
+ def filthy(self):
+ self._viewport.filthy()
+ self._horiz.filthy()
+ self._vert.filthy()
+ Widget.filthy(self)
+
+ def render(self, width, height, terminal):
+ wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
+ self._viewport.draw(width - 2, height - 2, wrapper)
+ if self.focused:
+ terminal.write('\x1b[31m')
+ horizontalLine(terminal, 0, 1, width - 1)
+ verticalLine(terminal, 0, 1, height - 1)
+ self._vert.draw(1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0))
+ self._horiz.draw(width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1))
+ terminal.write('\x1b[0m')
+
+def cursor(terminal, ch):
+ terminal.saveCursor()
+ terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
+ terminal.write(ch)
+ terminal.restoreCursor()
+ terminal.cursorForward()
+
+class Selection(Widget):
+ # Index into the sequence
+ focusedIndex = 0
+
+ # Offset into the displayed subset of the sequence
+ renderOffset = 0
+
+ def __init__(self, sequence, onSelect, minVisible=None):
+ Widget.__init__(self)
+ self.sequence = sequence
+ self.onSelect = onSelect
+ self.minVisible = minVisible
+ if minVisible is not None:
+ self._width = max(map(len, self.sequence))
+
+ def sizeHint(self):
+ if self.minVisible is not None:
+ return self._width, self.minVisible
+
+ def func_UP_ARROW(self, modifier):
+ if self.focusedIndex > 0:
+ self.focusedIndex -= 1
+ if self.renderOffset > 0:
+ self.renderOffset -= 1
+ self.repaint()
+
+ def func_PGUP(self, modifier):
+ if self.renderOffset != 0:
+ self.focusedIndex -= self.renderOffset
+ self.renderOffset = 0
+ else:
+ self.focusedIndex = max(0, self.focusedIndex - self.height)
+ self.repaint()
+
+ def func_DOWN_ARROW(self, modifier):
+ if self.focusedIndex < len(self.sequence) - 1:
+ self.focusedIndex += 1
+ if self.renderOffset < self.height - 1:
+ self.renderOffset += 1
+ self.repaint()
+
+
+ def func_PGDN(self, modifier):
+ if self.renderOffset != self.height - 1:
+ change = self.height - self.renderOffset - 1
+ if change + self.focusedIndex >= len(self.sequence):
+ change = len(self.sequence) - self.focusedIndex - 1
+ self.focusedIndex += change
+ self.renderOffset = self.height - 1
+ else:
+ self.focusedIndex = min(len(self.sequence) - 1, self.focusedIndex + self.height)
+ self.repaint()
+
+ def characterReceived(self, keyID, modifier):
+ if keyID == '\r':
+ self.onSelect(self.sequence[self.focusedIndex])
+
+ def render(self, width, height, terminal):
+ self.height = height
+ start = self.focusedIndex - self.renderOffset
+ if start > len(self.sequence) - height:
+ start = max(0, len(self.sequence) - height)
+
+ elements = self.sequence[start:start+height]
+
+ for n, ele in enumerate(elements):
+ terminal.cursorPosition(0, n)
+ if n == self.renderOffset:
+ terminal.saveCursor()
+ if self.focused:
+ modes = str(insults.REVERSE_VIDEO), str(insults.BOLD)
+ else:
+ modes = str(insults.REVERSE_VIDEO),
+ terminal.selectGraphicRendition(*modes)
+ text = ele[:width]
+ terminal.write(text + (' ' * (width - len(text))))
+ if n == self.renderOffset:
+ terminal.restoreCursor()
diff --git a/vendor/Twisted-10.0.0/twisted/conch/interfaces.py b/vendor/Twisted-10.0.0/twisted/conch/interfaces.py
new file mode 100644
index 0000000000..2cc84a97a5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/interfaces.py
@@ -0,0 +1,402 @@
+# Copyright (c) 2007-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module contains interfaces defined for the L{twisted.conch} package.
+"""
+
+from zope.interface import Interface, Attribute
+
+class IConchUser(Interface):
+ """
+ A user who has been authenticated to Cred through Conch. This is
+ the interface between the SSH connection and the user.
+ """
+
+ conn = Attribute('The SSHConnection object for this user.')
+
+ def lookupChannel(channelType, windowSize, maxPacket, data):
+ """
+ The other side requested a channel of some sort.
+ channelType is the type of channel being requested,
+ windowSize is the initial size of the remote window,
+ maxPacket is the largest packet we should send,
+ data is any other packet data (often nothing).
+
+ We return a subclass of L{SSHChannel<ssh.channel.SSHChannel>}. If
+ an appropriate channel can not be found, an exception will be
+ raised. If a L{ConchError<error.ConchError>} is raised, the .value
+ will be the message, and the .data will be the error code.
+
+ @type channelType: C{str}
+ @type windowSize: C{int}
+ @type maxPacket: C{int}
+ @type data: C{str}
+ @rtype: subclass of L{SSHChannel}/C{tuple}
+ """
+
+ def lookupSubsystem(subsystem, data):
+ """
+ The other side requested a subsystem.
+ subsystem is the name of the subsystem being requested.
+ data is any other packet data (often nothing).
+
+ We return a L{Protocol}.
+ """
+
+ def gotGlobalRequest(requestType, data):
+ """
+ A global request was sent from the other side.
+
+ By default, this dispatches to a method 'channel_channelType' with any
+ non-alphanumerics in the channelType replace with _'s. If it cannot
+ find a suitable method, it returns an OPEN_UNKNOWN_CHANNEL_TYPE error.
+ The method is called with arguments of windowSize, maxPacket, data.
+ """
+
+class ISession(Interface):
+
+ def getPty(term, windowSize, modes):
+ """
+ Get a psuedo-terminal for use by a shell or command.
+
+ If a psuedo-terminal is not available, or the request otherwise
+ fails, raise an exception.
+ """
+
+ def openShell(proto):
+ """
+ Open a shell and connect it to proto.
+
+ @param proto: a L{ProcessProtocol} instance.
+ """
+
+ def execCommand(proto, command):
+ """
+ Execute a command.
+
+ @param proto: a L{ProcessProtocol} instance.
+ """
+
+ def windowChanged(newWindowSize):
+ """
+ Called when the size of the remote screen has changed.
+ """
+
+ def eofReceived():
+ """
+ Called when the other side has indicated no more data will be sent.
+ """
+
+ def closed():
+ """
+ Called when the session is closed.
+ """
+
+
+class ISFTPServer(Interface):
+ """
+ The only attribute of this class is "avatar". It is the avatar
+ returned by the Realm that we are authenticated with, and
+ represents the logged-in user. Each method should check to verify
+ that the user has permission for their actions.
+ """
+
+ def gotVersion(otherVersion, extData):
+ """
+ Called when the client sends their version info.
+
+ otherVersion is an integer representing the version of the SFTP
+ protocol they are claiming.
+ extData is a dictionary of extended_name : extended_data items.
+ These items are sent by the client to indicate additional features.
+
+ This method should return a dictionary of extended_name : extended_data
+ items. These items are the additional features (if any) supported
+ by the server.
+ """
+ return {}
+
+ def openFile(filename, flags, attrs):
+ """
+ Called when the clients asks to open a file.
+
+ @param filename: a string representing the file to open.
+
+ @param flags: an integer of the flags to open the file with, ORed together.
+ The flags and their values are listed at the bottom of this file.
+
+ @param attrs: a list of attributes to open the file with. It is a
+ dictionary, consisting of 0 or more keys. The possible keys are::
+
+ size: the size of the file in bytes
+ uid: the user ID of the file as an integer
+ gid: the group ID of the file as an integer
+ permissions: the permissions of the file with as an integer.
+ the bit representation of this field is defined by POSIX.
+ atime: the access time of the file as seconds since the epoch.
+ mtime: the modification time of the file as seconds since the epoch.
+ ext_*: extended attributes. The server is not required to
+ understand this, but it may.
+
+ NOTE: there is no way to indicate text or binary files. it is up
+ to the SFTP client to deal with this.
+
+ This method returns an object that meets the ISFTPFile interface.
+ Alternatively, it can return a L{Deferred} that will be called back
+ with the object.
+ """
+
+ def removeFile(filename):
+ """
+ Remove the given file.
+
+ This method returns when the remove succeeds, or a Deferred that is
+ called back when it succeeds.
+
+ @param filename: the name of the file as a string.
+ """
+
+ def renameFile(oldpath, newpath):
+ """
+ Rename the given file.
+
+ This method returns when the rename succeeds, or a L{Deferred} that is
+ called back when it succeeds. If the rename fails, C{renameFile} will
+ raise an implementation-dependent exception.
+
+ @param oldpath: the current location of the file.
+ @param newpath: the new file name.
+ """
+
+ def makeDirectory(path, attrs):
+ """
+ Make a directory.
+
+ This method returns when the directory is created, or a Deferred that
+ is called back when it is created.
+
+ @param path: the name of the directory to create as a string.
+ @param attrs: a dictionary of attributes to create the directory with.
+ Its meaning is the same as the attrs in the L{openFile} method.
+ """
+
+ def removeDirectory(path):
+ """
+ Remove a directory (non-recursively)
+
+ It is an error to remove a directory that has files or directories in
+ it.
+
+ This method returns when the directory is removed, or a Deferred that
+ is called back when it is removed.
+
+ @param path: the directory to remove.
+ """
+
+ def openDirectory(path):
+ """
+ Open a directory for scanning.
+
+ This method returns an iterable object that has a close() method,
+ or a Deferred that is called back with same.
+
+ The close() method is called when the client is finished reading
+ from the directory. At this point, the iterable will no longer
+ be used.
+
+ The iterable should return triples of the form (filename,
+ longname, attrs) or Deferreds that return the same. The
+ sequence must support __getitem__, but otherwise may be any
+ 'sequence-like' object.
+
+ filename is the name of the file relative to the directory.
+ logname is an expanded format of the filename. The recommended format
+ is:
+ -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer
+ 1234567890 123 12345678 12345678 12345678 123456789012
+
+ The first line is sample output, the second is the length of the field.
+ The fields are: permissions, link count, user owner, group owner,
+ size in bytes, modification time.
+
+ attrs is a dictionary in the format of the attrs argument to openFile.
+
+ @param path: the directory to open.
+ """
+
+ def getAttrs(path, followLinks):
+ """
+ Return the attributes for the given path.
+
+ This method returns a dictionary in the same format as the attrs
+ argument to openFile or a Deferred that is called back with same.
+
+ @param path: the path to return attributes for as a string.
+ @param followLinks: a boolean. If it is True, follow symbolic links
+ and return attributes for the real path at the base. If it is False,
+ return attributes for the specified path.
+ """
+
+ def setAttrs(path, attrs):
+ """
+ Set the attributes for the path.
+
+ This method returns when the attributes are set or a Deferred that is
+ called back when they are.
+
+ @param path: the path to set attributes for as a string.
+ @param attrs: a dictionary in the same format as the attrs argument to
+ L{openFile}.
+ """
+
+ def readLink(path):
+ """
+ Find the root of a set of symbolic links.
+
+ This method returns the target of the link, or a Deferred that
+ returns the same.
+
+ @param path: the path of the symlink to read.
+ """
+
+ def makeLink(linkPath, targetPath):
+ """
+ Create a symbolic link.
+
+ This method returns when the link is made, or a Deferred that
+ returns the same.
+
+ @param linkPath: the pathname of the symlink as a string.
+ @param targetPath: the path of the target of the link as a string.
+ """
+
+ def realPath(path):
+ """
+ Convert any path to an absolute path.
+
+ This method returns the absolute path as a string, or a Deferred
+ that returns the same.
+
+ @param path: the path to convert as a string.
+ """
+
+ def extendedRequest(extendedName, extendedData):
+ """
+ This is the extension mechanism for SFTP. The other side can send us
+ arbitrary requests.
+
+ If we don't implement the request given by extendedName, raise
+ NotImplementedError.
+
+ The return value is a string, or a Deferred that will be called
+ back with a string.
+
+ @param extendedName: the name of the request as a string.
+ @param extendedData: the data the other side sent with the request,
+ as a string.
+ """
+
+
+
+class IKnownHostEntry(Interface):
+ """
+ A L{IKnownHostEntry} is an entry in an OpenSSH-formatted C{known_hosts}
+ file.
+
+ @since: 8.2
+ """
+
+ def matchesKey(key):
+ """
+ Return True if this entry matches the given Key object, False
+ otherwise.
+
+ @param key: The key object to match against.
+ @type key: L{twisted.conch.ssh.Key}
+ """
+
+
+ def matchesHost(hostname):
+ """
+ Return True if this entry matches the given hostname, False otherwise.
+
+ Note that this does no name resolution; if you want to match an IP
+ address, you have to resolve it yourself, and pass it in as a dotted
+ quad string.
+
+ @param key: The hostname to match against.
+ @type key: L{str}
+ """
+
+
+ def toString():
+ """
+ @return: a serialized string representation of this entry, suitable for
+ inclusion in a known_hosts file. (Newline not included.)
+
+ @rtype: L{str}
+ """
+
+
+
+class ISFTPFile(Interface):
+ """
+ This represents an open file on the server. An object adhering to this
+ interface should be returned from L{openFile}().
+ """
+
+ def close():
+ """
+ Close the file.
+
+ This method returns nothing if the close succeeds immediately, or a
+ Deferred that is called back when the close succeeds.
+ """
+
+ def readChunk(offset, length):
+ """
+ Read from the file.
+
+ If EOF is reached before any data is read, raise EOFError.
+
+ This method returns the data as a string, or a Deferred that is
+ called back with same.
+
+ @param offset: an integer that is the index to start from in the file.
+ @param length: the maximum length of data to return. The actual amount
+ returned may less than this. For normal disk files, however,
+ this should read the requested number (up to the end of the file).
+ """
+
+ def writeChunk(offset, data):
+ """
+ Write to the file.
+
+ This method returns when the write completes, or a Deferred that is
+ called when it completes.
+
+ @param offset: an integer that is the index to start from in the file.
+ @param data: a string that is the data to write.
+ """
+
+ def getAttrs():
+ """
+ Return the attributes for the file.
+
+ This method returns a dictionary in the same format as the attrs
+ argument to L{openFile} or a L{Deferred} that is called back with same.
+ """
+
+ def setAttrs(attrs):
+ """
+ Set the attributes for the file.
+
+ This method returns when the attributes are set or a Deferred that is
+ called back when they are.
+
+ @param attrs: a dictionary in the same format as the attrs argument to
+ L{openFile}.
+ """
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ls.py b/vendor/Twisted-10.0.0/twisted/conch/ls.py
new file mode 100644
index 0000000000..b127b1d730
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ls.py
@@ -0,0 +1,60 @@
+# -*- test-case-name: twisted.conch.test.test_cftp -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import array
+import stat
+
+from time import time, strftime, localtime
+
+
+def lsLine(name, s):
+ mode = s.st_mode
+ perms = array.array('c', '-'*10)
+ ft = stat.S_IFMT(mode)
+ if stat.S_ISDIR(ft): perms[0] = 'd'
+ elif stat.S_ISCHR(ft): perms[0] = 'c'
+ elif stat.S_ISBLK(ft): perms[0] = 'b'
+ elif stat.S_ISREG(ft): perms[0] = '-'
+ elif stat.S_ISFIFO(ft): perms[0] = 'f'
+ elif stat.S_ISLNK(ft): perms[0] = 'l'
+ elif stat.S_ISSOCK(ft): perms[0] = 's'
+ else: perms[0] = '!'
+ # user
+ if mode&stat.S_IRUSR:perms[1] = 'r'
+ if mode&stat.S_IWUSR:perms[2] = 'w'
+ if mode&stat.S_IXUSR:perms[3] = 'x'
+ # group
+ if mode&stat.S_IRGRP:perms[4] = 'r'
+ if mode&stat.S_IWGRP:perms[5] = 'w'
+ if mode&stat.S_IXGRP:perms[6] = 'x'
+ # other
+ if mode&stat.S_IROTH:perms[7] = 'r'
+ if mode&stat.S_IWOTH:perms[8] = 'w'
+ if mode&stat.S_IXOTH:perms[9] = 'x'
+ # suid/sgid
+ if mode&stat.S_ISUID:
+ if perms[3] == 'x': perms[3] = 's'
+ else: perms[3] = 'S'
+ if mode&stat.S_ISGID:
+ if perms[6] == 'x': perms[6] = 's'
+ else: perms[6] = 'S'
+ l = perms.tostring()
+ l += str(s.st_nlink).rjust(5) + ' '
+ un = str(s.st_uid)
+ l += un.ljust(9)
+ gr = str(s.st_gid)
+ l += gr.ljust(9)
+ sz = str(s.st_size)
+ l += sz.rjust(8)
+ l += ' '
+ sixmo = 60 * 60 * 24 * 7 * 26
+ if s.st_mtime + sixmo < time(): # last edited more than 6mo ago
+ l += strftime("%b %d %Y ", localtime(s.st_mtime))
+ else:
+ l += strftime("%b %d %H:%M ", localtime(s.st_mtime))
+ l += name
+ return l
+
+
+__all__ = ['lsLine']
diff --git a/vendor/Twisted-10.0.0/twisted/conch/manhole.py b/vendor/Twisted-10.0.0/twisted/conch/manhole.py
new file mode 100644
index 0000000000..18d85bf2ad
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/manhole.py
@@ -0,0 +1,336 @@
+# -*- test-case-name: twisted.conch.test.test_manhole -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Line-input oriented interactive interpreter loop.
+
+Provides classes for handling Python source input and arbitrary output
+interactively from a Twisted application. Also included is syntax coloring
+code with support for VT102 terminals, control code handling (^C, ^D, ^Q),
+and reasonable handling of Deferreds.
+
+@author: Jp Calderone
+"""
+
+import code, sys, StringIO, tokenize
+
+from twisted.conch import recvline
+
+from twisted.internet import defer
+from twisted.python.htmlizer import TokenPrinter
+
+class FileWrapper:
+ """Minimal write-file-like object.
+
+ Writes are translated into addOutput calls on an object passed to
+ __init__. Newlines are also converted from network to local style.
+ """
+
+ softspace = 0
+ state = 'normal'
+
+ def __init__(self, o):
+ self.o = o
+
+ def flush(self):
+ pass
+
+ def write(self, data):
+ self.o.addOutput(data.replace('\r\n', '\n'))
+
+ def writelines(self, lines):
+ self.write(''.join(lines))
+
+class ManholeInterpreter(code.InteractiveInterpreter):
+ """Interactive Interpreter with special output and Deferred support.
+
+ Aside from the features provided by L{code.InteractiveInterpreter}, this
+ class captures sys.stdout output and redirects it to the appropriate
+ location (the Manhole protocol instance). It also treats Deferreds
+ which reach the top-level specially: each is formatted to the user with
+ a unique identifier and a new callback and errback added to it, each of
+ which will format the unique identifier and the result with which the
+ Deferred fires and then pass it on to the next participant in the
+ callback chain.
+ """
+
+ numDeferreds = 0
+ def __init__(self, handler, locals=None, filename="<console>"):
+ code.InteractiveInterpreter.__init__(self, locals)
+ self._pendingDeferreds = {}
+ self.handler = handler
+ self.filename = filename
+ self.resetBuffer()
+
+ def resetBuffer(self):
+ """Reset the input buffer."""
+ self.buffer = []
+
+ def push(self, line):
+ """Push a line to the interpreter.
+
+ The line should not have a trailing newline; it may have
+ internal newlines. The line is appended to a buffer and the
+ interpreter's runsource() method is called with the
+ concatenated contents of the buffer as source. If this
+ indicates that the command was executed or invalid, the buffer
+ is reset; otherwise, the command is incomplete, and the buffer
+ is left as it was after the line was appended. The return
+ value is 1 if more input is required, 0 if the line was dealt
+ with in some way (this is the same as runsource()).
+
+ """
+ self.buffer.append(line)
+ source = "\n".join(self.buffer)
+ more = self.runsource(source, self.filename)
+ if not more:
+ self.resetBuffer()
+ return more
+
+ def runcode(self, *a, **kw):
+ orighook, sys.displayhook = sys.displayhook, self.displayhook
+ try:
+ origout, sys.stdout = sys.stdout, FileWrapper(self.handler)
+ try:
+ code.InteractiveInterpreter.runcode(self, *a, **kw)
+ finally:
+ sys.stdout = origout
+ finally:
+ sys.displayhook = orighook
+
+ def displayhook(self, obj):
+ self.locals['_'] = obj
+ if isinstance(obj, defer.Deferred):
+ # XXX Ick, where is my "hasFired()" interface?
+ if hasattr(obj, "result"):
+ self.write(repr(obj))
+ elif id(obj) in self._pendingDeferreds:
+ self.write("<Deferred #%d>" % (self._pendingDeferreds[id(obj)][0],))
+ else:
+ d = self._pendingDeferreds
+ k = self.numDeferreds
+ d[id(obj)] = (k, obj)
+ self.numDeferreds += 1
+ obj.addCallbacks(self._cbDisplayDeferred, self._ebDisplayDeferred,
+ callbackArgs=(k, obj), errbackArgs=(k, obj))
+ self.write("<Deferred #%d>" % (k,))
+ elif obj is not None:
+ self.write(repr(obj))
+
+ def _cbDisplayDeferred(self, result, k, obj):
+ self.write("Deferred #%d called back: %r" % (k, result), True)
+ del self._pendingDeferreds[id(obj)]
+ return result
+
+ def _ebDisplayDeferred(self, failure, k, obj):
+ self.write("Deferred #%d failed: %r" % (k, failure.getErrorMessage()), True)
+ del self._pendingDeferreds[id(obj)]
+ return failure
+
+ def write(self, data, async=False):
+ self.handler.addOutput(data, async)
+
+CTRL_C = '\x03'
+CTRL_D = '\x04'
+CTRL_BACKSLASH = '\x1c'
+CTRL_L = '\x0c'
+
+class Manhole(recvline.HistoricRecvLine):
+ """Mediator between a fancy line source and an interactive interpreter.
+
+ This accepts lines from its transport and passes them on to a
+ L{ManholeInterpreter}. Control commands (^C, ^D, ^\) are also handled
+ with something approximating their normal terminal-mode behavior. It
+ can optionally be constructed with a dict which will be used as the
+ local namespace for any code executed.
+ """
+
+ namespace = None
+
+ def __init__(self, namespace=None):
+ recvline.HistoricRecvLine.__init__(self)
+ if namespace is not None:
+ self.namespace = namespace.copy()
+
+ def connectionMade(self):
+ recvline.HistoricRecvLine.connectionMade(self)
+ self.interpreter = ManholeInterpreter(self, self.namespace)
+ self.keyHandlers[CTRL_C] = self.handle_INT
+ self.keyHandlers[CTRL_D] = self.handle_EOF
+ self.keyHandlers[CTRL_L] = self.handle_FF
+ self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
+
+
+ def handle_INT(self):
+ """
+ Handle ^C as an interrupt keystroke by resetting the current input
+ variables to their initial state.
+ """
+ self.pn = 0
+ self.lineBuffer = []
+ self.lineBufferIndex = 0
+ self.interpreter.resetBuffer()
+
+ self.terminal.nextLine()
+ self.terminal.write("KeyboardInterrupt")
+ self.terminal.nextLine()
+ self.terminal.write(self.ps[self.pn])
+
+
+ def handle_EOF(self):
+ if self.lineBuffer:
+ self.terminal.write('\a')
+ else:
+ self.handle_QUIT()
+
+
+ def handle_FF(self):
+ """
+ Handle a 'form feed' byte - generally used to request a screen
+ refresh/redraw.
+ """
+ self.terminal.eraseDisplay()
+ self.terminal.cursorHome()
+ self.drawInputLine()
+
+
+ def handle_QUIT(self):
+ self.terminal.loseConnection()
+
+
+ def _needsNewline(self):
+ w = self.terminal.lastWrite
+ return not w.endswith('\n') and not w.endswith('\x1bE')
+
+ def addOutput(self, bytes, async=False):
+ if async:
+ self.terminal.eraseLine()
+ self.terminal.cursorBackward(len(self.lineBuffer) + len(self.ps[self.pn]))
+
+ self.terminal.write(bytes)
+
+ if async:
+ if self._needsNewline():
+ self.terminal.nextLine()
+
+ self.terminal.write(self.ps[self.pn])
+
+ if self.lineBuffer:
+ oldBuffer = self.lineBuffer
+ self.lineBuffer = []
+ self.lineBufferIndex = 0
+
+ self._deliverBuffer(oldBuffer)
+
+ def lineReceived(self, line):
+ more = self.interpreter.push(line)
+ self.pn = bool(more)
+ if self._needsNewline():
+ self.terminal.nextLine()
+ self.terminal.write(self.ps[self.pn])
+
+class VT102Writer:
+ """Colorizer for Python tokens.
+
+ A series of tokens are written to instances of this object. Each is
+ colored in a particular way. The final line of the result of this is
+ generally added to the output.
+ """
+
+ typeToColor = {
+ 'identifier': '\x1b[31m',
+ 'keyword': '\x1b[32m',
+ 'parameter': '\x1b[33m',
+ 'variable': '\x1b[1;33m',
+ 'string': '\x1b[35m',
+ 'number': '\x1b[36m',
+ 'op': '\x1b[37m'}
+
+ normalColor = '\x1b[0m'
+
+ def __init__(self):
+ self.written = []
+
+ def color(self, type):
+ r = self.typeToColor.get(type, '')
+ return r
+
+ def write(self, token, type=None):
+ if token and token != '\r':
+ c = self.color(type)
+ if c:
+ self.written.append(c)
+ self.written.append(token)
+ if c:
+ self.written.append(self.normalColor)
+
+ def __str__(self):
+ s = ''.join(self.written)
+ return s.strip('\n').splitlines()[-1]
+
+def lastColorizedLine(source):
+ """Tokenize and colorize the given Python source.
+
+ Returns a VT102-format colorized version of the last line of C{source}.
+ """
+ w = VT102Writer()
+ p = TokenPrinter(w.write).printtoken
+ s = StringIO.StringIO(source)
+
+ tokenize.tokenize(s.readline, p)
+
+ return str(w)
+
+class ColoredManhole(Manhole):
+ """A REPL which syntax colors input as users type it.
+ """
+
+ def getSource(self):
+ """Return a string containing the currently entered source.
+
+ This is only the code which will be considered for execution
+ next.
+ """
+ return ('\n'.join(self.interpreter.buffer) +
+ '\n' +
+ ''.join(self.lineBuffer))
+
+
+ def characterReceived(self, ch, moreCharactersComing):
+ if self.mode == 'insert':
+ self.lineBuffer.insert(self.lineBufferIndex, ch)
+ else:
+ self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
+ self.lineBufferIndex += 1
+
+ if moreCharactersComing:
+ # Skip it all, we'll get called with another character in
+ # like 2 femtoseconds.
+ return
+
+ if ch == ' ':
+ # Don't bother to try to color whitespace
+ self.terminal.write(ch)
+ return
+
+ source = self.getSource()
+
+ # Try to write some junk
+ try:
+ coloredLine = lastColorizedLine(source)
+ except tokenize.TokenError:
+ # We couldn't do it. Strange. Oh well, just add the character.
+ self.terminal.write(ch)
+ else:
+ # Success! Clear the source on this line.
+ self.terminal.eraseLine()
+ self.terminal.cursorBackward(len(self.lineBuffer) + len(self.ps[self.pn]) - 1)
+
+ # And write a new, colorized one.
+ self.terminal.write(self.ps[self.pn] + coloredLine)
+
+ # And move the cursor to where it belongs
+ n = len(self.lineBuffer) - self.lineBufferIndex
+ if n:
+ self.terminal.cursorBackward(n)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/manhole_ssh.py b/vendor/Twisted-10.0.0/twisted/conch/manhole_ssh.py
new file mode 100644
index 0000000000..381c1fbb3e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/manhole_ssh.py
@@ -0,0 +1,146 @@
+# -*- test-case-name: twisted.conch.test.test_manhole -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+insults/SSH integration support.
+
+@author: Jp Calderone
+"""
+
+from zope.interface import implements
+
+from twisted.conch import avatar, interfaces as iconch, error as econch
+from twisted.conch.ssh import factory, keys, session
+from twisted.cred import credentials, checkers, portal
+from twisted.python import components
+
+from twisted.conch.insults import insults
+
+class _Glue:
+ """A feeble class for making one attribute look like another.
+
+ This should be replaced with a real class at some point, probably.
+ Try not to write new code that uses it.
+ """
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+ def __getattr__(self, name):
+ raise AttributeError(self.name, "has no attribute", name)
+
+class TerminalSessionTransport:
+ def __init__(self, proto, chainedProtocol, avatar, width, height):
+ self.proto = proto
+ self.avatar = avatar
+ self.chainedProtocol = chainedProtocol
+
+ session = self.proto.session
+
+ self.proto.makeConnection(
+ _Glue(write=self.chainedProtocol.dataReceived,
+ loseConnection=lambda: avatar.conn.sendClose(session),
+ name="SSH Proto Transport"))
+
+ def loseConnection():
+ self.proto.loseConnection()
+
+ self.chainedProtocol.makeConnection(
+ _Glue(write=self.proto.write,
+ loseConnection=loseConnection,
+ name="Chained Proto Transport"))
+
+ # XXX TODO
+ # chainedProtocol is supposed to be an ITerminalTransport,
+ # maybe. That means perhaps its terminalProtocol attribute is
+ # an ITerminalProtocol, it could be. So calling terminalSize
+ # on that should do the right thing But it'd be nice to clean
+ # this bit up.
+ self.chainedProtocol.terminalProtocol.terminalSize(width, height)
+
+class TerminalSession(components.Adapter):
+ implements(iconch.ISession)
+
+ transportFactory = TerminalSessionTransport
+ chainedProtocolFactory = insults.ServerProtocol
+
+ def getPty(self, term, windowSize, attrs):
+ self.height, self.width = windowSize[:2]
+
+ def openShell(self, proto):
+ self.transportFactory(
+ proto, self.chainedProtocolFactory(),
+ iconch.IConchUser(self.original),
+ self.width, self.height)
+
+ def execCommand(self, proto, cmd):
+ raise econch.ConchError("Cannot execute commands")
+
+ def closed(self):
+ pass
+
+class TerminalUser(avatar.ConchUser, components.Adapter):
+ def __init__(self, original, avatarId):
+ components.Adapter.__init__(self, original)
+ avatar.ConchUser.__init__(self)
+ self.channelLookup['session'] = session.SSHSession
+
+class TerminalRealm:
+ userFactory = TerminalUser
+ sessionFactory = TerminalSession
+
+ transportFactory = TerminalSessionTransport
+ chainedProtocolFactory = insults.ServerProtocol
+
+ def _getAvatar(self, avatarId):
+ comp = components.Componentized()
+ user = self.userFactory(comp, avatarId)
+ sess = self.sessionFactory(comp)
+
+ sess.transportFactory = self.transportFactory
+ sess.chainedProtocolFactory = self.chainedProtocolFactory
+
+ comp.setComponent(iconch.IConchUser, user)
+ comp.setComponent(iconch.ISession, sess)
+
+ return user
+
+ def __init__(self, transportFactory=None):
+ if transportFactory is not None:
+ self.transportFactory = transportFactory
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ for i in interfaces:
+ if i is iconch.IConchUser:
+ return (iconch.IConchUser,
+ self._getAvatar(avatarId),
+ lambda: None)
+ raise NotImplementedError()
+
+class ConchFactory(factory.SSHFactory):
+ publicKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBEvLi8DVPrJ3/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYLh5KmRpslkYHRivcJSkbh/C+BR3utDS555mV'
+
+ publicKeys = {
+ 'ssh-rsa' : keys.Key.fromString(publicKey)
+ }
+ del publicKey
+
+ privateKey = """-----BEGIN RSA PRIVATE KEY-----
+MIIByAIBAAJhAK8ycfDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW
+4sbUIZR/ZXzY1CMfuC5qyR+UDUbBaaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fw
+vgUd7rQ0ueeZlQIBIwJgbh+1VZfr7WftK5lu7MHtqE1S1vPWZQYE3+VUn8yJADyb
+Z4fsZaCrzW9lkIqXkE3GIY+ojdhZhkO1gbG0118sIgphwSWKRxK0mvh6ERxKqIt1
+xJEJO74EykXZV4oNJ8sjAjEA3J9r2ZghVhGN6V8DnQrTk24Td0E8hU8AcP0FVP+8
+PQm/g/aXf2QQkQT+omdHVEJrAjEAy0pL0EBH6EVS98evDCBtQw22OZT52qXlAwZ2
+gyTriKFVoqjeEjt3SZKKqXHSApP/AjBLpF99zcJJZRq2abgYlf9lv1chkrWqDHUu
+DZttmYJeEfiFBBavVYIF1dOlZT0G8jMCMBc7sOSZodFnAiryP+Qg9otSBjJ3bQML
+pSTqy7c3a2AScC/YyOwkDaICHnnD3XyjMwIxALRzl0tQEKMXs6hH8ToUdlLROCrP
+EhQ0wahUTCk1gKA4uPD6TMTChavbh4K63OvbKg==
+-----END RSA PRIVATE KEY-----"""
+ privateKeys = {
+ 'ssh-rsa' : keys.Key.fromString(privateKey)
+ }
+ del privateKey
+
+ def __init__(self, portal):
+ self.portal = portal
diff --git a/vendor/Twisted-10.0.0/twisted/conch/manhole_tap.py b/vendor/Twisted-10.0.0/twisted/conch/manhole_tap.py
new file mode 100644
index 0000000000..1df969348c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/manhole_tap.py
@@ -0,0 +1,128 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+TAP plugin for creating telnet- and ssh-accessible manhole servers.
+
+@author: Jp Calderone
+"""
+
+from zope.interface import implements
+
+from twisted.internet import protocol
+from twisted.application import service, strports
+from twisted.conch.ssh import session
+from twisted.conch import interfaces as iconch
+from twisted.cred import portal, checkers
+from twisted.python import usage
+
+from twisted.conch.insults import insults
+from twisted.conch import manhole, manhole_ssh, telnet
+
+class makeTelnetProtocol:
+ def __init__(self, portal):
+ self.portal = portal
+
+ def __call__(self):
+ auth = telnet.AuthenticatingTelnetProtocol
+ args = (self.portal,)
+ return telnet.TelnetTransport(auth, *args)
+
+class chainedProtocolFactory:
+ def __init__(self, namespace):
+ self.namespace = namespace
+
+ def __call__(self):
+ return insults.ServerProtocol(manhole.ColoredManhole, self.namespace)
+
+class _StupidRealm:
+ implements(portal.IRealm)
+
+ def __init__(self, proto, *a, **kw):
+ self.protocolFactory = proto
+ self.protocolArgs = a
+ self.protocolKwArgs = kw
+
+ def requestAvatar(self, avatarId, *interfaces):
+ if telnet.ITelnetProtocol in interfaces:
+ return (telnet.ITelnetProtocol,
+ self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs),
+ lambda: None)
+ raise NotImplementedError()
+
+class Options(usage.Options):
+ optParameters = [
+ ["telnetPort", "t", None, "strports description of the address on which to listen for telnet connections"],
+ ["sshPort", "s", None, "strports description of the address on which to listen for ssh connections"],
+ ["passwd", "p", "/etc/passwd", "name of a passwd(5)-format username/password file"]]
+
+ def __init__(self):
+ usage.Options.__init__(self)
+ self.users = []
+ self['namespace'] = None
+
+ def opt_user(self, name):
+ self.users.append(name)
+
+ def postOptions(self):
+ if self['telnetPort'] is None and self['sshPort'] is None:
+ raise usage.UsageError("At least one of --telnetPort and --sshPort must be specified")
+
+def makeService(options):
+ """Create a manhole server service.
+
+ @type options: C{dict}
+ @param options: A mapping describing the configuration of
+ the desired service. Recognized key/value pairs are::
+
+ "telnetPort": strports description of the address on which
+ to listen for telnet connections. If None,
+ no telnet service will be started.
+
+ "sshPort": strports description of the address on which to
+ listen for ssh connections. If None, no ssh
+ service will be started.
+
+ "namespace": dictionary containing desired initial locals
+ for manhole connections. If None, an empty
+ dictionary will be used.
+
+ "passwd": Name of a passwd(5)-format username/password file.
+
+ @rtype: L{twisted.application.service.IService}
+ @return: A manhole service.
+ """
+
+ svc = service.MultiService()
+
+ namespace = options['namespace']
+ if namespace is None:
+ namespace = {}
+
+ checker = checkers.FilePasswordDB(options['passwd'])
+
+ if options['telnetPort']:
+ telnetRealm = _StupidRealm(telnet.TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ manhole.ColoredManhole,
+ namespace)
+
+ telnetPortal = portal.Portal(telnetRealm, [checker])
+
+ telnetFactory = protocol.ServerFactory()
+ telnetFactory.protocol = makeTelnetProtocol(telnetPortal)
+ telnetService = strports.service(options['telnetPort'],
+ telnetFactory)
+ telnetService.setServiceParent(svc)
+
+ if options['sshPort']:
+ sshRealm = manhole_ssh.TerminalRealm()
+ sshRealm.chainedProtocolFactory = chainedProtocolFactory(namespace)
+
+ sshPortal = portal.Portal(sshRealm, [checker])
+ sshFactory = manhole_ssh.ConchFactory(sshPortal)
+ sshService = strports.service(options['sshPort'],
+ sshFactory)
+ sshService.setServiceParent(svc)
+
+ return svc
diff --git a/vendor/Twisted-10.0.0/twisted/conch/mixin.py b/vendor/Twisted-10.0.0/twisted/conch/mixin.py
new file mode 100644
index 0000000000..f0700a1e68
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/mixin.py
@@ -0,0 +1,49 @@
+# -*- test-case-name: twisted.conch.test.test_mixin -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Experimental optimization
+
+This module provides a single mixin class which allows protocols to
+collapse numerous small writes into a single larger one.
+
+@author: Jp Calderone
+"""
+
+from twisted.internet import reactor
+
+class BufferingMixin:
+ """Mixin which adds write buffering.
+ """
+ _delayedWriteCall = None
+ bytes = None
+
+ DELAY = 0.0
+
+ def schedule(self):
+ return reactor.callLater(self.DELAY, self.flush)
+
+ def reschedule(self, token):
+ token.reset(self.DELAY)
+
+ def write(self, bytes):
+ """Buffer some bytes to be written soon.
+
+ Every call to this function delays the real write by C{self.DELAY}
+ seconds. When the delay expires, all collected bytes are written
+ to the underlying transport using L{ITransport.writeSequence}.
+ """
+ if self._delayedWriteCall is None:
+ self.bytes = []
+ self._delayedWriteCall = self.schedule()
+ else:
+ self.reschedule(self._delayedWriteCall)
+ self.bytes.append(bytes)
+
+ def flush(self):
+ """Flush the buffer immediately.
+ """
+ self._delayedWriteCall = None
+ self.transport.writeSequence(self.bytes)
+ self.bytes = None
diff --git a/vendor/Twisted-10.0.0/twisted/conch/openssh_compat/__init__.py b/vendor/Twisted-10.0.0/twisted/conch/openssh_compat/__init__.py
new file mode 100644
index 0000000000..fe2200e61f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/openssh_compat/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+Support for OpenSSH configuration files.
+
+Maintainer: Paul Swartz
+"""
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/openssh_compat/factory.py b/vendor/Twisted-10.0.0/twisted/conch/openssh_compat/factory.py
new file mode 100644
index 0000000000..b3f6eaa1dd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/openssh_compat/factory.py
@@ -0,0 +1,73 @@
+# -*- test-case-name: twisted.conch.test.test_openssh_compat -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Factory for reading openssh configuration files: public keys, private keys, and
+modile file.
+"""
+
+import os, errno
+
+from twisted.python import log
+from twisted.python.util import runAsEffectiveUser
+
+from twisted.conch.ssh import keys, factory, common
+from twisted.conch.openssh_compat import primes
+
+
+
+class OpenSSHFactory(factory.SSHFactory):
+ dataRoot = '/usr/local/etc'
+ moduliRoot = '/usr/local/etc' # for openbsd which puts moduli in a different
+ # directory from keys
+
+
+ def getPublicKeys(self):
+ """
+ Return the server public keys.
+ """
+ ks = {}
+ for filename in os.listdir(self.dataRoot):
+ if filename[:9] == 'ssh_host_' and filename[-8:]=='_key.pub':
+ try:
+ k = keys.Key.fromFile(
+ os.path.join(self.dataRoot, filename))
+ t = common.getNS(k.blob())[0]
+ ks[t] = k
+ except Exception, e:
+ log.msg('bad public key file %s: %s' % (filename, e))
+ return ks
+
+
+ def getPrivateKeys(self):
+ """
+ Return the server private keys.
+ """
+ privateKeys = {}
+ for filename in os.listdir(self.dataRoot):
+ if filename[:9] == 'ssh_host_' and filename[-4:]=='_key':
+ fullPath = os.path.join(self.dataRoot, filename)
+ try:
+ key = keys.Key.fromFile(fullPath)
+ except IOError, e:
+ if e.errno == errno.EACCES:
+ # Not allowed, let's switch to root
+ key = runAsEffectiveUser(0, 0, keys.Key.fromFile, fullPath)
+ keyType = keys.objectType(key.keyObject)
+ privateKeys[keyType] = key
+ else:
+ raise
+ except Exception, e:
+ log.msg('bad private key file %s: %s' % (filename, e))
+ else:
+ keyType = keys.objectType(key.keyObject)
+ privateKeys[keyType] = key
+ return privateKeys
+
+
+ def getPrimes(self):
+ try:
+ return primes.parseModuliFile(self.moduliRoot+'/moduli')
+ except IOError:
+ return None
diff --git a/vendor/Twisted-10.0.0/twisted/conch/openssh_compat/primes.py b/vendor/Twisted-10.0.0/twisted/conch/openssh_compat/primes.py
new file mode 100644
index 0000000000..04e6f45fc7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/openssh_compat/primes.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+Parsing for the moduli file, which contains Diffie-Hellman prime groups.
+
+Maintainer: Paul Swartz
+"""
+
+def parseModuliFile(filename):
+ lines = open(filename).readlines()
+ primes = {}
+ for l in lines:
+ l = l.strip()
+ if not l or l[0]=='#':
+ continue
+ tim, typ, tst, tri, size, gen, mod = l.split()
+ size = int(size) + 1
+ gen = long(gen)
+ mod = long(mod, 16)
+ if not primes.has_key(size):
+ primes[size] = []
+ primes[size].append((gen, mod))
+ return primes
diff --git a/vendor/Twisted-10.0.0/twisted/conch/recvline.py b/vendor/Twisted-10.0.0/twisted/conch/recvline.py
new file mode 100644
index 0000000000..a6c69b6c2e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/recvline.py
@@ -0,0 +1,328 @@
+# -*- test-case-name: twisted.conch.test.test_recvline -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Basic line editing support.
+
+@author: Jp Calderone
+"""
+
+import string
+
+from zope.interface import implements
+
+from twisted.conch.insults import insults, helper
+
+from twisted.python import log, reflect
+
+_counters = {}
+class Logging(object):
+ """Wrapper which logs attribute lookups.
+
+ This was useful in debugging something, I guess. I forget what.
+ It can probably be deleted or moved somewhere more appropriate.
+ Nothing special going on here, really.
+ """
+ def __init__(self, original):
+ self.original = original
+ key = reflect.qual(original.__class__)
+ count = _counters.get(key, 0)
+ _counters[key] = count + 1
+ self._logFile = file(key + '-' + str(count), 'w')
+
+ def __str__(self):
+ return str(super(Logging, self).__getattribute__('original'))
+
+ def __repr__(self):
+ return repr(super(Logging, self).__getattribute__('original'))
+
+ def __getattribute__(self, name):
+ original = super(Logging, self).__getattribute__('original')
+ logFile = super(Logging, self).__getattribute__('_logFile')
+ logFile.write(name + '\n')
+ return getattr(original, name)
+
+class TransportSequence(object):
+ """An L{ITerminalTransport} implementation which forwards calls to
+ one or more other L{ITerminalTransport}s.
+
+ This is a cheap way for servers to keep track of the state they
+ expect the client to see, since all terminal manipulations can be
+ send to the real client and to a terminal emulator that lives in
+ the server process.
+ """
+ implements(insults.ITerminalTransport)
+
+ for keyID in ('UP_ARROW', 'DOWN_ARROW', 'RIGHT_ARROW', 'LEFT_ARROW',
+ 'HOME', 'INSERT', 'DELETE', 'END', 'PGUP', 'PGDN',
+ 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
+ 'F10', 'F11', 'F12'):
+ exec '%s = object()' % (keyID,)
+
+ TAB = '\t'
+ BACKSPACE = '\x7f'
+
+ def __init__(self, *transports):
+ assert transports, "Cannot construct a TransportSequence with no transports"
+ self.transports = transports
+
+ for method in insults.ITerminalTransport:
+ exec """\
+def %s(self, *a, **kw):
+ for tpt in self.transports:
+ result = tpt.%s(*a, **kw)
+ return result
+""" % (method, method)
+
+class LocalTerminalBufferMixin(object):
+ """A mixin for RecvLine subclasses which records the state of the terminal.
+
+ This is accomplished by performing all L{ITerminalTransport} operations on both
+ the transport passed to makeConnection and an instance of helper.TerminalBuffer.
+
+ @ivar terminalCopy: A L{helper.TerminalBuffer} instance which efforts
+ will be made to keep up to date with the actual terminal
+ associated with this protocol instance.
+ """
+
+ def makeConnection(self, transport):
+ self.terminalCopy = helper.TerminalBuffer()
+ self.terminalCopy.connectionMade()
+ return super(LocalTerminalBufferMixin, self).makeConnection(
+ TransportSequence(transport, self.terminalCopy))
+
+ def __str__(self):
+ return str(self.terminalCopy)
+
+class RecvLine(insults.TerminalProtocol):
+ """L{TerminalProtocol} which adds line editing features.
+
+ Clients will be prompted for lines of input with all the usual
+ features: character echoing, left and right arrow support for
+ moving the cursor to different areas of the line buffer, backspace
+ and delete for removing characters, and insert for toggling
+ between typeover and insert mode. Tabs will be expanded to enough
+ spaces to move the cursor to the next tabstop (every four
+ characters by default). Enter causes the line buffer to be
+ cleared and the line to be passed to the lineReceived() method
+ which, by default, does nothing. Subclasses are responsible for
+ redrawing the input prompt (this will probably change).
+ """
+ width = 80
+ height = 24
+
+ TABSTOP = 4
+
+ ps = ('>>> ', '... ')
+ pn = 0
+
+ def connectionMade(self):
+ # A list containing the characters making up the current line
+ self.lineBuffer = []
+
+ # A zero-based (wtf else?) index into self.lineBuffer.
+ # Indicates the current cursor position.
+ self.lineBufferIndex = 0
+
+ t = self.terminal
+ # A map of keyIDs to bound instance methods.
+ self.keyHandlers = {
+ t.LEFT_ARROW: self.handle_LEFT,
+ t.RIGHT_ARROW: self.handle_RIGHT,
+ t.TAB: self.handle_TAB,
+
+ # Both of these should not be necessary, but figuring out
+ # which is necessary is a huge hassle.
+ '\r': self.handle_RETURN,
+ '\n': self.handle_RETURN,
+
+ t.BACKSPACE: self.handle_BACKSPACE,
+ t.DELETE: self.handle_DELETE,
+ t.INSERT: self.handle_INSERT,
+ t.HOME: self.handle_HOME,
+ t.END: self.handle_END}
+
+ self.initializeScreen()
+
+ def initializeScreen(self):
+ # Hmm, state sucks. Oh well.
+ # For now we will just take over the whole terminal.
+ self.terminal.reset()
+ self.terminal.write(self.ps[self.pn])
+ # XXX Note: I would prefer to default to starting in insert
+ # mode, however this does not seem to actually work! I do not
+ # know why. This is probably of interest to implementors
+ # subclassing RecvLine.
+
+ # XXX XXX Note: But the unit tests all expect the initial mode
+ # to be insert right now. Fuck, there needs to be a way to
+ # query the current mode or something.
+ # self.setTypeoverMode()
+ self.setInsertMode()
+
+ def currentLineBuffer(self):
+ s = ''.join(self.lineBuffer)
+ return s[:self.lineBufferIndex], s[self.lineBufferIndex:]
+
+ def setInsertMode(self):
+ self.mode = 'insert'
+ self.terminal.setModes([insults.modes.IRM])
+
+ def setTypeoverMode(self):
+ self.mode = 'typeover'
+ self.terminal.resetModes([insults.modes.IRM])
+
+ def drawInputLine(self):
+ """
+ Write a line containing the current input prompt and the current line
+ buffer at the current cursor position.
+ """
+ self.terminal.write(self.ps[self.pn] + ''.join(self.lineBuffer))
+
+ def terminalSize(self, width, height):
+ # XXX - Clear the previous input line, redraw it at the new
+ # cursor position
+ self.terminal.eraseDisplay()
+ self.terminal.cursorHome()
+ self.width = width
+ self.height = height
+ self.drawInputLine()
+
+ def unhandledControlSequence(self, seq):
+ pass
+
+ def keystrokeReceived(self, keyID, modifier):
+ m = self.keyHandlers.get(keyID)
+ if m is not None:
+ m()
+ elif keyID in string.printable:
+ self.characterReceived(keyID, False)
+ else:
+ log.msg("Received unhandled keyID: %r" % (keyID,))
+
+ def characterReceived(self, ch, moreCharactersComing):
+ if self.mode == 'insert':
+ self.lineBuffer.insert(self.lineBufferIndex, ch)
+ else:
+ self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
+ self.lineBufferIndex += 1
+ self.terminal.write(ch)
+
+ def handle_TAB(self):
+ n = self.TABSTOP - (len(self.lineBuffer) % self.TABSTOP)
+ self.terminal.cursorForward(n)
+ self.lineBufferIndex += n
+ self.lineBuffer.extend(' ' * n)
+
+ def handle_LEFT(self):
+ if self.lineBufferIndex > 0:
+ self.lineBufferIndex -= 1
+ self.terminal.cursorBackward()
+
+ def handle_RIGHT(self):
+ if self.lineBufferIndex < len(self.lineBuffer):
+ self.lineBufferIndex += 1
+ self.terminal.cursorForward()
+
+ def handle_HOME(self):
+ if self.lineBufferIndex:
+ self.terminal.cursorBackward(self.lineBufferIndex)
+ self.lineBufferIndex = 0
+
+ def handle_END(self):
+ offset = len(self.lineBuffer) - self.lineBufferIndex
+ if offset:
+ self.terminal.cursorForward(offset)
+ self.lineBufferIndex = len(self.lineBuffer)
+
+ def handle_BACKSPACE(self):
+ if self.lineBufferIndex > 0:
+ self.lineBufferIndex -= 1
+ del self.lineBuffer[self.lineBufferIndex]
+ self.terminal.cursorBackward()
+ self.terminal.deleteCharacter()
+
+ def handle_DELETE(self):
+ if self.lineBufferIndex < len(self.lineBuffer):
+ del self.lineBuffer[self.lineBufferIndex]
+ self.terminal.deleteCharacter()
+
+ def handle_RETURN(self):
+ line = ''.join(self.lineBuffer)
+ self.lineBuffer = []
+ self.lineBufferIndex = 0
+ self.terminal.nextLine()
+ self.lineReceived(line)
+
+ def handle_INSERT(self):
+ assert self.mode in ('typeover', 'insert')
+ if self.mode == 'typeover':
+ self.setInsertMode()
+ else:
+ self.setTypeoverMode()
+
+ def lineReceived(self, line):
+ pass
+
+class HistoricRecvLine(RecvLine):
+ """L{TerminalProtocol} which adds both basic line-editing features and input history.
+
+ Everything supported by L{RecvLine} is also supported by this class. In addition, the
+ up and down arrows traverse the input history. Each received line is automatically
+ added to the end of the input history.
+ """
+ def connectionMade(self):
+ RecvLine.connectionMade(self)
+
+ self.historyLines = []
+ self.historyPosition = 0
+
+ t = self.terminal
+ self.keyHandlers.update({t.UP_ARROW: self.handle_UP,
+ t.DOWN_ARROW: self.handle_DOWN})
+
+ def currentHistoryBuffer(self):
+ b = tuple(self.historyLines)
+ return b[:self.historyPosition], b[self.historyPosition:]
+
+ def _deliverBuffer(self, buf):
+ if buf:
+ for ch in buf[:-1]:
+ self.characterReceived(ch, True)
+ self.characterReceived(buf[-1], False)
+
+ def handle_UP(self):
+ if self.lineBuffer and self.historyPosition == len(self.historyLines):
+ self.historyLines.append(self.lineBuffer)
+ if self.historyPosition > 0:
+ self.handle_HOME()
+ self.terminal.eraseToLineEnd()
+
+ self.historyPosition -= 1
+ self.lineBuffer = []
+
+ self._deliverBuffer(self.historyLines[self.historyPosition])
+
+ def handle_DOWN(self):
+ if self.historyPosition < len(self.historyLines) - 1:
+ self.handle_HOME()
+ self.terminal.eraseToLineEnd()
+
+ self.historyPosition += 1
+ self.lineBuffer = []
+
+ self._deliverBuffer(self.historyLines[self.historyPosition])
+ else:
+ self.handle_HOME()
+ self.terminal.eraseToLineEnd()
+
+ self.historyPosition = len(self.historyLines)
+ self.lineBuffer = []
+ self.lineBufferIndex = 0
+
+ def handle_RETURN(self):
+ if self.lineBuffer:
+ self.historyLines.append(''.join(self.lineBuffer))
+ self.historyPosition = len(self.historyLines)
+ return RecvLine.handle_RETURN(self)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/scripts/__init__.py b/vendor/Twisted-10.0.0/twisted/conch/scripts/__init__.py
new file mode 100644
index 0000000000..63fdb3d2fe
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/scripts/__init__.py
@@ -0,0 +1 @@
+'conch scripts'
diff --git a/vendor/Twisted-10.0.0/twisted/conch/scripts/cftp.py b/vendor/Twisted-10.0.0/twisted/conch/scripts/cftp.py
new file mode 100644
index 0000000000..52e9f64685
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/scripts/cftp.py
@@ -0,0 +1,811 @@
+# -*- test-case-name: twisted.conch.test.test_cftp -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implementation module for the I{cftp} command.
+"""
+
+import os, sys, getpass, struct, tty, fcntl, stat
+import fnmatch, pwd, time, glob
+
+from twisted.conch.client import connect, default, options
+from twisted.conch.ssh import connection, common
+from twisted.conch.ssh import channel, filetransfer
+from twisted.protocols import basic
+from twisted.internet import reactor, stdio, defer, utils
+from twisted.python import log, usage, failure
+
+class ClientOptions(options.ConchOptions):
+
+ synopsis = """Usage: cftp [options] [user@]host
+ cftp [options] [user@]host[:dir[/]]
+ cftp [options] [user@]host[:file [localfile]]
+"""
+ longdesc = ("cftp is a client for logging into a remote machine and "
+ "executing commands to send and receive file information")
+
+ optParameters = [
+ ['buffersize', 'B', 32768, 'Size of the buffer to use for sending/receiving.'],
+ ['batchfile', 'b', None, 'File to read commands from, or \'-\' for stdin.'],
+ ['requests', 'R', 5, 'Number of requests to make before waiting for a reply.'],
+ ['subsystem', 's', 'sftp', 'Subsystem/server program to connect to.']]
+ zsh_altArgDescr = {"buffersize":"Size of send/receive buffer (default: 32768)"}
+ #zsh_multiUse = ["foo", "bar"]
+ #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
+ #zsh_actions = {"foo":'_files -g "*.foo"', "bar":"(one two three)"}
+ #zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
+ zsh_extras = ['2::localfile:{if [[ $words[1] == *:* ]]; then; _files; fi}']
+
+ def parseArgs(self, host, localPath=None):
+ self['remotePath'] = ''
+ if ':' in host:
+ host, self['remotePath'] = host.split(':', 1)
+ self['remotePath'].rstrip('/')
+ self['host'] = host
+ self['localPath'] = localPath
+
+def run():
+# import hotshot
+# prof = hotshot.Profile('cftp.prof')
+# prof.start()
+ args = sys.argv[1:]
+ if '-l' in args: # cvs is an idiot
+ i = args.index('-l')
+ args = args[i:i+2]+args
+ del args[i+2:i+4]
+ options = ClientOptions()
+ try:
+ options.parseOptions(args)
+ except usage.UsageError, u:
+ print 'ERROR: %s' % u
+ sys.exit(1)
+ if options['log']:
+ realout = sys.stdout
+ log.startLogging(sys.stderr)
+ sys.stdout = realout
+ else:
+ log.discardLogs()
+ doConnect(options)
+ reactor.run()
+# prof.stop()
+# prof.close()
+
+def handleError():
+ global exitStatus
+ exitStatus = 2
+ try:
+ reactor.stop()
+ except: pass
+ log.err(failure.Failure())
+ raise
+
+def doConnect(options):
+# log.deferr = handleError # HACK
+ if '@' in options['host']:
+ options['user'], options['host'] = options['host'].split('@',1)
+ host = options['host']
+ if not options['user']:
+ options['user'] = getpass.getuser()
+ if not options['port']:
+ options['port'] = 22
+ else:
+ options['port'] = int(options['port'])
+ host = options['host']
+ port = options['port']
+ conn = SSHConnection()
+ conn.options = options
+ vhk = default.verifyHostKey
+ uao = default.SSHUserAuthClient(options['user'], options, conn)
+ connect.connect(host, port, options, vhk, uao).addErrback(_ebExit)
+
+def _ebExit(f):
+ #global exitStatus
+ if hasattr(f.value, 'value'):
+ s = f.value.value
+ else:
+ s = str(f)
+ print s
+ #exitStatus = "conch: exiting with error %s" % f
+ try:
+ reactor.stop()
+ except: pass
+
+def _ignore(*args): pass
+
+class FileWrapper:
+
+ def __init__(self, f):
+ self.f = f
+ self.total = 0.0
+ f.seek(0, 2) # seek to the end
+ self.size = f.tell()
+
+ def __getattr__(self, attr):
+ return getattr(self.f, attr)
+
+class StdioClient(basic.LineReceiver):
+
+ _pwd = pwd
+
+ ps = 'cftp> '
+ delimiter = '\n'
+
+ def __init__(self, client, f = None):
+ self.client = client
+ self.currentDirectory = ''
+ self.file = f
+ self.useProgressBar = (not f and 1) or 0
+
+ def connectionMade(self):
+ self.client.realPath('').addCallback(self._cbSetCurDir)
+
+ def _cbSetCurDir(self, path):
+ self.currentDirectory = path
+ self._newLine()
+
+ def lineReceived(self, line):
+ if self.client.transport.localClosed:
+ return
+ log.msg('got line %s' % repr(line))
+ line = line.lstrip()
+ if not line:
+ self._newLine()
+ return
+ if self.file and line.startswith('-'):
+ self.ignoreErrors = 1
+ line = line[1:]
+ else:
+ self.ignoreErrors = 0
+ d = self._dispatchCommand(line)
+ if d is not None:
+ d.addCallback(self._cbCommand)
+ d.addErrback(self._ebCommand)
+
+
+ def _dispatchCommand(self, line):
+ if ' ' in line:
+ command, rest = line.split(' ', 1)
+ rest = rest.lstrip()
+ else:
+ command, rest = line, ''
+ if command.startswith('!'): # command
+ f = self.cmd_EXEC
+ rest = (command[1:] + ' ' + rest).strip()
+ else:
+ command = command.upper()
+ log.msg('looking up cmd %s' % command)
+ f = getattr(self, 'cmd_%s' % command, None)
+ if f is not None:
+ return defer.maybeDeferred(f, rest)
+ else:
+ self._ebCommand(failure.Failure(NotImplementedError(
+ "No command called `%s'" % command)))
+ self._newLine()
+
+ def _printFailure(self, f):
+ log.msg(f)
+ e = f.trap(NotImplementedError, filetransfer.SFTPError, OSError, IOError)
+ if e == NotImplementedError:
+ self.transport.write(self.cmd_HELP(''))
+ elif e == filetransfer.SFTPError:
+ self.transport.write("remote error %i: %s\n" %
+ (f.value.code, f.value.message))
+ elif e in (OSError, IOError):
+ self.transport.write("local error %i: %s\n" %
+ (f.value.errno, f.value.strerror))
+
+ def _newLine(self):
+ if self.client.transport.localClosed:
+ return
+ self.transport.write(self.ps)
+ self.ignoreErrors = 0
+ if self.file:
+ l = self.file.readline()
+ if not l:
+ self.client.transport.loseConnection()
+ else:
+ self.transport.write(l)
+ self.lineReceived(l.strip())
+
+ def _cbCommand(self, result):
+ if result is not None:
+ self.transport.write(result)
+ if not result.endswith('\n'):
+ self.transport.write('\n')
+ self._newLine()
+
+ def _ebCommand(self, f):
+ self._printFailure(f)
+ if self.file and not self.ignoreErrors:
+ self.client.transport.loseConnection()
+ self._newLine()
+
+ def cmd_CD(self, path):
+ path, rest = self._getFilename(path)
+ if not path.endswith('/'):
+ path += '/'
+ newPath = path and os.path.join(self.currentDirectory, path) or ''
+ d = self.client.openDirectory(newPath)
+ d.addCallback(self._cbCd)
+ d.addErrback(self._ebCommand)
+ return d
+
+ def _cbCd(self, directory):
+ directory.close()
+ d = self.client.realPath(directory.name)
+ d.addCallback(self._cbCurDir)
+ return d
+
+ def _cbCurDir(self, path):
+ self.currentDirectory = path
+
+ def cmd_CHGRP(self, rest):
+ grp, rest = rest.split(None, 1)
+ path, rest = self._getFilename(rest)
+ grp = int(grp)
+ d = self.client.getAttrs(path)
+ d.addCallback(self._cbSetUsrGrp, path, grp=grp)
+ return d
+
+ def cmd_CHMOD(self, rest):
+ mod, rest = rest.split(None, 1)
+ path, rest = self._getFilename(rest)
+ mod = int(mod, 8)
+ d = self.client.setAttrs(path, {'permissions':mod})
+ d.addCallback(_ignore)
+ return d
+
+ def cmd_CHOWN(self, rest):
+ usr, rest = rest.split(None, 1)
+ path, rest = self._getFilename(rest)
+ usr = int(usr)
+ d = self.client.getAttrs(path)
+ d.addCallback(self._cbSetUsrGrp, path, usr=usr)
+ return d
+
+ def _cbSetUsrGrp(self, attrs, path, usr=None, grp=None):
+ new = {}
+ new['uid'] = (usr is not None) and usr or attrs['uid']
+ new['gid'] = (grp is not None) and grp or attrs['gid']
+ d = self.client.setAttrs(path, new)
+ d.addCallback(_ignore)
+ return d
+
+ def cmd_GET(self, rest):
+ remote, rest = self._getFilename(rest)
+ if '*' in remote or '?' in remote: # wildcard
+ if rest:
+ local, rest = self._getFilename(rest)
+ if not os.path.isdir(local):
+ return "Wildcard get with non-directory target."
+ else:
+ local = ''
+ d = self._remoteGlob(remote)
+ d.addCallback(self._cbGetMultiple, local)
+ return d
+ if rest:
+ local, rest = self._getFilename(rest)
+ else:
+ local = os.path.split(remote)[1]
+ log.msg((remote, local))
+ lf = file(local, 'w', 0)
+ path = os.path.join(self.currentDirectory, remote)
+ d = self.client.openFile(path, filetransfer.FXF_READ, {})
+ d.addCallback(self._cbGetOpenFile, lf)
+ d.addErrback(self._ebCloseLf, lf)
+ return d
+
+ def _cbGetMultiple(self, files, local):
+ #if self._useProgressBar: # one at a time
+ # XXX this can be optimized for times w/o progress bar
+ return self._cbGetMultipleNext(None, files, local)
+
+ def _cbGetMultipleNext(self, res, files, local):
+ if isinstance(res, failure.Failure):
+ self._printFailure(res)
+ elif res:
+ self.transport.write(res)
+ if not res.endswith('\n'):
+ self.transport.write('\n')
+ if not files:
+ return
+ f = files.pop(0)[0]
+ lf = file(os.path.join(local, os.path.split(f)[1]), 'w', 0)
+ path = os.path.join(self.currentDirectory, f)
+ d = self.client.openFile(path, filetransfer.FXF_READ, {})
+ d.addCallback(self._cbGetOpenFile, lf)
+ d.addErrback(self._ebCloseLf, lf)
+ d.addBoth(self._cbGetMultipleNext, files, local)
+ return d
+
+ def _ebCloseLf(self, f, lf):
+ lf.close()
+ return f
+
+ def _cbGetOpenFile(self, rf, lf):
+ return rf.getAttrs().addCallback(self._cbGetFileSize, rf, lf)
+
+ def _cbGetFileSize(self, attrs, rf, lf):
+ if not stat.S_ISREG(attrs['permissions']):
+ rf.close()
+ lf.close()
+ return "Can't get non-regular file: %s" % rf.name
+ rf.size = attrs['size']
+ bufferSize = self.client.transport.conn.options['buffersize']
+ numRequests = self.client.transport.conn.options['requests']
+ rf.total = 0.0
+ dList = []
+ chunks = []
+ startTime = time.time()
+ for i in range(numRequests):
+ d = self._cbGetRead('', rf, lf, chunks, 0, bufferSize, startTime)
+ dList.append(d)
+ dl = defer.DeferredList(dList, fireOnOneErrback=1)
+ dl.addCallback(self._cbGetDone, rf, lf)
+ return dl
+
+ def _getNextChunk(self, chunks):
+ end = 0
+ for chunk in chunks:
+ if end == 'eof':
+ return # nothing more to get
+ if end != chunk[0]:
+ i = chunks.index(chunk)
+ chunks.insert(i, (end, chunk[0]))
+ return (end, chunk[0] - end)
+ end = chunk[1]
+ bufSize = int(self.client.transport.conn.options['buffersize'])
+ chunks.append((end, end + bufSize))
+ return (end, bufSize)
+
+ def _cbGetRead(self, data, rf, lf, chunks, start, size, startTime):
+ if data and isinstance(data, failure.Failure):
+ log.msg('get read err: %s' % data)
+ reason = data
+ reason.trap(EOFError)
+ i = chunks.index((start, start + size))
+ del chunks[i]
+ chunks.insert(i, (start, 'eof'))
+ elif data:
+ log.msg('get read data: %i' % len(data))
+ lf.seek(start)
+ lf.write(data)
+ if len(data) != size:
+ log.msg('got less than we asked for: %i < %i' %
+ (len(data), size))
+ i = chunks.index((start, start + size))
+ del chunks[i]
+ chunks.insert(i, (start, start + len(data)))
+ rf.total += len(data)
+ if self.useProgressBar:
+ self._printProgessBar(rf, startTime)
+ chunk = self._getNextChunk(chunks)
+ if not chunk:
+ return
+ else:
+ start, length = chunk
+ log.msg('asking for %i -> %i' % (start, start+length))
+ d = rf.readChunk(start, length)
+ d.addBoth(self._cbGetRead, rf, lf, chunks, start, length, startTime)
+ return d
+
+ def _cbGetDone(self, ignored, rf, lf):
+ log.msg('get done')
+ rf.close()
+ lf.close()
+ if self.useProgressBar:
+ self.transport.write('\n')
+ return "Transferred %s to %s" % (rf.name, lf.name)
+
+ def cmd_PUT(self, rest):
+ local, rest = self._getFilename(rest)
+ if '*' in local or '?' in local: # wildcard
+ if rest:
+ remote, rest = self._getFilename(rest)
+ path = os.path.join(self.currentDirectory, remote)
+ d = self.client.getAttrs(path)
+ d.addCallback(self._cbPutTargetAttrs, remote, local)
+ return d
+ else:
+ remote = ''
+ files = glob.glob(local)
+ return self._cbPutMultipleNext(None, files, remote)
+ if rest:
+ remote, rest = self._getFilename(rest)
+ else:
+ remote = os.path.split(local)[1]
+ lf = file(local, 'r')
+ path = os.path.join(self.currentDirectory, remote)
+ flags = filetransfer.FXF_WRITE|filetransfer.FXF_CREAT|filetransfer.FXF_TRUNC
+ d = self.client.openFile(path, flags, {})
+ d.addCallback(self._cbPutOpenFile, lf)
+ d.addErrback(self._ebCloseLf, lf)
+ return d
+
+ def _cbPutTargetAttrs(self, attrs, path, local):
+ if not stat.S_ISDIR(attrs['permissions']):
+ return "Wildcard put with non-directory target."
+ return self._cbPutMultipleNext(None, files, path)
+
+ def _cbPutMultipleNext(self, res, files, path):
+ if isinstance(res, failure.Failure):
+ self._printFailure(res)
+ elif res:
+ self.transport.write(res)
+ if not res.endswith('\n'):
+ self.transport.write('\n')
+ f = None
+ while files and not f:
+ try:
+ f = files.pop(0)
+ lf = file(f, 'r')
+ except:
+ self._printFailure(failure.Failure())
+ f = None
+ if not f:
+ return
+ name = os.path.split(f)[1]
+ remote = os.path.join(self.currentDirectory, path, name)
+ log.msg((name, remote, path))
+ flags = filetransfer.FXF_WRITE|filetransfer.FXF_CREAT|filetransfer.FXF_TRUNC
+ d = self.client.openFile(remote, flags, {})
+ d.addCallback(self._cbPutOpenFile, lf)
+ d.addErrback(self._ebCloseLf, lf)
+ d.addBoth(self._cbPutMultipleNext, files, path)
+ return d
+
+ def _cbPutOpenFile(self, rf, lf):
+ numRequests = self.client.transport.conn.options['requests']
+ if self.useProgressBar:
+ lf = FileWrapper(lf)
+ dList = []
+ chunks = []
+ startTime = time.time()
+ for i in range(numRequests):
+ d = self._cbPutWrite(None, rf, lf, chunks, startTime)
+ if d:
+ dList.append(d)
+ dl = defer.DeferredList(dList, fireOnOneErrback=1)
+ dl.addCallback(self._cbPutDone, rf, lf)
+ return dl
+
+ def _cbPutWrite(self, ignored, rf, lf, chunks, startTime):
+ chunk = self._getNextChunk(chunks)
+ start, size = chunk
+ lf.seek(start)
+ data = lf.read(size)
+ if self.useProgressBar:
+ lf.total += len(data)
+ self._printProgessBar(lf, startTime)
+ if data:
+ d = rf.writeChunk(start, data)
+ d.addCallback(self._cbPutWrite, rf, lf, chunks, startTime)
+ return d
+ else:
+ return
+
+ def _cbPutDone(self, ignored, rf, lf):
+ lf.close()
+ rf.close()
+ if self.useProgressBar:
+ self.transport.write('\n')
+ return 'Transferred %s to %s' % (lf.name, rf.name)
+
+ def cmd_LCD(self, path):
+ os.chdir(path)
+
+ def cmd_LN(self, rest):
+ linkpath, rest = self._getFilename(rest)
+ targetpath, rest = self._getFilename(rest)
+ linkpath, targetpath = map(
+ lambda x: os.path.join(self.currentDirectory, x),
+ (linkpath, targetpath))
+ return self.client.makeLink(linkpath, targetpath).addCallback(_ignore)
+
+ def cmd_LS(self, rest):
+ # possible lines:
+ # ls current directory
+ # ls name_of_file that file
+ # ls name_of_directory that directory
+ # ls some_glob_string current directory, globbed for that string
+ options = []
+ rest = rest.split()
+ while rest and rest[0] and rest[0][0] == '-':
+ opts = rest.pop(0)[1:]
+ for o in opts:
+ if o == 'l':
+ options.append('verbose')
+ elif o == 'a':
+ options.append('all')
+ rest = ' '.join(rest)
+ path, rest = self._getFilename(rest)
+ if not path:
+ fullPath = self.currentDirectory + '/'
+ else:
+ fullPath = os.path.join(self.currentDirectory, path)
+ d = self._remoteGlob(fullPath)
+ d.addCallback(self._cbDisplayFiles, options)
+ return d
+
+ def _cbDisplayFiles(self, files, options):
+ files.sort()
+ if 'all' not in options:
+ files = [f for f in files if not f[0].startswith('.')]
+ if 'verbose' in options:
+ lines = [f[1] for f in files]
+ else:
+ lines = [f[0] for f in files]
+ if not lines:
+ return None
+ else:
+ return '\n'.join(lines)
+
+ def cmd_MKDIR(self, path):
+ path, rest = self._getFilename(path)
+ path = os.path.join(self.currentDirectory, path)
+ return self.client.makeDirectory(path, {}).addCallback(_ignore)
+
+ def cmd_RMDIR(self, path):
+ path, rest = self._getFilename(path)
+ path = os.path.join(self.currentDirectory, path)
+ return self.client.removeDirectory(path).addCallback(_ignore)
+
+ def cmd_LMKDIR(self, path):
+ os.system("mkdir %s" % path)
+
+ def cmd_RM(self, path):
+ path, rest = self._getFilename(path)
+ path = os.path.join(self.currentDirectory, path)
+ return self.client.removeFile(path).addCallback(_ignore)
+
+ def cmd_LLS(self, rest):
+ os.system("ls %s" % rest)
+
+ def cmd_RENAME(self, rest):
+ oldpath, rest = self._getFilename(rest)
+ newpath, rest = self._getFilename(rest)
+ oldpath, newpath = map (
+ lambda x: os.path.join(self.currentDirectory, x),
+ (oldpath, newpath))
+ return self.client.renameFile(oldpath, newpath).addCallback(_ignore)
+
+ def cmd_EXIT(self, ignored):
+ self.client.transport.loseConnection()
+
+ cmd_QUIT = cmd_EXIT
+
+ def cmd_VERSION(self, ignored):
+ return "SFTP version %i" % self.client.version
+
+ def cmd_HELP(self, ignored):
+ return """Available commands:
+cd path Change remote directory to 'path'.
+chgrp gid path Change gid of 'path' to 'gid'.
+chmod mode path Change mode of 'path' to 'mode'.
+chown uid path Change uid of 'path' to 'uid'.
+exit Disconnect from the server.
+get remote-path [local-path] Get remote file.
+help Get a list of available commands.
+lcd path Change local directory to 'path'.
+lls [ls-options] [path] Display local directory listing.
+lmkdir path Create local directory.
+ln linkpath targetpath Symlink remote file.
+lpwd Print the local working directory.
+ls [-l] [path] Display remote directory listing.
+mkdir path Create remote directory.
+progress Toggle progress bar.
+put local-path [remote-path] Put local file.
+pwd Print the remote working directory.
+quit Disconnect from the server.
+rename oldpath newpath Rename remote file.
+rmdir path Remove remote directory.
+rm path Remove remote file.
+version Print the SFTP version.
+? Synonym for 'help'.
+"""
+
+ def cmd_PWD(self, ignored):
+ return self.currentDirectory
+
+ def cmd_LPWD(self, ignored):
+ return os.getcwd()
+
+ def cmd_PROGRESS(self, ignored):
+ self.useProgressBar = not self.useProgressBar
+ return "%ssing progess bar." % (self.useProgressBar and "U" or "Not u")
+
+ def cmd_EXEC(self, rest):
+ """
+ Run C{rest} using the user's shell (or /bin/sh if they do not have
+ one).
+ """
+ shell = self._pwd.getpwnam(getpass.getuser())[6]
+ if not shell:
+ shell = '/bin/sh'
+ if rest:
+ cmds = ['-c', rest]
+ return utils.getProcessOutput(shell, cmds, errortoo=1)
+ else:
+ os.system(shell)
+
+ # accessory functions
+
+ def _remoteGlob(self, fullPath):
+ log.msg('looking up %s' % fullPath)
+ head, tail = os.path.split(fullPath)
+ if '*' in tail or '?' in tail:
+ glob = 1
+ else:
+ glob = 0
+ if tail and not glob: # could be file or directory
+ # try directory first
+ d = self.client.openDirectory(fullPath)
+ d.addCallback(self._cbOpenList, '')
+ d.addErrback(self._ebNotADirectory, head, tail)
+ else:
+ d = self.client.openDirectory(head)
+ d.addCallback(self._cbOpenList, tail)
+ return d
+
+ def _cbOpenList(self, directory, glob):
+ files = []
+ d = directory.read()
+ d.addBoth(self._cbReadFile, files, directory, glob)
+ return d
+
+ def _ebNotADirectory(self, reason, path, glob):
+ d = self.client.openDirectory(path)
+ d.addCallback(self._cbOpenList, glob)
+ return d
+
+ def _cbReadFile(self, files, l, directory, glob):
+ if not isinstance(files, failure.Failure):
+ if glob:
+ l.extend([f for f in files if fnmatch.fnmatch(f[0], glob)])
+ else:
+ l.extend(files)
+ d = directory.read()
+ d.addBoth(self._cbReadFile, l, directory, glob)
+ return d
+ else:
+ reason = files
+ reason.trap(EOFError)
+ directory.close()
+ return l
+
+ def _abbrevSize(self, size):
+ # from http://mail.python.org/pipermail/python-list/1999-December/018395.html
+ _abbrevs = [
+ (1<<50L, 'PB'),
+ (1<<40L, 'TB'),
+ (1<<30L, 'GB'),
+ (1<<20L, 'MB'),
+ (1<<10L, 'kb'),
+ (1, '')
+ ]
+
+ for factor, suffix in _abbrevs:
+ if size > factor:
+ break
+ return '%.1f' % (size/factor) + suffix
+
+ def _abbrevTime(self, t):
+ if t > 3600: # 1 hour
+ hours = int(t / 3600)
+ t -= (3600 * hours)
+ mins = int(t / 60)
+ t -= (60 * mins)
+ return "%i:%02i:%02i" % (hours, mins, t)
+ else:
+ mins = int(t/60)
+ t -= (60 * mins)
+ return "%02i:%02i" % (mins, t)
+
+ def _printProgessBar(self, f, startTime):
+ diff = time.time() - startTime
+ total = f.total
+ try:
+ winSize = struct.unpack('4H',
+ fcntl.ioctl(0, tty.TIOCGWINSZ, '12345679'))
+ except IOError:
+ winSize = [None, 80]
+ speed = total/diff
+ if speed:
+ timeLeft = (f.size - total) / speed
+ else:
+ timeLeft = 0
+ front = f.name
+ back = '%3i%% %s %sps %s ' % ((total/f.size)*100, self._abbrevSize(total),
+ self._abbrevSize(total/diff), self._abbrevTime(timeLeft))
+ spaces = (winSize[1] - (len(front) + len(back) + 1)) * ' '
+ self.transport.write('\r%s%s%s' % (front, spaces, back))
+
+ def _getFilename(self, line):
+ line.lstrip()
+ if not line:
+ return None, ''
+ if line[0] in '\'"':
+ ret = []
+ line = list(line)
+ try:
+ for i in range(1,len(line)):
+ c = line[i]
+ if c == line[0]:
+ return ''.join(ret), ''.join(line[i+1:]).lstrip()
+ elif c == '\\': # quoted character
+ del line[i]
+ if line[i] not in '\'"\\':
+ raise IndexError, "bad quote: \\%s" % line[i]
+ ret.append(line[i])
+ else:
+ ret.append(line[i])
+ except IndexError:
+ raise IndexError, "unterminated quote"
+ ret = line.split(None, 1)
+ if len(ret) == 1:
+ return ret[0], ''
+ else:
+ return ret
+
+StdioClient.__dict__['cmd_?'] = StdioClient.cmd_HELP
+
+class SSHConnection(connection.SSHConnection):
+ def serviceStarted(self):
+ self.openChannel(SSHSession())
+
+class SSHSession(channel.SSHChannel):
+
+ name = 'session'
+
+ def channelOpen(self, foo):
+ log.msg('session %s open' % self.id)
+ if self.conn.options['subsystem'].startswith('/'):
+ request = 'exec'
+ else:
+ request = 'subsystem'
+ d = self.conn.sendRequest(self, request, \
+ common.NS(self.conn.options['subsystem']), wantReply=1)
+ d.addCallback(self._cbSubsystem)
+ d.addErrback(_ebExit)
+
+ def _cbSubsystem(self, result):
+ self.client = filetransfer.FileTransferClient()
+ self.client.makeConnection(self)
+ self.dataReceived = self.client.dataReceived
+ f = None
+ if self.conn.options['batchfile']:
+ fn = self.conn.options['batchfile']
+ if fn != '-':
+ f = file(fn)
+ self.stdio = stdio.StandardIO(StdioClient(self.client, f))
+
+ def extReceived(self, t, data):
+ if t==connection.EXTENDED_DATA_STDERR:
+ log.msg('got %s stderr data' % len(data))
+ sys.stderr.write(data)
+ sys.stderr.flush()
+
+ def eofReceived(self):
+ log.msg('got eof')
+ self.stdio.closeStdin()
+
+ def closeReceived(self):
+ log.msg('remote side closed %s' % self)
+ self.conn.sendClose(self)
+
+ def closed(self):
+ try:
+ reactor.stop()
+ except:
+ pass
+
+ def stopWriting(self):
+ self.stdio.pauseProducing()
+
+ def startWriting(self):
+ self.stdio.resumeProducing()
+
+if __name__ == '__main__':
+ run()
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/scripts/ckeygen.py b/vendor/Twisted-10.0.0/twisted/conch/scripts/ckeygen.py
new file mode 100644
index 0000000000..87ede544f0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/scripts/ckeygen.py
@@ -0,0 +1,188 @@
+# -*- test-case-name: twisted.conch.test.test_ckeygen -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implementation module for the `ckeygen` command.
+"""
+
+import sys, os, getpass, socket
+if getpass.getpass == getpass.unix_getpass:
+ try:
+ import termios # hack around broken termios
+ termios.tcgetattr, termios.tcsetattr
+ except (ImportError, AttributeError):
+ sys.modules['termios'] = None
+ reload(getpass)
+
+from twisted.conch.ssh import keys
+from twisted.python import filepath, log, usage, randbytes
+
+
+class GeneralOptions(usage.Options):
+ synopsis = """Usage: ckeygen [options]
+ """
+
+ longdesc = "ckeygen manipulates public/private keys in various ways."
+
+ optParameters = [['bits', 'b', 1024, 'Number of bits in the key to create.'],
+ ['filename', 'f', None, 'Filename of the key file.'],
+ ['type', 't', None, 'Specify type of key to create.'],
+ ['comment', 'C', None, 'Provide new comment.'],
+ ['newpass', 'N', None, 'Provide new passphrase.'],
+ ['pass', 'P', None, 'Provide old passphrase']]
+
+ optFlags = [['fingerprint', 'l', 'Show fingerprint of key file.'],
+ ['changepass', 'p', 'Change passphrase of private key file.'],
+ ['quiet', 'q', 'Quiet.'],
+ ['showpub', 'y', 'Read private key file and print public key.']]
+
+ #zsh_altArgDescr = {"bits":"Number of bits in the key (default: 1024)"}
+ #zsh_multiUse = ["foo", "bar"]
+ #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
+ zsh_actions = {"type":"(rsa dsa)"}
+ #zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
+
+def run():
+ options = GeneralOptions()
+ try:
+ options.parseOptions(sys.argv[1:])
+ except usage.UsageError, u:
+ print 'ERROR: %s' % u
+ options.opt_help()
+ sys.exit(1)
+ log.discardLogs()
+ log.deferr = handleError # HACK
+ if options['type']:
+ if options['type'] == 'rsa':
+ generateRSAkey(options)
+ elif options['type'] == 'dsa':
+ generateDSAkey(options)
+ else:
+ sys.exit('Key type was %s, must be one of: rsa, dsa' % options['type'])
+ elif options['fingerprint']:
+ printFingerprint(options)
+ elif options['changepass']:
+ changePassPhrase(options)
+ elif options['showpub']:
+ displayPublicKey(options)
+ else:
+ options.opt_help()
+ sys.exit(1)
+
+def handleError():
+ from twisted.python import failure
+ global exitStatus
+ exitStatus = 2
+ log.err(failure.Failure())
+ reactor.stop()
+ raise
+
+def generateRSAkey(options):
+ from Crypto.PublicKey import RSA
+ print 'Generating public/private rsa key pair.'
+ key = RSA.generate(int(options['bits']), randbytes.secureRandom)
+ _saveKey(key, options)
+
+def generateDSAkey(options):
+ from Crypto.PublicKey import DSA
+ print 'Generating public/private dsa key pair.'
+ key = DSA.generate(int(options['bits']), randbytes.secureRandom)
+ _saveKey(key, options)
+
+
+def printFingerprint(options):
+ if not options['filename']:
+ filename = os.path.expanduser('~/.ssh/id_rsa')
+ options['filename'] = raw_input('Enter file in which the key is (%s): ' % filename)
+ if os.path.exists(options['filename']+'.pub'):
+ options['filename'] += '.pub'
+ try:
+ key = keys.Key.fromFile(options['filename'])
+ obj = key.keyObject
+ string = key.blob()
+ print '%s %s %s' % (
+ obj.size() + 1,
+ key.fingerprint(),
+ os.path.basename(options['filename']))
+ except:
+ sys.exit('bad key')
+
+def changePassPhrase(options):
+ if not options['filename']:
+ filename = os.path.expanduser('~/.ssh/id_rsa')
+ options['filename'] = raw_input('Enter file in which the key is (%s): ' % filename)
+ try:
+ key = keys.getPrivateKeyObject(options['filename'])
+ except keys.BadKeyError, e:
+ if e.args[0] != 'encrypted key with no passphrase':
+ raise
+ else:
+ if not options['pass']:
+ options['pass'] = getpass.getpass('Enter old passphrase: ')
+ key = keys.getPrivateKeyObject(options['filename'], passphrase = options['pass'])
+ if not options['newpass']:
+ while 1:
+ p1 = getpass.getpass('Enter new passphrase (empty for no passphrase): ')
+ p2 = getpass.getpass('Enter same passphrase again: ')
+ if p1 == p2:
+ break
+ print 'Passphrases do not match. Try again.'
+ options['newpass'] = p1
+ open(options['filename'], 'w').write(
+ keys.makePrivateKeyString(key, passphrase=options['newpass']))
+ print 'Your identification has been saved with the new passphrase.'
+
+def displayPublicKey(options):
+ if not options['filename']:
+ filename = os.path.expanduser('~/.ssh/id_rsa')
+ options['filename'] = raw_input('Enter file in which the key is (%s): ' % filename)
+ try:
+ key = keys.getPrivateKeyObject(options['filename'])
+ except keys.BadKeyError, e:
+ if e.args[0] != 'encrypted key with no passphrase':
+ raise
+ else:
+ if not options['pass']:
+ options['pass'] = getpass.getpass('Enter passphrase: ')
+ key = keys.getPrivateKeyObject(options['filename'], passphrase = options['pass'])
+ print keys.makePublicKeyString(key)
+
+def _saveKey(key, options):
+ if not options['filename']:
+ kind = keys.objectType(key)
+ kind = {'ssh-rsa':'rsa','ssh-dss':'dsa'}[kind]
+ filename = os.path.expanduser('~/.ssh/id_%s'%kind)
+ options['filename'] = raw_input('Enter file in which to save the key (%s): '%filename).strip() or filename
+ if os.path.exists(options['filename']):
+ print '%s already exists.' % options['filename']
+ yn = raw_input('Overwrite (y/n)? ')
+ if yn[0].lower() != 'y':
+ sys.exit()
+ if not options['pass']:
+ while 1:
+ p1 = getpass.getpass('Enter passphrase (empty for no passphrase): ')
+ p2 = getpass.getpass('Enter same passphrase again: ')
+ if p1 == p2:
+ break
+ print 'Passphrases do not match. Try again.'
+ options['pass'] = p1
+
+ keyObj = keys.Key(key)
+ comment = '%s@%s' % (getpass.getuser(), socket.gethostname())
+
+ filepath.FilePath(options['filename']).setContent(
+ keyObj.toString('openssh', options['pass']))
+ os.chmod(options['filename'], 33152)
+
+ filepath.FilePath(options['filename'] + '.pub').setContent(
+ keyObj.public().toString('openssh', comment))
+
+ print 'Your identification has been saved in %s' % options['filename']
+ print 'Your public key has been saved in %s.pub' % options['filename']
+ print 'The key fingerprint is:'
+ print keyObj.fingerprint()
+
+if __name__ == '__main__':
+ run()
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/scripts/conch.py b/vendor/Twisted-10.0.0/twisted/conch/scripts/conch.py
new file mode 100644
index 0000000000..7649e3bf08
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/scripts/conch.py
@@ -0,0 +1,510 @@
+# -*- test-case-name: twisted.conch.test.test_conch -*-
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+# $Id: conch.py,v 1.65 2004/03/11 00:29:14 z3p Exp $
+
+#""" Implementation module for the `conch` command.
+#"""
+from twisted.conch.client import connect, default, options
+from twisted.conch.error import ConchError
+from twisted.conch.ssh import connection, common
+from twisted.conch.ssh import session, forwarding, channel
+from twisted.internet import reactor, stdio, task
+from twisted.python import log, usage
+
+import os, sys, getpass, struct, tty, fcntl, signal
+
+class ClientOptions(options.ConchOptions):
+
+ synopsis = """Usage: conch [options] host [command]
+"""
+ longdesc = ("conch is a SSHv2 client that allows logging into a remote "
+ "machine and executing commands.")
+
+ optParameters = [['escape', 'e', '~'],
+ ['localforward', 'L', None, 'listen-port:host:port Forward local port to remote address'],
+ ['remoteforward', 'R', None, 'listen-port:host:port Forward remote port to local address'],
+ ]
+
+ optFlags = [['null', 'n', 'Redirect input from /dev/null.'],
+ ['fork', 'f', 'Fork to background after authentication.'],
+ ['tty', 't', 'Tty; allocate a tty even if command is given.'],
+ ['notty', 'T', 'Do not allocate a tty.'],
+ ['noshell', 'N', 'Do not execute a shell or command.'],
+ ['subsystem', 's', 'Invoke command (mandatory) as SSH2 subsystem.'],
+ ]
+
+ #zsh_altArgDescr = {"foo":"use this description for foo instead"}
+ #zsh_multiUse = ["foo", "bar"]
+ #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
+ #zsh_actions = {"foo":'_files -g "*.foo"', "bar":"(one two three)"}
+ zsh_actionDescr = {"localforward":"listen-port:host:port",
+ "remoteforward":"listen-port:host:port"}
+ zsh_extras = ["*:command: "]
+
+ localForwards = []
+ remoteForwards = []
+
+ def opt_escape(self, esc):
+ "Set escape character; ``none'' = disable"
+ if esc == 'none':
+ self['escape'] = None
+ elif esc[0] == '^' and len(esc) == 2:
+ self['escape'] = chr(ord(esc[1])-64)
+ elif len(esc) == 1:
+ self['escape'] = esc
+ else:
+ sys.exit("Bad escape character '%s'." % esc)
+
+ def opt_localforward(self, f):
+ "Forward local port to remote address (lport:host:port)"
+ localPort, remoteHost, remotePort = f.split(':') # doesn't do v6 yet
+ localPort = int(localPort)
+ remotePort = int(remotePort)
+ self.localForwards.append((localPort, (remoteHost, remotePort)))
+
+ def opt_remoteforward(self, f):
+ """Forward remote port to local address (rport:host:port)"""
+ remotePort, connHost, connPort = f.split(':') # doesn't do v6 yet
+ remotePort = int(remotePort)
+ connPort = int(connPort)
+ self.remoteForwards.append((remotePort, (connHost, connPort)))
+
+ def parseArgs(self, host, *command):
+ self['host'] = host
+ self['command'] = ' '.join(command)
+
+# Rest of code in "run"
+options = None
+conn = None
+exitStatus = 0
+old = None
+_inRawMode = 0
+_savedRawMode = None
+
+def run():
+ global options, old
+ args = sys.argv[1:]
+ if '-l' in args: # cvs is an idiot
+ i = args.index('-l')
+ args = args[i:i+2]+args
+ del args[i+2:i+4]
+ for arg in args[:]:
+ try:
+ i = args.index(arg)
+ if arg[:2] == '-o' and args[i+1][0]!='-':
+ args[i:i+2] = [] # suck on it scp
+ except ValueError:
+ pass
+ options = ClientOptions()
+ try:
+ options.parseOptions(args)
+ except usage.UsageError, u:
+ print 'ERROR: %s' % u
+ options.opt_help()
+ sys.exit(1)
+ if options['log']:
+ if options['logfile']:
+ if options['logfile'] == '-':
+ f = sys.stdout
+ else:
+ f = file(options['logfile'], 'a+')
+ else:
+ f = sys.stderr
+ realout = sys.stdout
+ log.startLogging(f)
+ sys.stdout = realout
+ else:
+ log.discardLogs()
+ doConnect()
+ fd = sys.stdin.fileno()
+ try:
+ old = tty.tcgetattr(fd)
+ except:
+ old = None
+ try:
+ oldUSR1 = signal.signal(signal.SIGUSR1, lambda *a: reactor.callLater(0, reConnect))
+ except:
+ oldUSR1 = None
+ try:
+ reactor.run()
+ finally:
+ if old:
+ tty.tcsetattr(fd, tty.TCSANOW, old)
+ if oldUSR1:
+ signal.signal(signal.SIGUSR1, oldUSR1)
+ if (options['command'] and options['tty']) or not options['notty']:
+ signal.signal(signal.SIGWINCH, signal.SIG_DFL)
+ if sys.stdout.isatty() and not options['command']:
+ print 'Connection to %s closed.' % options['host']
+ sys.exit(exitStatus)
+
+def handleError():
+ from twisted.python import failure
+ global exitStatus
+ exitStatus = 2
+ reactor.callLater(0.01, _stopReactor)
+ log.err(failure.Failure())
+ raise
+
+def _stopReactor():
+ try:
+ reactor.stop()
+ except: pass
+
+def doConnect():
+# log.deferr = handleError # HACK
+ if '@' in options['host']:
+ options['user'], options['host'] = options['host'].split('@',1)
+ if not options.identitys:
+ options.identitys = ['~/.ssh/id_rsa', '~/.ssh/id_dsa']
+ host = options['host']
+ if not options['user']:
+ options['user'] = getpass.getuser()
+ if not options['port']:
+ options['port'] = 22
+ else:
+ options['port'] = int(options['port'])
+ host = options['host']
+ port = options['port']
+ vhk = default.verifyHostKey
+ uao = default.SSHUserAuthClient(options['user'], options, SSHConnection())
+ connect.connect(host, port, options, vhk, uao).addErrback(_ebExit)
+
+def _ebExit(f):
+ global exitStatus
+ if hasattr(f.value, 'value'):
+ s = f.value.value
+ else:
+ s = str(f)
+ exitStatus = "conch: exiting with error %s" % f
+ reactor.callLater(0.1, _stopReactor)
+
+def onConnect():
+# if keyAgent and options['agent']:
+# cc = protocol.ClientCreator(reactor, SSHAgentForwardingLocal, conn)
+# cc.connectUNIX(os.environ['SSH_AUTH_SOCK'])
+ if hasattr(conn.transport, 'sendIgnore'):
+ _KeepAlive(conn)
+ if options.localForwards:
+ for localPort, hostport in options.localForwards:
+ s = reactor.listenTCP(localPort,
+ forwarding.SSHListenForwardingFactory(conn,
+ hostport,
+ SSHListenClientForwardingChannel))
+ conn.localForwards.append(s)
+ if options.remoteForwards:
+ for remotePort, hostport in options.remoteForwards:
+ log.msg('asking for remote forwarding for %s:%s' %
+ (remotePort, hostport))
+ conn.requestRemoteForwarding(remotePort, hostport)
+ reactor.addSystemEventTrigger('before', 'shutdown', beforeShutdown)
+ if not options['noshell'] or options['agent']:
+ conn.openChannel(SSHSession())
+ if options['fork']:
+ if os.fork():
+ os._exit(0)
+ os.setsid()
+ for i in range(3):
+ try:
+ os.close(i)
+ except OSError, e:
+ import errno
+ if e.errno != errno.EBADF:
+ raise
+
+def reConnect():
+ beforeShutdown()
+ conn.transport.transport.loseConnection()
+
+def beforeShutdown():
+ remoteForwards = options.remoteForwards
+ for remotePort, hostport in remoteForwards:
+ log.msg('cancelling %s:%s' % (remotePort, hostport))
+ conn.cancelRemoteForwarding(remotePort)
+
+def stopConnection():
+ if not options['reconnect']:
+ reactor.callLater(0.1, _stopReactor)
+
+class _KeepAlive:
+
+ def __init__(self, conn):
+ self.conn = conn
+ self.globalTimeout = None
+ self.lc = task.LoopingCall(self.sendGlobal)
+ self.lc.start(300)
+
+ def sendGlobal(self):
+ d = self.conn.sendGlobalRequest("conch-keep-alive@twistedmatrix.com",
+ "", wantReply = 1)
+ d.addBoth(self._cbGlobal)
+ self.globalTimeout = reactor.callLater(30, self._ebGlobal)
+
+ def _cbGlobal(self, res):
+ if self.globalTimeout:
+ self.globalTimeout.cancel()
+ self.globalTimeout = None
+
+ def _ebGlobal(self):
+ if self.globalTimeout:
+ self.globalTimeout = None
+ self.conn.transport.loseConnection()
+
+class SSHConnection(connection.SSHConnection):
+ def serviceStarted(self):
+ global conn
+ conn = self
+ self.localForwards = []
+ self.remoteForwards = {}
+ if not isinstance(self, connection.SSHConnection):
+ # make these fall through
+ del self.__class__.requestRemoteForwarding
+ del self.__class__.cancelRemoteForwarding
+ onConnect()
+
+ def serviceStopped(self):
+ lf = self.localForwards
+ self.localForwards = []
+ for s in lf:
+ s.loseConnection()
+ stopConnection()
+
+ def requestRemoteForwarding(self, remotePort, hostport):
+ data = forwarding.packGlobal_tcpip_forward(('0.0.0.0', remotePort))
+ d = self.sendGlobalRequest('tcpip-forward', data,
+ wantReply=1)
+ log.msg('requesting remote forwarding %s:%s' %(remotePort, hostport))
+ d.addCallback(self._cbRemoteForwarding, remotePort, hostport)
+ d.addErrback(self._ebRemoteForwarding, remotePort, hostport)
+
+ def _cbRemoteForwarding(self, result, remotePort, hostport):
+ log.msg('accepted remote forwarding %s:%s' % (remotePort, hostport))
+ self.remoteForwards[remotePort] = hostport
+ log.msg(repr(self.remoteForwards))
+
+ def _ebRemoteForwarding(self, f, remotePort, hostport):
+ log.msg('remote forwarding %s:%s failed' % (remotePort, hostport))
+ log.msg(f)
+
+ def cancelRemoteForwarding(self, remotePort):
+ data = forwarding.packGlobal_tcpip_forward(('0.0.0.0', remotePort))
+ self.sendGlobalRequest('cancel-tcpip-forward', data)
+ log.msg('cancelling remote forwarding %s' % remotePort)
+ try:
+ del self.remoteForwards[remotePort]
+ except:
+ pass
+ log.msg(repr(self.remoteForwards))
+
+ def channel_forwarded_tcpip(self, windowSize, maxPacket, data):
+ log.msg('%s %s' % ('FTCP', repr(data)))
+ remoteHP, origHP = forwarding.unpackOpen_forwarded_tcpip(data)
+ log.msg(self.remoteForwards)
+ log.msg(remoteHP)
+ if self.remoteForwards.has_key(remoteHP[1]):
+ connectHP = self.remoteForwards[remoteHP[1]]
+ log.msg('connect forwarding %s' % (connectHP,))
+ return SSHConnectForwardingChannel(connectHP,
+ remoteWindow = windowSize,
+ remoteMaxPacket = maxPacket,
+ conn = self)
+ else:
+ raise ConchError(connection.OPEN_CONNECT_FAILED, "don't know about that port")
+
+# def channel_auth_agent_openssh_com(self, windowSize, maxPacket, data):
+# if options['agent'] and keyAgent:
+# return agent.SSHAgentForwardingChannel(remoteWindow = windowSize,
+# remoteMaxPacket = maxPacket,
+# conn = self)
+# else:
+# return connection.OPEN_CONNECT_FAILED, "don't have an agent"
+
+ def channelClosed(self, channel):
+ log.msg('connection closing %s' % channel)
+ log.msg(self.channels)
+ if len(self.channels) == 1: # just us left
+ log.msg('stopping connection')
+ stopConnection()
+ else:
+ # because of the unix thing
+ self.__class__.__bases__[0].channelClosed(self, channel)
+
+class SSHSession(channel.SSHChannel):
+
+ name = 'session'
+
+ def channelOpen(self, foo):
+ log.msg('session %s open' % self.id)
+ if options['agent']:
+ d = self.conn.sendRequest(self, 'auth-agent-req@openssh.com', '', wantReply=1)
+ d.addBoth(lambda x:log.msg(x))
+ if options['noshell']: return
+ if (options['command'] and options['tty']) or not options['notty']:
+ _enterRawMode()
+ c = session.SSHSessionClient()
+ if options['escape'] and not options['notty']:
+ self.escapeMode = 1
+ c.dataReceived = self.handleInput
+ else:
+ c.dataReceived = self.write
+ c.connectionLost = lambda x=None,s=self:s.sendEOF()
+ self.stdio = stdio.StandardIO(c)
+ fd = 0
+ if options['subsystem']:
+ self.conn.sendRequest(self, 'subsystem', \
+ common.NS(options['command']))
+ elif options['command']:
+ if options['tty']:
+ term = os.environ['TERM']
+ winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
+ winSize = struct.unpack('4H', winsz)
+ ptyReqData = session.packRequest_pty_req(term, winSize, '')
+ self.conn.sendRequest(self, 'pty-req', ptyReqData)
+ signal.signal(signal.SIGWINCH, self._windowResized)
+ self.conn.sendRequest(self, 'exec', \
+ common.NS(options['command']))
+ else:
+ if not options['notty']:
+ term = os.environ['TERM']
+ winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
+ winSize = struct.unpack('4H', winsz)
+ ptyReqData = session.packRequest_pty_req(term, winSize, '')
+ self.conn.sendRequest(self, 'pty-req', ptyReqData)
+ signal.signal(signal.SIGWINCH, self._windowResized)
+ self.conn.sendRequest(self, 'shell', '')
+ #if hasattr(conn.transport, 'transport'):
+ # conn.transport.transport.setTcpNoDelay(1)
+
+ def handleInput(self, char):
+ #log.msg('handling %s' % repr(char))
+ if char in ('\n', '\r'):
+ self.escapeMode = 1
+ self.write(char)
+ elif self.escapeMode == 1 and char == options['escape']:
+ self.escapeMode = 2
+ elif self.escapeMode == 2:
+ self.escapeMode = 1 # so we can chain escapes together
+ if char == '.': # disconnect
+ log.msg('disconnecting from escape')
+ stopConnection()
+ return
+ elif char == '\x1a': # ^Z, suspend
+ def _():
+ _leaveRawMode()
+ sys.stdout.flush()
+ sys.stdin.flush()
+ os.kill(os.getpid(), signal.SIGTSTP)
+ _enterRawMode()
+ reactor.callLater(0, _)
+ return
+ elif char == 'R': # rekey connection
+ log.msg('rekeying connection')
+ self.conn.transport.sendKexInit()
+ return
+ elif char == '#': # display connections
+ self.stdio.write('\r\nThe following connections are open:\r\n')
+ channels = self.conn.channels.keys()
+ channels.sort()
+ for channelId in channels:
+ self.stdio.write(' #%i %s\r\n' % (channelId, str(self.conn.channels[channelId])))
+ return
+ self.write('~' + char)
+ else:
+ self.escapeMode = 0
+ self.write(char)
+
+ def dataReceived(self, data):
+ self.stdio.write(data)
+
+ def extReceived(self, t, data):
+ if t==connection.EXTENDED_DATA_STDERR:
+ log.msg('got %s stderr data' % len(data))
+ sys.stderr.write(data)
+
+ def eofReceived(self):
+ log.msg('got eof')
+ self.stdio.loseWriteConnection()
+
+ def closeReceived(self):
+ log.msg('remote side closed %s' % self)
+ self.conn.sendClose(self)
+
+ def closed(self):
+ global old
+ log.msg('closed %s' % self)
+ log.msg(repr(self.conn.channels))
+
+ def request_exit_status(self, data):
+ global exitStatus
+ exitStatus = int(struct.unpack('>L', data)[0])
+ log.msg('exit status: %s' % exitStatus)
+
+ def sendEOF(self):
+ self.conn.sendEOF(self)
+
+ def stopWriting(self):
+ self.stdio.pauseProducing()
+
+ def startWriting(self):
+ self.stdio.resumeProducing()
+
+ def _windowResized(self, *args):
+ winsz = fcntl.ioctl(0, tty.TIOCGWINSZ, '12345678')
+ winSize = struct.unpack('4H', winsz)
+ newSize = winSize[1], winSize[0], winSize[2], winSize[3]
+ self.conn.sendRequest(self, 'window-change', struct.pack('!4L', *newSize))
+
+
+class SSHListenClientForwardingChannel(forwarding.SSHListenClientForwardingChannel): pass
+class SSHConnectForwardingChannel(forwarding.SSHConnectForwardingChannel): pass
+
+def _leaveRawMode():
+ global _inRawMode
+ if not _inRawMode:
+ return
+ fd = sys.stdin.fileno()
+ tty.tcsetattr(fd, tty.TCSANOW, _savedMode)
+ _inRawMode = 0
+
+def _enterRawMode():
+ global _inRawMode, _savedMode
+ if _inRawMode:
+ return
+ fd = sys.stdin.fileno()
+ try:
+ old = tty.tcgetattr(fd)
+ new = old[:]
+ except:
+ log.msg('not a typewriter!')
+ else:
+ # iflage
+ new[0] = new[0] | tty.IGNPAR
+ new[0] = new[0] & ~(tty.ISTRIP | tty.INLCR | tty.IGNCR | tty.ICRNL |
+ tty.IXON | tty.IXANY | tty.IXOFF)
+ if hasattr(tty, 'IUCLC'):
+ new[0] = new[0] & ~tty.IUCLC
+
+ # lflag
+ new[3] = new[3] & ~(tty.ISIG | tty.ICANON | tty.ECHO | tty.ECHO |
+ tty.ECHOE | tty.ECHOK | tty.ECHONL)
+ if hasattr(tty, 'IEXTEN'):
+ new[3] = new[3] & ~tty.IEXTEN
+
+ #oflag
+ new[1] = new[1] & ~tty.OPOST
+
+ new[6][tty.VMIN] = 1
+ new[6][tty.VTIME] = 0
+
+ _savedMode = old
+ tty.tcsetattr(fd, tty.TCSANOW, new)
+ #tty.setraw(fd)
+ _inRawMode = 1
+
+if __name__ == '__main__':
+ run()
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/scripts/tkconch.py b/vendor/Twisted-10.0.0/twisted/conch/scripts/tkconch.py
new file mode 100644
index 0000000000..da20b17538
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/scripts/tkconch.py
@@ -0,0 +1,572 @@
+# -*- test-case-name: twisted.conch.test.test_scripts -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+# $Id: tkconch.py,v 1.6 2003/02/22 08:10:15 z3p Exp $
+
+""" Implementation module for the `tkconch` command.
+"""
+
+from __future__ import nested_scopes
+
+import Tkinter, tkFileDialog, tkFont, tkMessageBox, string
+from twisted.conch.ui import tkvt100
+from twisted.conch.ssh import transport, userauth, connection, common, keys
+from twisted.conch.ssh import session, forwarding, channel
+from twisted.conch.client.default import isInKnownHosts
+from twisted.internet import reactor, defer, protocol, tksupport
+from twisted.python import usage, log
+
+import os, sys, getpass, struct, base64, signal
+
+class TkConchMenu(Tkinter.Frame):
+ def __init__(self, *args, **params):
+ ## Standard heading: initialization
+ apply(Tkinter.Frame.__init__, (self,) + args, params)
+
+ self.master.title('TkConch')
+ self.localRemoteVar = Tkinter.StringVar()
+ self.localRemoteVar.set('local')
+
+ Tkinter.Label(self, anchor='w', justify='left', text='Hostname').grid(column=1, row=1, sticky='w')
+ self.host = Tkinter.Entry(self)
+ self.host.grid(column=2, columnspan=2, row=1, sticky='nesw')
+
+ Tkinter.Label(self, anchor='w', justify='left', text='Port').grid(column=1, row=2, sticky='w')
+ self.port = Tkinter.Entry(self)
+ self.port.grid(column=2, columnspan=2, row=2, sticky='nesw')
+
+ Tkinter.Label(self, anchor='w', justify='left', text='Username').grid(column=1, row=3, sticky='w')
+ self.user = Tkinter.Entry(self)
+ self.user.grid(column=2, columnspan=2, row=3, sticky='nesw')
+
+ Tkinter.Label(self, anchor='w', justify='left', text='Command').grid(column=1, row=4, sticky='w')
+ self.command = Tkinter.Entry(self)
+ self.command.grid(column=2, columnspan=2, row=4, sticky='nesw')
+
+ Tkinter.Label(self, anchor='w', justify='left', text='Identity').grid(column=1, row=5, sticky='w')
+ self.identity = Tkinter.Entry(self)
+ self.identity.grid(column=2, row=5, sticky='nesw')
+ Tkinter.Button(self, command=self.getIdentityFile, text='Browse').grid(column=3, row=5, sticky='nesw')
+
+ Tkinter.Label(self, text='Port Forwarding').grid(column=1, row=6, sticky='w')
+ self.forwards = Tkinter.Listbox(self, height=0, width=0)
+ self.forwards.grid(column=2, columnspan=2, row=6, sticky='nesw')
+ Tkinter.Button(self, text='Add', command=self.addForward).grid(column=1, row=7)
+ Tkinter.Button(self, text='Remove', command=self.removeForward).grid(column=1, row=8)
+ self.forwardPort = Tkinter.Entry(self)
+ self.forwardPort.grid(column=2, row=7, sticky='nesw')
+ Tkinter.Label(self, text='Port').grid(column=3, row=7, sticky='nesw')
+ self.forwardHost = Tkinter.Entry(self)
+ self.forwardHost.grid(column=2, row=8, sticky='nesw')
+ Tkinter.Label(self, text='Host').grid(column=3, row=8, sticky='nesw')
+ self.localForward = Tkinter.Radiobutton(self, text='Local', variable=self.localRemoteVar, value='local')
+ self.localForward.grid(column=2, row=9)
+ self.remoteForward = Tkinter.Radiobutton(self, text='Remote', variable=self.localRemoteVar, value='remote')
+ self.remoteForward.grid(column=3, row=9)
+
+ Tkinter.Label(self, text='Advanced Options').grid(column=1, columnspan=3, row=10, sticky='nesw')
+
+ Tkinter.Label(self, anchor='w', justify='left', text='Cipher').grid(column=1, row=11, sticky='w')
+ self.cipher = Tkinter.Entry(self, name='cipher')
+ self.cipher.grid(column=2, columnspan=2, row=11, sticky='nesw')
+
+ Tkinter.Label(self, anchor='w', justify='left', text='MAC').grid(column=1, row=12, sticky='w')
+ self.mac = Tkinter.Entry(self, name='mac')
+ self.mac.grid(column=2, columnspan=2, row=12, sticky='nesw')
+
+ Tkinter.Label(self, anchor='w', justify='left', text='Escape Char').grid(column=1, row=13, sticky='w')
+ self.escape = Tkinter.Entry(self, name='escape')
+ self.escape.grid(column=2, columnspan=2, row=13, sticky='nesw')
+ Tkinter.Button(self, text='Connect!', command=self.doConnect).grid(column=1, columnspan=3, row=14, sticky='nesw')
+
+ # Resize behavior(s)
+ self.grid_rowconfigure(6, weight=1, minsize=64)
+ self.grid_columnconfigure(2, weight=1, minsize=2)
+
+ self.master.protocol("WM_DELETE_WINDOW", sys.exit)
+
+
+ def getIdentityFile(self):
+ r = tkFileDialog.askopenfilename()
+ if r:
+ self.identity.delete(0, Tkinter.END)
+ self.identity.insert(Tkinter.END, r)
+
+ def addForward(self):
+ port = self.forwardPort.get()
+ self.forwardPort.delete(0, Tkinter.END)
+ host = self.forwardHost.get()
+ self.forwardHost.delete(0, Tkinter.END)
+ if self.localRemoteVar.get() == 'local':
+ self.forwards.insert(Tkinter.END, 'L:%s:%s' % (port, host))
+ else:
+ self.forwards.insert(Tkinter.END, 'R:%s:%s' % (port, host))
+
+ def removeForward(self):
+ cur = self.forwards.curselection()
+ if cur:
+ self.forwards.remove(cur[0])
+
+ def doConnect(self):
+ finished = 1
+ options['host'] = self.host.get()
+ options['port'] = self.port.get()
+ options['user'] = self.user.get()
+ options['command'] = self.command.get()
+ cipher = self.cipher.get()
+ mac = self.mac.get()
+ escape = self.escape.get()
+ if cipher:
+ if cipher in SSHClientTransport.supportedCiphers:
+ SSHClientTransport.supportedCiphers = [cipher]
+ else:
+ tkMessageBox.showerror('TkConch', 'Bad cipher.')
+ finished = 0
+
+ if mac:
+ if mac in SSHClientTransport.supportedMACs:
+ SSHClientTransport.supportedMACs = [mac]
+ elif finished:
+ tkMessageBox.showerror('TkConch', 'Bad MAC.')
+ finished = 0
+
+ if escape:
+ if escape == 'none':
+ options['escape'] = None
+ elif escape[0] == '^' and len(escape) == 2:
+ options['escape'] = chr(ord(escape[1])-64)
+ elif len(escape) == 1:
+ options['escape'] = escape
+ elif finished:
+ tkMessageBox.showerror('TkConch', "Bad escape character '%s'." % escape)
+ finished = 0
+
+ if self.identity.get():
+ options.identitys.append(self.identity.get())
+
+ for line in self.forwards.get(0,Tkinter.END):
+ if line[0]=='L':
+ options.opt_localforward(line[2:])
+ else:
+ options.opt_remoteforward(line[2:])
+
+ if '@' in options['host']:
+ options['user'], options['host'] = options['host'].split('@',1)
+
+ if (not options['host'] or not options['user']) and finished:
+ tkMessageBox.showerror('TkConch', 'Missing host or username.')
+ finished = 0
+ if finished:
+ self.master.quit()
+ self.master.destroy()
+ if options['log']:
+ realout = sys.stdout
+ log.startLogging(sys.stderr)
+ sys.stdout = realout
+ else:
+ log.discardLogs()
+ log.deferr = handleError # HACK
+ if not options.identitys:
+ options.identitys = ['~/.ssh/id_rsa', '~/.ssh/id_dsa']
+ host = options['host']
+ port = int(options['port'] or 22)
+ log.msg((host,port))
+ reactor.connectTCP(host, port, SSHClientFactory())
+ frame.master.deiconify()
+ frame.master.title('%s@%s - TkConch' % (options['user'], options['host']))
+ else:
+ self.focus()
+
+class GeneralOptions(usage.Options):
+ synopsis = """Usage: tkconch [options] host [command]
+ """
+
+ optParameters = [['user', 'l', None, 'Log in using this user name.'],
+ ['identity', 'i', '~/.ssh/identity', 'Identity for public key authentication'],
+ ['escape', 'e', '~', "Set escape character; ``none'' = disable"],
+ ['cipher', 'c', None, 'Select encryption algorithm.'],
+ ['macs', 'm', None, 'Specify MAC algorithms for protocol version 2.'],
+ ['port', 'p', None, 'Connect to this port. Server must be on the same port.'],
+ ['localforward', 'L', None, 'listen-port:host:port Forward local port to remote address'],
+ ['remoteforward', 'R', None, 'listen-port:host:port Forward remote port to local address'],
+ ]
+
+ optFlags = [['tty', 't', 'Tty; allocate a tty even if command is given.'],
+ ['notty', 'T', 'Do not allocate a tty.'],
+ ['version', 'V', 'Display version number only.'],
+ ['compress', 'C', 'Enable compression.'],
+ ['noshell', 'N', 'Do not execute a shell or command.'],
+ ['subsystem', 's', 'Invoke command (mandatory) as SSH2 subsystem.'],
+ ['log', 'v', 'Log to stderr'],
+ ['ansilog', 'a', 'Print the receieved data to stdout']]
+
+ #zsh_altArgDescr = {"foo":"use this description for foo instead"}
+ #zsh_multiUse = ["foo", "bar"]
+ zsh_mutuallyExclusive = [("tty", "notty")]
+ zsh_actions = {"cipher":"(%s)" % " ".join(transport.SSHClientTransport.supportedCiphers),
+ "macs":"(%s)" % " ".join(transport.SSHClientTransport.supportedMACs)}
+ zsh_actionDescr = {"localforward":"listen-port:host:port",
+ "remoteforward":"listen-port:host:port"}
+ # user, host, or user@host completion similar to zsh's ssh completion
+ zsh_extras = ['1:host | user@host:{_ssh;if compset -P "*@"; then _wanted hosts expl "remote host name" _ssh_hosts && ret=0 elif compset -S "@*"; then _wanted users expl "login name" _ssh_users -S "" && ret=0 else if (( $+opt_args[-l] )); then tmp=() else tmp=( "users:login name:_ssh_users -qS@" ) fi; _alternative "hosts:remote host name:_ssh_hosts" "$tmp[@]" && ret=0 fi}',
+ '*:command: ']
+
+ identitys = []
+ localForwards = []
+ remoteForwards = []
+
+ def opt_identity(self, i):
+ self.identitys.append(i)
+
+ def opt_localforward(self, f):
+ localPort, remoteHost, remotePort = f.split(':') # doesn't do v6 yet
+ localPort = int(localPort)
+ remotePort = int(remotePort)
+ self.localForwards.append((localPort, (remoteHost, remotePort)))
+
+ def opt_remoteforward(self, f):
+ remotePort, connHost, connPort = f.split(':') # doesn't do v6 yet
+ remotePort = int(remotePort)
+ connPort = int(connPort)
+ self.remoteForwards.append((remotePort, (connHost, connPort)))
+
+ def opt_compress(self):
+ SSHClientTransport.supportedCompressions[0:1] = ['zlib']
+
+ def parseArgs(self, *args):
+ if args:
+ self['host'] = args[0]
+ self['command'] = ' '.join(args[1:])
+ else:
+ self['host'] = ''
+ self['command'] = ''
+
+# Rest of code in "run"
+options = None
+menu = None
+exitStatus = 0
+frame = None
+
+def deferredAskFrame(question, echo):
+ if frame.callback:
+ raise ValueError("can't ask 2 questions at once!")
+ d = defer.Deferred()
+ resp = []
+ def gotChar(ch, resp=resp):
+ if not ch: return
+ if ch=='\x03': # C-c
+ reactor.stop()
+ if ch=='\r':
+ frame.write('\r\n')
+ stresp = ''.join(resp)
+ del resp
+ frame.callback = None
+ d.callback(stresp)
+ return
+ elif 32 <= ord(ch) < 127:
+ resp.append(ch)
+ if echo:
+ frame.write(ch)
+ elif ord(ch) == 8 and resp: # BS
+ if echo: frame.write('\x08 \x08')
+ resp.pop()
+ frame.callback = gotChar
+ frame.write(question)
+ frame.canvas.focus_force()
+ return d
+
+def run():
+ global menu, options, frame
+ args = sys.argv[1:]
+ if '-l' in args: # cvs is an idiot
+ i = args.index('-l')
+ args = args[i:i+2]+args
+ del args[i+2:i+4]
+ for arg in args[:]:
+ try:
+ i = args.index(arg)
+ if arg[:2] == '-o' and args[i+1][0]!='-':
+ args[i:i+2] = [] # suck on it scp
+ except ValueError:
+ pass
+ root = Tkinter.Tk()
+ root.withdraw()
+ top = Tkinter.Toplevel()
+ menu = TkConchMenu(top)
+ menu.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
+ options = GeneralOptions()
+ try:
+ options.parseOptions(args)
+ except usage.UsageError, u:
+ print 'ERROR: %s' % u
+ options.opt_help()
+ sys.exit(1)
+ for k,v in options.items():
+ if v and hasattr(menu, k):
+ getattr(menu,k).insert(Tkinter.END, v)
+ for (p, (rh, rp)) in options.localForwards:
+ menu.forwards.insert(Tkinter.END, 'L:%s:%s:%s' % (p, rh, rp))
+ options.localForwards = []
+ for (p, (rh, rp)) in options.remoteForwards:
+ menu.forwards.insert(Tkinter.END, 'R:%s:%s:%s' % (p, rh, rp))
+ options.remoteForwards = []
+ frame = tkvt100.VT100Frame(root, callback=None)
+ root.geometry('%dx%d'%(tkvt100.fontWidth*frame.width+3, tkvt100.fontHeight*frame.height+3))
+ frame.pack(side = Tkinter.TOP)
+ tksupport.install(root)
+ root.withdraw()
+ if (options['host'] and options['user']) or '@' in options['host']:
+ menu.doConnect()
+ else:
+ top.mainloop()
+ reactor.run()
+ sys.exit(exitStatus)
+
+def handleError():
+ from twisted.python import failure
+ global exitStatus
+ exitStatus = 2
+ log.err(failure.Failure())
+ reactor.stop()
+ raise
+
+class SSHClientFactory(protocol.ClientFactory):
+ noisy = 1
+
+ def stopFactory(self):
+ reactor.stop()
+
+ def buildProtocol(self, addr):
+ return SSHClientTransport()
+
+ def clientConnectionFailed(self, connector, reason):
+ tkMessageBox.showwarning('TkConch','Connection Failed, Reason:\n %s: %s' % (reason.type, reason.value))
+
+class SSHClientTransport(transport.SSHClientTransport):
+
+ def receiveError(self, code, desc):
+ global exitStatus
+ exitStatus = 'conch:\tRemote side disconnected with error code %i\nconch:\treason: %s' % (code, desc)
+
+ def sendDisconnect(self, code, reason):
+ global exitStatus
+ exitStatus = 'conch:\tSending disconnect with error code %i\nconch:\treason: %s' % (code, reason)
+ transport.SSHClientTransport.sendDisconnect(self, code, reason)
+
+ def receiveDebug(self, alwaysDisplay, message, lang):
+ global options
+ if alwaysDisplay or options['log']:
+ log.msg('Received Debug Message: %s' % message)
+
+ def verifyHostKey(self, pubKey, fingerprint):
+ #d = defer.Deferred()
+ #d.addCallback(lambda x:defer.succeed(1))
+ #d.callback(2)
+ #return d
+ goodKey = isInKnownHosts(options['host'], pubKey, {'known-hosts': None})
+ if goodKey == 1: # good key
+ return defer.succeed(1)
+ elif goodKey == 2: # AAHHHHH changed
+ return defer.fail(error.ConchError('bad host key'))
+ else:
+ if options['host'] == self.transport.getPeer()[1]:
+ host = options['host']
+ khHost = options['host']
+ else:
+ host = '%s (%s)' % (options['host'],
+ self.transport.getPeer()[1])
+ khHost = '%s,%s' % (options['host'],
+ self.transport.getPeer()[1])
+ keyType = common.getNS(pubKey)[0]
+ ques = """The authenticity of host '%s' can't be established.\r
+%s key fingerprint is %s.""" % (host,
+ {'ssh-dss':'DSA', 'ssh-rsa':'RSA'}[keyType],
+ fingerprint)
+ ques+='\r\nAre you sure you want to continue connecting (yes/no)? '
+ return deferredAskFrame(ques, 1).addCallback(self._cbVerifyHostKey, pubKey, khHost, keyType)
+
+ def _cbVerifyHostKey(self, ans, pubKey, khHost, keyType):
+ if ans.lower() not in ('yes', 'no'):
+ return deferredAskFrame("Please type 'yes' or 'no': ",1).addCallback(self._cbVerifyHostKey, pubKey, khHost, keyType)
+ if ans.lower() == 'no':
+ frame.write('Host key verification failed.\r\n')
+ raise error.ConchError('bad host key')
+ try:
+ frame.write("Warning: Permanently added '%s' (%s) to the list of known hosts.\r\n" % (khHost, {'ssh-dss':'DSA', 'ssh-rsa':'RSA'}[keyType]))
+ known_hosts = open(os.path.expanduser('~/.ssh/known_hosts'), 'a')
+ encodedKey = base64.encodestring(pubKey).replace('\n', '')
+ known_hosts.write('\n%s %s %s' % (khHost, keyType, encodedKey))
+ known_hosts.close()
+ except:
+ log.deferr()
+ raise error.ConchError
+
+ def connectionSecure(self):
+ if options['user']:
+ user = options['user']
+ else:
+ user = getpass.getuser()
+ self.requestService(SSHUserAuthClient(user, SSHConnection()))
+
+class SSHUserAuthClient(userauth.SSHUserAuthClient):
+ usedFiles = []
+
+ def getPassword(self, prompt = None):
+ if not prompt:
+ prompt = "%s@%s's password: " % (self.user, options['host'])
+ return deferredAskFrame(prompt,0)
+
+ def getPublicKey(self):
+ files = [x for x in options.identitys if x not in self.usedFiles]
+ if not files:
+ return None
+ file = files[0]
+ log.msg(file)
+ self.usedFiles.append(file)
+ file = os.path.expanduser(file)
+ file += '.pub'
+ if not os.path.exists(file):
+ return
+ try:
+ return keys.getPublicKeyString(file)
+ except:
+ return self.getPublicKey() # try again
+
+ def getPrivateKey(self):
+ file = os.path.expanduser(self.usedFiles[-1])
+ if not os.path.exists(file):
+ return None
+ try:
+ return defer.succeed(keys.getPrivateKeyObject(file))
+ except keys.BadKeyError, e:
+ if e.args[0] == 'encrypted key with no password':
+ prompt = "Enter passphrase for key '%s': " % \
+ self.usedFiles[-1]
+ return deferredAskFrame(prompt, 0).addCallback(self._cbGetPrivateKey, 0)
+ def _cbGetPrivateKey(self, ans, count):
+ file = os.path.expanduser(self.usedFiles[-1])
+ try:
+ return keys.getPrivateKeyObject(file, password = ans)
+ except keys.BadKeyError:
+ if count == 2:
+ raise
+ prompt = "Enter passphrase for key '%s': " % \
+ self.usedFiles[-1]
+ return deferredAskFrame(prompt, 0).addCallback(self._cbGetPrivateKey, count+1)
+
+class SSHConnection(connection.SSHConnection):
+ def serviceStarted(self):
+ if not options['noshell']:
+ self.openChannel(SSHSession())
+ if options.localForwards:
+ for localPort, hostport in options.localForwards:
+ reactor.listenTCP(localPort,
+ forwarding.SSHListenForwardingFactory(self,
+ hostport,
+ forwarding.SSHListenClientForwardingChannel))
+ if options.remoteForwards:
+ for remotePort, hostport in options.remoteForwards:
+ log.msg('asking for remote forwarding for %s:%s' %
+ (remotePort, hostport))
+ data = forwarding.packGlobal_tcpip_forward(
+ ('0.0.0.0', remotePort))
+ d = self.sendGlobalRequest('tcpip-forward', data)
+ self.remoteForwards[remotePort] = hostport
+
+class SSHSession(channel.SSHChannel):
+
+ name = 'session'
+
+ def channelOpen(self, foo):
+ #global globalSession
+ #globalSession = self
+ # turn off local echo
+ self.escapeMode = 1
+ c = session.SSHSessionClient()
+ if options['escape']:
+ c.dataReceived = self.handleInput
+ else:
+ c.dataReceived = self.write
+ c.connectionLost = self.sendEOF
+ frame.callback = c.dataReceived
+ frame.canvas.focus_force()
+ if options['subsystem']:
+ self.conn.sendRequest(self, 'subsystem', \
+ common.NS(options['command']))
+ elif options['command']:
+ if options['tty']:
+ term = os.environ.get('TERM', 'xterm')
+ #winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
+ winSize = (25,80,0,0) #struct.unpack('4H', winsz)
+ ptyReqData = session.packRequest_pty_req(term, winSize, '')
+ self.conn.sendRequest(self, 'pty-req', ptyReqData)
+ self.conn.sendRequest(self, 'exec', \
+ common.NS(options['command']))
+ else:
+ if not options['notty']:
+ term = os.environ.get('TERM', 'xterm')
+ #winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
+ winSize = (25,80,0,0) #struct.unpack('4H', winsz)
+ ptyReqData = session.packRequest_pty_req(term, winSize, '')
+ self.conn.sendRequest(self, 'pty-req', ptyReqData)
+ self.conn.sendRequest(self, 'shell', '')
+ self.conn.transport.transport.setTcpNoDelay(1)
+
+ def handleInput(self, char):
+ #log.msg('handling %s' % repr(char))
+ if char in ('\n', '\r'):
+ self.escapeMode = 1
+ self.write(char)
+ elif self.escapeMode == 1 and char == options['escape']:
+ self.escapeMode = 2
+ elif self.escapeMode == 2:
+ self.escapeMode = 1 # so we can chain escapes together
+ if char == '.': # disconnect
+ log.msg('disconnecting from escape')
+ reactor.stop()
+ return
+ elif char == '\x1a': # ^Z, suspend
+ # following line courtesy of Erwin@freenode
+ os.kill(os.getpid(), signal.SIGSTOP)
+ return
+ elif char == 'R': # rekey connection
+ log.msg('rekeying connection')
+ self.conn.transport.sendKexInit()
+ return
+ self.write('~' + char)
+ else:
+ self.escapeMode = 0
+ self.write(char)
+
+ def dataReceived(self, data):
+ if options['ansilog']:
+ print repr(data)
+ frame.write(data)
+
+ def extReceived(self, t, data):
+ if t==connection.EXTENDED_DATA_STDERR:
+ log.msg('got %s stderr data' % len(data))
+ sys.stderr.write(data)
+ sys.stderr.flush()
+
+ def eofReceived(self):
+ log.msg('got eof')
+ sys.stdin.close()
+
+ def closed(self):
+ log.msg('closed %s' % self)
+ if len(self.conn.channels) == 1: # just us left
+ reactor.stop()
+
+ def request_exit_status(self, data):
+ global exitStatus
+ exitStatus = int(struct.unpack('>L', data)[0])
+ log.msg('exit status: %s' % exitStatus)
+
+ def sendEOF(self):
+ self.conn.sendEOF(self)
+
+if __name__=="__main__":
+ run()
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/__init__.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/__init__.py
new file mode 100644
index 0000000000..a53c9a7ae7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/__init__.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+An SSHv2 implementation for Twisted. Part of the Twisted.Conch package.
+
+Maintainer: Paul Swartz
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/agent.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/agent.py
new file mode 100644
index 0000000000..9d1f7600ef
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/agent.py
@@ -0,0 +1,294 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implements the SSH v2 key agent protocol. This protocol is documented in the
+SSH source code, in the file
+U{PROTOCOL.agent<http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent>}.
+
+Maintainer: Paul Swartz
+"""
+
+import struct
+
+from twisted.conch.ssh.common import NS, getNS, getMP
+from twisted.conch.error import ConchError, MissingKeyStoreError
+from twisted.conch.ssh import keys
+from twisted.internet import defer, protocol
+
+
+
+class SSHAgentClient(protocol.Protocol):
+ """
+ The client side of the SSH agent protocol. This is equivalent to
+ ssh-add(1) and can be used with either ssh-agent(1) or the SSHAgentServer
+ protocol, also in this package.
+ """
+
+ def __init__(self):
+ self.buf = ''
+ self.deferreds = []
+
+
+ def dataReceived(self, data):
+ self.buf += data
+ while 1:
+ if len(self.buf) <= 4:
+ return
+ packLen = struct.unpack('!L', self.buf[:4])[0]
+ if len(self.buf) < 4 + packLen:
+ return
+ packet, self.buf = self.buf[4:4 + packLen], self.buf[4 + packLen:]
+ reqType = ord(packet[0])
+ d = self.deferreds.pop(0)
+ if reqType == AGENT_FAILURE:
+ d.errback(ConchError('agent failure'))
+ elif reqType == AGENT_SUCCESS:
+ d.callback('')
+ else:
+ d.callback(packet)
+
+
+ def sendRequest(self, reqType, data):
+ pack = struct.pack('!LB',len(data) + 1, reqType) + data
+ self.transport.write(pack)
+ d = defer.Deferred()
+ self.deferreds.append(d)
+ return d
+
+
+ def requestIdentities(self):
+ """
+ @return: A L{Deferred} which will fire with a list of all keys found in
+ the SSH agent. The list of keys is comprised of (public key blob,
+ comment) tuples.
+ """
+ d = self.sendRequest(AGENTC_REQUEST_IDENTITIES, '')
+ d.addCallback(self._cbRequestIdentities)
+ return d
+
+
+ def _cbRequestIdentities(self, data):
+ """
+ Unpack a collection of identities into a list of tuples comprised of
+ public key blobs and comments.
+ """
+ if ord(data[0]) != AGENT_IDENTITIES_ANSWER:
+ raise ConchError('unexpected response: %i' % ord(data[0]))
+ numKeys = struct.unpack('!L', data[1:5])[0]
+ keys = []
+ data = data[5:]
+ for i in range(numKeys):
+ blob, data = getNS(data)
+ comment, data = getNS(data)
+ keys.append((blob, comment))
+ return keys
+
+
+ def addIdentity(self, blob, comment = ''):
+ """
+ Add a private key blob to the agent's collection of keys.
+ """
+ req = blob
+ req += NS(comment)
+ return self.sendRequest(AGENTC_ADD_IDENTITY, req)
+
+
+ def signData(self, blob, data):
+ """
+ Request that the agent sign the given C{data} with the private key
+ which corresponds to the public key given by C{blob}. The private
+ key should have been added to the agent already.
+
+ @type blob: C{str}
+ @type data: C{str}
+ @return: A L{Deferred} which fires with a signature for given data
+ created with the given key.
+ """
+ req = NS(blob)
+ req += NS(data)
+ req += '\000\000\000\000' # flags
+ return self.sendRequest(AGENTC_SIGN_REQUEST, req).addCallback(self._cbSignData)
+
+
+ def _cbSignData(self, data):
+ if ord(data[0]) != AGENT_SIGN_RESPONSE:
+ raise ConchError('unexpected data: %i' % ord(data[0]))
+ signature = getNS(data[1:])[0]
+ return signature
+
+
+ def removeIdentity(self, blob):
+ """
+ Remove the private key corresponding to the public key in blob from the
+ running agent.
+ """
+ req = NS(blob)
+ return self.sendRequest(AGENTC_REMOVE_IDENTITY, req)
+
+
+ def removeAllIdentities(self):
+ """
+ Remove all keys from the running agent.
+ """
+ return self.sendRequest(AGENTC_REMOVE_ALL_IDENTITIES, '')
+
+
+
+class SSHAgentServer(protocol.Protocol):
+ """
+ The server side of the SSH agent protocol. This is equivalent to
+ ssh-agent(1) and can be used with either ssh-add(1) or the SSHAgentClient
+ protocol, also in this package.
+ """
+
+ def __init__(self):
+ self.buf = ''
+
+
+ def dataReceived(self, data):
+ self.buf += data
+ while 1:
+ if len(self.buf) <= 4:
+ return
+ packLen = struct.unpack('!L', self.buf[:4])[0]
+ if len(self.buf) < 4 + packLen:
+ return
+ packet, self.buf = self.buf[4:4 + packLen], self.buf[4 + packLen:]
+ reqType = ord(packet[0])
+ reqName = messages.get(reqType, None)
+ if not reqName:
+ self.sendResponse(AGENT_FAILURE, '')
+ else:
+ f = getattr(self, 'agentc_%s' % reqName)
+ if getattr(self.factory, 'keys', None) is None:
+ self.sendResponse(AGENT_FAILURE, '')
+ raise MissingKeyStoreError()
+ f(packet[1:])
+
+
+ def sendResponse(self, reqType, data):
+ pack = struct.pack('!LB', len(data) + 1, reqType) + data
+ self.transport.write(pack)
+
+
+ def agentc_REQUEST_IDENTITIES(self, data):
+ """
+ Return all of the identities that have been added to the server
+ """
+ assert data == ''
+ numKeys = len(self.factory.keys)
+ resp = []
+
+ resp.append(struct.pack('!L', numKeys))
+ for key, comment in self.factory.keys.itervalues():
+ resp.append(NS(key.blob())) # yes, wrapped in an NS
+ resp.append(NS(comment))
+ self.sendResponse(AGENT_IDENTITIES_ANSWER, ''.join(resp))
+
+
+ def agentc_SIGN_REQUEST(self, data):
+ """
+ Data is a structure with a reference to an already added key object and
+ some data that the clients wants signed with that key. If the key
+ object wasn't loaded, return AGENT_FAILURE, else return the signature.
+ """
+ blob, data = getNS(data)
+ if blob not in self.factory.keys:
+ return self.sendResponse(AGENT_FAILURE, '')
+ signData, data = getNS(data)
+ assert data == '\000\000\000\000'
+ self.sendResponse(AGENT_SIGN_RESPONSE, NS(self.factory.keys[blob][0].sign(signData)))
+
+
+ def agentc_ADD_IDENTITY(self, data):
+ """
+ Adds a private key to the agent's collection of identities. On
+ subsequent interactions, the private key can be accessed using only the
+ corresponding public key.
+ """
+
+ # need to pre-read the key data so we can get past it to the comment string
+ keyType, rest = getNS(data)
+ if keyType == 'ssh-rsa':
+ nmp = 6
+ elif keyType == 'ssh-dss':
+ nmp = 5
+ else:
+ raise keys.BadKeyError('unknown blob type: %s' % keyType)
+
+ rest = getMP(rest, nmp)[-1] # ignore the key data for now, we just want the comment
+ comment, rest = getNS(rest) # the comment, tacked onto the end of the key blob
+
+ k = keys.Key.fromString(data, type='private_blob') # not wrapped in NS here
+ self.factory.keys[k.blob()] = (k, comment)
+ self.sendResponse(AGENT_SUCCESS, '')
+
+
+ def agentc_REMOVE_IDENTITY(self, data):
+ """
+ Remove a specific key from the agent's collection of identities.
+ """
+ blob, _ = getNS(data)
+ k = keys.Key.fromString(blob, type='blob')
+ del self.factory.keys[k.blob()]
+ self.sendResponse(AGENT_SUCCESS, '')
+
+
+ def agentc_REMOVE_ALL_IDENTITIES(self, data):
+ """
+ Remove all keys from the agent's collection of identities.
+ """
+ assert data == ''
+ self.factory.keys = {}
+ self.sendResponse(AGENT_SUCCESS, '')
+
+ # v1 messages that we ignore because we don't keep v1 keys
+ # open-ssh sends both v1 and v2 commands, so we have to
+ # do no-ops for v1 commands or we'll get "bad request" errors
+
+ def agentc_REQUEST_RSA_IDENTITIES(self, data):
+ """
+ v1 message for listing RSA1 keys; superseded by
+ agentc_REQUEST_IDENTITIES, which handles different key types.
+ """
+ self.sendResponse(AGENT_RSA_IDENTITIES_ANSWER, struct.pack('!L', 0))
+
+
+ def agentc_REMOVE_RSA_IDENTITY(self, data):
+ """
+ v1 message for removing RSA1 keys; superseded by
+ agentc_REMOVE_IDENTITY, which handles different key types.
+ """
+ self.sendResponse(AGENT_SUCCESS, '')
+
+
+ def agentc_REMOVE_ALL_RSA_IDENTITIES(self, data):
+ """
+ v1 message for removing all RSA1 keys; superseded by
+ agentc_REMOVE_ALL_IDENTITIES, which handles different key types.
+ """
+ self.sendResponse(AGENT_SUCCESS, '')
+
+
+AGENTC_REQUEST_RSA_IDENTITIES = 1
+AGENT_RSA_IDENTITIES_ANSWER = 2
+AGENT_FAILURE = 5
+AGENT_SUCCESS = 6
+
+AGENTC_REMOVE_RSA_IDENTITY = 8
+AGENTC_REMOVE_ALL_RSA_IDENTITIES = 9
+
+AGENTC_REQUEST_IDENTITIES = 11
+AGENT_IDENTITIES_ANSWER = 12
+AGENTC_SIGN_REQUEST = 13
+AGENT_SIGN_RESPONSE = 14
+AGENTC_ADD_IDENTITY = 17
+AGENTC_REMOVE_IDENTITY = 18
+AGENTC_REMOVE_ALL_IDENTITIES = 19
+
+messages = {}
+for name, value in locals().copy().items():
+ if name[:7] == 'AGENTC_':
+ messages[value] = name[7:] # doesn't handle doubles
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/asn1.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/asn1.py
new file mode 100644
index 0000000000..5f1526fdf3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/asn1.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A basic ASN.1 parser. Deprecated since Twisted 9.0 in favor of PyASN1.
+
+Maintainer: Paul Swartz
+"""
+
+import itertools
+from pyasn1.type import univ
+from pyasn1.codec.ber import decoder, encoder
+from twisted.python.deprecate import deprecated
+from twisted.python import versions
+
+Twisted9point0 = versions.Version('Twisted', 9, 0, 0)
+
+def parse(data):
+ return decoder.decode(data)[0]
+
+parse = deprecated(Twisted9point0)(parse)
+
+
+def pack(data):
+ asn1Sequence = univ.Sequence()
+ for index, value in itertools.izip(itertools.count(), data):
+ try:
+ valueAsInteger = univ.Integer(value)
+ except TypeError:
+ raise ValueError("cannot pack %r" % (value,))
+ asn1Sequence.setComponentByPosition(index, univ.Integer(value))
+ return encoder.encode(asn1Sequence)
+
+pack = deprecated(Twisted9point0)(pack)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/channel.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/channel.py
new file mode 100644
index 0000000000..216264b657
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/channel.py
@@ -0,0 +1,281 @@
+# -*- test-case-name: twisted.conch.test.test_channel -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""
+The parent class for all the SSH Channels. Currently implemented channels
+are session. direct-tcp, and forwarded-tcp.
+
+Maintainer: Paul Swartz
+"""
+
+from twisted.python import log
+from twisted.internet import interfaces
+from zope.interface import implements
+
+
+class SSHChannel(log.Logger):
+ """
+ A class that represents a multiplexed channel over an SSH connection.
+ The channel has a local window which is the maximum amount of data it will
+ receive, and a remote which is the maximum amount of data the remote side
+ will accept. There is also a maximum packet size for any individual data
+ packet going each way.
+
+ @ivar name: the name of the channel.
+ @type name: C{str}
+ @ivar localWindowSize: the maximum size of the local window in bytes.
+ @type localWindowSize: C{int}
+ @ivar localWindowLeft: how many bytes are left in the local window.
+ @type localWindowLeft: C{int}
+ @ivar localMaxPacket: the maximum size of packet we will accept in bytes.
+ @type localMaxPacket: C{int}
+ @ivar remoteWindowLeft: how many bytes are left in the remote window.
+ @type remoteWindowLeft: C{int}
+ @ivar remoteMaxPacket: the maximum size of a packet the remote side will
+ accept in bytes.
+ @type remoteMaxPacket: C{int}
+ @ivar conn: the connection this channel is multiplexed through.
+ @type conn: L{SSHConnection}
+ @ivar data: any data to send to the other size when the channel is
+ requested.
+ @type data: C{str}
+ @ivar avatar: an avatar for the logged-in user (if a server channel)
+ @ivar localClosed: True if we aren't accepting more data.
+ @type localClosed: C{bool}
+ @ivar remoteClosed: True if the other size isn't accepting more data.
+ @type remoteClosed: C{bool}
+ """
+
+ implements(interfaces.ITransport)
+
+ name = None # only needed for client channels
+
+ def __init__(self, localWindow = 0, localMaxPacket = 0,
+ remoteWindow = 0, remoteMaxPacket = 0,
+ conn = None, data=None, avatar = None):
+ self.localWindowSize = localWindow or 131072
+ self.localWindowLeft = self.localWindowSize
+ self.localMaxPacket = localMaxPacket or 32768
+ self.remoteWindowLeft = remoteWindow
+ self.remoteMaxPacket = remoteMaxPacket
+ self.areWriting = 1
+ self.conn = conn
+ self.data = data
+ self.avatar = avatar
+ self.specificData = ''
+ self.buf = ''
+ self.extBuf = []
+ self.closing = 0
+ self.localClosed = 0
+ self.remoteClosed = 0
+ self.id = None # gets set later by SSHConnection
+
+ def __str__(self):
+ return '<SSHChannel %s (lw %i rw %i)>' % (self.name,
+ self.localWindowLeft, self.remoteWindowLeft)
+
+ def logPrefix(self):
+ id = (self.id is not None and str(self.id)) or "unknown"
+ return "SSHChannel %s (%s) on %s" % (self.name, id,
+ self.conn.logPrefix())
+
+ def channelOpen(self, specificData):
+ """
+ Called when the channel is opened. specificData is any data that the
+ other side sent us when opening the channel.
+
+ @type specificData: C{str}
+ """
+ log.msg('channel open')
+
+ def openFailed(self, reason):
+ """
+ Called when the the open failed for some reason.
+ reason.desc is a string descrption, reason.code the the SSH error code.
+
+ @type reason: L{error.ConchError}
+ """
+ log.msg('other side refused open\nreason: %s'% reason)
+
+ def addWindowBytes(self, bytes):
+ """
+ Called when bytes are added to the remote window. By default it clears
+ the data buffers.
+
+ @type bytes: C{int}
+ """
+ self.remoteWindowLeft = self.remoteWindowLeft+bytes
+ if not self.areWriting and not self.closing:
+ self.areWriting = True
+ self.startWriting()
+ if self.buf:
+ b = self.buf
+ self.buf = ''
+ self.write(b)
+ if self.extBuf:
+ b = self.extBuf
+ self.extBuf = []
+ for (type, data) in b:
+ self.writeExtended(type, data)
+
+ def requestReceived(self, requestType, data):
+ """
+ Called when a request is sent to this channel. By default it delegates
+ to self.request_<requestType>.
+ If this function returns true, the request succeeded, otherwise it
+ failed.
+
+ @type requestType: C{str}
+ @type data: C{str}
+ @rtype: C{bool}
+ """
+ foo = requestType.replace('-', '_')
+ f = getattr(self, 'request_%s'%foo, None)
+ if f:
+ return f(data)
+ log.msg('unhandled request for %s'%requestType)
+ return 0
+
+ def dataReceived(self, data):
+ """
+ Called when we receive data.
+
+ @type data: C{str}
+ """
+ log.msg('got data %s'%repr(data))
+
+ def extReceived(self, dataType, data):
+ """
+ Called when we receive extended data (usually standard error).
+
+ @type dataType: C{int}
+ @type data: C{str}
+ """
+ log.msg('got extended data %s %s'%(dataType, repr(data)))
+
+ def eofReceived(self):
+ """
+ Called when the other side will send no more data.
+ """
+ log.msg('remote eof')
+
+ def closeReceived(self):
+ """
+ Called when the other side has closed the channel.
+ """
+ log.msg('remote close')
+ self.loseConnection()
+
+ def closed(self):
+ """
+ Called when the channel is closed. This means that both our side and
+ the remote side have closed the channel.
+ """
+ log.msg('closed')
+
+ # transport stuff
+ def write(self, data):
+ """
+ Write some data to the channel. If there is not enough remote window
+ available, buffer until it is. Otherwise, split the data into
+ packets of length remoteMaxPacket and send them.
+
+ @type data: C{str}
+ """
+ if self.buf:
+ self.buf += data
+ return
+ top = len(data)
+ if top > self.remoteWindowLeft:
+ data, self.buf = (data[:self.remoteWindowLeft],
+ data[self.remoteWindowLeft:])
+ self.areWriting = 0
+ self.stopWriting()
+ top = self.remoteWindowLeft
+ rmp = self.remoteMaxPacket
+ write = self.conn.sendData
+ r = range(0, top, rmp)
+ for offset in r:
+ write(self, data[offset: offset+rmp])
+ self.remoteWindowLeft -= top
+ if self.closing and not self.buf:
+ self.loseConnection() # try again
+
+ def writeExtended(self, dataType, data):
+ """
+ Send extended data to this channel. If there is not enough remote
+ window available, buffer until there is. Otherwise, split the data
+ into packets of length remoteMaxPacket and send them.
+
+ @type dataType: C{int}
+ @type data: C{str}
+ """
+ if self.extBuf:
+ if self.extBuf[-1][0] == dataType:
+ self.extBuf[-1][1] += data
+ else:
+ self.extBuf.append([dataType, data])
+ return
+ if len(data) > self.remoteWindowLeft:
+ data, self.extBuf = (data[:self.remoteWindowLeft],
+ [[dataType, data[self.remoteWindowLeft:]]])
+ self.areWriting = 0
+ self.stopWriting()
+ while len(data) > self.remoteMaxPacket:
+ self.conn.sendExtendedData(self, dataType,
+ data[:self.remoteMaxPacket])
+ data = data[self.remoteMaxPacket:]
+ self.remoteWindowLeft -= self.remoteMaxPacket
+ if data:
+ self.conn.sendExtendedData(self, dataType, data)
+ self.remoteWindowLeft -= len(data)
+ if self.closing:
+ self.loseConnection() # try again
+
+ def writeSequence(self, data):
+ """
+ Part of the Transport interface. Write a list of strings to the
+ channel.
+
+ @type data: C{list} of C{str}
+ """
+ self.write(''.join(data))
+
+ def loseConnection(self):
+ """
+ Close the channel if there is no buferred data. Otherwise, note the
+ request and return.
+ """
+ self.closing = 1
+ if not self.buf and not self.extBuf:
+ self.conn.sendClose(self)
+
+ def getPeer(self):
+ """
+ Return a tuple describing the other side of the connection.
+
+ @rtype: C{tuple}
+ """
+ return('SSH', )+self.conn.transport.getPeer()
+
+ def getHost(self):
+ """
+ Return a tuple describing our side of the connection.
+
+ @rtype: C{tuple}
+ """
+ return('SSH', )+self.conn.transport.getHost()
+
+ def stopWriting(self):
+ """
+ Called when the remote buffer is full, as a hint to stop writing.
+ This can be ignored, but it can be helpful.
+ """
+
+ def startWriting(self):
+ """
+ Called when the remote buffer has more room, as a hint to continue
+ writing.
+ """
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/common.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/common.py
new file mode 100644
index 0000000000..8436febbbb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/common.py
@@ -0,0 +1,130 @@
+# -*- test-case-name: twisted.conch.test.test_ssh -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Common functions for the SSH classes.
+
+Maintainer: Paul Swartz
+"""
+
+import struct, warnings
+
+try:
+ from Crypto import Util
+except ImportError:
+ warnings.warn("PyCrypto not installed, but continuing anyways!",
+ RuntimeWarning)
+
+from twisted.python import randbytes
+
+class Entropy(object):
+ """
+ A Crypto.Util.randpool.RandomPool mock for compatibility.
+ """
+ def get_bytes(self, numBytes):
+ """
+ Get a number of random bytes.
+ """
+ warnings.warn("entropy.get_bytes is deprecated, please use "
+ "twisted.python.randbytes.secureRandom instead.",
+ category=DeprecationWarning, stacklevel=2)
+ return randbytes.secureRandom(numBytes)
+
+entropy = Entropy()
+
+
+def NS(t):
+ """
+ net string
+ """
+ return struct.pack('!L',len(t)) + t
+
+def getNS(s, count=1):
+ """
+ get net string
+ """
+ ns = []
+ c = 0
+ for i in range(count):
+ l, = struct.unpack('!L',s[c:c+4])
+ ns.append(s[c+4:4+l+c])
+ c += 4 + l
+ return tuple(ns) + (s[c:],)
+
+def MP(number):
+ if number==0: return '\000'*4
+ assert number>0
+ bn = Util.number.long_to_bytes(number)
+ if ord(bn[0])&128:
+ bn = '\000' + bn
+ return struct.pack('>L',len(bn)) + bn
+
+def getMP(data, count=1):
+ """
+ Get multiple precision integer out of the string. A multiple precision
+ integer is stored as a 4-byte length followed by length bytes of the
+ integer. If count is specified, get count integers out of the string.
+ The return value is a tuple of count integers followed by the rest of
+ the data.
+ """
+ mp = []
+ c = 0
+ for i in range(count):
+ length, = struct.unpack('>L',data[c:c+4])
+ mp.append(Util.number.bytes_to_long(data[c+4:c+4+length]))
+ c += 4 + length
+ return tuple(mp) + (data[c:],)
+
+def _MPpow(x, y, z):
+ """return the MP version of (x**y)%z
+ """
+ return MP(pow(x,y,z))
+
+def ffs(c, s):
+ """
+ first from second
+ goes through the first list, looking for items in the second, returns the first one
+ """
+ for i in c:
+ if i in s: return i
+
+getMP_py = getMP
+MP_py = MP
+_MPpow_py = _MPpow
+pyPow = pow
+
+def _fastgetMP(data, count=1):
+ mp = []
+ c = 0
+ for i in range(count):
+ length = struct.unpack('!L', data[c:c+4])[0]
+ mp.append(long(gmpy.mpz(data[c + 4:c + 4 + length][::-1] + '\x00', 256)))
+ c += length + 4
+ return tuple(mp) + (data[c:],)
+
+def _fastMP(i):
+ i2 = gmpy.mpz(i).binary()[::-1]
+ return struct.pack('!L', len(i2)) + i2
+
+def _fastMPpow(x, y, z=None):
+ r = pyPow(gmpy.mpz(x),y,z).binary()[::-1]
+ return struct.pack('!L', len(r)) + r
+
+def _fastpow(x, y, z=None):
+ return pyPow(gmpy.mpz(x), y, z)
+
+def install():
+ global getMP, MP, _MPpow
+ getMP = _fastgetMP
+ MP = _fastMP
+ _MPpow = _fastMPpow
+ __builtins__['pow'] = _fastpow # evil evil
+
+try:
+ import gmpy
+ install()
+except ImportError:
+ pass
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/connection.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/connection.py
new file mode 100644
index 0000000000..264fcd0383
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/connection.py
@@ -0,0 +1,613 @@
+# -*- test-case-name: twisted.conch.test.test_connection -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+This module contains the implementation of the ssh-connection service, which
+allows access to the shell and port-forwarding.
+
+Maintainer: Paul Swartz
+"""
+
+import struct
+
+from twisted.conch.ssh import service, common
+from twisted.conch import error
+from twisted.internet import defer
+from twisted.python import log
+
+class SSHConnection(service.SSHService):
+ """
+ An implementation of the 'ssh-connection' service. It is used to
+ multiplex multiple channels over the single SSH connection.
+
+ @ivar localChannelID: the next number to use as a local channel ID.
+ @type localChannelID: C{int}
+ @ivar channels: a C{dict} mapping a local channel ID to C{SSHChannel}
+ subclasses.
+ @type channels: C{dict}
+ @ivar localToRemoteChannel: a C{dict} mapping a local channel ID to a
+ remote channel ID.
+ @type localToRemoteChannel: C{dict}
+ @ivar channelsToRemoteChannel: a C{dict} mapping a C{SSHChannel} subclass
+ to remote channel ID.
+ @type channelsToRemoteChannel: C{dict}
+ @ivar deferreds: a C{dict} mapping a local channel ID to a C{list} of
+ C{Deferreds} for outstanding channel requests. Also, the 'global'
+ key stores the C{list} of pending global request C{Deferred}s.
+ """
+ name = 'ssh-connection'
+
+ def __init__(self):
+ self.localChannelID = 0 # this is the current # to use for channel ID
+ self.localToRemoteChannel = {} # local channel ID -> remote channel ID
+ self.channels = {} # local channel ID -> subclass of SSHChannel
+ self.channelsToRemoteChannel = {} # subclass of SSHChannel ->
+ # remote channel ID
+ self.deferreds = {} # local channel -> list of deferreds for pending
+ # requests or 'global' -> list of deferreds for
+ # global requests
+ self.transport = None # gets set later
+
+ def serviceStarted(self):
+ if hasattr(self.transport, 'avatar'):
+ self.transport.avatar.conn = self
+
+ def serviceStopped(self):
+ map(self.channelClosed, self.channels.values())
+
+ # packet methods
+ def ssh_GLOBAL_REQUEST(self, packet):
+ """
+ The other side has made a global request. Payload::
+ string request type
+ bool want reply
+ <request specific data>
+
+ This dispatches to self.gotGlobalRequest.
+ """
+ requestType, rest = common.getNS(packet)
+ wantReply, rest = ord(rest[0]), rest[1:]
+ ret = self.gotGlobalRequest(requestType, rest)
+ if wantReply:
+ reply = MSG_REQUEST_FAILURE
+ data = ''
+ if ret:
+ reply = MSG_REQUEST_SUCCESS
+ if isinstance(ret, (tuple, list)):
+ data = ret[1]
+ self.transport.sendPacket(reply, data)
+
+ def ssh_REQUEST_SUCCESS(self, packet):
+ """
+ Our global request succeeded. Get the appropriate Deferred and call
+ it back with the packet we received.
+ """
+ log.msg('RS')
+ self.deferreds['global'].pop(0).callback(packet)
+
+ def ssh_REQUEST_FAILURE(self, packet):
+ """
+ Our global request failed. Get the appropriate Deferred and errback
+ it with the packet we received.
+ """
+ log.msg('RF')
+ self.deferreds['global'].pop(0).errback(
+ error.ConchError('global request failed', packet))
+
+ def ssh_CHANNEL_OPEN(self, packet):
+ """
+ The other side wants to get a channel. Payload::
+ string channel name
+ uint32 remote channel number
+ uint32 remote window size
+ uint32 remote maximum packet size
+ <channel specific data>
+
+ We get a channel from self.getChannel(), give it a local channel number
+ and notify the other side. Then notify the channel by calling its
+ channelOpen method.
+ """
+ channelType, rest = common.getNS(packet)
+ senderChannel, windowSize, maxPacket = struct.unpack('>3L', rest[:12])
+ packet = rest[12:]
+ try:
+ channel = self.getChannel(channelType, windowSize, maxPacket,
+ packet)
+ localChannel = self.localChannelID
+ self.localChannelID += 1
+ channel.id = localChannel
+ self.channels[localChannel] = channel
+ self.channelsToRemoteChannel[channel] = senderChannel
+ self.localToRemoteChannel[localChannel] = senderChannel
+ self.transport.sendPacket(MSG_CHANNEL_OPEN_CONFIRMATION,
+ struct.pack('>4L', senderChannel, localChannel,
+ channel.localWindowSize,
+ channel.localMaxPacket)+channel.specificData)
+ log.callWithLogger(channel, channel.channelOpen, packet)
+ except Exception, e:
+ log.msg('channel open failed')
+ log.err(e)
+ if isinstance(e, error.ConchError):
+ textualInfo, reason = e.args
+ else:
+ reason = OPEN_CONNECT_FAILED
+ textualInfo = "unknown failure"
+ self.transport.sendPacket(MSG_CHANNEL_OPEN_FAILURE,
+ struct.pack('>2L', senderChannel, reason) +
+ common.NS(textualInfo) + common.NS(''))
+
+ def ssh_CHANNEL_OPEN_CONFIRMATION(self, packet):
+ """
+ The other side accepted our MSG_CHANNEL_OPEN request. Payload::
+ uint32 local channel number
+ uint32 remote channel number
+ uint32 remote window size
+ uint32 remote maximum packet size
+ <channel specific data>
+
+ Find the channel using the local channel number and notify its
+ channelOpen method.
+ """
+ (localChannel, remoteChannel, windowSize,
+ maxPacket) = struct.unpack('>4L', packet[: 16])
+ specificData = packet[16:]
+ channel = self.channels[localChannel]
+ channel.conn = self
+ self.localToRemoteChannel[localChannel] = remoteChannel
+ self.channelsToRemoteChannel[channel] = remoteChannel
+ channel.remoteWindowLeft = windowSize
+ channel.remoteMaxPacket = maxPacket
+ log.callWithLogger(channel, channel.channelOpen, specificData)
+
+ def ssh_CHANNEL_OPEN_FAILURE(self, packet):
+ """
+ The other side did not accept our MSG_CHANNEL_OPEN request. Payload::
+ uint32 local channel number
+ uint32 reason code
+ string reason description
+
+ Find the channel using the local channel number and notify it by
+ calling its openFailed() method.
+ """
+ localChannel, reasonCode = struct.unpack('>2L', packet[:8])
+ reasonDesc = common.getNS(packet[8:])[0]
+ channel = self.channels[localChannel]
+ del self.channels[localChannel]
+ channel.conn = self
+ reason = error.ConchError(reasonDesc, reasonCode)
+ log.callWithLogger(channel, channel.openFailed, reason)
+
+ def ssh_CHANNEL_WINDOW_ADJUST(self, packet):
+ """
+ The other side is adding bytes to its window. Payload::
+ uint32 local channel number
+ uint32 bytes to add
+
+ Call the channel's addWindowBytes() method to add new bytes to the
+ remote window.
+ """
+ localChannel, bytesToAdd = struct.unpack('>2L', packet[:8])
+ channel = self.channels[localChannel]
+ log.callWithLogger(channel, channel.addWindowBytes, bytesToAdd)
+
+ def ssh_CHANNEL_DATA(self, packet):
+ """
+ The other side is sending us data. Payload::
+ uint32 local channel number
+ string data
+
+ Check to make sure the other side hasn't sent too much data (more
+ than what's in the window, or more than the maximum packet size). If
+ they have, close the channel. Otherwise, decrease the available
+ window and pass the data to the channel's dataReceived().
+ """
+ localChannel, dataLength = struct.unpack('>2L', packet[:8])
+ channel = self.channels[localChannel]
+ # XXX should this move to dataReceived to put client in charge?
+ if (dataLength > channel.localWindowLeft or
+ dataLength > channel.localMaxPacket): # more data than we want
+ log.callWithLogger(channel, log.msg, 'too much data')
+ self.sendClose(channel)
+ return
+ #packet = packet[:channel.localWindowLeft+4]
+ data = common.getNS(packet[4:])[0]
+ channel.localWindowLeft -= dataLength
+ if channel.localWindowLeft < channel.localWindowSize / 2:
+ self.adjustWindow(channel, channel.localWindowSize - \
+ channel.localWindowLeft)
+ #log.msg('local window left: %s/%s' % (channel.localWindowLeft,
+ # channel.localWindowSize))
+ log.callWithLogger(channel, channel.dataReceived, data)
+
+ def ssh_CHANNEL_EXTENDED_DATA(self, packet):
+ """
+ The other side is sending us exteneded data. Payload::
+ uint32 local channel number
+ uint32 type code
+ string data
+
+ Check to make sure the other side hasn't sent too much data (more
+ than what's in the window, or or than the maximum packet size). If
+ they have, close the channel. Otherwise, decrease the available
+ window and pass the data and type code to the channel's
+ extReceived().
+ """
+ localChannel, typeCode, dataLength = struct.unpack('>3L', packet[:12])
+ channel = self.channels[localChannel]
+ if (dataLength > channel.localWindowLeft or
+ dataLength > channel.localMaxPacket):
+ log.callWithLogger(channel, log.msg, 'too much extdata')
+ self.sendClose(channel)
+ return
+ data = common.getNS(packet[8:])[0]
+ channel.localWindowLeft -= dataLength
+ if channel.localWindowLeft < channel.localWindowSize / 2:
+ self.adjustWindow(channel, channel.localWindowSize -
+ channel.localWindowLeft)
+ log.callWithLogger(channel, channel.extReceived, typeCode, data)
+
+ def ssh_CHANNEL_EOF(self, packet):
+ """
+ The other side is not sending any more data. Payload::
+ uint32 local channel number
+
+ Notify the channel by calling its eofReceived() method.
+ """
+ localChannel = struct.unpack('>L', packet[:4])[0]
+ channel = self.channels[localChannel]
+ log.callWithLogger(channel, channel.eofReceived)
+
+ def ssh_CHANNEL_CLOSE(self, packet):
+ """
+ The other side is closing its end; it does not want to receive any
+ more data. Payload::
+ uint32 local channel number
+
+ Notify the channnel by calling its closeReceived() method. If
+ the channel has also sent a close message, call self.channelClosed().
+ """
+ localChannel = struct.unpack('>L', packet[:4])[0]
+ channel = self.channels[localChannel]
+ log.callWithLogger(channel, channel.closeReceived)
+ channel.remoteClosed = True
+ if channel.localClosed and channel.remoteClosed:
+ self.channelClosed(channel)
+
+ def ssh_CHANNEL_REQUEST(self, packet):
+ """
+ The other side is sending a request to a channel. Payload::
+ uint32 local channel number
+ string request name
+ bool want reply
+ <request specific data>
+
+ Pass the message to the channel's requestReceived method. If the
+ other side wants a reply, add callbacks which will send the
+ reply.
+ """
+ localChannel = struct.unpack('>L', packet[: 4])[0]
+ requestType, rest = common.getNS(packet[4:])
+ wantReply = ord(rest[0])
+ channel = self.channels[localChannel]
+ d = defer.maybeDeferred(log.callWithLogger, channel,
+ channel.requestReceived, requestType, rest[1:])
+ if wantReply:
+ d.addCallback(self._cbChannelRequest, localChannel)
+ d.addErrback(self._ebChannelRequest, localChannel)
+ return d
+
+ def _cbChannelRequest(self, result, localChannel):
+ """
+ Called back if the other side wanted a reply to a channel request. If
+ the result is true, send a MSG_CHANNEL_SUCCESS. Otherwise, raise
+ a C{error.ConchError}
+
+ @param result: the value returned from the channel's requestReceived()
+ method. If it's False, the request failed.
+ @type result: C{bool}
+ @param localChannel: the local channel ID of the channel to which the
+ request was made.
+ @type localChannel: C{int}
+ @raises ConchError: if the result is False.
+ """
+ if not result:
+ raise error.ConchError('failed request')
+ self.transport.sendPacket(MSG_CHANNEL_SUCCESS, struct.pack('>L',
+ self.localToRemoteChannel[localChannel]))
+
+ def _ebChannelRequest(self, result, localChannel):
+ """
+ Called if the other wisde wanted a reply to the channel requeset and
+ the channel request failed.
+
+ @param result: a Failure, but it's not used.
+ @param localChannel: the local channel ID of the channel to which the
+ request was made.
+ @type localChannel: C{int}
+ """
+ self.transport.sendPacket(MSG_CHANNEL_FAILURE, struct.pack('>L',
+ self.localToRemoteChannel[localChannel]))
+
+ def ssh_CHANNEL_SUCCESS(self, packet):
+ """
+ Our channel request to the other other side succeeded. Payload::
+ uint32 local channel number
+
+ Get the C{Deferred} out of self.deferreds and call it back.
+ """
+ localChannel = struct.unpack('>L', packet[:4])[0]
+ if self.deferreds.get(localChannel):
+ d = self.deferreds[localChannel].pop(0)
+ log.callWithLogger(self.channels[localChannel],
+ d.callback, '')
+
+ def ssh_CHANNEL_FAILURE(self, packet):
+ """
+ Our channel request to the other side failed. Payload::
+ uint32 local channel number
+
+ Get the C{Deferred} out of self.deferreds and errback it with a
+ C{error.ConchError}.
+ """
+ localChannel = struct.unpack('>L', packet[:4])[0]
+ if self.deferreds.get(localChannel):
+ d = self.deferreds[localChannel].pop(0)
+ log.callWithLogger(self.channels[localChannel],
+ d.errback,
+ error.ConchError('channel request failed'))
+
+ # methods for users of the connection to call
+
+ def sendGlobalRequest(self, request, data, wantReply=0):
+ """
+ Send a global request for this connection. Current this is only used
+ for remote->local TCP forwarding.
+
+ @type request: C{str}
+ @type data: C{str}
+ @type wantReply: C{bool}
+ @rtype C{Deferred}/C{None}
+ """
+ self.transport.sendPacket(MSG_GLOBAL_REQUEST,
+ common.NS(request)
+ + (wantReply and '\xff' or '\x00')
+ + data)
+ if wantReply:
+ d = defer.Deferred()
+ self.deferreds.setdefault('global', []).append(d)
+ return d
+
+ def openChannel(self, channel, extra=''):
+ """
+ Open a new channel on this connection.
+
+ @type channel: subclass of C{SSHChannel}
+ @type extra: C{str}
+ """
+ log.msg('opening channel %s with %s %s'%(self.localChannelID,
+ channel.localWindowSize, channel.localMaxPacket))
+ self.transport.sendPacket(MSG_CHANNEL_OPEN, common.NS(channel.name)
+ + struct.pack('>3L', self.localChannelID,
+ channel.localWindowSize, channel.localMaxPacket)
+ + extra)
+ channel.id = self.localChannelID
+ self.channels[self.localChannelID] = channel
+ self.localChannelID += 1
+
+ def sendRequest(self, channel, requestType, data, wantReply=0):
+ """
+ Send a request to a channel.
+
+ @type channel: subclass of C{SSHChannel}
+ @type requestType: C{str}
+ @type data: C{str}
+ @type wantReply: C{bool}
+ @rtype C{Deferred}/C{None}
+ """
+ if channel.localClosed:
+ return
+ log.msg('sending request %s' % requestType)
+ self.transport.sendPacket(MSG_CHANNEL_REQUEST, struct.pack('>L',
+ self.channelsToRemoteChannel[channel])
+ + common.NS(requestType)+chr(wantReply)
+ + data)
+ if wantReply:
+ d = defer.Deferred()
+ self.deferreds.setdefault(channel.id, []).append(d)
+ return d
+
+ def adjustWindow(self, channel, bytesToAdd):
+ """
+ Tell the other side that we will receive more data. This should not
+ normally need to be called as it is managed automatically.
+
+ @type channel: subclass of L{SSHChannel}
+ @type bytesToAdd: C{int}
+ """
+ if channel.localClosed:
+ return # we're already closed
+ self.transport.sendPacket(MSG_CHANNEL_WINDOW_ADJUST, struct.pack('>2L',
+ self.channelsToRemoteChannel[channel],
+ bytesToAdd))
+ log.msg('adding %i to %i in channel %i' % (bytesToAdd,
+ channel.localWindowLeft, channel.id))
+ channel.localWindowLeft += bytesToAdd
+
+ def sendData(self, channel, data):
+ """
+ Send data to a channel. This should not normally be used: instead use
+ channel.write(data) as it manages the window automatically.
+
+ @type channel: subclass of L{SSHChannel}
+ @type data: C{str}
+ """
+ if channel.localClosed:
+ return # we're already closed
+ self.transport.sendPacket(MSG_CHANNEL_DATA, struct.pack('>L',
+ self.channelsToRemoteChannel[channel]) +
+ common.NS(data))
+
+ def sendExtendedData(self, channel, dataType, data):
+ """
+ Send extended data to a channel. This should not normally be used:
+ instead use channel.writeExtendedData(data, dataType) as it manages
+ the window automatically.
+
+ @type channel: subclass of L{SSHChannel}
+ @type dataType: C{int}
+ @type data: C{str}
+ """
+ if channel.localClosed:
+ return # we're already closed
+ self.transport.sendPacket(MSG_CHANNEL_EXTENDED_DATA, struct.pack('>2L',
+ self.channelsToRemoteChannel[channel],dataType) \
+ + common.NS(data))
+
+ def sendEOF(self, channel):
+ """
+ Send an EOF (End of File) for a channel.
+
+ @type channel: subclass of L{SSHChannel}
+ """
+ if channel.localClosed:
+ return # we're already closed
+ log.msg('sending eof')
+ self.transport.sendPacket(MSG_CHANNEL_EOF, struct.pack('>L',
+ self.channelsToRemoteChannel[channel]))
+
+ def sendClose(self, channel):
+ """
+ Close a channel.
+
+ @type channel: subclass of L{SSHChannel}
+ """
+ if channel.localClosed:
+ return # we're already closed
+ log.msg('sending close %i' % channel.id)
+ self.transport.sendPacket(MSG_CHANNEL_CLOSE, struct.pack('>L',
+ self.channelsToRemoteChannel[channel]))
+ channel.localClosed = True
+ if channel.localClosed and channel.remoteClosed:
+ self.channelClosed(channel)
+
+ # methods to override
+ def getChannel(self, channelType, windowSize, maxPacket, data):
+ """
+ The other side requested a channel of some sort.
+ channelType is the type of channel being requested,
+ windowSize is the initial size of the remote window,
+ maxPacket is the largest packet we should send,
+ data is any other packet data (often nothing).
+
+ We return a subclass of L{SSHChannel}.
+
+ By default, this dispatches to a method 'channel_channelType' with any
+ non-alphanumerics in the channelType replace with _'s. If it cannot
+ find a suitable method, it returns an OPEN_UNKNOWN_CHANNEL_TYPE error.
+ The method is called with arguments of windowSize, maxPacket, data.
+
+ @type channelType: C{str}
+ @type windowSize: C{int}
+ @type maxPacket: C{int}
+ @type data: C{str}
+ @rtype: subclass of L{SSHChannel}/C{tuple}
+ """
+ log.msg('got channel %s request' % channelType)
+ if hasattr(self.transport, "avatar"): # this is a server!
+ chan = self.transport.avatar.lookupChannel(channelType,
+ windowSize,
+ maxPacket,
+ data)
+ else:
+ channelType = channelType.translate(TRANSLATE_TABLE)
+ f = getattr(self, 'channel_%s' % channelType, None)
+ if f is not None:
+ chan = f(windowSize, maxPacket, data)
+ else:
+ chan = None
+ if chan is None:
+ raise error.ConchError('unknown channel',
+ OPEN_UNKNOWN_CHANNEL_TYPE)
+ else:
+ chan.conn = self
+ return chan
+
+ def gotGlobalRequest(self, requestType, data):
+ """
+ We got a global request. pretty much, this is just used by the client
+ to request that we forward a port from the server to the client.
+ Returns either:
+ - 1: request accepted
+ - 1, <data>: request accepted with request specific data
+ - 0: request denied
+
+ By default, this dispatches to a method 'global_requestType' with
+ -'s in requestType replaced with _'s. The found method is passed data.
+ If this method cannot be found, this method returns 0. Otherwise, it
+ returns the return value of that method.
+
+ @type requestType: C{str}
+ @type data: C{str}
+ @rtype: C{int}/C{tuple}
+ """
+ log.msg('got global %s request' % requestType)
+ if hasattr(self.transport, 'avatar'): # this is a server!
+ return self.transport.avatar.gotGlobalRequest(requestType, data)
+
+ requestType = requestType.replace('-','_')
+ f = getattr(self, 'global_%s' % requestType, None)
+ if not f:
+ return 0
+ return f(data)
+
+ def channelClosed(self, channel):
+ """
+ Called when a channel is closed.
+ It clears the local state related to the channel, and calls
+ channel.closed().
+ MAKE SURE YOU CALL THIS METHOD, even if you subclass L{SSHConnection}.
+ If you don't, things will break mysteriously.
+ """
+ if channel in self.channelsToRemoteChannel: # actually open
+ channel.localClosed = channel.remoteClosed = True
+ del self.localToRemoteChannel[channel.id]
+ del self.channels[channel.id]
+ del self.channelsToRemoteChannel[channel]
+ self.deferreds[channel.id] = []
+ log.callWithLogger(channel, channel.closed)
+
+MSG_GLOBAL_REQUEST = 80
+MSG_REQUEST_SUCCESS = 81
+MSG_REQUEST_FAILURE = 82
+MSG_CHANNEL_OPEN = 90
+MSG_CHANNEL_OPEN_CONFIRMATION = 91
+MSG_CHANNEL_OPEN_FAILURE = 92
+MSG_CHANNEL_WINDOW_ADJUST = 93
+MSG_CHANNEL_DATA = 94
+MSG_CHANNEL_EXTENDED_DATA = 95
+MSG_CHANNEL_EOF = 96
+MSG_CHANNEL_CLOSE = 97
+MSG_CHANNEL_REQUEST = 98
+MSG_CHANNEL_SUCCESS = 99
+MSG_CHANNEL_FAILURE = 100
+
+OPEN_ADMINISTRATIVELY_PROHIBITED = 1
+OPEN_CONNECT_FAILED = 2
+OPEN_UNKNOWN_CHANNEL_TYPE = 3
+OPEN_RESOURCE_SHORTAGE = 4
+
+EXTENDED_DATA_STDERR = 1
+
+messages = {}
+for name, value in locals().copy().items():
+ if name[:4] == 'MSG_':
+ messages[value] = name # doesn't handle doubles
+
+import string
+alphanums = string.letters + string.digits
+TRANSLATE_TABLE = ''.join([chr(i) in alphanums and chr(i) or '_'
+ for i in range(256)])
+SSHConnection.protocolMessages = messages
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/factory.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/factory.py
new file mode 100644
index 0000000000..2a272d02e5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/factory.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A Factory for SSH servers, along with an OpenSSHFactory to use the same
+data sources as OpenSSH.
+
+Maintainer: Paul Swartz
+"""
+
+try:
+ import resource
+except ImportError:
+ resource = None
+
+from twisted.internet import protocol
+from twisted.python import log
+from twisted.python.reflect import qual
+
+from twisted.conch import error
+from twisted.conch.ssh import keys
+import transport, userauth, connection
+
+import random
+import warnings
+
+class SSHFactory(protocol.Factory):
+
+ protocol = transport.SSHServerTransport
+
+ services = {
+ 'ssh-userauth':userauth.SSHUserAuthServer,
+ 'ssh-connection':connection.SSHConnection
+ }
+ def startFactory(self):
+ # disable coredumps
+ if resource:
+ resource.setrlimit(resource.RLIMIT_CORE, (0,0))
+ else:
+ log.msg('INSECURE: unable to disable core dumps.')
+ if not hasattr(self,'publicKeys'):
+ self.publicKeys = self.getPublicKeys()
+ for keyType, value in self.publicKeys.items():
+ if isinstance(value, str):
+ warnings.warn("Returning a mapping from strings to "
+ "strings from getPublicKeys()/publicKeys (in %s) "
+ "is deprecated. Return a mapping from "
+ "strings to Key objects instead." %
+ (qual(self.__class__)),
+ DeprecationWarning, stacklevel=1)
+ self.publicKeys[keyType] = keys.Key.fromString(value)
+ if not hasattr(self,'privateKeys'):
+ self.privateKeys = self.getPrivateKeys()
+ for keyType, value in self.privateKeys.items():
+ if not isinstance(value, keys.Key):
+ warnings.warn("Returning a mapping from strings to "
+ "PyCrypto key objects from "
+ "getPrivateKeys()/privateKeys (in %s) "
+ "is deprecated. Return a mapping from "
+ "strings to Key objects instead." %
+ (qual(self.__class__),),
+ DeprecationWarning, stacklevel=1)
+ self.privateKeys[keyType] = keys.Key(value)
+ if not self.publicKeys or not self.privateKeys:
+ raise error.ConchError('no host keys, failing')
+ if not hasattr(self,'primes'):
+ self.primes = self.getPrimes()
+
+ def buildProtocol(self, addr):
+ t = protocol.Factory.buildProtocol(self, addr)
+ t.supportedPublicKeys = self.privateKeys.keys()
+ if not self.primes:
+ log.msg('disabling diffie-hellman-group-exchange because we '
+ 'cannot find moduli file')
+ ske = t.supportedKeyExchanges[:]
+ ske.remove('diffie-hellman-group-exchange-sha1')
+ t.supportedKeyExchanges = ske
+ return t
+
+ def getPublicKeys(self):
+ """
+ Called when the factory is started to get the public portions of the
+ servers host keys. Returns a dictionary mapping SSH key types to
+ public key strings.
+
+ @rtype: C{dict}
+ """
+ raise NotImplementedError('getPublicKeys unimplemented')
+
+ def getPrivateKeys(self):
+ """
+ Called when the factory is started to get the private portions of the
+ servers host keys. Returns a dictionary mapping SSH key types to
+ C{Crypto.PublicKey.pubkey.pubkey} objects.
+
+ @rtype: C{dict}
+ """
+ raise NotImplementedError('getPrivateKeys unimplemented')
+
+ def getPrimes(self):
+ """
+ Called when the factory is started to get Diffie-Hellman generators and
+ primes to use. Returns a dictionary mapping number of bits to lists
+ of tuple of (generator, prime).
+
+ @rtype: C{dict}
+ """
+
+ def getDHPrime(self, bits):
+ """
+ Return a tuple of (g, p) for a Diffe-Hellman process, with p being as
+ close to bits bits as possible.
+
+ @type bits: C{int}
+ @rtype: C{tuple}
+ """
+ primesKeys = self.primes.keys()
+ primesKeys.sort(lambda x, y: cmp(abs(x - bits), abs(y - bits)))
+ realBits = primesKeys[0]
+ return random.choice(self.primes[realBits])
+
+ def getService(self, transport, service):
+ """
+ Return a class to use as a service for the given transport.
+
+ @type transport: L{transport.SSHServerTransport}
+ @type service: C{str}
+ @rtype: subclass of L{service.SSHService}
+ """
+ if service == 'ssh-userauth' or hasattr(transport, 'avatar'):
+ return self.services[service]
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/filetransfer.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/filetransfer.py
new file mode 100644
index 0000000000..e2cd7492b6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/filetransfer.py
@@ -0,0 +1,927 @@
+# -*- test-case-name: twisted.conch.test.test_filetransfer -*-
+#
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import struct, errno
+
+from twisted.internet import defer, protocol
+from twisted.python import failure, log
+
+from common import NS, getNS
+from twisted.conch.interfaces import ISFTPServer, ISFTPFile
+
+from zope import interface
+
+
+
+class FileTransferBase(protocol.Protocol):
+
+ versions = (3, )
+
+ packetTypes = {}
+
+ def __init__(self):
+ self.buf = ''
+ self.otherVersion = None # this gets set
+
+ def sendPacket(self, kind, data):
+ self.transport.write(struct.pack('!LB', len(data)+1, kind) + data)
+
+ def dataReceived(self, data):
+ self.buf += data
+ while len(self.buf) > 5:
+ length, kind = struct.unpack('!LB', self.buf[:5])
+ if len(self.buf) < 4 + length:
+ return
+ data, self.buf = self.buf[5:4+length], self.buf[4+length:]
+ packetType = self.packetTypes.get(kind, None)
+ if not packetType:
+ log.msg('no packet type for', kind)
+ continue
+ f = getattr(self, 'packet_%s' % packetType, None)
+ if not f:
+ log.msg('not implemented: %s' % packetType)
+ log.msg(repr(data[4:]))
+ reqId, = struct.unpack('!L', data[:4])
+ self._sendStatus(reqId, FX_OP_UNSUPPORTED,
+ "don't understand %s" % packetType)
+ #XXX not implemented
+ continue
+ try:
+ f(data)
+ except:
+ log.err()
+ continue
+ reqId ,= struct.unpack('!L', data[:4])
+ self._ebStatus(failure.Failure(e), reqId)
+
+ def _parseAttributes(self, data):
+ flags ,= struct.unpack('!L', data[:4])
+ attrs = {}
+ data = data[4:]
+ if flags & FILEXFER_ATTR_SIZE == FILEXFER_ATTR_SIZE:
+ size ,= struct.unpack('!Q', data[:8])
+ attrs['size'] = size
+ data = data[8:]
+ if flags & FILEXFER_ATTR_OWNERGROUP == FILEXFER_ATTR_OWNERGROUP:
+ uid, gid = struct.unpack('!2L', data[:8])
+ attrs['uid'] = uid
+ attrs['gid'] = gid
+ data = data[8:]
+ if flags & FILEXFER_ATTR_PERMISSIONS == FILEXFER_ATTR_PERMISSIONS:
+ perms ,= struct.unpack('!L', data[:4])
+ attrs['permissions'] = perms
+ data = data[4:]
+ if flags & FILEXFER_ATTR_ACMODTIME == FILEXFER_ATTR_ACMODTIME:
+ atime, mtime = struct.unpack('!2L', data[:8])
+ attrs['atime'] = atime
+ attrs['mtime'] = mtime
+ data = data[8:]
+ if flags & FILEXFER_ATTR_EXTENDED == FILEXFER_ATTR_EXTENDED:
+ extended_count ,= struct.unpack('!L', data[:4])
+ data = data[4:]
+ for i in xrange(extended_count):
+ extended_type, data = getNS(data)
+ extended_data, data = getNS(data)
+ attrs['ext_%s' % extended_type] = extended_data
+ return attrs, data
+
+ def _packAttributes(self, attrs):
+ flags = 0
+ data = ''
+ if 'size' in attrs:
+ data += struct.pack('!Q', attrs['size'])
+ flags |= FILEXFER_ATTR_SIZE
+ if 'uid' in attrs and 'gid' in attrs:
+ data += struct.pack('!2L', attrs['uid'], attrs['gid'])
+ flags |= FILEXFER_ATTR_OWNERGROUP
+ if 'permissions' in attrs:
+ data += struct.pack('!L', attrs['permissions'])
+ flags |= FILEXFER_ATTR_PERMISSIONS
+ if 'atime' in attrs and 'mtime' in attrs:
+ data += struct.pack('!2L', attrs['atime'], attrs['mtime'])
+ flags |= FILEXFER_ATTR_ACMODTIME
+ extended = []
+ for k in attrs:
+ if k.startswith('ext_'):
+ ext_type = NS(k[4:])
+ ext_data = NS(attrs[k])
+ extended.append(ext_type+ext_data)
+ if extended:
+ data += struct.pack('!L', len(extended))
+ data += ''.join(extended)
+ flags |= FILEXFER_ATTR_EXTENDED
+ return struct.pack('!L', flags) + data
+
+class FileTransferServer(FileTransferBase):
+
+ def __init__(self, data=None, avatar=None):
+ FileTransferBase.__init__(self)
+ self.client = ISFTPServer(avatar) # yay interfaces
+ self.openFiles = {}
+ self.openDirs = {}
+
+ def packet_INIT(self, data):
+ version ,= struct.unpack('!L', data[:4])
+ self.version = min(list(self.versions) + [version])
+ data = data[4:]
+ ext = {}
+ while data:
+ ext_name, data = getNS(data)
+ ext_data, data = getNS(data)
+ ext[ext_name] = ext_data
+ our_ext = self.client.gotVersion(version, ext)
+ our_ext_data = ""
+ for (k,v) in our_ext.items():
+ our_ext_data += NS(k) + NS(v)
+ self.sendPacket(FXP_VERSION, struct.pack('!L', self.version) + \
+ our_ext_data)
+
+ def packet_OPEN(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ filename, data = getNS(data)
+ flags ,= struct.unpack('!L', data[:4])
+ data = data[4:]
+ attrs, data = self._parseAttributes(data)
+ assert data == '', 'still have data in OPEN: %s' % repr(data)
+ d = defer.maybeDeferred(self.client.openFile, filename, flags, attrs)
+ d.addCallback(self._cbOpenFile, requestId)
+ d.addErrback(self._ebStatus, requestId, "open failed")
+
+ def _cbOpenFile(self, fileObj, requestId):
+ fileId = str(hash(fileObj))
+ if fileId in self.openFiles:
+ raise KeyError, 'id already open'
+ self.openFiles[fileId] = fileObj
+ self.sendPacket(FXP_HANDLE, requestId + NS(fileId))
+
+ def packet_CLOSE(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ assert data == '', 'still have data in CLOSE: %s' % repr(data)
+ if handle in self.openFiles:
+ fileObj = self.openFiles[handle]
+ d = defer.maybeDeferred(fileObj.close)
+ d.addCallback(self._cbClose, handle, requestId)
+ d.addErrback(self._ebStatus, requestId, "close failed")
+ elif handle in self.openDirs:
+ dirObj = self.openDirs[handle][0]
+ d = defer.maybeDeferred(dirObj.close)
+ d.addCallback(self._cbClose, handle, requestId, 1)
+ d.addErrback(self._ebStatus, requestId, "close failed")
+ else:
+ self._ebClose(failure.Failure(KeyError()), requestId)
+
+ def _cbClose(self, result, handle, requestId, isDir = 0):
+ if isDir:
+ del self.openDirs[handle]
+ else:
+ del self.openFiles[handle]
+ self._sendStatus(requestId, FX_OK, 'file closed')
+
+ def packet_READ(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ (offset, length), data = struct.unpack('!QL', data[:12]), data[12:]
+ assert data == '', 'still have data in READ: %s' % repr(data)
+ if handle not in self.openFiles:
+ self._ebRead(failure.Failure(KeyError()), requestId)
+ else:
+ fileObj = self.openFiles[handle]
+ d = defer.maybeDeferred(fileObj.readChunk, offset, length)
+ d.addCallback(self._cbRead, requestId)
+ d.addErrback(self._ebStatus, requestId, "read failed")
+
+ def _cbRead(self, result, requestId):
+ if result == '': # python's read will return this for EOF
+ raise EOFError()
+ self.sendPacket(FXP_DATA, requestId + NS(result))
+
+ def packet_WRITE(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ offset, = struct.unpack('!Q', data[:8])
+ data = data[8:]
+ writeData, data = getNS(data)
+ assert data == '', 'still have data in WRITE: %s' % repr(data)
+ if handle not in self.openFiles:
+ self._ebWrite(failure.Failure(KeyError()), requestId)
+ else:
+ fileObj = self.openFiles[handle]
+ d = defer.maybeDeferred(fileObj.writeChunk, offset, writeData)
+ d.addCallback(self._cbStatus, requestId, "write succeeded")
+ d.addErrback(self._ebStatus, requestId, "write failed")
+
+ def packet_REMOVE(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ filename, data = getNS(data)
+ assert data == '', 'still have data in REMOVE: %s' % repr(data)
+ d = defer.maybeDeferred(self.client.removeFile, filename)
+ d.addCallback(self._cbStatus, requestId, "remove succeeded")
+ d.addErrback(self._ebStatus, requestId, "remove failed")
+
+ def packet_RENAME(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ oldPath, data = getNS(data)
+ newPath, data = getNS(data)
+ assert data == '', 'still have data in RENAME: %s' % repr(data)
+ d = defer.maybeDeferred(self.client.renameFile, oldPath, newPath)
+ d.addCallback(self._cbStatus, requestId, "rename succeeded")
+ d.addErrback(self._ebStatus, requestId, "rename failed")
+
+ def packet_MKDIR(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ attrs, data = self._parseAttributes(data)
+ assert data == '', 'still have data in MKDIR: %s' % repr(data)
+ d = defer.maybeDeferred(self.client.makeDirectory, path, attrs)
+ d.addCallback(self._cbStatus, requestId, "mkdir succeeded")
+ d.addErrback(self._ebStatus, requestId, "mkdir failed")
+
+ def packet_RMDIR(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ assert data == '', 'still have data in RMDIR: %s' % repr(data)
+ d = defer.maybeDeferred(self.client.removeDirectory, path)
+ d.addCallback(self._cbStatus, requestId, "rmdir succeeded")
+ d.addErrback(self._ebStatus, requestId, "rmdir failed")
+
+ def packet_OPENDIR(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ assert data == '', 'still have data in OPENDIR: %s' % repr(data)
+ d = defer.maybeDeferred(self.client.openDirectory, path)
+ d.addCallback(self._cbOpenDirectory, requestId)
+ d.addErrback(self._ebStatus, requestId, "opendir failed")
+
+ def _cbOpenDirectory(self, dirObj, requestId):
+ handle = str(hash(dirObj))
+ if handle in self.openDirs:
+ raise KeyError, "already opened this directory"
+ self.openDirs[handle] = [dirObj, iter(dirObj)]
+ self.sendPacket(FXP_HANDLE, requestId + NS(handle))
+
+ def packet_READDIR(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ assert data == '', 'still have data in READDIR: %s' % repr(data)
+ if handle not in self.openDirs:
+ self._ebStatus(failure.Failure(KeyError()), requestId)
+ else:
+ dirObj, dirIter = self.openDirs[handle]
+ d = defer.maybeDeferred(self._scanDirectory, dirIter, [])
+ d.addCallback(self._cbSendDirectory, requestId)
+ d.addErrback(self._ebStatus, requestId, "scan directory failed")
+
+ def _scanDirectory(self, dirIter, f):
+ while len(f) < 250:
+ try:
+ info = dirIter.next()
+ except StopIteration:
+ if not f:
+ raise EOFError
+ return f
+ if isinstance(info, defer.Deferred):
+ info.addCallback(self._cbScanDirectory, dirIter, f)
+ return
+ else:
+ f.append(info)
+ return f
+
+ def _cbScanDirectory(self, result, dirIter, f):
+ f.append(result)
+ return self._scanDirectory(dirIter, f)
+
+ def _cbSendDirectory(self, result, requestId):
+ data = ''
+ for (filename, longname, attrs) in result:
+ data += NS(filename)
+ data += NS(longname)
+ data += self._packAttributes(attrs)
+ self.sendPacket(FXP_NAME, requestId +
+ struct.pack('!L', len(result))+data)
+
+ def packet_STAT(self, data, followLinks = 1):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ assert data == '', 'still have data in STAT/LSTAT: %s' % repr(data)
+ d = defer.maybeDeferred(self.client.getAttrs, path, followLinks)
+ d.addCallback(self._cbStat, requestId)
+ d.addErrback(self._ebStatus, requestId, 'stat/lstat failed')
+
+ def packet_LSTAT(self, data):
+ self.packet_STAT(data, 0)
+
+ def packet_FSTAT(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ assert data == '', 'still have data in FSTAT: %s' % repr(data)
+ if handle not in self.openFiles:
+ self._ebStatus(failure.Failure(KeyError('%s not in self.openFiles'
+ % handle)), requestId)
+ else:
+ fileObj = self.openFiles[handle]
+ d = defer.maybeDeferred(fileObj.getAttrs)
+ d.addCallback(self._cbStat, requestId)
+ d.addErrback(self._ebStatus, requestId, 'fstat failed')
+
+ def _cbStat(self, result, requestId):
+ data = requestId + self._packAttributes(result)
+ self.sendPacket(FXP_ATTRS, data)
+
+ def packet_SETSTAT(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ attrs, data = self._parseAttributes(data)
+ if data != '':
+ log.msg('WARN: still have data in SETSTAT: %s' % repr(data))
+ d = defer.maybeDeferred(self.client.setAttrs, path, attrs)
+ d.addCallback(self._cbStatus, requestId, 'setstat succeeded')
+ d.addErrback(self._ebStatus, requestId, 'setstat failed')
+
+ def packet_FSETSTAT(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ handle, data = getNS(data)
+ attrs, data = self._parseAttributes(data)
+ assert data == '', 'still have data in FSETSTAT: %s' % repr(data)
+ if handle not in self.openFiles:
+ self._ebStatus(failure.Failure(KeyError()), requestId)
+ else:
+ fileObj = self.openFiles[handle]
+ d = defer.maybeDeferred(fileObj.setAttrs, attrs)
+ d.addCallback(self._cbStatus, requestId, 'fsetstat succeeded')
+ d.addErrback(self._ebStatus, requestId, 'fsetstat failed')
+
+ def packet_READLINK(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ assert data == '', 'still have data in READLINK: %s' % repr(data)
+ d = defer.maybeDeferred(self.client.readLink, path)
+ d.addCallback(self._cbReadLink, requestId)
+ d.addErrback(self._ebStatus, requestId, 'readlink failed')
+
+ def _cbReadLink(self, result, requestId):
+ self._cbSendDirectory([(result, '', {})], requestId)
+
+ def packet_SYMLINK(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ linkPath, data = getNS(data)
+ targetPath, data = getNS(data)
+ d = defer.maybeDeferred(self.client.makeLink, linkPath, targetPath)
+ d.addCallback(self._cbStatus, requestId, 'symlink succeeded')
+ d.addErrback(self._ebStatus, requestId, 'symlink failed')
+
+ def packet_REALPATH(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ path, data = getNS(data)
+ assert data == '', 'still have data in REALPATH: %s' % repr(data)
+ d = defer.maybeDeferred(self.client.realPath, path)
+ d.addCallback(self._cbReadLink, requestId) # same return format
+ d.addErrback(self._ebStatus, requestId, 'realpath failed')
+
+ def packet_EXTENDED(self, data):
+ requestId = data[:4]
+ data = data[4:]
+ extName, extData = getNS(data)
+ d = defer.maybeDeferred(self.client.extendedRequest, extName, extData)
+ d.addCallback(self._cbExtended, requestId)
+ d.addErrback(self._ebStatus, requestId, 'extended %s failed' % extName)
+
+ def _cbExtended(self, data, requestId):
+ self.sendPacket(FXP_EXTENDED_REPLY, requestId + data)
+
+ def _cbStatus(self, result, requestId, msg = "request succeeded"):
+ self._sendStatus(requestId, FX_OK, msg)
+
+ def _ebStatus(self, reason, requestId, msg = "request failed"):
+ code = FX_FAILURE
+ message = msg
+ if reason.type in (IOError, OSError):
+ if reason.value.errno == errno.ENOENT: # no such file
+ code = FX_NO_SUCH_FILE
+ message = reason.value.strerror
+ elif reason.value.errno == errno.EACCES: # permission denied
+ code = FX_PERMISSION_DENIED
+ message = reason.value.strerror
+ elif reason.value.errno == errno.EEXIST:
+ code = FX_FILE_ALREADY_EXISTS
+ else:
+ log.err(reason)
+ elif reason.type == EOFError: # EOF
+ code = FX_EOF
+ if reason.value.args:
+ message = reason.value.args[0]
+ elif reason.type == NotImplementedError:
+ code = FX_OP_UNSUPPORTED
+ if reason.value.args:
+ message = reason.value.args[0]
+ elif reason.type == SFTPError:
+ code = reason.value.code
+ message = reason.value.message
+ else:
+ log.err(reason)
+ self._sendStatus(requestId, code, message)
+
+ def _sendStatus(self, requestId, code, message, lang = ''):
+ """
+ Helper method to send a FXP_STATUS message.
+ """
+ data = requestId + struct.pack('!L', code)
+ data += NS(message)
+ data += NS(lang)
+ self.sendPacket(FXP_STATUS, data)
+
+
+ def connectionLost(self, reason):
+ """
+ Clean all opened files and directories.
+ """
+ for fileObj in self.openFiles.values():
+ fileObj.close()
+ self.openFiles = {}
+ for (dirObj, dirIter) in self.openDirs.values():
+ dirObj.close()
+ self.openDirs = {}
+
+
+
+class FileTransferClient(FileTransferBase):
+
+ def __init__(self, extData = {}):
+ """
+ @param extData: a dict of extended_name : extended_data items
+ to be sent to the server.
+ """
+ FileTransferBase.__init__(self)
+ self.extData = {}
+ self.counter = 0
+ self.openRequests = {} # id -> Deferred
+ self.wasAFile = {} # Deferred -> 1 TERRIBLE HACK
+
+ def connectionMade(self):
+ data = struct.pack('!L', max(self.versions))
+ for k,v in self.extData.itervalues():
+ data += NS(k) + NS(v)
+ self.sendPacket(FXP_INIT, data)
+
+ def _sendRequest(self, msg, data):
+ data = struct.pack('!L', self.counter) + data
+ d = defer.Deferred()
+ self.openRequests[self.counter] = d
+ self.counter += 1
+ self.sendPacket(msg, data)
+ return d
+
+ def _parseRequest(self, data):
+ (id,) = struct.unpack('!L', data[:4])
+ d = self.openRequests[id]
+ del self.openRequests[id]
+ return d, data[4:]
+
+ def openFile(self, filename, flags, attrs):
+ """
+ Open a file.
+
+ This method returns a L{Deferred} that is called back with an object
+ that provides the L{ISFTPFile} interface.
+
+ @param filename: a string representing the file to open.
+
+ @param flags: a integer of the flags to open the file with, ORed together.
+ The flags and their values are listed at the bottom of this file.
+
+ @param attrs: a list of attributes to open the file with. It is a
+ dictionary, consisting of 0 or more keys. The possible keys are::
+
+ size: the size of the file in bytes
+ uid: the user ID of the file as an integer
+ gid: the group ID of the file as an integer
+ permissions: the permissions of the file with as an integer.
+ the bit representation of this field is defined by POSIX.
+ atime: the access time of the file as seconds since the epoch.
+ mtime: the modification time of the file as seconds since the epoch.
+ ext_*: extended attributes. The server is not required to
+ understand this, but it may.
+
+ NOTE: there is no way to indicate text or binary files. it is up
+ to the SFTP client to deal with this.
+ """
+ data = NS(filename) + struct.pack('!L', flags) + self._packAttributes(attrs)
+ d = self._sendRequest(FXP_OPEN, data)
+ self.wasAFile[d] = (1, filename) # HACK
+ return d
+
+ def removeFile(self, filename):
+ """
+ Remove the given file.
+
+ This method returns a Deferred that is called back when it succeeds.
+
+ @param filename: the name of the file as a string.
+ """
+ return self._sendRequest(FXP_REMOVE, NS(filename))
+
+ def renameFile(self, oldpath, newpath):
+ """
+ Rename the given file.
+
+ This method returns a Deferred that is called back when it succeeds.
+
+ @param oldpath: the current location of the file.
+ @param newpath: the new file name.
+ """
+ return self._sendRequest(FXP_RENAME, NS(oldpath)+NS(newpath))
+
+ def makeDirectory(self, path, attrs):
+ """
+ Make a directory.
+
+ This method returns a Deferred that is called back when it is
+ created.
+
+ @param path: the name of the directory to create as a string.
+
+ @param attrs: a dictionary of attributes to create the directory
+ with. Its meaning is the same as the attrs in the openFile method.
+ """
+ return self._sendRequest(FXP_MKDIR, NS(path)+self._packAttributes(attrs))
+
+ def removeDirectory(self, path):
+ """
+ Remove a directory (non-recursively)
+
+ It is an error to remove a directory that has files or directories in
+ it.
+
+ This method returns a Deferred that is called back when it is removed.
+
+ @param path: the directory to remove.
+ """
+ return self._sendRequest(FXP_RMDIR, NS(path))
+
+ def openDirectory(self, path):
+ """
+ Open a directory for scanning.
+
+ This method returns a Deferred that is called back with an iterable
+ object that has a close() method.
+
+ The close() method is called when the client is finished reading
+ from the directory. At this point, the iterable will no longer
+ be used.
+
+ The iterable returns triples of the form (filename, longname, attrs)
+ or a Deferred that returns the same. The sequence must support
+ __getitem__, but otherwise may be any 'sequence-like' object.
+
+ filename is the name of the file relative to the directory.
+ logname is an expanded format of the filename. The recommended format
+ is:
+ -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer
+ 1234567890 123 12345678 12345678 12345678 123456789012
+
+ The first line is sample output, the second is the length of the field.
+ The fields are: permissions, link count, user owner, group owner,
+ size in bytes, modification time.
+
+ attrs is a dictionary in the format of the attrs argument to openFile.
+
+ @param path: the directory to open.
+ """
+ d = self._sendRequest(FXP_OPENDIR, NS(path))
+ self.wasAFile[d] = (0, path)
+ return d
+
+ def getAttrs(self, path, followLinks=0):
+ """
+ Return the attributes for the given path.
+
+ This method returns a dictionary in the same format as the attrs
+ argument to openFile or a Deferred that is called back with same.
+
+ @param path: the path to return attributes for as a string.
+ @param followLinks: a boolean. if it is True, follow symbolic links
+ and return attributes for the real path at the base. if it is False,
+ return attributes for the specified path.
+ """
+ if followLinks: m = FXP_STAT
+ else: m = FXP_LSTAT
+ return self._sendRequest(m, NS(path))
+
+ def setAttrs(self, path, attrs):
+ """
+ Set the attributes for the path.
+
+ This method returns when the attributes are set or a Deferred that is
+ called back when they are.
+
+ @param path: the path to set attributes for as a string.
+ @param attrs: a dictionary in the same format as the attrs argument to
+ openFile.
+ """
+ data = NS(path) + self._packAttributes(attrs)
+ return self._sendRequest(FXP_SETSTAT, data)
+
+ def readLink(self, path):
+ """
+ Find the root of a set of symbolic links.
+
+ This method returns the target of the link, or a Deferred that
+ returns the same.
+
+ @param path: the path of the symlink to read.
+ """
+ d = self._sendRequest(FXP_READLINK, NS(path))
+ return d.addCallback(self._cbRealPath)
+
+ def makeLink(self, linkPath, targetPath):
+ """
+ Create a symbolic link.
+
+ This method returns when the link is made, or a Deferred that
+ returns the same.
+
+ @param linkPath: the pathname of the symlink as a string
+ @param targetPath: the path of the target of the link as a string.
+ """
+ return self._sendRequest(FXP_SYMLINK, NS(linkPath)+NS(targetPath))
+
+ def realPath(self, path):
+ """
+ Convert any path to an absolute path.
+
+ This method returns the absolute path as a string, or a Deferred
+ that returns the same.
+
+ @param path: the path to convert as a string.
+ """
+ d = self._sendRequest(FXP_REALPATH, NS(path))
+ return d.addCallback(self._cbRealPath)
+
+ def _cbRealPath(self, result):
+ name, longname, attrs = result[0]
+ return name
+
+ def extendedRequest(self, request, data):
+ """
+ Make an extended request of the server.
+
+ The method returns a Deferred that is called back with
+ the result of the extended request.
+
+ @param request: the name of the extended request to make.
+ @param data: any other data that goes along with the request.
+ """
+ return self._sendRequest(FXP_EXTENDED, NS(request) + data)
+
+ def packet_VERSION(self, data):
+ version, = struct.unpack('!L', data[:4])
+ data = data[4:]
+ d = {}
+ while data:
+ k, data = getNS(data)
+ v, data = getNS(data)
+ d[k]=v
+ self.version = version
+ self.gotServerVersion(version, d)
+
+ def packet_STATUS(self, data):
+ d, data = self._parseRequest(data)
+ code, = struct.unpack('!L', data[:4])
+ data = data[4:]
+ msg, data = getNS(data)
+ lang = getNS(data)
+ if code == FX_OK:
+ d.callback((msg, lang))
+ elif code == FX_EOF:
+ d.errback(EOFError(msg))
+ elif code == FX_OP_UNSUPPORTED:
+ d.errback(NotImplementedError(msg))
+ else:
+ d.errback(SFTPError(code, msg, lang))
+
+ def packet_HANDLE(self, data):
+ d, data = self._parseRequest(data)
+ isFile, name = self.wasAFile.pop(d)
+ if isFile:
+ cb = ClientFile(self, getNS(data)[0])
+ else:
+ cb = ClientDirectory(self, getNS(data)[0])
+ cb.name = name
+ d.callback(cb)
+
+ def packet_DATA(self, data):
+ d, data = self._parseRequest(data)
+ d.callback(getNS(data)[0])
+
+ def packet_NAME(self, data):
+ d, data = self._parseRequest(data)
+ count, = struct.unpack('!L', data[:4])
+ data = data[4:]
+ files = []
+ for i in range(count):
+ filename, data = getNS(data)
+ longname, data = getNS(data)
+ attrs, data = self._parseAttributes(data)
+ files.append((filename, longname, attrs))
+ d.callback(files)
+
+ def packet_ATTRS(self, data):
+ d, data = self._parseRequest(data)
+ d.callback(self._parseAttributes(data)[0])
+
+ def packet_EXTENDED_REPLY(self, data):
+ d, data = self._parseRequest(data)
+ d.callback(data)
+
+ def gotServerVersion(self, serverVersion, extData):
+ """
+ Called when the client sends their version info.
+
+ @param otherVersion: an integer representing the version of the SFTP
+ protocol they are claiming.
+ @param extData: a dictionary of extended_name : extended_data items.
+ These items are sent by the client to indicate additional features.
+ """
+
+class ClientFile:
+
+ interface.implements(ISFTPFile)
+
+ def __init__(self, parent, handle):
+ self.parent = parent
+ self.handle = NS(handle)
+
+ def close(self):
+ return self.parent._sendRequest(FXP_CLOSE, self.handle)
+
+ def readChunk(self, offset, length):
+ data = self.handle + struct.pack("!QL", offset, length)
+ return self.parent._sendRequest(FXP_READ, data)
+
+ def writeChunk(self, offset, chunk):
+ data = self.handle + struct.pack("!Q", offset) + NS(chunk)
+ return self.parent._sendRequest(FXP_WRITE, data)
+
+ def getAttrs(self):
+ return self.parent._sendRequest(FXP_FSTAT, self.handle)
+
+ def setAttrs(self, attrs):
+ data = self.handle + self.parent._packAttributes(attrs)
+ return self.parent._sendRequest(FXP_FSTAT, data)
+
+class ClientDirectory:
+
+ def __init__(self, parent, handle):
+ self.parent = parent
+ self.handle = NS(handle)
+ self.filesCache = []
+
+ def read(self):
+ d = self.parent._sendRequest(FXP_READDIR, self.handle)
+ return d
+
+ def close(self):
+ return self.parent._sendRequest(FXP_CLOSE, self.handle)
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ if self.filesCache:
+ return self.filesCache.pop(0)
+ d = self.read()
+ d.addCallback(self._cbReadDir)
+ d.addErrback(self._ebReadDir)
+ return d
+
+ def _cbReadDir(self, names):
+ self.filesCache = names[1:]
+ return names[0]
+
+ def _ebReadDir(self, reason):
+ reason.trap(EOFError)
+ def _():
+ raise StopIteration
+ self.next = _
+ return reason
+
+
+class SFTPError(Exception):
+
+ def __init__(self, errorCode, errorMessage, lang = ''):
+ Exception.__init__(self)
+ self.code = errorCode
+ self._message = errorMessage
+ self.lang = lang
+
+
+ def message(self):
+ """
+ A string received over the network that explains the error to a human.
+ """
+ # Python 2.6 deprecates assigning to the 'message' attribute of an
+ # exception. We define this read-only property here in order to
+ # prevent the warning about deprecation while maintaining backwards
+ # compatibility with object clients that rely on the 'message'
+ # attribute being set correctly. See bug #3897.
+ return self._message
+ message = property(message)
+
+
+ def __str__(self):
+ return 'SFTPError %s: %s' % (self.code, self.message)
+
+FXP_INIT = 1
+FXP_VERSION = 2
+FXP_OPEN = 3
+FXP_CLOSE = 4
+FXP_READ = 5
+FXP_WRITE = 6
+FXP_LSTAT = 7
+FXP_FSTAT = 8
+FXP_SETSTAT = 9
+FXP_FSETSTAT = 10
+FXP_OPENDIR = 11
+FXP_READDIR = 12
+FXP_REMOVE = 13
+FXP_MKDIR = 14
+FXP_RMDIR = 15
+FXP_REALPATH = 16
+FXP_STAT = 17
+FXP_RENAME = 18
+FXP_READLINK = 19
+FXP_SYMLINK = 20
+FXP_STATUS = 101
+FXP_HANDLE = 102
+FXP_DATA = 103
+FXP_NAME = 104
+FXP_ATTRS = 105
+FXP_EXTENDED = 200
+FXP_EXTENDED_REPLY = 201
+
+FILEXFER_ATTR_SIZE = 0x00000001
+FILEXFER_ATTR_UIDGID = 0x00000002
+FILEXFER_ATTR_OWNERGROUP = FILEXFER_ATTR_UIDGID
+FILEXFER_ATTR_PERMISSIONS = 0x00000004
+FILEXFER_ATTR_ACMODTIME = 0x00000008
+FILEXFER_ATTR_EXTENDED = 0x80000000L
+
+FILEXFER_TYPE_REGULAR = 1
+FILEXFER_TYPE_DIRECTORY = 2
+FILEXFER_TYPE_SYMLINK = 3
+FILEXFER_TYPE_SPECIAL = 4
+FILEXFER_TYPE_UNKNOWN = 5
+
+FXF_READ = 0x00000001
+FXF_WRITE = 0x00000002
+FXF_APPEND = 0x00000004
+FXF_CREAT = 0x00000008
+FXF_TRUNC = 0x00000010
+FXF_EXCL = 0x00000020
+FXF_TEXT = 0x00000040
+
+FX_OK = 0
+FX_EOF = 1
+FX_NO_SUCH_FILE = 2
+FX_PERMISSION_DENIED = 3
+FX_FAILURE = 4
+FX_BAD_MESSAGE = 5
+FX_NO_CONNECTION = 6
+FX_CONNECTION_LOST = 7
+FX_OP_UNSUPPORTED = 8
+FX_FILE_ALREADY_EXISTS = 11
+# http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/ defines more
+# useful error codes, but so far OpenSSH doesn't implement them. We use them
+# internally for clarity, but for now define them all as FX_FAILURE to be
+# compatible with existing software.
+FX_NOT_A_DIRECTORY = FX_FAILURE
+FX_FILE_IS_A_DIRECTORY = FX_FAILURE
+
+
+# initialize FileTransferBase.packetTypes:
+g = globals()
+for name in g.keys():
+ if name.startswith('FXP_'):
+ value = g[name]
+ FileTransferBase.packetTypes[value] = name[4:]
+del g, name, value
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/forwarding.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/forwarding.py
new file mode 100755
index 0000000000..717827c79f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/forwarding.py
@@ -0,0 +1,181 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+This module contains the implementation of the TCP forwarding, which allows
+clients and servers to forward arbitrary TCP data across the connection.
+
+Maintainer: Paul Swartz
+"""
+
+import struct
+
+from twisted.internet import protocol, reactor
+from twisted.python import log
+
+import common, channel
+
+class SSHListenForwardingFactory(protocol.Factory):
+ def __init__(self, connection, hostport, klass):
+ self.conn = connection
+ self.hostport = hostport # tuple
+ self.klass = klass
+
+ def buildProtocol(self, addr):
+ channel = self.klass(conn = self.conn)
+ client = SSHForwardingClient(channel)
+ channel.client = client
+ addrTuple = (addr.host, addr.port)
+ channelOpenData = packOpen_direct_tcpip(self.hostport, addrTuple)
+ self.conn.openChannel(channel, channelOpenData)
+ return client
+
+class SSHListenForwardingChannel(channel.SSHChannel):
+
+ def channelOpen(self, specificData):
+ log.msg('opened forwarding channel %s' % self.id)
+ if len(self.client.buf)>1:
+ b = self.client.buf[1:]
+ self.write(b)
+ self.client.buf = ''
+
+ def openFailed(self, reason):
+ self.closed()
+
+ def dataReceived(self, data):
+ self.client.transport.write(data)
+
+ def eofReceived(self):
+ self.client.transport.loseConnection()
+
+ def closed(self):
+ if hasattr(self, 'client'):
+ log.msg('closing local forwarding channel %s' % self.id)
+ self.client.transport.loseConnection()
+ del self.client
+
+class SSHListenClientForwardingChannel(SSHListenForwardingChannel):
+
+ name = 'direct-tcpip'
+
+class SSHListenServerForwardingChannel(SSHListenForwardingChannel):
+
+ name = 'forwarded-tcpip'
+
+class SSHConnectForwardingChannel(channel.SSHChannel):
+
+ def __init__(self, hostport, *args, **kw):
+ channel.SSHChannel.__init__(self, *args, **kw)
+ self.hostport = hostport
+ self.client = None
+ self.clientBuf = ''
+
+ def channelOpen(self, specificData):
+ cc = protocol.ClientCreator(reactor, SSHForwardingClient, self)
+ log.msg("connecting to %s:%i" % self.hostport)
+ cc.connectTCP(*self.hostport).addCallbacks(self._setClient, self._close)
+
+ def _setClient(self, client):
+ self.client = client
+ log.msg("connected to %s:%i" % self.hostport)
+ if self.clientBuf:
+ self.client.transport.write(self.clientBuf)
+ self.clientBuf = None
+ if self.client.buf[1:]:
+ self.write(self.client.buf[1:])
+ self.client.buf = ''
+
+ def _close(self, reason):
+ log.msg("failed to connect: %s" % reason)
+ self.loseConnection()
+
+ def dataReceived(self, data):
+ if self.client:
+ self.client.transport.write(data)
+ else:
+ self.clientBuf += data
+
+ def closed(self):
+ if self.client:
+ log.msg('closed remote forwarding channel %s' % self.id)
+ if self.client.channel:
+ self.loseConnection()
+ self.client.transport.loseConnection()
+ del self.client
+
+def openConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar):
+ remoteHP, origHP = unpackOpen_direct_tcpip(data)
+ return SSHConnectForwardingChannel(remoteHP,
+ remoteWindow=remoteWindow,
+ remoteMaxPacket=remoteMaxPacket,
+ avatar=avatar)
+
+class SSHForwardingClient(protocol.Protocol):
+
+ def __init__(self, channel):
+ self.channel = channel
+ self.buf = '\000'
+
+ def dataReceived(self, data):
+ if self.buf:
+ self.buf += data
+ else:
+ self.channel.write(data)
+
+ def connectionLost(self, reason):
+ if self.channel:
+ self.channel.loseConnection()
+ self.channel = None
+
+
+def packOpen_direct_tcpip((connHost, connPort), (origHost, origPort)):
+ """Pack the data suitable for sending in a CHANNEL_OPEN packet.
+ """
+ conn = common.NS(connHost) + struct.pack('>L', connPort)
+ orig = common.NS(origHost) + struct.pack('>L', origPort)
+ return conn + orig
+
+packOpen_forwarded_tcpip = packOpen_direct_tcpip
+
+def unpackOpen_direct_tcpip(data):
+ """Unpack the data to a usable format.
+ """
+ connHost, rest = common.getNS(data)
+ connPort = int(struct.unpack('>L', rest[:4])[0])
+ origHost, rest = common.getNS(rest[4:])
+ origPort = int(struct.unpack('>L', rest[:4])[0])
+ return (connHost, connPort), (origHost, origPort)
+
+unpackOpen_forwarded_tcpip = unpackOpen_direct_tcpip
+
+def packGlobal_tcpip_forward((host, port)):
+ return common.NS(host) + struct.pack('>L', port)
+
+def unpackGlobal_tcpip_forward(data):
+ host, rest = common.getNS(data)
+ port = int(struct.unpack('>L', rest[:4])[0])
+ return host, port
+
+"""This is how the data -> eof -> close stuff /should/ work.
+
+debug3: channel 1: waiting for connection
+debug1: channel 1: connected
+debug1: channel 1: read<=0 rfd 7 len 0
+debug1: channel 1: read failed
+debug1: channel 1: close_read
+debug1: channel 1: input open -> drain
+debug1: channel 1: ibuf empty
+debug1: channel 1: send eof
+debug1: channel 1: input drain -> closed
+debug1: channel 1: rcvd eof
+debug1: channel 1: output open -> drain
+debug1: channel 1: obuf empty
+debug1: channel 1: close_write
+debug1: channel 1: output drain -> closed
+debug1: channel 1: rcvd close
+debug3: channel 1: will not send data after close
+debug1: channel 1: send close
+debug1: channel 1: is dead
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/keys.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/keys.py
new file mode 100644
index 0000000000..5dc47188d5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/keys.py
@@ -0,0 +1,941 @@
+# -*- test-case-name: twisted.conch.test.test_keys -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Handling of RSA and DSA keys.
+
+Maintainer: U{Paul Swartz}
+"""
+
+# base library imports
+import base64
+import warnings
+import itertools
+
+# external library imports
+from Crypto.Cipher import DES3
+from Crypto.PublicKey import RSA, DSA
+from Crypto import Util
+from pyasn1.type import univ
+from pyasn1.codec.ber import decoder as berDecoder
+from pyasn1.codec.ber import encoder as berEncoder
+
+# twisted
+from twisted.python import randbytes
+from twisted.python.hashlib import md5, sha1
+
+# sibling imports
+from twisted.conch.ssh import common, sexpy
+
+
+class BadKeyError(Exception):
+ """
+ Raised when a key isn't what we expected from it.
+
+ XXX: we really need to check for bad keys
+ """
+
+class EncryptedKeyError(Exception):
+ """
+ Raised when an encrypted key is presented to fromString/fromFile without
+ a password.
+ """
+
+class Key(object):
+ """
+ An object representing a key. A key can be either a public or
+ private key. A public key can verify a signature; a private key can
+ create or verify a signature. To generate a string that can be stored
+ on disk, use the toString method. If you have a private key, but want
+ the string representation of the public key, use Key.public().toString().
+
+ @ivar keyObject: The C{Crypto.PublicKey.pubkey.pubkey} object that
+ operations are performed with.
+ """
+
+ def fromFile(Class, filename, type=None, passphrase=None):
+ """
+ Return a Key object corresponding to the data in filename. type
+ and passphrase function as they do in fromString.
+ """
+ return Class.fromString(file(filename, 'rb').read(), type, passphrase)
+ fromFile = classmethod(fromFile)
+
+ def fromString(Class, data, type=None, passphrase=None):
+ """
+ Return a Key object corresponding to the string data.
+ type is optionally the type of string, matching a _fromString_*
+ method. Otherwise, the _guessStringType() classmethod will be used
+ to guess a type. If the key is encrypted, passphrase is used as
+ the decryption key.
+
+ @type data: C{str}
+ @type type: C{None}/C{str}
+ @type passphrase: C{None}/C{str}
+ @rtype: C{Key}
+ """
+ if type is None:
+ type = Class._guessStringType(data)
+ if type is None:
+ raise BadKeyError('cannot guess the type of %r' % data)
+ method = getattr(Class, '_fromString_%s' % type.upper(), None)
+ if method is None:
+ raise BadKeyError('no _fromString method for %s' % type)
+ if method.func_code.co_argcount == 2: # no passphrase
+ if passphrase:
+ raise BadKeyError('key not encrypted')
+ return method(data)
+ else:
+ return method(data, passphrase)
+ fromString = classmethod(fromString)
+
+ def _fromString_BLOB(Class, blob):
+ """
+ Return a public key object corresponding to this public key blob.
+ The format of a RSA public key blob is::
+ string 'ssh-rsa'
+ integer e
+ integer n
+
+ The format of a DSA public key blob is::
+ string 'ssh-dss'
+ integer p
+ integer q
+ integer g
+ integer y
+
+ @type blob: C{str}
+ @return: a C{Crypto.PublicKey.pubkey.pubkey} object
+ @raises BadKeyError: if the key type (the first string) is unknown.
+ """
+ keyType, rest = common.getNS(blob)
+ if keyType == 'ssh-rsa':
+ e, n, rest = common.getMP(rest, 2)
+ return Class(RSA.construct((n, e)))
+ elif keyType == 'ssh-dss':
+ p, q, g, y, rest = common.getMP(rest, 4)
+ return Class(DSA.construct((y, g, p, q)))
+ else:
+ raise BadKeyError('unknown blob type: %s' % keyType)
+ _fromString_BLOB = classmethod(_fromString_BLOB)
+
+ def _fromString_PRIVATE_BLOB(Class, blob):
+ """
+ Return a private key object corresponding to this private key blob.
+ The blob formats are as follows:
+
+ RSA keys::
+ string 'ssh-rsa'
+ integer n
+ integer e
+ integer d
+ integer u
+ integer p
+ integer q
+
+ DSA keys::
+ string 'ssh-dss'
+ integer p
+ integer q
+ integer g
+ integer y
+ integer x
+
+ @type blob: C{str}
+ @return: a C{Crypto.PublicKey.pubkey.pubkey} object
+ @raises BadKeyError: if the key type (the first string) is unknown.
+ """
+ keyType, rest = common.getNS(blob)
+
+ if keyType == 'ssh-rsa':
+ n, e, d, u, p, q, rest = common.getMP(rest, 6)
+ rsakey = Class(RSA.construct((n, e, d, p, q, u)))
+ return rsakey
+ elif keyType == 'ssh-dss':
+ p, q, g, y, x, rest = common.getMP(rest, 5)
+ dsakey = Class(DSA.construct((y, g, p, q, x)))
+ return dsakey
+ else:
+ raise BadKeyError('unknown blob type: %s' % keyType)
+ _fromString_PRIVATE_BLOB = classmethod(_fromString_PRIVATE_BLOB)
+
+ def _fromString_PUBLIC_OPENSSH(Class, data):
+ """
+ Return a public key object corresponding to this OpenSSH public key
+ string. The format of an OpenSSH public key string is::
+ <key type> <base64-encoded public key blob>
+
+ @type data: C{str}
+ @return: A {Crypto.PublicKey.pubkey.pubkey} object
+ @raises BadKeyError: if the blob type is unknown.
+ """
+ blob = base64.decodestring(data.split()[1])
+ return Class._fromString_BLOB(blob)
+ _fromString_PUBLIC_OPENSSH = classmethod(_fromString_PUBLIC_OPENSSH)
+
+ def _fromString_PRIVATE_OPENSSH(Class, data, passphrase):
+ """
+ Return a private key object corresponding to this OpenSSH private key
+ string. If the key is encrypted, passphrase MUST be provided.
+ Providing a passphrase for an unencrypted key is an error.
+
+ The format of an OpenSSH private key string is::
+ -----BEGIN <key type> PRIVATE KEY-----
+ [Proc-Type: 4,ENCRYPTED
+ DEK-Info: DES-EDE3-CBC,<initialization value>]
+ <base64-encoded ASN.1 structure>
+ ------END <key type> PRIVATE KEY------
+
+ The ASN.1 structure of a RSA key is::
+ (0, n, e, d, p, q)
+
+ The ASN.1 structure of a DSA key is::
+ (0, p, q, g, y, x)
+
+ @type data: C{str}
+ @type passphrase: C{str}
+ @return: a C{Crypto.PublicKey.pubkey.pubkey} object
+ @raises BadKeyError: if
+ * a passphrase is provided for an unencrypted key
+ * a passphrase is not provided for an encrypted key
+ * the ASN.1 encoding is incorrect
+ """
+ lines = [x + '\n' for x in data.split('\n')]
+ kind = lines[0][11:14]
+ if lines[1].startswith('Proc-Type: 4,ENCRYPTED'): # encrypted key
+ ivdata = lines[2].split(',')[1][:-1]
+ iv = ''.join([chr(int(ivdata[i:i + 2], 16)) for i in range(0,
+ len(ivdata), 2)])
+ if not passphrase:
+ raise EncryptedKeyError('encrypted key with no passphrase')
+ ba = md5(passphrase + iv).digest()
+ bb = md5(ba + passphrase + iv).digest()
+ decKey = (ba + bb)[:24]
+ b64Data = base64.decodestring(''.join(lines[3:-1]))
+ keyData = DES3.new(decKey, DES3.MODE_CBC, iv).decrypt(b64Data)
+ removeLen = ord(keyData[-1])
+ keyData = keyData[:-removeLen]
+ else:
+ b64Data = ''.join(lines[1:-1])
+ keyData = base64.decodestring(b64Data)
+ try:
+ decodedKey = berDecoder.decode(keyData)[0]
+ except Exception, e:
+ raise BadKeyError, 'something wrong with decode'
+ if kind == 'RSA':
+ if len(decodedKey) == 2: # alternate RSA key
+ decodedKey = decodedKey[0]
+ if len(decodedKey) < 6:
+ raise BadKeyError('RSA key failed to decode properly')
+ n, e, d, p, q = [long(value) for value in decodedKey[1:6]]
+ if p > q: # make p smaller than q
+ p, q = q, p
+ return Class(RSA.construct((n, e, d, p, q)))
+ elif kind == 'DSA':
+ p, q, g, y, x = [long(value) for value in decodedKey[1: 6]]
+ if len(decodedKey) < 6:
+ raise BadKeyError('DSA key failed to decode properly')
+ return Class(DSA.construct((y, g, p, q, x)))
+ _fromString_PRIVATE_OPENSSH = classmethod(_fromString_PRIVATE_OPENSSH)
+
+ def _fromString_PUBLIC_LSH(Class, data):
+ """
+ Return a public key corresponding to this LSH public key string.
+ The LSH public key string format is::
+ <s-expression: ('public-key', (<key type>, (<name, <value>)+))>
+
+ The names for a RSA (key type 'rsa-pkcs1-sha1') key are: n, e.
+ The names for a DSA (key type 'dsa') key are: y, g, p, q.
+
+ @type data: C{str}
+ @return: a C{Crypto.PublicKey.pubkey.pubkey} object
+ @raises BadKeyError: if the key type is unknown
+ """
+ sexp = sexpy.parse(base64.decodestring(data[1:-1]))
+ assert sexp[0] == 'public-key'
+ kd = {}
+ for name, data in sexp[1][1:]:
+ kd[name] = common.getMP(common.NS(data))[0]
+ if sexp[1][0] == 'dsa':
+ return Class(DSA.construct((kd['y'], kd['g'], kd['p'], kd['q'])))
+ elif sexp[1][0] == 'rsa-pkcs1-sha1':
+ return Class(RSA.construct((kd['n'], kd['e'])))
+ else:
+ raise BadKeyError('unknown lsh key type %s' % sexp[1][0])
+ _fromString_PUBLIC_LSH = classmethod(_fromString_PUBLIC_LSH)
+
+ def _fromString_PRIVATE_LSH(Class, data):
+ """
+ Return a private key corresponding to this LSH private key string.
+ The LSH private key string format is::
+ <s-expression: ('private-key', (<key type>, (<name>, <value>)+))>
+
+ The names for a RSA (key type 'rsa-pkcs1-sha1') key are: n, e, d, p, q.
+ The names for a DSA (key type 'dsa') key are: y, g, p, q, x.
+
+ @type data: C{str}
+ @return: a {Crypto.PublicKey.pubkey.pubkey} object
+ @raises BadKeyError: if the key type is unknown
+ """
+ sexp = sexpy.parse(data)
+ assert sexp[0] == 'private-key'
+ kd = {}
+ for name, data in sexp[1][1:]:
+ kd[name] = common.getMP(common.NS(data))[0]
+ if sexp[1][0] == 'dsa':
+ assert len(kd) == 5, len(kd)
+ return Class(DSA.construct((kd['y'], kd['g'], kd['p'],
+ kd['q'], kd['x'])))
+ elif sexp[1][0] == 'rsa-pkcs1':
+ assert len(kd) == 8, len(kd)
+ if kd['p'] > kd['q']: # make p smaller than q
+ kd['p'], kd['q'] = kd['q'], kd['p']
+ return Class(RSA.construct((kd['n'], kd['e'], kd['d'],
+ kd['p'], kd['q'])))
+ else:
+ raise BadKeyError('unknown lsh key type %s' % sexp[1][0])
+ _fromString_PRIVATE_LSH = classmethod(_fromString_PRIVATE_LSH)
+
+ def _fromString_AGENTV3(Class, data):
+ """
+ Return a private key object corresponsing to the Secure Shell Key
+ Agent v3 format.
+
+ The SSH Key Agent v3 format for a RSA key is::
+ string 'ssh-rsa'
+ integer e
+ integer d
+ integer n
+ integer u
+ integer p
+ integer q
+
+ The SSH Key Agent v3 format for a DSA key is::
+ string 'ssh-dss'
+ integer p
+ integer q
+ integer g
+ integer y
+ integer x
+
+ @type data: C{str}
+ @return: a C{Crypto.PublicKey.pubkey.pubkey} object
+ @raises BadKeyError: if the key type (the first string) is unknown
+ """
+ keyType, data = common.getNS(data)
+ if keyType == 'ssh-dss':
+ p, data = common.getMP(data)
+ q, data = common.getMP(data)
+ g, data = common.getMP(data)
+ y, data = common.getMP(data)
+ x, data = common.getMP(data)
+ return Class(DSA.construct((y,g,p,q,x)))
+ elif keyType == 'ssh-rsa':
+ e, data = common.getMP(data)
+ d, data = common.getMP(data)
+ n, data = common.getMP(data)
+ u, data = common.getMP(data)
+ p, data = common.getMP(data)
+ q, data = common.getMP(data)
+ return Class(RSA.construct((n,e,d,p,q,u)))
+ else:
+ raise BadKeyError("unknown key type %s" % keyType)
+ _fromString_AGENTV3 = classmethod(_fromString_AGENTV3)
+
+ def _guessStringType(Class, data):
+ """
+ Guess the type of key in data. The types map to _fromString_*
+ methods.
+ """
+ if data.startswith('ssh-'):
+ return 'public_openssh'
+ elif data.startswith('-----BEGIN'):
+ return 'private_openssh'
+ elif data.startswith('{'):
+ return 'public_lsh'
+ elif data.startswith('('):
+ return 'private_lsh'
+ elif data.startswith('\x00\x00\x00\x07ssh-'):
+ ignored, rest = common.getNS(data)
+ count = 0
+ while rest:
+ count += 1
+ ignored, rest = common.getMP(rest)
+ if count > 4:
+ return 'agentv3'
+ else:
+ return 'blob'
+ _guessStringType = classmethod(_guessStringType)
+
+ def __init__(self, keyObject):
+ """
+ Initialize a PublicKey with a C{Crypto.PublicKey.pubkey.pubkey}
+ object.
+
+ @type keyObject: C{Crypto.PublicKey.pubkey.pubkey}
+ """
+ self.keyObject = keyObject
+
+ def __eq__(self, other):
+ """
+ Return True if other represents an object with the same key.
+ """
+ if type(self) == type(other):
+ return self.type() == other.type() and self.data() == other.data()
+ else:
+ return NotImplemented
+
+ def __ne__(self, other):
+ """
+ Return True if other represents anything other than this key.
+ """
+ result = self.__eq__(other)
+ if result == NotImplemented:
+ return result
+ return not result
+
+ def __repr__(self):
+ """
+ Return a pretty representation of this object.
+ """
+ lines = ['<%s %s (%s bits)' % (self.type(),
+ self.isPublic() and 'Public Key' or 'Private Key',
+ self.keyObject.size())]
+ for k, v in self.data().items():
+ lines.append('attr %s:' % k)
+ by = common.MP(v)[4:]
+ while by:
+ m = by[:15]
+ by = by[15:]
+ o = ''
+ for c in m:
+ o = o + '%02x:' % ord(c)
+ if len(m) < 15:
+ o = o[:-1]
+ lines.append('\t' + o)
+ lines[-1] = lines[-1] + '>'
+ return '\n'.join(lines)
+
+ def isPublic(self):
+ """
+ Returns True if this Key is a public key.
+ """
+ return not self.keyObject.has_private()
+
+ def public(self):
+ """
+ Returns a version of this key containing only the public key data.
+ If this is a public key, this may or may not be the same object
+ as self.
+ """
+ return Key(self.keyObject.publickey())
+
+
+ def fingerprint(self):
+ """
+ Get the user presentation of the fingerprint of this L{Key}. As
+ described by U{RFC 4716 section
+ 4<http://tools.ietf.org/html/rfc4716#section-4>}::
+
+ The fingerprint of a public key consists of the output of the MD5
+ message-digest algorithm [RFC1321]. The input to the algorithm is
+ the public key data as specified by [RFC4253]. (...) The output
+ of the (MD5) algorithm is presented to the user as a sequence of 16
+ octets printed as hexadecimal with lowercase letters and separated
+ by colons.
+
+ @since: 8.2
+
+ @return: the user presentation of this L{Key}'s fingerprint, as a
+ string.
+
+ @rtype: L{str}
+ """
+ return ':'.join([x.encode('hex') for x in md5(self.blob()).digest()])
+
+
+ def type(self):
+ """
+ Return the type of the object we wrap. Currently this can only be
+ 'RSA' or 'DSA'.
+ """
+ # the class is Crypto.PublicKey.<type>.<stuff we don't care about>
+ klass = str(self.keyObject.__class__)
+ if klass.startswith('Crypto.PublicKey'):
+ type = klass.split('.')[2]
+ else:
+ raise RuntimeError('unknown type of object: %r' % self.keyObject)
+ if type in ('RSA', 'DSA'):
+ return type
+ else:
+ raise RuntimeError('unknown type of key: %s' % type)
+
+ def sshType(self):
+ """
+ Return the type of the object we wrap as defined in the ssh protocol.
+ Currently this can only be 'ssh-rsa' or 'ssh-dss'.
+ """
+ return {'RSA':'ssh-rsa', 'DSA':'ssh-dss'}[self.type()]
+
+ def data(self):
+ """
+ Return the values of the public key as a dictionary.
+
+ @rtype: C{dict}
+ """
+ keyData = {}
+ for name in self.keyObject.keydata:
+ value = getattr(self.keyObject, name, None)
+ if value is not None:
+ keyData[name] = value
+ return keyData
+
+ def blob(self):
+ """
+ Return the public key blob for this key. The blob is the
+ over-the-wire format for public keys:
+
+ RSA keys::
+ string 'ssh-rsa'
+ integer e
+ integer n
+
+ DSA keys::
+ string 'ssh-dss'
+ integer p
+ integer q
+ integer g
+ integer y
+
+ @rtype: C{str}
+ """
+ type = self.type()
+ data = self.data()
+ if type == 'RSA':
+ return (common.NS('ssh-rsa') + common.MP(data['e']) +
+ common.MP(data['n']))
+ elif type == 'DSA':
+ return (common.NS('ssh-dss') + common.MP(data['p']) +
+ common.MP(data['q']) + common.MP(data['g']) +
+ common.MP(data['y']))
+
+ def privateBlob(self):
+ """
+ Return the private key blob for this key. The blob is the
+ over-the-wire format for private keys:
+
+ RSA keys::
+ string 'ssh-rsa'
+ integer n
+ integer e
+ integer d
+ integer u
+ integer p
+ integer q
+
+ DSA keys::
+ string 'ssh-dss'
+ integer p
+ integer q
+ integer g
+ integer y
+ integer x
+ """
+ type = self.type()
+ data = self.data()
+ if type == 'RSA':
+ return (common.NS('ssh-rsa') + common.MP(data['n']) +
+ common.MP(data['e']) + common.MP(data['d']) +
+ common.MP(data['u']) + common.MP(data['p']) +
+ common.MP(data['q']))
+ elif type == 'DSA':
+ return (common.NS('ssh-dss') + common.MP(data['p']) +
+ common.MP(data['q']) + common.MP(data['g']) +
+ common.MP(data['y']) + common.MP(data['x']))
+
+ def toString(self, type, extra=None):
+ """
+ Create a string representation of this key. If the key is a private
+ key and you want the represenation of its public key, use
+ C{key.public().toString()}. type maps to a _toString_* method.
+
+ @param type: The type of string to emit. Currently supported values
+ are C{'OPENSSH'}, C{'LSH'}, and C{'AGENTV3'}.
+ @type type: L{str}
+
+ @param extra: Any extra data supported by the selected format which
+ is not part of the key itself. For public OpenSSH keys, this is
+ a comment. For private OpenSSH keys, this is a passphrase to
+ encrypt with.
+ @type extra: L{str} or L{NoneType}
+
+ @rtype: L{str}
+ """
+ method = getattr(self, '_toString_%s' % type.upper(), None)
+ if method is None:
+ raise BadKeyError('unknown type: %s' % type)
+ if method.func_code.co_argcount == 2:
+ return method(extra)
+ else:
+ return method()
+
+ def _toString_OPENSSH(self, extra):
+ """
+ Return a public or private OpenSSH string. See
+ _fromString_PUBLIC_OPENSSH and _fromString_PRIVATE_OPENSSH for the
+ string formats. If extra is present, it represents a comment for a
+ public key, or a passphrase for a private key.
+
+ @type extra: C{str}
+ @rtype: C{str}
+ """
+ data = self.data()
+ if self.isPublic():
+ b64Data = base64.encodestring(self.blob()).replace('\n', '')
+ if not extra:
+ extra = ''
+ return ('%s %s %s' % (self.sshType(), b64Data, extra)).strip()
+ else:
+ lines = ['-----BEGIN %s PRIVATE KEY-----' % self.type()]
+ if self.type() == 'RSA':
+ p, q = data['p'], data['q']
+ objData = (0, data['n'], data['e'], data['d'], q, p,
+ data['d'] % (q - 1), data['d'] % (p - 1),
+ data['u'])
+ else:
+ objData = (0, data['p'], data['q'], data['g'], data['y'],
+ data['x'])
+ asn1Sequence = univ.Sequence()
+ for index, value in itertools.izip(itertools.count(), objData):
+ asn1Sequence.setComponentByPosition(index, univ.Integer(value))
+ asn1Data = berEncoder.encode(asn1Sequence)
+ if extra:
+ iv = randbytes.secureRandom(8)
+ hexiv = ''.join(['%02X' % ord(x) for x in iv])
+ lines.append('Proc-Type: 4,ENCRYPTED')
+ lines.append('DEK-Info: DES-EDE3-CBC,%s\n' % hexiv)
+ ba = md5(extra + iv).digest()
+ bb = md5(ba + extra + iv).digest()
+ encKey = (ba + bb)[:24]
+ padLen = 8 - (len(asn1Data) % 8)
+ asn1Data += (chr(padLen) * padLen)
+ asn1Data = DES3.new(encKey, DES3.MODE_CBC,
+ iv).encrypt(asn1Data)
+ b64Data = base64.encodestring(asn1Data).replace('\n', '')
+ lines += [b64Data[i:i + 64] for i in range(0, len(b64Data), 64)]
+ lines.append('-----END %s PRIVATE KEY-----' % self.type())
+ return '\n'.join(lines)
+
+ def _toString_LSH(self):
+ """
+ Return a public or private LSH key. See _fromString_PUBLIC_LSH and
+ _fromString_PRIVATE_LSH for the key formats.
+
+ @rtype: C{str}
+ """
+ data = self.data()
+ if self.isPublic():
+ if self.type() == 'RSA':
+ keyData = sexpy.pack([['public-key', ['rsa-pkcs1-sha1',
+ ['n', common.MP(data['n'])[4:]],
+ ['e', common.MP(data['e'])[4:]]]]])
+ elif self.type() == 'DSA':
+ keyData = sexpy.pack([['public-key', ['dsa',
+ ['p', common.MP(data['p'])[4:]],
+ ['q', common.MP(data['q'])[4:]],
+ ['g', common.MP(data['g'])[4:]],
+ ['y', common.MP(data['y'])[4:]]]]])
+ return '{' + base64.encodestring(keyData).replace('\n', '') + '}'
+ else:
+ if self.type() == 'RSA':
+ p, q = data['p'], data['q']
+ return sexpy.pack([['private-key', ['rsa-pkcs1',
+ ['n', common.MP(data['n'])[4:]],
+ ['e', common.MP(data['e'])[4:]],
+ ['d', common.MP(data['d'])[4:]],
+ ['p', common.MP(q)[4:]],
+ ['q', common.MP(p)[4:]],
+ ['a', common.MP(data['d'] % (q - 1))[4:]],
+ ['b', common.MP(data['d'] % (p - 1))[4:]],
+ ['c', common.MP(data['u'])[4:]]]]])
+ elif self.type() == 'DSA':
+ return sexpy.pack([['private-key', ['dsa',
+ ['p', common.MP(data['p'])[4:]],
+ ['q', common.MP(data['q'])[4:]],
+ ['g', common.MP(data['g'])[4:]],
+ ['y', common.MP(data['y'])[4:]],
+ ['x', common.MP(data['x'])[4:]]]]])
+
+ def _toString_AGENTV3(self):
+ """
+ Return a private Secure Shell Agent v3 key. See
+ _fromString_AGENTV3 for the key format.
+
+ @rtype: C{str}
+ """
+ data = self.data()
+ if not self.isPublic():
+ if self.type() == 'RSA':
+ values = (data['e'], data['d'], data['n'], data['u'],
+ data['p'], data['q'])
+ elif self.type() == 'DSA':
+ values = (data['p'], data['q'], data['g'], data['y'],
+ data['x'])
+ return common.NS(self.sshType()) + ''.join(map(common.MP, values))
+
+
+ def sign(self, data):
+ """
+ Returns a signature with this Key.
+
+ @type data: C{str}
+ @rtype: C{str}
+ """
+ if self.type() == 'RSA':
+ digest = pkcs1Digest(data, self.keyObject.size()/8)
+ signature = self.keyObject.sign(digest, '')[0]
+ ret = common.NS(Util.number.long_to_bytes(signature))
+ elif self.type() == 'DSA':
+ digest = sha1(data).digest()
+ randomBytes = randbytes.secureRandom(19)
+ sig = self.keyObject.sign(digest, randomBytes)
+ # SSH insists that the DSS signature blob be two 160-bit integers
+ # concatenated together. The sig[0], [1] numbers from obj.sign
+ # are just numbers, and could be any length from 0 to 160 bits.
+ # Make sure they are padded out to 160 bits (20 bytes each)
+ ret = common.NS(Util.number.long_to_bytes(sig[0], 20) +
+ Util.number.long_to_bytes(sig[1], 20))
+ return common.NS(self.sshType()) + ret
+
+ def verify(self, signature, data):
+ """
+ Returns true if the signature for data is valid for this Key.
+
+ @type signature: C{str}
+ @type data: C{str}
+ @rtype: C{bool}
+ """
+ signatureType, signature = common.getNS(signature)
+ if signatureType != self.sshType():
+ return False
+ if self.type() == 'RSA':
+ numbers = common.getMP(signature)
+ digest = pkcs1Digest(data, self.keyObject.size() / 8)
+ elif self.type() == 'DSA':
+ signature = common.getNS(signature)[0]
+ numbers = [Util.number.bytes_to_long(n) for n in signature[:20],
+ signature[20:]]
+ digest = sha1(data).digest()
+ return self.keyObject.verify(digest, numbers)
+
+def getPublicKeyString(filename=None, line=0, data=''):
+ """
+ Return a public key string suitable for being sent over the wire.
+ Takes a filename or data of a public key. Currently handles OpenSSH
+ and LSH keys.
+
+ This function has been deprecated since Twisted Conch 0.9. Use
+ Key.fromString() instead.
+
+ @type filename: C{str}
+ @type line: C{int}
+ @type data: C{str}
+ @rtype: C{str}
+ """
+ warnings.warn("getPublicKeyString is deprecated since Twisted Conch 0.9."
+ " Use Key.fromString().blob().",
+ DeprecationWarning, stacklevel=2)
+ if filename and data:
+ raise BadKeyError("either filename or data, not both")
+ if filename:
+ lines = open(filename).readlines()
+ data = lines[line]
+ return Key.fromString(data).blob()
+
+def makePublicKeyString(obj, comment='', kind='openssh'):
+ """
+ Return an public key given a C{Crypto.PublicKey.pubkey.pubkey}
+ object.
+ kind is one of ('openssh', 'lsh')
+
+ This function is deprecated since Twisted Conch 0.9. Instead use
+ Key(obj).toString().
+
+ @type obj: C{Crypto.PublicKey.pubkey.pubkey}
+ @type comment: C{str}
+ @type kind: C{str}
+ @rtype: C{str}
+ """
+ warnings.warn("makePublicKeyString is deprecated since Twisted Conch 0.9."
+ " Use Key(obj).public().toString().",
+ DeprecationWarning, stacklevel=2)
+ return Key(obj).public().toString(kind, comment)
+
+def getPublicKeyObject(data):
+ """
+ Return a C{Crypto.PublicKey.pubkey.pubkey} corresponding to the SSHv2
+ public key data. data is in the over-the-wire public key format.
+
+ This function is deprecated since Twisted Conch 0.9. Instead, use
+ Key.fromString().
+
+ @type data: C{str}
+ @rtype: C{Crypto.PublicKey.pubkey.pubkey}
+ """
+ warnings.warn("getPublicKeyObject is deprecated since Twisted Conch 0.9."
+ " Use Key.fromString().",
+ DeprecationWarning, stacklevel=2)
+ return Key.fromString(data).keyObject
+
+def getPrivateKeyObject(filename=None, data='', passphrase=''):
+ """
+ Return a C{Crypto.PublicKey.pubkey.pubkey} object corresponding to the
+ private key file/data. If the private key is encrypted, passphrase B{must}
+ be specified, other wise a L{BadKeyError} will be raised.
+
+ This method is deprecated since Twisted Conch 0.9. Instead, use
+ the fromString or fromFile classmethods of Key.
+
+ @type filename: C{str}
+ @type data: C{str}
+ @type passphrase: C{str}
+ @rtype: C{Crypto.PublicKey.pubkey.pubkey}
+ @raises BadKeyError: if the key is invalid or a passphrase is not specified
+ """
+ warnings.warn("getPrivateKeyObject is deprecated since Twisted Conch 0.9."
+ " Use Key.fromString().",
+ DeprecationWarning, stacklevel=2)
+ if filename and data:
+ raise BadKeyError("either filename or data, not both")
+ if filename:
+ return Key.fromFile(filename, passphrase=passphrase).keyObject
+ else:
+ return Key.fromString(data, passphrase=passphrase).keyObject
+
+def makePrivateKeyString(obj, passphrase=None, kind='openssh'):
+ """
+ Return an OpenSSH-style private key for a
+ C{Crypto.PublicKey.pubkey.pubkey} object. If passphrase is given, encrypt
+ the private key with it.
+ kind is one of ('openssh', 'lsh', 'agentv3')
+
+ This function is deprecated since Twisted Conch 0.9. Instead use
+ Key(obj).toString().
+
+ @type obj: C{Crypto.PublicKey.pubkey.pubkey}
+ @type passphrase: C{str}/C{None}
+ @type kind: C{str}
+ @rtype: C{str}
+ """
+ warnings.warn("makePrivateKeyString is deprecated since Twisted Conch 0.9."
+ " Use Key(obj).toString().",
+ DeprecationWarning, stacklevel=2)
+ return Key(obj).toString(kind, passphrase)
+
+def makePublicKeyBlob(obj):
+ """
+ Make a public key blob from a C{Crypto.PublicKey.pubkey.pubkey}.
+
+ This function is deprecated since Twisted Conch 0.9. Use
+ Key().blob() instead.
+ """
+ warnings.warn("makePublicKeyBlob is deprecated since Twisted Conch 0.9."
+ " Use Key(obj).blob().",
+ DeprecationWarning, stacklevel=2)
+ return Key(obj).blob()
+
+def objectType(obj):
+ """
+ Return the SSH key type corresponding to a C{Crypto.PublicKey.pubkey.pubkey}
+ object.
+
+ @type obj: C{Crypto.PublicKey.pubkey.pubkey}
+ @rtype: C{str}
+ """
+ keyDataMapping = {
+ ('n', 'e', 'd', 'p', 'q'): 'ssh-rsa',
+ ('n', 'e', 'd', 'p', 'q', 'u'): 'ssh-rsa',
+ ('y', 'g', 'p', 'q', 'x'): 'ssh-dss'
+ }
+ try:
+ return keyDataMapping[tuple(obj.keydata)]
+ except (KeyError, AttributeError):
+ raise BadKeyError("invalid key object", obj)
+
+def pkcs1Pad(data, messageLength):
+ """
+ Pad out data to messageLength according to the PKCS#1 standard.
+ @type data: C{str}
+ @type messageLength: C{int}
+ """
+ lenPad = messageLength - 2 - len(data)
+ return '\x01' + ('\xff' * lenPad) + '\x00' + data
+
+def pkcs1Digest(data, messageLength):
+ """
+ Create a message digest using the SHA1 hash algorithm according to the
+ PKCS#1 standard.
+ @type data: C{str}
+ @type messageLength: C{str}
+ """
+ digest = sha1(data).digest()
+ return pkcs1Pad(ID_SHA1+digest, messageLength)
+
+def lenSig(obj):
+ """
+ Return the length of the signature in bytes for a key object.
+
+ @type obj: C{Crypto.PublicKey.pubkey.pubkey}
+ @rtype: C{long}
+ """
+ return obj.size()/8
+
+def signData(obj, data):
+ """
+ Sign the data with the given C{Crypto.PublicKey.pubkey.pubkey} object.
+
+ This method is deprecated since Twisted Conch 0.9. Instead use
+ Key().sign().
+
+ @type obj: C{Crypto.PublicKey.pubkey.pubkey}
+ @type data: C{str}
+ @rtype: C{str}
+ """
+ warnings.warn("signData is deprecated since Twisted Conch 0.9."
+ " Use Key(obj).sign(data).",
+ DeprecationWarning, stacklevel=2)
+ return Key(obj).sign(data)
+
+def verifySignature(obj, sig, data):
+ """
+ Verify that the signature for the data is valid.
+
+ This method is deprecated since Twisted Conch 0.9. Use
+ Key().verify().
+
+ @type obj: C{Crypto.PublicKey.pubkey.pubkey}
+ @type sig: C{str}
+ @type data: C{str}
+ @rtype: C{bool}
+ """
+ warnings.warn("verifySignature is deprecated since Twisted Conch 0.9."
+ " Use Key(obj).verify(signature, data).",
+ DeprecationWarning, stacklevel=2)
+ return Key(obj).verify(sig, data)
+
+def printKey(obj):
+ """
+ Pretty print a C{Crypto.PublicKey.pubkey.pubkey} object.
+
+ This function is deprecated since Twisted Conch 0.9. Use
+ repr(Key()).
+
+ @type obj: C{Crypto.PublicKey.pubkey.pubkey}
+ """
+ warnings.warn("printKey is deprecated since Twisted Conch 0.9."
+ " Use repr(Key(obj)).",
+ DeprecationWarning, stacklevel=2)
+ return repr(Key(obj))[1:-1]
+
+ID_SHA1 = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/service.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/service.py
new file mode 100644
index 0000000000..2b4b160cc6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/service.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+The parent class for all the SSH services. Currently implemented services
+are ssh-userauth and ssh-connection.
+
+Maintainer: Paul Swartz
+"""
+
+
+from twisted.python import log
+
+class SSHService(log.Logger):
+ name = None # this is the ssh name for the service
+ protocolMessages = {} # these map #'s -> protocol names
+ transport = None # gets set later
+
+ def serviceStarted(self):
+ """
+ called when the service is active on the transport.
+ """
+
+ def serviceStopped(self):
+ """
+ called when the service is stopped, either by the connection ending
+ or by another service being started
+ """
+
+ def logPrefix(self):
+ return "SSHService %s on %s" % (self.name,
+ self.transport.transport.logPrefix())
+
+ def packetReceived(self, messageNum, packet):
+ """
+ called when we receive a packet on the transport
+ """
+ #print self.protocolMessages
+ if messageNum in self.protocolMessages:
+ messageType = self.protocolMessages[messageNum]
+ f = getattr(self,'ssh_%s' % messageType[4:],
+ None)
+ if f is not None:
+ return f(packet)
+ log.msg("couldn't handle %r" % messageNum)
+ log.msg(repr(packet))
+ self.transport.sendUnimplemented()
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/session.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/session.py
new file mode 100755
index 0000000000..c50372cfcd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/session.py
@@ -0,0 +1,310 @@
+# -*- test-case-name: twisted.conch.test.test_session -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module contains the implementation of SSHSession, which (by default)
+allows access to a shell and a python interpreter over SSH.
+
+Maintainer: Paul Swartz
+"""
+
+import struct
+import signal
+import sys
+import os
+
+from twisted.internet import protocol
+from twisted.python import log
+from twisted.conch.interfaces import ISession
+from twisted.conch.ssh import common, channel
+
+class SSHSession(channel.SSHChannel):
+
+ name = 'session'
+ def __init__(self, *args, **kw):
+ channel.SSHChannel.__init__(self, *args, **kw)
+ self.buf = ''
+ self.client = None
+ self.session = None
+
+ def request_subsystem(self, data):
+ subsystem, ignored= common.getNS(data)
+ log.msg('asking for subsystem "%s"' % subsystem)
+ client = self.avatar.lookupSubsystem(subsystem, data)
+ if client:
+ pp = SSHSessionProcessProtocol(self)
+ proto = wrapProcessProtocol(pp)
+ client.makeConnection(proto)
+ pp.makeConnection(wrapProtocol(client))
+ self.client = pp
+ return 1
+ else:
+ log.msg('failed to get subsystem')
+ return 0
+
+ def request_shell(self, data):
+ log.msg('getting shell')
+ if not self.session:
+ self.session = ISession(self.avatar)
+ try:
+ pp = SSHSessionProcessProtocol(self)
+ self.session.openShell(pp)
+ except:
+ log.deferr()
+ return 0
+ else:
+ self.client = pp
+ return 1
+
+ def request_exec(self, data):
+ if not self.session:
+ self.session = ISession(self.avatar)
+ f,data = common.getNS(data)
+ log.msg('executing command "%s"' % f)
+ try:
+ pp = SSHSessionProcessProtocol(self)
+ self.session.execCommand(pp, f)
+ except:
+ log.deferr()
+ return 0
+ else:
+ self.client = pp
+ return 1
+
+ def request_pty_req(self, data):
+ if not self.session:
+ self.session = ISession(self.avatar)
+ term, windowSize, modes = parseRequest_pty_req(data)
+ log.msg('pty request: %s %s' % (term, windowSize))
+ try:
+ self.session.getPty(term, windowSize, modes)
+ except:
+ log.err()
+ return 0
+ else:
+ return 1
+
+ def request_window_change(self, data):
+ if not self.session:
+ self.session = ISession(self.avatar)
+ winSize = parseRequest_window_change(data)
+ try:
+ self.session.windowChanged(winSize)
+ except:
+ log.msg('error changing window size')
+ log.err()
+ return 0
+ else:
+ return 1
+
+ def dataReceived(self, data):
+ if not self.client:
+ #self.conn.sendClose(self)
+ self.buf += data
+ return
+ self.client.transport.write(data)
+
+ def extReceived(self, dataType, data):
+ if dataType == connection.EXTENDED_DATA_STDERR:
+ if self.client and hasattr(self.client.transport, 'writeErr'):
+ self.client.transport.writeErr(data)
+ else:
+ log.msg('weird extended data: %s'%dataType)
+
+ def eofReceived(self):
+ if self.session:
+ self.session.eofReceived()
+ elif self.client:
+ self.conn.sendClose(self)
+
+ def closed(self):
+ if self.session:
+ self.session.closed()
+ elif self.client:
+ self.client.transport.loseConnection()
+
+ #def closeReceived(self):
+ # self.loseConnection() # don't know what to do with this
+
+ def loseConnection(self):
+ if self.client:
+ self.client.transport.loseConnection()
+ channel.SSHChannel.loseConnection(self)
+
+class _ProtocolWrapper(protocol.ProcessProtocol):
+ """
+ This class wraps a L{Protocol} instance in a L{ProcessProtocol} instance.
+ """
+ def __init__(self, proto):
+ self.proto = proto
+
+ def connectionMade(self): self.proto.connectionMade()
+
+ def outReceived(self, data): self.proto.dataReceived(data)
+
+ def processEnded(self, reason): self.proto.connectionLost(reason)
+
+class _DummyTransport:
+
+ def __init__(self, proto):
+ self.proto = proto
+
+ def dataReceived(self, data):
+ self.proto.transport.write(data)
+
+ def write(self, data):
+ self.proto.dataReceived(data)
+
+ def writeSequence(self, seq):
+ self.write(''.join(seq))
+
+ def loseConnection(self):
+ self.proto.connectionLost(protocol.connectionDone)
+
+def wrapProcessProtocol(inst):
+ if isinstance(inst, protocol.Protocol):
+ return _ProtocolWrapper(inst)
+ else:
+ return inst
+
+def wrapProtocol(proto):
+ return _DummyTransport(proto)
+
+
+
+# SUPPORTED_SIGNALS is a list of signals that every session channel is supposed
+# to accept. See RFC 4254
+SUPPORTED_SIGNALS = ["ABRT", "ALRM", "FPE", "HUP", "ILL", "INT", "KILL",
+ "PIPE", "QUIT", "SEGV", "TERM", "USR1", "USR2"]
+
+
+
+class SSHSessionProcessProtocol(protocol.ProcessProtocol):
+
+ # once initialized, a dictionary mapping signal values to strings
+ # that follow RFC 4254.
+ _signalValuesToNames = None
+
+ def __init__(self, session):
+ self.session = session
+
+ def connectionMade(self):
+ if self.session.buf:
+ self.transport.write(self.session.buf)
+ self.session.buf = None
+
+ def outReceived(self, data):
+ self.session.write(data)
+
+ def errReceived(self, err):
+ self.session.writeExtended(connection.EXTENDED_DATA_STDERR, err)
+
+ def inConnectionLost(self):
+ self.session.conn.sendEOF(self.session)
+
+ def connectionLost(self, reason = None):
+ self.session.loseConnection()
+
+
+ def _getSignalName(self, signum):
+ """
+ Get a signal name given a signal number.
+ """
+ if self._signalValuesToNames is None:
+ self._signalValuesToNames = {}
+ # make sure that the POSIX ones are the defaults
+ for signame in SUPPORTED_SIGNALS:
+ signame = 'SIG' + signame
+ sigvalue = getattr(signal, signame, None)
+ if sigvalue is not None:
+ self._signalValuesToNames[sigvalue] = signame
+ for k, v in signal.__dict__.items():
+ # Check for platform specific signals, ignoring Python specific
+ # SIG_DFL and SIG_IGN
+ if k.startswith('SIG') and not k.startswith('SIG_'):
+ if v not in self._signalValuesToNames:
+ self._signalValuesToNames[v] = k + '@' + sys.platform
+ return self._signalValuesToNames[signum]
+
+
+ def processEnded(self, reason=None):
+ """
+ When we are told the process ended, try to notify the other side about
+ how the process ended using the exit-signal or exit-status requests.
+ Also, close the channel.
+ """
+ if reason is not None:
+ err = reason.value
+ if err.signal is not None:
+ signame = self._getSignalName(err.signal)
+ if (getattr(os, 'WCOREDUMP', None) is not None and
+ os.WCOREDUMP(err.status)):
+ log.msg('exitSignal: %s (core dumped)' % (signame,))
+ coreDumped = 1
+ else:
+ log.msg('exitSignal: %s' % (signame,))
+ coreDumped = 0
+ self.session.conn.sendRequest(self.session, 'exit-signal',
+ common.NS(signame[3:]) + chr(coreDumped) +
+ common.NS('') + common.NS(''))
+ elif err.exitCode is not None:
+ log.msg('exitCode: %r' % (err.exitCode,))
+ self.session.conn.sendRequest(self.session, 'exit-status',
+ struct.pack('>L', err.exitCode))
+ self.session.loseConnection()
+
+ # transport stuff (we are also a transport!)
+
+ def write(self, data):
+ self.session.write(data)
+
+ def writeSequence(self, seq):
+ self.session.write(''.join(seq))
+
+ def loseConnection(self):
+ self.session.loseConnection()
+
+class SSHSessionClient(protocol.Protocol):
+
+ def dataReceived(self, data):
+ if self.transport:
+ self.transport.write(data)
+
+# methods factored out to make live easier on server writers
+def parseRequest_pty_req(data):
+ """Parse the data from a pty-req request into usable data.
+
+ @returns: a tuple of (terminal type, (rows, cols, xpixel, ypixel), modes)
+ """
+ term, rest = common.getNS(data)
+ cols, rows, xpixel, ypixel = struct.unpack('>4L', rest[: 16])
+ modes, ignored= common.getNS(rest[16:])
+ winSize = (rows, cols, xpixel, ypixel)
+ modes = [(ord(modes[i]), struct.unpack('>L', modes[i+1: i+5])[0]) for i in range(0, len(modes)-1, 5)]
+ return term, winSize, modes
+
+def packRequest_pty_req(term, (rows, cols, xpixel, ypixel), modes):
+ """Pack a pty-req request so that it is suitable for sending.
+
+ NOTE: modes must be packed before being sent here.
+ """
+ termPacked = common.NS(term)
+ winSizePacked = struct.pack('>4L', cols, rows, xpixel, ypixel)
+ modesPacked = common.NS(modes) # depend on the client packing modes
+ return termPacked + winSizePacked + modesPacked
+
+def parseRequest_window_change(data):
+ """Parse the data from a window-change request into usuable data.
+
+ @returns: a tuple of (rows, cols, xpixel, ypixel)
+ """
+ cols, rows, xpixel, ypixel = struct.unpack('>4L', data)
+ return rows, cols, xpixel, ypixel
+
+def packRequest_window_change((rows, cols, xpixel, ypixel)):
+ """Pack a window-change request so that it is suitable for sending.
+ """
+ return struct.pack('>4L', cols, rows, xpixel, ypixel)
+
+import connection
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/sexpy.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/sexpy.py
new file mode 100644
index 0000000000..b7f19d98e8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/sexpy.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+def parse(s):
+ s = s.strip()
+ expr = []
+ while s:
+ if s[0] == '(':
+ newSexp = []
+ if expr:
+ expr[-1].append(newSexp)
+ expr.append(newSexp)
+ s = s[1:]
+ continue
+ if s[0] == ')':
+ aList = expr.pop()
+ s=s[1:]
+ if not expr:
+ assert not s
+ return aList
+ continue
+ i = 0
+ while s[i].isdigit(): i+=1
+ assert i
+ length = int(s[:i])
+ data = s[i+1:i+1+length]
+ expr[-1].append(data)
+ s=s[i+1+length:]
+ assert 0, "this should not happen"
+
+def pack(sexp):
+ s = ""
+ for o in sexp:
+ if type(o) in (type(()), type([])):
+ s+='('
+ s+=pack(o)
+ s+=')'
+ else:
+ s+='%i:%s' % (len(o), o)
+ return s
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/transport.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/transport.py
new file mode 100644
index 0000000000..6d68ae77c3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/transport.py
@@ -0,0 +1,1404 @@
+# -*- test-case-name: twisted.conch.test.test_transport -*-
+#
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+The lowest level SSH protocol. This handles the key negotiation, the
+encryption and the compression. The transport layer is described in
+RFC 4253.
+
+Maintainer: Paul Swartz
+"""
+
+# base library imports
+import struct
+import zlib
+import array
+
+# external library imports
+from Crypto import Util
+from Crypto.Cipher import XOR
+
+# twisted imports
+from twisted.internet import protocol, defer
+from twisted.conch import error
+from twisted.python import log, randbytes
+from twisted.python.hashlib import md5, sha1
+
+# sibling imports
+from twisted.conch.ssh import keys
+from twisted.conch.ssh.common import NS, getNS, MP, getMP, _MPpow, ffs
+
+
+
+class SSHTransportBase(protocol.Protocol):
+ """
+ Protocol supporting basic SSH functionality: sending/receiving packets
+ and message dispatch. To connect to or run a server, you must use
+ SSHClientTransport or SSHServerTransport.
+
+ @ivar protocolVersion: A string representing the version of the SSH
+ protocol we support. Currently defaults to '2.0'.
+
+ @ivar version: A string representing the version of the server or client.
+ Currently defaults to 'Twisted'.
+
+ @ivar comment: An optional string giving more information about the
+ server or client.
+
+ @ivar supportedCiphers: A list of strings representing the encryption
+ algorithms supported, in order from most-preferred to least.
+
+ @ivar supportedMACs: A list of strings representing the message
+ authentication codes (hashes) supported, in order from most-preferred
+ to least. Both this and supportedCiphers can include 'none' to use
+ no encryption or authentication, but that must be done manually,
+
+ @ivar supportedKeyExchanges: A list of strings representing the
+ key exchanges supported, in order from most-preferred to least.
+
+ @ivar supportedPublicKeys: A list of strings representing the
+ public key types supported, in order from most-preferred to least.
+
+ @ivar supportedCompressions: A list of strings representing compression
+ types supported, from most-preferred to least.
+
+ @ivar supportedLanguages: A list of strings representing languages
+ supported, from most-preferred to least.
+
+ @ivar isClient: A boolean indicating whether this is a client or server.
+
+ @ivar gotVersion: A boolean indicating whether we have receieved the
+ version string from the other side.
+
+ @ivar buf: Data we've received but hasn't been parsed into a packet.
+
+ @ivar outgoingPacketSequence: the sequence number of the next packet we
+ will send.
+
+ @ivar incomingPacketSequence: the sequence number of the next packet we
+ are expecting from the other side.
+
+ @ivar outgoingCompression: an object supporting the .compress(str) and
+ .flush() methods, or None if there is no outgoing compression. Used to
+ compress outgoing data.
+
+ @ivar outgoingCompressionType: A string representing the outgoing
+ compression type.
+
+ @ivar incomingCompression: an object supporting the .decompress(str)
+ method, or None if there is no incoming compression. Used to
+ decompress incoming data.
+
+ @ivar incomingCompressionType: A string representing the incoming
+ compression type.
+
+ @ivar ourVersionString: the version string that we sent to the other side.
+ Used in the key exchange.
+
+ @ivar otherVersionString: the version string sent by the other side. Used
+ in the key exchange.
+
+ @ivar ourKexInitPayload: the MSG_KEXINIT payload we sent. Used in the key
+ exchange.
+
+ @ivar otherKexInitPayload: the MSG_KEXINIT payload we received. Used in
+ the key exchange
+
+ @ivar sessionID: a string that is unique to this SSH session. Created as
+ part of the key exchange, sessionID is used to generate the various
+ encryption and authentication keys.
+
+ @ivar service: an SSHService instance, or None. If it's set to an object,
+ it's the currently running service.
+
+ @ivar kexAlg: the agreed-upon key exchange algorithm.
+
+ @ivar keyAlg: the agreed-upon public key type for the key exchange.
+
+ @ivar currentEncryptions: an SSHCiphers instance. It represents the
+ current encryption and authentication options for the transport.
+
+ @ivar nextEncryptions: an SSHCiphers instance. Held here until the
+ MSG_NEWKEYS messages are exchanged, when nextEncryptions is
+ transitioned to currentEncryptions.
+
+ @ivar first: the first bytes of the next packet. In order to avoid
+ decrypting data twice, the first bytes are decrypted and stored until
+ the whole packet is available.
+
+ """
+
+
+ protocolVersion = '2.0'
+ version = 'Twisted'
+ comment = ''
+ ourVersionString = ('SSH-' + protocolVersion + '-' + version + ' '
+ + comment).strip()
+ supportedCiphers = ['aes256-ctr', 'aes256-cbc', 'aes192-ctr', 'aes192-cbc',
+ 'aes128-ctr', 'aes128-cbc', 'cast128-ctr',
+ 'cast128-cbc', 'blowfish-ctr', 'blowfish-cbc',
+ '3des-ctr', '3des-cbc'] # ,'none']
+ supportedMACs = ['hmac-sha1', 'hmac-md5'] # , 'none']
+ # both of the above support 'none', but for security are disabled by
+ # default. to enable them, subclass this class and add it, or do:
+ # SSHTransportBase.supportedCiphers.append('none')
+ supportedKeyExchanges = ['diffie-hellman-group-exchange-sha1',
+ 'diffie-hellman-group1-sha1']
+ supportedPublicKeys = ['ssh-rsa', 'ssh-dss']
+ supportedCompressions = ['none', 'zlib']
+ supportedLanguages = ()
+ isClient = False
+ gotVersion = False
+ buf = ''
+ outgoingPacketSequence = 0
+ incomingPacketSequence = 0
+ outgoingCompression = None
+ incomingCompression = None
+ sessionID = None
+ service = None
+
+
+ def connectionLost(self, reason):
+ if self.service:
+ self.service.serviceStopped()
+ if hasattr(self, 'avatar'):
+ self.logoutFunction()
+ log.msg('connection lost')
+
+
+ def connectionMade(self):
+ """
+ Called when the connection is made to the other side. We sent our
+ version and the MSG_KEXINIT packet.
+ """
+ self.transport.write('%s\r\n' % (self.ourVersionString,))
+ self.currentEncryptions = SSHCiphers('none', 'none', 'none', 'none')
+ self.currentEncryptions.setKeys('', '', '', '', '', '')
+ self.sendKexInit()
+
+
+ def sendKexInit(self):
+ self.ourKexInitPayload = (chr(MSG_KEXINIT) +
+ randbytes.secureRandom(16) +
+ NS(','.join(self.supportedKeyExchanges)) +
+ NS(','.join(self.supportedPublicKeys)) +
+ NS(','.join(self.supportedCiphers)) +
+ NS(','.join(self.supportedCiphers)) +
+ NS(','.join(self.supportedMACs)) +
+ NS(','.join(self.supportedMACs)) +
+ NS(','.join(self.supportedCompressions)) +
+ NS(','.join(self.supportedCompressions)) +
+ NS(','.join(self.supportedLanguages)) +
+ NS(','.join(self.supportedLanguages)) +
+ '\000' + '\000\000\000\000')
+ self.sendPacket(MSG_KEXINIT, self.ourKexInitPayload[1:])
+
+
+ def sendPacket(self, messageType, payload):
+ """
+ Sends a packet. If it's been set up, compress the data, encrypt it,
+ and authenticate it before sending.
+
+ @param messageType: The type of the packet; generally one of the
+ MSG_* values.
+ @type messageType: C{int}
+ @param payload: The payload for the message.
+ @type payload: C{str}
+ """
+ payload = chr(messageType) + payload
+ if self.outgoingCompression:
+ payload = (self.outgoingCompression.compress(payload)
+ + self.outgoingCompression.flush(2))
+ bs = self.currentEncryptions.encBlockSize
+ # 4 for the packet length and 1 for the padding length
+ totalSize = 5 + len(payload)
+ lenPad = bs - (totalSize % bs)
+ if lenPad < 4:
+ lenPad = lenPad + bs
+ packet = (struct.pack('!LB',
+ totalSize + lenPad - 4, lenPad) +
+ payload + randbytes.secureRandom(lenPad))
+ encPacket = (
+ self.currentEncryptions.encrypt(packet) +
+ self.currentEncryptions.makeMAC(
+ self.outgoingPacketSequence, packet))
+ self.transport.write(encPacket)
+ self.outgoingPacketSequence += 1
+
+
+ def getPacket(self):
+ """
+ Try to return a decrypted, authenticated, and decompressed packet
+ out of the buffer. If there is not enough data, return None.
+
+ @rtype: C{str}/C{None}
+ """
+ bs = self.currentEncryptions.decBlockSize
+ ms = self.currentEncryptions.verifyDigestSize
+ if len(self.buf) < bs: return # not enough data
+ if not hasattr(self, 'first'):
+ first = self.currentEncryptions.decrypt(self.buf[:bs])
+ else:
+ first = self.first
+ del self.first
+ packetLen, paddingLen = struct.unpack('!LB', first[:5])
+ if packetLen > 1048576: # 1024 ** 2
+ self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
+ 'bad packet length %s' % packetLen)
+ return
+ if len(self.buf) < packetLen + 4 + ms:
+ self.first = first
+ return # not enough packet
+ if(packetLen + 4) % bs != 0:
+ self.sendDisconnect(
+ DISCONNECT_PROTOCOL_ERROR,
+ 'bad packet mod (%i%%%i == %i)' % (packetLen + 4, bs,
+ (packetLen + 4) % bs))
+ return
+ encData, self.buf = self.buf[:4 + packetLen], self.buf[4 + packetLen:]
+ packet = first + self.currentEncryptions.decrypt(encData[bs:])
+ if len(packet) != 4 + packetLen:
+ self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
+ 'bad decryption')
+ return
+ if ms:
+ macData, self.buf = self.buf[:ms], self.buf[ms:]
+ if not self.currentEncryptions.verify(self.incomingPacketSequence,
+ packet, macData):
+ self.sendDisconnect(DISCONNECT_MAC_ERROR, 'bad MAC')
+ return
+ payload = packet[5:-paddingLen]
+ if self.incomingCompression:
+ try:
+ payload = self.incomingCompression.decompress(payload)
+ except: # bare except, because who knows what kind of errors
+ # decompression can raise
+ log.err()
+ self.sendDisconnect(DISCONNECT_COMPRESSION_ERROR,
+ 'compression error')
+ return
+ self.incomingPacketSequence += 1
+ return payload
+
+
+ def dataReceived(self, data):
+ """
+ First, check for the version string (SSH-2.0-*). After that has been
+ received, this method adds data to the buffer, and pulls out any
+ packets.
+
+ @type data: C{str}
+ """
+ self.buf = self.buf + data
+ if not self.gotVersion:
+ if self.buf.find('\n', self.buf.find('SSH-')) == -1:
+ return
+ lines = self.buf.split('\n')
+ for p in lines:
+ if p.startswith('SSH-'):
+ self.gotVersion = True
+ self.otherVersionString = p.strip()
+ if p.split('-')[1] not in ('1.99', '2.0'): # bad version
+ self.sendDisconnect(
+ DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
+ 'bad version ' + p.split('-')[1])
+ return
+ i = lines.index(p)
+ self.buf = '\n'.join(lines[i + 1:])
+ packet = self.getPacket()
+ while packet:
+ messageNum = ord(packet[0])
+ self.dispatchMessage(messageNum, packet[1:])
+ packet = self.getPacket()
+
+
+ def dispatchMessage(self, messageNum, payload):
+ """
+ Send a received message to the appropriate method.
+
+ @type messageNum: C{int}
+ @type payload: c{str}
+ """
+ if messageNum < 50 and messageNum in messages:
+ messageType = messages[messageNum][4:]
+ f = getattr(self, 'ssh_%s' % messageType, None)
+ if f is not None:
+ f(payload)
+ else:
+ log.msg("couldn't handle %s" % messageType)
+ log.msg(repr(payload))
+ self.sendUnimplemented()
+ elif self.service:
+ log.callWithLogger(self.service, self.service.packetReceived,
+ messageNum, payload)
+ else:
+ log.msg("couldn't handle %s" % messageNum)
+ log.msg(repr(payload))
+ self.sendUnimplemented()
+
+
+ def ssh_KEXINIT(self, packet):
+ """
+ Called when we receive a MSG_KEXINIT message. Payload::
+ bytes[16] cookie
+ string keyExchangeAlgorithms
+ string keyAlgorithms
+ string incomingEncryptions
+ string outgoingEncryptions
+ string incomingAuthentications
+ string outgoingAuthentications
+ string incomingCompressions
+ string outgoingCompressions
+ string incomingLanguages
+ string outgoingLanguages
+ bool firstPacketFollows
+ unit32 0 (reserved)
+
+ Starts setting up the key exchange, keys, encryptions, and
+ authentications. Extended by ssh_KEXINIT in SSHServerTransport and
+ SSHClientTransport.
+ """
+ self.otherKexInitPayload = chr(MSG_KEXINIT) + packet
+ #cookie = packet[: 16] # taking this is useless
+ k = getNS(packet[16:], 10)
+ strings, rest = k[:-1], k[-1]
+ (kexAlgs, keyAlgs, encCS, encSC, macCS, macSC, compCS, compSC, langCS,
+ langSC) = [s.split(',') for s in strings]
+ # these are the server directions
+ outs = [encSC, macSC, compSC]
+ ins = [encCS, macSC, compCS]
+ if self.isClient:
+ outs, ins = ins, outs # switch directions
+ server = (self.supportedKeyExchanges, self.supportedPublicKeys,
+ self.supportedCiphers, self.supportedCiphers,
+ self.supportedMACs, self.supportedMACs,
+ self.supportedCompressions, self.supportedCompressions)
+ client = (kexAlgs, keyAlgs, outs[0], ins[0], outs[1], ins[1],
+ outs[2], ins[2])
+ if self.isClient:
+ server, client = client, server
+ self.kexAlg = ffs(client[0], server[0])
+ self.keyAlg = ffs(client[1], server[1])
+ self.nextEncryptions = SSHCiphers(
+ ffs(client[2], server[2]),
+ ffs(client[3], server[3]),
+ ffs(client[4], server[4]),
+ ffs(client[5], server[5]))
+ self.outgoingCompressionType = ffs(client[6], server[6])
+ self.incomingCompressionType = ffs(client[7], server[7])
+ if None in (self.kexAlg, self.keyAlg, self.outgoingCompressionType,
+ self.incomingCompressionType):
+ self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
+ "couldn't match all kex parts")
+ return
+ if None in self.nextEncryptions.__dict__.values():
+ self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
+ "couldn't match all kex parts")
+ return
+ log.msg('kex alg, key alg: %s %s' % (self.kexAlg, self.keyAlg))
+ log.msg('outgoing: %s %s %s' % (self.nextEncryptions.outCipType,
+ self.nextEncryptions.outMACType,
+ self.outgoingCompressionType))
+ log.msg('incoming: %s %s %s' % (self.nextEncryptions.inCipType,
+ self.nextEncryptions.inMACType,
+ self.incomingCompressionType))
+ return kexAlgs, keyAlgs, rest # for SSHServerTransport to use
+
+
+ def ssh_DISCONNECT(self, packet):
+ """
+ Called when we receive a MSG_DISCONNECT message. Payload::
+ long code
+ string description
+
+ This means that the other side has disconnected. Pass the message up
+ and disconnect ourselves.
+ """
+ reasonCode = struct.unpack('>L', packet[: 4])[0]
+ description, foo = getNS(packet[4:])
+ self.receiveError(reasonCode, description)
+ self.transport.loseConnection()
+
+
+ def ssh_IGNORE(self, packet):
+ """
+ Called when we receieve a MSG_IGNORE message. No payload.
+ This means nothing; we simply return.
+ """
+
+
+ def ssh_UNIMPLEMENTED(self, packet):
+ """
+ Called when we receieve a MSG_UNIMPLEMENTED message. Payload::
+ long packet
+
+ This means that the other side did not implement one of our packets.
+ """
+ seqnum, = struct.unpack('>L', packet)
+ self.receiveUnimplemented(seqnum)
+
+
+ def ssh_DEBUG(self, packet):
+ """
+ Called when we receieve a MSG_DEBUG message. Payload::
+ bool alwaysDisplay
+ string message
+ string language
+
+ This means the other side has passed along some debugging info.
+ """
+ alwaysDisplay = bool(packet[0])
+ message, lang, foo = getNS(packet[1:], 2)
+ self.receiveDebug(alwaysDisplay, message, lang)
+
+
+ def setService(self, service):
+ """
+ Set our service to service and start it running. If we were
+ running a service previously, stop it first.
+
+ @type service: C{SSHService}
+ """
+ log.msg('starting service %s' % service.name)
+ if self.service:
+ self.service.serviceStopped()
+ self.service = service
+ service.transport = self
+ self.service.serviceStarted()
+
+
+ def sendDebug(self, message, alwaysDisplay=False, language=''):
+ """
+ Send a debug message to the other side.
+
+ @param message: the message to send.
+ @type message: C{str}
+ @param alwaysDisplay: if True, tell the other side to always
+ display this message.
+ @type alwaysDisplay: C{bool}
+ @param language: optionally, the language the message is in.
+ @type language: C{str}
+ """
+ self.sendPacket(MSG_DEBUG, chr(alwaysDisplay) + NS(message) +
+ NS(language))
+
+
+ def sendIgnore(self, message):
+ """
+ Send a message that will be ignored by the other side. This is
+ useful to fool attacks based on guessing packet sizes in the
+ encrypted stream.
+
+ @param message: data to send with the message
+ @type message: C{str}
+ """
+ self.sendPacket(MSG_IGNORE, NS(message))
+
+
+ def sendUnimplemented(self):
+ """
+ Send a message to the other side that the last packet was not
+ understood.
+ """
+ seqnum = self.incomingPacketSequence
+ self.sendPacket(MSG_UNIMPLEMENTED, struct.pack('!L', seqnum))
+
+
+ def sendDisconnect(self, reason, desc):
+ """
+ Send a disconnect message to the other side and then disconnect.
+
+ @param reason: the reason for the disconnect. Should be one of the
+ DISCONNECT_* values.
+ @type reason: C{int}
+ @param desc: a descrption of the reason for the disconnection.
+ @type desc: C{str}
+ """
+ self.sendPacket(
+ MSG_DISCONNECT, struct.pack('>L', reason) + NS(desc) + NS(''))
+ log.msg('Disconnecting with error, code %s\nreason: %s' % (reason,
+ desc))
+ self.transport.loseConnection()
+
+
+ def _getKey(self, c, sharedSecret, exchangeHash):
+ """
+ Get one of the keys for authentication/encryption.
+
+ @type c: C{str}
+ @type sharedSecret: C{str}
+ @type exchangeHash: C{str}
+ """
+ k1 = sha1(sharedSecret + exchangeHash + c + self.sessionID)
+ k1 = k1.digest()
+ k2 = sha1(sharedSecret + exchangeHash + k1).digest()
+ return k1 + k2
+
+
+ def _keySetup(self, sharedSecret, exchangeHash):
+ """
+ Set up the keys for the connection and sends MSG_NEWKEYS when
+ finished,
+
+ @param sharedSecret: a secret string agreed upon using a Diffie-
+ Hellman exchange, so it is only shared between
+ the server and the client.
+ @type sharedSecret: C{str}
+ @param exchangeHash: A hash of various data known by both sides.
+ @type exchangeHash: C{str}
+ """
+ if not self.sessionID:
+ self.sessionID = exchangeHash
+ initIVCS = self._getKey('A', sharedSecret, exchangeHash)
+ initIVSC = self._getKey('B', sharedSecret, exchangeHash)
+ encKeyCS = self._getKey('C', sharedSecret, exchangeHash)
+ encKeySC = self._getKey('D', sharedSecret, exchangeHash)
+ integKeyCS = self._getKey('E', sharedSecret, exchangeHash)
+ integKeySC = self._getKey('F', sharedSecret, exchangeHash)
+ outs = [initIVSC, encKeySC, integKeySC]
+ ins = [initIVCS, encKeyCS, integKeyCS]
+ if self.isClient: # reverse for the client
+ log.msg('REVERSE')
+ outs, ins = ins, outs
+ self.nextEncryptions.setKeys(outs[0], outs[1], ins[0], ins[1],
+ outs[2], ins[2])
+ self.sendPacket(MSG_NEWKEYS, '')
+
+
+ def isEncrypted(self, direction="out"):
+ """
+ Return True if the connection is encrypted in the given direction.
+ Direction must be one of ["out", "in", "both"].
+ """
+ if direction == "out":
+ return self.currentEncryptions.outCipType != 'none'
+ elif direction == "in":
+ return self.currentEncryptions.inCipType != 'none'
+ elif direction == "both":
+ return self.isEncrypted("in") and self.isEncrypted("out")
+ else:
+ raise TypeError('direction must be "out", "in", or "both"')
+
+
+ def isVerified(self, direction="out"):
+ """
+ Return True if the connecction is verified/authenticated in the
+ given direction. Direction must be one of ["out", "in", "both"].
+ """
+ if direction == "out":
+ return self.currentEncryptions.outMACType != 'none'
+ elif direction == "in":
+ return self.currentEncryptions.inMACType != 'none'
+ elif direction == "both":
+ return self.isVerified("in")and self.isVerified("out")
+ else:
+ raise TypeError('direction must be "out", "in", or "both"')
+
+
+ def loseConnection(self):
+ """
+ Lose the connection to the other side, sending a
+ DISCONNECT_CONNECTION_LOST message.
+ """
+ self.sendDisconnect(DISCONNECT_CONNECTION_LOST,
+ "user closed connection")
+
+
+ # client methods
+ def receiveError(self, reasonCode, description):
+ """
+ Called when we receive a disconnect error message from the other
+ side.
+
+ @param reasonCode: the reason for the disconnect, one of the
+ DISCONNECT_ values.
+ @type reasonCode: C{int}
+ @param description: a human-readable description of the
+ disconnection.
+ @type description: C{str}
+ """
+ log.msg('Got remote error, code %s\nreason: %s' % (reasonCode,
+ description))
+
+
+ def receiveUnimplemented(self, seqnum):
+ """
+ Called when we receive an unimplemented packet message from the other
+ side.
+
+ @param seqnum: the sequence number that was not understood.
+ @type seqnum: C{int}
+ """
+ log.msg('other side unimplemented packet #%s' % seqnum)
+
+
+ def receiveDebug(self, alwaysDisplay, message, lang):
+ """
+ Called when we receive a debug message from the other side.
+
+ @param alwaysDisplay: if True, this message should always be
+ displayed.
+ @type alwaysDisplay: C{bool}
+ @param message: the debug message
+ @type message: C{str}
+ @param lang: optionally the language the message is in.
+ @type lang: C{str}
+ """
+ if alwaysDisplay:
+ log.msg('Remote Debug Message: %s' % message)
+
+
+
+class SSHServerTransport(SSHTransportBase):
+ """
+ SSHServerTransport implements the server side of the SSH protocol.
+
+ @ivar isClient: since we are never the client, this is always False.
+
+ @ivar ignoreNextPacket: if True, ignore the next key exchange packet. This
+ is set when the client sends a guessed key exchange packet but with
+ an incorrect guess.
+
+ @ivar dhGexRequest: the KEX_DH_GEX_REQUEST(_OLD) that the client sent.
+ The key generation needs this to be stored.
+
+ @ivar g: the Diffie-Hellman group generator.
+
+ @ivar p: the Diffie-Hellman group prime.
+ """
+ isClient = False
+ ignoreNextPacket = 0
+
+
+ def ssh_KEXINIT(self, packet):
+ """
+ Called when we receive a MSG_KEXINIT message. For a description
+ of the packet, see SSHTransportBase.ssh_KEXINIT(). Additionally,
+ this method checks if a guessed key exchange packet was sent. If
+ it was sent, and it guessed incorrectly, the next key exchange
+ packet MUST be ignored.
+ """
+ retval = SSHTransportBase.ssh_KEXINIT(self, packet)
+ if not retval: # disconnected
+ return
+ else:
+ kexAlgs, keyAlgs, rest = retval
+ if ord(rest[0]): # first_kex_packet_follows
+ if (kexAlgs[0] != self.supportedKeyExchanges[0] or
+ keyAlgs[0] != self.supportedPublicKeys[0]):
+ self.ignoreNextPacket = True # guess was wrong
+
+
+ def ssh_KEX_DH_GEX_REQUEST_OLD(self, packet):
+ """
+ This represents two different key exchange methods that share the
+ same integer value.
+
+ KEXDH_INIT (for diffie-hellman-group1-sha1 exchanges) payload::
+
+ integer e (the client's Diffie-Hellman public key)
+
+ We send the KEXDH_REPLY with our host key and signature.
+
+ KEX_DH_GEX_REQUEST_OLD (for diffie-hellman-group-exchange-sha1)
+ payload::
+
+ integer ideal (ideal size for the Diffie-Hellman prime)
+
+ We send the KEX_DH_GEX_GROUP message with the group that is
+ closest in size to ideal.
+
+ If we were told to ignore the next key exchange packet by
+ ssh_KEXINIT, drop it on the floor and return.
+ """
+ if self.ignoreNextPacket:
+ self.ignoreNextPacket = 0
+ return
+ if self.kexAlg == 'diffie-hellman-group1-sha1':
+ # this is really KEXDH_INIT
+ clientDHpublicKey, foo = getMP(packet)
+ y = Util.number.getRandomNumber(512, randbytes.secureRandom)
+ serverDHpublicKey = _MPpow(DH_GENERATOR, y, DH_PRIME)
+ sharedSecret = _MPpow(clientDHpublicKey, y, DH_PRIME)
+ h = sha1()
+ h.update(NS(self.otherVersionString))
+ h.update(NS(self.ourVersionString))
+ h.update(NS(self.otherKexInitPayload))
+ h.update(NS(self.ourKexInitPayload))
+ h.update(NS(self.factory.publicKeys[self.keyAlg].blob()))
+ h.update(MP(clientDHpublicKey))
+ h.update(serverDHpublicKey)
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+ self.sendPacket(
+ MSG_KEXDH_REPLY,
+ NS(self.factory.publicKeys[self.keyAlg].blob()) +
+ serverDHpublicKey +
+ NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash)))
+ self._keySetup(sharedSecret, exchangeHash)
+ elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
+ self.dhGexRequest = packet
+ ideal = struct.unpack('>L', packet)[0]
+ self.g, self.p = self.factory.getDHPrime(ideal)
+ self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g))
+ else:
+ raise error.ConchError('bad kexalg: %s' % self.kexAlg)
+
+
+ def ssh_KEX_DH_GEX_REQUEST(self, packet):
+ """
+ Called when we receive a MSG_KEX_DH_GEX_REQUEST message. Payload::
+ integer minimum
+ integer ideal
+ integer maximum
+
+ The client is asking for a Diffie-Hellman group between minimum and
+ maximum size, and close to ideal if possible. We reply with a
+ MSG_KEX_DH_GEX_GROUP message.
+
+ If we were told to ignore the next key exchange packekt by
+ ssh_KEXINIT, drop it on the floor and return.
+ """
+ if self.ignoreNextPacket:
+ self.ignoreNextPacket = 0
+ return
+ self.dhGexRequest = packet
+ min, ideal, max = struct.unpack('>3L', packet)
+ self.g, self.p = self.factory.getDHPrime(ideal)
+ self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g))
+
+
+ def ssh_KEX_DH_GEX_INIT(self, packet):
+ """
+ Called when we get a MSG_KEX_DH_GEX_INIT message. Payload::
+ integer e (client DH public key)
+
+ We send the MSG_KEX_DH_GEX_REPLY message with our host key and
+ signature.
+ """
+ clientDHpublicKey, foo = getMP(packet)
+ # TODO: we should also look at the value they send to us and reject
+ # insecure values of f (if g==2 and f has a single '1' bit while the
+ # rest are '0's, then they must have used a small y also).
+
+ # TODO: This could be computed when self.p is set up
+ # or do as openssh does and scan f for a single '1' bit instead
+
+ pSize = Util.number.size(self.p)
+ y = Util.number.getRandomNumber(pSize, randbytes.secureRandom)
+
+ serverDHpublicKey = _MPpow(self.g, y, self.p)
+ sharedSecret = _MPpow(clientDHpublicKey, y, self.p)
+ h = sha1()
+ h.update(NS(self.otherVersionString))
+ h.update(NS(self.ourVersionString))
+ h.update(NS(self.otherKexInitPayload))
+ h.update(NS(self.ourKexInitPayload))
+ h.update(NS(self.factory.publicKeys[self.keyAlg].blob()))
+ h.update(self.dhGexRequest)
+ h.update(MP(self.p))
+ h.update(MP(self.g))
+ h.update(MP(clientDHpublicKey))
+ h.update(serverDHpublicKey)
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+ self.sendPacket(
+ MSG_KEX_DH_GEX_REPLY,
+ NS(self.factory.publicKeys[self.keyAlg].blob()) +
+ serverDHpublicKey +
+ NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash)))
+ self._keySetup(sharedSecret, exchangeHash)
+
+
+ def ssh_NEWKEYS(self, packet):
+ """
+ Called when we get a MSG_NEWKEYS message. No payload.
+ When we get this, the keys have been set on both sides, and we
+ start using them to encrypt and authenticate the connection.
+ """
+ log.msg('NEW KEYS')
+ if packet != '':
+ self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
+ "NEWKEYS takes no data")
+ return
+ self.currentEncryptions = self.nextEncryptions
+ if self.outgoingCompressionType == 'zlib':
+ self.outgoingCompression = zlib.compressobj(6)
+ if self.incomingCompressionType == 'zlib':
+ self.incomingCompression = zlib.decompressobj()
+
+
+ def ssh_SERVICE_REQUEST(self, packet):
+ """
+ Called when we get a MSG_SERVICE_REQUEST message. Payload::
+ string serviceName
+
+ The client has requested a service. If we can start the service,
+ start it; otherwise, disconnect with
+ DISCONNECT_SERVICE_NOT_AVAILABLE.
+ """
+ service, rest = getNS(packet)
+ cls = self.factory.getService(self, service)
+ if not cls:
+ self.sendDisconnect(DISCONNECT_SERVICE_NOT_AVAILABLE,
+ "don't have service %s" % service)
+ return
+ else:
+ self.sendPacket(MSG_SERVICE_ACCEPT, NS(service))
+ self.setService(cls())
+
+
+
+class SSHClientTransport(SSHTransportBase):
+ """
+ SSHClientTransport implements the client side of the SSH protocol.
+
+ @ivar isClient: since we are always the client, this is always True.
+
+ @ivar _gotNewKeys: if we receive a MSG_NEWKEYS message before we are
+ ready to transition to the new keys, this is set to True so we
+ can transition when the keys are ready locally.
+
+ @ivar x: our Diffie-Hellman private key.
+
+ @ivar e: our Diffie-Hellman public key.
+
+ @ivar g: the Diffie-Hellman group generator.
+
+ @ivar p: the Diffie-Hellman group prime
+
+ @ivar instance: the SSHService object we are requesting.
+ """
+ isClient = True
+
+
+ def connectionMade(self):
+ """
+ Called when the connection is started with the server. Just sets
+ up a private instance variable.
+ """
+ SSHTransportBase.connectionMade(self)
+ self._gotNewKeys = 0
+
+
+ def ssh_KEXINIT(self, packet):
+ """
+ Called when we receive a MSG_KEXINIT message. For a description
+ of the packet, see SSHTransportBase.ssh_KEXINIT(). Additionally,
+ this method sends the first key exchange packet. If the agreed-upon
+ exchange is diffie-hellman-group1-sha1, generate a public key
+ and send it in a MSG_KEXDH_INIT message. If the exchange is
+ diffie-hellman-group-exchange-sha1, ask for a 2048 bit group with a
+ MSG_KEX_DH_GEX_REQUEST_OLD message.
+ """
+ if SSHTransportBase.ssh_KEXINIT(self, packet) is None:
+ return # we disconnected
+ if self.kexAlg == 'diffie-hellman-group1-sha1':
+ self.x = Util.number.getRandomNumber(512, randbytes.secureRandom)
+ self.e = _MPpow(DH_GENERATOR, self.x, DH_PRIME)
+ self.sendPacket(MSG_KEXDH_INIT, self.e)
+ elif self.kexAlg == 'diffie-hellman-group-exchange-sha1':
+ self.sendPacket(MSG_KEX_DH_GEX_REQUEST_OLD, '\x00\x00\x08\x00')
+ else:
+ raise error.ConchError("somehow, the kexAlg has been set "
+ "to something we don't support")
+
+
+ def ssh_KEX_DH_GEX_GROUP(self, packet):
+ """
+ This handles two different message which share an integer value.
+ If the key exchange is diffie-hellman-group1-sha1, this is
+ MSG_KEXDH_REPLY. Payload::
+ string serverHostKey
+ integer f (server Diffie-Hellman public key)
+ string signature
+
+ We verify the host key by calling verifyHostKey, then continue in
+ _continueKEXDH_REPLY.
+
+ If the key exchange is diffie-hellman-group-exchange-sha1, this is
+ MSG_KEX_DH_GEX_GROUP. Payload::
+ string g (group generator)
+ string p (group prime)
+
+ We generate a Diffie-Hellman public key and send it in a
+ MSG_KEX_DH_GEX_INIT message.
+ """
+ if self.kexAlg == 'diffie-hellman-group1-sha1':
+ # actually MSG_KEXDH_REPLY
+ pubKey, packet = getNS(packet)
+ f, packet = getMP(packet)
+ signature, packet = getNS(packet)
+ fingerprint = ':'.join([ch.encode('hex') for ch in
+ md5(pubKey).digest()])
+ d = self.verifyHostKey(pubKey, fingerprint)
+ d.addCallback(self._continueKEXDH_REPLY, pubKey, f, signature)
+ d.addErrback(
+ lambda unused: self.sendDisconnect(
+ DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
+ return d
+ else:
+ self.p, rest = getMP(packet)
+ self.g, rest = getMP(rest)
+ self.x = Util.number.getRandomNumber(320, randbytes.secureRandom)
+ self.e = _MPpow(self.g, self.x, self.p)
+ self.sendPacket(MSG_KEX_DH_GEX_INIT, self.e)
+
+
+ def _continueKEXDH_REPLY(self, ignored, pubKey, f, signature):
+ """
+ The host key has been verified, so we generate the keys.
+
+ @param pubKey: the public key blob for the server's public key.
+ @type pubKey: C{str}
+ @param f: the server's Diffie-Hellman public key.
+ @type f: C{long}
+ @param signature: the server's signature, verifying that it has the
+ correct private key.
+ @type signature: C{str}
+ """
+ serverKey = keys.Key.fromString(pubKey)
+ sharedSecret = _MPpow(f, self.x, DH_PRIME)
+ h = sha1()
+ h.update(NS(self.ourVersionString))
+ h.update(NS(self.otherVersionString))
+ h.update(NS(self.ourKexInitPayload))
+ h.update(NS(self.otherKexInitPayload))
+ h.update(NS(pubKey))
+ h.update(self.e)
+ h.update(MP(f))
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+ if not serverKey.verify(signature, exchangeHash):
+ self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
+ 'bad signature')
+ return
+ self._keySetup(sharedSecret, exchangeHash)
+
+
+ def ssh_KEX_DH_GEX_REPLY(self, packet):
+ """
+ Called when we receieve a MSG_KEX_DH_GEX_REPLY message. Payload::
+ string server host key
+ integer f (server DH public key)
+
+ We verify the host key by calling verifyHostKey, then continue in
+ _continueGEX_REPLY.
+ """
+ pubKey, packet = getNS(packet)
+ f, packet = getMP(packet)
+ signature, packet = getNS(packet)
+ fingerprint = ':'.join(map(lambda c: '%02x'%ord(c),
+ md5(pubKey).digest()))
+ d = self.verifyHostKey(pubKey, fingerprint)
+ d.addCallback(self._continueGEX_REPLY, pubKey, f, signature)
+ d.addErrback(
+ lambda unused: self.sendDisconnect(
+ DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 'bad host key'))
+ return d
+
+
+ def _continueGEX_REPLY(self, ignored, pubKey, f, signature):
+ """
+ The host key has been verified, so we generate the keys.
+
+ @param pubKey: the public key blob for the server's public key.
+ @type pubKey: C{str}
+ @param f: the server's Diffie-Hellman public key.
+ @type f: C{long}
+ @param signature: the server's signature, verifying that it has the
+ correct private key.
+ @type signature: C{str}
+ """
+ serverKey = keys.Key.fromString(pubKey)
+ sharedSecret = _MPpow(f, self.x, self.p)
+ h = sha1()
+ h.update(NS(self.ourVersionString))
+ h.update(NS(self.otherVersionString))
+ h.update(NS(self.ourKexInitPayload))
+ h.update(NS(self.otherKexInitPayload))
+ h.update(NS(pubKey))
+ h.update('\x00\x00\x08\x00')
+ h.update(MP(self.p))
+ h.update(MP(self.g))
+ h.update(self.e)
+ h.update(MP(f))
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+ if not serverKey.verify(signature, exchangeHash):
+ self.sendDisconnect(DISCONNECT_KEY_EXCHANGE_FAILED,
+ 'bad signature')
+ return
+ self._keySetup(sharedSecret, exchangeHash)
+
+
+ def _keySetup(self, sharedSecret, exchangeHash):
+ """
+ See SSHTransportBase._keySetup().
+ """
+ SSHTransportBase._keySetup(self, sharedSecret, exchangeHash)
+ if self._gotNewKeys:
+ self.ssh_NEWKEYS('')
+
+
+ def ssh_NEWKEYS(self, packet):
+ """
+ Called when we receieve a MSG_NEWKEYS message. No payload.
+ If we've finished setting up our own keys, start using them.
+ Otherwise, remeber that we've receieved this message.
+ """
+ if packet != '':
+ self.sendDisconnect(DISCONNECT_PROTOCOL_ERROR,
+ "NEWKEYS takes no data")
+ return
+ if not self.nextEncryptions.encBlockSize:
+ self._gotNewKeys = 1
+ return
+ log.msg('NEW KEYS')
+ self.currentEncryptions = self.nextEncryptions
+ if self.outgoingCompressionType == 'zlib':
+ self.outgoingCompression = zlib.compressobj(6)
+ if self.incomingCompressionType == 'zlib':
+ self.incomingCompression = zlib.decompressobj()
+ self.connectionSecure()
+
+
+ def ssh_SERVICE_ACCEPT(self, packet):
+ """
+ Called when we receieve a MSG_SERVICE_ACCEPT message. Payload::
+ string service name
+
+ Start the service we requested.
+ """
+ name = getNS(packet)[0]
+ if name != self.instance.name:
+ self.sendDisconnect(
+ DISCONNECT_PROTOCOL_ERROR,
+ "received accept for service we did not request")
+ self.setService(self.instance)
+
+
+ def requestService(self, instance):
+ """
+ Request that a service be run over this transport.
+
+ @type instance: subclass of L{twisted.conch.ssh.service.SSHService}
+ """
+ self.sendPacket(MSG_SERVICE_REQUEST, NS(instance.name))
+ self.instance = instance
+
+
+ # client methods
+ def verifyHostKey(self, hostKey, fingerprint):
+ """
+ Returns a Deferred that gets a callback if it is a valid key, or
+ an errback if not.
+
+ @type hostKey: C{str}
+ @type fingerprint: C{str}
+ @rtype: L{twisted.internet.defer.Deferred}
+ """
+ # return if it's good
+ return defer.fail(NotImplementedError())
+
+
+ def connectionSecure(self):
+ """
+ Called when the encryption has been set up. Generally,
+ requestService() is called to run another service over the transport.
+ """
+ raise NotImplementedError()
+
+
+
+class _DummyCipher:
+ """
+ A cipher for the none encryption method.
+
+ @ivar block_size: the block size of the encryption. In the case of the
+ none cipher, this is 8 bytes.
+ """
+ block_size = 8
+
+
+ def encrypt(self, x):
+ return x
+
+
+ decrypt = encrypt
+
+
+class SSHCiphers:
+ """
+ SSHCiphers represents all the encryption operations that need to occur
+ to encrypt and authenticate the SSH connection.
+
+ @cvar cipherMap: A dictionary mapping SSH encryption names to 3-tuples of
+ (<Crypto.Cipher.* name>, <block size>, <counter mode>)
+ @cvar macMap: A dictionary mapping SSH MAC names to hash modules.
+
+ @ivar outCipType: the string type of the outgoing cipher.
+ @ivar inCipType: the string type of the incoming cipher.
+ @ivar outMACType: the string type of the incoming MAC.
+ @ivar inMACType: the string type of the incoming MAC.
+ @ivar encBlockSize: the block size of the outgoing cipher.
+ @ivar decBlockSize: the block size of the incoming cipher.
+ @ivar verifyDigestSize: the size of the incoming MAC.
+ @ivar outMAC: a tuple of (<hash module>, <inner key>, <outer key>,
+ <digest size>) representing the outgoing MAC.
+ @ivar inMAc: see outMAC, but for the incoming MAC.
+ """
+
+
+ cipherMap = {
+ '3des-cbc':('DES3', 24, 0),
+ 'blowfish-cbc':('Blowfish', 16,0 ),
+ 'aes256-cbc':('AES', 32, 0),
+ 'aes192-cbc':('AES', 24, 0),
+ 'aes128-cbc':('AES', 16, 0),
+ 'cast128-cbc':('CAST', 16, 0),
+ 'aes128-ctr':('AES', 16, 1),
+ 'aes192-ctr':('AES', 24, 1),
+ 'aes256-ctr':('AES', 32, 1),
+ '3des-ctr':('DES3', 24, 1),
+ 'blowfish-ctr':('Blowfish', 16, 1),
+ 'cast128-ctr':('CAST', 16, 1),
+ 'none':(None, 0, 0),
+ }
+ macMap = {
+ 'hmac-sha1': sha1,
+ 'hmac-md5': md5,
+ 'none': None
+ }
+
+
+ def __init__(self, outCip, inCip, outMac, inMac):
+ self.outCipType = outCip
+ self.inCipType = inCip
+ self.outMACType = outMac
+ self.inMACType = inMac
+ self.encBlockSize = 0
+ self.decBlockSize = 0
+ self.verifyDigestSize = 0
+ self.outMAC = (None, '', '', 0)
+ self.inMAC = (None, '', '', 0)
+
+
+ def setKeys(self, outIV, outKey, inIV, inKey, outInteg, inInteg):
+ """
+ Set up the ciphers and hashes using the given keys,
+
+ @param outIV: the outgoing initialization vector
+ @param outKey: the outgoing encryption key
+ @param inIV: the incoming initialization vector
+ @param inKey: the incoming encryption key
+ @param outInteg: the outgoing integrity key
+ @param inInteg: the incoming integrity key.
+ """
+ o = self._getCipher(self.outCipType, outIV, outKey)
+ self.encrypt = o.encrypt
+ self.encBlockSize = o.block_size
+ o = self._getCipher(self.inCipType, inIV, inKey)
+ self.decrypt = o.decrypt
+ self.decBlockSize = o.block_size
+ self.outMAC = self._getMAC(self.outMACType, outInteg)
+ self.inMAC = self._getMAC(self.inMACType, inInteg)
+ if self.inMAC:
+ self.verifyDigestSize = self.inMAC[3]
+
+
+ def _getCipher(self, cip, iv, key):
+ """
+ Creates an initialized cipher object.
+
+ @param cip: the name of the cipher: maps into Crypto.Cipher.*
+ @param iv: the initialzation vector
+ @param key: the encryption key
+ """
+ modName, keySize, counterMode = self.cipherMap[cip]
+ if not modName: # no cipher
+ return _DummyCipher()
+ mod = __import__('Crypto.Cipher.%s'%modName, {}, {}, 'x')
+ if counterMode:
+ return mod.new(key[:keySize], mod.MODE_CTR, iv[:mod.block_size],
+ counter=_Counter(iv, mod.block_size))
+ else:
+ return mod.new(key[:keySize], mod.MODE_CBC, iv[:mod.block_size])
+
+
+ def _getMAC(self, mac, key):
+ """
+ Gets a 4-tuple representing the message authentication code.
+ (<hash module>, <inner hash value>, <outer hash value>,
+ <digest size>)
+
+ @param mac: a key mapping into macMap
+ @type mac: C{str}
+ @param key: the MAC key.
+ @type key: C{str}
+ """
+ mod = self.macMap[mac]
+ if not mod:
+ return (None, '', '', 0)
+ ds = mod().digest_size
+ key = key[:ds] + '\x00' * (64 - ds)
+ i = XOR.new('\x36').encrypt(key)
+ o = XOR.new('\x5c').encrypt(key)
+ return mod, i, o, ds
+
+
+ def encrypt(self, blocks):
+ """
+ Encrypt blocks. Overridden by the encrypt method of a
+ Crypto.Cipher.* object in setKeys().
+
+ @type blocks: C{str}
+ """
+ raise NotImplementedError()
+
+
+ def decrypt(self, blocks):
+ """
+ Decrypt blocks. See encrypt().
+
+ @type blocks: C{str}
+ """
+ raise NotImplementedError()
+
+
+ def makeMAC(self, seqid, data):
+ """
+ Create a message authentication code (MAC) for the given packet using
+ the outgoing MAC values.
+
+ @param seqid: the sequence ID of the outgoing packet
+ @type seqid: C{int}
+ @param data: the data to create a MAC for
+ @type data: C{str}
+ @rtype: C{str}
+ """
+ if not self.outMAC[0]:
+ return ''
+ data = struct.pack('>L', seqid) + data
+ mod, i, o, ds = self.outMAC
+ inner = mod(i + data)
+ outer = mod(o + inner.digest())
+ return outer.digest()
+
+
+ def verify(self, seqid, data, mac):
+ """
+ Verify an incoming MAC using the incoming MAC values. Return True
+ if the MAC is valid.
+
+ @param seqid: the sequence ID of the incoming packet
+ @type seqid: C{int}
+ @param data: the packet data to verify
+ @type data: C{str}
+ @param mac: the MAC sent with the packet
+ @type mac: C{str}
+ @rtype: C{bool}
+ """
+ if not self.inMAC[0]:
+ return mac == ''
+ data = struct.pack('>L', seqid) + data
+ mod, i, o, ds = self.inMAC
+ inner = mod(i + data)
+ outer = mod(o + inner.digest())
+ return mac == outer.digest()
+
+
+
+class _Counter:
+ """
+ Stateful counter which returns results packed in a byte string
+ """
+
+
+ def __init__(self, initialVector, blockSize):
+ """
+ @type initialVector: C{str}
+ @param initialVector: A byte string representing the initial counter
+ value.
+ @type blockSize: C{int}
+ @param blockSize: The length of the output buffer, as well as the
+ number of bytes at the beginning of C{initialVector} to consider.
+ """
+ initialVector = initialVector[:blockSize]
+ self.count = getMP('\xff\xff\xff\xff' + initialVector)[0]
+ self.blockSize = blockSize
+ self.count = Util.number.long_to_bytes(self.count - 1)
+ self.count = '\x00' * (self.blockSize - len(self.count)) + self.count
+ self.count = array.array('c', self.count)
+ self.len = len(self.count) - 1
+
+
+ def __call__(self):
+ """
+ Increment the counter and return the new value.
+ """
+ i = self.len
+ while i > -1:
+ self.count[i] = n = chr((ord(self.count[i]) + 1) % 256)
+ if n == '\x00':
+ i -= 1
+ else:
+ return self.count.tostring()
+
+ self.count = array.array('c', '\x00' * self.blockSize)
+ return self.count.tostring()
+
+
+
+# Diffie-Hellman primes from Oakley Group 2 [RFC 2409]
+DH_PRIME = long('17976931348623159077083915679378745319786029604875601170644'
+'442368419718021615851936894783379586492554150218056548598050364644054819923'
+'910005079287700335581663922955313623907650873575991482257486257500742530207'
+'744771258955095793777842444242661733472762929938766870920560605027081084290'
+'7692932019128194467627007L')
+DH_GENERATOR = 2L
+
+
+
+MSG_DISCONNECT = 1
+MSG_IGNORE = 2
+MSG_UNIMPLEMENTED = 3
+MSG_DEBUG = 4
+MSG_SERVICE_REQUEST = 5
+MSG_SERVICE_ACCEPT = 6
+MSG_KEXINIT = 20
+MSG_NEWKEYS = 21
+MSG_KEXDH_INIT = 30
+MSG_KEXDH_REPLY = 31
+MSG_KEX_DH_GEX_REQUEST_OLD = 30
+MSG_KEX_DH_GEX_REQUEST = 34
+MSG_KEX_DH_GEX_GROUP = 31
+MSG_KEX_DH_GEX_INIT = 32
+MSG_KEX_DH_GEX_REPLY = 33
+
+
+
+DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1
+DISCONNECT_PROTOCOL_ERROR = 2
+DISCONNECT_KEY_EXCHANGE_FAILED = 3
+DISCONNECT_RESERVED = 4
+DISCONNECT_MAC_ERROR = 5
+DISCONNECT_COMPRESSION_ERROR = 6
+DISCONNECT_SERVICE_NOT_AVAILABLE = 7
+DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8
+DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9
+DISCONNECT_CONNECTION_LOST = 10
+DISCONNECT_BY_APPLICATION = 11
+DISCONNECT_TOO_MANY_CONNECTIONS = 12
+DISCONNECT_AUTH_CANCELLED_BY_USER = 13
+DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14
+DISCONNECT_ILLEGAL_USER_NAME = 15
+
+
+
+messages = {}
+for name, value in globals().items():
+ if name.startswith('MSG_'):
+ messages[value] = name
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ssh/userauth.py b/vendor/Twisted-10.0.0/twisted/conch/ssh/userauth.py
new file mode 100644
index 0000000000..ea013bab18
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ssh/userauth.py
@@ -0,0 +1,846 @@
+# -*- test-case-name: twisted.conch.test.test_userauth -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implementation of the ssh-userauth service.
+Currently implemented authentication types are public-key and password.
+
+Maintainer: Paul Swartz
+"""
+
+import struct, warnings
+from twisted.conch import error, interfaces
+from twisted.conch.ssh import keys, transport, service
+from twisted.conch.ssh.common import NS, getNS
+from twisted.cred import credentials
+from twisted.cred.error import UnauthorizedLogin
+from twisted.internet import defer, reactor
+from twisted.python import failure, log, util
+
+
+
+class SSHUserAuthServer(service.SSHService):
+ """
+ A service implementing the server side of the 'ssh-userauth' service. It
+ is used to authenticate the user on the other side as being able to access
+ this server.
+
+ @ivar name: the name of this service: 'ssh-userauth'
+ @type name: C{str}
+ @ivar authenticatedWith: a list of authentication methods that have
+ already been used.
+ @type authenticatedWith: C{list}
+ @ivar loginTimeout: the number of seconds we wait before disconnecting
+ the user for taking too long to authenticate
+ @type loginTimeout: C{int}
+ @ivar attemptsBeforeDisconnect: the number of failed login attempts we
+ allow before disconnecting.
+ @type attemptsBeforeDisconnect: C{int}
+ @ivar loginAttempts: the number of login attempts that have been made
+ @type loginAttempts: C{int}
+ @ivar passwordDelay: the number of seconds to delay when the user gives
+ an incorrect password
+ @type passwordDelay: C{int}
+ @ivar interfaceToMethod: a C{dict} mapping credential interfaces to
+ authentication methods. The server checks to see which of the
+ cred interfaces have checkers and tells the client that those methods
+ are valid for authentication.
+ @type interfaceToMethod: C{dict}
+ @ivar supportedAuthentications: A list of the supported authentication
+ methods.
+ @type supportedAuthentications: C{list} of C{str}
+ @ivar user: the last username the client tried to authenticate with
+ @type user: C{str}
+ @ivar method: the current authentication method
+ @type method: C{str}
+ @ivar nextService: the service the user wants started after authentication
+ has been completed.
+ @type nextService: C{str}
+ @ivar portal: the L{twisted.cred.portal.Portal} we are using for
+ authentication
+ @type portal: L{twisted.cred.portal.Portal}
+ @ivar clock: an object with a callLater method. Stubbed out for testing.
+ """
+
+
+ name = 'ssh-userauth'
+ loginTimeout = 10 * 60 * 60
+ # 10 minutes before we disconnect them
+ attemptsBeforeDisconnect = 20
+ # 20 login attempts before a disconnect
+ passwordDelay = 1 # number of seconds to delay on a failed password
+ clock = reactor
+ interfaceToMethod = {
+ credentials.ISSHPrivateKey : 'publickey',
+ credentials.IUsernamePassword : 'password',
+ credentials.IPluggableAuthenticationModules : 'keyboard-interactive',
+ }
+
+
+ def serviceStarted(self):
+ """
+ Called when the userauth service is started. Set up instance
+ variables, check if we should allow password/keyboard-interactive
+ authentication (only allow if the outgoing connection is encrypted) and
+ set up a login timeout.
+ """
+ self.authenticatedWith = []
+ self.loginAttempts = 0
+ self.user = None
+ self.nextService = None
+ self._pamDeferred = None
+ self.portal = self.transport.factory.portal
+
+ self.supportedAuthentications = []
+ for i in self.portal.listCredentialsInterfaces():
+ if i in self.interfaceToMethod:
+ self.supportedAuthentications.append(self.interfaceToMethod[i])
+
+ if not self.transport.isEncrypted('in'):
+ # don't let us transport password in plaintext
+ if 'password' in self.supportedAuthentications:
+ self.supportedAuthentications.remove('password')
+ if 'keyboard-interactive' in self.supportedAuthentications:
+ self.supportedAuthentications.remove('keyboard-interactive')
+ self._cancelLoginTimeout = self.clock.callLater(
+ self.loginTimeout,
+ self.timeoutAuthentication)
+
+
+ def serviceStopped(self):
+ """
+ Called when the userauth service is stopped. Cancel the login timeout
+ if it's still going.
+ """
+ if self._cancelLoginTimeout:
+ self._cancelLoginTimeout.cancel()
+ self._cancelLoginTimeout = None
+
+
+ def timeoutAuthentication(self):
+ """
+ Called when the user has timed out on authentication. Disconnect
+ with a DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE message.
+ """
+ self._cancelLoginTimeout = None
+ self.transport.sendDisconnect(
+ transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ 'you took too long')
+
+
+ def tryAuth(self, kind, user, data):
+ """
+ Try to authenticate the user with the given method. Dispatches to a
+ auth_* method.
+
+ @param kind: the authentication method to try.
+ @type kind: C{str}
+ @param user: the username the client is authenticating with.
+ @type user: C{str}
+ @param data: authentication specific data sent by the client.
+ @type data: C{str}
+ @return: A Deferred called back if the method succeeded, or erred back
+ if it failed.
+ @rtype: C{defer.Deferred}
+ """
+ log.msg('%s trying auth %s' % (user, kind))
+ if kind not in self.supportedAuthentications:
+ return defer.fail(
+ error.ConchError('unsupported authentication, failing'))
+ kind = kind.replace('-', '_')
+ f = getattr(self,'auth_%s'%kind, None)
+ if f:
+ ret = f(data)
+ if not ret:
+ return defer.fail(
+ error.ConchError('%s return None instead of a Deferred'
+ % kind))
+ else:
+ return ret
+ return defer.fail(error.ConchError('bad auth type: %s' % kind))
+
+
+ def ssh_USERAUTH_REQUEST(self, packet):
+ """
+ The client has requested authentication. Payload::
+ string user
+ string next service
+ string method
+ <authentication specific data>
+
+ @type packet: C{str}
+ """
+ user, nextService, method, rest = getNS(packet, 3)
+ if user != self.user or nextService != self.nextService:
+ self.authenticatedWith = [] # clear auth state
+ self.user = user
+ self.nextService = nextService
+ self.method = method
+ d = self.tryAuth(method, user, rest)
+ if not d:
+ self._ebBadAuth(
+ failure.Failure(error.ConchError('auth returned none')))
+ return
+ d.addCallback(self._cbFinishedAuth)
+ d.addErrback(self._ebMaybeBadAuth)
+ d.addErrback(self._ebBadAuth)
+ return d
+
+
+ def _cbFinishedAuth(self, (interface, avatar, logout)):
+ """
+ The callback when user has successfully been authenticated. For a
+ description of the arguments, see L{twisted.cred.portal.Portal.login}.
+ We start the service requested by the user.
+ """
+ self.transport.avatar = avatar
+ self.transport.logoutFunction = logout
+ service = self.transport.factory.getService(self.transport,
+ self.nextService)
+ if not service:
+ raise error.ConchError('could not get next service: %s'
+ % self.nextService)
+ log.msg('%s authenticated with %s' % (self.user, self.method))
+ self.transport.sendPacket(MSG_USERAUTH_SUCCESS, '')
+ self.transport.setService(service())
+
+
+ def _ebMaybeBadAuth(self, reason):
+ """
+ An intermediate errback. If the reason is
+ error.NotEnoughAuthentication, we send a MSG_USERAUTH_FAILURE, but
+ with the partial success indicator set.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+ reason.trap(error.NotEnoughAuthentication)
+ self.transport.sendPacket(MSG_USERAUTH_FAILURE,
+ NS(','.join(self.supportedAuthentications)) + '\xff')
+
+
+ def _ebBadAuth(self, reason):
+ """
+ The final errback in the authentication chain. If the reason is
+ error.IgnoreAuthentication, we simply return; the authentication
+ method has sent its own response. Otherwise, send a failure message
+ and (if the method is not 'none') increment the number of login
+ attempts.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+ if reason.check(error.IgnoreAuthentication):
+ return
+ if self.method != 'none':
+ log.msg('%s failed auth %s' % (self.user, self.method))
+ if reason.check(UnauthorizedLogin):
+ log.msg('unauthorized login: %s' % reason.getErrorMessage())
+ elif reason.check(error.ConchError):
+ log.msg('reason: %s' % reason.getErrorMessage())
+ else:
+ log.msg(reason.getTraceback())
+ self.loginAttempts += 1
+ if self.loginAttempts > self.attemptsBeforeDisconnect:
+ self.transport.sendDisconnect(
+ transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ 'too many bad auths')
+ return
+ self.transport.sendPacket(
+ MSG_USERAUTH_FAILURE,
+ NS(','.join(self.supportedAuthentications)) + '\x00')
+
+
+ def auth_publickey(self, packet):
+ """
+ Public key authentication. Payload::
+ byte has signature
+ string algorithm name
+ string key blob
+ [string signature] (if has signature is True)
+
+ Create a SSHPublicKey credential and verify it using our portal.
+ """
+ hasSig = ord(packet[0])
+ algName, blob, rest = getNS(packet[1:], 2)
+ pubKey = keys.Key.fromString(blob)
+ signature = hasSig and getNS(rest)[0] or None
+ if hasSig:
+ b = (NS(self.transport.sessionID) + chr(MSG_USERAUTH_REQUEST) +
+ NS(self.user) + NS(self.nextService) + NS('publickey') +
+ chr(hasSig) + NS(pubKey.sshType()) + NS(blob))
+ c = credentials.SSHPrivateKey(self.user, algName, blob, b,
+ signature)
+ return self.portal.login(c, None, interfaces.IConchUser)
+ else:
+ c = credentials.SSHPrivateKey(self.user, algName, blob, None, None)
+ return self.portal.login(c, None,
+ interfaces.IConchUser).addErrback(self._ebCheckKey,
+ packet[1:])
+
+
+ def _ebCheckKey(self, reason, packet):
+ """
+ Called back if the user did not sent a signature. If reason is
+ error.ValidPublicKey then this key is valid for the user to
+ authenticate with. Send MSG_USERAUTH_PK_OK.
+ """
+ reason.trap(error.ValidPublicKey)
+ # if we make it here, it means that the publickey is valid
+ self.transport.sendPacket(MSG_USERAUTH_PK_OK, packet)
+ return failure.Failure(error.IgnoreAuthentication())
+
+
+ def auth_password(self, packet):
+ """
+ Password authentication. Payload::
+ string password
+
+ Make a UsernamePassword credential and verify it with our portal.
+ """
+ password = getNS(packet[1:])[0]
+ c = credentials.UsernamePassword(self.user, password)
+ return self.portal.login(c, None, interfaces.IConchUser).addErrback(
+ self._ebPassword)
+
+
+ def _ebPassword(self, f):
+ """
+ If the password is invalid, wait before sending the failure in order
+ to delay brute-force password guessing.
+ """
+ d = defer.Deferred()
+ self.clock.callLater(self.passwordDelay, d.callback, f)
+ return d
+
+
+ def auth_keyboard_interactive(self, packet):
+ """
+ Keyboard interactive authentication. No payload. We create a
+ PluggableAuthenticationModules credential and authenticate with our
+ portal.
+ """
+ if self._pamDeferred is not None:
+ self.transport.sendDisconnect(
+ transport.DISCONNECT_PROTOCOL_ERROR,
+ "only one keyboard interactive attempt at a time")
+ return defer.fail(error.IgnoreAuthentication())
+ c = credentials.PluggableAuthenticationModules(self.user,
+ self._pamConv)
+ return self.portal.login(c, None, interfaces.IConchUser)
+
+
+ def _pamConv(self, items):
+ """
+ Convert a list of PAM authentication questions into a
+ MSG_USERAUTH_INFO_REQUEST. Returns a Deferred that will be called
+ back when the user has responses to the questions.
+
+ @param items: a list of 2-tuples (message, kind). We only care about
+ kinds 1 (password) and 2 (text).
+ @type items: C{list}
+ @rtype: L{defer.Deferred}
+ """
+ resp = []
+ for message, kind in items:
+ if kind == 1: # password
+ resp.append((message, 0))
+ elif kind == 2: # text
+ resp.append((message, 1))
+ elif kind in (3, 4):
+ return defer.fail(error.ConchError(
+ 'cannot handle PAM 3 or 4 messages'))
+ else:
+ return defer.fail(error.ConchError(
+ 'bad PAM auth kind %i' % kind))
+ packet = NS('') + NS('') + NS('')
+ packet += struct.pack('>L', len(resp))
+ for prompt, echo in resp:
+ packet += NS(prompt)
+ packet += chr(echo)
+ self.transport.sendPacket(MSG_USERAUTH_INFO_REQUEST, packet)
+ self._pamDeferred = defer.Deferred()
+ return self._pamDeferred
+
+
+ def ssh_USERAUTH_INFO_RESPONSE(self, packet):
+ """
+ The user has responded with answers to PAMs authentication questions.
+ Parse the packet into a PAM response and callback self._pamDeferred.
+ Payload::
+ uint32 numer of responses
+ string response 1
+ ...
+ string response n
+ """
+ d, self._pamDeferred = self._pamDeferred, None
+
+ try:
+ resp = []
+ numResps = struct.unpack('>L', packet[:4])[0]
+ packet = packet[4:]
+ while len(resp) < numResps:
+ response, packet = getNS(packet)
+ resp.append((response, 0))
+ if packet:
+ raise error.ConchError("%i bytes of extra data" % len(packet))
+ except:
+ d.errback(failure.Failure())
+ else:
+ d.callback(resp)
+
+
+
+class SSHUserAuthClient(service.SSHService):
+ """
+ A service implementing the client side of 'ssh-userauth'.
+
+ @ivar name: the name of this service: 'ssh-userauth'
+ @type name: C{str}
+ @ivar preferredOrder: a list of authentication methods we support, in
+ order of preference. The client will try authentication methods in
+ this order, making callbacks for information when necessary.
+ @type preferredOrder: C{list}
+ @ivar user: the name of the user to authenticate as
+ @type user: C{str}
+ @ivar instance: the service to start after authentication has finished
+ @type instance: L{service.SSHService}
+ @ivar authenticatedWith: a list of strings of authentication methods we've tried
+ @type authenticatedWith: C{list} of C{str}
+ @ivar triedPublicKeys: a list of public key objects that we've tried to
+ authenticate with
+ @type triedPublicKeys: C{list} of L{Key}
+ @ivar lastPublicKey: the last public key object we've tried to authenticate
+ with
+ @type lastPublicKey: L{Key}
+ """
+
+
+ name = 'ssh-userauth'
+ preferredOrder = ['publickey', 'password', 'keyboard-interactive']
+
+
+ def __init__(self, user, instance):
+ self.user = user
+ self.instance = instance
+
+
+ def serviceStarted(self):
+ self.authenticatedWith = []
+ self.triedPublicKeys = []
+ self.lastPublicKey = None
+ self.askForAuth('none', '')
+
+
+ def askForAuth(self, kind, extraData):
+ """
+ Send a MSG_USERAUTH_REQUEST.
+
+ @param kind: the authentication method to try.
+ @type kind: C{str}
+ @param extraData: method-specific data to go in the packet
+ @type extraData: C{str}
+ """
+ self.lastAuth = kind
+ self.transport.sendPacket(MSG_USERAUTH_REQUEST, NS(self.user) +
+ NS(self.instance.name) + NS(kind) + extraData)
+
+
+ def tryAuth(self, kind):
+ """
+ Dispatch to an authentication method.
+
+ @param kind: the authentication method
+ @type kind: C{str}
+ """
+ kind = kind.replace('-', '_')
+ log.msg('trying to auth with %s' % (kind,))
+ f = getattr(self,'auth_%s' % (kind,), None)
+ if f:
+ return f()
+
+
+ def _ebAuth(self, ignored, *args):
+ """
+ Generic callback for a failed authentication attempt. Respond by
+ asking for the list of accepted methods (the 'none' method)
+ """
+ self.askForAuth('none', '')
+
+
+ def ssh_USERAUTH_SUCCESS(self, packet):
+ """
+ We received a MSG_USERAUTH_SUCCESS. The server has accepted our
+ authentication, so start the next service.
+ """
+ self.transport.setService(self.instance)
+
+
+ def ssh_USERAUTH_FAILURE(self, packet):
+ """
+ We received a MSG_USERAUTH_FAILURE. Payload::
+ string methods
+ byte partial success
+
+ If partial success is C{True}, then the previous method succeeded but is
+ not sufficent for authentication. C{methods} is a comma-separated list
+ of accepted authentication methods.
+
+ We sort the list of methods by their position in C{self.preferredOrder},
+ removing methods that have already succeeded. We then call
+ C{self.tryAuth} with the most preferred method.
+
+ @param packet: the L{MSG_USERAUTH_FAILURE} payload.
+ @type packet: C{str}
+
+ @return: a L{defer.Deferred} that will be callbacked with C{None} as
+ soon as all authentication methods have been tried, or C{None} if no
+ more authentication methods are available.
+ @rtype: C{defer.Deferred} or C{None}
+ """
+ canContinue, partial = getNS(packet)
+ partial = ord(partial)
+ if partial:
+ self.authenticatedWith.append(self.lastAuth)
+
+ def orderByPreference(meth):
+ """
+ Invoked once per authentication method in order to extract a
+ comparison key which is then used for sorting.
+
+ @param meth: the authentication method.
+ @type meth: C{str}
+
+ @return: the comparison key for C{meth}.
+ @rtype: C{int}
+ """
+ if meth in self.preferredOrder:
+ return self.preferredOrder.index(meth)
+ else:
+ # put the element at the end of the list.
+ return len(self.preferredOrder)
+
+ canContinue = util.dsu([meth for meth in canContinue.split(',')
+ if meth not in self.authenticatedWith],
+ orderByPreference)
+
+ log.msg('can continue with: %s' % canContinue)
+ return self._cbUserauthFailure(None, iter(canContinue))
+
+
+ def _cbUserauthFailure(self, result, iterator):
+ if result:
+ return
+ try:
+ method = iterator.next()
+ except StopIteration:
+ self.transport.sendDisconnect(
+ transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ 'no more authentication methods available')
+ else:
+ d = defer.maybeDeferred(self.tryAuth, method)
+ d.addCallback(self._cbUserauthFailure, iterator)
+ return d
+
+
+ def ssh_USERAUTH_PK_OK(self, packet):
+ """
+ This message (number 60) can mean several different messages depending
+ on the current authentication type. We dispatch to individual methods
+ in order to handle this request.
+ """
+ func = getattr(self, 'ssh_USERAUTH_PK_OK_%s' %
+ self.lastAuth.replace('-', '_'), None)
+ if func is not None:
+ return func(packet)
+ else:
+ self.askForAuth('none', '')
+
+
+ def ssh_USERAUTH_PK_OK_publickey(self, packet):
+ """
+ This is MSG_USERAUTH_PK. Our public key is valid, so we create a
+ signature and try to authenticate with it.
+ """
+ publicKey = self.lastPublicKey
+ b = (NS(self.transport.sessionID) + chr(MSG_USERAUTH_REQUEST) +
+ NS(self.user) + NS(self.instance.name) + NS('publickey') +
+ '\xff' + NS(publicKey.sshType()) + NS(publicKey.blob()))
+ d = self.signData(publicKey, b)
+ if not d:
+ self.askForAuth('none', '')
+ # this will fail, we'll move on
+ return
+ d.addCallback(self._cbSignedData)
+ d.addErrback(self._ebAuth)
+
+
+ def ssh_USERAUTH_PK_OK_password(self, packet):
+ """
+ This is MSG_USERAUTH_PASSWD_CHANGEREQ. The password given has expired.
+ We ask for an old password and a new password, then send both back to
+ the server.
+ """
+ prompt, language, rest = getNS(packet, 2)
+ self._oldPass = self._newPass = None
+ d = self.getPassword('Old Password: ')
+ d = d.addCallbacks(self._setOldPass, self._ebAuth)
+ d.addCallback(lambda ignored: self.getPassword(prompt))
+ d.addCallbacks(self._setNewPass, self._ebAuth)
+
+
+ def ssh_USERAUTH_PK_OK_keyboard_interactive(self, packet):
+ """
+ This is MSG_USERAUTH_INFO_RESPONSE. The server has sent us the
+ questions it wants us to answer, so we ask the user and sent the
+ responses.
+ """
+ name, instruction, lang, data = getNS(packet, 3)
+ numPrompts = struct.unpack('!L', data[:4])[0]
+ data = data[4:]
+ prompts = []
+ for i in range(numPrompts):
+ prompt, data = getNS(data)
+ echo = bool(ord(data[0]))
+ data = data[1:]
+ prompts.append((prompt, echo))
+ d = self.getGenericAnswers(name, instruction, prompts)
+ d.addCallback(self._cbGenericAnswers)
+ d.addErrback(self._ebAuth)
+
+
+ def _cbSignedData(self, signedData):
+ """
+ Called back out of self.signData with the signed data. Send the
+ authentication request with the signature.
+
+ @param signedData: the data signed by the user's private key.
+ @type signedData: C{str}
+ """
+ publicKey = self.lastPublicKey
+ self.askForAuth('publickey', '\xff' + NS(publicKey.sshType()) +
+ NS(publicKey.blob()) + NS(signedData))
+
+
+ def _setOldPass(self, op):
+ """
+ Called back when we are choosing a new password. Simply store the old
+ password for now.
+
+ @param op: the old password as entered by the user
+ @type op: C{str}
+ """
+ self._oldPass = op
+
+
+ def _setNewPass(self, np):
+ """
+ Called back when we are choosing a new password. Get the old password
+ and send the authentication message with both.
+
+ @param np: the new password as entered by the user
+ @type np: C{str}
+ """
+ op = self._oldPass
+ self._oldPass = None
+ self.askForAuth('password', '\xff' + NS(op) + NS(np))
+
+
+ def _cbGenericAnswers(self, responses):
+ """
+ Called back when we are finished answering keyboard-interactive
+ questions. Send the info back to the server in a
+ MSG_USERAUTH_INFO_RESPONSE.
+
+ @param responses: a list of C{str} responses
+ @type responses: C{list}
+ """
+ data = struct.pack('!L', len(responses))
+ for r in responses:
+ data += NS(r.encode('UTF8'))
+ self.transport.sendPacket(MSG_USERAUTH_INFO_RESPONSE, data)
+
+
+ def auth_publickey(self):
+ """
+ Try to authenticate with a public key. Ask the user for a public key;
+ if the user has one, send the request to the server and return True.
+ Otherwise, return False.
+
+ @rtype: C{bool}
+ """
+ d = defer.maybeDeferred(self.getPublicKey)
+ d.addBoth(self._cbGetPublicKey)
+ return d
+
+
+ def _cbGetPublicKey(self, publicKey):
+ if isinstance(publicKey, str):
+ warnings.warn("Returning a string from "
+ "SSHUserAuthClient.getPublicKey() is deprecated "
+ "since Twisted 9.0. Return a keys.Key() instead.",
+ DeprecationWarning)
+ publicKey = keys.Key.fromString(publicKey)
+ if not isinstance(publicKey, keys.Key): # failure or None
+ publicKey = None
+ if publicKey is not None:
+ self.lastPublicKey = publicKey
+ self.triedPublicKeys.append(publicKey)
+ log.msg('using key of type %s' % publicKey.type())
+ self.askForAuth('publickey', '\x00' + NS(publicKey.sshType()) +
+ NS(publicKey.blob()))
+ return True
+ else:
+ return False
+
+
+ def auth_password(self):
+ """
+ Try to authenticate with a password. Ask the user for a password.
+ If the user will return a password, return True. Otherwise, return
+ False.
+
+ @rtype: C{bool}
+ """
+ d = self.getPassword()
+ if d:
+ d.addCallbacks(self._cbPassword, self._ebAuth)
+ return True
+ else: # returned None, don't do password auth
+ return False
+
+
+ def auth_keyboard_interactive(self):
+ """
+ Try to authenticate with keyboard-interactive authentication. Send
+ the request to the server and return True.
+
+ @rtype: C{bool}
+ """
+ log.msg('authing with keyboard-interactive')
+ self.askForAuth('keyboard-interactive', NS('') + NS(''))
+ return True
+
+
+ def _cbPassword(self, password):
+ """
+ Called back when the user gives a password. Send the request to the
+ server.
+
+ @param password: the password the user entered
+ @type password: C{str}
+ """
+ self.askForAuth('password', '\x00' + NS(password))
+
+
+ def signData(self, publicKey, signData):
+ """
+ Sign the given data with the given public key.
+
+ By default, this will call getPrivateKey to get the private key,
+ then sign the data using Key.sign().
+
+ This method is factored out so that it can be overridden to use
+ alternate methods, such as a key agent.
+
+ @param publicKey: The public key object returned from L{getPublicKey}
+ @type publicKey: L{keys.Key}
+
+ @param signData: the data to be signed by the private key.
+ @type signData: C{str}
+ @return: a Deferred that's called back with the signature
+ @rtype: L{defer.Deferred}
+ """
+ key = self.getPrivateKey()
+ if not key:
+ return
+ return key.addCallback(self._cbSignData, signData)
+
+
+ def _cbSignData(self, privateKey, signData):
+ """
+ Called back when the private key is returned. Sign the data and
+ return the signature.
+
+ @param privateKey: the private key object
+ @type publicKey: L{keys.Key}
+ @param signData: the data to be signed by the private key.
+ @type signData: C{str}
+ @return: the signature
+ @rtype: C{str}
+ """
+ if not isinstance(privateKey, keys.Key):
+ warnings.warn("Returning a PyCrypto key object from "
+ "SSHUserAuthClient.getPrivateKey() is deprecated "
+ "since Twisted 9.0. Return a keys.Key() instead.",
+ DeprecationWarning)
+ privateKey = keys.Key(privateKey)
+ return privateKey.sign(signData)
+
+
+ def getPublicKey(self):
+ """
+ Return a public key for the user. If no more public keys are
+ available, return C{None}.
+
+ This implementation always returns C{None}. Override it in a
+ subclass to actually find and return a public key object.
+
+ @rtype: L{Key} or L{NoneType}
+ """
+ return None
+
+
+ def getPrivateKey(self):
+ """
+ Return a L{Deferred} that will be called back with the private key
+ object corresponding to the last public key from getPublicKey().
+ If the private key is not available, errback on the Deferred.
+
+ @rtype: L{Deferred} called back with L{Key}
+ """
+ return defer.fail(NotImplementedError())
+
+
+ def getPassword(self, prompt = None):
+ """
+ Return a L{Deferred} that will be called back with a password.
+ prompt is a string to display for the password, or None for a generic
+ 'user@hostname's password: '.
+
+ @type prompt: C{str}/C{None}
+ @rtype: L{defer.Deferred}
+ """
+ return defer.fail(NotImplementedError())
+
+
+ def getGenericAnswers(self, name, instruction, prompts):
+ """
+ Returns a L{Deferred} with the responses to the promopts.
+
+ @param name: The name of the authentication currently in progress.
+ @param instruction: Describes what the authentication wants.
+ @param prompts: A list of (prompt, echo) pairs, where prompt is a
+ string to display and echo is a boolean indicating whether the
+ user's response should be echoed as they type it.
+ """
+ return defer.fail(NotImplementedError())
+
+
+MSG_USERAUTH_REQUEST = 50
+MSG_USERAUTH_FAILURE = 51
+MSG_USERAUTH_SUCCESS = 52
+MSG_USERAUTH_BANNER = 53
+MSG_USERAUTH_PASSWD_CHANGEREQ = 60
+MSG_USERAUTH_INFO_REQUEST = 60
+MSG_USERAUTH_INFO_RESPONSE = 61
+MSG_USERAUTH_PK_OK = 60
+
+messages = {}
+for k, v in locals().items():
+ if k[:4]=='MSG_':
+ messages[v] = k # doesn't handle doubles
+
+SSHUserAuthServer.protocolMessages = messages
+SSHUserAuthClient.protocolMessages = messages
+del messages
+del v
diff --git a/vendor/Twisted-10.0.0/twisted/conch/stdio.py b/vendor/Twisted-10.0.0/twisted/conch/stdio.py
new file mode 100644
index 0000000000..28fa7e9936
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/stdio.py
@@ -0,0 +1,95 @@
+# -*- test-case-name: twisted.conch.test.test_manhole -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Asynchronous local terminal input handling
+
+@author: Jp Calderone
+"""
+
+import os, tty, sys, termios
+
+from twisted.internet import reactor, stdio, protocol, defer
+from twisted.python import failure, reflect, log
+
+from twisted.conch.insults.insults import ServerProtocol
+from twisted.conch.manhole import ColoredManhole
+
+class UnexpectedOutputError(Exception):
+ pass
+
+class TerminalProcessProtocol(protocol.ProcessProtocol):
+ def __init__(self, proto):
+ self.proto = proto
+ self.onConnection = defer.Deferred()
+
+ def connectionMade(self):
+ self.proto.makeConnection(self)
+ self.onConnection.callback(None)
+ self.onConnection = None
+
+ def write(self, bytes):
+ self.transport.write(bytes)
+
+ def outReceived(self, bytes):
+ self.proto.dataReceived(bytes)
+
+ def errReceived(self, bytes):
+ self.transport.loseConnection()
+ if self.proto is not None:
+ self.proto.connectionLost(failure.Failure(UnexpectedOutputError(bytes)))
+ self.proto = None
+
+ def childConnectionLost(self, childFD):
+ if self.proto is not None:
+ self.proto.childConnectionLost(childFD)
+
+ def processEnded(self, reason):
+ if self.proto is not None:
+ self.proto.connectionLost(reason)
+ self.proto = None
+
+
+
+class ConsoleManhole(ColoredManhole):
+ """
+ A manhole protocol specifically for use with L{stdio.StandardIO}.
+ """
+ def connectionLost(self, reason):
+ """
+ When the connection is lost, there is nothing more to do. Stop the
+ reactor so that the process can exit.
+ """
+ reactor.stop()
+
+
+
+def runWithProtocol(klass):
+ fd = sys.__stdin__.fileno()
+ oldSettings = termios.tcgetattr(fd)
+ tty.setraw(fd)
+ try:
+ p = ServerProtocol(klass)
+ stdio.StandardIO(p)
+ reactor.run()
+ finally:
+ termios.tcsetattr(fd, termios.TCSANOW, oldSettings)
+ os.write(fd, "\r\x1bc\r")
+
+
+
+def main(argv=None):
+ log.startLogging(file('child.log', 'w'))
+
+ if argv is None:
+ argv = sys.argv[1:]
+ if argv:
+ klass = reflect.namedClass(argv[0])
+ else:
+ klass = ConsoleManhole
+ runWithProtocol(klass)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/twisted/conch/tap.py b/vendor/Twisted-10.0.0/twisted/conch/tap.py
new file mode 100644
index 0000000000..1f739564ce
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/tap.py
@@ -0,0 +1,48 @@
+# -*- test-case-name: twisted.conch.test.test_tap -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Support module for making SSH servers with twistd.
+"""
+
+from twisted.conch import checkers, unix
+from twisted.conch.openssh_compat import factory
+from twisted.cred import portal
+from twisted.python import usage
+from twisted.application import strports
+try:
+ from twisted.cred import pamauth
+except ImportError:
+ pamauth = None
+
+
+
+class Options(usage.Options):
+ synopsis = "[-i <interface>] [-p <port>] [-d <dir>] "
+ longdesc = "Makes a Conch SSH server."
+ optParameters = [
+ ["interface", "i", "", "local interface to which we listen"],
+ ["port", "p", "22", "Port on which to listen"],
+ ["data", "d", "/etc", "directory to look for host keys in"],
+ ["moduli", "", None, "directory to look for moduli in "
+ "(if different from --data)"]
+ ]
+ zsh_actions = {"data" : "_dirs", "moduli" : "_dirs"}
+
+
+def makeService(config):
+ t = factory.OpenSSHFactory()
+ t.portal = portal.Portal(unix.UnixSSHRealm())
+ t.portal.registerChecker(checkers.UNIXPasswordDatabase())
+ t.portal.registerChecker(checkers.SSHPublicKeyDatabase())
+ if pamauth is not None:
+ from twisted.cred.checkers import PluggableAuthenticationModulesChecker
+ t.portal.registerChecker(PluggableAuthenticationModulesChecker())
+ t.dataRoot = config['data']
+ t.moduliRoot = config['moduli'] or config['data']
+ port = config['port']
+ if config['interface']:
+ # Add warning here
+ port += ':interface='+config['interface']
+ return strports.service(port, t)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/telnet.py b/vendor/Twisted-10.0.0/twisted/conch/telnet.py
new file mode 100644
index 0000000000..1dcfb781f4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/telnet.py
@@ -0,0 +1,1017 @@
+# -*- test-case-name: twisted.conch.test.test_telnet -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Telnet protocol implementation.
+
+@author: Jp Calderone
+"""
+
+import struct
+
+from zope.interface import implements
+
+from twisted.internet import protocol, interfaces as iinternet, defer
+from twisted.python import log
+
+MODE = chr(1)
+EDIT = 1
+TRAPSIG = 2
+MODE_ACK = 4
+SOFT_TAB = 8
+LIT_ECHO = 16
+
+# Characters gleaned from the various (and conflicting) RFCs. Not all of these are correct.
+
+NULL = chr(0) # No operation.
+BEL = chr(7) # Produces an audible or
+ # visible signal (which does
+ # NOT move the print head).
+BS = chr(8) # Moves the print head one
+ # character position towards
+ # the left margin.
+HT = chr(9) # Moves the printer to the
+ # next horizontal tab stop.
+ # It remains unspecified how
+ # either party determines or
+ # establishes where such tab
+ # stops are located.
+LF = chr(10) # Moves the printer to the
+ # next print line, keeping the
+ # same horizontal position.
+VT = chr(11) # Moves the printer to the
+ # next vertical tab stop. It
+ # remains unspecified how
+ # either party determines or
+ # establishes where such tab
+ # stops are located.
+FF = chr(12) # Moves the printer to the top
+ # of the next page, keeping
+ # the same horizontal position.
+CR = chr(13) # Moves the printer to the left
+ # margin of the current line.
+
+ECHO = chr(1) # User-to-Server: Asks the server to send
+ # Echos of the transmitted data.
+SGA = chr(3) # Suppress Go Ahead. Go Ahead is silly
+ # and most modern servers should suppress
+ # it.
+NAWS = chr(31) # Negotiate About Window Size. Indicate that
+ # information about the size of the terminal
+ # can be communicated.
+LINEMODE = chr(34) # Allow line buffering to be
+ # negotiated about.
+
+SE = chr(240) # End of subnegotiation parameters.
+NOP = chr(241) # No operation.
+DM = chr(242) # "Data Mark": The data stream portion
+ # of a Synch. This should always be
+ # accompanied by a TCP Urgent
+ # notification.
+BRK = chr(243) # NVT character Break.
+IP = chr(244) # The function Interrupt Process.
+AO = chr(245) # The function Abort Output
+AYT = chr(246) # The function Are You There.
+EC = chr(247) # The function Erase Character.
+EL = chr(248) # The function Erase Line
+GA = chr(249) # The Go Ahead signal.
+SB = chr(250) # Indicates that what follows is
+ # subnegotiation of the indicated
+ # option.
+WILL = chr(251) # Indicates the desire to begin
+ # performing, or confirmation that
+ # you are now performing, the
+ # indicated option.
+WONT = chr(252) # Indicates the refusal to perform,
+ # or continue performing, the
+ # indicated option.
+DO = chr(253) # Indicates the request that the
+ # other party perform, or
+ # confirmation that you are expecting
+ # the other party to perform, the
+ # indicated option.
+DONT = chr(254) # Indicates the demand that the
+ # other party stop performing,
+ # or confirmation that you are no
+ # longer expecting the other party
+ # to perform, the indicated option.
+IAC = chr(255) # Data Byte 255. Introduces a
+ # telnet command.
+
+LINEMODE_MODE = chr(1)
+LINEMODE_EDIT = chr(1)
+LINEMODE_TRAPSIG = chr(2)
+LINEMODE_MODE_ACK = chr(4)
+LINEMODE_SOFT_TAB = chr(8)
+LINEMODE_LIT_ECHO = chr(16)
+LINEMODE_FORWARDMASK = chr(2)
+LINEMODE_SLC = chr(3)
+LINEMODE_SLC_SYNCH = chr(1)
+LINEMODE_SLC_BRK = chr(2)
+LINEMODE_SLC_IP = chr(3)
+LINEMODE_SLC_AO = chr(4)
+LINEMODE_SLC_AYT = chr(5)
+LINEMODE_SLC_EOR = chr(6)
+LINEMODE_SLC_ABORT = chr(7)
+LINEMODE_SLC_EOF = chr(8)
+LINEMODE_SLC_SUSP = chr(9)
+LINEMODE_SLC_EC = chr(10)
+LINEMODE_SLC_EL = chr(11)
+
+LINEMODE_SLC_EW = chr(12)
+LINEMODE_SLC_RP = chr(13)
+LINEMODE_SLC_LNEXT = chr(14)
+LINEMODE_SLC_XON = chr(15)
+LINEMODE_SLC_XOFF = chr(16)
+LINEMODE_SLC_FORW1 = chr(17)
+LINEMODE_SLC_FORW2 = chr(18)
+LINEMODE_SLC_MCL = chr(19)
+LINEMODE_SLC_MCR = chr(20)
+LINEMODE_SLC_MCWL = chr(21)
+LINEMODE_SLC_MCWR = chr(22)
+LINEMODE_SLC_MCBOL = chr(23)
+LINEMODE_SLC_MCEOL = chr(24)
+LINEMODE_SLC_INSRT = chr(25)
+LINEMODE_SLC_OVER = chr(26)
+LINEMODE_SLC_ECR = chr(27)
+LINEMODE_SLC_EWR = chr(28)
+LINEMODE_SLC_EBOL = chr(29)
+LINEMODE_SLC_EEOL = chr(30)
+
+LINEMODE_SLC_DEFAULT = chr(3)
+LINEMODE_SLC_VALUE = chr(2)
+LINEMODE_SLC_CANTCHANGE = chr(1)
+LINEMODE_SLC_NOSUPPORT = chr(0)
+LINEMODE_SLC_LEVELBITS = chr(3)
+
+LINEMODE_SLC_ACK = chr(128)
+LINEMODE_SLC_FLUSHIN = chr(64)
+LINEMODE_SLC_FLUSHOUT = chr(32)
+LINEMODE_EOF = chr(236)
+LINEMODE_SUSP = chr(237)
+LINEMODE_ABORT = chr(238)
+
+class ITelnetProtocol(iinternet.IProtocol):
+ def unhandledCommand(command, argument):
+ """A command was received but not understood.
+ """
+
+ def unhandledSubnegotiation(bytes):
+ """A subnegotiation command was received but not understood.
+ """
+
+ def enableLocal(option):
+ """Enable the given option locally.
+
+ This should enable the given option on this side of the
+ telnet connection and return True. If False is returned,
+ the option will be treated as still disabled and the peer
+ will be notified.
+ """
+
+ def enableRemote(option):
+ """Indicate whether the peer should be allowed to enable this option.
+
+ Returns True if the peer should be allowed to enable this option,
+ False otherwise.
+ """
+
+ def disableLocal(option):
+ """Disable the given option locally.
+
+ Unlike enableLocal, this method cannot fail. The option must be
+ disabled.
+ """
+
+ def disableRemote(option):
+ """Indicate that the peer has disabled this option.
+ """
+
+class ITelnetTransport(iinternet.ITransport):
+ def do(option):
+ """Indicate a desire for the peer to begin performing the given option.
+
+ Returns a Deferred that fires with True when the peer begins performing
+ the option, or False when the peer refuses to perform it. If the peer
+ is already performing the given option, the Deferred will fail with
+ L{AlreadyEnabled}. If a negotiation regarding this option is already
+ in progress, the Deferred will fail with L{AlreadyNegotiating}.
+
+ Note: It is currently possible that this Deferred will never fire,
+ if the peer never responds, or if the peer believes the option to
+ already be enabled.
+ """
+
+ def dont(option):
+ """Indicate a desire for the peer to cease performing the given option.
+
+ Returns a Deferred that fires with True when the peer ceases performing
+ the option. If the peer is not performing the given option, the
+ Deferred will fail with L{AlreadyDisabled}. If negotiation regarding
+ this option is already in progress, the Deferred will fail with
+ L{AlreadyNegotiating}.
+
+ Note: It is currently possible that this Deferred will never fire,
+ if the peer never responds, or if the peer believes the option to
+ already be disabled.
+ """
+
+ def will(option):
+ """Indicate our willingness to begin performing this option locally.
+
+ Returns a Deferred that fires with True when the peer agrees to allow
+ us to begin performing this option, or False if the peer refuses to
+ allow us to begin performing it. If the option is already enabled
+ locally, the Deferred will fail with L{AlreadyEnabled}. If negotiation
+ regarding this option is already in progress, the Deferred will fail with
+ L{AlreadyNegotiating}.
+
+ Note: It is currently possible that this Deferred will never fire,
+ if the peer never responds, or if the peer believes the option to
+ already be enabled.
+ """
+
+ def wont(option):
+ """Indicate that we will stop performing the given option.
+
+ Returns a Deferred that fires with True when the peer acknowledges
+ we have stopped performing this option. If the option is already
+ disabled locally, the Deferred will fail with L{AlreadyDisabled}.
+ If negotiation regarding this option is already in progress,
+ the Deferred will fail with L{AlreadyNegotiating}.
+
+ Note: It is currently possible that this Deferred will never fire,
+ if the peer never responds, or if the peer believes the option to
+ already be disabled.
+ """
+
+ def requestNegotiation(about, bytes):
+ """Send a subnegotiation request.
+
+ @param about: A byte indicating the feature being negotiated.
+ @param bytes: Any number of bytes containing specific information
+ about the negotiation being requested. No values in this string
+ need to be escaped, as this function will escape any value which
+ requires it.
+ """
+
+class TelnetError(Exception):
+ pass
+
+class NegotiationError(TelnetError):
+ def __str__(self):
+ return self.__class__.__module__ + '.' + self.__class__.__name__ + ':' + repr(self.args[0])
+
+class OptionRefused(NegotiationError):
+ pass
+
+class AlreadyEnabled(NegotiationError):
+ pass
+
+class AlreadyDisabled(NegotiationError):
+ pass
+
+class AlreadyNegotiating(NegotiationError):
+ pass
+
+class TelnetProtocol(protocol.Protocol):
+ implements(ITelnetProtocol)
+
+ def unhandledCommand(self, command, argument):
+ pass
+
+ def unhandledSubnegotiation(self, command, bytes):
+ pass
+
+ def enableLocal(self, option):
+ pass
+
+ def enableRemote(self, option):
+ pass
+
+ def disableLocal(self, option):
+ pass
+
+ def disableRemote(self, option):
+ pass
+
+
+class Telnet(protocol.Protocol):
+ """
+ @ivar commandMap: A mapping of bytes to callables. When a
+ telnet command is received, the command byte (the first byte
+ after IAC) is looked up in this dictionary. If a callable is
+ found, it is invoked with the argument of the command, or None
+ if the command takes no argument. Values should be added to
+ this dictionary if commands wish to be handled. By default,
+ only WILL, WONT, DO, and DONT are handled. These should not
+ be overridden, as this class handles them correctly and
+ provides an API for interacting with them.
+
+ @ivar negotiationMap: A mapping of bytes to callables. When
+ a subnegotiation command is received, the command byte (the
+ first byte after SB) is looked up in this dictionary. If
+ a callable is found, it is invoked with the argument of the
+ subnegotiation. Values should be added to this dictionary if
+ subnegotiations are to be handled. By default, no values are
+ handled.
+
+ @ivar options: A mapping of option bytes to their current
+ state. This state is likely of little use to user code.
+ Changes should not be made to it.
+
+ @ivar state: A string indicating the current parse state. It
+ can take on the values "data", "escaped", "command", "newline",
+ "subnegotiation", and "subnegotiation-escaped". Changes
+ should not be made to it.
+
+ @ivar transport: This protocol's transport object.
+ """
+
+ # One of a lot of things
+ state = 'data'
+
+ def __init__(self):
+ self.options = {}
+ self.negotiationMap = {}
+ self.commandMap = {
+ WILL: self.telnet_WILL,
+ WONT: self.telnet_WONT,
+ DO: self.telnet_DO,
+ DONT: self.telnet_DONT}
+
+ def _write(self, bytes):
+ self.transport.write(bytes)
+
+ class _OptionState:
+ class _Perspective:
+ state = 'no'
+ negotiating = False
+ onResult = None
+
+ def __str__(self):
+ return self.state + ('*' * self.negotiating)
+
+ def __init__(self):
+ self.us = self._Perspective()
+ self.him = self._Perspective()
+
+ def __repr__(self):
+ return '<_OptionState us=%s him=%s>' % (self.us, self.him)
+
+ def getOptionState(self, opt):
+ return self.options.setdefault(opt, self._OptionState())
+
+ def _do(self, option):
+ self._write(IAC + DO + option)
+
+ def _dont(self, option):
+ self._write(IAC + DONT + option)
+
+ def _will(self, option):
+ self._write(IAC + WILL + option)
+
+ def _wont(self, option):
+ self._write(IAC + WONT + option)
+
+ def will(self, option):
+ """Indicate our willingness to enable an option.
+ """
+ s = self.getOptionState(option)
+ if s.us.negotiating or s.him.negotiating:
+ return defer.fail(AlreadyNegotiating(option))
+ elif s.us.state == 'yes':
+ return defer.fail(AlreadyEnabled(option))
+ else:
+ s.us.negotiating = True
+ s.us.onResult = d = defer.Deferred()
+ self._will(option)
+ return d
+
+ def wont(self, option):
+ """Indicate we are not willing to enable an option.
+ """
+ s = self.getOptionState(option)
+ if s.us.negotiating or s.him.negotiating:
+ return defer.fail(AlreadyNegotiating(option))
+ elif s.us.state == 'no':
+ return defer.fail(AlreadyDisabled(option))
+ else:
+ s.us.negotiating = True
+ s.us.onResult = d = defer.Deferred()
+ self._wont(option)
+ return d
+
+ def do(self, option):
+ s = self.getOptionState(option)
+ if s.us.negotiating or s.him.negotiating:
+ return defer.fail(AlreadyNegotiating(option))
+ elif s.him.state == 'yes':
+ return defer.fail(AlreadyEnabled(option))
+ else:
+ s.him.negotiating = True
+ s.him.onResult = d = defer.Deferred()
+ self._do(option)
+ return d
+
+ def dont(self, option):
+ s = self.getOptionState(option)
+ if s.us.negotiating or s.him.negotiating:
+ return defer.fail(AlreadyNegotiating(option))
+ elif s.him.state == 'no':
+ return defer.fail(AlreadyDisabled(option))
+ else:
+ s.him.negotiating = True
+ s.him.onResult = d = defer.Deferred()
+ self._dont(option)
+ return d
+
+
+ def requestNegotiation(self, about, bytes):
+ """
+ Send a negotiation message for the option C{about} with C{bytes} as the
+ payload.
+
+ @see: L{ITelnetTransport.requestNegotiation}
+ """
+ bytes = bytes.replace(IAC, IAC * 2)
+ self._write(IAC + SB + about + bytes + IAC + SE)
+
+
+ def dataReceived(self, data):
+ appDataBuffer = []
+
+ for b in data:
+ if self.state == 'data':
+ if b == IAC:
+ self.state = 'escaped'
+ elif b == '\r':
+ self.state = 'newline'
+ else:
+ appDataBuffer.append(b)
+ elif self.state == 'escaped':
+ if b == IAC:
+ appDataBuffer.append(b)
+ self.state = 'data'
+ elif b == SB:
+ self.state = 'subnegotiation'
+ self.commands = []
+ elif b in (NOP, DM, BRK, IP, AO, AYT, EC, EL, GA):
+ self.state = 'data'
+ if appDataBuffer:
+ self.applicationDataReceived(''.join(appDataBuffer))
+ del appDataBuffer[:]
+ self.commandReceived(b, None)
+ elif b in (WILL, WONT, DO, DONT):
+ self.state = 'command'
+ self.command = b
+ else:
+ raise ValueError("Stumped", b)
+ elif self.state == 'command':
+ self.state = 'data'
+ command = self.command
+ del self.command
+ if appDataBuffer:
+ self.applicationDataReceived(''.join(appDataBuffer))
+ del appDataBuffer[:]
+ self.commandReceived(command, b)
+ elif self.state == 'newline':
+ self.state = 'data'
+ if b == '\n':
+ appDataBuffer.append('\n')
+ elif b == '\0':
+ appDataBuffer.append('\r')
+ elif b == IAC:
+ # IAC isn't really allowed after \r, according to the
+ # RFC, but handling it this way is less surprising than
+ # delivering the IAC to the app as application data.
+ # The purpose of the restriction is to allow terminals
+ # to unambiguously interpret the behavior of the CR
+ # after reading only one more byte. CR LF is supposed
+ # to mean one thing (cursor to next line, first column),
+ # CR NUL another (cursor to first column). Absent the
+ # NUL, it still makes sense to interpret this as CR and
+ # then apply all the usual interpretation to the IAC.
+ appDataBuffer.append('\r')
+ self.state = 'escaped'
+ else:
+ appDataBuffer.append('\r' + b)
+ elif self.state == 'subnegotiation':
+ if b == IAC:
+ self.state = 'subnegotiation-escaped'
+ else:
+ self.commands.append(b)
+ elif self.state == 'subnegotiation-escaped':
+ if b == SE:
+ self.state = 'data'
+ commands = self.commands
+ del self.commands
+ if appDataBuffer:
+ self.applicationDataReceived(''.join(appDataBuffer))
+ del appDataBuffer[:]
+ self.negotiate(commands)
+ else:
+ self.state = 'subnegotiation'
+ self.commands.append(b)
+ else:
+ raise ValueError("How'd you do this?")
+
+ if appDataBuffer:
+ self.applicationDataReceived(''.join(appDataBuffer))
+
+
+ def connectionLost(self, reason):
+ for state in self.options.values():
+ if state.us.onResult is not None:
+ d = state.us.onResult
+ state.us.onResult = None
+ d.errback(reason)
+ if state.him.onResult is not None:
+ d = state.him.onResult
+ state.him.onResult = None
+ d.errback(reason)
+
+ def applicationDataReceived(self, bytes):
+ """Called with application-level data.
+ """
+
+ def unhandledCommand(self, command, argument):
+ """Called for commands for which no handler is installed.
+ """
+
+ def commandReceived(self, command, argument):
+ cmdFunc = self.commandMap.get(command)
+ if cmdFunc is None:
+ self.unhandledCommand(command, argument)
+ else:
+ cmdFunc(argument)
+
+ def unhandledSubnegotiation(self, command, bytes):
+ """Called for subnegotiations for which no handler is installed.
+ """
+
+ def negotiate(self, bytes):
+ command, bytes = bytes[0], bytes[1:]
+ cmdFunc = self.negotiationMap.get(command)
+ if cmdFunc is None:
+ self.unhandledSubnegotiation(command, bytes)
+ else:
+ cmdFunc(bytes)
+
+ def telnet_WILL(self, option):
+ s = self.getOptionState(option)
+ self.willMap[s.him.state, s.him.negotiating](self, s, option)
+
+ def will_no_false(self, state, option):
+ # He is unilaterally offering to enable an option.
+ if self.enableRemote(option):
+ state.him.state = 'yes'
+ self._do(option)
+ else:
+ self._dont(option)
+
+ def will_no_true(self, state, option):
+ # Peer agreed to enable an option in response to our request.
+ state.him.state = 'yes'
+ state.him.negotiating = False
+ d = state.him.onResult
+ state.him.onResult = None
+ d.callback(True)
+ assert self.enableRemote(option), "enableRemote must return True in this context (for option %r)" % (option,)
+
+ def will_yes_false(self, state, option):
+ # He is unilaterally offering to enable an already-enabled option.
+ # Ignore this.
+ pass
+
+ def will_yes_true(self, state, option):
+ # This is a bogus state. It is here for completeness. It will
+ # never be entered.
+ assert False, "will_yes_true can never be entered, but was called with %r, %r" % (state, option)
+
+ willMap = {('no', False): will_no_false, ('no', True): will_no_true,
+ ('yes', False): will_yes_false, ('yes', True): will_yes_true}
+
+ def telnet_WONT(self, option):
+ s = self.getOptionState(option)
+ self.wontMap[s.him.state, s.him.negotiating](self, s, option)
+
+ def wont_no_false(self, state, option):
+ # He is unilaterally demanding that an already-disabled option be/remain disabled.
+ # Ignore this (although we could record it and refuse subsequent enable attempts
+ # from our side - he can always refuse them again though, so we won't)
+ pass
+
+ def wont_no_true(self, state, option):
+ # Peer refused to enable an option in response to our request.
+ state.him.negotiating = False
+ d = state.him.onResult
+ state.him.onResult = None
+ d.errback(OptionRefused(option))
+
+ def wont_yes_false(self, state, option):
+ # Peer is unilaterally demanding that an option be disabled.
+ state.him.state = 'no'
+ self.disableRemote(option)
+ self._dont(option)
+
+ def wont_yes_true(self, state, option):
+ # Peer agreed to disable an option at our request.
+ state.him.state = 'no'
+ state.him.negotiating = False
+ d = state.him.onResult
+ state.him.onResult = None
+ d.callback(True)
+ self.disableRemote(option)
+
+ wontMap = {('no', False): wont_no_false, ('no', True): wont_no_true,
+ ('yes', False): wont_yes_false, ('yes', True): wont_yes_true}
+
+ def telnet_DO(self, option):
+ s = self.getOptionState(option)
+ self.doMap[s.us.state, s.us.negotiating](self, s, option)
+
+ def do_no_false(self, state, option):
+ # Peer is unilaterally requesting that we enable an option.
+ if self.enableLocal(option):
+ state.us.state = 'yes'
+ self._will(option)
+ else:
+ self._wont(option)
+
+ def do_no_true(self, state, option):
+ # Peer agreed to allow us to enable an option at our request.
+ state.us.state = 'yes'
+ state.us.negotiating = False
+ d = state.us.onResult
+ state.us.onResult = None
+ d.callback(True)
+ self.enableLocal(option)
+
+ def do_yes_false(self, state, option):
+ # Peer is unilaterally requesting us to enable an already-enabled option.
+ # Ignore this.
+ pass
+
+ def do_yes_true(self, state, option):
+ # This is a bogus state. It is here for completeness. It will never be
+ # entered.
+ assert False, "do_yes_true can never be entered, but was called with %r, %r" % (state, option)
+
+ doMap = {('no', False): do_no_false, ('no', True): do_no_true,
+ ('yes', False): do_yes_false, ('yes', True): do_yes_true}
+
+ def telnet_DONT(self, option):
+ s = self.getOptionState(option)
+ self.dontMap[s.us.state, s.us.negotiating](self, s, option)
+
+ def dont_no_false(self, state, option):
+ # Peer is unilaterally demanding us to disable an already-disabled option.
+ # Ignore this.
+ pass
+
+ def dont_no_true(self, state, option):
+ # This is a bogus state. It is here for completeness. It will never be
+ # entered.
+ assert False, "dont_no_true can never be entered, but was called with %r, %r" % (state, option)
+
+
+ def dont_yes_false(self, state, option):
+ # Peer is unilaterally demanding we disable an option.
+ state.us.state = 'no'
+ self.disableLocal(option)
+ self._wont(option)
+
+ def dont_yes_true(self, state, option):
+ # Peer acknowledged our notice that we will disable an option.
+ state.us.state = 'no'
+ state.us.negotiating = False
+ d = state.us.onResult
+ state.us.onResult = None
+ d.callback(True)
+ self.disableLocal(option)
+
+ dontMap = {('no', False): dont_no_false, ('no', True): dont_no_true,
+ ('yes', False): dont_yes_false, ('yes', True): dont_yes_true}
+
+ def enableLocal(self, option):
+ """
+ Reject all attempts to enable options.
+ """
+ return False
+
+
+ def enableRemote(self, option):
+ """
+ Reject all attempts to enable options.
+ """
+ return False
+
+
+ def disableLocal(self, option):
+ """
+ Signal a programming error by raising an exception.
+
+ L{enableLocal} must return true for the given value of C{option} in
+ order for this method to be called. If a subclass of L{Telnet}
+ overrides enableLocal to allow certain options to be enabled, it must
+ also override disableLocal to disable those options.
+
+ @raise NotImplementedError: Always raised.
+ """
+ raise NotImplementedError(
+ "Don't know how to disable local telnet option %r" % (option,))
+
+
+ def disableRemote(self, option):
+ """
+ Signal a programming error by raising an exception.
+
+ L{enableRemote} must return true for the given value of C{option} in
+ order for this method to be called. If a subclass of L{Telnet}
+ overrides enableRemote to allow certain options to be enabled, it must
+ also override disableRemote tto disable those options.
+
+ @raise NotImplementedError: Always raised.
+ """
+ raise NotImplementedError(
+ "Don't know how to disable remote telnet option %r" % (option,))
+
+
+
+class ProtocolTransportMixin:
+ def write(self, bytes):
+ self.transport.write(bytes.replace('\n', '\r\n'))
+
+ def writeSequence(self, seq):
+ self.transport.writeSequence(seq)
+
+ def loseConnection(self):
+ self.transport.loseConnection()
+
+ def getHost(self):
+ return self.transport.getHost()
+
+ def getPeer(self):
+ return self.transport.getPeer()
+
+class TelnetTransport(Telnet, ProtocolTransportMixin):
+ """
+ @ivar protocol: An instance of the protocol to which this
+ transport is connected, or None before the connection is
+ established and after it is lost.
+
+ @ivar protocolFactory: A callable which returns protocol instances
+ which provide L{ITelnetProtocol}. This will be invoked when a
+ connection is established. It is passed *protocolArgs and
+ **protocolKwArgs.
+
+ @ivar protocolArgs: A tuple of additional arguments to
+ pass to protocolFactory.
+
+ @ivar protocolKwArgs: A dictionary of additional arguments
+ to pass to protocolFactory.
+ """
+
+ disconnecting = False
+
+ protocolFactory = None
+ protocol = None
+
+ def __init__(self, protocolFactory=None, *a, **kw):
+ Telnet.__init__(self)
+ if protocolFactory is not None:
+ self.protocolFactory = protocolFactory
+ self.protocolArgs = a
+ self.protocolKwArgs = kw
+
+ def connectionMade(self):
+ if self.protocolFactory is not None:
+ self.protocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
+ assert ITelnetProtocol.providedBy(self.protocol)
+ try:
+ factory = self.factory
+ except AttributeError:
+ pass
+ else:
+ self.protocol.factory = factory
+ self.protocol.makeConnection(self)
+
+ def connectionLost(self, reason):
+ Telnet.connectionLost(self, reason)
+ if self.protocol is not None:
+ try:
+ self.protocol.connectionLost(reason)
+ finally:
+ del self.protocol
+
+ def enableLocal(self, option):
+ return self.protocol.enableLocal(option)
+
+ def enableRemote(self, option):
+ return self.protocol.enableRemote(option)
+
+ def disableLocal(self, option):
+ return self.protocol.disableLocal(option)
+
+ def disableRemote(self, option):
+ return self.protocol.disableRemote(option)
+
+ def unhandledSubnegotiation(self, command, bytes):
+ self.protocol.unhandledSubnegotiation(command, bytes)
+
+ def unhandledCommand(self, command, argument):
+ self.protocol.unhandledCommand(command, argument)
+
+ def applicationDataReceived(self, bytes):
+ self.protocol.dataReceived(bytes)
+
+ def write(self, data):
+ ProtocolTransportMixin.write(self, data.replace('\xff','\xff\xff'))
+
+
+class TelnetBootstrapProtocol(TelnetProtocol, ProtocolTransportMixin):
+ implements()
+
+ protocol = None
+
+ def __init__(self, protocolFactory, *args, **kw):
+ self.protocolFactory = protocolFactory
+ self.protocolArgs = args
+ self.protocolKwArgs = kw
+
+ def connectionMade(self):
+ self.transport.negotiationMap[NAWS] = self.telnet_NAWS
+ self.transport.negotiationMap[LINEMODE] = self.telnet_LINEMODE
+
+ for opt in (LINEMODE, NAWS, SGA):
+ self.transport.do(opt).addErrback(log.err)
+ for opt in (ECHO,):
+ self.transport.will(opt).addErrback(log.err)
+
+ self.protocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
+
+ try:
+ factory = self.factory
+ except AttributeError:
+ pass
+ else:
+ self.protocol.factory = factory
+
+ self.protocol.makeConnection(self)
+
+ def connectionLost(self, reason):
+ if self.protocol is not None:
+ try:
+ self.protocol.connectionLost(reason)
+ finally:
+ del self.protocol
+
+ def dataReceived(self, data):
+ self.protocol.dataReceived(data)
+
+ def enableLocal(self, opt):
+ if opt == ECHO:
+ return True
+ elif opt == SGA:
+ return True
+ else:
+ return False
+
+ def enableRemote(self, opt):
+ if opt == LINEMODE:
+ self.transport.requestNegotiation(LINEMODE, MODE + chr(TRAPSIG))
+ return True
+ elif opt == NAWS:
+ return True
+ elif opt == SGA:
+ return True
+ else:
+ return False
+
+ def telnet_NAWS(self, bytes):
+ # NAWS is client -> server *only*. self.protocol will
+ # therefore be an ITerminalTransport, the `.protocol'
+ # attribute of which will be an ITerminalProtocol. Maybe.
+ # You know what, XXX TODO clean this up.
+ if len(bytes) == 4:
+ width, height = struct.unpack('!HH', ''.join(bytes))
+ self.protocol.terminalProtocol.terminalSize(width, height)
+ else:
+ log.msg("Wrong number of NAWS bytes")
+
+
+ linemodeSubcommands = {
+ LINEMODE_SLC: 'SLC'}
+ def telnet_LINEMODE(self, bytes):
+ revmap = {}
+ linemodeSubcommand = bytes[0]
+ if 0:
+ # XXX TODO: This should be enabled to parse linemode subnegotiation.
+ getattr(self, 'linemode_' + self.linemodeSubcommands[linemodeSubcommand])(bytes[1:])
+
+ def linemode_SLC(self, bytes):
+ chunks = zip(*[iter(bytes)]*3)
+ for slcFunction, slcValue, slcWhat in chunks:
+ # Later, we should parse stuff.
+ 'SLC', ord(slcFunction), ord(slcValue), ord(slcWhat)
+
+from twisted.protocols import basic
+
+class StatefulTelnetProtocol(basic.LineReceiver, TelnetProtocol):
+ delimiter = '\n'
+
+ state = 'Discard'
+
+ def connectionLost(self, reason):
+ basic.LineReceiver.connectionLost(self, reason)
+ TelnetProtocol.connectionLost(self, reason)
+
+ def lineReceived(self, line):
+ oldState = self.state
+ newState = getattr(self, "telnet_" + oldState)(line)
+ if newState is not None:
+ if self.state == oldState:
+ self.state = newState
+ else:
+ log.msg("Warning: state changed and new state returned")
+
+ def telnet_Discard(self, line):
+ pass
+
+from twisted.cred import credentials
+
+class AuthenticatingTelnetProtocol(StatefulTelnetProtocol):
+ """A protocol which prompts for credentials and attempts to authenticate them.
+
+ Username and password prompts are given (the password is obscured). When the
+ information is collected, it is passed to a portal and an avatar implementing
+ L{ITelnetProtocol} is requested. If an avatar is returned, it connected to this
+ protocol's transport, and this protocol's transport is connected to it.
+ Otherwise, the user is re-prompted for credentials.
+ """
+
+ state = "User"
+ protocol = None
+
+ def __init__(self, portal):
+ self.portal = portal
+
+ def connectionMade(self):
+ self.transport.write("Username: ")
+
+ def connectionLost(self, reason):
+ StatefulTelnetProtocol.connectionLost(self, reason)
+ if self.protocol is not None:
+ try:
+ self.protocol.connectionLost(reason)
+ self.logout()
+ finally:
+ del self.protocol, self.logout
+
+ def telnet_User(self, line):
+ self.username = line
+ self.transport.will(ECHO)
+ self.transport.write("Password: ")
+ return 'Password'
+
+ def telnet_Password(self, line):
+ username, password = self.username, line
+ del self.username
+ def login(ignored):
+ creds = credentials.UsernamePassword(username, password)
+ d = self.portal.login(creds, None, ITelnetProtocol)
+ d.addCallback(self._cbLogin)
+ d.addErrback(self._ebLogin)
+ self.transport.wont(ECHO).addCallback(login)
+ return 'Discard'
+
+ def _cbLogin(self, ial):
+ interface, protocol, logout = ial
+ assert interface is ITelnetProtocol
+ self.protocol = protocol
+ self.logout = logout
+ self.state = 'Command'
+
+ protocol.makeConnection(self.transport)
+ self.transport.protocol = protocol
+
+ def _ebLogin(self, failure):
+ self.transport.write("\nAuthentication failed\n")
+ self.transport.write("Username: ")
+ self.state = "User"
+
+__all__ = [
+ # Exceptions
+ 'TelnetError', 'NegotiationError', 'OptionRefused',
+ 'AlreadyNegotiating', 'AlreadyEnabled', 'AlreadyDisabled',
+
+ # Interfaces
+ 'ITelnetProtocol', 'ITelnetTransport',
+
+ # Other stuff, protocols, etc.
+ 'Telnet', 'TelnetProtocol', 'TelnetTransport',
+ 'TelnetBootstrapProtocol',
+
+ ]
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/__init__.py b/vendor/Twisted-10.0.0/twisted/conch/test/__init__.py
new file mode 100644
index 0000000000..d09b412291
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/__init__.py
@@ -0,0 +1 @@
+'conch tests'
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/keydata.py b/vendor/Twisted-10.0.0/twisted/conch/test/keydata.py
new file mode 100644
index 0000000000..33879459b0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/keydata.py
@@ -0,0 +1,174 @@
+# -*- test-case-name: twisted.conch.test.test_keys -*-
+# Copyright (c) 2007-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Data used by test_keys as well as others.
+"""
+RSAData = {
+ 'n':long('1062486685755247411169438309495398947372127791189432809481'
+ '382072971106157632182084539383569281493520117634129557550415277'
+ '516685881326038852354459895734875625093273594925884531272867425'
+ '864910490065695876046999646807138717162833156501L'),
+ 'e':35L,
+ 'd':long('6678487739032983727350755088256793383481946116047863373882'
+ '973030104095847973715959961839578340816412167985957218887914482'
+ '713602371850869127033494910375212470664166001439410214474266799'
+ '85974425203903884190893469297150446322896587555L'),
+ 'q':long('3395694744258061291019136154000709371890447462086362702627'
+ '9704149412726577280741108645721676968699696898960891593323L'),
+ 'p':long('3128922844292337321766351031842562691837301298995834258844'
+ '4720539204069737532863831050930719431498338835415515173887L')}
+
+DSAData = {
+ 'y':long('2300663509295750360093768159135720439490120577534296730713'
+ '348508834878775464483169644934425336771277908527130096489120714'
+ '610188630979820723924744291603865L'),
+ 'g':long('4451569990409370769930903934104221766858515498655655091803'
+ '866645719060300558655677517139568505649468378587802312867198352'
+ '1161998270001677664063945776405L'),
+ 'p':long('7067311773048598659694590252855127633397024017439939353776'
+ '608320410518694001356789646664502838652272205440894335303988504'
+ '978724817717069039110940675621677L'),
+ 'q':1184501645189849666738820838619601267690550087703L,
+ 'x':863951293559205482820041244219051653999559962819L}
+
+publicRSA_openssh = ("ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBE"
+"vLi8DVPrJ3/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYL"
+"h5KmRpslkYHRivcJSkbh/C+BR3utDS555mV comment")
+
+privateRSA_openssh = """-----BEGIN RSA PRIVATE KEY-----
+MIIByAIBAAJhAK8ycfDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW
+4sbUIZR/ZXzY1CMfuC5qyR+UDUbBaaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fw
+vgUd7rQ0ueeZlQIBIwJgbh+1VZfr7WftK5lu7MHtqE1S1vPWZQYE3+VUn8yJADyb
+Z4fsZaCrzW9lkIqXkE3GIY+ojdhZhkO1gbG0118sIgphwSWKRxK0mvh6ERxKqIt1
+xJEJO74EykXZV4oNJ8sjAjEA3J9r2ZghVhGN6V8DnQrTk24Td0E8hU8AcP0FVP+8
+PQm/g/aXf2QQkQT+omdHVEJrAjEAy0pL0EBH6EVS98evDCBtQw22OZT52qXlAwZ2
+gyTriKFVoqjeEjt3SZKKqXHSApP/AjBLpF99zcJJZRq2abgYlf9lv1chkrWqDHUu
+DZttmYJeEfiFBBavVYIF1dOlZT0G8jMCMBc7sOSZodFnAiryP+Qg9otSBjJ3bQML
+pSTqy7c3a2AScC/YyOwkDaICHnnD3XyjMwIxALRzl0tQEKMXs6hH8ToUdlLROCrP
+EhQ0wahUTCk1gKA4uPD6TMTChavbh4K63OvbKg==
+-----END RSA PRIVATE KEY-----"""
+
+# some versions of OpenSSH generate these (slightly different keys)
+privateRSA_openssh_alternate = """-----BEGIN RSA PRIVATE KEY-----
+MIIBzjCCAcgCAQACYQCvMnHw5g6cmbN/i18ES8uLwNU+snf9z2TYj8DPrh/GMd/2
+KbJEluLG1CGUf2V82NQjH7guaskflA1GwWmitwcMo5PBNNguHkqZGmyWRgdGK9wl
+KRuH8L4FHe60NLnnmZUCASMCYG4ftVWX6+1n7SuZbuzB7ahNUtbz1mUGBN/lVJ/M
+iQA8m2eH7GWgq81vZZCKl5BNxiGPqI3YWYZDtYGxtNdfLCIKYcElikcStJr4ehEc
+SqiLdcSRCTu+BMpF2VeKDSfLIwIxANyfa9mYIVYRjelfA50K05NuE3dBPIVPAHD9
+BVT/vD0Jv4P2l39kEJEE/qJnR1RCawIxAMtKS9BAR+hFUvfHrwwgbUMNtjmU+dql
+5QMGdoMk64ihVaKo3hI7d0mSiqlx0gKT/wIwS6Rffc3CSWUatmm4GJX/Zb9XIZK1
+qgx1Lg2bbZmCXhH4hQQWr1WCBdXTpWU9BvIzAjAXO7DkmaHRZwIq8j/kIPaLUgYy
+d20DC6Uk6su3N2tgEnAv2MjsJA2iAh55w918ozMCMQC0c5dLUBCjF7OoR/E6FHZS
+0TgqzxIUNMGoVEwpNYCgOLjw+kzEwoWr24eCutzr2yowAA==
+------END RSA PRIVATE KEY------"""
+
+privateRSA_openssh_encrypted = """-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,FFFFFFFFFFFFFFFF
+
+30qUR7DYY/rpVJu159paRM1mUqt/IMibfEMTKWSjNhCVD21hskftZCJROw/WgIFt
+ncusHpJMkjgwEpho0KyKilcC7zxjpunTex24Meb5pCdXCrYft8AyUkRdq3dugMqT
+4nuWuWxziluBhKQ2M9tPGcEOeulU4vVjceZt2pZhZQVBf08o3XUv5/7RYd24M9md
+WIo+5zdj2YQkI6xMFTP954O/X32ME1KQt98wgNEy6mxhItbvf00mH3woALwEKP3v
+PSMxxtx3VKeDKd9YTOm1giKkXZUf91vZWs0378tUBrU4U5qJxgryTjvvVKOtofj6
+4qQy6+r6M6wtwVlXBgeRm2gBPvL3nv6MsROp3E6ztBd/e7A8fSec+UTq3ko/EbGP
+0QG+IG5tg8FsdITxQ9WAIITZL3Rc6hA5Ymx1VNhySp3iSiso8Jof27lku4pyuvRV
+ko/B3N2H7LnQrGV0GyrjeYocW/qZh/PCsY48JBFhlNQexn2mn44AJW3y5xgbhvKA
+3mrmMD1hD17ZvZxi4fPHjbuAyM1vFqhQx63eT9ijbwJ91svKJl5O5MIv41mCRonm
+hxvOXw8S0mjSasyofptzzQCtXxFLQigXbpQBltII+Ys=
+-----END RSA PRIVATE KEY-----"""
+
+publicRSA_lsh = ("{KDEwOnB1YmxpYy1rZXkoMTQ6cnNhLXBrY3MxLXNoYTEoMTpuOTc6AK8yc"
+"fDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW4sbUIZR/ZXzY1CMfuC5qyR+UDUbB"
+"aaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fwvgUd7rQ0ueeZlSkoMTplMTojKSkp}")
+
+privateRSA_lsh = ("(11:private-key(9:rsa-pkcs1(1:n97:\x00\xaf2q\xf0\xe6\x0e"
+"\x9c\x99\xb3\x7f\x8b_\x04K\xcb\x8b\xc0\xd5>\xb2w\xfd\xcfd\xd8\x8f\xc0\xcf"
+"\xae\x1f\xc61\xdf\xf6)\xb2D\x96\xe2\xc6\xd4!\x94\x7fe|\xd8\xd4#\x1f\xb8.j"
+"\xc9\x1f\x94\rF\xc1i\xa2\xb7\x07\x0c\xa3\x93\xc14\xd8.\x1eJ\x99\x1al\x96F"
+"\x07F+\xdc%)\x1b\x87\xf0\xbe\x05\x1d\xee\xb44\xb9\xe7\x99\x95)(1:e1:#)(1:d9"
+"6:n\x1f\xb5U\x97\xeb\xedg\xed+\x99n\xec\xc1\xed\xa8MR\xd6\xf3\xd6e\x06\x04"
+"\xdf\xe5T\x9f\xcc\x89\x00<\x9bg\x87\xece\xa0\xab\xcdoe\x90\x8a\x97\x90M\xc6"
+'!\x8f\xa8\x8d\xd8Y\x86C\xb5\x81\xb1\xb4\xd7_,"\na\xc1%\x8aG\x12\xb4\x9a\xf8'
+"z\x11\x1cJ\xa8\x8bu\xc4\x91\t;\xbe\x04\xcaE\xd9W\x8a\r\'\xcb#)(1:p49:\x00"
+"\xdc\x9fk\xd9\x98!V\x11\x8d\xe9_\x03\x9d\n\xd3\x93n\x13wA<\x85O\x00p\xfd"
+"\x05T\xff\xbc=\t\xbf\x83\xf6\x97\x7fd\x10\x91\x04\xfe\xa2gGTBk)(1:q49:\x00"
+"\xcbJK\xd0@G\xe8ER\xf7\xc7\xaf\x0c mC\r\xb69\x94\xf9\xda\xa5\xe5\x03\x06v"
+"\x83$\xeb\x88\xa1U\xa2\xa8\xde\x12;wI\x92\x8a\xa9q\xd2\x02\x93\xff)(1:a48:K"
+"\xa4_}\xcd\xc2Ie\x1a\xb6i\xb8\x18\x95\xffe\xbfW!\x92\xb5\xaa\x0cu.\r\x9bm"
+"\x99\x82^\x11\xf8\x85\x04\x16\xafU\x82\x05\xd5\xd3\xa5e=\x06\xf23)(1:b48:"
+"\x17;\xb0\xe4\x99\xa1\xd1g\x02*\xf2?\xe4 \xf6\x8bR\x062wm\x03\x0b\xa5$\xea"
+"\xcb\xb77k`\x12p/\xd8\xc8\xec$\r\xa2\x02\x1ey\xc3\xdd|\xa33)(1:c49:\x00\xb4"
+"s\x97KP\x10\xa3\x17\xb3\xa8G\xf1:\x14vR\xd18*\xcf\x12\x144\xc1\xa8TL)5\x80"
+"\xa08\xb8\xf0\xfaL\xc4\xc2\x85\xab\xdb\x87\x82\xba\xdc\xeb\xdb*)))")
+
+privateRSA_agentv3 = ("\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01#\x00\x00\x00`"
+"n\x1f\xb5U\x97\xeb\xedg\xed+\x99n\xec\xc1\xed\xa8MR\xd6\xf3\xd6e\x06\x04"
+"\xdf\xe5T\x9f\xcc\x89\x00<\x9bg\x87\xece\xa0\xab\xcdoe\x90\x8a\x97\x90M\xc6"
+'!\x8f\xa8\x8d\xd8Y\x86C\xb5\x81\xb1\xb4\xd7_,"\na\xc1%\x8aG\x12\xb4\x9a\xf8'
+"z\x11\x1cJ\xa8\x8bu\xc4\x91\t;\xbe\x04\xcaE\xd9W\x8a\r\'\xcb#\x00\x00\x00a"
+"\x00\xaf2q\xf0\xe6\x0e\x9c\x99\xb3\x7f\x8b_\x04K\xcb\x8b\xc0\xd5>\xb2w\xfd"
+"\xcfd\xd8\x8f\xc0\xcf\xae\x1f\xc61\xdf\xf6)\xb2D\x96\xe2\xc6\xd4!\x94\x7fe|"
+"\xd8\xd4#\x1f\xb8.j\xc9\x1f\x94\rF\xc1i\xa2\xb7\x07\x0c\xa3\x93\xc14\xd8."
+"\x1eJ\x99\x1al\x96F\x07F+\xdc%)\x1b\x87\xf0\xbe\x05\x1d\xee\xb44\xb9\xe7"
+"\x99\x95\x00\x00\x001\x00\xb4s\x97KP\x10\xa3\x17\xb3\xa8G\xf1:\x14vR\xd18*"
+"\xcf\x12\x144\xc1\xa8TL)5\x80\xa08\xb8\xf0\xfaL\xc4\xc2\x85\xab\xdb\x87\x82"
+"\xba\xdc\xeb\xdb*\x00\x00\x001\x00\xcbJK\xd0@G\xe8ER\xf7\xc7\xaf\x0c mC\r"
+"\xb69\x94\xf9\xda\xa5\xe5\x03\x06v\x83$\xeb\x88\xa1U\xa2\xa8\xde\x12;wI\x92"
+"\x8a\xa9q\xd2\x02\x93\xff\x00\x00\x001\x00\xdc\x9fk\xd9\x98!V\x11\x8d\xe9_"
+"\x03\x9d\n\xd3\x93n\x13wA<\x85O\x00p\xfd\x05T\xff\xbc=\t\xbf\x83\xf6\x97"
+"\x7fd\x10\x91\x04\xfe\xa2gGTBk")
+
+publicDSA_openssh = ("ssh-dss AAAAB3NzaC1kc3MAAABBAIbwTOSsZ7Bl7U1KyMNqV13Tu7"
+"yRAtTr70PVI3QnfrPumf2UzCgpL1ljbKxSfAi05XvrE/1vfCFAsFYXRZLhQy0AAAAVAM965Akmo"
+"6eAi7K+k9qDR4TotFAXAAAAQADZlpTW964haQWS4vC063NGdldT6xpUGDcDRqbm90CoPEa2RmNO"
+"uOqi8lnbhYraEzypYH3K4Gzv/bxCBnKtHRUAAABAK+1osyWBS0+P90u/rAuko6chZ98thUSY2kL"
+"SHp6hLKyy2bjnT29h7haELE+XHfq2bM9fckDx2FLOSIJzy83VmQ== comment")
+
+privateDSA_openssh = """-----BEGIN DSA PRIVATE KEY-----
+MIH4AgEAAkEAhvBM5KxnsGXtTUrIw2pXXdO7vJEC1OvvQ9UjdCd+s+6Z/ZTMKCkv
+WWNsrFJ8CLTle+sT/W98IUCwVhdFkuFDLQIVAM965Akmo6eAi7K+k9qDR4TotFAX
+AkAA2ZaU1veuIWkFkuLwtOtzRnZXU+saVBg3A0am5vdAqDxGtkZjTrjqovJZ24WK
+2hM8qWB9yuBs7/28QgZyrR0VAkAr7WizJYFLT4/3S7+sC6SjpyFn3y2FRJjaQtIe
+nqEsrLLZuOdPb2HuFoQsT5cd+rZsz19yQPHYUs5IgnPLzdWZAhUAl1TqdmlAG/b4
+nnVchGiO9sML8MM=
+-----END DSA PRIVATE KEY-----"""
+
+publicDSA_lsh = ("{KDEwOnB1YmxpYy1rZXkoMzpkc2EoMTpwNjU6AIbwTOSsZ7Bl7U1KyMNqV"
+"13Tu7yRAtTr70PVI3QnfrPumf2UzCgpL1ljbKxSfAi05XvrE/1vfCFAsFYXRZLhQy0pKDE6cTIx"
+"OgDPeuQJJqOngIuyvpPag0eE6LRQFykoMTpnNjQ6ANmWlNb3riFpBZLi8LTrc0Z2V1PrGlQYNwN"
+"Gpub3QKg8RrZGY0646qLyWduFitoTPKlgfcrgbO/9vEIGcq0dFSkoMTp5NjQ6K+1osyWBS0+P90"
+"u/rAuko6chZ98thUSY2kLSHp6hLKyy2bjnT29h7haELE+XHfq2bM9fckDx2FLOSIJzy83VmSkpK"
+"Q==}")
+
+privateDSA_lsh = ("(11:private-key(3:dsa(1:p65:\x00\x86\xf0L\xe4\xacg\xb0e"
+"\xedMJ\xc8\xc3jW]\xd3\xbb\xbc\x91\x02\xd4\xeb\xefC\xd5#t'~\xb3\xee\x99\xfd"
+"\x94\xcc()/Ycl\xacR|\x08\xb4\xe5{\xeb\x13\xfdo|!@\xb0V\x17E\x92\xe1C-)(1:q2"
+"1:\x00\xcfz\xe4\t&\xa3\xa7\x80\x8b\xb2\xbe\x93\xda\x83G\x84\xe8\xb4P\x17)(1"
+":g64:\x00\xd9\x96\x94\xd6\xf7\xae!i\x05\x92\xe2\xf0\xb4\xebsFvWS\xeb\x1aT"
+"\x187\x03F\xa6\xe6\xf7@\xa8<F\xb6FcN\xb8\xea\xa2\xf2Y\xdb\x85\x8a\xda\x13<"
+"\xa9`}\xca\xe0l\xef\xfd\xbcB\x06r\xad\x1d\x15)(1:y64:+\xedh\xb3%\x81KO\x8f"
+"\xf7K\xbf\xac\x0b\xa4\xa3\xa7!g\xdf-\x85D\x98\xdaB\xd2\x1e\x9e\xa1,\xac\xb2"
+"\xd9\xb8\xe7Ooa\xee\x16\x84,O\x97\x1d\xfa\xb6l\xcf_r@\xf1\xd8R\xceH\x82s"
+"\xcb\xcd\xd5\x99)(1:x21:\x00\x97T\xeavi@\x1b\xf6\xf8\x9eu\\\x84h\x8e\xf6"
+"\xc3\x0b\xf0\xc3)))")
+
+privateDSA_agentv3 = ("\x00\x00\x00\x07ssh-dss\x00\x00\x00A\x00\x86\xf0L\xe4"
+"\xacg\xb0e\xedMJ\xc8\xc3jW]\xd3\xbb\xbc\x91\x02\xd4\xeb\xefC\xd5#t'~\xb3"
+"\xee\x99\xfd\x94\xcc()/Ycl\xacR|\x08\xb4\xe5{\xeb\x13\xfdo|!@\xb0V\x17E\x92"
+"\xe1C-\x00\x00\x00\x15\x00\xcfz\xe4\t&\xa3\xa7\x80\x8b\xb2\xbe\x93\xda\x83G"
+"\x84\xe8\xb4P\x17\x00\x00\x00@\x00\xd9\x96\x94\xd6\xf7\xae!i\x05\x92\xe2"
+"\xf0\xb4\xebsFvWS\xeb\x1aT\x187\x03F\xa6\xe6\xf7@\xa8<F\xb6FcN\xb8\xea\xa2"
+"\xf2Y\xdb\x85\x8a\xda\x13<\xa9`}\xca\xe0l\xef\xfd\xbcB\x06r\xad\x1d\x15\x00"
+"\x00\x00@+\xedh\xb3%\x81KO\x8f\xf7K\xbf\xac\x0b\xa4\xa3\xa7!g\xdf-\x85D\x98"
+"\xdaB\xd2\x1e\x9e\xa1,\xac\xb2\xd9\xb8\xe7Ooa\xee\x16\x84,O\x97\x1d\xfa\xb6"
+"l\xcf_r@\xf1\xd8R\xceH\x82s\xcb\xcd\xd5\x99\x00\x00\x00\x15\x00\x97T\xeavi@"
+"\x1b\xf6\xf8\x9eu\\\x84h\x8e\xf6\xc3\x0b\xf0\xc3")
+
+__all__ = ['DSAData', 'RSAData', 'privateDSA_agentv3', 'privateDSA_lsh',
+ 'privateDSA_openssh', 'privateRSA_agentv3', 'privateRSA_lsh',
+ 'privateRSA_openssh', 'publicDSA_lsh', 'publicDSA_openssh',
+ 'publicRSA_lsh', 'publicRSA_openssh', 'privateRSA_openssh_alternate']
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_agent.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_agent.py
new file mode 100644
index 0000000000..44b16a8990
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_agent.py
@@ -0,0 +1,399 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.ssh.agent}.
+"""
+
+import struct
+
+from twisted.trial import unittest
+
+try:
+ import OpenSSL
+except ImportError:
+ iosim = None
+else:
+ from twisted.test import iosim
+
+try:
+ import Crypto.Cipher.DES3
+except ImportError:
+ Crypto = None
+
+try:
+ import pyasn1
+except ImportError:
+ pyasn1 = None
+
+if Crypto and pyasn1:
+ from twisted.conch.ssh import keys, agent
+else:
+ keys = agent = None
+
+from twisted.conch.test import keydata
+from twisted.conch.error import ConchError, MissingKeyStoreError
+
+
+class StubFactory(object):
+ """
+ Mock factory that provides the keys attribute required by the
+ SSHAgentServerProtocol
+ """
+ def __init__(self):
+ self.keys = {}
+
+
+
+class AgentTestBase(unittest.TestCase):
+ """
+ Tests for SSHAgentServer/Client.
+ """
+ if iosim is None:
+ skip = "iosim requires SSL, but SSL is not available"
+ elif agent is None or keys is None:
+ skip = "Cannot run without PyCrypto or PyASN1"
+
+ def setUp(self):
+ # wire up our client <-> server
+ self.client, self.server, self.pump = iosim.connectedServerAndClient(
+ agent.SSHAgentServer, agent.SSHAgentClient)
+
+ # the server's end of the protocol is stateful and we store it on the
+ # factory, for which we only need a mock
+ self.server.factory = StubFactory()
+
+ # pub/priv keys of each kind
+ self.rsaPrivate = keys.Key.fromString(keydata.privateRSA_openssh)
+ self.dsaPrivate = keys.Key.fromString(keydata.privateDSA_openssh)
+
+ self.rsaPublic = keys.Key.fromString(keydata.publicRSA_openssh)
+ self.dsaPublic = keys.Key.fromString(keydata.publicDSA_openssh)
+
+
+
+class TestServerProtocolContractWithFactory(AgentTestBase):
+ """
+ The server protocol is stateful and so uses its factory to track state
+ across requests. This test asserts that the protocol raises if its factory
+ doesn't provide the necessary storage for that state.
+ """
+ def test_factorySuppliesKeyStorageForServerProtocol(self):
+ # need a message to send into the server
+ msg = struct.pack('!LB',1, agent.AGENTC_REQUEST_IDENTITIES)
+ del self.server.factory.__dict__['keys']
+ self.assertRaises(MissingKeyStoreError,
+ self.server.dataReceived, msg)
+
+
+
+class TestUnimplementedVersionOneServer(AgentTestBase):
+ """
+ Tests for methods with no-op implementations on the server. We need these
+ for clients, such as openssh, that try v1 methods before going to v2.
+
+ Because the client doesn't expose these operations with nice method names,
+ we invoke sendRequest directly with an op code.
+ """
+
+ def test_agentc_REQUEST_RSA_IDENTITIES(self):
+ """
+ assert that we get the correct op code for an RSA identities request
+ """
+ d = self.client.sendRequest(agent.AGENTC_REQUEST_RSA_IDENTITIES, '')
+ self.pump.flush()
+ def _cb(packet):
+ self.assertEqual(
+ agent.AGENT_RSA_IDENTITIES_ANSWER, ord(packet[0]))
+ return d.addCallback(_cb)
+
+
+ def test_agentc_REMOVE_RSA_IDENTITY(self):
+ """
+ assert that we get the correct op code for an RSA remove identity request
+ """
+ d = self.client.sendRequest(agent.AGENTC_REMOVE_RSA_IDENTITY, '')
+ self.pump.flush()
+ return d.addCallback(self.assertEqual, '')
+
+
+ def test_agentc_REMOVE_ALL_RSA_IDENTITIES(self):
+ """
+ assert that we get the correct op code for an RSA remove all identities
+ request.
+ """
+ d = self.client.sendRequest(agent.AGENTC_REMOVE_ALL_RSA_IDENTITIES, '')
+ self.pump.flush()
+ return d.addCallback(self.assertEqual, '')
+
+
+
+if agent is not None:
+ class CorruptServer(agent.SSHAgentServer):
+ """
+ A misbehaving server that returns bogus response op codes so that we can
+ verify that our callbacks that deal with these op codes handle such
+ miscreants.
+ """
+ def agentc_REQUEST_IDENTITIES(self, data):
+ self.sendResponse(254, '')
+
+
+ def agentc_SIGN_REQUEST(self, data):
+ self.sendResponse(254, '')
+
+
+
+class TestClientWithBrokenServer(AgentTestBase):
+ """
+ verify error handling code in the client using a misbehaving server
+ """
+
+ def setUp(self):
+ AgentTestBase.setUp(self)
+ self.client, self.server, self.pump = iosim.connectedServerAndClient(
+ CorruptServer, agent.SSHAgentClient)
+ # the server's end of the protocol is stateful and we store it on the
+ # factory, for which we only need a mock
+ self.server.factory = StubFactory()
+
+
+ def test_signDataCallbackErrorHandling(self):
+ """
+ Assert that L{SSHAgentClient.signData} raises a ConchError
+ if we get a response from the server whose opcode doesn't match
+ the protocol for data signing requests.
+ """
+ d = self.client.signData(self.rsaPublic.blob(), "John Hancock")
+ self.pump.flush()
+ return self.assertFailure(d, ConchError)
+
+
+ def test_requestIdentitiesCallbackErrorHandling(self):
+ """
+ Assert that L{SSHAgentClient.requestIdentities} raises a ConchError
+ if we get a response from the server whose opcode doesn't match
+ the protocol for identity requests.
+ """
+ d = self.client.requestIdentities()
+ self.pump.flush()
+ return self.assertFailure(d, ConchError)
+
+
+
+class TestAgentKeyAddition(AgentTestBase):
+ """
+ Test adding different flavors of keys to an agent.
+ """
+
+ def test_addRSAIdentityNoComment(self):
+ """
+ L{SSHAgentClient.addIdentity} adds the private key it is called
+ with to the SSH agent server to which it is connected, associating
+ it with the comment it is called with.
+
+ This test asserts that ommitting the comment produces an
+ empty string for the comment on the server.
+ """
+ d = self.client.addIdentity(self.rsaPrivate.privateBlob())
+ self.pump.flush()
+ def _check(ignored):
+ serverKey = self.server.factory.keys[self.rsaPrivate.blob()]
+ self.assertEqual(self.rsaPrivate, serverKey[0])
+ self.assertEqual('', serverKey[1])
+ return d.addCallback(_check)
+
+
+ def test_addDSAIdentityNoComment(self):
+ """
+ L{SSHAgentClient.addIdentity} adds the private key it is called
+ with to the SSH agent server to which it is connected, associating
+ it with the comment it is called with.
+
+ This test asserts that ommitting the comment produces an
+ empty string for the comment on the server.
+ """
+ d = self.client.addIdentity(self.dsaPrivate.privateBlob())
+ self.pump.flush()
+ def _check(ignored):
+ serverKey = self.server.factory.keys[self.dsaPrivate.blob()]
+ self.assertEqual(self.dsaPrivate, serverKey[0])
+ self.assertEqual('', serverKey[1])
+ return d.addCallback(_check)
+
+
+ def test_addRSAIdentityWithComment(self):
+ """
+ L{SSHAgentClient.addIdentity} adds the private key it is called
+ with to the SSH agent server to which it is connected, associating
+ it with the comment it is called with.
+
+ This test asserts that the server receives/stores the comment
+ as sent by the client.
+ """
+ d = self.client.addIdentity(
+ self.rsaPrivate.privateBlob(), comment='My special key')
+ self.pump.flush()
+ def _check(ignored):
+ serverKey = self.server.factory.keys[self.rsaPrivate.blob()]
+ self.assertEqual(self.rsaPrivate, serverKey[0])
+ self.assertEqual('My special key', serverKey[1])
+ return d.addCallback(_check)
+
+
+ def test_addDSAIdentityWithComment(self):
+ """
+ L{SSHAgentClient.addIdentity} adds the private key it is called
+ with to the SSH agent server to which it is connected, associating
+ it with the comment it is called with.
+
+ This test asserts that the server receives/stores the comment
+ as sent by the client.
+ """
+ d = self.client.addIdentity(
+ self.dsaPrivate.privateBlob(), comment='My special key')
+ self.pump.flush()
+ def _check(ignored):
+ serverKey = self.server.factory.keys[self.dsaPrivate.blob()]
+ self.assertEqual(self.dsaPrivate, serverKey[0])
+ self.assertEqual('My special key', serverKey[1])
+ return d.addCallback(_check)
+
+
+
+class TestAgentClientFailure(AgentTestBase):
+ def test_agentFailure(self):
+ """
+ verify that the client raises ConchError on AGENT_FAILURE
+ """
+ d = self.client.sendRequest(254, '')
+ self.pump.flush()
+ return self.assertFailure(d, ConchError)
+
+
+
+class TestAgentIdentityRequests(AgentTestBase):
+ """
+ Test operations against a server with identities already loaded.
+ """
+
+ def setUp(self):
+ AgentTestBase.setUp(self)
+ self.server.factory.keys[self.dsaPrivate.blob()] = (
+ self.dsaPrivate, 'a comment')
+ self.server.factory.keys[self.rsaPrivate.blob()] = (
+ self.rsaPrivate, 'another comment')
+
+
+ def test_signDataRSA(self):
+ """
+ Sign data with an RSA private key and then verify it with the public
+ key.
+ """
+ d = self.client.signData(self.rsaPublic.blob(), "John Hancock")
+ self.pump.flush()
+ def _check(sig):
+ expected = self.rsaPrivate.sign("John Hancock")
+ self.assertEqual(expected, sig)
+ self.assertTrue(self.rsaPublic.verify(sig, "John Hancock"))
+ return d.addCallback(_check)
+
+
+ def test_signDataDSA(self):
+ """
+ Sign data with a DSA private key and then verify it with the public
+ key.
+ """
+ d = self.client.signData(self.dsaPublic.blob(), "John Hancock")
+ self.pump.flush()
+ def _check(sig):
+ # Cannot do this b/c DSA uses random numbers when signing
+ # expected = self.dsaPrivate.sign("John Hancock")
+ # self.assertEquals(expected, sig)
+ self.assertTrue(self.dsaPublic.verify(sig, "John Hancock"))
+ return d.addCallback(_check)
+
+
+ def test_signDataRSAErrbackOnUnknownBlob(self):
+ """
+ Assert that we get an errback if we try to sign data using a key that
+ wasn't added.
+ """
+ del self.server.factory.keys[self.rsaPublic.blob()]
+ d = self.client.signData(self.rsaPublic.blob(), "John Hancock")
+ self.pump.flush()
+ return self.assertFailure(d, ConchError)
+
+
+ def test_requestIdentities(self):
+ """
+ Assert that we get all of the keys/comments that we add when we issue a
+ request for all identities.
+ """
+ d = self.client.requestIdentities()
+ self.pump.flush()
+ def _check(keyt):
+ expected = {}
+ expected[self.dsaPublic.blob()] = 'a comment'
+ expected[self.rsaPublic.blob()] = 'another comment'
+
+ received = {}
+ for k in keyt:
+ received[keys.Key.fromString(k[0], type='blob').blob()] = k[1]
+ self.assertEquals(expected, received)
+ return d.addCallback(_check)
+
+
+
+class TestAgentKeyRemoval(AgentTestBase):
+ """
+ Test support for removing keys in a remote server.
+ """
+
+ def setUp(self):
+ AgentTestBase.setUp(self)
+ self.server.factory.keys[self.dsaPrivate.blob()] = (
+ self.dsaPrivate, 'a comment')
+ self.server.factory.keys[self.rsaPrivate.blob()] = (
+ self.rsaPrivate, 'another comment')
+
+
+ def test_removeRSAIdentity(self):
+ """
+ Assert that we can remove an RSA identity.
+ """
+ # only need public key for this
+ d = self.client.removeIdentity(self.rsaPrivate.blob())
+ self.pump.flush()
+
+ def _check(ignored):
+ self.assertEqual(1, len(self.server.factory.keys))
+ self.assertIn(self.dsaPrivate.blob(), self.server.factory.keys)
+ self.assertNotIn(self.rsaPrivate.blob(), self.server.factory.keys)
+ return d.addCallback(_check)
+
+
+ def test_removeDSAIdentity(self):
+ """
+ Assert that we can remove a DSA identity.
+ """
+ # only need public key for this
+ d = self.client.removeIdentity(self.dsaPrivate.blob())
+ self.pump.flush()
+
+ def _check(ignored):
+ self.assertEqual(1, len(self.server.factory.keys))
+ self.assertIn(self.rsaPrivate.blob(), self.server.factory.keys)
+ return d.addCallback(_check)
+
+
+ def test_removeAllIdentities(self):
+ """
+ Assert that we can remove all identities.
+ """
+ d = self.client.removeAllIdentities()
+ self.pump.flush()
+
+ def _check(ignored):
+ self.assertEquals(0, len(self.server.factory.keys))
+ return d.addCallback(_check)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_cftp.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_cftp.py
new file mode 100644
index 0000000000..7303ca3c52
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_cftp.py
@@ -0,0 +1,881 @@
+# -*- test-case-name: twisted.conch.test.test_cftp -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE file for details.
+
+"""
+Tests for L{twisted.conch.scripts.cftp}.
+"""
+
+import time, sys, os, operator, getpass
+
+from twisted.conch.test.test_ssh import Crypto, pyasn1
+
+_reason = None
+if Crypto and pyasn1:
+ try:
+ from twisted.conch import unix
+ from twisted.conch.scripts import cftp
+ from twisted.conch.test.test_filetransfer import FileTransferForTestAvatar
+ except ImportError, e:
+ # Python 2.3 compatibility fix
+ sys.modules.pop("twisted.conch.unix", None)
+ unix = None
+ _reason = str(e)
+ del e
+else:
+ unix = None
+
+
+from twisted.python.fakepwd import UserDatabase
+from twisted.trial.unittest import TestCase
+from twisted.cred import portal
+from twisted.internet import reactor, protocol, interfaces, defer, error
+from twisted.internet.utils import getProcessOutputAndValue
+from twisted.python import log
+from twisted.conch import ls
+from twisted.test.proto_helpers import StringTransport
+
+from twisted.conch.test import test_ssh, test_conch
+from twisted.conch.test.test_filetransfer import SFTPTestBase
+from twisted.conch.test.test_filetransfer import FileTransferTestAvatar
+
+
+
+class ListingTests(TestCase):
+ """
+ Tests for L{lsLine}, the function which generates an entry for a file or
+ directory in an SFTP I{ls} command's output.
+ """
+ if getattr(time, 'tzset', None) is None:
+ skip = "Cannot test timestamp formatting code without time.tzset"
+
+ def setUp(self):
+ """
+ Patch the L{ls} module's time function so the results of L{lsLine} are
+ deterministic.
+ """
+ self.now = 123456789
+ def fakeTime():
+ return self.now
+ self.patch(ls, 'time', fakeTime)
+
+ # Make sure that the timezone ends up the same after these tests as
+ # it was before.
+ if 'TZ' in os.environ:
+ self.addCleanup(operator.setitem, os.environ, 'TZ', os.environ['TZ'])
+ self.addCleanup(time.tzset)
+ else:
+ def cleanup():
+ # os.environ.pop is broken! Don't use it! Ever! Or die!
+ try:
+ del os.environ['TZ']
+ except KeyError:
+ pass
+ time.tzset()
+ self.addCleanup(cleanup)
+
+
+ def _lsInTimezone(self, timezone, stat):
+ """
+ Call L{ls.lsLine} after setting the timezone to C{timezone} and return
+ the result.
+ """
+ # Set the timezone to a well-known value so the timestamps are
+ # predictable.
+ os.environ['TZ'] = timezone
+ time.tzset()
+ return ls.lsLine('foo', stat)
+
+
+ def test_oldFile(self):
+ """
+ A file with an mtime six months (approximately) or more in the past has
+ a listing including a low-resolution timestamp.
+ """
+ # Go with 7 months. That's more than 6 months.
+ then = self.now - (60 * 60 * 24 * 31 * 7)
+ stat = os.stat_result((0, 0, 0, 0, 0, 0, 0, 0, then, 0))
+
+ self.assertEqual(
+ self._lsInTimezone('America/New_York', stat),
+ '!--------- 0 0 0 0 Apr 26 1973 foo')
+ self.assertEqual(
+ self._lsInTimezone('Pacific/Auckland', stat),
+ '!--------- 0 0 0 0 Apr 27 1973 foo')
+
+
+ def test_oldSingleDigitDayOfMonth(self):
+ """
+ A file with a high-resolution timestamp which falls on a day of the
+ month which can be represented by one decimal digit is formatted with
+ one padding 0 to preserve the columns which come after it.
+ """
+ # A point about 7 months in the past, tweaked to fall on the first of a
+ # month so we test the case we want to test.
+ then = self.now - (60 * 60 * 24 * 31 * 7) + (60 * 60 * 24 * 5)
+ stat = os.stat_result((0, 0, 0, 0, 0, 0, 0, 0, then, 0))
+
+ self.assertEqual(
+ self._lsInTimezone('America/New_York', stat),
+ '!--------- 0 0 0 0 May 01 1973 foo')
+ self.assertEqual(
+ self._lsInTimezone('Pacific/Auckland', stat),
+ '!--------- 0 0 0 0 May 02 1973 foo')
+
+
+ def test_newFile(self):
+ """
+ A file with an mtime fewer than six months (approximately) in the past
+ has a listing including a high-resolution timestamp excluding the year.
+ """
+ # A point about three months in the past.
+ then = self.now - (60 * 60 * 24 * 31 * 3)
+ stat = os.stat_result((0, 0, 0, 0, 0, 0, 0, 0, then, 0))
+
+ self.assertEqual(
+ self._lsInTimezone('America/New_York', stat),
+ '!--------- 0 0 0 0 Aug 28 17:33 foo')
+ self.assertEqual(
+ self._lsInTimezone('Pacific/Auckland', stat),
+ '!--------- 0 0 0 0 Aug 29 09:33 foo')
+
+
+ def test_newSingleDigitDayOfMonth(self):
+ """
+ A file with a high-resolution timestamp which falls on a day of the
+ month which can be represented by one decimal digit is formatted with
+ one padding 0 to preserve the columns which come after it.
+ """
+ # A point about three months in the past, tweaked to fall on the first
+ # of a month so we test the case we want to test.
+ then = self.now - (60 * 60 * 24 * 31 * 3) + (60 * 60 * 24 * 4)
+ stat = os.stat_result((0, 0, 0, 0, 0, 0, 0, 0, then, 0))
+
+ self.assertEqual(
+ self._lsInTimezone('America/New_York', stat),
+ '!--------- 0 0 0 0 Sep 01 17:33 foo')
+ self.assertEqual(
+ self._lsInTimezone('Pacific/Auckland', stat),
+ '!--------- 0 0 0 0 Sep 02 09:33 foo')
+
+
+
+class StdioClientTests(TestCase):
+ """
+ Tests for L{cftp.StdioClient}.
+ """
+ def setUp(self):
+ """
+ Create a L{cftp.StdioClient} hooked up to dummy transport and a fake
+ user database.
+ """
+ class Connection:
+ pass
+
+ conn = Connection()
+ conn.transport = StringTransport()
+ conn.transport.localClosed = False
+
+ self.client = cftp.StdioClient(conn)
+ self.database = self.client._pwd = UserDatabase()
+
+ # Intentionally bypassing makeConnection - that triggers some code
+ # which uses features not provided by our dumb Connection fake.
+ self.client.transport = StringTransport()
+
+
+ def test_exec(self):
+ """
+ The I{exec} command runs its arguments locally in a child process
+ using the user's shell.
+ """
+ self.database.addUser(
+ getpass.getuser(), 'secret', os.getuid(), 1234, 'foo', 'bar',
+ sys.executable)
+
+ d = self.client._dispatchCommand("exec print 1 + 2")
+ d.addCallback(self.assertEquals, "3\n")
+ return d
+
+
+ def test_execWithoutShell(self):
+ """
+ If the local user has no shell, the I{exec} command runs its arguments
+ using I{/bin/sh}.
+ """
+ self.database.addUser(
+ getpass.getuser(), 'secret', os.getuid(), 1234, 'foo', 'bar', '')
+
+ d = self.client._dispatchCommand("exec echo hello")
+ d.addCallback(self.assertEquals, "hello\n")
+ return d
+
+
+ def test_bang(self):
+ """
+ The I{exec} command is run for lines which start with C{"!"}.
+ """
+ self.database.addUser(
+ getpass.getuser(), 'secret', os.getuid(), 1234, 'foo', 'bar',
+ '/bin/sh')
+
+ d = self.client._dispatchCommand("!echo hello")
+ d.addCallback(self.assertEquals, "hello\n")
+ return d
+
+
+
+class FileTransferTestRealm:
+ def __init__(self, testDir):
+ self.testDir = testDir
+
+ def requestAvatar(self, avatarID, mind, *interfaces):
+ a = FileTransferTestAvatar(self.testDir)
+ return interfaces[0], a, lambda: None
+
+
+class SFTPTestProcess(protocol.ProcessProtocol):
+ """
+ Protocol for testing cftp. Provides an interface between Python (where all
+ the tests are) and the cftp client process (which does the work that is
+ being tested).
+ """
+
+ def __init__(self, onOutReceived):
+ """
+ @param onOutReceived: A L{Deferred} to be fired as soon as data is
+ received from stdout.
+ """
+ self.clearBuffer()
+ self.onOutReceived = onOutReceived
+ self.onProcessEnd = None
+ self._expectingCommand = None
+ self._processEnded = False
+
+ def clearBuffer(self):
+ """
+ Clear any buffered data received from stdout. Should be private.
+ """
+ self.buffer = ''
+ self._linesReceived = []
+ self._lineBuffer = ''
+
+ def outReceived(self, data):
+ """
+ Called by Twisted when the cftp client prints data to stdout.
+ """
+ log.msg('got %s' % data)
+ lines = (self._lineBuffer + data).split('\n')
+ self._lineBuffer = lines.pop(-1)
+ self._linesReceived.extend(lines)
+ # XXX - not strictly correct.
+ # We really want onOutReceived to fire after the first 'cftp>' prompt
+ # has been received. (See use in TestOurServerCmdLineClient.setUp)
+ if self.onOutReceived is not None:
+ d, self.onOutReceived = self.onOutReceived, None
+ d.callback(data)
+ self.buffer += data
+ self._checkForCommand()
+
+ def _checkForCommand(self):
+ prompt = 'cftp> '
+ if self._expectingCommand and self._lineBuffer == prompt:
+ buf = '\n'.join(self._linesReceived)
+ if buf.startswith(prompt):
+ buf = buf[len(prompt):]
+ self.clearBuffer()
+ d, self._expectingCommand = self._expectingCommand, None
+ d.callback(buf)
+
+ def errReceived(self, data):
+ """
+ Called by Twisted when the cftp client prints data to stderr.
+ """
+ log.msg('err: %s' % data)
+
+ def getBuffer(self):
+ """
+ Return the contents of the buffer of data received from stdout.
+ """
+ return self.buffer
+
+ def runCommand(self, command):
+ """
+ Issue the given command via the cftp client. Return a C{Deferred} that
+ fires when the server returns a result. Note that the C{Deferred} will
+ callback even if the server returns some kind of error.
+
+ @param command: A string containing an sftp command.
+
+ @return: A C{Deferred} that fires when the sftp server returns a
+ result. The payload is the server's response string.
+ """
+ self._expectingCommand = defer.Deferred()
+ self.clearBuffer()
+ self.transport.write(command + '\n')
+ return self._expectingCommand
+
+ def runScript(self, commands):
+ """
+ Run each command in sequence and return a Deferred that fires when all
+ commands are completed.
+
+ @param commands: A list of strings containing sftp commands.
+
+ @return: A C{Deferred} that fires when all commands are completed. The
+ payload is a list of response strings from the server, in the same
+ order as the commands.
+ """
+ sem = defer.DeferredSemaphore(1)
+ dl = [sem.run(self.runCommand, command) for command in commands]
+ return defer.gatherResults(dl)
+
+ def killProcess(self):
+ """
+ Kill the process if it is still running.
+
+ If the process is still running, sends a KILL signal to the transport
+ and returns a C{Deferred} which fires when L{processEnded} is called.
+
+ @return: a C{Deferred}.
+ """
+ if self._processEnded:
+ return defer.succeed(None)
+ self.onProcessEnd = defer.Deferred()
+ self.transport.signalProcess('KILL')
+ return self.onProcessEnd
+
+ def processEnded(self, reason):
+ """
+ Called by Twisted when the cftp client process ends.
+ """
+ self._processEnded = True
+ if self.onProcessEnd:
+ d, self.onProcessEnd = self.onProcessEnd, None
+ d.callback(None)
+
+
+class CFTPClientTestBase(SFTPTestBase):
+ def setUp(self):
+ f = open('dsa_test.pub','w')
+ f.write(test_ssh.publicDSA_openssh)
+ f.close()
+ f = open('dsa_test','w')
+ f.write(test_ssh.privateDSA_openssh)
+ f.close()
+ os.chmod('dsa_test', 33152)
+ f = open('kh_test','w')
+ f.write('127.0.0.1 ' + test_ssh.publicRSA_openssh)
+ f.close()
+ return SFTPTestBase.setUp(self)
+
+ def startServer(self):
+ realm = FileTransferTestRealm(self.testDir)
+ p = portal.Portal(realm)
+ p.registerChecker(test_ssh.ConchTestPublicKeyChecker())
+ fac = test_ssh.ConchTestServerFactory()
+ fac.portal = p
+ self.server = reactor.listenTCP(0, fac, interface="127.0.0.1")
+
+ def stopServer(self):
+ if not hasattr(self.server.factory, 'proto'):
+ return self._cbStopServer(None)
+ self.server.factory.proto.expectedLoseConnection = 1
+ d = defer.maybeDeferred(
+ self.server.factory.proto.transport.loseConnection)
+ d.addCallback(self._cbStopServer)
+ return d
+
+ def _cbStopServer(self, ignored):
+ return defer.maybeDeferred(self.server.stopListening)
+
+ def tearDown(self):
+ for f in ['dsa_test.pub', 'dsa_test', 'kh_test']:
+ try:
+ os.remove(f)
+ except:
+ pass
+ return SFTPTestBase.tearDown(self)
+
+
+
+class TestOurServerCmdLineClient(CFTPClientTestBase):
+
+ def setUp(self):
+ CFTPClientTestBase.setUp(self)
+
+ self.startServer()
+ cmds = ('-p %i -l testuser '
+ '--known-hosts kh_test '
+ '--user-authentications publickey '
+ '--host-key-algorithms ssh-rsa '
+ '-i dsa_test '
+ '-a '
+ '-v '
+ '127.0.0.1')
+ port = self.server.getHost().port
+ cmds = test_conch._makeArgs((cmds % port).split(), mod='cftp')
+ log.msg('running %s %s' % (sys.executable, cmds))
+ d = defer.Deferred()
+ self.processProtocol = SFTPTestProcess(d)
+ d.addCallback(lambda _: self.processProtocol.clearBuffer())
+ env = os.environ.copy()
+ env['PYTHONPATH'] = os.pathsep.join(sys.path)
+ reactor.spawnProcess(self.processProtocol, sys.executable, cmds,
+ env=env)
+ return d
+
+ def tearDown(self):
+ d = self.stopServer()
+ d.addCallback(lambda _: self.processProtocol.killProcess())
+ return d
+
+ def _killProcess(self, ignored):
+ try:
+ self.processProtocol.transport.signalProcess('KILL')
+ except error.ProcessExitedAlready:
+ pass
+
+ def runCommand(self, command):
+ """
+ Run the given command with the cftp client. Return a C{Deferred} that
+ fires when the command is complete. Payload is the server's output for
+ that command.
+ """
+ return self.processProtocol.runCommand(command)
+
+ def runScript(self, *commands):
+ """
+ Run the given commands with the cftp client. Returns a C{Deferred}
+ that fires when the commands are all complete. The C{Deferred}'s
+ payload is a list of output for each command.
+ """
+ return self.processProtocol.runScript(commands)
+
+ def testCdPwd(self):
+ """
+ Test that 'pwd' reports the current remote directory, that 'lpwd'
+ reports the current local directory, and that changing to a
+ subdirectory then changing to its parent leaves you in the original
+ remote directory.
+ """
+ # XXX - not actually a unit test, see docstring.
+ homeDir = os.path.join(os.getcwd(), self.testDir)
+ d = self.runScript('pwd', 'lpwd', 'cd testDirectory', 'cd ..', 'pwd')
+ d.addCallback(lambda xs: xs[:3] + xs[4:])
+ d.addCallback(self.assertEqual,
+ [homeDir, os.getcwd(), '', homeDir])
+ return d
+
+ def testChAttrs(self):
+ """
+ Check that 'ls -l' output includes the access permissions and that
+ this output changes appropriately with 'chmod'.
+ """
+ def _check(results):
+ self.flushLoggedErrors()
+ self.assertTrue(results[0].startswith('-rw-r--r--'))
+ self.assertEqual(results[1], '')
+ self.assertTrue(results[2].startswith('----------'), results[2])
+ self.assertEqual(results[3], '')
+
+ d = self.runScript('ls -l testfile1', 'chmod 0 testfile1',
+ 'ls -l testfile1', 'chmod 644 testfile1')
+ return d.addCallback(_check)
+ # XXX test chgrp/own
+
+
+ def testList(self):
+ """
+ Check 'ls' works as expected. Checks for wildcards, hidden files,
+ listing directories and listing empty directories.
+ """
+ def _check(results):
+ self.assertEqual(results[0], ['testDirectory', 'testRemoveFile',
+ 'testRenameFile', 'testfile1'])
+ self.assertEqual(results[1], ['testDirectory', 'testRemoveFile',
+ 'testRenameFile', 'testfile1'])
+ self.assertEqual(results[2], ['testRemoveFile', 'testRenameFile'])
+ self.assertEqual(results[3], ['.testHiddenFile', 'testRemoveFile',
+ 'testRenameFile'])
+ self.assertEqual(results[4], [''])
+ d = self.runScript('ls', 'ls ../' + os.path.basename(self.testDir),
+ 'ls *File', 'ls -a *File', 'ls -l testDirectory')
+ d.addCallback(lambda xs: [x.split('\n') for x in xs])
+ return d.addCallback(_check)
+
+
+ def testHelp(self):
+ """
+ Check that running the '?' command returns help.
+ """
+ d = self.runCommand('?')
+ d.addCallback(self.assertEqual,
+ cftp.StdioClient(None).cmd_HELP('').strip())
+ return d
+
+ def assertFilesEqual(self, name1, name2, msg=None):
+ """
+ Assert that the files at C{name1} and C{name2} contain exactly the
+ same data.
+ """
+ f1 = file(name1).read()
+ f2 = file(name2).read()
+ self.failUnlessEqual(f1, f2, msg)
+
+
+ def testGet(self):
+ """
+ Test that 'get' saves the remote file to the correct local location,
+ that the output of 'get' is correct and that 'rm' actually removes
+ the file.
+ """
+ # XXX - not actually a unit test
+ expectedOutput = ("Transferred %s/%s/testfile1 to %s/test file2"
+ % (os.getcwd(), self.testDir, self.testDir))
+ def _checkGet(result):
+ self.assertTrue(result.endswith(expectedOutput))
+ self.assertFilesEqual(self.testDir + '/testfile1',
+ self.testDir + '/test file2',
+ "get failed")
+ return self.runCommand('rm "test file2"')
+
+ d = self.runCommand('get testfile1 "%s/test file2"' % (self.testDir,))
+ d.addCallback(_checkGet)
+ d.addCallback(lambda _: self.failIf(
+ os.path.exists(self.testDir + '/test file2')))
+ return d
+
+
+ def testWildcardGet(self):
+ """
+ Test that 'get' works correctly when given wildcard parameters.
+ """
+ def _check(ignored):
+ self.assertFilesEqual(self.testDir + '/testRemoveFile',
+ 'testRemoveFile',
+ 'testRemoveFile get failed')
+ self.assertFilesEqual(self.testDir + '/testRenameFile',
+ 'testRenameFile',
+ 'testRenameFile get failed')
+
+ d = self.runCommand('get testR*')
+ return d.addCallback(_check)
+
+
+ def testPut(self):
+ """
+ Check that 'put' uploads files correctly and that they can be
+ successfully removed. Also check the output of the put command.
+ """
+ # XXX - not actually a unit test
+ expectedOutput = ('Transferred %s/testfile1 to %s/%s/test"file2'
+ % (self.testDir, os.getcwd(), self.testDir))
+ def _checkPut(result):
+ self.assertFilesEqual(self.testDir + '/testfile1',
+ self.testDir + '/test"file2')
+ self.failUnless(result.endswith(expectedOutput))
+ return self.runCommand('rm "test\\"file2"')
+
+ d = self.runCommand('put %s/testfile1 "test\\"file2"'
+ % (self.testDir,))
+ d.addCallback(_checkPut)
+ d.addCallback(lambda _: self.failIf(
+ os.path.exists(self.testDir + '/test"file2')))
+ return d
+
+
+ def test_putOverLongerFile(self):
+ """
+ Check that 'put' uploads files correctly when overwriting a longer
+ file.
+ """
+ # XXX - not actually a unit test
+ f = file(os.path.join(self.testDir, 'shorterFile'), 'w')
+ f.write("a")
+ f.close()
+ f = file(os.path.join(self.testDir, 'longerFile'), 'w')
+ f.write("bb")
+ f.close()
+ def _checkPut(result):
+ self.assertFilesEqual(self.testDir + '/shorterFile',
+ self.testDir + '/longerFile')
+
+ d = self.runCommand('put %s/shorterFile longerFile'
+ % (self.testDir,))
+ d.addCallback(_checkPut)
+ return d
+
+
+ def test_putMultipleOverLongerFile(self):
+ """
+ Check that 'put' uploads files correctly when overwriting a longer
+ file and you use a wildcard to specify the files to upload.
+ """
+ # XXX - not actually a unit test
+ os.mkdir(os.path.join(self.testDir, 'dir'))
+ f = file(os.path.join(self.testDir, 'dir', 'file'), 'w')
+ f.write("a")
+ f.close()
+ f = file(os.path.join(self.testDir, 'file'), 'w')
+ f.write("bb")
+ f.close()
+ def _checkPut(result):
+ self.assertFilesEqual(self.testDir + '/dir/file',
+ self.testDir + '/file')
+
+ d = self.runCommand('put %s/dir/*'
+ % (self.testDir,))
+ d.addCallback(_checkPut)
+ return d
+
+
+ def testWildcardPut(self):
+ """
+ What happens if you issue a 'put' command and include a wildcard (i.e.
+ '*') in parameter? Check that all files matching the wildcard are
+ uploaded to the correct directory.
+ """
+ def check(results):
+ self.assertEqual(results[0], '')
+ self.assertEqual(results[2], '')
+ self.assertFilesEqual(self.testDir + '/testRemoveFile',
+ self.testDir + '/../testRemoveFile',
+ 'testRemoveFile get failed')
+ self.assertFilesEqual(self.testDir + '/testRenameFile',
+ self.testDir + '/../testRenameFile',
+ 'testRenameFile get failed')
+
+ d = self.runScript('cd ..',
+ 'put %s/testR*' % (self.testDir,),
+ 'cd %s' % os.path.basename(self.testDir))
+ d.addCallback(check)
+ return d
+
+
+ def testLink(self):
+ """
+ Test that 'ln' creates a file which appears as a link in the output of
+ 'ls'. Check that removing the new file succeeds without output.
+ """
+ def _check(results):
+ self.flushLoggedErrors()
+ self.assertEqual(results[0], '')
+ self.assertTrue(results[1].startswith('l'), 'link failed')
+ return self.runCommand('rm testLink')
+
+ d = self.runScript('ln testLink testfile1', 'ls -l testLink')
+ d.addCallback(_check)
+ d.addCallback(self.assertEqual, '')
+ return d
+
+
+ def testRemoteDirectory(self):
+ """
+ Test that we can create and remove directories with the cftp client.
+ """
+ def _check(results):
+ self.assertEqual(results[0], '')
+ self.assertTrue(results[1].startswith('d'))
+ return self.runCommand('rmdir testMakeDirectory')
+
+ d = self.runScript('mkdir testMakeDirectory',
+ 'ls -l testMakeDirector?')
+ d.addCallback(_check)
+ d.addCallback(self.assertEqual, '')
+ return d
+
+
+ def test_existingRemoteDirectory(self):
+ """
+ Test that a C{mkdir} on an existing directory fails with the
+ appropriate error, and doesn't log an useless error server side.
+ """
+ def _check(results):
+ self.assertEquals(results[0], '')
+ self.assertEquals(results[1],
+ 'remote error 11: mkdir failed')
+
+ d = self.runScript('mkdir testMakeDirectory',
+ 'mkdir testMakeDirectory')
+ d.addCallback(_check)
+ return d
+
+
+ def testLocalDirectory(self):
+ """
+ Test that we can create a directory locally and remove it with the
+ cftp client. This test works because the 'remote' server is running
+ out of a local directory.
+ """
+ d = self.runCommand('lmkdir %s/testLocalDirectory' % (self.testDir,))
+ d.addCallback(self.assertEqual, '')
+ d.addCallback(lambda _: self.runCommand('rmdir testLocalDirectory'))
+ d.addCallback(self.assertEqual, '')
+ return d
+
+
+ def testRename(self):
+ """
+ Test that we can rename a file.
+ """
+ def _check(results):
+ self.assertEqual(results[0], '')
+ self.assertEqual(results[1], 'testfile2')
+ return self.runCommand('rename testfile2 testfile1')
+
+ d = self.runScript('rename testfile1 testfile2', 'ls testfile?')
+ d.addCallback(_check)
+ d.addCallback(self.assertEqual, '')
+ return d
+
+
+
+class TestOurServerBatchFile(CFTPClientTestBase):
+ def setUp(self):
+ CFTPClientTestBase.setUp(self)
+ self.startServer()
+
+ def tearDown(self):
+ CFTPClientTestBase.tearDown(self)
+ return self.stopServer()
+
+ def _getBatchOutput(self, f):
+ fn = self.mktemp()
+ open(fn, 'w').write(f)
+ port = self.server.getHost().port
+ cmds = ('-p %i -l testuser '
+ '--known-hosts kh_test '
+ '--user-authentications publickey '
+ '--host-key-algorithms ssh-rsa '
+ '-i dsa_test '
+ '-a '
+ '-v -b %s 127.0.0.1') % (port, fn)
+ cmds = test_conch._makeArgs(cmds.split(), mod='cftp')[1:]
+ log.msg('running %s %s' % (sys.executable, cmds))
+ env = os.environ.copy()
+ env['PYTHONPATH'] = os.pathsep.join(sys.path)
+
+ self.server.factory.expectedLoseConnection = 1
+
+ d = getProcessOutputAndValue(sys.executable, cmds, env=env)
+
+ def _cleanup(res):
+ os.remove(fn)
+ return res
+
+ d.addCallback(lambda res: res[0])
+ d.addBoth(_cleanup)
+
+ return d
+
+ def testBatchFile(self):
+ """Test whether batch file function of cftp ('cftp -b batchfile').
+ This works by treating the file as a list of commands to be run.
+ """
+ cmds = """pwd
+ls
+exit
+"""
+ def _cbCheckResult(res):
+ res = res.split('\n')
+ log.msg('RES %s' % str(res))
+ self.failUnless(res[1].find(self.testDir) != -1, repr(res))
+ self.failUnlessEqual(res[3:-2], ['testDirectory', 'testRemoveFile',
+ 'testRenameFile', 'testfile1'])
+
+ d = self._getBatchOutput(cmds)
+ d.addCallback(_cbCheckResult)
+ return d
+
+ def testError(self):
+ """Test that an error in the batch file stops running the batch.
+ """
+ cmds = """chown 0 missingFile
+pwd
+exit
+"""
+ def _cbCheckResult(res):
+ self.failIf(res.find(self.testDir) != -1)
+
+ d = self._getBatchOutput(cmds)
+ d.addCallback(_cbCheckResult)
+ return d
+
+ def testIgnoredError(self):
+ """Test that a minus sign '-' at the front of a line ignores
+ any errors.
+ """
+ cmds = """-chown 0 missingFile
+pwd
+exit
+"""
+ def _cbCheckResult(res):
+ self.failIf(res.find(self.testDir) == -1)
+
+ d = self._getBatchOutput(cmds)
+ d.addCallback(_cbCheckResult)
+ return d
+
+
+
+class TestOurServerSftpClient(CFTPClientTestBase):
+ """
+ Test the sftp server against sftp command line client.
+ """
+
+ def setUp(self):
+ CFTPClientTestBase.setUp(self)
+ return self.startServer()
+
+
+ def tearDown(self):
+ return self.stopServer()
+
+
+ def test_extendedAttributes(self):
+ """
+ Test the return of extended attributes by the server: the sftp client
+ should ignore them, but still be able to parse the response correctly.
+
+ This test is mainly here to check that
+ L{filetransfer.FILEXFER_ATTR_EXTENDED} has the correct value.
+ """
+ fn = self.mktemp()
+ open(fn, 'w').write("ls .\nexit")
+ port = self.server.getHost().port
+
+ oldGetAttr = FileTransferForTestAvatar._getAttrs
+ def _getAttrs(self, s):
+ attrs = oldGetAttr(self, s)
+ attrs["ext_foo"] = "bar"
+ return attrs
+
+ self.patch(FileTransferForTestAvatar, "_getAttrs", _getAttrs)
+
+ self.server.factory.expectedLoseConnection = True
+ cmds = ('-o', 'IdentityFile=dsa_test',
+ '-o', 'UserKnownHostsFile=kh_test',
+ '-o', 'HostKeyAlgorithms=ssh-rsa',
+ '-o', 'Port=%i' % (port,), '-b', fn, 'testuser@127.0.0.1')
+ d = getProcessOutputAndValue("sftp", cmds)
+ def check(result):
+ self.assertEquals(result[2], 0)
+ for i in ['testDirectory', 'testRemoveFile',
+ 'testRenameFile', 'testfile1']:
+ self.assertIn(i, result[0])
+ return d.addCallback(check)
+
+
+
+if unix is None or Crypto is None or pyasn1 is None or interfaces.IReactorProcess(reactor, None) is None:
+ if _reason is None:
+ _reason = "don't run w/o spawnProcess or PyCrypto or pyasn1"
+ TestOurServerCmdLineClient.skip = _reason
+ TestOurServerBatchFile.skip = _reason
+ TestOurServerSftpClient.skip = _reason
+ StdioClientTests.skip = _reason
+else:
+ from twisted.python.procutils import which
+ if not which('sftp'):
+ TestOurServerSftpClient.skip = "no sftp command-line client available"
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_channel.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_channel.py
new file mode 100644
index 0000000000..ac3057d720
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_channel.py
@@ -0,0 +1,279 @@
+# Copyright (C) 2007-2008 Twisted Matrix Laboratories
+# See LICENSE for details
+
+"""
+Test ssh/channel.py.
+"""
+from twisted.conch.ssh import channel
+from twisted.trial import unittest
+
+
+class MockTransport(object):
+ """
+ A mock Transport. All we use is the getPeer() and getHost() methods.
+ Channels implement the ITransport interface, and their getPeer() and
+ getHost() methods return ('SSH', <transport's getPeer/Host value>) so
+ we need to implement these methods so they have something to draw
+ from.
+ """
+ def getPeer(self):
+ return ('MockPeer',)
+
+ def getHost(self):
+ return ('MockHost',)
+
+
+class MockConnection(object):
+ """
+ A mock for twisted.conch.ssh.connection.SSHConnection. Record the data
+ that channels send, and when they try to close the connection.
+
+ @ivar data: a C{dict} mapping channel id #s to lists of data sent by that
+ channel.
+ @ivar extData: a C{dict} mapping channel id #s to lists of 2-tuples
+ (extended data type, data) sent by that channel.
+ @ivar closes: a C{dict} mapping channel id #s to True if that channel sent
+ a close message.
+ """
+ transport = MockTransport()
+
+ def __init__(self):
+ self.data = {}
+ self.extData = {}
+ self.closes = {}
+
+ def logPrefix(self):
+ """
+ Return our logging prefix.
+ """
+ return "MockConnection"
+
+ def sendData(self, channel, data):
+ """
+ Record the sent data.
+ """
+ self.data.setdefault(channel, []).append(data)
+
+ def sendExtendedData(self, channel, type, data):
+ """
+ Record the sent extended data.
+ """
+ self.extData.setdefault(channel, []).append((type, data))
+
+ def sendClose(self, channel):
+ """
+ Record that the channel sent a close message.
+ """
+ self.closes[channel] = True
+
+
+class ChannelTestCase(unittest.TestCase):
+
+ def setUp(self):
+ """
+ Initialize the channel. remoteMaxPacket is 10 so that data is able
+ to be sent (the default of 0 means no data is sent because no packets
+ are made).
+ """
+ self.conn = MockConnection()
+ self.channel = channel.SSHChannel(conn=self.conn,
+ remoteMaxPacket=10)
+ self.channel.name = 'channel'
+
+ def test_init(self):
+ """
+ Test that SSHChannel initializes correctly. localWindowSize defaults
+ to 131072 (2**17) and localMaxPacket to 32768 (2**15) as reasonable
+ defaults (what OpenSSH uses for those variables).
+
+ The values in the second set of assertions are meaningless; they serve
+ only to verify that the instance variables are assigned in the correct
+ order.
+ """
+ c = channel.SSHChannel(conn=self.conn)
+ self.assertEquals(c.localWindowSize, 131072)
+ self.assertEquals(c.localWindowLeft, 131072)
+ self.assertEquals(c.localMaxPacket, 32768)
+ self.assertEquals(c.remoteWindowLeft, 0)
+ self.assertEquals(c.remoteMaxPacket, 0)
+ self.assertEquals(c.conn, self.conn)
+ self.assertEquals(c.data, None)
+ self.assertEquals(c.avatar, None)
+
+ c2 = channel.SSHChannel(1, 2, 3, 4, 5, 6, 7)
+ self.assertEquals(c2.localWindowSize, 1)
+ self.assertEquals(c2.localWindowLeft, 1)
+ self.assertEquals(c2.localMaxPacket, 2)
+ self.assertEquals(c2.remoteWindowLeft, 3)
+ self.assertEquals(c2.remoteMaxPacket, 4)
+ self.assertEquals(c2.conn, 5)
+ self.assertEquals(c2.data, 6)
+ self.assertEquals(c2.avatar, 7)
+
+ def test_str(self):
+ """
+ Test that str(SSHChannel) works gives the channel name and local and
+ remote windows at a glance..
+ """
+ self.assertEquals(str(self.channel), '<SSHChannel channel (lw 131072 '
+ 'rw 0)>')
+
+ def test_logPrefix(self):
+ """
+ Test that SSHChannel.logPrefix gives the name of the channel, the
+ local channel ID and the underlying connection.
+ """
+ self.assertEquals(self.channel.logPrefix(), 'SSHChannel channel '
+ '(unknown) on MockConnection')
+
+ def test_addWindowBytes(self):
+ """
+ Test that addWindowBytes adds bytes to the window and resumes writing
+ if it was paused.
+ """
+ cb = [False]
+ def stubStartWriting():
+ cb[0] = True
+ self.channel.startWriting = stubStartWriting
+ self.channel.write('test')
+ self.channel.writeExtended(1, 'test')
+ self.channel.addWindowBytes(50)
+ self.assertEquals(self.channel.remoteWindowLeft, 50 - 4 - 4)
+ self.assertTrue(self.channel.areWriting)
+ self.assertTrue(cb[0])
+ self.assertEquals(self.channel.buf, '')
+ self.assertEquals(self.conn.data[self.channel], ['test'])
+ self.assertEquals(self.channel.extBuf, [])
+ self.assertEquals(self.conn.extData[self.channel], [(1, 'test')])
+
+ cb[0] = False
+ self.channel.addWindowBytes(20)
+ self.assertFalse(cb[0])
+
+ self.channel.write('a'*80)
+ self.channel.loseConnection()
+ self.channel.addWindowBytes(20)
+ self.assertFalse(cb[0])
+
+ def test_requestReceived(self):
+ """
+ Test that requestReceived handles requests by dispatching them to
+ request_* methods.
+ """
+ self.channel.request_test_method = lambda data: data == ''
+ self.assertTrue(self.channel.requestReceived('test-method', ''))
+ self.assertFalse(self.channel.requestReceived('test-method', 'a'))
+ self.assertFalse(self.channel.requestReceived('bad-method', ''))
+
+ def test_closeReceieved(self):
+ """
+ Test that the default closeReceieved closes the connection.
+ """
+ self.assertFalse(self.channel.closing)
+ self.channel.closeReceived()
+ self.assertTrue(self.channel.closing)
+
+ def test_write(self):
+ """
+ Test that write handles data correctly. Send data up to the size
+ of the remote window, splitting the data into packets of length
+ remoteMaxPacket.
+ """
+ cb = [False]
+ def stubStopWriting():
+ cb[0] = True
+ # no window to start with
+ self.channel.stopWriting = stubStopWriting
+ self.channel.write('d')
+ self.channel.write('a')
+ self.assertFalse(self.channel.areWriting)
+ self.assertTrue(cb[0])
+ # regular write
+ self.channel.addWindowBytes(20)
+ self.channel.write('ta')
+ data = self.conn.data[self.channel]
+ self.assertEquals(data, ['da', 'ta'])
+ self.assertEquals(self.channel.remoteWindowLeft, 16)
+ # larger than max packet
+ self.channel.write('12345678901')
+ self.assertEquals(data, ['da', 'ta', '1234567890', '1'])
+ self.assertEquals(self.channel.remoteWindowLeft, 5)
+ # running out of window
+ cb[0] = False
+ self.channel.write('123456')
+ self.assertFalse(self.channel.areWriting)
+ self.assertTrue(cb[0])
+ self.assertEquals(data, ['da', 'ta', '1234567890', '1', '12345'])
+ self.assertEquals(self.channel.buf, '6')
+ self.assertEquals(self.channel.remoteWindowLeft, 0)
+
+ def test_writeExtended(self):
+ """
+ Test that writeExtended handles data correctly. Send extended data
+ up to the size of the window, splitting the extended data into packets
+ of length remoteMaxPacket.
+ """
+ cb = [False]
+ def stubStopWriting():
+ cb[0] = True
+ # no window to start with
+ self.channel.stopWriting = stubStopWriting
+ self.channel.writeExtended(1, 'd')
+ self.channel.writeExtended(1, 'a')
+ self.channel.writeExtended(2, 't')
+ self.assertFalse(self.channel.areWriting)
+ self.assertTrue(cb[0])
+ # regular write
+ self.channel.addWindowBytes(20)
+ self.channel.writeExtended(2, 'a')
+ data = self.conn.extData[self.channel]
+ self.assertEquals(data, [(1, 'da'), (2, 't'), (2, 'a')])
+ self.assertEquals(self.channel.remoteWindowLeft, 16)
+ # larger than max packet
+ self.channel.writeExtended(3, '12345678901')
+ self.assertEquals(data, [(1, 'da'), (2, 't'), (2, 'a'),
+ (3, '1234567890'), (3, '1')])
+ self.assertEquals(self.channel.remoteWindowLeft, 5)
+ # running out of window
+ cb[0] = False
+ self.channel.writeExtended(4, '123456')
+ self.assertFalse(self.channel.areWriting)
+ self.assertTrue(cb[0])
+ self.assertEquals(data, [(1, 'da'), (2, 't'), (2, 'a'),
+ (3, '1234567890'), (3, '1'), (4, '12345')])
+ self.assertEquals(self.channel.extBuf, [[4, '6']])
+ self.assertEquals(self.channel.remoteWindowLeft, 0)
+
+ def test_writeSequence(self):
+ """
+ Test that writeSequence is equivalent to write(''.join(sequece)).
+ """
+ self.channel.addWindowBytes(20)
+ self.channel.writeSequence(map(str, range(10)))
+ self.assertEquals(self.conn.data[self.channel], ['0123456789'])
+
+ def test_loseConnection(self):
+ """
+ Tesyt that loseConnection() doesn't close the channel until all
+ the data is sent.
+ """
+ self.channel.write('data')
+ self.channel.writeExtended(1, 'datadata')
+ self.channel.loseConnection()
+ self.assertEquals(self.conn.closes.get(self.channel), None)
+ self.channel.addWindowBytes(4) # send regular data
+ self.assertEquals(self.conn.closes.get(self.channel), None)
+ self.channel.addWindowBytes(8) # send extended data
+ self.assertTrue(self.conn.closes.get(self.channel))
+
+ def test_getPeer(self):
+ """
+ Test that getPeer() returns ('SSH', <connection transport peer>).
+ """
+ self.assertEquals(self.channel.getPeer(), ('SSH', 'MockPeer'))
+
+ def test_getHost(self):
+ """
+ Test that getHost() returns ('SSH', <connection transport host>).
+ """
+ self.assertEquals(self.channel.getHost(), ('SSH', 'MockHost'))
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_checkers.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_checkers.py
new file mode 100644
index 0000000000..92fbc2244f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_checkers.py
@@ -0,0 +1,280 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.checkers}.
+"""
+
+try:
+ import pwd
+except ImportError:
+ pwd = None
+
+import os, base64
+
+from twisted.trial.unittest import TestCase
+from twisted.python.filepath import FilePath
+from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+from twisted.cred.credentials import UsernamePassword, IUsernamePassword, \
+ SSHPrivateKey, ISSHPrivateKey
+from twisted.cred.error import UnhandledCredentials, UnauthorizedLogin
+from twisted.python.fakepwd import UserDatabase
+from twisted.test.test_process import MockOS
+
+try:
+ import Crypto.Cipher.DES3
+ import pyasn1
+except ImportError:
+ SSHPublicKeyDatabase = None
+else:
+ from twisted.conch.ssh import keys
+ from twisted.conch.checkers import SSHPublicKeyDatabase, SSHProtocolChecker
+ from twisted.conch.error import NotEnoughAuthentication, ValidPublicKey
+ from twisted.conch.test import keydata
+
+
+class SSHPublicKeyDatabaseTestCase(TestCase):
+ """
+ Tests for L{SSHPublicKeyDatabase}.
+ """
+
+ if pwd is None:
+ skip = "Cannot run without pwd module"
+ elif SSHPublicKeyDatabase is None:
+ skip = "Cannot run without PyCrypto or PyASN1"
+
+ def setUp(self):
+ self.checker = SSHPublicKeyDatabase()
+ self.key1 = base64.encodestring("foobar")
+ self.key2 = base64.encodestring("eggspam")
+ self.content = "t1 %s foo\nt2 %s egg\n" % (self.key1, self.key2)
+
+ self.mockos = MockOS()
+ self.mockos.path = FilePath(self.mktemp())
+ self.mockos.path.makedirs()
+ self.sshDir = self.mockos.path.child('.ssh')
+ self.sshDir.makedirs()
+
+ userdb = UserDatabase()
+ userdb.addUser('user', 'password', 1, 2, 'first last',
+ self.mockos.path.path, '/bin/shell')
+
+ self.patch(pwd, "getpwnam", userdb.getpwnam)
+ self.patch(os, "seteuid", self.mockos.seteuid)
+ self.patch(os, "setegid", self.mockos.setegid)
+
+
+ def _testCheckKey(self, filename):
+ self.sshDir.child(filename).setContent(self.content)
+ user = UsernamePassword("user", "password")
+ user.blob = "foobar"
+ self.assertTrue(self.checker.checkKey(user))
+ user.blob = "eggspam"
+ self.assertTrue(self.checker.checkKey(user))
+ user.blob = "notallowed"
+ self.assertFalse(self.checker.checkKey(user))
+
+
+ def test_checkKey(self):
+ """
+ L{SSHPublicKeyDatabase.checkKey} should retrieve the content of the
+ authorized_keys file and check the keys against that file.
+ """
+ self._testCheckKey("authorized_keys")
+ self.assertEquals(self.mockos.seteuidCalls, [])
+ self.assertEquals(self.mockos.setegidCalls, [])
+
+
+ def test_checkKey2(self):
+ """
+ L{SSHPublicKeyDatabase.checkKey} should retrieve the content of the
+ authorized_keys2 file and check the keys against that file.
+ """
+ self._testCheckKey("authorized_keys2")
+ self.assertEquals(self.mockos.seteuidCalls, [])
+ self.assertEquals(self.mockos.setegidCalls, [])
+
+
+ def test_checkKeyAsRoot(self):
+ """
+ If the key file is readable, L{SSHPublicKeyDatabase.checkKey} should
+ switch its uid/gid to the ones of the authenticated user.
+ """
+ keyFile = self.sshDir.child("authorized_keys")
+ keyFile.setContent(self.content)
+ # Fake permission error by changing the mode
+ keyFile.chmod(0000)
+ self.addCleanup(keyFile.chmod, 0777)
+ # And restore the right mode when seteuid is called
+ savedSeteuid = os.seteuid
+ def seteuid(euid):
+ keyFile.chmod(0777)
+ return savedSeteuid(euid)
+ self.patch(os, "seteuid", seteuid)
+ user = UsernamePassword("user", "password")
+ user.blob = "foobar"
+ self.assertTrue(self.checker.checkKey(user))
+ self.assertEquals(self.mockos.seteuidCalls, [0, 1, 0, os.getuid()])
+ self.assertEquals(self.mockos.setegidCalls, [2, os.getgid()])
+
+
+ def test_requestAvatarId(self):
+ """
+ L{SSHPublicKeyDatabase.requestAvatarId} should return the avatar id
+ passed in if its C{_checkKey} method returns True.
+ """
+ def _checkKey(ignored):
+ return True
+ self.patch(self.checker, 'checkKey', _checkKey)
+ credentials = SSHPrivateKey('test', 'ssh-rsa', keydata.publicRSA_openssh,
+ 'foo', keys.Key.fromString(keydata.privateRSA_openssh).sign('foo'))
+ d = self.checker.requestAvatarId(credentials)
+ def _verify(avatarId):
+ self.assertEquals(avatarId, 'test')
+ return d.addCallback(_verify)
+
+
+ def test_requestAvatarIdWithoutSignature(self):
+ """
+ L{SSHPublicKeyDatabase.requestAvatarId} should raise L{ValidPublicKey}
+ if the credentials represent a valid key without a signature. This
+ tells the user that the key is valid for login, but does not actually
+ allow that user to do so without a signature.
+ """
+ def _checkKey(ignored):
+ return True
+ self.patch(self.checker, 'checkKey', _checkKey)
+ credentials = SSHPrivateKey('test', 'ssh-rsa', keydata.publicRSA_openssh, None, None)
+ d = self.checker.requestAvatarId(credentials)
+ return self.assertFailure(d, ValidPublicKey)
+
+
+ def test_requestAvatarIdInvalidKey(self):
+ """
+ If L{SSHPublicKeyDatabase.checkKey} returns False,
+ C{_cbRequestAvatarId} should raise L{UnauthorizedLogin}.
+ """
+ def _checkKey(ignored):
+ return False
+ self.patch(self.checker, 'checkKey', _checkKey)
+ d = self.checker.requestAvatarId(None);
+ return self.assertFailure(d, UnauthorizedLogin)
+
+
+ def test_requestAvatarIdInvalidSignature(self):
+ """
+ Valid keys with invalid signatures should cause
+ L{SSHPublicKeyDatabase.requestAvatarId} to return a {UnauthorizedLogin}
+ failure
+ """
+ def _checkKey(ignored):
+ return True
+ self.patch(self.checker, 'checkKey', _checkKey)
+ credentials = SSHPrivateKey('test', 'ssh-rsa', keydata.publicRSA_openssh,
+ 'foo', keys.Key.fromString(keydata.privateDSA_openssh).sign('foo'))
+ d = self.checker.requestAvatarId(credentials)
+ return self.assertFailure(d, UnauthorizedLogin)
+
+
+ def test_requestAvatarIdNormalizeException(self):
+ """
+ Exceptions raised while verifying the key should be normalized into an
+ C{UnauthorizedLogin} failure.
+ """
+ def _checkKey(ignored):
+ return True
+ self.patch(self.checker, 'checkKey', _checkKey)
+ credentials = SSHPrivateKey('test', None, 'blob', 'sigData', 'sig')
+ d = self.checker.requestAvatarId(credentials)
+ def _verifyLoggedException(failure):
+ errors = self.flushLoggedErrors(keys.BadKeyError)
+ self.assertEqual(len(errors), 1)
+ return failure
+ d.addErrback(_verifyLoggedException)
+ return self.assertFailure(d, UnauthorizedLogin)
+
+
+class SSHProtocolCheckerTestCase(TestCase):
+ """
+ Tests for L{SSHProtocolChecker}.
+ """
+
+ if SSHPublicKeyDatabase is None:
+ skip = "Cannot run without PyCrypto"
+
+ def test_registerChecker(self):
+ """
+ L{SSHProcotolChecker.registerChecker} should add the given checker to
+ the list of registered checkers.
+ """
+ checker = SSHProtocolChecker()
+ self.assertEquals(checker.credentialInterfaces, [])
+ checker.registerChecker(SSHPublicKeyDatabase(), )
+ self.assertEquals(checker.credentialInterfaces, [ISSHPrivateKey])
+ self.assertIsInstance(checker.checkers[ISSHPrivateKey],
+ SSHPublicKeyDatabase)
+
+
+ def test_registerCheckerWithInterface(self):
+ """
+ If a apecific interface is passed into
+ L{SSHProtocolChecker.registerChecker}, that interface should be
+ registered instead of what the checker specifies in
+ credentialIntefaces.
+ """
+ checker = SSHProtocolChecker()
+ self.assertEquals(checker.credentialInterfaces, [])
+ checker.registerChecker(SSHPublicKeyDatabase(), IUsernamePassword)
+ self.assertEquals(checker.credentialInterfaces, [IUsernamePassword])
+ self.assertIsInstance(checker.checkers[IUsernamePassword],
+ SSHPublicKeyDatabase)
+
+
+ def test_requestAvatarId(self):
+ """
+ L{SSHProtocolChecker.requestAvatarId} should defer to one if its
+ registered checkers to authenticate a user.
+ """
+ checker = SSHProtocolChecker()
+ passwordDatabase = InMemoryUsernamePasswordDatabaseDontUse()
+ passwordDatabase.addUser('test', 'test')
+ checker.registerChecker(passwordDatabase)
+ d = checker.requestAvatarId(UsernamePassword('test', 'test'))
+ def _callback(avatarId):
+ self.assertEquals(avatarId, 'test')
+ return d.addCallback(_callback)
+
+
+ def test_requestAvatarIdWithNotEnoughAuthentication(self):
+ """
+ If the client indicates that it is never satisfied, by always returning
+ False from _areDone, then L{SSHProtocolChecker} should raise
+ L{NotEnoughAuthentication}.
+ """
+ checker = SSHProtocolChecker()
+ def _areDone(avatarId):
+ return False
+ self.patch(checker, 'areDone', _areDone)
+
+ passwordDatabase = InMemoryUsernamePasswordDatabaseDontUse()
+ passwordDatabase.addUser('test', 'test')
+ checker.registerChecker(passwordDatabase)
+ d = checker.requestAvatarId(UsernamePassword('test', 'test'))
+ return self.assertFailure(d, NotEnoughAuthentication)
+
+
+ def test_requestAvatarIdInvalidCredential(self):
+ """
+ If the passed credentials aren't handled by any registered checker,
+ L{SSHProtocolChecker} should raise L{UnhandledCredentials}.
+ """
+ checker = SSHProtocolChecker()
+ d = checker.requestAvatarId(UsernamePassword('test', 'test'))
+ return self.assertFailure(d, UnhandledCredentials)
+
+
+ def test_areDone(self):
+ """
+ The default L{SSHProcotolChecker.areDone} should simply return True.
+ """
+ self.assertEquals(SSHProtocolChecker().areDone(None), True)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_ckeygen.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_ckeygen.py
new file mode 100644
index 0000000000..07d6b0fd66
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_ckeygen.py
@@ -0,0 +1,80 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.scripts.ckeygen}.
+"""
+
+import sys
+from StringIO import StringIO
+
+try:
+ import Crypto
+ import pyasn1
+except ImportError:
+ skip = "PyCrypto and pyasn1 required for twisted.conch.scripts.ckeygen."
+else:
+ from twisted.conch.ssh.keys import Key
+ from twisted.conch.scripts.ckeygen import printFingerprint, _saveKey
+
+from twisted.python.filepath import FilePath
+from twisted.trial.unittest import TestCase
+from twisted.conch.test.keydata import publicRSA_openssh, privateRSA_openssh
+
+
+
+class KeyGenTests(TestCase):
+ """
+ Tests for various functions used to implement the I{ckeygen} script.
+ """
+ def setUp(self):
+ """
+ Patch C{sys.stdout} with a L{StringIO} instance to tests can make
+ assertions about what's printed.
+ """
+ self.stdout = StringIO()
+ self.patch(sys, 'stdout', self.stdout)
+
+
+ def test_printFingerprint(self):
+ """
+ L{printFingerprint} writes a line to standard out giving the number of
+ bits of the key, its fingerprint, and the basename of the file from it
+ was read.
+ """
+ filename = self.mktemp()
+ FilePath(filename).setContent(publicRSA_openssh)
+ printFingerprint({'filename': filename})
+ self.assertEqual(
+ self.stdout.getvalue(),
+ '768 3d:13:5f:cb:c9:79:8a:93:06:27:65:bc:3d:0b:8f:af temp\n')
+
+
+ def test_saveKey(self):
+ """
+ L{_saveKey} writes the private and public parts of a key to two
+ different files and writes a report of this to standard out.
+ """
+ base = FilePath(self.mktemp())
+ base.makedirs()
+ filename = base.child('id_rsa').path
+ key = Key.fromString(privateRSA_openssh)
+ _saveKey(
+ key.keyObject,
+ {'filename': filename, 'pass': 'passphrase'})
+ self.assertEqual(
+ self.stdout.getvalue(),
+ "Your identification has been saved in %s\n"
+ "Your public key has been saved in %s.pub\n"
+ "The key fingerprint is:\n"
+ "3d:13:5f:cb:c9:79:8a:93:06:27:65:bc:3d:0b:8f:af\n" % (
+ filename,
+ filename))
+ self.assertEqual(
+ key.fromString(
+ base.child('id_rsa').getContent(), None, 'passphrase'),
+ key)
+ self.assertEqual(
+ Key.fromString(base.child('id_rsa.pub').getContent()),
+ key.public())
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_conch.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_conch.py
new file mode 100644
index 0000000000..82d8b0f0fa
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_conch.py
@@ -0,0 +1,437 @@
+# -*- test-case-name: twisted.conch.test.test_conch -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import os, sys, socket
+
+from twisted.cred import portal
+from twisted.internet import reactor, defer, protocol
+from twisted.internet.error import ProcessExitedAlready
+from twisted.python import log, runtime
+from twisted.trial import unittest
+from twisted.conch.error import ConchError
+try:
+ from twisted.conch.scripts.conch import SSHSession as StdioInteractingSession
+except ImportError, e:
+ StdioInteractingSession = None
+ _reason = str(e)
+ del e
+
+from twisted.conch.test.test_ssh import ConchTestRealm
+from twisted.python.procutils import which
+
+from twisted.conch.test.keydata import publicRSA_openssh, privateRSA_openssh
+from twisted.conch.test.keydata import publicDSA_openssh, privateDSA_openssh
+
+from twisted.conch.test.test_ssh import Crypto, pyasn1
+try:
+ from twisted.conch.test.test_ssh import ConchTestServerFactory, \
+ ConchTestPublicKeyChecker
+except ImportError:
+ pass
+
+
+
+class StdioInteractingSessionTests(unittest.TestCase):
+ """
+ Tests for L{twisted.conch.scripts.conch.SSHSession}.
+ """
+ if StdioInteractingSession is None:
+ skip = _reason
+
+ def test_eofReceived(self):
+ """
+ L{twisted.conch.scripts.conch.SSHSession.eofReceived} loses the
+ write half of its stdio connection.
+ """
+ class FakeStdio:
+ writeConnLost = False
+
+ def loseWriteConnection(self):
+ self.writeConnLost = True
+
+ stdio = FakeStdio()
+ channel = StdioInteractingSession()
+ channel.stdio = stdio
+ channel.eofReceived()
+ self.assertTrue(stdio.writeConnLost)
+
+
+
+class Echo(protocol.Protocol):
+ def connectionMade(self):
+ log.msg('ECHO CONNECTION MADE')
+
+
+ def connectionLost(self, reason):
+ log.msg('ECHO CONNECTION DONE')
+
+
+ def dataReceived(self, data):
+ self.transport.write(data)
+ if '\n' in data:
+ self.transport.loseConnection()
+
+
+
+class EchoFactory(protocol.Factory):
+ protocol = Echo
+
+
+
+class ConchTestOpenSSHProcess(protocol.ProcessProtocol):
+ """
+ Test protocol for launching an OpenSSH client process.
+
+ @ivar deferred: Set by whatever uses this object. Accessed using
+ L{_getDeferred}, which destroys the value so the Deferred is not
+ fired twice. Fires when the process is terminated.
+ """
+
+ deferred = None
+ buf = ''
+
+ def _getDeferred(self):
+ d, self.deferred = self.deferred, None
+ return d
+
+
+ def outReceived(self, data):
+ self.buf += data
+
+
+ def processEnded(self, reason):
+ """
+ Called when the process has ended.
+
+ @param reason: a Failure giving the reason for the process' end.
+ """
+ if reason.value.exitCode != 0:
+ self._getDeferred().errback(
+ ConchError("exit code was not 0: %s" %
+ reason.value.exitCode))
+ else:
+ buf = self.buf.replace('\r\n', '\n')
+ self._getDeferred().callback(buf)
+
+
+
+class ConchTestForwardingProcess(protocol.ProcessProtocol):
+ """
+ Manages a third-party process which launches a server.
+
+ Uses L{ConchTestForwardingPort} to connect to the third-party server.
+ Once L{ConchTestForwardingPort} has disconnected, kill the process and fire
+ a Deferred with the data received by the L{ConchTestForwardingPort}.
+
+ @ivar deferred: Set by whatever uses this object. Accessed using
+ L{_getDeferred}, which destroys the value so the Deferred is not
+ fired twice. Fires when the process is terminated.
+ """
+
+ deferred = None
+
+ def __init__(self, port, data):
+ """
+ @type port: C{int}
+ @param port: The port on which the third-party server is listening.
+ (it is assumed that the server is running on localhost).
+
+ @type data: C{str}
+ @param data: This is sent to the third-party server. Must end with '\n'
+ in order to trigger a disconnect.
+ """
+ self.port = port
+ self.buffer = None
+ self.data = data
+
+
+ def _getDeferred(self):
+ d, self.deferred = self.deferred, None
+ return d
+
+
+ def connectionMade(self):
+ self._connect()
+
+
+ def _connect(self):
+ """
+ Connect to the server, which is often a third-party process.
+ Tries to reconnect if it fails because we have no way of determining
+ exactly when the port becomes available for listening -- we can only
+ know when the process starts.
+ """
+ cc = protocol.ClientCreator(reactor, ConchTestForwardingPort, self,
+ self.data)
+ d = cc.connectTCP('127.0.0.1', self.port)
+ d.addErrback(self._ebConnect)
+ return d
+
+
+ def _ebConnect(self, f):
+ reactor.callLater(.1, self._connect)
+
+
+ def forwardingPortDisconnected(self, buffer):
+ """
+ The network connection has died; save the buffer of output
+ from the network and attempt to quit the process gracefully,
+ and then (after the reactor has spun) send it a KILL signal.
+ """
+ self.buffer = buffer
+ self.transport.write('\x03')
+ self.transport.loseConnection()
+ reactor.callLater(0, self._reallyDie)
+
+
+ def _reallyDie(self):
+ try:
+ self.transport.signalProcess('KILL')
+ except ProcessExitedAlready:
+ pass
+
+
+ def processEnded(self, reason):
+ """
+ Fire the Deferred at self.deferred with the data collected
+ from the L{ConchTestForwardingPort} connection, if any.
+ """
+ self._getDeferred().callback(self.buffer)
+
+
+
+class ConchTestForwardingPort(protocol.Protocol):
+ """
+ Connects to server launched by a third-party process (managed by
+ L{ConchTestForwardingProcess}) sends data, then reports whatever it
+ received back to the L{ConchTestForwardingProcess} once the connection
+ is ended.
+ """
+
+
+ def __init__(self, protocol, data):
+ """
+ @type protocol: L{ConchTestForwardingProcess}
+ @param protocol: The L{ProcessProtocol} which made this connection.
+
+ @type data: str
+ @param data: The data to be sent to the third-party server.
+ """
+ self.protocol = protocol
+ self.data = data
+
+
+ def connectionMade(self):
+ self.buffer = ''
+ self.transport.write(self.data)
+
+
+ def dataReceived(self, data):
+ self.buffer += data
+
+
+ def connectionLost(self, reason):
+ self.protocol.forwardingPortDisconnected(self.buffer)
+
+
+
+def _makeArgs(args, mod="conch"):
+ start = [sys.executable, '-c'
+"""
+### Twisted Preamble
+import sys, os
+path = os.path.abspath(sys.argv[0])
+while os.path.dirname(path) != path:
+ if os.path.basename(path).startswith('Twisted'):
+ sys.path.insert(0, path)
+ break
+ path = os.path.dirname(path)
+
+from twisted.conch.scripts.%s import run
+run()""" % mod]
+ return start + list(args)
+
+
+
+class ForwardingTestBase:
+ """
+ Template class for tests of the Conch server's ability to forward arbitrary
+ protocols over SSH.
+
+ These tests are integration tests, not unit tests. They launch a Conch
+ server, a custom TCP server (just an L{EchoProtocol}) and then call
+ L{execute}.
+
+ L{execute} is implemented by subclasses of L{ForwardingTestBase}. It should
+ cause an SSH client to connect to the Conch server, asking it to forward
+ data to the custom TCP server.
+ """
+
+ if not Crypto:
+ skip = "can't run w/o PyCrypto"
+
+ if not pyasn1:
+ skip = "can't run w/o PyASN1"
+
+ def _createFiles(self):
+ for f in ['rsa_test','rsa_test.pub','dsa_test','dsa_test.pub',
+ 'kh_test']:
+ if os.path.exists(f):
+ os.remove(f)
+ open('rsa_test','w').write(privateRSA_openssh)
+ open('rsa_test.pub','w').write(publicRSA_openssh)
+ open('dsa_test.pub','w').write(publicDSA_openssh)
+ open('dsa_test','w').write(privateDSA_openssh)
+ os.chmod('dsa_test', 33152)
+ os.chmod('rsa_test', 33152)
+ open('kh_test','w').write('127.0.0.1 '+publicRSA_openssh)
+
+
+ def _getFreePort(self):
+ s = socket.socket()
+ s.bind(('', 0))
+ port = s.getsockname()[1]
+ s.close()
+ return port
+
+
+ def _makeConchFactory(self):
+ """
+ Make a L{ConchTestServerFactory}, which allows us to start a
+ L{ConchTestServer} -- i.e. an actually listening conch.
+ """
+ realm = ConchTestRealm()
+ p = portal.Portal(realm)
+ p.registerChecker(ConchTestPublicKeyChecker())
+ factory = ConchTestServerFactory()
+ factory.portal = p
+ return factory
+
+
+ def setUp(self):
+ self._createFiles()
+ self.conchFactory = self._makeConchFactory()
+ self.conchFactory.expectedLoseConnection = 1
+ self.conchServer = reactor.listenTCP(0, self.conchFactory,
+ interface="127.0.0.1")
+ self.echoServer = reactor.listenTCP(0, EchoFactory())
+ self.echoPort = self.echoServer.getHost().port
+
+
+ def tearDown(self):
+ try:
+ self.conchFactory.proto.done = 1
+ except AttributeError:
+ pass
+ else:
+ self.conchFactory.proto.transport.loseConnection()
+ return defer.gatherResults([
+ defer.maybeDeferred(self.conchServer.stopListening),
+ defer.maybeDeferred(self.echoServer.stopListening)])
+
+
+ def test_exec(self):
+ """
+ Test that we can use whatever client to send the command "echo goodbye"
+ to the Conch server. Make sure we receive "goodbye" back from the
+ server.
+ """
+ d = self.execute('echo goodbye', ConchTestOpenSSHProcess())
+ return d.addCallback(self.assertEquals, 'goodbye\n')
+
+
+ def test_localToRemoteForwarding(self):
+ """
+ Test that we can use whatever client to forward a local port to a
+ specified port on the server.
+ """
+ localPort = self._getFreePort()
+ process = ConchTestForwardingProcess(localPort, 'test\n')
+ d = self.execute('', process,
+ sshArgs='-N -L%i:127.0.0.1:%i'
+ % (localPort, self.echoPort))
+ d.addCallback(self.assertEqual, 'test\n')
+ return d
+
+
+ def test_remoteToLocalForwarding(self):
+ """
+ Test that we can use whatever client to forward a port from the server
+ to a port locally.
+ """
+ localPort = self._getFreePort()
+ process = ConchTestForwardingProcess(localPort, 'test\n')
+ d = self.execute('', process,
+ sshArgs='-N -R %i:127.0.0.1:%i'
+ % (localPort, self.echoPort))
+ d.addCallback(self.assertEqual, 'test\n')
+ return d
+
+
+
+class OpenSSHClientTestCase(ForwardingTestBase, unittest.TestCase):
+
+ if not which('ssh'):
+ skip = "no ssh command-line client available"
+
+ def execute(self, remoteCommand, process, sshArgs=''):
+ """
+ Connects to the SSH server started in L{ForwardingTestBase.setUp} by
+ running the 'ssh' command line tool.
+
+ @type remoteCommand: str
+ @param remoteCommand: The command (with arguments) to run on the
+ remote end.
+
+ @type process: L{ConchTestOpenSSHProcess}
+
+ @type sshArgs: str
+ @param sshArgs: Arguments to pass to the 'ssh' process.
+
+ @return: L{defer.Deferred}
+ """
+ process.deferred = defer.Deferred()
+ cmdline = ('ssh -2 -l testuser -p %i '
+ '-oUserKnownHostsFile=kh_test '
+ '-oPasswordAuthentication=no '
+ # Always use the RSA key, since that's the one in kh_test.
+ '-oHostKeyAlgorithms=ssh-rsa '
+ '-a '
+ '-i dsa_test ') + sshArgs + \
+ ' 127.0.0.1 ' + remoteCommand
+ port = self.conchServer.getHost().port
+ cmds = (cmdline % port).split()
+ reactor.spawnProcess(process, "ssh", cmds)
+ return process.deferred
+
+
+
+class CmdLineClientTestCase(ForwardingTestBase, unittest.TestCase):
+ if runtime.platformType == 'win32':
+ skip = "can't run cmdline client on win32"
+
+ def execute(self, remoteCommand, process, sshArgs=''):
+ """
+ As for L{OpenSSHClientTestCase.execute}, except it runs the 'conch'
+ command line tool, not 'ssh'.
+ """
+ process.deferred = defer.Deferred()
+ port = self.conchServer.getHost().port
+ cmd = ('-p %i -l testuser '
+ '--known-hosts kh_test '
+ '--user-authentications publickey '
+ '--host-key-algorithms ssh-rsa '
+ '-a '
+ '-i dsa_test '
+ '-v ') % port + sshArgs + \
+ ' 127.0.0.1 ' + remoteCommand
+ cmds = _makeArgs(cmd.split())
+ log.msg(str(cmds))
+ env = os.environ.copy()
+ env['PYTHONPATH'] = os.pathsep.join(sys.path)
+ reactor.spawnProcess(process, sys.executable, cmds, env=env)
+ return process.deferred
+
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_connection.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_connection.py
new file mode 100644
index 0000000000..22f38c2616
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_connection.py
@@ -0,0 +1,623 @@
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details
+
+"""
+This module tests twisted.conch.ssh.connection.
+"""
+
+import struct
+
+from twisted.conch import error
+from twisted.conch.ssh import channel, common, connection
+from twisted.trial import unittest
+from twisted.conch.test import test_userauth
+
+
+class TestChannel(channel.SSHChannel):
+ """
+ A mocked-up version of twisted.conch.ssh.channel.SSHChannel.
+
+ @ivar gotOpen: True if channelOpen has been called.
+ @type gotOpen: C{bool}
+ @ivar specificData: the specific channel open data passed to channelOpen.
+ @type specificData: C{str}
+ @ivar openFailureReason: the reason passed to openFailed.
+ @type openFailed: C{error.ConchError}
+ @ivar inBuffer: a C{list} of strings received by the channel.
+ @type inBuffer: C{list}
+ @ivar extBuffer: a C{list} of 2-tuples (type, extended data) of received by
+ the channel.
+ @type extBuffer: C{list}
+ @ivar numberRequests: the number of requests that have been made to this
+ channel.
+ @type numberRequests: C{int}
+ @ivar gotEOF: True if the other side sent EOF.
+ @type gotEOF: C{bool}
+ @ivar gotOneClose: True if the other side closed the connection.
+ @type gotOneClose: C{bool}
+ @ivar gotClosed: True if the channel is closed.
+ @type gotClosed: C{bool}
+ """
+ name = "TestChannel"
+ gotOpen = False
+
+ def logPrefix(self):
+ return "TestChannel %i" % self.id
+
+ def channelOpen(self, specificData):
+ """
+ The channel is open. Set up the instance variables.
+ """
+ self.gotOpen = True
+ self.specificData = specificData
+ self.inBuffer = []
+ self.extBuffer = []
+ self.numberRequests = 0
+ self.gotEOF = False
+ self.gotOneClose = False
+ self.gotClosed = False
+
+ def openFailed(self, reason):
+ """
+ Opening the channel failed. Store the reason why.
+ """
+ self.openFailureReason = reason
+
+ def request_test(self, data):
+ """
+ A test request. Return True if data is 'data'.
+
+ @type data: C{str}
+ """
+ self.numberRequests += 1
+ return data == 'data'
+
+ def dataReceived(self, data):
+ """
+ Data was received. Store it in the buffer.
+ """
+ self.inBuffer.append(data)
+
+ def extReceived(self, code, data):
+ """
+ Extended data was received. Store it in the buffer.
+ """
+ self.extBuffer.append((code, data))
+
+ def eofReceived(self):
+ """
+ EOF was received. Remember it.
+ """
+ self.gotEOF = True
+
+ def closeReceived(self):
+ """
+ Close was received. Remember it.
+ """
+ self.gotOneClose = True
+
+ def closed(self):
+ """
+ The channel is closed. Rembember it.
+ """
+ self.gotClosed = True
+
+class TestAvatar:
+ """
+ A mocked-up version of twisted.conch.avatar.ConchUser
+ """
+
+ def lookupChannel(self, channelType, windowSize, maxPacket, data):
+ """
+ The server wants us to return a channel. If the requested channel is
+ our TestChannel, return it, otherwise return None.
+ """
+ if channelType == TestChannel.name:
+ return TestChannel(remoteWindow=windowSize,
+ remoteMaxPacket=maxPacket,
+ data=data, avatar=self)
+
+ def gotGlobalRequest(self, requestType, data):
+ """
+ The client has made a global request. If the global request is
+ 'TestGlobal', return True. If the global request is 'TestData',
+ return True and the request-specific data we received. Otherwise,
+ return False.
+ """
+ if requestType == 'TestGlobal':
+ return True
+ elif requestType == 'TestData':
+ return True, data
+ else:
+ return False
+
+class TestConnection(connection.SSHConnection):
+ """
+ A subclass of SSHConnection for testing.
+
+ @ivar channel: the current channel.
+ @type channel. C{TestChannel}
+ """
+
+ def logPrefix(self):
+ return "TestConnection"
+
+ def global_TestGlobal(self, data):
+ """
+ The other side made the 'TestGlobal' global request. Return True.
+ """
+ return True
+
+ def global_Test_Data(self, data):
+ """
+ The other side made the 'Test-Data' global request. Return True and
+ the data we received.
+ """
+ return True, data
+
+ def channel_TestChannel(self, windowSize, maxPacket, data):
+ """
+ The other side is requesting the TestChannel. Create a C{TestChannel}
+ instance, store it, and return it.
+ """
+ self.channel = TestChannel(remoteWindow=windowSize,
+ remoteMaxPacket=maxPacket, data=data)
+ return self.channel
+
+ def channel_ErrorChannel(self, windowSize, maxPacket, data):
+ """
+ The other side is requesting the ErrorChannel. Raise an exception.
+ """
+ raise AssertionError('no such thing')
+
+
+
+class ConnectionTestCase(unittest.TestCase):
+
+ if test_userauth.transport is None:
+ skip = "Cannot run without PyCrypto"
+
+ def setUp(self):
+ self.transport = test_userauth.FakeTransport(None)
+ self.transport.avatar = TestAvatar()
+ self.conn = TestConnection()
+ self.conn.transport = self.transport
+ self.conn.serviceStarted()
+
+ def _openChannel(self, channel):
+ """
+ Open the channel with the default connection.
+ """
+ self.conn.openChannel(channel)
+ self.transport.packets = self.transport.packets[:-1]
+ self.conn.ssh_CHANNEL_OPEN_CONFIRMATION(struct.pack('>2L',
+ channel.id, 255) + '\x00\x02\x00\x00\x00\x00\x80\x00')
+
+ def tearDown(self):
+ self.conn.serviceStopped()
+
+ def test_linkAvatar(self):
+ """
+ Test that the connection links itself to the avatar in the
+ transport.
+ """
+ self.assertIdentical(self.transport.avatar.conn, self.conn)
+
+ def test_serviceStopped(self):
+ """
+ Test that serviceStopped() closes any open channels.
+ """
+ channel1 = TestChannel()
+ channel2 = TestChannel()
+ self.conn.openChannel(channel1)
+ self.conn.openChannel(channel2)
+ self.conn.ssh_CHANNEL_OPEN_CONFIRMATION('\x00\x00\x00\x00' * 4)
+ self.assertTrue(channel1.gotOpen)
+ self.assertFalse(channel2.gotOpen)
+ self.conn.serviceStopped()
+ self.assertTrue(channel1.gotClosed)
+
+ def test_GLOBAL_REQUEST(self):
+ """
+ Test that global request packets are dispatched to the global_*
+ methods and the return values are translated into success or failure
+ messages.
+ """
+ self.conn.ssh_GLOBAL_REQUEST(common.NS('TestGlobal') + '\xff')
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_REQUEST_SUCCESS, '')])
+ self.transport.packets = []
+ self.conn.ssh_GLOBAL_REQUEST(common.NS('TestData') + '\xff' +
+ 'test data')
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_REQUEST_SUCCESS, 'test data')])
+ self.transport.packets = []
+ self.conn.ssh_GLOBAL_REQUEST(common.NS('TestBad') + '\xff')
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_REQUEST_FAILURE, '')])
+ self.transport.packets = []
+ self.conn.ssh_GLOBAL_REQUEST(common.NS('TestGlobal') + '\x00')
+ self.assertEquals(self.transport.packets, [])
+
+ def test_REQUEST_SUCCESS(self):
+ """
+ Test that global request success packets cause the Deferred to be
+ called back.
+ """
+ d = self.conn.sendGlobalRequest('request', 'data', True)
+ self.conn.ssh_REQUEST_SUCCESS('data')
+ def check(data):
+ self.assertEquals(data, 'data')
+ d.addCallback(check)
+ d.addErrback(self.fail)
+ return d
+
+ def test_REQUEST_FAILURE(self):
+ """
+ Test that global request failure packets cause the Deferred to be
+ erred back.
+ """
+ d = self.conn.sendGlobalRequest('request', 'data', True)
+ self.conn.ssh_REQUEST_FAILURE('data')
+ def check(f):
+ self.assertEquals(f.value.data, 'data')
+ d.addCallback(self.fail)
+ d.addErrback(check)
+ return d
+
+ def test_CHANNEL_OPEN(self):
+ """
+ Test that open channel packets cause a channel to be created and
+ opened or a failure message to be returned.
+ """
+ del self.transport.avatar
+ self.conn.ssh_CHANNEL_OPEN(common.NS('TestChannel') +
+ '\x00\x00\x00\x01' * 4)
+ self.assertTrue(self.conn.channel.gotOpen)
+ self.assertEquals(self.conn.channel.conn, self.conn)
+ self.assertEquals(self.conn.channel.data, '\x00\x00\x00\x01')
+ self.assertEquals(self.conn.channel.specificData, '\x00\x00\x00\x01')
+ self.assertEquals(self.conn.channel.remoteWindowLeft, 1)
+ self.assertEquals(self.conn.channel.remoteMaxPacket, 1)
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_OPEN_CONFIRMATION,
+ '\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x00'
+ '\x00\x00\x80\x00')])
+ self.transport.packets = []
+ self.conn.ssh_CHANNEL_OPEN(common.NS('BadChannel') +
+ '\x00\x00\x00\x02' * 4)
+ self.flushLoggedErrors()
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_OPEN_FAILURE,
+ '\x00\x00\x00\x02\x00\x00\x00\x03' + common.NS(
+ 'unknown channel') + common.NS(''))])
+ self.transport.packets = []
+ self.conn.ssh_CHANNEL_OPEN(common.NS('ErrorChannel') +
+ '\x00\x00\x00\x02' * 4)
+ self.flushLoggedErrors()
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_OPEN_FAILURE,
+ '\x00\x00\x00\x02\x00\x00\x00\x02' + common.NS(
+ 'unknown failure') + common.NS(''))])
+
+ def test_CHANNEL_OPEN_CONFIRMATION(self):
+ """
+ Test that channel open confirmation packets cause the channel to be
+ notified that it's open.
+ """
+ channel = TestChannel()
+ self.conn.openChannel(channel)
+ self.conn.ssh_CHANNEL_OPEN_CONFIRMATION('\x00\x00\x00\x00'*5)
+ self.assertEquals(channel.remoteWindowLeft, 0)
+ self.assertEquals(channel.remoteMaxPacket, 0)
+ self.assertEquals(channel.specificData, '\x00\x00\x00\x00')
+ self.assertEquals(self.conn.channelsToRemoteChannel[channel],
+ 0)
+ self.assertEquals(self.conn.localToRemoteChannel[0], 0)
+
+ def test_CHANNEL_OPEN_FAILURE(self):
+ """
+ Test that channel open failure packets cause the channel to be
+ notified that its opening failed.
+ """
+ channel = TestChannel()
+ self.conn.openChannel(channel)
+ self.conn.ssh_CHANNEL_OPEN_FAILURE('\x00\x00\x00\x00\x00\x00\x00'
+ '\x01' + common.NS('failure!'))
+ self.assertEquals(channel.openFailureReason.args, ('failure!', 1))
+ self.assertEquals(self.conn.channels.get(channel), None)
+
+
+ def test_CHANNEL_WINDOW_ADJUST(self):
+ """
+ Test that channel window adjust messages add bytes to the channel
+ window.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ oldWindowSize = channel.remoteWindowLeft
+ self.conn.ssh_CHANNEL_WINDOW_ADJUST('\x00\x00\x00\x00\x00\x00\x00'
+ '\x01')
+ self.assertEquals(channel.remoteWindowLeft, oldWindowSize + 1)
+
+ def test_CHANNEL_DATA(self):
+ """
+ Test that channel data messages are passed up to the channel, or
+ cause the channel to be closed if the data is too large.
+ """
+ channel = TestChannel(localWindow=6, localMaxPacket=5)
+ self._openChannel(channel)
+ self.conn.ssh_CHANNEL_DATA('\x00\x00\x00\x00' + common.NS('data'))
+ self.assertEquals(channel.inBuffer, ['data'])
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_WINDOW_ADJUST, '\x00\x00\x00\xff'
+ '\x00\x00\x00\x04')])
+ self.transport.packets = []
+ longData = 'a' * (channel.localWindowLeft + 1)
+ self.conn.ssh_CHANNEL_DATA('\x00\x00\x00\x00' + common.NS(longData))
+ self.assertEquals(channel.inBuffer, ['data'])
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
+ channel = TestChannel()
+ self._openChannel(channel)
+ bigData = 'a' * (channel.localMaxPacket + 1)
+ self.transport.packets = []
+ self.conn.ssh_CHANNEL_DATA('\x00\x00\x00\x01' + common.NS(bigData))
+ self.assertEquals(channel.inBuffer, [])
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
+
+ def test_CHANNEL_EXTENDED_DATA(self):
+ """
+ Test that channel extended data messages are passed up to the channel,
+ or cause the channel to be closed if they're too big.
+ """
+ channel = TestChannel(localWindow=6, localMaxPacket=5)
+ self._openChannel(channel)
+ self.conn.ssh_CHANNEL_EXTENDED_DATA('\x00\x00\x00\x00\x00\x00\x00'
+ '\x00' + common.NS('data'))
+ self.assertEquals(channel.extBuffer, [(0, 'data')])
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_WINDOW_ADJUST, '\x00\x00\x00\xff'
+ '\x00\x00\x00\x04')])
+ self.transport.packets = []
+ longData = 'a' * (channel.localWindowLeft + 1)
+ self.conn.ssh_CHANNEL_EXTENDED_DATA('\x00\x00\x00\x00\x00\x00\x00'
+ '\x00' + common.NS(longData))
+ self.assertEquals(channel.extBuffer, [(0, 'data')])
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
+ channel = TestChannel()
+ self._openChannel(channel)
+ bigData = 'a' * (channel.localMaxPacket + 1)
+ self.transport.packets = []
+ self.conn.ssh_CHANNEL_EXTENDED_DATA('\x00\x00\x00\x01\x00\x00\x00'
+ '\x00' + common.NS(bigData))
+ self.assertEquals(channel.extBuffer, [])
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
+
+ def test_CHANNEL_EOF(self):
+ """
+ Test that channel eof messages are passed up to the channel.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ self.conn.ssh_CHANNEL_EOF('\x00\x00\x00\x00')
+ self.assertTrue(channel.gotEOF)
+
+ def test_CHANNEL_CLOSE(self):
+ """
+ Test that channel close messages are passed up to the channel. Also,
+ test that channel.close() is called if both sides are closed when this
+ message is received.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ self.conn.sendClose(channel)
+ self.conn.ssh_CHANNEL_CLOSE('\x00\x00\x00\x00')
+ self.assertTrue(channel.gotOneClose)
+ self.assertTrue(channel.gotClosed)
+
+ def test_CHANNEL_REQUEST_success(self):
+ """
+ Test that channel requests that succeed send MSG_CHANNEL_SUCCESS.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ self.conn.ssh_CHANNEL_REQUEST('\x00\x00\x00\x00' + common.NS('test')
+ + '\x00')
+ self.assertEquals(channel.numberRequests, 1)
+ d = self.conn.ssh_CHANNEL_REQUEST('\x00\x00\x00\x00' + common.NS(
+ 'test') + '\xff' + 'data')
+ def check(result):
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_SUCCESS, '\x00\x00\x00\xff')])
+ d.addCallback(check)
+ return d
+
+ def test_CHANNEL_REQUEST_failure(self):
+ """
+ Test that channel requests that fail send MSG_CHANNEL_FAILURE.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ d = self.conn.ssh_CHANNEL_REQUEST('\x00\x00\x00\x00' + common.NS(
+ 'test') + '\xff')
+ def check(result):
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_FAILURE, '\x00\x00\x00\xff'
+ )])
+ d.addCallback(self.fail)
+ d.addErrback(check)
+ return d
+
+ def test_CHANNEL_REQUEST_SUCCESS(self):
+ """
+ Test that channel request success messages cause the Deferred to be
+ called back.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ d = self.conn.sendRequest(channel, 'test', 'data', True)
+ self.conn.ssh_CHANNEL_SUCCESS('\x00\x00\x00\x00')
+ def check(result):
+ self.assertTrue(result)
+ return d
+
+ def test_CHANNEL_REQUEST_FAILURE(self):
+ """
+ Test that channel request failure messages cause the Deferred to be
+ erred back.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ d = self.conn.sendRequest(channel, 'test', '', True)
+ self.conn.ssh_CHANNEL_FAILURE('\x00\x00\x00\x00')
+ def check(result):
+ self.assertEquals(result.value.value, 'channel request failed')
+ d.addCallback(self.fail)
+ d.addErrback(check)
+ return d
+
+ def test_sendGlobalRequest(self):
+ """
+ Test that global request messages are sent in the right format.
+ """
+ d = self.conn.sendGlobalRequest('wantReply', 'data', True)
+ self.conn.sendGlobalRequest('noReply', '', False)
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_GLOBAL_REQUEST, common.NS('wantReply') +
+ '\xffdata'),
+ (connection.MSG_GLOBAL_REQUEST, common.NS('noReply') +
+ '\x00')])
+ self.assertEquals(self.conn.deferreds, {'global':[d]})
+
+ def test_openChannel(self):
+ """
+ Test that open channel messages are sent in the right format.
+ """
+ channel = TestChannel()
+ self.conn.openChannel(channel, 'aaaa')
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_OPEN, common.NS('TestChannel') +
+ '\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x80\x00aaaa')])
+ self.assertEquals(channel.id, 0)
+ self.assertEquals(self.conn.localChannelID, 1)
+
+ def test_sendRequest(self):
+ """
+ Test that channel request messages are sent in the right format.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ d = self.conn.sendRequest(channel, 'test', 'test', True)
+ self.conn.sendRequest(channel, 'test2', '', False)
+ channel.localClosed = True # emulate sending a close message
+ self.conn.sendRequest(channel, 'test3', '', True)
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_REQUEST, '\x00\x00\x00\xff' +
+ common.NS('test') + '\x01test'),
+ (connection.MSG_CHANNEL_REQUEST, '\x00\x00\x00\xff' +
+ common.NS('test2') + '\x00')])
+ self.assertEquals(self.conn.deferreds, {0:[d]})
+
+ def test_adjustWindow(self):
+ """
+ Test that channel window adjust messages cause bytes to be added
+ to the window.
+ """
+ channel = TestChannel(localWindow=5)
+ self._openChannel(channel)
+ channel.localWindowLeft = 0
+ self.conn.adjustWindow(channel, 1)
+ self.assertEquals(channel.localWindowLeft, 1)
+ channel.localClosed = True
+ self.conn.adjustWindow(channel, 2)
+ self.assertEquals(channel.localWindowLeft, 1)
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_WINDOW_ADJUST, '\x00\x00\x00\xff'
+ '\x00\x00\x00\x01')])
+
+ def test_sendData(self):
+ """
+ Test that channel data messages are sent in the right format.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ self.conn.sendData(channel, 'a')
+ channel.localClosed = True
+ self.conn.sendData(channel, 'b')
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_DATA, '\x00\x00\x00\xff' +
+ common.NS('a'))])
+
+ def test_sendExtendedData(self):
+ """
+ Test that channel extended data messages are sent in the right format.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ self.conn.sendExtendedData(channel, 1, 'test')
+ channel.localClosed = True
+ self.conn.sendExtendedData(channel, 2, 'test2')
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_EXTENDED_DATA, '\x00\x00\x00\xff' +
+ '\x00\x00\x00\x01' + common.NS('test'))])
+
+ def test_sendEOF(self):
+ """
+ Test that channel EOF messages are sent in the right format.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ self.conn.sendEOF(channel)
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_EOF, '\x00\x00\x00\xff')])
+ channel.localClosed = True
+ self.conn.sendEOF(channel)
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_EOF, '\x00\x00\x00\xff')])
+
+ def test_sendClose(self):
+ """
+ Test that channel close messages are sent in the right format.
+ """
+ channel = TestChannel()
+ self._openChannel(channel)
+ self.conn.sendClose(channel)
+ self.assertTrue(channel.localClosed)
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
+ self.conn.sendClose(channel)
+ self.assertEquals(self.transport.packets,
+ [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
+
+ channel2 = TestChannel()
+ self._openChannel(channel2)
+ channel2.remoteClosed = True
+ self.conn.sendClose(channel2)
+ self.assertTrue(channel2.gotClosed)
+
+ def test_getChannelWithAvatar(self):
+ """
+ Test that getChannel dispatches to the avatar when an avatar is
+ present. Correct functioning without the avatar is verified in
+ test_CHANNEL_OPEN.
+ """
+ channel = self.conn.getChannel('TestChannel', 50, 30, 'data')
+ self.assertEquals(channel.data, 'data')
+ self.assertEquals(channel.remoteWindowLeft, 50)
+ self.assertEquals(channel.remoteMaxPacket, 30)
+ self.assertRaises(error.ConchError, self.conn.getChannel,
+ 'BadChannel', 50, 30, 'data')
+
+ def test_gotGlobalRequestWithoutAvatar(self):
+ """
+ Test that gotGlobalRequests dispatches to global_* without an avatar.
+ """
+ del self.transport.avatar
+ self.assertTrue(self.conn.gotGlobalRequest('TestGlobal', 'data'))
+ self.assertEquals(self.conn.gotGlobalRequest('Test-Data', 'data'),
+ (True, 'data'))
+ self.assertFalse(self.conn.gotGlobalRequest('BadGlobal', 'data'))
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_default.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_default.py
new file mode 100644
index 0000000000..20036be709
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_default.py
@@ -0,0 +1,171 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.client.default}.
+"""
+try:
+ import Crypto.Cipher.DES3
+ import pyasn1
+except ImportError:
+ skip = "PyCrypto and PyASN1 required for twisted.conch.client.default."
+else:
+ from twisted.conch.client.agent import SSHAgentClient
+ from twisted.conch.client.default import SSHUserAuthClient
+ from twisted.conch.client.options import ConchOptions
+ from twisted.conch.ssh.keys import Key
+
+
+from twisted.trial.unittest import TestCase
+from twisted.python.filepath import FilePath
+from twisted.conch.test import keydata
+from twisted.test.proto_helpers import StringTransport
+
+
+
+class SSHUserAuthClientTest(TestCase):
+ """
+ Tests for L{SSHUserAuthClient}.
+
+ @type rsaPublic: L{Key}
+ @ivar rsaPublic: A public RSA key.
+ """
+
+ def setUp(self):
+ self.rsaPublic = Key.fromString(keydata.publicRSA_openssh)
+ self.tmpdir = FilePath(self.mktemp())
+ self.tmpdir.makedirs()
+ self.rsaFile = self.tmpdir.child('id_rsa')
+ self.rsaFile.setContent(keydata.privateRSA_openssh)
+ self.tmpdir.child('id_rsa.pub').setContent(keydata.publicRSA_openssh)
+
+
+ def test_signDataWithAgent(self):
+ """
+ When connected to an agent, L{SSHUserAuthClient} can use it to
+ request signatures of particular data with a particular L{Key}.
+ """
+ client = SSHUserAuthClient("user", ConchOptions(), None)
+ agent = SSHAgentClient()
+ transport = StringTransport()
+ agent.makeConnection(transport)
+ client.keyAgent = agent
+ cleartext = "Sign here"
+ client.signData(self.rsaPublic, cleartext)
+ self.assertEquals(
+ transport.value(),
+ "\x00\x00\x00\x8b\r\x00\x00\x00u" + self.rsaPublic.blob() +
+ "\x00\x00\x00\t" + cleartext +
+ "\x00\x00\x00\x00")
+
+
+ def test_agentGetPublicKey(self):
+ """
+ L{SSHUserAuthClient} looks up public keys from the agent using the
+ L{SSHAgentClient} class. That L{SSHAgentClient.getPublicKey} returns a
+ L{Key} object with one of the public keys in the agent. If no more
+ keys are present, it returns C{None}.
+ """
+ agent = SSHAgentClient()
+ agent.blobs = [self.rsaPublic.blob()]
+ key = agent.getPublicKey()
+ self.assertEquals(key.isPublic(), True)
+ self.assertEquals(key, self.rsaPublic)
+ self.assertEquals(agent.getPublicKey(), None)
+
+
+ def test_getPublicKeyFromFile(self):
+ """
+ L{SSHUserAuthClient.getPublicKey()} is able to get a public key from
+ the first file described by its options' C{identitys} list, and return
+ the corresponding public L{Key} object.
+ """
+ options = ConchOptions()
+ options.identitys = [self.rsaFile.path]
+ client = SSHUserAuthClient("user", options, None)
+ key = client.getPublicKey()
+ self.assertEquals(key.isPublic(), True)
+ self.assertEquals(key, self.rsaPublic)
+
+
+ def test_getPublicKeyAgentFallback(self):
+ """
+ If an agent is present, but doesn't return a key,
+ L{SSHUserAuthClient.getPublicKey} continue with the normal key lookup.
+ """
+ options = ConchOptions()
+ options.identitys = [self.rsaFile.path]
+ agent = SSHAgentClient()
+ client = SSHUserAuthClient("user", options, None)
+ client.keyAgent = agent
+ key = client.getPublicKey()
+ self.assertEquals(key.isPublic(), True)
+ self.assertEquals(key, self.rsaPublic)
+
+
+ def test_getPublicKeyBadKeyError(self):
+ """
+ If L{keys.Key.fromFile} raises a L{keys.BadKeyError}, the
+ L{SSHUserAuthClient.getPublicKey} tries again to get a public key by
+ calling itself recursively.
+ """
+ options = ConchOptions()
+ self.tmpdir.child('id_dsa.pub').setContent(keydata.publicDSA_openssh)
+ dsaFile = self.tmpdir.child('id_dsa')
+ dsaFile.setContent(keydata.privateDSA_openssh)
+ options.identitys = [self.rsaFile.path, dsaFile.path]
+ self.tmpdir.child('id_rsa.pub').setContent('not a key!')
+ client = SSHUserAuthClient("user", options, None)
+ key = client.getPublicKey()
+ self.assertEquals(key.isPublic(), True)
+ self.assertEquals(key, Key.fromString(keydata.publicDSA_openssh))
+ self.assertEquals(client.usedFiles, [self.rsaFile.path, dsaFile.path])
+
+
+ def test_getPrivateKey(self):
+ """
+ L{SSHUserAuthClient.getPrivateKey} will load a private key from the
+ last used file populated by L{SSHUserAuthClient.getPublicKey}, and
+ return a L{Deferred} which fires with the corresponding private L{Key}.
+ """
+ rsaPrivate = Key.fromString(keydata.privateRSA_openssh)
+ options = ConchOptions()
+ options.identitys = [self.rsaFile.path]
+ client = SSHUserAuthClient("user", options, None)
+ # Populate the list of used files
+ client.getPublicKey()
+
+ def _cbGetPrivateKey(key):
+ self.assertEquals(key.isPublic(), False)
+ self.assertEquals(key, rsaPrivate)
+
+ return client.getPrivateKey().addCallback(_cbGetPrivateKey)
+
+
+ def test_getPrivateKeyPassphrase(self):
+ """
+ L{SSHUserAuthClient} can get a private key from a file, and return a
+ Deferred called back with a private L{Key} object, even if the key is
+ encrypted.
+ """
+ rsaPrivate = Key.fromString(keydata.privateRSA_openssh)
+ passphrase = 'this is the passphrase'
+ self.rsaFile.setContent(rsaPrivate.toString('openssh', passphrase))
+ options = ConchOptions()
+ options.identitys = [self.rsaFile.path]
+ client = SSHUserAuthClient("user", options, None)
+ # Populate the list of used files
+ client.getPublicKey()
+
+ def _getPassword(prompt):
+ self.assertEquals(prompt,
+ "Enter passphrase for key '%s': " % (
+ self.rsaFile.path,))
+ return passphrase
+
+ def _cbGetPrivateKey(key):
+ self.assertEquals(key.isPublic(), False)
+ self.assertEquals(key, rsaPrivate)
+
+ self.patch(client, '_getPassword', _getPassword)
+ return client.getPrivateKey().addCallback(_cbGetPrivateKey)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_filetransfer.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_filetransfer.py
new file mode 100644
index 0000000000..9d7a021671
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_filetransfer.py
@@ -0,0 +1,677 @@
+# -*- test-case-name: twisted.conch.test.test_filetransfer -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE file for details.
+
+
+import os
+import re
+import struct
+import sys
+
+from twisted.trial import unittest
+try:
+ from twisted.conch import unix
+ unix # shut up pyflakes
+except ImportError:
+ unix = None
+ try:
+ del sys.modules['twisted.conch.unix'] # remove the bad import
+ except KeyError:
+ # In Python 2.4, the bad import has already been cleaned up for us.
+ # Hooray.
+ pass
+
+from twisted.conch import avatar
+from twisted.conch.ssh import common, connection, filetransfer, session
+from twisted.internet import defer
+from twisted.protocols import loopback
+from twisted.python import components
+
+
+class TestAvatar(avatar.ConchUser):
+ def __init__(self):
+ avatar.ConchUser.__init__(self)
+ self.channelLookup['session'] = session.SSHSession
+ self.subsystemLookup['sftp'] = filetransfer.FileTransferServer
+
+ def _runAsUser(self, f, *args, **kw):
+ try:
+ f = iter(f)
+ except TypeError:
+ f = [(f, args, kw)]
+ for i in f:
+ func = i[0]
+ args = len(i)>1 and i[1] or ()
+ kw = len(i)>2 and i[2] or {}
+ r = func(*args, **kw)
+ return r
+
+
+class FileTransferTestAvatar(TestAvatar):
+
+ def __init__(self, homeDir):
+ TestAvatar.__init__(self)
+ self.homeDir = homeDir
+
+ def getHomeDir(self):
+ return os.path.join(os.getcwd(), self.homeDir)
+
+
+class ConchSessionForTestAvatar:
+
+ def __init__(self, avatar):
+ self.avatar = avatar
+
+if unix:
+ if not hasattr(unix, 'SFTPServerForUnixConchUser'):
+ # unix should either be a fully working module, or None. I'm not sure
+ # how this happens, but on win32 it does. Try to cope. --spiv.
+ import warnings
+ warnings.warn(("twisted.conch.unix imported %r, "
+ "but doesn't define SFTPServerForUnixConchUser'")
+ % (unix,))
+ unix = None
+ else:
+ class FileTransferForTestAvatar(unix.SFTPServerForUnixConchUser):
+
+ def gotVersion(self, version, otherExt):
+ return {'conchTest' : 'ext data'}
+
+ def extendedRequest(self, extName, extData):
+ if extName == 'testExtendedRequest':
+ return 'bar'
+ raise NotImplementedError
+
+ components.registerAdapter(FileTransferForTestAvatar,
+ TestAvatar,
+ filetransfer.ISFTPServer)
+
+class SFTPTestBase(unittest.TestCase):
+
+ def setUp(self):
+ self.testDir = self.mktemp()
+ # Give the testDir another level so we can safely "cd .." from it in
+ # tests.
+ self.testDir = os.path.join(self.testDir, 'extra')
+ os.makedirs(os.path.join(self.testDir, 'testDirectory'))
+
+ f = file(os.path.join(self.testDir, 'testfile1'),'w')
+ f.write('a'*10+'b'*10)
+ f.write(file('/dev/urandom').read(1024*64)) # random data
+ os.chmod(os.path.join(self.testDir, 'testfile1'), 0644)
+ file(os.path.join(self.testDir, 'testRemoveFile'), 'w').write('a')
+ file(os.path.join(self.testDir, 'testRenameFile'), 'w').write('a')
+ file(os.path.join(self.testDir, '.testHiddenFile'), 'w').write('a')
+
+
+class TestOurServerOurClient(SFTPTestBase):
+
+ if not unix:
+ skip = "can't run on non-posix computers"
+
+ def setUp(self):
+ SFTPTestBase.setUp(self)
+
+ self.avatar = FileTransferTestAvatar(self.testDir)
+ self.server = filetransfer.FileTransferServer(avatar=self.avatar)
+ clientTransport = loopback.LoopbackRelay(self.server)
+
+ self.client = filetransfer.FileTransferClient()
+ self._serverVersion = None
+ self._extData = None
+ def _(serverVersion, extData):
+ self._serverVersion = serverVersion
+ self._extData = extData
+ self.client.gotServerVersion = _
+ serverTransport = loopback.LoopbackRelay(self.client)
+ self.client.makeConnection(clientTransport)
+ self.server.makeConnection(serverTransport)
+
+ self.clientTransport = clientTransport
+ self.serverTransport = serverTransport
+
+ self._emptyBuffers()
+
+
+ def _emptyBuffers(self):
+ while self.serverTransport.buffer or self.clientTransport.buffer:
+ self.serverTransport.clearBuffer()
+ self.clientTransport.clearBuffer()
+
+
+ def tearDown(self):
+ self.serverTransport.loseConnection()
+ self.clientTransport.loseConnection()
+ self.serverTransport.clearBuffer()
+ self.clientTransport.clearBuffer()
+
+
+ def testServerVersion(self):
+ self.failUnlessEqual(self._serverVersion, 3)
+ self.failUnlessEqual(self._extData, {'conchTest' : 'ext data'})
+
+
+ def test_openedFileClosedWithConnection(self):
+ """
+ A file opened with C{openFile} is close when the connection is lost.
+ """
+ d = self.client.openFile("testfile1", filetransfer.FXF_READ |
+ filetransfer.FXF_WRITE, {})
+ self._emptyBuffers()
+
+ oldClose = os.close
+ closed = []
+ def close(fd):
+ closed.append(fd)
+ oldClose(fd)
+
+ self.patch(os, "close", close)
+
+ def _fileOpened(openFile):
+ fd = self.server.openFiles[openFile.handle[4:]].fd
+ self.serverTransport.loseConnection()
+ self.clientTransport.loseConnection()
+ self.serverTransport.clearBuffer()
+ self.clientTransport.clearBuffer()
+ self.assertEquals(self.server.openFiles, {})
+ self.assertIn(fd, closed)
+
+ d.addCallback(_fileOpened)
+ return d
+
+
+ def test_openedDirectoryClosedWithConnection(self):
+ """
+ A directory opened with C{openDirectory} is close when the connection
+ is lost.
+ """
+ d = self.client.openDirectory('')
+ self._emptyBuffers()
+
+ def _getFiles(openDir):
+ self.serverTransport.loseConnection()
+ self.clientTransport.loseConnection()
+ self.serverTransport.clearBuffer()
+ self.clientTransport.clearBuffer()
+ self.assertEquals(self.server.openDirs, {})
+
+ d.addCallback(_getFiles)
+ return d
+
+
+ def testOpenFileIO(self):
+ d = self.client.openFile("testfile1", filetransfer.FXF_READ |
+ filetransfer.FXF_WRITE, {})
+ self._emptyBuffers()
+
+ def _fileOpened(openFile):
+ self.failUnlessEqual(openFile, filetransfer.ISFTPFile(openFile))
+ d = _readChunk(openFile)
+ d.addCallback(_writeChunk, openFile)
+ return d
+
+ def _readChunk(openFile):
+ d = openFile.readChunk(0, 20)
+ self._emptyBuffers()
+ d.addCallback(self.failUnlessEqual, 'a'*10 + 'b'*10)
+ return d
+
+ def _writeChunk(_, openFile):
+ d = openFile.writeChunk(20, 'c'*10)
+ self._emptyBuffers()
+ d.addCallback(_readChunk2, openFile)
+ return d
+
+ def _readChunk2(_, openFile):
+ d = openFile.readChunk(0, 30)
+ self._emptyBuffers()
+ d.addCallback(self.failUnlessEqual, 'a'*10 + 'b'*10 + 'c'*10)
+ return d
+
+ d.addCallback(_fileOpened)
+ return d
+
+ def testClosedFileGetAttrs(self):
+ d = self.client.openFile("testfile1", filetransfer.FXF_READ |
+ filetransfer.FXF_WRITE, {})
+ self._emptyBuffers()
+
+ def _getAttrs(_, openFile):
+ d = openFile.getAttrs()
+ self._emptyBuffers()
+ return d
+
+ def _err(f):
+ self.flushLoggedErrors()
+ return f
+
+ def _close(openFile):
+ d = openFile.close()
+ self._emptyBuffers()
+ d.addCallback(_getAttrs, openFile)
+ d.addErrback(_err)
+ return self.assertFailure(d, filetransfer.SFTPError)
+
+ d.addCallback(_close)
+ return d
+
+ def testOpenFileAttributes(self):
+ d = self.client.openFile("testfile1", filetransfer.FXF_READ |
+ filetransfer.FXF_WRITE, {})
+ self._emptyBuffers()
+
+ def _getAttrs(openFile):
+ d = openFile.getAttrs()
+ self._emptyBuffers()
+ d.addCallback(_getAttrs2)
+ return d
+
+ def _getAttrs2(attrs1):
+ d = self.client.getAttrs('testfile1')
+ self._emptyBuffers()
+ d.addCallback(self.failUnlessEqual, attrs1)
+ return d
+
+ return d.addCallback(_getAttrs)
+
+
+ def testOpenFileSetAttrs(self):
+ # XXX test setAttrs
+ # Ok, how about this for a start? It caught a bug :) -- spiv.
+ d = self.client.openFile("testfile1", filetransfer.FXF_READ |
+ filetransfer.FXF_WRITE, {})
+ self._emptyBuffers()
+
+ def _getAttrs(openFile):
+ d = openFile.getAttrs()
+ self._emptyBuffers()
+ d.addCallback(_setAttrs)
+ return d
+
+ def _setAttrs(attrs):
+ attrs['atime'] = 0
+ d = self.client.setAttrs('testfile1', attrs)
+ self._emptyBuffers()
+ d.addCallback(_getAttrs2)
+ d.addCallback(self.failUnlessEqual, attrs)
+ return d
+
+ def _getAttrs2(_):
+ d = self.client.getAttrs('testfile1')
+ self._emptyBuffers()
+ return d
+
+ d.addCallback(_getAttrs)
+ return d
+
+
+ def test_openFileExtendedAttributes(self):
+ """
+ Check that L{filetransfer.FileTransferClient.openFile} can send
+ extended attributes, that should be extracted server side. By default,
+ they are ignored, so we just verify they are correctly parsed.
+ """
+ savedAttributes = {}
+ oldOpenFile = self.server.client.openFile
+ def openFile(filename, flags, attrs):
+ savedAttributes.update(attrs)
+ return oldOpenFile(filename, flags, attrs)
+ self.server.client.openFile = openFile
+
+ d = self.client.openFile("testfile1", filetransfer.FXF_READ |
+ filetransfer.FXF_WRITE, {"ext_foo": "bar"})
+ self._emptyBuffers()
+
+ def check(ign):
+ self.assertEquals(savedAttributes, {"ext_foo": "bar"})
+
+ return d.addCallback(check)
+
+
+ def testRemoveFile(self):
+ d = self.client.getAttrs("testRemoveFile")
+ self._emptyBuffers()
+ def _removeFile(ignored):
+ d = self.client.removeFile("testRemoveFile")
+ self._emptyBuffers()
+ return d
+ d.addCallback(_removeFile)
+ d.addCallback(_removeFile)
+ return self.assertFailure(d, filetransfer.SFTPError)
+
+ def testRenameFile(self):
+ d = self.client.getAttrs("testRenameFile")
+ self._emptyBuffers()
+ def _rename(attrs):
+ d = self.client.renameFile("testRenameFile", "testRenamedFile")
+ self._emptyBuffers()
+ d.addCallback(_testRenamed, attrs)
+ return d
+ def _testRenamed(_, attrs):
+ d = self.client.getAttrs("testRenamedFile")
+ self._emptyBuffers()
+ d.addCallback(self.failUnlessEqual, attrs)
+ return d.addCallback(_rename)
+
+ def testDirectoryBad(self):
+ d = self.client.getAttrs("testMakeDirectory")
+ self._emptyBuffers()
+ return self.assertFailure(d, filetransfer.SFTPError)
+
+ def testDirectoryCreation(self):
+ d = self.client.makeDirectory("testMakeDirectory", {})
+ self._emptyBuffers()
+
+ def _getAttrs(_):
+ d = self.client.getAttrs("testMakeDirectory")
+ self._emptyBuffers()
+ return d
+
+ # XXX not until version 4/5
+ # self.failUnlessEqual(filetransfer.FILEXFER_TYPE_DIRECTORY&attrs['type'],
+ # filetransfer.FILEXFER_TYPE_DIRECTORY)
+
+ def _removeDirectory(_):
+ d = self.client.removeDirectory("testMakeDirectory")
+ self._emptyBuffers()
+ return d
+
+ d.addCallback(_getAttrs)
+ d.addCallback(_removeDirectory)
+ d.addCallback(_getAttrs)
+ return self.assertFailure(d, filetransfer.SFTPError)
+
+ def testOpenDirectory(self):
+ d = self.client.openDirectory('')
+ self._emptyBuffers()
+ files = []
+
+ def _getFiles(openDir):
+ def append(f):
+ files.append(f)
+ return openDir
+ d = defer.maybeDeferred(openDir.next)
+ self._emptyBuffers()
+ d.addCallback(append)
+ d.addCallback(_getFiles)
+ d.addErrback(_close, openDir)
+ return d
+
+ def _checkFiles(ignored):
+ fs = list(zip(*files)[0])
+ fs.sort()
+ self.failUnlessEqual(fs,
+ ['.testHiddenFile', 'testDirectory',
+ 'testRemoveFile', 'testRenameFile',
+ 'testfile1'])
+
+ def _close(_, openDir):
+ d = openDir.close()
+ self._emptyBuffers()
+ return d
+
+ d.addCallback(_getFiles)
+ d.addCallback(_checkFiles)
+ return d
+
+ def testLinkDoesntExist(self):
+ d = self.client.getAttrs('testLink')
+ self._emptyBuffers()
+ return self.assertFailure(d, filetransfer.SFTPError)
+
+ def testLinkSharesAttrs(self):
+ d = self.client.makeLink('testLink', 'testfile1')
+ self._emptyBuffers()
+ def _getFirstAttrs(_):
+ d = self.client.getAttrs('testLink', 1)
+ self._emptyBuffers()
+ return d
+ def _getSecondAttrs(firstAttrs):
+ d = self.client.getAttrs('testfile1')
+ self._emptyBuffers()
+ d.addCallback(self.assertEqual, firstAttrs)
+ return d
+ d.addCallback(_getFirstAttrs)
+ return d.addCallback(_getSecondAttrs)
+
+ def testLinkPath(self):
+ d = self.client.makeLink('testLink', 'testfile1')
+ self._emptyBuffers()
+ def _readLink(_):
+ d = self.client.readLink('testLink')
+ self._emptyBuffers()
+ d.addCallback(self.failUnlessEqual,
+ os.path.join(os.getcwd(), self.testDir, 'testfile1'))
+ return d
+ def _realPath(_):
+ d = self.client.realPath('testLink')
+ self._emptyBuffers()
+ d.addCallback(self.failUnlessEqual,
+ os.path.join(os.getcwd(), self.testDir, 'testfile1'))
+ return d
+ d.addCallback(_readLink)
+ d.addCallback(_realPath)
+ return d
+
+ def testExtendedRequest(self):
+ d = self.client.extendedRequest('testExtendedRequest', 'foo')
+ self._emptyBuffers()
+ d.addCallback(self.failUnlessEqual, 'bar')
+ d.addCallback(self._cbTestExtendedRequest)
+ return d
+
+ def _cbTestExtendedRequest(self, ignored):
+ d = self.client.extendedRequest('testBadRequest', '')
+ self._emptyBuffers()
+ return self.assertFailure(d, NotImplementedError)
+
+
+class FakeConn:
+ def sendClose(self, channel):
+ pass
+
+
+class TestFileTransferClose(unittest.TestCase):
+
+ if not unix:
+ skip = "can't run on non-posix computers"
+
+ def setUp(self):
+ self.avatar = TestAvatar()
+
+ def buildServerConnection(self):
+ # make a server connection
+ conn = connection.SSHConnection()
+ # server connections have a 'self.transport.avatar'.
+ class DummyTransport:
+ def __init__(self):
+ self.transport = self
+ def sendPacket(self, kind, data):
+ pass
+ def logPrefix(self):
+ return 'dummy transport'
+ conn.transport = DummyTransport()
+ conn.transport.avatar = self.avatar
+ return conn
+
+ def interceptConnectionLost(self, sftpServer):
+ self.connectionLostFired = False
+ origConnectionLost = sftpServer.connectionLost
+ def connectionLost(reason):
+ self.connectionLostFired = True
+ origConnectionLost(reason)
+ sftpServer.connectionLost = connectionLost
+
+ def assertSFTPConnectionLost(self):
+ self.assertTrue(self.connectionLostFired,
+ "sftpServer's connectionLost was not called")
+
+ def test_sessionClose(self):
+ """
+ Closing a session should notify an SFTP subsystem launched by that
+ session.
+ """
+ # make a session
+ testSession = session.SSHSession(conn=FakeConn(), avatar=self.avatar)
+
+ # start an SFTP subsystem on the session
+ testSession.request_subsystem(common.NS('sftp'))
+ sftpServer = testSession.client.transport.proto
+
+ # intercept connectionLost so we can check that it's called
+ self.interceptConnectionLost(sftpServer)
+
+ # close session
+ testSession.closeReceived()
+
+ self.assertSFTPConnectionLost()
+
+ def test_clientClosesChannelOnConnnection(self):
+ """
+ A client sending CHANNEL_CLOSE should trigger closeReceived on the
+ associated channel instance.
+ """
+ conn = self.buildServerConnection()
+
+ # somehow get a session
+ packet = common.NS('session') + struct.pack('>L', 0) * 3
+ conn.ssh_CHANNEL_OPEN(packet)
+ sessionChannel = conn.channels[0]
+
+ sessionChannel.request_subsystem(common.NS('sftp'))
+ sftpServer = sessionChannel.client.transport.proto
+ self.interceptConnectionLost(sftpServer)
+
+ # intercept closeReceived
+ self.interceptConnectionLost(sftpServer)
+
+ # close the connection
+ conn.ssh_CHANNEL_CLOSE(struct.pack('>L', 0))
+
+ self.assertSFTPConnectionLost()
+
+
+ def test_stopConnectionServiceClosesChannel(self):
+ """
+ Closing an SSH connection should close all sessions within it.
+ """
+ conn = self.buildServerConnection()
+
+ # somehow get a session
+ packet = common.NS('session') + struct.pack('>L', 0) * 3
+ conn.ssh_CHANNEL_OPEN(packet)
+ sessionChannel = conn.channels[0]
+
+ sessionChannel.request_subsystem(common.NS('sftp'))
+ sftpServer = sessionChannel.client.transport.proto
+ self.interceptConnectionLost(sftpServer)
+
+ # close the connection
+ conn.serviceStopped()
+
+ self.assertSFTPConnectionLost()
+
+
+
+class TestConstants(unittest.TestCase):
+ """
+ Tests for the constants used by the SFTP protocol implementation.
+
+ @ivar filexferSpecExcerpts: Excerpts from the
+ draft-ietf-secsh-filexfer-02.txt (draft) specification of the SFTP
+ protocol. There are more recent drafts of the specification, but this
+ one describes version 3, which is what conch (and OpenSSH) implements.
+ """
+
+
+ filexferSpecExcerpts = [
+ """
+ The following values are defined for packet types.
+
+ #define SSH_FXP_INIT 1
+ #define SSH_FXP_VERSION 2
+ #define SSH_FXP_OPEN 3
+ #define SSH_FXP_CLOSE 4
+ #define SSH_FXP_READ 5
+ #define SSH_FXP_WRITE 6
+ #define SSH_FXP_LSTAT 7
+ #define SSH_FXP_FSTAT 8
+ #define SSH_FXP_SETSTAT 9
+ #define SSH_FXP_FSETSTAT 10
+ #define SSH_FXP_OPENDIR 11
+ #define SSH_FXP_READDIR 12
+ #define SSH_FXP_REMOVE 13
+ #define SSH_FXP_MKDIR 14
+ #define SSH_FXP_RMDIR 15
+ #define SSH_FXP_REALPATH 16
+ #define SSH_FXP_STAT 17
+ #define SSH_FXP_RENAME 18
+ #define SSH_FXP_READLINK 19
+ #define SSH_FXP_SYMLINK 20
+ #define SSH_FXP_STATUS 101
+ #define SSH_FXP_HANDLE 102
+ #define SSH_FXP_DATA 103
+ #define SSH_FXP_NAME 104
+ #define SSH_FXP_ATTRS 105
+ #define SSH_FXP_EXTENDED 200
+ #define SSH_FXP_EXTENDED_REPLY 201
+
+ Additional packet types should only be defined if the protocol
+ version number (see Section ``Protocol Initialization'') is
+ incremented, and their use MUST be negotiated using the version
+ number. However, the SSH_FXP_EXTENDED and SSH_FXP_EXTENDED_REPLY
+ packets can be used to implement vendor-specific extensions. See
+ Section ``Vendor-Specific-Extensions'' for more details.
+ """,
+ """
+ The flags bits are defined to have the following values:
+
+ #define SSH_FILEXFER_ATTR_SIZE 0x00000001
+ #define SSH_FILEXFER_ATTR_UIDGID 0x00000002
+ #define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004
+ #define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008
+ #define SSH_FILEXFER_ATTR_EXTENDED 0x80000000
+
+ """,
+ """
+ The `pflags' field is a bitmask. The following bits have been
+ defined.
+
+ #define SSH_FXF_READ 0x00000001
+ #define SSH_FXF_WRITE 0x00000002
+ #define SSH_FXF_APPEND 0x00000004
+ #define SSH_FXF_CREAT 0x00000008
+ #define SSH_FXF_TRUNC 0x00000010
+ #define SSH_FXF_EXCL 0x00000020
+ """,
+ """
+ Currently, the following values are defined (other values may be
+ defined by future versions of this protocol):
+
+ #define SSH_FX_OK 0
+ #define SSH_FX_EOF 1
+ #define SSH_FX_NO_SUCH_FILE 2
+ #define SSH_FX_PERMISSION_DENIED 3
+ #define SSH_FX_FAILURE 4
+ #define SSH_FX_BAD_MESSAGE 5
+ #define SSH_FX_NO_CONNECTION 6
+ #define SSH_FX_CONNECTION_LOST 7
+ #define SSH_FX_OP_UNSUPPORTED 8
+ """]
+
+
+ def test_constantsAgainstSpec(self):
+ """
+ The constants used by the SFTP protocol implementation match those
+ found by searching through the spec.
+ """
+ constants = {}
+ for excerpt in self.filexferSpecExcerpts:
+ for line in excerpt.splitlines():
+ m = re.match('^\s*#define SSH_([A-Z_]+)\s+([0-9x]*)\s*$', line)
+ if m:
+ constants[m.group(1)] = long(m.group(2), 0)
+ self.assertTrue(
+ len(constants) > 0, "No constants found (the test must be buggy).")
+ for k, v in constants.items():
+ self.assertEqual(v, getattr(filetransfer, k))
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_helper.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_helper.py
new file mode 100644
index 0000000000..95db6ab2c2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_helper.py
@@ -0,0 +1,560 @@
+# -*- test-case-name: twisted.conch.test.test_helper -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.conch.insults import helper
+from twisted.conch.insults.insults import G0, G1, G2, G3
+from twisted.conch.insults.insults import modes, privateModes
+from twisted.conch.insults.insults import NORMAL, BOLD, UNDERLINE, BLINK, REVERSE_VIDEO
+
+from twisted.trial import unittest
+
+WIDTH = 80
+HEIGHT = 24
+
+class BufferTestCase(unittest.TestCase):
+ def setUp(self):
+ self.term = helper.TerminalBuffer()
+ self.term.connectionMade()
+
+ def testInitialState(self):
+ self.assertEquals(self.term.width, WIDTH)
+ self.assertEquals(self.term.height, HEIGHT)
+ self.assertEquals(str(self.term),
+ '\n' * (HEIGHT - 1))
+ self.assertEquals(self.term.reportCursorPosition(), (0, 0))
+
+
+ def test_initialPrivateModes(self):
+ """
+ Verify that only DEC Auto Wrap Mode (DECAWM) and DEC Text Cursor Enable
+ Mode (DECTCEM) are initially in the Set Mode (SM) state.
+ """
+ self.assertEqual(
+ {privateModes.AUTO_WRAP: True,
+ privateModes.CURSOR_MODE: True},
+ self.term.privateModes)
+
+
+ def test_carriageReturn(self):
+ """
+ C{"\r"} moves the cursor to the first column in the current row.
+ """
+ self.term.cursorForward(5)
+ self.term.cursorDown(3)
+ self.assertEqual(self.term.reportCursorPosition(), (5, 3))
+ self.term.insertAtCursor("\r")
+ self.assertEqual(self.term.reportCursorPosition(), (0, 3))
+
+
+ def test_linefeed(self):
+ """
+ C{"\n"} moves the cursor to the next row without changing the column.
+ """
+ self.term.cursorForward(5)
+ self.assertEqual(self.term.reportCursorPosition(), (5, 0))
+ self.term.insertAtCursor("\n")
+ self.assertEqual(self.term.reportCursorPosition(), (5, 1))
+
+
+ def test_newline(self):
+ """
+ C{write} transforms C{"\n"} into C{"\r\n"}.
+ """
+ self.term.cursorForward(5)
+ self.term.cursorDown(3)
+ self.assertEqual(self.term.reportCursorPosition(), (5, 3))
+ self.term.write("\n")
+ self.assertEqual(self.term.reportCursorPosition(), (0, 4))
+
+
+ def test_setPrivateModes(self):
+ """
+ Verify that L{helper.TerminalBuffer.setPrivateModes} changes the Set
+ Mode (SM) state to "set" for the private modes it is passed.
+ """
+ expected = self.term.privateModes.copy()
+ self.term.setPrivateModes([privateModes.SCROLL, privateModes.SCREEN])
+ expected[privateModes.SCROLL] = True
+ expected[privateModes.SCREEN] = True
+ self.assertEqual(expected, self.term.privateModes)
+
+
+ def test_resetPrivateModes(self):
+ """
+ Verify that L{helper.TerminalBuffer.resetPrivateModes} changes the Set
+ Mode (SM) state to "reset" for the private modes it is passed.
+ """
+ expected = self.term.privateModes.copy()
+ self.term.resetPrivateModes([privateModes.AUTO_WRAP, privateModes.CURSOR_MODE])
+ del expected[privateModes.AUTO_WRAP]
+ del expected[privateModes.CURSOR_MODE]
+ self.assertEqual(expected, self.term.privateModes)
+
+
+ def testCursorDown(self):
+ self.term.cursorDown(3)
+ self.assertEquals(self.term.reportCursorPosition(), (0, 3))
+ self.term.cursorDown()
+ self.assertEquals(self.term.reportCursorPosition(), (0, 4))
+ self.term.cursorDown(HEIGHT)
+ self.assertEquals(self.term.reportCursorPosition(), (0, HEIGHT - 1))
+
+ def testCursorUp(self):
+ self.term.cursorUp(5)
+ self.assertEquals(self.term.reportCursorPosition(), (0, 0))
+
+ self.term.cursorDown(20)
+ self.term.cursorUp(1)
+ self.assertEquals(self.term.reportCursorPosition(), (0, 19))
+
+ self.term.cursorUp(19)
+ self.assertEquals(self.term.reportCursorPosition(), (0, 0))
+
+ def testCursorForward(self):
+ self.term.cursorForward(2)
+ self.assertEquals(self.term.reportCursorPosition(), (2, 0))
+ self.term.cursorForward(2)
+ self.assertEquals(self.term.reportCursorPosition(), (4, 0))
+ self.term.cursorForward(WIDTH)
+ self.assertEquals(self.term.reportCursorPosition(), (WIDTH, 0))
+
+ def testCursorBackward(self):
+ self.term.cursorForward(10)
+ self.term.cursorBackward(2)
+ self.assertEquals(self.term.reportCursorPosition(), (8, 0))
+ self.term.cursorBackward(7)
+ self.assertEquals(self.term.reportCursorPosition(), (1, 0))
+ self.term.cursorBackward(1)
+ self.assertEquals(self.term.reportCursorPosition(), (0, 0))
+ self.term.cursorBackward(1)
+ self.assertEquals(self.term.reportCursorPosition(), (0, 0))
+
+ def testCursorPositioning(self):
+ self.term.cursorPosition(3, 9)
+ self.assertEquals(self.term.reportCursorPosition(), (3, 9))
+
+ def testSimpleWriting(self):
+ s = "Hello, world."
+ self.term.write(s)
+ self.assertEquals(
+ str(self.term),
+ s + '\n' +
+ '\n' * (HEIGHT - 2))
+
+ def testOvertype(self):
+ s = "hello, world."
+ self.term.write(s)
+ self.term.cursorBackward(len(s))
+ self.term.resetModes([modes.IRM])
+ self.term.write("H")
+ self.assertEquals(
+ str(self.term),
+ ("H" + s[1:]) + '\n' +
+ '\n' * (HEIGHT - 2))
+
+ def testInsert(self):
+ s = "ello, world."
+ self.term.write(s)
+ self.term.cursorBackward(len(s))
+ self.term.setModes([modes.IRM])
+ self.term.write("H")
+ self.assertEquals(
+ str(self.term),
+ ("H" + s) + '\n' +
+ '\n' * (HEIGHT - 2))
+
+ def testWritingInTheMiddle(self):
+ s = "Hello, world."
+ self.term.cursorDown(5)
+ self.term.cursorForward(5)
+ self.term.write(s)
+ self.assertEquals(
+ str(self.term),
+ '\n' * 5 +
+ (self.term.fill * 5) + s + '\n' +
+ '\n' * (HEIGHT - 7))
+
+ def testWritingWrappedAtEndOfLine(self):
+ s = "Hello, world."
+ self.term.cursorForward(WIDTH - 5)
+ self.term.write(s)
+ self.assertEquals(
+ str(self.term),
+ s[:5].rjust(WIDTH) + '\n' +
+ s[5:] + '\n' +
+ '\n' * (HEIGHT - 3))
+
+ def testIndex(self):
+ self.term.index()
+ self.assertEquals(self.term.reportCursorPosition(), (0, 1))
+ self.term.cursorDown(HEIGHT)
+ self.assertEquals(self.term.reportCursorPosition(), (0, HEIGHT - 1))
+ self.term.index()
+ self.assertEquals(self.term.reportCursorPosition(), (0, HEIGHT - 1))
+
+ def testReverseIndex(self):
+ self.term.reverseIndex()
+ self.assertEquals(self.term.reportCursorPosition(), (0, 0))
+ self.term.cursorDown(2)
+ self.assertEquals(self.term.reportCursorPosition(), (0, 2))
+ self.term.reverseIndex()
+ self.assertEquals(self.term.reportCursorPosition(), (0, 1))
+
+ def test_nextLine(self):
+ """
+ C{nextLine} positions the cursor at the beginning of the row below the
+ current row.
+ """
+ self.term.nextLine()
+ self.assertEquals(self.term.reportCursorPosition(), (0, 1))
+ self.term.cursorForward(5)
+ self.assertEquals(self.term.reportCursorPosition(), (5, 1))
+ self.term.nextLine()
+ self.assertEquals(self.term.reportCursorPosition(), (0, 2))
+
+ def testSaveCursor(self):
+ self.term.cursorDown(5)
+ self.term.cursorForward(7)
+ self.assertEquals(self.term.reportCursorPosition(), (7, 5))
+ self.term.saveCursor()
+ self.term.cursorDown(7)
+ self.term.cursorBackward(3)
+ self.assertEquals(self.term.reportCursorPosition(), (4, 12))
+ self.term.restoreCursor()
+ self.assertEquals(self.term.reportCursorPosition(), (7, 5))
+
+ def testSingleShifts(self):
+ self.term.singleShift2()
+ self.term.write('Hi')
+
+ ch = self.term.getCharacter(0, 0)
+ self.assertEquals(ch[0], 'H')
+ self.assertEquals(ch[1].charset, G2)
+
+ ch = self.term.getCharacter(1, 0)
+ self.assertEquals(ch[0], 'i')
+ self.assertEquals(ch[1].charset, G0)
+
+ self.term.singleShift3()
+ self.term.write('!!')
+
+ ch = self.term.getCharacter(2, 0)
+ self.assertEquals(ch[0], '!')
+ self.assertEquals(ch[1].charset, G3)
+
+ ch = self.term.getCharacter(3, 0)
+ self.assertEquals(ch[0], '!')
+ self.assertEquals(ch[1].charset, G0)
+
+ def testShifting(self):
+ s1 = "Hello"
+ s2 = "World"
+ s3 = "Bye!"
+ self.term.write("Hello\n")
+ self.term.shiftOut()
+ self.term.write("World\n")
+ self.term.shiftIn()
+ self.term.write("Bye!\n")
+
+ g = G0
+ h = 0
+ for s in (s1, s2, s3):
+ for i in range(len(s)):
+ ch = self.term.getCharacter(i, h)
+ self.assertEquals(ch[0], s[i])
+ self.assertEquals(ch[1].charset, g)
+ g = g == G0 and G1 or G0
+ h += 1
+
+ def testGraphicRendition(self):
+ self.term.selectGraphicRendition(BOLD, UNDERLINE, BLINK, REVERSE_VIDEO)
+ self.term.write('W')
+ self.term.selectGraphicRendition(NORMAL)
+ self.term.write('X')
+ self.term.selectGraphicRendition(BLINK)
+ self.term.write('Y')
+ self.term.selectGraphicRendition(BOLD)
+ self.term.write('Z')
+
+ ch = self.term.getCharacter(0, 0)
+ self.assertEquals(ch[0], 'W')
+ self.failUnless(ch[1].bold)
+ self.failUnless(ch[1].underline)
+ self.failUnless(ch[1].blink)
+ self.failUnless(ch[1].reverseVideo)
+
+ ch = self.term.getCharacter(1, 0)
+ self.assertEquals(ch[0], 'X')
+ self.failIf(ch[1].bold)
+ self.failIf(ch[1].underline)
+ self.failIf(ch[1].blink)
+ self.failIf(ch[1].reverseVideo)
+
+ ch = self.term.getCharacter(2, 0)
+ self.assertEquals(ch[0], 'Y')
+ self.failUnless(ch[1].blink)
+ self.failIf(ch[1].bold)
+ self.failIf(ch[1].underline)
+ self.failIf(ch[1].reverseVideo)
+
+ ch = self.term.getCharacter(3, 0)
+ self.assertEquals(ch[0], 'Z')
+ self.failUnless(ch[1].blink)
+ self.failUnless(ch[1].bold)
+ self.failIf(ch[1].underline)
+ self.failIf(ch[1].reverseVideo)
+
+ def testColorAttributes(self):
+ s1 = "Merry xmas"
+ s2 = "Just kidding"
+ self.term.selectGraphicRendition(helper.FOREGROUND + helper.RED,
+ helper.BACKGROUND + helper.GREEN)
+ self.term.write(s1 + "\n")
+ self.term.selectGraphicRendition(NORMAL)
+ self.term.write(s2 + "\n")
+
+ for i in range(len(s1)):
+ ch = self.term.getCharacter(i, 0)
+ self.assertEquals(ch[0], s1[i])
+ self.assertEquals(ch[1].charset, G0)
+ self.assertEquals(ch[1].bold, False)
+ self.assertEquals(ch[1].underline, False)
+ self.assertEquals(ch[1].blink, False)
+ self.assertEquals(ch[1].reverseVideo, False)
+ self.assertEquals(ch[1].foreground, helper.RED)
+ self.assertEquals(ch[1].background, helper.GREEN)
+
+ for i in range(len(s2)):
+ ch = self.term.getCharacter(i, 1)
+ self.assertEquals(ch[0], s2[i])
+ self.assertEquals(ch[1].charset, G0)
+ self.assertEquals(ch[1].bold, False)
+ self.assertEquals(ch[1].underline, False)
+ self.assertEquals(ch[1].blink, False)
+ self.assertEquals(ch[1].reverseVideo, False)
+ self.assertEquals(ch[1].foreground, helper.WHITE)
+ self.assertEquals(ch[1].background, helper.BLACK)
+
+ def testEraseLine(self):
+ s1 = 'line 1'
+ s2 = 'line 2'
+ s3 = 'line 3'
+ self.term.write('\n'.join((s1, s2, s3)) + '\n')
+ self.term.cursorPosition(1, 1)
+ self.term.eraseLine()
+
+ self.assertEquals(
+ str(self.term),
+ s1 + '\n' +
+ '\n' +
+ s3 + '\n' +
+ '\n' * (HEIGHT - 4))
+
+ def testEraseToLineEnd(self):
+ s = 'Hello, world.'
+ self.term.write(s)
+ self.term.cursorBackward(5)
+ self.term.eraseToLineEnd()
+ self.assertEquals(
+ str(self.term),
+ s[:-5] + '\n' +
+ '\n' * (HEIGHT - 2))
+
+ def testEraseToLineBeginning(self):
+ s = 'Hello, world.'
+ self.term.write(s)
+ self.term.cursorBackward(5)
+ self.term.eraseToLineBeginning()
+ self.assertEquals(
+ str(self.term),
+ s[-4:].rjust(len(s)) + '\n' +
+ '\n' * (HEIGHT - 2))
+
+ def testEraseDisplay(self):
+ self.term.write('Hello world\n')
+ self.term.write('Goodbye world\n')
+ self.term.eraseDisplay()
+
+ self.assertEquals(
+ str(self.term),
+ '\n' * (HEIGHT - 1))
+
+ def testEraseToDisplayEnd(self):
+ s1 = "Hello world"
+ s2 = "Goodbye world"
+ self.term.write('\n'.join((s1, s2, '')))
+ self.term.cursorPosition(5, 1)
+ self.term.eraseToDisplayEnd()
+
+ self.assertEquals(
+ str(self.term),
+ s1 + '\n' +
+ s2[:5] + '\n' +
+ '\n' * (HEIGHT - 3))
+
+ def testEraseToDisplayBeginning(self):
+ s1 = "Hello world"
+ s2 = "Goodbye world"
+ self.term.write('\n'.join((s1, s2)))
+ self.term.cursorPosition(5, 1)
+ self.term.eraseToDisplayBeginning()
+
+ self.assertEquals(
+ str(self.term),
+ '\n' +
+ s2[6:].rjust(len(s2)) + '\n' +
+ '\n' * (HEIGHT - 3))
+
+ def testLineInsertion(self):
+ s1 = "Hello world"
+ s2 = "Goodbye world"
+ self.term.write('\n'.join((s1, s2)))
+ self.term.cursorPosition(7, 1)
+ self.term.insertLine()
+
+ self.assertEquals(
+ str(self.term),
+ s1 + '\n' +
+ '\n' +
+ s2 + '\n' +
+ '\n' * (HEIGHT - 4))
+
+ def testLineDeletion(self):
+ s1 = "Hello world"
+ s2 = "Middle words"
+ s3 = "Goodbye world"
+ self.term.write('\n'.join((s1, s2, s3)))
+ self.term.cursorPosition(9, 1)
+ self.term.deleteLine()
+
+ self.assertEquals(
+ str(self.term),
+ s1 + '\n' +
+ s3 + '\n' +
+ '\n' * (HEIGHT - 3))
+
+class FakeDelayedCall:
+ called = False
+ cancelled = False
+ def __init__(self, fs, timeout, f, a, kw):
+ self.fs = fs
+ self.timeout = timeout
+ self.f = f
+ self.a = a
+ self.kw = kw
+
+ def active(self):
+ return not (self.cancelled or self.called)
+
+ def cancel(self):
+ self.cancelled = True
+# self.fs.calls.remove(self)
+
+ def call(self):
+ self.called = True
+ self.f(*self.a, **self.kw)
+
+class FakeScheduler:
+ def __init__(self):
+ self.calls = []
+
+ def callLater(self, timeout, f, *a, **kw):
+ self.calls.append(FakeDelayedCall(self, timeout, f, a, kw))
+ return self.calls[-1]
+
+class ExpectTestCase(unittest.TestCase):
+ def setUp(self):
+ self.term = helper.ExpectableBuffer()
+ self.term.connectionMade()
+ self.fs = FakeScheduler()
+
+ def testSimpleString(self):
+ result = []
+ d = self.term.expect("hello world", timeout=1, scheduler=self.fs)
+ d.addCallback(result.append)
+
+ self.term.write("greeting puny earthlings\n")
+ self.failIf(result)
+ self.term.write("hello world\n")
+ self.failUnless(result)
+ self.assertEquals(result[0].group(), "hello world")
+ self.assertEquals(len(self.fs.calls), 1)
+ self.failIf(self.fs.calls[0].active())
+
+ def testBrokenUpString(self):
+ result = []
+ d = self.term.expect("hello world")
+ d.addCallback(result.append)
+
+ self.failIf(result)
+ self.term.write("hello ")
+ self.failIf(result)
+ self.term.write("worl")
+ self.failIf(result)
+ self.term.write("d")
+ self.failUnless(result)
+ self.assertEquals(result[0].group(), "hello world")
+
+
+ def testMultiple(self):
+ result = []
+ d1 = self.term.expect("hello ")
+ d1.addCallback(result.append)
+ d2 = self.term.expect("world")
+ d2.addCallback(result.append)
+
+ self.failIf(result)
+ self.term.write("hello")
+ self.failIf(result)
+ self.term.write(" ")
+ self.assertEquals(len(result), 1)
+ self.term.write("world")
+ self.assertEquals(len(result), 2)
+ self.assertEquals(result[0].group(), "hello ")
+ self.assertEquals(result[1].group(), "world")
+
+ def testSynchronous(self):
+ self.term.write("hello world")
+
+ result = []
+ d = self.term.expect("hello world")
+ d.addCallback(result.append)
+ self.failUnless(result)
+ self.assertEquals(result[0].group(), "hello world")
+
+ def testMultipleSynchronous(self):
+ self.term.write("goodbye world")
+
+ result = []
+ d1 = self.term.expect("bye")
+ d1.addCallback(result.append)
+ d2 = self.term.expect("world")
+ d2.addCallback(result.append)
+
+ self.assertEquals(len(result), 2)
+ self.assertEquals(result[0].group(), "bye")
+ self.assertEquals(result[1].group(), "world")
+
+ def _cbTestTimeoutFailure(self, res):
+ self.assert_(hasattr(res, 'type'))
+ self.assertEqual(res.type, helper.ExpectationTimeout)
+
+ def testTimeoutFailure(self):
+ d = self.term.expect("hello world", timeout=1, scheduler=self.fs)
+ d.addBoth(self._cbTestTimeoutFailure)
+ self.fs.calls[0].call()
+
+ def testOverlappingTimeout(self):
+ self.term.write("not zoomtastic")
+
+ result = []
+ d1 = self.term.expect("hello world", timeout=1, scheduler=self.fs)
+ d1.addBoth(self._cbTestTimeoutFailure)
+ d2 = self.term.expect("zoom")
+ d2.addCallback(result.append)
+
+ self.fs.calls[0].call()
+
+ self.assertEquals(len(result), 1)
+ self.assertEquals(result[0].group(), "zoom")
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_insults.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_insults.py
new file mode 100644
index 0000000000..523e061f97
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_insults.py
@@ -0,0 +1,460 @@
+# -*- test-case-name: twisted.conch.test.test_insults -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.trial import unittest
+from twisted.test.proto_helpers import StringTransport
+
+from twisted.conch.insults.insults import ServerProtocol, ClientProtocol
+from twisted.conch.insults.insults import CS_UK, CS_US, CS_DRAWING, CS_ALTERNATE, CS_ALTERNATE_SPECIAL
+from twisted.conch.insults.insults import G0, G1
+from twisted.conch.insults.insults import modes
+
+def _getattr(mock, name):
+ return super(Mock, mock).__getattribute__(name)
+
+def occurrences(mock):
+ return _getattr(mock, 'occurrences')
+
+def methods(mock):
+ return _getattr(mock, 'methods')
+
+def _append(mock, obj):
+ occurrences(mock).append(obj)
+
+default = object()
+
+class Mock(object):
+ callReturnValue = default
+
+ def __init__(self, methods=None, callReturnValue=default):
+ """
+ @param methods: Mapping of names to return values
+ @param callReturnValue: object __call__ should return
+ """
+ self.occurrences = []
+ if methods is None:
+ methods = {}
+ self.methods = methods
+ if callReturnValue is not default:
+ self.callReturnValue = callReturnValue
+
+ def __call__(self, *a, **kw):
+ returnValue = _getattr(self, 'callReturnValue')
+ if returnValue is default:
+ returnValue = Mock()
+ # _getattr(self, 'occurrences').append(('__call__', returnValue, a, kw))
+ _append(self, ('__call__', returnValue, a, kw))
+ return returnValue
+
+ def __getattribute__(self, name):
+ methods = _getattr(self, 'methods')
+ if name in methods:
+ attrValue = Mock(callReturnValue=methods[name])
+ else:
+ attrValue = Mock()
+ # _getattr(self, 'occurrences').append((name, attrValue))
+ _append(self, (name, attrValue))
+ return attrValue
+
+class MockMixin:
+ def assertCall(self, occurrence, methodName, expectedPositionalArgs=(),
+ expectedKeywordArgs={}):
+ attr, mock = occurrence
+ self.assertEquals(attr, methodName)
+ self.assertEquals(len(occurrences(mock)), 1)
+ [(call, result, args, kw)] = occurrences(mock)
+ self.assertEquals(call, "__call__")
+ self.assertEquals(args, expectedPositionalArgs)
+ self.assertEquals(kw, expectedKeywordArgs)
+ return result
+
+
+_byteGroupingTestTemplate = """\
+def testByte%(groupName)s(self):
+ transport = StringTransport()
+ proto = Mock()
+ parser = self.protocolFactory(lambda: proto)
+ parser.factory = self
+ parser.makeConnection(transport)
+
+ bytes = self.TEST_BYTES
+ while bytes:
+ chunk = bytes[:%(bytesPer)d]
+ bytes = bytes[%(bytesPer)d:]
+ parser.dataReceived(chunk)
+
+ self.verifyResults(transport, proto, parser)
+"""
+class ByteGroupingsMixin(MockMixin):
+ protocolFactory = None
+
+ for word, n in [('Pairs', 2), ('Triples', 3), ('Quads', 4), ('Quints', 5), ('Sexes', 6)]:
+ exec _byteGroupingTestTemplate % {'groupName': word, 'bytesPer': n}
+ del word, n
+
+ def verifyResults(self, transport, proto, parser):
+ result = self.assertCall(occurrences(proto).pop(0), "makeConnection", (parser,))
+ self.assertEquals(occurrences(result), [])
+
+del _byteGroupingTestTemplate
+
+class ServerArrowKeys(ByteGroupingsMixin, unittest.TestCase):
+ protocolFactory = ServerProtocol
+
+ # All the arrow keys once
+ TEST_BYTES = '\x1b[A\x1b[B\x1b[C\x1b[D'
+
+ def verifyResults(self, transport, proto, parser):
+ ByteGroupingsMixin.verifyResults(self, transport, proto, parser)
+
+ for arrow in (parser.UP_ARROW, parser.DOWN_ARROW,
+ parser.RIGHT_ARROW, parser.LEFT_ARROW):
+ result = self.assertCall(occurrences(proto).pop(0), "keystrokeReceived", (arrow, None))
+ self.assertEquals(occurrences(result), [])
+ self.failIf(occurrences(proto))
+
+
+class PrintableCharacters(ByteGroupingsMixin, unittest.TestCase):
+ protocolFactory = ServerProtocol
+
+ # Some letters and digits, first on their own, then capitalized,
+ # then modified with alt
+
+ TEST_BYTES = 'abc123ABC!@#\x1ba\x1bb\x1bc\x1b1\x1b2\x1b3'
+
+ def verifyResults(self, transport, proto, parser):
+ ByteGroupingsMixin.verifyResults(self, transport, proto, parser)
+
+ for char in 'abc123ABC!@#':
+ result = self.assertCall(occurrences(proto).pop(0), "keystrokeReceived", (char, None))
+ self.assertEquals(occurrences(result), [])
+
+ for char in 'abc123':
+ result = self.assertCall(occurrences(proto).pop(0), "keystrokeReceived", (char, parser.ALT))
+ self.assertEquals(occurrences(result), [])
+
+ occs = occurrences(proto)
+ self.failIf(occs, "%r should have been []" % (occs,))
+
+class ServerFunctionKeys(ByteGroupingsMixin, unittest.TestCase):
+ """Test for parsing and dispatching function keys (F1 - F12)
+ """
+ protocolFactory = ServerProtocol
+
+ byteList = []
+ for bytes in ('OP', 'OQ', 'OR', 'OS', # F1 - F4
+ '15~', '17~', '18~', '19~', # F5 - F8
+ '20~', '21~', '23~', '24~'): # F9 - F12
+ byteList.append('\x1b[' + bytes)
+ TEST_BYTES = ''.join(byteList)
+ del byteList, bytes
+
+ def verifyResults(self, transport, proto, parser):
+ ByteGroupingsMixin.verifyResults(self, transport, proto, parser)
+ for funcNum in range(1, 13):
+ funcArg = getattr(parser, 'F%d' % (funcNum,))
+ result = self.assertCall(occurrences(proto).pop(0), "keystrokeReceived", (funcArg, None))
+ self.assertEquals(occurrences(result), [])
+ self.failIf(occurrences(proto))
+
+class ClientCursorMovement(ByteGroupingsMixin, unittest.TestCase):
+ protocolFactory = ClientProtocol
+
+ d2 = "\x1b[2B"
+ r4 = "\x1b[4C"
+ u1 = "\x1b[A"
+ l2 = "\x1b[2D"
+ # Move the cursor down two, right four, up one, left two, up one, left two
+ TEST_BYTES = d2 + r4 + u1 + l2 + u1 + l2
+ del d2, r4, u1, l2
+
+ def verifyResults(self, transport, proto, parser):
+ ByteGroupingsMixin.verifyResults(self, transport, proto, parser)
+
+ for (method, count) in [('Down', 2), ('Forward', 4), ('Up', 1),
+ ('Backward', 2), ('Up', 1), ('Backward', 2)]:
+ result = self.assertCall(occurrences(proto).pop(0), "cursor" + method, (count,))
+ self.assertEquals(occurrences(result), [])
+ self.failIf(occurrences(proto))
+
+class ClientControlSequences(unittest.TestCase, MockMixin):
+ def setUp(self):
+ self.transport = StringTransport()
+ self.proto = Mock()
+ self.parser = ClientProtocol(lambda: self.proto)
+ self.parser.factory = self
+ self.parser.makeConnection(self.transport)
+ result = self.assertCall(occurrences(self.proto).pop(0), "makeConnection", (self.parser,))
+ self.failIf(occurrences(result))
+
+ def testSimpleCardinals(self):
+ self.parser.dataReceived(
+ ''.join([''.join(['\x1b[' + str(n) + ch for n in ('', 2, 20, 200)]) for ch in 'BACD']))
+ occs = occurrences(self.proto)
+
+ for meth in ("Down", "Up", "Forward", "Backward"):
+ for count in (1, 2, 20, 200):
+ result = self.assertCall(occs.pop(0), "cursor" + meth, (count,))
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testScrollRegion(self):
+ self.parser.dataReceived('\x1b[5;22r\x1b[r')
+ occs = occurrences(self.proto)
+
+ result = self.assertCall(occs.pop(0), "setScrollRegion", (5, 22))
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "setScrollRegion", (None, None))
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testHeightAndWidth(self):
+ self.parser.dataReceived("\x1b#3\x1b#4\x1b#5\x1b#6")
+ occs = occurrences(self.proto)
+
+ result = self.assertCall(occs.pop(0), "doubleHeightLine", (True,))
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "doubleHeightLine", (False,))
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "singleWidthLine")
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "doubleWidthLine")
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testCharacterSet(self):
+ self.parser.dataReceived(
+ ''.join([''.join(['\x1b' + g + n for n in 'AB012']) for g in '()']))
+ occs = occurrences(self.proto)
+
+ for which in (G0, G1):
+ for charset in (CS_UK, CS_US, CS_DRAWING, CS_ALTERNATE, CS_ALTERNATE_SPECIAL):
+ result = self.assertCall(occs.pop(0), "selectCharacterSet", (charset, which))
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testShifting(self):
+ self.parser.dataReceived("\x15\x14")
+ occs = occurrences(self.proto)
+
+ result = self.assertCall(occs.pop(0), "shiftIn")
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "shiftOut")
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testSingleShifts(self):
+ self.parser.dataReceived("\x1bN\x1bO")
+ occs = occurrences(self.proto)
+
+ result = self.assertCall(occs.pop(0), "singleShift2")
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "singleShift3")
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testKeypadMode(self):
+ self.parser.dataReceived("\x1b=\x1b>")
+ occs = occurrences(self.proto)
+
+ result = self.assertCall(occs.pop(0), "applicationKeypadMode")
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "numericKeypadMode")
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testCursor(self):
+ self.parser.dataReceived("\x1b7\x1b8")
+ occs = occurrences(self.proto)
+
+ result = self.assertCall(occs.pop(0), "saveCursor")
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "restoreCursor")
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testReset(self):
+ self.parser.dataReceived("\x1bc")
+ occs = occurrences(self.proto)
+
+ result = self.assertCall(occs.pop(0), "reset")
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testIndex(self):
+ self.parser.dataReceived("\x1bD\x1bM\x1bE")
+ occs = occurrences(self.proto)
+
+ result = self.assertCall(occs.pop(0), "index")
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "reverseIndex")
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "nextLine")
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testModes(self):
+ self.parser.dataReceived(
+ "\x1b[" + ';'.join(map(str, [modes.KAM, modes.IRM, modes.LNM])) + "h")
+ self.parser.dataReceived(
+ "\x1b[" + ';'.join(map(str, [modes.KAM, modes.IRM, modes.LNM])) + "l")
+ occs = occurrences(self.proto)
+
+ result = self.assertCall(occs.pop(0), "setModes", ([modes.KAM, modes.IRM, modes.LNM],))
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "resetModes", ([modes.KAM, modes.IRM, modes.LNM],))
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testErasure(self):
+ self.parser.dataReceived(
+ "\x1b[K\x1b[1K\x1b[2K\x1b[J\x1b[1J\x1b[2J\x1b[3P")
+ occs = occurrences(self.proto)
+
+ for meth in ("eraseToLineEnd", "eraseToLineBeginning", "eraseLine",
+ "eraseToDisplayEnd", "eraseToDisplayBeginning",
+ "eraseDisplay"):
+ result = self.assertCall(occs.pop(0), meth)
+ self.failIf(occurrences(result))
+
+ result = self.assertCall(occs.pop(0), "deleteCharacter", (3,))
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testLineDeletion(self):
+ self.parser.dataReceived("\x1b[M\x1b[3M")
+ occs = occurrences(self.proto)
+
+ for arg in (1, 3):
+ result = self.assertCall(occs.pop(0), "deleteLine", (arg,))
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testLineInsertion(self):
+ self.parser.dataReceived("\x1b[L\x1b[3L")
+ occs = occurrences(self.proto)
+
+ for arg in (1, 3):
+ result = self.assertCall(occs.pop(0), "insertLine", (arg,))
+ self.failIf(occurrences(result))
+ self.failIf(occs)
+
+ def testCursorPosition(self):
+ methods(self.proto)['reportCursorPosition'] = (6, 7)
+ self.parser.dataReceived("\x1b[6n")
+ self.assertEquals(self.transport.value(), "\x1b[7;8R")
+ occs = occurrences(self.proto)
+
+ result = self.assertCall(occs.pop(0), "reportCursorPosition")
+ # This isn't really an interesting assert, since it only tests that
+ # our mock setup is working right, but I'll include it anyway.
+ self.assertEquals(result, (6, 7))
+
+
+ def test_applicationDataBytes(self):
+ """
+ Contiguous non-control bytes are passed to a single call to the
+ C{write} method of the terminal to which the L{ClientProtocol} is
+ connected.
+ """
+ occs = occurrences(self.proto)
+ self.parser.dataReceived('a')
+ self.assertCall(occs.pop(0), "write", ("a",))
+ self.parser.dataReceived('bc')
+ self.assertCall(occs.pop(0), "write", ("bc",))
+
+
+ def _applicationDataTest(self, data, calls):
+ occs = occurrences(self.proto)
+ self.parser.dataReceived(data)
+ while calls:
+ self.assertCall(occs.pop(0), *calls.pop(0))
+ self.assertFalse(occs, "No other calls should happen: %r" % (occs,))
+
+
+ def test_shiftInAfterApplicationData(self):
+ """
+ Application data bytes followed by a shift-in command are passed to a
+ call to C{write} before the terminal's C{shiftIn} method is called.
+ """
+ self._applicationDataTest(
+ 'ab\x15', [
+ ("write", ("ab",)),
+ ("shiftIn",)])
+
+
+ def test_shiftOutAfterApplicationData(self):
+ """
+ Application data bytes followed by a shift-out command are passed to a
+ call to C{write} before the terminal's C{shiftOut} method is called.
+ """
+ self._applicationDataTest(
+ 'ab\x14', [
+ ("write", ("ab",)),
+ ("shiftOut",)])
+
+
+ def test_cursorBackwardAfterApplicationData(self):
+ """
+ Application data bytes followed by a cursor-backward command are passed
+ to a call to C{write} before the terminal's C{cursorBackward} method is
+ called.
+ """
+ self._applicationDataTest(
+ 'ab\x08', [
+ ("write", ("ab",)),
+ ("cursorBackward",)])
+
+
+ def test_escapeAfterApplicationData(self):
+ """
+ Application data bytes followed by an escape character are passed to a
+ call to C{write} before the terminal's handler method for the escape is
+ called.
+ """
+ # Test a short escape
+ self._applicationDataTest(
+ 'ab\x1bD', [
+ ("write", ("ab",)),
+ ("index",)])
+
+ # And a long escape
+ self._applicationDataTest(
+ 'ab\x1b[4h', [
+ ("write", ("ab",)),
+ ("setModes", ([4],))])
+
+ # There's some other cases too, but they're all handled by the same
+ # codepaths as above.
+
+
+
+class ServerProtocolOutputTests(unittest.TestCase):
+ """
+ Tests for the bytes L{ServerProtocol} writes to its transport when its
+ methods are called.
+ """
+ def test_nextLine(self):
+ """
+ L{ServerProtocol.nextLine} writes C{"\r\n"} to its transport.
+ """
+ # Why doesn't it write ESC E? Because ESC E is poorly supported. For
+ # example, gnome-terminal (many different versions) fails to scroll if
+ # it receives ESC E and the cursor is already on the last row.
+ protocol = ServerProtocol()
+ transport = StringTransport()
+ protocol.makeConnection(transport)
+ protocol.nextLine()
+ self.assertEqual(transport.value(), "\r\n")
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_keys.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_keys.py
new file mode 100644
index 0000000000..9a5be53ce2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_keys.py
@@ -0,0 +1,961 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.ssh.keys}.
+"""
+
+try:
+ import Crypto.Cipher.DES3
+except ImportError:
+ # we'll have to skip these tests without PyCypto and pyasn1
+ Crypto = None
+
+try:
+ import pyasn1
+except ImportError:
+ pyasn1 = None
+
+if Crypto and pyasn1:
+ from twisted.conch.ssh import asn1, keys, common, sexpy
+
+import os, base64
+from twisted.conch.test import keydata
+from twisted.python import randbytes
+from twisted.python.hashlib import sha1
+from twisted.trial import unittest
+
+class SSHKeysHandlingTestCase(unittest.TestCase):
+ """
+ test the handling of reading/signing/verifying with RSA and DSA keys
+ assumed test keys are in test/
+ """
+
+ if Crypto is None:
+ skip = "cannot run w/o PyCrypto"
+ if pyasn1 is None:
+ skip = "cannot run w/o/ PyASN1"
+
+ def setUp(self):
+ self.tmpdir = self.mktemp()
+ os.mkdir(self.tmpdir)
+ self.privateKeyFile = os.path.join(self.tmpdir, 'private')
+ self.publicKeyFile = os.path.join(self.tmpdir, 'public')
+ file(self.privateKeyFile, 'wb').write(keydata.privateRSA_openssh)
+ file(self.publicKeyFile, 'wb').write('first line\n' +
+ keydata.publicRSA_openssh)
+
+ def test_readFile(self):
+ """
+ Test that reading a key from a file works as expected.
+ """
+ self.assertEquals(self.assertWarns(DeprecationWarning,
+ "getPublicKeyString is deprecated since Twisted Conch 0.9. "
+ "Use Key.fromString().blob().", unittest.__file__,
+ keys.getPublicKeyString, self.publicKeyFile, 1),
+ keys.Key.fromString(keydata.publicRSA_openssh).blob())
+ self.assertEquals(self.assertWarns(DeprecationWarning,
+ "getPrivateKeyObject is deprecated since Twisted Conch 0.9. "
+ "Use Key.fromString().", unittest.__file__,
+ keys.getPrivateKeyObject, self.privateKeyFile),
+ keys.Key.fromString(keydata.privateRSA_openssh).keyObject)
+
+ def test_DSA(self):
+ """
+ Test DSA keys using both OpenSSH and LSH formats.
+ """
+ self._testKey(keydata.publicDSA_openssh, keydata.privateDSA_openssh,
+ keydata.DSAData, 'openssh')
+ self._testKey(keydata.publicDSA_lsh, keydata.privateDSA_lsh,
+ keydata.DSAData,'lsh')
+ obj = self.assertWarns(DeprecationWarning, "getPrivateKeyObject is "
+ "deprecated since Twisted Conch 0.9. Use Key.fromString().",
+ unittest.__file__, keys.getPrivateKeyObject,
+ data=keydata.privateDSA_agentv3)
+ self._testGeneratePrivateKey(obj, keydata.privateDSA_agentv3,
+ 'agentv3')
+
+ def test_RSA(self):
+ """
+ Same as test_DSA but for RSA keys.
+ """
+ self._testKey(keydata.publicRSA_openssh, keydata.privateRSA_openssh,
+ keydata.RSAData, 'openssh')
+ self._testKey(keydata.publicRSA_lsh, keydata.privateRSA_lsh,
+ keydata.RSAData, 'lsh')
+ obj = self.assertWarns(DeprecationWarning, "getPrivateKeyObject is "
+ "deprecated since Twisted Conch 0.9. Use Key.fromString().",
+ unittest.__file__, keys.getPrivateKeyObject,
+ data=keydata.privateRSA_agentv3)
+ self._testGeneratePrivateKey(obj, keydata.privateRSA_agentv3,
+ 'agentv3')
+
+
+ def test_fingerprint(self):
+ """
+ L{Key.fingerprint} returns a hex-encoded colon-separated md5 sum of the
+ public key.
+ """
+ self.assertEquals(
+ '3d:13:5f:cb:c9:79:8a:93:06:27:65:bc:3d:0b:8f:af',
+ keys.Key.fromString(keydata.publicRSA_openssh).fingerprint())
+
+
+ def _testKey(self, pubStr, privStr, data, keyType):
+ """
+ Run each of the key tests with the public/private keypairs.
+
+ @param pubStr: The data for a public key in the format defined by
+ keyType.
+ @param privStr: The data for a private key in the format defined by
+ keyType.
+ @param data: The numerical values encoded in the key.
+ @param keyType: the type of the public and private key data: either
+ "openssh" or "lsh".
+ """
+ pubBlob = self.assertWarns(DeprecationWarning, "getPublicKeyString is "
+ "deprecated since Twisted Conch 0.9. "
+ "Use Key.fromString().blob().",
+ unittest.__file__, keys.getPublicKeyString, data=pubStr)
+ pubObj = self.assertWarns(DeprecationWarning, "getPublicKeyObject is "
+ "deprecated since Twisted Conch 0.9. Use Key.fromString().",
+ unittest.__file__, keys.getPublicKeyObject, pubBlob)
+ privObj = self.assertWarns(DeprecationWarning, "getPrivateKeyObject is "
+ "deprecated since Twisted Conch 0.9. Use Key.fromString().",
+ unittest.__file__, keys.getPrivateKeyObject, data=privStr)
+
+ self._testKeySignVerify(privObj, pubObj)
+ self._testKeyFromString(privObj, pubObj, data, keyType)
+ self._testGeneratePublicKey(privObj, pubObj, pubStr, keyType)
+ self._testGeneratePrivateKey(privObj, privStr, keyType)
+ self._testGenerateBlob(privObj, pubObj, pubBlob)
+
+ def _testKeySignVerify(self, privObj, pubObj):
+ """
+ Test that signing and verifying works correctly.
+ @param privObj: a private key object.
+ @type privObj: C{Crypto.PublicKey.pubkey.pubkey}
+ @param pubObj: a public key object.
+ @type pubObj: C{Crypto.PublicKey.pubkey.pubkey}
+ """
+
+ testData = 'this is the test data'
+ sig = self.assertWarns(DeprecationWarning,
+ "signData is deprecated since Twisted Conch 0.9. "
+ "Use Key(obj).sign(data).", unittest.__file__, keys.signData,
+ privObj, testData)
+ self.assertTrue(self.assertWarns(DeprecationWarning,
+ "verifySignature is deprecated since Twisted Conch 0.9. "
+ "Use Key(obj).verify(signature, data).", unittest.__file__,
+ keys.verifySignature, privObj, sig, testData),
+ 'verifying with private %s failed' %
+ keys.objectType(privObj))
+
+ self.assertTrue(self.assertWarns(DeprecationWarning,
+ "verifySignature is deprecated since Twisted Conch 0.9. "
+ "Use Key(obj).verify(signature, data).", unittest.__file__,
+ keys.verifySignature, pubObj, sig, testData),
+ 'verifying with public %s failed' %
+ keys.objectType(pubObj))
+
+ self.failIf(self.assertWarns(DeprecationWarning,
+ "verifySignature is deprecated since Twisted Conch 0.9. "
+ "Use Key(obj).verify(signature, data).", unittest.__file__,
+ keys.verifySignature,privObj, sig, 'other data'),
+ 'verified bad data with %s' %
+ keys.objectType(privObj))
+
+ self.failIf(self.assertWarns(DeprecationWarning,
+ "verifySignature is deprecated since Twisted Conch 0.9. "
+ "Use Key(obj).verify(signature, data).", unittest.__file__,
+ keys.verifySignature, privObj, 'bad sig', testData),
+ 'verified badsign with %s' %
+ keys.objectType(privObj))
+
+ def _testKeyFromString(self, privObj, pubObj, data, keyType):
+ """
+ Test key object generation from a string. The public key objects
+ were generated in _testKey; just check that they were created
+ correctly.
+ """
+ for k in data.keys():
+ self.assertEquals(getattr(privObj, k), data[k])
+ for k in pubObj.keydata:
+ if hasattr(pubObj, k): # public key objects don't have all the
+ # attributes
+ self.assertEquals(getattr(pubObj, k), data[k])
+
+ def _testGeneratePublicKey(self, privObj, pubObj, pubStr, keyType):
+ """
+ Test public key string generation from an object.
+ """
+ self.assertEquals(self.assertWarns(DeprecationWarning,
+ "makePublicKeyString is deprecated since Twisted Conch 0.9. "
+ "Use Key(obj).public().toString().", unittest.__file__,
+ keys.makePublicKeyString, pubObj, 'comment',
+ keyType), pubStr)
+ self.assertEquals(self.assertWarns(DeprecationWarning,
+ "makePublicKeyString is deprecated since Twisted Conch 0.9. "
+ "Use Key(obj).public().toString().", unittest.__file__,
+ keys.makePublicKeyString, privObj, 'comment',
+ keyType), pubStr)
+
+ def _testGeneratePrivateKey(self, privObj, privStr, keyType):
+ """
+ Test private key string generation from an object.
+ """
+ self.assertEquals(self.assertWarns(DeprecationWarning,
+ "makePrivateKeyString is deprecated since Twisted Conch 0.9. "
+ "Use Key(obj).toString().", unittest.__file__,
+ keys.makePrivateKeyString, privObj, kind=keyType),
+ privStr)
+ if keyType == 'openssh':
+ encData = self.assertWarns(DeprecationWarning,
+ "makePrivateKeyString is deprecated since Twisted Conch "
+ "0.9. Use Key(obj).toString().", unittest.__file__,
+ keys.makePrivateKeyString, privObj, passphrase='test',
+ kind=keyType)
+ self.assertEquals(self.assertWarns(DeprecationWarning,
+ "getPrivateKeyObject is deprecated since Twisted Conch 0.9. "
+ "Use Key.fromString().", unittest.__file__,
+ keys.getPrivateKeyObject, data = encData, passphrase='test'),
+ privObj)
+
+ def _testGenerateBlob(self, privObj, pubObj, pubBlob):
+ """
+ Test wire-format blob generation.
+ """
+ self.assertEquals(self.assertWarns(DeprecationWarning,
+ "makePublicKeyBlob is deprecated since Twisted Conch 0.9. "
+ "Use Key(obj).blob().", unittest.__file__,
+ keys.makePublicKeyBlob, pubObj), pubBlob)
+ self.assertEquals(self.assertWarns(DeprecationWarning,
+ "makePublicKeyBlob is deprecated since Twisted Conch 0.9. "
+ "Use Key(obj).blob().", unittest.__file__,
+ keys.makePublicKeyBlob, privObj), pubBlob)
+
+ def test_getPublicKeyStringErrors(self):
+ """
+ Test that getPublicKeyString raises errors in appropriate cases.
+ """
+ self.assertWarns(DeprecationWarning, "getPublicKeyString is deprecated"
+ " since Twisted Conch 0.9. Use Key.fromString().blob().",
+ unittest.__file__, self.assertRaises, keys.BadKeyError,
+ keys.getPublicKeyString, self.publicKeyFile, 1,
+ data=keydata.publicRSA_openssh)
+ self.assertWarns(DeprecationWarning, "getPublicKeyString is deprecated"
+ " since Twisted Conch 0.9. Use Key.fromString().blob().",
+ unittest.__file__, self.assertRaises, keys.BadKeyError,
+ keys.getPublicKeyString, data = 'invalid key')
+ sexp = sexpy.pack([['public-key', ['bad-key', ['p', '2']]]])
+ self.assertWarns(DeprecationWarning, "getPublicKeyString is deprecated"
+ " since Twisted Conch 0.9. Use Key.fromString().blob().",
+ unittest.__file__, self.assertRaises, keys.BadKeyError,
+ keys.getPublicKeyString, data='{'+base64.encodestring(sexp)+'}')
+
+ def test_getPrivateKeyObjectErrors(self):
+ """
+ Test that getPrivateKeyObject raises errors in appropriate cases.
+ """
+ self.assertWarns(DeprecationWarning, "getPrivateKeyObject is deprecated"
+ " since Twisted Conch 0.9. Use Key.fromString().",
+ unittest.__file__, self.assertRaises, keys.BadKeyError,
+ keys.getPrivateKeyObject, self.privateKeyFile,
+ keydata.privateRSA_openssh)
+ self.assertWarns(DeprecationWarning, "getPrivateKeyObject is deprecated"
+ " since Twisted Conch 0.9. Use Key.fromString().",
+ unittest.__file__, self.assertRaises, keys.BadKeyError,
+ keys.getPrivateKeyObject, data = 'invalid key')
+ sexp = sexpy.pack([['private-key', ['bad-key', ['p', '2']]]])
+ self.assertWarns(DeprecationWarning, "getPrivateKeyObject is deprecated"
+ " since Twisted Conch 0.9. Use Key.fromString().",
+ unittest.__file__, self.assertRaises, keys.BadKeyError,
+ keys.getPrivateKeyObject, data=sexp)
+ self.assertWarns(DeprecationWarning, "getPrivateKeyObject is deprecated"
+ " since Twisted Conch 0.9. Use Key.fromString().",
+ unittest.__file__, self.assertRaises, keys.BadKeyError,
+ keys.getPrivateKeyObject,
+ data='\x00\x00\x00\x07ssh-foo'+'\x00\x00\x00\x01\x01'*5)
+
+ def test_makePublicKeyStringErrors(self):
+ """
+ Test that makePublicKeyString raises errors in appropriate cases.
+ """
+ self.assertWarns(DeprecationWarning, "makePublicKeyString is deprecated"
+ " since Twisted Conch 0.9. Use Key(obj).public().toString().",
+ unittest.__file__, self.assertRaises, Exception,
+ keys.makePublicKeyString, None, kind='bad type')
+ self.assertWarns(DeprecationWarning, "makePublicKeyString is deprecated"
+ " since Twisted Conch 0.9. Use Key(obj).public().toString().",
+ unittest.__file__, self.assertRaises, Exception,
+ keys.makePublicKeyString, None)
+ self.assertWarns(DeprecationWarning, "makePublicKeyString is deprecated"
+ " since Twisted Conch 0.9. Use Key(obj).public().toString().",
+ unittest.__file__, self.assertRaises, Exception,
+ keys.makePublicKeyString, None, kind='lsh')
+
+ def test_getPublicKeyObjectErrors(self):
+ """
+ Test that getPublicKeyObject raises errors in appropriate cases.
+ """
+ self.assertWarns(DeprecationWarning, "getPublicKeyObject is deprecated"
+ " since Twisted Conch 0.9. Use Key.fromString().",
+ unittest.__file__, self.assertRaises, keys.BadKeyError,
+ keys.getPublicKeyObject, '\x00\x00\x00\x01A')
+
+ def test_makePrivateKeyStringErrors(self):
+ """
+ Test that makePrivateKeyString raises errors in appropriate cases.
+ """
+ self.assertWarns(DeprecationWarning, "makePrivateKeyString is "
+ "deprecated since Twisted Conch 0.9. Use Key(obj).toString().",
+ unittest.__file__, self.assertRaises, Exception,
+ keys.makePrivateKeyString, None, kind='bad type')
+ self.assertWarns(DeprecationWarning, "makePrivateKeyString is "
+ "deprecated since Twisted Conch 0.9. Use Key(obj).toString().",
+ unittest.__file__, self.assertRaises, Exception,
+ keys.makePrivateKeyString, None)
+ self.assertWarns(DeprecationWarning, "makePrivateKeyString is "
+ "deprecated since Twisted Conch 0.9. Use Key(obj).toString().",
+ unittest.__file__, self.assertRaises, Exception,
+ keys.makePrivateKeyString, None, kind='lsh')
+
+class HelpersTestCase(unittest.TestCase):
+
+ if Crypto is None:
+ skip = "cannot run w/o PyCrypto"
+ if pyasn1 is None:
+ skip = "cannot run w/o/ PyASN1"
+
+ def setUp(self):
+ self._secureRandom = randbytes.secureRandom
+ randbytes.secureRandom = lambda x: '\x55' * x
+
+ def tearDown(self):
+ randbytes.secureRandom = self._secureRandom
+ self._secureRandom = None
+
+ def test_pkcs1(self):
+ """
+ Test Public Key Cryptographic Standard #1 functions.
+ """
+ data = 'ABC'
+ messageSize = 6
+ self.assertEquals(keys.pkcs1Pad(data, messageSize),
+ '\x01\xff\x00ABC')
+ hash = sha1().digest()
+ messageSize = 40
+ self.assertEquals(keys.pkcs1Digest('', messageSize),
+ '\x01\xff\xff\xff\x00' + keys.ID_SHA1 + hash)
+
+ def _signRSA(self, data):
+ key = keys.Key.fromString(keydata.privateRSA_openssh)
+ sig = key.sign(data)
+ return key.keyObject, sig
+
+ def _signDSA(self, data):
+ key = keys.Key.fromString(keydata.privateDSA_openssh)
+ sig = key.sign(data)
+ return key.keyObject, sig
+
+ def test_signRSA(self):
+ """
+ Test that RSA keys return appropriate signatures.
+ """
+ data = 'data'
+ key, sig = self._signRSA(data)
+ sigData = keys.pkcs1Digest(data, keys.lenSig(key))
+ v = key.sign(sigData, '')[0]
+ self.assertEquals(sig, common.NS('ssh-rsa') + common.MP(v))
+ return key, sig
+
+ def test_signDSA(self):
+ """
+ Test that DSA keys return appropriate signatures.
+ """
+ data = 'data'
+ key, sig = self._signDSA(data)
+ sigData = sha1(data).digest()
+ v = key.sign(sigData, '\x55' * 19)
+ self.assertEquals(sig, common.NS('ssh-dss') + common.NS(
+ Crypto.Util.number.long_to_bytes(v[0], 20) +
+ Crypto.Util.number.long_to_bytes(v[1], 20)))
+ return key, sig
+
+ def test_verifyRSA(self):
+ """
+ Test that RSA signatures are verified appropriately.
+ """
+ data = 'data'
+ key, sig = self._signRSA(data)
+ self.assertTrue(self.assertWarns(DeprecationWarning, "verifySignature "
+ "is deprecated since Twisted Conch 0.9. Use "
+ "Key(obj).verify(signature, data).", unittest.__file__,
+ keys.verifySignature, key, sig, data))
+
+ def test_verifyDSA(self):
+ """
+ Test that RSA signatures are verified appropriately.
+ """
+ data = 'data'
+ key, sig = self._signDSA(data)
+ self.assertTrue(self.assertWarns(DeprecationWarning, "verifySignature "
+ "is deprecated since Twisted Conch 0.9. Use "
+ "Key(obj).verify(signature, data).", unittest.__file__,
+ keys.verifySignature, key, sig, data))
+
+ def test_objectType(self):
+ """
+ Test that objectType, returns the correct type for objects.
+ """
+ self.assertEquals(keys.objectType(keys.Key.fromString(
+ keydata.privateRSA_openssh).keyObject), 'ssh-rsa')
+ self.assertEquals(keys.objectType(keys.Key.fromString(
+ keydata.privateDSA_openssh).keyObject), 'ssh-dss')
+ self.assertRaises(keys.BadKeyError, keys.objectType, None)
+
+ def test_asn1PackError(self):
+ """
+ L{asn1.pack} should raise a C{ValueError} when given a type not
+ handled.
+ """
+ self.assertRaises(ValueError, asn1.pack, [object()])
+ self.assertEquals(len(self.flushWarnings()), 1)
+
+ def test_asn1DeprecationWarnings(self):
+ """
+ L{asn1.pack} and L{asn1.parse} were deprecated in Twisted 9.0.0. Make
+ sure that they tell their callers.
+ """
+ self.assertEquals(
+ self.callDeprecated(asn1.Twisted9point0, asn1.pack, []),
+ '0\x00')
+ self.assertEquals(
+ len(self.callDeprecated(asn1.Twisted9point0, asn1.parse,
+ '\x10\x00\x00')),
+ 0)
+
+ def test_printKey(self):
+ """
+ Test that the printKey function prints correctly.
+ """
+ obj = keys.Key.fromString(keydata.privateRSA_openssh).keyObject
+ self.assertEquals(self.assertWarns(DeprecationWarning, "printKey is "
+ "deprecated since Twisted Conch 0.9. Use repr(Key(obj)).",
+ unittest.__file__, keys.printKey, obj),
+ """RSA Private Key (767 bits)
+attr e:
+\t23
+attr d:
+\t6e:1f:b5:55:97:eb:ed:67:ed:2b:99:6e:ec:c1:ed:
+\ta8:4d:52:d6:f3:d6:65:06:04:df:e5:54:9f:cc:89:
+\t00:3c:9b:67:87:ec:65:a0:ab:cd:6f:65:90:8a:97:
+\t90:4d:c6:21:8f:a8:8d:d8:59:86:43:b5:81:b1:b4:
+\td7:5f:2c:22:0a:61:c1:25:8a:47:12:b4:9a:f8:7a:
+\t11:1c:4a:a8:8b:75:c4:91:09:3b:be:04:ca:45:d9:
+\t57:8a:0d:27:cb:23
+attr n:
+\t00:af:32:71:f0:e6:0e:9c:99:b3:7f:8b:5f:04:4b:
+\tcb:8b:c0:d5:3e:b2:77:fd:cf:64:d8:8f:c0:cf:ae:
+\t1f:c6:31:df:f6:29:b2:44:96:e2:c6:d4:21:94:7f:
+\t65:7c:d8:d4:23:1f:b8:2e:6a:c9:1f:94:0d:46:c1:
+\t69:a2:b7:07:0c:a3:93:c1:34:d8:2e:1e:4a:99:1a:
+\t6c:96:46:07:46:2b:dc:25:29:1b:87:f0:be:05:1d:
+\tee:b4:34:b9:e7:99:95
+attr q:
+\t00:dc:9f:6b:d9:98:21:56:11:8d:e9:5f:03:9d:0a:
+\td3:93:6e:13:77:41:3c:85:4f:00:70:fd:05:54:ff:
+\tbc:3d:09:bf:83:f6:97:7f:64:10:91:04:fe:a2:67:
+\t47:54:42:6b
+attr p:
+\t00:cb:4a:4b:d0:40:47:e8:45:52:f7:c7:af:0c:20:
+\t6d:43:0d:b6:39:94:f9:da:a5:e5:03:06:76:83:24:
+\teb:88:a1:55:a2:a8:de:12:3b:77:49:92:8a:a9:71:
+\td2:02:93:ff
+attr u:
+\t00:b4:73:97:4b:50:10:a3:17:b3:a8:47:f1:3a:14:
+\t76:52:d1:38:2a:cf:12:14:34:c1:a8:54:4c:29:35:
+\t80:a0:38:b8:f0:fa:4c:c4:c2:85:ab:db:87:82:ba:
+\tdc:eb:db:2a""")
+
+class KeyTestCase(unittest.TestCase):
+
+ if Crypto is None:
+ skip = "cannot run w/o PyCrypto"
+ if pyasn1 is None:
+ skip = "cannot run w/o/ PyASN1"
+
+ def setUp(self):
+ self.rsaObj = Crypto.PublicKey.RSA.construct((1L, 2L, 3L, 4L, 5L))
+ self.dsaObj = Crypto.PublicKey.DSA.construct((1L, 2L, 3L, 4L, 5L))
+ self.rsaSignature = ('\x00\x00\x00\x07ssh-rsa\x00'
+ '\x00\x00`N\xac\xb4@qK\xa0(\xc3\xf2h \xd3\xdd\xee6Np\x9d_'
+ '\xb0>\xe3\x0c(L\x9d{\txUd|!\xf6m\x9c\xd3\x93\x842\x7fU'
+ '\x05\xf4\xf7\xfaD\xda\xce\x81\x8ea\x7f=Y\xed*\xb7\xba\x81'
+ '\xf2\xad\xda\xeb(\x97\x03S\x08\x81\xc7\xb1\xb7\xe6\xe3'
+ '\xcd*\xd4\xbd\xc0wt\xf7y\xcd\xf0\xb7\x7f\xfb\x1e>\xf9r'
+ '\x8c\xba')
+ self.dsaSignature = ('\x00\x00\x00\x07ssh-dss\x00\x00'
+ '\x00(\x18z)H\x8a\x1b\xc6\r\xbbq\xa2\xd7f\x7f$\xa7\xbf'
+ '\xe8\x87\x8c\x88\xef\xd9k\x1a\x98\xdd{=\xdec\x18\t\xe3'
+ '\x87\xa9\xc72h\x95')
+ self.oldSecureRandom = randbytes.secureRandom
+ randbytes.secureRandom = lambda x: '\xff' * x
+ self.keyFile = self.mktemp()
+ file(self.keyFile, 'wb').write(keydata.privateRSA_lsh)
+
+ def tearDown(self):
+ randbytes.secureRandom = self.oldSecureRandom
+ del self.oldSecureRandom
+ os.unlink(self.keyFile)
+
+ def test__guessStringType(self):
+ """
+ Test that the _guessStringType method guesses string types
+ correctly.
+ """
+ self.assertEquals(keys.Key._guessStringType(keydata.publicRSA_openssh),
+ 'public_openssh')
+ self.assertEquals(keys.Key._guessStringType(keydata.publicDSA_openssh),
+ 'public_openssh')
+ self.assertEquals(keys.Key._guessStringType(
+ keydata.privateRSA_openssh), 'private_openssh')
+ self.assertEquals(keys.Key._guessStringType(
+ keydata.privateDSA_openssh), 'private_openssh')
+ self.assertEquals(keys.Key._guessStringType(keydata.publicRSA_lsh),
+ 'public_lsh')
+ self.assertEquals(keys.Key._guessStringType(keydata.publicDSA_lsh),
+ 'public_lsh')
+ self.assertEquals(keys.Key._guessStringType(keydata.privateRSA_lsh),
+ 'private_lsh')
+ self.assertEquals(keys.Key._guessStringType(keydata.privateDSA_lsh),
+ 'private_lsh')
+ self.assertEquals(keys.Key._guessStringType(
+ keydata.privateRSA_agentv3), 'agentv3')
+ self.assertEquals(keys.Key._guessStringType(
+ keydata.privateDSA_agentv3), 'agentv3')
+ self.assertEquals(keys.Key._guessStringType(
+ '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01\x01'),
+ 'blob')
+ self.assertEquals(keys.Key._guessStringType(
+ '\x00\x00\x00\x07ssh-dss\x00\x00\x00\x01\x01'),
+ 'blob')
+ self.assertEquals(keys.Key._guessStringType('not a key'),
+ None)
+
+ def _testPublicPrivateFromString(self, public, private, type, data):
+ self._testPublicFromString(public, type, data)
+ self._testPrivateFromString(private, type, data)
+
+ def _testPublicFromString(self, public, type, data):
+ publicKey = keys.Key.fromString(public)
+ self.assertTrue(publicKey.isPublic())
+ self.assertEquals(publicKey.type(), type)
+ for k, v in publicKey.data().items():
+ self.assertEquals(data[k], v)
+
+ def _testPrivateFromString(self, private, type, data):
+ privateKey = keys.Key.fromString(private)
+ self.assertFalse(privateKey.isPublic())
+ self.assertEquals(privateKey.type(), type)
+ for k, v in data.items():
+ self.assertEquals(privateKey.data()[k], v)
+
+ def test_fromOpenSSH(self):
+ """
+ Test that keys are correctly generated from OpenSSH strings.
+ """
+ self._testPublicPrivateFromString(keydata.publicRSA_openssh,
+ keydata.privateRSA_openssh, 'RSA', keydata.RSAData)
+ self.assertEquals(keys.Key.fromString(
+ keydata.privateRSA_openssh_encrypted,
+ passphrase='encrypted'),
+ keys.Key.fromString(keydata.privateRSA_openssh))
+ self.assertEquals(keys.Key.fromString(
+ keydata.privateRSA_openssh_alternate),
+ keys.Key.fromString(keydata.privateRSA_openssh))
+ self._testPublicPrivateFromString(keydata.publicDSA_openssh,
+ keydata.privateDSA_openssh, 'DSA', keydata.DSAData)
+
+ def test_fromOpenSSH_with_whitespace(self):
+ """
+ If key strings have trailing whitespace, it should be ignored.
+ """
+ # from bug #3391, since our test key data doesn't have
+ # an issue with appended newlines
+ privateDSAData = """-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQDylESNuc61jq2yatCzZbenlr9llG+p9LhIpOLUbXhhHcwC6hrh
+EZIdCKqTO0USLrGoP5uS9UHAUoeN62Z0KXXWTwOWGEQn/syyPzNJtnBorHpNUT9D
+Qzwl1yUa53NNgEctpo4NoEFOx8PuU6iFLyvgHCjNn2MsuGuzkZm7sI9ZpQIVAJiR
+9dPc08KLdpJyRxz8T74b4FQRAoGAGBc4Z5Y6R/HZi7AYM/iNOM8su6hrk8ypkBwR
+a3Dbhzk97fuV3SF1SDrcQu4zF7c4CtH609N5nfZs2SUjLLGPWln83Ysb8qhh55Em
+AcHXuROrHS/sDsnqu8FQp86MaudrqMExCOYyVPE7jaBWW+/JWFbKCxmgOCSdViUJ
+esJpBFsCgYEA7+jtVvSt9yrwsS/YU1QGP5wRAiDYB+T5cK4HytzAqJKRdC5qS4zf
+C7R0eKcDHHLMYO39aPnCwXjscisnInEhYGNblTDyPyiyNxAOXuC8x7luTmwzMbNJ
+/ow0IqSj0VF72VJN9uSoPpFd4lLT0zN8v42RWja0M8ohWNf+YNJluPgCFE0PT4Vm
+SUrCyZXsNh6VXwjs3gKQ
+-----END DSA PRIVATE KEY-----"""
+ self.assertEquals(keys.Key.fromString(privateDSAData),
+ keys.Key.fromString(privateDSAData + '\n'))
+
+ def test_fromLSH(self):
+ """
+ Test that keys are correctly generated from LSH strings.
+ """
+ self._testPublicPrivateFromString(keydata.publicRSA_lsh,
+ keydata.privateRSA_lsh, 'RSA', keydata.RSAData)
+ self._testPublicPrivateFromString(keydata.publicDSA_lsh,
+ keydata.privateDSA_lsh, 'DSA', keydata.DSAData)
+ sexp = sexpy.pack([['public-key', ['bad-key', ['p', '2']]]])
+ self.assertRaises(keys.BadKeyError, keys.Key.fromString,
+ data='{'+base64.encodestring(sexp)+'}')
+ sexp = sexpy.pack([['private-key', ['bad-key', ['p', '2']]]])
+ self.assertRaises(keys.BadKeyError, keys.Key.fromString,
+ sexp)
+
+ def test_fromAgentv3(self):
+ """
+ Test that keys are correctly generated from Agent v3 strings.
+ """
+ self._testPrivateFromString(keydata.privateRSA_agentv3, 'RSA',
+ keydata.RSAData)
+ self._testPrivateFromString(keydata.privateDSA_agentv3, 'DSA',
+ keydata.DSAData)
+ self.assertRaises(keys.BadKeyError, keys.Key.fromString,
+ '\x00\x00\x00\x07ssh-foo'+'\x00\x00\x00\x01\x01'*5)
+
+ def test_fromStringErrors(self):
+ """
+ keys.Key.fromString should raise BadKeyError when the key is invalid.
+ """
+ self.assertRaises(keys.BadKeyError, keys.Key.fromString, '')
+ # no key data with a bad key type
+ self.assertRaises(keys.BadKeyError, keys.Key.fromString, '',
+ 'bad_type')
+ # trying to decrypt a key which doesn't support encryption
+ self.assertRaises(keys.BadKeyError, keys.Key.fromString,
+ keydata.publicRSA_lsh, passphrase = 'unencrypted')
+ # trying to decrypt an unencrypted key
+ self.assertRaises(keys.EncryptedKeyError, keys.Key.fromString,
+ keys.Key(self.rsaObj).toString('openssh', 'encrypted'))
+ # key with no key data
+ self.assertRaises(keys.BadKeyError, keys.Key.fromString,
+ '-----BEGIN RSA KEY-----\nwA==\n')
+
+ def test_fromFile(self):
+ """
+ Test that fromFile works correctly.
+ """
+ self.assertEquals(keys.Key.fromFile(self.keyFile),
+ keys.Key.fromString(keydata.privateRSA_lsh))
+ self.assertRaises(keys.BadKeyError, keys.Key.fromFile,
+ self.keyFile, 'bad_type')
+ self.assertRaises(keys.BadKeyError, keys.Key.fromFile,
+ self.keyFile, passphrase='unencrypted')
+
+ def test_init(self):
+ """
+ Test that the PublicKey object is initialized correctly.
+ """
+ obj = Crypto.PublicKey.RSA.construct((1L, 2L))
+ key = keys.Key(obj)
+ self.assertEquals(key.keyObject, obj)
+
+ def test_equal(self):
+ """
+ Test that Key objects are compared correctly.
+ """
+ rsa1 = keys.Key(self.rsaObj)
+ rsa2 = keys.Key(self.rsaObj)
+ rsa3 = keys.Key(Crypto.PublicKey.RSA.construct((1L, 2L)))
+ dsa = keys.Key(self.dsaObj)
+ self.assertTrue(rsa1 == rsa2)
+ self.assertFalse(rsa1 == rsa3)
+ self.assertFalse(rsa1 == dsa)
+ self.assertFalse(rsa1 == object)
+ self.assertFalse(rsa1 == None)
+
+ def test_notEqual(self):
+ """
+ Test that Key objects are not-compared correctly.
+ """
+ rsa1 = keys.Key(self.rsaObj)
+ rsa2 = keys.Key(self.rsaObj)
+ rsa3 = keys.Key(Crypto.PublicKey.RSA.construct((1L, 2L)))
+ dsa = keys.Key(self.dsaObj)
+ self.assertFalse(rsa1 != rsa2)
+ self.assertTrue(rsa1 != rsa3)
+ self.assertTrue(rsa1 != dsa)
+ self.assertTrue(rsa1 != object)
+ self.assertTrue(rsa1 != None)
+
+ def test_type(self):
+ """
+ Test that the type method returns the correct type for an object.
+ """
+ self.assertEquals(keys.Key(self.rsaObj).type(), 'RSA')
+ self.assertEquals(keys.Key(self.rsaObj).sshType(), 'ssh-rsa')
+ self.assertEquals(keys.Key(self.dsaObj).type(), 'DSA')
+ self.assertEquals(keys.Key(self.dsaObj).sshType(), 'ssh-dss')
+ self.assertRaises(RuntimeError, keys.Key(None).type)
+ self.assertRaises(RuntimeError, keys.Key(None).sshType)
+ self.assertRaises(RuntimeError, keys.Key(self).type)
+ self.assertRaises(RuntimeError, keys.Key(self).sshType)
+
+ def test_fromBlob(self):
+ """
+ Test that a public key is correctly generated from a public key blob.
+ """
+ rsaBlob = common.NS('ssh-rsa') + common.MP(2) + common.MP(3)
+ rsaKey = keys.Key.fromString(rsaBlob)
+ dsaBlob = (common.NS('ssh-dss') + common.MP(2) + common.MP(3) +
+ common.MP(4) + common.MP(5))
+ dsaKey = keys.Key.fromString(dsaBlob)
+ badBlob = common.NS('ssh-bad')
+ self.assertTrue(rsaKey.isPublic())
+ self.assertEquals(rsaKey.data(), {'e':2L, 'n':3L})
+ self.assertTrue(dsaKey.isPublic())
+ self.assertEquals(dsaKey.data(), {'p':2L, 'q':3L, 'g':4L, 'y':5L})
+ self.assertRaises(keys.BadKeyError,
+ keys.Key.fromString, badBlob)
+
+
+ def test_fromPrivateBlob(self):
+ """
+ Test that a private key is correctly generated from a private key blob.
+ """
+ rsaBlob = (common.NS('ssh-rsa') + common.MP(2) + common.MP(3) +
+ common.MP(4) + common.MP(5) + common.MP(6) + common.MP(7))
+ rsaKey = keys.Key._fromString_PRIVATE_BLOB(rsaBlob)
+ dsaBlob = (common.NS('ssh-dss') + common.MP(2) + common.MP(3) +
+ common.MP(4) + common.MP(5) + common.MP(6))
+ dsaKey = keys.Key._fromString_PRIVATE_BLOB(dsaBlob)
+ badBlob = common.NS('ssh-bad')
+ self.assertFalse(rsaKey.isPublic())
+ self.assertEqual(
+ rsaKey.data(), {'n':2L, 'e':3L, 'd':4L, 'u':5L, 'p':6L, 'q':7L})
+ self.assertFalse(dsaKey.isPublic())
+ self.assertEqual(dsaKey.data(), {'p':2L, 'q':3L, 'g':4L, 'y':5L, 'x':6L})
+ self.assertRaises(
+ keys.BadKeyError, keys.Key._fromString_PRIVATE_BLOB, badBlob)
+
+
+ def test_blob(self):
+ """
+ Test that the Key object generates blobs correctly.
+ """
+ self.assertEquals(keys.Key(self.rsaObj).blob(),
+ '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01\x02'
+ '\x00\x00\x00\x01\x01')
+ self.assertEquals(keys.Key(self.dsaObj).blob(),
+ '\x00\x00\x00\x07ssh-dss\x00\x00\x00\x01\x03'
+ '\x00\x00\x00\x01\x04\x00\x00\x00\x01\x02'
+ '\x00\x00\x00\x01\x01')
+
+ badKey = keys.Key(None)
+ self.assertRaises(RuntimeError, badKey.blob)
+
+
+ def test_privateBlob(self):
+ """
+ L{Key.privateBlob} returns the SSH protocol-level format of the private
+ key and raises L{RuntimeError} if the underlying key object is invalid.
+ """
+ self.assertEquals(keys.Key(self.rsaObj).privateBlob(),
+ '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01\x01'
+ '\x00\x00\x00\x01\x02\x00\x00\x00\x01\x03\x00'
+ '\x00\x00\x01\x04\x00\x00\x00\x01\x04\x00\x00'
+ '\x00\x01\x05')
+ self.assertEquals(keys.Key(self.dsaObj).privateBlob(),
+ '\x00\x00\x00\x07ssh-dss\x00\x00\x00\x01\x03'
+ '\x00\x00\x00\x01\x04\x00\x00\x00\x01\x02\x00'
+ '\x00\x00\x01\x01\x00\x00\x00\x01\x05')
+
+ badKey = keys.Key(None)
+ self.assertRaises(RuntimeError, badKey.privateBlob)
+
+
+ def test_toOpenSSH(self):
+ """
+ Test that the Key object generates OpenSSH keys correctly.
+ """
+ key = keys.Key.fromString(keydata.privateRSA_lsh)
+ self.assertEquals(key.toString('openssh'), keydata.privateRSA_openssh)
+ self.assertEquals(key.toString('openssh', 'encrypted'),
+ keydata.privateRSA_openssh_encrypted)
+ self.assertEquals(key.public().toString('openssh'),
+ keydata.publicRSA_openssh[:-8]) # no comment
+ self.assertEquals(key.public().toString('openssh', 'comment'),
+ keydata.publicRSA_openssh)
+ key = keys.Key.fromString(keydata.privateDSA_lsh)
+ self.assertEquals(key.toString('openssh'), keydata.privateDSA_openssh)
+ self.assertEquals(key.public().toString('openssh', 'comment'),
+ keydata.publicDSA_openssh)
+ self.assertEquals(key.public().toString('openssh'),
+ keydata.publicDSA_openssh[:-8]) # no comment
+
+ def test_toLSH(self):
+ """
+ Test that the Key object generates LSH keys correctly.
+ """
+ key = keys.Key.fromString(keydata.privateRSA_openssh)
+ self.assertEquals(key.toString('lsh'), keydata.privateRSA_lsh)
+ self.assertEquals(key.public().toString('lsh'),
+ keydata.publicRSA_lsh)
+ key = keys.Key.fromString(keydata.privateDSA_openssh)
+ self.assertEquals(key.toString('lsh'), keydata.privateDSA_lsh)
+ self.assertEquals(key.public().toString('lsh'),
+ keydata.publicDSA_lsh)
+
+ def test_toAgentv3(self):
+ """
+ Test that the Key object generates Agent v3 keys correctly.
+ """
+ key = keys.Key.fromString(keydata.privateRSA_openssh)
+ self.assertEquals(key.toString('agentv3'), keydata.privateRSA_agentv3)
+ key = keys.Key.fromString(keydata.privateDSA_openssh)
+ self.assertEquals(key.toString('agentv3'), keydata.privateDSA_agentv3)
+
+ def test_toStringErrors(self):
+ """
+ Test that toString raises errors appropriately.
+ """
+ self.assertRaises(keys.BadKeyError, keys.Key(self.rsaObj).toString,
+ 'bad_type')
+
+ def test_sign(self):
+ """
+ Test that the Key object generates correct signatures.
+ """
+ key = keys.Key.fromString(keydata.privateRSA_openssh)
+ self.assertEquals(key.sign(''), self.rsaSignature)
+ key = keys.Key.fromString(keydata.privateDSA_openssh)
+ self.assertEquals(key.sign(''), self.dsaSignature)
+
+
+ def test_verify(self):
+ """
+ Test that the Key object correctly verifies signatures.
+ """
+ key = keys.Key.fromString(keydata.publicRSA_openssh)
+ self.assertTrue(key.verify(self.rsaSignature, ''))
+ self.assertFalse(key.verify(self.rsaSignature, 'a'))
+ self.assertFalse(key.verify(self.dsaSignature, ''))
+ key = keys.Key.fromString(keydata.publicDSA_openssh)
+ self.assertTrue(key.verify(self.dsaSignature, ''))
+ self.assertFalse(key.verify(self.dsaSignature, 'a'))
+ self.assertFalse(key.verify(self.rsaSignature, ''))
+
+ def test_repr(self):
+ """
+ Test the pretty representation of Key.
+ """
+ self.assertEquals(repr(keys.Key(self.rsaObj)),
+"""<RSA Private Key (0 bits)
+attr e:
+\t02
+attr d:
+\t03
+attr n:
+\t01
+attr q:
+\t05
+attr p:
+\t04
+attr u:
+\t04>""")
+
+class WarningsTestCase(unittest.TestCase):
+ """
+ Test that deprecated functions warn the user of their deprecation.
+ """
+ if Crypto is None:
+ skip = "cannot run w/o PyCrypto"
+ if pyasn1 is None:
+ skip = "cannot run w/o/ PyASN1"
+
+ def setUp(self):
+ self.keyObject = keys.Key.fromString(keydata.privateRSA_lsh).keyObject
+
+ def test_getPublicKeyString(self):
+ """
+ Test that getPublicKeyString warns with a DeprecationWarning.
+ """
+ self.assertWarns(DeprecationWarning,
+ "getPublicKeyString is deprecated since Twisted Conch 0.9."
+ " Use Key.fromString().blob().",
+ unittest.__file__, keys.getPublicKeyString,
+ data=keydata.publicRSA_openssh)
+
+ def test_makePublicKeyString(self):
+ """
+ Test that makePublicKeyString warns with a DeprecationWarning.
+ """
+ self.assertWarns(DeprecationWarning,
+ "makePublicKeyString is deprecated since Twisted Conch 0.9."
+ " Use Key(obj).public().toString().", unittest.__file__,
+ keys.makePublicKeyString, self.keyObject)
+
+ def test_getPublicKeyObject(self):
+ """
+ Test that getPublicKeyObject warns with a DeprecationWarning.
+ """
+ self.assertWarns(DeprecationWarning,
+ "getPublicKeyObject is deprecated since Twisted Conch 0.9."
+ " Use Key.fromString().", unittest.__file__,
+ keys.getPublicKeyObject, keydata.publicRSA_lsh)
+
+ def test_getPrivateKeyObject(self):
+ """
+ Test that getPrivateKeyObject warns with a DeprecationWarning.
+ """
+ self.assertWarns(DeprecationWarning,
+ "getPrivateKeyObject is deprecated since Twisted Conch 0.9."
+ " Use Key.fromString().", unittest.__file__,
+ keys.getPrivateKeyObject, data=keydata.privateRSA_lsh)
+
+ def test_makePrivateKeyString(self):
+ """
+ Test that makePrivateKeyString warns with a DeprecationWarning.
+ """
+ self.assertWarns(DeprecationWarning,
+ "makePrivateKeyString is deprecated since Twisted Conch 0.9."
+ " Use Key(obj).toString().", unittest.__file__,
+ keys.makePrivateKeyString, self.keyObject)
+
+ def test_makePublicKeyBlob(self):
+ """
+ Test that makePublicKeyBlob warns with a DeprecationWarning.
+ """
+ self.assertWarns(DeprecationWarning,
+ "makePublicKeyBlob is deprecated since Twisted Conch 0.9."
+ " Use Key(obj).blob().", unittest.__file__,
+ keys.makePublicKeyBlob, self.keyObject)
+
+ def test_signData(self):
+ """
+ Test that signData warns with a DeprecationWarning.
+ """
+ self.assertWarns(DeprecationWarning,
+ "signData is deprecated since Twisted Conch 0.9."
+ " Use Key(obj).sign(data).", unittest.__file__,
+ keys.signData, self.keyObject, '')
+
+ def test_verifySignature(self):
+ """
+ Test that signData warns with a DeprecationWarning.
+ """
+ self.assertWarns(DeprecationWarning,
+ "verifySignature is deprecated since Twisted Conch 0.9."
+ " Use Key(obj).verify(signature, data).", unittest.__file__,
+ keys.verifySignature, self.keyObject, '\x00\x00\x00\x00', '')
+
+ def test_printKey(self):
+ """
+ Test that signData warns with a DeprecationWarning.
+ """
+ self.assertWarns(DeprecationWarning,
+ "printKey is deprecated since Twisted Conch 0.9."
+ " Use repr(Key(obj)).", unittest.__file__,
+ keys.printKey, self.keyObject)
+
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_knownhosts.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_knownhosts.py
new file mode 100644
index 0000000000..c814263835
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_knownhosts.py
@@ -0,0 +1,979 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.client.knownhosts}.
+"""
+
+import os
+from binascii import Error as BinasciiError, b2a_base64, a2b_base64
+
+try:
+ import Crypto
+ import pyasn1
+except ImportError:
+ skip = "PyCrypto and PyASN1 required for twisted.conch.knownhosts."
+else:
+ from twisted.conch.ssh.keys import Key, BadKeyError
+ from twisted.conch.client.knownhosts import \
+ PlainEntry, HashedEntry, KnownHostsFile, UnparsedEntry, ConsoleUI
+ from twisted.conch.client import default
+
+from zope.interface.verify import verifyObject
+
+from twisted.python.filepath import FilePath
+from twisted.trial.unittest import TestCase
+from twisted.internet.defer import Deferred
+from twisted.conch.interfaces import IKnownHostEntry
+from twisted.conch.error import HostKeyChanged, UserRejectedKey, InvalidEntry
+
+
+sampleEncodedKey = (
+ 'AAAAB3NzaC1yc2EAAAABIwAAAQEAsV0VMRbGmzhqxxayLRHmvnFvtyNqgbNKV46dU1bVFB+3y'
+ 'tNvue4Riqv/SVkPRNwMb7eWH29SviXaBxUhYyzKkDoNUq3rTNnH1Vnif6d6X4JCrUb5d3W+Dm'
+ 'YClyJrZ5HgD/hUpdSkTRqdbQ2TrvSAxRacj+vHHT4F4dm1bJSewm3B2D8HVOoi/CbVh3dsIiC'
+ 'dp8VltdZx4qYVfYe2LwVINCbAa3d3tj9ma7RVfw3OH2Mfb+toLd1N5tBQFb7oqTt2nC6I/6Bd'
+ '4JwPUld+IEitw/suElq/AIJVQXXujeyiZlea90HE65U2mF1ytr17HTAIT2ySokJWyuBANGACk'
+ '6iIaw==')
+
+otherSampleEncodedKey = (
+ 'AAAAB3NzaC1yc2EAAAABIwAAAIEAwaeCZd3UCuPXhX39+/p9qO028jTF76DMVd9mPvYVDVXuf'
+ 'WckKZauF7+0b7qm+ChT7kan6BzRVo4++gCVNfAlMzLysSt3ylmOR48tFpAfygg9UCX3DjHz0E'
+ 'lOOUKh3iifc9aUShD0OPaK3pR5JJ8jfiBfzSYWt/hDi/iZ4igsSs8=')
+
+thirdSampleEncodedKey = (
+ 'AAAAB3NzaC1yc2EAAAABIwAAAQEAl/TQakPkePlnwCBRPitIVUTg6Z8VzN1en+DGkyo/evkmLw'
+ '7o4NWR5qbysk9A9jXW332nxnEuAnbcCam9SHe1su1liVfyIK0+3bdn0YRB0sXIbNEtMs2LtCho'
+ '/aV3cXPS+Cf1yut3wvIpaRnAzXxuKPCTXQ7/y0IXa8TwkRBH58OJa3RqfQ/NsSp5SAfdsrHyH2'
+ 'aitiVKm2jfbTKzSEqOQG/zq4J9GXTkq61gZugory/Tvl5/yPgSnOR6C9jVOMHf27ZPoRtyj9SY'
+ '343Hd2QHiIE0KPZJEgCynKeWoKz8v6eTSK8n4rBnaqWdp8MnGZK1WGy05MguXbyCDuTC8AmJXQ'
+ '==')
+
+sampleKey = a2b_base64(sampleEncodedKey)
+otherSampleKey = a2b_base64(otherSampleEncodedKey)
+thirdSampleKey = a2b_base64(thirdSampleEncodedKey)
+
+samplePlaintextLine = (
+ "www.twistedmatrix.com ssh-rsa " + sampleEncodedKey + "\n")
+
+otherSamplePlaintextLine = (
+ "divmod.com ssh-rsa " + otherSampleEncodedKey + "\n")
+
+sampleHostIPLine = (
+ "www.twistedmatrix.com,198.49.126.131 ssh-rsa " + sampleEncodedKey + "\n")
+
+sampleHashedLine = (
+ "|1|gJbSEPBG9ZSBoZpHNtZBD1bHKBA=|bQv+0Xa0dByrwkA1EB0E7Xop/Fo= ssh-rsa " +
+ sampleEncodedKey + "\n")
+
+
+
+class EntryTestsMixin:
+ """
+ Tests for implementations of L{IKnownHostEntry}. Subclasses must set the
+ 'entry' attribute to a provider of that interface, the implementation of
+ that interface under test.
+
+ @ivar entry: a provider of L{IKnownHostEntry} with a hostname of
+ www.twistedmatrix.com and an RSA key of sampleKey.
+ """
+
+ def test_providesInterface(self):
+ """
+ The given entry should provide IKnownHostEntry.
+ """
+ verifyObject(IKnownHostEntry, self.entry)
+
+
+ def test_fromString(self):
+ """
+ Constructing a plain text entry from an unhashed known_hosts entry will
+ result in an L{IKnownHostEntry} provider with 'keyString', 'hostname',
+ and 'keyType' attributes. While outside the interface in question,
+ these attributes are held in common by L{PlainEntry} and L{HashedEntry}
+ implementations; other implementations should override this method in
+ subclasses.
+ """
+ entry = self.entry
+ self.assertEqual(entry.publicKey, Key.fromString(sampleKey))
+ self.assertEqual(entry.keyType, "ssh-rsa")
+
+
+ def test_matchesKey(self):
+ """
+ L{IKnownHostEntry.matchesKey} checks to see if an entry matches a given
+ SSH key.
+ """
+ twistedmatrixDotCom = Key.fromString(sampleKey)
+ divmodDotCom = Key.fromString(otherSampleKey)
+ self.assertEqual(
+ True,
+ self.entry.matchesKey(twistedmatrixDotCom))
+ self.assertEqual(
+ False,
+ self.entry.matchesKey(divmodDotCom))
+
+
+ def test_matchesHost(self):
+ """
+ L{IKnownHostEntry.matchesHost} checks to see if an entry matches a
+ given hostname.
+ """
+ self.assertEqual(True, self.entry.matchesHost(
+ "www.twistedmatrix.com"))
+ self.assertEqual(False, self.entry.matchesHost(
+ "www.divmod.com"))
+
+
+
+class PlainEntryTests(EntryTestsMixin, TestCase):
+ """
+ Test cases for L{PlainEntry}.
+ """
+ plaintextLine = samplePlaintextLine
+ hostIPLine = sampleHostIPLine
+
+ def setUp(self):
+ """
+ Set 'entry' to a sample plain-text entry with sampleKey as its key.
+ """
+ self.entry = PlainEntry.fromString(self.plaintextLine)
+
+
+ def test_matchesHostIP(self):
+ """
+ A "hostname,ip" formatted line will match both the host and the IP.
+ """
+ self.entry = PlainEntry.fromString(self.hostIPLine)
+ self.assertEqual(True, self.entry.matchesHost("198.49.126.131"))
+ self.test_matchesHost()
+
+
+ def test_toString(self):
+ """
+ L{PlainEntry.toString} generates the serialized OpenSSL format string
+ for the entry, sans newline.
+ """
+ self.assertEqual(self.entry.toString(), self.plaintextLine.rstrip("\n"))
+ multiHostEntry = PlainEntry.fromString(self.hostIPLine)
+ self.assertEqual(multiHostEntry.toString(), self.hostIPLine.rstrip("\n"))
+
+
+
+class PlainTextWithCommentTests(PlainEntryTests):
+ """
+ Test cases for L{PlainEntry} when parsed from a line with a comment.
+ """
+
+ plaintextLine = samplePlaintextLine[:-1] + " plain text comment.\n"
+ hostIPLine = sampleHostIPLine[:-1] + " text following host/IP line\n"
+
+
+
+class HashedEntryTests(EntryTestsMixin, TestCase):
+ """
+ Tests for L{HashedEntry}.
+
+ This suite doesn't include any tests for host/IP pairs because hashed
+ entries store IP addresses the same way as hostnames and does not support
+ comma-separated lists. (If you hash the IP and host together you can't
+ tell if you've got the key already for one or the other.)
+ """
+ hashedLine = sampleHashedLine
+
+ def setUp(self):
+ """
+ Set 'entry' to a sample hashed entry for twistedmatrix.com with
+ sampleKey as its key.
+ """
+ self.entry = HashedEntry.fromString(self.hashedLine)
+
+
+ def test_toString(self):
+ """
+ L{HashedEntry.toString} generates the serialized OpenSSL format string
+ for the entry, sans the newline.
+ """
+ self.assertEqual(self.entry.toString(), self.hashedLine.rstrip("\n"))
+
+
+
+class HashedEntryWithCommentTests(HashedEntryTests):
+ """
+ Test cases for L{PlainEntry} when parsed from a line with a comment.
+ """
+
+ hashedLine = sampleHashedLine[:-1] + " plain text comment.\n"
+
+
+
+class UnparsedEntryTests(TestCase, EntryTestsMixin):
+ """
+ Tests for L{UnparsedEntry}
+ """
+ def setUp(self):
+ """
+ Set up the 'entry' to be an unparsed entry for some random text.
+ """
+ self.entry = UnparsedEntry(" This is a bogus entry. \n")
+
+
+ def test_fromString(self):
+ """
+ Creating an L{UnparsedEntry} should simply record the string it was
+ passed.
+ """
+ self.assertEqual(" This is a bogus entry. \n",
+ self.entry._string)
+
+
+ def test_matchesHost(self):
+ """
+ An unparsed entry can't match any hosts.
+ """
+ self.assertEqual(False, self.entry.matchesHost("www.twistedmatrix.com"))
+
+
+ def test_matchesKey(self):
+ """
+ An unparsed entry can't match any keys.
+ """
+ self.assertEqual(False, self.entry.matchesKey(Key.fromString(sampleKey)))
+
+
+ def test_toString(self):
+ """
+ L{UnparsedEntry.toString} returns its input string, sans trailing newline.
+ """
+ self.assertEqual(" This is a bogus entry. ", self.entry.toString())
+
+
+
+class ParseErrorTests(TestCase):
+ """
+ L{HashedEntry.fromString} and L{PlainEntry.fromString} can raise a variety
+ of errors depending on misformattings of certain strings. These tests make
+ sure those errors are caught. Since many of the ways that this can go
+ wrong are in the lower-level APIs being invoked by the parsing logic,
+ several of these are integration tests with the L{base64} and
+ L{twisted.conch.ssh.keys} modules.
+ """
+
+ def invalidEntryTest(self, cls):
+ """
+ If there are fewer than three elements, C{fromString} should raise
+ L{InvalidEntry}.
+ """
+ self.assertRaises(InvalidEntry, cls.fromString, "invalid")
+
+
+ def notBase64Test(self, cls):
+ """
+ If the key is not base64, C{fromString} should raise L{BinasciiError}.
+ """
+ self.assertRaises(BinasciiError, cls.fromString, "x x x")
+
+
+ def badKeyTest(self, cls, prefix):
+ """
+ If the key portion of the entry is valid base64, but is not actually an
+ SSH key, C{fromString} should raise L{BadKeyError}.
+ """
+ self.assertRaises(BadKeyError, cls.fromString, ' '.join(
+ [prefix, "ssh-rsa", b2a_base64(
+ "Hey, this isn't an SSH key!").strip()]))
+
+
+ def test_invalidPlainEntry(self):
+ """
+ If there are fewer than three whitespace-separated elements in an
+ entry, L{PlainEntry.fromString} should raise L{InvalidEntry}.
+ """
+ self.invalidEntryTest(PlainEntry)
+
+
+ def test_invalidHashedEntry(self):
+ """
+ If there are fewer than three whitespace-separated elements in an
+ entry, or the hostname salt/hash portion has more than two elements,
+ L{HashedEntry.fromString} should raise L{InvalidEntry}.
+ """
+ self.invalidEntryTest(HashedEntry)
+ a, b, c = sampleHashedLine.split()
+ self.assertRaises(InvalidEntry, HashedEntry.fromString, ' '.join(
+ [a + "||", b, c]))
+
+
+ def test_plainNotBase64(self):
+ """
+ If the key portion of a plain entry is not decodable as base64,
+ C{fromString} should raise L{BinasciiError}.
+ """
+ self.notBase64Test(PlainEntry)
+
+
+ def test_hashedNotBase64(self):
+ """
+ If the key, host salt, or host hash portion of a hashed entry is not
+ encoded, it will raise L{BinasciiError}.
+ """
+ self.notBase64Test(HashedEntry)
+ a, b, c = sampleHashedLine.split()
+ # Salt not valid base64.
+ self.assertRaises(
+ BinasciiError, HashedEntry.fromString,
+ ' '.join(["|1|x|" + b2a_base64("stuff").strip(), b, c]))
+ # Host hash not valid base64.
+ self.assertRaises(
+ BinasciiError, HashedEntry.fromString,
+ ' '.join([HashedEntry.MAGIC + b2a_base64("stuff").strip() + "|x", b, c]))
+ # Neither salt nor hash valid base64.
+ self.assertRaises(
+ BinasciiError, HashedEntry.fromString,
+ ' '.join(["|1|x|x", b, c]))
+
+
+ def test_hashedBadKey(self):
+ """
+ If the key portion of the entry is valid base64, but is not actually an
+ SSH key, C{HashedEntry.fromString} should raise L{BadKeyError}.
+ """
+ a, b, c = sampleHashedLine.split()
+ self.badKeyTest(HashedEntry, a)
+
+
+ def test_plainBadKey(self):
+ """
+ If the key portion of the entry is valid base64, but is not actually an
+ SSH key, C{PlainEntry.fromString} should raise L{BadKeyError}.
+ """
+ self.badKeyTest(PlainEntry, "hostname")
+
+
+
+class KnownHostsDatabaseTests(TestCase):
+ """
+ Tests for L{KnownHostsFile}.
+ """
+
+ def pathWithContent(self, content):
+ """
+ Return a FilePath with the given initial content.
+ """
+ fp = FilePath(self.mktemp())
+ fp.setContent(content)
+ return fp
+
+
+ def loadSampleHostsFile(self, content=(
+ sampleHashedLine + otherSamplePlaintextLine +
+ "\n# That was a blank line.\n"
+ "This is just unparseable.\n"
+ "This also unparseable.\n")):
+ """
+ Return a sample hosts file, with keys for www.twistedmatrix.com and
+ divmod.com present.
+ """
+ return KnownHostsFile.fromPath(self.pathWithContent(content))
+
+
+ def test_loadFromPath(self):
+ """
+ Loading a L{KnownHostsFile} from a path with six entries in it will
+ result in a L{KnownHostsFile} object with six L{IKnownHostEntry}
+ providers in it, each of the appropriate type.
+ """
+ hostsFile = self.loadSampleHostsFile()
+ self.assertEqual(len(hostsFile._entries), 6)
+ self.assertIsInstance(hostsFile._entries[0], HashedEntry)
+ self.assertEqual(True, hostsFile._entries[0].matchesHost(
+ "www.twistedmatrix.com"))
+ self.assertIsInstance(hostsFile._entries[1], PlainEntry)
+ self.assertEqual(True, hostsFile._entries[1].matchesHost(
+ "divmod.com"))
+ self.assertIsInstance(hostsFile._entries[2], UnparsedEntry)
+ self.assertEqual(hostsFile._entries[2].toString(), "")
+ self.assertIsInstance(hostsFile._entries[3], UnparsedEntry)
+ self.assertEqual(hostsFile._entries[3].toString(),
+ "# That was a blank line.")
+ self.assertIsInstance(hostsFile._entries[4], UnparsedEntry)
+ self.assertEqual(hostsFile._entries[4].toString(),
+ "This is just unparseable.")
+ self.assertIsInstance(hostsFile._entries[5], UnparsedEntry)
+ self.assertEqual(hostsFile._entries[5].toString(),
+ "This also unparseable.")
+
+
+ def test_loadNonExistent(self):
+ """
+ Loading a L{KnownHostsFile} from a path that does not exist should
+ result in an empty L{KnownHostsFile} that will save back to that path.
+ """
+ pn = self.mktemp()
+ knownHostsFile = KnownHostsFile.fromPath(FilePath(pn))
+ self.assertEqual([], list(knownHostsFile._entries))
+ self.assertEqual(False, FilePath(pn).exists())
+ knownHostsFile.save()
+ self.assertEqual(True, FilePath(pn).exists())
+
+
+ def test_loadNonExistentParent(self):
+ """
+ Loading a L{KnownHostsFile} from a path whose parent directory does not
+ exist should result in an empty L{KnownHostsFile} that will save back
+ to that path, creating its parent directory(ies) in the process.
+ """
+ thePath = FilePath(self.mktemp())
+ knownHostsPath = thePath.child("foo").child("known_hosts")
+ knownHostsFile = KnownHostsFile.fromPath(knownHostsPath)
+ knownHostsFile.save()
+ knownHostsPath.restat(False)
+ self.assertEqual(True, knownHostsPath.exists())
+
+
+ def test_savingAddsEntry(self):
+ """
+ L{KnownHostsFile.save()} will write out a new file with any entries
+ that have been added.
+ """
+ path = self.pathWithContent(sampleHashedLine +
+ otherSamplePlaintextLine)
+ knownHostsFile = KnownHostsFile.fromPath(path)
+ newEntry = knownHostsFile.addHostKey("some.example.com", Key.fromString(thirdSampleKey))
+ expectedContent = (
+ sampleHashedLine +
+ otherSamplePlaintextLine + HashedEntry.MAGIC +
+ b2a_base64(newEntry._hostSalt).strip() + "|" +
+ b2a_base64(newEntry._hostHash).strip() + " ssh-rsa " +
+ thirdSampleEncodedKey + "\n")
+
+ # Sanity check, let's make sure the base64 API being used for the test
+ # isn't inserting spurious newlines.
+ self.assertEqual(3, expectedContent.count("\n"))
+ knownHostsFile.save()
+ self.assertEqual(expectedContent, path.getContent())
+
+
+ def test_hasPresentKey(self):
+ """
+ L{KnownHostsFile.hasHostKey} returns C{True} when a key for the given
+ hostname is present and matches the expected key.
+ """
+ hostsFile = self.loadSampleHostsFile()
+ self.assertEqual(True, hostsFile.hasHostKey(
+ "www.twistedmatrix.com", Key.fromString(sampleKey)))
+
+
+ def test_hasNonPresentKey(self):
+ """
+ L{KnownHostsFile.hasHostKey} returns C{False} when a key for the given
+ hostname is not present.
+ """
+ hostsFile = self.loadSampleHostsFile()
+ self.assertEqual(False, hostsFile.hasHostKey(
+ "non-existent.example.com", Key.fromString(sampleKey)))
+
+
+ def test_hasKeyMismatch(self):
+ """
+ L{KnownHostsFile.hasHostKey} raises L{HostKeyChanged} if the host key
+ is present, but different from the expected one. The resulting
+ exception should have an offendingEntry indicating the given entry.
+ """
+ hostsFile = self.loadSampleHostsFile()
+ exception = self.assertRaises(
+ HostKeyChanged, hostsFile.hasHostKey,
+ "www.twistedmatrix.com", Key.fromString(otherSampleKey))
+ self.assertEqual(exception.offendingEntry, hostsFile._entries[0])
+ self.assertEqual(exception.lineno, 1)
+ self.assertEqual(exception.path, hostsFile._savePath)
+
+
+ def test_addHostKey(self):
+ """
+ L{KnownHostsFile.addHostKey} adds a new L{HashedEntry} to the host
+ file, and returns it.
+ """
+ hostsFile = self.loadSampleHostsFile()
+ aKey = Key.fromString(thirdSampleKey)
+ self.assertEqual(False,
+ hostsFile.hasHostKey("somewhere.example.com", aKey))
+ newEntry = hostsFile.addHostKey("somewhere.example.com", aKey)
+
+ # The code in OpenSSH requires host salts to be 20 characters long.
+ # This is the required length of a SHA-1 HMAC hash, so it's just a
+ # sanity check.
+ self.assertEqual(20, len(newEntry._hostSalt))
+ self.assertEqual(True,
+ newEntry.matchesHost("somewhere.example.com"))
+ self.assertEqual(newEntry.keyType, "ssh-rsa")
+ self.assertEqual(aKey, newEntry.publicKey)
+ self.assertEqual(True,
+ hostsFile.hasHostKey("somewhere.example.com", aKey))
+
+
+ def test_randomSalts(self):
+ """
+ L{KnownHostsFile.addHostKey} generates a random salt for each new key,
+ so subsequent salts will be different.
+ """
+ hostsFile = self.loadSampleHostsFile()
+ aKey = Key.fromString(thirdSampleKey)
+ self.assertNotEqual(
+ hostsFile.addHostKey("somewhere.example.com", aKey)._hostSalt,
+ hostsFile.addHostKey("somewhere-else.example.com", aKey)._hostSalt)
+
+
+ def test_verifyValidKey(self):
+ """
+ Verifying a valid key should return a L{Deferred} which fires with
+ True.
+ """
+ hostsFile = self.loadSampleHostsFile()
+ hostsFile.addHostKey("1.2.3.4", Key.fromString(sampleKey))
+ ui = FakeUI()
+ d = hostsFile.verifyHostKey(ui, "www.twistedmatrix.com", "1.2.3.4",
+ Key.fromString(sampleKey))
+ l = []
+ d.addCallback(l.append)
+ self.assertEqual(l, [True])
+
+
+ def test_verifyInvalidKey(self):
+ """
+ Verfying an invalid key should return a L{Deferred} which fires with a
+ L{HostKeyChanged} failure.
+ """
+ hostsFile = self.loadSampleHostsFile()
+ wrongKey = Key.fromString(thirdSampleKey)
+ ui = FakeUI()
+ hostsFile.addHostKey("1.2.3.4", Key.fromString(sampleKey))
+ d = hostsFile.verifyHostKey(
+ ui, "www.twistedmatrix.com", "1.2.3.4", wrongKey)
+ return self.assertFailure(d, HostKeyChanged)
+
+
+ def verifyNonPresentKey(self):
+ """
+ Set up a test to verify a key that isn't present. Return a 3-tuple of
+ the UI, a list set up to collect the result of the verifyHostKey call,
+ and the sample L{KnownHostsFile} being used.
+
+ This utility method avoids returning a L{Deferred}, and records results
+ in the returned list instead, because the events which get generated
+ here are pre-recorded in the 'ui' object. If the L{Deferred} in
+ question does not fire, the it will fail quickly with an empty list.
+ """
+ hostsFile = self.loadSampleHostsFile()
+ absentKey = Key.fromString(thirdSampleKey)
+ ui = FakeUI()
+ l = []
+ d = hostsFile.verifyHostKey(
+ ui, "sample-host.example.com", "4.3.2.1", absentKey)
+ d.addBoth(l.append)
+ self.assertEqual([], l)
+ self.assertEqual(
+ ui.promptText,
+ "The authenticity of host 'sample-host.example.com (4.3.2.1)' "
+ "can't be established.\n"
+ "RSA key fingerprint is "
+ "89:4e:cc:8c:57:83:96:48:ef:63:ad:ee:99:00:4c:8f.\n"
+ "Are you sure you want to continue connecting (yes/no)? ")
+ return ui, l, hostsFile
+
+
+ def test_verifyNonPresentKey_Yes(self):
+ """
+ Verifying a key where neither the hostname nor the IP are present
+ should result in the UI being prompted with a message explaining as
+ much. If the UI says yes, the Deferred should fire with True.
+ """
+ ui, l, knownHostsFile = self.verifyNonPresentKey()
+ ui.promptDeferred.callback(True)
+ self.assertEqual([True], l)
+ reloaded = KnownHostsFile.fromPath(knownHostsFile._savePath)
+ self.assertEqual(
+ True,
+ reloaded.hasHostKey("4.3.2.1", Key.fromString(thirdSampleKey)))
+ self.assertEqual(
+ True,
+ reloaded.hasHostKey("sample-host.example.com",
+ Key.fromString(thirdSampleKey)))
+
+
+ def test_verifyNonPresentKey_No(self):
+ """
+ Verifying a key where neither the hostname nor the IP are present
+ should result in the UI being prompted with a message explaining as
+ much. If the UI says no, the Deferred should fail with
+ UserRejectedKey.
+ """
+ ui, l, knownHostsFile = self.verifyNonPresentKey()
+ ui.promptDeferred.callback(False)
+ l[0].trap(UserRejectedKey)
+
+
+ def test_verifyHostIPMismatch(self):
+ """
+ Verifying a key where the host is present (and correct), but the IP is
+ present and different, should result the deferred firing in a
+ HostKeyChanged failure.
+ """
+ hostsFile = self.loadSampleHostsFile()
+ wrongKey = Key.fromString(thirdSampleKey)
+ ui = FakeUI()
+ d = hostsFile.verifyHostKey(
+ ui, "www.twistedmatrix.com", "4.3.2.1", wrongKey)
+ return self.assertFailure(d, HostKeyChanged)
+
+
+ def test_verifyKeyForHostAndIP(self):
+ """
+ Verifying a key where the hostname is present but the IP is not should
+ result in the key being added for the IP and the user being warned
+ about the change.
+ """
+ ui = FakeUI()
+ hostsFile = self.loadSampleHostsFile()
+ expectedKey = Key.fromString(sampleKey)
+ hostsFile.verifyHostKey(
+ ui, "www.twistedmatrix.com", "5.4.3.2", expectedKey)
+ self.assertEqual(
+ True, KnownHostsFile.fromPath(hostsFile._savePath).hasHostKey(
+ "5.4.3.2", expectedKey))
+ self.assertEqual(
+ ["Warning: Permanently added the RSA host key for IP address "
+ "'5.4.3.2' to the list of known hosts."],
+ ui.userWarnings)
+
+
+class FakeFile(object):
+ """
+ A fake file-like object that acts enough like a file for
+ L{ConsoleUI.prompt}.
+ """
+
+ def __init__(self):
+ self.inlines = []
+ self.outchunks = []
+ self.closed = False
+
+
+ def readline(self):
+ """
+ Return a line from the 'inlines' list.
+ """
+ return self.inlines.pop(0)
+
+
+ def write(self, chunk):
+ """
+ Append the given item to the 'outchunks' list.
+ """
+ if self.closed:
+ raise IOError("the file was closed")
+ self.outchunks.append(chunk)
+
+
+ def close(self):
+ """
+ Set the 'closed' flag to True, explicitly marking that it has been
+ closed.
+ """
+ self.closed = True
+
+
+
+class ConsoleUITests(TestCase):
+ """
+ Test cases for L{ConsoleUI}.
+ """
+
+ def setUp(self):
+ """
+ Create a L{ConsoleUI} pointed at a L{FakeFile}.
+ """
+ self.fakeFile = FakeFile()
+ self.ui = ConsoleUI(self.openFile)
+
+
+ def openFile(self):
+ """
+ Return the current fake file.
+ """
+ return self.fakeFile
+
+
+ def newFile(self, lines):
+ """
+ Create a new fake file (the next file that self.ui will open) with the
+ given list of lines to be returned from readline().
+ """
+ self.fakeFile = FakeFile()
+ self.fakeFile.inlines = lines
+
+
+ def test_promptYes(self):
+ """
+ L{ConsoleUI.prompt} writes a message to the console, then reads a line.
+ If that line is 'yes', then it returns a L{Deferred} that fires with
+ True.
+ """
+ for okYes in ['yes', 'Yes', 'yes\n']:
+ self.newFile([okYes])
+ l = []
+ self.ui.prompt("Hello, world!").addCallback(l.append)
+ self.assertEqual(["Hello, world!"], self.fakeFile.outchunks)
+ self.assertEqual([True], l)
+ self.assertEqual(True, self.fakeFile.closed)
+
+
+ def test_promptNo(self):
+ """
+ L{ConsoleUI.prompt} writes a message to the console, then reads a line.
+ If that line is 'no', then it returns a L{Deferred} that fires with
+ False.
+ """
+ for okNo in ['no', 'No', 'no\n']:
+ self.newFile([okNo])
+ l = []
+ self.ui.prompt("Goodbye, world!").addCallback(l.append)
+ self.assertEqual(["Goodbye, world!"], self.fakeFile.outchunks)
+ self.assertEqual([False], l)
+ self.assertEqual(True, self.fakeFile.closed)
+
+
+ def test_promptRepeatedly(self):
+ """
+ L{ConsoleUI.prompt} writes a message to the console, then reads a line.
+ If that line is neither 'yes' nor 'no', then it says "Please enter
+ 'yes' or 'no'" until it gets a 'yes' or a 'no', at which point it
+ returns a Deferred that answers either True or False.
+ """
+ self.newFile(['what', 'uh', 'okay', 'yes'])
+ l = []
+ self.ui.prompt("Please say something useful.").addCallback(l.append)
+ self.assertEqual([True], l)
+ self.assertEqual(self.fakeFile.outchunks,
+ ["Please say something useful."] +
+ ["Please type 'yes' or 'no': "] * 3)
+ self.assertEqual(True, self.fakeFile.closed)
+ self.newFile(['blah', 'stuff', 'feh', 'no'])
+ l = []
+ self.ui.prompt("Please say something negative.").addCallback(l.append)
+ self.assertEqual([False], l)
+ self.assertEqual(self.fakeFile.outchunks,
+ ["Please say something negative."] +
+ ["Please type 'yes' or 'no': "] * 3)
+ self.assertEqual(True, self.fakeFile.closed)
+
+
+ def test_promptOpenFailed(self):
+ """
+ If the C{opener} passed to L{ConsoleUI} raises an exception, that
+ exception will fail the L{Deferred} returned from L{ConsoleUI.prompt}.
+ """
+ def raiseIt():
+ raise IOError()
+ ui = ConsoleUI(raiseIt)
+ d = ui.prompt("This is a test.")
+ return self.assertFailure(d, IOError)
+
+
+ def test_warn(self):
+ """
+ L{ConsoleUI.warn} should output a message to the console object.
+ """
+ self.ui.warn("Test message.")
+ self.assertEqual(["Test message."], self.fakeFile.outchunks)
+ self.assertEqual(True, self.fakeFile.closed)
+
+
+ def test_warnOpenFailed(self):
+ """
+ L{ConsoleUI.warn} should log a traceback if the output can't be opened.
+ """
+ def raiseIt():
+ 1 / 0
+ ui = ConsoleUI(raiseIt)
+ ui.warn("This message never makes it.")
+ self.assertEqual(len(self.flushLoggedErrors(ZeroDivisionError)), 1)
+
+
+
+class FakeUI(object):
+ """
+ A fake UI object, adhering to the interface expected by
+ L{KnownHostsFile.verifyHostKey}
+
+ @ivar userWarnings: inputs provided to 'warn'.
+
+ @ivar promptDeferred: last result returned from 'prompt'.
+
+ @ivar promptText: the last input provided to 'prompt'.
+ """
+
+ def __init__(self):
+ self.userWarnings = []
+ self.promptDeferred = None
+ self.promptText = None
+
+
+ def prompt(self, text):
+ """
+ Issue the user an interactive prompt, which they can accept or deny.
+ """
+ self.promptText = text
+ self.promptDeferred = Deferred()
+ return self.promptDeferred
+
+
+ def warn(self, text):
+ """
+ Issue a non-interactive warning to the user.
+ """
+ self.userWarnings.append(text)
+
+
+
+class FakeObject(object):
+ """
+ A fake object that can have some attributes. Used to fake
+ L{SSHClientTransport} and L{SSHClientFactory}.
+ """
+
+
+class DefaultAPITests(TestCase):
+ """
+ The API in L{twisted.conch.client.default.verifyHostKey} is the integration
+ point between the code in the rest of conch and L{KnownHostsFile}.
+ """
+
+ def patchedOpen(self, fname, mode):
+ """
+ The patched version of 'open'; this returns a L{FakeFile} that the
+ instantiated L{ConsoleUI} can use.
+ """
+ self.assertEqual(fname, "/dev/tty")
+ self.assertEqual(mode, "r+b")
+ return self.fakeFile
+
+
+ def setUp(self):
+ """
+ Patch 'open' in verifyHostKey.
+ """
+ self.fakeFile = FakeFile()
+ self.patch(default, "_open", self.patchedOpen)
+ self.hostsOption = self.mktemp()
+ knownHostsFile = KnownHostsFile(FilePath(self.hostsOption))
+ knownHostsFile.addHostKey("exists.example.com", Key.fromString(sampleKey))
+ knownHostsFile.addHostKey("4.3.2.1", Key.fromString(sampleKey))
+ knownHostsFile.save()
+ self.fakeTransport = FakeObject()
+ self.fakeTransport.factory = FakeObject()
+ self.options = self.fakeTransport.factory.options = {
+ 'host': "exists.example.com",
+ 'known-hosts': self.hostsOption
+ }
+
+
+ def test_verifyOKKey(self):
+ """
+ L{default.verifyHostKey} should return a L{Deferred} which fires with
+ C{1} when passed a host, IP, and key which already match the
+ known_hosts file it is supposed to check.
+ """
+ l = []
+ default.verifyHostKey(self.fakeTransport, "4.3.2.1", sampleKey,
+ "I don't care.").addCallback(l.append)
+ self.assertEqual([1], l)
+
+
+ def replaceHome(self, tempHome):
+ """
+ Replace the HOME environment variable until the end of the current
+ test, with the given new home-directory, so that L{os.path.expanduser}
+ will yield controllable, predictable results.
+
+ @param tempHome: the pathname to replace the HOME variable with.
+
+ @type tempHome: L{str}
+ """
+ oldHome = os.environ.get('HOME')
+ def cleanupHome():
+ if oldHome is None:
+ del os.environ['HOME']
+ else:
+ os.environ['HOME'] = oldHome
+ self.addCleanup(cleanupHome)
+ os.environ['HOME'] = tempHome
+
+
+ def test_noKnownHostsOption(self):
+ """
+ L{default.verifyHostKey} should find your known_hosts file in
+ ~/.ssh/known_hosts if you don't specify one explicitly on the command
+ line.
+ """
+ l = []
+ tmpdir = self.mktemp()
+ oldHostsOption = self.hostsOption
+ hostsNonOption = FilePath(tmpdir).child(".ssh").child("known_hosts")
+ hostsNonOption.parent().makedirs()
+ FilePath(oldHostsOption).moveTo(hostsNonOption)
+ self.replaceHome(tmpdir)
+ self.options['known-hosts'] = None
+ default.verifyHostKey(self.fakeTransport, "4.3.2.1", sampleKey,
+ "I don't care.").addCallback(l.append)
+ self.assertEqual([1], l)
+
+
+ def test_verifyHostButNotIP(self):
+ """
+ L{default.verifyHostKey} should return a L{Deferred} which fires with
+ C{1} when passed a host which matches with an IP is not present in its
+ known_hosts file, and should also warn the user that it has added the
+ IP address.
+ """
+ l = []
+ default.verifyHostKey(self.fakeTransport, "8.7.6.5", sampleKey,
+ "Fingerprint not required.").addCallback(l.append)
+ self.assertEqual(
+ ["Warning: Permanently added the RSA host key for IP address "
+ "'8.7.6.5' to the list of known hosts."],
+ self.fakeFile.outchunks)
+ self.assertEqual([1], l)
+ knownHostsFile = KnownHostsFile.fromPath(FilePath(self.hostsOption))
+ self.assertEqual(True, knownHostsFile.hasHostKey("8.7.6.5",
+ Key.fromString(sampleKey)))
+
+
+ def test_verifyQuestion(self):
+ """
+ L{default.verifyHostKey} should return a L{Default} which fires with
+ C{0} when passed a unknown host that the user refuses to acknowledge.
+ """
+ self.fakeTransport.factory.options['host'] = 'fake.example.com'
+ self.fakeFile.inlines.append("no")
+ d = default.verifyHostKey(
+ self.fakeTransport, "9.8.7.6", otherSampleKey, "No fingerprint!")
+ self.assertEqual(
+ ["The authenticity of host 'fake.example.com (9.8.7.6)' "
+ "can't be established.\n"
+ "RSA key fingerprint is "
+ "57:a1:c2:a1:07:a0:2b:f4:ce:b5:e5:b7:ae:cc:e1:99.\n"
+ "Are you sure you want to continue connecting (yes/no)? "],
+ self.fakeFile.outchunks)
+ return self.assertFailure(d, UserRejectedKey)
+
+
+ def test_verifyBadKey(self):
+ """
+ L{default.verifyHostKey} should return a L{Deferred} which fails with
+ L{HostKeyChanged} if the host key is incorrect.
+ """
+ d = default.verifyHostKey(
+ self.fakeTransport, "4.3.2.1", otherSampleKey,
+ "Again, not required.")
+ return self.assertFailure(d, HostKeyChanged)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_manhole.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_manhole.py
new file mode 100644
index 0000000000..2b5c81ef19
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_manhole.py
@@ -0,0 +1,348 @@
+# -*- test-case-name: twisted.conch.test.test_manhole -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.manhole}.
+"""
+
+import traceback
+
+from twisted.trial import unittest
+from twisted.internet import error, defer
+from twisted.test.proto_helpers import StringTransport
+from twisted.conch.test.test_recvline import _TelnetMixin, _SSHMixin, _StdioMixin, stdio, ssh
+from twisted.conch import manhole
+from twisted.conch.insults import insults
+
+
+def determineDefaultFunctionName():
+ """
+ Return the string used by Python as the name for code objects which are
+ compiled from interactive input or at the top-level of modules.
+ """
+ try:
+ 1 / 0
+ except:
+ # The last frame is this function. The second to last frame is this
+ # function's caller, which is module-scope, which is what we want,
+ # so -2.
+ return traceback.extract_stack()[-2][2]
+defaultFunctionName = determineDefaultFunctionName()
+
+
+
+class ManholeInterpreterTests(unittest.TestCase):
+ """
+ Tests for L{manhole.ManholeInterpreter}.
+ """
+ def test_resetBuffer(self):
+ """
+ L{ManholeInterpreter.resetBuffer} should empty the input buffer.
+ """
+ interpreter = manhole.ManholeInterpreter(None)
+ interpreter.buffer.extend(["1", "2"])
+ interpreter.resetBuffer()
+ self.assertFalse(interpreter.buffer)
+
+
+
+class ManholeProtocolTests(unittest.TestCase):
+ """
+ Tests for L{manhole.Manhole}.
+ """
+ def test_interruptResetsInterpreterBuffer(self):
+ """
+ L{manhole.Manhole.handle_INT} should cause the interpreter input buffer
+ to be reset.
+ """
+ transport = StringTransport()
+ terminal = insults.ServerProtocol(manhole.Manhole)
+ terminal.makeConnection(transport)
+ protocol = terminal.terminalProtocol
+ interpreter = protocol.interpreter
+ interpreter.buffer.extend(["1", "2"])
+ protocol.handle_INT()
+ self.assertFalse(interpreter.buffer)
+
+
+
+class WriterTestCase(unittest.TestCase):
+ def testInteger(self):
+ manhole.lastColorizedLine("1")
+
+
+ def testDoubleQuoteString(self):
+ manhole.lastColorizedLine('"1"')
+
+
+ def testSingleQuoteString(self):
+ manhole.lastColorizedLine("'1'")
+
+
+ def testTripleSingleQuotedString(self):
+ manhole.lastColorizedLine("'''1'''")
+
+
+ def testTripleDoubleQuotedString(self):
+ manhole.lastColorizedLine('"""1"""')
+
+
+ def testFunctionDefinition(self):
+ manhole.lastColorizedLine("def foo():")
+
+
+ def testClassDefinition(self):
+ manhole.lastColorizedLine("class foo:")
+
+
+class ManholeLoopbackMixin:
+ serverProtocol = manhole.ColoredManhole
+
+ def wfd(self, d):
+ return defer.waitForDeferred(d)
+
+ def testSimpleExpression(self):
+ done = self.recvlineClient.expect("done")
+
+ self._testwrite(
+ "1 + 1\n"
+ "done")
+
+ def finished(ign):
+ self._assertBuffer(
+ [">>> 1 + 1",
+ "2",
+ ">>> done"])
+
+ return done.addCallback(finished)
+
+ def testTripleQuoteLineContinuation(self):
+ done = self.recvlineClient.expect("done")
+
+ self._testwrite(
+ "'''\n'''\n"
+ "done")
+
+ def finished(ign):
+ self._assertBuffer(
+ [">>> '''",
+ "... '''",
+ "'\\n'",
+ ">>> done"])
+
+ return done.addCallback(finished)
+
+ def testFunctionDefinition(self):
+ done = self.recvlineClient.expect("done")
+
+ self._testwrite(
+ "def foo(bar):\n"
+ "\tprint bar\n\n"
+ "foo(42)\n"
+ "done")
+
+ def finished(ign):
+ self._assertBuffer(
+ [">>> def foo(bar):",
+ "... print bar",
+ "... ",
+ ">>> foo(42)",
+ "42",
+ ">>> done"])
+
+ return done.addCallback(finished)
+
+ def testClassDefinition(self):
+ done = self.recvlineClient.expect("done")
+
+ self._testwrite(
+ "class Foo:\n"
+ "\tdef bar(self):\n"
+ "\t\tprint 'Hello, world!'\n\n"
+ "Foo().bar()\n"
+ "done")
+
+ def finished(ign):
+ self._assertBuffer(
+ [">>> class Foo:",
+ "... def bar(self):",
+ "... print 'Hello, world!'",
+ "... ",
+ ">>> Foo().bar()",
+ "Hello, world!",
+ ">>> done"])
+
+ return done.addCallback(finished)
+
+ def testException(self):
+ done = self.recvlineClient.expect("done")
+
+ self._testwrite(
+ "raise Exception('foo bar baz')\n"
+ "done")
+
+ def finished(ign):
+ self._assertBuffer(
+ [">>> raise Exception('foo bar baz')",
+ "Traceback (most recent call last):",
+ ' File "<console>", line 1, in ' + defaultFunctionName,
+ "Exception: foo bar baz",
+ ">>> done"])
+
+ return done.addCallback(finished)
+
+ def testControlC(self):
+ done = self.recvlineClient.expect("done")
+
+ self._testwrite(
+ "cancelled line" + manhole.CTRL_C +
+ "done")
+
+ def finished(ign):
+ self._assertBuffer(
+ [">>> cancelled line",
+ "KeyboardInterrupt",
+ ">>> done"])
+
+ return done.addCallback(finished)
+
+
+ def test_interruptDuringContinuation(self):
+ """
+ Sending ^C to Manhole while in a state where more input is required to
+ complete a statement should discard the entire ongoing statement and
+ reset the input prompt to the non-continuation prompt.
+ """
+ continuing = self.recvlineClient.expect("things")
+
+ self._testwrite("(\nthings")
+
+ def gotContinuation(ignored):
+ self._assertBuffer(
+ [">>> (",
+ "... things"])
+ interrupted = self.recvlineClient.expect(">>> ")
+ self._testwrite(manhole.CTRL_C)
+ return interrupted
+ continuing.addCallback(gotContinuation)
+
+ def gotInterruption(ignored):
+ self._assertBuffer(
+ [">>> (",
+ "... things",
+ "KeyboardInterrupt",
+ ">>> "])
+ continuing.addCallback(gotInterruption)
+ return continuing
+
+
+ def testControlBackslash(self):
+ self._testwrite("cancelled line")
+ partialLine = self.recvlineClient.expect("cancelled line")
+
+ def gotPartialLine(ign):
+ self._assertBuffer(
+ [">>> cancelled line"])
+ self._testwrite(manhole.CTRL_BACKSLASH)
+
+ d = self.recvlineClient.onDisconnection
+ return self.assertFailure(d, error.ConnectionDone)
+
+ def gotClearedLine(ign):
+ self._assertBuffer(
+ [""])
+
+ return partialLine.addCallback(gotPartialLine).addCallback(gotClearedLine)
+
+ def testControlD(self):
+ self._testwrite("1 + 1")
+ helloWorld = self.wfd(self.recvlineClient.expect(r"\+ 1"))
+ yield helloWorld
+ helloWorld.getResult()
+ self._assertBuffer([">>> 1 + 1"])
+
+ self._testwrite(manhole.CTRL_D + " + 1")
+ cleared = self.wfd(self.recvlineClient.expect(r"\+ 1"))
+ yield cleared
+ cleared.getResult()
+ self._assertBuffer([">>> 1 + 1 + 1"])
+
+ self._testwrite("\n")
+ printed = self.wfd(self.recvlineClient.expect("3\n>>> "))
+ yield printed
+ printed.getResult()
+
+ self._testwrite(manhole.CTRL_D)
+ d = self.recvlineClient.onDisconnection
+ disconnected = self.wfd(self.assertFailure(d, error.ConnectionDone))
+ yield disconnected
+ disconnected.getResult()
+ testControlD = defer.deferredGenerator(testControlD)
+
+
+ def testControlL(self):
+ """
+ CTRL+L is generally used as a redraw-screen command in terminal
+ applications. Manhole doesn't currently respect this usage of it,
+ but it should at least do something reasonable in response to this
+ event (rather than, say, eating your face).
+ """
+ # Start off with a newline so that when we clear the display we can
+ # tell by looking for the missing first empty prompt line.
+ self._testwrite("\n1 + 1")
+ helloWorld = self.wfd(self.recvlineClient.expect(r"\+ 1"))
+ yield helloWorld
+ helloWorld.getResult()
+ self._assertBuffer([">>> ", ">>> 1 + 1"])
+
+ self._testwrite(manhole.CTRL_L + " + 1")
+ redrew = self.wfd(self.recvlineClient.expect(r"1 \+ 1 \+ 1"))
+ yield redrew
+ redrew.getResult()
+ self._assertBuffer([">>> 1 + 1 + 1"])
+ testControlL = defer.deferredGenerator(testControlL)
+
+
+ def testDeferred(self):
+ self._testwrite(
+ "from twisted.internet import defer, reactor\n"
+ "d = defer.Deferred()\n"
+ "d\n")
+
+ deferred = self.wfd(self.recvlineClient.expect("<Deferred #0>"))
+ yield deferred
+ deferred.getResult()
+
+ self._testwrite(
+ "c = reactor.callLater(0.1, d.callback, 'Hi!')\n")
+ delayed = self.wfd(self.recvlineClient.expect(">>> "))
+ yield delayed
+ delayed.getResult()
+
+ called = self.wfd(self.recvlineClient.expect("Deferred #0 called back: 'Hi!'\n>>> "))
+ yield called
+ called.getResult()
+ self._assertBuffer(
+ [">>> from twisted.internet import defer, reactor",
+ ">>> d = defer.Deferred()",
+ ">>> d",
+ "<Deferred #0>",
+ ">>> c = reactor.callLater(0.1, d.callback, 'Hi!')",
+ "Deferred #0 called back: 'Hi!'",
+ ">>> "])
+
+ testDeferred = defer.deferredGenerator(testDeferred)
+
+class ManholeLoopbackTelnet(_TelnetMixin, unittest.TestCase, ManholeLoopbackMixin):
+ pass
+
+class ManholeLoopbackSSH(_SSHMixin, unittest.TestCase, ManholeLoopbackMixin):
+ if ssh is None:
+ skip = "Crypto requirements missing, can't run manhole tests over ssh"
+
+class ManholeLoopbackStdio(_StdioMixin, unittest.TestCase, ManholeLoopbackMixin):
+ if stdio is None:
+ skip = "Terminal requirements missing, can't run manhole tests over stdio"
+ else:
+ serverProtocol = stdio.ConsoleManhole
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_mixin.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_mixin.py
new file mode 100644
index 0000000000..b35a89f6e8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_mixin.py
@@ -0,0 +1,47 @@
+# -*- twisted.conch.test.test_mixin -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import time
+
+from twisted.internet import reactor, protocol
+
+from twisted.trial import unittest
+from twisted.test.proto_helpers import StringTransport
+
+from twisted.conch import mixin
+
+
+class TestBufferingProto(mixin.BufferingMixin):
+ scheduled = False
+ rescheduled = 0
+ def schedule(self):
+ self.scheduled = True
+ return object()
+
+ def reschedule(self, token):
+ self.rescheduled += 1
+
+
+
+class BufferingTest(unittest.TestCase):
+ def testBuffering(self):
+ p = TestBufferingProto()
+ t = p.transport = StringTransport()
+
+ self.failIf(p.scheduled)
+
+ L = ['foo', 'bar', 'baz', 'quux']
+
+ p.write('foo')
+ self.failUnless(p.scheduled)
+ self.failIf(p.rescheduled)
+
+ for s in L:
+ n = p.rescheduled
+ p.write(s)
+ self.assertEquals(p.rescheduled, n + 1)
+ self.assertEquals(t.value(), '')
+
+ p.flush()
+ self.assertEquals(t.value(), 'foo' + ''.join(L))
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_openssh_compat.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_openssh_compat.py
new file mode 100644
index 0000000000..506bb1d615
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_openssh_compat.py
@@ -0,0 +1,102 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.openssh_compat}.
+"""
+
+import os
+
+from twisted.trial.unittest import TestCase
+from twisted.python.filepath import FilePath
+from twisted.python.compat import set
+
+try:
+ import Crypto.Cipher.DES3
+ import pyasn1
+except ImportError:
+ OpenSSHFactory = None
+else:
+ from twisted.conch.openssh_compat.factory import OpenSSHFactory
+
+from twisted.conch.test import keydata
+from twisted.test.test_process import MockOS
+
+
+class OpenSSHFactoryTests(TestCase):
+ """
+ Tests for L{OpenSSHFactory}.
+ """
+ if getattr(os, "geteuid", None) is None:
+ skip = "geteuid/seteuid not available"
+ elif OpenSSHFactory is None:
+ skip = "Cannot run without PyCrypto or PyASN1"
+
+ def setUp(self):
+ self.factory = OpenSSHFactory()
+ self.keysDir = FilePath(self.mktemp())
+ self.keysDir.makedirs()
+ self.factory.dataRoot = self.keysDir.path
+
+ self.keysDir.child("ssh_host_foo").setContent("foo")
+ self.keysDir.child("bar_key").setContent("foo")
+ self.keysDir.child("ssh_host_one_key").setContent(
+ keydata.privateRSA_openssh)
+ self.keysDir.child("ssh_host_two_key").setContent(
+ keydata.privateDSA_openssh)
+ self.keysDir.child("ssh_host_three_key").setContent(
+ "not a key content")
+
+ self.keysDir.child("ssh_host_one_key.pub").setContent(
+ keydata.publicRSA_openssh)
+
+ self.mockos = MockOS()
+ self.patch(os, "seteuid", self.mockos.seteuid)
+ self.patch(os, "setegid", self.mockos.setegid)
+
+
+ def test_getPublicKeys(self):
+ """
+ L{OpenSSHFactory.getPublicKeys} should return the available public keys
+ in the data directory
+ """
+ keys = self.factory.getPublicKeys()
+ self.assertEquals(len(keys), 1)
+ keyTypes = keys.keys()
+ self.assertEqual(keyTypes, ['ssh-rsa'])
+
+
+ def test_getPrivateKeys(self):
+ """
+ L{OpenSSHFactory.getPrivateKeys} should return the available private
+ keys in the data directory.
+ """
+ keys = self.factory.getPrivateKeys()
+ self.assertEquals(len(keys), 2)
+ keyTypes = keys.keys()
+ self.assertEqual(set(keyTypes), set(['ssh-rsa', 'ssh-dss']))
+ self.assertEquals(self.mockos.seteuidCalls, [])
+ self.assertEquals(self.mockos.setegidCalls, [])
+
+
+ def test_getPrivateKeysAsRoot(self):
+ """
+ L{OpenSSHFactory.getPrivateKeys} should switch to root if the keys
+ aren't readable by the current user.
+ """
+ keyFile = self.keysDir.child("ssh_host_two_key")
+ # Fake permission error by changing the mode
+ keyFile.chmod(0000)
+ self.addCleanup(keyFile.chmod, 0777)
+ # And restore the right mode when seteuid is called
+ savedSeteuid = os.seteuid
+ def seteuid(euid):
+ keyFile.chmod(0777)
+ return savedSeteuid(euid)
+ self.patch(os, "seteuid", seteuid)
+ keys = self.factory.getPrivateKeys()
+ self.assertEquals(len(keys), 2)
+ keyTypes = keys.keys()
+ self.assertEqual(set(keyTypes), set(['ssh-rsa', 'ssh-dss']))
+ self.assertEquals(self.mockos.seteuidCalls, [0, os.geteuid()])
+ self.assertEquals(self.mockos.setegidCalls, [0, os.getegid()])
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_recvline.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_recvline.py
new file mode 100644
index 0000000000..164a7b60e6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_recvline.py
@@ -0,0 +1,649 @@
+# -*- test-case-name: twisted.conch.test.test_recvline -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.recvline} and fixtures for testing related
+functionality.
+"""
+
+import sys, os
+
+from twisted.conch.insults import insults
+from twisted.conch import recvline
+
+from twisted.python import reflect, components
+from twisted.internet import defer, error
+from twisted.trial import unittest
+from twisted.cred import portal
+from twisted.test.proto_helpers import StringTransport
+
+class Arrows(unittest.TestCase):
+ def setUp(self):
+ self.underlyingTransport = StringTransport()
+ self.pt = insults.ServerProtocol()
+ self.p = recvline.HistoricRecvLine()
+ self.pt.protocolFactory = lambda: self.p
+ self.pt.factory = self
+ self.pt.makeConnection(self.underlyingTransport)
+ # self.p.makeConnection(self.pt)
+
+ def testPrintableCharacters(self):
+ self.p.keystrokeReceived('x', None)
+ self.p.keystrokeReceived('y', None)
+ self.p.keystrokeReceived('z', None)
+
+ self.assertEquals(self.p.currentLineBuffer(), ('xyz', ''))
+
+ def testHorizontalArrows(self):
+ kR = lambda ch: self.p.keystrokeReceived(ch, None)
+ for ch in 'xyz':
+ kR(ch)
+
+ self.assertEquals(self.p.currentLineBuffer(), ('xyz', ''))
+
+ kR(self.pt.RIGHT_ARROW)
+ self.assertEquals(self.p.currentLineBuffer(), ('xyz', ''))
+
+ kR(self.pt.LEFT_ARROW)
+ self.assertEquals(self.p.currentLineBuffer(), ('xy', 'z'))
+
+ kR(self.pt.LEFT_ARROW)
+ self.assertEquals(self.p.currentLineBuffer(), ('x', 'yz'))
+
+ kR(self.pt.LEFT_ARROW)
+ self.assertEquals(self.p.currentLineBuffer(), ('', 'xyz'))
+
+ kR(self.pt.LEFT_ARROW)
+ self.assertEquals(self.p.currentLineBuffer(), ('', 'xyz'))
+
+ kR(self.pt.RIGHT_ARROW)
+ self.assertEquals(self.p.currentLineBuffer(), ('x', 'yz'))
+
+ kR(self.pt.RIGHT_ARROW)
+ self.assertEquals(self.p.currentLineBuffer(), ('xy', 'z'))
+
+ kR(self.pt.RIGHT_ARROW)
+ self.assertEquals(self.p.currentLineBuffer(), ('xyz', ''))
+
+ kR(self.pt.RIGHT_ARROW)
+ self.assertEquals(self.p.currentLineBuffer(), ('xyz', ''))
+
+ def testNewline(self):
+ kR = lambda ch: self.p.keystrokeReceived(ch, None)
+
+ for ch in 'xyz\nabc\n123\n':
+ kR(ch)
+
+ self.assertEquals(self.p.currentHistoryBuffer(),
+ (('xyz', 'abc', '123'), ()))
+
+ kR('c')
+ kR('b')
+ kR('a')
+ self.assertEquals(self.p.currentHistoryBuffer(),
+ (('xyz', 'abc', '123'), ()))
+
+ kR('\n')
+ self.assertEquals(self.p.currentHistoryBuffer(),
+ (('xyz', 'abc', '123', 'cba'), ()))
+
+ def testVerticalArrows(self):
+ kR = lambda ch: self.p.keystrokeReceived(ch, None)
+
+ for ch in 'xyz\nabc\n123\n':
+ kR(ch)
+
+ self.assertEquals(self.p.currentHistoryBuffer(),
+ (('xyz', 'abc', '123'), ()))
+ self.assertEquals(self.p.currentLineBuffer(), ('', ''))
+
+ kR(self.pt.UP_ARROW)
+ self.assertEquals(self.p.currentHistoryBuffer(),
+ (('xyz', 'abc'), ('123',)))
+ self.assertEquals(self.p.currentLineBuffer(), ('123', ''))
+
+ kR(self.pt.UP_ARROW)
+ self.assertEquals(self.p.currentHistoryBuffer(),
+ (('xyz',), ('abc', '123')))
+ self.assertEquals(self.p.currentLineBuffer(), ('abc', ''))
+
+ kR(self.pt.UP_ARROW)
+ self.assertEquals(self.p.currentHistoryBuffer(),
+ ((), ('xyz', 'abc', '123')))
+ self.assertEquals(self.p.currentLineBuffer(), ('xyz', ''))
+
+ kR(self.pt.UP_ARROW)
+ self.assertEquals(self.p.currentHistoryBuffer(),
+ ((), ('xyz', 'abc', '123')))
+ self.assertEquals(self.p.currentLineBuffer(), ('xyz', ''))
+
+ for i in range(4):
+ kR(self.pt.DOWN_ARROW)
+ self.assertEquals(self.p.currentHistoryBuffer(),
+ (('xyz', 'abc', '123'), ()))
+
+ def testHome(self):
+ kR = lambda ch: self.p.keystrokeReceived(ch, None)
+
+ for ch in 'hello, world':
+ kR(ch)
+ self.assertEquals(self.p.currentLineBuffer(), ('hello, world', ''))
+
+ kR(self.pt.HOME)
+ self.assertEquals(self.p.currentLineBuffer(), ('', 'hello, world'))
+
+ def testEnd(self):
+ kR = lambda ch: self.p.keystrokeReceived(ch, None)
+
+ for ch in 'hello, world':
+ kR(ch)
+ self.assertEquals(self.p.currentLineBuffer(), ('hello, world', ''))
+
+ kR(self.pt.HOME)
+ kR(self.pt.END)
+ self.assertEquals(self.p.currentLineBuffer(), ('hello, world', ''))
+
+ def testBackspace(self):
+ kR = lambda ch: self.p.keystrokeReceived(ch, None)
+
+ for ch in 'xyz':
+ kR(ch)
+ self.assertEquals(self.p.currentLineBuffer(), ('xyz', ''))
+
+ kR(self.pt.BACKSPACE)
+ self.assertEquals(self.p.currentLineBuffer(), ('xy', ''))
+
+ kR(self.pt.LEFT_ARROW)
+ kR(self.pt.BACKSPACE)
+ self.assertEquals(self.p.currentLineBuffer(), ('', 'y'))
+
+ kR(self.pt.BACKSPACE)
+ self.assertEquals(self.p.currentLineBuffer(), ('', 'y'))
+
+ def testDelete(self):
+ kR = lambda ch: self.p.keystrokeReceived(ch, None)
+
+ for ch in 'xyz':
+ kR(ch)
+ self.assertEquals(self.p.currentLineBuffer(), ('xyz', ''))
+
+ kR(self.pt.DELETE)
+ self.assertEquals(self.p.currentLineBuffer(), ('xyz', ''))
+
+ kR(self.pt.LEFT_ARROW)
+ kR(self.pt.DELETE)
+ self.assertEquals(self.p.currentLineBuffer(), ('xy', ''))
+
+ kR(self.pt.LEFT_ARROW)
+ kR(self.pt.DELETE)
+ self.assertEquals(self.p.currentLineBuffer(), ('x', ''))
+
+ kR(self.pt.LEFT_ARROW)
+ kR(self.pt.DELETE)
+ self.assertEquals(self.p.currentLineBuffer(), ('', ''))
+
+ kR(self.pt.DELETE)
+ self.assertEquals(self.p.currentLineBuffer(), ('', ''))
+
+ def testInsert(self):
+ kR = lambda ch: self.p.keystrokeReceived(ch, None)
+
+ for ch in 'xyz':
+ kR(ch)
+
+ # kR(self.pt.INSERT)
+
+ kR(self.pt.LEFT_ARROW)
+ kR('A')
+ self.assertEquals(self.p.currentLineBuffer(), ('xyA', 'z'))
+
+ kR(self.pt.LEFT_ARROW)
+ kR('B')
+ self.assertEquals(self.p.currentLineBuffer(), ('xyB', 'Az'))
+
+ def testTypeover(self):
+ kR = lambda ch: self.p.keystrokeReceived(ch, None)
+
+ for ch in 'xyz':
+ kR(ch)
+
+ kR(self.pt.INSERT)
+
+ kR(self.pt.LEFT_ARROW)
+ kR('A')
+ self.assertEquals(self.p.currentLineBuffer(), ('xyA', ''))
+
+ kR(self.pt.LEFT_ARROW)
+ kR('B')
+ self.assertEquals(self.p.currentLineBuffer(), ('xyB', ''))
+
+
+from twisted.conch import telnet
+from twisted.conch.insults import helper
+from twisted.protocols import loopback
+
+class EchoServer(recvline.HistoricRecvLine):
+ def lineReceived(self, line):
+ self.terminal.write(line + '\n' + self.ps[self.pn])
+
+# An insults API for this would be nice.
+left = "\x1b[D"
+right = "\x1b[C"
+up = "\x1b[A"
+down = "\x1b[B"
+insert = "\x1b[2~"
+home = "\x1b[1~"
+delete = "\x1b[3~"
+end = "\x1b[4~"
+backspace = "\x7f"
+
+from twisted.cred import checkers
+
+try:
+ from twisted.conch.ssh import userauth, transport, channel, connection, session
+ from twisted.conch.manhole_ssh import TerminalUser, TerminalSession, TerminalRealm, TerminalSessionTransport, ConchFactory
+except ImportError:
+ ssh = False
+else:
+ ssh = True
+ class SessionChannel(channel.SSHChannel):
+ name = 'session'
+
+ def __init__(self, protocolFactory, protocolArgs, protocolKwArgs, width, height, *a, **kw):
+ channel.SSHChannel.__init__(self, *a, **kw)
+
+ self.protocolFactory = protocolFactory
+ self.protocolArgs = protocolArgs
+ self.protocolKwArgs = protocolKwArgs
+
+ self.width = width
+ self.height = height
+
+ def channelOpen(self, data):
+ term = session.packRequest_pty_req("vt102", (self.height, self.width, 0, 0), '')
+ self.conn.sendRequest(self, 'pty-req', term)
+ self.conn.sendRequest(self, 'shell', '')
+
+ self._protocolInstance = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
+ self._protocolInstance.factory = self
+ self._protocolInstance.makeConnection(self)
+
+ def closed(self):
+ self._protocolInstance.connectionLost(error.ConnectionDone())
+
+ def dataReceived(self, data):
+ self._protocolInstance.dataReceived(data)
+
+ class TestConnection(connection.SSHConnection):
+ def __init__(self, protocolFactory, protocolArgs, protocolKwArgs, width, height, *a, **kw):
+ connection.SSHConnection.__init__(self, *a, **kw)
+
+ self.protocolFactory = protocolFactory
+ self.protocolArgs = protocolArgs
+ self.protocolKwArgs = protocolKwArgs
+
+ self.width = width
+ self.height = height
+
+ def serviceStarted(self):
+ self.__channel = SessionChannel(self.protocolFactory, self.protocolArgs, self.protocolKwArgs, self.width, self.height)
+ self.openChannel(self.__channel)
+
+ def write(self, bytes):
+ return self.__channel.write(bytes)
+
+ class TestAuth(userauth.SSHUserAuthClient):
+ def __init__(self, username, password, *a, **kw):
+ userauth.SSHUserAuthClient.__init__(self, username, *a, **kw)
+ self.password = password
+
+ def getPassword(self):
+ return defer.succeed(self.password)
+
+ class TestTransport(transport.SSHClientTransport):
+ def __init__(self, protocolFactory, protocolArgs, protocolKwArgs, username, password, width, height, *a, **kw):
+ # transport.SSHClientTransport.__init__(self, *a, **kw)
+ self.protocolFactory = protocolFactory
+ self.protocolArgs = protocolArgs
+ self.protocolKwArgs = protocolKwArgs
+ self.username = username
+ self.password = password
+ self.width = width
+ self.height = height
+
+ def verifyHostKey(self, hostKey, fingerprint):
+ return defer.succeed(True)
+
+ def connectionSecure(self):
+ self.__connection = TestConnection(self.protocolFactory, self.protocolArgs, self.protocolKwArgs, self.width, self.height)
+ self.requestService(
+ TestAuth(self.username, self.password, self.__connection))
+
+ def write(self, bytes):
+ return self.__connection.write(bytes)
+
+ class TestSessionTransport(TerminalSessionTransport):
+ def protocolFactory(self):
+ return self.avatar.conn.transport.factory.serverProtocol()
+
+ class TestSession(TerminalSession):
+ transportFactory = TestSessionTransport
+
+ class TestUser(TerminalUser):
+ pass
+
+ components.registerAdapter(TestSession, TestUser, session.ISession)
+
+
+class LoopbackRelay(loopback.LoopbackRelay):
+ clearCall = None
+
+ def logPrefix(self):
+ return "LoopbackRelay(%r)" % (self.target.__class__.__name__,)
+
+ def write(self, bytes):
+ loopback.LoopbackRelay.write(self, bytes)
+ if self.clearCall is not None:
+ self.clearCall.cancel()
+
+ from twisted.internet import reactor
+ self.clearCall = reactor.callLater(0, self._clearBuffer)
+
+ def _clearBuffer(self):
+ self.clearCall = None
+ loopback.LoopbackRelay.clearBuffer(self)
+
+
+class NotifyingExpectableBuffer(helper.ExpectableBuffer):
+ def __init__(self):
+ self.onConnection = defer.Deferred()
+ self.onDisconnection = defer.Deferred()
+
+ def connectionMade(self):
+ helper.ExpectableBuffer.connectionMade(self)
+ self.onConnection.callback(self)
+
+ def connectionLost(self, reason):
+ self.onDisconnection.errback(reason)
+
+
+class _BaseMixin:
+ WIDTH = 80
+ HEIGHT = 24
+
+ def _assertBuffer(self, lines):
+ receivedLines = str(self.recvlineClient).splitlines()
+ expectedLines = lines + ([''] * (self.HEIGHT - len(lines) - 1))
+ self.assertEquals(len(receivedLines), len(expectedLines))
+ for i in range(len(receivedLines)):
+ self.assertEquals(
+ receivedLines[i], expectedLines[i],
+ str(receivedLines[max(0, i-1):i+1]) +
+ " != " +
+ str(expectedLines[max(0, i-1):i+1]))
+
+ def _trivialTest(self, input, output):
+ done = self.recvlineClient.expect("done")
+
+ self._testwrite(input)
+
+ def finished(ign):
+ self._assertBuffer(output)
+
+ return done.addCallback(finished)
+
+
+class _SSHMixin(_BaseMixin):
+ def setUp(self):
+ if not ssh:
+ raise unittest.SkipTest("Crypto requirements missing, can't run historic recvline tests over ssh")
+
+ u, p = 'testuser', 'testpass'
+ rlm = TerminalRealm()
+ rlm.userFactory = TestUser
+ rlm.chainedProtocolFactory = lambda: insultsServer
+
+ ptl = portal.Portal(
+ rlm,
+ [checkers.InMemoryUsernamePasswordDatabaseDontUse(**{u: p})])
+ sshFactory = ConchFactory(ptl)
+ sshFactory.serverProtocol = self.serverProtocol
+ sshFactory.startFactory()
+
+ recvlineServer = self.serverProtocol()
+ insultsServer = insults.ServerProtocol(lambda: recvlineServer)
+ sshServer = sshFactory.buildProtocol(None)
+ clientTransport = LoopbackRelay(sshServer)
+
+ recvlineClient = NotifyingExpectableBuffer()
+ insultsClient = insults.ClientProtocol(lambda: recvlineClient)
+ sshClient = TestTransport(lambda: insultsClient, (), {}, u, p, self.WIDTH, self.HEIGHT)
+ serverTransport = LoopbackRelay(sshClient)
+
+ sshClient.makeConnection(clientTransport)
+ sshServer.makeConnection(serverTransport)
+
+ self.recvlineClient = recvlineClient
+ self.sshClient = sshClient
+ self.sshServer = sshServer
+ self.clientTransport = clientTransport
+ self.serverTransport = serverTransport
+
+ return recvlineClient.onConnection
+
+ def _testwrite(self, bytes):
+ self.sshClient.write(bytes)
+
+from twisted.conch.test import test_telnet
+
+class TestInsultsClientProtocol(insults.ClientProtocol,
+ test_telnet.TestProtocol):
+ pass
+
+
+class TestInsultsServerProtocol(insults.ServerProtocol,
+ test_telnet.TestProtocol):
+ pass
+
+class _TelnetMixin(_BaseMixin):
+ def setUp(self):
+ recvlineServer = self.serverProtocol()
+ insultsServer = TestInsultsServerProtocol(lambda: recvlineServer)
+ telnetServer = telnet.TelnetTransport(lambda: insultsServer)
+ clientTransport = LoopbackRelay(telnetServer)
+
+ recvlineClient = NotifyingExpectableBuffer()
+ insultsClient = TestInsultsClientProtocol(lambda: recvlineClient)
+ telnetClient = telnet.TelnetTransport(lambda: insultsClient)
+ serverTransport = LoopbackRelay(telnetClient)
+
+ telnetClient.makeConnection(clientTransport)
+ telnetServer.makeConnection(serverTransport)
+
+ serverTransport.clearBuffer()
+ clientTransport.clearBuffer()
+
+ self.recvlineClient = recvlineClient
+ self.telnetClient = telnetClient
+ self.clientTransport = clientTransport
+ self.serverTransport = serverTransport
+
+ return recvlineClient.onConnection
+
+ def _testwrite(self, bytes):
+ self.telnetClient.write(bytes)
+
+try:
+ from twisted.conch import stdio
+except ImportError:
+ stdio = None
+
+class _StdioMixin(_BaseMixin):
+ def setUp(self):
+ # A memory-only terminal emulator, into which the server will
+ # write things and make other state changes. What ends up
+ # here is basically what a user would have seen on their
+ # screen.
+ testTerminal = NotifyingExpectableBuffer()
+
+ # An insults client protocol which will translate bytes
+ # received from the child process into keystroke commands for
+ # an ITerminalProtocol.
+ insultsClient = insults.ClientProtocol(lambda: testTerminal)
+
+ # A process protocol which will translate stdout and stderr
+ # received from the child process to dataReceived calls and
+ # error reporting on an insults client protocol.
+ processClient = stdio.TerminalProcessProtocol(insultsClient)
+
+ # Run twisted/conch/stdio.py with the name of a class
+ # implementing ITerminalProtocol. This class will be used to
+ # handle bytes we send to the child process.
+ exe = sys.executable
+ module = stdio.__file__
+ if module.endswith('.pyc') or module.endswith('.pyo'):
+ module = module[:-1]
+ args = [exe, module, reflect.qual(self.serverProtocol)]
+ env = os.environ.copy()
+ env["PYTHONPATH"] = os.pathsep.join(sys.path)
+
+ from twisted.internet import reactor
+ clientTransport = reactor.spawnProcess(processClient, exe, args,
+ env=env, usePTY=True)
+
+ self.recvlineClient = self.testTerminal = testTerminal
+ self.processClient = processClient
+ self.clientTransport = clientTransport
+
+ # Wait for the process protocol and test terminal to become
+ # connected before proceeding. The former should always
+ # happen first, but it doesn't hurt to be safe.
+ return defer.gatherResults(filter(None, [
+ processClient.onConnection,
+ testTerminal.expect(">>> ")]))
+
+ def tearDown(self):
+ # Kill the child process. We're done with it.
+ try:
+ self.clientTransport.signalProcess("KILL")
+ except (error.ProcessExitedAlready, OSError):
+ pass
+ def trap(failure):
+ failure.trap(error.ProcessTerminated)
+ self.assertEquals(failure.value.exitCode, None)
+ self.assertEquals(failure.value.status, 9)
+ return self.testTerminal.onDisconnection.addErrback(trap)
+
+ def _testwrite(self, bytes):
+ self.clientTransport.write(bytes)
+
+class RecvlineLoopbackMixin:
+ serverProtocol = EchoServer
+
+ def testSimple(self):
+ return self._trivialTest(
+ "first line\ndone",
+ [">>> first line",
+ "first line",
+ ">>> done"])
+
+ def testLeftArrow(self):
+ return self._trivialTest(
+ insert + 'first line' + left * 4 + "xxxx\ndone",
+ [">>> first xxxx",
+ "first xxxx",
+ ">>> done"])
+
+ def testRightArrow(self):
+ return self._trivialTest(
+ insert + 'right line' + left * 4 + right * 2 + "xx\ndone",
+ [">>> right lixx",
+ "right lixx",
+ ">>> done"])
+
+ def testBackspace(self):
+ return self._trivialTest(
+ "second line" + backspace * 4 + "xxxx\ndone",
+ [">>> second xxxx",
+ "second xxxx",
+ ">>> done"])
+
+ def testDelete(self):
+ return self._trivialTest(
+ "delete xxxx" + left * 4 + delete * 4 + "line\ndone",
+ [">>> delete line",
+ "delete line",
+ ">>> done"])
+
+ def testInsert(self):
+ return self._trivialTest(
+ "third ine" + left * 3 + "l\ndone",
+ [">>> third line",
+ "third line",
+ ">>> done"])
+
+ def testTypeover(self):
+ return self._trivialTest(
+ "fourth xine" + left * 4 + insert + "l\ndone",
+ [">>> fourth line",
+ "fourth line",
+ ">>> done"])
+
+ def testHome(self):
+ return self._trivialTest(
+ insert + "blah line" + home + "home\ndone",
+ [">>> home line",
+ "home line",
+ ">>> done"])
+
+ def testEnd(self):
+ return self._trivialTest(
+ "end " + left * 4 + end + "line\ndone",
+ [">>> end line",
+ "end line",
+ ">>> done"])
+
+class RecvlineLoopbackTelnet(_TelnetMixin, unittest.TestCase, RecvlineLoopbackMixin):
+ pass
+
+class RecvlineLoopbackSSH(_SSHMixin, unittest.TestCase, RecvlineLoopbackMixin):
+ pass
+
+class RecvlineLoopbackStdio(_StdioMixin, unittest.TestCase, RecvlineLoopbackMixin):
+ if stdio is None:
+ skip = "Terminal requirements missing, can't run recvline tests over stdio"
+
+
+class HistoricRecvlineLoopbackMixin:
+ serverProtocol = EchoServer
+
+ def testUpArrow(self):
+ return self._trivialTest(
+ "first line\n" + up + "\ndone",
+ [">>> first line",
+ "first line",
+ ">>> first line",
+ "first line",
+ ">>> done"])
+
+ def testDownArrow(self):
+ return self._trivialTest(
+ "first line\nsecond line\n" + up * 2 + down + "\ndone",
+ [">>> first line",
+ "first line",
+ ">>> second line",
+ "second line",
+ ">>> second line",
+ "second line",
+ ">>> done"])
+
+class HistoricRecvlineLoopbackTelnet(_TelnetMixin, unittest.TestCase, HistoricRecvlineLoopbackMixin):
+ pass
+
+class HistoricRecvlineLoopbackSSH(_SSHMixin, unittest.TestCase, HistoricRecvlineLoopbackMixin):
+ pass
+
+class HistoricRecvlineLoopbackStdio(_StdioMixin, unittest.TestCase, HistoricRecvlineLoopbackMixin):
+ if stdio is None:
+ skip = "Terminal requirements missing, can't run historic recvline tests over stdio"
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_session.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_session.py
new file mode 100644
index 0000000000..f59fd725e3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_session.py
@@ -0,0 +1,1210 @@
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for the 'session' channel implementation in twisted.conch.ssh.session.
+
+See also RFC 4254.
+"""
+
+import os, signal, sys, struct
+
+from zope.interface import implements
+
+from twisted.internet.error import ProcessTerminated, ProcessDone
+from twisted.python.failure import Failure
+from twisted.conch.ssh import common, session, connection
+from twisted.internet import defer, protocol, error
+from twisted.python import components, failure
+from twisted.trial import unittest
+
+
+
+class SubsystemOnlyAvatar(object):
+ """
+ A stub class representing an avatar that is only useful for
+ getting a subsystem.
+ """
+
+
+ def lookupSubsystem(self, name, data):
+ """
+ If the other side requests the 'subsystem' subsystem, allow it by
+ returning a MockProtocol to implement it. Otherwise, return
+ None which is interpreted by SSHSession as a failure.
+ """
+ if name == 'subsystem':
+ return MockProtocol()
+
+
+
+class StubAvatar:
+ """
+ A stub class representing the avatar representing the authenticated user.
+ It implements the I{ISession} interface.
+ """
+
+
+ def lookupSubsystem(self, name, data):
+ """
+ If the user requests the TestSubsystem subsystem, connect them to a
+ MockProtocol. If they request neither, then None is returned which is
+ interpreted by SSHSession as a failure.
+ """
+ if name == 'TestSubsystem':
+ self.subsystem = MockProtocol()
+ self.subsystem.packetData = data
+ return self.subsystem
+
+
+
+class StubSessionForStubAvatar(object):
+ """
+ A stub ISession implementation for our StubAvatar. The instance
+ variables generally keep track of method invocations so that we can test
+ that the methods were called.
+
+ @ivar avatar: the L{StubAvatar} we are adapting.
+ @ivar ptyRequest: if present, the terminal, window size, and modes passed
+ to the getPty method.
+ @ivar windowChange: if present, the window size passed to the
+ windowChangned method.
+ @ivar shellProtocol: if present, the L{SSHSessionProcessProtocol} passed
+ to the openShell method.
+ @ivar shellTransport: if present, the L{EchoTransport} connected to
+ shellProtocol.
+ @ivar execProtocol: if present, the L{SSHSessionProcessProtocol} passed
+ to the execCommand method.
+ @ivar execTransport: if present, the L{EchoTransport} connected to
+ execProtocol.
+ @ivar execCommandLine: if present, the command line passed to the
+ execCommand method.
+ @ivar gotEOF: if present, an EOF message was received.
+ @ivar gotClosed: if present, a closed message was received.
+ """
+
+
+ implements(session.ISession)
+
+
+ def __init__(self, avatar):
+ """
+ Store the avatar we're adapting.
+ """
+ self.avatar = avatar
+ self.shellProtocol = None
+
+
+ def getPty(self, terminal, window, modes):
+ """
+ If the terminal is 'bad', fail. Otherwise, store the information in
+ the ptyRequest variable.
+ """
+ if terminal != 'bad':
+ self.ptyRequest = (terminal, window, modes)
+ else:
+ raise RuntimeError('not getting a pty')
+
+
+ def windowChanged(self, window):
+ """
+ If all the window sizes are 0, fail. Otherwise, store the size in the
+ windowChange variable.
+ """
+ if window == (0, 0, 0, 0):
+ raise RuntimeError('not changing the window size')
+ else:
+ self.windowChange = window
+
+
+ def openShell(self, pp):
+ """
+ If we have gotten a shell request before, fail. Otherwise, store the
+ process protocol in the shellProtocol variable, connect it to the
+ EchoTransport and store that as shellTransport.
+ """
+ if self.shellProtocol is not None:
+ raise RuntimeError('not getting a shell this time')
+ else:
+ self.shellProtocol = pp
+ self.shellTransport = EchoTransport(pp)
+
+
+ def execCommand(self, pp, command):
+ """
+ If the command is 'true', store the command, the process protocol, and
+ the transport we connect to the process protocol. Otherwise, just
+ store the command and raise an error.
+ """
+ self.execCommandLine = command
+ if command == 'success':
+ self.execProtocol = pp
+ elif command[:6] == 'repeat':
+ self.execProtocol = pp
+ self.execTransport = EchoTransport(pp)
+ pp.outReceived(command[7:])
+ else:
+ raise RuntimeError('not getting a command')
+
+
+ def eofReceived(self):
+ """
+ Note that EOF has been received.
+ """
+ self.gotEOF = True
+
+
+ def closed(self):
+ """
+ Note that close has been received.
+ """
+ self.gotClosed = True
+
+
+
+components.registerAdapter(StubSessionForStubAvatar, StubAvatar,
+ session.ISession)
+
+
+
+
+class MockProcessProtocol(protocol.ProcessProtocol):
+ """
+ A mock ProcessProtocol which echoes back data sent to it and
+ appends a tilde. The tilde is appended so the tests can verify that
+ we received and processed the data.
+
+ @ivar packetData: C{str} of data to be sent when the connection is made.
+ @ivar data: a C{str} of data received.
+ @ivar err: a C{str} of error data received.
+ @ivar inConnectionOpen: True if the input side is open.
+ @ivar outConnectionOpen: True if the output side is open.
+ @ivar errConnectionOpen: True if the error side is open.
+ @ivar ended: False if the protocol has not ended, a C{Failure} if the
+ process has ended.
+ """
+ packetData = ''
+
+
+ def connectionMade(self):
+ """
+ Set up variables.
+ """
+ self.data = ''
+ self.err = ''
+ self.inConnectionOpen = True
+ self.outConnectionOpen = True
+ self.errConnectionOpen = True
+ self.ended = False
+ if self.packetData:
+ self.outReceived(self.packetData)
+
+
+ def outReceived(self, data):
+ """
+ Data was received. Store it and echo it back with a tilde.
+ """
+ self.data += data
+ if self.transport is not None:
+ self.transport.write(data + '~')
+
+
+ def errReceived(self, data):
+ """
+ Error data was received. Store it and echo it back backwards.
+ """
+ self.err += data
+ self.transport.write(data[::-1])
+
+
+ def inConnectionLost(self):
+ """
+ Close the input side.
+ """
+ self.inConnectionOpen = False
+
+
+ def outConnectionLost(self):
+ """
+ Close the output side.
+ """
+ self.outConnectionOpen = False
+
+
+ def errConnectionLost(self):
+ """
+ Close the error side.
+ """
+ self.errConnectionOpen = False
+
+
+ def processEnded(self, reason):
+ """
+ End the process and store the reason.
+ """
+ self.ended = reason
+
+
+
+class EchoTransport:
+ """
+ A transport for a ProcessProtocol which echos data that is sent to it with
+ a Window newline (CR LF) appended to it. If a null byte is in the data,
+ disconnect. When we are asked to disconnect, disconnect the
+ C{ProcessProtocol} with a 0 exit code.
+
+ @ivar proto: the C{ProcessProtocol} connected to us.
+ @ivar data: a C{str} of data written to us.
+ """
+
+
+ def __init__(self, processProtocol):
+ """
+ Initialize our instance variables.
+
+ @param processProtocol: a C{ProcessProtocol} to connect to ourself.
+ """
+ self.proto = processProtocol
+ self.closed = False
+ self.data = ''
+ processProtocol.makeConnection(self)
+
+
+ def write(self, data):
+ """
+ We got some data. Give it back to our C{ProcessProtocol} with
+ a newline attached. Disconnect if there's a null byte.
+ """
+ self.data += data
+ self.proto.outReceived(data)
+ self.proto.outReceived('\r\n')
+ if '\x00' in data: # mimic 'exit' for the shell test
+ self.loseConnection()
+
+
+ def loseConnection(self):
+ """
+ If we're asked to disconnect (and we haven't already) shut down
+ the C{ProcessProtocol} with a 0 exit code.
+ """
+ if self.closed:
+ return
+ self.closed = 1
+ self.proto.inConnectionLost()
+ self.proto.outConnectionLost()
+ self.proto.errConnectionLost()
+ self.proto.processEnded(failure.Failure(
+ error.ProcessTerminated(0, None, None)))
+
+
+
+class MockProtocol(protocol.Protocol):
+ """
+ A sample Protocol which stores the data passed to it.
+
+ @ivar packetData: a C{str} of data to be sent when the connection is made.
+ @ivar data: a C{str} of the data passed to us.
+ @ivar open: True if the channel is open.
+ @ivar reason: if not None, the reason the protocol was closed.
+ """
+ packetData = ''
+
+
+ def connectionMade(self):
+ """
+ Set up the instance variables. If we have any packetData, send it
+ along.
+ """
+
+ self.data = ''
+ self.open = True
+ self.reason = None
+ if self.packetData:
+ self.dataReceived(self.packetData)
+
+
+ def dataReceived(self, data):
+ """
+ Store the received data and write it back with a tilde appended.
+ The tilde is appended so that the tests can verify that we processed
+ the data.
+ """
+ self.data += data
+ if self.transport is not None:
+ self.transport.write(data + '~')
+
+
+ def connectionLost(self, reason):
+ """
+ Close the protocol and store the reason.
+ """
+ self.open = False
+ self.reason = reason
+
+
+
+class StubConnection(object):
+ """
+ A stub for twisted.conch.ssh.connection.SSHConnection. Record the data
+ that channels send, and when they try to close the connection.
+
+ @ivar data: a C{dict} mapping C{SSHChannel}s to a C{list} of C{str} of data
+ they sent.
+ @ivar extData: a C{dict} mapping L{SSHChannel}s to a C{list} of C{tuple} of
+ (C{int}, C{str}) of extended data they sent.
+ @ivar requests: a C{dict} mapping L{SSHChannel}s to a C{list} of C{tuple}
+ of (C{str}, C{str}) of channel requests they made.
+ @ivar eofs: a C{dict} mapping L{SSHChannel}s to C{true} if they have sent
+ an EOF.
+ @ivar closes: a C{dict} mapping L{SSHChannel}s to C{true} if they have sent
+ a close.
+ """
+
+
+ def __init__(self):
+ """
+ Initialize our instance variables.
+ """
+ self.data = {}
+ self.extData = {}
+ self.requests = {}
+ self.eofs = {}
+ self.closes = {}
+
+
+ def logPrefix(self):
+ """
+ Return our logging prefix.
+ """
+ return "MockConnection"
+
+
+ def sendData(self, channel, data):
+ """
+ Record the sent data.
+ """
+ self.data.setdefault(channel, []).append(data)
+
+
+ def sendExtendedData(self, channel, type, data):
+ """
+ Record the sent extended data.
+ """
+ self.extData.setdefault(channel, []).append((type, data))
+
+
+ def sendRequest(self, channel, request, data, wantReply=False):
+ """
+ Record the sent channel request.
+ """
+ self.requests.setdefault(channel, []).append((request, data,
+ wantReply))
+ if wantReply:
+ return defer.succeed(None)
+
+
+ def sendEOF(self, channel):
+ """
+ Record the sent EOF.
+ """
+ self.eofs[channel] = True
+
+
+ def sendClose(self, channel):
+ """
+ Record the sent close.
+ """
+ self.closes[channel] = True
+
+
+
+
+
+
+class StubTransport:
+ """
+ A stub transport which records the data written.
+
+ @ivar buf: the data sent to the transport.
+ @type buf: C{str}
+
+ @ivar close: flags indicating if the transport has been closed.
+ @type close: C{bool}
+ """
+
+ buf = ''
+ close = False
+
+
+ def write(self, data):
+ """
+ Record data in the buffer.
+ """
+ self.buf += data
+
+
+ def loseConnection(self):
+ """
+ Note that the connection was closed.
+ """
+ self.close = True
+
+
+class StubTransportWithWriteErr(StubTransport):
+ """
+ A version of StubTransport which records the error data sent to it.
+
+ @ivar err: the extended data sent to the transport.
+ @type err: C{str}
+ """
+
+ err = ''
+
+
+ def writeErr(self, data):
+ """
+ Record the extended data in the buffer. This was an old interface
+ that allowed the Transports from ISession.openShell() or
+ ISession.execCommand() to receive extended data from the client.
+ """
+ self.err += data
+
+
+
+class StubClient(object):
+ """
+ A stub class representing the client to a SSHSession.
+
+ @ivar transport: A L{StubTransport} object which keeps track of the data
+ passed to it.
+ """
+
+
+ def __init__(self):
+ self.transport = StubTransportWithWriteErr()
+
+
+
+class SessionInterfaceTestCase(unittest.TestCase):
+ """
+ Tests for the SSHSession class interface. This interface is not ideal, but
+ it is tested in order to maintain backwards compatibility.
+ """
+
+
+ def setUp(self):
+ """
+ Make an SSHSession object to test. Give the channel some window
+ so that it's allowed to send packets. 500 and 100 are arbitrary
+ values.
+ """
+ self.session = session.SSHSession(remoteWindow=500,
+ remoteMaxPacket=100, conn=StubConnection(),
+ avatar=StubAvatar())
+
+
+ def assertSessionIsStubSession(self):
+ """
+ Asserts that self.session.session is an instance of
+ StubSessionForStubOldAvatar.
+ """
+ self.assertIsInstance(self.session.session,
+ StubSessionForStubAvatar)
+
+
+ def test_init(self):
+ """
+ SSHSession initializes its buffer (buf), client, and ISession adapter.
+ The avatar should not need to be adaptable to an ISession immediately.
+ """
+ s = session.SSHSession(avatar=object) # use object because it doesn't
+ # have an adapter
+ self.assertEquals(s.buf, '')
+ self.assertIdentical(s.client, None)
+ self.assertIdentical(s.session, None)
+
+
+ def test_client_dataReceived(self):
+ """
+ SSHSession.dataReceived() passes data along to a client. If the data
+ comes before there is a client, the data should be discarded.
+ """
+ self.session.dataReceived('1')
+ self.session.client = StubClient()
+ self.session.dataReceived('2')
+ self.assertEquals(self.session.client.transport.buf, '2')
+
+ def test_client_extReceived(self):
+ """
+ SSHSession.extReceived() passed data of type EXTENDED_DATA_STDERR along
+ to the client. If the data comes before there is a client, or if the
+ data is not of type EXTENDED_DATA_STDERR, it is discared.
+ """
+ self.session.extReceived(connection.EXTENDED_DATA_STDERR, '1')
+ self.session.extReceived(255, '2') # 255 is arbitrary
+ self.session.client = StubClient()
+ self.session.extReceived(connection.EXTENDED_DATA_STDERR, '3')
+ self.assertEquals(self.session.client.transport.err, '3')
+
+
+ def test_client_extReceivedWithoutWriteErr(self):
+ """
+ SSHSession.extReceived() should handle the case where the transport
+ on the client doesn't have a writeErr method.
+ """
+ client = self.session.client = StubClient()
+ client.transport = StubTransport() # doesn't have writeErr
+
+ # should not raise an error
+ self.session.extReceived(connection.EXTENDED_DATA_STDERR, 'ignored')
+
+
+
+ def test_client_closed(self):
+ """
+ SSHSession.closed() should tell the transport connected to the client
+ that the connection was lost.
+ """
+ self.session.client = StubClient()
+ self.session.closed()
+ self.assertTrue(self.session.client.transport.close)
+ self.session.client.transport.close = False
+
+
+ def test_badSubsystemDoesNotCreateClient(self):
+ """
+ When a subsystem request fails, SSHSession.client should not be set.
+ """
+ ret = self.session.requestReceived(
+ 'subsystem', common.NS('BadSubsystem'))
+ self.assertFalse(ret)
+ self.assertIdentical(self.session.client, None)
+
+
+ def test_lookupSubsystem(self):
+ """
+ When a client requests a subsystem, the SSHSession object should get
+ the subsystem by calling avatar.lookupSubsystem, and attach it as
+ the client.
+ """
+ ret = self.session.requestReceived(
+ 'subsystem', common.NS('TestSubsystem') + 'data')
+ self.assertTrue(ret)
+ self.assertIsInstance(self.session.client, protocol.ProcessProtocol)
+ self.assertIdentical(self.session.client.transport.proto,
+ self.session.avatar.subsystem)
+
+
+
+ def test_lookupSubsystemDoesNotNeedISession(self):
+ """
+ Previously, if one only wanted to implement a subsystem, an ISession
+ adapter wasn't needed because subsystems were looked up using the
+ lookupSubsystem method on the avatar.
+ """
+ s = session.SSHSession(avatar=SubsystemOnlyAvatar(),
+ conn=StubConnection())
+ ret = s.request_subsystem(
+ common.NS('subsystem') + 'data')
+ self.assertTrue(ret)
+ self.assertNotIdentical(s.client, None)
+ self.assertIdentical(s.conn.closes.get(s), None)
+ s.eofReceived()
+ self.assertTrue(s.conn.closes.get(s))
+ # these should not raise errors
+ s.loseConnection()
+ s.closed()
+
+
+ def test_lookupSubsystem_data(self):
+ """
+ After having looked up a subsystem, data should be passed along to the
+ client. Additionally, subsystems were passed the entire request packet
+ as data, instead of just the additional data.
+
+ We check for the additional tidle to verify that the data passed
+ through the client.
+ """
+ #self.session.dataReceived('1')
+ # subsystems didn't get extended data
+ #self.session.extReceived(connection.EXTENDED_DATA_STDERR, '2')
+
+ self.session.requestReceived('subsystem',
+ common.NS('TestSubsystem') + 'data')
+
+ self.assertEquals(self.session.conn.data[self.session],
+ ['\x00\x00\x00\x0dTestSubsystemdata~'])
+ self.session.dataReceived('more data')
+ self.assertEquals(self.session.conn.data[self.session][-1],
+ 'more data~')
+
+
+ def test_lookupSubsystem_closeReceived(self):
+ """
+ SSHSession.closeReceived() should sent a close message to the remote
+ side.
+ """
+ self.session.requestReceived('subsystem',
+ common.NS('TestSubsystem') + 'data')
+
+ self.session.closeReceived()
+ self.assertTrue(self.session.conn.closes[self.session])
+
+
+ def assertRequestRaisedRuntimeError(self):
+ """
+ Assert that the request we just made raised a RuntimeError (and only a
+ RuntimeError).
+ """
+ errors = self.flushLoggedErrors(RuntimeError)
+ self.assertEquals(len(errors), 1, "Multiple RuntimeErrors raised: %s" %
+ '\n'.join([repr(error) for error in errors]))
+ errors[0].trap(RuntimeError)
+
+
+ def test_requestShell(self):
+ """
+ When a client requests a shell, the SSHSession object should get
+ the shell by getting an ISession adapter for the avatar, then
+ calling openShell() with a ProcessProtocol to attach.
+ """
+ # gets a shell the first time
+ ret = self.session.requestReceived('shell', '')
+ self.assertTrue(ret)
+ self.assertSessionIsStubSession()
+ self.assertIsInstance(self.session.client,
+ session.SSHSessionProcessProtocol)
+ self.assertIdentical(self.session.session.shellProtocol,
+ self.session.client)
+ # doesn't get a shell the second time
+ self.assertFalse(self.session.requestReceived('shell', ''))
+ self.assertRequestRaisedRuntimeError()
+
+
+ def test_requestShellWithData(self):
+ """
+ When a client executes a shell, it should be able to give pass data
+ back and forth between the local and the remote side.
+ """
+ ret = self.session.requestReceived('shell', '')
+ self.assertTrue(ret)
+ self.assertSessionIsStubSession()
+ self.session.dataReceived('some data\x00')
+ self.assertEquals(self.session.session.shellTransport.data,
+ 'some data\x00')
+ self.assertEquals(self.session.conn.data[self.session],
+ ['some data\x00', '\r\n'])
+ self.assertTrue(self.session.session.shellTransport.closed)
+ self.assertEquals(self.session.conn.requests[self.session],
+ [('exit-status', '\x00\x00\x00\x00', False)])
+
+
+ def test_requestExec(self):
+ """
+ When a client requests a command, the SSHSession object should get
+ the command by getting an ISession adapter for the avatar, then
+ calling execCommand with a ProcessProtocol to attach and the
+ command line.
+ """
+ ret = self.session.requestReceived('exec',
+ common.NS('failure'))
+ self.assertFalse(ret)
+ self.assertRequestRaisedRuntimeError()
+ self.assertIdentical(self.session.client, None)
+
+ self.assertTrue(self.session.requestReceived('exec',
+ common.NS('success')))
+ self.assertSessionIsStubSession()
+ self.assertIsInstance(self.session.client,
+ session.SSHSessionProcessProtocol)
+ self.assertIdentical(self.session.session.execProtocol,
+ self.session.client)
+ self.assertEquals(self.session.session.execCommandLine,
+ 'success')
+
+
+ def test_requestExecWithData(self):
+ """
+ When a client executes a command, it should be able to give pass data
+ back and forth.
+ """
+ ret = self.session.requestReceived('exec',
+ common.NS('repeat hello'))
+ self.assertTrue(ret)
+ self.assertSessionIsStubSession()
+ self.session.dataReceived('some data')
+ self.assertEquals(self.session.session.execTransport.data, 'some data')
+ self.assertEquals(self.session.conn.data[self.session],
+ ['hello', 'some data', '\r\n'])
+ self.session.eofReceived()
+ self.session.closeReceived()
+ self.session.closed()
+ self.assertTrue(self.session.session.execTransport.closed)
+ self.assertEquals(self.session.conn.requests[self.session],
+ [('exit-status', '\x00\x00\x00\x00', False)])
+
+
+ def test_requestPty(self):
+ """
+ When a client requests a PTY, the SSHSession object should make
+ the request by getting an ISession adapter for the avatar, then
+ calling getPty with the terminal type, the window size, and any modes
+ the client gave us.
+ """
+ # 'bad' terminal type fails
+ ret = self.session.requestReceived(
+ 'pty_req', session.packRequest_pty_req(
+ 'bad', (1, 2, 3, 4), ''))
+ self.assertFalse(ret)
+ self.assertSessionIsStubSession()
+ self.assertRequestRaisedRuntimeError()
+ # 'good' terminal type succeeds
+ self.assertTrue(self.session.requestReceived('pty_req',
+ session.packRequest_pty_req('good', (1, 2, 3, 4), '')))
+ self.assertEquals(self.session.session.ptyRequest,
+ ('good', (1, 2, 3, 4), []))
+
+
+ def test_requestWindowChange(self):
+ """
+ When the client requests to change the window size, the SSHSession
+ object should make the request by getting an ISession adapter for the
+ avatar, then calling windowChanged with the new window size.
+ """
+ ret = self.session.requestReceived(
+ 'window_change',
+ session.packRequest_window_change((0, 0, 0, 0)))
+ self.assertFalse(ret)
+ self.assertRequestRaisedRuntimeError()
+ self.assertSessionIsStubSession()
+ self.assertTrue(self.session.requestReceived('window_change',
+ session.packRequest_window_change((1, 2, 3, 4))))
+ self.assertEquals(self.session.session.windowChange,
+ (1, 2, 3, 4))
+
+
+ def test_eofReceived(self):
+ """
+ When an EOF is received and a ISession adapter is present, it should
+ be notified of the EOF message.
+ """
+ self.session.session = session.ISession(self.session.avatar)
+ self.session.eofReceived()
+ self.assertTrue(self.session.session.gotEOF)
+
+
+ def test_closeReceived(self):
+ """
+ When a close is received, the session should send a close message.
+ """
+ ret = self.session.closeReceived()
+ self.assertIdentical(ret, None)
+ self.assertTrue(self.session.conn.closes[self.session])
+
+
+ def test_closed(self):
+ """
+ When a close is received and a ISession adapter is present, it should
+ be notified of the close message.
+ """
+ self.session.session = session.ISession(self.session.avatar)
+ self.session.closed()
+ self.assertTrue(self.session.session.gotClosed)
+
+
+
+class SessionWithNoAvatarTestCase(unittest.TestCase):
+ """
+ Test for the SSHSession interface. Several of the methods (request_shell,
+ request_exec, request_pty_req, request_window_change) would create a
+ 'session' instance variable from the avatar if one didn't exist when they
+ were called.
+ """
+
+
+ def setUp(self):
+ self.session = session.SSHSession()
+ self.session.avatar = StubAvatar()
+ self.assertIdentical(self.session.session, None)
+
+
+ def assertSessionProvidesISession(self):
+ """
+ self.session.session should provide I{ISession}.
+ """
+ self.assertTrue(session.ISession.providedBy(self.session.session),
+ "ISession not provided by %r" % self.session.session)
+
+
+ def test_requestShellGetsSession(self):
+ """
+ If an ISession adapter isn't already present, request_shell should get
+ one.
+ """
+ self.session.requestReceived('shell', '')
+ self.assertSessionProvidesISession()
+
+
+ def test_requestExecGetsSession(self):
+ """
+ If an ISession adapter isn't already present, request_exec should get
+ one.
+ """
+ self.session.requestReceived('exec',
+ common.NS('success'))
+ self.assertSessionProvidesISession()
+
+
+ def test_requestPtyReqGetsSession(self):
+ """
+ If an ISession adapter isn't already present, request_pty_req should
+ get one.
+ """
+ self.session.requestReceived('pty_req',
+ session.packRequest_pty_req(
+ 'term', (0, 0, 0, 0), ''))
+ self.assertSessionProvidesISession()
+
+
+ def test_requestWindowChangeGetsSession(self):
+ """
+ If an ISession adapter isn't already present, request_window_change
+ should get one.
+ """
+ self.session.requestReceived(
+ 'window_change',
+ session.packRequest_window_change(
+ (1, 1, 1, 1)))
+ self.assertSessionProvidesISession()
+
+
+
+class WrappersTestCase(unittest.TestCase):
+ """
+ A test for the wrapProtocol and wrapProcessProtocol functions.
+ """
+
+ def test_wrapProtocol(self):
+ """
+ L{wrapProtocol}, when passed a L{Protocol} should return something that
+ has write(), writeSequence(), loseConnection() methods which call the
+ Protocol's dataReceived() and connectionLost() methods, respectively.
+ """
+ protocol = MockProtocol()
+ protocol.transport = StubTransport()
+ protocol.connectionMade()
+ wrapped = session.wrapProtocol(protocol)
+ wrapped.dataReceived('dataReceived')
+ self.assertEquals(protocol.transport.buf, 'dataReceived')
+ wrapped.write('data')
+ wrapped.writeSequence(['1', '2'])
+ wrapped.loseConnection()
+ self.assertEquals(protocol.data, 'data12')
+ protocol.reason.trap(error.ConnectionDone)
+
+ def test_wrapProcessProtocol_Protocol(self):
+ """
+ L{wrapPRocessProtocol}, when passed a L{Protocol} should return
+ something that follows the L{IProcessProtocol} interface, with
+ connectionMade() mapping to connectionMade(), outReceived() mapping to
+ dataReceived() and processEnded() mapping to connectionLost().
+ """
+ protocol = MockProtocol()
+ protocol.transport = StubTransport()
+ process_protocol = session.wrapProcessProtocol(protocol)
+ process_protocol.connectionMade()
+ process_protocol.outReceived('data')
+ self.assertEquals(protocol.transport.buf, 'data~')
+ process_protocol.processEnded(failure.Failure(
+ error.ProcessTerminated(0, None, None)))
+ protocol.reason.trap(error.ProcessTerminated)
+
+
+
+class TestHelpers(unittest.TestCase):
+ """
+ Tests for the 4 helper functions: parseRequest_* and packRequest_*.
+ """
+
+
+ def test_parseRequest_pty_req(self):
+ """
+ The payload of a pty-req message is::
+ string terminal
+ uint32 columns
+ uint32 rows
+ uint32 x pixels
+ uint32 y pixels
+ string modes
+
+ Modes are::
+ byte mode number
+ uint32 mode value
+ """
+ self.assertEquals(session.parseRequest_pty_req(common.NS('xterm') +
+ struct.pack('>4L',
+ 1, 2, 3, 4)
+ + common.NS(
+ struct.pack('>BL', 5, 6))),
+ ('xterm', (2, 1, 3, 4), [(5, 6)]))
+
+
+ def test_packRequest_pty_req_old(self):
+ """
+ See test_parseRequest_pty_req for the payload format.
+ """
+ packed = session.packRequest_pty_req('xterm', (2, 1, 3, 4),
+ '\x05\x00\x00\x00\x06')
+
+ self.assertEquals(packed,
+ common.NS('xterm') + struct.pack('>4L', 1, 2, 3, 4) +
+ common.NS(struct.pack('>BL', 5, 6)))
+
+
+ def test_packRequest_pty_req(self):
+ """
+ See test_parseRequest_pty_req for the payload format.
+ """
+ packed = session.packRequest_pty_req('xterm', (2, 1, 3, 4),
+ '\x05\x00\x00\x00\x06')
+ self.assertEquals(packed,
+ common.NS('xterm') + struct.pack('>4L', 1, 2, 3, 4) +
+ common.NS(struct.pack('>BL', 5, 6)))
+
+
+ def test_parseRequest_window_change(self):
+ """
+ The payload of a window_change request is::
+ uint32 columns
+ uint32 rows
+ uint32 x pixels
+ uint32 y pixels
+
+ parseRequest_window_change() returns (rows, columns, x pixels,
+ y pixels).
+ """
+ self.assertEquals(session.parseRequest_window_change(
+ struct.pack('>4L', 1, 2, 3, 4)), (2, 1, 3, 4))
+
+
+ def test_packRequest_window_change(self):
+ """
+ See test_parseRequest_window_change for the payload format.
+ """
+ self.assertEquals(session.packRequest_window_change((2, 1, 3, 4)),
+ struct.pack('>4L', 1, 2, 3, 4))
+
+
+
+class SSHSessionProcessProtocolTestCase(unittest.TestCase):
+ """
+ Tests for L{SSHSessionProcessProtocol}.
+ """
+
+ def setUp(self):
+ self.session = session.SSHSession(
+ conn=StubConnection(), remoteWindow=500, remoteMaxPacket=100)
+ self.transport = StubTransport()
+ self.pp = session.SSHSessionProcessProtocol(self.session)
+ self.pp.makeConnection(self.transport)
+
+
+ def assertSessionClosed(self):
+ """
+ Assert that C{self.session} is closed.
+ """
+ self.assertTrue(self.session.conn.closes[self.session])
+
+
+ def assertRequestsEqual(self, expectedRequests):
+ """
+ Assert that C{self.session} has sent the C{expectedRequests}.
+ """
+ self.assertEqual(
+ self.session.conn.requests[self.session],
+ expectedRequests)
+
+
+ def test_init(self):
+ """
+ SSHSessionProcessProtocol should set self.session to the session passed
+ to the __init__ method.
+ """
+ self.assertEquals(self.pp.session, self.session)
+
+
+ def test_connectionMade(self):
+ """
+ SSHSessionProcessProtocol.connectionMade() should check if there's a
+ 'buf' attribute on its session and write it to the transport if so.
+ """
+ self.session.buf = 'buffer'
+ self.pp.connectionMade()
+ self.assertEquals(self.transport.buf, 'buffer')
+
+
+ def test_getSignalName(self):
+ """
+ _getSignalName should return the name of a signal when given the
+ signal number.
+ """
+ for signalName in session.SUPPORTED_SIGNALS:
+ signalName = 'SIG' + signalName
+ signalValue = getattr(signal, signalName)
+ sshName = self.pp._getSignalName(signalValue)
+ self.assertEquals(sshName, signalName,
+ "%i: %s != %s" % (signalValue, sshName,
+ signalName))
+
+
+ def test_getSignalNameWithLocalSignal(self):
+ """
+ If there are signals in the signal module which aren't in the SSH RFC,
+ we map their name to [signal name]@[platform].
+ """
+ signal.SIGTwistedTest = signal.NSIG + 1 # value can't exist normally
+ # Force reinitialization of signals
+ self.pp._signalValuesToNames = None
+ self.assertEquals(self.pp._getSignalName(signal.SIGTwistedTest),
+ 'SIGTwistedTest@' + sys.platform)
+
+
+ if getattr(signal, 'SIGALRM', None) is None:
+ test_getSignalName.skip = test_getSignalNameWithLocalSignal.skip = \
+ "Not all signals available"
+
+
+ def test_outReceived(self):
+ """
+ When data is passed to the outReceived method, it should be sent to
+ the session's write method.
+ """
+ self.pp.outReceived('test data')
+ self.assertEquals(self.session.conn.data[self.session],
+ ['test data'])
+
+
+ def test_write(self):
+ """
+ When data is passed to the write method, it should be sent to the
+ session channel's write method.
+ """
+ self.pp.write('test data')
+ self.assertEquals(self.session.conn.data[self.session],
+ ['test data'])
+
+ def test_writeSequence(self):
+ """
+ When a sequence is passed to the writeSequence method, it should be
+ joined together and sent to the session channel's write method.
+ """
+ self.pp.writeSequence(['test ', 'data'])
+ self.assertEquals(self.session.conn.data[self.session],
+ ['test data'])
+
+
+ def test_errReceived(self):
+ """
+ When data is passed to the errReceived method, it should be sent to
+ the session's writeExtended method.
+ """
+ self.pp.errReceived('test data')
+ self.assertEquals(self.session.conn.extData[self.session],
+ [(1, 'test data')])
+
+
+ def test_inConnectionLost(self):
+ """
+ When inConnectionLost is called, it should send an EOF message,
+ """
+ self.pp.inConnectionLost()
+ self.assertTrue(self.session.conn.eofs[self.session])
+
+
+ def test_loseConnection(self):
+ """
+ When loseConnection() is called, it should call loseConnection
+ on the session channel.
+ """
+ self.pp.loseConnection()
+ self.assertTrue(self.session.conn.closes[self.session])
+
+
+ def test_connectionLost(self):
+ """
+ When connectionLost() is called, it should call loseConnection()
+ on the session channel.
+ """
+ self.pp.connectionLost(failure.Failure(
+ ProcessDone(0)))
+
+
+ def test_processEndedWithExitCode(self):
+ """
+ When processEnded is called, if there is an exit code in the reason
+ it should be sent in an exit-status method. The connection should be
+ closed.
+ """
+ self.pp.processEnded(Failure(ProcessDone(None)))
+ self.assertRequestsEqual(
+ [('exit-status', struct.pack('>I', 0) , False)])
+ self.assertSessionClosed()
+
+
+ def test_processEndedWithExitSignalCoreDump(self):
+ """
+ When processEnded is called, if there is an exit signal in the reason
+ it should be sent in an exit-signal message. The connection should be
+ closed.
+ """
+ self.pp.processEnded(
+ Failure(ProcessTerminated(1,
+ signal.SIGTERM, 1 << 7))) # 7th bit means core dumped
+ self.assertRequestsEqual(
+ [('exit-signal',
+ common.NS('TERM') # signal name
+ + '\x01' # core dumped is true
+ + common.NS('') # error message
+ + common.NS(''), # language tag
+ False)])
+ self.assertSessionClosed()
+
+
+ def test_processEndedWithExitSignalNoCoreDump(self):
+ """
+ When processEnded is called, if there is an exit signal in the
+ reason it should be sent in an exit-signal message. If no
+ core was dumped, don't set the core-dump bit.
+ """
+ self.pp.processEnded(
+ Failure(ProcessTerminated(1, signal.SIGTERM, 0)))
+ # see comments in test_processEndedWithExitSignalCoreDump for the
+ # meaning of the parts in the request
+ self.assertRequestsEqual(
+ [('exit-signal', common.NS('TERM') + '\x00' + common.NS('') +
+ common.NS(''), False)])
+ self.assertSessionClosed()
+
+
+ if getattr(os, 'WCOREDUMP', None) is None:
+ skipMsg = "can't run this w/o os.WCOREDUMP"
+ test_processEndedWithExitSignalCoreDump.skip = skipMsg
+ test_processEndedWithExitSignalNoCoreDump.skip = skipMsg
+
+
+
+class SSHSessionClientTestCase(unittest.TestCase):
+ """
+ SSHSessionClient is an obsolete class used to connect standard IO to
+ an SSHSession.
+ """
+
+
+ def test_dataReceived(self):
+ """
+ When data is received, it should be sent to the transport.
+ """
+ client = session.SSHSessionClient()
+ client.transport = StubTransport()
+ client.dataReceived('test data')
+ self.assertEquals(client.transport.buf, 'test data')
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_ssh.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_ssh.py
new file mode 100644
index 0000000000..d28d6507f4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_ssh.py
@@ -0,0 +1,886 @@
+# -*- test-case-name: twisted.conch.test.test_ssh -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import struct
+
+try:
+ import Crypto.Cipher.DES3
+except ImportError:
+ Crypto = None
+
+try:
+ import pyasn1
+except ImportError:
+ pyasn1 = None
+
+from twisted.conch.ssh import common, session, forwarding
+from twisted.conch import avatar, error
+from twisted.conch.test.keydata import publicRSA_openssh, privateRSA_openssh
+from twisted.conch.test.keydata import publicDSA_openssh, privateDSA_openssh
+from twisted.cred import portal
+from twisted.internet import defer, protocol, reactor
+from twisted.internet.error import ProcessTerminated
+from twisted.python import failure, log
+from twisted.trial import unittest
+
+from test_recvline import LoopbackRelay
+
+
+
+class ConchTestRealm:
+
+ def requestAvatar(self, avatarID, mind, *interfaces):
+ unittest.assertEquals(avatarID, 'testuser')
+ a = ConchTestAvatar()
+ return interfaces[0], a, a.logout
+
+class ConchTestAvatar(avatar.ConchUser):
+ loggedOut = False
+
+ def __init__(self):
+ avatar.ConchUser.__init__(self)
+ self.listeners = {}
+ self.channelLookup.update({'session': session.SSHSession,
+ 'direct-tcpip':forwarding.openConnectForwardingClient})
+ self.subsystemLookup.update({'crazy': CrazySubsystem})
+
+ def global_foo(self, data):
+ unittest.assertEquals(data, 'bar')
+ return 1
+
+ def global_foo_2(self, data):
+ unittest.assertEquals(data, 'bar2')
+ return 1, 'data'
+
+ def global_tcpip_forward(self, data):
+ host, port = forwarding.unpackGlobal_tcpip_forward(data)
+ try: listener = reactor.listenTCP(port,
+ forwarding.SSHListenForwardingFactory(self.conn,
+ (host, port),
+ forwarding.SSHListenServerForwardingChannel),
+ interface = host)
+ except:
+ log.err()
+ unittest.fail("something went wrong with remote->local forwarding")
+ return 0
+ else:
+ self.listeners[(host, port)] = listener
+ return 1
+
+ def global_cancel_tcpip_forward(self, data):
+ host, port = forwarding.unpackGlobal_tcpip_forward(data)
+ listener = self.listeners.get((host, port), None)
+ if not listener:
+ return 0
+ del self.listeners[(host, port)]
+ listener.stopListening()
+ return 1
+
+ def logout(self):
+ self.loggedOut = True
+ for listener in self.listeners.values():
+ log.msg('stopListening %s' % listener)
+ listener.stopListening()
+
+class ConchSessionForTestAvatar:
+
+ def __init__(self, avatar):
+ unittest.assert_(isinstance(avatar, ConchTestAvatar))
+ self.avatar = avatar
+ self.cmd = None
+ self.proto = None
+ self.ptyReq = False
+ self.eof = 0
+
+ def getPty(self, term, windowSize, attrs):
+ log.msg('pty req')
+ unittest.assertEquals(term, 'conch-test-term')
+ unittest.assertEquals(windowSize, (24, 80, 0, 0))
+ self.ptyReq = True
+
+ def openShell(self, proto):
+ log.msg('openning shell')
+ unittest.assertEquals(self.ptyReq, True)
+ self.proto = proto
+ EchoTransport(proto)
+ self.cmd = 'shell'
+
+ def execCommand(self, proto, cmd):
+ self.cmd = cmd
+ unittest.assert_(cmd.split()[0] in ['false', 'echo', 'secho', 'eecho','jumboliah'],
+ 'invalid command: %s' % cmd.split()[0])
+ if cmd == 'jumboliah':
+ raise error.ConchError('bad exec')
+ self.proto = proto
+ f = cmd.split()[0]
+ if f == 'false':
+ FalseTransport(proto)
+ elif f == 'echo':
+ t = EchoTransport(proto)
+ t.write(cmd[5:])
+ t.loseConnection()
+ elif f == 'secho':
+ t = SuperEchoTransport(proto)
+ t.write(cmd[6:])
+ t.loseConnection()
+ elif f == 'eecho':
+ t = ErrEchoTransport(proto)
+ t.write(cmd[6:])
+ t.loseConnection()
+ self.avatar.conn.transport.expectedLoseConnection = 1
+
+# def closeReceived(self):
+# #if self.proto:
+# # self.proto.transport.loseConnection()
+# self.loseConnection()
+
+ def eofReceived(self):
+ self.eof = 1
+
+ def closed(self):
+ log.msg('closed cmd "%s"' % self.cmd)
+ if self.cmd == 'echo hello':
+ rwl = self.proto.session.remoteWindowLeft
+ unittest.assertEquals(rwl, 4)
+ elif self.cmd == 'eecho hello':
+ rwl = self.proto.session.remoteWindowLeft
+ unittest.assertEquals(rwl, 4)
+ elif self.cmd == 'shell':
+ unittest.assert_(self.eof)
+
+from twisted.python import components
+components.registerAdapter(ConchSessionForTestAvatar, ConchTestAvatar, session.ISession)
+
+class CrazySubsystem(protocol.Protocol):
+
+ def __init__(self, *args, **kw):
+ pass
+
+ def connectionMade(self):
+ """
+ good ... good
+ """
+
+class FalseTransport:
+
+ def __init__(self, p):
+ p.makeConnection(self)
+ p.processEnded(failure.Failure(ProcessTerminated(255, None, None)))
+
+ def loseConnection(self):
+ pass
+
+class EchoTransport:
+
+ def __init__(self, p):
+ self.proto = p
+ p.makeConnection(self)
+ self.closed = 0
+
+ def write(self, data):
+ log.msg(repr(data))
+ self.proto.outReceived(data)
+ self.proto.outReceived('\r\n')
+ if '\x00' in data: # mimic 'exit' for the shell test
+ self.loseConnection()
+
+ def loseConnection(self):
+ if self.closed: return
+ self.closed = 1
+ self.proto.inConnectionLost()
+ self.proto.outConnectionLost()
+ self.proto.errConnectionLost()
+ self.proto.processEnded(failure.Failure(ProcessTerminated(0, None, None)))
+
+class ErrEchoTransport:
+
+ def __init__(self, p):
+ self.proto = p
+ p.makeConnection(self)
+ self.closed = 0
+
+ def write(self, data):
+ self.proto.errReceived(data)
+ self.proto.errReceived('\r\n')
+
+ def loseConnection(self):
+ if self.closed: return
+ self.closed = 1
+ self.proto.inConnectionLost()
+ self.proto.outConnectionLost()
+ self.proto.errConnectionLost()
+ self.proto.processEnded(failure.Failure(ProcessTerminated(0, None, None)))
+
+class SuperEchoTransport:
+
+ def __init__(self, p):
+ self.proto = p
+ p.makeConnection(self)
+ self.closed = 0
+
+ def write(self, data):
+ self.proto.outReceived(data)
+ self.proto.outReceived('\r\n')
+ self.proto.errReceived(data)
+ self.proto.errReceived('\r\n')
+
+ def loseConnection(self):
+ if self.closed: return
+ self.closed = 1
+ self.proto.inConnectionLost()
+ self.proto.outConnectionLost()
+ self.proto.errConnectionLost()
+ self.proto.processEnded(failure.Failure(ProcessTerminated(0, None, None)))
+
+
+if Crypto is not None and pyasn1 is not None:
+ from twisted.conch import checkers
+ from twisted.conch.ssh import channel, connection, factory, keys
+ from twisted.conch.ssh import transport, userauth
+
+ class UtilityTestCase(unittest.TestCase):
+ def testCounter(self):
+ c = transport._Counter('\x00\x00', 2)
+ for i in xrange(256 * 256):
+ self.assertEquals(c(), struct.pack('!H', (i + 1) % (2 ** 16)))
+ # It should wrap around, too.
+ for i in xrange(256 * 256):
+ self.assertEquals(c(), struct.pack('!H', (i + 1) % (2 ** 16)))
+
+
+ class ConchTestPublicKeyChecker(checkers.SSHPublicKeyDatabase):
+ def checkKey(self, credentials):
+ unittest.assertEquals(credentials.username, 'testuser', 'bad username')
+ unittest.assertEquals(credentials.blob, keys.getPublicKeyString(data=publicDSA_openssh))
+ return 1
+
+ class ConchTestPasswordChecker:
+ credentialInterfaces = checkers.IUsernamePassword,
+
+ def requestAvatarId(self, credentials):
+ unittest.assertEquals(credentials.username, 'testuser', 'bad username')
+ unittest.assertEquals(credentials.password, 'testpass', 'bad password')
+ return defer.succeed(credentials.username)
+
+ class ConchTestSSHChecker(checkers.SSHProtocolChecker):
+
+ def areDone(self, avatarId):
+ unittest.assertEquals(avatarId, 'testuser')
+ if len(self.successfulCredentials[avatarId]) < 2:
+ return 0
+ else:
+ return 1
+
+ class ConchTestServerFactory(factory.SSHFactory):
+ noisy = 0
+
+ services = {
+ 'ssh-userauth':userauth.SSHUserAuthServer,
+ 'ssh-connection':connection.SSHConnection
+ }
+
+ def buildProtocol(self, addr):
+ proto = ConchTestServer()
+ proto.supportedPublicKeys = self.privateKeys.keys()
+ proto.factory = self
+
+ if hasattr(self, 'expectedLoseConnection'):
+ proto.expectedLoseConnection = self.expectedLoseConnection
+
+ self.proto = proto
+ return proto
+
+ def getPublicKeys(self):
+ return {
+ 'ssh-rsa':keys.getPublicKeyString(data=publicRSA_openssh),
+ 'ssh-dss':keys.getPublicKeyString(data=publicDSA_openssh)
+ }
+
+ def getPrivateKeys(self):
+ return {
+ 'ssh-rsa':keys.getPrivateKeyObject(data=privateRSA_openssh),
+ 'ssh-dss':keys.getPrivateKeyObject(data=privateDSA_openssh)
+ }
+
+ def getPrimes(self):
+ return {
+ 2048:[(transport.DH_GENERATOR, transport.DH_PRIME)]
+ }
+
+ def getService(self, trans, name):
+ return factory.SSHFactory.getService(self, trans, name)
+
+ class ConchTestBase:
+
+ done = 0
+ allowedToError = 0
+
+ def connectionLost(self, reason):
+ if self.done:
+ return
+ if not hasattr(self,'expectedLoseConnection'):
+ unittest.fail('unexpectedly lost connection %s\n%s' % (self, reason))
+ self.done = 1
+
+ def receiveError(self, reasonCode, desc):
+ self.expectedLoseConnection = 1
+ if not self.allowedToError:
+ unittest.fail('got disconnect for %s: reason %s, desc: %s' %
+ (self, reasonCode, desc))
+ self.loseConnection()
+
+ def receiveUnimplemented(self, seqID):
+ unittest.fail('got unimplemented: seqid %s' % seqID)
+ self.expectedLoseConnection = 1
+ self.loseConnection()
+
+ class ConchTestServer(ConchTestBase, transport.SSHServerTransport):
+
+ def connectionLost(self, reason):
+ ConchTestBase.connectionLost(self, reason)
+ transport.SSHServerTransport.connectionLost(self, reason)
+
+ class ConchTestClient(ConchTestBase, transport.SSHClientTransport):
+
+ def connectionLost(self, reason):
+ ConchTestBase.connectionLost(self, reason)
+ transport.SSHClientTransport.connectionLost(self, reason)
+
+ def verifyHostKey(self, key, fp):
+ unittest.assertEquals(key, keys.getPublicKeyString(data = publicRSA_openssh))
+ unittest.assertEquals(fp,'3d:13:5f:cb:c9:79:8a:93:06:27:65:bc:3d:0b:8f:af')
+ return defer.succeed(1)
+
+ def connectionSecure(self):
+ self.requestService(ConchTestClientAuth('testuser',
+ ConchTestClientConnection()))
+
+ class ConchTestClientAuth(userauth.SSHUserAuthClient):
+
+ hasTriedNone = 0 # have we tried the 'none' auth yet?
+ canSucceedPublicKey = 0 # can we succed with this yet?
+ canSucceedPassword = 0
+
+ def ssh_USERAUTH_SUCCESS(self, packet):
+ if not self.canSucceedPassword and self.canSucceedPublicKey:
+ unittest.fail('got USERAUTH_SUCESS before password and publickey')
+ userauth.SSHUserAuthClient.ssh_USERAUTH_SUCCESS(self, packet)
+
+ def getPassword(self):
+ self.canSucceedPassword = 1
+ return defer.succeed('testpass')
+
+ def getPrivateKey(self):
+ self.canSucceedPublicKey = 1
+ return defer.succeed(keys.getPrivateKeyObject(data=privateDSA_openssh))
+
+ def getPublicKey(self):
+ return keys.getPublicKeyString(data=publicDSA_openssh)
+
+ class ConchTestClientConnection(connection.SSHConnection):
+
+ name = 'ssh-connection'
+ results = 0
+ totalResults = 8
+
+ def serviceStarted(self):
+ self.openChannel(SSHTestFailExecChannel(conn = self))
+ self.openChannel(SSHTestFalseChannel(conn = self))
+ self.openChannel(SSHTestEchoChannel(localWindow=4, localMaxPacket=5, conn = self))
+ self.openChannel(SSHTestErrChannel(localWindow=4, localMaxPacket=5, conn = self))
+ self.openChannel(SSHTestMaxPacketChannel(localWindow=12, localMaxPacket=1, conn = self))
+ self.openChannel(SSHTestShellChannel(conn = self))
+ self.openChannel(SSHTestSubsystemChannel(conn = self))
+ self.openChannel(SSHUnknownChannel(conn = self))
+
+ def addResult(self):
+ self.results += 1
+ log.msg('got %s of %s results' % (self.results, self.totalResults))
+ if self.results == self.totalResults:
+ self.transport.expectedLoseConnection = 1
+ self.serviceStopped()
+
+ class SSHUnknownChannel(channel.SSHChannel):
+
+ name = 'crazy-unknown-channel'
+
+ def openFailed(self, reason):
+ """
+ good .... good
+ """
+ log.msg('unknown open failed')
+ log.flushErrors()
+ self.conn.addResult()
+
+ def channelOpen(self, ignored):
+ unittest.fail("opened unknown channel")
+
+ class SSHTestFailExecChannel(channel.SSHChannel):
+
+ name = 'session'
+
+ def openFailed(self, reason):
+ unittest.fail('fail exec open failed: %s' % reason)
+
+ def channelOpen(self, ignore):
+ d = self.conn.sendRequest(self, 'exec', common.NS('jumboliah'), 1)
+ d.addCallback(self._cbRequestWorked)
+ d.addErrback(self._ebRequestWorked)
+ log.msg('opened fail exec')
+
+ def _cbRequestWorked(self, ignored):
+ unittest.fail('fail exec succeeded')
+
+ def _ebRequestWorked(self, ignored):
+ log.msg('fail exec finished')
+ log.flushErrors()
+ self.conn.addResult()
+ self.loseConnection()
+
+ class SSHTestFalseChannel(channel.SSHChannel):
+
+ name = 'session'
+
+ def openFailed(self, reason):
+ unittest.fail('false open failed: %s' % reason)
+
+ def channelOpen(self, ignored):
+ d = self.conn.sendRequest(self, 'exec', common.NS('false'), 1)
+ d.addCallback(self._cbRequestWorked)
+ d.addErrback(self._ebRequestFailed)
+ log.msg('opened false')
+
+ def _cbRequestWorked(self, ignored):
+ pass
+
+ def _ebRequestFailed(self, reason):
+ unittest.fail('false exec failed: %s' % reason)
+
+ def dataReceived(self, data):
+ unittest.fail('got data when using false')
+
+ def request_exit_status(self, status):
+ status, = struct.unpack('>L', status)
+ if status == 0:
+ unittest.fail('false exit status was 0')
+ log.msg('finished false')
+ self.conn.addResult()
+ return 1
+
+ class SSHTestEchoChannel(channel.SSHChannel):
+
+ name = 'session'
+ testBuf = ''
+ eofCalled = 0
+
+ def openFailed(self, reason):
+ unittest.fail('echo open failed: %s' % reason)
+
+ def channelOpen(self, ignore):
+ d = self.conn.sendRequest(self, 'exec', common.NS('echo hello'), 1)
+ d.addErrback(self._ebRequestFailed)
+ log.msg('opened echo')
+
+ def _ebRequestFailed(self, reason):
+ unittest.fail('echo exec failed: %s' % reason)
+
+ def dataReceived(self, data):
+ self.testBuf += data
+
+ def errReceived(self, dataType, data):
+ unittest.fail('echo channel got extended data')
+
+ def request_exit_status(self, status):
+ self.status ,= struct.unpack('>L', status)
+
+ def eofReceived(self):
+ log.msg('eof received')
+ self.eofCalled = 1
+
+ def closed(self):
+ if self.status != 0:
+ unittest.fail('echo exit status was not 0: %i' % self.status)
+ if self.testBuf != "hello\r\n":
+ unittest.fail('echo did not return hello: %s' % repr(self.testBuf))
+ unittest.assertEquals(self.localWindowLeft, 4)
+ unittest.assert_(self.eofCalled)
+ log.msg('finished echo')
+ self.conn.addResult()
+ return 1
+
+ class SSHTestErrChannel(channel.SSHChannel):
+
+ name = 'session'
+ testBuf = ''
+ eofCalled = 0
+
+ def openFailed(self, reason):
+ unittest.fail('err open failed: %s' % reason)
+
+ def channelOpen(self, ignore):
+ d = self.conn.sendRequest(self, 'exec', common.NS('eecho hello'), 1)
+ d.addErrback(self._ebRequestFailed)
+ log.msg('opened err')
+
+ def _ebRequestFailed(self, reason):
+ unittest.fail('err exec failed: %s' % reason)
+
+ def dataReceived(self, data):
+ unittest.fail('err channel got regular data: %s' % repr(data))
+
+ def extReceived(self, dataType, data):
+ unittest.assertEquals(dataType, connection.EXTENDED_DATA_STDERR)
+ self.testBuf += data
+
+ def request_exit_status(self, status):
+ self.status ,= struct.unpack('>L', status)
+
+ def eofReceived(self):
+ log.msg('eof received')
+ self.eofCalled = 1
+
+ def closed(self):
+ if self.status != 0:
+ unittest.fail('err exit status was not 0: %i' % self.status)
+ if self.testBuf != "hello\r\n":
+ unittest.fail('err did not return hello: %s' % repr(self.testBuf))
+ unittest.assertEquals(self.localWindowLeft, 4)
+ unittest.assert_(self.eofCalled)
+ log.msg('finished err')
+ self.conn.addResult()
+ return 1
+
+ class SSHTestMaxPacketChannel(channel.SSHChannel):
+
+ name = 'session'
+ testBuf = ''
+ testExtBuf = ''
+ eofCalled = 0
+
+ def openFailed(self, reason):
+ unittest.fail('max packet open failed: %s' % reason)
+
+ def channelOpen(self, ignore):
+ d = self.conn.sendRequest(self, 'exec', common.NS('secho hello'), 1)
+ d.addErrback(self._ebRequestFailed)
+ log.msg('opened max packet')
+
+ def _ebRequestFailed(self, reason):
+ unittest.fail('max packet exec failed: %s' % reason)
+
+ def dataReceived(self, data):
+ self.testBuf += data
+
+ def extReceived(self, dataType, data):
+ unittest.assertEquals(dataType, connection.EXTENDED_DATA_STDERR)
+ self.testExtBuf += data
+
+ def request_exit_status(self, status):
+ self.status ,= struct.unpack('>L', status)
+
+ def eofReceived(self):
+ log.msg('eof received')
+ self.eofCalled = 1
+
+ def closed(self):
+ if self.status != 0:
+ unittest.fail('echo exit status was not 0: %i' % self.status)
+ unittest.assertEquals(self.testBuf, 'hello\r\n')
+ unittest.assertEquals(self.testExtBuf, 'hello\r\n')
+ unittest.assertEquals(self.localWindowLeft, 12)
+ unittest.assert_(self.eofCalled)
+ log.msg('finished max packet')
+ self.conn.addResult()
+ return 1
+
+ class SSHTestShellChannel(channel.SSHChannel):
+
+ name = 'session'
+ testBuf = ''
+ eofCalled = 0
+ closeCalled = 0
+
+ def openFailed(self, reason):
+ unittest.fail('shell open failed: %s' % reason)
+
+ def channelOpen(self, ignored):
+ data = session.packRequest_pty_req('conch-test-term', (24, 80, 0, 0), '')
+ d = self.conn.sendRequest(self, 'pty-req', data, 1)
+ d.addCallback(self._cbPtyReq)
+ d.addErrback(self._ebPtyReq)
+ log.msg('opened shell')
+
+ def _cbPtyReq(self, ignored):
+ d = self.conn.sendRequest(self, 'shell', '', 1)
+ d.addCallback(self._cbShellOpen)
+ d.addErrback(self._ebShellOpen)
+
+ def _ebPtyReq(self, reason):
+ unittest.fail('pty request failed: %s' % reason)
+
+ def _cbShellOpen(self, ignored):
+ self.write('testing the shell!\x00')
+ self.conn.sendEOF(self)
+
+ def _ebShellOpen(self, reason):
+ unittest.fail('shell request failed: %s' % reason)
+
+ def dataReceived(self, data):
+ self.testBuf += data
+
+ def request_exit_status(self, status):
+ self.status ,= struct.unpack('>L', status)
+
+ def eofReceived(self):
+ self.eofCalled = 1
+
+ def closed(self):
+ log.msg('calling shell closed')
+ if self.status != 0:
+ log.msg('shell exit status was not 0: %i' % self.status)
+ unittest.assertEquals(self.testBuf, 'testing the shell!\x00\r\n')
+ unittest.assert_(self.eofCalled)
+ log.msg('finished shell')
+ self.conn.addResult()
+
+ class SSHTestSubsystemChannel(channel.SSHChannel):
+
+ name = 'session'
+
+ def openFailed(self, reason):
+ unittest.fail('subsystem open failed: %s' % reason)
+
+ def channelOpen(self, ignore):
+ d = self.conn.sendRequest(self, 'subsystem', common.NS('not-crazy'), 1)
+ d.addCallback(self._cbRequestWorked)
+ d.addErrback(self._ebRequestFailed)
+
+
+ def _cbRequestWorked(self, ignored):
+ unittest.fail('opened non-crazy subsystem')
+
+ def _ebRequestFailed(self, ignored):
+ d = self.conn.sendRequest(self, 'subsystem', common.NS('crazy'), 1)
+ d.addCallback(self._cbRealRequestWorked)
+ d.addErrback(self._ebRealRequestFailed)
+
+ def _cbRealRequestWorked(self, ignored):
+ d1 = self.conn.sendGlobalRequest('foo', 'bar', 1)
+ d1.addErrback(self._ebFirstGlobal)
+
+ d2 = self.conn.sendGlobalRequest('foo-2', 'bar2', 1)
+ d2.addCallback(lambda x: unittest.assertEquals(x, 'data'))
+ d2.addErrback(self._ebSecondGlobal)
+
+ d3 = self.conn.sendGlobalRequest('bar', 'foo', 1)
+ d3.addCallback(self._cbThirdGlobal)
+ d3.addErrback(lambda x,s=self: log.msg('subsystem finished') or s.conn.addResult() or s.loseConnection())
+
+ def _ebRealRequestFailed(self, reason):
+ unittest.fail('opening crazy subsystem failed: %s' % reason)
+
+ def _ebFirstGlobal(self, reason):
+ unittest.fail('first global request failed: %s' % reason)
+
+ def _ebSecondGlobal(self, reason):
+ unittest.fail('second global request failed: %s' % reason)
+
+ def _cbThirdGlobal(self, ignored):
+ unittest.fail('second global request succeeded')
+
+
+
+class SSHProtocolTestCase(unittest.TestCase):
+
+ if not Crypto:
+ skip = "can't run w/o PyCrypto"
+
+ if not pyasn1:
+ skip = "can't run w/o PyASN1"
+
+ def testOurServerOurClient(self):
+ """test the Conch server against the Conch client
+ """
+ realm = ConchTestRealm()
+ p = portal.Portal(realm)
+ sshpc = ConchTestSSHChecker()
+ sshpc.registerChecker(ConchTestPasswordChecker())
+ sshpc.registerChecker(ConchTestPublicKeyChecker())
+ p.registerChecker(sshpc)
+ fac = ConchTestServerFactory()
+ fac.portal = p
+ fac.startFactory()
+ self.server = fac.buildProtocol(None)
+ self.clientTransport = LoopbackRelay(self.server)
+ self.client = ConchTestClient()
+ self.serverTransport = LoopbackRelay(self.client)
+
+ self.server.makeConnection(self.serverTransport)
+ self.client.makeConnection(self.clientTransport)
+
+ while self.serverTransport.buffer or self.clientTransport.buffer:
+ log.callWithContext({'system': 'serverTransport'},
+ self.serverTransport.clearBuffer)
+ log.callWithContext({'system': 'clientTransport'},
+ self.clientTransport.clearBuffer)
+ self.failIf(self.server.done and self.client.done)
+
+
+class TestSSHFactory(unittest.TestCase):
+
+ if not Crypto:
+ skip = "can't run w/o PyCrypto"
+
+ if not pyasn1:
+ skip = "can't run w/o PyASN1"
+
+ def makeSSHFactory(self, primes=None):
+ sshFactory = factory.SSHFactory()
+ gpk = lambda: {'ssh-rsa' : keys.Key(None)}
+ sshFactory.getPrimes = lambda: primes
+ sshFactory.getPublicKeys = sshFactory.getPrivateKeys = gpk
+ sshFactory.startFactory()
+ return sshFactory
+
+
+ def test_buildProtocol(self):
+ """
+ By default, buildProtocol() constructs an instance of
+ SSHServerTransport.
+ """
+ factory = self.makeSSHFactory()
+ protocol = factory.buildProtocol(None)
+ self.assertIsInstance(protocol, transport.SSHServerTransport)
+
+
+ def test_buildProtocolRespectsProtocol(self):
+ """
+ buildProtocol() calls 'self.protocol()' to construct a protocol
+ instance.
+ """
+ calls = []
+ def makeProtocol(*args):
+ calls.append(args)
+ return transport.SSHServerTransport()
+ factory = self.makeSSHFactory()
+ factory.protocol = makeProtocol
+ factory.buildProtocol(None)
+ self.assertEquals([()], calls)
+
+
+ def test_multipleFactories(self):
+ f1 = self.makeSSHFactory(primes=None)
+ f2 = self.makeSSHFactory(primes={1:(2,3)})
+ p1 = f1.buildProtocol(None)
+ p2 = f2.buildProtocol(None)
+ self.failIf('diffie-hellman-group-exchange-sha1' in p1.supportedKeyExchanges,
+ p1.supportedKeyExchanges)
+ self.failUnless('diffie-hellman-group-exchange-sha1' in p2.supportedKeyExchanges,
+ p2.supportedKeyExchanges)
+
+
+class EntropyTestCase(unittest.TestCase):
+ """
+ Tests for L{common.entropy}.
+ """
+
+ def test_deprecation(self):
+ """
+ Test the deprecation of L{common.entropy.get_bytes}.
+ """
+ def wrapper():
+ return common.entropy.get_bytes(10)
+ self.assertWarns(DeprecationWarning,
+ "entropy.get_bytes is deprecated, please use "
+ "twisted.python.randbytes.secureRandom instead.",
+ __file__, wrapper)
+
+
+
+class MPTestCase(unittest.TestCase):
+ """
+ Tests for L{common.getMP}.
+
+ @cvar getMP: a method providing a MP parser.
+ @type getMP: C{callable}
+ """
+ getMP = staticmethod(common.getMP)
+
+ if not Crypto:
+ skip = "can't run w/o PyCrypto"
+
+ if not pyasn1:
+ skip = "can't run w/o PyASN1"
+
+
+ def test_getMP(self):
+ """
+ L{common.getMP} should parse the a multiple precision integer from a
+ string: a 4-byte length followed by length bytes of the integer.
+ """
+ self.assertEquals(
+ self.getMP('\x00\x00\x00\x04\x00\x00\x00\x01'),
+ (1, ''))
+
+
+ def test_getMPBigInteger(self):
+ """
+ L{common.getMP} should be able to parse a big enough integer
+ (that doesn't fit on one byte).
+ """
+ self.assertEquals(
+ self.getMP('\x00\x00\x00\x04\x01\x02\x03\x04'),
+ (16909060, ''))
+
+
+ def test_multipleGetMP(self):
+ """
+ L{common.getMP} has the ability to parse multiple integer in the same
+ string.
+ """
+ self.assertEquals(
+ self.getMP('\x00\x00\x00\x04\x00\x00\x00\x01'
+ '\x00\x00\x00\x04\x00\x00\x00\x02', 2),
+ (1, 2, ''))
+
+
+ def test_getMPRemainingData(self):
+ """
+ When more data than needed is sent to L{common.getMP}, it should return
+ the remaining data.
+ """
+ self.assertEquals(
+ self.getMP('\x00\x00\x00\x04\x00\x00\x00\x01foo'),
+ (1, 'foo'))
+
+
+ def test_notEnoughData(self):
+ """
+ When the string passed to L{common.getMP} doesn't even make 5 bytes,
+ it should raise a L{struct.error}.
+ """
+ self.assertRaises(struct.error, self.getMP, '\x02\x00')
+
+
+
+class PyMPTestCase(MPTestCase):
+ """
+ Tests for the python implementation of L{common.getMP}.
+ """
+ getMP = staticmethod(common.getMP_py)
+
+
+
+class GMPYMPTestCase(MPTestCase):
+ """
+ Tests for the gmpy implementation of L{common.getMP}.
+ """
+ getMP = staticmethod(common._fastgetMP)
+
+
+
+try:
+ import gmpy
+except ImportError:
+ GMPYMPTestCase.skip = "gmpy not available"
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_tap.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_tap.py
new file mode 100644
index 0000000000..b0502b8394
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_tap.py
@@ -0,0 +1,95 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.tap}.
+"""
+
+try:
+ import Crypto.Cipher.DES3
+except:
+ Crypto = None
+
+try:
+ import pyasn1
+except ImportError:
+ pyasn1 = None
+
+try:
+ from twisted.conch import unix
+except ImportError:
+ unix = None
+
+if Crypto and pyasn1 and unix:
+ from twisted.conch import tap
+ from twisted.conch.openssh_compat.factory import OpenSSHFactory
+
+from twisted.python.compat import set
+from twisted.application.internet import TCPServer
+from twisted.cred.credentials import IPluggableAuthenticationModules
+from twisted.cred.credentials import ISSHPrivateKey
+from twisted.cred.credentials import IUsernamePassword
+
+from twisted.trial.unittest import TestCase
+
+
+
+class MakeServiceTest(TestCase):
+ """
+ Tests for L{tap.makeService}.
+ """
+
+ if not Crypto:
+ skip = "can't run w/o PyCrypto"
+
+ if not pyasn1:
+ skip = "can't run w/o PyASN1"
+
+ if not unix:
+ skip = "can't run on non-posix computers"
+
+ def test_basic(self):
+ """
+ L{tap.makeService} returns a L{TCPServer} instance running on port 22,
+ and the linked protocol factory is an instance of L{OpenSSHFactory}.
+ """
+ config = tap.Options()
+ service = tap.makeService(config)
+ self.assertIsInstance(service, TCPServer)
+ self.assertEquals(service.args[0], 22)
+ factory = service.args[1]
+ self.assertIsInstance(factory, OpenSSHFactory)
+
+
+ def test_checkersPamAuth(self):
+ """
+ The L{OpenSSHFactory} built by L{tap.makeService} has a portal with
+ L{IPluggableAuthenticationModules}, L{ISSHPrivateKey} and
+ L{IUsernamePassword} interfaces registered as checkers if C{pamauth} is
+ available.
+ """
+ # Fake the presence of pamauth, even if PyPAM is not installed
+ self.patch(tap, "pamauth", object())
+ config = tap.Options()
+ service = tap.makeService(config)
+ portal = service.args[1].portal
+ self.assertEquals(
+ set(portal.checkers.keys()),
+ set([IPluggableAuthenticationModules, ISSHPrivateKey,
+ IUsernamePassword]))
+
+
+ def test_checkersWithoutPamAuth(self):
+ """
+ The L{OpenSSHFactory} built by L{tap.makeService} has a portal with
+ L{ISSHPrivateKey} and L{IUsernamePassword} interfaces registered as
+ checkers if C{pamauth} is not available.
+ """
+ # Fake the absence of pamauth, even if PyPAM is installed
+ self.patch(tap, "pamauth", None)
+ config = tap.Options()
+ service = tap.makeService(config)
+ portal = service.args[1].portal
+ self.assertEquals(
+ set(portal.checkers.keys()),
+ set([ISSHPrivateKey, IUsernamePassword]))
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_telnet.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_telnet.py
new file mode 100644
index 0000000000..ab2a8cd4ad
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_telnet.py
@@ -0,0 +1,710 @@
+# -*- test-case-name: twisted.conch.test.test_telnet -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.conch.telnet}.
+"""
+
+from zope.interface import implements
+
+from twisted.internet import defer
+
+from twisted.conch import telnet
+
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+class TestProtocol:
+ implements(telnet.ITelnetProtocol)
+
+ localEnableable = ()
+ remoteEnableable = ()
+
+ def __init__(self):
+ self.bytes = ''
+ self.subcmd = ''
+ self.calls = []
+
+ self.enabledLocal = []
+ self.enabledRemote = []
+ self.disabledLocal = []
+ self.disabledRemote = []
+
+ def makeConnection(self, transport):
+ d = transport.negotiationMap = {}
+ d['\x12'] = self.neg_TEST_COMMAND
+
+ d = transport.commandMap = transport.commandMap.copy()
+ for cmd in ('NOP', 'DM', 'BRK', 'IP', 'AO', 'AYT', 'EC', 'EL', 'GA'):
+ d[getattr(telnet, cmd)] = lambda arg, cmd=cmd: self.calls.append(cmd)
+
+ def dataReceived(self, bytes):
+ self.bytes += bytes
+
+ def connectionLost(self, reason):
+ pass
+
+ def neg_TEST_COMMAND(self, payload):
+ self.subcmd = payload
+
+ def enableLocal(self, option):
+ if option in self.localEnableable:
+ self.enabledLocal.append(option)
+ return True
+ return False
+
+ def disableLocal(self, option):
+ self.disabledLocal.append(option)
+
+ def enableRemote(self, option):
+ if option in self.remoteEnableable:
+ self.enabledRemote.append(option)
+ return True
+ return False
+
+ def disableRemote(self, option):
+ self.disabledRemote.append(option)
+
+
+
+class TelnetTransportTestCase(unittest.TestCase):
+ """
+ Tests for L{telnet.TelnetTransport}.
+ """
+ def setUp(self):
+ self.p = telnet.TelnetTransport(TestProtocol)
+ self.t = proto_helpers.StringTransport()
+ self.p.makeConnection(self.t)
+
+ def testRegularBytes(self):
+ # Just send a bunch of bytes. None of these do anything
+ # with telnet. They should pass right through to the
+ # application layer.
+ h = self.p.protocol
+
+ L = ["here are some bytes la la la",
+ "some more arrive here",
+ "lots of bytes to play with",
+ "la la la",
+ "ta de da",
+ "dum"]
+ for b in L:
+ self.p.dataReceived(b)
+
+ self.assertEquals(h.bytes, ''.join(L))
+
+ def testNewlineHandling(self):
+ # Send various kinds of newlines and make sure they get translated
+ # into \n.
+ h = self.p.protocol
+
+ L = ["here is the first line\r\n",
+ "here is the second line\r\0",
+ "here is the third line\r\n",
+ "here is the last line\r\0"]
+
+ for b in L:
+ self.p.dataReceived(b)
+
+ self.assertEquals(h.bytes, L[0][:-2] + '\n' +
+ L[1][:-2] + '\r' +
+ L[2][:-2] + '\n' +
+ L[3][:-2] + '\r')
+
+ def testIACEscape(self):
+ # Send a bunch of bytes and a couple quoted \xFFs. Unquoted,
+ # \xFF is a telnet command. Quoted, one of them from each pair
+ # should be passed through to the application layer.
+ h = self.p.protocol
+
+ L = ["here are some bytes\xff\xff with an embedded IAC",
+ "and here is a test of a border escape\xff",
+ "\xff did you get that IAC?"]
+
+ for b in L:
+ self.p.dataReceived(b)
+
+ self.assertEquals(h.bytes, ''.join(L).replace('\xff\xff', '\xff'))
+
+ def _simpleCommandTest(self, cmdName):
+ # Send a single simple telnet command and make sure
+ # it gets noticed and the appropriate method gets
+ # called.
+ h = self.p.protocol
+
+ cmd = telnet.IAC + getattr(telnet, cmdName)
+ L = ["Here's some bytes, tra la la",
+ "But ono!" + cmd + " an interrupt"]
+
+ for b in L:
+ self.p.dataReceived(b)
+
+ self.assertEquals(h.calls, [cmdName])
+ self.assertEquals(h.bytes, ''.join(L).replace(cmd, ''))
+
+ def testInterrupt(self):
+ self._simpleCommandTest("IP")
+
+ def testNoOperation(self):
+ self._simpleCommandTest("NOP")
+
+ def testDataMark(self):
+ self._simpleCommandTest("DM")
+
+ def testBreak(self):
+ self._simpleCommandTest("BRK")
+
+ def testAbortOutput(self):
+ self._simpleCommandTest("AO")
+
+ def testAreYouThere(self):
+ self._simpleCommandTest("AYT")
+
+ def testEraseCharacter(self):
+ self._simpleCommandTest("EC")
+
+ def testEraseLine(self):
+ self._simpleCommandTest("EL")
+
+ def testGoAhead(self):
+ self._simpleCommandTest("GA")
+
+ def testSubnegotiation(self):
+ # Send a subnegotiation command and make sure it gets
+ # parsed and that the correct method is called.
+ h = self.p.protocol
+
+ cmd = telnet.IAC + telnet.SB + '\x12hello world' + telnet.IAC + telnet.SE
+ L = ["These are some bytes but soon" + cmd,
+ "there will be some more"]
+
+ for b in L:
+ self.p.dataReceived(b)
+
+ self.assertEquals(h.bytes, ''.join(L).replace(cmd, ''))
+ self.assertEquals(h.subcmd, list("hello world"))
+
+ def testSubnegotiationWithEmbeddedSE(self):
+ # Send a subnegotiation command with an embedded SE. Make sure
+ # that SE gets passed to the correct method.
+ h = self.p.protocol
+
+ cmd = (telnet.IAC + telnet.SB +
+ '\x12' + telnet.SE +
+ telnet.IAC + telnet.SE)
+
+ L = ["Some bytes are here" + cmd + "and here",
+ "and here"]
+
+ for b in L:
+ self.p.dataReceived(b)
+
+ self.assertEquals(h.bytes, ''.join(L).replace(cmd, ''))
+ self.assertEquals(h.subcmd, [telnet.SE])
+
+ def testBoundarySubnegotiation(self):
+ # Send a subnegotiation command. Split it at every possible byte boundary
+ # and make sure it always gets parsed and that it is passed to the correct
+ # method.
+ cmd = (telnet.IAC + telnet.SB +
+ '\x12' + telnet.SE + 'hello' +
+ telnet.IAC + telnet.SE)
+
+ for i in range(len(cmd)):
+ h = self.p.protocol = TestProtocol()
+ h.makeConnection(self.p)
+
+ a, b = cmd[:i], cmd[i:]
+ L = ["first part" + a,
+ b + "last part"]
+
+ for bytes in L:
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(h.bytes, ''.join(L).replace(cmd, ''))
+ self.assertEquals(h.subcmd, [telnet.SE] + list('hello'))
+
+ def _enabledHelper(self, o, eL=[], eR=[], dL=[], dR=[]):
+ self.assertEquals(o.enabledLocal, eL)
+ self.assertEquals(o.enabledRemote, eR)
+ self.assertEquals(o.disabledLocal, dL)
+ self.assertEquals(o.disabledRemote, dR)
+
+ def testRefuseWill(self):
+ # Try to enable an option. The server should refuse to enable it.
+ cmd = telnet.IAC + telnet.WILL + '\x12'
+
+ bytes = "surrounding bytes" + cmd + "to spice things up"
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(self.p.protocol.bytes, bytes.replace(cmd, ''))
+ self.assertEquals(self.t.value(), telnet.IAC + telnet.DONT + '\x12')
+ self._enabledHelper(self.p.protocol)
+
+ def testRefuseDo(self):
+ # Try to enable an option. The server should refuse to enable it.
+ cmd = telnet.IAC + telnet.DO + '\x12'
+
+ bytes = "surrounding bytes" + cmd + "to spice things up"
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(self.p.protocol.bytes, bytes.replace(cmd, ''))
+ self.assertEquals(self.t.value(), telnet.IAC + telnet.WONT + '\x12')
+ self._enabledHelper(self.p.protocol)
+
+ def testAcceptDo(self):
+ # Try to enable an option. The option is in our allowEnable
+ # list, so we will allow it to be enabled.
+ cmd = telnet.IAC + telnet.DO + '\x19'
+ bytes = 'padding' + cmd + 'trailer'
+
+ h = self.p.protocol
+ h.localEnableable = ('\x19',)
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(self.t.value(), telnet.IAC + telnet.WILL + '\x19')
+ self._enabledHelper(h, eL=['\x19'])
+
+ def testAcceptWill(self):
+ # Same as testAcceptDo, but reversed.
+ cmd = telnet.IAC + telnet.WILL + '\x91'
+ bytes = 'header' + cmd + 'padding'
+
+ h = self.p.protocol
+ h.remoteEnableable = ('\x91',)
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(self.t.value(), telnet.IAC + telnet.DO + '\x91')
+ self._enabledHelper(h, eR=['\x91'])
+
+ def testAcceptWont(self):
+ # Try to disable an option. The server must allow any option to
+ # be disabled at any time. Make sure it disables it and sends
+ # back an acknowledgement of this.
+ cmd = telnet.IAC + telnet.WONT + '\x29'
+
+ # Jimmy it - after these two lines, the server will be in a state
+ # such that it believes the option to have been previously enabled
+ # via normal negotiation.
+ s = self.p.getOptionState('\x29')
+ s.him.state = 'yes'
+
+ bytes = "fiddle dee" + cmd
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(self.p.protocol.bytes, bytes.replace(cmd, ''))
+ self.assertEquals(self.t.value(), telnet.IAC + telnet.DONT + '\x29')
+ self.assertEquals(s.him.state, 'no')
+ self._enabledHelper(self.p.protocol, dR=['\x29'])
+
+ def testAcceptDont(self):
+ # Try to disable an option. The server must allow any option to
+ # be disabled at any time. Make sure it disables it and sends
+ # back an acknowledgement of this.
+ cmd = telnet.IAC + telnet.DONT + '\x29'
+
+ # Jimmy it - after these two lines, the server will be in a state
+ # such that it believes the option to have beenp previously enabled
+ # via normal negotiation.
+ s = self.p.getOptionState('\x29')
+ s.us.state = 'yes'
+
+ bytes = "fiddle dum " + cmd
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(self.p.protocol.bytes, bytes.replace(cmd, ''))
+ self.assertEquals(self.t.value(), telnet.IAC + telnet.WONT + '\x29')
+ self.assertEquals(s.us.state, 'no')
+ self._enabledHelper(self.p.protocol, dL=['\x29'])
+
+ def testIgnoreWont(self):
+ # Try to disable an option. The option is already disabled. The
+ # server should send nothing in response to this.
+ cmd = telnet.IAC + telnet.WONT + '\x47'
+
+ bytes = "dum de dum" + cmd + "tra la la"
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(self.p.protocol.bytes, bytes.replace(cmd, ''))
+ self.assertEquals(self.t.value(), '')
+ self._enabledHelper(self.p.protocol)
+
+ def testIgnoreDont(self):
+ # Try to disable an option. The option is already disabled. The
+ # server should send nothing in response to this. Doing so could
+ # lead to a negotiation loop.
+ cmd = telnet.IAC + telnet.DONT + '\x47'
+
+ bytes = "dum de dum" + cmd + "tra la la"
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(self.p.protocol.bytes, bytes.replace(cmd, ''))
+ self.assertEquals(self.t.value(), '')
+ self._enabledHelper(self.p.protocol)
+
+ def testIgnoreWill(self):
+ # Try to enable an option. The option is already enabled. The
+ # server should send nothing in response to this. Doing so could
+ # lead to a negotiation loop.
+ cmd = telnet.IAC + telnet.WILL + '\x56'
+
+ # Jimmy it - after these two lines, the server will be in a state
+ # such that it believes the option to have been previously enabled
+ # via normal negotiation.
+ s = self.p.getOptionState('\x56')
+ s.him.state = 'yes'
+
+ bytes = "tra la la" + cmd + "dum de dum"
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(self.p.protocol.bytes, bytes.replace(cmd, ''))
+ self.assertEquals(self.t.value(), '')
+ self._enabledHelper(self.p.protocol)
+
+ def testIgnoreDo(self):
+ # Try to enable an option. The option is already enabled. The
+ # server should send nothing in response to this. Doing so could
+ # lead to a negotiation loop.
+ cmd = telnet.IAC + telnet.DO + '\x56'
+
+ # Jimmy it - after these two lines, the server will be in a state
+ # such that it believes the option to have been previously enabled
+ # via normal negotiation.
+ s = self.p.getOptionState('\x56')
+ s.us.state = 'yes'
+
+ bytes = "tra la la" + cmd + "dum de dum"
+ self.p.dataReceived(bytes)
+
+ self.assertEquals(self.p.protocol.bytes, bytes.replace(cmd, ''))
+ self.assertEquals(self.t.value(), '')
+ self._enabledHelper(self.p.protocol)
+
+ def testAcceptedEnableRequest(self):
+ # Try to enable an option through the user-level API. This
+ # returns a Deferred that fires when negotiation about the option
+ # finishes. Make sure it fires, make sure state gets updated
+ # properly, make sure the result indicates the option was enabled.
+ d = self.p.do('\x42')
+
+ h = self.p.protocol
+ h.remoteEnableable = ('\x42',)
+
+ self.assertEquals(self.t.value(), telnet.IAC + telnet.DO + '\x42')
+
+ self.p.dataReceived(telnet.IAC + telnet.WILL + '\x42')
+
+ d.addCallback(self.assertEquals, True)
+ d.addCallback(lambda _: self._enabledHelper(h, eR=['\x42']))
+ return d
+
+ def testRefusedEnableRequest(self):
+ # Try to enable an option through the user-level API. This
+ # returns a Deferred that fires when negotiation about the option
+ # finishes. Make sure it fires, make sure state gets updated
+ # properly, make sure the result indicates the option was enabled.
+ d = self.p.do('\x42')
+
+ self.assertEquals(self.t.value(), telnet.IAC + telnet.DO + '\x42')
+
+ self.p.dataReceived(telnet.IAC + telnet.WONT + '\x42')
+
+ d = self.assertFailure(d, telnet.OptionRefused)
+ d.addCallback(lambda _: self._enabledHelper(self.p.protocol))
+ return d
+
+ def testAcceptedDisableRequest(self):
+ # Try to disable an option through the user-level API. This
+ # returns a Deferred that fires when negotiation about the option
+ # finishes. Make sure it fires, make sure state gets updated
+ # properly, make sure the result indicates the option was enabled.
+ s = self.p.getOptionState('\x42')
+ s.him.state = 'yes'
+
+ d = self.p.dont('\x42')
+
+ self.assertEquals(self.t.value(), telnet.IAC + telnet.DONT + '\x42')
+
+ self.p.dataReceived(telnet.IAC + telnet.WONT + '\x42')
+
+ d.addCallback(self.assertEquals, True)
+ d.addCallback(lambda _: self._enabledHelper(self.p.protocol,
+ dR=['\x42']))
+ return d
+
+ def testNegotiationBlocksFurtherNegotiation(self):
+ # Try to disable an option, then immediately try to enable it, then
+ # immediately try to disable it. Ensure that the 2nd and 3rd calls
+ # fail quickly with the right exception.
+ s = self.p.getOptionState('\x24')
+ s.him.state = 'yes'
+ d2 = self.p.dont('\x24') # fires after the first line of _final
+
+ def _do(x):
+ d = self.p.do('\x24')
+ return self.assertFailure(d, telnet.AlreadyNegotiating)
+
+ def _dont(x):
+ d = self.p.dont('\x24')
+ return self.assertFailure(d, telnet.AlreadyNegotiating)
+
+ def _final(x):
+ self.p.dataReceived(telnet.IAC + telnet.WONT + '\x24')
+ # an assertion that only passes if d2 has fired
+ self._enabledHelper(self.p.protocol, dR=['\x24'])
+ # Make sure we allow this
+ self.p.protocol.remoteEnableable = ('\x24',)
+ d = self.p.do('\x24')
+ self.p.dataReceived(telnet.IAC + telnet.WILL + '\x24')
+ d.addCallback(self.assertEquals, True)
+ d.addCallback(lambda _: self._enabledHelper(self.p.protocol,
+ eR=['\x24'],
+ dR=['\x24']))
+ return d
+
+ d = _do(None)
+ d.addCallback(_dont)
+ d.addCallback(_final)
+ return d
+
+ def testSuperfluousDisableRequestRaises(self):
+ # Try to disable a disabled option. Make sure it fails properly.
+ d = self.p.dont('\xab')
+ return self.assertFailure(d, telnet.AlreadyDisabled)
+
+ def testSuperfluousEnableRequestRaises(self):
+ # Try to disable a disabled option. Make sure it fails properly.
+ s = self.p.getOptionState('\xab')
+ s.him.state = 'yes'
+ d = self.p.do('\xab')
+ return self.assertFailure(d, telnet.AlreadyEnabled)
+
+ def testLostConnectionFailsDeferreds(self):
+ d1 = self.p.do('\x12')
+ d2 = self.p.do('\x23')
+ d3 = self.p.do('\x34')
+
+ class TestException(Exception):
+ pass
+
+ self.p.connectionLost(TestException("Total failure!"))
+
+ d1 = self.assertFailure(d1, TestException)
+ d2 = self.assertFailure(d2, TestException)
+ d3 = self.assertFailure(d3, TestException)
+ return defer.gatherResults([d1, d2, d3])
+
+
+class TestTelnet(telnet.Telnet):
+ """
+ A trivial extension of the telnet protocol class useful to unit tests.
+ """
+ def __init__(self):
+ telnet.Telnet.__init__(self)
+ self.events = []
+
+
+ def applicationDataReceived(self, bytes):
+ """
+ Record the given data in C{self.events}.
+ """
+ self.events.append(('bytes', bytes))
+
+
+ def unhandledCommand(self, command, bytes):
+ """
+ Record the given command in C{self.events}.
+ """
+ self.events.append(('command', command, bytes))
+
+
+ def unhandledSubnegotiation(self, command, bytes):
+ """
+ Record the given subnegotiation command in C{self.events}.
+ """
+ self.events.append(('negotiate', command, bytes))
+
+
+
+class TelnetTests(unittest.TestCase):
+ """
+ Tests for L{telnet.Telnet}.
+
+ L{telnet.Telnet} implements the TELNET protocol (RFC 854), including option
+ and suboption negotiation, and option state tracking.
+ """
+ def setUp(self):
+ """
+ Create an unconnected L{telnet.Telnet} to be used by tests.
+ """
+ self.protocol = TestTelnet()
+
+
+ def test_enableLocal(self):
+ """
+ L{telnet.Telnet.enableLocal} should reject all options, since
+ L{telnet.Telnet} does not know how to implement any options.
+ """
+ self.assertFalse(self.protocol.enableLocal('\0'))
+
+
+ def test_enableRemote(self):
+ """
+ L{telnet.Telnet.enableRemote} should reject all options, since
+ L{telnet.Telnet} does not know how to implement any options.
+ """
+ self.assertFalse(self.protocol.enableRemote('\0'))
+
+
+ def test_disableLocal(self):
+ """
+ It is an error for L{telnet.Telnet.disableLocal} to be called, since
+ L{telnet.Telnet.enableLocal} will never allow any options to be enabled
+ locally. If a subclass overrides enableLocal, it must also override
+ disableLocal.
+ """
+ self.assertRaises(NotImplementedError, self.protocol.disableLocal, '\0')
+
+
+ def test_disableRemote(self):
+ """
+ It is an error for L{telnet.Telnet.disableRemote} to be called, since
+ L{telnet.Telnet.enableRemote} will never allow any options to be
+ enabled remotely. If a subclass overrides enableRemote, it must also
+ override disableRemote.
+ """
+ self.assertRaises(NotImplementedError, self.protocol.disableRemote, '\0')
+
+
+ def test_requestNegotiation(self):
+ """
+ L{telnet.Telnet.requestNegotiation} formats the feature byte and the
+ payload bytes into the subnegotiation format and sends them.
+
+ See RFC 855.
+ """
+ transport = proto_helpers.StringTransport()
+ self.protocol.makeConnection(transport)
+ self.protocol.requestNegotiation('\x01', '\x02\x03')
+ self.assertEqual(
+ transport.value(),
+ # IAC SB feature bytes IAC SE
+ '\xff\xfa\x01\x02\x03\xff\xf0')
+
+
+ def test_requestNegotiationEscapesIAC(self):
+ """
+ If the payload for a subnegotiation includes I{IAC}, it is escaped by
+ L{telnet.Telnet.requestNegotiation} with another I{IAC}.
+
+ See RFC 855.
+ """
+ transport = proto_helpers.StringTransport()
+ self.protocol.makeConnection(transport)
+ self.protocol.requestNegotiation('\x01', '\xff')
+ self.assertEqual(
+ transport.value(),
+ '\xff\xfa\x01\xff\xff\xff\xf0')
+
+
+ def _deliver(self, bytes, *expected):
+ """
+ Pass the given bytes to the protocol's C{dataReceived} method and
+ assert that the given events occur.
+ """
+ received = self.protocol.events = []
+ self.protocol.dataReceived(bytes)
+ self.assertEqual(received, list(expected))
+
+
+ def test_oneApplicationDataByte(self):
+ """
+ One application-data byte in the default state gets delivered right
+ away.
+ """
+ self._deliver('a', ('bytes', 'a'))
+
+
+ def test_twoApplicationDataBytes(self):
+ """
+ Two application-data bytes in the default state get delivered
+ together.
+ """
+ self._deliver('bc', ('bytes', 'bc'))
+
+
+ def test_threeApplicationDataBytes(self):
+ """
+ Three application-data bytes followed by a control byte get
+ delivered, but the control byte doesn't.
+ """
+ self._deliver('def' + telnet.IAC, ('bytes', 'def'))
+
+
+ def test_escapedControl(self):
+ """
+ IAC in the escaped state gets delivered and so does another
+ application-data byte following it.
+ """
+ self._deliver(telnet.IAC)
+ self._deliver(telnet.IAC + 'g', ('bytes', telnet.IAC + 'g'))
+
+
+ def test_carriageReturn(self):
+ """
+ A carriage return only puts the protocol into the newline state. A
+ linefeed in the newline state causes just the newline to be
+ delivered. A nul in the newline state causes a carriage return to
+ be delivered. An IAC in the newline state causes a carriage return
+ to be delivered and puts the protocol into the escaped state.
+ Anything else causes a carriage return and that thing to be
+ delivered.
+ """
+ self._deliver('\r')
+ self._deliver('\n', ('bytes', '\n'))
+ self._deliver('\r\n', ('bytes', '\n'))
+
+ self._deliver('\r')
+ self._deliver('\0', ('bytes', '\r'))
+ self._deliver('\r\0', ('bytes', '\r'))
+
+ self._deliver('\r')
+ self._deliver('a', ('bytes', '\ra'))
+ self._deliver('\ra', ('bytes', '\ra'))
+
+ self._deliver('\r')
+ self._deliver(
+ telnet.IAC + telnet.IAC + 'x', ('bytes', '\r' + telnet.IAC + 'x'))
+
+
+ def test_applicationDataBeforeSimpleCommand(self):
+ """
+ Application bytes received before a command are delivered before the
+ command is processed.
+ """
+ self._deliver(
+ 'x' + telnet.IAC + telnet.NOP,
+ ('bytes', 'x'), ('command', telnet.NOP, None))
+
+
+ def test_applicationDataBeforeCommand(self):
+ """
+ Application bytes received before a WILL/WONT/DO/DONT are delivered
+ before the command is processed.
+ """
+ self.protocol.commandMap = {}
+ self._deliver(
+ 'y' + telnet.IAC + telnet.WILL + '\x00',
+ ('bytes', 'y'), ('command', telnet.WILL, '\x00'))
+
+
+ def test_applicationDataBeforeSubnegotiation(self):
+ """
+ Application bytes received before a subnegotiation command are
+ delivered before the negotiation is processed.
+ """
+ self._deliver(
+ 'z' + telnet.IAC + telnet.SB + 'Qx' + telnet.IAC + telnet.SE,
+ ('bytes', 'z'), ('negotiate', 'Q', ['x']))
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_text.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_text.py
new file mode 100644
index 0000000000..bfc5545767
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_text.py
@@ -0,0 +1,101 @@
+# -*- test-case-name: twisted.conch.test.test_text -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.trial import unittest
+
+from twisted.conch.insults import helper, text
+
+A = text.attributes
+
+class Serialization(unittest.TestCase):
+ def setUp(self):
+ self.attrs = helper.CharacterAttribute()
+
+ def testTrivial(self):
+ self.assertEquals(
+ text.flatten(A.normal['Hello, world.'], self.attrs),
+ 'Hello, world.')
+
+ def testBold(self):
+ self.assertEquals(
+ text.flatten(A.bold['Hello, world.'], self.attrs),
+ '\x1b[1mHello, world.')
+
+ def testUnderline(self):
+ self.assertEquals(
+ text.flatten(A.underline['Hello, world.'], self.attrs),
+ '\x1b[4mHello, world.')
+
+ def testBlink(self):
+ self.assertEquals(
+ text.flatten(A.blink['Hello, world.'], self.attrs),
+ '\x1b[5mHello, world.')
+
+ def testReverseVideo(self):
+ self.assertEquals(
+ text.flatten(A.reverseVideo['Hello, world.'], self.attrs),
+ '\x1b[7mHello, world.')
+
+ def testMinus(self):
+ self.assertEquals(
+ text.flatten(
+ A.bold[A.blink['Hello', -A.bold[' world'], '.']],
+ self.attrs),
+ '\x1b[1;5mHello\x1b[0;5m world\x1b[1;5m.')
+
+ def testForeground(self):
+ self.assertEquals(
+ text.flatten(
+ A.normal[A.fg.red['Hello, '], A.fg.green['world!']],
+ self.attrs),
+ '\x1b[31mHello, \x1b[32mworld!')
+
+ def testBackground(self):
+ self.assertEquals(
+ text.flatten(
+ A.normal[A.bg.red['Hello, '], A.bg.green['world!']],
+ self.attrs),
+ '\x1b[41mHello, \x1b[42mworld!')
+
+
+class EfficiencyTestCase(unittest.TestCase):
+ todo = ("flatten() isn't quite stateful enough to avoid emitting a few extra bytes in "
+ "certain circumstances, so these tests fail. The failures take the form of "
+ "additional elements in the ;-delimited character attribute lists. For example, "
+ "\\x1b[0;31;46m might be emitted instead of \\x[46m, even if 31 has already been "
+ "activated and no conflicting attributes are set which need to be cleared.")
+
+ def setUp(self):
+ self.attrs = helper.CharacterAttribute()
+
+ def testComplexStructure(self):
+ output = A.normal[
+ A.bold[
+ A.bg.cyan[
+ A.fg.red[
+ "Foreground Red, Background Cyan, Bold",
+ A.blink[
+ "Blinking"],
+ -A.bold[
+ "Foreground Red, Background Cyan, normal"]],
+ A.fg.green[
+ "Foreground Green, Background Cyan, Bold"]]]]
+
+ self.assertEquals(
+ text.flatten(output, self.attrs),
+ "\x1b[1;31;46mForeground Red, Background Cyan, Bold"
+ "\x1b[5mBlinking"
+ "\x1b[0;31;46mForeground Red, Background Cyan, normal"
+ "\x1b[1;32;46mForeground Green, Background Cyan, Bold")
+
+ def testNesting(self):
+ self.assertEquals(
+ text.flatten(A.bold['Hello, ', A.underline['world.']], self.attrs),
+ '\x1b[1mHello, \x1b[4mworld.')
+
+ self.assertEquals(
+ text.flatten(
+ A.bold[A.reverseVideo['Hello, ', A.normal['world'], '.']],
+ self.attrs),
+ '\x1b[1;7mHello, \x1b[0mworld\x1b[1;7m.')
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_transport.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_transport.py
new file mode 100644
index 0000000000..54d73dcf46
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_transport.py
@@ -0,0 +1,1953 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for ssh/transport.py and the classes therein.
+"""
+
+try:
+ import pyasn1
+except ImportError:
+ pyasn1 = None
+
+try:
+ import Crypto.Cipher.DES3
+except ImportError:
+ Crypto = None
+
+if pyasn1 is None or Crypto is None:
+ class transport: # fictional modules to make classes work
+ class SSHTransportBase: pass
+ class SSHServerTransport: pass
+ class SSHClientTransport: pass
+ class factory:
+ class SSHFactory:
+ pass
+else:
+ from twisted.conch.ssh import transport, common, keys, factory
+ from twisted.conch.test import keydata
+
+from twisted.trial import unittest
+from twisted.internet import defer
+from twisted.protocols import loopback
+from twisted.python import randbytes
+from twisted.python.reflect import qual
+from twisted.python.hashlib import md5, sha1
+from twisted.conch.ssh import service
+from twisted.test import proto_helpers
+
+from twisted.conch.error import ConchError
+
+
+
+class MockTransportBase(transport.SSHTransportBase):
+ """
+ A base class for the client and server protocols. Stores the messages
+ it receieves instead of ignoring them.
+
+ @ivar errors: a list of tuples: (reasonCode, description)
+ @ivar unimplementeds: a list of integers: sequence number
+ @ivar debugs: a list of tuples: (alwaysDisplay, message, lang)
+ @ivar ignoreds: a list of strings: ignored data
+ """
+
+
+ def connectionMade(self):
+ """
+ Set up instance variables.
+ """
+ transport.SSHTransportBase.connectionMade(self)
+ self.errors = []
+ self.unimplementeds = []
+ self.debugs = []
+ self.ignoreds = []
+
+
+ def receiveError(self, reasonCode, description):
+ """
+ Store any errors received.
+
+ @type reasonCode: C{int}
+ @type description: C{str}
+ """
+ self.errors.append((reasonCode, description))
+
+
+ def receiveUnimplemented(self, seqnum):
+ """
+ Store any unimplemented packet messages.
+
+ @type seqnum: C{int}
+ """
+ self.unimplementeds.append(seqnum)
+
+
+ def receiveDebug(self, alwaysDisplay, message, lang):
+ """
+ Store any debug messages.
+
+ @type alwaysDisplay: C{bool}
+ @type message: C{str}
+ @type lang: C{str}
+ """
+ self.debugs.append((alwaysDisplay, message, lang))
+
+
+ def ssh_IGNORE(self, packet):
+ """
+ Store any ignored data.
+
+ @type packet: C{str}
+ """
+ self.ignoreds.append(packet)
+
+
+class MockCipher(object):
+ """
+ A mocked-up version of twisted.conch.ssh.transport.SSHCiphers.
+ """
+ outCipType = 'test'
+ encBlockSize = 6
+ inCipType = 'test'
+ decBlockSize = 6
+ inMACType = 'test'
+ outMACType = 'test'
+ verifyDigestSize = 1
+ usedEncrypt = False
+ usedDecrypt = False
+ outMAC = (None, '', '', 1)
+ inMAC = (None, '', '', 1)
+ keys = ()
+
+
+ def encrypt(self, x):
+ """
+ Called to encrypt the packet. Simply record that encryption was used
+ and return the data unchanged.
+ """
+ self.usedEncrypt = True
+ if (len(x) % self.encBlockSize) != 0:
+ raise RuntimeError("length %i modulo blocksize %i is not 0: %i" %
+ (len(x), self.encBlockSize, len(x) % self.encBlockSize))
+ return x
+
+
+ def decrypt(self, x):
+ """
+ Called to decrypt the packet. Simply record that decryption was used
+ and return the data unchanged.
+ """
+ self.usedDecrypt = True
+ if (len(x) % self.encBlockSize) != 0:
+ raise RuntimeError("length %i modulo blocksize %i is not 0: %i" %
+ (len(x), self.decBlockSize, len(x) % self.decBlockSize))
+ return x
+
+
+ def makeMAC(self, outgoingPacketSequence, payload):
+ """
+ Make a Message Authentication Code by sending the character value of
+ the outgoing packet.
+ """
+ return chr(outgoingPacketSequence)
+
+
+ def verify(self, incomingPacketSequence, packet, macData):
+ """
+ Verify the Message Authentication Code by checking that the packet
+ sequence number is the same.
+ """
+ return chr(incomingPacketSequence) == macData
+
+
+ def setKeys(self, ivOut, keyOut, ivIn, keyIn, macIn, macOut):
+ """
+ Record the keys.
+ """
+ self.keys = (ivOut, keyOut, ivIn, keyIn, macIn, macOut)
+
+
+
+class MockCompression:
+ """
+ A mocked-up compression, based on the zlib interface. Instead of
+ compressing, it reverses the data and adds a 0x66 byte to the end.
+ """
+
+
+ def compress(self, payload):
+ return payload[::-1] # reversed
+
+
+ def decompress(self, payload):
+ return payload[:-1][::-1]
+
+
+ def flush(self, kind):
+ return '\x66'
+
+
+
+class MockService(service.SSHService):
+ """
+ A mocked-up service, based on twisted.conch.ssh.service.SSHService.
+
+ @ivar started: True if this service has been started.
+ @ivar stopped: True if this service has been stopped.
+ """
+ name = "MockService"
+ started = False
+ stopped = False
+ protocolMessages = {0xff: "MSG_TEST", 71: "MSG_fiction"}
+
+
+ def logPrefix(self):
+ return "MockService"
+
+
+ def serviceStarted(self):
+ """
+ Record that the service was started.
+ """
+ self.started = True
+
+
+ def serviceStopped(self):
+ """
+ Record that the service was stopped.
+ """
+ self.stopped = True
+
+
+ def ssh_TEST(self, packet):
+ """
+ A message that this service responds to.
+ """
+ self.transport.sendPacket(0xff, packet)
+
+
+class MockFactory(factory.SSHFactory):
+ """
+ A mocked-up factory based on twisted.conch.ssh.factory.SSHFactory.
+ """
+ services = {
+ 'ssh-userauth': MockService}
+
+
+ def getPublicKeys(self):
+ """
+ Return the public keys that authenticate this server.
+ """
+ return {
+ 'ssh-rsa': keys.Key.fromString(keydata.publicRSA_openssh),
+ 'ssh-dsa': keys.Key.fromString(keydata.publicDSA_openssh)}
+
+
+ def getPrivateKeys(self):
+ """
+ Return the private keys that authenticate this server.
+ """
+ return {
+ 'ssh-rsa': keys.Key.fromString(keydata.privateRSA_openssh),
+ 'ssh-dsa': keys.Key.fromString(keydata.privateDSA_openssh)}
+
+
+ def getPrimes(self):
+ """
+ Return the Diffie-Hellman primes that can be used for the
+ diffie-hellman-group-exchange-sha1 key exchange.
+ """
+ return {
+ 1024: ((2, transport.DH_PRIME),),
+ 2048: ((3, transport.DH_PRIME),),
+ 4096: ((5, 7),)}
+
+
+
+class MockOldFactoryPublicKeys(MockFactory):
+ """
+ The old SSHFactory returned mappings from key names to strings from
+ getPublicKeys(). We return those here for testing.
+ """
+
+
+ def getPublicKeys(self):
+ """
+ We used to map key types to public key blobs as strings.
+ """
+ keys = MockFactory.getPublicKeys(self)
+ for name, key in keys.items()[:]:
+ keys[name] = key.blob()
+ return keys
+
+
+
+class MockOldFactoryPrivateKeys(MockFactory):
+ """
+ The old SSHFactory returned mappings from key names to PyCrypto key
+ objects from getPrivateKeys(). We return those here for testing.
+ """
+
+
+ def getPrivateKeys(self):
+ """
+ We used to map key types to PyCrypto key objects.
+ """
+ keys = MockFactory.getPrivateKeys(self)
+ for name, key in keys.items()[:]:
+ keys[name] = key.keyObject
+ return keys
+
+
+
+class TransportTestCase(unittest.TestCase):
+ """
+ Base class for transport test cases.
+ """
+ klass = None
+
+ if Crypto is None:
+ skip = "cannot run w/o PyCrypto"
+
+ if pyasn1 is None:
+ skip = "cannot run w/o PyASN1"
+
+
+ def setUp(self):
+ self.transport = proto_helpers.StringTransport()
+ self.proto = self.klass()
+ self.packets = []
+ def secureRandom(len):
+ """
+ Return a consistent entropy value
+ """
+ return '\x99' * len
+ self.oldSecureRandom = randbytes.secureRandom
+ randbytes.secureRandom = secureRandom
+ def stubSendPacket(messageType, payload):
+ self.packets.append((messageType, payload))
+ self.proto.makeConnection(self.transport)
+ # we just let the kex packet go into the transport
+ self.proto.sendPacket = stubSendPacket
+
+
+ def tearDown(self):
+ randbytes.secureRandom = self.oldSecureRandom
+ self.oldSecureRandom = None
+
+
+
+class BaseSSHTransportTestCase(TransportTestCase):
+ """
+ Test TransportBase. It implements the non-server/client specific
+ parts of the SSH transport protocol.
+ """
+
+ klass = MockTransportBase
+
+
+ def test_sendVersion(self):
+ """
+ Test that the first thing sent over the connection is the version
+ string.
+ """
+ # the other setup was done in the setup method
+ self.assertEquals(self.transport.value().split('\r\n', 1)[0],
+ "SSH-2.0-Twisted")
+
+
+ def test_sendPacketPlain(self):
+ """
+ Test that plain (unencrypted, uncompressed) packets are sent
+ correctly. The format is::
+ uint32 length (including type and padding length)
+ byte padding length
+ byte type
+ bytes[length-padding length-2] data
+ bytes[padding length] padding
+ """
+ proto = MockTransportBase()
+ proto.makeConnection(self.transport)
+ self.transport.clear()
+ message = ord('A')
+ payload = 'BCDEFG'
+ proto.sendPacket(message, payload)
+ value = self.transport.value()
+ self.assertEquals(value, '\x00\x00\x00\x0c\x04ABCDEFG\x99\x99\x99\x99')
+
+
+ def test_sendPacketEncrypted(self):
+ """
+ Test that packets sent while encryption is enabled are sent
+ correctly. The whole packet should be encrypted.
+ """
+ proto = MockTransportBase()
+ proto.makeConnection(self.transport)
+ proto.currentEncryptions = testCipher = MockCipher()
+ message = ord('A')
+ payload = 'BC'
+ self.transport.clear()
+ proto.sendPacket(message, payload)
+ self.assertTrue(testCipher.usedEncrypt)
+ value = self.transport.value()
+ self.assertEquals(value, '\x00\x00\x00\x08\x04ABC\x99\x99\x99\x99\x01')
+
+
+ def test_sendPacketCompressed(self):
+ """
+ Test that packets sent while compression is enabled are sent
+ correctly. The packet type and data should be encrypted.
+ """
+ proto = MockTransportBase()
+ proto.makeConnection(self.transport)
+ proto.outgoingCompression = MockCompression()
+ self.transport.clear()
+ proto.sendPacket(ord('A'), 'B')
+ value = self.transport.value()
+ self.assertEquals(
+ value,
+ '\x00\x00\x00\x0c\x08BA\x66\x99\x99\x99\x99\x99\x99\x99\x99')
+
+
+ def test_sendPacketBoth(self):
+ """
+ Test that packets sent while compression and encryption are
+ enabled are sent correctly. The packet type and data should be
+ compressed and then the whole packet should be encrypted.
+ """
+ proto = MockTransportBase()
+ proto.makeConnection(self.transport)
+ proto.currentEncryptions = testCipher = MockCipher()
+ proto.outgoingCompression = MockCompression()
+ message = ord('A')
+ payload = 'BC'
+ self.transport.clear()
+ proto.sendPacket(message, payload)
+ value = self.transport.value()
+ self.assertEquals(
+ value,
+ '\x00\x00\x00\x0e\x09CBA\x66\x99\x99\x99\x99\x99\x99\x99\x99\x99'
+ '\x01')
+
+
+ def test_getPacketPlain(self):
+ """
+ Test that packets are retrieved correctly out of the buffer when
+ no encryption is enabled.
+ """
+ proto = MockTransportBase()
+ proto.makeConnection(self.transport)
+ self.transport.clear()
+ proto.sendPacket(ord('A'), 'BC')
+ proto.buf = self.transport.value() + 'extra'
+ self.assertEquals(proto.getPacket(), 'ABC')
+ self.assertEquals(proto.buf, 'extra')
+
+
+ def test_getPacketEncrypted(self):
+ """
+ Test that encrypted packets are retrieved correctly.
+ See test_sendPacketEncrypted.
+ """
+ proto = MockTransportBase()
+ proto.sendKexInit = lambda: None # don't send packets
+ proto.makeConnection(self.transport)
+ self.transport.clear()
+ proto.currentEncryptions = testCipher = MockCipher()
+ proto.sendPacket(ord('A'), 'BCD')
+ value = self.transport.value()
+ proto.buf = value[:MockCipher.decBlockSize]
+ self.assertEquals(proto.getPacket(), None)
+ self.assertTrue(testCipher.usedDecrypt)
+ self.assertEquals(proto.first, '\x00\x00\x00\x0e\x09A')
+ proto.buf += value[MockCipher.decBlockSize:]
+ self.assertEquals(proto.getPacket(), 'ABCD')
+ self.assertEquals(proto.buf, '')
+
+
+ def test_getPacketCompressed(self):
+ """
+ Test that compressed packets are retrieved correctly. See
+ test_sendPacketCompressed.
+ """
+ proto = MockTransportBase()
+ proto.makeConnection(self.transport)
+ self.transport.clear()
+ proto.outgoingCompression = MockCompression()
+ proto.incomingCompression = proto.outgoingCompression
+ proto.sendPacket(ord('A'), 'BCD')
+ proto.buf = self.transport.value()
+ self.assertEquals(proto.getPacket(), 'ABCD')
+
+
+ def test_getPacketBoth(self):
+ """
+ Test that compressed and encrypted packets are retrieved correctly.
+ See test_sendPacketBoth.
+ """
+ proto = MockTransportBase()
+ proto.sendKexInit = lambda: None
+ proto.makeConnection(self.transport)
+ self.transport.clear()
+ proto.currentEncryptions = testCipher = MockCipher()
+ proto.outgoingCompression = MockCompression()
+ proto.incomingCompression = proto.outgoingCompression
+ proto.sendPacket(ord('A'), 'BCDEFG')
+ proto.buf = self.transport.value()
+ self.assertEquals(proto.getPacket(), 'ABCDEFG')
+
+
+ def test_ciphersAreValid(self):
+ """
+ Test that all the supportedCiphers are valid.
+ """
+ ciphers = transport.SSHCiphers('A', 'B', 'C', 'D')
+ iv = key = '\x00' * 16
+ for cipName in self.proto.supportedCiphers:
+ self.assertTrue(ciphers._getCipher(cipName, iv, key))
+
+
+ def test_sendKexInit(self):
+ """
+ Test that the KEXINIT (key exchange initiation) message is sent
+ correctly. Payload::
+ bytes[16] cookie
+ string key exchange algorithms
+ string public key algorithms
+ string outgoing ciphers
+ string incoming ciphers
+ string outgoing MACs
+ string incoming MACs
+ string outgoing compressions
+ string incoming compressions
+ bool first packet follows
+ uint32 0
+ """
+ value = self.transport.value().split('\r\n', 1)[1]
+ self.proto.buf = value
+ packet = self.proto.getPacket()
+ self.assertEquals(packet[0], chr(transport.MSG_KEXINIT))
+ self.assertEquals(packet[1:17], '\x99' * 16)
+ (kex, pubkeys, ciphers1, ciphers2, macs1, macs2, compressions1,
+ compressions2, languages1, languages2,
+ buf) = common.getNS(packet[17:], 10)
+
+ self.assertEquals(kex, ','.join(self.proto.supportedKeyExchanges))
+ self.assertEquals(pubkeys, ','.join(self.proto.supportedPublicKeys))
+ self.assertEquals(ciphers1, ','.join(self.proto.supportedCiphers))
+ self.assertEquals(ciphers2, ','.join(self.proto.supportedCiphers))
+ self.assertEquals(macs1, ','.join(self.proto.supportedMACs))
+ self.assertEquals(macs2, ','.join(self.proto.supportedMACs))
+ self.assertEquals(compressions1,
+ ','.join(self.proto.supportedCompressions))
+ self.assertEquals(compressions2,
+ ','.join(self.proto.supportedCompressions))
+ self.assertEquals(languages1, ','.join(self.proto.supportedLanguages))
+ self.assertEquals(languages2, ','.join(self.proto.supportedLanguages))
+ self.assertEquals(buf, '\x00' * 5)
+
+
+ def test_sendDebug(self):
+ """
+ Test that debug messages are sent correctly. Payload::
+ bool always display
+ string debug message
+ string language
+ """
+ self.proto.sendDebug("test", True, 'en')
+ self.assertEquals(
+ self.packets,
+ [(transport.MSG_DEBUG,
+ "\x01\x00\x00\x00\x04test\x00\x00\x00\x02en")])
+
+
+ def test_receiveDebug(self):
+ """
+ Test that debug messages are received correctly. See test_sendDebug.
+ """
+ self.proto.dispatchMessage(
+ transport.MSG_DEBUG,
+ '\x01\x00\x00\x00\x04test\x00\x00\x00\x02en')
+ self.assertEquals(self.proto.debugs, [(True, 'test', 'en')])
+
+
+ def test_sendIgnore(self):
+ """
+ Test that ignored messages are sent correctly. Payload::
+ string ignored data
+ """
+ self.proto.sendIgnore("test")
+ self.assertEquals(
+ self.packets, [(transport.MSG_IGNORE,
+ '\x00\x00\x00\x04test')])
+
+
+ def test_receiveIgnore(self):
+ """
+ Test that ignored messages are received correctly. See
+ test_sendIgnore.
+ """
+ self.proto.dispatchMessage(transport.MSG_IGNORE, 'test')
+ self.assertEquals(self.proto.ignoreds, ['test'])
+
+
+ def test_sendUnimplemented(self):
+ """
+ Test that unimplemented messages are sent correctly. Payload::
+ uint32 sequence number
+ """
+ self.proto.sendUnimplemented()
+ self.assertEquals(
+ self.packets, [(transport.MSG_UNIMPLEMENTED,
+ '\x00\x00\x00\x00')])
+
+
+ def test_receiveUnimplemented(self):
+ """
+ Test that unimplemented messages are received correctly. See
+ test_sendUnimplemented.
+ """
+ self.proto.dispatchMessage(transport.MSG_UNIMPLEMENTED,
+ '\x00\x00\x00\xff')
+ self.assertEquals(self.proto.unimplementeds, [255])
+
+
+ def test_sendDisconnect(self):
+ """
+ Test that disconnection messages are sent correctly. Payload::
+ uint32 reason code
+ string reason description
+ string language
+ """
+ disconnected = [False]
+ def stubLoseConnection():
+ disconnected[0] = True
+ self.transport.loseConnection = stubLoseConnection
+ self.proto.sendDisconnect(0xff, "test")
+ self.assertEquals(
+ self.packets,
+ [(transport.MSG_DISCONNECT,
+ "\x00\x00\x00\xff\x00\x00\x00\x04test\x00\x00\x00\x00")])
+ self.assertTrue(disconnected[0])
+
+
+ def test_receiveDisconnect(self):
+ """
+ Test that disconnection messages are received correctly. See
+ test_sendDisconnect.
+ """
+ disconnected = [False]
+ def stubLoseConnection():
+ disconnected[0] = True
+ self.transport.loseConnection = stubLoseConnection
+ self.proto.dispatchMessage(transport.MSG_DISCONNECT,
+ '\x00\x00\x00\xff\x00\x00\x00\x04test')
+ self.assertEquals(self.proto.errors, [(255, 'test')])
+ self.assertTrue(disconnected[0])
+
+
+ def test_dataReceived(self):
+ """
+ Test that dataReceived parses packets and dispatches them to
+ ssh_* methods.
+ """
+ kexInit = [False]
+ def stubKEXINIT(packet):
+ kexInit[0] = True
+ self.proto.ssh_KEXINIT = stubKEXINIT
+ self.proto.dataReceived(self.transport.value())
+ self.assertTrue(self.proto.gotVersion)
+ self.assertEquals(self.proto.ourVersionString,
+ self.proto.otherVersionString)
+ self.assertTrue(kexInit[0])
+
+
+ def test_service(self):
+ """
+ Test that the transport can set the running service and dispatches
+ packets to the service's packetReceived method.
+ """
+ service = MockService()
+ self.proto.setService(service)
+ self.assertEquals(self.proto.service, service)
+ self.assertTrue(service.started)
+ self.proto.dispatchMessage(0xff, "test")
+ self.assertEquals(self.packets, [(0xff, "test")])
+
+ service2 = MockService()
+ self.proto.setService(service2)
+ self.assertTrue(service2.started)
+ self.assertTrue(service.stopped)
+
+ self.proto.connectionLost(None)
+ self.assertTrue(service2.stopped)
+
+
+ def test_avatar(self):
+ """
+ Test that the transport notifies the avatar of disconnections.
+ """
+ disconnected = [False]
+ def logout():
+ disconnected[0] = True
+ self.proto.logoutFunction = logout
+ self.proto.avatar = True
+
+ self.proto.connectionLost(None)
+ self.assertTrue(disconnected[0])
+
+
+ def test_isEncrypted(self):
+ """
+ Test that the transport accurately reflects its encrypted status.
+ """
+ self.assertFalse(self.proto.isEncrypted('in'))
+ self.assertFalse(self.proto.isEncrypted('out'))
+ self.assertFalse(self.proto.isEncrypted('both'))
+ self.proto.currentEncryptions = MockCipher()
+ self.assertTrue(self.proto.isEncrypted('in'))
+ self.assertTrue(self.proto.isEncrypted('out'))
+ self.assertTrue(self.proto.isEncrypted('both'))
+ self.proto.currentEncryptions = transport.SSHCiphers('none', 'none',
+ 'none', 'none')
+ self.assertFalse(self.proto.isEncrypted('in'))
+ self.assertFalse(self.proto.isEncrypted('out'))
+ self.assertFalse(self.proto.isEncrypted('both'))
+
+ self.assertRaises(TypeError, self.proto.isEncrypted, 'bad')
+
+
+ def test_isVerified(self):
+ """
+ Test that the transport accurately reflects its verified status.
+ """
+ self.assertFalse(self.proto.isVerified('in'))
+ self.assertFalse(self.proto.isVerified('out'))
+ self.assertFalse(self.proto.isVerified('both'))
+ self.proto.currentEncryptions = MockCipher()
+ self.assertTrue(self.proto.isVerified('in'))
+ self.assertTrue(self.proto.isVerified('out'))
+ self.assertTrue(self.proto.isVerified('both'))
+ self.proto.currentEncryptions = transport.SSHCiphers('none', 'none',
+ 'none', 'none')
+ self.assertFalse(self.proto.isVerified('in'))
+ self.assertFalse(self.proto.isVerified('out'))
+ self.assertFalse(self.proto.isVerified('both'))
+
+ self.assertRaises(TypeError, self.proto.isVerified, 'bad')
+
+
+ def test_loseConnection(self):
+ """
+ Test that loseConnection sends a disconnect message and closes the
+ connection.
+ """
+ disconnected = [False]
+ def stubLoseConnection():
+ disconnected[0] = True
+ self.transport.loseConnection = stubLoseConnection
+ self.proto.loseConnection()
+ self.assertEquals(self.packets[0][0], transport.MSG_DISCONNECT)
+ self.assertEquals(self.packets[0][1][3],
+ chr(transport.DISCONNECT_CONNECTION_LOST))
+
+
+ def test_badVersion(self):
+ """
+ Test that the transport disconnects when it receives a bad version.
+ """
+ def testBad(version):
+ self.packets = []
+ self.proto.gotVersion = False
+ disconnected = [False]
+ def stubLoseConnection():
+ disconnected[0] = True
+ self.transport.loseConnection = stubLoseConnection
+ for c in version + '\r\n':
+ self.proto.dataReceived(c)
+ self.assertTrue(disconnected[0])
+ self.assertEquals(self.packets[0][0], transport.MSG_DISCONNECT)
+ self.assertEquals(
+ self.packets[0][1][3],
+ chr(transport.DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED))
+ testBad('SSH-1.5-OpenSSH')
+ testBad('SSH-3.0-Twisted')
+ testBad('GET / HTTP/1.1')
+
+
+ def test_dataBeforeVersion(self):
+ """
+ Test that the transport ignores data sent before the version string.
+ """
+ proto = MockTransportBase()
+ proto.makeConnection(proto_helpers.StringTransport())
+ data = ("""here's some stuff beforehand
+here's some other stuff
+""" + proto.ourVersionString + "\r\n")
+ [proto.dataReceived(c) for c in data]
+ self.assertTrue(proto.gotVersion)
+ self.assertEquals(proto.otherVersionString, proto.ourVersionString)
+
+
+ def test_compatabilityVersion(self):
+ """
+ Test that the transport treats the compatbility version (1.99)
+ as equivalent to version 2.0.
+ """
+ proto = MockTransportBase()
+ proto.makeConnection(proto_helpers.StringTransport())
+ proto.dataReceived("SSH-1.99-OpenSSH\n")
+ self.assertTrue(proto.gotVersion)
+ self.assertEquals(proto.otherVersionString, "SSH-1.99-OpenSSH")
+
+
+ def test_badPackets(self):
+ """
+ Test that the transport disconnects with an error when it receives
+ bad packets.
+ """
+ def testBad(packet, error=transport.DISCONNECT_PROTOCOL_ERROR):
+ self.packets = []
+ self.proto.buf = packet
+ self.assertEquals(self.proto.getPacket(), None)
+ self.assertEquals(len(self.packets), 1)
+ self.assertEquals(self.packets[0][0], transport.MSG_DISCONNECT)
+ self.assertEquals(self.packets[0][1][3], chr(error))
+
+ testBad('\xff' * 8) # big packet
+ testBad('\x00\x00\x00\x05\x00BCDE') # length not modulo blocksize
+ oldEncryptions = self.proto.currentEncryptions
+ self.proto.currentEncryptions = MockCipher()
+ testBad('\x00\x00\x00\x08\x06AB123456', # bad MAC
+ transport.DISCONNECT_MAC_ERROR)
+ self.proto.currentEncryptions.decrypt = lambda x: x[:-1]
+ testBad('\x00\x00\x00\x08\x06BCDEFGHIJK') # bad decryption
+ self.proto.currentEncryptions = oldEncryptions
+ self.proto.incomingCompression = MockCompression()
+ def stubDecompress(payload):
+ raise Exception('bad compression')
+ self.proto.incomingCompression.decompress = stubDecompress
+ testBad('\x00\x00\x00\x04\x00BCDE', # bad decompression
+ transport.DISCONNECT_COMPRESSION_ERROR)
+ self.flushLoggedErrors()
+
+
+ def test_unimplementedPackets(self):
+ """
+ Test that unimplemented packet types cause MSG_UNIMPLEMENTED packets
+ to be sent.
+ """
+ seqnum = self.proto.incomingPacketSequence
+ def checkUnimplemented(seqnum=seqnum):
+ self.assertEquals(self.packets[0][0],
+ transport.MSG_UNIMPLEMENTED)
+ self.assertEquals(self.packets[0][1][3], chr(seqnum))
+ self.proto.packets = []
+ seqnum += 1
+
+ self.proto.dispatchMessage(40, '')
+ checkUnimplemented()
+ transport.messages[41] = 'MSG_fiction'
+ self.proto.dispatchMessage(41, '')
+ checkUnimplemented()
+ self.proto.dispatchMessage(60, '')
+ checkUnimplemented()
+ self.proto.setService(MockService())
+ self.proto.dispatchMessage(70, '')
+ checkUnimplemented()
+ self.proto.dispatchMessage(71, '')
+ checkUnimplemented()
+
+
+ def test_getKey(self):
+ """
+ Test that _getKey generates the correct keys.
+ """
+ self.proto.sessionID = 'EF'
+
+ k1 = sha1('AB' + 'CD' + 'K' + self.proto.sessionID).digest()
+ k2 = sha1('ABCD' + k1).digest()
+ self.assertEquals(self.proto._getKey('K', 'AB', 'CD'), k1 + k2)
+
+
+ def test_multipleClasses(self):
+ """
+ Test that multiple instances have distinct states.
+ """
+ proto = self.proto
+ proto.dataReceived(self.transport.value())
+ proto.currentEncryptions = MockCipher()
+ proto.outgoingCompression = MockCompression()
+ proto.incomingCompression = MockCompression()
+ proto.setService(MockService())
+ proto2 = MockTransportBase()
+ proto2.makeConnection(proto_helpers.StringTransport())
+ proto2.sendIgnore('')
+ self.failIfEquals(proto.gotVersion, proto2.gotVersion)
+ self.failIfEquals(proto.transport, proto2.transport)
+ self.failIfEquals(proto.outgoingPacketSequence,
+ proto2.outgoingPacketSequence)
+ self.failIfEquals(proto.incomingPacketSequence,
+ proto2.incomingPacketSequence)
+ self.failIfEquals(proto.currentEncryptions,
+ proto2.currentEncryptions)
+ self.failIfEquals(proto.service, proto2.service)
+
+
+
+class ServerAndClientSSHTransportBaseCase:
+ """
+ Tests that need to be run on both the server and the client.
+ """
+
+
+ def checkDisconnected(self, kind=None):
+ """
+ Helper function to check if the transport disconnected.
+ """
+ if kind is None:
+ kind = transport.DISCONNECT_PROTOCOL_ERROR
+ self.assertEquals(self.packets[-1][0], transport.MSG_DISCONNECT)
+ self.assertEquals(self.packets[-1][1][3], chr(kind))
+
+
+ def connectModifiedProtocol(self, protoModification,
+ kind=None):
+ """
+ Helper function to connect a modified protocol to the test protocol
+ and test for disconnection.
+ """
+ if kind is None:
+ kind = transport.DISCONNECT_KEY_EXCHANGE_FAILED
+ proto2 = self.klass()
+ protoModification(proto2)
+ proto2.makeConnection(proto_helpers.StringTransport())
+ self.proto.dataReceived(proto2.transport.value())
+ if kind:
+ self.checkDisconnected(kind)
+ return proto2
+
+
+ def test_disconnectIfCantMatchKex(self):
+ """
+ Test that the transport disconnects if it can't match the key
+ exchange
+ """
+ def blankKeyExchanges(proto2):
+ proto2.supportedKeyExchanges = []
+ self.connectModifiedProtocol(blankKeyExchanges)
+
+
+ def test_disconnectIfCantMatchKeyAlg(self):
+ """
+ Like test_disconnectIfCantMatchKex, but for the key algorithm.
+ """
+ def blankPublicKeys(proto2):
+ proto2.supportedPublicKeys = []
+ self.connectModifiedProtocol(blankPublicKeys)
+
+
+ def test_disconnectIfCantMatchCompression(self):
+ """
+ Like test_disconnectIfCantMatchKex, but for the compression.
+ """
+ def blankCompressions(proto2):
+ proto2.supportedCompressions = []
+ self.connectModifiedProtocol(blankCompressions)
+
+
+ def test_disconnectIfCantMatchCipher(self):
+ """
+ Like test_disconnectIfCantMatchKex, but for the encryption.
+ """
+ def blankCiphers(proto2):
+ proto2.supportedCiphers = []
+ self.connectModifiedProtocol(blankCiphers)
+
+
+ def test_disconnectIfCantMatchMAC(self):
+ """
+ Like test_disconnectIfCantMatchKex, but for the MAC.
+ """
+ def blankMACs(proto2):
+ proto2.supportedMACs = []
+ self.connectModifiedProtocol(blankMACs)
+
+
+
+class ServerSSHTransportTestCase(ServerAndClientSSHTransportBaseCase,
+ TransportTestCase):
+ """
+ Tests for the SSHServerTransport.
+ """
+
+ klass = transport.SSHServerTransport
+
+
+ def setUp(self):
+ TransportTestCase.setUp(self)
+ self.proto.factory = MockFactory()
+ self.proto.factory.startFactory()
+
+
+ def tearDown(self):
+ TransportTestCase.tearDown(self)
+ self.proto.factory.stopFactory()
+ del self.proto.factory
+
+
+ def test_KEXINIT(self):
+ """
+ Test that receiving a KEXINIT packet sets up the correct values on the
+ server.
+ """
+ self.proto.dataReceived( 'SSH-2.0-Twisted\r\n\x00\x00\x01\xd4\t\x14'
+ '\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99'
+ '\x99\x00\x00\x00=diffie-hellman-group1-sha1,diffie-hellman-g'
+ 'roup-exchange-sha1\x00\x00\x00\x0fssh-dss,ssh-rsa\x00\x00\x00'
+ '\x85aes128-ctr,aes128-cbc,aes192-ctr,aes192-cbc,aes256-ctr,ae'
+ 's256-cbc,cast128-ctr,cast128-cbc,blowfish-ctr,blowfish-cbc,3d'
+ 'es-ctr,3des-cbc\x00\x00\x00\x85aes128-ctr,aes128-cbc,aes192-c'
+ 'tr,aes192-cbc,aes256-ctr,aes256-cbc,cast128-ctr,cast128-cbc,b'
+ 'lowfish-ctr,blowfish-cbc,3des-ctr,3des-cbc\x00\x00\x00\x12hma'
+ 'c-md5,hmac-sha1\x00\x00\x00\x12hmac-md5,hmac-sha1\x00\x00\x00'
+ '\tnone,zlib\x00\x00\x00\tnone,zlib\x00\x00\x00\x00\x00\x00'
+ '\x00\x00\x00\x00\x00\x00\x00\x99\x99\x99\x99\x99\x99\x99\x99'
+ '\x99')
+ self.assertEquals(self.proto.kexAlg,
+ 'diffie-hellman-group1-sha1')
+ self.assertEquals(self.proto.keyAlg,
+ 'ssh-dss')
+ self.assertEquals(self.proto.outgoingCompressionType,
+ 'none')
+ self.assertEquals(self.proto.incomingCompressionType,
+ 'none')
+ ne = self.proto.nextEncryptions
+ self.assertEquals(ne.outCipType, 'aes128-ctr')
+ self.assertEquals(ne.inCipType, 'aes128-ctr')
+ self.assertEquals(ne.outMACType, 'hmac-md5')
+ self.assertEquals(ne.inMACType, 'hmac-md5')
+
+
+ def test_ignoreGuessPacketKex(self):
+ """
+ The client is allowed to send a guessed key exchange packet
+ after it sends the KEXINIT packet. However, if the key exchanges
+ do not match, that guess packet must be ignored. This tests that
+ the packet is ignored in the case of the key exchange method not
+ matching.
+ """
+ kexInitPacket = '\x00' * 16 + (
+ ''.join([common.NS(x) for x in
+ [','.join(y) for y in
+ [self.proto.supportedKeyExchanges[::-1],
+ self.proto.supportedPublicKeys,
+ self.proto.supportedCiphers,
+ self.proto.supportedCiphers,
+ self.proto.supportedMACs,
+ self.proto.supportedMACs,
+ self.proto.supportedCompressions,
+ self.proto.supportedCompressions,
+ self.proto.supportedLanguages,
+ self.proto.supportedLanguages]]])) + (
+ '\xff\x00\x00\x00\x00')
+ self.proto.ssh_KEXINIT(kexInitPacket)
+ self.assertTrue(self.proto.ignoreNextPacket)
+ self.proto.ssh_DEBUG("\x01\x00\x00\x00\x04test\x00\x00\x00\x00")
+ self.assertTrue(self.proto.ignoreNextPacket)
+
+
+ self.proto.ssh_KEX_DH_GEX_REQUEST_OLD('\x00\x00\x08\x00')
+ self.assertFalse(self.proto.ignoreNextPacket)
+ self.assertEquals(self.packets, [])
+ self.proto.ignoreNextPacket = True
+
+ self.proto.ssh_KEX_DH_GEX_REQUEST('\x00\x00\x08\x00' * 3)
+ self.assertFalse(self.proto.ignoreNextPacket)
+ self.assertEquals(self.packets, [])
+
+
+ def test_ignoreGuessPacketKey(self):
+ """
+ Like test_ignoreGuessPacketKex, but for an incorrectly guessed
+ public key format.
+ """
+ kexInitPacket = '\x00' * 16 + (
+ ''.join([common.NS(x) for x in
+ [','.join(y) for y in
+ [self.proto.supportedKeyExchanges,
+ self.proto.supportedPublicKeys[::-1],
+ self.proto.supportedCiphers,
+ self.proto.supportedCiphers,
+ self.proto.supportedMACs,
+ self.proto.supportedMACs,
+ self.proto.supportedCompressions,
+ self.proto.supportedCompressions,
+ self.proto.supportedLanguages,
+ self.proto.supportedLanguages]]])) + (
+ '\xff\x00\x00\x00\x00')
+ self.proto.ssh_KEXINIT(kexInitPacket)
+ self.assertTrue(self.proto.ignoreNextPacket)
+ self.proto.ssh_DEBUG("\x01\x00\x00\x00\x04test\x00\x00\x00\x00")
+ self.assertTrue(self.proto.ignoreNextPacket)
+
+ self.proto.ssh_KEX_DH_GEX_REQUEST_OLD('\x00\x00\x08\x00')
+ self.assertFalse(self.proto.ignoreNextPacket)
+ self.assertEquals(self.packets, [])
+ self.proto.ignoreNextPacket = True
+
+ self.proto.ssh_KEX_DH_GEX_REQUEST('\x00\x00\x08\x00' * 3)
+ self.assertFalse(self.proto.ignoreNextPacket)
+ self.assertEquals(self.packets, [])
+
+
+ def test_KEXDH_INIT(self):
+ """
+ Test that the KEXDH_INIT packet causes the server to send a
+ KEXDH_REPLY with the server's public key and a signature.
+ """
+ self.proto.supportedKeyExchanges = ['diffie-hellman-group1-sha1']
+ self.proto.supportedPublicKeys = ['ssh-rsa']
+ self.proto.dataReceived(self.transport.value())
+ e = pow(transport.DH_GENERATOR, 5000,
+ transport.DH_PRIME)
+
+ self.proto.ssh_KEX_DH_GEX_REQUEST_OLD(common.MP(e))
+ y = common.getMP('\x00\x00\x00\x40' + '\x99' * 64)[0]
+ f = common._MPpow(transport.DH_GENERATOR, y, transport.DH_PRIME)
+ sharedSecret = common._MPpow(e, y, transport.DH_PRIME)
+
+ h = sha1()
+ h.update(common.NS(self.proto.ourVersionString) * 2)
+ h.update(common.NS(self.proto.ourKexInitPayload) * 2)
+ h.update(common.NS(self.proto.factory.publicKeys['ssh-rsa'].blob()))
+ h.update(common.MP(e))
+ h.update(f)
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+
+ signature = self.proto.factory.privateKeys['ssh-rsa'].sign(
+ exchangeHash)
+
+ self.assertEquals(
+ self.packets,
+ [(transport.MSG_KEXDH_REPLY,
+ common.NS(self.proto.factory.publicKeys['ssh-rsa'].blob())
+ + f + common.NS(signature)),
+ (transport.MSG_NEWKEYS, '')])
+
+
+ def test_KEX_DH_GEX_REQUEST_OLD(self):
+ """
+ Test that the KEX_DH_GEX_REQUEST_OLD message causes the server
+ to reply with a KEX_DH_GEX_GROUP message with the correct
+ Diffie-Hellman group.
+ """
+ self.proto.supportedKeyExchanges = [
+ 'diffie-hellman-group-exchange-sha1']
+ self.proto.supportedPublicKeys = ['ssh-rsa']
+ self.proto.dataReceived(self.transport.value())
+ self.proto.ssh_KEX_DH_GEX_REQUEST_OLD('\x00\x00\x04\x00')
+ self.assertEquals(
+ self.packets,
+ [(transport.MSG_KEX_DH_GEX_GROUP,
+ common.MP(transport.DH_PRIME) + '\x00\x00\x00\x01\x02')])
+ self.assertEquals(self.proto.g, 2)
+ self.assertEquals(self.proto.p, transport.DH_PRIME)
+
+
+ def test_KEX_DH_GEX_REQUEST_OLD_badKexAlg(self):
+ """
+ Test that if the server recieves a KEX_DH_GEX_REQUEST_OLD message
+ and the key exchange algorithm is not 'diffie-hellman-group1-sha1' or
+ 'diffie-hellman-group-exchange-sha1', we raise a ConchError.
+ """
+ self.proto.kexAlg = None
+ self.assertRaises(ConchError, self.proto.ssh_KEX_DH_GEX_REQUEST_OLD,
+ None)
+
+
+ def test_KEX_DH_GEX_REQUEST(self):
+ """
+ Test that the KEX_DH_GEX_REQUEST message causes the server to reply
+ with a KEX_DH_GEX_GROUP message with the correct Diffie-Hellman
+ group.
+ """
+ self.proto.supportedKeyExchanges = [
+ 'diffie-hellman-group-exchange-sha1']
+ self.proto.supportedPublicKeys = ['ssh-rsa']
+ self.proto.dataReceived(self.transport.value())
+ self.proto.ssh_KEX_DH_GEX_REQUEST('\x00\x00\x04\x00\x00\x00\x08\x00' +
+ '\x00\x00\x0c\x00')
+ self.assertEquals(
+ self.packets,
+ [(transport.MSG_KEX_DH_GEX_GROUP,
+ common.MP(transport.DH_PRIME) + '\x00\x00\x00\x01\x03')])
+ self.assertEquals(self.proto.g, 3)
+ self.assertEquals(self.proto.p, transport.DH_PRIME)
+
+
+ def test_KEX_DH_GEX_INIT_after_REQUEST(self):
+ """
+ Test that the KEX_DH_GEX_INIT message after the client sends
+ KEX_DH_GEX_REQUEST causes the server to send a KEX_DH_GEX_INIT message
+ with a public key and signature.
+ """
+ self.test_KEX_DH_GEX_REQUEST()
+ e = pow(self.proto.g, 3, self.proto.p)
+ y = common.getMP('\x00\x00\x00\x80' + '\x99' * 128)[0]
+ f = common._MPpow(self.proto.g, y, self.proto.p)
+ sharedSecret = common._MPpow(e, y, self.proto.p)
+ h = sha1()
+ h.update(common.NS(self.proto.ourVersionString) * 2)
+ h.update(common.NS(self.proto.ourKexInitPayload) * 2)
+ h.update(common.NS(self.proto.factory.publicKeys['ssh-rsa'].blob()))
+ h.update('\x00\x00\x04\x00\x00\x00\x08\x00\x00\x00\x0c\x00')
+ h.update(common.MP(self.proto.p))
+ h.update(common.MP(self.proto.g))
+ h.update(common.MP(e))
+ h.update(f)
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+ self.proto.ssh_KEX_DH_GEX_INIT(common.MP(e))
+ self.assertEquals(
+ self.packets[1],
+ (transport.MSG_KEX_DH_GEX_REPLY,
+ common.NS(self.proto.factory.publicKeys['ssh-rsa'].blob()) +
+ f + common.NS(self.proto.factory.privateKeys['ssh-rsa'].sign(
+ exchangeHash))))
+
+
+ def test_KEX_DH_GEX_INIT_after_REQUEST_OLD(self):
+ """
+ Test that the KEX_DH_GEX_INIT message after the client sends
+ KEX_DH_GEX_REQUEST_OLD causes the server to sent a KEX_DH_GEX_INIT
+ message with a public key and signature.
+ """
+ self.test_KEX_DH_GEX_REQUEST_OLD()
+ e = pow(self.proto.g, 3, self.proto.p)
+ y = common.getMP('\x00\x00\x00\x80' + '\x99' * 128)[0]
+ f = common._MPpow(self.proto.g, y, self.proto.p)
+ sharedSecret = common._MPpow(e, y, self.proto.p)
+ h = sha1()
+ h.update(common.NS(self.proto.ourVersionString) * 2)
+ h.update(common.NS(self.proto.ourKexInitPayload) * 2)
+ h.update(common.NS(self.proto.factory.publicKeys['ssh-rsa'].blob()))
+ h.update('\x00\x00\x04\x00')
+ h.update(common.MP(self.proto.p))
+ h.update(common.MP(self.proto.g))
+ h.update(common.MP(e))
+ h.update(f)
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+ self.proto.ssh_KEX_DH_GEX_INIT(common.MP(e))
+ self.assertEquals(
+ self.packets[1:],
+ [(transport.MSG_KEX_DH_GEX_REPLY,
+ common.NS(self.proto.factory.publicKeys['ssh-rsa'].blob()) +
+ f + common.NS(self.proto.factory.privateKeys['ssh-rsa'].sign(
+ exchangeHash))),
+ (transport.MSG_NEWKEYS, '')])
+
+
+ def test_keySetup(self):
+ """
+ Test that _keySetup sets up the next encryption keys.
+ """
+ self.proto.nextEncryptions = MockCipher()
+ self.proto._keySetup('AB', 'CD')
+ self.assertEquals(self.proto.sessionID, 'CD')
+ self.proto._keySetup('AB', 'EF')
+ self.assertEquals(self.proto.sessionID, 'CD')
+ self.assertEquals(self.packets[-1], (transport.MSG_NEWKEYS, ''))
+ newKeys = [self.proto._getKey(c, 'AB', 'EF') for c in 'ABCDEF']
+ self.assertEquals(
+ self.proto.nextEncryptions.keys,
+ (newKeys[1], newKeys[3], newKeys[0], newKeys[2], newKeys[5],
+ newKeys[4]))
+
+
+ def test_NEWKEYS(self):
+ """
+ Test that NEWKEYS transitions the keys in nextEncryptions to
+ currentEncryptions.
+ """
+ self.test_KEXINIT()
+
+ self.proto.nextEncryptions = transport.SSHCiphers('none', 'none',
+ 'none', 'none')
+ self.proto.ssh_NEWKEYS('')
+ self.assertIdentical(self.proto.currentEncryptions,
+ self.proto.nextEncryptions)
+ self.assertIdentical(self.proto.outgoingCompression, None)
+ self.assertIdentical(self.proto.incomingCompression, None)
+ self.proto.outgoingCompressionType = 'zlib'
+ self.proto.ssh_NEWKEYS('')
+ self.failIfIdentical(self.proto.outgoingCompression, None)
+ self.proto.incomingCompressionType = 'zlib'
+ self.proto.ssh_NEWKEYS('')
+ self.failIfIdentical(self.proto.incomingCompression, None)
+
+
+ def test_SERVICE_REQUEST(self):
+ """
+ Test that the SERVICE_REQUEST message requests and starts a
+ service.
+ """
+ self.proto.ssh_SERVICE_REQUEST(common.NS('ssh-userauth'))
+ self.assertEquals(self.packets, [(transport.MSG_SERVICE_ACCEPT,
+ common.NS('ssh-userauth'))])
+ self.assertEquals(self.proto.service.name, 'MockService')
+
+
+ def test_disconnectNEWKEYSData(self):
+ """
+ Test that NEWKEYS disconnects if it receives data.
+ """
+ self.proto.ssh_NEWKEYS("bad packet")
+ self.checkDisconnected()
+
+
+ def test_disconnectSERVICE_REQUESTBadService(self):
+ """
+ Test that SERVICE_REQUESTS disconnects if an unknown service is
+ requested.
+ """
+ self.proto.ssh_SERVICE_REQUEST(common.NS('no service'))
+ self.checkDisconnected(transport.DISCONNECT_SERVICE_NOT_AVAILABLE)
+
+
+
+class ClientSSHTransportTestCase(ServerAndClientSSHTransportBaseCase,
+ TransportTestCase):
+ """
+ Tests for SSHClientTransport.
+ """
+
+ klass = transport.SSHClientTransport
+
+
+ def test_KEXINIT(self):
+ """
+ Test that receiving a KEXINIT packet sets up the correct values on the
+ client. The way algorithms are picks is that the first item in the
+ client's list that is also in the server's list is chosen.
+ """
+ self.proto.dataReceived( 'SSH-2.0-Twisted\r\n\x00\x00\x01\xd4\t\x14'
+ '\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99'
+ '\x99\x00\x00\x00=diffie-hellman-group1-sha1,diffie-hellman-g'
+ 'roup-exchange-sha1\x00\x00\x00\x0fssh-dss,ssh-rsa\x00\x00\x00'
+ '\x85aes128-ctr,aes128-cbc,aes192-ctr,aes192-cbc,aes256-ctr,ae'
+ 's256-cbc,cast128-ctr,cast128-cbc,blowfish-ctr,blowfish-cbc,3d'
+ 'es-ctr,3des-cbc\x00\x00\x00\x85aes128-ctr,aes128-cbc,aes192-c'
+ 'tr,aes192-cbc,aes256-ctr,aes256-cbc,cast128-ctr,cast128-cbc,b'
+ 'lowfish-ctr,blowfish-cbc,3des-ctr,3des-cbc\x00\x00\x00\x12hma'
+ 'c-md5,hmac-sha1\x00\x00\x00\x12hmac-md5,hmac-sha1\x00\x00\x00'
+ '\tzlib,none\x00\x00\x00\tzlib,none\x00\x00\x00\x00\x00\x00'
+ '\x00\x00\x00\x00\x00\x00\x00\x99\x99\x99\x99\x99\x99\x99\x99'
+ '\x99')
+ self.assertEquals(self.proto.kexAlg,
+ 'diffie-hellman-group-exchange-sha1')
+ self.assertEquals(self.proto.keyAlg,
+ 'ssh-rsa')
+ self.assertEquals(self.proto.outgoingCompressionType,
+ 'none')
+ self.assertEquals(self.proto.incomingCompressionType,
+ 'none')
+ ne = self.proto.nextEncryptions
+ self.assertEquals(ne.outCipType, 'aes256-ctr')
+ self.assertEquals(ne.inCipType, 'aes256-ctr')
+ self.assertEquals(ne.outMACType, 'hmac-sha1')
+ self.assertEquals(ne.inMACType, 'hmac-sha1')
+
+
+ def verifyHostKey(self, pubKey, fingerprint):
+ """
+ Mock version of SSHClientTransport.verifyHostKey.
+ """
+ self.calledVerifyHostKey = True
+ self.assertEquals(pubKey, self.blob)
+ self.assertEquals(fingerprint.replace(':', ''),
+ md5(pubKey).hexdigest())
+ return defer.succeed(True)
+
+
+ def setUp(self):
+ TransportTestCase.setUp(self)
+ self.blob = keys.Key.fromString(keydata.publicRSA_openssh).blob()
+ self.privObj = keys.Key.fromString(keydata.privateRSA_openssh)
+ self.calledVerifyHostKey = False
+ self.proto.verifyHostKey = self.verifyHostKey
+
+
+ def test_notImplementedClientMethods(self):
+ """
+ verifyHostKey() should return a Deferred which fails with a
+ NotImplementedError exception. connectionSecure() should raise
+ NotImplementedError().
+ """
+ self.assertRaises(NotImplementedError, self.klass().connectionSecure)
+ def _checkRaises(f):
+ f.trap(NotImplementedError)
+ d = self.klass().verifyHostKey(None, None)
+ return d.addCallback(self.fail).addErrback(_checkRaises)
+
+
+ def test_KEXINIT_groupexchange(self):
+ """
+ Test that a KEXINIT packet with a group-exchange key exchange results
+ in a KEX_DH_GEX_REQUEST_OLD message..
+ """
+ self.proto.supportedKeyExchanges = [
+ 'diffie-hellman-group-exchange-sha1']
+ self.proto.dataReceived(self.transport.value())
+ self.assertEquals(self.packets, [(transport.MSG_KEX_DH_GEX_REQUEST_OLD,
+ '\x00\x00\x08\x00')])
+
+
+ def test_KEXINIT_group1(self):
+ """
+ Like test_KEXINIT_groupexchange, but for the group-1 key exchange.
+ """
+ self.proto.supportedKeyExchanges = ['diffie-hellman-group1-sha1']
+ self.proto.dataReceived(self.transport.value())
+ self.assertEquals(common.MP(self.proto.x)[5:], '\x99' * 64)
+ self.assertEquals(self.packets,
+ [(transport.MSG_KEXDH_INIT, self.proto.e)])
+
+
+ def test_KEXINIT_badKexAlg(self):
+ """
+ Test that the client raises a ConchError if it receives a
+ KEXINIT message bug doesn't have a key exchange algorithm that we
+ understand.
+ """
+ self.proto.supportedKeyExchanges = ['diffie-hellman-group2-sha1']
+ data = self.transport.value().replace('group1', 'group2')
+ self.assertRaises(ConchError, self.proto.dataReceived, data)
+
+
+ def test_KEXDH_REPLY(self):
+ """
+ Test that the KEXDH_REPLY message verifies the server.
+ """
+ self.test_KEXINIT_group1()
+
+ sharedSecret = common._MPpow(transport.DH_GENERATOR,
+ self.proto.x, transport.DH_PRIME)
+ h = sha1()
+ h.update(common.NS(self.proto.ourVersionString) * 2)
+ h.update(common.NS(self.proto.ourKexInitPayload) * 2)
+ h.update(common.NS(self.blob))
+ h.update(self.proto.e)
+ h.update('\x00\x00\x00\x01\x02') # f
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+
+ def _cbTestKEXDH_REPLY(value):
+ self.assertIdentical(value, None)
+ self.assertEquals(self.calledVerifyHostKey, True)
+ self.assertEquals(self.proto.sessionID, exchangeHash)
+
+ signature = self.privObj.sign(exchangeHash)
+
+ d = self.proto.ssh_KEX_DH_GEX_GROUP(
+ (common.NS(self.blob) + '\x00\x00\x00\x01\x02' +
+ common.NS(signature)))
+ d.addCallback(_cbTestKEXDH_REPLY)
+
+ return d
+
+
+ def test_KEX_DH_GEX_GROUP(self):
+ """
+ Test that the KEX_DH_GEX_GROUP message results in a
+ KEX_DH_GEX_INIT message with the client's Diffie-Hellman public key.
+ """
+ self.test_KEXINIT_groupexchange()
+ self.proto.ssh_KEX_DH_GEX_GROUP(
+ '\x00\x00\x00\x01\x0f\x00\x00\x00\x01\x02')
+ self.assertEquals(self.proto.p, 15)
+ self.assertEquals(self.proto.g, 2)
+ self.assertEquals(common.MP(self.proto.x)[5:], '\x99' * 40)
+ self.assertEquals(self.proto.e,
+ common.MP(pow(2, self.proto.x, 15)))
+ self.assertEquals(self.packets[1:], [(transport.MSG_KEX_DH_GEX_INIT,
+ self.proto.e)])
+
+
+ def test_KEX_DH_GEX_REPLY(self):
+ """
+ Test that the KEX_DH_GEX_REPLY message results in a verified
+ server.
+ """
+
+ self.test_KEX_DH_GEX_GROUP()
+ sharedSecret = common._MPpow(3, self.proto.x, self.proto.p)
+ h = sha1()
+ h.update(common.NS(self.proto.ourVersionString) * 2)
+ h.update(common.NS(self.proto.ourKexInitPayload) * 2)
+ h.update(common.NS(self.blob))
+ h.update('\x00\x00\x08\x00\x00\x00\x00\x01\x0f\x00\x00\x00\x01\x02')
+ h.update(self.proto.e)
+ h.update('\x00\x00\x00\x01\x03') # f
+ h.update(sharedSecret)
+ exchangeHash = h.digest()
+
+ def _cbTestKEX_DH_GEX_REPLY(value):
+ self.assertIdentical(value, None)
+ self.assertEquals(self.calledVerifyHostKey, True)
+ self.assertEquals(self.proto.sessionID, exchangeHash)
+
+ signature = self.privObj.sign(exchangeHash)
+
+ d = self.proto.ssh_KEX_DH_GEX_REPLY(
+ common.NS(self.blob) +
+ '\x00\x00\x00\x01\x03' +
+ common.NS(signature))
+ d.addCallback(_cbTestKEX_DH_GEX_REPLY)
+ return d
+
+
+ def test_keySetup(self):
+ """
+ Test that _keySetup sets up the next encryption keys.
+ """
+ self.proto.nextEncryptions = MockCipher()
+ self.proto._keySetup('AB', 'CD')
+ self.assertEquals(self.proto.sessionID, 'CD')
+ self.proto._keySetup('AB', 'EF')
+ self.assertEquals(self.proto.sessionID, 'CD')
+ self.assertEquals(self.packets[-1], (transport.MSG_NEWKEYS, ''))
+ newKeys = [self.proto._getKey(c, 'AB', 'EF') for c in 'ABCDEF']
+ self.assertEquals(self.proto.nextEncryptions.keys,
+ (newKeys[0], newKeys[2], newKeys[1], newKeys[3],
+ newKeys[4], newKeys[5]))
+
+
+ def test_NEWKEYS(self):
+ """
+ Test that NEWKEYS transitions the keys from nextEncryptions to
+ currentEncryptions.
+ """
+ self.test_KEXINIT()
+ secure = [False]
+ def stubConnectionSecure():
+ secure[0] = True
+ self.proto.connectionSecure = stubConnectionSecure
+
+ self.proto.nextEncryptions = transport.SSHCiphers('none', 'none',
+ 'none', 'none')
+ self.proto.ssh_NEWKEYS('')
+
+ self.failIfIdentical(self.proto.currentEncryptions,
+ self.proto.nextEncryptions)
+
+ self.proto.nextEncryptions = MockCipher()
+ self.proto._keySetup('AB', 'EF')
+ self.assertIdentical(self.proto.outgoingCompression, None)
+ self.assertIdentical(self.proto.incomingCompression, None)
+ self.assertIdentical(self.proto.currentEncryptions,
+ self.proto.nextEncryptions)
+ self.assertTrue(secure[0])
+ self.proto.outgoingCompressionType = 'zlib'
+ self.proto.ssh_NEWKEYS('')
+ self.failIfIdentical(self.proto.outgoingCompression, None)
+ self.proto.incomingCompressionType = 'zlib'
+ self.proto.ssh_NEWKEYS('')
+ self.failIfIdentical(self.proto.incomingCompression, None)
+
+
+ def test_SERVICE_ACCEPT(self):
+ """
+ Test that the SERVICE_ACCEPT packet starts the requested service.
+ """
+ self.proto.instance = MockService()
+ self.proto.ssh_SERVICE_ACCEPT('\x00\x00\x00\x0bMockService')
+ self.assertTrue(self.proto.instance.started)
+
+
+ def test_requestService(self):
+ """
+ Test that requesting a service sends a SERVICE_REQUEST packet.
+ """
+ self.proto.requestService(MockService())
+ self.assertEquals(self.packets, [(transport.MSG_SERVICE_REQUEST,
+ '\x00\x00\x00\x0bMockService')])
+
+
+ def test_disconnectKEXDH_REPLYBadSignature(self):
+ """
+ Test that KEXDH_REPLY disconnects if the signature is bad.
+ """
+ self.test_KEXDH_REPLY()
+ self.proto._continueKEXDH_REPLY(None, self.blob, 3, "bad signature")
+ self.checkDisconnected(transport.DISCONNECT_KEY_EXCHANGE_FAILED)
+
+
+ def test_disconnectGEX_REPLYBadSignature(self):
+ """
+ Like test_disconnectKEXDH_REPLYBadSignature, but for DH_GEX_REPLY.
+ """
+ self.test_KEX_DH_GEX_REPLY()
+ self.proto._continueGEX_REPLY(None, self.blob, 3, "bad signature")
+ self.checkDisconnected(transport.DISCONNECT_KEY_EXCHANGE_FAILED)
+
+
+ def test_disconnectNEWKEYSData(self):
+ """
+ Test that NEWKEYS disconnects if it receives data.
+ """
+ self.proto.ssh_NEWKEYS("bad packet")
+ self.checkDisconnected()
+
+
+ def test_disconnectSERVICE_ACCEPT(self):
+ """
+ Test that SERVICE_ACCEPT disconnects if the accepted protocol is
+ differet from the asked-for protocol.
+ """
+ self.proto.instance = MockService()
+ self.proto.ssh_SERVICE_ACCEPT('\x00\x00\x00\x03bad')
+ self.checkDisconnected()
+
+
+
+class SSHCiphersTestCase(unittest.TestCase):
+ """
+ Tests for the SSHCiphers helper class.
+ """
+ if Crypto is None:
+ skip = "cannot run w/o PyCrypto"
+
+ if pyasn1 is None:
+ skip = "cannot run w/o PyASN1"
+
+
+ def test_init(self):
+ """
+ Test that the initializer sets up the SSHCiphers object.
+ """
+ ciphers = transport.SSHCiphers('A', 'B', 'C', 'D')
+ self.assertEquals(ciphers.outCipType, 'A')
+ self.assertEquals(ciphers.inCipType, 'B')
+ self.assertEquals(ciphers.outMACType, 'C')
+ self.assertEquals(ciphers.inMACType, 'D')
+
+
+ def test_getCipher(self):
+ """
+ Test that the _getCipher method returns the correct cipher.
+ """
+ ciphers = transport.SSHCiphers('A', 'B', 'C', 'D')
+ iv = key = '\x00' * 16
+ for cipName, (modName, keySize, counter) in ciphers.cipherMap.items():
+ cip = ciphers._getCipher(cipName, iv, key)
+ if cipName == 'none':
+ self.assertIsInstance(cip, transport._DummyCipher)
+ else:
+ self.assertTrue(str(cip).startswith('<' + modName))
+
+
+ def test_getMAC(self):
+ """
+ Test that the _getMAC method returns the correct MAC.
+ """
+ ciphers = transport.SSHCiphers('A', 'B', 'C', 'D')
+ key = '\x00' * 64
+ for macName, mac in ciphers.macMap.items():
+ mod = ciphers._getMAC(macName, key)
+ if macName == 'none':
+ self.assertIdentical(mac, None)
+ else:
+ self.assertEquals(mod[0], mac)
+ self.assertEquals(mod[1],
+ Crypto.Cipher.XOR.new('\x36').encrypt(key))
+ self.assertEquals(mod[2],
+ Crypto.Cipher.XOR.new('\x5c').encrypt(key))
+ self.assertEquals(mod[3], len(mod[0]().digest()))
+
+
+ def test_setKeysCiphers(self):
+ """
+ Test that setKeys sets up the ciphers.
+ """
+ key = '\x00' * 64
+ cipherItems = transport.SSHCiphers.cipherMap.items()
+ for cipName, (modName, keySize, counter) in cipherItems:
+ encCipher = transport.SSHCiphers(cipName, 'none', 'none', 'none')
+ decCipher = transport.SSHCiphers('none', cipName, 'none', 'none')
+ cip = encCipher._getCipher(cipName, key, key)
+ bs = cip.block_size
+ encCipher.setKeys(key, key, '', '', '', '')
+ decCipher.setKeys('', '', key, key, '', '')
+ self.assertEquals(encCipher.encBlockSize, bs)
+ self.assertEquals(decCipher.decBlockSize, bs)
+ enc = cip.encrypt(key[:bs])
+ enc2 = cip.encrypt(key[:bs])
+ if counter:
+ self.failIfEquals(enc, enc2)
+ self.assertEquals(encCipher.encrypt(key[:bs]), enc)
+ self.assertEquals(encCipher.encrypt(key[:bs]), enc2)
+ self.assertEquals(decCipher.decrypt(enc), key[:bs])
+ self.assertEquals(decCipher.decrypt(enc2), key[:bs])
+
+
+ def test_setKeysMACs(self):
+ """
+ Test that setKeys sets up the MACs.
+ """
+ key = '\x00' * 64
+ for macName, mod in transport.SSHCiphers.macMap.items():
+ outMac = transport.SSHCiphers('none', 'none', macName, 'none')
+ inMac = transport.SSHCiphers('none', 'none', 'none', macName)
+ outMac.setKeys('', '', '', '', key, '')
+ inMac.setKeys('', '', '', '', '', key)
+ if mod:
+ ds = mod().digest_size
+ else:
+ ds = 0
+ self.assertEquals(inMac.verifyDigestSize, ds)
+ if mod:
+ mod, i, o, ds = outMac._getMAC(macName, key)
+ seqid = 0
+ data = key
+ packet = '\x00' * 4 + key
+ if mod:
+ mac = mod(o + mod(i + packet).digest()).digest()
+ else:
+ mac = ''
+ self.assertEquals(outMac.makeMAC(seqid, data), mac)
+ self.assertTrue(inMac.verify(seqid, data, mac))
+
+
+
+class CounterTestCase(unittest.TestCase):
+ """
+ Tests for the _Counter helper class.
+ """
+ if Crypto is None:
+ skip = "cannot run w/o PyCrypto"
+
+ if pyasn1 is None:
+ skip = "cannot run w/o PyASN1"
+
+
+ def test_init(self):
+ """
+ Test that the counter is initialized correctly.
+ """
+ counter = transport._Counter('\x00' * 8 + '\xff' * 8, 8)
+ self.assertEquals(counter.blockSize, 8)
+ self.assertEquals(counter.count.tostring(), '\x00' * 8)
+
+
+ def test_count(self):
+ """
+ Test that the counter counts incrementally and wraps at the top.
+ """
+ counter = transport._Counter('\x00', 1)
+ self.assertEquals(counter(), '\x01')
+ self.assertEquals(counter(), '\x02')
+ [counter() for i in range(252)]
+ self.assertEquals(counter(), '\xff')
+ self.assertEquals(counter(), '\x00')
+
+
+
+class TransportLoopbackTestCase(unittest.TestCase):
+ """
+ Test the server transport and client transport against each other,
+ """
+ if Crypto is None:
+ skip = "cannot run w/o PyCrypto"
+
+ if pyasn1 is None:
+ skip = "cannot run w/o PyASN1"
+
+
+ def _runClientServer(self, mod):
+ """
+ Run an async client and server, modifying each using the mod function
+ provided. Returns a Deferred called back when both Protocols have
+ disconnected.
+
+ @type mod: C{func}
+ @rtype: C{defer.Deferred}
+ """
+ factory = MockFactory()
+ server = transport.SSHServerTransport()
+ server.factory = factory
+ factory.startFactory()
+ server.errors = []
+ server.receiveError = lambda code, desc: server.errors.append((
+ code, desc))
+ client = transport.SSHClientTransport()
+ client.verifyHostKey = lambda x, y: defer.succeed(None)
+ client.errors = []
+ client.receiveError = lambda code, desc: client.errors.append((
+ code, desc))
+ client.connectionSecure = lambda: client.loseConnection()
+ server = mod(server)
+ client = mod(client)
+ def check(ignored, server, client):
+ name = repr([server.supportedCiphers[0],
+ server.supportedMACs[0],
+ server.supportedKeyExchanges[0],
+ server.supportedCompressions[0]])
+ self.assertEquals(client.errors, [])
+ self.assertEquals(server.errors, [(
+ transport.DISCONNECT_CONNECTION_LOST,
+ "user closed connection")])
+ if server.supportedCiphers[0] == 'none':
+ self.assertFalse(server.isEncrypted(), name)
+ self.assertFalse(client.isEncrypted(), name)
+ else:
+ self.assertTrue(server.isEncrypted(), name)
+ self.assertTrue(client.isEncrypted(), name)
+ if server.supportedMACs[0] == 'none':
+ self.assertFalse(server.isVerified(), name)
+ self.assertFalse(client.isVerified(), name)
+ else:
+ self.assertTrue(server.isVerified(), name)
+ self.assertTrue(client.isVerified(), name)
+
+ d = loopback.loopbackAsync(server, client)
+ d.addCallback(check, server, client)
+ return d
+
+
+ def test_ciphers(self):
+ """
+ Test that the client and server play nicely together, in all
+ the various combinations of ciphers.
+ """
+ deferreds = []
+ for cipher in transport.SSHTransportBase.supportedCiphers + ['none']:
+ def setCipher(proto):
+ proto.supportedCiphers = [cipher]
+ return proto
+ deferreds.append(self._runClientServer(setCipher))
+ return defer.DeferredList(deferreds, fireOnOneErrback=True)
+
+
+ def test_macs(self):
+ """
+ Like test_ciphers, but for the various MACs.
+ """
+ deferreds = []
+ for mac in transport.SSHTransportBase.supportedMACs + ['none']:
+ def setMAC(proto):
+ proto.supportedMACs = [mac]
+ return proto
+ deferreds.append(self._runClientServer(setMAC))
+ return defer.DeferredList(deferreds, fireOnOneErrback=True)
+
+
+ def test_keyexchanges(self):
+ """
+ Like test_ciphers, but for the various key exchanges.
+ """
+ deferreds = []
+ for kex in transport.SSHTransportBase.supportedKeyExchanges:
+ def setKeyExchange(proto):
+ proto.supportedKeyExchanges = [kex]
+ return proto
+ deferreds.append(self._runClientServer(setKeyExchange))
+ return defer.DeferredList(deferreds, fireOnOneErrback=True)
+
+
+ def test_compressions(self):
+ """
+ Like test_ciphers, but for the various compressions.
+ """
+ deferreds = []
+ for compression in transport.SSHTransportBase.supportedCompressions:
+ def setCompression(proto):
+ proto.supportedCompressions = [compression]
+ return proto
+ deferreds.append(self._runClientServer(setCompression))
+ return defer.DeferredList(deferreds, fireOnOneErrback=True)
+
+
+
+class OldFactoryTestCase(unittest.TestCase):
+ """
+ The old C{SSHFactory.getPublicKeys}() returned mappings of key names to
+ strings of key blobs and mappings of key names to PyCrypto key objects from
+ C{SSHFactory.getPrivateKeys}() (they could also be specified with the
+ C{publicKeys} and C{privateKeys} attributes). This is no longer supported
+ by the C{SSHServerTransport}, so we warn the user if they create an old
+ factory.
+ """
+
+ if Crypto is None:
+ skip = "cannot run w/o PyCrypto"
+
+ if pyasn1 is None:
+ skip = "cannot run w/o PyASN1"
+
+
+ def test_getPublicKeysWarning(self):
+ """
+ If the return value of C{getPublicKeys}() isn't a mapping from key
+ names to C{Key} objects, then warn the user and convert the mapping.
+ """
+ sshFactory = MockOldFactoryPublicKeys()
+ self.assertWarns(DeprecationWarning,
+ "Returning a mapping from strings to strings from"
+ " getPublicKeys()/publicKeys (in %s) is deprecated. Return "
+ "a mapping from strings to Key objects instead." %
+ (qual(MockOldFactoryPublicKeys),),
+ factory.__file__, sshFactory.startFactory)
+ self.assertEquals(sshFactory.publicKeys, MockFactory().getPublicKeys())
+
+
+ def test_getPrivateKeysWarning(self):
+ """
+ If the return value of C{getPrivateKeys}() isn't a mapping from key
+ names to C{Key} objects, then warn the user and convert the mapping.
+ """
+ sshFactory = MockOldFactoryPrivateKeys()
+ self.assertWarns(DeprecationWarning,
+ "Returning a mapping from strings to PyCrypto key objects from"
+ " getPrivateKeys()/privateKeys (in %s) is deprecated. Return"
+ " a mapping from strings to Key objects instead." %
+ (qual(MockOldFactoryPrivateKeys),),
+ factory.__file__, sshFactory.startFactory)
+ self.assertEquals(sshFactory.privateKeys,
+ MockFactory().getPrivateKeys())
+
+
+ def test_publicKeysWarning(self):
+ """
+ If the value of the C{publicKeys} attribute isn't a mapping from key
+ names to C{Key} objects, then warn the user and convert the mapping.
+ """
+ sshFactory = MockOldFactoryPublicKeys()
+ sshFactory.publicKeys = sshFactory.getPublicKeys()
+ self.assertWarns(DeprecationWarning,
+ "Returning a mapping from strings to strings from"
+ " getPublicKeys()/publicKeys (in %s) is deprecated. Return "
+ "a mapping from strings to Key objects instead." %
+ (qual(MockOldFactoryPublicKeys),),
+ factory.__file__, sshFactory.startFactory)
+ self.assertEquals(sshFactory.publicKeys, MockFactory().getPublicKeys())
+
+
+ def test_privateKeysWarning(self):
+ """
+ If the return value of C{privateKeys} attribute isn't a mapping from
+ key names to C{Key} objects, then warn the user and convert the
+ mapping.
+ """
+ sshFactory = MockOldFactoryPrivateKeys()
+ sshFactory.privateKeys = sshFactory.getPrivateKeys()
+ self.assertWarns(DeprecationWarning,
+ "Returning a mapping from strings to PyCrypto key objects from"
+ " getPrivateKeys()/privateKeys (in %s) is deprecated. Return"
+ " a mapping from strings to Key objects instead." %
+ (qual(MockOldFactoryPrivateKeys),),
+ factory.__file__, sshFactory.startFactory)
+ self.assertEquals(sshFactory.privateKeys,
+ MockFactory().getPrivateKeys())
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_userauth.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_userauth.py
new file mode 100644
index 0000000000..47526255ab
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_userauth.py
@@ -0,0 +1,1062 @@
+# -*- test-case-name: twisted.conch.test.test_userauth -*-
+# Copyright (c) 2007-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for the implementation of the ssh-userauth service.
+
+Maintainer: Paul Swartz
+"""
+
+from zope.interface import implements
+
+from twisted.cred.checkers import ICredentialsChecker
+from twisted.cred.credentials import IUsernamePassword, ISSHPrivateKey
+from twisted.cred.credentials import IPluggableAuthenticationModules
+from twisted.cred.credentials import IAnonymous
+from twisted.cred.error import UnauthorizedLogin
+from twisted.cred.portal import IRealm, Portal
+from twisted.conch.error import ConchError, ValidPublicKey
+from twisted.internet import defer, task
+from twisted.protocols import loopback
+from twisted.trial import unittest
+
+try:
+ import Crypto.Cipher.DES3, Crypto.Cipher.XOR
+ import pyasn1
+except ImportError:
+ keys = None
+
+
+ class transport:
+ class SSHTransportBase:
+ """
+ A stub class so that later class definitions won't die.
+ """
+
+ class userauth:
+ class SSHUserAuthClient:
+ """
+ A stub class so that leter class definitions won't die.
+ """
+else:
+ from twisted.conch.ssh.common import NS
+ from twisted.conch.checkers import SSHProtocolChecker
+ from twisted.conch.ssh import keys, userauth, transport
+ from twisted.conch.test import keydata
+
+
+
+class ClientUserAuth(userauth.SSHUserAuthClient):
+ """
+ A mock user auth client.
+ """
+
+
+ def getPublicKey(self):
+ """
+ If this is the first time we've been called, return a blob for
+ the DSA key. Otherwise, return a blob
+ for the RSA key.
+ """
+ if self.lastPublicKey:
+ return keys.Key.fromString(keydata.publicRSA_openssh)
+ else:
+ return defer.succeed(keys.Key.fromString(keydata.publicDSA_openssh))
+
+
+ def getPrivateKey(self):
+ """
+ Return the private key object for the RSA key.
+ """
+ return defer.succeed(keys.Key.fromString(keydata.privateRSA_openssh))
+
+
+ def getPassword(self, prompt=None):
+ """
+ Return 'foo' as the password.
+ """
+ return defer.succeed('foo')
+
+
+ def getGenericAnswers(self, name, information, answers):
+ """
+ Return 'foo' as the answer to two questions.
+ """
+ return defer.succeed(('foo', 'foo'))
+
+
+
+class OldClientAuth(userauth.SSHUserAuthClient):
+ """
+ The old SSHUserAuthClient returned a PyCrypto key object from
+ getPrivateKey() and a string from getPublicKey
+ """
+
+
+ def getPrivateKey(self):
+ return defer.succeed(keys.Key.fromString(
+ keydata.privateRSA_openssh).keyObject)
+
+
+ def getPublicKey(self):
+ return keys.Key.fromString(keydata.publicRSA_openssh).blob()
+
+class ClientAuthWithoutPrivateKey(userauth.SSHUserAuthClient):
+ """
+ This client doesn't have a private key, but it does have a public key.
+ """
+
+
+ def getPrivateKey(self):
+ return
+
+
+ def getPublicKey(self):
+ return keys.Key.fromString(keydata.publicRSA_openssh)
+
+
+
+class FakeTransport(transport.SSHTransportBase):
+ """
+ L{userauth.SSHUserAuthServer} expects an SSH transport which has a factory
+ attribute which has a portal attribute. Because the portal is important for
+ testing authentication, we need to be able to provide an interesting portal
+ object to the L{SSHUserAuthServer}.
+
+ In addition, we want to be able to capture any packets sent over the
+ transport.
+
+ @ivar packets: a list of 2-tuples: (messageType, data). Each 2-tuple is
+ a sent packet.
+ @type packets: C{list}
+ @param lostConnecion: True if loseConnection has been called on us.
+ @type lostConnection: C{bool}
+ """
+
+
+ class Service(object):
+ """
+ A mock service, representing the other service offered by the server.
+ """
+ name = 'nancy'
+
+
+ def serviceStarted(self):
+ pass
+
+
+
+ class Factory(object):
+ """
+ A mock factory, representing the factory that spawned this user auth
+ service.
+ """
+
+
+ def getService(self, transport, service):
+ """
+ Return our fake service.
+ """
+ if service == 'none':
+ return FakeTransport.Service
+
+
+
+ def __init__(self, portal):
+ self.factory = self.Factory()
+ self.factory.portal = portal
+ self.lostConnection = False
+ self.transport = self
+ self.packets = []
+
+
+
+ def sendPacket(self, messageType, message):
+ """
+ Record the packet sent by the service.
+ """
+ self.packets.append((messageType, message))
+
+
+ def isEncrypted(self, direction):
+ """
+ Pretend that this transport encrypts traffic in both directions. The
+ SSHUserAuthServer disables password authentication if the transport
+ isn't encrypted.
+ """
+ return True
+
+
+ def loseConnection(self):
+ self.lostConnection = True
+
+
+
+class Realm(object):
+ """
+ A mock realm for testing L{userauth.SSHUserAuthServer}.
+
+ This realm is not actually used in the course of testing, so it returns the
+ simplest thing that could possibly work.
+ """
+ implements(IRealm)
+
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ return defer.succeed((interfaces[0], None, lambda: None))
+
+
+
+class PasswordChecker(object):
+ """
+ A very simple username/password checker which authenticates anyone whose
+ password matches their username and rejects all others.
+ """
+ credentialInterfaces = (IUsernamePassword,)
+ implements(ICredentialsChecker)
+
+
+ def requestAvatarId(self, creds):
+ if creds.username == creds.password:
+ return defer.succeed(creds.username)
+ return defer.fail(UnauthorizedLogin("Invalid username/password pair"))
+
+
+
+class PrivateKeyChecker(object):
+ """
+ A very simple public key checker which authenticates anyone whose
+ public/private keypair is the same keydata.public/privateRSA_openssh.
+ """
+ credentialInterfaces = (ISSHPrivateKey,)
+ implements(ICredentialsChecker)
+
+
+
+ def requestAvatarId(self, creds):
+ if creds.blob == keys.Key.fromString(keydata.publicRSA_openssh).blob():
+ if creds.signature is not None:
+ obj = keys.Key.fromString(creds.blob)
+ if obj.verify(creds.signature, creds.sigData):
+ return creds.username
+ else:
+ raise ValidPublicKey()
+ raise UnauthorizedLogin()
+
+
+
+class PAMChecker(object):
+ """
+ A simple PAM checker which asks the user for a password, verifying them
+ if the password is the same as their username.
+ """
+ credentialInterfaces = (IPluggableAuthenticationModules,)
+ implements(ICredentialsChecker)
+
+
+ def requestAvatarId(self, creds):
+ d = creds.pamConversion([('Name: ', 2), ("Password: ", 1)])
+ def check(values):
+ if values == [(creds.username, 0), (creds.username, 0)]:
+ return creds.username
+ raise UnauthorizedLogin()
+ return d.addCallback(check)
+
+
+
+class AnonymousChecker(object):
+ """
+ A simple checker which isn't supported by L{SSHUserAuthServer}.
+ """
+ credentialInterfaces = (IAnonymous,)
+ implements(ICredentialsChecker)
+
+
+
+class SSHUserAuthServerTestCase(unittest.TestCase):
+ """
+ Tests for SSHUserAuthServer.
+ """
+
+
+ if keys is None:
+ skip = "cannot run w/o PyCrypto"
+
+
+ def setUp(self):
+ self.realm = Realm()
+ self.portal = Portal(self.realm)
+ self.portal.registerChecker(PasswordChecker())
+ self.portal.registerChecker(PrivateKeyChecker())
+ self.portal.registerChecker(PAMChecker())
+ self.authServer = userauth.SSHUserAuthServer()
+ self.authServer.transport = FakeTransport(self.portal)
+ self.authServer.serviceStarted()
+ self.authServer.supportedAuthentications.sort() # give a consistent
+ # order
+
+
+ def tearDown(self):
+ self.authServer.serviceStopped()
+ self.authServer = None
+
+
+ def _checkFailed(self, ignored):
+ """
+ Check that the authentication has failed.
+ """
+ self.assertEquals(self.authServer.transport.packets[-1],
+ (userauth.MSG_USERAUTH_FAILURE,
+ NS('keyboard-interactive,password,publickey') + '\x00'))
+
+
+ def test_noneAuthentication(self):
+ """
+ A client may request a list of authentication 'method name' values
+ that may continue by using the "none" authentication 'method name'.
+
+ See RFC 4252 Section 5.2.
+ """
+ d = self.authServer.ssh_USERAUTH_REQUEST(NS('foo') + NS('service') +
+ NS('none'))
+ return d.addCallback(self._checkFailed)
+
+
+ def test_successfulPasswordAuthentication(self):
+ """
+ When provided with correct password authentication information, the
+ server should respond by sending a MSG_USERAUTH_SUCCESS message with
+ no other data.
+
+ See RFC 4252, Section 5.1.
+ """
+ packet = NS('foo') + NS('none') + NS('password') + chr(0) + NS('foo')
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ def check(ignored):
+ self.assertEqual(
+ self.authServer.transport.packets,
+ [(userauth.MSG_USERAUTH_SUCCESS, '')])
+ return d.addCallback(check)
+
+
+ def test_failedPasswordAuthentication(self):
+ """
+ When provided with invalid authentication details, the server should
+ respond by sending a MSG_USERAUTH_FAILURE message which states whether
+ the authentication was partially successful, and provides other, open
+ options for authentication.
+
+ See RFC 4252, Section 5.1.
+ """
+ # packet = username, next_service, authentication type, FALSE, password
+ packet = NS('foo') + NS('none') + NS('password') + chr(0) + NS('bar')
+ self.authServer.clock = task.Clock()
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ self.assertEquals(self.authServer.transport.packets, [])
+ self.authServer.clock.advance(2)
+ return d.addCallback(self._checkFailed)
+
+
+ def test_successfulPrivateKeyAuthentication(self):
+ """
+ Test that private key authentication completes sucessfully,
+ """
+ blob = keys.Key.fromString(keydata.publicRSA_openssh).blob()
+ obj = keys.Key.fromString(keydata.privateRSA_openssh)
+ packet = (NS('foo') + NS('none') + NS('publickey') + '\xff'
+ + NS(obj.sshType()) + NS(blob))
+ self.authServer.transport.sessionID = 'test'
+ signature = obj.sign(NS('test') + chr(userauth.MSG_USERAUTH_REQUEST)
+ + packet)
+ packet += NS(signature)
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ def check(ignored):
+ self.assertEquals(self.authServer.transport.packets,
+ [(userauth.MSG_USERAUTH_SUCCESS, '')])
+ return d.addCallback(check)
+
+
+ def test_requestRaisesConchError(self):
+ """
+ ssh_USERAUTH_REQUEST should raise a ConchError if tryAuth returns
+ None. Added to catch a bug noticed by pyflakes.
+ """
+ d = defer.Deferred()
+
+ def mockCbFinishedAuth(self, ignored):
+ self.fail('request should have raised ConochError')
+
+ def mockTryAuth(kind, user, data):
+ return None
+
+ def mockEbBadAuth(reason):
+ d.errback(reason.value)
+
+ self.patch(self.authServer, 'tryAuth', mockTryAuth)
+ self.patch(self.authServer, '_cbFinishedAuth', mockCbFinishedAuth)
+ self.patch(self.authServer, '_ebBadAuth', mockEbBadAuth)
+
+ packet = NS('user') + NS('none') + NS('public-key') + NS('data')
+ # If an error other than ConchError is raised, this will trigger an
+ # exception.
+ self.authServer.ssh_USERAUTH_REQUEST(packet)
+ return self.assertFailure(d, ConchError)
+
+
+ def test_verifyValidPrivateKey(self):
+ """
+ Test that verifying a valid private key works.
+ """
+ blob = keys.Key.fromString(keydata.publicRSA_openssh).blob()
+ packet = (NS('foo') + NS('none') + NS('publickey') + '\x00'
+ + NS('ssh-rsa') + NS(blob))
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ def check(ignored):
+ self.assertEquals(self.authServer.transport.packets,
+ [(userauth.MSG_USERAUTH_PK_OK, NS('ssh-rsa') + NS(blob))])
+ return d.addCallback(check)
+
+
+ def test_failedPrivateKeyAuthenticationWithoutSignature(self):
+ """
+ Test that private key authentication fails when the public key
+ is invalid.
+ """
+ blob = keys.Key.fromString(keydata.publicDSA_openssh).blob()
+ packet = (NS('foo') + NS('none') + NS('publickey') + '\x00'
+ + NS('ssh-dsa') + NS(blob))
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ return d.addCallback(self._checkFailed)
+
+
+ def test_failedPrivateKeyAuthenticationWithSignature(self):
+ """
+ Test that private key authentication fails when the public key
+ is invalid.
+ """
+ blob = keys.Key.fromString(keydata.publicRSA_openssh).blob()
+ obj = keys.Key.fromString(keydata.privateRSA_openssh)
+ packet = (NS('foo') + NS('none') + NS('publickey') + '\xff'
+ + NS('ssh-rsa') + NS(blob) + NS(obj.sign(blob)))
+ self.authServer.transport.sessionID = 'test'
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ return d.addCallback(self._checkFailed)
+
+
+ def test_successfulPAMAuthentication(self):
+ """
+ Test that keyboard-interactive authentication succeeds.
+ """
+ packet = (NS('foo') + NS('none') + NS('keyboard-interactive')
+ + NS('') + NS(''))
+ response = '\x00\x00\x00\x02' + NS('foo') + NS('foo')
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ self.authServer.ssh_USERAUTH_INFO_RESPONSE(response)
+ def check(ignored):
+ self.assertEquals(self.authServer.transport.packets,
+ [(userauth.MSG_USERAUTH_INFO_REQUEST, (NS('') + NS('')
+ + NS('') + '\x00\x00\x00\x02' + NS('Name: ') + '\x01'
+ + NS('Password: ') + '\x00')),
+ (userauth.MSG_USERAUTH_SUCCESS, '')])
+
+ return d.addCallback(check)
+
+
+ def test_failedPAMAuthentication(self):
+ """
+ Test that keyboard-interactive authentication fails.
+ """
+ packet = (NS('foo') + NS('none') + NS('keyboard-interactive')
+ + NS('') + NS(''))
+ response = '\x00\x00\x00\x02' + NS('bar') + NS('bar')
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ self.authServer.ssh_USERAUTH_INFO_RESPONSE(response)
+ def check(ignored):
+ self.assertEquals(self.authServer.transport.packets[0],
+ (userauth.MSG_USERAUTH_INFO_REQUEST, (NS('') + NS('')
+ + NS('') + '\x00\x00\x00\x02' + NS('Name: ') + '\x01'
+ + NS('Password: ') + '\x00')))
+ return d.addCallback(check).addCallback(self._checkFailed)
+
+
+ def test_invalid_USERAUTH_INFO_RESPONSE_not_enough_data(self):
+ """
+ If ssh_USERAUTH_INFO_RESPONSE gets an invalid packet,
+ the user authentication should fail.
+ """
+ packet = (NS('foo') + NS('none') + NS('keyboard-interactive')
+ + NS('') + NS(''))
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ self.authServer.ssh_USERAUTH_INFO_RESPONSE(NS('\x00\x00\x00\x00' +
+ NS('hi')))
+ return d.addCallback(self._checkFailed)
+
+
+ def test_invalid_USERAUTH_INFO_RESPONSE_too_much_data(self):
+ """
+ If ssh_USERAUTH_INFO_RESPONSE gets too much data, the user
+ authentication should fail.
+ """
+ packet = (NS('foo') + NS('none') + NS('keyboard-interactive')
+ + NS('') + NS(''))
+ response = '\x00\x00\x00\x02' + NS('foo') + NS('foo') + NS('foo')
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ self.authServer.ssh_USERAUTH_INFO_RESPONSE(response)
+ return d.addCallback(self._checkFailed)
+
+
+ def test_onlyOnePAMAuthentication(self):
+ """
+ Because it requires an intermediate message, one can't send a second
+ keyboard-interactive request while the first is still pending.
+ """
+ packet = (NS('foo') + NS('none') + NS('keyboard-interactive')
+ + NS('') + NS(''))
+ self.authServer.ssh_USERAUTH_REQUEST(packet)
+ self.authServer.ssh_USERAUTH_REQUEST(packet)
+ self.assertEquals(self.authServer.transport.packets[-1][0],
+ transport.MSG_DISCONNECT)
+ self.assertEquals(self.authServer.transport.packets[-1][1][3],
+ chr(transport.DISCONNECT_PROTOCOL_ERROR))
+
+
+ def test_ignoreUnknownCredInterfaces(self):
+ """
+ L{SSHUserAuthServer} sets up
+ C{SSHUserAuthServer.supportedAuthentications} by checking the portal's
+ credentials interfaces and mapping them to SSH authentication method
+ strings. If the Portal advertises an interface that
+ L{SSHUserAuthServer} can't map, it should be ignored. This is a white
+ box test.
+ """
+ server = userauth.SSHUserAuthServer()
+ server.transport = FakeTransport(self.portal)
+ self.portal.registerChecker(AnonymousChecker())
+ server.serviceStarted()
+ server.serviceStopped()
+ server.supportedAuthentications.sort() # give a consistent order
+ self.assertEquals(server.supportedAuthentications,
+ ['keyboard-interactive', 'password', 'publickey'])
+
+
+ def test_removePasswordIfUnencrypted(self):
+ """
+ Test that the userauth service does not advertise password
+ authentication if the password would be send in cleartext.
+ """
+ self.assertIn('password', self.authServer.supportedAuthentications)
+ # no encryption
+ clearAuthServer = userauth.SSHUserAuthServer()
+ clearAuthServer.transport = FakeTransport(self.portal)
+ clearAuthServer.transport.isEncrypted = lambda x: False
+ clearAuthServer.serviceStarted()
+ clearAuthServer.serviceStopped()
+ self.failIfIn('password', clearAuthServer.supportedAuthentications)
+ # only encrypt incoming (the direction the password is sent)
+ halfAuthServer = userauth.SSHUserAuthServer()
+ halfAuthServer.transport = FakeTransport(self.portal)
+ halfAuthServer.transport.isEncrypted = lambda x: x == 'in'
+ halfAuthServer.serviceStarted()
+ halfAuthServer.serviceStopped()
+ self.assertIn('password', halfAuthServer.supportedAuthentications)
+
+
+ def test_removeKeyboardInteractiveIfUnencrypted(self):
+ """
+ Test that the userauth service does not advertise keyboard-interactive
+ authentication if the password would be send in cleartext.
+ """
+ self.assertIn('keyboard-interactive',
+ self.authServer.supportedAuthentications)
+ # no encryption
+ clearAuthServer = userauth.SSHUserAuthServer()
+ clearAuthServer.transport = FakeTransport(self.portal)
+ clearAuthServer.transport.isEncrypted = lambda x: False
+ clearAuthServer.serviceStarted()
+ clearAuthServer.serviceStopped()
+ self.failIfIn('keyboard-interactive',
+ clearAuthServer.supportedAuthentications)
+ # only encrypt incoming (the direction the password is sent)
+ halfAuthServer = userauth.SSHUserAuthServer()
+ halfAuthServer.transport = FakeTransport(self.portal)
+ halfAuthServer.transport.isEncrypted = lambda x: x == 'in'
+ halfAuthServer.serviceStarted()
+ halfAuthServer.serviceStopped()
+ self.assertIn('keyboard-interactive',
+ halfAuthServer.supportedAuthentications)
+
+
+ def test_unencryptedConnectionWithoutPasswords(self):
+ """
+ If the L{SSHUserAuthServer} is not advertising passwords, then an
+ unencrypted connection should not cause any warnings or exceptions.
+ This is a white box test.
+ """
+ # create a Portal without password authentication
+ portal = Portal(self.realm)
+ portal.registerChecker(PrivateKeyChecker())
+
+ # no encryption
+ clearAuthServer = userauth.SSHUserAuthServer()
+ clearAuthServer.transport = FakeTransport(portal)
+ clearAuthServer.transport.isEncrypted = lambda x: False
+ clearAuthServer.serviceStarted()
+ clearAuthServer.serviceStopped()
+ self.assertEquals(clearAuthServer.supportedAuthentications,
+ ['publickey'])
+
+ # only encrypt incoming (the direction the password is sent)
+ halfAuthServer = userauth.SSHUserAuthServer()
+ halfAuthServer.transport = FakeTransport(portal)
+ halfAuthServer.transport.isEncrypted = lambda x: x == 'in'
+ halfAuthServer.serviceStarted()
+ halfAuthServer.serviceStopped()
+ self.assertEquals(clearAuthServer.supportedAuthentications,
+ ['publickey'])
+
+
+ def test_loginTimeout(self):
+ """
+ Test that the login times out.
+ """
+ timeoutAuthServer = userauth.SSHUserAuthServer()
+ timeoutAuthServer.clock = task.Clock()
+ timeoutAuthServer.transport = FakeTransport(self.portal)
+ timeoutAuthServer.serviceStarted()
+ timeoutAuthServer.clock.advance(11 * 60 * 60)
+ timeoutAuthServer.serviceStopped()
+ self.assertEquals(timeoutAuthServer.transport.packets,
+ [(transport.MSG_DISCONNECT,
+ '\x00' * 3 +
+ chr(transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) +
+ NS("you took too long") + NS(''))])
+ self.assertTrue(timeoutAuthServer.transport.lostConnection)
+
+
+ def test_cancelLoginTimeout(self):
+ """
+ Test that stopping the service also stops the login timeout.
+ """
+ timeoutAuthServer = userauth.SSHUserAuthServer()
+ timeoutAuthServer.clock = task.Clock()
+ timeoutAuthServer.transport = FakeTransport(self.portal)
+ timeoutAuthServer.serviceStarted()
+ timeoutAuthServer.serviceStopped()
+ timeoutAuthServer.clock.advance(11 * 60 * 60)
+ self.assertEquals(timeoutAuthServer.transport.packets, [])
+ self.assertFalse(timeoutAuthServer.transport.lostConnection)
+
+
+ def test_tooManyAttempts(self):
+ """
+ Test that the server disconnects if the client fails authentication
+ too many times.
+ """
+ packet = NS('foo') + NS('none') + NS('password') + chr(0) + NS('bar')
+ self.authServer.clock = task.Clock()
+ for i in range(21):
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ self.authServer.clock.advance(2)
+ def check(ignored):
+ self.assertEquals(self.authServer.transport.packets[-1],
+ (transport.MSG_DISCONNECT,
+ '\x00' * 3 +
+ chr(transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) +
+ NS("too many bad auths") + NS('')))
+ return d.addCallback(check)
+
+
+ def test_failIfUnknownService(self):
+ """
+ If the user requests a service that we don't support, the
+ authentication should fail.
+ """
+ packet = NS('foo') + NS('') + NS('password') + chr(0) + NS('foo')
+ self.authServer.clock = task.Clock()
+ d = self.authServer.ssh_USERAUTH_REQUEST(packet)
+ return d.addCallback(self._checkFailed)
+
+
+ def test__pamConvErrors(self):
+ """
+ _pamConv should fail if it gets a message that's not 1 or 2.
+ """
+ def secondTest(ignored):
+ d2 = self.authServer._pamConv([('', 90)])
+ return self.assertFailure(d2, ConchError)
+
+ d = self.authServer._pamConv([('', 3)])
+ return self.assertFailure(d, ConchError).addCallback(secondTest)
+
+
+ def test_tryAuthEdgeCases(self):
+ """
+ tryAuth() has two edge cases that are difficult to reach.
+
+ 1) an authentication method auth_* returns None instead of a Deferred.
+ 2) an authentication type that is defined does not have a matching
+ auth_* method.
+
+ Both these cases should return a Deferred which fails with a
+ ConchError.
+ """
+ def mockAuth(packet):
+ return None
+
+ self.patch(self.authServer, 'auth_publickey', mockAuth) # first case
+ self.patch(self.authServer, 'auth_password', None) # second case
+
+ def secondTest(ignored):
+ d2 = self.authServer.tryAuth('password', None, None)
+ return self.assertFailure(d2, ConchError)
+
+ d1 = self.authServer.tryAuth('publickey', None, None)
+ return self.assertFailure(d1, ConchError).addCallback(secondTest)
+
+
+
+
+class SSHUserAuthClientTestCase(unittest.TestCase):
+ """
+ Tests for SSHUserAuthClient.
+ """
+
+
+ if keys is None:
+ skip = "cannot run w/o PyCrypto"
+
+
+ def setUp(self):
+ self.authClient = ClientUserAuth('foo', FakeTransport.Service())
+ self.authClient.transport = FakeTransport(None)
+ self.authClient.transport.sessionID = 'test'
+ self.authClient.serviceStarted()
+
+
+ def tearDown(self):
+ self.authClient.serviceStopped()
+ self.authClient = None
+
+
+ def test_init(self):
+ """
+ Test that client is initialized properly.
+ """
+ self.assertEquals(self.authClient.user, 'foo')
+ self.assertEquals(self.authClient.instance.name, 'nancy')
+ self.assertEquals(self.authClient.transport.packets,
+ [(userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy')
+ + NS('none'))])
+
+
+ def test_USERAUTH_SUCCESS(self):
+ """
+ Test that the client succeeds properly.
+ """
+ instance = [None]
+ def stubSetService(service):
+ instance[0] = service
+ self.authClient.transport.setService = stubSetService
+ self.authClient.ssh_USERAUTH_SUCCESS('')
+ self.assertEquals(instance[0], self.authClient.instance)
+
+
+ def test_publickey(self):
+ """
+ Test that the client can authenticate with a public key.
+ """
+ self.authClient.ssh_USERAUTH_FAILURE(NS('publickey') + '\x00')
+ self.assertEquals(self.authClient.transport.packets[-1],
+ (userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy')
+ + NS('publickey') + '\x00' + NS('ssh-dss')
+ + NS(keys.Key.fromString(
+ keydata.publicDSA_openssh).blob())))
+ # that key isn't good
+ self.authClient.ssh_USERAUTH_FAILURE(NS('publickey') + '\x00')
+ blob = NS(keys.Key.fromString(keydata.publicRSA_openssh).blob())
+ self.assertEquals(self.authClient.transport.packets[-1],
+ (userauth.MSG_USERAUTH_REQUEST, (NS('foo') + NS('nancy')
+ + NS('publickey') + '\x00'+ NS('ssh-rsa') + blob)))
+ self.authClient.ssh_USERAUTH_PK_OK(NS('ssh-rsa')
+ + NS(keys.Key.fromString(keydata.publicRSA_openssh).blob()))
+ sigData = (NS(self.authClient.transport.sessionID)
+ + chr(userauth.MSG_USERAUTH_REQUEST) + NS('foo')
+ + NS('nancy') + NS('publickey') + '\xff' + NS('ssh-rsa')
+ + blob)
+ obj = keys.Key.fromString(keydata.privateRSA_openssh)
+ self.assertEquals(self.authClient.transport.packets[-1],
+ (userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy')
+ + NS('publickey') + '\xff' + NS('ssh-rsa') + blob
+ + NS(obj.sign(sigData))))
+
+
+ def test_publickey_without_privatekey(self):
+ """
+ If the SSHUserAuthClient doesn't return anything from signData,
+ the client should start the authentication over again by requesting
+ 'none' authentication.
+ """
+ authClient = ClientAuthWithoutPrivateKey('foo',
+ FakeTransport.Service())
+
+ authClient.transport = FakeTransport(None)
+ authClient.transport.sessionID = 'test'
+ authClient.serviceStarted()
+ authClient.tryAuth('publickey')
+ authClient.transport.packets = []
+ self.assertIdentical(authClient.ssh_USERAUTH_PK_OK(''), None)
+ self.assertEquals(authClient.transport.packets, [
+ (userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy') +
+ NS('none'))])
+
+
+ def test_old_publickey_getPublicKey(self):
+ """
+ Old SSHUserAuthClients returned strings of public key blobs from
+ getPublicKey(). Test that a Deprecation warning is raised but the key is
+ verified correctly.
+ """
+ oldAuth = OldClientAuth('foo', FakeTransport.Service())
+ oldAuth.transport = FakeTransport(None)
+ oldAuth.transport.sessionID = 'test'
+ oldAuth.serviceStarted()
+ oldAuth.transport.packets = []
+ self.assertWarns(DeprecationWarning, "Returning a string from "
+ "SSHUserAuthClient.getPublicKey() is deprecated since "
+ "Twisted 9.0. Return a keys.Key() instead.",
+ userauth.__file__, oldAuth.tryAuth, 'publickey')
+ self.assertEquals(oldAuth.transport.packets, [
+ (userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy') +
+ NS('publickey') + '\x00' + NS('ssh-rsa') +
+ NS(keys.Key.fromString(keydata.publicRSA_openssh).blob()))])
+
+
+ def test_old_publickey_getPrivateKey(self):
+ """
+ Old SSHUserAuthClients returned a PyCrypto key object from
+ getPrivateKey(). Test that _cbSignData signs the data warns the
+ user about the deprecation, but signs the data correctly.
+ """
+ oldAuth = OldClientAuth('foo', FakeTransport.Service())
+ d = self.assertWarns(DeprecationWarning, "Returning a PyCrypto key "
+ "object from SSHUserAuthClient.getPrivateKey() is "
+ "deprecated since Twisted 9.0. "
+ "Return a keys.Key() instead.", userauth.__file__,
+ oldAuth.signData, None, 'data')
+ def _checkSignedData(sig):
+ self.assertEquals(sig,
+ keys.Key.fromString(keydata.privateRSA_openssh).sign(
+ 'data'))
+ d.addCallback(_checkSignedData)
+ return d
+
+
+ def test_no_publickey(self):
+ """
+ If there's no public key, auth_publickey should return a Deferred
+ called back with a False value.
+ """
+ self.authClient.getPublicKey = lambda x: None
+ d = self.authClient.tryAuth('publickey')
+ def check(result):
+ self.assertFalse(result)
+ return d.addCallback(check)
+
+ def test_password(self):
+ """
+ Test that the client can authentication with a password. This
+ includes changing the password.
+ """
+ self.authClient.ssh_USERAUTH_FAILURE(NS('password') + '\x00')
+ self.assertEquals(self.authClient.transport.packets[-1],
+ (userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy')
+ + NS('password') + '\x00' + NS('foo')))
+ self.authClient.ssh_USERAUTH_PK_OK(NS('') + NS(''))
+ self.assertEquals(self.authClient.transport.packets[-1],
+ (userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy')
+ + NS('password') + '\xff' + NS('foo') * 2))
+
+
+ def test_no_password(self):
+ """
+ If getPassword returns None, tryAuth should return False.
+ """
+ self.authClient.getPassword = lambda: None
+ self.assertFalse(self.authClient.tryAuth('password'))
+
+
+ def test_keyboardInteractive(self):
+ """
+ Test that the client can authenticate using keyboard-interactive
+ authentication.
+ """
+ self.authClient.ssh_USERAUTH_FAILURE(NS('keyboard-interactive')
+ + '\x00')
+ self.assertEquals(self.authClient.transport.packets[-1],
+ (userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy')
+ + NS('keyboard-interactive') + NS('')*2))
+ self.authClient.ssh_USERAUTH_PK_OK(NS('')*3 + '\x00\x00\x00\x02'
+ + NS('Name: ') + '\xff' + NS('Password: ') + '\x00')
+ self.assertEquals(self.authClient.transport.packets[-1],
+ (userauth.MSG_USERAUTH_INFO_RESPONSE, '\x00\x00\x00\x02'
+ + NS('foo')*2))
+
+
+ def test_USERAUTH_PK_OK_unknown_method(self):
+ """
+ If C{SSHUserAuthClient} gets a MSG_USERAUTH_PK_OK packet when it's not
+ expecting it, it should fail the current authentication and move on to
+ the next type.
+ """
+ self.authClient.lastAuth = 'unknown'
+ self.authClient.transport.packets = []
+ self.authClient.ssh_USERAUTH_PK_OK('')
+ self.assertEquals(self.authClient.transport.packets,
+ [(userauth.MSG_USERAUTH_REQUEST, NS('foo') +
+ NS('nancy') + NS('none'))])
+
+
+ def test_USERAUTH_FAILURE_sorting(self):
+ """
+ ssh_USERAUTH_FAILURE should sort the methods by their position
+ in SSHUserAuthClient.preferredOrder. Methods that are not in
+ preferredOrder should be sorted at the end of that list.
+ """
+ def auth_firstmethod():
+ self.authClient.transport.sendPacket(255, 'here is data')
+ def auth_anothermethod():
+ self.authClient.transport.sendPacket(254, 'other data')
+ return True
+ self.authClient.auth_firstmethod = auth_firstmethod
+ self.authClient.auth_anothermethod = auth_anothermethod
+
+ # although they shouldn't get called, method callbacks auth_* MUST
+ # exist in order for the test to work properly.
+ self.authClient.ssh_USERAUTH_FAILURE(NS('anothermethod,password') +
+ '\x00')
+ # should send password packet
+ self.assertEquals(self.authClient.transport.packets[-1],
+ (userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy')
+ + NS('password') + '\x00' + NS('foo')))
+ self.authClient.ssh_USERAUTH_FAILURE(
+ NS('firstmethod,anothermethod,password') + '\xff')
+ self.assertEquals(self.authClient.transport.packets[-2:],
+ [(255, 'here is data'), (254, 'other data')])
+
+
+ def test_disconnectIfNoMoreAuthentication(self):
+ """
+ If there are no more available user authentication messages,
+ the SSHUserAuthClient should disconnect with code
+ DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE.
+ """
+ self.authClient.ssh_USERAUTH_FAILURE(NS('password') + '\x00')
+ self.authClient.ssh_USERAUTH_FAILURE(NS('password') + '\xff')
+ self.assertEquals(self.authClient.transport.packets[-1],
+ (transport.MSG_DISCONNECT, '\x00\x00\x00\x0e' +
+ NS('no more authentication methods available') +
+ '\x00\x00\x00\x00'))
+
+
+ def test_ebAuth(self):
+ """
+ _ebAuth (the generic authentication error handler) should send
+ a request for the 'none' authentication method.
+ """
+ self.authClient.transport.packets = []
+ self.authClient._ebAuth(None)
+ self.assertEquals(self.authClient.transport.packets,
+ [(userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy')
+ + NS('none'))])
+
+
+ def test_defaults(self):
+ """
+ getPublicKey() should return None. getPrivateKey() should return a
+ failed Deferred. getPassword() should return a failed Deferred.
+ getGenericAnswers() should return a failed Deferred.
+ """
+ authClient = userauth.SSHUserAuthClient('foo', FakeTransport.Service())
+ self.assertIdentical(authClient.getPublicKey(), None)
+ def check(result):
+ result.trap(NotImplementedError)
+ d = authClient.getPassword()
+ return d.addCallback(self.fail).addErrback(check2)
+ def check2(result):
+ result.trap(NotImplementedError)
+ d = authClient.getGenericAnswers(None, None, None)
+ return d.addCallback(self.fail).addErrback(check3)
+ def check3(result):
+ result.trap(NotImplementedError)
+ d = authClient.getPrivateKey()
+ return d.addCallback(self.fail).addErrback(check)
+
+
+
+class LoopbackTestCase(unittest.TestCase):
+
+
+ if keys is None:
+ skip = "cannot run w/o PyCrypto or PyASN1"
+
+
+ class Factory:
+ class Service:
+ name = 'TestService'
+
+
+ def serviceStarted(self):
+ self.transport.loseConnection()
+
+
+ def serviceStopped(self):
+ pass
+
+
+ def getService(self, avatar, name):
+ return self.Service
+
+
+ def test_loopback(self):
+ """
+ Test that the userauth server and client play nicely with each other.
+ """
+ server = userauth.SSHUserAuthServer()
+ client = ClientUserAuth('foo', self.Factory.Service())
+
+ # set up transports
+ server.transport = transport.SSHTransportBase()
+ server.transport.service = server
+ server.transport.isEncrypted = lambda x: True
+ client.transport = transport.SSHTransportBase()
+ client.transport.service = client
+ server.transport.sessionID = client.transport.sessionID = ''
+ # don't send key exchange packet
+ server.transport.sendKexInit = client.transport.sendKexInit = \
+ lambda: None
+
+ # set up server authentication
+ server.transport.factory = self.Factory()
+ server.passwordDelay = 0 # remove bad password delay
+ realm = Realm()
+ portal = Portal(realm)
+ checker = SSHProtocolChecker()
+ checker.registerChecker(PasswordChecker())
+ checker.registerChecker(PrivateKeyChecker())
+ checker.registerChecker(PAMChecker())
+ checker.areDone = lambda aId: (
+ len(checker.successfulCredentials[aId]) == 3)
+ portal.registerChecker(checker)
+ server.transport.factory.portal = portal
+
+ d = loopback.loopbackAsync(server.transport, client.transport)
+ server.transport.transport.logPrefix = lambda: '_ServerLoopback'
+ client.transport.transport.logPrefix = lambda: '_ClientLoopback'
+
+ server.serviceStarted()
+ client.serviceStarted()
+
+ def check(ignored):
+ self.assertEquals(server.transport.service.name, 'TestService')
+ return d.addCallback(check)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/test/test_window.py b/vendor/Twisted-10.0.0/twisted/conch/test/test_window.py
new file mode 100644
index 0000000000..29b5898827
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/test/test_window.py
@@ -0,0 +1,49 @@
+
+"""
+Tests for the insults windowing module, L{twisted.conch.insults.window}.
+"""
+
+from twisted.trial.unittest import TestCase
+
+from twisted.conch.insults.window import TopWindow
+
+
+class TopWindowTests(TestCase):
+ """
+ Tests for L{TopWindow}, the root window container class.
+ """
+
+ def test_paintScheduling(self):
+ """
+ Verify that L{TopWindow.repaint} schedules an actual paint to occur
+ using the scheduling object passed to its initializer.
+ """
+ paints = []
+ scheduled = []
+ root = TopWindow(lambda: paints.append(None), scheduled.append)
+
+ # Nothing should have happened yet.
+ self.assertEqual(paints, [])
+ self.assertEqual(scheduled, [])
+
+ # Cause a paint to be scheduled.
+ root.repaint()
+ self.assertEqual(paints, [])
+ self.assertEqual(len(scheduled), 1)
+
+ # Do another one to verify nothing else happens as long as the previous
+ # one is still pending.
+ root.repaint()
+ self.assertEqual(paints, [])
+ self.assertEqual(len(scheduled), 1)
+
+ # Run the actual paint call.
+ scheduled.pop()()
+ self.assertEqual(len(paints), 1)
+ self.assertEqual(scheduled, [])
+
+ # Do one more to verify that now that the previous one is finished
+ # future paints will succeed.
+ root.repaint()
+ self.assertEqual(len(paints), 1)
+ self.assertEqual(len(scheduled), 1)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/topfiles/NEWS b/vendor/Twisted-10.0.0/twisted/conch/topfiles/NEWS
new file mode 100644
index 0000000000..3be844256d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/topfiles/NEWS
@@ -0,0 +1,206 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/<number>
+
+Twisted Conch 10.0.0 (2010-03-01)
+=================================
+
+Bugfixes
+--------
+ - twisted.conch.checkers.SSHPublicKeyDatabase now looks in the
+ correct user directory for authorized_keys files. (#3984)
+
+ - twisted.conch.ssh.SSHUserAuthClient now honors preferredOrder when
+ authenticating. (#4266)
+
+Other
+-----
+ - #2391, #4203, #4265
+
+
+Twisted Conch 9.0.0 (2009-11-24)
+================================
+
+Fixes
+-----
+ - The SSH key parser has been removed and conch now uses pyASN1 to parse keys.
+ This should fix a number of cases where parsing a key would fail, but it now
+ requires users to have pyASN1 installed (#3391)
+ - The time field on SFTP file listings should now be correct (#3503)
+ - The day field on SFTP file listings should now be correct on Windows (#3503)
+ - The "cftp" sftp client now truncates files it is uploading over (#2519)
+ - The telnet server protocol can now properly respond to subnegotiation
+ requests (#3655)
+ - Tests and factoring of the SSHv2 server implementation are now much better
+ (#2682)
+ - The SSHv2 server now sends "exit-signal" messages to the client, instead of
+ raising an exception, when a process dies due to a signal (#2687)
+ - cftp's client-side "exec" command now uses /bin/sh if the current user has
+ no shell (#3914)
+
+Deprecations and Removals
+-------------------------
+ - The buggy SSH connection sharing feature of the SSHv2 client was removed
+ (#3498)
+ - Use of strings and PyCrypto objects to represent keys is deprecated in favor
+ of using Conch Key objects (#2682)
+
+Other
+-----
+ - #3548, #3537, #3551, #3220, #3568, #3689, #3709, #3809, #2763, #3540, #3750,
+ #3897, #3813, #3871, #3916, #4047, #3940, #4050
+
+
+Conch 8.2.0 (2008-12-16)
+========================
+
+Features
+--------
+ - The type of the protocols instantiated by SSHFactory is now parameterized
+ (#3443)
+
+Fixes
+-----
+ - A file descriptor leak has been fixed (#3213, #1789)
+ - "File Already Exists" errors are now handled more correctly (#3033)
+ - Handling of CR IAC in TelnetClient is now improved (#3305)
+ - SSHAgent is no longer completely unusable (#3332)
+ - The performance of insults.ClientProtocol is now greatly increased by
+ delivering more than one byte at a time to application code (#3386)
+ - Manhole and the conch server no longer need to be run as root when not
+ necessary (#2607)
+ - The value of FILEXFER_ATTR_ACMODTIME has been corrected (#2902)
+ - The management of known_hosts and host key verification has been overhauled
+ (#1376, #1301, #3494, #3496, #1292, #3499)
+
+Other
+-----
+ - #3193, #1633
+
+
+8.1.0 (2008-05-18)
+==================
+
+Fixes
+-----
+ - A regression was fixed whereby the publicKeys and privateKeys attributes of
+ SSHFactory would not be interpreted as strings (#3141)
+ - The sshsimpleserver.py example had a minor bug fix (#3135)
+ - The deprecated mktap API is no longer used (#3127)
+ - An infelicity was fixed whereby a NameError would be raised in certain
+ circumstances during authentication when a ConchError should have been
+ (#3154)
+ - A workaround was added to conch.insults for a bug in gnome-terminal whereby
+ it would not scroll correctly (#3189)
+
+
+8.0.0 (2008-03-17)
+==================
+
+Features
+--------
+ - Add DEC private mode manipulation methods to ITerminalTransport. (#2403)
+
+Fixes
+-----
+ - Parameterize the scheduler function used by the insults TopWindow widget.
+ This change breaks backwards compatibility in the TopWindow initializer.
+ (#2413)
+ - Notify subsystems, like SFTP, of connection close. (#2421)
+ - Change the process file descriptor "connection lost" code to reverse the
+ setNonBlocking operation done during initialization. (#2371)
+ - Change ConsoleManhole to wait for connectionLost notification before
+ stopping the reactor. (#2123, #2371)
+ - Make SSHUserAuthServer.ssh_USERAUTH_REQUEST return a Deferred. (#2528)
+ - Manhole's initializer calls its parent class's initializer with its
+ namespace argument. (#2587)
+ - Handle ^C during input line continuation in manhole by updating the prompt
+ and line buffer correctly. (#2663)
+ - Make twisted.conch.telnet.Telnet by default reject all attempts to enable
+ options. (#1967)
+ - Reduce the number of calls into application code to deliver application-level
+ data in twisted.conch.telnet.Telnet.dataReceived (#2107)
+ - Fix definition and management of extended attributes in conch file transfer.
+ (#3010)
+ - Fix parsing of OpenSSH-generated RSA keys with differing ASN.1 packing style.
+ (#3008)
+ - Fix handling of missing $HOME in twisted.conch.client.unix. (#3061)
+
+Misc
+----
+ - #2267, #2378, #2604, #2707, #2341, #2685, #2679, #2912, #2977, #2678, #2709
+ #2063, #2847
+
+
+0.8.0 (2007-01-06)
+==================
+
+Features
+--------
+ - Manhole now supports Ctrl-l to emulate the same behavior in the
+ Python interactive interpreter (#1565)
+ - Python 2.5 is now supported (#1867)
+
+Misc
+----
+ - #1673, #1636, #1892, #1943, #2057, #1180, #1185, #2148, #2159, #2291,
+
+Deprecations and Removals
+-------------------------
+
+ - The old twisted.cred API (Identities, Authorizers, etc) is no
+ longer supported (#1440)
+
+
+0.7.0 (2006-05-21)
+==================
+
+Features
+--------
+ - Timeout support for ExpectableBuffer.expect()
+
+Fixes
+-----
+ - ~5x speedup for bulk data transfer (#1325)
+ - Misc: #1428
+
+0.6.0:
+
+ Bugfixes and improvements in SSH support and Insults:
+ - PAM authenticator support factored out into twisted.cred
+ - Poorly supported next-line terminal operation replaced with simple \r\n
+
+ New functionality:
+ - An ITerminalTransport implementation with expect-like features
+ - Support for the "none" SSH cipher
+ - Insults support for handling more keystrokes and more methods for
+ terminal manipulation
+ - New, simple insults-based widget library added
+
+ Better test coverage:
+ - Dependence on `localhost' name removed
+ - Some timing-sensitive tests changed to be more reliable
+ - Process spawning tests initialize environment more robustly
+
+0.5.0:
+
+ Many improvements to SSH support. Here's some in particular:
+ - Add --reconnect option to conch binary
+ - utmp/wtmp logging
+ - Unix login improvements, PAM support
+ - Add "cftp" -- Conch SFTP.
+ - Deferred retrieval of public keys is supported
+ - PAM support for client and server
+ - Bugfixes:
+ - fix conch failing to exit, and hangs.
+ - Remote->Local forwarding
+ - Channel closing
+ - Invalid known_host writing
+ - Many others
+
+ New functionality:
+ - twisted.conch.telnet: new, much improved telnet implementation.
+ - twisted.conch.insults: Basic curses-like terminal support (server-side).
+ - twisted.conch.manhole: new interactive python interactive interpreter,
+ can be used with conch's telnet, ssh, or on the console.
+ - Main features: Syntax coloring, line editing, and useful interactive
+ handling of Deferreds.
diff --git a/vendor/Twisted-10.0.0/twisted/conch/topfiles/README b/vendor/Twisted-10.0.0/twisted/conch/topfiles/README
new file mode 100644
index 0000000000..f0745a5778
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/topfiles/README
@@ -0,0 +1,4 @@
+Twisted Conch 10.0.0
+
+Conch depends on Python Crypto (<http://www.pycrypto.org>)
+extensions and the pyasn1 module (<http://pyasn1.sourceforge.net/>).
diff --git a/vendor/Twisted-10.0.0/twisted/conch/topfiles/setup.py b/vendor/Twisted-10.0.0/twisted/conch/topfiles/setup.py
new file mode 100644
index 0000000000..5f4090dafa
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/topfiles/setup.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+
+try:
+ from twisted.python import dist
+except ImportError:
+ raise SystemExit("twisted.python.dist module not found. Make sure you "
+ "have installed the Twisted core package before "
+ "attempting to install any other Twisted projects.")
+
+if __name__ == '__main__':
+ if sys.version_info[:2] >= (2, 4):
+ extraMeta = dict(
+ classifiers=[
+ "Development Status :: 4 - Beta",
+ "Environment :: Console",
+ "Environment :: No Input/Output (Daemon)",
+ "Intended Audience :: Developers",
+ "Intended Audience :: End Users/Desktop",
+ "Intended Audience :: System Administrators",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python",
+ "Topic :: Internet",
+ "Topic :: Security",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Terminals",
+ ])
+ else:
+ extraMeta = {}
+
+ dist.setup(
+ twisted_subproject="conch",
+ scripts=dist.getScripts("conch"),
+ # metadata
+ name="Twisted Conch",
+ description="Twisted SSHv2 implementation.",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="Paul Swartz",
+ url="http://twistedmatrix.com/trac/wiki/TwistedConch",
+ license="MIT",
+ long_description="""\
+Conch is an SSHv2 implementation using the Twisted framework. It
+includes a server, client, a SFTP client, and a key generator.
+""",
+ **extraMeta)
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ttymodes.py b/vendor/Twisted-10.0.0/twisted/conch/ttymodes.py
new file mode 100644
index 0000000000..020c10473a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ttymodes.py
@@ -0,0 +1,121 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+import tty
+# this module was autogenerated.
+
+VINTR = 1
+VQUIT = 2
+VERASE = 3
+VKILL = 4
+VEOF = 5
+VEOL = 6
+VEOL2 = 7
+VSTART = 8
+VSTOP = 9
+VSUSP = 10
+VDSUSP = 11
+VREPRINT = 12
+VWERASE = 13
+VLNEXT = 14
+VFLUSH = 15
+VSWTCH = 16
+VSTATUS = 17
+VDISCARD = 18
+IGNPAR = 30
+PARMRK = 31
+INPCK = 32
+ISTRIP = 33
+INLCR = 34
+IGNCR = 35
+ICRNL = 36
+IUCLC = 37
+IXON = 38
+IXANY = 39
+IXOFF = 40
+IMAXBEL = 41
+ISIG = 50
+ICANON = 51
+XCASE = 52
+ECHO = 53
+ECHOE = 54
+ECHOK = 55
+ECHONL = 56
+NOFLSH = 57
+TOSTOP = 58
+IEXTEN = 59
+ECHOCTL = 60
+ECHOKE = 61
+PENDIN = 62
+OPOST = 70
+OLCUC = 71
+ONLCR = 72
+OCRNL = 73
+ONOCR = 74
+ONLRET = 75
+CS7 = 90
+CS8 = 91
+PARENB = 92
+PARODD = 93
+TTY_OP_ISPEED = 128
+TTY_OP_OSPEED = 129
+
+TTYMODES = {
+ 1 : 'VINTR',
+ 2 : 'VQUIT',
+ 3 : 'VERASE',
+ 4 : 'VKILL',
+ 5 : 'VEOF',
+ 6 : 'VEOL',
+ 7 : 'VEOL2',
+ 8 : 'VSTART',
+ 9 : 'VSTOP',
+ 10 : 'VSUSP',
+ 11 : 'VDSUSP',
+ 12 : 'VREPRINT',
+ 13 : 'VWERASE',
+ 14 : 'VLNEXT',
+ 15 : 'VFLUSH',
+ 16 : 'VSWTCH',
+ 17 : 'VSTATUS',
+ 18 : 'VDISCARD',
+ 30 : (tty.IFLAG, 'IGNPAR'),
+ 31 : (tty.IFLAG, 'PARMRK'),
+ 32 : (tty.IFLAG, 'INPCK'),
+ 33 : (tty.IFLAG, 'ISTRIP'),
+ 34 : (tty.IFLAG, 'INLCR'),
+ 35 : (tty.IFLAG, 'IGNCR'),
+ 36 : (tty.IFLAG, 'ICRNL'),
+ 37 : (tty.IFLAG, 'IUCLC'),
+ 38 : (tty.IFLAG, 'IXON'),
+ 39 : (tty.IFLAG, 'IXANY'),
+ 40 : (tty.IFLAG, 'IXOFF'),
+ 41 : (tty.IFLAG, 'IMAXBEL'),
+ 50 : (tty.LFLAG, 'ISIG'),
+ 51 : (tty.LFLAG, 'ICANON'),
+ 52 : (tty.LFLAG, 'XCASE'),
+ 53 : (tty.LFLAG, 'ECHO'),
+ 54 : (tty.LFLAG, 'ECHOE'),
+ 55 : (tty.LFLAG, 'ECHOK'),
+ 56 : (tty.LFLAG, 'ECHONL'),
+ 57 : (tty.LFLAG, 'NOFLSH'),
+ 58 : (tty.LFLAG, 'TOSTOP'),
+ 59 : (tty.LFLAG, 'IEXTEN'),
+ 60 : (tty.LFLAG, 'ECHOCTL'),
+ 61 : (tty.LFLAG, 'ECHOKE'),
+ 62 : (tty.LFLAG, 'PENDIN'),
+ 70 : (tty.OFLAG, 'OPOST'),
+ 71 : (tty.OFLAG, 'OLCUC'),
+ 72 : (tty.OFLAG, 'ONLCR'),
+ 73 : (tty.OFLAG, 'OCRNL'),
+ 74 : (tty.OFLAG, 'ONOCR'),
+ 75 : (tty.OFLAG, 'ONLRET'),
+# 90 : (tty.CFLAG, 'CS7'),
+# 91 : (tty.CFLAG, 'CS8'),
+ 92 : (tty.CFLAG, 'PARENB'),
+ 93 : (tty.CFLAG, 'PARODD'),
+ 128 : 'ISPEED',
+ 129 : 'OSPEED'
+}
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ui/__init__.py b/vendor/Twisted-10.0.0/twisted/conch/ui/__init__.py
new file mode 100755
index 0000000000..f20821960b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ui/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+
+"""
+twisted.conch.ui is home to the UI elements for tkconch.
+
+Maintainer: Paul Swartz
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ui/ansi.py b/vendor/Twisted-10.0.0/twisted/conch/ui/ansi.py
new file mode 100644
index 0000000000..68281c80a3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ui/ansi.py
@@ -0,0 +1,240 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""Module to parse ANSI escape sequences
+
+Maintainer: Jean-Paul Calderone
+"""
+
+import string
+
+# Twisted imports
+from twisted.python import log
+
+class ColorText:
+ """
+ Represents an element of text along with the texts colors and
+ additional attributes.
+ """
+
+ # The colors to use
+ COLORS = ('b', 'r', 'g', 'y', 'l', 'm', 'c', 'w')
+ BOLD_COLORS = tuple([x.upper() for x in COLORS])
+ BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(len(COLORS))
+
+ # Color names
+ COLOR_NAMES = (
+ 'Black', 'Red', 'Green', 'Yellow', 'Blue', 'Magenta', 'Cyan', 'White'
+ )
+
+ def __init__(self, text, fg, bg, display, bold, underline, flash, reverse):
+ self.text, self.fg, self.bg = text, fg, bg
+ self.display = display
+ self.bold = bold
+ self.underline = underline
+ self.flash = flash
+ self.reverse = reverse
+ if self.reverse:
+ self.fg, self.bg = self.bg, self.fg
+
+
+class AnsiParser:
+ """
+ Parser class for ANSI codes.
+ """
+
+ # Terminators for cursor movement ansi controls - unsupported
+ CURSOR_SET = ('H', 'f', 'A', 'B', 'C', 'D', 'R', 's', 'u', 'd','G')
+
+ # Terminators for erasure ansi controls - unsupported
+ ERASE_SET = ('J', 'K', 'P')
+
+ # Terminators for mode change ansi controls - unsupported
+ MODE_SET = ('h', 'l')
+
+ # Terminators for keyboard assignment ansi controls - unsupported
+ ASSIGN_SET = ('p',)
+
+ # Terminators for color change ansi controls - supported
+ COLOR_SET = ('m',)
+
+ SETS = (CURSOR_SET, ERASE_SET, MODE_SET, ASSIGN_SET, COLOR_SET)
+
+ def __init__(self, defaultFG, defaultBG):
+ self.defaultFG, self.defaultBG = defaultFG, defaultBG
+ self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
+ self.bold, self.flash, self.underline, self.reverse = 0, 0, 0, 0
+ self.display = 1
+ self.prepend = ''
+
+
+ def stripEscapes(self, string):
+ """
+ Remove all ANSI color escapes from the given string.
+ """
+ result = ''
+ show = 1
+ i = 0
+ L = len(string)
+ while i < L:
+ if show == 0 and string[i] in _sets:
+ show = 1
+ elif show:
+ n = string.find('\x1B', i)
+ if n == -1:
+ return result + string[i:]
+ else:
+ result = result + string[i:n]
+ i = n
+ show = 0
+ i = i + 1
+ return result
+
+ def writeString(self, colorstr):
+ pass
+
+ def parseString(self, str):
+ """
+ Turn a string input into a list of L{ColorText} elements.
+ """
+
+ if self.prepend:
+ str = self.prepend + str
+ self.prepend = ''
+ parts = str.split('\x1B')
+
+ if len(parts) == 1:
+ self.writeString(self.formatText(parts[0]))
+ else:
+ self.writeString(self.formatText(parts[0]))
+ for s in parts[1:]:
+ L = len(s)
+ i = 0
+ type = None
+ while i < L:
+ if s[i] not in string.digits+'[;?':
+ break
+ i+=1
+ if not s:
+ self.prepend = '\x1b'
+ return
+ if s[0]!='[':
+ self.writeString(self.formatText(s[i+1:]))
+ continue
+ else:
+ s=s[1:]
+ i-=1
+ if i==L-1:
+ self.prepend = '\x1b['
+ return
+ type = _setmap.get(s[i], None)
+ if type is None:
+ continue
+
+ if type == AnsiParser.COLOR_SET:
+ self.parseColor(s[:i + 1])
+ s = s[i + 1:]
+ self.writeString(self.formatText(s))
+ elif type == AnsiParser.CURSOR_SET:
+ cursor, s = s[:i+1], s[i+1:]
+ self.parseCursor(cursor)
+ self.writeString(self.formatText(s))
+ elif type == AnsiParser.ERASE_SET:
+ erase, s = s[:i+1], s[i+1:]
+ self.parseErase(erase)
+ self.writeString(self.formatText(s))
+ elif type == AnsiParser.MODE_SET:
+ mode, s = s[:i+1], s[i+1:]
+ #self.parseErase('2J')
+ self.writeString(self.formatText(s))
+ elif i == L:
+ self.prepend = '\x1B[' + s
+ else:
+ log.msg('Unhandled ANSI control type: %c' % (s[i],))
+ s = s[i + 1:]
+ self.writeString(self.formatText(s))
+
+ def parseColor(self, str):
+ """
+ Handle a single ANSI color sequence
+ """
+ # Drop the trailing 'm'
+ str = str[:-1]
+
+ if not str:
+ str = '0'
+
+ try:
+ parts = map(int, str.split(';'))
+ except ValueError:
+ log.msg('Invalid ANSI color sequence (%d): %s' % (len(str), str))
+ self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
+ return
+
+ for x in parts:
+ if x == 0:
+ self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
+ self.bold, self.flash, self.underline, self.reverse = 0, 0, 0, 0
+ self.display = 1
+ elif x == 1:
+ self.bold = 1
+ elif 30 <= x <= 37:
+ self.currentFG = x - 30
+ elif 40 <= x <= 47:
+ self.currentBG = x - 40
+ elif x == 39:
+ self.currentFG = self.defaultFG
+ elif x == 49:
+ self.currentBG = self.defaultBG
+ elif x == 4:
+ self.underline = 1
+ elif x == 5:
+ self.flash = 1
+ elif x == 7:
+ self.reverse = 1
+ elif x == 8:
+ self.display = 0
+ elif x == 22:
+ self.bold = 0
+ elif x == 24:
+ self.underline = 0
+ elif x == 25:
+ self.blink = 0
+ elif x == 27:
+ self.reverse = 0
+ elif x == 28:
+ self.display = 1
+ else:
+ log.msg('Unrecognised ANSI color command: %d' % (x,))
+
+ def parseCursor(self, cursor):
+ pass
+
+ def parseErase(self, erase):
+ pass
+
+
+ def pickColor(self, value, mode, BOLD = ColorText.BOLD_COLORS):
+ if mode:
+ return ColorText.COLORS[value]
+ else:
+ return self.bold and BOLD[value] or ColorText.COLORS[value]
+
+
+ def formatText(self, text):
+ return ColorText(
+ text,
+ self.pickColor(self.currentFG, 0),
+ self.pickColor(self.currentBG, 1),
+ self.display, self.bold, self.underline, self.flash, self.reverse
+ )
+
+
+_sets = ''.join(map(''.join, AnsiParser.SETS))
+
+_setmap = {}
+for s in AnsiParser.SETS:
+ for r in s:
+ _setmap[r] = s
+del s
diff --git a/vendor/Twisted-10.0.0/twisted/conch/ui/tkvt100.py b/vendor/Twisted-10.0.0/twisted/conch/ui/tkvt100.py
new file mode 100644
index 0000000000..87d6f1eb0a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/ui/tkvt100.py
@@ -0,0 +1,197 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""Module to emulate a VT100 terminal in Tkinter.
+
+Maintainer: Paul Swartz
+"""
+
+import Tkinter, tkFont
+import ansi
+import string
+
+ttyFont = None#tkFont.Font(family = 'Courier', size = 10)
+fontWidth, fontHeight = None,None#max(map(ttyFont.measure, string.letters+string.digits)), int(ttyFont.metrics()['linespace'])
+
+colorKeys = (
+ 'b', 'r', 'g', 'y', 'l', 'm', 'c', 'w',
+ 'B', 'R', 'G', 'Y', 'L', 'M', 'C', 'W'
+)
+
+colorMap = {
+ 'b': '#000000', 'r': '#c40000', 'g': '#00c400', 'y': '#c4c400',
+ 'l': '#000080', 'm': '#c400c4', 'c': '#00c4c4', 'w': '#c4c4c4',
+ 'B': '#626262', 'R': '#ff0000', 'G': '#00ff00', 'Y': '#ffff00',
+ 'L': '#0000ff', 'M': '#ff00ff', 'C': '#00ffff', 'W': '#ffffff',
+}
+
+class VT100Frame(Tkinter.Frame):
+ def __init__(self, *args, **kw):
+ global ttyFont, fontHeight, fontWidth
+ ttyFont = tkFont.Font(family = 'Courier', size = 10)
+ fontWidth, fontHeight = max(map(ttyFont.measure, string.letters+string.digits)), int(ttyFont.metrics()['linespace'])
+ self.width = kw.get('width', 80)
+ self.height = kw.get('height', 25)
+ self.callback = kw['callback']
+ del kw['callback']
+ kw['width'] = w = fontWidth * self.width
+ kw['height'] = h = fontHeight * self.height
+ Tkinter.Frame.__init__(self, *args, **kw)
+ self.canvas = Tkinter.Canvas(bg='#000000', width=w, height=h)
+ self.canvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
+ self.canvas.bind('<Key>', self.keyPressed)
+ self.canvas.bind('<1>', lambda x: 'break')
+ self.canvas.bind('<Up>', self.upPressed)
+ self.canvas.bind('<Down>', self.downPressed)
+ self.canvas.bind('<Left>', self.leftPressed)
+ self.canvas.bind('<Right>', self.rightPressed)
+ self.canvas.focus()
+
+ self.ansiParser = ansi.AnsiParser(ansi.ColorText.WHITE, ansi.ColorText.BLACK)
+ self.ansiParser.writeString = self.writeString
+ self.ansiParser.parseCursor = self.parseCursor
+ self.ansiParser.parseErase = self.parseErase
+ #for (a, b) in colorMap.items():
+ # self.canvas.tag_config(a, foreground=b)
+ # self.canvas.tag_config('b'+a, background=b)
+ #self.canvas.tag_config('underline', underline=1)
+
+ self.x = 0
+ self.y = 0
+ self.cursor = self.canvas.create_rectangle(0,0,fontWidth-1,fontHeight-1,fill='green',outline='green')
+
+ def _delete(self, sx, sy, ex, ey):
+ csx = sx*fontWidth + 1
+ csy = sy*fontHeight + 1
+ cex = ex*fontWidth + 3
+ cey = ey*fontHeight + 3
+ items = self.canvas.find_overlapping(csx,csy, cex,cey)
+ for item in items:
+ self.canvas.delete(item)
+
+ def _write(self, ch, fg, bg):
+ if self.x == self.width:
+ self.x = 0
+ self.y+=1
+ if self.y == self.height:
+ [self.canvas.move(x,0,-fontHeight) for x in self.canvas.find_all()]
+ self.y-=1
+ canvasX = self.x*fontWidth + 1
+ canvasY = self.y*fontHeight + 1
+ items = self.canvas.find_overlapping(canvasX, canvasY, canvasX+2, canvasY+2)
+ if items:
+ [self.canvas.delete(item) for item in items]
+ if bg:
+ self.canvas.create_rectangle(canvasX, canvasY, canvasX+fontWidth-1, canvasY+fontHeight-1, fill=bg, outline=bg)
+ self.canvas.create_text(canvasX, canvasY, anchor=Tkinter.NW, font=ttyFont, text=ch, fill=fg)
+ self.x+=1
+
+ def write(self, data):
+ #print self.x,self.y,repr(data)
+ #if len(data)>5: raw_input()
+ self.ansiParser.parseString(data)
+ self.canvas.delete(self.cursor)
+ canvasX = self.x*fontWidth + 1
+ canvasY = self.y*fontHeight + 1
+ self.cursor = self.canvas.create_rectangle(canvasX,canvasY,canvasX+fontWidth-1,canvasY+fontHeight-1, fill='green', outline='green')
+ self.canvas.lower(self.cursor)
+
+ def writeString(self, i):
+ if not i.display:
+ return
+ fg = colorMap[i.fg]
+ bg = i.bg != 'b' and colorMap[i.bg]
+ for ch in i.text:
+ b = ord(ch)
+ if b == 7: # bell
+ self.bell()
+ elif b == 8: # BS
+ if self.x:
+ self.x-=1
+ elif b == 9: # TAB
+ [self._write(' ',fg,bg) for i in range(8)]
+ elif b == 10:
+ if self.y == self.height-1:
+ self._delete(0,0,self.width,0)
+ [self.canvas.move(x,0,-fontHeight) for x in self.canvas.find_all()]
+ else:
+ self.y+=1
+ elif b == 13:
+ self.x = 0
+ elif 32 <= b < 127:
+ self._write(ch, fg, bg)
+
+ def parseErase(self, erase):
+ if ';' in erase:
+ end = erase[-1]
+ parts = erase[:-1].split(';')
+ [self.parseErase(x+end) for x in parts]
+ return
+ start = 0
+ x,y = self.x, self.y
+ if len(erase) > 1:
+ start = int(erase[:-1])
+ if erase[-1] == 'J':
+ if start == 0:
+ self._delete(x,y,self.width,self.height)
+ else:
+ self._delete(0,0,self.width,self.height)
+ self.x = 0
+ self.y = 0
+ elif erase[-1] == 'K':
+ if start == 0:
+ self._delete(x,y,self.width,y)
+ elif start == 1:
+ self._delete(0,y,x,y)
+ self.x = 0
+ else:
+ self._delete(0,y,self.width,y)
+ self.x = 0
+ elif erase[-1] == 'P':
+ self._delete(x,y,x+start,y)
+
+ def parseCursor(self, cursor):
+ #if ';' in cursor and cursor[-1]!='H':
+ # end = cursor[-1]
+ # parts = cursor[:-1].split(';')
+ # [self.parseCursor(x+end) for x in parts]
+ # return
+ start = 1
+ if len(cursor) > 1 and cursor[-1]!='H':
+ start = int(cursor[:-1])
+ if cursor[-1] == 'C':
+ self.x+=start
+ elif cursor[-1] == 'D':
+ self.x-=start
+ elif cursor[-1]=='d':
+ self.y=start-1
+ elif cursor[-1]=='G':
+ self.x=start-1
+ elif cursor[-1]=='H':
+ if len(cursor)>1:
+ y,x = map(int, cursor[:-1].split(';'))
+ y-=1
+ x-=1
+ else:
+ x,y=0,0
+ self.x = x
+ self.y = y
+
+ def keyPressed(self, event):
+ if self.callback and event.char:
+ self.callback(event.char)
+ return 'break'
+
+ def upPressed(self, event):
+ self.callback('\x1bOA')
+
+ def downPressed(self, event):
+ self.callback('\x1bOB')
+
+ def rightPressed(self, event):
+ self.callback('\x1bOC')
+
+ def leftPressed(self, event):
+ self.callback('\x1bOD')
diff --git a/vendor/Twisted-10.0.0/twisted/conch/unix.py b/vendor/Twisted-10.0.0/twisted/conch/unix.py
new file mode 100644
index 0000000000..c1e0675cda
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/conch/unix.py
@@ -0,0 +1,457 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.cred import portal
+from twisted.python import components, log
+from twisted.internet.error import ProcessExitedAlready
+from zope import interface
+from ssh import session, forwarding, filetransfer
+from ssh.filetransfer import FXF_READ, FXF_WRITE, FXF_APPEND, FXF_CREAT, FXF_TRUNC, FXF_EXCL
+from twisted.conch.ls import lsLine
+
+from avatar import ConchUser
+from error import ConchError
+from interfaces import ISession, ISFTPServer, ISFTPFile
+
+import struct, os, time, socket
+import fcntl, tty
+import pwd, grp
+import pty
+import ttymodes
+
+try:
+ import utmp
+except ImportError:
+ utmp = None
+
+class UnixSSHRealm:
+ interface.implements(portal.IRealm)
+
+ def requestAvatar(self, username, mind, *interfaces):
+ user = UnixConchUser(username)
+ return interfaces[0], user, user.logout
+
+
+class UnixConchUser(ConchUser):
+
+ def __init__(self, username):
+ ConchUser.__init__(self)
+ self.username = username
+ self.pwdData = pwd.getpwnam(self.username)
+ l = [self.pwdData[3]]
+ for groupname, password, gid, userlist in grp.getgrall():
+ if username in userlist:
+ l.append(gid)
+ self.otherGroups = l
+ self.listeners = {} # dict mapping (interface, port) -> listener
+ self.channelLookup.update(
+ {"session": session.SSHSession,
+ "direct-tcpip": forwarding.openConnectForwardingClient})
+
+ self.subsystemLookup.update(
+ {"sftp": filetransfer.FileTransferServer})
+
+ def getUserGroupId(self):
+ return self.pwdData[2:4]
+
+ def getOtherGroups(self):
+ return self.otherGroups
+
+ def getHomeDir(self):
+ return self.pwdData[5]
+
+ def getShell(self):
+ return self.pwdData[6]
+
+ def global_tcpip_forward(self, data):
+ hostToBind, portToBind = forwarding.unpackGlobal_tcpip_forward(data)
+ from twisted.internet import reactor
+ try: listener = self._runAsUser(
+ reactor.listenTCP, portToBind,
+ forwarding.SSHListenForwardingFactory(self.conn,
+ (hostToBind, portToBind),
+ forwarding.SSHListenServerForwardingChannel),
+ interface = hostToBind)
+ except:
+ return 0
+ else:
+ self.listeners[(hostToBind, portToBind)] = listener
+ if portToBind == 0:
+ portToBind = listener.getHost()[2] # the port
+ return 1, struct.pack('>L', portToBind)
+ else:
+ return 1
+
+ def global_cancel_tcpip_forward(self, data):
+ hostToBind, portToBind = forwarding.unpackGlobal_tcpip_forward(data)
+ listener = self.listeners.get((hostToBind, portToBind), None)
+ if not listener:
+ return 0
+ del self.listeners[(hostToBind, portToBind)]
+ self._runAsUser(listener.stopListening)
+ return 1
+
+ def logout(self):
+ # remove all listeners
+ for listener in self.listeners.itervalues():
+ self._runAsUser(listener.stopListening)
+ log.msg('avatar %s logging out (%i)' % (self.username, len(self.listeners)))
+
+ def _runAsUser(self, f, *args, **kw):
+ euid = os.geteuid()
+ egid = os.getegid()
+ groups = os.getgroups()
+ uid, gid = self.getUserGroupId()
+ os.setegid(0)
+ os.seteuid(0)
+ os.setgroups(self.getOtherGroups())
+ os.setegid(gid)
+ os.seteuid(uid)
+ try:
+ f = iter(f)
+ except TypeError:
+ f = [(f, args, kw)]
+ try:
+ for i in f:
+ func = i[0]
+ args = len(i)>1 and i[1] or ()
+ kw = len(i)>2 and i[2] or {}
+ r = func(*args, **kw)
+ finally:
+ os.setegid(0)
+ os.seteuid(0)
+ os.setgroups(groups)
+ os.setegid(egid)
+ os.seteuid(euid)
+ return r
+
+class SSHSessionForUnixConchUser:
+
+ interface.implements(ISession)
+
+ def __init__(self, avatar):
+ self.avatar = avatar
+ self. environ = {'PATH':'/bin:/usr/bin:/usr/local/bin'}
+ self.pty = None
+ self.ptyTuple = 0
+
+ def addUTMPEntry(self, loggedIn=1):
+ if not utmp:
+ return
+ ipAddress = self.avatar.conn.transport.transport.getPeer().host
+ packedIp ,= struct.unpack('L', socket.inet_aton(ipAddress))
+ ttyName = self.ptyTuple[2][5:]
+ t = time.time()
+ t1 = int(t)
+ t2 = int((t-t1) * 1e6)
+ entry = utmp.UtmpEntry()
+ entry.ut_type = loggedIn and utmp.USER_PROCESS or utmp.DEAD_PROCESS
+ entry.ut_pid = self.pty.pid
+ entry.ut_line = ttyName
+ entry.ut_id = ttyName[-4:]
+ entry.ut_tv = (t1,t2)
+ if loggedIn:
+ entry.ut_user = self.avatar.username
+ entry.ut_host = socket.gethostbyaddr(ipAddress)[0]
+ entry.ut_addr_v6 = (packedIp, 0, 0, 0)
+ a = utmp.UtmpRecord(utmp.UTMP_FILE)
+ a.pututline(entry)
+ a.endutent()
+ b = utmp.UtmpRecord(utmp.WTMP_FILE)
+ b.pututline(entry)
+ b.endutent()
+
+
+ def getPty(self, term, windowSize, modes):
+ self.environ['TERM'] = term
+ self.winSize = windowSize
+ self.modes = modes
+ master, slave = pty.openpty()
+ ttyname = os.ttyname(slave)
+ self.environ['SSH_TTY'] = ttyname
+ self.ptyTuple = (master, slave, ttyname)
+
+ def openShell(self, proto):
+ from twisted.internet import reactor
+ if not self.ptyTuple: # we didn't get a pty-req
+ log.msg('tried to get shell without pty, failing')
+ raise ConchError("no pty")
+ uid, gid = self.avatar.getUserGroupId()
+ homeDir = self.avatar.getHomeDir()
+ shell = self.avatar.getShell()
+ self.environ['USER'] = self.avatar.username
+ self.environ['HOME'] = homeDir
+ self.environ['SHELL'] = shell
+ shellExec = os.path.basename(shell)
+ peer = self.avatar.conn.transport.transport.getPeer()
+ host = self.avatar.conn.transport.transport.getHost()
+ self.environ['SSH_CLIENT'] = '%s %s %s' % (peer.host, peer.port, host.port)
+ self.getPtyOwnership()
+ self.pty = reactor.spawnProcess(proto, \
+ shell, ['-%s' % shellExec], self.environ, homeDir, uid, gid,
+ usePTY = self.ptyTuple)
+ self.addUTMPEntry()
+ fcntl.ioctl(self.pty.fileno(), tty.TIOCSWINSZ,
+ struct.pack('4H', *self.winSize))
+ if self.modes:
+ self.setModes()
+ self.oldWrite = proto.transport.write
+ proto.transport.write = self._writeHack
+ self.avatar.conn.transport.transport.setTcpNoDelay(1)
+
+ def execCommand(self, proto, cmd):
+ from twisted.internet import reactor
+ uid, gid = self.avatar.getUserGroupId()
+ homeDir = self.avatar.getHomeDir()
+ shell = self.avatar.getShell() or '/bin/sh'
+ command = (shell, '-c', cmd)
+ peer = self.avatar.conn.transport.transport.getPeer()
+ host = self.avatar.conn.transport.transport.getHost()
+ self.environ['SSH_CLIENT'] = '%s %s %s' % (peer.host, peer.port, host.port)
+ if self.ptyTuple:
+ self.getPtyOwnership()
+ self.pty = reactor.spawnProcess(proto, \
+ shell, command, self.environ, homeDir,
+ uid, gid, usePTY = self.ptyTuple or 0)
+ if self.ptyTuple:
+ self.addUTMPEntry()
+ if self.modes:
+ self.setModes()
+# else:
+# tty.setraw(self.pty.pipes[0].fileno(), tty.TCSANOW)
+ self.avatar.conn.transport.transport.setTcpNoDelay(1)
+
+ def getPtyOwnership(self):
+ ttyGid = os.stat(self.ptyTuple[2])[5]
+ uid, gid = self.avatar.getUserGroupId()
+ euid, egid = os.geteuid(), os.getegid()
+ os.setegid(0)
+ os.seteuid(0)
+ try:
+ os.chown(self.ptyTuple[2], uid, ttyGid)
+ finally:
+ os.setegid(egid)
+ os.seteuid(euid)
+
+ def setModes(self):
+ pty = self.pty
+ attr = tty.tcgetattr(pty.fileno())
+ for mode, modeValue in self.modes:
+ if not ttymodes.TTYMODES.has_key(mode): continue
+ ttyMode = ttymodes.TTYMODES[mode]
+ if len(ttyMode) == 2: # flag
+ flag, ttyAttr = ttyMode
+ if not hasattr(tty, ttyAttr): continue
+ ttyval = getattr(tty, ttyAttr)
+ if modeValue:
+ attr[flag] = attr[flag]|ttyval
+ else:
+ attr[flag] = attr[flag]&~ttyval
+ elif ttyMode == 'OSPEED':
+ attr[tty.OSPEED] = getattr(tty, 'B%s'%modeValue)
+ elif ttyMode == 'ISPEED':
+ attr[tty.ISPEED] = getattr(tty, 'B%s'%modeValue)
+ else:
+ if not hasattr(tty, ttyMode): continue
+ ttyval = getattr(tty, ttyMode)
+ attr[tty.CC][ttyval] = chr(modeValue)
+ tty.tcsetattr(pty.fileno(), tty.TCSANOW, attr)
+
+ def eofReceived(self):
+ if self.pty:
+ self.pty.closeStdin()
+
+ def closed(self):
+ if self.ptyTuple and os.path.exists(self.ptyTuple[2]):
+ ttyGID = os.stat(self.ptyTuple[2])[5]
+ os.chown(self.ptyTuple[2], 0, ttyGID)
+ if self.pty:
+ try:
+ self.pty.signalProcess('HUP')
+ except (OSError,ProcessExitedAlready):
+ pass
+ self.pty.loseConnection()
+ self.addUTMPEntry(0)
+ log.msg('shell closed')
+
+ def windowChanged(self, winSize):
+ self.winSize = winSize
+ fcntl.ioctl(self.pty.fileno(), tty.TIOCSWINSZ,
+ struct.pack('4H', *self.winSize))
+
+ def _writeHack(self, data):
+ """
+ Hack to send ignore messages when we aren't echoing.
+ """
+ if self.pty is not None:
+ attr = tty.tcgetattr(self.pty.fileno())[3]
+ if not attr & tty.ECHO and attr & tty.ICANON: # no echo
+ self.avatar.conn.transport.sendIgnore('\x00'*(8+len(data)))
+ self.oldWrite(data)
+
+
+class SFTPServerForUnixConchUser:
+
+ interface.implements(ISFTPServer)
+
+ def __init__(self, avatar):
+ self.avatar = avatar
+
+
+ def _setAttrs(self, path, attrs):
+ """
+ NOTE: this function assumes it runs as the logged-in user:
+ i.e. under _runAsUser()
+ """
+ if attrs.has_key("uid") and attrs.has_key("gid"):
+ os.chown(path, attrs["uid"], attrs["gid"])
+ if attrs.has_key("permissions"):
+ os.chmod(path, attrs["permissions"])
+ if attrs.has_key("atime") and attrs.has_key("mtime"):
+ os.utime(path, (attrs["atime"], attrs["mtime"]))
+
+ def _getAttrs(self, s):
+ return {
+ "size" : s.st_size,
+ "uid" : s.st_uid,
+ "gid" : s.st_gid,
+ "permissions" : s.st_mode,
+ "atime" : int(s.st_atime),
+ "mtime" : int(s.st_mtime)
+ }
+
+ def _absPath(self, path):
+ home = self.avatar.getHomeDir()
+ return os.path.abspath(os.path.join(home, path))
+
+ def gotVersion(self, otherVersion, extData):
+ return {}
+
+ def openFile(self, filename, flags, attrs):
+ return UnixSFTPFile(self, self._absPath(filename), flags, attrs)
+
+ def removeFile(self, filename):
+ filename = self._absPath(filename)
+ return self.avatar._runAsUser(os.remove, filename)
+
+ def renameFile(self, oldpath, newpath):
+ oldpath = self._absPath(oldpath)
+ newpath = self._absPath(newpath)
+ return self.avatar._runAsUser(os.rename, oldpath, newpath)
+
+ def makeDirectory(self, path, attrs):
+ path = self._absPath(path)
+ return self.avatar._runAsUser([(os.mkdir, (path,)),
+ (self._setAttrs, (path, attrs))])
+
+ def removeDirectory(self, path):
+ path = self._absPath(path)
+ self.avatar._runAsUser(os.rmdir, path)
+
+ def openDirectory(self, path):
+ return UnixSFTPDirectory(self, self._absPath(path))
+
+ def getAttrs(self, path, followLinks):
+ path = self._absPath(path)
+ if followLinks:
+ s = self.avatar._runAsUser(os.stat, path)
+ else:
+ s = self.avatar._runAsUser(os.lstat, path)
+ return self._getAttrs(s)
+
+ def setAttrs(self, path, attrs):
+ path = self._absPath(path)
+ self.avatar._runAsUser(self._setAttrs, path, attrs)
+
+ def readLink(self, path):
+ path = self._absPath(path)
+ return self.avatar._runAsUser(os.readlink, path)
+
+ def makeLink(self, linkPath, targetPath):
+ linkPath = self._absPath(linkPath)
+ targetPath = self._absPath(targetPath)
+ return self.avatar._runAsUser(os.symlink, targetPath, linkPath)
+
+ def realPath(self, path):
+ return os.path.realpath(self._absPath(path))
+
+ def extendedRequest(self, extName, extData):
+ raise NotImplementedError
+
+class UnixSFTPFile:
+
+ interface.implements(ISFTPFile)
+
+ def __init__(self, server, filename, flags, attrs):
+ self.server = server
+ openFlags = 0
+ if flags & FXF_READ == FXF_READ and flags & FXF_WRITE == 0:
+ openFlags = os.O_RDONLY
+ if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == 0:
+ openFlags = os.O_WRONLY
+ if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == FXF_READ:
+ openFlags = os.O_RDWR
+ if flags & FXF_APPEND == FXF_APPEND:
+ openFlags |= os.O_APPEND
+ if flags & FXF_CREAT == FXF_CREAT:
+ openFlags |= os.O_CREAT
+ if flags & FXF_TRUNC == FXF_TRUNC:
+ openFlags |= os.O_TRUNC
+ if flags & FXF_EXCL == FXF_EXCL:
+ openFlags |= os.O_EXCL
+ if attrs.has_key("permissions"):
+ mode = attrs["permissions"]
+ del attrs["permissions"]
+ else:
+ mode = 0777
+ fd = server.avatar._runAsUser(os.open, filename, openFlags, mode)
+ if attrs:
+ server.avatar._runAsUser(server._setAttrs, filename, attrs)
+ self.fd = fd
+
+ def close(self):
+ return self.server.avatar._runAsUser(os.close, self.fd)
+
+ def readChunk(self, offset, length):
+ return self.server.avatar._runAsUser([ (os.lseek, (self.fd, offset, 0)),
+ (os.read, (self.fd, length)) ])
+
+ def writeChunk(self, offset, data):
+ return self.server.avatar._runAsUser([(os.lseek, (self.fd, offset, 0)),
+ (os.write, (self.fd, data))])
+
+ def getAttrs(self):
+ s = self.server.avatar._runAsUser(os.fstat, self.fd)
+ return self.server._getAttrs(s)
+
+ def setAttrs(self, attrs):
+ raise NotImplementedError
+
+
+class UnixSFTPDirectory:
+
+ def __init__(self, server, directory):
+ self.server = server
+ self.files = server.avatar._runAsUser(os.listdir, directory)
+ self.dir = directory
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ try:
+ f = self.files.pop(0)
+ except IndexError:
+ raise StopIteration
+ else:
+ s = self.server.avatar._runAsUser(os.lstat, os.path.join(self.dir, f))
+ longname = lsLine(f, s)
+ attrs = self.server._getAttrs(s)
+ return (f, longname, attrs)
+
+ def close(self):
+ self.files = []
+
+
+components.registerAdapter(SFTPServerForUnixConchUser, UnixConchUser, filetransfer.ISFTPServer)
+components.registerAdapter(SSHSessionForUnixConchUser, UnixConchUser, session.ISession)
diff --git a/vendor/Twisted-10.0.0/twisted/copyright.py b/vendor/Twisted-10.0.0/twisted/copyright.py
new file mode 100644
index 0000000000..1fceb0e124
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/copyright.py
@@ -0,0 +1,39 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Copyright information for Twisted.
+"""
+
+from twisted import __version__ as version, version as longversion
+
+longversion = str(longversion)
+
+copyright="""\
+Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+See LICENSE for details."""
+
+disclaimer='''
+Twisted, the Framework of Your Internet
+%s
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+''' % copyright
diff --git a/vendor/Twisted-10.0.0/twisted/cred/__init__.py b/vendor/Twisted-10.0.0/twisted/cred/__init__.py
new file mode 100644
index 0000000000..b8048fdf14
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/cred/__init__.py
@@ -0,0 +1,13 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Twisted Cred
+
+Support for verifying credentials, and providing services to users based on
+those credentials.
+
+(This package was previously known as the module twisted.internet.passport.)
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/cred/_digest.py b/vendor/Twisted-10.0.0/twisted/cred/_digest.py
new file mode 100644
index 0000000000..30dcbb51cb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/cred/_digest.py
@@ -0,0 +1,129 @@
+# -*- test-case-name: twisted.test.test_digestauth -*-
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Calculations for HTTP Digest authentication.
+
+@see: U{http://www.faqs.org/rfcs/rfc2617.html}
+"""
+
+from twisted.python.hashlib import md5, sha1
+
+
+
+# The digest math
+
+algorithms = {
+ 'md5': md5,
+
+ # md5-sess is more complicated than just another algorithm. It requires
+ # H(A1) state to be remembered from the first WWW-Authenticate challenge
+ # issued and re-used to process any Authorization header in response to
+ # that WWW-Authenticate challenge. It is *not* correct to simply
+ # recalculate H(A1) each time an Authorization header is received. Read
+ # RFC 2617, section 3.2.2.2 and do not try to make DigestCredentialFactory
+ # support this unless you completely understand it. -exarkun
+ 'md5-sess': md5,
+
+ 'sha': sha1,
+}
+
+# DigestCalcHA1
+def calcHA1(pszAlg, pszUserName, pszRealm, pszPassword, pszNonce, pszCNonce,
+ preHA1=None):
+ """
+ Compute H(A1) from RFC 2617.
+
+ @param pszAlg: The name of the algorithm to use to calculate the digest.
+ Currently supported are md5, md5-sess, and sha.
+ @param pszUserName: The username
+ @param pszRealm: The realm
+ @param pszPassword: The password
+ @param pszNonce: The nonce
+ @param pszCNonce: The cnonce
+
+ @param preHA1: If available this is a str containing a previously
+ calculated H(A1) as a hex string. If this is given then the values for
+ pszUserName, pszRealm, and pszPassword must be C{None} and are ignored.
+ """
+
+ if (preHA1 and (pszUserName or pszRealm or pszPassword)):
+ raise TypeError(("preHA1 is incompatible with the pszUserName, "
+ "pszRealm, and pszPassword arguments"))
+
+ if preHA1 is None:
+ # We need to calculate the HA1 from the username:realm:password
+ m = algorithms[pszAlg]()
+ m.update(pszUserName)
+ m.update(":")
+ m.update(pszRealm)
+ m.update(":")
+ m.update(pszPassword)
+ HA1 = m.digest()
+ else:
+ # We were given a username:realm:password
+ HA1 = preHA1.decode('hex')
+
+ if pszAlg == "md5-sess":
+ m = algorithms[pszAlg]()
+ m.update(HA1)
+ m.update(":")
+ m.update(pszNonce)
+ m.update(":")
+ m.update(pszCNonce)
+ HA1 = m.digest()
+
+ return HA1.encode('hex')
+
+
+def calcHA2(algo, pszMethod, pszDigestUri, pszQop, pszHEntity):
+ """
+ Compute H(A2) from RFC 2617.
+
+ @param pszAlg: The name of the algorithm to use to calculate the digest.
+ Currently supported are md5, md5-sess, and sha.
+ @param pszMethod: The request method.
+ @param pszDigestUri: The request URI.
+ @param pszQop: The Quality-of-Protection value.
+ @param pszHEntity: The hash of the entity body or C{None} if C{pszQop} is
+ not C{'auth-int'}.
+ @return: The hash of the A2 value for the calculation of the response
+ digest.
+ """
+ m = algorithms[algo]()
+ m.update(pszMethod)
+ m.update(":")
+ m.update(pszDigestUri)
+ if pszQop == "auth-int":
+ m.update(":")
+ m.update(pszHEntity)
+ return m.digest().encode('hex')
+
+
+def calcResponse(HA1, HA2, algo, pszNonce, pszNonceCount, pszCNonce, pszQop):
+ """
+ Compute the digest for the given parameters.
+
+ @param HA1: The H(A1) value, as computed by L{calcHA1}.
+ @param HA2: The H(A2) value, as computed by L{calcHA2}.
+ @param pszNonce: The challenge nonce.
+ @param pszNonceCount: The (client) nonce count value for this response.
+ @param pszCNonce: The client nonce.
+ @param pszQop: The Quality-of-Protection value.
+ """
+ m = algorithms[algo]()
+ m.update(HA1)
+ m.update(":")
+ m.update(pszNonce)
+ m.update(":")
+ if pszNonceCount and pszCNonce:
+ m.update(pszNonceCount)
+ m.update(":")
+ m.update(pszCNonce)
+ m.update(":")
+ m.update(pszQop)
+ m.update(":")
+ m.update(HA2)
+ respHash = m.digest().encode('hex')
+ return respHash
diff --git a/vendor/Twisted-10.0.0/twisted/cred/checkers.py b/vendor/Twisted-10.0.0/twisted/cred/checkers.py
new file mode 100644
index 0000000000..d2e08f331a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/cred/checkers.py
@@ -0,0 +1,268 @@
+# -*- test-case-name: twisted.test.test_newcred -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import os
+
+from zope.interface import implements, Interface, Attribute
+
+from twisted.internet import defer
+from twisted.python import failure, log
+from twisted.cred import error, credentials
+
+
+
+class ICredentialsChecker(Interface):
+ """
+ An object that can check sub-interfaces of ICredentials.
+ """
+
+ credentialInterfaces = Attribute(
+ 'A list of sub-interfaces of ICredentials which specifies which I may check.')
+
+
+ def requestAvatarId(credentials):
+ """
+ @param credentials: something which implements one of the interfaces in
+ self.credentialInterfaces.
+
+ @return: a Deferred which will fire a string which identifies an
+ avatar, an empty tuple to specify an authenticated anonymous user
+ (provided as checkers.ANONYMOUS) or fire a Failure(UnauthorizedLogin).
+ Alternatively, return the result itself.
+
+ @see: L{twisted.cred.credentials}
+ """
+
+
+
+# A note on anonymity - We do not want None as the value for anonymous
+# because it is too easy to accidentally return it. We do not want the
+# empty string, because it is too easy to mistype a password file. For
+# example, an .htpasswd file may contain the lines: ['hello:asdf',
+# 'world:asdf', 'goodbye', ':world']. This misconfiguration will have an
+# ill effect in any case, but accidentally granting anonymous access is a
+# worse failure mode than simply granting access to an untypeable
+# username. We do not want an instance of 'object', because that would
+# create potential problems with persistence.
+
+ANONYMOUS = ()
+
+
+class AllowAnonymousAccess:
+ implements(ICredentialsChecker)
+ credentialInterfaces = credentials.IAnonymous,
+
+ def requestAvatarId(self, credentials):
+ return defer.succeed(ANONYMOUS)
+
+
+class InMemoryUsernamePasswordDatabaseDontUse:
+ """
+ An extremely simple credentials checker.
+
+ This is only of use in one-off test programs or examples which don't
+ want to focus too much on how credentials are verified.
+
+ You really don't want to use this for anything else. It is, at best, a
+ toy. If you need a simple credentials checker for a real application,
+ see L{FilePasswordDB}.
+ """
+
+ implements(ICredentialsChecker)
+
+ credentialInterfaces = (credentials.IUsernamePassword,
+ credentials.IUsernameHashedPassword)
+
+ def __init__(self, **users):
+ self.users = users
+
+ def addUser(self, username, password):
+ self.users[username] = password
+
+ def _cbPasswordMatch(self, matched, username):
+ if matched:
+ return username
+ else:
+ return failure.Failure(error.UnauthorizedLogin())
+
+ def requestAvatarId(self, credentials):
+ if credentials.username in self.users:
+ return defer.maybeDeferred(
+ credentials.checkPassword,
+ self.users[credentials.username]).addCallback(
+ self._cbPasswordMatch, str(credentials.username))
+ else:
+ return defer.fail(error.UnauthorizedLogin())
+
+
+class FilePasswordDB:
+ """A file-based, text-based username/password database.
+
+ Records in the datafile for this class are delimited by a particular
+ string. The username appears in a fixed field of the columns delimited
+ by this string, as does the password. Both fields are specifiable. If
+ the passwords are not stored plaintext, a hash function must be supplied
+ to convert plaintext passwords to the form stored on disk and this
+ CredentialsChecker will only be able to check IUsernamePassword
+ credentials. If the passwords are stored plaintext,
+ IUsernameHashedPassword credentials will be checkable as well.
+ """
+
+ implements(ICredentialsChecker)
+
+ cache = False
+ _credCache = None
+ _cacheTimestamp = 0
+
+ def __init__(self, filename, delim=':', usernameField=0, passwordField=1,
+ caseSensitive=True, hash=None, cache=False):
+ """
+ @type filename: C{str}
+ @param filename: The name of the file from which to read username and
+ password information.
+
+ @type delim: C{str}
+ @param delim: The field delimiter used in the file.
+
+ @type usernameField: C{int}
+ @param usernameField: The index of the username after splitting a
+ line on the delimiter.
+
+ @type passwordField: C{int}
+ @param passwordField: The index of the password after splitting a
+ line on the delimiter.
+
+ @type caseSensitive: C{bool}
+ @param caseSensitive: If true, consider the case of the username when
+ performing a lookup. Ignore it otherwise.
+
+ @type hash: Three-argument callable or C{None}
+ @param hash: A function used to transform the plaintext password
+ received over the network to a format suitable for comparison
+ against the version stored on disk. The arguments to the callable
+ are the username, the network-supplied password, and the in-file
+ version of the password. If the return value compares equal to the
+ version stored on disk, the credentials are accepted.
+
+ @type cache: C{bool}
+ @param cache: If true, maintain an in-memory cache of the
+ contents of the password file. On lookups, the mtime of the
+ file will be checked, and the file will only be re-parsed if
+ the mtime is newer than when the cache was generated.
+ """
+ self.filename = filename
+ self.delim = delim
+ self.ufield = usernameField
+ self.pfield = passwordField
+ self.caseSensitive = caseSensitive
+ self.hash = hash
+ self.cache = cache
+
+ if self.hash is None:
+ # The passwords are stored plaintext. We can support both
+ # plaintext and hashed passwords received over the network.
+ self.credentialInterfaces = (
+ credentials.IUsernamePassword,
+ credentials.IUsernameHashedPassword
+ )
+ else:
+ # The passwords are hashed on disk. We can support only
+ # plaintext passwords received over the network.
+ self.credentialInterfaces = (
+ credentials.IUsernamePassword,
+ )
+
+
+ def __getstate__(self):
+ d = dict(vars(self))
+ for k in '_credCache', '_cacheTimestamp':
+ try:
+ del d[k]
+ except KeyError:
+ pass
+ return d
+
+
+ def _cbPasswordMatch(self, matched, username):
+ if matched:
+ return username
+ else:
+ return failure.Failure(error.UnauthorizedLogin())
+
+
+ def _loadCredentials(self):
+ try:
+ f = file(self.filename)
+ except:
+ log.err()
+ raise error.UnauthorizedLogin()
+ else:
+ for line in f:
+ line = line.rstrip()
+ parts = line.split(self.delim)
+
+ if self.ufield >= len(parts) or self.pfield >= len(parts):
+ continue
+ if self.caseSensitive:
+ yield parts[self.ufield], parts[self.pfield]
+ else:
+ yield parts[self.ufield].lower(), parts[self.pfield]
+
+
+ def getUser(self, username):
+ if not self.caseSensitive:
+ username = username.lower()
+
+ if self.cache:
+ if self._credCache is None or os.path.getmtime(self.filename) > self._cacheTimestamp:
+ self._cacheTimestamp = os.path.getmtime(self.filename)
+ self._credCache = dict(self._loadCredentials())
+ return username, self._credCache[username]
+ else:
+ for u, p in self._loadCredentials():
+ if u == username:
+ return u, p
+ raise KeyError(username)
+
+
+ def requestAvatarId(self, c):
+ try:
+ u, p = self.getUser(c.username)
+ except KeyError:
+ return defer.fail(error.UnauthorizedLogin())
+ else:
+ up = credentials.IUsernamePassword(c, None)
+ if self.hash:
+ if up is not None:
+ h = self.hash(up.username, up.password, p)
+ if h == p:
+ return defer.succeed(u)
+ return defer.fail(error.UnauthorizedLogin())
+ else:
+ return defer.maybeDeferred(c.checkPassword, p
+ ).addCallback(self._cbPasswordMatch, u)
+
+
+
+class PluggableAuthenticationModulesChecker:
+ implements(ICredentialsChecker)
+ credentialInterfaces = credentials.IPluggableAuthenticationModules,
+ service = 'Twisted'
+
+ def requestAvatarId(self, credentials):
+ try:
+ from twisted.cred import pamauth
+ except ImportError: # PyPAM is missing
+ return defer.fail(error.UnauthorizedLogin())
+ else:
+ d = pamauth.pamAuthenticate(self.service, credentials.username,
+ credentials.pamConversion)
+ d.addCallback(lambda x: credentials.username)
+ return d
+
+
+
+# For backwards compatibility
+# Allow access as the old name.
+OnDiskUsernamePasswordDatabase = FilePasswordDB
diff --git a/vendor/Twisted-10.0.0/twisted/cred/credentials.py b/vendor/Twisted-10.0.0/twisted/cred/credentials.py
new file mode 100644
index 0000000000..bae1cfbe7f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/cred/credentials.py
@@ -0,0 +1,483 @@
+# -*- test-case-name: twisted.test.test_newcred-*-
+
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from zope.interface import implements, Interface
+
+import hmac, time, random
+from twisted.python.hashlib import md5
+from twisted.python.randbytes import secureRandom
+from twisted.cred._digest import calcResponse, calcHA1, calcHA2
+from twisted.cred import error
+
+class ICredentials(Interface):
+ """
+ I check credentials.
+
+ Implementors _must_ specify which sub-interfaces of ICredentials
+ to which it conforms, using zope.interface.implements().
+ """
+
+
+
+class IUsernameDigestHash(ICredentials):
+ """
+ This credential is used when a CredentialChecker has access to the hash
+ of the username:realm:password as in an Apache .htdigest file.
+ """
+ def checkHash(digestHash):
+ """
+ @param digestHash: The hashed username:realm:password to check against.
+
+ @return: C{True} if the credentials represented by this object match
+ the given hash, C{False} if they do not, or a L{Deferred} which
+ will be called back with one of these values.
+ """
+
+
+
+class IUsernameHashedPassword(ICredentials):
+ """
+ I encapsulate a username and a hashed password.
+
+ This credential is used when a hashed password is received from the
+ party requesting authentication. CredentialCheckers which check this
+ kind of credential must store the passwords in plaintext (or as
+ password-equivalent hashes) form so that they can be hashed in a manner
+ appropriate for the particular credentials class.
+
+ @type username: C{str}
+ @ivar username: The username associated with these credentials.
+ """
+
+ def checkPassword(password):
+ """
+ Validate these credentials against the correct password.
+
+ @type password: C{str}
+ @param password: The correct, plaintext password against which to
+ check.
+
+ @rtype: C{bool} or L{Deferred}
+ @return: C{True} if the credentials represented by this object match the
+ given password, C{False} if they do not, or a L{Deferred} which will
+ be called back with one of these values.
+ """
+
+
+
+class IUsernamePassword(ICredentials):
+ """
+ I encapsulate a username and a plaintext password.
+
+ This encapsulates the case where the password received over the network
+ has been hashed with the identity function (That is, not at all). The
+ CredentialsChecker may store the password in whatever format it desires,
+ it need only transform the stored password in a similar way before
+ performing the comparison.
+
+ @type username: C{str}
+ @ivar username: The username associated with these credentials.
+
+ @type password: C{str}
+ @ivar password: The password associated with these credentials.
+ """
+
+ def checkPassword(password):
+ """
+ Validate these credentials against the correct password.
+
+ @type password: C{str}
+ @param password: The correct, plaintext password against which to
+ check.
+
+ @rtype: C{bool} or L{Deferred}
+ @return: C{True} if the credentials represented by this object match the
+ given password, C{False} if they do not, or a L{Deferred} which will
+ be called back with one of these values.
+ """
+
+
+
+class IAnonymous(ICredentials):
+ """
+ I am an explicitly anonymous request for access.
+ """
+
+
+
+class DigestedCredentials(object):
+ """
+ Yet Another Simple HTTP Digest authentication scheme.
+ """
+ implements(IUsernameHashedPassword, IUsernameDigestHash)
+
+ def __init__(self, username, method, realm, fields):
+ self.username = username
+ self.method = method
+ self.realm = realm
+ self.fields = fields
+
+
+ def checkPassword(self, password):
+ """
+ Verify that the credentials represented by this object agree with the
+ given plaintext C{password} by hashing C{password} in the same way the
+ response hash represented by this object was generated and comparing
+ the results.
+ """
+ response = self.fields.get('response')
+ uri = self.fields.get('uri')
+ nonce = self.fields.get('nonce')
+ cnonce = self.fields.get('cnonce')
+ nc = self.fields.get('nc')
+ algo = self.fields.get('algorithm', 'md5').lower()
+ qop = self.fields.get('qop', 'auth')
+
+ expected = calcResponse(
+ calcHA1(algo, self.username, self.realm, password, nonce, cnonce),
+ calcHA2(algo, self.method, uri, qop, None),
+ algo, nonce, nc, cnonce, qop)
+
+ return expected == response
+
+
+ def checkHash(self, digestHash):
+ """
+ Verify that the credentials represented by this object agree with the
+ credentials represented by the I{H(A1)} given in C{digestHash}.
+
+ @param digestHash: A precomputed H(A1) value based on the username,
+ realm, and password associate with this credentials object.
+ """
+ response = self.fields.get('response')
+ uri = self.fields.get('uri')
+ nonce = self.fields.get('nonce')
+ cnonce = self.fields.get('cnonce')
+ nc = self.fields.get('nc')
+ algo = self.fields.get('algorithm', 'md5').lower()
+ qop = self.fields.get('qop', 'auth')
+
+ expected = calcResponse(
+ calcHA1(algo, None, None, None, nonce, cnonce, preHA1=digestHash),
+ calcHA2(algo, self.method, uri, qop, None),
+ algo, nonce, nc, cnonce, qop)
+
+ return expected == response
+
+
+
+class DigestCredentialFactory(object):
+ """
+ Support for RFC2617 HTTP Digest Authentication
+
+ @cvar CHALLENGE_LIFETIME_SECS: The number of seconds for which an
+ opaque should be valid.
+
+ @type privateKey: C{str}
+ @ivar privateKey: A random string used for generating the secure opaque.
+
+ @type algorithm: C{str}
+ @param algorithm: Case insensitive string specifying the hash algorithm to
+ use. Must be either C{'md5'} or C{'sha'}. C{'md5-sess'} is B{not}
+ supported.
+
+ @type authenticationRealm: C{str}
+ @param authenticationRealm: case sensitive string that specifies the realm
+ portion of the challenge
+ """
+
+ CHALLENGE_LIFETIME_SECS = 15 * 60 # 15 minutes
+
+ scheme = "digest"
+
+ def __init__(self, algorithm, authenticationRealm):
+ self.algorithm = algorithm
+ self.authenticationRealm = authenticationRealm
+ self.privateKey = secureRandom(12)
+
+
+ def getChallenge(self, address):
+ """
+ Generate the challenge for use in the WWW-Authenticate header.
+
+ @param address: The client address to which this challenge is being
+ sent.
+
+ @return: The C{dict} that can be used to generate a WWW-Authenticate
+ header.
+ """
+ c = self._generateNonce()
+ o = self._generateOpaque(c, address)
+
+ return {'nonce': c,
+ 'opaque': o,
+ 'qop': 'auth',
+ 'algorithm': self.algorithm,
+ 'realm': self.authenticationRealm}
+
+
+ def _generateNonce(self):
+ """
+ Create a random value suitable for use as the nonce parameter of a
+ WWW-Authenticate challenge.
+
+ @rtype: C{str}
+ """
+ return secureRandom(12).encode('hex')
+
+
+ def _getTime(self):
+ """
+ Parameterize the time based seed used in C{_generateOpaque}
+ so we can deterministically unittest it's behavior.
+ """
+ return time.time()
+
+
+ def _generateOpaque(self, nonce, clientip):
+ """
+ Generate an opaque to be returned to the client. This is a unique
+ string that can be returned to us and verified.
+ """
+ # Now, what we do is encode the nonce, client ip and a timestamp in the
+ # opaque value with a suitable digest.
+ now = str(int(self._getTime()))
+ if clientip is None:
+ clientip = ''
+ key = "%s,%s,%s" % (nonce, clientip, now)
+ digest = md5(key + self.privateKey).hexdigest()
+ ekey = key.encode('base64')
+ return "%s-%s" % (digest, ekey.replace('\n', ''))
+
+
+ def _verifyOpaque(self, opaque, nonce, clientip):
+ """
+ Given the opaque and nonce from the request, as well as the client IP
+ that made the request, verify that the opaque was generated by us.
+ And that it's not too old.
+
+ @param opaque: The opaque value from the Digest response
+ @param nonce: The nonce value from the Digest response
+ @param clientip: The remote IP address of the client making the request
+ or C{None} if the request was submitted over a channel where this
+ does not make sense.
+
+ @return: C{True} if the opaque was successfully verified.
+
+ @raise error.LoginFailed: if C{opaque} could not be parsed or
+ contained the wrong values.
+ """
+ # First split the digest from the key
+ opaqueParts = opaque.split('-')
+ if len(opaqueParts) != 2:
+ raise error.LoginFailed('Invalid response, invalid opaque value')
+
+ if clientip is None:
+ clientip = ''
+
+ # Verify the key
+ key = opaqueParts[1].decode('base64')
+ keyParts = key.split(',')
+
+ if len(keyParts) != 3:
+ raise error.LoginFailed('Invalid response, invalid opaque value')
+
+ if keyParts[0] != nonce:
+ raise error.LoginFailed(
+ 'Invalid response, incompatible opaque/nonce values')
+
+ if keyParts[1] != clientip:
+ raise error.LoginFailed(
+ 'Invalid response, incompatible opaque/client values')
+
+ try:
+ when = int(keyParts[2])
+ except ValueError:
+ raise error.LoginFailed(
+ 'Invalid response, invalid opaque/time values')
+
+ if (int(self._getTime()) - when >
+ DigestCredentialFactory.CHALLENGE_LIFETIME_SECS):
+
+ raise error.LoginFailed(
+ 'Invalid response, incompatible opaque/nonce too old')
+
+ # Verify the digest
+ digest = md5(key + self.privateKey).hexdigest()
+ if digest != opaqueParts[0]:
+ raise error.LoginFailed('Invalid response, invalid opaque value')
+
+ return True
+
+
+ def decode(self, response, method, host):
+ """
+ Decode the given response and attempt to generate a
+ L{DigestedCredentials} from it.
+
+ @type response: C{str}
+ @param response: A string of comma seperated key=value pairs
+
+ @type method: C{str}
+ @param method: The action requested to which this response is addressed
+ (GET, POST, INVITE, OPTIONS, etc).
+
+ @type host: C{str}
+ @param host: The address the request was sent from.
+
+ @raise error.LoginFailed: If the response does not contain a username,
+ a nonce, an opaque, or if the opaque is invalid.
+
+ @return: L{DigestedCredentials}
+ """
+ def unq(s):
+ if s[0] == s[-1] == '"':
+ return s[1:-1]
+ return s
+ response = ' '.join(response.splitlines())
+ parts = response.split(',')
+
+ auth = {}
+
+ for (k, v) in [p.split('=', 1) for p in parts]:
+ auth[k.strip()] = unq(v.strip())
+
+ username = auth.get('username')
+ if not username:
+ raise error.LoginFailed('Invalid response, no username given.')
+
+ if 'opaque' not in auth:
+ raise error.LoginFailed('Invalid response, no opaque given.')
+
+ if 'nonce' not in auth:
+ raise error.LoginFailed('Invalid response, no nonce given.')
+
+ # Now verify the nonce/opaque values for this client
+ if self._verifyOpaque(auth.get('opaque'), auth.get('nonce'), host):
+ return DigestedCredentials(username,
+ method,
+ self.authenticationRealm,
+ auth)
+
+
+
+class CramMD5Credentials:
+ implements(IUsernameHashedPassword)
+
+ challenge = ''
+ response = ''
+
+ def __init__(self, host=None):
+ self.host = host
+
+ def getChallenge(self):
+ if self.challenge:
+ return self.challenge
+ # The data encoded in the first ready response contains an
+ # presumptively arbitrary string of random digits, a timestamp, and
+ # the fully-qualified primary host name of the server. The syntax of
+ # the unencoded form must correspond to that of an RFC 822 'msg-id'
+ # [RFC822] as described in [POP3].
+ # -- RFC 2195
+ r = random.randrange(0x7fffffff)
+ t = time.time()
+ self.challenge = '<%d.%d@%s>' % (r, t, self.host)
+ return self.challenge
+
+ def setResponse(self, response):
+ self.username, self.response = response.split(None, 1)
+
+ def moreChallenges(self):
+ return False
+
+ def checkPassword(self, password):
+ verify = hmac.HMAC(password, self.challenge).hexdigest()
+ return verify == self.response
+
+
+class UsernameHashedPassword:
+ implements(IUsernameHashedPassword)
+
+ def __init__(self, username, hashed):
+ self.username = username
+ self.hashed = hashed
+
+ def checkPassword(self, password):
+ return self.hashed == password
+
+
+class UsernamePassword:
+ implements(IUsernamePassword)
+
+ def __init__(self, username, password):
+ self.username = username
+ self.password = password
+
+ def checkPassword(self, password):
+ return self.password == password
+
+
+class Anonymous:
+ implements(IAnonymous)
+
+
+
+class ISSHPrivateKey(ICredentials):
+ """
+ L{ISSHPrivateKey} credentials encapsulate an SSH public key to be checked
+ against a user's private key.
+
+ @ivar username: The username associated with these credentials.
+ @type username: C{str}
+
+ @ivar algName: The algorithm name for the blob.
+ @type algName: C{str}
+
+ @ivar blob: The public key blob as sent by the client.
+ @type blob: C{str}
+
+ @ivar sigData: The data the signature was made from.
+ @type sigData: C{str}
+
+ @ivar signature: The signed data. This is checked to verify that the user
+ owns the private key.
+ @type signature: C{str} or C{NoneType}
+ """
+
+
+
+class SSHPrivateKey:
+ implements(ISSHPrivateKey)
+ def __init__(self, username, algName, blob, sigData, signature):
+ self.username = username
+ self.algName = algName
+ self.blob = blob
+ self.sigData = sigData
+ self.signature = signature
+
+
+class IPluggableAuthenticationModules(ICredentials):
+ """I encapsulate the authentication of a user via PAM (Pluggable
+ Authentication Modules. I use PyPAM (available from
+ http://www.tummy.com/Software/PyPam/index.html).
+
+ @ivar username: The username for the user being logged in.
+
+ @ivar pamConversion: A function that is called with a list of tuples
+ (message, messageType). See the PAM documentation
+ for the meaning of messageType. The function
+ returns a Deferred which will fire with a list
+ of (response, 0), one for each message. The 0 is
+ currently unused, but is required by the PAM library.
+ """
+
+class PluggableAuthenticationModules:
+ implements(IPluggableAuthenticationModules)
+
+ def __init__(self, username, pamConversion):
+ self.username = username
+ self.pamConversion = pamConversion
+
diff --git a/vendor/Twisted-10.0.0/twisted/cred/error.py b/vendor/Twisted-10.0.0/twisted/cred/error.py
new file mode 100644
index 0000000000..ec7b3e39d1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/cred/error.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Cred errors."""
+
+class Unauthorized(Exception):
+ """Standard unauthorized error."""
+
+
+
+class LoginFailed(Exception):
+ """
+ The user's request to log in failed for some reason.
+ """
+
+
+
+class UnauthorizedLogin(LoginFailed, Unauthorized):
+ """The user was not authorized to log in.
+ """
+
+
+
+class UnhandledCredentials(LoginFailed):
+ """A type of credentials were passed in with no knowledge of how to check
+ them. This is a server configuration error - it means that a protocol was
+ connected to a Portal without a CredentialChecker that can check all of its
+ potential authentication strategies.
+ """
+
+
+
+class LoginDenied(LoginFailed):
+ """
+ The realm rejected this login for some reason.
+
+ Examples of reasons this might be raised include an avatar logging in
+ too frequently, a quota having been fully used, or the overall server
+ load being too high.
+ """
diff --git a/vendor/Twisted-10.0.0/twisted/cred/pamauth.py b/vendor/Twisted-10.0.0/twisted/cred/pamauth.py
new file mode 100644
index 0000000000..1537a5fa46
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/cred/pamauth.py
@@ -0,0 +1,79 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Support for asynchronously authenticating using PAM.
+"""
+
+
+import PAM
+
+import getpass, threading, os
+
+from twisted.internet import threads, defer
+
+def pamAuthenticateThread(service, user, conv):
+ def _conv(items):
+ from twisted.internet import reactor
+ try:
+ d = conv(items)
+ except:
+ import traceback
+ traceback.print_exc()
+ return
+ ev = threading.Event()
+ def cb(r):
+ ev.r = (1, r)
+ ev.set()
+ def eb(e):
+ ev.r = (0, e)
+ ev.set()
+ reactor.callFromThread(d.addCallbacks, cb, eb)
+ ev.wait()
+ done = ev.r
+ if done[0]:
+ return done[1]
+ else:
+ raise done[1].type, done[1].value
+
+ return callIntoPAM(service, user, _conv)
+
+def callIntoPAM(service, user, conv):
+ """A testing hook.
+ """
+ pam = PAM.pam()
+ pam.start(service)
+ pam.set_item(PAM.PAM_USER, user)
+ pam.set_item(PAM.PAM_CONV, conv)
+ gid = os.getegid()
+ uid = os.geteuid()
+ os.setegid(0)
+ os.seteuid(0)
+ try:
+ pam.authenticate() # these will raise
+ pam.acct_mgmt()
+ return 1
+ finally:
+ os.setegid(gid)
+ os.seteuid(uid)
+
+def defConv(items):
+ resp = []
+ for i in range(len(items)):
+ message, kind = items[i]
+ if kind == 1: # password
+ p = getpass.getpass(message)
+ resp.append((p, 0))
+ elif kind == 2: # text
+ p = raw_input(message)
+ resp.append((p, 0))
+ elif kind in (3,4):
+ print message
+ resp.append(("", 0))
+ else:
+ return defer.fail('foo')
+ d = defer.succeed(resp)
+ return d
+
+def pamAuthenticate(service, user, conv):
+ return threads.deferToThread(pamAuthenticateThread, service, user, conv)
diff --git a/vendor/Twisted-10.0.0/twisted/cred/portal.py b/vendor/Twisted-10.0.0/twisted/cred/portal.py
new file mode 100644
index 0000000000..cbfcec87eb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/cred/portal.py
@@ -0,0 +1,121 @@
+# -*- test-case-name: twisted.test.test_newcred -*-
+
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+The point of integration of application and authentication.
+"""
+
+
+from twisted.internet import defer
+from twisted.internet.defer import maybeDeferred
+from twisted.python import failure, reflect
+from twisted.cred import error
+from zope.interface import providedBy, Interface
+
+
+class IRealm(Interface):
+ """
+ The realm connects application-specific objects to the
+ authentication system.
+ """
+ def requestAvatar(avatarId, mind, *interfaces):
+ """
+ Return avatar which provides one of the given interfaces.
+
+ @param avatarId: a string that identifies an avatar, as returned by
+ L{ICredentialsChecker.requestAvatarId<twisted.cred.checkers.ICredentialsChecker.requestAvatarId>}
+ (via a Deferred). Alternatively, it may be
+ C{twisted.cred.checkers.ANONYMOUS}.
+ @param mind: usually None. See the description of mind in
+ L{Portal.login}.
+ @param interfaces: the interface(s) the returned avatar should
+ implement, e.g. C{IMailAccount}. See the description of
+ L{Portal.login}.
+
+ @returns: a deferred which will fire a tuple of (interface,
+ avatarAspect, logout), or the tuple itself. The interface will be
+ one of the interfaces passed in the 'interfaces' argument. The
+ 'avatarAspect' will implement that interface. The 'logout' object
+ is a callable which will detach the mind from the avatar.
+ """
+
+
+class Portal:
+ """
+ A mediator between clients and a realm.
+
+ A portal is associated with one Realm and zero or more credentials checkers.
+ When a login is attempted, the portal finds the appropriate credentials
+ checker for the credentials given, invokes it, and if the credentials are
+ valid, retrieves the appropriate avatar from the Realm.
+
+ This class is not intended to be subclassed. Customization should be done
+ in the realm object and in the credentials checker objects.
+ """
+ def __init__(self, realm, checkers=()):
+ """
+ Create a Portal to a L{IRealm}.
+ """
+ self.realm = realm
+ self.checkers = {}
+ for checker in checkers:
+ self.registerChecker(checker)
+
+ def listCredentialsInterfaces(self):
+ """
+ Return list of credentials interfaces that can be used to login.
+ """
+ return self.checkers.keys()
+
+ def registerChecker(self, checker, *credentialInterfaces):
+ if not credentialInterfaces:
+ credentialInterfaces = checker.credentialInterfaces
+ for credentialInterface in credentialInterfaces:
+ self.checkers[credentialInterface] = checker
+
+ def login(self, credentials, mind, *interfaces):
+ """
+ @param credentials: an implementor of
+ L{twisted.cred.credentials.ICredentials}
+
+ @param mind: an object which implements a client-side interface for
+ your particular realm. In many cases, this may be None, so if the
+ word 'mind' confuses you, just ignore it.
+
+ @param interfaces: list of interfaces for the perspective that the mind
+ wishes to attach to. Usually, this will be only one interface, for
+ example IMailAccount. For highly dynamic protocols, however, this
+ may be a list like (IMailAccount, IUserChooser, IServiceInfo). To
+ expand: if we are speaking to the system over IMAP, any information
+ that will be relayed to the user MUST be returned as an
+ IMailAccount implementor; IMAP clients would not be able to
+ understand anything else. Any information about unusual status
+ would have to be relayed as a single mail message in an
+ otherwise-empty mailbox. However, in a web-based mail system, or a
+ PB-based client, the ``mind'' object inside the web server
+ (implemented with a dynamic page-viewing mechanism such as a
+ Twisted Web Resource) or on the user's client program may be
+ intelligent enough to respond to several ``server''-side
+ interfaces.
+
+ @return: A deferred which will fire a tuple of (interface,
+ avatarAspect, logout). The interface will be one of the interfaces
+ passed in the 'interfaces' argument. The 'avatarAspect' will
+ implement that interface. The 'logout' object is a callable which
+ will detach the mind from the avatar. It must be called when the
+ user has conceptually disconnected from the service. Although in
+ some cases this will not be in connectionLost (such as in a
+ web-based session), it will always be at the end of a user's
+ interactive session.
+ """
+ for i in self.checkers:
+ if i.providedBy(credentials):
+ return maybeDeferred(self.checkers[i].requestAvatarId, credentials
+ ).addCallback(self.realm.requestAvatar, mind, *interfaces
+ )
+ ifac = providedBy(credentials)
+ return defer.fail(failure.Failure(error.UnhandledCredentials(
+ "No checker for %s" % ', '.join(map(reflect.qual, ifac)))))
+
diff --git a/vendor/Twisted-10.0.0/twisted/cred/strcred.py b/vendor/Twisted-10.0.0/twisted/cred/strcred.py
new file mode 100644
index 0000000000..a436edcbd9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/cred/strcred.py
@@ -0,0 +1,270 @@
+# -*- test-case-name: twisted.test.test_strcred -*-
+#
+# Copyright (c) 2007-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+#
+
+"""
+Support for resolving command-line strings that represent different
+checkers available to cred.
+
+Examples:
+ - passwd:/etc/passwd
+ - memory:admin:asdf:user:lkj
+ - unix
+"""
+
+import sys
+
+from zope.interface import Interface, Attribute
+
+from twisted.plugin import getPlugins
+from twisted.python import usage
+
+
+
+class ICheckerFactory(Interface):
+ """
+ A factory for objects which provide
+ L{twisted.cred.checkers.ICredentialsChecker}.
+
+ It's implemented by twistd plugins creating checkers.
+ """
+
+ authType = Attribute(
+ 'A tag that identifies the authentication method.')
+
+
+ authHelp = Attribute(
+ 'A detailed (potentially multi-line) description of precisely '
+ 'what functionality this CheckerFactory provides.')
+
+
+ argStringFormat = Attribute(
+ 'A short (one-line) description of the argument string format.')
+
+
+ credentialInterfaces = Attribute(
+ 'A list of credentials interfaces that this factory will support.')
+
+
+ def generateChecker(argstring):
+ """
+ Return an L{ICredentialChecker} provider using the supplied
+ argument string.
+ """
+
+
+
+class StrcredException(Exception):
+ """
+ Base exception class for strcred.
+ """
+
+
+
+class InvalidAuthType(StrcredException):
+ """
+ Raised when a user provides an invalid identifier for the
+ authentication plugin (known as the authType).
+ """
+
+
+
+class InvalidAuthArgumentString(StrcredException):
+ """
+ Raised by an authentication plugin when the argument string
+ provided is formatted incorrectly.
+ """
+
+
+
+class UnsupportedInterfaces(StrcredException):
+ """
+ Raised when an application is given a checker to use that does not
+ provide any of the application's supported credentials interfaces.
+ """
+
+
+
+# This will be used to warn the users whenever they view help for an
+# authType that is not supported by the application.
+notSupportedWarning = ("WARNING: This authType is not supported by "
+ "this application.")
+
+
+
+def findCheckerFactories():
+ """
+ Find all objects that implement L{ICheckerFactory}.
+ """
+ return getPlugins(ICheckerFactory)
+
+
+
+def findCheckerFactory(authType):
+ """
+ Find the first checker factory that supports the given authType.
+ """
+ for factory in findCheckerFactories():
+ if factory.authType == authType:
+ return factory
+ raise InvalidAuthType(authType)
+
+
+
+def makeChecker(description):
+ """
+ Returns an L{twisted.cred.checkers.ICredentialsChecker} based on the
+ contents of a descriptive string. Similar to
+ L{twisted.application.strports}.
+ """
+ if ':' in description:
+ authType, argstring = description.split(':', 1)
+ else:
+ authType = description
+ argstring = ''
+ return findCheckerFactory(authType).generateChecker(argstring)
+
+
+
+class AuthOptionMixin:
+ """
+ Defines helper methods that can be added on to any
+ L{usage.Options} subclass that needs authentication.
+
+ This mixin implements three new options methods:
+
+ The opt_auth method (--auth) will write two new values to the
+ 'self' dictionary: C{credInterfaces} (a dict of lists) and
+ C{credCheckers} (a list).
+
+ The opt_help_auth method (--help-auth) will search for all
+ available checker plugins and list them for the user; it will exit
+ when finished.
+
+ The opt_help_auth_type method (--help-auth-type) will display
+ detailed help for a particular checker plugin.
+
+ @cvar supportedInterfaces: An iterable object that returns
+ credential interfaces which this application is able to support.
+
+ @cvar authOutput: A writeable object to which this options class
+ will send all help-related output. Default: L{sys.stdout}
+ """
+
+ supportedInterfaces = None
+ authOutput = sys.stdout
+
+
+ def supportsInterface(self, interface):
+ """
+ Returns whether a particular credentials interface is supported.
+ """
+ return (self.supportedInterfaces is None
+ or interface in self.supportedInterfaces)
+
+
+ def supportsCheckerFactory(self, factory):
+ """
+ Returns whether a checker factory will provide at least one of
+ the credentials interfaces that we care about.
+ """
+ for interface in factory.credentialInterfaces:
+ if self.supportsInterface(interface):
+ return True
+ return False
+
+
+ def addChecker(self, checker):
+ """
+ Supply a supplied credentials checker to the Options class.
+ """
+ # First figure out which interfaces we're willing to support.
+ supported = []
+ if self.supportedInterfaces is None:
+ supported = checker.credentialInterfaces
+ else:
+ for interface in checker.credentialInterfaces:
+ if self.supportsInterface(interface):
+ supported.append(interface)
+ if not supported:
+ raise UnsupportedInterfaces(checker.credentialInterfaces)
+ # If we get this far, then we know we can use this checker.
+ if 'credInterfaces' not in self:
+ self['credInterfaces'] = {}
+ if 'credCheckers' not in self:
+ self['credCheckers'] = []
+ self['credCheckers'].append(checker)
+ for interface in supported:
+ self['credInterfaces'].setdefault(interface, []).append(checker)
+
+
+ def opt_auth(self, description):
+ """
+ Specify an authentication method for the server.
+ """
+ try:
+ self.addChecker(makeChecker(description))
+ except UnsupportedInterfaces, e:
+ raise usage.UsageError(
+ 'Auth plugin not supported: %s' % e.args[0])
+ except InvalidAuthType, e:
+ raise usage.UsageError(
+ 'Auth plugin not recognized: %s' % e.args[0])
+ except Exception, e:
+ raise usage.UsageError('Unexpected error: %s' % e)
+
+
+ def _checkerFactoriesForOptHelpAuth(self):
+ """
+ Return a list of which authTypes will be displayed by --help-auth.
+ This makes it a lot easier to test this module.
+ """
+ for factory in findCheckerFactories():
+ for interface in factory.credentialInterfaces:
+ if self.supportsInterface(interface):
+ yield factory
+ break
+
+
+ def opt_help_auth(self):
+ """
+ Show all authentication methods available.
+ """
+ self.authOutput.write("Usage: --auth AuthType[:ArgString]\n")
+ self.authOutput.write("For detailed help: --help-auth-type AuthType\n")
+ self.authOutput.write('\n')
+ # Figure out the right width for our columns
+ firstLength = 0
+ for factory in self._checkerFactoriesForOptHelpAuth():
+ if len(factory.authType) > firstLength:
+ firstLength = len(factory.authType)
+ formatString = ' %%-%is\t%%s\n' % firstLength
+ self.authOutput.write(formatString % ('AuthType', 'ArgString format'))
+ self.authOutput.write(formatString % ('========', '================'))
+ for factory in self._checkerFactoriesForOptHelpAuth():
+ self.authOutput.write(
+ formatString % (factory.authType, factory.argStringFormat))
+ self.authOutput.write('\n')
+ raise SystemExit(0)
+
+
+ def opt_help_auth_type(self, authType):
+ """
+ Show help for a particular authentication type.
+ """
+ try:
+ cf = findCheckerFactory(authType)
+ except InvalidAuthType:
+ raise usage.UsageError("Invalid auth type: %s" % authType)
+ self.authOutput.write("Usage: --auth %s[:ArgString]\n" % authType)
+ self.authOutput.write("ArgString format: %s\n" % cf.argStringFormat)
+ self.authOutput.write('\n')
+ for line in cf.authHelp.strip().splitlines():
+ self.authOutput.write(' %s\n' % line.rstrip())
+ self.authOutput.write('\n')
+ if not self.supportsCheckerFactory(cf):
+ self.authOutput.write(' %s\n' % notSupportedWarning)
+ self.authOutput.write('\n')
+ raise SystemExit(0)
diff --git a/vendor/Twisted-10.0.0/twisted/cred/util.py b/vendor/Twisted-10.0.0/twisted/cred/util.py
new file mode 100644
index 0000000000..03507c8b10
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/cred/util.py
@@ -0,0 +1,46 @@
+# -*- test-case-name: twisted.test.test_newcred -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Outdated, deprecated functionality related to challenge-based authentication.
+
+Seek a solution to your problem elsewhere. This module is deprecated.
+"""
+
+# System Imports
+import random, warnings
+
+from twisted.python.hashlib import md5
+from twisted.cred.error import Unauthorized
+
+
+def respond(challenge, password):
+ """Respond to a challenge.
+ This is useful for challenge/response authentication.
+ """
+ warnings.warn(
+ "twisted.cred.util.respond is deprecated since Twisted 8.3.",
+ category=PendingDeprecationWarning,
+ stacklevel=2)
+ m = md5()
+ m.update(password)
+ hashedPassword = m.digest()
+ m = md5()
+ m.update(hashedPassword)
+ m.update(challenge)
+ doubleHashedPassword = m.digest()
+ return doubleHashedPassword
+
+def challenge():
+ """I return some random data.
+ """
+ warnings.warn(
+ "twisted.cred.util.challenge is deprecated since Twisted 8.3.",
+ category=PendingDeprecationWarning,
+ stacklevel=2)
+ crap = ''
+ for x in range(random.randrange(15,25)):
+ crap = crap + chr(random.randint(65,90))
+ crap = md5(crap).digest()
+ return crap
diff --git a/vendor/Twisted-10.0.0/twisted/enterprise/__init__.py b/vendor/Twisted-10.0.0/twisted/enterprise/__init__.py
new file mode 100644
index 0000000000..5364bed9c7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/enterprise/__init__.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Twisted Enterprise: database support for Twisted services.
+"""
+
+__all__ = ['adbapi', 'reflector', 'row', 'sqlreflector', 'util']
diff --git a/vendor/Twisted-10.0.0/twisted/enterprise/adbapi.py b/vendor/Twisted-10.0.0/twisted/enterprise/adbapi.py
new file mode 100644
index 0000000000..54e87c4e85
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/enterprise/adbapi.py
@@ -0,0 +1,488 @@
+# -*- test-case-name: twisted.test.test_adbapi -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An asynchronous mapping to U{DB-API 2.0<http://www.python.org/topics/database/DatabaseAPI-2.0.html>}.
+"""
+
+import sys
+
+from twisted.internet import threads
+from twisted.python import reflect, log
+from twisted.python.deprecate import deprecated
+from twisted.python.versions import Version
+
+
+
+class ConnectionLost(Exception):
+ """
+ This exception means that a db connection has been lost. Client code may
+ try again.
+ """
+
+
+
+class Connection(object):
+ """
+ A wrapper for a DB-API connection instance.
+
+ The wrapper passes almost everything to the wrapped connection and so has
+ the same API. However, the Connection knows about its pool and also
+ handle reconnecting should when the real connection dies.
+ """
+
+ def __init__(self, pool):
+ self._pool = pool
+ self._connection = None
+ self.reconnect()
+
+ def close(self):
+ # The way adbapi works right now means that closing a connection is
+ # a really bad thing as it leaves a dead connection associated with
+ # a thread in the thread pool.
+ # Really, I think closing a pooled connection should return it to the
+ # pool but that's handled by the runWithConnection method already so,
+ # rather than upsetting anyone by raising an exception, let's ignore
+ # the request
+ pass
+
+ def rollback(self):
+ if not self._pool.reconnect:
+ self._connection.rollback()
+ return
+
+ try:
+ self._connection.rollback()
+ curs = self._connection.cursor()
+ curs.execute(self._pool.good_sql)
+ curs.close()
+ self._connection.commit()
+ return
+ except:
+ log.err(None, "Rollback failed")
+
+ self._pool.disconnect(self._connection)
+
+ if self._pool.noisy:
+ log.msg("Connection lost.")
+
+ raise ConnectionLost()
+
+ def reconnect(self):
+ if self._connection is not None:
+ self._pool.disconnect(self._connection)
+ self._connection = self._pool.connect()
+
+ def __getattr__(self, name):
+ return getattr(self._connection, name)
+
+
+class Transaction:
+ """A lightweight wrapper for a DB-API 'cursor' object.
+
+ Relays attribute access to the DB cursor. That is, you can call
+ execute(), fetchall(), etc., and they will be called on the
+ underlying DB-API cursor object. Attributes will also be
+ retrieved from there.
+ """
+ _cursor = None
+
+ def __init__(self, pool, connection):
+ self._pool = pool
+ self._connection = connection
+ self.reopen()
+
+ def close(self):
+ _cursor = self._cursor
+ self._cursor = None
+ _cursor.close()
+
+ def reopen(self):
+ if self._cursor is not None:
+ self.close()
+
+ try:
+ self._cursor = self._connection.cursor()
+ return
+ except:
+ if not self._pool.reconnect:
+ raise
+ else:
+ log.err(None, "Cursor creation failed")
+
+ if self._pool.noisy:
+ log.msg('Connection lost, reconnecting')
+
+ self.reconnect()
+ self._cursor = self._connection.cursor()
+
+ def reconnect(self):
+ self._connection.reconnect()
+ self._cursor = None
+
+ def __getattr__(self, name):
+ return getattr(self._cursor, name)
+
+
+class ConnectionPool:
+ """
+ Represent a pool of connections to a DB-API 2.0 compliant database.
+
+ @ivar connectionFactory: factory for connections, default to L{Connection}.
+ @type connectionFactory: any callable.
+
+ @ivar transactionFactory: factory for transactions, default to
+ L{Transaction}.
+ @type transactionFactory: any callable
+ """
+
+ CP_ARGS = "min max name noisy openfun reconnect good_sql".split()
+
+ noisy = False # if true, generate informational log messages
+ min = 3 # minimum number of connections in pool
+ max = 5 # maximum number of connections in pool
+ name = None # Name to assign to thread pool for debugging
+ openfun = None # A function to call on new connections
+ reconnect = False # reconnect when connections fail
+ good_sql = 'select 1' # a query which should always succeed
+
+ running = False # true when the pool is operating
+ connectionFactory = Connection
+ transactionFactory = Transaction
+
+ def __init__(self, dbapiName, *connargs, **connkw):
+ """Create a new ConnectionPool.
+
+ Any positional or keyword arguments other than those documented here
+ are passed to the DB-API object when connecting. Use these arguments to
+ pass database names, usernames, passwords, etc.
+
+ @param dbapiName: an import string to use to obtain a DB-API compatible
+ module (e.g. 'pyPgSQL.PgSQL')
+
+ @param cp_min: the minimum number of connections in pool (default 3)
+
+ @param cp_max: the maximum number of connections in pool (default 5)
+
+ @param cp_noisy: generate informational log messages during operation
+ (default False)
+
+ @param cp_openfun: a callback invoked after every connect() on the
+ underlying DB-API object. The callback is passed a
+ new DB-API connection object. This callback can
+ setup per-connection state such as charset,
+ timezone, etc.
+
+ @param cp_reconnect: detect connections which have failed and reconnect
+ (default False). Failed connections may result in
+ ConnectionLost exceptions, which indicate the
+ query may need to be re-sent.
+
+ @param cp_good_sql: an sql query which should always succeed and change
+ no state (default 'select 1')
+ """
+
+ self.dbapiName = dbapiName
+ self.dbapi = reflect.namedModule(dbapiName)
+
+ if getattr(self.dbapi, 'apilevel', None) != '2.0':
+ log.msg('DB API module not DB API 2.0 compliant.')
+
+ if getattr(self.dbapi, 'threadsafety', 0) < 1:
+ log.msg('DB API module not sufficiently thread-safe.')
+
+ self.connargs = connargs
+ self.connkw = connkw
+
+ for arg in self.CP_ARGS:
+ cp_arg = 'cp_%s' % arg
+ if connkw.has_key(cp_arg):
+ setattr(self, arg, connkw[cp_arg])
+ del connkw[cp_arg]
+
+ self.min = min(self.min, self.max)
+ self.max = max(self.min, self.max)
+
+ self.connections = {} # all connections, hashed on thread id
+
+ # these are optional so import them here
+ from twisted.python import threadpool
+ import thread
+
+ self.threadID = thread.get_ident
+ self.threadpool = threadpool.ThreadPool(self.min, self.max)
+
+ from twisted.internet import reactor
+ self.startID = reactor.callWhenRunning(self._start)
+
+ def _start(self):
+ self.startID = None
+ return self.start()
+
+ def start(self):
+ """Start the connection pool.
+
+ If you are using the reactor normally, this function does *not*
+ need to be called.
+ """
+
+ if not self.running:
+ from twisted.internet import reactor
+ self.threadpool.start()
+ self.shutdownID = reactor.addSystemEventTrigger('during',
+ 'shutdown',
+ self.finalClose)
+ self.running = True
+
+
+ def runWithConnection(self, func, *args, **kw):
+ """
+ Execute a function with a database connection and return the result.
+
+ @param func: A callable object of one argument which will be executed
+ in a thread with a connection from the pool. It will be passed as
+ its first argument a L{Connection} instance (whose interface is
+ mostly identical to that of a connection object for your DB-API
+ module of choice), and its results will be returned as a Deferred.
+ If the method raises an exception the transaction will be rolled
+ back. Otherwise, the transaction will be committed. B{Note} that
+ this function is B{not} run in the main thread: it must be
+ threadsafe.
+
+ @param *args: positional arguments to be passed to func
+
+ @param **kw: keyword arguments to be passed to func
+
+ @return: a Deferred which will fire the return value of
+ C{func(Transaction(...), *args, **kw)}, or a Failure.
+ """
+ from twisted.internet import reactor
+ return threads.deferToThreadPool(reactor, self.threadpool,
+ self._runWithConnection,
+ func, *args, **kw)
+
+
+ def _runWithConnection(self, func, *args, **kw):
+ conn = self.connectionFactory(self)
+ try:
+ result = func(conn, *args, **kw)
+ conn.commit()
+ return result
+ except:
+ excType, excValue, excTraceback = sys.exc_info()
+ try:
+ conn.rollback()
+ except:
+ log.err(None, "Rollback failed")
+ raise excType, excValue, excTraceback
+
+
+ def runInteraction(self, interaction, *args, **kw):
+ """
+ Interact with the database and return the result.
+
+ The 'interaction' is a callable object which will be executed
+ in a thread using a pooled connection. It will be passed an
+ L{Transaction} object as an argument (whose interface is
+ identical to that of the database cursor for your DB-API
+ module of choice), and its results will be returned as a
+ Deferred. If running the method raises an exception, the
+ transaction will be rolled back. If the method returns a
+ value, the transaction will be committed.
+
+ NOTE that the function you pass is *not* run in the main
+ thread: you may have to worry about thread-safety in the
+ function you pass to this if it tries to use non-local
+ objects.
+
+ @param interaction: a callable object whose first argument
+ is an L{adbapi.Transaction}.
+
+ @param *args: additional positional arguments to be passed
+ to interaction
+
+ @param **kw: keyword arguments to be passed to interaction
+
+ @return: a Deferred which will fire the return value of
+ 'interaction(Transaction(...), *args, **kw)', or a Failure.
+ """
+ from twisted.internet import reactor
+ return threads.deferToThreadPool(reactor, self.threadpool,
+ self._runInteraction,
+ interaction, *args, **kw)
+
+
+ def runQuery(self, *args, **kw):
+ """Execute an SQL query and return the result.
+
+ A DB-API cursor will will be invoked with cursor.execute(*args, **kw).
+ The exact nature of the arguments will depend on the specific flavor
+ of DB-API being used, but the first argument in *args be an SQL
+ statement. The result of a subsequent cursor.fetchall() will be
+ fired to the Deferred which is returned. If either the 'execute' or
+ 'fetchall' methods raise an exception, the transaction will be rolled
+ back and a Failure returned.
+
+ The *args and **kw arguments will be passed to the DB-API cursor's
+ 'execute' method.
+
+ @return: a Deferred which will fire the return value of a DB-API
+ cursor's 'fetchall' method, or a Failure.
+ """
+ return self.runInteraction(self._runQuery, *args, **kw)
+
+
+ def runOperation(self, *args, **kw):
+ """Execute an SQL query and return None.
+
+ A DB-API cursor will will be invoked with cursor.execute(*args, **kw).
+ The exact nature of the arguments will depend on the specific flavor
+ of DB-API being used, but the first argument in *args will be an SQL
+ statement. This method will not attempt to fetch any results from the
+ query and is thus suitable for INSERT, DELETE, and other SQL statements
+ which do not return values. If the 'execute' method raises an
+ exception, the transaction will be rolled back and a Failure returned.
+
+ The args and kw arguments will be passed to the DB-API cursor's
+ 'execute' method.
+
+ return: a Deferred which will fire None or a Failure.
+ """
+ return self.runInteraction(self._runOperation, *args, **kw)
+
+
+ def close(self):
+ """Close all pool connections and shutdown the pool."""
+
+ from twisted.internet import reactor
+ if self.shutdownID:
+ reactor.removeSystemEventTrigger(self.shutdownID)
+ self.shutdownID = None
+ if self.startID:
+ reactor.removeSystemEventTrigger(self.startID)
+ self.startID = None
+ self.finalClose()
+
+ def finalClose(self):
+ """This should only be called by the shutdown trigger."""
+
+ self.shutdownID = None
+ self.threadpool.stop()
+ self.running = False
+ for conn in self.connections.values():
+ self._close(conn)
+ self.connections.clear()
+
+ def connect(self):
+ """Return a database connection when one becomes available.
+
+ This method blocks and should be run in a thread from the internal
+ threadpool. Don't call this method directly from non-threaded code.
+ Using this method outside the external threadpool may exceed the
+ maximum number of connections in the pool.
+
+ @return: a database connection from the pool.
+ """
+
+ tid = self.threadID()
+ conn = self.connections.get(tid)
+ if conn is None:
+ if self.noisy:
+ log.msg('adbapi connecting: %s %s%s' % (self.dbapiName,
+ self.connargs or '',
+ self.connkw or ''))
+ conn = self.dbapi.connect(*self.connargs, **self.connkw)
+ if self.openfun != None:
+ self.openfun(conn)
+ self.connections[tid] = conn
+ return conn
+
+ def disconnect(self, conn):
+ """Disconnect a database connection associated with this pool.
+
+ Note: This function should only be used by the same thread which
+ called connect(). As with connect(), this function is not used
+ in normal non-threaded twisted code.
+ """
+ tid = self.threadID()
+ if conn is not self.connections.get(tid):
+ raise Exception("wrong connection for thread")
+ if conn is not None:
+ self._close(conn)
+ del self.connections[tid]
+
+
+ def _close(self, conn):
+ if self.noisy:
+ log.msg('adbapi closing: %s' % (self.dbapiName,))
+ try:
+ conn.close()
+ except:
+ log.err(None, "Connection close failed")
+
+
+ def _runInteraction(self, interaction, *args, **kw):
+ conn = self.connectionFactory(self)
+ trans = self.transactionFactory(self, conn)
+ try:
+ result = interaction(trans, *args, **kw)
+ trans.close()
+ conn.commit()
+ return result
+ except:
+ excType, excValue, excTraceback = sys.exc_info()
+ try:
+ conn.rollback()
+ except:
+ log.err(None, "Rollback failed")
+ raise excType, excValue, excTraceback
+
+
+ def _runQuery(self, trans, *args, **kw):
+ trans.execute(*args, **kw)
+ return trans.fetchall()
+
+ def _runOperation(self, trans, *args, **kw):
+ trans.execute(*args, **kw)
+
+ def __getstate__(self):
+ return {'dbapiName': self.dbapiName,
+ 'min': self.min,
+ 'max': self.max,
+ 'noisy': self.noisy,
+ 'reconnect': self.reconnect,
+ 'good_sql': self.good_sql,
+ 'connargs': self.connargs,
+ 'connkw': self.connkw}
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+ self.__init__(self.dbapiName, *self.connargs, **self.connkw)
+
+
+
+# Common deprecation decorator used for all deprecations.
+_unreleasedVersion = Version("Twisted", 8, 0, 0)
+_unreleasedDeprecation = deprecated(_unreleasedVersion)
+
+
+
+def _safe(text):
+ """
+ Something really stupid that replaces quotes with escaped quotes.
+ """
+ return text.replace("'", "''").replace("\\", "\\\\")
+
+
+
+def safe(text):
+ """
+ Make a string safe to include in an SQL statement.
+ """
+ return _safe(text)
+
+safe = _unreleasedDeprecation(safe)
+
+
+__all__ = ['Transaction', 'ConnectionPool', 'safe']
diff --git a/vendor/Twisted-10.0.0/twisted/enterprise/reflector.py b/vendor/Twisted-10.0.0/twisted/enterprise/reflector.py
new file mode 100644
index 0000000000..cc15a3705c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/enterprise/reflector.py
@@ -0,0 +1,167 @@
+# -*- test-case-name: twisted.test.test_reflector -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import weakref, warnings
+
+from twisted.enterprise.util import DBError
+
+class Reflector:
+ """
+ DEPRECATED.
+
+ Base class for enterprise reflectors. This implements row caching.
+ """
+ populated = 0
+
+ def __init__(self, rowClasses):
+ """
+ Initialize me against a database.
+
+ @param rowClasses: a list of row class objects that describe the
+ database schema.
+ """
+ warnings.warn("twisted.enterprise.reflector is deprecated since "
+ "Twisted 8.0", category=DeprecationWarning, stacklevel=2)
+ # does not hold references to cached rows.
+ self.rowCache = weakref.WeakValueDictionary()
+ self.rowClasses = rowClasses
+ self.schema = {}
+ self._populate()
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ del d['rowCache']
+ return d
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+ self.rowCache = weakref.WeakValueDictionary()
+ self._populate()
+
+ def _populate(self):
+ """Implement me to populate schema information for the reflector.
+ """
+ raise DBError("not implemented")
+
+ def populateSchemaFor(self, tableInfo):
+ """This is called once for each registered rowClass to add it
+ and its foreign key relationships for that rowClass to the
+ schema."""
+
+ self.schema[ tableInfo.rowTableName ] = tableInfo
+
+ # add the foreign key to the foreign table.
+ for foreignTableName, childColumns, parentColumns, containerMethod, autoLoad in tableInfo.rowForeignKeys:
+ self.schema[foreignTableName].addForeignKey(childColumns,
+ parentColumns, tableInfo.rowClass,
+ containerMethod, autoLoad)
+
+ def getTableInfo(self, rowObject):
+ """Get a TableInfo record about a particular instance.
+
+ This record contains various information about the instance's
+ class as registered with this reflector.
+
+ @param rowObject: a L{RowObject} instance of a class previously
+ registered with me.
+ @raises twisted.enterprise.row.DBError: raised if this class was not
+ previously registered.
+ """
+ try:
+ return self.schema[rowObject.rowTableName]
+ except KeyError:
+ raise DBError("class %s was not registered with %s" % (
+ rowObject.__class__, self))
+
+ def buildWhereClause(self, relationship, row):
+ """util method used by reflectors. builds a where clause to link a row to another table.
+ """
+ whereClause = []
+ for i in range(0,len(relationship.childColumns)):
+ value = getattr(row, relationship.parentColumns[i][0])
+ whereClause.append( [relationship.childColumns[i][0], EQUAL, value] )
+ return whereClause
+
+ def addToParent(self, parentRow, rows, tableName):
+ """util method used by reflectors. adds these rows to the parent row object.
+ If a rowClass does not have a containerMethod, then a list attribute "childRows"
+ will be used.
+ """
+ parentInfo = self.getTableInfo(parentRow)
+ relationship = parentInfo.getRelationshipFor(tableName)
+ if not relationship:
+ raise DBError("no relationship from %s to %s" % ( parentRow.rowTableName, tableName) )
+
+ if not relationship.containerMethod:
+ if hasattr(parentRow, "childRows"):
+ for row in rows:
+ if row not in parentRow.childRows:
+ parentRow.childRows.append(row)
+ else:
+ parentRow.childRows = rows
+ return
+
+ if not hasattr(parentRow, relationship.containerMethod):
+ raise DBError("parent row (%s) doesnt have container method <%s>!" % (parentRow, relationship.containerMethod))
+
+ meth = getattr(parentRow, relationship.containerMethod)
+ for row in rows:
+ meth(row)
+
+ ####### Row Cache ########
+
+ def addToCache(self, rowObject):
+ """NOTE: Should this be recursive?! requires better container knowledge..."""
+ self.rowCache[ rowObject.getKeyTuple() ] = rowObject
+
+ def findInCache(self, rowClass, kw):
+ keys = []
+ keys.append(rowClass.rowTableName)
+ for keyName, keyType in rowClass.rowKeyColumns:
+ keys.append( kw[keyName] )
+ keyTuple = tuple(keys)
+ return self.rowCache.get(keyTuple)
+
+ def removeFromCache(self, rowObject):
+ """NOTE: should this be recursive!??"""
+ key = rowObject.getKeyTuple()
+ if self.rowCache.has_key(key):
+ del self.rowCache[key]
+
+ ####### Row Operations ########
+
+ def loadObjectsFrom(self, tableName, parent=None, data=None,
+ whereClause=[], loadChildren=1):
+ """Implement me to load objects from the database.
+
+ @param whereClause: a list of tuples of (columnName, conditional, value)
+ so it can be parsed by all types of reflectors. eg.::
+ whereClause = [("name", EQUALS, "fred"), ("age", GREATERTHAN, 18)]
+ """
+ raise DBError("not implemented")
+
+ def updateRow(self, rowObject):
+ """update this rowObject to the database.
+ """
+ raise DBError("not implemented")
+
+ def insertRow(self, rowObject):
+ """insert a new row for this object instance.
+ """
+ raise DBError("not implemented")
+
+ def deleteRow(self, rowObject):
+ """delete the row for this object from the database.
+ """
+ raise DBError("not implemented")
+
+# conditionals
+EQUAL = 0
+LESSTHAN = 1
+GREATERTHAN = 2
+LIKE = 3
+
+
+__all__ = ['Reflector', 'EQUAL', 'LESSTHAN', 'GREATERTHAN', 'LIKE']
diff --git a/vendor/Twisted-10.0.0/twisted/enterprise/row.py b/vendor/Twisted-10.0.0/twisted/enterprise/row.py
new file mode 100644
index 0000000000..7cf2541789
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/enterprise/row.py
@@ -0,0 +1,127 @@
+# -*- test-case-name: twisted.test.test_reflector -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+DEPRECATED.
+
+A (R)elational (O)bject (W)rapper.
+
+This is an extremely thin wrapper.
+
+Maintainer: Dave Peticolas
+"""
+
+import warnings
+
+from twisted.enterprise.util import DBError, NOQUOTE, getKeyColumn, dbTypeMap
+
+
+class RowObject:
+ """
+ I represent a row in a table in a relational database.
+
+ My class is "populated" by a Reflector object. After I am
+ populated, instances of me are able to interact with a particular
+ database table.
+
+ You should use a class derived from this class for each database
+ table.
+
+ reflector.loadObjectsFrom() is used to create sets of
+ instance of objects of this class from database tables.
+
+ Once created, the "key column" attributes cannot be changed.
+
+
+ Class Attributes that users must supply::
+
+ rowKeyColumns # list of key columns in form: [(columnName, typeName)]
+ rowTableName # name of database table
+ rowColumns # list of the columns in the table with the correct
+ # case.this will be used to create member variables.
+ rowFactoryMethod # method to create an instance of this class.
+ # HACK: must be in a list!!! [factoryMethod] (optional)
+ rowForeignKeys # keys to other tables (optional)
+ """
+
+ populated = 0 # set on the class when the class is "populated" with SQL
+ dirty = 0 # set on an instance when the instance is out-of-sync with the database
+
+ def __init__(self):
+ """
+ DEPRECATED.
+ """
+ warnings.warn("twisted.enterprise.row is deprecated since Twisted 8.0",
+ category=DeprecationWarning, stacklevel=2)
+
+ def assignKeyAttr(self, attrName, value):
+ """Assign to a key attribute.
+
+ This cannot be done through normal means to protect changing
+ keys of db objects.
+ """
+ found = 0
+ for keyColumn, type in self.rowKeyColumns:
+ if keyColumn == attrName:
+ found = 1
+ if not found:
+ raise DBError("%s is not a key columns." % attrName)
+ self.__dict__[attrName] = value
+
+ def findAttribute(self, attrName):
+ """Find an attribute by caseless name.
+ """
+ for attr, type in self.rowColumns:
+ if attr.lower() == attrName.lower():
+ return getattr(self, attr)
+ raise DBError("Unable to find attribute %s" % attrName)
+
+ def __setattr__(self, name, value):
+ """Special setattr to prevent changing of key values.
+ """
+ # build where clause
+ if getKeyColumn(self.__class__, name):
+ raise DBError("cannot assign value <%s> to key column attribute <%s> of RowObject class" % (value,name))
+
+ if name in self.rowColumns:
+ if value != self.__dict__.get(name,None) and not self.dirty:
+ self.setDirty(1)
+
+ self.__dict__[name] = value
+
+ def createDefaultAttributes(self):
+ """Populate instance with default attributes.
+
+ This is used when creating a new instance NOT from the
+ database.
+ """
+ for attr in self.rowColumns:
+ if getKeyColumn(self.__class__, attr):
+ continue
+ for column, ctype, typeid in self.dbColumns:
+ if column.lower(column) == attr.lower():
+ q = dbTypeMap.get(ctype, None)
+ if q == NOQUOTE:
+ setattr(self, attr, 0)
+ else:
+ setattr(self, attr, "")
+
+ def setDirty(self, flag):
+ """Use this to set the 'dirty' flag.
+
+ (note: this avoids infinite recursion in __setattr__, and
+ prevents the 'dirty' flag )
+ """
+ self.__dict__["dirty"] = flag
+
+ def getKeyTuple(self):
+ keys = []
+ keys.append(self.rowTableName)
+ for keyName, keyType in self.rowKeyColumns:
+ keys.append( getattr(self, keyName) )
+ return tuple(keys)
+
+
+__all__ = ['RowObject']
diff --git a/vendor/Twisted-10.0.0/twisted/enterprise/sqlreflector.py b/vendor/Twisted-10.0.0/twisted/enterprise/sqlreflector.py
new file mode 100644
index 0000000000..d29d9fbad0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/enterprise/sqlreflector.py
@@ -0,0 +1,327 @@
+# -*- test-case-name: twisted.test.test_reflector -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.enterprise import reflector
+from twisted.enterprise.util import DBError, getKeyColumn, quote, safe
+from twisted.enterprise.util import _TableInfo
+from twisted.enterprise.row import RowObject
+
+from twisted.python import reflect
+
+class SQLReflector(reflector.Reflector):
+ """
+ DEPRECATED.
+
+ I reflect on a database and load RowObjects from it.
+
+ In order to do this, I interrogate a relational database to
+ extract schema information and interface with RowObject class
+ objects that can interact with specific tables.
+ """
+ populated = 0
+ conditionalLabels = {
+ reflector.EQUAL : "=",
+ reflector.LESSTHAN : "<",
+ reflector.GREATERTHAN : ">",
+ reflector.LIKE : "like"
+ }
+
+ def __init__(self, dbpool, rowClasses):
+ """Initialize me against a database.
+ """
+ reflector.Reflector.__init__(self, rowClasses)
+ self.dbpool = dbpool
+
+ def _populate(self):
+ self._transPopulateSchema()
+
+ def _transPopulateSchema(self):
+ """Used to construct the row classes in a single interaction.
+ """
+ for rc in self.rowClasses:
+ if not issubclass(rc, RowObject):
+ raise DBError("Stub class (%s) is not derived from RowObject" % reflect.qual(rc.rowClass))
+
+ self._populateSchemaFor(rc)
+ self.populated = 1
+
+ def _populateSchemaFor(self, rc):
+ """Construct all the SQL templates for database operations on
+ <tableName> and populate the class <rowClass> with that info.
+ """
+ attributes = ("rowColumns", "rowKeyColumns", "rowTableName" )
+ for att in attributes:
+ if not hasattr(rc, att):
+ raise DBError("RowClass %s must have class variable: %s" % (rc, att))
+
+ tableInfo = _TableInfo(rc)
+ tableInfo.updateSQL = self.buildUpdateSQL(tableInfo)
+ tableInfo.insertSQL = self.buildInsertSQL(tableInfo)
+ tableInfo.deleteSQL = self.buildDeleteSQL(tableInfo)
+ self.populateSchemaFor(tableInfo)
+
+ def escape_string(self, text):
+ """Escape a string for use in an SQL statement. The default
+ implementation escapes ' with '' and \ with \\. Redefine this
+ function in a subclass if your database server uses different
+ escaping rules.
+ """
+ return safe(text)
+
+ def quote_value(self, value, type):
+ """Format a value for use in an SQL statement.
+
+ @param value: a value to format as data in SQL.
+ @param type: a key in util.dbTypeMap.
+ """
+ return quote(value, type, string_escaper=self.escape_string)
+
+ def loadObjectsFrom(self, tableName, parentRow=None, data=None,
+ whereClause=None, forceChildren=0):
+ """Load a set of RowObjects from a database.
+
+ Create a set of python objects of <rowClass> from the contents
+ of a table populated with appropriate data members.
+ Example::
+
+ | class EmployeeRow(row.RowObject):
+ | pass
+ |
+ | def gotEmployees(employees):
+ | for emp in employees:
+ | emp.manager = "fred smith"
+ | manager.updateRow(emp)
+ |
+ | reflector.loadObjectsFrom("employee",
+ | data = userData,
+ | whereClause = [("manager" , EQUAL, "fred smith")]
+ | ).addCallback(gotEmployees)
+
+ NOTE: the objects and all children should be loaded in a single transaction.
+ NOTE: can specify a parentRow _OR_ a whereClause.
+
+ """
+ if parentRow and whereClause:
+ raise DBError("Must specify one of parentRow _OR_ whereClause")
+ if parentRow:
+ info = self.getTableInfo(parentRow)
+ relationship = info.getRelationshipFor(tableName)
+ whereClause = self.buildWhereClause(relationship, parentRow)
+ elif whereClause:
+ pass
+ else:
+ whereClause = []
+ return self.dbpool.runInteraction(self._rowLoader, tableName,
+ parentRow, data, whereClause,
+ forceChildren)
+
+ def _rowLoader(self, transaction, tableName, parentRow, data,
+ whereClause, forceChildren):
+ """immediate loading of rowobjects from the table with the whereClause.
+ """
+ tableInfo = self.schema[tableName]
+ # Build the SQL for the query
+ sql = "SELECT "
+ first = 1
+ for column, type in tableInfo.rowColumns:
+ if first:
+ first = 0
+ else:
+ sql = sql + ","
+ sql = sql + " %s" % column
+ sql = sql + " FROM %s " % (tableName)
+ if whereClause:
+ sql += " WHERE "
+ first = 1
+ for wItem in whereClause:
+ if first:
+ first = 0
+ else:
+ sql += " AND "
+ (columnName, cond, value) = wItem
+ t = self.findTypeFor(tableName, columnName)
+ quotedValue = self.quote_value(value, t)
+ sql += "%s %s %s" % (columnName, self.conditionalLabels[cond],
+ quotedValue)
+
+ # execute the query
+ transaction.execute(sql)
+ rows = transaction.fetchall()
+
+ # construct the row objects
+ results = []
+ newRows = []
+ for args in rows:
+ kw = {}
+ for i in range(0,len(args)):
+ ColumnName = tableInfo.rowColumns[i][0].lower()
+ for attr, type in tableInfo.rowClass.rowColumns:
+ if attr.lower() == ColumnName:
+ kw[attr] = args[i]
+ break
+ # find the row in the cache or add it
+ resultObject = self.findInCache(tableInfo.rowClass, kw)
+ if not resultObject:
+ meth = tableInfo.rowFactoryMethod[0]
+ resultObject = meth(tableInfo.rowClass, data, kw)
+ self.addToCache(resultObject)
+ newRows.append(resultObject)
+ results.append(resultObject)
+
+ # add these rows to the parentRow if required
+ if parentRow:
+ self.addToParent(parentRow, newRows, tableName)
+
+ # load children or each of these rows if required
+ for relationship in tableInfo.relationships:
+ if not forceChildren and not relationship.autoLoad:
+ continue
+ for row in results:
+ # build where clause
+ childWhereClause = self.buildWhereClause(relationship, row)
+ # load the children immediately, but do nothing with them
+ self._rowLoader(transaction,
+ relationship.childRowClass.rowTableName,
+ row, data, childWhereClause, forceChildren)
+
+ return results
+
+ def findTypeFor(self, tableName, columnName):
+ tableInfo = self.schema[tableName]
+ columnName = columnName.lower()
+ for column, type in tableInfo.rowColumns:
+ if column.lower() == columnName:
+ return type
+
+ def buildUpdateSQL(self, tableInfo):
+ """(Internal) Build SQL template to update a RowObject.
+
+ Returns: SQL that is used to contruct a rowObject class.
+ """
+ sql = "UPDATE %s SET" % tableInfo.rowTableName
+ # build update attributes
+ first = 1
+ for column, type in tableInfo.rowColumns:
+ if getKeyColumn(tableInfo.rowClass, column):
+ continue
+ if not first:
+ sql = sql + ", "
+ sql = sql + " %s = %s" % (column, "%s")
+ first = 0
+
+ # build where clause
+ first = 1
+ sql = sql + " WHERE "
+ for keyColumn, type in tableInfo.rowKeyColumns:
+ if not first:
+ sql = sql + " AND "
+ sql = sql + " %s = %s " % (keyColumn, "%s")
+ first = 0
+ return sql
+
+ def buildInsertSQL(self, tableInfo):
+ """(Internal) Build SQL template to insert a new row.
+
+ Returns: SQL that is used to insert a new row for a rowObject
+ instance not created from the database.
+ """
+ sql = "INSERT INTO %s (" % tableInfo.rowTableName
+ # build column list
+ first = 1
+ for column, type in tableInfo.rowColumns:
+ if not first:
+ sql = sql + ", "
+ sql = sql + column
+ first = 0
+
+ sql = sql + " ) VALUES ("
+
+ # build values list
+ first = 1
+ for column, type in tableInfo.rowColumns:
+ if not first:
+ sql = sql + ", "
+ sql = sql + "%s"
+ first = 0
+
+ sql = sql + ")"
+ return sql
+
+ def buildDeleteSQL(self, tableInfo):
+ """Build the SQL template to delete a row from the table.
+ """
+ sql = "DELETE FROM %s " % tableInfo.rowTableName
+ # build where clause
+ first = 1
+ sql = sql + " WHERE "
+ for keyColumn, type in tableInfo.rowKeyColumns:
+ if not first:
+ sql = sql + " AND "
+ sql = sql + " %s = %s " % (keyColumn, "%s")
+ first = 0
+ return sql
+
+ def updateRowSQL(self, rowObject):
+ """Build SQL to update the contents of rowObject.
+ """
+ args = []
+ tableInfo = self.schema[rowObject.rowTableName]
+ # build update attributes
+ for column, type in tableInfo.rowColumns:
+ if not getKeyColumn(rowObject.__class__, column):
+ args.append(self.quote_value(rowObject.findAttribute(column),
+ type))
+ # build where clause
+ for keyColumn, type in tableInfo.rowKeyColumns:
+ args.append(self.quote_value(rowObject.findAttribute(keyColumn),
+ type))
+
+ return self.getTableInfo(rowObject).updateSQL % tuple(args)
+
+ def updateRow(self, rowObject):
+ """Update the contents of rowObject to the database.
+ """
+ sql = self.updateRowSQL(rowObject)
+ rowObject.setDirty(0)
+ return self.dbpool.runOperation(sql)
+
+ def insertRowSQL(self, rowObject):
+ """Build SQL to insert the contents of rowObject.
+ """
+ args = []
+ tableInfo = self.schema[rowObject.rowTableName]
+ # build values
+ for column, type in tableInfo.rowColumns:
+ args.append(self.quote_value(rowObject.findAttribute(column),type))
+ return self.getTableInfo(rowObject).insertSQL % tuple(args)
+
+ def insertRow(self, rowObject):
+ """Insert a new row for rowObject.
+ """
+ rowObject.setDirty(0)
+ sql = self.insertRowSQL(rowObject)
+ return self.dbpool.runOperation(sql)
+
+ def deleteRowSQL(self, rowObject):
+ """Build SQL to delete rowObject from the database.
+ """
+ args = []
+ tableInfo = self.schema[rowObject.rowTableName]
+ # build where clause
+ for keyColumn, type in tableInfo.rowKeyColumns:
+ args.append(self.quote_value(rowObject.findAttribute(keyColumn),
+ type))
+
+ return self.getTableInfo(rowObject).deleteSQL % tuple(args)
+
+ def deleteRow(self, rowObject):
+ """Delete the row for rowObject from the database.
+ """
+ sql = self.deleteRowSQL(rowObject)
+ self.removeFromCache(rowObject)
+ return self.dbpool.runOperation(sql)
+
+
+__all__ = ['SQLReflector']
diff --git a/vendor/Twisted-10.0.0/twisted/enterprise/util.py b/vendor/Twisted-10.0.0/twisted/enterprise/util.py
new file mode 100644
index 0000000000..e75b92f401
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/enterprise/util.py
@@ -0,0 +1,200 @@
+# -*- test-case-name: twisted.test.test_enterprise -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import warnings, types
+
+from twisted.python.versions import Version, getVersionString
+from twisted.python.deprecate import deprecated
+from twisted.enterprise.adbapi import _safe
+
+# Common deprecation decorator used for all deprecations.
+_deprecatedVersion = Version("Twisted", 8, 0, 0)
+_releasedDeprecation = deprecated(_deprecatedVersion)
+
+warnings.warn(
+ "twisted.enterprise.util is deprecated since %s." % (
+ getVersionString(_deprecatedVersion),),
+ category=DeprecationWarning)
+
+NOQUOTE = 1
+USEQUOTE = 2
+
+dbTypeMap = {
+ "bigint": NOQUOTE,
+ "bool": USEQUOTE,
+ "boolean": USEQUOTE,
+ "bytea": USEQUOTE,
+ "date": USEQUOTE,
+ "int2": NOQUOTE,
+ "int4": NOQUOTE,
+ "int8": NOQUOTE,
+ "int": NOQUOTE,
+ "integer": NOQUOTE,
+ "float4": NOQUOTE,
+ "float8": NOQUOTE,
+ "numeric": NOQUOTE,
+ "real": NOQUOTE,
+ "smallint": NOQUOTE,
+ "char": USEQUOTE,
+ "text": USEQUOTE,
+ "time": USEQUOTE,
+ "timestamp": USEQUOTE,
+ "varchar": USEQUOTE
+ }
+
+class DBError(Exception):
+ pass
+
+
+
+### Utility functions
+
+def getKeyColumn(rowClass, name):
+ lcname = name.lower()
+ for keyColumn, type in rowClass.rowKeyColumns:
+ if lcname == keyColumn.lower():
+ return name
+ return None
+getKeyColumn = _releasedDeprecation(getKeyColumn)
+
+
+
+def quote(value, typeCode, string_escaper=_safe):
+ """Add quotes for text types and no quotes for integer types.
+ NOTE: uses Postgresql type codes.
+ """
+ q = dbTypeMap.get(typeCode, None)
+ if q is None:
+ raise DBError("Type %s not known" % typeCode)
+ if value is None:
+ return 'null'
+ if q == NOQUOTE:
+ return str(value)
+ elif q == USEQUOTE:
+ if typeCode.startswith('bool'):
+ if value:
+ value = '1'
+ else:
+ value = '0'
+ if typeCode == "bytea":
+ l = ["'"]
+ for c in value:
+ i = ord(c)
+ if i == 0:
+ l.append("\\\\000")
+ elif i == 92:
+ l.append(c * 4)
+ elif 32 <= i <= 126:
+ l.append(c)
+ else:
+ l.append("\\%03o" % i)
+ l.append("'")
+ return "".join(l)
+ if not isinstance(value, types.StringType) and \
+ not isinstance(value, types.UnicodeType):
+ value = str(value)
+ return "'%s'" % string_escaper(value)
+quote = _releasedDeprecation(quote)
+
+
+def safe(text):
+ """
+ Make a string safe to include in an SQL statement.
+ """
+ return _safe(text)
+
+safe = _releasedDeprecation(safe)
+
+
+def makeKW(rowClass, args):
+ """Utility method to construct a dictionary for the attributes
+ of an object from set of args. This also fixes the case of column names.
+ """
+ kw = {}
+ for i in range(0,len(args)):
+ columnName = rowClass.dbColumns[i][0].lower()
+ for attr in rowClass.rowColumns:
+ if attr.lower() == columnName:
+ kw[attr] = args[i]
+ break
+ return kw
+makeKW = _releasedDeprecation(makeKW)
+
+
+def defaultFactoryMethod(rowClass, data, kw):
+ """Used by loadObjects to create rowObject instances.
+ """
+ newObject = rowClass()
+ newObject.__dict__.update(kw)
+ return newObject
+defaultFactoryMethod = _releasedDeprecation(defaultFactoryMethod)
+
+### utility classes
+
+class _TableInfo:
+ """(internal)
+
+ Info about a table/class and it's relationships. Also serves as a container for
+ generated SQL.
+ """
+ def __init__(self, rc):
+ self.rowClass = rc
+ self.rowTableName = rc.rowTableName
+ self.rowKeyColumns = rc.rowKeyColumns
+ self.rowColumns = rc.rowColumns
+
+ if hasattr(rc, "rowForeignKeys"):
+ self.rowForeignKeys = rc.rowForeignKeys
+ else:
+ self.rowForeignKeys = []
+
+ if hasattr(rc, "rowFactoryMethod"):
+ if rc.rowFactoryMethod:
+ self.rowFactoryMethod = rc.rowFactoryMethod
+ else:
+ self.rowFactoryMethod = [defaultFactoryMethod]
+ else:
+ self.rowFactoryMethod = [defaultFactoryMethod]
+
+ self.updateSQL = None
+ self.deleteSQL = None
+ self.insertSQL = None
+ self.relationships = []
+ self.dbColumns = []
+
+ def addForeignKey(self, childColumns, parentColumns, childRowClass, containerMethod, autoLoad):
+ """This information is attached to the "parent" table
+ childColumns - columns of the "child" table
+ parentColumns - columns of the "parent" table, the one being joined to... the "foreign" table
+ """
+ self.relationships.append( _TableRelationship(childColumns, parentColumns,
+ childRowClass, containerMethod, autoLoad) )
+
+ def getRelationshipFor(self, tableName):
+ for relationship in self.relationships:
+ if relationship.childRowClass.rowTableName == tableName:
+ return relationship
+ return None
+
+class _TableRelationship:
+ """(Internal)
+
+ A foreign key relationship between two tables.
+ """
+ def __init__(self,
+ childColumns,
+ parentColumns,
+ childRowClass,
+ containerMethod,
+ autoLoad):
+ self.childColumns = childColumns
+ self.parentColumns = parentColumns
+ self.childRowClass = childRowClass
+ self.containerMethod = containerMethod
+ self.autoLoad = autoLoad
+
+
+__all__ = ['NOQUOTE', 'USEQUOTE', 'dbTypeMap', 'DBError', 'getKeyColumn',
+ 'safe', 'quote']
diff --git a/vendor/Twisted-10.0.0/twisted/internet/__init__.py b/vendor/Twisted-10.0.0/twisted/internet/__init__.py
new file mode 100644
index 0000000000..7c617cfd58
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Twisted Internet: Asynchronous I/O and Events.
+
+Twisted Internet is a collection of compatible event-loops for Python. It contains
+the code to dispatch events to interested observers and a portable API so that
+observers need not care about which event loop is running. Thus, it is possible
+to use the same code for different loops, from Twisted's basic, yet portable,
+select-based loop to the loops of various GUI toolkits like GTK+ or Tk.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/internet/_baseprocess.py b/vendor/Twisted-10.0.0/twisted/internet/_baseprocess.py
new file mode 100644
index 0000000000..c411753534
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/_baseprocess.py
@@ -0,0 +1,62 @@
+# -*- test-case-name: twisted.test.test_process -*-
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Cross-platform process-related functionality used by different
+L{IReactorProcess} implementations.
+"""
+
+from twisted.python.reflect import qual
+from twisted.python.deprecate import getWarningMethod
+from twisted.python.failure import Failure
+from twisted.python.log import err
+from twisted.persisted.styles import Ephemeral
+
+_missingProcessExited = ("Since Twisted 8.2, IProcessProtocol.processExited "
+ "is required. %s must implement it.")
+
+class BaseProcess(Ephemeral):
+ pid = None
+ status = None
+ lostProcess = 0
+ proto = None
+
+ def __init__(self, protocol):
+ self.proto = protocol
+
+
+ def _callProcessExited(self, reason):
+ default = object()
+ processExited = getattr(self.proto, 'processExited', default)
+ if processExited is default:
+ getWarningMethod()(
+ _missingProcessExited % (qual(self.proto.__class__),),
+ DeprecationWarning, stacklevel=0)
+ else:
+ processExited(Failure(reason))
+
+
+ def processEnded(self, status):
+ """
+ This is called when the child terminates.
+ """
+ self.status = status
+ self.lostProcess += 1
+ self.pid = None
+ self._callProcessExited(self._getReason(status))
+ self.maybeCallProcessEnded()
+
+
+ def maybeCallProcessEnded(self):
+ """
+ Call processEnded on protocol after final cleanup.
+ """
+ if self.proto is not None:
+ reason = self._getReason(self.status)
+ proto = self.proto
+ self.proto = None
+ try:
+ proto.processEnded(Failure(reason))
+ except:
+ err(None, "unexpected error in processEnded")
diff --git a/vendor/Twisted-10.0.0/twisted/internet/_dumbwin32proc.py b/vendor/Twisted-10.0.0/twisted/internet/_dumbwin32proc.py
new file mode 100644
index 0000000000..6a31ae38ef
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/_dumbwin32proc.py
@@ -0,0 +1,340 @@
+# -*- test-case-name: twisted.test.test_process -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+http://isometric.sixsided.org/_/gates_in_the_head/
+"""
+
+import os
+
+# Win32 imports
+import win32api
+import win32con
+import win32event
+import win32file
+import win32pipe
+import win32process
+import win32security
+
+import pywintypes
+
+# security attributes for pipes
+PIPE_ATTRS_INHERITABLE = win32security.SECURITY_ATTRIBUTES()
+PIPE_ATTRS_INHERITABLE.bInheritHandle = 1
+
+from zope.interface import implements
+from twisted.internet.interfaces import IProcessTransport, IConsumer, IProducer
+
+from twisted.python.win32 import quoteArguments
+
+from twisted.internet import error
+from twisted.python import failure
+
+from twisted.internet import _pollingfile
+from twisted.internet._baseprocess import BaseProcess
+
+def debug(msg):
+ import sys
+ print msg
+ sys.stdout.flush()
+
+class _Reaper(_pollingfile._PollableResource):
+
+ def __init__(self, proc):
+ self.proc = proc
+
+ def checkWork(self):
+ if win32event.WaitForSingleObject(self.proc.hProcess, 0) != win32event.WAIT_OBJECT_0:
+ return 0
+ exitCode = win32process.GetExitCodeProcess(self.proc.hProcess)
+ self.deactivate()
+ self.proc.processEnded(exitCode)
+ return 0
+
+
+def _findShebang(filename):
+ """
+ Look for a #! line, and return the value following the #! if one exists, or
+ None if this file is not a script.
+
+ I don't know if there are any conventions for quoting in Windows shebang
+ lines, so this doesn't support any; therefore, you may not pass any
+ arguments to scripts invoked as filters. That's probably wrong, so if
+ somebody knows more about the cultural expectations on Windows, please feel
+ free to fix.
+
+ This shebang line support was added in support of the CGI tests;
+ appropriately enough, I determined that shebang lines are culturally
+ accepted in the Windows world through this page::
+
+ http://www.cgi101.com/learn/connect/winxp.html
+
+ @param filename: str representing a filename
+
+ @return: a str representing another filename.
+ """
+ f = file(filename, 'rU')
+ if f.read(2) == '#!':
+ exe = f.readline(1024).strip('\n')
+ return exe
+
+def _invalidWin32App(pywinerr):
+ """
+ Determine if a pywintypes.error is telling us that the given process is
+ 'not a valid win32 application', i.e. not a PE format executable.
+
+ @param pywinerr: a pywintypes.error instance raised by CreateProcess
+
+ @return: a boolean
+ """
+
+ # Let's do this better in the future, but I have no idea what this error
+ # is; MSDN doesn't mention it, and there is no symbolic constant in
+ # win32process module that represents 193.
+
+ return pywinerr.args[0] == 193
+
+class Process(_pollingfile._PollingTimer, BaseProcess):
+ """A process that integrates with the Twisted event loop.
+
+ If your subprocess is a python program, you need to:
+
+ - Run python.exe with the '-u' command line option - this turns on
+ unbuffered I/O. Buffering stdout/err/in can cause problems, see e.g.
+ http://support.microsoft.com/default.aspx?scid=kb;EN-US;q1903
+
+ - If you don't want Windows messing with data passed over
+ stdin/out/err, set the pipes to be in binary mode::
+
+ import os, sys, mscvrt
+ msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
+ msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
+
+ """
+ implements(IProcessTransport, IConsumer, IProducer)
+
+ closedNotifies = 0
+
+ def __init__(self, reactor, protocol, command, args, environment, path):
+ _pollingfile._PollingTimer.__init__(self, reactor)
+ BaseProcess.__init__(self, protocol)
+
+ # security attributes for pipes
+ sAttrs = win32security.SECURITY_ATTRIBUTES()
+ sAttrs.bInheritHandle = 1
+
+ # create the pipes which will connect to the secondary process
+ self.hStdoutR, hStdoutW = win32pipe.CreatePipe(sAttrs, 0)
+ self.hStderrR, hStderrW = win32pipe.CreatePipe(sAttrs, 0)
+ hStdinR, self.hStdinW = win32pipe.CreatePipe(sAttrs, 0)
+
+ win32pipe.SetNamedPipeHandleState(self.hStdinW,
+ win32pipe.PIPE_NOWAIT,
+ None,
+ None)
+
+ # set the info structure for the new process.
+ StartupInfo = win32process.STARTUPINFO()
+ StartupInfo.hStdOutput = hStdoutW
+ StartupInfo.hStdError = hStderrW
+ StartupInfo.hStdInput = hStdinR
+ StartupInfo.dwFlags = win32process.STARTF_USESTDHANDLES
+
+ # Create new handles whose inheritance property is false
+ currentPid = win32api.GetCurrentProcess()
+
+ tmp = win32api.DuplicateHandle(currentPid, self.hStdoutR, currentPid, 0, 0,
+ win32con.DUPLICATE_SAME_ACCESS)
+ win32file.CloseHandle(self.hStdoutR)
+ self.hStdoutR = tmp
+
+ tmp = win32api.DuplicateHandle(currentPid, self.hStderrR, currentPid, 0, 0,
+ win32con.DUPLICATE_SAME_ACCESS)
+ win32file.CloseHandle(self.hStderrR)
+ self.hStderrR = tmp
+
+ tmp = win32api.DuplicateHandle(currentPid, self.hStdinW, currentPid, 0, 0,
+ win32con.DUPLICATE_SAME_ACCESS)
+ win32file.CloseHandle(self.hStdinW)
+ self.hStdinW = tmp
+
+ # Add the specified environment to the current environment - this is
+ # necessary because certain operations are only supported on Windows
+ # if certain environment variables are present.
+
+ env = os.environ.copy()
+ env.update(environment or {})
+
+ cmdline = quoteArguments(args)
+ # TODO: error detection here.
+ def doCreate():
+ self.hProcess, self.hThread, self.pid, dwTid = win32process.CreateProcess(
+ command, cmdline, None, None, 1, 0, env, path, StartupInfo)
+ try:
+ doCreate()
+ except pywintypes.error, pwte:
+ if not _invalidWin32App(pwte):
+ # This behavior isn't _really_ documented, but let's make it
+ # consistent with the behavior that is documented.
+ raise OSError(pwte)
+ else:
+ # look for a shebang line. Insert the original 'command'
+ # (actually a script) into the new arguments list.
+ sheb = _findShebang(command)
+ if sheb is None:
+ raise OSError(
+ "%r is neither a Windows executable, "
+ "nor a script with a shebang line" % command)
+ else:
+ args = list(args)
+ args.insert(0, command)
+ cmdline = quoteArguments(args)
+ origcmd = command
+ command = sheb
+ try:
+ # Let's try again.
+ doCreate()
+ except pywintypes.error, pwte2:
+ # d'oh, failed again!
+ if _invalidWin32App(pwte2):
+ raise OSError(
+ "%r has an invalid shebang line: "
+ "%r is not a valid executable" % (
+ origcmd, sheb))
+ raise OSError(pwte2)
+
+ # close handles which only the child will use
+ win32file.CloseHandle(hStderrW)
+ win32file.CloseHandle(hStdoutW)
+ win32file.CloseHandle(hStdinR)
+
+ # set up everything
+ self.stdout = _pollingfile._PollableReadPipe(
+ self.hStdoutR,
+ lambda data: self.proto.childDataReceived(1, data),
+ self.outConnectionLost)
+
+ self.stderr = _pollingfile._PollableReadPipe(
+ self.hStderrR,
+ lambda data: self.proto.childDataReceived(2, data),
+ self.errConnectionLost)
+
+ self.stdin = _pollingfile._PollableWritePipe(
+ self.hStdinW, self.inConnectionLost)
+
+ for pipewatcher in self.stdout, self.stderr, self.stdin:
+ self._addPollableResource(pipewatcher)
+
+
+ # notify protocol
+ self.proto.makeConnection(self)
+
+ self._addPollableResource(_Reaper(self))
+
+
+ def signalProcess(self, signalID):
+ if self.pid is None:
+ raise error.ProcessExitedAlready()
+ if signalID in ("INT", "TERM", "KILL"):
+ win32process.TerminateProcess(self.hProcess, 1)
+
+
+ def _getReason(self, status):
+ if status == 0:
+ return error.ProcessDone(status)
+ return error.ProcessTerminated(status)
+
+
+ def write(self, data):
+ """Write data to the process' stdin."""
+ self.stdin.write(data)
+
+ def writeSequence(self, seq):
+ """Write data to the process' stdin."""
+ self.stdin.writeSequence(seq)
+
+ def closeChildFD(self, fd):
+ if fd == 0:
+ self.closeStdin()
+ elif fd == 1:
+ self.closeStdout()
+ elif fd == 2:
+ self.closeStderr()
+ else:
+ raise NotImplementedError("Only standard-IO file descriptors available on win32")
+
+ def closeStdin(self):
+ """Close the process' stdin.
+ """
+ self.stdin.close()
+
+ def closeStderr(self):
+ self.stderr.close()
+
+ def closeStdout(self):
+ self.stdout.close()
+
+ def loseConnection(self):
+ """Close the process' stdout, in and err."""
+ self.closeStdin()
+ self.closeStdout()
+ self.closeStderr()
+
+
+ def outConnectionLost(self):
+ self.proto.childConnectionLost(1)
+ self.connectionLostNotify()
+
+
+ def errConnectionLost(self):
+ self.proto.childConnectionLost(2)
+ self.connectionLostNotify()
+
+
+ def inConnectionLost(self):
+ self.proto.childConnectionLost(0)
+ self.connectionLostNotify()
+
+
+ def connectionLostNotify(self):
+ """
+ Will be called 3 times, by stdout/err threads and process handle.
+ """
+ self.closedNotifies += 1
+ self.maybeCallProcessEnded()
+
+
+ def maybeCallProcessEnded(self):
+ if self.closedNotifies == 3 and self.lostProcess:
+ win32file.CloseHandle(self.hProcess)
+ win32file.CloseHandle(self.hThread)
+ self.hProcess = None
+ self.hThread = None
+ BaseProcess.maybeCallProcessEnded(self)
+
+
+ # IConsumer
+ def registerProducer(self, producer, streaming):
+ self.stdin.registerProducer(producer, streaming)
+
+ def unregisterProducer(self):
+ self.stdin.unregisterProducer()
+
+ # IProducer
+ def pauseProducing(self):
+ self._pause()
+
+ def resumeProducing(self):
+ self._unpause()
+
+ def stopProducing(self):
+ self.loseConnection()
+
+
+ def __repr__(self):
+ """
+ Return a string representation of the process.
+ """
+ return "<%s pid=%s>" % (self.__class__.__name__, self.pid)
diff --git a/vendor/Twisted-10.0.0/twisted/internet/_javaserialport.py b/vendor/Twisted-10.0.0/twisted/internet/_javaserialport.py
new file mode 100644
index 0000000000..ee8469508d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/_javaserialport.py
@@ -0,0 +1,78 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Serial Port Protocol
+"""
+
+# system imports
+import os
+
+# dependent on pyserial ( http://pyserial.sf.net/ )
+# only tested w/ 1.18 (5 Dec 2002)
+import serial
+from serial import PARITY_NONE, PARITY_EVEN, PARITY_ODD
+from serial import STOPBITS_ONE, STOPBITS_TWO
+from serial import FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS
+from serialport import BaseSerialPort
+
+# twisted imports
+from twisted.internet import abstract, javareactor, main
+from twisted.python import log
+
+class SerialPort(BaseSerialPort, javareactor.JConnection):
+ """A select()able serial device, acting as a transport."""
+ connected = 1
+
+ def __init__(self, protocol, deviceNameOrPortNumber, reactor,
+ baudrate = 9600, bytesize = EIGHTBITS, parity = PARITY_NONE,
+ stopbits = STOPBITS_ONE, timeout = 3, xonxoff = 0, rtscts = 0):
+ # do NOT use timeout = 0 !!
+ self._serial = serial.Serial(deviceNameOrPortNumber, baudrate = baudrate, bytesize = bytesize, parity = parity, stopbits = stopbits, timeout = timeout, xonxoff = xonxoff, rtscts = rtscts)
+ javareactor.JConnection.__init__(self, self._serial.sPort, protocol, None)
+ self.flushInput()
+ self.flushOutput()
+
+ self.reactor = reactor
+ self.protocol = protocol
+ self.protocol.makeConnection(self)
+ wb = javareactor.WriteBlocker(self, reactor.q)
+ wb.start()
+ self.writeBlocker = wb
+ javareactor.ReadBlocker(self, reactor.q).start()
+
+ def writeSomeData(self, data):
+ try:
+ self._serial.write(data)
+ return len(data)
+ # should have something better here
+ except Exception, e:
+ return main.CONNECTION_LOST
+
+ def doRead(self):
+ readBytes = ''
+ try:
+ readBytes = self._serial.read(min(8192, self.inWaiting()))
+ except Exception, e:
+ return main.CONNECTION_LOST
+ if not readBytes:
+ return main.CONNECTION_LOST
+ self.protocol.dataReceived(readBytes)
+
+ def connectionLost(self, reason):
+ self._serial.close()
+ self.protocol.connectionLost(reason)
+ abstract.FileDescriptor.connectionLost(self, reason)
+
+ def getHost(self):
+ raise NotImplementedError
+
+ def getPeer(self):
+ raise NotImplementedError
+
+ def getTcpNoDelay(self):
+ raise NotImplementedError
+
+ def setTcpNoDelay(self, enabled):
+ raise NotImplementedError
diff --git a/vendor/Twisted-10.0.0/twisted/internet/_pollingfile.py b/vendor/Twisted-10.0.0/twisted/internet/_pollingfile.py
new file mode 100644
index 0000000000..161c6ca627
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/_pollingfile.py
@@ -0,0 +1,279 @@
+# -*- test-case-name: twisted.internet.test.test_pollingfile -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implements a simple polling interface for file descriptors that don't work with
+select() - this is pretty much only useful on Windows.
+"""
+
+from zope.interface import implements
+
+from twisted.internet.interfaces import IConsumer, IPushProducer
+
+
+MIN_TIMEOUT = 0.000000001
+MAX_TIMEOUT = 0.1
+
+
+
+class _PollableResource:
+ active = True
+
+ def activate(self):
+ self.active = True
+
+
+ def deactivate(self):
+ self.active = False
+
+
+
+class _PollingTimer:
+ # Everything is private here because it is really an implementation detail.
+
+ def __init__(self, reactor):
+ self.reactor = reactor
+ self._resources = []
+ self._pollTimer = None
+ self._currentTimeout = MAX_TIMEOUT
+ self._paused = False
+
+ def _addPollableResource(self, res):
+ self._resources.append(res)
+ self._checkPollingState()
+
+ def _checkPollingState(self):
+ for resource in self._resources:
+ if resource.active:
+ self._startPolling()
+ break
+ else:
+ self._stopPolling()
+
+ def _startPolling(self):
+ if self._pollTimer is None:
+ self._pollTimer = self._reschedule()
+
+ def _stopPolling(self):
+ if self._pollTimer is not None:
+ self._pollTimer.cancel()
+ self._pollTimer = None
+
+ def _pause(self):
+ self._paused = True
+
+ def _unpause(self):
+ self._paused = False
+ self._checkPollingState()
+
+ def _reschedule(self):
+ if not self._paused:
+ return self.reactor.callLater(self._currentTimeout, self._pollEvent)
+
+ def _pollEvent(self):
+ workUnits = 0.
+ anyActive = []
+ for resource in self._resources:
+ if resource.active:
+ workUnits += resource.checkWork()
+ # Check AFTER work has been done
+ if resource.active:
+ anyActive.append(resource)
+
+ newTimeout = self._currentTimeout
+ if workUnits:
+ newTimeout = self._currentTimeout / (workUnits + 1.)
+ if newTimeout < MIN_TIMEOUT:
+ newTimeout = MIN_TIMEOUT
+ else:
+ newTimeout = self._currentTimeout * 2.
+ if newTimeout > MAX_TIMEOUT:
+ newTimeout = MAX_TIMEOUT
+ self._currentTimeout = newTimeout
+ if anyActive:
+ self._pollTimer = self._reschedule()
+
+
+# If we ever (let's hope not) need the above functionality on UNIX, this could
+# be factored into a different module.
+
+import win32pipe
+import win32file
+import win32api
+import pywintypes
+
+class _PollableReadPipe(_PollableResource):
+
+ implements(IPushProducer)
+
+ def __init__(self, pipe, receivedCallback, lostCallback):
+ # security attributes for pipes
+ self.pipe = pipe
+ self.receivedCallback = receivedCallback
+ self.lostCallback = lostCallback
+
+ def checkWork(self):
+ finished = 0
+ fullDataRead = []
+
+ while 1:
+ try:
+ buffer, bytesToRead, result = win32pipe.PeekNamedPipe(self.pipe, 1)
+ # finished = (result == -1)
+ if not bytesToRead:
+ break
+ hr, data = win32file.ReadFile(self.pipe, bytesToRead, None)
+ fullDataRead.append(data)
+ except win32api.error:
+ finished = 1
+ break
+
+ dataBuf = ''.join(fullDataRead)
+ if dataBuf:
+ self.receivedCallback(dataBuf)
+ if finished:
+ self.cleanup()
+ return len(dataBuf)
+
+ def cleanup(self):
+ self.deactivate()
+ self.lostCallback()
+
+ def close(self):
+ try:
+ win32api.CloseHandle(self.pipe)
+ except pywintypes.error:
+ # You can't close std handles...?
+ pass
+
+ def stopProducing(self):
+ self.close()
+
+ def pauseProducing(self):
+ self.deactivate()
+
+ def resumeProducing(self):
+ self.activate()
+
+
+FULL_BUFFER_SIZE = 64 * 1024
+
+class _PollableWritePipe(_PollableResource):
+
+ implements(IConsumer)
+
+ def __init__(self, writePipe, lostCallback):
+ self.disconnecting = False
+ self.producer = None
+ self.producerPaused = 0
+ self.streamingProducer = 0
+ self.outQueue = []
+ self.writePipe = writePipe
+ self.lostCallback = lostCallback
+ try:
+ win32pipe.SetNamedPipeHandleState(writePipe,
+ win32pipe.PIPE_NOWAIT,
+ None,
+ None)
+ except pywintypes.error:
+ # Maybe it's an invalid handle. Who knows.
+ pass
+
+ def close(self):
+ self.disconnecting = True
+
+ def bufferFull(self):
+ if self.producer is not None:
+ self.producerPaused = 1
+ self.producer.pauseProducing()
+
+ def bufferEmpty(self):
+ if self.producer is not None and ((not self.streamingProducer) or
+ self.producerPaused):
+ self.producer.producerPaused = 0
+ self.producer.resumeProducing()
+ return True
+ return False
+
+ # almost-but-not-quite-exact copy-paste from abstract.FileDescriptor... ugh
+
+ def registerProducer(self, producer, streaming):
+ """Register to receive data from a producer.
+
+ This sets this selectable to be a consumer for a producer. When this
+ selectable runs out of data on a write() call, it will ask the producer
+ to resumeProducing(). A producer should implement the IProducer
+ interface.
+
+ FileDescriptor provides some infrastructure for producer methods.
+ """
+ if self.producer is not None:
+ raise RuntimeError(
+ "Cannot register producer %s, because producer %s was never "
+ "unregistered." % (producer, self.producer))
+ if not self.active:
+ producer.stopProducing()
+ else:
+ self.producer = producer
+ self.streamingProducer = streaming
+ if not streaming:
+ producer.resumeProducing()
+
+ def unregisterProducer(self):
+ """Stop consuming data from a producer, without disconnecting.
+ """
+ self.producer = None
+
+ def writeConnectionLost(self):
+ self.deactivate()
+ try:
+ win32api.CloseHandle(self.writePipe)
+ except pywintypes.error:
+ # OMG what
+ pass
+ self.lostCallback()
+
+ def writeSequence(self, seq):
+ self.outQueue.extend(seq)
+
+ def write(self, data):
+ if self.disconnecting:
+ return
+ self.outQueue.append(data)
+ if sum(map(len, self.outQueue)) > FULL_BUFFER_SIZE:
+ self.bufferFull()
+
+ def checkWork(self):
+ numBytesWritten = 0
+ if not self.outQueue:
+ if self.disconnecting:
+ self.writeConnectionLost()
+ return 0
+ try:
+ win32file.WriteFile(self.writePipe, '', None)
+ except pywintypes.error:
+ self.writeConnectionLost()
+ return numBytesWritten
+ while self.outQueue:
+ data = self.outQueue.pop(0)
+ errCode = 0
+ if isinstance(data, unicode):
+ raise TypeError("unicode not allowed")
+ try:
+ errCode, nBytesWritten = win32file.WriteFile(self.writePipe,
+ data, None)
+ except win32api.error:
+ self.writeConnectionLost()
+ break
+ else:
+ # assert not errCode, "wtf an error code???"
+ numBytesWritten += nBytesWritten
+ if len(data) > nBytesWritten:
+ self.outQueue.insert(0, data[nBytesWritten:])
+ break
+ else:
+ resumed = self.bufferEmpty()
+ if not resumed and self.disconnecting:
+ self.writeConnectionLost()
+ return numBytesWritten
diff --git a/vendor/Twisted-10.0.0/twisted/internet/_posixserialport.py b/vendor/Twisted-10.0.0/twisted/internet/_posixserialport.py
new file mode 100644
index 0000000000..c94ecee35c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/_posixserialport.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Serial Port Protocol
+"""
+
+# system imports
+import os, errno
+
+# dependent on pyserial ( http://pyserial.sf.net/ )
+# only tested w/ 1.18 (5 Dec 2002)
+import serial
+from serial import PARITY_NONE, PARITY_EVEN, PARITY_ODD
+from serial import STOPBITS_ONE, STOPBITS_TWO
+from serial import FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS
+
+from serialport import BaseSerialPort
+
+# twisted imports
+from twisted.internet import abstract, fdesc, main
+
+class SerialPort(BaseSerialPort, abstract.FileDescriptor):
+ """
+ A select()able serial device, acting as a transport.
+ """
+
+ connected = 1
+
+ def __init__(self, protocol, deviceNameOrPortNumber, reactor,
+ baudrate = 9600, bytesize = EIGHTBITS, parity = PARITY_NONE,
+ stopbits = STOPBITS_ONE, timeout = 0, xonxoff = 0, rtscts = 0):
+ abstract.FileDescriptor.__init__(self, reactor)
+ self._serial = serial.Serial(deviceNameOrPortNumber, baudrate = baudrate, bytesize = bytesize, parity = parity, stopbits = stopbits, timeout = timeout, xonxoff = xonxoff, rtscts = rtscts)
+ self.reactor = reactor
+ self.flushInput()
+ self.flushOutput()
+ self.protocol = protocol
+ self.protocol.makeConnection(self)
+ self.startReading()
+
+ def fileno(self):
+ return self._serial.fd
+
+ def writeSomeData(self, data):
+ """
+ Write some data to the serial device.
+ """
+ return fdesc.writeToFD(self.fileno(), data)
+
+ def doRead(self):
+ """
+ Some data's readable from serial device.
+ """
+ return fdesc.readFromFD(self.fileno(), self.protocol.dataReceived)
+
+ def connectionLost(self, reason):
+ abstract.FileDescriptor.connectionLost(self, reason)
+ self._serial.close()
diff --git a/vendor/Twisted-10.0.0/twisted/internet/_posixstdio.py b/vendor/Twisted-10.0.0/twisted/internet/_posixstdio.py
new file mode 100644
index 0000000000..9b094ccd54
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/_posixstdio.py
@@ -0,0 +1,173 @@
+# -*- test-case-name: twisted.test.test_stdio -*-
+
+"""Standard input/out/err support.
+
+Future Plans::
+
+ support for stderr, perhaps
+ Rewrite to use the reactor instead of an ad-hoc mechanism for connecting
+ protocols to transport.
+
+Maintainer: James Y Knight
+"""
+
+import warnings
+from zope.interface import implements
+
+from twisted.internet import process, error, interfaces
+from twisted.python import log, failure
+
+
+class PipeAddress(object):
+ implements(interfaces.IAddress)
+
+
+class StandardIO(object):
+ implements(interfaces.ITransport, interfaces.IProducer, interfaces.IConsumer, interfaces.IHalfCloseableDescriptor)
+ _reader = None
+ _writer = None
+ disconnected = False
+ disconnecting = False
+
+ def __init__(self, proto, stdin=0, stdout=1):
+ from twisted.internet import reactor
+ self.protocol = proto
+
+ self._reader=process.ProcessReader(reactor, self, 'read', stdin)
+ self._reader.startReading()
+ self._writer=process.ProcessWriter(reactor, self, 'write', stdout)
+ self._writer.startReading()
+ self.protocol.makeConnection(self)
+
+ # ITransport
+
+ # XXX Actually, see #3597.
+ def loseWriteConnection(self):
+ if self._writer is not None:
+ self._writer.loseConnection()
+
+ def write(self, data):
+ if self._writer is not None:
+ self._writer.write(data)
+
+ def writeSequence(self, data):
+ if self._writer is not None:
+ self._writer.writeSequence(data)
+
+ def loseConnection(self):
+ self.disconnecting = True
+
+ if self._writer is not None:
+ self._writer.loseConnection()
+ if self._reader is not None:
+ # Don't loseConnection, because we don't want to SIGPIPE it.
+ self._reader.stopReading()
+
+ def getPeer(self):
+ return PipeAddress()
+
+ def getHost(self):
+ return PipeAddress()
+
+
+ # Callbacks from process.ProcessReader/ProcessWriter
+ def childDataReceived(self, fd, data):
+ self.protocol.dataReceived(data)
+
+ def childConnectionLost(self, fd, reason):
+ if self.disconnected:
+ return
+
+ if reason.value.__class__ == error.ConnectionDone:
+ # Normal close
+ if fd == 'read':
+ self._readConnectionLost(reason)
+ else:
+ self._writeConnectionLost(reason)
+ else:
+ self.connectionLost(reason)
+
+ def connectionLost(self, reason):
+ self.disconnected = True
+
+ # Make sure to cleanup the other half
+ _reader = self._reader
+ _writer = self._writer
+ protocol = self.protocol
+ self._reader = self._writer = None
+ self.protocol = None
+
+ if _writer is not None and not _writer.disconnected:
+ _writer.connectionLost(reason)
+
+ if _reader is not None and not _reader.disconnected:
+ _reader.connectionLost(reason)
+
+ try:
+ protocol.connectionLost(reason)
+ except:
+ log.err()
+
+ def _writeConnectionLost(self, reason):
+ self._writer=None
+ if self.disconnecting:
+ self.connectionLost(reason)
+ return
+
+ p = interfaces.IHalfCloseableProtocol(self.protocol, None)
+ if p:
+ try:
+ p.writeConnectionLost()
+ except:
+ log.err()
+ self.connectionLost(failure.Failure())
+
+ def _readConnectionLost(self, reason):
+ self._reader=None
+ p = interfaces.IHalfCloseableProtocol(self.protocol, None)
+ if p:
+ try:
+ p.readConnectionLost()
+ except:
+ log.err()
+ self.connectionLost(failure.Failure())
+ else:
+ self.connectionLost(reason)
+
+ # IConsumer
+ def registerProducer(self, producer, streaming):
+ if self._writer is None:
+ producer.stopProducing()
+ else:
+ self._writer.registerProducer(producer, streaming)
+
+ def unregisterProducer(self):
+ if self._writer is not None:
+ self._writer.unregisterProducer()
+
+ # IProducer
+ def stopProducing(self):
+ self.loseConnection()
+
+ def pauseProducing(self):
+ if self._reader is not None:
+ self._reader.pauseProducing()
+
+ def resumeProducing(self):
+ if self._reader is not None:
+ self._reader.resumeProducing()
+
+ # Stupid compatibility:
+ def closeStdin(self):
+ """Compatibility only, don't use. Same as loseWriteConnection."""
+ warnings.warn("This function is deprecated, use loseWriteConnection instead.",
+ category=DeprecationWarning, stacklevel=2)
+ self.loseWriteConnection()
+
+ def stopReading(self):
+ """Compatibility only, don't use. Call pauseProducing."""
+ self.pauseProducing()
+
+ def startReading(self):
+ """Compatibility only, don't use. Call resumeProducing."""
+ self.resumeProducing()
diff --git a/vendor/Twisted-10.0.0/twisted/internet/_sslverify.py b/vendor/Twisted-10.0.0/twisted/internet/_sslverify.py
new file mode 100644
index 0000000000..93fdec255c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/_sslverify.py
@@ -0,0 +1,748 @@
+# -*- test-case-name: twisted.test.test_sslverify -*-
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+# Copyright (c) 2005-2008 Twisted Matrix Laboratories.
+
+import itertools
+from OpenSSL import SSL, crypto
+
+from twisted.python import reflect, util
+from twisted.python.hashlib import md5
+from twisted.internet.defer import Deferred
+from twisted.internet.error import VerifyError, CertificateError
+
+# Private - shared between all OpenSSLCertificateOptions, counts up to provide
+# a unique session id for each context
+_sessionCounter = itertools.count().next
+
+_x509names = {
+ 'CN': 'commonName',
+ 'commonName': 'commonName',
+
+ 'O': 'organizationName',
+ 'organizationName': 'organizationName',
+
+ 'OU': 'organizationalUnitName',
+ 'organizationalUnitName': 'organizationalUnitName',
+
+ 'L': 'localityName',
+ 'localityName': 'localityName',
+
+ 'ST': 'stateOrProvinceName',
+ 'stateOrProvinceName': 'stateOrProvinceName',
+
+ 'C': 'countryName',
+ 'countryName': 'countryName',
+
+ 'emailAddress': 'emailAddress'}
+
+
+class DistinguishedName(dict):
+ """
+ Identify and describe an entity.
+
+ Distinguished names are used to provide a minimal amount of identifying
+ information about a certificate issuer or subject. They are commonly
+ created with one or more of the following fields::
+
+ commonName (CN)
+ organizationName (O)
+ organizationalUnitName (OU)
+ localityName (L)
+ stateOrProvinceName (ST)
+ countryName (C)
+ emailAddress
+ """
+ __slots__ = ()
+
+ def __init__(self, **kw):
+ for k, v in kw.iteritems():
+ setattr(self, k, v)
+
+
+ def _copyFrom(self, x509name):
+ d = {}
+ for name in _x509names:
+ value = getattr(x509name, name, None)
+ if value is not None:
+ setattr(self, name, value)
+
+
+ def _copyInto(self, x509name):
+ for k, v in self.iteritems():
+ setattr(x509name, k, v)
+
+
+ def __repr__(self):
+ return '<DN %s>' % (dict.__repr__(self)[1:-1])
+
+
+ def __getattr__(self, attr):
+ try:
+ return self[_x509names[attr]]
+ except KeyError:
+ raise AttributeError(attr)
+
+
+ def __setattr__(self, attr, value):
+ assert type(attr) is str
+ if not attr in _x509names:
+ raise AttributeError("%s is not a valid OpenSSL X509 name field" % (attr,))
+ realAttr = _x509names[attr]
+ value = value.encode('ascii')
+ assert type(value) is str
+ self[realAttr] = value
+
+
+ def inspect(self):
+ """
+ Return a multi-line, human-readable representation of this DN.
+ """
+ l = []
+ lablen = 0
+ def uniqueValues(mapping):
+ return dict.fromkeys(mapping.itervalues()).keys()
+ for k in uniqueValues(_x509names):
+ label = util.nameToLabel(k)
+ lablen = max(len(label), lablen)
+ v = getattr(self, k, None)
+ if v is not None:
+ l.append((label, v))
+ lablen += 2
+ for n, (label, attr) in enumerate(l):
+ l[n] = (label.rjust(lablen)+': '+ attr)
+ return '\n'.join(l)
+
+DN = DistinguishedName
+
+
+class CertBase:
+ def __init__(self, original):
+ self.original = original
+
+ def _copyName(self, suffix):
+ dn = DistinguishedName()
+ dn._copyFrom(getattr(self.original, 'get_'+suffix)())
+ return dn
+
+ def getSubject(self):
+ """
+ Retrieve the subject of this certificate.
+
+ @rtype: L{DistinguishedName}
+ @return: A copy of the subject of this certificate.
+ """
+ return self._copyName('subject')
+
+
+
+def _handleattrhelper(Class, transport, methodName):
+ """
+ (private) Helper for L{Certificate.peerFromTransport} and
+ L{Certificate.hostFromTransport} which checks for incompatible handle types
+ and null certificates and raises the appropriate exception or returns the
+ appropriate certificate object.
+ """
+ method = getattr(transport.getHandle(),
+ "get_%s_certificate" % (methodName,), None)
+ if method is None:
+ raise CertificateError(
+ "non-TLS transport %r did not have %s certificate" % (transport, methodName))
+ cert = method()
+ if cert is None:
+ raise CertificateError(
+ "TLS transport %r did not have %s certificate" % (transport, methodName))
+ return Class(cert)
+
+
+class Certificate(CertBase):
+ """
+ An x509 certificate.
+ """
+ def __repr__(self):
+ return '<%s Subject=%s Issuer=%s>' % (self.__class__.__name__,
+ self.getSubject().commonName,
+ self.getIssuer().commonName)
+
+ def __eq__(self, other):
+ if isinstance(other, Certificate):
+ return self.dump() == other.dump()
+ return False
+
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+
+ def load(Class, requestData, format=crypto.FILETYPE_ASN1, args=()):
+ """
+ Load a certificate from an ASN.1- or PEM-format string.
+
+ @rtype: C{Class}
+ """
+ return Class(crypto.load_certificate(format, requestData), *args)
+ load = classmethod(load)
+ _load = load
+
+
+ def dumpPEM(self):
+ """
+ Dump this certificate to a PEM-format data string.
+
+ @rtype: C{str}
+ """
+ return self.dump(crypto.FILETYPE_PEM)
+
+
+ def loadPEM(Class, data):
+ """
+ Load a certificate from a PEM-format data string.
+
+ @rtype: C{Class}
+ """
+ return Class.load(data, crypto.FILETYPE_PEM)
+ loadPEM = classmethod(loadPEM)
+
+
+ def peerFromTransport(Class, transport):
+ """
+ Get the certificate for the remote end of the given transport.
+
+ @type: L{ISystemHandle}
+ @rtype: C{Class}
+
+ @raise: L{CertificateError}, if the given transport does not have a peer
+ certificate.
+ """
+ return _handleattrhelper(Class, transport, 'peer')
+ peerFromTransport = classmethod(peerFromTransport)
+
+
+ def hostFromTransport(Class, transport):
+ """
+ Get the certificate for the local end of the given transport.
+
+ @param transport: an L{ISystemHandle} provider; the transport we will
+
+ @rtype: C{Class}
+
+ @raise: L{CertificateError}, if the given transport does not have a host
+ certificate.
+ """
+ return _handleattrhelper(Class, transport, 'host')
+ hostFromTransport = classmethod(hostFromTransport)
+
+
+ def getPublicKey(self):
+ """
+ Get the public key for this certificate.
+
+ @rtype: L{PublicKey}
+ """
+ return PublicKey(self.original.get_pubkey())
+
+
+ def dump(self, format=crypto.FILETYPE_ASN1):
+ return crypto.dump_certificate(format, self.original)
+
+
+ def serialNumber(self):
+ """
+ Retrieve the serial number of this certificate.
+
+ @rtype: C{int}
+ """
+ return self.original.get_serial_number()
+
+
+ def digest(self, method='md5'):
+ """
+ Return a digest hash of this certificate using the specified hash
+ algorithm.
+
+ @param method: One of C{'md5'} or C{'sha'}.
+ @rtype: C{str}
+ """
+ return self.original.digest(method)
+
+
+ def _inspect(self):
+ return '\n'.join(['Certificate For Subject:',
+ self.getSubject().inspect(),
+ '\nIssuer:',
+ self.getIssuer().inspect(),
+ '\nSerial Number: %d' % self.serialNumber(),
+ 'Digest: %s' % self.digest()])
+
+
+ def inspect(self):
+ """
+ Return a multi-line, human-readable representation of this
+ Certificate, including information about the subject, issuer, and
+ public key.
+ """
+ return '\n'.join((self._inspect(), self.getPublicKey().inspect()))
+
+
+ def getIssuer(self):
+ """
+ Retrieve the issuer of this certificate.
+
+ @rtype: L{DistinguishedName}
+ @return: A copy of the issuer of this certificate.
+ """
+ return self._copyName('issuer')
+
+
+ def options(self, *authorities):
+ raise NotImplementedError('Possible, but doubtful we need this yet')
+
+
+
+class CertificateRequest(CertBase):
+ """
+ An x509 certificate request.
+
+ Certificate requests are given to certificate authorities to be signed and
+ returned resulting in an actual certificate.
+ """
+ def load(Class, requestData, requestFormat=crypto.FILETYPE_ASN1):
+ req = crypto.load_certificate_request(requestFormat, requestData)
+ dn = DistinguishedName()
+ dn._copyFrom(req.get_subject())
+ if not req.verify(req.get_pubkey()):
+ raise VerifyError("Can't verify that request for %r is self-signed." % (dn,))
+ return Class(req)
+ load = classmethod(load)
+
+
+ def dump(self, format=crypto.FILETYPE_ASN1):
+ return crypto.dump_certificate_request(format, self.original)
+
+
+
+class PrivateCertificate(Certificate):
+ """
+ An x509 certificate and private key.
+ """
+ def __repr__(self):
+ return Certificate.__repr__(self) + ' with ' + repr(self.privateKey)
+
+
+ def _setPrivateKey(self, privateKey):
+ if not privateKey.matches(self.getPublicKey()):
+ raise VerifyError(
+ "Certificate public and private keys do not match.")
+ self.privateKey = privateKey
+ return self
+
+
+ def newCertificate(self, newCertData, format=crypto.FILETYPE_ASN1):
+ """
+ Create a new L{PrivateCertificate} from the given certificate data and
+ this instance's private key.
+ """
+ return self.load(newCertData, self.privateKey, format)
+
+
+ def load(Class, data, privateKey, format=crypto.FILETYPE_ASN1):
+ return Class._load(data, format)._setPrivateKey(privateKey)
+ load = classmethod(load)
+
+
+ def inspect(self):
+ return '\n'.join([Certificate._inspect(self),
+ self.privateKey.inspect()])
+
+
+ def dumpPEM(self):
+ """
+ Dump both public and private parts of a private certificate to
+ PEM-format data.
+ """
+ return self.dump(crypto.FILETYPE_PEM) + self.privateKey.dump(crypto.FILETYPE_PEM)
+
+
+ def loadPEM(Class, data):
+ """
+ Load both private and public parts of a private certificate from a
+ chunk of PEM-format data.
+ """
+ return Class.load(data, KeyPair.load(data, crypto.FILETYPE_PEM),
+ crypto.FILETYPE_PEM)
+ loadPEM = classmethod(loadPEM)
+
+
+ def fromCertificateAndKeyPair(Class, certificateInstance, privateKey):
+ privcert = Class(certificateInstance.original)
+ return privcert._setPrivateKey(privateKey)
+ fromCertificateAndKeyPair = classmethod(fromCertificateAndKeyPair)
+
+
+ def options(self, *authorities):
+ options = dict(privateKey=self.privateKey.original,
+ certificate=self.original)
+ if authorities:
+ options.update(dict(verify=True,
+ requireCertificate=True,
+ caCerts=[auth.original for auth in authorities]))
+ return OpenSSLCertificateOptions(**options)
+
+
+ def certificateRequest(self, format=crypto.FILETYPE_ASN1,
+ digestAlgorithm='md5'):
+ return self.privateKey.certificateRequest(
+ self.getSubject(),
+ format,
+ digestAlgorithm)
+
+
+ def signCertificateRequest(self,
+ requestData,
+ verifyDNCallback,
+ serialNumber,
+ requestFormat=crypto.FILETYPE_ASN1,
+ certificateFormat=crypto.FILETYPE_ASN1):
+ issuer = self.getSubject()
+ return self.privateKey.signCertificateRequest(
+ issuer,
+ requestData,
+ verifyDNCallback,
+ serialNumber,
+ requestFormat,
+ certificateFormat)
+
+
+ def signRequestObject(self, certificateRequest, serialNumber,
+ secondsToExpiry=60 * 60 * 24 * 365, # One year
+ digestAlgorithm='md5'):
+ return self.privateKey.signRequestObject(self.getSubject(),
+ certificateRequest,
+ serialNumber,
+ secondsToExpiry,
+ digestAlgorithm)
+
+
+class PublicKey:
+ def __init__(self, osslpkey):
+ self.original = osslpkey
+ req1 = crypto.X509Req()
+ req1.set_pubkey(osslpkey)
+ self._emptyReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, req1)
+
+
+ def matches(self, otherKey):
+ return self._emptyReq == otherKey._emptyReq
+
+
+ # XXX This could be a useful method, but sometimes it triggers a segfault,
+ # so we'll steer clear for now.
+# def verifyCertificate(self, certificate):
+# """
+# returns None, or raises a VerifyError exception if the certificate
+# could not be verified.
+# """
+# if not certificate.original.verify(self.original):
+# raise VerifyError("We didn't sign that certificate.")
+
+ def __repr__(self):
+ return '<%s %s>' % (self.__class__.__name__, self.keyHash())
+
+
+ def keyHash(self):
+ """
+ MD5 hex digest of signature on an empty certificate request with this
+ key.
+ """
+ return md5(self._emptyReq).hexdigest()
+
+
+ def inspect(self):
+ return 'Public Key with Hash: %s' % (self.keyHash(),)
+
+
+
+class KeyPair(PublicKey):
+
+ def load(Class, data, format=crypto.FILETYPE_ASN1):
+ return Class(crypto.load_privatekey(format, data))
+ load = classmethod(load)
+
+
+ def dump(self, format=crypto.FILETYPE_ASN1):
+ return crypto.dump_privatekey(format, self.original)
+
+
+ def __getstate__(self):
+ return self.dump()
+
+
+ def __setstate__(self, state):
+ self.__init__(crypto.load_privatekey(crypto.FILETYPE_ASN1, state))
+
+
+ def inspect(self):
+ t = self.original.type()
+ if t == crypto.TYPE_RSA:
+ ts = 'RSA'
+ elif t == crypto.TYPE_DSA:
+ ts = 'DSA'
+ else:
+ ts = '(Unknown Type!)'
+ L = (self.original.bits(), ts, self.keyHash())
+ return '%s-bit %s Key Pair with Hash: %s' % L
+
+
+ def generate(Class, kind=crypto.TYPE_RSA, size=1024):
+ pkey = crypto.PKey()
+ pkey.generate_key(kind, size)
+ return Class(pkey)
+
+
+ def newCertificate(self, newCertData, format=crypto.FILETYPE_ASN1):
+ return PrivateCertificate.load(newCertData, self, format)
+ generate = classmethod(generate)
+
+
+ def requestObject(self, distinguishedName, digestAlgorithm='md5'):
+ req = crypto.X509Req()
+ req.set_pubkey(self.original)
+ distinguishedName._copyInto(req.get_subject())
+ req.sign(self.original, digestAlgorithm)
+ return CertificateRequest(req)
+
+
+ def certificateRequest(self, distinguishedName,
+ format=crypto.FILETYPE_ASN1,
+ digestAlgorithm='md5'):
+ """Create a certificate request signed with this key.
+
+ @return: a string, formatted according to the 'format' argument.
+ """
+ return self.requestObject(distinguishedName, digestAlgorithm).dump(format)
+
+
+ def signCertificateRequest(self,
+ issuerDistinguishedName,
+ requestData,
+ verifyDNCallback,
+ serialNumber,
+ requestFormat=crypto.FILETYPE_ASN1,
+ certificateFormat=crypto.FILETYPE_ASN1,
+ secondsToExpiry=60 * 60 * 24 * 365, # One year
+ digestAlgorithm='md5'):
+ """
+ Given a blob of certificate request data and a certificate authority's
+ DistinguishedName, return a blob of signed certificate data.
+
+ If verifyDNCallback returns a Deferred, I will return a Deferred which
+ fires the data when that Deferred has completed.
+ """
+ hlreq = CertificateRequest.load(requestData, requestFormat)
+
+ dn = hlreq.getSubject()
+ vval = verifyDNCallback(dn)
+
+ def verified(value):
+ if not value:
+ raise VerifyError("DN callback %r rejected request DN %r" % (verifyDNCallback, dn))
+ return self.signRequestObject(issuerDistinguishedName, hlreq,
+ serialNumber, secondsToExpiry, digestAlgorithm).dump(certificateFormat)
+
+ if isinstance(vval, Deferred):
+ return vval.addCallback(verified)
+ else:
+ return verified(vval)
+
+
+ def signRequestObject(self,
+ issuerDistinguishedName,
+ requestObject,
+ serialNumber,
+ secondsToExpiry=60 * 60 * 24 * 365, # One year
+ digestAlgorithm='md5'):
+ """
+ Sign a CertificateRequest instance, returning a Certificate instance.
+ """
+ req = requestObject.original
+ dn = requestObject.getSubject()
+ cert = crypto.X509()
+ issuerDistinguishedName._copyInto(cert.get_issuer())
+ cert.set_subject(req.get_subject())
+ cert.set_pubkey(req.get_pubkey())
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(secondsToExpiry)
+ cert.set_serial_number(serialNumber)
+ cert.sign(self.original, digestAlgorithm)
+ return Certificate(cert)
+
+
+ def selfSignedCert(self, serialNumber, **kw):
+ dn = DN(**kw)
+ return PrivateCertificate.fromCertificateAndKeyPair(
+ self.signRequestObject(dn, self.requestObject(dn), serialNumber),
+ self)
+
+
+
+class OpenSSLCertificateOptions(object):
+ """
+ A factory for SSL context objects for both SSL servers and clients.
+ """
+
+ _context = None
+ # Older versions of PyOpenSSL didn't provide OP_ALL. Fudge it here, just in case.
+ _OP_ALL = getattr(SSL, 'OP_ALL', 0x0000FFFF)
+ # OP_NO_TICKET is not (yet) exposed by PyOpenSSL
+ _OP_NO_TICKET = 0x00004000
+
+ method = SSL.TLSv1_METHOD
+
+ def __init__(self,
+ privateKey=None,
+ certificate=None,
+ method=None,
+ verify=False,
+ caCerts=None,
+ verifyDepth=9,
+ requireCertificate=True,
+ verifyOnce=True,
+ enableSingleUseKeys=True,
+ enableSessions=True,
+ fixBrokenPeers=False,
+ enableSessionTickets=False):
+ """
+ Create an OpenSSL context SSL connection context factory.
+
+ @param privateKey: A PKey object holding the private key.
+
+ @param certificate: An X509 object holding the certificate.
+
+ @param method: The SSL protocol to use, one of SSLv23_METHOD,
+ SSLv2_METHOD, SSLv3_METHOD, TLSv1_METHOD. Defaults to TLSv1_METHOD.
+
+ @param verify: If True, verify certificates received from the peer and
+ fail the handshake if verification fails. Otherwise, allow anonymous
+ sessions and sessions with certificates which fail validation. By
+ default this is False.
+
+ @param caCerts: List of certificate authority certificates to
+ send to the client when requesting a certificate. Only used if verify
+ is True, and if verify is True, either this must be specified or
+ caCertsFile must be given. Since verify is False by default,
+ this is None by default.
+
+ @param verifyDepth: Depth in certificate chain down to which to verify.
+ If unspecified, use the underlying default (9).
+
+ @param requireCertificate: If True, do not allow anonymous sessions.
+
+ @param verifyOnce: If True, do not re-verify the certificate
+ on session resumption.
+
+ @param enableSingleUseKeys: If True, generate a new key whenever
+ ephemeral DH parameters are used to prevent small subgroup attacks.
+
+ @param enableSessions: If True, set a session ID on each context. This
+ allows a shortened handshake to be used when a known client reconnects.
+
+ @param fixBrokenPeers: If True, enable various non-spec protocol fixes
+ for broken SSL implementations. This should be entirely safe,
+ according to the OpenSSL documentation, but YMMV. This option is now
+ off by default, because it causes problems with connections between
+ peers using OpenSSL 0.9.8a.
+
+ @param enableSessionTickets: If True, enable session ticket extension
+ for session resumption per RFC 5077. Note there is no support for
+ controlling session tickets. This option is off by default, as some
+ server implementations don't correctly process incoming empty session
+ ticket extensions in the hello.
+ """
+
+ assert (privateKey is None) == (certificate is None), "Specify neither or both of privateKey and certificate"
+ self.privateKey = privateKey
+ self.certificate = certificate
+ if method is not None:
+ self.method = method
+
+ self.verify = verify
+ assert ((verify and caCerts) or
+ (not verify)), "Specify client CA certificate information if and only if enabling certificate verification"
+
+ self.caCerts = caCerts
+ self.verifyDepth = verifyDepth
+ self.requireCertificate = requireCertificate
+ self.verifyOnce = verifyOnce
+ self.enableSingleUseKeys = enableSingleUseKeys
+ self.enableSessions = enableSessions
+ self.fixBrokenPeers = fixBrokenPeers
+ self.enableSessionTickets = enableSessionTickets
+
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ try:
+ del d['_context']
+ except KeyError:
+ pass
+ return d
+
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+
+
+ def getContext(self):
+ """Return a SSL.Context object.
+ """
+ if self._context is None:
+ self._context = self._makeContext()
+ return self._context
+
+
+ def _makeContext(self):
+ ctx = SSL.Context(self.method)
+
+ if self.certificate is not None and self.privateKey is not None:
+ ctx.use_certificate(self.certificate)
+ ctx.use_privatekey(self.privateKey)
+ # Sanity check
+ ctx.check_privatekey()
+
+ verifyFlags = SSL.VERIFY_NONE
+ if self.verify:
+ verifyFlags = SSL.VERIFY_PEER
+ if self.requireCertificate:
+ verifyFlags |= SSL.VERIFY_FAIL_IF_NO_PEER_CERT
+ if self.verifyOnce:
+ verifyFlags |= SSL.VERIFY_CLIENT_ONCE
+ if self.caCerts:
+ store = ctx.get_cert_store()
+ for cert in self.caCerts:
+ store.add_cert(cert)
+
+ # It'd be nice if pyOpenSSL let us pass None here for this behavior (as
+ # the underlying OpenSSL API call allows NULL to be passed). It
+ # doesn't, so we'll supply a function which does the same thing.
+ def _verifyCallback(conn, cert, errno, depth, preverify_ok):
+ return preverify_ok
+ ctx.set_verify(verifyFlags, _verifyCallback)
+
+ if self.verifyDepth is not None:
+ ctx.set_verify_depth(self.verifyDepth)
+
+ if self.enableSingleUseKeys:
+ ctx.set_options(SSL.OP_SINGLE_DH_USE)
+
+ if self.fixBrokenPeers:
+ ctx.set_options(self._OP_ALL)
+
+ if self.enableSessions:
+ sessionName = md5("%s-%d" % (reflect.qual(self.__class__), _sessionCounter())).hexdigest()
+ ctx.set_session_id(sessionName)
+
+ if not self.enableSessionTickets:
+ ctx.set_options(self._OP_NO_TICKET)
+
+ return ctx
diff --git a/vendor/Twisted-10.0.0/twisted/internet/_threadedselect.py b/vendor/Twisted-10.0.0/twisted/internet/_threadedselect.py
new file mode 100644
index 0000000000..390c50933a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/_threadedselect.py
@@ -0,0 +1,362 @@
+# -*- test-case-name: twisted.test.test_internet -*-
+# $Id: default.py,v 1.90 2004/01/06 22:35:22 warner Exp $
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from __future__ import generators
+
+"""
+Threaded select reactor
+
+Maintainer: Bob Ippolito
+
+
+The threadedselectreactor is a specialized reactor for integrating with
+arbitrary foreign event loop, such as those you find in GUI toolkits.
+
+There are three things you'll need to do to use this reactor.
+
+Install the reactor at the beginning of your program, before importing
+the rest of Twisted::
+
+ | from twisted.internet import _threadedselect
+ | _threadedselect.install()
+
+Interleave this reactor with your foreign event loop, at some point after
+your event loop is initialized::
+
+ | from twisted.internet import reactor
+ | reactor.interleave(foreignEventLoopWakerFunction)
+ | self.addSystemEventTrigger('after', 'shutdown', foreignEventLoopStop)
+
+Instead of shutting down the foreign event loop directly, shut down the
+reactor::
+
+ | from twisted.internet import reactor
+ | reactor.stop()
+
+In order for Twisted to do its work in the main thread (the thread that
+interleave is called from), a waker function is necessary. The waker function
+will be called from a "background" thread with one argument: func.
+The waker function's purpose is to call func() from the main thread.
+Many GUI toolkits ship with appropriate waker functions.
+Some examples of this are wxPython's wx.callAfter (may be wxCallAfter in
+older versions of wxPython) or PyObjC's PyObjCTools.AppHelper.callAfter.
+These would be used in place of "foreignEventLoopWakerFunction" in the above
+example.
+
+The other integration point at which the foreign event loop and this reactor
+must integrate is shutdown. In order to ensure clean shutdown of Twisted,
+you must allow for Twisted to come to a complete stop before quitting the
+application. Typically, you will do this by setting up an after shutdown
+trigger to stop your foreign event loop, and call reactor.stop() where you
+would normally have initiated the shutdown procedure for the foreign event
+loop. Shutdown functions that could be used in place of
+"foreignEventloopStop" would be the ExitMainLoop method of the wxApp instance
+with wxPython, or the PyObjCTools.AppHelper.stopEventLoop function.
+"""
+
+from threading import Thread
+from Queue import Queue, Empty
+from time import sleep
+import sys
+
+from zope.interface import implements
+
+from twisted.internet.interfaces import IReactorFDSet
+from twisted.internet import error
+from twisted.internet import posixbase
+from twisted.python import log, failure, threadable
+from twisted.persisted import styles
+from twisted.python.runtime import platformType
+
+import select
+from errno import EINTR, EBADF
+
+from twisted.internet.selectreactor import _select
+
+# Exceptions that doSelect might return frequently
+_NO_FILENO = error.ConnectionFdescWentAway('Handler has no fileno method')
+_NO_FILEDESC = error.ConnectionFdescWentAway('Filedescriptor went away')
+
+def dictRemove(dct, value):
+ try:
+ del dct[value]
+ except KeyError:
+ pass
+
+def raiseException(e):
+ raise e
+
+class ThreadedSelectReactor(posixbase.PosixReactorBase):
+ """A threaded select() based reactor - runs on all POSIX platforms and on
+ Win32.
+ """
+ implements(IReactorFDSet)
+
+ def __init__(self):
+ threadable.init(1)
+ self.reads = {}
+ self.writes = {}
+ self.toThreadQueue = Queue()
+ self.toMainThread = Queue()
+ self.workerThread = None
+ self.mainWaker = None
+ posixbase.PosixReactorBase.__init__(self)
+ self.addSystemEventTrigger('after', 'shutdown', self._mainLoopShutdown)
+
+ def wakeUp(self):
+ # we want to wake up from any thread
+ self.waker.wakeUp()
+
+ def callLater(self, *args, **kw):
+ tple = posixbase.PosixReactorBase.callLater(self, *args, **kw)
+ self.wakeUp()
+ return tple
+
+ def _sendToMain(self, msg, *args):
+ #print >>sys.stderr, 'sendToMain', msg, args
+ self.toMainThread.put((msg, args))
+ if self.mainWaker is not None:
+ self.mainWaker()
+
+ def _sendToThread(self, fn, *args):
+ #print >>sys.stderr, 'sendToThread', fn, args
+ self.toThreadQueue.put((fn, args))
+
+ def _preenDescriptorsInThread(self):
+ log.msg("Malformed file descriptor found. Preening lists.")
+ readers = self.reads.keys()
+ writers = self.writes.keys()
+ self.reads.clear()
+ self.writes.clear()
+ for selDict, selList in ((self.reads, readers), (self.writes, writers)):
+ for selectable in selList:
+ try:
+ select.select([selectable], [selectable], [selectable], 0)
+ except:
+ log.msg("bad descriptor %s" % selectable)
+ else:
+ selDict[selectable] = 1
+
+ def _workerInThread(self):
+ try:
+ while 1:
+ fn, args = self.toThreadQueue.get()
+ #print >>sys.stderr, "worker got", fn, args
+ fn(*args)
+ except SystemExit:
+ pass # exception indicates this thread should exit
+ except:
+ f = failure.Failure()
+ self._sendToMain('Failure', f)
+ #print >>sys.stderr, "worker finished"
+
+ def _doSelectInThread(self, timeout):
+ """Run one iteration of the I/O monitor loop.
+
+ This will run all selectables who had input or output readiness
+ waiting for them.
+ """
+ reads = self.reads
+ writes = self.writes
+ while 1:
+ try:
+ r, w, ignored = _select(reads.keys(),
+ writes.keys(),
+ [], timeout)
+ break
+ except ValueError, ve:
+ # Possibly a file descriptor has gone negative?
+ log.err()
+ self._preenDescriptorsInThread()
+ except TypeError, te:
+ # Something *totally* invalid (object w/o fileno, non-integral
+ # result) was passed
+ log.err()
+ self._preenDescriptorsInThread()
+ except (select.error, IOError), se:
+ # select(2) encountered an error
+ if se.args[0] in (0, 2):
+ # windows does this if it got an empty list
+ if (not reads) and (not writes):
+ return
+ else:
+ raise
+ elif se.args[0] == EINTR:
+ return
+ elif se.args[0] == EBADF:
+ self._preenDescriptorsInThread()
+ else:
+ # OK, I really don't know what's going on. Blow up.
+ raise
+ self._sendToMain('Notify', r, w)
+
+ def _process_Notify(self, r, w):
+ #print >>sys.stderr, "_process_Notify"
+ reads = self.reads
+ writes = self.writes
+
+ _drdw = self._doReadOrWrite
+ _logrun = log.callWithLogger
+ for selectables, method, dct in ((r, "doRead", reads), (w, "doWrite", writes)):
+ for selectable in selectables:
+ # if this was disconnected in another thread, kill it.
+ if selectable not in dct:
+ continue
+ # This for pausing input when we're not ready for more.
+ _logrun(selectable, _drdw, selectable, method, dct)
+ #print >>sys.stderr, "done _process_Notify"
+
+ def _process_Failure(self, f):
+ f.raiseException()
+
+ _doIterationInThread = _doSelectInThread
+
+ def ensureWorkerThread(self):
+ if self.workerThread is None or not self.workerThread.isAlive():
+ self.workerThread = Thread(target=self._workerInThread)
+ self.workerThread.start()
+
+ def doThreadIteration(self, timeout):
+ self._sendToThread(self._doIterationInThread, timeout)
+ self.ensureWorkerThread()
+ #print >>sys.stderr, 'getting...'
+ msg, args = self.toMainThread.get()
+ #print >>sys.stderr, 'got', msg, args
+ getattr(self, '_process_' + msg)(*args)
+
+ doIteration = doThreadIteration
+
+ def _interleave(self):
+ while self.running:
+ #print >>sys.stderr, "runUntilCurrent"
+ self.runUntilCurrent()
+ t2 = self.timeout()
+ t = self.running and t2
+ self._sendToThread(self._doIterationInThread, t)
+ #print >>sys.stderr, "yielding"
+ yield None
+ #print >>sys.stderr, "fetching"
+ msg, args = self.toMainThread.get_nowait()
+ getattr(self, '_process_' + msg)(*args)
+
+ def interleave(self, waker, *args, **kw):
+ """
+ interleave(waker) interleaves this reactor with the
+ current application by moving the blocking parts of
+ the reactor (select() in this case) to a separate
+ thread. This is typically useful for integration with
+ GUI applications which have their own event loop
+ already running.
+
+ See the module docstring for more information.
+ """
+ self.startRunning(*args, **kw)
+ loop = self._interleave()
+ def mainWaker(waker=waker, loop=loop):
+ #print >>sys.stderr, "mainWaker()"
+ waker(loop.next)
+ self.mainWaker = mainWaker
+ loop.next()
+ self.ensureWorkerThread()
+
+ def _mainLoopShutdown(self):
+ self.mainWaker = None
+ if self.workerThread is not None:
+ #print >>sys.stderr, 'getting...'
+ self._sendToThread(raiseException, SystemExit)
+ self.wakeUp()
+ try:
+ while 1:
+ msg, args = self.toMainThread.get_nowait()
+ #print >>sys.stderr, "ignored:", (msg, args)
+ except Empty:
+ pass
+ self.workerThread.join()
+ self.workerThread = None
+ try:
+ while 1:
+ fn, args = self.toThreadQueue.get_nowait()
+ if fn is self._doIterationInThread:
+ log.msg('Iteration is still in the thread queue!')
+ elif fn is raiseException and args[0] is SystemExit:
+ pass
+ else:
+ fn(*args)
+ except Empty:
+ pass
+
+ def _doReadOrWrite(self, selectable, method, dict):
+ try:
+ why = getattr(selectable, method)()
+ handfn = getattr(selectable, 'fileno', None)
+ if not handfn:
+ why = _NO_FILENO
+ elif handfn() == -1:
+ why = _NO_FILEDESC
+ except:
+ why = sys.exc_info()[1]
+ log.err()
+ if why:
+ self._disconnectSelectable(selectable, why, method == "doRead")
+
+ def addReader(self, reader):
+ """Add a FileDescriptor for notification of data available to read.
+ """
+ self._sendToThread(self.reads.__setitem__, reader, 1)
+ self.wakeUp()
+
+ def addWriter(self, writer):
+ """Add a FileDescriptor for notification of data available to write.
+ """
+ self._sendToThread(self.writes.__setitem__, writer, 1)
+ self.wakeUp()
+
+ def removeReader(self, reader):
+ """Remove a Selectable for notification of data available to read.
+ """
+ self._sendToThread(dictRemove, self.reads, reader)
+
+ def removeWriter(self, writer):
+ """Remove a Selectable for notification of data available to write.
+ """
+ self._sendToThread(dictRemove, self.writes, writer)
+
+ def removeAll(self):
+ return self._removeAll(self.reads, self.writes)
+
+
+ def getReaders(self):
+ return self.reads.keys()
+
+
+ def getWriters(self):
+ return self.writes.keys()
+
+
+ def run(self, installSignalHandlers=1):
+ self.startRunning(installSignalHandlers=installSignalHandlers)
+ self.mainLoop()
+
+ def mainLoop(self):
+ q = Queue()
+ self.interleave(q.put)
+ while self.running:
+ try:
+ q.get()()
+ except StopIteration:
+ break
+
+
+
+def install():
+ """Configure the twisted mainloop to be run using the select() reactor.
+ """
+ reactor = ThreadedSelectReactor()
+ from twisted.internet.main import installReactor
+ installReactor(reactor)
+ return reactor
+
+__all__ = ['install']
diff --git a/vendor/Twisted-10.0.0/twisted/internet/_win32serialport.py b/vendor/Twisted-10.0.0/twisted/internet/_win32serialport.py
new file mode 100644
index 0000000000..576e7b4983
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/_win32serialport.py
@@ -0,0 +1,112 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Serial port support for Windows.
+
+Requires PySerial and win32all, and needs to be used with win32event
+reactor.
+"""
+
+# system imports
+import os
+import serial
+from serial import PARITY_NONE, PARITY_EVEN, PARITY_ODD
+from serial import STOPBITS_ONE, STOPBITS_TWO
+from serial import FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS
+import win32file, win32event
+
+# twisted imports
+from twisted.protocols import basic
+from twisted.internet import abstract
+from twisted.python import log
+
+# sibling imports
+from serialport import BaseSerialPort
+
+
+class SerialPort(BaseSerialPort, abstract.FileDescriptor):
+ """A select()able serial device, acting as a transport."""
+
+ connected = 1
+
+ def __init__(self, protocol, deviceNameOrPortNumber, reactor,
+ baudrate = 9600, bytesize = EIGHTBITS, parity = PARITY_NONE,
+ stopbits = STOPBITS_ONE, xonxoff = 0, rtscts = 0):
+ self._serial = serial.Serial(deviceNameOrPortNumber, baudrate=baudrate,
+ bytesize=bytesize, parity=parity,
+ stopbits=stopbits, timeout=None,
+ xonxoff=xonxoff, rtscts=rtscts)
+ self.flushInput()
+ self.flushOutput()
+ self.reactor = reactor
+ self.protocol = protocol
+ self.outQueue = []
+ self.closed = 0
+ self.closedNotifies = 0
+ self.writeInProgress = 0
+
+ self.protocol = protocol
+ self._overlappedRead = win32file.OVERLAPPED()
+ self._overlappedRead.hEvent = win32event.CreateEvent(None, 1, 0, None)
+ self._overlappedWrite = win32file.OVERLAPPED()
+ self._overlappedWrite.hEvent = win32event.CreateEvent(None, 0, 0, None)
+
+ self.reactor.addEvent(self._overlappedRead.hEvent, self, 'serialReadEvent')
+ self.reactor.addEvent(self._overlappedWrite.hEvent, self, 'serialWriteEvent')
+
+ self.protocol.makeConnection(self)
+
+ flags, comstat = win32file.ClearCommError(self._serial.hComPort)
+ rc, self.read_buf = win32file.ReadFile(self._serial.hComPort,
+ win32file.AllocateReadBuffer(1),
+ self._overlappedRead)
+
+ def serialReadEvent(self):
+ #get that character we set up
+ n = win32file.GetOverlappedResult(self._serial.hComPort, self._overlappedRead, 0)
+ if n:
+ first = str(self.read_buf[:n])
+ #now we should get everything that is already in the buffer
+ flags, comstat = win32file.ClearCommError(self._serial.hComPort)
+ if comstat.cbInQue:
+ win32event.ResetEvent(self._overlappedRead.hEvent)
+ rc, buf = win32file.ReadFile(self._serial.hComPort,
+ win32file.AllocateReadBuffer(comstat.cbInQue),
+ self._overlappedRead)
+ n = win32file.GetOverlappedResult(self._serial.hComPort, self._overlappedRead, 1)
+ #handle all the received data:
+ self.protocol.dataReceived(first + str(buf[:n]))
+ else:
+ #handle all the received data:
+ self.protocol.dataReceived(first)
+
+ #set up next one
+ win32event.ResetEvent(self._overlappedRead.hEvent)
+ rc, self.read_buf = win32file.ReadFile(self._serial.hComPort,
+ win32file.AllocateReadBuffer(1),
+ self._overlappedRead)
+
+ def write(self, data):
+ if data:
+ if self.writeInProgress:
+ self.outQueue.append(data)
+ else:
+ self.writeInProgress = 1
+ win32file.WriteFile(self._serial.hComPort, data, self._overlappedWrite)
+
+ def serialWriteEvent(self):
+ try:
+ dataToWrite = self.outQueue.pop(0)
+ except IndexError:
+ self.writeInProgress = 0
+ return
+ else:
+ win32file.WriteFile(self._serial.hComPort, dataToWrite, self._overlappedWrite)
+
+ def connectionLost(self, reason):
+ self.reactor.removeEvent(self._overlappedRead.hEvent)
+ self.reactor.removeEvent(self._overlappedWrite.hEvent)
+ abstract.FileDescriptor.connectionLost(self, reason)
+ self._serial.close()
diff --git a/vendor/Twisted-10.0.0/twisted/internet/_win32stdio.py b/vendor/Twisted-10.0.0/twisted/internet/_win32stdio.py
new file mode 100644
index 0000000000..c4c564407c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/_win32stdio.py
@@ -0,0 +1,124 @@
+# -*- test-case-name: twisted.test.test_stdio -*-
+
+"""
+Windows-specific implementation of the L{twisted.internet.stdio} interface.
+"""
+
+import win32api
+import os, msvcrt
+
+from zope.interface import implements
+
+from twisted.internet.interfaces import IHalfCloseableProtocol, ITransport, IAddress
+from twisted.internet.interfaces import IConsumer, IPushProducer
+
+from twisted.internet import _pollingfile, main
+from twisted.python.failure import Failure
+
+
+class Win32PipeAddress(object):
+ implements(IAddress)
+
+
+
+class StandardIO(_pollingfile._PollingTimer):
+
+ implements(ITransport,
+ IConsumer,
+ IPushProducer)
+
+ disconnecting = False
+ disconnected = False
+
+ def __init__(self, proto):
+ """
+ Start talking to standard IO with the given protocol.
+
+ Also, put it stdin/stdout/stderr into binary mode.
+ """
+ from twisted.internet import reactor
+
+ for stdfd in range(0, 1, 2):
+ msvcrt.setmode(stdfd, os.O_BINARY)
+
+ _pollingfile._PollingTimer.__init__(self, reactor)
+ self.proto = proto
+
+ hstdin = win32api.GetStdHandle(win32api.STD_INPUT_HANDLE)
+ hstdout = win32api.GetStdHandle(win32api.STD_OUTPUT_HANDLE)
+
+ self.stdin = _pollingfile._PollableReadPipe(
+ hstdin, self.dataReceived, self.readConnectionLost)
+
+ self.stdout = _pollingfile._PollableWritePipe(
+ hstdout, self.writeConnectionLost)
+
+ self._addPollableResource(self.stdin)
+ self._addPollableResource(self.stdout)
+
+ self.proto.makeConnection(self)
+
+ def dataReceived(self, data):
+ self.proto.dataReceived(data)
+
+ def readConnectionLost(self):
+ if IHalfCloseableProtocol.providedBy(self.proto):
+ self.proto.readConnectionLost()
+ self.checkConnLost()
+
+ def writeConnectionLost(self):
+ if IHalfCloseableProtocol.providedBy(self.proto):
+ self.proto.writeConnectionLost()
+ self.checkConnLost()
+
+ connsLost = 0
+
+ def checkConnLost(self):
+ self.connsLost += 1
+ if self.connsLost >= 2:
+ self.disconnecting = True
+ self.disconnected = True
+ self.proto.connectionLost(Failure(main.CONNECTION_DONE))
+
+ # ITransport
+
+ def write(self, data):
+ self.stdout.write(data)
+
+ def writeSequence(self, seq):
+ self.stdout.write(''.join(seq))
+
+ def loseConnection(self):
+ self.disconnecting = True
+ self.stdin.close()
+ self.stdout.close()
+
+ def getPeer(self):
+ return Win32PipeAddress()
+
+ def getHost(self):
+ return Win32PipeAddress()
+
+ # IConsumer
+
+ def registerProducer(self, producer, streaming):
+ return self.stdout.registerProducer(producer, streaming)
+
+ def unregisterProducer(self):
+ return self.stdout.unregisterProducer()
+
+ # def write() above
+
+ # IProducer
+
+ def stopProducing(self):
+ self.stdin.stopProducing()
+
+ # IPushProducer
+
+ def pauseProducing(self):
+ self.stdin.pauseProducing()
+
+ def resumeProducing(self):
+ self.stdin.resumeProducing()
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/abstract.py b/vendor/Twisted-10.0.0/twisted/internet/abstract.py
new file mode 100644
index 0000000000..63212390b5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/abstract.py
@@ -0,0 +1,378 @@
+# -*- test-case-name: twisted.test.test_abstract -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Support for generic select()able objects.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+from zope.interface import implements
+
+# Twisted Imports
+from twisted.python import log, reflect, failure
+from twisted.persisted import styles
+from twisted.internet import interfaces, main
+
+
+class FileDescriptor(log.Logger, styles.Ephemeral, object):
+ """An object which can be operated on by select().
+
+ This is an abstract superclass of all objects which may be notified when
+ they are readable or writable; e.g. they have a file-descriptor that is
+ valid to be passed to select(2).
+ """
+ connected = 0
+ producerPaused = 0
+ streamingProducer = 0
+ producer = None
+ disconnected = 0
+ disconnecting = 0
+ _writeDisconnecting = False
+ _writeDisconnected = False
+ dataBuffer = ""
+ offset = 0
+
+ SEND_LIMIT = 128*1024
+
+ implements(interfaces.IProducer, interfaces.IReadWriteDescriptor,
+ interfaces.IConsumer, interfaces.ITransport, interfaces.IHalfCloseableDescriptor)
+
+ def __init__(self, reactor=None):
+ if not reactor:
+ from twisted.internet import reactor
+ self.reactor = reactor
+ self._tempDataBuffer = [] # will be added to dataBuffer in doWrite
+ self._tempDataLen = 0
+
+ def connectionLost(self, reason):
+ """The connection was lost.
+
+ This is called when the connection on a selectable object has been
+ lost. It will be called whether the connection was closed explicitly,
+ an exception occurred in an event handler, or the other end of the
+ connection closed it first.
+
+ Clean up state here, but make sure to call back up to FileDescriptor.
+ """
+
+ self.disconnected = 1
+ self.connected = 0
+ if self.producer is not None:
+ self.producer.stopProducing()
+ self.producer = None
+ self.stopReading()
+ self.stopWriting()
+
+
+ def writeSomeData(self, data):
+ """
+ Write as much as possible of the given data, immediately.
+
+ This is called to invoke the lower-level writing functionality, such
+ as a socket's send() method, or a file's write(); this method
+ returns an integer or an exception. If an integer, it is the number
+ of bytes written (possibly zero); if an exception, it indicates the
+ connection was lost.
+ """
+ raise NotImplementedError("%s does not implement writeSomeData" %
+ reflect.qual(self.__class__))
+
+
+ def doRead(self):
+ """Called when data is avaliable for reading.
+
+ Subclasses must override this method. The result will be interpreted
+ in the same way as a result of doWrite().
+ """
+ raise NotImplementedError("%s does not implement doRead" %
+ reflect.qual(self.__class__))
+
+ def doWrite(self):
+ """
+ Called when data can be written.
+
+ A result that is true (which will be a negative number or an
+ exception instance) indicates that the connection was lost. A false
+ result implies the connection is still there; a result of 0
+ indicates no write was done, and a result of None indicates that a
+ write was done.
+ """
+ if len(self.dataBuffer) - self.offset < self.SEND_LIMIT:
+ # If there is currently less than SEND_LIMIT bytes left to send
+ # in the string, extend it with the array data.
+ self.dataBuffer = buffer(self.dataBuffer, self.offset) + "".join(self._tempDataBuffer)
+ self.offset = 0
+ self._tempDataBuffer = []
+ self._tempDataLen = 0
+
+ # Send as much data as you can.
+ if self.offset:
+ l = self.writeSomeData(buffer(self.dataBuffer, self.offset))
+ else:
+ l = self.writeSomeData(self.dataBuffer)
+
+ # There is no writeSomeData implementation in Twisted which returns
+ # 0, but the documentation for writeSomeData used to claim negative
+ # integers meant connection lost. Keep supporting this here,
+ # although it may be worth deprecating and removing at some point.
+ if l < 0 or isinstance(l, Exception):
+ return l
+ if l == 0 and self.dataBuffer:
+ result = 0
+ else:
+ result = None
+ self.offset += l
+ # If there is nothing left to send,
+ if self.offset == len(self.dataBuffer) and not self._tempDataLen:
+ self.dataBuffer = ""
+ self.offset = 0
+ # stop writing.
+ self.stopWriting()
+ # If I've got a producer who is supposed to supply me with data,
+ if self.producer is not None and ((not self.streamingProducer)
+ or self.producerPaused):
+ # tell them to supply some more.
+ self.producerPaused = 0
+ self.producer.resumeProducing()
+ elif self.disconnecting:
+ # But if I was previously asked to let the connection die, do
+ # so.
+ return self._postLoseConnection()
+ elif self._writeDisconnecting:
+ # I was previously asked to to half-close the connection.
+ result = self._closeWriteConnection()
+ self._writeDisconnected = True
+ return result
+ return result
+
+ def _postLoseConnection(self):
+ """Called after a loseConnection(), when all data has been written.
+
+ Whatever this returns is then returned by doWrite.
+ """
+ # default implementation, telling reactor we're finished
+ return main.CONNECTION_DONE
+
+ def _closeWriteConnection(self):
+ # override in subclasses
+ pass
+
+ def writeConnectionLost(self, reason):
+ # in current code should never be called
+ self.connectionLost(reason)
+
+ def readConnectionLost(self, reason):
+ # override in subclasses
+ self.connectionLost(reason)
+
+ def write(self, data):
+ """Reliably write some data.
+
+ The data is buffered until the underlying file descriptor is ready
+ for writing. If there is more than C{self.bufferSize} data in the
+ buffer and this descriptor has a registered streaming producer, its
+ C{pauseProducing()} method will be called.
+ """
+ if isinstance(data, unicode): # no, really, I mean it
+ raise TypeError("Data must not be unicode")
+ if not self.connected or self._writeDisconnected:
+ return
+ if data:
+ self._tempDataBuffer.append(data)
+ self._tempDataLen += len(data)
+ # If we are responsible for pausing our producer,
+ if self.producer is not None and self.streamingProducer:
+ # and our buffer is full,
+ if len(self.dataBuffer) + self._tempDataLen > self.bufferSize:
+ # pause it.
+ self.producerPaused = 1
+ self.producer.pauseProducing()
+ self.startWriting()
+
+ def writeSequence(self, iovec):
+ """Reliably write a sequence of data.
+
+ Currently, this is a convenience method roughly equivalent to::
+
+ for chunk in iovec:
+ fd.write(chunk)
+
+ It may have a more efficient implementation at a later time or in a
+ different reactor.
+
+ As with the C{write()} method, if a buffer size limit is reached and a
+ streaming producer is registered, it will be paused until the buffered
+ data is written to the underlying file descriptor.
+ """
+ if not self.connected or not iovec or self._writeDisconnected:
+ return
+ self._tempDataBuffer.extend(iovec)
+ for i in iovec:
+ self._tempDataLen += len(i)
+ # If we are responsible for pausing our producer,
+ if self.producer is not None and self.streamingProducer:
+ # and our buffer is full,
+ if len(self.dataBuffer) + self._tempDataLen > self.bufferSize:
+ # pause it.
+ self.producerPaused = 1
+ self.producer.pauseProducing()
+ self.startWriting()
+
+ def loseConnection(self, _connDone=failure.Failure(main.CONNECTION_DONE)):
+ """Close the connection at the next available opportunity.
+
+ Call this to cause this FileDescriptor to lose its connection. It will
+ first write any data that it has buffered.
+
+ If there is data buffered yet to be written, this method will cause the
+ transport to lose its connection as soon as it's done flushing its
+ write buffer. If you have a producer registered, the connection won't
+ be closed until the producer is finished. Therefore, make sure you
+ unregister your producer when it's finished, or the connection will
+ never close.
+ """
+
+ if self.connected and not self.disconnecting:
+ if self._writeDisconnected:
+ # doWrite won't trigger the connection close anymore
+ self.stopReading()
+ self.stopWriting()
+ self.connectionLost(_connDone)
+ else:
+ self.stopReading()
+ self.startWriting()
+ self.disconnecting = 1
+
+ def loseWriteConnection(self):
+ self._writeDisconnecting = True
+ self.startWriting()
+
+ def stopReading(self):
+ """Stop waiting for read availability.
+
+ Call this to remove this selectable from being notified when it is
+ ready for reading.
+ """
+ self.reactor.removeReader(self)
+
+ def stopWriting(self):
+ """Stop waiting for write availability.
+
+ Call this to remove this selectable from being notified when it is ready
+ for writing.
+ """
+ self.reactor.removeWriter(self)
+
+ def startReading(self):
+ """Start waiting for read availability.
+ """
+ self.reactor.addReader(self)
+
+ def startWriting(self):
+ """Start waiting for write availability.
+
+ Call this to have this FileDescriptor be notified whenever it is ready for
+ writing.
+ """
+ self.reactor.addWriter(self)
+
+ # Producer/consumer implementation
+
+ # first, the consumer stuff. This requires no additional work, as
+ # any object you can write to can be a consumer, really.
+
+ producer = None
+ bufferSize = 2**2**2**2
+
+ def registerProducer(self, producer, streaming):
+ """Register to receive data from a producer.
+
+ This sets this selectable to be a consumer for a producer. When this
+ selectable runs out of data on a write() call, it will ask the producer
+ to resumeProducing(). When the FileDescriptor's internal data buffer is
+ filled, it will ask the producer to pauseProducing(). If the connection
+ is lost, FileDescriptor calls producer's stopProducing() method.
+
+ If streaming is true, the producer should provide the IPushProducer
+ interface. Otherwise, it is assumed that producer provides the
+ IPullProducer interface. In this case, the producer won't be asked
+ to pauseProducing(), but it has to be careful to write() data only
+ when its resumeProducing() method is called.
+ """
+ if self.producer is not None:
+ raise RuntimeError("Cannot register producer %s, because producer %s was never unregistered." % (producer, self.producer))
+ if self.disconnected:
+ producer.stopProducing()
+ else:
+ self.producer = producer
+ self.streamingProducer = streaming
+ if not streaming:
+ producer.resumeProducing()
+
+ def unregisterProducer(self):
+ """Stop consuming data from a producer, without disconnecting.
+ """
+ self.producer = None
+
+ def stopConsuming(self):
+ """Stop consuming data.
+
+ This is called when a producer has lost its connection, to tell the
+ consumer to go lose its connection (and break potential circular
+ references).
+ """
+ self.unregisterProducer()
+ self.loseConnection()
+
+ # producer interface implementation
+
+ def resumeProducing(self):
+ assert self.connected and not self.disconnecting
+ self.startReading()
+
+ def pauseProducing(self):
+ self.stopReading()
+
+ def stopProducing(self):
+ self.loseConnection()
+
+
+ def fileno(self):
+ """File Descriptor number for select().
+
+ This method must be overridden or assigned in subclasses to
+ indicate a valid file descriptor for the operating system.
+ """
+ return -1
+
+
+def isIPAddress(addr):
+ """
+ Determine whether the given string represents an IPv4 address.
+
+ @type addr: C{str}
+ @param addr: A string which may or may not be the decimal dotted
+ representation of an IPv4 address.
+
+ @rtype: C{bool}
+ @return: C{True} if C{addr} represents an IPv4 address, C{False}
+ otherwise.
+ """
+ dottedParts = addr.split('.')
+ if len(dottedParts) == 4:
+ for octet in dottedParts:
+ try:
+ value = int(octet)
+ except ValueError:
+ return False
+ else:
+ if value < 0 or value > 255:
+ return False
+ return True
+ return False
+
+
+__all__ = ["FileDescriptor"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/address.py b/vendor/Twisted-10.0.0/twisted/internet/address.py
new file mode 100644
index 0000000000..b349080048
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/address.py
@@ -0,0 +1,113 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Address objects for network connections."""
+
+import warnings, os
+from zope.interface import implements
+from twisted.internet.interfaces import IAddress
+
+
+class IPv4Address(object):
+ """
+ Object representing an IPv4 socket endpoint.
+
+ @ivar type: A string describing the type of transport, either 'TCP' or 'UDP'.
+ @ivar host: A string containing the dotted-quad IP address.
+ @ivar port: An integer representing the port number.
+ """
+
+ # _bwHack is given to old users who think we are a tuple. They expected
+ # addr[0] to define the socket type rather than the address family, so
+ # the value comes from a different namespace than the new .type value:
+
+ # type = map[_bwHack]
+ # map = { 'SSL': 'TCP', 'INET': 'TCP', 'INET_UDP': 'UDP' }
+
+ implements(IAddress)
+
+ def __init__(self, type, host, port, _bwHack = None):
+ assert type in ('TCP', 'UDP')
+ self.type = type
+ self.host = host
+ self.port = port
+ self._bwHack = _bwHack
+
+ def __getitem__(self, index):
+ warnings.warn("IPv4Address.__getitem__ is deprecated. Use attributes instead.",
+ category=DeprecationWarning, stacklevel=2)
+ return (self._bwHack or self.type, self.host, self.port).__getitem__(index)
+
+ def __getslice__(self, start, stop):
+ warnings.warn("IPv4Address.__getitem__ is deprecated. Use attributes instead.",
+ category=DeprecationWarning, stacklevel=2)
+ return (self._bwHack or self.type, self.host, self.port)[start:stop]
+
+ def __eq__(self, other):
+ if isinstance(other, tuple):
+ return tuple(self) == other
+ elif isinstance(other, IPv4Address):
+ a = (self.type, self.host, self.port)
+ b = (other.type, other.host, other.port)
+ return a == b
+ return False
+
+ def __str__(self):
+ return 'IPv4Address(%s, %r, %d)' % (self.type, self.host, self.port)
+
+
+class UNIXAddress(object):
+ """
+ Object representing a UNIX socket endpoint.
+
+ @ivar name: The filename associated with this socket.
+ @type name: C{str}
+ """
+
+ implements(IAddress)
+
+ def __init__(self, name, _bwHack='UNIX'):
+ self.name = name
+ self._bwHack = _bwHack
+
+ def __getitem__(self, index):
+ warnings.warn("UNIXAddress.__getitem__ is deprecated. Use attributes instead.",
+ category=DeprecationWarning, stacklevel=2)
+ return (self._bwHack, self.name).__getitem__(index)
+
+ def __getslice__(self, start, stop):
+ warnings.warn("UNIXAddress.__getitem__ is deprecated. Use attributes instead.",
+ category=DeprecationWarning, stacklevel=2)
+ return (self._bwHack, self.name)[start:stop]
+
+ def __eq__(self, other):
+ if isinstance(other, tuple):
+ return tuple(self) == other
+ elif isinstance(other, UNIXAddress):
+ try:
+ return os.path.samefile(self.name, other.name)
+ except OSError:
+ pass
+ return False
+
+ def __str__(self):
+ return 'UNIXSocket(%r)' % (self.name,)
+
+
+# These are for buildFactory backwards compatability due to
+# stupidity-induced inconsistency.
+
+class _ServerFactoryIPv4Address(IPv4Address):
+ """Backwards compatability hack. Just like IPv4Address in practice."""
+
+ def __eq__(self, other):
+ if isinstance(other, tuple):
+ warnings.warn("IPv4Address.__getitem__ is deprecated. Use attributes instead.",
+ category=DeprecationWarning, stacklevel=2)
+ return (self.host, self.port) == other
+ elif isinstance(other, IPv4Address):
+ a = (self.type, self.host, self.port)
+ b = (other.type, other.host, other.port)
+ return a == b
+ return False
diff --git a/vendor/Twisted-10.0.0/twisted/internet/base.py b/vendor/Twisted-10.0.0/twisted/internet/base.py
new file mode 100644
index 0000000000..7513f6ddd3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/base.py
@@ -0,0 +1,1191 @@
+# -*- test-case-name: twisted.test.test_internet -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Very basic functionality for a Reactor implementation.
+"""
+
+import socket # needed only for sync-dns
+from zope.interface import implements, classImplements
+
+import sys
+import warnings
+from heapq import heappush, heappop, heapify
+
+import traceback
+
+from twisted.python.compat import set
+from twisted.python.util import unsignedID
+from twisted.internet.interfaces import IReactorCore, IReactorTime, IReactorThreads
+from twisted.internet.interfaces import IResolverSimple, IReactorPluggableResolver
+from twisted.internet.interfaces import IConnector, IDelayedCall
+from twisted.internet import fdesc, main, error, abstract, defer, threads
+from twisted.python import log, failure, reflect
+from twisted.python.runtime import seconds as runtimeSeconds, platform, platformType
+from twisted.internet.defer import Deferred, DeferredList
+from twisted.persisted import styles
+
+# This import is for side-effects! Even if you don't see any code using it
+# in this module, don't delete it.
+from twisted.python import threadable
+
+
+class DelayedCall(styles.Ephemeral):
+
+ implements(IDelayedCall)
+ # enable .debug to record creator call stack, and it will be logged if
+ # an exception occurs while the function is being run
+ debug = False
+ _str = None
+
+ def __init__(self, time, func, args, kw, cancel, reset,
+ seconds=runtimeSeconds):
+ """
+ @param time: Seconds from the epoch at which to call C{func}.
+ @param func: The callable to call.
+ @param args: The positional arguments to pass to the callable.
+ @param kw: The keyword arguments to pass to the callable.
+ @param cancel: A callable which will be called with this
+ DelayedCall before cancellation.
+ @param reset: A callable which will be called with this
+ DelayedCall after changing this DelayedCall's scheduled
+ execution time. The callable should adjust any necessary
+ scheduling details to ensure this DelayedCall is invoked
+ at the new appropriate time.
+ @param seconds: If provided, a no-argument callable which will be
+ used to determine the current time any time that information is
+ needed.
+ """
+ self.time, self.func, self.args, self.kw = time, func, args, kw
+ self.resetter = reset
+ self.canceller = cancel
+ self.seconds = seconds
+ self.cancelled = self.called = 0
+ self.delayed_time = 0
+ if self.debug:
+ self.creator = traceback.format_stack()[:-2]
+
+ def getTime(self):
+ """Return the time at which this call will fire
+
+ @rtype: C{float}
+ @return: The number of seconds after the epoch at which this call is
+ scheduled to be made.
+ """
+ return self.time + self.delayed_time
+
+ def cancel(self):
+ """Unschedule this call
+
+ @raise AlreadyCancelled: Raised if this call has already been
+ unscheduled.
+
+ @raise AlreadyCalled: Raised if this call has already been made.
+ """
+ if self.cancelled:
+ raise error.AlreadyCancelled
+ elif self.called:
+ raise error.AlreadyCalled
+ else:
+ self.canceller(self)
+ self.cancelled = 1
+ if self.debug:
+ self._str = str(self)
+ del self.func, self.args, self.kw
+
+ def reset(self, secondsFromNow):
+ """Reschedule this call for a different time
+
+ @type secondsFromNow: C{float}
+ @param secondsFromNow: The number of seconds from the time of the
+ C{reset} call at which this call will be scheduled.
+
+ @raise AlreadyCancelled: Raised if this call has been cancelled.
+ @raise AlreadyCalled: Raised if this call has already been made.
+ """
+ if self.cancelled:
+ raise error.AlreadyCancelled
+ elif self.called:
+ raise error.AlreadyCalled
+ else:
+ newTime = self.seconds() + secondsFromNow
+ if newTime < self.time:
+ self.delayed_time = 0
+ self.time = newTime
+ self.resetter(self)
+ else:
+ self.delayed_time = newTime - self.time
+
+ def delay(self, secondsLater):
+ """Reschedule this call for a later time
+
+ @type secondsLater: C{float}
+ @param secondsLater: The number of seconds after the originally
+ scheduled time for which to reschedule this call.
+
+ @raise AlreadyCancelled: Raised if this call has been cancelled.
+ @raise AlreadyCalled: Raised if this call has already been made.
+ """
+ if self.cancelled:
+ raise error.AlreadyCancelled
+ elif self.called:
+ raise error.AlreadyCalled
+ else:
+ self.delayed_time += secondsLater
+ if self.delayed_time < 0:
+ self.activate_delay()
+ self.resetter(self)
+
+ def activate_delay(self):
+ self.time += self.delayed_time
+ self.delayed_time = 0
+
+ def active(self):
+ """Determine whether this call is still pending
+
+ @rtype: C{bool}
+ @return: True if this call has not yet been made or cancelled,
+ False otherwise.
+ """
+ return not (self.cancelled or self.called)
+
+ def __le__(self, other):
+ return self.time <= other.time
+
+
+ def __str__(self):
+ if self._str is not None:
+ return self._str
+ if hasattr(self, 'func'):
+ if hasattr(self.func, 'func_name'):
+ func = self.func.func_name
+ if hasattr(self.func, 'im_class'):
+ func = self.func.im_class.__name__ + '.' + func
+ else:
+ func = reflect.safe_repr(self.func)
+ else:
+ func = None
+
+ now = self.seconds()
+ L = ["<DelayedCall 0x%x [%ss] called=%s cancelled=%s" % (
+ unsignedID(self), self.time - now, self.called,
+ self.cancelled)]
+ if func is not None:
+ L.extend((" ", func, "("))
+ if self.args:
+ L.append(", ".join([reflect.safe_repr(e) for e in self.args]))
+ if self.kw:
+ L.append(", ")
+ if self.kw:
+ L.append(", ".join(['%s=%s' % (k, reflect.safe_repr(v)) for (k, v) in self.kw.iteritems()]))
+ L.append(")")
+
+ if self.debug:
+ L.append("\n\ntraceback at creation: \n\n%s" % (' '.join(self.creator)))
+ L.append('>')
+
+ return "".join(L)
+
+
+
+class ThreadedResolver(object):
+ """
+ L{ThreadedResolver} uses a reactor, a threadpool, and
+ L{socket.gethostbyname} to perform name lookups without blocking the
+ reactor thread. It also supports timeouts indepedently from whatever
+ timeout logic L{socket.gethostbyname} might have.
+
+ @ivar reactor: The reactor the threadpool of which will be used to call
+ L{socket.gethostbyname} and the I/O thread of which the result will be
+ delivered.
+ """
+ implements(IResolverSimple)
+
+ def __init__(self, reactor):
+ self.reactor = reactor
+ self._runningQueries = {}
+
+
+ def _fail(self, name, err):
+ err = error.DNSLookupError("address %r not found: %s" % (name, err))
+ return failure.Failure(err)
+
+
+ def _cleanup(self, name, lookupDeferred):
+ userDeferred, cancelCall = self._runningQueries[lookupDeferred]
+ del self._runningQueries[lookupDeferred]
+ userDeferred.errback(self._fail(name, "timeout error"))
+
+
+ def _checkTimeout(self, result, name, lookupDeferred):
+ try:
+ userDeferred, cancelCall = self._runningQueries[lookupDeferred]
+ except KeyError:
+ pass
+ else:
+ del self._runningQueries[lookupDeferred]
+ cancelCall.cancel()
+
+ if isinstance(result, failure.Failure):
+ userDeferred.errback(self._fail(name, result.getErrorMessage()))
+ else:
+ userDeferred.callback(result)
+
+
+ def getHostByName(self, name, timeout = (1, 3, 11, 45)):
+ """
+ See L{twisted.internet.interfaces.IResolverSimple.getHostByName}.
+
+ Note that the elements of C{timeout} are summed and the result is used
+ as a timeout for the lookup. Any intermediate timeout or retry logic
+ is left up to the platform via L{socket.gethostbyname}.
+ """
+ if timeout:
+ timeoutDelay = sum(timeout)
+ else:
+ timeoutDelay = 60
+ userDeferred = defer.Deferred()
+ lookupDeferred = threads.deferToThreadPool(
+ self.reactor, self.reactor.getThreadPool(),
+ socket.gethostbyname, name)
+ cancelCall = self.reactor.callLater(
+ timeoutDelay, self._cleanup, name, lookupDeferred)
+ self._runningQueries[lookupDeferred] = (userDeferred, cancelCall)
+ lookupDeferred.addBoth(self._checkTimeout, name, lookupDeferred)
+ return userDeferred
+
+
+
+class BlockingResolver:
+ implements(IResolverSimple)
+
+ def getHostByName(self, name, timeout = (1, 3, 11, 45)):
+ try:
+ address = socket.gethostbyname(name)
+ except socket.error:
+ msg = "address %r not found" % (name,)
+ err = error.DNSLookupError(msg)
+ return defer.fail(err)
+ else:
+ return defer.succeed(address)
+
+
+class _ThreePhaseEvent(object):
+ """
+ Collection of callables (with arguments) which can be invoked as a group in
+ a particular order.
+
+ This provides the underlying implementation for the reactor's system event
+ triggers. An instance of this class tracks triggers for all phases of a
+ single type of event.
+
+ @ivar before: A list of the before-phase triggers containing three-tuples
+ of a callable, a tuple of positional arguments, and a dict of keyword
+ arguments
+
+ @ivar finishedBefore: A list of the before-phase triggers which have
+ already been executed. This is only populated in the C{'BEFORE'} state.
+
+ @ivar during: A list of the during-phase triggers containing three-tuples
+ of a callable, a tuple of positional arguments, and a dict of keyword
+ arguments
+
+ @ivar after: A list of the after-phase triggers containing three-tuples
+ of a callable, a tuple of positional arguments, and a dict of keyword
+ arguments
+
+ @ivar state: A string indicating what is currently going on with this
+ object. One of C{'BASE'} (for when nothing in particular is happening;
+ this is the initial value), C{'BEFORE'} (when the before-phase triggers
+ are in the process of being executed).
+ """
+ def __init__(self):
+ self.before = []
+ self.during = []
+ self.after = []
+ self.state = 'BASE'
+
+
+ def addTrigger(self, phase, callable, *args, **kwargs):
+ """
+ Add a trigger to the indicate phase.
+
+ @param phase: One of C{'before'}, C{'during'}, or C{'after'}.
+
+ @param callable: An object to be called when this event is triggered.
+ @param *args: Positional arguments to pass to C{callable}.
+ @param **kwargs: Keyword arguments to pass to C{callable}.
+
+ @return: An opaque handle which may be passed to L{removeTrigger} to
+ reverse the effects of calling this method.
+ """
+ if phase not in ('before', 'during', 'after'):
+ raise KeyError("invalid phase")
+ getattr(self, phase).append((callable, args, kwargs))
+ return phase, callable, args, kwargs
+
+
+ def removeTrigger(self, handle):
+ """
+ Remove a previously added trigger callable.
+
+ @param handle: An object previously returned by L{addTrigger}. The
+ trigger added by that call will be removed.
+
+ @raise ValueError: If the trigger associated with C{handle} has already
+ been removed or if C{handle} is not a valid handle.
+ """
+ return getattr(self, 'removeTrigger_' + self.state)(handle)
+
+
+ def removeTrigger_BASE(self, handle):
+ """
+ Just try to remove the trigger.
+
+ @see: removeTrigger
+ """
+ try:
+ phase, callable, args, kwargs = handle
+ except (TypeError, ValueError), e:
+ raise ValueError("invalid trigger handle")
+ else:
+ if phase not in ('before', 'during', 'after'):
+ raise KeyError("invalid phase")
+ getattr(self, phase).remove((callable, args, kwargs))
+
+
+ def removeTrigger_BEFORE(self, handle):
+ """
+ Remove the trigger if it has yet to be executed, otherwise emit a
+ warning that in the future an exception will be raised when removing an
+ already-executed trigger.
+
+ @see: removeTrigger
+ """
+ phase, callable, args, kwargs = handle
+ if phase != 'before':
+ return self.removeTrigger_BASE(handle)
+ if (callable, args, kwargs) in self.finishedBefore:
+ warnings.warn(
+ "Removing already-fired system event triggers will raise an "
+ "exception in a future version of Twisted.",
+ category=DeprecationWarning,
+ stacklevel=3)
+ else:
+ self.removeTrigger_BASE(handle)
+
+
+ def fireEvent(self):
+ """
+ Call the triggers added to this event.
+ """
+ self.state = 'BEFORE'
+ self.finishedBefore = []
+ beforeResults = []
+ while self.before:
+ callable, args, kwargs = self.before.pop(0)
+ self.finishedBefore.append((callable, args, kwargs))
+ try:
+ result = callable(*args, **kwargs)
+ except:
+ log.err()
+ else:
+ if isinstance(result, Deferred):
+ beforeResults.append(result)
+ DeferredList(beforeResults).addCallback(self._continueFiring)
+
+
+ def _continueFiring(self, ignored):
+ """
+ Call the during and after phase triggers for this event.
+ """
+ self.state = 'BASE'
+ self.finishedBefore = []
+ for phase in self.during, self.after:
+ while phase:
+ callable, args, kwargs = phase.pop(0)
+ try:
+ callable(*args, **kwargs)
+ except:
+ log.err()
+
+
+
+class ReactorBase(object):
+ """
+ Default base class for Reactors.
+
+ @type _stopped: C{bool}
+ @ivar _stopped: A flag which is true between paired calls to C{reactor.run}
+ and C{reactor.stop}. This should be replaced with an explicit state
+ machine.
+
+ @type _justStopped: C{bool}
+ @ivar _justStopped: A flag which is true between the time C{reactor.stop}
+ is called and the time the shutdown system event is fired. This is
+ used to determine whether that event should be fired after each
+ iteration through the mainloop. This should be replaced with an
+ explicit state machine.
+
+ @type _started: C{bool}
+ @ivar _started: A flag which is true from the time C{reactor.run} is called
+ until the time C{reactor.run} returns. This is used to prevent calls
+ to C{reactor.run} on a running reactor. This should be replaced with
+ an explicit state machine.
+
+ @ivar running: See L{IReactorCore.running}
+ """
+ implements(IReactorCore, IReactorTime, IReactorPluggableResolver)
+
+ _stopped = True
+ installed = False
+ usingThreads = False
+ resolver = BlockingResolver()
+
+ __name__ = "twisted.internet.reactor"
+
+ def __init__(self):
+ self.threadCallQueue = []
+ self._eventTriggers = {}
+ self._pendingTimedCalls = []
+ self._newTimedCalls = []
+ self._cancellations = 0
+ self.running = False
+ self._started = False
+ self._justStopped = False
+ # reactor internal readers, e.g. the waker.
+ self._internalReaders = set()
+ self.waker = None
+
+ # Arrange for the running attribute to change to True at the right time
+ # and let a subclass possibly do other things at that time (eg install
+ # signal handlers).
+ self.addSystemEventTrigger(
+ 'during', 'startup', self._reallyStartRunning)
+ self.addSystemEventTrigger('during', 'shutdown', self.crash)
+ self.addSystemEventTrigger('during', 'shutdown', self.disconnectAll)
+
+ if platform.supportsThreads():
+ self._initThreads()
+
+ # override in subclasses
+
+ _lock = None
+
+ def installWaker(self):
+ raise NotImplementedError(
+ reflect.qual(self.__class__) + " did not implement installWaker")
+
+ def installResolver(self, resolver):
+ assert IResolverSimple.providedBy(resolver)
+ oldResolver = self.resolver
+ self.resolver = resolver
+ return oldResolver
+
+ def wakeUp(self):
+ """
+ Wake up the event loop.
+ """
+ if self.waker:
+ self.waker.wakeUp()
+ # if the waker isn't installed, the reactor isn't running, and
+ # therefore doesn't need to be woken up
+
+ def doIteration(self, delay):
+ """
+ Do one iteration over the readers and writers which have been added.
+ """
+ raise NotImplementedError(
+ reflect.qual(self.__class__) + " did not implement doIteration")
+
+ def addReader(self, reader):
+ raise NotImplementedError(
+ reflect.qual(self.__class__) + " did not implement addReader")
+
+ def addWriter(self, writer):
+ raise NotImplementedError(
+ reflect.qual(self.__class__) + " did not implement addWriter")
+
+ def removeReader(self, reader):
+ raise NotImplementedError(
+ reflect.qual(self.__class__) + " did not implement removeReader")
+
+ def removeWriter(self, writer):
+ raise NotImplementedError(
+ reflect.qual(self.__class__) + " did not implement removeWriter")
+
+ def removeAll(self):
+ raise NotImplementedError(
+ reflect.qual(self.__class__) + " did not implement removeAll")
+
+
+ def getReaders(self):
+ raise NotImplementedError(
+ reflect.qual(self.__class__) + " did not implement getReaders")
+
+
+ def getWriters(self):
+ raise NotImplementedError(
+ reflect.qual(self.__class__) + " did not implement getWriters")
+
+
+ def resolve(self, name, timeout = (1, 3, 11, 45)):
+ """Return a Deferred that will resolve a hostname.
+ """
+ if not name:
+ # XXX - This is *less than* '::', and will screw up IPv6 servers
+ return defer.succeed('0.0.0.0')
+ if abstract.isIPAddress(name):
+ return defer.succeed(name)
+ return self.resolver.getHostByName(name, timeout)
+
+ # Installation.
+
+ # IReactorCore
+ def stop(self):
+ """
+ See twisted.internet.interfaces.IReactorCore.stop.
+ """
+ if self._stopped:
+ raise error.ReactorNotRunning(
+ "Can't stop reactor that isn't running.")
+ self._stopped = True
+ self._justStopped = True
+
+
+ def crash(self):
+ """
+ See twisted.internet.interfaces.IReactorCore.crash.
+
+ Reset reactor state tracking attributes and re-initialize certain
+ state-transition helpers which were set up in C{__init__} but later
+ destroyed (through use).
+ """
+ self._started = False
+ self.running = False
+ self.addSystemEventTrigger(
+ 'during', 'startup', self._reallyStartRunning)
+
+ def sigInt(self, *args):
+ """Handle a SIGINT interrupt.
+ """
+ log.msg("Received SIGINT, shutting down.")
+ self.callFromThread(self.stop)
+
+ def sigBreak(self, *args):
+ """Handle a SIGBREAK interrupt.
+ """
+ log.msg("Received SIGBREAK, shutting down.")
+ self.callFromThread(self.stop)
+
+ def sigTerm(self, *args):
+ """Handle a SIGTERM interrupt.
+ """
+ log.msg("Received SIGTERM, shutting down.")
+ self.callFromThread(self.stop)
+
+ def disconnectAll(self):
+ """Disconnect every reader, and writer in the system.
+ """
+ selectables = self.removeAll()
+ for reader in selectables:
+ log.callWithLogger(reader,
+ reader.connectionLost,
+ failure.Failure(main.CONNECTION_LOST))
+
+
+ def iterate(self, delay=0):
+ """See twisted.internet.interfaces.IReactorCore.iterate.
+ """
+ self.runUntilCurrent()
+ self.doIteration(delay)
+
+
+ def fireSystemEvent(self, eventType):
+ """See twisted.internet.interfaces.IReactorCore.fireSystemEvent.
+ """
+ event = self._eventTriggers.get(eventType)
+ if event is not None:
+ event.fireEvent()
+
+
+ def addSystemEventTrigger(self, _phase, _eventType, _f, *args, **kw):
+ """See twisted.internet.interfaces.IReactorCore.addSystemEventTrigger.
+ """
+ assert callable(_f), "%s is not callable" % _f
+ if _eventType not in self._eventTriggers:
+ self._eventTriggers[_eventType] = _ThreePhaseEvent()
+ return (_eventType, self._eventTriggers[_eventType].addTrigger(
+ _phase, _f, *args, **kw))
+
+
+ def removeSystemEventTrigger(self, triggerID):
+ """See twisted.internet.interfaces.IReactorCore.removeSystemEventTrigger.
+ """
+ eventType, handle = triggerID
+ self._eventTriggers[eventType].removeTrigger(handle)
+
+
+ def callWhenRunning(self, _callable, *args, **kw):
+ """See twisted.internet.interfaces.IReactorCore.callWhenRunning.
+ """
+ if self.running:
+ _callable(*args, **kw)
+ else:
+ return self.addSystemEventTrigger('after', 'startup',
+ _callable, *args, **kw)
+
+ def startRunning(self):
+ """
+ Method called when reactor starts: do some initialization and fire
+ startup events.
+
+ Don't call this directly, call reactor.run() instead: it should take
+ care of calling this.
+
+ This method is somewhat misnamed. The reactor will not necessarily be
+ in the running state by the time this method returns. The only
+ guarantee is that it will be on its way to the running state.
+ """
+ if self._started:
+ raise error.ReactorAlreadyRunning()
+ self._started = True
+ self._stopped = False
+ threadable.registerAsIOThread()
+ self.fireSystemEvent('startup')
+
+
+ def _reallyStartRunning(self):
+ """
+ Method called to transition to the running state. This should happen
+ in the I{during startup} event trigger phase.
+ """
+ self.running = True
+
+ # IReactorTime
+
+ seconds = staticmethod(runtimeSeconds)
+
+ def callLater(self, _seconds, _f, *args, **kw):
+ """See twisted.internet.interfaces.IReactorTime.callLater.
+ """
+ assert callable(_f), "%s is not callable" % _f
+ assert sys.maxint >= _seconds >= 0, \
+ "%s is not greater than or equal to 0 seconds" % (_seconds,)
+ tple = DelayedCall(self.seconds() + _seconds, _f, args, kw,
+ self._cancelCallLater,
+ self._moveCallLaterSooner,
+ seconds=self.seconds)
+ self._newTimedCalls.append(tple)
+ return tple
+
+ def _moveCallLaterSooner(self, tple):
+ # Linear time find: slow.
+ heap = self._pendingTimedCalls
+ try:
+ pos = heap.index(tple)
+
+ # Move elt up the heap until it rests at the right place.
+ elt = heap[pos]
+ while pos != 0:
+ parent = (pos-1) // 2
+ if heap[parent] <= elt:
+ break
+ # move parent down
+ heap[pos] = heap[parent]
+ pos = parent
+ heap[pos] = elt
+ except ValueError:
+ # element was not found in heap - oh well...
+ pass
+
+ def _cancelCallLater(self, tple):
+ self._cancellations+=1
+
+ def cancelCallLater(self, callID):
+ """See twisted.internet.interfaces.IReactorTime.cancelCallLater.
+ """
+ # DO NOT DELETE THIS - this is documented in Python in a Nutshell, so we
+ # we can't get rid of it for a long time.
+ warnings.warn("reactor.cancelCallLater(callID) is deprecated - use callID.cancel() instead")
+ callID.cancel()
+
+ def getDelayedCalls(self):
+ """Return all the outstanding delayed calls in the system.
+ They are returned in no particular order.
+ This method is not efficient -- it is really only meant for
+ test cases."""
+ return [x for x in (self._pendingTimedCalls + self._newTimedCalls) if not x.cancelled]
+
+ def _insertNewDelayedCalls(self):
+ for call in self._newTimedCalls:
+ if call.cancelled:
+ self._cancellations-=1
+ else:
+ call.activate_delay()
+ heappush(self._pendingTimedCalls, call)
+ self._newTimedCalls = []
+
+ def timeout(self):
+ # insert new delayed calls to make sure to include them in timeout value
+ self._insertNewDelayedCalls()
+
+ if not self._pendingTimedCalls:
+ return None
+
+ return max(0, self._pendingTimedCalls[0].time - self.seconds())
+
+
+ def runUntilCurrent(self):
+ """Run all pending timed calls.
+ """
+ if self.threadCallQueue:
+ # Keep track of how many calls we actually make, as we're
+ # making them, in case another call is added to the queue
+ # while we're in this loop.
+ count = 0
+ total = len(self.threadCallQueue)
+ for (f, a, kw) in self.threadCallQueue:
+ try:
+ f(*a, **kw)
+ except:
+ log.err()
+ count += 1
+ if count == total:
+ break
+ del self.threadCallQueue[:count]
+ if self.threadCallQueue:
+ self.wakeUp()
+
+ # insert new delayed calls now
+ self._insertNewDelayedCalls()
+
+ now = self.seconds()
+ while self._pendingTimedCalls and (self._pendingTimedCalls[0].time <= now):
+ call = heappop(self._pendingTimedCalls)
+ if call.cancelled:
+ self._cancellations-=1
+ continue
+
+ if call.delayed_time > 0:
+ call.activate_delay()
+ heappush(self._pendingTimedCalls, call)
+ continue
+
+ try:
+ call.called = 1
+ call.func(*call.args, **call.kw)
+ except:
+ log.deferr()
+ if hasattr(call, "creator"):
+ e = "\n"
+ e += " C: previous exception occurred in " + \
+ "a DelayedCall created here:\n"
+ e += " C:"
+ e += "".join(call.creator).rstrip().replace("\n","\n C:")
+ e += "\n"
+ log.msg(e)
+
+
+ if (self._cancellations > 50 and
+ self._cancellations > len(self._pendingTimedCalls) >> 1):
+ self._cancellations = 0
+ self._pendingTimedCalls = [x for x in self._pendingTimedCalls
+ if not x.cancelled]
+ heapify(self._pendingTimedCalls)
+
+ if self._justStopped:
+ self._justStopped = False
+ self.fireSystemEvent("shutdown")
+
+ # IReactorProcess
+
+ def _checkProcessArgs(self, args, env):
+ """
+ Check for valid arguments and environment to spawnProcess.
+
+ @return: A two element tuple giving values to use when creating the
+ process. The first element of the tuple is a C{list} of C{str}
+ giving the values for argv of the child process. The second element
+ of the tuple is either C{None} if C{env} was C{None} or a C{dict}
+ mapping C{str} environment keys to C{str} environment values.
+ """
+ # Any unicode string which Python would successfully implicitly
+ # encode to a byte string would have worked before these explicit
+ # checks were added. Anything which would have failed with a
+ # UnicodeEncodeError during that implicit encoding step would have
+ # raised an exception in the child process and that would have been
+ # a pain in the butt to debug.
+ #
+ # So, we will explicitly attempt the same encoding which Python
+ # would implicitly do later. If it fails, we will report an error
+ # without ever spawning a child process. If it succeeds, we'll save
+ # the result so that Python doesn't need to do it implicitly later.
+ #
+ # For any unicode which we can actually encode, we'll also issue a
+ # deprecation warning, because no one should be passing unicode here
+ # anyway.
+ #
+ # -exarkun
+ defaultEncoding = sys.getdefaultencoding()
+
+ # Common check function
+ def argChecker(arg):
+ """
+ Return either a str or None. If the given value is not
+ allowable for some reason, None is returned. Otherwise, a
+ possibly different object which should be used in place of arg
+ is returned. This forces unicode encoding to happen now, rather
+ than implicitly later.
+ """
+ if isinstance(arg, unicode):
+ try:
+ arg = arg.encode(defaultEncoding)
+ except UnicodeEncodeError:
+ return None
+ warnings.warn(
+ "Argument strings and environment keys/values passed to "
+ "reactor.spawnProcess should be str, not unicode.",
+ category=DeprecationWarning,
+ stacklevel=4)
+ if isinstance(arg, str) and '\0' not in arg:
+ return arg
+ return None
+
+ # Make a few tests to check input validity
+ if not isinstance(args, (tuple, list)):
+ raise TypeError("Arguments must be a tuple or list")
+
+ outputArgs = []
+ for arg in args:
+ arg = argChecker(arg)
+ if arg is None:
+ raise TypeError("Arguments contain a non-string value")
+ else:
+ outputArgs.append(arg)
+
+ outputEnv = None
+ if env is not None:
+ outputEnv = {}
+ for key, val in env.iteritems():
+ key = argChecker(key)
+ if key is None:
+ raise TypeError("Environment contains a non-string key")
+ val = argChecker(val)
+ if val is None:
+ raise TypeError("Environment contains a non-string value")
+ outputEnv[key] = val
+ return outputArgs, outputEnv
+
+ # IReactorThreads
+ if platform.supportsThreads():
+ threadpool = None
+ # ID of the trigger starting the threadpool
+ _threadpoolStartupID = None
+ # ID of the trigger stopping the threadpool
+ threadpoolShutdownID = None
+
+ def _initThreads(self):
+ self.usingThreads = True
+ self.resolver = ThreadedResolver(self)
+ self.installWaker()
+
+ def callFromThread(self, f, *args, **kw):
+ """
+ See L{twisted.internet.interfaces.IReactorThreads.callFromThread}.
+ """
+ assert callable(f), "%s is not callable" % (f,)
+ # lists are thread-safe in CPython, but not in Jython
+ # this is probably a bug in Jython, but until fixed this code
+ # won't work in Jython.
+ self.threadCallQueue.append((f, args, kw))
+ self.wakeUp()
+
+ def _initThreadPool(self):
+ """
+ Create the threadpool accessible with callFromThread.
+ """
+ from twisted.python import threadpool
+ self.threadpool = threadpool.ThreadPool(
+ 0, 10, 'twisted.internet.reactor')
+ self._threadpoolStartupID = self.callWhenRunning(
+ self.threadpool.start)
+ self.threadpoolShutdownID = self.addSystemEventTrigger(
+ 'during', 'shutdown', self._stopThreadPool)
+
+ def _stopThreadPool(self):
+ """
+ Stop the reactor threadpool. This method is only valid if there
+ is currently a threadpool (created by L{_initThreadPool}). It
+ is not intended to be called directly; instead, it will be
+ called by a shutdown trigger created in L{_initThreadPool}.
+ """
+ triggers = [self._threadpoolStartupID, self.threadpoolShutdownID]
+ for trigger in filter(None, triggers):
+ try:
+ self.removeSystemEventTrigger(trigger)
+ except ValueError:
+ pass
+ self._threadpoolStartupID = None
+ self.threadpoolShutdownID = None
+ self.threadpool.stop()
+ self.threadpool = None
+
+
+ def getThreadPool(self):
+ """
+ See L{twisted.internet.interfaces.IReactorThreads.getThreadPool}.
+ """
+ if self.threadpool is None:
+ self._initThreadPool()
+ return self.threadpool
+
+
+ def callInThread(self, _callable, *args, **kwargs):
+ """
+ See L{twisted.internet.interfaces.IReactorThreads.callInThread}.
+ """
+ self.getThreadPool().callInThread(_callable, *args, **kwargs)
+
+ def suggestThreadPoolSize(self, size):
+ """
+ See L{twisted.internet.interfaces.IReactorThreads.suggestThreadPoolSize}.
+ """
+ self.getThreadPool().adjustPoolsize(maxthreads=size)
+ else:
+ # This is for signal handlers.
+ def callFromThread(self, f, *args, **kw):
+ assert callable(f), "%s is not callable" % (f,)
+ # See comment in the other callFromThread implementation.
+ self.threadCallQueue.append((f, args, kw))
+
+if platform.supportsThreads():
+ classImplements(ReactorBase, IReactorThreads)
+
+
+class BaseConnector(styles.Ephemeral):
+ """Basic implementation of connector.
+
+ State can be: "connecting", "connected", "disconnected"
+ """
+
+ implements(IConnector)
+
+ timeoutID = None
+ factoryStarted = 0
+
+ def __init__(self, factory, timeout, reactor):
+ self.state = "disconnected"
+ self.reactor = reactor
+ self.factory = factory
+ self.timeout = timeout
+
+ def disconnect(self):
+ """Disconnect whatever our state is."""
+ if self.state == 'connecting':
+ self.stopConnecting()
+ elif self.state == 'connected':
+ self.transport.loseConnection()
+
+ def connect(self):
+ """Start connection to remote server."""
+ if self.state != "disconnected":
+ raise RuntimeError, "can't connect in this state"
+
+ self.state = "connecting"
+ if not self.factoryStarted:
+ self.factory.doStart()
+ self.factoryStarted = 1
+ self.transport = transport = self._makeTransport()
+ if self.timeout is not None:
+ self.timeoutID = self.reactor.callLater(self.timeout, transport.failIfNotConnected, error.TimeoutError())
+ self.factory.startedConnecting(self)
+
+ def stopConnecting(self):
+ """Stop attempting to connect."""
+ if self.state != "connecting":
+ raise error.NotConnectingError, "we're not trying to connect"
+
+ self.state = "disconnected"
+ self.transport.failIfNotConnected(error.UserError())
+ del self.transport
+
+ def cancelTimeout(self):
+ if self.timeoutID is not None:
+ try:
+ self.timeoutID.cancel()
+ except ValueError:
+ pass
+ del self.timeoutID
+
+ def buildProtocol(self, addr):
+ self.state = "connected"
+ self.cancelTimeout()
+ return self.factory.buildProtocol(addr)
+
+ def connectionFailed(self, reason):
+ self.cancelTimeout()
+ self.transport = None
+ self.state = "disconnected"
+ self.factory.clientConnectionFailed(self, reason)
+ if self.state == "disconnected":
+ # factory hasn't called our connect() method
+ self.factory.doStop()
+ self.factoryStarted = 0
+
+ def connectionLost(self, reason):
+ self.state = "disconnected"
+ self.factory.clientConnectionLost(self, reason)
+ if self.state == "disconnected":
+ # factory hasn't called our connect() method
+ self.factory.doStop()
+ self.factoryStarted = 0
+
+ def getDestination(self):
+ raise NotImplementedError(
+ reflect.qual(self.__class__) + " did not implement "
+ "getDestination")
+
+
+
+class BasePort(abstract.FileDescriptor):
+ """Basic implementation of a ListeningPort.
+
+ Note: This does not actually implement IListeningPort.
+ """
+
+ addressFamily = None
+ socketType = None
+
+ def createInternetSocket(self):
+ s = socket.socket(self.addressFamily, self.socketType)
+ s.setblocking(0)
+ fdesc._setCloseOnExec(s.fileno())
+ return s
+
+
+ def doWrite(self):
+ """Raises a RuntimeError"""
+ raise RuntimeError, "doWrite called on a %s" % reflect.qual(self.__class__)
+
+
+
+class _SignalReactorMixin:
+ """
+ Private mixin to manage signals: it installs signal handlers at start time,
+ and define run method.
+
+ It can only be used mixed in with L{ReactorBase}, and has to be defined
+ first in the inheritance (so that method resolution order finds
+ startRunning first).
+
+ @type _installSignalHandlers: C{bool}
+ @ivar _installSignalHandlers: A flag which indicates whether any signal
+ handlers will be installed during startup. This includes handlers for
+ SIGCHLD to monitor child processes, and SIGINT, SIGTERM, and SIGBREAK
+ to stop the reactor.
+ """
+
+ _installSignalHandlers = False
+
+ def _handleSignals(self):
+ """
+ Install the signal handlers for the Twisted event loop.
+ """
+ try:
+ import signal
+ except ImportError:
+ log.msg("Warning: signal module unavailable -- "
+ "not installing signal handlers.")
+ return
+
+ if signal.getsignal(signal.SIGINT) == signal.default_int_handler:
+ # only handle if there isn't already a handler, e.g. for Pdb.
+ signal.signal(signal.SIGINT, self.sigInt)
+ signal.signal(signal.SIGTERM, self.sigTerm)
+
+ # Catch Ctrl-Break in windows
+ if hasattr(signal, "SIGBREAK"):
+ signal.signal(signal.SIGBREAK, self.sigBreak)
+
+ if platformType == 'posix':
+ signal.signal(signal.SIGCHLD, self._handleSigchld)
+ # Also call the signal handler right now, in case we missed any
+ # signals before we installed it. This should only happen if
+ # someone used spawnProcess before calling reactor.run (and the
+ # process also exited already).
+ self._handleSigchld(signal.SIGCHLD, None)
+
+
+ def _handleSigchld(self, signum, frame, _threadSupport=platform.supportsThreads()):
+ """
+ Reap all processes on SIGCHLD.
+
+ This gets called on SIGCHLD. We do no processing inside a signal
+ handler, as the calls we make here could occur between any two
+ python bytecode instructions. Deferring processing to the next
+ eventloop round prevents us from violating the state constraints
+ of arbitrary classes.
+ """
+ from twisted.internet.process import reapAllProcesses
+ if _threadSupport:
+ self.callFromThread(reapAllProcesses)
+ else:
+ self.callLater(0, reapAllProcesses)
+
+
+ def startRunning(self, installSignalHandlers=True):
+ """
+ Extend the base implementation in order to remember whether signal
+ handlers should be installed later.
+
+ @type installSignalHandlers: C{bool}
+ @param installSignalHandlers: A flag which, if set, indicates that
+ handlers for a number of (implementation-defined) signals should be
+ installed during startup.
+ """
+ self._installSignalHandlers = installSignalHandlers
+ ReactorBase.startRunning(self)
+
+
+ def _reallyStartRunning(self):
+ """
+ Extend the base implementation by also installing signal handlers, if
+ C{self._installSignalHandlers} is true.
+ """
+ ReactorBase._reallyStartRunning(self)
+ if self._installSignalHandlers:
+ # Make sure this happens before after-startup events, since the
+ # expectation of after-startup is that the reactor is fully
+ # initialized. Don't do it right away for historical reasons
+ # (perhaps some before-startup triggers don't want there to be a
+ # custom SIGCHLD handler so that they can run child processes with
+ # some blocking api).
+ self._handleSignals()
+
+
+ def run(self, installSignalHandlers=True):
+ self.startRunning(installSignalHandlers=installSignalHandlers)
+ self.mainLoop()
+
+
+ def mainLoop(self):
+ while self._started:
+ try:
+ while self._started:
+ # Advance simulation time in delayed event
+ # processors.
+ self.runUntilCurrent()
+ t2 = self.timeout()
+ t = self.running and t2
+ self.doIteration(t)
+ except:
+ log.msg("Unexpected error in main loop.")
+ log.err()
+ else:
+ log.msg('Main loop terminated.')
+
+
+
+__all__ = []
diff --git a/vendor/Twisted-10.0.0/twisted/internet/cfreactor.py b/vendor/Twisted-10.0.0/twisted/internet/cfreactor.py
new file mode 100644
index 0000000000..00dcc4cb74
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/cfreactor.py
@@ -0,0 +1,342 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+This module provides support for Twisted to interact with CoreFoundation
+CFRunLoops. This includes Cocoa's NSRunLoop.
+
+In order to use this support, simply do the following::
+
+ | from twisted.internet import cfreactor
+ | cfreactor.install()
+
+Then use the twisted.internet APIs as usual. The other methods here are not
+intended to be called directly under normal use. However, install can take
+a runLoop kwarg, and run will take a withRunLoop arg if you need to explicitly
+pass a CFRunLoop for some reason. Otherwise it will make a pretty good guess
+as to which runLoop you want (the current NSRunLoop if PyObjC is imported,
+otherwise the current CFRunLoop. Either way, if one doesn't exist, it will
+be created).
+
+Maintainer: Bob Ippolito
+"""
+
+__all__ = ['install']
+
+import sys
+
+# hints for py2app
+import Carbon.CF
+import traceback
+
+import cfsupport as cf
+
+from zope.interface import implements
+
+from twisted.python import log, threadable, failure
+from twisted.internet.interfaces import IReactorFDSet
+from twisted.internet import posixbase, error
+from weakref import WeakKeyDictionary
+from Foundation import NSRunLoop
+from AppKit import NSApp
+
+# cache two extremely common "failures" without traceback info
+_faildict = {
+ error.ConnectionDone: failure.Failure(error.ConnectionDone()),
+ error.ConnectionLost: failure.Failure(error.ConnectionLost()),
+}
+
+class SelectableSocketWrapper(object):
+ _objCache = WeakKeyDictionary()
+
+ cf = None
+ def socketWrapperForReactorAndObject(klass, reactor, obj):
+ _objCache = klass._objCache
+ if obj in _objCache:
+ return _objCache[obj]
+ v = _objCache[obj] = klass(reactor, obj)
+ return v
+ socketWrapperForReactorAndObject = classmethod(socketWrapperForReactorAndObject)
+
+ def __init__(self, reactor, obj):
+ if self.cf:
+ raise ValueError, "This socket wrapper is already initialized"
+ self.reactor = reactor
+ self.obj = obj
+ obj._orig_ssw_connectionLost = obj.connectionLost
+ obj.connectionLost = self.objConnectionLost
+ self.fd = obj.fileno()
+ self.writing = False
+ self.reading = False
+ self.wouldRead = False
+ self.wouldWrite = False
+ self.cf = cf.PyCFSocket(obj.fileno(), self.doRead, self.doWrite, self.doConnect)
+ self.cf.stopWriting()
+ reactor.getRunLoop().addSocket(self.cf)
+
+ def __repr__(self):
+ return 'SSW(fd=%r r=%r w=%r x=%08x o=%08x)' % (self.fd, int(self.reading), int(self.writing), id(self), id(self.obj))
+
+ def objConnectionLost(self, *args, **kwargs):
+ obj = self.obj
+ self.reactor.removeReader(obj)
+ self.reactor.removeWriter(obj)
+ obj.connectionLost = obj._orig_ssw_connectionLost
+ obj.connectionLost(*args, **kwargs)
+ try:
+ del self._objCache[obj]
+ except:
+ pass
+ self.obj = None
+ self.cf = None
+
+ def doConnect(self, why):
+ pass
+
+ def startReading(self):
+ self.cf.startReading()
+ self.reading = True
+ if self.wouldRead:
+ if not self.reactor.running:
+ self.reactor.callLater(0, self.doRead)
+ else:
+ self.doRead()
+ self.wouldRead = False
+ return self
+
+ def stopReading(self):
+ self.cf.stopReading()
+ self.reading = False
+ self.wouldRead = False
+ return self
+
+ def startWriting(self):
+ self.cf.startWriting()
+ self.writing = True
+ if self.wouldWrite:
+ if not self.reactor.running:
+ self.reactor.callLater(0, self.doWrite)
+ else:
+ self.doWrite()
+ self.wouldWrite = False
+ return self
+
+ def stopWriting(self):
+ self.cf.stopWriting()
+ self.writing = False
+ self.wouldWrite = False
+
+ def _finishReadOrWrite(self, fn, faildict=_faildict):
+ try:
+ why = fn()
+ except:
+ why = sys.exc_info()[1]
+ log.err()
+ if why:
+ try:
+ f = faildict.get(why.__class__) or failure.Failure(why)
+ self.objConnectionLost(f)
+ except:
+ log.err()
+ if self.reactor.running:
+ self.reactor.simulate()
+
+ def doRead(self):
+ obj = self.obj
+ if not obj:
+ return
+ if not self.reading:
+ self.wouldRead = True
+ if self.reactor.running:
+ self.reactor.simulate()
+ return
+ self._finishReadOrWrite(obj.doRead)
+
+ def doWrite(self):
+ obj = self.obj
+ if not obj:
+ return
+ if not self.writing:
+ self.wouldWrite = True
+ if self.reactor.running:
+ self.reactor.simulate()
+ return
+ self._finishReadOrWrite(obj.doWrite)
+
+ def __hash__(self):
+ return hash(self.fd)
+
+class CFReactor(posixbase.PosixReactorBase):
+ implements(IReactorFDSet)
+ # how long to poll if we're don't care about signals
+ longIntervalOfTime = 60.0
+
+ # how long we should poll if we do care about signals
+ shortIntervalOfTime = 1.0
+
+ # don't set this
+ pollInterval = longIntervalOfTime
+
+ def __init__(self, runLoop=None):
+ self.readers = {}
+ self.writers = {}
+ self.running = 0
+ self.crashing = False
+ self._doRunUntilCurrent = True
+ self.timer = None
+ self.runLoop = None
+ self.nsRunLoop = None
+ self.didStartRunLoop = False
+ if runLoop is not None:
+ self.getRunLoop(runLoop)
+ posixbase.PosixReactorBase.__init__(self)
+
+ def getRunLoop(self, runLoop=None):
+ if self.runLoop is None:
+ self.nsRunLoop = runLoop or NSRunLoop.currentRunLoop()
+ self.runLoop = cf.PyCFRunLoop(self.nsRunLoop.getCFRunLoop())
+ return self.runLoop
+
+ def addReader(self, reader):
+ self.readers[reader] = SelectableSocketWrapper.socketWrapperForReactorAndObject(self, reader).startReading()
+
+ def addWriter(self, writer):
+ self.writers[writer] = SelectableSocketWrapper.socketWrapperForReactorAndObject(self, writer).startWriting()
+
+ def removeReader(self, reader):
+ wrapped = self.readers.get(reader, None)
+ if wrapped is not None:
+ del self.readers[reader]
+ wrapped.stopReading()
+
+ def removeWriter(self, writer):
+ wrapped = self.writers.get(writer, None)
+ if wrapped is not None:
+ del self.writers[writer]
+ wrapped.stopWriting()
+
+
+ def getReaders(self):
+ return self.readers.keys()
+
+
+ def getWriters(self):
+ return self.writers.keys()
+
+
+ def removeAll(self):
+ r = self.readers.keys()
+ for s in self.readers.itervalues():
+ s.stopReading()
+ for s in self.writers.itervalues():
+ s.stopWriting()
+ self.readers.clear()
+ self.writers.clear()
+ return r
+
+ def run(self, installSignalHandlers=1, withRunLoop=None):
+ if self.running:
+ raise ValueError, "Reactor already running"
+ if installSignalHandlers:
+ self.pollInterval = self.shortIntervalOfTime
+ runLoop = self.getRunLoop(withRunLoop)
+ self._startup()
+
+ self.startRunning(installSignalHandlers=installSignalHandlers)
+
+ self.running = True
+ if NSApp() is None and self.nsRunLoop.currentMode() is None:
+ # Most of the time the NSRunLoop will have already started,
+ # but in this case it wasn't.
+ runLoop.run()
+ self.crashing = False
+ self.didStartRunLoop = True
+
+ def callLater(self, howlong, *args, **kwargs):
+ rval = posixbase.PosixReactorBase.callLater(self, howlong, *args, **kwargs)
+ if self.timer:
+ timeout = self.timeout()
+ if timeout is None:
+ timeout = howlong
+ sleepUntil = cf.now() + min(timeout, howlong)
+ if sleepUntil < self.timer.getNextFireDate():
+ self.timer.setNextFireDate(sleepUntil)
+ else:
+ pass
+ return rval
+
+ def iterate(self, howlong=0.0):
+ if self.running:
+ raise ValueError, "Can't iterate a running reactor"
+ self.runUntilCurrent()
+ self.doIteration(howlong)
+
+ def doIteration(self, howlong):
+ if self.running:
+ raise ValueError, "Can't iterate a running reactor"
+ howlong = howlong or 0.01
+ pi = self.pollInterval
+ self.pollInterval = howlong
+ self._doRunUntilCurrent = False
+ self.run()
+ self._doRunUntilCurrent = True
+ self.pollInterval = pi
+
+ def simulate(self):
+ if self.crashing:
+ return
+ if not self.running:
+ raise ValueError, "You can't simulate a stopped reactor"
+ if self._doRunUntilCurrent:
+ self.runUntilCurrent()
+ if self.crashing:
+ return
+ if self.timer is None:
+ return
+ nap = self.timeout()
+ if nap is None:
+ nap = self.pollInterval
+ else:
+ nap = min(self.pollInterval, nap)
+ if self.running:
+ self.timer.setNextFireDate(cf.now() + nap)
+ if not self._doRunUntilCurrent:
+ self.crash()
+
+ def _startup(self):
+ if self.running:
+ raise ValueError, "Can't bootstrap a running reactor"
+ self.timer = cf.PyCFRunLoopTimer(cf.now(), self.pollInterval, self.simulate)
+ self.runLoop.addTimer(self.timer)
+
+ def cleanup(self):
+ pass
+
+ def sigInt(self, *args):
+ self.callLater(0.0, self.stop)
+
+ def crash(self):
+ if not self.running:
+ raise ValueError, "Can't crash a stopped reactor"
+ posixbase.PosixReactorBase.crash(self)
+ self.crashing = True
+ if self.timer is not None:
+ self.runLoop.removeTimer(self.timer)
+ self.timer = None
+ if self.didStartRunLoop:
+ self.runLoop.stop()
+
+ def stop(self):
+ if not self.running:
+ raise ValueError, "Can't stop a stopped reactor"
+ posixbase.PosixReactorBase.stop(self)
+
+def install(runLoop=None):
+ """Configure the twisted mainloop to be run inside CFRunLoop.
+ """
+ reactor = CFReactor(runLoop=runLoop)
+ reactor.addSystemEventTrigger('after', 'shutdown', reactor.cleanup)
+ from twisted.internet.main import installReactor
+ installReactor(reactor)
+ return reactor
diff --git a/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfdate.pxi b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfdate.pxi
new file mode 100644
index 0000000000..3a773d7eb8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfdate.pxi
@@ -0,0 +1,2 @@
+def now():
+ return CFAbsoluteTimeGetCurrent()
diff --git a/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfdecl.pxi b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfdecl.pxi
new file mode 100644
index 0000000000..7586001b16
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfdecl.pxi
@@ -0,0 +1,227 @@
+cdef extern from "pymactoolbox.h":
+ # CFBase
+ ctypedef void *CFTypeRef
+ ctypedef CFTypeRef CFAllocatorRef
+ ctypedef CFTypeRef CFStringRef
+ ctypedef CFStringRef CFMutableStringRef
+ ctypedef CFTypeRef CFDataRef
+ ctypedef CFDataRef CFMutableDataRef
+ ctypedef CFTypeRef CFArrayRef
+ ctypedef CFArrayRef CFMutableArrayRef
+ ctypedef CFTypeRef CFDictionaryRef
+ ctypedef CFDictionaryRef CFMutableDictionaryRef
+ ctypedef CFTypeRef CFURLRef
+
+ ctypedef unsigned short UniChar
+ ctypedef int CFIndex
+ ctypedef int CFOptionFlags
+ ctypedef int Boolean
+
+ ctypedef struct CFRange:
+ CFIndex location
+ CFIndex length
+
+ cdef extern CFAllocatorRef kCFAllocatorDefault
+ cdef extern CFAllocatorRef kCFAllocatorNull
+
+ cdef CFTypeRef CFRetain(CFTypeRef cf)
+ cdef void CFRelease(CFTypeRef cf)
+ cdef CFIndex CFGetRetainCount(CFTypeRef cf)
+ cdef CFStringRef CFCopyDescription(CFTypeRef cf)
+ cdef CFRange CFRangeMake(CFIndex location, CFIndex length)
+
+ # CFData
+ cdef CFDataRef CFDataCreate(CFAllocatorRef allocator, unsigned char *bytes, CFIndex length)
+ cdef CFDataRef CFDataCreateWithBytesNoCopy(CFAllocatorRef, unsigned char *bytes, CFIndexLength, CFAllocatorRef bytesDeallocator)
+ cdef CFMutableDataRef CFDataCreateMutable(CFAllocatorRef allocator, CFIndex capacity)
+ cdef CFMutableDataRef CFDataCreateMutableCopy(CFAllocatorRef allocator, CFIndex capacity, CFDataRef theData)
+ cdef CFIndex CFDataGetLength(CFDataRef theData)
+ cdef unsigned char *CFDataGetBytePtr(CFDataRef theData)
+ cdef unsigned char *CFDataGetMutableBytePtr(CFMutableDataRef theData)
+ cdef CFDataGetBytes(CFDataRef theData, CFRange range, unsigned char *buffer)
+ cdef CFDataSetLength(CFMutableDataRef theData, CFIndex length)
+ cdef CFDataIncreaseLength(CFMutableDataRef theData, CFIndex extraLength)
+ cdef CFDataAppendBytes(CFMutableDataRef theData, unsigned char *bytes, CFIndex length)
+ cdef CFDataReplaceBytes(CFMutableDataRef theData, CFRange range, unsigned char *newBytes, CFIndex newLength)
+ cdef CFDataDeleteBytes(CFMutableDataRef theData, CFRange range)
+
+
+ # CFString
+
+ ctypedef enum CFStringBuiltInEncodings:
+ kCFStringEncodingMacRoman = 0
+ kCFStringEncodingWindowsLatin1 = 0x0500
+ kCFStringEncodingISOLatin1 = 0x0201
+ kCFStringEncodingNextStepLatin = 0x0B01
+ kCFStringEncodingASCII = 0x0600
+ kCFStringEncodingUnicode = 0x0100
+ kCFStringEncodingUTF8 = 0x08000100
+ kCFStringEncodingNonLossyASCII = 0x0BFF
+ kCFStringEncodingInvalidId = 0xffffffff
+ ctypedef unsigned int CFStringEncoding
+
+ cdef void CFStringGetCharacters(CFStringRef theString, CFRange range, UniChar *ubuffer)
+ cdef CFIndex CFStringGetLength(CFStringRef theString)
+ cdef CFStringRef CFStringCreateWithCString(CFAllocatorRef alloc, char *cStr, CFStringEncoding encoding)
+ cdef CFStringRef CFStringCreateWithCharacters(CFAllocatorRef alloc, UniChar *chars, CFIndex numChars)
+
+ # CFArray
+
+ cdef CFIndex CFArrayGetCount(CFArrayRef theArray)
+ cdef void *CFArrayGetValueAtIndex(CFArrayRef theArray, CFIndex idx)
+ cdef void CFArrayGetValues(CFArrayRef theArray, CFRange range, void **values)
+
+ # CFDate
+ ctypedef double CFTimeInterval
+ ctypedef CFTimeInterval CFAbsoluteTime
+
+ cdef CFAbsoluteTime CFAbsoluteTimeGetCurrent()
+
+ # CFRunLoop
+ ctypedef struct CFRunLoopTimerContext:
+ CFIndex version
+ void* info
+ void *(*retain)(void *info)
+ void (*release)(void *info)
+ CFStringRef (*copyDescription)(void *info)
+
+ ctypedef CFTypeRef CFRunLoopRef
+ ctypedef CFTypeRef CFRunLoopTimerRef
+ ctypedef CFTypeRef CFRunLoopSourceRef
+ ctypedef void (*CFRunLoopTimerCallBack)(CFRunLoopTimerRef timer, void *info)
+
+ cdef extern CFStringRef kCFRunLoopCommonModes
+
+ cdef CFRunLoopRef CFRunLoopGetCurrent()
+ cdef void CFRunLoopRun()
+ cdef void CFRunLoopStop(CFRunLoopRef rl)
+ cdef void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
+ cdef void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
+ cdef CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl)
+
+ cdef void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
+ cdef void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
+
+ cdef CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, CFRunLoopTimerCallBack callout, CFRunLoopTimerContext *context)
+ cdef CFAbsoluteTime CFRunLoopTimerGetNextFireDate(CFRunLoopTimerRef timer)
+ cdef void CFRunLoopTimerSetNextFireDate(CFRunLoopTimerRef timer, CFAbsoluteTime fireDate)
+ cdef void CFRunLoopTimerInvalidate(CFRunLoopTimerRef timer)
+
+ # CFSocket
+ enum kCFSocketFlags:
+ kCFSocketAutomaticallyReenableReadCallBack = 1
+ kCFSocketAutomaticallyReenableAcceptCallBack = 2
+ kCFSocketAutomaticallyReenableDataCallBack = 3
+ kCFSocketAutomaticallyReenableWriteCallBack = 8
+ kCFSocketCloseOnInvalidate = 128
+
+ ctypedef enum CFSocketCallBackType:
+ kCFSocketNoCallBack = 0
+ kCFSocketReadCallBack = 1
+ kCFSocketAcceptCallBack = 2
+ kCFSocketDataCallBack = 3
+ kCFSocketConnectCallBack = 4
+ kCFSocketWriteCallBack = 8
+
+ ctypedef struct CFSocketContext:
+ CFIndex version
+ void *info
+ void *(*retain)(void *info)
+ void (*release)(void *info)
+ CFStringRef (*copyDescription)(void *info)
+
+ ctypedef int CFSocketNativeHandle
+ ctypedef void *CFSocketRef
+ ctypedef void (*CFSocketCallBack)(CFSocketRef s, CFSocketCallBackType _type, CFDataRef address, void *data, void *info)
+
+ cdef CFSocketRef CFSocketCreateWithNative(CFAllocatorRef allocator, CFSocketNativeHandle sock, CFOptionFlags callbackTypes, CFSocketCallBack callout, CFSocketContext* context)
+ cdef CFSocketNativeHandle CFSocketGetNative(CFSocketRef s)
+ cdef CFRunLoopSourceRef CFSocketCreateRunLoopSource(CFAllocatorRef allocator, CFSocketRef s, CFIndex order)
+ cdef void CFSocketEnableCallBacks(CFSocketRef s, CFOptionFlags callBackTypes)
+ cdef void CFSocketDisableCallBacks(CFSocketRef s, CFOptionFlags callBackTypes)
+ cdef CFOptionFlags CFSocketGetSocketFlags(CFSocketRef s)
+ cdef void CFSocketSetSocketFlags(CFSocketRef s, CFOptionFlags socketFlags)
+ cdef void CFSocketInvalidate(CFSocketRef s)
+
+ # CFStream
+ ctypedef enum CFStreamErrorDomain:
+ kCFStreamErrorDomainCustom = -1
+ kCFStreamErrorDomainPOSIX = 1
+ kCFStreamErrorDomainMacOSStatus = 2
+
+ ctypedef struct CFStreamError:
+ CFStreamErrorDomain domain
+ int error
+
+ cdef object CFObj_New(CFTypeRef)
+ cdef int CFObj_Convert(object, CFTypeRef *)
+ cdef object CFTypeRefObj_New(CFTypeRef)
+ cdef int CFTypeRefObj_Convert(object, CFTypeRef *)
+ cdef object CFStringRefObj_New(CFStringRef)
+ cdef int CFStringRefObj_Convert(object, CFStringRef *)
+ cdef object CFMutableStringRefObj_New(CFMutableStringRef)
+ cdef int CFMutableStringRefObj_Convert(object, CFMutableStringRef *)
+ cdef object CFArrayRefObj_New(CFArrayRef)
+ cdef int CFArrayRefObj_Convert(object, CFArrayRef *)
+ cdef object CFMutableArrayRefObj_New(CFMutableArrayRef)
+ cdef int CFMutableArrayRefObj_Convert(object, CFMutableArrayRef *)
+ cdef object CFDictionaryRefObj_New(CFDictionaryRef)
+ cdef int CFDictionaryRefObj_Convert(object, CFDictionaryRef *)
+ cdef object CFMutableDictionaryRefObj_New(CFMutableDictionaryRef)
+ cdef int CFMutableDictionaryRefObj_Convert(object, CFMutableDictionaryRef *)
+ cdef object CFURLRefObj_New(CFURLRef)
+ cdef int CFURLRefObj_Convert(object, CFURLRef *)
+ cdef int OptionalCFURLRefObj_Convert(object, CFURLRef *)
+
+ # CFNetwork
+ ctypedef struct CFNetServiceClientContext:
+ CFIndex version
+ void *info
+ void *(*retain)(void *info)
+ void (*release)(void *info)
+ CFStringRef (*copyDescription)(void *info)
+
+ ctypedef enum CFNetServiceBrowserClientCallBackFlags:
+ kCFNetServiceFlagMoreComing = 1
+ kCFNetServiceFlagIsDomain = 2
+ kCFNetServiceFlagIsRegistrationDomain = 4
+ kCFNetServiceFlagRemove = 8
+
+ ctypedef CFTypeRef CFNetServiceBrowserRef
+ ctypedef CFTypeRef CFNetServiceRef
+ ctypedef void (*CFNetServiceBrowserClientCallBack)(CFNetServiceBrowserRef browser, CFOptionFlags flags, CFTypeRef domainOrService, CFStreamError* error, void* info)
+ ctypedef void (*CFNetServiceClientCallBack)(CFNetServiceRef theService, CFStreamError* error, void* info)
+
+ cdef CFNetServiceBrowserRef CFNetServiceBrowserCreate(CFAllocatorRef alloc, CFNetServiceBrowserClientCallBack clientCB, CFNetServiceClientContext* clientContext)
+ cdef void CFNetworkServiceBrowserInvalidate(CFNetServiceBrowserRef browser)
+ cdef void CFNetServiceBrowserScheduleWithRunLoop(CFNetServiceBrowserRef browser, CFRunLoopRef runLoop, CFStringRef runLoopMode)
+ cdef Boolean CFNetServiceBrowserSearchForDomains(CFNetServiceBrowserRef browser, Boolean registrationDomain, CFStreamError* error)
+ cdef Boolean CFNetServiceBrowserSearchForServices(CFNetServiceBrowserRef browser, CFStringRef domain, CFStringRef type, CFStreamError* error)
+ cdef void CFNetServiceBrowserStopSearch(CFNetServiceBrowserRef browser, CFStreamError* error)
+
+ # Call this function to shut down a browser that is running asynchronously.
+ # To complete the shutdown, call CFNetServiceBrowserInvalidate followed by CFNetServiceBrowserStopSearch.
+ cdef void CFNetServiceBrowserUnscheduleFromRunLoop(CFNetServiceBrowserRef browser, CFRunLoopRef runLoop, CFStringRef runLoopMode)
+
+ cdef void CFNetServiceCancel(CFNetServiceRef theService)
+ cdef CFNetServiceRef CFNetServiceCreate(CFAllocatorRef alloc, CFStringRef domain, CFStringRef type, CFStringRef name, unsigned int port)
+
+ cdef CFArrayRef CFNetServiceGetAddressing(CFNetServiceRef theService)
+ cdef CFStringRef CFNetServiceGetDomain(CFNetServiceRef theService)
+ cdef CFStringRef CFNetServiceGetName(CFNetServiceRef theService)
+ cdef CFStringRef CFNetServiceGetProtocolSpecificInformation(CFNetServiceRef theService)
+ cdef CFStringRef CFNetServiceGetType(CFNetServiceRef theService)
+ cdef Boolean CFNetServiceRegister(CFNetServiceRef theService, CFStreamError* error)
+ cdef Boolean CFNetServiceResolve(CFNetServiceRef theService, CFStreamError* error)
+ cdef void CFNetServiceScheduleWithRunLoop(CFNetServiceRef theService, CFRunLoopRef runLoop, CFStringRef runLoopMode)
+
+ # For CFNetServices that will operate asynchronously, call this function and then call CFNetServiceScheduleWithRunLoop to schedule the service on a run loop.
+ # Then call CFNetServiceRegister or CFNetServiceResolve
+ cdef Boolean CFNetServiceSetClient(CFNetServiceRef theService, CFNetServiceClientCallBack clientCB, CFNetServiceClientContext* clientContext)
+
+ cdef void CFNetServiceSetProtocolSpecificInformation(CFNetServiceRef theService, CFStringRef theInfo)
+
+ # Unschedules the specified service from the specified run loop and mode.
+ # Call this function to shut down a service that is running asynchronously.
+ # To complete the shutdown, call CFNetServiceSetClient and set clientCB to NULL. Then call CFNetServiceCancel.
+ cdef void CFNetServiceUnscheduleFromRunLoop(CFNetServiceRef theService, CFRunLoopRef runLoop, CFStringRef runLoopMode)
diff --git a/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfrunloop.pxi b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfrunloop.pxi
new file mode 100644
index 0000000000..8cd40a8946
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfrunloop.pxi
@@ -0,0 +1,104 @@
+import traceback
+
+# possibly figure out how to subclass these or something
+
+cdef class PyCFRunLoopTimer:
+ cdef CFRunLoopTimerRef cf
+ cdef public object callout
+ cdef CFRunLoopTimerContext context
+
+ def __new__(self, double fireDate, double interval, callout):
+ self.callout = callout
+ self.context.version = 0
+ self.context.info = <void *>self
+ self.context.retain = NULL
+ self.context.release = NULL
+ self.context.copyDescription = NULL
+ self.cf = CFRunLoopTimerCreate(kCFAllocatorDefault, fireDate, interval, 0, 0, <CFRunLoopTimerCallBack>&gilRunLoopTimerCallBack, &self.context)
+ if self.cf == NULL:
+ raise ValueError("Invalid Socket")
+
+ def getNextFireDate(self):
+ return CFRunLoopTimerGetNextFireDate(self.cf)
+
+ def setNextFireDate(self, double fireDate):
+ CFRunLoopTimerSetNextFireDate(self.cf, fireDate)
+
+ def invalidate(self):
+ CFRunLoopTimerInvalidate(self.cf)
+
+ def __dealloc__(self):
+ if self.cf != NULL:
+ CFRelease(self.cf)
+
+cdef void runLoopTimerCallBack(CFRunLoopTimerRef timer, void *info):
+ cdef PyCFRunLoopTimer obj
+ obj = <PyCFRunLoopTimer>info
+ try:
+ if obj.callout:
+ obj.callout()
+ except:
+ traceback.print_exc()
+
+cdef void gilRunLoopTimerCallBack(CFRunLoopTimerRef timer, void *info):
+ cdef PyGILState_STATE gil
+ gil = PyGILState_Ensure()
+ runLoopTimerCallBack(timer, info)
+ PyGILState_Release(gil)
+
+cdef class PyCFRunLoop:
+ cdef public object cf
+
+ def __new__(self, runLoop=None):
+ cdef CFTypeRef _runLoop
+ if runLoop is None:
+ _runLoop = CFRunLoopGetCurrent()
+ else:
+ if CFObj_Convert(runLoop, &_runLoop) == 0:
+ raise
+ #return -1
+ # this is going to leak a reference
+ self.cf = CFObj_New(CFRetain(_runLoop))
+
+ def run(self):
+ CFRunLoopRun()
+
+ def stop(self):
+ cdef CFTypeRef _runLoop
+ if CFObj_Convert(self.cf, &_runLoop) == 0:
+ raise ValueError, "CFRunLoopReference is invalid"
+ CFRunLoopStop(_runLoop)
+
+ def currentMode(self):
+ cdef CFTypeRef _currentMode
+ cdef CFTypeRef _runLoop
+ if CFObj_Convert(self.cf, &_runLoop) == 0:
+ raise ValueError, "CFRunLoopReference is invalid"
+ _currentMode = CFRunLoopCopyCurrentMode(_runLoop)
+ if _currentMode == NULL:
+ return None
+ return CFObj_New(_currentMode)
+
+ def addSocket(self, PyCFSocket socket not None):
+ cdef CFTypeRef _runLoop
+ if CFObj_Convert(self.cf, &_runLoop) == 0:
+ raise ValueError, "CFRunLoopReference is invalid"
+ CFRunLoopAddSource(_runLoop, socket.source, kCFRunLoopCommonModes)
+
+ def removeSocket(self, PyCFSocket socket not None):
+ cdef CFTypeRef _runLoop
+ if CFObj_Convert(self.cf, &_runLoop) == 0:
+ raise ValueError, "CFRunLoopReference is invalid"
+ CFRunLoopRemoveSource(_runLoop, socket.source, kCFRunLoopCommonModes)
+
+ def addTimer(self, PyCFRunLoopTimer timer not None):
+ cdef CFTypeRef _runLoop
+ if CFObj_Convert(self.cf, &_runLoop) == 0:
+ raise ValueError, "CFRunLoopReference is invalid"
+ CFRunLoopAddTimer(_runLoop, timer.cf, kCFRunLoopCommonModes)
+
+ def removeTimer(self, PyCFRunLoopTimer timer not None):
+ cdef CFTypeRef _runLoop
+ if CFObj_Convert(self.cf, &_runLoop) == 0:
+ raise ValueError, "CFRunLoopReference is invalid"
+ CFRunLoopRemoveTimer(_runLoop, timer.cf, kCFRunLoopCommonModes)
diff --git a/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsocket.pxi b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsocket.pxi
new file mode 100644
index 0000000000..b87cfb6fcc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsocket.pxi
@@ -0,0 +1,111 @@
+import traceback
+
+cdef class PyCFSocket
+
+cdef void socketCallBack(CFSocketRef s, CFSocketCallBackType _type, CFDataRef address, void *data, void *info):
+ cdef PyCFSocket socket
+ cdef int res
+ cdef int mask
+ socket = (<PyCFSocket>info)
+ #print "fileno = %r" % (socket.fileno,)
+ try:
+ if _type == kCFSocketReadCallBack:
+ if socket.readcallback:
+ socket.readcallback()
+ elif _type == kCFSocketWriteCallBack:
+ if socket.writecallback:
+ socket.writecallback()
+ elif _type == kCFSocketConnectCallBack:
+ if data == NULL:
+ res = 0
+ else:
+ res = (<int*>data)[0]
+ if socket.connectcallback:
+ socket.connectcallback(res)
+ except:
+ traceback.print_exc()
+
+cdef void gilSocketCallBack(CFSocketRef s, CFSocketCallBackType _type, CFDataRef address, void *data, void *info):
+ cdef PyGILState_STATE gil
+ gil = PyGILState_Ensure()
+ socketCallBack(s, _type, address, data, info)
+ PyGILState_Release(gil)
+
+cdef class PyCFSocket:
+ cdef public object readcallback
+ cdef public object writecallback
+ cdef public object connectcallback
+ cdef public object reading
+ cdef public object writing
+ cdef CFSocketRef cf
+ cdef CFRunLoopSourceRef source
+ cdef readonly CFSocketNativeHandle fileno
+ cdef CFSocketContext context
+
+ def __new__(self, CFSocketNativeHandle fileno, readcallback=None, writecallback=None, connectcallback=None):
+ #print "new socket %r" % (fileno,)
+ self.fileno = fileno
+ self.readcallback = readcallback
+ self.writecallback = writecallback
+ self.connectcallback = connectcallback
+ self.context.version = 0
+ self.context.info = <void *>self
+ self.context.retain = NULL
+ self.context.release = NULL
+ self.context.copyDescription = NULL
+ self.reading = False
+ self.writing = False
+ self.cf = CFSocketCreateWithNative(kCFAllocatorDefault, fileno, kCFSocketConnectCallBack | kCFSocketReadCallBack | kCFSocketWriteCallBack, <CFSocketCallBack>&gilSocketCallBack, &self.context)
+ if self.cf == NULL:
+ raise ValueError("Invalid Socket")
+ self.source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, self.cf, 10000)
+ if self.source == NULL:
+ raise ValueError("Couldn't create runloop source")
+ #print "made new socket"
+
+ def update(self):
+ cdef int mask
+ cdef int offmask
+ cdef int automask
+ mask = kCFSocketConnectCallBack | kCFSocketAcceptCallBack
+ offmask = 0
+ automask = kCFSocketAutomaticallyReenableAcceptCallBack
+ if self.reading:
+ mask = mask | kCFSocketReadCallBack
+ automask = automask | kCFSocketAutomaticallyReenableReadCallBack
+ else:
+ offmask = offmask | kCFSocketReadCallBack
+ if self.writing:
+ mask = mask | kCFSocketWriteCallBack
+ automask = automask | kCFSocketAutomaticallyReenableWriteCallBack
+ else:
+ offmask = offmask | kCFSocketWriteCallBack
+ CFSocketDisableCallBacks(self.cf, offmask)
+ CFSocketEnableCallBacks(self.cf, mask)
+ CFSocketSetSocketFlags(self.cf, automask)
+
+
+ def startReading(self):
+ self.reading = True
+ self.update()
+
+ def stopReading(self):
+ self.reading = False
+ self.update()
+
+ def startWriting(self):
+ self.writing = True
+ self.update()
+
+ def stopWriting(self):
+ self.writing = False
+ self.update()
+
+ def __dealloc__(self):
+ #print "PyCFSocket(%r).__dealloc__()" % (self.fileno,)
+ if self.source != NULL:
+ CFRelease(self.source)
+ if self.cf != NULL:
+ CFSocketInvalidate(self.cf)
+ CFRelease(self.cf)
+ #print "__dealloc__()"
diff --git a/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsupport.c b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsupport.c
new file mode 100644
index 0000000000..eb1b371cf8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsupport.c
@@ -0,0 +1,2136 @@
+/* Generated by Pyrex 0.9.3 on Sat Mar 12 04:43:18 2005 */
+
+#include "Python.h"
+#include "structmember.h"
+#ifndef PY_LONG_LONG
+ #define PY_LONG_LONG LONG_LONG
+#endif
+#include "pymactoolbox.h"
+
+
+typedef struct {PyObject **p; char *s;} __Pyx_InternTabEntry; /*proto*/
+typedef struct {PyObject **p; char *s; long n;} __Pyx_StringTabEntry; /*proto*/
+static PyObject *__Pyx_UnpackItem(PyObject *, int); /*proto*/
+static int __Pyx_EndUnpack(PyObject *, int); /*proto*/
+static int __Pyx_PrintItem(PyObject *); /*proto*/
+static int __Pyx_PrintNewline(void); /*proto*/
+static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb); /*proto*/
+static void __Pyx_ReRaise(void); /*proto*/
+static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list); /*proto*/
+static PyObject *__Pyx_GetExcValue(void); /*proto*/
+static int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, int none_allowed, char *name); /*proto*/
+static int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type); /*proto*/
+static int __Pyx_GetStarArgs(PyObject **args, PyObject **kwds, char *kwd_list[], int nargs, PyObject **args2, PyObject **kwds2); /*proto*/
+static void __Pyx_WriteUnraisable(char *name); /*proto*/
+static void __Pyx_AddTraceback(char *funcname); /*proto*/
+static PyTypeObject *__Pyx_ImportType(char *module_name, char *class_name, long size); /*proto*/
+static int __Pyx_SetVtable(PyObject *dict, void *vtable); /*proto*/
+static int __Pyx_GetVtable(PyObject *dict, void *vtabptr); /*proto*/
+static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, char *modname); /*proto*/
+static int __Pyx_InternStrings(__Pyx_InternTabEntry *t); /*proto*/
+static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); /*proto*/
+static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name); /*proto*/
+
+static PyObject *__pyx_m;
+static PyObject *__pyx_b;
+static int __pyx_lineno;
+static char *__pyx_filename;
+staticforward char **__pyx_f;
+
+/* Declarations from cfsupport */
+
+staticforward PyTypeObject __pyx_type_9cfsupport_PyCFSocket;
+
+struct __pyx_obj_9cfsupport_PyCFSocket {
+ PyObject_HEAD
+ PyObject *readcallback;
+ PyObject *writecallback;
+ PyObject *connectcallback;
+ PyObject *reading;
+ PyObject *writing;
+ CFSocketRef cf;
+ CFRunLoopSourceRef source;
+ CFSocketNativeHandle fileno;
+ CFSocketContext context;
+};
+
+staticforward PyTypeObject __pyx_type_9cfsupport_PyCFRunLoopTimer;
+
+struct __pyx_obj_9cfsupport_PyCFRunLoopTimer {
+ PyObject_HEAD
+ CFRunLoopTimerRef cf;
+ PyObject *callout;
+ CFRunLoopTimerContext context;
+};
+
+staticforward PyTypeObject __pyx_type_9cfsupport_PyCFRunLoop;
+
+struct __pyx_obj_9cfsupport_PyCFRunLoop {
+ PyObject_HEAD
+ PyObject *cf;
+};
+
+static PyTypeObject *__pyx_ptype_9cfsupport_PyCFSocket = 0;
+static PyTypeObject *__pyx_ptype_9cfsupport_PyCFRunLoopTimer = 0;
+static PyTypeObject *__pyx_ptype_9cfsupport_PyCFRunLoop = 0;
+static PyObject *__pyx_k2;
+static PyObject *__pyx_k3;
+static PyObject *__pyx_k4;
+static PyObject *__pyx_k6;
+static void (__pyx_f_9cfsupport_socketCallBack(CFSocketRef ,CFSocketCallBackType ,CFDataRef ,void (*),void (*))); /*proto*/
+static void (__pyx_f_9cfsupport_gilSocketCallBack(CFSocketRef ,CFSocketCallBackType ,CFDataRef ,void (*),void (*))); /*proto*/
+static void (__pyx_f_9cfsupport_runLoopTimerCallBack(CFRunLoopTimerRef ,void (*))); /*proto*/
+static void (__pyx_f_9cfsupport_gilRunLoopTimerCallBack(CFRunLoopTimerRef ,void (*))); /*proto*/
+
+/* Implementation of cfsupport */
+
+static char (__pyx_k7[]) = "0.4";
+
+static PyObject *__pyx_n_now;
+static PyObject *__pyx_n_traceback;
+static PyObject *__pyx_n___version__;
+
+static PyObject *__pyx_k7p;
+
+static PyObject *__pyx_f_9cfsupport_now(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_now(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfdate.pxi":2 */
+ __pyx_1 = PyFloat_FromDouble(CFAbsoluteTimeGetCurrent()); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 2; goto __pyx_L1;}
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ __Pyx_AddTraceback("cfsupport.now");
+ __pyx_r = 0;
+ __pyx_L0:;
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_print_exc;
+
+static void __pyx_f_9cfsupport_socketCallBack(CFSocketRef __pyx_v_s,CFSocketCallBackType __pyx_v__type,CFDataRef __pyx_v_address,void (*__pyx_v_data),void (*__pyx_v_info)) {
+ struct __pyx_obj_9cfsupport_PyCFSocket *__pyx_v_socket;
+ int __pyx_v_res;
+ int __pyx_v_mask;
+ PyObject *__pyx_1 = 0;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ ((PyObject*)__pyx_v_socket) = Py_None; Py_INCREF(((PyObject*)__pyx_v_socket));
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":9 */
+ __pyx_1 = (PyObject *)__pyx_v_info;
+ Py_INCREF(__pyx_1);
+ Py_DECREF(((PyObject *)__pyx_v_socket));
+ ((PyObject *)__pyx_v_socket) = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":11 */
+ /*try:*/ {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":12 */
+ __pyx_2 = (__pyx_v__type == kCFSocketReadCallBack);
+ if (__pyx_2) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":13 */
+ __pyx_2 = PyObject_IsTrue(__pyx_v_socket->readcallback); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 13; goto __pyx_L2;}
+ if (__pyx_2) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":14 */
+ __pyx_1 = PyTuple_New(0); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 14; goto __pyx_L2;}
+ __pyx_3 = PyObject_CallObject(__pyx_v_socket->readcallback, __pyx_1); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 14; goto __pyx_L2;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+ goto __pyx_L4;
+ }
+ __pyx_2 = (__pyx_v__type == kCFSocketWriteCallBack);
+ if (__pyx_2) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":16 */
+ __pyx_2 = PyObject_IsTrue(__pyx_v_socket->writecallback); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 16; goto __pyx_L2;}
+ if (__pyx_2) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":17 */
+ __pyx_1 = PyTuple_New(0); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 17; goto __pyx_L2;}
+ __pyx_3 = PyObject_CallObject(__pyx_v_socket->writecallback, __pyx_1); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 17; goto __pyx_L2;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ goto __pyx_L6;
+ }
+ __pyx_L6:;
+ goto __pyx_L4;
+ }
+ __pyx_2 = (__pyx_v__type == kCFSocketConnectCallBack);
+ if (__pyx_2) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":19 */
+ __pyx_2 = (__pyx_v_data == 0);
+ if (__pyx_2) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":20 */
+ __pyx_v_res = 0;
+ goto __pyx_L7;
+ }
+ /*else*/ {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":22 */
+ __pyx_v_res = (((int (*))__pyx_v_data)[0]);
+ }
+ __pyx_L7:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":23 */
+ __pyx_2 = PyObject_IsTrue(__pyx_v_socket->connectcallback); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 23; goto __pyx_L2;}
+ if (__pyx_2) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":24 */
+ __pyx_1 = PyInt_FromLong(__pyx_v_res); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 24; goto __pyx_L2;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 24; goto __pyx_L2;}
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_1);
+ __pyx_1 = 0;
+ __pyx_1 = PyObject_CallObject(__pyx_v_socket->connectcallback, __pyx_3); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 24; goto __pyx_L2;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ goto __pyx_L8;
+ }
+ __pyx_L8:;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+ }
+ goto __pyx_L3;
+ __pyx_L2:;
+ Py_XDECREF(__pyx_3); __pyx_3 = 0;
+ Py_XDECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":25 */
+ /*except:*/ {
+ __Pyx_AddTraceback("cfsupport.socketCallBack");
+ __pyx_3 = __Pyx_GetExcValue(); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 25; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":26 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_traceback); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 26; goto __pyx_L1;}
+ __pyx_3 = PyObject_GetAttr(__pyx_1, __pyx_n_print_exc); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 26; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyTuple_New(0); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 26; goto __pyx_L1;}
+ __pyx_4 = PyObject_CallObject(__pyx_3, __pyx_1); if (!__pyx_4) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 26; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_WriteUnraisable("cfsupport.socketCallBack");
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_socket);
+}
+
+static void __pyx_f_9cfsupport_gilSocketCallBack(CFSocketRef __pyx_v_s,CFSocketCallBackType __pyx_v__type,CFDataRef __pyx_v_address,void (*__pyx_v_data),void (*__pyx_v_info)) {
+ PyGILState_STATE __pyx_v_gil;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":30 */
+ __pyx_v_gil = PyGILState_Ensure();
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":31 */
+ __pyx_f_9cfsupport_socketCallBack(__pyx_v_s,__pyx_v__type,__pyx_v_address,__pyx_v_data,__pyx_v_info);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":32 */
+ PyGILState_Release(__pyx_v_gil);
+
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_WriteUnraisable("cfsupport.gilSocketCallBack");
+ __pyx_L0:;
+}
+
+static PyObject *__pyx_n_False;
+static PyObject *__pyx_n_ValueError;
+
+static PyObject *__pyx_k8p;
+static PyObject *__pyx_k9p;
+
+static char (__pyx_k8[]) = "Invalid Socket";
+static char (__pyx_k9[]) = "Couldn't create runloop source";
+
+static int __pyx_f_9cfsupport_10PyCFSocket___new__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static int __pyx_f_9cfsupport_10PyCFSocket___new__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ int __pyx_v_fileno;
+ PyObject *__pyx_v_readcallback = 0;
+ PyObject *__pyx_v_writecallback = 0;
+ PyObject *__pyx_v_connectcallback = 0;
+ int __pyx_r;
+ PyObject *__pyx_1 = 0;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ static char *__pyx_argnames[] = {"fileno","readcallback","writecallback","connectcallback",0};
+ __pyx_v_readcallback = __pyx_k2;
+ __pyx_v_writecallback = __pyx_k3;
+ __pyx_v_connectcallback = __pyx_k4;
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "i|OOO", __pyx_argnames, &__pyx_v_fileno, &__pyx_v_readcallback, &__pyx_v_writecallback, &__pyx_v_connectcallback)) return -1;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_readcallback);
+ Py_INCREF(__pyx_v_writecallback);
+ Py_INCREF(__pyx_v_connectcallback);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":47 */
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->fileno = __pyx_v_fileno;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":48 */
+ Py_INCREF(__pyx_v_readcallback);
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->readcallback);
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->readcallback = __pyx_v_readcallback;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":49 */
+ Py_INCREF(__pyx_v_writecallback);
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->writecallback);
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->writecallback = __pyx_v_writecallback;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":50 */
+ Py_INCREF(__pyx_v_connectcallback);
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->connectcallback);
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->connectcallback = __pyx_v_connectcallback;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":51 */
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->context.version = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":52 */
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->context.info = ((void (*))__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":53 */
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->context.retain = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":54 */
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->context.release = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":55 */
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->context.copyDescription = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":56 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 56; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->reading);
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->reading = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":57 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 57; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->writing);
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->writing = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":58 */
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->cf = CFSocketCreateWithNative(kCFAllocatorDefault,__pyx_v_fileno,((kCFSocketConnectCallBack | kCFSocketReadCallBack) | kCFSocketWriteCallBack),((CFSocketCallBack )(&__pyx_f_9cfsupport_gilSocketCallBack)),(&((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->context));
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":59 */
+ __pyx_2 = (((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->cf == 0);
+ if (__pyx_2) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":60 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 60; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 60; goto __pyx_L1;}
+ Py_INCREF(__pyx_k8p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k8p);
+ __pyx_4 = PyObject_CallObject(__pyx_1, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 60; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[1]; __pyx_lineno = 60; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":61 */
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->source = CFSocketCreateRunLoopSource(kCFAllocatorDefault,((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->cf,10000);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":62 */
+ __pyx_2 = (((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->source == 0);
+ if (__pyx_2) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":63 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 63; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 63; goto __pyx_L1;}
+ Py_INCREF(__pyx_k9p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k9p);
+ __pyx_4 = PyObject_CallObject(__pyx_1, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 63; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[1]; __pyx_lineno = 63; goto __pyx_L1;}
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("cfsupport.PyCFSocket.__new__");
+ __pyx_r = -1;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_readcallback);
+ Py_DECREF(__pyx_v_writecallback);
+ Py_DECREF(__pyx_v_connectcallback);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_9cfsupport_10PyCFSocket_update(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_10PyCFSocket_update(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ int __pyx_v_mask;
+ int __pyx_v_offmask;
+ int __pyx_v_automask;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":70 */
+ __pyx_v_mask = (kCFSocketConnectCallBack | kCFSocketAcceptCallBack);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":71 */
+ __pyx_v_offmask = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":72 */
+ __pyx_v_automask = kCFSocketAutomaticallyReenableAcceptCallBack;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":73 */
+ __pyx_1 = PyObject_IsTrue(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->reading); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 73; goto __pyx_L1;}
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":74 */
+ __pyx_v_mask = (__pyx_v_mask | kCFSocketReadCallBack);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":75 */
+ __pyx_v_automask = (__pyx_v_automask | kCFSocketAutomaticallyReenableReadCallBack);
+ goto __pyx_L2;
+ }
+ /*else*/ {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":77 */
+ __pyx_v_offmask = (__pyx_v_offmask | kCFSocketReadCallBack);
+ }
+ __pyx_L2:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":78 */
+ __pyx_1 = PyObject_IsTrue(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->writing); if (__pyx_1 < 0) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 78; goto __pyx_L1;}
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":79 */
+ __pyx_v_mask = (__pyx_v_mask | kCFSocketWriteCallBack);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":80 */
+ __pyx_v_automask = (__pyx_v_automask | kCFSocketAutomaticallyReenableWriteCallBack);
+ goto __pyx_L3;
+ }
+ /*else*/ {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":82 */
+ __pyx_v_offmask = (__pyx_v_offmask | kCFSocketWriteCallBack);
+ }
+ __pyx_L3:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":83 */
+ CFSocketDisableCallBacks(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->cf,__pyx_v_offmask);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":84 */
+ CFSocketEnableCallBacks(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->cf,__pyx_v_mask);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":85 */
+ CFSocketSetSocketFlags(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->cf,__pyx_v_automask);
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("cfsupport.PyCFSocket.update");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_True;
+static PyObject *__pyx_n_update;
+
+static PyObject *__pyx_f_9cfsupport_10PyCFSocket_startReading(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_10PyCFSocket_startReading(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":89 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 89; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->reading);
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->reading = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":90 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_self, __pyx_n_update); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 90; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(0); if (!__pyx_2) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 90; goto __pyx_L1;}
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 90; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("cfsupport.PyCFSocket.startReading");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_9cfsupport_10PyCFSocket_stopReading(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_10PyCFSocket_stopReading(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":93 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 93; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->reading);
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->reading = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":94 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_self, __pyx_n_update); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 94; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(0); if (!__pyx_2) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 94; goto __pyx_L1;}
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 94; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("cfsupport.PyCFSocket.stopReading");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_9cfsupport_10PyCFSocket_startWriting(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_10PyCFSocket_startWriting(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":97 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_True); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 97; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->writing);
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->writing = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":98 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_self, __pyx_n_update); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 98; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(0); if (!__pyx_2) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 98; goto __pyx_L1;}
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 98; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("cfsupport.PyCFSocket.startWriting");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_9cfsupport_10PyCFSocket_stopWriting(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_10PyCFSocket_stopWriting(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":101 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 101; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->writing);
+ ((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->writing = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":102 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_self, __pyx_n_update); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 102; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(0); if (!__pyx_2) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 102; goto __pyx_L1;}
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 102; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("cfsupport.PyCFSocket.stopWriting");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static void __pyx_f_9cfsupport_10PyCFSocket___dealloc__(PyObject *__pyx_v_self); /*proto*/
+static void __pyx_f_9cfsupport_10PyCFSocket___dealloc__(PyObject *__pyx_v_self) {
+ int __pyx_1;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":106 */
+ __pyx_1 = (((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->source != 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":107 */
+ CFRelease(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->source);
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":108 */
+ __pyx_1 = (((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->cf != 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":109 */
+ CFSocketInvalidate(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->cf);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":110 */
+ CFRelease(((struct __pyx_obj_9cfsupport_PyCFSocket *)__pyx_v_self)->cf);
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("cfsupport.PyCFSocket.__dealloc__");
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+}
+
+static PyObject *__pyx_k10p;
+
+static char (__pyx_k10[]) = "Invalid Socket";
+
+static int __pyx_f_9cfsupport_16PyCFRunLoopTimer___new__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static int __pyx_f_9cfsupport_16PyCFRunLoopTimer___new__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ double __pyx_v_fireDate;
+ double __pyx_v_interval;
+ PyObject *__pyx_v_callout = 0;
+ int __pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ static char *__pyx_argnames[] = {"fireDate","interval","callout",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "ddO", __pyx_argnames, &__pyx_v_fireDate, &__pyx_v_interval, &__pyx_v_callout)) return -1;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_callout);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":11 */
+ Py_INCREF(__pyx_v_callout);
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->callout);
+ ((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->callout = __pyx_v_callout;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":12 */
+ ((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->context.version = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":13 */
+ ((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->context.info = ((void (*))__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":14 */
+ ((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->context.retain = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":15 */
+ ((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->context.release = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":16 */
+ ((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->context.copyDescription = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":17 */
+ ((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->cf = CFRunLoopTimerCreate(kCFAllocatorDefault,__pyx_v_fireDate,__pyx_v_interval,0,0,((CFRunLoopTimerCallBack )(&__pyx_f_9cfsupport_gilRunLoopTimerCallBack)),(&((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->context));
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":18 */
+ __pyx_1 = (((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->cf == 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":19 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 19; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 19; goto __pyx_L1;}
+ Py_INCREF(__pyx_k10p);
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_k10p);
+ __pyx_4 = PyObject_CallObject(__pyx_2, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 19; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __Pyx_Raise(__pyx_4, 0, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[2]; __pyx_lineno = 19; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoopTimer.__new__");
+ __pyx_r = -1;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_callout);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_9cfsupport_16PyCFRunLoopTimer_getNextFireDate(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_16PyCFRunLoopTimer_getNextFireDate(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":22 */
+ __pyx_1 = PyFloat_FromDouble(CFRunLoopTimerGetNextFireDate(((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->cf)); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 22; goto __pyx_L1;}
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoopTimer.getNextFireDate");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_9cfsupport_16PyCFRunLoopTimer_setNextFireDate(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_16PyCFRunLoopTimer_setNextFireDate(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ double __pyx_v_fireDate;
+ PyObject *__pyx_r;
+ static char *__pyx_argnames[] = {"fireDate",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "d", __pyx_argnames, &__pyx_v_fireDate)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":25 */
+ CFRunLoopTimerSetNextFireDate(((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->cf,__pyx_v_fireDate);
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoopTimer.setNextFireDate");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_9cfsupport_16PyCFRunLoopTimer_invalidate(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_16PyCFRunLoopTimer_invalidate(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":28 */
+ CFRunLoopTimerInvalidate(((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->cf);
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoopTimer.invalidate");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static void __pyx_f_9cfsupport_16PyCFRunLoopTimer___dealloc__(PyObject *__pyx_v_self); /*proto*/
+static void __pyx_f_9cfsupport_16PyCFRunLoopTimer___dealloc__(PyObject *__pyx_v_self) {
+ int __pyx_1;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":31 */
+ __pyx_1 = (((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->cf != 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":32 */
+ CFRelease(((struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)__pyx_v_self)->cf);
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoopTimer.__dealloc__");
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+}
+
+static void __pyx_f_9cfsupport_runLoopTimerCallBack(CFRunLoopTimerRef __pyx_v_timer,void (*__pyx_v_info)) {
+ struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *__pyx_v_obj;
+ PyObject *__pyx_1 = 0;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ ((PyObject*)__pyx_v_obj) = Py_None; Py_INCREF(((PyObject*)__pyx_v_obj));
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":36 */
+ __pyx_1 = (PyObject *)__pyx_v_info;
+ Py_INCREF(__pyx_1);
+ Py_DECREF(((PyObject *)__pyx_v_obj));
+ ((PyObject *)__pyx_v_obj) = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":37 */
+ /*try:*/ {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":38 */
+ __pyx_2 = PyObject_IsTrue(__pyx_v_obj->callout); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 38; goto __pyx_L2;}
+ if (__pyx_2) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":39 */
+ __pyx_1 = PyTuple_New(0); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 39; goto __pyx_L2;}
+ __pyx_3 = PyObject_CallObject(__pyx_v_obj->callout, __pyx_1); if (!__pyx_3) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 39; goto __pyx_L2;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+ }
+ goto __pyx_L3;
+ __pyx_L2:;
+ Py_XDECREF(__pyx_1); __pyx_1 = 0;
+ Py_XDECREF(__pyx_3); __pyx_3 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":40 */
+ /*except:*/ {
+ __Pyx_AddTraceback("cfsupport.runLoopTimerCallBack");
+ __pyx_1 = __Pyx_GetExcValue(); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 40; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":41 */
+ __pyx_3 = __Pyx_GetName(__pyx_m, __pyx_n_traceback); if (!__pyx_3) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 41; goto __pyx_L1;}
+ __pyx_1 = PyObject_GetAttr(__pyx_3, __pyx_n_print_exc); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 41; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_3 = PyTuple_New(0); if (!__pyx_3) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 41; goto __pyx_L1;}
+ __pyx_4 = PyObject_CallObject(__pyx_1, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 41; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_WriteUnraisable("cfsupport.runLoopTimerCallBack");
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_obj);
+}
+
+static void __pyx_f_9cfsupport_gilRunLoopTimerCallBack(CFRunLoopTimerRef __pyx_v_timer,void (*__pyx_v_info)) {
+ PyGILState_STATE __pyx_v_gil;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":45 */
+ __pyx_v_gil = PyGILState_Ensure();
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":46 */
+ __pyx_f_9cfsupport_runLoopTimerCallBack(__pyx_v_timer,__pyx_v_info);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":47 */
+ PyGILState_Release(__pyx_v_gil);
+
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_WriteUnraisable("cfsupport.gilRunLoopTimerCallBack");
+ __pyx_L0:;
+}
+
+static int __pyx_f_9cfsupport_11PyCFRunLoop___new__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static int __pyx_f_9cfsupport_11PyCFRunLoop___new__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_runLoop = 0;
+ CFTypeRef __pyx_v__runLoop;
+ int __pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {"runLoop",0};
+ __pyx_v_runLoop = __pyx_k6;
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "|O", __pyx_argnames, &__pyx_v_runLoop)) return -1;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_runLoop);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":54 */
+ __pyx_1 = __pyx_v_runLoop == Py_None;
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":55 */
+ __pyx_v__runLoop = CFRunLoopGetCurrent();
+ goto __pyx_L2;
+ }
+ /*else*/ {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":57 */
+ __pyx_1 = (CFObj_Convert(__pyx_v_runLoop,(&__pyx_v__runLoop)) == 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":58 */
+ __Pyx_ReRaise();
+ {__pyx_filename = __pyx_f[2]; __pyx_lineno = 58; goto __pyx_L1;}
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+ }
+ __pyx_L2:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":61 */
+ __pyx_2 = CFObj_New(CFRetain(__pyx_v__runLoop)); if (!__pyx_2) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 61; goto __pyx_L1;}
+ Py_DECREF(((struct __pyx_obj_9cfsupport_PyCFRunLoop *)__pyx_v_self)->cf);
+ ((struct __pyx_obj_9cfsupport_PyCFRunLoop *)__pyx_v_self)->cf = __pyx_2;
+ __pyx_2 = 0;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoop.__new__");
+ __pyx_r = -1;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_runLoop);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_run(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_run(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":64 */
+ CFRunLoopRun();
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoop.run");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k11p;
+
+static char (__pyx_k11[]) = "CFRunLoopReference is invalid";
+
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_stop(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_stop(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ CFTypeRef __pyx_v__runLoop;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":68 */
+ __pyx_1 = (CFObj_Convert(((struct __pyx_obj_9cfsupport_PyCFRunLoop *)__pyx_v_self)->cf,(&__pyx_v__runLoop)) == 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":69 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 69; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_2, __pyx_k11p, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[2]; __pyx_lineno = 69; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":70 */
+ CFRunLoopStop(__pyx_v__runLoop);
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoop.stop");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k12p;
+
+static char (__pyx_k12[]) = "CFRunLoopReference is invalid";
+
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_currentMode(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_currentMode(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ CFTypeRef __pyx_v__currentMode;
+ CFTypeRef __pyx_v__runLoop;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":75 */
+ __pyx_1 = (CFObj_Convert(((struct __pyx_obj_9cfsupport_PyCFRunLoop *)__pyx_v_self)->cf,(&__pyx_v__runLoop)) == 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":76 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 76; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_2, __pyx_k12p, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[2]; __pyx_lineno = 76; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":77 */
+ __pyx_v__currentMode = CFRunLoopCopyCurrentMode(__pyx_v__runLoop);
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":78 */
+ __pyx_1 = (__pyx_v__currentMode == 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":79 */
+ Py_INCREF(Py_None);
+ __pyx_r = Py_None;
+ goto __pyx_L0;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":80 */
+ __pyx_2 = CFObj_New(__pyx_v__currentMode); if (!__pyx_2) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 80; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoop.currentMode");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k13p;
+
+static char (__pyx_k13[]) = "CFRunLoopReference is invalid";
+
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_addSocket(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_addSocket(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ struct __pyx_obj_9cfsupport_PyCFSocket *__pyx_v_socket = 0;
+ CFTypeRef __pyx_v__runLoop;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {"socket",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "O", __pyx_argnames, &__pyx_v_socket)) return 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_socket);
+ if (!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_socket), __pyx_ptype_9cfsupport_PyCFSocket, 0, "socket")) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 82; goto __pyx_L1;}
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":84 */
+ __pyx_1 = (CFObj_Convert(((struct __pyx_obj_9cfsupport_PyCFRunLoop *)__pyx_v_self)->cf,(&__pyx_v__runLoop)) == 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":85 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 85; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_2, __pyx_k13p, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[2]; __pyx_lineno = 85; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":86 */
+ CFRunLoopAddSource(__pyx_v__runLoop,__pyx_v_socket->source,kCFRunLoopCommonModes);
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoop.addSocket");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_socket);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k14p;
+
+static char (__pyx_k14[]) = "CFRunLoopReference is invalid";
+
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_removeSocket(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_removeSocket(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ struct __pyx_obj_9cfsupport_PyCFSocket *__pyx_v_socket = 0;
+ CFTypeRef __pyx_v__runLoop;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {"socket",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "O", __pyx_argnames, &__pyx_v_socket)) return 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_socket);
+ if (!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_socket), __pyx_ptype_9cfsupport_PyCFSocket, 0, "socket")) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 88; goto __pyx_L1;}
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":90 */
+ __pyx_1 = (CFObj_Convert(((struct __pyx_obj_9cfsupport_PyCFRunLoop *)__pyx_v_self)->cf,(&__pyx_v__runLoop)) == 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":91 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 91; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_2, __pyx_k14p, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[2]; __pyx_lineno = 91; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":92 */
+ CFRunLoopRemoveSource(__pyx_v__runLoop,__pyx_v_socket->source,kCFRunLoopCommonModes);
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoop.removeSocket");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_socket);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k15p;
+
+static char (__pyx_k15[]) = "CFRunLoopReference is invalid";
+
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_addTimer(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_addTimer(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *__pyx_v_timer = 0;
+ CFTypeRef __pyx_v__runLoop;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {"timer",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "O", __pyx_argnames, &__pyx_v_timer)) return 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_timer);
+ if (!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_timer), __pyx_ptype_9cfsupport_PyCFRunLoopTimer, 0, "timer")) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 94; goto __pyx_L1;}
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":96 */
+ __pyx_1 = (CFObj_Convert(((struct __pyx_obj_9cfsupport_PyCFRunLoop *)__pyx_v_self)->cf,(&__pyx_v__runLoop)) == 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":97 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 97; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_2, __pyx_k15p, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[2]; __pyx_lineno = 97; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":98 */
+ CFRunLoopAddTimer(__pyx_v__runLoop,__pyx_v_timer->cf,kCFRunLoopCommonModes);
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoop.addTimer");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_timer);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k16p;
+
+static char (__pyx_k16[]) = "CFRunLoopReference is invalid";
+
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_removeTimer(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_9cfsupport_11PyCFRunLoop_removeTimer(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *__pyx_v_timer = 0;
+ CFTypeRef __pyx_v__runLoop;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {"timer",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "O", __pyx_argnames, &__pyx_v_timer)) return 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_timer);
+ if (!__Pyx_ArgTypeTest(((PyObject *)__pyx_v_timer), __pyx_ptype_9cfsupport_PyCFRunLoopTimer, 0, "timer")) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 100; goto __pyx_L1;}
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":102 */
+ __pyx_1 = (CFObj_Convert(((struct __pyx_obj_9cfsupport_PyCFRunLoop *)__pyx_v_self)->cf,(&__pyx_v__runLoop)) == 0);
+ if (__pyx_1) {
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":103 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 103; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_2, __pyx_k16p, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[2]; __pyx_lineno = 103; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":104 */
+ CFRunLoopRemoveTimer(__pyx_v__runLoop,__pyx_v_timer->cf,kCFRunLoopCommonModes);
+
+ __pyx_r = Py_None; Py_INCREF(__pyx_r);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("cfsupport.PyCFRunLoop.removeTimer");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_timer);
+ return __pyx_r;
+}
+
+static __Pyx_InternTabEntry __pyx_intern_tab[] = {
+ {&__pyx_n_False, "False"},
+ {&__pyx_n_True, "True"},
+ {&__pyx_n_ValueError, "ValueError"},
+ {&__pyx_n___version__, "__version__"},
+ {&__pyx_n_now, "now"},
+ {&__pyx_n_print_exc, "print_exc"},
+ {&__pyx_n_traceback, "traceback"},
+ {&__pyx_n_update, "update"},
+ {0, 0}
+};
+
+static __Pyx_StringTabEntry __pyx_string_tab[] = {
+ {&__pyx_k7p, __pyx_k7, sizeof(__pyx_k7)},
+ {&__pyx_k8p, __pyx_k8, sizeof(__pyx_k8)},
+ {&__pyx_k9p, __pyx_k9, sizeof(__pyx_k9)},
+ {&__pyx_k10p, __pyx_k10, sizeof(__pyx_k10)},
+ {&__pyx_k11p, __pyx_k11, sizeof(__pyx_k11)},
+ {&__pyx_k12p, __pyx_k12, sizeof(__pyx_k12)},
+ {&__pyx_k13p, __pyx_k13, sizeof(__pyx_k13)},
+ {&__pyx_k14p, __pyx_k14, sizeof(__pyx_k14)},
+ {&__pyx_k15p, __pyx_k15, sizeof(__pyx_k15)},
+ {&__pyx_k16p, __pyx_k16, sizeof(__pyx_k16)},
+ {0, 0, 0}
+};
+
+static PyObject *__pyx_tp_new_9cfsupport_PyCFSocket(PyTypeObject *t, PyObject *a, PyObject *k) {
+ PyObject *o = (*t->tp_alloc)(t, 0);
+ struct __pyx_obj_9cfsupport_PyCFSocket *p = (struct __pyx_obj_9cfsupport_PyCFSocket *)o;
+ p->readcallback = Py_None; Py_INCREF(p->readcallback);
+ p->writecallback = Py_None; Py_INCREF(p->writecallback);
+ p->connectcallback = Py_None; Py_INCREF(p->connectcallback);
+ p->reading = Py_None; Py_INCREF(p->reading);
+ p->writing = Py_None; Py_INCREF(p->writing);
+ if (__pyx_f_9cfsupport_10PyCFSocket___new__(o, a, k) < 0) {
+ Py_DECREF(o); o = 0;
+ }
+ return o;
+}
+
+static void __pyx_tp_dealloc_9cfsupport_PyCFSocket(PyObject *o) {
+ struct __pyx_obj_9cfsupport_PyCFSocket *p = (struct __pyx_obj_9cfsupport_PyCFSocket *)o;
+ {
+ PyObject *etype, *eval, *etb;
+ PyErr_Fetch(&etype, &eval, &etb);
+ ++o->ob_refcnt;
+ __pyx_f_9cfsupport_10PyCFSocket___dealloc__(o);
+ if (PyErr_Occurred()) PyErr_WriteUnraisable(o);
+ --o->ob_refcnt;
+ PyErr_Restore(etype, eval, etb);
+ }
+ Py_XDECREF(p->readcallback);
+ Py_XDECREF(p->writecallback);
+ Py_XDECREF(p->connectcallback);
+ Py_XDECREF(p->reading);
+ Py_XDECREF(p->writing);
+ (*o->ob_type->tp_free)(o);
+}
+
+static int __pyx_tp_traverse_9cfsupport_PyCFSocket(PyObject *o, visitproc v, void *a) {
+ int e;
+ struct __pyx_obj_9cfsupport_PyCFSocket *p = (struct __pyx_obj_9cfsupport_PyCFSocket *)o;
+ if (p->readcallback) {
+ e = (*v)(p->readcallback, a); if (e) return e;
+ }
+ if (p->writecallback) {
+ e = (*v)(p->writecallback, a); if (e) return e;
+ }
+ if (p->connectcallback) {
+ e = (*v)(p->connectcallback, a); if (e) return e;
+ }
+ if (p->reading) {
+ e = (*v)(p->reading, a); if (e) return e;
+ }
+ if (p->writing) {
+ e = (*v)(p->writing, a); if (e) return e;
+ }
+ return 0;
+}
+
+static int __pyx_tp_clear_9cfsupport_PyCFSocket(PyObject *o) {
+ struct __pyx_obj_9cfsupport_PyCFSocket *p = (struct __pyx_obj_9cfsupport_PyCFSocket *)o;
+ Py_XDECREF(p->readcallback);
+ p->readcallback = Py_None; Py_INCREF(p->readcallback);
+ Py_XDECREF(p->writecallback);
+ p->writecallback = Py_None; Py_INCREF(p->writecallback);
+ Py_XDECREF(p->connectcallback);
+ p->connectcallback = Py_None; Py_INCREF(p->connectcallback);
+ Py_XDECREF(p->reading);
+ p->reading = Py_None; Py_INCREF(p->reading);
+ Py_XDECREF(p->writing);
+ p->writing = Py_None; Py_INCREF(p->writing);
+ return 0;
+}
+
+static struct PyMethodDef __pyx_methods_9cfsupport_PyCFSocket[] = {
+ {"update", (PyCFunction)__pyx_f_9cfsupport_10PyCFSocket_update, METH_VARARGS|METH_KEYWORDS, 0},
+ {"startReading", (PyCFunction)__pyx_f_9cfsupport_10PyCFSocket_startReading, METH_VARARGS|METH_KEYWORDS, 0},
+ {"stopReading", (PyCFunction)__pyx_f_9cfsupport_10PyCFSocket_stopReading, METH_VARARGS|METH_KEYWORDS, 0},
+ {"startWriting", (PyCFunction)__pyx_f_9cfsupport_10PyCFSocket_startWriting, METH_VARARGS|METH_KEYWORDS, 0},
+ {"stopWriting", (PyCFunction)__pyx_f_9cfsupport_10PyCFSocket_stopWriting, METH_VARARGS|METH_KEYWORDS, 0},
+ {0, 0, 0, 0}
+};
+
+static struct PyMemberDef __pyx_members_9cfsupport_PyCFSocket[] = {
+ {"readcallback", T_OBJECT, offsetof(struct __pyx_obj_9cfsupport_PyCFSocket, readcallback), 0, 0},
+ {"writecallback", T_OBJECT, offsetof(struct __pyx_obj_9cfsupport_PyCFSocket, writecallback), 0, 0},
+ {"connectcallback", T_OBJECT, offsetof(struct __pyx_obj_9cfsupport_PyCFSocket, connectcallback), 0, 0},
+ {"reading", T_OBJECT, offsetof(struct __pyx_obj_9cfsupport_PyCFSocket, reading), 0, 0},
+ {"writing", T_OBJECT, offsetof(struct __pyx_obj_9cfsupport_PyCFSocket, writing), 0, 0},
+ {"fileno", T_INT, offsetof(struct __pyx_obj_9cfsupport_PyCFSocket, fileno), READONLY, 0},
+ {0, 0, 0, 0, 0}
+};
+
+static PyNumberMethods __pyx_tp_as_number_PyCFSocket = {
+ 0, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ 0, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+ 0, /*nb_oct*/
+ 0, /*nb_hex*/
+ 0, /*nb_inplace_add*/
+ 0, /*nb_inplace_subtract*/
+ 0, /*nb_inplace_multiply*/
+ 0, /*nb_inplace_divide*/
+ 0, /*nb_inplace_remainder*/
+ 0, /*nb_inplace_power*/
+ 0, /*nb_inplace_lshift*/
+ 0, /*nb_inplace_rshift*/
+ 0, /*nb_inplace_and*/
+ 0, /*nb_inplace_xor*/
+ 0, /*nb_inplace_or*/
+ 0, /*nb_floor_divide*/
+ 0, /*nb_true_divide*/
+ 0, /*nb_inplace_floor_divide*/
+ 0, /*nb_inplace_true_divide*/
+};
+
+static PySequenceMethods __pyx_tp_as_sequence_PyCFSocket = {
+ 0, /*sq_length*/
+ 0, /*sq_concat*/
+ 0, /*sq_repeat*/
+ 0, /*sq_item*/
+ 0, /*sq_slice*/
+ 0, /*sq_ass_item*/
+ 0, /*sq_ass_slice*/
+ 0, /*sq_contains*/
+ 0, /*sq_inplace_concat*/
+ 0, /*sq_inplace_repeat*/
+};
+
+static PyMappingMethods __pyx_tp_as_mapping_PyCFSocket = {
+ 0, /*mp_length*/
+ 0, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
+};
+
+static PyBufferProcs __pyx_tp_as_buffer_PyCFSocket = {
+ 0, /*bf_getreadbuffer*/
+ 0, /*bf_getwritebuffer*/
+ 0, /*bf_getsegcount*/
+ 0, /*bf_getcharbuffer*/
+};
+
+statichere PyTypeObject __pyx_type_9cfsupport_PyCFSocket = {
+ PyObject_HEAD_INIT(0)
+ 0, /*ob_size*/
+ "cfsupport.PyCFSocket", /*tp_name*/
+ sizeof(struct __pyx_obj_9cfsupport_PyCFSocket), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ __pyx_tp_dealloc_9cfsupport_PyCFSocket, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ &__pyx_tp_as_number_PyCFSocket, /*tp_as_number*/
+ &__pyx_tp_as_sequence_PyCFSocket, /*tp_as_sequence*/
+ &__pyx_tp_as_mapping_PyCFSocket, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ &__pyx_tp_as_buffer_PyCFSocket, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+ 0, /*tp_doc*/
+ __pyx_tp_traverse_9cfsupport_PyCFSocket, /*tp_traverse*/
+ __pyx_tp_clear_9cfsupport_PyCFSocket, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ __pyx_methods_9cfsupport_PyCFSocket, /*tp_methods*/
+ __pyx_members_9cfsupport_PyCFSocket, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ 0, /*tp_init*/
+ 0, /*tp_alloc*/
+ __pyx_tp_new_9cfsupport_PyCFSocket, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+ 0, /*tp_bases*/
+ 0, /*tp_mro*/
+ 0, /*tp_cache*/
+ 0, /*tp_subclasses*/
+ 0, /*tp_weaklist*/
+};
+
+static PyObject *__pyx_tp_new_9cfsupport_PyCFRunLoopTimer(PyTypeObject *t, PyObject *a, PyObject *k) {
+ PyObject *o = (*t->tp_alloc)(t, 0);
+ struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *p = (struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)o;
+ p->callout = Py_None; Py_INCREF(p->callout);
+ if (__pyx_f_9cfsupport_16PyCFRunLoopTimer___new__(o, a, k) < 0) {
+ Py_DECREF(o); o = 0;
+ }
+ return o;
+}
+
+static void __pyx_tp_dealloc_9cfsupport_PyCFRunLoopTimer(PyObject *o) {
+ struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *p = (struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)o;
+ {
+ PyObject *etype, *eval, *etb;
+ PyErr_Fetch(&etype, &eval, &etb);
+ ++o->ob_refcnt;
+ __pyx_f_9cfsupport_16PyCFRunLoopTimer___dealloc__(o);
+ if (PyErr_Occurred()) PyErr_WriteUnraisable(o);
+ --o->ob_refcnt;
+ PyErr_Restore(etype, eval, etb);
+ }
+ Py_XDECREF(p->callout);
+ (*o->ob_type->tp_free)(o);
+}
+
+static int __pyx_tp_traverse_9cfsupport_PyCFRunLoopTimer(PyObject *o, visitproc v, void *a) {
+ int e;
+ struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *p = (struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)o;
+ if (p->callout) {
+ e = (*v)(p->callout, a); if (e) return e;
+ }
+ return 0;
+}
+
+static int __pyx_tp_clear_9cfsupport_PyCFRunLoopTimer(PyObject *o) {
+ struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *p = (struct __pyx_obj_9cfsupport_PyCFRunLoopTimer *)o;
+ Py_XDECREF(p->callout);
+ p->callout = Py_None; Py_INCREF(p->callout);
+ return 0;
+}
+
+static struct PyMethodDef __pyx_methods_9cfsupport_PyCFRunLoopTimer[] = {
+ {"getNextFireDate", (PyCFunction)__pyx_f_9cfsupport_16PyCFRunLoopTimer_getNextFireDate, METH_VARARGS|METH_KEYWORDS, 0},
+ {"setNextFireDate", (PyCFunction)__pyx_f_9cfsupport_16PyCFRunLoopTimer_setNextFireDate, METH_VARARGS|METH_KEYWORDS, 0},
+ {"invalidate", (PyCFunction)__pyx_f_9cfsupport_16PyCFRunLoopTimer_invalidate, METH_VARARGS|METH_KEYWORDS, 0},
+ {0, 0, 0, 0}
+};
+
+static struct PyMemberDef __pyx_members_9cfsupport_PyCFRunLoopTimer[] = {
+ {"callout", T_OBJECT, offsetof(struct __pyx_obj_9cfsupport_PyCFRunLoopTimer, callout), 0, 0},
+ {0, 0, 0, 0, 0}
+};
+
+static PyNumberMethods __pyx_tp_as_number_PyCFRunLoopTimer = {
+ 0, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ 0, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+ 0, /*nb_oct*/
+ 0, /*nb_hex*/
+ 0, /*nb_inplace_add*/
+ 0, /*nb_inplace_subtract*/
+ 0, /*nb_inplace_multiply*/
+ 0, /*nb_inplace_divide*/
+ 0, /*nb_inplace_remainder*/
+ 0, /*nb_inplace_power*/
+ 0, /*nb_inplace_lshift*/
+ 0, /*nb_inplace_rshift*/
+ 0, /*nb_inplace_and*/
+ 0, /*nb_inplace_xor*/
+ 0, /*nb_inplace_or*/
+ 0, /*nb_floor_divide*/
+ 0, /*nb_true_divide*/
+ 0, /*nb_inplace_floor_divide*/
+ 0, /*nb_inplace_true_divide*/
+};
+
+static PySequenceMethods __pyx_tp_as_sequence_PyCFRunLoopTimer = {
+ 0, /*sq_length*/
+ 0, /*sq_concat*/
+ 0, /*sq_repeat*/
+ 0, /*sq_item*/
+ 0, /*sq_slice*/
+ 0, /*sq_ass_item*/
+ 0, /*sq_ass_slice*/
+ 0, /*sq_contains*/
+ 0, /*sq_inplace_concat*/
+ 0, /*sq_inplace_repeat*/
+};
+
+static PyMappingMethods __pyx_tp_as_mapping_PyCFRunLoopTimer = {
+ 0, /*mp_length*/
+ 0, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
+};
+
+static PyBufferProcs __pyx_tp_as_buffer_PyCFRunLoopTimer = {
+ 0, /*bf_getreadbuffer*/
+ 0, /*bf_getwritebuffer*/
+ 0, /*bf_getsegcount*/
+ 0, /*bf_getcharbuffer*/
+};
+
+statichere PyTypeObject __pyx_type_9cfsupport_PyCFRunLoopTimer = {
+ PyObject_HEAD_INIT(0)
+ 0, /*ob_size*/
+ "cfsupport.PyCFRunLoopTimer", /*tp_name*/
+ sizeof(struct __pyx_obj_9cfsupport_PyCFRunLoopTimer), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ __pyx_tp_dealloc_9cfsupport_PyCFRunLoopTimer, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ &__pyx_tp_as_number_PyCFRunLoopTimer, /*tp_as_number*/
+ &__pyx_tp_as_sequence_PyCFRunLoopTimer, /*tp_as_sequence*/
+ &__pyx_tp_as_mapping_PyCFRunLoopTimer, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ &__pyx_tp_as_buffer_PyCFRunLoopTimer, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+ 0, /*tp_doc*/
+ __pyx_tp_traverse_9cfsupport_PyCFRunLoopTimer, /*tp_traverse*/
+ __pyx_tp_clear_9cfsupport_PyCFRunLoopTimer, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ __pyx_methods_9cfsupport_PyCFRunLoopTimer, /*tp_methods*/
+ __pyx_members_9cfsupport_PyCFRunLoopTimer, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ 0, /*tp_init*/
+ 0, /*tp_alloc*/
+ __pyx_tp_new_9cfsupport_PyCFRunLoopTimer, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+ 0, /*tp_bases*/
+ 0, /*tp_mro*/
+ 0, /*tp_cache*/
+ 0, /*tp_subclasses*/
+ 0, /*tp_weaklist*/
+};
+
+static PyObject *__pyx_tp_new_9cfsupport_PyCFRunLoop(PyTypeObject *t, PyObject *a, PyObject *k) {
+ PyObject *o = (*t->tp_alloc)(t, 0);
+ struct __pyx_obj_9cfsupport_PyCFRunLoop *p = (struct __pyx_obj_9cfsupport_PyCFRunLoop *)o;
+ p->cf = Py_None; Py_INCREF(p->cf);
+ if (__pyx_f_9cfsupport_11PyCFRunLoop___new__(o, a, k) < 0) {
+ Py_DECREF(o); o = 0;
+ }
+ return o;
+}
+
+static void __pyx_tp_dealloc_9cfsupport_PyCFRunLoop(PyObject *o) {
+ struct __pyx_obj_9cfsupport_PyCFRunLoop *p = (struct __pyx_obj_9cfsupport_PyCFRunLoop *)o;
+ Py_XDECREF(p->cf);
+ (*o->ob_type->tp_free)(o);
+}
+
+static int __pyx_tp_traverse_9cfsupport_PyCFRunLoop(PyObject *o, visitproc v, void *a) {
+ int e;
+ struct __pyx_obj_9cfsupport_PyCFRunLoop *p = (struct __pyx_obj_9cfsupport_PyCFRunLoop *)o;
+ if (p->cf) {
+ e = (*v)(p->cf, a); if (e) return e;
+ }
+ return 0;
+}
+
+static int __pyx_tp_clear_9cfsupport_PyCFRunLoop(PyObject *o) {
+ struct __pyx_obj_9cfsupport_PyCFRunLoop *p = (struct __pyx_obj_9cfsupport_PyCFRunLoop *)o;
+ Py_XDECREF(p->cf);
+ p->cf = Py_None; Py_INCREF(p->cf);
+ return 0;
+}
+
+static struct PyMethodDef __pyx_methods_9cfsupport_PyCFRunLoop[] = {
+ {"run", (PyCFunction)__pyx_f_9cfsupport_11PyCFRunLoop_run, METH_VARARGS|METH_KEYWORDS, 0},
+ {"stop", (PyCFunction)__pyx_f_9cfsupport_11PyCFRunLoop_stop, METH_VARARGS|METH_KEYWORDS, 0},
+ {"currentMode", (PyCFunction)__pyx_f_9cfsupport_11PyCFRunLoop_currentMode, METH_VARARGS|METH_KEYWORDS, 0},
+ {"addSocket", (PyCFunction)__pyx_f_9cfsupport_11PyCFRunLoop_addSocket, METH_VARARGS|METH_KEYWORDS, 0},
+ {"removeSocket", (PyCFunction)__pyx_f_9cfsupport_11PyCFRunLoop_removeSocket, METH_VARARGS|METH_KEYWORDS, 0},
+ {"addTimer", (PyCFunction)__pyx_f_9cfsupport_11PyCFRunLoop_addTimer, METH_VARARGS|METH_KEYWORDS, 0},
+ {"removeTimer", (PyCFunction)__pyx_f_9cfsupport_11PyCFRunLoop_removeTimer, METH_VARARGS|METH_KEYWORDS, 0},
+ {0, 0, 0, 0}
+};
+
+static struct PyMemberDef __pyx_members_9cfsupport_PyCFRunLoop[] = {
+ {"cf", T_OBJECT, offsetof(struct __pyx_obj_9cfsupport_PyCFRunLoop, cf), 0, 0},
+ {0, 0, 0, 0, 0}
+};
+
+static PyNumberMethods __pyx_tp_as_number_PyCFRunLoop = {
+ 0, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ 0, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+ 0, /*nb_oct*/
+ 0, /*nb_hex*/
+ 0, /*nb_inplace_add*/
+ 0, /*nb_inplace_subtract*/
+ 0, /*nb_inplace_multiply*/
+ 0, /*nb_inplace_divide*/
+ 0, /*nb_inplace_remainder*/
+ 0, /*nb_inplace_power*/
+ 0, /*nb_inplace_lshift*/
+ 0, /*nb_inplace_rshift*/
+ 0, /*nb_inplace_and*/
+ 0, /*nb_inplace_xor*/
+ 0, /*nb_inplace_or*/
+ 0, /*nb_floor_divide*/
+ 0, /*nb_true_divide*/
+ 0, /*nb_inplace_floor_divide*/
+ 0, /*nb_inplace_true_divide*/
+};
+
+static PySequenceMethods __pyx_tp_as_sequence_PyCFRunLoop = {
+ 0, /*sq_length*/
+ 0, /*sq_concat*/
+ 0, /*sq_repeat*/
+ 0, /*sq_item*/
+ 0, /*sq_slice*/
+ 0, /*sq_ass_item*/
+ 0, /*sq_ass_slice*/
+ 0, /*sq_contains*/
+ 0, /*sq_inplace_concat*/
+ 0, /*sq_inplace_repeat*/
+};
+
+static PyMappingMethods __pyx_tp_as_mapping_PyCFRunLoop = {
+ 0, /*mp_length*/
+ 0, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
+};
+
+static PyBufferProcs __pyx_tp_as_buffer_PyCFRunLoop = {
+ 0, /*bf_getreadbuffer*/
+ 0, /*bf_getwritebuffer*/
+ 0, /*bf_getsegcount*/
+ 0, /*bf_getcharbuffer*/
+};
+
+statichere PyTypeObject __pyx_type_9cfsupport_PyCFRunLoop = {
+ PyObject_HEAD_INIT(0)
+ 0, /*ob_size*/
+ "cfsupport.PyCFRunLoop", /*tp_name*/
+ sizeof(struct __pyx_obj_9cfsupport_PyCFRunLoop), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ __pyx_tp_dealloc_9cfsupport_PyCFRunLoop, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ &__pyx_tp_as_number_PyCFRunLoop, /*tp_as_number*/
+ &__pyx_tp_as_sequence_PyCFRunLoop, /*tp_as_sequence*/
+ &__pyx_tp_as_mapping_PyCFRunLoop, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ &__pyx_tp_as_buffer_PyCFRunLoop, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+ 0, /*tp_doc*/
+ __pyx_tp_traverse_9cfsupport_PyCFRunLoop, /*tp_traverse*/
+ __pyx_tp_clear_9cfsupport_PyCFRunLoop, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ __pyx_methods_9cfsupport_PyCFRunLoop, /*tp_methods*/
+ __pyx_members_9cfsupport_PyCFRunLoop, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ 0, /*tp_init*/
+ 0, /*tp_alloc*/
+ __pyx_tp_new_9cfsupport_PyCFRunLoop, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+ 0, /*tp_bases*/
+ 0, /*tp_mro*/
+ 0, /*tp_cache*/
+ 0, /*tp_subclasses*/
+ 0, /*tp_weaklist*/
+};
+
+static struct PyMethodDef __pyx_methods[] = {
+ {"now", (PyCFunction)__pyx_f_9cfsupport_now, METH_VARARGS|METH_KEYWORDS, 0},
+ {0, 0, 0, 0}
+};
+
+DL_EXPORT(void) initcfsupport(void); /*proto*/
+DL_EXPORT(void) initcfsupport(void) {
+ PyObject *__pyx_1 = 0;
+ __pyx_m = Py_InitModule4("cfsupport", __pyx_methods, 0, 0, PYTHON_API_VERSION);
+ if (!__pyx_m) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 1; goto __pyx_L1;};
+ __pyx_b = PyImport_AddModule("__builtin__");
+ if (!__pyx_b) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 1; goto __pyx_L1;};
+ if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 1; goto __pyx_L1;};
+ if (__Pyx_InternStrings(__pyx_intern_tab) < 0) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 1; goto __pyx_L1;};
+ if (__Pyx_InitStrings(__pyx_string_tab) < 0) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 1; goto __pyx_L1;};
+ __pyx_type_9cfsupport_PyCFSocket.tp_free = _PyObject_GC_Del;
+ if (PyType_Ready(&__pyx_type_9cfsupport_PyCFSocket) < 0) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 34; goto __pyx_L1;}
+ if (PyObject_SetAttrString(__pyx_m, "PyCFSocket", (PyObject *)&__pyx_type_9cfsupport_PyCFSocket) < 0) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 34; goto __pyx_L1;}
+ __pyx_ptype_9cfsupport_PyCFSocket = &__pyx_type_9cfsupport_PyCFSocket;
+ __pyx_type_9cfsupport_PyCFRunLoopTimer.tp_free = _PyObject_GC_Del;
+ if (PyType_Ready(&__pyx_type_9cfsupport_PyCFRunLoopTimer) < 0) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 5; goto __pyx_L1;}
+ if (PyObject_SetAttrString(__pyx_m, "PyCFRunLoopTimer", (PyObject *)&__pyx_type_9cfsupport_PyCFRunLoopTimer) < 0) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 5; goto __pyx_L1;}
+ __pyx_ptype_9cfsupport_PyCFRunLoopTimer = &__pyx_type_9cfsupport_PyCFRunLoopTimer;
+ __pyx_type_9cfsupport_PyCFRunLoop.tp_free = _PyObject_GC_Del;
+ if (PyType_Ready(&__pyx_type_9cfsupport_PyCFRunLoop) < 0) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 49; goto __pyx_L1;}
+ if (PyObject_SetAttrString(__pyx_m, "PyCFRunLoop", (PyObject *)&__pyx_type_9cfsupport_PyCFRunLoop) < 0) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 49; goto __pyx_L1;}
+ __pyx_ptype_9cfsupport_PyCFRunLoop = &__pyx_type_9cfsupport_PyCFRunLoop;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":1 */
+ __pyx_1 = __Pyx_Import(__pyx_n_traceback, 0); if (!__pyx_1) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 1; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_traceback, __pyx_1) < 0) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 1; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsocket.pxi":45 */
+ Py_INCREF(Py_None);
+ __pyx_k2 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k3 = Py_None;
+ Py_INCREF(Py_None);
+ __pyx_k4 = Py_None;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":1 */
+ __pyx_1 = __Pyx_Import(__pyx_n_traceback, 0); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 1; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_traceback, __pyx_1) < 0) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 1; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfrunloop.pxi":52 */
+ Py_INCREF(Py_None);
+ __pyx_k6 = Py_None;
+
+ /* "/Volumes/Crack/src/Twisted/twisted/internet/cfsupport/cfsupport.pyx":6 */
+ if (PyObject_SetAttr(__pyx_m, __pyx_n___version__, __pyx_k7p) < 0) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 6; goto __pyx_L1;}
+ return;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ __Pyx_AddTraceback("cfsupport");
+}
+
+static char *__pyx_filenames[] = {
+ "cfdate.pxi",
+ "cfsocket.pxi",
+ "cfrunloop.pxi",
+ "cfsupport.pyx",
+};
+statichere char **__pyx_f = __pyx_filenames;
+
+/* Runtime support code */
+
+static int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, int none_allowed, char *name) {
+ if (!type) {
+ PyErr_Format(PyExc_SystemError, "Missing type object");
+ return 0;
+ }
+ if ((none_allowed && obj == Py_None) || PyObject_TypeCheck(obj, type))
+ return 1;
+ PyErr_Format(PyExc_TypeError,
+ "Argument '%s' has incorrect type (expected %s, got %s)",
+ name, type->tp_name, obj->ob_type->tp_name);
+ return 0;
+}
+
+static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list) {
+ PyObject *__import__ = 0;
+ PyObject *empty_list = 0;
+ PyObject *module = 0;
+ PyObject *global_dict = 0;
+ PyObject *empty_dict = 0;
+ PyObject *list;
+ __import__ = PyObject_GetAttrString(__pyx_b, "__import__");
+ if (!__import__)
+ goto bad;
+ if (from_list)
+ list = from_list;
+ else {
+ empty_list = PyList_New(0);
+ if (!empty_list)
+ goto bad;
+ list = empty_list;
+ }
+ global_dict = PyModule_GetDict(__pyx_m);
+ if (!global_dict)
+ goto bad;
+ empty_dict = PyDict_New();
+ if (!empty_dict)
+ goto bad;
+ module = PyObject_CallFunction(__import__, "OOOO",
+ name, global_dict, empty_dict, list);
+bad:
+ Py_XDECREF(empty_list);
+ Py_XDECREF(__import__);
+ Py_XDECREF(empty_dict);
+ return module;
+}
+
+static PyObject *__Pyx_GetExcValue(void) {
+ PyObject *type = 0, *value = 0, *tb = 0;
+ PyObject *result = 0;
+ PyThreadState *tstate = PyThreadState_Get();
+ PyErr_Fetch(&type, &value, &tb);
+ PyErr_NormalizeException(&type, &value, &tb);
+ if (PyErr_Occurred())
+ goto bad;
+ if (!value) {
+ value = Py_None;
+ Py_INCREF(value);
+ }
+ Py_XDECREF(tstate->exc_type);
+ Py_XDECREF(tstate->exc_value);
+ Py_XDECREF(tstate->exc_traceback);
+ tstate->exc_type = type;
+ tstate->exc_value = value;
+ tstate->exc_traceback = tb;
+ result = value;
+ Py_XINCREF(result);
+ type = 0;
+ value = 0;
+ tb = 0;
+bad:
+ Py_XDECREF(type);
+ Py_XDECREF(value);
+ Py_XDECREF(tb);
+ return result;
+}
+
+static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name) {
+ PyObject *result;
+ result = PyObject_GetAttr(dict, name);
+ if (!result)
+ PyErr_SetObject(PyExc_NameError, name);
+ return result;
+}
+
+static void __Pyx_WriteUnraisable(char *name) {
+ PyObject *old_exc, *old_val, *old_tb;
+ PyObject *ctx;
+ PyErr_Fetch(&old_exc, &old_val, &old_tb);
+ ctx = PyString_FromString(name);
+ PyErr_Restore(old_exc, old_val, old_tb);
+ if (!ctx)
+ ctx = Py_None;
+ PyErr_WriteUnraisable(ctx);
+}
+
+static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb) {
+ Py_XINCREF(type);
+ Py_XINCREF(value);
+ Py_XINCREF(tb);
+ /* First, check the traceback argument, replacing None with NULL. */
+ if (tb == Py_None) {
+ Py_DECREF(tb);
+ tb = 0;
+ }
+ else if (tb != NULL && !PyTraceBack_Check(tb)) {
+ PyErr_SetString(PyExc_TypeError,
+ "raise: arg 3 must be a traceback or None");
+ goto raise_error;
+ }
+ /* Next, replace a missing value with None */
+ if (value == NULL) {
+ value = Py_None;
+ Py_INCREF(value);
+ }
+ /* Next, repeatedly, replace a tuple exception with its first item */
+ while (PyTuple_Check(type) && PyTuple_Size(type) > 0) {
+ PyObject *tmp = type;
+ type = PyTuple_GET_ITEM(type, 0);
+ Py_INCREF(type);
+ Py_DECREF(tmp);
+ }
+ if (PyString_Check(type))
+ ;
+ else if (PyClass_Check(type))
+ ; /*PyErr_NormalizeException(&type, &value, &tb);*/
+ else if (PyInstance_Check(type)) {
+ /* Raising an instance. The value should be a dummy. */
+ if (value != Py_None) {
+ PyErr_SetString(PyExc_TypeError,
+ "instance exception may not have a separate value");
+ goto raise_error;
+ }
+ else {
+ /* Normalize to raise <class>, <instance> */
+ Py_DECREF(value);
+ value = type;
+ type = (PyObject*) ((PyInstanceObject*)type)->in_class;
+ Py_INCREF(type);
+ }
+ }
+ else {
+ /* Not something you can raise. You get an exception
+ anyway, just not what you specified :-) */
+ PyErr_Format(PyExc_TypeError,
+ "exceptions must be strings, classes, or "
+ "instances, not %s", type->ob_type->tp_name);
+ goto raise_error;
+ }
+ PyErr_Restore(type, value, tb);
+ return;
+raise_error:
+ Py_XDECREF(value);
+ Py_XDECREF(type);
+ Py_XDECREF(tb);
+ return;
+}
+
+static void __Pyx_ReRaise(void) {
+ PyThreadState *tstate = PyThreadState_Get();
+ PyObject *type = tstate->exc_type;
+ PyObject *value = tstate->exc_value;
+ PyObject *tb = tstate->exc_traceback;
+ Py_XINCREF(type);
+ Py_XINCREF(value);
+ Py_XINCREF(tb);
+ PyErr_Restore(type, value, tb);
+}
+
+static int __Pyx_InternStrings(__Pyx_InternTabEntry *t) {
+ while (t->p) {
+ *t->p = PyString_InternFromString(t->s);
+ if (!*t->p)
+ return -1;
+ ++t;
+ }
+ return 0;
+}
+
+static int __Pyx_InitStrings(__Pyx_StringTabEntry *t) {
+ while (t->p) {
+ *t->p = PyString_FromStringAndSize(t->s, t->n - 1);
+ if (!*t->p)
+ return -1;
+ ++t;
+ }
+ return 0;
+}
+
+#include "compile.h"
+#include "frameobject.h"
+#include "traceback.h"
+
+static void __Pyx_AddTraceback(char *funcname) {
+ PyObject *py_srcfile = 0;
+ PyObject *py_funcname = 0;
+ PyObject *py_globals = 0;
+ PyObject *empty_tuple = 0;
+ PyObject *empty_string = 0;
+ PyCodeObject *py_code = 0;
+ PyFrameObject *py_frame = 0;
+
+ py_srcfile = PyString_FromString(__pyx_filename);
+ if (!py_srcfile) goto bad;
+ py_funcname = PyString_FromString(funcname);
+ if (!py_funcname) goto bad;
+ py_globals = PyModule_GetDict(__pyx_m);
+ if (!py_globals) goto bad;
+ empty_tuple = PyTuple_New(0);
+ if (!empty_tuple) goto bad;
+ empty_string = PyString_FromString("");
+ if (!empty_string) goto bad;
+ py_code = PyCode_New(
+ 0, /*int argcount,*/
+ 0, /*int nlocals,*/
+ 0, /*int stacksize,*/
+ 0, /*int flags,*/
+ empty_string, /*PyObject *code,*/
+ empty_tuple, /*PyObject *consts,*/
+ empty_tuple, /*PyObject *names,*/
+ empty_tuple, /*PyObject *varnames,*/
+ empty_tuple, /*PyObject *freevars,*/
+ empty_tuple, /*PyObject *cellvars,*/
+ py_srcfile, /*PyObject *filename,*/
+ py_funcname, /*PyObject *name,*/
+ __pyx_lineno, /*int firstlineno,*/
+ empty_string /*PyObject *lnotab*/
+ );
+ if (!py_code) goto bad;
+ py_frame = PyFrame_New(
+ PyThreadState_Get(), /*PyThreadState *tstate,*/
+ py_code, /*PyCodeObject *code,*/
+ py_globals, /*PyObject *globals,*/
+ 0 /*PyObject *locals*/
+ );
+ if (!py_frame) goto bad;
+ py_frame->f_lineno = __pyx_lineno;
+ PyTraceBack_Here(py_frame);
+bad:
+ Py_XDECREF(py_srcfile);
+ Py_XDECREF(py_funcname);
+ Py_XDECREF(empty_tuple);
+ Py_XDECREF(empty_string);
+ Py_XDECREF(py_code);
+ Py_XDECREF(py_frame);
+}
diff --git a/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsupport.pyx b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsupport.pyx
new file mode 100644
index 0000000000..0b2afd5de0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/cfsupport.pyx
@@ -0,0 +1,6 @@
+include "python.pxi"
+include "cfdecl.pxi"
+include "cfdate.pxi"
+include "cfsocket.pxi"
+include "cfrunloop.pxi"
+__version__ = '0.4'
diff --git a/vendor/Twisted-10.0.0/twisted/internet/cfsupport/python.pxi b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/python.pxi
new file mode 100644
index 0000000000..2f97458547
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/python.pxi
@@ -0,0 +1,5 @@
+cdef extern from "Python.h":
+ ctypedef void *PyGILState_STATE
+ void PyErr_Clear()
+ PyGILState_STATE PyGILState_Ensure()
+ void PyGILState_Release(PyGILState_STATE)
diff --git a/vendor/Twisted-10.0.0/twisted/internet/cfsupport/setup.py b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/setup.py
new file mode 100644
index 0000000000..e5b3b2d06e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/cfsupport/setup.py
@@ -0,0 +1,50 @@
+from distutils.core import setup
+from distutils.extension import Extension
+try:
+ from Pyrex.Distutils import build_ext
+ # pyrex is available
+ setup(
+ name = 'cfsupport',
+ version = '0.4',
+ description = "Enough CoreFoundation wrappers to deal with CFRunLoop",
+ long_description = "Pythonic wrappers for pieces of Apple's CoreFoundation API's that are not otherwise wrapped by MacPython.\nPrimarily useful for dealing with CFRunLoop.",
+ maintainer = 'Bob Ippolito',
+ maintainer_email = 'bob@redivi.com',
+ license = 'Python',
+ platforms = ['Mac OSX'],
+ keywords = ['CoreFoundation', 'CFRunLoop', 'Cocoa', 'GUI'],
+ ext_modules=[
+ Extension(
+ 'cfsupport',
+ ['cfsupport.pyx'],
+ extra_link_args=[
+ '-framework','CoreFoundation',
+ '-framework','CoreServices',
+ ],
+ ),
+ ],
+ cmdclass = {'build_ext': build_ext}
+ )
+except ImportError:
+ # pyrex is not available, use existing .c
+ setup(
+ name = 'cfsupport',
+ version = '0.4',
+ description = "Enough CoreFoundation wrappers to deal with CFRunLoop",
+ long_description = "Pythonic wrappers for pieces of Apple's CoreFoundation API's that are not otherwise wrapped by MacPython.\nPrimarily useful for dealing with CFRunLoop.",
+ maintainer = 'Bob Ippolito',
+ maintainer_email = 'bob@redivi.com',
+ license = 'Python',
+ platforms = ['Mac OSX'],
+ keywords = ['CoreFoundation', 'CFRunLoop', 'Cocoa', 'GUI'],
+ ext_modules=[
+ Extension(
+ 'cfsupport',
+ ['cfsupport.c'],
+ extra_link_args=[
+ '-framework','CoreFoundation',
+ '-framework','CoreServices',
+ ],
+ ),
+ ],
+ )
diff --git a/vendor/Twisted-10.0.0/twisted/internet/default.py b/vendor/Twisted-10.0.0/twisted/internet/default.py
new file mode 100644
index 0000000000..f158c94001
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/default.py
@@ -0,0 +1,21 @@
+# -*- test-case-name: twisted.test.test_internet -*-
+# $Id: default.py,v 1.90 2004/01/06 22:35:22 warner Exp $
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Deprecated module that used to contain SelectReactor and PosixReactorBase
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+import warnings
+warnings.warn("twisted.internet.default is deprecated. Use posixbase or selectreactor instead.", category=DeprecationWarning)
+
+# Backwards compat
+from posixbase import PosixReactorBase
+from selectreactor import SelectReactor, install
+
+__all__ = ["install", "PosixReactorBase", "SelectReactor"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/defer.py b/vendor/Twisted-10.0.0/twisted/internet/defer.py
new file mode 100644
index 0000000000..afc2db2fdb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/defer.py
@@ -0,0 +1,1264 @@
+# -*- test-case-name: twisted.test.test_defer,twisted.test.test_defgen,twisted.internet.test.test_inlinecb -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Support for results that aren't immediately available.
+
+Maintainer: Glyph Lefkowitz
+"""
+
+import traceback
+import warnings
+from sys import exc_info
+
+# Twisted imports
+from twisted.python import log, failure, lockfile
+from twisted.python.util import unsignedID, mergeFunctionMetadata
+
+
+
+class AlreadyCalledError(Exception):
+ pass
+
+
+
+class TimeoutError(Exception):
+ pass
+
+
+
+def logError(err):
+ log.err(err)
+ return err
+
+
+
+def succeed(result):
+ """
+ Return a L{Deferred} that has already had C{.callback(result)} called.
+
+ This is useful when you're writing synchronous code to an
+ asynchronous interface: i.e., some code is calling you expecting a
+ L{Deferred} result, but you don't actually need to do anything
+ asynchronous. Just return C{defer.succeed(theResult)}.
+
+ See L{fail} for a version of this function that uses a failing
+ L{Deferred} rather than a successful one.
+
+ @param result: The result to give to the Deferred's 'callback'
+ method.
+
+ @rtype: L{Deferred}
+ """
+ d = Deferred()
+ d.callback(result)
+ return d
+
+
+
+def fail(result=None):
+ """
+ Return a L{Deferred} that has already had C{.errback(result)} called.
+
+ See L{succeed}'s docstring for rationale.
+
+ @param result: The same argument that L{Deferred.errback} takes.
+
+ @raise NoCurrentExceptionError: If C{result} is C{None} but there is no
+ current exception state.
+
+ @rtype: L{Deferred}
+ """
+ d = Deferred()
+ d.errback(result)
+ return d
+
+
+
+def execute(callable, *args, **kw):
+ """
+ Create a L{Deferred} from a callable and arguments.
+
+ Call the given function with the given arguments. Return a L{Deferred}
+ which has been fired with its callback as the result of that invocation
+ or its C{errback} with a L{Failure} for the exception thrown.
+ """
+ try:
+ result = callable(*args, **kw)
+ except:
+ return fail()
+ else:
+ return succeed(result)
+
+
+
+def maybeDeferred(f, *args, **kw):
+ """
+ Invoke a function that may or may not return a L{Deferred}.
+
+ Call the given function with the given arguments. If the returned
+ object is a L{Deferred}, return it. If the returned object is a L{Failure},
+ wrap it with L{fail} and return it. Otherwise, wrap it in L{succeed} and
+ return it. If an exception is raised, convert it to a L{Failure}, wrap it
+ in L{fail}, and then return it.
+
+ @type f: Any callable
+ @param f: The callable to invoke
+
+ @param args: The arguments to pass to C{f}
+ @param kw: The keyword arguments to pass to C{f}
+
+ @rtype: L{Deferred}
+ @return: The result of the function call, wrapped in a L{Deferred} if
+ necessary.
+ """
+ try:
+ result = f(*args, **kw)
+ except:
+ return fail(failure.Failure())
+
+ if isinstance(result, Deferred):
+ return result
+ elif isinstance(result, failure.Failure):
+ return fail(result)
+ else:
+ return succeed(result)
+
+
+
+def timeout(deferred):
+ deferred.errback(failure.Failure(TimeoutError("Callback timed out")))
+
+
+
+def passthru(arg):
+ return arg
+
+
+
+def setDebugging(on):
+ """
+ Enable or disable L{Deferred} debugging.
+
+ When debugging is on, the call stacks from creation and invocation are
+ recorded, and added to any L{AlreadyCalledErrors} we raise.
+ """
+ Deferred.debug=bool(on)
+
+
+
+def getDebugging():
+ """
+ Determine whether L{Deferred} debugging is enabled.
+ """
+ return Deferred.debug
+
+
+
+class Deferred:
+ """
+ This is a callback which will be put off until later.
+
+ Why do we want this? Well, in cases where a function in a threaded
+ program would block until it gets a result, for Twisted it should
+ not block. Instead, it should return a L{Deferred}.
+
+ This can be implemented for protocols that run over the network by
+ writing an asynchronous protocol for L{twisted.internet}. For methods
+ that come from outside packages that are not under our control, we use
+ threads (see for example L{twisted.enterprise.adbapi}).
+
+ For more information about Deferreds, see doc/howto/defer.html or
+ U{http://twistedmatrix.com/projects/core/documentation/howto/defer.html}
+ """
+
+ called = 0
+ paused = 0
+ timeoutCall = None
+ _debugInfo = None
+
+ # Are we currently running a user-installed callback? Meant to prevent
+ # recursive running of callbacks when a reentrant call to add a callback is
+ # used.
+ _runningCallbacks = False
+
+ # Keep this class attribute for now, for compatibility with code that
+ # sets it directly.
+ debug = False
+
+
+ def __init__(self):
+ self.callbacks = []
+ if self.debug:
+ self._debugInfo = DebugInfo()
+ self._debugInfo.creator = traceback.format_stack()[:-1]
+
+
+ def addCallbacks(self, callback, errback=None,
+ callbackArgs=None, callbackKeywords=None,
+ errbackArgs=None, errbackKeywords=None):
+ """
+ Add a pair of callbacks (success and error) to this L{Deferred}.
+
+ These will be executed when the 'master' callback is run.
+ """
+ assert callable(callback)
+ assert errback == None or callable(errback)
+ cbs = ((callback, callbackArgs, callbackKeywords),
+ (errback or (passthru), errbackArgs, errbackKeywords))
+ self.callbacks.append(cbs)
+
+ if self.called:
+ self._runCallbacks()
+ return self
+
+
+ def addCallback(self, callback, *args, **kw):
+ """
+ Convenience method for adding just a callback.
+
+ See L{addCallbacks}.
+ """
+ return self.addCallbacks(callback, callbackArgs=args,
+ callbackKeywords=kw)
+
+
+ def addErrback(self, errback, *args, **kw):
+ """
+ Convenience method for adding just an errback.
+
+ See L{addCallbacks}.
+ """
+ return self.addCallbacks(passthru, errback,
+ errbackArgs=args,
+ errbackKeywords=kw)
+
+
+ def addBoth(self, callback, *args, **kw):
+ """
+ Convenience method for adding a single callable as both a callback
+ and an errback.
+
+ See L{addCallbacks}.
+ """
+ return self.addCallbacks(callback, callback,
+ callbackArgs=args, errbackArgs=args,
+ callbackKeywords=kw, errbackKeywords=kw)
+
+
+ def chainDeferred(self, d):
+ """
+ Chain another L{Deferred} to this L{Deferred}.
+
+ This method adds callbacks to this L{Deferred} to call C{d}'s callback
+ or errback, as appropriate. It is merely a shorthand way of performing
+ the following::
+
+ self.addCallbacks(d.callback, d.errback)
+
+ When you chain a deferred d2 to another deferred d1 with
+ d1.chainDeferred(d2), you are making d2 participate in the callback
+ chain of d1. Thus any event that fires d1 will also fire d2.
+ However, the converse is B{not} true; if d2 is fired d1 will not be
+ affected.
+ """
+ return self.addCallbacks(d.callback, d.errback)
+
+
+ def callback(self, result):
+ """
+ Run all success callbacks that have been added to this L{Deferred}.
+
+ Each callback will have its result passed as the first
+ argument to the next; this way, the callbacks act as a
+ 'processing chain'. Also, if the success-callback returns a L{Failure}
+ or raises an L{Exception}, processing will continue on the *error*-
+ callback chain.
+ """
+ assert not isinstance(result, Deferred)
+ self._startRunCallbacks(result)
+
+
+ def errback(self, fail=None):
+ """
+ Run all error callbacks that have been added to this L{Deferred}.
+
+ Each callback will have its result passed as the first
+ argument to the next; this way, the callbacks act as a
+ 'processing chain'. Also, if the error-callback returns a non-Failure
+ or doesn't raise an L{Exception}, processing will continue on the
+ *success*-callback chain.
+
+ If the argument that's passed to me is not a L{failure.Failure} instance,
+ it will be embedded in one. If no argument is passed, a
+ L{failure.Failure} instance will be created based on the current
+ traceback stack.
+
+ Passing a string as `fail' is deprecated, and will be punished with
+ a warning message.
+
+ @raise NoCurrentExceptionError: If C{fail} is C{None} but there is
+ no current exception state.
+ """
+ if not isinstance(fail, failure.Failure):
+ fail = failure.Failure(fail)
+
+ self._startRunCallbacks(fail)
+
+
+ def pause(self):
+ """
+ Stop processing on a L{Deferred} until L{unpause}() is called.
+ """
+ self.paused = self.paused + 1
+
+
+ def unpause(self):
+ """
+ Process all callbacks made since L{pause}() was called.
+ """
+ self.paused = self.paused - 1
+ if self.paused:
+ return
+ if self.called:
+ self._runCallbacks()
+
+
+ def _continue(self, result):
+ self.result = result
+ self.unpause()
+
+
+ def _startRunCallbacks(self, result):
+ if self.called:
+ if self.debug:
+ if self._debugInfo is None:
+ self._debugInfo = DebugInfo()
+ extra = "\n" + self._debugInfo._getDebugTracebacks()
+ raise AlreadyCalledError(extra)
+ raise AlreadyCalledError
+ if self.debug:
+ if self._debugInfo is None:
+ self._debugInfo = DebugInfo()
+ self._debugInfo.invoker = traceback.format_stack()[:-2]
+ self.called = True
+ self.result = result
+ if self.timeoutCall:
+ try:
+ self.timeoutCall.cancel()
+ except:
+ pass
+
+ del self.timeoutCall
+ self._runCallbacks()
+
+
+ def _runCallbacks(self):
+ if self._runningCallbacks:
+ # Don't recursively run callbacks
+ return
+ if not self.paused:
+ while self.callbacks:
+ item = self.callbacks.pop(0)
+ callback, args, kw = item[
+ isinstance(self.result, failure.Failure)]
+ args = args or ()
+ kw = kw or {}
+ try:
+ self._runningCallbacks = True
+ try:
+ self.result = callback(self.result, *args, **kw)
+ finally:
+ self._runningCallbacks = False
+ if isinstance(self.result, Deferred):
+ # note: this will cause _runCallbacks to be called
+ # recursively if self.result already has a result.
+ # This shouldn't cause any problems, since there is no
+ # relevant state in this stack frame at this point.
+ # The recursive call will continue to process
+ # self.callbacks until it is empty, then return here,
+ # where there is no more work to be done, so this call
+ # will return as well.
+ self.pause()
+ self.result.addBoth(self._continue)
+ break
+ except:
+ self.result = failure.Failure()
+
+ if isinstance(self.result, failure.Failure):
+ self.result.cleanFailure()
+ if self._debugInfo is None:
+ self._debugInfo = DebugInfo()
+ self._debugInfo.failResult = self.result
+ else:
+ if self._debugInfo is not None:
+ self._debugInfo.failResult = None
+
+
+ def setTimeout(self, seconds, timeoutFunc=timeout, *args, **kw):
+ """
+ Set a timeout function to be triggered if I am not called.
+
+ @param seconds: How long to wait (from now) before firing the
+ C{timeoutFunc}.
+
+ @param timeoutFunc: will receive the L{Deferred} and *args, **kw as its
+ arguments. The default C{timeoutFunc} will call the errback with a
+ L{TimeoutError}.
+ """
+ warnings.warn(
+ "Deferred.setTimeout is deprecated. Look for timeout "
+ "support specific to the API you are using instead.",
+ DeprecationWarning, stacklevel=2)
+
+ if self.called:
+ return
+ assert not self.timeoutCall, "Don't call setTimeout twice on the same Deferred."
+
+ from twisted.internet import reactor
+ self.timeoutCall = reactor.callLater(
+ seconds,
+ lambda: self.called or timeoutFunc(self, *args, **kw))
+ return self.timeoutCall
+
+ def __str__(self):
+ cname = self.__class__.__name__
+ if hasattr(self, 'result'):
+ return "<%s at %s current result: %r>" % (cname, hex(unsignedID(self)),
+ self.result)
+ return "<%s at %s>" % (cname, hex(unsignedID(self)))
+ __repr__ = __str__
+
+
+
+class DebugInfo:
+ """
+ Deferred debug helper.
+ """
+
+ failResult = None
+
+
+ def _getDebugTracebacks(self):
+ info = ''
+ if hasattr(self, "creator"):
+ info += " C: Deferred was created:\n C:"
+ info += "".join(self.creator).rstrip().replace("\n","\n C:")
+ info += "\n"
+ if hasattr(self, "invoker"):
+ info += " I: First Invoker was:\n I:"
+ info += "".join(self.invoker).rstrip().replace("\n","\n I:")
+ info += "\n"
+ return info
+
+
+ def __del__(self):
+ """
+ Print tracebacks and die.
+
+ If the *last* (and I do mean *last*) callback leaves me in an error
+ state, print a traceback (if said errback is a L{Failure}).
+ """
+ if self.failResult is not None:
+ log.msg("Unhandled error in Deferred:", isError=True)
+ debugInfo = self._getDebugTracebacks()
+ if debugInfo != '':
+ log.msg("(debug: " + debugInfo + ")", isError=True)
+ log.err(self.failResult)
+
+
+
+class FirstError(Exception):
+ """
+ First error to occur in a L{DeferredList} if C{fireOnOneErrback} is set.
+
+ @ivar subFailure: The L{Failure} that occurred.
+ @type subFailure: L{Failure}
+
+ @ivar index: The index of the L{Deferred} in the L{DeferredList} where
+ it happened.
+ @type index: C{int}
+ """
+ def __init__(self, failure, index):
+ Exception.__init__(self, failure, index)
+ self.subFailure = failure
+ self.index = index
+
+
+ def __repr__(self):
+ """
+ The I{repr} of L{FirstError} instances includes the repr of the
+ wrapped failure's exception and the index of the L{FirstError}.
+ """
+ return 'FirstError[#%d, %r]' % (self.index, self.subFailure.value)
+
+
+ def __str__(self):
+ """
+ The I{str} of L{FirstError} instances includes the I{str} of the
+ entire wrapped failure (including its traceback and exception) and
+ the index of the L{FirstError}.
+ """
+ return 'FirstError[#%d, %s]' % (self.index, self.subFailure)
+
+
+ def __cmp__(self, other):
+ """
+ Comparison between L{FirstError} and other L{FirstError} instances
+ is defined as the comparison of the index and sub-failure of each
+ instance. L{FirstError} instances don't compare equal to anything
+ that isn't a L{FirstError} instance.
+
+ @since: 8.2
+ """
+ if isinstance(other, FirstError):
+ return cmp(
+ (self.index, self.subFailure),
+ (other.index, other.subFailure))
+ return -1
+
+
+
+class DeferredList(Deferred):
+ """
+ I combine a group of deferreds into one callback.
+
+ I track a list of L{Deferred}s for their callbacks, and make a single
+ callback when they have all completed, a list of (success, result)
+ tuples, 'success' being a boolean.
+
+ Note that you can still use a L{Deferred} after putting it in a
+ DeferredList. For example, you can suppress 'Unhandled error in Deferred'
+ messages by adding errbacks to the Deferreds *after* putting them in the
+ DeferredList, as a DeferredList won't swallow the errors. (Although a more
+ convenient way to do this is simply to set the consumeErrors flag)
+ """
+
+ fireOnOneCallback = 0
+ fireOnOneErrback = 0
+
+
+ def __init__(self, deferredList, fireOnOneCallback=0, fireOnOneErrback=0,
+ consumeErrors=0):
+ """
+ Initialize a DeferredList.
+
+ @type deferredList: C{list} of L{Deferred}s
+ @param deferredList: The list of deferreds to track.
+ @param fireOnOneCallback: (keyword param) a flag indicating that
+ only one callback needs to be fired for me to call
+ my callback
+ @param fireOnOneErrback: (keyword param) a flag indicating that
+ only one errback needs to be fired for me to call
+ my errback
+ @param consumeErrors: (keyword param) a flag indicating that any errors
+ raised in the original deferreds should be
+ consumed by this DeferredList. This is useful to
+ prevent spurious warnings being logged.
+ """
+ self.resultList = [None] * len(deferredList)
+ Deferred.__init__(self)
+ if len(deferredList) == 0 and not fireOnOneCallback:
+ self.callback(self.resultList)
+
+ # These flags need to be set *before* attaching callbacks to the
+ # deferreds, because the callbacks use these flags, and will run
+ # synchronously if any of the deferreds are already fired.
+ self.fireOnOneCallback = fireOnOneCallback
+ self.fireOnOneErrback = fireOnOneErrback
+ self.consumeErrors = consumeErrors
+ self.finishedCount = 0
+
+ index = 0
+ for deferred in deferredList:
+ deferred.addCallbacks(self._cbDeferred, self._cbDeferred,
+ callbackArgs=(index,SUCCESS),
+ errbackArgs=(index,FAILURE))
+ index = index + 1
+
+
+ def _cbDeferred(self, result, index, succeeded):
+ """
+ (internal) Callback for when one of my deferreds fires.
+ """
+ self.resultList[index] = (succeeded, result)
+
+ self.finishedCount += 1
+ if not self.called:
+ if succeeded == SUCCESS and self.fireOnOneCallback:
+ self.callback((result, index))
+ elif succeeded == FAILURE and self.fireOnOneErrback:
+ self.errback(failure.Failure(FirstError(result, index)))
+ elif self.finishedCount == len(self.resultList):
+ self.callback(self.resultList)
+
+ if succeeded == FAILURE and self.consumeErrors:
+ result = None
+
+ return result
+
+
+
+def _parseDListResult(l, fireOnOneErrback=0):
+ if __debug__:
+ for success, value in l:
+ assert success
+ return [x[1] for x in l]
+
+
+
+def gatherResults(deferredList):
+ """
+ Returns list with result of given L{Deferred}s.
+
+ This builds on L{DeferredList} but is useful since you don't
+ need to parse the result for success/failure.
+
+ @type deferredList: C{list} of L{Deferred}s
+ """
+ d = DeferredList(deferredList, fireOnOneErrback=1)
+ d.addCallback(_parseDListResult)
+ return d
+
+
+
+# Constants for use with DeferredList
+
+SUCCESS = True
+FAILURE = False
+
+
+
+## deferredGenerator
+
+class waitForDeferred:
+ """
+ See L{deferredGenerator}.
+ """
+
+ def __init__(self, d):
+ if not isinstance(d, Deferred):
+ raise TypeError("You must give waitForDeferred a Deferred. You gave it %r." % (d,))
+ self.d = d
+
+
+ def getResult(self):
+ if isinstance(self.result, failure.Failure):
+ self.result.raiseException()
+ return self.result
+
+
+
+def _deferGenerator(g, deferred):
+ """
+ See L{deferredGenerator}.
+ """
+ result = None
+
+ # This function is complicated by the need to prevent unbounded recursion
+ # arising from repeatedly yielding immediately ready deferreds. This while
+ # loop and the waiting variable solve that by manually unfolding the
+ # recursion.
+
+ waiting = [True, # defgen is waiting for result?
+ None] # result
+
+ while 1:
+ try:
+ result = g.next()
+ except StopIteration:
+ deferred.callback(result)
+ return deferred
+ except:
+ deferred.errback()
+ return deferred
+
+ # Deferred.callback(Deferred) raises an error; we catch this case
+ # early here and give a nicer error message to the user in case
+ # they yield a Deferred.
+ if isinstance(result, Deferred):
+ return fail(TypeError("Yield waitForDeferred(d), not d!"))
+
+ if isinstance(result, waitForDeferred):
+ # a waitForDeferred was yielded, get the result.
+ # Pass result in so it don't get changed going around the loop
+ # This isn't a problem for waiting, as it's only reused if
+ # gotResult has already been executed.
+ def gotResult(r, result=result):
+ result.result = r
+ if waiting[0]:
+ waiting[0] = False
+ waiting[1] = r
+ else:
+ _deferGenerator(g, deferred)
+ result.d.addBoth(gotResult)
+ if waiting[0]:
+ # Haven't called back yet, set flag so that we get reinvoked
+ # and return from the loop
+ waiting[0] = False
+ return deferred
+ # Reset waiting to initial values for next loop
+ waiting[0] = True
+ waiting[1] = None
+
+ result = None
+
+
+
+def deferredGenerator(f):
+ """
+ deferredGenerator and waitForDeferred help you write L{Deferred}-using code
+ that looks like a regular sequential function. If your code has a minimum
+ requirement of Python 2.5, consider the use of L{inlineCallbacks} instead,
+ which can accomplish the same thing in a more concise manner.
+
+ There are two important functions involved: L{waitForDeferred}, and
+ L{deferredGenerator}. They are used together, like this::
+
+ def thingummy():
+ thing = waitForDeferred(makeSomeRequestResultingInDeferred())
+ yield thing
+ thing = thing.getResult()
+ print thing #the result! hoorj!
+ thingummy = deferredGenerator(thingummy)
+
+ L{waitForDeferred} returns something that you should immediately yield; when
+ your generator is resumed, calling C{thing.getResult()} will either give you
+ the result of the L{Deferred} if it was a success, or raise an exception if it
+ was a failure. Calling C{getResult} is B{absolutely mandatory}. If you do
+ not call it, I{your program will not work}.
+
+ L{deferredGenerator} takes one of these waitForDeferred-using generator
+ functions and converts it into a function that returns a L{Deferred}. The
+ result of the L{Deferred} will be the last value that your generator yielded
+ unless the last value is a L{waitForDeferred} instance, in which case the
+ result will be C{None}. If the function raises an unhandled exception, the
+ L{Deferred} will errback instead. Remember that C{return result} won't work;
+ use C{yield result; return} in place of that.
+
+ Note that not yielding anything from your generator will make the L{Deferred}
+ result in C{None}. Yielding a L{Deferred} from your generator is also an error
+ condition; always yield C{waitForDeferred(d)} instead.
+
+ The L{Deferred} returned from your deferred generator may also errback if your
+ generator raised an exception. For example::
+
+ def thingummy():
+ thing = waitForDeferred(makeSomeRequestResultingInDeferred())
+ yield thing
+ thing = thing.getResult()
+ if thing == 'I love Twisted':
+ # will become the result of the Deferred
+ yield 'TWISTED IS GREAT!'
+ return
+ else:
+ # will trigger an errback
+ raise Exception('DESTROY ALL LIFE')
+ thingummy = deferredGenerator(thingummy)
+
+ Put succinctly, these functions connect deferred-using code with this 'fake
+ blocking' style in both directions: L{waitForDeferred} converts from a
+ L{Deferred} to the 'blocking' style, and L{deferredGenerator} converts from the
+ 'blocking' style to a L{Deferred}.
+ """
+
+ def unwindGenerator(*args, **kwargs):
+ return _deferGenerator(f(*args, **kwargs), Deferred())
+ return mergeFunctionMetadata(f, unwindGenerator)
+
+
+## inlineCallbacks
+
+# BaseException is only in Py 2.5.
+try:
+ BaseException
+except NameError:
+ BaseException=Exception
+
+
+
+class _DefGen_Return(BaseException):
+ def __init__(self, value):
+ self.value = value
+
+
+
+def returnValue(val):
+ """
+ Return val from a L{inlineCallbacks} generator.
+
+ Note: this is currently implemented by raising an exception
+ derived from L{BaseException}. You might want to change any
+ 'except:' clauses to an 'except Exception:' clause so as not to
+ catch this exception.
+
+ Also: while this function currently will work when called from
+ within arbitrary functions called from within the generator, do
+ not rely upon this behavior.
+ """
+ raise _DefGen_Return(val)
+
+
+
+def _inlineCallbacks(result, g, deferred):
+ """
+ See L{inlineCallbacks}.
+ """
+ # This function is complicated by the need to prevent unbounded recursion
+ # arising from repeatedly yielding immediately ready deferreds. This while
+ # loop and the waiting variable solve that by manually unfolding the
+ # recursion.
+
+ waiting = [True, # waiting for result?
+ None] # result
+
+ while 1:
+ try:
+ # Send the last result back as the result of the yield expression.
+ isFailure = isinstance(result, failure.Failure)
+ if isFailure:
+ result = result.throwExceptionIntoGenerator(g)
+ else:
+ result = g.send(result)
+ except StopIteration:
+ # fell off the end, or "return" statement
+ deferred.callback(None)
+ return deferred
+ except _DefGen_Return, e:
+ # returnValue() was called; time to give a result to the original
+ # Deferred. First though, let's try to identify the potentially
+ # confusing situation which results when returnValue() is
+ # accidentally invoked from a different function, one that wasn't
+ # decorated with @inlineCallbacks.
+
+ # The traceback starts in this frame (the one for
+ # _inlineCallbacks); the next one down should be the application
+ # code.
+ appCodeTrace = exc_info()[2].tb_next
+ if isFailure:
+ # If we invoked this generator frame by throwing an exception
+ # into it, then throwExceptionIntoGenerator will consume an
+ # additional stack frame itself, so we need to skip that too.
+ appCodeTrace = appCodeTrace.tb_next
+ # Now that we've identified the frame being exited by the
+ # exception, let's figure out if returnValue was called from it
+ # directly. returnValue itself consumes a stack frame, so the
+ # application code will have a tb_next, but it will *not* have a
+ # second tb_next.
+ if appCodeTrace.tb_next.tb_next:
+ # If returnValue was invoked non-local to the frame which it is
+ # exiting, identify the frame that ultimately invoked
+ # returnValue so that we can warn the user, as this behavior is
+ # confusing.
+ ultimateTrace = appCodeTrace
+ while ultimateTrace.tb_next.tb_next:
+ ultimateTrace = ultimateTrace.tb_next
+ filename = ultimateTrace.tb_frame.f_code.co_filename
+ lineno = ultimateTrace.tb_lineno
+ warnings.warn_explicit(
+ "returnValue() in %r causing %r to exit: "
+ "returnValue should only be invoked by functions decorated "
+ "with inlineCallbacks" % (
+ ultimateTrace.tb_frame.f_code.co_name,
+ appCodeTrace.tb_frame.f_code.co_name),
+ DeprecationWarning, filename, lineno)
+ deferred.callback(e.value)
+ return deferred
+ except:
+ deferred.errback()
+ return deferred
+
+ if isinstance(result, Deferred):
+ # a deferred was yielded, get the result.
+ def gotResult(r):
+ if waiting[0]:
+ waiting[0] = False
+ waiting[1] = r
+ else:
+ _inlineCallbacks(r, g, deferred)
+
+ result.addBoth(gotResult)
+ if waiting[0]:
+ # Haven't called back yet, set flag so that we get reinvoked
+ # and return from the loop
+ waiting[0] = False
+ return deferred
+
+ result = waiting[1]
+ # Reset waiting to initial values for next loop. gotResult uses
+ # waiting, but this isn't a problem because gotResult is only
+ # executed once, and if it hasn't been executed yet, the return
+ # branch above would have been taken.
+
+
+ waiting[0] = True
+ waiting[1] = None
+
+
+ return deferred
+
+
+
+def inlineCallbacks(f):
+ """
+ WARNING: this function will not work in Python 2.4 and earlier!
+
+ inlineCallbacks helps you write Deferred-using code that looks like a
+ regular sequential function. This function uses features of Python 2.5
+ generators. If you need to be compatible with Python 2.4 or before, use
+ the L{deferredGenerator} function instead, which accomplishes the same
+ thing, but with somewhat more boilerplate. For example::
+
+ def thingummy():
+ thing = yield makeSomeRequestResultingInDeferred()
+ print thing #the result! hoorj!
+ thingummy = inlineCallbacks(thingummy)
+
+ When you call anything that results in a L{Deferred}, you can simply yield it;
+ your generator will automatically be resumed when the Deferred's result is
+ available. The generator will be sent the result of the L{Deferred} with the
+ 'send' method on generators, or if the result was a failure, 'throw'.
+
+ Your inlineCallbacks-enabled generator will return a L{Deferred} object, which
+ will result in the return value of the generator (or will fail with a
+ failure object if your generator raises an unhandled exception). Note that
+ you can't use C{return result} to return a value; use C{returnValue(result)}
+ instead. Falling off the end of the generator, or simply using C{return}
+ will cause the L{Deferred} to have a result of C{None}.
+
+ The L{Deferred} returned from your deferred generator may errback if your
+ generator raised an exception::
+
+ def thingummy():
+ thing = yield makeSomeRequestResultingInDeferred()
+ if thing == 'I love Twisted':
+ # will become the result of the Deferred
+ returnValue('TWISTED IS GREAT!')
+ else:
+ # will trigger an errback
+ raise Exception('DESTROY ALL LIFE')
+ thingummy = inlineCallbacks(thingummy)
+ """
+ def unwindGenerator(*args, **kwargs):
+ return _inlineCallbacks(None, f(*args, **kwargs), Deferred())
+ return mergeFunctionMetadata(f, unwindGenerator)
+
+
+## DeferredLock/DeferredQueue
+
+class _ConcurrencyPrimitive(object):
+ def __init__(self):
+ self.waiting = []
+
+
+ def _releaseAndReturn(self, r):
+ self.release()
+ return r
+
+
+ def run(*args, **kwargs):
+ """
+ Acquire, run, release.
+
+ This function takes a callable as its first argument and any
+ number of other positional and keyword arguments. When the
+ lock or semaphore is acquired, the callable will be invoked
+ with those arguments.
+
+ The callable may return a L{Deferred}; if it does, the lock or
+ semaphore won't be released until that L{Deferred} fires.
+
+ @return: L{Deferred} of function result.
+ """
+ if len(args) < 2:
+ if not args:
+ raise TypeError("run() takes at least 2 arguments, none given.")
+ raise TypeError("%s.run() takes at least 2 arguments, 1 given" % (
+ args[0].__class__.__name__,))
+ self, f = args[:2]
+ args = args[2:]
+
+ def execute(ignoredResult):
+ d = maybeDeferred(f, *args, **kwargs)
+ d.addBoth(self._releaseAndReturn)
+ return d
+
+ d = self.acquire()
+ d.addCallback(execute)
+ return d
+
+
+
+class DeferredLock(_ConcurrencyPrimitive):
+ """
+ A lock for event driven systems.
+
+ @ivar locked: C{True} when this Lock has been acquired, false at all
+ other times. Do not change this value, but it is useful to
+ examine for the equivalent of a "non-blocking" acquisition.
+ """
+
+ locked = 0
+
+
+ def acquire(self):
+ """
+ Attempt to acquire the lock. Returns a L{Deferred} that fires on
+ lock acquisition with the L{DeferredLock} as the value. If the lock
+ is locked, then the Deferred is placed at the end of a waiting list.
+
+ @return: a L{Deferred} which fires on lock acquisition.
+ @rtype: a L{Deferred}
+ """
+ d = Deferred()
+ if self.locked:
+ self.waiting.append(d)
+ else:
+ self.locked = 1
+ d.callback(self)
+ return d
+
+
+ def release(self):
+ """
+ Release the lock. If there is a waiting list, then the first
+ L{Deferred} in that waiting list will be called back.
+
+ Should be called by whomever did the L{acquire}() when the shared
+ resource is free.
+ """
+ assert self.locked, "Tried to release an unlocked lock"
+ self.locked = 0
+ if self.waiting:
+ # someone is waiting to acquire lock
+ self.locked = 1
+ d = self.waiting.pop(0)
+ d.callback(self)
+
+
+
+class DeferredSemaphore(_ConcurrencyPrimitive):
+ """
+ A semaphore for event driven systems.
+ """
+
+
+ def __init__(self, tokens):
+ _ConcurrencyPrimitive.__init__(self)
+ self.tokens = tokens
+ self.limit = tokens
+
+
+ def acquire(self):
+ """
+ Attempt to acquire the token.
+
+ @return: a L{Deferred} which fires on token acquisition.
+ """
+ assert self.tokens >= 0, "Internal inconsistency?? tokens should never be negative"
+ d = Deferred()
+ if not self.tokens:
+ self.waiting.append(d)
+ else:
+ self.tokens = self.tokens - 1
+ d.callback(self)
+ return d
+
+
+ def release(self):
+ """
+ Release the token.
+
+ Should be called by whoever did the L{acquire}() when the shared
+ resource is free.
+ """
+ assert self.tokens < self.limit, "Someone released me too many times: too many tokens!"
+ self.tokens = self.tokens + 1
+ if self.waiting:
+ # someone is waiting to acquire token
+ self.tokens = self.tokens - 1
+ d = self.waiting.pop(0)
+ d.callback(self)
+
+
+
+class QueueOverflow(Exception):
+ pass
+
+
+
+class QueueUnderflow(Exception):
+ pass
+
+
+
+class DeferredQueue(object):
+ """
+ An event driven queue.
+
+ Objects may be added as usual to this queue. When an attempt is
+ made to retrieve an object when the queue is empty, a L{Deferred} is
+ returned which will fire when an object becomes available.
+
+ @ivar size: The maximum number of objects to allow into the queue
+ at a time. When an attempt to add a new object would exceed this
+ limit, L{QueueOverflow} is raised synchronously. C{None} for no limit.
+
+ @ivar backlog: The maximum number of L{Deferred} gets to allow at
+ one time. When an attempt is made to get an object which would
+ exceed this limit, L{QueueUnderflow} is raised synchronously. C{None}
+ for no limit.
+ """
+
+ def __init__(self, size=None, backlog=None):
+ self.waiting = []
+ self.pending = []
+ self.size = size
+ self.backlog = backlog
+
+
+ def put(self, obj):
+ """
+ Add an object to this queue.
+
+ @raise QueueOverflow: Too many objects are in this queue.
+ """
+ if self.waiting:
+ self.waiting.pop(0).callback(obj)
+ elif self.size is None or len(self.pending) < self.size:
+ self.pending.append(obj)
+ else:
+ raise QueueOverflow()
+
+
+ def get(self):
+ """
+ Attempt to retrieve and remove an object from the queue.
+
+ @return: a L{Deferred} which fires with the next object available in
+ the queue.
+
+ @raise QueueUnderflow: Too many (more than C{backlog})
+ L{Deferred}s are already waiting for an object from this queue.
+ """
+ if self.pending:
+ return succeed(self.pending.pop(0))
+ elif self.backlog is None or len(self.waiting) < self.backlog:
+ d = Deferred()
+ self.waiting.append(d)
+ return d
+ else:
+ raise QueueUnderflow()
+
+
+
+class AlreadyTryingToLockError(Exception):
+ """
+ Raised when L{DeferredFilesystemLock.deferUntilLocked} is called twice on a
+ single L{DeferredFilesystemLock}.
+ """
+
+
+
+class DeferredFilesystemLock(lockfile.FilesystemLock):
+ """
+ A L{FilesystemLock} that allows for a L{Deferred} to be fired when the lock is
+ acquired.
+
+ @ivar _scheduler: The object in charge of scheduling retries. In this
+ implementation this is parameterized for testing.
+
+ @ivar _interval: The retry interval for an L{IReactorTime} based scheduler.
+
+ @ivar _tryLockCall: A L{DelayedCall} based on C{_interval} that will manage
+ the next retry for aquiring the lock.
+
+ @ivar _timeoutCall: A L{DelayedCall} based on C{deferUntilLocked}'s timeout
+ argument. This is in charge of timing out our attempt to acquire the
+ lock.
+ """
+ _interval = 1
+ _tryLockCall = None
+ _timeoutCall = None
+
+
+ def __init__(self, name, scheduler=None):
+ """
+ @param name: The name of the lock to acquire
+ @param scheduler: An object which provides L{IReactorTime}
+ """
+ lockfile.FilesystemLock.__init__(self, name)
+
+ if scheduler is None:
+ from twisted.internet import reactor
+ scheduler = reactor
+
+ self._scheduler = scheduler
+
+
+ def deferUntilLocked(self, timeout=None):
+ """
+ Wait until we acquire this lock. This method is not safe for
+ concurrent use.
+
+ @type timeout: C{float} or C{int}
+ @param timeout: the number of seconds after which to time out if the
+ lock has not been acquired.
+
+ @return: a L{Deferred} which will callback when the lock is acquired, or
+ errback with a L{TimeoutError} after timing out or an
+ L{AlreadyTryingToLockError} if the L{deferUntilLocked} has already
+ been called and not successfully locked the file.
+ """
+ if self._tryLockCall is not None:
+ return fail(
+ AlreadyTryingToLockError(
+ "deferUntilLocked isn't safe for concurrent use."))
+
+ d = Deferred()
+
+ def _cancelLock():
+ self._tryLockCall.cancel()
+ self._tryLockCall = None
+ self._timeoutCall = None
+
+ if self.lock():
+ d.callback(None)
+ else:
+ d.errback(failure.Failure(
+ TimeoutError("Timed out aquiring lock: %s after %fs" % (
+ self.name,
+ timeout))))
+
+ def _tryLock():
+ if self.lock():
+ if self._timeoutCall is not None:
+ self._timeoutCall.cancel()
+ self._timeoutCall = None
+
+ self._tryLockCall = None
+
+ d.callback(None)
+ else:
+ if timeout is not None and self._timeoutCall is None:
+ self._timeoutCall = self._scheduler.callLater(
+ timeout, _cancelLock)
+
+ self._tryLockCall = self._scheduler.callLater(
+ self._interval, _tryLock)
+
+ _tryLock()
+
+ return d
+
+
+
+__all__ = ["Deferred", "DeferredList", "succeed", "fail", "FAILURE", "SUCCESS",
+ "AlreadyCalledError", "TimeoutError", "gatherResults",
+ "maybeDeferred",
+ "waitForDeferred", "deferredGenerator", "inlineCallbacks",
+ "returnValue",
+ "DeferredLock", "DeferredSemaphore", "DeferredQueue",
+ "DeferredFilesystemLock", "AlreadyTryingToLockError",
+ ]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/epollreactor.py b/vendor/Twisted-10.0.0/twisted/internet/epollreactor.py
new file mode 100644
index 0000000000..467b3aba0a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/epollreactor.py
@@ -0,0 +1,235 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An epoll() based implementation of the twisted main loop.
+
+To install the event loop (and you should do this before any connections,
+listeners or connectors are added)::
+
+ from twisted.internet import epollreactor
+ epollreactor.install()
+"""
+
+import sys, errno
+
+from zope.interface import implements
+
+from twisted.internet.interfaces import IReactorFDSet
+
+from twisted.python import _epoll
+from twisted.python import log
+from twisted.internet import posixbase, error
+from twisted.internet.main import CONNECTION_LOST
+
+
+_POLL_DISCONNECTED = (_epoll.HUP | _epoll.ERR)
+
+class EPollReactor(posixbase.PosixReactorBase):
+ """
+ A reactor that uses epoll(4).
+
+ @ivar _poller: A L{poll} which will be used to check for I/O
+ readiness.
+
+ @ivar _selectables: A dictionary mapping integer file descriptors to
+ instances of L{FileDescriptor} which have been registered with the
+ reactor. All L{FileDescriptors} which are currently receiving read or
+ write readiness notifications will be present as values in this
+ dictionary.
+
+ @ivar _reads: A dictionary mapping integer file descriptors to arbitrary
+ values (this is essentially a set). Keys in this dictionary will be
+ registered with C{_poller} for read readiness notifications which will
+ be dispatched to the corresponding L{FileDescriptor} instances in
+ C{_selectables}.
+
+ @ivar _writes: A dictionary mapping integer file descriptors to arbitrary
+ values (this is essentially a set). Keys in this dictionary will be
+ registered with C{_poller} for write readiness notifications which will
+ be dispatched to the corresponding L{FileDescriptor} instances in
+ C{_selectables}.
+ """
+ implements(IReactorFDSet)
+
+ def __init__(self):
+ """
+ Initialize epoll object, file descriptor tracking dictionaries, and the
+ base class.
+ """
+ # Create the poller we're going to use. The 1024 here is just a hint
+ # to the kernel, it is not a hard maximum.
+ self._poller = _epoll.epoll(1024)
+ self._reads = {}
+ self._writes = {}
+ self._selectables = {}
+ posixbase.PosixReactorBase.__init__(self)
+
+
+ def _add(self, xer, primary, other, selectables, event, antievent):
+ """
+ Private method for adding a descriptor from the event loop.
+
+ It takes care of adding it if new or modifying it if already added
+ for another state (read -> read/write for example).
+ """
+ fd = xer.fileno()
+ if fd not in primary:
+ cmd = _epoll.CTL_ADD
+ flags = event
+ if fd in other:
+ flags |= antievent
+ cmd = _epoll.CTL_MOD
+ primary[fd] = 1
+ selectables[fd] = xer
+ # epoll_ctl can raise all kinds of IOErrors, and every one
+ # indicates a bug either in the reactor or application-code.
+ # Let them all through so someone sees a traceback and fixes
+ # something. We'll do the same thing for every other call to
+ # this method in this file.
+ self._poller._control(cmd, fd, flags)
+
+
+ def addReader(self, reader):
+ """
+ Add a FileDescriptor for notification of data available to read.
+ """
+ self._add(reader, self._reads, self._writes, self._selectables, _epoll.IN, _epoll.OUT)
+
+
+ def addWriter(self, writer):
+ """
+ Add a FileDescriptor for notification of data available to write.
+ """
+ self._add(writer, self._writes, self._reads, self._selectables, _epoll.OUT, _epoll.IN)
+
+
+ def _remove(self, xer, primary, other, selectables, event, antievent):
+ """
+ Private method for removing a descriptor from the event loop.
+
+ It does the inverse job of _add, and also add a check in case of the fd
+ has gone away.
+ """
+ fd = xer.fileno()
+ if fd == -1:
+ for fd, fdes in selectables.items():
+ if xer is fdes:
+ break
+ else:
+ return
+ if fd in primary:
+ cmd = _epoll.CTL_DEL
+ flags = event
+ if fd in other:
+ flags = antievent
+ cmd = _epoll.CTL_MOD
+ else:
+ del selectables[fd]
+ del primary[fd]
+ # See comment above _control call in _add.
+ self._poller._control(cmd, fd, flags)
+
+
+ def removeReader(self, reader):
+ """
+ Remove a Selectable for notification of data available to read.
+ """
+ self._remove(reader, self._reads, self._writes, self._selectables, _epoll.IN, _epoll.OUT)
+
+
+ def removeWriter(self, writer):
+ """
+ Remove a Selectable for notification of data available to write.
+ """
+ self._remove(writer, self._writes, self._reads, self._selectables, _epoll.OUT, _epoll.IN)
+
+ def removeAll(self):
+ """
+ Remove all selectables, and return a list of them.
+ """
+ return self._removeAll(
+ [self._selectables[fd] for fd in self._reads],
+ [self._selectables[fd] for fd in self._writes])
+
+
+ def getReaders(self):
+ return [self._selectables[fd] for fd in self._reads]
+
+
+ def getWriters(self):
+ return [self._selectables[fd] for fd in self._writes]
+
+
+ def doPoll(self, timeout):
+ """
+ Poll the poller for new events.
+ """
+ if timeout is None:
+ timeout = 1
+ timeout = int(timeout * 1000) # convert seconds to milliseconds
+
+ try:
+ # Limit the number of events to the number of io objects we're
+ # currently tracking (because that's maybe a good heuristic) and
+ # the amount of time we block to the value specified by our
+ # caller.
+ l = self._poller.wait(len(self._selectables), timeout)
+ except IOError, err:
+ if err.errno == errno.EINTR:
+ return
+ # See epoll_wait(2) for documentation on the other conditions
+ # under which this can fail. They can only be due to a serious
+ # programming error on our part, so let's just announce them
+ # loudly.
+ raise
+
+ _drdw = self._doReadOrWrite
+ for fd, event in l:
+ try:
+ selectable = self._selectables[fd]
+ except KeyError:
+ pass
+ else:
+ log.callWithLogger(selectable, _drdw, selectable, fd, event)
+
+ doIteration = doPoll
+
+ def _doReadOrWrite(self, selectable, fd, event):
+ """
+ fd is available for read or write, make the work and raise errors
+ if necessary.
+ """
+ why = None
+ inRead = False
+ if event & _POLL_DISCONNECTED and not (event & _epoll.IN):
+ why = CONNECTION_LOST
+ else:
+ try:
+ if event & _epoll.IN:
+ why = selectable.doRead()
+ inRead = True
+ if not why and event & _epoll.OUT:
+ why = selectable.doWrite()
+ inRead = False
+ if selectable.fileno() != fd:
+ why = error.ConnectionFdescWentAway(
+ 'Filedescriptor went away')
+ inRead = False
+ except:
+ log.err()
+ why = sys.exc_info()[1]
+ if why:
+ self._disconnectSelectable(selectable, why, inRead)
+
+def install():
+ """
+ Install the epoll() reactor.
+ """
+ p = EPollReactor()
+ from twisted.internet.main import installReactor
+ installReactor(p)
+
+
+__all__ = ["EPollReactor", "install"]
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/error.py b/vendor/Twisted-10.0.0/twisted/internet/error.py
new file mode 100644
index 0000000000..8a74d0e36d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/error.py
@@ -0,0 +1,319 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Exceptions and errors for use in twisted.internet modules.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+import socket
+
+from twisted.python import deprecate
+from twisted.python.versions import Version
+
+
+
+class BindError(Exception):
+ """An error occurred binding to an interface"""
+
+ def __str__(self):
+ s = self.__doc__
+ if self.args:
+ s = '%s: %s' % (s, ' '.join(self.args))
+ s = '%s.' % s
+ return s
+
+class CannotListenError(BindError):
+ """This gets raised by a call to startListening, when the object cannot start listening.
+
+ @ivar interface: the interface I tried to listen on
+ @ivar port: the port I tried to listen on
+ @ivar socketError: the exception I got when I tried to listen
+ @type socketError: L{socket.error}
+ """
+ def __init__(self, interface, port, socketError):
+ BindError.__init__(self, interface, port, socketError)
+ self.interface = interface
+ self.port = port
+ self.socketError = socketError
+
+ def __str__(self):
+ iface = self.interface or 'any'
+ return "Couldn't listen on %s:%s: %s." % (iface, self.port,
+ self.socketError)
+
+
+class MulticastJoinError(Exception):
+ """
+ An attempt to join a multicast group failed.
+ """
+
+
+class MessageLengthError(Exception):
+ """Message is too long to send"""
+
+ def __str__(self):
+ s = self.__doc__
+ if self.args:
+ s = '%s: %s' % (s, ' '.join(self.args))
+ s = '%s.' % s
+ return s
+
+
+class DNSLookupError(IOError):
+ """DNS lookup failed"""
+
+ def __str__(self):
+ s = self.__doc__
+ if self.args:
+ s = '%s: %s' % (s, ' '.join(self.args))
+ s = '%s.' % s
+ return s
+
+
+class ConnectInProgressError(Exception):
+ """A connect operation was started and isn't done yet."""
+
+
+# connection errors
+
+class ConnectError(Exception):
+ """An error occurred while connecting"""
+
+ def __init__(self, osError=None, string=""):
+ self.osError = osError
+ Exception.__init__(self, string)
+
+ def __str__(self):
+ s = self.__doc__ or self.__class__.__name__
+ if self.osError:
+ s = '%s: %s' % (s, self.osError)
+ if self[0]:
+ s = '%s: %s' % (s, self[0])
+ s = '%s.' % s
+ return s
+
+
+class ConnectBindError(ConnectError):
+ """Couldn't bind"""
+
+
+class UnknownHostError(ConnectError):
+ """Hostname couldn't be looked up"""
+
+
+class NoRouteError(ConnectError):
+ """No route to host"""
+
+
+class ConnectionRefusedError(ConnectError):
+ """Connection was refused by other side"""
+
+
+class TCPTimedOutError(ConnectError):
+ """TCP connection timed out"""
+
+
+class BadFileError(ConnectError):
+ """File used for UNIX socket is no good"""
+
+
+class ServiceNameUnknownError(ConnectError):
+ """Service name given as port is unknown"""
+
+
+class UserError(ConnectError):
+ """User aborted connection"""
+
+
+class TimeoutError(UserError):
+ """User timeout caused connection failure"""
+
+class SSLError(ConnectError):
+ """An SSL error occurred"""
+
+class VerifyError(Exception):
+ """Could not verify something that was supposed to be signed.
+ """
+
+class PeerVerifyError(VerifyError):
+ """The peer rejected our verify error.
+ """
+
+class CertificateError(Exception):
+ """
+ We did not find a certificate where we expected to find one.
+ """
+
+try:
+ import errno
+ errnoMapping = {
+ errno.ENETUNREACH: NoRouteError,
+ errno.ECONNREFUSED: ConnectionRefusedError,
+ errno.ETIMEDOUT: TCPTimedOutError,
+ }
+ if hasattr(errno, "WSAECONNREFUSED"):
+ errnoMapping[errno.WSAECONNREFUSED] = ConnectionRefusedError
+ errnoMapping[errno.WSAENETUNREACH] = NoRouteError
+except ImportError:
+ errnoMapping = {}
+
+def getConnectError(e):
+ """Given a socket exception, return connection error."""
+ try:
+ number, string = e
+ except ValueError:
+ return ConnectError(string=e)
+
+ if hasattr(socket, 'gaierror') and isinstance(e, socket.gaierror):
+ # only works in 2.2
+ klass = UnknownHostError
+ else:
+ klass = errnoMapping.get(number, ConnectError)
+ return klass(number, string)
+
+
+
+class ConnectionClosed(Exception):
+ """
+ Connection was closed, whether cleanly or non-cleanly.
+ """
+
+
+
+class ConnectionLost(ConnectionClosed):
+ """Connection to the other side was lost in a non-clean fashion"""
+
+ def __str__(self):
+ s = self.__doc__
+ if self.args:
+ s = '%s: %s' % (s, ' '.join(self.args))
+ s = '%s.' % s
+ return s
+
+
+
+class ConnectionDone(ConnectionClosed):
+ """Connection was closed cleanly"""
+
+ def __str__(self):
+ s = self.__doc__
+ if self.args:
+ s = '%s: %s' % (s, ' '.join(self.args))
+ s = '%s.' % s
+ return s
+
+
+class ConnectionFdescWentAway(ConnectionLost):
+ """Uh""" #TODO
+
+
+class AlreadyCalled(ValueError):
+ """Tried to cancel an already-called event"""
+
+ def __str__(self):
+ s = self.__doc__
+ if self.args:
+ s = '%s: %s' % (s, ' '.join(self.args))
+ s = '%s.' % s
+ return s
+
+
+class AlreadyCancelled(ValueError):
+ """Tried to cancel an already-cancelled event"""
+
+ def __str__(self):
+ s = self.__doc__
+ if self.args:
+ s = '%s: %s' % (s, ' '.join(self.args))
+ s = '%s.' % s
+ return s
+
+
+
+class PotentialZombieWarning(Warning):
+ """
+ Emitted when L{IReactorProcess.spawnProcess} is called in a way which may
+ result in termination of the created child process not being reported.
+
+ Deprecated in Twisted 10.0.
+ """
+ MESSAGE = (
+ "spawnProcess called, but the SIGCHLD handler is not "
+ "installed. This probably means you have not yet "
+ "called reactor.run, or called "
+ "reactor.run(installSignalHandler=0). You will probably "
+ "never see this process finish, and it may become a "
+ "zombie process.")
+
+deprecate.deprecatedModuleAttribute(
+ Version("Twisted", 10, 0, 0),
+ "There is no longer any potential for zombie process.",
+ __name__,
+ "PotentialZombieWarning")
+
+
+
+class ProcessDone(ConnectionDone):
+ """A process has ended without apparent errors"""
+
+ def __init__(self, status):
+ Exception.__init__(self, "process finished with exit code 0")
+ self.exitCode = 0
+ self.signal = None
+ self.status = status
+
+
+class ProcessTerminated(ConnectionLost):
+ """A process has ended with a probable error condition"""
+
+ def __init__(self, exitCode=None, signal=None, status=None):
+ self.exitCode = exitCode
+ self.signal = signal
+ self.status = status
+ s = "process ended"
+ if exitCode is not None: s = s + " with exit code %s" % exitCode
+ if signal is not None: s = s + " by signal %s" % signal
+ Exception.__init__(self, s)
+
+
+class ProcessExitedAlready(Exception):
+ """
+ The process has already exited and the operation requested can no longer
+ be performed.
+ """
+
+
+class NotConnectingError(RuntimeError):
+ """The Connector was not connecting when it was asked to stop connecting"""
+
+ def __str__(self):
+ s = self.__doc__
+ if self.args:
+ s = '%s: %s' % (s, ' '.join(self.args))
+ s = '%s.' % s
+ return s
+
+class NotListeningError(RuntimeError):
+ """The Port was not listening when it was asked to stop listening"""
+
+ def __str__(self):
+ s = self.__doc__
+ if self.args:
+ s = '%s: %s' % (s, ' '.join(self.args))
+ s = '%s.' % s
+ return s
+
+
+class ReactorNotRunning(RuntimeError):
+ """
+ Error raised when trying to stop a reactor which is not running.
+ """
+
+
+class ReactorAlreadyRunning(RuntimeError):
+ """
+ Error raised when trying to start the reactor multiple times.
+ """
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/fdesc.py b/vendor/Twisted-10.0.0/twisted/internet/fdesc.py
new file mode 100644
index 0000000000..57b192fe7a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/fdesc.py
@@ -0,0 +1,118 @@
+# -*- test-case-name: twisted.test.test_fdesc -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Utility functions for dealing with POSIX file descriptors.
+"""
+
+import os
+import errno
+try:
+ import fcntl
+except ImportError:
+ fcntl = None
+
+# twisted imports
+from twisted.internet.main import CONNECTION_LOST, CONNECTION_DONE
+from twisted.python.runtime import platformType
+
+def setNonBlocking(fd):
+ """
+ Make a file descriptor non-blocking.
+ """
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+ flags = flags | os.O_NONBLOCK
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+
+
+def setBlocking(fd):
+ """
+ Make a file descriptor blocking.
+ """
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+ flags = flags & ~os.O_NONBLOCK
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+
+
+if fcntl is None:
+ # fcntl isn't available on Windows. By default, handles aren't
+ # inherited on Windows, so we can do nothing here.
+ _setCloseOnExec = _unsetCloseOnExec = lambda fd: None
+else:
+ def _setCloseOnExec(fd):
+ """
+ Make a file descriptor close-on-exec.
+ """
+ flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+ flags = flags | fcntl.FD_CLOEXEC
+ fcntl.fcntl(fd, fcntl.F_SETFD, flags)
+
+
+ def _unsetCloseOnExec(fd):
+ """
+ Make a file descriptor close-on-exec.
+ """
+ flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+ flags = flags & ~fcntl.FD_CLOEXEC
+ fcntl.fcntl(fd, fcntl.F_SETFD, flags)
+
+
+def readFromFD(fd, callback):
+ """
+ Read from file descriptor, calling callback with resulting data.
+
+ If successful, call 'callback' with a single argument: the
+ resulting data.
+
+ Returns same thing FileDescriptor.doRead would: CONNECTION_LOST,
+ CONNECTION_DONE, or None.
+
+ @type fd: C{int}
+ @param fd: non-blocking file descriptor to be read from.
+ @param callback: a callable which accepts a single argument. If
+ data is read from the file descriptor it will be called with this
+ data. Handling exceptions from calling the callback is up to the
+ caller.
+
+ Note that if the descriptor is still connected but no data is read,
+ None will be returned but callback will not be called.
+
+ @return: CONNECTION_LOST on error, CONNECTION_DONE when fd is
+ closed, otherwise None.
+ """
+ try:
+ output = os.read(fd, 8192)
+ except (OSError, IOError), ioe:
+ if ioe.args[0] in (errno.EAGAIN, errno.EINTR):
+ return
+ else:
+ return CONNECTION_LOST
+ if not output:
+ return CONNECTION_DONE
+ callback(output)
+
+
+def writeToFD(fd, data):
+ """
+ Write data to file descriptor.
+
+ Returns same thing FileDescriptor.writeSomeData would.
+
+ @type fd: C{int}
+ @param fd: non-blocking file descriptor to be written to.
+ @type data: C{str} or C{buffer}
+ @param data: bytes to write to fd.
+
+ @return: number of bytes written, or CONNECTION_LOST.
+ """
+ try:
+ return os.write(fd, data)
+ except (OSError, IOError), io:
+ if io.errno in (errno.EAGAIN, errno.EINTR):
+ return 0
+ return CONNECTION_LOST
+
+
+__all__ = ["setNonBlocking", "setBlocking", "readFromFD", "writeToFD"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/glib2reactor.py b/vendor/Twisted-10.0.0/twisted/internet/glib2reactor.py
new file mode 100644
index 0000000000..39f09e3bfd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/glib2reactor.py
@@ -0,0 +1,49 @@
+
+"""
+This module provides support for Twisted to interact with the glib mainloop.
+This is like gtk2, but slightly faster and does not require a working
+$DISPLAY. However, you cannot run GUIs under this reactor: for that you must
+use the gtk2reactor instead.
+
+In order to use this support, simply do the following::
+
+ | from twisted.internet import glib2reactor
+ | glib2reactor.install()
+
+Then use twisted.internet APIs as usual. The other methods here are not
+intended to be called directly.
+
+When installing the reactor, you can choose whether to use the glib
+event loop or the GTK+ event loop which is based on it but adds GUI
+integration.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+from twisted.internet import gtk2reactor
+
+
+
+class Glib2Reactor(gtk2reactor.Gtk2Reactor):
+ """
+ The reactor using the glib mainloop.
+ """
+
+ def __init__(self):
+ """
+ Override init to set the C{useGtk} flag.
+ """
+ gtk2reactor.Gtk2Reactor.__init__(self, useGtk=False)
+
+
+
+def install():
+ """
+ Configure the twisted mainloop to be run inside the glib mainloop.
+ """
+ reactor = Glib2Reactor()
+ from twisted.internet.main import installReactor
+ installReactor(reactor)
+
+__all__ = ['install']
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/gtk2reactor.py b/vendor/Twisted-10.0.0/twisted/internet/gtk2reactor.py
new file mode 100644
index 0000000000..bd979bcac6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/gtk2reactor.py
@@ -0,0 +1,377 @@
+# -*- test-case-name: twisted.internet.test.test_gtk2reactor -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+This module provides support for Twisted to interact with the glib/gtk2
+mainloop.
+
+In order to use this support, simply do the following::
+
+ | from twisted.internet import gtk2reactor
+ | gtk2reactor.install()
+
+Then use twisted.internet APIs as usual. The other methods here are not
+intended to be called directly.
+
+When installing the reactor, you can choose whether to use the glib
+event loop or the GTK+ event loop which is based on it but adds GUI
+integration.
+"""
+
+# System Imports
+import sys
+from zope.interface import implements
+try:
+ if not hasattr(sys, 'frozen'):
+ # Don't want to check this for py2exe
+ import pygtk
+ pygtk.require('2.0')
+except (ImportError, AttributeError):
+ pass # maybe we're using pygtk before this hack existed.
+import gobject
+if hasattr(gobject, "threads_init"):
+ # recent versions of python-gtk expose this. python-gtk=2.4.1
+ # (wrapping glib-2.4.7) does. python-gtk=2.0.0 (wrapping
+ # glib-2.2.3) does not.
+ gobject.threads_init()
+
+# Twisted Imports
+from twisted.python import log, runtime, failure
+from twisted.python.compat import set
+from twisted.internet.interfaces import IReactorFDSet
+from twisted.internet import main, posixbase, error, selectreactor
+
+POLL_DISCONNECTED = gobject.IO_HUP | gobject.IO_ERR | gobject.IO_NVAL
+
+# glib's iochannel sources won't tell us about any events that we haven't
+# asked for, even if those events aren't sensible inputs to the poll()
+# call.
+INFLAGS = gobject.IO_IN | POLL_DISCONNECTED
+OUTFLAGS = gobject.IO_OUT | POLL_DISCONNECTED
+
+
+
+def _our_mainquit():
+ # XXX: gtk.main_quit() (which is used for crash()) raises an exception if
+ # gtk.main_level() == 0; however, all the tests freeze if we use this
+ # function to stop the reactor. what gives? (I believe this may have been
+ # a stupid mistake where I forgot to import gtk here... I will remove this
+ # comment if the tests pass)
+ import gtk
+ if gtk.main_level():
+ gtk.main_quit()
+
+
+
+class Gtk2Reactor(posixbase.PosixReactorBase):
+ """
+ GTK+-2 event loop reactor.
+
+ @ivar _sources: A dictionary mapping L{FileDescriptor} instances to gtk
+ watch handles.
+
+ @ivar _reads: A set of L{FileDescriptor} instances currently monitored for
+ reading.
+
+ @ivar _writes: A set of L{FileDescriptor} instances currently monitored for
+ writing.
+
+ @ivar _simtag: A gtk timeout handle for the next L{simulate} call.
+ """
+ implements(IReactorFDSet)
+
+ def __init__(self, useGtk=True):
+ self._simtag = None
+ self._reads = set()
+ self._writes = set()
+ self._sources = {}
+ posixbase.PosixReactorBase.__init__(self)
+ # pre 2.3.91 the glib iteration and mainloop functions didn't release
+ # global interpreter lock, thus breaking thread and signal support.
+ if getattr(gobject, "pygtk_version", ()) >= (2, 3, 91) and not useGtk:
+ self.context = gobject.main_context_default()
+ self.__pending = self.context.pending
+ self.__iteration = self.context.iteration
+ self.loop = gobject.MainLoop()
+ self.__crash = self.loop.quit
+ self.__run = self.loop.run
+ else:
+ import gtk
+ self.__pending = gtk.events_pending
+ self.__iteration = gtk.main_iteration
+ self.__crash = _our_mainquit
+ self.__run = gtk.main
+
+ # The input_add function in pygtk1 checks for objects with a
+ # 'fileno' method and, if present, uses the result of that method
+ # as the input source. The pygtk2 input_add does not do this. The
+ # function below replicates the pygtk1 functionality.
+
+ # In addition, pygtk maps gtk.input_add to _gobject.io_add_watch, and
+ # g_io_add_watch() takes different condition bitfields than
+ # gtk_input_add(). We use g_io_add_watch() here in case pygtk fixes this
+ # bug.
+ def input_add(self, source, condition, callback):
+ if hasattr(source, 'fileno'):
+ # handle python objects
+ def wrapper(source, condition, real_s=source, real_cb=callback):
+ return real_cb(real_s, condition)
+ return gobject.io_add_watch(source.fileno(), condition, wrapper)
+ else:
+ return gobject.io_add_watch(source, condition, callback)
+
+
+ def _add(self, source, primary, other, primaryFlag, otherFlag):
+ """
+ Add the given L{FileDescriptor} for monitoring either for reading or
+ writing. If the file is already monitored for the other operation, we
+ delete the previous registration and re-register it for both reading
+ and writing.
+ """
+ if source in primary:
+ return
+ flags = primaryFlag
+ if source in other:
+ gobject.source_remove(self._sources[source])
+ flags |= otherFlag
+ self._sources[source] = self.input_add(source, flags, self.callback)
+ primary.add(source)
+
+
+ def addReader(self, reader):
+ """
+ Add a L{FileDescriptor} for monitoring of data available to read.
+ """
+ self._add(reader, self._reads, self._writes, INFLAGS, OUTFLAGS)
+
+
+ def addWriter(self, writer):
+ """
+ Add a L{FileDescriptor} for monitoring ability to write data.
+ """
+ self._add(writer, self._writes, self._reads, OUTFLAGS, INFLAGS)
+
+
+ def getReaders(self):
+ """
+ Retrieve the list of current L{FileDescriptor} monitored for reading.
+ """
+ return list(self._reads)
+
+
+ def getWriters(self):
+ """
+ Retrieve the list of current L{FileDescriptor} monitored for writing.
+ """
+ return list(self._writes)
+
+
+ def removeAll(self):
+ """
+ Remove monitoring for all registered L{FileDescriptor}s.
+ """
+ return self._removeAll(self._reads, self._writes)
+
+
+ def _remove(self, source, primary, other, flags):
+ """
+ Remove monitoring the given L{FileDescriptor} for either reading or
+ writing. If it's still monitored for the other operation, we
+ re-register the L{FileDescriptor} for only that operation.
+ """
+ if source not in primary:
+ return
+ gobject.source_remove(self._sources[source])
+ primary.remove(source)
+ if source in other:
+ self._sources[source] = self.input_add(
+ source, flags, self.callback)
+ else:
+ self._sources.pop(source)
+
+
+ def removeReader(self, reader):
+ """
+ Stop monitoring the given L{FileDescriptor} for reading.
+ """
+ self._remove(reader, self._reads, self._writes, OUTFLAGS)
+
+
+ def removeWriter(self, writer):
+ """
+ Stop monitoring the given L{FileDescriptor} for writing.
+ """
+ self._remove(writer, self._writes, self._reads, INFLAGS)
+
+
+ doIterationTimer = None
+
+ def doIterationTimeout(self, *args):
+ self.doIterationTimer = None
+ return 0 # auto-remove
+
+
+ def doIteration(self, delay):
+ # flush some pending events, return if there was something to do
+ # don't use the usual "while self.context.pending(): self.context.iteration()"
+ # idiom because lots of IO (in particular test_tcp's
+ # ProperlyCloseFilesTestCase) can keep us from ever exiting.
+ log.msg(channel='system', event='iteration', reactor=self)
+ if self.__pending():
+ self.__iteration(0)
+ return
+ # nothing to do, must delay
+ if delay == 0:
+ return # shouldn't delay, so just return
+ self.doIterationTimer = gobject.timeout_add(int(delay * 1000),
+ self.doIterationTimeout)
+ # This will either wake up from IO or from a timeout.
+ self.__iteration(1) # block
+ # note: with the .simulate timer below, delays > 0.1 will always be
+ # woken up by the .simulate timer
+ if self.doIterationTimer:
+ # if woken by IO, need to cancel the timer
+ gobject.source_remove(self.doIterationTimer)
+ self.doIterationTimer = None
+
+
+ def crash(self):
+ posixbase.PosixReactorBase.crash(self)
+ self.__crash()
+
+
+ def run(self, installSignalHandlers=1):
+ self.startRunning(installSignalHandlers=installSignalHandlers)
+ gobject.timeout_add(0, self.simulate)
+ if self._started:
+ self.__run()
+
+
+ def _doReadOrWrite(self, source, condition, faildict={
+ error.ConnectionDone: failure.Failure(error.ConnectionDone()),
+ error.ConnectionLost: failure.Failure(error.ConnectionLost()),
+ }):
+ why = None
+ didRead = None
+ if condition & POLL_DISCONNECTED and \
+ not (condition & gobject.IO_IN):
+ why = main.CONNECTION_LOST
+ else:
+ try:
+ if condition & gobject.IO_IN:
+ why = source.doRead()
+ didRead = source.doRead
+ if not why and condition & gobject.IO_OUT:
+ # if doRead caused connectionLost, don't call doWrite
+ # if doRead is doWrite, don't call it again.
+ if not source.disconnected and source.doWrite != didRead:
+ why = source.doWrite()
+ didRead = source.doWrite # if failed it was in write
+ except:
+ why = sys.exc_info()[1]
+ log.msg('Error In %s' % source)
+ log.deferr()
+
+ if why:
+ self._disconnectSelectable(source, why, didRead == source.doRead)
+
+
+ def callback(self, source, condition):
+ log.callWithLogger(source, self._doReadOrWrite, source, condition)
+ self.simulate() # fire Twisted timers
+ return 1 # 1=don't auto-remove the source
+
+
+ def simulate(self):
+ """
+ Run simulation loops and reschedule callbacks.
+ """
+ if self._simtag is not None:
+ gobject.source_remove(self._simtag)
+ self.runUntilCurrent()
+ timeout = min(self.timeout(), 0.1)
+ if timeout is None:
+ timeout = 0.1
+ # grumble
+ self._simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
+
+
+
+class PortableGtkReactor(selectreactor.SelectReactor):
+ """
+ Reactor that works on Windows.
+
+ Sockets aren't supported by GTK+'s input_add on Win32.
+ """
+ _simtag = None
+
+ def crash(self):
+ selectreactor.SelectReactor.crash(self)
+ import gtk
+ # mainquit is deprecated in newer versions
+ if gtk.main_level():
+ if hasattr(gtk, 'main_quit'):
+ gtk.main_quit()
+ else:
+ gtk.mainquit()
+
+
+ def run(self, installSignalHandlers=1):
+ import gtk
+ self.startRunning(installSignalHandlers=installSignalHandlers)
+ gobject.timeout_add(0, self.simulate)
+ # mainloop is deprecated in newer versions
+ if hasattr(gtk, 'main'):
+ gtk.main()
+ else:
+ gtk.mainloop()
+
+
+ def simulate(self):
+ """
+ Run simulation loops and reschedule callbacks.
+ """
+ if self._simtag is not None:
+ gobject.source_remove(self._simtag)
+ self.iterate()
+ timeout = min(self.timeout(), 0.1)
+ if timeout is None:
+ timeout = 0.1
+ # grumble
+ self._simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
+
+
+
+def install(useGtk=True):
+ """
+ Configure the twisted mainloop to be run inside the gtk mainloop.
+
+ @param useGtk: should glib rather than GTK+ event loop be
+ used (this will be slightly faster but does not support GUI).
+ """
+ reactor = Gtk2Reactor(useGtk)
+ from twisted.internet.main import installReactor
+ installReactor(reactor)
+ return reactor
+
+
+
+def portableInstall(useGtk=True):
+ """
+ Configure the twisted mainloop to be run inside the gtk mainloop.
+ """
+ reactor = PortableGtkReactor()
+ from twisted.internet.main import installReactor
+ installReactor(reactor)
+ return reactor
+
+
+
+if runtime.platform.getType() != 'posix':
+ install = portableInstall
+
+
+
+__all__ = ['install']
diff --git a/vendor/Twisted-10.0.0/twisted/internet/gtkreactor.py b/vendor/Twisted-10.0.0/twisted/internet/gtkreactor.py
new file mode 100644
index 0000000000..c879baf0cd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/gtkreactor.py
@@ -0,0 +1,232 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module provides support for Twisted to interact with the PyGTK mainloop.
+
+In order to use this support, simply do the following::
+
+ | from twisted.internet import gtkreactor
+ | gtkreactor.install()
+
+Then use twisted.internet APIs as usual. The other methods here are not
+intended to be called directly.
+"""
+
+import sys
+
+# System Imports
+try:
+ import pygtk
+ pygtk.require('1.2')
+except ImportError, AttributeError:
+ pass # maybe we're using pygtk before this hack existed.
+import gtk
+
+from zope.interface import implements
+
+# Twisted Imports
+from twisted.python import log, runtime
+from twisted.internet.interfaces import IReactorFDSet
+
+# Sibling Imports
+from twisted.internet import posixbase, selectreactor
+
+
+class GtkReactor(posixbase.PosixReactorBase):
+ """
+ GTK+ event loop reactor.
+
+ @ivar _reads: A dictionary mapping L{FileDescriptor} instances to gtk INPUT_READ
+ watch handles.
+
+ @ivar _writes: A dictionary mapping L{FileDescriptor} instances to gtk
+ INTPUT_WRITE watch handles.
+
+ @ivar _simtag: A gtk timeout handle for the next L{simulate} call.
+ """
+ implements(IReactorFDSet)
+
+ def __init__(self):
+ """
+ Initialize the file descriptor tracking dictionaries and the base
+ class.
+ """
+ self._simtag = None
+ self._reads = {}
+ self._writes = {}
+ posixbase.PosixReactorBase.__init__(self)
+
+
+ def addReader(self, reader):
+ if reader not in self._reads:
+ self._reads[reader] = gtk.input_add(reader, gtk.GDK.INPUT_READ, self.callback)
+
+ def addWriter(self, writer):
+ if writer not in self._writes:
+ self._writes[writer] = gtk.input_add(writer, gtk.GDK.INPUT_WRITE, self.callback)
+
+
+ def getReaders(self):
+ return self._reads.keys()
+
+
+ def getWriters(self):
+ return self._writes.keys()
+
+
+ def removeAll(self):
+ return self._removeAll(self._reads, self._writes)
+
+
+ def removeReader(self, reader):
+ if reader in self._reads:
+ gtk.input_remove(self._reads[reader])
+ del self._reads[reader]
+
+ def removeWriter(self, writer):
+ if writer in self._writes:
+ gtk.input_remove(self._writes[writer])
+ del self._writes[writer]
+
+ doIterationTimer = None
+
+ def doIterationTimeout(self, *args):
+ self.doIterationTimer = None
+ return 0 # auto-remove
+ def doIteration(self, delay):
+ # flush some pending events, return if there was something to do
+ # don't use the usual "while gtk.events_pending(): mainiteration()"
+ # idiom because lots of IO (in particular test_tcp's
+ # ProperlyCloseFilesTestCase) can keep us from ever exiting.
+ log.msg(channel='system', event='iteration', reactor=self)
+ if gtk.events_pending():
+ gtk.mainiteration(0)
+ return
+ # nothing to do, must delay
+ if delay == 0:
+ return # shouldn't delay, so just return
+ self.doIterationTimer = gtk.timeout_add(int(delay * 1000),
+ self.doIterationTimeout)
+ # This will either wake up from IO or from a timeout.
+ gtk.mainiteration(1) # block
+ # note: with the .simulate timer below, delays > 0.1 will always be
+ # woken up by the .simulate timer
+ if self.doIterationTimer:
+ # if woken by IO, need to cancel the timer
+ gtk.timeout_remove(self.doIterationTimer)
+ self.doIterationTimer = None
+
+ def crash(self):
+ posixbase.PosixReactorBase.crash(self)
+ gtk.mainquit()
+
+ def run(self, installSignalHandlers=1):
+ self.startRunning(installSignalHandlers=installSignalHandlers)
+ gtk.timeout_add(0, self.simulate)
+ gtk.mainloop()
+
+ def _readAndWrite(self, source, condition):
+ # note: gtk-1.2's gtk_input_add presents an API in terms of gdk
+ # constants like INPUT_READ and INPUT_WRITE. Internally, it will add
+ # POLL_HUP and POLL_ERR to the poll() events, but if they happen it
+ # will turn them back into INPUT_READ and INPUT_WRITE. gdkevents.c
+ # maps IN/HUP/ERR to INPUT_READ, and OUT/ERR to INPUT_WRITE. This
+ # means there is no immediate way to detect a disconnected socket.
+
+ # The g_io_add_watch() API is more suited to this task. I don't think
+ # pygtk exposes it, though.
+ why = None
+ didRead = None
+ try:
+ if condition & gtk.GDK.INPUT_READ:
+ why = source.doRead()
+ didRead = source.doRead
+ if not why and condition & gtk.GDK.INPUT_WRITE:
+ # if doRead caused connectionLost, don't call doWrite
+ # if doRead is doWrite, don't call it again.
+ if not source.disconnected and source.doWrite != didRead:
+ why = source.doWrite()
+ didRead = source.doWrite # if failed it was in write
+ except:
+ why = sys.exc_info()[1]
+ log.msg('Error In %s' % source)
+ log.deferr()
+
+ if why:
+ self._disconnectSelectable(source, why, didRead == source.doRead)
+
+ def callback(self, source, condition):
+ log.callWithLogger(source, self._readAndWrite, source, condition)
+ self.simulate() # fire Twisted timers
+ return 1 # 1=don't auto-remove the source
+
+ def simulate(self):
+ """Run simulation loops and reschedule callbacks.
+ """
+ if self._simtag is not None:
+ gtk.timeout_remove(self._simtag)
+ self.runUntilCurrent()
+ timeout = min(self.timeout(), 0.1)
+ if timeout is None:
+ timeout = 0.1
+ # Quoth someone other than me, "grumble", yet I know not why. Try to be
+ # more specific in your complaints, guys. -exarkun
+ self._simtag = gtk.timeout_add(int(timeout * 1010), self.simulate)
+
+
+
+class PortableGtkReactor(selectreactor.SelectReactor):
+ """Reactor that works on Windows.
+
+ input_add is not supported on GTK+ for Win32, apparently.
+
+ @ivar _simtag: A gtk timeout handle for the next L{simulate} call.
+ """
+ _simtag = None
+
+
+ def crash(self):
+ selectreactor.SelectReactor.crash(self)
+ gtk.mainquit()
+
+ def run(self, installSignalHandlers=1):
+ self.startRunning(installSignalHandlers=installSignalHandlers)
+ self.simulate()
+ gtk.mainloop()
+
+ def simulate(self):
+ """Run simulation loops and reschedule callbacks.
+ """
+ if self._simtag is not None:
+ gtk.timeout_remove(self._simtag)
+ self.iterate()
+ timeout = min(self.timeout(), 0.1)
+ if timeout is None:
+ timeout = 0.1
+
+ # See comment for identical line in GtkReactor.simulate.
+ self._simtag = gtk.timeout_add((timeout * 1010), self.simulate)
+
+
+
+def install():
+ """Configure the twisted mainloop to be run inside the gtk mainloop.
+ """
+ reactor = GtkReactor()
+ from twisted.internet.main import installReactor
+ installReactor(reactor)
+ return reactor
+
+def portableInstall():
+ """Configure the twisted mainloop to be run inside the gtk mainloop.
+ """
+ reactor = PortableGtkReactor()
+ from twisted.internet.main import installReactor
+ installReactor(reactor)
+ return reactor
+
+if runtime.platform.getType() != 'posix':
+ install = portableInstall
+
+__all__ = ['install']
diff --git a/vendor/Twisted-10.0.0/twisted/internet/interfaces.py b/vendor/Twisted-10.0.0/twisted/internet/interfaces.py
new file mode 100644
index 0000000000..6769a6fc5e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/interfaces.py
@@ -0,0 +1,1693 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Interface documentation.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+from zope.interface import Interface, Attribute
+
+
+class IAddress(Interface):
+ """
+ An address, e.g. a TCP C{(host, port)}.
+
+ Default implementations are in L{twisted.internet.address}.
+ """
+
+### Reactor Interfaces
+
+class IConnector(Interface):
+ """
+ Object used to interface between connections and protocols.
+
+ Each L{IConnector} manages one connection.
+ """
+
+ def stopConnecting():
+ """
+ Stop attempting to connect.
+ """
+
+ def disconnect():
+ """
+ Disconnect regardless of the connection state.
+
+ If we are connected, disconnect, if we are trying to connect,
+ stop trying.
+ """
+
+ def connect():
+ """
+ Try to connect to remote address.
+ """
+
+ def getDestination():
+ """
+ Return destination this will try to connect to.
+
+ @return: An object which provides L{IAddress}.
+ """
+
+
+class IResolverSimple(Interface):
+
+ def getHostByName(name, timeout = (1, 3, 11, 45)):
+ """
+ Resolve the domain name C{name} into an IP address.
+
+ @type name: C{str}
+ @type timeout: C{tuple}
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: The callback of the Deferred that is returned will be
+ passed a string that represents the IP address of the specified
+ name, or the errback will be called if the lookup times out. If
+ multiple types of address records are associated with the name,
+ A6 records will be returned in preference to AAAA records, which
+ will be returned in preference to A records. If there are multiple
+ records of the type to be returned, one will be selected at random.
+
+ @raise twisted.internet.defer.TimeoutError: Raised (asynchronously)
+ if the name cannot be resolved within the specified timeout period.
+ """
+
+class IResolver(IResolverSimple):
+ def lookupRecord(name, cls, type, timeout = 10):
+ """
+ Lookup the records associated with the given name
+ that are of the given type and in the given class.
+ """
+
+ def query(query, timeout = 10):
+ """
+ Interpret and dispatch a query object to the appropriate
+ lookup* method.
+ """
+
+ def lookupAddress(name, timeout = 10):
+ """
+ Lookup the A records associated with C{name}.
+ """
+
+ def lookupAddress6(name, timeout = 10):
+ """
+ Lookup all the A6 records associated with C{name}.
+ """
+
+ def lookupIPV6Address(name, timeout = 10):
+ """
+ Lookup all the AAAA records associated with C{name}.
+ """
+
+ def lookupMailExchange(name, timeout = 10):
+ """
+ Lookup the MX records associated with C{name}.
+ """
+
+ def lookupNameservers(name, timeout = 10):
+ """
+ Lookup the the NS records associated with C{name}.
+ """
+
+ def lookupCanonicalName(name, timeout = 10):
+ """
+ Lookup the CNAME records associated with C{name}.
+ """
+
+ def lookupMailBox(name, timeout = 10):
+ """
+ Lookup the MB records associated with C{name}.
+ """
+
+ def lookupMailGroup(name, timeout = 10):
+ """
+ Lookup the MG records associated with C{name}.
+ """
+
+ def lookupMailRename(name, timeout = 10):
+ """
+ Lookup the MR records associated with C{name}.
+ """
+
+ def lookupPointer(name, timeout = 10):
+ """
+ Lookup the PTR records associated with C{name}.
+ """
+
+ def lookupAuthority(name, timeout = 10):
+ """
+ Lookup the SOA records associated with C{name}.
+ """
+
+ def lookupNull(name, timeout = 10):
+ """
+ Lookup the NULL records associated with C{name}.
+ """
+
+ def lookupWellKnownServices(name, timeout = 10):
+ """
+ Lookup the WKS records associated with C{name}.
+ """
+
+ def lookupHostInfo(name, timeout = 10):
+ """
+ Lookup the HINFO records associated with C{name}.
+ """
+
+ def lookupMailboxInfo(name, timeout = 10):
+ """
+ Lookup the MINFO records associated with C{name}.
+ """
+
+ def lookupText(name, timeout = 10):
+ """
+ Lookup the TXT records associated with C{name}.
+ """
+
+ def lookupResponsibility(name, timeout = 10):
+ """
+ Lookup the RP records associated with C{name}.
+ """
+
+ def lookupAFSDatabase(name, timeout = 10):
+ """
+ Lookup the AFSDB records associated with C{name}.
+ """
+
+ def lookupService(name, timeout = 10):
+ """
+ Lookup the SRV records associated with C{name}.
+ """
+
+ def lookupAllRecords(name, timeout = 10):
+ """
+ Lookup all records associated with C{name}.
+ """
+
+ def lookupZone(name, timeout = 10):
+ """
+ Perform a zone transfer for the given C{name}.
+ """
+
+
+class IReactorArbitrary(Interface):
+
+ def listenWith(portType, *args, **kw):
+ """
+ Start an instance of the given C{portType} listening.
+
+ @type portType: type which implements L{IListeningPort}
+
+ @param portType: The object given by C{portType(*args, **kw)} will be
+ started listening.
+
+ @return: an object which provides L{IListeningPort}.
+ """
+
+ def connectWith(connectorType, *args, **kw):
+ """
+ Start an instance of the given C{connectorType} connecting.
+
+ @type connectorType: type which implements L{IConnector}
+
+ @param connectorType: The object given by C{connectorType(*args, **kw)}
+ will be started connecting.
+
+ @return: An object which provides L{IConnector}.
+ """
+
+class IReactorTCP(Interface):
+
+ def listenTCP(port, factory, backlog=50, interface=''):
+ """
+ Connects a given protocol factory to the given numeric TCP/IP port.
+
+ @param port: a port number on which to listen
+
+ @param factory: a L{twisted.internet.protocol.ServerFactory} instance
+
+ @param backlog: size of the listen queue
+
+ @param interface: the hostname to bind to, defaults to '' (all)
+
+ @return: an object that provides L{IListeningPort}.
+
+ @raise CannotListenError: as defined here
+ L{twisted.internet.error.CannotListenError},
+ if it cannot listen on this port (e.g., it
+ cannot bind to the required port number)
+ """
+
+ def connectTCP(host, port, factory, timeout=30, bindAddress=None):
+ """
+ Connect a TCP client.
+
+ @param host: a host name
+
+ @param port: a port number
+
+ @param factory: a L{twisted.internet.protocol.ClientFactory} instance
+
+ @param timeout: number of seconds to wait before assuming the
+ connection has failed.
+
+ @param bindAddress: a (host, port) tuple of local address to bind
+ to, or None.
+
+ @return: An object which provides L{IConnector}. This connector will
+ call various callbacks on the factory when a connection is
+ made, failed, or lost - see
+ L{ClientFactory<twisted.internet.protocol.ClientFactory>}
+ docs for details.
+ """
+
+class IReactorSSL(Interface):
+
+ def connectSSL(host, port, factory, contextFactory, timeout=30, bindAddress=None):
+ """
+ Connect a client Protocol to a remote SSL socket.
+
+ @param host: a host name
+
+ @param port: a port number
+
+ @param factory: a L{twisted.internet.protocol.ClientFactory} instance
+
+ @param contextFactory: a L{twisted.internet.ssl.ClientContextFactory} object.
+
+ @param timeout: number of seconds to wait before assuming the
+ connection has failed.
+
+ @param bindAddress: a (host, port) tuple of local address to bind to,
+ or C{None}.
+
+ @return: An object which provides L{IConnector}.
+ """
+
+ def listenSSL(port, factory, contextFactory, backlog=50, interface=''):
+ """
+ Connects a given protocol factory to the given numeric TCP/IP port.
+ The connection is a SSL one, using contexts created by the context
+ factory.
+
+ @param port: a port number on which to listen
+
+ @param factory: a L{twisted.internet.protocol.ServerFactory} instance
+
+ @param contextFactory: a L{twisted.internet.ssl.ContextFactory} instance
+
+ @param backlog: size of the listen queue
+
+ @param interface: the hostname to bind to, defaults to '' (all)
+ """
+
+
+class IReactorUNIX(Interface):
+ """
+ UNIX socket methods.
+ """
+
+ def connectUNIX(address, factory, timeout=30, checkPID=0):
+ """
+ Connect a client protocol to a UNIX socket.
+
+ @param address: a path to a unix socket on the filesystem.
+
+ @param factory: a L{twisted.internet.protocol.ClientFactory} instance
+
+ @param timeout: number of seconds to wait before assuming the connection
+ has failed.
+
+ @param checkPID: if True, check for a pid file to verify that a server
+ is listening.
+
+ @return: An object which provides L{IConnector}.
+ """
+
+ def listenUNIX(address, factory, backlog=50, mode=0666, wantPID=0):
+ """
+ Listen on a UNIX socket.
+
+ @param address: a path to a unix socket on the filesystem.
+
+ @param factory: a L{twisted.internet.protocol.Factory} instance.
+
+ @param backlog: number of connections to allow in backlog.
+
+ @param mode: mode to set on the unix socket. This parameter is
+ deprecated. Permissions should be set on the directory which
+ contains the UNIX socket.
+
+ @param wantPID: if True, create a pidfile for the socket.
+
+ @return: An object which provides L{IListeningPort}.
+ """
+
+
+class IReactorUNIXDatagram(Interface):
+ """
+ Datagram UNIX socket methods.
+ """
+
+ def connectUNIXDatagram(address, protocol, maxPacketSize=8192, mode=0666, bindAddress=None):
+ """
+ Connect a client protocol to a datagram UNIX socket.
+
+ @param address: a path to a unix socket on the filesystem.
+
+ @param protocol: a L{twisted.internet.protocol.ConnectedDatagramProtocol} instance
+
+ @param maxPacketSize: maximum packet size to accept
+
+ @param mode: mode to set on the unix socket. This parameter is
+ deprecated. Permissions should be set on the directory which
+ contains the UNIX socket.
+
+ @param bindAddress: address to bind to
+
+ @return: An object which provides L{IConnector}.
+ """
+
+ def listenUNIXDatagram(address, protocol, maxPacketSize=8192, mode=0666):
+ """
+ Listen on a datagram UNIX socket.
+
+ @param address: a path to a unix socket on the filesystem.
+
+ @param protocol: a L{twisted.internet.protocol.DatagramProtocol} instance.
+
+ @param maxPacketSize: maximum packet size to accept
+
+ @param mode: mode to set on the unix socket. This parameter is
+ deprecated. Permissions should be set on the directory which
+ contains the UNIX socket.
+
+ @return: An object which provides L{IListeningPort}.
+ """
+
+
+class IReactorUDP(Interface):
+ """
+ UDP socket methods.
+ """
+
+ def listenUDP(port, protocol, interface='', maxPacketSize=8192):
+ """
+ Connects a given DatagramProtocol to the given numeric UDP port.
+
+ @return: object which provides L{IListeningPort}.
+ """
+
+
+
+class IReactorMulticast(Interface):
+ """
+ UDP socket methods that support multicast.
+
+ IMPORTANT: This is an experimental new interface. It may change
+ without backwards compatability. Suggestions are welcome.
+ """
+
+ def listenMulticast(port, protocol, interface='', maxPacketSize=8192,
+ listenMultiple=False):
+ """
+ Connects a given
+ L{DatagramProtocol<twisted.internet.protocol.DatagramProtocol>} to the
+ given numeric UDP port.
+
+ @param listenMultiple: boolean indicating whether multiple sockets can
+ bind to same UDP port.
+
+ @returns: An object which provides L{IListeningPort}.
+ """
+
+
+class IReactorProcess(Interface):
+
+ def spawnProcess(processProtocol, executable, args=(), env={}, path=None,
+ uid=None, gid=None, usePTY=0, childFDs=None):
+ """
+ Spawn a process, with a process protocol.
+
+ @type processProtocol: L{IProcessProtocol} provider
+ @param processProtocol: An object which will be notified of all
+ events related to the created process.
+
+ @param executable: the file name to spawn - the full path should be
+ used.
+
+ @param args: the command line arguments to pass to the process; a
+ sequence of strings. The first string should be the
+ executable's name.
+
+ @type env: a C{dict} mapping C{str} to C{str}, or C{None}.
+ @param env: the environment variables to pass to the child process. The
+ resulting behavior varies between platforms. If
+ - C{env} is not set:
+ - On POSIX: pass an empty environment.
+ - On Windows: pass C{os.environ}.
+ - C{env} is C{None}:
+ - On POSIX: pass C{os.environ}.
+ - On Windows: pass C{os.environ}.
+ - C{env} is a C{dict}:
+ - On POSIX: pass the key/value pairs in C{env} as the
+ complete environment.
+ - On Windows: update C{os.environ} with the key/value
+ pairs in the C{dict} before passing it. As a
+ consequence of U{bug #1640
+ <http://twistedmatrix.com/trac/ticket/1640>}, passing
+ keys with empty values in an effort to unset
+ environment variables I{won't} unset them.
+
+ @param path: the path to run the subprocess in - defaults to the
+ current directory.
+
+ @param uid: user ID to run the subprocess as. (Only available on
+ POSIX systems.)
+
+ @param gid: group ID to run the subprocess as. (Only available on
+ POSIX systems.)
+
+ @param usePTY: if true, run this process in a pseudo-terminal.
+ optionally a tuple of C{(masterfd, slavefd, ttyname)},
+ in which case use those file descriptors.
+ (Not available on all systems.)
+
+ @param childFDs: A dictionary mapping file descriptors in the new child
+ process to an integer or to the string 'r' or 'w'.
+
+ If the value is an integer, it specifies a file
+ descriptor in the parent process which will be mapped
+ to a file descriptor (specified by the key) in the
+ child process. This is useful for things like inetd
+ and shell-like file redirection.
+
+ If it is the string 'r', a pipe will be created and
+ attached to the child at that file descriptor: the
+ child will be able to write to that file descriptor
+ and the parent will receive read notification via the
+ L{IProcessProtocol.childDataReceived} callback. This
+ is useful for the child's stdout and stderr.
+
+ If it is the string 'w', similar setup to the previous
+ case will occur, with the pipe being readable by the
+ child instead of writeable. The parent process can
+ write to that file descriptor using
+ L{IProcessTransport.writeToChild}. This is useful for
+ the child's stdin.
+
+ If childFDs is not passed, the default behaviour is to
+ use a mapping that opens the usual stdin/stdout/stderr
+ pipes.
+
+ @see: L{twisted.internet.protocol.ProcessProtocol}
+
+ @return: An object which provides L{IProcessTransport}.
+
+ @raise OSError: Raised with errno C{EAGAIN} or C{ENOMEM} if there are
+ insufficient system resources to create a new process.
+ """
+
+class IReactorTime(Interface):
+ """
+ Time methods that a Reactor should implement.
+ """
+
+ def seconds():
+ """
+ Get the current time in seconds.
+
+ @return: A number-like object of some sort.
+ """
+
+
+ def callLater(delay, callable, *args, **kw):
+ """
+ Call a function later.
+
+ @type delay: C{float}
+ @param delay: the number of seconds to wait.
+
+ @param callable: the callable object to call later.
+
+ @param args: the arguments to call it with.
+
+ @param kw: the keyword arguments to call it with.
+
+ @return: An object which provides L{IDelayedCall} and can be used to
+ cancel the scheduled call, by calling its C{cancel()} method.
+ It also may be rescheduled by calling its C{delay()} or
+ C{reset()} methods.
+ """
+
+ def cancelCallLater(callID):
+ """
+ This method is deprecated.
+
+ Cancel a call that would happen later.
+
+ @param callID: this is an opaque identifier returned from C{callLater}
+ that will be used to cancel a specific call.
+
+ @raise ValueError: if the callID is not recognized.
+ """
+
+ def getDelayedCalls():
+ """
+ Retrieve all currently scheduled delayed calls.
+
+ @return: A tuple of all L{IDelayedCall} providers representing all
+ currently scheduled calls. This is everything that has been
+ returned by C{callLater} but not yet called or canceled.
+ """
+
+
+class IDelayedCall(Interface):
+ """
+ A scheduled call.
+
+ There are probably other useful methods we can add to this interface;
+ suggestions are welcome.
+ """
+
+ def getTime():
+ """
+ Get time when delayed call will happen.
+
+ @return: time in seconds since epoch (a float).
+ """
+
+ def cancel():
+ """
+ Cancel the scheduled call.
+
+ @raises twisted.internet.error.AlreadyCalled: if the call has already
+ happened.
+ @raises twisted.internet.error.AlreadyCancelled: if the call has already
+ been cancelled.
+ """
+
+ def delay(secondsLater):
+ """
+ Delay the scheduled call.
+
+ @param secondsLater: how many seconds from its current firing time to delay
+
+ @raises twisted.internet.error.AlreadyCalled: if the call has already
+ happened.
+ @raises twisted.internet.error.AlreadyCancelled: if the call has already
+ been cancelled.
+ """
+
+ def reset(secondsFromNow):
+ """
+ Reset the scheduled call's timer.
+
+ @param secondsFromNow: how many seconds from now it should fire,
+ equivalent to C{.cancel()} and then doing another
+ C{reactor.callLater(secondsLater, ...)}
+
+ @raises twisted.internet.error.AlreadyCalled: if the call has already
+ happened.
+ @raises twisted.internet.error.AlreadyCancelled: if the call has already
+ been cancelled.
+ """
+
+ def active():
+ """
+ @return: True if this call is still active, False if it has been
+ called or cancelled.
+ """
+
+class IReactorThreads(Interface):
+ """
+ Dispatch methods to be run in threads.
+
+ Internally, this should use a thread pool and dispatch methods to them.
+ """
+
+ def getThreadPool():
+ """
+ Return the threadpool used by L{callInThread}. Create it first if
+ necessary.
+
+ @rtype: L{twisted.python.threadpool.ThreadPool}
+ """
+
+
+ def callInThread(callable, *args, **kwargs):
+ """
+ Run the callable object in a separate thread.
+ """
+
+
+ def callFromThread(callable, *args, **kw):
+ """
+ Cause a function to be executed by the reactor thread.
+
+ Use this method when you want to run a function in the reactor's thread
+ from another thread. Calling L{callFromThread} should wake up the main
+ thread (where L{reactor.run()<reactor.run>} is executing) and run the
+ given callable in that thread.
+
+ If you're writing a multi-threaded application the C{callable} may need
+ to be thread safe, but this method doesn't require it as such. If you
+ want to call a function in the next mainloop iteration, but you're in
+ the same thread, use L{callLater} with a delay of 0.
+ """
+
+
+ def suggestThreadPoolSize(size):
+ """
+ Suggest the size of the internal threadpool used to dispatch functions
+ passed to L{callInThread}.
+ """
+
+
+class IReactorCore(Interface):
+ """
+ Core methods that a Reactor must implement.
+ """
+
+ running = Attribute(
+ "A C{bool} which is C{True} from I{during startup} to "
+ "I{during shutdown} and C{False} the rest of the time.")
+
+
+ def resolve(name, timeout=10):
+ """
+ Return a L{twisted.internet.defer.Deferred} that will resolve a hostname.
+ """
+
+ def run():
+ """
+ Fire 'startup' System Events, move the reactor to the 'running'
+ state, then run the main loop until it is stopped with C{stop()} or
+ C{crash()}.
+ """
+
+ def stop():
+ """
+ Fire 'shutdown' System Events, which will move the reactor to the
+ 'stopped' state and cause C{reactor.run()} to exit.
+ """
+
+ def crash():
+ """
+ Stop the main loop *immediately*, without firing any system events.
+
+ This is named as it is because this is an extremely "rude" thing to do;
+ it is possible to lose data and put your system in an inconsistent
+ state by calling this. However, it is necessary, as sometimes a system
+ can become wedged in a pre-shutdown call.
+ """
+
+ def iterate(delay=0):
+ """
+ Run the main loop's I/O polling function for a period of time.
+
+ This is most useful in applications where the UI is being drawn "as
+ fast as possible", such as games. All pending L{IDelayedCall}s will
+ be called.
+
+ The reactor must have been started (via the C{run()} method) prior to
+ any invocations of this method. It must also be stopped manually
+ after the last call to this method (via the C{stop()} method). This
+ method is not re-entrant: you must not call it recursively; in
+ particular, you must not call it while the reactor is running.
+ """
+
+ def fireSystemEvent(eventType):
+ """
+ Fire a system-wide event.
+
+ System-wide events are things like 'startup', 'shutdown', and
+ 'persist'.
+ """
+
+ def addSystemEventTrigger(phase, eventType, callable, *args, **kw):
+ """
+ Add a function to be called when a system event occurs.
+
+ Each "system event" in Twisted, such as 'startup', 'shutdown', and
+ 'persist', has 3 phases: 'before', 'during', and 'after' (in that
+ order, of course). These events will be fired internally by the
+ Reactor.
+
+ An implementor of this interface must only implement those events
+ described here.
+
+ Callbacks registered for the "before" phase may return either None or a
+ Deferred. The "during" phase will not execute until all of the
+ Deferreds from the "before" phase have fired.
+
+ Once the "during" phase is running, all of the remaining triggers must
+ execute; their return values must be ignored.
+
+ @param phase: a time to call the event -- either the string 'before',
+ 'after', or 'during', describing when to call it
+ relative to the event's execution.
+
+ @param eventType: this is a string describing the type of event.
+
+ @param callable: the object to call before shutdown.
+
+ @param args: the arguments to call it with.
+
+ @param kw: the keyword arguments to call it with.
+
+ @return: an ID that can be used to remove this call with
+ removeSystemEventTrigger.
+ """
+
+ def removeSystemEventTrigger(triggerID):
+ """
+ Removes a trigger added with addSystemEventTrigger.
+
+ @param triggerID: a value returned from addSystemEventTrigger.
+
+ @raise KeyError: If there is no system event trigger for the given
+ C{triggerID}.
+
+ @raise ValueError: If there is no system event trigger for the given
+ C{triggerID}.
+
+ @raise TypeError: If there is no system event trigger for the given
+ C{triggerID}.
+ """
+
+ def callWhenRunning(callable, *args, **kw):
+ """
+ Call a function when the reactor is running.
+
+ If the reactor has not started, the callable will be scheduled
+ to run when it does start. Otherwise, the callable will be invoked
+ immediately.
+
+ @param callable: the callable object to call later.
+
+ @param args: the arguments to call it with.
+
+ @param kw: the keyword arguments to call it with.
+
+ @return: None if the callable was invoked, otherwise a system
+ event id for the scheduled call.
+ """
+
+
+class IReactorPluggableResolver(Interface):
+ """
+ A reactor with a pluggable name resolver interface.
+ """
+
+ def installResolver(resolver):
+ """
+ Set the internal resolver to use to for name lookups.
+
+ @type resolver: An object implementing the L{IResolverSimple} interface
+ @param resolver: The new resolver to use.
+
+ @return: The previously installed resolver.
+ """
+
+
+class IReactorFDSet(Interface):
+ """
+ Implement me to be able to use L{IFileDescriptor} type resources.
+
+ This assumes that your main-loop uses UNIX-style numeric file descriptors
+ (or at least similarly opaque IDs returned from a .fileno() method)
+ """
+
+ def addReader(reader):
+ """
+ I add reader to the set of file descriptors to get read events for.
+
+ @param reader: An L{IReadDescriptor} provider that will be checked for
+ read events until it is removed from the reactor with
+ L{removeReader}.
+
+ @return: C{None}.
+ """
+
+ def addWriter(writer):
+ """
+ I add writer to the set of file descriptors to get write events for.
+
+ @param writer: An L{IWriteDescriptor} provider that will be checked for
+ write events until it is removed from the reactor with
+ L{removeWriter}.
+
+ @return: C{None}.
+ """
+
+ def removeReader(reader):
+ """
+ Removes an object previously added with L{addReader}.
+
+ @return: C{None}.
+ """
+
+ def removeWriter(writer):
+ """
+ Removes an object previously added with L{addWriter}.
+
+ @return: C{None}.
+ """
+
+ def removeAll():
+ """
+ Remove all readers and writers.
+
+ Should not remove reactor internal reactor connections (like a waker).
+
+ @return: A list of L{IReadDescriptor} and L{IWriteDescriptor} providers
+ which were removed.
+ """
+
+ def getReaders():
+ """
+ Return the list of file descriptors currently monitored for input
+ events by the reactor.
+
+ @return: the list of file descriptors monitored for input events.
+ @rtype: C{list} of C{IReadDescriptor}
+ """
+
+ def getWriters():
+ """
+ Return the list file descriptors currently monitored for output events
+ by the reactor.
+
+ @return: the list of file descriptors monitored for output events.
+ @rtype: C{list} of C{IWriteDescriptor}
+ """
+
+
+class IListeningPort(Interface):
+ """
+ A listening port.
+ """
+
+ def startListening():
+ """
+ Start listening on this port.
+
+ @raise CannotListenError: If it cannot listen on this port (e.g., it is
+ a TCP port and it cannot bind to the required
+ port number).
+ """
+
+ def stopListening():
+ """
+ Stop listening on this port.
+
+ If it does not complete immediately, will return Deferred that fires
+ upon completion.
+ """
+
+ def getHost():
+ """
+ Get the host that this port is listening for.
+
+ @return: An L{IAddress} provider.
+ """
+
+
+class ILoggingContext(Interface):
+ """
+ Give context information that will be used to log events generated by
+ this item.
+ """
+
+ def logPrefix():
+ """
+ @return: Prefix used during log formatting to indicate context.
+ @rtype: C{str}
+ """
+
+
+class IFileDescriptor(ILoggingContext):
+ """
+ An interface representing a UNIX-style numeric file descriptor.
+ """
+
+ def fileno():
+ """
+ @return: The platform-specified representation of a file descriptor
+ number.
+ """
+
+ def connectionLost(reason):
+ """
+ Called when the connection was lost.
+
+ This is called when the connection on a selectable object has been
+ lost. It will be called whether the connection was closed explicitly,
+ an exception occurred in an event handler, or the other end of the
+ connection closed it first.
+
+ See also L{IHalfCloseableDescriptor} if your descriptor wants to be
+ notified separately of the two halves of the connection being closed.
+
+ @param reason: A failure instance indicating the reason why the
+ connection was lost. L{error.ConnectionLost} and
+ L{error.ConnectionDone} are of special note, but the
+ failure may be of other classes as well.
+ """
+
+
+class IReadDescriptor(IFileDescriptor):
+ """
+ An L{IFileDescriptor} that can read.
+
+ This interface is generally used in conjunction with L{IReactorFDSet}.
+ """
+
+ def doRead():
+ """
+ Some data is available for reading on your descriptor.
+ """
+
+
+class IWriteDescriptor(IFileDescriptor):
+ """
+ An L{IFileDescriptor} that can write.
+
+ This interface is generally used in conjunction with L{IReactorFDSet}.
+ """
+
+ def doWrite():
+ """
+ Some data can be written to your descriptor.
+ """
+
+
+class IReadWriteDescriptor(IReadDescriptor, IWriteDescriptor):
+ """
+ An L{IFileDescriptor} that can both read and write.
+ """
+
+
+class IHalfCloseableDescriptor(Interface):
+ """
+ A descriptor that can be half-closed.
+ """
+
+ def writeConnectionLost(reason):
+ """
+ Indicates write connection was lost.
+ """
+
+ def readConnectionLost(reason):
+ """
+ Indicates read connection was lost.
+ """
+
+
+class ISystemHandle(Interface):
+ """
+ An object that wraps a networking OS-specific handle.
+ """
+
+ def getHandle():
+ """
+ Return a system- and reactor-specific handle.
+
+ This might be a socket.socket() object, or some other type of
+ object, depending on which reactor is being used. Use and
+ manipulate at your own risk.
+
+ This might be used in cases where you want to set specific
+ options not exposed by the Twisted APIs.
+ """
+
+
+class IConsumer(Interface):
+ """
+ A consumer consumes data from a producer.
+ """
+
+ def registerProducer(producer, streaming):
+ """
+ Register to receive data from a producer.
+
+ This sets self to be a consumer for a producer. When this object runs
+ out of data (as when a send(2) call on a socket succeeds in moving the
+ last data from a userspace buffer into a kernelspace buffer), it will
+ ask the producer to resumeProducing().
+
+ For L{IPullProducer} providers, C{resumeProducing} will be called once
+ each time data is required.
+
+ For L{IPushProducer} providers, C{pauseProducing} will be called
+ whenever the write buffer fills up and C{resumeProducing} will only be
+ called when it empties.
+
+ @type producer: L{IProducer} provider
+
+ @type streaming: C{bool}
+ @param streaming: C{True} if C{producer} provides L{IPushProducer},
+ C{False} if C{producer} provides L{IPullProducer}.
+
+ @raise RuntimeError: If a producer is already registered.
+
+ @return: C{None}
+ """
+
+ def unregisterProducer():
+ """
+ Stop consuming data from a producer, without disconnecting.
+ """
+
+ def write(data):
+ """
+ The producer will write data by calling this method.
+ """
+
+class IFinishableConsumer(IConsumer):
+ """
+ A Consumer for producers that finish.
+ """
+
+ def finish():
+ """
+ The producer has finished producing.
+ """
+
+class IProducer(Interface):
+ """
+ A producer produces data for a consumer.
+
+ Typically producing is done by calling the write method of an class
+ implementing L{IConsumer}.
+ """
+
+ def stopProducing():
+ """
+ Stop producing data.
+
+ This tells a producer that its consumer has died, so it must stop
+ producing data for good.
+ """
+
+
+class IPushProducer(IProducer):
+ """
+ A push producer, also known as a streaming producer is expected to
+ produce (write to this consumer) data on a continous basis, unless
+ it has been paused. A paused push producer will resume producing
+ after its resumeProducing() method is called. For a push producer
+ which is not pauseable, these functions may be noops.
+ """
+
+ def pauseProducing():
+ """
+ Pause producing data.
+
+ Tells a producer that it has produced too much data to process for
+ the time being, and to stop until resumeProducing() is called.
+ """
+ def resumeProducing():
+ """
+ Resume producing data.
+
+ This tells a producer to re-add itself to the main loop and produce
+ more data for its consumer.
+ """
+
+class IPullProducer(IProducer):
+ """
+ A pull producer, also known as a non-streaming producer, is
+ expected to produce data each time resumeProducing() is called.
+ """
+
+ def resumeProducing():
+ """
+ Produce data for the consumer a single time.
+
+ This tells a producer to produce data for the consumer once
+ (not repeatedly, once only). Typically this will be done
+ by calling the consumer's write() method a single time with
+ produced data.
+ """
+
+class IProtocol(Interface):
+
+ def dataReceived(data):
+ """
+ Called whenever data is received.
+
+ Use this method to translate to a higher-level message. Usually, some
+ callback will be made upon the receipt of each complete protocol
+ message.
+
+ @param data: a string of indeterminate length. Please keep in mind
+ that you will probably need to buffer some data, as partial
+ (or multiple) protocol messages may be received! I recommend
+ that unit tests for protocols call through to this method with
+ differing chunk sizes, down to one byte at a time.
+ """
+
+ def connectionLost(reason):
+ """
+ Called when the connection is shut down.
+
+ Clear any circular references here, and any external references
+ to this Protocol. The connection has been closed. The C{reason}
+ Failure wraps a L{twisted.internet.error.ConnectionDone} or
+ L{twisted.internet.error.ConnectionLost} instance (or a subclass
+ of one of those).
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+
+ def makeConnection(transport):
+ """
+ Make a connection to a transport and a server.
+ """
+
+ def connectionMade():
+ """
+ Called when a connection is made.
+
+ This may be considered the initializer of the protocol, because
+ it is called when the connection is completed. For clients,
+ this is called once the connection to the server has been
+ established; for servers, this is called after an accept() call
+ stops blocking and a socket has been received. If you need to
+ send any greeting or initial message, do it here.
+ """
+
+
+class IProcessProtocol(Interface):
+ """
+ Interface for process-related event handlers.
+ """
+
+ def makeConnection(process):
+ """
+ Called when the process has been created.
+
+ @type process: L{IProcessTransport} provider
+ @param process: An object representing the process which has been
+ created and associated with this protocol.
+ """
+
+
+ def childDataReceived(childFD, data):
+ """
+ Called when data arrives from the child process.
+
+ @type childFD: C{int}
+ @param childFD: The file descriptor from which the data was
+ received.
+
+ @type data: C{str}
+ @param data: The data read from the child's file descriptor.
+ """
+
+
+ def childConnectionLost(childFD):
+ """
+ Called when a file descriptor associated with the child process is
+ closed.
+
+ @type childFD: C{int}
+ @param childFD: The file descriptor which was closed.
+ """
+
+
+ def processExited(reason):
+ """
+ Called when the child process exits.
+
+ @type reason: L{twisted.python.failure.Failure}
+ @param reason: A failure giving the reason the child process
+ terminated. The type of exception for this failure is either
+ L{twisted.internet.error.ProcessDone} or
+ L{twisted.internet.error.ProcessTerminated}.
+
+ @since: 8.2
+ """
+
+
+ def processEnded(reason):
+ """
+ Called when the child process exits and all file descriptors associated
+ with it have been closed.
+
+ @type reason: L{twisted.python.failure.Failure}
+ @param reason: A failure giving the reason the child process
+ terminated. The type of exception for this failure is either
+ L{twisted.internet.error.ProcessDone} or
+ L{twisted.internet.error.ProcessTerminated}.
+ """
+
+
+
+class IHalfCloseableProtocol(Interface):
+ """
+ Implemented to indicate they want notification of half-closes.
+
+ TCP supports the notion of half-closing the connection, e.g.
+ closing the write side but still not stopping reading. A protocol
+ that implements this interface will be notified of such events,
+ instead of having connectionLost called.
+ """
+
+ def readConnectionLost():
+ """
+ Notification of the read connection being closed.
+
+ This indicates peer did half-close of write side. It is now
+ the responsiblity of the this protocol to call
+ loseConnection(). In addition, the protocol MUST make sure a
+ reference to it still exists (i.e. by doing a callLater with
+ one of its methods, etc.) as the reactor will only have a
+ reference to it if it is writing.
+
+ If the protocol does not do so, it might get garbage collected
+ without the connectionLost method ever being called.
+ """
+
+ def writeConnectionLost():
+ """
+ Notification of the write connection being closed.
+
+ This will never be called for TCP connections as TCP does not
+ support notification of this type of half-close.
+ """
+
+
+class IProtocolFactory(Interface):
+ """
+ Interface for protocol factories.
+ """
+
+ def buildProtocol(addr):
+ """
+ Called when a connection has been established to addr.
+
+ If None is returned, the connection is assumed to have been refused,
+ and the Port will close the connection.
+
+ @type addr: (host, port)
+ @param addr: The address of the newly-established connection
+
+ @return: None if the connection was refused, otherwise an object
+ providing L{IProtocol}.
+ """
+
+ def doStart():
+ """
+ Called every time this is connected to a Port or Connector.
+ """
+
+ def doStop():
+ """
+ Called every time this is unconnected from a Port or Connector.
+ """
+
+
+class ITransport(Interface):
+ """
+ I am a transport for bytes.
+
+ I represent (and wrap) the physical connection and synchronicity
+ of the framework which is talking to the network. I make no
+ representations about whether calls to me will happen immediately
+ or require returning to a control loop, or whether they will happen
+ in the same or another thread. Consider methods of this class
+ (aside from getPeer) to be 'thrown over the wall', to happen at some
+ indeterminate time.
+ """
+
+ def write(data):
+ """
+ Write some data to the physical connection, in sequence, in a
+ non-blocking fashion.
+
+ If possible, make sure that it is all written. No data will
+ ever be lost, although (obviously) the connection may be closed
+ before it all gets through.
+ """
+
+ def writeSequence(data):
+ """
+ Write a list of strings to the physical connection.
+
+ If possible, make sure that all of the data is written to
+ the socket at once, without first copying it all into a
+ single string.
+ """
+
+ def loseConnection():
+ """
+ Close my connection, after writing all pending data.
+
+ Note that if there is a registered producer on a transport it
+ will not be closed until the producer has been unregistered.
+ """
+
+ def getPeer():
+ """
+ Get the remote address of this connection.
+
+ Treat this method with caution. It is the unfortunate result of the
+ CGI and Jabber standards, but should not be considered reliable for
+ the usual host of reasons; port forwarding, proxying, firewalls, IP
+ masquerading, etc.
+
+ @return: An L{IAddress} provider.
+ """
+
+ def getHost():
+ """
+ Similar to getPeer, but returns an address describing this side of the
+ connection.
+
+ @return: An L{IAddress} provider.
+ """
+
+
+class ITCPTransport(ITransport):
+ """
+ A TCP based transport.
+ """
+
+ def loseWriteConnection():
+ """
+ Half-close the write side of a TCP connection.
+
+ If the protocol instance this is attached to provides
+ IHalfCloseableProtocol, it will get notified when the operation is
+ done. When closing write connection, as with loseConnection this will
+ only happen when buffer has emptied and there is no registered
+ producer.
+ """
+
+ def getTcpNoDelay():
+ """
+ Return if C{TCP_NODELAY} is enabled.
+ """
+
+ def setTcpNoDelay(enabled):
+ """
+ Enable/disable C{TCP_NODELAY}.
+
+ Enabling C{TCP_NODELAY} turns off Nagle's algorithm. Small packets are
+ sent sooner, possibly at the expense of overall throughput.
+ """
+
+ def getTcpKeepAlive():
+ """
+ Return if C{SO_KEEPALIVE} is enabled.
+ """
+
+ def setTcpKeepAlive(enabled):
+ """
+ Enable/disable C{SO_KEEPALIVE}.
+
+ Enabling C{SO_KEEPALIVE} sends packets periodically when the connection
+ is otherwise idle, usually once every two hours. They are intended
+ to allow detection of lost peers in a non-infinite amount of time.
+ """
+
+ def getHost():
+ """
+ Returns L{IPv4Address}.
+ """
+
+ def getPeer():
+ """
+ Returns L{IPv4Address}.
+ """
+
+
+class ITLSTransport(ITCPTransport):
+ """
+ A TCP transport that supports switching to TLS midstream.
+
+ Once TLS mode is started the transport will implement L{ISSLTransport}.
+ """
+
+ def startTLS(contextFactory):
+ """
+ Initiate TLS negotiation.
+
+ @param contextFactory: A context factory (see L{ssl.py<twisted.internet.ssl>})
+ """
+
+class ISSLTransport(ITCPTransport):
+ """
+ A SSL/TLS based transport.
+ """
+
+ def getPeerCertificate():
+ """
+ Return an object with the peer's certificate info.
+ """
+
+
+class IProcessTransport(ITransport):
+ """
+ A process transport.
+ """
+
+ pid = Attribute(
+ "From before L{IProcessProtocol.makeConnection} is called to before "
+ "L{IProcessProtocol.processEnded} is called, C{pid} is an L{int} "
+ "giving the platform process ID of this process. C{pid} is L{None} "
+ "at all other times.")
+
+ def closeStdin():
+ """
+ Close stdin after all data has been written out.
+ """
+
+ def closeStdout():
+ """
+ Close stdout.
+ """
+
+ def closeStderr():
+ """
+ Close stderr.
+ """
+
+ def closeChildFD(descriptor):
+ """
+ Close a file descriptor which is connected to the child process, identified
+ by its FD in the child process.
+ """
+
+ def writeToChild(childFD, data):
+ """
+ Similar to L{ITransport.write} but also allows the file descriptor in
+ the child process which will receive the bytes to be specified.
+
+ This is not available on all platforms.
+
+ @type childFD: C{int}
+ @param childFD: The file descriptor to which to write.
+
+ @type data: C{str}
+ @param data: The bytes to write.
+
+ @return: C{None}
+ """
+
+ def loseConnection():
+ """
+ Close stdin, stderr and stdout.
+ """
+
+ def signalProcess(signalID):
+ """
+ Send a signal to the process.
+
+ @param signalID: can be
+ - one of C{\"HUP\"}, C{\"KILL\"}, C{\"STOP\"}, or C{\"INT\"}.
+ These will be implemented in a
+ cross-platform manner, and so should be used
+ if possible.
+ - an integer, where it represents a POSIX
+ signal ID.
+
+ @raise twisted.internet.error.ProcessExitedAlready: The process has
+ already exited.
+ """
+
+
+class IServiceCollection(Interface):
+ """
+ An object which provides access to a collection of services.
+ """
+
+ def getServiceNamed(serviceName):
+ """
+ Retrieve the named service from this application.
+
+ Raise a C{KeyError} if there is no such service name.
+ """
+
+ def addService(service):
+ """
+ Add a service to this collection.
+ """
+
+ def removeService(service):
+ """
+ Remove a service from this collection.
+ """
+
+
+class IUDPTransport(Interface):
+ """
+ Transport for UDP DatagramProtocols.
+ """
+
+ def write(packet, addr=None):
+ """
+ Write packet to given address.
+
+ @param addr: a tuple of (ip, port). For connected transports must
+ be the address the transport is connected to, or None.
+ In non-connected mode this is mandatory.
+
+ @raise twisted.internet.error.MessageLengthError: C{packet} was too
+ long.
+ """
+
+ def connect(host, port):
+ """
+ Connect the transport to an address.
+
+ This changes it to connected mode. Datagrams can only be sent to
+ this address, and will only be received from this address. In addition
+ the protocol's connectionRefused method might get called if destination
+ is not receiving datagrams.
+
+ @param host: an IP address, not a domain name ('127.0.0.1', not 'localhost')
+ @param port: port to connect to.
+ """
+
+ def getHost():
+ """
+ Returns L{IPv4Address}.
+ """
+
+ def stopListening():
+ """
+ Stop listening on this port.
+
+ If it does not complete immediately, will return L{Deferred} that fires
+ upon completion.
+ """
+
+
+class IUDPConnectedTransport(Interface):
+ """
+ DEPRECATED. Transport for UDP ConnectedPacketProtocols.
+ """
+
+ def write(packet):
+ """
+ Write packet to address we are connected to.
+ """
+
+ def getHost():
+ """
+ Returns L{UNIXAddress}.
+ """
+
+
+class IUNIXDatagramTransport(Interface):
+ """
+ Transport for UDP PacketProtocols.
+ """
+
+ def write(packet, address):
+ """
+ Write packet to given address.
+ """
+
+ def getHost():
+ """
+ Returns L{UNIXAddress}.
+ """
+
+
+class IUNIXDatagramConnectedTransport(Interface):
+ """
+ Transport for UDP ConnectedPacketProtocols.
+ """
+
+ def write(packet):
+ """
+ Write packet to address we are connected to.
+ """
+
+ def getHost():
+ """
+ Returns L{UNIXAddress}.
+ """
+
+ def getPeer():
+ """
+ Returns L{UNIXAddress}.
+ """
+
+
+class IMulticastTransport(Interface):
+ """
+ Additional functionality for multicast UDP.
+ """
+
+ def getOutgoingInterface():
+ """
+ Return interface of outgoing multicast packets.
+ """
+
+ def setOutgoingInterface(addr):
+ """
+ Set interface for outgoing multicast packets.
+
+ Returns Deferred of success.
+ """
+
+ def getLoopbackMode():
+ """
+ Return if loopback mode is enabled.
+ """
+
+ def setLoopbackMode(mode):
+ """
+ Set if loopback mode is enabled.
+ """
+
+ def getTTL():
+ """
+ Get time to live for multicast packets.
+ """
+
+ def setTTL(ttl):
+ """
+ Set time to live on multicast packets.
+ """
+
+ def joinGroup(addr, interface=""):
+ """
+ Join a multicast group. Returns L{Deferred} of success or failure.
+
+ If an error occurs, the returned L{Deferred} will fail with
+ L{error.MulticastJoinError}.
+ """
+
+ def leaveGroup(addr, interface=""):
+ """
+ Leave multicast group, return L{Deferred} of success.
+ """
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/__init__.py b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/__init__.py
new file mode 100644
index 0000000000..ad494ae6c6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/__init__.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+I/O Completion Ports reactor
+"""
+
+from twisted.internet.iocpreactor.reactor import install
+
+__all__ = ['install']
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/abstract.py b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/abstract.py
new file mode 100644
index 0000000000..c1838a51b3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/abstract.py
@@ -0,0 +1,456 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Abstract file handle class
+"""
+
+from twisted.internet import main, error, interfaces
+from twisted.python import log, failure
+from twisted.persisted import styles
+
+from zope.interface import implements
+import errno
+
+from twisted.internet.iocpreactor.const import ERROR_HANDLE_EOF
+from twisted.internet.iocpreactor.const import ERROR_IO_PENDING
+from twisted.internet.iocpreactor import iocpsupport as _iocp
+
+
+
+class FileHandle(log.Logger, styles.Ephemeral, object):
+ """
+ File handle that can read and write asynchronously
+ """
+ implements(interfaces.IProducer, interfaces.IConsumer,
+ interfaces.ITransport, interfaces.IHalfCloseableDescriptor)
+ # read stuff
+ maxReadBuffers = 16
+ readBufferSize = 4096
+ reading = False
+ dynamicReadBuffers = True # set this to false if subclass doesn't do iovecs
+ _readNextBuffer = 0
+ _readSize = 0 # how much data we have in the read buffer
+ _readScheduled = None
+ _readScheduledInOS = False
+
+
+ def startReading(self):
+ self.reactor.addActiveHandle(self)
+ if not self._readScheduled and not self.reading:
+ self.reading = True
+ self._readScheduled = self.reactor.callLater(0,
+ self._resumeReading)
+
+
+ def stopReading(self):
+ if self._readScheduled:
+ self._readScheduled.cancel()
+ self._readScheduled = None
+ self.reading = False
+
+
+ def _resumeReading(self):
+ self._readScheduled = None
+ if self._dispatchData() and not self._readScheduledInOS:
+ self.doRead()
+
+
+ def _dispatchData(self):
+ """
+ Dispatch previously read data. Return True if self.reading and we don't
+ have any more data
+ """
+ if not self._readSize:
+ return self.reading
+ size = self._readSize
+ full_buffers = size // self.readBufferSize
+ while self._readNextBuffer < full_buffers:
+ self.dataReceived(self._readBuffers[self._readNextBuffer])
+ self._readNextBuffer += 1
+ if not self.reading:
+ return False
+ remainder = size % self.readBufferSize
+ if remainder:
+ self.dataReceived(buffer(self._readBuffers[full_buffers],
+ 0, remainder))
+ if self.dynamicReadBuffers:
+ total_buffer_size = self.readBufferSize * len(self._readBuffers)
+ # we have one buffer too many
+ if size < total_buffer_size - self.readBufferSize:
+ del self._readBuffers[-1]
+ # we filled all buffers, so allocate one more
+ elif (size == total_buffer_size and
+ len(self._readBuffers) < self.maxReadBuffers):
+ self._readBuffers.append(_iocp.AllocateReadBuffer(
+ self.readBufferSize))
+ self._readNextBuffer = 0
+ self._readSize = 0
+ return self.reading
+
+
+ def _cbRead(self, rc, bytes, evt):
+ self._readScheduledInOS = False
+ if self._handleRead(rc, bytes, evt):
+ self.doRead()
+
+
+ def _handleRead(self, rc, bytes, evt):
+ """
+ Returns False if we should stop reading for now
+ """
+ if self.disconnected:
+ return False
+ # graceful disconnection
+ if (not (rc or bytes)) or rc in (errno.WSAEDISCON, ERROR_HANDLE_EOF):
+ self.reactor.removeActiveHandle(self)
+ self.readConnectionLost(failure.Failure(main.CONNECTION_DONE))
+ return False
+ # XXX: not handling WSAEWOULDBLOCK
+ # ("too many outstanding overlapped I/O requests")
+ elif rc:
+ self.connectionLost(failure.Failure(
+ error.ConnectionLost("read error -- %s (%s)" %
+ (errno.errorcode.get(rc, 'unknown'), rc))))
+ return False
+ else:
+ assert self._readSize == 0
+ assert self._readNextBuffer == 0
+ self._readSize = bytes
+ return self._dispatchData()
+
+
+ def doRead(self):
+ numReads = 0
+ while 1:
+ evt = _iocp.Event(self._cbRead, self)
+
+ evt.buff = buff = self._readBuffers
+ rc, bytes = self.readFromHandle(buff, evt)
+
+ if (rc == ERROR_IO_PENDING
+ or (not rc and numReads >= self.maxReads)):
+ self._readScheduledInOS = True
+ break
+ else:
+ evt.ignore = True
+ if not self._handleRead(rc, bytes, evt):
+ break
+ numReads += 1
+
+
+ def readFromHandle(self, bufflist, evt):
+ raise NotImplementedError() # TODO: this should default to ReadFile
+
+
+ def dataReceived(self, data):
+ raise NotImplementedError
+
+
+ def readConnectionLost(self, reason):
+ self.connectionLost(reason)
+
+
+ # write stuff
+ dataBuffer = ''
+ offset = 0
+ writing = False
+ _writeScheduled = None
+ _writeDisconnecting = False
+ _writeDisconnected = False
+ writeBufferSize = 2**2**2**2
+ maxWrites = 5
+
+
+ def loseWriteConnection(self):
+ self._writeDisconnecting = True
+ self.startWriting()
+
+
+ def _closeWriteConnection(self):
+ # override in subclasses
+ pass
+
+
+ def writeConnectionLost(self, reason):
+ # in current code should never be called
+ self.connectionLost(reason)
+
+
+ def startWriting(self):
+ self.reactor.addActiveHandle(self)
+ self.writing = True
+ if not self._writeScheduled:
+ self._writeScheduled = self.reactor.callLater(0,
+ self._resumeWriting)
+
+
+ def stopWriting(self):
+ if self._writeScheduled:
+ self._writeScheduled.cancel()
+ self._writeScheduled = None
+ self.writing = False
+
+
+ def _resumeWriting(self):
+ self._writeScheduled = None
+ self.doWrite()
+
+
+ def _cbWrite(self, rc, bytes, evt):
+ if self._handleWrite(rc, bytes, evt):
+ self.doWrite()
+
+
+ def _handleWrite(self, rc, bytes, evt):
+ """
+ Returns false if we should stop writing for now
+ """
+ if self.disconnected or self._writeDisconnected:
+ return False
+ # XXX: not handling WSAEWOULDBLOCK
+ # ("too many outstanding overlapped I/O requests")
+ if rc:
+ self.connectionLost(failure.Failure(
+ error.ConnectionLost("write error -- %s (%s)" %
+ (errno.errorcode.get(rc, 'unknown'), rc))))
+ return False
+ else:
+ self.offset += bytes
+ # If there is nothing left to send,
+ if self.offset == len(self.dataBuffer) and not self._tempDataLen:
+ self.dataBuffer = ""
+ self.offset = 0
+ # stop writing
+ self.stopWriting()
+ # If I've got a producer who is supposed to supply me with data
+ if self.producer is not None and ((not self.streamingProducer)
+ or self.producerPaused):
+ # tell them to supply some more.
+ self.producerPaused = True
+ self.producer.resumeProducing()
+ elif self.disconnecting:
+ # But if I was previously asked to let the connection die,
+ # do so.
+ self.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ elif self._writeDisconnecting:
+ # I was previously asked to to half-close the connection.
+ self._closeWriteConnection()
+ self._writeDisconnected = True
+ return False
+ else:
+ return True
+
+
+ def doWrite(self):
+ numWrites = 0
+ while 1:
+ if len(self.dataBuffer) - self.offset < self.SEND_LIMIT:
+ # If there is currently less than SEND_LIMIT bytes left to send
+ # in the string, extend it with the array data.
+ self.dataBuffer = (buffer(self.dataBuffer, self.offset) +
+ "".join(self._tempDataBuffer))
+ self.offset = 0
+ self._tempDataBuffer = []
+ self._tempDataLen = 0
+
+ evt = _iocp.Event(self._cbWrite, self)
+
+ # Send as much data as you can.
+ if self.offset:
+ evt.buff = buff = buffer(self.dataBuffer, self.offset)
+ else:
+ evt.buff = buff = self.dataBuffer
+ rc, bytes = self.writeToHandle(buff, evt)
+ if (rc == ERROR_IO_PENDING
+ or (not rc and numWrites >= self.maxWrites)):
+ break
+ else:
+ evt.ignore = True
+ if not self._handleWrite(rc, bytes, evt):
+ break
+ numWrites += 1
+
+
+ def writeToHandle(self, buff, evt):
+ raise NotImplementedError() # TODO: this should default to WriteFile
+
+
+ def write(self, data):
+ """Reliably write some data.
+
+ The data is buffered until his file descriptor is ready for writing.
+ """
+ if isinstance(data, unicode): # no, really, I mean it
+ raise TypeError("Data must not be unicode")
+ if not self.connected or self._writeDisconnected:
+ return
+ if data:
+ self._tempDataBuffer.append(data)
+ self._tempDataLen += len(data)
+ if self.producer is not None:
+ if (len(self.dataBuffer) + self._tempDataLen
+ > self.writeBufferSize):
+ self.producerPaused = True
+ self.producer.pauseProducing()
+ self.startWriting()
+
+
+ def writeSequence(self, iovec):
+ if not self.connected or not iovec or self._writeDisconnected:
+ return
+ self._tempDataBuffer.extend(iovec)
+ for i in iovec:
+ self._tempDataLen += len(i)
+ if self.producer is not None:
+ if len(self.dataBuffer) + self._tempDataLen > self.writeBufferSize:
+ self.producerPaused = True
+ self.producer.pauseProducing()
+ self.startWriting()
+
+
+ # general stuff
+ connected = False
+ disconnected = False
+ disconnecting = False
+ logstr = "Uninitialized"
+
+ SEND_LIMIT = 128*1024
+
+ maxReads = 5
+
+
+ def __init__(self, reactor = None):
+ if not reactor:
+ from twisted.internet import reactor
+ self.reactor = reactor
+ self._tempDataBuffer = [] # will be added to dataBuffer in doWrite
+ self._tempDataLen = 0
+ self._readBuffers = [_iocp.AllocateReadBuffer(self.readBufferSize)]
+
+
+ def connectionLost(self, reason):
+ """
+ The connection was lost.
+
+ This is called when the connection on a selectable object has been
+ lost. It will be called whether the connection was closed explicitly,
+ an exception occurred in an event handler, or the other end of the
+ connection closed it first.
+
+ Clean up state here, but make sure to call back up to FileDescriptor.
+ """
+
+ self.disconnected = True
+ self.connected = False
+ if self.producer is not None:
+ self.producer.stopProducing()
+ self.producer = None
+ self.stopReading()
+ self.stopWriting()
+ self.reactor.removeActiveHandle(self)
+
+
+ def getFileHandle(self):
+ return -1
+
+
+ def loseConnection(self, _connDone=failure.Failure(main.CONNECTION_DONE)):
+ """
+ Close the connection at the next available opportunity.
+
+ Call this to cause this FileDescriptor to lose its connection. It will
+ first write any data that it has buffered.
+
+ If there is data buffered yet to be written, this method will cause the
+ transport to lose its connection as soon as it's done flushing its
+ write buffer. If you have a producer registered, the connection won't
+ be closed until the producer is finished. Therefore, make sure you
+ unregister your producer when it's finished, or the connection will
+ never close.
+ """
+
+ if self.connected and not self.disconnecting:
+ if self._writeDisconnected:
+ # doWrite won't trigger the connection close anymore
+ self.stopReading()
+ self.stopWriting
+ self.connectionLost(_connDone)
+ else:
+ self.stopReading()
+ self.startWriting()
+ self.disconnecting = 1
+
+
+ # Producer/consumer implementation
+
+ producerPaused = False
+ streamingProducer = False
+
+ # first, the consumer stuff. This requires no additional work, as
+ # any object you can write to can be a consumer, really.
+
+ producer = None
+
+
+ def registerProducer(self, producer, streaming):
+ """
+ Register to receive data from a producer.
+
+ This sets this selectable to be a consumer for a producer. When this
+ selectable runs out of data on a write() call, it will ask the producer
+ to resumeProducing(). A producer should implement the IProducer
+ interface.
+
+ FileDescriptor provides some infrastructure for producer methods.
+ """
+ if self.producer is not None:
+ raise RuntimeError(
+ "Cannot register producer %s, because producer "
+ "%s was never unregistered." % (producer, self.producer))
+ if self.disconnected:
+ producer.stopProducing()
+ else:
+ self.producer = producer
+ self.streamingProducer = streaming
+ if not streaming:
+ producer.resumeProducing()
+
+
+ def unregisterProducer(self):
+ """
+ Stop consuming data from a producer, without disconnecting.
+ """
+ self.producer = None
+
+
+ def stopConsuming(self):
+ """
+ Stop consuming data.
+
+ This is called when a producer has lost its connection, to tell the
+ consumer to go lose its connection (and break potential circular
+ references).
+ """
+ self.unregisterProducer()
+ self.loseConnection()
+
+
+ # producer interface implementation
+
+ def resumeProducing(self):
+ assert self.connected and not self.disconnecting
+ self.startReading()
+
+
+ def pauseProducing(self):
+ self.stopReading()
+
+
+ def stopProducing(self):
+ self.loseConnection()
+
+
+__all__ = ['FileHandle']
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/build.bat b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/build.bat
new file mode 100644
index 0000000000..25f361be91
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/build.bat
@@ -0,0 +1,4 @@
+del iocpsupport\iocpsupport.c iocpsupport.pyd
+del /f /s /q build
+python setup.py build_ext -i -c mingw32
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/const.py b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/const.py
new file mode 100644
index 0000000000..edc09f2615
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/const.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Windows constants for IOCP
+"""
+
+
+# this stuff should really be gotten from Windows headers via pyrex, but it
+# probably is not going to change
+
+ERROR_PORT_UNREACHABLE = 1234
+ERROR_NETWORK_UNREACHABLE = 1231
+ERROR_CONNECTION_REFUSED = 1225
+ERROR_IO_PENDING = 997
+ERROR_OPERATION_ABORTED = 995
+WAIT_TIMEOUT = 258
+ERROR_NETNAME_DELETED = 64
+ERROR_HANDLE_EOF = 38
+
+INFINITE = -1
+
+SO_UPDATE_CONNECT_CONTEXT = 0x7010
+SO_UPDATE_ACCEPT_CONTEXT = 0x700B
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/interfaces.py b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/interfaces.py
new file mode 100644
index 0000000000..180f3cc107
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/interfaces.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Interfaces for iocpreactor
+"""
+
+
+from zope.interface import Interface
+
+
+
+class IReadHandle(Interface):
+ def readFromHandle(bufflist, evt):
+ """
+ Read from this handle into the buffer list
+ """
+
+
+
+class IWriteHandle(Interface):
+ def writeToHandle(buff, evt):
+ """
+ Write the buffer to this handle
+ """
+
+
+
+class IReadWriteHandle(IReadHandle, IWriteHandle):
+ pass
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/acceptex.pxi b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/acceptex.pxi
new file mode 100644
index 0000000000..8fd76ef8c2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/acceptex.pxi
@@ -0,0 +1,38 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+def accept(long listening, long accepting, object buff, object obj):
+ cdef unsigned long bytes
+ cdef int size, rc
+ cdef void *mem_buffer
+ cdef myOVERLAPPED *ov
+
+ PyObject_AsWriteBuffer(buff, &mem_buffer, &size)
+
+ ov = makeOV()
+ if obj is not None:
+ ov.obj = <PyObject *>obj
+
+ rc = lpAcceptEx(listening, accepting, mem_buffer, 0, size / 2, size / 2,
+ &bytes, <OVERLAPPED *>ov)
+ if not rc:
+ rc = WSAGetLastError()
+ if rc != ERROR_IO_PENDING:
+ return rc
+
+ # operation is in progress
+ Py_XINCREF(obj)
+ return rc
+
+def get_accept_addrs(long s, object buff):
+ cdef WSAPROTOCOL_INFO wsa_pi
+ cdef int size, locallen, remotelen
+ cdef void *mem_buffer
+ cdef sockaddr *localaddr, *remoteaddr
+
+ PyObject_AsReadBuffer(buff, &mem_buffer, &size)
+
+ lpGetAcceptExSockaddrs(mem_buffer, 0, size / 2, size / 2, &localaddr, &locallen, &remoteaddr, &remotelen)
+ return remoteaddr.sa_family, _makesockaddr(localaddr, locallen), _makesockaddr(remoteaddr, remotelen)
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/connectex.pxi b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/connectex.pxi
new file mode 100644
index 0000000000..65f074344d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/connectex.pxi
@@ -0,0 +1,34 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+def connect(long s, object addr, object obj):
+ cdef int family, rc
+ cdef myOVERLAPPED *ov
+ cdef sockaddr name
+
+ if not have_connectex:
+ raise ValueError, 'ConnectEx is not available on this system'
+
+ family = getAddrFamily(s)
+ if family == AF_INET:
+ fillinetaddr(<sockaddr_in *>&name, addr)
+ else:
+ raise ValueError, 'unsupported address family'
+ name.sa_family = family
+
+ ov = makeOV()
+ if obj is not None:
+ ov.obj = <PyObject *>obj
+
+ rc = lpConnectEx(s, &name, sizeof(name), NULL, 0, NULL, <OVERLAPPED *>ov)
+
+ if not rc:
+ rc = WSAGetLastError()
+ if rc != ERROR_IO_PENDING:
+ return rc
+
+ # operation is in progress
+ Py_XINCREF(obj)
+ return rc
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/iocpsupport.c b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/iocpsupport.c
new file mode 100644
index 0000000000..4e0de7e91c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/iocpsupport.c
@@ -0,0 +1,2003 @@
+/* Generated by Pyrex 0.9.6.4 on Tue Mar 18 10:18:51 2008 */
+
+#define PY_SSIZE_T_CLEAN
+#include "Python.h"
+#include "structmember.h"
+#ifndef PY_LONG_LONG
+ #define PY_LONG_LONG LONG_LONG
+#endif
+#if PY_VERSION_HEX < 0x02050000
+ typedef int Py_ssize_t;
+ #define PY_SSIZE_T_MAX INT_MAX
+ #define PY_SSIZE_T_MIN INT_MIN
+ #define PyInt_FromSsize_t(z) PyInt_FromLong(z)
+ #define PyInt_AsSsize_t(o) PyInt_AsLong(o)
+#endif
+#ifndef WIN32
+ #ifndef __stdcall
+ #define __stdcall
+ #endif
+ #ifndef __cdecl
+ #define __cdecl
+ #endif
+#endif
+#ifdef __cplusplus
+#define __PYX_EXTERN_C extern "C"
+#else
+#define __PYX_EXTERN_C extern
+#endif
+#include <math.h>
+#include "io.h"
+#include "errno.h"
+#include "winsock2.h"
+#include "windows.h"
+#include "python.h"
+#include "string.h"
+#include "winsock_pointers.h"
+
+
+typedef struct {PyObject **p; char *s;} __Pyx_InternTabEntry; /*proto*/
+typedef struct {PyObject **p; char *s; long n;} __Pyx_StringTabEntry; /*proto*/
+
+static PyObject *__pyx_m;
+static PyObject *__pyx_b;
+static int __pyx_lineno;
+static char *__pyx_filename;
+static char **__pyx_f;
+
+static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, char *modname); /*proto*/
+
+static int __Pyx_GetStarArgs(PyObject **args, PyObject **kwds, char *kwd_list[], Py_ssize_t nargs, PyObject **args2, PyObject **kwds2, char rqd_kwds[]); /*proto*/
+
+static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list); /*proto*/
+
+static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name); /*proto*/
+
+static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb); /*proto*/
+
+static PyObject *__Pyx_UnpackItem(PyObject *); /*proto*/
+static int __Pyx_EndUnpack(PyObject *); /*proto*/
+
+static int __Pyx_InternStrings(__Pyx_InternTabEntry *t); /*proto*/
+
+static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); /*proto*/
+
+static void __Pyx_AddTraceback(char *funcname); /*proto*/
+
+/* Declarations from iocpsupport */
+
+typedef int __pyx_t_11iocpsupport_size_t;
+
+typedef unsigned long __pyx_t_11iocpsupport_HANDLE;
+
+typedef unsigned long __pyx_t_11iocpsupport_SOCKET;
+
+typedef unsigned long __pyx_t_11iocpsupport_DWORD;
+
+typedef unsigned long __pyx_t_11iocpsupport_ULONG_PTR;
+
+typedef int __pyx_t_11iocpsupport_BOOL;
+
+struct __pyx_t_11iocpsupport_myOVERLAPPED {
+ OVERLAPPED ov;
+ struct PyObject *obj;
+};
+
+struct __pyx_obj_11iocpsupport_CompletionPort {
+ PyObject_HEAD
+ __pyx_t_11iocpsupport_HANDLE port;
+};
+
+
+static PyTypeObject *__pyx_ptype_11iocpsupport_CompletionPort = 0;
+static long __pyx_k2;
+static unsigned long __pyx_k5;
+static unsigned long __pyx_k6;
+static unsigned long __pyx_k7;
+static struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_f_11iocpsupport_makeOV(void); /*proto*/
+static void __pyx_f_11iocpsupport_raise_error(int,PyObject *); /*proto*/
+static PyObject *__pyx_f_11iocpsupport__makesockaddr(struct sockaddr *,int); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_fillinetaddr(struct sockaddr_in *,PyObject *); /*proto*/
+static int __pyx_f_11iocpsupport_getAddrFamily(__pyx_t_11iocpsupport_SOCKET); /*proto*/
+
+
+/* Implementation of iocpsupport */
+
+static char __pyx_k4[] = "Failed to initialize Winsock function vectors";
+
+static PyObject *__pyx_n_Event;
+static PyObject *__pyx_n___init__;
+static PyObject *__pyx_n_socket;
+static PyObject *__pyx_n_ValueError;
+static PyObject *__pyx_n_have_connectex;
+
+static PyObject *__pyx_k4p;
+
+static PyObject *__pyx_n_MemoryError;
+
+static struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_f_11iocpsupport_makeOV(void) {
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_v_res;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_r;
+ void *__pyx_1;
+ int __pyx_2;
+ PyObject *__pyx_3 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":110 */
+ __pyx_1 = PyMem_Malloc((sizeof(struct __pyx_t_11iocpsupport_myOVERLAPPED))); if (__pyx_1 == NULL) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; goto __pyx_L1;}
+ __pyx_v_res = ((struct __pyx_t_11iocpsupport_myOVERLAPPED *)__pyx_1);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":111 */
+ __pyx_2 = (!(__pyx_v_res != 0));
+ if (__pyx_2) {
+ __pyx_3 = __Pyx_GetName(__pyx_b, __pyx_n_MemoryError); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 112; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 112; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":113 */
+ memset(__pyx_v_res,0,(sizeof(struct __pyx_t_11iocpsupport_myOVERLAPPED)));
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":114 */
+ __pyx_r = __pyx_v_res;
+ goto __pyx_L0;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("iocpsupport.makeOV");
+ __pyx_r = NULL;
+ __pyx_L0:;
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_WindowsError;
+
+static void __pyx_f_11iocpsupport_raise_error(int __pyx_v_err,PyObject *__pyx_v_message) {
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ Py_INCREF(__pyx_v_message);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":117 */
+ __pyx_1 = (!__pyx_v_err);
+ if (__pyx_1) {
+ __pyx_v_err = GetLastError();
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":119 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_WindowsError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 119; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(__pyx_v_err); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 119; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(2); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 119; goto __pyx_L1;}
+ Py_INCREF(__pyx_v_message);
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_v_message);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_3);
+ __pyx_3 = 0;
+ __pyx_3 = PyObject_CallObject(__pyx_2, __pyx_4); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 119; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 119; goto __pyx_L1;}
+
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("iocpsupport.raise_error");
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_message);
+}
+
+static PyObject *__pyx_n_callback;
+static PyObject *__pyx_n_owner;
+static PyObject *__pyx_n_False;
+static PyObject *__pyx_n_ignore;
+static PyObject *__pyx_n_items;
+
+static PyObject *__pyx_f_11iocpsupport_5Event___init__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyMethodDef __pyx_mdef_11iocpsupport_5Event___init__ = {"__init__", (PyCFunction)__pyx_f_11iocpsupport_5Event___init__, METH_VARARGS|METH_KEYWORDS, 0};
+static PyObject *__pyx_f_11iocpsupport_5Event___init__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_self = 0;
+ PyObject *__pyx_v_callback = 0;
+ PyObject *__pyx_v_owner = 0;
+ PyObject *__pyx_v_kw = 0;
+ PyObject *__pyx_v_k;
+ PyObject *__pyx_v_v;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ int __pyx_4;
+ static char *__pyx_argnames[] = {"self","callback","owner",0};
+ if (__Pyx_GetStarArgs(&__pyx_args, &__pyx_kwds, __pyx_argnames, 3, 0, &__pyx_v_kw, 0) < 0) return 0;
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "OOO", __pyx_argnames, &__pyx_v_self, &__pyx_v_callback, &__pyx_v_owner)) {
+ Py_XDECREF(__pyx_args);
+ Py_XDECREF(__pyx_kwds);
+ Py_XDECREF(__pyx_v_kw);
+ return 0;
+ }
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_callback);
+ Py_INCREF(__pyx_v_owner);
+ __pyx_v_k = Py_None; Py_INCREF(Py_None);
+ __pyx_v_v = Py_None; Py_INCREF(Py_None);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":123 */
+ if (PyObject_SetAttr(__pyx_v_self, __pyx_n_callback, __pyx_v_callback) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 123; goto __pyx_L1;}
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":124 */
+ if (PyObject_SetAttr(__pyx_v_self, __pyx_n_owner, __pyx_v_owner) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 124; goto __pyx_L1;}
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":125 */
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_False); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 125; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_v_self, __pyx_n_ignore, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 125; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":126 */
+ __pyx_1 = PyObject_GetAttr(__pyx_v_kw, __pyx_n_items); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 126; goto __pyx_L1;}
+ __pyx_2 = PyObject_CallObject(__pyx_1, 0); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 126; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_1 = PyObject_GetIter(__pyx_2); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 126; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ for (;;) {
+ __pyx_2 = PyIter_Next(__pyx_1);
+ if (!__pyx_2) {
+ if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 126; goto __pyx_L1;}
+ break;
+ }
+ __pyx_3 = PyObject_GetIter(__pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 126; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = __Pyx_UnpackItem(__pyx_3); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 126; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_k);
+ __pyx_v_k = __pyx_2;
+ __pyx_2 = 0;
+ __pyx_2 = __Pyx_UnpackItem(__pyx_3); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 126; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_v);
+ __pyx_v_v = __pyx_2;
+ __pyx_2 = 0;
+ if (__Pyx_EndUnpack(__pyx_3) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 126; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ __pyx_4 = PyObject_SetAttr(__pyx_v_self,__pyx_v_k,__pyx_v_v); if (__pyx_4 == -1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 127; goto __pyx_L1;}
+ }
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("iocpsupport.Event.__init__");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_XDECREF(__pyx_v_kw);
+ Py_DECREF(__pyx_v_k);
+ Py_DECREF(__pyx_v_v);
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_callback);
+ Py_DECREF(__pyx_v_owner);
+ Py_XDECREF(__pyx_args);
+ Py_XDECREF(__pyx_kwds);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_CreateIoCompletionPort;
+
+
+static int __pyx_f_11iocpsupport_14CompletionPort___init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static int __pyx_f_11iocpsupport_14CompletionPort___init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ __pyx_t_11iocpsupport_HANDLE __pyx_v_res;
+ int __pyx_r;
+ int __pyx_1;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return -1;
+ Py_INCREF(__pyx_v_self);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":133 */
+ __pyx_v_res = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":134 */
+ __pyx_1 = (!__pyx_v_res);
+ if (__pyx_1) {
+ __pyx_f_11iocpsupport_raise_error(0,__pyx_n_CreateIoCompletionPort); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 135; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":136 */
+ ((struct __pyx_obj_11iocpsupport_CompletionPort *)__pyx_v_self)->port = __pyx_v_res;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("iocpsupport.CompletionPort.__init__");
+ __pyx_r = -1;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+
+static PyObject *__pyx_f_11iocpsupport_14CompletionPort_addHandle(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_14CompletionPort_addHandle(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ long __pyx_v_handle;
+ long __pyx_v_key;
+ __pyx_t_11iocpsupport_HANDLE __pyx_v_res;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ static char *__pyx_argnames[] = {"handle","key",0};
+ __pyx_v_key = __pyx_k2;
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "l|l", __pyx_argnames, &__pyx_v_handle, &__pyx_v_key)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":140 */
+ __pyx_v_res = CreateIoCompletionPort(__pyx_v_handle,((struct __pyx_obj_11iocpsupport_CompletionPort *)__pyx_v_self)->port,__pyx_v_key,0);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":141 */
+ __pyx_1 = (!__pyx_v_res);
+ if (__pyx_1) {
+ __pyx_f_11iocpsupport_raise_error(0,__pyx_n_CreateIoCompletionPort); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 142; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("iocpsupport.CompletionPort.addHandle");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_11iocpsupport_14CompletionPort_getEvent(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_14CompletionPort_getEvent(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ long __pyx_v_timeout;
+ struct PyThreadState *__pyx_v__save;
+ unsigned long __pyx_v_bytes;
+ unsigned long __pyx_v_key;
+ unsigned long __pyx_v_rc;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_v_ov;
+ PyObject *__pyx_v_obj;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ static char *__pyx_argnames[] = {"timeout",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "l", __pyx_argnames, &__pyx_v_timeout)) return 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_obj = Py_None; Py_INCREF(Py_None);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":149 */
+ __pyx_v__save = PyEval_SaveThread();
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":150 */
+ __pyx_v_rc = GetQueuedCompletionStatus(((struct __pyx_obj_11iocpsupport_CompletionPort *)__pyx_v_self)->port,(&__pyx_v_bytes),(&__pyx_v_key),((OVERLAPPED **)(&__pyx_v_ov)),__pyx_v_timeout);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":151 */
+ PyEval_RestoreThread(__pyx_v__save);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":153 */
+ __pyx_1 = (!__pyx_v_rc);
+ if (__pyx_1) {
+ __pyx_v_rc = GetLastError();
+ goto __pyx_L2;
+ }
+ /*else*/ {
+ __pyx_v_rc = 0;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":158 */
+ Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_obj);
+ __pyx_v_obj = Py_None;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":159 */
+ __pyx_1 = (__pyx_v_ov != 0);
+ if (__pyx_1) {
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":160 */
+ __pyx_1 = (__pyx_v_ov->obj != 0);
+ if (__pyx_1) {
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":161 */
+ __pyx_2 = (PyObject *)__pyx_v_ov->obj;
+ Py_INCREF(__pyx_2);
+ Py_DECREF(__pyx_v_obj);
+ __pyx_v_obj = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":162 */
+ Py_DECREF(__pyx_v_obj);
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":163 */
+ PyMem_Free(__pyx_v_ov);
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":165 */
+ __pyx_2 = PyLong_FromUnsignedLong(__pyx_v_rc); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 165; goto __pyx_L1;}
+ __pyx_3 = PyLong_FromUnsignedLong(__pyx_v_bytes); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 165; goto __pyx_L1;}
+ __pyx_4 = PyLong_FromUnsignedLong(__pyx_v_key); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 165; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(4); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 165; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 2, __pyx_4);
+ Py_INCREF(__pyx_v_obj);
+ PyTuple_SET_ITEM(__pyx_5, 3, __pyx_v_obj);
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_r = __pyx_5;
+ __pyx_5 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("iocpsupport.CompletionPort.getEvent");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_obj);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_PostQueuedCompletionStatus;
+
+
+static PyObject *__pyx_f_11iocpsupport_14CompletionPort_postEvent(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_14CompletionPort_postEvent(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ unsigned long __pyx_v_bytes;
+ unsigned long __pyx_v_key;
+ PyObject *__pyx_v_obj = 0;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_v_ov;
+ unsigned long __pyx_v_rc;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_2;
+ static char *__pyx_argnames[] = {"bytes","key","obj",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "kkO", __pyx_argnames, &__pyx_v_bytes, &__pyx_v_key, &__pyx_v_obj)) return 0;
+ Py_INCREF(__pyx_v_self);
+ Py_INCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":171 */
+ __pyx_1 = __pyx_v_obj != Py_None;
+ if (__pyx_1) {
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":172 */
+ __pyx_2 = __pyx_f_11iocpsupport_makeOV(); if (__pyx_2 == NULL) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 172; goto __pyx_L1;}
+ __pyx_v_ov = __pyx_2;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":173 */
+ Py_INCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":174 */
+ __pyx_v_ov->obj = ((struct PyObject *)__pyx_v_obj);
+ goto __pyx_L2;
+ }
+ /*else*/ {
+ __pyx_v_ov = NULL;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":178 */
+ __pyx_v_rc = PostQueuedCompletionStatus(((struct __pyx_obj_11iocpsupport_CompletionPort *)__pyx_v_self)->port,__pyx_v_bytes,__pyx_v_key,((OVERLAPPED *)__pyx_v_ov));
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":179 */
+ __pyx_1 = (!__pyx_v_rc);
+ if (__pyx_1) {
+ __pyx_f_11iocpsupport_raise_error(0,__pyx_n_PostQueuedCompletionStatus); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 180; goto __pyx_L1;}
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("iocpsupport.CompletionPort.postEvent");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ Py_DECREF(__pyx_v_obj);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_11iocpsupport_14CompletionPort___del__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_14CompletionPort___del__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+ CloseHandle(((struct __pyx_obj_11iocpsupport_CompletionPort *)__pyx_v_self)->port);
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_11iocpsupport_makesockaddr(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_makesockaddr(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_v_buff = 0;
+ void *__pyx_v_mem_buffer;
+ int __pyx_v_size;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {"buff",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "O", __pyx_argnames, &__pyx_v_buff)) return 0;
+ Py_INCREF(__pyx_v_buff);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":189 */
+ __pyx_1 = PyObject_AsReadBuffer(__pyx_v_buff,(&__pyx_v_mem_buffer),(&__pyx_v_size)); if (__pyx_1 == (-1)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 189; goto __pyx_L1;}
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":191 */
+ __pyx_2 = __pyx_f_11iocpsupport__makesockaddr(((struct sockaddr *)__pyx_v_mem_buffer),__pyx_v_size); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 191; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("iocpsupport.makesockaddr");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_buff);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_11iocpsupport__makesockaddr(struct sockaddr *__pyx_v_addr,int __pyx_v_len) {
+ struct sockaddr_in *__pyx_v_sin;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":195 */
+ __pyx_1 = (!__pyx_v_len);
+ if (__pyx_1) {
+ Py_INCREF(Py_None);
+ __pyx_r = Py_None;
+ goto __pyx_L0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":197 */
+ __pyx_1 = (__pyx_v_addr->sa_family == AF_INET);
+ if (__pyx_1) {
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":198 */
+ __pyx_v_sin = ((struct sockaddr_in *)__pyx_v_addr);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":199 */
+ __pyx_2 = PyString_FromString(inet_ntoa(__pyx_v_sin->sin_addr)); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 199; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(ntohs(__pyx_v_sin->sin_port)); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 199; goto __pyx_L1;}
+ __pyx_4 = PyTuple_New(2); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 199; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_4, 0, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_4, 1, __pyx_3);
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_r = __pyx_4;
+ __pyx_4 = 0;
+ goto __pyx_L0;
+ goto __pyx_L3;
+ }
+ /*else*/ {
+ __pyx_2 = PyString_FromStringAndSize(__pyx_v_addr->sa_data,(sizeof(__pyx_v_addr->sa_data))); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 201; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+ }
+ __pyx_L3:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("iocpsupport._makesockaddr");
+ __pyx_r = 0;
+ __pyx_L0:;
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k11p;
+
+static char __pyx_k11[] = "invalid IP address";
+
+static PyObject *__pyx_f_11iocpsupport_fillinetaddr(struct sockaddr_in *__pyx_v_dest,PyObject *__pyx_v_addr) {
+ short __pyx_v_port;
+ unsigned long __pyx_v_res;
+ char *__pyx_v_hoststr;
+ PyObject *__pyx_v_host;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ short __pyx_3;
+ char *__pyx_4;
+ int __pyx_5;
+ Py_INCREF(__pyx_v_addr);
+ __pyx_v_host = Py_None; Py_INCREF(Py_None);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":207 */
+ __pyx_1 = PyObject_GetIter(__pyx_v_addr); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 207; goto __pyx_L1;}
+ __pyx_2 = __Pyx_UnpackItem(__pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 207; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_host);
+ __pyx_v_host = __pyx_2;
+ __pyx_2 = 0;
+ __pyx_2 = __Pyx_UnpackItem(__pyx_1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 207; goto __pyx_L1;}
+ __pyx_3 = PyInt_AsLong(__pyx_2); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 207; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_v_port = __pyx_3;
+ if (__Pyx_EndUnpack(__pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 207; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":209 */
+ __pyx_4 = PyString_AsString(__pyx_v_host); if (__pyx_4 == NULL) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 209; goto __pyx_L1;}
+ __pyx_v_hoststr = __pyx_4;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":210 */
+ __pyx_v_res = inet_addr(__pyx_v_hoststr);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":211 */
+ __pyx_5 = (__pyx_v_res == INADDR_ANY);
+ if (__pyx_5) {
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 212; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_2, __pyx_k11p, 0);
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 212; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":213 */
+ __pyx_v_dest->sin_addr.s_addr = __pyx_v_res;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":215 */
+ __pyx_v_dest->sin_port = htons(__pyx_v_port);
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("iocpsupport.fillinetaddr");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_host);
+ Py_DECREF(__pyx_v_addr);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_11iocpsupport_AllocateReadBuffer(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_AllocateReadBuffer(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ int __pyx_v_size;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ static char *__pyx_argnames[] = {"size",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "i", __pyx_argnames, &__pyx_v_size)) return 0;
+ __pyx_1 = PyBuffer_New(__pyx_v_size); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 218; goto __pyx_L1;}
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ __Pyx_AddTraceback("iocpsupport.AllocateReadBuffer");
+ __pyx_r = 0;
+ __pyx_L0:;
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_getsockopt;
+
+
+static PyObject *__pyx_f_11iocpsupport_maxAddrLen(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_maxAddrLen(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ long __pyx_v_s;
+ WSAPROTOCOL_INFO __pyx_v_wsa_pi;
+ int __pyx_v_size;
+ int __pyx_v_rc;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ static char *__pyx_argnames[] = {"s",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "l", __pyx_argnames, &__pyx_v_s)) return 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":224 */
+ __pyx_v_size = (sizeof(__pyx_v_wsa_pi));
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":225 */
+ __pyx_v_rc = getsockopt(__pyx_v_s,SOL_SOCKET,SO_PROTOCOL_INFO,((char *)(&__pyx_v_wsa_pi)),(&__pyx_v_size));
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":226 */
+ __pyx_1 = (__pyx_v_rc == SOCKET_ERROR);
+ if (__pyx_1) {
+ __pyx_f_11iocpsupport_raise_error(WSAGetLastError(),__pyx_n_getsockopt); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 227; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":228 */
+ __pyx_2 = PyInt_FromLong(__pyx_v_wsa_pi.iMaxSockAddr); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 228; goto __pyx_L1;}
+ __pyx_r = __pyx_2;
+ __pyx_2 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ __Pyx_AddTraceback("iocpsupport.maxAddrLen");
+ __pyx_r = 0;
+ __pyx_L0:;
+ return __pyx_r;
+}
+
+
+static int __pyx_f_11iocpsupport_getAddrFamily(__pyx_t_11iocpsupport_SOCKET __pyx_v_s) {
+ WSAPROTOCOL_INFO __pyx_v_wsa_pi;
+ int __pyx_v_size;
+ int __pyx_v_rc;
+ int __pyx_r;
+ int __pyx_1;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":234 */
+ __pyx_v_size = (sizeof(__pyx_v_wsa_pi));
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":235 */
+ __pyx_v_rc = getsockopt(__pyx_v_s,SOL_SOCKET,SO_PROTOCOL_INFO,((char *)(&__pyx_v_wsa_pi)),(&__pyx_v_size));
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":236 */
+ __pyx_1 = (__pyx_v_rc == SOCKET_ERROR);
+ if (__pyx_1) {
+ __pyx_f_11iocpsupport_raise_error(WSAGetLastError(),__pyx_n_getsockopt); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 237; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":238 */
+ __pyx_r = __pyx_v_wsa_pi.iAddressFamily;
+ goto __pyx_L0;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("iocpsupport.getAddrFamily");
+ __pyx_L0:;
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_11iocpsupport_accept(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_accept(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ long __pyx_v_listening;
+ long __pyx_v_accepting;
+ PyObject *__pyx_v_buff = 0;
+ PyObject *__pyx_v_obj = 0;
+ unsigned long __pyx_v_bytes;
+ int __pyx_v_size;
+ int __pyx_v_rc;
+ void *__pyx_v_mem_buffer;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_v_ov;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_2;
+ PyObject *__pyx_3 = 0;
+ static char *__pyx_argnames[] = {"listening","accepting","buff","obj",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "llOO", __pyx_argnames, &__pyx_v_listening, &__pyx_v_accepting, &__pyx_v_buff, &__pyx_v_obj)) return 0;
+ Py_INCREF(__pyx_v_buff);
+ Py_INCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":11 */
+ __pyx_1 = PyObject_AsWriteBuffer(__pyx_v_buff,(&__pyx_v_mem_buffer),(&__pyx_v_size)); if (__pyx_1 == (-1)) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 11; goto __pyx_L1;}
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":13 */
+ __pyx_2 = __pyx_f_11iocpsupport_makeOV(); if (__pyx_2 == NULL) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 13; goto __pyx_L1;}
+ __pyx_v_ov = __pyx_2;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":14 */
+ __pyx_1 = __pyx_v_obj != Py_None;
+ if (__pyx_1) {
+ __pyx_v_ov->obj = ((struct PyObject *)__pyx_v_obj);
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":17 */
+ __pyx_v_rc = lpAcceptEx(__pyx_v_listening,__pyx_v_accepting,__pyx_v_mem_buffer,0,(__pyx_v_size / 2),(__pyx_v_size / 2),(&__pyx_v_bytes),((OVERLAPPED *)__pyx_v_ov));
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":19 */
+ __pyx_1 = (!__pyx_v_rc);
+ if (__pyx_1) {
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":20 */
+ __pyx_v_rc = WSAGetLastError();
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":21 */
+ __pyx_1 = (__pyx_v_rc != ERROR_IO_PENDING);
+ if (__pyx_1) {
+ __pyx_3 = PyInt_FromLong(__pyx_v_rc); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 22; goto __pyx_L1;}
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":25 */
+ Py_XINCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":26 */
+ __pyx_3 = PyInt_FromLong(__pyx_v_rc); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 26; goto __pyx_L1;}
+ __pyx_r = __pyx_3;
+ __pyx_3 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("iocpsupport.accept");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_buff);
+ Py_DECREF(__pyx_v_obj);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_11iocpsupport_get_accept_addrs(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_get_accept_addrs(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ long __pyx_v_s;
+ PyObject *__pyx_v_buff = 0;
+ int __pyx_v_size;
+ int __pyx_v_locallen;
+ int __pyx_v_remotelen;
+ void *__pyx_v_mem_buffer;
+ struct sockaddr *__pyx_v_localaddr;
+ struct sockaddr *__pyx_v_remoteaddr;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ static char *__pyx_argnames[] = {"s","buff",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "lO", __pyx_argnames, &__pyx_v_s, &__pyx_v_buff)) return 0;
+ Py_INCREF(__pyx_v_buff);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":34 */
+ __pyx_1 = PyObject_AsReadBuffer(__pyx_v_buff,(&__pyx_v_mem_buffer),(&__pyx_v_size)); if (__pyx_1 == (-1)) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 34; goto __pyx_L1;}
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":36 */
+ lpGetAcceptExSockaddrs(__pyx_v_mem_buffer,0,(__pyx_v_size / 2),(__pyx_v_size / 2),(&__pyx_v_localaddr),(&__pyx_v_locallen),(&__pyx_v_remoteaddr),(&__pyx_v_remotelen));
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\acceptex.pxi":37 */
+ __pyx_2 = PyInt_FromLong(__pyx_v_remoteaddr->sa_family); if (!__pyx_2) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 37; goto __pyx_L1;}
+ __pyx_3 = __pyx_f_11iocpsupport__makesockaddr(__pyx_v_localaddr,__pyx_v_locallen); if (!__pyx_3) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 37; goto __pyx_L1;}
+ __pyx_4 = __pyx_f_11iocpsupport__makesockaddr(__pyx_v_remoteaddr,__pyx_v_remotelen); if (!__pyx_4) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 37; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(3); if (!__pyx_5) {__pyx_filename = __pyx_f[1]; __pyx_lineno = 37; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_2);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 2, __pyx_4);
+ __pyx_2 = 0;
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_r = __pyx_5;
+ __pyx_5 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("iocpsupport.get_accept_addrs");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_buff);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_k14p;
+static PyObject *__pyx_k15p;
+
+static char __pyx_k14[] = "ConnectEx is not available on this system";
+static char __pyx_k15[] = "unsupported address family";
+
+static PyObject *__pyx_f_11iocpsupport_connect(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_connect(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ long __pyx_v_s;
+ PyObject *__pyx_v_addr = 0;
+ PyObject *__pyx_v_obj = 0;
+ int __pyx_v_family;
+ int __pyx_v_rc;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_v_ov;
+ struct sockaddr __pyx_v_name;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ int __pyx_2;
+ int __pyx_3;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_4;
+ static char *__pyx_argnames[] = {"s","addr","obj",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "lOO", __pyx_argnames, &__pyx_v_s, &__pyx_v_addr, &__pyx_v_obj)) return 0;
+ Py_INCREF(__pyx_v_addr);
+ Py_INCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":10 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_have_connectex); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 10; goto __pyx_L1;}
+ __pyx_2 = PyObject_IsTrue(__pyx_1); if (__pyx_2 < 0) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 10; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ __pyx_3 = (!__pyx_2);
+ if (__pyx_3) {
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 11; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_1, __pyx_k14p, 0);
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ {__pyx_filename = __pyx_f[2]; __pyx_lineno = 11; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":13 */
+ __pyx_2 = __pyx_f_11iocpsupport_getAddrFamily(__pyx_v_s); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 13; goto __pyx_L1;}
+ __pyx_v_family = __pyx_2;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":14 */
+ __pyx_3 = (__pyx_v_family == AF_INET);
+ if (__pyx_3) {
+ __pyx_1 = __pyx_f_11iocpsupport_fillinetaddr(((struct sockaddr_in *)(&__pyx_v_name)),__pyx_v_addr); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 15; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ goto __pyx_L3;
+ }
+ /*else*/ {
+ __pyx_1 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 17; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_1, __pyx_k15p, 0);
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ {__pyx_filename = __pyx_f[2]; __pyx_lineno = 17; goto __pyx_L1;}
+ }
+ __pyx_L3:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":18 */
+ __pyx_v_name.sa_family = __pyx_v_family;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":20 */
+ __pyx_4 = __pyx_f_11iocpsupport_makeOV(); if (__pyx_4 == NULL) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 20; goto __pyx_L1;}
+ __pyx_v_ov = __pyx_4;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":21 */
+ __pyx_2 = __pyx_v_obj != Py_None;
+ if (__pyx_2) {
+ __pyx_v_ov->obj = ((struct PyObject *)__pyx_v_obj);
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":24 */
+ __pyx_v_rc = lpConnectEx(__pyx_v_s,(&__pyx_v_name),(sizeof(__pyx_v_name)),NULL,0,NULL,((OVERLAPPED *)__pyx_v_ov));
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":26 */
+ __pyx_3 = (!__pyx_v_rc);
+ if (__pyx_3) {
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":27 */
+ __pyx_v_rc = WSAGetLastError();
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":28 */
+ __pyx_2 = (__pyx_v_rc != ERROR_IO_PENDING);
+ if (__pyx_2) {
+ __pyx_1 = PyInt_FromLong(__pyx_v_rc); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 29; goto __pyx_L1;}
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+ goto __pyx_L6;
+ }
+ __pyx_L6:;
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":32 */
+ Py_XINCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\connectex.pxi":33 */
+ __pyx_1 = PyInt_FromLong(__pyx_v_rc); if (!__pyx_1) {__pyx_filename = __pyx_f[2]; __pyx_lineno = 33; goto __pyx_L1;}
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ __Pyx_AddTraceback("iocpsupport.connect");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_addr);
+ Py_DECREF(__pyx_v_obj);
+ return __pyx_r;
+}
+
+static char __pyx_k16[] = "second argument needs to be a list";
+
+static PyObject *__pyx_f_11iocpsupport_recv(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_recv(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ long __pyx_v_s;
+ PyObject *__pyx_v_bufflist = 0;
+ PyObject *__pyx_v_obj = 0;
+ unsigned long __pyx_v_flags;
+ int __pyx_v_rc;
+ int __pyx_v_buffcount;
+ int __pyx_v_i;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_v_ov;
+ WSABUF *__pyx_v_ws_buf;
+ unsigned long __pyx_v_bytes;
+ struct PyObject **__pyx_v_buffers;
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ void *__pyx_2;
+ int __pyx_3;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_4;
+ PyObject *__pyx_5 = 0;
+ PyObject *__pyx_6 = 0;
+ static char *__pyx_argnames[] = {"s","bufflist","obj","flags",0};
+ __pyx_v_flags = __pyx_k5;
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "lOO|k", __pyx_argnames, &__pyx_v_s, &__pyx_v_bufflist, &__pyx_v_obj, &__pyx_v_flags)) return 0;
+ Py_INCREF(__pyx_v_bufflist);
+ Py_INCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":12 */
+ __pyx_1 = PySequence_Fast(__pyx_v_bufflist,__pyx_k16); if (!__pyx_1) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 12; goto __pyx_L1;}
+ Py_DECREF(__pyx_v_bufflist);
+ __pyx_v_bufflist = __pyx_1;
+ __pyx_1 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":13 */
+ __pyx_v_buffcount = PySequence_Fast_GET_SIZE(__pyx_v_bufflist);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":14 */
+ __pyx_v_buffers = PySequence_Fast_ITEMS(__pyx_v_bufflist);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":16 */
+ __pyx_2 = PyMem_Malloc((__pyx_v_buffcount * (sizeof(WSABUF)))); if (__pyx_2 == NULL) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 16; goto __pyx_L1;}
+ __pyx_v_ws_buf = ((WSABUF *)__pyx_2);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":18 */
+ /*try:*/ {
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":19 */
+ for (__pyx_v_i = 0; __pyx_v_i < __pyx_v_buffcount; ++__pyx_v_i) {
+ __pyx_1 = (PyObject *)(__pyx_v_buffers[__pyx_v_i]);
+ Py_INCREF(__pyx_1);
+ __pyx_3 = PyObject_AsWriteBuffer(__pyx_1,((void **)(&(__pyx_v_ws_buf[__pyx_v_i]).buf)),((int *)(&(__pyx_v_ws_buf[__pyx_v_i]).len))); if (__pyx_3 == (-1)) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 20; goto __pyx_L3;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ }
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":22 */
+ __pyx_4 = __pyx_f_11iocpsupport_makeOV(); if (__pyx_4 == NULL) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 22; goto __pyx_L3;}
+ __pyx_v_ov = __pyx_4;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":23 */
+ __pyx_3 = __pyx_v_obj != Py_None;
+ if (__pyx_3) {
+ __pyx_v_ov->obj = ((struct PyObject *)__pyx_v_obj);
+ goto __pyx_L7;
+ }
+ __pyx_L7:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":26 */
+ __pyx_v_rc = WSARecv(__pyx_v_s,__pyx_v_ws_buf,__pyx_v_buffcount,(&__pyx_v_bytes),(&__pyx_v_flags),((OVERLAPPED *)__pyx_v_ov),NULL);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":28 */
+ __pyx_3 = (__pyx_v_rc == SOCKET_ERROR);
+ if (__pyx_3) {
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":29 */
+ __pyx_v_rc = WSAGetLastError();
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":30 */
+ __pyx_3 = (__pyx_v_rc != ERROR_IO_PENDING);
+ if (__pyx_3) {
+ __pyx_1 = PyInt_FromLong(__pyx_v_rc); if (!__pyx_1) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 31; goto __pyx_L3;}
+ __pyx_5 = PyInt_FromLong(0); if (!__pyx_5) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 31; goto __pyx_L3;}
+ __pyx_6 = PyTuple_New(2); if (!__pyx_6) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 31; goto __pyx_L3;}
+ PyTuple_SET_ITEM(__pyx_6, 0, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_6, 1, __pyx_5);
+ __pyx_1 = 0;
+ __pyx_5 = 0;
+ __pyx_r = __pyx_6;
+ __pyx_6 = 0;
+ goto __pyx_L2;
+ goto __pyx_L9;
+ }
+ __pyx_L9:;
+ goto __pyx_L8;
+ }
+ __pyx_L8:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":33 */
+ Py_XINCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":34 */
+ __pyx_1 = PyInt_FromLong(__pyx_v_rc); if (!__pyx_1) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 34; goto __pyx_L3;}
+ __pyx_5 = PyLong_FromUnsignedLong(__pyx_v_bytes); if (!__pyx_5) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 34; goto __pyx_L3;}
+ __pyx_6 = PyTuple_New(2); if (!__pyx_6) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 34; goto __pyx_L3;}
+ PyTuple_SET_ITEM(__pyx_6, 0, __pyx_1);
+ PyTuple_SET_ITEM(__pyx_6, 1, __pyx_5);
+ __pyx_1 = 0;
+ __pyx_5 = 0;
+ __pyx_r = __pyx_6;
+ __pyx_6 = 0;
+ goto __pyx_L2;
+ }
+ /*finally:*/ {
+ int __pyx_why;
+ PyObject *__pyx_exc_type, *__pyx_exc_value, *__pyx_exc_tb;
+ int __pyx_exc_lineno;
+ __pyx_why = 0; goto __pyx_L4;
+ __pyx_L2: __pyx_why = 3; goto __pyx_L4;
+ __pyx_L3: {
+ __pyx_why = 4;
+ Py_XDECREF(__pyx_1); __pyx_1 = 0;
+ Py_XDECREF(__pyx_5); __pyx_5 = 0;
+ Py_XDECREF(__pyx_6); __pyx_6 = 0;
+ PyErr_Fetch(&__pyx_exc_type, &__pyx_exc_value, &__pyx_exc_tb);
+ __pyx_exc_lineno = __pyx_lineno;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+ PyMem_Free(__pyx_v_ws_buf);
+ switch (__pyx_why) {
+ case 3: goto __pyx_L0;
+ case 4: {
+ PyErr_Restore(__pyx_exc_type, __pyx_exc_value, __pyx_exc_tb);
+ __pyx_lineno = __pyx_exc_lineno;
+ __pyx_exc_type = 0;
+ __pyx_exc_value = 0;
+ __pyx_exc_tb = 0;
+ goto __pyx_L1;
+ }
+ }
+ }
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_5);
+ Py_XDECREF(__pyx_6);
+ __Pyx_AddTraceback("iocpsupport.recv");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_bufflist);
+ Py_DECREF(__pyx_v_obj);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_11iocpsupport_recvfrom(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_recvfrom(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ long __pyx_v_s;
+ PyObject *__pyx_v_buff = 0;
+ PyObject *__pyx_v_addr_buff = 0;
+ PyObject *__pyx_v_obj = 0;
+ unsigned long __pyx_v_flags;
+ int __pyx_v_rc;
+ int __pyx_v_fromlen;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_v_ov;
+ WSABUF __pyx_v_ws_buf;
+ unsigned long __pyx_v_bytes;
+ struct sockaddr *__pyx_v_fromaddr;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_2;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ static char *__pyx_argnames[] = {"s","buff","addr_buff","obj","flags",0};
+ __pyx_v_flags = __pyx_k6;
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "lOOO|k", __pyx_argnames, &__pyx_v_s, &__pyx_v_buff, &__pyx_v_addr_buff, &__pyx_v_obj, &__pyx_v_flags)) return 0;
+ Py_INCREF(__pyx_v_buff);
+ Py_INCREF(__pyx_v_addr_buff);
+ Py_INCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":45 */
+ __pyx_1 = PyObject_AsWriteBuffer(__pyx_v_buff,((void **)(&__pyx_v_ws_buf.buf)),((int *)(&__pyx_v_ws_buf.len))); if (__pyx_1 == (-1)) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 45; goto __pyx_L1;}
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":46 */
+ __pyx_1 = PyObject_AsWriteBuffer(__pyx_v_addr_buff,((void **)(&__pyx_v_fromaddr)),(&__pyx_v_fromlen)); if (__pyx_1 == (-1)) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 46; goto __pyx_L1;}
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":48 */
+ __pyx_2 = __pyx_f_11iocpsupport_makeOV(); if (__pyx_2 == NULL) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 48; goto __pyx_L1;}
+ __pyx_v_ov = __pyx_2;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":49 */
+ __pyx_1 = __pyx_v_obj != Py_None;
+ if (__pyx_1) {
+ __pyx_v_ov->obj = ((struct PyObject *)__pyx_v_obj);
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":52 */
+ __pyx_v_rc = WSARecvFrom(__pyx_v_s,(&__pyx_v_ws_buf),1,(&__pyx_v_bytes),(&__pyx_v_flags),__pyx_v_fromaddr,(&__pyx_v_fromlen),((OVERLAPPED *)__pyx_v_ov),NULL);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":54 */
+ __pyx_1 = (__pyx_v_rc == SOCKET_ERROR);
+ if (__pyx_1) {
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":55 */
+ __pyx_v_rc = WSAGetLastError();
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":56 */
+ __pyx_1 = (__pyx_v_rc != ERROR_IO_PENDING);
+ if (__pyx_1) {
+ __pyx_3 = PyInt_FromLong(__pyx_v_rc); if (!__pyx_3) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 57; goto __pyx_L1;}
+ __pyx_4 = PyInt_FromLong(0); if (!__pyx_4) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 57; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(2); if (!__pyx_5) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 57; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_4);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_r = __pyx_5;
+ __pyx_5 = 0;
+ goto __pyx_L0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":59 */
+ Py_XINCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":60 */
+ __pyx_3 = PyInt_FromLong(__pyx_v_rc); if (!__pyx_3) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 60; goto __pyx_L1;}
+ __pyx_4 = PyLong_FromUnsignedLong(__pyx_v_bytes); if (!__pyx_4) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 60; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(2); if (!__pyx_5) {__pyx_filename = __pyx_f[3]; __pyx_lineno = 60; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_4);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_r = __pyx_5;
+ __pyx_5 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("iocpsupport.recvfrom");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_buff);
+ Py_DECREF(__pyx_v_addr_buff);
+ Py_DECREF(__pyx_v_obj);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_11iocpsupport_send(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static PyObject *__pyx_f_11iocpsupport_send(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ long __pyx_v_s;
+ PyObject *__pyx_v_buff = 0;
+ PyObject *__pyx_v_obj = 0;
+ unsigned long __pyx_v_flags;
+ int __pyx_v_rc;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_v_ov;
+ WSABUF __pyx_v_ws_buf;
+ unsigned long __pyx_v_bytes;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ struct __pyx_t_11iocpsupport_myOVERLAPPED *__pyx_2;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ static char *__pyx_argnames[] = {"s","buff","obj","flags",0};
+ __pyx_v_flags = __pyx_k7;
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "lOO|k", __pyx_argnames, &__pyx_v_s, &__pyx_v_buff, &__pyx_v_obj, &__pyx_v_flags)) return 0;
+ Py_INCREF(__pyx_v_buff);
+ Py_INCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsasend.pxi":11 */
+ __pyx_1 = PyObject_AsReadBuffer(__pyx_v_buff,((void **)(&__pyx_v_ws_buf.buf)),((int *)(&__pyx_v_ws_buf.len))); if (__pyx_1 == (-1)) {__pyx_filename = __pyx_f[4]; __pyx_lineno = 11; goto __pyx_L1;}
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsasend.pxi":13 */
+ __pyx_2 = __pyx_f_11iocpsupport_makeOV(); if (__pyx_2 == NULL) {__pyx_filename = __pyx_f[4]; __pyx_lineno = 13; goto __pyx_L1;}
+ __pyx_v_ov = __pyx_2;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsasend.pxi":14 */
+ __pyx_1 = __pyx_v_obj != Py_None;
+ if (__pyx_1) {
+ __pyx_v_ov->obj = ((struct PyObject *)__pyx_v_obj);
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsasend.pxi":17 */
+ __pyx_v_rc = WSASend(__pyx_v_s,(&__pyx_v_ws_buf),1,(&__pyx_v_bytes),__pyx_v_flags,((OVERLAPPED *)__pyx_v_ov),NULL);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsasend.pxi":19 */
+ __pyx_1 = (__pyx_v_rc == SOCKET_ERROR);
+ if (__pyx_1) {
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsasend.pxi":20 */
+ __pyx_v_rc = WSAGetLastError();
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsasend.pxi":21 */
+ __pyx_1 = (__pyx_v_rc != ERROR_IO_PENDING);
+ if (__pyx_1) {
+ __pyx_3 = PyInt_FromLong(__pyx_v_rc); if (!__pyx_3) {__pyx_filename = __pyx_f[4]; __pyx_lineno = 22; goto __pyx_L1;}
+ __pyx_4 = PyLong_FromUnsignedLong(__pyx_v_bytes); if (!__pyx_4) {__pyx_filename = __pyx_f[4]; __pyx_lineno = 22; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(2); if (!__pyx_5) {__pyx_filename = __pyx_f[4]; __pyx_lineno = 22; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_4);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_r = __pyx_5;
+ __pyx_5 = 0;
+ goto __pyx_L0;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsasend.pxi":24 */
+ Py_XINCREF(__pyx_v_obj);
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsasend.pxi":25 */
+ __pyx_3 = PyInt_FromLong(__pyx_v_rc); if (!__pyx_3) {__pyx_filename = __pyx_f[4]; __pyx_lineno = 25; goto __pyx_L1;}
+ __pyx_4 = PyLong_FromUnsignedLong(__pyx_v_bytes); if (!__pyx_4) {__pyx_filename = __pyx_f[4]; __pyx_lineno = 25; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(2); if (!__pyx_5) {__pyx_filename = __pyx_f[4]; __pyx_lineno = 25; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_4);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_r = __pyx_5;
+ __pyx_5 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("iocpsupport.send");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_buff);
+ Py_DECREF(__pyx_v_obj);
+ return __pyx_r;
+}
+
+static __Pyx_InternTabEntry __pyx_intern_tab[] = {
+ {&__pyx_n_CreateIoCompletionPort, "CreateIoCompletionPort"},
+ {&__pyx_n_Event, "Event"},
+ {&__pyx_n_False, "False"},
+ {&__pyx_n_MemoryError, "MemoryError"},
+ {&__pyx_n_PostQueuedCompletionStatus, "PostQueuedCompletionStatus"},
+ {&__pyx_n_ValueError, "ValueError"},
+ {&__pyx_n_WindowsError, "WindowsError"},
+ {&__pyx_n___init__, "__init__"},
+ {&__pyx_n_callback, "callback"},
+ {&__pyx_n_getsockopt, "getsockopt"},
+ {&__pyx_n_have_connectex, "have_connectex"},
+ {&__pyx_n_ignore, "ignore"},
+ {&__pyx_n_items, "items"},
+ {&__pyx_n_owner, "owner"},
+ {&__pyx_n_socket, "socket"},
+ {0, 0}
+};
+
+static __Pyx_StringTabEntry __pyx_string_tab[] = {
+ {&__pyx_k4p, __pyx_k4, sizeof(__pyx_k4)},
+ {&__pyx_k11p, __pyx_k11, sizeof(__pyx_k11)},
+ {&__pyx_k14p, __pyx_k14, sizeof(__pyx_k14)},
+ {&__pyx_k15p, __pyx_k15, sizeof(__pyx_k15)},
+ {0, 0, 0}
+};
+
+static PyObject *__pyx_tp_new_11iocpsupport_CompletionPort(PyTypeObject *t, PyObject *a, PyObject *k) {
+ PyObject *o = (*t->tp_alloc)(t, 0);
+ if (!o) return 0;
+ return o;
+}
+
+static void __pyx_tp_dealloc_11iocpsupport_CompletionPort(PyObject *o) {
+ (*o->ob_type->tp_free)(o);
+}
+
+static int __pyx_tp_traverse_11iocpsupport_CompletionPort(PyObject *o, visitproc v, void *a) {
+ return 0;
+}
+
+static int __pyx_tp_clear_11iocpsupport_CompletionPort(PyObject *o) {
+ return 0;
+}
+
+static struct PyMethodDef __pyx_methods_11iocpsupport_CompletionPort[] = {
+ {"addHandle", (PyCFunction)__pyx_f_11iocpsupport_14CompletionPort_addHandle, METH_VARARGS|METH_KEYWORDS, 0},
+ {"getEvent", (PyCFunction)__pyx_f_11iocpsupport_14CompletionPort_getEvent, METH_VARARGS|METH_KEYWORDS, 0},
+ {"postEvent", (PyCFunction)__pyx_f_11iocpsupport_14CompletionPort_postEvent, METH_VARARGS|METH_KEYWORDS, 0},
+ {"__del__", (PyCFunction)__pyx_f_11iocpsupport_14CompletionPort___del__, METH_VARARGS|METH_KEYWORDS, 0},
+ {0, 0, 0, 0}
+};
+
+static PyNumberMethods __pyx_tp_as_number_CompletionPort = {
+ 0, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ 0, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+ 0, /*nb_oct*/
+ 0, /*nb_hex*/
+ 0, /*nb_inplace_add*/
+ 0, /*nb_inplace_subtract*/
+ 0, /*nb_inplace_multiply*/
+ 0, /*nb_inplace_divide*/
+ 0, /*nb_inplace_remainder*/
+ 0, /*nb_inplace_power*/
+ 0, /*nb_inplace_lshift*/
+ 0, /*nb_inplace_rshift*/
+ 0, /*nb_inplace_and*/
+ 0, /*nb_inplace_xor*/
+ 0, /*nb_inplace_or*/
+ 0, /*nb_floor_divide*/
+ 0, /*nb_true_divide*/
+ 0, /*nb_inplace_floor_divide*/
+ 0, /*nb_inplace_true_divide*/
+ #if Py_TPFLAGS_DEFAULT & Py_TPFLAGS_HAVE_INDEX
+ 0, /*nb_index*/
+ #endif
+};
+
+static PySequenceMethods __pyx_tp_as_sequence_CompletionPort = {
+ 0, /*sq_length*/
+ 0, /*sq_concat*/
+ 0, /*sq_repeat*/
+ 0, /*sq_item*/
+ 0, /*sq_slice*/
+ 0, /*sq_ass_item*/
+ 0, /*sq_ass_slice*/
+ 0, /*sq_contains*/
+ 0, /*sq_inplace_concat*/
+ 0, /*sq_inplace_repeat*/
+};
+
+static PyMappingMethods __pyx_tp_as_mapping_CompletionPort = {
+ 0, /*mp_length*/
+ 0, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
+};
+
+static PyBufferProcs __pyx_tp_as_buffer_CompletionPort = {
+ 0, /*bf_getreadbuffer*/
+ 0, /*bf_getwritebuffer*/
+ 0, /*bf_getsegcount*/
+ 0, /*bf_getcharbuffer*/
+};
+
+PyTypeObject __pyx_type_11iocpsupport_CompletionPort = {
+ PyObject_HEAD_INIT(0)
+ 0, /*ob_size*/
+ "iocpsupport.CompletionPort", /*tp_name*/
+ sizeof(struct __pyx_obj_11iocpsupport_CompletionPort), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ __pyx_tp_dealloc_11iocpsupport_CompletionPort, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ &__pyx_tp_as_number_CompletionPort, /*tp_as_number*/
+ &__pyx_tp_as_sequence_CompletionPort, /*tp_as_sequence*/
+ &__pyx_tp_as_mapping_CompletionPort, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ &__pyx_tp_as_buffer_CompletionPort, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+ 0, /*tp_doc*/
+ __pyx_tp_traverse_11iocpsupport_CompletionPort, /*tp_traverse*/
+ __pyx_tp_clear_11iocpsupport_CompletionPort, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ __pyx_methods_11iocpsupport_CompletionPort, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ __pyx_f_11iocpsupport_14CompletionPort___init__, /*tp_init*/
+ 0, /*tp_alloc*/
+ __pyx_tp_new_11iocpsupport_CompletionPort, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+ 0, /*tp_bases*/
+ 0, /*tp_mro*/
+ 0, /*tp_cache*/
+ 0, /*tp_subclasses*/
+ 0, /*tp_weaklist*/
+};
+
+static struct PyMethodDef __pyx_methods[] = {
+ {"makesockaddr", (PyCFunction)__pyx_f_11iocpsupport_makesockaddr, METH_VARARGS|METH_KEYWORDS, 0},
+ {"AllocateReadBuffer", (PyCFunction)__pyx_f_11iocpsupport_AllocateReadBuffer, METH_VARARGS|METH_KEYWORDS, 0},
+ {"maxAddrLen", (PyCFunction)__pyx_f_11iocpsupport_maxAddrLen, METH_VARARGS|METH_KEYWORDS, 0},
+ {"accept", (PyCFunction)__pyx_f_11iocpsupport_accept, METH_VARARGS|METH_KEYWORDS, 0},
+ {"get_accept_addrs", (PyCFunction)__pyx_f_11iocpsupport_get_accept_addrs, METH_VARARGS|METH_KEYWORDS, 0},
+ {"connect", (PyCFunction)__pyx_f_11iocpsupport_connect, METH_VARARGS|METH_KEYWORDS, 0},
+ {"recv", (PyCFunction)__pyx_f_11iocpsupport_recv, METH_VARARGS|METH_KEYWORDS, 0},
+ {"recvfrom", (PyCFunction)__pyx_f_11iocpsupport_recvfrom, METH_VARARGS|METH_KEYWORDS, 0},
+ {"send", (PyCFunction)__pyx_f_11iocpsupport_send, METH_VARARGS|METH_KEYWORDS, 0},
+ {0, 0, 0, 0}
+};
+
+static void __pyx_init_filenames(void); /*proto*/
+
+PyMODINIT_FUNC initiocpsupport(void); /*proto*/
+PyMODINIT_FUNC initiocpsupport(void) {
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ int __pyx_5;
+ __pyx_init_filenames();
+ __pyx_m = Py_InitModule4("iocpsupport", __pyx_methods, 0, 0, PYTHON_API_VERSION);
+ if (!__pyx_m) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 5; goto __pyx_L1;};
+ Py_INCREF(__pyx_m);
+ __pyx_b = PyImport_AddModule("__builtin__");
+ if (!__pyx_b) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 5; goto __pyx_L1;};
+ if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 5; goto __pyx_L1;};
+ if (__Pyx_InternStrings(__pyx_intern_tab) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 5; goto __pyx_L1;};
+ if (__Pyx_InitStrings(__pyx_string_tab) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 5; goto __pyx_L1;};
+ if (PyType_Ready(&__pyx_type_11iocpsupport_CompletionPort) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 129; goto __pyx_L1;}
+ if (PyObject_SetAttrString(__pyx_m, "CompletionPort", (PyObject *)&__pyx_type_11iocpsupport_CompletionPort) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 129; goto __pyx_L1;}
+ __pyx_ptype_11iocpsupport_CompletionPort = &__pyx_type_11iocpsupport_CompletionPort;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":121 */
+ __pyx_1 = PyDict_New(); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 121; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(0); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 121; goto __pyx_L1;}
+ __pyx_3 = __Pyx_CreateClass(__pyx_2, __pyx_1, __pyx_n_Event, "iocpsupport"); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 121; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __pyx_2 = PyCFunction_New(&__pyx_mdef_11iocpsupport_5Event___init__, 0); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 122; goto __pyx_L1;}
+ __pyx_4 = PyMethod_New(__pyx_2, 0, __pyx_3); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 122; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ if (PyObject_SetAttr(__pyx_3, __pyx_n___init__, __pyx_4) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 122; goto __pyx_L1;}
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_Event, __pyx_3) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 121; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":138 */
+ __pyx_k2 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":240 */
+ __pyx_2 = __Pyx_Import(__pyx_n_socket, 0); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 240; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_socket, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 240; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":241 */
+ __pyx_5 = (!initWinsockPointers());
+ if (__pyx_5) {
+ __pyx_4 = __Pyx_GetName(__pyx_b, __pyx_n_ValueError); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 242; goto __pyx_L1;}
+ __Pyx_Raise(__pyx_4, __pyx_k4p, 0);
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 242; goto __pyx_L1;}
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport/iocpsupport.pyx":244 */
+ __pyx_3 = PyInt_FromLong((lpConnectEx != NULL)); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 244; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_have_connectex, __pyx_3) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 244; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":5 */
+ __pyx_k5 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsarecv.pxi":38 */
+ __pyx_k6 = 0;
+
+ /* "X:\projects\Twisted\trunk\twisted\internet\iocpreactor\iocpsupport\wsasend.pxi":5 */
+ __pyx_k7 = 0;
+ return;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ __Pyx_AddTraceback("iocpsupport");
+}
+
+static char *__pyx_filenames[] = {
+ "iocpsupport.pyx",
+ "acceptex.pxi",
+ "connectex.pxi",
+ "wsarecv.pxi",
+ "wsasend.pxi",
+};
+
+/* Runtime support code */
+
+static void __pyx_init_filenames(void) {
+ __pyx_f = __pyx_filenames;
+}
+
+static PyObject *__Pyx_CreateClass(
+ PyObject *bases, PyObject *dict, PyObject *name, char *modname)
+{
+ PyObject *py_modname;
+ PyObject *result = 0;
+
+ py_modname = PyString_FromString(modname);
+ if (!py_modname)
+ goto bad;
+ if (PyDict_SetItemString(dict, "__module__", py_modname) < 0)
+ goto bad;
+ result = PyClass_New(bases, dict, name);
+bad:
+ Py_XDECREF(py_modname);
+ return result;
+}
+
+static int __Pyx_GetStarArgs(
+ PyObject **args,
+ PyObject **kwds,
+ char *kwd_list[],
+ Py_ssize_t nargs,
+ PyObject **args2,
+ PyObject **kwds2,
+ char rqd_kwds[])
+{
+ PyObject *x = 0, *args1 = 0, *kwds1 = 0;
+ int i;
+ char **p;
+
+ if (args2)
+ *args2 = 0;
+ if (kwds2)
+ *kwds2 = 0;
+
+ if (args2) {
+ args1 = PyTuple_GetSlice(*args, 0, nargs);
+ if (!args1)
+ goto bad;
+ *args2 = PyTuple_GetSlice(*args, nargs, PyTuple_GET_SIZE(*args));
+ if (!*args2)
+ goto bad;
+ }
+ else if (PyTuple_GET_SIZE(*args) > nargs) {
+ int m = nargs;
+ int n = PyTuple_GET_SIZE(*args);
+ PyErr_Format(PyExc_TypeError,
+ "function takes at most %d positional arguments (%d given)",
+ m, n);
+ goto bad;
+ }
+ else {
+ args1 = *args;
+ Py_INCREF(args1);
+ }
+
+ if (rqd_kwds && !*kwds)
+ for (i = 0, p = kwd_list; *p; i++, p++)
+ if (rqd_kwds[i])
+ goto missing_kwarg;
+
+ if (kwds2) {
+ if (*kwds) {
+ kwds1 = PyDict_New();
+ if (!kwds1)
+ goto bad;
+ *kwds2 = PyDict_Copy(*kwds);
+ if (!*kwds2)
+ goto bad;
+ for (i = 0, p = kwd_list; *p; i++, p++) {
+ x = PyDict_GetItemString(*kwds, *p);
+ if (x) {
+ if (PyDict_SetItemString(kwds1, *p, x) < 0)
+ goto bad;
+ if (PyDict_DelItemString(*kwds2, *p) < 0)
+ goto bad;
+ }
+ else if (rqd_kwds && rqd_kwds[i])
+ goto missing_kwarg;
+ }
+ }
+ else {
+ *kwds2 = PyDict_New();
+ if (!*kwds2)
+ goto bad;
+ }
+ }
+ else {
+ kwds1 = *kwds;
+ Py_XINCREF(kwds1);
+ if (rqd_kwds && *kwds)
+ for (i = 0, p = kwd_list; *p; i++, p++)
+ if (rqd_kwds[i] && !PyDict_GetItemString(*kwds, *p))
+ goto missing_kwarg;
+ }
+
+ *args = args1;
+ *kwds = kwds1;
+ return 0;
+missing_kwarg:
+ PyErr_Format(PyExc_TypeError,
+ "required keyword argument '%s' is missing", *p);
+bad:
+ Py_XDECREF(args1);
+ Py_XDECREF(kwds1);
+ if (args2) {
+ Py_XDECREF(*args2);
+ }
+ if (kwds2) {
+ Py_XDECREF(*kwds2);
+ }
+ return -1;
+}
+
+static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list) {
+ PyObject *__import__ = 0;
+ PyObject *empty_list = 0;
+ PyObject *module = 0;
+ PyObject *global_dict = 0;
+ PyObject *empty_dict = 0;
+ PyObject *list;
+ __import__ = PyObject_GetAttrString(__pyx_b, "__import__");
+ if (!__import__)
+ goto bad;
+ if (from_list)
+ list = from_list;
+ else {
+ empty_list = PyList_New(0);
+ if (!empty_list)
+ goto bad;
+ list = empty_list;
+ }
+ global_dict = PyModule_GetDict(__pyx_m);
+ if (!global_dict)
+ goto bad;
+ empty_dict = PyDict_New();
+ if (!empty_dict)
+ goto bad;
+ module = PyObject_CallFunction(__import__, "OOOO",
+ name, global_dict, empty_dict, list);
+bad:
+ Py_XDECREF(empty_list);
+ Py_XDECREF(__import__);
+ Py_XDECREF(empty_dict);
+ return module;
+}
+
+static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name) {
+ PyObject *result;
+ result = PyObject_GetAttr(dict, name);
+ if (!result)
+ PyErr_SetObject(PyExc_NameError, name);
+ return result;
+}
+
+static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb) {
+ Py_XINCREF(type);
+ Py_XINCREF(value);
+ Py_XINCREF(tb);
+ /* First, check the traceback argument, replacing None with NULL. */
+ if (tb == Py_None) {
+ Py_DECREF(tb);
+ tb = 0;
+ }
+ else if (tb != NULL && !PyTraceBack_Check(tb)) {
+ PyErr_SetString(PyExc_TypeError,
+ "raise: arg 3 must be a traceback or None");
+ goto raise_error;
+ }
+ /* Next, replace a missing value with None */
+ if (value == NULL) {
+ value = Py_None;
+ Py_INCREF(value);
+ }
+ #if PY_VERSION_HEX < 0x02050000
+ if (!PyClass_Check(type))
+ #else
+ if (!PyType_Check(type))
+ #endif
+ {
+ /* Raising an instance. The value should be a dummy. */
+ if (value != Py_None) {
+ PyErr_SetString(PyExc_TypeError,
+ "instance exception may not have a separate value");
+ goto raise_error;
+ }
+ /* Normalize to raise <class>, <instance> */
+ Py_DECREF(value);
+ value = type;
+ #if PY_VERSION_HEX < 0x02050000
+ if (PyInstance_Check(type)) {
+ type = (PyObject*) ((PyInstanceObject*)type)->in_class;
+ Py_INCREF(type);
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "raise: exception must be an old-style class or instance");
+ goto raise_error;
+ }
+ #else
+ type = (PyObject*) type->ob_type;
+ Py_INCREF(type);
+ if (!PyType_IsSubtype((PyTypeObject *)type, (PyTypeObject *)PyExc_BaseException)) {
+ PyErr_SetString(PyExc_TypeError,
+ "raise: exception class must be a subclass of BaseException");
+ goto raise_error;
+ }
+ #endif
+ }
+ PyErr_Restore(type, value, tb);
+ return;
+raise_error:
+ Py_XDECREF(value);
+ Py_XDECREF(type);
+ Py_XDECREF(tb);
+ return;
+}
+
+static void __Pyx_UnpackError(void) {
+ PyErr_SetString(PyExc_ValueError, "unpack sequence of wrong size");
+}
+
+static PyObject *__Pyx_UnpackItem(PyObject *iter) {
+ PyObject *item;
+ if (!(item = PyIter_Next(iter))) {
+ if (!PyErr_Occurred())
+ __Pyx_UnpackError();
+ }
+ return item;
+}
+
+static int __Pyx_EndUnpack(PyObject *iter) {
+ PyObject *item;
+ if ((item = PyIter_Next(iter))) {
+ Py_DECREF(item);
+ __Pyx_UnpackError();
+ return -1;
+ }
+ else if (!PyErr_Occurred())
+ return 0;
+ else
+ return -1;
+}
+
+static int __Pyx_InternStrings(__Pyx_InternTabEntry *t) {
+ while (t->p) {
+ *t->p = PyString_InternFromString(t->s);
+ if (!*t->p)
+ return -1;
+ ++t;
+ }
+ return 0;
+}
+
+static int __Pyx_InitStrings(__Pyx_StringTabEntry *t) {
+ while (t->p) {
+ *t->p = PyString_FromStringAndSize(t->s, t->n - 1);
+ if (!*t->p)
+ return -1;
+ ++t;
+ }
+ return 0;
+}
+
+#include "compile.h"
+#include "frameobject.h"
+#include "traceback.h"
+
+static void __Pyx_AddTraceback(char *funcname) {
+ PyObject *py_srcfile = 0;
+ PyObject *py_funcname = 0;
+ PyObject *py_globals = 0;
+ PyObject *empty_tuple = 0;
+ PyObject *empty_string = 0;
+ PyCodeObject *py_code = 0;
+ PyFrameObject *py_frame = 0;
+
+ py_srcfile = PyString_FromString(__pyx_filename);
+ if (!py_srcfile) goto bad;
+ py_funcname = PyString_FromString(funcname);
+ if (!py_funcname) goto bad;
+ py_globals = PyModule_GetDict(__pyx_m);
+ if (!py_globals) goto bad;
+ empty_tuple = PyTuple_New(0);
+ if (!empty_tuple) goto bad;
+ empty_string = PyString_FromString("");
+ if (!empty_string) goto bad;
+ py_code = PyCode_New(
+ 0, /*int argcount,*/
+ 0, /*int nlocals,*/
+ 0, /*int stacksize,*/
+ 0, /*int flags,*/
+ empty_string, /*PyObject *code,*/
+ empty_tuple, /*PyObject *consts,*/
+ empty_tuple, /*PyObject *names,*/
+ empty_tuple, /*PyObject *varnames,*/
+ empty_tuple, /*PyObject *freevars,*/
+ empty_tuple, /*PyObject *cellvars,*/
+ py_srcfile, /*PyObject *filename,*/
+ py_funcname, /*PyObject *name,*/
+ __pyx_lineno, /*int firstlineno,*/
+ empty_string /*PyObject *lnotab*/
+ );
+ if (!py_code) goto bad;
+ py_frame = PyFrame_New(
+ PyThreadState_Get(), /*PyThreadState *tstate,*/
+ py_code, /*PyCodeObject *code,*/
+ py_globals, /*PyObject *globals,*/
+ 0 /*PyObject *locals*/
+ );
+ if (!py_frame) goto bad;
+ py_frame->f_lineno = __pyx_lineno;
+ PyTraceBack_Here(py_frame);
+bad:
+ Py_XDECREF(py_srcfile);
+ Py_XDECREF(py_funcname);
+ Py_XDECREF(empty_tuple);
+ Py_XDECREF(empty_string);
+ Py_XDECREF(py_code);
+ Py_XDECREF(py_frame);
+}
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/iocpsupport.pyx b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/iocpsupport.pyx
new file mode 100644
index 0000000000..85f8c4783f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/iocpsupport.pyx
@@ -0,0 +1,250 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+ctypedef int size_t
+ctypedef unsigned long HANDLE
+ctypedef unsigned long SOCKET
+ctypedef unsigned long DWORD
+ctypedef unsigned long ULONG_PTR
+ctypedef int BOOL
+
+cdef extern from 'io.h':
+ long _get_osfhandle(int filehandle)
+
+cdef extern from 'errno.h':
+ int errno
+ enum:
+ EBADF
+
+cdef extern from 'winsock2.h':
+ pass
+
+cdef extern from 'windows.h':
+ ctypedef struct OVERLAPPED:
+ pass
+ HANDLE CreateIoCompletionPort(HANDLE fileHandle, HANDLE existing, ULONG_PTR key, DWORD numThreads)
+ BOOL GetQueuedCompletionStatus(HANDLE port, DWORD *bytes, ULONG_PTR *key, OVERLAPPED **ov, DWORD timeout)
+ BOOL PostQueuedCompletionStatus(HANDLE port, DWORD bytes, ULONG_PTR key, OVERLAPPED *ov)
+ DWORD GetLastError()
+ BOOL CloseHandle(HANDLE h)
+ enum:
+ INVALID_HANDLE_VALUE
+ void DebugBreak()
+
+cdef extern from 'python.h':
+ struct PyObject:
+ pass
+ void *PyMem_Malloc(size_t n) except NULL
+ void PyMem_Free(void *p)
+ struct PyThreadState:
+ pass
+ PyThreadState *PyEval_SaveThread()
+ void PyEval_RestoreThread(PyThreadState *tstate)
+ void Py_INCREF(object o)
+ void Py_XINCREF(object o)
+ void Py_DECREF(object o)
+ void Py_XDECREF(object o)
+ int PyObject_AsWriteBuffer(object obj, void **buffer, int *buffer_len) except -1
+ int PyObject_AsReadBuffer(object obj, void **buffer, int *buffer_len) except -1
+ object PyString_FromString(char *v)
+ object PyString_FromStringAndSize(char *v, int len)
+ object PyBuffer_New(int size)
+ char *PyString_AsString(object obj) except NULL
+ object PySequence_Fast(object o, char *m)
+# object PySequence_Fast_GET_ITEM(object o, int i)
+ PyObject** PySequence_Fast_ITEMS(object o)
+ PyObject* PySequence_ITEM( PyObject *o, int i)
+ int PySequence_Fast_GET_SIZE(object o)
+
+cdef extern from '':
+ struct sockaddr:
+ int sa_family
+ char sa_data[0]
+ cdef struct in_addr:
+ unsigned long s_addr
+ struct sockaddr_in:
+ int sin_port
+ in_addr sin_addr
+ int getsockopt(SOCKET s, int level, int optname, char *optval, int *optlen)
+ enum:
+ SOL_SOCKET
+ SO_PROTOCOL_INFO
+ SOCKET_ERROR
+ ERROR_IO_PENDING
+ AF_INET
+ INADDR_ANY
+ ctypedef struct WSAPROTOCOL_INFO:
+ int iMaxSockAddr
+ int iAddressFamily
+ int WSAGetLastError()
+ char *inet_ntoa(in_addr ina)
+ unsigned long inet_addr(char *cp)
+ short ntohs(short netshort)
+ short htons(short hostshort)
+ ctypedef struct WSABUF:
+ long len
+ char *buf
+# cdef struct TRANSMIT_FILE_BUFFERS:
+# pass
+ int WSARecv(SOCKET s, WSABUF *buffs, DWORD buffcount, DWORD *bytes, DWORD *flags, OVERLAPPED *ov, void *crud)
+ int WSARecvFrom(SOCKET s, WSABUF *buffs, DWORD buffcount, DWORD *bytes, DWORD *flags, sockaddr *fromaddr, int *fromlen, OVERLAPPED *ov, void *crud)
+ int WSASend(SOCKET s, WSABUF *buffs, DWORD buffcount, DWORD *bytes, DWORD flags, OVERLAPPED *ov, void *crud)
+
+cdef extern from 'string.h':
+ void *memset(void *s, int c, size_t n)
+
+cdef extern from 'winsock_pointers.h':
+ int initWinsockPointers()
+ BOOL (*lpAcceptEx)(SOCKET listening, SOCKET accepting, void *buffer, DWORD recvlen, DWORD locallen, DWORD remotelen, DWORD *bytes, OVERLAPPED *ov)
+ void (*lpGetAcceptExSockaddrs)(void *buffer, DWORD recvlen, DWORD locallen, DWORD remotelen, sockaddr **localaddr, int *locallen, sockaddr **remoteaddr, int *remotelen)
+ BOOL (*lpConnectEx)(SOCKET s, sockaddr *name, int namelen, void *buff, DWORD sendlen, DWORD *sentlen, OVERLAPPED *ov)
+# BOOL (*lpTransmitFile)(SOCKET s, HANDLE hFile, DWORD size, DWORD buffer_size, OVERLAPPED *ov, TRANSMIT_FILE_BUFFERS *buff, DWORD flags)
+
+cdef struct myOVERLAPPED:
+ OVERLAPPED ov
+ PyObject *obj
+
+cdef myOVERLAPPED *makeOV() except NULL:
+ cdef myOVERLAPPED *res
+ res = <myOVERLAPPED *>PyMem_Malloc(sizeof(myOVERLAPPED))
+ if not res:
+ raise MemoryError
+ memset(res, 0, sizeof(myOVERLAPPED))
+ return res
+
+cdef void raise_error(int err, object message) except *:
+ if not err:
+ err = GetLastError()
+ raise WindowsError(message, err)
+
+class Event:
+ def __init__(self, callback, owner, **kw):
+ self.callback = callback
+ self.owner = owner
+ self.ignore = False
+ for k, v in kw.items():
+ setattr(self, k, v)
+
+cdef class CompletionPort:
+ cdef HANDLE port
+ def __init__(self):
+ cdef HANDLE res
+ res = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0)
+ if not res:
+ raise_error(0, 'CreateIoCompletionPort')
+ self.port = res
+
+ def addHandle(self, long handle, long key=0):
+ cdef HANDLE res
+ res = CreateIoCompletionPort(handle, self.port, key, 0)
+ if not res:
+ raise_error(0, 'CreateIoCompletionPort')
+
+ def getEvent(self, long timeout):
+ cdef PyThreadState *_save
+ cdef unsigned long bytes, key, rc
+ cdef myOVERLAPPED *ov
+
+ _save = PyEval_SaveThread()
+ rc = GetQueuedCompletionStatus(self.port, &bytes, &key, <OVERLAPPED **>&ov, timeout)
+ PyEval_RestoreThread(_save)
+
+ if not rc:
+ rc = GetLastError()
+ else:
+ rc = 0
+
+ obj = None
+ if ov:
+ if ov.obj:
+ obj = <object>ov.obj
+ Py_DECREF(obj) # we are stealing a reference here
+ PyMem_Free(ov)
+
+ return (rc, bytes, key, obj)
+
+ def postEvent(self, unsigned long bytes, unsigned long key, obj):
+ cdef myOVERLAPPED *ov
+ cdef unsigned long rc
+
+ if obj is not None:
+ ov = makeOV()
+ Py_INCREF(obj) # give ov its own reference to obj
+ ov.obj = <PyObject *>obj
+ else:
+ ov = NULL
+
+ rc = PostQueuedCompletionStatus(self.port, bytes, key, <OVERLAPPED *>ov)
+ if not rc:
+ raise_error(0, 'PostQueuedCompletionStatus')
+
+ def __del__(self):
+ CloseHandle(self.port)
+
+def makesockaddr(object buff):
+ cdef void *mem_buffer
+ cdef int size
+
+ PyObject_AsReadBuffer(buff, &mem_buffer, &size)
+ # XXX: this should really return the address family as well
+ return _makesockaddr(<sockaddr *>mem_buffer, size)
+
+cdef object _makesockaddr(sockaddr *addr, int len):
+ cdef sockaddr_in *sin
+ if not len:
+ return None
+ if addr.sa_family == AF_INET:
+ sin = <sockaddr_in *>addr
+ return PyString_FromString(inet_ntoa(sin.sin_addr)), ntohs(sin.sin_port)
+ else:
+ return PyString_FromStringAndSize(addr.sa_data, sizeof(addr.sa_data))
+
+cdef object fillinetaddr(sockaddr_in *dest, object addr):
+ cdef short port
+ cdef unsigned long res
+ cdef char *hoststr
+ host, port = addr
+
+ hoststr = PyString_AsString(host)
+ res = inet_addr(hoststr)
+ if res == INADDR_ANY:
+ raise ValueError, 'invalid IP address'
+ dest.sin_addr.s_addr = res
+
+ dest.sin_port = htons(port)
+
+def AllocateReadBuffer(int size):
+ return PyBuffer_New(size)
+
+def maxAddrLen(long s):
+ cdef WSAPROTOCOL_INFO wsa_pi
+ cdef int size, rc
+
+ size = sizeof(wsa_pi)
+ rc = getsockopt(s, SOL_SOCKET, SO_PROTOCOL_INFO, <char *>&wsa_pi, &size)
+ if rc == SOCKET_ERROR:
+ raise_error(WSAGetLastError(), 'getsockopt')
+ return wsa_pi.iMaxSockAddr
+
+cdef int getAddrFamily(SOCKET s) except *:
+ cdef WSAPROTOCOL_INFO wsa_pi
+ cdef int size, rc
+
+ size = sizeof(wsa_pi)
+ rc = getsockopt(s, SOL_SOCKET, SO_PROTOCOL_INFO, <char *>&wsa_pi, &size)
+ if rc == SOCKET_ERROR:
+ raise_error(WSAGetLastError(), 'getsockopt')
+ return wsa_pi.iAddressFamily
+
+import socket # for WSAStartup
+if not initWinsockPointers():
+ raise ValueError, 'Failed to initialize Winsock function vectors'
+
+have_connectex = (lpConnectEx != NULL)
+
+include 'acceptex.pxi'
+include 'connectex.pxi'
+include 'wsarecv.pxi'
+include 'wsasend.pxi'
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c
new file mode 100644
index 0000000000..9bd115afad
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c
@@ -0,0 +1,62 @@
+/* Copyright (c) 2008 Twisted Matrix Laboratories.
+ * See LICENSE for details.
+ */
+
+
+#include<winsock2.h>
+#include<assert.h>
+#include<stdio.h>
+#include<stdlib.h>
+
+#ifndef WSAID_CONNECTEX
+#define WSAID_CONNECTEX {0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e}}
+#endif
+#ifndef WSAID_GETACCEPTEXSOCKADDRS
+#define WSAID_GETACCEPTEXSOCKADDRS {0xb5367df2,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
+#endif
+#ifndef WSAID_ACCEPTEX
+#define WSAID_ACCEPTEX {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
+#endif
+/*#ifndef WSAID_TRANSMITFILE
+#define WSAID_TRANSMITFILE {0xb5367df0,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
+#endif*/
+
+
+void *lpAcceptEx, *lpGetAcceptExSockaddrs, *lpConnectEx, *lpTransmitFile;
+
+int initPointer(SOCKET s, void **fun, GUID guid) {
+ int res;
+ DWORD bytes;
+
+ *fun = NULL;
+ res = WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &guid, sizeof(guid),
+ fun, sizeof(fun),
+ &bytes, NULL, NULL);
+ return !res;
+}
+
+int initWinsockPointers() {
+ SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
+ /* I hate C */
+ GUID guid1 = WSAID_ACCEPTEX;
+ GUID guid2 = WSAID_GETACCEPTEXSOCKADDRS;
+ GUID guid3 = WSAID_CONNECTEX;
+ /*GUID guid4 = WSAID_TRANSMITFILE;*/
+ if (!s) {
+ return 0;
+ }
+ if (!initPointer(s, &lpAcceptEx, guid1))
+ {
+ return 0;
+ }
+ if (!initPointer(s, &lpGetAcceptExSockaddrs, guid2)) {
+ return 0;
+ }
+ if (!initPointer(s, &lpConnectEx, guid3)) {
+ return 0;
+ };
+ /*initPointer(s, &lpTransmitFile, guid4);*/
+ return 1;
+}
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.h b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.h
new file mode 100644
index 0000000000..83e9ba8399
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.h
@@ -0,0 +1,51 @@
+/* Copyright (c) 2008 Twisted Matrix Laboratories.
+ * See LICENSE for details.
+ */
+
+
+#include<windows.h>
+
+int initWinsockPointers();
+BOOL
+(PASCAL FAR * lpAcceptEx)(
+ IN SOCKET sListenSocket,
+ IN SOCKET sAcceptSocket,
+ IN PVOID lpOutputBuffer,
+ IN DWORD dwReceiveDataLength,
+ IN DWORD dwLocalAddressLength,
+ IN DWORD dwRemoteAddressLength,
+ OUT LPDWORD lpdwBytesReceived,
+ IN LPOVERLAPPED lpOverlapped
+ );
+VOID
+(PASCAL FAR * lpGetAcceptExSockaddrs)(
+ IN PVOID lpOutputBuffer,
+ IN DWORD dwReceiveDataLength,
+ IN DWORD dwLocalAddressLength,
+ IN DWORD dwRemoteAddressLength,
+ OUT struct sockaddr **LocalSockaddr,
+ OUT LPINT LocalSockaddrLength,
+ OUT struct sockaddr **RemoteSockaddr,
+ OUT LPINT RemoteSockaddrLength
+ );
+BOOL
+(PASCAL FAR * lpConnectEx) (
+ IN SOCKET s,
+ IN const struct sockaddr FAR *name,
+ IN int namelen,
+ IN PVOID lpSendBuffer OPTIONAL,
+ IN DWORD dwSendDataLength,
+ OUT LPDWORD lpdwBytesSent,
+ IN LPOVERLAPPED lpOverlapped
+ );
+/*BOOL
+(PASCAL FAR * lpTransmitFile)(
+ IN SOCKET hSocket,
+ IN HANDLE hFile,
+ IN DWORD nNumberOfBytesToWrite,
+ IN DWORD nNumberOfBytesPerSend,
+ IN LPOVERLAPPED lpOverlapped,
+ IN LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,
+ IN DWORD dwReserved
+ );*/
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/wsarecv.pxi b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/wsarecv.pxi
new file mode 100644
index 0000000000..afb1906052
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/wsarecv.pxi
@@ -0,0 +1,61 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+def recv(long s, object bufflist, object obj, unsigned long flags = 0):
+ cdef int rc, buffcount, i, res
+ cdef myOVERLAPPED *ov
+ cdef WSABUF *ws_buf
+ cdef unsigned long bytes
+ cdef PyObject **buffers
+
+ bufflist = PySequence_Fast(bufflist, 'second argument needs to be a list')
+ buffcount = PySequence_Fast_GET_SIZE(bufflist)
+ buffers = PySequence_Fast_ITEMS(bufflist)
+
+ ws_buf = <WSABUF *>PyMem_Malloc(buffcount*sizeof(WSABUF))
+
+ try:
+ for i from 0 <= i < buffcount:
+ PyObject_AsWriteBuffer(<object>buffers[i], <void **>&ws_buf[i].buf, <int *>&ws_buf[i].len)
+
+ ov = makeOV()
+ if obj is not None:
+ ov.obj = <PyObject *>obj
+
+ rc = WSARecv(s, ws_buf, buffcount, &bytes, &flags, <OVERLAPPED *>ov, NULL)
+
+ if rc == SOCKET_ERROR:
+ rc = WSAGetLastError()
+ if rc != ERROR_IO_PENDING:
+ return rc, 0
+
+ Py_XINCREF(obj)
+ return rc, bytes
+ finally:
+ PyMem_Free(ws_buf)
+
+def recvfrom(long s, object buff, object addr_buff, object obj, unsigned long flags = 0):
+ cdef int rc, fromlen
+ cdef myOVERLAPPED *ov
+ cdef WSABUF ws_buf
+ cdef unsigned long bytes
+ cdef sockaddr *fromaddr
+
+ PyObject_AsWriteBuffer(buff, <void **>&ws_buf.buf, <int *>&ws_buf.len)
+ PyObject_AsWriteBuffer(addr_buff, <void **>&fromaddr, &fromlen)
+
+ ov = makeOV()
+ if obj is not None:
+ ov.obj = <PyObject *>obj
+
+ rc = WSARecvFrom(s, &ws_buf, 1, &bytes, &flags, fromaddr, &fromlen, <OVERLAPPED *>ov, NULL)
+
+ if rc == SOCKET_ERROR:
+ rc = WSAGetLastError()
+ if rc != ERROR_IO_PENDING:
+ return rc, 0
+
+ Py_XINCREF(obj)
+ return rc, bytes
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/wsasend.pxi b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/wsasend.pxi
new file mode 100644
index 0000000000..03c44ada67
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/iocpsupport/wsasend.pxi
@@ -0,0 +1,27 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+def send(long s, object buff, object obj, unsigned long flags = 0):
+ cdef int rc
+ cdef myOVERLAPPED *ov
+ cdef WSABUF ws_buf
+ cdef unsigned long bytes
+
+ PyObject_AsReadBuffer(buff, <void **>&ws_buf.buf, <int *>&ws_buf.len)
+
+ ov = makeOV()
+ if obj is not None:
+ ov.obj = <PyObject *>obj
+
+ rc = WSASend(s, &ws_buf, 1, &bytes, flags, <OVERLAPPED *>ov, NULL)
+
+ if rc == SOCKET_ERROR:
+ rc = WSAGetLastError()
+ if rc != ERROR_IO_PENDING:
+ return rc, bytes
+
+ Py_XINCREF(obj)
+ return rc, bytes
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/notes.txt b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/notes.txt
new file mode 100644
index 0000000000..4caffb882f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/notes.txt
@@ -0,0 +1,24 @@
+test specifically:
+failed accept error message -- similar to test_tcp_internals
+immediate success on accept/connect/recv, including Event.ignore
+parametrize iocpsupport somehow -- via reactor?
+
+do:
+break handling -- WaitForSingleObject on the IOCP handle?
+iovecs for write buffer
+do not wait for a mainloop iteration if resumeProducing (in _handleWrite) does startWriting
+don't addActiveHandle in every call to startWriting/startReading
+iocpified process support
+ win32er-in-a-thread (or run GQCS in a thread -- it can't receive SIGBREAK)
+blocking in sendto() -- I think Windows can do that, especially with local UDP
+
+buildbot:
+run in vmware
+start from a persistent snapshot
+
+use a stub inside the vm to svnup/run tests/collect stdio
+lift logs through SMB? or ship them via tcp beams to the VM host
+
+have a timeout on the test run
+if we time out, take a screenshot, save it, kill the VM
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/reactor.py b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/reactor.py
new file mode 100644
index 0000000000..4db26926a6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/reactor.py
@@ -0,0 +1,267 @@
+# -*- test-case-name: twisted.internet.test.test_iocp -*-
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Reactor that uses IO completion ports
+"""
+
+import warnings, socket, sys
+
+from zope.interface import implements
+
+from twisted.internet import base, interfaces, main, error
+from twisted.python import log, failure
+from twisted.internet._dumbwin32proc import Process
+
+from twisted.internet.iocpreactor import iocpsupport as _iocp
+from twisted.internet.iocpreactor.const import WAIT_TIMEOUT
+from twisted.internet.iocpreactor import tcp, udp
+
+try:
+ from twisted.protocols.tls import TLSMemoryBIOFactory
+except ImportError:
+ # Either pyOpenSSL isn't installed, or it is too old for this code to work.
+ # The reactor won't provide IReactorSSL.
+ TLSMemoryBIOFactory = None
+ _extraInterfaces = ()
+ warnings.warn(
+ "pyOpenSSL 0.10 or newer is required for SSL support in iocpreactor. "
+ "It is missing, so the reactor will not support SSL APIs.")
+else:
+ _extraInterfaces = (interfaces.IReactorSSL,)
+
+from twisted.python.compat import set
+
+MAX_TIMEOUT = 2000 # 2 seconds, see doIteration for explanation
+
+EVENTS_PER_LOOP = 1000 # XXX: what's a good value here?
+
+# keys to associate with normal and waker events
+KEY_NORMAL, KEY_WAKEUP = range(2)
+
+_NO_GETHANDLE = error.ConnectionFdescWentAway(
+ 'Handler has no getFileHandle method')
+_NO_FILEDESC = error.ConnectionFdescWentAway('Filedescriptor went away')
+
+
+
+class IOCPReactor(base._SignalReactorMixin, base.ReactorBase):
+ implements(interfaces.IReactorTCP, interfaces.IReactorUDP,
+ interfaces.IReactorMulticast, interfaces.IReactorProcess,
+ *_extraInterfaces)
+
+ port = None
+
+ def __init__(self):
+ base.ReactorBase.__init__(self)
+ self.port = _iocp.CompletionPort()
+ self.handles = set()
+
+
+ def addActiveHandle(self, handle):
+ self.handles.add(handle)
+
+
+ def removeActiveHandle(self, handle):
+ self.handles.discard(handle)
+
+
+ def doIteration(self, timeout):
+ # This function sits and waits for an IO completion event.
+ #
+ # There are two requirements: process IO events as soon as they arrive
+ # and process ctrl-break from the user in a reasonable amount of time.
+ #
+ # There are three kinds of waiting.
+ # 1) GetQueuedCompletionStatus (self.port.getEvent) to wait for IO
+ # events only.
+ # 2) Msg* family of wait functions that can stop waiting when
+ # ctrl-break is detected (then, I think, Python converts it into a
+ # KeyboardInterrupt)
+ # 3) *Ex family of wait functions that put the thread into an
+ # "alertable" wait state which is supposedly triggered by IO completion
+ #
+ # 2) and 3) can be combined. Trouble is, my IO completion is not
+ # causing 3) to trigger, possibly because I do not use an IO completion
+ # callback. Windows is weird.
+ # There are two ways to handle this. I could use MsgWaitForSingleObject
+ # here and GetQueuedCompletionStatus in a thread. Or I could poll with
+ # a reasonable interval. Guess what! Threads are hard.
+
+ processed_events = 0
+ if timeout is None:
+ timeout = MAX_TIMEOUT
+ else:
+ timeout = min(MAX_TIMEOUT, int(1000*timeout))
+ rc, bytes, key, evt = self.port.getEvent(timeout)
+ while processed_events < EVENTS_PER_LOOP:
+ if rc == WAIT_TIMEOUT:
+ break
+ if key != KEY_WAKEUP:
+ assert key == KEY_NORMAL
+ if not evt.ignore:
+ log.callWithLogger(evt.owner, self._callEventCallback,
+ rc, bytes, evt)
+ processed_events += 1
+ rc, bytes, key, evt = self.port.getEvent(0)
+
+
+ def _callEventCallback(self, rc, bytes, evt):
+ owner = evt.owner
+ why = None
+ try:
+ evt.callback(rc, bytes, evt)
+ handfn = getattr(owner, 'getFileHandle', None)
+ if not handfn:
+ why = _NO_GETHANDLE
+ elif handfn() == -1:
+ why = _NO_FILEDESC
+ if why:
+ return # ignore handles that were closed
+ except:
+ why = sys.exc_info()[1]
+ log.err()
+ if why:
+ owner.loseConnection(failure.Failure(why))
+
+
+ def installWaker(self):
+ pass
+
+
+ def wakeUp(self):
+ self.port.postEvent(0, KEY_WAKEUP, None)
+
+
+ def registerHandle(self, handle):
+ self.port.addHandle(handle, KEY_NORMAL)
+
+
+ def createSocket(self, af, stype):
+ skt = socket.socket(af, stype)
+ self.registerHandle(skt.fileno())
+ return skt
+
+
+ def listenTCP(self, port, factory, backlog=50, interface=''):
+ """
+ @see: twisted.internet.interfaces.IReactorTCP.listenTCP
+ """
+ p = tcp.Port(port, factory, backlog, interface, self)
+ p.startListening()
+ return p
+
+
+ def connectTCP(self, host, port, factory, timeout=30, bindAddress=None):
+ """
+ @see: twisted.internet.interfaces.IReactorTCP.connectTCP
+ """
+ c = tcp.Connector(host, port, factory, timeout, bindAddress, self)
+ c.connect()
+ return c
+
+
+ if TLSMemoryBIOFactory is not None:
+ def listenSSL(self, port, factory, contextFactory, backlog=50, interface=''):
+ """
+ @see: twisted.internet.interfaces.IReactorSSL.listenSSL
+ """
+ return self.listenTCP(
+ port,
+ TLSMemoryBIOFactory(contextFactory, False, factory),
+ backlog, interface)
+
+
+ def connectSSL(self, host, port, factory, contextFactory, timeout=30, bindAddress=None):
+ """
+ @see: twisted.internet.interfaces.IReactorSSL.connectSSL
+ """
+ return self.connectTCP(
+ host, port,
+ TLSMemoryBIOFactory(contextFactory, True, factory),
+ timeout, bindAddress)
+ else:
+ def listenSSL(self, port, factory, contextFactory, backlog=50, interface=''):
+ """
+ Non-implementation of L{IReactorSSL.listenSSL}. Some dependency
+ is not satisfied. This implementation always raises
+ L{NotImplementedError}.
+ """
+ raise NotImplementedError(
+ "pyOpenSSL 0.10 or newer is required for SSL support in "
+ "iocpreactor. It is missing, so the reactor does not support "
+ "SSL APIs.")
+
+
+ def connectSSL(self, host, port, factory, contextFactory, timeout=30, bindAddress=None):
+ """
+ Non-implementation of L{IReactorSSL.connectSSL}. Some dependency
+ is not satisfied. This implementation always raises
+ L{NotImplementedError}.
+ """
+ raise NotImplementedError(
+ "pyOpenSSL 0.10 or newer is required for SSL support in "
+ "iocpreactor. It is missing, so the reactor does not support "
+ "SSL APIs.")
+
+
+ def listenUDP(self, port, protocol, interface='', maxPacketSize=8192):
+ """
+ Connects a given L{DatagramProtocol} to the given numeric UDP port.
+
+ @returns: object conforming to L{IListeningPort}.
+ """
+ p = udp.Port(port, protocol, interface, maxPacketSize, self)
+ p.startListening()
+ return p
+
+
+ def listenMulticast(self, port, protocol, interface='', maxPacketSize=8192,
+ listenMultiple=False):
+ """
+ Connects a given DatagramProtocol to the given numeric UDP port.
+
+ EXPERIMENTAL.
+
+ @returns: object conforming to IListeningPort.
+ """
+ p = udp.MulticastPort(port, protocol, interface, maxPacketSize, self,
+ listenMultiple)
+ p.startListening()
+ return p
+
+
+ def spawnProcess(self, processProtocol, executable, args=(), env={},
+ path=None, uid=None, gid=None, usePTY=0, childFDs=None):
+ """
+ Spawn a process.
+ """
+ if uid is not None:
+ raise ValueError("Setting UID is unsupported on this platform.")
+ if gid is not None:
+ raise ValueError("Setting GID is unsupported on this platform.")
+ if usePTY:
+ raise ValueError("PTYs are unsupported on this platform.")
+ if childFDs is not None:
+ raise ValueError(
+ "Custom child file descriptor mappings are unsupported on "
+ "this platform.")
+ args, env = self._checkProcessArgs(args, env)
+ return Process(self, processProtocol, executable, args, env, path)
+
+
+ def removeAll(self):
+ res = list(self.handles)
+ self.handles.clear()
+ return res
+
+
+
+def install():
+ r = IOCPReactor()
+ main.installReactor(r)
+
+
+__all__ = ['IOCPReactor', 'install']
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/setup.py b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/setup.py
new file mode 100644
index 0000000000..6a70d55770
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/setup.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Distutils file for building low-level IOCP bindings from their Pyrex source
+"""
+
+
+from distutils.core import setup
+from distutils.extension import Extension
+from Pyrex.Distutils import build_ext
+
+setup(name='iocpsupport',
+ ext_modules=[Extension('iocpsupport',
+ ['iocpsupport/iocpsupport.pyx',
+ 'iocpsupport/winsock_pointers.c'],
+ libraries = ['ws2_32'],
+ )
+ ],
+ cmdclass = {'build_ext': build_ext},
+ )
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/tcp.py b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/tcp.py
new file mode 100644
index 0000000000..7550d476a6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/tcp.py
@@ -0,0 +1,639 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+TCP support for IOCP reactor
+"""
+
+import socket, operator, errno, struct
+
+from zope.interface import implements, directlyProvides
+
+from twisted.internet import interfaces, error, address, main, defer
+from twisted.internet.abstract import isIPAddress
+from twisted.internet.tcp import _SocketCloser, Connector as TCPConnector
+from twisted.persisted import styles
+from twisted.python import log, failure, reflect, util
+
+from twisted.internet.iocpreactor import iocpsupport as _iocp, abstract
+from twisted.internet.iocpreactor.interfaces import IReadWriteHandle
+from twisted.internet.iocpreactor.const import ERROR_IO_PENDING
+from twisted.internet.iocpreactor.const import SO_UPDATE_CONNECT_CONTEXT
+from twisted.internet.iocpreactor.const import SO_UPDATE_ACCEPT_CONTEXT
+from twisted.internet.iocpreactor.const import ERROR_CONNECTION_REFUSED
+from twisted.internet.iocpreactor.const import ERROR_NETWORK_UNREACHABLE
+
+try:
+ from twisted.protocols.tls import TLSMemoryBIOFactory, TLSMemoryBIOProtocol
+except ImportError:
+ TLSMemoryBIOProtocol = TLSMemoryBIOFactory = None
+ _extraInterfaces = ()
+else:
+ _extraInterfaces = (interfaces.ITLSTransport,)
+
+# ConnectEx returns these. XXX: find out what it does for timeout
+connectExErrors = {
+ ERROR_CONNECTION_REFUSED: errno.WSAECONNREFUSED,
+ ERROR_NETWORK_UNREACHABLE: errno.WSAENETUNREACH,
+ }
+
+
+class _BypassTLS(object):
+ """
+ L{_BypassTLS} is used as the transport object for the TLS protocol object
+ used to implement C{startTLS}. Its methods skip any TLS logic which
+ C{startTLS} enables.
+
+ @ivar _connection: A L{Connection} which TLS has been started on which will
+ be proxied to by this object. Any method which has its behavior
+ altered after C{startTLS} will be skipped in favor of the base class's
+ implementation. This allows the TLS protocol object to have direct
+ access to the transport, necessary to actually implement TLS.
+ """
+ def __init__(self, connection):
+ self._connection = connection
+
+
+ def __getattr__(self, name):
+ return getattr(self._connection, name)
+
+
+ def write(self, data):
+ return abstract.FileHandle.write(self._connection, data)
+
+
+ def writeSequence(self, iovec):
+ return abstract.FileHandle.writeSequence(self._connection, iovec)
+
+
+ def loseConnection(self, reason=None):
+ return abstract.FileHandle.loseConnection(self._connection, reason)
+
+
+
+class Connection(abstract.FileHandle, _SocketCloser):
+ """
+ @ivar _tls: C{False} to indicate the connection is in normal TCP mode,
+ C{True} to indicate that TLS has been started and that operations must
+ be routed through the L{TLSMemoryBIOProtocol} instance.
+
+ @ivar _tlsClientDefault: A flag which must be set by a subclass. If set to
+ C{True}, L{startTLS} will default to initiating SSL as a client. If
+ set to C{False}, L{startTLS} will default to initiating SSL as a
+ server.
+ """
+ implements(IReadWriteHandle, interfaces.ITCPTransport,
+ interfaces.ISystemHandle, *_extraInterfaces)
+
+ _tls = False
+
+ def __init__(self, sock, proto, reactor=None):
+ abstract.FileHandle.__init__(self, reactor)
+ self.socket = sock
+ self.getFileHandle = sock.fileno
+ self.protocol = proto
+
+
+ def getHandle(self):
+ return self.socket
+
+
+ def dataReceived(self, rbuffer):
+ # XXX: some day, we'll have protocols that can handle raw buffers
+ self.protocol.dataReceived(str(rbuffer))
+
+
+ def readFromHandle(self, bufflist, evt):
+ return _iocp.recv(self.getFileHandle(), bufflist, evt)
+
+
+ def writeToHandle(self, buff, evt):
+ return _iocp.send(self.getFileHandle(), buff, evt)
+
+
+ def _closeWriteConnection(self):
+ try:
+ getattr(self.socket, self._socketShutdownMethod)(1)
+ except socket.error:
+ pass
+ p = interfaces.IHalfCloseableProtocol(self.protocol, None)
+ if p:
+ try:
+ p.writeConnectionLost()
+ except:
+ f = failure.Failure()
+ log.err()
+ self.connectionLost(f)
+
+
+ def readConnectionLost(self, reason):
+ p = interfaces.IHalfCloseableProtocol(self.protocol, None)
+ if p:
+ try:
+ p.readConnectionLost()
+ except:
+ log.err()
+ self.connectionLost(failure.Failure())
+ else:
+ self.connectionLost(reason)
+
+
+ def connectionLost(self, reason):
+ abstract.FileHandle.connectionLost(self, reason)
+ self._closeSocket()
+ protocol = self.protocol
+ del self.protocol
+ del self.socket
+ del self.getFileHandle
+ protocol.connectionLost(reason)
+
+
+ def logPrefix(self):
+ """
+ Return the prefix to log with when I own the logging thread.
+ """
+ return self.logstr
+
+
+ def getTcpNoDelay(self):
+ return operator.truth(self.socket.getsockopt(socket.IPPROTO_TCP,
+ socket.TCP_NODELAY))
+
+
+ def setTcpNoDelay(self, enabled):
+ self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, enabled)
+
+
+ def getTcpKeepAlive(self):
+ return operator.truth(self.socket.getsockopt(socket.SOL_SOCKET,
+ socket.SO_KEEPALIVE))
+
+
+ def setTcpKeepAlive(self, enabled):
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, enabled)
+
+
+ if TLSMemoryBIOFactory is not None:
+ def startTLS(self, contextFactory, normal=True):
+ """
+ @see: L{ITLSTransport.startTLS}
+ """
+ # Figure out which direction the SSL goes in. If normal is True,
+ # we'll go in the direction indicated by the subclass. Otherwise,
+ # we'll go the other way (client = not normal ^ _tlsClientDefault,
+ # in other words).
+ if normal:
+ client = self._tlsClientDefault
+ else:
+ client = not self._tlsClientDefault
+
+ tlsFactory = TLSMemoryBIOFactory(contextFactory, client, None)
+ tlsProtocol = TLSMemoryBIOProtocol(tlsFactory, self.protocol, False)
+ self.protocol = tlsProtocol
+
+ self.getHandle = tlsProtocol.getHandle
+ self.getPeerCertificate = tlsProtocol.getPeerCertificate
+
+ # Mark the transport as secure.
+ directlyProvides(self, interfaces.ISSLTransport)
+
+ # Remember we did this so that write and writeSequence can send the
+ # data to the right place.
+ self._tls = True
+
+ # Hook it up
+ self.protocol.makeConnection(_BypassTLS(self))
+
+
+ def write(self, data):
+ """
+ Write some data, either directly to the underlying handle or, if TLS
+ has been started, to the L{TLSMemoryBIOProtocol} for it to encrypt and
+ send.
+
+ @see: L{ITCPTransport.write}
+ """
+ if self._tls:
+ self.protocol.write(data)
+ else:
+ abstract.FileHandle.write(self, data)
+
+
+ def writeSequence(self, iovec):
+ """
+ Write some data, either directly to the underlying handle or, if TLS
+ has been started, to the L{TLSMemoryBIOProtocol} for it to encrypt and
+ send.
+
+ @see: L{ITCPTransport.writeSequence}
+ """
+ if self._tls:
+ self.protocol.writeSequence(iovec)
+ else:
+ abstract.FileHandle.writeSequence(self, iovec)
+
+
+ def loseConnection(self, reason=None):
+ """
+ Close the underlying handle or, if TLS has been started, first shut it
+ down.
+
+ @see: L{ITCPTransport.loseConnection}
+ """
+ if self._tls:
+ if self.connected and not self.disconnecting:
+ self.protocol.loseConnection()
+ else:
+ abstract.FileHandle.loseConnection(self, reason)
+
+
+
+class Client(Connection):
+ addressFamily = socket.AF_INET
+ socketType = socket.SOCK_STREAM
+
+ _tlsClientDefault = True
+
+ def __init__(self, host, port, bindAddress, connector, reactor):
+ self.connector = connector
+ self.addr = (host, port)
+ self.reactor = reactor
+ # ConnectEx documentation says socket _has_ to be bound
+ if bindAddress is None:
+ bindAddress = ('', 0)
+
+ try:
+ try:
+ skt = reactor.createSocket(self.addressFamily, self.socketType)
+ except socket.error, se:
+ raise error.ConnectBindError(se[0], se[1])
+ else:
+ try:
+ skt.bind(bindAddress)
+ except socket.error, se:
+ raise error.ConnectBindError(se[0], se[1])
+ self.socket = skt
+ Connection.__init__(self, skt, None, reactor)
+ reactor.callLater(0, self.resolveAddress)
+ except error.ConnectBindError, err:
+ reactor.callLater(0, self.failIfNotConnected, err)
+
+
+ def resolveAddress(self):
+ if isIPAddress(self.addr[0]):
+ self._setRealAddress(self.addr[0])
+ else:
+ d = self.reactor.resolve(self.addr[0])
+ d.addCallbacks(self._setRealAddress, self.failIfNotConnected)
+
+
+ def _setRealAddress(self, address):
+ self.realAddress = (address, self.addr[1])
+ self.doConnect()
+
+
+ def failIfNotConnected(self, err):
+ if (self.connected or self.disconnected or
+ not hasattr(self, "connector")):
+ return
+
+ try:
+ self._closeSocket()
+ except AttributeError:
+ pass
+ else:
+ del self.socket, self.getFileHandle
+ self.reactor.removeActiveHandle(self)
+
+ self.connector.connectionFailed(failure.Failure(err))
+ del self.connector
+
+
+ def stopConnecting(self):
+ """
+ Stop attempt to connect.
+ """
+ self.failIfNotConnected(error.UserError())
+
+
+ def cbConnect(self, rc, bytes, evt):
+ if rc:
+ rc = connectExErrors.get(rc, rc)
+ self.failIfNotConnected(error.getConnectError((rc,
+ errno.errorcode.get(rc, 'Unknown error'))))
+ else:
+ self.socket.setsockopt(socket.SOL_SOCKET,
+ SO_UPDATE_CONNECT_CONTEXT,
+ struct.pack('I', self.socket.fileno()))
+ self.protocol = self.connector.buildProtocol(self.getPeer())
+ self.connected = True
+ self.logstr = self.protocol.__class__.__name__+",client"
+ self.protocol.makeConnection(self)
+ self.startReading()
+
+
+ def doConnect(self):
+ if not hasattr(self, "connector"):
+ # this happens if we connector.stopConnecting in
+ # factory.startedConnecting
+ return
+ assert _iocp.have_connectex
+ self.reactor.addActiveHandle(self)
+ evt = _iocp.Event(self.cbConnect, self)
+
+ rc = _iocp.connect(self.socket.fileno(), self.realAddress, evt)
+ if rc == ERROR_IO_PENDING:
+ return
+ else:
+ evt.ignore = True
+ self.cbConnect(rc, 0, 0, evt)
+
+
+ def getHost(self):
+ """
+ Returns an IPv4Address.
+
+ This indicates the address from which I am connecting.
+ """
+ return address.IPv4Address('TCP', *(self.socket.getsockname() +
+ ('INET',)))
+
+
+ def getPeer(self):
+ """
+ Returns an IPv4Address.
+
+ This indicates the address that I am connected to.
+ """
+ return address.IPv4Address('TCP', *(self.realAddress + ('INET',)))
+
+
+ def __repr__(self):
+ s = ('<%s to %s at %x>' %
+ (self.__class__, self.addr, util.unsignedID(self)))
+ return s
+
+
+ def connectionLost(self, reason):
+ if not self.connected:
+ self.failIfNotConnected(error.ConnectError(string=reason))
+ else:
+ Connection.connectionLost(self, reason)
+ self.connector.connectionLost(reason)
+
+
+
+class Server(Connection):
+ """
+ Serverside socket-stream connection class.
+
+ I am a serverside network connection transport; a socket which came from an
+ accept() on a server.
+ """
+
+ _tlsClientDefault = False
+
+
+ def __init__(self, sock, protocol, clientAddr, serverAddr, sessionno, reactor):
+ """
+ Server(sock, protocol, client, server, sessionno)
+
+ Initialize me with a socket, a protocol, a descriptor for my peer (a
+ tuple of host, port describing the other end of the connection), an
+ instance of Port, and a session number.
+ """
+ Connection.__init__(self, sock, protocol, reactor)
+ self.serverAddr = serverAddr
+ self.clientAddr = clientAddr
+ self.sessionno = sessionno
+ self.logstr = "%s,%s,%s" % (self.protocol.__class__.__name__,
+ sessionno, self.clientAddr.host)
+ self.repstr = "<%s #%s on %s>" % (self.protocol.__class__.__name__,
+ self.sessionno, self.serverAddr.port)
+ self.connected = True
+ self.startReading()
+
+
+ def __repr__(self):
+ """
+ A string representation of this connection.
+ """
+ return self.repstr
+
+
+ def getHost(self):
+ """
+ Returns an IPv4Address.
+
+ This indicates the server's address.
+ """
+ return self.serverAddr
+
+
+ def getPeer(self):
+ """
+ Returns an IPv4Address.
+
+ This indicates the client's address.
+ """
+ return self.clientAddr
+
+
+
+class Connector(TCPConnector):
+ def _makeTransport(self):
+ return Client(self.host, self.port, self.bindAddress, self,
+ self.reactor)
+
+
+
+class Port(styles.Ephemeral, _SocketCloser):
+ implements(interfaces.IListeningPort)
+
+ connected = False
+ disconnected = False
+ disconnecting = False
+ addressFamily = socket.AF_INET
+ socketType = socket.SOCK_STREAM
+
+ sessionno = 0
+
+ maxAccepts = 100
+
+ # Actual port number being listened on, only set to a non-None
+ # value when we are actually listening.
+ _realPortNumber = None
+
+
+ def __init__(self, port, factory, backlog=50, interface='', reactor=None):
+ self.port = port
+ self.factory = factory
+ self.backlog = backlog
+ self.interface = interface
+ self.reactor = reactor
+
+
+ def __repr__(self):
+ if self._realPortNumber is not None:
+ return "<%s of %s on %s>" % (self.__class__,
+ self.factory.__class__,
+ self._realPortNumber)
+ else:
+ return "<%s of %s (not listening)>" % (self.__class__,
+ self.factory.__class__)
+
+
+ def startListening(self):
+ try:
+ skt = self.reactor.createSocket(self.addressFamily,
+ self.socketType)
+ # TODO: resolve self.interface if necessary
+ skt.bind((self.interface, self.port))
+ except socket.error, le:
+ raise error.CannotListenError, (self.interface, self.port, le)
+
+ self.addrLen = _iocp.maxAddrLen(skt.fileno())
+
+ # Make sure that if we listened on port 0, we update that to
+ # reflect what the OS actually assigned us.
+ self._realPortNumber = skt.getsockname()[1]
+
+ log.msg("%s starting on %s" % (self.factory.__class__,
+ self._realPortNumber))
+
+ self.factory.doStart()
+ skt.listen(self.backlog)
+ self.connected = True
+ self.disconnected = False
+ self.reactor.addActiveHandle(self)
+ self.socket = skt
+ self.getFileHandle = self.socket.fileno
+ self.doAccept()
+
+
+ def loseConnection(self, connDone=failure.Failure(main.CONNECTION_DONE)):
+ """
+ Stop accepting connections on this port.
+
+ This will shut down my socket and call self.connectionLost().
+ It returns a deferred which will fire successfully when the
+ port is actually closed.
+ """
+ self.disconnecting = True
+ if self.connected:
+ self.deferred = defer.Deferred()
+ self.reactor.callLater(0, self.connectionLost, connDone)
+ return self.deferred
+
+ stopListening = loseConnection
+
+
+ def connectionLost(self, reason):
+ """
+ Cleans up the socket.
+ """
+ log.msg('(Port %s Closed)' % self._realPortNumber)
+ self._realPortNumber = None
+ d = None
+ if hasattr(self, "deferred"):
+ d = self.deferred
+ del self.deferred
+
+ self.disconnected = True
+ self.reactor.removeActiveHandle(self)
+ self.connected = False
+ self._closeSocket()
+ del self.socket
+ del self.getFileHandle
+
+ try:
+ self.factory.doStop()
+ except:
+ self.disconnecting = False
+ if d is not None:
+ d.errback(failure.Failure())
+ else:
+ raise
+ else:
+ self.disconnecting = False
+ if d is not None:
+ d.callback(None)
+
+
+ def logPrefix(self):
+ """
+ Returns the name of my class, to prefix log entries with.
+ """
+ return reflect.qual(self.factory.__class__)
+
+
+ def getHost(self):
+ """
+ Returns an IPv4Address.
+
+ This indicates the server's address.
+ """
+ return address.IPv4Address('TCP', *(self.socket.getsockname() +
+ ('INET',)))
+
+
+ def cbAccept(self, rc, bytes, evt):
+ self.handleAccept(rc, evt)
+ if not (self.disconnecting or self.disconnected):
+ self.doAccept()
+
+
+ def handleAccept(self, rc, evt):
+ if self.disconnecting or self.disconnected:
+ return False
+
+ # possible errors:
+ # (WSAEMFILE, WSAENOBUFS, WSAENFILE, WSAENOMEM, WSAECONNABORTED)
+ if rc:
+ log.msg("Could not accept new connection -- %s (%s)" %
+ (errno.errorcode.get(rc, 'unknown error'), rc))
+ return False
+ else:
+ evt.newskt.setsockopt(socket.SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
+ struct.pack('I', self.socket.fileno()))
+ family, lAddr, rAddr = _iocp.get_accept_addrs(evt.newskt.fileno(),
+ evt.buff)
+ assert family == self.addressFamily
+
+ protocol = self.factory.buildProtocol(
+ address._ServerFactoryIPv4Address('TCP', rAddr[0], rAddr[1]))
+ if protocol is None:
+ evt.newskt.close()
+ else:
+ s = self.sessionno
+ self.sessionno = s+1
+ transport = Server(evt.newskt, protocol,
+ address.IPv4Address('TCP', rAddr[0], rAddr[1], 'INET'),
+ address.IPv4Address('TCP', lAddr[0], lAddr[1], 'INET'),
+ s, self.reactor)
+ protocol.makeConnection(transport)
+ return True
+
+
+ def doAccept(self):
+ numAccepts = 0
+ while 1:
+ evt = _iocp.Event(self.cbAccept, self)
+
+ # see AcceptEx documentation
+ evt.buff = buff = _iocp.AllocateReadBuffer(2 * (self.addrLen + 16))
+
+ evt.newskt = newskt = self.reactor.createSocket(self.addressFamily,
+ self.socketType)
+ rc = _iocp.accept(self.socket.fileno(), newskt.fileno(), buff, evt)
+
+ if (rc == ERROR_IO_PENDING
+ or (not rc and numAccepts >= self.maxAccepts)):
+ break
+ else:
+ evt.ignore = True
+ if not self.handleAccept(rc, evt):
+ break
+ numAccepts += 1
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/udp.py b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/udp.py
new file mode 100644
index 0000000000..c0e1a8ee70
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/iocpreactor/udp.py
@@ -0,0 +1,389 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+UDP support for IOCP reactor
+"""
+
+from twisted.internet import defer, address, error, interfaces
+from twisted.internet.abstract import isIPAddress
+from twisted.python import log, reflect, failure
+
+from zope.interface import implements
+import socket, operator, struct, warnings, errno
+
+from twisted.internet.iocpreactor.const import ERROR_IO_PENDING
+from twisted.internet.iocpreactor.const import ERROR_CONNECTION_REFUSED
+from twisted.internet.iocpreactor.const import ERROR_PORT_UNREACHABLE
+from twisted.internet.iocpreactor.interfaces import IReadWriteHandle
+from twisted.internet.iocpreactor import iocpsupport as _iocp, abstract
+
+
+
+class Port(abstract.FileHandle):
+ """
+ UDP port, listening for packets.
+ """
+
+ implements(IReadWriteHandle, interfaces.IUDPTransport,
+ interfaces.ISystemHandle)
+
+ addressFamily = socket.AF_INET
+ socketType = socket.SOCK_DGRAM
+ maxThroughput = 256 * 1024 # max bytes we read in one eventloop iteration
+ dynamicReadBuffers = False
+
+ # Actual port number being listened on, only set to a non-None
+ # value when we are actually listening.
+ _realPortNumber = None
+
+
+ def __init__(self, port, proto, interface='', maxPacketSize=8192,
+ reactor=None):
+ """
+ Initialize with a numeric port to listen on.
+ """
+ self.port = port
+ self.protocol = proto
+ self.readBufferSize = maxPacketSize
+ self.interface = interface
+ self.setLogStr()
+ self._connectedAddr = None
+
+ abstract.FileHandle.__init__(self, reactor)
+
+ skt = socket.socket(self.addressFamily, self.socketType)
+ addrLen = _iocp.maxAddrLen(skt.fileno())
+ self.addressBuffer = _iocp.AllocateReadBuffer(addrLen)
+
+
+ def __repr__(self):
+ if self._realPortNumber is not None:
+ return ("<%s on %s>" %
+ (self.protocol.__class__, self._realPortNumber))
+ else:
+ return "<%s not connected>" % (self.protocol.__class__,)
+
+
+ def getHandle(self):
+ """
+ Return a socket object.
+ """
+ return self.socket
+
+
+ def startListening(self):
+ """
+ Create and bind my socket, and begin listening on it.
+
+ This is called on unserialization, and must be called after creating a
+ server to begin listening on the specified port.
+ """
+ self._bindSocket()
+ self._connectToProtocol()
+
+
+ def createSocket(self):
+ return self.reactor.createSocket(self.addressFamily, self.socketType)
+
+
+ def _bindSocket(self):
+ try:
+ skt = self.createSocket()
+ skt.bind((self.interface, self.port))
+ except socket.error, le:
+ raise error.CannotListenError, (self.interface, self.port, le)
+
+ # Make sure that if we listened on port 0, we update that to
+ # reflect what the OS actually assigned us.
+ self._realPortNumber = skt.getsockname()[1]
+
+ log.msg("%s starting on %s" %
+ (self.protocol.__class__, self._realPortNumber))
+
+ self.connected = True
+ self.socket = skt
+ self.getFileHandle = self.socket.fileno
+
+
+ def _connectToProtocol(self):
+ self.protocol.makeConnection(self)
+ self.startReading()
+ self.reactor.addActiveHandle(self)
+
+
+ def cbRead(self, rc, bytes, evt):
+ if self.reading:
+ self.handleRead(rc, bytes, evt)
+ self.doRead()
+
+
+ def handleRead(self, rc, bytes, evt):
+ if rc in (errno.WSAECONNREFUSED, errno.WSAECONNRESET,
+ ERROR_CONNECTION_REFUSED, ERROR_PORT_UNREACHABLE):
+ if self._connectedAddr:
+ self.protocol.connectionRefused()
+ elif rc:
+ log.msg("error in recvfrom -- %s (%s)" %
+ (errno.errorcode.get(rc, 'unknown error'), rc))
+ else:
+ try:
+ self.protocol.datagramReceived(str(evt.buff[:bytes]),
+ _iocp.makesockaddr(evt.addr_buff))
+ except:
+ log.err()
+
+
+ def doRead(self):
+ read = 0
+ while self.reading:
+ evt = _iocp.Event(self.cbRead, self)
+
+ evt.buff = buff = self._readBuffers[0]
+ evt.addr_buff = addr_buff = self.addressBuffer
+ rc, bytes = _iocp.recvfrom(self.getFileHandle(), buff,
+ addr_buff, evt)
+
+ if (rc == ERROR_IO_PENDING
+ or (not rc and read >= self.maxThroughput)):
+ break
+ else:
+ evt.ignore = True
+ self.handleRead(rc, bytes, evt)
+ read += bytes
+
+
+ def write(self, datagram, addr=None):
+ """
+ Write a datagram.
+
+ @param addr: should be a tuple (ip, port), can be None in connected
+ mode.
+ """
+ if self._connectedAddr:
+ assert addr in (None, self._connectedAddr)
+ try:
+ return self.socket.send(datagram)
+ except socket.error, se:
+ no = se.args[0]
+ if no == errno.WSAEINTR:
+ return self.write(datagram)
+ elif no == errno.WSAEMSGSIZE:
+ raise error.MessageLengthError, "message too long"
+ elif no in (errno.WSAECONNREFUSED, errno.WSAECONNRESET,
+ ERROR_CONNECTION_REFUSED, ERROR_PORT_UNREACHABLE):
+ self.protocol.connectionRefused()
+ else:
+ raise
+ else:
+ assert addr != None
+ if not addr[0].replace(".", "").isdigit():
+ warnings.warn("Please only pass IPs to write(), not hostnames",
+ DeprecationWarning, stacklevel=2)
+ try:
+ return self.socket.sendto(datagram, addr)
+ except socket.error, se:
+ no = se.args[0]
+ if no == errno.WSAEINTR:
+ return self.write(datagram, addr)
+ elif no == errno.WSAEMSGSIZE:
+ raise error.MessageLengthError, "message too long"
+ elif no in (errno.WSAECONNREFUSED, errno.WSAECONNRESET,
+ ERROR_CONNECTION_REFUSED, ERROR_PORT_UNREACHABLE):
+ # in non-connected UDP ECONNREFUSED is platform dependent,
+ # I think and the info is not necessarily useful.
+ # Nevertheless maybe we should call connectionRefused? XXX
+ return
+ else:
+ raise
+
+
+ def writeSequence(self, seq, addr):
+ self.write("".join(seq), addr)
+
+
+ def connect(self, host, port):
+ """
+ 'Connect' to remote server.
+ """
+ if self._connectedAddr:
+ raise RuntimeError(
+ "already connected, reconnecting is not currently supported "
+ "(talk to itamar if you want this)")
+ if not isIPAddress(host):
+ raise ValueError, "please pass only IP addresses, not domain names"
+ self._connectedAddr = (host, port)
+ self.socket.connect((host, port))
+
+
+ def _loseConnection(self):
+ self.stopReading()
+ self.reactor.removeActiveHandle(self)
+ if self.connected: # actually means if we are *listening*
+ from twisted.internet import reactor
+ reactor.callLater(0, self.connectionLost)
+
+
+ def stopListening(self):
+ if self.connected:
+ result = self.d = defer.Deferred()
+ else:
+ result = None
+ self._loseConnection()
+ return result
+
+
+ def loseConnection(self):
+ warnings.warn("Please use stopListening() to disconnect port",
+ DeprecationWarning, stacklevel=2)
+ self.stopListening()
+
+
+ def connectionLost(self, reason=None):
+ """
+ Cleans up my socket.
+ """
+ log.msg('(Port %s Closed)' % self._realPortNumber)
+ self._realPortNumber = None
+ self.stopReading()
+ if hasattr(self, "protocol"):
+ # we won't have attribute in ConnectedPort, in cases
+ # where there was an error in connection process
+ self.protocol.doStop()
+ self.connected = False
+ self.disconnected = True
+ self.socket.close()
+ del self.socket
+ del self.getFileHandle
+ if hasattr(self, "d"):
+ self.d.callback(None)
+ del self.d
+
+
+ def setLogStr(self):
+ self.logstr = reflect.qual(self.protocol.__class__) + " (UDP)"
+
+
+ def logPrefix(self):
+ """
+ Returns the name of my class, to prefix log entries with.
+ """
+ return self.logstr
+
+
+ def getHost(self):
+ """
+ Returns an IPv4Address.
+
+ This indicates the address from which I am connecting.
+ """
+ return address.IPv4Address('UDP', *(self.socket.getsockname() +
+ ('INET_UDP',)))
+
+
+
+class MulticastMixin:
+ """
+ Implement multicast functionality.
+ """
+
+
+ def getOutgoingInterface(self):
+ i = self.socket.getsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF)
+ return socket.inet_ntoa(struct.pack("@i", i))
+
+
+ def setOutgoingInterface(self, addr):
+ """
+ Returns Deferred of success.
+ """
+ return self.reactor.resolve(addr).addCallback(self._setInterface)
+
+
+ def _setInterface(self, addr):
+ i = socket.inet_aton(addr)
+ self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, i)
+ return 1
+
+
+ def getLoopbackMode(self):
+ return self.socket.getsockopt(socket.IPPROTO_IP,
+ socket.IP_MULTICAST_LOOP)
+
+
+ def setLoopbackMode(self, mode):
+ mode = struct.pack("b", operator.truth(mode))
+ self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP,
+ mode)
+
+
+ def getTTL(self):
+ return self.socket.getsockopt(socket.IPPROTO_IP,
+ socket.IP_MULTICAST_TTL)
+
+
+ def setTTL(self, ttl):
+ ttl = struct.pack("B", ttl)
+ self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
+
+
+ def joinGroup(self, addr, interface=""):
+ """
+ Join a multicast group. Returns Deferred of success.
+ """
+ return self.reactor.resolve(addr).addCallback(self._joinAddr1,
+ interface, 1)
+
+
+ def _joinAddr1(self, addr, interface, join):
+ return self.reactor.resolve(interface).addCallback(self._joinAddr2,
+ addr, join)
+
+
+ def _joinAddr2(self, interface, addr, join):
+ addr = socket.inet_aton(addr)
+ interface = socket.inet_aton(interface)
+ if join:
+ cmd = socket.IP_ADD_MEMBERSHIP
+ else:
+ cmd = socket.IP_DROP_MEMBERSHIP
+ try:
+ self.socket.setsockopt(socket.IPPROTO_IP, cmd, addr + interface)
+ except socket.error, e:
+ return failure.Failure(error.MulticastJoinError(addr, interface,
+ *e.args))
+
+
+ def leaveGroup(self, addr, interface=""):
+ """
+ Leave multicast group, return Deferred of success.
+ """
+ return self.reactor.resolve(addr).addCallback(self._joinAddr1,
+ interface, 0)
+
+
+
+class MulticastPort(MulticastMixin, Port):
+ """
+ UDP Port that supports multicasting.
+ """
+
+ implements(interfaces.IMulticastTransport)
+
+
+ def __init__(self, port, proto, interface='', maxPacketSize=8192,
+ reactor=None, listenMultiple=False):
+ Port.__init__(self, port, proto, interface, maxPacketSize, reactor)
+ self.listenMultiple = listenMultiple
+
+
+ def createSocket(self):
+ skt = Port.createSocket(self)
+ if self.listenMultiple:
+ skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ if hasattr(socket, "SO_REUSEPORT"):
+ skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ return skt
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/kqreactor.py b/vendor/Twisted-10.0.0/twisted/internet/kqreactor.py
new file mode 100644
index 0000000000..ec69ce3abb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/kqreactor.py
@@ -0,0 +1,221 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A kqueue()/kevent() based implementation of the Twisted main loop.
+
+To install the event loop (and you should do this before any connections,
+listeners or connectors are added)::
+
+ | from twisted.internet import kqreactor
+ | kqreactor.install()
+
+This reactor only works on FreeBSD and requires PyKQueue 1.3, which is
+available at: U{http://people.freebsd.org/~dwhite/PyKQueue/}
+
+
+
+You're going to need to patch PyKqueue::
+
+ =====================================================
+ --- PyKQueue-1.3/kqsyscallmodule.c Sun Jan 28 21:59:50 2001
+ +++ PyKQueue-1.3/kqsyscallmodule.c.new Tue Jul 30 18:06:08 2002
+ @@ -137,7 +137,7 @@
+ }
+
+ statichere PyTypeObject KQEvent_Type = {
+ - PyObject_HEAD_INIT(NULL)
+ + PyObject_HEAD_INIT(&PyType_Type)
+ 0, // ob_size
+ "KQEvent", // tp_name
+ sizeof(KQEventObject), // tp_basicsize
+ @@ -291,13 +291,14 @@
+
+ /* Build timespec for timeout */
+ totimespec.tv_sec = timeout / 1000;
+ - totimespec.tv_nsec = (timeout % 1000) * 100000;
+ + totimespec.tv_nsec = (timeout % 1000) * 1000000;
+
+ // printf("timespec: sec=%d nsec=%d\\n", totimespec.tv_sec, totimespec.tv_nsec);
+
+ /* Make the call */
+ -
+ + Py_BEGIN_ALLOW_THREADS
+ gotNumEvents = kevent (self->fd, changelist, haveNumEvents, triggered, wantNumEvents, &totimespec);
+ + Py_END_ALLOW_THREADS
+
+ /* Don't need the input event list anymore, so get rid of it */
+ free (changelist);
+ @@ -361,7 +362,7 @@
+ statichere PyTypeObject KQueue_Type = {
+ /* The ob_type field must be initialized in the module init function
+ * to be portable to Windows without using C++. */
+ - PyObject_HEAD_INIT(NULL)
+ + PyObject_HEAD_INIT(&PyType_Type)
+ 0, /*ob_size*/
+ "KQueue", /*tp_name*/
+ sizeof(KQueueObject), /*tp_basicsize*/
+
+"""
+
+import errno, sys
+
+from zope.interface import implements
+
+from kqsyscall import EVFILT_READ, EVFILT_WRITE, EV_DELETE, EV_ADD
+from kqsyscall import kqueue, kevent
+
+from twisted.internet.interfaces import IReactorFDSet
+
+from twisted.python import log, failure
+from twisted.internet import main, posixbase
+
+
+class KQueueReactor(posixbase.PosixReactorBase):
+ """
+ A reactor that uses kqueue(2)/kevent(2).
+
+ @ivar _kq: A L{kqueue} which will be used to check for I/O readiness.
+
+ @ivar _selectables: A dictionary mapping integer file descriptors to
+ instances of L{FileDescriptor} which have been registered with the
+ reactor. All L{FileDescriptors} which are currently receiving read or
+ write readiness notifications will be present as values in this
+ dictionary.
+
+ @ivar _reads: A dictionary mapping integer file descriptors to arbitrary
+ values (this is essentially a set). Keys in this dictionary will be
+ registered with C{_kq} for read readiness notifications which will be
+ dispatched to the corresponding L{FileDescriptor} instances in
+ C{_selectables}.
+
+ @ivar _writes: A dictionary mapping integer file descriptors to arbitrary
+ values (this is essentially a set). Keys in this dictionary will be
+ registered with C{_kq} for write readiness notifications which will be
+ dispatched to the corresponding L{FileDescriptor} instances in
+ C{_selectables}.
+ """
+ implements(IReactorFDSet)
+
+ def __init__(self):
+ """
+ Initialize kqueue object, file descriptor tracking dictionaries, and the
+ base class.
+ """
+ self._kq = kqueue()
+ self._reads = {}
+ self._writes = {}
+ self._selectables = {}
+ posixbase.PosixReactorBase.__init__(self)
+
+
+ def _updateRegistration(self, *args):
+ self._kq.kevent([kevent(*args)], 0, 0)
+
+ def addReader(self, reader):
+ """Add a FileDescriptor for notification of data available to read.
+ """
+ fd = reader.fileno()
+ if fd not in self._reads:
+ self._selectables[fd] = reader
+ self._reads[fd] = 1
+ self._updateRegistration(fd, EVFILT_READ, EV_ADD)
+
+ def addWriter(self, writer):
+ """Add a FileDescriptor for notification of data available to write.
+ """
+ fd = writer.fileno()
+ if fd not in self._writes:
+ self._selectables[fd] = writer
+ self._writes[fd] = 1
+ self._updateRegistration(fd, EVFILT_WRITE, EV_ADD)
+
+ def removeReader(self, reader):
+ """Remove a Selectable for notification of data available to read.
+ """
+ fd = reader.fileno()
+ if fd in self._reads:
+ del self._reads[fd]
+ if fd not in self._writes:
+ del self._selectables[fd]
+ self._updateRegistration(fd, EVFILT_READ, EV_DELETE)
+
+ def removeWriter(self, writer):
+ """Remove a Selectable for notification of data available to write.
+ """
+ fd = writer.fileno()
+ if fd in self._writes:
+ del self._writes[fd]
+ if fd not in self._reads:
+ del self._selectables[fd]
+ self._updateRegistration(fd, EVFILT_WRITE, EV_DELETE)
+
+ def removeAll(self):
+ """
+ Remove all selectables, and return a list of them.
+ """
+ return self._removeAll(
+ [self._selectables[fd] for fd in self._reads],
+ [self._selectables[fd] for fd in self._writes])
+
+
+ def getReaders(self):
+ return [self._selectables[fd] for fd in self._reads]
+
+
+ def getWriters(self):
+ return [self._selectables[fd] for fd in self._writes]
+
+
+ def doKEvent(self, timeout):
+ """Poll the kqueue for new events."""
+ if timeout is None:
+ timeout = 1000
+ else:
+ timeout = int(timeout * 1000) # convert seconds to milliseconds
+
+ try:
+ l = self._kq.kevent([], len(self._selectables), timeout)
+ except OSError, e:
+ if e[0] == errno.EINTR:
+ return
+ else:
+ raise
+ _drdw = self._doWriteOrRead
+ for event in l:
+ why = None
+ fd, filter = event.ident, event.filter
+ try:
+ selectable = self._selectables[fd]
+ except KeyError:
+ # Handles the infrequent case where one selectable's
+ # handler disconnects another.
+ continue
+ log.callWithLogger(selectable, _drdw, selectable, fd, filter)
+
+ def _doWriteOrRead(self, selectable, fd, filter):
+ try:
+ if filter == EVFILT_READ:
+ why = selectable.doRead()
+ if filter == EVFILT_WRITE:
+ why = selectable.doWrite()
+ if not selectable.fileno() == fd:
+ why = main.CONNECTION_LOST
+ except:
+ why = sys.exc_info()[1]
+ log.deferr()
+
+ if why:
+ self.removeReader(selectable)
+ self.removeWriter(selectable)
+ selectable.connectionLost(failure.Failure(why))
+
+ doIteration = doKEvent
+
+
+def install():
+ k = KQueueReactor()
+ main.installReactor(k)
+
+
+__all__ = ["KQueueReactor", "install"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/main.py b/vendor/Twisted-10.0.0/twisted/internet/main.py
new file mode 100644
index 0000000000..76c3024db8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/main.py
@@ -0,0 +1,28 @@
+# -*- test-case-name: twisted.test.test_app -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Backwards compatability, and utility functions.
+
+In general, this module should not be used, other than by reactor authors
+who need to use the 'installReactor' method.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+import error
+
+CONNECTION_DONE = error.ConnectionDone('Connection done')
+CONNECTION_LOST = error.ConnectionLost('Connection lost')
+
+def installReactor(reactor):
+ # this stuff should be common to all reactors.
+ import twisted.internet
+ import sys
+ assert not sys.modules.has_key('twisted.internet.reactor'), \
+ "reactor already installed"
+ twisted.internet.reactor = reactor
+ sys.modules['twisted.internet.reactor'] = reactor
+
+__all__ = ["CONNECTION_LOST", "CONNECTION_DONE", "installReactor"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/pollreactor.py b/vendor/Twisted-10.0.0/twisted/internet/pollreactor.py
new file mode 100644
index 0000000000..390e7e22de
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/pollreactor.py
@@ -0,0 +1,208 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+A poll() based implementation of the twisted main loop.
+
+To install the event loop (and you should do this before any connections,
+listeners or connectors are added)::
+
+ from twisted.internet import pollreactor
+ pollreactor.install()
+"""
+
+# System imports
+import errno, sys
+from select import error as SelectError, poll
+from select import POLLIN, POLLOUT, POLLHUP, POLLERR, POLLNVAL
+
+from zope.interface import implements
+
+# Twisted imports
+from twisted.python import log
+from twisted.internet import main, posixbase, error
+from twisted.internet.interfaces import IReactorFDSet
+
+POLL_DISCONNECTED = (POLLHUP | POLLERR | POLLNVAL)
+
+
+class PollReactor(posixbase.PosixReactorBase):
+ """
+ A reactor that uses poll(2).
+
+ @ivar _poller: A L{poll} which will be used to check for I/O
+ readiness.
+
+ @ivar _selectables: A dictionary mapping integer file descriptors to
+ instances of L{FileDescriptor} which have been registered with the
+ reactor. All L{FileDescriptors} which are currently receiving read or
+ write readiness notifications will be present as values in this
+ dictionary.
+
+ @ivar _reads: A dictionary mapping integer file descriptors to arbitrary
+ values (this is essentially a set). Keys in this dictionary will be
+ registered with C{_poller} for read readiness notifications which will
+ be dispatched to the corresponding L{FileDescriptor} instances in
+ C{_selectables}.
+
+ @ivar _writes: A dictionary mapping integer file descriptors to arbitrary
+ values (this is essentially a set). Keys in this dictionary will be
+ registered with C{_poller} for write readiness notifications which will
+ be dispatched to the corresponding L{FileDescriptor} instances in
+ C{_selectables}.
+ """
+ implements(IReactorFDSet)
+
+ def __init__(self):
+ """
+ Initialize polling object, file descriptor tracking dictionaries, and
+ the base class.
+ """
+ self._poller = poll()
+ self._selectables = {}
+ self._reads = {}
+ self._writes = {}
+ posixbase.PosixReactorBase.__init__(self)
+
+
+ def _updateRegistration(self, fd):
+ """Register/unregister an fd with the poller."""
+ try:
+ self._poller.unregister(fd)
+ except KeyError:
+ pass
+
+ mask = 0
+ if fd in self._reads:
+ mask = mask | POLLIN
+ if fd in self._writes:
+ mask = mask | POLLOUT
+ if mask != 0:
+ self._poller.register(fd, mask)
+ else:
+ if fd in self._selectables:
+ del self._selectables[fd]
+
+ def _dictRemove(self, selectable, mdict):
+ try:
+ # the easy way
+ fd = selectable.fileno()
+ # make sure the fd is actually real. In some situations we can get
+ # -1 here.
+ mdict[fd]
+ except:
+ # the hard way: necessary because fileno() may disappear at any
+ # moment, thanks to python's underlying sockets impl
+ for fd, fdes in self._selectables.items():
+ if selectable is fdes:
+ break
+ else:
+ # Hmm, maybe not the right course of action? This method can't
+ # fail, because it happens inside error detection...
+ return
+ if fd in mdict:
+ del mdict[fd]
+ self._updateRegistration(fd)
+
+ def addReader(self, reader):
+ """Add a FileDescriptor for notification of data available to read.
+ """
+ fd = reader.fileno()
+ if fd not in self._reads:
+ self._selectables[fd] = reader
+ self._reads[fd] = 1
+ self._updateRegistration(fd)
+
+ def addWriter(self, writer):
+ """Add a FileDescriptor for notification of data available to write.
+ """
+ fd = writer.fileno()
+ if fd not in self._writes:
+ self._selectables[fd] = writer
+ self._writes[fd] = 1
+ self._updateRegistration(fd)
+
+ def removeReader(self, reader):
+ """Remove a Selectable for notification of data available to read.
+ """
+ return self._dictRemove(reader, self._reads)
+
+ def removeWriter(self, writer):
+ """Remove a Selectable for notification of data available to write.
+ """
+ return self._dictRemove(writer, self._writes)
+
+ def removeAll(self):
+ """
+ Remove all selectables, and return a list of them.
+ """
+ return self._removeAll(
+ [self._selectables[fd] for fd in self._reads],
+ [self._selectables[fd] for fd in self._writes])
+
+
+ def doPoll(self, timeout):
+ """Poll the poller for new events."""
+ if timeout is not None:
+ timeout = int(timeout * 1000) # convert seconds to milliseconds
+
+ try:
+ l = self._poller.poll(timeout)
+ except SelectError, e:
+ if e[0] == errno.EINTR:
+ return
+ else:
+ raise
+ _drdw = self._doReadOrWrite
+ for fd, event in l:
+ try:
+ selectable = self._selectables[fd]
+ except KeyError:
+ # Handles the infrequent case where one selectable's
+ # handler disconnects another.
+ continue
+ log.callWithLogger(selectable, _drdw, selectable, fd, event)
+
+ doIteration = doPoll
+
+ def _doReadOrWrite(self, selectable, fd, event):
+ why = None
+ inRead = False
+ if event & POLL_DISCONNECTED and not (event & POLLIN):
+ why = main.CONNECTION_LOST
+ else:
+ try:
+ if event & POLLIN:
+ why = selectable.doRead()
+ inRead = True
+ if not why and event & POLLOUT:
+ why = selectable.doWrite()
+ inRead = False
+ if not selectable.fileno() == fd:
+ why = error.ConnectionFdescWentAway('Filedescriptor went away')
+ inRead = False
+ except:
+ log.deferr()
+ why = sys.exc_info()[1]
+ if why:
+ self._disconnectSelectable(selectable, why, inRead)
+
+
+ def getReaders(self):
+ return [self._selectables[fd] for fd in self._reads]
+
+
+ def getWriters(self):
+ return [self._selectables[fd] for fd in self._writes]
+
+
+
+def install():
+ """Install the poll() reactor."""
+ p = PollReactor()
+ from twisted.internet.main import installReactor
+ installReactor(p)
+
+
+__all__ = ["PollReactor", "install"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/posixbase.py b/vendor/Twisted-10.0.0/twisted/internet/posixbase.py
new file mode 100644
index 0000000000..b410a4ee54
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/posixbase.py
@@ -0,0 +1,417 @@
+# -*- test-case-name: twisted.test.test_internet -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Posix reactor base class
+"""
+
+import warnings
+import socket
+import errno
+import os
+
+from zope.interface import implements, classImplements
+
+from twisted.python.compat import set
+from twisted.internet.interfaces import IReactorUNIX, IReactorUNIXDatagram
+from twisted.internet.interfaces import IReactorTCP, IReactorUDP, IReactorSSL, IReactorArbitrary
+from twisted.internet.interfaces import IReactorProcess, IReactorMulticast
+from twisted.internet.interfaces import IHalfCloseableDescriptor
+from twisted.internet import error
+from twisted.internet import tcp, udp
+
+from twisted.python import log, failure, util
+from twisted.persisted import styles
+from twisted.python.runtime import platformType, platform
+
+from twisted.internet.base import ReactorBase, _SignalReactorMixin
+
+try:
+ from twisted.internet import ssl
+ sslEnabled = True
+except ImportError:
+ sslEnabled = False
+
+try:
+ from twisted.internet import unix
+ unixEnabled = True
+except ImportError:
+ unixEnabled = False
+
+processEnabled = False
+if platformType == 'posix':
+ from twisted.internet import fdesc
+ import process
+ processEnabled = True
+
+if platform.isWindows():
+ try:
+ import win32process
+ processEnabled = True
+ except ImportError:
+ win32process = None
+
+
+class _SocketWaker(log.Logger, styles.Ephemeral):
+ """
+ The I{self-pipe trick<http://cr.yp.to/docs/selfpipe.html>}, implemented
+ using a pair of sockets rather than pipes (due to the lack of support in
+ select() on Windows for pipes), used to wake up the main loop from
+ another thread.
+ """
+ disconnected = 0
+
+ def __init__(self, reactor):
+ """Initialize.
+ """
+ self.reactor = reactor
+ # Following select_trigger (from asyncore)'s example;
+ server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ server.bind(('127.0.0.1', 0))
+ server.listen(1)
+ client.connect(server.getsockname())
+ reader, clientaddr = server.accept()
+ client.setblocking(0)
+ reader.setblocking(0)
+ self.r = reader
+ self.w = client
+ self.fileno = self.r.fileno
+
+ def wakeUp(self):
+ """Send a byte to my connection.
+ """
+ try:
+ util.untilConcludes(self.w.send, 'x')
+ except socket.error, (err, msg):
+ if err != errno.WSAEWOULDBLOCK:
+ raise
+
+ def doRead(self):
+ """Read some data from my connection.
+ """
+ try:
+ self.r.recv(8192)
+ except socket.error:
+ pass
+
+ def connectionLost(self, reason):
+ self.r.close()
+ self.w.close()
+
+
+
+class _PipeWaker(log.Logger, styles.Ephemeral):
+ """
+ The I{self-pipe trick<http://cr.yp.to/docs/selfpipe.html>}, used to wake
+ up the main loop from another thread or a signal handler.
+ """
+ disconnected = 0
+
+ i = None
+ o = None
+
+ def __init__(self, reactor):
+ """Initialize.
+ """
+ self.reactor = reactor
+ self.i, self.o = os.pipe()
+ fdesc.setNonBlocking(self.i)
+ fdesc._setCloseOnExec(self.i)
+ fdesc.setNonBlocking(self.o)
+ fdesc._setCloseOnExec(self.o)
+ self.fileno = lambda: self.i
+
+ def doRead(self):
+ """Read some bytes from the pipe.
+ """
+ fdesc.readFromFD(self.fileno(), lambda data: None)
+
+ def wakeUp(self):
+ """Write one byte to the pipe, and flush it.
+ """
+ # We don't use fdesc.writeToFD since we need to distinguish
+ # between EINTR (try again) and EAGAIN (do nothing).
+ if self.o is not None:
+ try:
+ util.untilConcludes(os.write, self.o, 'x')
+ except OSError, e:
+ if e.errno != errno.EAGAIN:
+ raise
+
+ def connectionLost(self, reason):
+ """Close both ends of my pipe.
+ """
+ if not hasattr(self, "o"):
+ return
+ for fd in self.i, self.o:
+ try:
+ os.close(fd)
+ except IOError:
+ pass
+ del self.i, self.o
+
+
+if platformType == 'posix':
+ _Waker = _PipeWaker
+else:
+ # Primarily Windows and Jython.
+ _Waker = _SocketWaker
+
+
+class PosixReactorBase(_SignalReactorMixin, ReactorBase):
+ """
+ A basis for reactors that use file descriptors.
+ """
+ implements(IReactorArbitrary, IReactorTCP, IReactorUDP, IReactorMulticast)
+
+ def __init__(self):
+ ReactorBase.__init__(self)
+ if self.usingThreads or platformType == "posix":
+ self.installWaker()
+
+
+ def _disconnectSelectable(self, selectable, why, isRead, faildict={
+ error.ConnectionDone: failure.Failure(error.ConnectionDone()),
+ error.ConnectionLost: failure.Failure(error.ConnectionLost())
+ }):
+ """
+ Utility function for disconnecting a selectable.
+
+ Supports half-close notification, isRead should be boolean indicating
+ whether error resulted from doRead().
+ """
+ self.removeReader(selectable)
+ f = faildict.get(why.__class__)
+ if f:
+ if (isRead and why.__class__ == error.ConnectionDone
+ and IHalfCloseableDescriptor.providedBy(selectable)):
+ selectable.readConnectionLost(f)
+ else:
+ self.removeWriter(selectable)
+ selectable.connectionLost(f)
+ else:
+ self.removeWriter(selectable)
+ selectable.connectionLost(failure.Failure(why))
+
+ def installWaker(self):
+ """
+ Install a `waker' to allow threads and signals to wake up the IO thread.
+
+ We use the self-pipe trick (http://cr.yp.to/docs/selfpipe.html) to wake
+ the reactor. On Windows we use a pair of sockets.
+ """
+ if not self.waker:
+ self.waker = _Waker(self)
+ self._internalReaders.add(self.waker)
+ self.addReader(self.waker)
+
+
+ # IReactorProcess
+
+ def spawnProcess(self, processProtocol, executable, args=(),
+ env={}, path=None,
+ uid=None, gid=None, usePTY=0, childFDs=None):
+ args, env = self._checkProcessArgs(args, env)
+ if platformType == 'posix':
+ if usePTY:
+ if childFDs is not None:
+ raise ValueError("Using childFDs is not supported with usePTY=True.")
+ return process.PTYProcess(self, executable, args, env, path,
+ processProtocol, uid, gid, usePTY)
+ else:
+ return process.Process(self, executable, args, env, path,
+ processProtocol, uid, gid, childFDs)
+ elif platformType == "win32":
+ if uid is not None or gid is not None:
+ raise ValueError("The uid and gid parameters are not supported on Windows.")
+ if usePTY:
+ raise ValueError("The usePTY parameter is not supported on Windows.")
+ if childFDs:
+ raise ValueError("Customizing childFDs is not supported on Windows.")
+
+ if win32process:
+ from twisted.internet._dumbwin32proc import Process
+ return Process(self, processProtocol, executable, args, env, path)
+ else:
+ raise NotImplementedError, "spawnProcess not available since pywin32 is not installed."
+ else:
+ raise NotImplementedError, "spawnProcess only available on Windows or POSIX."
+
+ # IReactorUDP
+
+ def listenUDP(self, port, protocol, interface='', maxPacketSize=8192):
+ """Connects a given L{DatagramProtocol} to the given numeric UDP port.
+
+ @returns: object conforming to L{IListeningPort}.
+ """
+ p = udp.Port(port, protocol, interface, maxPacketSize, self)
+ p.startListening()
+ return p
+
+ # IReactorMulticast
+
+ def listenMulticast(self, port, protocol, interface='', maxPacketSize=8192, listenMultiple=False):
+ """Connects a given DatagramProtocol to the given numeric UDP port.
+
+ EXPERIMENTAL.
+
+ @returns: object conforming to IListeningPort.
+ """
+ p = udp.MulticastPort(port, protocol, interface, maxPacketSize, self, listenMultiple)
+ p.startListening()
+ return p
+
+
+ # IReactorUNIX
+
+ def connectUNIX(self, address, factory, timeout=30, checkPID=0):
+ """@see: twisted.internet.interfaces.IReactorUNIX.connectUNIX
+ """
+ assert unixEnabled, "UNIX support is not present"
+ c = unix.Connector(address, factory, timeout, self, checkPID)
+ c.connect()
+ return c
+
+ _unspecified = object()
+ def _checkMode(self, name, mode):
+ """
+ Check C{mode} to see if a value was specified for it and emit a
+ deprecation warning if so. Return the default value if none was
+ specified, otherwise return C{mode}.
+ """
+ if mode is not self._unspecified:
+ warnings.warn(
+ 'The mode parameter of %(name)s will be removed. Do not pass '
+ 'a value for it. Set permissions on the containing directory '
+ 'before calling %(name)s, instead.' % dict(name=name),
+ category=DeprecationWarning,
+ stacklevel=3)
+ else:
+ mode = 0666
+ return mode
+
+
+ def listenUNIX(self, address, factory, backlog=50, mode=_unspecified,
+ wantPID=0):
+ """
+ @see: twisted.internet.interfaces.IReactorUNIX.listenUNIX
+ """
+ assert unixEnabled, "UNIX support is not present"
+ mode = self._checkMode('IReactorUNIX.listenUNIX', mode)
+ p = unix.Port(address, factory, backlog, mode, self, wantPID)
+ p.startListening()
+ return p
+
+
+ # IReactorUNIXDatagram
+
+ def listenUNIXDatagram(self, address, protocol, maxPacketSize=8192,
+ mode=_unspecified):
+ """
+ Connects a given L{DatagramProtocol} to the given path.
+
+ EXPERIMENTAL.
+
+ @returns: object conforming to L{IListeningPort}.
+ """
+ assert unixEnabled, "UNIX support is not present"
+ mode = self._checkMode('IReactorUNIXDatagram.listenUNIXDatagram', mode)
+ p = unix.DatagramPort(address, protocol, maxPacketSize, mode, self)
+ p.startListening()
+ return p
+
+ def connectUNIXDatagram(self, address, protocol, maxPacketSize=8192,
+ mode=_unspecified, bindAddress=None):
+ """
+ Connects a L{ConnectedDatagramProtocol} instance to a path.
+
+ EXPERIMENTAL.
+ """
+ assert unixEnabled, "UNIX support is not present"
+ mopde = self._checkMode('IReactorUNIXDatagram.connectUNIXDatagram', mode)
+ p = unix.ConnectedDatagramPort(address, protocol, maxPacketSize, mode, bindAddress, self)
+ p.startListening()
+ return p
+
+
+ # IReactorTCP
+
+ def listenTCP(self, port, factory, backlog=50, interface=''):
+ """@see: twisted.internet.interfaces.IReactorTCP.listenTCP
+ """
+ p = tcp.Port(port, factory, backlog, interface, self)
+ p.startListening()
+ return p
+
+ def connectTCP(self, host, port, factory, timeout=30, bindAddress=None):
+ """@see: twisted.internet.interfaces.IReactorTCP.connectTCP
+ """
+ c = tcp.Connector(host, port, factory, timeout, bindAddress, self)
+ c.connect()
+ return c
+
+ # IReactorSSL (sometimes, not implemented)
+
+ def connectSSL(self, host, port, factory, contextFactory, timeout=30, bindAddress=None):
+ """@see: twisted.internet.interfaces.IReactorSSL.connectSSL
+ """
+ assert sslEnabled, "SSL support is not present"
+ c = ssl.Connector(host, port, factory, contextFactory, timeout, bindAddress, self)
+ c.connect()
+ return c
+
+ def listenSSL(self, port, factory, contextFactory, backlog=50, interface=''):
+ """@see: twisted.internet.interfaces.IReactorSSL.listenSSL
+ """
+ assert sslEnabled, "SSL support is not present"
+ p = ssl.Port(port, factory, contextFactory, backlog, interface, self)
+ p.startListening()
+ return p
+
+ # IReactorArbitrary
+ def listenWith(self, portType, *args, **kw):
+ kw['reactor'] = self
+ p = portType(*args, **kw)
+ p.startListening()
+ return p
+
+ def connectWith(self, connectorType, *args, **kw):
+ kw['reactor'] = self
+ c = connectorType(*args, **kw)
+ c.connect()
+ return c
+
+ def _removeAll(self, readers, writers):
+ """
+ Remove all readers and writers, and list of removed L{IReadDescriptor}s
+ and L{IWriteDescriptor}s.
+
+ Meant for calling from subclasses, to implement removeAll, like::
+
+ def removeAll(self):
+ return self._removeAll(self._reads, self._writes)
+
+ where C{self._reads} and C{self._writes} are iterables.
+ """
+ removedReaders = set(readers) - self._internalReaders
+ for reader in removedReaders:
+ self.removeReader(reader)
+
+ removedWriters = set(writers)
+ for writer in removedWriters:
+ self.removeWriter(writer)
+
+ return list(removedReaders | removedWriters)
+
+
+if sslEnabled:
+ classImplements(PosixReactorBase, IReactorSSL)
+if unixEnabled:
+ classImplements(PosixReactorBase, IReactorUNIX, IReactorUNIXDatagram)
+if processEnabled:
+ classImplements(PosixReactorBase, IReactorProcess)
+
+__all__ = ["PosixReactorBase"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/process.py b/vendor/Twisted-10.0.0/twisted/internet/process.py
new file mode 100644
index 0000000000..7c4d533c53
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/process.py
@@ -0,0 +1,931 @@
+# -*- test-case-name: twisted.test.test_process -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+UNIX Process management.
+
+Do NOT use this module directly - use reactor.spawnProcess() instead.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+# System Imports
+import gc, os, sys, traceback, select, signal, errno
+
+try:
+ import pty
+except ImportError:
+ pty = None
+
+try:
+ import fcntl, termios
+except ImportError:
+ fcntl = None
+
+from twisted.python import log, failure
+from twisted.python.util import switchUID
+from twisted.internet import fdesc, abstract, error
+from twisted.internet.main import CONNECTION_LOST, CONNECTION_DONE
+from twisted.internet._baseprocess import BaseProcess
+
+# Some people were importing this, which is incorrect, just keeping it
+# here for backwards compatibility:
+ProcessExitedAlready = error.ProcessExitedAlready
+
+reapProcessHandlers = {}
+
+def reapAllProcesses():
+ """
+ Reap all registered processes.
+ """
+ for process in reapProcessHandlers.values():
+ process.reapProcess()
+
+
+def registerReapProcessHandler(pid, process):
+ """
+ Register a process handler for the given pid, in case L{reapAllProcesses}
+ is called.
+
+ @param pid: the pid of the process.
+ @param process: a process handler.
+ """
+ if pid in reapProcessHandlers:
+ raise RuntimeError("Try to register an already registered process.")
+ try:
+ auxPID, status = os.waitpid(pid, os.WNOHANG)
+ except:
+ log.msg('Failed to reap %d:' % pid)
+ log.err()
+ auxPID = None
+ if auxPID:
+ process.processEnded(status)
+ else:
+ # if auxPID is 0, there are children but none have exited
+ reapProcessHandlers[pid] = process
+
+
+def unregisterReapProcessHandler(pid, process):
+ """
+ Unregister a process handler previously registered with
+ L{registerReapProcessHandler}.
+ """
+ if not (pid in reapProcessHandlers
+ and reapProcessHandlers[pid] == process):
+ raise RuntimeError("Try to unregister a process not registered.")
+ del reapProcessHandlers[pid]
+
+
+def detectLinuxBrokenPipeBehavior():
+ """
+ On some Linux version, write-only pipe are detected as readable. This
+ function is here to check if this bug is present or not.
+
+ See L{ProcessWriter.doRead} for a more detailed explanation.
+ """
+ global brokenLinuxPipeBehavior
+ r, w = os.pipe()
+ os.write(w, 'a')
+ reads, writes, exes = select.select([w], [], [], 0)
+ if reads:
+ # Linux < 2.6.11 says a write-only pipe is readable.
+ brokenLinuxPipeBehavior = True
+ else:
+ brokenLinuxPipeBehavior = False
+ os.close(r)
+ os.close(w)
+
+# Call at import time
+detectLinuxBrokenPipeBehavior()
+
+
+class ProcessWriter(abstract.FileDescriptor):
+ """
+ (Internal) Helper class to write into a Process's input pipe.
+
+ I am a helper which describes a selectable asynchronous writer to a
+ process's input pipe, including stdin.
+ """
+ connected = 1
+ ic = 0
+ enableReadHack = False
+
+ def __init__(self, reactor, proc, name, fileno, forceReadHack=False):
+ """
+ Initialize, specifying a Process instance to connect to.
+ """
+ abstract.FileDescriptor.__init__(self, reactor)
+ fdesc.setNonBlocking(fileno)
+ self.proc = proc
+ self.name = name
+ self.fd = fileno
+
+ if forceReadHack:
+ self.enableReadHack = True
+ else:
+ # Detect if this fd is actually a write-only fd. If it's
+ # valid to read, don't try to detect closing via read.
+ # This really only means that we cannot detect a TTY's write
+ # pipe being closed.
+ try:
+ os.read(self.fileno(), 0)
+ except OSError:
+ # It's a write-only pipe end, enable hack
+ self.enableReadHack = True
+
+ if self.enableReadHack:
+ self.startReading()
+
+ def fileno(self):
+ """
+ Return the fileno() of my process's stdin.
+ """
+ return self.fd
+
+ def writeSomeData(self, data):
+ """
+ Write some data to the open process.
+ """
+ rv = fdesc.writeToFD(self.fd, data)
+ if rv == len(data) and self.enableReadHack:
+ self.startReading()
+ return rv
+
+ def write(self, data):
+ self.stopReading()
+ abstract.FileDescriptor.write(self, data)
+
+ def doRead(self):
+ """
+ The only way a write pipe can become "readable" is at EOF, because the
+ child has closed it, and we're using a reactor which doesn't
+ distinguish between readable and closed (such as the select reactor).
+
+ Except that's not true on linux < 2.6.11. It has the following
+ characteristics: write pipe is completely empty => POLLOUT (writable in
+ select), write pipe is not completely empty => POLLIN (readable in
+ select), write pipe's reader closed => POLLIN|POLLERR (readable and
+ writable in select)
+
+ That's what this funky code is for. If linux was not broken, this
+ function could be simply "return CONNECTION_LOST".
+
+ BUG: We call select no matter what the reactor.
+ If the reactor is pollreactor, and the fd is > 1024, this will fail.
+ (only occurs on broken versions of linux, though).
+ """
+ if self.enableReadHack:
+ if brokenLinuxPipeBehavior:
+ fd = self.fd
+ r, w, x = select.select([fd], [fd], [], 0)
+ if r and w:
+ return CONNECTION_LOST
+ else:
+ return CONNECTION_LOST
+ else:
+ self.stopReading()
+
+ def connectionLost(self, reason):
+ """
+ See abstract.FileDescriptor.connectionLost.
+ """
+ # At least on OS X 10.4, exiting while stdout is non-blocking can
+ # result in data loss. For some reason putting the file descriptor
+ # back into blocking mode seems to resolve this issue.
+ fdesc.setBlocking(self.fd)
+
+ abstract.FileDescriptor.connectionLost(self, reason)
+ self.proc.childConnectionLost(self.name, reason)
+
+
+
+class ProcessReader(abstract.FileDescriptor):
+ """
+ ProcessReader
+
+ I am a selectable representation of a process's output pipe, such as
+ stdout and stderr.
+ """
+ connected = 1
+
+ def __init__(self, reactor, proc, name, fileno):
+ """
+ Initialize, specifying a process to connect to.
+ """
+ abstract.FileDescriptor.__init__(self, reactor)
+ fdesc.setNonBlocking(fileno)
+ self.proc = proc
+ self.name = name
+ self.fd = fileno
+ self.startReading()
+
+ def fileno(self):
+ """
+ Return the fileno() of my process's stderr.
+ """
+ return self.fd
+
+ def writeSomeData(self, data):
+ # the only time this is actually called is after .loseConnection Any
+ # actual write attempt would fail, so we must avoid that. This hack
+ # allows us to use .loseConnection on both readers and writers.
+ assert data == ""
+ return CONNECTION_LOST
+
+ def doRead(self):
+ """
+ This is called when the pipe becomes readable.
+ """
+ return fdesc.readFromFD(self.fd, self.dataReceived)
+
+ def dataReceived(self, data):
+ self.proc.childDataReceived(self.name, data)
+
+ def loseConnection(self):
+ if self.connected and not self.disconnecting:
+ self.disconnecting = 1
+ self.stopReading()
+ self.reactor.callLater(0, self.connectionLost,
+ failure.Failure(CONNECTION_DONE))
+
+ def connectionLost(self, reason):
+ """
+ Close my end of the pipe, signal the Process (which signals the
+ ProcessProtocol).
+ """
+ abstract.FileDescriptor.connectionLost(self, reason)
+ self.proc.childConnectionLost(self.name, reason)
+
+
+class _BaseProcess(BaseProcess, object):
+ """
+ Base class for Process and PTYProcess.
+ """
+ status = None
+ pid = None
+
+ def reapProcess(self):
+ """
+ Try to reap a process (without blocking) via waitpid.
+
+ This is called when sigchild is caught or a Process object loses its
+ "connection" (stdout is closed) This ought to result in reaping all
+ zombie processes, since it will be called twice as often as it needs
+ to be.
+
+ (Unfortunately, this is a slightly experimental approach, since
+ UNIX has no way to be really sure that your process is going to
+ go away w/o blocking. I don't want to block.)
+ """
+ try:
+ try:
+ pid, status = os.waitpid(self.pid, os.WNOHANG)
+ except OSError, e:
+ if e.errno == errno.ECHILD:
+ # no child process
+ pid = None
+ else:
+ raise
+ except:
+ log.msg('Failed to reap %d:' % self.pid)
+ log.err()
+ pid = None
+ if pid:
+ self.processEnded(status)
+ unregisterReapProcessHandler(pid, self)
+
+
+ def _getReason(self, status):
+ exitCode = sig = None
+ if os.WIFEXITED(status):
+ exitCode = os.WEXITSTATUS(status)
+ else:
+ sig = os.WTERMSIG(status)
+ if exitCode or sig:
+ return error.ProcessTerminated(exitCode, sig, status)
+ return error.ProcessDone(status)
+
+
+ def signalProcess(self, signalID):
+ """
+ Send the given signal C{signalID} to the process. It'll translate a
+ few signals ('HUP', 'STOP', 'INT', 'KILL', 'TERM') from a string
+ representation to its int value, otherwise it'll pass directly the
+ value provided
+
+ @type signalID: C{str} or C{int}
+ """
+ if signalID in ('HUP', 'STOP', 'INT', 'KILL', 'TERM'):
+ signalID = getattr(signal, 'SIG%s' % (signalID,))
+ if self.pid is None:
+ raise ProcessExitedAlready()
+ os.kill(self.pid, signalID)
+
+
+ def _resetSignalDisposition(self):
+ # The Python interpreter ignores some signals, and our child
+ # process will inherit that behaviour. To have a child process
+ # that responds to signals normally, we need to reset our
+ # child process's signal handling (just) after we fork and
+ # before we execvpe.
+ for signalnum in range(1, signal.NSIG):
+ if signal.getsignal(signalnum) == signal.SIG_IGN:
+ # Reset signal handling to the default
+ signal.signal(signalnum, signal.SIG_DFL)
+
+
+ def _fork(self, path, uid, gid, executable, args, environment, **kwargs):
+ """
+ Fork and then exec sub-process.
+
+ @param path: the path where to run the new process.
+ @type path: C{str}
+ @param uid: if defined, the uid used to run the new process.
+ @type uid: C{int}
+ @param gid: if defined, the gid used to run the new process.
+ @type gid: C{int}
+ @param executable: the executable to run in a new process.
+ @type executable: C{str}
+ @param args: arguments used to create the new process.
+ @type args: C{list}.
+ @param environment: environment used for the new process.
+ @type environment: C{dict}.
+ @param kwargs: keyword arguments to L{_setupChild} method.
+ """
+ settingUID = (uid is not None) or (gid is not None)
+ if settingUID:
+ curegid = os.getegid()
+ currgid = os.getgid()
+ cureuid = os.geteuid()
+ curruid = os.getuid()
+ if uid is None:
+ uid = cureuid
+ if gid is None:
+ gid = curegid
+ # prepare to change UID in subprocess
+ os.setuid(0)
+ os.setgid(0)
+
+ collectorEnabled = gc.isenabled()
+ gc.disable()
+ try:
+ self.pid = os.fork()
+ except:
+ # Still in the parent process
+ if settingUID:
+ os.setregid(currgid, curegid)
+ os.setreuid(curruid, cureuid)
+ if collectorEnabled:
+ gc.enable()
+ raise
+ else:
+ if self.pid == 0: # pid is 0 in the child process
+ # do not put *ANY* code outside the try block. The child process
+ # must either exec or _exit. If it gets outside this block (due
+ # to an exception that is not handled here, but which might be
+ # handled higher up), there will be two copies of the parent
+ # running in parallel, doing all kinds of damage.
+
+ # After each change to this code, review it to make sure there
+ # are no exit paths.
+ try:
+ # Stop debugging. If I am, I don't care anymore.
+ sys.settrace(None)
+ self._setupChild(**kwargs)
+ self._execChild(path, settingUID, uid, gid,
+ executable, args, environment)
+ except:
+ # If there are errors, bail and try to write something
+ # descriptive to stderr.
+ # XXX: The parent's stderr isn't necessarily fd 2 anymore, or
+ # even still available
+ # XXXX: however even libc assumes write(2, err) is a useful
+ # thing to attempt
+ try:
+ stderr = os.fdopen(2, 'w')
+ stderr.write("Upon execvpe %s %s in environment %s\n:" %
+ (executable, str(args),
+ "id %s" % id(environment)))
+ traceback.print_exc(file=stderr)
+ stderr.flush()
+ for fd in range(3):
+ os.close(fd)
+ except:
+ pass # make *sure* the child terminates
+ # Did you read the comment about not adding code here?
+ os._exit(1)
+
+ # we are now in parent process
+ if settingUID:
+ os.setregid(currgid, curegid)
+ os.setreuid(curruid, cureuid)
+ if collectorEnabled:
+ gc.enable()
+ self.status = -1 # this records the exit status of the child
+
+ def _setupChild(self, *args, **kwargs):
+ """
+ Setup the child process. Override in subclasses.
+ """
+ raise NotImplementedError()
+
+ def _execChild(self, path, settingUID, uid, gid,
+ executable, args, environment):
+ """
+ The exec() which is done in the forked child.
+ """
+ if path:
+ os.chdir(path)
+ # set the UID before I actually exec the process
+ if settingUID:
+ switchUID(uid, gid)
+ os.execvpe(executable, args, environment)
+
+ def __repr__(self):
+ """
+ String representation of a process.
+ """
+ return "<%s pid=%s status=%s>" % (self.__class__.__name__,
+ self.pid, self.status)
+
+class Process(_BaseProcess):
+ """
+ An operating-system Process.
+
+ This represents an operating-system process with arbitrary input/output
+ pipes connected to it. Those pipes may represent standard input,
+ standard output, and standard error, or any other file descriptor.
+
+ On UNIX, this is implemented using fork(), exec(), pipe()
+ and fcntl(). These calls may not exist elsewhere so this
+ code is not cross-platform. (also, windows can only select
+ on sockets...)
+ """
+
+ debug = False
+ debug_child = False
+
+ status = -1
+ pid = None
+
+ processWriterFactory = ProcessWriter
+ processReaderFactory = ProcessReader
+
+ def __init__(self,
+ reactor, executable, args, environment, path, proto,
+ uid=None, gid=None, childFDs=None):
+ """
+ Spawn an operating-system process.
+
+ This is where the hard work of disconnecting all currently open
+ files / forking / executing the new process happens. (This is
+ executed automatically when a Process is instantiated.)
+
+ This will also run the subprocess as a given user ID and group ID, if
+ specified. (Implementation Note: this doesn't support all the arcane
+ nuances of setXXuid on UNIX: it will assume that either your effective
+ or real UID is 0.)
+ """
+ if not proto:
+ assert 'r' not in childFDs.values()
+ assert 'w' not in childFDs.values()
+ _BaseProcess.__init__(self, proto)
+
+ self.pipes = {}
+ # keys are childFDs, we can sense them closing
+ # values are ProcessReader/ProcessWriters
+
+ helpers = {}
+ # keys are childFDs
+ # values are parentFDs
+
+ if childFDs is None:
+ childFDs = {0: "w", # we write to the child's stdin
+ 1: "r", # we read from their stdout
+ 2: "r", # and we read from their stderr
+ }
+
+ debug = self.debug
+ if debug: print "childFDs", childFDs
+
+ _openedPipes = []
+ def pipe():
+ r, w = os.pipe()
+ _openedPipes.extend([r, w])
+ return r, w
+
+ # fdmap.keys() are filenos of pipes that are used by the child.
+ fdmap = {} # maps childFD to parentFD
+ try:
+ for childFD, target in childFDs.items():
+ if debug: print "[%d]" % childFD, target
+ if target == "r":
+ # we need a pipe that the parent can read from
+ readFD, writeFD = pipe()
+ if debug: print "readFD=%d, writeFD=%d" % (readFD, writeFD)
+ fdmap[childFD] = writeFD # child writes to this
+ helpers[childFD] = readFD # parent reads from this
+ elif target == "w":
+ # we need a pipe that the parent can write to
+ readFD, writeFD = pipe()
+ if debug: print "readFD=%d, writeFD=%d" % (readFD, writeFD)
+ fdmap[childFD] = readFD # child reads from this
+ helpers[childFD] = writeFD # parent writes to this
+ else:
+ assert type(target) == int, '%r should be an int' % (target,)
+ fdmap[childFD] = target # parent ignores this
+ if debug: print "fdmap", fdmap
+ if debug: print "helpers", helpers
+ # the child only cares about fdmap.values()
+
+ self._fork(path, uid, gid, executable, args, environment, fdmap=fdmap)
+ except:
+ map(os.close, _openedPipes)
+ raise
+
+ # we are the parent process:
+ self.proto = proto
+
+ # arrange for the parent-side pipes to be read and written
+ for childFD, parentFD in helpers.items():
+ os.close(fdmap[childFD])
+
+ if childFDs[childFD] == "r":
+ reader = self.processReaderFactory(reactor, self, childFD,
+ parentFD)
+ self.pipes[childFD] = reader
+
+ if childFDs[childFD] == "w":
+ writer = self.processWriterFactory(reactor, self, childFD,
+ parentFD, forceReadHack=True)
+ self.pipes[childFD] = writer
+
+ try:
+ # the 'transport' is used for some compatibility methods
+ if self.proto is not None:
+ self.proto.makeConnection(self)
+ except:
+ log.err()
+
+ # The reactor might not be running yet. This might call back into
+ # processEnded synchronously, triggering an application-visible
+ # callback. That's probably not ideal. The replacement API for
+ # spawnProcess should improve upon this situation.
+ registerReapProcessHandler(self.pid, self)
+
+
+ def _setupChild(self, fdmap):
+ """
+ fdmap[childFD] = parentFD
+
+ The child wants to end up with 'childFD' attached to what used to be
+ the parent's parentFD. As an example, a bash command run like
+ 'command 2>&1' would correspond to an fdmap of {0:0, 1:1, 2:1}.
+ 'command >foo.txt' would be {0:0, 1:os.open('foo.txt'), 2:2}.
+
+ This is accomplished in two steps::
+
+ 1. close all file descriptors that aren't values of fdmap. This
+ means 0 .. maxfds.
+
+ 2. for each childFD::
+
+ - if fdmap[childFD] == childFD, the descriptor is already in
+ place. Make sure the CLOEXEC flag is not set, then delete
+ the entry from fdmap.
+
+ - if childFD is in fdmap.values(), then the target descriptor
+ is busy. Use os.dup() to move it elsewhere, update all
+ fdmap[childFD] items that point to it, then close the
+ original. Then fall through to the next case.
+
+ - now fdmap[childFD] is not in fdmap.values(), and is free.
+ Use os.dup2() to move it to the right place, then close the
+ original.
+ """
+
+ debug = self.debug_child
+ if debug:
+ errfd = sys.stderr
+ errfd.write("starting _setupChild\n")
+
+ destList = fdmap.values()
+ try:
+ import resource
+ maxfds = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + 1
+ # OS-X reports 9223372036854775808. That's a lot of fds to close
+ if maxfds > 1024:
+ maxfds = 1024
+ except:
+ maxfds = 256
+
+ for fd in xrange(maxfds):
+ if fd in destList:
+ continue
+ if debug and fd == errfd.fileno():
+ continue
+ try:
+ os.close(fd)
+ except:
+ pass
+
+ # at this point, the only fds still open are the ones that need to
+ # be moved to their appropriate positions in the child (the targets
+ # of fdmap, i.e. fdmap.values() )
+
+ if debug: print >>errfd, "fdmap", fdmap
+ childlist = fdmap.keys()
+ childlist.sort()
+
+ for child in childlist:
+ target = fdmap[child]
+ if target == child:
+ # fd is already in place
+ if debug: print >>errfd, "%d already in place" % target
+ fdesc._unsetCloseOnExec(child)
+ else:
+ if child in fdmap.values():
+ # we can't replace child-fd yet, as some other mapping
+ # still needs the fd it wants to target. We must preserve
+ # that old fd by duping it to a new home.
+ newtarget = os.dup(child) # give it a safe home
+ if debug: print >>errfd, "os.dup(%d) -> %d" % (child,
+ newtarget)
+ os.close(child) # close the original
+ for c, p in fdmap.items():
+ if p == child:
+ fdmap[c] = newtarget # update all pointers
+ # now it should be available
+ if debug: print >>errfd, "os.dup2(%d,%d)" % (target, child)
+ os.dup2(target, child)
+
+ # At this point, the child has everything it needs. We want to close
+ # everything that isn't going to be used by the child, i.e.
+ # everything not in fdmap.keys(). The only remaining fds open are
+ # those in fdmap.values().
+
+ # Any given fd may appear in fdmap.values() multiple times, so we
+ # need to remove duplicates first.
+
+ old = []
+ for fd in fdmap.values():
+ if not fd in old:
+ if not fd in fdmap.keys():
+ old.append(fd)
+ if debug: print >>errfd, "old", old
+ for fd in old:
+ os.close(fd)
+
+ self._resetSignalDisposition()
+
+
+ def writeToChild(self, childFD, data):
+ self.pipes[childFD].write(data)
+
+ def closeChildFD(self, childFD):
+ # for writer pipes, loseConnection tries to write the remaining data
+ # out to the pipe before closing it
+ # if childFD is not in the list of pipes, assume that it is already
+ # closed
+ if childFD in self.pipes:
+ self.pipes[childFD].loseConnection()
+
+ def pauseProducing(self):
+ for p in self.pipes.itervalues():
+ if isinstance(p, ProcessReader):
+ p.stopReading()
+
+ def resumeProducing(self):
+ for p in self.pipes.itervalues():
+ if isinstance(p, ProcessReader):
+ p.startReading()
+
+ # compatibility
+ def closeStdin(self):
+ """
+ Call this to close standard input on this process.
+ """
+ self.closeChildFD(0)
+
+ def closeStdout(self):
+ self.closeChildFD(1)
+
+ def closeStderr(self):
+ self.closeChildFD(2)
+
+ def loseConnection(self):
+ self.closeStdin()
+ self.closeStderr()
+ self.closeStdout()
+
+ def write(self, data):
+ """
+ Call this to write to standard input on this process.
+
+ NOTE: This will silently lose data if there is no standard input.
+ """
+ if 0 in self.pipes:
+ self.pipes[0].write(data)
+
+ def registerProducer(self, producer, streaming):
+ """
+ Call this to register producer for standard input.
+
+ If there is no standard input producer.stopProducing() will
+ be called immediately.
+ """
+ if 0 in self.pipes:
+ self.pipes[0].registerProducer(producer, streaming)
+ else:
+ producer.stopProducing()
+
+ def unregisterProducer(self):
+ """
+ Call this to unregister producer for standard input."""
+ if 0 in self.pipes:
+ self.pipes[0].unregisterProducer()
+
+ def writeSequence(self, seq):
+ """
+ Call this to write to standard input on this process.
+
+ NOTE: This will silently lose data if there is no standard input.
+ """
+ if 0 in self.pipes:
+ self.pipes[0].writeSequence(seq)
+
+
+ def childDataReceived(self, name, data):
+ self.proto.childDataReceived(name, data)
+
+
+ def childConnectionLost(self, childFD, reason):
+ # this is called when one of the helpers (ProcessReader or
+ # ProcessWriter) notices their pipe has been closed
+ os.close(self.pipes[childFD].fileno())
+ del self.pipes[childFD]
+ try:
+ self.proto.childConnectionLost(childFD)
+ except:
+ log.err()
+ self.maybeCallProcessEnded()
+
+ def maybeCallProcessEnded(self):
+ # we don't call ProcessProtocol.processEnded until:
+ # the child has terminated, AND
+ # all writers have indicated an error status, AND
+ # all readers have indicated EOF
+ # This insures that we've gathered all output from the process.
+ if self.pipes:
+ return
+ if not self.lostProcess:
+ self.reapProcess()
+ return
+ _BaseProcess.maybeCallProcessEnded(self)
+
+
+class PTYProcess(abstract.FileDescriptor, _BaseProcess):
+ """
+ An operating-system Process that uses PTY support.
+ """
+ status = -1
+ pid = None
+
+ def __init__(self, reactor, executable, args, environment, path, proto,
+ uid=None, gid=None, usePTY=None):
+ """
+ Spawn an operating-system process.
+
+ This is where the hard work of disconnecting all currently open
+ files / forking / executing the new process happens. (This is
+ executed automatically when a Process is instantiated.)
+
+ This will also run the subprocess as a given user ID and group ID, if
+ specified. (Implementation Note: this doesn't support all the arcane
+ nuances of setXXuid on UNIX: it will assume that either your effective
+ or real UID is 0.)
+ """
+ if pty is None and not isinstance(usePTY, (tuple, list)):
+ # no pty module and we didn't get a pty to use
+ raise NotImplementedError(
+ "cannot use PTYProcess on platforms without the pty module.")
+ abstract.FileDescriptor.__init__(self, reactor)
+ _BaseProcess.__init__(self, proto)
+
+ if isinstance(usePTY, (tuple, list)):
+ masterfd, slavefd, ttyname = usePTY
+ else:
+ masterfd, slavefd = pty.openpty()
+ ttyname = os.ttyname(slavefd)
+
+ try:
+ self._fork(path, uid, gid, executable, args, environment,
+ masterfd=masterfd, slavefd=slavefd)
+ except:
+ if not isinstance(usePTY, (tuple, list)):
+ os.close(masterfd)
+ os.close(slavefd)
+ raise
+
+ # we are now in parent process:
+ os.close(slavefd)
+ fdesc.setNonBlocking(masterfd)
+ self.fd = masterfd
+ self.startReading()
+ self.connected = 1
+ self.status = -1
+ try:
+ self.proto.makeConnection(self)
+ except:
+ log.err()
+ registerReapProcessHandler(self.pid, self)
+
+ def _setupChild(self, masterfd, slavefd):
+ """
+ Setup child process after fork() but before exec().
+ """
+ os.close(masterfd)
+ if hasattr(termios, 'TIOCNOTTY'):
+ try:
+ fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
+ except OSError:
+ pass
+ else:
+ try:
+ fcntl.ioctl(fd, termios.TIOCNOTTY, '')
+ except:
+ pass
+ os.close(fd)
+
+ os.setsid()
+
+ if hasattr(termios, 'TIOCSCTTY'):
+ fcntl.ioctl(slavefd, termios.TIOCSCTTY, '')
+
+ for fd in range(3):
+ if fd != slavefd:
+ os.close(fd)
+
+ os.dup2(slavefd, 0) # stdin
+ os.dup2(slavefd, 1) # stdout
+ os.dup2(slavefd, 2) # stderr
+
+ for fd in xrange(3, 256):
+ try:
+ os.close(fd)
+ except:
+ pass
+
+ self._resetSignalDisposition()
+
+
+ # PTYs do not have stdin/stdout/stderr. They only have in and out, just
+ # like sockets. You cannot close one without closing off the entire PTY.
+ def closeStdin(self):
+ pass
+
+ def closeStdout(self):
+ pass
+
+ def closeStderr(self):
+ pass
+
+ def doRead(self):
+ """
+ Called when my standard output stream is ready for reading.
+ """
+ return fdesc.readFromFD(
+ self.fd,
+ lambda data: self.proto.childDataReceived(1, data))
+
+ def fileno(self):
+ """
+ This returns the file number of standard output on this process.
+ """
+ return self.fd
+
+ def maybeCallProcessEnded(self):
+ # two things must happen before we call the ProcessProtocol's
+ # processEnded method. 1: the child process must die and be reaped
+ # (which calls our own processEnded method). 2: the child must close
+ # their stdin/stdout/stderr fds, causing the pty to close, causing
+ # our connectionLost method to be called. #2 can also be triggered
+ # by calling .loseConnection().
+ if self.lostProcess == 2:
+ _BaseProcess.maybeCallProcessEnded(self)
+
+ def connectionLost(self, reason):
+ """
+ I call this to clean up when one or all of my connections has died.
+ """
+ abstract.FileDescriptor.connectionLost(self, reason)
+ os.close(self.fd)
+ self.lostProcess += 1
+ self.maybeCallProcessEnded()
+
+ def writeSomeData(self, data):
+ """
+ Write some data to the open process.
+ """
+ return fdesc.writeToFD(self.fd, data)
diff --git a/vendor/Twisted-10.0.0/twisted/internet/protocol.py b/vendor/Twisted-10.0.0/twisted/internet/protocol.py
new file mode 100644
index 0000000000..453c9ee861
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/protocol.py
@@ -0,0 +1,699 @@
+# -*- test-case-name: twisted.test.test_factories -*-
+#
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Standard implementations of Twisted protocol-related interfaces.
+
+Start here if you are looking to write a new protocol implementation for
+Twisted. The Protocol class contains some introductory material.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+import random
+from zope.interface import implements
+
+# Twisted Imports
+from twisted.python import log, failure, components
+from twisted.internet import interfaces, error, defer
+
+
+class Factory:
+ """This is a factory which produces protocols.
+
+ By default, buildProtocol will create a protocol of the class given in
+ self.protocol.
+ """
+
+ implements(interfaces.IProtocolFactory)
+
+ # put a subclass of Protocol here:
+ protocol = None
+
+ numPorts = 0
+ noisy = True
+
+ def doStart(self):
+ """Make sure startFactory is called.
+
+ Users should not call this function themselves!
+ """
+ if not self.numPorts:
+ if self.noisy:
+ log.msg("Starting factory %r" % self)
+ self.startFactory()
+ self.numPorts = self.numPorts + 1
+
+ def doStop(self):
+ """Make sure stopFactory is called.
+
+ Users should not call this function themselves!
+ """
+ if self.numPorts == 0:
+ # this shouldn't happen, but does sometimes and this is better
+ # than blowing up in assert as we did previously.
+ return
+ self.numPorts = self.numPorts - 1
+ if not self.numPorts:
+ if self.noisy:
+ log.msg("Stopping factory %r" % self)
+ self.stopFactory()
+
+ def startFactory(self):
+ """This will be called before I begin listening on a Port or Connector.
+
+ It will only be called once, even if the factory is connected
+ to multiple ports.
+
+ This can be used to perform 'unserialization' tasks that
+ are best put off until things are actually running, such
+ as connecting to a database, opening files, etcetera.
+ """
+
+ def stopFactory(self):
+ """This will be called before I stop listening on all Ports/Connectors.
+
+ This can be overridden to perform 'shutdown' tasks such as disconnecting
+ database connections, closing files, etc.
+
+ It will be called, for example, before an application shuts down,
+ if it was connected to a port. User code should not call this function
+ directly.
+ """
+
+ def buildProtocol(self, addr):
+ """Create an instance of a subclass of Protocol.
+
+ The returned instance will handle input on an incoming server
+ connection, and an attribute \"factory\" pointing to the creating
+ factory.
+
+ Override this method to alter how Protocol instances get created.
+
+ @param addr: an object implementing L{twisted.internet.interfaces.IAddress}
+ """
+ p = self.protocol()
+ p.factory = self
+ return p
+
+
+class ClientFactory(Factory):
+ """A Protocol factory for clients.
+
+ This can be used together with the various connectXXX methods in
+ reactors.
+ """
+
+ def startedConnecting(self, connector):
+ """Called when a connection has been started.
+
+ You can call connector.stopConnecting() to stop the connection attempt.
+
+ @param connector: a Connector object.
+ """
+
+ def clientConnectionFailed(self, connector, reason):
+ """Called when a connection has failed to connect.
+
+ It may be useful to call connector.connect() - this will reconnect.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+
+ def clientConnectionLost(self, connector, reason):
+ """Called when an established connection is lost.
+
+ It may be useful to call connector.connect() - this will reconnect.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+
+
+class _InstanceFactory(ClientFactory):
+ """Factory used by ClientCreator."""
+
+ noisy = False
+
+ def __init__(self, reactor, instance, deferred):
+ self.reactor = reactor
+ self.instance = instance
+ self.deferred = deferred
+
+ def __repr__(self):
+ return "<ClientCreator factory: %r>" % (self.instance, )
+
+ def buildProtocol(self, addr):
+ self.reactor.callLater(0, self.deferred.callback, self.instance)
+ del self.deferred
+ return self.instance
+
+ def clientConnectionFailed(self, connector, reason):
+ self.reactor.callLater(0, self.deferred.errback, reason)
+ del self.deferred
+
+
+class ClientCreator:
+ """Client connections that do not require a factory.
+
+ The various connect* methods create a protocol instance using the given
+ protocol class and arguments, and connect it, returning a Deferred of the
+ resulting protocol instance.
+
+ Useful for cases when we don't really need a factory. Mainly this
+ is when there is no shared state between protocol instances, and no need
+ to reconnect.
+ """
+
+ def __init__(self, reactor, protocolClass, *args, **kwargs):
+ self.reactor = reactor
+ self.protocolClass = protocolClass
+ self.args = args
+ self.kwargs = kwargs
+
+ def connectTCP(self, host, port, timeout=30, bindAddress=None):
+ """Connect to remote host, return Deferred of resulting protocol instance."""
+ d = defer.Deferred()
+ f = _InstanceFactory(self.reactor, self.protocolClass(*self.args, **self.kwargs), d)
+ self.reactor.connectTCP(host, port, f, timeout=timeout, bindAddress=bindAddress)
+ return d
+
+ def connectUNIX(self, address, timeout = 30, checkPID=0):
+ """Connect to Unix socket, return Deferred of resulting protocol instance."""
+ d = defer.Deferred()
+ f = _InstanceFactory(self.reactor, self.protocolClass(*self.args, **self.kwargs), d)
+ self.reactor.connectUNIX(address, f, timeout = timeout, checkPID=checkPID)
+ return d
+
+ def connectSSL(self, host, port, contextFactory, timeout=30, bindAddress=None):
+ """Connect to SSL server, return Deferred of resulting protocol instance."""
+ d = defer.Deferred()
+ f = _InstanceFactory(self.reactor, self.protocolClass(*self.args, **self.kwargs), d)
+ self.reactor.connectSSL(host, port, f, contextFactory, timeout=timeout, bindAddress=bindAddress)
+ return d
+
+
+class ReconnectingClientFactory(ClientFactory):
+ """
+ Factory which auto-reconnects clients with an exponential back-off.
+
+ Note that clients should call my resetDelay method after they have
+ connected successfully.
+
+ @ivar maxDelay: Maximum number of seconds between connection attempts.
+ @ivar initialDelay: Delay for the first reconnection attempt.
+ @ivar factor: a multiplicitive factor by which the delay grows
+ @ivar jitter: percentage of randomness to introduce into the delay length
+ to prevent stampeding.
+ @ivar clock: the clock used to schedule reconnection. It's mainly useful to
+ be parametrized in tests. If the factory is serialized, this attribute
+ will not be serialized, and the default value (the reactor) will be
+ restored when deserialized.
+ """
+ maxDelay = 3600
+ initialDelay = 1.0
+ # Note: These highly sensitive factors have been precisely measured by
+ # the National Institute of Science and Technology. Take extreme care
+ # in altering them, or you may damage your Internet!
+ # (Seriously: <http://physics.nist.gov/cuu/Constants/index.html>)
+ factor = 2.7182818284590451 # (math.e)
+ # Phi = 1.6180339887498948 # (Phi is acceptable for use as a
+ # factor if e is too large for your application.)
+ jitter = 0.11962656472 # molar Planck constant times c, joule meter/mole
+
+ delay = initialDelay
+ retries = 0
+ maxRetries = None
+ _callID = None
+ connector = None
+ clock = None
+
+ continueTrying = 1
+
+
+ def clientConnectionFailed(self, connector, reason):
+ if self.continueTrying:
+ self.connector = connector
+ self.retry()
+
+
+ def clientConnectionLost(self, connector, unused_reason):
+ if self.continueTrying:
+ self.connector = connector
+ self.retry()
+
+
+ def retry(self, connector=None):
+ """
+ Have this connector connect again, after a suitable delay.
+ """
+ if not self.continueTrying:
+ if self.noisy:
+ log.msg("Abandoning %s on explicit request" % (connector,))
+ return
+
+ if connector is None:
+ if self.connector is None:
+ raise ValueError("no connector to retry")
+ else:
+ connector = self.connector
+
+ self.retries += 1
+ if self.maxRetries is not None and (self.retries > self.maxRetries):
+ if self.noisy:
+ log.msg("Abandoning %s after %d retries." %
+ (connector, self.retries))
+ return
+
+ self.delay = min(self.delay * self.factor, self.maxDelay)
+ if self.jitter:
+ self.delay = random.normalvariate(self.delay,
+ self.delay * self.jitter)
+
+ if self.noisy:
+ log.msg("%s will retry in %d seconds" % (connector, self.delay,))
+
+ def reconnector():
+ self._callID = None
+ connector.connect()
+ if self.clock is None:
+ from twisted.internet import reactor
+ self.clock = reactor
+ self._callID = self.clock.callLater(self.delay, reconnector)
+
+
+ def stopTrying(self):
+ """
+ Put a stop to any attempt to reconnect in progress.
+ """
+ # ??? Is this function really stopFactory?
+ if self._callID:
+ self._callID.cancel()
+ self._callID = None
+ if self.connector:
+ # Hopefully this doesn't just make clientConnectionFailed
+ # retry again.
+ try:
+ self.connector.stopConnecting()
+ except error.NotConnectingError:
+ pass
+ self.continueTrying = 0
+
+
+ def resetDelay(self):
+ """
+ Call this method after a successful connection: it resets the delay and
+ the retry counter.
+ """
+ self.delay = self.initialDelay
+ self.retries = 0
+ self._callID = None
+ self.continueTrying = 1
+
+
+ def __getstate__(self):
+ """
+ Remove all of the state which is mutated by connection attempts and
+ failures, returning just the state which describes how reconnections
+ should be attempted. This will make the unserialized instance
+ behave just as this one did when it was first instantiated.
+ """
+ state = self.__dict__.copy()
+ for key in ['connector', 'retries', 'delay',
+ 'continueTrying', '_callID', 'clock']:
+ if key in state:
+ del state[key]
+ return state
+
+
+
+class ServerFactory(Factory):
+ """Subclass this to indicate that your protocol.Factory is only usable for servers.
+ """
+
+
+class BaseProtocol:
+ """This is the abstract superclass of all protocols.
+
+ If you are going to write a new protocol for Twisted, start here. The
+ docstrings of this class explain how you can get started. Any protocol
+ implementation, either client or server, should be a subclass of me.
+
+ My API is quite simple. Implement dataReceived(data) to handle both
+ event-based and synchronous input; output can be sent through the
+ 'transport' attribute, which is to be an instance that implements
+ L{twisted.internet.interfaces.ITransport}.
+
+ Some subclasses exist already to help you write common types of protocols:
+ see the L{twisted.protocols.basic} module for a few of them.
+ """
+
+ connected = 0
+ transport = None
+
+ def makeConnection(self, transport):
+ """Make a connection to a transport and a server.
+
+ This sets the 'transport' attribute of this Protocol, and calls the
+ connectionMade() callback.
+ """
+ self.connected = 1
+ self.transport = transport
+ self.connectionMade()
+
+ def connectionMade(self):
+ """Called when a connection is made.
+
+ This may be considered the initializer of the protocol, because
+ it is called when the connection is completed. For clients,
+ this is called once the connection to the server has been
+ established; for servers, this is called after an accept() call
+ stops blocking and a socket has been received. If you need to
+ send any greeting or initial message, do it here.
+ """
+
+connectionDone=failure.Failure(error.ConnectionDone())
+connectionDone.cleanFailure()
+
+
+class Protocol(BaseProtocol):
+
+ implements(interfaces.IProtocol)
+
+ def dataReceived(self, data):
+ """Called whenever data is received.
+
+ Use this method to translate to a higher-level message. Usually, some
+ callback will be made upon the receipt of each complete protocol
+ message.
+
+ @param data: a string of indeterminate length. Please keep in mind
+ that you will probably need to buffer some data, as partial
+ (or multiple) protocol messages may be received! I recommend
+ that unit tests for protocols call through to this method with
+ differing chunk sizes, down to one byte at a time.
+ """
+
+ def connectionLost(self, reason=connectionDone):
+ """Called when the connection is shut down.
+
+ Clear any circular references here, and any external references
+ to this Protocol. The connection has been closed.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+
+
+class ProtocolToConsumerAdapter(components.Adapter):
+ implements(interfaces.IConsumer)
+
+ def write(self, data):
+ self.original.dataReceived(data)
+
+ def registerProducer(self, producer, streaming):
+ pass
+
+ def unregisterProducer(self):
+ pass
+
+components.registerAdapter(ProtocolToConsumerAdapter, interfaces.IProtocol,
+ interfaces.IConsumer)
+
+class ConsumerToProtocolAdapter(components.Adapter):
+ implements(interfaces.IProtocol)
+
+ def dataReceived(self, data):
+ self.original.write(data)
+
+ def connectionLost(self, reason):
+ pass
+
+ def makeConnection(self, transport):
+ pass
+
+ def connectionMade(self):
+ pass
+
+components.registerAdapter(ConsumerToProtocolAdapter, interfaces.IConsumer,
+ interfaces.IProtocol)
+
+class ProcessProtocol(BaseProtocol):
+ """
+ Base process protocol implementation which does simple dispatching for
+ stdin, stdout, and stderr file descriptors.
+ """
+ implements(interfaces.IProcessProtocol)
+
+ def childDataReceived(self, childFD, data):
+ if childFD == 1:
+ self.outReceived(data)
+ elif childFD == 2:
+ self.errReceived(data)
+
+
+ def outReceived(self, data):
+ """
+ Some data was received from stdout.
+ """
+
+
+ def errReceived(self, data):
+ """
+ Some data was received from stderr.
+ """
+
+
+ def childConnectionLost(self, childFD):
+ if childFD == 0:
+ self.inConnectionLost()
+ elif childFD == 1:
+ self.outConnectionLost()
+ elif childFD == 2:
+ self.errConnectionLost()
+
+
+ def inConnectionLost(self):
+ """
+ This will be called when stdin is closed.
+ """
+
+
+ def outConnectionLost(self):
+ """
+ This will be called when stdout is closed.
+ """
+
+
+ def errConnectionLost(self):
+ """
+ This will be called when stderr is closed.
+ """
+
+
+ def processExited(self, reason):
+ """
+ This will be called when the subprocess exits.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+
+
+ def processEnded(self, reason):
+ """
+ This will be called when the subprocess is finished.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+
+
+
+class AbstractDatagramProtocol:
+ """
+ Abstract protocol for datagram-oriented transports, e.g. IP, ICMP, ARP, UDP.
+ """
+
+ transport = None
+ numPorts = 0
+ noisy = True
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ d['transport'] = None
+ return d
+
+ def doStart(self):
+ """Make sure startProtocol is called.
+
+ This will be called by makeConnection(), users should not call it.
+ """
+ if not self.numPorts:
+ if self.noisy:
+ log.msg("Starting protocol %s" % self)
+ self.startProtocol()
+ self.numPorts = self.numPorts + 1
+
+ def doStop(self):
+ """Make sure stopProtocol is called.
+
+ This will be called by the port, users should not call it.
+ """
+ assert self.numPorts > 0
+ self.numPorts = self.numPorts - 1
+ self.transport = None
+ if not self.numPorts:
+ if self.noisy:
+ log.msg("Stopping protocol %s" % self)
+ self.stopProtocol()
+
+ def startProtocol(self):
+ """Called when a transport is connected to this protocol.
+
+ Will only be called once, even if multiple ports are connected.
+ """
+
+ def stopProtocol(self):
+ """Called when the transport is disconnected.
+
+ Will only be called once, after all ports are disconnected.
+ """
+
+ def makeConnection(self, transport):
+ """Make a connection to a transport and a server.
+
+ This sets the 'transport' attribute of this DatagramProtocol, and calls the
+ doStart() callback.
+ """
+ assert self.transport == None
+ self.transport = transport
+ self.doStart()
+
+ def datagramReceived(self, datagram, addr):
+ """Called when a datagram is received.
+
+ @param datagram: the string received from the transport.
+ @param addr: tuple of source of datagram.
+ """
+
+
+class DatagramProtocol(AbstractDatagramProtocol):
+ """
+ Protocol for datagram-oriented transport, e.g. UDP.
+
+ @type transport: C{NoneType} or
+ L{IUDPTransport<twisted.internet.interfaces.IUDPTransport>} provider
+ @ivar transport: The transport with which this protocol is associated,
+ if it is associated with one.
+ """
+
+ def connectionRefused(self):
+ """Called due to error from write in connected mode.
+
+ Note this is a result of ICMP message generated by *previous*
+ write.
+ """
+
+
+class ConnectedDatagramProtocol(DatagramProtocol):
+ """Protocol for connected datagram-oriented transport.
+
+ No longer necessary for UDP.
+ """
+
+ def datagramReceived(self, datagram):
+ """Called when a datagram is received.
+
+ @param datagram: the string received from the transport.
+ """
+
+ def connectionFailed(self, failure):
+ """Called if connecting failed.
+
+ Usually this will be due to a DNS lookup failure.
+ """
+
+
+
+class FileWrapper:
+ """A wrapper around a file-like object to make it behave as a Transport.
+
+ This doesn't actually stream the file to the attached protocol,
+ and is thus useful mainly as a utility for debugging protocols.
+ """
+
+ implements(interfaces.ITransport)
+
+ closed = 0
+ disconnecting = 0
+ producer = None
+ streamingProducer = 0
+
+ def __init__(self, file):
+ self.file = file
+
+ def write(self, data):
+ try:
+ self.file.write(data)
+ except:
+ self.handleException()
+ # self._checkProducer()
+
+ def _checkProducer(self):
+ # Cheating; this is called at "idle" times to allow producers to be
+ # found and dealt with
+ if self.producer:
+ self.producer.resumeProducing()
+
+ def registerProducer(self, producer, streaming):
+ """From abstract.FileDescriptor
+ """
+ self.producer = producer
+ self.streamingProducer = streaming
+ if not streaming:
+ producer.resumeProducing()
+
+ def unregisterProducer(self):
+ self.producer = None
+
+ def stopConsuming(self):
+ self.unregisterProducer()
+ self.loseConnection()
+
+ def writeSequence(self, iovec):
+ self.write("".join(iovec))
+
+ def loseConnection(self):
+ self.closed = 1
+ try:
+ self.file.close()
+ except (IOError, OSError):
+ self.handleException()
+
+ def getPeer(self):
+ # XXX: According to ITransport, this should return an IAddress!
+ return 'file', 'file'
+
+ def getHost(self):
+ # XXX: According to ITransport, this should return an IAddress!
+ return 'file'
+
+ def handleException(self):
+ pass
+
+ def resumeProducing(self):
+ # Never sends data anyways
+ pass
+
+ def pauseProducing(self):
+ # Never sends data anyways
+ pass
+
+ def stopProducing(self):
+ self.loseConnection()
+
+
+__all__ = ["Factory", "ClientFactory", "ReconnectingClientFactory", "connectionDone",
+ "Protocol", "ProcessProtocol", "FileWrapper", "ServerFactory",
+ "AbstractDatagramProtocol", "DatagramProtocol", "ConnectedDatagramProtocol",
+ "ClientCreator"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/pyuisupport.py b/vendor/Twisted-10.0.0/twisted/internet/pyuisupport.py
new file mode 100644
index 0000000000..cdcf5a2add
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/pyuisupport.py
@@ -0,0 +1,37 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+This module integrates PyUI with twisted.internet's mainloop.
+
+Maintainer: Jp Calderone
+
+See doc/examples/pyuidemo.py for example usage.
+"""
+
+# System imports
+import pyui
+
+def _guiUpdate(reactor, delay):
+ pyui.draw()
+ if pyui.update() == 0:
+ pyui.quit()
+ reactor.stop()
+ else:
+ reactor.callLater(delay, _guiUpdate, reactor, delay)
+
+
+def install(ms=10, reactor=None, args=(), kw={}):
+ """
+ Schedule PyUI's display to be updated approximately every C{ms}
+ milliseconds, and initialize PyUI with the specified arguments.
+ """
+ d = pyui.init(*args, **kw)
+
+ if reactor is None:
+ from twisted.internet import reactor
+ _guiUpdate(reactor, ms / 1000.0)
+ return d
+
+__all__ = ["install"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/qtreactor.py b/vendor/Twisted-10.0.0/twisted/internet/qtreactor.py
new file mode 100644
index 0000000000..abbd3baa73
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/qtreactor.py
@@ -0,0 +1,19 @@
+# -*- test-case-name: twisted.internet.test.test_qtreactor -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+try:
+ # 'import qtreactor' would have imported this file instead of the
+ # top-level qtreactor. __import__ does the right thing
+ # (kids, don't repeat this at home)
+ install = __import__('qtreactor').install
+except ImportError:
+ from twisted.plugins.twisted_qtstub import errorMessage
+ raise ImportError(errorMessage)
+else:
+ import warnings
+ warnings.warn("Please use qtreactor instead of twisted.internet.qtreactor",
+ category=DeprecationWarning)
+
+__all__ = ['install']
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/reactor.py b/vendor/Twisted-10.0.0/twisted/internet/reactor.py
new file mode 100644
index 0000000000..08d19bd2da
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/reactor.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+The reactor is the Twisted event loop within Twisted, the loop which drives
+applications using Twisted. The reactor provides APIs for networking,
+threading, dispatching events, and more.
+
+The default reactor is based on C{select(2)} and will be installed if this
+module is imported without another reactor being explicitly installed.
+Regardless of which reactor is installed, importing this module is the correct
+way to get a reference to it.
+
+New application code should prefer to pass and accept the reactor as a
+parameter where it is needed, rather than relying on being able to import this
+module to get a reference. This simplifies unit testing and may make it easier
+to one day support multiple reactors (as a performance enhancement), though
+this is not currently possible.
+
+@see: L{IReactorCore<twisted.internet.interfaces.IReactorCore>}
+@see: L{IReactorTime<twisted.internet.interfaces.IReactorTime>}
+@see: L{IReactorProcess<twisted.internet.interfaces.IReactorProcess>}
+@see: L{IReactorTCP<twisted.internet.interfaces.IReactorTCP>}
+@see: L{IReactorSSL<twisted.internet.interfaces.IReactorSSL>}
+@see: L{IReactorUDP<twisted.internet.interfaces.IReactorUDP>}
+@see: L{IReactorMulticast<twisted.internet.interfaces.IReactorMulticast>}
+@see: L{IReactorUNIX<twisted.internet.interfaces.IReactorUNIX>}
+@see: L{IReactorUNIXDatagram<twisted.internet.interfaces.IReactorUNIXDatagram>}
+@see: L{IReactorFDSet<twisted.internet.interfaces.IReactorFDSet>}
+@see: L{IReactorThreads<twisted.internet.interfaces.IReactorThreads>}
+@see: L{IReactorArbitrary<twisted.internet.interfaces.IReactorArbitrary>}
+@see: L{IReactorPluggableResolver<twisted.internet.interfaces.IReactorPluggableResolver>}
+"""
+
+import sys
+del sys.modules['twisted.internet.reactor']
+from twisted.internet import selectreactor
+selectreactor.install()
diff --git a/vendor/Twisted-10.0.0/twisted/internet/selectreactor.py b/vendor/Twisted-10.0.0/twisted/internet/selectreactor.py
new file mode 100644
index 0000000000..ccef9a580b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/selectreactor.py
@@ -0,0 +1,204 @@
+# -*- test-case-name: twisted.test.test_internet -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Select reactor
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+from time import sleep
+import sys
+import select
+from errno import EINTR, EBADF
+
+from zope.interface import implements
+
+from twisted.internet.interfaces import IReactorFDSet
+from twisted.internet import error
+from twisted.internet import posixbase
+from twisted.python import log
+from twisted.python.runtime import platformType
+
+
+def win32select(r, w, e, timeout=None):
+ """Win32 select wrapper."""
+ if not (r or w):
+ # windows select() exits immediately when no sockets
+ if timeout is None:
+ timeout = 0.01
+ else:
+ timeout = min(timeout, 0.001)
+ sleep(timeout)
+ return [], [], []
+ # windows doesn't process 'signals' inside select(), so we set a max
+ # time or ctrl-c will never be recognized
+ if timeout is None or timeout > 0.5:
+ timeout = 0.5
+ r, w, e = select.select(r, w, w, timeout)
+ return r, w + e, []
+
+if platformType == "win32":
+ _select = win32select
+else:
+ _select = select.select
+
+# Exceptions that doSelect might return frequently
+_NO_FILENO = error.ConnectionFdescWentAway('Handler has no fileno method')
+_NO_FILEDESC = error.ConnectionFdescWentAway('Filedescriptor went away')
+
+class SelectReactor(posixbase.PosixReactorBase):
+ """
+ A select() based reactor - runs on all POSIX platforms and on Win32.
+
+ @ivar _reads: A dictionary mapping L{FileDescriptor} instances to arbitrary
+ values (this is essentially a set). Keys in this dictionary will be
+ checked for read events.
+
+ @ivar _writes: A dictionary mapping L{FileDescriptor} instances to
+ arbitrary values (this is essentially a set). Keys in this dictionary
+ will be checked for writability.
+ """
+ implements(IReactorFDSet)
+
+ def __init__(self):
+ """
+ Initialize file descriptor tracking dictionaries and the base class.
+ """
+ self._reads = {}
+ self._writes = {}
+ posixbase.PosixReactorBase.__init__(self)
+
+
+ def _preenDescriptors(self):
+ log.msg("Malformed file descriptor found. Preening lists.")
+ readers = self._reads.keys()
+ writers = self._writes.keys()
+ self._reads.clear()
+ self._writes.clear()
+ for selDict, selList in ((self._reads, readers),
+ (self._writes, writers)):
+ for selectable in selList:
+ try:
+ select.select([selectable], [selectable], [selectable], 0)
+ except Exception, e:
+ log.msg("bad descriptor %s" % selectable)
+ self._disconnectSelectable(selectable, e, False)
+ else:
+ selDict[selectable] = 1
+
+
+ def doSelect(self, timeout):
+ """
+ Run one iteration of the I/O monitor loop.
+
+ This will run all selectables who had input or output readiness
+ waiting for them.
+ """
+ while 1:
+ try:
+ r, w, ignored = _select(self._reads.keys(),
+ self._writes.keys(),
+ [], timeout)
+ break
+ except ValueError, ve:
+ # Possibly a file descriptor has gone negative?
+ log.err()
+ self._preenDescriptors()
+ except TypeError, te:
+ # Something *totally* invalid (object w/o fileno, non-integral
+ # result) was passed
+ log.err()
+ self._preenDescriptors()
+ except (select.error, IOError), se:
+ # select(2) encountered an error
+ if se.args[0] in (0, 2):
+ # windows does this if it got an empty list
+ if (not self._reads) and (not self._writes):
+ return
+ else:
+ raise
+ elif se.args[0] == EINTR:
+ return
+ elif se.args[0] == EBADF:
+ self._preenDescriptors()
+ else:
+ # OK, I really don't know what's going on. Blow up.
+ raise
+ _drdw = self._doReadOrWrite
+ _logrun = log.callWithLogger
+ for selectables, method, fdset in ((r, "doRead", self._reads),
+ (w,"doWrite", self._writes)):
+ for selectable in selectables:
+ # if this was disconnected in another thread, kill it.
+ # ^^^^ --- what the !@#*? serious! -exarkun
+ if selectable not in fdset:
+ continue
+ # This for pausing input when we're not ready for more.
+ _logrun(selectable, _drdw, selectable, method, dict)
+
+ doIteration = doSelect
+
+ def _doReadOrWrite(self, selectable, method, dict):
+ try:
+ why = getattr(selectable, method)()
+ handfn = getattr(selectable, 'fileno', None)
+ if not handfn:
+ why = _NO_FILENO
+ elif handfn() == -1:
+ why = _NO_FILEDESC
+ except:
+ why = sys.exc_info()[1]
+ log.err()
+ if why:
+ self._disconnectSelectable(selectable, why, method=="doRead")
+
+ def addReader(self, reader):
+ """
+ Add a FileDescriptor for notification of data available to read.
+ """
+ self._reads[reader] = 1
+
+ def addWriter(self, writer):
+ """
+ Add a FileDescriptor for notification of data available to write.
+ """
+ self._writes[writer] = 1
+
+ def removeReader(self, reader):
+ """
+ Remove a Selectable for notification of data available to read.
+ """
+ if reader in self._reads:
+ del self._reads[reader]
+
+ def removeWriter(self, writer):
+ """
+ Remove a Selectable for notification of data available to write.
+ """
+ if writer in self._writes:
+ del self._writes[writer]
+
+ def removeAll(self):
+ return self._removeAll(self._reads, self._writes)
+
+
+ def getReaders(self):
+ return self._reads.keys()
+
+
+ def getWriters(self):
+ return self._writes.keys()
+
+
+
+def install():
+ """Configure the twisted mainloop to be run using the select() reactor.
+ """
+ reactor = SelectReactor()
+ from twisted.internet.main import installReactor
+ installReactor(reactor)
+
+__all__ = ['install']
diff --git a/vendor/Twisted-10.0.0/twisted/internet/serialport.py b/vendor/Twisted-10.0.0/twisted/internet/serialport.py
new file mode 100644
index 0000000000..6ac58ae0ce
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/serialport.py
@@ -0,0 +1,65 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Serial Port Protocol
+"""
+
+# system imports
+import os, sys
+
+# all of them require pyserial at the moment, so check that first
+import serial
+from serial import PARITY_NONE, PARITY_EVEN, PARITY_ODD
+from serial import STOPBITS_ONE, STOPBITS_TWO
+from serial import FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS
+
+# common code for serial ports
+class BaseSerialPort:
+ def setBaudRate(self, baudrate):
+ if hasattr(self._serial, "setBaudrate"):
+ self._serial.setBaudrate(baudrate)
+ else:
+ self._serial.setBaudRate(baudrate)
+
+ def inWaiting(self):
+ return self._serial.inWaiting()
+
+ def flushInput(self):
+ self._serial.flushInput()
+
+ def flushOutput(self):
+ self._serial.flushOutput()
+
+ def sendBreak(self):
+ self._serial.sendBreak()
+
+ def getDSR(self):
+ return self._serial.getDSR()
+
+ def getCD(self):
+ return self._serial.getCD()
+
+ def getRI(self):
+ return self._serial.getRI()
+
+ def getCTS(self):
+ return self._serial.getCTS()
+
+ def setDTR(self, on = 1):
+ self._serial.setDTR(on)
+
+ def setRTS(self, on = 1):
+ self._serial.setRTS(on)
+
+class SerialPort(BaseSerialPort):
+ pass
+
+# replace SerialPort with appropriate serial port
+if os.name == 'posix':
+ from twisted.internet._posixserialport import SerialPort
+elif os.name == 'java':
+ from twisted.internet._javaserialport import SerialPort
+elif sys.platform == 'win32':
+ from twisted.internet._win32serialport import SerialPort
diff --git a/vendor/Twisted-10.0.0/twisted/internet/ssl.py b/vendor/Twisted-10.0.0/twisted/internet/ssl.py
new file mode 100644
index 0000000000..1c47c74360
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/ssl.py
@@ -0,0 +1,233 @@
+# -*- test-case-name: twisted.test.test_ssl -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+SSL transport. Requires PyOpenSSL (http://pyopenssl.sf.net).
+
+SSL connections require a ContextFactory so they can create SSL contexts.
+End users should only use the ContextFactory classes directly - for SSL
+connections use the reactor.connectSSL/listenSSL and so on, as documented
+in IReactorSSL.
+
+All server context factories should inherit from ContextFactory, and all
+client context factories should inherit from ClientContextFactory. At the
+moment this is not enforced, but in the future it might be.
+
+Future Plans:
+ - split module so reactor-specific classes are in a separate module
+ - support for switching TCP into SSL
+ - more options
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+# If something goes wrong, most notably an OpenSSL import failure,
+# sys.modules['twisted.internet.ssl'] will be bound to a partially
+# initialized module object. This is wacko, but we will take advantage
+# of it to publish whether or not SSL is available.
+# See the end of this module for the other half of this solution.
+
+# The correct idiom to import this module is thus:
+
+# try:
+# from twisted.internet import ssl
+# except ImportError:
+# # happens the first time the interpreter tries to import it
+# ssl = None
+# if ssl and not ssl.supported:
+# # happens second and later times
+# ssl = None
+
+supported = False
+
+# System imports
+from OpenSSL import SSL
+from zope.interface import implements, implementsOnly, implementedBy
+
+# Twisted imports
+from twisted.internet import tcp, interfaces, base, address
+
+
+class ContextFactory:
+ """A factory for SSL context objects, for server SSL connections."""
+
+ isClient = 0
+
+ def getContext(self):
+ """Return a SSL.Context object. override in subclasses."""
+ raise NotImplementedError
+
+
+class DefaultOpenSSLContextFactory(ContextFactory):
+ """
+ L{DefaultOpenSSLContextFactory} is a factory for server-side SSL context
+ objects. These objects define certain parameters related to SSL
+ handshakes and the subsequent connection.
+
+ @ivar _contextFactory: A callable which will be used to create new
+ context objects. This is typically L{SSL.Context}.
+ """
+ _context = None
+
+ def __init__(self, privateKeyFileName, certificateFileName,
+ sslmethod=SSL.SSLv23_METHOD, _contextFactory=SSL.Context):
+ """
+ @param privateKeyFileName: Name of a file containing a private key
+ @param certificateFileName: Name of a file containing a certificate
+ @param sslmethod: The SSL method to use
+ """
+ self.privateKeyFileName = privateKeyFileName
+ self.certificateFileName = certificateFileName
+ self.sslmethod = sslmethod
+ self._contextFactory = _contextFactory
+
+ # Create a context object right now. This is to force validation of
+ # the given parameters so that errors are detected earlier rather
+ # than later.
+ self.cacheContext()
+
+
+ def cacheContext(self):
+ if self._context is None:
+ ctx = self._contextFactory(self.sslmethod)
+ # Disallow SSLv2! It's insecure! SSLv3 has been around since
+ # 1996. It's time to move on.
+ ctx.set_options(SSL.OP_NO_SSLv2)
+ ctx.use_certificate_file(self.certificateFileName)
+ ctx.use_privatekey_file(self.privateKeyFileName)
+ self._context = ctx
+
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ del d['_context']
+ return d
+
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+
+
+ def getContext(self):
+ """
+ Return an SSL context.
+ """
+ return self._context
+
+
+class ClientContextFactory:
+ """A context factory for SSL clients."""
+
+ isClient = 1
+
+ # SSLv23_METHOD allows SSLv2, SSLv3, and TLSv1. We disable SSLv2 below,
+ # though.
+ method = SSL.SSLv23_METHOD
+
+ _contextFactory = SSL.Context
+
+ def getContext(self):
+ ctx = self._contextFactory(self.method)
+ # See comment in DefaultOpenSSLContextFactory about SSLv2.
+ ctx.set_options(SSL.OP_NO_SSLv2)
+ return ctx
+
+
+
+class Client(tcp.Client):
+ """I am an SSL client."""
+
+ implementsOnly(interfaces.ISSLTransport,
+ *[i for i in implementedBy(tcp.Client) if i != interfaces.ITLSTransport])
+
+ def __init__(self, host, port, bindAddress, ctxFactory, connector, reactor=None):
+ # tcp.Client.__init__ depends on self.ctxFactory being set
+ self.ctxFactory = ctxFactory
+ tcp.Client.__init__(self, host, port, bindAddress, connector, reactor)
+
+ def getHost(self):
+ """Returns the address from which I am connecting."""
+ h, p = self.socket.getsockname()
+ return address.IPv4Address('TCP', h, p, 'SSL')
+
+ def getPeer(self):
+ """Returns the address that I am connected."""
+ return address.IPv4Address('TCP', self.addr[0], self.addr[1], 'SSL')
+
+ def _connectDone(self):
+ self.startTLS(self.ctxFactory)
+ self.startWriting()
+ tcp.Client._connectDone(self)
+
+
+class Server(tcp.Server):
+ """I am an SSL server.
+ """
+
+ implements(interfaces.ISSLTransport)
+
+ def getHost(self):
+ """Return server's address."""
+ h, p = self.socket.getsockname()
+ return address.IPv4Address('TCP', h, p, 'SSL')
+
+ def getPeer(self):
+ """Return address of peer."""
+ h, p = self.client
+ return address.IPv4Address('TCP', h, p, 'SSL')
+
+
+class Port(tcp.Port):
+ """I am an SSL port."""
+ _socketShutdownMethod = 'sock_shutdown'
+
+ transport = Server
+
+ def __init__(self, port, factory, ctxFactory, backlog=50, interface='', reactor=None):
+ tcp.Port.__init__(self, port, factory, backlog, interface, reactor)
+ self.ctxFactory = ctxFactory
+
+ def createInternetSocket(self):
+ """(internal) create an SSL socket
+ """
+ sock = tcp.Port.createInternetSocket(self)
+ return SSL.Connection(self.ctxFactory.getContext(), sock)
+
+ def _preMakeConnection(self, transport):
+ # *Don't* call startTLS here
+ # The transport already has the SSL.Connection object from above
+ transport._startTLS()
+ return tcp.Port._preMakeConnection(self, transport)
+
+
+class Connector(base.BaseConnector):
+ def __init__(self, host, port, factory, contextFactory, timeout, bindAddress, reactor=None):
+ self.host = host
+ self.port = port
+ self.bindAddress = bindAddress
+ self.contextFactory = contextFactory
+ base.BaseConnector.__init__(self, factory, timeout, reactor)
+
+ def _makeTransport(self):
+ return Client(self.host, self.port, self.bindAddress, self.contextFactory, self, self.reactor)
+
+ def getDestination(self):
+ return address.IPv4Address('TCP', self.host, self.port, 'SSL')
+
+from twisted.internet._sslverify import DistinguishedName, DN, Certificate
+from twisted.internet._sslverify import CertificateRequest, PrivateCertificate
+from twisted.internet._sslverify import KeyPair
+from twisted.internet._sslverify import OpenSSLCertificateOptions as CertificateOptions
+
+__all__ = [
+ "ContextFactory", "DefaultOpenSSLContextFactory", "ClientContextFactory",
+
+ 'DistinguishedName', 'DN',
+ 'Certificate', 'CertificateRequest', 'PrivateCertificate',
+ 'KeyPair',
+ 'CertificateOptions',
+ ]
+
+supported = True
diff --git a/vendor/Twisted-10.0.0/twisted/internet/stdio.py b/vendor/Twisted-10.0.0/twisted/internet/stdio.py
new file mode 100644
index 0000000000..b0fd8f83a2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/stdio.py
@@ -0,0 +1,32 @@
+# -*- test-case-name: twisted.test.test_stdio -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Standard input/out/err support.
+
+This module exposes one name, StandardIO, which is a factory that takes an
+IProtocol provider as an argument. It connects that protocol to standard input
+and output on the current process.
+
+It should work on any UNIX and also on Win32 (with some caveats: due to
+platform limitations, it will perform very poorly on Win32).
+
+Future Plans::
+
+ support for stderr, perhaps
+ Rewrite to use the reactor instead of an ad-hoc mechanism for connecting
+ protocols to transport.
+
+
+Maintainer: James Y Knight
+"""
+
+from twisted.python.runtime import platform
+
+if platform.isWindows():
+ from twisted.internet._win32stdio import StandardIO
+else:
+ from twisted.internet._posixstdio import StandardIO
+
+__all__ = ['StandardIO']
diff --git a/vendor/Twisted-10.0.0/twisted/internet/task.py b/vendor/Twisted-10.0.0/twisted/internet/task.py
new file mode 100644
index 0000000000..ca0590ed4a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/task.py
@@ -0,0 +1,750 @@
+# -*- test-case-name: twisted.test.test_task,twisted.test.test_cooperator -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Scheduling utility methods and classes.
+
+@author: Jp Calderone
+"""
+
+__metaclass__ = type
+
+import time
+
+from zope.interface import implements
+
+from twisted.python import reflect
+from twisted.python.failure import Failure
+
+from twisted.internet import base, defer
+from twisted.internet.interfaces import IReactorTime
+
+
+class LoopingCall:
+ """Call a function repeatedly.
+
+ If C{f} returns a deferred, rescheduling will not take place until the
+ deferred has fired. The result value is ignored.
+
+ @ivar f: The function to call.
+ @ivar a: A tuple of arguments to pass the function.
+ @ivar kw: A dictionary of keyword arguments to pass to the function.
+ @ivar clock: A provider of
+ L{twisted.internet.interfaces.IReactorTime}. The default is
+ L{twisted.internet.reactor}. Feel free to set this to
+ something else, but it probably ought to be set *before*
+ calling L{start}.
+
+ @type _expectNextCallAt: C{float}
+ @ivar _expectNextCallAt: The time at which this instance most recently
+ scheduled itself to run.
+
+ @type _realLastTime: C{float}
+ @ivar _realLastTime: When counting skips, the time at which the skip counter
+ was last invoked.
+
+ @type _runAtStart: C{bool}
+ @ivar _runAtStart: A flag indicating whether the 'now' argument was passed
+ to L{LoopingCall.start}.
+ """
+
+ call = None
+ running = False
+ deferred = None
+ interval = None
+ _expectNextCallAt = 0.0
+ _runAtStart = False
+ starttime = None
+
+ def __init__(self, f, *a, **kw):
+ self.f = f
+ self.a = a
+ self.kw = kw
+ from twisted.internet import reactor
+ self.clock = reactor
+
+
+ def withCount(cls, countCallable):
+ """
+ An alternate constructor for L{LoopingCall} that makes available the
+ number of calls which should have occurred since it was last invoked.
+
+ Note that this number is an C{int} value; It represents the discrete
+ number of calls that should have been made. For example, if you are
+ using a looping call to display an animation with discrete frames, this
+ number would be the number of frames to advance.
+
+ The count is normally 1, but can be higher. For example, if the reactor
+ is blocked and takes too long to invoke the L{LoopingCall}, a Deferred
+ returned from a previous call is not fired before an interval has
+ elapsed, or if the callable itself blocks for longer than an interval,
+ preventing I{itself} from being called.
+
+ @param countCallable: A callable that will be invoked each time the
+ resulting LoopingCall is run, with an integer specifying the number
+ of calls that should have been invoked.
+
+ @type countCallable: 1-argument callable which takes an C{int}
+
+ @return: An instance of L{LoopingCall} with call counting enabled,
+ which provides the count as the first positional argument.
+
+ @rtype: L{LoopingCall}
+
+ @since: 9.0
+ """
+
+ def counter():
+ now = self.clock.seconds()
+ lastTime = self._realLastTime
+ if lastTime is None:
+ lastTime = self.starttime
+ if self._runAtStart:
+ lastTime -= self.interval
+ self._realLastTime = now
+ lastInterval = self._intervalOf(lastTime)
+ thisInterval = self._intervalOf(now)
+ count = thisInterval - lastInterval
+ return countCallable(count)
+
+ self = cls(counter)
+
+ self._realLastTime = None
+
+ return self
+
+ withCount = classmethod(withCount)
+
+
+ def _intervalOf(self, t):
+ """
+ Determine the number of intervals passed as of the given point in
+ time.
+
+ @param t: The specified time (from the start of the L{LoopingCall}) to
+ be measured in intervals
+
+ @return: The C{int} number of intervals which have passed as of the
+ given point in time.
+ """
+ elapsedTime = t - self.starttime
+ intervalNum = int(elapsedTime / self.interval)
+ return intervalNum
+
+
+ def start(self, interval, now=True):
+ """
+ Start running function every interval seconds.
+
+ @param interval: The number of seconds between calls. May be
+ less than one. Precision will depend on the underlying
+ platform, the available hardware, and the load on the system.
+
+ @param now: If True, run this call right now. Otherwise, wait
+ until the interval has elapsed before beginning.
+
+ @return: A Deferred whose callback will be invoked with
+ C{self} when C{self.stop} is called, or whose errback will be
+ invoked when the function raises an exception or returned a
+ deferred that has its errback invoked.
+ """
+ assert not self.running, ("Tried to start an already running "
+ "LoopingCall.")
+ if interval < 0:
+ raise ValueError, "interval must be >= 0"
+ self.running = True
+ d = self.deferred = defer.Deferred()
+ self.starttime = self.clock.seconds()
+ self._expectNextCallAt = self.starttime
+ self.interval = interval
+ self._runAtStart = now
+ if now:
+ self()
+ else:
+ self._reschedule()
+ return d
+
+ def stop(self):
+ """Stop running function.
+ """
+ assert self.running, ("Tried to stop a LoopingCall that was "
+ "not running.")
+ self.running = False
+ if self.call is not None:
+ self.call.cancel()
+ self.call = None
+ d, self.deferred = self.deferred, None
+ d.callback(self)
+
+ def __call__(self):
+ def cb(result):
+ if self.running:
+ self._reschedule()
+ else:
+ d, self.deferred = self.deferred, None
+ d.callback(self)
+
+ def eb(failure):
+ self.running = False
+ d, self.deferred = self.deferred, None
+ d.errback(failure)
+
+ self.call = None
+ d = defer.maybeDeferred(self.f, *self.a, **self.kw)
+ d.addCallback(cb)
+ d.addErrback(eb)
+
+
+ def _reschedule(self):
+ """
+ Schedule the next iteration of this looping call.
+ """
+ if self.interval == 0:
+ self.call = self.clock.callLater(0, self)
+ return
+
+ currentTime = self.clock.seconds()
+ # Find how long is left until the interval comes around again.
+ untilNextTime = (self._expectNextCallAt - currentTime) % self.interval
+ # Make sure it is in the future, in case more than one interval worth
+ # of time passed since the previous call was made.
+ nextTime = max(
+ self._expectNextCallAt + self.interval, currentTime + untilNextTime)
+ # If the interval falls on the current time exactly, skip it and
+ # schedule the call for the next interval.
+ if nextTime == currentTime:
+ nextTime += self.interval
+ self._expectNextCallAt = nextTime
+ self.call = self.clock.callLater(nextTime - currentTime, self)
+
+
+ def __repr__(self):
+ if hasattr(self.f, 'func_name'):
+ func = self.f.func_name
+ if hasattr(self.f, 'im_class'):
+ func = self.f.im_class.__name__ + '.' + func
+ else:
+ func = reflect.safe_repr(self.f)
+
+ return 'LoopingCall<%r>(%s, *%s, **%s)' % (
+ self.interval, func, reflect.safe_repr(self.a),
+ reflect.safe_repr(self.kw))
+
+
+
+class SchedulerError(Exception):
+ """
+ The operation could not be completed because the scheduler or one of its
+ tasks was in an invalid state. This exception should not be raised
+ directly, but is a superclass of various scheduler-state-related
+ exceptions.
+ """
+
+
+
+class SchedulerStopped(SchedulerError):
+ """
+ The operation could not complete because the scheduler was stopped in
+ progress or was already stopped.
+ """
+
+
+
+class TaskFinished(SchedulerError):
+ """
+ The operation could not complete because the task was already completed,
+ stopped, encountered an error or otherwise permanently stopped running.
+ """
+
+
+
+class TaskDone(TaskFinished):
+ """
+ The operation could not complete because the task was already completed.
+ """
+
+
+
+class TaskStopped(TaskFinished):
+ """
+ The operation could not complete because the task was stopped.
+ """
+
+
+
+class TaskFailed(TaskFinished):
+ """
+ The operation could not complete because the task died with an unhandled
+ error.
+ """
+
+
+
+class NotPaused(SchedulerError):
+ """
+ This exception is raised when a task is resumed which was not previously
+ paused.
+ """
+
+
+
+class _Timer(object):
+ MAX_SLICE = 0.01
+ def __init__(self):
+ self.end = time.time() + self.MAX_SLICE
+
+
+ def __call__(self):
+ return time.time() >= self.end
+
+
+
+_EPSILON = 0.00000001
+def _defaultScheduler(x):
+ from twisted.internet import reactor
+ return reactor.callLater(_EPSILON, x)
+
+
+class CooperativeTask(object):
+ """
+ A L{CooperativeTask} is a task object inside a L{Cooperator}, which can be
+ paused, resumed, and stopped. It can also have its completion (or
+ termination) monitored.
+
+ @see: L{CooperativeTask.cooperate}
+
+ @ivar _iterator: the iterator to iterate when this L{CooperativeTask} is
+ asked to do work.
+
+ @ivar _cooperator: the L{Cooperator} that this L{CooperativeTask}
+ participates in, which is used to re-insert it upon resume.
+
+ @ivar _deferreds: the list of L{defer.Deferred}s to fire when this task
+ completes, fails, or finishes.
+
+ @type _deferreds: L{list}
+
+ @type _cooperator: L{Cooperator}
+
+ @ivar _pauseCount: the number of times that this L{CooperativeTask} has
+ been paused; if 0, it is running.
+
+ @type _pauseCount: L{int}
+
+ @ivar _completionState: The completion-state of this L{CooperativeTask}.
+ C{None} if the task is not yet completed, an instance of L{TaskStopped}
+ if C{stop} was called to stop this task early, of L{TaskFailed} if the
+ application code in the iterator raised an exception which caused it to
+ terminate, and of L{TaskDone} if it terminated normally via raising
+ L{StopIteration}.
+
+ @type _completionState: L{TaskFinished}
+ """
+
+ def __init__(self, iterator, cooperator):
+ """
+ A private constructor: to create a new L{CooperativeTask}, see
+ L{Cooperator.cooperate}.
+ """
+ self._iterator = iterator
+ self._cooperator = cooperator
+ self._deferreds = []
+ self._pauseCount = 0
+ self._completionState = None
+ self._completionResult = None
+ cooperator._addTask(self)
+
+
+ def whenDone(self):
+ """
+ Get a L{defer.Deferred} notification of when this task is complete.
+
+ @return: a L{defer.Deferred} that fires with the C{iterator} that this
+ L{CooperativeTask} was created with when the iterator has been
+ exhausted (i.e. its C{next} method has raised L{StopIteration}), or
+ fails with the exception raised by C{next} if it raises some other
+ exception.
+
+ @rtype: L{defer.Deferred}
+ """
+ d = defer.Deferred()
+ if self._completionState is None:
+ self._deferreds.append(d)
+ else:
+ d.callback(self._completionResult)
+ return d
+
+
+ def pause(self):
+ """
+ Pause this L{CooperativeTask}. Stop doing work until
+ L{CooperativeTask.resume} is called. If C{pause} is called more than
+ once, C{resume} must be called an equal number of times to resume this
+ task.
+
+ @raise TaskFinished: if this task has already finished or completed.
+ """
+ self._checkFinish()
+ self._pauseCount += 1
+ if self._pauseCount == 1:
+ self._cooperator._removeTask(self)
+
+
+ def resume(self):
+ """
+ Resume processing of a paused L{CooperativeTask}.
+
+ @raise NotPaused: if this L{CooperativeTask} is not paused.
+ """
+ if self._pauseCount == 0:
+ raise NotPaused()
+ self._pauseCount -= 1
+ if self._pauseCount == 0 and self._completionState is None:
+ self._cooperator._addTask(self)
+
+
+ def _completeWith(self, completionState, deferredResult):
+ """
+ @param completionState: a L{TaskFinished} exception or a subclass
+ thereof, indicating what exception should be raised when subsequent
+ operations are performed.
+
+ @param deferredResult: the result to fire all the deferreds with.
+ """
+ self._completionState = completionState
+ self._completionResult = deferredResult
+ if not self._pauseCount:
+ self._cooperator._removeTask(self)
+
+ # The Deferreds need to be invoked after all this is completed, because
+ # a Deferred may want to manipulate other tasks in a Cooperator. For
+ # example, if you call "stop()" on a cooperator in a callback on a
+ # Deferred returned from whenDone(), this CooperativeTask must be gone
+ # from the Cooperator by that point so that _completeWith is not
+ # invoked reentrantly; that would cause these Deferreds to blow up with
+ # an AlreadyCalledError, or the _removeTask to fail with a ValueError.
+ for d in self._deferreds:
+ d.callback(deferredResult)
+
+
+ def stop(self):
+ """
+ Stop further processing of this task.
+
+ @raise TaskFinished: if this L{CooperativeTask} has previously
+ completed, via C{stop}, completion, or failure.
+ """
+ self._checkFinish()
+ self._completeWith(TaskStopped(), Failure(TaskStopped()))
+
+
+ def _checkFinish(self):
+ """
+ If this task has been stopped, raise the appropriate subclass of
+ L{TaskFinished}.
+ """
+ if self._completionState is not None:
+ raise self._completionState
+
+
+ def _oneWorkUnit(self):
+ """
+ Perform one unit of work for this task, retrieving one item from its
+ iterator, stopping if there are no further items in the iterator, and
+ pausing if the result was a L{defer.Deferred}.
+ """
+ try:
+ result = self._iterator.next()
+ except StopIteration:
+ self._completeWith(TaskDone(), self._iterator)
+ except:
+ self._completeWith(TaskFailed(), Failure())
+ else:
+ if isinstance(result, defer.Deferred):
+ self.pause()
+ def failLater(f):
+ self._completeWith(TaskFailed(), f)
+ result.addCallbacks(lambda result: self.resume(),
+ failLater)
+
+
+
+class Cooperator(object):
+ """
+ Cooperative task scheduler.
+ """
+
+ def __init__(self,
+ terminationPredicateFactory=_Timer,
+ scheduler=_defaultScheduler,
+ started=True):
+ """
+ Create a scheduler-like object to which iterators may be added.
+
+ @param terminationPredicateFactory: A no-argument callable which will
+ be invoked at the beginning of each step and should return a
+ no-argument callable which will return False when the step should be
+ terminated. The default factory is time-based and allows iterators to
+ run for 1/100th of a second at a time.
+
+ @param scheduler: A one-argument callable which takes a no-argument
+ callable and should invoke it at some future point. This will be used
+ to schedule each step of this Cooperator.
+
+ @param started: A boolean which indicates whether iterators should be
+ stepped as soon as they are added, or if they will be queued up until
+ L{Cooperator.start} is called.
+ """
+ self._tasks = []
+ self._metarator = iter(())
+ self._terminationPredicateFactory = terminationPredicateFactory
+ self._scheduler = scheduler
+ self._delayedCall = None
+ self._stopped = False
+ self._started = started
+
+
+ def coiterate(self, iterator, doneDeferred=None):
+ """
+ Add an iterator to the list of iterators this L{Cooperator} is
+ currently running.
+
+ @param doneDeferred: If specified, this will be the Deferred used as
+ the completion deferred. It is suggested that you use the default,
+ which creates a new Deferred for you.
+
+ @return: a Deferred that will fire when the iterator finishes.
+ """
+ if doneDeferred is None:
+ doneDeferred = defer.Deferred()
+ CooperativeTask(iterator, self).whenDone().chainDeferred(doneDeferred)
+ return doneDeferred
+
+
+ def cooperate(self, iterator):
+ """
+ Start running the given iterator as a long-running cooperative task, by
+ calling next() on it as a periodic timed event.
+
+ @param iterator: the iterator to invoke.
+
+ @return: a L{CooperativeTask} object representing this task.
+ """
+ return CooperativeTask(iterator, self)
+
+
+ def _addTask(self, task):
+ """
+ Add a L{CooperativeTask} object to this L{Cooperator}.
+ """
+ if self._stopped:
+ self._tasks.append(task) # XXX silly, I know, but _completeWith
+ # does the inverse
+ task._completeWith(SchedulerStopped(), Failure(SchedulerStopped()))
+ else:
+ self._tasks.append(task)
+ self._reschedule()
+
+
+ def _removeTask(self, task):
+ """
+ Remove a L{CooperativeTask} from this L{Cooperator}.
+ """
+ self._tasks.remove(task)
+
+
+ def _tasksWhileNotStopped(self):
+ """
+ Yield all L{CooperativeTask} objects in a loop as long as this
+ L{Cooperator}'s termination condition has not been met.
+ """
+ terminator = self._terminationPredicateFactory()
+ while self._tasks:
+ for t in self._metarator:
+ yield t
+ if terminator():
+ return
+ self._metarator = iter(self._tasks)
+
+
+ def _tick(self):
+ """
+ Run one scheduler tick.
+ """
+ self._delayedCall = None
+ for taskObj in self._tasksWhileNotStopped():
+ taskObj._oneWorkUnit()
+ self._reschedule()
+
+
+ _mustScheduleOnStart = False
+ def _reschedule(self):
+ if not self._started:
+ self._mustScheduleOnStart = True
+ return
+ if self._delayedCall is None and self._tasks:
+ self._delayedCall = self._scheduler(self._tick)
+
+
+ def start(self):
+ """
+ Begin scheduling steps.
+ """
+ self._stopped = False
+ self._started = True
+ if self._mustScheduleOnStart:
+ del self._mustScheduleOnStart
+ self._reschedule()
+
+
+ def stop(self):
+ """
+ Stop scheduling steps. Errback the completion Deferreds of all
+ iterators which have been added and forget about them.
+ """
+ self._stopped = True
+ for taskObj in self._tasks:
+ taskObj._completeWith(SchedulerStopped(),
+ Failure(SchedulerStopped()))
+ self._tasks = []
+ if self._delayedCall is not None:
+ self._delayedCall.cancel()
+ self._delayedCall = None
+
+
+
+_theCooperator = Cooperator()
+
+def coiterate(iterator):
+ """
+ Cooperatively iterate over the given iterator, dividing runtime between it
+ and all other iterators which have been passed to this function and not yet
+ exhausted.
+ """
+ return _theCooperator.coiterate(iterator)
+
+
+
+def cooperate(iterator):
+ """
+ Start running the given iterator as a long-running cooperative task, by
+ calling next() on it as a periodic timed event.
+
+ @param iterator: the iterator to invoke.
+
+ @return: a L{CooperativeTask} object representing this task.
+ """
+ return _theCooperator.cooperate(iterator)
+
+
+
+
+class Clock:
+ """
+ Provide a deterministic, easily-controlled implementation of
+ L{IReactorTime.callLater}. This is commonly useful for writing
+ deterministic unit tests for code which schedules events using this API.
+ """
+ implements(IReactorTime)
+
+ rightNow = 0.0
+
+ def __init__(self):
+ self.calls = []
+
+ def seconds(self):
+ """
+ Pretend to be time.time(). This is used internally when an operation
+ such as L{IDelayedCall.reset} needs to determine a a time value
+ relative to the current time.
+
+ @rtype: C{float}
+ @return: The time which should be considered the current time.
+ """
+ return self.rightNow
+
+
+ def callLater(self, when, what, *a, **kw):
+ """
+ See L{twisted.internet.interfaces.IReactorTime.callLater}.
+ """
+ dc = base.DelayedCall(self.seconds() + when,
+ what, a, kw,
+ self.calls.remove,
+ lambda c: None,
+ self.seconds)
+ self.calls.append(dc)
+ self.calls.sort(lambda a, b: cmp(a.getTime(), b.getTime()))
+ return dc
+
+ def getDelayedCalls(self):
+ """
+ See L{twisted.internet.interfaces.IReactorTime.getDelayedCalls}
+ """
+ return self.calls
+
+ def advance(self, amount):
+ """
+ Move time on this clock forward by the given amount and run whatever
+ pending calls should be run.
+
+ @type amount: C{float}
+ @param amount: The number of seconds which to advance this clock's
+ time.
+ """
+ self.rightNow += amount
+ while self.calls and self.calls[0].getTime() <= self.seconds():
+ call = self.calls.pop(0)
+ call.called = 1
+ call.func(*call.args, **call.kw)
+
+
+ def pump(self, timings):
+ """
+ Advance incrementally by the given set of times.
+
+ @type timings: iterable of C{float}
+ """
+ for amount in timings:
+ self.advance(amount)
+
+
+def deferLater(clock, delay, callable, *args, **kw):
+ """
+ Call the given function after a certain period of time has passed.
+
+ @type clock: L{IReactorTime} provider
+ @param clock: The object which will be used to schedule the delayed
+ call.
+
+ @type delay: C{float} or C{int}
+ @param delay: The number of seconds to wait before calling the function.
+
+ @param callable: The object to call after the delay.
+
+ @param *args: The positional arguments to pass to C{callable}.
+
+ @param **kw: The keyword arguments to pass to C{callable}.
+
+ @rtype: L{defer.Deferred}
+
+ @return: A deferred that fires with the result of the callable when the
+ specified time has elapsed.
+ """
+ d = defer.Deferred()
+ d.addCallback(lambda ignored: callable(*args, **kw))
+ clock.callLater(delay, d.callback, None)
+ return d
+
+
+
+__all__ = [
+ 'LoopingCall',
+
+ 'Clock',
+
+ 'SchedulerStopped', 'Cooperator', 'coiterate',
+
+ 'deferLater',
+ ]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/tcp.py b/vendor/Twisted-10.0.0/twisted/internet/tcp.py
new file mode 100644
index 0000000000..41ed68a37e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/tcp.py
@@ -0,0 +1,1019 @@
+# -*- test-case-name: twisted.test.test_tcp -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Various asynchronous TCP/IP classes.
+
+End users shouldn't use this module directly - use the reactor APIs instead.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+
+# System Imports
+import os
+import types
+import socket
+import sys
+import operator
+
+from zope.interface import implements, classImplements
+
+try:
+ from OpenSSL import SSL
+except ImportError:
+ SSL = None
+
+from twisted.python.runtime import platformType
+
+
+if platformType == 'win32':
+ # no such thing as WSAEPERM or error code 10001 according to winsock.h or MSDN
+ EPERM = object()
+ from errno import WSAEINVAL as EINVAL
+ from errno import WSAEWOULDBLOCK as EWOULDBLOCK
+ from errno import WSAEINPROGRESS as EINPROGRESS
+ from errno import WSAEALREADY as EALREADY
+ from errno import WSAECONNRESET as ECONNRESET
+ from errno import WSAEISCONN as EISCONN
+ from errno import WSAENOTCONN as ENOTCONN
+ from errno import WSAEINTR as EINTR
+ from errno import WSAENOBUFS as ENOBUFS
+ from errno import WSAEMFILE as EMFILE
+ # No such thing as WSAENFILE, either.
+ ENFILE = object()
+ # Nor ENOMEM
+ ENOMEM = object()
+ EAGAIN = EWOULDBLOCK
+ from errno import WSAECONNRESET as ECONNABORTED
+
+ from twisted.python.win32 import formatError as strerror
+else:
+ from errno import EPERM
+ from errno import EINVAL
+ from errno import EWOULDBLOCK
+ from errno import EINPROGRESS
+ from errno import EALREADY
+ from errno import ECONNRESET
+ from errno import EISCONN
+ from errno import ENOTCONN
+ from errno import EINTR
+ from errno import ENOBUFS
+ from errno import EMFILE
+ from errno import ENFILE
+ from errno import ENOMEM
+ from errno import EAGAIN
+ from errno import ECONNABORTED
+
+ from os import strerror
+
+from errno import errorcode
+
+# Twisted Imports
+from twisted.internet import defer, base, address, fdesc
+from twisted.internet.task import deferLater
+from twisted.python import log, failure, reflect
+from twisted.python.util import unsignedID
+from twisted.internet.error import CannotListenError
+from twisted.internet import abstract, main, interfaces, error
+
+
+
+class _SocketCloser:
+ _socketShutdownMethod = 'shutdown'
+
+ def _closeSocket(self):
+ # socket.close() doesn't *really* close if there's another reference
+ # to it in the TCP/IP stack, e.g. if it was was inherited by a
+ # subprocess. And we really do want to close the connection. So we
+ # use shutdown() instead, and then close() in order to release the
+ # filedescriptor.
+ skt = self.socket
+ try:
+ getattr(skt, self._socketShutdownMethod)(2)
+ except socket.error:
+ pass
+ try:
+ skt.close()
+ except socket.error:
+ pass
+
+
+
+class _TLSMixin:
+ _socketShutdownMethod = 'sock_shutdown'
+
+ writeBlockedOnRead = 0
+ readBlockedOnWrite = 0
+ _userWantRead = _userWantWrite = True
+
+ def getPeerCertificate(self):
+ return self.socket.get_peer_certificate()
+
+ def doRead(self):
+ if self.disconnected:
+ # See the comment in the similar check in doWrite below.
+ # Additionally, in order for anything other than returning
+ # CONNECTION_DONE here to make sense, it will probably be necessary
+ # to implement a way to switch back to TCP from TLS (actually, if
+ # we did something other than return CONNECTION_DONE, that would be
+ # a big part of implementing that feature). In other words, the
+ # expectation is that doRead will be called when self.disconnected
+ # is True only when the connection has been lost. It's possible
+ # that the other end could stop speaking TLS and then send us some
+ # non-TLS data. We'll end up ignoring that data and dropping the
+ # connection. There's no unit tests for this check in the cases
+ # where it makes a difference. The test suite only hits this
+ # codepath when it would have otherwise hit the SSL.ZeroReturnError
+ # exception handler below, which has exactly the same behavior as
+ # this conditional. Maybe that's the only case that can ever be
+ # triggered, I'm not sure. -exarkun
+ return main.CONNECTION_DONE
+ if self.writeBlockedOnRead:
+ self.writeBlockedOnRead = 0
+ self._resetReadWrite()
+ try:
+ return Connection.doRead(self)
+ except SSL.ZeroReturnError:
+ return main.CONNECTION_DONE
+ except SSL.WantReadError:
+ return
+ except SSL.WantWriteError:
+ self.readBlockedOnWrite = 1
+ Connection.startWriting(self)
+ Connection.stopReading(self)
+ return
+ except SSL.SysCallError, (retval, desc):
+ if ((retval == -1 and desc == 'Unexpected EOF')
+ or retval > 0):
+ return main.CONNECTION_LOST
+ log.err()
+ return main.CONNECTION_LOST
+ except SSL.Error, e:
+ return e
+
+ def doWrite(self):
+ # Retry disconnecting
+ if self.disconnected:
+ # This case is triggered when "disconnected" is set to True by a
+ # call to _postLoseConnection from FileDescriptor.doWrite (to which
+ # we upcall at the end of this overridden version of that API). It
+ # means that while, as far as any protocol connected to this
+ # transport is concerned, the connection no longer exists, the
+ # connection *does* actually still exist. Instead of closing the
+ # connection in the overridden _postLoseConnection, we probably
+ # tried (and failed) to send a TLS close alert. The TCP connection
+ # is still up and we're waiting for the socket to become writeable
+ # enough for the TLS close alert to actually be sendable. Only
+ # then will the connection actually be torn down. -exarkun
+ return self._postLoseConnection()
+ if self._writeDisconnected:
+ return self._closeWriteConnection()
+
+ if self.readBlockedOnWrite:
+ self.readBlockedOnWrite = 0
+ self._resetReadWrite()
+ return Connection.doWrite(self)
+
+ def writeSomeData(self, data):
+ try:
+ return Connection.writeSomeData(self, data)
+ except SSL.WantWriteError:
+ return 0
+ except SSL.WantReadError:
+ self.writeBlockedOnRead = 1
+ Connection.stopWriting(self)
+ Connection.startReading(self)
+ return 0
+ except SSL.ZeroReturnError:
+ return main.CONNECTION_LOST
+ except SSL.SysCallError, e:
+ if e[0] == -1 and data == "":
+ # errors when writing empty strings are expected
+ # and can be ignored
+ return 0
+ else:
+ return main.CONNECTION_LOST
+ except SSL.Error, e:
+ return e
+
+
+ def _postLoseConnection(self):
+ """
+ Gets called after loseConnection(), after buffered data is sent.
+
+ We try to send an SSL shutdown alert, but if it doesn't work, retry
+ when the socket is writable.
+ """
+ # Here, set "disconnected" to True to trick higher levels into thinking
+ # the connection is really gone. It's not, and we're not going to
+ # close it yet. Instead, we'll try to send a TLS close alert to shut
+ # down the TLS connection cleanly. Only after we actually get the
+ # close alert into the socket will we disconnect the underlying TCP
+ # connection.
+ self.disconnected = True
+ if hasattr(self.socket, 'set_shutdown'):
+ # If possible, mark the state of the TLS connection as having
+ # already received a TLS close alert from the peer. Why do
+ # this???
+ self.socket.set_shutdown(SSL.RECEIVED_SHUTDOWN)
+ return self._sendCloseAlert()
+
+
+ def _sendCloseAlert(self):
+ # Okay, *THIS* is a bit complicated.
+
+ # Basically, the issue is, OpenSSL seems to not actually return
+ # errors from SSL_shutdown. Therefore, the only way to
+ # determine if the close notification has been sent is by
+ # SSL_shutdown returning "done". However, it will not claim it's
+ # done until it's both sent *and* received a shutdown notification.
+
+ # I don't actually want to wait for a received shutdown
+ # notification, though, so, I have to set RECEIVED_SHUTDOWN
+ # before calling shutdown. Then, it'll return True once it's
+ # *SENT* the shutdown.
+
+ # However, RECEIVED_SHUTDOWN can't be left set, because then
+ # reads will fail, breaking half close.
+
+ # Also, since shutdown doesn't report errors, an empty write call is
+ # done first, to try to detect if the connection has gone away.
+ # (*NOT* an SSL_write call, because that fails once you've called
+ # shutdown)
+ try:
+ os.write(self.socket.fileno(), '')
+ except OSError, se:
+ if se.args[0] in (EINTR, EWOULDBLOCK, ENOBUFS):
+ return 0
+ # Write error, socket gone
+ return main.CONNECTION_LOST
+
+ try:
+ if hasattr(self.socket, 'set_shutdown'):
+ laststate = self.socket.get_shutdown()
+ self.socket.set_shutdown(laststate | SSL.RECEIVED_SHUTDOWN)
+ done = self.socket.shutdown()
+ if not (laststate & SSL.RECEIVED_SHUTDOWN):
+ self.socket.set_shutdown(SSL.SENT_SHUTDOWN)
+ else:
+ #warnings.warn("SSL connection shutdown possibly unreliable, "
+ # "please upgrade to ver 0.XX", category=UserWarning)
+ self.socket.shutdown()
+ done = True
+ except SSL.Error, e:
+ return e
+
+ if done:
+ self.stopWriting()
+ # Note that this is tested for by identity below.
+ return main.CONNECTION_DONE
+ else:
+ # For some reason, the close alert wasn't sent. Start writing
+ # again so that we'll get another chance to send it.
+ self.startWriting()
+ # On Linux, select will sometimes not report a closed file
+ # descriptor in the write set (in particular, it seems that if a
+ # send() fails with EPIPE, the socket will not appear in the write
+ # set). The shutdown call above (which calls down to SSL_shutdown)
+ # may have swallowed a write error. Therefore, also start reading
+ # so that if the socket is closed we will notice. This doesn't
+ # seem to be a problem for poll (because poll reports errors
+ # separately) or with select on BSD (presumably because, unlike
+ # Linux, it doesn't implement select in terms of poll and then map
+ # POLLHUP to select's in fd_set).
+ self.startReading()
+ return None
+
+ def _closeWriteConnection(self):
+ result = self._sendCloseAlert()
+
+ if result is main.CONNECTION_DONE:
+ return Connection._closeWriteConnection(self)
+
+ return result
+
+ def startReading(self):
+ self._userWantRead = True
+ if not self.readBlockedOnWrite:
+ return Connection.startReading(self)
+
+ def stopReading(self):
+ self._userWantRead = False
+ if not self.writeBlockedOnRead:
+ return Connection.stopReading(self)
+
+ def startWriting(self):
+ self._userWantWrite = True
+ if not self.writeBlockedOnRead:
+ return Connection.startWriting(self)
+
+ def stopWriting(self):
+ self._userWantWrite = False
+ if not self.readBlockedOnWrite:
+ return Connection.stopWriting(self)
+
+ def _resetReadWrite(self):
+ # After changing readBlockedOnWrite or writeBlockedOnRead,
+ # call this to reset the state to what the user requested.
+ if self._userWantWrite:
+ self.startWriting()
+ else:
+ self.stopWriting()
+
+ if self._userWantRead:
+ self.startReading()
+ else:
+ self.stopReading()
+
+
+
+class _TLSDelayed(object):
+ """
+ State tracking record for TLS startup parameters. Used to remember how
+ TLS should be started when starting it is delayed to wait for the output
+ buffer to be flushed.
+
+ @ivar bufferedData: A C{list} which contains all the data which was
+ written to the transport after an attempt to start TLS was made but
+ before the buffers outstanding at that time could be flushed and TLS
+ could really be started. This is appended to by the transport's
+ write and writeSequence methods until it is possible to actually
+ start TLS, then it is written to the TLS-enabled transport.
+
+ @ivar context: An SSL context factory object to use to start TLS.
+
+ @ivar extra: An extra argument to pass to the transport's C{startTLS}
+ method.
+ """
+ def __init__(self, bufferedData, context, extra):
+ self.bufferedData = bufferedData
+ self.context = context
+ self.extra = extra
+
+
+
+def _getTLSClass(klass, _existing={}):
+ if klass not in _existing:
+ class TLSConnection(_TLSMixin, klass):
+ implements(interfaces.ISSLTransport)
+ _existing[klass] = TLSConnection
+ return _existing[klass]
+
+
+
+class Connection(abstract.FileDescriptor, _SocketCloser):
+ """
+ Superclass of all socket-based FileDescriptors.
+
+ This is an abstract superclass of all objects which represent a TCP/IP
+ connection based socket.
+
+ @ivar logstr: prefix used when logging events related to this connection.
+ @type logstr: C{str}
+ """
+
+ implements(interfaces.ITCPTransport, interfaces.ISystemHandle)
+
+ TLS = 0
+
+ def __init__(self, skt, protocol, reactor=None):
+ abstract.FileDescriptor.__init__(self, reactor=reactor)
+ self.socket = skt
+ self.socket.setblocking(0)
+ self.fileno = skt.fileno
+ self.protocol = protocol
+
+ if SSL:
+ _tlsWaiting = None
+ def startTLS(self, ctx, extra):
+ assert not self.TLS
+ if self.dataBuffer or self._tempDataBuffer:
+ # pre-TLS bytes are still being written. Starting TLS now
+ # will do the wrong thing. Instead, mark that we're trying
+ # to go into the TLS state.
+ self._tlsWaiting = _TLSDelayed([], ctx, extra)
+ return False
+
+ self.stopReading()
+ self.stopWriting()
+ self._startTLS()
+ self.socket = SSL.Connection(ctx.getContext(), self.socket)
+ self.fileno = self.socket.fileno
+ self.startReading()
+ return True
+
+
+ def _startTLS(self):
+ self.TLS = 1
+ self.__class__ = _getTLSClass(self.__class__)
+
+
+ def write(self, bytes):
+ if self._tlsWaiting is not None:
+ self._tlsWaiting.bufferedData.append(bytes)
+ else:
+ abstract.FileDescriptor.write(self, bytes)
+
+
+ def writeSequence(self, iovec):
+ if self._tlsWaiting is not None:
+ self._tlsWaiting.bufferedData.extend(iovec)
+ else:
+ abstract.FileDescriptor.writeSequence(self, iovec)
+
+
+ def doWrite(self):
+ result = abstract.FileDescriptor.doWrite(self)
+ if self._tlsWaiting is not None:
+ if not self.dataBuffer and not self._tempDataBuffer:
+ waiting = self._tlsWaiting
+ self._tlsWaiting = None
+ self.startTLS(waiting.context, waiting.extra)
+ self.writeSequence(waiting.bufferedData)
+ return result
+
+
+ def getHandle(self):
+ """Return the socket for this connection."""
+ return self.socket
+
+
+ def doRead(self):
+ """Calls self.protocol.dataReceived with all available data.
+
+ This reads up to self.bufferSize bytes of data from its socket, then
+ calls self.dataReceived(data) to process it. If the connection is not
+ lost through an error in the physical recv(), this function will return
+ the result of the dataReceived call.
+ """
+ try:
+ data = self.socket.recv(self.bufferSize)
+ except socket.error, se:
+ if se.args[0] == EWOULDBLOCK:
+ return
+ else:
+ return main.CONNECTION_LOST
+ if not data:
+ return main.CONNECTION_DONE
+ return self.protocol.dataReceived(data)
+
+
+ def writeSomeData(self, data):
+ """
+ Write as much as possible of the given data to this TCP connection.
+
+ This sends up to C{self.SEND_LIMIT} bytes from C{data}. If the
+ connection is lost, an exception is returned. Otherwise, the number
+ of bytes successfully written is returned.
+ """
+ try:
+ # Limit length of buffer to try to send, because some OSes are too
+ # stupid to do so themselves (ahem windows)
+ return self.socket.send(buffer(data, 0, self.SEND_LIMIT))
+ except socket.error, se:
+ if se.args[0] == EINTR:
+ return self.writeSomeData(data)
+ elif se.args[0] in (EWOULDBLOCK, ENOBUFS):
+ return 0
+ else:
+ return main.CONNECTION_LOST
+
+
+ def _closeWriteConnection(self):
+ try:
+ getattr(self.socket, self._socketShutdownMethod)(1)
+ except socket.error:
+ pass
+ p = interfaces.IHalfCloseableProtocol(self.protocol, None)
+ if p:
+ try:
+ p.writeConnectionLost()
+ except:
+ f = failure.Failure()
+ log.err()
+ self.connectionLost(f)
+
+
+ def readConnectionLost(self, reason):
+ p = interfaces.IHalfCloseableProtocol(self.protocol, None)
+ if p:
+ try:
+ p.readConnectionLost()
+ except:
+ log.err()
+ self.connectionLost(failure.Failure())
+ else:
+ self.connectionLost(reason)
+
+ def connectionLost(self, reason):
+ """See abstract.FileDescriptor.connectionLost().
+ """
+ abstract.FileDescriptor.connectionLost(self, reason)
+ self._closeSocket()
+ protocol = self.protocol
+ del self.protocol
+ del self.socket
+ del self.fileno
+ protocol.connectionLost(reason)
+
+ logstr = "Uninitialized"
+
+ def logPrefix(self):
+ """Return the prefix to log with when I own the logging thread.
+ """
+ return self.logstr
+
+ def getTcpNoDelay(self):
+ return operator.truth(self.socket.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY))
+
+ def setTcpNoDelay(self, enabled):
+ self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, enabled)
+
+ def getTcpKeepAlive(self):
+ return operator.truth(self.socket.getsockopt(socket.SOL_SOCKET,
+ socket.SO_KEEPALIVE))
+
+ def setTcpKeepAlive(self, enabled):
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, enabled)
+
+if SSL:
+ classImplements(Connection, interfaces.ITLSTransport)
+
+class BaseClient(Connection):
+ """A base class for client TCP (and similiar) sockets.
+ """
+ addressFamily = socket.AF_INET
+ socketType = socket.SOCK_STREAM
+
+ def _finishInit(self, whenDone, skt, error, reactor):
+ """Called by base classes to continue to next stage of initialization."""
+ if whenDone:
+ Connection.__init__(self, skt, None, reactor)
+ self.doWrite = self.doConnect
+ self.doRead = self.doConnect
+ reactor.callLater(0, whenDone)
+ else:
+ reactor.callLater(0, self.failIfNotConnected, error)
+
+ def startTLS(self, ctx, client=1):
+ if Connection.startTLS(self, ctx, client):
+ if client:
+ self.socket.set_connect_state()
+ else:
+ self.socket.set_accept_state()
+
+
+ def stopConnecting(self):
+ """Stop attempt to connect."""
+ self.failIfNotConnected(error.UserError())
+
+ def failIfNotConnected(self, err):
+ """
+ Generic method called when the attemps to connect failed. It basically
+ cleans everything it can: call connectionFailed, stop read and write,
+ delete socket related members.
+ """
+ if (self.connected or self.disconnected or
+ not hasattr(self, "connector")):
+ return
+
+ self.connector.connectionFailed(failure.Failure(err))
+ if hasattr(self, "reactor"):
+ # this doesn't happen if we failed in __init__
+ self.stopReading()
+ self.stopWriting()
+ del self.connector
+
+ try:
+ self._closeSocket()
+ except AttributeError:
+ pass
+ else:
+ del self.socket, self.fileno
+
+ def createInternetSocket(self):
+ """(internal) Create a non-blocking socket using
+ self.addressFamily, self.socketType.
+ """
+ s = socket.socket(self.addressFamily, self.socketType)
+ s.setblocking(0)
+ fdesc._setCloseOnExec(s.fileno())
+ return s
+
+ def resolveAddress(self):
+ if abstract.isIPAddress(self.addr[0]):
+ self._setRealAddress(self.addr[0])
+ else:
+ d = self.reactor.resolve(self.addr[0])
+ d.addCallbacks(self._setRealAddress, self.failIfNotConnected)
+
+ def _setRealAddress(self, address):
+ self.realAddress = (address, self.addr[1])
+ self.doConnect()
+
+ def doConnect(self):
+ """I connect the socket.
+
+ Then, call the protocol's makeConnection, and start waiting for data.
+ """
+ if not hasattr(self, "connector"):
+ # this happens when connection failed but doConnect
+ # was scheduled via a callLater in self._finishInit
+ return
+
+ err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+ if err:
+ self.failIfNotConnected(error.getConnectError((err, strerror(err))))
+ return
+
+
+ # doConnect gets called twice. The first time we actually need to
+ # start the connection attempt. The second time we don't really
+ # want to (SO_ERROR above will have taken care of any errors, and if
+ # it reported none, the mere fact that doConnect was called again is
+ # sufficient to indicate that the connection has succeeded), but it
+ # is not /particularly/ detrimental to do so. This should get
+ # cleaned up some day, though.
+ try:
+ connectResult = self.socket.connect_ex(self.realAddress)
+ except socket.error, se:
+ connectResult = se.args[0]
+ if connectResult:
+ if connectResult == EISCONN:
+ pass
+ # on Windows EINVAL means sometimes that we should keep trying:
+ # http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winsock/winsock/connect_2.asp
+ elif ((connectResult in (EWOULDBLOCK, EINPROGRESS, EALREADY)) or
+ (connectResult == EINVAL and platformType == "win32")):
+ self.startReading()
+ self.startWriting()
+ return
+ else:
+ self.failIfNotConnected(error.getConnectError((connectResult, strerror(connectResult))))
+ return
+
+ # If I have reached this point without raising or returning, that means
+ # that the socket is connected.
+ del self.doWrite
+ del self.doRead
+ # we first stop and then start, to reset any references to the old doRead
+ self.stopReading()
+ self.stopWriting()
+ self._connectDone()
+
+ def _connectDone(self):
+ self.protocol = self.connector.buildProtocol(self.getPeer())
+ self.connected = 1
+ self.logstr = self.protocol.__class__.__name__ + ",client"
+ self.startReading()
+ self.protocol.makeConnection(self)
+
+ def connectionLost(self, reason):
+ if not self.connected:
+ self.failIfNotConnected(error.ConnectError(string=reason))
+ else:
+ Connection.connectionLost(self, reason)
+ self.connector.connectionLost(reason)
+
+
+class Client(BaseClient):
+ """A TCP client."""
+
+ def __init__(self, host, port, bindAddress, connector, reactor=None):
+ # BaseClient.__init__ is invoked later
+ self.connector = connector
+ self.addr = (host, port)
+
+ whenDone = self.resolveAddress
+ err = None
+ skt = None
+
+ try:
+ skt = self.createInternetSocket()
+ except socket.error, se:
+ err = error.ConnectBindError(se[0], se[1])
+ whenDone = None
+ if whenDone and bindAddress is not None:
+ try:
+ skt.bind(bindAddress)
+ except socket.error, se:
+ err = error.ConnectBindError(se[0], se[1])
+ whenDone = None
+ self._finishInit(whenDone, skt, err, reactor)
+
+ def getHost(self):
+ """Returns an IPv4Address.
+
+ This indicates the address from which I am connecting.
+ """
+ return address.IPv4Address('TCP', *(self.socket.getsockname() + ('INET',)))
+
+ def getPeer(self):
+ """Returns an IPv4Address.
+
+ This indicates the address that I am connected to.
+ """
+ return address.IPv4Address('TCP', *(self.realAddress + ('INET',)))
+
+ def __repr__(self):
+ s = '<%s to %s at %x>' % (self.__class__, self.addr, unsignedID(self))
+ return s
+
+
+class Server(Connection):
+ """
+ Serverside socket-stream connection class.
+
+ This is a serverside network connection transport; a socket which came from
+ an accept() on a server.
+ """
+
+ def __init__(self, sock, protocol, client, server, sessionno, reactor):
+ """
+ Server(sock, protocol, client, server, sessionno)
+
+ Initialize it with a socket, a protocol, a descriptor for my peer (a
+ tuple of host, port describing the other end of the connection), an
+ instance of Port, and a session number.
+ """
+ Connection.__init__(self, sock, protocol, reactor)
+ self.server = server
+ self.client = client
+ self.sessionno = sessionno
+ self.hostname = client[0]
+ self.logstr = "%s,%s,%s" % (self.protocol.__class__.__name__,
+ sessionno,
+ self.hostname)
+ self.repstr = "<%s #%s on %s>" % (self.protocol.__class__.__name__,
+ self.sessionno,
+ self.server._realPortNumber)
+ self.startReading()
+ self.connected = 1
+
+ def __repr__(self):
+ """A string representation of this connection.
+ """
+ return self.repstr
+
+ def startTLS(self, ctx, server=1):
+ if Connection.startTLS(self, ctx, server):
+ if server:
+ self.socket.set_accept_state()
+ else:
+ self.socket.set_connect_state()
+
+
+ def getHost(self):
+ """Returns an IPv4Address.
+
+ This indicates the server's address.
+ """
+ return address.IPv4Address('TCP', *(self.socket.getsockname() + ('INET',)))
+
+ def getPeer(self):
+ """Returns an IPv4Address.
+
+ This indicates the client's address.
+ """
+ return address.IPv4Address('TCP', *(self.client + ('INET',)))
+
+class Port(base.BasePort, _SocketCloser):
+ """
+ A TCP server port, listening for connections.
+
+ When a connection is accepted, this will call a factory's buildProtocol
+ with the incoming address as an argument, according to the specification
+ described in L{twisted.internet.interfaces.IProtocolFactory}.
+
+ If you wish to change the sort of transport that will be used, the
+ C{transport} attribute will be called with the signature expected for
+ C{Server.__init__}, so it can be replaced.
+
+ @ivar deferred: a deferred created when L{stopListening} is called, and
+ that will fire when connection is lost. This is not to be used it
+ directly: prefer the deferred returned by L{stopListening} instead.
+ @type deferred: L{defer.Deferred}
+
+ @ivar disconnecting: flag indicating that the L{stopListening} method has
+ been called and that no connections should be accepted anymore.
+ @type disconnecting: C{bool}
+
+ @ivar connected: flag set once the listen has successfully been called on
+ the socket.
+ @type connected: C{bool}
+ """
+
+ implements(interfaces.IListeningPort)
+
+ addressFamily = socket.AF_INET
+ socketType = socket.SOCK_STREAM
+
+ transport = Server
+ sessionno = 0
+ interface = ''
+ backlog = 50
+
+ # Actual port number being listened on, only set to a non-None
+ # value when we are actually listening.
+ _realPortNumber = None
+
+ def __init__(self, port, factory, backlog=50, interface='', reactor=None):
+ """Initialize with a numeric port to listen on.
+ """
+ base.BasePort.__init__(self, reactor=reactor)
+ self.port = port
+ self.factory = factory
+ self.backlog = backlog
+ self.interface = interface
+
+ def __repr__(self):
+ if self._realPortNumber is not None:
+ return "<%s of %s on %s>" % (self.__class__, self.factory.__class__,
+ self._realPortNumber)
+ else:
+ return "<%s of %s (not listening)>" % (self.__class__, self.factory.__class__)
+
+ def createInternetSocket(self):
+ s = base.BasePort.createInternetSocket(self)
+ if platformType == "posix" and sys.platform != "cygwin":
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ return s
+
+
+ def startListening(self):
+ """Create and bind my socket, and begin listening on it.
+
+ This is called on unserialization, and must be called after creating a
+ server to begin listening on the specified port.
+ """
+ try:
+ skt = self.createInternetSocket()
+ skt.bind((self.interface, self.port))
+ except socket.error, le:
+ raise CannotListenError, (self.interface, self.port, le)
+
+ # Make sure that if we listened on port 0, we update that to
+ # reflect what the OS actually assigned us.
+ self._realPortNumber = skt.getsockname()[1]
+
+ log.msg("%s starting on %s" % (self.factory.__class__, self._realPortNumber))
+
+ # The order of the next 6 lines is kind of bizarre. If no one
+ # can explain it, perhaps we should re-arrange them.
+ self.factory.doStart()
+ skt.listen(self.backlog)
+ self.connected = True
+ self.socket = skt
+ self.fileno = self.socket.fileno
+ self.numberAccepts = 100
+
+ self.startReading()
+
+
+ def _buildAddr(self, (host, port)):
+ return address._ServerFactoryIPv4Address('TCP', host, port)
+
+
+ def doRead(self):
+ """Called when my socket is ready for reading.
+
+ This accepts a connection and calls self.protocol() to handle the
+ wire-level protocol.
+ """
+ try:
+ if platformType == "posix":
+ numAccepts = self.numberAccepts
+ else:
+ # win32 event loop breaks if we do more than one accept()
+ # in an iteration of the event loop.
+ numAccepts = 1
+ for i in range(numAccepts):
+ # we need this so we can deal with a factory's buildProtocol
+ # calling our loseConnection
+ if self.disconnecting:
+ return
+ try:
+ skt, addr = self.socket.accept()
+ except socket.error, e:
+ if e.args[0] in (EWOULDBLOCK, EAGAIN):
+ self.numberAccepts = i
+ break
+ elif e.args[0] == EPERM:
+ # Netfilter on Linux may have rejected the
+ # connection, but we get told to try to accept()
+ # anyway.
+ continue
+ elif e.args[0] in (EMFILE, ENOBUFS, ENFILE, ENOMEM, ECONNABORTED):
+
+ # Linux gives EMFILE when a process is not allowed
+ # to allocate any more file descriptors. *BSD and
+ # Win32 give (WSA)ENOBUFS. Linux can also give
+ # ENFILE if the system is out of inodes, or ENOMEM
+ # if there is insufficient memory to allocate a new
+ # dentry. ECONNABORTED is documented as possible on
+ # both Linux and Windows, but it is not clear
+ # whether there are actually any circumstances under
+ # which it can happen (one might expect it to be
+ # possible if a client sends a FIN or RST after the
+ # server sends a SYN|ACK but before application code
+ # calls accept(2), however at least on Linux this
+ # _seems_ to be short-circuited by syncookies.
+
+ log.msg("Could not accept new connection (%s)" % (
+ errorcode[e.args[0]],))
+ break
+ raise
+
+ fdesc._setCloseOnExec(skt.fileno())
+ protocol = self.factory.buildProtocol(self._buildAddr(addr))
+ if protocol is None:
+ skt.close()
+ continue
+ s = self.sessionno
+ self.sessionno = s+1
+ transport = self.transport(skt, protocol, addr, self, s, self.reactor)
+ transport = self._preMakeConnection(transport)
+ protocol.makeConnection(transport)
+ else:
+ self.numberAccepts = self.numberAccepts+20
+ except:
+ # Note that in TLS mode, this will possibly catch SSL.Errors
+ # raised by self.socket.accept()
+ #
+ # There is no "except SSL.Error:" above because SSL may be
+ # None if there is no SSL support. In any case, all the
+ # "except SSL.Error:" suite would probably do is log.deferr()
+ # and return, so handling it here works just as well.
+ log.deferr()
+
+ def _preMakeConnection(self, transport):
+ return transport
+
+ def loseConnection(self, connDone=failure.Failure(main.CONNECTION_DONE)):
+ """
+ Stop accepting connections on this port.
+
+ This will shut down the socket and call self.connectionLost(). It
+ returns a deferred which will fire successfully when the port is
+ actually closed, or with a failure if an error occurs shutting down.
+ """
+ self.disconnecting = True
+ self.stopReading()
+ if self.connected:
+ self.deferred = deferLater(
+ self.reactor, 0, self.connectionLost, connDone)
+ return self.deferred
+
+ stopListening = loseConnection
+
+
+ def connectionLost(self, reason):
+ """
+ Cleans up the socket.
+ """
+ log.msg('(Port %s Closed)' % self._realPortNumber)
+ self._realPortNumber = None
+
+ base.BasePort.connectionLost(self, reason)
+ self.connected = False
+ self._closeSocket()
+ del self.socket
+ del self.fileno
+
+ try:
+ self.factory.doStop()
+ finally:
+ self.disconnecting = False
+
+
+ def logPrefix(self):
+ """Returns the name of my class, to prefix log entries with.
+ """
+ return reflect.qual(self.factory.__class__)
+
+ def getHost(self):
+ """Returns an IPv4Address.
+
+ This indicates the server's address.
+ """
+ return address.IPv4Address('TCP', *(self.socket.getsockname() + ('INET',)))
+
+class Connector(base.BaseConnector):
+ def __init__(self, host, port, factory, timeout, bindAddress, reactor=None):
+ self.host = host
+ if isinstance(port, types.StringTypes):
+ try:
+ port = socket.getservbyname(port, 'tcp')
+ except socket.error, e:
+ raise error.ServiceNameUnknownError(string="%s (%r)" % (e, port))
+ self.port = port
+ self.bindAddress = bindAddress
+ base.BaseConnector.__init__(self, factory, timeout, reactor)
+
+ def _makeTransport(self):
+ return Client(self.host, self.port, self.bindAddress, self, self.reactor)
+
+ def getDestination(self):
+ return address.IPv4Address('TCP', self.host, self.port, 'INET')
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/__init__.py b/vendor/Twisted-10.0.0/twisted/internet/test/__init__.py
new file mode 100644
index 0000000000..2c1f28e448
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.internet}.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/inlinecb_tests.py b/vendor/Twisted-10.0.0/twisted/internet/test/inlinecb_tests.py
new file mode 100644
index 0000000000..2632e971fd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/inlinecb_tests.py
@@ -0,0 +1,92 @@
+# -*- test-case-name: twisted.internet.test.test_inlinecb -*-
+# Copyright (c) 2009-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.internet.defer.inlineCallbacks}.
+
+These tests are defined in a non-C{test_*} module because they are
+syntactically invalid on python < 2.5. test_inlinecb will conditionally import
+these tests on python 2.5 and greater.
+
+Some tests for inlineCallbacks are defined in L{twisted.test.test_defgen} as
+well: see U{http://twistedmatrix.com/trac/ticket/4182}.
+"""
+
+from twisted.trial.unittest import TestCase
+from twisted.internet.defer import Deferred, returnValue, inlineCallbacks
+
+class NonLocalExitTests(TestCase):
+ """
+ It's possible for L{returnValue} to be (accidentally) invoked at a stack
+ level below the L{inlineCallbacks}-decorated function which it is exiting.
+ If this happens, L{returnValue} should report useful errors.
+
+ If L{returnValue} is invoked from a function not decorated by
+ L{inlineCallbacks}, it will emit a warning if it causes an
+ L{inlineCallbacks} function further up the stack to exit.
+ """
+
+ def mistakenMethod(self):
+ """
+ This method mistakenly invokes L{returnValue}, despite the fact that it
+ is not decorated with L{inlineCallbacks}.
+ """
+ returnValue(1)
+
+
+ def assertMistakenMethodWarning(self, resultList):
+ """
+ Flush the current warnings and assert that we have been told that
+ C{mistakenMethod} was invoked, and that the result from the Deferred
+ that was fired (appended to the given list) is C{mistakenMethod}'s
+ result. The warning should indicate that an inlineCallbacks function
+ called 'inline' was made to exit.
+ """
+ self.assertEqual(resultList, [1])
+ warnings = self.flushWarnings(offendingFunctions=[self.mistakenMethod])
+ self.assertEqual(len(warnings), 1)
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warnings[0]['message'],
+ "returnValue() in 'mistakenMethod' causing 'inline' to exit: "
+ "returnValue should only be invoked by functions decorated with "
+ "inlineCallbacks")
+
+
+ def test_returnValueNonLocalWarning(self):
+ """
+ L{returnValue} will emit a non-local exit warning in the simplest case,
+ where the offending function is invoked immediately.
+ """
+ @inlineCallbacks
+ def inline():
+ self.mistakenMethod()
+ returnValue(2)
+ yield 0
+ d = inline()
+ results = []
+ d.addCallback(results.append)
+ self.assertMistakenMethodWarning(results)
+
+
+ def test_returnValueNonLocalDeferred(self):
+ """
+ L{returnValue} will emit a non-local warning in the case where the
+ L{inlineCallbacks}-decorated function has already yielded a Deferred
+ and therefore moved its generator function along.
+ """
+ cause = Deferred()
+ @inlineCallbacks
+ def inline():
+ yield cause
+ self.mistakenMethod()
+ returnValue(2)
+ effect = inline()
+ results = []
+ effect.addCallback(results.append)
+ self.assertEquals(results, [])
+ cause.callback(1)
+ self.assertMistakenMethodWarning(results)
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/process_helper.py b/vendor/Twisted-10.0.0/twisted/internet/test/process_helper.py
new file mode 100644
index 0000000000..b921697ae9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/process_helper.py
@@ -0,0 +1,33 @@
+
+# A program which exits after starting a child which inherits its
+# stdin/stdout/stderr and keeps them open until stdin is closed.
+
+import sys, os
+
+def grandchild():
+ sys.stdout.write('grandchild started')
+ sys.stdout.flush()
+ sys.stdin.read()
+
+def main():
+ if sys.argv[1] == 'child':
+ if sys.argv[2] == 'windows':
+ import win32api as api, win32process as proc
+ info = proc.STARTUPINFO()
+ info.hStdInput = api.GetStdHandle(api.STD_INPUT_HANDLE)
+ info.hStdOutput = api.GetStdHandle(api.STD_OUTPUT_HANDLE)
+ info.hStdError = api.GetStdHandle(api.STD_ERROR_HANDLE)
+ python = sys.executable
+ scriptDir = os.path.dirname(__file__)
+ scriptName = os.path.basename(__file__)
+ proc.CreateProcess(
+ None, " ".join((python, scriptName, "grandchild")), None,
+ None, 1, 0, os.environ, scriptDir, info)
+ else:
+ if os.fork() == 0:
+ grandchild()
+ else:
+ grandchild()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/reactormixins.py b/vendor/Twisted-10.0.0/twisted/internet/test/reactormixins.py
new file mode 100644
index 0000000000..2895daf250
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/reactormixins.py
@@ -0,0 +1,193 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorTime}.
+"""
+
+__metaclass__ = type
+
+import signal
+
+from twisted.internet.defer import TimeoutError
+from twisted.trial.unittest import TestCase, SkipTest
+from twisted.python.runtime import platformType
+from twisted.python.reflect import namedAny
+from twisted.python import log
+from twisted.python.failure import Failure
+
+# Access private APIs.
+if platformType == 'posix':
+ from twisted.internet import process
+else:
+ process = None
+
+
+class ReactorBuilder:
+ """
+ L{TestCase} mixin which provides a reactor-creation API. This mixin
+ defines C{setUp} and C{tearDown}, so mix it in before L{TestCase} or call
+ its methods from the overridden ones in the subclass.
+
+ @cvar skippedReactors: A dict mapping FQPN strings of reactors for
+ which the tests defined by this class will be skipped to strings
+ giving the skip message.
+ @cvar requiredInterfaces: A C{list} of interfaces which the reactor must
+ provide or these tests will be skipped. The default, C{None}, means
+ that no interfaces are required.
+ @ivar reactorFactory: A no-argument callable which returns the reactor to
+ use for testing.
+ @ivar originalHandler: The SIGCHLD handler which was installed when setUp
+ ran and which will be re-installed when tearDown runs.
+ @ivar _reactors: A list of FQPN strings giving the reactors for which
+ TestCases will be created.
+ """
+
+ _reactors = ["twisted.internet.selectreactor.SelectReactor",
+ "twisted.internet.pollreactor.PollReactor",
+ "twisted.internet.epollreactor.EPollReactor",
+ "twisted.internet.glib2reactor.Glib2Reactor",
+ "twisted.internet.gtk2reactor.Gtk2Reactor",
+ "twisted.internet.kqueuereactor.KQueueReactor",
+ "twisted.internet.win32eventreactor.Win32Reactor",
+ "twisted.internet.iocpreactor.reactor.IOCPReactor"]
+
+ reactorFactory = None
+ originalHandler = None
+ requiredInterface = None
+ skippedReactors = {}
+
+ def setUp(self):
+ """
+ Clear the SIGCHLD handler, if there is one, to ensure an environment
+ like the one which exists prior to a call to L{reactor.run}.
+ """
+ if platformType == 'posix':
+ self.originalHandler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
+
+ def tearDown(self):
+ """
+ Restore the original SIGCHLD handler and reap processes as long as
+ there seem to be any remaining.
+ """
+ if self.originalHandler is not None:
+ signal.signal(signal.SIGCHLD, self.originalHandler)
+ if process is not None:
+ while process.reapProcessHandlers:
+ log.msg(
+ "ReactorBuilder.tearDown reaping some processes %r" % (
+ process.reapProcessHandlers,))
+ process.reapAllProcesses()
+
+
+ def unbuildReactor(self, reactor):
+ """
+ Clean up any resources which may have been allocated for the given
+ reactor by its creation or by a test which used it.
+ """
+ # Chris says:
+ #
+ # XXX These explicit calls to clean up the waker (and any other
+ # internal readers) should become obsolete when bug #3063 is
+ # fixed. -radix, 2008-02-29. Fortunately it should probably cause an
+ # error when bug #3063 is fixed, so it should be removed in the same
+ # branch that fixes it.
+ #
+ # -exarkun
+ if getattr(reactor, '_internalReaders', None) is not None:
+ for reader in reactor._internalReaders:
+ reactor.removeReader(reader)
+ reader.connectionLost(None)
+ reactor._internalReaders.clear()
+
+ # Here's an extra thing unrelated to wakers but necessary for
+ # cleaning up after the reactors we make. -exarkun
+ reactor.disconnectAll()
+
+ # It would also be bad if any timed calls left over were allowed to
+ # run.
+ calls = reactor.getDelayedCalls()
+ for c in calls:
+ c.cancel()
+
+
+ def buildReactor(self):
+ """
+ Create and return a reactor using C{self.reactorFactory}.
+ """
+ try:
+ reactor = self.reactorFactory()
+ except:
+ # Unfortunately, not all errors which result in a reactor being
+ # unusable are detectable without actually instantiating the
+ # reactor. So we catch some more here and skip the test if
+ # necessary.
+ raise SkipTest(Failure().getErrorMessage())
+ else:
+ if self.requiredInterface is not None:
+ if not self.requiredInterface.providedBy(reactor):
+ self.unbuildReactor(reactor)
+ raise SkipTest(
+ "%r does not provide %r" % (
+ reactor, self.requiredInterface))
+ self.addCleanup(self.unbuildReactor, reactor)
+ return reactor
+
+
+ def runReactor(self, reactor, timeout=None):
+ """
+ Run the reactor for at most the given amount of time.
+
+ @param reactor: The reactor to run.
+
+ @type timeout: C{int} or C{float}
+ @param timeout: The maximum amount of time, specified in seconds, to
+ allow the reactor to run. If the reactor is still running after
+ this much time has elapsed, it will be stopped and an exception
+ raised. If C{None}, the default test method timeout imposed by
+ Trial will be used. This depends on the L{IReactorTime}
+ implementation of C{reactor} for correct operation.
+
+ @raise TimeoutError: If the reactor is still running after C{timeout}
+ seconds.
+ """
+ if timeout is None:
+ timeout = self.getTimeout()
+
+ timedOut = []
+ def stop():
+ timedOut.append(None)
+ reactor.stop()
+
+ reactor.callLater(timeout, stop)
+ reactor.run()
+ if timedOut:
+ raise TimeoutError(
+ "reactor still running after %s seconds" % (timeout,))
+
+
+ def makeTestCaseClasses(cls):
+ """
+ Create a L{TestCase} subclass which mixes in C{cls} for each known
+ reactor and return a dict mapping their names to them.
+ """
+ classes = {}
+ for reactor in cls._reactors:
+ shortReactorName = reactor.split(".")[-1]
+ name = (cls.__name__ + "." + shortReactorName).replace(".", "_")
+ class testcase(cls, TestCase):
+ __module__ = cls.__module__
+ if reactor in cls.skippedReactors:
+ skip = cls.skippedReactors[reactor]
+ try:
+ reactorFactory = namedAny(reactor)
+ except:
+ skip = Failure().getErrorMessage()
+ testcase.__name__ = name
+ classes[testcase.__name__] = testcase
+ return classes
+ makeTestCaseClasses = classmethod(makeTestCaseClasses)
+
+
+__all__ = ['ReactorBuilder']
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_base.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_base.py
new file mode 100644
index 0000000000..8e1e0fb022
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_base.py
@@ -0,0 +1,179 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.internet.base}.
+"""
+
+import socket
+from Queue import Queue
+
+from zope.interface import implements
+
+from twisted.python.threadpool import ThreadPool
+from twisted.python.util import setIDFunction
+from twisted.internet.interfaces import IReactorTime, IReactorThreads
+from twisted.internet.error import DNSLookupError
+from twisted.internet.base import ThreadedResolver, DelayedCall
+from twisted.internet.task import Clock
+from twisted.trial.unittest import TestCase
+
+
+class FakeReactor(object):
+ """
+ A fake reactor implementation which just supports enough reactor APIs for
+ L{ThreadedResolver}.
+ """
+ implements(IReactorTime, IReactorThreads)
+
+ def __init__(self):
+ self._clock = Clock()
+ self.callLater = self._clock.callLater
+
+ self._threadpool = ThreadPool()
+ self._threadpool.start()
+ self.getThreadPool = lambda: self._threadpool
+
+ self._threadCalls = Queue()
+
+
+ def callFromThread(self, f, *args, **kwargs):
+ self._threadCalls.put((f, args, kwargs))
+
+
+ def _runThreadCalls(self):
+ f, args, kwargs = self._threadCalls.get()
+ f(*args, **kwargs)
+
+
+ def _stop(self):
+ self._threadpool.stop()
+
+
+
+class ThreadedResolverTests(TestCase):
+ """
+ Tests for L{ThreadedResolver}.
+ """
+ def test_success(self):
+ """
+ L{ThreadedResolver.getHostByName} returns a L{Deferred} which fires
+ with the value returned by the call to L{socket.gethostbyname} in the
+ threadpool of the reactor passed to L{ThreadedResolver.__init__}.
+ """
+ ip = "10.0.0.17"
+ name = "foo.bar.example.com"
+ timeout = 30
+
+ reactor = FakeReactor()
+ self.addCleanup(reactor._stop)
+
+ lookedUp = []
+ resolvedTo = []
+ def fakeGetHostByName(name):
+ lookedUp.append(name)
+ return ip
+
+ self.patch(socket, 'gethostbyname', fakeGetHostByName)
+
+ resolver = ThreadedResolver(reactor)
+ d = resolver.getHostByName(name, (timeout,))
+ d.addCallback(resolvedTo.append)
+
+ reactor._runThreadCalls()
+
+ self.assertEqual(lookedUp, [name])
+ self.assertEqual(resolvedTo, [ip])
+
+ # Make sure that any timeout-related stuff gets cleaned up.
+ reactor._clock.advance(timeout + 1)
+ self.assertEqual(reactor._clock.calls, [])
+
+
+ def test_failure(self):
+ """
+ L{ThreadedResolver.getHostByName} returns a L{Deferred} which fires a
+ L{Failure} if the call to L{socket.gethostbyname} raises an exception.
+ """
+ timeout = 30
+
+ reactor = FakeReactor()
+ self.addCleanup(reactor._stop)
+
+ def fakeGetHostByName(name):
+ raise IOError("ENOBUFS (this is a funny joke)")
+
+ self.patch(socket, 'gethostbyname', fakeGetHostByName)
+
+ failedWith = []
+ resolver = ThreadedResolver(reactor)
+ d = resolver.getHostByName("some.name", (timeout,))
+ self.assertFailure(d, DNSLookupError)
+ d.addCallback(failedWith.append)
+
+ reactor._runThreadCalls()
+
+ self.assertEqual(len(failedWith), 1)
+
+ # Make sure that any timeout-related stuff gets cleaned up.
+ reactor._clock.advance(timeout + 1)
+ self.assertEqual(reactor._clock.calls, [])
+
+
+ def test_timeout(self):
+ """
+ If L{socket.gethostbyname} does not complete before the specified
+ timeout elapsed, the L{Deferred} returned by
+ L{ThreadedResolver.getHostByBame} fails with L{DNSLookupError}.
+ """
+ timeout = 10
+
+ reactor = FakeReactor()
+ self.addCleanup(reactor._stop)
+
+ result = Queue()
+ def fakeGetHostByName(name):
+ raise result.get()
+
+ self.patch(socket, 'gethostbyname', fakeGetHostByName)
+
+ failedWith = []
+ resolver = ThreadedResolver(reactor)
+ d = resolver.getHostByName("some.name", (timeout,))
+ self.assertFailure(d, DNSLookupError)
+ d.addCallback(failedWith.append)
+
+ reactor._clock.advance(timeout - 1)
+ self.assertEqual(failedWith, [])
+ reactor._clock.advance(1)
+ self.assertEqual(len(failedWith), 1)
+
+ # Eventually the socket.gethostbyname does finish - in this case, with
+ # an exception. Nobody cares, though.
+ result.put(IOError("The I/O was errorful"))
+
+
+
+class DelayedCallTests(TestCase):
+ """
+ Tests for L{DelayedCall}.
+ """
+ def test_str(self):
+ """
+ The string representation of a L{DelayedCall} instance, as returned by
+ C{str}, includes the unsigned id of the instance, as well as its state,
+ the function to be called, and the function arguments.
+ """
+ def nothing():
+ pass
+ dc = DelayedCall(12, nothing, (3, ), {"A": 5}, None, None, lambda: 1.5)
+ ids = {dc: 200}
+ def fakeID(obj):
+ try:
+ return ids[obj]
+ except (TypeError, KeyError):
+ return id(obj)
+ self.addCleanup(setIDFunction, setIDFunction(fakeID))
+ self.assertEquals(
+ str(dc),
+ "<DelayedCall 0xc8 [10.5s] called=0 cancelled=0 nothing(3, A=5)>")
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_baseprocess.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_baseprocess.py
new file mode 100644
index 0000000000..3c6a8ff065
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_baseprocess.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.internet._baseprocess} which implements process-related
+functionality that is useful in all platforms supporting L{IReactorProcess}.
+"""
+
+__metaclass__ = type
+
+from twisted.python.deprecate import getWarningMethod, setWarningMethod
+from twisted.trial.unittest import TestCase
+from twisted.internet._baseprocess import BaseProcess
+
+
+class BaseProcessTests(TestCase):
+ """
+ Tests for L{BaseProcess}, a parent class for other classes which represent
+ processes which implements functionality common to many different process
+ implementations.
+ """
+ def test_callProcessExited(self):
+ """
+ L{BaseProcess._callProcessExited} calls the C{processExited} method of
+ its C{proto} attribute and passes it a L{Failure} wrapping the given
+ exception.
+ """
+ class FakeProto:
+ reason = None
+
+ def processExited(self, reason):
+ self.reason = reason
+
+ reason = RuntimeError("fake reason")
+ process = BaseProcess(FakeProto())
+ process._callProcessExited(reason)
+ process.proto.reason.trap(RuntimeError)
+ self.assertIdentical(reason, process.proto.reason.value)
+
+
+ def test_callProcessExitedMissing(self):
+ """
+ L{BaseProcess._callProcessExited} emits a L{DeprecationWarning} if the
+ object referred to by its C{proto} attribute has no C{processExited}
+ method.
+ """
+ class FakeProto:
+ pass
+
+ reason = object()
+ process = BaseProcess(FakeProto())
+
+ self.addCleanup(setWarningMethod, getWarningMethod())
+ warnings = []
+ def collect(message, category, stacklevel):
+ warnings.append((message, category, stacklevel))
+ setWarningMethod(collect)
+
+ process._callProcessExited(reason)
+
+ [(message, category, stacklevel)] = warnings
+ self.assertEqual(
+ message,
+ "Since Twisted 8.2, IProcessProtocol.processExited is required. "
+ "%s.%s must implement it." % (
+ FakeProto.__module__, FakeProto.__name__))
+ self.assertIdentical(category, DeprecationWarning)
+ # The stacklevel doesn't really make sense for this kind of
+ # deprecation. Requiring it to be 0 will at least avoid pointing to
+ # any part of Twisted or a random part of the application's code, which
+ # I think would be more misleading than having it point inside the
+ # warning system itself. -exarkun
+ self.assertEqual(stacklevel, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_core.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_core.py
new file mode 100644
index 0000000000..3079096fad
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_core.py
@@ -0,0 +1,275 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorCore}.
+"""
+
+__metaclass__ = type
+
+import signal
+import time
+
+from twisted.internet.abstract import FileDescriptor
+from twisted.internet.error import ReactorAlreadyRunning
+from twisted.internet.defer import Deferred
+from twisted.internet.test.reactormixins import ReactorBuilder
+
+
+class SystemEventTestsBuilder(ReactorBuilder):
+ """
+ Builder defining tests relating to L{IReactorCore.addSystemEventTrigger}
+ and L{IReactorCore.fireSystemEvent}.
+ """
+ def test_stopWhenNotStarted(self):
+ """
+ C{reactor.stop()} raises L{RuntimeError} when called when the reactor
+ has not been started.
+ """
+ reactor = self.buildReactor()
+ self.assertRaises(RuntimeError, reactor.stop)
+
+
+ def test_stopWhenAlreadyStopped(self):
+ """
+ C{reactor.stop()} raises L{RuntimeError} when called after the reactor
+ has been stopped.
+ """
+ reactor = self.buildReactor()
+ reactor.callWhenRunning(reactor.stop)
+ reactor.run()
+ self.assertRaises(RuntimeError, reactor.stop)
+
+
+ def test_callWhenRunningOrder(self):
+ """
+ Functions are run in the order that they were passed to
+ L{reactor.callWhenRunning}.
+ """
+ reactor = self.buildReactor()
+ events = []
+ reactor.callWhenRunning(events.append, "first")
+ reactor.callWhenRunning(events.append, "second")
+ reactor.callWhenRunning(reactor.stop)
+ reactor.run()
+ self.assertEqual(events, ["first", "second"])
+
+
+ def test_runningForStartupEvents(self):
+ """
+ The reactor is not running when C{"before"} C{"startup"} triggers are
+ called and is running when C{"during"} and C{"after"} C{"startup"}
+ triggers are called.
+ """
+ reactor = self.buildReactor()
+ state = {}
+ def beforeStartup():
+ state['before'] = reactor.running
+ def duringStartup():
+ state['during'] = reactor.running
+ def afterStartup():
+ state['after'] = reactor.running
+ reactor.addSystemEventTrigger("before", "startup", beforeStartup)
+ reactor.addSystemEventTrigger("during", "startup", duringStartup)
+ reactor.addSystemEventTrigger("after", "startup", afterStartup)
+ reactor.callWhenRunning(reactor.stop)
+ self.assertEqual(state, {})
+ reactor.run()
+ self.assertEqual(
+ state,
+ {"before": False,
+ "during": True,
+ "after": True})
+
+
+ def test_signalHandlersInstalledDuringStartup(self):
+ """
+ Signal handlers are installed in responsed to the C{"during"}
+ C{"startup"}.
+ """
+ reactor = self.buildReactor()
+ phase = [None]
+ def beforeStartup():
+ phase[0] = "before"
+ def afterStartup():
+ phase[0] = "after"
+ reactor.addSystemEventTrigger("before", "startup", beforeStartup)
+ reactor.addSystemEventTrigger("after", "startup", afterStartup)
+
+ sawPhase = [None]
+ def fakeSignal(signum, action):
+ sawPhase[0] = phase[0]
+ self.patch(signal, 'signal', fakeSignal)
+ reactor.callWhenRunning(reactor.stop)
+ self.assertEqual(phase[0], None)
+ self.assertEqual(sawPhase[0], None)
+ reactor.run()
+ self.assertEqual(sawPhase[0], "before")
+ self.assertEqual(phase[0], "after")
+
+
+ def test_stopShutDownEvents(self):
+ """
+ C{reactor.stop()} fires all three phases of shutdown event triggers
+ before it makes C{reactor.run()} return.
+ """
+ reactor = self.buildReactor()
+ events = []
+ reactor.addSystemEventTrigger(
+ "before", "shutdown",
+ lambda: events.append(("before", "shutdown")))
+ reactor.addSystemEventTrigger(
+ "during", "shutdown",
+ lambda: events.append(("during", "shutdown")))
+ reactor.addSystemEventTrigger(
+ "after", "shutdown",
+ lambda: events.append(("after", "shutdown")))
+ reactor.callWhenRunning(reactor.stop)
+ reactor.run()
+ self.assertEquals(events, [("before", "shutdown"),
+ ("during", "shutdown"),
+ ("after", "shutdown")])
+
+
+ def test_shutdownFiresTriggersAsynchronously(self):
+ """
+ C{"before"} C{"shutdown"} triggers are not run synchronously from
+ L{reactor.stop}.
+ """
+ reactor = self.buildReactor()
+ events = []
+ reactor.addSystemEventTrigger(
+ "before", "shutdown", events.append, "before shutdown")
+ def stopIt():
+ reactor.stop()
+ events.append("stopped")
+ reactor.callWhenRunning(stopIt)
+ self.assertEqual(events, [])
+ reactor.run()
+ self.assertEqual(events, ["stopped", "before shutdown"])
+
+
+ def test_shutdownDisconnectsCleanly(self):
+ """
+ A L{IFileDescriptor.connectionLost} implementation which raises an
+ exception does not prevent the remaining L{IFileDescriptor}s from
+ having their C{connectionLost} method called.
+ """
+ lostOK = [False]
+
+ # Subclass FileDescriptor to get logPrefix
+ class ProblematicFileDescriptor(FileDescriptor):
+ def connectionLost(self, reason):
+ raise RuntimeError("simulated connectionLost error")
+
+ class OKFileDescriptor(FileDescriptor):
+ def connectionLost(self, reason):
+ lostOK[0] = True
+
+ reactor = self.buildReactor()
+
+ # Unfortunately, it is necessary to patch removeAll to directly control
+ # the order of the returned values. The test is only valid if
+ # ProblematicFileDescriptor comes first. Also, return these
+ # descriptors only the first time removeAll is called so that if it is
+ # called again the file descriptors aren't re-disconnected.
+ fds = iter([ProblematicFileDescriptor(), OKFileDescriptor()])
+ reactor.removeAll = lambda: fds
+ reactor.callWhenRunning(reactor.stop)
+ self.runReactor(reactor)
+ self.assertEqual(len(self.flushLoggedErrors(RuntimeError)), 1)
+ self.assertTrue(lostOK[0])
+
+
+ def test_multipleRun(self):
+ """
+ C{reactor.run()} raises L{ReactorAlreadyRunning} when called when
+ the reactor is already running.
+ """
+ events = []
+ def reentrantRun():
+ self.assertRaises(ReactorAlreadyRunning, reactor.run)
+ events.append("tested")
+ reactor = self.buildReactor()
+ reactor.callWhenRunning(reentrantRun)
+ reactor.callWhenRunning(reactor.stop)
+ reactor.run()
+ self.assertEqual(events, ["tested"])
+
+
+ def test_runWithAsynchronousBeforeStartupTrigger(self):
+ """
+ When there is a C{'before'} C{'startup'} trigger which returns an
+ unfired L{Deferred}, C{reactor.run()} starts the reactor and does not
+ return until after C{reactor.stop()} is called
+ """
+ events = []
+ def trigger():
+ events.append('trigger')
+ d = Deferred()
+ d.addCallback(callback)
+ reactor.callLater(0, d.callback, None)
+ return d
+ def callback(ignored):
+ events.append('callback')
+ reactor.stop()
+ reactor = self.buildReactor()
+ reactor.addSystemEventTrigger('before', 'startup', trigger)
+ reactor.run()
+ self.assertEqual(events, ['trigger', 'callback'])
+
+
+ def test_iterate(self):
+ """
+ C{reactor.iterate()} does not block.
+ """
+ reactor = self.buildReactor()
+ t = reactor.callLater(5, reactor.crash)
+
+ start = time.time()
+ reactor.iterate(0) # Shouldn't block
+ elapsed = time.time() - start
+
+ self.failUnless(elapsed < 2)
+ t.cancel()
+
+
+ def test_crash(self):
+ """
+ C{reactor.crash()} stops the reactor and does not fire shutdown
+ triggers.
+ """
+ reactor = self.buildReactor()
+ events = []
+ reactor.addSystemEventTrigger(
+ "before", "shutdown",
+ lambda: events.append(("before", "shutdown")))
+ reactor.callWhenRunning(reactor.callLater, 0, reactor.crash)
+ reactor.run()
+ self.assertFalse(reactor.running)
+ self.assertFalse(
+ events,
+ "Shutdown triggers invoked but they should not have been.")
+
+
+ def test_runAfterCrash(self):
+ """
+ C{reactor.run()} restarts the reactor after it has been stopped by
+ C{reactor.crash()}.
+ """
+ events = []
+ def crash():
+ events.append('crash')
+ reactor.crash()
+ reactor = self.buildReactor()
+ reactor.callWhenRunning(crash)
+ reactor.run()
+ def stop():
+ events.append(('stop', reactor.running))
+ reactor.stop()
+ reactor.callWhenRunning(stop)
+ reactor.run()
+ self.assertEqual(events, ['crash', ('stop', True)])
+
+
+globals().update(SystemEventTestsBuilder.makeTestCaseClasses())
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_fdset.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_fdset.py
new file mode 100644
index 0000000000..da1aa4673a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_fdset.py
@@ -0,0 +1,209 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorFDSet}.
+"""
+
+__metaclass__ = type
+
+import socket
+
+from twisted.internet.interfaces import IReactorFDSet
+from twisted.internet.abstract import FileDescriptor
+from twisted.internet.test.reactormixins import ReactorBuilder
+
+# twisted.internet.tcp nicely defines some names with proper values on
+# several different platforms.
+from twisted.internet.tcp import EINPROGRESS, EWOULDBLOCK
+
+
+class ReactorFDSetTestsBuilder(ReactorBuilder):
+ """
+ Builder defining tests relating to L{IReactorFDSet}.
+ """
+ requiredInterface = IReactorFDSet
+
+ def _connectedPair(self):
+ """
+ Return the two sockets which make up a new TCP connection.
+ """
+ serverSocket = socket.socket()
+ serverSocket.bind(('127.0.0.1', 0))
+ serverSocket.listen(1)
+ self.addCleanup(serverSocket.close)
+
+ client = socket.socket()
+ self.addCleanup(client.close)
+ client.setblocking(False)
+ try:
+ client.connect(('127.0.0.1', serverSocket.getsockname()[1]))
+ except socket.error, e:
+ self.assertIn(e.args[0], (EINPROGRESS, EWOULDBLOCK))
+ else:
+ self.fail("Connect should have raised EINPROGRESS or EWOULDBLOCK")
+ server, addr = serverSocket.accept()
+ self.addCleanup(server.close)
+
+ return client, server
+
+
+ def _simpleSetup(self):
+ reactor = self.buildReactor()
+
+ client, server = self._connectedPair()
+
+ fd = FileDescriptor(reactor)
+ fd.fileno = client.fileno
+
+ return reactor, fd, server
+
+
+ def test_addReader(self):
+ """
+ C{reactor.addReader()} accepts an L{IReadDescriptor} provider and calls
+ its C{doRead} method when there may be data available on its C{fileno}.
+ """
+ reactor, fd, server = self._simpleSetup()
+
+ fd.doRead = reactor.stop
+ reactor.addReader(fd)
+ server.sendall('x')
+
+ # The reactor will only stop if it calls fd.doRead.
+ self.runReactor(reactor)
+ # Nothing to assert, just be glad we got this far.
+
+
+ def test_removeReader(self):
+ """
+ L{reactor.removeReader()} accepts an L{IReadDescriptor} provider
+ previously passed to C{reactor.addReader()} and causes it to no longer
+ be monitored for input events.
+ """
+ reactor, fd, server = self._simpleSetup()
+
+ def fail():
+ self.fail("doRead should not be called")
+ fd.doRead = fail
+
+ reactor.addReader(fd)
+ reactor.removeReader(fd)
+ server.sendall('x')
+
+ # Give the reactor two timed event passes to notice that there's I/O
+ # (if it is incorrectly watching for I/O).
+ reactor.callLater(0, reactor.callLater, 0, reactor.stop)
+
+ self.runReactor(reactor)
+ # Getting here means the right thing happened probably.
+
+
+ def test_addWriter(self):
+ """
+ C{reactor.addWriter()} accepts an L{IWriteDescriptor} provider and
+ calls its C{doWrite} method when it may be possible to write to its
+ C{fileno}.
+ """
+ reactor, fd, server = self._simpleSetup()
+
+ fd.doWrite = reactor.stop
+ reactor.addWriter(fd)
+
+ self.runReactor(reactor)
+ # Getting here is great.
+
+
+ def _getFDTest(self, kind):
+ """
+ Helper for getReaders and getWriters tests.
+ """
+ reactor = self.buildReactor()
+ get = getattr(reactor, 'get' + kind + 's')
+ add = getattr(reactor, 'add' + kind)
+ remove = getattr(reactor, 'remove' + kind)
+
+ client, server = self._connectedPair()
+
+ self.assertNotIn(client, get())
+ self.assertNotIn(server, get())
+
+ add(client)
+ self.assertIn(client, get())
+ self.assertNotIn(server, get())
+
+ remove(client)
+ self.assertNotIn(client, get())
+ self.assertNotIn(server, get())
+
+
+ def test_getReaders(self):
+ """
+ L{IReactorFDSet.getReaders} reflects the additions and removals made
+ with L{IReactorFDSet.addReader} and L{IReactorFDSet.removeReader}.
+ """
+ self._getFDTest('Reader')
+
+
+ def test_removeWriter(self):
+ """
+ L{reactor.removeWriter()} accepts an L{IWriteDescriptor} provider
+ previously passed to C{reactor.addWriter()} and causes it to no longer
+ be monitored for outputability.
+ """
+ reactor, fd, server = self._simpleSetup()
+
+ def fail():
+ self.fail("doWrite should not be called")
+ fd.doWrite = fail
+
+ reactor.addWriter(fd)
+ reactor.removeWriter(fd)
+
+ # Give the reactor two timed event passes to notice that there's I/O
+ # (if it is incorrectly watching for I/O).
+ reactor.callLater(0, reactor.callLater, 0, reactor.stop)
+
+ self.runReactor(reactor)
+ # Getting here means the right thing happened probably.
+
+
+ def test_getWriters(self):
+ """
+ L{IReactorFDSet.getWriters} reflects the additions and removals made
+ with L{IReactorFDSet.addWriter} and L{IReactorFDSet.removeWriter}.
+ """
+ self._getFDTest('Writer')
+
+
+ def test_removeAll(self):
+ """
+ C{reactor.removeAll()} removes all registered L{IReadDescriptor}
+ providers and all registered L{IWriteDescriptor} providers and returns
+ them.
+ """
+ reactor = self.buildReactor()
+
+ reactor, fd, server = self._simpleSetup()
+
+ fd.doRead = lambda: self.fail("doRead should not be called")
+ fd.doWrite = lambda: self.fail("doWrite should not be called")
+
+ server.sendall('x')
+
+ reactor.addReader(fd)
+ reactor.addWriter(fd)
+
+ removed = reactor.removeAll()
+
+ # Give the reactor two timed event passes to notice that there's I/O
+ # (if it is incorrectly watching for I/O).
+ reactor.callLater(0, reactor.callLater, 0, reactor.stop)
+
+ self.runReactor(reactor)
+ # Getting here means the right thing happened probably.
+
+ self.assertEqual(removed, [fd])
+
+
+globals().update(ReactorFDSetTestsBuilder.makeTestCaseClasses())
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_inlinecb.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_inlinecb.py
new file mode 100644
index 0000000000..0a9ab4504c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_inlinecb.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2009-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Conditional import of C{inlinecb_tests} for Python 2.5 and greater.
+"""
+import sys
+
+__all__ = ['NonLocalExitTests']
+
+if sys.version_info[:2] >= (2, 5):
+ from twisted.internet.test.inlinecb_tests import NonLocalExitTests
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_iocp.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_iocp.py
new file mode 100644
index 0000000000..ed72b9e4c7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_iocp.py
@@ -0,0 +1,105 @@
+from twisted.internet.protocol import ServerFactory, Protocol, ClientCreator
+from twisted.internet.defer import DeferredList, maybeDeferred, Deferred
+from twisted.trial import unittest
+from twisted.internet import reactor
+from twisted.python import log
+
+from zope.interface.verify import verifyClass
+
+class StopStartReadingProtocol(Protocol):
+ def connectionMade(self):
+ self.transport.pauseProducing()
+ self.transport.resumeProducing()
+ reactor.callLater(0, self._beTerrible)
+ self.data = ''
+
+
+ def _beTerrible(self):
+ self.transport.pauseProducing()
+ self.transport.resumeProducing()
+ reactor.callLater(0, self._beMoreTerrible)
+
+
+ def _beMoreTerrible(self):
+ self.transport.pauseProducing()
+ self.transport.resumeProducing()
+ reactor.callLater(0, self.factory.ready_d.callback, self)
+
+
+ def dataReceived(self, data):
+ log.msg('got data', len(data))
+ self.data += data
+ if len(self.data) == 4*self.transport.readBufferSize:
+ self.factory.stop_d.callback(self.data)
+
+
+
+class IOCPReactorTestCase(unittest.TestCase):
+ def test_noPendingTimerEvents(self):
+ """
+ Test reactor behavior (doIteration) when there are no pending time
+ events.
+ """
+ from twisted.internet.iocpreactor.reactor import IOCPReactor
+ ir = IOCPReactor()
+ ir.wakeUp()
+ self.failIf(ir.doIteration(None))
+
+
+ def test_stopStartReading(self):
+ """
+ This test checks transport read state! There are three bits
+ of it:
+ 1) The transport producer is paused -- transport.reading
+ is False)
+ 2) The transport is about to schedule an OS read, on the next
+ reactor iteration -- transport._readScheduled
+ 3) The OS has a pending asynchronous read on our behalf --
+ transport._readScheduledInOS
+ if 3) is not implemented, it is possible to trick IOCPReactor into
+ scheduling an OS read before the previous one finishes
+ """
+ sf = ServerFactory()
+ sf.protocol = StopStartReadingProtocol
+ sf.ready_d = Deferred()
+ sf.stop_d = Deferred()
+ p = reactor.listenTCP(0, sf)
+ port = p.getHost().port
+ cc = ClientCreator(reactor, Protocol)
+ def proceed(protos, port):
+ log.msg('PROCEEDING WITH THE TESTATHRON')
+ self.assert_(protos[0])
+ self.assert_(protos[1])
+ protos = protos[0][1], protos[1][1]
+ protos[0].transport.write(
+ 'x' * (2 * protos[0].transport.readBufferSize) +
+ 'y' * (2 * protos[0].transport.readBufferSize))
+ return sf.stop_d.addCallback(cleanup, protos, port)
+
+ def cleanup(data, protos, port):
+ self.assert_(data == 'x'*(2*protos[0].transport.readBufferSize)+
+ 'y'*(2*protos[0].transport.readBufferSize),
+ 'did not get the right data')
+ return DeferredList([
+ maybeDeferred(protos[0].transport.loseConnection),
+ maybeDeferred(protos[1].transport.loseConnection),
+ maybeDeferred(port.stopListening)])
+
+ return (DeferredList([cc.connectTCP('127.0.0.1', port), sf.ready_d])
+ .addCallback(proceed, p))
+
+
+ def test_reactorInterfaces(self):
+ """
+ Verify that IOCP socket-representing classes implement IReadWriteHandle
+ """
+ from twisted.internet.iocpreactor.interfaces import IReadWriteHandle
+ from twisted.internet.iocpreactor import tcp, udp
+ verifyClass(IReadWriteHandle, tcp.Connection)
+ verifyClass(IReadWriteHandle, udp.Port)
+
+
+
+if reactor.__class__.__name__ != 'IOCPReactor':
+ IOCPReactorTestCase.skip = 'This test only applies to IOCPReactor'
+
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_pollingfile.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_pollingfile.py
new file mode 100644
index 0000000000..a170d7fb41
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_pollingfile.py
@@ -0,0 +1,39 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.internet._pollingfile}.
+"""
+
+from twisted.python.runtime import platform
+from twisted.trial.unittest import TestCase
+
+if platform.isWindows():
+ from twisted.internet import _pollingfile
+else:
+ _pollingfile = None
+
+
+
+class TestPollableWritePipe(TestCase):
+ """
+ Tests for L{_pollingfile._PollableWritePipe}.
+ """
+
+ def test_checkWorkUnicode(self):
+ """
+ When one tries to pass unicode to L{_pollingfile._PollableWritePipe}, a
+ C{TypeError} is raised instead of passing the data to C{WriteFile}
+ call which is going to mangle it.
+ """
+ p = _pollingfile._PollableWritePipe(1, lambda: None)
+ p.write("test")
+ p.checkWork()
+
+ p.write(u"test")
+ self.assertRaises(TypeError, p.checkWork)
+
+
+
+if _pollingfile is None:
+ TestPollableWritePipe.skip = "_pollingfile is only avalable under Windows."
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_posixbase.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_posixbase.py
new file mode 100644
index 0000000000..4b00359152
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_posixbase.py
@@ -0,0 +1,259 @@
+# Copyright (c) 2009-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.internet.posixbase} and supporting code.
+"""
+
+from twisted.python.compat import set
+from twisted.trial.unittest import TestCase
+from twisted.internet.defer import Deferred
+from twisted.internet.posixbase import PosixReactorBase, _Waker
+from twisted.internet.protocol import ServerFactory
+from twisted.internet.tcp import Port
+from twisted.internet import reactor
+
+
+class TrivialReactor(PosixReactorBase):
+ def __init__(self):
+ self._readers = {}
+ self._writers = {}
+ PosixReactorBase.__init__(self)
+
+
+ def addReader(self, reader):
+ self._readers[reader] = True
+
+
+ def removeReader(self, reader):
+ del self._readers[reader]
+
+
+ def addWriter(self, writer):
+ self._writers[writer] = True
+
+
+ def removeWriter(self, writer):
+ del self._writers[writer]
+
+
+
+class PosixReactorBaseTests(TestCase):
+ """
+ Tests for L{PosixReactorBase}.
+ """
+
+ def _checkWaker(self, reactor):
+ self.assertIsInstance(reactor.waker, _Waker)
+ self.assertIn(reactor.waker, reactor._internalReaders)
+ self.assertIn(reactor.waker, reactor._readers)
+
+
+ def test_wakerIsInternalReader(self):
+ """
+ When L{PosixReactorBase} is instantiated, it creates a waker and adds
+ it to its internal readers set.
+ """
+ reactor = TrivialReactor()
+ self._checkWaker(reactor)
+
+
+ def test_removeAllSkipsInternalReaders(self):
+ """
+ Any L{IReadDescriptors} in L{PosixReactorBase._internalReaders} are
+ left alone by L{PosixReactorBase._removeAll}.
+ """
+ reactor = TrivialReactor()
+ extra = object()
+ reactor._internalReaders.add(extra)
+ reactor.addReader(extra)
+ reactor._removeAll(reactor._readers, reactor._writers)
+ self._checkWaker(reactor)
+ self.assertIn(extra, reactor._internalReaders)
+ self.assertIn(extra, reactor._readers)
+
+
+ def test_removeAllReturnsRemovedDescriptors(self):
+ """
+ L{PosixReactorBase._removeAll} returns a list of removed
+ L{IReadDescriptor} and L{IWriteDescriptor} objects.
+ """
+ reactor = TrivialReactor()
+ reader = object()
+ writer = object()
+ reactor.addReader(reader)
+ reactor.addWriter(writer)
+ removed = reactor._removeAll(
+ reactor._readers, reactor._writers)
+ self.assertEqual(set(removed), set([reader, writer]))
+ self.assertNotIn(reader, reactor._readers)
+ self.assertNotIn(writer, reactor._writers)
+
+
+
+class TCPPortTests(TestCase):
+ """
+ Tests for L{twisted.internet.tcp.Port}.
+ """
+
+ if not isinstance(reactor, PosixReactorBase):
+ skip = "Non-posixbase reactor"
+
+ def test_connectionLostFailed(self):
+ """
+ L{Port.stopListening} returns a L{Deferred} which errbacks if
+ L{Port.connectionLost} raises an exception.
+ """
+ port = Port(12345, ServerFactory())
+ port.connected = True
+ port.connectionLost = lambda reason: 1 / 0
+ return self.assertFailure(port.stopListening(), ZeroDivisionError)
+
+
+
+class TimeoutReportReactor(PosixReactorBase):
+ """
+ A reactor which is just barely runnable and which cannot monitor any
+ readers or writers, and which fires a L{Deferred} with the timeout
+ passed to its C{doIteration} method as soon as that method is invoked.
+ """
+ def __init__(self):
+ PosixReactorBase.__init__(self)
+ self.iterationTimeout = Deferred()
+ self.now = 100
+
+
+ def addReader(self, reader):
+ """
+ Ignore the reader. This is necessary because the waker will be
+ added. However, we won't actually monitor it for any events.
+ """
+
+
+ def removeAll(self):
+ """
+ There are no readers or writers, so there is nothing to remove.
+ This will be called when the reactor stops, though, so it must be
+ implemented.
+ """
+ return []
+
+
+ def seconds(self):
+ """
+ Override the real clock with a deterministic one that can be easily
+ controlled in a unit test.
+ """
+ return self.now
+
+
+ def doIteration(self, timeout):
+ d = self.iterationTimeout
+ if d is not None:
+ self.iterationTimeout = None
+ d.callback(timeout)
+
+
+
+class IterationTimeoutTests(TestCase):
+ """
+ Tests for the timeout argument L{PosixReactorBase.run} calls
+ L{PosixReactorBase.doIteration} with in the presence of various delayed
+ calls.
+ """
+ def _checkIterationTimeout(self, reactor):
+ timeout = []
+ reactor.iterationTimeout.addCallback(timeout.append)
+ reactor.iterationTimeout.addCallback(lambda ignored: reactor.stop())
+ reactor.run()
+ return timeout[0]
+
+
+ def test_noCalls(self):
+ """
+ If there are no delayed calls, C{doIteration} is called with a
+ timeout of C{None}.
+ """
+ reactor = TimeoutReportReactor()
+ timeout = self._checkIterationTimeout(reactor)
+ self.assertEquals(timeout, None)
+
+
+ def test_delayedCall(self):
+ """
+ If there is a delayed call, C{doIteration} is called with a timeout
+ which is the difference between the current time and the time at
+ which that call is to run.
+ """
+ reactor = TimeoutReportReactor()
+ reactor.callLater(100, lambda: None)
+ timeout = self._checkIterationTimeout(reactor)
+ self.assertEquals(timeout, 100)
+
+
+ def test_timePasses(self):
+ """
+ If a delayed call is scheduled and then some time passes, the
+ timeout passed to C{doIteration} is reduced by the amount of time
+ which passed.
+ """
+ reactor = TimeoutReportReactor()
+ reactor.callLater(100, lambda: None)
+ reactor.now += 25
+ timeout = self._checkIterationTimeout(reactor)
+ self.assertEquals(timeout, 75)
+
+
+ def test_multipleDelayedCalls(self):
+ """
+ If there are several delayed calls, C{doIteration} is called with a
+ timeout which is the difference between the current time and the
+ time at which the earlier of the two calls is to run.
+ """
+ reactor = TimeoutReportReactor()
+ reactor.callLater(50, lambda: None)
+ reactor.callLater(10, lambda: None)
+ reactor.callLater(100, lambda: None)
+ timeout = self._checkIterationTimeout(reactor)
+ self.assertEquals(timeout, 10)
+
+
+ def test_resetDelayedCall(self):
+ """
+ If a delayed call is reset, the timeout passed to C{doIteration} is
+ based on the interval between the time when reset is called and the
+ new delay of the call.
+ """
+ reactor = TimeoutReportReactor()
+ call = reactor.callLater(50, lambda: None)
+ reactor.now += 25
+ call.reset(15)
+ timeout = self._checkIterationTimeout(reactor)
+ self.assertEquals(timeout, 15)
+
+
+ def test_delayDelayedCall(self):
+ """
+ If a delayed call is re-delayed, the timeout passed to
+ C{doIteration} is based on the remaining time before the call would
+ have been made and the additional amount of time passed to the delay
+ method.
+ """
+ reactor = TimeoutReportReactor()
+ call = reactor.callLater(50, lambda: None)
+ reactor.now += 10
+ call.delay(20)
+ timeout = self._checkIterationTimeout(reactor)
+ self.assertEquals(timeout, 60)
+
+
+ def test_cancelDelayedCall(self):
+ """
+ If the only delayed call is canceled, C{None} is the timeout passed
+ to C{doIteration}.
+ """
+ reactor = TimeoutReportReactor()
+ call = reactor.callLater(50, lambda: None)
+ call.cancel()
+ timeout = self._checkIterationTimeout(reactor)
+ self.assertEquals(timeout, None)
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_process.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_process.py
new file mode 100644
index 0000000000..fbfb788cdb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_process.py
@@ -0,0 +1,475 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorProcess}.
+"""
+
+__metaclass__ = type
+
+import os, sys, signal, threading
+
+from twisted.trial.unittest import TestCase
+from twisted.internet.test.reactormixins import ReactorBuilder
+from twisted.python.compat import set
+from twisted.python.log import msg, err
+from twisted.python.runtime import platform
+from twisted.python.filepath import FilePath
+from twisted.internet import utils
+from twisted.internet.interfaces import IReactorProcess
+from twisted.internet.defer import Deferred, succeed
+from twisted.internet.protocol import ProcessProtocol
+from twisted.internet.error import ProcessDone, ProcessTerminated
+
+
+class _ShutdownCallbackProcessProtocol(ProcessProtocol):
+ """
+ An L{IProcessProtocol} which fires a Deferred when the process it is
+ associated with ends.
+ """
+ def __init__(self, whenFinished):
+ self.whenFinished = whenFinished
+
+
+ def processEnded(self, reason):
+ self.whenFinished.callback(None)
+
+
+
+class ProcessTestsBuilderBase(ReactorBuilder):
+ """
+ Base class for L{IReactorProcess} tests which defines some tests which
+ can be applied to PTY or non-PTY uses of C{spawnProcess}.
+
+ Subclasses are expected to set the C{usePTY} attribute to C{True} or
+ C{False}.
+ """
+ requiredInterfaces = [IReactorProcess]
+
+ def test_spawnProcessEarlyIsReaped(self):
+ """
+ If, before the reactor is started with L{IReactorCore.run}, a
+ process is started with L{IReactorProcess.spawnProcess} and
+ terminates, the process is reaped once the reactor is started.
+ """
+ reactor = self.buildReactor()
+
+ # Create the process with no shared file descriptors, so that there
+ # are no other events for the reactor to notice and "cheat" with.
+ # We want to be sure it's really dealing with the process exiting,
+ # not some associated event.
+ if self.usePTY:
+ childFDs = None
+ else:
+ childFDs = {}
+
+ # Arrange to notice the SIGCHLD.
+ signaled = threading.Event()
+ def handler(*args):
+ signaled.set()
+ signal.signal(signal.SIGCHLD, handler)
+
+ # Start a process - before starting the reactor!
+ ended = Deferred()
+ reactor.spawnProcess(
+ _ShutdownCallbackProcessProtocol(ended), sys.executable,
+ [sys.executable, "-c", ""], usePTY=self.usePTY, childFDs=childFDs)
+
+ # Wait for the SIGCHLD (which might have been delivered before we got
+ # here, but that's okay because the signal handler was installed above,
+ # before we could have gotten it).
+ signaled.wait(120)
+ if not signaled.isSet():
+ self.fail("Timed out waiting for child process to exit.")
+
+ # Capture the processEnded callback.
+ result = []
+ ended.addCallback(result.append)
+
+ if result:
+ # The synchronous path through spawnProcess / Process.__init__ /
+ # registerReapProcessHandler was encountered. There's no reason to
+ # start the reactor, because everything is done already.
+ return
+
+ # Otherwise, though, start the reactor so it can tell us the process
+ # exited.
+ ended.addCallback(lambda ignored: reactor.stop())
+ self.runReactor(reactor)
+
+ # Make sure the reactor stopped because the Deferred fired.
+ self.assertTrue(result)
+
+ if getattr(signal, 'SIGCHLD', None) is None:
+ test_spawnProcessEarlyIsReaped.skip = (
+ "Platform lacks SIGCHLD, early-spawnProcess test can't work.")
+
+
+ def test_processExitedWithSignal(self):
+ """
+ The C{reason} argument passed to L{IProcessProtocol.processExited} is a
+ L{ProcessTerminated} instance if the child process exits with a signal.
+ """
+ sigName = 'TERM'
+ sigNum = getattr(signal, 'SIG' + sigName)
+ exited = Deferred()
+ source = (
+ "import sys\n"
+ # Talk so the parent process knows the process is running. This is
+ # necessary because ProcessProtocol.makeConnection may be called
+ # before this process is exec'd. It would be unfortunate if we
+ # SIGTERM'd the Twisted process while it was on its way to doing
+ # the exec.
+ "sys.stdout.write('x')\n"
+ "sys.stdout.flush()\n"
+ "sys.stdin.read()\n")
+
+ class Exiter(ProcessProtocol):
+ def childDataReceived(self, fd, data):
+ msg('childDataReceived(%d, %r)' % (fd, data))
+ self.transport.signalProcess(sigName)
+
+ def childConnectionLost(self, fd):
+ msg('childConnectionLost(%d)' % (fd,))
+
+ def processExited(self, reason):
+ msg('processExited(%r)' % (reason,))
+ # Protect the Deferred from the failure so that it follows
+ # the callback chain. This doesn't use the errback chain
+ # because it wants to make sure reason is a Failure. An
+ # Exception would also make an errback-based test pass, and
+ # that would be wrong.
+ exited.callback([reason])
+
+ def processEnded(self, reason):
+ msg('processEnded(%r)' % (reason,))
+
+ reactor = self.buildReactor()
+ reactor.callWhenRunning(
+ reactor.spawnProcess, Exiter(), sys.executable,
+ [sys.executable, "-c", source], usePTY=self.usePTY)
+
+ def cbExited((failure,)):
+ # Trapping implicitly verifies that it's a Failure (rather than
+ # an exception) and explicitly makes sure it's the right type.
+ failure.trap(ProcessTerminated)
+ err = failure.value
+ if platform.isWindows():
+ # Windows can't really /have/ signals, so it certainly can't
+ # report them as the reason for termination. Maybe there's
+ # something better we could be doing here, anyway? Hard to
+ # say. Anyway, this inconsistency between different platforms
+ # is extremely unfortunate and I would remove it if I
+ # could. -exarkun
+ self.assertIdentical(err.signal, None)
+ self.assertEqual(err.exitCode, 1)
+ else:
+ self.assertEqual(err.signal, sigNum)
+ self.assertIdentical(err.exitCode, None)
+
+ exited.addCallback(cbExited)
+ exited.addErrback(err)
+ exited.addCallback(lambda ign: reactor.stop())
+
+ self.runReactor(reactor)
+
+
+
+class ProcessTestsBuilder(ProcessTestsBuilderBase):
+ """
+ Builder defining tests relating to L{IReactorProcess} for child processes
+ which do not have a PTY.
+ """
+ usePTY = False
+
+ keepStdioOpenProgram = FilePath(__file__).sibling('process_helper.py').path
+ if platform.isWindows():
+ keepStdioOpenArg = "windows"
+ else:
+ # Just a value that doesn't equal "windows"
+ keepStdioOpenArg = ""
+
+ # Define this test here because PTY-using processes only have stdin and
+ # stdout and the test would need to be different for that to work.
+ def test_childConnectionLost(self):
+ """
+ L{IProcessProtocol.childConnectionLost} is called each time a file
+ descriptor associated with a child process is closed.
+ """
+ connected = Deferred()
+ lost = {0: Deferred(), 1: Deferred(), 2: Deferred()}
+
+ class Closer(ProcessProtocol):
+ def makeConnection(self, transport):
+ connected.callback(transport)
+
+ def childConnectionLost(self, childFD):
+ lost[childFD].callback(None)
+
+ source = (
+ "import os, sys\n"
+ "while 1:\n"
+ " line = sys.stdin.readline().strip()\n"
+ " if not line:\n"
+ " break\n"
+ " os.close(int(line))\n")
+
+ reactor = self.buildReactor()
+ reactor.callWhenRunning(
+ reactor.spawnProcess, Closer(), sys.executable,
+ [sys.executable, "-c", source], usePTY=self.usePTY)
+
+ def cbConnected(transport):
+ transport.write('2\n')
+ return lost[2].addCallback(lambda ign: transport)
+ connected.addCallback(cbConnected)
+
+ def lostSecond(transport):
+ transport.write('1\n')
+ return lost[1].addCallback(lambda ign: transport)
+ connected.addCallback(lostSecond)
+
+ def lostFirst(transport):
+ transport.write('\n')
+ connected.addCallback(lostFirst)
+ connected.addErrback(err)
+
+ def cbEnded(ignored):
+ reactor.stop()
+ connected.addCallback(cbEnded)
+
+ self.runReactor(reactor)
+
+
+ # This test is here because PTYProcess never delivers childConnectionLost.
+ def test_processEnded(self):
+ """
+ L{IProcessProtocol.processEnded} is called after the child process
+ exits and L{IProcessProtocol.childConnectionLost} is called for each of
+ its file descriptors.
+ """
+ ended = Deferred()
+ lost = []
+
+ class Ender(ProcessProtocol):
+ def childDataReceived(self, fd, data):
+ msg('childDataReceived(%d, %r)' % (fd, data))
+ self.transport.loseConnection()
+
+ def childConnectionLost(self, childFD):
+ msg('childConnectionLost(%d)' % (childFD,))
+ lost.append(childFD)
+
+ def processExited(self, reason):
+ msg('processExited(%r)' % (reason,))
+
+ def processEnded(self, reason):
+ msg('processEnded(%r)' % (reason,))
+ ended.callback([reason])
+
+ reactor = self.buildReactor()
+ reactor.callWhenRunning(
+ reactor.spawnProcess, Ender(), sys.executable,
+ [sys.executable, self.keepStdioOpenProgram, "child",
+ self.keepStdioOpenArg],
+ usePTY=self.usePTY)
+
+ def cbEnded((failure,)):
+ failure.trap(ProcessDone)
+ self.assertEqual(set(lost), set([0, 1, 2]))
+ ended.addCallback(cbEnded)
+
+ ended.addErrback(err)
+ ended.addCallback(lambda ign: reactor.stop())
+
+ self.runReactor(reactor)
+
+
+ # This test is here because PTYProcess.loseConnection does not actually
+ # close the file descriptors to the child process. This test needs to be
+ # written fairly differently for PTYProcess.
+ def test_processExited(self):
+ """
+ L{IProcessProtocol.processExited} is called when the child process
+ exits, even if file descriptors associated with the child are still
+ open.
+ """
+ exited = Deferred()
+ allLost = Deferred()
+ lost = []
+
+ class Waiter(ProcessProtocol):
+ def childDataReceived(self, fd, data):
+ msg('childDataReceived(%d, %r)' % (fd, data))
+
+ def childConnectionLost(self, childFD):
+ msg('childConnectionLost(%d)' % (childFD,))
+ lost.append(childFD)
+ if len(lost) == 3:
+ allLost.callback(None)
+
+ def processExited(self, reason):
+ msg('processExited(%r)' % (reason,))
+ # See test_processExitedWithSignal
+ exited.callback([reason])
+ self.transport.loseConnection()
+
+ reactor = self.buildReactor()
+ reactor.callWhenRunning(
+ reactor.spawnProcess, Waiter(), sys.executable,
+ [sys.executable, self.keepStdioOpenProgram, "child",
+ self.keepStdioOpenArg],
+ usePTY=self.usePTY)
+
+ def cbExited((failure,)):
+ failure.trap(ProcessDone)
+ msg('cbExited; lost = %s' % (lost,))
+ self.assertEqual(lost, [])
+ return allLost
+ exited.addCallback(cbExited)
+
+ def cbAllLost(ignored):
+ self.assertEqual(set(lost), set([0, 1, 2]))
+ exited.addCallback(cbAllLost)
+
+ exited.addErrback(err)
+ exited.addCallback(lambda ign: reactor.stop())
+
+ self.runReactor(reactor)
+
+
+ def makeSourceFile(self, sourceLines):
+ """
+ Write the given list of lines to a text file and return the absolute
+ path to it.
+ """
+ script = self.mktemp()
+ scriptFile = file(script, 'wt')
+ scriptFile.write(os.linesep.join(sourceLines) + os.linesep)
+ scriptFile.close()
+ return os.path.abspath(script)
+
+
+ def test_shebang(self):
+ """
+ Spawning a process with an executable which is a script starting
+ with an interpreter definition line (#!) uses that interpreter to
+ evaluate the script.
+ """
+ SHEBANG_OUTPUT = 'this is the shebang output'
+
+ scriptFile = self.makeSourceFile([
+ "#!%s" % (sys.executable,),
+ "import sys",
+ "sys.stdout.write('%s')" % (SHEBANG_OUTPUT,),
+ "sys.stdout.flush()"])
+ os.chmod(scriptFile, 0700)
+
+ reactor = self.buildReactor()
+
+ def cbProcessExited((out, err, code)):
+ msg("cbProcessExited((%r, %r, %d))" % (out, err, code))
+ self.assertEqual(out, SHEBANG_OUTPUT)
+ self.assertEqual(err, "")
+ self.assertEqual(code, 0)
+
+ def shutdown(passthrough):
+ reactor.stop()
+ return passthrough
+
+ def start():
+ d = utils.getProcessOutputAndValue(scriptFile, reactor=reactor)
+ d.addBoth(shutdown)
+ d.addCallback(cbProcessExited)
+ d.addErrback(err)
+
+ reactor.callWhenRunning(start)
+ self.runReactor(reactor)
+
+
+ def test_processCommandLineArguments(self):
+ """
+ Arguments given to spawnProcess are passed to the child process as
+ originally intended.
+ """
+ source = (
+ # On Windows, stdout is not opened in binary mode by default,
+ # so newline characters are munged on writing, interfering with
+ # the tests.
+ 'import sys, os\n'
+ 'try:\n'
+ ' import msvcrt\n'
+ ' msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)\n'
+ 'except ImportError:\n'
+ ' pass\n'
+ 'for arg in sys.argv[1:]:\n'
+ ' sys.stdout.write(arg + chr(0))\n'
+ ' sys.stdout.flush()')
+
+ args = ['hello', '"', ' \t|<>^&', r'"\\"hello\\"', r'"foo\ bar baz\""']
+ # Ensure that all non-NUL characters can be passed too.
+ args.append(''.join(map(chr, xrange(1, 256))))
+
+ reactor = self.buildReactor()
+
+ def processFinished(output):
+ output = output.split('\0')
+ # Drop the trailing \0.
+ output.pop()
+ self.assertEquals(args, output)
+
+ def shutdown(result):
+ reactor.stop()
+ return result
+
+ def spawnChild():
+ d = succeed(None)
+ d.addCallback(lambda dummy: utils.getProcessOutput(
+ sys.executable, ['-c', source] + args, reactor=reactor))
+ d.addCallback(processFinished)
+ d.addBoth(shutdown)
+
+ reactor.callWhenRunning(spawnChild)
+ self.runReactor(reactor)
+globals().update(ProcessTestsBuilder.makeTestCaseClasses())
+
+
+
+class PTYProcessTestsBuilder(ProcessTestsBuilderBase):
+ """
+ Builder defining tests relating to L{IReactorProcess} for child processes
+ which have a PTY.
+ """
+ usePTY = True
+
+ if platform.isWindows():
+ skip = "PTYs are not supported on Windows."
+ elif platform.isMacOSX():
+ skippedReactors = {
+ "twisted.internet.pollreactor.PollReactor":
+ "OS X's poll() does not support PTYs"}
+globals().update(PTYProcessTestsBuilder.makeTestCaseClasses())
+
+
+
+class PotentialZombieWarningTests(TestCase):
+ """
+ Tests for L{twisted.internet.error.PotentialZombieWarning}.
+ """
+ def test_deprecated(self):
+ """
+ Accessing L{PotentialZombieWarning} via the
+ I{PotentialZombieWarning} attribute of L{twisted.internet.error}
+ results in a deprecation warning being emitted.
+ """
+ from twisted.internet import error
+ error.PotentialZombieWarning
+
+ warnings = self.flushWarnings([self.test_deprecated])
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warnings[0]['message'],
+ "twisted.internet.error.PotentialZombieWarning was deprecated in "
+ "Twisted 10.0.0: There is no longer any potential for zombie "
+ "process.")
+ self.assertEquals(len(warnings), 1)
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_qtreactor.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_qtreactor.py
new file mode 100644
index 0000000000..bdc1956b0e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_qtreactor.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+
+from twisted.trial import unittest
+from twisted.python.runtime import platform
+from twisted.python.util import sibpath
+from twisted.internet.utils import getProcessOutputAndValue
+
+
+skipWindowsNopywin32 = None
+if platform.isWindows():
+ try:
+ import win32process
+ except ImportError:
+ skipWindowsNopywin32 = ("On windows, spawnProcess is not available "
+ "in the absence of win32process.")
+
+class QtreactorTestCase(unittest.TestCase):
+ """
+ Tests for L{twisted.internet.qtreactor}.
+ """
+ def test_importQtreactor(self):
+ """
+ Attempting to import L{twisted.internet.qtreactor} should raise an
+ C{ImportError} indicating that C{qtreactor} is no longer a part of
+ Twisted.
+ """
+ sys.modules["qtreactor"] = None
+ from twisted.plugins.twisted_qtstub import errorMessage
+ try:
+ import twisted.internet.qtreactor
+ except ImportError, e:
+ self.assertEquals(str(e), errorMessage)
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_tcp.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_tcp.py
new file mode 100644
index 0000000000..24e41d2555
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_tcp.py
@@ -0,0 +1,143 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorTCP}.
+"""
+
+__metaclass__ = type
+
+import socket
+
+from zope.interface import implements
+
+from twisted.internet.test.reactormixins import ReactorBuilder
+from twisted.internet.error import DNSLookupError
+from twisted.internet.interfaces import IResolverSimple
+from twisted.internet.address import IPv4Address
+from twisted.internet.defer import succeed, fail
+from twisted.internet.protocol import ServerFactory, ClientFactory, Protocol
+
+
+class Stop(ClientFactory):
+ """
+ A client factory which stops a reactor when a connection attempt fails.
+ """
+ def __init__(self, reactor):
+ self.reactor = reactor
+
+
+ def clientConnectionFailed(self, connector, reason):
+ self.reactor.stop()
+
+
+
+class FakeResolver:
+ """
+ A resolver implementation based on a C{dict} mapping names to addresses.
+ """
+ implements(IResolverSimple)
+
+ def __init__(self, names):
+ self.names = names
+
+
+ def getHostByName(self, name, timeout):
+ try:
+ return succeed(self.names[name])
+ except KeyError:
+ return fail(DNSLookupError("FakeResolver couldn't find " + name))
+
+
+
+class TCPClientTestsBuilder(ReactorBuilder):
+ """
+ Builder defining tests relating to L{IReactorTCP.connectTCP}.
+ """
+ def _freePort(self, interface='127.0.0.1'):
+ probe = socket.socket()
+ try:
+ probe.bind((interface, 0))
+ return probe.getsockname()
+ finally:
+ probe.close()
+
+ def test_clientConnectionFailedStopsReactor(self):
+ """
+ The reactor can be stopped by a client factory's
+ C{clientConnectionFailed} method.
+ """
+ host, port = self._freePort()
+ reactor = self.buildReactor()
+ reactor.connectTCP(host, port, Stop(reactor))
+ reactor.run()
+
+
+ def test_addresses(self):
+ """
+ A client's transport's C{getHost} and C{getPeer} return L{IPv4Address}
+ instances which give the dotted-quad string form of the local and
+ remote endpoints of the connection respectively.
+ """
+ host, port = self._freePort()
+ reactor = self.buildReactor()
+
+ serverFactory = ServerFactory()
+ serverFactory.protocol = Protocol
+ server = reactor.listenTCP(0, serverFactory, interface=host)
+ serverAddress = server.getHost()
+
+ addresses = {'host': None, 'peer': None}
+ class CheckAddress(Protocol):
+ def makeConnection(self, transport):
+ addresses['host'] = transport.getHost()
+ addresses['peer'] = transport.getPeer()
+ reactor.stop()
+
+ clientFactory = Stop(reactor)
+ clientFactory.protocol = CheckAddress
+ client = reactor.connectTCP(
+ 'localhost', server.getHost().port, clientFactory,
+ bindAddress=('127.0.0.1', port))
+
+ reactor.installResolver(FakeResolver({'localhost': '127.0.0.1'}))
+ reactor.run() # self.runReactor(reactor)
+
+ self.assertEqual(
+ addresses['host'],
+ IPv4Address('TCP', '127.0.0.1', port))
+ self.assertEqual(
+ addresses['peer'],
+ IPv4Address('TCP', '127.0.0.1', serverAddress.port))
+
+
+ def test_connectEvent(self):
+ """
+ This test checks that we correctly get notifications event for a
+ client. This ought to prevent a regression under Windows using the GTK2
+ reactor. See #3925.
+ """
+ reactor = self.buildReactor()
+
+ serverFactory = ServerFactory()
+ serverFactory.protocol = Protocol
+ server = reactor.listenTCP(0, serverFactory)
+ connected = []
+
+ class CheckConnection(Protocol):
+ def connectionMade(self):
+ connected.append(self)
+ reactor.stop()
+
+ clientFactory = Stop(reactor)
+ clientFactory.protocol = CheckConnection
+ client = reactor.connectTCP(
+ '127.0.0.1', server.getHost().port, clientFactory)
+
+ reactor.run()
+
+ self.assertTrue(connected)
+
+
+
+globals().update(TCPClientTestsBuilder.makeTestCaseClasses())
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_threads.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_threads.py
new file mode 100644
index 0000000000..95860fc7cd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_threads.py
@@ -0,0 +1,163 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorThreads}.
+"""
+
+__metaclass__ = type
+
+from weakref import ref
+import gc
+
+from twisted.internet.test.reactormixins import ReactorBuilder
+from twisted.python.threadpool import ThreadPool
+
+
+class ThreadTestsBuilder(ReactorBuilder):
+ """
+ Builder for defining tests relating to L{IReactorThreads}.
+ """
+ def test_getThreadPool(self):
+ """
+ C{reactor.getThreadPool()} returns an instance of L{ThreadPool} which
+ starts when C{reactor.run()} is called and stops before it returns.
+ """
+ state = []
+ reactor = self.buildReactor()
+
+ pool = reactor.getThreadPool()
+ self.assertIsInstance(pool, ThreadPool)
+ self.assertFalse(
+ pool.started, "Pool should not start before reactor.run")
+
+ def f():
+ # Record the state for later assertions
+ state.append(pool.started)
+ state.append(pool.joined)
+ reactor.stop()
+
+ reactor.callWhenRunning(f)
+ self.runReactor(reactor, 2)
+
+ self.assertTrue(
+ state[0], "Pool should start after reactor.run")
+ self.assertFalse(
+ state[1], "Pool should not be joined before reactor.stop")
+ self.assertTrue(
+ pool.joined,
+ "Pool should be stopped after reactor.run returns")
+
+
+ def test_suggestThreadPoolSize(self):
+ """
+ C{reactor.suggestThreadPoolSize()} sets the maximum size of the reactor
+ threadpool.
+ """
+ reactor = self.buildReactor()
+ reactor.suggestThreadPoolSize(17)
+ pool = reactor.getThreadPool()
+ self.assertEqual(pool.max, 17)
+
+
+ def test_delayedCallFromThread(self):
+ """
+ A function scheduled with L{IReactorThreads.callFromThread} invoked
+ from a delayed call is run immediately in the next reactor iteration.
+
+ When invoked from the reactor thread, previous implementations of
+ L{IReactorThreads.callFromThread} would skip the pipe/socket based wake
+ up step, assuming the reactor would wake up on its own. However, this
+ resulted in the reactor not noticing a insert into the thread queue at
+ the right time (in this case, after the thread queue has been processed
+ for that reactor iteration).
+ """
+ reactor = self.buildReactor()
+
+ def threadCall():
+ reactor.stop()
+
+ # Set up the use of callFromThread being tested.
+ reactor.callLater(0, reactor.callFromThread, threadCall)
+
+ before = reactor.seconds()
+ self.runReactor(reactor, 60)
+ after = reactor.seconds()
+
+ # We specified a timeout of 60 seconds. The timeout code in runReactor
+ # probably won't actually work, though. If the reactor comes out of
+ # the event notification API just a little bit early, say after 59.9999
+ # seconds instead of after 60 seconds, then the queued thread call will
+ # get processed but the timeout delayed call runReactor sets up won't!
+ # Then the reactor will stop and runReactor will return without the
+ # timeout firing. As it turns out, select() and poll() are quite
+ # likely to return *slightly* earlier than we ask them to, so the
+ # timeout will rarely happen, even if callFromThread is broken. So,
+ # instead we'll measure the elapsed time and make sure it's something
+ # less than about half of the timeout we specified. This is heuristic.
+ # It assumes that select() won't ever return after 30 seconds when we
+ # asked it to timeout after 60 seconds. And of course like all
+ # time-based tests, it's slightly non-deterministic. If the OS doesn't
+ # schedule this process for 30 seconds, then the test might fail even
+ # if callFromThread is working.
+ self.assertTrue(after - before < 30)
+
+
+ def test_stopThreadPool(self):
+ """
+ When the reactor stops, L{ReactorBase._stopThreadPool} drops the
+ reactor's direct reference to its internal threadpool and removes
+ the associated startup and shutdown triggers.
+
+ This is the case of the thread pool being created before the reactor
+ is run.
+ """
+ reactor = self.buildReactor()
+ threadpool = ref(reactor.getThreadPool())
+ reactor.callWhenRunning(reactor.stop)
+ self.runReactor(reactor)
+ gc.collect()
+ self.assertIdentical(threadpool(), None)
+
+
+ def test_stopThreadPoolWhenStartedAfterReactorRan(self):
+ """
+ We must handle the case of shutting down the thread pool when it was
+ started after the reactor was run in a special way.
+
+ Some implementation background: The thread pool is started with
+ callWhenRunning, which only returns a system trigger ID when it is
+ invoked before the reactor is started.
+
+ This is the case of the thread pool being created after the reactor
+ is started.
+ """
+ reactor = self.buildReactor()
+ threadPoolRefs = []
+ def acquireThreadPool():
+ threadPoolRefs.append(ref(reactor.getThreadPool()))
+ reactor.stop()
+ reactor.callWhenRunning(acquireThreadPool)
+ self.runReactor(reactor)
+ gc.collect()
+ self.assertIdentical(threadPoolRefs[0](), None)
+
+
+ def test_cleanUpThreadPoolEvenBeforeReactorIsRun(self):
+ """
+ When the reactor has its shutdown event fired before it is run, the
+ thread pool is completely destroyed.
+
+ For what it's worth, the reason we support this behavior at all is
+ because Trial does this.
+
+ This is the case of the thread pool being created without the reactor
+ being started at al.
+ """
+ reactor = self.buildReactor()
+ threadPoolRef = ref(reactor.getThreadPool())
+ reactor.fireSystemEvent("shutdown")
+ self.assertIdentical(threadPoolRef(), None)
+
+
+globals().update(ThreadTestsBuilder.makeTestCaseClasses())
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_time.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_time.py
new file mode 100644
index 0000000000..57eb29d843
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_time.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorTime}.
+"""
+
+__metaclass__ = type
+
+from twisted.internet.test.reactormixins import ReactorBuilder
+
+
+class TimeTestsBuilder(ReactorBuilder):
+ """
+ Builder for defining tests relating to L{IReactorTime}.
+ """
+ def test_delayedCallStopsReactor(self):
+ """
+ The reactor can be stopped by a delayed call.
+ """
+ reactor = self.buildReactor()
+ reactor.callLater(0, reactor.stop)
+ reactor.run()
+
+
+globals().update(TimeTestsBuilder.makeTestCaseClasses())
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_tls.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_tls.py
new file mode 100644
index 0000000000..f64a06c7e6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_tls.py
@@ -0,0 +1,163 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{ITLSTransport}.
+"""
+
+__metaclass__ = type
+
+from twisted.internet.test.reactormixins import ReactorBuilder
+from twisted.internet.protocol import ServerFactory, ClientFactory, Protocol
+from twisted.internet.interfaces import ITLSTransport
+from twisted.internet.defer import Deferred, DeferredList
+from twisted.internet.error import ConnectionClosed
+from twisted.trial.unittest import SkipTest
+from twisted.python.runtime import platform
+
+try:
+ from OpenSSL.crypto import FILETYPE_PEM
+except ImportError:
+ FILETYPE_PEM = None
+else:
+ from twisted.internet.ssl import PrivateCertificate, KeyPair
+ from twisted.internet.ssl import ClientContextFactory
+
+
+
+class SSLClientTestsMixin(ReactorBuilder):
+ """
+ Mixin defining tests relating to L{ITLSTransport}.
+ """
+ if FILETYPE_PEM is None:
+ skip = "OpenSSL is unavailable"
+
+ if platform.isWindows():
+ msg = (
+ "For some reason, these reactors don't deal with SSL "
+ "disconnection correctly on Windows. See #3371.")
+ skippedReactors = {
+ "twisted.internet.glib2reactor.Glib2Reactor": msg,
+ "twisted.internet.gtk2reactor.Gtk2Reactor": msg}
+
+
+ _certificateText = (
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER\n"
+ "MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD\n"
+ "ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n\n"
+ "cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL\n"
+ "MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv\n"
+ "c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB\n"
+ "BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh\n"
+ "5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC\n"
+ "MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl\n"
+ "MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7\n"
+ "hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT\n"
+ "CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw\n"
+ "dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx\n"
+ "LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6\n"
+ "BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++\n"
+ "7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE\n"
+ "WUQ9Ho4EzbYCOQ==\n"
+ "-----END CERTIFICATE-----\n")
+
+ _privateKeyText = (
+ "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh\n"
+ "5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx\n"
+ "OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT\n"
+ "ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4\n"
+ "nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2\n"
+ "HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA\n"
+ "oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=\n"
+ "-----END RSA PRIVATE KEY-----\n")
+
+
+ def getServerContext(self):
+ """
+ Return a new SSL context suitable for use in a test server.
+ """
+ cert = PrivateCertificate.load(
+ self._certificateText,
+ KeyPair.load(self._privateKeyText, FILETYPE_PEM),
+ FILETYPE_PEM)
+ return cert.options()
+
+
+ def test_disconnectAfterWriteAfterStartTLS(self):
+ """
+ L{ITCPTransport.loseConnection} ends a connection which was set up with
+ L{ITLSTransport.startTLS} and which has recently been written to. This
+ is intended to verify that a socket send error masked by the TLS
+ implementation doesn't prevent the connection from being reported as
+ closed.
+ """
+ class ShortProtocol(Protocol):
+ def connectionMade(self):
+ if not ITLSTransport.providedBy(self.transport):
+ # Functionality isn't available to be tested.
+ finished = self.factory.finished
+ self.factory.finished = None
+ finished.errback(SkipTest("No ITLSTransport support"))
+ return
+
+ # Switch the transport to TLS.
+ self.transport.startTLS(self.factory.context)
+ # Force TLS to really get negotiated. If nobody talks, nothing
+ # will happen.
+ self.transport.write("x")
+
+ def dataReceived(self, data):
+ # Stuff some bytes into the socket. This mostly has the effect
+ # of causing the next write to fail with ENOTCONN or EPIPE.
+ # With the pyOpenSSL implementation of ITLSTransport, the error
+ # is swallowed outside of the control of Twisted.
+ self.transport.write("y")
+ # Now close the connection, which requires a TLS close alert to
+ # be sent.
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ # This is the success case. The client and the server want to
+ # get here.
+ finished = self.factory.finished
+ if finished is not None:
+ self.factory.finished = None
+ finished.callback(reason)
+
+ serverFactory = ServerFactory()
+ serverFactory.finished = Deferred()
+ serverFactory.protocol = ShortProtocol
+ serverFactory.context = self.getServerContext()
+
+ clientFactory = ClientFactory()
+ clientFactory.finished = Deferred()
+ clientFactory.protocol = ShortProtocol
+ clientFactory.context = ClientContextFactory()
+ clientFactory.context.method = serverFactory.context.method
+
+ lostConnectionResults = []
+ finished = DeferredList(
+ [serverFactory.finished, clientFactory.finished],
+ consumeErrors=True)
+ def cbFinished(results):
+ lostConnectionResults.extend([results[0][1], results[1][1]])
+ finished.addCallback(cbFinished)
+
+ reactor = self.buildReactor()
+
+ port = reactor.listenTCP(0, serverFactory, interface='127.0.0.1')
+ self.addCleanup(port.stopListening)
+
+ connector = reactor.connectTCP(
+ port.getHost().host, port.getHost().port, clientFactory)
+ self.addCleanup(connector.disconnect)
+
+ finished.addCallback(lambda ign: reactor.stop())
+ self.runReactor(reactor)
+ lostConnectionResults[0].trap(ConnectionClosed)
+ lostConnectionResults[1].trap(ConnectionClosed)
+
+
+globals().update(SSLClientTestsMixin.makeTestCaseClasses())
diff --git a/vendor/Twisted-10.0.0/twisted/internet/test/test_unix.py b/vendor/Twisted-10.0.0/twisted/internet/test/test_unix.py
new file mode 100644
index 0000000000..c0124493bf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/test/test_unix.py
@@ -0,0 +1,137 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorUNIX}.
+"""
+
+from stat import S_IMODE
+from os import stat
+from socket import socket, SOCK_DGRAM
+try:
+ from socket import AF_UNIX
+except ImportError:
+ AF_UNIX = None
+
+from twisted.trial import util
+from twisted.internet.protocol import ServerFactory, DatagramProtocol
+from twisted.internet.protocol import ConnectedDatagramProtocol
+from twisted.internet.test.reactormixins import ReactorBuilder
+
+
+_deprecatedModeMessage = (
+ 'The mode parameter of %(interface)s.%(method)s will be removed. Do '
+ 'not pass a value for it. Set permissions on the containing directory '
+ 'before calling %(interface)s.%(method)s, instead.')
+
+
+class UNIXFamilyMixin:
+ """
+ Test-helper defining mixin for things related to AF_UNIX sockets.
+ """
+ if AF_UNIX is None:
+ skip = "Platform does not support AF_UNIX sockets"
+
+ def _modeTest(self, methodName, path, factory):
+ """
+ Assert that the mode of the created unix socket is set to the mode
+ specified to the reactor method.
+ """
+ mode = 0600
+ reactor = self.buildReactor()
+ unixPort = getattr(reactor, methodName)(path, factory, mode=mode)
+ unixPort.stopListening()
+ self.assertEqual(S_IMODE(stat(path).st_mode), mode)
+
+
+ def _deprecatedModeTest(self, interfaceName, methodName, path, factory):
+ """
+ Assert that a deprecation warning is emitted when a value is specified
+ for the mode parameter to the indicated reactor method.
+ """
+ reactor = self.buildReactor()
+ method = getattr(reactor, methodName)
+ port = self.assertWarns(
+ DeprecationWarning,
+ _deprecatedModeMessage % dict(
+ interface=interfaceName, method=methodName),
+ __file__,
+ lambda: method(path, factory, mode=0246))
+ port.stopListening()
+
+
+
+class UNIXTestsBuilder(UNIXFamilyMixin, ReactorBuilder):
+ """
+ Builder defining tests relating to L{IReactorUNIX}.
+ """
+ def test_mode(self):
+ """
+ The UNIX socket created by L{IReactorUNIX.listenUNIX} is created with
+ the mode specified.
+ """
+ self._modeTest('listenUNIX', self.mktemp(), ServerFactory())
+ test_mode.suppress = [
+ util.suppress(category=DeprecationWarning,
+ message=_deprecatedModeMessage % dict(
+ interface='IReactorUNIX',
+ method='listenUNIX'))]
+
+
+ def test_deprecatedMode(self):
+ """
+ Passing any value for the C{mode} parameter of L{listenUNIX} causes a
+ deprecation warning to be emitted.
+ """
+ self._deprecatedModeTest(
+ 'IReactorUNIX', 'listenUNIX', self.mktemp(), ServerFactory())
+
+
+
+class UNIXDatagramTestsBuilder(UNIXFamilyMixin, ReactorBuilder):
+ """
+ Builder defining tests relating to L{IReactorUNIXDatagram}.
+ """
+ # There's no corresponding test_connectMode because the mode parameter to
+ # connectUNIXDatagram has been completely ignored since that API was first
+ # introduced.
+ def test_listenMode(self):
+ """
+ The UNIX socket created by L{IReactorUNIXDatagram.listenUNIXDatagram}
+ is created with the mode specified.
+ """
+ self._modeTest('listenUNIXDatagram', self.mktemp(), DatagramProtocol())
+ test_listenMode.suppress = [
+ util.suppress(category=DeprecationWarning,
+ message=_deprecatedModeMessage % dict(
+ interface='IReactorUNIXDatagram',
+ method='listenUNIXDatagram'))]
+
+
+ def test_deprecatedListenMode(self):
+ """
+ Passing any value for the C{mode} parameter of L{listenUNIXDatagram}
+ causes a deprecation warning to be emitted.
+ """
+ self._deprecatedModeTest(
+ 'IReactorUNIXDatagram', 'listenUNIXDatagram', self.mktemp(),
+ DatagramProtocol())
+
+
+ def test_deprecatedConnectMode(self):
+ """
+ Passing any value for the C{mode} parameter of L{connectUNIXDatagram}
+ causes a deprecation warning to be emitted.
+ """
+ path = self.mktemp()
+ server = socket(AF_UNIX, SOCK_DGRAM)
+ server.bind(path)
+ self.addCleanup(server.close)
+
+ self._deprecatedModeTest(
+ 'IReactorUNIXDatagram', 'connectUNIXDatagram',
+ path, ConnectedDatagramProtocol())
+
+
+globals().update(UNIXTestsBuilder.makeTestCaseClasses())
+globals().update(UNIXDatagramTestsBuilder.makeTestCaseClasses())
diff --git a/vendor/Twisted-10.0.0/twisted/internet/threads.py b/vendor/Twisted-10.0.0/twisted/internet/threads.py
new file mode 100644
index 0000000000..84b90188b5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/threads.py
@@ -0,0 +1,117 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Extended thread dispatching support.
+
+For basic support see reactor threading API docs.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+import Queue
+
+from twisted.python import failure
+from twisted.internet import defer
+
+
+def deferToThreadPool(reactor, threadpool, f, *args, **kwargs):
+ """
+ Call the function C{f} using a thread from the given threadpool and return
+ the result as a Deferred.
+
+ This function is only used by client code which is maintaining its own
+ threadpool. To run a function in the reactor's threadpool, use
+ C{deferToThread}.
+
+ @param reactor: The reactor in whose main thread the Deferred will be
+ invoked.
+
+ @param threadpool: An object which supports the C{callInThreadWithCallback}
+ method of C{twisted.python.threadpool.ThreadPool}.
+
+ @param f: The function to call.
+ @param *args: positional arguments to pass to f.
+ @param **kwargs: keyword arguments to pass to f.
+
+ @return: A Deferred which fires a callback with the result of f, or an
+ errback with a L{twisted.python.failure.Failure} if f throws an
+ exception.
+ """
+ d = defer.Deferred()
+
+ def onResult(success, result):
+ if success:
+ reactor.callFromThread(d.callback, result)
+ else:
+ reactor.callFromThread(d.errback, result)
+
+ threadpool.callInThreadWithCallback(onResult, f, *args, **kwargs)
+
+ return d
+
+
+def deferToThread(f, *args, **kwargs):
+ """
+ Run a function in a thread and return the result as a Deferred.
+
+ @param f: The function to call.
+ @param *args: positional arguments to pass to f.
+ @param **kwargs: keyword arguments to pass to f.
+
+ @return: A Deferred which fires a callback with the result of f,
+ or an errback with a L{twisted.python.failure.Failure} if f throws
+ an exception.
+ """
+ from twisted.internet import reactor
+ return deferToThreadPool(reactor, reactor.getThreadPool(),
+ f, *args, **kwargs)
+
+
+def _runMultiple(tupleList):
+ """
+ Run a list of functions.
+ """
+ for f, args, kwargs in tupleList:
+ f(*args, **kwargs)
+
+
+def callMultipleInThread(tupleList):
+ """
+ Run a list of functions in the same thread.
+
+ tupleList should be a list of (function, argsList, kwargsDict) tuples.
+ """
+ from twisted.internet import reactor
+ reactor.callInThread(_runMultiple, tupleList)
+
+
+def blockingCallFromThread(reactor, f, *a, **kw):
+ """
+ Run a function in the reactor from a thread, and wait for the result
+ synchronously, i.e. until the callback chain returned by the function
+ get a result.
+
+ @param reactor: The L{IReactorThreads} provider which will be used to
+ schedule the function call.
+ @param f: the callable to run in the reactor thread
+ @type f: any callable.
+ @param a: the arguments to pass to C{f}.
+ @param kw: the keyword arguments to pass to C{f}.
+
+ @return: the result of the callback chain.
+ @raise: any error raised during the callback chain.
+ """
+ queue = Queue.Queue()
+ def _callFromThread():
+ result = defer.maybeDeferred(f, *a, **kw)
+ result.addBoth(queue.put)
+ reactor.callFromThread(_callFromThread)
+ result = queue.get()
+ if isinstance(result, failure.Failure):
+ result.raiseException()
+ return result
+
+
+__all__ = ["deferToThread", "deferToThreadPool", "callMultipleInThread",
+ "blockingCallFromThread"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/tksupport.py b/vendor/Twisted-10.0.0/twisted/internet/tksupport.py
new file mode 100644
index 0000000000..946f772c4b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/tksupport.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+This module integrates Tkinter with twisted.internet's mainloop.
+
+Maintainer: Itamar Shtull-Trauring
+
+To use, do::
+
+ | tksupport.install(rootWidget)
+
+and then run your reactor as usual - do *not* call Tk's mainloop(),
+use Twisted's regular mechanism for running the event loop.
+
+Likewise, to stop your program you will need to stop Twisted's
+event loop. For example, if you want closing your root widget to
+stop Twisted::
+
+ | root.protocol('WM_DELETE_WINDOW', reactor.stop)
+
+"""
+
+# system imports
+import Tkinter, tkSimpleDialog, tkMessageBox
+
+# twisted imports
+from twisted.python import log
+from twisted.internet import task
+
+
+_task = None
+
+def install(widget, ms=10, reactor=None):
+ """Install a Tkinter.Tk() object into the reactor."""
+ installTkFunctions()
+ global _task
+ _task = task.LoopingCall(widget.update)
+ _task.start(ms / 1000.0, False)
+
+def uninstall():
+ """Remove the root Tk widget from the reactor.
+
+ Call this before destroy()ing the root widget.
+ """
+ global _task
+ _task.stop()
+ _task = None
+
+
+def installTkFunctions():
+ import twisted.python.util
+ twisted.python.util.getPassword = getPassword
+
+
+def getPassword(prompt = '', confirm = 0):
+ while 1:
+ try1 = tkSimpleDialog.askstring('Password Dialog', prompt, show='*')
+ if not confirm:
+ return try1
+ try2 = tkSimpleDialog.askstring('Password Dialog', 'Confirm Password', show='*')
+ if try1 == try2:
+ return try1
+ else:
+ tkMessageBox.showerror('Password Mismatch', 'Passwords did not match, starting over')
+
+__all__ = ["install", "uninstall"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/udp.py b/vendor/Twisted-10.0.0/twisted/internet/udp.py
new file mode 100644
index 0000000000..2ece521297
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/udp.py
@@ -0,0 +1,297 @@
+# -*- test-case-name: twisted.test.test_udp -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Various asynchronous UDP classes.
+
+Please do not use this module directly.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+# System Imports
+import socket
+import operator
+import struct
+import warnings
+
+from zope.interface import implements
+
+from twisted.python.runtime import platformType
+if platformType == 'win32':
+ from errno import WSAEWOULDBLOCK as EWOULDBLOCK
+ from errno import WSAEINTR as EINTR
+ from errno import WSAEMSGSIZE as EMSGSIZE
+ from errno import WSAECONNREFUSED as ECONNREFUSED
+ from errno import WSAECONNRESET
+ EAGAIN=EWOULDBLOCK
+else:
+ from errno import EWOULDBLOCK, EINTR, EMSGSIZE, ECONNREFUSED, EAGAIN
+
+# Twisted Imports
+from twisted.internet import base, defer, address
+from twisted.python import log, reflect, failure
+from twisted.internet import abstract, error, interfaces
+
+
+class Port(base.BasePort):
+ """UDP port, listening for packets."""
+
+ implements(interfaces.IUDPTransport, interfaces.ISystemHandle)
+
+ addressFamily = socket.AF_INET
+ socketType = socket.SOCK_DGRAM
+ maxThroughput = 256 * 1024 # max bytes we read in one eventloop iteration
+
+ # Actual port number being listened on, only set to a non-None
+ # value when we are actually listening.
+ _realPortNumber = None
+
+ def __init__(self, port, proto, interface='', maxPacketSize=8192, reactor=None):
+ """Initialize with a numeric port to listen on.
+ """
+ base.BasePort.__init__(self, reactor)
+ self.port = port
+ self.protocol = proto
+ self.maxPacketSize = maxPacketSize
+ self.interface = interface
+ self.setLogStr()
+ self._connectedAddr = None
+
+ def __repr__(self):
+ if self._realPortNumber is not None:
+ return "<%s on %s>" % (self.protocol.__class__, self._realPortNumber)
+ else:
+ return "<%s not connected>" % (self.protocol.__class__,)
+
+ def getHandle(self):
+ """Return a socket object."""
+ return self.socket
+
+ def startListening(self):
+ """Create and bind my socket, and begin listening on it.
+
+ This is called on unserialization, and must be called after creating a
+ server to begin listening on the specified port.
+ """
+ self._bindSocket()
+ self._connectToProtocol()
+
+ def _bindSocket(self):
+ try:
+ skt = self.createInternetSocket()
+ skt.bind((self.interface, self.port))
+ except socket.error, le:
+ raise error.CannotListenError, (self.interface, self.port, le)
+
+ # Make sure that if we listened on port 0, we update that to
+ # reflect what the OS actually assigned us.
+ self._realPortNumber = skt.getsockname()[1]
+
+ log.msg("%s starting on %s"%(self.protocol.__class__, self._realPortNumber))
+
+ self.connected = 1
+ self.socket = skt
+ self.fileno = self.socket.fileno
+
+ def _connectToProtocol(self):
+ self.protocol.makeConnection(self)
+ self.startReading()
+
+
+ def doRead(self):
+ """Called when my socket is ready for reading."""
+ read = 0
+ while read < self.maxThroughput:
+ try:
+ data, addr = self.socket.recvfrom(self.maxPacketSize)
+ except socket.error, se:
+ no = se.args[0]
+ if no in (EAGAIN, EINTR, EWOULDBLOCK):
+ return
+ if (no == ECONNREFUSED) or (platformType == "win32" and no == WSAECONNRESET):
+ if self._connectedAddr:
+ self.protocol.connectionRefused()
+ else:
+ raise
+ else:
+ read += len(data)
+ try:
+ self.protocol.datagramReceived(data, addr)
+ except:
+ log.err()
+
+
+ def write(self, datagram, addr=None):
+ """Write a datagram.
+
+ @param addr: should be a tuple (ip, port), can be None in connected mode.
+ """
+ if self._connectedAddr:
+ assert addr in (None, self._connectedAddr)
+ try:
+ return self.socket.send(datagram)
+ except socket.error, se:
+ no = se.args[0]
+ if no == EINTR:
+ return self.write(datagram)
+ elif no == EMSGSIZE:
+ raise error.MessageLengthError, "message too long"
+ elif no == ECONNREFUSED:
+ self.protocol.connectionRefused()
+ else:
+ raise
+ else:
+ assert addr != None
+ if not addr[0].replace(".", "").isdigit():
+ warnings.warn("Please only pass IPs to write(), not hostnames", DeprecationWarning, stacklevel=2)
+ try:
+ return self.socket.sendto(datagram, addr)
+ except socket.error, se:
+ no = se.args[0]
+ if no == EINTR:
+ return self.write(datagram, addr)
+ elif no == EMSGSIZE:
+ raise error.MessageLengthError, "message too long"
+ elif no == ECONNREFUSED:
+ # in non-connected UDP ECONNREFUSED is platform dependent, I think
+ # and the info is not necessarily useful. Nevertheless maybe we
+ # should call connectionRefused? XXX
+ return
+ else:
+ raise
+
+ def writeSequence(self, seq, addr):
+ self.write("".join(seq), addr)
+
+ def connect(self, host, port):
+ """'Connect' to remote server."""
+ if self._connectedAddr:
+ raise RuntimeError, "already connected, reconnecting is not currently supported (talk to itamar if you want this)"
+ if not abstract.isIPAddress(host):
+ raise ValueError, "please pass only IP addresses, not domain names"
+ self._connectedAddr = (host, port)
+ self.socket.connect((host, port))
+
+ def _loseConnection(self):
+ self.stopReading()
+ if self.connected: # actually means if we are *listening*
+ from twisted.internet import reactor
+ reactor.callLater(0, self.connectionLost)
+
+ def stopListening(self):
+ if self.connected:
+ result = self.d = defer.Deferred()
+ else:
+ result = None
+ self._loseConnection()
+ return result
+
+ def loseConnection(self):
+ warnings.warn("Please use stopListening() to disconnect port", DeprecationWarning, stacklevel=2)
+ self.stopListening()
+
+ def connectionLost(self, reason=None):
+ """Cleans up my socket.
+ """
+ log.msg('(Port %s Closed)' % self._realPortNumber)
+ self._realPortNumber = None
+ base.BasePort.connectionLost(self, reason)
+ self.protocol.doStop()
+ self.connected = 0
+ self.socket.close()
+ del self.socket
+ del self.fileno
+ if hasattr(self, "d"):
+ self.d.callback(None)
+ del self.d
+
+ def setLogStr(self):
+ self.logstr = reflect.qual(self.protocol.__class__) + " (UDP)"
+
+ def logPrefix(self):
+ """Returns the name of my class, to prefix log entries with.
+ """
+ return self.logstr
+
+ def getHost(self):
+ """
+ Returns an IPv4Address.
+
+ This indicates the address from which I am connecting.
+ """
+ return address.IPv4Address('UDP', *(self.socket.getsockname() + ('INET_UDP',)))
+
+
+
+class MulticastMixin:
+ """Implement multicast functionality."""
+
+ def getOutgoingInterface(self):
+ i = self.socket.getsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF)
+ return socket.inet_ntoa(struct.pack("@i", i))
+
+ def setOutgoingInterface(self, addr):
+ """Returns Deferred of success."""
+ return self.reactor.resolve(addr).addCallback(self._setInterface)
+
+ def _setInterface(self, addr):
+ i = socket.inet_aton(addr)
+ self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, i)
+ return 1
+
+ def getLoopbackMode(self):
+ return self.socket.getsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP)
+
+ def setLoopbackMode(self, mode):
+ mode = struct.pack("b", operator.truth(mode))
+ self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, mode)
+
+ def getTTL(self):
+ return self.socket.getsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL)
+
+ def setTTL(self, ttl):
+ ttl = struct.pack("B", ttl)
+ self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
+
+ def joinGroup(self, addr, interface=""):
+ """Join a multicast group. Returns Deferred of success."""
+ return self.reactor.resolve(addr).addCallback(self._joinAddr1, interface, 1)
+
+ def _joinAddr1(self, addr, interface, join):
+ return self.reactor.resolve(interface).addCallback(self._joinAddr2, addr, join)
+
+ def _joinAddr2(self, interface, addr, join):
+ addr = socket.inet_aton(addr)
+ interface = socket.inet_aton(interface)
+ if join:
+ cmd = socket.IP_ADD_MEMBERSHIP
+ else:
+ cmd = socket.IP_DROP_MEMBERSHIP
+ try:
+ self.socket.setsockopt(socket.IPPROTO_IP, cmd, addr + interface)
+ except socket.error, e:
+ return failure.Failure(error.MulticastJoinError(addr, interface, *e.args))
+
+ def leaveGroup(self, addr, interface=""):
+ """Leave multicast group, return Deferred of success."""
+ return self.reactor.resolve(addr).addCallback(self._joinAddr1, interface, 0)
+
+
+class MulticastPort(MulticastMixin, Port):
+ """UDP Port that supports multicasting."""
+
+ implements(interfaces.IMulticastTransport)
+
+ def __init__(self, port, proto, interface='', maxPacketSize=8192, reactor=None, listenMultiple=False):
+ Port.__init__(self, port, proto, interface, maxPacketSize, reactor)
+ self.listenMultiple = listenMultiple
+
+ def createInternetSocket(self):
+ skt = Port.createInternetSocket(self)
+ if self.listenMultiple:
+ skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ if hasattr(socket, "SO_REUSEPORT"):
+ skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ return skt
diff --git a/vendor/Twisted-10.0.0/twisted/internet/unix.py b/vendor/Twisted-10.0.0/twisted/internet/unix.py
new file mode 100644
index 0000000000..13377db004
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/unix.py
@@ -0,0 +1,297 @@
+# -*- test-case-name: twisted.test.test_unix -*-
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Various asynchronous TCP/IP classes.
+
+End users shouldn't use this module directly - use the reactor APIs instead.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+# System imports
+import os, stat, socket
+from errno import EINTR, EMSGSIZE, EAGAIN, EWOULDBLOCK, ECONNREFUSED
+
+from zope.interface import implements, implementsOnly, implementedBy
+
+if not hasattr(socket, 'AF_UNIX'):
+ raise ImportError("UNIX sockets not supported on this platform")
+
+# Twisted imports
+from twisted.internet import base, tcp, udp, error, interfaces, protocol, address
+from twisted.internet.error import CannotListenError
+from twisted.python import lockfile, log, reflect, failure
+
+
+class Server(tcp.Server):
+ def __init__(self, sock, protocol, client, server, sessionno, reactor):
+ tcp.Server.__init__(self, sock, protocol, (client, None), server, sessionno, reactor)
+
+ def getHost(self):
+ return address.UNIXAddress(self.socket.getsockname())
+
+ def getPeer(self):
+ return address.UNIXAddress(self.hostname)
+
+
+class Port(tcp.Port):
+ addressFamily = socket.AF_UNIX
+ socketType = socket.SOCK_STREAM
+
+ transport = Server
+ lockFile = None
+
+ def __init__(self, fileName, factory, backlog=50, mode=0666, reactor=None, wantPID = 0):
+ tcp.Port.__init__(self, fileName, factory, backlog, reactor=reactor)
+ self.mode = mode
+ self.wantPID = wantPID
+
+ def __repr__(self):
+ factoryName = reflect.qual(self.factory.__class__)
+ if hasattr(self, 'socket'):
+ return '<%s on %r>' % (factoryName, self.port)
+ else:
+ return '<%s (not listening)>' % (factoryName,)
+
+ def _buildAddr(self, name):
+ return address.UNIXAddress(name)
+
+ def startListening(self):
+ """Create and bind my socket, and begin listening on it.
+
+ This is called on unserialization, and must be called after creating a
+ server to begin listening on the specified port.
+ """
+ log.msg("%s starting on %r" % (self.factory.__class__, repr(self.port)))
+ if self.wantPID:
+ self.lockFile = lockfile.FilesystemLock(self.port + ".lock")
+ if not self.lockFile.lock():
+ raise CannotListenError, (None, self.port, "Cannot acquire lock")
+ else:
+ if not self.lockFile.clean:
+ try:
+ # This is a best-attempt at cleaning up
+ # left-over unix sockets on the filesystem.
+ # If it fails, there's not much else we can
+ # do. The bind() below will fail with an
+ # exception that actually propegates.
+ if stat.S_ISSOCK(os.stat(self.port).st_mode):
+ os.remove(self.port)
+ except:
+ pass
+
+ self.factory.doStart()
+ try:
+ skt = self.createInternetSocket()
+ skt.bind(self.port)
+ except socket.error, le:
+ raise CannotListenError, (None, self.port, le)
+ else:
+ # Make the socket readable and writable to the world.
+ try:
+ os.chmod(self.port, self.mode)
+ except OSError: # probably not a visible filesystem name
+ pass
+ skt.listen(self.backlog)
+ self.connected = True
+ self.socket = skt
+ self.fileno = self.socket.fileno
+ self.numberAccepts = 100
+ self.startReading()
+
+ def connectionLost(self, reason):
+ os.unlink(self.port)
+ if self.lockFile is not None:
+ self.lockFile.unlock()
+ tcp.Port.connectionLost(self, reason)
+
+ def getHost(self):
+ """Returns a UNIXAddress.
+
+ This indicates the server's address.
+ """
+ return address.UNIXAddress(self.socket.getsockname())
+
+
+class Client(tcp.BaseClient):
+ """A client for Unix sockets."""
+ addressFamily = socket.AF_UNIX
+ socketType = socket.SOCK_STREAM
+
+ def __init__(self, filename, connector, reactor=None, checkPID = 0):
+ self.connector = connector
+ self.realAddress = self.addr = filename
+ if checkPID and not lockfile.isLocked(filename + ".lock"):
+ self._finishInit(None, None, error.BadFileError(filename), reactor)
+ self._finishInit(self.doConnect, self.createInternetSocket(),
+ None, reactor)
+
+ def getPeer(self):
+ return address.UNIXAddress(self.addr)
+
+ def getHost(self):
+ return address.UNIXAddress(None)
+
+
+class Connector(base.BaseConnector):
+ def __init__(self, address, factory, timeout, reactor, checkPID):
+ base.BaseConnector.__init__(self, factory, timeout, reactor)
+ self.address = address
+ self.checkPID = checkPID
+
+ def _makeTransport(self):
+ return Client(self.address, self, self.reactor, self.checkPID)
+
+ def getDestination(self):
+ return address.UNIXAddress(self.address)
+
+
+class DatagramPort(udp.Port):
+ """Datagram UNIX port, listening for packets."""
+
+ implements(interfaces.IUNIXDatagramTransport)
+
+ addressFamily = socket.AF_UNIX
+
+ def __init__(self, addr, proto, maxPacketSize=8192, mode=0666, reactor=None):
+ """Initialize with address to listen on.
+ """
+ udp.Port.__init__(self, addr, proto, maxPacketSize=maxPacketSize, reactor=reactor)
+ self.mode = mode
+
+
+ def __repr__(self):
+ protocolName = reflect.qual(self.protocol.__class__,)
+ if hasattr(self, 'socket'):
+ return '<%s on %r>' % (protocolName, self.port)
+ else:
+ return '<%s (not listening)>' % (protocolName,)
+
+
+ def _bindSocket(self):
+ log.msg("%s starting on %s"%(self.protocol.__class__, repr(self.port)))
+ try:
+ skt = self.createInternetSocket() # XXX: haha misnamed method
+ if self.port:
+ skt.bind(self.port)
+ except socket.error, le:
+ raise error.CannotListenError, (None, self.port, le)
+ if self.port:
+ try:
+ os.chmod(self.port, self.mode)
+ except: # probably not a visible filesystem name
+ pass
+ self.connected = 1
+ self.socket = skt
+ self.fileno = self.socket.fileno
+
+ def write(self, datagram, address):
+ """Write a datagram."""
+ try:
+ return self.socket.sendto(datagram, address)
+ except socket.error, se:
+ no = se.args[0]
+ if no == EINTR:
+ return self.write(datagram, address)
+ elif no == EMSGSIZE:
+ raise error.MessageLengthError, "message too long"
+ elif no == EAGAIN:
+ # oh, well, drop the data. The only difference from UDP
+ # is that UDP won't ever notice.
+ # TODO: add TCP-like buffering
+ pass
+ else:
+ raise
+
+ def connectionLost(self, reason=None):
+ """Cleans up my socket.
+ """
+ log.msg('(Port %s Closed)' % repr(self.port))
+ base.BasePort.connectionLost(self, reason)
+ if hasattr(self, "protocol"):
+ # we won't have attribute in ConnectedPort, in cases
+ # where there was an error in connection process
+ self.protocol.doStop()
+ self.connected = 0
+ self.socket.close()
+ del self.socket
+ del self.fileno
+ if hasattr(self, "d"):
+ self.d.callback(None)
+ del self.d
+
+ def setLogStr(self):
+ self.logstr = reflect.qual(self.protocol.__class__) + " (UDP)"
+
+ def getHost(self):
+ return address.UNIXAddress(self.socket.getsockname())
+
+
+class ConnectedDatagramPort(DatagramPort):
+ """A connected datagram UNIX socket."""
+
+ implementsOnly(interfaces.IUNIXDatagramConnectedTransport,
+ *(implementedBy(base.BasePort)))
+
+ def __init__(self, addr, proto, maxPacketSize=8192, mode=0666, bindAddress=None, reactor=None):
+ assert isinstance(proto, protocol.ConnectedDatagramProtocol)
+ DatagramPort.__init__(self, bindAddress, proto, maxPacketSize, mode, reactor)
+ self.remoteaddr = addr
+
+ def startListening(self):
+ try:
+ self._bindSocket()
+ self.socket.connect(self.remoteaddr)
+ self._connectToProtocol()
+ except:
+ self.connectionFailed(failure.Failure())
+
+ def connectionFailed(self, reason):
+ self.loseConnection()
+ self.protocol.connectionFailed(reason)
+ del self.protocol
+
+ def doRead(self):
+ """Called when my socket is ready for reading."""
+ read = 0
+ while read < self.maxThroughput:
+ try:
+ data, addr = self.socket.recvfrom(self.maxPacketSize)
+ read += len(data)
+ self.protocol.datagramReceived(data)
+ except socket.error, se:
+ no = se.args[0]
+ if no in (EAGAIN, EINTR, EWOULDBLOCK):
+ return
+ if no == ECONNREFUSED:
+ self.protocol.connectionRefused()
+ else:
+ raise
+ except:
+ log.deferr()
+
+ def write(self, data):
+ """Write a datagram."""
+ try:
+ return self.socket.send(data)
+ except socket.error, se:
+ no = se.args[0]
+ if no == EINTR:
+ return self.write(data)
+ elif no == EMSGSIZE:
+ raise error.MessageLengthError, "message too long"
+ elif no == ECONNREFUSED:
+ self.protocol.connectionRefused()
+ elif no == EAGAIN:
+ # oh, well, drop the data. The only difference from UDP
+ # is that UDP won't ever notice.
+ # TODO: add TCP-like buffering
+ pass
+ else:
+ raise
+
+ def getPeer(self):
+ return address.UNIXAddress(self.remoteaddr)
diff --git a/vendor/Twisted-10.0.0/twisted/internet/utils.py b/vendor/Twisted-10.0.0/twisted/internet/utils.py
new file mode 100644
index 0000000000..0a781489d3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/utils.py
@@ -0,0 +1,219 @@
+# -*- test-case-name: twisted.test.test_iutils -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Utility methods.
+"""
+
+import sys, warnings
+
+from twisted.internet import protocol, defer
+from twisted.python import failure, util as tputil
+
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+def _callProtocolWithDeferred(protocol, executable, args, env, path, reactor=None):
+ if reactor is None:
+ from twisted.internet import reactor
+
+ d = defer.Deferred()
+ p = protocol(d)
+ reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path)
+ return d
+
+
+
+class _UnexpectedErrorOutput(IOError):
+ """
+ Standard error data was received where it was not expected. This is a
+ subclass of L{IOError} to preserve backward compatibility with the previous
+ error behavior of L{getProcessOutput}.
+
+ @ivar processEnded: A L{Deferred} which will fire when the process which
+ produced the data on stderr has ended (exited and all file descriptors
+ closed).
+ """
+ def __init__(self, text, processEnded):
+ IOError.__init__(self, "got stderr: %r" % (text,))
+ self.processEnded = processEnded
+
+
+
+class _BackRelay(protocol.ProcessProtocol):
+ """
+ Trivial protocol for communicating with a process and turning its output
+ into the result of a L{Deferred}.
+
+ @ivar deferred: A L{Deferred} which will be called back with all of stdout
+ and, if C{errortoo} is true, all of stderr as well (mixed together in
+ one string). If C{errortoo} is false and any bytes are received over
+ stderr, this will fire with an L{_UnexpectedErrorOutput} instance and
+ the attribute will be set to C{None}.
+
+ @ivar onProcessEnded: If C{errortoo} is false and bytes are received over
+ stderr, this attribute will refer to a L{Deferred} which will be called
+ back when the process ends. This C{Deferred} is also associated with
+ the L{_UnexpectedErrorOutput} which C{deferred} fires with earlier in
+ this case so that users can determine when the process has actually
+ ended, in addition to knowing when bytes have been received via stderr.
+ """
+
+ def __init__(self, deferred, errortoo=0):
+ self.deferred = deferred
+ self.s = StringIO.StringIO()
+ if errortoo:
+ self.errReceived = self.errReceivedIsGood
+ else:
+ self.errReceived = self.errReceivedIsBad
+
+ def errReceivedIsBad(self, text):
+ if self.deferred is not None:
+ self.onProcessEnded = defer.Deferred()
+ err = _UnexpectedErrorOutput(text, self.onProcessEnded)
+ self.deferred.errback(failure.Failure(err))
+ self.deferred = None
+ self.transport.loseConnection()
+
+ def errReceivedIsGood(self, text):
+ self.s.write(text)
+
+ def outReceived(self, text):
+ self.s.write(text)
+
+ def processEnded(self, reason):
+ if self.deferred is not None:
+ self.deferred.callback(self.s.getvalue())
+ elif self.onProcessEnded is not None:
+ self.onProcessEnded.errback(reason)
+
+
+
+def getProcessOutput(executable, args=(), env={}, path=None, reactor=None,
+ errortoo=0):
+ """
+ Spawn a process and return its output as a deferred returning a string.
+
+ @param executable: The file name to run and get the output of - the
+ full path should be used.
+
+ @param args: the command line arguments to pass to the process; a
+ sequence of strings. The first string should *NOT* be the
+ executable's name.
+
+ @param env: the environment variables to pass to the processs; a
+ dictionary of strings.
+
+ @param path: the path to run the subprocess in - defaults to the
+ current directory.
+
+ @param reactor: the reactor to use - defaults to the default reactor
+
+ @param errortoo: If true, include stderr in the result. If false, if
+ stderr is received the returned L{Deferred} will errback with an
+ L{IOError} instance with a C{processEnded} attribute. The
+ C{processEnded} attribute refers to a L{Deferred} which fires when the
+ executed process ends.
+ """
+ return _callProtocolWithDeferred(lambda d:
+ _BackRelay(d, errortoo=errortoo),
+ executable, args, env, path,
+ reactor)
+
+
+class _ValueGetter(protocol.ProcessProtocol):
+
+ def __init__(self, deferred):
+ self.deferred = deferred
+
+ def processEnded(self, reason):
+ self.deferred.callback(reason.value.exitCode)
+
+
+def getProcessValue(executable, args=(), env={}, path=None, reactor=None):
+ """Spawn a process and return its exit code as a Deferred."""
+ return _callProtocolWithDeferred(_ValueGetter, executable, args, env, path,
+ reactor)
+
+
+class _EverythingGetter(protocol.ProcessProtocol):
+
+ def __init__(self, deferred):
+ self.deferred = deferred
+ self.outBuf = StringIO.StringIO()
+ self.errBuf = StringIO.StringIO()
+ self.outReceived = self.outBuf.write
+ self.errReceived = self.errBuf.write
+
+ def processEnded(self, reason):
+ out = self.outBuf.getvalue()
+ err = self.errBuf.getvalue()
+ e = reason.value
+ code = e.exitCode
+ if e.signal:
+ self.deferred.errback((out, err, e.signal))
+ else:
+ self.deferred.callback((out, err, code))
+
+def getProcessOutputAndValue(executable, args=(), env={}, path=None,
+ reactor=None):
+ """Spawn a process and returns a Deferred that will be called back with
+ its output (from stdout and stderr) and it's exit code as (out, err, code)
+ If a signal is raised, the Deferred will errback with the stdout and
+ stderr up to that point, along with the signal, as (out, err, signalNum)
+ """
+ return _callProtocolWithDeferred(_EverythingGetter, executable, args, env, path,
+ reactor)
+
+def _resetWarningFilters(passthrough, addedFilters):
+ for f in addedFilters:
+ try:
+ warnings.filters.remove(f)
+ except ValueError:
+ pass
+ return passthrough
+
+
+def runWithWarningsSuppressed(suppressedWarnings, f, *a, **kw):
+ """Run the function C{f}, but with some warnings suppressed.
+
+ @param suppressedWarnings: A list of arguments to pass to filterwarnings.
+ Must be a sequence of 2-tuples (args, kwargs).
+ @param f: A callable, followed by its arguments and keyword arguments
+ """
+ for args, kwargs in suppressedWarnings:
+ warnings.filterwarnings(*args, **kwargs)
+ addedFilters = warnings.filters[:len(suppressedWarnings)]
+ try:
+ result = f(*a, **kw)
+ except:
+ exc_info = sys.exc_info()
+ _resetWarningFilters(None, addedFilters)
+ raise exc_info[0], exc_info[1], exc_info[2]
+ else:
+ if isinstance(result, defer.Deferred):
+ result.addBoth(_resetWarningFilters, addedFilters)
+ else:
+ _resetWarningFilters(None, addedFilters)
+ return result
+
+
+def suppressWarnings(f, *suppressedWarnings):
+ """
+ Wrap C{f} in a callable which suppresses the indicated warnings before
+ invoking C{f} and unsuppresses them afterwards. If f returns a Deferred,
+ warnings will remain suppressed until the Deferred fires.
+ """
+ def warningSuppressingWrapper(*a, **kw):
+ return runWithWarningsSuppressed(suppressedWarnings, f, *a, **kw)
+ return tputil.mergeFunctionMetadata(f, warningSuppressingWrapper)
+
+
+__all__ = [
+ "runWithWarningsSuppressed", "suppressWarnings",
+
+ "getProcessOutput", "getProcessValue", "getProcessOutputAndValue",
+ ]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/win32eventreactor.py b/vendor/Twisted-10.0.0/twisted/internet/win32eventreactor.py
new file mode 100644
index 0000000000..209e49baab
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/win32eventreactor.py
@@ -0,0 +1,244 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+A win32event based implementation of the Twisted main loop.
+
+This requires win32all or ActivePython to be installed.
+
+Maintainer: Itamar Shtull-Trauring
+
+
+LIMITATIONS:
+ 1. WaitForMultipleObjects and thus the event loop can only handle 64 objects.
+ 2. Process running has some problems (see Process docstring).
+
+
+TODO:
+ 1. Event loop handling of writes is *very* problematic (this is causing failed tests).
+ Switch to doing it the correct way, whatever that means (see below).
+ 2. Replace icky socket loopback waker with event based waker (use dummyEvent object)
+ 3. Switch everyone to using Free Software so we don't have to deal with proprietary APIs.
+
+
+ALTERNATIVE SOLUTIONS:
+ - IIRC, sockets can only be registered once. So we switch to a structure
+ like the poll() reactor, thus allowing us to deal with write events in
+ a decent fashion. This should allow us to pass tests, but we're still
+ limited to 64 events.
+
+Or:
+
+ - Instead of doing a reactor, we make this an addon to the select reactor.
+ The WFMO event loop runs in a separate thread. This means no need to maintain
+ separate code for networking, 64 event limit doesn't apply to sockets,
+ we can run processes and other win32 stuff in default event loop. The
+ only problem is that we're stuck with the icky socket based waker.
+ Another benefit is that this could be extended to support >64 events
+ in a simpler manner than the previous solution.
+
+The 2nd solution is probably what will get implemented.
+"""
+
+# System imports
+import time
+import sys
+
+from zope.interface import implements
+
+# Win32 imports
+from win32file import WSAEventSelect, FD_READ, FD_CLOSE, FD_ACCEPT, FD_CONNECT
+from win32event import CreateEvent, MsgWaitForMultipleObjects
+from win32event import WAIT_OBJECT_0, WAIT_TIMEOUT, QS_ALLINPUT, QS_ALLEVENTS
+
+import win32gui
+
+# Twisted imports
+from twisted.internet import posixbase
+from twisted.python import log, threadable, failure
+from twisted.internet.interfaces import IReactorFDSet, IReactorProcess
+
+from twisted.internet._dumbwin32proc import Process
+
+
+class Win32Reactor(posixbase.PosixReactorBase):
+ """
+ Reactor that uses Win32 event APIs.
+
+ @ivar _reads: A dictionary mapping L{FileDescriptor} instances to a
+ win32 event object used to check for read events for that descriptor.
+
+ @ivar _writes: A dictionary mapping L{FileDescriptor} instances to a
+ arbitrary value. Keys in this dictionary will be given a chance to
+ write out their data.
+
+ @ivar _events: A dictionary mapping win32 event object to tuples of
+ L{FileDescriptor} instances and event masks.
+ """
+ implements(IReactorFDSet, IReactorProcess)
+
+ dummyEvent = CreateEvent(None, 0, 0, None)
+
+ def __init__(self):
+ self._reads = {}
+ self._writes = {}
+ self._events = {}
+ posixbase.PosixReactorBase.__init__(self)
+
+
+ def _makeSocketEvent(self, fd, action, why):
+ """
+ Make a win32 event object for a socket.
+ """
+ event = CreateEvent(None, 0, 0, None)
+ WSAEventSelect(fd, event, why)
+ self._events[event] = (fd, action)
+ return event
+
+
+ def addEvent(self, event, fd, action):
+ """
+ Add a new win32 event to the event loop.
+ """
+ self._events[event] = (fd, action)
+
+
+ def removeEvent(self, event):
+ """
+ Remove an event.
+ """
+ del self._events[event]
+
+
+ def addReader(self, reader):
+ """
+ Add a socket FileDescriptor for notification of data available to read.
+ """
+ if reader not in self._reads:
+ self._reads[reader] = self._makeSocketEvent(
+ reader, 'doRead', FD_READ | FD_ACCEPT | FD_CONNECT | FD_CLOSE)
+
+ def addWriter(self, writer):
+ """
+ Add a socket FileDescriptor for notification of data available to write.
+ """
+ if writer not in self._writes:
+ self._writes[writer] = 1
+
+ def removeReader(self, reader):
+ """Remove a Selectable for notification of data available to read.
+ """
+ if reader in self._reads:
+ del self._events[self._reads[reader]]
+ del self._reads[reader]
+
+ def removeWriter(self, writer):
+ """Remove a Selectable for notification of data available to write.
+ """
+ if writer in self._writes:
+ del self._writes[writer]
+
+ def removeAll(self):
+ """
+ Remove all selectables, and return a list of them.
+ """
+ return self._removeAll(self._reads, self._writes)
+
+
+ def getReaders(self):
+ return self._reads.keys()
+
+
+ def getWriters(self):
+ return self._writes.keys()
+
+
+ def doWaitForMultipleEvents(self, timeout):
+ log.msg(channel='system', event='iteration', reactor=self)
+ if timeout is None:
+ #timeout = INFINITE
+ timeout = 100
+ else:
+ timeout = int(timeout * 1000)
+
+ if not (self._events or self._writes):
+ # sleep so we don't suck up CPU time
+ time.sleep(timeout / 1000.0)
+ return
+
+ canDoMoreWrites = 0
+ for fd in self._writes.keys():
+ if log.callWithLogger(fd, self._runWrite, fd):
+ canDoMoreWrites = 1
+
+ if canDoMoreWrites:
+ timeout = 0
+
+ handles = self._events.keys() or [self.dummyEvent]
+ val = MsgWaitForMultipleObjects(handles, 0, timeout, QS_ALLINPUT | QS_ALLEVENTS)
+ if val == WAIT_TIMEOUT:
+ return
+ elif val == WAIT_OBJECT_0 + len(handles):
+ exit = win32gui.PumpWaitingMessages()
+ if exit:
+ self.callLater(0, self.stop)
+ return
+ elif val >= WAIT_OBJECT_0 and val < WAIT_OBJECT_0 + len(handles):
+ fd, action = self._events[handles[val - WAIT_OBJECT_0]]
+ log.callWithLogger(fd, self._runAction, action, fd)
+
+ def _runWrite(self, fd):
+ closed = 0
+ try:
+ closed = fd.doWrite()
+ except:
+ closed = sys.exc_info()[1]
+ log.deferr()
+
+ if closed:
+ self.removeReader(fd)
+ self.removeWriter(fd)
+ try:
+ fd.connectionLost(failure.Failure(closed))
+ except:
+ log.deferr()
+ elif closed is None:
+ return 1
+
+ def _runAction(self, action, fd):
+ try:
+ closed = getattr(fd, action)()
+ except:
+ closed = sys.exc_info()[1]
+ log.deferr()
+
+ if closed:
+ self._disconnectSelectable(fd, closed, action == 'doRead')
+
+ doIteration = doWaitForMultipleEvents
+
+ def spawnProcess(self, processProtocol, executable, args=(), env={}, path=None, uid=None, gid=None, usePTY=0, childFDs=None):
+ """Spawn a process."""
+ if uid is not None:
+ raise ValueError("Setting UID is unsupported on this platform.")
+ if gid is not None:
+ raise ValueError("Setting GID is unsupported on this platform.")
+ if usePTY:
+ raise ValueError("PTYs are unsupported on this platform.")
+ if childFDs is not None:
+ raise ValueError(
+ "Custom child file descriptor mappings are unsupported on "
+ "this platform.")
+ args, env = self._checkProcessArgs(args, env)
+ return Process(self, processProtocol, executable, args, env, path)
+
+
+def install():
+ threadable.init(1)
+ r = Win32Reactor()
+ import main
+ main.installReactor(r)
+
+
+__all__ = ["Win32Reactor", "install"]
diff --git a/vendor/Twisted-10.0.0/twisted/internet/wxreactor.py b/vendor/Twisted-10.0.0/twisted/internet/wxreactor.py
new file mode 100644
index 0000000000..15defd81b3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/wxreactor.py
@@ -0,0 +1,181 @@
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module provides wxPython event loop support for Twisted.
+
+In order to use this support, simply do the following::
+
+ | from twisted.internet import wxreactor
+ | wxreactor.install()
+
+Then, when your root wxApp has been created::
+
+ | from twisted.internet import reactor
+ | reactor.registerWxApp(yourApp)
+ | reactor.run()
+
+Then use twisted.internet APIs as usual. Stop the event loop using
+reactor.stop(), not yourApp.ExitMainLoop().
+
+IMPORTANT: tests will fail when run under this reactor. This is
+expected and probably does not reflect on the reactor's ability to run
+real applications.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+import Queue
+try:
+ from wx import PySimpleApp as wxPySimpleApp, CallAfter as wxCallAfter, \
+ Timer as wxTimer
+except ImportError:
+ # older version of wxPython:
+ from wxPython.wx import wxPySimpleApp, wxCallAfter, wxTimer
+
+from twisted.python import log, runtime
+from twisted.internet import _threadedselect
+
+
+class ProcessEventsTimer(wxTimer):
+ """
+ Timer that tells wx to process pending events.
+
+ This is necessary on OS X, probably due to a bug in wx, if we want
+ wxCallAfters to be handled when modal dialogs, menus, etc. are open.
+ """
+ def __init__(self, wxapp):
+ wxTimer.__init__(self)
+ self.wxapp = wxapp
+
+
+ def Notify(self):
+ """
+ Called repeatedly by wx event loop.
+ """
+ self.wxapp.ProcessPendingEvents()
+
+
+
+class WxReactor(_threadedselect.ThreadedSelectReactor):
+ """
+ wxPython reactor.
+
+ wxPython drives the event loop, select() runs in a thread.
+ """
+
+ _stopping = False
+
+ def registerWxApp(self, wxapp):
+ """
+ Register wxApp instance with the reactor.
+ """
+ self.wxapp = wxapp
+
+ def _installSignalHandlersAgain(self):
+ """
+ wx sometimes removes our own signal handlers, so re-add them.
+ """
+ try:
+ # make _handleSignals happy:
+ import signal
+ signal.signal(signal.SIGINT, signal.default_int_handler)
+ except ImportError:
+ return
+ self._handleSignals()
+
+ def stop(self):
+ """
+ Stop the reactor.
+ """
+ if self._stopping:
+ return
+ self._stopping = True
+ _threadedselect.ThreadedSelectReactor.stop(self)
+
+ def _runInMainThread(self, f):
+ """
+ Schedule function to run in main wx/Twisted thread.
+
+ Called by the select() thread.
+ """
+ if hasattr(self, "wxapp"):
+ wxCallAfter(f)
+ else:
+ # wx shutdown but twisted hasn't
+ self._postQueue.put(f)
+
+ def _stopWx(self):
+ """
+ Stop the wx event loop if it hasn't already been stopped.
+
+ Called during Twisted event loop shutdown.
+ """
+ if hasattr(self, "wxapp"):
+ self.wxapp.ExitMainLoop()
+
+ def run(self, installSignalHandlers=True):
+ """
+ Start the reactor.
+ """
+ self._postQueue = Queue.Queue()
+ if not hasattr(self, "wxapp"):
+ log.msg("registerWxApp() was not called on reactor, "
+ "registering my own wxApp instance.")
+ self.registerWxApp(wxPySimpleApp())
+
+ # start select() thread:
+ self.interleave(self._runInMainThread,
+ installSignalHandlers=installSignalHandlers)
+ if installSignalHandlers:
+ self.callLater(0, self._installSignalHandlersAgain)
+
+ # add cleanup events:
+ self.addSystemEventTrigger("after", "shutdown", self._stopWx)
+ self.addSystemEventTrigger("after", "shutdown",
+ lambda: self._postQueue.put(None))
+
+ # On Mac OS X, work around wx bug by starting timer to ensure
+ # wxCallAfter calls are always processed. We don't wake up as
+ # often as we could since that uses too much CPU.
+ if runtime.platform.isMacOSX():
+ t = ProcessEventsTimer(self.wxapp)
+ t.Start(2) # wake up every 2ms
+
+ self.wxapp.MainLoop()
+ wxapp = self.wxapp
+ del self.wxapp
+
+ if not self._stopping:
+ # wx event loop exited without reactor.stop() being
+ # called. At this point events from select() thread will
+ # be added to _postQueue, but some may still be waiting
+ # unprocessed in wx, thus the ProcessPendingEvents()
+ # below.
+ self.stop()
+ wxapp.ProcessPendingEvents() # deal with any queued wxCallAfters
+ while 1:
+ try:
+ f = self._postQueue.get(timeout=0.01)
+ except Queue.Empty:
+ continue
+ else:
+ if f is None:
+ break
+ try:
+ f()
+ except:
+ log.err()
+
+
+def install():
+ """
+ Configure the twisted mainloop to be run inside the wxPython mainloop.
+ """
+ reactor = WxReactor()
+ from twisted.internet.main import installReactor
+ installReactor(reactor)
+ return reactor
+
+
+__all__ = ['install']
diff --git a/vendor/Twisted-10.0.0/twisted/internet/wxsupport.py b/vendor/Twisted-10.0.0/twisted/internet/wxsupport.py
new file mode 100644
index 0000000000..3cee8a9e4f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/internet/wxsupport.py
@@ -0,0 +1,61 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""Old method of wxPython support for Twisted.
+
+twisted.internet.wxreactor is probably a better choice.
+
+To use::
+
+ | # given a wxApp instance called myWxAppInstance:
+ | from twisted.internet import wxsupport
+ | wxsupport.install(myWxAppInstance)
+
+Use Twisted's APIs for running and stopping the event loop, don't use
+wxPython's methods.
+
+On Windows the Twisted event loop might block when dialogs are open
+or menus are selected.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+import warnings
+warnings.warn("wxsupport is not fully functional on Windows, wxreactor is better.")
+
+# wxPython imports
+from wxPython.wx import wxApp
+
+# twisted imports
+from twisted.internet import reactor
+from twisted.python.runtime import platformType
+
+
+class wxRunner:
+ """Make sure GUI events are handled."""
+
+ def __init__(self, app):
+ self.app = app
+
+ def run(self):
+ """
+ Execute pending WX events followed by WX idle events and
+ reschedule.
+ """
+ # run wx events
+ while self.app.Pending():
+ self.app.Dispatch()
+
+ # run wx idle events
+ self.app.ProcessIdle()
+ reactor.callLater(0.02, self.run)
+
+
+def install(app):
+ """Install the wxPython support, given a wxApp instance"""
+ runner = wxRunner(app)
+ reactor.callLater(0.02, runner.run)
+
+
+__all__ = ["install"]
diff --git a/vendor/Twisted-10.0.0/twisted/lore/__init__.py b/vendor/Twisted-10.0.0/twisted/lore/__init__.py
new file mode 100644
index 0000000000..78b63f0048
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/__init__.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+'''
+The Twisted Documentation Generation System
+
+Maintainer: Andrew Bennetts
+'''
+
+# TODO
+# Abstract
+# Bibliography
+# Index
+# Allow non-web image formats (EPS, specifically)
+# Allow pickle output and input to minimize parses
+# Numbered headers
+# Navigational aides
+
+from twisted.lore._version import version
+__version__ = version.short()
diff --git a/vendor/Twisted-10.0.0/twisted/lore/_version.py b/vendor/Twisted-10.0.0/twisted/lore/_version.py
new file mode 100644
index 0000000000..d5e0c37542
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/_version.py
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twisted.lore', 10, 0, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/lore/default.py b/vendor/Twisted-10.0.0/twisted/lore/default.py
new file mode 100644
index 0000000000..4a6f11b630
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/default.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Default processing factory plugin.
+"""
+
+from xml.dom import minidom as dom
+
+from twisted.lore import tree, latex, lint, process
+from twisted.web import sux
+
+htmlDefault = {'template': 'template.tpl', 'baseurl': '%s', 'ext': '.html'}
+
+class ProcessingFunctionFactory:
+
+ def getDoFile(self):
+ return tree.doFile
+
+ def generate_html(self, options, filenameGenerator=tree.getOutputFileName):
+ n = htmlDefault.copy()
+ n.update(options)
+ options = n
+ try:
+ fp = open(options['template'])
+ templ = dom.parse(fp)
+ except IOError, e:
+ raise process.NoProcessorError(e.filename+": "+e.strerror)
+ except sux.ParseError, e:
+ raise process.NoProcessorError(str(e))
+ df = lambda file, linkrel: self.getDoFile()(file, linkrel, options['ext'],
+ options['baseurl'], templ, options, filenameGenerator)
+ return df
+
+ latexSpitters = {None: latex.LatexSpitter,
+ 'section': latex.SectionLatexSpitter,
+ 'chapter': latex.ChapterLatexSpitter,
+ 'book': latex.BookLatexSpitter,
+ }
+
+ def generate_latex(self, options, filenameGenerator=None):
+ spitter = self.latexSpitters[None]
+ for (key, value) in self.latexSpitters.items():
+ if key and options.get(key):
+ spitter = value
+ df = lambda file, linkrel: latex.convertFile(file, spitter)
+ return df
+
+ def getLintChecker(self):
+ return lint.getDefaultChecker()
+
+ def generate_lint(self, options, filenameGenerator=None):
+ checker = self.getLintChecker()
+ return lambda file, linkrel: lint.doFile(file, checker)
+
+factory = ProcessingFunctionFactory()
diff --git a/vendor/Twisted-10.0.0/twisted/lore/docbook.py b/vendor/Twisted-10.0.0/twisted/lore/docbook.py
new file mode 100644
index 0000000000..42a538cf19
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/docbook.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+DocBook output support for Lore.
+"""
+
+import os, cgi
+from xml.dom import minidom as dom
+
+from twisted.lore import latex
+
+
+class DocbookSpitter(latex.BaseLatexSpitter):
+
+ currentLevel = 1
+
+ def writeNodeData(self, node):
+ self.writer(node.data)
+
+ def visitNode_body(self, node):
+ self.visitNodeDefault(node)
+ self.writer('</section>'*self.currentLevel)
+
+ def visitNodeHeader(self, node):
+ level = int(node.tagName[1])
+ difference, self.currentLevel = level-self.currentLevel, level
+ self.writer('<section>'*difference+'</section>'*-difference)
+ if difference<=0:
+ self.writer('</section>\n<section>')
+ self.writer('<title>')
+ self.visitNodeDefault(node)
+
+ def visitNode_a_listing(self, node):
+ fileName = os.path.join(self.currDir, node.getAttribute('href'))
+ self.writer('<programlisting>\n')
+ self.writer(cgi.escape(open(fileName).read()))
+ self.writer('</programlisting>\n')
+
+ def visitNode_a_href(self, node):
+ self.visitNodeDefault(node)
+
+ def visitNode_a_name(self, node):
+ self.visitNodeDefault(node)
+
+ def visitNode_li(self, node):
+ for child in node.childNodes:
+ if getattr(child, 'tagName', None) != 'p':
+ new = dom.Element('p')
+ new.childNodes = [child]
+ node.replaceChild(new, child)
+ self.visitNodeDefault(node)
+
+ visitNode_h2 = visitNode_h3 = visitNode_h4 = visitNodeHeader
+ end_h2 = end_h3 = end_h4 = '</title><para />'
+ start_title, end_title = '<section><title>', '</title><para />'
+ start_p, end_p = '<para>', '</para>'
+ start_strong, end_strong = start_em, end_em = '<emphasis>', '</emphasis>'
+ start_span_footnote, end_span_footnote = '<footnote><para>', '</para></footnote>'
+ start_q = end_q = '"'
+ start_pre, end_pre = '<programlisting>', '</programlisting>'
+ start_div_note, end_div_note = '<note>', '</note>'
+ start_li, end_li = '<listitem>', '</listitem>'
+ start_ul, end_ul = '<itemizedlist>', '</itemizedlist>'
+ start_ol, end_ol = '<orderedlist>', '</orderedlist>'
+ start_dl, end_dl = '<variablelist>', '</variablelist>'
+ start_dt, end_dt = '<varlistentry><term>', '</term>'
+ start_dd, end_dd = '<listitem><para>', '</para></listitem></varlistentry>'
diff --git a/vendor/Twisted-10.0.0/twisted/lore/htmlbook.py b/vendor/Twisted-10.0.0/twisted/lore/htmlbook.py
new file mode 100644
index 0000000000..a26abb3e80
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/htmlbook.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+def getNumber(filename):
+ return None
+
+def getReference(filename):
+ return None
+
+class Book:
+
+ def __init__(self, filename):
+ self.chapters = []
+ self.indexFilename = None
+
+ global Chapter
+ Chapter = self.Chapter
+ global getNumber
+ getNumber = self.getNumber
+ global getReference
+ getReference = self.getNumber
+ global Index
+ Index = self.Index
+
+ if filename:
+ execfile(filename)
+
+ def getFiles(self):
+ return [c[0] for c in self.chapters]
+
+ def getNumber(self, filename):
+ for c in self.chapters:
+ if c[0] == filename:
+ return c[1]
+ return None
+
+ def getIndexFilename(self):
+ return self.indexFilename
+
+ def Chapter(self, filename, number):
+ self.chapters.append((filename, number))
+
+ def Index(self, filename):
+ self.indexFilename = filename
+
+#_book = Book(None)
diff --git a/vendor/Twisted-10.0.0/twisted/lore/indexer.py b/vendor/Twisted-10.0.0/twisted/lore/indexer.py
new file mode 100644
index 0000000000..5feb95d579
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/indexer.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+def setIndexFilename(filename='index.xhtml'):
+ global indexFilename
+ indexFilename = filename
+
+def getIndexFilename():
+ global indexFilename
+ return indexFilename
+
+def addEntry(filename, anchor, text, reference):
+ global entries
+ if not entries.has_key(text):
+ entries[text] = []
+ entries[text].append((filename, anchor, reference))
+
+def clearEntries():
+ global entries
+ entries = {}
+
+def generateIndex():
+ global entries
+ global indexFilename
+
+ if not indexFilename:
+ return
+
+ f = open(indexFilename, 'w')
+ sortedEntries = [(e.lower(), e) for e in entries]
+ sortedEntries.sort()
+ sortedEntries = [e[1] for e in sortedEntries]
+ for text in sortedEntries:
+ refs = []
+ f.write(text.replace('!', ', ') + ': ')
+ for (file, anchor, reference) in entries[text]:
+ refs.append('<a href="%s#%s">%s</a>' % (file, anchor, reference))
+ if text == 'infinite recursion':
+ refs.append('<em>See Also:</em> recursion, infinite\n')
+ if text == 'recursion!infinite':
+ refs.append('<em>See Also:</em> infinite recursion\n')
+ f.write('%s<br />\n' % ", ".join(refs))
+ f.close()
+
+def reset():
+ clearEntries()
+ setIndexFilename()
+
+reset()
diff --git a/vendor/Twisted-10.0.0/twisted/lore/latex.py b/vendor/Twisted-10.0.0/twisted/lore/latex.py
new file mode 100644
index 0000000000..2976a8a0e6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/latex.py
@@ -0,0 +1,463 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+LaTeX output support for Lore.
+"""
+
+from xml.dom import minidom as dom
+import os.path, re, string
+from cStringIO import StringIO
+import urlparse
+
+from twisted.web import domhelpers
+from twisted.python import text, procutils
+
+import tree
+
+escapingRE = re.compile(r'([\[\]#$%&_{}^~\\])')
+lowerUpperRE = re.compile(r'([a-z])([A-Z])')
+
+def _escapeMatch(match):
+ c = match.group()
+ if c == '\\':
+ return '$\\backslash$'
+ elif c == '~':
+ return '\\~{}'
+ elif c == '^':
+ return '\\^{}'
+ elif c in '[]':
+ return '{'+c+'}'
+ else:
+ return '\\' + c
+
+def latexEscape(txt):
+ txt = escapingRE.sub(_escapeMatch, txt)
+ return txt.replace('\n', ' ')
+
+entities = {'amp': '\&', 'gt': '>', 'lt': '<', 'quot': '"',
+ 'copy': '\\copyright', 'mdash': '---', 'rdquo': '``',
+ 'ldquo': "''"}
+
+
+def realpath(path):
+ # Normalise path
+ cwd = os.getcwd()
+ path = os.path.normpath(os.path.join(cwd, path))
+ return path.replace('\\', '/') # windows slashes make LaTeX blow up
+
+
+def getLatexText(node, writer, filter=lambda x:x, entities=entities):
+ if hasattr(node, 'eref'):
+ return writer(entities.get(node.eref, ''))
+ if hasattr(node, 'data'):
+ if isinstance(node.data, unicode):
+ data = node.data.encode('utf-8')
+ else:
+ data = node.data
+ return writer(filter(data))
+ for child in node.childNodes:
+ getLatexText(child, writer, filter, entities)
+
+class BaseLatexSpitter:
+
+ def __init__(self, writer, currDir='.', filename=''):
+ self.writer = writer
+ self.currDir = currDir
+ self.filename = filename
+
+ def visitNode(self, node):
+ if isinstance(node, dom.Comment):
+ return
+ if not hasattr(node, 'tagName'):
+ self.writeNodeData(node)
+ return
+ getattr(self, 'visitNode_'+node.tagName, self.visitNodeDefault)(node)
+
+ def visitNodeDefault(self, node):
+ self.writer(getattr(self, 'start_'+node.tagName, ''))
+ for child in node.childNodes:
+ self.visitNode(child)
+ self.writer(getattr(self, 'end_'+node.tagName, ''))
+
+ def visitNode_a(self, node):
+ if node.hasAttribute('class'):
+ if node.getAttribute('class').endswith('listing'):
+ return self.visitNode_a_listing(node)
+ if node.hasAttribute('href'):
+ return self.visitNode_a_href(node)
+ if node.hasAttribute('name'):
+ return self.visitNode_a_name(node)
+ self.visitNodeDefault(node)
+
+ def visitNode_span(self, node):
+ if not node.hasAttribute('class'):
+ return self.visitNodeDefault(node)
+ node.tagName += '_'+node.getAttribute('class')
+ self.visitNode(node)
+
+ visitNode_div = visitNode_span
+
+ def visitNode_h1(self, node):
+ pass
+
+ def visitNode_style(self, node):
+ pass
+
+
+class LatexSpitter(BaseLatexSpitter):
+
+ baseLevel = 0
+ diaHack = bool(procutils.which("dia"))
+
+ def writeNodeData(self, node):
+ buf = StringIO()
+ getLatexText(node, buf.write, latexEscape)
+ self.writer(buf.getvalue().replace('<', '$<$').replace('>', '$>$'))
+
+ def visitNode_head(self, node):
+ authorNodes = domhelpers.findElementsWithAttribute(node, 'rel', 'author')
+ authorNodes = [n for n in authorNodes if n.tagName == 'link']
+
+ if authorNodes:
+ self.writer('\\author{')
+ authors = []
+ for aNode in authorNodes:
+ name = aNode.getAttribute('title')
+ href = aNode.getAttribute('href')
+ if href.startswith('mailto:'):
+ href = href[7:]
+ if href:
+ if name:
+ name += ' '
+ name += '$<$' + href + '$>$'
+ if name:
+ authors.append(name)
+
+ self.writer(' \\and '.join(authors))
+ self.writer('}')
+
+ self.visitNodeDefault(node)
+
+ def visitNode_pre(self, node):
+ self.writer('\\begin{verbatim}\n')
+ buf = StringIO()
+ getLatexText(node, buf.write)
+ self.writer(text.removeLeadingTrailingBlanks(buf.getvalue()))
+ self.writer('\\end{verbatim}\n')
+
+ def visitNode_code(self, node):
+ fout = StringIO()
+ getLatexText(node, fout.write, latexEscape)
+ data = lowerUpperRE.sub(r'\1\\linebreak[1]\2', fout.getvalue())
+ data = data[:1] + data[1:].replace('.', '.\\linebreak[1]')
+ self.writer('\\texttt{'+data+'}')
+
+ def visitNode_img(self, node):
+ fileName = os.path.join(self.currDir, node.getAttribute('src'))
+ target, ext = os.path.splitext(fileName)
+ if self.diaHack and os.access(target + '.dia', os.R_OK):
+ ext = '.dia'
+ fileName = target + ext
+ f = getattr(self, 'convert_'+ext[1:], None)
+ if not f:
+ return
+ target = os.path.join(self.currDir, os.path.basename(target)+'.eps')
+ f(fileName, target)
+ target = os.path.basename(target)
+ self._write_img(target)
+
+ def _write_img(self, target):
+ """Write LaTeX for image."""
+ self.writer('\\begin{center}\\includegraphics[%%\n'
+ 'width=1.0\n'
+ '\\textwidth,height=1.0\\textheight,\nkeepaspectratio]'
+ '{%s}\\end{center}\n' % target)
+
+ def convert_png(self, src, target):
+ # XXX there's a *reason* Python comes with the pipes module -
+ # someone fix this to use it.
+ r = os.system('pngtopnm "%s" | pnmtops -noturn > "%s"' % (src, target))
+ if r != 0:
+ raise OSError(r)
+
+ def convert_dia(self, src, target):
+ # EVIL DISGUSTING HACK
+ data = os.popen("gunzip -dc %s" % (src)).read()
+ pre = '<dia:attribute name="scaling">\n <dia:real val="1"/>'
+ post = '<dia:attribute name="scaling">\n <dia:real val="0.5"/>'
+ f = open('%s_hacked.dia' % (src), 'wb')
+ f.write(data.replace(pre, post))
+ f.close()
+ os.system('gzip %s_hacked.dia' % (src,))
+ os.system('mv %s_hacked.dia.gz %s_hacked.dia' % (src,src))
+ # Let's pretend we never saw that.
+
+ # Silly dia needs an X server, even though it doesn't display anything.
+ # If this is a problem for you, try using Xvfb.
+ os.system("dia %s_hacked.dia -n -e %s" % (src, target))
+
+ def visitNodeHeader(self, node):
+ level = (int(node.tagName[1])-2)+self.baseLevel
+ self.writer('\n\n\\'+level*'sub'+'section{')
+ spitter = HeadingLatexSpitter(self.writer, self.currDir, self.filename)
+ spitter.visitNodeDefault(node)
+ self.writer('}\n')
+
+ def visitNode_a_listing(self, node):
+ fileName = os.path.join(self.currDir, node.getAttribute('href'))
+ self.writer('\\begin{verbatim}\n')
+ lines = map(string.rstrip, open(fileName).readlines())
+ skipLines = int(node.getAttribute('skipLines') or 0)
+ lines = lines[skipLines:]
+ self.writer(text.removeLeadingTrailingBlanks('\n'.join(lines)))
+ self.writer('\\end{verbatim}')
+
+ # Write a caption for this source listing
+ fileName = os.path.basename(fileName)
+ caption = domhelpers.getNodeText(node)
+ if caption == fileName:
+ caption = 'Source listing'
+ self.writer('\parbox[b]{\linewidth}{\\begin{center}%s --- '
+ '\\begin{em}%s\\end{em}\\end{center}}'
+ % (latexEscape(caption), latexEscape(fileName)))
+
+ def visitNode_a_href(self, node):
+ supported_schemes=['http', 'https', 'ftp', 'mailto']
+ href = node.getAttribute('href')
+ if urlparse.urlparse(href)[0] in supported_schemes:
+ text = domhelpers.getNodeText(node)
+ self.visitNodeDefault(node)
+ if text != href:
+ self.writer('\\footnote{%s}' % latexEscape(href))
+ else:
+ path, fragid = (href.split('#', 1) + [None])[:2]
+ if path == '':
+ path = self.filename
+ else:
+ path = os.path.join(os.path.dirname(self.filename), path)
+ #if path == '':
+ #path = os.path.basename(self.filename)
+ #else:
+ # # Hack for linking to man pages from howtos, i.e.
+ # # ../doc/foo-man.html -> foo-man.html
+ # path = os.path.basename(path)
+
+ path = realpath(path)
+
+ if fragid:
+ ref = path + 'HASH' + fragid
+ else:
+ ref = path
+ self.writer('\\textit{')
+ self.visitNodeDefault(node)
+ self.writer('}')
+ self.writer('\\loreref{%s}' % ref)
+
+ def visitNode_a_name(self, node):
+ self.writer('\\label{%sHASH%s}' % (
+ realpath(self.filename), node.getAttribute('name')))
+ self.visitNodeDefault(node)
+
+ def visitNode_table(self, node):
+ rows = [[col for col in row.childNodes
+ if getattr(col, 'tagName', None) in ('th', 'td')]
+ for row in node.childNodes if getattr(row, 'tagName', None)=='tr']
+ numCols = 1+max([len(row) for row in rows])
+ self.writer('\\begin{table}[ht]\\begin{center}')
+ self.writer('\\begin{tabular}{@{}'+'l'*numCols+'@{}}')
+ for row in rows:
+ th = 0
+ for col in row:
+ self.visitNode(col)
+ self.writer('&')
+ if col.tagName == 'th':
+ th = 1
+ self.writer('\\\\\n') #\\ ends lines
+ if th:
+ self.writer('\\hline\n')
+ self.writer('\\end{tabular}\n')
+ if node.hasAttribute('title'):
+ self.writer('\\caption{%s}'
+ % latexEscape(node.getAttribute('title')))
+ self.writer('\\end{center}\\end{table}\n')
+
+ def visitNode_span_footnote(self, node):
+ self.writer('\\footnote{')
+ spitter = FootnoteLatexSpitter(self.writer, self.currDir, self.filename)
+ spitter.visitNodeDefault(node)
+ self.writer('}')
+
+ def visitNode_span_index(self, node):
+ self.writer('\\index{%s}\n' % node.getAttribute('value'))
+ self.visitNodeDefault(node)
+
+ visitNode_h2 = visitNode_h3 = visitNode_h4 = visitNodeHeader
+
+ start_title = '\\title{'
+ end_title = '}\n'
+
+ start_sub = '$_{'
+ end_sub = '}$'
+
+ start_sup = '$^{'
+ end_sup = '}$'
+
+ start_html = '''\\documentclass{article}
+ \\newcommand{\\loreref}[1]{%
+ \\ifthenelse{\\value{page}=\\pageref{#1}}%
+ { (this page)}%
+ { (page \\pageref{#1})}%
+ }'''
+
+ start_body = '\\begin{document}\n\\maketitle\n'
+ end_body = '\\end{document}'
+
+ start_dl = '\\begin{description}\n'
+ end_dl = '\\end{description}\n'
+ start_ul = '\\begin{itemize}\n'
+ end_ul = '\\end{itemize}\n'
+
+ start_ol = '\\begin{enumerate}\n'
+ end_ol = '\\end{enumerate}\n'
+
+ start_li = '\\item '
+ end_li = '\n'
+
+ start_dt = '\\item['
+ end_dt = ']'
+ end_dd = '\n'
+
+ start_p = '\n\n'
+
+ start_strong = start_em = '\\begin{em}'
+ end_strong = end_em = '\\end{em}'
+
+ start_q = "``"
+ end_q = "''"
+
+ start_div_note = '\\begin{quotation}\\textbf{Note:}'
+ end_div_note = '\\end{quotation}'
+
+ start_th = '\\textbf{'
+ end_th = '}'
+
+
+class SectionLatexSpitter(LatexSpitter):
+
+ baseLevel = 1
+
+ start_title = '\\section{'
+
+ def visitNode_title(self, node):
+ self.visitNodeDefault(node)
+ #self.writer('\\label{%s}}\n' % os.path.basename(self.filename))
+ self.writer('\\label{%s}}\n' % realpath(self.filename))
+
+ end_title = end_body = start_body = start_html = ''
+
+
+class ChapterLatexSpitter(SectionLatexSpitter):
+ baseLevel = 0
+ start_title = '\\chapter{'
+
+
+class HeadingLatexSpitter(BaseLatexSpitter):
+ start_q = "``"
+ end_q = "''"
+
+ writeNodeData = LatexSpitter.writeNodeData.im_func
+
+
+class FootnoteLatexSpitter(LatexSpitter):
+ """For multi-paragraph footnotes, this avoids having an empty leading
+ paragraph."""
+
+ start_p = ''
+
+ def visitNode_span_footnote(self, node):
+ self.visitNodeDefault(node)
+
+ def visitNode_p(self, node):
+ self.visitNodeDefault(node)
+ self.start_p = LatexSpitter.start_p
+
+class BookLatexSpitter(LatexSpitter):
+ def visitNode_body(self, node):
+ tocs=domhelpers.locateNodes([node], 'class', 'toc')
+ domhelpers.clearNode(node)
+ if len(tocs):
+ toc=tocs[0]
+ node.appendChild(toc)
+ self.visitNodeDefault(node)
+
+ def visitNode_link(self, node):
+ if not node.hasAttribute('rel'):
+ return self.visitNodeDefault(node)
+ node.tagName += '_'+node.getAttribute('rel')
+ self.visitNode(node)
+
+ def visitNode_link_author(self, node):
+ self.writer('\\author{%s}\n' % node.getAttribute('text'))
+
+ def visitNode_link_stylesheet(self, node):
+ if node.hasAttribute('type') and node.hasAttribute('href'):
+ if node.getAttribute('type')=='application/x-latex':
+ packagename=node.getAttribute('href')
+ packagebase,ext=os.path.splitext(packagename)
+ self.writer('\\usepackage{%s}\n' % packagebase)
+
+ start_html = r'''\documentclass[oneside]{book}
+\usepackage{graphicx}
+\usepackage{times,mathptmx}
+'''
+
+ start_body = r'''\begin{document}
+\maketitle
+\tableofcontents
+'''
+
+ start_li=''
+ end_li=''
+ start_ul=''
+ end_ul=''
+
+
+ def visitNode_a(self, node):
+ if node.hasAttribute('class'):
+ a_class=node.getAttribute('class')
+ if a_class.endswith('listing'):
+ return self.visitNode_a_listing(node)
+ else:
+ return getattr(self, 'visitNode_a_%s' % a_class)(node)
+ if node.hasAttribute('href'):
+ return self.visitNode_a_href(node)
+ if node.hasAttribute('name'):
+ return self.visitNode_a_name(node)
+ self.visitNodeDefault(node)
+
+ def visitNode_a_chapter(self, node):
+ self.writer('\\chapter{')
+ self.visitNodeDefault(node)
+ self.writer('}\n')
+
+ def visitNode_a_sect(self, node):
+ base,ext=os.path.splitext(node.getAttribute('href'))
+ self.writer('\\input{%s}\n' % base)
+
+
+
+def processFile(spitter, fin):
+ # XXX Use Inversion Of Control Pattern to orthogonalize the parsing API
+ # from the Visitor Pattern application. (EnterPrise)
+ dom = tree.parseFileAndReport(fin.name, lambda x: fin).documentElement
+ spitter.visitNode(dom)
+
+
+def convertFile(filename, spitterClass):
+ fout = open(os.path.splitext(filename)[0]+".tex", 'w')
+ spitter = spitterClass(fout.write, os.path.dirname(filename), filename)
+ fin = open(filename)
+ processFile(spitter, fin)
+ fin.close()
+ fout.close()
diff --git a/vendor/Twisted-10.0.0/twisted/lore/lint.py b/vendor/Twisted-10.0.0/twisted/lore/lint.py
new file mode 100644
index 0000000000..1ac22e268b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/lint.py
@@ -0,0 +1,204 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Checker for common errors in Lore documents.
+"""
+
+from xml.dom import minidom as dom
+import parser, urlparse, os.path
+
+from twisted.lore import tree, process
+from twisted.web import domhelpers
+from twisted.python import reflect
+
+
+# parser.suite in Python 2.3 raises SyntaxError, <2.3 raises parser.ParserError
+parserErrors = (SyntaxError, parser.ParserError)
+
+class TagChecker:
+
+ def check(self, dom, filename):
+ self.hadErrors = 0
+ for method in reflect.prefixedMethods(self, 'check_'):
+ method(dom, filename)
+ if self.hadErrors:
+ raise process.ProcessingFailure("invalid format")
+
+ def _reportError(self, filename, element, error):
+ hlint = element.hasAttribute('hlint') and element.getAttribute('hlint')
+ if hlint != 'off':
+ self.hadErrors = 1
+ pos = getattr(element, '_markpos', None) or (0, 0)
+ print "%s:%s:%s: %s" % ((filename,)+pos+(error,))
+
+
+class DefaultTagChecker(TagChecker):
+
+ def __init__(self, allowedTags, allowedClasses):
+ self.allowedTags = allowedTags
+ self.allowedClasses = allowedClasses
+
+ def check_disallowedElements(self, dom, filename):
+ def m(node, self=self):
+ return not self.allowedTags(node.tagName)
+ for element in domhelpers.findElements(dom, m):
+ self._reportError(filename, element,
+ 'unrecommended tag %s' % element.tagName)
+
+ def check_disallowedClasses(self, dom, filename):
+ def matcher(element, self=self):
+ if not element.hasAttribute('class'):
+ return 0
+ checker = self.allowedClasses.get(element.tagName, lambda x:0)
+ return not checker(element.getAttribute('class'))
+ for element in domhelpers.findElements(dom, matcher):
+ self._reportError(filename, element,
+ 'unknown class %s' %element.getAttribute('class'))
+
+ def check_quote(self, doc, filename):
+ def matcher(node):
+ return ('"' in getattr(node, 'data', '') and
+ not isinstance(node, dom.Comment) and
+ not [1 for n in domhelpers.getParents(node)[1:-1]
+ if n.tagName in ('pre', 'code')])
+ for node in domhelpers.findNodes(doc, matcher):
+ self._reportError(filename, node.parentNode, 'contains quote')
+
+ def check_styleattr(self, dom, filename):
+ for node in domhelpers.findElementsWithAttribute(dom, 'style'):
+ self._reportError(filename, node, 'explicit style')
+
+ def check_align(self, dom, filename):
+ for node in domhelpers.findElementsWithAttribute(dom, 'align'):
+ self._reportError(filename, node, 'explicit alignment')
+
+ def check_style(self, dom, filename):
+ for node in domhelpers.findNodesNamed(dom, 'style'):
+ if domhelpers.getNodeText(node) != '':
+ self._reportError(filename, node, 'hand hacked style')
+
+ def check_title(self, dom, filename):
+ doc = dom.documentElement
+ title = domhelpers.findNodesNamed(dom, 'title')
+ if len(title)!=1:
+ return self._reportError(filename, doc, 'not exactly one title')
+ h1 = domhelpers.findNodesNamed(dom, 'h1')
+ if len(h1)!=1:
+ return self._reportError(filename, doc, 'not exactly one h1')
+ if domhelpers.getNodeText(h1[0]) != domhelpers.getNodeText(title[0]):
+ self._reportError(filename, h1[0], 'title and h1 text differ')
+
+ def check_80_columns(self, dom, filename):
+ for node in domhelpers.findNodesNamed(dom, 'pre'):
+ # the ps/pdf output is in a font that cuts off at 80 characters,
+ # so this is enforced to make sure the interesting parts (which
+ # are likely to be on the right-hand edge) stay on the printed
+ # page.
+ for line in domhelpers.gatherTextNodes(node, 1).split('\n'):
+ if len(line.rstrip()) > 80:
+ self._reportError(filename, node,
+ 'text wider than 80 columns in pre')
+ for node in domhelpers.findNodesNamed(dom, 'a'):
+ if node.getAttribute('class').endswith('listing'):
+ try:
+ fn = os.path.dirname(filename)
+ fn = os.path.join(fn, node.getAttribute('href'))
+ lines = open(fn,'r').readlines()
+ except:
+ self._reportError(filename, node,
+ 'bad listing href: %r' %
+ node.getAttribute('href'))
+ continue
+
+ for line in lines:
+ if len(line.rstrip()) > 80:
+ self._reportError(filename, node,
+ 'listing wider than 80 columns')
+
+ def check_pre_py_listing(self, dom, filename):
+ for node in domhelpers.findNodesNamed(dom, 'pre'):
+ if node.getAttribute('class') == 'python':
+ try:
+ text = domhelpers.getNodeText(node)
+ # Fix < and >
+ text = text.replace('&gt;', '>').replace('&lt;', '<')
+ # Strip blank lines
+ lines = filter(None,[l.rstrip() for l in text.split('\n')])
+ # Strip leading space
+ while not [1 for line in lines if line[:1] not in ('',' ')]:
+ lines = [line[1:] for line in lines]
+ text = '\n'.join(lines) + '\n'
+ try:
+ parser.suite(text)
+ except parserErrors, e:
+ # Pretend the "..." idiom is syntactically valid
+ text = text.replace("...","'...'")
+ parser.suite(text)
+ except parserErrors, e:
+ self._reportError(filename, node,
+ 'invalid python code:' + str(e))
+
+ def check_anchor_in_heading(self, dom, filename):
+ headingNames = ['h%d' % n for n in range(1,7)]
+ for hname in headingNames:
+ for node in domhelpers.findNodesNamed(dom, hname):
+ if domhelpers.findNodesNamed(node, 'a'):
+ self._reportError(filename, node, 'anchor in heading')
+
+ def check_texturl_matches_href(self, dom, filename):
+ for node in domhelpers.findNodesNamed(dom, 'a'):
+ if not node.hasAttribute('href'):
+ continue
+ text = domhelpers.getNodeText(node)
+ proto = urlparse.urlparse(text)[0]
+ if proto and ' ' not in text:
+ if text != node.getAttribute('href'):
+ self._reportError(filename, node,
+ 'link text does not match href')
+
+ def check_lists(self, dom, filename):
+ for node in (domhelpers.findNodesNamed(dom, 'ul')+
+ domhelpers.findNodesNamed(dom, 'ol')):
+ if not node.childNodes:
+ self._reportError(filename, node, 'empty list')
+ for child in node.childNodes:
+ if child.nodeName != 'li':
+ self._reportError(filename, node,
+ 'only list items allowed in lists')
+
+
+def list2dict(l):
+ d = {}
+ for el in l:
+ d[el] = None
+ return d
+
+classes = list2dict(['shell', 'API', 'python', 'py-prototype', 'py-filename',
+ 'py-src-string', 'py-signature', 'py-src-parameter',
+ 'py-src-identifier', 'py-src-keyword'])
+
+tags = list2dict(["html", "title", "head", "body", "h1", "h2", "h3", "ol", "ul",
+ "dl", "li", "dt", "dd", "p", "code", "img", "blockquote", "a",
+ "cite", "div", "span", "strong", "em", "pre", "q", "table",
+ "tr", "td", "th", "style", "sub", "sup", "link"])
+
+span = list2dict(['footnote', 'manhole-output', 'index'])
+
+div = list2dict(['note', 'boxed', 'doit'])
+
+a = list2dict(['listing', 'py-listing', 'html-listing', 'absolute'])
+
+pre = list2dict(['python', 'shell', 'python-interpreter', 'elisp'])
+
+allowed = {'code': classes.has_key, 'span': span.has_key, 'div': div.has_key,
+ 'a': a.has_key, 'pre': pre.has_key, 'ul': lambda x: x=='toc',
+ 'ol': lambda x: x=='toc', 'li': lambda x: x=='ignoretoc'}
+
+def getDefaultChecker():
+ return DefaultTagChecker(tags.has_key, allowed)
+
+def doFile(file, checker):
+ doc = tree.parseFileAndReport(file)
+ if doc:
+ checker.check(doc, file)
diff --git a/vendor/Twisted-10.0.0/twisted/lore/lmath.py b/vendor/Twisted-10.0.0/twisted/lore/lmath.py
new file mode 100644
index 0000000000..ab2fc6fc02
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/lmath.py
@@ -0,0 +1,85 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+LaTeX-defined image support for Lore documents.
+"""
+
+import os, tempfile
+from xml.dom import minidom as dom
+
+from twisted.web import domhelpers
+import latex, tree, lint, default
+
+
+class MathLatexSpitter(latex.LatexSpitter):
+
+ start_html = '\\documentclass{amsart}\n'
+
+ def visitNode_div_latexmacros(self, node):
+ self.writer(domhelpers.getNodeText(node))
+
+ def visitNode_span_latexformula(self, node):
+ self.writer('\[')
+ self.writer(domhelpers.getNodeText(node))
+ self.writer('\]')
+
+def formulaeToImages(document, dir, _system=os.system):
+ # gather all macros
+ macros = ''
+ for node in domhelpers.findElementsWithAttribute(document, 'class',
+ 'latexmacros'):
+ macros += domhelpers.getNodeText(node)
+ node.parentNode.removeChild(node)
+ i = 0
+ for node in domhelpers.findElementsWithAttribute(document, 'class',
+ 'latexformula'):
+ latexText='''\\documentclass[12pt]{amsart}%s
+ \\begin{document}\[%s\]
+ \\end{document}''' % (macros, domhelpers.getNodeText(node))
+ # This file really should be cleaned up by this function, or placed
+ # somewhere such that the calling code can find it and clean it up.
+ file = tempfile.mktemp()
+ f = open(file+'.tex', 'w')
+ f.write(latexText)
+ f.close()
+ _system('latex %s.tex' % file)
+ _system('dvips %s.dvi -o %s.ps' % (os.path.basename(file), file))
+ baseimgname = 'latexformula%d.png' % i
+ imgname = os.path.join(dir, baseimgname)
+ i += 1
+ _system('pstoimg -type png -crop a -trans -interlace -out '
+ '%s %s.ps' % (imgname, file))
+ newNode = dom.parseString(
+ '<span><br /><img src="%s" /><br /></span>' % (
+ baseimgname,)).documentElement
+ node.parentNode.replaceChild(newNode, node)
+
+
+def doFile(fn, docsdir, ext, url, templ, linkrel='', d=None):
+ d = d or {}
+ doc = tree.parseFileAndReport(fn)
+ formulaeToImages(doc, os.path.dirname(fn))
+ cn = templ.cloneNode(1)
+ tree.munge(doc, cn, linkrel, docsdir, fn, ext, url, d)
+ cn.writexml(open(os.path.splitext(fn)[0]+ext, 'wb'))
+
+
+class ProcessingFunctionFactory(default.ProcessingFunctionFactory):
+
+ latexSpitters = {None: MathLatexSpitter}
+
+ def getDoFile(self):
+ return doFile
+
+ def getLintChecker(self):
+ checker = lint.getDefaultChecker()
+ checker.allowedClasses = checker.allowedClasses.copy()
+ oldDiv = checker.allowedClasses['div']
+ oldSpan = checker.allowedClasses['span']
+ checker.allowedClasses['div'] = lambda x:oldDiv(x) or x=='latexmacros'
+ checker.allowedClasses['span'] = (lambda x:oldSpan(x) or
+ x=='latexformula')
+ return checker
+
+factory = ProcessingFunctionFactory()
diff --git a/vendor/Twisted-10.0.0/twisted/lore/man2lore.py b/vendor/Twisted-10.0.0/twisted/lore/man2lore.py
new file mode 100644
index 0000000000..62dc228bdb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/man2lore.py
@@ -0,0 +1,295 @@
+# -*- test-case-name: twisted.lore.test.test_man2lore -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+man2lore: Converts man page source (i.e. groff) into lore-compatible html.
+
+This is nasty and hackish (and doesn't support lots of real groff), but is good
+enough for converting fairly simple man pages.
+"""
+
+import re, os
+
+quoteRE = re.compile('"(.*?)"')
+
+
+
+def escape(text):
+ text = text.replace('<', '&lt;').replace('>', '&gt;')
+ text = quoteRE.sub('<q>\\1</q>', text)
+ return text
+
+
+
+def stripQuotes(s):
+ if s[0] == s[-1] == '"':
+ s = s[1:-1]
+ return s
+
+
+
+class ManConverter(object):
+ """
+ Convert a man page to the Lore format.
+
+ @ivar tp: State variable for handling text inside a C{TP} token. It can
+ take values from 0 to 3:
+ - 0: when outside of a C{TP} token.
+ - 1: once a C{TP} token has been encountered. If the previous value
+ was 0, a definition list is started. Then, at the first line of
+ text, a definition term is started.
+ - 2: when the first line after the C{TP} token has been handled.
+ The definition term is closed, and a definition is started with
+ the next line of text.
+ - 3: when the first line as definition data has been handled.
+ @type tp: C{int}
+ """
+ state = 'regular'
+ name = None
+ tp = 0
+ dl = 0
+ para = 0
+
+ def convert(self, inf, outf):
+ self.write = outf.write
+ longline = ''
+ for line in inf.readlines():
+ if line.rstrip() and line.rstrip()[-1] == '\\':
+ longline += line.rstrip()[:-1] + ' '
+ continue
+ if longline:
+ line = longline + line
+ longline = ''
+ self.lineReceived(line)
+ self.closeTags()
+ self.write('</body>\n</html>\n')
+ outf.flush()
+
+
+ def lineReceived(self, line):
+ if line[0] == '.':
+ f = getattr(self, 'macro_' + line[1:3].rstrip().upper(), None)
+ if f:
+ f(line[3:].strip())
+ else:
+ self.text(line)
+
+
+ def continueReceived(self, cont):
+ if not cont:
+ return
+ if cont[0].isupper():
+ f = getattr(self, 'macro_' + cont[:2].rstrip().upper(), None)
+ if f:
+ f(cont[2:].strip())
+ else:
+ self.text(cont)
+
+
+ def closeTags(self):
+ if self.state != 'regular':
+ self.write('</%s>' % self.state)
+ if self.tp == 3:
+ self.write('</dd>\n\n')
+ self.tp = 0
+ if self.dl:
+ self.write('</dl>\n\n')
+ self.dl = 0
+ if self.para:
+ self.write('</p>\n\n')
+ self.para = 0
+
+
+ def paraCheck(self):
+ if not self.tp and not self.para:
+ self.write('<p>')
+ self.para = 1
+
+
+ def macro_TH(self, line):
+ self.write(
+ '<?xml version="1.0"?>\n'
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n'
+ ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
+ self.write('<html><head>\n')
+ parts = [stripQuotes(x) for x in line.split(' ', 2)] + ['', '']
+ title, manSection = parts[:2]
+ self.write('<title>%s.%s</title>' % (title, manSection))
+ self.write('</head>\n<body>\n\n')
+ self.write('<h1>%s.%s</h1>\n\n' % (title, manSection))
+
+ macro_DT = macro_TH
+
+
+ def macro_SH(self, line):
+ self.closeTags()
+ self.write('<h2>')
+ self.para = 1
+ self.text(stripQuotes(line))
+ self.para = 0
+ self.closeTags()
+ self.write('</h2>\n\n')
+
+
+ def macro_B(self, line):
+ words = line.split()
+ words[0] = '\\fB' + words[0] + '\\fR '
+ self.text(' '.join(words))
+
+
+ def macro_NM(self, line):
+ if not self.name:
+ self.name = line
+ self.text(self.name + ' ')
+
+
+ def macro_NS(self, line):
+ parts = line.split(' Ns ')
+ i = 0
+ for l in parts:
+ i = not i
+ if i:
+ self.text(l)
+ else:
+ self.continueReceived(l)
+
+
+ def macro_OO(self, line):
+ self.text('[')
+ self.continueReceived(line)
+
+
+ def macro_OC(self, line):
+ self.text(']')
+ self.continueReceived(line)
+
+
+ def macro_OP(self, line):
+ self.text('[')
+ self.continueReceived(line)
+ self.text(']')
+
+
+ def macro_FL(self, line):
+ parts = line.split()
+ self.text('\\fB-%s\\fR' % parts[0])
+ self.continueReceived(' '.join(parts[1:]))
+
+
+ def macro_AR(self, line):
+ parts = line.split()
+ self.text('\\fI %s\\fR' % parts[0])
+ self.continueReceived(' '.join(parts[1:]))
+
+
+ def macro_PP(self, line):
+ self.closeTags()
+
+
+ def macro_IC(self, line):
+ cmd = line.split(' ', 1)[0]
+ args = line[line.index(cmd) + len(cmd):]
+ args = args.split(' ')
+ text = cmd
+ while args:
+ arg = args.pop(0)
+ if arg.lower() == "ar":
+ text += " \\fU%s\\fR" % (args.pop(0),)
+ elif arg.lower() == "op":
+ ign = args.pop(0)
+ text += " [\\fU%s\\fR]" % (args.pop(0),)
+
+ self.text(text)
+
+
+ def macro_TP(self, line):
+ """
+ Handle C{TP} token: start a definition list if it's first token, or
+ close previous definition data.
+ """
+ if self.tp == 3:
+ self.write('</dd>\n\n')
+ self.tp = 1
+ else:
+ self.tp = 1
+ self.write('<dl>')
+ self.dl = 1
+
+
+ def macro_BL(self, line):
+ self.write('<dl>')
+ self.tp = 1
+
+
+ def macro_EL(self, line):
+ if self.tp == 3:
+ self.write('</dd>')
+ self.tp = 1
+ self.write('</dl>\n\n')
+ self.tp = 0
+
+
+ def macro_IT(self, line):
+ if self.tp == 3:
+ self.write('</dd>')
+ self.tp = 1
+ self.continueReceived(line)
+
+
+ def text(self, line):
+ """
+ Handle a line of text without detected token.
+ """
+ if self.tp == 1:
+ self.write('<dt>')
+ if self.tp == 2:
+ self.write('<dd>')
+ self.paraCheck()
+
+ bits = line.split('\\')
+ self.write(escape(bits[0]))
+ for bit in bits[1:]:
+ if bit[:2] == 'fI':
+ self.write('<em>' + escape(bit[2:]))
+ self.state = 'em'
+ elif bit[:2] == 'fB':
+ self.write('<strong>' + escape(bit[2:]))
+ self.state = 'strong'
+ elif bit[:2] == 'fR':
+ self.write('</%s>' % self.state)
+ self.write(escape(bit[2:]))
+ self.state = 'regular'
+ elif bit[:2] == 'fU':
+ # fU doesn't really exist, but it helps us to manage underlined
+ # text.
+ self.write('<u>' + escape(bit[2:]))
+ self.state = 'u'
+ elif bit[:3] == '(co':
+ self.write('&copy;' + escape(bit[3:]))
+ else:
+ self.write(escape(bit))
+
+ if self.tp == 1:
+ self.write('</dt>')
+ self.tp = 2
+ elif self.tp == 2:
+ self.tp = 3
+
+
+
+class ProcessingFunctionFactory:
+
+ def generate_lore(self, d, filenameGenerator=None):
+ ext = d.get('ext', '.html')
+ return lambda file,_: ManConverter().convert(open(file),
+ open(os.path.splitext(file)[0]+ext, 'w'))
+
+
+
+factory = ProcessingFunctionFactory()
+
+
+if __name__ == '__main__':
+ import sys
+ mc = ManConverter().convert(open(sys.argv[1]), sys.stdout)
diff --git a/vendor/Twisted-10.0.0/twisted/lore/numberer.py b/vendor/Twisted-10.0.0/twisted/lore/numberer.py
new file mode 100644
index 0000000000..aa7582acea
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/numberer.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+def reset():
+ resetFilenum()
+ setNumberSections(False)
+
+def resetFilenum():
+ setFilenum(0)
+
+def setFilenum(arg):
+ global filenum
+ filenum = arg
+
+def getFilenum():
+ global filenum
+ return filenum
+
+def getNextFilenum():
+ global filenum
+ filenum += 1
+ return filenum
+
+def setNumberSections(arg):
+ global numberSections
+ numberSections = arg
+
+def getNumberSections():
+ global numberSections
+ return numberSections
+
+reset()
diff --git a/vendor/Twisted-10.0.0/twisted/lore/process.py b/vendor/Twisted-10.0.0/twisted/lore/process.py
new file mode 100644
index 0000000000..f1b8fb2008
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/process.py
@@ -0,0 +1,120 @@
+# -*- test-case-name: twisted.lore.test.test_lore -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+import sys, os
+import tree #todo: get rid of this later
+import indexer
+
+class NoProcessorError(Exception):
+ pass
+
+class ProcessingFailure(Exception):
+ pass
+
+cols = 79
+
+def dircount(d):
+ return len([1 for el in d.split("/") if el != '.'])
+
+
+class Walker:
+
+ def __init__(self, df, fext, linkrel):
+ self.df = df
+ self.linkrel = linkrel
+ self.fext = fext
+ self.walked = []
+ self.failures = []
+
+ def walkdir(self, topdir, prefix=''):
+ self.basecount = dircount(topdir)
+ os.path.walk(topdir, self.walk, prefix)
+
+ def walk(self, prefix, d, names):
+ linkrel = prefix + '../' * (dircount(d) - self.basecount)
+ for name in names:
+ fullpath = os.path.join(d, name)
+ fext = os.path.splitext(name)[1]
+ if fext == self.fext:
+ self.walked.append((linkrel, fullpath))
+
+ def generate(self):
+ i = 0
+ indexer.clearEntries()
+ tree.filenum = 0
+ for linkrel, fullpath in self.walked:
+ linkrel = self.linkrel + linkrel
+ i += 1
+ fname = os.path.splitext(fullpath)[0]
+ self.percentdone((float(i) / len(self.walked)), fname)
+ try:
+ self.df(fullpath, linkrel)
+ except ProcessingFailure, e:
+ self.failures.append((fullpath, e))
+ indexer.generateIndex()
+ self.percentdone(1., None)
+
+ def percentdone(self, percent, fname):
+ # override for neater progress bars
+ proglen = 40
+ hashes = int(percent * proglen)
+ spaces = proglen - hashes
+ progstat = "[%s%s] (%s)" %('#' * hashes, ' ' * spaces,fname or "*Done*")
+ progstat += (cols - len(progstat)) * ' '
+ progstat += '\r'
+ sys.stdout.write(progstat)
+ sys.stdout.flush()
+ if fname is None:
+ print
+
+class PlainReportingWalker(Walker):
+
+ def percentdone(self, percent, fname):
+ if fname:
+ print fname
+
+class NullReportingWalker(Walker):
+
+ def percentdone(self, percent, fname):
+ pass
+
+def parallelGenerator(originalFileName, outputExtension):
+ return os.path.splitext(originalFileName)[0]+outputExtension
+
+def fooAddingGenerator(originalFileName, outputExtension):
+ return os.path.splitext(originalFileName)[0]+"foo"+outputExtension
+
+def outputdirGenerator(originalFileName, outputExtension, inputdir, outputdir):
+ originalFileName = os.path.abspath(originalFileName)
+ abs_inputdir = os.path.abspath(inputdir)
+ if os.path.commonprefix((originalFileName, abs_inputdir)) != abs_inputdir:
+ raise ValueError("Original file name '" + originalFileName +
+ "' not under input directory '" + abs_inputdir + "'")
+
+ adjustedPath = os.path.join(outputdir, os.path.basename(originalFileName))
+ return tree.getOutputFileName(adjustedPath, outputExtension)
+
+def getFilenameGenerator(config, outputExt):
+ if config.get('outputdir'):
+ return (lambda originalFileName, outputExtension:
+ outputdirGenerator(originalFileName, outputExtension,
+ os.path.abspath(config.get('inputdir')),
+ os.path.abspath(config.get('outputdir'))))
+ else:
+ return tree.getOutputFileName
+
+def getProcessor(module, output, config):
+ try:
+ m = getattr(module.factory, 'generate_'+output)
+ except AttributeError:
+ raise NoProcessorError("cannot generate "+output+" output")
+
+ if config.get('ext'):
+ ext = config['ext']
+ else:
+ from default import htmlDefault
+ ext = htmlDefault['ext']
+
+ return m(config, getFilenameGenerator(config, ext))
diff --git a/vendor/Twisted-10.0.0/twisted/lore/scripts/__init__.py b/vendor/Twisted-10.0.0/twisted/lore/scripts/__init__.py
new file mode 100644
index 0000000000..265270ef52
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/scripts/__init__.py
@@ -0,0 +1 @@
+"lore scripts"
diff --git a/vendor/Twisted-10.0.0/twisted/lore/scripts/lore.py b/vendor/Twisted-10.0.0/twisted/lore/scripts/lore.py
new file mode 100755
index 0000000000..1b2b47ca0d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/scripts/lore.py
@@ -0,0 +1,159 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+
+from zope.interface import Interface, Attribute
+
+from twisted.lore import process, indexer, numberer, htmlbook
+
+from twisted.python import usage, reflect
+from twisted import plugin as plugin
+
+class IProcessor(Interface):
+ """
+ """
+
+ name = Attribute("The user-facing name of this processor")
+
+ moduleName = Attribute(
+ "The fully qualified Python name of the object defining "
+ "this processor. This object (typically a module) should "
+ "have a C{factory} attribute with C{generate_<output>} methods.")
+
+
+class Options(usage.Options):
+
+ longdesc = "lore converts documentation formats."
+
+ optFlags = [["plain", 'p', "Report filenames without progress bar"],
+ ["null", 'n', "Do not report filenames"],
+ ["number", 'N', "Add chapter/section numbers to section headings"],
+]
+
+ optParameters = [
+ ["input", "i", 'lore'],
+ ["inputext", "e", ".xhtml", "The extension that your Lore input files have"],
+ ["docsdir", "d", None],
+ ["linkrel", "l", ''],
+ ["output", "o", 'html'],
+ ["index", "x", None, "The base filename you want to give your index file"],
+ ["book", "b", None, "The book file to generate a book from"],
+ ["prefixurl", None, "", "The prefix to stick on to relative links; only useful when processing directories"],
+ ]
+
+ #zsh_altArgDescr = {"foo":"use this description for foo instead"}
+ #zsh_multiUse = ["foo", "bar"]
+ #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
+ #zsh_actions = {"foo":'_files -g "*.foo"', "bar":"(one two three)"}
+ #zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
+ zsh_extras = ["*:files:_files"]
+
+ def __init__(self, *args, **kw):
+ usage.Options.__init__(self, *args, **kw)
+ self.config = {}
+
+ def opt_config(self, s):
+ if '=' in s:
+ k, v = s.split('=', 1)
+ self.config[k] = v
+ else:
+ self.config[s] = 1
+
+ def parseArgs(self, *files):
+ self['files'] = files
+
+
+def getProcessor(input, output, config):
+ plugins = plugin.getPlugins(IProcessor)
+ for plug in plugins:
+ if plug.name == input:
+ module = reflect.namedModule(plug.moduleName)
+ break
+ else:
+ # try treating it as a module name
+ try:
+ module = reflect.namedModule(input)
+ except ImportError:
+ print '%s: no such input: %s' % (sys.argv[0], input)
+ return
+ try:
+ return process.getProcessor(module, output, config)
+ except process.NoProcessorError, e:
+ print "%s: %s" % (sys.argv[0], e)
+
+
+def getWalker(df, opt):
+ klass = process.Walker
+ if opt['plain']:
+ klass = process.PlainReportingWalker
+ if opt['null']:
+ klass = process.NullReportingWalker
+ return klass(df, opt['inputext'], opt['linkrel'])
+
+
+def runGivenOptions(opt):
+ """Do everything but parse the options; useful for testing.
+ Returns a descriptive string if there's an error."""
+
+ book = None
+ if opt['book']:
+ book = htmlbook.Book(opt['book'])
+
+ df = getProcessor(opt['input'], opt['output'], opt.config)
+ if not df:
+ return 'getProcessor() failed'
+
+ walker = getWalker(df, opt)
+
+ if opt['files']:
+ for filename in opt['files']:
+ walker.walked.append(('', filename))
+ elif book:
+ for filename in book.getFiles():
+ walker.walked.append(('', filename))
+ else:
+ walker.walkdir(opt['docsdir'] or '.', opt['prefixurl'])
+
+ if opt['index']:
+ indexFilename = opt['index']
+ elif book:
+ indexFilename = book.getIndexFilename()
+ else:
+ indexFilename = None
+
+ if indexFilename:
+ indexer.setIndexFilename("%s.%s" % (indexFilename, opt['output']))
+ else:
+ indexer.setIndexFilename(None)
+
+ ## TODO: get numberSections from book, if any
+ numberer.setNumberSections(opt['number'])
+
+ walker.generate()
+
+ if walker.failures:
+ for (file, errors) in walker.failures:
+ for error in errors:
+ print "%s:%s" % (file, error)
+ return 'Walker failures'
+
+
+def run():
+ opt = Options()
+ try:
+ opt.parseOptions()
+ except usage.UsageError, errortext:
+ print '%s: %s' % (sys.argv[0], errortext)
+ print '%s: Try --help for usage details.' % sys.argv[0]
+ sys.exit(1)
+
+ result = runGivenOptions(opt)
+ if result:
+ print result
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ run()
+
diff --git a/vendor/Twisted-10.0.0/twisted/lore/slides.py b/vendor/Twisted-10.0.0/twisted/lore/slides.py
new file mode 100644
index 0000000000..92af4b6d94
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/slides.py
@@ -0,0 +1,359 @@
+# -*- test-case-name: twisted.lore.test.test_slides -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Rudimentary slide support for Lore.
+
+TODO:
+ - Complete mgp output target
+ - syntax highlighting
+ - saner font handling
+ - probably lots more
+ - Add HTML output targets
+ - one slides per page (with navigation links)
+ - all in one page
+
+Example input file::
+ <html>
+
+ <head><title>Title of talk</title></head>
+
+ <body>
+ <h1>Title of talk</h1>
+
+ <h2>First Slide</h2>
+
+ <ul>
+ <li>Bullet point</li>
+ <li>Look ma, I'm <strong>bold</strong>!</li>
+ <li>... etc ...</li>
+ </ul>
+
+
+ <h2>Second Slide</h2>
+
+ <pre class="python">
+ # Sample code sample.
+ print "Hello, World!"
+ </pre>
+
+ </body>
+
+ </html>
+"""
+
+from xml.dom import minidom as dom
+import os.path, re
+from cStringIO import StringIO
+
+from twisted.lore import default
+from twisted.web import domhelpers
+from twisted.python import text
+# These should be factored out
+from twisted.lore.latex import BaseLatexSpitter, LatexSpitter, processFile
+from twisted.lore.latex import getLatexText, HeadingLatexSpitter
+from twisted.lore.tree import getHeaders
+from twisted.lore.tree import removeH1, fixAPI, fontifyPython
+from twisted.lore.tree import addPyListings, addHTMLListings, setTitle
+
+hacked_entities = { 'amp': ' &', 'gt': ' >', 'lt': ' <', 'quot': ' "',
+ 'copy': ' (c)'}
+
+entities = { 'amp': '&', 'gt': '>', 'lt': '<', 'quot': '"',
+ 'copy': '(c)'}
+
+class MagicpointOutput(BaseLatexSpitter):
+ bulletDepth = 0
+
+ def writeNodeData(self, node):
+ buf = StringIO()
+ getLatexText(node, buf.write, entities=hacked_entities)
+ data = buf.getvalue().rstrip().replace('\n', ' ')
+ self.writer(re.sub(' +', ' ', data))
+
+ def visitNode_title(self, node):
+ self.title = domhelpers.getNodeText(node)
+
+ def visitNode_body(self, node):
+ # Adapted from tree.generateToC
+ self.fontStack = [('standard', None)]
+
+ # Title slide
+ self.writer(self.start_h2)
+ self.writer(self.title)
+ self.writer(self.end_h2)
+
+ self.writer('%center\n\n\n\n\n')
+ for authorNode in domhelpers.findElementsWithAttribute(node, 'class', 'author'):
+ getLatexText(authorNode, self.writer, entities=entities)
+ self.writer('\n')
+
+ # Table of contents
+ self.writer(self.start_h2)
+ self.writer(self.title)
+ self.writer(self.end_h2)
+
+ for element in getHeaders(node):
+ level = int(element.tagName[1])-1
+ self.writer(level * '\t')
+ self.writer(domhelpers.getNodeText(element))
+ self.writer('\n')
+
+ self.visitNodeDefault(node)
+
+ def visitNode_div_author(self, node):
+ # Skip this node; it's already been used by visitNode_body
+ pass
+
+ def visitNode_div_pause(self, node):
+ self.writer('%pause\n')
+
+ def visitNode_pre(self, node):
+ # TODO: Syntax highlighting
+ buf = StringIO()
+ getLatexText(node, buf.write, entities=entities)
+ data = buf.getvalue()
+ data = text.removeLeadingTrailingBlanks(data)
+ lines = data.split('\n')
+ self.fontStack.append(('typewriter', 4))
+ self.writer('%' + self.fontName() + '\n')
+ for line in lines:
+ self.writer(' ' + line + '\n')
+ del self.fontStack[-1]
+ self.writer('%' + self.fontName() + '\n')
+
+ def visitNode_ul(self, node):
+ if self.bulletDepth > 0:
+ self.writer(self._start_ul)
+ self.bulletDepth += 1
+ self.start_li = self._start_li * self.bulletDepth
+ self.visitNodeDefault(node)
+ self.bulletDepth -= 1
+ self.start_li = self._start_li * self.bulletDepth
+
+ def visitNode_strong(self, node):
+ self.doFont(node, 'bold')
+
+ def visitNode_em(self, node):
+ self.doFont(node, 'italic')
+
+ def visitNode_code(self, node):
+ self.doFont(node, 'typewriter')
+
+ def doFont(self, node, style):
+ self.fontStack.append((style, None))
+ self.writer(' \n%cont, ' + self.fontName() + '\n')
+ self.visitNodeDefault(node)
+ del self.fontStack[-1]
+ self.writer('\n%cont, ' + self.fontName() + '\n')
+
+ def fontName(self):
+ names = [x[0] for x in self.fontStack]
+ if 'typewriter' in names:
+ name = 'typewriter'
+ else:
+ name = ''
+
+ if 'bold' in names:
+ name += 'bold'
+ if 'italic' in names:
+ name += 'italic'
+
+ if name == '':
+ name = 'standard'
+
+ sizes = [x[1] for x in self.fontStack]
+ sizes.reverse()
+ for size in sizes:
+ if size:
+ return 'font "%s", size %d' % (name, size)
+
+ return 'font "%s"' % name
+
+ start_h2 = "%page\n\n"
+ end_h2 = '\n\n\n'
+
+ _start_ul = '\n'
+
+ _start_li = "\t"
+ end_li = "\n"
+
+
+def convertFile(filename, outputter, template, ext=".mgp"):
+ fout = open(os.path.splitext(filename)[0]+ext, 'w')
+ fout.write(open(template).read())
+ spitter = outputter(fout.write, os.path.dirname(filename), filename)
+ fin = open(filename)
+ processFile(spitter, fin)
+ fin.close()
+ fout.close()
+
+
+# HTML DOM tree stuff
+
+def splitIntoSlides(document):
+ body = domhelpers.findNodesNamed(document, 'body')[0]
+ slides = []
+ slide = []
+ title = '(unset)'
+ for child in body.childNodes:
+ if isinstance(child, dom.Element) and child.tagName == 'h2':
+ if slide:
+ slides.append((title, slide))
+ slide = []
+ title = domhelpers.getNodeText(child)
+ else:
+ slide.append(child)
+ slides.append((title, slide))
+ return slides
+
+def insertPrevNextLinks(slides, filename, ext):
+ for slide in slides:
+ for name, offset in (("previous", -1), ("next", +1)):
+ if (slide.pos > 0 and name == "previous") or \
+ (slide.pos < len(slides)-1 and name == "next"):
+ for node in domhelpers.findElementsWithAttribute(slide.dom, "class", name):
+ if node.tagName == 'a':
+ node.setAttribute('href', '%s-%d%s'
+ % (filename[0], slide.pos+offset, ext))
+ else:
+ text = dom.Text()
+ text.data = slides[slide.pos+offset].title
+ node.appendChild(text)
+ else:
+ for node in domhelpers.findElementsWithAttribute(slide.dom, "class", name):
+ pos = 0
+ for child in node.parentNode.childNodes:
+ if child is node:
+ del node.parentNode.childNodes[pos]
+ break
+ pos += 1
+
+
+class HTMLSlide:
+ def __init__(self, dom, title, pos):
+ self.dom = dom
+ self.title = title
+ self.pos = pos
+
+
+def munge(document, template, linkrel, d, fullpath, ext, url, config):
+ # FIXME: This has *way* to much duplicated crap in common with tree.munge
+ #fixRelativeLinks(template, linkrel)
+ removeH1(document)
+ fixAPI(document, url)
+ fontifyPython(document)
+ addPyListings(document, d)
+ addHTMLListings(document, d)
+ #fixLinks(document, ext)
+ #putInToC(template, generateToC(document))
+ template = template.cloneNode(1)
+
+ # Insert the slides into the template
+ slides = []
+ pos = 0
+ for title, slide in splitIntoSlides(document):
+ t = template.cloneNode(1)
+ text = dom.Text()
+ text.data = title
+ setTitle(t, [text])
+ tmplbody = domhelpers.findElementsWithAttribute(t, "class", "body")[0]
+ tmplbody.childNodes = slide
+ tmplbody.setAttribute("class", "content")
+ # FIXME: Next/Prev links
+ # FIXME: Perhaps there should be a "Template" class? (setTitle/setBody
+ # could be methods...)
+ slides.append(HTMLSlide(t, title, pos))
+ pos += 1
+
+ insertPrevNextLinks(slides, os.path.splitext(os.path.basename(fullpath)), ext)
+
+ return slides
+
+from tree import makeSureDirectoryExists
+
+def getOutputFileName(originalFileName, outputExtension, index):
+ return os.path.splitext(originalFileName)[0]+'-'+str(index) + outputExtension
+
+def doFile(filename, linkrel, ext, url, templ, options={}, outfileGenerator=getOutputFileName):
+ from tree import parseFileAndReport
+ doc = parseFileAndReport(filename)
+ slides = munge(doc, templ, linkrel, os.path.dirname(filename), filename, ext, url, options)
+ for slide, index in zip(slides, range(len(slides))):
+ newFilename = outfileGenerator(filename, ext, index)
+ makeSureDirectoryExists(newFilename)
+ f = open(newFilename, 'wb')
+ slide.dom.writexml(f)
+ f.close()
+
+# Prosper output
+
+class ProsperSlides(LatexSpitter):
+ firstSlide = 1
+ start_html = '\\documentclass[ps]{prosper}\n'
+ start_body = '\\begin{document}\n'
+ start_div_author = '\\author{'
+ end_div_author = '}'
+
+ def visitNode_h2(self, node):
+ if self.firstSlide:
+ self.firstSlide = 0
+ self.end_body = '\\end{slide}\n\n' + self.end_body
+ else:
+ self.writer('\\end{slide}\n\n')
+ self.writer('\\begin{slide}{')
+ spitter = HeadingLatexSpitter(self.writer, self.currDir, self.filename)
+ spitter.visitNodeDefault(node)
+ self.writer('}')
+
+ def _write_img(self, target):
+ self.writer('\\begin{center}\\includegraphics[%%\nwidth=1.0\n\\textwidth,'
+ 'height=1.0\\textheight,\nkeepaspectratio]{%s}\\end{center}\n' % target)
+
+
+class PagebreakLatex(LatexSpitter):
+
+ everyN = 1
+ currentN = 0
+ seenH2 = 0
+
+ start_html = LatexSpitter.start_html+"\\date{}\n"
+ start_body = '\\begin{document}\n\n'
+
+ def visitNode_h2(self, node):
+ if not self.seenH2:
+ self.currentN = 0
+ self.seenH2 = 1
+ else:
+ self.currentN += 1
+ self.currentN %= self.everyN
+ if not self.currentN:
+ self.writer('\\clearpage\n')
+ level = (int(node.tagName[1])-2)+self.baseLevel
+ self.writer('\n\n\\'+level*'sub'+'section*{')
+ spitter = HeadingLatexSpitter(self.writer, self.currDir, self.filename)
+ spitter.visitNodeDefault(node)
+ self.writer('}\n')
+
+class TwoPagebreakLatex(PagebreakLatex):
+
+ everyN = 2
+
+
+class SlidesProcessingFunctionFactory(default.ProcessingFunctionFactory):
+
+ latexSpitters = default.ProcessingFunctionFactory.latexSpitters.copy()
+ latexSpitters['prosper'] = ProsperSlides
+ latexSpitters['page'] = PagebreakLatex
+ latexSpitters['twopage'] = TwoPagebreakLatex
+
+ def getDoFile(self):
+ return doFile
+
+ def generate_mgp(self, d, fileNameGenerator=None):
+ template = d.get('template', 'template.mgp')
+ df = lambda file, linkrel: convertFile(file, MagicpointOutput, template, ext=".mgp")
+ return df
+
+factory=SlidesProcessingFunctionFactory()
diff --git a/vendor/Twisted-10.0.0/twisted/lore/template.mgp b/vendor/Twisted-10.0.0/twisted/lore/template.mgp
new file mode 100644
index 0000000000..79fc4d1cde
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/template.mgp
@@ -0,0 +1,24 @@
+%%deffont "standard" tfont "Arial.ttf"
+%%deffont "bold" tfont "Arial_Bold.ttf"
+%%deffont "italic" tfont "Arial_Italic.ttf"
+%%deffont "bolditalic" tfont "Arial_Bold_Italic.ttf"
+%%deffont "typewriter" tfont "Courier_New.ttf"
+%deffont "standard" xfont "Arial-medium-r"
+%deffont "bold" xfont "Arial-bold-r"
+%deffont "italic" xfont "Arial-medium-i"
+%deffont "bolditalic" xfont "Arial-bold-i"
+%deffont "typewriter" xfont "andale mono"
+%%deffont "standard" tfont "tahoma.ttf"
+%%deffont "thick" tfont "tahomabd.ttf"
+#%deffont "typewriter" tfont "Andale_Mono.ttf"
+%default 1 area 90 90, leftfill, size 2, fore "white", back "black", font "bold"
+%default 2 size 7, vgap 10, prefix " ", center
+%default 3 size 2, bar "gray70", vgap 10, leftfill
+%default 4 size 1, fore "white", vgap 30, prefix " ", font "standard"
+%default 5 size 5
+%%tab 1 size 5, vgap 40, prefix " ", icon box "green" 50
+%%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
+%%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
+%tab 1 size 5, vgap 50, prefix " ", icon box "green" 50
+%tab 2 size 5, vgap 50, prefix " ", icon arc "yellow" 50
+%tab 3 size 4, vgap 50, prefix " ", icon delta3 "white" 40
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/__init__.py b/vendor/Twisted-10.0.0/twisted/lore/test/__init__.py
new file mode 100644
index 0000000000..1641a43141
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/__init__.py
@@ -0,0 +1 @@
+"lore tests"
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_out.html b/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_out.html
new file mode 100644
index 0000000000..0490f0c1e4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_out.html
@@ -0,0 +1,2 @@
+language of programming: <a href="lore_index_test.html#index02">1.3</a><br />
+programming language: <a href="lore_index_test.html#index01">1.2</a><br />
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_out_multiple.html b/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_out_multiple.html
new file mode 100644
index 0000000000..fa0235ef81
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_out_multiple.html
@@ -0,0 +1,5 @@
+aahz: <a href="lore_index_test2.html#index03">link</a>
+aahz2: <a href="lore_index_test2.html#index02">link</a>
+language of programming: <a href="lore_index_test.html#index02">link</a>
+<a href="lore_index_test2.html#index01">link</a>
+programming language: <a href="lore_index_test.html#index01">link</a>
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_unnumbered_out.html b/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_unnumbered_out.html
new file mode 100644
index 0000000000..fa724d7c44
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_file_unnumbered_out.html
@@ -0,0 +1,2 @@
+language of programming: <a href="lore_index_test.html#index02">link</a><br />
+programming language: <a href="lore_index_test.html#index01">link</a><br />
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_test.xhtml b/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_test.xhtml
new file mode 100644
index 0000000000..570b4114f6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_test.xhtml
@@ -0,0 +1,21 @@
+<html>
+<head>
+ <title>The way of the program</title>
+</head>
+
+<body>
+
+<h1>The way of the program</h1>
+
+<p>The first paragraph.</p>
+
+
+<h2>The Python programming language</h2>
+<span class="index" value="programming language" />
+<span class="index" value="language of programming" />
+
+<p>The second paragraph.</p>
+
+
+</body>
+</html>
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_test2.xhtml b/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_test2.xhtml
new file mode 100644
index 0000000000..4214e48546
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/lore_index_test2.xhtml
@@ -0,0 +1,22 @@
+<html>
+<head>
+ <title>The second page to index</title>
+</head>
+
+<body>
+
+<h1>The second page to index</h1>
+
+<p>The first paragraph of the second page.</p>
+
+
+<h2>The Jython programming language</h2>
+<span class="index" value="language of programming" />
+<span class="index" value="aahz2" />
+<span class="index" value="aahz" />
+
+<p>The second paragraph of the second page.</p>
+
+
+</body>
+</html>
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/lore_numbering_test_out.html b/vendor/Twisted-10.0.0/twisted/lore/test/lore_numbering_test_out.html
new file mode 100644
index 0000000000..15bb2b78e5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/lore_numbering_test_out.html
@@ -0,0 +1,2 @@
+<?xml version="1.0"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><title>Twisted Documentation: 1. The way of the program</title><link href="resources/stylesheet.css" type="text/css" rel="stylesheet" /></head><body bgcolor="white"><h1 class="title">1. The way of the program</h1><div class="toc"><ol><li><a href="#auto0">The Python programming language</a></li><li><a href="#auto1">Section The Second</a></li><li><a href="#auto2">Section The Third</a></li></ol></div><div class="content"><span></span><p>The first paragraph.</p><h2>1.1 The Python programming language<a name="auto0"></a></h2><a name="index01"></a><a name="index02"></a><p>The second paragraph.</p><h2>1.2 Section The Second<a name="auto1"></a></h2><p>The second section.</p><h2>1.3 Section The Third<a name="auto2"></a></h2><p>The Third section.</p></div><p><a href="theIndexFile.html">Index</a></p></body></html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/lore_numbering_test_out2.html b/vendor/Twisted-10.0.0/twisted/lore/test/lore_numbering_test_out2.html
new file mode 100644
index 0000000000..33aff77ad8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/lore_numbering_test_out2.html
@@ -0,0 +1,2 @@
+<?xml version="1.0"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><title>Twisted Documentation: 2. The second page to index</title><link href="resources/stylesheet.css" type="text/css" rel="stylesheet" /></head><body bgcolor="white"><h1 class="title">2. The second page to index</h1><div class="toc"><ol><li><a href="#auto0">The Jython programming language</a></li><li><a href="#auto1">Second Section</a></li><li><a href="#auto2">Third Section of Second Page</a></li></ol></div><div class="content"><span></span><p>The first paragraph of the second page.</p><h2>2.1 The Jython programming language<a name="auto0"></a></h2><a name="index01"></a><a name="index02"></a><a name="index03"></a><p>The second paragraph of the second page.</p><h2>2.2 Second Section<a name="auto1"></a></h2><p>The second section of the second page.</p><h2>2.3 Third Section of Second Page<a name="auto2"></a></h2><p>The Third section.</p></div><p><a href="theIndexFile.html">Index</a></p></body></html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/simple.html b/vendor/Twisted-10.0.0/twisted/lore/test/simple.html
new file mode 100644
index 0000000000..8d77609072
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/simple.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>My Test Lore Input</title>
+</head>
+<body>
+<h1>My Test Lore Input</h1>
+<p>A Body.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/simple3.html b/vendor/Twisted-10.0.0/twisted/lore/test/simple3.html
new file mode 100644
index 0000000000..8d77609072
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/simple3.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>My Test Lore Input</title>
+</head>
+<body>
+<h1>My Test Lore Input</h1>
+<p>A Body.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/simple4.html b/vendor/Twisted-10.0.0/twisted/lore/test/simple4.html
new file mode 100644
index 0000000000..8d77609072
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/simple4.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>My Test Lore Input</title>
+</head>
+<body>
+<h1>My Test Lore Input</h1>
+<p>A Body.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/template.tpl b/vendor/Twisted-10.0.0/twisted/lore/test/template.tpl
new file mode 100644
index 0000000000..195f6ca399
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/template.tpl
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head><title>Twisted Documentation: </title></head>
+ <body bgcolor="white">
+ <h1 class="title" />
+ <div class="body" />
+ <span class="index-link">Index</span>
+ </body>
+</html>
+
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/test_docbook.py b/vendor/Twisted-10.0.0/twisted/lore/test/test_docbook.py
new file mode 100644
index 0000000000..aadec1200f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/test_docbook.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.lore.docbook}.
+"""
+
+from xml.dom.minidom import Element, Text
+
+from twisted.trial.unittest import TestCase
+from twisted.lore.docbook import DocbookSpitter
+
+
+class DocbookSpitterTests(TestCase):
+ """
+ Tests for L{twisted.lore.docbook.DocbookSpitter}.
+ """
+ def test_li(self):
+ """
+ L{DocbookSpitter} wraps any non-I{p} elements found intside any I{li}
+ elements with I{p} elements.
+ """
+ output = []
+ spitter = DocbookSpitter(output.append)
+
+ li = Element('li')
+ li.appendChild(Element('p'))
+ text = Text()
+ text.data = 'foo bar'
+ li.appendChild(text)
+
+ spitter.visitNode(li)
+ self.assertEqual(
+ ''.join(output),
+ '<listitem><para></para><para>foo bar</para></listitem>')
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/test_latex.py b/vendor/Twisted-10.0.0/twisted/lore/test/test_latex.py
new file mode 100644
index 0000000000..3ac9a51666
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/test_latex.py
@@ -0,0 +1,146 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.lore.latex}.
+"""
+
+import os.path
+from xml.dom.minidom import Comment, Element, Text
+
+from twisted.python.filepath import FilePath
+from twisted.trial.unittest import TestCase
+from twisted.lore.latex import LatexSpitter, getLatexText
+
+
+class LatexHelperTests(TestCase):
+ """
+ Tests for free functions in L{twisted.lore.latex}.
+ """
+ def test_getLatexText(self):
+ """
+ L{getLatexText} calls the writer function with all of the text at or
+ beneath the given node. Non-ASCII characters are encoded using
+ UTF-8.
+ """
+ node = Element('foo')
+ text = Text()
+ text.data = u"foo \N{SNOWMAN}"
+ node.appendChild(text)
+ result = []
+ getLatexText(node, result.append)
+ self.assertEqual(result, [u"foo \N{SNOWMAN}".encode('utf-8')])
+
+
+
+class LatexSpitterTests(TestCase):
+ """
+ Tests for L{LatexSpitter}.
+ """
+ def setUp(self):
+ self.filename = self.mktemp()
+ self.output = []
+ self.spitter = LatexSpitter(self.output.append, filename=self.filename)
+
+
+ def test_head(self):
+ """
+ L{LatexSpitter.visitNode} writes out author information for each
+ I{link} element with a I{rel} attribute set to I{author}.
+ """
+ head = Element('head')
+ first = Element('link')
+ first.setAttribute('rel', 'author')
+ first.setAttribute('title', 'alice')
+ second = Element('link')
+ second.setAttribute('rel', 'author')
+ second.setAttribute('href', 'http://example.com/bob')
+ third = Element('link')
+ third.setAttribute('rel', 'author')
+ third.setAttribute('href', 'mailto:carol@example.com')
+ head.appendChild(first)
+ head.appendChild(second)
+ head.appendChild(third)
+
+ self.spitter.visitNode(head)
+
+ self.assertEqual(
+ ''.join(self.output),
+ '\\author{alice \\and $<$http://example.com/bob$>$ \\and $<$carol@example.com$>$}')
+
+
+ def test_skipComments(self):
+ """
+ L{LatexSpitter.visitNode} writes nothing to its output stream for
+ comments.
+ """
+ self.spitter.visitNode(Comment('foo'))
+ self.assertNotIn('foo', ''.join(self.output))
+
+
+ def test_anchorListing(self):
+ """
+ L{LatexSpitter.visitNode} emits a verbatim block when it encounters a
+ code listing (represented by an I{a} element with a I{listing} class).
+ """
+ path = FilePath(self.mktemp())
+ path.setContent('foo\nbar\n')
+ listing = Element('a')
+ listing.setAttribute('class', 'listing')
+ listing.setAttribute('href', path.path)
+ self.spitter.visitNode(listing)
+ self.assertEqual(
+ ''.join(self.output),
+ "\\begin{verbatim}\n"
+ "foo\n"
+ "bar\n"
+ "\\end{verbatim}\\parbox[b]{\\linewidth}{\\begin{center} --- "
+ "\\begin{em}temp\\end{em}\\end{center}}")
+
+
+ def test_anchorListingSkipLines(self):
+ """
+ When passed an I{a} element with a I{listing} class and an I{skipLines}
+ attribute, L{LatexSpitter.visitNode} emits a verbatim block which skips
+ the indicated number of lines from the beginning of the source listing.
+ """
+ path = FilePath(self.mktemp())
+ path.setContent('foo\nbar\n')
+ listing = Element('a')
+ listing.setAttribute('class', 'listing')
+ listing.setAttribute('skipLines', '1')
+ listing.setAttribute('href', path.path)
+ self.spitter.visitNode(listing)
+ self.assertEqual(
+ ''.join(self.output),
+ "\\begin{verbatim}\n"
+ "bar\n"
+ "\\end{verbatim}\\parbox[b]{\\linewidth}{\\begin{center} --- "
+ "\\begin{em}temp\\end{em}\\end{center}}")
+
+
+ def test_anchorRef(self):
+ """
+ L{LatexSpitter.visitNode} emits a footnote when it encounters an I{a}
+ element with an I{href} attribute with a network scheme.
+ """
+ listing = Element('a')
+ listing.setAttribute('href', 'http://example.com/foo')
+ self.spitter.visitNode(listing)
+ self.assertEqual(
+ ''.join(self.output),
+ "\\footnote{http://example.com/foo}")
+
+
+ def test_anchorName(self):
+ """
+ When passed an I{a} element with a I{name} attribute,
+ L{LatexSpitter.visitNode} emits a label.
+ """
+ listing = Element('a')
+ listing.setAttribute('name', 'foo')
+ self.spitter.visitNode(listing)
+ self.assertEqual(
+ ''.join(self.output),
+ "\\label{%sHASHfoo}" % (
+ os.path.abspath(self.filename).replace('\\', '/'),))
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/test_lint.py b/vendor/Twisted-10.0.0/twisted/lore/test/test_lint.py
new file mode 100644
index 0000000000..9fef060751
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/test_lint.py
@@ -0,0 +1,132 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.lore.lint}.
+"""
+
+import sys
+from xml.dom import minidom
+from cStringIO import StringIO
+
+from twisted.trial.unittest import TestCase
+from twisted.lore.lint import getDefaultChecker
+from twisted.lore.process import ProcessingFailure
+
+
+
+class DefaultTagCheckerTests(TestCase):
+ """
+ Tests for L{twisted.lore.lint.DefaultTagChecker}.
+ """
+ def test_quote(self):
+ """
+ If a non-comment node contains a quote (C{'"'}), the checker returned
+ by L{getDefaultChecker} reports an error and raises
+ L{ProcessingFailure}.
+ """
+ documentSource = (
+ '<html>'
+ '<head><title>foo</title></head>'
+ '<body><h1>foo</h1><div>"</div></body>'
+ '</html>')
+ document = minidom.parseString(documentSource)
+ filename = self.mktemp()
+ checker = getDefaultChecker()
+
+ output = StringIO()
+ patch = self.patch(sys, 'stdout', output)
+ self.assertRaises(ProcessingFailure, checker.check, document, filename)
+ patch.restore()
+
+ self.assertIn("contains quote", output.getvalue())
+
+
+ def test_quoteComment(self):
+ """
+ If a comment node contains a quote (C{'"'}), the checker returned by
+ L{getDefaultChecker} does not report an error.
+ """
+ documentSource = (
+ '<html>'
+ '<head><title>foo</title></head>'
+ '<body><h1>foo</h1><!-- " --></body>'
+ '</html>')
+ document = minidom.parseString(documentSource)
+ filename = self.mktemp()
+ checker = getDefaultChecker()
+
+ output = StringIO()
+ patch = self.patch(sys, 'stdout', output)
+ checker.check(document, filename)
+ patch.restore()
+
+ self.assertEqual(output.getvalue(), "")
+
+
+ def test_aNode(self):
+ """
+ If there is an <a> tag in the document, the checker returned by
+ L{getDefaultChecker} does not report an error.
+ """
+ documentSource = (
+ '<html>'
+ '<head><title>foo</title></head>'
+ '<body><h1>foo</h1><a>A link.</a></body>'
+ '</html>')
+
+ self.assertEquals(self._lintCheck(True, documentSource), "")
+
+
+ def test_textMatchesRef(self):
+ """
+ If an I{a} node has a link with a scheme as its contained text, a
+ warning is emitted if that link does not match the value of the
+ I{href} attribute.
+ """
+ documentSource = (
+ '<html>'
+ '<head><title>foo</title></head>'
+ '<body><h1>foo</h1>'
+ '<a href="http://bar/baz">%s</a>'
+ '</body>'
+ '</html>')
+ self.assertEquals(
+ self._lintCheck(True, documentSource % ("http://bar/baz",)), "")
+ self.assertIn(
+ "link text does not match href",
+ self._lintCheck(False, documentSource % ("http://bar/quux",)))
+
+
+ def _lintCheck(self, expectSuccess, source):
+ """
+ Lint the given document source and return the output.
+
+ @param expectSuccess: A flag indicating whether linting is expected
+ to succeed or not.
+
+ @param source: The document source to lint.
+
+ @return: A C{str} of the output of linting.
+ """
+ document = minidom.parseString(source)
+ filename = self.mktemp()
+ checker = getDefaultChecker()
+
+ output = StringIO()
+ patch = self.patch(sys, 'stdout', output)
+ try:
+ try:
+ checker.check(document, filename)
+ finally:
+ patch.restore()
+ except ProcessingFailure, e:
+ if expectSuccess:
+ raise
+ else:
+ if not expectSuccess:
+ self.fail(
+ "Expected checker to fail, but it did not. "
+ "Output was: %r" % (output.getvalue(),))
+
+ return output.getvalue()
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/test_lmath.py b/vendor/Twisted-10.0.0/twisted/lore/test/test_lmath.py
new file mode 100644
index 0000000000..fbdcfb64dd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/test_lmath.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.lore.lmath}.
+"""
+
+from xml.dom.minidom import Element, Text
+
+from twisted.trial.unittest import TestCase
+from twisted.python.filepath import FilePath
+
+from twisted.lore.lmath import formulaeToImages
+
+
+class FormulaeTests(TestCase):
+ """
+ Tests for L{formulaeToImages}.
+ """
+ def test_insertImages(self):
+ """
+ L{formulaeToImages} replaces any elements with the I{latexformula}
+ class with I{img} elements which refer to external images generated
+ based on the latex in the original elements.
+ """
+ parent = Element('div')
+ base = FilePath(self.mktemp())
+ base.makedirs()
+
+ macros = Element('span')
+ macros.setAttribute('class', 'latexmacros')
+ text = Text()
+ text.data = 'foo'
+ macros.appendChild(text)
+ parent.appendChild(macros)
+
+ formula = Element('span')
+ formula.setAttribute('class', 'latexformula')
+ text = Text()
+ text.data = 'bar'
+ formula.appendChild(text)
+ parent.appendChild(formula)
+
+ # Avoid actually executing the commands to generate images from the
+ # latex. It might be nice to have some assertions about what commands
+ # are executed, or perhaps even execute them and make sure an image
+ # file is created, but that is a task for another day.
+ commands = []
+ formulaeToImages(parent, base.path, _system=commands.append)
+
+ self.assertEqual(
+ parent.toxml(),
+ '<div><span><br/><img src="latexformula0.png"/><br/></span></div>')
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/test_lore.py b/vendor/Twisted-10.0.0/twisted/lore/test/test_lore.py
new file mode 100644
index 0000000000..c12577d63c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/test_lore.py
@@ -0,0 +1,1228 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# ++ single anchor added to individual output file
+# ++ two anchors added to individual output file
+# ++ anchors added to individual output files
+# ++ entry added to index
+# ++ index entry pointing to correct file and anchor
+# ++ multiple entries added to index
+# ++ multiple index entries pointing to correct files and anchors
+# __ all of above for files in deep directory structure
+#
+# ++ group index entries by indexed term
+# ++ sort index entries by indexed term
+# __ hierarchical index entries (e.g. language!programming)
+#
+# ++ add parameter for what the index filename should be
+# ++ add (default) ability to NOT index (if index not specified)
+#
+# ++ put actual index filename into INDEX link (if any) in the template
+# __ make index links RELATIVE!
+# __ make index pay attention to the outputdir!
+#
+# __ make index look nice
+#
+# ++ add section numbers to headers in lore output
+# ++ make text of index entry links be chapter numbers
+# ++ make text of index entry links be section numbers
+#
+# __ put all of our test files someplace neat and tidy
+#
+
+import os, shutil, errno, time
+from StringIO import StringIO
+from xml.dom import minidom as dom
+
+from twisted.trial import unittest
+from twisted.python.filepath import FilePath
+from twisted.python.versions import Version
+
+from twisted.lore import tree, process, indexer, numberer, htmlbook, default
+from twisted.lore.default import factory
+from twisted.lore.latex import LatexSpitter
+
+from twisted.python.util import sibpath
+
+from twisted.lore.scripts import lore
+
+from twisted.web import domhelpers
+
+def sp(originalFileName):
+ return sibpath(__file__, originalFileName)
+
+options = {"template" : sp("template.tpl"), 'baseurl': '%s', 'ext': '.xhtml' }
+d = options
+
+
+class _XMLAssertionMixin:
+ """
+ Test mixin defining a method for comparing serialized XML documents.
+ """
+ def assertXMLEqual(self, first, second):
+ """
+ Verify that two strings represent the same XML document.
+ """
+ self.assertEqual(
+ dom.parseString(first).toxml(),
+ dom.parseString(second).toxml())
+
+
+class TestFactory(unittest.TestCase, _XMLAssertionMixin):
+
+ file = sp('simple.html')
+ linkrel = ""
+
+ def assertEqualFiles1(self, exp, act):
+ if (exp == act): return True
+ fact = open(act)
+ self.assertEqualsFile(exp, fact.read())
+
+ def assertEqualFiles(self, exp, act):
+ if (exp == act): return True
+ fact = open(sp(act))
+ self.assertEqualsFile(exp, fact.read())
+
+ def assertEqualsFile(self, exp, act):
+ expected = open(sp(exp)).read()
+ self.assertEqual(expected, act)
+
+ def makeTemp(self, *filenames):
+ tmp = self.mktemp()
+ os.mkdir(tmp)
+ for filename in filenames:
+ tmpFile = os.path.join(tmp, filename)
+ shutil.copyfile(sp(filename), tmpFile)
+ return tmp
+
+########################################
+
+ def setUp(self):
+ indexer.reset()
+ numberer.reset()
+
+ def testProcessingFunctionFactory(self):
+ base = FilePath(self.mktemp())
+ base.makedirs()
+
+ simple = base.child('simple.html')
+ FilePath(__file__).sibling('simple.html').copyTo(simple)
+
+ htmlGenerator = factory.generate_html(options)
+ htmlGenerator(simple.path, self.linkrel)
+
+ self.assertXMLEqual(
+ """\
+<?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>Twisted Documentation: My Test Lore Input</title></head>
+ <body bgcolor="white">
+ <h1 class="title">My Test Lore Input</h1>
+ <div class="content">
+<span/>
+<p>A Body.</p>
+</div>
+ <a href="index.xhtml">Index</a>
+ </body>
+</html>""",
+ simple.sibling('simple.xhtml').getContent())
+
+
+ def testProcessingFunctionFactoryWithFilenameGenerator(self):
+ base = FilePath(self.mktemp())
+ base.makedirs()
+
+ def filenameGenerator(originalFileName, outputExtension):
+ name = os.path.splitext(FilePath(originalFileName).basename())[0]
+ return base.child(name + outputExtension).path
+
+ htmlGenerator = factory.generate_html(options, filenameGenerator)
+ htmlGenerator(self.file, self.linkrel)
+ self.assertXMLEqual(
+ """\
+<?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>Twisted Documentation: My Test Lore Input</title></head>
+ <body bgcolor="white">
+ <h1 class="title">My Test Lore Input</h1>
+ <div class="content">
+<span/>
+<p>A Body.</p>
+</div>
+ <a href="index.xhtml">Index</a>
+ </body>
+</html>""",
+ base.child("simple.xhtml").getContent())
+
+
+ def test_doFile(self):
+ base = FilePath(self.mktemp())
+ base.makedirs()
+
+ simple = base.child('simple.html')
+ FilePath(__file__).sibling('simple.html').copyTo(simple)
+
+ templ = dom.parse(open(d['template']))
+
+ tree.doFile(simple.path, self.linkrel, d['ext'], d['baseurl'], templ, d)
+ self.assertXMLEqual(
+ """\
+<?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>Twisted Documentation: My Test Lore Input</title></head>
+ <body bgcolor="white">
+ <h1 class="title">My Test Lore Input</h1>
+ <div class="content">
+<span/>
+<p>A Body.</p>
+</div>
+ <a href="index.xhtml">Index</a>
+ </body>
+</html>""",
+ base.child("simple.xhtml").getContent())
+
+
+ def test_doFile_withFilenameGenerator(self):
+ base = FilePath(self.mktemp())
+ base.makedirs()
+
+ def filenameGenerator(originalFileName, outputExtension):
+ name = os.path.splitext(FilePath(originalFileName).basename())[0]
+ return base.child(name + outputExtension).path
+
+ templ = dom.parse(open(d['template']))
+ tree.doFile(self.file, self.linkrel, d['ext'], d['baseurl'], templ, d, filenameGenerator)
+
+ self.assertXMLEqual(
+ """\
+<?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>Twisted Documentation: My Test Lore Input</title></head>
+ <body bgcolor="white">
+ <h1 class="title">My Test Lore Input</h1>
+ <div class="content">
+<span/>
+<p>A Body.</p>
+</div>
+ <a href="index.xhtml">Index</a>
+ </body>
+</html>""",
+ base.child("simple.xhtml").getContent())
+
+
+ def test_munge(self):
+ indexer.setIndexFilename("lore_index_file.html")
+ doc = dom.parse(open(self.file))
+ node = dom.parse(open(d['template']))
+ tree.munge(doc, node, self.linkrel,
+ os.path.dirname(self.file),
+ self.file,
+ d['ext'], d['baseurl'], d)
+
+ self.assertXMLEqual(
+ """\
+<?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>Twisted Documentation: My Test Lore Input</title></head>
+ <body bgcolor="white">
+ <h1 class="title">My Test Lore Input</h1>
+ <div class="content">
+<span/>
+<p>A Body.</p>
+</div>
+ <a href="lore_index_file.html">Index</a>
+ </body>
+</html>""",
+ node.toxml())
+
+
+ def test_mungeAuthors(self):
+ """
+ If there is a node with a I{class} attribute set to C{"authors"},
+ L{tree.munge} adds anchors as children to it, takeing the necessary
+ information from any I{link} nodes in the I{head} with their I{rel}
+ attribute set to C{"author"}.
+ """
+ document = dom.parseString(
+ """\
+<html>
+ <head>
+ <title>munge authors</title>
+ <link rel="author" title="foo" href="bar"/>
+ <link rel="author" title="baz" href="quux"/>
+ <link rel="author" title="foobar" href="barbaz"/>
+ </head>
+ <body>
+ <h1>munge authors</h1>
+ </body>
+</html>""")
+ template = dom.parseString(
+ """\
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+ <head>
+ <title />
+ </head>
+
+ <body>
+ <div class="body" />
+ <div class="authors" />
+ </body>
+</html>
+""")
+ tree.munge(
+ document, template, self.linkrel, os.path.dirname(self.file),
+ self.file, d['ext'], d['baseurl'], d)
+
+ self.assertXMLEqual(
+ template.toxml(),
+ """\
+<?xml version="1.0" ?><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>munge authors</title>
+ <link href="bar" rel="author" title="foo"/><link href="quux" rel="author" title="baz"/><link href="barbaz" rel="author" title="foobar"/></head>
+
+ <body>
+ <div class="content">
+ <span/>
+ </div>
+ <div class="authors"><span><a href="bar">foo</a>, <a href="quux">baz</a>, and <a href="barbaz">foobar</a></span></div>
+ </body>
+</html>""")
+
+
+ def test_getProcessor(self):
+
+ base = FilePath(self.mktemp())
+ base.makedirs()
+ input = base.child("simple3.html")
+ FilePath(__file__).sibling("simple3.html").copyTo(input)
+
+ options = { 'template': sp('template.tpl'), 'ext': '.xhtml', 'baseurl': 'burl',
+ 'filenameMapping': None }
+ p = process.getProcessor(default, "html", options)
+ p(input.path, self.linkrel)
+ self.assertXMLEqual(
+ """\
+<?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>Twisted Documentation: My Test Lore Input</title></head>
+ <body bgcolor="white">
+ <h1 class="title">My Test Lore Input</h1>
+ <div class="content">
+<span/>
+<p>A Body.</p>
+</div>
+ <a href="index.xhtml">Index</a>
+ </body>
+</html>""",
+ base.child("simple3.xhtml").getContent())
+
+ def test_outputdirGenerator(self):
+ normp = os.path.normpath; join = os.path.join
+ inputdir = normp(join("/", 'home', 'joe'))
+ outputdir = normp(join("/", 'away', 'joseph'))
+ actual = process.outputdirGenerator(join("/", 'home', 'joe', "myfile.html"),
+ '.xhtml', inputdir, outputdir)
+ expected = normp(join("/", 'away', 'joseph', 'myfile.xhtml'))
+ self.assertEquals(expected, actual)
+
+ def test_outputdirGeneratorBadInput(self):
+ options = {'outputdir': '/away/joseph/', 'inputdir': '/home/joe/' }
+ self.assertRaises(ValueError, process.outputdirGenerator, '.html', '.xhtml', **options)
+
+ def test_makeSureDirectoryExists(self):
+ dirname = os.path.join("tmp", 'nonexistentdir')
+ if os.path.exists(dirname):
+ os.rmdir(dirname)
+ self.failIf(os.path.exists(dirname), "Hey: someone already created the dir")
+ filename = os.path.join(dirname, 'newfile')
+ tree.makeSureDirectoryExists(filename)
+ self.failUnless(os.path.exists(dirname), 'should have created dir')
+ os.rmdir(dirname)
+
+
+ def test_indexAnchorsAdded(self):
+ indexer.setIndexFilename('theIndexFile.html')
+ # generate the output file
+ templ = dom.parse(open(d['template']))
+ tmp = self.makeTemp('lore_index_test.xhtml')
+
+ tree.doFile(os.path.join(tmp, 'lore_index_test.xhtml'),
+ self.linkrel, '.html', d['baseurl'], templ, d)
+
+ self.assertXMLEqual(
+ """\
+<?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>Twisted Documentation: The way of the program</title></head>
+ <body bgcolor="white">
+ <h1 class="title">The way of the program</h1>
+ <div class="content">
+
+<span/>
+
+<p>The first paragraph.</p>
+
+
+<h2>The Python programming language<a name="auto0"/></h2>
+<a name="index01"/>
+<a name="index02"/>
+
+<p>The second paragraph.</p>
+
+
+</div>
+ <a href="theIndexFile.html">Index</a>
+ </body>
+</html>""",
+ FilePath(tmp).child("lore_index_test.html").getContent())
+
+
+ def test_indexEntriesAdded(self):
+ indexer.addEntry('lore_index_test.html', 'index02', 'language of programming', '1.3')
+ indexer.addEntry('lore_index_test.html', 'index01', 'programming language', '1.2')
+ indexer.setIndexFilename("lore_index_file.html")
+ indexer.generateIndex()
+ self.assertEqualFiles1("lore_index_file_out.html", "lore_index_file.html")
+
+ def test_book(self):
+ tmp = self.makeTemp()
+ inputFilename = sp('lore_index_test.xhtml')
+
+ bookFilename = os.path.join(tmp, 'lore_test_book.book')
+ bf = open(bookFilename, 'w')
+ bf.write('Chapter(r"%s", None)\r\n' % inputFilename)
+ bf.close()
+
+ book = htmlbook.Book(bookFilename)
+ expected = {'indexFilename': None,
+ 'chapters': [(inputFilename, None)],
+ }
+ dct = book.__dict__
+ for k in dct:
+ self.assertEquals(dct[k], expected[k])
+
+ def test_runningLore(self):
+ options = lore.Options()
+ tmp = self.makeTemp('lore_index_test.xhtml')
+
+ templateFilename = sp('template.tpl')
+ inputFilename = os.path.join(tmp, 'lore_index_test.xhtml')
+ indexFilename = 'theIndexFile'
+
+ bookFilename = os.path.join(tmp, 'lore_test_book.book')
+ bf = open(bookFilename, 'w')
+ bf.write('Chapter(r"%s", None)\n' % inputFilename)
+ bf.close()
+
+ options.parseOptions(['--null', '--book=%s' % bookFilename,
+ '--config', 'template=%s' % templateFilename,
+ '--index=%s' % indexFilename
+ ])
+ result = lore.runGivenOptions(options)
+ self.assertEquals(None, result)
+ self.assertEqualFiles1("lore_index_file_unnumbered_out.html", indexFilename + ".html")
+
+
+ def test_runningLoreMultipleFiles(self):
+ tmp = self.makeTemp('lore_index_test.xhtml', 'lore_index_test2.xhtml')
+ templateFilename = sp('template.tpl')
+ inputFilename = os.path.join(tmp, 'lore_index_test.xhtml')
+ inputFilename2 = os.path.join(tmp, 'lore_index_test2.xhtml')
+ indexFilename = 'theIndexFile'
+
+ bookFilename = os.path.join(tmp, 'lore_test_book.book')
+ bf = open(bookFilename, 'w')
+ bf.write('Chapter(r"%s", None)\n' % inputFilename)
+ bf.write('Chapter(r"%s", None)\n' % inputFilename2)
+ bf.close()
+
+ options = lore.Options()
+ options.parseOptions(['--null', '--book=%s' % bookFilename,
+ '--config', 'template=%s' % templateFilename,
+ '--index=%s' % indexFilename
+ ])
+ result = lore.runGivenOptions(options)
+ self.assertEquals(None, result)
+
+ self.assertEqual(
+ # XXX This doesn't seem like a very good index file.
+ """\
+aahz: <a href="lore_index_test2.html#index03">link</a><br />
+aahz2: <a href="lore_index_test2.html#index02">link</a><br />
+language of programming: <a href="lore_index_test.html#index02">link</a>, <a href="lore_index_test2.html#index01">link</a><br />
+programming language: <a href="lore_index_test.html#index01">link</a><br />
+""",
+ file(FilePath(indexFilename + ".html").path).read())
+
+ self.assertXMLEqual(
+ """\
+<?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>Twisted Documentation: The way of the program</title></head>
+ <body bgcolor="white">
+ <h1 class="title">The way of the program</h1>
+ <div class="content">
+
+<span/>
+
+<p>The first paragraph.</p>
+
+
+<h2>The Python programming language<a name="auto0"/></h2>
+<a name="index01"/>
+<a name="index02"/>
+
+<p>The second paragraph.</p>
+
+
+</div>
+ <a href="theIndexFile.html">Index</a>
+ </body>
+</html>""",
+ FilePath(tmp).child("lore_index_test.html").getContent())
+
+ self.assertXMLEqual(
+ """\
+<?xml version="1.0" ?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>Twisted Documentation: The second page to index</title></head>
+ <body bgcolor="white">
+ <h1 class="title">The second page to index</h1>
+ <div class="content">
+
+<span/>
+
+<p>The first paragraph of the second page.</p>
+
+
+<h2>The Jython programming language<a name="auto0"/></h2>
+<a name="index01"/>
+<a name="index02"/>
+<a name="index03"/>
+
+<p>The second paragraph of the second page.</p>
+
+
+</div>
+ <a href="theIndexFile.html">Index</a>
+ </body>
+</html>""",
+ FilePath(tmp).child("lore_index_test2.html").getContent())
+
+
+
+ def XXXtest_NumberedSections(self):
+ # run two files through lore, with numbering turned on
+ # every h2 should be numbered:
+ # first file's h2s should be 1.1, 1.2
+ # second file's h2s should be 2.1, 2.2
+ templateFilename = sp('template.tpl')
+ inputFilename = sp('lore_numbering_test.xhtml')
+ inputFilename2 = sp('lore_numbering_test2.xhtml')
+ indexFilename = 'theIndexFile'
+
+ # you can number without a book:
+ options = lore.Options()
+ options.parseOptions(['--null',
+ '--index=%s' % indexFilename,
+ '--config', 'template=%s' % templateFilename,
+ '--config', 'ext=%s' % ".tns",
+ '--number',
+ inputFilename, inputFilename2])
+ result = lore.runGivenOptions(options)
+
+ self.assertEquals(None, result)
+ #self.assertEqualFiles1("lore_index_file_out_multiple.html", indexFilename + ".tns")
+ # VVV change to new, numbered files
+ self.assertEqualFiles("lore_numbering_test_out.html", "lore_numbering_test.tns")
+ self.assertEqualFiles("lore_numbering_test_out2.html", "lore_numbering_test2.tns")
+
+
+ def test_setTitle(self):
+ """
+ L{tree.setTitle} inserts the given title into the first I{title}
+ element and the first element with the I{title} class in the given
+ template.
+ """
+ parent = dom.Element('div')
+ firstTitle = dom.Element('title')
+ parent.appendChild(firstTitle)
+ secondTitle = dom.Element('span')
+ secondTitle.setAttribute('class', 'title')
+ parent.appendChild(secondTitle)
+
+ titleNodes = [dom.Text()]
+ # minidom has issues with cloning documentless-nodes. See Python issue
+ # 4851.
+ titleNodes[0].ownerDocument = dom.Document()
+ titleNodes[0].data = 'foo bar'
+
+ tree.setTitle(parent, titleNodes, None)
+ self.assertEqual(firstTitle.toxml(), '<title>foo bar</title>')
+ self.assertEqual(
+ secondTitle.toxml(), '<span class="title">foo bar</span>')
+
+
+ def test_setTitleWithChapter(self):
+ """
+ L{tree.setTitle} includes a chapter number if it is passed one.
+ """
+ document = dom.Document()
+
+ parent = dom.Element('div')
+ parent.ownerDocument = document
+
+ title = dom.Element('title')
+ parent.appendChild(title)
+
+ titleNodes = [dom.Text()]
+ titleNodes[0].ownerDocument = document
+ titleNodes[0].data = 'foo bar'
+
+ # Oh yea. The numberer has to agree to put the chapter number in, too.
+ numberer.setNumberSections(True)
+
+ tree.setTitle(parent, titleNodes, '13')
+ self.assertEqual(title.toxml(), '<title>13. foo bar</title>')
+
+
+ def test_setIndexLink(self):
+ """
+ Tests to make sure that index links are processed when an index page
+ exists and removed when there is not.
+ """
+ templ = dom.parse(open(d['template']))
+ indexFilename = 'theIndexFile'
+ numLinks = len(domhelpers.findElementsWithAttribute(templ,
+ "class",
+ "index-link"))
+
+ # if our testing template has no index-link nodes, complain about it
+ self.assertNotEquals(
+ [],
+ domhelpers.findElementsWithAttribute(templ,
+ "class",
+ "index-link"))
+
+ tree.setIndexLink(templ, indexFilename)
+
+ self.assertEquals(
+ [],
+ domhelpers.findElementsWithAttribute(templ,
+ "class",
+ "index-link"))
+
+ indexLinks = domhelpers.findElementsWithAttribute(templ,
+ "href",
+ indexFilename)
+ self.assertTrue(len(indexLinks) >= numLinks)
+
+ templ = dom.parse(open(d['template']))
+ self.assertNotEquals(
+ [],
+ domhelpers.findElementsWithAttribute(templ,
+ "class",
+ "index-link"))
+ indexFilename = None
+
+ tree.setIndexLink(templ, indexFilename)
+
+ self.assertEquals(
+ [],
+ domhelpers.findElementsWithAttribute(templ,
+ "class",
+ "index-link"))
+
+
+ def test_addMtime(self):
+ """
+ L{tree.addMtime} inserts a text node giving the last modification time
+ of the specified file wherever it encounters an element with the
+ I{mtime} class.
+ """
+ path = FilePath(self.mktemp())
+ path.setContent('')
+ when = time.ctime(path.getModificationTime())
+
+ parent = dom.Element('div')
+ mtime = dom.Element('span')
+ mtime.setAttribute('class', 'mtime')
+ parent.appendChild(mtime)
+
+ tree.addMtime(parent, path.path)
+ self.assertEqual(
+ mtime.toxml(), '<span class="mtime">' + when + '</span>')
+
+
+ def test_makeLineNumbers(self):
+ """
+ L{tree._makeLineNumbers} takes an integer and returns a I{p} tag with
+ that number of line numbers in it.
+ """
+ numbers = tree._makeLineNumbers(1)
+ self.assertEqual(numbers.tagName, 'p')
+ self.assertEqual(numbers.getAttribute('class'), 'py-linenumber')
+ self.assertIsInstance(numbers.firstChild, dom.Text)
+ self.assertEqual(numbers.firstChild.nodeValue, '1\n')
+
+ numbers = tree._makeLineNumbers(10)
+ self.assertEqual(numbers.tagName, 'p')
+ self.assertEqual(numbers.getAttribute('class'), 'py-linenumber')
+ self.assertIsInstance(numbers.firstChild, dom.Text)
+ self.assertEqual(
+ numbers.firstChild.nodeValue,
+ ' 1\n 2\n 3\n 4\n 5\n'
+ ' 6\n 7\n 8\n 9\n10\n')
+
+
+ def test_fontifyPythonNode(self):
+ """
+ L{tree.fontifyPythonNode} accepts a text node and replaces it in its
+ parent with a syntax colored and line numbered version of the Python
+ source it contains.
+ """
+ parent = dom.Element('div')
+ source = dom.Text()
+ source.data = 'def foo():\n pass\n'
+ parent.appendChild(source)
+
+ tree.fontifyPythonNode(source)
+
+ expected = """\
+<div><pre class="python"><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">foo</span>():
+ <span class="py-src-keyword">pass</span>
+</pre></div>"""
+
+ self.assertEqual(parent.toxml(), expected)
+
+
+ def test_addPyListings(self):
+ """
+ L{tree.addPyListings} accepts a document with nodes with their I{class}
+ attribute set to I{py-listing} and replaces those nodes with Python
+ source listings from the file given by the node's I{href} attribute.
+ """
+ listingPath = FilePath(self.mktemp())
+ listingPath.setContent('def foo():\n pass\n')
+
+ parent = dom.Element('div')
+ listing = dom.Element('a')
+ listing.setAttribute('href', listingPath.basename())
+ listing.setAttribute('class', 'py-listing')
+ parent.appendChild(listing)
+
+ tree.addPyListings(parent, listingPath.dirname())
+
+ expected = """\
+<div><div class="py-listing"><pre><p class="py-linenumber">1
+2
+</p><span class="py-src-keyword">def</span> <span class="py-src-identifier">foo</span>():
+ <span class="py-src-keyword">pass</span>
+</pre><div class="caption"> - <a href="temp"><span class="filename">temp</span></a></div></div></div>"""
+
+ self.assertEqual(parent.toxml(), expected)
+
+
+ def test_addPyListingsSkipLines(self):
+ """
+ If a node with the I{py-listing} class also has a I{skipLines}
+ attribute, that number of lines from the beginning of the source
+ listing are omitted.
+ """
+ listingPath = FilePath(self.mktemp())
+ listingPath.setContent('def foo():\n pass\n')
+
+ parent = dom.Element('div')
+ listing = dom.Element('a')
+ listing.setAttribute('href', listingPath.basename())
+ listing.setAttribute('class', 'py-listing')
+ listing.setAttribute('skipLines', 1)
+ parent.appendChild(listing)
+
+ tree.addPyListings(parent, listingPath.dirname())
+
+ expected = """\
+<div><div class="py-listing"><pre><p class="py-linenumber">1
+</p> <span class="py-src-keyword">pass</span>
+</pre><div class="caption"> - <a href="temp"><span class="filename">temp</span></a></div></div></div>"""
+
+ self.assertEqual(parent.toxml(), expected)
+
+
+ def test_fixAPI(self):
+ """
+ The element passed to L{tree.fixAPI} has all of its children with the
+ I{API} class rewritten to contain links to the API which is referred to
+ by the text they contain.
+ """
+ parent = dom.Element('div')
+ link = dom.Element('span')
+ link.setAttribute('class', 'API')
+ text = dom.Text()
+ text.data = 'foo'
+ link.appendChild(text)
+ parent.appendChild(link)
+
+ tree.fixAPI(parent, 'http://example.com/%s')
+ self.assertEqual(
+ parent.toxml(),
+ '<div><span class="API">'
+ '<a href="http://example.com/foo" title="foo">foo</a>'
+ '</span></div>')
+
+
+ def test_fixAPIBase(self):
+ """
+ If a node with the I{API} class and a value for the I{base} attribute
+ is included in the DOM passed to L{tree.fixAPI}, the link added to that
+ node refers to the API formed by joining the value of the I{base}
+ attribute to the text contents of the node.
+ """
+ parent = dom.Element('div')
+ link = dom.Element('span')
+ link.setAttribute('class', 'API')
+ link.setAttribute('base', 'bar')
+ text = dom.Text()
+ text.data = 'baz'
+ link.appendChild(text)
+ parent.appendChild(link)
+
+ tree.fixAPI(parent, 'http://example.com/%s')
+
+ self.assertEqual(
+ parent.toxml(),
+ '<div><span class="API">'
+ '<a href="http://example.com/bar.baz" title="bar.baz">baz</a>'
+ '</span></div>')
+
+
+ def test_fixLinks(self):
+ """
+ Links in the nodes of the DOM passed to L{tree.fixLinks} have their
+ extensions rewritten to the given extension.
+ """
+ parent = dom.Element('div')
+ link = dom.Element('a')
+ link.setAttribute('href', 'foo.html')
+ parent.appendChild(link)
+
+ tree.fixLinks(parent, '.xhtml')
+
+ self.assertEqual(parent.toxml(), '<div><a href="foo.xhtml"/></div>')
+
+
+ def test_setVersion(self):
+ """
+ Nodes of the DOM passed to L{tree.setVersion} which have the I{version}
+ class have the given version added to them a child.
+ """
+ parent = dom.Element('div')
+ version = dom.Element('span')
+ version.setAttribute('class', 'version')
+ parent.appendChild(version)
+
+ tree.setVersion(parent, '1.2.3')
+
+ self.assertEqual(
+ parent.toxml(), '<div><span class="version">1.2.3</span></div>')
+
+
+ def test_footnotes(self):
+ """
+ L{tree.footnotes} finds all of the nodes with the I{footnote} class in
+ the DOM passed to it and adds a footnotes section to the end of the
+ I{body} element which includes them. It also inserts links to those
+ footnotes from the original definition location.
+ """
+ parent = dom.Element('div')
+ body = dom.Element('body')
+ footnote = dom.Element('span')
+ footnote.setAttribute('class', 'footnote')
+ text = dom.Text()
+ text.data = 'this is the footnote'
+ footnote.appendChild(text)
+ body.appendChild(footnote)
+ body.appendChild(dom.Element('p'))
+ parent.appendChild(body)
+
+ tree.footnotes(parent)
+
+ self.assertEqual(
+ parent.toxml(),
+ '<div><body>'
+ '<a href="#footnote-1" title="this is the footnote">'
+ '<super>1</super>'
+ '</a>'
+ '<p/>'
+ '<h2>Footnotes</h2>'
+ '<ol><li><a name="footnote-1">'
+ '<span class="footnote">this is the footnote</span>'
+ '</a></li></ol>'
+ '</body></div>')
+
+
+ def test_generateTableOfContents(self):
+ """
+ L{tree.generateToC} returns an element which contains a table of
+ contents generated from the headers in the document passed to it.
+ """
+ parent = dom.Element('body')
+ header = dom.Element('h2')
+ text = dom.Text()
+ text.data = u'header & special character'
+ header.appendChild(text)
+ parent.appendChild(header)
+ subheader = dom.Element('h3')
+ text = dom.Text()
+ text.data = 'subheader'
+ subheader.appendChild(text)
+ parent.appendChild(subheader)
+
+ tableOfContents = tree.generateToC(parent)
+ self.assertEqual(
+ tableOfContents.toxml(),
+ '<ol><li><a href="#auto0">header &amp; special character</a></li><ul><li><a href="#auto1">subheader</a></li></ul></ol>')
+
+ self.assertEqual(
+ header.toxml(),
+ '<h2>header &amp; special character<a name="auto0"/></h2>')
+
+ self.assertEqual(
+ subheader.toxml(),
+ '<h3>subheader<a name="auto1"/></h3>')
+
+
+ def test_putInToC(self):
+ """
+ L{tree.putInToC} replaces all of the children of the first node with
+ the I{toc} class with the given node representing a table of contents.
+ """
+ parent = dom.Element('div')
+ toc = dom.Element('span')
+ toc.setAttribute('class', 'toc')
+ toc.appendChild(dom.Element('foo'))
+ parent.appendChild(toc)
+
+ tree.putInToC(parent, dom.Element('toc'))
+
+ self.assertEqual(toc.toxml(), '<span class="toc"><toc/></span>')
+
+
+ def test_invalidTableOfContents(self):
+ """
+ If passed a document with I{h3} elements before any I{h2} element,
+ L{tree.generateToC} raises L{ValueError} explaining that this is not a
+ valid document.
+ """
+ parent = dom.Element('body')
+ parent.appendChild(dom.Element('h3'))
+ err = self.assertRaises(ValueError, tree.generateToC, parent)
+ self.assertEqual(
+ str(err), "No H3 element is allowed until after an H2 element")
+
+
+ def test_notes(self):
+ """
+ L{tree.notes} inserts some additional markup before the first child of
+ any node with the I{note} class.
+ """
+ parent = dom.Element('div')
+ noteworthy = dom.Element('span')
+ noteworthy.setAttribute('class', 'note')
+ noteworthy.appendChild(dom.Element('foo'))
+ parent.appendChild(noteworthy)
+
+ tree.notes(parent)
+
+ self.assertEqual(
+ noteworthy.toxml(),
+ '<span class="note"><strong>Note: </strong><foo/></span>')
+
+
+ def test_findNodeJustBefore(self):
+ """
+ L{tree.findNodeJustBefore} returns the previous sibling of the node it
+ is passed. The list of nodes passed in is ignored.
+ """
+ parent = dom.Element('div')
+ result = dom.Element('foo')
+ target = dom.Element('bar')
+ parent.appendChild(result)
+ parent.appendChild(target)
+
+ self.assertIdentical(
+ tree.findNodeJustBefore(target, [parent, result]),
+ result)
+
+ # Also, support other configurations. This is a really not nice API.
+ newTarget = dom.Element('baz')
+ target.appendChild(newTarget)
+ self.assertIdentical(
+ tree.findNodeJustBefore(newTarget, [parent, result]),
+ result)
+
+
+ def test_getSectionNumber(self):
+ """
+ L{tree.getSectionNumber} accepts an I{H2} element and returns its text
+ content.
+ """
+ header = dom.Element('foo')
+ text = dom.Text()
+ text.data = 'foobar'
+ header.appendChild(text)
+ self.assertEqual(tree.getSectionNumber(header), 'foobar')
+
+
+ def test_numberDocument(self):
+ """
+ L{tree.numberDocument} inserts section numbers into the text of each
+ header.
+ """
+ parent = dom.Element('foo')
+ section = dom.Element('h2')
+ text = dom.Text()
+ text.data = 'foo'
+ section.appendChild(text)
+ parent.appendChild(section)
+
+ tree.numberDocument(parent, '7')
+
+ self.assertEqual(section.toxml(), '<h2>7.1 foo</h2>')
+
+
+ def test_parseFileAndReport(self):
+ """
+ L{tree.parseFileAndReport} parses the contents of the filename passed
+ to it and returns the corresponding DOM.
+ """
+ path = FilePath(self.mktemp())
+ path.setContent('<foo bar="baz">hello</foo>\n')
+
+ document = tree.parseFileAndReport(path.path)
+ self.assertXMLEqual(
+ document.toxml(),
+ '<?xml version="1.0" ?><foo bar="baz">hello</foo>')
+
+
+ def test_parseFileAndReportMismatchedTags(self):
+ """
+ If the contents of the file passed to L{tree.parseFileAndReport}
+ contain a mismatched tag, L{process.ProcessingFailure} is raised
+ indicating the location of the open and close tags which were
+ mismatched.
+ """
+ path = FilePath(self.mktemp())
+ path.setContent(' <foo>\n\n </bar>')
+
+ err = self.assertRaises(
+ process.ProcessingFailure, tree.parseFileAndReport, path.path)
+ self.assertEqual(
+ str(err),
+ "mismatched close tag at line 3, column 4; expected </foo> "
+ "(from line 1, column 2)")
+
+ # Test a case which requires involves proper close tag handling.
+ path.setContent('<foo><bar></bar>\n </baz>')
+
+ err = self.assertRaises(
+ process.ProcessingFailure, tree.parseFileAndReport, path.path)
+ self.assertEqual(
+ str(err),
+ "mismatched close tag at line 2, column 4; expected </foo> "
+ "(from line 1, column 0)")
+
+
+ def test_parseFileAndReportParseError(self):
+ """
+ If the contents of the file passed to L{tree.parseFileAndReport} cannot
+ be parsed for a reason other than mismatched tags,
+ L{process.ProcessingFailure} is raised with a string describing the
+ parse error.
+ """
+ path = FilePath(self.mktemp())
+ path.setContent('\n foo')
+
+ err = self.assertRaises(
+ process.ProcessingFailure, tree.parseFileAndReport, path.path)
+ self.assertEqual(str(err), 'syntax error at line 2, column 3')
+
+
+ def test_parseFileAndReportIOError(self):
+ """
+ If an L{IOError} is raised while reading from the file specified to
+ L{tree.parseFileAndReport}, a L{process.ProcessingFailure} is raised
+ indicating what the error was. The file should be closed by the
+ time the exception is raised to the caller.
+ """
+ class FakeFile:
+ _open = True
+ def read(self, bytes=None):
+ raise IOError(errno.ENOTCONN, 'socket not connected')
+
+ def close(self):
+ self._open = False
+
+ theFile = FakeFile()
+ def fakeOpen(filename):
+ return theFile
+
+ err = self.assertRaises(
+ process.ProcessingFailure, tree.parseFileAndReport, "foo", fakeOpen)
+ self.assertEqual(str(err), "socket not connected, filename was 'foo'")
+ self.assertFalse(theFile._open)
+
+
+
+class XMLParsingTests(unittest.TestCase):
+ """
+ Tests for various aspects of parsing a Lore XML input document using
+ L{tree.parseFileAndReport}.
+ """
+ def _parseTest(self, xml):
+ path = FilePath(self.mktemp())
+ path.setContent(xml)
+ return tree.parseFileAndReport(path.path)
+
+
+ def test_withoutDocType(self):
+ """
+ A Lore XML input document may omit a I{DOCTYPE} declaration. If it
+ does so, the XHTML1 Strict DTD is used.
+ """
+ # Parsing should succeed.
+ document = self._parseTest("<foo>uses an xhtml entity: &copy;</foo>")
+ # But even more than that, the &copy; entity should be turned into the
+ # appropriate unicode codepoint.
+ self.assertEqual(
+ domhelpers.gatherTextNodes(document.documentElement),
+ u"uses an xhtml entity: \N{COPYRIGHT SIGN}")
+
+
+ def test_withTransitionalDocType(self):
+ """
+ A Lore XML input document may include a I{DOCTYPE} declaration
+ referring to the XHTML1 Transitional DTD.
+ """
+ # Parsing should succeed.
+ document = self._parseTest("""\
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<foo>uses an xhtml entity: &copy;</foo>
+""")
+ # But even more than that, the &copy; entity should be turned into the
+ # appropriate unicode codepoint.
+ self.assertEqual(
+ domhelpers.gatherTextNodes(document.documentElement),
+ u"uses an xhtml entity: \N{COPYRIGHT SIGN}")
+
+
+ def test_withStrictDocType(self):
+ """
+ A Lore XML input document may include a I{DOCTYPE} declaration
+ referring to the XHTML1 Strict DTD.
+ """
+ # Parsing should succeed.
+ document = self._parseTest("""\
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<foo>uses an xhtml entity: &copy;</foo>
+""")
+ # But even more than that, the &copy; entity should be turned into the
+ # appropriate unicode codepoint.
+ self.assertEqual(
+ domhelpers.gatherTextNodes(document.documentElement),
+ u"uses an xhtml entity: \N{COPYRIGHT SIGN}")
+
+
+ def test_withDisallowedDocType(self):
+ """
+ A Lore XML input document may not include a I{DOCTYPE} declaration
+ referring to any DTD other than XHTML1 Transitional or XHTML1 Strict.
+ """
+ self.assertRaises(
+ process.ProcessingFailure,
+ self._parseTest,
+ """\
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+<foo>uses an xhtml entity: &copy;</foo>
+""")
+
+
+
+class XMLSerializationTests(unittest.TestCase, _XMLAssertionMixin):
+ """
+ Tests for L{tree._writeDocument}.
+ """
+ def test_nonASCIIData(self):
+ """
+ A document which contains non-ascii characters is serialized to a
+ file using UTF-8.
+ """
+ document = dom.Document()
+ parent = dom.Element('foo')
+ text = dom.Text()
+ text.data = u'\N{SNOWMAN}'
+ parent.appendChild(text)
+ document.appendChild(parent)
+ outFile = self.mktemp()
+ tree._writeDocument(outFile, document)
+ self.assertXMLEqual(
+ FilePath(outFile).getContent(),
+ u'<foo>\N{SNOWMAN}</foo>'.encode('utf-8'))
+
+
+
+class LatexSpitterTestCase(unittest.TestCase):
+ """
+ Tests for the Latex output plugin.
+ """
+ def test_indexedSpan(self):
+ """
+ Test processing of a span tag with an index class results in a latex
+ \\index directive the correct value.
+ """
+ doc = dom.parseString('<span class="index" value="name" />').documentElement
+ out = StringIO()
+ spitter = LatexSpitter(out.write)
+ spitter.visitNode(doc)
+ self.assertEqual(out.getvalue(), u'\\index{name}\n')
+
+
+
+class ScriptTests(unittest.TestCase):
+ """
+ Tests for L{twisted.lore.scripts.lore}, the I{lore} command's
+ implementation,
+ """
+ def test_getProcessor(self):
+ """
+ L{lore.getProcessor} loads the specified output plugin from the
+ specified input plugin.
+ """
+ processor = lore.getProcessor("lore", "html", options)
+ self.assertNotIdentical(processor, None)
+
+
+
+class DeprecationTests(unittest.TestCase):
+ """
+ Tests for deprecated APIs in L{twisted.lore.tree}.
+ """
+ def test_comparePosition(self):
+ """
+ L{tree.comparePosition} is deprecated.
+ """
+ from twisted.web.microdom import parseString
+ element = parseString('<foo/>').documentElement
+ self.assertEqual(
+ self.callDeprecated(
+ Version('Twisted', 9, 0, 0),
+ tree.comparePosition, element, element),
+ 0)
+
+
+ def test_compareMarkPos(self):
+ """
+ L{tree.compareMarkPos} is deprecated.
+ """
+ self.assertEqual(
+ self.callDeprecated(
+ Version('Twisted', 9, 0, 0),
+ tree.compareMarkPos, [0, 1], [1, 2]),
+ -1)
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/test_man2lore.py b/vendor/Twisted-10.0.0/twisted/lore/test/test_man2lore.py
new file mode 100644
index 0000000000..dbf80cb9a5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/test_man2lore.py
@@ -0,0 +1,169 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Tests for L{twisted.lore.man2lore}.
+"""
+
+from StringIO import StringIO
+
+from twisted.trial.unittest import TestCase
+
+from twisted.lore.man2lore import ManConverter
+
+
+_TRANSITIONAL_XHTML_DTD = ("""\
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+""")
+
+
+class ManConverterTestCase(TestCase):
+ """
+ Tests for L{ManConverter}.
+ """
+
+ def setUp(self):
+ """
+ Build instance variables useful for tests.
+
+ @ivar converter: a L{ManConverter} to be used during tests.
+ """
+ self.converter = ManConverter()
+
+
+ def assertConvert(self, inputLines, expectedOutput):
+ """
+ Helper method to check conversion from a man page to a Lore output.
+
+ @param inputLines: lines of the manpages.
+ @type inputLines: C{list}
+
+ @param expectedOutput: expected Lore content.
+ @type expectedOutput: C{str}
+ """
+ inputFile = StringIO()
+ for line in inputLines:
+ inputFile.write(line + '\n')
+ inputFile.seek(0)
+ outputFile = StringIO()
+ self.converter.convert(inputFile, outputFile)
+ self.assertEquals(
+ outputFile.getvalue(), _TRANSITIONAL_XHTML_DTD + expectedOutput)
+
+
+ def test_convert(self):
+ """
+ Test convert on a minimal example.
+ """
+ inputLines = ['.TH BAR "1" "Oct 2007" "" ""', "Foo\n"]
+ output = ("<html><head>\n<title>BAR.1</title></head>\n<body>\n\n"
+ "<h1>BAR.1</h1>\n\n<p>Foo\n\n</p>\n\n</body>\n</html>\n")
+ self.assertConvert(inputLines, output)
+
+
+ def test_TP(self):
+ """
+ Test C{TP} parsing.
+ """
+ inputLines = ['.TH BAR "1" "Oct 2007" "" ""',
+ ".SH HEADER",
+ ".TP",
+ "\\fB-o\\fR, \\fB--option\\fR",
+ "An option"]
+ output = ("<html><head>\n<title>BAR.1</title></head>\n<body>\n\n"
+ "<h1>BAR.1</h1>\n\n<h2>HEADER</h2>\n\n<dl><dt>"
+ "<strong>-o</strong>, <strong>--option</strong>\n</dt>"
+ "<dd>An option\n</dd>\n\n</dl>\n\n</body>\n</html>\n")
+ self.assertConvert(inputLines, output)
+
+
+ def test_TPMultipleOptions(self):
+ """
+ Try to parse multiple C{TP} fields.
+ """
+ inputLines = ['.TH BAR "1" "Oct 2007" "" ""',
+ ".SH HEADER",
+ ".TP",
+ "\\fB-o\\fR, \\fB--option\\fR",
+ "An option",
+ ".TP",
+ "\\fB-n\\fR, \\fB--another\\fR",
+ "Another option",
+ ]
+ output = ("<html><head>\n<title>BAR.1</title></head>\n<body>\n\n"
+ "<h1>BAR.1</h1>\n\n<h2>HEADER</h2>\n\n<dl><dt>"
+ "<strong>-o</strong>, <strong>--option</strong>\n</dt>"
+ "<dd>An option\n</dd>\n\n<dt>"
+ "<strong>-n</strong>, <strong>--another</strong>\n</dt>"
+ "<dd>Another option\n</dd>\n\n</dl>\n\n</body>\n</html>\n")
+ self.assertConvert(inputLines, output)
+
+
+ def test_TPMultiLineOptions(self):
+ """
+ Try to parse multiple C{TP} fields, with options text on several lines.
+ """
+ inputLines = ['.TH BAR "1" "Oct 2007" "" ""',
+ ".SH HEADER",
+ ".TP",
+ "\\fB-o\\fR, \\fB--option\\fR",
+ "An option",
+ "on two lines",
+ ".TP",
+ "\\fB-n\\fR, \\fB--another\\fR",
+ "Another option",
+ "on two lines",
+ ]
+ output = ("<html><head>\n<title>BAR.1</title></head>\n<body>\n\n"
+ "<h1>BAR.1</h1>\n\n<h2>HEADER</h2>\n\n<dl><dt>"
+ "<strong>-o</strong>, <strong>--option</strong>\n</dt>"
+ "<dd>An option\non two lines\n</dd>\n\n"
+ "<dt><strong>-n</strong>, <strong>--another</strong>\n</dt>"
+ "<dd>Another option\non two lines\n</dd>\n\n</dl>\n\n"
+ "</body>\n</html>\n")
+ self.assertConvert(inputLines, output)
+
+
+ def test_ITLegacyManagement(self):
+ """
+ Test management of BL/IT/EL used in some man pages.
+ """
+ inputLines = ['.TH BAR "1" "Oct 2007" "" ""',
+ ".SH HEADER",
+ ".BL",
+ ".IT An option",
+ "on two lines",
+ ".IT",
+ "Another option",
+ "on two lines",
+ ".EL"
+ ]
+ output = ("<html><head>\n<title>BAR.1</title></head>\n<body>\n\n"
+ "<h1>BAR.1</h1>\n\n<h2>HEADER</h2>\n\n<dl>"
+ "<dt>on two lines\n</dt><dd>Another option\non two lines\n"
+ "</dd></dl>\n\n</body>\n</html>\n")
+ self.assertConvert(inputLines, output)
+
+
+ def test_interactiveCommand(self):
+ """
+ Test management of interactive command tag.
+ """
+ inputLines = ['.TH BAR "1" "Oct 2007" "" ""',
+ ".SH HEADER",
+ ".BL",
+ ".IT IC foo AR bar",
+ "option 1",
+ ".IT IC egg AR spam OP AR stuff",
+ "option 2",
+ ".EL"
+ ]
+ output = ("<html><head>\n<title>BAR.1</title></head>\n<body>\n\n"
+ "<h1>BAR.1</h1>\n\n<h2>HEADER</h2>\n\n<dl>"
+ "<dt>foo <u>bar</u></dt><dd>option 1\n</dd><dt>egg "
+ "<u>spam</u> [<u>stuff</u>]</dt><dd>option 2\n</dd></dl>"
+ "\n\n</body>\n</html>\n")
+ self.assertConvert(inputLines, output)
diff --git a/vendor/Twisted-10.0.0/twisted/lore/test/test_slides.py b/vendor/Twisted-10.0.0/twisted/lore/test/test_slides.py
new file mode 100644
index 0000000000..0857398033
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/test/test_slides.py
@@ -0,0 +1,85 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.lore.slides}.
+"""
+
+from xml.dom.minidom import Element, Text
+
+from twisted.trial.unittest import TestCase
+from twisted.lore.slides import HTMLSlide, splitIntoSlides, insertPrevNextLinks
+
+
+class SlidesTests(TestCase):
+ """
+ Tests for functions in L{twisted.lore.slides}.
+ """
+ def test_splitIntoSlides(self):
+ """
+ L{splitIntoSlides} accepts a document and returns a list of two-tuples,
+ each element of which contains the title of a slide taken from an I{h2}
+ element and the body of that slide.
+ """
+ parent = Element('html')
+ body = Element('body')
+ parent.appendChild(body)
+
+ first = Element('h2')
+ text = Text()
+ text.data = 'first slide'
+ first.appendChild(text)
+ body.appendChild(first)
+ body.appendChild(Element('div'))
+ body.appendChild(Element('span'))
+
+ second = Element('h2')
+ text = Text()
+ text.data = 'second slide'
+ second.appendChild(text)
+ body.appendChild(second)
+ body.appendChild(Element('p'))
+ body.appendChild(Element('br'))
+
+ slides = splitIntoSlides(parent)
+
+ self.assertEqual(slides[0][0], 'first slide')
+ firstContent = slides[0][1]
+ self.assertEqual(firstContent[0].tagName, 'div')
+ self.assertEqual(firstContent[1].tagName, 'span')
+ self.assertEqual(len(firstContent), 2)
+
+ self.assertEqual(slides[1][0], 'second slide')
+ secondContent = slides[1][1]
+ self.assertEqual(secondContent[0].tagName, 'p')
+ self.assertEqual(secondContent[1].tagName, 'br')
+ self.assertEqual(len(secondContent), 2)
+
+ self.assertEqual(len(slides), 2)
+
+
+ def test_insertPrevNextText(self):
+ """
+ L{insertPrevNextLinks} appends a text node with the title of the
+ previous slide to each node with a I{previous} class and the title of
+ the next slide to each node with a I{next} class.
+ """
+ next = Element('span')
+ next.setAttribute('class', 'next')
+ container = Element('div')
+ container.appendChild(next)
+ slideWithNext = HTMLSlide(container, 'first', 0)
+
+ previous = Element('span')
+ previous.setAttribute('class', 'previous')
+ container = Element('div')
+ container.appendChild(previous)
+ slideWithPrevious = HTMLSlide(container, 'second', 1)
+
+ insertPrevNextLinks(
+ [slideWithNext, slideWithPrevious], None, None)
+
+ self.assertEqual(
+ next.toxml(), '<span class="next">second</span>')
+ self.assertEqual(
+ previous.toxml(), '<span class="previous">first</span>')
diff --git a/vendor/Twisted-10.0.0/twisted/lore/texi.py b/vendor/Twisted-10.0.0/twisted/lore/texi.py
new file mode 100644
index 0000000000..2763951c01
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/texi.py
@@ -0,0 +1,109 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+from cStringIO import StringIO
+import os, re
+from twisted.python import text
+from twisted.web import domhelpers
+import latex, tree
+
+spaceRe = re.compile('\s+')
+
+def texiEscape(text):
+ return spaceRe.sub(text, ' ')
+
+entities = latex.entities.copy()
+entities['copy'] = '@copyright{}'
+
+class TexiSpitter(latex.BaseLatexSpitter):
+
+ baseLevel = 1
+
+ def writeNodeData(self, node):
+ buf = StringIO()
+ latex.getLatexText(node, self.writer, texiEscape, entities)
+
+ def visitNode_title(self, node):
+ self.writer('@node ')
+ self.visitNodeDefault(node)
+ self.writer('\n')
+ self.writer('@section ')
+ self.visitNodeDefault(node)
+ self.writer('\n')
+ headers = tree.getHeaders(domhelpers.getParents(node)[-1])
+ if not headers:
+ return
+ self.writer('@menu\n')
+ for header in headers:
+ self.writer('* %s::\n' % domhelpers.getNodeText(header))
+ self.writer('@end menu\n')
+
+ def visitNode_pre(self, node):
+ self.writer('@verbatim\n')
+ buf = StringIO()
+ latex.getLatexText(node, buf.write, entities=entities)
+ self.writer(text.removeLeadingTrailingBlanks(buf.getvalue()))
+ self.writer('@end verbatim\n')
+
+ def visitNode_code(self, node):
+ fout = StringIO()
+ latex.getLatexText(node, fout.write, texiEscape, entities)
+ self.writer('@code{'+fout.getvalue()+'}')
+
+ def visitNodeHeader(self, node):
+ self.writer('\n\n@node ')
+ self.visitNodeDefault(node)
+ self.writer('\n')
+ level = (int(node.tagName[1])-2)+self.baseLevel
+ self.writer('\n\n@'+level*'sub'+'section ')
+ self.visitNodeDefault(node)
+ self.writer('\n')
+
+ def visitNode_a_listing(self, node):
+ fileName = os.path.join(self.currDir, node.getAttribute('href'))
+ self.writer('@verbatim\n')
+ self.writer(open(fileName).read())
+ self.writer('@end verbatim')
+ # Write a caption for this source listing
+
+ def visitNode_a_href(self, node):
+ self.visitNodeDefault(node)
+
+ def visitNode_a_name(self, node):
+ self.visitNodeDefault(node)
+
+ visitNode_h2 = visitNode_h3 = visitNode_h4 = visitNodeHeader
+
+ start_dl = '@itemize\n'
+ end_dl = '@end itemize\n'
+ start_ul = '@itemize\n'
+ end_ul = '@end itemize\n'
+
+ start_ol = '@enumerate\n'
+ end_ol = '@end enumerate\n'
+
+ start_li = '@item\n'
+ end_li = '\n'
+
+ start_dt = '@item\n'
+ end_dt = ': '
+ end_dd = '\n'
+
+ start_p = '\n\n'
+
+ start_strong = start_em = '@emph{'
+ end_strong = end_em = '}'
+
+ start_q = "``"
+ end_q = "''"
+
+ start_span_footnote = '@footnote{'
+ end_span_footnote = '}'
+
+ start_div_note = '@quotation\n@strong{Note:}'
+ end_div_note = '@end quotation\n'
+
+ start_th = '@strong{'
+ end_th = '}'
diff --git a/vendor/Twisted-10.0.0/twisted/lore/topfiles/NEWS b/vendor/Twisted-10.0.0/twisted/lore/topfiles/NEWS
new file mode 100644
index 0000000000..79ed3b6cb2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/topfiles/NEWS
@@ -0,0 +1,103 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/<number>
+
+Twisted Lore 10.0.0 (2010-03-01)
+================================
+
+Other
+-----
+ - #4241
+
+
+Twisted Lore 9.0.0 (2009-11-24)
+===============================
+
+Features
+--------
+ - Python source listings now include line numbers (#3486)
+
+Fixes
+-----
+ - Lore now uses minidom instead of Twisted's microdom, which incidentally
+ fixes some Lore bugs such as throwing away certain whitespace
+ (#3560, #414, #3619)
+ - Lore's "lint" command should no longer break on documents with links in them
+ (#4051, #4115)
+
+Deprecations and Removals
+-------------------------
+ - Lore no longer uses the ancient "tml" Twisted plugin system (#1911)
+
+Other
+-----
+ - #3565, #3246, #3540, #3750, #4050
+
+
+Lore 8.2.0 (2008-12-16)
+=======================
+
+Other
+-----
+ - #2207, #2514
+
+
+8.1.0 (2008-05-18)
+==================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+8.0.0 (2008-03-17)
+==================
+
+Fixes
+-----
+ - Change twisted.lore.tree.setIndexLin so that it removes node with index-link
+ class when the specified index filename is None. (#812)
+ - Fix the conversion of the list of options in man pages to Lore format.
+ (#3017)
+ - Fix conch man pages generation. (#3075)
+ - Fix management of the interactive command tag in man2lore. (#3076)
+
+Misc
+----
+ - #2847
+
+
+0.3.0 (2007-01-06)
+==================
+
+Features
+--------
+ - Many docstrings were added to twisted.lore.tree (#2301)
+
+Fixes
+-----
+ - Emitting a span with an index class to latex now works (#2134)
+
+
+0.2.0 (2006-05-24)
+==================
+
+Features
+--------
+ - Docstring improvements.
+
+Fixes
+-----
+ - Embedded Dia support for Latex no longer requires the 'which'
+ command line tool.
+ - Misc: #1142.
+
+Deprecations
+------------
+ - The unused, undocumented, untested and severely crashy 'bookify'
+ functionality was removed.
+
+
+0.1.0
+=====
+ - Use htmlizer mode that doesn't insert extra span tags, thus making
+ it not mess up in Safari.
diff --git a/vendor/Twisted-10.0.0/twisted/lore/topfiles/README b/vendor/Twisted-10.0.0/twisted/lore/topfiles/README
new file mode 100644
index 0000000000..cf88753912
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/topfiles/README
@@ -0,0 +1,3 @@
+Twisted Lore 10.0.0
+
+Twisted Lore depends on Twisted and Twisted Web.
diff --git a/vendor/Twisted-10.0.0/twisted/lore/topfiles/setup.py b/vendor/Twisted-10.0.0/twisted/lore/topfiles/setup.py
new file mode 100644
index 0000000000..e8072f5395
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/topfiles/setup.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+
+try:
+ from twisted.python import dist
+except ImportError:
+ raise SystemExit("twisted.python.dist module not found. Make sure you "
+ "have installed the Twisted core package before "
+ "attempting to install any other Twisted projects.")
+
+if __name__ == '__main__':
+ dist.setup(
+ twisted_subproject="lore",
+ scripts=dist.getScripts("lore"),
+ # metadata
+ name="Twisted Lore",
+ description="Twisted documentation system",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="Andrew Bennetts",
+ url="http://twistedmatrix.com/trac/wiki/TwistedLore",
+ license="MIT",
+ long_description="""\
+Twisted Lore is a documentation generator with HTML and LaTeX support,
+used in the Twisted project.
+""",
+ )
diff --git a/vendor/Twisted-10.0.0/twisted/lore/tree.py b/vendor/Twisted-10.0.0/twisted/lore/tree.py
new file mode 100755
index 0000000000..00e5a294c0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/tree.py
@@ -0,0 +1,1152 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from itertools import count
+import re, os, cStringIO, time, cgi, string, urlparse
+from xml.dom import minidom as dom
+from xml.sax.handler import ErrorHandler, feature_validation
+from xml.dom.pulldom import SAX2DOM
+from xml.sax import make_parser
+from xml.sax.xmlreader import InputSource
+
+from twisted.python import htmlizer, text
+from twisted.python.filepath import FilePath
+from twisted.python.deprecate import deprecated
+from twisted.python.versions import Version
+from twisted.web import domhelpers
+import process, latex, indexer, numberer, htmlbook
+
+# relative links to html files
+def fixLinks(document, ext):
+ """
+ Rewrite links to XHTML lore input documents so they point to lore XHTML
+ output documents.
+
+ Any node with an C{href} attribute which does not contain a value starting
+ with C{http}, C{https}, C{ftp}, or C{mailto} and which does not have a
+ C{class} attribute of C{absolute} or which contains C{listing} and which
+ does point to an URL ending with C{html} will have that attribute value
+ rewritten so that the filename extension is C{ext} instead of C{html}.
+
+ @type document: A DOM Node or Document
+ @param document: The input document which contains all of the content to be
+ presented.
+
+ @type ext: C{str}
+ @param ext: The extension to use when selecting an output file name. This
+ replaces the extension of the input file name.
+
+ @return: C{None}
+ """
+ supported_schemes=['http', 'https', 'ftp', 'mailto']
+ for node in domhelpers.findElementsWithAttribute(document, 'href'):
+ href = node.getAttribute("href")
+ if urlparse.urlparse(href)[0] in supported_schemes:
+ continue
+ if node.getAttribute("class") == "absolute":
+ continue
+ if node.getAttribute("class").find('listing') != -1:
+ continue
+
+ # This is a relative link, so it should be munged.
+ if href.endswith('html') or href[:href.rfind('#')].endswith('html'):
+ fname, fext = os.path.splitext(href)
+ if '#' in fext:
+ fext = ext+'#'+fext.split('#', 1)[1]
+ else:
+ fext = ext
+ node.setAttribute("href", fname + fext)
+
+
+
+def addMtime(document, fullpath):
+ """
+ Set the last modified time of the given document.
+
+ @type document: A DOM Node or Document
+ @param document: The output template which defines the presentation of the
+ last modified time.
+
+ @type fullpath: C{str}
+ @param fullpath: The file name from which to take the last modified time.
+
+ @return: C{None}
+ """
+ for node in domhelpers.findElementsWithAttribute(document, "class","mtime"):
+ txt = dom.Text()
+ txt.data = time.ctime(os.path.getmtime(fullpath))
+ node.appendChild(txt)
+
+
+
+def _getAPI(node):
+ """
+ Retrieve the fully qualified Python name represented by the given node.
+
+ The name is represented by one or two aspects of the node: the value of the
+ node's first child forms the end of the name. If the node has a C{base}
+ attribute, that attribute's value is prepended to the node's value, with
+ C{.} separating the two parts.
+
+ @rtype: C{str}
+ @return: The fully qualified Python name.
+ """
+ base = ""
+ if node.hasAttribute("base"):
+ base = node.getAttribute("base") + "."
+ return base+node.childNodes[0].nodeValue
+
+
+
+def fixAPI(document, url):
+ """
+ Replace API references with links to API documentation.
+
+ @type document: A DOM Node or Document
+ @param document: The input document which contains all of the content to be
+ presented.
+
+ @type url: C{str}
+ @param url: A string which will be interpolated with the fully qualified
+ Python name of any API reference encountered in the input document, the
+ result of which will be used as a link to API documentation for that name
+ in the output document.
+
+ @return: C{None}
+ """
+ # API references
+ for node in domhelpers.findElementsWithAttribute(document, "class", "API"):
+ fullname = _getAPI(node)
+ anchor = dom.Element('a')
+ anchor.setAttribute('href', url % (fullname,))
+ anchor.setAttribute('title', fullname)
+ while node.childNodes:
+ child = node.childNodes[0]
+ node.removeChild(child)
+ anchor.appendChild(child)
+ node.appendChild(anchor)
+ if node.hasAttribute('base'):
+ node.removeAttribute('base')
+
+
+
+def fontifyPython(document):
+ """
+ Syntax color any node in the given document which contains a Python source
+ listing.
+
+ @type document: A DOM Node or Document
+ @param document: The input document which contains all of the content to be
+ presented.
+
+ @return: C{None}
+ """
+ def matcher(node):
+ return (node.nodeName == 'pre' and node.hasAttribute('class') and
+ node.getAttribute('class') == 'python')
+ for node in domhelpers.findElements(document, matcher):
+ fontifyPythonNode(node)
+
+
+
+def fontifyPythonNode(node):
+ """
+ Syntax color the given node containing Python source code.
+
+ The node must have a parent.
+
+ @return: C{None}
+ """
+ oldio = cStringIO.StringIO()
+ latex.getLatexText(node, oldio.write,
+ entities={'lt': '<', 'gt': '>', 'amp': '&'})
+ oldio = cStringIO.StringIO(oldio.getvalue().strip()+'\n')
+ howManyLines = len(oldio.getvalue().splitlines())
+ newio = cStringIO.StringIO()
+ htmlizer.filter(oldio, newio, writer=htmlizer.SmallerHTMLWriter)
+ lineLabels = _makeLineNumbers(howManyLines)
+ newel = dom.parseString(newio.getvalue()).documentElement
+ newel.setAttribute("class", "python")
+ node.parentNode.replaceChild(newel, node)
+ newel.insertBefore(lineLabels, newel.firstChild)
+
+
+
+def addPyListings(document, dir):
+ """
+ Insert Python source listings into the given document from files in the
+ given directory based on C{py-listing} nodes.
+
+ Any node in C{document} with a C{class} attribute set to C{py-listing} will
+ have source lines taken from the file named in that node's C{href}
+ attribute (searched for in C{dir}) inserted in place of that node.
+
+ If a node has a C{skipLines} attribute, its value will be parsed as an
+ integer and that many lines will be skipped at the beginning of the source
+ file.
+
+ @type document: A DOM Node or Document
+ @param document: The document within which to make listing replacements.
+
+ @type dir: C{str}
+ @param dir: The directory in which to find source files containing the
+ referenced Python listings.
+
+ @return: C{None}
+ """
+ for node in domhelpers.findElementsWithAttribute(document, "class",
+ "py-listing"):
+ filename = node.getAttribute("href")
+ outfile = cStringIO.StringIO()
+ lines = map(string.rstrip, open(os.path.join(dir, filename)).readlines())
+
+ skip = node.getAttribute('skipLines') or 0
+ lines = lines[int(skip):]
+ howManyLines = len(lines)
+ data = '\n'.join(lines)
+
+ data = cStringIO.StringIO(text.removeLeadingTrailingBlanks(data))
+ htmlizer.filter(data, outfile, writer=htmlizer.SmallerHTMLWriter)
+ sourceNode = dom.parseString(outfile.getvalue()).documentElement
+ sourceNode.insertBefore(_makeLineNumbers(howManyLines), sourceNode.firstChild)
+ _replaceWithListing(node, sourceNode.toxml(), filename, "py-listing")
+
+
+
+def _makeLineNumbers(howMany):
+ """
+ Return an element which will render line numbers for a source listing.
+
+ @param howMany: The number of lines in the source listing.
+ @type howMany: C{int}
+
+ @return: An L{dom.Element} which can be added to the document before
+ the source listing to add line numbers to it.
+ """
+ # Figure out how many digits wide the widest line number label will be.
+ width = len(str(howMany))
+
+ # Render all the line labels with appropriate padding
+ labels = ['%*d' % (width, i) for i in range(1, howMany + 1)]
+
+ # Create a p element with the right style containing the labels
+ p = dom.Element('p')
+ p.setAttribute('class', 'py-linenumber')
+ t = dom.Text()
+ t.data = '\n'.join(labels) + '\n'
+ p.appendChild(t)
+ return p
+
+
+def _replaceWithListing(node, val, filename, class_):
+ captionTitle = domhelpers.getNodeText(node)
+ if captionTitle == os.path.basename(filename):
+ captionTitle = 'Source listing'
+ text = ('<div class="%s">%s<div class="caption">%s - '
+ '<a href="%s"><span class="filename">%s</span></a></div></div>' %
+ (class_, val, captionTitle, filename, filename))
+ newnode = dom.parseString(text).documentElement
+ node.parentNode.replaceChild(newnode, node)
+
+
+
+def addHTMLListings(document, dir):
+ """
+ Insert HTML source listings into the given document from files in the given
+ directory based on C{html-listing} nodes.
+
+ Any node in C{document} with a C{class} attribute set to C{html-listing}
+ will have source lines taken from the file named in that node's C{href}
+ attribute (searched for in C{dir}) inserted in place of that node.
+
+ @type document: A DOM Node or Document
+ @param document: The document within which to make listing replacements.
+
+ @type dir: C{str}
+ @param dir: The directory in which to find source files containing the
+ referenced HTML listings.
+
+ @return: C{None}
+ """
+ for node in domhelpers.findElementsWithAttribute(document, "class",
+ "html-listing"):
+ filename = node.getAttribute("href")
+ val = ('<pre class="htmlsource">\n%s</pre>' %
+ cgi.escape(open(os.path.join(dir, filename)).read()))
+ _replaceWithListing(node, val, filename, "html-listing")
+
+
+
+def addPlainListings(document, dir):
+ """
+ Insert text listings into the given document from files in the given
+ directory based on C{listing} nodes.
+
+ Any node in C{document} with a C{class} attribute set to C{listing} will
+ have source lines taken from the file named in that node's C{href}
+ attribute (searched for in C{dir}) inserted in place of that node.
+
+ @type document: A DOM Node or Document
+ @param document: The document within which to make listing replacements.
+
+ @type dir: C{str}
+ @param dir: The directory in which to find source files containing the
+ referenced text listings.
+
+ @return: C{None}
+ """
+ for node in domhelpers.findElementsWithAttribute(document, "class",
+ "listing"):
+ filename = node.getAttribute("href")
+ val = ('<pre>\n%s</pre>' %
+ cgi.escape(open(os.path.join(dir, filename)).read()))
+ _replaceWithListing(node, val, filename, "listing")
+
+
+
+def getHeaders(document):
+ """
+ Return all H2 and H3 nodes in the given document.
+
+ @type document: A DOM Node or Document
+
+ @rtype: C{list}
+ """
+ return domhelpers.findElements(
+ document,
+ lambda n, m=re.compile('h[23]$').match: m(n.nodeName))
+
+
+
+def generateToC(document):
+ """
+ Create a table of contents for the given document.
+
+ @type document: A DOM Node or Document
+
+ @rtype: A DOM Node
+ @return: a Node containing a table of contents based on the headers of the
+ given document.
+ """
+ subHeaders = None
+ headers = []
+ for element in getHeaders(document):
+ if element.tagName == 'h2':
+ subHeaders = []
+ headers.append((element, subHeaders))
+ elif subHeaders is None:
+ raise ValueError(
+ "No H3 element is allowed until after an H2 element")
+ else:
+ subHeaders.append(element)
+
+ auto = count().next
+
+ def addItem(headerElement, parent):
+ anchor = dom.Element('a')
+ name = 'auto%d' % (auto(),)
+ anchor.setAttribute('href', '#' + name)
+ text = dom.Text()
+ text.data = domhelpers.getNodeText(headerElement)
+ anchor.appendChild(text)
+ headerNameItem = dom.Element('li')
+ headerNameItem.appendChild(anchor)
+ parent.appendChild(headerNameItem)
+ anchor = dom.Element('a')
+ anchor.setAttribute('name', name)
+ headerElement.appendChild(anchor)
+
+ toc = dom.Element('ol')
+ for headerElement, subHeaders in headers:
+ addItem(headerElement, toc)
+ if subHeaders:
+ subtoc = dom.Element('ul')
+ toc.appendChild(subtoc)
+ for subHeaderElement in subHeaders:
+ addItem(subHeaderElement, subtoc)
+
+ return toc
+
+
+
+def putInToC(document, toc):
+ """
+ Insert the given table of contents into the given document.
+
+ The node with C{class} attribute set to C{toc} has its children replaced
+ with C{toc}.
+
+ @type document: A DOM Node or Document
+ @type toc: A DOM Node
+ """
+ tocOrig = domhelpers.findElementsWithAttribute(document, 'class', 'toc')
+ if tocOrig:
+ tocOrig= tocOrig[0]
+ tocOrig.childNodes = [toc]
+
+
+
+def removeH1(document):
+ """
+ Replace all C{h1} nodes in the given document with empty C{span} nodes.
+
+ C{h1} nodes mark up document sections and the output template is given an
+ opportunity to present this information in a different way.
+
+ @type document: A DOM Node or Document
+ @param document: The input document which contains all of the content to be
+ presented.
+
+ @return: C{None}
+ """
+ h1 = domhelpers.findNodesNamed(document, 'h1')
+ empty = dom.Element('span')
+ for node in h1:
+ node.parentNode.replaceChild(empty, node)
+
+
+
+def footnotes(document):
+ """
+ Find footnotes in the given document, move them to the end of the body, and
+ generate links to them.
+
+ A footnote is any node with a C{class} attribute set to C{footnote}.
+ Footnote links are generated as superscript. Footnotes are collected in a
+ C{ol} node at the end of the document.
+
+ @type document: A DOM Node or Document
+ @param document: The input document which contains all of the content to be
+ presented.
+
+ @return: C{None}
+ """
+ footnotes = domhelpers.findElementsWithAttribute(document, "class",
+ "footnote")
+ if not footnotes:
+ return
+ footnoteElement = dom.Element('ol')
+ id = 1
+ for footnote in footnotes:
+ href = dom.parseString('<a href="#footnote-%(id)d">'
+ '<super>%(id)d</super></a>'
+ % vars()).documentElement
+ text = ' '.join(domhelpers.getNodeText(footnote).split())
+ href.setAttribute('title', text)
+ target = dom.Element('a')
+ target.setAttribute('name', 'footnote-%d' % (id,))
+ target.childNodes = [footnote]
+ footnoteContent = dom.Element('li')
+ footnoteContent.childNodes = [target]
+ footnoteElement.childNodes.append(footnoteContent)
+ footnote.parentNode.replaceChild(href, footnote)
+ id += 1
+ body = domhelpers.findNodesNamed(document, "body")[0]
+ header = dom.parseString('<h2>Footnotes</h2>').documentElement
+ body.childNodes.append(header)
+ body.childNodes.append(footnoteElement)
+
+
+
+def notes(document):
+ """
+ Find notes in the given document and mark them up as such.
+
+ A note is any node with a C{class} attribute set to C{note}.
+
+ (I think this is a very stupid feature. When I found it I actually
+ exclaimed out loud. -exarkun)
+
+ @type document: A DOM Node or Document
+ @param document: The input document which contains all of the content to be
+ presented.
+
+ @return: C{None}
+ """
+ notes = domhelpers.findElementsWithAttribute(document, "class", "note")
+ notePrefix = dom.parseString('<strong>Note: </strong>').documentElement
+ for note in notes:
+ note.childNodes.insert(0, notePrefix)
+
+
+
+def compareMarkPos(a, b):
+ """
+ Perform in every way identically to L{cmp} for valid inputs.
+ """
+ linecmp = cmp(a[0], b[0])
+ if linecmp:
+ return linecmp
+ return cmp(a[1], b[1])
+compareMarkPos = deprecated(Version('Twisted', 9, 0, 0))(compareMarkPos)
+
+
+
+def comparePosition(firstElement, secondElement):
+ """
+ Compare the two elements given by their position in the document or
+ documents they were parsed from.
+
+ @type firstElement: C{dom.Element}
+ @type secondElement: C{dom.Element}
+
+ @return: C{-1}, C{0}, or C{1}, with the same meanings as the return value
+ of L{cmp}.
+ """
+ return cmp(firstElement._markpos, secondElement._markpos)
+comparePosition = deprecated(Version('Twisted', 9, 0, 0))(comparePosition)
+
+
+
+def findNodeJustBefore(target, nodes):
+ """
+ Find the last Element which is a sibling of C{target} and is in C{nodes}.
+
+ @param target: A node the previous sibling of which to return.
+ @param nodes: A list of nodes which might be the right node.
+
+ @return: The previous sibling of C{target}.
+ """
+ while target is not None:
+ node = target.previousSibling
+ while node is not None:
+ if node in nodes:
+ return node
+ node = node.previousSibling
+ target = target.parentNode
+ raise RuntimeError("Oops")
+
+
+
+def getFirstAncestorWithSectionHeader(entry):
+ """
+ Visit the ancestors of C{entry} until one with at least one C{h2} child
+ node is found, then return all of that node's C{h2} child nodes.
+
+ @type entry: A DOM Node
+ @param entry: The node from which to begin traversal. This node itself is
+ excluded from consideration.
+
+ @rtype: C{list} of DOM Nodes
+ @return: All C{h2} nodes of the ultimately selected parent node.
+ """
+ for a in domhelpers.getParents(entry)[1:]:
+ headers = domhelpers.findNodesNamed(a, "h2")
+ if len(headers) > 0:
+ return headers
+ return []
+
+
+
+def getSectionNumber(header):
+ """
+ Retrieve the section number of the given node.
+
+ This is probably intended to interact in a rather specific way with
+ L{numberDocument}.
+
+ @type header: A DOM Node or L{None}
+ @param header: The section from which to extract a number. The section
+ number is the value of this node's first child.
+
+ @return: C{None} or a C{str} giving the section number.
+ """
+ if not header:
+ return None
+ return domhelpers.gatherTextNodes(header.childNodes[0])
+
+
+
+def getSectionReference(entry):
+ """
+ Find the section number which contains the given node.
+
+ This function looks at the given node's ancestry until it finds a node
+ which defines a section, then returns that section's number.
+
+ @type entry: A DOM Node
+ @param entry: The node for which to determine the section.
+
+ @rtype: C{str}
+ @return: The section number, as returned by C{getSectionNumber} of the
+ first ancestor of C{entry} which defines a section, as determined by
+ L{getFirstAncestorWithSectionHeader}.
+ """
+ headers = getFirstAncestorWithSectionHeader(entry)
+ myHeader = findNodeJustBefore(entry, headers)
+ return getSectionNumber(myHeader)
+
+
+
+def index(document, filename, chapterReference):
+ """
+ Extract index entries from the given document and store them for later use
+ and insert named anchors so that the index can link back to those entries.
+
+ Any node with a C{class} attribute set to C{index} is considered an index
+ entry.
+
+ @type document: A DOM Node or Document
+ @param document: The input document which contains all of the content to be
+ presented.
+
+ @type filename: C{str}
+ @param filename: A link to the output for the given document which will be
+ included in the index to link to any index entry found here.
+
+ @type chapterReference: ???
+ @param chapterReference: ???
+
+ @return: C{None}
+ """
+ entries = domhelpers.findElementsWithAttribute(document, "class", "index")
+ if not entries:
+ return
+ i = 0;
+ for entry in entries:
+ i += 1
+ anchor = 'index%02d' % i
+ if chapterReference:
+ ref = getSectionReference(entry) or chapterReference
+ else:
+ ref = 'link'
+ indexer.addEntry(filename, anchor, entry.getAttribute('value'), ref)
+ # does nodeName even affect anything?
+ entry.nodeName = entry.tagName = entry.endTagName = 'a'
+ for attrName in entry.attributes.keys():
+ entry.removeAttribute(attrName)
+ entry.setAttribute('name', anchor)
+
+
+
+def setIndexLink(template, indexFilename):
+ """
+ Insert a link to an index document.
+
+ Any node with a C{class} attribute set to C{index-link} will have its tag
+ name changed to C{a} and its C{href} attribute set to C{indexFilename}.
+
+ @type template: A DOM Node or Document
+ @param template: The output template which defines the presentation of the
+ version information.
+
+ @type indexFilename: C{str}
+ @param indexFilename: The address of the index document to which to link.
+ If any C{False} value, this function will remove all index-link nodes.
+
+ @return: C{None}
+ """
+ indexLinks = domhelpers.findElementsWithAttribute(template,
+ "class",
+ "index-link")
+ for link in indexLinks:
+ if indexFilename is None:
+ link.parentNode.removeChild(link)
+ else:
+ link.nodeName = link.tagName = link.endTagName = 'a'
+ for attrName in link.attributes.keys():
+ link.removeAttribute(attrName)
+ link.setAttribute('href', indexFilename)
+
+
+
+def numberDocument(document, chapterNumber):
+ """
+ Number the sections of the given document.
+
+ A dot-separated chapter, section number is added to the beginning of each
+ section, as defined by C{h2} nodes.
+
+ This is probably intended to interact in a rather specific way with
+ L{getSectionNumber}.
+
+ @type document: A DOM Node or Document
+ @param document: The input document which contains all of the content to be
+ presented.
+
+ @type chapterNumber: C{int}
+ @param chapterNumber: The chapter number of this content in an overall
+ document.
+
+ @return: C{None}
+ """
+ i = 1
+ for node in domhelpers.findNodesNamed(document, "h2"):
+ label = dom.Text()
+ label.data = "%s.%d " % (chapterNumber, i)
+ node.insertBefore(label, node.firstChild)
+ i += 1
+
+
+
+def fixRelativeLinks(document, linkrel):
+ """
+ Replace relative links in C{str} and C{href} attributes with links relative
+ to C{linkrel}.
+
+ @type document: A DOM Node or Document
+ @param document: The output template.
+
+ @type linkrel: C{str}
+ @param linkrel: An prefix to apply to all relative links in C{src} or
+ C{href} attributes in the input document when generating the output
+ document.
+ """
+ for attr in 'src', 'href':
+ for node in domhelpers.findElementsWithAttribute(document, attr):
+ href = node.getAttribute(attr)
+ if not href.startswith('http') and not href.startswith('/'):
+ node.setAttribute(attr, linkrel+node.getAttribute(attr))
+
+
+
+def setTitle(template, title, chapterNumber):
+ """
+ Add title and chapter number information to the template document.
+
+ The title is added to the end of the first C{title} tag and the end of the
+ first tag with a C{class} attribute set to C{title}. If specified, the
+ chapter is inserted before the title.
+
+ @type template: A DOM Node or Document
+ @param template: The output template which defines the presentation of the
+ version information.
+
+ @type title: C{list} of DOM Nodes
+ @param title: Nodes from the input document defining its title.
+
+ @type chapterNumber: C{int}
+ @param chapterNumber: The chapter number of this content in an overall
+ document. If not applicable, any C{False} value will result in this
+ information being omitted.
+
+ @return: C{None}
+ """
+ if numberer.getNumberSections() and chapterNumber:
+ titleNode = dom.Text()
+ # This is necessary in order for cloning below to work. See Python
+ # isuse 4851.
+ titleNode.ownerDocument = template.ownerDocument
+ titleNode.data = '%s. ' % (chapterNumber,)
+ title.insert(0, titleNode)
+
+ for nodeList in (domhelpers.findNodesNamed(template, "title"),
+ domhelpers.findElementsWithAttribute(template, "class",
+ 'title')):
+ if nodeList:
+ for titleNode in title:
+ nodeList[0].appendChild(titleNode.cloneNode(True))
+
+
+
+def setAuthors(template, authors):
+ """
+ Add author information to the template document.
+
+ Names and contact information for authors are added to each node with a
+ C{class} attribute set to C{authors} and to the template head as C{link}
+ nodes.
+
+ @type template: A DOM Node or Document
+ @param template: The output template which defines the presentation of the
+ version information.
+
+ @type authors: C{list} of two-tuples of C{str}
+ @param authors: List of names and contact information for the authors of
+ the input document.
+
+ @return: C{None}
+ """
+
+ for node in domhelpers.findElementsWithAttribute(template,
+ "class", 'authors'):
+
+ # First, similarly to setTitle, insert text into an <div
+ # class="authors">
+ container = dom.Element('span')
+ for name, href in authors:
+ anchor = dom.Element('a')
+ anchor.setAttribute('href', href)
+ anchorText = dom.Text()
+ anchorText.data = name
+ anchor.appendChild(anchorText)
+ if (name, href) == authors[-1]:
+ if len(authors) == 1:
+ container.appendChild(anchor)
+ else:
+ andText = dom.Text()
+ andText.data = 'and '
+ container.appendChild(andText)
+ container.appendChild(anchor)
+ else:
+ container.appendChild(anchor)
+ commaText = dom.Text()
+ commaText.data = ', '
+ container.appendChild(commaText)
+
+ node.appendChild(container)
+
+ # Second, add appropriate <link rel="author" ...> tags to the <head>.
+ head = domhelpers.findNodesNamed(template, 'head')[0]
+ authors = [dom.parseString('<link rel="author" href="%s" title="%s"/>'
+ % (href, name)).childNodes[0]
+ for name, href in authors]
+ head.childNodes.extend(authors)
+
+
+
+def setVersion(template, version):
+ """
+ Add a version indicator to the given template.
+
+ @type template: A DOM Node or Document
+ @param template: The output template which defines the presentation of the
+ version information.
+
+ @type version: C{str}
+ @param version: The version string to add to the template.
+
+ @return: C{None}
+ """
+ for node in domhelpers.findElementsWithAttribute(template, "class",
+ "version"):
+ text = dom.Text()
+ text.data = version
+ node.appendChild(text)
+
+
+
+def getOutputFileName(originalFileName, outputExtension, index=None):
+ """
+ Return a filename which is the same as C{originalFileName} except for the
+ extension, which is replaced with C{outputExtension}.
+
+ For example, if C{originalFileName} is C{'/foo/bar.baz'} and
+ C{outputExtension} is C{'quux'}, the return value will be
+ C{'/foo/bar.quux'}.
+
+ @type originalFileName: C{str}
+ @type outputExtension: C{stR}
+ @param index: ignored, never passed.
+ @rtype: C{str}
+ """
+ return os.path.splitext(originalFileName)[0]+outputExtension
+
+
+
+def munge(document, template, linkrel, dir, fullpath, ext, url, config, outfileGenerator=getOutputFileName):
+ """
+ Mutate C{template} until it resembles C{document}.
+
+ @type document: A DOM Node or Document
+ @param document: The input document which contains all of the content to be
+ presented.
+
+ @type template: A DOM Node or Document
+ @param template: The template document which defines the desired
+ presentation format of the content.
+
+ @type linkrel: C{str}
+ @param linkrel: An prefix to apply to all relative links in C{src} or
+ C{href} attributes in the input document when generating the output
+ document.
+
+ @type dir: C{str}
+ @param dir: The directory in which to search for source listing files.
+
+ @type fullpath: C{str}
+ @param fullpath: The file name which contained the input document.
+
+ @type ext: C{str}
+ @param ext: The extension to use when selecting an output file name. This
+ replaces the extension of the input file name.
+
+ @type url: C{str}
+ @param url: A string which will be interpolated with the fully qualified
+ Python name of any API reference encountered in the input document, the
+ result of which will be used as a link to API documentation for that name
+ in the output document.
+
+ @type config: C{dict}
+ @param config: Further specification of the desired form of the output.
+ Valid keys in this dictionary::
+
+ noapi: If present and set to a True value, links to API documentation
+ will not be generated.
+
+ version: A string which will be included in the output to indicate the
+ version of this documentation.
+
+ @type outfileGenerator: Callable of C{str}, C{str} returning C{str}
+ @param outfileGenerator: Output filename factory. This is invoked with the
+ intput filename and C{ext} and the output document is serialized to the
+ file with the name returned.
+
+ @return: C{None}
+ """
+ fixRelativeLinks(template, linkrel)
+ addMtime(template, fullpath)
+ removeH1(document)
+ if not config.get('noapi', False):
+ fixAPI(document, url)
+ fontifyPython(document)
+ fixLinks(document, ext)
+ addPyListings(document, dir)
+ addHTMLListings(document, dir)
+ addPlainListings(document, dir)
+ putInToC(template, generateToC(document))
+ footnotes(document)
+ notes(document)
+
+ setIndexLink(template, indexer.getIndexFilename())
+ setVersion(template, config.get('version', ''))
+
+ # Insert the document into the template
+ chapterNumber = htmlbook.getNumber(fullpath)
+ title = domhelpers.findNodesNamed(document, 'title')[0].childNodes
+ setTitle(template, title, chapterNumber)
+ if numberer.getNumberSections() and chapterNumber:
+ numberDocument(document, chapterNumber)
+ index(document, outfileGenerator(os.path.split(fullpath)[1], ext),
+ htmlbook.getReference(fullpath))
+
+ authors = domhelpers.findNodesNamed(document, 'link')
+ authors = [(node.getAttribute('title') or '',
+ node.getAttribute('href') or '')
+ for node in authors
+ if node.getAttribute('rel') == 'author']
+ setAuthors(template, authors)
+
+ body = domhelpers.findNodesNamed(document, "body")[0]
+ tmplbody = domhelpers.findElementsWithAttribute(template, "class",
+ "body")[0]
+ tmplbody.childNodes = body.childNodes
+ tmplbody.setAttribute("class", "content")
+
+
+class _LocationReportingErrorHandler(ErrorHandler):
+ """
+ Define a SAX error handler which can report the location of fatal
+ errors.
+
+ Unlike the errors reported during parsing by other APIs in the xml
+ package, this one tries to mismatched tag errors by including the
+ location of both the relevant opening and closing tags.
+ """
+ def __init__(self, contentHandler):
+ self.contentHandler = contentHandler
+
+ def fatalError(self, err):
+ # Unfortunately, the underlying expat error code is only exposed as
+ # a string. I surely do hope no one ever goes and localizes expat.
+ if err.getMessage() == 'mismatched tag':
+ expect, begLine, begCol = self.contentHandler._locationStack[-1]
+ endLine, endCol = err.getLineNumber(), err.getColumnNumber()
+ raise process.ProcessingFailure(
+ "mismatched close tag at line %d, column %d; expected </%s> "
+ "(from line %d, column %d)" % (
+ endLine, endCol, expect, begLine, begCol))
+ raise process.ProcessingFailure(
+ '%s at line %d, column %d' % (err.getMessage(),
+ err.getLineNumber(),
+ err.getColumnNumber()))
+
+
+class _TagTrackingContentHandler(SAX2DOM):
+ """
+ Define a SAX content handler which keeps track of the start location of
+ all open tags. This information is used by the above defined error
+ handler to report useful locations when a fatal error is encountered.
+ """
+ def __init__(self):
+ SAX2DOM.__init__(self)
+ self._locationStack = []
+
+ def setDocumentLocator(self, locator):
+ self._docLocator = locator
+ SAX2DOM.setDocumentLocator(self, locator)
+
+ def startElement(self, name, attrs):
+ self._locationStack.append((name, self._docLocator.getLineNumber(), self._docLocator.getColumnNumber()))
+ SAX2DOM.startElement(self, name, attrs)
+
+ def endElement(self, name):
+ self._locationStack.pop()
+ SAX2DOM.endElement(self, name)
+
+
+class _LocalEntityResolver(object):
+ """
+ Implement DTD loading (from a local source) for the limited number of
+ DTDs which are allowed for Lore input documents.
+
+ @ivar filename: The name of the file containing the lore input
+ document.
+
+ @ivar knownDTDs: A mapping from DTD system identifiers to L{FilePath}
+ instances pointing to the corresponding DTD.
+ """
+ s = FilePath(__file__).sibling
+
+ knownDTDs = {
+ None: s("xhtml1-strict.dtd"),
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd": s("xhtml1-strict.dtd"),
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd": s("xhtml1-transitional.dtd"),
+ "xhtml-lat1.ent": s("xhtml-lat1.ent"),
+ "xhtml-symbol.ent": s("xhtml-symbol.ent"),
+ "xhtml-special.ent": s("xhtml-special.ent"),
+ }
+ del s
+
+ def __init__(self, filename):
+ self.filename = filename
+
+
+ def resolveEntity(self, publicId, systemId):
+ source = InputSource()
+ source.setSystemId(systemId)
+ try:
+ dtdPath = self.knownDTDs[systemId]
+ except KeyError:
+ raise process.ProcessingFailure(
+ "Invalid DTD system identifier (%r) in %s. Only "
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd "
+ "is allowed." % (systemId, self.filename))
+ source.setByteStream(dtdPath.open())
+ return source
+
+
+
+def parseFileAndReport(filename, _open=file):
+ """
+ Parse and return the contents of the given lore XHTML document.
+
+ @type filename: C{str}
+ @param filename: The name of a file containing a lore XHTML document to
+ load.
+
+ @raise process.ProcessingFailure: When the contents of the specified file
+ cannot be parsed.
+
+ @rtype: A DOM Document
+ @return: The document contained in C{filename}.
+ """
+ content = _TagTrackingContentHandler()
+ error = _LocationReportingErrorHandler(content)
+ parser = make_parser()
+ parser.setContentHandler(content)
+ parser.setErrorHandler(error)
+
+ # In order to call a method on the expat parser which will be used by this
+ # parser, we need the expat parser to be created. This doesn't happen
+ # until reset is called, normally by the parser's parse method. That's too
+ # late for us, since it will then go on to parse the document without
+ # letting us do any extra set up. So, force the expat parser to be created
+ # here, and then disable reset so that the parser created is the one
+ # actually used to parse our document. Resetting is only needed if more
+ # than one document is going to be parsed, and that isn't the case here.
+ parser.reset()
+ parser.reset = lambda: None
+
+ # This is necessary to make the xhtml1 transitional declaration optional.
+ # It causes LocalEntityResolver.resolveEntity(None, None) to be called.
+ # LocalEntityResolver handles that case by giving out the xhtml1
+ # transitional dtd. Unfortunately, there is no public API for manipulating
+ # the expat parser when using xml.sax. Using the private _parser attribute
+ # may break. It's also possible that make_parser will return a parser
+ # which doesn't use expat, but uses some other parser. Oh well. :(
+ # -exarkun
+ parser._parser.UseForeignDTD(True)
+ parser.setEntityResolver(_LocalEntityResolver(filename))
+
+ # This is probably no-op because expat is not a validating parser. Who
+ # knows though, maybe you figured out a way to not use expat.
+ parser.setFeature(feature_validation, False)
+
+ fObj = _open(filename)
+ try:
+ try:
+ parser.parse(fObj)
+ except IOError, e:
+ raise process.ProcessingFailure(
+ e.strerror + ", filename was '" + filename + "'")
+ finally:
+ fObj.close()
+ return content.document
+
+
+def makeSureDirectoryExists(filename):
+ filename = os.path.abspath(filename)
+ dirname = os.path.dirname(filename)
+ if (not os.path.exists(dirname)):
+ os.makedirs(dirname)
+
+def doFile(filename, linkrel, ext, url, templ, options={}, outfileGenerator=getOutputFileName):
+ """
+ Process the input document at C{filename} and write an output document.
+
+ @type filename: C{str}
+ @param filename: The path to the input file which will be processed.
+
+ @type linkrel: C{str}
+ @param linkrel: An prefix to apply to all relative links in C{src} or
+ C{href} attributes in the input document when generating the output
+ document.
+
+ @type ext: C{str}
+ @param ext: The extension to use when selecting an output file name. This
+ replaces the extension of the input file name.
+
+ @type url: C{str}
+ @param url: A string which will be interpolated with the fully qualified
+ Python name of any API reference encountered in the input document, the
+ result of which will be used as a link to API documentation for that name
+ in the output document.
+
+ @type templ: A DOM Node or Document
+ @param templ: The template on which the output document will be based.
+ This is mutated and then serialized to the output file.
+
+ @type options: C{dict}
+ @param options: Further specification of the desired form of the output.
+ Valid keys in this dictionary::
+
+ noapi: If present and set to a True value, links to API documentation
+ will not be generated.
+
+ version: A string which will be included in the output to indicate the
+ version of this documentation.
+
+ @type outfileGenerator: Callable of C{str}, C{str} returning C{str}
+ @param outfileGenerator: Output filename factory. This is invoked with the
+ intput filename and C{ext} and the output document is serialized to the
+ file with the name returned.
+
+ @return: C{None}
+ """
+ doc = parseFileAndReport(filename)
+ clonedNode = templ.cloneNode(1)
+ munge(doc, clonedNode, linkrel, os.path.dirname(filename), filename, ext,
+ url, options, outfileGenerator)
+ newFilename = outfileGenerator(filename, ext)
+ _writeDocument(newFilename, clonedNode)
+
+
+
+def _writeDocument(newFilename, clonedNode):
+ """
+ Serialize the given node to XML into the named file.
+
+ @param newFilename: The name of the file to which the XML will be
+ written. If this is in a directory which does not exist, the
+ directory will be created.
+
+ @param clonedNode: The root DOM node which will be serialized.
+
+ @return: C{None}
+ """
+ makeSureDirectoryExists(newFilename)
+ f = open(newFilename, 'w')
+ f.write(clonedNode.toxml('utf-8'))
+ f.close()
diff --git a/vendor/Twisted-10.0.0/twisted/lore/xhtml-lat1.ent b/vendor/Twisted-10.0.0/twisted/lore/xhtml-lat1.ent
new file mode 100644
index 0000000000..ffee223eb1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/xhtml-lat1.ent
@@ -0,0 +1,196 @@
+<!-- Portions (C) International Organization for Standardization 1986
+ Permission to copy in any form is granted for use with
+ conforming SGML systems and applications as defined in
+ ISO 8879, provided this notice is included in all copies.
+-->
+<!-- Character entity set. Typical invocation:
+ <!ENTITY % HTMLlat1 PUBLIC
+ "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent">
+ %HTMLlat1;
+-->
+
+<!ENTITY nbsp "&#160;"> <!-- no-break space = non-breaking space,
+ U+00A0 ISOnum -->
+<!ENTITY iexcl "&#161;"> <!-- inverted exclamation mark, U+00A1 ISOnum -->
+<!ENTITY cent "&#162;"> <!-- cent sign, U+00A2 ISOnum -->
+<!ENTITY pound "&#163;"> <!-- pound sign, U+00A3 ISOnum -->
+<!ENTITY curren "&#164;"> <!-- currency sign, U+00A4 ISOnum -->
+<!ENTITY yen "&#165;"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->
+<!ENTITY brvbar "&#166;"> <!-- broken bar = broken vertical bar,
+ U+00A6 ISOnum -->
+<!ENTITY sect "&#167;"> <!-- section sign, U+00A7 ISOnum -->
+<!ENTITY uml "&#168;"> <!-- diaeresis = spacing diaeresis,
+ U+00A8 ISOdia -->
+<!ENTITY copy "&#169;"> <!-- copyright sign, U+00A9 ISOnum -->
+<!ENTITY ordf "&#170;"> <!-- feminine ordinal indicator, U+00AA ISOnum -->
+<!ENTITY laquo "&#171;"> <!-- left-pointing double angle quotation mark
+ = left pointing guillemet, U+00AB ISOnum -->
+<!ENTITY not "&#172;"> <!-- not sign = angled dash,
+ U+00AC ISOnum -->
+<!ENTITY shy "&#173;"> <!-- soft hyphen = discretionary hyphen,
+ U+00AD ISOnum -->
+<!ENTITY reg "&#174;"> <!-- registered sign = registered trade mark sign,
+ U+00AE ISOnum -->
+<!ENTITY macr "&#175;"> <!-- macron = spacing macron = overline
+ = APL overbar, U+00AF ISOdia -->
+<!ENTITY deg "&#176;"> <!-- degree sign, U+00B0 ISOnum -->
+<!ENTITY plusmn "&#177;"> <!-- plus-minus sign = plus-or-minus sign,
+ U+00B1 ISOnum -->
+<!ENTITY sup2 "&#178;"> <!-- superscript two = superscript digit two
+ = squared, U+00B2 ISOnum -->
+<!ENTITY sup3 "&#179;"> <!-- superscript three = superscript digit three
+ = cubed, U+00B3 ISOnum -->
+<!ENTITY acute "&#180;"> <!-- acute accent = spacing acute,
+ U+00B4 ISOdia -->
+<!ENTITY micro "&#181;"> <!-- micro sign, U+00B5 ISOnum -->
+<!ENTITY para "&#182;"> <!-- pilcrow sign = paragraph sign,
+ U+00B6 ISOnum -->
+<!ENTITY middot "&#183;"> <!-- middle dot = Georgian comma
+ = Greek middle dot, U+00B7 ISOnum -->
+<!ENTITY cedil "&#184;"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->
+<!ENTITY sup1 "&#185;"> <!-- superscript one = superscript digit one,
+ U+00B9 ISOnum -->
+<!ENTITY ordm "&#186;"> <!-- masculine ordinal indicator,
+ U+00BA ISOnum -->
+<!ENTITY raquo "&#187;"> <!-- right-pointing double angle quotation mark
+ = right pointing guillemet, U+00BB ISOnum -->
+<!ENTITY frac14 "&#188;"> <!-- vulgar fraction one quarter
+ = fraction one quarter, U+00BC ISOnum -->
+<!ENTITY frac12 "&#189;"> <!-- vulgar fraction one half
+ = fraction one half, U+00BD ISOnum -->
+<!ENTITY frac34 "&#190;"> <!-- vulgar fraction three quarters
+ = fraction three quarters, U+00BE ISOnum -->
+<!ENTITY iquest "&#191;"> <!-- inverted question mark
+ = turned question mark, U+00BF ISOnum -->
+<!ENTITY Agrave "&#192;"> <!-- latin capital letter A with grave
+ = latin capital letter A grave,
+ U+00C0 ISOlat1 -->
+<!ENTITY Aacute "&#193;"> <!-- latin capital letter A with acute,
+ U+00C1 ISOlat1 -->
+<!ENTITY Acirc "&#194;"> <!-- latin capital letter A with circumflex,
+ U+00C2 ISOlat1 -->
+<!ENTITY Atilde "&#195;"> <!-- latin capital letter A with tilde,
+ U+00C3 ISOlat1 -->
+<!ENTITY Auml "&#196;"> <!-- latin capital letter A with diaeresis,
+ U+00C4 ISOlat1 -->
+<!ENTITY Aring "&#197;"> <!-- latin capital letter A with ring above
+ = latin capital letter A ring,
+ U+00C5 ISOlat1 -->
+<!ENTITY AElig "&#198;"> <!-- latin capital letter AE
+ = latin capital ligature AE,
+ U+00C6 ISOlat1 -->
+<!ENTITY Ccedil "&#199;"> <!-- latin capital letter C with cedilla,
+ U+00C7 ISOlat1 -->
+<!ENTITY Egrave "&#200;"> <!-- latin capital letter E with grave,
+ U+00C8 ISOlat1 -->
+<!ENTITY Eacute "&#201;"> <!-- latin capital letter E with acute,
+ U+00C9 ISOlat1 -->
+<!ENTITY Ecirc "&#202;"> <!-- latin capital letter E with circumflex,
+ U+00CA ISOlat1 -->
+<!ENTITY Euml "&#203;"> <!-- latin capital letter E with diaeresis,
+ U+00CB ISOlat1 -->
+<!ENTITY Igrave "&#204;"> <!-- latin capital letter I with grave,
+ U+00CC ISOlat1 -->
+<!ENTITY Iacute "&#205;"> <!-- latin capital letter I with acute,
+ U+00CD ISOlat1 -->
+<!ENTITY Icirc "&#206;"> <!-- latin capital letter I with circumflex,
+ U+00CE ISOlat1 -->
+<!ENTITY Iuml "&#207;"> <!-- latin capital letter I with diaeresis,
+ U+00CF ISOlat1 -->
+<!ENTITY ETH "&#208;"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->
+<!ENTITY Ntilde "&#209;"> <!-- latin capital letter N with tilde,
+ U+00D1 ISOlat1 -->
+<!ENTITY Ograve "&#210;"> <!-- latin capital letter O with grave,
+ U+00D2 ISOlat1 -->
+<!ENTITY Oacute "&#211;"> <!-- latin capital letter O with acute,
+ U+00D3 ISOlat1 -->
+<!ENTITY Ocirc "&#212;"> <!-- latin capital letter O with circumflex,
+ U+00D4 ISOlat1 -->
+<!ENTITY Otilde "&#213;"> <!-- latin capital letter O with tilde,
+ U+00D5 ISOlat1 -->
+<!ENTITY Ouml "&#214;"> <!-- latin capital letter O with diaeresis,
+ U+00D6 ISOlat1 -->
+<!ENTITY times "&#215;"> <!-- multiplication sign, U+00D7 ISOnum -->
+<!ENTITY Oslash "&#216;"> <!-- latin capital letter O with stroke
+ = latin capital letter O slash,
+ U+00D8 ISOlat1 -->
+<!ENTITY Ugrave "&#217;"> <!-- latin capital letter U with grave,
+ U+00D9 ISOlat1 -->
+<!ENTITY Uacute "&#218;"> <!-- latin capital letter U with acute,
+ U+00DA ISOlat1 -->
+<!ENTITY Ucirc "&#219;"> <!-- latin capital letter U with circumflex,
+ U+00DB ISOlat1 -->
+<!ENTITY Uuml "&#220;"> <!-- latin capital letter U with diaeresis,
+ U+00DC ISOlat1 -->
+<!ENTITY Yacute "&#221;"> <!-- latin capital letter Y with acute,
+ U+00DD ISOlat1 -->
+<!ENTITY THORN "&#222;"> <!-- latin capital letter THORN,
+ U+00DE ISOlat1 -->
+<!ENTITY szlig "&#223;"> <!-- latin small letter sharp s = ess-zed,
+ U+00DF ISOlat1 -->
+<!ENTITY agrave "&#224;"> <!-- latin small letter a with grave
+ = latin small letter a grave,
+ U+00E0 ISOlat1 -->
+<!ENTITY aacute "&#225;"> <!-- latin small letter a with acute,
+ U+00E1 ISOlat1 -->
+<!ENTITY acirc "&#226;"> <!-- latin small letter a with circumflex,
+ U+00E2 ISOlat1 -->
+<!ENTITY atilde "&#227;"> <!-- latin small letter a with tilde,
+ U+00E3 ISOlat1 -->
+<!ENTITY auml "&#228;"> <!-- latin small letter a with diaeresis,
+ U+00E4 ISOlat1 -->
+<!ENTITY aring "&#229;"> <!-- latin small letter a with ring above
+ = latin small letter a ring,
+ U+00E5 ISOlat1 -->
+<!ENTITY aelig "&#230;"> <!-- latin small letter ae
+ = latin small ligature ae, U+00E6 ISOlat1 -->
+<!ENTITY ccedil "&#231;"> <!-- latin small letter c with cedilla,
+ U+00E7 ISOlat1 -->
+<!ENTITY egrave "&#232;"> <!-- latin small letter e with grave,
+ U+00E8 ISOlat1 -->
+<!ENTITY eacute "&#233;"> <!-- latin small letter e with acute,
+ U+00E9 ISOlat1 -->
+<!ENTITY ecirc "&#234;"> <!-- latin small letter e with circumflex,
+ U+00EA ISOlat1 -->
+<!ENTITY euml "&#235;"> <!-- latin small letter e with diaeresis,
+ U+00EB ISOlat1 -->
+<!ENTITY igrave "&#236;"> <!-- latin small letter i with grave,
+ U+00EC ISOlat1 -->
+<!ENTITY iacute "&#237;"> <!-- latin small letter i with acute,
+ U+00ED ISOlat1 -->
+<!ENTITY icirc "&#238;"> <!-- latin small letter i with circumflex,
+ U+00EE ISOlat1 -->
+<!ENTITY iuml "&#239;"> <!-- latin small letter i with diaeresis,
+ U+00EF ISOlat1 -->
+<!ENTITY eth "&#240;"> <!-- latin small letter eth, U+00F0 ISOlat1 -->
+<!ENTITY ntilde "&#241;"> <!-- latin small letter n with tilde,
+ U+00F1 ISOlat1 -->
+<!ENTITY ograve "&#242;"> <!-- latin small letter o with grave,
+ U+00F2 ISOlat1 -->
+<!ENTITY oacute "&#243;"> <!-- latin small letter o with acute,
+ U+00F3 ISOlat1 -->
+<!ENTITY ocirc "&#244;"> <!-- latin small letter o with circumflex,
+ U+00F4 ISOlat1 -->
+<!ENTITY otilde "&#245;"> <!-- latin small letter o with tilde,
+ U+00F5 ISOlat1 -->
+<!ENTITY ouml "&#246;"> <!-- latin small letter o with diaeresis,
+ U+00F6 ISOlat1 -->
+<!ENTITY divide "&#247;"> <!-- division sign, U+00F7 ISOnum -->
+<!ENTITY oslash "&#248;"> <!-- latin small letter o with stroke,
+ = latin small letter o slash,
+ U+00F8 ISOlat1 -->
+<!ENTITY ugrave "&#249;"> <!-- latin small letter u with grave,
+ U+00F9 ISOlat1 -->
+<!ENTITY uacute "&#250;"> <!-- latin small letter u with acute,
+ U+00FA ISOlat1 -->
+<!ENTITY ucirc "&#251;"> <!-- latin small letter u with circumflex,
+ U+00FB ISOlat1 -->
+<!ENTITY uuml "&#252;"> <!-- latin small letter u with diaeresis,
+ U+00FC ISOlat1 -->
+<!ENTITY yacute "&#253;"> <!-- latin small letter y with acute,
+ U+00FD ISOlat1 -->
+<!ENTITY thorn "&#254;"> <!-- latin small letter thorn,
+ U+00FE ISOlat1 -->
+<!ENTITY yuml "&#255;"> <!-- latin small letter y with diaeresis,
+ U+00FF ISOlat1 -->
diff --git a/vendor/Twisted-10.0.0/twisted/lore/xhtml-special.ent b/vendor/Twisted-10.0.0/twisted/lore/xhtml-special.ent
new file mode 100644
index 0000000000..ca358b2fec
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/xhtml-special.ent
@@ -0,0 +1,80 @@
+<!-- Special characters for XHTML -->
+
+<!-- Character entity set. Typical invocation:
+ <!ENTITY % HTMLspecial PUBLIC
+ "-//W3C//ENTITIES Special for XHTML//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent">
+ %HTMLspecial;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+ Permission to copy in any form is granted for use with
+ conforming SGML systems and applications as defined in
+ ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+ New names (i.e., not in ISO 8879 list) do not clash with any
+ existing ISO 8879 entity names. ISO 10646 character numbers
+ are given for each character, in hex. values are decimal
+ conversions of the ISO 10646 values and refer to the document
+ character set. Names are Unicode names.
+-->
+
+<!-- C0 Controls and Basic Latin -->
+<!ENTITY quot "&#34;"> <!-- quotation mark, U+0022 ISOnum -->
+<!ENTITY amp "&#38;#38;"> <!-- ampersand, U+0026 ISOnum -->
+<!ENTITY lt "&#38;#60;"> <!-- less-than sign, U+003C ISOnum -->
+<!ENTITY gt "&#62;"> <!-- greater-than sign, U+003E ISOnum -->
+<!ENTITY apos "&#39;"> <!-- apostrophe = APL quote, U+0027 ISOnum -->
+
+<!-- Latin Extended-A -->
+<!ENTITY OElig "&#338;"> <!-- latin capital ligature OE,
+ U+0152 ISOlat2 -->
+<!ENTITY oelig "&#339;"> <!-- latin small ligature oe, U+0153 ISOlat2 -->
+<!-- ligature is a misnomer, this is a separate character in some languages -->
+<!ENTITY Scaron "&#352;"> <!-- latin capital letter S with caron,
+ U+0160 ISOlat2 -->
+<!ENTITY scaron "&#353;"> <!-- latin small letter s with caron,
+ U+0161 ISOlat2 -->
+<!ENTITY Yuml "&#376;"> <!-- latin capital letter Y with diaeresis,
+ U+0178 ISOlat2 -->
+
+<!-- Spacing Modifier Letters -->
+<!ENTITY circ "&#710;"> <!-- modifier letter circumflex accent,
+ U+02C6 ISOpub -->
+<!ENTITY tilde "&#732;"> <!-- small tilde, U+02DC ISOdia -->
+
+<!-- General Punctuation -->
+<!ENTITY ensp "&#8194;"> <!-- en space, U+2002 ISOpub -->
+<!ENTITY emsp "&#8195;"> <!-- em space, U+2003 ISOpub -->
+<!ENTITY thinsp "&#8201;"> <!-- thin space, U+2009 ISOpub -->
+<!ENTITY zwnj "&#8204;"> <!-- zero width non-joiner,
+ U+200C NEW RFC 2070 -->
+<!ENTITY zwj "&#8205;"> <!-- zero width joiner, U+200D NEW RFC 2070 -->
+<!ENTITY lrm "&#8206;"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->
+<!ENTITY rlm "&#8207;"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->
+<!ENTITY ndash "&#8211;"> <!-- en dash, U+2013 ISOpub -->
+<!ENTITY mdash "&#8212;"> <!-- em dash, U+2014 ISOpub -->
+<!ENTITY lsquo "&#8216;"> <!-- left single quotation mark,
+ U+2018 ISOnum -->
+<!ENTITY rsquo "&#8217;"> <!-- right single quotation mark,
+ U+2019 ISOnum -->
+<!ENTITY sbquo "&#8218;"> <!-- single low-9 quotation mark, U+201A NEW -->
+<!ENTITY ldquo "&#8220;"> <!-- left double quotation mark,
+ U+201C ISOnum -->
+<!ENTITY rdquo "&#8221;"> <!-- right double quotation mark,
+ U+201D ISOnum -->
+<!ENTITY bdquo "&#8222;"> <!-- double low-9 quotation mark, U+201E NEW -->
+<!ENTITY dagger "&#8224;"> <!-- dagger, U+2020 ISOpub -->
+<!ENTITY Dagger "&#8225;"> <!-- double dagger, U+2021 ISOpub -->
+<!ENTITY permil "&#8240;"> <!-- per mille sign, U+2030 ISOtech -->
+<!ENTITY lsaquo "&#8249;"> <!-- single left-pointing angle quotation mark,
+ U+2039 ISO proposed -->
+<!-- lsaquo is proposed but not yet ISO standardized -->
+<!ENTITY rsaquo "&#8250;"> <!-- single right-pointing angle quotation mark,
+ U+203A ISO proposed -->
+<!-- rsaquo is proposed but not yet ISO standardized -->
+
+<!-- Currency Symbols -->
+<!ENTITY euro "&#8364;"> <!-- euro sign, U+20AC NEW -->
diff --git a/vendor/Twisted-10.0.0/twisted/lore/xhtml-symbol.ent b/vendor/Twisted-10.0.0/twisted/lore/xhtml-symbol.ent
new file mode 100644
index 0000000000..63c2abfa6f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/xhtml-symbol.ent
@@ -0,0 +1,237 @@
+<!-- Mathematical, Greek and Symbolic characters for XHTML -->
+
+<!-- Character entity set. Typical invocation:
+ <!ENTITY % HTMLsymbol PUBLIC
+ "-//W3C//ENTITIES Symbols for XHTML//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent">
+ %HTMLsymbol;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+ Permission to copy in any form is granted for use with
+ conforming SGML systems and applications as defined in
+ ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+ New names (i.e., not in ISO 8879 list) do not clash with any
+ existing ISO 8879 entity names. ISO 10646 character numbers
+ are given for each character, in hex. values are decimal
+ conversions of the ISO 10646 values and refer to the document
+ character set. Names are Unicode names.
+-->
+
+<!-- Latin Extended-B -->
+<!ENTITY fnof "&#402;"> <!-- latin small letter f with hook = function
+ = florin, U+0192 ISOtech -->
+
+<!-- Greek -->
+<!ENTITY Alpha "&#913;"> <!-- greek capital letter alpha, U+0391 -->
+<!ENTITY Beta "&#914;"> <!-- greek capital letter beta, U+0392 -->
+<!ENTITY Gamma "&#915;"> <!-- greek capital letter gamma,
+ U+0393 ISOgrk3 -->
+<!ENTITY Delta "&#916;"> <!-- greek capital letter delta,
+ U+0394 ISOgrk3 -->
+<!ENTITY Epsilon "&#917;"> <!-- greek capital letter epsilon, U+0395 -->
+<!ENTITY Zeta "&#918;"> <!-- greek capital letter zeta, U+0396 -->
+<!ENTITY Eta "&#919;"> <!-- greek capital letter eta, U+0397 -->
+<!ENTITY Theta "&#920;"> <!-- greek capital letter theta,
+ U+0398 ISOgrk3 -->
+<!ENTITY Iota "&#921;"> <!-- greek capital letter iota, U+0399 -->
+<!ENTITY Kappa "&#922;"> <!-- greek capital letter kappa, U+039A -->
+<!ENTITY Lambda "&#923;"> <!-- greek capital letter lamda,
+ U+039B ISOgrk3 -->
+<!ENTITY Mu "&#924;"> <!-- greek capital letter mu, U+039C -->
+<!ENTITY Nu "&#925;"> <!-- greek capital letter nu, U+039D -->
+<!ENTITY Xi "&#926;"> <!-- greek capital letter xi, U+039E ISOgrk3 -->
+<!ENTITY Omicron "&#927;"> <!-- greek capital letter omicron, U+039F -->
+<!ENTITY Pi "&#928;"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->
+<!ENTITY Rho "&#929;"> <!-- greek capital letter rho, U+03A1 -->
+<!-- there is no Sigmaf, and no U+03A2 character either -->
+<!ENTITY Sigma "&#931;"> <!-- greek capital letter sigma,
+ U+03A3 ISOgrk3 -->
+<!ENTITY Tau "&#932;"> <!-- greek capital letter tau, U+03A4 -->
+<!ENTITY Upsilon "&#933;"> <!-- greek capital letter upsilon,
+ U+03A5 ISOgrk3 -->
+<!ENTITY Phi "&#934;"> <!-- greek capital letter phi,
+ U+03A6 ISOgrk3 -->
+<!ENTITY Chi "&#935;"> <!-- greek capital letter chi, U+03A7 -->
+<!ENTITY Psi "&#936;"> <!-- greek capital letter psi,
+ U+03A8 ISOgrk3 -->
+<!ENTITY Omega "&#937;"> <!-- greek capital letter omega,
+ U+03A9 ISOgrk3 -->
+
+<!ENTITY alpha "&#945;"> <!-- greek small letter alpha,
+ U+03B1 ISOgrk3 -->
+<!ENTITY beta "&#946;"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->
+<!ENTITY gamma "&#947;"> <!-- greek small letter gamma,
+ U+03B3 ISOgrk3 -->
+<!ENTITY delta "&#948;"> <!-- greek small letter delta,
+ U+03B4 ISOgrk3 -->
+<!ENTITY epsilon "&#949;"> <!-- greek small letter epsilon,
+ U+03B5 ISOgrk3 -->
+<!ENTITY zeta "&#950;"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->
+<!ENTITY eta "&#951;"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->
+<!ENTITY theta "&#952;"> <!-- greek small letter theta,
+ U+03B8 ISOgrk3 -->
+<!ENTITY iota "&#953;"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->
+<!ENTITY kappa "&#954;"> <!-- greek small letter kappa,
+ U+03BA ISOgrk3 -->
+<!ENTITY lambda "&#955;"> <!-- greek small letter lamda,
+ U+03BB ISOgrk3 -->
+<!ENTITY mu "&#956;"> <!-- greek small letter mu, U+03BC ISOgrk3 -->
+<!ENTITY nu "&#957;"> <!-- greek small letter nu, U+03BD ISOgrk3 -->
+<!ENTITY xi "&#958;"> <!-- greek small letter xi, U+03BE ISOgrk3 -->
+<!ENTITY omicron "&#959;"> <!-- greek small letter omicron, U+03BF NEW -->
+<!ENTITY pi "&#960;"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->
+<!ENTITY rho "&#961;"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->
+<!ENTITY sigmaf "&#962;"> <!-- greek small letter final sigma,
+ U+03C2 ISOgrk3 -->
+<!ENTITY sigma "&#963;"> <!-- greek small letter sigma,
+ U+03C3 ISOgrk3 -->
+<!ENTITY tau "&#964;"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->
+<!ENTITY upsilon "&#965;"> <!-- greek small letter upsilon,
+ U+03C5 ISOgrk3 -->
+<!ENTITY phi "&#966;"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->
+<!ENTITY chi "&#967;"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->
+<!ENTITY psi "&#968;"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->
+<!ENTITY omega "&#969;"> <!-- greek small letter omega,
+ U+03C9 ISOgrk3 -->
+<!ENTITY thetasym "&#977;"> <!-- greek theta symbol,
+ U+03D1 NEW -->
+<!ENTITY upsih "&#978;"> <!-- greek upsilon with hook symbol,
+ U+03D2 NEW -->
+<!ENTITY piv "&#982;"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->
+
+<!-- General Punctuation -->
+<!ENTITY bull "&#8226;"> <!-- bullet = black small circle,
+ U+2022 ISOpub -->
+<!-- bullet is NOT the same as bullet operator, U+2219 -->
+<!ENTITY hellip "&#8230;"> <!-- horizontal ellipsis = three dot leader,
+ U+2026 ISOpub -->
+<!ENTITY prime "&#8242;"> <!-- prime = minutes = feet, U+2032 ISOtech -->
+<!ENTITY Prime "&#8243;"> <!-- double prime = seconds = inches,
+ U+2033 ISOtech -->
+<!ENTITY oline "&#8254;"> <!-- overline = spacing overscore,
+ U+203E NEW -->
+<!ENTITY frasl "&#8260;"> <!-- fraction slash, U+2044 NEW -->
+
+<!-- Letterlike Symbols -->
+<!ENTITY weierp "&#8472;"> <!-- script capital P = power set
+ = Weierstrass p, U+2118 ISOamso -->
+<!ENTITY image "&#8465;"> <!-- black-letter capital I = imaginary part,
+ U+2111 ISOamso -->
+<!ENTITY real "&#8476;"> <!-- black-letter capital R = real part symbol,
+ U+211C ISOamso -->
+<!ENTITY trade "&#8482;"> <!-- trade mark sign, U+2122 ISOnum -->
+<!ENTITY alefsym "&#8501;"> <!-- alef symbol = first transfinite cardinal,
+ U+2135 NEW -->
+<!-- alef symbol is NOT the same as hebrew letter alef,
+ U+05D0 although the same glyph could be used to depict both characters -->
+
+<!-- Arrows -->
+<!ENTITY larr "&#8592;"> <!-- leftwards arrow, U+2190 ISOnum -->
+<!ENTITY uarr "&#8593;"> <!-- upwards arrow, U+2191 ISOnum-->
+<!ENTITY rarr "&#8594;"> <!-- rightwards arrow, U+2192 ISOnum -->
+<!ENTITY darr "&#8595;"> <!-- downwards arrow, U+2193 ISOnum -->
+<!ENTITY harr "&#8596;"> <!-- left right arrow, U+2194 ISOamsa -->
+<!ENTITY crarr "&#8629;"> <!-- downwards arrow with corner leftwards
+ = carriage return, U+21B5 NEW -->
+<!ENTITY lArr "&#8656;"> <!-- leftwards double arrow, U+21D0 ISOtech -->
+<!-- Unicode does not say that lArr is the same as the 'is implied by' arrow
+ but also does not have any other character for that function. So lArr can
+ be used for 'is implied by' as ISOtech suggests -->
+<!ENTITY uArr "&#8657;"> <!-- upwards double arrow, U+21D1 ISOamsa -->
+<!ENTITY rArr "&#8658;"> <!-- rightwards double arrow,
+ U+21D2 ISOtech -->
+<!-- Unicode does not say this is the 'implies' character but does not have
+ another character with this function so rArr can be used for 'implies'
+ as ISOtech suggests -->
+<!ENTITY dArr "&#8659;"> <!-- downwards double arrow, U+21D3 ISOamsa -->
+<!ENTITY hArr "&#8660;"> <!-- left right double arrow,
+ U+21D4 ISOamsa -->
+
+<!-- Mathematical Operators -->
+<!ENTITY forall "&#8704;"> <!-- for all, U+2200 ISOtech -->
+<!ENTITY part "&#8706;"> <!-- partial differential, U+2202 ISOtech -->
+<!ENTITY exist "&#8707;"> <!-- there exists, U+2203 ISOtech -->
+<!ENTITY empty "&#8709;"> <!-- empty set = null set, U+2205 ISOamso -->
+<!ENTITY nabla "&#8711;"> <!-- nabla = backward difference,
+ U+2207 ISOtech -->
+<!ENTITY isin "&#8712;"> <!-- element of, U+2208 ISOtech -->
+<!ENTITY notin "&#8713;"> <!-- not an element of, U+2209 ISOtech -->
+<!ENTITY ni "&#8715;"> <!-- contains as member, U+220B ISOtech -->
+<!ENTITY prod "&#8719;"> <!-- n-ary product = product sign,
+ U+220F ISOamsb -->
+<!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though
+ the same glyph might be used for both -->
+<!ENTITY sum "&#8721;"> <!-- n-ary summation, U+2211 ISOamsb -->
+<!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+ though the same glyph might be used for both -->
+<!ENTITY minus "&#8722;"> <!-- minus sign, U+2212 ISOtech -->
+<!ENTITY lowast "&#8727;"> <!-- asterisk operator, U+2217 ISOtech -->
+<!ENTITY radic "&#8730;"> <!-- square root = radical sign,
+ U+221A ISOtech -->
+<!ENTITY prop "&#8733;"> <!-- proportional to, U+221D ISOtech -->
+<!ENTITY infin "&#8734;"> <!-- infinity, U+221E ISOtech -->
+<!ENTITY ang "&#8736;"> <!-- angle, U+2220 ISOamso -->
+<!ENTITY and "&#8743;"> <!-- logical and = wedge, U+2227 ISOtech -->
+<!ENTITY or "&#8744;"> <!-- logical or = vee, U+2228 ISOtech -->
+<!ENTITY cap "&#8745;"> <!-- intersection = cap, U+2229 ISOtech -->
+<!ENTITY cup "&#8746;"> <!-- union = cup, U+222A ISOtech -->
+<!ENTITY int "&#8747;"> <!-- integral, U+222B ISOtech -->
+<!ENTITY there4 "&#8756;"> <!-- therefore, U+2234 ISOtech -->
+<!ENTITY sim "&#8764;"> <!-- tilde operator = varies with = similar to,
+ U+223C ISOtech -->
+<!-- tilde operator is NOT the same character as the tilde, U+007E,
+ although the same glyph might be used to represent both -->
+<!ENTITY cong "&#8773;"> <!-- approximately equal to, U+2245 ISOtech -->
+<!ENTITY asymp "&#8776;"> <!-- almost equal to = asymptotic to,
+ U+2248 ISOamsr -->
+<!ENTITY ne "&#8800;"> <!-- not equal to, U+2260 ISOtech -->
+<!ENTITY equiv "&#8801;"> <!-- identical to, U+2261 ISOtech -->
+<!ENTITY le "&#8804;"> <!-- less-than or equal to, U+2264 ISOtech -->
+<!ENTITY ge "&#8805;"> <!-- greater-than or equal to,
+ U+2265 ISOtech -->
+<!ENTITY sub "&#8834;"> <!-- subset of, U+2282 ISOtech -->
+<!ENTITY sup "&#8835;"> <!-- superset of, U+2283 ISOtech -->
+<!ENTITY nsub "&#8836;"> <!-- not a subset of, U+2284 ISOamsn -->
+<!ENTITY sube "&#8838;"> <!-- subset of or equal to, U+2286 ISOtech -->
+<!ENTITY supe "&#8839;"> <!-- superset of or equal to,
+ U+2287 ISOtech -->
+<!ENTITY oplus "&#8853;"> <!-- circled plus = direct sum,
+ U+2295 ISOamsb -->
+<!ENTITY otimes "&#8855;"> <!-- circled times = vector product,
+ U+2297 ISOamsb -->
+<!ENTITY perp "&#8869;"> <!-- up tack = orthogonal to = perpendicular,
+ U+22A5 ISOtech -->
+<!ENTITY sdot "&#8901;"> <!-- dot operator, U+22C5 ISOamsb -->
+<!-- dot operator is NOT the same character as U+00B7 middle dot -->
+
+<!-- Miscellaneous Technical -->
+<!ENTITY lceil "&#8968;"> <!-- left ceiling = APL upstile,
+ U+2308 ISOamsc -->
+<!ENTITY rceil "&#8969;"> <!-- right ceiling, U+2309 ISOamsc -->
+<!ENTITY lfloor "&#8970;"> <!-- left floor = APL downstile,
+ U+230A ISOamsc -->
+<!ENTITY rfloor "&#8971;"> <!-- right floor, U+230B ISOamsc -->
+<!ENTITY lang "&#9001;"> <!-- left-pointing angle bracket = bra,
+ U+2329 ISOtech -->
+<!-- lang is NOT the same character as U+003C 'less than sign'
+ or U+2039 'single left-pointing angle quotation mark' -->
+<!ENTITY rang "&#9002;"> <!-- right-pointing angle bracket = ket,
+ U+232A ISOtech -->
+<!-- rang is NOT the same character as U+003E 'greater than sign'
+ or U+203A 'single right-pointing angle quotation mark' -->
+
+<!-- Geometric Shapes -->
+<!ENTITY loz "&#9674;"> <!-- lozenge, U+25CA ISOpub -->
+
+<!-- Miscellaneous Symbols -->
+<!ENTITY spades "&#9824;"> <!-- black spade suit, U+2660 ISOpub -->
+<!-- black here seems to mean filled as opposed to hollow -->
+<!ENTITY clubs "&#9827;"> <!-- black club suit = shamrock,
+ U+2663 ISOpub -->
+<!ENTITY hearts "&#9829;"> <!-- black heart suit = valentine,
+ U+2665 ISOpub -->
+<!ENTITY diams "&#9830;"> <!-- black diamond suit, U+2666 ISOpub -->
diff --git a/vendor/Twisted-10.0.0/twisted/lore/xhtml1-strict.dtd b/vendor/Twisted-10.0.0/twisted/lore/xhtml1-strict.dtd
new file mode 100644
index 0000000000..2927b9ece7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/xhtml1-strict.dtd
@@ -0,0 +1,978 @@
+<!--
+ Extensible HTML version 1.0 Strict DTD
+
+ This is the same as HTML 4 Strict except for
+ changes due to the differences between XML and SGML.
+
+ Namespace = http://www.w3.org/1999/xhtml
+
+ For further information, see: http://www.w3.org/TR/xhtml1
+
+ Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
+ All Rights Reserved.
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+
+ $Revision: 1.1 $
+ $Date: 2002/08/01 13:56:03 $
+
+-->
+
+<!--================ Character mnemonic entities =========================-->
+
+<!ENTITY % HTMLlat1 PUBLIC
+ "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+ "xhtml-lat1.ent">
+%HTMLlat1;
+
+<!ENTITY % HTMLsymbol PUBLIC
+ "-//W3C//ENTITIES Symbols for XHTML//EN"
+ "xhtml-symbol.ent">
+%HTMLsymbol;
+
+<!ENTITY % HTMLspecial PUBLIC
+ "-//W3C//ENTITIES Special for XHTML//EN"
+ "xhtml-special.ent">
+%HTMLspecial;
+
+<!--================== Imported Names ====================================-->
+
+<!ENTITY % ContentType "CDATA">
+ <!-- media type, as per [RFC2045] -->
+
+<!ENTITY % ContentTypes "CDATA">
+ <!-- comma-separated list of media types, as per [RFC2045] -->
+
+<!ENTITY % Charset "CDATA">
+ <!-- a character encoding, as per [RFC2045] -->
+
+<!ENTITY % Charsets "CDATA">
+ <!-- a space separated list of character encodings, as per [RFC2045] -->
+
+<!ENTITY % LanguageCode "NMTOKEN">
+ <!-- a language code, as per [RFC3066] -->
+
+<!ENTITY % Character "CDATA">
+ <!-- a single character, as per section 2.2 of [XML] -->
+
+<!ENTITY % Number "CDATA">
+ <!-- one or more digits -->
+
+<!ENTITY % LinkTypes "CDATA">
+ <!-- space-separated list of link types -->
+
+<!ENTITY % MediaDesc "CDATA">
+ <!-- single or comma-separated list of media descriptors -->
+
+<!ENTITY % URI "CDATA">
+ <!-- a Uniform Resource Identifier, see [RFC2396] -->
+
+<!ENTITY % UriList "CDATA">
+ <!-- a space separated list of Uniform Resource Identifiers -->
+
+<!ENTITY % Datetime "CDATA">
+ <!-- date and time information. ISO date format -->
+
+<!ENTITY % Script "CDATA">
+ <!-- script expression -->
+
+<!ENTITY % StyleSheet "CDATA">
+ <!-- style sheet data -->
+
+<!ENTITY % Text "CDATA">
+ <!-- used for titles etc. -->
+
+<!ENTITY % Length "CDATA">
+ <!-- nn for pixels or nn% for percentage length -->
+
+<!ENTITY % MultiLength "CDATA">
+ <!-- pixel, percentage, or relative -->
+
+<!ENTITY % Pixels "CDATA">
+ <!-- integer representing length in pixels -->
+
+<!-- these are used for image maps -->
+
+<!ENTITY % Shape "(rect|circle|poly|default)">
+
+<!ENTITY % Coords "CDATA">
+ <!-- comma separated list of lengths -->
+
+<!--=================== Generic Attributes ===============================-->
+
+<!-- core attributes common to most elements
+ id document-wide unique id
+ class space separated list of classes
+ style associated style info
+ title advisory title/amplification
+-->
+<!ENTITY % coreattrs
+ "id ID #IMPLIED
+ class CDATA #IMPLIED
+ style %StyleSheet; #IMPLIED
+ title %Text; #IMPLIED"
+ >
+
+<!-- internationalization attributes
+ lang language code (backwards compatible)
+ xml:lang language code (as per XML 1.0 spec)
+ dir direction for weak/neutral text
+-->
+<!ENTITY % i18n
+ "lang %LanguageCode; #IMPLIED
+ xml:lang %LanguageCode; #IMPLIED
+ dir (ltr|rtl) #IMPLIED"
+ >
+
+<!-- attributes for common UI events
+ onclick a pointer button was clicked
+ ondblclick a pointer button was double clicked
+ onmousedown a pointer button was pressed down
+ onmouseup a pointer button was released
+ onmousemove a pointer was moved onto the element
+ onmouseout a pointer was moved away from the element
+ onkeypress a key was pressed and released
+ onkeydown a key was pressed down
+ onkeyup a key was released
+-->
+<!ENTITY % events
+ "onclick %Script; #IMPLIED
+ ondblclick %Script; #IMPLIED
+ onmousedown %Script; #IMPLIED
+ onmouseup %Script; #IMPLIED
+ onmouseover %Script; #IMPLIED
+ onmousemove %Script; #IMPLIED
+ onmouseout %Script; #IMPLIED
+ onkeypress %Script; #IMPLIED
+ onkeydown %Script; #IMPLIED
+ onkeyup %Script; #IMPLIED"
+ >
+
+<!-- attributes for elements that can get the focus
+ accesskey accessibility key character
+ tabindex position in tabbing order
+ onfocus the element got the focus
+ onblur the element lost the focus
+-->
+<!ENTITY % focus
+ "accesskey %Character; #IMPLIED
+ tabindex %Number; #IMPLIED
+ onfocus %Script; #IMPLIED
+ onblur %Script; #IMPLIED"
+ >
+
+<!ENTITY % attrs "%coreattrs; %i18n; %events;">
+
+<!--=================== Text Elements ====================================-->
+
+<!ENTITY % special.pre
+ "br | span | bdo | map">
+
+
+<!ENTITY % special
+ "%special.pre; | object | img ">
+
+<!ENTITY % fontstyle "tt | i | b | big | small ">
+
+<!ENTITY % phrase "em | strong | dfn | code | q |
+ samp | kbd | var | cite | abbr | acronym | sub | sup ">
+
+<!ENTITY % inline.forms "input | select | textarea | label | button">
+
+<!-- these can occur at block or inline level -->
+<!ENTITY % misc.inline "ins | del | script">
+
+<!-- these can only occur at block level -->
+<!ENTITY % misc "noscript | %misc.inline;">
+
+<!ENTITY % inline "a | %special; | %fontstyle; | %phrase; | %inline.forms;">
+
+<!-- %Inline; covers inline or "text-level" elements -->
+<!ENTITY % Inline "(#PCDATA | %inline; | %misc.inline;)*">
+
+<!--================== Block level elements ==============================-->
+
+<!ENTITY % heading "h1|h2|h3|h4|h5|h6">
+<!ENTITY % lists "ul | ol | dl">
+<!ENTITY % blocktext "pre | hr | blockquote | address">
+
+<!ENTITY % block
+ "p | %heading; | div | %lists; | %blocktext; | fieldset | table">
+
+<!ENTITY % Block "(%block; | form | %misc;)*">
+
+<!-- %Flow; mixes block and inline and is used for list items etc. -->
+<!ENTITY % Flow "(#PCDATA | %block; | form | %inline; | %misc;)*">
+
+<!--================== Content models for exclusions =====================-->
+
+<!-- a elements use %Inline; excluding a -->
+
+<!ENTITY % a.content
+ "(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*">
+
+<!-- pre uses %Inline excluding big, small, sup or sup -->
+
+<!ENTITY % pre.content
+ "(#PCDATA | a | %fontstyle; | %phrase; | %special.pre; | %misc.inline;
+ | %inline.forms;)*">
+
+<!-- form uses %Block; excluding form -->
+
+<!ENTITY % form.content "(%block; | %misc;)*">
+
+<!-- button uses %Flow; but excludes a, form and form controls -->
+
+<!ENTITY % button.content
+ "(#PCDATA | p | %heading; | div | %lists; | %blocktext; |
+ table | %special; | %fontstyle; | %phrase; | %misc;)*">
+
+<!--================ Document Structure ==================================-->
+
+<!-- the namespace URI designates the document profile -->
+
+<!ELEMENT html (head, body)>
+<!ATTLIST html
+ %i18n;
+ id ID #IMPLIED
+ xmlns %URI; #FIXED 'http://www.w3.org/1999/xhtml'
+ >
+
+<!--================ Document Head =======================================-->
+
+<!ENTITY % head.misc "(script|style|meta|link|object)*">
+
+<!-- content model is %head.misc; combined with a single
+ title and an optional base element in any order -->
+
+<!ELEMENT head (%head.misc;,
+ ((title, %head.misc;, (base, %head.misc;)?) |
+ (base, %head.misc;, (title, %head.misc;))))>
+
+<!ATTLIST head
+ %i18n;
+ id ID #IMPLIED
+ profile %URI; #IMPLIED
+ >
+
+<!-- The title element is not considered part of the flow of text.
+ It should be displayed, for example as the page header or
+ window title. Exactly one title is required per document.
+ -->
+<!ELEMENT title (#PCDATA)>
+<!ATTLIST title
+ %i18n;
+ id ID #IMPLIED
+ >
+
+<!-- document base URI -->
+
+<!ELEMENT base EMPTY>
+<!ATTLIST base
+ href %URI; #REQUIRED
+ id ID #IMPLIED
+ >
+
+<!-- generic metainformation -->
+<!ELEMENT meta EMPTY>
+<!ATTLIST meta
+ %i18n;
+ id ID #IMPLIED
+ http-equiv CDATA #IMPLIED
+ name CDATA #IMPLIED
+ content CDATA #REQUIRED
+ scheme CDATA #IMPLIED
+ >
+
+<!--
+ Relationship values can be used in principle:
+
+ a) for document specific toolbars/menus when used
+ with the link element in document head e.g.
+ start, contents, previous, next, index, end, help
+ b) to link to a separate style sheet (rel="stylesheet")
+ c) to make a link to a script (rel="script")
+ d) by stylesheets to control how collections of
+ html nodes are rendered into printed documents
+ e) to make a link to a printable version of this document
+ e.g. a PostScript or PDF version (rel="alternate" media="print")
+-->
+
+<!ELEMENT link EMPTY>
+<!ATTLIST link
+ %attrs;
+ charset %Charset; #IMPLIED
+ href %URI; #IMPLIED
+ hreflang %LanguageCode; #IMPLIED
+ type %ContentType; #IMPLIED
+ rel %LinkTypes; #IMPLIED
+ rev %LinkTypes; #IMPLIED
+ media %MediaDesc; #IMPLIED
+ >
+
+<!-- style info, which may include CDATA sections -->
+<!ELEMENT style (#PCDATA)>
+<!ATTLIST style
+ %i18n;
+ id ID #IMPLIED
+ type %ContentType; #REQUIRED
+ media %MediaDesc; #IMPLIED
+ title %Text; #IMPLIED
+ xml:space (preserve) #FIXED 'preserve'
+ >
+
+<!-- script statements, which may include CDATA sections -->
+<!ELEMENT script (#PCDATA)>
+<!ATTLIST script
+ id ID #IMPLIED
+ charset %Charset; #IMPLIED
+ type %ContentType; #REQUIRED
+ src %URI; #IMPLIED
+ defer (defer) #IMPLIED
+ xml:space (preserve) #FIXED 'preserve'
+ >
+
+<!-- alternate content container for non script-based rendering -->
+
+<!ELEMENT noscript %Block;>
+<!ATTLIST noscript
+ %attrs;
+ >
+
+<!--=================== Document Body ====================================-->
+
+<!ELEMENT body %Block;>
+<!ATTLIST body
+ %attrs;
+ onload %Script; #IMPLIED
+ onunload %Script; #IMPLIED
+ >
+
+<!ELEMENT div %Flow;> <!-- generic language/style container -->
+<!ATTLIST div
+ %attrs;
+ >
+
+<!--=================== Paragraphs =======================================-->
+
+<!ELEMENT p %Inline;>
+<!ATTLIST p
+ %attrs;
+ >
+
+<!--=================== Headings =========================================-->
+
+<!--
+ There are six levels of headings from h1 (the most important)
+ to h6 (the least important).
+-->
+
+<!ELEMENT h1 %Inline;>
+<!ATTLIST h1
+ %attrs;
+ >
+
+<!ELEMENT h2 %Inline;>
+<!ATTLIST h2
+ %attrs;
+ >
+
+<!ELEMENT h3 %Inline;>
+<!ATTLIST h3
+ %attrs;
+ >
+
+<!ELEMENT h4 %Inline;>
+<!ATTLIST h4
+ %attrs;
+ >
+
+<!ELEMENT h5 %Inline;>
+<!ATTLIST h5
+ %attrs;
+ >
+
+<!ELEMENT h6 %Inline;>
+<!ATTLIST h6
+ %attrs;
+ >
+
+<!--=================== Lists ============================================-->
+
+<!-- Unordered list -->
+
+<!ELEMENT ul (li)+>
+<!ATTLIST ul
+ %attrs;
+ >
+
+<!-- Ordered (numbered) list -->
+
+<!ELEMENT ol (li)+>
+<!ATTLIST ol
+ %attrs;
+ >
+
+<!-- list item -->
+
+<!ELEMENT li %Flow;>
+<!ATTLIST li
+ %attrs;
+ >
+
+<!-- definition lists - dt for term, dd for its definition -->
+
+<!ELEMENT dl (dt|dd)+>
+<!ATTLIST dl
+ %attrs;
+ >
+
+<!ELEMENT dt %Inline;>
+<!ATTLIST dt
+ %attrs;
+ >
+
+<!ELEMENT dd %Flow;>
+<!ATTLIST dd
+ %attrs;
+ >
+
+<!--=================== Address ==========================================-->
+
+<!-- information on author -->
+
+<!ELEMENT address %Inline;>
+<!ATTLIST address
+ %attrs;
+ >
+
+<!--=================== Horizontal Rule ==================================-->
+
+<!ELEMENT hr EMPTY>
+<!ATTLIST hr
+ %attrs;
+ >
+
+<!--=================== Preformatted Text ================================-->
+
+<!-- content is %Inline; excluding "img|object|big|small|sub|sup" -->
+
+<!ELEMENT pre %pre.content;>
+<!ATTLIST pre
+ %attrs;
+ xml:space (preserve) #FIXED 'preserve'
+ >
+
+<!--=================== Block-like Quotes ================================-->
+
+<!ELEMENT blockquote %Block;>
+<!ATTLIST blockquote
+ %attrs;
+ cite %URI; #IMPLIED
+ >
+
+<!--=================== Inserted/Deleted Text ============================-->
+
+<!--
+ ins/del are allowed in block and inline content, but its
+ inappropriate to include block content within an ins element
+ occurring in inline content.
+-->
+<!ELEMENT ins %Flow;>
+<!ATTLIST ins
+ %attrs;
+ cite %URI; #IMPLIED
+ datetime %Datetime; #IMPLIED
+ >
+
+<!ELEMENT del %Flow;>
+<!ATTLIST del
+ %attrs;
+ cite %URI; #IMPLIED
+ datetime %Datetime; #IMPLIED
+ >
+
+<!--================== The Anchor Element ================================-->
+
+<!-- content is %Inline; except that anchors shouldn't be nested -->
+
+<!ELEMENT a %a.content;>
+<!ATTLIST a
+ %attrs;
+ %focus;
+ charset %Charset; #IMPLIED
+ type %ContentType; #IMPLIED
+ name NMTOKEN #IMPLIED
+ href %URI; #IMPLIED
+ hreflang %LanguageCode; #IMPLIED
+ rel %LinkTypes; #IMPLIED
+ rev %LinkTypes; #IMPLIED
+ shape %Shape; "rect"
+ coords %Coords; #IMPLIED
+ >
+
+<!--===================== Inline Elements ================================-->
+
+<!ELEMENT span %Inline;> <!-- generic language/style container -->
+<!ATTLIST span
+ %attrs;
+ >
+
+<!ELEMENT bdo %Inline;> <!-- I18N BiDi over-ride -->
+<!ATTLIST bdo
+ %coreattrs;
+ %events;
+ lang %LanguageCode; #IMPLIED
+ xml:lang %LanguageCode; #IMPLIED
+ dir (ltr|rtl) #REQUIRED
+ >
+
+<!ELEMENT br EMPTY> <!-- forced line break -->
+<!ATTLIST br
+ %coreattrs;
+ >
+
+<!ELEMENT em %Inline;> <!-- emphasis -->
+<!ATTLIST em %attrs;>
+
+<!ELEMENT strong %Inline;> <!-- strong emphasis -->
+<!ATTLIST strong %attrs;>
+
+<!ELEMENT dfn %Inline;> <!-- definitional -->
+<!ATTLIST dfn %attrs;>
+
+<!ELEMENT code %Inline;> <!-- program code -->
+<!ATTLIST code %attrs;>
+
+<!ELEMENT samp %Inline;> <!-- sample -->
+<!ATTLIST samp %attrs;>
+
+<!ELEMENT kbd %Inline;> <!-- something user would type -->
+<!ATTLIST kbd %attrs;>
+
+<!ELEMENT var %Inline;> <!-- variable -->
+<!ATTLIST var %attrs;>
+
+<!ELEMENT cite %Inline;> <!-- citation -->
+<!ATTLIST cite %attrs;>
+
+<!ELEMENT abbr %Inline;> <!-- abbreviation -->
+<!ATTLIST abbr %attrs;>
+
+<!ELEMENT acronym %Inline;> <!-- acronym -->
+<!ATTLIST acronym %attrs;>
+
+<!ELEMENT q %Inline;> <!-- inlined quote -->
+<!ATTLIST q
+ %attrs;
+ cite %URI; #IMPLIED
+ >
+
+<!ELEMENT sub %Inline;> <!-- subscript -->
+<!ATTLIST sub %attrs;>
+
+<!ELEMENT sup %Inline;> <!-- superscript -->
+<!ATTLIST sup %attrs;>
+
+<!ELEMENT tt %Inline;> <!-- fixed pitch font -->
+<!ATTLIST tt %attrs;>
+
+<!ELEMENT i %Inline;> <!-- italic font -->
+<!ATTLIST i %attrs;>
+
+<!ELEMENT b %Inline;> <!-- bold font -->
+<!ATTLIST b %attrs;>
+
+<!ELEMENT big %Inline;> <!-- bigger font -->
+<!ATTLIST big %attrs;>
+
+<!ELEMENT small %Inline;> <!-- smaller font -->
+<!ATTLIST small %attrs;>
+
+<!--==================== Object ======================================-->
+<!--
+ object is used to embed objects as part of HTML pages.
+ param elements should precede other content. Parameters
+ can also be expressed as attribute/value pairs on the
+ object element itself when brevity is desired.
+-->
+
+<!ELEMENT object (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST object
+ %attrs;
+ declare (declare) #IMPLIED
+ classid %URI; #IMPLIED
+ codebase %URI; #IMPLIED
+ data %URI; #IMPLIED
+ type %ContentType; #IMPLIED
+ codetype %ContentType; #IMPLIED
+ archive %UriList; #IMPLIED
+ standby %Text; #IMPLIED
+ height %Length; #IMPLIED
+ width %Length; #IMPLIED
+ usemap %URI; #IMPLIED
+ name NMTOKEN #IMPLIED
+ tabindex %Number; #IMPLIED
+ >
+
+<!--
+ param is used to supply a named property value.
+ In XML it would seem natural to follow RDF and support an
+ abbreviated syntax where the param elements are replaced
+ by attribute value pairs on the object start tag.
+-->
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+ id ID #IMPLIED
+ name CDATA #IMPLIED
+ value CDATA #IMPLIED
+ valuetype (data|ref|object) "data"
+ type %ContentType; #IMPLIED
+ >
+
+<!--=================== Images ===========================================-->
+
+<!--
+ To avoid accessibility problems for people who aren't
+ able to see the image, you should provide a text
+ description using the alt and longdesc attributes.
+ In addition, avoid the use of server-side image maps.
+ Note that in this DTD there is no name attribute. That
+ is only available in the transitional and frameset DTD.
+-->
+
+<!ELEMENT img EMPTY>
+<!ATTLIST img
+ %attrs;
+ src %URI; #REQUIRED
+ alt %Text; #REQUIRED
+ longdesc %URI; #IMPLIED
+ height %Length; #IMPLIED
+ width %Length; #IMPLIED
+ usemap %URI; #IMPLIED
+ ismap (ismap) #IMPLIED
+ >
+
+<!-- usemap points to a map element which may be in this document
+ or an external document, although the latter is not widely supported -->
+
+<!--================== Client-side image maps ============================-->
+
+<!-- These can be placed in the same document or grouped in a
+ separate document although this isn't yet widely supported -->
+
+<!ELEMENT map ((%block; | form | %misc;)+ | area+)>
+<!ATTLIST map
+ %i18n;
+ %events;
+ id ID #REQUIRED
+ class CDATA #IMPLIED
+ style %StyleSheet; #IMPLIED
+ title %Text; #IMPLIED
+ name NMTOKEN #IMPLIED
+ >
+
+<!ELEMENT area EMPTY>
+<!ATTLIST area
+ %attrs;
+ %focus;
+ shape %Shape; "rect"
+ coords %Coords; #IMPLIED
+ href %URI; #IMPLIED
+ nohref (nohref) #IMPLIED
+ alt %Text; #REQUIRED
+ >
+
+<!--================ Forms ===============================================-->
+<!ELEMENT form %form.content;> <!-- forms shouldn't be nested -->
+
+<!ATTLIST form
+ %attrs;
+ action %URI; #REQUIRED
+ method (get|post) "get"
+ enctype %ContentType; "application/x-www-form-urlencoded"
+ onsubmit %Script; #IMPLIED
+ onreset %Script; #IMPLIED
+ accept %ContentTypes; #IMPLIED
+ accept-charset %Charsets; #IMPLIED
+ >
+
+<!--
+ Each label must not contain more than ONE field
+ Label elements shouldn't be nested.
+-->
+<!ELEMENT label %Inline;>
+<!ATTLIST label
+ %attrs;
+ for IDREF #IMPLIED
+ accesskey %Character; #IMPLIED
+ onfocus %Script; #IMPLIED
+ onblur %Script; #IMPLIED
+ >
+
+<!ENTITY % InputType
+ "(text | password | checkbox |
+ radio | submit | reset |
+ file | hidden | image | button)"
+ >
+
+<!-- the name attribute is required for all but submit & reset -->
+
+<!ELEMENT input EMPTY> <!-- form control -->
+<!ATTLIST input
+ %attrs;
+ %focus;
+ type %InputType; "text"
+ name CDATA #IMPLIED
+ value CDATA #IMPLIED
+ checked (checked) #IMPLIED
+ disabled (disabled) #IMPLIED
+ readonly (readonly) #IMPLIED
+ size CDATA #IMPLIED
+ maxlength %Number; #IMPLIED
+ src %URI; #IMPLIED
+ alt CDATA #IMPLIED
+ usemap %URI; #IMPLIED
+ onselect %Script; #IMPLIED
+ onchange %Script; #IMPLIED
+ accept %ContentTypes; #IMPLIED
+ >
+
+<!ELEMENT select (optgroup|option)+> <!-- option selector -->
+<!ATTLIST select
+ %attrs;
+ name CDATA #IMPLIED
+ size %Number; #IMPLIED
+ multiple (multiple) #IMPLIED
+ disabled (disabled) #IMPLIED
+ tabindex %Number; #IMPLIED
+ onfocus %Script; #IMPLIED
+ onblur %Script; #IMPLIED
+ onchange %Script; #IMPLIED
+ >
+
+<!ELEMENT optgroup (option)+> <!-- option group -->
+<!ATTLIST optgroup
+ %attrs;
+ disabled (disabled) #IMPLIED
+ label %Text; #REQUIRED
+ >
+
+<!ELEMENT option (#PCDATA)> <!-- selectable choice -->
+<!ATTLIST option
+ %attrs;
+ selected (selected) #IMPLIED
+ disabled (disabled) #IMPLIED
+ label %Text; #IMPLIED
+ value CDATA #IMPLIED
+ >
+
+<!ELEMENT textarea (#PCDATA)> <!-- multi-line text field -->
+<!ATTLIST textarea
+ %attrs;
+ %focus;
+ name CDATA #IMPLIED
+ rows %Number; #REQUIRED
+ cols %Number; #REQUIRED
+ disabled (disabled) #IMPLIED
+ readonly (readonly) #IMPLIED
+ onselect %Script; #IMPLIED
+ onchange %Script; #IMPLIED
+ >
+
+<!--
+ The fieldset element is used to group form fields.
+ Only one legend element should occur in the content
+ and if present should only be preceded by whitespace.
+-->
+<!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*>
+<!ATTLIST fieldset
+ %attrs;
+ >
+
+<!ELEMENT legend %Inline;> <!-- fieldset label -->
+<!ATTLIST legend
+ %attrs;
+ accesskey %Character; #IMPLIED
+ >
+
+<!--
+ Content is %Flow; excluding a, form and form controls
+-->
+<!ELEMENT button %button.content;> <!-- push button -->
+<!ATTLIST button
+ %attrs;
+ %focus;
+ name CDATA #IMPLIED
+ value CDATA #IMPLIED
+ type (button|submit|reset) "submit"
+ disabled (disabled) #IMPLIED
+ >
+
+<!--======================= Tables =======================================-->
+
+<!-- Derived from IETF HTML table standard, see [RFC1942] -->
+
+<!--
+ The border attribute sets the thickness of the frame around the
+ table. The default units are screen pixels.
+
+ The frame attribute specifies which parts of the frame around
+ the table should be rendered. The values are not the same as
+ CALS to avoid a name clash with the valign attribute.
+-->
+<!ENTITY % TFrame "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
+
+<!--
+ The rules attribute defines which rules to draw between cells:
+
+ If rules is absent then assume:
+ "none" if border is absent or border="0" otherwise "all"
+-->
+
+<!ENTITY % TRules "(none | groups | rows | cols | all)">
+
+<!-- horizontal alignment attributes for cell contents
+
+ char alignment char, e.g. char=':'
+ charoff offset for alignment char
+-->
+<!ENTITY % cellhalign
+ "align (left|center|right|justify|char) #IMPLIED
+ char %Character; #IMPLIED
+ charoff %Length; #IMPLIED"
+ >
+
+<!-- vertical alignment attributes for cell contents -->
+<!ENTITY % cellvalign
+ "valign (top|middle|bottom|baseline) #IMPLIED"
+ >
+
+<!ELEMENT table
+ (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
+<!ELEMENT caption %Inline;>
+<!ELEMENT thead (tr)+>
+<!ELEMENT tfoot (tr)+>
+<!ELEMENT tbody (tr)+>
+<!ELEMENT colgroup (col)*>
+<!ELEMENT col EMPTY>
+<!ELEMENT tr (th|td)+>
+<!ELEMENT th %Flow;>
+<!ELEMENT td %Flow;>
+
+<!ATTLIST table
+ %attrs;
+ summary %Text; #IMPLIED
+ width %Length; #IMPLIED
+ border %Pixels; #IMPLIED
+ frame %TFrame; #IMPLIED
+ rules %TRules; #IMPLIED
+ cellspacing %Length; #IMPLIED
+ cellpadding %Length; #IMPLIED
+ >
+
+<!ATTLIST caption
+ %attrs;
+ >
+
+<!--
+colgroup groups a set of col elements. It allows you to group
+several semantically related columns together.
+-->
+<!ATTLIST colgroup
+ %attrs;
+ span %Number; "1"
+ width %MultiLength; #IMPLIED
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!--
+ col elements define the alignment properties for cells in
+ one or more columns.
+
+ The width attribute specifies the width of the columns, e.g.
+
+ width=64 width in screen pixels
+ width=0.5* relative width of 0.5
+
+ The span attribute causes the attributes of one
+ col element to apply to more than one column.
+-->
+<!ATTLIST col
+ %attrs;
+ span %Number; "1"
+ width %MultiLength; #IMPLIED
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!--
+ Use thead to duplicate headers when breaking table
+ across page boundaries, or for static headers when
+ tbody sections are rendered in scrolling panel.
+
+ Use tfoot to duplicate footers when breaking table
+ across page boundaries, or for static footers when
+ tbody sections are rendered in scrolling panel.
+
+ Use multiple tbody sections when rules are needed
+ between groups of table rows.
+-->
+<!ATTLIST thead
+ %attrs;
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!ATTLIST tfoot
+ %attrs;
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!ATTLIST tbody
+ %attrs;
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!ATTLIST tr
+ %attrs;
+ %cellhalign;
+ %cellvalign;
+ >
+
+
+<!-- Scope is simpler than headers attribute for common tables -->
+<!ENTITY % Scope "(row|col|rowgroup|colgroup)">
+
+<!-- th is for headers, td for data and for cells acting as both -->
+
+<!ATTLIST th
+ %attrs;
+ abbr %Text; #IMPLIED
+ axis CDATA #IMPLIED
+ headers IDREFS #IMPLIED
+ scope %Scope; #IMPLIED
+ rowspan %Number; "1"
+ colspan %Number; "1"
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!ATTLIST td
+ %attrs;
+ abbr %Text; #IMPLIED
+ axis CDATA #IMPLIED
+ headers IDREFS #IMPLIED
+ scope %Scope; #IMPLIED
+ rowspan %Number; "1"
+ colspan %Number; "1"
+ %cellhalign;
+ %cellvalign;
+ >
+
diff --git a/vendor/Twisted-10.0.0/twisted/lore/xhtml1-transitional.dtd b/vendor/Twisted-10.0.0/twisted/lore/xhtml1-transitional.dtd
new file mode 100644
index 0000000000..628f27ac50
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/lore/xhtml1-transitional.dtd
@@ -0,0 +1,1201 @@
+<!--
+ Extensible HTML version 1.0 Transitional DTD
+
+ This is the same as HTML 4 Transitional except for
+ changes due to the differences between XML and SGML.
+
+ Namespace = http://www.w3.org/1999/xhtml
+
+ For further information, see: http://www.w3.org/TR/xhtml1
+
+ Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
+ All Rights Reserved.
+
+ This DTD module is identified by the PUBLIC and SYSTEM identifiers:
+
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
+
+ $Revision: 1.2 $
+ $Date: 2002/08/01 18:37:55 $
+
+-->
+
+<!--================ Character mnemonic entities =========================-->
+
+<!ENTITY % HTMLlat1 PUBLIC
+ "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+ "xhtml-lat1.ent">
+%HTMLlat1;
+
+<!ENTITY % HTMLsymbol PUBLIC
+ "-//W3C//ENTITIES Symbols for XHTML//EN"
+ "xhtml-symbol.ent">
+%HTMLsymbol;
+
+<!ENTITY % HTMLspecial PUBLIC
+ "-//W3C//ENTITIES Special for XHTML//EN"
+ "xhtml-special.ent">
+%HTMLspecial;
+
+<!--================== Imported Names ====================================-->
+
+<!ENTITY % ContentType "CDATA">
+ <!-- media type, as per [RFC2045] -->
+
+<!ENTITY % ContentTypes "CDATA">
+ <!-- comma-separated list of media types, as per [RFC2045] -->
+
+<!ENTITY % Charset "CDATA">
+ <!-- a character encoding, as per [RFC2045] -->
+
+<!ENTITY % Charsets "CDATA">
+ <!-- a space separated list of character encodings, as per [RFC2045] -->
+
+<!ENTITY % LanguageCode "NMTOKEN">
+ <!-- a language code, as per [RFC3066] -->
+
+<!ENTITY % Character "CDATA">
+ <!-- a single character, as per section 2.2 of [XML] -->
+
+<!ENTITY % Number "CDATA">
+ <!-- one or more digits -->
+
+<!ENTITY % LinkTypes "CDATA">
+ <!-- space-separated list of link types -->
+
+<!ENTITY % MediaDesc "CDATA">
+ <!-- single or comma-separated list of media descriptors -->
+
+<!ENTITY % URI "CDATA">
+ <!-- a Uniform Resource Identifier, see [RFC2396] -->
+
+<!ENTITY % UriList "CDATA">
+ <!-- a space separated list of Uniform Resource Identifiers -->
+
+<!ENTITY % Datetime "CDATA">
+ <!-- date and time information. ISO date format -->
+
+<!ENTITY % Script "CDATA">
+ <!-- script expression -->
+
+<!ENTITY % StyleSheet "CDATA">
+ <!-- style sheet data -->
+
+<!ENTITY % Text "CDATA">
+ <!-- used for titles etc. -->
+
+<!ENTITY % FrameTarget "NMTOKEN">
+ <!-- render in this frame -->
+
+<!ENTITY % Length "CDATA">
+ <!-- nn for pixels or nn% for percentage length -->
+
+<!ENTITY % MultiLength "CDATA">
+ <!-- pixel, percentage, or relative -->
+
+<!ENTITY % Pixels "CDATA">
+ <!-- integer representing length in pixels -->
+
+<!-- these are used for image maps -->
+
+<!ENTITY % Shape "(rect|circle|poly|default)">
+
+<!ENTITY % Coords "CDATA">
+ <!-- comma separated list of lengths -->
+
+<!-- used for object, applet, img, input and iframe -->
+<!ENTITY % ImgAlign "(top|middle|bottom|left|right)">
+
+<!-- a color using sRGB: #RRGGBB as Hex values -->
+<!ENTITY % Color "CDATA">
+
+<!-- There are also 16 widely known color names with their sRGB values:
+
+ Black = #000000 Green = #008000
+ Silver = #C0C0C0 Lime = #00FF00
+ Gray = #808080 Olive = #808000
+ White = #FFFFFF Yellow = #FFFF00
+ Maroon = #800000 Navy = #000080
+ Red = #FF0000 Blue = #0000FF
+ Purple = #800080 Teal = #008080
+ Fuchsia= #FF00FF Aqua = #00FFFF
+-->
+
+<!--=================== Generic Attributes ===============================-->
+
+<!-- core attributes common to most elements
+ id document-wide unique id
+ class space separated list of classes
+ style associated style info
+ title advisory title/amplification
+-->
+<!ENTITY % coreattrs
+ "id ID #IMPLIED
+ class CDATA #IMPLIED
+ style %StyleSheet; #IMPLIED
+ title %Text; #IMPLIED"
+ >
+
+<!-- internationalization attributes
+ lang language code (backwards compatible)
+ xml:lang language code (as per XML 1.0 spec)
+ dir direction for weak/neutral text
+-->
+<!ENTITY % i18n
+ "lang %LanguageCode; #IMPLIED
+ xml:lang %LanguageCode; #IMPLIED
+ dir (ltr|rtl) #IMPLIED"
+ >
+
+<!-- attributes for common UI events
+ onclick a pointer button was clicked
+ ondblclick a pointer button was double clicked
+ onmousedown a pointer button was pressed down
+ onmouseup a pointer button was released
+ onmousemove a pointer was moved onto the element
+ onmouseout a pointer was moved away from the element
+ onkeypress a key was pressed and released
+ onkeydown a key was pressed down
+ onkeyup a key was released
+-->
+<!ENTITY % events
+ "onclick %Script; #IMPLIED
+ ondblclick %Script; #IMPLIED
+ onmousedown %Script; #IMPLIED
+ onmouseup %Script; #IMPLIED
+ onmouseover %Script; #IMPLIED
+ onmousemove %Script; #IMPLIED
+ onmouseout %Script; #IMPLIED
+ onkeypress %Script; #IMPLIED
+ onkeydown %Script; #IMPLIED
+ onkeyup %Script; #IMPLIED"
+ >
+
+<!-- attributes for elements that can get the focus
+ accesskey accessibility key character
+ tabindex position in tabbing order
+ onfocus the element got the focus
+ onblur the element lost the focus
+-->
+<!ENTITY % focus
+ "accesskey %Character; #IMPLIED
+ tabindex %Number; #IMPLIED
+ onfocus %Script; #IMPLIED
+ onblur %Script; #IMPLIED"
+ >
+
+<!ENTITY % attrs "%coreattrs; %i18n; %events;">
+
+<!-- text alignment for p, div, h1-h6. The default is
+ align="left" for ltr headings, "right" for rtl -->
+
+<!ENTITY % TextAlign "align (left|center|right|justify) #IMPLIED">
+
+<!--=================== Text Elements ====================================-->
+
+<!ENTITY % special.extra
+ "object | applet | img | map | iframe">
+
+<!ENTITY % special.basic
+ "br | span | bdo">
+
+<!ENTITY % special
+ "%special.basic; | %special.extra;">
+
+<!ENTITY % fontstyle.extra "big | small | font | basefont">
+
+<!ENTITY % fontstyle.basic "tt | i | b | u
+ | s | strike ">
+
+<!ENTITY % fontstyle "%fontstyle.basic; | %fontstyle.extra;">
+
+<!ENTITY % phrase.extra "sub | sup">
+<!ENTITY % phrase.basic "em | strong | dfn | code | q |
+ samp | kbd | var | cite | abbr | acronym">
+
+<!ENTITY % phrase "%phrase.basic; | %phrase.extra;">
+
+<!ENTITY % inline.forms "input | select | textarea | label | button">
+
+<!-- these can occur at block or inline level -->
+<!ENTITY % misc.inline "ins | del | script">
+
+<!-- these can only occur at block level -->
+<!ENTITY % misc "noscript | %misc.inline;">
+
+<!ENTITY % inline "a | %special; | %fontstyle; | %phrase; | %inline.forms;">
+
+<!-- %Inline; covers inline or "text-level" elements -->
+<!ENTITY % Inline "(#PCDATA | %inline; | %misc.inline;)*">
+
+<!--================== Block level elements ==============================-->
+
+<!ENTITY % heading "h1|h2|h3|h4|h5|h6">
+<!ENTITY % lists "ul | ol | dl | menu | dir">
+<!ENTITY % blocktext "pre | hr | blockquote | address | center | noframes">
+
+<!ENTITY % block
+ "p | %heading; | div | %lists; | %blocktext; | isindex |fieldset | table">
+
+<!-- %Flow; mixes block and inline and is used for list items etc. -->
+<!ENTITY % Flow "(#PCDATA | %block; | form | %inline; | %misc;)*">
+
+<!--================== Content models for exclusions =====================-->
+
+<!-- a elements use %Inline; excluding a -->
+
+<!ENTITY % a.content
+ "(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*">
+
+<!-- pre uses %Inline excluding img, object, applet, big, small,
+ font, or basefont -->
+
+<!ENTITY % pre.content
+ "(#PCDATA | a | %special.basic; | %fontstyle.basic; | %phrase.basic; |
+ %inline.forms; | %misc.inline;)*">
+
+<!-- form uses %Flow; excluding form -->
+
+<!ENTITY % form.content "(#PCDATA | %block; | %inline; | %misc;)*">
+
+<!-- button uses %Flow; but excludes a, form, form controls, iframe -->
+
+<!ENTITY % button.content
+ "(#PCDATA | p | %heading; | div | %lists; | %blocktext; |
+ table | br | span | bdo | object | applet | img | map |
+ %fontstyle; | %phrase; | %misc;)*">
+
+<!--================ Document Structure ==================================-->
+
+<!-- the namespace URI designates the document profile -->
+
+<!ELEMENT html (head, body)>
+<!ATTLIST html
+ %i18n;
+ id ID #IMPLIED
+ xmlns %URI; #FIXED 'http://www.w3.org/1999/xhtml'
+ >
+
+<!--================ Document Head =======================================-->
+
+<!ENTITY % head.misc "(script|style|meta|link|object|isindex)*">
+
+<!-- content model is %head.misc; combined with a single
+ title and an optional base element in any order -->
+
+<!ELEMENT head (%head.misc;,
+ ((title, %head.misc;, (base, %head.misc;)?) |
+ (base, %head.misc;, (title, %head.misc;))))>
+
+<!ATTLIST head
+ %i18n;
+ id ID #IMPLIED
+ profile %URI; #IMPLIED
+ >
+
+<!-- The title element is not considered part of the flow of text.
+ It should be displayed, for example as the page header or
+ window title. Exactly one title is required per document.
+ -->
+<!ELEMENT title (#PCDATA)>
+<!ATTLIST title
+ %i18n;
+ id ID #IMPLIED
+ >
+
+<!-- document base URI -->
+
+<!ELEMENT base EMPTY>
+<!ATTLIST base
+ id ID #IMPLIED
+ href %URI; #IMPLIED
+ target %FrameTarget; #IMPLIED
+ >
+
+<!-- generic metainformation -->
+<!ELEMENT meta EMPTY>
+<!ATTLIST meta
+ %i18n;
+ id ID #IMPLIED
+ http-equiv CDATA #IMPLIED
+ name CDATA #IMPLIED
+ content CDATA #REQUIRED
+ scheme CDATA #IMPLIED
+ >
+
+<!--
+ Relationship values can be used in principle:
+
+ a) for document specific toolbars/menus when used
+ with the link element in document head e.g.
+ start, contents, previous, next, index, end, help
+ b) to link to a separate style sheet (rel="stylesheet")
+ c) to make a link to a script (rel="script")
+ d) by stylesheets to control how collections of
+ html nodes are rendered into printed documents
+ e) to make a link to a printable version of this document
+ e.g. a PostScript or PDF version (rel="alternate" media="print")
+-->
+
+<!ELEMENT link EMPTY>
+<!ATTLIST link
+ %attrs;
+ charset %Charset; #IMPLIED
+ href %URI; #IMPLIED
+ hreflang %LanguageCode; #IMPLIED
+ type %ContentType; #IMPLIED
+ rel %LinkTypes; #IMPLIED
+ rev %LinkTypes; #IMPLIED
+ media %MediaDesc; #IMPLIED
+ target %FrameTarget; #IMPLIED
+ >
+
+<!-- style info, which may include CDATA sections -->
+<!ELEMENT style (#PCDATA)>
+<!ATTLIST style
+ %i18n;
+ id ID #IMPLIED
+ type %ContentType; #REQUIRED
+ media %MediaDesc; #IMPLIED
+ title %Text; #IMPLIED
+ xml:space (preserve) #FIXED 'preserve'
+ >
+
+<!-- script statements, which may include CDATA sections -->
+<!ELEMENT script (#PCDATA)>
+<!ATTLIST script
+ id ID #IMPLIED
+ charset %Charset; #IMPLIED
+ type %ContentType; #REQUIRED
+ language CDATA #IMPLIED
+ src %URI; #IMPLIED
+ defer (defer) #IMPLIED
+ xml:space (preserve) #FIXED 'preserve'
+ >
+
+<!-- alternate content container for non script-based rendering -->
+
+<!ELEMENT noscript %Flow;>
+<!ATTLIST noscript
+ %attrs;
+ >
+
+<!--======================= Frames =======================================-->
+
+<!-- inline subwindow -->
+
+<!ELEMENT iframe %Flow;>
+<!ATTLIST iframe
+ %coreattrs;
+ longdesc %URI; #IMPLIED
+ name NMTOKEN #IMPLIED
+ src %URI; #IMPLIED
+ frameborder (1|0) "1"
+ marginwidth %Pixels; #IMPLIED
+ marginheight %Pixels; #IMPLIED
+ scrolling (yes|no|auto) "auto"
+ align %ImgAlign; #IMPLIED
+ height %Length; #IMPLIED
+ width %Length; #IMPLIED
+ >
+
+<!-- alternate content container for non frame-based rendering -->
+
+<!ELEMENT noframes %Flow;>
+<!ATTLIST noframes
+ %attrs;
+ >
+
+<!--=================== Document Body ====================================-->
+
+<!ELEMENT body %Flow;>
+<!ATTLIST body
+ %attrs;
+ onload %Script; #IMPLIED
+ onunload %Script; #IMPLIED
+ background %URI; #IMPLIED
+ bgcolor %Color; #IMPLIED
+ text %Color; #IMPLIED
+ link %Color; #IMPLIED
+ vlink %Color; #IMPLIED
+ alink %Color; #IMPLIED
+ >
+
+<!ELEMENT div %Flow;> <!-- generic language/style container -->
+<!ATTLIST div
+ %attrs;
+ %TextAlign;
+ >
+
+<!--=================== Paragraphs =======================================-->
+
+<!ELEMENT p %Inline;>
+<!ATTLIST p
+ %attrs;
+ %TextAlign;
+ >
+
+<!--=================== Headings =========================================-->
+
+<!--
+ There are six levels of headings from h1 (the most important)
+ to h6 (the least important).
+-->
+
+<!ELEMENT h1 %Inline;>
+<!ATTLIST h1
+ %attrs;
+ %TextAlign;
+ >
+
+<!ELEMENT h2 %Inline;>
+<!ATTLIST h2
+ %attrs;
+ %TextAlign;
+ >
+
+<!ELEMENT h3 %Inline;>
+<!ATTLIST h3
+ %attrs;
+ %TextAlign;
+ >
+
+<!ELEMENT h4 %Inline;>
+<!ATTLIST h4
+ %attrs;
+ %TextAlign;
+ >
+
+<!ELEMENT h5 %Inline;>
+<!ATTLIST h5
+ %attrs;
+ %TextAlign;
+ >
+
+<!ELEMENT h6 %Inline;>
+<!ATTLIST h6
+ %attrs;
+ %TextAlign;
+ >
+
+<!--=================== Lists ============================================-->
+
+<!-- Unordered list bullet styles -->
+
+<!ENTITY % ULStyle "(disc|square|circle)">
+
+<!-- Unordered list -->
+
+<!ELEMENT ul (li)+>
+<!ATTLIST ul
+ %attrs;
+ type %ULStyle; #IMPLIED
+ compact (compact) #IMPLIED
+ >
+
+<!-- Ordered list numbering style
+
+ 1 arabic numbers 1, 2, 3, ...
+ a lower alpha a, b, c, ...
+ A upper alpha A, B, C, ...
+ i lower roman i, ii, iii, ...
+ I upper roman I, II, III, ...
+
+ The style is applied to the sequence number which by default
+ is reset to 1 for the first list item in an ordered list.
+-->
+<!ENTITY % OLStyle "CDATA">
+
+<!-- Ordered (numbered) list -->
+
+<!ELEMENT ol (li)+>
+<!ATTLIST ol
+ %attrs;
+ type %OLStyle; #IMPLIED
+ compact (compact) #IMPLIED
+ start %Number; #IMPLIED
+ >
+
+<!-- single column list (DEPRECATED) -->
+<!ELEMENT menu (li)+>
+<!ATTLIST menu
+ %attrs;
+ compact (compact) #IMPLIED
+ >
+
+<!-- multiple column list (DEPRECATED) -->
+<!ELEMENT dir (li)+>
+<!ATTLIST dir
+ %attrs;
+ compact (compact) #IMPLIED
+ >
+
+<!-- LIStyle is constrained to: "(%ULStyle;|%OLStyle;)" -->
+<!ENTITY % LIStyle "CDATA">
+
+<!-- list item -->
+
+<!ELEMENT li %Flow;>
+<!ATTLIST li
+ %attrs;
+ type %LIStyle; #IMPLIED
+ value %Number; #IMPLIED
+ >
+
+<!-- definition lists - dt for term, dd for its definition -->
+
+<!ELEMENT dl (dt|dd)+>
+<!ATTLIST dl
+ %attrs;
+ compact (compact) #IMPLIED
+ >
+
+<!ELEMENT dt %Inline;>
+<!ATTLIST dt
+ %attrs;
+ >
+
+<!ELEMENT dd %Flow;>
+<!ATTLIST dd
+ %attrs;
+ >
+
+<!--=================== Address ==========================================-->
+
+<!-- information on author -->
+
+<!ELEMENT address (#PCDATA | %inline; | %misc.inline; | p)*>
+<!ATTLIST address
+ %attrs;
+ >
+
+<!--=================== Horizontal Rule ==================================-->
+
+<!ELEMENT hr EMPTY>
+<!ATTLIST hr
+ %attrs;
+ align (left|center|right) #IMPLIED
+ noshade (noshade) #IMPLIED
+ size %Pixels; #IMPLIED
+ width %Length; #IMPLIED
+ >
+
+<!--=================== Preformatted Text ================================-->
+
+<!-- content is %Inline; excluding
+ "img|object|applet|big|small|sub|sup|font|basefont" -->
+
+<!ELEMENT pre %pre.content;>
+<!ATTLIST pre
+ %attrs;
+ width %Number; #IMPLIED
+ xml:space (preserve) #FIXED 'preserve'
+ >
+
+<!--=================== Block-like Quotes ================================-->
+
+<!ELEMENT blockquote %Flow;>
+<!ATTLIST blockquote
+ %attrs;
+ cite %URI; #IMPLIED
+ >
+
+<!--=================== Text alignment ===================================-->
+
+<!-- center content -->
+<!ELEMENT center %Flow;>
+<!ATTLIST center
+ %attrs;
+ >
+
+<!--=================== Inserted/Deleted Text ============================-->
+
+<!--
+ ins/del are allowed in block and inline content, but its
+ inappropriate to include block content within an ins element
+ occurring in inline content.
+-->
+<!ELEMENT ins %Flow;>
+<!ATTLIST ins
+ %attrs;
+ cite %URI; #IMPLIED
+ datetime %Datetime; #IMPLIED
+ >
+
+<!ELEMENT del %Flow;>
+<!ATTLIST del
+ %attrs;
+ cite %URI; #IMPLIED
+ datetime %Datetime; #IMPLIED
+ >
+
+<!--================== The Anchor Element ================================-->
+
+<!-- content is %Inline; except that anchors shouldn't be nested -->
+
+<!ELEMENT a %a.content;>
+<!ATTLIST a
+ %attrs;
+ %focus;
+ charset %Charset; #IMPLIED
+ type %ContentType; #IMPLIED
+ name NMTOKEN #IMPLIED
+ href %URI; #IMPLIED
+ hreflang %LanguageCode; #IMPLIED
+ rel %LinkTypes; #IMPLIED
+ rev %LinkTypes; #IMPLIED
+ shape %Shape; "rect"
+ coords %Coords; #IMPLIED
+ target %FrameTarget; #IMPLIED
+ >
+
+<!--===================== Inline Elements ================================-->
+
+<!ELEMENT span %Inline;> <!-- generic language/style container -->
+<!ATTLIST span
+ %attrs;
+ >
+
+<!ELEMENT bdo %Inline;> <!-- I18N BiDi over-ride -->
+<!ATTLIST bdo
+ %coreattrs;
+ %events;
+ lang %LanguageCode; #IMPLIED
+ xml:lang %LanguageCode; #IMPLIED
+ dir (ltr|rtl) #REQUIRED
+ >
+
+<!ELEMENT br EMPTY> <!-- forced line break -->
+<!ATTLIST br
+ %coreattrs;
+ clear (left|all|right|none) "none"
+ >
+
+<!ELEMENT em %Inline;> <!-- emphasis -->
+<!ATTLIST em %attrs;>
+
+<!ELEMENT strong %Inline;> <!-- strong emphasis -->
+<!ATTLIST strong %attrs;>
+
+<!ELEMENT dfn %Inline;> <!-- definitional -->
+<!ATTLIST dfn %attrs;>
+
+<!ELEMENT code %Inline;> <!-- program code -->
+<!ATTLIST code %attrs;>
+
+<!ELEMENT samp %Inline;> <!-- sample -->
+<!ATTLIST samp %attrs;>
+
+<!ELEMENT kbd %Inline;> <!-- something user would type -->
+<!ATTLIST kbd %attrs;>
+
+<!ELEMENT var %Inline;> <!-- variable -->
+<!ATTLIST var %attrs;>
+
+<!ELEMENT cite %Inline;> <!-- citation -->
+<!ATTLIST cite %attrs;>
+
+<!ELEMENT abbr %Inline;> <!-- abbreviation -->
+<!ATTLIST abbr %attrs;>
+
+<!ELEMENT acronym %Inline;> <!-- acronym -->
+<!ATTLIST acronym %attrs;>
+
+<!ELEMENT q %Inline;> <!-- inlined quote -->
+<!ATTLIST q
+ %attrs;
+ cite %URI; #IMPLIED
+ >
+
+<!ELEMENT sub %Inline;> <!-- subscript -->
+<!ATTLIST sub %attrs;>
+
+<!ELEMENT sup %Inline;> <!-- superscript -->
+<!ATTLIST sup %attrs;>
+
+<!ELEMENT tt %Inline;> <!-- fixed pitch font -->
+<!ATTLIST tt %attrs;>
+
+<!ELEMENT i %Inline;> <!-- italic font -->
+<!ATTLIST i %attrs;>
+
+<!ELEMENT b %Inline;> <!-- bold font -->
+<!ATTLIST b %attrs;>
+
+<!ELEMENT big %Inline;> <!-- bigger font -->
+<!ATTLIST big %attrs;>
+
+<!ELEMENT small %Inline;> <!-- smaller font -->
+<!ATTLIST small %attrs;>
+
+<!ELEMENT u %Inline;> <!-- underline -->
+<!ATTLIST u %attrs;>
+
+<!ELEMENT s %Inline;> <!-- strike-through -->
+<!ATTLIST s %attrs;>
+
+<!ELEMENT strike %Inline;> <!-- strike-through -->
+<!ATTLIST strike %attrs;>
+
+<!ELEMENT basefont EMPTY> <!-- base font size -->
+<!ATTLIST basefont
+ id ID #IMPLIED
+ size CDATA #REQUIRED
+ color %Color; #IMPLIED
+ face CDATA #IMPLIED
+ >
+
+<!ELEMENT font %Inline;> <!-- local change to font -->
+<!ATTLIST font
+ %coreattrs;
+ %i18n;
+ size CDATA #IMPLIED
+ color %Color; #IMPLIED
+ face CDATA #IMPLIED
+ >
+
+<!--==================== Object ======================================-->
+<!--
+ object is used to embed objects as part of HTML pages.
+ param elements should precede other content. Parameters
+ can also be expressed as attribute/value pairs on the
+ object element itself when brevity is desired.
+-->
+
+<!ELEMENT object (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST object
+ %attrs;
+ declare (declare) #IMPLIED
+ classid %URI; #IMPLIED
+ codebase %URI; #IMPLIED
+ data %URI; #IMPLIED
+ type %ContentType; #IMPLIED
+ codetype %ContentType; #IMPLIED
+ archive %UriList; #IMPLIED
+ standby %Text; #IMPLIED
+ height %Length; #IMPLIED
+ width %Length; #IMPLIED
+ usemap %URI; #IMPLIED
+ name NMTOKEN #IMPLIED
+ tabindex %Number; #IMPLIED
+ align %ImgAlign; #IMPLIED
+ border %Pixels; #IMPLIED
+ hspace %Pixels; #IMPLIED
+ vspace %Pixels; #IMPLIED
+ >
+
+<!--
+ param is used to supply a named property value.
+ In XML it would seem natural to follow RDF and support an
+ abbreviated syntax where the param elements are replaced
+ by attribute value pairs on the object start tag.
+-->
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+ id ID #IMPLIED
+ name CDATA #REQUIRED
+ value CDATA #IMPLIED
+ valuetype (data|ref|object) "data"
+ type %ContentType; #IMPLIED
+ >
+
+<!--=================== Java applet ==================================-->
+<!--
+ One of code or object attributes must be present.
+ Place param elements before other content.
+-->
+<!ELEMENT applet (#PCDATA | param | %block; | form | %inline; | %misc;)*>
+<!ATTLIST applet
+ %coreattrs;
+ codebase %URI; #IMPLIED
+ archive CDATA #IMPLIED
+ code CDATA #IMPLIED
+ object CDATA #IMPLIED
+ alt %Text; #IMPLIED
+ name NMTOKEN #IMPLIED
+ width %Length; #REQUIRED
+ height %Length; #REQUIRED
+ align %ImgAlign; #IMPLIED
+ hspace %Pixels; #IMPLIED
+ vspace %Pixels; #IMPLIED
+ >
+
+<!--=================== Images ===========================================-->
+
+<!--
+ To avoid accessibility problems for people who aren't
+ able to see the image, you should provide a text
+ description using the alt and longdesc attributes.
+ In addition, avoid the use of server-side image maps.
+-->
+
+<!ELEMENT img EMPTY>
+<!ATTLIST img
+ %attrs;
+ src %URI; #REQUIRED
+ alt %Text; #REQUIRED
+ name NMTOKEN #IMPLIED
+ longdesc %URI; #IMPLIED
+ height %Length; #IMPLIED
+ width %Length; #IMPLIED
+ usemap %URI; #IMPLIED
+ ismap (ismap) #IMPLIED
+ align %ImgAlign; #IMPLIED
+ border %Length; #IMPLIED
+ hspace %Pixels; #IMPLIED
+ vspace %Pixels; #IMPLIED
+ >
+
+<!-- usemap points to a map element which may be in this document
+ or an external document, although the latter is not widely supported -->
+
+<!--================== Client-side image maps ============================-->
+
+<!-- These can be placed in the same document or grouped in a
+ separate document although this isn't yet widely supported -->
+
+<!ELEMENT map ((%block; | form | %misc;)+ | area+)>
+<!ATTLIST map
+ %i18n;
+ %events;
+ id ID #REQUIRED
+ class CDATA #IMPLIED
+ style %StyleSheet; #IMPLIED
+ title %Text; #IMPLIED
+ name CDATA #IMPLIED
+ >
+
+<!ELEMENT area EMPTY>
+<!ATTLIST area
+ %attrs;
+ %focus;
+ shape %Shape; "rect"
+ coords %Coords; #IMPLIED
+ href %URI; #IMPLIED
+ nohref (nohref) #IMPLIED
+ alt %Text; #REQUIRED
+ target %FrameTarget; #IMPLIED
+ >
+
+<!--================ Forms ===============================================-->
+
+<!ELEMENT form %form.content;> <!-- forms shouldn't be nested -->
+
+<!ATTLIST form
+ %attrs;
+ action %URI; #REQUIRED
+ method (get|post) "get"
+ name NMTOKEN #IMPLIED
+ enctype %ContentType; "application/x-www-form-urlencoded"
+ onsubmit %Script; #IMPLIED
+ onreset %Script; #IMPLIED
+ accept %ContentTypes; #IMPLIED
+ accept-charset %Charsets; #IMPLIED
+ target %FrameTarget; #IMPLIED
+ >
+
+<!--
+ Each label must not contain more than ONE field
+ Label elements shouldn't be nested.
+-->
+<!ELEMENT label %Inline;>
+<!ATTLIST label
+ %attrs;
+ for IDREF #IMPLIED
+ accesskey %Character; #IMPLIED
+ onfocus %Script; #IMPLIED
+ onblur %Script; #IMPLIED
+ >
+
+<!ENTITY % InputType
+ "(text | password | checkbox |
+ radio | submit | reset |
+ file | hidden | image | button)"
+ >
+
+<!-- the name attribute is required for all but submit & reset -->
+
+<!ELEMENT input EMPTY> <!-- form control -->
+<!ATTLIST input
+ %attrs;
+ %focus;
+ type %InputType; "text"
+ name CDATA #IMPLIED
+ value CDATA #IMPLIED
+ checked (checked) #IMPLIED
+ disabled (disabled) #IMPLIED
+ readonly (readonly) #IMPLIED
+ size CDATA #IMPLIED
+ maxlength %Number; #IMPLIED
+ src %URI; #IMPLIED
+ alt CDATA #IMPLIED
+ usemap %URI; #IMPLIED
+ onselect %Script; #IMPLIED
+ onchange %Script; #IMPLIED
+ accept %ContentTypes; #IMPLIED
+ align %ImgAlign; #IMPLIED
+ >
+
+<!ELEMENT select (optgroup|option)+> <!-- option selector -->
+<!ATTLIST select
+ %attrs;
+ name CDATA #IMPLIED
+ size %Number; #IMPLIED
+ multiple (multiple) #IMPLIED
+ disabled (disabled) #IMPLIED
+ tabindex %Number; #IMPLIED
+ onfocus %Script; #IMPLIED
+ onblur %Script; #IMPLIED
+ onchange %Script; #IMPLIED
+ >
+
+<!ELEMENT optgroup (option)+> <!-- option group -->
+<!ATTLIST optgroup
+ %attrs;
+ disabled (disabled) #IMPLIED
+ label %Text; #REQUIRED
+ >
+
+<!ELEMENT option (#PCDATA)> <!-- selectable choice -->
+<!ATTLIST option
+ %attrs;
+ selected (selected) #IMPLIED
+ disabled (disabled) #IMPLIED
+ label %Text; #IMPLIED
+ value CDATA #IMPLIED
+ >
+
+<!ELEMENT textarea (#PCDATA)> <!-- multi-line text field -->
+<!ATTLIST textarea
+ %attrs;
+ %focus;
+ name CDATA #IMPLIED
+ rows %Number; #REQUIRED
+ cols %Number; #REQUIRED
+ disabled (disabled) #IMPLIED
+ readonly (readonly) #IMPLIED
+ onselect %Script; #IMPLIED
+ onchange %Script; #IMPLIED
+ >
+
+<!--
+ The fieldset element is used to group form fields.
+ Only one legend element should occur in the content
+ and if present should only be preceded by whitespace.
+-->
+<!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*>
+<!ATTLIST fieldset
+ %attrs;
+ >
+
+<!ENTITY % LAlign "(top|bottom|left|right)">
+
+<!ELEMENT legend %Inline;> <!-- fieldset label -->
+<!ATTLIST legend
+ %attrs;
+ accesskey %Character; #IMPLIED
+ align %LAlign; #IMPLIED
+ >
+
+<!--
+ Content is %Flow; excluding a, form, form controls, iframe
+-->
+<!ELEMENT button %button.content;> <!-- push button -->
+<!ATTLIST button
+ %attrs;
+ %focus;
+ name CDATA #IMPLIED
+ value CDATA #IMPLIED
+ type (button|submit|reset) "submit"
+ disabled (disabled) #IMPLIED
+ >
+
+<!-- single-line text input control (DEPRECATED) -->
+<!ELEMENT isindex EMPTY>
+<!ATTLIST isindex
+ %coreattrs;
+ %i18n;
+ prompt %Text; #IMPLIED
+ >
+
+<!--======================= Tables =======================================-->
+
+<!-- Derived from IETF HTML table standard, see [RFC1942] -->
+
+<!--
+ The border attribute sets the thickness of the frame around the
+ table. The default units are screen pixels.
+
+ The frame attribute specifies which parts of the frame around
+ the table should be rendered. The values are not the same as
+ CALS to avoid a name clash with the valign attribute.
+-->
+<!ENTITY % TFrame "(void|above|below|hsides|lhs|rhs|vsides|box|border)">
+
+<!--
+ The rules attribute defines which rules to draw between cells:
+
+ If rules is absent then assume:
+ "none" if border is absent or border="0" otherwise "all"
+-->
+
+<!ENTITY % TRules "(none | groups | rows | cols | all)">
+
+<!-- horizontal placement of table relative to document -->
+<!ENTITY % TAlign "(left|center|right)">
+
+<!-- horizontal alignment attributes for cell contents
+
+ char alignment char, e.g. char=':'
+ charoff offset for alignment char
+-->
+<!ENTITY % cellhalign
+ "align (left|center|right|justify|char) #IMPLIED
+ char %Character; #IMPLIED
+ charoff %Length; #IMPLIED"
+ >
+
+<!-- vertical alignment attributes for cell contents -->
+<!ENTITY % cellvalign
+ "valign (top|middle|bottom|baseline) #IMPLIED"
+ >
+
+<!ELEMENT table
+ (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
+<!ELEMENT caption %Inline;>
+<!ELEMENT thead (tr)+>
+<!ELEMENT tfoot (tr)+>
+<!ELEMENT tbody (tr)+>
+<!ELEMENT colgroup (col)*>
+<!ELEMENT col EMPTY>
+<!ELEMENT tr (th|td)+>
+<!ELEMENT th %Flow;>
+<!ELEMENT td %Flow;>
+
+<!ATTLIST table
+ %attrs;
+ summary %Text; #IMPLIED
+ width %Length; #IMPLIED
+ border %Pixels; #IMPLIED
+ frame %TFrame; #IMPLIED
+ rules %TRules; #IMPLIED
+ cellspacing %Length; #IMPLIED
+ cellpadding %Length; #IMPLIED
+ align %TAlign; #IMPLIED
+ bgcolor %Color; #IMPLIED
+ >
+
+<!ENTITY % CAlign "(top|bottom|left|right)">
+
+<!ATTLIST caption
+ %attrs;
+ align %CAlign; #IMPLIED
+ >
+
+<!--
+colgroup groups a set of col elements. It allows you to group
+several semantically related columns together.
+-->
+<!ATTLIST colgroup
+ %attrs;
+ span %Number; "1"
+ width %MultiLength; #IMPLIED
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!--
+ col elements define the alignment properties for cells in
+ one or more columns.
+
+ The width attribute specifies the width of the columns, e.g.
+
+ width=64 width in screen pixels
+ width=0.5* relative width of 0.5
+
+ The span attribute causes the attributes of one
+ col element to apply to more than one column.
+-->
+<!ATTLIST col
+ %attrs;
+ span %Number; "1"
+ width %MultiLength; #IMPLIED
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!--
+ Use thead to duplicate headers when breaking table
+ across page boundaries, or for static headers when
+ tbody sections are rendered in scrolling panel.
+
+ Use tfoot to duplicate footers when breaking table
+ across page boundaries, or for static footers when
+ tbody sections are rendered in scrolling panel.
+
+ Use multiple tbody sections when rules are needed
+ between groups of table rows.
+-->
+<!ATTLIST thead
+ %attrs;
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!ATTLIST tfoot
+ %attrs;
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!ATTLIST tbody
+ %attrs;
+ %cellhalign;
+ %cellvalign;
+ >
+
+<!ATTLIST tr
+ %attrs;
+ %cellhalign;
+ %cellvalign;
+ bgcolor %Color; #IMPLIED
+ >
+
+<!-- Scope is simpler than headers attribute for common tables -->
+<!ENTITY % Scope "(row|col|rowgroup|colgroup)">
+
+<!-- th is for headers, td for data and for cells acting as both -->
+
+<!ATTLIST th
+ %attrs;
+ abbr %Text; #IMPLIED
+ axis CDATA #IMPLIED
+ headers IDREFS #IMPLIED
+ scope %Scope; #IMPLIED
+ rowspan %Number; "1"
+ colspan %Number; "1"
+ %cellhalign;
+ %cellvalign;
+ nowrap (nowrap) #IMPLIED
+ bgcolor %Color; #IMPLIED
+ width %Length; #IMPLIED
+ height %Length; #IMPLIED
+ >
+
+<!ATTLIST td
+ %attrs;
+ abbr %Text; #IMPLIED
+ axis CDATA #IMPLIED
+ headers IDREFS #IMPLIED
+ scope %Scope; #IMPLIED
+ rowspan %Number; "1"
+ colspan %Number; "1"
+ %cellhalign;
+ %cellvalign;
+ nowrap (nowrap) #IMPLIED
+ bgcolor %Color; #IMPLIED
+ width %Length; #IMPLIED
+ height %Length; #IMPLIED
+ >
+
diff --git a/vendor/Twisted-10.0.0/twisted/mail/__init__.py b/vendor/Twisted-10.0.0/twisted/mail/__init__.py
new file mode 100644
index 0000000000..ea3b868f85
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/__init__.py
@@ -0,0 +1,15 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+
+Twisted Mail: a Twisted E-Mail Server.
+
+Maintainer: Jp Calderone
+
+"""
+
+from twisted.mail._version import version
+__version__ = version.short()
diff --git a/vendor/Twisted-10.0.0/twisted/mail/_version.py b/vendor/Twisted-10.0.0/twisted/mail/_version.py
new file mode 100644
index 0000000000..49edfd7717
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/_version.py
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twisted.mail', 10, 0, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/mail/alias.py b/vendor/Twisted-10.0.0/twisted/mail/alias.py
new file mode 100644
index 0000000000..0a7371461f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/alias.py
@@ -0,0 +1,435 @@
+# -*- test-case-name: twisted.mail.test.test_mail -*-
+#
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Support for aliases(5) configuration files
+
+@author: Jp Calderone
+
+TODO::
+ Monitor files for reparsing
+ Handle non-local alias targets
+ Handle maildir alias targets
+"""
+
+import os
+import tempfile
+
+from twisted.mail import smtp
+from twisted.internet import reactor
+from twisted.internet import protocol
+from twisted.internet import defer
+from twisted.python import failure
+from twisted.python import log
+from zope.interface import implements, Interface
+
+
+def handle(result, line, filename, lineNo):
+ parts = [p.strip() for p in line.split(':', 1)]
+ if len(parts) != 2:
+ fmt = "Invalid format on line %d of alias file %s."
+ arg = (lineNo, filename)
+ log.err(fmt % arg)
+ else:
+ user, alias = parts
+ result.setdefault(user.strip(), []).extend(map(str.strip, alias.split(',')))
+
+def loadAliasFile(domains, filename=None, fp=None):
+ """Load a file containing email aliases.
+
+ Lines in the file should be formatted like so::
+
+ username: alias1,alias2,...,aliasN
+
+ Aliases beginning with a | will be treated as programs, will be run, and
+ the message will be written to their stdin.
+
+ Aliases without a host part will be assumed to be addresses on localhost.
+
+ If a username is specified multiple times, the aliases for each are joined
+ together as if they had all been on one line.
+
+ @type domains: C{dict} of implementor of C{IDomain}
+ @param domains: The domains to which these aliases will belong.
+
+ @type filename: C{str}
+ @param filename: The filename from which to load aliases.
+
+ @type fp: Any file-like object.
+ @param fp: If specified, overrides C{filename}, and aliases are read from
+ it.
+
+ @rtype: C{dict}
+ @return: A dictionary mapping usernames to C{AliasGroup} objects.
+ """
+ result = {}
+ if fp is None:
+ fp = file(filename)
+ else:
+ filename = getattr(fp, 'name', '<unknown>')
+ i = 0
+ prev = ''
+ for line in fp:
+ i += 1
+ line = line.rstrip()
+ if line.lstrip().startswith('#'):
+ continue
+ elif line.startswith(' ') or line.startswith('\t'):
+ prev = prev + line
+ else:
+ if prev:
+ handle(result, prev, filename, i)
+ prev = line
+ if prev:
+ handle(result, prev, filename, i)
+ for (u, a) in result.items():
+ addr = smtp.Address(u)
+ result[u] = AliasGroup(a, domains, u)
+ return result
+
+class IAlias(Interface):
+ def createMessageReceiver():
+ pass
+
+class AliasBase:
+ def __init__(self, domains, original):
+ self.domains = domains
+ self.original = smtp.Address(original)
+
+ def domain(self):
+ return self.domains[self.original.domain]
+
+ def resolve(self, aliasmap, memo=None):
+ if memo is None:
+ memo = {}
+ if str(self) in memo:
+ return None
+ memo[str(self)] = None
+ return self.createMessageReceiver()
+
+class AddressAlias(AliasBase):
+ """The simplest alias, translating one email address into another."""
+
+ implements(IAlias)
+
+ def __init__(self, alias, *args):
+ AliasBase.__init__(self, *args)
+ self.alias = smtp.Address(alias)
+
+ def __str__(self):
+ return '<Address %s>' % (self.alias,)
+
+ def createMessageReceiver(self):
+ return self.domain().startMessage(str(self.alias))
+
+ def resolve(self, aliasmap, memo=None):
+ if memo is None:
+ memo = {}
+ if str(self) in memo:
+ return None
+ memo[str(self)] = None
+ try:
+ return self.domain().exists(smtp.User(self.alias, None, None, None), memo)()
+ except smtp.SMTPBadRcpt:
+ pass
+ if self.alias.local in aliasmap:
+ return aliasmap[self.alias.local].resolve(aliasmap, memo)
+ return None
+
+class FileWrapper:
+ implements(smtp.IMessage)
+
+ def __init__(self, filename):
+ self.fp = tempfile.TemporaryFile()
+ self.finalname = filename
+
+ def lineReceived(self, line):
+ self.fp.write(line + '\n')
+
+ def eomReceived(self):
+ self.fp.seek(0, 0)
+ try:
+ f = file(self.finalname, 'a')
+ except:
+ return defer.fail(failure.Failure())
+
+ f.write(self.fp.read())
+ self.fp.close()
+ f.close()
+
+ return defer.succeed(self.finalname)
+
+ def connectionLost(self):
+ self.fp.close()
+ self.fp = None
+
+ def __str__(self):
+ return '<FileWrapper %s>' % (self.finalname,)
+
+
+class FileAlias(AliasBase):
+
+ implements(IAlias)
+
+ def __init__(self, filename, *args):
+ AliasBase.__init__(self, *args)
+ self.filename = filename
+
+ def __str__(self):
+ return '<File %s>' % (self.filename,)
+
+ def createMessageReceiver(self):
+ return FileWrapper(self.filename)
+
+
+
+class ProcessAliasTimeout(Exception):
+ """
+ A timeout occurred while processing aliases.
+ """
+
+
+
+class MessageWrapper:
+ """
+ A message receiver which delivers content to a child process.
+
+ @type completionTimeout: C{int} or C{float}
+ @ivar completionTimeout: The number of seconds to wait for the child
+ process to exit before reporting the delivery as a failure.
+
+ @type _timeoutCallID: C{NoneType} or L{IDelayedCall}
+ @ivar _timeoutCallID: The call used to time out delivery, started when the
+ connection to the child process is closed.
+
+ @type done: C{bool}
+ @ivar done: Flag indicating whether the child process has exited or not.
+
+ @ivar reactor: An L{IReactorTime} provider which will be used to schedule
+ timeouts.
+ """
+ implements(smtp.IMessage)
+
+ done = False
+
+ completionTimeout = 60
+ _timeoutCallID = None
+
+ reactor = reactor
+
+ def __init__(self, protocol, process=None, reactor=None):
+ self.processName = process
+ self.protocol = protocol
+ self.completion = defer.Deferred()
+ self.protocol.onEnd = self.completion
+ self.completion.addBoth(self._processEnded)
+
+ if reactor is not None:
+ self.reactor = reactor
+
+
+ def _processEnded(self, result):
+ """
+ Record process termination and cancel the timeout call if it is active.
+ """
+ self.done = True
+ if self._timeoutCallID is not None:
+ # eomReceived was called, we're actually waiting for the process to
+ # exit.
+ self._timeoutCallID.cancel()
+ self._timeoutCallID = None
+ else:
+ # eomReceived was not called, this is unexpected, propagate the
+ # error.
+ return result
+
+
+ def lineReceived(self, line):
+ if self.done:
+ return
+ self.protocol.transport.write(line + '\n')
+
+
+ def eomReceived(self):
+ """
+ Disconnect from the child process, set up a timeout to wait for it to
+ exit, and return a Deferred which will be called back when the child
+ process exits.
+ """
+ if not self.done:
+ self.protocol.transport.loseConnection()
+ self._timeoutCallID = self.reactor.callLater(
+ self.completionTimeout, self._completionCancel)
+ return self.completion
+
+
+ def _completionCancel(self):
+ """
+ Handle the expiration of the timeout for the child process to exit by
+ terminating the child process forcefully and issuing a failure to the
+ completion deferred returned by L{eomReceived}.
+ """
+ self._timeoutCallID = None
+ self.protocol.transport.signalProcess('KILL')
+ exc = ProcessAliasTimeout(
+ "No answer after %s seconds" % (self.completionTimeout,))
+ self.protocol.onEnd = None
+ self.completion.errback(failure.Failure(exc))
+
+
+ def connectionLost(self):
+ # Heh heh
+ pass
+
+
+ def __str__(self):
+ return '<ProcessWrapper %s>' % (self.processName,)
+
+
+
+class ProcessAliasProtocol(protocol.ProcessProtocol):
+ """
+ Trivial process protocol which will callback a Deferred when the associated
+ process ends.
+
+ @ivar onEnd: If not C{None}, a L{Deferred} which will be called back with
+ the failure passed to C{processEnded}, when C{processEnded} is called.
+ """
+
+ onEnd = None
+
+ def processEnded(self, reason):
+ """
+ Call back C{onEnd} if it is set.
+ """
+ if self.onEnd is not None:
+ self.onEnd.errback(reason)
+
+
+
+class ProcessAlias(AliasBase):
+ """
+ An alias which is handled by the execution of a particular program.
+
+ @ivar reactor: An L{IReactorProcess} and L{IReactorTime} provider which
+ will be used to create and timeout the alias child process.
+ """
+ implements(IAlias)
+
+ reactor = reactor
+
+ def __init__(self, path, *args):
+ AliasBase.__init__(self, *args)
+ self.path = path.split()
+ self.program = self.path[0]
+
+
+ def __str__(self):
+ """
+ Build a string representation containing the path.
+ """
+ return '<Process %s>' % (self.path,)
+
+
+ def spawnProcess(self, proto, program, path):
+ """
+ Wrapper around C{reactor.spawnProcess}, to be customized for tests
+ purpose.
+ """
+ return self.reactor.spawnProcess(proto, program, path)
+
+
+ def createMessageReceiver(self):
+ """
+ Create a message receiver by launching a process.
+ """
+ p = ProcessAliasProtocol()
+ m = MessageWrapper(p, self.program, self.reactor)
+ fd = self.spawnProcess(p, self.program, self.path)
+ return m
+
+
+
+class MultiWrapper:
+ """
+ Wrapper to deliver a single message to multiple recipients.
+ """
+
+ implements(smtp.IMessage)
+
+ def __init__(self, objs):
+ self.objs = objs
+
+ def lineReceived(self, line):
+ for o in self.objs:
+ o.lineReceived(line)
+
+ def eomReceived(self):
+ return defer.DeferredList([
+ o.eomReceived() for o in self.objs
+ ])
+
+ def connectionLost(self):
+ for o in self.objs:
+ o.connectionLost()
+
+ def __str__(self):
+ return '<GroupWrapper %r>' % (map(str, self.objs),)
+
+
+
+class AliasGroup(AliasBase):
+ """
+ An alias which points to more than one recipient.
+
+ @ivar processAliasFactory: a factory for resolving process aliases.
+ @type processAliasFactory: C{class}
+ """
+
+ implements(IAlias)
+
+ processAliasFactory = ProcessAlias
+
+ def __init__(self, items, *args):
+ AliasBase.__init__(self, *args)
+ self.aliases = []
+ while items:
+ addr = items.pop().strip()
+ if addr.startswith(':'):
+ try:
+ f = file(addr[1:])
+ except:
+ log.err("Invalid filename in alias file %r" % (addr[1:],))
+ else:
+ addr = ' '.join([l.strip() for l in f])
+ items.extend(addr.split(','))
+ elif addr.startswith('|'):
+ self.aliases.append(self.processAliasFactory(addr[1:], *args))
+ elif addr.startswith('/'):
+ if os.path.isdir(addr):
+ log.err("Directory delivery not supported")
+ else:
+ self.aliases.append(FileAlias(addr, *args))
+ else:
+ self.aliases.append(AddressAlias(addr, *args))
+
+ def __len__(self):
+ return len(self.aliases)
+
+ def __str__(self):
+ return '<AliasGroup [%s]>' % (', '.join(map(str, self.aliases)))
+
+ def createMessageReceiver(self):
+ return MultiWrapper([a.createMessageReceiver() for a in self.aliases])
+
+ def resolve(self, aliasmap, memo=None):
+ if memo is None:
+ memo = {}
+ r = []
+ for a in self.aliases:
+ r.append(a.resolve(aliasmap, memo))
+ return MultiWrapper(filter(None, r))
+
diff --git a/vendor/Twisted-10.0.0/twisted/mail/bounce.py b/vendor/Twisted-10.0.0/twisted/mail/bounce.py
new file mode 100644
index 0000000000..d57c495ec7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/bounce.py
@@ -0,0 +1,61 @@
+# -*- test-case-name: twisted.mail.test.test_bounce -*-
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import StringIO
+import rfc822
+import string
+import time
+import os
+
+
+from twisted.mail import smtp
+
+BOUNCE_FORMAT = """\
+From: postmaster@%(failedDomain)s
+To: %(failedFrom)s
+Subject: Returned Mail: see transcript for details
+Message-ID: %(messageID)s
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="%(boundary)s"
+
+--%(boundary)s
+
+%(transcript)s
+
+--%(boundary)s
+Content-Type: message/delivery-status
+Arrival-Date: %(ctime)s
+Final-Recipient: RFC822; %(failedTo)s
+"""
+
+def generateBounce(message, failedFrom, failedTo, transcript=''):
+ if not transcript:
+ transcript = '''\
+I'm sorry, the following address has permanent errors: %(failedTo)s.
+I've given up, and I will not retry the message again.
+''' % vars()
+
+ boundary = "%s_%s_%s" % (time.time(), os.getpid(), 'XXXXX')
+ failedAddress = rfc822.AddressList(failedTo)[0][1]
+ failedDomain = string.split(failedAddress, '@', 1)[1]
+ messageID = smtp.messageid(uniq='bounce')
+ ctime = time.ctime(time.time())
+
+ fp = StringIO.StringIO()
+ fp.write(BOUNCE_FORMAT % vars())
+ orig = message.tell()
+ message.seek(2, 0)
+ sz = message.tell()
+ message.seek(0, orig)
+ if sz > 10000:
+ while 1:
+ line = message.readline()
+ if len(line)<=1:
+ break
+ fp.write(line)
+ else:
+ fp.write(message.read())
+ return '', failedFrom, fp.getvalue()
diff --git a/vendor/Twisted-10.0.0/twisted/mail/imap4.py b/vendor/Twisted-10.0.0/twisted/mail/imap4.py
new file mode 100644
index 0000000000..5a351eb1dc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/imap4.py
@@ -0,0 +1,5670 @@
+# -*- test-case-name: twisted.mail.test.test_imap -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An IMAP4 protocol implementation
+
+@author: Jp Calderone
+
+To do::
+ Suspend idle timeout while server is processing
+ Use an async message parser instead of buffering in memory
+ Figure out a way to not queue multi-message client requests (Flow? A simple callback?)
+ Clarify some API docs (Query, etc)
+ Make APPEND recognize (again) non-existent mailboxes before accepting the literal
+"""
+
+import rfc822
+import base64
+import binascii
+import hmac
+import re
+import copy
+import tempfile
+import string
+import time
+import random
+import types
+
+import email.Utils
+
+try:
+ import cStringIO as StringIO
+except:
+ import StringIO
+
+from zope.interface import implements, Interface
+
+from twisted.protocols import basic
+from twisted.protocols import policies
+from twisted.internet import defer
+from twisted.internet import error
+from twisted.internet.defer import maybeDeferred
+from twisted.python import log, text
+from twisted.internet import interfaces
+
+from twisted import cred
+import twisted.cred.error
+import twisted.cred.credentials
+
+
+
+class MessageSet(object):
+ """
+ Essentially an infinite bitfield, with some extra features.
+
+ @type getnext: Function taking C{int} returning C{int}
+ @ivar getnext: A function that returns the next message number,
+ used when iterating through the MessageSet. By default, a function
+ returning the next integer is supplied, but as this can be rather
+ inefficient for sparse UID iterations, it is recommended to supply
+ one when messages are requested by UID. The argument is provided
+ as a hint to the implementation and may be ignored if it makes sense
+ to do so (eg, if an iterator is being used that maintains its own
+ state, it is guaranteed that it will not be called out-of-order).
+ """
+ _empty = []
+
+ def __init__(self, start=_empty, end=_empty):
+ """
+ Create a new MessageSet()
+
+ @type start: Optional C{int}
+ @param start: Start of range, or only message number
+
+ @type end: Optional C{int}
+ @param end: End of range.
+ """
+ self._last = self._empty # Last message/UID in use
+ self.ranges = [] # List of ranges included
+ self.getnext = lambda x: x+1 # A function which will return the next
+ # message id. Handy for UID requests.
+
+ if start is self._empty:
+ return
+
+ if isinstance(start, types.ListType):
+ self.ranges = start[:]
+ self.clean()
+ else:
+ self.add(start,end)
+
+ # Ooo. A property.
+ def last():
+ def _setLast(self, value):
+ if self._last is not self._empty:
+ raise ValueError("last already set")
+
+ self._last = value
+ for i, (l, h) in enumerate(self.ranges):
+ if l is not None:
+ break # There are no more Nones after this
+ l = value
+ if h is None:
+ h = value
+ if l > h:
+ l, h = h, l
+ self.ranges[i] = (l, h)
+
+ self.clean()
+
+ def _getLast(self):
+ return self._last
+
+ doc = '''
+ "Highest" message number, refered to by "*".
+ Must be set before attempting to use the MessageSet.
+ '''
+ return _getLast, _setLast, None, doc
+ last = property(*last())
+
+ def add(self, start, end=_empty):
+ """
+ Add another range
+
+ @type start: C{int}
+ @param start: Start of range, or only message number
+
+ @type end: Optional C{int}
+ @param end: End of range.
+ """
+ if end is self._empty:
+ end = start
+
+ if self._last is not self._empty:
+ if start is None:
+ start = self.last
+ if end is None:
+ end = self.last
+
+ if start > end:
+ # Try to keep in low, high order if possible
+ # (But we don't know what None means, this will keep
+ # None at the start of the ranges list)
+ start, end = end, start
+
+ self.ranges.append((start, end))
+ self.clean()
+
+ def __add__(self, other):
+ if isinstance(other, MessageSet):
+ ranges = self.ranges + other.ranges
+ return MessageSet(ranges)
+ else:
+ res = MessageSet(self.ranges)
+ try:
+ res.add(*other)
+ except TypeError:
+ res.add(other)
+ return res
+
+
+ def extend(self, other):
+ if isinstance(other, MessageSet):
+ self.ranges.extend(other.ranges)
+ self.clean()
+ else:
+ try:
+ self.add(*other)
+ except TypeError:
+ self.add(other)
+
+ return self
+
+
+ def clean(self):
+ """
+ Clean ranges list, combining adjacent ranges
+ """
+
+ self.ranges.sort()
+
+ oldl, oldh = None, None
+ for i,(l, h) in enumerate(self.ranges):
+ if l is None:
+ continue
+ # l is >= oldl and h is >= oldh due to sort()
+ if oldl is not None and l <= oldh + 1:
+ l = oldl
+ h = max(oldh, h)
+ self.ranges[i - 1] = None
+ self.ranges[i] = (l, h)
+
+ oldl, oldh = l, h
+
+ self.ranges = filter(None, self.ranges)
+
+
+ def __contains__(self, value):
+ """
+ May raise TypeError if we encounter unknown "high" values
+ """
+ for l, h in self.ranges:
+ if l is None:
+ raise TypeError(
+ "Can't determine membership; last value not set")
+ if l <= value <= h:
+ return True
+
+ return False
+
+
+ def _iterator(self):
+ for l, h in self.ranges:
+ l = self.getnext(l-1)
+ while l <= h:
+ yield l
+ l = self.getnext(l)
+ if l is None:
+ break
+
+ def __iter__(self):
+ if self.ranges and self.ranges[0][0] is None:
+ raise TypeError("Can't iterate; last value not set")
+
+ return self._iterator()
+
+ def __len__(self):
+ res = 0
+ for l, h in self.ranges:
+ if l is None:
+ raise TypeError("Can't size object; last value not set")
+ res += (h - l) + 1
+
+ return res
+
+ def __str__(self):
+ p = []
+ for low, high in self.ranges:
+ if low == high:
+ if low is None:
+ p.append('*')
+ else:
+ p.append(str(low))
+ elif low is None:
+ p.append('%d:*' % (high,))
+ else:
+ p.append('%d:%d' % (low, high))
+ return ','.join(p)
+
+ def __repr__(self):
+ return '<MessageSet %s>' % (str(self),)
+
+ def __eq__(self, other):
+ if isinstance(other, MessageSet):
+ return self.ranges == other.ranges
+ return False
+
+
+class LiteralString:
+ def __init__(self, size, defered):
+ self.size = size
+ self.data = []
+ self.defer = defered
+
+ def write(self, data):
+ self.size -= len(data)
+ passon = None
+ if self.size > 0:
+ self.data.append(data)
+ else:
+ if self.size:
+ data, passon = data[:self.size], data[self.size:]
+ else:
+ passon = ''
+ if data:
+ self.data.append(data)
+ return passon
+
+ def callback(self, line):
+ """
+ Call defered with data and rest of line
+ """
+ self.defer.callback((''.join(self.data), line))
+
+class LiteralFile:
+ _memoryFileLimit = 1024 * 1024 * 10
+
+ def __init__(self, size, defered):
+ self.size = size
+ self.defer = defered
+ if size > self._memoryFileLimit:
+ self.data = tempfile.TemporaryFile()
+ else:
+ self.data = StringIO.StringIO()
+
+ def write(self, data):
+ self.size -= len(data)
+ passon = None
+ if self.size > 0:
+ self.data.write(data)
+ else:
+ if self.size:
+ data, passon = data[:self.size], data[self.size:]
+ else:
+ passon = ''
+ if data:
+ self.data.write(data)
+ return passon
+
+ def callback(self, line):
+ """
+ Call defered with data and rest of line
+ """
+ self.data.seek(0,0)
+ self.defer.callback((self.data, line))
+
+
+class WriteBuffer:
+ """Buffer up a bunch of writes before sending them all to a transport at once.
+ """
+ def __init__(self, transport, size=8192):
+ self.bufferSize = size
+ self.transport = transport
+ self._length = 0
+ self._writes = []
+
+ def write(self, s):
+ self._length += len(s)
+ self._writes.append(s)
+ if self._length > self.bufferSize:
+ self.flush()
+
+ def flush(self):
+ if self._writes:
+ self.transport.writeSequence(self._writes)
+ self._writes = []
+ self._length = 0
+
+
+class Command:
+ _1_RESPONSES = ('CAPABILITY', 'FLAGS', 'LIST', 'LSUB', 'STATUS', 'SEARCH', 'NAMESPACE')
+ _2_RESPONSES = ('EXISTS', 'EXPUNGE', 'FETCH', 'RECENT')
+ _OK_RESPONSES = ('UIDVALIDITY', 'UNSEEN', 'READ-WRITE', 'READ-ONLY', 'UIDNEXT', 'PERMANENTFLAGS')
+ defer = None
+
+ def __init__(self, command, args=None, wantResponse=(),
+ continuation=None, *contArgs, **contKw):
+ self.command = command
+ self.args = args
+ self.wantResponse = wantResponse
+ self.continuation = lambda x: continuation(x, *contArgs, **contKw)
+ self.lines = []
+
+ def format(self, tag):
+ if self.args is None:
+ return ' '.join((tag, self.command))
+ return ' '.join((tag, self.command, self.args))
+
+ def finish(self, lastLine, unusedCallback):
+ send = []
+ unuse = []
+ for L in self.lines:
+ names = parseNestedParens(L)
+ N = len(names)
+ if (N >= 1 and names[0] in self._1_RESPONSES or
+ N >= 2 and names[1] in self._2_RESPONSES or
+ N >= 2 and names[0] == 'OK' and isinstance(names[1], types.ListType) and names[1][0] in self._OK_RESPONSES):
+ send.append(names)
+ else:
+ unuse.append(names)
+ d, self.defer = self.defer, None
+ d.callback((send, lastLine))
+ if unuse:
+ unusedCallback(unuse)
+
+class LOGINCredentials(cred.credentials.UsernamePassword):
+ def __init__(self):
+ self.challenges = ['Password\0', 'User Name\0']
+ self.responses = ['password', 'username']
+ cred.credentials.UsernamePassword.__init__(self, None, None)
+
+ def getChallenge(self):
+ return self.challenges.pop()
+
+ def setResponse(self, response):
+ setattr(self, self.responses.pop(), response)
+
+ def moreChallenges(self):
+ return bool(self.challenges)
+
+class PLAINCredentials(cred.credentials.UsernamePassword):
+ def __init__(self):
+ cred.credentials.UsernamePassword.__init__(self, None, None)
+
+ def getChallenge(self):
+ return ''
+
+ def setResponse(self, response):
+ parts = response.split('\0')
+ if len(parts) != 3:
+ raise IllegalClientResponse("Malformed Response - wrong number of parts")
+ useless, self.username, self.password = parts
+
+ def moreChallenges(self):
+ return False
+
+class IMAP4Exception(Exception):
+ def __init__(self, *args):
+ Exception.__init__(self, *args)
+
+class IllegalClientResponse(IMAP4Exception): pass
+
+class IllegalOperation(IMAP4Exception): pass
+
+class IllegalMailboxEncoding(IMAP4Exception): pass
+
+class IMailboxListener(Interface):
+ """Interface for objects interested in mailbox events"""
+
+ def modeChanged(writeable):
+ """Indicates that the write status of a mailbox has changed.
+
+ @type writeable: C{bool}
+ @param writeable: A true value if write is now allowed, false
+ otherwise.
+ """
+
+ def flagsChanged(newFlags):
+ """Indicates that the flags of one or more messages have changed.
+
+ @type newFlags: C{dict}
+ @param newFlags: A mapping of message identifiers to tuples of flags
+ now set on that message.
+ """
+
+ def newMessages(exists, recent):
+ """Indicates that the number of messages in a mailbox has changed.
+
+ @type exists: C{int} or C{None}
+ @param exists: The total number of messages now in this mailbox.
+ If the total number of messages has not changed, this should be
+ C{None}.
+
+ @type recent: C{int}
+ @param recent: The number of messages now flagged \\Recent.
+ If the number of recent messages has not changed, this should be
+ C{None}.
+ """
+
+class IMAP4Server(basic.LineReceiver, policies.TimeoutMixin):
+ """
+ Protocol implementation for an IMAP4rev1 server.
+
+ The server can be in any of four states:
+ - Non-authenticated
+ - Authenticated
+ - Selected
+ - Logout
+ """
+ implements(IMailboxListener)
+
+ # Identifier for this server software
+ IDENT = 'Twisted IMAP4rev1 Ready'
+
+ # Number of seconds before idle timeout
+ # Initially 1 minute. Raised to 30 minutes after login.
+ timeOut = 60
+
+ POSTAUTH_TIMEOUT = 60 * 30
+
+ # Whether STARTTLS has been issued successfully yet or not.
+ startedTLS = False
+
+ # Whether our transport supports TLS
+ canStartTLS = False
+
+ # Mapping of tags to commands we have received
+ tags = None
+
+ # The object which will handle logins for us
+ portal = None
+
+ # The account object for this connection
+ account = None
+
+ # Logout callback
+ _onLogout = None
+
+ # The currently selected mailbox
+ mbox = None
+
+ # Command data to be processed when literal data is received
+ _pendingLiteral = None
+
+ # Maximum length to accept for a "short" string literal
+ _literalStringLimit = 4096
+
+ # IChallengeResponse factories for AUTHENTICATE command
+ challengers = None
+
+ # Search terms the implementation of which needs to be passed the
+ # last sequence id value.
+ _requiresLastSequenceId = set(["OR", "NOT"])
+
+ state = 'unauth'
+
+ parseState = 'command'
+
+ def __init__(self, chal = None, contextFactory = None, scheduler = None):
+ if chal is None:
+ chal = {}
+ self.challengers = chal
+ self.ctx = contextFactory
+ if scheduler is None:
+ scheduler = iterateInReactor
+ self._scheduler = scheduler
+ self._queuedAsync = []
+
+ def capabilities(self):
+ cap = {'AUTH': self.challengers.keys()}
+ if self.ctx and self.canStartTLS:
+ if not self.startedTLS and interfaces.ISSLTransport(self.transport, None) is None:
+ cap['LOGINDISABLED'] = None
+ cap['STARTTLS'] = None
+ cap['NAMESPACE'] = None
+ cap['IDLE'] = None
+ return cap
+
+ def connectionMade(self):
+ self.tags = {}
+ self.canStartTLS = interfaces.ITLSTransport(self.transport, None) is not None
+ self.setTimeout(self.timeOut)
+ self.sendServerGreeting()
+
+ def connectionLost(self, reason):
+ self.setTimeout(None)
+ if self._onLogout:
+ self._onLogout()
+ self._onLogout = None
+
+ def timeoutConnection(self):
+ self.sendLine('* BYE Autologout; connection idle too long')
+ self.transport.loseConnection()
+ if self.mbox:
+ self.mbox.removeListener(self)
+ cmbx = ICloseableMailbox(self.mbox, None)
+ if cmbx is not None:
+ maybeDeferred(cmbx.close).addErrback(log.err)
+ self.mbox = None
+ self.state = 'timeout'
+
+ def rawDataReceived(self, data):
+ self.resetTimeout()
+ passon = self._pendingLiteral.write(data)
+ if passon is not None:
+ self.setLineMode(passon)
+
+ # Avoid processing commands while buffers are being dumped to
+ # our transport
+ blocked = None
+
+ def _unblock(self):
+ commands = self.blocked
+ self.blocked = None
+ while commands and self.blocked is None:
+ self.lineReceived(commands.pop(0))
+ if self.blocked is not None:
+ self.blocked.extend(commands)
+
+ def lineReceived(self, line):
+ if self.blocked is not None:
+ self.blocked.append(line)
+ return
+
+ self.resetTimeout()
+
+ f = getattr(self, 'parse_' + self.parseState)
+ try:
+ f(line)
+ except Exception, e:
+ self.sendUntaggedResponse('BAD Server error: ' + str(e))
+ log.err()
+
+ def parse_command(self, line):
+ args = line.split(None, 2)
+ rest = None
+ if len(args) == 3:
+ tag, cmd, rest = args
+ elif len(args) == 2:
+ tag, cmd = args
+ elif len(args) == 1:
+ tag = args[0]
+ self.sendBadResponse(tag, 'Missing command')
+ return None
+ else:
+ self.sendBadResponse(None, 'Null command')
+ return None
+
+ cmd = cmd.upper()
+ try:
+ return self.dispatchCommand(tag, cmd, rest)
+ except IllegalClientResponse, e:
+ self.sendBadResponse(tag, 'Illegal syntax: ' + str(e))
+ except IllegalOperation, e:
+ self.sendNegativeResponse(tag, 'Illegal operation: ' + str(e))
+ except IllegalMailboxEncoding, e:
+ self.sendNegativeResponse(tag, 'Illegal mailbox name: ' + str(e))
+
+ def parse_pending(self, line):
+ d = self._pendingLiteral
+ self._pendingLiteral = None
+ self.parseState = 'command'
+ d.callback(line)
+
+ def dispatchCommand(self, tag, cmd, rest, uid=None):
+ f = self.lookupCommand(cmd)
+ if f:
+ fn = f[0]
+ parseargs = f[1:]
+ self.__doCommand(tag, fn, [self, tag], parseargs, rest, uid)
+ else:
+ self.sendBadResponse(tag, 'Unsupported command')
+
+ def lookupCommand(self, cmd):
+ return getattr(self, '_'.join((self.state, cmd.upper())), None)
+
+ def __doCommand(self, tag, handler, args, parseargs, line, uid):
+ for (i, arg) in enumerate(parseargs):
+ if callable(arg):
+ parseargs = parseargs[i+1:]
+ maybeDeferred(arg, self, line).addCallback(
+ self.__cbDispatch, tag, handler, args,
+ parseargs, uid).addErrback(self.__ebDispatch, tag)
+ return
+ else:
+ args.append(arg)
+
+ if line:
+ # Too many arguments
+ raise IllegalClientResponse("Too many arguments for command: " + repr(line))
+
+ if uid is not None:
+ handler(uid=uid, *args)
+ else:
+ handler(*args)
+
+ def __cbDispatch(self, (arg, rest), tag, fn, args, parseargs, uid):
+ args.append(arg)
+ self.__doCommand(tag, fn, args, parseargs, rest, uid)
+
+ def __ebDispatch(self, failure, tag):
+ if failure.check(IllegalClientResponse):
+ self.sendBadResponse(tag, 'Illegal syntax: ' + str(failure.value))
+ elif failure.check(IllegalOperation):
+ self.sendNegativeResponse(tag, 'Illegal operation: ' +
+ str(failure.value))
+ elif failure.check(IllegalMailboxEncoding):
+ self.sendNegativeResponse(tag, 'Illegal mailbox name: ' +
+ str(failure.value))
+ else:
+ self.sendBadResponse(tag, 'Server error: ' + str(failure.value))
+ log.err(failure)
+
+ def _stringLiteral(self, size):
+ if size > self._literalStringLimit:
+ raise IllegalClientResponse(
+ "Literal too long! I accept at most %d octets" %
+ (self._literalStringLimit,))
+ d = defer.Deferred()
+ self.parseState = 'pending'
+ self._pendingLiteral = LiteralString(size, d)
+ self.sendContinuationRequest('Ready for %d octets of text' % size)
+ self.setRawMode()
+ return d
+
+ def _fileLiteral(self, size):
+ d = defer.Deferred()
+ self.parseState = 'pending'
+ self._pendingLiteral = LiteralFile(size, d)
+ self.sendContinuationRequest('Ready for %d octets of data' % size)
+ self.setRawMode()
+ return d
+
+ def arg_astring(self, line):
+ """
+ Parse an astring from the line, return (arg, rest), possibly
+ via a deferred (to handle literals)
+ """
+ line = line.strip()
+ if not line:
+ raise IllegalClientResponse("Missing argument")
+ d = None
+ arg, rest = None, None
+ if line[0] == '"':
+ try:
+ spam, arg, rest = line.split('"',2)
+ rest = rest[1:] # Strip space
+ except ValueError:
+ raise IllegalClientResponse("Unmatched quotes")
+ elif line[0] == '{':
+ # literal
+ if line[-1] != '}':
+ raise IllegalClientResponse("Malformed literal")
+ try:
+ size = int(line[1:-1])
+ except ValueError:
+ raise IllegalClientResponse("Bad literal size: " + line[1:-1])
+ d = self._stringLiteral(size)
+ else:
+ arg = line.split(' ',1)
+ if len(arg) == 1:
+ arg.append('')
+ arg, rest = arg
+ return d or (arg, rest)
+
+ # ATOM: Any CHAR except ( ) { % * " \ ] CTL SP (CHAR is 7bit)
+ atomre = re.compile(r'(?P<atom>[^\](){%*"\\\x00-\x20\x80-\xff]+)( (?P<rest>.*$)|$)')
+
+ def arg_atom(self, line):
+ """
+ Parse an atom from the line
+ """
+ if not line:
+ raise IllegalClientResponse("Missing argument")
+ m = self.atomre.match(line)
+ if m:
+ return m.group('atom'), m.group('rest')
+ else:
+ raise IllegalClientResponse("Malformed ATOM")
+
+ def arg_plist(self, line):
+ """
+ Parse a (non-nested) parenthesised list from the line
+ """
+ if not line:
+ raise IllegalClientResponse("Missing argument")
+
+ if line[0] != "(":
+ raise IllegalClientResponse("Missing parenthesis")
+
+ i = line.find(")")
+
+ if i == -1:
+ raise IllegalClientResponse("Mismatched parenthesis")
+
+ return (parseNestedParens(line[1:i],0), line[i+2:])
+
+ def arg_literal(self, line):
+ """
+ Parse a literal from the line
+ """
+ if not line:
+ raise IllegalClientResponse("Missing argument")
+
+ if line[0] != '{':
+ raise IllegalClientResponse("Missing literal")
+
+ if line[-1] != '}':
+ raise IllegalClientResponse("Malformed literal")
+
+ try:
+ size = int(line[1:-1])
+ except ValueError:
+ raise IllegalClientResponse("Bad literal size: " + line[1:-1])
+
+ return self._fileLiteral(size)
+
+ def arg_searchkeys(self, line):
+ """
+ searchkeys
+ """
+ query = parseNestedParens(line)
+ # XXX Should really use list of search terms and parse into
+ # a proper tree
+
+ return (query, '')
+
+ def arg_seqset(self, line):
+ """
+ sequence-set
+ """
+ rest = ''
+ arg = line.split(' ',1)
+ if len(arg) == 2:
+ rest = arg[1]
+ arg = arg[0]
+
+ try:
+ return (parseIdList(arg), rest)
+ except IllegalIdentifierError, e:
+ raise IllegalClientResponse("Bad message number " + str(e))
+
+ def arg_fetchatt(self, line):
+ """
+ fetch-att
+ """
+ p = _FetchParser()
+ p.parseString(line)
+ return (p.result, '')
+
+ def arg_flaglist(self, line):
+ """
+ Flag part of store-att-flag
+ """
+ flags = []
+ if line[0] == '(':
+ if line[-1] != ')':
+ raise IllegalClientResponse("Mismatched parenthesis")
+ line = line[1:-1]
+
+ while line:
+ m = self.atomre.search(line)
+ if not m:
+ raise IllegalClientResponse("Malformed flag")
+ if line[0] == '\\' and m.start() == 1:
+ flags.append('\\' + m.group('atom'))
+ elif m.start() == 0:
+ flags.append(m.group('atom'))
+ else:
+ raise IllegalClientResponse("Malformed flag")
+ line = m.group('rest')
+
+ return (flags, '')
+
+ def arg_line(self, line):
+ """
+ Command line of UID command
+ """
+ return (line, '')
+
+ def opt_plist(self, line):
+ """
+ Optional parenthesised list
+ """
+ if line.startswith('('):
+ return self.arg_plist(line)
+ else:
+ return (None, line)
+
+ def opt_datetime(self, line):
+ """
+ Optional date-time string
+ """
+ if line.startswith('"'):
+ try:
+ spam, date, rest = line.split('"',2)
+ except IndexError:
+ raise IllegalClientResponse("Malformed date-time")
+ return (date, rest[1:])
+ else:
+ return (None, line)
+
+ def opt_charset(self, line):
+ """
+ Optional charset of SEARCH command
+ """
+ if line[:7].upper() == 'CHARSET':
+ arg = line.split(' ',2)
+ if len(arg) == 1:
+ raise IllegalClientResponse("Missing charset identifier")
+ if len(arg) == 2:
+ arg.append('')
+ spam, arg, rest = arg
+ return (arg, rest)
+ else:
+ return (None, line)
+
+ def sendServerGreeting(self):
+ msg = '[CAPABILITY %s] %s' % (' '.join(self.listCapabilities()), self.IDENT)
+ self.sendPositiveResponse(message=msg)
+
+ def sendBadResponse(self, tag = None, message = ''):
+ self._respond('BAD', tag, message)
+
+ def sendPositiveResponse(self, tag = None, message = ''):
+ self._respond('OK', tag, message)
+
+ def sendNegativeResponse(self, tag = None, message = ''):
+ self._respond('NO', tag, message)
+
+ def sendUntaggedResponse(self, message, async=False):
+ if not async or (self.blocked is None):
+ self._respond(message, None, None)
+ else:
+ self._queuedAsync.append(message)
+
+ def sendContinuationRequest(self, msg = 'Ready for additional command text'):
+ if msg:
+ self.sendLine('+ ' + msg)
+ else:
+ self.sendLine('+')
+
+ def _respond(self, state, tag, message):
+ if state in ('OK', 'NO', 'BAD') and self._queuedAsync:
+ lines = self._queuedAsync
+ self._queuedAsync = []
+ for msg in lines:
+ self._respond(msg, None, None)
+ if not tag:
+ tag = '*'
+ if message:
+ self.sendLine(' '.join((tag, state, message)))
+ else:
+ self.sendLine(' '.join((tag, state)))
+
+ def listCapabilities(self):
+ caps = ['IMAP4rev1']
+ for c, v in self.capabilities().iteritems():
+ if v is None:
+ caps.append(c)
+ elif len(v):
+ caps.extend([('%s=%s' % (c, cap)) for cap in v])
+ return caps
+
+ def do_CAPABILITY(self, tag):
+ self.sendUntaggedResponse('CAPABILITY ' + ' '.join(self.listCapabilities()))
+ self.sendPositiveResponse(tag, 'CAPABILITY completed')
+
+ unauth_CAPABILITY = (do_CAPABILITY,)
+ auth_CAPABILITY = unauth_CAPABILITY
+ select_CAPABILITY = unauth_CAPABILITY
+ logout_CAPABILITY = unauth_CAPABILITY
+
+ def do_LOGOUT(self, tag):
+ self.sendUntaggedResponse('BYE Nice talking to you')
+ self.sendPositiveResponse(tag, 'LOGOUT successful')
+ self.transport.loseConnection()
+
+ unauth_LOGOUT = (do_LOGOUT,)
+ auth_LOGOUT = unauth_LOGOUT
+ select_LOGOUT = unauth_LOGOUT
+ logout_LOGOUT = unauth_LOGOUT
+
+ def do_NOOP(self, tag):
+ self.sendPositiveResponse(tag, 'NOOP No operation performed')
+
+ unauth_NOOP = (do_NOOP,)
+ auth_NOOP = unauth_NOOP
+ select_NOOP = unauth_NOOP
+ logout_NOOP = unauth_NOOP
+
+ def do_AUTHENTICATE(self, tag, args):
+ args = args.upper().strip()
+ if args not in self.challengers:
+ self.sendNegativeResponse(tag, 'AUTHENTICATE method unsupported')
+ else:
+ self.authenticate(self.challengers[args](), tag)
+
+ unauth_AUTHENTICATE = (do_AUTHENTICATE, arg_atom)
+
+ def authenticate(self, chal, tag):
+ if self.portal is None:
+ self.sendNegativeResponse(tag, 'Temporary authentication failure')
+ return
+
+ self._setupChallenge(chal, tag)
+
+ def _setupChallenge(self, chal, tag):
+ try:
+ challenge = chal.getChallenge()
+ except Exception, e:
+ self.sendBadResponse(tag, 'Server error: ' + str(e))
+ else:
+ coded = base64.encodestring(challenge)[:-1]
+ self.parseState = 'pending'
+ self._pendingLiteral = defer.Deferred()
+ self.sendContinuationRequest(coded)
+ self._pendingLiteral.addCallback(self.__cbAuthChunk, chal, tag)
+ self._pendingLiteral.addErrback(self.__ebAuthChunk, tag)
+
+ def __cbAuthChunk(self, result, chal, tag):
+ try:
+ uncoded = base64.decodestring(result)
+ except binascii.Error:
+ raise IllegalClientResponse("Malformed Response - not base64")
+
+ chal.setResponse(uncoded)
+ if chal.moreChallenges():
+ self._setupChallenge(chal, tag)
+ else:
+ self.portal.login(chal, None, IAccount).addCallbacks(
+ self.__cbAuthResp,
+ self.__ebAuthResp,
+ (tag,), None, (tag,), None
+ )
+
+ def __cbAuthResp(self, (iface, avatar, logout), tag):
+ assert iface is IAccount, "IAccount is the only supported interface"
+ self.account = avatar
+ self.state = 'auth'
+ self._onLogout = logout
+ self.sendPositiveResponse(tag, 'Authentication successful')
+ self.setTimeout(self.POSTAUTH_TIMEOUT)
+
+ def __ebAuthResp(self, failure, tag):
+ if failure.check(cred.error.UnauthorizedLogin):
+ self.sendNegativeResponse(tag, 'Authentication failed: unauthorized')
+ elif failure.check(cred.error.UnhandledCredentials):
+ self.sendNegativeResponse(tag, 'Authentication failed: server misconfigured')
+ else:
+ self.sendBadResponse(tag, 'Server error: login failed unexpectedly')
+ log.err(failure)
+
+ def __ebAuthChunk(self, failure, tag):
+ self.sendNegativeResponse(tag, 'Authentication failed: ' + str(failure.value))
+
+ def do_STARTTLS(self, tag):
+ if self.startedTLS:
+ self.sendNegativeResponse(tag, 'TLS already negotiated')
+ elif self.ctx and self.canStartTLS:
+ self.sendPositiveResponse(tag, 'Begin TLS negotiation now')
+ self.transport.startTLS(self.ctx)
+ self.startedTLS = True
+ self.challengers = self.challengers.copy()
+ if 'LOGIN' not in self.challengers:
+ self.challengers['LOGIN'] = LOGINCredentials
+ if 'PLAIN' not in self.challengers:
+ self.challengers['PLAIN'] = PLAINCredentials
+ else:
+ self.sendNegativeResponse(tag, 'TLS not available')
+
+ unauth_STARTTLS = (do_STARTTLS,)
+
+ def do_LOGIN(self, tag, user, passwd):
+ if 'LOGINDISABLED' in self.capabilities():
+ self.sendBadResponse(tag, 'LOGIN is disabled before STARTTLS')
+ return
+
+ maybeDeferred(self.authenticateLogin, user, passwd
+ ).addCallback(self.__cbLogin, tag
+ ).addErrback(self.__ebLogin, tag
+ )
+
+ unauth_LOGIN = (do_LOGIN, arg_astring, arg_astring)
+
+ def authenticateLogin(self, user, passwd):
+ """Lookup the account associated with the given parameters
+
+ Override this method to define the desired authentication behavior.
+
+ The default behavior is to defer authentication to C{self.portal}
+ if it is not None, or to deny the login otherwise.
+
+ @type user: C{str}
+ @param user: The username to lookup
+
+ @type passwd: C{str}
+ @param passwd: The password to login with
+ """
+ if self.portal:
+ return self.portal.login(
+ cred.credentials.UsernamePassword(user, passwd),
+ None, IAccount
+ )
+ raise cred.error.UnauthorizedLogin()
+
+ def __cbLogin(self, (iface, avatar, logout), tag):
+ if iface is not IAccount:
+ self.sendBadResponse(tag, 'Server error: login returned unexpected value')
+ log.err("__cbLogin called with %r, IAccount expected" % (iface,))
+ else:
+ self.account = avatar
+ self._onLogout = logout
+ self.sendPositiveResponse(tag, 'LOGIN succeeded')
+ self.state = 'auth'
+ self.setTimeout(self.POSTAUTH_TIMEOUT)
+
+ def __ebLogin(self, failure, tag):
+ if failure.check(cred.error.UnauthorizedLogin):
+ self.sendNegativeResponse(tag, 'LOGIN failed')
+ else:
+ self.sendBadResponse(tag, 'Server error: ' + str(failure.value))
+ log.err(failure)
+
+ def do_NAMESPACE(self, tag):
+ personal = public = shared = None
+ np = INamespacePresenter(self.account, None)
+ if np is not None:
+ personal = np.getPersonalNamespaces()
+ public = np.getSharedNamespaces()
+ shared = np.getSharedNamespaces()
+ self.sendUntaggedResponse('NAMESPACE ' + collapseNestedLists([personal, public, shared]))
+ self.sendPositiveResponse(tag, "NAMESPACE command completed")
+
+ auth_NAMESPACE = (do_NAMESPACE,)
+ select_NAMESPACE = auth_NAMESPACE
+
+ def _parseMbox(self, name):
+ if isinstance(name, unicode):
+ return name
+ try:
+ return name.decode('imap4-utf-7')
+ except:
+ log.err()
+ raise IllegalMailboxEncoding(name)
+
+ def _selectWork(self, tag, name, rw, cmdName):
+ if self.mbox:
+ self.mbox.removeListener(self)
+ cmbx = ICloseableMailbox(self.mbox, None)
+ if cmbx is not None:
+ maybeDeferred(cmbx.close).addErrback(log.err)
+ self.mbox = None
+ self.state = 'auth'
+
+ name = self._parseMbox(name)
+ maybeDeferred(self.account.select, self._parseMbox(name), rw
+ ).addCallback(self._cbSelectWork, cmdName, tag
+ ).addErrback(self._ebSelectWork, cmdName, tag
+ )
+
+ def _ebSelectWork(self, failure, cmdName, tag):
+ self.sendBadResponse(tag, "%s failed: Server error" % (cmdName,))
+ log.err(failure)
+
+ def _cbSelectWork(self, mbox, cmdName, tag):
+ if mbox is None:
+ self.sendNegativeResponse(tag, 'No such mailbox')
+ return
+ if '\\noselect' in [s.lower() for s in mbox.getFlags()]:
+ self.sendNegativeResponse(tag, 'Mailbox cannot be selected')
+ return
+
+ flags = mbox.getFlags()
+ self.sendUntaggedResponse(str(mbox.getMessageCount()) + ' EXISTS')
+ self.sendUntaggedResponse(str(mbox.getRecentCount()) + ' RECENT')
+ self.sendUntaggedResponse('FLAGS (%s)' % ' '.join(flags))
+ self.sendPositiveResponse(None, '[UIDVALIDITY %d]' % mbox.getUIDValidity())
+
+ s = mbox.isWriteable() and 'READ-WRITE' or 'READ-ONLY'
+ mbox.addListener(self)
+ self.sendPositiveResponse(tag, '[%s] %s successful' % (s, cmdName))
+ self.state = 'select'
+ self.mbox = mbox
+
+ auth_SELECT = ( _selectWork, arg_astring, 1, 'SELECT' )
+ select_SELECT = auth_SELECT
+
+ auth_EXAMINE = ( _selectWork, arg_astring, 0, 'EXAMINE' )
+ select_EXAMINE = auth_EXAMINE
+
+
+ def do_IDLE(self, tag):
+ self.sendContinuationRequest(None)
+ self.parseTag = tag
+ self.lastState = self.parseState
+ self.parseState = 'idle'
+
+ def parse_idle(self, *args):
+ self.parseState = self.lastState
+ del self.lastState
+ self.sendPositiveResponse(self.parseTag, "IDLE terminated")
+ del self.parseTag
+
+ select_IDLE = ( do_IDLE, )
+ auth_IDLE = select_IDLE
+
+
+ def do_CREATE(self, tag, name):
+ name = self._parseMbox(name)
+ try:
+ result = self.account.create(name)
+ except MailboxException, c:
+ self.sendNegativeResponse(tag, str(c))
+ except:
+ self.sendBadResponse(tag, "Server error encountered while creating mailbox")
+ log.err()
+ else:
+ if result:
+ self.sendPositiveResponse(tag, 'Mailbox created')
+ else:
+ self.sendNegativeResponse(tag, 'Mailbox not created')
+
+ auth_CREATE = (do_CREATE, arg_astring)
+ select_CREATE = auth_CREATE
+
+ def do_DELETE(self, tag, name):
+ name = self._parseMbox(name)
+ if name.lower() == 'inbox':
+ self.sendNegativeResponse(tag, 'You cannot delete the inbox')
+ return
+ try:
+ self.account.delete(name)
+ except MailboxException, m:
+ self.sendNegativeResponse(tag, str(m))
+ except:
+ self.sendBadResponse(tag, "Server error encountered while deleting mailbox")
+ log.err()
+ else:
+ self.sendPositiveResponse(tag, 'Mailbox deleted')
+
+ auth_DELETE = (do_DELETE, arg_astring)
+ select_DELETE = auth_DELETE
+
+ def do_RENAME(self, tag, oldname, newname):
+ oldname, newname = [self._parseMbox(n) for n in oldname, newname]
+ if oldname.lower() == 'inbox' or newname.lower() == 'inbox':
+ self.sendNegativeResponse(tag, 'You cannot rename the inbox, or rename another mailbox to inbox.')
+ return
+ try:
+ self.account.rename(oldname, newname)
+ except TypeError:
+ self.sendBadResponse(tag, 'Invalid command syntax')
+ except MailboxException, m:
+ self.sendNegativeResponse(tag, str(m))
+ except:
+ self.sendBadResponse(tag, "Server error encountered while renaming mailbox")
+ log.err()
+ else:
+ self.sendPositiveResponse(tag, 'Mailbox renamed')
+
+ auth_RENAME = (do_RENAME, arg_astring, arg_astring)
+ select_RENAME = auth_RENAME
+
+ def do_SUBSCRIBE(self, tag, name):
+ name = self._parseMbox(name)
+ try:
+ self.account.subscribe(name)
+ except MailboxException, m:
+ self.sendNegativeResponse(tag, str(m))
+ except:
+ self.sendBadResponse(tag, "Server error encountered while subscribing to mailbox")
+ log.err()
+ else:
+ self.sendPositiveResponse(tag, 'Subscribed')
+
+ auth_SUBSCRIBE = (do_SUBSCRIBE, arg_astring)
+ select_SUBSCRIBE = auth_SUBSCRIBE
+
+ def do_UNSUBSCRIBE(self, tag, name):
+ name = self._parseMbox(name)
+ try:
+ self.account.unsubscribe(name)
+ except MailboxException, m:
+ self.sendNegativeResponse(tag, str(m))
+ except:
+ self.sendBadResponse(tag, "Server error encountered while unsubscribing from mailbox")
+ log.err()
+ else:
+ self.sendPositiveResponse(tag, 'Unsubscribed')
+
+ auth_UNSUBSCRIBE = (do_UNSUBSCRIBE, arg_astring)
+ select_UNSUBSCRIBE = auth_UNSUBSCRIBE
+
+ def _listWork(self, tag, ref, mbox, sub, cmdName):
+ mbox = self._parseMbox(mbox)
+ maybeDeferred(self.account.listMailboxes, ref, mbox
+ ).addCallback(self._cbListWork, tag, sub, cmdName
+ ).addErrback(self._ebListWork, tag
+ )
+
+ def _cbListWork(self, mailboxes, tag, sub, cmdName):
+ for (name, box) in mailboxes:
+ if not sub or self.account.isSubscribed(name):
+ flags = box.getFlags()
+ delim = box.getHierarchicalDelimiter()
+ resp = (DontQuoteMe(cmdName), map(DontQuoteMe, flags), delim, name.encode('imap4-utf-7'))
+ self.sendUntaggedResponse(collapseNestedLists(resp))
+ self.sendPositiveResponse(tag, '%s completed' % (cmdName,))
+
+ def _ebListWork(self, failure, tag):
+ self.sendBadResponse(tag, "Server error encountered while listing mailboxes.")
+ log.err(failure)
+
+ auth_LIST = (_listWork, arg_astring, arg_astring, 0, 'LIST')
+ select_LIST = auth_LIST
+
+ auth_LSUB = (_listWork, arg_astring, arg_astring, 1, 'LSUB')
+ select_LSUB = auth_LSUB
+
+ def do_STATUS(self, tag, mailbox, names):
+ mailbox = self._parseMbox(mailbox)
+ maybeDeferred(self.account.select, mailbox, 0
+ ).addCallback(self._cbStatusGotMailbox, tag, mailbox, names
+ ).addErrback(self._ebStatusGotMailbox, tag
+ )
+
+ def _cbStatusGotMailbox(self, mbox, tag, mailbox, names):
+ if mbox:
+ maybeDeferred(mbox.requestStatus, names).addCallbacks(
+ self.__cbStatus, self.__ebStatus,
+ (tag, mailbox), None, (tag, mailbox), None
+ )
+ else:
+ self.sendNegativeResponse(tag, "Could not open mailbox")
+
+ def _ebStatusGotMailbox(self, failure, tag):
+ self.sendBadResponse(tag, "Server error encountered while opening mailbox.")
+ log.err(failure)
+
+ auth_STATUS = (do_STATUS, arg_astring, arg_plist)
+ select_STATUS = auth_STATUS
+
+ def __cbStatus(self, status, tag, box):
+ line = ' '.join(['%s %s' % x for x in status.iteritems()])
+ self.sendUntaggedResponse('STATUS %s (%s)' % (box, line))
+ self.sendPositiveResponse(tag, 'STATUS complete')
+
+ def __ebStatus(self, failure, tag, box):
+ self.sendBadResponse(tag, 'STATUS %s failed: %s' % (box, str(failure.value)))
+
+ def do_APPEND(self, tag, mailbox, flags, date, message):
+ mailbox = self._parseMbox(mailbox)
+ maybeDeferred(self.account.select, mailbox
+ ).addCallback(self._cbAppendGotMailbox, tag, flags, date, message
+ ).addErrback(self._ebAppendGotMailbox, tag
+ )
+
+ def _cbAppendGotMailbox(self, mbox, tag, flags, date, message):
+ if not mbox:
+ self.sendNegativeResponse(tag, '[TRYCREATE] No such mailbox')
+ return
+
+ d = mbox.addMessage(message, flags, date)
+ d.addCallback(self.__cbAppend, tag, mbox)
+ d.addErrback(self.__ebAppend, tag)
+
+ def _ebAppendGotMailbox(self, failure, tag):
+ self.sendBadResponse(tag, "Server error encountered while opening mailbox.")
+ log.err(failure)
+
+ auth_APPEND = (do_APPEND, arg_astring, opt_plist, opt_datetime,
+ arg_literal)
+ select_APPEND = auth_APPEND
+
+ def __cbAppend(self, result, tag, mbox):
+ self.sendUntaggedResponse('%d EXISTS' % mbox.getMessageCount())
+ self.sendPositiveResponse(tag, 'APPEND complete')
+
+ def __ebAppend(self, failure, tag):
+ self.sendBadResponse(tag, 'APPEND failed: ' + str(failure.value))
+
+ def do_CHECK(self, tag):
+ d = self.checkpoint()
+ if d is None:
+ self.__cbCheck(None, tag)
+ else:
+ d.addCallbacks(
+ self.__cbCheck,
+ self.__ebCheck,
+ callbackArgs=(tag,),
+ errbackArgs=(tag,)
+ )
+ select_CHECK = (do_CHECK,)
+
+ def __cbCheck(self, result, tag):
+ self.sendPositiveResponse(tag, 'CHECK completed')
+
+ def __ebCheck(self, failure, tag):
+ self.sendBadResponse(tag, 'CHECK failed: ' + str(failure.value))
+
+ def checkpoint(self):
+ """Called when the client issues a CHECK command.
+
+ This should perform any checkpoint operations required by the server.
+ It may be a long running operation, but may not block. If it returns
+ a deferred, the client will only be informed of success (or failure)
+ when the deferred's callback (or errback) is invoked.
+ """
+ return None
+
+ def do_CLOSE(self, tag):
+ d = None
+ if self.mbox.isWriteable():
+ d = maybeDeferred(self.mbox.expunge)
+ cmbx = ICloseableMailbox(self.mbox, None)
+ if cmbx is not None:
+ if d is not None:
+ d.addCallback(lambda result: cmbx.close())
+ else:
+ d = maybeDeferred(cmbx.close)
+ if d is not None:
+ d.addCallbacks(self.__cbClose, self.__ebClose, (tag,), None, (tag,), None)
+ else:
+ self.__cbClose(None, tag)
+
+ select_CLOSE = (do_CLOSE,)
+
+ def __cbClose(self, result, tag):
+ self.sendPositiveResponse(tag, 'CLOSE completed')
+ self.mbox.removeListener(self)
+ self.mbox = None
+ self.state = 'auth'
+
+ def __ebClose(self, failure, tag):
+ self.sendBadResponse(tag, 'CLOSE failed: ' + str(failure.value))
+
+ def do_EXPUNGE(self, tag):
+ if self.mbox.isWriteable():
+ maybeDeferred(self.mbox.expunge).addCallbacks(
+ self.__cbExpunge, self.__ebExpunge, (tag,), None, (tag,), None
+ )
+ else:
+ self.sendNegativeResponse(tag, 'EXPUNGE ignored on read-only mailbox')
+
+ select_EXPUNGE = (do_EXPUNGE,)
+
+ def __cbExpunge(self, result, tag):
+ for e in result:
+ self.sendUntaggedResponse('%d EXPUNGE' % e)
+ self.sendPositiveResponse(tag, 'EXPUNGE completed')
+
+ def __ebExpunge(self, failure, tag):
+ self.sendBadResponse(tag, 'EXPUNGE failed: ' + str(failure.value))
+ log.err(failure)
+
+ def do_SEARCH(self, tag, charset, query, uid=0):
+ sm = ISearchableMailbox(self.mbox, None)
+ if sm is not None:
+ maybeDeferred(sm.search, query, uid=uid).addCallbacks(
+ self.__cbSearch, self.__ebSearch,
+ (tag, self.mbox, uid), None, (tag,), None
+ )
+ else:
+ s = parseIdList('1:*')
+ maybeDeferred(self.mbox.fetch, s, uid=uid).addCallbacks(
+ self.__cbManualSearch, self.__ebSearch,
+ (tag, self.mbox, query, uid), None, (tag,), None
+ )
+
+ select_SEARCH = (do_SEARCH, opt_charset, arg_searchkeys)
+
+ def __cbSearch(self, result, tag, mbox, uid):
+ if uid:
+ result = map(mbox.getUID, result)
+ ids = ' '.join([str(i) for i in result])
+ self.sendUntaggedResponse('SEARCH ' + ids)
+ self.sendPositiveResponse(tag, 'SEARCH completed')
+
+
+ def __cbManualSearch(self, result, tag, mbox, query, uid,
+ searchResults=None):
+ if searchResults is None:
+ searchResults = []
+ i = 0
+
+ lastSequenceId = result[-1][0]
+
+ for (i, (id, msg)) in zip(range(5), result):
+ # searchFilter and singleSearchStep will mutate the query. Dang.
+ # Copy it here or else things will go poorly for subsequent
+ # messages.
+ if self._searchFilter(copy.deepcopy(query), id, msg, lastSequenceId):
+ if uid:
+ searchResults.append(str(msg.getUID()))
+ else:
+ searchResults.append(str(id))
+ if i == 4:
+ from twisted.internet import reactor
+ reactor.callLater(
+ 0, self.__cbManualSearch, result, tag, mbox, query, uid,
+ searchResults)
+ else:
+ if searchResults:
+ self.sendUntaggedResponse('SEARCH ' + ' '.join(searchResults))
+ self.sendPositiveResponse(tag, 'SEARCH completed')
+
+
+ def _searchFilter(self, query, id, msg, lastSequenceId):
+ """
+ Pop search terms from the beginning of C{query} until there are none
+ left and apply them to the given message.
+
+ @param query: A list representing the parsed form of the search query.
+
+ @param id: The sequence number of the message being checked.
+
+ @param msg: The message being checked.
+
+ @param lastSequenceId: The highest sequence number of any message in
+ the mailbox being searched.
+
+ @return: Boolean indicating whether all of the query terms match the
+ message.
+ """
+ while query:
+ if not self._singleSearchStep(query, id, msg, lastSequenceId):
+ return False
+ return True
+
+
+ def _singleSearchStep(self, query, id, msg, lastSequenceId):
+ """
+ Pop one search term from the beginning of C{query} (possibly more than
+ one element) and return whether it matches the given message.
+
+ @param query: A list representing the parsed form of the search query.
+
+ @param id: The sequence number of the message being checked.
+
+ @param msg: The message being checked.
+
+ @param lastSequenceId: The highest sequence number of any message in
+ the mailbox being searched.
+
+ @return: Boolean indicating whether the query term matched the message.
+ """
+ q = query.pop(0)
+ if isinstance(q, list):
+ if not self._searchFilter(q, id, msg, lastSequenceId):
+ return False
+ else:
+ c = q.upper()
+ if not c[:1].isalpha():
+ # A search term may be a word like ALL, ANSWERED, BCC, etc (see
+ # below) or it may be a message sequence set. Here we
+ # recognize a message sequence set "N:M".
+ messageSet = parseIdList(c)
+ messageSet.last = lastSequenceId
+ return id in messageSet
+ else:
+ f = getattr(self, 'search_' + c)
+ if f is not None:
+ if c in self._requiresLastSequenceId:
+ result = f(query, id, msg, lastSequenceId)
+ else:
+ result = f(query, id, msg)
+ if not result:
+ return False
+ return True
+
+ def search_ALL(self, query, id, msg):
+ return True
+
+ def search_ANSWERED(self, query, id, msg):
+ return '\\Answered' in msg.getFlags()
+
+ def search_BCC(self, query, id, msg):
+ bcc = msg.getHeaders(False, 'bcc').get('bcc', '')
+ return bcc.lower().find(query.pop(0).lower()) != -1
+
+ def search_BEFORE(self, query, id, msg):
+ date = parseTime(query.pop(0))
+ return rfc822.parsedate(msg.getInternalDate()) < date
+
+ def search_BODY(self, query, id, msg):
+ body = query.pop(0).lower()
+ return text.strFile(body, msg.getBodyFile(), False)
+
+ def search_CC(self, query, id, msg):
+ cc = msg.getHeaders(False, 'cc').get('cc', '')
+ return cc.lower().find(query.pop(0).lower()) != -1
+
+ def search_DELETED(self, query, id, msg):
+ return '\\Deleted' in msg.getFlags()
+
+ def search_DRAFT(self, query, id, msg):
+ return '\\Draft' in msg.getFlags()
+
+ def search_FLAGGED(self, query, id, msg):
+ return '\\Flagged' in msg.getFlags()
+
+ def search_FROM(self, query, id, msg):
+ fm = msg.getHeaders(False, 'from').get('from', '')
+ return fm.lower().find(query.pop(0).lower()) != -1
+
+ def search_HEADER(self, query, id, msg):
+ hdr = query.pop(0).lower()
+ hdr = msg.getHeaders(False, hdr).get(hdr, '')
+ return hdr.lower().find(query.pop(0).lower()) != -1
+
+ def search_KEYWORD(self, query, id, msg):
+ query.pop(0)
+ return False
+
+ def search_LARGER(self, query, id, msg):
+ return int(query.pop(0)) < msg.getSize()
+
+ def search_NEW(self, query, id, msg):
+ return '\\Recent' in msg.getFlags() and '\\Seen' not in msg.getFlags()
+
+ def search_NOT(self, query, id, msg, lastSequenceId):
+ return not self._singleSearchStep(query, id, msg, lastSequenceId)
+
+ def search_OLD(self, query, id, msg):
+ return '\\Recent' not in msg.getFlags()
+
+ def search_ON(self, query, id, msg):
+ date = parseTime(query.pop(0))
+ return rfc822.parsedate(msg.getInternalDate()) == date
+
+ def search_OR(self, query, id, msg, lastSequenceId):
+ a = self._singleSearchStep(query, id, msg, lastSequenceId)
+ b = self._singleSearchStep(query, id, msg, lastSequenceId)
+ return a or b
+
+ def search_RECENT(self, query, id, msg):
+ return '\\Recent' in msg.getFlags()
+
+ def search_SEEN(self, query, id, msg):
+ return '\\Seen' in msg.getFlags()
+
+ def search_SENTBEFORE(self, query, id, msg):
+ """
+ Returns C{True} if the message date is earlier than the query date.
+
+ @type query: A C{list} of C{str}
+ @param query: A list whose first element starts with a stringified date
+ that is a fragment of an L{imap4.Query()}. The date must be in the
+ format 'DD-Mon-YYYY', for example '03-March-2003' or '03-Mar-2003'.
+
+ @type msg: Provider of L{imap4.IMessage}
+ """
+ date = msg.getHeaders(False, 'date').get('date', '')
+ date = rfc822.parsedate(date)
+ return date < parseTime(query.pop(0))
+
+ def search_SENTON(self, query, id, msg):
+ """
+ Returns C{True} if the message date is the same as the query date.
+
+ @type query: A C{list} of C{str}
+ @param query: A list whose first element starts with a stringified date
+ that is a fragment of an L{imap4.Query()}. The date must be in the
+ format 'DD-Mon-YYYY', for example '03-March-2003' or '03-Mar-2003'.
+
+ @type msg: Provider of L{imap4.IMessage}
+ """
+ date = msg.getHeaders(False, 'date').get('date', '')
+ date = rfc822.parsedate(date)
+ return date[:3] == parseTime(query.pop(0))[:3]
+
+ def search_SENTSINCE(self, query, id, msg):
+ """
+ Returns C{True} if the message date is later than the query date.
+
+ @type query: A C{list} of C{str}
+ @param query: A list whose first element starts with a stringified date
+ that is a fragment of an L{imap4.Query()}. The date must be in the
+ format 'DD-Mon-YYYY', for example '03-March-2003' or '03-Mar-2003'.
+
+ @type msg: Provider of L{imap4.IMessage}
+ """
+ date = msg.getHeaders(False, 'date').get('date', '')
+ date = rfc822.parsedate(date)
+ return date > parseTime(query.pop(0))
+
+ def search_SINCE(self, query, id, msg):
+ date = parseTime(query.pop(0))
+ return rfc822.parsedate(msg.getInternalDate()) > date
+
+ def search_SMALLER(self, query, id, msg):
+ return int(query.pop(0)) > msg.getSize()
+
+ def search_SUBJECT(self, query, id, msg):
+ subj = msg.getHeaders(False, 'subject').get('subject', '')
+ return subj.lower().find(query.pop(0).lower()) != -1
+
+ def search_TEXT(self, query, id, msg):
+ # XXX - This must search headers too
+ body = query.pop(0).lower()
+ return text.strFile(body, msg.getBodyFile(), False)
+
+ def search_TO(self, query, id, msg):
+ to = msg.getHeaders(False, 'to').get('to', '')
+ return to.lower().find(query.pop(0).lower()) != -1
+
+ def search_UID(self, query, id, msg):
+ c = query.pop(0)
+ m = parseIdList(c)
+ return msg.getUID() in m
+
+ def search_UNANSWERED(self, query, id, msg):
+ return '\\Answered' not in msg.getFlags()
+
+ def search_UNDELETED(self, query, id, msg):
+ return '\\Deleted' not in msg.getFlags()
+
+ def search_UNDRAFT(self, query, id, msg):
+ return '\\Draft' not in msg.getFlags()
+
+ def search_UNFLAGGED(self, query, id, msg):
+ return '\\Flagged' not in msg.getFlags()
+
+ def search_UNKEYWORD(self, query, id, msg):
+ query.pop(0)
+ return False
+
+ def search_UNSEEN(self, query, id, msg):
+ return '\\Seen' not in msg.getFlags()
+
+ def __ebSearch(self, failure, tag):
+ self.sendBadResponse(tag, 'SEARCH failed: ' + str(failure.value))
+ log.err(failure)
+
+ def do_FETCH(self, tag, messages, query, uid=0):
+ if query:
+ self._oldTimeout = self.setTimeout(None)
+ maybeDeferred(self.mbox.fetch, messages, uid=uid
+ ).addCallback(iter
+ ).addCallback(self.__cbFetch, tag, query, uid
+ ).addErrback(self.__ebFetch, tag
+ )
+ else:
+ self.sendPositiveResponse(tag, 'FETCH complete')
+
+ select_FETCH = (do_FETCH, arg_seqset, arg_fetchatt)
+
+ def __cbFetch(self, results, tag, query, uid):
+ if self.blocked is None:
+ self.blocked = []
+ try:
+ id, msg = results.next()
+ except StopIteration:
+ # The idle timeout was suspended while we delivered results,
+ # restore it now.
+ self.setTimeout(self._oldTimeout)
+ del self._oldTimeout
+
+ # All results have been processed, deliver completion notification.
+
+ # It's important to run this *after* resetting the timeout to "rig
+ # a race" in some test code. writing to the transport will
+ # synchronously call test code, which synchronously loses the
+ # connection, calling our connectionLost method, which cancels the
+ # timeout. We want to make sure that timeout is cancelled *after*
+ # we reset it above, so that the final state is no timed
+ # calls. This avoids reactor uncleanliness errors in the test
+ # suite.
+ # XXX: Perhaps loopback should be fixed to not call the user code
+ # synchronously in transport.write?
+ self.sendPositiveResponse(tag, 'FETCH completed')
+
+ # Instance state is now consistent again (ie, it is as though
+ # the fetch command never ran), so allow any pending blocked
+ # commands to execute.
+ self._unblock()
+ else:
+ self.spewMessage(id, msg, query, uid
+ ).addCallback(lambda _: self.__cbFetch(results, tag, query, uid)
+ ).addErrback(self.__ebSpewMessage
+ )
+
+ def __ebSpewMessage(self, failure):
+ # This indicates a programming error.
+ # There's no reliable way to indicate anything to the client, since we
+ # may have already written an arbitrary amount of data in response to
+ # the command.
+ log.err(failure)
+ self.transport.loseConnection()
+
+ def spew_envelope(self, id, msg, _w=None, _f=None):
+ if _w is None:
+ _w = self.transport.write
+ _w('ENVELOPE ' + collapseNestedLists([getEnvelope(msg)]))
+
+ def spew_flags(self, id, msg, _w=None, _f=None):
+ if _w is None:
+ _w = self.transport.write
+ _w('FLAGS ' + '(%s)' % (' '.join(msg.getFlags())))
+
+ def spew_internaldate(self, id, msg, _w=None, _f=None):
+ if _w is None:
+ _w = self.transport.write
+ idate = msg.getInternalDate()
+ ttup = rfc822.parsedate_tz(idate)
+ if ttup is None:
+ log.msg("%d:%r: unpareseable internaldate: %r" % (id, msg, idate))
+ raise IMAP4Exception("Internal failure generating INTERNALDATE")
+
+ odate = time.strftime("%d-%b-%Y %H:%M:%S ", ttup[:9])
+ if ttup[9] is None:
+ odate = odate + "+0000"
+ else:
+ if ttup[9] >= 0:
+ sign = "+"
+ else:
+ sign = "-"
+ odate = odate + sign + string.zfill(str(((abs(ttup[9]) / 3600) * 100 + (abs(ttup[9]) % 3600) / 60)), 4)
+ _w('INTERNALDATE ' + _quote(odate))
+
+ def spew_rfc822header(self, id, msg, _w=None, _f=None):
+ if _w is None:
+ _w = self.transport.write
+ hdrs = _formatHeaders(msg.getHeaders(True))
+ _w('RFC822.HEADER ' + _literal(hdrs))
+
+ def spew_rfc822text(self, id, msg, _w=None, _f=None):
+ if _w is None:
+ _w = self.transport.write
+ _w('RFC822.TEXT ')
+ _f()
+ return FileProducer(msg.getBodyFile()
+ ).beginProducing(self.transport
+ )
+
+ def spew_rfc822size(self, id, msg, _w=None, _f=None):
+ if _w is None:
+ _w = self.transport.write
+ _w('RFC822.SIZE ' + str(msg.getSize()))
+
+ def spew_rfc822(self, id, msg, _w=None, _f=None):
+ if _w is None:
+ _w = self.transport.write
+ _w('RFC822 ')
+ _f()
+ mf = IMessageFile(msg, None)
+ if mf is not None:
+ return FileProducer(mf.open()
+ ).beginProducing(self.transport
+ )
+ return MessageProducer(msg, None, self._scheduler
+ ).beginProducing(self.transport
+ )
+
+ def spew_uid(self, id, msg, _w=None, _f=None):
+ if _w is None:
+ _w = self.transport.write
+ _w('UID ' + str(msg.getUID()))
+
+ def spew_bodystructure(self, id, msg, _w=None, _f=None):
+ _w('BODYSTRUCTURE ' + collapseNestedLists([getBodyStructure(msg, True)]))
+
+ def spew_body(self, part, id, msg, _w=None, _f=None):
+ if _w is None:
+ _w = self.transport.write
+ for p in part.part:
+ if msg.isMultipart():
+ msg = msg.getSubPart(p)
+ elif p > 0:
+ # Non-multipart messages have an implicit first part but no
+ # other parts - reject any request for any other part.
+ raise TypeError("Requested subpart of non-multipart message")
+
+ if part.header:
+ hdrs = msg.getHeaders(part.header.negate, *part.header.fields)
+ hdrs = _formatHeaders(hdrs)
+ _w(str(part) + ' ' + _literal(hdrs))
+ elif part.text:
+ _w(str(part) + ' ')
+ _f()
+ return FileProducer(msg.getBodyFile()
+ ).beginProducing(self.transport
+ )
+ elif part.mime:
+ hdrs = _formatHeaders(msg.getHeaders(True))
+ _w(str(part) + ' ' + _literal(hdrs))
+ elif part.empty:
+ _w(str(part) + ' ')
+ _f()
+ if part.part:
+ return FileProducer(msg.getBodyFile()
+ ).beginProducing(self.transport
+ )
+ else:
+ mf = IMessageFile(msg, None)
+ if mf is not None:
+ return FileProducer(mf.open()).beginProducing(self.transport)
+ return MessageProducer(msg, None, self._scheduler).beginProducing(self.transport)
+
+ else:
+ _w('BODY ' + collapseNestedLists([getBodyStructure(msg)]))
+
+ def spewMessage(self, id, msg, query, uid):
+ wbuf = WriteBuffer(self.transport)
+ write = wbuf.write
+ flush = wbuf.flush
+ def start():
+ write('* %d FETCH (' % (id,))
+ def finish():
+ write(')\r\n')
+ def space():
+ write(' ')
+
+ def spew():
+ seenUID = False
+ start()
+ for part in query:
+ if part.type == 'uid':
+ seenUID = True
+ if part.type == 'body':
+ yield self.spew_body(part, id, msg, write, flush)
+ else:
+ f = getattr(self, 'spew_' + part.type)
+ yield f(id, msg, write, flush)
+ if part is not query[-1]:
+ space()
+ if uid and not seenUID:
+ space()
+ yield self.spew_uid(id, msg, write, flush)
+ finish()
+ flush()
+ return self._scheduler(spew())
+
+ def __ebFetch(self, failure, tag):
+ self.setTimeout(self._oldTimeout)
+ del self._oldTimeout
+ log.err(failure)
+ self.sendBadResponse(tag, 'FETCH failed: ' + str(failure.value))
+
+ def do_STORE(self, tag, messages, mode, flags, uid=0):
+ mode = mode.upper()
+ silent = mode.endswith('SILENT')
+ if mode.startswith('+'):
+ mode = 1
+ elif mode.startswith('-'):
+ mode = -1
+ else:
+ mode = 0
+
+ maybeDeferred(self.mbox.store, messages, flags, mode, uid=uid).addCallbacks(
+ self.__cbStore, self.__ebStore, (tag, self.mbox, uid, silent), None, (tag,), None
+ )
+
+ select_STORE = (do_STORE, arg_seqset, arg_atom, arg_flaglist)
+
+ def __cbStore(self, result, tag, mbox, uid, silent):
+ if result and not silent:
+ for (k, v) in result.iteritems():
+ if uid:
+ uidstr = ' UID %d' % mbox.getUID(k)
+ else:
+ uidstr = ''
+ self.sendUntaggedResponse('%d FETCH (FLAGS (%s)%s)' %
+ (k, ' '.join(v), uidstr))
+ self.sendPositiveResponse(tag, 'STORE completed')
+
+ def __ebStore(self, failure, tag):
+ self.sendBadResponse(tag, 'Server error: ' + str(failure.value))
+
+ def do_COPY(self, tag, messages, mailbox, uid=0):
+ mailbox = self._parseMbox(mailbox)
+ maybeDeferred(self.account.select, mailbox
+ ).addCallback(self._cbCopySelectedMailbox, tag, messages, mailbox, uid
+ ).addErrback(self._ebCopySelectedMailbox, tag
+ )
+ select_COPY = (do_COPY, arg_seqset, arg_astring)
+
+ def _cbCopySelectedMailbox(self, mbox, tag, messages, mailbox, uid):
+ if not mbox:
+ self.sendNegativeResponse(tag, 'No such mailbox: ' + mailbox)
+ else:
+ maybeDeferred(self.mbox.fetch, messages, uid
+ ).addCallback(self.__cbCopy, tag, mbox
+ ).addCallback(self.__cbCopied, tag, mbox
+ ).addErrback(self.__ebCopy, tag
+ )
+
+ def _ebCopySelectedMailbox(self, failure, tag):
+ self.sendBadResponse(tag, 'Server error: ' + str(failure.value))
+
+ def __cbCopy(self, messages, tag, mbox):
+ # XXX - This should handle failures with a rollback or something
+ addedDeferreds = []
+ addedIDs = []
+ failures = []
+
+ fastCopyMbox = IMessageCopier(mbox, None)
+ for (id, msg) in messages:
+ if fastCopyMbox is not None:
+ d = maybeDeferred(fastCopyMbox.copy, msg)
+ addedDeferreds.append(d)
+ continue
+
+ # XXX - The following should be an implementation of IMessageCopier.copy
+ # on an IMailbox->IMessageCopier adapter.
+
+ flags = msg.getFlags()
+ date = msg.getInternalDate()
+
+ body = IMessageFile(msg, None)
+ if body is not None:
+ bodyFile = body.open()
+ d = maybeDeferred(mbox.addMessage, bodyFile, flags, date)
+ else:
+ def rewind(f):
+ f.seek(0)
+ return f
+ buffer = tempfile.TemporaryFile()
+ d = MessageProducer(msg, buffer, self._scheduler
+ ).beginProducing(None
+ ).addCallback(lambda _, b=buffer, f=flags, d=date: mbox.addMessage(rewind(b), f, d)
+ )
+ addedDeferreds.append(d)
+ return defer.DeferredList(addedDeferreds)
+
+ def __cbCopied(self, deferredIds, tag, mbox):
+ ids = []
+ failures = []
+ for (status, result) in deferredIds:
+ if status:
+ ids.append(result)
+ else:
+ failures.append(result.value)
+ if failures:
+ self.sendNegativeResponse(tag, '[ALERT] Some messages were not copied')
+ else:
+ self.sendPositiveResponse(tag, 'COPY completed')
+
+ def __ebCopy(self, failure, tag):
+ self.sendBadResponse(tag, 'COPY failed:' + str(failure.value))
+ log.err(failure)
+
+ def do_UID(self, tag, command, line):
+ command = command.upper()
+
+ if command not in ('COPY', 'FETCH', 'STORE', 'SEARCH'):
+ raise IllegalClientResponse(command)
+
+ self.dispatchCommand(tag, command, line, uid=1)
+
+ select_UID = (do_UID, arg_atom, arg_line)
+ #
+ # IMailboxListener implementation
+ #
+ def modeChanged(self, writeable):
+ if writeable:
+ self.sendUntaggedResponse(message='[READ-WRITE]', async=True)
+ else:
+ self.sendUntaggedResponse(message='[READ-ONLY]', async=True)
+
+ def flagsChanged(self, newFlags):
+ for (mId, flags) in newFlags.iteritems():
+ msg = '%d FETCH (FLAGS (%s))' % (mId, ' '.join(flags))
+ self.sendUntaggedResponse(msg, async=True)
+
+ def newMessages(self, exists, recent):
+ if exists is not None:
+ self.sendUntaggedResponse('%d EXISTS' % exists, async=True)
+ if recent is not None:
+ self.sendUntaggedResponse('%d RECENT' % recent, async=True)
+
+
+class UnhandledResponse(IMAP4Exception): pass
+
+class NegativeResponse(IMAP4Exception): pass
+
+class NoSupportedAuthentication(IMAP4Exception):
+ def __init__(self, serverSupports, clientSupports):
+ IMAP4Exception.__init__(self, 'No supported authentication schemes available')
+ self.serverSupports = serverSupports
+ self.clientSupports = clientSupports
+
+ def __str__(self):
+ return (IMAP4Exception.__str__(self)
+ + ': Server supports %r, client supports %r'
+ % (self.serverSupports, self.clientSupports))
+
+class IllegalServerResponse(IMAP4Exception): pass
+
+TIMEOUT_ERROR = error.TimeoutError()
+
+class IMAP4Client(basic.LineReceiver, policies.TimeoutMixin):
+ """IMAP4 client protocol implementation
+
+ @ivar state: A string representing the state the connection is currently
+ in.
+ """
+ implements(IMailboxListener)
+
+ tags = None
+ waiting = None
+ queued = None
+ tagID = 1
+ state = None
+
+ startedTLS = False
+
+ # Number of seconds to wait before timing out a connection.
+ # If the number is <= 0 no timeout checking will be performed.
+ timeout = 0
+
+ # Capabilities are not allowed to change during the session
+ # So cache the first response and use that for all later
+ # lookups
+ _capCache = None
+
+ _memoryFileLimit = 1024 * 1024 * 10
+
+ # Authentication is pluggable. This maps names to IClientAuthentication
+ # objects.
+ authenticators = None
+
+ STATUS_CODES = ('OK', 'NO', 'BAD', 'PREAUTH', 'BYE')
+
+ STATUS_TRANSFORMATIONS = {
+ 'MESSAGES': int, 'RECENT': int, 'UNSEEN': int
+ }
+
+ context = None
+
+ def __init__(self, contextFactory = None):
+ self.tags = {}
+ self.queued = []
+ self.authenticators = {}
+ self.context = contextFactory
+
+ self._tag = None
+ self._parts = None
+ self._lastCmd = None
+
+ def registerAuthenticator(self, auth):
+ """Register a new form of authentication
+
+ When invoking the authenticate() method of IMAP4Client, the first
+ matching authentication scheme found will be used. The ordering is
+ that in which the server lists support authentication schemes.
+
+ @type auth: Implementor of C{IClientAuthentication}
+ @param auth: The object to use to perform the client
+ side of this authentication scheme.
+ """
+ self.authenticators[auth.getName().upper()] = auth
+
+ def rawDataReceived(self, data):
+ if self.timeout > 0:
+ self.resetTimeout()
+
+ self._pendingSize -= len(data)
+ if self._pendingSize > 0:
+ self._pendingBuffer.write(data)
+ else:
+ passon = ''
+ if self._pendingSize < 0:
+ data, passon = data[:self._pendingSize], data[self._pendingSize:]
+ self._pendingBuffer.write(data)
+ rest = self._pendingBuffer
+ self._pendingBuffer = None
+ self._pendingSize = None
+ rest.seek(0, 0)
+ self._parts.append(rest.read())
+ self.setLineMode(passon.lstrip('\r\n'))
+
+# def sendLine(self, line):
+# print 'S:', repr(line)
+# return basic.LineReceiver.sendLine(self, line)
+
+ def _setupForLiteral(self, rest, octets):
+ self._pendingBuffer = self.messageFile(octets)
+ self._pendingSize = octets
+ if self._parts is None:
+ self._parts = [rest, '\r\n']
+ else:
+ self._parts.extend([rest, '\r\n'])
+ self.setRawMode()
+
+ def connectionMade(self):
+ if self.timeout > 0:
+ self.setTimeout(self.timeout)
+
+ def connectionLost(self, reason):
+ """We are no longer connected"""
+ if self.timeout > 0:
+ self.setTimeout(None)
+ if self.queued is not None:
+ queued = self.queued
+ self.queued = None
+ for cmd in queued:
+ cmd.defer.errback(reason)
+ if self.tags is not None:
+ tags = self.tags
+ self.tags = None
+ for cmd in tags.itervalues():
+ if cmd is not None and cmd.defer is not None:
+ cmd.defer.errback(reason)
+
+
+ def lineReceived(self, line):
+ """
+ Attempt to parse a single line from the server.
+
+ @type line: C{str}
+ @param line: The line from the server, without the line delimiter.
+
+ @raise IllegalServerResponse: If the line or some part of the line
+ does not represent an allowed message from the server at this time.
+ """
+# print 'C: ' + repr(line)
+ if self.timeout > 0:
+ self.resetTimeout()
+
+ lastPart = line.rfind('{')
+ if lastPart != -1:
+ lastPart = line[lastPart + 1:]
+ if lastPart.endswith('}'):
+ # It's a literal a-comin' in
+ try:
+ octets = int(lastPart[:-1])
+ except ValueError:
+ raise IllegalServerResponse(line)
+ if self._parts is None:
+ self._tag, parts = line.split(None, 1)
+ else:
+ parts = line
+ self._setupForLiteral(parts, octets)
+ return
+
+ if self._parts is None:
+ # It isn't a literal at all
+ self._regularDispatch(line)
+ else:
+ # If an expression is in progress, no tag is required here
+ # Since we didn't find a literal indicator, this expression
+ # is done.
+ self._parts.append(line)
+ tag, rest = self._tag, ''.join(self._parts)
+ self._tag = self._parts = None
+ self.dispatchCommand(tag, rest)
+
+ def timeoutConnection(self):
+ if self._lastCmd and self._lastCmd.defer is not None:
+ d, self._lastCmd.defer = self._lastCmd.defer, None
+ d.errback(TIMEOUT_ERROR)
+
+ if self.queued:
+ for cmd in self.queued:
+ if cmd.defer is not None:
+ d, cmd.defer = cmd.defer, d
+ d.errback(TIMEOUT_ERROR)
+
+ self.transport.loseConnection()
+
+ def _regularDispatch(self, line):
+ parts = line.split(None, 1)
+ if len(parts) != 2:
+ parts.append('')
+ tag, rest = parts
+ self.dispatchCommand(tag, rest)
+
+ def messageFile(self, octets):
+ """Create a file to which an incoming message may be written.
+
+ @type octets: C{int}
+ @param octets: The number of octets which will be written to the file
+
+ @rtype: Any object which implements C{write(string)} and
+ C{seek(int, int)}
+ @return: A file-like object
+ """
+ if octets > self._memoryFileLimit:
+ return tempfile.TemporaryFile()
+ else:
+ return StringIO.StringIO()
+
+ def makeTag(self):
+ tag = '%0.4X' % self.tagID
+ self.tagID += 1
+ return tag
+
+ def dispatchCommand(self, tag, rest):
+ if self.state is None:
+ f = self.response_UNAUTH
+ else:
+ f = getattr(self, 'response_' + self.state.upper(), None)
+ if f:
+ try:
+ f(tag, rest)
+ except:
+ log.err()
+ self.transport.loseConnection()
+ else:
+ log.err("Cannot dispatch: %s, %s, %s" % (self.state, tag, rest))
+ self.transport.loseConnection()
+
+ def response_UNAUTH(self, tag, rest):
+ if self.state is None:
+ # Server greeting, this is
+ status, rest = rest.split(None, 1)
+ if status.upper() == 'OK':
+ self.state = 'unauth'
+ elif status.upper() == 'PREAUTH':
+ self.state = 'auth'
+ else:
+ # XXX - This is rude.
+ self.transport.loseConnection()
+ raise IllegalServerResponse(tag + ' ' + rest)
+
+ b, e = rest.find('['), rest.find(']')
+ if b != -1 and e != -1:
+ self.serverGreeting(
+ self.__cbCapabilities(
+ ([parseNestedParens(rest[b + 1:e])], None)))
+ else:
+ self.serverGreeting(None)
+ else:
+ self._defaultHandler(tag, rest)
+
+ def response_AUTH(self, tag, rest):
+ self._defaultHandler(tag, rest)
+
+ def _defaultHandler(self, tag, rest):
+ if tag == '*' or tag == '+':
+ if not self.waiting:
+ self._extraInfo([parseNestedParens(rest)])
+ else:
+ cmd = self.tags[self.waiting]
+ if tag == '+':
+ cmd.continuation(rest)
+ else:
+ cmd.lines.append(rest)
+ else:
+ try:
+ cmd = self.tags[tag]
+ except KeyError:
+ # XXX - This is rude.
+ self.transport.loseConnection()
+ raise IllegalServerResponse(tag + ' ' + rest)
+ else:
+ status, line = rest.split(None, 1)
+ if status == 'OK':
+ # Give them this last line, too
+ cmd.finish(rest, self._extraInfo)
+ else:
+ cmd.defer.errback(IMAP4Exception(line))
+ del self.tags[tag]
+ self.waiting = None
+ self._flushQueue()
+
+ def _flushQueue(self):
+ if self.queued:
+ cmd = self.queued.pop(0)
+ t = self.makeTag()
+ self.tags[t] = cmd
+ self.sendLine(cmd.format(t))
+ self.waiting = t
+
+ def _extraInfo(self, lines):
+ # XXX - This is terrible.
+ # XXX - Also, this should collapse temporally proximate calls into single
+ # invocations of IMailboxListener methods, where possible.
+ flags = {}
+ recent = exists = None
+ for response in lines:
+ elements = len(response)
+ if elements == 1 and response[0] == ['READ-ONLY']:
+ self.modeChanged(False)
+ elif elements == 1 and response[0] == ['READ-WRITE']:
+ self.modeChanged(True)
+ elif elements == 2 and response[1] == 'EXISTS':
+ exists = int(response[0])
+ elif elements == 2 and response[1] == 'RECENT':
+ recent = int(response[0])
+ elif elements == 3 and response[1] == 'FETCH':
+ mId = int(response[0])
+ values = self._parseFetchPairs(response[2])
+ flags.setdefault(mId, []).extend(values.get('FLAGS', ()))
+ else:
+ log.msg('Unhandled unsolicited response: %s' % (response,))
+
+ if flags:
+ self.flagsChanged(flags)
+ if recent is not None or exists is not None:
+ self.newMessages(exists, recent)
+
+ def sendCommand(self, cmd):
+ cmd.defer = defer.Deferred()
+ if self.waiting:
+ self.queued.append(cmd)
+ return cmd.defer
+ t = self.makeTag()
+ self.tags[t] = cmd
+ self.sendLine(cmd.format(t))
+ self.waiting = t
+ self._lastCmd = cmd
+ return cmd.defer
+
+ def getCapabilities(self, useCache=1):
+ """Request the capabilities available on this server.
+
+ This command is allowed in any state of connection.
+
+ @type useCache: C{bool}
+ @param useCache: Specify whether to use the capability-cache or to
+ re-retrieve the capabilities from the server. Server capabilities
+ should never change, so for normal use, this flag should never be
+ false.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback will be invoked with a
+ dictionary mapping capability types to lists of supported
+ mechanisms, or to None if a support list is not applicable.
+ """
+ if useCache and self._capCache is not None:
+ return defer.succeed(self._capCache)
+ cmd = 'CAPABILITY'
+ resp = ('CAPABILITY',)
+ d = self.sendCommand(Command(cmd, wantResponse=resp))
+ d.addCallback(self.__cbCapabilities)
+ return d
+
+ def __cbCapabilities(self, (lines, tagline)):
+ caps = {}
+ for rest in lines:
+ for cap in rest[1:]:
+ parts = cap.split('=', 1)
+ if len(parts) == 1:
+ category, value = parts[0], None
+ else:
+ category, value = parts
+ caps.setdefault(category, []).append(value)
+
+ # Preserve a non-ideal API for backwards compatibility. It would
+ # probably be entirely sensible to have an object with a wider API than
+ # dict here so this could be presented less insanely.
+ for category in caps:
+ if caps[category] == [None]:
+ caps[category] = None
+ self._capCache = caps
+ return caps
+
+ def logout(self):
+ """Inform the server that we are done with the connection.
+
+ This command is allowed in any state of connection.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback will be invoked with None
+ when the proper server acknowledgement has been received.
+ """
+ d = self.sendCommand(Command('LOGOUT', wantResponse=('BYE',)))
+ d.addCallback(self.__cbLogout)
+ return d
+
+ def __cbLogout(self, (lines, tagline)):
+ self.transport.loseConnection()
+ # We don't particularly care what the server said
+ return None
+
+
+ def noop(self):
+ """Perform no operation.
+
+ This command is allowed in any state of connection.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback will be invoked with a list
+ of untagged status updates the server responds with.
+ """
+ d = self.sendCommand(Command('NOOP'))
+ d.addCallback(self.__cbNoop)
+ return d
+
+ def __cbNoop(self, (lines, tagline)):
+ # Conceivable, this is elidable.
+ # It is, afterall, a no-op.
+ return lines
+
+ def startTLS(self, contextFactory=None):
+ """
+ Initiates a 'STARTTLS' request and negotiates the TLS / SSL
+ Handshake.
+
+ @param contextFactory: The TLS / SSL Context Factory to
+ leverage. If the contextFactory is None the IMAP4Client will
+ either use the current TLS / SSL Context Factory or attempt to
+ create a new one.
+
+ @type contextFactory: C{ssl.ClientContextFactory}
+
+ @return: A Deferred which fires when the transport has been
+ secured according to the given contextFactory, or which fails
+ if the transport cannot be secured.
+ """
+ assert not self.startedTLS, "Client and Server are currently communicating via TLS"
+
+ if contextFactory is None:
+ contextFactory = self._getContextFactory()
+
+ if contextFactory is None:
+ return defer.fail(IMAP4Exception(
+ "IMAP4Client requires a TLS context to "
+ "initiate the STARTTLS handshake"))
+
+ if 'STARTTLS' not in self._capCache:
+ return defer.fail(IMAP4Exception(
+ "Server does not support secure communication "
+ "via TLS / SSL"))
+
+ tls = interfaces.ITLSTransport(self.transport, None)
+ if tls is None:
+ return defer.fail(IMAP4Exception(
+ "IMAP4Client transport does not implement "
+ "interfaces.ITLSTransport"))
+
+ d = self.sendCommand(Command('STARTTLS'))
+ d.addCallback(self._startedTLS, contextFactory)
+ d.addCallback(lambda _: self.getCapabilities())
+ return d
+
+
+ def authenticate(self, secret):
+ """Attempt to enter the authenticated state with the server
+
+ This command is allowed in the Non-Authenticated state.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked if the authentication
+ succeeds and whose errback will be invoked otherwise.
+ """
+ if self._capCache is None:
+ d = self.getCapabilities()
+ else:
+ d = defer.succeed(self._capCache)
+ d.addCallback(self.__cbAuthenticate, secret)
+ return d
+
+ def __cbAuthenticate(self, caps, secret):
+ auths = caps.get('AUTH', ())
+ for scheme in auths:
+ if scheme.upper() in self.authenticators:
+ cmd = Command('AUTHENTICATE', scheme, (),
+ self.__cbContinueAuth, scheme,
+ secret)
+ return self.sendCommand(cmd)
+
+ if self.startedTLS:
+ return defer.fail(NoSupportedAuthentication(
+ auths, self.authenticators.keys()))
+ else:
+ def ebStartTLS(err):
+ err.trap(IMAP4Exception)
+ # We couldn't negotiate TLS for some reason
+ return defer.fail(NoSupportedAuthentication(
+ auths, self.authenticators.keys()))
+
+ d = self.startTLS()
+ d.addErrback(ebStartTLS)
+ d.addCallback(lambda _: self.getCapabilities())
+ d.addCallback(self.__cbAuthTLS, secret)
+ return d
+
+
+ def __cbContinueAuth(self, rest, scheme, secret):
+ try:
+ chal = base64.decodestring(rest + '\n')
+ except binascii.Error:
+ self.sendLine('*')
+ raise IllegalServerResponse(rest)
+ self.transport.loseConnection()
+ else:
+ auth = self.authenticators[scheme]
+ chal = auth.challengeResponse(secret, chal)
+ self.sendLine(base64.encodestring(chal).strip())
+
+ def __cbAuthTLS(self, caps, secret):
+ auths = caps.get('AUTH', ())
+ for scheme in auths:
+ if scheme.upper() in self.authenticators:
+ cmd = Command('AUTHENTICATE', scheme, (),
+ self.__cbContinueAuth, scheme,
+ secret)
+ return self.sendCommand(cmd)
+ raise NoSupportedAuthentication(auths, self.authenticators.keys())
+
+
+ def login(self, username, password):
+ """Authenticate with the server using a username and password
+
+ This command is allowed in the Non-Authenticated state. If the
+ server supports the STARTTLS capability and our transport supports
+ TLS, TLS is negotiated before the login command is issued.
+
+ A more secure way to log in is to use C{startTLS} or
+ C{authenticate} or both.
+
+ @type username: C{str}
+ @param username: The username to log in with
+
+ @type password: C{str}
+ @param password: The password to log in with
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked if login is successful
+ and whose errback is invoked otherwise.
+ """
+ d = maybeDeferred(self.getCapabilities)
+ d.addCallback(self.__cbLoginCaps, username, password)
+ return d
+
+ def serverGreeting(self, caps):
+ """Called when the server has sent us a greeting.
+
+ @type caps: C{dict}
+ @param caps: Capabilities the server advertised in its greeting.
+ """
+
+ def _getContextFactory(self):
+ if self.context is not None:
+ return self.context
+ try:
+ from twisted.internet import ssl
+ except ImportError:
+ return None
+ else:
+ context = ssl.ClientContextFactory()
+ context.method = ssl.SSL.TLSv1_METHOD
+ return context
+
+ def __cbLoginCaps(self, capabilities, username, password):
+ # If the server advertises STARTTLS, we might want to try to switch to TLS
+ tryTLS = 'STARTTLS' in capabilities
+
+ # If our transport supports switching to TLS, we might want to try to switch to TLS.
+ tlsableTransport = interfaces.ITLSTransport(self.transport, None) is not None
+
+ # If our transport is not already using TLS, we might want to try to switch to TLS.
+ nontlsTransport = interfaces.ISSLTransport(self.transport, None) is None
+
+ if not self.startedTLS and tryTLS and tlsableTransport and nontlsTransport:
+ d = self.startTLS()
+
+ d.addCallbacks(
+ self.__cbLoginTLS,
+ self.__ebLoginTLS,
+ callbackArgs=(username, password),
+ )
+ return d
+ else:
+ if nontlsTransport:
+ log.msg("Server has no TLS support. logging in over cleartext!")
+ args = ' '.join((_quote(username), _quote(password)))
+ return self.sendCommand(Command('LOGIN', args))
+
+ def _startedTLS(self, result, context):
+ self.transport.startTLS(context)
+ self._capCache = None
+ self.startedTLS = True
+ return result
+
+ def __cbLoginTLS(self, result, username, password):
+ args = ' '.join((_quote(username), _quote(password)))
+ return self.sendCommand(Command('LOGIN', args))
+
+ def __ebLoginTLS(self, failure):
+ log.err(failure)
+ return failure
+
+ def namespace(self):
+ """Retrieve information about the namespaces available to this account
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with namespace
+ information. An example of this information is::
+
+ [[['', '/']], [], []]
+
+ which indicates a single personal namespace called '' with '/'
+ as its hierarchical delimiter, and no shared or user namespaces.
+ """
+ cmd = 'NAMESPACE'
+ resp = ('NAMESPACE',)
+ d = self.sendCommand(Command(cmd, wantResponse=resp))
+ d.addCallback(self.__cbNamespace)
+ return d
+
+ def __cbNamespace(self, (lines, last)):
+ for parts in lines:
+ if len(parts) == 4 and parts[0] == 'NAMESPACE':
+ return [e or [] for e in parts[1:]]
+ log.err("No NAMESPACE response to NAMESPACE command")
+ return [[], [], []]
+
+ def select(self, mailbox):
+ """Select a mailbox
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @type mailbox: C{str}
+ @param mailbox: The name of the mailbox to select
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with mailbox
+ information if the select is successful and whose errback is
+ invoked otherwise. Mailbox information consists of a dictionary
+ with the following keys and values::
+
+ FLAGS: A list of strings containing the flags settable on
+ messages in this mailbox.
+
+ EXISTS: An integer indicating the number of messages in this
+ mailbox.
+
+ RECENT: An integer indicating the number of \"recent\"
+ messages in this mailbox.
+
+ UNSEEN: An integer indicating the number of messages not
+ flagged \\Seen in this mailbox.
+
+ PERMANENTFLAGS: A list of strings containing the flags that
+ can be permanently set on messages in this mailbox.
+
+ UIDVALIDITY: An integer uniquely identifying this mailbox.
+ """
+ cmd = 'SELECT'
+ args = _prepareMailboxName(mailbox)
+ resp = ('FLAGS', 'EXISTS', 'RECENT', 'UNSEEN', 'PERMANENTFLAGS', 'UIDVALIDITY')
+ d = self.sendCommand(Command(cmd, args, wantResponse=resp))
+ d.addCallback(self.__cbSelect, 1)
+ return d
+
+ def examine(self, mailbox):
+ """Select a mailbox in read-only mode
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @type mailbox: C{str}
+ @param mailbox: The name of the mailbox to examine
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with mailbox
+ information if the examine is successful and whose errback
+ is invoked otherwise. Mailbox information consists of a dictionary
+ with the following keys and values::
+
+ 'FLAGS': A list of strings containing the flags settable on
+ messages in this mailbox.
+
+ 'EXISTS': An integer indicating the number of messages in this
+ mailbox.
+
+ 'RECENT': An integer indicating the number of \"recent\"
+ messages in this mailbox.
+
+ 'UNSEEN': An integer indicating the number of messages not
+ flagged \\Seen in this mailbox.
+
+ 'PERMANENTFLAGS': A list of strings containing the flags that
+ can be permanently set on messages in this mailbox.
+
+ 'UIDVALIDITY': An integer uniquely identifying this mailbox.
+ """
+ cmd = 'EXAMINE'
+ args = _prepareMailboxName(mailbox)
+ resp = ('FLAGS', 'EXISTS', 'RECENT', 'UNSEEN', 'PERMANENTFLAGS', 'UIDVALIDITY')
+ d = self.sendCommand(Command(cmd, args, wantResponse=resp))
+ d.addCallback(self.__cbSelect, 0)
+ return d
+
+
+ def _intOrRaise(self, value, phrase):
+ """
+ Parse C{value} as an integer and return the result or raise
+ L{IllegalServerResponse} with C{phrase} as an argument if C{value}
+ cannot be parsed as an integer.
+ """
+ try:
+ return int(value)
+ except ValueError:
+ raise IllegalServerResponse(phrase)
+
+
+ def __cbSelect(self, (lines, tagline), rw):
+ """
+ Handle lines received in response to a SELECT or EXAMINE command.
+
+ See RFC 3501, section 6.3.1.
+ """
+ # In the absense of specification, we are free to assume:
+ # READ-WRITE access
+ datum = {'READ-WRITE': rw}
+ lines.append(parseNestedParens(tagline))
+ for split in lines:
+ if len(split) > 0 and split[0].upper() == 'OK':
+ # Handle all the kinds of OK response.
+ content = split[1]
+ key = content[0].upper()
+ if key == 'READ-ONLY':
+ datum['READ-WRITE'] = False
+ elif key == 'READ-WRITE':
+ datum['READ-WRITE'] = True
+ elif key == 'UIDVALIDITY':
+ datum['UIDVALIDITY'] = self._intOrRaise(
+ content[1], split)
+ elif key == 'UNSEEN':
+ datum['UNSEEN'] = self._intOrRaise(content[1], split)
+ elif key == 'UIDNEXT':
+ datum['UIDNEXT'] = self._intOrRaise(content[1], split)
+ elif key == 'PERMANENTFLAGS':
+ datum['PERMANENTFLAGS'] = tuple(content[1])
+ else:
+ log.err('Unhandled SELECT response (2): %s' % (split,))
+ elif len(split) == 2:
+ # Handle FLAGS, EXISTS, and RECENT
+ if split[0].upper() == 'FLAGS':
+ datum['FLAGS'] = tuple(split[1])
+ elif isinstance(split[1], str):
+ # Must make sure things are strings before treating them as
+ # strings since some other forms of response have nesting in
+ # places which results in lists instead.
+ if split[1].upper() == 'EXISTS':
+ datum['EXISTS'] = self._intOrRaise(split[0], split)
+ elif split[1].upper() == 'RECENT':
+ datum['RECENT'] = self._intOrRaise(split[0], split)
+ else:
+ log.err('Unhandled SELECT response (0): %s' % (split,))
+ else:
+ log.err('Unhandled SELECT response (1): %s' % (split,))
+ else:
+ log.err('Unhandled SELECT response (4): %s' % (split,))
+ return datum
+
+
+ def create(self, name):
+ """Create a new mailbox on the server
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @type name: C{str}
+ @param name: The name of the mailbox to create.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked if the mailbox creation
+ is successful and whose errback is invoked otherwise.
+ """
+ return self.sendCommand(Command('CREATE', _prepareMailboxName(name)))
+
+ def delete(self, name):
+ """Delete a mailbox
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @type name: C{str}
+ @param name: The name of the mailbox to delete.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose calblack is invoked if the mailbox is
+ deleted successfully and whose errback is invoked otherwise.
+ """
+ return self.sendCommand(Command('DELETE', _prepareMailboxName(name)))
+
+ def rename(self, oldname, newname):
+ """Rename a mailbox
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @type oldname: C{str}
+ @param oldname: The current name of the mailbox to rename.
+
+ @type newname: C{str}
+ @param newname: The new name to give the mailbox.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked if the rename is
+ successful and whose errback is invoked otherwise.
+ """
+ oldname = _prepareMailboxName(oldname)
+ newname = _prepareMailboxName(newname)
+ return self.sendCommand(Command('RENAME', ' '.join((oldname, newname))))
+
+ def subscribe(self, name):
+ """Add a mailbox to the subscription list
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @type name: C{str}
+ @param name: The mailbox to mark as 'active' or 'subscribed'
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked if the subscription
+ is successful and whose errback is invoked otherwise.
+ """
+ return self.sendCommand(Command('SUBSCRIBE', _prepareMailboxName(name)))
+
+ def unsubscribe(self, name):
+ """Remove a mailbox from the subscription list
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @type name: C{str}
+ @param name: The mailbox to unsubscribe
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked if the unsubscription
+ is successful and whose errback is invoked otherwise.
+ """
+ return self.sendCommand(Command('UNSUBSCRIBE', _prepareMailboxName(name)))
+
+ def list(self, reference, wildcard):
+ """List a subset of the available mailboxes
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @type reference: C{str}
+ @param reference: The context in which to interpret C{wildcard}
+
+ @type wildcard: C{str}
+ @param wildcard: The pattern of mailbox names to match, optionally
+ including either or both of the '*' and '%' wildcards. '*' will
+ match zero or more characters and cross hierarchical boundaries.
+ '%' will also match zero or more characters, but is limited to a
+ single hierarchical level.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a list of C{tuple}s,
+ the first element of which is a C{tuple} of mailbox flags, the second
+ element of which is the hierarchy delimiter for this mailbox, and the
+ third of which is the mailbox name; if the command is unsuccessful,
+ the deferred's errback is invoked instead.
+ """
+ cmd = 'LIST'
+ args = '"%s" "%s"' % (reference, wildcard.encode('imap4-utf-7'))
+ resp = ('LIST',)
+ d = self.sendCommand(Command(cmd, args, wantResponse=resp))
+ d.addCallback(self.__cbList, 'LIST')
+ return d
+
+ def lsub(self, reference, wildcard):
+ """List a subset of the subscribed available mailboxes
+
+ This command is allowed in the Authenticated and Selected states.
+
+ The parameters and returned object are the same as for the C{list}
+ method, with one slight difference: Only mailboxes which have been
+ subscribed can be included in the resulting list.
+ """
+ cmd = 'LSUB'
+ args = '"%s" "%s"' % (reference, wildcard.encode('imap4-utf-7'))
+ resp = ('LSUB',)
+ d = self.sendCommand(Command(cmd, args, wantResponse=resp))
+ d.addCallback(self.__cbList, 'LSUB')
+ return d
+
+ def __cbList(self, (lines, last), command):
+ results = []
+ for parts in lines:
+ if len(parts) == 4 and parts[0] == command:
+ parts[1] = tuple(parts[1])
+ results.append(tuple(parts[1:]))
+ return results
+
+ def status(self, mailbox, *names):
+ """
+ Retrieve the status of the given mailbox
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @type mailbox: C{str}
+ @param mailbox: The name of the mailbox to query
+
+ @type *names: C{str}
+ @param *names: The status names to query. These may be any number of:
+ C{'MESSAGES'}, C{'RECENT'}, C{'UIDNEXT'}, C{'UIDVALIDITY'}, and
+ C{'UNSEEN'}.
+
+ @rtype: C{Deferred}
+ @return: A deferred which fires with with the status information if the
+ command is successful and whose errback is invoked otherwise. The
+ status information is in the form of a C{dict}. Each element of
+ C{names} is a key in the dictionary. The value for each key is the
+ corresponding response from the server.
+ """
+ cmd = 'STATUS'
+ args = "%s (%s)" % (_prepareMailboxName(mailbox), ' '.join(names))
+ resp = ('STATUS',)
+ d = self.sendCommand(Command(cmd, args, wantResponse=resp))
+ d.addCallback(self.__cbStatus)
+ return d
+
+ def __cbStatus(self, (lines, last)):
+ status = {}
+ for parts in lines:
+ if parts[0] == 'STATUS':
+ items = parts[2]
+ items = [items[i:i+2] for i in range(0, len(items), 2)]
+ status.update(dict(items))
+ for k in status.keys():
+ t = self.STATUS_TRANSFORMATIONS.get(k)
+ if t:
+ try:
+ status[k] = t(status[k])
+ except Exception, e:
+ raise IllegalServerResponse('(%s %s): %s' % (k, status[k], str(e)))
+ return status
+
+ def append(self, mailbox, message, flags = (), date = None):
+ """Add the given message to the given mailbox.
+
+ This command is allowed in the Authenticated and Selected states.
+
+ @type mailbox: C{str}
+ @param mailbox: The mailbox to which to add this message.
+
+ @type message: Any file-like object
+ @param message: The message to add, in RFC822 format. Newlines
+ in this file should be \\r\\n-style.
+
+ @type flags: Any iterable of C{str}
+ @param flags: The flags to associated with this message.
+
+ @type date: C{str}
+ @param date: The date to associate with this message. This should
+ be of the format DD-MM-YYYY HH:MM:SS +/-HHMM. For example, in
+ Eastern Standard Time, on July 1st 2004 at half past 1 PM,
+ \"01-07-2004 13:30:00 -0500\".
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked when this command
+ succeeds or whose errback is invoked if it fails.
+ """
+ message.seek(0, 2)
+ L = message.tell()
+ message.seek(0, 0)
+ fmt = '%s (%s)%s {%d}'
+ if date:
+ date = ' "%s"' % date
+ else:
+ date = ''
+ cmd = fmt % (
+ _prepareMailboxName(mailbox), ' '.join(flags),
+ date, L
+ )
+ d = self.sendCommand(Command('APPEND', cmd, (), self.__cbContinueAppend, message))
+ return d
+
+ def __cbContinueAppend(self, lines, message):
+ s = basic.FileSender()
+ return s.beginFileTransfer(message, self.transport, None
+ ).addCallback(self.__cbFinishAppend)
+
+ def __cbFinishAppend(self, foo):
+ self.sendLine('')
+
+ def check(self):
+ """Tell the server to perform a checkpoint
+
+ This command is allowed in the Selected state.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked when this command
+ succeeds or whose errback is invoked if it fails.
+ """
+ return self.sendCommand(Command('CHECK'))
+
+ def close(self):
+ """Return the connection to the Authenticated state.
+
+ This command is allowed in the Selected state.
+
+ Issuing this command will also remove all messages flagged \\Deleted
+ from the selected mailbox if it is opened in read-write mode,
+ otherwise it indicates success by no messages are removed.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked when the command
+ completes successfully or whose errback is invoked if it fails.
+ """
+ return self.sendCommand(Command('CLOSE'))
+
+
+ def expunge(self):
+ """Return the connection to the Authenticate state.
+
+ This command is allowed in the Selected state.
+
+ Issuing this command will perform the same actions as issuing the
+ close command, but will also generate an 'expunge' response for
+ every message deleted.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a list of the
+ 'expunge' responses when this command is successful or whose errback
+ is invoked otherwise.
+ """
+ cmd = 'EXPUNGE'
+ resp = ('EXPUNGE',)
+ d = self.sendCommand(Command(cmd, wantResponse=resp))
+ d.addCallback(self.__cbExpunge)
+ return d
+
+
+ def __cbExpunge(self, (lines, last)):
+ ids = []
+ for parts in lines:
+ if len(parts) == 2 and parts[1] == 'EXPUNGE':
+ ids.append(self._intOrRaise(parts[0], parts))
+ return ids
+
+
+ def search(self, *queries, **kwarg):
+ """Search messages in the currently selected mailbox
+
+ This command is allowed in the Selected state.
+
+ Any non-zero number of queries are accepted by this method, as
+ returned by the C{Query}, C{Or}, and C{Not} functions.
+
+ One keyword argument is accepted: if uid is passed in with a non-zero
+ value, the server is asked to return message UIDs instead of message
+ sequence numbers.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback will be invoked with a list of all
+ the message sequence numbers return by the search, or whose errback
+ will be invoked if there is an error.
+ """
+ if kwarg.get('uid'):
+ cmd = 'UID SEARCH'
+ else:
+ cmd = 'SEARCH'
+ args = ' '.join(queries)
+ d = self.sendCommand(Command(cmd, args, wantResponse=(cmd,)))
+ d.addCallback(self.__cbSearch)
+ return d
+
+
+ def __cbSearch(self, (lines, end)):
+ ids = []
+ for parts in lines:
+ if len(parts) > 0 and parts[0] == 'SEARCH':
+ ids.extend([self._intOrRaise(p, parts) for p in parts[1:]])
+ return ids
+
+
+ def fetchUID(self, messages, uid=0):
+ """Retrieve the unique identifier for one or more messages
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message sequence numbers to unique message identifiers, or whose
+ errback is invoked if there is an error.
+ """
+ return self._fetch(messages, useUID=uid, uid=1)
+
+
+ def fetchFlags(self, messages, uid=0):
+ """Retrieve the flags for one or more messages
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: The messages for which to retrieve flags.
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to lists of flags, or whose errback is invoked if
+ there is an error.
+ """
+ return self._fetch(str(messages), useUID=uid, flags=1)
+
+
+ def fetchInternalDate(self, messages, uid=0):
+ """Retrieve the internal date associated with one or more messages
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: The messages for which to retrieve the internal date.
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to date strings, or whose errback is invoked
+ if there is an error. Date strings take the format of
+ \"day-month-year time timezone\".
+ """
+ return self._fetch(str(messages), useUID=uid, internaldate=1)
+
+
+ def fetchEnvelope(self, messages, uid=0):
+ """Retrieve the envelope data for one or more messages
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: The messages for which to retrieve envelope data.
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to envelope data, or whose errback is invoked
+ if there is an error. Envelope data consists of a sequence of the
+ date, subject, from, sender, reply-to, to, cc, bcc, in-reply-to,
+ and message-id header fields. The date, subject, in-reply-to, and
+ message-id fields are strings, while the from, sender, reply-to,
+ to, cc, and bcc fields contain address data. Address data consists
+ of a sequence of name, source route, mailbox name, and hostname.
+ Fields which are not present for a particular address may be C{None}.
+ """
+ return self._fetch(str(messages), useUID=uid, envelope=1)
+
+
+ def fetchBodyStructure(self, messages, uid=0):
+ """Retrieve the structure of the body of one or more messages
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: The messages for which to retrieve body structure
+ data.
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to body structure data, or whose errback is invoked
+ if there is an error. Body structure data describes the MIME-IMB
+ format of a message and consists of a sequence of mime type, mime
+ subtype, parameters, content id, description, encoding, and size.
+ The fields following the size field are variable: if the mime
+ type/subtype is message/rfc822, the contained message's envelope
+ information, body structure data, and number of lines of text; if
+ the mime type is text, the number of lines of text. Extension fields
+ may also be included; if present, they are: the MD5 hash of the body,
+ body disposition, body language.
+ """
+ return self._fetch(messages, useUID=uid, bodystructure=1)
+
+
+ def fetchSimplifiedBody(self, messages, uid=0):
+ """Retrieve the simplified body structure of one or more messages
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to body data, or whose errback is invoked
+ if there is an error. The simplified body structure is the same
+ as the body structure, except that extension fields will never be
+ present.
+ """
+ return self._fetch(messages, useUID=uid, body=1)
+
+
+ def fetchMessage(self, messages, uid=0):
+ """Retrieve one or more entire messages
+
+ This command is allowed in the Selected state.
+
+ @type messages: L{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: L{Deferred}
+
+ @return: A L{Deferred} which will fire with a C{dict} mapping message
+ sequence numbers to C{dict}s giving message data for the
+ corresponding message. If C{uid} is true, the inner dictionaries
+ have a C{'UID'} key mapped to a C{str} giving the UID for the
+ message. The text of the message is a C{str} associated with the
+ C{'RFC822'} key in each dictionary.
+ """
+ return self._fetch(messages, useUID=uid, rfc822=1)
+
+
+ def fetchHeaders(self, messages, uid=0):
+ """Retrieve headers of one or more messages
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to dicts of message headers, or whose errback is
+ invoked if there is an error.
+ """
+ return self._fetch(messages, useUID=uid, rfc822header=1)
+
+
+ def fetchBody(self, messages, uid=0):
+ """Retrieve body text of one or more messages
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to file-like objects containing body text, or whose
+ errback is invoked if there is an error.
+ """
+ return self._fetch(messages, useUID=uid, rfc822text=1)
+
+
+ def fetchSize(self, messages, uid=0):
+ """Retrieve the size, in octets, of one or more messages
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to sizes, or whose errback is invoked if there is
+ an error.
+ """
+ return self._fetch(messages, useUID=uid, rfc822size=1)
+
+
+ def fetchFull(self, messages, uid=0):
+ """Retrieve several different fields of one or more messages
+
+ This command is allowed in the Selected state. This is equivalent
+ to issuing all of the C{fetchFlags}, C{fetchInternalDate},
+ C{fetchSize}, C{fetchEnvelope}, and C{fetchSimplifiedBody}
+ functions.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to dict of the retrieved data values, or whose
+ errback is invoked if there is an error. They dictionary keys
+ are "flags", "date", "size", "envelope", and "body".
+ """
+ return self._fetch(
+ messages, useUID=uid, flags=1, internaldate=1,
+ rfc822size=1, envelope=1, body=1)
+
+
+ def fetchAll(self, messages, uid=0):
+ """Retrieve several different fields of one or more messages
+
+ This command is allowed in the Selected state. This is equivalent
+ to issuing all of the C{fetchFlags}, C{fetchInternalDate},
+ C{fetchSize}, and C{fetchEnvelope} functions.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to dict of the retrieved data values, or whose
+ errback is invoked if there is an error. They dictionary keys
+ are "flags", "date", "size", and "envelope".
+ """
+ return self._fetch(
+ messages, useUID=uid, flags=1, internaldate=1,
+ rfc822size=1, envelope=1)
+
+
+ def fetchFast(self, messages, uid=0):
+ """Retrieve several different fields of one or more messages
+
+ This command is allowed in the Selected state. This is equivalent
+ to issuing all of the C{fetchFlags}, C{fetchInternalDate}, and
+ C{fetchSize} functions.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a dict mapping
+ message numbers to dict of the retrieved data values, or whose
+ errback is invoked if there is an error. They dictionary keys are
+ "flags", "date", and "size".
+ """
+ return self._fetch(
+ messages, useUID=uid, flags=1, internaldate=1, rfc822size=1)
+
+
+ def _parseFetchPairs(self, fetchResponseList):
+ """
+ Given the result of parsing a single I{FETCH} response, construct a
+ C{dict} mapping response keys to response values.
+
+ @param fetchResponseList: The result of parsing a I{FETCH} response
+ with L{parseNestedParens} and extracting just the response data
+ (that is, just the part that comes after C{"FETCH"}). The form
+ of this input (and therefore the output of this method) is very
+ disagreable. A valuable improvement would be to enumerate the
+ possible keys (representing them as structured objects of some
+ sort) rather than using strings and tuples of tuples of strings
+ and so forth. This would allow the keys to be documented more
+ easily and would allow for a much simpler application-facing API
+ (one not based on looking up somewhat hard to predict keys in a
+ dict). Since C{fetchResponseList} notionally represents a
+ flattened sequence of pairs (identifying keys followed by their
+ associated values), collapsing such complex elements of this
+ list as C{["BODY", ["HEADER.FIELDS", ["SUBJECT"]]]} into a
+ single object would also greatly simplify the implementation of
+ this method.
+
+ @return: A C{dict} of the response data represented by C{pairs}. Keys
+ in this dictionary are things like C{"RFC822.TEXT"}, C{"FLAGS"}, or
+ C{("BODY", ("HEADER.FIELDS", ("SUBJECT",)))}. Values are entirely
+ dependent on the key with which they are associated, but retain the
+ same structured as produced by L{parseNestedParens}.
+ """
+ values = {}
+ responseParts = iter(fetchResponseList)
+ while True:
+ try:
+ key = responseParts.next()
+ except StopIteration:
+ break
+
+ try:
+ value = responseParts.next()
+ except StopIteration:
+ raise IllegalServerResponse(
+ "Not enough arguments", fetchResponseList)
+
+ # The parsed forms of responses like:
+ #
+ # BODY[] VALUE
+ # BODY[TEXT] VALUE
+ # BODY[HEADER.FIELDS (SUBJECT)] VALUE
+ # BODY[HEADER.FIELDS (SUBJECT)]<N.M> VALUE
+ #
+ # are:
+ #
+ # ["BODY", [], VALUE]
+ # ["BODY", ["TEXT"], VALUE]
+ # ["BODY", ["HEADER.FIELDS", ["SUBJECT"]], VALUE]
+ # ["BODY", ["HEADER.FIELDS", ["SUBJECT"]], "<N.M>", VALUE]
+ #
+ # Here, check for these cases and grab as many extra elements as
+ # necessary to retrieve the body information.
+ if key in ("BODY", "BODY.PEEK") and isinstance(value, list) and len(value) < 3:
+ if len(value) < 2:
+ key = (key, tuple(value))
+ else:
+ key = (key, (value[0], tuple(value[1])))
+ try:
+ value = responseParts.next()
+ except StopIteration:
+ raise IllegalServerResponse(
+ "Not enough arguments", fetchResponseList)
+
+ # Handle partial ranges
+ if value.startswith('<') and value.endswith('>'):
+ try:
+ int(value[1:-1])
+ except ValueError:
+ # This isn't really a range, it's some content.
+ pass
+ else:
+ key = key + (value,)
+ try:
+ value = responseParts.next()
+ except StopIteration:
+ raise IllegalServerResponse(
+ "Not enough arguments", fetchResponseList)
+
+ values[key] = value
+ return values
+
+
+ def _cbFetch(self, (lines, last), requestedParts, structured):
+ info = {}
+ for parts in lines:
+ if len(parts) == 3 and parts[1] == 'FETCH':
+ id = self._intOrRaise(parts[0], parts)
+ if id not in info:
+ info[id] = [parts[2]]
+ else:
+ info[id][0].extend(parts[2])
+
+ results = {}
+ for (messageId, values) in info.iteritems():
+ mapping = self._parseFetchPairs(values[0])
+ results.setdefault(messageId, {}).update(mapping)
+
+ flagChanges = {}
+ for messageId in results.keys():
+ values = results[messageId]
+ for part in values.keys():
+ if part not in requestedParts and part == 'FLAGS':
+ flagChanges[messageId] = values['FLAGS']
+ # Find flags in the result and get rid of them.
+ for i in range(len(info[messageId][0])):
+ if info[messageId][0][i] == 'FLAGS':
+ del info[messageId][0][i:i+2]
+ break
+ del values['FLAGS']
+ if not values:
+ del results[messageId]
+
+ if flagChanges:
+ self.flagsChanged(flagChanges)
+
+ if structured:
+ return results
+ else:
+ return info
+
+
+ def fetchSpecific(self, messages, uid=0, headerType=None,
+ headerNumber=None, headerArgs=None, peek=None,
+ offset=None, length=None):
+ """Retrieve a specific section of one or more messages
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @type headerType: C{str}
+ @param headerType: If specified, must be one of HEADER,
+ HEADER.FIELDS, HEADER.FIELDS.NOT, MIME, or TEXT, and will determine
+ which part of the message is retrieved. For HEADER.FIELDS and
+ HEADER.FIELDS.NOT, C{headerArgs} must be a sequence of header names.
+ For MIME, C{headerNumber} must be specified.
+
+ @type headerNumber: C{int} or C{int} sequence
+ @param headerNumber: The nested rfc822 index specifying the
+ entity to retrieve. For example, C{1} retrieves the first
+ entity of the message, and C{(2, 1, 3}) retrieves the 3rd
+ entity inside the first entity inside the second entity of
+ the message.
+
+ @type headerArgs: A sequence of C{str}
+ @param headerArgs: If C{headerType} is HEADER.FIELDS, these are the
+ headers to retrieve. If it is HEADER.FIELDS.NOT, these are the
+ headers to exclude from retrieval.
+
+ @type peek: C{bool}
+ @param peek: If true, cause the server to not set the \\Seen
+ flag on this message as a result of this command.
+
+ @type offset: C{int}
+ @param offset: The number of octets at the beginning of the result
+ to skip.
+
+ @type length: C{int}
+ @param length: The number of octets to retrieve.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a mapping of
+ message numbers to retrieved data, or whose errback is invoked
+ if there is an error.
+ """
+ fmt = '%s BODY%s[%s%s%s]%s'
+ if headerNumber is None:
+ number = ''
+ elif isinstance(headerNumber, int):
+ number = str(headerNumber)
+ else:
+ number = '.'.join(map(str, headerNumber))
+ if headerType is None:
+ header = ''
+ elif number:
+ header = '.' + headerType
+ else:
+ header = headerType
+ if header and headerType not in ('TEXT', 'MIME'):
+ if headerArgs is not None:
+ payload = ' (%s)' % ' '.join(headerArgs)
+ else:
+ payload = ' ()'
+ else:
+ payload = ''
+ if offset is None:
+ extra = ''
+ else:
+ extra = '<%d.%d>' % (offset, length)
+ fetch = uid and 'UID FETCH' or 'FETCH'
+ cmd = fmt % (messages, peek and '.PEEK' or '', number, header, payload, extra)
+ d = self.sendCommand(Command(fetch, cmd, wantResponse=('FETCH',)))
+ d.addCallback(self._cbFetch, (), False)
+ return d
+
+
+ def _fetch(self, messages, useUID=0, **terms):
+ fetch = useUID and 'UID FETCH' or 'FETCH'
+
+ if 'rfc822text' in terms:
+ del terms['rfc822text']
+ terms['rfc822.text'] = True
+ if 'rfc822size' in terms:
+ del terms['rfc822size']
+ terms['rfc822.size'] = True
+ if 'rfc822header' in terms:
+ del terms['rfc822header']
+ terms['rfc822.header'] = True
+
+ cmd = '%s (%s)' % (messages, ' '.join([s.upper() for s in terms.keys()]))
+ d = self.sendCommand(Command(fetch, cmd, wantResponse=('FETCH',)))
+ d.addCallback(self._cbFetch, map(str.upper, terms.keys()), True)
+ return d
+
+ def setFlags(self, messages, flags, silent=1, uid=0):
+ """Set the flags for one or more messages.
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type flags: Any iterable of C{str}
+ @param flags: The flags to set
+
+ @type silent: C{bool}
+ @param silent: If true, cause the server to supress its verbose
+ response.
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a list of the
+ the server's responses (C{[]} if C{silent} is true) or whose
+ errback is invoked if there is an error.
+ """
+ return self._store(str(messages), 'FLAGS', silent, flags, uid)
+
+ def addFlags(self, messages, flags, silent=1, uid=0):
+ """Add to the set flags for one or more messages.
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type flags: Any iterable of C{str}
+ @param flags: The flags to set
+
+ @type silent: C{bool}
+ @param silent: If true, cause the server to supress its verbose
+ response.
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a list of the
+ the server's responses (C{[]} if C{silent} is true) or whose
+ errback is invoked if there is an error.
+ """
+ return self._store(str(messages),'+FLAGS', silent, flags, uid)
+
+ def removeFlags(self, messages, flags, silent=1, uid=0):
+ """Remove from the set flags for one or more messages.
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{MessageSet} or C{str}
+ @param messages: A message sequence set
+
+ @type flags: Any iterable of C{str}
+ @param flags: The flags to set
+
+ @type silent: C{bool}
+ @param silent: If true, cause the server to supress its verbose
+ response.
+
+ @type uid: C{bool}
+ @param uid: Indicates whether the message sequence set is of message
+ numbers or of unique message IDs.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a list of the
+ the server's responses (C{[]} if C{silent} is true) or whose
+ errback is invoked if there is an error.
+ """
+ return self._store(str(messages), '-FLAGS', silent, flags, uid)
+
+
+ def _store(self, messages, cmd, silent, flags, uid):
+ if silent:
+ cmd = cmd + '.SILENT'
+ store = uid and 'UID STORE' or 'STORE'
+ args = ' '.join((messages, cmd, '(%s)' % ' '.join(flags)))
+ d = self.sendCommand(Command(store, args, wantResponse=('FETCH',)))
+ expected = ()
+ if not silent:
+ expected = ('FLAGS',)
+ d.addCallback(self._cbFetch, expected, True)
+ return d
+
+
+ def copy(self, messages, mailbox, uid):
+ """Copy the specified messages to the specified mailbox.
+
+ This command is allowed in the Selected state.
+
+ @type messages: C{str}
+ @param messages: A message sequence set
+
+ @type mailbox: C{str}
+ @param mailbox: The mailbox to which to copy the messages
+
+ @type uid: C{bool}
+ @param uid: If true, the C{messages} refers to message UIDs, rather
+ than message sequence numbers.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with a true value
+ when the copy is successful, or whose errback is invoked if there
+ is an error.
+ """
+ if uid:
+ cmd = 'UID COPY'
+ else:
+ cmd = 'COPY'
+ args = '%s %s' % (messages, _prepareMailboxName(mailbox))
+ return self.sendCommand(Command(cmd, args))
+
+ #
+ # IMailboxListener methods
+ #
+ def modeChanged(self, writeable):
+ """Override me"""
+
+ def flagsChanged(self, newFlags):
+ """Override me"""
+
+ def newMessages(self, exists, recent):
+ """Override me"""
+
+
+class IllegalIdentifierError(IMAP4Exception): pass
+
+def parseIdList(s):
+ res = MessageSet()
+ parts = s.split(',')
+ for p in parts:
+ if ':' in p:
+ low, high = p.split(':', 1)
+ try:
+ if low == '*':
+ low = None
+ else:
+ low = long(low)
+ if high == '*':
+ high = None
+ else:
+ high = long(high)
+ res.extend((low, high))
+ except ValueError:
+ raise IllegalIdentifierError(p)
+ else:
+ try:
+ if p == '*':
+ p = None
+ else:
+ p = long(p)
+ except ValueError:
+ raise IllegalIdentifierError(p)
+ else:
+ res.extend(p)
+ return res
+
+class IllegalQueryError(IMAP4Exception): pass
+
+_SIMPLE_BOOL = (
+ 'ALL', 'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'NEW', 'OLD', 'RECENT',
+ 'SEEN', 'UNANSWERED', 'UNDELETED', 'UNDRAFT', 'UNFLAGGED', 'UNSEEN'
+)
+
+_NO_QUOTES = (
+ 'LARGER', 'SMALLER', 'UID'
+)
+
+def Query(sorted=0, **kwarg):
+ """Create a query string
+
+ Among the accepted keywords are::
+
+ all : If set to a true value, search all messages in the
+ current mailbox
+
+ answered : If set to a true value, search messages flagged with
+ \\Answered
+
+ bcc : A substring to search the BCC header field for
+
+ before : Search messages with an internal date before this
+ value. The given date should be a string in the format
+ of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
+
+ body : A substring to search the body of the messages for
+
+ cc : A substring to search the CC header field for
+
+ deleted : If set to a true value, search messages flagged with
+ \\Deleted
+
+ draft : If set to a true value, search messages flagged with
+ \\Draft
+
+ flagged : If set to a true value, search messages flagged with
+ \\Flagged
+
+ from : A substring to search the From header field for
+
+ header : A two-tuple of a header name and substring to search
+ for in that header
+
+ keyword : Search for messages with the given keyword set
+
+ larger : Search for messages larger than this number of octets
+
+ messages : Search only the given message sequence set.
+
+ new : If set to a true value, search messages flagged with
+ \\Recent but not \\Seen
+
+ old : If set to a true value, search messages not flagged with
+ \\Recent
+
+ on : Search messages with an internal date which is on this
+ date. The given date should be a string in the format
+ of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
+
+ recent : If set to a true value, search for messages flagged with
+ \\Recent
+
+ seen : If set to a true value, search for messages flagged with
+ \\Seen
+
+ sentbefore : Search for messages with an RFC822 'Date' header before
+ this date. The given date should be a string in the format
+ of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
+
+ senton : Search for messages with an RFC822 'Date' header which is
+ on this date The given date should be a string in the format
+ of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
+
+ sentsince : Search for messages with an RFC822 'Date' header which is
+ after this date. The given date should be a string in the format
+ of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
+
+ since : Search for messages with an internal date that is after
+ this date.. The given date should be a string in the format
+ of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
+
+ smaller : Search for messages smaller than this number of octets
+
+ subject : A substring to search the 'subject' header for
+
+ text : A substring to search the entire message for
+
+ to : A substring to search the 'to' header for
+
+ uid : Search only the messages in the given message set
+
+ unanswered : If set to a true value, search for messages not
+ flagged with \\Answered
+
+ undeleted : If set to a true value, search for messages not
+ flagged with \\Deleted
+
+ undraft : If set to a true value, search for messages not
+ flagged with \\Draft
+
+ unflagged : If set to a true value, search for messages not
+ flagged with \\Flagged
+
+ unkeyword : Search for messages without the given keyword set
+
+ unseen : If set to a true value, search for messages not
+ flagged with \\Seen
+
+ @type sorted: C{bool}
+ @param sorted: If true, the output will be sorted, alphabetically.
+ The standard does not require it, but it makes testing this function
+ easier. The default is zero, and this should be acceptable for any
+ application.
+
+ @rtype: C{str}
+ @return: The formatted query string
+ """
+ cmd = []
+ keys = kwarg.keys()
+ if sorted:
+ keys.sort()
+ for k in keys:
+ v = kwarg[k]
+ k = k.upper()
+ if k in _SIMPLE_BOOL and v:
+ cmd.append(k)
+ elif k == 'HEADER':
+ cmd.extend([k, v[0], '"%s"' % (v[1],)])
+ elif k not in _NO_QUOTES:
+ cmd.extend([k, '"%s"' % (v,)])
+ else:
+ cmd.extend([k, '%s' % (v,)])
+ if len(cmd) > 1:
+ return '(%s)' % ' '.join(cmd)
+ else:
+ return ' '.join(cmd)
+
+def Or(*args):
+ """The disjunction of two or more queries"""
+ if len(args) < 2:
+ raise IllegalQueryError, args
+ elif len(args) == 2:
+ return '(OR %s %s)' % args
+ else:
+ return '(OR %s %s)' % (args[0], Or(*args[1:]))
+
+def Not(query):
+ """The negation of a query"""
+ return '(NOT %s)' % (query,)
+
+class MismatchedNesting(IMAP4Exception):
+ pass
+
+class MismatchedQuoting(IMAP4Exception):
+ pass
+
+def wildcardToRegexp(wildcard, delim=None):
+ wildcard = wildcard.replace('*', '(?:.*?)')
+ if delim is None:
+ wildcard = wildcard.replace('%', '(?:.*?)')
+ else:
+ wildcard = wildcard.replace('%', '(?:(?:[^%s])*?)' % re.escape(delim))
+ return re.compile(wildcard, re.I)
+
+def splitQuoted(s):
+ """Split a string into whitespace delimited tokens
+
+ Tokens that would otherwise be separated but are surrounded by \"
+ remain as a single token. Any token that is not quoted and is
+ equal to \"NIL\" is tokenized as C{None}.
+
+ @type s: C{str}
+ @param s: The string to be split
+
+ @rtype: C{list} of C{str}
+ @return: A list of the resulting tokens
+
+ @raise MismatchedQuoting: Raised if an odd number of quotes are present
+ """
+ s = s.strip()
+ result = []
+ word = []
+ inQuote = inWord = False
+ for i, c in enumerate(s):
+ if c == '"':
+ if i and s[i-1] == '\\':
+ word.pop()
+ word.append('"')
+ elif not inQuote:
+ inQuote = True
+ else:
+ inQuote = False
+ result.append(''.join(word))
+ word = []
+ elif not inWord and not inQuote and c not in ('"' + string.whitespace):
+ inWord = True
+ word.append(c)
+ elif inWord and not inQuote and c in string.whitespace:
+ w = ''.join(word)
+ if w == 'NIL':
+ result.append(None)
+ else:
+ result.append(w)
+ word = []
+ inWord = False
+ elif inWord or inQuote:
+ word.append(c)
+
+ if inQuote:
+ raise MismatchedQuoting(s)
+ if inWord:
+ w = ''.join(word)
+ if w == 'NIL':
+ result.append(None)
+ else:
+ result.append(w)
+
+ return result
+
+
+
+def splitOn(sequence, predicate, transformers):
+ result = []
+ mode = predicate(sequence[0])
+ tmp = [sequence[0]]
+ for e in sequence[1:]:
+ p = predicate(e)
+ if p != mode:
+ result.extend(transformers[mode](tmp))
+ tmp = [e]
+ mode = p
+ else:
+ tmp.append(e)
+ result.extend(transformers[mode](tmp))
+ return result
+
+def collapseStrings(results):
+ """
+ Turns a list of length-one strings and lists into a list of longer
+ strings and lists. For example,
+
+ ['a', 'b', ['c', 'd']] is returned as ['ab', ['cd']]
+
+ @type results: C{list} of C{str} and C{list}
+ @param results: The list to be collapsed
+
+ @rtype: C{list} of C{str} and C{list}
+ @return: A new list which is the collapsed form of C{results}
+ """
+ copy = []
+ begun = None
+ listsList = [isinstance(s, types.ListType) for s in results]
+
+ pred = lambda e: isinstance(e, types.TupleType)
+ tran = {
+ 0: lambda e: splitQuoted(''.join(e)),
+ 1: lambda e: [''.join([i[0] for i in e])]
+ }
+ for (i, c, isList) in zip(range(len(results)), results, listsList):
+ if isList:
+ if begun is not None:
+ copy.extend(splitOn(results[begun:i], pred, tran))
+ begun = None
+ copy.append(collapseStrings(c))
+ elif begun is None:
+ begun = i
+ if begun is not None:
+ copy.extend(splitOn(results[begun:], pred, tran))
+ return copy
+
+
+def parseNestedParens(s, handleLiteral = 1):
+ """Parse an s-exp-like string into a more useful data structure.
+
+ @type s: C{str}
+ @param s: The s-exp-like string to parse
+
+ @rtype: C{list} of C{str} and C{list}
+ @return: A list containing the tokens present in the input.
+
+ @raise MismatchedNesting: Raised if the number or placement
+ of opening or closing parenthesis is invalid.
+ """
+ s = s.strip()
+ inQuote = 0
+ contentStack = [[]]
+ try:
+ i = 0
+ L = len(s)
+ while i < L:
+ c = s[i]
+ if inQuote:
+ if c == '\\':
+ contentStack[-1].append(s[i:i+2])
+ i += 2
+ continue
+ elif c == '"':
+ inQuote = not inQuote
+ contentStack[-1].append(c)
+ i += 1
+ else:
+ if c == '"':
+ contentStack[-1].append(c)
+ inQuote = not inQuote
+ i += 1
+ elif handleLiteral and c == '{':
+ end = s.find('}', i)
+ if end == -1:
+ raise ValueError, "Malformed literal"
+ literalSize = int(s[i+1:end])
+ contentStack[-1].append((s[end+3:end+3+literalSize],))
+ i = end + 3 + literalSize
+ elif c == '(' or c == '[':
+ contentStack.append([])
+ i += 1
+ elif c == ')' or c == ']':
+ contentStack[-2].append(contentStack.pop())
+ i += 1
+ else:
+ contentStack[-1].append(c)
+ i += 1
+ except IndexError:
+ raise MismatchedNesting(s)
+ if len(contentStack) != 1:
+ raise MismatchedNesting(s)
+ return collapseStrings(contentStack[0])
+
+def _quote(s):
+ return '"%s"' % (s.replace('\\', '\\\\').replace('"', '\\"'),)
+
+def _literal(s):
+ return '{%d}\r\n%s' % (len(s), s)
+
+class DontQuoteMe:
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return str(self.value)
+
+_ATOM_SPECIALS = '(){ %*"'
+def _needsQuote(s):
+ if s == '':
+ return 1
+ for c in s:
+ if c < '\x20' or c > '\x7f':
+ return 1
+ if c in _ATOM_SPECIALS:
+ return 1
+ return 0
+
+def _prepareMailboxName(name):
+ name = name.encode('imap4-utf-7')
+ if _needsQuote(name):
+ return _quote(name)
+ return name
+
+def _needsLiteral(s):
+ # Change this to "return 1" to wig out stupid clients
+ return '\n' in s or '\r' in s or len(s) > 1000
+
+def collapseNestedLists(items):
+ """Turn a nested list structure into an s-exp-like string.
+
+ Strings in C{items} will be sent as literals if they contain CR or LF,
+ otherwise they will be quoted. References to None in C{items} will be
+ translated to the atom NIL. Objects with a 'read' attribute will have
+ it called on them with no arguments and the returned string will be
+ inserted into the output as a literal. Integers will be converted to
+ strings and inserted into the output unquoted. Instances of
+ C{DontQuoteMe} will be converted to strings and inserted into the output
+ unquoted.
+
+ This function used to be much nicer, and only quote things that really
+ needed to be quoted (and C{DontQuoteMe} did not exist), however, many
+ broken IMAP4 clients were unable to deal with this level of sophistication,
+ forcing the current behavior to be adopted for practical reasons.
+
+ @type items: Any iterable
+
+ @rtype: C{str}
+ """
+ pieces = []
+ for i in items:
+ if i is None:
+ pieces.extend([' ', 'NIL'])
+ elif isinstance(i, (DontQuoteMe, int, long)):
+ pieces.extend([' ', str(i)])
+ elif isinstance(i, types.StringTypes):
+ if _needsLiteral(i):
+ pieces.extend([' ', '{', str(len(i)), '}', IMAP4Server.delimiter, i])
+ else:
+ pieces.extend([' ', _quote(i)])
+ elif hasattr(i, 'read'):
+ d = i.read()
+ pieces.extend([' ', '{', str(len(d)), '}', IMAP4Server.delimiter, d])
+ else:
+ pieces.extend([' ', '(%s)' % (collapseNestedLists(i),)])
+ return ''.join(pieces[1:])
+
+
+class IClientAuthentication(Interface):
+ def getName():
+ """Return an identifier associated with this authentication scheme.
+
+ @rtype: C{str}
+ """
+
+ def challengeResponse(secret, challenge):
+ """Generate a challenge response string"""
+
+
+
+class CramMD5ClientAuthenticator:
+ implements(IClientAuthentication)
+
+ def __init__(self, user):
+ self.user = user
+
+ def getName(self):
+ return "CRAM-MD5"
+
+ def challengeResponse(self, secret, chal):
+ response = hmac.HMAC(secret, chal).hexdigest()
+ return '%s %s' % (self.user, response)
+
+
+
+class LOGINAuthenticator:
+ implements(IClientAuthentication)
+
+ def __init__(self, user):
+ self.user = user
+ self.challengeResponse = self.challengeUsername
+
+ def getName(self):
+ return "LOGIN"
+
+ def challengeUsername(self, secret, chal):
+ # Respond to something like "Username:"
+ self.challengeResponse = self.challengeSecret
+ return self.user
+
+ def challengeSecret(self, secret, chal):
+ # Respond to something like "Password:"
+ return secret
+
+class PLAINAuthenticator:
+ implements(IClientAuthentication)
+
+ def __init__(self, user):
+ self.user = user
+
+ def getName(self):
+ return "PLAIN"
+
+ def challengeResponse(self, secret, chal):
+ return '\0%s\0%s' % (self.user, secret)
+
+
+class MailboxException(IMAP4Exception): pass
+
+class MailboxCollision(MailboxException):
+ def __str__(self):
+ return 'Mailbox named %s already exists' % self.args
+
+class NoSuchMailbox(MailboxException):
+ def __str__(self):
+ return 'No mailbox named %s exists' % self.args
+
+class ReadOnlyMailbox(MailboxException):
+ def __str__(self):
+ return 'Mailbox open in read-only state'
+
+
+class IAccount(Interface):
+ """Interface for Account classes
+
+ Implementors of this interface should consider implementing
+ C{INamespacePresenter}.
+ """
+
+ def addMailbox(name, mbox = None):
+ """Add a new mailbox to this account
+
+ @type name: C{str}
+ @param name: The name associated with this mailbox. It may not
+ contain multiple hierarchical parts.
+
+ @type mbox: An object implementing C{IMailbox}
+ @param mbox: The mailbox to associate with this name. If C{None},
+ a suitable default is created and used.
+
+ @rtype: C{Deferred} or C{bool}
+ @return: A true value if the creation succeeds, or a deferred whose
+ callback will be invoked when the creation succeeds.
+
+ @raise MailboxException: Raised if this mailbox cannot be added for
+ some reason. This may also be raised asynchronously, if a C{Deferred}
+ is returned.
+ """
+
+ def create(pathspec):
+ """Create a new mailbox from the given hierarchical name.
+
+ @type pathspec: C{str}
+ @param pathspec: The full hierarchical name of a new mailbox to create.
+ If any of the inferior hierarchical names to this one do not exist,
+ they are created as well.
+
+ @rtype: C{Deferred} or C{bool}
+ @return: A true value if the creation succeeds, or a deferred whose
+ callback will be invoked when the creation succeeds.
+
+ @raise MailboxException: Raised if this mailbox cannot be added.
+ This may also be raised asynchronously, if a C{Deferred} is
+ returned.
+ """
+
+ def select(name, rw=True):
+ """Acquire a mailbox, given its name.
+
+ @type name: C{str}
+ @param name: The mailbox to acquire
+
+ @type rw: C{bool}
+ @param rw: If a true value, request a read-write version of this
+ mailbox. If a false value, request a read-only version.
+
+ @rtype: Any object implementing C{IMailbox} or C{Deferred}
+ @return: The mailbox object, or a C{Deferred} whose callback will
+ be invoked with the mailbox object. None may be returned if the
+ specified mailbox may not be selected for any reason.
+ """
+
+ def delete(name):
+ """Delete the mailbox with the specified name.
+
+ @type name: C{str}
+ @param name: The mailbox to delete.
+
+ @rtype: C{Deferred} or C{bool}
+ @return: A true value if the mailbox is successfully deleted, or a
+ C{Deferred} whose callback will be invoked when the deletion
+ completes.
+
+ @raise MailboxException: Raised if this mailbox cannot be deleted.
+ This may also be raised asynchronously, if a C{Deferred} is returned.
+ """
+
+ def rename(oldname, newname):
+ """Rename a mailbox
+
+ @type oldname: C{str}
+ @param oldname: The current name of the mailbox to rename.
+
+ @type newname: C{str}
+ @param newname: The new name to associate with the mailbox.
+
+ @rtype: C{Deferred} or C{bool}
+ @return: A true value if the mailbox is successfully renamed, or a
+ C{Deferred} whose callback will be invoked when the rename operation
+ is completed.
+
+ @raise MailboxException: Raised if this mailbox cannot be
+ renamed. This may also be raised asynchronously, if a C{Deferred}
+ is returned.
+ """
+
+ def isSubscribed(name):
+ """Check the subscription status of a mailbox
+
+ @type name: C{str}
+ @param name: The name of the mailbox to check
+
+ @rtype: C{Deferred} or C{bool}
+ @return: A true value if the given mailbox is currently subscribed
+ to, a false value otherwise. A C{Deferred} may also be returned
+ whose callback will be invoked with one of these values.
+ """
+
+ def subscribe(name):
+ """Subscribe to a mailbox
+
+ @type name: C{str}
+ @param name: The name of the mailbox to subscribe to
+
+ @rtype: C{Deferred} or C{bool}
+ @return: A true value if the mailbox is subscribed to successfully,
+ or a Deferred whose callback will be invoked with this value when
+ the subscription is successful.
+
+ @raise MailboxException: Raised if this mailbox cannot be
+ subscribed to. This may also be raised asynchronously, if a
+ C{Deferred} is returned.
+ """
+
+ def unsubscribe(name):
+ """Unsubscribe from a mailbox
+
+ @type name: C{str}
+ @param name: The name of the mailbox to unsubscribe from
+
+ @rtype: C{Deferred} or C{bool}
+ @return: A true value if the mailbox is unsubscribed from successfully,
+ or a Deferred whose callback will be invoked with this value when
+ the unsubscription is successful.
+
+ @raise MailboxException: Raised if this mailbox cannot be
+ unsubscribed from. This may also be raised asynchronously, if a
+ C{Deferred} is returned.
+ """
+
+ def listMailboxes(ref, wildcard):
+ """List all the mailboxes that meet a certain criteria
+
+ @type ref: C{str}
+ @param ref: The context in which to apply the wildcard
+
+ @type wildcard: C{str}
+ @param wildcard: An expression against which to match mailbox names.
+ '*' matches any number of characters in a mailbox name, and '%'
+ matches similarly, but will not match across hierarchical boundaries.
+
+ @rtype: C{list} of C{tuple}
+ @return: A list of C{(mailboxName, mailboxObject)} which meet the
+ given criteria. C{mailboxObject} should implement either
+ C{IMailboxInfo} or C{IMailbox}. A Deferred may also be returned.
+ """
+
+class INamespacePresenter(Interface):
+ def getPersonalNamespaces():
+ """Report the available personal namespaces.
+
+ Typically there should be only one personal namespace. A common
+ name for it is \"\", and its hierarchical delimiter is usually
+ \"/\".
+
+ @rtype: iterable of two-tuples of strings
+ @return: The personal namespaces and their hierarchical delimiters.
+ If no namespaces of this type exist, None should be returned.
+ """
+
+ def getSharedNamespaces():
+ """Report the available shared namespaces.
+
+ Shared namespaces do not belong to any individual user but are
+ usually to one or more of them. Examples of shared namespaces
+ might be \"#news\" for a usenet gateway.
+
+ @rtype: iterable of two-tuples of strings
+ @return: The shared namespaces and their hierarchical delimiters.
+ If no namespaces of this type exist, None should be returned.
+ """
+
+ def getUserNamespaces():
+ """Report the available user namespaces.
+
+ These are namespaces that contain folders belonging to other users
+ access to which this account has been granted.
+
+ @rtype: iterable of two-tuples of strings
+ @return: The user namespaces and their hierarchical delimiters.
+ If no namespaces of this type exist, None should be returned.
+ """
+
+
+class MemoryAccount(object):
+ implements(IAccount, INamespacePresenter)
+
+ mailboxes = None
+ subscriptions = None
+ top_id = 0
+
+ def __init__(self, name):
+ self.name = name
+ self.mailboxes = {}
+ self.subscriptions = []
+
+ def allocateID(self):
+ id = self.top_id
+ self.top_id += 1
+ return id
+
+ ##
+ ## IAccount
+ ##
+ def addMailbox(self, name, mbox = None):
+ name = name.upper()
+ if self.mailboxes.has_key(name):
+ raise MailboxCollision, name
+ if mbox is None:
+ mbox = self._emptyMailbox(name, self.allocateID())
+ self.mailboxes[name] = mbox
+ return 1
+
+ def create(self, pathspec):
+ paths = filter(None, pathspec.split('/'))
+ for accum in range(1, len(paths)):
+ try:
+ self.addMailbox('/'.join(paths[:accum]))
+ except MailboxCollision:
+ pass
+ try:
+ self.addMailbox('/'.join(paths))
+ except MailboxCollision:
+ if not pathspec.endswith('/'):
+ return False
+ return True
+
+ def _emptyMailbox(self, name, id):
+ raise NotImplementedError
+
+ def select(self, name, readwrite=1):
+ return self.mailboxes.get(name.upper())
+
+ def delete(self, name):
+ name = name.upper()
+ # See if this mailbox exists at all
+ mbox = self.mailboxes.get(name)
+ if not mbox:
+ raise MailboxException("No such mailbox")
+ # See if this box is flagged \Noselect
+ if r'\Noselect' in mbox.getFlags():
+ # Check for hierarchically inferior mailboxes with this one
+ # as part of their root.
+ for others in self.mailboxes.keys():
+ if others != name and others.startswith(name):
+ raise MailboxException, "Hierarchically inferior mailboxes exist and \\Noselect is set"
+ mbox.destroy()
+
+ # iff there are no hierarchically inferior names, we will
+ # delete it from our ken.
+ if self._inferiorNames(name) > 1:
+ del self.mailboxes[name]
+
+ def rename(self, oldname, newname):
+ oldname = oldname.upper()
+ newname = newname.upper()
+ if not self.mailboxes.has_key(oldname):
+ raise NoSuchMailbox, oldname
+
+ inferiors = self._inferiorNames(oldname)
+ inferiors = [(o, o.replace(oldname, newname, 1)) for o in inferiors]
+
+ for (old, new) in inferiors:
+ if self.mailboxes.has_key(new):
+ raise MailboxCollision, new
+
+ for (old, new) in inferiors:
+ self.mailboxes[new] = self.mailboxes[old]
+ del self.mailboxes[old]
+
+ def _inferiorNames(self, name):
+ inferiors = []
+ for infname in self.mailboxes.keys():
+ if infname.startswith(name):
+ inferiors.append(infname)
+ return inferiors
+
+ def isSubscribed(self, name):
+ return name.upper() in self.subscriptions
+
+ def subscribe(self, name):
+ name = name.upper()
+ if name not in self.subscriptions:
+ self.subscriptions.append(name)
+
+ def unsubscribe(self, name):
+ name = name.upper()
+ if name not in self.subscriptions:
+ raise MailboxException, "Not currently subscribed to " + name
+ self.subscriptions.remove(name)
+
+ def listMailboxes(self, ref, wildcard):
+ ref = self._inferiorNames(ref.upper())
+ wildcard = wildcardToRegexp(wildcard, '/')
+ return [(i, self.mailboxes[i]) for i in ref if wildcard.match(i)]
+
+ ##
+ ## INamespacePresenter
+ ##
+ def getPersonalNamespaces(self):
+ return [["", "/"]]
+
+ def getSharedNamespaces(self):
+ return None
+
+ def getOtherNamespaces(self):
+ return None
+
+
+
+_statusRequestDict = {
+ 'MESSAGES': 'getMessageCount',
+ 'RECENT': 'getRecentCount',
+ 'UIDNEXT': 'getUIDNext',
+ 'UIDVALIDITY': 'getUIDValidity',
+ 'UNSEEN': 'getUnseenCount'
+}
+def statusRequestHelper(mbox, names):
+ r = {}
+ for n in names:
+ r[n] = getattr(mbox, _statusRequestDict[n.upper()])()
+ return r
+
+def parseAddr(addr):
+ if addr is None:
+ return [(None, None, None),]
+ addrs = email.Utils.getaddresses([addr])
+ return [[fn or None, None] + addr.split('@') for fn, addr in addrs]
+
+def getEnvelope(msg):
+ headers = msg.getHeaders(True)
+ date = headers.get('date')
+ subject = headers.get('subject')
+ from_ = headers.get('from')
+ sender = headers.get('sender', from_)
+ reply_to = headers.get('reply-to', from_)
+ to = headers.get('to')
+ cc = headers.get('cc')
+ bcc = headers.get('bcc')
+ in_reply_to = headers.get('in-reply-to')
+ mid = headers.get('message-id')
+ return (date, subject, parseAddr(from_), parseAddr(sender),
+ reply_to and parseAddr(reply_to), to and parseAddr(to),
+ cc and parseAddr(cc), bcc and parseAddr(bcc), in_reply_to, mid)
+
+def getLineCount(msg):
+ # XXX - Super expensive, CACHE THIS VALUE FOR LATER RE-USE
+ # XXX - This must be the number of lines in the ENCODED version
+ lines = 0
+ for _ in msg.getBodyFile():
+ lines += 1
+ return lines
+
+def unquote(s):
+ if s[0] == s[-1] == '"':
+ return s[1:-1]
+ return s
+
+def getBodyStructure(msg, extended=False):
+ # XXX - This does not properly handle multipart messages
+ # BODYSTRUCTURE is obscenely complex and criminally under-documented.
+
+ attrs = {}
+ headers = 'content-type', 'content-id', 'content-description', 'content-transfer-encoding'
+ headers = msg.getHeaders(False, *headers)
+ mm = headers.get('content-type')
+ if mm:
+ mm = ''.join(mm.splitlines())
+ mimetype = mm.split(';')
+ if mimetype:
+ type = mimetype[0].split('/', 1)
+ if len(type) == 1:
+ major = type[0]
+ minor = None
+ elif len(type) == 2:
+ major, minor = type
+ else:
+ major = minor = None
+ attrs = dict([x.strip().lower().split('=', 1) for x in mimetype[1:]])
+ else:
+ major = minor = None
+ else:
+ major = minor = None
+
+
+ size = str(msg.getSize())
+ unquotedAttrs = [(k, unquote(v)) for (k, v) in attrs.iteritems()]
+ result = [
+ major, minor, # Main and Sub MIME types
+ unquotedAttrs, # content-type parameter list
+ headers.get('content-id'),
+ headers.get('content-description'),
+ headers.get('content-transfer-encoding'),
+ size, # Number of octets total
+ ]
+
+ if major is not None:
+ if major.lower() == 'text':
+ result.append(str(getLineCount(msg)))
+ elif (major.lower(), minor.lower()) == ('message', 'rfc822'):
+ contained = msg.getSubPart(0)
+ result.append(getEnvelope(contained))
+ result.append(getBodyStructure(contained, False))
+ result.append(str(getLineCount(contained)))
+
+ if not extended or major is None:
+ return result
+
+ if major.lower() != 'multipart':
+ headers = 'content-md5', 'content-disposition', 'content-language'
+ headers = msg.getHeaders(False, *headers)
+ disp = headers.get('content-disposition')
+
+ # XXX - I dunno if this is really right
+ if disp:
+ disp = disp.split('; ')
+ if len(disp) == 1:
+ disp = (disp[0].lower(), None)
+ elif len(disp) > 1:
+ disp = (disp[0].lower(), [x.split('=') for x in disp[1:]])
+
+ result.append(headers.get('content-md5'))
+ result.append(disp)
+ result.append(headers.get('content-language'))
+ else:
+ result = [result]
+ try:
+ i = 0
+ while True:
+ submsg = msg.getSubPart(i)
+ result.append(getBodyStructure(submsg))
+ i += 1
+ except IndexError:
+ result.append(minor)
+ result.append(attrs.items())
+
+ # XXX - I dunno if this is really right
+ headers = msg.getHeaders(False, 'content-disposition', 'content-language')
+ disp = headers.get('content-disposition')
+ if disp:
+ disp = disp.split('; ')
+ if len(disp) == 1:
+ disp = (disp[0].lower(), None)
+ elif len(disp) > 1:
+ disp = (disp[0].lower(), [x.split('=') for x in disp[1:]])
+
+ result.append(disp)
+ result.append(headers.get('content-language'))
+
+ return result
+
+class IMessagePart(Interface):
+ def getHeaders(negate, *names):
+ """Retrieve a group of message headers.
+
+ @type names: C{tuple} of C{str}
+ @param names: The names of the headers to retrieve or omit.
+
+ @type negate: C{bool}
+ @param negate: If True, indicates that the headers listed in C{names}
+ should be omitted from the return value, rather than included.
+
+ @rtype: C{dict}
+ @return: A mapping of header field names to header field values
+ """
+
+ def getBodyFile():
+ """Retrieve a file object containing only the body of this message.
+ """
+
+ def getSize():
+ """Retrieve the total size, in octets, of this message.
+
+ @rtype: C{int}
+ """
+
+ def isMultipart():
+ """Indicate whether this message has subparts.
+
+ @rtype: C{bool}
+ """
+
+ def getSubPart(part):
+ """Retrieve a MIME sub-message
+
+ @type part: C{int}
+ @param part: The number of the part to retrieve, indexed from 0.
+
+ @raise IndexError: Raised if the specified part does not exist.
+ @raise TypeError: Raised if this message is not multipart.
+
+ @rtype: Any object implementing C{IMessagePart}.
+ @return: The specified sub-part.
+ """
+
+class IMessage(IMessagePart):
+ def getUID():
+ """Retrieve the unique identifier associated with this message.
+ """
+
+ def getFlags():
+ """Retrieve the flags associated with this message.
+
+ @rtype: C{iterable}
+ @return: The flags, represented as strings.
+ """
+
+ def getInternalDate():
+ """Retrieve the date internally associated with this message.
+
+ @rtype: C{str}
+ @return: An RFC822-formatted date string.
+ """
+
+class IMessageFile(Interface):
+ """Optional message interface for representing messages as files.
+
+ If provided by message objects, this interface will be used instead
+ the more complex MIME-based interface.
+ """
+ def open():
+ """Return an file-like object opened for reading.
+
+ Reading from the returned file will return all the bytes
+ of which this message consists.
+ """
+
+class ISearchableMailbox(Interface):
+ def search(query, uid):
+ """Search for messages that meet the given query criteria.
+
+ If this interface is not implemented by the mailbox, L{IMailbox.fetch}
+ and various methods of L{IMessage} will be used instead.
+
+ Implementations which wish to offer better performance than the
+ default implementation should implement this interface.
+
+ @type query: C{list}
+ @param query: The search criteria
+
+ @type uid: C{bool}
+ @param uid: If true, the IDs specified in the query are UIDs;
+ otherwise they are message sequence IDs.
+
+ @rtype: C{list} or C{Deferred}
+ @return: A list of message sequence numbers or message UIDs which
+ match the search criteria or a C{Deferred} whose callback will be
+ invoked with such a list.
+ """
+
+class IMessageCopier(Interface):
+ def copy(messageObject):
+ """Copy the given message object into this mailbox.
+
+ The message object will be one which was previously returned by
+ L{IMailbox.fetch}.
+
+ Implementations which wish to offer better performance than the
+ default implementation should implement this interface.
+
+ If this interface is not implemented by the mailbox, IMailbox.addMessage
+ will be used instead.
+
+ @rtype: C{Deferred} or C{int}
+ @return: Either the UID of the message or a Deferred which fires
+ with the UID when the copy finishes.
+ """
+
+class IMailboxInfo(Interface):
+ """Interface specifying only the methods required for C{listMailboxes}.
+
+ Implementations can return objects implementing only these methods for
+ return to C{listMailboxes} if it can allow them to operate more
+ efficiently.
+ """
+
+ def getFlags():
+ """Return the flags defined in this mailbox
+
+ Flags with the \\ prefix are reserved for use as system flags.
+
+ @rtype: C{list} of C{str}
+ @return: A list of the flags that can be set on messages in this mailbox.
+ """
+
+ def getHierarchicalDelimiter():
+ """Get the character which delimits namespaces for in this mailbox.
+
+ @rtype: C{str}
+ """
+
+class IMailbox(IMailboxInfo):
+ def getUIDValidity():
+ """Return the unique validity identifier for this mailbox.
+
+ @rtype: C{int}
+ """
+
+ def getUIDNext():
+ """Return the likely UID for the next message added to this mailbox.
+
+ @rtype: C{int}
+ """
+
+ def getUID(message):
+ """Return the UID of a message in the mailbox
+
+ @type message: C{int}
+ @param message: The message sequence number
+
+ @rtype: C{int}
+ @return: The UID of the message.
+ """
+
+ def getMessageCount():
+ """Return the number of messages in this mailbox.
+
+ @rtype: C{int}
+ """
+
+ def getRecentCount():
+ """Return the number of messages with the 'Recent' flag.
+
+ @rtype: C{int}
+ """
+
+ def getUnseenCount():
+ """Return the number of messages with the 'Unseen' flag.
+
+ @rtype: C{int}
+ """
+
+ def isWriteable():
+ """Get the read/write status of the mailbox.
+
+ @rtype: C{int}
+ @return: A true value if write permission is allowed, a false value otherwise.
+ """
+
+ def destroy():
+ """Called before this mailbox is deleted, permanently.
+
+ If necessary, all resources held by this mailbox should be cleaned
+ up here. This function _must_ set the \\Noselect flag on this
+ mailbox.
+ """
+
+ def requestStatus(names):
+ """Return status information about this mailbox.
+
+ Mailboxes which do not intend to do any special processing to
+ generate the return value, C{statusRequestHelper} can be used
+ to build the dictionary by calling the other interface methods
+ which return the data for each name.
+
+ @type names: Any iterable
+ @param names: The status names to return information regarding.
+ The possible values for each name are: MESSAGES, RECENT, UIDNEXT,
+ UIDVALIDITY, UNSEEN.
+
+ @rtype: C{dict} or C{Deferred}
+ @return: A dictionary containing status information about the
+ requested names is returned. If the process of looking this
+ information up would be costly, a deferred whose callback will
+ eventually be passed this dictionary is returned instead.
+ """
+
+ def addListener(listener):
+ """Add a mailbox change listener
+
+ @type listener: Any object which implements C{IMailboxListener}
+ @param listener: An object to add to the set of those which will
+ be notified when the contents of this mailbox change.
+ """
+
+ def removeListener(listener):
+ """Remove a mailbox change listener
+
+ @type listener: Any object previously added to and not removed from
+ this mailbox as a listener.
+ @param listener: The object to remove from the set of listeners.
+
+ @raise ValueError: Raised when the given object is not a listener for
+ this mailbox.
+ """
+
+ def addMessage(message, flags = (), date = None):
+ """Add the given message to this mailbox.
+
+ @type message: A file-like object
+ @param message: The RFC822 formatted message
+
+ @type flags: Any iterable of C{str}
+ @param flags: The flags to associate with this message
+
+ @type date: C{str}
+ @param date: If specified, the date to associate with this
+ message.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked with the message
+ id if the message is added successfully and whose errback is
+ invoked otherwise.
+
+ @raise ReadOnlyMailbox: Raised if this Mailbox is not open for
+ read-write.
+ """
+
+ def expunge():
+ """Remove all messages flagged \\Deleted.
+
+ @rtype: C{list} or C{Deferred}
+ @return: The list of message sequence numbers which were deleted,
+ or a C{Deferred} whose callback will be invoked with such a list.
+
+ @raise ReadOnlyMailbox: Raised if this Mailbox is not open for
+ read-write.
+ """
+
+ def fetch(messages, uid):
+ """Retrieve one or more messages.
+
+ @type messages: C{MessageSet}
+ @param messages: The identifiers of messages to retrieve information
+ about
+
+ @type uid: C{bool}
+ @param uid: If true, the IDs specified in the query are UIDs;
+ otherwise they are message sequence IDs.
+
+ @rtype: Any iterable of two-tuples of message sequence numbers and
+ implementors of C{IMessage}.
+ """
+
+ def store(messages, flags, mode, uid):
+ """Set the flags of one or more messages.
+
+ @type messages: A MessageSet object with the list of messages requested
+ @param messages: The identifiers of the messages to set the flags of.
+
+ @type flags: sequence of C{str}
+ @param flags: The flags to set, unset, or add.
+
+ @type mode: -1, 0, or 1
+ @param mode: If mode is -1, these flags should be removed from the
+ specified messages. If mode is 1, these flags should be added to
+ the specified messages. If mode is 0, all existing flags should be
+ cleared and these flags should be added.
+
+ @type uid: C{bool}
+ @param uid: If true, the IDs specified in the query are UIDs;
+ otherwise they are message sequence IDs.
+
+ @rtype: C{dict} or C{Deferred}
+ @return: A C{dict} mapping message sequence numbers to sequences of C{str}
+ representing the flags set on the message after this operation has
+ been performed, or a C{Deferred} whose callback will be invoked with
+ such a C{dict}.
+
+ @raise ReadOnlyMailbox: Raised if this mailbox is not open for
+ read-write.
+ """
+
+class ICloseableMailbox(Interface):
+ """A supplementary interface for mailboxes which require cleanup on close.
+
+ Implementing this interface is optional. If it is implemented, the protocol
+ code will call the close method defined whenever a mailbox is closed.
+ """
+ def close():
+ """Close this mailbox.
+
+ @return: A C{Deferred} which fires when this mailbox
+ has been closed, or None if the mailbox can be closed
+ immediately.
+ """
+
+def _formatHeaders(headers):
+ hdrs = [': '.join((k.title(), '\r\n'.join(v.splitlines()))) for (k, v)
+ in headers.iteritems()]
+ hdrs = '\r\n'.join(hdrs) + '\r\n'
+ return hdrs
+
+def subparts(m):
+ i = 0
+ try:
+ while True:
+ yield m.getSubPart(i)
+ i += 1
+ except IndexError:
+ pass
+
+def iterateInReactor(i):
+ """Consume an interator at most a single iteration per reactor iteration.
+
+ If the iterator produces a Deferred, the next iteration will not occur
+ until the Deferred fires, otherwise the next iteration will be taken
+ in the next reactor iteration.
+
+ @rtype: C{Deferred}
+ @return: A deferred which fires (with None) when the iterator is
+ exhausted or whose errback is called if there is an exception.
+ """
+ from twisted.internet import reactor
+ d = defer.Deferred()
+ def go(last):
+ try:
+ r = i.next()
+ except StopIteration:
+ d.callback(last)
+ except:
+ d.errback()
+ else:
+ if isinstance(r, defer.Deferred):
+ r.addCallback(go)
+ else:
+ reactor.callLater(0, go, r)
+ go(None)
+ return d
+
+class MessageProducer:
+ CHUNK_SIZE = 2 ** 2 ** 2 ** 2
+
+ def __init__(self, msg, buffer = None, scheduler = None):
+ """Produce this message.
+
+ @param msg: The message I am to produce.
+ @type msg: L{IMessage}
+
+ @param buffer: A buffer to hold the message in. If None, I will
+ use a L{tempfile.TemporaryFile}.
+ @type buffer: file-like
+ """
+ self.msg = msg
+ if buffer is None:
+ buffer = tempfile.TemporaryFile()
+ self.buffer = buffer
+ if scheduler is None:
+ scheduler = iterateInReactor
+ self.scheduler = scheduler
+ self.write = self.buffer.write
+
+ def beginProducing(self, consumer):
+ self.consumer = consumer
+ return self.scheduler(self._produce())
+
+ def _produce(self):
+ headers = self.msg.getHeaders(True)
+ boundary = None
+ if self.msg.isMultipart():
+ content = headers.get('content-type')
+ parts = [x.split('=', 1) for x in content.split(';')[1:]]
+ parts = dict([(k.lower().strip(), v) for (k, v) in parts])
+ boundary = parts.get('boundary')
+ if boundary is None:
+ # Bastards
+ boundary = '----=_%f_boundary_%f' % (time.time(), random.random())
+ headers['content-type'] += '; boundary="%s"' % (boundary,)
+ else:
+ if boundary.startswith('"') and boundary.endswith('"'):
+ boundary = boundary[1:-1]
+
+ self.write(_formatHeaders(headers))
+ self.write('\r\n')
+ if self.msg.isMultipart():
+ for p in subparts(self.msg):
+ self.write('\r\n--%s\r\n' % (boundary,))
+ yield MessageProducer(p, self.buffer, self.scheduler
+ ).beginProducing(None
+ )
+ self.write('\r\n--%s--\r\n' % (boundary,))
+ else:
+ f = self.msg.getBodyFile()
+ while True:
+ b = f.read(self.CHUNK_SIZE)
+ if b:
+ self.buffer.write(b)
+ yield None
+ else:
+ break
+ if self.consumer:
+ self.buffer.seek(0, 0)
+ yield FileProducer(self.buffer
+ ).beginProducing(self.consumer
+ ).addCallback(lambda _: self
+ )
+
+class _FetchParser:
+ class Envelope:
+ # Response should be a list of fields from the message:
+ # date, subject, from, sender, reply-to, to, cc, bcc, in-reply-to,
+ # and message-id.
+ #
+ # from, sender, reply-to, to, cc, and bcc are themselves lists of
+ # address information:
+ # personal name, source route, mailbox name, host name
+ #
+ # reply-to and sender must not be None. If not present in a message
+ # they should be defaulted to the value of the from field.
+ type = 'envelope'
+ __str__ = lambda self: 'envelope'
+
+ class Flags:
+ type = 'flags'
+ __str__ = lambda self: 'flags'
+
+ class InternalDate:
+ type = 'internaldate'
+ __str__ = lambda self: 'internaldate'
+
+ class RFC822Header:
+ type = 'rfc822header'
+ __str__ = lambda self: 'rfc822.header'
+
+ class RFC822Text:
+ type = 'rfc822text'
+ __str__ = lambda self: 'rfc822.text'
+
+ class RFC822Size:
+ type = 'rfc822size'
+ __str__ = lambda self: 'rfc822.size'
+
+ class RFC822:
+ type = 'rfc822'
+ __str__ = lambda self: 'rfc822'
+
+ class UID:
+ type = 'uid'
+ __str__ = lambda self: 'uid'
+
+ class Body:
+ type = 'body'
+ peek = False
+ header = None
+ mime = None
+ text = None
+ part = ()
+ empty = False
+ partialBegin = None
+ partialLength = None
+ def __str__(self):
+ base = 'BODY'
+ part = ''
+ separator = ''
+ if self.part:
+ part = '.'.join([str(x + 1) for x in self.part])
+ separator = '.'
+# if self.peek:
+# base += '.PEEK'
+ if self.header:
+ base += '[%s%s%s]' % (part, separator, self.header,)
+ elif self.text:
+ base += '[%s%sTEXT]' % (part, separator)
+ elif self.mime:
+ base += '[%s%sMIME]' % (part, separator)
+ elif self.empty:
+ base += '[%s]' % (part,)
+ if self.partialBegin is not None:
+ base += '<%d.%d>' % (self.partialBegin, self.partialLength)
+ return base
+
+ class BodyStructure:
+ type = 'bodystructure'
+ __str__ = lambda self: 'bodystructure'
+
+ # These three aren't top-level, they don't need type indicators
+ class Header:
+ negate = False
+ fields = None
+ part = None
+ def __str__(self):
+ base = 'HEADER'
+ if self.fields:
+ base += '.FIELDS'
+ if self.negate:
+ base += '.NOT'
+ fields = []
+ for f in self.fields:
+ f = f.title()
+ if _needsQuote(f):
+ f = _quote(f)
+ fields.append(f)
+ base += ' (%s)' % ' '.join(fields)
+ if self.part:
+ base = '.'.join([str(x + 1) for x in self.part]) + '.' + base
+ return base
+
+ class Text:
+ pass
+
+ class MIME:
+ pass
+
+ parts = None
+
+ _simple_fetch_att = [
+ ('envelope', Envelope),
+ ('flags', Flags),
+ ('internaldate', InternalDate),
+ ('rfc822.header', RFC822Header),
+ ('rfc822.text', RFC822Text),
+ ('rfc822.size', RFC822Size),
+ ('rfc822', RFC822),
+ ('uid', UID),
+ ('bodystructure', BodyStructure),
+ ]
+
+ def __init__(self):
+ self.state = ['initial']
+ self.result = []
+ self.remaining = ''
+
+ def parseString(self, s):
+ s = self.remaining + s
+ try:
+ while s or self.state:
+ # print 'Entering state_' + self.state[-1] + ' with', repr(s)
+ state = self.state.pop()
+ try:
+ used = getattr(self, 'state_' + state)(s)
+ except:
+ self.state.append(state)
+ raise
+ else:
+ # print state, 'consumed', repr(s[:used])
+ s = s[used:]
+ finally:
+ self.remaining = s
+
+ def state_initial(self, s):
+ # In the initial state, the literals "ALL", "FULL", and "FAST"
+ # are accepted, as is a ( indicating the beginning of a fetch_att
+ # token, as is the beginning of a fetch_att token.
+ if s == '':
+ return 0
+
+ l = s.lower()
+ if l.startswith('all'):
+ self.result.extend((
+ self.Flags(), self.InternalDate(),
+ self.RFC822Size(), self.Envelope()
+ ))
+ return 3
+ if l.startswith('full'):
+ self.result.extend((
+ self.Flags(), self.InternalDate(),
+ self.RFC822Size(), self.Envelope(),
+ self.Body()
+ ))
+ return 4
+ if l.startswith('fast'):
+ self.result.extend((
+ self.Flags(), self.InternalDate(), self.RFC822Size(),
+ ))
+ return 4
+
+ if l.startswith('('):
+ self.state.extend(('close_paren', 'maybe_fetch_att', 'fetch_att'))
+ return 1
+
+ self.state.append('fetch_att')
+ return 0
+
+ def state_close_paren(self, s):
+ if s.startswith(')'):
+ return 1
+ raise Exception("Missing )")
+
+ def state_whitespace(self, s):
+ # Eat up all the leading whitespace
+ if not s or not s[0].isspace():
+ raise Exception("Whitespace expected, none found")
+ i = 0
+ for i in range(len(s)):
+ if not s[i].isspace():
+ break
+ return i
+
+ def state_maybe_fetch_att(self, s):
+ if not s.startswith(')'):
+ self.state.extend(('maybe_fetch_att', 'fetch_att', 'whitespace'))
+ return 0
+
+ def state_fetch_att(self, s):
+ # Allowed fetch_att tokens are "ENVELOPE", "FLAGS", "INTERNALDATE",
+ # "RFC822", "RFC822.HEADER", "RFC822.SIZE", "RFC822.TEXT", "BODY",
+ # "BODYSTRUCTURE", "UID",
+ # "BODY [".PEEK"] [<section>] ["<" <number> "." <nz_number> ">"]
+
+ l = s.lower()
+ for (name, cls) in self._simple_fetch_att:
+ if l.startswith(name):
+ self.result.append(cls())
+ return len(name)
+
+ b = self.Body()
+ if l.startswith('body.peek'):
+ b.peek = True
+ used = 9
+ elif l.startswith('body'):
+ used = 4
+ else:
+ raise Exception("Nothing recognized in fetch_att: %s" % (l,))
+
+ self.pending_body = b
+ self.state.extend(('got_body', 'maybe_partial', 'maybe_section'))
+ return used
+
+ def state_got_body(self, s):
+ self.result.append(self.pending_body)
+ del self.pending_body
+ return 0
+
+ def state_maybe_section(self, s):
+ if not s.startswith("["):
+ return 0
+
+ self.state.extend(('section', 'part_number'))
+ return 1
+
+ _partExpr = re.compile(r'(\d+(?:\.\d+)*)\.?')
+ def state_part_number(self, s):
+ m = self._partExpr.match(s)
+ if m is not None:
+ self.parts = [int(p) - 1 for p in m.groups()[0].split('.')]
+ return m.end()
+ else:
+ self.parts = []
+ return 0
+
+ def state_section(self, s):
+ # Grab "HEADER]" or "HEADER.FIELDS (Header list)]" or
+ # "HEADER.FIELDS.NOT (Header list)]" or "TEXT]" or "MIME]" or
+ # just "]".
+
+ l = s.lower()
+ used = 0
+ if l.startswith(']'):
+ self.pending_body.empty = True
+ used += 1
+ elif l.startswith('header]'):
+ h = self.pending_body.header = self.Header()
+ h.negate = True
+ h.fields = ()
+ used += 7
+ elif l.startswith('text]'):
+ self.pending_body.text = self.Text()
+ used += 5
+ elif l.startswith('mime]'):
+ self.pending_body.mime = self.MIME()
+ used += 5
+ else:
+ h = self.Header()
+ if l.startswith('header.fields.not'):
+ h.negate = True
+ used += 17
+ elif l.startswith('header.fields'):
+ used += 13
+ else:
+ raise Exception("Unhandled section contents: %r" % (l,))
+
+ self.pending_body.header = h
+ self.state.extend(('finish_section', 'header_list', 'whitespace'))
+ self.pending_body.part = tuple(self.parts)
+ self.parts = None
+ return used
+
+ def state_finish_section(self, s):
+ if not s.startswith(']'):
+ raise Exception("section must end with ]")
+ return 1
+
+ def state_header_list(self, s):
+ if not s.startswith('('):
+ raise Exception("Header list must begin with (")
+ end = s.find(')')
+ if end == -1:
+ raise Exception("Header list must end with )")
+
+ headers = s[1:end].split()
+ self.pending_body.header.fields = map(str.upper, headers)
+ return end + 1
+
+ def state_maybe_partial(self, s):
+ # Grab <number.number> or nothing at all
+ if not s.startswith('<'):
+ return 0
+ end = s.find('>')
+ if end == -1:
+ raise Exception("Found < but not >")
+
+ partial = s[1:end]
+ parts = partial.split('.', 1)
+ if len(parts) != 2:
+ raise Exception("Partial specification did not include two .-delimited integers")
+ begin, length = map(int, parts)
+ self.pending_body.partialBegin = begin
+ self.pending_body.partialLength = length
+
+ return end + 1
+
+class FileProducer:
+ CHUNK_SIZE = 2 ** 2 ** 2 ** 2
+
+ firstWrite = True
+
+ def __init__(self, f):
+ self.f = f
+
+ def beginProducing(self, consumer):
+ self.consumer = consumer
+ self.produce = consumer.write
+ d = self._onDone = defer.Deferred()
+ self.consumer.registerProducer(self, False)
+ return d
+
+ def resumeProducing(self):
+ b = ''
+ if self.firstWrite:
+ b = '{%d}\r\n' % self._size()
+ self.firstWrite = False
+ if not self.f:
+ return
+ b = b + self.f.read(self.CHUNK_SIZE)
+ if not b:
+ self.consumer.unregisterProducer()
+ self._onDone.callback(self)
+ self._onDone = self.f = self.consumer = None
+ else:
+ self.produce(b)
+
+ def pauseProducing(self):
+ pass
+
+ def stopProducing(self):
+ pass
+
+ def _size(self):
+ b = self.f.tell()
+ self.f.seek(0, 2)
+ e = self.f.tell()
+ self.f.seek(b, 0)
+ return e - b
+
+def parseTime(s):
+ # XXX - This may require localization :(
+ months = [
+ 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct',
+ 'nov', 'dec', 'january', 'february', 'march', 'april', 'may', 'june',
+ 'july', 'august', 'september', 'october', 'november', 'december'
+ ]
+ expr = {
+ 'day': r"(?P<day>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
+ 'mon': r"(?P<mon>\w+)",
+ 'year': r"(?P<year>\d\d\d\d)"
+ }
+ m = re.match('%(day)s-%(mon)s-%(year)s' % expr, s)
+ if not m:
+ raise ValueError, "Cannot parse time string %r" % (s,)
+ d = m.groupdict()
+ try:
+ d['mon'] = 1 + (months.index(d['mon'].lower()) % 12)
+ d['year'] = int(d['year'])
+ d['day'] = int(d['day'])
+ except ValueError:
+ raise ValueError, "Cannot parse time string %r" % (s,)
+ else:
+ return time.struct_time(
+ (d['year'], d['mon'], d['day'], 0, 0, 0, -1, -1, -1)
+ )
+
+import codecs
+def modified_base64(s):
+ s_utf7 = s.encode('utf-7')
+ return s_utf7[1:-1].replace('/', ',')
+
+def modified_unbase64(s):
+ s_utf7 = '+' + s.replace(',', '/') + '-'
+ return s_utf7.decode('utf-7')
+
+def encoder(s, errors=None):
+ """
+ Encode the given C{unicode} string using the IMAP4 specific variation of
+ UTF-7.
+
+ @type s: C{unicode}
+ @param s: The text to encode.
+
+ @param errors: Policy for handling encoding errors. Currently ignored.
+
+ @return: C{tuple} of a C{str} giving the encoded bytes and an C{int}
+ giving the number of code units consumed from the input.
+ """
+ r = []
+ _in = []
+ for c in s:
+ if ord(c) in (range(0x20, 0x26) + range(0x27, 0x7f)):
+ if _in:
+ r.extend(['&', modified_base64(''.join(_in)), '-'])
+ del _in[:]
+ r.append(str(c))
+ elif c == '&':
+ if _in:
+ r.extend(['&', modified_base64(''.join(_in)), '-'])
+ del _in[:]
+ r.append('&-')
+ else:
+ _in.append(c)
+ if _in:
+ r.extend(['&', modified_base64(''.join(_in)), '-'])
+ return (''.join(r), len(s))
+
+def decoder(s, errors=None):
+ """
+ Decode the given C{str} using the IMAP4 specific variation of UTF-7.
+
+ @type s: C{str}
+ @param s: The bytes to decode.
+
+ @param errors: Policy for handling decoding errors. Currently ignored.
+
+ @return: a C{tuple} of a C{unicode} string giving the text which was
+ decoded and an C{int} giving the number of bytes consumed from the
+ input.
+ """
+ r = []
+ decode = []
+ for c in s:
+ if c == '&' and not decode:
+ decode.append('&')
+ elif c == '-' and decode:
+ if len(decode) == 1:
+ r.append('&')
+ else:
+ r.append(modified_unbase64(''.join(decode[1:])))
+ decode = []
+ elif decode:
+ decode.append(c)
+ else:
+ r.append(c)
+ if decode:
+ r.append(modified_unbase64(''.join(decode[1:])))
+ return (''.join(r), len(s))
+
+class StreamReader(codecs.StreamReader):
+ def decode(self, s, errors='strict'):
+ return decoder(s)
+
+class StreamWriter(codecs.StreamWriter):
+ def encode(self, s, errors='strict'):
+ return encoder(s)
+
+_codecInfo = (encoder, decoder, StreamReader, StreamWriter)
+try:
+ _codecInfoClass = codecs.CodecInfo
+except AttributeError:
+ pass
+else:
+ _codecInfo = _codecInfoClass(*_codecInfo)
+
+def imap4_utf_7(name):
+ if name == 'imap4-utf-7':
+ return _codecInfo
+codecs.register(imap4_utf_7)
+
+__all__ = [
+ # Protocol classes
+ 'IMAP4Server', 'IMAP4Client',
+
+ # Interfaces
+ 'IMailboxListener', 'IClientAuthentication', 'IAccount', 'IMailbox',
+ 'INamespacePresenter', 'ICloseableMailbox', 'IMailboxInfo',
+ 'IMessage', 'IMessageCopier', 'IMessageFile', 'ISearchableMailbox',
+
+ # Exceptions
+ 'IMAP4Exception', 'IllegalClientResponse', 'IllegalOperation',
+ 'IllegalMailboxEncoding', 'UnhandledResponse', 'NegativeResponse',
+ 'NoSupportedAuthentication', 'IllegalServerResponse',
+ 'IllegalIdentifierError', 'IllegalQueryError', 'MismatchedNesting',
+ 'MismatchedQuoting', 'MailboxException', 'MailboxCollision',
+ 'NoSuchMailbox', 'ReadOnlyMailbox',
+
+ # Auth objects
+ 'CramMD5ClientAuthenticator', 'PLAINAuthenticator', 'LOGINAuthenticator',
+ 'PLAINCredentials', 'LOGINCredentials',
+
+ # Simple query interface
+ 'Query', 'Not', 'Or',
+
+ # Miscellaneous
+ 'MemoryAccount',
+ 'statusRequestHelper',
+]
diff --git a/vendor/Twisted-10.0.0/twisted/mail/mail.py b/vendor/Twisted-10.0.0/twisted/mail/mail.py
new file mode 100644
index 0000000000..233c1b66fe
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/mail.py
@@ -0,0 +1,333 @@
+# -*- test-case-name: twisted.mail.test.test_mail -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Mail support for twisted python.
+"""
+
+# Twisted imports
+from twisted.internet import defer
+from twisted.application import service, internet
+from twisted.python import util
+from twisted.python import log
+
+from twisted import cred
+import twisted.cred.portal
+
+# Sibling imports
+from twisted.mail import protocols, smtp
+
+# System imports
+import os
+from zope.interface import implements, Interface
+
+
+class DomainWithDefaultDict:
+ '''Simulate a dictionary with a default value for non-existing keys.
+ '''
+ def __init__(self, domains, default):
+ self.domains = domains
+ self.default = default
+
+ def setDefaultDomain(self, domain):
+ self.default = domain
+
+ def has_key(self, name):
+ return 1
+
+ def fromkeys(klass, keys, value=None):
+ d = klass()
+ for k in keys:
+ d[k] = value
+ return d
+ fromkeys = classmethod(fromkeys)
+
+ def __contains__(self, name):
+ return 1
+
+ def __getitem__(self, name):
+ return self.domains.get(name, self.default)
+
+ def __setitem__(self, name, value):
+ self.domains[name] = value
+
+ def __delitem__(self, name):
+ del self.domains[name]
+
+ def __iter__(self):
+ return iter(self.domains)
+
+ def __len__(self):
+ return len(self.domains)
+
+
+ def __str__(self):
+ """
+ Return a string describing the underlying domain mapping of this
+ object.
+ """
+ return '<DomainWithDefaultDict %s>' % (self.domains,)
+
+
+ def __repr__(self):
+ """
+ Return a pseudo-executable string describing the underlying domain
+ mapping of this object.
+ """
+ return 'DomainWithDefaultDict(%s)' % (self.domains,)
+
+
+ def get(self, key, default=None):
+ return self.domains.get(key, default)
+
+ def copy(self):
+ return DomainWithDefaultDict(self.domains.copy(), self.default)
+
+ def iteritems(self):
+ return self.domains.iteritems()
+
+ def iterkeys(self):
+ return self.domains.iterkeys()
+
+ def itervalues(self):
+ return self.domains.itervalues()
+
+ def keys(self):
+ return self.domains.keys()
+
+ def values(self):
+ return self.domains.values()
+
+ def items(self):
+ return self.domains.items()
+
+ def popitem(self):
+ return self.domains.popitem()
+
+ def update(self, other):
+ return self.domains.update(other)
+
+ def clear(self):
+ return self.domains.clear()
+
+ def setdefault(self, key, default):
+ return self.domains.setdefault(key, default)
+
+class IDomain(Interface):
+ """An email domain."""
+
+ def exists(user):
+ """
+ Check whether or not the specified user exists in this domain.
+
+ @type user: C{twisted.protocols.smtp.User}
+ @param user: The user to check
+
+ @rtype: No-argument callable
+ @return: A C{Deferred} which becomes, or a callable which
+ takes no arguments and returns an object implementing C{IMessage}.
+ This will be called and the returned object used to deliver the
+ message when it arrives.
+
+ @raise twisted.protocols.smtp.SMTPBadRcpt: Raised if the given
+ user does not exist in this domain.
+ """
+
+ def addUser(user, password):
+ """Add a username/password to this domain."""
+
+ def startMessage(user):
+ """Create and return a new message to be delivered to the given user.
+
+ DEPRECATED. Implement validateTo() correctly instead.
+ """
+
+ def getCredentialsCheckers():
+ """Return a list of ICredentialsChecker implementors for this domain.
+ """
+
+class IAliasableDomain(IDomain):
+ def setAliasGroup(aliases):
+ """Set the group of defined aliases for this domain
+
+ @type aliases: C{dict}
+ @param aliases: Mapping of domain names to objects implementing
+ C{IAlias}
+ """
+
+ def exists(user, memo=None):
+ """
+ Check whether or not the specified user exists in this domain.
+
+ @type user: C{twisted.protocols.smtp.User}
+ @param user: The user to check
+
+ @type memo: C{dict}
+ @param memo: A record of the addresses already considered while
+ resolving aliases. The default value should be used by all
+ external code.
+
+ @rtype: No-argument callable
+ @return: A C{Deferred} which becomes, or a callable which
+ takes no arguments and returns an object implementing C{IMessage}.
+ This will be called and the returned object used to deliver the
+ message when it arrives.
+
+ @raise twisted.protocols.smtp.SMTPBadRcpt: Raised if the given
+ user does not exist in this domain.
+ """
+
+class BounceDomain:
+ """A domain in which no user exists.
+
+ This can be used to block off certain domains.
+ """
+
+ implements(IDomain)
+
+ def exists(self, user):
+ raise smtp.SMTPBadRcpt(user)
+
+ def willRelay(self, user, protocol):
+ return False
+
+ def addUser(self, user, password):
+ pass
+
+ def startMessage(self, user):
+ """
+ No code should ever call this function.
+ """
+ raise NotImplementedError(
+ "No code should ever call this method for any reason")
+
+ def getCredentialsCheckers(self):
+ return []
+
+
+class FileMessage:
+ """A file we can write an email too."""
+
+ implements(smtp.IMessage)
+
+ def __init__(self, fp, name, finalName):
+ self.fp = fp
+ self.name = name
+ self.finalName = finalName
+
+ def lineReceived(self, line):
+ self.fp.write(line+'\n')
+
+ def eomReceived(self):
+ self.fp.close()
+ os.rename(self.name, self.finalName)
+ return defer.succeed(self.finalName)
+
+ def connectionLost(self):
+ self.fp.close()
+ os.remove(self.name)
+
+
+class MailService(service.MultiService):
+ """An email service."""
+
+ queue = None
+ domains = None
+ portals = None
+ aliases = None
+ smtpPortal = None
+
+ def __init__(self):
+ service.MultiService.__init__(self)
+ # Domains and portals for "client" protocols - POP3, IMAP4, etc
+ self.domains = DomainWithDefaultDict({}, BounceDomain())
+ self.portals = {}
+
+ self.monitor = FileMonitoringService()
+ self.monitor.setServiceParent(self)
+ self.smtpPortal = cred.portal.Portal(self)
+
+ def getPOP3Factory(self):
+ return protocols.POP3Factory(self)
+
+ def getSMTPFactory(self):
+ return protocols.SMTPFactory(self, self.smtpPortal)
+
+ def getESMTPFactory(self):
+ return protocols.ESMTPFactory(self, self.smtpPortal)
+
+ def addDomain(self, name, domain):
+ portal = cred.portal.Portal(domain)
+ map(portal.registerChecker, domain.getCredentialsCheckers())
+ self.domains[name] = domain
+ self.portals[name] = portal
+ if self.aliases and IAliasableDomain.providedBy(domain):
+ domain.setAliasGroup(self.aliases)
+
+ def setQueue(self, queue):
+ """Set the queue for outgoing emails."""
+ self.queue = queue
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if smtp.IMessageDelivery in interfaces:
+ a = protocols.ESMTPDomainDelivery(self, avatarId)
+ return smtp.IMessageDelivery, a, lambda: None
+ raise NotImplementedError()
+
+ def lookupPortal(self, name):
+ return self.portals[name]
+
+ def defaultPortal(self):
+ return self.portals['']
+
+
+class FileMonitoringService(internet.TimerService):
+
+ def __init__(self):
+ self.files = []
+ self.intervals = iter(util.IntervalDifferential([], 60))
+
+ def startService(self):
+ service.Service.startService(self)
+ self._setupMonitor()
+
+ def _setupMonitor(self):
+ from twisted.internet import reactor
+ t, self.index = self.intervals.next()
+ self._call = reactor.callLater(t, self._monitor)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ if self._call:
+ self._call.cancel()
+ self._call = None
+
+ def monitorFile(self, name, callback, interval=10):
+ try:
+ mtime = os.path.getmtime(name)
+ except:
+ mtime = 0
+ self.files.append([interval, name, callback, mtime])
+ self.intervals.addInterval(interval)
+
+ def unmonitorFile(self, name):
+ for i in range(len(self.files)):
+ if name == self.files[i][1]:
+ self.intervals.removeInterval(self.files[i][0])
+ del self.files[i]
+ break
+
+ def _monitor(self):
+ self._call = None
+ if self.index is not None:
+ name, callback, mtime = self.files[self.index][1:]
+ try:
+ now = os.path.getmtime(name)
+ except:
+ now = 0
+ if now > mtime:
+ log.msg("%s changed, notifying listener" % (name,))
+ self.files[self.index][3] = now
+ callback(name)
+ self._setupMonitor()
diff --git a/vendor/Twisted-10.0.0/twisted/mail/maildir.py b/vendor/Twisted-10.0.0/twisted/mail/maildir.py
new file mode 100644
index 0000000000..61c8fcf1d5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/maildir.py
@@ -0,0 +1,517 @@
+# -*- test-case-name: twisted.mail.test.test_mail -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Maildir-style mailbox support
+"""
+
+import os
+import stat
+import socket
+import time
+
+from zope.interface import implements
+
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+from twisted.python.compat import set
+from twisted.mail import pop3
+from twisted.mail import smtp
+from twisted.protocols import basic
+from twisted.persisted import dirdbm
+from twisted.python import log, failure
+from twisted.python.hashlib import md5
+from twisted.mail import mail
+from twisted.internet import interfaces, defer, reactor
+
+from twisted import cred
+import twisted.cred.portal
+import twisted.cred.credentials
+import twisted.cred.checkers
+import twisted.cred.error
+
+INTERNAL_ERROR = '''\
+From: Twisted.mail Internals
+Subject: An Error Occurred
+
+ An internal server error has occurred. Please contact the
+ server administrator.
+'''
+
+class _MaildirNameGenerator:
+ """
+ Utility class to generate a unique maildir name
+
+ @ivar _clock: An L{IReactorTime} provider which will be used to learn
+ the current time to include in names returned by L{generate} so that
+ they sort properly.
+ """
+ n = 0
+ p = os.getpid()
+ s = socket.gethostname().replace('/', r'\057').replace(':', r'\072')
+
+ def __init__(self, clock):
+ self._clock = clock
+
+ def generate(self):
+ """
+ Return a string which is intended to unique across all calls to this
+ function (across all processes, reboots, etc).
+
+ Strings returned by earlier calls to this method will compare less
+ than strings returned by later calls as long as the clock provided
+ doesn't go backwards.
+ """
+ self.n = self.n + 1
+ t = self._clock.seconds()
+ seconds = str(int(t))
+ microseconds = '%07d' % (int((t - int(t)) * 10e6),)
+ return '%s.M%sP%sQ%s.%s' % (seconds, microseconds,
+ self.p, self.n, self.s)
+
+_generateMaildirName = _MaildirNameGenerator(reactor).generate
+
+def initializeMaildir(dir):
+ if not os.path.isdir(dir):
+ os.mkdir(dir, 0700)
+ for subdir in ['new', 'cur', 'tmp', '.Trash']:
+ os.mkdir(os.path.join(dir, subdir), 0700)
+ for subdir in ['new', 'cur', 'tmp']:
+ os.mkdir(os.path.join(dir, '.Trash', subdir), 0700)
+ # touch
+ open(os.path.join(dir, '.Trash', 'maildirfolder'), 'w').close()
+
+
+class MaildirMessage(mail.FileMessage):
+ size = None
+
+ def __init__(self, address, fp, *a, **kw):
+ header = "Delivered-To: %s\n" % address
+ fp.write(header)
+ self.size = len(header)
+ mail.FileMessage.__init__(self, fp, *a, **kw)
+
+ def lineReceived(self, line):
+ mail.FileMessage.lineReceived(self, line)
+ self.size += len(line)+1
+
+ def eomReceived(self):
+ self.finalName = self.finalName+',S=%d' % self.size
+ return mail.FileMessage.eomReceived(self)
+
+class AbstractMaildirDomain:
+ """Abstract maildir-backed domain.
+ """
+ alias = None
+ root = None
+
+ def __init__(self, service, root):
+ """Initialize.
+ """
+ self.root = root
+
+ def userDirectory(self, user):
+ """Get the maildir directory for a given user
+
+ Override to specify where to save mails for users.
+ Return None for non-existing users.
+ """
+ return None
+
+ ##
+ ## IAliasableDomain
+ ##
+
+ def setAliasGroup(self, alias):
+ self.alias = alias
+
+ ##
+ ## IDomain
+ ##
+ def exists(self, user, memo=None):
+ """Check for existence of user in the domain
+ """
+ if self.userDirectory(user.dest.local) is not None:
+ return lambda: self.startMessage(user)
+ try:
+ a = self.alias[user.dest.local]
+ except:
+ raise smtp.SMTPBadRcpt(user)
+ else:
+ aliases = a.resolve(self.alias, memo)
+ if aliases:
+ return lambda: aliases
+ log.err("Bad alias configuration: " + str(user))
+ raise smtp.SMTPBadRcpt(user)
+
+ def startMessage(self, user):
+ """Save a message for a given user
+ """
+ if isinstance(user, str):
+ name, domain = user.split('@', 1)
+ else:
+ name, domain = user.dest.local, user.dest.domain
+ dir = self.userDirectory(name)
+ fname = _generateMaildirName()
+ filename = os.path.join(dir, 'tmp', fname)
+ fp = open(filename, 'w')
+ return MaildirMessage('%s@%s' % (name, domain), fp, filename,
+ os.path.join(dir, 'new', fname))
+
+ def willRelay(self, user, protocol):
+ return False
+
+ def addUser(self, user, password):
+ raise NotImplementedError
+
+ def getCredentialsCheckers(self):
+ raise NotImplementedError
+ ##
+ ## end of IDomain
+ ##
+
+class _MaildirMailboxAppendMessageTask:
+ implements(interfaces.IConsumer)
+
+ osopen = staticmethod(os.open)
+ oswrite = staticmethod(os.write)
+ osclose = staticmethod(os.close)
+ osrename = staticmethod(os.rename)
+
+ def __init__(self, mbox, msg):
+ self.mbox = mbox
+ self.defer = defer.Deferred()
+ self.openCall = None
+ if not hasattr(msg, "read"):
+ msg = StringIO.StringIO(msg)
+ self.msg = msg
+ # This is needed, as this startup phase might call defer.errback and zero out self.defer
+ # By doing it on the reactor iteration appendMessage is able to use .defer without problems.
+ reactor.callLater(0, self.startUp)
+
+ def startUp(self):
+ self.createTempFile()
+ if self.fh != -1:
+ self.filesender = basic.FileSender()
+ self.filesender.beginFileTransfer(self.msg, self)
+
+ def registerProducer(self, producer, streaming):
+ self.myproducer = producer
+ self.streaming = streaming
+ if not streaming:
+ self.prodProducer()
+
+ def prodProducer(self):
+ self.openCall = None
+ if self.myproducer is not None:
+ self.openCall = reactor.callLater(0, self.prodProducer)
+ self.myproducer.resumeProducing()
+
+ def unregisterProducer(self):
+ self.myproducer = None
+ self.streaming = None
+ self.osclose(self.fh)
+ self.moveFileToNew()
+
+ def write(self, data):
+ try:
+ self.oswrite(self.fh, data)
+ except:
+ self.fail()
+
+ def fail(self, err=None):
+ if err is None:
+ err = failure.Failure()
+ if self.openCall is not None:
+ self.openCall.cancel()
+ self.defer.errback(err)
+ self.defer = None
+
+ def moveFileToNew(self):
+ while True:
+ newname = os.path.join(self.mbox.path, "new", _generateMaildirName())
+ try:
+ self.osrename(self.tmpname, newname)
+ break
+ except OSError, (err, estr):
+ import errno
+ # if the newname exists, retry with a new newname.
+ if err != errno.EEXIST:
+ self.fail()
+ newname = None
+ break
+ if newname is not None:
+ self.mbox.list.append(newname)
+ self.defer.callback(None)
+ self.defer = None
+
+ def createTempFile(self):
+ attr = (os.O_RDWR | os.O_CREAT | os.O_EXCL
+ | getattr(os, "O_NOINHERIT", 0)
+ | getattr(os, "O_NOFOLLOW", 0))
+ tries = 0
+ self.fh = -1
+ while True:
+ self.tmpname = os.path.join(self.mbox.path, "tmp", _generateMaildirName())
+ try:
+ self.fh = self.osopen(self.tmpname, attr, 0600)
+ return None
+ except OSError:
+ tries += 1
+ if tries > 500:
+ self.defer.errback(RuntimeError("Could not create tmp file for %s" % self.mbox.path))
+ self.defer = None
+ return None
+
+class MaildirMailbox(pop3.Mailbox):
+ """Implement the POP3 mailbox semantics for a Maildir mailbox
+ """
+ AppendFactory = _MaildirMailboxAppendMessageTask
+
+ def __init__(self, path):
+ """Initialize with name of the Maildir mailbox
+ """
+ self.path = path
+ self.list = []
+ self.deleted = {}
+ initializeMaildir(path)
+ for name in ('cur', 'new'):
+ for file in os.listdir(os.path.join(path, name)):
+ self.list.append((file, os.path.join(path, name, file)))
+ self.list.sort()
+ self.list = [e[1] for e in self.list]
+
+ def listMessages(self, i=None):
+ """Return a list of lengths of all files in new/ and cur/
+ """
+ if i is None:
+ ret = []
+ for mess in self.list:
+ if mess:
+ ret.append(os.stat(mess)[stat.ST_SIZE])
+ else:
+ ret.append(0)
+ return ret
+ return self.list[i] and os.stat(self.list[i])[stat.ST_SIZE] or 0
+
+ def getMessage(self, i):
+ """Return an open file-pointer to a message
+ """
+ return open(self.list[i])
+
+ def getUidl(self, i):
+ """Return a unique identifier for a message
+
+ This is done using the basename of the filename.
+ It is globally unique because this is how Maildirs are designed.
+ """
+ # Returning the actual filename is a mistake. Hash it.
+ base = os.path.basename(self.list[i])
+ return md5(base).hexdigest()
+
+ def deleteMessage(self, i):
+ """Delete a message
+
+ This only moves a message to the .Trash/ subfolder,
+ so it can be undeleted by an administrator.
+ """
+ trashFile = os.path.join(
+ self.path, '.Trash', 'cur', os.path.basename(self.list[i])
+ )
+ os.rename(self.list[i], trashFile)
+ self.deleted[self.list[i]] = trashFile
+ self.list[i] = 0
+
+ def undeleteMessages(self):
+ """Undelete any deleted messages it is possible to undelete
+
+ This moves any messages from .Trash/ subfolder back to their
+ original position, and empties out the deleted dictionary.
+ """
+ for (real, trash) in self.deleted.items():
+ try:
+ os.rename(trash, real)
+ except OSError, (err, estr):
+ import errno
+ # If the file has been deleted from disk, oh well!
+ if err != errno.ENOENT:
+ raise
+ # This is a pass
+ else:
+ try:
+ self.list[self.list.index(0)] = real
+ except ValueError:
+ self.list.append(real)
+ self.deleted.clear()
+
+ def appendMessage(self, txt):
+ """Appends a message into the mailbox."""
+ task = self.AppendFactory(self, txt)
+ return task.defer
+
+class StringListMailbox:
+ """
+ L{StringListMailbox} is an in-memory mailbox.
+
+ @ivar msgs: A C{list} of C{str} giving the contents of each message in the
+ mailbox.
+
+ @ivar _delete: A C{set} of the indexes of messages which have been deleted
+ since the last C{sync} call.
+ """
+ implements(pop3.IMailbox)
+
+ def __init__(self, msgs):
+ self.msgs = msgs
+ self._delete = set()
+
+
+ def listMessages(self, i=None):
+ """
+ Return the length of the message at the given offset, or a list of all
+ message lengths.
+ """
+ if i is None:
+ return [self.listMessages(i) for i in range(len(self.msgs))]
+ if i in self._delete:
+ return 0
+ return len(self.msgs[i])
+
+
+ def getMessage(self, i):
+ """
+ Return an in-memory file-like object for the message content at the
+ given offset.
+ """
+ return StringIO.StringIO(self.msgs[i])
+
+
+ def getUidl(self, i):
+ """
+ Return a hash of the contents of the message at the given offset.
+ """
+ return md5(self.msgs[i]).hexdigest()
+
+
+ def deleteMessage(self, i):
+ """
+ Mark the given message for deletion.
+ """
+ self._delete.add(i)
+
+
+ def undeleteMessages(self):
+ """
+ Reset deletion tracking, undeleting any messages which have been
+ deleted since the last call to C{sync}.
+ """
+ self._delete = set()
+
+
+ def sync(self):
+ """
+ Discard the contents of any message marked for deletion and reset
+ deletion tracking.
+ """
+ for index in self._delete:
+ self.msgs[index] = ""
+ self._delete = set()
+
+
+
+class MaildirDirdbmDomain(AbstractMaildirDomain):
+ """A Maildir Domain where membership is checked by a dirdbm file
+ """
+
+ implements(cred.portal.IRealm, mail.IAliasableDomain)
+
+ portal = None
+ _credcheckers = None
+
+ def __init__(self, service, root, postmaster=0):
+ """Initialize
+
+ The first argument is where the Domain directory is rooted.
+ The second is whether non-existing addresses are simply
+ forwarded to postmaster instead of outright bounce
+
+ The directory structure of a MailddirDirdbmDomain is:
+
+ /passwd <-- a dirdbm file
+ /USER/{cur,new,del} <-- each user has these three directories
+ """
+ AbstractMaildirDomain.__init__(self, service, root)
+ dbm = os.path.join(root, 'passwd')
+ if not os.path.exists(dbm):
+ os.makedirs(dbm)
+ self.dbm = dirdbm.open(dbm)
+ self.postmaster = postmaster
+
+ def userDirectory(self, name):
+ """Get the directory for a user
+
+ If the user exists in the dirdbm file, return the directory
+ os.path.join(root, name), creating it if necessary.
+ Otherwise, returns postmaster's mailbox instead if bounces
+ go to postmaster, otherwise return None
+ """
+ if not self.dbm.has_key(name):
+ if not self.postmaster:
+ return None
+ name = 'postmaster'
+ dir = os.path.join(self.root, name)
+ if not os.path.exists(dir):
+ initializeMaildir(dir)
+ return dir
+
+ ##
+ ## IDomain
+ ##
+ def addUser(self, user, password):
+ self.dbm[user] = password
+ # Ensure it is initialized
+ self.userDirectory(user)
+
+ def getCredentialsCheckers(self):
+ if self._credcheckers is None:
+ self._credcheckers = [DirdbmDatabase(self.dbm)]
+ return self._credcheckers
+
+ ##
+ ## IRealm
+ ##
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pop3.IMailbox not in interfaces:
+ raise NotImplementedError("No interface")
+ if avatarId == cred.checkers.ANONYMOUS:
+ mbox = StringListMailbox([INTERNAL_ERROR])
+ else:
+ mbox = MaildirMailbox(os.path.join(self.root, avatarId))
+
+ return (
+ pop3.IMailbox,
+ mbox,
+ lambda: None
+ )
+
+class DirdbmDatabase:
+ implements(cred.checkers.ICredentialsChecker)
+
+ credentialInterfaces = (
+ cred.credentials.IUsernamePassword,
+ cred.credentials.IUsernameHashedPassword
+ )
+
+ def __init__(self, dbm):
+ self.dirdbm = dbm
+
+ def requestAvatarId(self, c):
+ if c.username in self.dirdbm:
+ if c.checkPassword(self.dirdbm[c.username]):
+ return c.username
+ raise cred.error.UnauthorizedLogin()
diff --git a/vendor/Twisted-10.0.0/twisted/mail/pb.py b/vendor/Twisted-10.0.0/twisted/mail/pb.py
new file mode 100644
index 0000000000..dfe72d3707
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/pb.py
@@ -0,0 +1,115 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.spread import pb
+from twisted.spread import banana
+
+import os
+import types
+
+class Maildir(pb.Referenceable):
+
+ def __init__(self, directory, rootDirectory):
+ self.virtualDirectory = directory
+ self.rootDirectory = rootDirectory
+ self.directory = os.path.join(rootDirectory, directory)
+
+ def getFolderMessage(self, folder, name):
+ if '/' in name:
+ raise IOError("can only open files in '%s' directory'" % folder)
+ fp = open(os.path.join(self.directory, 'new', name))
+ try:
+ return fp.read()
+ finally:
+ fp.close()
+
+ def deleteFolderMessage(self, folder, name):
+ if '/' in name:
+ raise IOError("can only delete files in '%s' directory'" % folder)
+ os.rename(os.path.join(self.directory, folder, name),
+ os.path.join(self.rootDirectory, '.Trash', folder, name))
+
+ def deleteNewMessage(self, name):
+ return self.deleteFolderMessage('new', name)
+ remote_deleteNewMessage = deleteNewMessage
+
+ def deleteCurMessage(self, name):
+ return self.deleteFolderMessage('cur', name)
+ remote_deleteCurMessage = deleteCurMessage
+
+ def getNewMessages(self):
+ return os.listdir(os.path.join(self.directory, 'new'))
+ remote_getNewMessages = getNewMessages
+
+ def getCurMessages(self):
+ return os.listdir(os.path.join(self.directory, 'cur'))
+ remote_getCurMessages = getCurMessages
+
+ def getNewMessage(self, name):
+ return self.getFolderMessage('new', name)
+ remote_getNewMessage = getNewMessage
+
+ def getCurMessage(self, name):
+ return self.getFolderMessage('cur', name)
+ remote_getCurMessage = getCurMessage
+
+ def getSubFolder(self, name):
+ if name[0] == '.':
+ raise IOError("subfolder name cannot begin with a '.'")
+ name = name.replace('/', ':')
+ if self.virtualDirectoy == '.':
+ name = '.'+name
+ else:
+ name = self.virtualDirectory+':'+name
+ if not self._isSubFolder(name):
+ raise IOError("not a subfolder")
+ return Maildir(name, self.rootDirectory)
+ remote_getSubFolder = getSubFolder
+
+ def _isSubFolder(self, name):
+ return (not os.path.isdir(os.path.join(self.rootDirectory, name)) or
+ not os.path.isfile(os.path.join(self.rootDirectory, name,
+ 'maildirfolder')))
+
+
+class MaildirCollection(pb.Referenceable):
+
+ def __init__(self, root):
+ self.root = root
+
+ def getSubFolders(self):
+ return os.listdir(self.getRoot())
+ remote_getSubFolders = getSubFolders
+
+ def getSubFolder(self, name):
+ if '/' in name or name[0] == '.':
+ raise IOError("invalid name")
+ return Maildir('.', os.path.join(self.getRoot(), name))
+ remote_getSubFolder = getSubFolder
+
+
+class MaildirBroker(pb.Broker):
+
+ def proto_getCollection(self, requestID, name, domain, password):
+ collection = self._getCollection()
+ if collection is None:
+ self.sendError(requestID, "permission denied")
+ else:
+ self.sendAnswer(requestID, collection)
+
+ def getCollection(self, name, domain, password):
+ if not self.domains.has_key(domain):
+ return
+ domain = self.domains[domain]
+ if (domain.dbm.has_key(name) and
+ domain.dbm[name] == password):
+ return MaildirCollection(domain.userDirectory(name))
+
+
+class MaildirClient(pb.Broker):
+
+ def getCollection(self, name, domain, password, callback, errback):
+ requestID = self.newRequestID()
+ self.waitingForAnswers[requestID] = callback, errback
+ self.sendCall("getCollection", requestID, name, domain, password)
diff --git a/vendor/Twisted-10.0.0/twisted/mail/pop3.py b/vendor/Twisted-10.0.0/twisted/mail/pop3.py
new file mode 100644
index 0000000000..39a263d392
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/pop3.py
@@ -0,0 +1,1072 @@
+# -*- test-case-name: twisted.mail.test.test_pop3 -*-
+#
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Post-office Protocol version 3
+
+@author: Glyph Lefkowitz
+@author: Jp Calderone
+"""
+
+import string
+import base64
+import binascii
+import warnings
+
+from zope.interface import implements, Interface
+
+from twisted.mail import smtp
+from twisted.protocols import basic
+from twisted.protocols import policies
+from twisted.internet import task
+from twisted.internet import defer
+from twisted.internet import interfaces
+from twisted.python import log
+from twisted.python.hashlib import md5
+
+from twisted import cred
+import twisted.cred.error
+import twisted.cred.credentials
+
+##
+## Authentication
+##
+class APOPCredentials:
+ implements(cred.credentials.IUsernamePassword)
+
+ def __init__(self, magic, username, digest):
+ self.magic = magic
+ self.username = username
+ self.digest = digest
+
+ def checkPassword(self, password):
+ seed = self.magic + password
+ myDigest = md5(seed).hexdigest()
+ return myDigest == self.digest
+
+
+class _HeadersPlusNLines:
+ def __init__(self, f, n):
+ self.f = f
+ self.n = n
+ self.linecount = 0
+ self.headers = 1
+ self.done = 0
+ self.buf = ''
+
+ def read(self, bytes):
+ if self.done:
+ return ''
+ data = self.f.read(bytes)
+ if not data:
+ return data
+ if self.headers:
+ df, sz = data.find('\r\n\r\n'), 4
+ if df == -1:
+ df, sz = data.find('\n\n'), 2
+ if df != -1:
+ df += sz
+ val = data[:df]
+ data = data[df:]
+ self.linecount = 1
+ self.headers = 0
+ else:
+ val = ''
+ if self.linecount > 0:
+ dsplit = (self.buf+data).split('\n')
+ self.buf = dsplit[-1]
+ for ln in dsplit[:-1]:
+ if self.linecount > self.n:
+ self.done = 1
+ return val
+ val += (ln + '\n')
+ self.linecount += 1
+ return val
+ else:
+ return data
+
+
+
+class _POP3MessageDeleted(Exception):
+ """
+ Internal control-flow exception. Indicates the file of a deleted message
+ was requested.
+ """
+
+
+class POP3Error(Exception):
+ pass
+
+
+
+class _IteratorBuffer(object):
+ bufSize = 0
+
+ def __init__(self, write, iterable, memoryBufferSize=None):
+ """
+ Create a _IteratorBuffer.
+
+ @param write: A one-argument callable which will be invoked with a list
+ of strings which have been buffered.
+
+ @param iterable: The source of input strings as any iterable.
+
+ @param memoryBufferSize: The upper limit on buffered string length,
+ beyond which the buffer will be flushed to the writer.
+ """
+ self.lines = []
+ self.write = write
+ self.iterator = iter(iterable)
+ if memoryBufferSize is None:
+ memoryBufferSize = 2 ** 16
+ self.memoryBufferSize = memoryBufferSize
+
+
+ def __iter__(self):
+ return self
+
+
+ def next(self):
+ try:
+ v = self.iterator.next()
+ except StopIteration:
+ if self.lines:
+ self.write(self.lines)
+ # Drop some references, in case they're edges in a cycle.
+ del self.iterator, self.lines, self.write
+ raise
+ else:
+ if v is not None:
+ self.lines.append(v)
+ self.bufSize += len(v)
+ if self.bufSize > self.memoryBufferSize:
+ self.write(self.lines)
+ self.lines = []
+ self.bufSize = 0
+
+
+
+def iterateLineGenerator(proto, gen):
+ """
+ Hook the given protocol instance up to the given iterator with an
+ _IteratorBuffer and schedule the result to be exhausted via the protocol.
+
+ @type proto: L{POP3}
+ @type gen: iterator
+ @rtype: L{twisted.internet.defer.Deferred}
+ """
+ coll = _IteratorBuffer(proto.transport.writeSequence, gen)
+ return proto.schedule(coll)
+
+
+
+def successResponse(response):
+ """
+ Format the given object as a positive response.
+ """
+ response = str(response)
+ return '+OK %s\r\n' % (response,)
+
+
+
+def formatStatResponse(msgs):
+ """
+ Format the list of message sizes appropriately for a STAT response.
+
+ Yields None until it finishes computing a result, then yields a str
+ instance that is suitable for use as a response to the STAT command.
+ Intended to be used with a L{twisted.internet.task.Cooperator}.
+ """
+ i = 0
+ bytes = 0
+ for size in msgs:
+ i += 1
+ bytes += size
+ yield None
+ yield successResponse('%d %d' % (i, bytes))
+
+
+
+def formatListLines(msgs):
+ """
+ Format a list of message sizes appropriately for the lines of a LIST
+ response.
+
+ Yields str instances formatted appropriately for use as lines in the
+ response to the LIST command. Does not include the trailing '.'.
+ """
+ i = 0
+ for size in msgs:
+ i += 1
+ yield '%d %d\r\n' % (i, size)
+
+
+
+def formatListResponse(msgs):
+ """
+ Format a list of message sizes appropriately for a complete LIST response.
+
+ Yields str instances formatted appropriately for use as a LIST command
+ response.
+ """
+ yield successResponse(len(msgs))
+ for ele in formatListLines(msgs):
+ yield ele
+ yield '.\r\n'
+
+
+
+def formatUIDListLines(msgs, getUidl):
+ """
+ Format the list of message sizes appropriately for the lines of a UIDL
+ response.
+
+ Yields str instances formatted appropriately for use as lines in the
+ response to the UIDL command. Does not include the trailing '.'.
+ """
+ for i, m in enumerate(msgs):
+ if m is not None:
+ uid = getUidl(i)
+ yield '%d %s\r\n' % (i + 1, uid)
+
+
+
+def formatUIDListResponse(msgs, getUidl):
+ """
+ Format a list of message sizes appropriately for a complete UIDL response.
+
+ Yields str instances formatted appropriately for use as a UIDL command
+ response.
+ """
+ yield successResponse('')
+ for ele in formatUIDListLines(msgs, getUidl):
+ yield ele
+ yield '.\r\n'
+
+
+
+class POP3(basic.LineOnlyReceiver, policies.TimeoutMixin):
+ """
+ POP3 server protocol implementation.
+
+ @ivar portal: A reference to the L{twisted.cred.portal.Portal} instance we
+ will authenticate through.
+
+ @ivar factory: A L{twisted.mail.pop3.IServerFactory} which will be used to
+ determine some extended behavior of the server.
+
+ @ivar timeOut: An integer which defines the minimum amount of time which
+ may elapse without receiving any traffic after which the client will be
+ disconnected.
+
+ @ivar schedule: A one-argument callable which should behave like
+ L{twisted.internet.task.coiterate}.
+ """
+ implements(interfaces.IProducer)
+
+ magic = None
+ _userIs = None
+ _onLogout = None
+
+ AUTH_CMDS = ['CAPA', 'USER', 'PASS', 'APOP', 'AUTH', 'RPOP', 'QUIT']
+
+ portal = None
+ factory = None
+
+ # The mailbox we're serving
+ mbox = None
+
+ # Set this pretty low -- POP3 clients are expected to log in, download
+ # everything, and log out.
+ timeOut = 300
+
+ # Current protocol state
+ state = "COMMAND"
+
+ # PIPELINE
+ blocked = None
+
+ # Cooperate and suchlike.
+ schedule = staticmethod(task.coiterate)
+
+ # Message index of the highest retrieved message.
+ _highest = 0
+
+ def connectionMade(self):
+ if self.magic is None:
+ self.magic = self.generateMagic()
+ self.successResponse(self.magic)
+ self.setTimeout(self.timeOut)
+ if getattr(self.factory, 'noisy', True):
+ log.msg("New connection from " + str(self.transport.getPeer()))
+
+
+ def connectionLost(self, reason):
+ if self._onLogout is not None:
+ self._onLogout()
+ self._onLogout = None
+ self.setTimeout(None)
+
+
+ def generateMagic(self):
+ return smtp.messageid()
+
+
+ def successResponse(self, message=''):
+ self.transport.write(successResponse(message))
+
+ def failResponse(self, message=''):
+ self.sendLine('-ERR ' + str(message))
+
+# def sendLine(self, line):
+# print 'S:', repr(line)
+# basic.LineOnlyReceiver.sendLine(self, line)
+
+ def lineReceived(self, line):
+# print 'C:', repr(line)
+ self.resetTimeout()
+ getattr(self, 'state_' + self.state)(line)
+
+ def _unblock(self, _):
+ commands = self.blocked
+ self.blocked = None
+ while commands and self.blocked is None:
+ cmd, args = commands.pop(0)
+ self.processCommand(cmd, *args)
+ if self.blocked is not None:
+ self.blocked.extend(commands)
+
+ def state_COMMAND(self, line):
+ try:
+ return self.processCommand(*line.split(' '))
+ except (ValueError, AttributeError, POP3Error, TypeError), e:
+ log.err()
+ self.failResponse('bad protocol or server: %s: %s' % (e.__class__.__name__, e))
+
+ def processCommand(self, command, *args):
+ if self.blocked is not None:
+ self.blocked.append((command, args))
+ return
+
+ command = string.upper(command)
+ authCmd = command in self.AUTH_CMDS
+ if not self.mbox and not authCmd:
+ raise POP3Error("not authenticated yet: cannot do " + command)
+ f = getattr(self, 'do_' + command, None)
+ if f:
+ return f(*args)
+ raise POP3Error("Unknown protocol command: " + command)
+
+
+ def listCapabilities(self):
+ baseCaps = [
+ "TOP",
+ "USER",
+ "UIDL",
+ "PIPELINE",
+ "CELERITY",
+ "AUSPEX",
+ "POTENCE",
+ ]
+
+ if IServerFactory.providedBy(self.factory):
+ # Oh my god. We can't just loop over a list of these because
+ # each has spectacularly different return value semantics!
+ try:
+ v = self.factory.cap_IMPLEMENTATION()
+ except NotImplementedError:
+ pass
+ except:
+ log.err()
+ else:
+ baseCaps.append("IMPLEMENTATION " + str(v))
+
+ try:
+ v = self.factory.cap_EXPIRE()
+ except NotImplementedError:
+ pass
+ except:
+ log.err()
+ else:
+ if v is None:
+ v = "NEVER"
+ if self.factory.perUserExpiration():
+ if self.mbox:
+ v = str(self.mbox.messageExpiration)
+ else:
+ v = str(v) + " USER"
+ v = str(v)
+ baseCaps.append("EXPIRE " + v)
+
+ try:
+ v = self.factory.cap_LOGIN_DELAY()
+ except NotImplementedError:
+ pass
+ except:
+ log.err()
+ else:
+ if self.factory.perUserLoginDelay():
+ if self.mbox:
+ v = str(self.mbox.loginDelay)
+ else:
+ v = str(v) + " USER"
+ v = str(v)
+ baseCaps.append("LOGIN-DELAY " + v)
+
+ try:
+ v = self.factory.challengers
+ except AttributeError:
+ pass
+ except:
+ log.err()
+ else:
+ baseCaps.append("SASL " + ' '.join(v.keys()))
+ return baseCaps
+
+ def do_CAPA(self):
+ self.successResponse("I can do the following:")
+ for cap in self.listCapabilities():
+ self.sendLine(cap)
+ self.sendLine(".")
+
+ def do_AUTH(self, args=None):
+ if not getattr(self.factory, 'challengers', None):
+ self.failResponse("AUTH extension unsupported")
+ return
+
+ if args is None:
+ self.successResponse("Supported authentication methods:")
+ for a in self.factory.challengers:
+ self.sendLine(a.upper())
+ self.sendLine(".")
+ return
+
+ auth = self.factory.challengers.get(args.strip().upper())
+ if not self.portal or not auth:
+ self.failResponse("Unsupported SASL selected")
+ return
+
+ self._auth = auth()
+ chal = self._auth.getChallenge()
+
+ self.sendLine('+ ' + base64.encodestring(chal).rstrip('\n'))
+ self.state = 'AUTH'
+
+ def state_AUTH(self, line):
+ self.state = "COMMAND"
+ try:
+ parts = base64.decodestring(line).split(None, 1)
+ except binascii.Error:
+ self.failResponse("Invalid BASE64 encoding")
+ else:
+ if len(parts) != 2:
+ self.failResponse("Invalid AUTH response")
+ return
+ self._auth.username = parts[0]
+ self._auth.response = parts[1]
+ d = self.portal.login(self._auth, None, IMailbox)
+ d.addCallback(self._cbMailbox, parts[0])
+ d.addErrback(self._ebMailbox)
+ d.addErrback(self._ebUnexpected)
+
+ def do_APOP(self, user, digest):
+ d = defer.maybeDeferred(self.authenticateUserAPOP, user, digest)
+ d.addCallbacks(self._cbMailbox, self._ebMailbox, callbackArgs=(user,)
+ ).addErrback(self._ebUnexpected)
+
+ def _cbMailbox(self, (interface, avatar, logout), user):
+ if interface is not IMailbox:
+ self.failResponse('Authentication failed')
+ log.err("_cbMailbox() called with an interface other than IMailbox")
+ return
+
+ self.mbox = avatar
+ self._onLogout = logout
+ self.successResponse('Authentication succeeded')
+ if getattr(self.factory, 'noisy', True):
+ log.msg("Authenticated login for " + user)
+
+ def _ebMailbox(self, failure):
+ failure = failure.trap(cred.error.LoginDenied, cred.error.LoginFailed)
+ if issubclass(failure, cred.error.LoginDenied):
+ self.failResponse("Access denied: " + str(failure))
+ elif issubclass(failure, cred.error.LoginFailed):
+ self.failResponse('Authentication failed')
+ if getattr(self.factory, 'noisy', True):
+ log.msg("Denied login attempt from " + str(self.transport.getPeer()))
+
+ def _ebUnexpected(self, failure):
+ self.failResponse('Server error: ' + failure.getErrorMessage())
+ log.err(failure)
+
+ def do_USER(self, user):
+ self._userIs = user
+ self.successResponse('USER accepted, send PASS')
+
+ def do_PASS(self, password):
+ if self._userIs is None:
+ self.failResponse("USER required before PASS")
+ return
+ user = self._userIs
+ self._userIs = None
+ d = defer.maybeDeferred(self.authenticateUserPASS, user, password)
+ d.addCallbacks(self._cbMailbox, self._ebMailbox, callbackArgs=(user,)
+ ).addErrback(self._ebUnexpected)
+
+
+ def _longOperation(self, d):
+ # Turn off timeouts and block further processing until the Deferred
+ # fires, then reverse those changes.
+ timeOut = self.timeOut
+ self.setTimeout(None)
+ self.blocked = []
+ d.addCallback(self._unblock)
+ d.addCallback(lambda ign: self.setTimeout(timeOut))
+ return d
+
+
+ def _coiterate(self, gen):
+ return self.schedule(_IteratorBuffer(self.transport.writeSequence, gen))
+
+
+ def do_STAT(self):
+ d = defer.maybeDeferred(self.mbox.listMessages)
+ def cbMessages(msgs):
+ return self._coiterate(formatStatResponse(msgs))
+ def ebMessages(err):
+ self.failResponse(err.getErrorMessage())
+ log.msg("Unexpected do_STAT failure:")
+ log.err(err)
+ return self._longOperation(d.addCallbacks(cbMessages, ebMessages))
+
+
+ def do_LIST(self, i=None):
+ if i is None:
+ d = defer.maybeDeferred(self.mbox.listMessages)
+ def cbMessages(msgs):
+ return self._coiterate(formatListResponse(msgs))
+ def ebMessages(err):
+ self.failResponse(err.getErrorMessage())
+ log.msg("Unexpected do_LIST failure:")
+ log.err(err)
+ return self._longOperation(d.addCallbacks(cbMessages, ebMessages))
+ else:
+ try:
+ i = int(i)
+ if i < 1:
+ raise ValueError()
+ except ValueError:
+ self.failResponse("Invalid message-number: %r" % (i,))
+ else:
+ d = defer.maybeDeferred(self.mbox.listMessages, i - 1)
+ def cbMessage(msg):
+ self.successResponse('%d %d' % (i, msg))
+ def ebMessage(err):
+ errcls = err.check(ValueError, IndexError)
+ if errcls is not None:
+ if errcls is IndexError:
+ # IndexError was supported for a while, but really
+ # shouldn't be. One error condition, one exception
+ # type.
+ warnings.warn(
+ "twisted.mail.pop3.IMailbox.listMessages may not "
+ "raise IndexError for out-of-bounds message numbers: "
+ "raise ValueError instead.",
+ PendingDeprecationWarning)
+ self.failResponse("Invalid message-number: %r" % (i,))
+ else:
+ self.failResponse(err.getErrorMessage())
+ log.msg("Unexpected do_LIST failure:")
+ log.err(err)
+ return self._longOperation(d.addCallbacks(cbMessage, ebMessage))
+
+
+ def do_UIDL(self, i=None):
+ if i is None:
+ d = defer.maybeDeferred(self.mbox.listMessages)
+ def cbMessages(msgs):
+ return self._coiterate(formatUIDListResponse(msgs, self.mbox.getUidl))
+ def ebMessages(err):
+ self.failResponse(err.getErrorMessage())
+ log.msg("Unexpected do_UIDL failure:")
+ log.err(err)
+ return self._longOperation(d.addCallbacks(cbMessages, ebMessages))
+ else:
+ try:
+ i = int(i)
+ if i < 1:
+ raise ValueError()
+ except ValueError:
+ self.failResponse("Bad message number argument")
+ else:
+ try:
+ msg = self.mbox.getUidl(i - 1)
+ except IndexError:
+ # XXX TODO See above comment regarding IndexError.
+ warnings.warn(
+ "twisted.mail.pop3.IMailbox.getUidl may not "
+ "raise IndexError for out-of-bounds message numbers: "
+ "raise ValueError instead.",
+ PendingDeprecationWarning)
+ self.failResponse("Bad message number argument")
+ except ValueError:
+ self.failResponse("Bad message number argument")
+ else:
+ self.successResponse(str(msg))
+
+
+ def _getMessageFile(self, i):
+ """
+ Retrieve the size and contents of a given message, as a two-tuple.
+
+ @param i: The number of the message to operate on. This is a base-ten
+ string representation starting at 1.
+
+ @return: A Deferred which fires with a two-tuple of an integer and a
+ file-like object.
+ """
+ try:
+ msg = int(i) - 1
+ if msg < 0:
+ raise ValueError()
+ except ValueError:
+ self.failResponse("Bad message number argument")
+ return defer.succeed(None)
+
+ sizeDeferred = defer.maybeDeferred(self.mbox.listMessages, msg)
+ def cbMessageSize(size):
+ if not size:
+ return defer.fail(_POP3MessageDeleted())
+ fileDeferred = defer.maybeDeferred(self.mbox.getMessage, msg)
+ fileDeferred.addCallback(lambda fObj: (size, fObj))
+ return fileDeferred
+
+ def ebMessageSomething(err):
+ errcls = err.check(_POP3MessageDeleted, ValueError, IndexError)
+ if errcls is _POP3MessageDeleted:
+ self.failResponse("message deleted")
+ elif errcls in (ValueError, IndexError):
+ if errcls is IndexError:
+ # XXX TODO See above comment regarding IndexError.
+ warnings.warn(
+ "twisted.mail.pop3.IMailbox.listMessages may not "
+ "raise IndexError for out-of-bounds message numbers: "
+ "raise ValueError instead.",
+ PendingDeprecationWarning)
+ self.failResponse("Bad message number argument")
+ else:
+ log.msg("Unexpected _getMessageFile failure:")
+ log.err(err)
+ return None
+
+ sizeDeferred.addCallback(cbMessageSize)
+ sizeDeferred.addErrback(ebMessageSomething)
+ return sizeDeferred
+
+
+ def _sendMessageContent(self, i, fpWrapper, successResponse):
+ d = self._getMessageFile(i)
+ def cbMessageFile(info):
+ if info is None:
+ # Some error occurred - a failure response has been sent
+ # already, just give up.
+ return
+
+ self._highest = max(self._highest, int(i))
+ resp, fp = info
+ fp = fpWrapper(fp)
+ self.successResponse(successResponse(resp))
+ s = basic.FileSender()
+ d = s.beginFileTransfer(fp, self.transport, self.transformChunk)
+
+ def cbFileTransfer(lastsent):
+ if lastsent != '\n':
+ line = '\r\n.'
+ else:
+ line = '.'
+ self.sendLine(line)
+
+ def ebFileTransfer(err):
+ self.transport.loseConnection()
+ log.msg("Unexpected error in _sendMessageContent:")
+ log.err(err)
+
+ d.addCallback(cbFileTransfer)
+ d.addErrback(ebFileTransfer)
+ return d
+ return self._longOperation(d.addCallback(cbMessageFile))
+
+
+ def do_TOP(self, i, size):
+ try:
+ size = int(size)
+ if size < 0:
+ raise ValueError
+ except ValueError:
+ self.failResponse("Bad line count argument")
+ else:
+ return self._sendMessageContent(
+ i,
+ lambda fp: _HeadersPlusNLines(fp, size),
+ lambda size: "Top of message follows")
+
+
+ def do_RETR(self, i):
+ return self._sendMessageContent(
+ i,
+ lambda fp: fp,
+ lambda size: "%d" % (size,))
+
+
+ def transformChunk(self, chunk):
+ return chunk.replace('\n', '\r\n').replace('\r\n.', '\r\n..')
+
+
+ def finishedFileTransfer(self, lastsent):
+ if lastsent != '\n':
+ line = '\r\n.'
+ else:
+ line = '.'
+ self.sendLine(line)
+
+
+ def do_DELE(self, i):
+ i = int(i)-1
+ self.mbox.deleteMessage(i)
+ self.successResponse()
+
+
+ def do_NOOP(self):
+ """Perform no operation. Return a success code"""
+ self.successResponse()
+
+
+ def do_RSET(self):
+ """Unset all deleted message flags"""
+ try:
+ self.mbox.undeleteMessages()
+ except:
+ log.err()
+ self.failResponse()
+ else:
+ self._highest = 0
+ self.successResponse()
+
+
+ def do_LAST(self):
+ """
+ Return the index of the highest message yet downloaded.
+ """
+ self.successResponse(self._highest)
+
+
+ def do_RPOP(self, user):
+ self.failResponse('permission denied, sucker')
+
+
+ def do_QUIT(self):
+ if self.mbox:
+ self.mbox.sync()
+ self.successResponse()
+ self.transport.loseConnection()
+
+
+ def authenticateUserAPOP(self, user, digest):
+ """Perform authentication of an APOP login.
+
+ @type user: C{str}
+ @param user: The name of the user attempting to log in.
+
+ @type digest: C{str}
+ @param digest: The response string with which the user replied.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked if the login is
+ successful, and whose errback will be invoked otherwise. The
+ callback will be passed a 3-tuple consisting of IMailbox,
+ an object implementing IMailbox, and a zero-argument callable
+ to be invoked when this session is terminated.
+ """
+ if self.portal is not None:
+ return self.portal.login(
+ APOPCredentials(self.magic, user, digest),
+ None,
+ IMailbox
+ )
+ raise cred.error.UnauthorizedLogin()
+
+ def authenticateUserPASS(self, user, password):
+ """Perform authentication of a username/password login.
+
+ @type user: C{str}
+ @param user: The name of the user attempting to log in.
+
+ @type password: C{str}
+ @param password: The password to attempt to authenticate with.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback is invoked if the login is
+ successful, and whose errback will be invoked otherwise. The
+ callback will be passed a 3-tuple consisting of IMailbox,
+ an object implementing IMailbox, and a zero-argument callable
+ to be invoked when this session is terminated.
+ """
+ if self.portal is not None:
+ return self.portal.login(
+ cred.credentials.UsernamePassword(user, password),
+ None,
+ IMailbox
+ )
+ raise cred.error.UnauthorizedLogin()
+
+
+class IServerFactory(Interface):
+ """Interface for querying additional parameters of this POP3 server.
+
+ Any cap_* method may raise NotImplementedError if the particular
+ capability is not supported. If cap_EXPIRE() does not raise
+ NotImplementedError, perUserExpiration() must be implemented, otherwise
+ they are optional. If cap_LOGIN_DELAY() is implemented,
+ perUserLoginDelay() must be implemented, otherwise they are optional.
+
+ @ivar challengers: A dictionary mapping challenger names to classes
+ implementing C{IUsernameHashedPassword}.
+ """
+
+ def cap_IMPLEMENTATION():
+ """Return a string describing this POP3 server implementation."""
+
+ def cap_EXPIRE():
+ """Return the minimum number of days messages are retained."""
+
+ def perUserExpiration():
+ """Indicate whether message expiration is per-user.
+
+ @return: True if it is, false otherwise.
+ """
+
+ def cap_LOGIN_DELAY():
+ """Return the minimum number of seconds between client logins."""
+
+ def perUserLoginDelay():
+ """Indicate whether the login delay period is per-user.
+
+ @return: True if it is, false otherwise.
+ """
+
+class IMailbox(Interface):
+ """
+ @type loginDelay: C{int}
+ @ivar loginDelay: The number of seconds between allowed logins for the
+ user associated with this mailbox. None
+
+ @type messageExpiration: C{int}
+ @ivar messageExpiration: The number of days messages in this mailbox will
+ remain on the server before being deleted.
+ """
+
+ def listMessages(index=None):
+ """Retrieve the size of one or more messages.
+
+ @type index: C{int} or C{None}
+ @param index: The number of the message for which to retrieve the
+ size (starting at 0), or None to retrieve the size of all messages.
+
+ @rtype: C{int} or any iterable of C{int} or a L{Deferred} which fires
+ with one of these.
+
+ @return: The number of octets in the specified message, or an iterable
+ of integers representing the number of octets in all the messages. Any
+ value which would have referred to a deleted message should be set to 0.
+
+ @raise ValueError: if C{index} is greater than the index of any message
+ in the mailbox.
+ """
+
+ def getMessage(index):
+ """Retrieve a file-like object for a particular message.
+
+ @type index: C{int}
+ @param index: The number of the message to retrieve
+
+ @rtype: A file-like object
+ @return: A file containing the message data with lines delimited by
+ C{\\n}.
+ """
+
+ def getUidl(index):
+ """Get a unique identifier for a particular message.
+
+ @type index: C{int}
+ @param index: The number of the message for which to retrieve a UIDL
+
+ @rtype: C{str}
+ @return: A string of printable characters uniquely identifying for all
+ time the specified message.
+
+ @raise ValueError: if C{index} is greater than the index of any message
+ in the mailbox.
+ """
+
+ def deleteMessage(index):
+ """Delete a particular message.
+
+ This must not change the number of messages in this mailbox. Further
+ requests for the size of deleted messages should return 0. Further
+ requests for the message itself may raise an exception.
+
+ @type index: C{int}
+ @param index: The number of the message to delete.
+ """
+
+ def undeleteMessages():
+ """
+ Undelete any messages which have been marked for deletion since the
+ most recent L{sync} call.
+
+ Any message which can be undeleted should be returned to its
+ original position in the message sequence and retain its original
+ UID.
+ """
+
+ def sync():
+ """Perform checkpointing.
+
+ This method will be called to indicate the mailbox should attempt to
+ clean up any remaining deleted messages.
+ """
+
+
+
+class Mailbox:
+ implements(IMailbox)
+
+ def listMessages(self, i=None):
+ return []
+ def getMessage(self, i):
+ raise ValueError
+ def getUidl(self, i):
+ raise ValueError
+ def deleteMessage(self, i):
+ raise ValueError
+ def undeleteMessages(self):
+ pass
+ def sync(self):
+ pass
+
+
+NONE, SHORT, FIRST_LONG, LONG = range(4)
+
+NEXT = {}
+NEXT[NONE] = NONE
+NEXT[SHORT] = NONE
+NEXT[FIRST_LONG] = LONG
+NEXT[LONG] = NONE
+
+class POP3Client(basic.LineOnlyReceiver):
+
+ mode = SHORT
+ command = 'WELCOME'
+ import re
+ welcomeRe = re.compile('<(.*)>')
+
+ def __init__(self):
+ import warnings
+ warnings.warn("twisted.mail.pop3.POP3Client is deprecated, "
+ "please use twisted.mail.pop3.AdvancedPOP3Client "
+ "instead.", DeprecationWarning,
+ stacklevel=3)
+
+ def sendShort(self, command, params=None):
+ if params is not None:
+ self.sendLine('%s %s' % (command, params))
+ else:
+ self.sendLine(command)
+ self.command = command
+ self.mode = SHORT
+
+ def sendLong(self, command, params):
+ if params:
+ self.sendLine('%s %s' % (command, params))
+ else:
+ self.sendLine(command)
+ self.command = command
+ self.mode = FIRST_LONG
+
+ def handle_default(self, line):
+ if line[:-4] == '-ERR':
+ self.mode = NONE
+
+ def handle_WELCOME(self, line):
+ code, data = line.split(' ', 1)
+ if code != '+OK':
+ self.transport.loseConnection()
+ else:
+ m = self.welcomeRe.match(line)
+ if m:
+ self.welcomeCode = m.group(1)
+
+ def _dispatch(self, command, default, *args):
+ try:
+ method = getattr(self, 'handle_'+command, default)
+ if method is not None:
+ method(*args)
+ except:
+ log.err()
+
+ def lineReceived(self, line):
+ if self.mode == SHORT or self.mode == FIRST_LONG:
+ self.mode = NEXT[self.mode]
+ self._dispatch(self.command, self.handle_default, line)
+ elif self.mode == LONG:
+ if line == '.':
+ self.mode = NEXT[self.mode]
+ self._dispatch(self.command+'_end', None)
+ return
+ if line[:1] == '.':
+ line = line[1:]
+ self._dispatch(self.command+"_continue", None, line)
+
+ def apopAuthenticate(self, user, password, magic):
+ digest = md5(magic + password).hexdigest()
+ self.apop(user, digest)
+
+ def apop(self, user, digest):
+ self.sendLong('APOP', ' '.join((user, digest)))
+ def retr(self, i):
+ self.sendLong('RETR', i)
+ def dele(self, i):
+ self.sendShort('DELE', i)
+ def list(self, i=''):
+ self.sendLong('LIST', i)
+ def uidl(self, i=''):
+ self.sendLong('UIDL', i)
+ def user(self, name):
+ self.sendShort('USER', name)
+ def pass_(self, pass_):
+ self.sendShort('PASS', pass_)
+ def quit(self):
+ self.sendShort('QUIT')
+
+from twisted.mail.pop3client import POP3Client as AdvancedPOP3Client
+from twisted.mail.pop3client import POP3ClientError
+from twisted.mail.pop3client import InsecureAuthenticationDisallowed
+from twisted.mail.pop3client import ServerErrorResponse
+from twisted.mail.pop3client import LineTooLong
+
+__all__ = [
+ # Interfaces
+ 'IMailbox', 'IServerFactory',
+
+ # Exceptions
+ 'POP3Error', 'POP3ClientError', 'InsecureAuthenticationDisallowed',
+ 'ServerErrorResponse', 'LineTooLong',
+
+ # Protocol classes
+ 'POP3', 'POP3Client', 'AdvancedPOP3Client',
+
+ # Misc
+ 'APOPCredentials', 'Mailbox']
diff --git a/vendor/Twisted-10.0.0/twisted/mail/pop3client.py b/vendor/Twisted-10.0.0/twisted/mail/pop3client.py
new file mode 100644
index 0000000000..cd3b57b202
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/pop3client.py
@@ -0,0 +1,706 @@
+# -*- test-case-name: twisted.mail.test.test_pop3client -*-
+# Copyright (c) 2001-2004 Divmod Inc.
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+POP3 client protocol implementation
+
+Don't use this module directly. Use twisted.mail.pop3 instead.
+
+@author: Jp Calderone
+"""
+
+import re
+
+from twisted.python import log
+from twisted.python.hashlib import md5
+from twisted.internet import defer
+from twisted.protocols import basic
+from twisted.protocols import policies
+from twisted.internet import error
+from twisted.internet import interfaces
+
+OK = '+OK'
+ERR = '-ERR'
+
+class POP3ClientError(Exception):
+ """Base class for all exceptions raised by POP3Client.
+ """
+
+class InsecureAuthenticationDisallowed(POP3ClientError):
+ """Secure authentication was required but no mechanism could be found.
+ """
+
+class TLSError(POP3ClientError):
+ """
+ Secure authentication was required but either the transport does
+ not support TLS or no TLS context factory was supplied.
+ """
+
+class TLSNotSupportedError(POP3ClientError):
+ """
+ Secure authentication was required but the server does not support
+ TLS.
+ """
+
+class ServerErrorResponse(POP3ClientError):
+ """The server returned an error response to a request.
+ """
+ def __init__(self, reason, consumer=None):
+ POP3ClientError.__init__(self, reason)
+ self.consumer = consumer
+
+class LineTooLong(POP3ClientError):
+ """The server sent an extremely long line.
+ """
+
+class _ListSetter:
+ # Internal helper. POP3 responses sometimes occur in the
+ # form of a list of lines containing two pieces of data,
+ # a message index and a value of some sort. When a message
+ # is deleted, it is omitted from these responses. The
+ # setitem method of this class is meant to be called with
+ # these two values. In the cases where indexes are skipped,
+ # it takes care of padding out the missing values with None.
+ def __init__(self, L):
+ self.L = L
+ def setitem(self, (item, value)):
+ diff = item - len(self.L) + 1
+ if diff > 0:
+ self.L.extend([None] * diff)
+ self.L[item] = value
+
+
+def _statXform(line):
+ # Parse a STAT response
+ numMsgs, totalSize = line.split(None, 1)
+ return int(numMsgs), int(totalSize)
+
+
+def _listXform(line):
+ # Parse a LIST response
+ index, size = line.split(None, 1)
+ return int(index) - 1, int(size)
+
+
+def _uidXform(line):
+ # Parse a UIDL response
+ index, uid = line.split(None, 1)
+ return int(index) - 1, uid
+
+def _codeStatusSplit(line):
+ # Parse an +OK or -ERR response
+ parts = line.split(' ', 1)
+ if len(parts) == 1:
+ return parts[0], ''
+ return parts
+
+def _dotUnquoter(line):
+ """
+ C{'.'} characters which begin a line of a message are doubled to avoid
+ confusing with the terminating C{'.\\r\\n'} sequence. This function
+ unquotes them.
+ """
+ if line.startswith('..'):
+ return line[1:]
+ return line
+
+class POP3Client(basic.LineOnlyReceiver, policies.TimeoutMixin):
+ """POP3 client protocol implementation class
+
+ Instances of this class provide a convenient, efficient API for
+ retrieving and deleting messages from a POP3 server.
+
+ @type startedTLS: C{bool}
+ @ivar startedTLS: Whether TLS has been negotiated successfully.
+
+
+ @type allowInsecureLogin: C{bool}
+ @ivar allowInsecureLogin: Indicate whether login() should be
+ allowed if the server offers no authentication challenge and if
+ our transport does not offer any protection via encryption.
+
+ @type serverChallenge: C{str} or C{None}
+ @ivar serverChallenge: Challenge received from the server
+
+ @type timeout: C{int}
+ @ivar timeout: Number of seconds to wait before timing out a
+ connection. If the number is <= 0, no timeout checking will be
+ performed.
+ """
+
+ startedTLS = False
+ allowInsecureLogin = False
+ timeout = 0
+ serverChallenge = None
+
+ # Capabilities are not allowed to change during the session
+ # (except when TLS is negotiated), so cache the first response and
+ # use that for all later lookups
+ _capCache = None
+
+ # Regular expression to search for in the challenge string in the server
+ # greeting line.
+ _challengeMagicRe = re.compile('(<[^>]+>)')
+
+ # List of pending calls.
+ # We are a pipelining API but don't actually
+ # support pipelining on the network yet.
+ _blockedQueue = None
+
+ # The Deferred to which the very next result will go.
+ _waiting = None
+
+ # Whether we dropped the connection because of a timeout
+ _timedOut = False
+
+ # If the server sends an initial -ERR, this is the message it sent
+ # with it.
+ _greetingError = None
+
+ def _blocked(self, f, *a):
+ # Internal helper. If commands are being blocked, append
+ # the given command and arguments to a list and return a Deferred
+ # that will be chained with the return value of the function
+ # when it eventually runs. Otherwise, set up for commands to be
+
+ # blocked and return None.
+ if self._blockedQueue is not None:
+ d = defer.Deferred()
+ self._blockedQueue.append((d, f, a))
+ return d
+ self._blockedQueue = []
+ return None
+
+ def _unblock(self):
+ # Internal helper. Indicate that a function has completed.
+ # If there are blocked commands, run the next one. If there
+ # are not, set up for the next command to not be blocked.
+ if self._blockedQueue == []:
+ self._blockedQueue = None
+ elif self._blockedQueue is not None:
+ _blockedQueue = self._blockedQueue
+ self._blockedQueue = None
+
+ d, f, a = _blockedQueue.pop(0)
+ d2 = f(*a)
+ d2.chainDeferred(d)
+ # f is a function which uses _blocked (otherwise it wouldn't
+ # have gotten into the blocked queue), which means it will have
+ # re-set _blockedQueue to an empty list, so we can put the rest
+ # of the blocked queue back into it now.
+ self._blockedQueue.extend(_blockedQueue)
+
+
+ def sendShort(self, cmd, args):
+ # Internal helper. Send a command to which a short response
+ # is expected. Return a Deferred that fires when the response
+ # is received. Block all further commands from being sent until
+ # the response is received. Transition the state to SHORT.
+ d = self._blocked(self.sendShort, cmd, args)
+ if d is not None:
+ return d
+
+ if args:
+ self.sendLine(cmd + ' ' + args)
+ else:
+ self.sendLine(cmd)
+ self.state = 'SHORT'
+ self._waiting = defer.Deferred()
+ return self._waiting
+
+ def sendLong(self, cmd, args, consumer, xform):
+ # Internal helper. Send a command to which a multiline
+ # response is expected. Return a Deferred that fires when
+ # the entire response is received. Block all further commands
+ # from being sent until the entire response is received.
+ # Transition the state to LONG_INITIAL.
+ d = self._blocked(self.sendLong, cmd, args, consumer, xform)
+ if d is not None:
+ return d
+
+ if args:
+ self.sendLine(cmd + ' ' + args)
+ else:
+ self.sendLine(cmd)
+ self.state = 'LONG_INITIAL'
+ self._xform = xform
+ self._consumer = consumer
+ self._waiting = defer.Deferred()
+ return self._waiting
+
+ # Twisted protocol callback
+ def connectionMade(self):
+ if self.timeout > 0:
+ self.setTimeout(self.timeout)
+
+ self.state = 'WELCOME'
+ self._blockedQueue = []
+
+ def timeoutConnection(self):
+ self._timedOut = True
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ if self.timeout > 0:
+ self.setTimeout(None)
+
+ if self._timedOut:
+ reason = error.TimeoutError()
+ elif self._greetingError:
+ reason = ServerErrorResponse(self._greetingError)
+
+ d = []
+ if self._waiting is not None:
+ d.append(self._waiting)
+ self._waiting = None
+ if self._blockedQueue is not None:
+ d.extend([deferred for (deferred, f, a) in self._blockedQueue])
+ self._blockedQueue = None
+ for w in d:
+ w.errback(reason)
+
+ def lineReceived(self, line):
+ if self.timeout > 0:
+ self.resetTimeout()
+
+ state = self.state
+ self.state = None
+ state = getattr(self, 'state_' + state)(line) or state
+ if self.state is None:
+ self.state = state
+
+ def lineLengthExceeded(self, buffer):
+ # XXX - We need to be smarter about this
+ if self._waiting is not None:
+ waiting, self._waiting = self._waiting, None
+ waiting.errback(LineTooLong())
+ self.transport.loseConnection()
+
+ # POP3 Client state logic - don't touch this.
+ def state_WELCOME(self, line):
+ # WELCOME is the first state. The server sends one line of text
+ # greeting us, possibly with an APOP challenge. Transition the
+ # state to WAITING.
+ code, status = _codeStatusSplit(line)
+ if code != OK:
+ self._greetingError = status
+ self.transport.loseConnection()
+ else:
+ m = self._challengeMagicRe.search(status)
+
+ if m is not None:
+ self.serverChallenge = m.group(1)
+
+ self.serverGreeting(status)
+
+ self._unblock()
+ return 'WAITING'
+
+ def state_WAITING(self, line):
+ # The server isn't supposed to send us anything in this state.
+ log.msg("Illegal line from server: " + repr(line))
+
+ def state_SHORT(self, line):
+ # This is the state we are in when waiting for a single
+ # line response. Parse it and fire the appropriate callback
+ # or errback. Transition the state back to WAITING.
+ deferred, self._waiting = self._waiting, None
+ self._unblock()
+ code, status = _codeStatusSplit(line)
+ if code == OK:
+ deferred.callback(status)
+ else:
+ deferred.errback(ServerErrorResponse(status))
+ return 'WAITING'
+
+ def state_LONG_INITIAL(self, line):
+ # This is the state we are in when waiting for the first
+ # line of a long response. Parse it and transition the
+ # state to LONG if it is an okay response; if it is an
+ # error response, fire an errback, clean up the things
+ # waiting for a long response, and transition the state
+ # to WAITING.
+ code, status = _codeStatusSplit(line)
+ if code == OK:
+ return 'LONG'
+ consumer = self._consumer
+ deferred = self._waiting
+ self._consumer = self._waiting = self._xform = None
+ self._unblock()
+ deferred.errback(ServerErrorResponse(status, consumer))
+ return 'WAITING'
+
+ def state_LONG(self, line):
+ # This is the state for each line of a long response.
+ # If it is the last line, finish things, fire the
+ # Deferred, and transition the state to WAITING.
+ # Otherwise, pass the line to the consumer.
+ if line == '.':
+ consumer = self._consumer
+ deferred = self._waiting
+ self._consumer = self._waiting = self._xform = None
+ self._unblock()
+ deferred.callback(consumer)
+ return 'WAITING'
+ else:
+ if self._xform is not None:
+ self._consumer(self._xform(line))
+ else:
+ self._consumer(line)
+ return 'LONG'
+
+
+ # Callbacks - override these
+ def serverGreeting(self, greeting):
+ """Called when the server has sent us a greeting.
+
+ @type greeting: C{str} or C{None}
+ @param greeting: The status message sent with the server
+ greeting. For servers implementing APOP authentication, this
+ will be a challenge string. .
+ """
+
+
+ # External API - call these (most of 'em anyway)
+ def startTLS(self, contextFactory=None):
+ """
+ Initiates a 'STLS' request and negotiates the TLS / SSL
+ Handshake.
+
+ @type contextFactory: C{ssl.ClientContextFactory} @param
+ contextFactory: The context factory with which to negotiate
+ TLS. If C{None}, try to create a new one.
+
+ @return: A Deferred which fires when the transport has been
+ secured according to the given contextFactory, or which fails
+ if the transport cannot be secured.
+ """
+ tls = interfaces.ITLSTransport(self.transport, None)
+ if tls is None:
+ return defer.fail(TLSError(
+ "POP3Client transport does not implement "
+ "interfaces.ITLSTransport"))
+
+ if contextFactory is None:
+ contextFactory = self._getContextFactory()
+
+ if contextFactory is None:
+ return defer.fail(TLSError(
+ "POP3Client requires a TLS context to "
+ "initiate the STLS handshake"))
+
+ d = self.capabilities()
+ d.addCallback(self._startTLS, contextFactory, tls)
+ return d
+
+
+ def _startTLS(self, caps, contextFactory, tls):
+ assert not self.startedTLS, "Client and Server are currently communicating via TLS"
+
+ if 'STLS' not in caps:
+ return defer.fail(TLSNotSupportedError(
+ "Server does not support secure communication "
+ "via TLS / SSL"))
+
+ d = self.sendShort('STLS', None)
+ d.addCallback(self._startedTLS, contextFactory, tls)
+ d.addCallback(lambda _: self.capabilities())
+ return d
+
+
+ def _startedTLS(self, result, context, tls):
+ self.transport = tls
+ self.transport.startTLS(context)
+ self._capCache = None
+ self.startedTLS = True
+ return result
+
+
+ def _getContextFactory(self):
+ try:
+ from twisted.internet import ssl
+ except ImportError:
+ return None
+ else:
+ context = ssl.ClientContextFactory()
+ context.method = ssl.SSL.TLSv1_METHOD
+ return context
+
+
+ def login(self, username, password):
+ """Log into the server.
+
+ If APOP is available it will be used. Otherwise, if TLS is
+ available an 'STLS' session will be started and plaintext
+ login will proceed. Otherwise, if the instance attribute
+ allowInsecureLogin is set to True, insecure plaintext login
+ will proceed. Otherwise, InsecureAuthenticationDisallowed
+ will be raised (asynchronously).
+
+ @param username: The username with which to log in.
+ @param password: The password with which to log in.
+
+ @rtype: C{Deferred}
+ @return: A deferred which fires when login has
+ completed.
+ """
+ d = self.capabilities()
+ d.addCallback(self._login, username, password)
+ return d
+
+
+ def _login(self, caps, username, password):
+ if self.serverChallenge is not None:
+ return self._apop(username, password, self.serverChallenge)
+
+ tryTLS = 'STLS' in caps
+
+ #If our transport supports switching to TLS, we might want to try to switch to TLS.
+ tlsableTransport = interfaces.ITLSTransport(self.transport, None) is not None
+
+ # If our transport is not already using TLS, we might want to try to switch to TLS.
+ nontlsTransport = interfaces.ISSLTransport(self.transport, None) is None
+
+ if not self.startedTLS and tryTLS and tlsableTransport and nontlsTransport:
+ d = self.startTLS()
+
+ d.addCallback(self._loginTLS, username, password)
+ return d
+
+ elif self.startedTLS or not nontlsTransport or self.allowInsecureLogin:
+ return self._plaintext(username, password)
+ else:
+ return defer.fail(InsecureAuthenticationDisallowed())
+
+
+ def _loginTLS(self, res, username, password):
+ return self._plaintext(username, password)
+
+ def _plaintext(self, username, password):
+ # Internal helper. Send a username/password pair, returning a Deferred
+ # that fires when both have succeeded or fails when the server rejects
+ # either.
+ return self.user(username).addCallback(lambda r: self.password(password))
+
+ def _apop(self, username, password, challenge):
+ # Internal helper. Computes and sends an APOP response. Returns
+ # a Deferred that fires when the server responds to the response.
+ digest = md5(challenge + password).hexdigest()
+ return self.apop(username, digest)
+
+ def apop(self, username, digest):
+ """Perform APOP login.
+
+ This should be used in special circumstances only, when it is
+ known that the server supports APOP authentication, and APOP
+ authentication is absolutely required. For the common case,
+ use L{login} instead.
+
+ @param username: The username with which to log in.
+ @param digest: The challenge response to authenticate with.
+ """
+ return self.sendShort('APOP', username + ' ' + digest)
+
+ def user(self, username):
+ """Send the user command.
+
+ This performs the first half of plaintext login. Unless this
+ is absolutely required, use the L{login} method instead.
+
+ @param username: The username with which to log in.
+ """
+ return self.sendShort('USER', username)
+
+ def password(self, password):
+ """Send the password command.
+
+ This performs the second half of plaintext login. Unless this
+ is absolutely required, use the L{login} method instead.
+
+ @param password: The plaintext password with which to authenticate.
+ """
+ return self.sendShort('PASS', password)
+
+ def delete(self, index):
+ """Delete a message from the server.
+
+ @type index: C{int}
+ @param index: The index of the message to delete.
+ This is 0-based.
+
+ @rtype: C{Deferred}
+ @return: A deferred which fires when the delete command
+ is successful, or fails if the server returns an error.
+ """
+ return self.sendShort('DELE', str(index + 1))
+
+ def _consumeOrSetItem(self, cmd, args, consumer, xform):
+ # Internal helper. Send a long command. If no consumer is
+ # provided, create a consumer that puts results into a list
+ # and return a Deferred that fires with that list when it
+ # is complete.
+ if consumer is None:
+ L = []
+ consumer = _ListSetter(L).setitem
+ return self.sendLong(cmd, args, consumer, xform).addCallback(lambda r: L)
+ return self.sendLong(cmd, args, consumer, xform)
+
+ def _consumeOrAppend(self, cmd, args, consumer, xform):
+ # Internal helper. Send a long command. If no consumer is
+ # provided, create a consumer that appends results to a list
+ # and return a Deferred that fires with that list when it is
+ # complete.
+ if consumer is None:
+ L = []
+ consumer = L.append
+ return self.sendLong(cmd, args, consumer, xform).addCallback(lambda r: L)
+ return self.sendLong(cmd, args, consumer, xform)
+
+ def capabilities(self, useCache=True):
+ """Retrieve the capabilities supported by this server.
+
+ Not all servers support this command. If the server does not
+ support this, it is treated as though it returned a successful
+ response listing no capabilities. At some future time, this may be
+ changed to instead seek out information about a server's
+ capabilities in some other fashion (only if it proves useful to do
+ so, and only if there are servers still in use which do not support
+ CAPA but which do support POP3 extensions that are useful).
+
+ @type useCache: C{bool}
+ @param useCache: If set, and if capabilities have been
+ retrieved previously, just return the previously retrieved
+ results.
+
+ @return: A Deferred which fires with a C{dict} mapping C{str}
+ to C{None} or C{list}s of C{str}. For example::
+
+ C: CAPA
+ S: +OK Capability list follows
+ S: TOP
+ S: USER
+ S: SASL CRAM-MD5 KERBEROS_V4
+ S: RESP-CODES
+ S: LOGIN-DELAY 900
+ S: PIPELINING
+ S: EXPIRE 60
+ S: UIDL
+ S: IMPLEMENTATION Shlemazle-Plotz-v302
+ S: .
+
+ will be lead to a result of::
+
+ | {'TOP': None,
+ | 'USER': None,
+ | 'SASL': ['CRAM-MD5', 'KERBEROS_V4'],
+ | 'RESP-CODES': None,
+ | 'LOGIN-DELAY': ['900'],
+ | 'PIPELINING': None,
+ | 'EXPIRE': ['60'],
+ | 'UIDL': None,
+ | 'IMPLEMENTATION': ['Shlemazle-Plotz-v302']}
+ """
+ if useCache and self._capCache is not None:
+ return defer.succeed(self._capCache)
+
+ cache = {}
+ def consume(line):
+ tmp = line.split()
+ if len(tmp) == 1:
+ cache[tmp[0]] = None
+ elif len(tmp) > 1:
+ cache[tmp[0]] = tmp[1:]
+
+ def capaNotSupported(err):
+ err.trap(ServerErrorResponse)
+ return None
+
+ def gotCapabilities(result):
+ self._capCache = cache
+ return cache
+
+ d = self._consumeOrAppend('CAPA', None, consume, None)
+ d.addErrback(capaNotSupported).addCallback(gotCapabilities)
+ return d
+
+
+ def noop(self):
+ """Do nothing, with the help of the server.
+
+ No operation is performed. The returned Deferred fires when
+ the server responds.
+ """
+ return self.sendShort("NOOP", None)
+
+
+ def reset(self):
+ """Remove the deleted flag from any messages which have it.
+
+ The returned Deferred fires when the server responds.
+ """
+ return self.sendShort("RSET", None)
+
+
+ def retrieve(self, index, consumer=None, lines=None):
+ """Retrieve a message from the server.
+
+ If L{consumer} is not None, it will be called with
+ each line of the message as it is received. Otherwise,
+ the returned Deferred will be fired with a list of all
+ the lines when the message has been completely received.
+ """
+ idx = str(index + 1)
+ if lines is None:
+ return self._consumeOrAppend('RETR', idx, consumer, _dotUnquoter)
+
+ return self._consumeOrAppend('TOP', '%s %d' % (idx, lines), consumer, _dotUnquoter)
+
+
+ def stat(self):
+ """Get information about the size of this mailbox.
+
+ The returned Deferred will be fired with a tuple containing
+ the number or messages in the mailbox and the size (in bytes)
+ of the mailbox.
+ """
+ return self.sendShort('STAT', None).addCallback(_statXform)
+
+
+ def listSize(self, consumer=None):
+ """Retrieve a list of the size of all messages on the server.
+
+ If L{consumer} is not None, it will be called with two-tuples
+ of message index number and message size as they are received.
+ Otherwise, a Deferred which will fire with a list of B{only}
+ message sizes will be returned. For messages which have been
+ deleted, None will be used in place of the message size.
+ """
+ return self._consumeOrSetItem('LIST', None, consumer, _listXform)
+
+
+ def listUID(self, consumer=None):
+ """Retrieve a list of the UIDs of all messages on the server.
+
+ If L{consumer} is not None, it will be called with two-tuples
+ of message index number and message UID as they are received.
+ Otherwise, a Deferred which will fire with of list of B{only}
+ message UIDs will be returned. For messages which have been
+ deleted, None will be used in place of the message UID.
+ """
+ return self._consumeOrSetItem('UIDL', None, consumer, _uidXform)
+
+
+ def quit(self):
+ """Disconnect from the server.
+ """
+ return self.sendShort('QUIT', None)
+
+__all__ = [
+ # Exceptions
+ 'InsecureAuthenticationDisallowed', 'LineTooLong', 'POP3ClientError',
+ 'ServerErrorResponse', 'TLSError', 'TLSNotSupportedError',
+
+ # Protocol classes
+ 'POP3Client']
diff --git a/vendor/Twisted-10.0.0/twisted/mail/protocols.py b/vendor/Twisted-10.0.0/twisted/mail/protocols.py
new file mode 100644
index 0000000000..dc449af527
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/protocols.py
@@ -0,0 +1,225 @@
+# -*- test-case-name: twisted.mail.test.test_mail -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Protocol support for twisted.mail."""
+
+# twisted imports
+from twisted.mail import pop3
+from twisted.mail import smtp
+from twisted.internet import protocol
+from twisted.internet import defer
+from twisted.copyright import longversion
+from twisted.python import log
+
+from twisted import cred
+import twisted.cred.error
+import twisted.cred.credentials
+
+from twisted.mail import relay
+
+from zope.interface import implements
+
+
+class DomainDeliveryBase:
+ """A server that uses twisted.mail service's domains."""
+
+ implements(smtp.IMessageDelivery)
+
+ service = None
+ protocolName = None
+
+ def __init__(self, service, user, host=smtp.DNSNAME):
+ self.service = service
+ self.user = user
+ self.host = host
+
+ def receivedHeader(self, helo, origin, recipients):
+ authStr = heloStr = ""
+ if self.user:
+ authStr = " auth=%s" % (self.user.encode('xtext'),)
+ if helo[0]:
+ heloStr = " helo=%s" % (helo[0],)
+ from_ = "from %s ([%s]%s%s)" % (helo[0], helo[1], heloStr, authStr)
+ by = "by %s with %s (%s)" % (
+ self.host, self.protocolName, longversion
+ )
+ for_ = "for <%s>; %s" % (' '.join(map(str, recipients)), smtp.rfc822date())
+ return "Received: %s\n\t%s\n\t%s" % (from_, by, for_)
+
+ def validateTo(self, user):
+ # XXX - Yick. This needs cleaning up.
+ if self.user and self.service.queue:
+ d = self.service.domains.get(user.dest.domain, None)
+ if d is None:
+ d = relay.DomainQueuer(self.service, True)
+ else:
+ d = self.service.domains[user.dest.domain]
+ return defer.maybeDeferred(d.exists, user)
+
+ def validateFrom(self, helo, origin):
+ if not helo:
+ raise smtp.SMTPBadSender(origin, 503, "Who are you? Say HELO first.")
+ if origin.local != '' and origin.domain == '':
+ raise smtp.SMTPBadSender(origin, 501, "Sender address must contain domain.")
+ return origin
+
+ def startMessage(self, users):
+ ret = []
+ for user in users:
+ ret.append(self.service.domains[user.dest.domain].startMessage(user))
+ return ret
+
+
+class SMTPDomainDelivery(DomainDeliveryBase):
+ protocolName = 'smtp'
+
+class ESMTPDomainDelivery(DomainDeliveryBase):
+ protocolName = 'esmtp'
+
+class DomainSMTP(SMTPDomainDelivery, smtp.SMTP):
+ service = user = None
+
+ def __init__(self, *args, **kw):
+ import warnings
+ warnings.warn(
+ "DomainSMTP is deprecated. Use IMessageDelivery objects instead.",
+ DeprecationWarning, stacklevel=2,
+ )
+ smtp.SMTP.__init__(self, *args, **kw)
+ if self.delivery is None:
+ self.delivery = self
+
+class DomainESMTP(ESMTPDomainDelivery, smtp.ESMTP):
+ service = user = None
+
+ def __init__(self, *args, **kw):
+ import warnings
+ warnings.warn(
+ "DomainESMTP is deprecated. Use IMessageDelivery objects instead.",
+ DeprecationWarning, stacklevel=2,
+ )
+ smtp.ESMTP.__init__(self, *args, **kw)
+ if self.delivery is None:
+ self.delivery = self
+
+class SMTPFactory(smtp.SMTPFactory):
+ """A protocol factory for SMTP."""
+
+ protocol = smtp.SMTP
+ portal = None
+
+ def __init__(self, service, portal = None):
+ smtp.SMTPFactory.__init__(self)
+ self.service = service
+ self.portal = portal
+
+ def buildProtocol(self, addr):
+ log.msg('Connection from %s' % (addr,))
+ p = smtp.SMTPFactory.buildProtocol(self, addr)
+ p.service = self.service
+ p.portal = self.portal
+ return p
+
+class ESMTPFactory(SMTPFactory):
+ protocol = smtp.ESMTP
+ context = None
+
+ def __init__(self, *args):
+ SMTPFactory.__init__(self, *args)
+ self.challengers = {
+ 'CRAM-MD5': cred.credentials.CramMD5Credentials
+ }
+
+ def buildProtocol(self, addr):
+ p = SMTPFactory.buildProtocol(self, addr)
+ p.challengers = self.challengers
+ p.ctx = self.context
+ return p
+
+class VirtualPOP3(pop3.POP3):
+ """Virtual hosting POP3."""
+
+ service = None
+
+ domainSpecifier = '@' # Gaagh! I hate POP3. No standardized way
+ # to indicate user@host. '@' doesn't work
+ # with NS, e.g.
+
+ def authenticateUserAPOP(self, user, digest):
+ # Override the default lookup scheme to allow virtual domains
+ user, domain = self.lookupDomain(user)
+ try:
+ portal = self.service.lookupPortal(domain)
+ except KeyError:
+ return defer.fail(cred.error.UnauthorizedLogin())
+ else:
+ return portal.login(
+ pop3.APOPCredentials(self.magic, user, digest),
+ None,
+ pop3.IMailbox
+ )
+
+ def authenticateUserPASS(self, user, password):
+ user, domain = self.lookupDomain(user)
+ try:
+ portal = self.service.lookupPortal(domain)
+ except KeyError:
+ return defer.fail(cred.error.UnauthorizedLogin())
+ else:
+ return portal.login(
+ cred.credentials.UsernamePassword(user, password),
+ None,
+ pop3.IMailbox
+ )
+
+ def lookupDomain(self, user):
+ try:
+ user, domain = user.split(self.domainSpecifier, 1)
+ except ValueError:
+ domain = ''
+ if domain not in self.service.domains:
+ raise pop3.POP3Error("no such domain %s" % domain)
+ return user, domain
+
+
+class POP3Factory(protocol.ServerFactory):
+ """POP3 protocol factory."""
+
+ protocol = VirtualPOP3
+ service = None
+
+ def __init__(self, service):
+ self.service = service
+
+ def buildProtocol(self, addr):
+ p = protocol.ServerFactory.buildProtocol(self, addr)
+ p.service = self.service
+ return p
+
+#
+# It is useful to know, perhaps, that the required file for this to work can
+# be created thusly:
+#
+# openssl req -x509 -newkey rsa:2048 -keyout file.key -out file.crt \
+# -days 365 -nodes
+#
+# And then cat file.key and file.crt together. The number of days and bits
+# can be changed, of course.
+#
+class SSLContextFactory:
+ """An SSL Context Factory
+
+ This loads a certificate and private key from a specified file.
+ """
+ def __init__(self, filename):
+ self.filename = filename
+
+ def getContext(self):
+ """Create an SSL context."""
+ from OpenSSL import SSL
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_certificate_file(self.filename)
+ ctx.use_privatekey_file(self.filename)
+ return ctx
diff --git a/vendor/Twisted-10.0.0/twisted/mail/relay.py b/vendor/Twisted-10.0.0/twisted/mail/relay.py
new file mode 100644
index 0000000000..5fbf48f4dd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/relay.py
@@ -0,0 +1,114 @@
+# -*- test-case-name: twisted.mail.test.test_mail -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Support for relaying mail for twisted.mail"""
+
+from twisted.mail import smtp
+from twisted.python import log
+from twisted.internet.address import UNIXAddress
+
+import os
+
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
+class DomainQueuer:
+ """An SMTP domain which add messages to a queue intended for relaying."""
+
+ def __init__(self, service, authenticated=False):
+ self.service = service
+ self.authed = authenticated
+
+ def exists(self, user):
+ """Check whether we will relay
+
+ Call overridable willRelay method
+ """
+ if self.willRelay(user.dest, user.protocol):
+ # The most cursor form of verification of the addresses
+ orig = filter(None, str(user.orig).split('@', 1))
+ dest = filter(None, str(user.dest).split('@', 1))
+ if len(orig) == 2 and len(dest) == 2:
+ return lambda: self.startMessage(user)
+ raise smtp.SMTPBadRcpt(user)
+
+ def willRelay(self, address, protocol):
+ """Check whether we agree to relay
+
+ The default is to relay for all connections over UNIX
+ sockets and all connections from localhost.
+ """
+ peer = protocol.transport.getPeer()
+ return self.authed or isinstance(peer, UNIXAddress) or peer.host == '127.0.0.1'
+
+ def startMessage(self, user):
+ """Add envelope to queue and returns ISMTPMessage."""
+ queue = self.service.queue
+ envelopeFile, smtpMessage = queue.createNewMessage()
+ try:
+ log.msg('Queueing mail %r -> %r' % (str(user.orig), str(user.dest)))
+ pickle.dump([str(user.orig), str(user.dest)], envelopeFile)
+ finally:
+ envelopeFile.close()
+ return smtpMessage
+
+class RelayerMixin:
+
+ # XXX - This is -totally- bogus
+ # It opens about a -hundred- -billion- files
+ # and -leaves- them open!
+
+ def loadMessages(self, messagePaths):
+ self.messages = []
+ self.names = []
+ for message in messagePaths:
+ fp = open(message+'-H')
+ try:
+ messageContents = pickle.load(fp)
+ finally:
+ fp.close()
+ fp = open(message+'-D')
+ messageContents.append(fp)
+ self.messages.append(messageContents)
+ self.names.append(message)
+
+ def getMailFrom(self):
+ if not self.messages:
+ return None
+ return self.messages[0][0]
+
+ def getMailTo(self):
+ if not self.messages:
+ return None
+ return [self.messages[0][1]]
+
+ def getMailData(self):
+ if not self.messages:
+ return None
+ return self.messages[0][2]
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ """Since we only use one recipient per envelope, this
+ will be called with 0 or 1 addresses. We probably want
+ to do something with the error message if we failed.
+ """
+ if code in smtp.SUCCESS:
+ # At least one, i.e. all, recipients successfully delivered
+ os.remove(self.names[0]+'-D')
+ os.remove(self.names[0]+'-H')
+ del self.messages[0]
+ del self.names[0]
+
+class SMTPRelayer(RelayerMixin, smtp.SMTPClient):
+ def __init__(self, messagePaths, *args, **kw):
+ smtp.SMTPClient.__init__(self, *args, **kw)
+ self.loadMessages(messagePaths)
+
+class ESMTPRelayer(RelayerMixin, smtp.ESMTPClient):
+ def __init__(self, messagePaths, *args, **kw):
+ smtp.ESMTPClient.__init__(self, *args, **kw)
+ self.loadMessages(messagePaths)
diff --git a/vendor/Twisted-10.0.0/twisted/mail/relaymanager.py b/vendor/Twisted-10.0.0/twisted/mail/relaymanager.py
new file mode 100644
index 0000000000..8cf4edace7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/relaymanager.py
@@ -0,0 +1,631 @@
+# -*- test-case-name: twisted.mail.test.test_mail -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Infrastructure for relaying mail through smart host
+
+Today, internet e-mail has stopped being Peer-to-peer for many problems,
+spam (unsolicited bulk mail) among them. Instead, most nodes on the
+internet send all e-mail to a single computer, usually the ISP's though
+sometimes other schemes, such as SMTP-after-POP, are used. This computer
+is supposedly permanently up and traceable, and will do the work of
+figuring out MXs and connecting to them. This kind of configuration
+is usually termed "smart host", since the host we are connecting to
+is "smart" (and will find MXs and connect to them) rather then just
+accepting mail for a small set of domains.
+
+The classes here are meant to facilitate support for such a configuration
+for the twisted.mail SMTP server
+"""
+
+import rfc822
+import os
+import time
+
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.python.compat import set
+from twisted.mail import relay
+from twisted.mail import bounce
+from twisted.internet import protocol
+from twisted.internet.defer import Deferred, DeferredList
+from twisted.internet.error import DNSLookupError
+from twisted.mail import smtp
+from twisted.application import internet
+
+class ManagedRelayerMixin:
+ """SMTP Relayer which notifies a manager
+
+ Notify the manager about successful mail, failed mail
+ and broken connections
+ """
+
+ def __init__(self, manager):
+ self.manager = manager
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ """called when e-mail has been sent
+
+ we will always get 0 or 1 addresses.
+ """
+ message = self.names[0]
+ if code in smtp.SUCCESS:
+ self.manager.notifySuccess(self.factory, message)
+ else:
+ self.manager.notifyFailure(self.factory, message)
+ del self.messages[0]
+ del self.names[0]
+
+ def connectionLost(self, reason):
+ """called when connection is broken
+
+ notify manager we will try to send no more e-mail
+ """
+ self.manager.notifyDone(self.factory)
+
+class SMTPManagedRelayer(ManagedRelayerMixin, relay.SMTPRelayer):
+ def __init__(self, messages, manager, *args, **kw):
+ """
+ @type messages: C{list} of C{str}
+ @param messages: Filenames of messages to relay
+
+ manager should support .notifySuccess, .notifyFailure
+ and .notifyDone
+ """
+ ManagedRelayerMixin.__init__(self, manager)
+ relay.SMTPRelayer.__init__(self, messages, *args, **kw)
+
+class ESMTPManagedRelayer(ManagedRelayerMixin, relay.ESMTPRelayer):
+ def __init__(self, messages, manager, *args, **kw):
+ """
+ @type messages: C{list} of C{str}
+ @param messages: Filenames of messages to relay
+
+ manager should support .notifySuccess, .notifyFailure
+ and .notifyDone
+ """
+ ManagedRelayerMixin.__init__(self, manager)
+ relay.ESMTPRelayer.__init__(self, messages, *args, **kw)
+
+class SMTPManagedRelayerFactory(protocol.ClientFactory):
+ protocol = SMTPManagedRelayer
+
+ def __init__(self, messages, manager, *args, **kw):
+ self.messages = messages
+ self.manager = manager
+ self.pArgs = args
+ self.pKwArgs = kw
+
+ def buildProtocol(self, addr):
+ protocol = self.protocol(self.messages, self.manager, *self.pArgs,
+ **self.pKwArgs)
+ protocol.factory = self
+ return protocol
+
+ def clientConnectionFailed(self, connector, reason):
+ """called when connection could not be made
+
+ our manager should be notified that this happened,
+ it might prefer some other host in that case"""
+ self.manager.notifyNoConnection(self)
+ self.manager.notifyDone(self)
+
+class ESMTPManagedRelayerFactory(SMTPManagedRelayerFactory):
+ protocol = ESMTPManagedRelayer
+
+ def __init__(self, messages, manager, secret, contextFactory, *args, **kw):
+ self.secret = secret
+ self.contextFactory = contextFactory
+ SMTPManagedRelayerFactory.__init__(self, messages, manager, *args, **kw)
+
+ def buildProtocol(self, addr):
+ s = self.secret and self.secret(addr)
+ protocol = self.protocol(self.messages, self.manager, s,
+ self.contextFactory, *self.pArgs, **self.pKwArgs)
+ protocol.factory = self
+ return protocol
+
+class Queue:
+ """A queue of ougoing emails."""
+
+ noisy = True
+
+ def __init__(self, directory):
+ self.directory = directory
+ self._init()
+
+ def _init(self):
+ self.n = 0
+ self.waiting = {}
+ self.relayed = {}
+ self.readDirectory()
+
+ def __getstate__(self):
+ """(internal) delete volatile state"""
+ return {'directory' : self.directory}
+
+ def __setstate__(self, state):
+ """(internal) restore volatile state"""
+ self.__dict__.update(state)
+ self._init()
+
+ def readDirectory(self):
+ """Read the messages directory.
+
+ look for new messages.
+ """
+ for message in os.listdir(self.directory):
+ # Skip non data files
+ if message[-2:]!='-D':
+ continue
+ self.addMessage(message[:-2])
+
+ def getWaiting(self):
+ return self.waiting.keys()
+
+ def hasWaiting(self):
+ return len(self.waiting) > 0
+
+ def getRelayed(self):
+ return self.relayed.keys()
+
+ def setRelaying(self, message):
+ del self.waiting[message]
+ self.relayed[message] = 1
+
+ def setWaiting(self, message):
+ del self.relayed[message]
+ self.waiting[message] = 1
+
+ def addMessage(self, message):
+ if message not in self.relayed:
+ self.waiting[message] = 1
+ if self.noisy:
+ log.msg('Set ' + message + ' waiting')
+
+ def done(self, message):
+ """Remove message to from queue."""
+ message = os.path.basename(message)
+ os.remove(self.getPath(message) + '-D')
+ os.remove(self.getPath(message) + '-H')
+ del self.relayed[message]
+
+ def getPath(self, message):
+ """Get the path in the filesystem of a message."""
+ return os.path.join(self.directory, message)
+
+ def getEnvelope(self, message):
+ return pickle.load(self.getEnvelopeFile(message))
+
+ def getEnvelopeFile(self, message):
+ return open(os.path.join(self.directory, message+'-H'), 'rb')
+
+ def createNewMessage(self):
+ """Create a new message in the queue.
+
+ Return a tuple - file-like object for headers, and ISMTPMessage.
+ """
+ fname = "%s_%s_%s_%s" % (os.getpid(), time.time(), self.n, id(self))
+ self.n = self.n + 1
+ headerFile = open(os.path.join(self.directory, fname+'-H'), 'wb')
+ tempFilename = os.path.join(self.directory, fname+'-C')
+ finalFilename = os.path.join(self.directory, fname+'-D')
+ messageFile = open(tempFilename, 'wb')
+
+ from twisted.mail.mail import FileMessage
+ return headerFile,FileMessage(messageFile, tempFilename, finalFilename)
+
+
+class _AttemptManager(object):
+ """
+ Manage the state of a single attempt to flush the relay queue.
+ """
+ def __init__(self, manager):
+ self.manager = manager
+ self._completionDeferreds = []
+
+
+ def getCompletionDeferred(self):
+ self._completionDeferreds.append(Deferred())
+ return self._completionDeferreds[-1]
+
+
+ def _finish(self, relay, message):
+ self.manager.managed[relay].remove(os.path.basename(message))
+ self.manager.queue.done(message)
+
+
+ def notifySuccess(self, relay, message):
+ """a relay sent a message successfully
+
+ Mark it as sent in our lists
+ """
+ if self.manager.queue.noisy:
+ log.msg("success sending %s, removing from queue" % message)
+ self._finish(relay, message)
+
+
+ def notifyFailure(self, relay, message):
+ """Relaying the message has failed."""
+ if self.manager.queue.noisy:
+ log.msg("could not relay "+message)
+ # Moshe - Bounce E-mail here
+ # Be careful: if it's a bounced bounce, silently
+ # discard it
+ message = os.path.basename(message)
+ fp = self.manager.queue.getEnvelopeFile(message)
+ from_, to = pickle.load(fp)
+ fp.close()
+ from_, to, bounceMessage = bounce.generateBounce(open(self.manager.queue.getPath(message)+'-D'), from_, to)
+ fp, outgoingMessage = self.manager.queue.createNewMessage()
+ pickle.dump([from_, to], fp)
+ fp.close()
+ for line in bounceMessage.splitlines():
+ outgoingMessage.lineReceived(line)
+ outgoingMessage.eomReceived()
+ self._finish(relay, self.manager.queue.getPath(message))
+
+
+ def notifyDone(self, relay):
+ """A relaying SMTP client is disconnected.
+
+ unmark all pending messages under this relay's responsibility
+ as being relayed, and remove the relay.
+ """
+ for message in self.manager.managed.get(relay, ()):
+ if self.manager.queue.noisy:
+ log.msg("Setting " + message + " waiting")
+ self.manager.queue.setWaiting(message)
+ try:
+ del self.manager.managed[relay]
+ except KeyError:
+ pass
+ notifications = self._completionDeferreds
+ self._completionDeferreds = None
+ for d in notifications:
+ d.callback(None)
+
+
+ def notifyNoConnection(self, relay):
+ """Relaying SMTP client couldn't connect.
+
+ Useful because it tells us our upstream server is unavailable.
+ """
+ # Back off a bit
+ try:
+ msgs = self.manager.managed[relay]
+ except KeyError:
+ log.msg("notifyNoConnection passed unknown relay!")
+ return
+
+ if self.manager.queue.noisy:
+ log.msg("Backing off on delivery of " + str(msgs))
+ def setWaiting(queue, messages):
+ map(queue.setWaiting, messages)
+ from twisted.internet import reactor
+ reactor.callLater(30, setWaiting, self.manager.queue, msgs)
+ del self.manager.managed[relay]
+
+
+
+class SmartHostSMTPRelayingManager:
+ """Manage SMTP Relayers
+
+ Manage SMTP relayers, keeping track of the existing connections,
+ each connection's responsibility in term of messages. Create
+ more relayers if the need arises.
+
+ Someone should press .checkState periodically
+
+ @ivar fArgs: Additional positional arguments used to instantiate
+ C{factory}.
+
+ @ivar fKwArgs: Additional keyword arguments used to instantiate
+ C{factory}.
+
+ @ivar factory: A callable which returns a ClientFactory suitable for
+ making SMTP connections.
+ """
+
+ factory = SMTPManagedRelayerFactory
+
+ PORT = 25
+
+ mxcalc = None
+
+ def __init__(self, queue, maxConnections=2, maxMessagesPerConnection=10):
+ """
+ @type queue: Any implementor of C{IQueue}
+ @param queue: The object used to queue messages on their way to
+ delivery.
+
+ @type maxConnections: C{int}
+ @param maxConnections: The maximum number of SMTP connections to
+ allow to be opened at any given time.
+
+ @type maxMessagesPerConnection: C{int}
+ @param maxMessagesPerConnection: The maximum number of messages a
+ relayer will be given responsibility for.
+
+ Default values are meant for a small box with 1-5 users.
+ """
+ self.maxConnections = maxConnections
+ self.maxMessagesPerConnection = maxMessagesPerConnection
+ self.managed = {} # SMTP clients we're managing
+ self.queue = queue
+ self.fArgs = ()
+ self.fKwArgs = {}
+
+ def __getstate__(self):
+ """(internal) delete volatile state"""
+ dct = self.__dict__.copy()
+ del dct['managed']
+ return dct
+
+ def __setstate__(self, state):
+ """(internal) restore volatile state"""
+ self.__dict__.update(state)
+ self.managed = {}
+
+ def checkState(self):
+ """
+ Synchronize with the state of the world, and maybe launch a new
+ relay.
+
+ Call me periodically to check I am still up to date.
+
+ @return: None or a Deferred which fires when all of the SMTP clients
+ started by this call have disconnected.
+ """
+ self.queue.readDirectory()
+ if (len(self.managed) >= self.maxConnections):
+ return
+ if not self.queue.hasWaiting():
+ return
+
+ return self._checkStateMX()
+
+ def _checkStateMX(self):
+ nextMessages = self.queue.getWaiting()
+ nextMessages.reverse()
+
+ exchanges = {}
+ for msg in nextMessages:
+ from_, to = self.queue.getEnvelope(msg)
+ name, addr = rfc822.parseaddr(to)
+ parts = addr.split('@', 1)
+ if len(parts) != 2:
+ log.err("Illegal message destination: " + to)
+ continue
+ domain = parts[1]
+
+ self.queue.setRelaying(msg)
+ exchanges.setdefault(domain, []).append(self.queue.getPath(msg))
+ if len(exchanges) >= (self.maxConnections - len(self.managed)):
+ break
+
+ if self.mxcalc is None:
+ self.mxcalc = MXCalculator()
+
+ relays = []
+ for (domain, msgs) in exchanges.iteritems():
+ manager = _AttemptManager(self)
+ factory = self.factory(msgs, manager, *self.fArgs, **self.fKwArgs)
+ self.managed[factory] = map(os.path.basename, msgs)
+ relayAttemptDeferred = manager.getCompletionDeferred()
+ connectSetupDeferred = self.mxcalc.getMX(domain)
+ connectSetupDeferred.addCallback(lambda mx: str(mx.name))
+ connectSetupDeferred.addCallback(self._cbExchange, self.PORT, factory)
+ connectSetupDeferred.addErrback(lambda err: (relayAttemptDeferred.errback(err), err)[1])
+ connectSetupDeferred.addErrback(self._ebExchange, factory, domain)
+ relays.append(relayAttemptDeferred)
+ return DeferredList(relays)
+
+
+ def _cbExchange(self, address, port, factory):
+ from twisted.internet import reactor
+ reactor.connectTCP(address, port, factory)
+
+ def _ebExchange(self, failure, factory, domain):
+ log.err('Error setting up managed relay factory for ' + domain)
+ log.err(failure)
+ def setWaiting(queue, messages):
+ map(queue.setWaiting, messages)
+ from twisted.internet import reactor
+ reactor.callLater(30, setWaiting, self.queue, self.managed[factory])
+ del self.managed[factory]
+
+class SmartHostESMTPRelayingManager(SmartHostSMTPRelayingManager):
+ factory = ESMTPManagedRelayerFactory
+
+def _checkState(manager):
+ manager.checkState()
+
+def RelayStateHelper(manager, delay):
+ return internet.TimerService(delay, _checkState, manager)
+
+
+
+class CanonicalNameLoop(Exception):
+ """
+ When trying to look up the MX record for a host, a set of CNAME records was
+ found which form a cycle and resolution was abandoned.
+ """
+
+
+class CanonicalNameChainTooLong(Exception):
+ """
+ When trying to look up the MX record for a host, too many CNAME records
+ which point to other CNAME records were encountered and resolution was
+ abandoned.
+ """
+
+
+class MXCalculator:
+ """
+ A utility for looking up mail exchange hosts and tracking whether they are
+ working or not.
+
+ @ivar clock: L{IReactorTime} provider which will be used to decide when to
+ retry mail exchanges which have not been working.
+ """
+ timeOutBadMX = 60 * 60 # One hour
+ fallbackToDomain = True
+
+ def __init__(self, resolver=None, clock=None):
+ self.badMXs = {}
+ if resolver is None:
+ from twisted.names.client import createResolver
+ resolver = createResolver()
+ self.resolver = resolver
+ if clock is None:
+ from twisted.internet import reactor as clock
+ self.clock = clock
+
+
+ def markBad(self, mx):
+ """Indicate a given mx host is not currently functioning.
+
+ @type mx: C{str}
+ @param mx: The hostname of the host which is down.
+ """
+ self.badMXs[str(mx)] = self.clock.seconds() + self.timeOutBadMX
+
+ def markGood(self, mx):
+ """Indicate a given mx host is back online.
+
+ @type mx: C{str}
+ @param mx: The hostname of the host which is up.
+ """
+ try:
+ del self.badMXs[mx]
+ except KeyError:
+ pass
+
+ def getMX(self, domain, maximumCanonicalChainLength=3):
+ """
+ Find an MX record for the given domain.
+
+ @type domain: C{str}
+ @param domain: The domain name for which to look up an MX record.
+
+ @type maximumCanonicalChainLength: C{int}
+ @param maximumCanonicalChainLength: The maximum number of unique CNAME
+ records to follow while looking up the MX record.
+
+ @return: A L{Deferred} which is called back with a string giving the
+ name in the found MX record or which is errbacked if no MX record
+ can be found.
+ """
+ mailExchangeDeferred = self.resolver.lookupMailExchange(domain)
+ mailExchangeDeferred.addCallback(self._filterRecords)
+ mailExchangeDeferred.addCallback(
+ self._cbMX, domain, maximumCanonicalChainLength)
+ mailExchangeDeferred.addErrback(self._ebMX, domain)
+ return mailExchangeDeferred
+
+
+ def _filterRecords(self, records):
+ """
+ Convert a DNS response (a three-tuple of lists of RRHeaders) into a
+ mapping from record names to lists of corresponding record payloads.
+ """
+ recordBag = {}
+ for answer in records[0]:
+ recordBag.setdefault(str(answer.name), []).append(answer.payload)
+ return recordBag
+
+
+ def _cbMX(self, answers, domain, cnamesLeft):
+ """
+ Try to find the MX host from the given DNS information.
+
+ This will attempt to resolve CNAME results. It can recognize loops
+ and will give up on non-cyclic chains after a specified number of
+ lookups.
+ """
+ # Do this import here so that relaymanager.py doesn't depend on
+ # twisted.names, only MXCalculator will.
+ from twisted.names import dns, error
+
+ seenAliases = set()
+ exchanges = []
+ # Examine the answers for the domain we asked about
+ pertinentRecords = answers.get(domain, [])
+ while pertinentRecords:
+ record = pertinentRecords.pop()
+
+ # If it's a CNAME, we'll need to do some more processing
+ if record.TYPE == dns.CNAME:
+
+ # Remember that this name was an alias.
+ seenAliases.add(domain)
+
+ canonicalName = str(record.name)
+ # See if we have some local records which might be relevant.
+ if canonicalName in answers:
+
+ # Make sure it isn't a loop contained entirely within the
+ # results we have here.
+ if canonicalName in seenAliases:
+ return Failure(CanonicalNameLoop(record))
+
+ pertinentRecords = answers[canonicalName]
+ exchanges = []
+ else:
+ if cnamesLeft:
+ # Request more information from the server.
+ return self.getMX(canonicalName, cnamesLeft - 1)
+ else:
+ # Give up.
+ return Failure(CanonicalNameChainTooLong(record))
+
+ # If it's an MX, collect it.
+ if record.TYPE == dns.MX:
+ exchanges.append((record.preference, record))
+
+ if exchanges:
+ exchanges.sort()
+ for (preference, record) in exchanges:
+ host = str(record.name)
+ if host not in self.badMXs:
+ return record
+ t = self.clock.seconds() - self.badMXs[host]
+ if t >= 0:
+ del self.badMXs[host]
+ return record
+ return exchanges[0][1]
+ else:
+ # Treat no answers the same as an error - jump to the errback to try
+ # to look up an A record. This provides behavior described as a
+ # special case in RFC 974 in the section headed I{Interpreting the
+ # List of MX RRs}.
+ return Failure(
+ error.DNSNameError("No MX records for %r" % (domain,)))
+
+
+ def _ebMX(self, failure, domain):
+ from twisted.names import error, dns
+
+ if self.fallbackToDomain:
+ failure.trap(error.DNSNameError)
+ log.msg("MX lookup failed; attempting to use hostname (%s) directly" % (domain,))
+
+ # Alright, I admit, this is a bit icky.
+ d = self.resolver.getHostByName(domain)
+ def cbResolved(addr):
+ return dns.Record_MX(name=addr)
+ def ebResolved(err):
+ err.trap(error.DNSNameError)
+ raise DNSLookupError()
+ d.addCallbacks(cbResolved, ebResolved)
+ return d
+ elif failure.check(error.DNSNameError):
+ raise IOError("No MX found for %r" % (domain,))
+ return failure
diff --git a/vendor/Twisted-10.0.0/twisted/mail/scripts/__init__.py b/vendor/Twisted-10.0.0/twisted/mail/scripts/__init__.py
new file mode 100644
index 0000000000..f653cc71ed
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/scripts/__init__.py
@@ -0,0 +1 @@
+"mail scripts"
diff --git a/vendor/Twisted-10.0.0/twisted/mail/scripts/mailmail.py b/vendor/Twisted-10.0.0/twisted/mail/scripts/mailmail.py
new file mode 100644
index 0000000000..fbc0df852f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/scripts/mailmail.py
@@ -0,0 +1,360 @@
+# -*- test-case-name: twisted.mail.test.test_mailmail -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implementation module for the I{mailmail} command.
+"""
+
+import os
+import sys
+import rfc822
+import getpass
+from ConfigParser import ConfigParser
+
+try:
+ import cStringIO as StringIO
+except:
+ import StringIO
+
+from twisted.internet import reactor
+from twisted.mail import smtp
+
+GLOBAL_CFG = "/etc/mailmail"
+LOCAL_CFG = os.path.expanduser("~/.twisted/mailmail")
+SMARTHOST = '127.0.0.1'
+
+ERROR_FMT = """\
+Subject: Failed Message Delivery
+
+ Message delivery failed. The following occurred:
+
+ %s
+--
+The Twisted sendmail application.
+"""
+
+def log(message, *args):
+ sys.stderr.write(str(message) % args + '\n')
+
+class Options:
+ """
+ @type to: C{list} of C{str}
+ @ivar to: The addresses to which to deliver this message.
+
+ @type sender: C{str}
+ @ivar sender: The address from which this message is being sent.
+
+ @type body: C{file}
+ @ivar body: The object from which the message is to be read.
+ """
+
+def getlogin():
+ try:
+ return os.getlogin()
+ except:
+ return getpass.getuser()
+
+
+_unsupportedOption = SystemExit("Unsupported option.")
+
+def parseOptions(argv):
+ o = Options()
+ o.to = [e for e in argv if not e.startswith('-')]
+ o.sender = getlogin()
+
+ # Just be very stupid
+
+ # Skip -bm -- it is the default
+
+ # -bp lists queue information. Screw that.
+ if '-bp' in argv:
+ raise _unsupportedOption
+
+ # -bs makes sendmail use stdin/stdout as its transport. Screw that.
+ if '-bs' in argv:
+ raise _unsupportedOption
+
+ # -F sets who the mail is from, but is overridable by the From header
+ if '-F' in argv:
+ o.sender = argv[argv.index('-F') + 1]
+ o.to.remove(o.sender)
+
+ # -i and -oi makes us ignore lone "."
+ if ('-i' in argv) or ('-oi' in argv):
+ raise _unsupportedOption
+
+ # -odb is background delivery
+ if '-odb' in argv:
+ o.background = True
+ else:
+ o.background = False
+
+ # -odf is foreground delivery
+ if '-odf' in argv:
+ o.background = False
+ else:
+ o.background = True
+
+ # -oem and -em cause errors to be mailed back to the sender.
+ # It is also the default.
+
+ # -oep and -ep cause errors to be printed to stderr
+ if ('-oep' in argv) or ('-ep' in argv):
+ o.printErrors = True
+ else:
+ o.printErrors = False
+
+ # -om causes a copy of the message to be sent to the sender if the sender
+ # appears in an alias expansion. We do not support aliases.
+ if '-om' in argv:
+ raise _unsupportedOption
+
+ # -t causes us to pick the recipients of the message from the To, Cc, and Bcc
+ # headers, and to remove the Bcc header if present.
+ if '-t' in argv:
+ o.recipientsFromHeaders = True
+ o.excludeAddresses = o.to
+ o.to = []
+ else:
+ o.recipientsFromHeaders = False
+ o.exludeAddresses = []
+
+ requiredHeaders = {
+ 'from': [],
+ 'to': [],
+ 'cc': [],
+ 'bcc': [],
+ 'date': [],
+ }
+
+ headers = []
+ buffer = StringIO.StringIO()
+ while 1:
+ write = 1
+ line = sys.stdin.readline()
+ if not line.strip():
+ break
+
+ hdrs = line.split(': ', 1)
+
+ hdr = hdrs[0].lower()
+ if o.recipientsFromHeaders and hdr in ('to', 'cc', 'bcc'):
+ o.to.extend([
+ a[1] for a in rfc822.AddressList(hdrs[1]).addresslist
+ ])
+ if hdr == 'bcc':
+ write = 0
+ elif hdr == 'from':
+ o.sender = rfc822.parseaddr(hdrs[1])[1]
+
+ if hdr in requiredHeaders:
+ requiredHeaders[hdr].append(hdrs[1])
+
+ if write:
+ buffer.write(line)
+
+ if not requiredHeaders['from']:
+ buffer.write('From: %s\r\n' % (o.sender,))
+ if not requiredHeaders['to']:
+ if not o.to:
+ raise SystemExit("No recipients specified.")
+ buffer.write('To: %s\r\n' % (', '.join(o.to),))
+ if not requiredHeaders['date']:
+ buffer.write('Date: %s\r\n' % (smtp.rfc822date(),))
+
+ buffer.write(line)
+
+ if o.recipientsFromHeaders:
+ for a in o.excludeAddresses:
+ try:
+ o.to.remove(a)
+ except:
+ pass
+
+ buffer.seek(0, 0)
+ o.body = StringIO.StringIO(buffer.getvalue() + sys.stdin.read())
+ return o
+
+class Configuration:
+ """
+ @ivar allowUIDs: A list of UIDs which are allowed to send mail.
+ @ivar allowGIDs: A list of GIDs which are allowed to send mail.
+ @ivar denyUIDs: A list of UIDs which are not allowed to send mail.
+ @ivar denyGIDs: A list of GIDs which are not allowed to send mail.
+
+ @type defaultAccess: C{bool}
+ @ivar defaultAccess: C{True} if access will be allowed when no other access
+ control rule matches or C{False} if it will be denied in that case.
+
+ @ivar useraccess: Either C{'allow'} to check C{allowUID} first
+ or C{'deny'} to check C{denyUID} first.
+
+ @ivar groupaccess: Either C{'allow'} to check C{allowGID} first or
+ C{'deny'} to check C{denyGID} first.
+
+ @ivar identities: A C{dict} mapping hostnames to credentials to use when
+ sending mail to that host.
+
+ @ivar smarthost: C{None} or a hostname through which all outgoing mail will
+ be sent.
+
+ @ivar domain: C{None} or the hostname with which to identify ourselves when
+ connecting to an MTA.
+ """
+ def __init__(self):
+ self.allowUIDs = []
+ self.denyUIDs = []
+ self.allowGIDs = []
+ self.denyGIDs = []
+ self.useraccess = 'deny'
+ self.groupaccess= 'deny'
+
+ self.identities = {}
+ self.smarthost = None
+ self.domain = None
+
+ self.defaultAccess = True
+
+
+def loadConfig(path):
+ # [useraccess]
+ # allow=uid1,uid2,...
+ # deny=uid1,uid2,...
+ # order=allow,deny
+ # [groupaccess]
+ # allow=gid1,gid2,...
+ # deny=gid1,gid2,...
+ # order=deny,allow
+ # [identity]
+ # host1=username:password
+ # host2=username:password
+ # [addresses]
+ # smarthost=a.b.c.d
+ # default_domain=x.y.z
+
+ c = Configuration()
+
+ if not os.access(path, os.R_OK):
+ return c
+
+ p = ConfigParser()
+ p.read(path)
+
+ au = c.allowUIDs
+ du = c.denyUIDs
+ ag = c.allowGIDs
+ dg = c.denyGIDs
+ for (section, a, d) in (('useraccess', au, du), ('groupaccess', ag, dg)):
+ if p.has_section(section):
+ for (mode, L) in (('allow', a), ('deny', d)):
+ if p.has_option(section, mode) and p.get(section, mode):
+ for id in p.get(section, mode).split(','):
+ try:
+ id = int(id)
+ except ValueError:
+ log("Illegal %sID in [%s] section: %s", section[0].upper(), section, id)
+ else:
+ L.append(id)
+ order = p.get(section, 'order')
+ order = map(str.split, map(str.lower, order.split(',')))
+ if order[0] == 'allow':
+ setattr(c, section, 'allow')
+ else:
+ setattr(c, section, 'deny')
+
+ if p.has_section('identity'):
+ for (host, up) in p.items('identity'):
+ parts = up.split(':', 1)
+ if len(parts) != 2:
+ log("Illegal entry in [identity] section: %s", up)
+ continue
+ p.identities[host] = parts
+
+ if p.has_section('addresses'):
+ if p.has_option('addresses', 'smarthost'):
+ c.smarthost = p.get('addresses', 'smarthost')
+ if p.has_option('addresses', 'default_domain'):
+ c.domain = p.get('addresses', 'default_domain')
+
+ return c
+
+def success(result):
+ reactor.stop()
+
+failed = None
+def failure(f):
+ global failed
+ reactor.stop()
+ failed = f
+
+def sendmail(host, options, ident):
+ d = smtp.sendmail(host, options.sender, options.to, options.body)
+ d.addCallbacks(success, failure)
+ reactor.run()
+
+def senderror(failure, options):
+ recipient = [options.sender]
+ sender = '"Internally Generated Message (%s)"<postmaster@%s>' % (sys.argv[0], smtp.DNSNAME)
+ error = StringIO.StringIO()
+ failure.printTraceback(file=error)
+ body = StringIO.StringIO(ERROR_FMT % error.getvalue())
+
+ d = smtp.sendmail('localhost', sender, recipient, body)
+ d.addBoth(lambda _: reactor.stop())
+
+def deny(conf):
+ uid = os.getuid()
+ gid = os.getgid()
+
+ if conf.useraccess == 'deny':
+ if uid in conf.denyUIDs:
+ return True
+ if uid in conf.allowUIDs:
+ return False
+ else:
+ if uid in conf.allowUIDs:
+ return False
+ if uid in conf.denyUIDs:
+ return True
+
+ if conf.groupaccess == 'deny':
+ if gid in conf.denyGIDs:
+ return True
+ if gid in conf.allowGIDs:
+ return False
+ else:
+ if gid in conf.allowGIDs:
+ return False
+ if gid in conf.denyGIDs:
+ return True
+
+ return not conf.defaultAccess
+
+def run():
+ o = parseOptions(sys.argv[1:])
+ gConf = loadConfig(GLOBAL_CFG)
+ lConf = loadConfig(LOCAL_CFG)
+
+ if deny(gConf) or deny(lConf):
+ log("Permission denied")
+ return
+
+ host = lConf.smarthost or gConf.smarthost or SMARTHOST
+
+ ident = gConf.identities.copy()
+ ident.update(lConf.identities)
+
+ if lConf.domain:
+ smtp.DNSNAME = lConf.domain
+ elif gConf.domain:
+ smtp.DNSNAME = gConf.domain
+
+ sendmail(host, o, ident)
+
+ if failed:
+ if o.printErrors:
+ failed.printTraceback(file=sys.stderr)
+ raise SystemExit(1)
+ else:
+ senderror(failed, o)
diff --git a/vendor/Twisted-10.0.0/twisted/mail/smtp.py b/vendor/Twisted-10.0.0/twisted/mail/smtp.py
new file mode 100644
index 0000000000..a4cb2cb892
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/smtp.py
@@ -0,0 +1,2023 @@
+# -*- test-case-name: twisted.mail.test.test_smtp -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Simple Mail Transfer Protocol implementation.
+"""
+
+import time, re, base64, types, socket, os, random, hmac
+import MimeWriter, tempfile, rfc822
+import warnings
+import binascii
+from email.base64MIME import encode as encode_base64
+
+from zope.interface import implements, Interface
+
+from twisted.copyright import longversion
+from twisted.protocols import basic
+from twisted.protocols import policies
+from twisted.internet import protocol
+from twisted.internet import defer
+from twisted.internet import error
+from twisted.internet import reactor
+from twisted.internet.interfaces import ITLSTransport
+from twisted.python import log
+from twisted.python import util
+from twisted.python import failure
+
+from twisted import cred
+import twisted.cred.checkers
+import twisted.cred.credentials
+from twisted.python.runtime import platform
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+# Cache the hostname (XXX Yes - this is broken)
+if platform.isMacOSX():
+ # On OS X, getfqdn() is ridiculously slow - use the
+ # probably-identical-but-sometimes-not gethostname() there.
+ DNSNAME = socket.gethostname()
+else:
+ DNSNAME = socket.getfqdn()
+
+# Used for fast success code lookup
+SUCCESS = dict(map(None, range(200, 300), []))
+
+class IMessageDelivery(Interface):
+ def receivedHeader(helo, origin, recipients):
+ """
+ Generate the Received header for a message
+
+ @type helo: C{(str, str)}
+ @param helo: The argument to the HELO command and the client's IP
+ address.
+
+ @type origin: C{Address}
+ @param origin: The address the message is from
+
+ @type recipients: C{list} of L{User}
+ @param recipients: A list of the addresses for which this message
+ is bound.
+
+ @rtype: C{str}
+ @return: The full \"Received\" header string.
+ """
+
+ def validateTo(user):
+ """
+ Validate the address for which the message is destined.
+
+ @type user: C{User}
+ @param user: The address to validate.
+
+ @rtype: no-argument callable
+ @return: A C{Deferred} which becomes, or a callable which
+ takes no arguments and returns an object implementing C{IMessage}.
+ This will be called and the returned object used to deliver the
+ message when it arrives.
+
+ @raise SMTPBadRcpt: Raised if messages to the address are
+ not to be accepted.
+ """
+
+ def validateFrom(helo, origin):
+ """
+ Validate the address from which the message originates.
+
+ @type helo: C{(str, str)}
+ @param helo: The argument to the HELO command and the client's IP
+ address.
+
+ @type origin: C{Address}
+ @param origin: The address the message is from
+
+ @rtype: C{Deferred} or C{Address}
+ @return: C{origin} or a C{Deferred} whose callback will be
+ passed C{origin}.
+
+ @raise SMTPBadSender: Raised of messages from this address are
+ not to be accepted.
+ """
+
+class IMessageDeliveryFactory(Interface):
+ """An alternate interface to implement for handling message delivery.
+
+ It is useful to implement this interface instead of L{IMessageDelivery}
+ directly because it allows the implementor to distinguish between
+ different messages delivery over the same connection. This can be
+ used to optimize delivery of a single message to multiple recipients,
+ something which cannot be done by L{IMessageDelivery} implementors
+ due to their lack of information.
+ """
+ def getMessageDelivery():
+ """Return an L{IMessageDelivery} object.
+
+ This will be called once per message.
+ """
+
+class SMTPError(Exception):
+ pass
+
+
+
+class SMTPClientError(SMTPError):
+ """Base class for SMTP client errors.
+ """
+ def __init__(self, code, resp, log=None, addresses=None, isFatal=False, retry=False):
+ """
+ @param code: The SMTP response code associated with this error.
+ @param resp: The string response associated with this error.
+
+ @param log: A string log of the exchange leading up to and including
+ the error.
+ @type log: L{str}
+
+ @param isFatal: A boolean indicating whether this connection can
+ proceed or not. If True, the connection will be dropped.
+
+ @param retry: A boolean indicating whether the delivery should be
+ retried. If True and the factory indicates further retries are
+ desirable, they will be attempted, otherwise the delivery will
+ be failed.
+ """
+ self.code = code
+ self.resp = resp
+ self.log = log
+ self.addresses = addresses
+ self.isFatal = isFatal
+ self.retry = retry
+
+
+ def __str__(self):
+ if self.code > 0:
+ res = ["%.3d %s" % (self.code, self.resp)]
+ else:
+ res = [self.resp]
+ if self.log:
+ res.append(self.log)
+ res.append('')
+ return '\n'.join(res)
+
+
+class ESMTPClientError(SMTPClientError):
+ """Base class for ESMTP client errors.
+ """
+
+class EHLORequiredError(ESMTPClientError):
+ """The server does not support EHLO.
+
+ This is considered a non-fatal error (the connection will not be
+ dropped).
+ """
+
+class AUTHRequiredError(ESMTPClientError):
+ """Authentication was required but the server does not support it.
+
+ This is considered a non-fatal error (the connection will not be
+ dropped).
+ """
+
+class TLSRequiredError(ESMTPClientError):
+ """Transport security was required but the server does not support it.
+
+ This is considered a non-fatal error (the connection will not be
+ dropped).
+ """
+
+class AUTHDeclinedError(ESMTPClientError):
+ """The server rejected our credentials.
+
+ Either the username, password, or challenge response
+ given to the server was rejected.
+
+ This is considered a non-fatal error (the connection will not be
+ dropped).
+ """
+
+class AuthenticationError(ESMTPClientError):
+ """An error ocurred while authenticating.
+
+ Either the server rejected our request for authentication or the
+ challenge received was malformed.
+
+ This is considered a non-fatal error (the connection will not be
+ dropped).
+ """
+
+class TLSError(ESMTPClientError):
+ """An error occurred while negiotiating for transport security.
+
+ This is considered a non-fatal error (the connection will not be
+ dropped).
+ """
+
+class SMTPConnectError(SMTPClientError):
+ """Failed to connect to the mail exchange host.
+
+ This is considered a fatal error. A retry will be made.
+ """
+ def __init__(self, code, resp, log=None, addresses=None, isFatal=True, retry=True):
+ SMTPClientError.__init__(self, code, resp, log, addresses, isFatal, retry)
+
+class SMTPTimeoutError(SMTPClientError):
+ """Failed to receive a response from the server in the expected time period.
+
+ This is considered a fatal error. A retry will be made.
+ """
+ def __init__(self, code, resp, log=None, addresses=None, isFatal=True, retry=True):
+ SMTPClientError.__init__(self, code, resp, log, addresses, isFatal, retry)
+
+class SMTPProtocolError(SMTPClientError):
+ """The server sent a mangled response.
+
+ This is considered a fatal error. A retry will not be made.
+ """
+ def __init__(self, code, resp, log=None, addresses=None, isFatal=True, retry=False):
+ SMTPClientError.__init__(self, code, resp, log, addresses, isFatal, retry)
+
+class SMTPDeliveryError(SMTPClientError):
+ """Indicates that a delivery attempt has had an error.
+ """
+
+class SMTPServerError(SMTPError):
+ def __init__(self, code, resp):
+ self.code = code
+ self.resp = resp
+
+ def __str__(self):
+ return "%.3d %s" % (self.code, self.resp)
+
+class SMTPAddressError(SMTPServerError):
+ def __init__(self, addr, code, resp):
+ SMTPServerError.__init__(self, code, resp)
+ self.addr = Address(addr)
+
+ def __str__(self):
+ return "%.3d <%s>... %s" % (self.code, self.addr, self.resp)
+
+class SMTPBadRcpt(SMTPAddressError):
+ def __init__(self, addr, code=550,
+ resp='Cannot receive for specified address'):
+ SMTPAddressError.__init__(self, addr, code, resp)
+
+class SMTPBadSender(SMTPAddressError):
+ def __init__(self, addr, code=550, resp='Sender not acceptable'):
+ SMTPAddressError.__init__(self, addr, code, resp)
+
+def rfc822date(timeinfo=None,local=1):
+ """
+ Format an RFC-2822 compliant date string.
+
+ @param timeinfo: (optional) A sequence as returned by C{time.localtime()}
+ or C{time.gmtime()}. Default is now.
+ @param local: (optional) Indicates if the supplied time is local or
+ universal time, or if no time is given, whether now should be local or
+ universal time. Default is local, as suggested (SHOULD) by rfc-2822.
+
+ @returns: A string representing the time and date in RFC-2822 format.
+ """
+ if not timeinfo:
+ if local:
+ timeinfo = time.localtime()
+ else:
+ timeinfo = time.gmtime()
+ if local:
+ if timeinfo[8]:
+ # DST
+ tz = -time.altzone
+ else:
+ tz = -time.timezone
+
+ (tzhr, tzmin) = divmod(abs(tz), 3600)
+ if tz:
+ tzhr *= int(abs(tz)/tz)
+ (tzmin, tzsec) = divmod(tzmin, 60)
+ else:
+ (tzhr, tzmin) = (0,0)
+
+ return "%s, %02d %s %04d %02d:%02d:%02d %+03d%02d" % (
+ ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][timeinfo[6]],
+ timeinfo[2],
+ ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][timeinfo[1] - 1],
+ timeinfo[0], timeinfo[3], timeinfo[4], timeinfo[5],
+ tzhr, tzmin)
+
+def idGenerator():
+ i = 0
+ while True:
+ yield i
+ i += 1
+
+def messageid(uniq=None, N=idGenerator().next):
+ """Return a globally unique random string in RFC 2822 Message-ID format
+
+ <datetime.pid.random@host.dom.ain>
+
+ Optional uniq string will be added to strenghten uniqueness if given.
+ """
+ datetime = time.strftime('%Y%m%d%H%M%S', time.gmtime())
+ pid = os.getpid()
+ rand = random.randrange(2**31L-1)
+ if uniq is None:
+ uniq = ''
+ else:
+ uniq = '.' + uniq
+
+ return '<%s.%s.%s%s.%s@%s>' % (datetime, pid, rand, uniq, N(), DNSNAME)
+
+def quoteaddr(addr):
+ """Turn an email address, possibly with realname part etc, into
+ a form suitable for and SMTP envelope.
+ """
+
+ if isinstance(addr, Address):
+ return '<%s>' % str(addr)
+
+ res = rfc822.parseaddr(addr)
+
+ if res == (None, None):
+ # It didn't parse, use it as-is
+ return '<%s>' % str(addr)
+ else:
+ return '<%s>' % str(res[1])
+
+COMMAND, DATA, AUTH = 'COMMAND', 'DATA', 'AUTH'
+
+class AddressError(SMTPError):
+ "Parse error in address"
+
+# Character classes for parsing addresses
+atom = r"[-A-Za-z0-9!\#$%&'*+/=?^_`{|}~]"
+
+class Address:
+ """Parse and hold an RFC 2821 address.
+
+ Source routes are stipped and ignored, UUCP-style bang-paths
+ and %-style routing are not parsed.
+
+ @type domain: C{str}
+ @ivar domain: The domain within which this address resides.
+
+ @type local: C{str}
+ @ivar local: The local (\"user\") portion of this address.
+ """
+
+ tstring = re.compile(r'''( # A string of
+ (?:"[^"]*" # quoted string
+ |\\. # backslash-escaped characted
+ |''' + atom + r''' # atom character
+ )+|.) # or any single character''',re.X)
+ atomre = re.compile(atom) # match any one atom character
+
+ def __init__(self, addr, defaultDomain=None):
+ if isinstance(addr, User):
+ addr = addr.dest
+ if isinstance(addr, Address):
+ self.__dict__ = addr.__dict__.copy()
+ return
+ elif not isinstance(addr, types.StringTypes):
+ addr = str(addr)
+ self.addrstr = addr
+
+ # Tokenize
+ atl = filter(None,self.tstring.split(addr))
+
+ local = []
+ domain = []
+
+ while atl:
+ if atl[0] == '<':
+ if atl[-1] != '>':
+ raise AddressError, "Unbalanced <>"
+ atl = atl[1:-1]
+ elif atl[0] == '@':
+ atl = atl[1:]
+ if not local:
+ # Source route
+ while atl and atl[0] != ':':
+ # remove it
+ atl = atl[1:]
+ if not atl:
+ raise AddressError, "Malformed source route"
+ atl = atl[1:] # remove :
+ elif domain:
+ raise AddressError, "Too many @"
+ else:
+ # Now in domain
+ domain = ['']
+ elif len(atl[0]) == 1 and not self.atomre.match(atl[0]) and atl[0] != '.':
+ raise AddressError, "Parse error at %r of %r" % (atl[0], (addr, atl))
+ else:
+ if not domain:
+ local.append(atl[0])
+ else:
+ domain.append(atl[0])
+ atl = atl[1:]
+
+ self.local = ''.join(local)
+ self.domain = ''.join(domain)
+ if self.local != '' and self.domain == '':
+ if defaultDomain is None:
+ defaultDomain = DNSNAME
+ self.domain = defaultDomain
+
+ dequotebs = re.compile(r'\\(.)')
+
+ def dequote(self,addr):
+ """Remove RFC-2821 quotes from address."""
+ res = []
+
+ atl = filter(None,self.tstring.split(str(addr)))
+
+ for t in atl:
+ if t[0] == '"' and t[-1] == '"':
+ res.append(t[1:-1])
+ elif '\\' in t:
+ res.append(self.dequotebs.sub(r'\1',t))
+ else:
+ res.append(t)
+
+ return ''.join(res)
+
+ def __str__(self):
+ if self.local or self.domain:
+ return '@'.join((self.local, self.domain))
+ else:
+ return ''
+
+ def __repr__(self):
+ return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
+ repr(str(self)))
+
+class User:
+ """Hold information about and SMTP message recipient,
+ including information on where the message came from
+ """
+
+ def __init__(self, destination, helo, protocol, orig):
+ host = getattr(protocol, 'host', None)
+ self.dest = Address(destination, host)
+ self.helo = helo
+ self.protocol = protocol
+ if isinstance(orig, Address):
+ self.orig = orig
+ else:
+ self.orig = Address(orig, host)
+
+ def __getstate__(self):
+ """Helper for pickle.
+
+ protocol isn't picklabe, but we want User to be, so skip it in
+ the pickle.
+ """
+ return { 'dest' : self.dest,
+ 'helo' : self.helo,
+ 'protocol' : None,
+ 'orig' : self.orig }
+
+ def __str__(self):
+ return str(self.dest)
+
+class IMessage(Interface):
+ """Interface definition for messages that can be sent via SMTP."""
+
+ def lineReceived(line):
+ """handle another line"""
+
+ def eomReceived():
+ """handle end of message
+
+ return a deferred. The deferred should be called with either:
+ callback(string) or errback(error)
+ """
+
+ def connectionLost():
+ """handle message truncated
+
+ semantics should be to discard the message
+ """
+
+class SMTP(basic.LineOnlyReceiver, policies.TimeoutMixin):
+ """SMTP server-side protocol."""
+
+ timeout = 600
+ host = DNSNAME
+ portal = None
+
+ # Control whether we log SMTP events
+ noisy = True
+
+ # A factory for IMessageDelivery objects. If an
+ # avatar implementing IMessageDeliveryFactory can
+ # be acquired from the portal, it will be used to
+ # create a new IMessageDelivery object for each
+ # message which is received.
+ deliveryFactory = None
+
+ # An IMessageDelivery object. A new instance is
+ # used for each message received if we can get an
+ # IMessageDeliveryFactory from the portal. Otherwise,
+ # a single instance is used throughout the lifetime
+ # of the connection.
+ delivery = None
+
+ # Cred cleanup function.
+ _onLogout = None
+
+ def __init__(self, delivery=None, deliveryFactory=None):
+ self.mode = COMMAND
+ self._from = None
+ self._helo = None
+ self._to = []
+ self.delivery = delivery
+ self.deliveryFactory = deliveryFactory
+
+ def timeoutConnection(self):
+ msg = '%s Timeout. Try talking faster next time!' % (self.host,)
+ self.sendCode(421, msg)
+ self.transport.loseConnection()
+
+ def greeting(self):
+ return '%s NO UCE NO UBE NO RELAY PROBES' % (self.host,)
+
+ def connectionMade(self):
+ # Ensure user-code always gets something sane for _helo
+ peer = self.transport.getPeer()
+ try:
+ host = peer.host
+ except AttributeError: # not an IPv4Address
+ host = str(peer)
+ self._helo = (None, host)
+ self.sendCode(220, self.greeting())
+ self.setTimeout(self.timeout)
+
+ def sendCode(self, code, message=''):
+ "Send an SMTP code with a message."
+ lines = message.splitlines()
+ lastline = lines[-1:]
+ for line in lines[:-1]:
+ self.sendLine('%3.3d-%s' % (code, line))
+ self.sendLine('%3.3d %s' % (code,
+ lastline and lastline[0] or ''))
+
+ def lineReceived(self, line):
+ self.resetTimeout()
+ return getattr(self, 'state_' + self.mode)(line)
+
+ def state_COMMAND(self, line):
+ # Ignore leading and trailing whitespace, as well as an arbitrary
+ # amount of whitespace between the command and its argument, though
+ # it is not required by the protocol, for it is a nice thing to do.
+ line = line.strip()
+
+ parts = line.split(None, 1)
+ if parts:
+ method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
+ if len(parts) == 2:
+ method(parts[1])
+ else:
+ method('')
+ else:
+ self.sendSyntaxError()
+
+ def sendSyntaxError(self):
+ self.sendCode(500, 'Error: bad syntax')
+
+ def lookupMethod(self, command):
+ return getattr(self, 'do_' + command.upper(), None)
+
+ def lineLengthExceeded(self, line):
+ if self.mode is DATA:
+ for message in self.__messages:
+ message.connectionLost()
+ self.mode = COMMAND
+ del self.__messages
+ self.sendCode(500, 'Line too long')
+
+ def do_UNKNOWN(self, rest):
+ self.sendCode(500, 'Command not implemented')
+
+ def do_HELO(self, rest):
+ peer = self.transport.getPeer()
+ try:
+ host = peer.host
+ except AttributeError:
+ host = str(peer)
+ self._helo = (rest, host)
+ self._from = None
+ self._to = []
+ self.sendCode(250, '%s Hello %s, nice to meet you' % (self.host, host))
+
+ def do_QUIT(self, rest):
+ self.sendCode(221, 'See you later')
+ self.transport.loseConnection()
+
+ # A string of quoted strings, backslash-escaped character or
+ # atom characters + '@.,:'
+ qstring = r'("[^"]*"|\\.|' + atom + r'|[@.,:])+'
+
+ mail_re = re.compile(r'''\s*FROM:\s*(?P<path><> # Empty <>
+ |<''' + qstring + r'''> # <addr>
+ |''' + qstring + r''' # addr
+ )\s*(\s(?P<opts>.*))? # Optional WS + ESMTP options
+ $''',re.I|re.X)
+ rcpt_re = re.compile(r'\s*TO:\s*(?P<path><' + qstring + r'''> # <addr>
+ |''' + qstring + r''' # addr
+ )\s*(\s(?P<opts>.*))? # Optional WS + ESMTP options
+ $''',re.I|re.X)
+
+ def do_MAIL(self, rest):
+ if self._from:
+ self.sendCode(503,"Only one sender per message, please")
+ return
+ # Clear old recipient list
+ self._to = []
+ m = self.mail_re.match(rest)
+ if not m:
+ self.sendCode(501, "Syntax error")
+ return
+
+ try:
+ addr = Address(m.group('path'), self.host)
+ except AddressError, e:
+ self.sendCode(553, str(e))
+ return
+
+ validated = defer.maybeDeferred(self.validateFrom, self._helo, addr)
+ validated.addCallbacks(self._cbFromValidate, self._ebFromValidate)
+
+
+ def _cbFromValidate(self, from_, code=250, msg='Sender address accepted'):
+ self._from = from_
+ self.sendCode(code, msg)
+
+
+ def _ebFromValidate(self, failure):
+ if failure.check(SMTPBadSender):
+ self.sendCode(failure.value.code,
+ 'Cannot receive from specified address %s: %s'
+ % (quoteaddr(failure.value.addr), failure.value.resp))
+ elif failure.check(SMTPServerError):
+ self.sendCode(failure.value.code, failure.value.resp)
+ else:
+ log.err(failure, "SMTP sender validation failure")
+ self.sendCode(
+ 451,
+ 'Requested action aborted: local error in processing')
+
+
+ def do_RCPT(self, rest):
+ if not self._from:
+ self.sendCode(503, "Must have sender before recipient")
+ return
+ m = self.rcpt_re.match(rest)
+ if not m:
+ self.sendCode(501, "Syntax error")
+ return
+
+ try:
+ user = User(m.group('path'), self._helo, self, self._from)
+ except AddressError, e:
+ self.sendCode(553, str(e))
+ return
+
+ d = defer.maybeDeferred(self.validateTo, user)
+ d.addCallbacks(
+ self._cbToValidate,
+ self._ebToValidate,
+ callbackArgs=(user,)
+ )
+
+ def _cbToValidate(self, to, user=None, code=250, msg='Recipient address accepted'):
+ if user is None:
+ user = to
+ self._to.append((user, to))
+ self.sendCode(code, msg)
+
+ def _ebToValidate(self, failure):
+ if failure.check(SMTPBadRcpt, SMTPServerError):
+ self.sendCode(failure.value.code, failure.value.resp)
+ else:
+ log.err(failure)
+ self.sendCode(
+ 451,
+ 'Requested action aborted: local error in processing'
+ )
+
+ def _disconnect(self, msgs):
+ for msg in msgs:
+ try:
+ msg.connectionLost()
+ except:
+ log.msg("msg raised exception from connectionLost")
+ log.err()
+
+ def do_DATA(self, rest):
+ if self._from is None or (not self._to):
+ self.sendCode(503, 'Must have valid receiver and originator')
+ return
+ self.mode = DATA
+ helo, origin = self._helo, self._from
+ recipients = self._to
+
+ self._from = None
+ self._to = []
+ self.datafailed = None
+
+ msgs = []
+ for (user, msgFunc) in recipients:
+ try:
+ msg = msgFunc()
+ rcvdhdr = self.receivedHeader(helo, origin, [user])
+ if rcvdhdr:
+ msg.lineReceived(rcvdhdr)
+ msgs.append(msg)
+ except SMTPServerError, e:
+ self.sendCode(e.code, e.resp)
+ self.mode = COMMAND
+ self._disconnect(msgs)
+ return
+ except:
+ log.err()
+ self.sendCode(550, "Internal server error")
+ self.mode = COMMAND
+ self._disconnect(msgs)
+ return
+ self.__messages = msgs
+
+ self.__inheader = self.__inbody = 0
+ self.sendCode(354, 'Continue')
+
+ if self.noisy:
+ fmt = 'Receiving message for delivery: from=%s to=%s'
+ log.msg(fmt % (origin, [str(u) for (u, f) in recipients]))
+
+ def connectionLost(self, reason):
+ # self.sendCode(421, 'Dropping connection.') # This does nothing...
+ # Ideally, if we (rather than the other side) lose the connection,
+ # we should be able to tell the other side that we are going away.
+ # RFC-2821 requires that we try.
+ if self.mode is DATA:
+ try:
+ for message in self.__messages:
+ try:
+ message.connectionLost()
+ except:
+ log.err()
+ del self.__messages
+ except AttributeError:
+ pass
+ if self._onLogout:
+ self._onLogout()
+ self._onLogout = None
+ self.setTimeout(None)
+
+ def do_RSET(self, rest):
+ self._from = None
+ self._to = []
+ self.sendCode(250, 'I remember nothing.')
+
+ def dataLineReceived(self, line):
+ if line[:1] == '.':
+ if line == '.':
+ self.mode = COMMAND
+ if self.datafailed:
+ self.sendCode(self.datafailed.code,
+ self.datafailed.resp)
+ return
+ if not self.__messages:
+ self._messageHandled("thrown away")
+ return
+ defer.DeferredList([
+ m.eomReceived() for m in self.__messages
+ ], consumeErrors=True).addCallback(self._messageHandled
+ )
+ del self.__messages
+ return
+ line = line[1:]
+
+ if self.datafailed:
+ return
+
+ try:
+ # Add a blank line between the generated Received:-header
+ # and the message body if the message comes in without any
+ # headers
+ if not self.__inheader and not self.__inbody:
+ if ':' in line:
+ self.__inheader = 1
+ elif line:
+ for message in self.__messages:
+ message.lineReceived('')
+ self.__inbody = 1
+
+ if not line:
+ self.__inbody = 1
+
+ for message in self.__messages:
+ message.lineReceived(line)
+ except SMTPServerError, e:
+ self.datafailed = e
+ for message in self.__messages:
+ message.connectionLost()
+ state_DATA = dataLineReceived
+
+ def _messageHandled(self, resultList):
+ failures = 0
+ for (success, result) in resultList:
+ if not success:
+ failures += 1
+ log.err(result)
+ if failures:
+ msg = 'Could not send e-mail'
+ L = len(resultList)
+ if L > 1:
+ msg += ' (%d failures out of %d recipients)' % (failures, L)
+ self.sendCode(550, msg)
+ else:
+ self.sendCode(250, 'Delivery in progress')
+
+
+ def _cbAnonymousAuthentication(self, (iface, avatar, logout)):
+ """
+ Save the state resulting from a successful anonymous cred login.
+ """
+ if issubclass(iface, IMessageDeliveryFactory):
+ self.deliveryFactory = avatar
+ self.delivery = None
+ elif issubclass(iface, IMessageDelivery):
+ self.deliveryFactory = None
+ self.delivery = avatar
+ else:
+ raise RuntimeError("%s is not a supported interface" % (iface.__name__,))
+ self._onLogout = logout
+ self.challenger = None
+
+
+ # overridable methods:
+ def validateFrom(self, helo, origin):
+ """
+ Validate the address from which the message originates.
+
+ @type helo: C{(str, str)}
+ @param helo: The argument to the HELO command and the client's IP
+ address.
+
+ @type origin: C{Address}
+ @param origin: The address the message is from
+
+ @rtype: C{Deferred} or C{Address}
+ @return: C{origin} or a C{Deferred} whose callback will be
+ passed C{origin}.
+
+ @raise SMTPBadSender: Raised of messages from this address are
+ not to be accepted.
+ """
+ if self.deliveryFactory is not None:
+ self.delivery = self.deliveryFactory.getMessageDelivery()
+
+ if self.delivery is not None:
+ return defer.maybeDeferred(self.delivery.validateFrom,
+ helo, origin)
+
+ # No login has been performed, no default delivery object has been
+ # provided: try to perform an anonymous login and then invoke this
+ # method again.
+ if self.portal:
+
+ result = self.portal.login(
+ cred.credentials.Anonymous(),
+ None,
+ IMessageDeliveryFactory, IMessageDelivery)
+
+ def ebAuthentication(err):
+ """
+ Translate cred exceptions into SMTP exceptions so that the
+ protocol code which invokes C{validateFrom} can properly report
+ the failure.
+ """
+ if err.check(cred.error.UnauthorizedLogin):
+ exc = SMTPBadSender(origin)
+ elif err.check(cred.error.UnhandledCredentials):
+ exc = SMTPBadSender(
+ origin, resp="Unauthenticated senders not allowed")
+ else:
+ return err
+ return defer.fail(exc)
+
+ result.addCallbacks(
+ self._cbAnonymousAuthentication, ebAuthentication)
+
+ def continueValidation(ignored):
+ """
+ Re-attempt from address validation.
+ """
+ return self.validateFrom(helo, origin)
+
+ result.addCallback(continueValidation)
+ return result
+
+ raise SMTPBadSender(origin)
+
+
+ def validateTo(self, user):
+ """
+ Validate the address for which the message is destined.
+
+ @type user: C{User}
+ @param user: The address to validate.
+
+ @rtype: no-argument callable
+ @return: A C{Deferred} which becomes, or a callable which
+ takes no arguments and returns an object implementing C{IMessage}.
+ This will be called and the returned object used to deliver the
+ message when it arrives.
+
+ @raise SMTPBadRcpt: Raised if messages to the address are
+ not to be accepted.
+ """
+ if self.delivery is not None:
+ return self.delivery.validateTo(user)
+ raise SMTPBadRcpt(user)
+
+ def receivedHeader(self, helo, origin, recipients):
+ if self.delivery is not None:
+ return self.delivery.receivedHeader(helo, origin, recipients)
+
+ heloStr = ""
+ if helo[0]:
+ heloStr = " helo=%s" % (helo[0],)
+ domain = self.transport.getHost().host
+ from_ = "from %s ([%s]%s)" % (helo[0], helo[1], heloStr)
+ by = "by %s with %s (%s)" % (domain,
+ self.__class__.__name__,
+ longversion)
+ for_ = "for %s; %s" % (' '.join(map(str, recipients)),
+ rfc822date())
+ return "Received: %s\n\t%s\n\t%s" % (from_, by, for_)
+
+ def startMessage(self, recipients):
+ if self.delivery:
+ return self.delivery.startMessage(recipients)
+ return []
+
+
+class SMTPFactory(protocol.ServerFactory):
+ """Factory for SMTP."""
+
+ # override in instances or subclasses
+ domain = DNSNAME
+ timeout = 600
+ protocol = SMTP
+
+ portal = None
+
+ def __init__(self, portal = None):
+ self.portal = portal
+
+ def buildProtocol(self, addr):
+ p = protocol.ServerFactory.buildProtocol(self, addr)
+ p.portal = self.portal
+ p.host = self.domain
+ return p
+
+class SMTPClient(basic.LineReceiver, policies.TimeoutMixin):
+ """
+ SMTP client for sending emails.
+
+ After the client has connected to the SMTP server, it repeatedly calls
+ L{SMTPClient.getMailFrom}, L{SMTPClient.getMailTo} and
+ L{SMTPClient.getMailData} and uses this information to send an email.
+ It then calls L{SMTPClient.getMailFrom} again; if it returns C{None}, the
+ client will disconnect, otherwise it will continue as normal i.e. call
+ L{SMTPClient.getMailTo} and L{SMTPClient.getMailData} and send a new email.
+ """
+
+ # If enabled then log SMTP client server communication
+ debug = True
+
+ # Number of seconds to wait before timing out a connection. If
+ # None, perform no timeout checking.
+ timeout = None
+
+ def __init__(self, identity, logsize=10):
+ self.identity = identity or ''
+ self.toAddressesResult = []
+ self.successAddresses = []
+ self._from = None
+ self.resp = []
+ self.code = -1
+ self.log = util.LineLog(logsize)
+
+ def sendLine(self, line):
+ # Log sendLine only if you are in debug mode for performance
+ if self.debug:
+ self.log.append('>>> ' + line)
+
+ basic.LineReceiver.sendLine(self,line)
+
+ def connectionMade(self):
+ self.setTimeout(self.timeout)
+
+ self._expected = [ 220 ]
+ self._okresponse = self.smtpState_helo
+ self._failresponse = self.smtpConnectionFailed
+
+ def connectionLost(self, reason=protocol.connectionDone):
+ """We are no longer connected"""
+ self.setTimeout(None)
+ self.mailFile = None
+
+ def timeoutConnection(self):
+ self.sendError(
+ SMTPTimeoutError(
+ -1, "Timeout waiting for SMTP server response",
+ self.log.str()))
+
+ def lineReceived(self, line):
+ self.resetTimeout()
+
+ # Log lineReceived only if you are in debug mode for performance
+ if self.debug:
+ self.log.append('<<< ' + line)
+
+ why = None
+
+ try:
+ self.code = int(line[:3])
+ except ValueError:
+ # This is a fatal error and will disconnect the transport lineReceived will not be called again
+ self.sendError(SMTPProtocolError(-1, "Invalid response from SMTP server: %s" % line, self.log.str()))
+ return
+
+ if line[0] == '0':
+ # Verbose informational message, ignore it
+ return
+
+ self.resp.append(line[4:])
+
+ if line[3:4] == '-':
+ # continuation
+ return
+
+ if self.code in self._expected:
+ why = self._okresponse(self.code,'\n'.join(self.resp))
+ else:
+ why = self._failresponse(self.code,'\n'.join(self.resp))
+
+ self.code = -1
+ self.resp = []
+ return why
+
+ def smtpConnectionFailed(self, code, resp):
+ self.sendError(SMTPConnectError(code, resp, self.log.str()))
+
+ def smtpTransferFailed(self, code, resp):
+ if code < 0:
+ self.sendError(SMTPProtocolError(code, resp, self.log.str()))
+ else:
+ self.smtpState_msgSent(code, resp)
+
+ def smtpState_helo(self, code, resp):
+ self.sendLine('HELO ' + self.identity)
+ self._expected = SUCCESS
+ self._okresponse = self.smtpState_from
+
+ def smtpState_from(self, code, resp):
+ self._from = self.getMailFrom()
+ self._failresponse = self.smtpTransferFailed
+ if self._from is not None:
+ self.sendLine('MAIL FROM:%s' % quoteaddr(self._from))
+ self._expected = [250]
+ self._okresponse = self.smtpState_to
+ else:
+ # All messages have been sent, disconnect
+ self._disconnectFromServer()
+
+ def smtpState_disconnect(self, code, resp):
+ self.transport.loseConnection()
+
+ def smtpState_to(self, code, resp):
+ self.toAddresses = iter(self.getMailTo())
+ self.toAddressesResult = []
+ self.successAddresses = []
+ self._okresponse = self.smtpState_toOrData
+ self._expected = xrange(0,1000)
+ self.lastAddress = None
+ return self.smtpState_toOrData(0, '')
+
+ def smtpState_toOrData(self, code, resp):
+ if self.lastAddress is not None:
+ self.toAddressesResult.append((self.lastAddress, code, resp))
+ if code in SUCCESS:
+ self.successAddresses.append(self.lastAddress)
+ try:
+ self.lastAddress = self.toAddresses.next()
+ except StopIteration:
+ if self.successAddresses:
+ self.sendLine('DATA')
+ self._expected = [ 354 ]
+ self._okresponse = self.smtpState_data
+ else:
+ return self.smtpState_msgSent(code,'No recipients accepted')
+ else:
+ self.sendLine('RCPT TO:%s' % quoteaddr(self.lastAddress))
+
+ def smtpState_data(self, code, resp):
+ s = basic.FileSender()
+ d = s.beginFileTransfer(
+ self.getMailData(), self.transport, self.transformChunk)
+ def ebTransfer(err):
+ self.sendError(err.value)
+ d.addCallbacks(self.finishedFileTransfer, ebTransfer)
+ self._expected = SUCCESS
+ self._okresponse = self.smtpState_msgSent
+
+
+ def smtpState_msgSent(self, code, resp):
+ if self._from is not None:
+ self.sentMail(code, resp, len(self.successAddresses),
+ self.toAddressesResult, self.log)
+
+ self.toAddressesResult = []
+ self._from = None
+ self.sendLine('RSET')
+ self._expected = SUCCESS
+ self._okresponse = self.smtpState_from
+
+ ##
+ ## Helpers for FileSender
+ ##
+ def transformChunk(self, chunk):
+ """
+ Perform the necessary local to network newline conversion and escape
+ leading periods.
+
+ This method also resets the idle timeout so that as long as process is
+ being made sending the message body, the client will not time out.
+ """
+ self.resetTimeout()
+ return chunk.replace('\n', '\r\n').replace('\r\n.', '\r\n..')
+
+ def finishedFileTransfer(self, lastsent):
+ if lastsent != '\n':
+ line = '\r\n.'
+ else:
+ line = '.'
+ self.sendLine(line)
+
+ ##
+ # these methods should be overriden in subclasses
+ def getMailFrom(self):
+ """Return the email address the mail is from."""
+ raise NotImplementedError
+
+ def getMailTo(self):
+ """Return a list of emails to send to."""
+ raise NotImplementedError
+
+ def getMailData(self):
+ """Return file-like object containing data of message to be sent.
+
+ Lines in the file should be delimited by '\\n'.
+ """
+ raise NotImplementedError
+
+ def sendError(self, exc):
+ """
+ If an error occurs before a mail message is sent sendError will be
+ called. This base class method sends a QUIT if the error is
+ non-fatal and disconnects the connection.
+
+ @param exc: The SMTPClientError (or child class) raised
+ @type exc: C{SMTPClientError}
+ """
+ if isinstance(exc, SMTPClientError) and not exc.isFatal:
+ self._disconnectFromServer()
+ else:
+ # If the error was fatal then the communication channel with the
+ # SMTP Server is broken so just close the transport connection
+ self.smtpState_disconnect(-1, None)
+
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ """Called when an attempt to send an email is completed.
+
+ If some addresses were accepted, code and resp are the response
+ to the DATA command. If no addresses were accepted, code is -1
+ and resp is an informative message.
+
+ @param code: the code returned by the SMTP Server
+ @param resp: The string response returned from the SMTP Server
+ @param numOK: the number of addresses accepted by the remote host.
+ @param addresses: is a list of tuples (address, code, resp) listing
+ the response to each RCPT command.
+ @param log: is the SMTP session log
+ """
+ raise NotImplementedError
+
+ def _disconnectFromServer(self):
+ self._expected = xrange(0, 1000)
+ self._okresponse = self.smtpState_disconnect
+ self.sendLine('QUIT')
+
+
+
+class ESMTPClient(SMTPClient):
+ # Fall back to HELO if the server does not support EHLO
+ heloFallback = True
+
+ # Refuse to proceed if authentication cannot be performed
+ requireAuthentication = False
+
+ # Refuse to proceed if TLS is not available
+ requireTransportSecurity = False
+
+ # Indicate whether or not our transport can be considered secure.
+ tlsMode = False
+
+ # ClientContextFactory to use for STARTTLS
+ context = None
+
+ def __init__(self, secret, contextFactory=None, *args, **kw):
+ SMTPClient.__init__(self, *args, **kw)
+ self.authenticators = []
+ self.secret = secret
+ self.context = contextFactory
+ self.tlsMode = False
+
+
+ def esmtpEHLORequired(self, code=-1, resp=None):
+ self.sendError(EHLORequiredError(502, "Server does not support ESMTP Authentication", self.log.str()))
+
+
+ def esmtpAUTHRequired(self, code=-1, resp=None):
+ tmp = []
+
+ for a in self.authenticators:
+ tmp.append(a.getName().upper())
+
+ auth = "[%s]" % ', '.join(tmp)
+
+ self.sendError(AUTHRequiredError(502, "Server does not support Client Authentication schemes %s" % auth,
+ self.log.str()))
+
+
+ def esmtpTLSRequired(self, code=-1, resp=None):
+ self.sendError(TLSRequiredError(502, "Server does not support secure communication via TLS / SSL",
+ self.log.str()))
+
+ def esmtpTLSFailed(self, code=-1, resp=None):
+ self.sendError(TLSError(code, "Could not complete the SSL/TLS handshake", self.log.str()))
+
+ def esmtpAUTHDeclined(self, code=-1, resp=None):
+ self.sendError(AUTHDeclinedError(code, resp, self.log.str()))
+
+ def esmtpAUTHMalformedChallenge(self, code=-1, resp=None):
+ str = "Login failed because the SMTP Server returned a malformed Authentication Challenge"
+ self.sendError(AuthenticationError(501, str, self.log.str()))
+
+ def esmtpAUTHServerError(self, code=-1, resp=None):
+ self.sendError(AuthenticationError(code, resp, self.log.str()))
+
+ def registerAuthenticator(self, auth):
+ """Registers an Authenticator with the ESMTPClient. The ESMTPClient
+ will attempt to login to the SMTP Server in the order the
+ Authenticators are registered. The most secure Authentication
+ mechanism should be registered first.
+
+ @param auth: The Authentication mechanism to register
+ @type auth: class implementing C{IClientAuthentication}
+ """
+
+ self.authenticators.append(auth)
+
+ def connectionMade(self):
+ SMTPClient.connectionMade(self)
+ self._okresponse = self.esmtpState_ehlo
+
+ def esmtpState_ehlo(self, code, resp):
+ self._expected = SUCCESS
+
+ self._okresponse = self.esmtpState_serverConfig
+ self._failresponse = self.esmtpEHLORequired
+
+ if self.heloFallback:
+ self._failresponse = self.smtpState_helo
+
+ self.sendLine('EHLO ' + self.identity)
+
+ def esmtpState_serverConfig(self, code, resp):
+ items = {}
+ for line in resp.splitlines():
+ e = line.split(None, 1)
+ if len(e) > 1:
+ items[e[0]] = e[1]
+ else:
+ items[e[0]] = None
+
+ if self.tlsMode:
+ self.authenticate(code, resp, items)
+ else:
+ self.tryTLS(code, resp, items)
+
+ def tryTLS(self, code, resp, items):
+ if self.context and 'STARTTLS' in items:
+ self._expected = [220]
+ self._okresponse = self.esmtpState_starttls
+ self._failresponse = self.esmtpTLSFailed
+ self.sendLine('STARTTLS')
+ elif self.requireTransportSecurity:
+ self.tlsMode = False
+ self.esmtpTLSRequired()
+ else:
+ self.tlsMode = False
+ self.authenticate(code, resp, items)
+
+ def esmtpState_starttls(self, code, resp):
+ try:
+ self.transport.startTLS(self.context)
+ self.tlsMode = True
+ except:
+ log.err()
+ self.esmtpTLSFailed(451)
+
+ # Send another EHLO once TLS has been started to
+ # get the TLS / AUTH schemes. Some servers only allow AUTH in TLS mode.
+ self.esmtpState_ehlo(code, resp)
+
+ def authenticate(self, code, resp, items):
+ if self.secret and items.get('AUTH'):
+ schemes = items['AUTH'].split()
+ tmpSchemes = {}
+
+ #XXX: May want to come up with a more efficient way to do this
+ for s in schemes:
+ tmpSchemes[s.upper()] = 1
+
+ for a in self.authenticators:
+ auth = a.getName().upper()
+
+ if auth in tmpSchemes:
+ self._authinfo = a
+
+ # Special condition handled
+ if auth == "PLAIN":
+ self._okresponse = self.smtpState_from
+ self._failresponse = self._esmtpState_plainAuth
+ self._expected = [235]
+ challenge = encode_base64(self._authinfo.challengeResponse(self.secret, 1), eol="")
+ self.sendLine('AUTH ' + auth + ' ' + challenge)
+ else:
+ self._expected = [334]
+ self._okresponse = self.esmtpState_challenge
+ # If some error occurs here, the server declined the AUTH
+ # before the user / password phase. This would be
+ # a very rare case
+ self._failresponse = self.esmtpAUTHServerError
+ self.sendLine('AUTH ' + auth)
+ return
+
+ if self.requireAuthentication:
+ self.esmtpAUTHRequired()
+ else:
+ self.smtpState_from(code, resp)
+
+ def _esmtpState_plainAuth(self, code, resp):
+ self._okresponse = self.smtpState_from
+ self._failresponse = self.esmtpAUTHDeclined
+ self._expected = [235]
+ challenge = encode_base64(self._authinfo.challengeResponse(self.secret, 2), eol="")
+ self.sendLine('AUTH PLAIN ' + challenge)
+
+ def esmtpState_challenge(self, code, resp):
+ self._authResponse(self._authinfo, resp)
+
+ def _authResponse(self, auth, challenge):
+ self._failresponse = self.esmtpAUTHDeclined
+ try:
+ challenge = base64.decodestring(challenge)
+ except binascii.Error, e:
+ # Illegal challenge, give up, then quit
+ self.sendLine('*')
+ self._okresponse = self.esmtpAUTHMalformedChallenge
+ self._failresponse = self.esmtpAUTHMalformedChallenge
+ else:
+ resp = auth.challengeResponse(self.secret, challenge)
+ self._expected = [235, 334]
+ self._okresponse = self.smtpState_maybeAuthenticated
+ self.sendLine(encode_base64(resp, eol=""))
+
+
+ def smtpState_maybeAuthenticated(self, code, resp):
+ """
+ Called to handle the next message from the server after sending a
+ response to a SASL challenge. The server response might be another
+ challenge or it might indicate authentication has succeeded.
+ """
+ if code == 235:
+ # Yes, authenticated!
+ del self._authinfo
+ self.smtpState_from(code, resp)
+ else:
+ # No, not authenticated yet. Keep trying.
+ self._authResponse(self._authinfo, resp)
+
+
+
+class ESMTP(SMTP):
+
+ ctx = None
+ canStartTLS = False
+ startedTLS = False
+
+ authenticated = False
+
+ def __init__(self, chal = None, contextFactory = None):
+ SMTP.__init__(self)
+ if chal is None:
+ chal = {}
+ self.challengers = chal
+ self.authenticated = False
+ self.ctx = contextFactory
+
+ def connectionMade(self):
+ SMTP.connectionMade(self)
+ self.canStartTLS = ITLSTransport.providedBy(self.transport)
+ self.canStartTLS = self.canStartTLS and (self.ctx is not None)
+
+
+ def greeting(self):
+ return SMTP.greeting(self) + ' ESMTP'
+
+
+ def extensions(self):
+ ext = {'AUTH': self.challengers.keys()}
+ if self.canStartTLS and not self.startedTLS:
+ ext['STARTTLS'] = None
+ return ext
+
+ def lookupMethod(self, command):
+ m = SMTP.lookupMethod(self, command)
+ if m is None:
+ m = getattr(self, 'ext_' + command.upper(), None)
+ return m
+
+ def listExtensions(self):
+ r = []
+ for (c, v) in self.extensions().iteritems():
+ if v is not None:
+ if v:
+ # Intentionally omit extensions with empty argument lists
+ r.append('%s %s' % (c, ' '.join(v)))
+ else:
+ r.append(c)
+ return '\n'.join(r)
+
+ def do_EHLO(self, rest):
+ peer = self.transport.getPeer().host
+ self._helo = (rest, peer)
+ self._from = None
+ self._to = []
+ self.sendCode(
+ 250,
+ '%s Hello %s, nice to meet you\n%s' % (
+ self.host, peer,
+ self.listExtensions(),
+ )
+ )
+
+ def ext_STARTTLS(self, rest):
+ if self.startedTLS:
+ self.sendCode(503, 'TLS already negotiated')
+ elif self.ctx and self.canStartTLS:
+ self.sendCode(220, 'Begin TLS negotiation now')
+ self.transport.startTLS(self.ctx)
+ self.startedTLS = True
+ else:
+ self.sendCode(454, 'TLS not available')
+
+ def ext_AUTH(self, rest):
+ if self.authenticated:
+ self.sendCode(503, 'Already authenticated')
+ return
+ parts = rest.split(None, 1)
+ chal = self.challengers.get(parts[0].upper(), lambda: None)()
+ if not chal:
+ self.sendCode(504, 'Unrecognized authentication type')
+ return
+
+ self.mode = AUTH
+ self.challenger = chal
+
+ if len(parts) > 1:
+ chal.getChallenge() # Discard it, apparently the client does not
+ # care about it.
+ rest = parts[1]
+ else:
+ rest = None
+ self.state_AUTH(rest)
+
+
+ def _cbAuthenticated(self, loginInfo):
+ """
+ Save the state resulting from a successful cred login and mark this
+ connection as authenticated.
+ """
+ result = SMTP._cbAnonymousAuthentication(self, loginInfo)
+ self.authenticated = True
+ return result
+
+
+ def _ebAuthenticated(self, reason):
+ """
+ Handle cred login errors by translating them to the SMTP authenticate
+ failed. Translate all other errors into a generic SMTP error code and
+ log the failure for inspection. Stop all errors from propagating.
+ """
+ self.challenge = None
+ if reason.check(cred.error.UnauthorizedLogin):
+ self.sendCode(535, 'Authentication failed')
+ else:
+ log.err(reason, "SMTP authentication failure")
+ self.sendCode(
+ 451,
+ 'Requested action aborted: local error in processing')
+
+
+ def state_AUTH(self, response):
+ """
+ Handle one step of challenge/response authentication.
+
+ @param response: The text of a response. If None, this
+ function has been called as a result of an AUTH command with
+ no initial response. A response of '*' aborts authentication,
+ as per RFC 2554.
+ """
+ if self.portal is None:
+ self.sendCode(454, 'Temporary authentication failure')
+ self.mode = COMMAND
+ return
+
+ if response is None:
+ challenge = self.challenger.getChallenge()
+ encoded = challenge.encode('base64')
+ self.sendCode(334, encoded)
+ return
+
+ if response == '*':
+ self.sendCode(501, 'Authentication aborted')
+ self.challenger = None
+ self.mode = COMMAND
+ return
+
+ try:
+ uncoded = response.decode('base64')
+ except binascii.Error:
+ self.sendCode(501, 'Syntax error in parameters or arguments')
+ self.challenger = None
+ self.mode = COMMAND
+ return
+
+ self.challenger.setResponse(uncoded)
+ if self.challenger.moreChallenges():
+ challenge = self.challenger.getChallenge()
+ coded = challenge.encode('base64')[:-1]
+ self.sendCode(334, coded)
+ return
+
+ self.mode = COMMAND
+ result = self.portal.login(
+ self.challenger, None,
+ IMessageDeliveryFactory, IMessageDelivery)
+ result.addCallback(self._cbAuthenticated)
+ result.addCallback(lambda ign: self.sendCode(235, 'Authentication successful.'))
+ result.addErrback(self._ebAuthenticated)
+
+
+
+class SenderMixin:
+ """Utility class for sending emails easily.
+
+ Use with SMTPSenderFactory or ESMTPSenderFactory.
+ """
+ done = 0
+
+ def getMailFrom(self):
+ if not self.done:
+ self.done = 1
+ return str(self.factory.fromEmail)
+ else:
+ return None
+
+ def getMailTo(self):
+ return self.factory.toEmail
+
+ def getMailData(self):
+ return self.factory.file
+
+ def sendError(self, exc):
+ # Call the base class to close the connection with the SMTP server
+ SMTPClient.sendError(self, exc)
+
+ # Do not retry to connect to SMTP Server if:
+ # 1. No more retries left (This allows the correct error to be returned to the errorback)
+ # 2. retry is false
+ # 3. The error code is not in the 4xx range (Communication Errors)
+
+ if (self.factory.retries >= 0 or
+ (not exc.retry and not (exc.code >= 400 and exc.code < 500))):
+ self.factory.sendFinished = 1
+ self.factory.result.errback(exc)
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ # Do not retry, the SMTP server acknowledged the request
+ self.factory.sendFinished = 1
+ if code not in SUCCESS:
+ errlog = []
+ for addr, acode, aresp in addresses:
+ if acode not in SUCCESS:
+ errlog.append("%s: %03d %s" % (addr, acode, aresp))
+
+ errlog.append(log.str())
+
+ exc = SMTPDeliveryError(code, resp, '\n'.join(errlog), addresses)
+ self.factory.result.errback(exc)
+ else:
+ self.factory.result.callback((numOk, addresses))
+
+
+class SMTPSender(SenderMixin, SMTPClient):
+ """
+ SMTP protocol that sends a single email based on information it
+ gets from its factory, a L{SMTPSenderFactory}.
+ """
+
+
+class SMTPSenderFactory(protocol.ClientFactory):
+ """
+ Utility factory for sending emails easily.
+ """
+
+ domain = DNSNAME
+ protocol = SMTPSender
+
+ def __init__(self, fromEmail, toEmail, file, deferred, retries=5,
+ timeout=None):
+ """
+ @param fromEmail: The RFC 2821 address from which to send this
+ message.
+
+ @param toEmail: A sequence of RFC 2821 addresses to which to
+ send this message.
+
+ @param file: A file-like object containing the message to send.
+
+ @param deferred: A Deferred to callback or errback when sending
+ of this message completes.
+
+ @param retries: The number of times to retry delivery of this
+ message.
+
+ @param timeout: Period, in seconds, for which to wait for
+ server responses, or None to wait forever.
+ """
+ assert isinstance(retries, (int, long))
+
+ if isinstance(toEmail, types.StringTypes):
+ toEmail = [toEmail]
+ self.fromEmail = Address(fromEmail)
+ self.nEmails = len(toEmail)
+ self.toEmail = toEmail
+ self.file = file
+ self.result = deferred
+ self.result.addBoth(self._removeDeferred)
+ self.sendFinished = 0
+
+ self.retries = -retries
+ self.timeout = timeout
+
+ def _removeDeferred(self, argh):
+ del self.result
+ return argh
+
+ def clientConnectionFailed(self, connector, err):
+ self._processConnectionError(connector, err)
+
+ def clientConnectionLost(self, connector, err):
+ self._processConnectionError(connector, err)
+
+ def _processConnectionError(self, connector, err):
+ if self.retries < self.sendFinished <= 0:
+ log.msg("SMTP Client retrying server. Retry: %s" % -self.retries)
+
+ # Rewind the file in case part of it was read while attempting to
+ # send the message.
+ self.file.seek(0, 0)
+ connector.connect()
+ self.retries += 1
+ elif self.sendFinished <= 0:
+ # If we were unable to communicate with the SMTP server a ConnectionDone will be
+ # returned. We want a more clear error message for debugging
+ if err.check(error.ConnectionDone):
+ err.value = SMTPConnectError(-1, "Unable to connect to server.")
+ self.result.errback(err.value)
+
+ def buildProtocol(self, addr):
+ p = self.protocol(self.domain, self.nEmails*2+2)
+ p.factory = self
+ p.timeout = self.timeout
+ return p
+
+
+
+from twisted.mail.imap4 import IClientAuthentication
+from twisted.mail.imap4 import CramMD5ClientAuthenticator, LOGINAuthenticator
+
+class PLAINAuthenticator:
+ implements(IClientAuthentication)
+
+ def __init__(self, user):
+ self.user = user
+
+ def getName(self):
+ return "PLAIN"
+
+ def challengeResponse(self, secret, chal=1):
+ if chal == 1:
+ return "%s\0%s\0%s" % (self.user, self.user, secret)
+ else:
+ return "%s\0%s" % (self.user, secret)
+
+
+
+class ESMTPSender(SenderMixin, ESMTPClient):
+
+ requireAuthentication = True
+ requireTransportSecurity = True
+
+ def __init__(self, username, secret, contextFactory=None, *args, **kw):
+ self.heloFallback = 0
+ self.username = username
+
+ if contextFactory is None:
+ contextFactory = self._getContextFactory()
+
+ ESMTPClient.__init__(self, secret, contextFactory, *args, **kw)
+
+ self._registerAuthenticators()
+
+ def _registerAuthenticators(self):
+ # Register Authenticator in order from most secure to least secure
+ self.registerAuthenticator(CramMD5ClientAuthenticator(self.username))
+ self.registerAuthenticator(LOGINAuthenticator(self.username))
+ self.registerAuthenticator(PLAINAuthenticator(self.username))
+
+ def _getContextFactory(self):
+ if self.context is not None:
+ return self.context
+ try:
+ from twisted.internet import ssl
+ except ImportError:
+ return None
+ else:
+ try:
+ context = ssl.ClientContextFactory()
+ context.method = ssl.SSL.TLSv1_METHOD
+ return context
+ except AttributeError:
+ return None
+
+
+class ESMTPSenderFactory(SMTPSenderFactory):
+ """
+ Utility factory for sending emails easily.
+ """
+
+ protocol = ESMTPSender
+
+ def __init__(self, username, password, fromEmail, toEmail, file,
+ deferred, retries=5, timeout=None,
+ contextFactory=None, heloFallback=False,
+ requireAuthentication=True,
+ requireTransportSecurity=True):
+
+ SMTPSenderFactory.__init__(self, fromEmail, toEmail, file, deferred, retries, timeout)
+ self.username = username
+ self.password = password
+ self._contextFactory = contextFactory
+ self._heloFallback = heloFallback
+ self._requireAuthentication = requireAuthentication
+ self._requireTransportSecurity = requireTransportSecurity
+
+ def buildProtocol(self, addr):
+ p = self.protocol(self.username, self.password, self._contextFactory, self.domain, self.nEmails*2+2)
+ p.heloFallback = self._heloFallback
+ p.requireAuthentication = self._requireAuthentication
+ p.requireTransportSecurity = self._requireTransportSecurity
+ p.factory = self
+ p.timeout = self.timeout
+ return p
+
+def sendmail(smtphost, from_addr, to_addrs, msg, senderDomainName=None, port=25):
+ """Send an email
+
+ This interface is intended to be a direct replacement for
+ smtplib.SMTP.sendmail() (with the obvious change that
+ you specify the smtphost as well). Also, ESMTP options
+ are not accepted, as we don't do ESMTP yet. I reserve the
+ right to implement the ESMTP options differently.
+
+ @param smtphost: The host the message should be sent to
+ @param from_addr: The (envelope) address sending this mail.
+ @param to_addrs: A list of addresses to send this mail to. A string will
+ be treated as a list of one address
+ @param msg: The message, including headers, either as a file or a string.
+ File-like objects need to support read() and close(). Lines must be
+ delimited by '\\n'. If you pass something that doesn't look like a
+ file, we try to convert it to a string (so you should be able to
+ pass an email.Message directly, but doing the conversion with
+ email.Generator manually will give you more control over the
+ process).
+
+ @param senderDomainName: Name by which to identify. If None, try
+ to pick something sane (but this depends on external configuration
+ and may not succeed).
+
+ @param port: Remote port to which to connect.
+
+ @rtype: L{Deferred}
+ @returns: A L{Deferred}, its callback will be called if a message is sent
+ to ANY address, the errback if no message is sent.
+
+ The callback will be called with a tuple (numOk, addresses) where numOk
+ is the number of successful recipient addresses and addresses is a list
+ of tuples (address, code, resp) giving the response to the RCPT command
+ for each address.
+ """
+ if not hasattr(msg,'read'):
+ # It's not a file
+ msg = StringIO(str(msg))
+
+ d = defer.Deferred()
+ factory = SMTPSenderFactory(from_addr, to_addrs, msg, d)
+
+ if senderDomainName is not None:
+ factory.domain = senderDomainName
+
+ reactor.connectTCP(smtphost, port, factory)
+
+ return d
+
+def sendEmail(smtphost, fromEmail, toEmail, content, headers = None, attachments = None, multipartbody = "mixed"):
+ """Send an email, optionally with attachments.
+
+ @type smtphost: str
+ @param smtphost: hostname of SMTP server to which to connect
+
+ @type fromEmail: str
+ @param fromEmail: email address to indicate this email is from
+
+ @type toEmail: str
+ @param toEmail: email address to which to send this email
+
+ @type content: str
+ @param content: The body if this email.
+
+ @type headers: dict
+ @param headers: Dictionary of headers to include in the email
+
+ @type attachments: list of 3-tuples
+ @param attachments: Each 3-tuple should consist of the name of the
+ attachment, the mime-type of the attachment, and a string that is
+ the attachment itself.
+
+ @type multipartbody: str
+ @param multipartbody: The type of MIME multi-part body. Generally
+ either "mixed" (as in text and images) or "alternative" (html email
+ with a fallback to text/plain).
+
+ @rtype: Deferred
+ @return: The returned Deferred has its callback or errback invoked when
+ the mail is successfully sent or when an error occurs, respectively.
+ """
+ warnings.warn("smtp.sendEmail may go away in the future.\n"
+ " Consider revising your code to use the email module\n"
+ " and smtp.sendmail.",
+ category=DeprecationWarning, stacklevel=2)
+
+ f = tempfile.TemporaryFile()
+ writer = MimeWriter.MimeWriter(f)
+
+ writer.addheader("Mime-Version", "1.0")
+ if headers:
+ # Setup the mail headers
+ for (header, value) in headers.items():
+ writer.addheader(header, value)
+
+ headkeys = [k.lower() for k in headers.keys()]
+ else:
+ headkeys = ()
+
+ # Add required headers if not present
+ if "message-id" not in headkeys:
+ writer.addheader("Message-ID", messageid())
+ if "date" not in headkeys:
+ writer.addheader("Date", rfc822date())
+ if "from" not in headkeys and "sender" not in headkeys:
+ writer.addheader("From", fromEmail)
+ if "to" not in headkeys and "cc" not in headkeys and "bcc" not in headkeys:
+ writer.addheader("To", toEmail)
+
+ writer.startmultipartbody(multipartbody)
+
+ # message body
+ part = writer.nextpart()
+ body = part.startbody("text/plain")
+ body.write(content)
+
+ if attachments is not None:
+ # add attachments
+ for (file, mime, attachment) in attachments:
+ part = writer.nextpart()
+ if mime.startswith('text'):
+ encoding = "7bit"
+ else:
+ attachment = base64.encodestring(attachment)
+ encoding = "base64"
+ part.addheader("Content-Transfer-Encoding", encoding)
+ body = part.startbody("%s; name=%s" % (mime, file))
+ body.write(attachment)
+
+ # finish
+ writer.lastpart()
+
+ # send message
+ f.seek(0, 0)
+ d = defer.Deferred()
+ factory = SMTPSenderFactory(fromEmail, toEmail, f, d)
+ reactor.connectTCP(smtphost, 25, factory)
+
+ return d
+
+##
+## Yerg. Codecs!
+##
+import codecs
+def xtext_encode(s, errors=None):
+ r = []
+ for ch in s:
+ o = ord(ch)
+ if ch == '+' or ch == '=' or o < 33 or o > 126:
+ r.append('+%02X' % o)
+ else:
+ r.append(chr(o))
+ return (''.join(r), len(s))
+
+
+def _slowXTextDecode(s, errors=None):
+ """
+ Decode the xtext-encoded string C{s}.
+ """
+ r = []
+ i = 0
+ while i < len(s):
+ if s[i] == '+':
+ try:
+ r.append(chr(int(s[i + 1:i + 3], 16)))
+ except ValueError:
+ r.append(s[i:i + 3])
+ i += 3
+ else:
+ r.append(s[i])
+ i += 1
+ return (''.join(r), len(s))
+
+try:
+ from twisted.protocols._c_urlarg import unquote as _helper_unquote
+except ImportError:
+ xtext_decode = _slowXTextDecode
+else:
+ def xtext_decode(s, errors=None):
+ """
+ Decode the xtext-encoded string C{s} using a fast extension function.
+ """
+ return (_helper_unquote(s, '+'), len(s))
+
+class xtextStreamReader(codecs.StreamReader):
+ def decode(self, s, errors='strict'):
+ return xtext_decode(s)
+
+class xtextStreamWriter(codecs.StreamWriter):
+ def decode(self, s, errors='strict'):
+ return xtext_encode(s)
+
+def xtext_codec(name):
+ if name == 'xtext':
+ return (xtext_encode, xtext_decode, xtextStreamReader, xtextStreamWriter)
+codecs.register(xtext_codec)
diff --git a/vendor/Twisted-10.0.0/twisted/mail/tap.py b/vendor/Twisted-10.0.0/twisted/mail/tap.py
new file mode 100644
index 0000000000..085bd4a2a8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/tap.py
@@ -0,0 +1,185 @@
+# -*- test-case-name: twisted.mail.test.test_options -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""I am the support module for creating mail servers with twistd
+"""
+
+import os
+import sys
+
+from twisted.mail import mail
+from twisted.mail import maildir
+from twisted.mail import relay
+from twisted.mail import relaymanager
+from twisted.mail import alias
+
+from twisted.python import usage
+
+from twisted.cred import checkers
+from twisted.application import internet
+
+
+class Options(usage.Options):
+ synopsis = "[options]"
+
+ optParameters = [
+ ["pop3", "p", 8110, "Port to start the POP3 server on (0 to disable).", usage.portCoerce],
+ ["pop3s", "S", 0, "Port to start the POP3-over-SSL server on (0 to disable).", usage.portCoerce],
+ ["smtp", "s", 8025, "Port to start the SMTP server on (0 to disable).", usage.portCoerce],
+ ["certificate", "c", None, "Certificate file to use for SSL connections"],
+ ["relay", "R", None,
+ "Relay messages according to their envelope 'To', using the given"
+ "path as a queue directory."],
+ ["hostname", "H", None, "The hostname by which to identify this server."],
+ ]
+
+ optFlags = [
+ ["esmtp", "E", "Use RFC 1425/1869 SMTP extensions"],
+ ["disable-anonymous", None, "Disallow non-authenticated SMTP connections"],
+ ]
+ zsh_actions = {"hostname" : "_hosts"}
+
+ longdesc = "This creates a mail.tap file that can be used by twistd."
+
+ def __init__(self):
+ usage.Options.__init__(self)
+ self.service = mail.MailService()
+ self.last_domain = None
+
+ def opt_passwordfile(self, filename):
+ """Specify a file containing username:password login info for authenticated ESMTP connections."""
+ ch = checkers.OnDiskUsernamePasswordDatabase(filename)
+ self.service.smtpPortal.registerChecker(ch)
+ opt_P = opt_passwordfile
+
+ def opt_default(self):
+ """Make the most recently specified domain the default domain."""
+ if self.last_domain:
+ self.service.addDomain('', self.last_domain)
+ else:
+ raise usage.UsageError("Specify a domain before specifying using --default")
+ opt_D = opt_default
+
+ def opt_maildirdbmdomain(self, domain):
+ """generate an SMTP/POP3 virtual domain which saves to \"path\"
+ """
+ try:
+ name, path = domain.split('=')
+ except ValueError:
+ raise usage.UsageError("Argument to --maildirdbmdomain must be of the form 'name=path'")
+
+ self.last_domain = maildir.MaildirDirdbmDomain(self.service, os.path.abspath(path))
+ self.service.addDomain(name, self.last_domain)
+ opt_d = opt_maildirdbmdomain
+
+ def opt_user(self, user_pass):
+ """add a user/password to the last specified domains
+ """
+ try:
+ user, password = user_pass.split('=', 1)
+ except ValueError:
+ raise usage.UsageError("Argument to --user must be of the form 'user=password'")
+ if self.last_domain:
+ self.last_domain.addUser(user, password)
+ else:
+ raise usage.UsageError("Specify a domain before specifying users")
+ opt_u = opt_user
+
+ def opt_bounce_to_postmaster(self):
+ """undelivered mails are sent to the postmaster
+ """
+ self.last_domain.postmaster = 1
+ opt_b = opt_bounce_to_postmaster
+
+ def opt_aliases(self, filename):
+ """Specify an aliases(5) file to use for this domain"""
+ if self.last_domain is not None:
+ if mail.IAliasableDomain.providedBy(self.last_domain):
+ aliases = alias.loadAliasFile(self.service.domains, filename)
+ self.last_domain.setAliasGroup(aliases)
+ self.service.monitor.monitorFile(
+ filename,
+ AliasUpdater(self.service.domains, self.last_domain)
+ )
+ else:
+ raise usage.UsageError(
+ "%s does not support alias files" % (
+ self.last_domain.__class__.__name__,
+ )
+ )
+ else:
+ raise usage.UsageError("Specify a domain before specifying aliases")
+ opt_A = opt_aliases
+
+ def postOptions(self):
+ if self['pop3s']:
+ if not self['certificate']:
+ raise usage.UsageError("Cannot specify --pop3s without "
+ "--certificate")
+ elif not os.path.exists(self['certificate']):
+ raise usage.UsageError("Certificate file %r does not exist."
+ % self['certificate'])
+
+ if not self['disable-anonymous']:
+ self.service.smtpPortal.registerChecker(checkers.AllowAnonymousAccess())
+
+ if not (self['pop3'] or self['smtp'] or self['pop3s']):
+ raise usage.UsageError("You cannot disable all protocols")
+
+class AliasUpdater:
+ def __init__(self, domains, domain):
+ self.domains = domains
+ self.domain = domain
+ def __call__(self, new):
+ self.domain.setAliasGroup(alias.loadAliasFile(self.domains, new))
+
+def makeService(config):
+ if config['esmtp']:
+ rmType = relaymanager.SmartHostESMTPRelayingManager
+ smtpFactory = config.service.getESMTPFactory
+ else:
+ rmType = relaymanager.SmartHostSMTPRelayingManager
+ smtpFactory = config.service.getSMTPFactory
+
+ if config['relay']:
+ dir = config['relay']
+ if not os.path.isdir(dir):
+ os.mkdir(dir)
+
+ config.service.setQueue(relaymanager.Queue(dir))
+ default = relay.DomainQueuer(config.service)
+
+ manager = rmType(config.service.queue)
+ if config['esmtp']:
+ manager.fArgs += (None, None)
+ manager.fArgs += (config['hostname'],)
+
+ helper = relaymanager.RelayStateHelper(manager, 1)
+ helper.setServiceParent(config.service)
+ config.service.domains.setDefaultDomain(default)
+
+ ctx = None
+ if config['certificate']:
+ from twisted.mail.protocols import SSLContextFactory
+ ctx = SSLContextFactory(config['certificate'])
+
+ if config['pop3']:
+ s = internet.TCPServer(config['pop3'], config.service.getPOP3Factory())
+ s.setServiceParent(config.service)
+ if config['pop3s']:
+ s = internet.SSLServer(config['pop3s'],
+ config.service.getPOP3Factory(), ctx)
+ s.setServiceParent(config.service)
+ if config['smtp']:
+ f = smtpFactory()
+ f.context = ctx
+ if config['hostname']:
+ f.domain = config['hostname']
+ f.fArgs = (f.domain,)
+ if config['esmtp']:
+ f.fArgs = (None, None) + f.fArgs
+ s = internet.TCPServer(config['smtp'], f)
+ s.setServiceParent(config.service)
+ return config.service
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/__init__.py b/vendor/Twisted-10.0.0/twisted/mail/test/__init__.py
new file mode 100644
index 0000000000..f8ec7056ee
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/__init__.py
@@ -0,0 +1 @@
+"Tests for twistd.mail"
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/pop3testserver.py b/vendor/Twisted-10.0.0/twisted/mail/test/pop3testserver.py
new file mode 100644
index 0000000000..39bdd52488
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/pop3testserver.py
@@ -0,0 +1,314 @@
+#!/usr/bin/env python
+# -*- test-case-name: twisted.mail.test.test_pop3client -*-
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet.protocol import Factory
+from twisted.protocols import basic
+from twisted.internet import reactor
+import sys, time
+
+USER = "test"
+PASS = "twisted"
+
+PORT = 1100
+
+SSL_SUPPORT = True
+UIDL_SUPPORT = True
+INVALID_SERVER_RESPONSE = False
+INVALID_CAPABILITY_RESPONSE = False
+INVALID_LOGIN_RESPONSE = False
+DENY_CONNECTION = False
+DROP_CONNECTION = False
+BAD_TLS_RESPONSE = False
+TIMEOUT_RESPONSE = False
+TIMEOUT_DEFERRED = False
+SLOW_GREETING = False
+
+"""Commands"""
+CONNECTION_MADE = "+OK POP3 localhost v2003.83 server ready"
+
+CAPABILITIES = [
+"TOP",
+"LOGIN-DELAY 180",
+"USER",
+"SASL LOGIN"
+]
+
+CAPABILITIES_SSL = "STLS"
+CAPABILITIES_UIDL = "UIDL"
+
+
+INVALID_RESPONSE = "-ERR Unknown request"
+VALID_RESPONSE = "+OK Command Completed"
+AUTH_DECLINED = "-ERR LOGIN failed"
+AUTH_ACCEPTED = "+OK Mailbox open, 0 messages"
+TLS_ERROR = "-ERR server side error start TLS handshake"
+LOGOUT_COMPLETE = "+OK quit completed"
+NOT_LOGGED_IN = "-ERR Unknown AUHORIZATION state command"
+STAT = "+OK 0 0"
+UIDL = "+OK Unique-ID listing follows\r\n."
+LIST = "+OK Mailbox scan listing follows\r\n."
+CAP_START = "+OK Capability list follows:"
+
+
+class POP3TestServer(basic.LineReceiver):
+ def __init__(self, contextFactory = None):
+ self.loggedIn = False
+ self.caps = None
+ self.tmpUser = None
+ self.ctx = contextFactory
+
+ def sendSTATResp(self, req):
+ self.sendLine(STAT)
+
+ def sendUIDLResp(self, req):
+ self.sendLine(UIDL)
+
+ def sendLISTResp(self, req):
+ self.sendLine(LIST)
+
+ def sendCapabilities(self):
+ if self.caps is None:
+ self.caps = [CAP_START]
+
+ if UIDL_SUPPORT:
+ self.caps.append(CAPABILITIES_UIDL)
+
+ if SSL_SUPPORT:
+ self.caps.append(CAPABILITIES_SSL)
+
+ for cap in CAPABILITIES:
+ self.caps.append(cap)
+ resp = '\r\n'.join(self.caps)
+ resp += "\r\n."
+
+ self.sendLine(resp)
+
+
+ def connectionMade(self):
+ if DENY_CONNECTION:
+ self.disconnect()
+ return
+
+ if SLOW_GREETING:
+ reactor.callLater(20, self.sendGreeting)
+
+ else:
+ self.sendGreeting()
+
+ def sendGreeting(self):
+ self.sendLine(CONNECTION_MADE)
+
+ def lineReceived(self, line):
+ """Error Conditions"""
+
+ uline = line.upper()
+ find = lambda s: uline.find(s) != -1
+
+ if TIMEOUT_RESPONSE:
+ # Do not respond to clients request
+ return
+
+ if DROP_CONNECTION:
+ self.disconnect()
+ return
+
+ elif find("CAPA"):
+ if INVALID_CAPABILITY_RESPONSE:
+ self.sendLine(INVALID_RESPONSE)
+ else:
+ self.sendCapabilities()
+
+ elif find("STLS") and SSL_SUPPORT:
+ self.startTLS()
+
+ elif find("USER"):
+ if INVALID_LOGIN_RESPONSE:
+ self.sendLine(INVALID_RESPONSE)
+ return
+
+ resp = None
+ try:
+ self.tmpUser = line.split(" ")[1]
+ resp = VALID_RESPONSE
+ except:
+ resp = AUTH_DECLINED
+
+ self.sendLine(resp)
+
+ elif find("PASS"):
+ resp = None
+ try:
+ pwd = line.split(" ")[1]
+
+ if self.tmpUser is None or pwd is None:
+ resp = AUTH_DECLINED
+ elif self.tmpUser == USER and pwd == PASS:
+ resp = AUTH_ACCEPTED
+ self.loggedIn = True
+ else:
+ resp = AUTH_DECLINED
+ except:
+ resp = AUTH_DECLINED
+
+ self.sendLine(resp)
+
+ elif find("QUIT"):
+ self.loggedIn = False
+ self.sendLine(LOGOUT_COMPLETE)
+ self.disconnect()
+
+ elif INVALID_SERVER_RESPONSE:
+ self.sendLine(INVALID_RESPONSE)
+
+ elif not self.loggedIn:
+ self.sendLine(NOT_LOGGED_IN)
+
+ elif find("NOOP"):
+ self.sendLine(VALID_RESPONSE)
+
+ elif find("STAT"):
+ if TIMEOUT_DEFERRED:
+ return
+ self.sendLine(STAT)
+
+ elif find("LIST"):
+ if TIMEOUT_DEFERRED:
+ return
+ self.sendLine(LIST)
+
+ elif find("UIDL"):
+ if TIMEOUT_DEFERRED:
+ return
+ elif not UIDL_SUPPORT:
+ self.sendLine(INVALID_RESPONSE)
+ return
+
+ self.sendLine(UIDL)
+
+ def startTLS(self):
+ if self.ctx is None:
+ self.getContext()
+
+ if SSL_SUPPORT and self.ctx is not None:
+ self.sendLine('+OK Begin TLS negotiation now')
+ self.transport.startTLS(self.ctx)
+ else:
+ self.sendLine('-ERR TLS not available')
+
+ def disconnect(self):
+ self.transport.loseConnection()
+
+ def getContext(self):
+ try:
+ from twisted.internet import ssl
+ except ImportError:
+ self.ctx = None
+ else:
+ self.ctx = ssl.ClientContextFactory()
+ self.ctx.method = ssl.SSL.TLSv1_METHOD
+
+
+usage = """popServer.py [arg] (default is Standard POP Server with no messages)
+no_ssl - Start with no SSL support
+no_uidl - Start with no UIDL support
+bad_resp - Send a non-RFC compliant response to the Client
+bad_cap_resp - send a non-RFC compliant response when the Client sends a 'CAPABILITY' request
+bad_login_resp - send a non-RFC compliant response when the Client sends a 'LOGIN' request
+deny - Deny the connection
+drop - Drop the connection after sending the greeting
+bad_tls - Send a bad response to a STARTTLS
+timeout - Do not return a response to a Client request
+to_deferred - Do not return a response on a 'Select' request. This
+ will test Deferred callback handling
+slow - Wait 20 seconds after the connection is made to return a Server Greeting
+"""
+
+def printMessage(msg):
+ print "Server Starting in %s mode" % msg
+
+def processArg(arg):
+
+ if arg.lower() == 'no_ssl':
+ global SSL_SUPPORT
+ SSL_SUPPORT = False
+ printMessage("NON-SSL")
+
+ elif arg.lower() == 'no_uidl':
+ global UIDL_SUPPORT
+ UIDL_SUPPORT = False
+ printMessage("NON-UIDL")
+
+ elif arg.lower() == 'bad_resp':
+ global INVALID_SERVER_RESPONSE
+ INVALID_SERVER_RESPONSE = True
+ printMessage("Invalid Server Response")
+
+ elif arg.lower() == 'bad_cap_resp':
+ global INVALID_CAPABILITY_RESPONSE
+ INVALID_CAPABILITY_RESPONSE = True
+ printMessage("Invalid Capability Response")
+
+ elif arg.lower() == 'bad_login_resp':
+ global INVALID_LOGIN_RESPONSE
+ INVALID_LOGIN_RESPONSE = True
+ printMessage("Invalid Capability Response")
+
+ elif arg.lower() == 'deny':
+ global DENY_CONNECTION
+ DENY_CONNECTION = True
+ printMessage("Deny Connection")
+
+ elif arg.lower() == 'drop':
+ global DROP_CONNECTION
+ DROP_CONNECTION = True
+ printMessage("Drop Connection")
+
+
+ elif arg.lower() == 'bad_tls':
+ global BAD_TLS_RESPONSE
+ BAD_TLS_RESPONSE = True
+ printMessage("Bad TLS Response")
+
+ elif arg.lower() == 'timeout':
+ global TIMEOUT_RESPONSE
+ TIMEOUT_RESPONSE = True
+ printMessage("Timeout Response")
+
+ elif arg.lower() == 'to_deferred':
+ global TIMEOUT_DEFERRED
+ TIMEOUT_DEFERRED = True
+ printMessage("Timeout Deferred Response")
+
+ elif arg.lower() == 'slow':
+ global SLOW_GREETING
+ SLOW_GREETING = True
+ printMessage("Slow Greeting")
+
+ elif arg.lower() == '--help':
+ print usage
+ sys.exit()
+
+ else:
+ print usage
+ sys.exit()
+
+def main():
+
+ if len(sys.argv) < 2:
+ printMessage("POP3 with no messages")
+ else:
+ args = sys.argv[1:]
+
+ for arg in args:
+ processArg(arg)
+
+ f = Factory()
+ f.protocol = POP3TestServer
+ reactor.listenTCP(PORT, f)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/rfc822.message b/vendor/Twisted-10.0.0/twisted/mail/test/rfc822.message
new file mode 100644
index 0000000000..ee97ab922a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/rfc822.message
@@ -0,0 +1,86 @@
+Return-Path: <twisted-commits-admin@twistedmatrix.com>
+Delivered-To: exarkun@meson.dyndns.org
+Received: from localhost [127.0.0.1]
+ by localhost with POP3 (fetchmail-6.2.1)
+ for exarkun@localhost (single-drop); Thu, 20 Mar 2003 14:50:20 -0500 (EST)
+Received: from pyramid.twistedmatrix.com (adsl-64-123-27-105.dsl.austtx.swbell.net [64.123.27.105])
+ by intarweb.us (Postfix) with ESMTP id 4A4A513EA4
+ for <exarkun@meson.dyndns.org>; Thu, 20 Mar 2003 14:49:27 -0500 (EST)
+Received: from localhost ([127.0.0.1] helo=pyramid.twistedmatrix.com)
+ by pyramid.twistedmatrix.com with esmtp (Exim 3.35 #1 (Debian))
+ id 18w648-0007Vl-00; Thu, 20 Mar 2003 13:51:04 -0600
+Received: from acapnotic by pyramid.twistedmatrix.com with local (Exim 3.35 #1 (Debian))
+ id 18w63j-0007VK-00
+ for <twisted-commits@twistedmatrix.com>; Thu, 20 Mar 2003 13:50:39 -0600
+To: twisted-commits@twistedmatrix.com
+From: etrepum CVS <etrepum@twistedmatrix.com>
+Reply-To: twisted-python@twistedmatrix.com
+X-Mailer: CVSToys
+Message-Id: <E18w63j-0007VK-00@pyramid.twistedmatrix.com>
+Subject: [Twisted-commits] rebuild now works on python versions from 2.2.0 and up.
+Sender: twisted-commits-admin@twistedmatrix.com
+Errors-To: twisted-commits-admin@twistedmatrix.com
+X-BeenThere: twisted-commits@twistedmatrix.com
+X-Mailman-Version: 2.0.11
+Precedence: bulk
+List-Help: <mailto:twisted-commits-request@twistedmatrix.com?subject=help>
+List-Post: <mailto:twisted-commits@twistedmatrix.com>
+List-Subscribe: <http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits>,
+ <mailto:twisted-commits-request@twistedmatrix.com?subject=subscribe>
+List-Id: <twisted-commits.twistedmatrix.com>
+List-Unsubscribe: <http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits>,
+ <mailto:twisted-commits-request@twistedmatrix.com?subject=unsubscribe>
+List-Archive: <http://twistedmatrix.com/pipermail/twisted-commits/>
+Date: Thu, 20 Mar 2003 13:50:39 -0600
+
+Modified files:
+Twisted/twisted/python/rebuild.py 1.19 1.20
+
+Log message:
+rebuild now works on python versions from 2.2.0 and up.
+
+
+ViewCVS links:
+http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/python/rebuild.py.diff?r1=text&tr1=1.19&r2=text&tr2=1.20&cvsroot=Twisted
+
+Index: Twisted/twisted/python/rebuild.py
+diff -u Twisted/twisted/python/rebuild.py:1.19 Twisted/twisted/python/rebuild.py:1.20
+--- Twisted/twisted/python/rebuild.py:1.19 Fri Jan 17 13:50:49 2003
++++ Twisted/twisted/python/rebuild.py Thu Mar 20 11:50:08 2003
+@@ -206,15 +206,27 @@
+ clazz.__dict__.clear()
+ clazz.__getattr__ = __getattr__
+ clazz.__module__ = module.__name__
++ if newclasses:
++ import gc
++ if (2, 2, 0) <= sys.version_info[:3] < (2, 2, 2):
++ hasBrokenRebuild = 1
++ gc_objects = gc.get_objects()
++ else:
++ hasBrokenRebuild = 0
+ for nclass in newclasses:
+ ga = getattr(module, nclass.__name__)
+ if ga is nclass:
+ log.msg("WARNING: new-class %s not replaced by reload!" % reflect.qual(nclass))
+ else:
+- import gc
+- for r in gc.get_referrers(nclass):
+- if isinstance(r, nclass):
++ if hasBrokenRebuild:
++ for r in gc_objects:
++ if not getattr(r, '__class__', None) is nclass:
++ continue
+ r.__class__ = ga
++ else:
++ for r in gc.get_referrers(nclass):
++ if getattr(r, '__class__', None) is nclass:
++ r.__class__ = ga
+ if doLog:
+ log.msg('')
+ log.msg(' (fixing %s): ' % str(module.__name__))
+
+
+_______________________________________________
+Twisted-commits mailing list
+Twisted-commits@twistedmatrix.com
+http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/test_bounce.py b/vendor/Twisted-10.0.0/twisted/mail/test/test_bounce.py
new file mode 100644
index 0000000000..ece92813e2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/test_bounce.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Test cases for bounce message generation
+"""
+
+from twisted.trial import unittest
+from twisted.mail import bounce
+import rfc822, cStringIO
+
+class BounceTestCase(unittest.TestCase):
+ """
+ testcases for bounce message generation
+ """
+
+ def testBounceFormat(self):
+ from_, to, s = bounce.generateBounce(cStringIO.StringIO('''\
+From: Moshe Zadka <moshez@example.com>
+To: nonexistant@example.org
+Subject: test
+
+'''), 'moshez@example.com', 'nonexistant@example.org')
+ self.assertEquals(from_, '')
+ self.assertEquals(to, 'moshez@example.com')
+ mess = rfc822.Message(cStringIO.StringIO(s))
+ self.assertEquals(mess['To'], 'moshez@example.com')
+ self.assertEquals(mess['From'], 'postmaster@example.org')
+ self.assertEquals(mess['subject'], 'Returned Mail: see transcript for details')
+
+ def testBounceMIME(self):
+ pass
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/test_imap.py b/vendor/Twisted-10.0.0/twisted/mail/test/test_imap.py
new file mode 100644
index 0000000000..9bcb5dfa3e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/test_imap.py
@@ -0,0 +1,4244 @@
+# -*- test-case-name: twisted.mail.test.test_imap -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Test case for twisted.mail.imap4
+"""
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+import os
+import types
+import codecs
+
+from zope.interface import implements
+
+from twisted.mail.imap4 import MessageSet
+from twisted.mail import imap4
+from twisted.protocols import loopback
+from twisted.internet import defer
+from twisted.internet import error
+from twisted.internet import reactor
+from twisted.internet import interfaces
+from twisted.internet.task import Clock
+from twisted.trial import unittest
+from twisted.python import util
+from twisted.python import failure
+
+from twisted import cred
+import twisted.cred.error
+import twisted.cred.checkers
+import twisted.cred.credentials
+import twisted.cred.portal
+
+from twisted.test.proto_helpers import StringTransport, StringTransportWithDisconnection
+
+try:
+ from twisted.test.ssl_helpers import ClientTLSContext, ServerTLSContext
+except ImportError:
+ ClientTLSContext = ServerTLSContext = None
+
+def strip(f):
+ return lambda result, f=f: f()
+
+def sortNest(l):
+ l = l[:]
+ l.sort()
+ for i in range(len(l)):
+ if isinstance(l[i], types.ListType):
+ l[i] = sortNest(l[i])
+ elif isinstance(l[i], types.TupleType):
+ l[i] = tuple(sortNest(list(l[i])))
+ return l
+
+class IMAP4UTF7TestCase(unittest.TestCase):
+ tests = [
+ [u'Hello world', 'Hello world'],
+ [u'Hello & world', 'Hello &- world'],
+ [u'Hello\xffworld', 'Hello&AP8-world'],
+ [u'\xff\xfe\xfd\xfc', '&AP8A,gD9APw-'],
+ [u'~peter/mail/\u65e5\u672c\u8a9e/\u53f0\u5317',
+ '~peter/mail/&ZeVnLIqe-/&U,BTFw-'], # example from RFC 2060
+ ]
+
+ def test_encodeWithErrors(self):
+ """
+ Specifying an error policy to C{unicode.encode} with the
+ I{imap4-utf-7} codec should produce the same result as not
+ specifying the error policy.
+ """
+ text = u'Hello world'
+ self.assertEqual(
+ text.encode('imap4-utf-7', 'strict'),
+ text.encode('imap4-utf-7'))
+
+
+ def test_decodeWithErrors(self):
+ """
+ Similar to L{test_encodeWithErrors}, but for C{str.decode}.
+ """
+ bytes = 'Hello world'
+ self.assertEqual(
+ bytes.decode('imap4-utf-7', 'strict'),
+ bytes.decode('imap4-utf-7'))
+
+
+ def test_getreader(self):
+ """
+ C{codecs.getreader('imap4-utf-7')} returns the I{imap4-utf-7} stream
+ reader class.
+ """
+ reader = codecs.getreader('imap4-utf-7')(StringIO('Hello&AP8-world'))
+ self.assertEquals(reader.read(), u'Hello\xffworld')
+
+
+ def test_getwriter(self):
+ """
+ C{codecs.getwriter('imap4-utf-7')} returns the I{imap4-utf-7} stream
+ writer class.
+ """
+ output = StringIO()
+ writer = codecs.getwriter('imap4-utf-7')(output)
+ writer.write(u'Hello\xffworld')
+ self.assertEquals(output.getvalue(), 'Hello&AP8-world')
+
+
+ def test_encode(self):
+ """
+ The I{imap4-utf-7} can be used to encode a unicode string into a byte
+ string according to the IMAP4 modified UTF-7 encoding rules.
+ """
+ for (input, output) in self.tests:
+ self.assertEquals(input.encode('imap4-utf-7'), output)
+
+
+ def test_decode(self):
+ """
+ The I{imap4-utf-7} can be used to decode a byte string into a unicode
+ string according to the IMAP4 modified UTF-7 encoding rules.
+ """
+ for (input, output) in self.tests:
+ self.assertEquals(input, output.decode('imap4-utf-7'))
+
+
+ def test_printableSingletons(self):
+ """
+ The IMAP4 modified UTF-7 implementation encodes all printable
+ characters which are in ASCII using the corresponding ASCII byte.
+ """
+ # All printables represent themselves
+ for o in range(0x20, 0x26) + range(0x27, 0x7f):
+ self.failUnlessEqual(chr(o), chr(o).encode('imap4-utf-7'))
+ self.failUnlessEqual(chr(o), chr(o).decode('imap4-utf-7'))
+ self.failUnlessEqual('&'.encode('imap4-utf-7'), '&-')
+ self.failUnlessEqual('&-'.decode('imap4-utf-7'), '&')
+
+
+
+class BufferingConsumer:
+ def __init__(self):
+ self.buffer = []
+
+ def write(self, bytes):
+ self.buffer.append(bytes)
+ if self.consumer:
+ self.consumer.resumeProducing()
+
+ def registerProducer(self, consumer, streaming):
+ self.consumer = consumer
+ self.consumer.resumeProducing()
+
+ def unregisterProducer(self):
+ self.consumer = None
+
+class MessageProducerTestCase(unittest.TestCase):
+ def testSinglePart(self):
+ body = 'This is body text. Rar.'
+ headers = util.OrderedDict()
+ headers['from'] = 'sender@host'
+ headers['to'] = 'recipient@domain'
+ headers['subject'] = 'booga booga boo'
+ headers['content-type'] = 'text/plain'
+
+ msg = FakeyMessage(headers, (), None, body, 123, None )
+
+ c = BufferingConsumer()
+ p = imap4.MessageProducer(msg)
+ d = p.beginProducing(c)
+
+ def cbProduced(result):
+ self.assertIdentical(result, p)
+ self.assertEquals(
+ ''.join(c.buffer),
+
+ '{119}\r\n'
+ 'From: sender@host\r\n'
+ 'To: recipient@domain\r\n'
+ 'Subject: booga booga boo\r\n'
+ 'Content-Type: text/plain\r\n'
+ '\r\n'
+ + body)
+ return d.addCallback(cbProduced)
+
+
+ def testSingleMultiPart(self):
+ outerBody = ''
+ innerBody = 'Contained body message text. Squarge.'
+ headers = util.OrderedDict()
+ headers['from'] = 'sender@host'
+ headers['to'] = 'recipient@domain'
+ headers['subject'] = 'booga booga boo'
+ headers['content-type'] = 'multipart/alternative; boundary="xyz"'
+
+ innerHeaders = util.OrderedDict()
+ innerHeaders['subject'] = 'this is subject text'
+ innerHeaders['content-type'] = 'text/plain'
+ msg = FakeyMessage(headers, (), None, outerBody, 123,
+ [FakeyMessage(innerHeaders, (), None, innerBody,
+ None, None)],
+ )
+
+ c = BufferingConsumer()
+ p = imap4.MessageProducer(msg)
+ d = p.beginProducing(c)
+
+ def cbProduced(result):
+ self.failUnlessIdentical(result, p)
+
+ self.assertEquals(
+ ''.join(c.buffer),
+
+ '{239}\r\n'
+ 'From: sender@host\r\n'
+ 'To: recipient@domain\r\n'
+ 'Subject: booga booga boo\r\n'
+ 'Content-Type: multipart/alternative; boundary="xyz"\r\n'
+ '\r\n'
+ '\r\n'
+ '--xyz\r\n'
+ 'Subject: this is subject text\r\n'
+ 'Content-Type: text/plain\r\n'
+ '\r\n'
+ + innerBody
+ + '\r\n--xyz--\r\n')
+
+ return d.addCallback(cbProduced)
+
+
+ def testMultipleMultiPart(self):
+ outerBody = ''
+ innerBody1 = 'Contained body message text. Squarge.'
+ innerBody2 = 'Secondary <i>message</i> text of squarge body.'
+ headers = util.OrderedDict()
+ headers['from'] = 'sender@host'
+ headers['to'] = 'recipient@domain'
+ headers['subject'] = 'booga booga boo'
+ headers['content-type'] = 'multipart/alternative; boundary="xyz"'
+ innerHeaders = util.OrderedDict()
+ innerHeaders['subject'] = 'this is subject text'
+ innerHeaders['content-type'] = 'text/plain'
+ innerHeaders2 = util.OrderedDict()
+ innerHeaders2['subject'] = '<b>this is subject</b>'
+ innerHeaders2['content-type'] = 'text/html'
+ msg = FakeyMessage(headers, (), None, outerBody, 123, [
+ FakeyMessage(innerHeaders, (), None, innerBody1, None, None),
+ FakeyMessage(innerHeaders2, (), None, innerBody2, None, None)
+ ],
+ )
+
+ c = BufferingConsumer()
+ p = imap4.MessageProducer(msg)
+ d = p.beginProducing(c)
+
+ def cbProduced(result):
+ self.failUnlessIdentical(result, p)
+
+ self.assertEquals(
+ ''.join(c.buffer),
+
+ '{354}\r\n'
+ 'From: sender@host\r\n'
+ 'To: recipient@domain\r\n'
+ 'Subject: booga booga boo\r\n'
+ 'Content-Type: multipart/alternative; boundary="xyz"\r\n'
+ '\r\n'
+ '\r\n'
+ '--xyz\r\n'
+ 'Subject: this is subject text\r\n'
+ 'Content-Type: text/plain\r\n'
+ '\r\n'
+ + innerBody1
+ + '\r\n--xyz\r\n'
+ 'Subject: <b>this is subject</b>\r\n'
+ 'Content-Type: text/html\r\n'
+ '\r\n'
+ + innerBody2
+ + '\r\n--xyz--\r\n')
+ return d.addCallback(cbProduced)
+
+
+
+class IMAP4HelperTestCase(unittest.TestCase):
+ def testFileProducer(self):
+ b = (('x' * 1) + ('y' * 1) + ('z' * 1)) * 10
+ c = BufferingConsumer()
+ f = StringIO(b)
+ p = imap4.FileProducer(f)
+ d = p.beginProducing(c)
+
+ def cbProduced(result):
+ self.failUnlessIdentical(result, p)
+ self.assertEquals(
+ ('{%d}\r\n' % len(b))+ b,
+ ''.join(c.buffer))
+ return d.addCallback(cbProduced)
+
+ def testWildcard(self):
+ cases = [
+ ['foo/%gum/bar',
+ ['foo/bar', 'oo/lalagum/bar', 'foo/gumx/bar', 'foo/gum/baz'],
+ ['foo/xgum/bar', 'foo/gum/bar'],
+ ], ['foo/x%x/bar',
+ ['foo', 'bar', 'fuz fuz fuz', 'foo/*/bar', 'foo/xyz/bar', 'foo/xx/baz'],
+ ['foo/xyx/bar', 'foo/xx/bar', 'foo/xxxxxxxxxxxxxx/bar'],
+ ], ['foo/xyz*abc/bar',
+ ['foo/xyz/bar', 'foo/abc/bar', 'foo/xyzab/cbar', 'foo/xyza/bcbar'],
+ ['foo/xyzabc/bar', 'foo/xyz/abc/bar', 'foo/xyz/123/abc/bar'],
+ ]
+ ]
+
+ for (wildcard, fail, succeed) in cases:
+ wildcard = imap4.wildcardToRegexp(wildcard, '/')
+ for x in fail:
+ self.failIf(wildcard.match(x))
+ for x in succeed:
+ self.failUnless(wildcard.match(x))
+
+ def testWildcardNoDelim(self):
+ cases = [
+ ['foo/%gum/bar',
+ ['foo/bar', 'oo/lalagum/bar', 'foo/gumx/bar', 'foo/gum/baz'],
+ ['foo/xgum/bar', 'foo/gum/bar', 'foo/x/gum/bar'],
+ ], ['foo/x%x/bar',
+ ['foo', 'bar', 'fuz fuz fuz', 'foo/*/bar', 'foo/xyz/bar', 'foo/xx/baz'],
+ ['foo/xyx/bar', 'foo/xx/bar', 'foo/xxxxxxxxxxxxxx/bar', 'foo/x/x/bar'],
+ ], ['foo/xyz*abc/bar',
+ ['foo/xyz/bar', 'foo/abc/bar', 'foo/xyzab/cbar', 'foo/xyza/bcbar'],
+ ['foo/xyzabc/bar', 'foo/xyz/abc/bar', 'foo/xyz/123/abc/bar'],
+ ]
+ ]
+
+ for (wildcard, fail, succeed) in cases:
+ wildcard = imap4.wildcardToRegexp(wildcard, None)
+ for x in fail:
+ self.failIf(wildcard.match(x), x)
+ for x in succeed:
+ self.failUnless(wildcard.match(x), x)
+
+ def testHeaderFormatter(self):
+ cases = [
+ ({'Header1': 'Value1', 'Header2': 'Value2'}, 'Header2: Value2\r\nHeader1: Value1\r\n'),
+ ]
+
+ for (input, output) in cases:
+ self.assertEquals(imap4._formatHeaders(input), output)
+
+ def testMessageSet(self):
+ m1 = MessageSet()
+ m2 = MessageSet()
+
+ self.assertEquals(m1, m2)
+
+ m1 = m1 + (1, 3)
+ self.assertEquals(len(m1), 3)
+ self.assertEquals(list(m1), [1, 2, 3])
+
+ m2 = m2 + (1, 3)
+ self.assertEquals(m1, m2)
+ self.assertEquals(list(m1 + m2), [1, 2, 3])
+
+ def testQuotedSplitter(self):
+ cases = [
+ '''Hello World''',
+ '''Hello "World!"''',
+ '''World "Hello" "How are you?"''',
+ '''"Hello world" How "are you?"''',
+ '''foo bar "baz buz" NIL''',
+ '''foo bar "baz buz" "NIL"''',
+ '''foo NIL "baz buz" bar''',
+ '''foo "NIL" "baz buz" bar''',
+ '''"NIL" bar "baz buz" foo''',
+ 'oo \\"oo\\" oo',
+ '"oo \\"oo\\" oo"',
+ 'oo \t oo',
+ '"oo \t oo"',
+ 'oo \\t oo',
+ '"oo \\t oo"',
+ 'oo \o oo',
+ '"oo \o oo"',
+ 'oo \\o oo',
+ '"oo \\o oo"',
+ ]
+
+ answers = [
+ ['Hello', 'World'],
+ ['Hello', 'World!'],
+ ['World', 'Hello', 'How are you?'],
+ ['Hello world', 'How', 'are you?'],
+ ['foo', 'bar', 'baz buz', None],
+ ['foo', 'bar', 'baz buz', 'NIL'],
+ ['foo', None, 'baz buz', 'bar'],
+ ['foo', 'NIL', 'baz buz', 'bar'],
+ ['NIL', 'bar', 'baz buz', 'foo'],
+ ['oo', '"oo"', 'oo'],
+ ['oo "oo" oo'],
+ ['oo', 'oo'],
+ ['oo \t oo'],
+ ['oo', '\\t', 'oo'],
+ ['oo \\t oo'],
+ ['oo', '\o', 'oo'],
+ ['oo \o oo'],
+ ['oo', '\\o', 'oo'],
+ ['oo \\o oo'],
+
+ ]
+
+ errors = [
+ '"mismatched quote',
+ 'mismatched quote"',
+ 'mismatched"quote',
+ '"oops here is" another"',
+ ]
+
+ for s in errors:
+ self.assertRaises(imap4.MismatchedQuoting, imap4.splitQuoted, s)
+
+ for (case, expected) in zip(cases, answers):
+ self.assertEquals(imap4.splitQuoted(case), expected)
+
+
+ def testStringCollapser(self):
+ cases = [
+ ['a', 'b', 'c', 'd', 'e'],
+ ['a', ' ', '"', 'b', 'c', ' ', '"', ' ', 'd', 'e'],
+ [['a', 'b', 'c'], 'd', 'e'],
+ ['a', ['b', 'c', 'd'], 'e'],
+ ['a', 'b', ['c', 'd', 'e']],
+ ['"', 'a', ' ', '"', ['b', 'c', 'd'], '"', ' ', 'e', '"'],
+ ['a', ['"', ' ', 'b', 'c', ' ', ' ', '"'], 'd', 'e'],
+ ]
+
+ answers = [
+ ['abcde'],
+ ['a', 'bc ', 'de'],
+ [['abc'], 'de'],
+ ['a', ['bcd'], 'e'],
+ ['ab', ['cde']],
+ ['a ', ['bcd'], ' e'],
+ ['a', [' bc '], 'de'],
+ ]
+
+ for (case, expected) in zip(cases, answers):
+ self.assertEquals(imap4.collapseStrings(case), expected)
+
+ def testParenParser(self):
+ s = '\r\n'.join(['xx'] * 4)
+ cases = [
+ '(BODY.PEEK[HEADER.FIELDS.NOT (subject bcc cc)] {%d}\r\n%s)' % (len(s), s,),
+
+# '(FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" '
+# 'RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" '
+# '"IMAP4rev1 WG mtg summary and minutes" '
+# '(("Terry Gray" NIL "gray" "cac.washington.edu")) '
+# '(("Terry Gray" NIL "gray" "cac.washington.edu")) '
+# '(("Terry Gray" NIL "gray" "cac.washington.edu")) '
+# '((NIL NIL "imap" "cac.washington.edu")) '
+# '((NIL NIL "minutes" "CNRI.Reston.VA.US") '
+# '("John Klensin" NIL "KLENSIN" "INFOODS.MIT.EDU")) NIL NIL '
+# '"<B27397-0100000@cac.washington.edu>") '
+# 'BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 92))',
+
+ '(FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" '
+ 'RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" '
+ '"IMAP4rev1 WG mtg summary and minutes" '
+ '(("Terry Gray" NIL gray cac.washington.edu)) '
+ '(("Terry Gray" NIL gray cac.washington.edu)) '
+ '(("Terry Gray" NIL gray cac.washington.edu)) '
+ '((NIL NIL imap cac.washington.edu)) '
+ '((NIL NIL minutes CNRI.Reston.VA.US) '
+ '("John Klensin" NIL KLENSIN INFOODS.MIT.EDU)) NIL NIL '
+ '<B27397-0100000@cac.washington.edu>) '
+ 'BODY (TEXT PLAIN (CHARSET US-ASCII) NIL NIL 7BIT 3028 92))',
+ '("oo \\"oo\\" oo")',
+ '("oo \\\\ oo")',
+ '("oo \\ oo")',
+ '("oo \\o")',
+ '("oo \o")',
+ '(oo \o)',
+ '(oo \\o)',
+
+ ]
+
+ answers = [
+ ['BODY.PEEK', ['HEADER.FIELDS.NOT', ['subject', 'bcc', 'cc']], s],
+
+ ['FLAGS', [r'\Seen'], 'INTERNALDATE',
+ '17-Jul-1996 02:44:25 -0700', 'RFC822.SIZE', '4286', 'ENVELOPE',
+ ['Wed, 17 Jul 1996 02:23:25 -0700 (PDT)',
+ 'IMAP4rev1 WG mtg summary and minutes', [["Terry Gray", None,
+ "gray", "cac.washington.edu"]], [["Terry Gray", None,
+ "gray", "cac.washington.edu"]], [["Terry Gray", None,
+ "gray", "cac.washington.edu"]], [[None, None, "imap",
+ "cac.washington.edu"]], [[None, None, "minutes",
+ "CNRI.Reston.VA.US"], ["John Klensin", None, "KLENSIN",
+ "INFOODS.MIT.EDU"]], None, None,
+ "<B27397-0100000@cac.washington.edu>"], "BODY", ["TEXT", "PLAIN",
+ ["CHARSET", "US-ASCII"], None, None, "7BIT", "3028", "92"]],
+ ['oo "oo" oo'],
+ ['oo \\\\ oo'],
+ ['oo \\ oo'],
+ ['oo \\o'],
+ ['oo \o'],
+ ['oo', '\o'],
+ ['oo', '\\o'],
+ ]
+
+ for (case, expected) in zip(cases, answers):
+ self.assertEquals(imap4.parseNestedParens(case), [expected])
+
+ # XXX This code used to work, but changes occurred within the
+ # imap4.py module which made it no longer necessary for *all* of it
+ # to work. In particular, only the part that makes
+ # 'BODY.PEEK[HEADER.FIELDS.NOT (Subject Bcc Cc)]' come out correctly
+ # no longer needs to work. So, I am loathe to delete the entire
+ # section of the test. --exarkun
+ #
+
+# for (case, expected) in zip(answers, cases):
+# self.assertEquals('(' + imap4.collapseNestedLists(case) + ')', expected)
+
+ def testFetchParserSimple(self):
+ cases = [
+ ['ENVELOPE', 'Envelope'],
+ ['FLAGS', 'Flags'],
+ ['INTERNALDATE', 'InternalDate'],
+ ['RFC822.HEADER', 'RFC822Header'],
+ ['RFC822.SIZE', 'RFC822Size'],
+ ['RFC822.TEXT', 'RFC822Text'],
+ ['RFC822', 'RFC822'],
+ ['UID', 'UID'],
+ ['BODYSTRUCTURE', 'BodyStructure'],
+ ]
+
+ for (inp, outp) in cases:
+ p = imap4._FetchParser()
+ p.parseString(inp)
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], getattr(p, outp)))
+
+ def testFetchParserMacros(self):
+ cases = [
+ ['ALL', (4, ['flags', 'internaldate', 'rfc822.size', 'envelope'])],
+ ['FULL', (5, ['flags', 'internaldate', 'rfc822.size', 'envelope', 'body'])],
+ ['FAST', (3, ['flags', 'internaldate', 'rfc822.size'])],
+ ]
+
+ for (inp, outp) in cases:
+ p = imap4._FetchParser()
+ p.parseString(inp)
+ self.assertEquals(len(p.result), outp[0])
+ p = [str(p).lower() for p in p.result]
+ p.sort()
+ outp[1].sort()
+ self.assertEquals(p, outp[1])
+
+ def testFetchParserBody(self):
+ P = imap4._FetchParser
+
+ p = P()
+ p.parseString('BODY')
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], p.Body))
+ self.assertEquals(p.result[0].peek, False)
+ self.assertEquals(p.result[0].header, None)
+ self.assertEquals(str(p.result[0]), 'BODY')
+
+ p = P()
+ p.parseString('BODY.PEEK')
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], p.Body))
+ self.assertEquals(p.result[0].peek, True)
+ self.assertEquals(str(p.result[0]), 'BODY')
+
+ p = P()
+ p.parseString('BODY[]')
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], p.Body))
+ self.assertEquals(p.result[0].empty, True)
+ self.assertEquals(str(p.result[0]), 'BODY[]')
+
+ p = P()
+ p.parseString('BODY[HEADER]')
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], p.Body))
+ self.assertEquals(p.result[0].peek, False)
+ self.failUnless(isinstance(p.result[0].header, p.Header))
+ self.assertEquals(p.result[0].header.negate, True)
+ self.assertEquals(p.result[0].header.fields, ())
+ self.assertEquals(p.result[0].empty, False)
+ self.assertEquals(str(p.result[0]), 'BODY[HEADER]')
+
+ p = P()
+ p.parseString('BODY.PEEK[HEADER]')
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], p.Body))
+ self.assertEquals(p.result[0].peek, True)
+ self.failUnless(isinstance(p.result[0].header, p.Header))
+ self.assertEquals(p.result[0].header.negate, True)
+ self.assertEquals(p.result[0].header.fields, ())
+ self.assertEquals(p.result[0].empty, False)
+ self.assertEquals(str(p.result[0]), 'BODY[HEADER]')
+
+ p = P()
+ p.parseString('BODY[HEADER.FIELDS (Subject Cc Message-Id)]')
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], p.Body))
+ self.assertEquals(p.result[0].peek, False)
+ self.failUnless(isinstance(p.result[0].header, p.Header))
+ self.assertEquals(p.result[0].header.negate, False)
+ self.assertEquals(p.result[0].header.fields, ['SUBJECT', 'CC', 'MESSAGE-ID'])
+ self.assertEquals(p.result[0].empty, False)
+ self.assertEquals(str(p.result[0]), 'BODY[HEADER.FIELDS (Subject Cc Message-Id)]')
+
+ p = P()
+ p.parseString('BODY.PEEK[HEADER.FIELDS (Subject Cc Message-Id)]')
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], p.Body))
+ self.assertEquals(p.result[0].peek, True)
+ self.failUnless(isinstance(p.result[0].header, p.Header))
+ self.assertEquals(p.result[0].header.negate, False)
+ self.assertEquals(p.result[0].header.fields, ['SUBJECT', 'CC', 'MESSAGE-ID'])
+ self.assertEquals(p.result[0].empty, False)
+ self.assertEquals(str(p.result[0]), 'BODY[HEADER.FIELDS (Subject Cc Message-Id)]')
+
+ p = P()
+ p.parseString('BODY.PEEK[HEADER.FIELDS.NOT (Subject Cc Message-Id)]')
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], p.Body))
+ self.assertEquals(p.result[0].peek, True)
+ self.failUnless(isinstance(p.result[0].header, p.Header))
+ self.assertEquals(p.result[0].header.negate, True)
+ self.assertEquals(p.result[0].header.fields, ['SUBJECT', 'CC', 'MESSAGE-ID'])
+ self.assertEquals(p.result[0].empty, False)
+ self.assertEquals(str(p.result[0]), 'BODY[HEADER.FIELDS.NOT (Subject Cc Message-Id)]')
+
+ p = P()
+ p.parseString('BODY[1.MIME]<10.50>')
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], p.Body))
+ self.assertEquals(p.result[0].peek, False)
+ self.failUnless(isinstance(p.result[0].mime, p.MIME))
+ self.assertEquals(p.result[0].part, (0,))
+ self.assertEquals(p.result[0].partialBegin, 10)
+ self.assertEquals(p.result[0].partialLength, 50)
+ self.assertEquals(p.result[0].empty, False)
+ self.assertEquals(str(p.result[0]), 'BODY[1.MIME]<10.50>')
+
+ p = P()
+ p.parseString('BODY.PEEK[1.3.9.11.HEADER.FIELDS.NOT (Message-Id Date)]<103.69>')
+ self.assertEquals(len(p.result), 1)
+ self.failUnless(isinstance(p.result[0], p.Body))
+ self.assertEquals(p.result[0].peek, True)
+ self.failUnless(isinstance(p.result[0].header, p.Header))
+ self.assertEquals(p.result[0].part, (0, 2, 8, 10))
+ self.assertEquals(p.result[0].header.fields, ['MESSAGE-ID', 'DATE'])
+ self.assertEquals(p.result[0].partialBegin, 103)
+ self.assertEquals(p.result[0].partialLength, 69)
+ self.assertEquals(p.result[0].empty, False)
+ self.assertEquals(str(p.result[0]), 'BODY[1.3.9.11.HEADER.FIELDS.NOT (Message-Id Date)]<103.69>')
+
+
+ def testFiles(self):
+ inputStructure = [
+ 'foo', 'bar', 'baz', StringIO('this is a file\r\n'), 'buz'
+ ]
+
+ output = '"foo" "bar" "baz" {16}\r\nthis is a file\r\n "buz"'
+
+ self.assertEquals(imap4.collapseNestedLists(inputStructure), output)
+
+ def testQuoteAvoider(self):
+ input = [
+ 'foo', imap4.DontQuoteMe('bar'), "baz", StringIO('this is a file\r\n'),
+ imap4.DontQuoteMe('buz'), ""
+ ]
+
+ output = '"foo" bar "baz" {16}\r\nthis is a file\r\n buz ""'
+
+ self.assertEquals(imap4.collapseNestedLists(input), output)
+
+ def testLiterals(self):
+ cases = [
+ ('({10}\r\n0123456789)', [['0123456789']]),
+ ]
+
+ for (case, expected) in cases:
+ self.assertEquals(imap4.parseNestedParens(case), expected)
+
+ def testQueryBuilder(self):
+ inputs = [
+ imap4.Query(flagged=1),
+ imap4.Query(sorted=1, unflagged=1, deleted=1),
+ imap4.Or(imap4.Query(flagged=1), imap4.Query(deleted=1)),
+ imap4.Query(before='today'),
+ imap4.Or(
+ imap4.Query(deleted=1),
+ imap4.Query(unseen=1),
+ imap4.Query(new=1)
+ ),
+ imap4.Or(
+ imap4.Not(
+ imap4.Or(
+ imap4.Query(sorted=1, since='yesterday', smaller=1000),
+ imap4.Query(sorted=1, before='tuesday', larger=10000),
+ imap4.Query(sorted=1, unseen=1, deleted=1, before='today'),
+ imap4.Not(
+ imap4.Query(subject='spam')
+ ),
+ ),
+ ),
+ imap4.Not(
+ imap4.Query(uid='1:5')
+ ),
+ )
+ ]
+
+ outputs = [
+ 'FLAGGED',
+ '(DELETED UNFLAGGED)',
+ '(OR FLAGGED DELETED)',
+ '(BEFORE "today")',
+ '(OR DELETED (OR UNSEEN NEW))',
+ '(OR (NOT (OR (SINCE "yesterday" SMALLER 1000) ' # Continuing
+ '(OR (BEFORE "tuesday" LARGER 10000) (OR (BEFORE ' # Some more
+ '"today" DELETED UNSEEN) (NOT (SUBJECT "spam")))))) ' # And more
+ '(NOT (UID 1:5)))',
+ ]
+
+ for (query, expected) in zip(inputs, outputs):
+ self.assertEquals(query, expected)
+
+ def testIdListParser(self):
+ inputs = [
+ '1:*',
+ '5:*',
+ '1:2,5:*',
+ '1',
+ '1,2',
+ '1,3,5',
+ '1:10',
+ '1:10,11',
+ '1:5,10:20',
+ '1,5:10',
+ '1,5:10,15:20',
+ '1:10,15,20:25',
+ ]
+
+ outputs = [
+ MessageSet(1, None),
+ MessageSet(5, None),
+ MessageSet(5, None) + MessageSet(1, 2),
+ MessageSet(1),
+ MessageSet(1, 2),
+ MessageSet(1) + MessageSet(3) + MessageSet(5),
+ MessageSet(1, 10),
+ MessageSet(1, 11),
+ MessageSet(1, 5) + MessageSet(10, 20),
+ MessageSet(1) + MessageSet(5, 10),
+ MessageSet(1) + MessageSet(5, 10) + MessageSet(15, 20),
+ MessageSet(1, 10) + MessageSet(15) + MessageSet(20, 25),
+ ]
+
+ lengths = [
+ None, None, None,
+ 1, 2, 3, 10, 11, 16, 7, 13, 17,
+ ]
+
+ for (input, expected) in zip(inputs, outputs):
+ self.assertEquals(imap4.parseIdList(input), expected)
+
+ for (input, expected) in zip(inputs, lengths):
+ try:
+ L = len(imap4.parseIdList(input))
+ except TypeError:
+ L = None
+ self.assertEquals(L, expected,
+ "len(%r) = %r != %r" % (input, L, expected))
+
+class SimpleMailbox:
+ implements(imap4.IMailboxInfo, imap4.IMailbox, imap4.ICloseableMailbox)
+
+ flags = ('\\Flag1', 'Flag2', '\\AnotherSysFlag', 'LastFlag')
+ messages = []
+ mUID = 0
+ rw = 1
+ closed = False
+
+ def __init__(self):
+ self.listeners = []
+ self.addListener = self.listeners.append
+ self.removeListener = self.listeners.remove
+
+ def getFlags(self):
+ return self.flags
+
+ def getUIDValidity(self):
+ return 42
+
+ def getUIDNext(self):
+ return len(self.messages) + 1
+
+ def getMessageCount(self):
+ return 9
+
+ def getRecentCount(self):
+ return 3
+
+ def getUnseenCount(self):
+ return 4
+
+ def isWriteable(self):
+ return self.rw
+
+ def destroy(self):
+ pass
+
+ def getHierarchicalDelimiter(self):
+ return '/'
+
+ def requestStatus(self, names):
+ r = {}
+ if 'MESSAGES' in names:
+ r['MESSAGES'] = self.getMessageCount()
+ if 'RECENT' in names:
+ r['RECENT'] = self.getRecentCount()
+ if 'UIDNEXT' in names:
+ r['UIDNEXT'] = self.getMessageCount() + 1
+ if 'UIDVALIDITY' in names:
+ r['UIDVALIDITY'] = self.getUID()
+ if 'UNSEEN' in names:
+ r['UNSEEN'] = self.getUnseenCount()
+ return defer.succeed(r)
+
+ def addMessage(self, message, flags, date = None):
+ self.messages.append((message, flags, date, self.mUID))
+ self.mUID += 1
+ return defer.succeed(None)
+
+ def expunge(self):
+ delete = []
+ for i in self.messages:
+ if '\\Deleted' in i[1]:
+ delete.append(i)
+ for i in delete:
+ self.messages.remove(i)
+ return [i[3] for i in delete]
+
+ def close(self):
+ self.closed = True
+
+class Account(imap4.MemoryAccount):
+ mailboxFactory = SimpleMailbox
+ def _emptyMailbox(self, name, id):
+ return self.mailboxFactory()
+
+ def select(self, name, rw=1):
+ mbox = imap4.MemoryAccount.select(self, name)
+ if mbox is not None:
+ mbox.rw = rw
+ return mbox
+
+class SimpleServer(imap4.IMAP4Server):
+ def __init__(self, *args, **kw):
+ imap4.IMAP4Server.__init__(self, *args, **kw)
+ realm = TestRealm()
+ realm.theAccount = Account('testuser')
+ portal = cred.portal.Portal(realm)
+ c = cred.checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ self.checker = c
+ self.portal = portal
+ portal.registerChecker(c)
+ self.timeoutTest = False
+
+ def lineReceived(self, line):
+ if self.timeoutTest:
+ #Do not send a respones
+ return
+
+ imap4.IMAP4Server.lineReceived(self, line)
+
+ _username = 'testuser'
+ _password = 'password-test'
+ def authenticateLogin(self, username, password):
+ if username == self._username and password == self._password:
+ return imap4.IAccount, self.theAccount, lambda: None
+ raise cred.error.UnauthorizedLogin()
+
+
+class SimpleClient(imap4.IMAP4Client):
+ def __init__(self, deferred, contextFactory = None):
+ imap4.IMAP4Client.__init__(self, contextFactory)
+ self.deferred = deferred
+ self.events = []
+
+ def serverGreeting(self, caps):
+ self.deferred.callback(None)
+
+ def modeChanged(self, writeable):
+ self.events.append(['modeChanged', writeable])
+ self.transport.loseConnection()
+
+ def flagsChanged(self, newFlags):
+ self.events.append(['flagsChanged', newFlags])
+ self.transport.loseConnection()
+
+ def newMessages(self, exists, recent):
+ self.events.append(['newMessages', exists, recent])
+ self.transport.loseConnection()
+
+
+
+class IMAP4HelperMixin:
+ serverCTX = None
+ clientCTX = None
+
+ def setUp(self):
+ d = defer.Deferred()
+ self.server = SimpleServer(contextFactory=self.serverCTX)
+ self.client = SimpleClient(d, contextFactory=self.clientCTX)
+ self.connected = d
+
+ SimpleMailbox.messages = []
+ theAccount = Account('testuser')
+ theAccount.mboxType = SimpleMailbox
+ SimpleServer.theAccount = theAccount
+
+ def tearDown(self):
+ del self.server
+ del self.client
+ del self.connected
+
+ def _cbStopClient(self, ignore):
+ self.client.transport.loseConnection()
+
+ def _ebGeneral(self, failure):
+ self.client.transport.loseConnection()
+ self.server.transport.loseConnection()
+ failure.raiseException()
+
+ def loopback(self):
+ return loopback.loopbackAsync(self.server, self.client)
+
+class IMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase):
+ def testCapability(self):
+ caps = {}
+ def getCaps():
+ def gotCaps(c):
+ caps.update(c)
+ self.server.transport.loseConnection()
+ return self.client.getCapabilities().addCallback(gotCaps)
+ d1 = self.connected.addCallback(strip(getCaps)).addErrback(self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ expected = {'IMAP4rev1': None, 'NAMESPACE': None, 'IDLE': None}
+ return d.addCallback(lambda _: self.assertEquals(expected, caps))
+
+ def testCapabilityWithAuth(self):
+ caps = {}
+ self.server.challengers['CRAM-MD5'] = cred.credentials.CramMD5Credentials
+ def getCaps():
+ def gotCaps(c):
+ caps.update(c)
+ self.server.transport.loseConnection()
+ return self.client.getCapabilities().addCallback(gotCaps)
+ d1 = self.connected.addCallback(strip(getCaps)).addErrback(self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+
+ expCap = {'IMAP4rev1': None, 'NAMESPACE': None,
+ 'IDLE': None, 'AUTH': ['CRAM-MD5']}
+
+ return d.addCallback(lambda _: self.assertEquals(expCap, caps))
+
+ def testLogout(self):
+ self.loggedOut = 0
+ def logout():
+ def setLoggedOut():
+ self.loggedOut = 1
+ self.client.logout().addCallback(strip(setLoggedOut))
+ self.connected.addCallback(strip(logout)).addErrback(self._ebGeneral)
+ d = self.loopback()
+ return d.addCallback(lambda _: self.assertEquals(self.loggedOut, 1))
+
+ def testNoop(self):
+ self.responses = None
+ def noop():
+ def setResponses(responses):
+ self.responses = responses
+ self.server.transport.loseConnection()
+ self.client.noop().addCallback(setResponses)
+ self.connected.addCallback(strip(noop)).addErrback(self._ebGeneral)
+ d = self.loopback()
+ return d.addCallback(lambda _: self.assertEquals(self.responses, []))
+
+ def testLogin(self):
+ def login():
+ d = self.client.login('testuser', 'password-test')
+ d.addCallback(self._cbStopClient)
+ d1 = self.connected.addCallback(strip(login)).addErrback(self._ebGeneral)
+ d = defer.gatherResults([d1, self.loopback()])
+ return d.addCallback(self._cbTestLogin)
+
+ def _cbTestLogin(self, ignored):
+ self.assertEquals(self.server.account, SimpleServer.theAccount)
+ self.assertEquals(self.server.state, 'auth')
+
+ def testFailedLogin(self):
+ def login():
+ d = self.client.login('testuser', 'wrong-password')
+ d.addBoth(self._cbStopClient)
+
+ d1 = self.connected.addCallback(strip(login)).addErrback(self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ return d.addCallback(self._cbTestFailedLogin)
+
+ def _cbTestFailedLogin(self, ignored):
+ self.assertEquals(self.server.account, None)
+ self.assertEquals(self.server.state, 'unauth')
+
+
+ def testLoginRequiringQuoting(self):
+ self.server._username = '{test}user'
+ self.server._password = '{test}password'
+
+ def login():
+ d = self.client.login('{test}user', '{test}password')
+ d.addBoth(self._cbStopClient)
+
+ d1 = self.connected.addCallback(strip(login)).addErrback(self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestLoginRequiringQuoting)
+
+ def _cbTestLoginRequiringQuoting(self, ignored):
+ self.assertEquals(self.server.account, SimpleServer.theAccount)
+ self.assertEquals(self.server.state, 'auth')
+
+
+ def testNamespace(self):
+ self.namespaceArgs = None
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def namespace():
+ def gotNamespace(args):
+ self.namespaceArgs = args
+ self._cbStopClient(None)
+ return self.client.namespace().addCallback(gotNamespace)
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(namespace))
+ d1.addErrback(self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ d.addCallback(lambda _: self.assertEquals(self.namespaceArgs,
+ [[['', '/']], [], []]))
+ return d
+
+ def testSelect(self):
+ SimpleServer.theAccount.addMailbox('test-mailbox')
+ self.selectedArgs = None
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def select():
+ def selected(args):
+ self.selectedArgs = args
+ self._cbStopClient(None)
+ d = self.client.select('test-mailbox')
+ d.addCallback(selected)
+ return d
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(select))
+ d1.addErrback(self._ebGeneral)
+ d2 = self.loopback()
+ return defer.gatherResults([d1, d2]).addCallback(self._cbTestSelect)
+
+ def _cbTestSelect(self, ignored):
+ mbox = SimpleServer.theAccount.mailboxes['TEST-MAILBOX']
+ self.assertEquals(self.server.mbox, mbox)
+ self.assertEquals(self.selectedArgs, {
+ 'EXISTS': 9, 'RECENT': 3, 'UIDVALIDITY': 42,
+ 'FLAGS': ('\\Flag1', 'Flag2', '\\AnotherSysFlag', 'LastFlag'),
+ 'READ-WRITE': 1
+ })
+
+
+ def test_examine(self):
+ """
+ L{IMAP4Client.examine} issues an I{EXAMINE} command to the server and
+ returns a L{Deferred} which fires with a C{dict} with as many of the
+ following keys as the server includes in its response: C{'FLAGS'},
+ C{'EXISTS'}, C{'RECENT'}, C{'UNSEEN'}, C{'READ-WRITE'}, C{'READ-ONLY'},
+ C{'UIDVALIDITY'}, and C{'PERMANENTFLAGS'}.
+
+ Unfortunately the server doesn't generate all of these so it's hard to
+ test the client's handling of them here. See
+ L{IMAP4ClientExamineTests} below.
+
+ See U{RFC 3501<http://www.faqs.org/rfcs/rfc3501.html>}, section 6.3.2,
+ for details.
+ """
+ SimpleServer.theAccount.addMailbox('test-mailbox')
+ self.examinedArgs = None
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def examine():
+ def examined(args):
+ self.examinedArgs = args
+ self._cbStopClient(None)
+ d = self.client.examine('test-mailbox')
+ d.addCallback(examined)
+ return d
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(examine))
+ d1.addErrback(self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ return d.addCallback(self._cbTestExamine)
+
+
+ def _cbTestExamine(self, ignored):
+ mbox = SimpleServer.theAccount.mailboxes['TEST-MAILBOX']
+ self.assertEquals(self.server.mbox, mbox)
+ self.assertEquals(self.examinedArgs, {
+ 'EXISTS': 9, 'RECENT': 3, 'UIDVALIDITY': 42,
+ 'FLAGS': ('\\Flag1', 'Flag2', '\\AnotherSysFlag', 'LastFlag'),
+ 'READ-WRITE': False})
+
+
+ def testCreate(self):
+ succeed = ('testbox', 'test/box', 'test/', 'test/box/box', 'INBOX')
+ fail = ('testbox', 'test/box')
+
+ def cb(): self.result.append(1)
+ def eb(failure): self.result.append(0)
+
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def create():
+ for name in succeed + fail:
+ d = self.client.create(name)
+ d.addCallback(strip(cb)).addErrback(eb)
+ d.addCallbacks(self._cbStopClient, self._ebGeneral)
+
+ self.result = []
+ d1 = self.connected.addCallback(strip(login)).addCallback(strip(create))
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ return d.addCallback(self._cbTestCreate, succeed, fail)
+
+ def _cbTestCreate(self, ignored, succeed, fail):
+ self.assertEquals(self.result, [1] * len(succeed) + [0] * len(fail))
+ mbox = SimpleServer.theAccount.mailboxes.keys()
+ answers = ['inbox', 'testbox', 'test/box', 'test', 'test/box/box']
+ mbox.sort()
+ answers.sort()
+ self.assertEquals(mbox, [a.upper() for a in answers])
+
+ def testDelete(self):
+ SimpleServer.theAccount.addMailbox('delete/me')
+
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def delete():
+ return self.client.delete('delete/me')
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(delete), self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ d.addCallback(lambda _:
+ self.assertEquals(SimpleServer.theAccount.mailboxes.keys(), []))
+ return d
+
+ def testIllegalInboxDelete(self):
+ self.stashed = None
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def delete():
+ return self.client.delete('inbox')
+ def stash(result):
+ self.stashed = result
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(delete), self._ebGeneral)
+ d1.addBoth(stash)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ d.addCallback(lambda _: self.failUnless(isinstance(self.stashed,
+ failure.Failure)))
+ return d
+
+
+ def testNonExistentDelete(self):
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def delete():
+ return self.client.delete('delete/me')
+ def deleteFailed(failure):
+ self.failure = failure
+
+ self.failure = None
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(delete)).addErrback(deleteFailed)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ d.addCallback(lambda _: self.assertEquals(str(self.failure.value),
+ 'No such mailbox'))
+ return d
+
+
+ def testIllegalDelete(self):
+ m = SimpleMailbox()
+ m.flags = (r'\Noselect',)
+ SimpleServer.theAccount.addMailbox('delete', m)
+ SimpleServer.theAccount.addMailbox('delete/me')
+
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def delete():
+ return self.client.delete('delete')
+ def deleteFailed(failure):
+ self.failure = failure
+
+ self.failure = None
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(delete)).addErrback(deleteFailed)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ expected = "Hierarchically inferior mailboxes exist and \\Noselect is set"
+ d.addCallback(lambda _:
+ self.assertEquals(str(self.failure.value), expected))
+ return d
+
+ def testRename(self):
+ SimpleServer.theAccount.addMailbox('oldmbox')
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def rename():
+ return self.client.rename('oldmbox', 'newname')
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(rename), self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ d.addCallback(lambda _:
+ self.assertEquals(SimpleServer.theAccount.mailboxes.keys(),
+ ['NEWNAME']))
+ return d
+
+ def testIllegalInboxRename(self):
+ self.stashed = None
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def rename():
+ return self.client.rename('inbox', 'frotz')
+ def stash(stuff):
+ self.stashed = stuff
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(rename), self._ebGeneral)
+ d1.addBoth(stash)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ d.addCallback(lambda _:
+ self.failUnless(isinstance(self.stashed, failure.Failure)))
+ return d
+
+ def testHierarchicalRename(self):
+ SimpleServer.theAccount.create('oldmbox/m1')
+ SimpleServer.theAccount.create('oldmbox/m2')
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def rename():
+ return self.client.rename('oldmbox', 'newname')
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(rename), self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ return d.addCallback(self._cbTestHierarchicalRename)
+
+ def _cbTestHierarchicalRename(self, ignored):
+ mboxes = SimpleServer.theAccount.mailboxes.keys()
+ expected = ['newname', 'newname/m1', 'newname/m2']
+ mboxes.sort()
+ self.assertEquals(mboxes, [s.upper() for s in expected])
+
+ def testSubscribe(self):
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def subscribe():
+ return self.client.subscribe('this/mbox')
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(subscribe), self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ d.addCallback(lambda _:
+ self.assertEquals(SimpleServer.theAccount.subscriptions,
+ ['THIS/MBOX']))
+ return d
+
+ def testUnsubscribe(self):
+ SimpleServer.theAccount.subscriptions = ['THIS/MBOX', 'THAT/MBOX']
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def unsubscribe():
+ return self.client.unsubscribe('this/mbox')
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(unsubscribe), self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ d.addCallback(lambda _:
+ self.assertEquals(SimpleServer.theAccount.subscriptions,
+ ['THAT/MBOX']))
+ return d
+
+ def _listSetup(self, f):
+ SimpleServer.theAccount.addMailbox('root/subthing')
+ SimpleServer.theAccount.addMailbox('root/another-thing')
+ SimpleServer.theAccount.addMailbox('non-root/subthing')
+
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def listed(answers):
+ self.listed = answers
+
+ self.listed = None
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(f), self._ebGeneral)
+ d1.addCallbacks(listed, self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ return defer.gatherResults([d1, d2]).addCallback(lambda _: self.listed)
+
+ def testList(self):
+ def list():
+ return self.client.list('root', '%')
+ d = self._listSetup(list)
+ d.addCallback(lambda listed: self.assertEquals(
+ sortNest(listed),
+ sortNest([
+ (SimpleMailbox.flags, "/", "ROOT/SUBTHING"),
+ (SimpleMailbox.flags, "/", "ROOT/ANOTHER-THING")
+ ])
+ ))
+ return d
+
+ def testLSub(self):
+ SimpleServer.theAccount.subscribe('ROOT/SUBTHING')
+ def lsub():
+ return self.client.lsub('root', '%')
+ d = self._listSetup(lsub)
+ d.addCallback(self.assertEquals,
+ [(SimpleMailbox.flags, "/", "ROOT/SUBTHING")])
+ return d
+
+ def testStatus(self):
+ SimpleServer.theAccount.addMailbox('root/subthing')
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def status():
+ return self.client.status('root/subthing', 'MESSAGES', 'UIDNEXT', 'UNSEEN')
+ def statused(result):
+ self.statused = result
+
+ self.statused = None
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(status), self._ebGeneral)
+ d1.addCallbacks(statused, self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ d.addCallback(lambda _: self.assertEquals(
+ self.statused,
+ {'MESSAGES': 9, 'UIDNEXT': '10', 'UNSEEN': 4}
+ ))
+ return d
+
+ def testFailedStatus(self):
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def status():
+ return self.client.status('root/nonexistent', 'MESSAGES', 'UIDNEXT', 'UNSEEN')
+ def statused(result):
+ self.statused = result
+ def failed(failure):
+ self.failure = failure
+
+ self.statused = self.failure = None
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(status), self._ebGeneral)
+ d1.addCallbacks(statused, failed)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ return defer.gatherResults([d1, d2]).addCallback(self._cbTestFailedStatus)
+
+ def _cbTestFailedStatus(self, ignored):
+ self.assertEquals(
+ self.statused, None
+ )
+ self.assertEquals(
+ self.failure.value.args,
+ ('Could not open mailbox',)
+ )
+
+ def testFullAppend(self):
+ infile = util.sibpath(__file__, 'rfc822.message')
+ message = open(infile)
+ SimpleServer.theAccount.addMailbox('root/subthing')
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def append():
+ return self.client.append(
+ 'root/subthing',
+ message,
+ ('\\SEEN', '\\DELETED'),
+ 'Tue, 17 Jun 2003 11:22:16 -0600 (MDT)',
+ )
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(append), self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ return d.addCallback(self._cbTestFullAppend, infile)
+
+ def _cbTestFullAppend(self, ignored, infile):
+ mb = SimpleServer.theAccount.mailboxes['ROOT/SUBTHING']
+ self.assertEquals(1, len(mb.messages))
+ self.assertEquals(
+ (['\\SEEN', '\\DELETED'], 'Tue, 17 Jun 2003 11:22:16 -0600 (MDT)', 0),
+ mb.messages[0][1:]
+ )
+ self.assertEquals(open(infile).read(), mb.messages[0][0].getvalue())
+
+ def testPartialAppend(self):
+ infile = util.sibpath(__file__, 'rfc822.message')
+ message = open(infile)
+ SimpleServer.theAccount.addMailbox('PARTIAL/SUBTHING')
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def append():
+ message = file(infile)
+ return self.client.sendCommand(
+ imap4.Command(
+ 'APPEND',
+ 'PARTIAL/SUBTHING (\\SEEN) "Right now" {%d}' % os.path.getsize(infile),
+ (), self.client._IMAP4Client__cbContinueAppend, message
+ )
+ )
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(append), self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ return d.addCallback(self._cbTestPartialAppend, infile)
+
+ def _cbTestPartialAppend(self, ignored, infile):
+ mb = SimpleServer.theAccount.mailboxes['PARTIAL/SUBTHING']
+ self.assertEquals(1, len(mb.messages))
+ self.assertEquals(
+ (['\\SEEN'], 'Right now', 0),
+ mb.messages[0][1:]
+ )
+ self.assertEquals(open(infile).read(), mb.messages[0][0].getvalue())
+
+ def testCheck(self):
+ SimpleServer.theAccount.addMailbox('root/subthing')
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def select():
+ return self.client.select('root/subthing')
+ def check():
+ return self.client.check()
+
+ d = self.connected.addCallback(strip(login))
+ d.addCallbacks(strip(select), self._ebGeneral)
+ d.addCallbacks(strip(check), self._ebGeneral)
+ d.addCallbacks(self._cbStopClient, self._ebGeneral)
+ return self.loopback()
+
+ # Okay, that was fun
+
+ def testClose(self):
+ m = SimpleMailbox()
+ m.messages = [
+ ('Message 1', ('\\Deleted', 'AnotherFlag'), None, 0),
+ ('Message 2', ('AnotherFlag',), None, 1),
+ ('Message 3', ('\\Deleted',), None, 2),
+ ]
+ SimpleServer.theAccount.addMailbox('mailbox', m)
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def select():
+ return self.client.select('mailbox')
+ def close():
+ return self.client.close()
+
+ d = self.connected.addCallback(strip(login))
+ d.addCallbacks(strip(select), self._ebGeneral)
+ d.addCallbacks(strip(close), self._ebGeneral)
+ d.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ return defer.gatherResults([d, d2]).addCallback(self._cbTestClose, m)
+
+ def _cbTestClose(self, ignored, m):
+ self.assertEquals(len(m.messages), 1)
+ self.assertEquals(m.messages[0], ('Message 2', ('AnotherFlag',), None, 1))
+ self.failUnless(m.closed)
+
+ def testExpunge(self):
+ m = SimpleMailbox()
+ m.messages = [
+ ('Message 1', ('\\Deleted', 'AnotherFlag'), None, 0),
+ ('Message 2', ('AnotherFlag',), None, 1),
+ ('Message 3', ('\\Deleted',), None, 2),
+ ]
+ SimpleServer.theAccount.addMailbox('mailbox', m)
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def select():
+ return self.client.select('mailbox')
+ def expunge():
+ return self.client.expunge()
+ def expunged(results):
+ self.failIf(self.server.mbox is None)
+ self.results = results
+
+ self.results = None
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallbacks(strip(select), self._ebGeneral)
+ d1.addCallbacks(strip(expunge), self._ebGeneral)
+ d1.addCallbacks(expunged, self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ return d.addCallback(self._cbTestExpunge, m)
+
+ def _cbTestExpunge(self, ignored, m):
+ self.assertEquals(len(m.messages), 1)
+ self.assertEquals(m.messages[0], ('Message 2', ('AnotherFlag',), None, 1))
+
+ self.assertEquals(self.results, [0, 2])
+
+
+
+class IMAP4ServerSearchTestCase(IMAP4HelperMixin, unittest.TestCase):
+ """
+ Tests for the behavior of the search_* functions in L{imap4.IMAP4Server}.
+ """
+ def setUp(self):
+ IMAP4HelperMixin.setUp(self)
+ self.earlierQuery = ["10-Dec-2009"]
+ self.sameDateQuery = ["13-Dec-2009"]
+ self.laterQuery = ["16-Dec-2009"]
+ self.seq = 0
+ self.msg = FakeyMessage({"date" : "Mon, 13 Dec 2009 21:25:10 GMT"}, [],
+ '', '', None, None)
+
+
+ def test_searchSentBefore(self):
+ """
+ L{imap4.IMAP4Server.search_SENTBEFORE} returns True if the message date
+ is earlier than the query date.
+ """
+ self.assertFalse(
+ self.server.search_SENTBEFORE(self.earlierQuery, self.seq, self.msg))
+ self.assertTrue(
+ self.server.search_SENTBEFORE(self.laterQuery, self.seq, self.msg))
+
+
+ def test_searchSentOn(self):
+ """
+ L{imap4.IMAP4Server.search_SENTON} returns True if the message date is
+ the same as the query date.
+ """
+ self.assertFalse(
+ self.server.search_SENTON(self.earlierQuery, self.seq, self.msg))
+ self.assertTrue(
+ self.server.search_SENTON(self.sameDateQuery, self.seq, self.msg))
+ self.assertFalse(
+ self.server.search_SENTON(self.laterQuery, self.seq, self.msg))
+
+
+ def test_searchSentSince(self):
+ """
+ L{imap4.IMAP4Server.search_SENTSINCE} returns True if the message date
+ is later than the query date.
+ """
+ self.assertTrue(
+ self.server.search_SENTSINCE(self.earlierQuery, self.seq, self.msg))
+ self.assertFalse(
+ self.server.search_SENTSINCE(self.laterQuery, self.seq, self.msg))
+
+
+ def test_searchOr(self):
+ """
+ L{imap4.IMAP4Server.search_OR} returns true if either of the two
+ expressions supplied to it returns true and returns false if neither
+ does.
+ """
+ self.assertTrue(
+ self.server.search_OR(
+ ["SENTSINCE"] + self.earlierQuery +
+ ["SENTSINCE"] + self.laterQuery,
+ self.seq, self.msg, None))
+ self.assertTrue(
+ self.server.search_OR(
+ ["SENTSINCE"] + self.laterQuery +
+ ["SENTSINCE"] + self.earlierQuery,
+ self.seq, self.msg, None))
+ self.assertFalse(
+ self.server.search_OR(
+ ["SENTON"] + self.laterQuery +
+ ["SENTSINCE"] + self.laterQuery,
+ self.seq, self.msg, None))
+
+
+ def test_searchNot(self):
+ """
+ L{imap4.IMAP4Server.search_NOT} returns the negation of the result
+ of the expression supplied to it.
+ """
+ self.assertFalse(self.server.search_NOT(
+ ["SENTSINCE"] + self.earlierQuery, self.seq, self.msg, None))
+ self.assertTrue(self.server.search_NOT(
+ ["SENTON"] + self.laterQuery, self.seq, self.msg, None))
+
+
+
+class TestRealm:
+ theAccount = None
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ return imap4.IAccount, self.theAccount, lambda: None
+
+class TestChecker:
+ credentialInterfaces = (cred.credentials.IUsernameHashedPassword, cred.credentials.IUsernamePassword)
+
+ users = {
+ 'testuser': 'secret'
+ }
+
+ def requestAvatarId(self, credentials):
+ if credentials.username in self.users:
+ return defer.maybeDeferred(
+ credentials.checkPassword, self.users[credentials.username]
+ ).addCallback(self._cbCheck, credentials.username)
+
+ def _cbCheck(self, result, username):
+ if result:
+ return username
+ raise cred.error.UnauthorizedLogin()
+
+class AuthenticatorTestCase(IMAP4HelperMixin, unittest.TestCase):
+ def setUp(self):
+ IMAP4HelperMixin.setUp(self)
+
+ realm = TestRealm()
+ realm.theAccount = Account('testuser')
+ portal = cred.portal.Portal(realm)
+ portal.registerChecker(TestChecker())
+ self.server.portal = portal
+
+ self.authenticated = 0
+ self.account = realm.theAccount
+
+ def testCramMD5(self):
+ self.server.challengers['CRAM-MD5'] = cred.credentials.CramMD5Credentials
+ cAuth = imap4.CramMD5ClientAuthenticator('testuser')
+ self.client.registerAuthenticator(cAuth)
+
+ def auth():
+ return self.client.authenticate('secret')
+ def authed():
+ self.authenticated = 1
+
+ d1 = self.connected.addCallback(strip(auth))
+ d1.addCallbacks(strip(authed), self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d2 = self.loopback()
+ d = defer.gatherResults([d1, d2])
+ return d.addCallback(self._cbTestCramMD5)
+
+ def _cbTestCramMD5(self, ignored):
+ self.assertEquals(self.authenticated, 1)
+ self.assertEquals(self.server.account, self.account)
+
+ def testFailedCramMD5(self):
+ self.server.challengers['CRAM-MD5'] = cred.credentials.CramMD5Credentials
+ cAuth = imap4.CramMD5ClientAuthenticator('testuser')
+ self.client.registerAuthenticator(cAuth)
+
+ def misauth():
+ return self.client.authenticate('not the secret')
+ def authed():
+ self.authenticated = 1
+ def misauthed():
+ self.authenticated = -1
+
+ d1 = self.connected.addCallback(strip(misauth))
+ d1.addCallbacks(strip(authed), strip(misauthed))
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestFailedCramMD5)
+
+ def _cbTestFailedCramMD5(self, ignored):
+ self.assertEquals(self.authenticated, -1)
+ self.assertEquals(self.server.account, None)
+
+ def testLOGIN(self):
+ self.server.challengers['LOGIN'] = imap4.LOGINCredentials
+ cAuth = imap4.LOGINAuthenticator('testuser')
+ self.client.registerAuthenticator(cAuth)
+
+ def auth():
+ return self.client.authenticate('secret')
+ def authed():
+ self.authenticated = 1
+
+ d1 = self.connected.addCallback(strip(auth))
+ d1.addCallbacks(strip(authed), self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestLOGIN)
+
+ def _cbTestLOGIN(self, ignored):
+ self.assertEquals(self.authenticated, 1)
+ self.assertEquals(self.server.account, self.account)
+
+ def testFailedLOGIN(self):
+ self.server.challengers['LOGIN'] = imap4.LOGINCredentials
+ cAuth = imap4.LOGINAuthenticator('testuser')
+ self.client.registerAuthenticator(cAuth)
+
+ def misauth():
+ return self.client.authenticate('not the secret')
+ def authed():
+ self.authenticated = 1
+ def misauthed():
+ self.authenticated = -1
+
+ d1 = self.connected.addCallback(strip(misauth))
+ d1.addCallbacks(strip(authed), strip(misauthed))
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestFailedLOGIN)
+
+ def _cbTestFailedLOGIN(self, ignored):
+ self.assertEquals(self.authenticated, -1)
+ self.assertEquals(self.server.account, None)
+
+ def testPLAIN(self):
+ self.server.challengers['PLAIN'] = imap4.PLAINCredentials
+ cAuth = imap4.PLAINAuthenticator('testuser')
+ self.client.registerAuthenticator(cAuth)
+
+ def auth():
+ return self.client.authenticate('secret')
+ def authed():
+ self.authenticated = 1
+
+ d1 = self.connected.addCallback(strip(auth))
+ d1.addCallbacks(strip(authed), self._ebGeneral)
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestPLAIN)
+
+ def _cbTestPLAIN(self, ignored):
+ self.assertEquals(self.authenticated, 1)
+ self.assertEquals(self.server.account, self.account)
+
+ def testFailedPLAIN(self):
+ self.server.challengers['PLAIN'] = imap4.PLAINCredentials
+ cAuth = imap4.PLAINAuthenticator('testuser')
+ self.client.registerAuthenticator(cAuth)
+
+ def misauth():
+ return self.client.authenticate('not the secret')
+ def authed():
+ self.authenticated = 1
+ def misauthed():
+ self.authenticated = -1
+
+ d1 = self.connected.addCallback(strip(misauth))
+ d1.addCallbacks(strip(authed), strip(misauthed))
+ d1.addCallbacks(self._cbStopClient, self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestFailedPLAIN)
+
+ def _cbTestFailedPLAIN(self, ignored):
+ self.assertEquals(self.authenticated, -1)
+ self.assertEquals(self.server.account, None)
+
+
+
+class SASLPLAINTestCase(unittest.TestCase):
+ """
+ Tests for I{SASL PLAIN} authentication, as implemented by
+ L{imap4.PLAINAuthenticator} and L{imap4.PLAINCredentials}.
+
+ @see: U{http://www.faqs.org/rfcs/rfc2595.html}
+ @see: U{http://www.faqs.org/rfcs/rfc4616.html}
+ """
+ def test_authenticatorChallengeResponse(self):
+ """
+ L{PLAINAuthenticator.challengeResponse} returns challenge strings of
+ the form::
+
+ NUL<authn-id>NUL<secret>
+ """
+ username = 'testuser'
+ secret = 'secret'
+ chal = 'challenge'
+ cAuth = imap4.PLAINAuthenticator(username)
+ response = cAuth.challengeResponse(secret, chal)
+ self.assertEquals(response, '\0%s\0%s' % (username, secret))
+
+
+ def test_credentialsSetResponse(self):
+ """
+ L{PLAINCredentials.setResponse} parses challenge strings of the
+ form::
+
+ NUL<authn-id>NUL<secret>
+ """
+ cred = imap4.PLAINCredentials()
+ cred.setResponse('\0testuser\0secret')
+ self.assertEquals(cred.username, 'testuser')
+ self.assertEquals(cred.password, 'secret')
+
+
+ def test_credentialsInvalidResponse(self):
+ """
+ L{PLAINCredentials.setResponse} raises L{imap4.IllegalClientResponse}
+ when passed a string not of the expected form.
+ """
+ cred = imap4.PLAINCredentials()
+ self.assertRaises(
+ imap4.IllegalClientResponse, cred.setResponse, 'hello')
+ self.assertRaises(
+ imap4.IllegalClientResponse, cred.setResponse, 'hello\0world')
+ self.assertRaises(
+ imap4.IllegalClientResponse, cred.setResponse,
+ 'hello\0world\0Zoom!\0')
+
+
+
+class UnsolicitedResponseTestCase(IMAP4HelperMixin, unittest.TestCase):
+ def testReadWrite(self):
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def loggedIn():
+ self.server.modeChanged(1)
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestReadWrite)
+
+ def _cbTestReadWrite(self, ignored):
+ E = self.client.events
+ self.assertEquals(E, [['modeChanged', 1]])
+
+ def testReadOnly(self):
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def loggedIn():
+ self.server.modeChanged(0)
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestReadOnly)
+
+ def _cbTestReadOnly(self, ignored):
+ E = self.client.events
+ self.assertEquals(E, [['modeChanged', 0]])
+
+ def testFlagChange(self):
+ flags = {
+ 1: ['\\Answered', '\\Deleted'],
+ 5: [],
+ 10: ['\\Recent']
+ }
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def loggedIn():
+ self.server.flagsChanged(flags)
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestFlagChange, flags)
+
+ def _cbTestFlagChange(self, ignored, flags):
+ E = self.client.events
+ expect = [['flagsChanged', {x[0]: x[1]}] for x in flags.items()]
+ E.sort()
+ expect.sort()
+ self.assertEquals(E, expect)
+
+ def testNewMessages(self):
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def loggedIn():
+ self.server.newMessages(10, None)
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestNewMessages)
+
+ def _cbTestNewMessages(self, ignored):
+ E = self.client.events
+ self.assertEquals(E, [['newMessages', 10, None]])
+
+ def testNewRecentMessages(self):
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def loggedIn():
+ self.server.newMessages(None, 10)
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestNewRecentMessages)
+
+ def _cbTestNewRecentMessages(self, ignored):
+ E = self.client.events
+ self.assertEquals(E, [['newMessages', None, 10]])
+
+ def testNewMessagesAndRecent(self):
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def loggedIn():
+ self.server.newMessages(20, 10)
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
+ d = defer.gatherResults([self.loopback(), d1])
+ return d.addCallback(self._cbTestNewMessagesAndRecent)
+
+ def _cbTestNewMessagesAndRecent(self, ignored):
+ E = self.client.events
+ self.assertEquals(E, [['newMessages', 20, None], ['newMessages', None, 10]])
+
+
+class ClientCapabilityTests(unittest.TestCase):
+ """
+ Tests for issuance of the CAPABILITY command and handling of its response.
+ """
+ def setUp(self):
+ """
+ Create an L{imap4.IMAP4Client} connected to a L{StringTransport}.
+ """
+ self.transport = StringTransport()
+ self.protocol = imap4.IMAP4Client()
+ self.protocol.makeConnection(self.transport)
+ self.protocol.dataReceived('* OK [IMAP4rev1]\r\n')
+
+
+ def test_simpleAtoms(self):
+ """
+ A capability response consisting only of atoms without C{'='} in them
+ should result in a dict mapping those atoms to C{None}.
+ """
+ capabilitiesResult = self.protocol.getCapabilities(useCache=False)
+ self.protocol.dataReceived('* CAPABILITY IMAP4rev1 LOGINDISABLED\r\n')
+ self.protocol.dataReceived('0001 OK Capability completed.\r\n')
+ def gotCapabilities(capabilities):
+ self.assertEqual(
+ capabilities, {'IMAP4rev1': None, 'LOGINDISABLED': None})
+ capabilitiesResult.addCallback(gotCapabilities)
+ return capabilitiesResult
+
+
+ def test_categoryAtoms(self):
+ """
+ A capability response consisting of atoms including C{'='} should have
+ those atoms split on that byte and have capabilities in the same
+ category aggregated into lists in the resulting dictionary.
+
+ (n.b. - I made up the word "category atom"; the protocol has no notion
+ of structure here, but rather allows each capability to define the
+ semantics of its entry in the capability response in a freeform manner.
+ If I had realized this earlier, the API for capabilities would look
+ different. As it is, we can hope that no one defines any crazy
+ semantics which are incompatible with this API, or try to figure out a
+ better API when someone does. -exarkun)
+ """
+ capabilitiesResult = self.protocol.getCapabilities(useCache=False)
+ self.protocol.dataReceived('* CAPABILITY IMAP4rev1 AUTH=LOGIN AUTH=PLAIN\r\n')
+ self.protocol.dataReceived('0001 OK Capability completed.\r\n')
+ def gotCapabilities(capabilities):
+ self.assertEqual(
+ capabilities, {'IMAP4rev1': None, 'AUTH': ['LOGIN', 'PLAIN']})
+ capabilitiesResult.addCallback(gotCapabilities)
+ return capabilitiesResult
+
+
+ def test_mixedAtoms(self):
+ """
+ A capability response consisting of both simple and category atoms of
+ the same type should result in a list containing C{None} as well as the
+ values for the category.
+ """
+ capabilitiesResult = self.protocol.getCapabilities(useCache=False)
+ # Exercise codepath for both orderings of =-having and =-missing
+ # capabilities.
+ self.protocol.dataReceived(
+ '* CAPABILITY IMAP4rev1 FOO FOO=BAR BAR=FOO BAR\r\n')
+ self.protocol.dataReceived('0001 OK Capability completed.\r\n')
+ def gotCapabilities(capabilities):
+ self.assertEqual(capabilities, {'IMAP4rev1': None,
+ 'FOO': [None, 'BAR'],
+ 'BAR': ['FOO', None]})
+ capabilitiesResult.addCallback(gotCapabilities)
+ return capabilitiesResult
+
+
+
+class StillSimplerClient(imap4.IMAP4Client):
+ """
+ An IMAP4 client which keeps track of unsolicited flag changes.
+ """
+ def __init__(self):
+ imap4.IMAP4Client.__init__(self)
+ self.flags = {}
+
+
+ def flagsChanged(self, newFlags):
+ self.flags.update(newFlags)
+
+
+
+class HandCraftedTestCase(IMAP4HelperMixin, unittest.TestCase):
+ def testTrailingLiteral(self):
+ transport = StringTransport()
+ c = imap4.IMAP4Client()
+ c.makeConnection(transport)
+ c.lineReceived('* OK [IMAP4rev1]')
+
+ def cbSelect(ignored):
+ d = c.fetchMessage('1')
+ c.dataReceived('* 1 FETCH (RFC822 {10}\r\n0123456789\r\n RFC822.SIZE 10)\r\n')
+ c.dataReceived('0003 OK FETCH\r\n')
+ return d
+
+ def cbLogin(ignored):
+ d = c.select('inbox')
+ c.lineReceived('0002 OK SELECT')
+ d.addCallback(cbSelect)
+ return d
+
+ d = c.login('blah', 'blah')
+ c.dataReceived('0001 OK LOGIN\r\n')
+ d.addCallback(cbLogin)
+ return d
+
+
+ def testPathelogicalScatteringOfLiterals(self):
+ self.server.checker.addUser('testuser', 'password-test')
+ transport = StringTransport()
+ self.server.makeConnection(transport)
+
+ transport.clear()
+ self.server.dataReceived("01 LOGIN {8}\r\n")
+ self.assertEquals(transport.value(), "+ Ready for 8 octets of text\r\n")
+
+ transport.clear()
+ self.server.dataReceived("testuser {13}\r\n")
+ self.assertEquals(transport.value(), "+ Ready for 13 octets of text\r\n")
+
+ transport.clear()
+ self.server.dataReceived("password-test\r\n")
+ self.assertEquals(transport.value(), "01 OK LOGIN succeeded\r\n")
+ self.assertEquals(self.server.state, 'auth')
+
+ self.server.connectionLost(error.ConnectionDone("Connection done."))
+
+
+ def test_unsolicitedResponseMixedWithSolicitedResponse(self):
+ """
+ If unsolicited data is received along with solicited data in the
+ response to a I{FETCH} command issued by L{IMAP4Client.fetchSpecific},
+ the unsolicited data is passed to the appropriate callback and not
+ included in the result with wihch the L{Deferred} returned by
+ L{IMAP4Client.fetchSpecific} fires.
+ """
+ transport = StringTransport()
+ c = StillSimplerClient()
+ c.makeConnection(transport)
+ c.lineReceived('* OK [IMAP4rev1]')
+
+ def login():
+ d = c.login('blah', 'blah')
+ c.dataReceived('0001 OK LOGIN\r\n')
+ return d
+ def select():
+ d = c.select('inbox')
+ c.lineReceived('0002 OK SELECT')
+ return d
+ def fetch():
+ d = c.fetchSpecific('1:*',
+ headerType='HEADER.FIELDS',
+ headerArgs=['SUBJECT'])
+ c.dataReceived('* 1 FETCH (BODY[HEADER.FIELDS ("SUBJECT")] {38}\r\n')
+ c.dataReceived('Subject: Suprise for your woman...\r\n')
+ c.dataReceived('\r\n')
+ c.dataReceived(')\r\n')
+ c.dataReceived('* 1 FETCH (FLAGS (\Seen))\r\n')
+ c.dataReceived('* 2 FETCH (BODY[HEADER.FIELDS ("SUBJECT")] {75}\r\n')
+ c.dataReceived('Subject: What you been doing. Order your meds here . ,. handcuff madsen\r\n')
+ c.dataReceived('\r\n')
+ c.dataReceived(')\r\n')
+ c.dataReceived('0003 OK FETCH completed\r\n')
+ return d
+ def test(res):
+ self.assertEquals(res, {
+ 1: [['BODY', ['HEADER.FIELDS', ['SUBJECT']],
+ 'Subject: Suprise for your woman...\r\n\r\n']],
+ 2: [['BODY', ['HEADER.FIELDS', ['SUBJECT']],
+ 'Subject: What you been doing. Order your meds here . ,. handcuff madsen\r\n\r\n']]
+ })
+
+ self.assertEquals(c.flags, {1: ['\\Seen']})
+
+ return login(
+ ).addCallback(strip(select)
+ ).addCallback(strip(fetch)
+ ).addCallback(test)
+
+
+ def test_literalWithoutPrecedingWhitespace(self):
+ """
+ Literals should be recognized even when they are not preceded by
+ whitespace.
+ """
+ transport = StringTransport()
+ protocol = imap4.IMAP4Client()
+
+ protocol.makeConnection(transport)
+ protocol.lineReceived('* OK [IMAP4rev1]')
+
+ def login():
+ d = protocol.login('blah', 'blah')
+ protocol.dataReceived('0001 OK LOGIN\r\n')
+ return d
+ def select():
+ d = protocol.select('inbox')
+ protocol.lineReceived('0002 OK SELECT')
+ return d
+ def fetch():
+ d = protocol.fetchSpecific('1:*',
+ headerType='HEADER.FIELDS',
+ headerArgs=['SUBJECT'])
+ protocol.dataReceived(
+ '* 1 FETCH (BODY[HEADER.FIELDS ({7}\r\nSUBJECT)] "Hello")\r\n')
+ protocol.dataReceived('0003 OK FETCH completed\r\n')
+ return d
+ def test(result):
+ self.assertEqual(
+ result, {1: [['BODY', ['HEADER.FIELDS', ['SUBJECT']], 'Hello']]})
+
+ d = login()
+ d.addCallback(strip(select))
+ d.addCallback(strip(fetch))
+ d.addCallback(test)
+ return d
+
+
+ def test_nonIntegerLiteralLength(self):
+ """
+ If the server sends a literal length which cannot be parsed as an
+ integer, L{IMAP4Client.lineReceived} should cause the protocol to be
+ disconnected by raising L{imap4.IllegalServerResponse}.
+ """
+ transport = StringTransport()
+ protocol = imap4.IMAP4Client()
+
+ protocol.makeConnection(transport)
+ protocol.lineReceived('* OK [IMAP4rev1]')
+
+ def login():
+ d = protocol.login('blah', 'blah')
+ protocol.dataReceived('0001 OK LOGIN\r\n')
+ return d
+ def select():
+ d = protocol.select('inbox')
+ protocol.lineReceived('0002 OK SELECT')
+ return d
+ def fetch():
+ d = protocol.fetchSpecific('1:*',
+ headerType='HEADER.FIELDS',
+ headerArgs=['SUBJECT'])
+ self.assertRaises(
+ imap4.IllegalServerResponse,
+ protocol.dataReceived,
+ '* 1 FETCH {xyz}\r\n...')
+ d = login()
+ d.addCallback(strip(select))
+ d.addCallback(strip(fetch))
+ return d
+
+
+ def test_flagsChangedInsideFetchSpecificResponse(self):
+ """
+ Any unrequested flag information received along with other requested
+ information in an untagged I{FETCH} received in response to a request
+ issued with L{IMAP4Client.fetchSpecific} is passed to the
+ C{flagsChanged} callback.
+ """
+ transport = StringTransport()
+ c = StillSimplerClient()
+ c.makeConnection(transport)
+ c.lineReceived('* OK [IMAP4rev1]')
+
+ def login():
+ d = c.login('blah', 'blah')
+ c.dataReceived('0001 OK LOGIN\r\n')
+ return d
+ def select():
+ d = c.select('inbox')
+ c.lineReceived('0002 OK SELECT')
+ return d
+ def fetch():
+ d = c.fetchSpecific('1:*',
+ headerType='HEADER.FIELDS',
+ headerArgs=['SUBJECT'])
+ # This response includes FLAGS after the requested data.
+ c.dataReceived('* 1 FETCH (BODY[HEADER.FIELDS ("SUBJECT")] {22}\r\n')
+ c.dataReceived('Subject: subject one\r\n')
+ c.dataReceived(' FLAGS (\\Recent))\r\n')
+ # And this one includes it before! Either is possible.
+ c.dataReceived('* 2 FETCH (FLAGS (\\Seen) BODY[HEADER.FIELDS ("SUBJECT")] {22}\r\n')
+ c.dataReceived('Subject: subject two\r\n')
+ c.dataReceived(')\r\n')
+ c.dataReceived('0003 OK FETCH completed\r\n')
+ return d
+
+ def test(res):
+ self.assertEquals(res, {
+ 1: [['BODY', ['HEADER.FIELDS', ['SUBJECT']],
+ 'Subject: subject one\r\n']],
+ 2: [['BODY', ['HEADER.FIELDS', ['SUBJECT']],
+ 'Subject: subject two\r\n']]
+ })
+
+ self.assertEquals(c.flags, {1: ['\\Recent'], 2: ['\\Seen']})
+
+ return login(
+ ).addCallback(strip(select)
+ ).addCallback(strip(fetch)
+ ).addCallback(test)
+
+
+ def test_flagsChangedInsideFetchMessageResponse(self):
+ """
+ Any unrequested flag information received along with other requested
+ information in an untagged I{FETCH} received in response to a request
+ issued with L{IMAP4Client.fetchMessage} is passed to the
+ C{flagsChanged} callback.
+ """
+ transport = StringTransport()
+ c = StillSimplerClient()
+ c.makeConnection(transport)
+ c.lineReceived('* OK [IMAP4rev1]')
+
+ def login():
+ d = c.login('blah', 'blah')
+ c.dataReceived('0001 OK LOGIN\r\n')
+ return d
+ def select():
+ d = c.select('inbox')
+ c.lineReceived('0002 OK SELECT')
+ return d
+ def fetch():
+ d = c.fetchMessage('1:*')
+ c.dataReceived('* 1 FETCH (RFC822 {24}\r\n')
+ c.dataReceived('Subject: first subject\r\n')
+ c.dataReceived(' FLAGS (\Seen))\r\n')
+ c.dataReceived('* 2 FETCH (FLAGS (\Recent \Seen) RFC822 {25}\r\n')
+ c.dataReceived('Subject: second subject\r\n')
+ c.dataReceived(')\r\n')
+ c.dataReceived('0003 OK FETCH completed\r\n')
+ return d
+
+ def test(res):
+ self.assertEquals(res, {
+ 1: {'RFC822': 'Subject: first subject\r\n'},
+ 2: {'RFC822': 'Subject: second subject\r\n'}})
+
+ self.assertEquals(
+ c.flags, {1: ['\\Seen'], 2: ['\\Recent', '\\Seen']})
+
+ return login(
+ ).addCallback(strip(select)
+ ).addCallback(strip(fetch)
+ ).addCallback(test)
+
+
+
+class PreauthIMAP4ClientMixin:
+ """
+ Mixin for L{unittest.TestCase} subclasses which provides a C{setUp} method
+ which creates an L{IMAP4Client} connected to a L{StringTransport} and puts
+ it into the I{authenticated} state.
+
+ @ivar transport: A L{StringTransport} to which C{client} is connected.
+ @ivar client: An L{IMAP4Client} which is connected to C{transport}.
+ """
+ clientProtocol = imap4.IMAP4Client
+
+ def setUp(self):
+ """
+ Create an IMAP4Client connected to a fake transport and in the
+ authenticated state.
+ """
+ self.transport = StringTransport()
+ self.client = self.clientProtocol()
+ self.client.makeConnection(self.transport)
+ self.client.dataReceived('* PREAUTH Hello unittest\r\n')
+
+
+ def _extractDeferredResult(self, d):
+ """
+ Synchronously extract the result of the given L{Deferred}. Fail the
+ test if that is not possible.
+ """
+ result = []
+ error = []
+ d.addCallbacks(result.append, error.append)
+ if result:
+ return result[0]
+ elif error:
+ error[0].raiseException()
+ else:
+ self.fail("Expected result not available")
+
+
+
+class IMAP4ClientExamineTests(PreauthIMAP4ClientMixin, unittest.TestCase):
+ """
+ Tests for the L{IMAP4Client.examine} method.
+
+ An example of usage of the EXAMINE command from RFC 3501, section 6.3.2::
+
+ S: * 17 EXISTS
+ S: * 2 RECENT
+ S: * OK [UNSEEN 8] Message 8 is first unseen
+ S: * OK [UIDVALIDITY 3857529045] UIDs valid
+ S: * OK [UIDNEXT 4392] Predicted next UID
+ S: * FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)
+ S: * OK [PERMANENTFLAGS ()] No permanent flags permitted
+ S: A932 OK [READ-ONLY] EXAMINE completed
+ """
+ def _examine(self):
+ """
+ Issue an examine command, assert that the correct bytes are written to
+ the transport, and return the L{Deferred} returned by the C{examine}
+ method.
+ """
+ d = self.client.examine('foobox')
+ self.assertEqual(self.transport.value(), '0001 EXAMINE foobox\r\n')
+ return d
+
+
+ def _response(self, *lines):
+ """
+ Deliver the given (unterminated) response lines to C{self.client} and
+ then deliver a tagged EXAMINE completion line to finish the EXAMINE
+ response.
+ """
+ for line in lines:
+ self.client.dataReceived(line + '\r\n')
+ self.client.dataReceived('0001 OK [READ-ONLY] EXAMINE completed\r\n')
+
+
+ def test_exists(self):
+ """
+ If the server response to an I{EXAMINE} command includes an I{EXISTS}
+ response, the L{Deferred} return by L{IMAP4Client.examine} fires with a
+ C{dict} including the value associated with the C{'EXISTS'} key.
+ """
+ d = self._examine()
+ self._response('* 3 EXISTS')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {'READ-WRITE': False, 'EXISTS': 3})
+
+
+ def test_nonIntegerExists(self):
+ """
+ If the server returns a non-integer EXISTS value in its response to an
+ I{EXAMINE} command, the L{Deferred} returned by L{IMAP4Client.examine}
+ fails with L{IllegalServerResponse}.
+ """
+ d = self._examine()
+ self._response('* foo EXISTS')
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+ def test_recent(self):
+ """
+ If the server response to an I{EXAMINE} command includes an I{RECENT}
+ response, the L{Deferred} return by L{IMAP4Client.examine} fires with a
+ C{dict} including the value associated with the C{'RECENT'} key.
+ """
+ d = self._examine()
+ self._response('* 5 RECENT')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {'READ-WRITE': False, 'RECENT': 5})
+
+
+ def test_nonIntegerRecent(self):
+ """
+ If the server returns a non-integer RECENT value in its response to an
+ I{EXAMINE} command, the L{Deferred} returned by L{IMAP4Client.examine}
+ fails with L{IllegalServerResponse}.
+ """
+ d = self._examine()
+ self._response('* foo RECENT')
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+ def test_unseen(self):
+ """
+ If the server response to an I{EXAMINE} command includes an I{UNSEEN}
+ response, the L{Deferred} returned by L{IMAP4Client.examine} fires with
+ a C{dict} including the value associated with the C{'UNSEEN'} key.
+ """
+ d = self._examine()
+ self._response('* OK [UNSEEN 8] Message 8 is first unseen')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {'READ-WRITE': False, 'UNSEEN': 8})
+
+
+ def test_nonIntegerUnseen(self):
+ """
+ If the server returns a non-integer UNSEEN value in its response to an
+ I{EXAMINE} command, the L{Deferred} returned by L{IMAP4Client.examine}
+ fails with L{IllegalServerResponse}.
+ """
+ d = self._examine()
+ self._response('* OK [UNSEEN foo] Message foo is first unseen')
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+ def test_uidvalidity(self):
+ """
+ If the server response to an I{EXAMINE} command includes an
+ I{UIDVALIDITY} response, the L{Deferred} returned by
+ L{IMAP4Client.examine} fires with a C{dict} including the value
+ associated with the C{'UIDVALIDITY'} key.
+ """
+ d = self._examine()
+ self._response('* OK [UIDVALIDITY 12345] UIDs valid')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {'READ-WRITE': False, 'UIDVALIDITY': 12345})
+
+
+ def test_nonIntegerUIDVALIDITY(self):
+ """
+ If the server returns a non-integer UIDVALIDITY value in its response
+ to an I{EXAMINE} command, the L{Deferred} returned by
+ L{IMAP4Client.examine} fails with L{IllegalServerResponse}.
+ """
+ d = self._examine()
+ self._response('* OK [UIDVALIDITY foo] UIDs valid')
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+ def test_uidnext(self):
+ """
+ If the server response to an I{EXAMINE} command includes an I{UIDNEXT}
+ response, the L{Deferred} returned by L{IMAP4Client.examine} fires with
+ a C{dict} including the value associated with the C{'UIDNEXT'} key.
+ """
+ d = self._examine()
+ self._response('* OK [UIDNEXT 4392] Predicted next UID')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {'READ-WRITE': False, 'UIDNEXT': 4392})
+
+
+ def test_nonIntegerUIDNEXT(self):
+ """
+ If the server returns a non-integer UIDNEXT value in its response to an
+ I{EXAMINE} command, the L{Deferred} returned by L{IMAP4Client.examine}
+ fails with L{IllegalServerResponse}.
+ """
+ d = self._examine()
+ self._response('* OK [UIDNEXT foo] Predicted next UID')
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+ def test_flags(self):
+ """
+ If the server response to an I{EXAMINE} command includes an I{FLAGS}
+ response, the L{Deferred} returned by L{IMAP4Client.examine} fires with
+ a C{dict} including the value associated with the C{'FLAGS'} key.
+ """
+ d = self._examine()
+ self._response(
+ '* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)')
+ self.assertEquals(
+ self._extractDeferredResult(d), {
+ 'READ-WRITE': False,
+ 'FLAGS': ('\\Answered', '\\Flagged', '\\Deleted', '\\Seen',
+ '\\Draft')})
+
+
+ def test_permanentflags(self):
+ """
+ If the server response to an I{EXAMINE} command includes an I{FLAGS}
+ response, the L{Deferred} returned by L{IMAP4Client.examine} fires with
+ a C{dict} including the value associated with the C{'FLAGS'} key.
+ """
+ d = self._examine()
+ self._response(
+ '* OK [PERMANENTFLAGS (\\Starred)] Just one permanent flag in '
+ 'that list up there')
+ self.assertEquals(
+ self._extractDeferredResult(d), {
+ 'READ-WRITE': False,
+ 'PERMANENTFLAGS': ('\\Starred',)})
+
+
+ def test_unrecognizedOk(self):
+ """
+ If the server response to an I{EXAMINE} command includes an I{OK} with
+ unrecognized response code text, parsing does not fail.
+ """
+ d = self._examine()
+ self._response(
+ '* OK [X-MADE-UP] I just made this response text up.')
+ # The value won't show up in the result. It would be okay if it did
+ # someday, perhaps. This shouldn't ever happen, though.
+ self.assertEquals(
+ self._extractDeferredResult(d), {'READ-WRITE': False})
+
+
+ def test_bareOk(self):
+ """
+ If the server response to an I{EXAMINE} command includes an I{OK} with
+ no response code text, parsing does not fail.
+ """
+ d = self._examine()
+ self._response('* OK')
+ self.assertEquals(
+ self._extractDeferredResult(d), {'READ-WRITE': False})
+
+
+
+class IMAP4ClientExpungeTests(PreauthIMAP4ClientMixin, unittest.TestCase):
+ """
+ Tests for the L{IMAP4Client.expunge} method.
+
+ An example of usage of the EXPUNGE command from RFC 3501, section 6.4.3::
+
+ C: A202 EXPUNGE
+ S: * 3 EXPUNGE
+ S: * 3 EXPUNGE
+ S: * 5 EXPUNGE
+ S: * 8 EXPUNGE
+ S: A202 OK EXPUNGE completed
+ """
+ def _expunge(self):
+ d = self.client.expunge()
+ self.assertEquals(self.transport.value(), '0001 EXPUNGE\r\n')
+ self.transport.clear()
+ return d
+
+
+ def _response(self, sequenceNumbers):
+ for number in sequenceNumbers:
+ self.client.lineReceived('* %s EXPUNGE' % (number,))
+ self.client.lineReceived('0001 OK EXPUNGE COMPLETED')
+
+
+ def test_expunge(self):
+ """
+ L{IMAP4Client.expunge} sends the I{EXPUNGE} command and returns a
+ L{Deferred} which fires with a C{list} of message sequence numbers
+ given by the server's response.
+ """
+ d = self._expunge()
+ self._response([3, 3, 5, 8])
+ self.assertEquals(self._extractDeferredResult(d), [3, 3, 5, 8])
+
+
+ def test_nonIntegerExpunged(self):
+ """
+ If the server responds with a non-integer where a message sequence
+ number is expected, the L{Deferred} returned by L{IMAP4Client.expunge}
+ fails with L{IllegalServerResponse}.
+ """
+ d = self._expunge()
+ self._response([3, 3, 'foo', 8])
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+
+class IMAP4ClientSearchTests(PreauthIMAP4ClientMixin, unittest.TestCase):
+ """
+ Tests for the L{IMAP4Client.search} method.
+
+ An example of usage of the SEARCH command from RFC 3501, section 6.4.4::
+
+ C: A282 SEARCH FLAGGED SINCE 1-Feb-1994 NOT FROM "Smith"
+ S: * SEARCH 2 84 882
+ S: A282 OK SEARCH completed
+ C: A283 SEARCH TEXT "string not in mailbox"
+ S: * SEARCH
+ S: A283 OK SEARCH completed
+ C: A284 SEARCH CHARSET UTF-8 TEXT {6}
+ C: XXXXXX
+ S: * SEARCH 43
+ S: A284 OK SEARCH completed
+ """
+ def _search(self):
+ d = self.client.search(imap4.Query(text="ABCDEF"))
+ self.assertEquals(
+ self.transport.value(), '0001 SEARCH (TEXT "ABCDEF")\r\n')
+ return d
+
+
+ def _response(self, messageNumbers):
+ self.client.lineReceived(
+ "* SEARCH " + " ".join(map(str, messageNumbers)))
+ self.client.lineReceived("0001 OK SEARCH completed")
+
+
+ def test_search(self):
+ """
+ L{IMAP4Client.search} sends the I{SEARCH} command and returns a
+ L{Deferred} which fires with a C{list} of message sequence numbers
+ given by the server's response.
+ """
+ d = self._search()
+ self._response([2, 5, 10])
+ self.assertEquals(self._extractDeferredResult(d), [2, 5, 10])
+
+
+ def test_nonIntegerFound(self):
+ """
+ If the server responds with a non-integer where a message sequence
+ number is expected, the L{Deferred} returned by L{IMAP4Client.search}
+ fails with L{IllegalServerResponse}.
+ """
+ d = self._search()
+ self._response([2, "foo", 10])
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+
+class IMAP4ClientFetchTests(PreauthIMAP4ClientMixin, unittest.TestCase):
+ """
+ Tests for the L{IMAP4Client.fetch} method.
+
+ See RFC 3501, section 6.4.5.
+ """
+ def test_fetchUID(self):
+ """
+ L{IMAP4Client.fetchUID} sends the I{FETCH UID} command and returns a
+ L{Deferred} which fires with a C{dict} mapping message sequence numbers
+ to C{dict}s mapping C{'UID'} to that message's I{UID} in the server's
+ response.
+ """
+ d = self.client.fetchUID('1:7')
+ self.assertEquals(self.transport.value(), '0001 FETCH 1:7 (UID)\r\n')
+ self.client.lineReceived('* 2 FETCH (UID 22)')
+ self.client.lineReceived('* 3 FETCH (UID 23)')
+ self.client.lineReceived('* 4 FETCH (UID 24)')
+ self.client.lineReceived('* 5 FETCH (UID 25)')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertEquals(
+ self._extractDeferredResult(d), {
+ 2: {'UID': '22'},
+ 3: {'UID': '23'},
+ 4: {'UID': '24'},
+ 5: {'UID': '25'}})
+
+
+ def test_fetchUIDNonIntegerFound(self):
+ """
+ If the server responds with a non-integer where a message sequence
+ number is expected, the L{Deferred} returned by L{IMAP4Client.fetchUID}
+ fails with L{IllegalServerResponse}.
+ """
+ d = self.client.fetchUID('1')
+ self.assertEquals(self.transport.value(), '0001 FETCH 1 (UID)\r\n')
+ self.client.lineReceived('* foo FETCH (UID 22)')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+ def test_incompleteFetchUIDResponse(self):
+ """
+ If the server responds with an incomplete I{FETCH} response line, the
+ L{Deferred} returned by L{IMAP4Client.fetchUID} fails with
+ L{IllegalServerResponse}.
+ """
+ d = self.client.fetchUID('1:7')
+ self.assertEquals(self.transport.value(), '0001 FETCH 1:7 (UID)\r\n')
+ self.client.lineReceived('* 2 FETCH (UID 22)')
+ self.client.lineReceived('* 3 FETCH (UID)')
+ self.client.lineReceived('* 4 FETCH (UID 24)')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+ def test_fetchBody(self):
+ """
+ L{IMAP4Client.fetchBody} sends the I{FETCH BODY} command and returns a
+ L{Deferred} which fires with a C{dict} mapping message sequence numbers
+ to C{dict}s mapping C{'RFC822.TEXT'} to that message's body as given in
+ the server's response.
+ """
+ d = self.client.fetchBody('3')
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 3 (RFC822.TEXT)\r\n')
+ self.client.lineReceived('* 3 FETCH (RFC822.TEXT "Message text")')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {3: {'RFC822.TEXT': 'Message text'}})
+
+
+ def test_fetchSpecific(self):
+ """
+ L{IMAP4Client.fetchSpecific} sends the I{BODY[]} command if no
+ parameters beyond the message set to retrieve are given. It returns a
+ L{Deferred} which fires with a C{dict} mapping message sequence numbers
+ to C{list}s of corresponding message data given by the server's
+ response.
+ """
+ d = self.client.fetchSpecific('7')
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 7 BODY[]\r\n')
+ self.client.lineReceived('* 7 FETCH (BODY[] "Some body")')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertEquals(
+ self._extractDeferredResult(d), {7: [['BODY', [], "Some body"]]})
+
+
+ def test_fetchSpecificPeek(self):
+ """
+ L{IMAP4Client.fetchSpecific} issues a I{BODY.PEEK[]} command if passed
+ C{True} for the C{peek} parameter.
+ """
+ d = self.client.fetchSpecific('6', peek=True)
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 6 BODY.PEEK[]\r\n')
+ # BODY.PEEK responses are just BODY
+ self.client.lineReceived('* 6 FETCH (BODY[] "Some body")')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertEquals(
+ self._extractDeferredResult(d), {6: [['BODY', [], "Some body"]]})
+
+
+ def test_fetchSpecificNumbered(self):
+ """
+ L{IMAP4Client.fetchSpecific}, when passed a sequence for for
+ C{headerNumber}, sends the I{BODY[N.M]} command. It returns a
+ L{Deferred} which fires with a C{dict} mapping message sequence numbers
+ to C{list}s of corresponding message data given by the server's
+ response.
+ """
+ d = self.client.fetchSpecific('7', headerNumber=(1, 2, 3))
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 7 BODY[1.2.3]\r\n')
+ self.client.lineReceived('* 7 FETCH (BODY[1.2.3] "Some body")')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {7: [['BODY', ['1.2.3'], "Some body"]]})
+
+
+ def test_fetchSpecificText(self):
+ """
+ L{IMAP4Client.fetchSpecific}, when passed C{'TEXT'} for C{headerType},
+ sends the I{BODY[TEXT]} command. It returns a L{Deferred} which fires
+ with a C{dict} mapping message sequence numbers to C{list}s of
+ corresponding message data given by the server's response.
+ """
+ d = self.client.fetchSpecific('8', headerType='TEXT')
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 8 BODY[TEXT]\r\n')
+ self.client.lineReceived('* 8 FETCH (BODY[TEXT] "Some body")')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {8: [['BODY', ['TEXT'], "Some body"]]})
+
+
+ def test_fetchSpecificNumberedText(self):
+ """
+ If passed a value for the C{headerNumber} parameter and C{'TEXT'} for
+ the C{headerType} parameter, L{IMAP4Client.fetchSpecific} sends a
+ I{BODY[number.TEXT]} request and returns a L{Deferred} which fires with
+ a C{dict} mapping message sequence numbers to C{list}s of message data
+ given by the server's response.
+ """
+ d = self.client.fetchSpecific('4', headerType='TEXT', headerNumber=7)
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 4 BODY[7.TEXT]\r\n')
+ self.client.lineReceived('* 4 FETCH (BODY[7.TEXT] "Some body")')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {4: [['BODY', ['7.TEXT'], "Some body"]]})
+
+
+ def test_incompleteFetchSpecificTextResponse(self):
+ """
+ If the server responds to a I{BODY[TEXT]} request with a I{FETCH} line
+ which is truncated after the I{BODY[TEXT]} tokens, the L{Deferred}
+ returned by L{IMAP4Client.fetchUID} fails with
+ L{IllegalServerResponse}.
+ """
+ d = self.client.fetchSpecific('8', headerType='TEXT')
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 8 BODY[TEXT]\r\n')
+ self.client.lineReceived('* 8 FETCH (BODY[TEXT])')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+ def test_fetchSpecificMIME(self):
+ """
+ L{IMAP4Client.fetchSpecific}, when passed C{'MIME'} for C{headerType},
+ sends the I{BODY[MIME]} command. It returns a L{Deferred} which fires
+ with a C{dict} mapping message sequence numbers to C{list}s of
+ corresponding message data given by the server's response.
+ """
+ d = self.client.fetchSpecific('8', headerType='MIME')
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 8 BODY[MIME]\r\n')
+ self.client.lineReceived('* 8 FETCH (BODY[MIME] "Some body")')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {8: [['BODY', ['MIME'], "Some body"]]})
+
+
+ def test_fetchSpecificPartial(self):
+ """
+ L{IMAP4Client.fetchSpecific}, when passed C{offset} and C{length},
+ sends a partial content request (like I{BODY[TEXT]<offset.length>}).
+ It returns a L{Deferred} which fires with a C{dict} mapping message
+ sequence numbers to C{list}s of corresponding message data given by the
+ server's response.
+ """
+ d = self.client.fetchSpecific(
+ '9', headerType='TEXT', offset=17, length=3)
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 9 BODY[TEXT]<17.3>\r\n')
+ self.client.lineReceived('* 9 FETCH (BODY[TEXT]<17> "foo")')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {9: [['BODY', ['TEXT'], '<17>', 'foo']]})
+
+
+ def test_incompleteFetchSpecificPartialResponse(self):
+ """
+ If the server responds to a I{BODY[TEXT]} request with a I{FETCH} line
+ which is truncated after the I{BODY[TEXT]<offset>} tokens, the
+ L{Deferred} returned by L{IMAP4Client.fetchUID} fails with
+ L{IllegalServerResponse}.
+ """
+ d = self.client.fetchSpecific('8', headerType='TEXT')
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 8 BODY[TEXT]\r\n')
+ self.client.lineReceived('* 8 FETCH (BODY[TEXT]<17>)')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertRaises(
+ imap4.IllegalServerResponse, self._extractDeferredResult, d)
+
+
+ def test_fetchSpecificHTML(self):
+ """
+ If the body of a message begins with I{<} and ends with I{>} (as,
+ for example, HTML bodies typically will), this is still interpreted
+ as the body by L{IMAP4Client.fetchSpecific} (and particularly, not
+ as a length indicator for a response to a request for a partial
+ body).
+ """
+ d = self.client.fetchSpecific('7')
+ self.assertEquals(
+ self.transport.value(), '0001 FETCH 7 BODY[]\r\n')
+ self.client.lineReceived('* 7 FETCH (BODY[] "<html>test</html>")')
+ self.client.lineReceived('0001 OK FETCH completed')
+ self.assertEquals(
+ self._extractDeferredResult(d), {7: [['BODY', [], "<html>test</html>"]]})
+
+
+
+class IMAP4ClientStoreTests(PreauthIMAP4ClientMixin, unittest.TestCase):
+ """
+ Tests for the L{IMAP4Client.setFlags}, L{IMAP4Client.addFlags}, and
+ L{IMAP4Client.removeFlags} methods.
+
+ An example of usage of the STORE command, in terms of which these three
+ methods are implemented, from RFC 3501, section 6.4.6::
+
+ C: A003 STORE 2:4 +FLAGS (\Deleted)
+ S: * 2 FETCH (FLAGS (\Deleted \Seen))
+ S: * 3 FETCH (FLAGS (\Deleted))
+ S: * 4 FETCH (FLAGS (\Deleted \Flagged \Seen))
+ S: A003 OK STORE completed
+ """
+ clientProtocol = StillSimplerClient
+
+ def _flagsTest(self, method, item):
+ """
+ Test a non-silent flag modifying method. Call the method, assert that
+ the correct bytes are sent, deliver a I{FETCH} response, and assert
+ that the result of the Deferred returned by the method is correct.
+
+ @param method: The name of the method to test.
+ @param item: The data item which is expected to be specified.
+ """
+ d = getattr(self.client, method)('3', ('\\Read', '\\Seen'), False)
+ self.assertEquals(
+ self.transport.value(),
+ '0001 STORE 3 ' + item + ' (\\Read \\Seen)\r\n')
+ self.client.lineReceived('* 3 FETCH (FLAGS (\\Read \\Seen))')
+ self.client.lineReceived('0001 OK STORE completed')
+ self.assertEquals(
+ self._extractDeferredResult(d),
+ {3: {'FLAGS': ['\\Read', '\\Seen']}})
+
+
+ def _flagsSilentlyTest(self, method, item):
+ """
+ Test a silent flag modifying method. Call the method, assert that the
+ correct bytes are sent, deliver an I{OK} response, and assert that the
+ result of the Deferred returned by the method is correct.
+
+ @param method: The name of the method to test.
+ @param item: The data item which is expected to be specified.
+ """
+ d = getattr(self.client, method)('3', ('\\Read', '\\Seen'), True)
+ self.assertEquals(
+ self.transport.value(),
+ '0001 STORE 3 ' + item + ' (\\Read \\Seen)\r\n')
+ self.client.lineReceived('0001 OK STORE completed')
+ self.assertEquals(self._extractDeferredResult(d), {})
+
+
+ def _flagsSilentlyWithUnsolicitedDataTest(self, method, item):
+ """
+ Test unsolicited data received in response to a silent flag modifying
+ method. Call the method, assert that the correct bytes are sent,
+ deliver the unsolicited I{FETCH} response, and assert that the result
+ of the Deferred returned by the method is correct.
+
+ @param method: The name of the method to test.
+ @param item: The data item which is expected to be specified.
+ """
+ d = getattr(self.client, method)('3', ('\\Read', '\\Seen'), True)
+ self.assertEquals(
+ self.transport.value(),
+ '0001 STORE 3 ' + item + ' (\\Read \\Seen)\r\n')
+ self.client.lineReceived('* 2 FETCH (FLAGS (\\Read \\Seen))')
+ self.client.lineReceived('0001 OK STORE completed')
+ self.assertEquals(self._extractDeferredResult(d), {})
+ self.assertEquals(self.client.flags, {2: ['\\Read', '\\Seen']})
+
+
+ def test_setFlags(self):
+ """
+ When passed a C{False} value for the C{silent} parameter,
+ L{IMAP4Client.setFlags} sends the I{STORE} command with a I{FLAGS} data
+ item and returns a L{Deferred} which fires with a C{dict} mapping
+ message sequence numbers to C{dict}s mapping C{'FLAGS'} to the new
+ flags of those messages.
+ """
+ self._flagsTest('setFlags', 'FLAGS')
+
+
+ def test_setFlagsSilently(self):
+ """
+ When passed a C{True} value for the C{silent} parameter,
+ L{IMAP4Client.setFlags} sends the I{STORE} command with a
+ I{FLAGS.SILENT} data item and returns a L{Deferred} which fires with an
+ empty dictionary.
+ """
+ self._flagsSilentlyTest('setFlags', 'FLAGS.SILENT')
+
+
+ def test_setFlagsSilentlyWithUnsolicitedData(self):
+ """
+ If unsolicited flag data is received in response to a I{STORE}
+ I{FLAGS.SILENT} request, that data is passed to the C{flagsChanged}
+ callback.
+ """
+ self._flagsSilentlyWithUnsolicitedDataTest('setFlags', 'FLAGS.SILENT')
+
+
+ def test_addFlags(self):
+ """
+ L{IMAP4Client.addFlags} is like L{IMAP4Client.setFlags}, but sends
+ I{+FLAGS} instead of I{FLAGS}.
+ """
+ self._flagsTest('addFlags', '+FLAGS')
+
+
+ def test_addFlagsSilently(self):
+ """
+ L{IMAP4Client.addFlags} with a C{True} value for C{silent} behaves like
+ L{IMAP4Client.setFlags} with a C{True} value for C{silent}, but it
+ sends I{+FLAGS.SILENT} instead of I{FLAGS.SILENT}.
+ """
+ self._flagsSilentlyTest('addFlags', '+FLAGS.SILENT')
+
+
+ def test_addFlagsSilentlyWithUnsolicitedData(self):
+ """
+ L{IMAP4Client.addFlags} behaves like L{IMAP4Client.setFlags} when used
+ in silent mode and unsolicited data is received.
+ """
+ self._flagsSilentlyWithUnsolicitedDataTest('addFlags', '+FLAGS.SILENT')
+
+
+ def test_removeFlags(self):
+ """
+ L{IMAP4Client.removeFlags} is like L{IMAP4Client.setFlags}, but sends
+ I{-FLAGS} instead of I{FLAGS}.
+ """
+ self._flagsTest('removeFlags', '-FLAGS')
+
+
+ def test_removeFlagsSilently(self):
+ """
+ L{IMAP4Client.removeFlags} with a C{True} value for C{silent} behaves
+ like L{IMAP4Client.setFlags} with a C{True} value for C{silent}, but it
+ sends I{-FLAGS.SILENT} instead of I{FLAGS.SILENT}.
+ """
+ self._flagsSilentlyTest('removeFlags', '-FLAGS.SILENT')
+
+
+ def test_removeFlagsSilentlyWithUnsolicitedData(self):
+ """
+ L{IMAP4Client.removeFlags} behaves like L{IMAP4Client.setFlags} when
+ used in silent mode and unsolicited data is received.
+ """
+ self._flagsSilentlyWithUnsolicitedDataTest('removeFlags', '-FLAGS.SILENT')
+
+
+
+class FakeyServer(imap4.IMAP4Server):
+ state = 'select'
+ timeout = None
+
+ def sendServerGreeting(self):
+ pass
+
+class FakeyMessage:
+ implements(imap4.IMessage)
+
+ def __init__(self, headers, flags, date, body, uid, subpart):
+ self.headers = headers
+ self.flags = flags
+ self.body = StringIO(body)
+ self.size = len(body)
+ self.date = date
+ self.uid = uid
+ self.subpart = subpart
+
+ def getHeaders(self, negate, *names):
+ self.got_headers = negate, names
+ return self.headers
+
+ def getFlags(self):
+ return self.flags
+
+ def getInternalDate(self):
+ return self.date
+
+ def getBodyFile(self):
+ return self.body
+
+ def getSize(self):
+ return self.size
+
+ def getUID(self):
+ return self.uid
+
+ def isMultipart(self):
+ return self.subpart is not None
+
+ def getSubPart(self, part):
+ self.got_subpart = part
+ return self.subpart[part]
+
+class NewStoreTestCase(unittest.TestCase, IMAP4HelperMixin):
+ result = None
+ storeArgs = None
+
+ def setUp(self):
+ self.received_messages = self.received_uid = None
+
+ self.server = imap4.IMAP4Server()
+ self.server.state = 'select'
+ self.server.mbox = self
+ self.connected = defer.Deferred()
+ self.client = SimpleClient(self.connected)
+
+ def addListener(self, x):
+ pass
+ def removeListener(self, x):
+ pass
+
+ def store(self, *args, **kw):
+ self.storeArgs = args, kw
+ return self.response
+
+ def _storeWork(self):
+ def connected():
+ return self.function(self.messages, self.flags, self.silent, self.uid)
+ def result(R):
+ self.result = R
+
+ self.connected.addCallback(strip(connected)
+ ).addCallback(result
+ ).addCallback(self._cbStopClient
+ ).addErrback(self._ebGeneral)
+
+ def check(ignored):
+ self.assertEquals(self.result, self.expected)
+ self.assertEquals(self.storeArgs, self.expectedArgs)
+ d = loopback.loopbackTCP(self.server, self.client, noisy=False)
+ d.addCallback(check)
+ return d
+
+ def testSetFlags(self, uid=0):
+ self.function = self.client.setFlags
+ self.messages = '1,5,9'
+ self.flags = ['\\A', '\\B', 'C']
+ self.silent = False
+ self.uid = uid
+ self.response = {
+ 1: ['\\A', '\\B', 'C'],
+ 5: ['\\A', '\\B', 'C'],
+ 9: ['\\A', '\\B', 'C'],
+ }
+ self.expected = {
+ 1: {'FLAGS': ['\\A', '\\B', 'C']},
+ 5: {'FLAGS': ['\\A', '\\B', 'C']},
+ 9: {'FLAGS': ['\\A', '\\B', 'C']},
+ }
+ msg = imap4.MessageSet()
+ msg.add(1)
+ msg.add(5)
+ msg.add(9)
+ self.expectedArgs = ((msg, ['\\A', '\\B', 'C'], 0), {'uid': 0})
+ return self._storeWork()
+
+
+class NewFetchTestCase(unittest.TestCase, IMAP4HelperMixin):
+ def setUp(self):
+ self.received_messages = self.received_uid = None
+ self.result = None
+
+ self.server = imap4.IMAP4Server()
+ self.server.state = 'select'
+ self.server.mbox = self
+ self.connected = defer.Deferred()
+ self.client = SimpleClient(self.connected)
+
+ def addListener(self, x):
+ pass
+ def removeListener(self, x):
+ pass
+
+ def fetch(self, messages, uid):
+ self.received_messages = messages
+ self.received_uid = uid
+ return iter(zip(range(len(self.msgObjs)), self.msgObjs))
+
+ def _fetchWork(self, uid):
+ if uid:
+ for (i, msg) in zip(range(len(self.msgObjs)), self.msgObjs):
+ self.expected[i]['UID'] = str(msg.getUID())
+
+ def result(R):
+ self.result = R
+
+ self.connected.addCallback(lambda _: self.function(self.messages, uid)
+ ).addCallback(result
+ ).addCallback(self._cbStopClient
+ ).addErrback(self._ebGeneral)
+
+ d = loopback.loopbackTCP(self.server, self.client, noisy=False)
+ d.addCallback(lambda x : self.assertEquals(self.result, self.expected))
+ return d
+
+ def testFetchUID(self):
+ self.function = lambda m, u: self.client.fetchUID(m)
+
+ self.messages = '7'
+ self.msgObjs = [
+ FakeyMessage({}, (), '', '', 12345, None),
+ FakeyMessage({}, (), '', '', 999, None),
+ FakeyMessage({}, (), '', '', 10101, None),
+ ]
+ self.expected = {
+ 0: {'UID': '12345'},
+ 1: {'UID': '999'},
+ 2: {'UID': '10101'},
+ }
+ return self._fetchWork(0)
+
+ def testFetchFlags(self, uid=0):
+ self.function = self.client.fetchFlags
+ self.messages = '9'
+ self.msgObjs = [
+ FakeyMessage({}, ['FlagA', 'FlagB', '\\FlagC'], '', '', 54321, None),
+ FakeyMessage({}, ['\\FlagC', 'FlagA', 'FlagB'], '', '', 12345, None),
+ ]
+ self.expected = {
+ 0: {'FLAGS': ['FlagA', 'FlagB', '\\FlagC']},
+ 1: {'FLAGS': ['\\FlagC', 'FlagA', 'FlagB']},
+ }
+ return self._fetchWork(uid)
+
+ def testFetchFlagsUID(self):
+ return self.testFetchFlags(1)
+
+ def testFetchInternalDate(self, uid=0):
+ self.function = self.client.fetchInternalDate
+ self.messages = '13'
+ self.msgObjs = [
+ FakeyMessage({}, (), 'Fri, 02 Nov 2003 21:25:10 GMT', '', 23232, None),
+ FakeyMessage({}, (), 'Thu, 29 Dec 2013 11:31:52 EST', '', 101, None),
+ FakeyMessage({}, (), 'Mon, 10 Mar 1992 02:44:30 CST', '', 202, None),
+ FakeyMessage({}, (), 'Sat, 11 Jan 2000 14:40:24 PST', '', 303, None),
+ ]
+ self.expected = {
+ 0: {'INTERNALDATE': '02-Nov-2003 21:25:10 +0000'},
+ 1: {'INTERNALDATE': '29-Dec-2013 11:31:52 -0500'},
+ 2: {'INTERNALDATE': '10-Mar-1992 02:44:30 -0600'},
+ 3: {'INTERNALDATE': '11-Jan-2000 14:40:24 -0800'},
+ }
+ return self._fetchWork(uid)
+
+ def testFetchInternalDateUID(self):
+ return self.testFetchInternalDate(1)
+
+ def testFetchEnvelope(self, uid=0):
+ self.function = self.client.fetchEnvelope
+ self.messages = '15'
+ self.msgObjs = [
+ FakeyMessage({
+ 'from': 'user@domain', 'to': 'resu@domain',
+ 'date': 'thursday', 'subject': 'it is a message',
+ 'message-id': 'id-id-id-yayaya'}, (), '', '', 65656,
+ None),
+ ]
+ self.expected = {
+ 0: {'ENVELOPE':
+ ['thursday', 'it is a message',
+ [[None, None, 'user', 'domain']],
+ [[None, None, 'user', 'domain']],
+ [[None, None, 'user', 'domain']],
+ [[None, None, 'resu', 'domain']],
+ None, None, None, 'id-id-id-yayaya']
+ }
+ }
+ return self._fetchWork(uid)
+
+ def testFetchEnvelopeUID(self):
+ return self.testFetchEnvelope(1)
+
+ def testFetchBodyStructure(self, uid=0):
+ self.function = self.client.fetchBodyStructure
+ self.messages = '3:9,10:*'
+ self.msgObjs = [FakeyMessage({
+ 'content-type': 'text/plain; name=thing; key="value"',
+ 'content-id': 'this-is-the-content-id',
+ 'content-description': 'describing-the-content-goes-here!',
+ 'content-transfer-encoding': '8BIT',
+ }, (), '', 'Body\nText\nGoes\nHere\n', 919293, None)]
+ self.expected = {0: {'BODYSTRUCTURE': [
+ 'text', 'plain', [['name', 'thing'], ['key', 'value']],
+ 'this-is-the-content-id', 'describing-the-content-goes-here!',
+ '8BIT', '20', '4', None, None, None]}}
+ return self._fetchWork(uid)
+
+ def testFetchBodyStructureUID(self):
+ return self.testFetchBodyStructure(1)
+
+ def testFetchSimplifiedBody(self, uid=0):
+ self.function = self.client.fetchSimplifiedBody
+ self.messages = '21'
+ self.msgObjs = [FakeyMessage({}, (), '', 'Yea whatever', 91825,
+ [FakeyMessage({'content-type': 'image/jpg'}, (), '',
+ 'Body Body Body', None, None
+ )]
+ )]
+ self.expected = {0:
+ {'BODY':
+ [None, None, [], None, None, None,
+ '12'
+ ]
+ }
+ }
+
+ return self._fetchWork(uid)
+
+ def testFetchSimplifiedBodyUID(self):
+ return self.testFetchSimplifiedBody(1)
+
+ def testFetchSimplifiedBodyText(self, uid=0):
+ self.function = self.client.fetchSimplifiedBody
+ self.messages = '21'
+ self.msgObjs = [FakeyMessage({'content-type': 'text/plain'},
+ (), '', 'Yea whatever', 91825, None)]
+ self.expected = {0:
+ {'BODY':
+ ['text', 'plain', [], None, None, None,
+ '12', '1'
+ ]
+ }
+ }
+
+ return self._fetchWork(uid)
+
+ def testFetchSimplifiedBodyTextUID(self):
+ return self.testFetchSimplifiedBodyText(1)
+
+ def testFetchSimplifiedBodyRFC822(self, uid=0):
+ self.function = self.client.fetchSimplifiedBody
+ self.messages = '21'
+ self.msgObjs = [FakeyMessage({'content-type': 'message/rfc822'},
+ (), '', 'Yea whatever', 91825,
+ [FakeyMessage({'content-type': 'image/jpg'}, (), '',
+ 'Body Body Body', None, None
+ )]
+ )]
+ self.expected = {0:
+ {'BODY':
+ ['message', 'rfc822', [], None, None, None,
+ '12', [None, None, [[None, None, None]],
+ [[None, None, None]], None, None, None,
+ None, None, None], ['image', 'jpg', [],
+ None, None, None, '14'], '1'
+ ]
+ }
+ }
+
+ return self._fetchWork(uid)
+
+ def testFetchSimplifiedBodyRFC822UID(self):
+ return self.testFetchSimplifiedBodyRFC822(1)
+
+ def testFetchMessage(self, uid=0):
+ self.function = self.client.fetchMessage
+ self.messages = '1,3,7,10101'
+ self.msgObjs = [
+ FakeyMessage({'Header': 'Value'}, (), '', 'BODY TEXT\r\n', 91, None),
+ ]
+ self.expected = {
+ 0: {'RFC822': 'Header: Value\r\n\r\nBODY TEXT\r\n'}
+ }
+ return self._fetchWork(uid)
+
+ def testFetchMessageUID(self):
+ return self.testFetchMessage(1)
+
+ def testFetchHeaders(self, uid=0):
+ self.function = self.client.fetchHeaders
+ self.messages = '9,6,2'
+ self.msgObjs = [
+ FakeyMessage({'H1': 'V1', 'H2': 'V2'}, (), '', '', 99, None),
+ ]
+ self.expected = {
+ 0: {'RFC822.HEADER': imap4._formatHeaders({'H1': 'V1', 'H2': 'V2'})},
+ }
+ return self._fetchWork(uid)
+
+ def testFetchHeadersUID(self):
+ return self.testFetchHeaders(1)
+
+ def testFetchBody(self, uid=0):
+ self.function = self.client.fetchBody
+ self.messages = '1,2,3,4,5,6,7'
+ self.msgObjs = [
+ FakeyMessage({'Header': 'Value'}, (), '', 'Body goes here\r\n', 171, None),
+ ]
+ self.expected = {
+ 0: {'RFC822.TEXT': 'Body goes here\r\n'},
+ }
+ return self._fetchWork(uid)
+
+ def testFetchBodyUID(self):
+ return self.testFetchBody(1)
+
+ def testFetchBodyParts(self):
+ """
+ Test the server's handling of requests for specific body sections.
+ """
+ self.function = self.client.fetchSpecific
+ self.messages = '1'
+ outerBody = ''
+ innerBody1 = 'Contained body message text. Squarge.'
+ innerBody2 = 'Secondary <i>message</i> text of squarge body.'
+ headers = util.OrderedDict()
+ headers['from'] = 'sender@host'
+ headers['to'] = 'recipient@domain'
+ headers['subject'] = 'booga booga boo'
+ headers['content-type'] = 'multipart/alternative; boundary="xyz"'
+ innerHeaders = util.OrderedDict()
+ innerHeaders['subject'] = 'this is subject text'
+ innerHeaders['content-type'] = 'text/plain'
+ innerHeaders2 = util.OrderedDict()
+ innerHeaders2['subject'] = '<b>this is subject</b>'
+ innerHeaders2['content-type'] = 'text/html'
+ self.msgObjs = [FakeyMessage(
+ headers, (), None, outerBody, 123,
+ [FakeyMessage(innerHeaders, (), None, innerBody1, None, None),
+ FakeyMessage(innerHeaders2, (), None, innerBody2, None, None)])]
+ self.expected = {
+ 0: [['BODY', ['1'], 'Contained body message text. Squarge.']]}
+
+ def result(R):
+ self.result = R
+
+ self.connected.addCallback(
+ lambda _: self.function(self.messages, headerNumber=1))
+ self.connected.addCallback(result)
+ self.connected.addCallback(self._cbStopClient)
+ self.connected.addErrback(self._ebGeneral)
+
+ d = loopback.loopbackTCP(self.server, self.client, noisy=False)
+ d.addCallback(lambda ign: self.assertEquals(self.result, self.expected))
+ return d
+
+
+ def test_fetchBodyPartOfNonMultipart(self):
+ """
+ Single-part messages have an implicit first part which clients
+ should be able to retrieve explicitly. Test that a client
+ requesting part 1 of a text/plain message receives the body of the
+ text/plain part.
+ """
+ self.function = self.client.fetchSpecific
+ self.messages = '1'
+ parts = [1]
+ outerBody = 'DA body'
+ headers = util.OrderedDict()
+ headers['from'] = 'sender@host'
+ headers['to'] = 'recipient@domain'
+ headers['subject'] = 'booga booga boo'
+ headers['content-type'] = 'text/plain'
+ self.msgObjs = [FakeyMessage(
+ headers, (), None, outerBody, 123, None)]
+
+ self.expected = {0: [['BODY', ['1'], 'DA body']]}
+
+ def result(R):
+ self.result = R
+
+ self.connected.addCallback(
+ lambda _: self.function(self.messages, headerNumber=parts))
+ self.connected.addCallback(result)
+ self.connected.addCallback(self._cbStopClient)
+ self.connected.addErrback(self._ebGeneral)
+
+ d = loopback.loopbackTCP(self.server, self.client, noisy=False)
+ d.addCallback(lambda ign: self.assertEquals(self.result, self.expected))
+ return d
+
+
+ def testFetchSize(self, uid=0):
+ self.function = self.client.fetchSize
+ self.messages = '1:100,2:*'
+ self.msgObjs = [
+ FakeyMessage({}, (), '', 'x' * 20, 123, None),
+ ]
+ self.expected = {
+ 0: {'RFC822.SIZE': '20'},
+ }
+ return self._fetchWork(uid)
+
+ def testFetchSizeUID(self):
+ return self.testFetchSize(1)
+
+ def testFetchFull(self, uid=0):
+ self.function = self.client.fetchFull
+ self.messages = '1,3'
+ self.msgObjs = [
+ FakeyMessage({}, ('\\XYZ', '\\YZX', 'Abc'),
+ 'Sun, 25 Jul 2010 06:20:30 -0400 (EDT)',
+ 'xyz' * 2, 654, None),
+ FakeyMessage({}, ('\\One', '\\Two', 'Three'),
+ 'Mon, 14 Apr 2003 19:43:44 -0400',
+ 'abc' * 4, 555, None),
+ ]
+ self.expected = {
+ 0: {'FLAGS': ['\\XYZ', '\\YZX', 'Abc'],
+ 'INTERNALDATE': '25-Jul-2010 06:20:30 -0400',
+ 'RFC822.SIZE': '6',
+ 'ENVELOPE': [None, None, [[None, None, None]], [[None, None, None]], None, None, None, None, None, None],
+ 'BODY': [None, None, [], None, None, None, '6']},
+ 1: {'FLAGS': ['\\One', '\\Two', 'Three'],
+ 'INTERNALDATE': '14-Apr-2003 19:43:44 -0400',
+ 'RFC822.SIZE': '12',
+ 'ENVELOPE': [None, None, [[None, None, None]], [[None, None, None]], None, None, None, None, None, None],
+ 'BODY': [None, None, [], None, None, None, '12']},
+ }
+ return self._fetchWork(uid)
+
+ def testFetchFullUID(self):
+ return self.testFetchFull(1)
+
+ def testFetchAll(self, uid=0):
+ self.function = self.client.fetchAll
+ self.messages = '1,2:3'
+ self.msgObjs = [
+ FakeyMessage({}, (), 'Mon, 14 Apr 2003 19:43:44 +0400',
+ 'Lalala', 10101, None),
+ FakeyMessage({}, (), 'Tue, 15 Apr 2003 19:43:44 +0200',
+ 'Alalal', 20202, None),
+ ]
+ self.expected = {
+ 0: {'ENVELOPE': [None, None, [[None, None, None]], [[None, None, None]], None, None, None, None, None, None],
+ 'RFC822.SIZE': '6',
+ 'INTERNALDATE': '14-Apr-2003 19:43:44 +0400',
+ 'FLAGS': []},
+ 1: {'ENVELOPE': [None, None, [[None, None, None]], [[None, None, None]], None, None, None, None, None, None],
+ 'RFC822.SIZE': '6',
+ 'INTERNALDATE': '15-Apr-2003 19:43:44 +0200',
+ 'FLAGS': []},
+ }
+ return self._fetchWork(uid)
+
+ def testFetchAllUID(self):
+ return self.testFetchAll(1)
+
+ def testFetchFast(self, uid=0):
+ self.function = self.client.fetchFast
+ self.messages = '1'
+ self.msgObjs = [
+ FakeyMessage({}, ('\\X',), '19 Mar 2003 19:22:21 -0500', '', 9, None),
+ ]
+ self.expected = {
+ 0: {'FLAGS': ['\\X'],
+ 'INTERNALDATE': '19-Mar-2003 19:22:21 -0500',
+ 'RFC822.SIZE': '0'},
+ }
+ return self._fetchWork(uid)
+
+ def testFetchFastUID(self):
+ return self.testFetchFast(1)
+
+
+
+class DefaultSearchTestCase(IMAP4HelperMixin, unittest.TestCase):
+ """
+ Test the behavior of the server's SEARCH implementation, particularly in
+ the face of unhandled search terms.
+ """
+ def setUp(self):
+ self.server = imap4.IMAP4Server()
+ self.server.state = 'select'
+ self.server.mbox = self
+ self.connected = defer.Deferred()
+ self.client = SimpleClient(self.connected)
+ self.msgObjs = [
+ FakeyMessage({}, (), '', '', 999, None),
+ FakeyMessage({}, (), '', '', 10101, None),
+ FakeyMessage({}, (), '', '', 12345, None),
+ ]
+
+
+ def fetch(self, messages, uid):
+ """
+ Pretend to be a mailbox and let C{self.server} lookup messages on me.
+ """
+ return zip(range(1, len(self.msgObjs) + 1), self.msgObjs)
+
+
+ def _messageSetSearchTest(self, queryTerms, expectedMessages):
+ """
+ Issue a search with given query and verify that the returned messages
+ match the given expected messages.
+
+ @param queryTerms: A string giving the search query.
+ @param expectedMessages: A list of the message sequence numbers
+ expected as the result of the search.
+ @return: A L{Deferred} which fires when the test is complete.
+ """
+ def search():
+ return self.client.search(queryTerms)
+
+ d = self.connected.addCallback(strip(search))
+ def searched(results):
+ self.assertEquals(results, expectedMessages)
+ d.addCallback(searched)
+ d.addCallback(self._cbStopClient)
+ d.addErrback(self._ebGeneral)
+ self.loopback()
+ return d
+
+
+ def test_searchMessageSet(self):
+ """
+ Test that a search which starts with a message set properly limits
+ the search results to messages in that set.
+ """
+ return self._messageSetSearchTest('1', [1])
+
+
+ def test_searchMessageSetWithStar(self):
+ """
+ If the search filter ends with a star, all the message from the
+ starting point are returned.
+ """
+ return self._messageSetSearchTest('2:*', [2, 3])
+
+
+ def test_searchMessageSetWithList(self):
+ """
+ If the search filter contains nesting terms, one of which includes a
+ message sequence set with a wildcard, IT ALL WORKS GOOD.
+ """
+ # 5 is bigger than the biggest message sequence number, but that's
+ # okay, because N:* includes the biggest message sequence number even
+ # if N is bigger than that (read the rfc nub).
+ return self._messageSetSearchTest('(5:*)', [3])
+
+
+ def test_searchOr(self):
+ """
+ If the search filter contains an I{OR} term, all messages
+ which match either subexpression are returned.
+ """
+ return self._messageSetSearchTest('OR 1 2', [1, 2])
+
+
+ def test_searchOrMessageSet(self):
+ """
+ If the search filter contains an I{OR} term with a
+ subexpression which includes a message sequence set wildcard,
+ all messages in that set are considered for inclusion in the
+ results.
+ """
+ return self._messageSetSearchTest('OR 2:* 2:*', [2, 3])
+
+
+ def test_searchNot(self):
+ """
+ If the search filter contains a I{NOT} term, all messages
+ which do not match the subexpression are returned.
+ """
+ return self._messageSetSearchTest('NOT 3', [1, 2])
+
+
+ def test_searchNotMessageSet(self):
+ """
+ If the search filter contains a I{NOT} term with a
+ subexpression which includes a message sequence set wildcard,
+ no messages in that set are considered for inclusion in the
+ result.
+ """
+ return self._messageSetSearchTest('NOT 2:*', [1])
+
+
+ def test_searchAndMessageSet(self):
+ """
+ If the search filter contains multiple terms implicitly
+ conjoined with a message sequence set wildcard, only the
+ intersection of the results of each term are returned.
+ """
+ return self._messageSetSearchTest('2:* 3', [3])
+
+
+
+class FetchSearchStoreTestCase(unittest.TestCase, IMAP4HelperMixin):
+ implements(imap4.ISearchableMailbox)
+
+ def setUp(self):
+ self.expected = self.result = None
+ self.server_received_query = None
+ self.server_received_uid = None
+ self.server_received_parts = None
+ self.server_received_messages = None
+
+ self.server = imap4.IMAP4Server()
+ self.server.state = 'select'
+ self.server.mbox = self
+ self.connected = defer.Deferred()
+ self.client = SimpleClient(self.connected)
+
+ def search(self, query, uid):
+ self.server_received_query = query
+ self.server_received_uid = uid
+ return self.expected
+
+ def addListener(self, *a, **kw):
+ pass
+ removeListener = addListener
+
+ def _searchWork(self, uid):
+ def search():
+ return self.client.search(self.query, uid=uid)
+ def result(R):
+ self.result = R
+
+ self.connected.addCallback(strip(search)
+ ).addCallback(result
+ ).addCallback(self._cbStopClient
+ ).addErrback(self._ebGeneral)
+
+ def check(ignored):
+ # Ensure no short-circuiting wierdness is going on
+ self.failIf(self.result is self.expected)
+
+ self.assertEquals(self.result, self.expected)
+ self.assertEquals(self.uid, self.server_received_uid)
+ self.assertEquals(
+ imap4.parseNestedParens(self.query),
+ self.server_received_query
+ )
+ d = loopback.loopbackTCP(self.server, self.client, noisy=False)
+ d.addCallback(check)
+ return d
+
+ def testSearch(self):
+ self.query = imap4.Or(
+ imap4.Query(header=('subject', 'substring')),
+ imap4.Query(larger=1024, smaller=4096),
+ )
+ self.expected = [1, 4, 5, 7]
+ self.uid = 0
+ return self._searchWork(0)
+
+ def testUIDSearch(self):
+ self.query = imap4.Or(
+ imap4.Query(header=('subject', 'substring')),
+ imap4.Query(larger=1024, smaller=4096),
+ )
+ self.uid = 1
+ self.expected = [1, 2, 3]
+ return self._searchWork(1)
+
+ def getUID(self, msg):
+ try:
+ return self.expected[msg]['UID']
+ except (TypeError, IndexError):
+ return self.expected[msg-1]
+ except KeyError:
+ return 42
+
+ def fetch(self, messages, uid):
+ self.server_received_uid = uid
+ self.server_received_messages = str(messages)
+ return self.expected
+
+ def _fetchWork(self, fetch):
+ def result(R):
+ self.result = R
+
+ self.connected.addCallback(strip(fetch)
+ ).addCallback(result
+ ).addCallback(self._cbStopClient
+ ).addErrback(self._ebGeneral)
+
+ def check(ignored):
+ # Ensure no short-circuiting wierdness is going on
+ self.failIf(self.result is self.expected)
+
+ self.parts and self.parts.sort()
+ self.server_received_parts and self.server_received_parts.sort()
+
+ if self.uid:
+ for (k, v) in self.expected.items():
+ v['UID'] = str(k)
+
+ self.assertEquals(self.result, self.expected)
+ self.assertEquals(self.uid, self.server_received_uid)
+ self.assertEquals(self.parts, self.server_received_parts)
+ self.assertEquals(imap4.parseIdList(self.messages),
+ imap4.parseIdList(self.server_received_messages))
+
+ d = loopback.loopbackTCP(self.server, self.client, noisy=False)
+ d.addCallback(check)
+ return d
+
+
+
+class FakeMailbox:
+ def __init__(self):
+ self.args = []
+ def addMessage(self, body, flags, date):
+ self.args.append((body, flags, date))
+ return defer.succeed(None)
+
+class FeaturefulMessage:
+ implements(imap4.IMessageFile)
+
+ def getFlags(self):
+ return 'flags'
+
+ def getInternalDate(self):
+ return 'internaldate'
+
+ def open(self):
+ return StringIO("open")
+
+class MessageCopierMailbox:
+ implements(imap4.IMessageCopier)
+
+ def __init__(self):
+ self.msgs = []
+
+ def copy(self, msg):
+ self.msgs.append(msg)
+ return len(self.msgs)
+
+class CopyWorkerTestCase(unittest.TestCase):
+ def testFeaturefulMessage(self):
+ s = imap4.IMAP4Server()
+
+ # Yes. I am grabbing this uber-non-public method to test it.
+ # It is complex. It needs to be tested directly!
+ # Perhaps it should be refactored, simplified, or split up into
+ # not-so-private components, but that is a task for another day.
+
+ # Ha ha! Addendum! Soon it will be split up, and this test will
+ # be re-written to just use the default adapter for IMailbox to
+ # IMessageCopier and call .copy on that adapter.
+ f = s._IMAP4Server__cbCopy
+
+ m = FakeMailbox()
+ d = f([(i, FeaturefulMessage()) for i in range(1, 11)], 'tag', m)
+
+ def cbCopy(results):
+ for a in m.args:
+ self.assertEquals(a[0].read(), "open")
+ self.assertEquals(a[1], "flags")
+ self.assertEquals(a[2], "internaldate")
+
+ for (status, result) in results:
+ self.failUnless(status)
+ self.assertEquals(result, None)
+
+ return d.addCallback(cbCopy)
+
+
+ def testUnfeaturefulMessage(self):
+ s = imap4.IMAP4Server()
+
+ # See above comment
+ f = s._IMAP4Server__cbCopy
+
+ m = FakeMailbox()
+ msgs = [FakeyMessage({'Header-Counter': str(i)}, (), 'Date', 'Body %d' % (i,), i + 10, None) for i in range(1, 11)]
+ d = f([im for im in zip(range(1, 11), msgs)], 'tag', m)
+
+ def cbCopy(results):
+ seen = []
+ for a in m.args:
+ seen.append(a[0].read())
+ self.assertEquals(a[1], ())
+ self.assertEquals(a[2], "Date")
+
+ seen.sort()
+ exp = ["Header-Counter: %d\r\n\r\nBody %d" % (i, i) for i in range(1, 11)]
+ exp.sort()
+ self.assertEquals(seen, exp)
+
+ for (status, result) in results:
+ self.failUnless(status)
+ self.assertEquals(result, None)
+
+ return d.addCallback(cbCopy)
+
+ def testMessageCopier(self):
+ s = imap4.IMAP4Server()
+
+ # See above comment
+ f = s._IMAP4Server__cbCopy
+
+ m = MessageCopierMailbox()
+ msgs = [object() for i in range(1, 11)]
+ d = f([im for im in zip(range(1, 11), msgs)], 'tag', m)
+
+ def cbCopy(results):
+ self.assertEquals(results, zip([1] * 10, range(1, 11)))
+ for (orig, new) in zip(msgs, m.msgs):
+ self.assertIdentical(orig, new)
+
+ return d.addCallback(cbCopy)
+
+
+class TLSTestCase(IMAP4HelperMixin, unittest.TestCase):
+ serverCTX = ServerTLSContext and ServerTLSContext()
+ clientCTX = ClientTLSContext and ClientTLSContext()
+
+ def loopback(self):
+ return loopback.loopbackTCP(self.server, self.client, noisy=False)
+
+ def testAPileOfThings(self):
+ SimpleServer.theAccount.addMailbox('inbox')
+ called = []
+ def login():
+ called.append(None)
+ return self.client.login('testuser', 'password-test')
+ def list():
+ called.append(None)
+ return self.client.list('inbox', '%')
+ def status():
+ called.append(None)
+ return self.client.status('inbox', 'UIDNEXT')
+ def examine():
+ called.append(None)
+ return self.client.examine('inbox')
+ def logout():
+ called.append(None)
+ return self.client.logout()
+
+ self.client.requireTransportSecurity = True
+
+ methods = [login, list, status, examine, logout]
+ map(self.connected.addCallback, map(strip, methods))
+ self.connected.addCallbacks(self._cbStopClient, self._ebGeneral)
+ def check(ignored):
+ self.assertEquals(self.server.startedTLS, True)
+ self.assertEquals(self.client.startedTLS, True)
+ self.assertEquals(len(called), len(methods))
+ d = self.loopback()
+ d.addCallback(check)
+ return d
+
+ def testLoginLogin(self):
+ self.server.checker.addUser('testuser', 'password-test')
+ success = []
+ self.client.registerAuthenticator(imap4.LOGINAuthenticator('testuser'))
+ self.connected.addCallback(
+ lambda _: self.client.authenticate('password-test')
+ ).addCallback(
+ lambda _: self.client.logout()
+ ).addCallback(success.append
+ ).addCallback(self._cbStopClient
+ ).addErrback(self._ebGeneral)
+
+ d = self.loopback()
+ d.addCallback(lambda x : self.assertEquals(len(success), 1))
+ return d
+
+
+ def test_startTLS(self):
+ """
+ L{IMAP4Client.startTLS} triggers TLS negotiation and returns a
+ L{Deferred} which fires after the client's transport is using
+ encryption.
+ """
+ success = []
+ self.connected.addCallback(lambda _: self.client.startTLS())
+ def checkSecure(ignored):
+ self.assertTrue(
+ interfaces.ISSLTransport.providedBy(self.client.transport))
+ self.connected.addCallback(checkSecure)
+ self.connected.addCallback(self._cbStopClient)
+ self.connected.addCallback(success.append)
+ self.connected.addErrback(self._ebGeneral)
+
+ d = self.loopback()
+ d.addCallback(lambda x : self.failUnless(success))
+ return defer.gatherResults([d, self.connected])
+
+
+ def testFailedStartTLS(self):
+ failure = []
+ def breakServerTLS(ign):
+ self.server.canStartTLS = False
+
+ self.connected.addCallback(breakServerTLS)
+ self.connected.addCallback(lambda ign: self.client.startTLS())
+ self.connected.addErrback(lambda err: failure.append(err.trap(imap4.IMAP4Exception)))
+ self.connected.addCallback(self._cbStopClient)
+ self.connected.addErrback(self._ebGeneral)
+
+ def check(ignored):
+ self.failUnless(failure)
+ self.assertIdentical(failure[0], imap4.IMAP4Exception)
+ return self.loopback().addCallback(check)
+
+
+
+class SlowMailbox(SimpleMailbox):
+ howSlow = 2
+ callLater = None
+ fetchDeferred = None
+
+ # Not a very nice implementation of fetch(), but it'll
+ # do for the purposes of testing.
+ def fetch(self, messages, uid):
+ d = defer.Deferred()
+ self.callLater(self.howSlow, d.callback, ())
+ self.fetchDeferred.callback(None)
+ return d
+
+class Timeout(IMAP4HelperMixin, unittest.TestCase):
+
+ def test_serverTimeout(self):
+ """
+ The *client* has a timeout mechanism which will close connections that
+ are inactive for a period.
+ """
+ c = Clock()
+ self.server.timeoutTest = True
+ self.client.timeout = 5 #seconds
+ self.client.callLater = c.callLater
+ self.selectedArgs = None
+
+ def login():
+ d = self.client.login('testuser', 'password-test')
+ c.advance(5)
+ d.addErrback(timedOut)
+ return d
+
+ def timedOut(failure):
+ self._cbStopClient(None)
+ failure.trap(error.TimeoutError)
+
+ d = self.connected.addCallback(strip(login))
+ d.addErrback(self._ebGeneral)
+ return defer.gatherResults([d, self.loopback()])
+
+
+ def test_longFetchDoesntTimeout(self):
+ """
+ The connection timeout does not take effect during fetches.
+ """
+ c = Clock()
+ SlowMailbox.callLater = c.callLater
+ SlowMailbox.fetchDeferred = defer.Deferred()
+ self.server.callLater = c.callLater
+ SimpleServer.theAccount.mailboxFactory = SlowMailbox
+ SimpleServer.theAccount.addMailbox('mailbox-test')
+
+ self.server.setTimeout(1)
+
+ def login():
+ return self.client.login('testuser', 'password-test')
+ def select():
+ self.server.setTimeout(1)
+ return self.client.select('mailbox-test')
+ def fetch():
+ return self.client.fetchUID('1:*')
+ def stillConnected():
+ self.assertNotEquals(self.server.state, 'timeout')
+
+ def cbAdvance(ignored):
+ for i in xrange(4):
+ c.advance(.5)
+
+ SlowMailbox.fetchDeferred.addCallback(cbAdvance)
+
+ d1 = self.connected.addCallback(strip(login))
+ d1.addCallback(strip(select))
+ d1.addCallback(strip(fetch))
+ d1.addCallback(strip(stillConnected))
+ d1.addCallback(self._cbStopClient)
+ d1.addErrback(self._ebGeneral)
+ d = defer.gatherResults([d1, self.loopback()])
+ return d
+
+
+ def test_idleClientDoesDisconnect(self):
+ """
+ The *server* has a timeout mechanism which will close connections that
+ are inactive for a period.
+ """
+ c = Clock()
+ # Hook up our server protocol
+ transport = StringTransportWithDisconnection()
+ transport.protocol = self.server
+ self.server.callLater = c.callLater
+ self.server.makeConnection(transport)
+
+ # Make sure we can notice when the connection goes away
+ lost = []
+ connLost = self.server.connectionLost
+ self.server.connectionLost = lambda reason: (lost.append(None), connLost(reason))[1]
+
+ # 2/3rds of the idle timeout elapses...
+ c.pump([0.0] + [self.server.timeOut / 3.0] * 2)
+ self.failIf(lost, lost)
+
+ # Now some more
+ c.pump([0.0, self.server.timeOut / 2.0])
+ self.failUnless(lost)
+
+
+
+class Disconnection(unittest.TestCase):
+ def testClientDisconnectFailsDeferreds(self):
+ c = imap4.IMAP4Client()
+ t = StringTransportWithDisconnection()
+ c.makeConnection(t)
+ d = self.assertFailure(c.login('testuser', 'example.com'), error.ConnectionDone)
+ c.connectionLost(error.ConnectionDone("Connection closed"))
+ return d
+
+
+
+class SynchronousMailbox(object):
+ """
+ Trivial, in-memory mailbox implementation which can produce a message
+ synchronously.
+ """
+ def __init__(self, messages):
+ self.messages = messages
+
+
+ def fetch(self, msgset, uid):
+ assert not uid, "Cannot handle uid requests."
+ for msg in msgset:
+ yield msg, self.messages[msg - 1]
+
+
+
+class StringTransportConsumer(StringTransport):
+ producer = None
+ streaming = None
+
+ def registerProducer(self, producer, streaming):
+ self.producer = producer
+ self.streaming = streaming
+
+
+
+class Pipelining(unittest.TestCase):
+ """
+ Tests for various aspects of the IMAP4 server's pipelining support.
+ """
+ messages = [
+ FakeyMessage({}, [], '', '0', None, None),
+ FakeyMessage({}, [], '', '1', None, None),
+ FakeyMessage({}, [], '', '2', None, None),
+ ]
+
+ def setUp(self):
+ self.iterators = []
+
+ self.transport = StringTransportConsumer()
+ self.server = imap4.IMAP4Server(None, None, self.iterateInReactor)
+ self.server.makeConnection(self.transport)
+
+
+ def iterateInReactor(self, iterator):
+ d = defer.Deferred()
+ self.iterators.append((iterator, d))
+ return d
+
+
+ def tearDown(self):
+ self.server.connectionLost(failure.Failure(error.ConnectionDone()))
+
+
+ def test_synchronousFetch(self):
+ """
+ Test that pipelined FETCH commands which can be responded to
+ synchronously are responded to correctly.
+ """
+ mailbox = SynchronousMailbox(self.messages)
+
+ # Skip over authentication and folder selection
+ self.server.state = 'select'
+ self.server.mbox = mailbox
+
+ # Get rid of any greeting junk
+ self.transport.clear()
+
+ # Here's some pipelined stuff
+ self.server.dataReceived(
+ '01 FETCH 1 BODY[]\r\n'
+ '02 FETCH 2 BODY[]\r\n'
+ '03 FETCH 3 BODY[]\r\n')
+
+ # Flush anything the server has scheduled to run
+ while self.iterators:
+ for e in self.iterators[0][0]:
+ break
+ else:
+ self.iterators.pop(0)[1].callback(None)
+
+ # The bodies are empty because we aren't simulating a transport
+ # exactly correctly (we have StringTransportConsumer but we never
+ # call resumeProducing on its producer). It doesn't matter: just
+ # make sure the surrounding structure is okay, and that no
+ # exceptions occurred.
+ self.assertEquals(
+ self.transport.value(),
+ '* 1 FETCH (BODY[] )\r\n'
+ '01 OK FETCH completed\r\n'
+ '* 2 FETCH (BODY[] )\r\n'
+ '02 OK FETCH completed\r\n'
+ '* 3 FETCH (BODY[] )\r\n'
+ '03 OK FETCH completed\r\n')
+
+
+
+if ClientTLSContext is None:
+ for case in (TLSTestCase,):
+ case.skip = "OpenSSL not present"
+elif interfaces.IReactorSSL(reactor, None) is None:
+ for case in (TLSTestCase,):
+ case.skip = "Reactor doesn't support SSL"
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/test_mail.py b/vendor/Twisted-10.0.0/twisted/mail/test/test_mail.py
new file mode 100644
index 0000000000..5f2349a4c0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/test_mail.py
@@ -0,0 +1,1968 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for large portions of L{twisted.mail}.
+"""
+
+import os
+import errno
+import shutil
+import pickle
+import StringIO
+import rfc822
+import tempfile
+import signal
+
+from zope.interface import Interface, implements
+
+from twisted.trial import unittest
+from twisted.mail import smtp
+from twisted.mail import pop3
+from twisted.names import dns
+from twisted.internet import protocol
+from twisted.internet import defer
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
+from twisted.internet import interfaces
+from twisted.internet import task
+from twisted.internet.error import DNSLookupError, CannotListenError
+from twisted.internet.error import ProcessDone, ProcessTerminated
+from twisted.internet import address
+from twisted.python import failure
+from twisted.python.filepath import FilePath
+from twisted.python.hashlib import md5
+
+from twisted import mail
+import twisted.mail.mail
+import twisted.mail.maildir
+import twisted.mail.relay
+import twisted.mail.relaymanager
+import twisted.mail.protocols
+import twisted.mail.alias
+
+from twisted.names.error import DNSNameError
+from twisted.names.dns import RRHeader, Record_CNAME, Record_MX
+
+from twisted import cred
+import twisted.cred.credentials
+import twisted.cred.checkers
+import twisted.cred.portal
+
+from twisted.test.proto_helpers import LineSendingProtocol
+
+class DomainWithDefaultsTestCase(unittest.TestCase):
+ def testMethods(self):
+ d = dict([(x, x + 10) for x in range(10)])
+ d = mail.mail.DomainWithDefaultDict(d, 'Default')
+
+ self.assertEquals(len(d), 10)
+ self.assertEquals(list(iter(d)), range(10))
+ self.assertEquals(list(d.iterkeys()), list(iter(d)))
+
+ items = list(d.iteritems())
+ items.sort()
+ self.assertEquals(items, [(x, x + 10) for x in range(10)])
+
+ values = list(d.itervalues())
+ values.sort()
+ self.assertEquals(values, range(10, 20))
+
+ items = d.items()
+ items.sort()
+ self.assertEquals(items, [(x, x + 10) for x in range(10)])
+
+ values = d.values()
+ values.sort()
+ self.assertEquals(values, range(10, 20))
+
+ for x in range(10):
+ self.assertEquals(d[x], x + 10)
+ self.assertEquals(d.get(x), x + 10)
+ self.failUnless(x in d)
+ self.failUnless(d.has_key(x))
+
+ del d[2], d[4], d[6]
+
+ self.assertEquals(len(d), 7)
+ self.assertEquals(d[2], 'Default')
+ self.assertEquals(d[4], 'Default')
+ self.assertEquals(d[6], 'Default')
+
+ d.update({'a': None, 'b': (), 'c': '*'})
+ self.assertEquals(len(d), 10)
+ self.assertEquals(d['a'], None)
+ self.assertEquals(d['b'], ())
+ self.assertEquals(d['c'], '*')
+
+ d.clear()
+ self.assertEquals(len(d), 0)
+
+ self.assertEquals(d.setdefault('key', 'value'), 'value')
+ self.assertEquals(d['key'], 'value')
+
+ self.assertEquals(d.popitem(), ('key', 'value'))
+ self.assertEquals(len(d), 0)
+
+ dcopy = d.copy()
+ self.assertEquals(d.domains, dcopy.domains)
+ self.assertEquals(d.default, dcopy.default)
+
+
+ def _stringificationTest(self, stringifier):
+ """
+ Assert that the class name of a L{mail.mail.DomainWithDefaultDict}
+ instance and the string-formatted underlying domain dictionary both
+ appear in the string produced by the given string-returning function.
+
+ @type stringifier: one-argument callable
+ @param stringifier: either C{str} or C{repr}, to be used to get a
+ string to make assertions against.
+ """
+ domain = mail.mail.DomainWithDefaultDict({}, 'Default')
+ self.assertIn(domain.__class__.__name__, stringifier(domain))
+ domain['key'] = 'value'
+ self.assertIn(str({'key': 'value'}), stringifier(domain))
+
+
+ def test_str(self):
+ """
+ L{DomainWithDefaultDict.__str__} should return a string including
+ the class name and the domain mapping held by the instance.
+ """
+ self._stringificationTest(str)
+
+
+ def test_repr(self):
+ """
+ L{DomainWithDefaultDict.__repr__} should return a string including
+ the class name and the domain mapping held by the instance.
+ """
+ self._stringificationTest(repr)
+
+
+
+class BounceTestCase(unittest.TestCase):
+ def setUp(self):
+ self.domain = mail.mail.BounceDomain()
+
+ def testExists(self):
+ self.assertRaises(smtp.AddressError, self.domain.exists, "any user")
+
+ def testRelay(self):
+ self.assertEquals(
+ self.domain.willRelay("random q emailer", "protocol"),
+ False
+ )
+
+ def testMessage(self):
+ self.assertRaises(NotImplementedError, self.domain.startMessage, "whomever")
+
+ def testAddUser(self):
+ self.domain.addUser("bob", "password")
+ self.assertRaises(smtp.SMTPBadRcpt, self.domain.exists, "bob")
+
+class FileMessageTestCase(unittest.TestCase):
+ def setUp(self):
+ self.name = "fileMessage.testFile"
+ self.final = "final.fileMessage.testFile"
+ self.f = file(self.name, 'w')
+ self.fp = mail.mail.FileMessage(self.f, self.name, self.final)
+
+ def tearDown(self):
+ try:
+ self.f.close()
+ except:
+ pass
+ try:
+ os.remove(self.name)
+ except:
+ pass
+ try:
+ os.remove(self.final)
+ except:
+ pass
+
+ def testFinalName(self):
+ return self.fp.eomReceived().addCallback(self._cbFinalName)
+
+ def _cbFinalName(self, result):
+ self.assertEquals(result, self.final)
+ self.failUnless(self.f.closed)
+ self.failIf(os.path.exists(self.name))
+
+ def testContents(self):
+ contents = "first line\nsecond line\nthird line\n"
+ for line in contents.splitlines():
+ self.fp.lineReceived(line)
+ self.fp.eomReceived()
+ self.assertEquals(file(self.final).read(), contents)
+
+ def testInterrupted(self):
+ contents = "first line\nsecond line\n"
+ for line in contents.splitlines():
+ self.fp.lineReceived(line)
+ self.fp.connectionLost()
+ self.failIf(os.path.exists(self.name))
+ self.failIf(os.path.exists(self.final))
+
+class MailServiceTestCase(unittest.TestCase):
+ def setUp(self):
+ self.service = mail.mail.MailService()
+
+ def testFactories(self):
+ f = self.service.getPOP3Factory()
+ self.failUnless(isinstance(f, protocol.ServerFactory))
+ self.failUnless(f.buildProtocol(('127.0.0.1', 12345)), pop3.POP3)
+
+ f = self.service.getSMTPFactory()
+ self.failUnless(isinstance(f, protocol.ServerFactory))
+ self.failUnless(f.buildProtocol(('127.0.0.1', 12345)), smtp.SMTP)
+
+ f = self.service.getESMTPFactory()
+ self.failUnless(isinstance(f, protocol.ServerFactory))
+ self.failUnless(f.buildProtocol(('127.0.0.1', 12345)), smtp.ESMTP)
+
+ def testPortals(self):
+ o1 = object()
+ o2 = object()
+ self.service.portals['domain'] = o1
+ self.service.portals[''] = o2
+
+ self.failUnless(self.service.lookupPortal('domain') is o1)
+ self.failUnless(self.service.defaultPortal() is o2)
+
+
+class StringListMailboxTests(unittest.TestCase):
+ """
+ Tests for L{StringListMailbox}, an in-memory only implementation of
+ L{pop3.IMailbox}.
+ """
+ def test_listOneMessage(self):
+ """
+ L{StringListMailbox.listMessages} returns the length of the message at
+ the offset into the mailbox passed to it.
+ """
+ mailbox = mail.maildir.StringListMailbox(["abc", "ab", "a"])
+ self.assertEqual(mailbox.listMessages(0), 3)
+ self.assertEqual(mailbox.listMessages(1), 2)
+ self.assertEqual(mailbox.listMessages(2), 1)
+
+
+ def test_listAllMessages(self):
+ """
+ L{StringListMailbox.listMessages} returns a list of the lengths of all
+ messages if not passed an index.
+ """
+ mailbox = mail.maildir.StringListMailbox(["a", "abc", "ab"])
+ self.assertEqual(mailbox.listMessages(), [1, 3, 2])
+
+
+ def test_getMessage(self):
+ """
+ L{StringListMailbox.getMessage} returns a file-like object from which
+ the contents of the message at the given offset into the mailbox can be
+ read.
+ """
+ mailbox = mail.maildir.StringListMailbox(["foo", "real contents"])
+ self.assertEqual(mailbox.getMessage(1).read(), "real contents")
+
+
+ def test_getUidl(self):
+ """
+ L{StringListMailbox.getUidl} returns a unique identifier for the
+ message at the given offset into the mailbox.
+ """
+ mailbox = mail.maildir.StringListMailbox(["foo", "bar"])
+ self.assertNotEqual(mailbox.getUidl(0), mailbox.getUidl(1))
+
+
+ def test_deleteMessage(self):
+ """
+ L{StringListMailbox.deleteMessage} marks a message for deletion causing
+ further requests for its length to return 0.
+ """
+ mailbox = mail.maildir.StringListMailbox(["foo"])
+ mailbox.deleteMessage(0)
+ self.assertEqual(mailbox.listMessages(0), 0)
+ self.assertEqual(mailbox.listMessages(), [0])
+
+
+ def test_undeleteMessages(self):
+ """
+ L{StringListMailbox.undeleteMessages} causes any messages marked for
+ deletion to be returned to their original state.
+ """
+ mailbox = mail.maildir.StringListMailbox(["foo"])
+ mailbox.deleteMessage(0)
+ mailbox.undeleteMessages()
+ self.assertEqual(mailbox.listMessages(0), 3)
+ self.assertEqual(mailbox.listMessages(), [3])
+
+
+ def test_sync(self):
+ """
+ L{StringListMailbox.sync} causes any messages as marked for deletion to
+ be permanently deleted.
+ """
+ mailbox = mail.maildir.StringListMailbox(["foo"])
+ mailbox.deleteMessage(0)
+ mailbox.sync()
+ mailbox.undeleteMessages()
+ self.assertEqual(mailbox.listMessages(0), 0)
+ self.assertEqual(mailbox.listMessages(), [0])
+
+
+
+class FailingMaildirMailboxAppendMessageTask(mail.maildir._MaildirMailboxAppendMessageTask):
+ _openstate = True
+ _writestate = True
+ _renamestate = True
+ def osopen(self, fn, attr, mode):
+ if self._openstate:
+ return os.open(fn, attr, mode)
+ else:
+ raise OSError(errno.EPERM, "Faked Permission Problem")
+ def oswrite(self, fh, data):
+ if self._writestate:
+ return os.write(fh, data)
+ else:
+ raise OSError(errno.ENOSPC, "Faked Space problem")
+ def osrename(self, oldname, newname):
+ if self._renamestate:
+ return os.rename(oldname, newname)
+ else:
+ raise OSError(errno.EPERM, "Faked Permission Problem")
+
+class MaildirAppendStringTestCase(unittest.TestCase):
+ def setUp(self):
+ self.d = self.mktemp()
+ mail.maildir.initializeMaildir(self.d)
+
+ def tearDown(self):
+ shutil.rmtree(self.d)
+
+ def _append(self, ignored, mbox):
+ d = mbox.appendMessage('TEST')
+ return self.assertFailure(d, Exception)
+
+ def _setState(self, ignored, mbox, rename=None, write=None, open=None):
+ if rename is not None:
+ mbox.AppendFactory._renameState = rename
+ if write is not None:
+ mbox.AppendFactory._writeState = write
+ if open is not None:
+ mbox.AppendFactory._openstate = open
+
+ def testAppend(self):
+ mbox = mail.maildir.MaildirMailbox(self.d)
+ mbox.AppendFactory = FailingMaildirMailboxAppendMessageTask
+ ds = []
+ for i in xrange(1, 11):
+ ds.append(mbox.appendMessage("X" * i))
+ ds[-1].addCallback(self.assertEqual, None)
+ d = defer.gatherResults(ds)
+ d.addCallback(self._cbTestAppend, mbox)
+ return d
+
+ def _cbTestAppend(self, result, mbox):
+ self.assertEquals(len(mbox.listMessages()),
+ 10)
+ self.assertEquals(len(mbox.getMessage(5).read()), 6)
+ # test in the right order: last to first error location.
+ mbox.AppendFactory._renamestate = False
+ d = self._append(None, mbox)
+ d.addCallback(self._setState, mbox, rename=True, write=False)
+ d.addCallback(self._append, mbox)
+ d.addCallback(self._setState, mbox, write=True, open=False)
+ d.addCallback(self._append, mbox)
+ d.addCallback(self._setState, mbox, open=True)
+ return d
+
+
+class MaildirAppendFileTestCase(unittest.TestCase):
+ def setUp(self):
+ self.d = self.mktemp()
+ mail.maildir.initializeMaildir(self.d)
+
+ def tearDown(self):
+ shutil.rmtree(self.d)
+
+ def testAppend(self):
+ mbox = mail.maildir.MaildirMailbox(self.d)
+ ds = []
+ def _check(res, t):
+ t.close()
+ self.assertEqual(res, None)
+ for i in xrange(1, 11):
+ temp = tempfile.TemporaryFile()
+ temp.write("X" * i)
+ temp.seek(0,0)
+ ds.append(mbox.appendMessage(temp))
+ ds[-1].addCallback(_check, temp)
+ return defer.gatherResults(ds).addCallback(self._cbTestAppend, mbox)
+
+ def _cbTestAppend(self, result, mbox):
+ self.assertEquals(len(mbox.listMessages()),
+ 10)
+ self.assertEquals(len(mbox.getMessage(5).read()), 6)
+
+
+class MaildirTestCase(unittest.TestCase):
+ def setUp(self):
+ self.d = self.mktemp()
+ mail.maildir.initializeMaildir(self.d)
+
+ def tearDown(self):
+ shutil.rmtree(self.d)
+
+ def testInitializer(self):
+ d = self.d
+ trash = os.path.join(d, '.Trash')
+
+ self.failUnless(os.path.exists(d) and os.path.isdir(d))
+ self.failUnless(os.path.exists(os.path.join(d, 'new')))
+ self.failUnless(os.path.exists(os.path.join(d, 'cur')))
+ self.failUnless(os.path.exists(os.path.join(d, 'tmp')))
+ self.failUnless(os.path.isdir(os.path.join(d, 'new')))
+ self.failUnless(os.path.isdir(os.path.join(d, 'cur')))
+ self.failUnless(os.path.isdir(os.path.join(d, 'tmp')))
+
+ self.failUnless(os.path.exists(os.path.join(trash, 'new')))
+ self.failUnless(os.path.exists(os.path.join(trash, 'cur')))
+ self.failUnless(os.path.exists(os.path.join(trash, 'tmp')))
+ self.failUnless(os.path.isdir(os.path.join(trash, 'new')))
+ self.failUnless(os.path.isdir(os.path.join(trash, 'cur')))
+ self.failUnless(os.path.isdir(os.path.join(trash, 'tmp')))
+
+
+ def test_nameGenerator(self):
+ """
+ Each call to L{_MaildirNameGenerator.generate} returns a unique
+ string suitable for use as the basename of a new message file. The
+ names are ordered such that those generated earlier sort less than
+ those generated later.
+ """
+ clock = task.Clock()
+ clock.advance(0.05)
+ generator = mail.maildir._MaildirNameGenerator(clock)
+
+ firstName = generator.generate()
+ clock.advance(0.05)
+ secondName = generator.generate()
+
+ self.assertTrue(firstName < secondName)
+
+
+ def test_mailbox(self):
+ """
+ Exercise the methods of L{IMailbox} as implemented by
+ L{MaildirMailbox}.
+ """
+ j = os.path.join
+ n = mail.maildir._generateMaildirName
+ msgs = [j(b, n()) for b in ('cur', 'new') for x in range(5)]
+
+ # Toss a few files into the mailbox
+ i = 1
+ for f in msgs:
+ fObj = file(j(self.d, f), 'w')
+ fObj.write('x' * i)
+ fObj.close()
+ i = i + 1
+
+ mb = mail.maildir.MaildirMailbox(self.d)
+ self.assertEquals(mb.listMessages(), range(1, 11))
+ self.assertEquals(mb.listMessages(1), 2)
+ self.assertEquals(mb.listMessages(5), 6)
+
+ self.assertEquals(mb.getMessage(6).read(), 'x' * 7)
+ self.assertEquals(mb.getMessage(1).read(), 'x' * 2)
+
+ d = {}
+ for i in range(10):
+ u = mb.getUidl(i)
+ self.failIf(u in d)
+ d[u] = None
+
+ p, f = os.path.split(msgs[5])
+
+ mb.deleteMessage(5)
+ self.assertEquals(mb.listMessages(5), 0)
+ self.failUnless(os.path.exists(j(self.d, '.Trash', 'cur', f)))
+ self.failIf(os.path.exists(j(self.d, msgs[5])))
+
+ mb.undeleteMessages()
+ self.assertEquals(mb.listMessages(5), 6)
+ self.failIf(os.path.exists(j(self.d, '.Trash', 'cur', f)))
+ self.failUnless(os.path.exists(j(self.d, msgs[5])))
+
+class MaildirDirdbmDomainTestCase(unittest.TestCase):
+ def setUp(self):
+ self.P = self.mktemp()
+ self.S = mail.mail.MailService()
+ self.D = mail.maildir.MaildirDirdbmDomain(self.S, self.P)
+
+ def tearDown(self):
+ shutil.rmtree(self.P)
+
+ def testAddUser(self):
+ toAdd = (('user1', 'pwd1'), ('user2', 'pwd2'), ('user3', 'pwd3'))
+ for (u, p) in toAdd:
+ self.D.addUser(u, p)
+
+ for (u, p) in toAdd:
+ self.failUnless(u in self.D.dbm)
+ self.assertEquals(self.D.dbm[u], p)
+ self.failUnless(os.path.exists(os.path.join(self.P, u)))
+
+ def testCredentials(self):
+ creds = self.D.getCredentialsCheckers()
+
+ self.assertEquals(len(creds), 1)
+ self.failUnless(cred.checkers.ICredentialsChecker.providedBy(creds[0]))
+ self.failUnless(cred.credentials.IUsernamePassword in creds[0].credentialInterfaces)
+
+ def testRequestAvatar(self):
+ class ISomething(Interface):
+ pass
+
+ self.D.addUser('user', 'password')
+ self.assertRaises(
+ NotImplementedError,
+ self.D.requestAvatar, 'user', None, ISomething
+ )
+
+ t = self.D.requestAvatar('user', None, pop3.IMailbox)
+ self.assertEquals(len(t), 3)
+ self.failUnless(t[0] is pop3.IMailbox)
+ self.failUnless(pop3.IMailbox.providedBy(t[1]))
+
+ t[2]()
+
+ def testRequestAvatarId(self):
+ self.D.addUser('user', 'password')
+ database = self.D.getCredentialsCheckers()[0]
+
+ creds = cred.credentials.UsernamePassword('user', 'wrong password')
+ self.assertRaises(
+ cred.error.UnauthorizedLogin,
+ database.requestAvatarId, creds
+ )
+
+ creds = cred.credentials.UsernamePassword('user', 'password')
+ self.assertEquals(database.requestAvatarId(creds), 'user')
+
+
+class StubAliasableDomain(object):
+ """
+ Minimal testable implementation of IAliasableDomain.
+ """
+ implements(mail.mail.IAliasableDomain)
+
+ def exists(self, user):
+ """
+ No test coverage for invocations of this method on domain objects,
+ so we just won't implement it.
+ """
+ raise NotImplementedError()
+
+
+ def addUser(self, user, password):
+ """
+ No test coverage for invocations of this method on domain objects,
+ so we just won't implement it.
+ """
+ raise NotImplementedError()
+
+
+ def getCredentialsCheckers(self):
+ """
+ This needs to succeed in order for other tests to complete
+ successfully, but we don't actually assert anything about its
+ behavior. Return an empty list. Sometime later we should return
+ something else and assert that a portal got set up properly.
+ """
+ return []
+
+
+ def setAliasGroup(self, aliases):
+ """
+ Just record the value so the test can check it later.
+ """
+ self.aliasGroup = aliases
+
+
+class ServiceDomainTestCase(unittest.TestCase):
+ def setUp(self):
+ self.S = mail.mail.MailService()
+ self.D = mail.protocols.DomainDeliveryBase(self.S, None)
+ self.D.service = self.S
+ self.D.protocolName = 'TEST'
+ self.D.host = 'hostname'
+
+ self.tmpdir = self.mktemp()
+ domain = mail.maildir.MaildirDirdbmDomain(self.S, self.tmpdir)
+ domain.addUser('user', 'password')
+ self.S.addDomain('test.domain', domain)
+
+ def tearDown(self):
+ shutil.rmtree(self.tmpdir)
+
+
+ def testAddAliasableDomain(self):
+ """
+ Test that adding an IAliasableDomain to a mail service properly sets
+ up alias group references and such.
+ """
+ aliases = object()
+ domain = StubAliasableDomain()
+ self.S.aliases = aliases
+ self.S.addDomain('example.com', domain)
+ self.assertIdentical(domain.aliasGroup, aliases)
+
+
+ def testReceivedHeader(self):
+ hdr = self.D.receivedHeader(
+ ('remotehost', '123.232.101.234'),
+ smtp.Address('<someguy@somplace>'),
+ ['user@host.name']
+ )
+ fp = StringIO.StringIO(hdr)
+ m = rfc822.Message(fp)
+ self.assertEquals(len(m.items()), 1)
+ self.failUnless(m.has_key('Received'))
+
+ def testValidateTo(self):
+ user = smtp.User('user@test.domain', 'helo', None, 'wherever@whatever')
+ return defer.maybeDeferred(self.D.validateTo, user
+ ).addCallback(self._cbValidateTo
+ )
+
+ def _cbValidateTo(self, result):
+ self.failUnless(callable(result))
+
+ def testValidateToBadUsername(self):
+ user = smtp.User('resu@test.domain', 'helo', None, 'wherever@whatever')
+ return self.assertFailure(
+ defer.maybeDeferred(self.D.validateTo, user),
+ smtp.SMTPBadRcpt)
+
+ def testValidateToBadDomain(self):
+ user = smtp.User('user@domain.test', 'helo', None, 'wherever@whatever')
+ return self.assertFailure(
+ defer.maybeDeferred(self.D.validateTo, user),
+ smtp.SMTPBadRcpt)
+
+ def testValidateFrom(self):
+ helo = ('hostname', '127.0.0.1')
+ origin = smtp.Address('<user@hostname>')
+ self.failUnless(self.D.validateFrom(helo, origin) is origin)
+
+ helo = ('hostname', '1.2.3.4')
+ origin = smtp.Address('<user@hostname>')
+ self.failUnless(self.D.validateFrom(helo, origin) is origin)
+
+ helo = ('hostname', '1.2.3.4')
+ origin = smtp.Address('<>')
+ self.failUnless(self.D.validateFrom(helo, origin) is origin)
+
+ self.assertRaises(
+ smtp.SMTPBadSender,
+ self.D.validateFrom, None, origin
+ )
+
+class VirtualPOP3TestCase(unittest.TestCase):
+ def setUp(self):
+ self.tmpdir = self.mktemp()
+ self.S = mail.mail.MailService()
+ self.D = mail.maildir.MaildirDirdbmDomain(self.S, self.tmpdir)
+ self.D.addUser('user', 'password')
+ self.S.addDomain('test.domain', self.D)
+
+ portal = cred.portal.Portal(self.D)
+ map(portal.registerChecker, self.D.getCredentialsCheckers())
+ self.S.portals[''] = self.S.portals['test.domain'] = portal
+
+ self.P = mail.protocols.VirtualPOP3()
+ self.P.service = self.S
+ self.P.magic = '<unit test magic>'
+
+ def tearDown(self):
+ shutil.rmtree(self.tmpdir)
+
+ def testAuthenticateAPOP(self):
+ resp = md5(self.P.magic + 'password').hexdigest()
+ return self.P.authenticateUserAPOP('user', resp
+ ).addCallback(self._cbAuthenticateAPOP
+ )
+
+ def _cbAuthenticateAPOP(self, result):
+ self.assertEquals(len(result), 3)
+ self.assertEquals(result[0], pop3.IMailbox)
+ self.failUnless(pop3.IMailbox.providedBy(result[1]))
+ result[2]()
+
+ def testAuthenticateIncorrectUserAPOP(self):
+ resp = md5(self.P.magic + 'password').hexdigest()
+ return self.assertFailure(
+ self.P.authenticateUserAPOP('resu', resp),
+ cred.error.UnauthorizedLogin)
+
+ def testAuthenticateIncorrectResponseAPOP(self):
+ resp = md5('wrong digest').hexdigest()
+ return self.assertFailure(
+ self.P.authenticateUserAPOP('user', resp),
+ cred.error.UnauthorizedLogin)
+
+ def testAuthenticatePASS(self):
+ return self.P.authenticateUserPASS('user', 'password'
+ ).addCallback(self._cbAuthenticatePASS
+ )
+
+ def _cbAuthenticatePASS(self, result):
+ self.assertEquals(len(result), 3)
+ self.assertEquals(result[0], pop3.IMailbox)
+ self.failUnless(pop3.IMailbox.providedBy(result[1]))
+ result[2]()
+
+ def testAuthenticateBadUserPASS(self):
+ return self.assertFailure(
+ self.P.authenticateUserPASS('resu', 'password'),
+ cred.error.UnauthorizedLogin)
+
+ def testAuthenticateBadPasswordPASS(self):
+ return self.assertFailure(
+ self.P.authenticateUserPASS('user', 'wrong password'),
+ cred.error.UnauthorizedLogin)
+
+class empty(smtp.User):
+ def __init__(self):
+ pass
+
+class RelayTestCase(unittest.TestCase):
+ def testExists(self):
+ service = mail.mail.MailService()
+ domain = mail.relay.DomainQueuer(service)
+
+ doRelay = [
+ address.UNIXAddress('/var/run/mail-relay'),
+ address.IPv4Address('TCP', '127.0.0.1', 12345),
+ ]
+
+ dontRelay = [
+ address.IPv4Address('TCP', '192.168.2.1', 62),
+ address.IPv4Address('TCP', '1.2.3.4', 1943),
+ ]
+
+ for peer in doRelay:
+ user = empty()
+ user.orig = 'user@host'
+ user.dest = 'tsoh@resu'
+ user.protocol = empty()
+ user.protocol.transport = empty()
+ user.protocol.transport.getPeer = lambda: peer
+
+ self.failUnless(callable(domain.exists(user)))
+
+ for peer in dontRelay:
+ user = empty()
+ user.orig = 'some@place'
+ user.protocol = empty()
+ user.protocol.transport = empty()
+ user.protocol.transport.getPeer = lambda: peer
+ user.dest = 'who@cares'
+
+ self.assertRaises(smtp.SMTPBadRcpt, domain.exists, user)
+
+class RelayerTestCase(unittest.TestCase):
+ def setUp(self):
+ self.tmpdir = self.mktemp()
+ os.mkdir(self.tmpdir)
+ self.messageFiles = []
+ for i in range(10):
+ name = os.path.join(self.tmpdir, 'body-%d' % (i,))
+ f = file(name + '-H', 'w')
+ pickle.dump(['from-%d' % (i,), 'to-%d' % (i,)], f)
+ f.close()
+
+ f = file(name + '-D', 'w')
+ f.write(name)
+ f.seek(0, 0)
+ self.messageFiles.append(name)
+
+ self.R = mail.relay.RelayerMixin()
+ self.R.loadMessages(self.messageFiles)
+
+ def tearDown(self):
+ shutil.rmtree(self.tmpdir)
+
+ def testMailFrom(self):
+ for i in range(10):
+ self.assertEquals(self.R.getMailFrom(), 'from-%d' % (i,))
+ self.R.sentMail(250, None, None, None, None)
+ self.assertEquals(self.R.getMailFrom(), None)
+
+ def testMailTo(self):
+ for i in range(10):
+ self.assertEquals(self.R.getMailTo(), ['to-%d' % (i,)])
+ self.R.sentMail(250, None, None, None, None)
+ self.assertEquals(self.R.getMailTo(), None)
+
+ def testMailData(self):
+ for i in range(10):
+ name = os.path.join(self.tmpdir, 'body-%d' % (i,))
+ self.assertEquals(self.R.getMailData().read(), name)
+ self.R.sentMail(250, None, None, None, None)
+ self.assertEquals(self.R.getMailData(), None)
+
+class Manager:
+ def __init__(self):
+ self.success = []
+ self.failure = []
+ self.done = []
+
+ def notifySuccess(self, factory, message):
+ self.success.append((factory, message))
+
+ def notifyFailure(self, factory, message):
+ self.failure.append((factory, message))
+
+ def notifyDone(self, factory):
+ self.done.append(factory)
+
+class ManagedRelayerTestCase(unittest.TestCase):
+ def setUp(self):
+ self.manager = Manager()
+ self.messages = range(0, 20, 2)
+ self.factory = object()
+ self.relay = mail.relaymanager.ManagedRelayerMixin(self.manager)
+ self.relay.messages = self.messages[:]
+ self.relay.names = self.messages[:]
+ self.relay.factory = self.factory
+
+ def testSuccessfulSentMail(self):
+ for i in self.messages:
+ self.relay.sentMail(250, None, None, None, None)
+
+ self.assertEquals(
+ self.manager.success,
+ [(self.factory, m) for m in self.messages]
+ )
+
+ def testFailedSentMail(self):
+ for i in self.messages:
+ self.relay.sentMail(550, None, None, None, None)
+
+ self.assertEquals(
+ self.manager.failure,
+ [(self.factory, m) for m in self.messages]
+ )
+
+ def testConnectionLost(self):
+ self.relay.connectionLost(failure.Failure(Exception()))
+ self.assertEquals(self.manager.done, [self.factory])
+
+class DirectoryQueueTestCase(unittest.TestCase):
+ def setUp(self):
+ # This is almost a test case itself.
+ self.tmpdir = self.mktemp()
+ os.mkdir(self.tmpdir)
+ self.queue = mail.relaymanager.Queue(self.tmpdir)
+ self.queue.noisy = False
+ for m in range(25):
+ hdrF, msgF = self.queue.createNewMessage()
+ pickle.dump(['header', m], hdrF)
+ hdrF.close()
+ msgF.lineReceived('body: %d' % (m,))
+ msgF.eomReceived()
+ self.queue.readDirectory()
+
+ def tearDown(self):
+ shutil.rmtree(self.tmpdir)
+
+ def testWaiting(self):
+ self.failUnless(self.queue.hasWaiting())
+ self.assertEquals(len(self.queue.getWaiting()), 25)
+
+ waiting = self.queue.getWaiting()
+ self.queue.setRelaying(waiting[0])
+ self.assertEquals(len(self.queue.getWaiting()), 24)
+
+ self.queue.setWaiting(waiting[0])
+ self.assertEquals(len(self.queue.getWaiting()), 25)
+
+ def testRelaying(self):
+ for m in self.queue.getWaiting():
+ self.queue.setRelaying(m)
+ self.assertEquals(
+ len(self.queue.getRelayed()),
+ 25 - len(self.queue.getWaiting())
+ )
+
+ self.failIf(self.queue.hasWaiting())
+
+ relayed = self.queue.getRelayed()
+ self.queue.setWaiting(relayed[0])
+ self.assertEquals(len(self.queue.getWaiting()), 1)
+ self.assertEquals(len(self.queue.getRelayed()), 24)
+
+ def testDone(self):
+ msg = self.queue.getWaiting()[0]
+ self.queue.setRelaying(msg)
+ self.queue.done(msg)
+
+ self.assertEquals(len(self.queue.getWaiting()), 24)
+ self.assertEquals(len(self.queue.getRelayed()), 0)
+
+ self.failIf(msg in self.queue.getWaiting())
+ self.failIf(msg in self.queue.getRelayed())
+
+ def testEnvelope(self):
+ envelopes = []
+
+ for msg in self.queue.getWaiting():
+ envelopes.append(self.queue.getEnvelope(msg))
+
+ envelopes.sort()
+ for i in range(25):
+ self.assertEquals(
+ envelopes.pop(0),
+ ['header', i]
+ )
+
+from twisted.names import server
+from twisted.names import client
+from twisted.names import common
+
+class TestAuthority(common.ResolverBase):
+ def __init__(self):
+ common.ResolverBase.__init__(self)
+ self.addresses = {}
+
+ def _lookup(self, name, cls, type, timeout = None):
+ if name in self.addresses and type == dns.MX:
+ results = []
+ for a in self.addresses[name]:
+ hdr = dns.RRHeader(
+ name, dns.MX, dns.IN, 60, dns.Record_MX(0, a)
+ )
+ results.append(hdr)
+ return defer.succeed((results, [], []))
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+
+def setUpDNS(self):
+ self.auth = TestAuthority()
+ factory = server.DNSServerFactory([self.auth])
+ protocol = dns.DNSDatagramProtocol(factory)
+ while 1:
+ self.port = reactor.listenTCP(0, factory, interface='127.0.0.1')
+ portNumber = self.port.getHost().port
+
+ try:
+ self.udpPort = reactor.listenUDP(portNumber, protocol, interface='127.0.0.1')
+ except CannotListenError:
+ self.port.stopListening()
+ else:
+ break
+ self.resolver = client.Resolver(servers=[('127.0.0.1', portNumber)])
+
+
+def tearDownDNS(self):
+ dl = []
+ dl.append(defer.maybeDeferred(self.port.stopListening))
+ dl.append(defer.maybeDeferred(self.udpPort.stopListening))
+ if self.resolver.protocol.transport is not None:
+ dl.append(defer.maybeDeferred(self.resolver.protocol.transport.stopListening))
+ try:
+ self.resolver._parseCall.cancel()
+ except:
+ pass
+ return defer.DeferredList(dl)
+
+class MXTestCase(unittest.TestCase):
+ """
+ Tests for L{mail.relaymanager.MXCalculator}.
+ """
+ def setUp(self):
+ setUpDNS(self)
+ self.clock = task.Clock()
+ self.mx = mail.relaymanager.MXCalculator(self.resolver, self.clock)
+
+ def tearDown(self):
+ return tearDownDNS(self)
+
+
+ def test_defaultClock(self):
+ """
+ L{MXCalculator}'s default clock is C{twisted.internet.reactor}.
+ """
+ self.assertIdentical(
+ mail.relaymanager.MXCalculator(self.resolver).clock,
+ reactor)
+
+
+ def testSimpleSuccess(self):
+ self.auth.addresses['test.domain'] = ['the.email.test.domain']
+ return self.mx.getMX('test.domain').addCallback(self._cbSimpleSuccess)
+
+ def _cbSimpleSuccess(self, mx):
+ self.assertEquals(mx.preference, 0)
+ self.assertEquals(str(mx.name), 'the.email.test.domain')
+
+ def testSimpleFailure(self):
+ self.mx.fallbackToDomain = False
+ return self.assertFailure(self.mx.getMX('test.domain'), IOError)
+
+ def testSimpleFailureWithFallback(self):
+ return self.assertFailure(self.mx.getMX('test.domain'), DNSLookupError)
+
+
+ def _exchangeTest(self, domain, records, correctMailExchange):
+ """
+ Issue an MX request for the given domain and arrange for it to be
+ responded to with the given records. Verify that the resulting mail
+ exchange is the indicated host.
+
+ @type domain: C{str}
+ @type records: C{list} of L{RRHeader}
+ @type correctMailExchange: C{str}
+ @rtype: L{Deferred}
+ """
+ class DummyResolver(object):
+ def lookupMailExchange(self, name):
+ if name == domain:
+ return defer.succeed((
+ records,
+ [],
+ []))
+ return defer.fail(DNSNameError(domain))
+
+ self.mx.resolver = DummyResolver()
+ d = self.mx.getMX(domain)
+ def gotMailExchange(record):
+ self.assertEqual(str(record.name), correctMailExchange)
+ d.addCallback(gotMailExchange)
+ return d
+
+
+ def test_mailExchangePreference(self):
+ """
+ The MX record with the lowest preference is returned by
+ L{MXCalculator.getMX}.
+ """
+ domain = "example.com"
+ good = "good.example.com"
+ bad = "bad.example.com"
+
+ records = [
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(1, bad)),
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(0, good)),
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(2, bad))]
+ return self._exchangeTest(domain, records, good)
+
+
+ def test_badExchangeExcluded(self):
+ """
+ L{MXCalculator.getMX} returns the MX record with the lowest preference
+ which is not also marked as bad.
+ """
+ domain = "example.com"
+ good = "good.example.com"
+ bad = "bad.example.com"
+
+ records = [
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(0, bad)),
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(1, good))]
+ self.mx.markBad(bad)
+ return self._exchangeTest(domain, records, good)
+
+
+ def test_fallbackForAllBadExchanges(self):
+ """
+ L{MXCalculator.getMX} returns the MX record with the lowest preference
+ if all the MX records in the response have been marked bad.
+ """
+ domain = "example.com"
+ bad = "bad.example.com"
+ worse = "worse.example.com"
+
+ records = [
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(0, bad)),
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(1, worse))]
+ self.mx.markBad(bad)
+ self.mx.markBad(worse)
+ return self._exchangeTest(domain, records, bad)
+
+
+ def test_badExchangeExpires(self):
+ """
+ L{MXCalculator.getMX} returns the MX record with the lowest preference
+ if it was last marked bad longer than L{MXCalculator.timeOutBadMX}
+ seconds ago.
+ """
+ domain = "example.com"
+ good = "good.example.com"
+ previouslyBad = "bad.example.com"
+
+ records = [
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(0, previouslyBad)),
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(1, good))]
+ self.mx.markBad(previouslyBad)
+ self.clock.advance(self.mx.timeOutBadMX)
+ return self._exchangeTest(domain, records, previouslyBad)
+
+
+ def test_goodExchangeUsed(self):
+ """
+ L{MXCalculator.getMX} returns the MX record with the lowest preference
+ if it was marked good after it was marked bad.
+ """
+ domain = "example.com"
+ good = "good.example.com"
+ previouslyBad = "bad.example.com"
+
+ records = [
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(0, previouslyBad)),
+ RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(1, good))]
+ self.mx.markBad(previouslyBad)
+ self.mx.markGood(previouslyBad)
+ self.clock.advance(self.mx.timeOutBadMX)
+ return self._exchangeTest(domain, records, previouslyBad)
+
+
+ def test_successWithoutResults(self):
+ """
+ If an MX lookup succeeds but the result set is empty,
+ L{MXCalculator.getMX} should try to look up an I{A} record for the
+ requested name and call back its returned Deferred with that
+ address.
+ """
+ ip = '1.2.3.4'
+ domain = 'example.org'
+
+ class DummyResolver(object):
+ """
+ Fake resolver which will respond to an MX lookup with an empty
+ result set.
+
+ @ivar mx: A dictionary mapping hostnames to three-tuples of
+ results to be returned from I{MX} lookups.
+
+ @ivar a: A dictionary mapping hostnames to addresses to be
+ returned from I{A} lookups.
+ """
+ mx = {domain: ([], [], [])}
+ a = {domain: ip}
+
+ def lookupMailExchange(self, domain):
+ return defer.succeed(self.mx[domain])
+
+ def getHostByName(self, domain):
+ return defer.succeed(self.a[domain])
+
+ self.mx.resolver = DummyResolver()
+ d = self.mx.getMX(domain)
+ d.addCallback(self.assertEqual, Record_MX(name=ip))
+ return d
+
+
+ def test_failureWithSuccessfulFallback(self):
+ """
+ Test that if the MX record lookup fails, fallback is enabled, and an A
+ record is available for the name, then the Deferred returned by
+ L{MXCalculator.getMX} ultimately fires with a Record_MX instance which
+ gives the address in the A record for the name.
+ """
+ class DummyResolver(object):
+ """
+ Fake resolver which will fail an MX lookup but then succeed a
+ getHostByName call.
+ """
+ def lookupMailExchange(self, domain):
+ return defer.fail(DNSNameError())
+
+ def getHostByName(self, domain):
+ return defer.succeed("1.2.3.4")
+
+ self.mx.resolver = DummyResolver()
+ d = self.mx.getMX("domain")
+ d.addCallback(self.assertEqual, Record_MX(name="1.2.3.4"))
+ return d
+
+
+ def test_cnameWithoutGlueRecords(self):
+ """
+ If an MX lookup returns a single CNAME record as a result, MXCalculator
+ will perform an MX lookup for the canonical name indicated and return
+ the MX record which results.
+ """
+ alias = "alias.example.com"
+ canonical = "canonical.example.com"
+ exchange = "mail.example.com"
+
+ class DummyResolver(object):
+ """
+ Fake resolver which will return a CNAME for an MX lookup of a name
+ which is an alias and an MX for an MX lookup of the canonical name.
+ """
+ def lookupMailExchange(self, domain):
+ if domain == alias:
+ return defer.succeed((
+ [RRHeader(name=domain,
+ type=Record_CNAME.TYPE,
+ payload=Record_CNAME(canonical))],
+ [], []))
+ elif domain == canonical:
+ return defer.succeed((
+ [RRHeader(name=domain,
+ type=Record_MX.TYPE,
+ payload=Record_MX(0, exchange))],
+ [], []))
+ else:
+ return defer.fail(DNSNameError(domain))
+
+ self.mx.resolver = DummyResolver()
+ d = self.mx.getMX(alias)
+ d.addCallback(self.assertEqual, Record_MX(name=exchange))
+ return d
+
+
+ def test_cnameChain(self):
+ """
+ If L{MXCalculator.getMX} encounters a CNAME chain which is longer than
+ the length specified, the returned L{Deferred} should errback with
+ L{CanonicalNameChainTooLong}.
+ """
+ class DummyResolver(object):
+ """
+ Fake resolver which generates a CNAME chain of infinite length in
+ response to MX lookups.
+ """
+ chainCounter = 0
+
+ def lookupMailExchange(self, domain):
+ self.chainCounter += 1
+ name = 'x-%d.example.com' % (self.chainCounter,)
+ return defer.succeed((
+ [RRHeader(name=domain,
+ type=Record_CNAME.TYPE,
+ payload=Record_CNAME(name))],
+ [], []))
+
+ cnameLimit = 3
+ self.mx.resolver = DummyResolver()
+ d = self.mx.getMX("mail.example.com", cnameLimit)
+ self.assertFailure(
+ d, twisted.mail.relaymanager.CanonicalNameChainTooLong)
+ def cbChainTooLong(error):
+ self.assertEqual(error.args[0], Record_CNAME("x-%d.example.com" % (cnameLimit + 1,)))
+ self.assertEqual(self.mx.resolver.chainCounter, cnameLimit + 1)
+ d.addCallback(cbChainTooLong)
+ return d
+
+
+ def test_cnameWithGlueRecords(self):
+ """
+ If an MX lookup returns a CNAME and the MX record for the CNAME, the
+ L{Deferred} returned by L{MXCalculator.getMX} should be called back
+ with the name from the MX record without further lookups being
+ attempted.
+ """
+ lookedUp = []
+ alias = "alias.example.com"
+ canonical = "canonical.example.com"
+ exchange = "mail.example.com"
+
+ class DummyResolver(object):
+ def lookupMailExchange(self, domain):
+ if domain != alias or lookedUp:
+ # Don't give back any results for anything except the alias
+ # or on any request after the first.
+ return ([], [], [])
+ return defer.succeed((
+ [RRHeader(name=alias,
+ type=Record_CNAME.TYPE,
+ payload=Record_CNAME(canonical)),
+ RRHeader(name=canonical,
+ type=Record_MX.TYPE,
+ payload=Record_MX(name=exchange))],
+ [], []))
+
+ self.mx.resolver = DummyResolver()
+ d = self.mx.getMX(alias)
+ d.addCallback(self.assertEqual, Record_MX(name=exchange))
+ return d
+
+
+ def test_cnameLoopWithGlueRecords(self):
+ """
+ If an MX lookup returns two CNAME records which point to each other,
+ the loop should be detected and the L{Deferred} returned by
+ L{MXCalculator.getMX} should be errbacked with L{CanonicalNameLoop}.
+ """
+ firstAlias = "cname1.example.com"
+ secondAlias = "cname2.example.com"
+
+ class DummyResolver(object):
+ def lookupMailExchange(self, domain):
+ return defer.succeed((
+ [RRHeader(name=firstAlias,
+ type=Record_CNAME.TYPE,
+ payload=Record_CNAME(secondAlias)),
+ RRHeader(name=secondAlias,
+ type=Record_CNAME.TYPE,
+ payload=Record_CNAME(firstAlias))],
+ [], []))
+
+ self.mx.resolver = DummyResolver()
+ d = self.mx.getMX(firstAlias)
+ self.assertFailure(d, twisted.mail.relaymanager.CanonicalNameLoop)
+ return d
+
+
+ def testManyRecords(self):
+ self.auth.addresses['test.domain'] = [
+ 'mx1.test.domain', 'mx2.test.domain', 'mx3.test.domain'
+ ]
+ return self.mx.getMX('test.domain'
+ ).addCallback(self._cbManyRecordsSuccessfulLookup
+ )
+
+ def _cbManyRecordsSuccessfulLookup(self, mx):
+ self.failUnless(str(mx.name).split('.', 1)[0] in ('mx1', 'mx2', 'mx3'))
+ self.mx.markBad(str(mx.name))
+ return self.mx.getMX('test.domain'
+ ).addCallback(self._cbManyRecordsDifferentResult, mx
+ )
+
+ def _cbManyRecordsDifferentResult(self, nextMX, mx):
+ self.assertNotEqual(str(mx.name), str(nextMX.name))
+ self.mx.markBad(str(nextMX.name))
+
+ return self.mx.getMX('test.domain'
+ ).addCallback(self._cbManyRecordsLastResult, mx, nextMX
+ )
+
+ def _cbManyRecordsLastResult(self, lastMX, mx, nextMX):
+ self.assertNotEqual(str(mx.name), str(lastMX.name))
+ self.assertNotEqual(str(nextMX.name), str(lastMX.name))
+
+ self.mx.markBad(str(lastMX.name))
+ self.mx.markGood(str(nextMX.name))
+
+ return self.mx.getMX('test.domain'
+ ).addCallback(self._cbManyRecordsRepeatSpecificResult, nextMX
+ )
+
+ def _cbManyRecordsRepeatSpecificResult(self, againMX, nextMX):
+ self.assertEqual(str(againMX.name), str(nextMX.name))
+
+class LiveFireExercise(unittest.TestCase):
+ if interfaces.IReactorUDP(reactor, None) is None:
+ skip = "UDP support is required to determining MX records"
+
+ def setUp(self):
+ setUpDNS(self)
+ self.tmpdirs = [
+ 'domainDir', 'insertionDomain', 'insertionQueue',
+ 'destinationDomain', 'destinationQueue'
+ ]
+
+ def tearDown(self):
+ for d in self.tmpdirs:
+ if os.path.exists(d):
+ shutil.rmtree(d)
+ return tearDownDNS(self)
+
+ def testLocalDelivery(self):
+ service = mail.mail.MailService()
+ service.smtpPortal.registerChecker(cred.checkers.AllowAnonymousAccess())
+ domain = mail.maildir.MaildirDirdbmDomain(service, 'domainDir')
+ domain.addUser('user', 'password')
+ service.addDomain('test.domain', domain)
+ service.portals[''] = service.portals['test.domain']
+ map(service.portals[''].registerChecker, domain.getCredentialsCheckers())
+
+ service.setQueue(mail.relay.DomainQueuer(service))
+ manager = mail.relaymanager.SmartHostSMTPRelayingManager(service.queue, None)
+ helper = mail.relaymanager.RelayStateHelper(manager, 1)
+
+ f = service.getSMTPFactory()
+
+ self.smtpServer = reactor.listenTCP(0, f, interface='127.0.0.1')
+
+ client = LineSendingProtocol([
+ 'HELO meson',
+ 'MAIL FROM: <user@hostname>',
+ 'RCPT TO: <user@test.domain>',
+ 'DATA',
+ 'This is the message',
+ '.',
+ 'QUIT'
+ ])
+
+ done = Deferred()
+ f = protocol.ClientFactory()
+ f.protocol = lambda: client
+ f.clientConnectionLost = lambda *args: done.callback(None)
+ reactor.connectTCP('127.0.0.1', self.smtpServer.getHost().port, f)
+
+ def finished(ign):
+ mbox = domain.requestAvatar('user', None, pop3.IMailbox)[1]
+ msg = mbox.getMessage(0).read()
+ self.failIfEqual(msg.find('This is the message'), -1)
+
+ return self.smtpServer.stopListening()
+ done.addCallback(finished)
+ return done
+
+
+ def testRelayDelivery(self):
+ # Here is the service we will connect to and send mail from
+ insServ = mail.mail.MailService()
+ insServ.smtpPortal.registerChecker(cred.checkers.AllowAnonymousAccess())
+ domain = mail.maildir.MaildirDirdbmDomain(insServ, 'insertionDomain')
+ insServ.addDomain('insertion.domain', domain)
+ os.mkdir('insertionQueue')
+ insServ.setQueue(mail.relaymanager.Queue('insertionQueue'))
+ insServ.domains.setDefaultDomain(mail.relay.DomainQueuer(insServ))
+ manager = mail.relaymanager.SmartHostSMTPRelayingManager(insServ.queue)
+ manager.fArgs += ('test.identity.hostname',)
+ helper = mail.relaymanager.RelayStateHelper(manager, 1)
+ # Yoink! Now the internet obeys OUR every whim!
+ manager.mxcalc = mail.relaymanager.MXCalculator(self.resolver)
+ # And this is our whim.
+ self.auth.addresses['destination.domain'] = ['127.0.0.1']
+
+ f = insServ.getSMTPFactory()
+ self.insServer = reactor.listenTCP(0, f, interface='127.0.0.1')
+
+ # Here is the service the previous one will connect to for final
+ # delivery
+ destServ = mail.mail.MailService()
+ destServ.smtpPortal.registerChecker(cred.checkers.AllowAnonymousAccess())
+ domain = mail.maildir.MaildirDirdbmDomain(destServ, 'destinationDomain')
+ domain.addUser('user', 'password')
+ destServ.addDomain('destination.domain', domain)
+ os.mkdir('destinationQueue')
+ destServ.setQueue(mail.relaymanager.Queue('destinationQueue'))
+ manager2 = mail.relaymanager.SmartHostSMTPRelayingManager(destServ.queue)
+ helper = mail.relaymanager.RelayStateHelper(manager, 1)
+ helper.startService()
+
+ f = destServ.getSMTPFactory()
+ self.destServer = reactor.listenTCP(0, f, interface='127.0.0.1')
+
+ # Update the port number the *first* relay will connect to, because we can't use
+ # port 25
+ manager.PORT = self.destServer.getHost().port
+
+ client = LineSendingProtocol([
+ 'HELO meson',
+ 'MAIL FROM: <user@wherever>',
+ 'RCPT TO: <user@destination.domain>',
+ 'DATA',
+ 'This is the message',
+ '.',
+ 'QUIT'
+ ])
+
+ done = Deferred()
+ f = protocol.ClientFactory()
+ f.protocol = lambda: client
+ f.clientConnectionLost = lambda *args: done.callback(None)
+ reactor.connectTCP('127.0.0.1', self.insServer.getHost().port, f)
+
+ def finished(ign):
+ # First part of the delivery is done. Poke the queue manually now
+ # so we don't have to wait for the queue to be flushed.
+ delivery = manager.checkState()
+ def delivered(ign):
+ mbox = domain.requestAvatar('user', None, pop3.IMailbox)[1]
+ msg = mbox.getMessage(0).read()
+ self.failIfEqual(msg.find('This is the message'), -1)
+
+ self.insServer.stopListening()
+ self.destServer.stopListening()
+ helper.stopService()
+ delivery.addCallback(delivered)
+ return delivery
+ done.addCallback(finished)
+ return done
+
+
+aliasFile = StringIO.StringIO("""\
+# Here's a comment
+ # woop another one
+testuser: address1,address2, address3,
+ continuation@address, |/bin/process/this
+
+usertwo:thisaddress,thataddress, lastaddress
+lastuser: :/includable, /filename, |/program, address
+""")
+
+class LineBufferMessage:
+ def __init__(self):
+ self.lines = []
+ self.eom = False
+ self.lost = False
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def eomReceived(self):
+ self.eom = True
+ return defer.succeed('<Whatever>')
+
+ def connectionLost(self):
+ self.lost = True
+
+class AliasTestCase(unittest.TestCase):
+ lines = [
+ 'First line',
+ 'Next line',
+ '',
+ 'After a blank line',
+ 'Last line'
+ ]
+
+ def setUp(self):
+ aliasFile.seek(0)
+
+ def testHandle(self):
+ result = {}
+ lines = [
+ 'user: another@host\n',
+ 'nextuser: |/bin/program\n',
+ 'user: me@again\n',
+ 'moreusers: :/etc/include/filename\n',
+ 'multiuser: first@host, second@host,last@anotherhost',
+ ]
+
+ for l in lines:
+ mail.alias.handle(result, l, 'TestCase', None)
+
+ self.assertEquals(result['user'], ['another@host', 'me@again'])
+ self.assertEquals(result['nextuser'], ['|/bin/program'])
+ self.assertEquals(result['moreusers'], [':/etc/include/filename'])
+ self.assertEquals(result['multiuser'], ['first@host', 'second@host', 'last@anotherhost'])
+
+ def testFileLoader(self):
+ domains = {'': object()}
+ result = mail.alias.loadAliasFile(domains, fp=aliasFile)
+
+ self.assertEquals(len(result), 3)
+
+ group = result['testuser']
+ s = str(group)
+ for a in ('address1', 'address2', 'address3', 'continuation@address', '/bin/process/this'):
+ self.failIfEqual(s.find(a), -1)
+ self.assertEquals(len(group), 5)
+
+ group = result['usertwo']
+ s = str(group)
+ for a in ('thisaddress', 'thataddress', 'lastaddress'):
+ self.failIfEqual(s.find(a), -1)
+ self.assertEquals(len(group), 3)
+
+ group = result['lastuser']
+ s = str(group)
+ self.failUnlessEqual(s.find('/includable'), -1)
+ for a in ('/filename', 'program', 'address'):
+ self.failIfEqual(s.find(a), -1, '%s not found' % a)
+ self.assertEquals(len(group), 3)
+
+ def testMultiWrapper(self):
+ msgs = LineBufferMessage(), LineBufferMessage(), LineBufferMessage()
+ msg = mail.alias.MultiWrapper(msgs)
+
+ for L in self.lines:
+ msg.lineReceived(L)
+ return msg.eomReceived().addCallback(self._cbMultiWrapper, msgs)
+
+ def _cbMultiWrapper(self, ignored, msgs):
+ for m in msgs:
+ self.failUnless(m.eom)
+ self.failIf(m.lost)
+ self.assertEquals(self.lines, m.lines)
+
+ def testFileAlias(self):
+ tmpfile = self.mktemp()
+ a = mail.alias.FileAlias(tmpfile, None, None)
+ m = a.createMessageReceiver()
+
+ for l in self.lines:
+ m.lineReceived(l)
+ return m.eomReceived().addCallback(self._cbTestFileAlias, tmpfile)
+
+ def _cbTestFileAlias(self, ignored, tmpfile):
+ lines = file(tmpfile).readlines()
+ self.assertEquals([L[:-1] for L in lines], self.lines)
+
+
+
+class DummyProcess(object):
+ __slots__ = ['onEnd']
+
+
+
+class MockProcessAlias(mail.alias.ProcessAlias):
+ """
+ A alias processor that doesn't actually launch processes.
+ """
+
+ def spawnProcess(self, proto, program, path):
+ """
+ Don't spawn a process.
+ """
+
+
+
+class MockAliasGroup(mail.alias.AliasGroup):
+ """
+ An alias group using C{MockProcessAlias}.
+ """
+ processAliasFactory = MockProcessAlias
+
+
+
+class StubProcess(object):
+ """
+ Fake implementation of L{IProcessTransport}.
+
+ @ivar signals: A list of all the signals which have been sent to this fake
+ process.
+ """
+ def __init__(self):
+ self.signals = []
+
+
+ def loseConnection(self):
+ """
+ No-op implementation of disconnection.
+ """
+
+
+ def signalProcess(self, signal):
+ """
+ Record a signal sent to this process for later inspection.
+ """
+ self.signals.append(signal)
+
+
+
+class ProcessAliasTestCase(unittest.TestCase):
+ """
+ Tests for alias resolution.
+ """
+ if interfaces.IReactorProcess(reactor, None) is None:
+ skip = "IReactorProcess not supported"
+
+ lines = [
+ 'First line',
+ 'Next line',
+ '',
+ 'After a blank line',
+ 'Last line'
+ ]
+
+ def exitStatus(self, code):
+ """
+ Construct a status from the given exit code.
+
+ @type code: L{int} between 0 and 255 inclusive.
+ @param code: The exit status which the code will represent.
+
+ @rtype: L{int}
+ @return: A status integer for the given exit code.
+ """
+ # /* Macros for constructing status values. */
+ # #define __W_EXITCODE(ret, sig) ((ret) << 8 | (sig))
+ status = (code << 8) | 0
+
+ # Sanity check
+ self.assertTrue(os.WIFEXITED(status))
+ self.assertEqual(os.WEXITSTATUS(status), code)
+ self.assertFalse(os.WIFSIGNALED(status))
+
+ return status
+
+
+ def signalStatus(self, signal):
+ """
+ Construct a status from the given signal.
+
+ @type signal: L{int} between 0 and 255 inclusive.
+ @param signal: The signal number which the status will represent.
+
+ @rtype: L{int}
+ @return: A status integer for the given signal.
+ """
+ # /* If WIFSIGNALED(STATUS), the terminating signal. */
+ # #define __WTERMSIG(status) ((status) & 0x7f)
+ # /* Nonzero if STATUS indicates termination by a signal. */
+ # #define __WIFSIGNALED(status) \
+ # (((signed char) (((status) & 0x7f) + 1) >> 1) > 0)
+ status = signal
+
+ # Sanity check
+ self.assertTrue(os.WIFSIGNALED(status))
+ self.assertEqual(os.WTERMSIG(status), signal)
+ self.assertFalse(os.WIFEXITED(status))
+
+ return status
+
+
+ def setUp(self):
+ """
+ Replace L{smtp.DNSNAME} with a well-known value.
+ """
+ self.DNSNAME = smtp.DNSNAME
+ smtp.DNSNAME = ''
+
+
+ def tearDown(self):
+ """
+ Restore the original value of L{smtp.DNSNAME}.
+ """
+ smtp.DNSNAME = self.DNSNAME
+
+
+ def test_processAlias(self):
+ """
+ Standard call to C{mail.alias.ProcessAlias}: check that the specified
+ script is called, and that the input is correctly transferred to it.
+ """
+ sh = FilePath(self.mktemp())
+ sh.setContent("""\
+#!/bin/sh
+rm -f process.alias.out
+while read i; do
+ echo $i >> process.alias.out
+done""")
+ os.chmod(sh.path, 0700)
+ a = mail.alias.ProcessAlias(sh.path, None, None)
+ m = a.createMessageReceiver()
+
+ for l in self.lines:
+ m.lineReceived(l)
+
+ def _cbProcessAlias(ignored):
+ lines = file('process.alias.out').readlines()
+ self.assertEquals([L[:-1] for L in lines], self.lines)
+
+ return m.eomReceived().addCallback(_cbProcessAlias)
+
+
+ def test_processAliasTimeout(self):
+ """
+ If the alias child process does not exit within a particular period of
+ time, the L{Deferred} returned by L{MessageWrapper.eomReceived} should
+ fail with L{ProcessAliasTimeout} and send the I{KILL} signal to the
+ child process..
+ """
+ reactor = task.Clock()
+ transport = StubProcess()
+ proto = mail.alias.ProcessAliasProtocol()
+ proto.makeConnection(transport)
+
+ receiver = mail.alias.MessageWrapper(proto, None, reactor)
+ d = receiver.eomReceived()
+ reactor.advance(receiver.completionTimeout)
+ def timedOut(ignored):
+ self.assertEqual(transport.signals, ['KILL'])
+ # Now that it has been killed, disconnect the protocol associated
+ # with it.
+ proto.processEnded(
+ ProcessTerminated(self.signalStatus(signal.SIGKILL)))
+ self.assertFailure(d, mail.alias.ProcessAliasTimeout)
+ d.addCallback(timedOut)
+ return d
+
+
+ def test_earlyProcessTermination(self):
+ """
+ If the process associated with an L{mail.alias.MessageWrapper} exits
+ before I{eomReceived} is called, the L{Deferred} returned by
+ I{eomReceived} should fail.
+ """
+ transport = StubProcess()
+ protocol = mail.alias.ProcessAliasProtocol()
+ protocol.makeConnection(transport)
+ receiver = mail.alias.MessageWrapper(protocol, None, None)
+ protocol.processEnded(failure.Failure(ProcessDone(0)))
+ return self.assertFailure(receiver.eomReceived(), ProcessDone)
+
+
+ def _terminationTest(self, status):
+ """
+ Verify that if the process associated with an
+ L{mail.alias.MessageWrapper} exits with the given status, the
+ L{Deferred} returned by I{eomReceived} fails with L{ProcessTerminated}.
+ """
+ transport = StubProcess()
+ protocol = mail.alias.ProcessAliasProtocol()
+ protocol.makeConnection(transport)
+ receiver = mail.alias.MessageWrapper(protocol, None, None)
+ protocol.processEnded(
+ failure.Failure(ProcessTerminated(status)))
+ return self.assertFailure(receiver.eomReceived(), ProcessTerminated)
+
+
+ def test_errorProcessTermination(self):
+ """
+ If the process associated with an L{mail.alias.MessageWrapper} exits
+ with a non-zero exit code, the L{Deferred} returned by I{eomReceived}
+ should fail.
+ """
+ return self._terminationTest(self.exitStatus(1))
+
+
+ def test_signalProcessTermination(self):
+ """
+ If the process associated with an L{mail.alias.MessageWrapper} exits
+ because it received a signal, the L{Deferred} returned by
+ I{eomReceived} should fail.
+ """
+ return self._terminationTest(self.signalStatus(signal.SIGHUP))
+
+
+ def test_aliasResolution(self):
+ """
+ Check that the C{resolve} method of alias processors produce the correct
+ set of objects:
+ - direct alias with L{mail.alias.AddressAlias} if a simple input is passed
+ - aliases in a file with L{mail.alias.FileWrapper} if an input in the format
+ '/file' is given
+ - aliases resulting of a process call wrapped by L{mail.alias.MessageWrapper}
+ if the format is '|process'
+ """
+ aliases = {}
+ domain = {'': TestDomain(aliases, ['user1', 'user2', 'user3'])}
+ A1 = MockAliasGroup(['user1', '|echo', '/file'], domain, 'alias1')
+ A2 = MockAliasGroup(['user2', 'user3'], domain, 'alias2')
+ A3 = mail.alias.AddressAlias('alias1', domain, 'alias3')
+ aliases.update({
+ 'alias1': A1,
+ 'alias2': A2,
+ 'alias3': A3,
+ })
+
+ res1 = A1.resolve(aliases)
+ r1 = map(str, res1.objs)
+ r1.sort()
+ expected = map(str, [
+ mail.alias.AddressAlias('user1', None, None),
+ mail.alias.MessageWrapper(DummyProcess(), 'echo'),
+ mail.alias.FileWrapper('/file'),
+ ])
+ expected.sort()
+ self.assertEquals(r1, expected)
+
+ res2 = A2.resolve(aliases)
+ r2 = map(str, res2.objs)
+ r2.sort()
+ expected = map(str, [
+ mail.alias.AddressAlias('user2', None, None),
+ mail.alias.AddressAlias('user3', None, None)
+ ])
+ expected.sort()
+ self.assertEquals(r2, expected)
+
+ res3 = A3.resolve(aliases)
+ r3 = map(str, res3.objs)
+ r3.sort()
+ expected = map(str, [
+ mail.alias.AddressAlias('user1', None, None),
+ mail.alias.MessageWrapper(DummyProcess(), 'echo'),
+ mail.alias.FileWrapper('/file'),
+ ])
+ expected.sort()
+ self.assertEquals(r3, expected)
+
+
+ def test_cyclicAlias(self):
+ """
+ Check that a cycle in alias resolution is correctly handled.
+ """
+ aliases = {}
+ domain = {'': TestDomain(aliases, [])}
+ A1 = mail.alias.AddressAlias('alias2', domain, 'alias1')
+ A2 = mail.alias.AddressAlias('alias3', domain, 'alias2')
+ A3 = mail.alias.AddressAlias('alias1', domain, 'alias3')
+ aliases.update({
+ 'alias1': A1,
+ 'alias2': A2,
+ 'alias3': A3
+ })
+
+ self.assertEquals(aliases['alias1'].resolve(aliases), None)
+ self.assertEquals(aliases['alias2'].resolve(aliases), None)
+ self.assertEquals(aliases['alias3'].resolve(aliases), None)
+
+ A4 = MockAliasGroup(['|echo', 'alias1'], domain, 'alias4')
+ aliases['alias4'] = A4
+
+ res = A4.resolve(aliases)
+ r = map(str, res.objs)
+ r.sort()
+ expected = map(str, [
+ mail.alias.MessageWrapper(DummyProcess(), 'echo')
+ ])
+ expected.sort()
+ self.assertEquals(r, expected)
+
+
+
+
+
+
+class TestDomain:
+ def __init__(self, aliases, users):
+ self.aliases = aliases
+ self.users = users
+
+ def exists(self, user, memo=None):
+ user = user.dest.local
+ if user in self.users:
+ return lambda: mail.alias.AddressAlias(user, None, None)
+ try:
+ a = self.aliases[user]
+ except:
+ raise smtp.SMTPBadRcpt(user)
+ else:
+ aliases = a.resolve(self.aliases, memo)
+ if aliases:
+ return lambda: aliases
+ raise smtp.SMTPBadRcpt(user)
+
+
+from twisted.python.runtime import platformType
+import types
+if platformType != "posix":
+ for o in locals().values():
+ if isinstance(o, (types.ClassType, type)) and issubclass(o, unittest.TestCase):
+ o.skip = "twisted.mail only works on posix"
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/test_mailmail.py b/vendor/Twisted-10.0.0/twisted/mail/test/test_mailmail.py
new file mode 100644
index 0000000000..776914cffa
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/test_mailmail.py
@@ -0,0 +1,75 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.mail.scripts.mailmail}, the implementation of the
+command line program I{mailmail}.
+"""
+
+import sys
+from StringIO import StringIO
+
+from twisted.trial.unittest import TestCase
+from twisted.mail.scripts.mailmail import parseOptions
+
+
+class OptionsTests(TestCase):
+ """
+ Tests for L{parseOptions} which parses command line arguments and reads
+ message text from stdin to produce an L{Options} instance which can be
+ used to send a message.
+ """
+ def test_unspecifiedRecipients(self):
+ """
+ If no recipients are given in the argument list and there is no
+ recipient header in the message text, L{parseOptions} raises
+ L{SystemExit} with a string describing the problem.
+ """
+ self.addCleanup(setattr, sys, 'stdin', sys.stdin)
+ sys.stdin = StringIO(
+ 'Subject: foo\n'
+ '\n'
+ 'Hello, goodbye.\n')
+ exc = self.assertRaises(SystemExit, parseOptions, [])
+ self.assertEqual(exc.args, ('No recipients specified.',))
+
+
+ def test_listQueueInformation(self):
+ """
+ The I{-bp} option for listing queue information is unsupported and
+ if it is passed to L{parseOptions}, L{SystemExit} is raised.
+ """
+ exc = self.assertRaises(SystemExit, parseOptions, ['-bp'])
+ self.assertEqual(exc.args, ("Unsupported option.",))
+
+
+ def test_stdioTransport(self):
+ """
+ The I{-bs} option for using stdin and stdout as the SMTP transport
+ is unsupported and if it is passed to L{parseOptions}, L{SystemExit}
+ is raised.
+ """
+ exc = self.assertRaises(SystemExit, parseOptions, ['-bs'])
+ self.assertEqual(exc.args, ("Unsupported option.",))
+
+
+ def test_ignoreFullStop(self):
+ """
+ The I{-i} and I{-oi} options for ignoring C{"."} by itself on a line
+ are unsupported and if either is passed to L{parseOptions},
+ L{SystemExit} is raised.
+ """
+ exc = self.assertRaises(SystemExit, parseOptions, ['-i'])
+ self.assertEqual(exc.args, ("Unsupported option.",))
+ exc = self.assertRaises(SystemExit, parseOptions, ['-oi'])
+ self.assertEqual(exc.args, ("Unsupported option.",))
+
+
+ def test_copyAliasedSender(self):
+ """
+ The I{-om} option for copying the sender if they appear in an alias
+ expansion is unsupported and if it is passed to L{parseOptions},
+ L{SystemExit} is raised.
+ """
+ exc = self.assertRaises(SystemExit, parseOptions, ['-om'])
+ self.assertEqual(exc.args, ("Unsupported option.",))
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/test_options.py b/vendor/Twisted-10.0.0/twisted/mail/test/test_options.py
new file mode 100644
index 0000000000..492cfd81a5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/test_options.py
@@ -0,0 +1,44 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.mail.tap}.
+"""
+
+from twisted.trial.unittest import TestCase
+
+from twisted.python.usage import UsageError
+from twisted.mail.tap import Options
+
+
+class OptionsTestCase(TestCase):
+ """
+ Tests for the command line option parser used for C{twistd mail}.
+ """
+ def setUp(self):
+ self.aliasFilename = self.mktemp()
+ aliasFile = file(self.aliasFilename, 'w')
+ aliasFile.write('someuser:\tdifferentuser\n')
+ aliasFile.close()
+
+
+ def testAliasesWithoutDomain(self):
+ """
+ Test that adding an aliases(5) file before adding a domain raises a
+ UsageError.
+ """
+ self.assertRaises(
+ UsageError,
+ Options().parseOptions,
+ ['--aliases', self.aliasFilename])
+
+
+ def testAliases(self):
+ """
+ Test that adding an aliases(5) file to an IAliasableDomain at least
+ doesn't raise an unhandled exception.
+ """
+ Options().parseOptions([
+ '--maildirdbmdomain', 'example.com=example.com',
+ '--aliases', self.aliasFilename])
+
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/test_pop3.py b/vendor/Twisted-10.0.0/twisted/mail/test/test_pop3.py
new file mode 100644
index 0000000000..e659de6d68
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/test_pop3.py
@@ -0,0 +1,1069 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for Ltwisted.mail.pop3} module.
+"""
+
+import StringIO
+import hmac
+import base64
+import itertools
+
+from zope.interface import implements
+
+from twisted.internet import defer
+
+from twisted.trial import unittest, util
+from twisted import mail
+import twisted.mail.protocols
+import twisted.mail.pop3
+import twisted.internet.protocol
+from twisted import internet
+from twisted.mail import pop3
+from twisted.protocols import loopback
+from twisted.python import failure
+
+from twisted import cred
+import twisted.cred.portal
+import twisted.cred.checkers
+import twisted.cred.credentials
+
+from twisted.test.proto_helpers import LineSendingProtocol
+
+
+class UtilityTestCase(unittest.TestCase):
+ """
+ Test the various helper functions and classes used by the POP3 server
+ protocol implementation.
+ """
+
+ def testLineBuffering(self):
+ """
+ Test creating a LineBuffer and feeding it some lines. The lines should
+ build up in its internal buffer for a while and then get spat out to
+ the writer.
+ """
+ output = []
+ input = iter(itertools.cycle(['012', '345', '6', '7', '8', '9']))
+ c = pop3._IteratorBuffer(output.extend, input, 6)
+ i = iter(c)
+ self.assertEquals(output, []) # nothing is buffer
+ i.next()
+ self.assertEquals(output, []) # '012' is buffered
+ i.next()
+ self.assertEquals(output, []) # '012345' is buffered
+ i.next()
+ self.assertEquals(output, ['012', '345', '6']) # nothing is buffered
+ for n in range(5):
+ i.next()
+ self.assertEquals(output, ['012', '345', '6', '7', '8', '9', '012', '345'])
+
+
+ def testFinishLineBuffering(self):
+ """
+ Test that a LineBuffer flushes everything when its iterator is
+ exhausted, and itself raises StopIteration.
+ """
+ output = []
+ input = iter(['a', 'b', 'c'])
+ c = pop3._IteratorBuffer(output.extend, input, 5)
+ for i in c:
+ pass
+ self.assertEquals(output, ['a', 'b', 'c'])
+
+
+ def testSuccessResponseFormatter(self):
+ """
+ Test that the thing that spits out POP3 'success responses' works
+ right.
+ """
+ self.assertEquals(
+ pop3.successResponse('Great.'),
+ '+OK Great.\r\n')
+
+
+ def testStatLineFormatter(self):
+ """
+ Test that the function which formats stat lines does so appropriately.
+ """
+ statLine = list(pop3.formatStatResponse([]))[-1]
+ self.assertEquals(statLine, '+OK 0 0\r\n')
+
+ statLine = list(pop3.formatStatResponse([10, 31, 0, 10101]))[-1]
+ self.assertEquals(statLine, '+OK 4 10142\r\n')
+
+
+ def testListLineFormatter(self):
+ """
+ Test that the function which formats the lines in response to a LIST
+ command does so appropriately.
+ """
+ listLines = list(pop3.formatListResponse([]))
+ self.assertEquals(
+ listLines,
+ ['+OK 0\r\n', '.\r\n'])
+
+ listLines = list(pop3.formatListResponse([1, 2, 3, 100]))
+ self.assertEquals(
+ listLines,
+ ['+OK 4\r\n', '1 1\r\n', '2 2\r\n', '3 3\r\n', '4 100\r\n', '.\r\n'])
+
+
+
+ def testUIDListLineFormatter(self):
+ """
+ Test that the function which formats lines in response to a UIDL
+ command does so appropriately.
+ """
+ UIDs = ['abc', 'def', 'ghi']
+ listLines = list(pop3.formatUIDListResponse([], UIDs.__getitem__))
+ self.assertEquals(
+ listLines,
+ ['+OK \r\n', '.\r\n'])
+
+ listLines = list(pop3.formatUIDListResponse([123, 431, 591], UIDs.__getitem__))
+ self.assertEquals(
+ listLines,
+ ['+OK \r\n', '1 abc\r\n', '2 def\r\n', '3 ghi\r\n', '.\r\n'])
+
+ listLines = list(pop3.formatUIDListResponse([0, None, 591], UIDs.__getitem__))
+ self.assertEquals(
+ listLines,
+ ['+OK \r\n', '1 abc\r\n', '3 ghi\r\n', '.\r\n'])
+
+
+
+class MyVirtualPOP3(mail.protocols.VirtualPOP3):
+
+ magic = '<moshez>'
+
+ def authenticateUserAPOP(self, user, digest):
+ user, domain = self.lookupDomain(user)
+ return self.service.domains['baz.com'].authenticateUserAPOP(user, digest, self.magic, domain)
+
+class DummyDomain:
+
+ def __init__(self):
+ self.users = {}
+
+ def addUser(self, name):
+ self.users[name] = []
+
+ def addMessage(self, name, message):
+ self.users[name].append(message)
+
+ def authenticateUserAPOP(self, name, digest, magic, domain):
+ return pop3.IMailbox, ListMailbox(self.users[name]), lambda: None
+
+
+class ListMailbox:
+
+ def __init__(self, list):
+ self.list = list
+
+ def listMessages(self, i=None):
+ if i is None:
+ return map(len, self.list)
+ return len(self.list[i])
+
+ def getMessage(self, i):
+ return StringIO.StringIO(self.list[i])
+
+ def getUidl(self, i):
+ return i
+
+ def deleteMessage(self, i):
+ self.list[i] = ''
+
+ def sync(self):
+ pass
+
+class MyPOP3Downloader(pop3.POP3Client):
+
+ def handle_WELCOME(self, line):
+ pop3.POP3Client.handle_WELCOME(self, line)
+ self.apop('hello@baz.com', 'world')
+
+ def handle_APOP(self, line):
+ parts = line.split()
+ code = parts[0]
+ data = (parts[1:] or ['NONE'])[0]
+ if code != '+OK':
+ print parts
+ raise AssertionError, 'code is ' + code
+ self.lines = []
+ self.retr(1)
+
+ def handle_RETR_continue(self, line):
+ self.lines.append(line)
+
+ def handle_RETR_end(self):
+ self.message = '\n'.join(self.lines) + '\n'
+ self.quit()
+
+ def handle_QUIT(self, line):
+ if line[:3] != '+OK':
+ raise AssertionError, 'code is ' + line
+
+
+class POP3TestCase(unittest.TestCase):
+
+ message = '''\
+Subject: urgent
+
+Someone set up us the bomb!
+'''
+
+ expectedOutput = '''\
++OK <moshez>\015
++OK Authentication succeeded\015
++OK \015
+1 0\015
+.\015
++OK %d\015
+Subject: urgent\015
+\015
+Someone set up us the bomb!\015
+.\015
++OK \015
+''' % len(message)
+
+ def setUp(self):
+ self.factory = internet.protocol.Factory()
+ self.factory.domains = {}
+ self.factory.domains['baz.com'] = DummyDomain()
+ self.factory.domains['baz.com'].addUser('hello')
+ self.factory.domains['baz.com'].addMessage('hello', self.message)
+
+ def testMessages(self):
+ client = LineSendingProtocol([
+ 'APOP hello@baz.com world',
+ 'UIDL',
+ 'RETR 1',
+ 'QUIT',
+ ])
+ server = MyVirtualPOP3()
+ server.service = self.factory
+ def check(ignored):
+ output = '\r\n'.join(client.response) + '\r\n'
+ self.assertEquals(output, self.expectedOutput)
+ return loopback.loopbackTCP(server, client).addCallback(check)
+
+ def testLoopback(self):
+ protocol = MyVirtualPOP3()
+ protocol.service = self.factory
+ clientProtocol = MyPOP3Downloader()
+ def check(ignored):
+ self.failUnlessEqual(clientProtocol.message, self.message)
+ protocol.connectionLost(
+ failure.Failure(Exception("Test harness disconnect")))
+ d = loopback.loopbackAsync(protocol, clientProtocol)
+ return d.addCallback(check)
+ testLoopback.suppress = [util.suppress(message="twisted.mail.pop3.POP3Client is deprecated")]
+
+
+
+class DummyPOP3(pop3.POP3):
+
+ magic = '<moshez>'
+
+ def authenticateUserAPOP(self, user, password):
+ return pop3.IMailbox, DummyMailbox(ValueError), lambda: None
+
+
+
+class DummyMailbox(pop3.Mailbox):
+
+ messages = ['From: moshe\nTo: moshe\n\nHow are you, friend?\n']
+
+ def __init__(self, exceptionType):
+ self.messages = DummyMailbox.messages[:]
+ self.exceptionType = exceptionType
+
+ def listMessages(self, i=None):
+ if i is None:
+ return map(len, self.messages)
+ if i >= len(self.messages):
+ raise self.exceptionType()
+ return len(self.messages[i])
+
+ def getMessage(self, i):
+ return StringIO.StringIO(self.messages[i])
+
+ def getUidl(self, i):
+ if i >= len(self.messages):
+ raise self.exceptionType()
+ return str(i)
+
+ def deleteMessage(self, i):
+ self.messages[i] = ''
+
+
+class AnotherPOP3TestCase(unittest.TestCase):
+
+ def runTest(self, lines, expectedOutput):
+ dummy = DummyPOP3()
+ client = LineSendingProtocol(lines)
+ d = loopback.loopbackAsync(dummy, client)
+ return d.addCallback(self._cbRunTest, client, dummy, expectedOutput)
+
+
+ def _cbRunTest(self, ignored, client, dummy, expectedOutput):
+ self.failUnlessEqual('\r\n'.join(expectedOutput),
+ '\r\n'.join(client.response))
+ dummy.connectionLost(failure.Failure(Exception("Test harness disconnect")))
+ return ignored
+
+
+ def test_buffer(self):
+ """
+ Test a lot of different POP3 commands in an extremely pipelined
+ scenario.
+
+ This test may cover legitimate behavior, but the intent and
+ granularity are not very good. It would likely be an improvement to
+ split it into a number of smaller, more focused tests.
+ """
+ return self.runTest(
+ ["APOP moshez dummy",
+ "LIST",
+ "UIDL",
+ "RETR 1",
+ "RETR 2",
+ "DELE 1",
+ "RETR 1",
+ "QUIT"],
+ ['+OK <moshez>',
+ '+OK Authentication succeeded',
+ '+OK 1',
+ '1 44',
+ '.',
+ '+OK ',
+ '1 0',
+ '.',
+ '+OK 44',
+ 'From: moshe',
+ 'To: moshe',
+ '',
+ 'How are you, friend?',
+ '.',
+ '-ERR Bad message number argument',
+ '+OK ',
+ '-ERR message deleted',
+ '+OK '])
+
+
+ def test_noop(self):
+ """
+ Test the no-op command.
+ """
+ return self.runTest(
+ ['APOP spiv dummy',
+ 'NOOP',
+ 'QUIT'],
+ ['+OK <moshez>',
+ '+OK Authentication succeeded',
+ '+OK ',
+ '+OK '])
+
+
+ def testAuthListing(self):
+ p = DummyPOP3()
+ p.factory = internet.protocol.Factory()
+ p.factory.challengers = {'Auth1': None, 'secondAuth': None, 'authLast': None}
+ client = LineSendingProtocol([
+ "AUTH",
+ "QUIT",
+ ])
+
+ d = loopback.loopbackAsync(p, client)
+ return d.addCallback(self._cbTestAuthListing, client)
+
+ def _cbTestAuthListing(self, ignored, client):
+ self.failUnless(client.response[1].startswith('+OK'))
+ self.assertEquals(client.response[2:6],
+ ["AUTH1", "SECONDAUTH", "AUTHLAST", "."])
+
+ def testIllegalPASS(self):
+ dummy = DummyPOP3()
+ client = LineSendingProtocol([
+ "PASS fooz",
+ "QUIT"
+ ])
+ d = loopback.loopbackAsync(dummy, client)
+ return d.addCallback(self._cbTestIllegalPASS, client, dummy)
+
+ def _cbTestIllegalPASS(self, ignored, client, dummy):
+ expected_output = '+OK <moshez>\r\n-ERR USER required before PASS\r\n+OK \r\n'
+ self.failUnlessEqual(expected_output, '\r\n'.join(client.response) + '\r\n')
+ dummy.connectionLost(failure.Failure(Exception("Test harness disconnect")))
+
+ def testEmptyPASS(self):
+ dummy = DummyPOP3()
+ client = LineSendingProtocol([
+ "PASS ",
+ "QUIT"
+ ])
+ d = loopback.loopbackAsync(dummy, client)
+ return d.addCallback(self._cbTestEmptyPASS, client, dummy)
+
+ def _cbTestEmptyPASS(self, ignored, client, dummy):
+ expected_output = '+OK <moshez>\r\n-ERR USER required before PASS\r\n+OK \r\n'
+ self.failUnlessEqual(expected_output, '\r\n'.join(client.response) + '\r\n')
+ dummy.connectionLost(failure.Failure(Exception("Test harness disconnect")))
+
+
+class TestServerFactory:
+ implements(pop3.IServerFactory)
+
+ def cap_IMPLEMENTATION(self):
+ return "Test Implementation String"
+
+ def cap_EXPIRE(self):
+ return 60
+
+ challengers = {"SCHEME_1": None, "SCHEME_2": None}
+
+ def cap_LOGIN_DELAY(self):
+ return 120
+
+ pue = True
+ def perUserExpiration(self):
+ return self.pue
+
+ puld = True
+ def perUserLoginDelay(self):
+ return self.puld
+
+
+class TestMailbox:
+ loginDelay = 100
+ messageExpiration = 25
+
+
+class CapabilityTestCase(unittest.TestCase):
+ def setUp(self):
+ s = StringIO.StringIO()
+ p = pop3.POP3()
+ p.factory = TestServerFactory()
+ p.transport = internet.protocol.FileWrapper(s)
+ p.connectionMade()
+ p.do_CAPA()
+
+ self.caps = p.listCapabilities()
+ self.pcaps = s.getvalue().splitlines()
+
+ s = StringIO.StringIO()
+ p.mbox = TestMailbox()
+ p.transport = internet.protocol.FileWrapper(s)
+ p.do_CAPA()
+
+ self.lpcaps = s.getvalue().splitlines()
+ p.connectionLost(failure.Failure(Exception("Test harness disconnect")))
+
+ def contained(self, s, *caps):
+ for c in caps:
+ self.assertIn(s, c)
+
+ def testUIDL(self):
+ self.contained("UIDL", self.caps, self.pcaps, self.lpcaps)
+
+ def testTOP(self):
+ self.contained("TOP", self.caps, self.pcaps, self.lpcaps)
+
+ def testUSER(self):
+ self.contained("USER", self.caps, self.pcaps, self.lpcaps)
+
+ def testEXPIRE(self):
+ self.contained("EXPIRE 60 USER", self.caps, self.pcaps)
+ self.contained("EXPIRE 25", self.lpcaps)
+
+ def testIMPLEMENTATION(self):
+ self.contained(
+ "IMPLEMENTATION Test Implementation String",
+ self.caps, self.pcaps, self.lpcaps
+ )
+
+ def testSASL(self):
+ self.contained(
+ "SASL SCHEME_1 SCHEME_2",
+ self.caps, self.pcaps, self.lpcaps
+ )
+
+ def testLOGIN_DELAY(self):
+ self.contained("LOGIN-DELAY 120 USER", self.caps, self.pcaps)
+ self.assertIn("LOGIN-DELAY 100", self.lpcaps)
+
+
+
+class GlobalCapabilitiesTestCase(unittest.TestCase):
+ def setUp(self):
+ s = StringIO.StringIO()
+ p = pop3.POP3()
+ p.factory = TestServerFactory()
+ p.factory.pue = p.factory.puld = False
+ p.transport = internet.protocol.FileWrapper(s)
+ p.connectionMade()
+ p.do_CAPA()
+
+ self.caps = p.listCapabilities()
+ self.pcaps = s.getvalue().splitlines()
+
+ s = StringIO.StringIO()
+ p.mbox = TestMailbox()
+ p.transport = internet.protocol.FileWrapper(s)
+ p.do_CAPA()
+
+ self.lpcaps = s.getvalue().splitlines()
+ p.connectionLost(failure.Failure(Exception("Test harness disconnect")))
+
+ def contained(self, s, *caps):
+ for c in caps:
+ self.assertIn(s, c)
+
+ def testEXPIRE(self):
+ self.contained("EXPIRE 60", self.caps, self.pcaps, self.lpcaps)
+
+ def testLOGIN_DELAY(self):
+ self.contained("LOGIN-DELAY 120", self.caps, self.pcaps, self.lpcaps)
+
+
+
+class TestRealm:
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if avatarId == 'testuser':
+ return pop3.IMailbox, DummyMailbox(ValueError), lambda: None
+ assert False
+
+
+
+class SASLTestCase(unittest.TestCase):
+ def testValidLogin(self):
+ p = pop3.POP3()
+ p.factory = TestServerFactory()
+ p.factory.challengers = {'CRAM-MD5': cred.credentials.CramMD5Credentials}
+ p.portal = cred.portal.Portal(TestRealm())
+ ch = cred.checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ ch.addUser('testuser', 'testpassword')
+ p.portal.registerChecker(ch)
+
+ s = StringIO.StringIO()
+ p.transport = internet.protocol.FileWrapper(s)
+ p.connectionMade()
+
+ p.lineReceived("CAPA")
+ self.failUnless(s.getvalue().find("SASL CRAM-MD5") >= 0)
+
+ p.lineReceived("AUTH CRAM-MD5")
+ chal = s.getvalue().splitlines()[-1][2:]
+ chal = base64.decodestring(chal)
+ response = hmac.HMAC('testpassword', chal).hexdigest()
+
+ p.lineReceived(base64.encodestring('testuser ' + response).rstrip('\n'))
+ self.failUnless(p.mbox)
+ self.failUnless(s.getvalue().splitlines()[-1].find("+OK") >= 0)
+ p.connectionLost(failure.Failure(Exception("Test harness disconnect")))
+
+
+
+class CommandMixin:
+ """
+ Tests for all the commands a POP3 server is allowed to receive.
+ """
+
+ extraMessage = '''\
+From: guy
+To: fellow
+
+More message text for you.
+'''
+
+
+ def setUp(self):
+ """
+ Make a POP3 server protocol instance hooked up to a simple mailbox and
+ a transport that buffers output to a StringIO.
+ """
+ p = pop3.POP3()
+ p.mbox = self.mailboxType(self.exceptionType)
+ p.schedule = list
+ self.pop3Server = p
+
+ s = StringIO.StringIO()
+ p.transport = internet.protocol.FileWrapper(s)
+ p.connectionMade()
+ s.truncate(0)
+ self.pop3Transport = s
+
+
+ def tearDown(self):
+ """
+ Disconnect the server protocol so it can clean up anything it might
+ need to clean up.
+ """
+ self.pop3Server.connectionLost(failure.Failure(Exception("Test harness disconnect")))
+
+
+ def _flush(self):
+ """
+ Do some of the things that the reactor would take care of, if the
+ reactor were actually running.
+ """
+ # Oh man FileWrapper is pooh.
+ self.pop3Server.transport._checkProducer()
+
+
+ def testLIST(self):
+ """
+ Test the two forms of list: with a message index number, which should
+ return a short-form response, and without a message index number, which
+ should return a long-form response, one line per message.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+
+ p.lineReceived("LIST 1")
+ self._flush()
+ self.assertEquals(s.getvalue(), "+OK 1 44\r\n")
+ s.truncate(0)
+
+ p.lineReceived("LIST")
+ self._flush()
+ self.assertEquals(s.getvalue(), "+OK 1\r\n1 44\r\n.\r\n")
+
+
+ def testLISTWithBadArgument(self):
+ """
+ Test that non-integers and out-of-bound integers produce appropriate
+ error responses.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+
+ p.lineReceived("LIST a")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Invalid message-number: 'a'\r\n")
+ s.truncate(0)
+
+ p.lineReceived("LIST 0")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Invalid message-number: 0\r\n")
+ s.truncate(0)
+
+ p.lineReceived("LIST 2")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Invalid message-number: 2\r\n")
+ s.truncate(0)
+
+
+ def testUIDL(self):
+ """
+ Test the two forms of the UIDL command. These are just like the two
+ forms of the LIST command.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+
+ p.lineReceived("UIDL 1")
+ self.assertEquals(s.getvalue(), "+OK 0\r\n")
+ s.truncate(0)
+
+ p.lineReceived("UIDL")
+ self._flush()
+ self.assertEquals(s.getvalue(), "+OK \r\n1 0\r\n.\r\n")
+
+
+ def testUIDLWithBadArgument(self):
+ """
+ Test that UIDL with a non-integer or an out-of-bounds integer produces
+ the appropriate error response.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+
+ p.lineReceived("UIDL a")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad message number argument\r\n")
+ s.truncate(0)
+
+ p.lineReceived("UIDL 0")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad message number argument\r\n")
+ s.truncate(0)
+
+ p.lineReceived("UIDL 2")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad message number argument\r\n")
+ s.truncate(0)
+
+
+ def testSTAT(self):
+ """
+ Test the single form of the STAT command, which returns a short-form
+ response of the number of messages in the mailbox and their total size.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+
+ p.lineReceived("STAT")
+ self._flush()
+ self.assertEquals(s.getvalue(), "+OK 1 44\r\n")
+
+
+ def testRETR(self):
+ """
+ Test downloading a message.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+
+ p.lineReceived("RETR 1")
+ self._flush()
+ self.assertEquals(
+ s.getvalue(),
+ "+OK 44\r\n"
+ "From: moshe\r\n"
+ "To: moshe\r\n"
+ "\r\n"
+ "How are you, friend?\r\n"
+ ".\r\n")
+ s.truncate(0)
+
+
+ def testRETRWithBadArgument(self):
+ """
+ Test that trying to download a message with a bad argument, either not
+ an integer or an out-of-bounds integer, fails with the appropriate
+ error response.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+
+ p.lineReceived("RETR a")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad message number argument\r\n")
+ s.truncate(0)
+
+ p.lineReceived("RETR 0")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad message number argument\r\n")
+ s.truncate(0)
+
+ p.lineReceived("RETR 2")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad message number argument\r\n")
+ s.truncate(0)
+
+
+ def testTOP(self):
+ """
+ Test downloading the headers and part of the body of a message.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+ p.mbox.messages.append(self.extraMessage)
+
+ p.lineReceived("TOP 1 0")
+ self._flush()
+ self.assertEquals(
+ s.getvalue(),
+ "+OK Top of message follows\r\n"
+ "From: moshe\r\n"
+ "To: moshe\r\n"
+ "\r\n"
+ ".\r\n")
+
+
+ def testTOPWithBadArgument(self):
+ """
+ Test that trying to download a message with a bad argument, either a
+ message number which isn't an integer or is an out-of-bounds integer or
+ a number of lines which isn't an integer or is a negative integer,
+ fails with the appropriate error response.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+ p.mbox.messages.append(self.extraMessage)
+
+ p.lineReceived("TOP 1 a")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad line count argument\r\n")
+ s.truncate(0)
+
+ p.lineReceived("TOP 1 -1")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad line count argument\r\n")
+ s.truncate(0)
+
+ p.lineReceived("TOP a 1")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad message number argument\r\n")
+ s.truncate(0)
+
+ p.lineReceived("TOP 0 1")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad message number argument\r\n")
+ s.truncate(0)
+
+ p.lineReceived("TOP 3 1")
+ self.assertEquals(
+ s.getvalue(),
+ "-ERR Bad message number argument\r\n")
+ s.truncate(0)
+
+
+ def testLAST(self):
+ """
+ Test the exceedingly pointless LAST command, which tells you the
+ highest message index which you have already downloaded.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+ p.mbox.messages.append(self.extraMessage)
+
+ p.lineReceived('LAST')
+ self.assertEquals(
+ s.getvalue(),
+ "+OK 0\r\n")
+ s.truncate(0)
+
+
+ def testRetrieveUpdatesHighest(self):
+ """
+ Test that issuing a RETR command updates the LAST response.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+ p.mbox.messages.append(self.extraMessage)
+
+ p.lineReceived('RETR 2')
+ self._flush()
+ s.truncate(0)
+ p.lineReceived('LAST')
+ self.assertEquals(
+ s.getvalue(),
+ '+OK 2\r\n')
+ s.truncate(0)
+
+
+ def testTopUpdatesHighest(self):
+ """
+ Test that issuing a TOP command updates the LAST response.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+ p.mbox.messages.append(self.extraMessage)
+
+ p.lineReceived('TOP 2 10')
+ self._flush()
+ s.truncate(0)
+ p.lineReceived('LAST')
+ self.assertEquals(
+ s.getvalue(),
+ '+OK 2\r\n')
+
+
+ def testHighestOnlyProgresses(self):
+ """
+ Test that downloading a message with a smaller index than the current
+ LAST response doesn't change the LAST response.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+ p.mbox.messages.append(self.extraMessage)
+
+ p.lineReceived('RETR 2')
+ self._flush()
+ p.lineReceived('TOP 1 10')
+ self._flush()
+ s.truncate(0)
+ p.lineReceived('LAST')
+ self.assertEquals(
+ s.getvalue(),
+ '+OK 2\r\n')
+
+
+ def testResetClearsHighest(self):
+ """
+ Test that issuing RSET changes the LAST response to 0.
+ """
+ p = self.pop3Server
+ s = self.pop3Transport
+ p.mbox.messages.append(self.extraMessage)
+
+ p.lineReceived('RETR 2')
+ self._flush()
+ p.lineReceived('RSET')
+ s.truncate(0)
+ p.lineReceived('LAST')
+ self.assertEquals(
+ s.getvalue(),
+ '+OK 0\r\n')
+
+
+
+_listMessageDeprecation = (
+ "twisted.mail.pop3.IMailbox.listMessages may not "
+ "raise IndexError for out-of-bounds message numbers: "
+ "raise ValueError instead.")
+_listMessageSuppression = util.suppress(
+ message=_listMessageDeprecation,
+ category=PendingDeprecationWarning)
+
+_getUidlDeprecation = (
+ "twisted.mail.pop3.IMailbox.getUidl may not "
+ "raise IndexError for out-of-bounds message numbers: "
+ "raise ValueError instead.")
+_getUidlSuppression = util.suppress(
+ message=_getUidlDeprecation,
+ category=PendingDeprecationWarning)
+
+class IndexErrorCommandTestCase(CommandMixin, unittest.TestCase):
+ """
+ Run all of the command tests against a mailbox which raises IndexError
+ when an out of bounds request is made. This behavior will be deprecated
+ shortly and then removed.
+ """
+ exceptionType = IndexError
+ mailboxType = DummyMailbox
+
+ def testLISTWithBadArgument(self):
+ return CommandMixin.testLISTWithBadArgument(self)
+ testLISTWithBadArgument.suppress = [_listMessageSuppression]
+
+
+ def testUIDLWithBadArgument(self):
+ return CommandMixin.testUIDLWithBadArgument(self)
+ testUIDLWithBadArgument.suppress = [_getUidlSuppression]
+
+
+ def testTOPWithBadArgument(self):
+ return CommandMixin.testTOPWithBadArgument(self)
+ testTOPWithBadArgument.suppress = [_listMessageSuppression]
+
+
+ def testRETRWithBadArgument(self):
+ return CommandMixin.testRETRWithBadArgument(self)
+ testRETRWithBadArgument.suppress = [_listMessageSuppression]
+
+
+
+class ValueErrorCommandTestCase(CommandMixin, unittest.TestCase):
+ """
+ Run all of the command tests against a mailbox which raises ValueError
+ when an out of bounds request is made. This is the correct behavior and
+ after support for mailboxes which raise IndexError is removed, this will
+ become just C{CommandTestCase}.
+ """
+ exceptionType = ValueError
+ mailboxType = DummyMailbox
+
+
+
+class SyncDeferredMailbox(DummyMailbox):
+ """
+ Mailbox which has a listMessages implementation which returns a Deferred
+ which has already fired.
+ """
+ def listMessages(self, n=None):
+ return defer.succeed(DummyMailbox.listMessages(self, n))
+
+
+
+class IndexErrorSyncDeferredCommandTestCase(IndexErrorCommandTestCase):
+ """
+ Run all of the L{IndexErrorCommandTestCase} tests with a
+ synchronous-Deferred returning IMailbox implementation.
+ """
+ mailboxType = SyncDeferredMailbox
+
+
+
+class ValueErrorSyncDeferredCommandTestCase(ValueErrorCommandTestCase):
+ """
+ Run all of the L{ValueErrorCommandTestCase} tests with a
+ synchronous-Deferred returning IMailbox implementation.
+ """
+ mailboxType = SyncDeferredMailbox
+
+
+
+class AsyncDeferredMailbox(DummyMailbox):
+ """
+ Mailbox which has a listMessages implementation which returns a Deferred
+ which has not yet fired.
+ """
+ def __init__(self, *a, **kw):
+ self.waiting = []
+ DummyMailbox.__init__(self, *a, **kw)
+
+
+ def listMessages(self, n=None):
+ d = defer.Deferred()
+ # See AsyncDeferredMailbox._flush
+ self.waiting.append((d, DummyMailbox.listMessages(self, n)))
+ return d
+
+
+
+class IndexErrorAsyncDeferredCommandTestCase(IndexErrorCommandTestCase):
+ """
+ Run all of the L{IndexErrorCommandTestCase} tests with an asynchronous-Deferred
+ returning IMailbox implementation.
+ """
+ mailboxType = AsyncDeferredMailbox
+
+ def _flush(self):
+ """
+ Fire whatever Deferreds we've built up in our mailbox.
+ """
+ while self.pop3Server.mbox.waiting:
+ d, a = self.pop3Server.mbox.waiting.pop()
+ d.callback(a)
+ IndexErrorCommandTestCase._flush(self)
+
+
+
+class ValueErrorAsyncDeferredCommandTestCase(ValueErrorCommandTestCase):
+ """
+ Run all of the L{IndexErrorCommandTestCase} tests with an asynchronous-Deferred
+ returning IMailbox implementation.
+ """
+ mailboxType = AsyncDeferredMailbox
+
+ def _flush(self):
+ """
+ Fire whatever Deferreds we've built up in our mailbox.
+ """
+ while self.pop3Server.mbox.waiting:
+ d, a = self.pop3Server.mbox.waiting.pop()
+ d.callback(a)
+ ValueErrorCommandTestCase._flush(self)
+
+class POP3MiscTestCase(unittest.TestCase):
+ """
+ Miscellaneous tests more to do with module/package structure than
+ anything to do with the Post Office Protocol.
+ """
+ def test_all(self):
+ """
+ This test checks that all names listed in
+ twisted.mail.pop3.__all__ are actually present in the module.
+ """
+ mod = twisted.mail.pop3
+ for attr in mod.__all__:
+ self.failUnless(hasattr(mod, attr))
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/test_pop3client.py b/vendor/Twisted-10.0.0/twisted/mail/test/test_pop3client.py
new file mode 100644
index 0000000000..e2c3b8d733
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/test_pop3client.py
@@ -0,0 +1,573 @@
+# -*- test-case-name: twisted.mail.test.test_pop3client -*-
+# Copyright (c) 2001-2004 Divmod Inc.
+# See LICENSE for details.
+
+from zope.interface import directlyProvides
+
+from twisted.mail.pop3 import AdvancedPOP3Client as POP3Client
+from twisted.mail.pop3 import InsecureAuthenticationDisallowed
+from twisted.mail.pop3 import ServerErrorResponse
+from twisted.protocols import loopback
+from twisted.internet import reactor, defer, error, protocol, interfaces
+from twisted.python import log
+
+from twisted.trial import unittest
+from twisted.test.proto_helpers import StringTransport
+from twisted.protocols import basic
+
+from twisted.mail.test import pop3testserver
+
+try:
+ from twisted.test.ssl_helpers import ClientTLSContext, ServerTLSContext
+except ImportError:
+ ClientTLSContext = ServerTLSContext = None
+
+
+class StringTransportWithConnectionLosing(StringTransport):
+ def loseConnection(self):
+ self.protocol.connectionLost(error.ConnectionDone())
+
+
+capCache = {"TOP": None, "LOGIN-DELAY": "180", "UIDL": None, \
+ "STLS": None, "USER": None, "SASL": "LOGIN"}
+def setUp(greet=True):
+ p = POP3Client()
+
+ # Skip the CAPA login will issue if it doesn't already have a
+ # capability cache
+ p._capCache = capCache
+
+ t = StringTransportWithConnectionLosing()
+ t.protocol = p
+ p.makeConnection(t)
+
+ if greet:
+ p.dataReceived('+OK Hello!\r\n')
+
+ return p, t
+
+def strip(f):
+ return lambda result, f=f: f()
+
+class POP3ClientLoginTestCase(unittest.TestCase):
+ def testNegativeGreeting(self):
+ p, t = setUp(greet=False)
+ p.allowInsecureLogin = True
+ d = p.login("username", "password")
+ p.dataReceived('-ERR Offline for maintenance\r\n')
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "Offline for maintenance"))
+
+
+ def testOkUser(self):
+ p, t = setUp()
+ d = p.user("username")
+ self.assertEquals(t.value(), "USER username\r\n")
+ p.dataReceived("+OK send password\r\n")
+ return d.addCallback(self.assertEqual, "send password")
+
+ def testBadUser(self):
+ p, t = setUp()
+ d = p.user("username")
+ self.assertEquals(t.value(), "USER username\r\n")
+ p.dataReceived("-ERR account suspended\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "account suspended"))
+
+ def testOkPass(self):
+ p, t = setUp()
+ d = p.password("password")
+ self.assertEquals(t.value(), "PASS password\r\n")
+ p.dataReceived("+OK you're in!\r\n")
+ return d.addCallback(self.assertEqual, "you're in!")
+
+ def testBadPass(self):
+ p, t = setUp()
+ d = p.password("password")
+ self.assertEquals(t.value(), "PASS password\r\n")
+ p.dataReceived("-ERR go away\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "go away"))
+
+ def testOkLogin(self):
+ p, t = setUp()
+ p.allowInsecureLogin = True
+ d = p.login("username", "password")
+ self.assertEquals(t.value(), "USER username\r\n")
+ p.dataReceived("+OK go ahead\r\n")
+ self.assertEquals(t.value(), "USER username\r\nPASS password\r\n")
+ p.dataReceived("+OK password accepted\r\n")
+ return d.addCallback(self.assertEqual, "password accepted")
+
+ def testBadPasswordLogin(self):
+ p, t = setUp()
+ p.allowInsecureLogin = True
+ d = p.login("username", "password")
+ self.assertEquals(t.value(), "USER username\r\n")
+ p.dataReceived("+OK waiting on you\r\n")
+ self.assertEquals(t.value(), "USER username\r\nPASS password\r\n")
+ p.dataReceived("-ERR bogus login\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "bogus login"))
+
+ def testBadUsernameLogin(self):
+ p, t = setUp()
+ p.allowInsecureLogin = True
+ d = p.login("username", "password")
+ self.assertEquals(t.value(), "USER username\r\n")
+ p.dataReceived("-ERR bogus login\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "bogus login"))
+
+ def testServerGreeting(self):
+ p, t = setUp(greet=False)
+ p.dataReceived("+OK lalala this has no challenge\r\n")
+ self.assertEquals(p.serverChallenge, None)
+
+ def testServerGreetingWithChallenge(self):
+ p, t = setUp(greet=False)
+ p.dataReceived("+OK <here is the challenge>\r\n")
+ self.assertEquals(p.serverChallenge, "<here is the challenge>")
+
+ def testAPOP(self):
+ p, t = setUp(greet=False)
+ p.dataReceived("+OK <challenge string goes here>\r\n")
+ d = p.login("username", "password")
+ self.assertEquals(t.value(), "APOP username f34f1e464d0d7927607753129cabe39a\r\n")
+ p.dataReceived("+OK Welcome!\r\n")
+ return d.addCallback(self.assertEqual, "Welcome!")
+
+ def testInsecureLoginRaisesException(self):
+ p, t = setUp(greet=False)
+ p.dataReceived("+OK Howdy\r\n")
+ d = p.login("username", "password")
+ self.failIf(t.value())
+ return self.assertFailure(
+ d, InsecureAuthenticationDisallowed)
+
+
+ def testSSLTransportConsideredSecure(self):
+ """
+ If a server doesn't offer APOP but the transport is secured using
+ SSL or TLS, a plaintext login should be allowed, not rejected with
+ an InsecureAuthenticationDisallowed exception.
+ """
+ p, t = setUp(greet=False)
+ directlyProvides(t, interfaces.ISSLTransport)
+ p.dataReceived("+OK Howdy\r\n")
+ d = p.login("username", "password")
+ self.assertEquals(t.value(), "USER username\r\n")
+ t.clear()
+ p.dataReceived("+OK\r\n")
+ self.assertEquals(t.value(), "PASS password\r\n")
+ p.dataReceived("+OK\r\n")
+ return d
+
+
+
+class ListConsumer:
+ def __init__(self):
+ self.data = {}
+
+ def consume(self, (item, value)):
+ self.data.setdefault(item, []).append(value)
+
+class MessageConsumer:
+ def __init__(self):
+ self.data = []
+
+ def consume(self, line):
+ self.data.append(line)
+
+class POP3ClientListTestCase(unittest.TestCase):
+ def testListSize(self):
+ p, t = setUp()
+ d = p.listSize()
+ self.assertEquals(t.value(), "LIST\r\n")
+ p.dataReceived("+OK Here it comes\r\n")
+ p.dataReceived("1 3\r\n2 2\r\n3 1\r\n.\r\n")
+ return d.addCallback(self.assertEqual, [3, 2, 1])
+
+ def testListSizeWithConsumer(self):
+ p, t = setUp()
+ c = ListConsumer()
+ f = c.consume
+ d = p.listSize(f)
+ self.assertEquals(t.value(), "LIST\r\n")
+ p.dataReceived("+OK Here it comes\r\n")
+ p.dataReceived("1 3\r\n2 2\r\n3 1\r\n")
+ self.assertEquals(c.data, {0: [3], 1: [2], 2: [1]})
+ p.dataReceived("5 3\r\n6 2\r\n7 1\r\n")
+ self.assertEquals(c.data, {0: [3], 1: [2], 2: [1], 4: [3], 5: [2], 6: [1]})
+ p.dataReceived(".\r\n")
+ return d.addCallback(self.assertIdentical, f)
+
+ def testFailedListSize(self):
+ p, t = setUp()
+ d = p.listSize()
+ self.assertEquals(t.value(), "LIST\r\n")
+ p.dataReceived("-ERR Fatal doom server exploded\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "Fatal doom server exploded"))
+
+ def testListUID(self):
+ p, t = setUp()
+ d = p.listUID()
+ self.assertEquals(t.value(), "UIDL\r\n")
+ p.dataReceived("+OK Here it comes\r\n")
+ p.dataReceived("1 abc\r\n2 def\r\n3 ghi\r\n.\r\n")
+ return d.addCallback(self.assertEqual, ["abc", "def", "ghi"])
+
+ def testListUIDWithConsumer(self):
+ p, t = setUp()
+ c = ListConsumer()
+ f = c.consume
+ d = p.listUID(f)
+ self.assertEquals(t.value(), "UIDL\r\n")
+ p.dataReceived("+OK Here it comes\r\n")
+ p.dataReceived("1 xyz\r\n2 abc\r\n5 mno\r\n")
+ self.assertEquals(c.data, {0: ["xyz"], 1: ["abc"], 4: ["mno"]})
+ p.dataReceived(".\r\n")
+ return d.addCallback(self.assertIdentical, f)
+
+ def testFailedListUID(self):
+ p, t = setUp()
+ d = p.listUID()
+ self.assertEquals(t.value(), "UIDL\r\n")
+ p.dataReceived("-ERR Fatal doom server exploded\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "Fatal doom server exploded"))
+
+class POP3ClientMessageTestCase(unittest.TestCase):
+ def testRetrieve(self):
+ p, t = setUp()
+ d = p.retrieve(7)
+ self.assertEquals(t.value(), "RETR 8\r\n")
+ p.dataReceived("+OK Message incoming\r\n")
+ p.dataReceived("La la la here is message text\r\n")
+ p.dataReceived("..Further message text tra la la\r\n")
+ p.dataReceived(".\r\n")
+ return d.addCallback(
+ self.assertEqual,
+ ["La la la here is message text",
+ ".Further message text tra la la"])
+
+ def testRetrieveWithConsumer(self):
+ p, t = setUp()
+ c = MessageConsumer()
+ f = c.consume
+ d = p.retrieve(7, f)
+ self.assertEquals(t.value(), "RETR 8\r\n")
+ p.dataReceived("+OK Message incoming\r\n")
+ p.dataReceived("La la la here is message text\r\n")
+ p.dataReceived("..Further message text\r\n.\r\n")
+ return d.addCallback(self._cbTestRetrieveWithConsumer, f, c)
+
+ def _cbTestRetrieveWithConsumer(self, result, f, c):
+ self.assertIdentical(result, f)
+ self.assertEquals(c.data, ["La la la here is message text",
+ ".Further message text"])
+
+ def testPartialRetrieve(self):
+ p, t = setUp()
+ d = p.retrieve(7, lines=2)
+ self.assertEquals(t.value(), "TOP 8 2\r\n")
+ p.dataReceived("+OK 2 lines on the way\r\n")
+ p.dataReceived("Line the first! Woop\r\n")
+ p.dataReceived("Line the last! Bye\r\n")
+ p.dataReceived(".\r\n")
+ return d.addCallback(
+ self.assertEqual,
+ ["Line the first! Woop",
+ "Line the last! Bye"])
+
+ def testPartialRetrieveWithConsumer(self):
+ p, t = setUp()
+ c = MessageConsumer()
+ f = c.consume
+ d = p.retrieve(7, f, lines=2)
+ self.assertEquals(t.value(), "TOP 8 2\r\n")
+ p.dataReceived("+OK 2 lines on the way\r\n")
+ p.dataReceived("Line the first! Woop\r\n")
+ p.dataReceived("Line the last! Bye\r\n")
+ p.dataReceived(".\r\n")
+ return d.addCallback(self._cbTestPartialRetrieveWithConsumer, f, c)
+
+ def _cbTestPartialRetrieveWithConsumer(self, result, f, c):
+ self.assertIdentical(result, f)
+ self.assertEquals(c.data, ["Line the first! Woop",
+ "Line the last! Bye"])
+
+ def testFailedRetrieve(self):
+ p, t = setUp()
+ d = p.retrieve(0)
+ self.assertEquals(t.value(), "RETR 1\r\n")
+ p.dataReceived("-ERR Fatal doom server exploded\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "Fatal doom server exploded"))
+
+
+ def test_concurrentRetrieves(self):
+ """
+ Issue three retrieve calls immediately without waiting for any to
+ succeed and make sure they all do succeed eventually.
+ """
+ p, t = setUp()
+ messages = [
+ p.retrieve(i).addCallback(
+ self.assertEquals,
+ ["First line of %d." % (i + 1,),
+ "Second line of %d." % (i + 1,)])
+ for i
+ in range(3)]
+
+ for i in range(1, 4):
+ self.assertEquals(t.value(), "RETR %d\r\n" % (i,))
+ t.clear()
+ p.dataReceived("+OK 2 lines on the way\r\n")
+ p.dataReceived("First line of %d.\r\n" % (i,))
+ p.dataReceived("Second line of %d.\r\n" % (i,))
+ self.assertEquals(t.value(), "")
+ p.dataReceived(".\r\n")
+
+ return defer.DeferredList(messages, fireOnOneErrback=True)
+
+
+
+class POP3ClientMiscTestCase(unittest.TestCase):
+ def testCapability(self):
+ p, t = setUp()
+ d = p.capabilities(useCache=0)
+ self.assertEquals(t.value(), "CAPA\r\n")
+ p.dataReceived("+OK Capabilities on the way\r\n")
+ p.dataReceived("X\r\nY\r\nZ\r\nA 1 2 3\r\nB 1 2\r\nC 1\r\n.\r\n")
+ return d.addCallback(
+ self.assertEqual,
+ {"X": None, "Y": None, "Z": None,
+ "A": ["1", "2", "3"],
+ "B": ["1", "2"],
+ "C": ["1"]})
+
+ def testCapabilityError(self):
+ p, t = setUp()
+ d = p.capabilities(useCache=0)
+ self.assertEquals(t.value(), "CAPA\r\n")
+ p.dataReceived("-ERR This server is lame!\r\n")
+ return d.addCallback(self.assertEquals, {})
+
+ def testStat(self):
+ p, t = setUp()
+ d = p.stat()
+ self.assertEquals(t.value(), "STAT\r\n")
+ p.dataReceived("+OK 1 1212\r\n")
+ return d.addCallback(self.assertEqual, (1, 1212))
+
+ def testStatError(self):
+ p, t = setUp()
+ d = p.stat()
+ self.assertEquals(t.value(), "STAT\r\n")
+ p.dataReceived("-ERR This server is lame!\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "This server is lame!"))
+
+ def testNoop(self):
+ p, t = setUp()
+ d = p.noop()
+ self.assertEquals(t.value(), "NOOP\r\n")
+ p.dataReceived("+OK No-op to you too!\r\n")
+ return d.addCallback(self.assertEqual, "No-op to you too!")
+
+ def testNoopError(self):
+ p, t = setUp()
+ d = p.noop()
+ self.assertEquals(t.value(), "NOOP\r\n")
+ p.dataReceived("-ERR This server is lame!\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "This server is lame!"))
+
+ def testRset(self):
+ p, t = setUp()
+ d = p.reset()
+ self.assertEquals(t.value(), "RSET\r\n")
+ p.dataReceived("+OK Reset state\r\n")
+ return d.addCallback(self.assertEqual, "Reset state")
+
+ def testRsetError(self):
+ p, t = setUp()
+ d = p.reset()
+ self.assertEquals(t.value(), "RSET\r\n")
+ p.dataReceived("-ERR This server is lame!\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "This server is lame!"))
+
+ def testDelete(self):
+ p, t = setUp()
+ d = p.delete(3)
+ self.assertEquals(t.value(), "DELE 4\r\n")
+ p.dataReceived("+OK Hasta la vista\r\n")
+ return d.addCallback(self.assertEqual, "Hasta la vista")
+
+ def testDeleteError(self):
+ p, t = setUp()
+ d = p.delete(3)
+ self.assertEquals(t.value(), "DELE 4\r\n")
+ p.dataReceived("-ERR Winner is not you.\r\n")
+ return self.assertFailure(
+ d, ServerErrorResponse).addCallback(
+ lambda exc: self.assertEquals(exc.args[0], "Winner is not you."))
+
+
+class SimpleClient(POP3Client):
+ def __init__(self, deferred, contextFactory = None):
+ self.deferred = deferred
+ self.allowInsecureLogin = True
+
+ def serverGreeting(self, challenge):
+ self.deferred.callback(None)
+
+class POP3HelperMixin:
+ serverCTX = None
+ clientCTX = None
+
+ def setUp(self):
+ d = defer.Deferred()
+ self.server = pop3testserver.POP3TestServer(contextFactory=self.serverCTX)
+ self.client = SimpleClient(d, contextFactory=self.clientCTX)
+ self.client.timeout = 30
+ self.connected = d
+
+ def tearDown(self):
+ del self.server
+ del self.client
+ del self.connected
+
+ def _cbStopClient(self, ignore):
+ self.client.transport.loseConnection()
+
+ def _ebGeneral(self, failure):
+ self.client.transport.loseConnection()
+ self.server.transport.loseConnection()
+ return failure
+
+ def loopback(self):
+ return loopback.loopbackTCP(self.server, self.client, noisy=False)
+
+
+class TLSServerFactory(protocol.ServerFactory):
+ class protocol(basic.LineReceiver):
+ context = None
+ output = []
+ def connectionMade(self):
+ self.factory.input = []
+ self.output = self.output[:]
+ map(self.sendLine, self.output.pop(0))
+ def lineReceived(self, line):
+ self.factory.input.append(line)
+ map(self.sendLine, self.output.pop(0))
+ if line == 'STLS':
+ self.transport.startTLS(self.context)
+
+
+class POP3TLSTestCase(unittest.TestCase):
+ def testStartTLS(self):
+ sf = TLSServerFactory()
+ sf.protocol.output = [
+ ['+OK'], # Server greeting
+ ['+OK', 'STLS', '.'], # CAPA response
+ ['+OK'], # STLS response
+ ['+OK', '.'], # Second CAPA response
+ ['+OK'] # QUIT response
+ ]
+ sf.protocol.context = ServerTLSContext()
+ port = reactor.listenTCP(0, sf, interface='127.0.0.1')
+ H = port.getHost().host
+ P = port.getHost().port
+
+ cp = SimpleClient(defer.Deferred(), ClientTLSContext())
+ cf = protocol.ClientFactory()
+ cf.protocol = lambda: cp
+
+ conn = reactor.connectTCP(H, P, cf)
+
+ def cbConnected(ignored):
+ log.msg("Connected to server; starting TLS")
+ return cp.startTLS()
+
+ def cbStartedTLS(ignored):
+ log.msg("Started TLS; disconnecting")
+ return cp.quit()
+
+ def cbDisconnected(ign):
+ log.msg("Disconnected; asserting correct input received")
+ self.assertEquals(
+ sf.input,
+ ['CAPA', 'STLS', 'CAPA', 'QUIT'])
+
+ def cleanup(result):
+ log.msg("Asserted correct input; disconnecting client and shutting down server")
+ conn.disconnect()
+
+ def cbShutdown(ignored):
+ log.msg("Shut down server")
+ return result
+
+ return defer.maybeDeferred(port.stopListening).addCallback(cbShutdown)
+
+ cp.deferred.addCallback(cbConnected)
+ cp.deferred.addCallback(cbStartedTLS)
+ cp.deferred.addCallback(cbDisconnected)
+ cp.deferred.addBoth(cleanup)
+
+ return cp.deferred
+
+
+class POP3TimeoutTestCase(POP3HelperMixin, unittest.TestCase):
+ def testTimeout(self):
+ def login():
+ d = self.client.login('test', 'twisted')
+ d.addCallback(loggedIn)
+ d.addErrback(timedOut)
+ return d
+
+ def loggedIn(result):
+ self.fail("Successfully logged in!? Impossible!")
+
+
+ def timedOut(failure):
+ failure.trap(error.TimeoutError)
+ self._cbStopClient(None)
+
+ def quit():
+ return self.client.quit()
+
+ self.client.timeout = 0.01
+
+ # Tell the server to not return a response to client. This
+ # will trigger a timeout.
+ pop3testserver.TIMEOUT_RESPONSE = True
+
+ methods = [login, quit]
+ map(self.connected.addCallback, map(strip, methods))
+ self.connected.addCallback(self._cbStopClient)
+ self.connected.addErrback(self._ebGeneral)
+ return self.loopback()
+
+
+if ClientTLSContext is None:
+ for case in (POP3TLSTestCase,):
+ case.skip = "OpenSSL not present"
+elif interfaces.IReactorSSL(reactor, None) is None:
+ for case in (POP3TLSTestCase,):
+ case.skip = "Reactor doesn't support SSL"
+
diff --git a/vendor/Twisted-10.0.0/twisted/mail/test/test_smtp.py b/vendor/Twisted-10.0.0/twisted/mail/test/test_smtp.py
new file mode 100644
index 0000000000..e1010208b7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/test/test_smtp.py
@@ -0,0 +1,1530 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for twisted.mail.smtp module.
+"""
+
+from zope.interface import implements
+
+from twisted.python.util import LineLog
+from twisted.trial import unittest, util
+from twisted.protocols import basic, loopback
+from twisted.mail import smtp
+from twisted.internet import defer, protocol, reactor, interfaces
+from twisted.internet import address, error, task
+from twisted.test.proto_helpers import StringTransport
+
+from twisted import cred
+import twisted.cred.error
+import twisted.cred.portal
+import twisted.cred.checkers
+import twisted.cred.credentials
+
+from twisted.cred.portal import IRealm, Portal
+from twisted.cred.checkers import ICredentialsChecker, AllowAnonymousAccess
+from twisted.cred.credentials import IAnonymous
+from twisted.cred.error import UnauthorizedLogin
+
+from twisted.mail import imap4
+
+
+try:
+ from twisted.test.ssl_helpers import ClientTLSContext, ServerTLSContext
+except ImportError:
+ ClientTLSContext = ServerTLSContext = None
+
+import re
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+
+def spameater(*spam, **eggs):
+ return None
+
+
+
+class BrokenMessage(object):
+ """
+ L{BrokenMessage} is an L{IMessage} which raises an unexpected exception
+ from its C{eomReceived} method. This is useful for creating a server which
+ can be used to test client retry behavior.
+ """
+ implements(smtp.IMessage)
+
+ def __init__(self, user):
+ pass
+
+
+ def lineReceived(self, line):
+ pass
+
+
+ def eomReceived(self):
+ raise RuntimeError("Some problem, delivery is failing.")
+
+
+ def connectionLost(self):
+ pass
+
+
+
+class DummyMessage(object):
+ """
+ L{BrokenMessage} is an L{IMessage} which saves the message delivered to it
+ to its domain object.
+
+ @ivar domain: A L{DummyDomain} which will be used to store the message once
+ it is received.
+ """
+ def __init__(self, domain, user):
+ self.domain = domain
+ self.user = user
+ self.buffer = []
+
+
+ def lineReceived(self, line):
+ # Throw away the generated Received: header
+ if not re.match('Received: From yyy.com \(\[.*\]\) by localhost;', line):
+ self.buffer.append(line)
+
+
+ def eomReceived(self):
+ message = '\n'.join(self.buffer) + '\n'
+ self.domain.messages[self.user.dest.local].append(message)
+ deferred = defer.Deferred()
+ deferred.callback("saved")
+ return deferred
+
+
+
+class DummyDomain(object):
+ """
+ L{DummyDomain} is an L{IDomain} which keeps track of messages delivered to
+ it in memory.
+ """
+ def __init__(self, names):
+ self.messages = {}
+ for name in names:
+ self.messages[name] = []
+
+
+ def exists(self, user):
+ if user.dest.local in self.messages:
+ return defer.succeed(lambda: self.startMessage(user))
+ return defer.fail(smtp.SMTPBadRcpt(user))
+
+
+ def startMessage(self, user):
+ return DummyMessage(self, user)
+
+
+
+class SMTPTestCase(unittest.TestCase):
+
+ messages = [('foo@bar.com', ['foo@baz.com', 'qux@baz.com'], '''\
+Subject: urgent\015
+\015
+Someone set up us the bomb!\015
+''')]
+
+ mbox = {'foo': ['Subject: urgent\n\nSomeone set up us the bomb!\n']}
+
+ def setUp(self):
+ """
+ Create an in-memory mail domain to which messages may be delivered by
+ tests and create a factory and transport to do the delivering.
+ """
+ self.factory = smtp.SMTPFactory()
+ self.factory.domains = {}
+ self.factory.domains['baz.com'] = DummyDomain(['foo'])
+ self.transport = StringTransport()
+
+
+ def testMessages(self):
+ from twisted.mail import protocols
+ protocol = protocols.DomainSMTP()
+ protocol.service = self.factory
+ protocol.factory = self.factory
+ protocol.receivedHeader = spameater
+ protocol.makeConnection(self.transport)
+ protocol.lineReceived('HELO yyy.com')
+ for message in self.messages:
+ protocol.lineReceived('MAIL FROM:<%s>' % message[0])
+ for target in message[1]:
+ protocol.lineReceived('RCPT TO:<%s>' % target)
+ protocol.lineReceived('DATA')
+ protocol.dataReceived(message[2])
+ protocol.lineReceived('.')
+ protocol.lineReceived('QUIT')
+ if self.mbox != self.factory.domains['baz.com'].messages:
+ raise AssertionError(self.factory.domains['baz.com'].messages)
+ protocol.setTimeout(None)
+
+ testMessages.suppress = [util.suppress(message='DomainSMTP', category=DeprecationWarning)]
+
+mail = '''\
+Subject: hello
+
+Goodbye
+'''
+
+class MyClient:
+ def __init__(self, messageInfo=None):
+ if messageInfo is None:
+ messageInfo = (
+ 'moshez@foo.bar', ['moshez@foo.bar'], StringIO(mail))
+ self._sender = messageInfo[0]
+ self._recipient = messageInfo[1]
+ self._data = messageInfo[2]
+
+
+ def getMailFrom(self):
+ return self._sender
+
+
+ def getMailTo(self):
+ return self._recipient
+
+
+ def getMailData(self):
+ return self._data
+
+
+ def sendError(self, exc):
+ self._error = exc
+
+
+ def sentMail(self, code, resp, numOk, addresses, log):
+ # Prevent another mail from being sent.
+ self._sender = None
+ self._recipient = None
+ self._data = None
+
+
+
+class MySMTPClient(MyClient, smtp.SMTPClient):
+ def __init__(self, messageInfo=None):
+ smtp.SMTPClient.__init__(self, 'foo.baz')
+ MyClient.__init__(self, messageInfo)
+
+class MyESMTPClient(MyClient, smtp.ESMTPClient):
+ def __init__(self, secret = '', contextFactory = None):
+ smtp.ESMTPClient.__init__(self, secret, contextFactory, 'foo.baz')
+ MyClient.__init__(self)
+
+class LoopbackMixin:
+ def loopback(self, server, client):
+ return loopback.loopbackTCP(server, client)
+
+class LoopbackTestCase(LoopbackMixin):
+ def testMessages(self):
+ factory = smtp.SMTPFactory()
+ factory.domains = {}
+ factory.domains['foo.bar'] = DummyDomain(['moshez'])
+ from twisted.mail.protocols import DomainSMTP
+ protocol = DomainSMTP()
+ protocol.service = factory
+ protocol.factory = factory
+ clientProtocol = self.clientClass()
+ return self.loopback(protocol, clientProtocol)
+ testMessages.suppress = [util.suppress(message='DomainSMTP', category=DeprecationWarning)]
+
+class LoopbackSMTPTestCase(LoopbackTestCase, unittest.TestCase):
+ clientClass = MySMTPClient
+
+class LoopbackESMTPTestCase(LoopbackTestCase, unittest.TestCase):
+ clientClass = MyESMTPClient
+
+
+class FakeSMTPServer(basic.LineReceiver):
+
+ clientData = [
+ '220 hello', '250 nice to meet you',
+ '250 great', '250 great', '354 go on, lad'
+ ]
+
+ def connectionMade(self):
+ self.buffer = []
+ self.clientData = self.clientData[:]
+ self.clientData.reverse()
+ self.sendLine(self.clientData.pop())
+
+ def lineReceived(self, line):
+ self.buffer.append(line)
+ if line == "QUIT":
+ self.transport.write("221 see ya around\r\n")
+ self.transport.loseConnection()
+ elif line == ".":
+ self.transport.write("250 gotcha\r\n")
+ elif line == "RSET":
+ self.transport.loseConnection()
+
+ if self.clientData:
+ self.sendLine(self.clientData.pop())
+
+
+class SMTPClientTestCase(unittest.TestCase, LoopbackMixin):
+ """
+ Tests for L{smtp.SMTPClient}.
+ """
+
+ def test_timeoutConnection(self):
+ """
+ L{smtp.SMTPClient.timeoutConnection} calls the C{sendError} hook with a
+ fatal L{SMTPTimeoutError} with the current line log.
+ """
+ error = []
+ client = MySMTPClient()
+ client.sendError = error.append
+ client.makeConnection(StringTransport())
+ client.lineReceived("220 hello")
+ client.timeoutConnection()
+ self.assertIsInstance(error[0], smtp.SMTPTimeoutError)
+ self.assertTrue(error[0].isFatal)
+ self.assertEqual(
+ str(error[0]),
+ "Timeout waiting for SMTP server response\n"
+ "<<< 220 hello\n"
+ ">>> HELO foo.baz\n")
+
+
+ expected_output = [
+ 'HELO foo.baz', 'MAIL FROM:<moshez@foo.bar>',
+ 'RCPT TO:<moshez@foo.bar>', 'DATA',
+ 'Subject: hello', '', 'Goodbye', '.', 'RSET'
+ ]
+
+ def test_messages(self):
+ """
+ L{smtp.SMTPClient} sends I{HELO}, I{MAIL FROM}, I{RCPT TO}, and I{DATA}
+ commands based on the return values of its C{getMailFrom},
+ C{getMailTo}, and C{getMailData} methods.
+ """
+ client = MySMTPClient()
+ server = FakeSMTPServer()
+ d = self.loopback(server, client)
+ d.addCallback(lambda x :
+ self.assertEquals(server.buffer, self.expected_output))
+ return d
+
+
+ def test_transferError(self):
+ """
+ If there is an error while producing the message body to the
+ connection, the C{sendError} callback is invoked.
+ """
+ client = MySMTPClient(
+ ('alice@example.com', ['bob@example.com'], StringIO("foo")))
+ transport = StringTransport()
+ client.makeConnection(transport)
+ client.dataReceived(
+ '220 Ok\r\n' # Greeting
+ '250 Ok\r\n' # EHLO response
+ '250 Ok\r\n' # MAIL FROM response
+ '250 Ok\r\n' # RCPT TO response
+ '354 Ok\r\n' # DATA response
+ )
+
+ # Sanity check - a pull producer should be registered now.
+ self.assertNotIdentical(transport.producer, None)
+ self.assertFalse(transport.streaming)
+
+ # Now stop the producer prematurely, meaning the message was not sent.
+ transport.producer.stopProducing()
+
+ # The sendError hook should have been invoked as a result.
+ self.assertIsInstance(client._error, Exception)
+
+
+ def test_sendFatalError(self):
+ """
+ If L{smtp.SMTPClient.sendError} is called with an L{SMTPClientError}
+ which is fatal, it disconnects its transport without writing anything
+ more to it.
+ """
+ client = smtp.SMTPClient(None)
+ transport = StringTransport()
+ client.makeConnection(transport)
+ client.sendError(smtp.SMTPClientError(123, "foo", isFatal=True))
+ self.assertEqual(transport.value(), "")
+ self.assertTrue(transport.disconnecting)
+
+
+ def test_sendNonFatalError(self):
+ """
+ If L{smtp.SMTPClient.sendError} is called with an L{SMTPClientError}
+ which is not fatal, it sends C{"QUIT"} and waits for the server to
+ close the connection.
+ """
+ client = smtp.SMTPClient(None)
+ transport = StringTransport()
+ client.makeConnection(transport)
+ client.sendError(smtp.SMTPClientError(123, "foo", isFatal=False))
+ self.assertEqual(transport.value(), "QUIT\r\n")
+ self.assertFalse(transport.disconnecting)
+
+
+ def test_sendOtherError(self):
+ """
+ If L{smtp.SMTPClient.sendError} is called with an exception which is
+ not an L{SMTPClientError}, it disconnects its transport without
+ writing anything more to it.
+ """
+ client = smtp.SMTPClient(None)
+ transport = StringTransport()
+ client.makeConnection(transport)
+ client.sendError(Exception("foo"))
+ self.assertEqual(transport.value(), "")
+ self.assertTrue(transport.disconnecting)
+
+
+
+class DummySMTPMessage:
+
+ def __init__(self, protocol, users):
+ self.protocol = protocol
+ self.users = users
+ self.buffer = []
+
+ def lineReceived(self, line):
+ self.buffer.append(line)
+
+ def eomReceived(self):
+ message = '\n'.join(self.buffer) + '\n'
+ helo, origin = self.users[0].helo[0], str(self.users[0].orig)
+ recipients = []
+ for user in self.users:
+ recipients.append(str(user))
+ self.protocol.message[tuple(recipients)] = (helo, origin, recipients, message)
+ return defer.succeed("saved")
+
+
+
+class DummyProto:
+ def connectionMade(self):
+ self.dummyMixinBase.connectionMade(self)
+ self.message = {}
+
+ def startMessage(self, users):
+ return DummySMTPMessage(self, users)
+
+ def receivedHeader(*spam):
+ return None
+
+ def validateTo(self, user):
+ self.delivery = SimpleDelivery(None)
+ return lambda: self.startMessage([user])
+
+ def validateFrom(self, helo, origin):
+ return origin
+
+
+
+class DummySMTP(DummyProto, smtp.SMTP):
+ dummyMixinBase = smtp.SMTP
+
+class DummyESMTP(DummyProto, smtp.ESMTP):
+ dummyMixinBase = smtp.ESMTP
+
+class AnotherTestCase:
+ serverClass = None
+ clientClass = None
+
+ messages = [ ('foo.com', 'moshez@foo.com', ['moshez@bar.com'],
+ 'moshez@foo.com', ['moshez@bar.com'], '''\
+From: Moshe
+To: Moshe
+
+Hi,
+how are you?
+'''),
+ ('foo.com', 'tttt@rrr.com', ['uuu@ooo', 'yyy@eee'],
+ 'tttt@rrr.com', ['uuu@ooo', 'yyy@eee'], '''\
+Subject: pass
+
+..rrrr..
+'''),
+ ('foo.com', '@this,@is,@ignored:foo@bar.com',
+ ['@ignore,@this,@too:bar@foo.com'],
+ 'foo@bar.com', ['bar@foo.com'], '''\
+Subject: apa
+To: foo
+
+123
+.
+456
+'''),
+ ]
+
+ data = [
+ ('', '220.*\r\n$', None, None),
+ ('HELO foo.com\r\n', '250.*\r\n$', None, None),
+ ('RSET\r\n', '250.*\r\n$', None, None),
+ ]
+ for helo_, from_, to_, realfrom, realto, msg in messages:
+ data.append(('MAIL FROM:<%s>\r\n' % from_, '250.*\r\n',
+ None, None))
+ for rcpt in to_:
+ data.append(('RCPT TO:<%s>\r\n' % rcpt, '250.*\r\n',
+ None, None))
+
+ data.append(('DATA\r\n','354.*\r\n',
+ msg, ('250.*\r\n',
+ (helo_, realfrom, realto, msg))))
+
+
+ def test_buffer(self):
+ """
+ Exercise a lot of the SMTP client code. This is a "shotgun" style unit
+ test. It does a lot of things and hopes that something will go really
+ wrong if it is going to go wrong. This test should be replaced with a
+ suite of nicer tests.
+ """
+ transport = StringTransport()
+ a = self.serverClass()
+ class fooFactory:
+ domain = 'foo.com'
+
+ a.factory = fooFactory()
+ a.makeConnection(transport)
+ for (send, expect, msg, msgexpect) in self.data:
+ if send:
+ a.dataReceived(send)
+ data = transport.value()
+ transport.clear()
+ if not re.match(expect, data):
+ raise AssertionError, (send, expect, data)
+ if data[:3] == '354':
+ for line in msg.splitlines():
+ if line and line[0] == '.':
+ line = '.' + line
+ a.dataReceived(line + '\r\n')
+ a.dataReceived('.\r\n')
+ # Special case for DATA. Now we want a 250, and then
+ # we compare the messages
+ data = transport.value()
+ transport.clear()
+ resp, msgdata = msgexpect
+ if not re.match(resp, data):
+ raise AssertionError, (resp, data)
+ for recip in msgdata[2]:
+ expected = list(msgdata[:])
+ expected[2] = [recip]
+ self.assertEquals(
+ a.message[(recip,)],
+ tuple(expected)
+ )
+ a.setTimeout(None)
+
+
+class AnotherESMTPTestCase(AnotherTestCase, unittest.TestCase):
+ serverClass = DummyESMTP
+ clientClass = MyESMTPClient
+
+class AnotherSMTPTestCase(AnotherTestCase, unittest.TestCase):
+ serverClass = DummySMTP
+ clientClass = MySMTPClient
+
+
+
+class DummyChecker:
+ implements(cred.checkers.ICredentialsChecker)
+
+ users = {
+ 'testuser': 'testpassword'
+ }
+
+ credentialInterfaces = (cred.credentials.IUsernamePassword,
+ cred.credentials.IUsernameHashedPassword)
+
+ def requestAvatarId(self, credentials):
+ return defer.maybeDeferred(
+ credentials.checkPassword, self.users[credentials.username]
+ ).addCallback(self._cbCheck, credentials.username)
+
+ def _cbCheck(self, result, username):
+ if result:
+ return username
+ raise cred.error.UnauthorizedLogin()
+
+
+
+class SimpleDelivery(object):
+ """
+ L{SimpleDelivery} is a message delivery factory with no interesting
+ behavior.
+ """
+ implements(smtp.IMessageDelivery)
+
+ def __init__(self, messageFactory):
+ self._messageFactory = messageFactory
+
+
+ def receivedHeader(self, helo, origin, recipients):
+ return None
+
+
+ def validateFrom(self, helo, origin):
+ return origin
+
+
+ def validateTo(self, user):
+ return lambda: self._messageFactory(user)
+
+
+
+class DummyRealm:
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ return smtp.IMessageDelivery, SimpleDelivery(None), lambda: None
+
+
+
+class AuthTestCase(unittest.TestCase, LoopbackMixin):
+ def test_crammd5Auth(self):
+ """
+ L{ESMTPClient} can authenticate using the I{CRAM-MD5} SASL mechanism.
+
+ @see: U{http://tools.ietf.org/html/rfc2195}
+ """
+ realm = DummyRealm()
+ p = cred.portal.Portal(realm)
+ p.registerChecker(DummyChecker())
+
+ server = DummyESMTP({'CRAM-MD5': cred.credentials.CramMD5Credentials})
+ server.portal = p
+ client = MyESMTPClient('testpassword')
+
+ cAuth = smtp.CramMD5ClientAuthenticator('testuser')
+ client.registerAuthenticator(cAuth)
+
+ d = self.loopback(server, client)
+ d.addCallback(lambda x : self.assertEquals(server.authenticated, 1))
+ return d
+
+
+ def test_loginAuth(self):
+ """
+ L{ESMTPClient} can authenticate using the I{LOGIN} SASL mechanism.
+
+ @see: U{http://sepp.oetiker.ch/sasl-2.1.19-ds/draft-murchison-sasl-login-00.txt}
+ """
+ realm = DummyRealm()
+ p = cred.portal.Portal(realm)
+ p.registerChecker(DummyChecker())
+
+ server = DummyESMTP({'LOGIN': imap4.LOGINCredentials})
+ server.portal = p
+ client = MyESMTPClient('testpassword')
+
+ cAuth = smtp.LOGINAuthenticator('testuser')
+ client.registerAuthenticator(cAuth)
+
+ d = self.loopback(server, client)
+ d.addCallback(lambda x: self.assertTrue(server.authenticated))
+ return d
+
+
+ def test_loginAgainstWeirdServer(self):
+ """
+ When communicating with a server which implements the I{LOGIN} SASL
+ mechanism using C{"Username:"} as the challenge (rather than C{"User
+ Name\\0"}), L{ESMTPClient} can still authenticate successfully using
+ the I{LOGIN} mechanism.
+ """
+ realm = DummyRealm()
+ p = cred.portal.Portal(realm)
+ p.registerChecker(DummyChecker())
+
+ class WeirdLOGIN(imap4.LOGINCredentials):
+ def __init__(self):
+ imap4.LOGINCredentials.__init__(self)
+ self.challenges[1] = 'Username:'
+
+ server = DummyESMTP({'LOGIN': WeirdLOGIN})
+ server.portal = p
+
+ client = MyESMTPClient('testpassword')
+ cAuth = smtp.LOGINAuthenticator('testuser')
+ client.registerAuthenticator(cAuth)
+
+ d = self.loopback(server, client)
+ d.addCallback(lambda x: self.assertTrue(server.authenticated))
+ return d
+
+
+
+class SMTPHelperTestCase(unittest.TestCase):
+ def testMessageID(self):
+ d = {}
+ for i in range(1000):
+ m = smtp.messageid('testcase')
+ self.failIf(m in d)
+ d[m] = None
+
+ def testQuoteAddr(self):
+ cases = [
+ ['user@host.name', '<user@host.name>'],
+ ['"User Name" <user@host.name>', '<user@host.name>'],
+ [smtp.Address('someguy@someplace'), '<someguy@someplace>'],
+ ['', '<>'],
+ [smtp.Address(''), '<>'],
+ ]
+
+ for (c, e) in cases:
+ self.assertEquals(smtp.quoteaddr(c), e)
+
+ def testUser(self):
+ u = smtp.User('user@host', 'helo.host.name', None, None)
+ self.assertEquals(str(u), 'user@host')
+
+ def testXtextEncoding(self):
+ cases = [
+ ('Hello world', 'Hello+20world'),
+ ('Hello+world', 'Hello+2Bworld'),
+ ('\0\1\2\3\4\5', '+00+01+02+03+04+05'),
+ ('e=mc2@example.com', 'e+3Dmc2@example.com')
+ ]
+
+ for (case, expected) in cases:
+ self.assertEqual(smtp.xtext_encode(case), (expected, len(case)))
+ self.assertEquals(case.encode('xtext'), expected)
+ self.assertEqual(
+ smtp.xtext_decode(expected), (case, len(expected)))
+ self.assertEquals(expected.decode('xtext'), case)
+
+
+ def test_encodeWithErrors(self):
+ """
+ Specifying an error policy to C{unicode.encode} with the
+ I{xtext} codec should produce the same result as not
+ specifying the error policy.
+ """
+ text = u'Hello world'
+ self.assertEqual(
+ smtp.xtext_encode(text, 'strict'),
+ (text.encode('xtext'), len(text)))
+ self.assertEqual(
+ text.encode('xtext', 'strict'),
+ text.encode('xtext'))
+
+
+ def test_decodeWithErrors(self):
+ """
+ Similar to L{test_encodeWithErrors}, but for C{str.decode}.
+ """
+ bytes = 'Hello world'
+ self.assertEqual(
+ smtp._slowXTextDecode(bytes, 'strict'),
+ (bytes.decode('xtext'), len(bytes)))
+ # This might be the same as _slowXTextDecode, but it might also be the
+ # fast version instead.
+ self.assertEqual(
+ smtp.xtext_decode(bytes, 'strict'),
+ (bytes.decode('xtext'), len(bytes)))
+ self.assertEqual(
+ bytes.decode('xtext', 'strict'),
+ bytes.decode('xtext'))
+
+
+
+class NoticeTLSClient(MyESMTPClient):
+ tls = False
+
+ def esmtpState_starttls(self, code, resp):
+ MyESMTPClient.esmtpState_starttls(self, code, resp)
+ self.tls = True
+
+class TLSTestCase(unittest.TestCase, LoopbackMixin):
+ def testTLS(self):
+ clientCTX = ClientTLSContext()
+ serverCTX = ServerTLSContext()
+
+ client = NoticeTLSClient(contextFactory=clientCTX)
+ server = DummyESMTP(contextFactory=serverCTX)
+
+ def check(ignored):
+ self.assertEquals(client.tls, True)
+ self.assertEquals(server.startedTLS, True)
+
+ return self.loopback(server, client).addCallback(check)
+
+if ClientTLSContext is None:
+ for case in (TLSTestCase,):
+ case.skip = "OpenSSL not present"
+
+if not interfaces.IReactorSSL.providedBy(reactor):
+ for case in (TLSTestCase,):
+ case.skip = "Reactor doesn't support SSL"
+
+class EmptyLineTestCase(unittest.TestCase):
+ def test_emptyLineSyntaxError(self):
+ """
+ If L{smtp.SMTP} receives an empty line, it responds with a 500 error
+ response code and a message about a syntax error.
+ """
+ proto = smtp.SMTP()
+ transport = StringTransport()
+ proto.makeConnection(transport)
+ proto.lineReceived('')
+ proto.setTimeout(None)
+
+ out = transport.value().splitlines()
+ self.assertEquals(len(out), 2)
+ self.failUnless(out[0].startswith('220'))
+ self.assertEquals(out[1], "500 Error: bad syntax")
+
+
+
+class TimeoutTestCase(unittest.TestCase, LoopbackMixin):
+ """
+ Check that SMTP client factories correctly use the timeout.
+ """
+
+ def _timeoutTest(self, onDone, clientFactory):
+ """
+ Connect the clientFactory, and check the timeout on the request.
+ """
+ clock = task.Clock()
+ client = clientFactory.buildProtocol(
+ address.IPv4Address('TCP', 'example.net', 25))
+ client.callLater = clock.callLater
+ t = StringTransport()
+ client.makeConnection(t)
+ t.protocol = client
+ def check(ign):
+ self.assertEquals(clock.seconds(), 0.5)
+ d = self.assertFailure(onDone, smtp.SMTPTimeoutError
+ ).addCallback(check)
+ # The first call should not trigger the timeout
+ clock.advance(0.1)
+ # But this one should
+ clock.advance(0.4)
+ return d
+
+
+ def test_SMTPClient(self):
+ """
+ Test timeout for L{smtp.SMTPSenderFactory}: the response L{Deferred}
+ should be errback with a L{smtp.SMTPTimeoutError}.
+ """
+ onDone = defer.Deferred()
+ clientFactory = smtp.SMTPSenderFactory(
+ 'source@address', 'recipient@address',
+ StringIO("Message body"), onDone,
+ retries=0, timeout=0.5)
+ return self._timeoutTest(onDone, clientFactory)
+
+
+ def test_ESMTPClient(self):
+ """
+ Test timeout for L{smtp.ESMTPSenderFactory}: the response L{Deferred}
+ should be errback with a L{smtp.SMTPTimeoutError}.
+ """
+ onDone = defer.Deferred()
+ clientFactory = smtp.ESMTPSenderFactory(
+ 'username', 'password',
+ 'source@address', 'recipient@address',
+ StringIO("Message body"), onDone,
+ retries=0, timeout=0.5)
+ return self._timeoutTest(onDone, clientFactory)
+
+
+ def test_resetTimeoutWhileSending(self):
+ """
+ The timeout is not allowed to expire after the server has accepted a
+ DATA command and the client is actively sending data to it.
+ """
+ class SlowFile:
+ """
+ A file-like which returns one byte from each read call until the
+ specified number of bytes have been returned.
+ """
+ def __init__(self, size):
+ self._size = size
+
+ def read(self, max=None):
+ if self._size:
+ self._size -= 1
+ return 'x'
+ return ''
+
+ failed = []
+ onDone = defer.Deferred()
+ onDone.addErrback(failed.append)
+ clientFactory = smtp.SMTPSenderFactory(
+ 'source@address', 'recipient@address',
+ SlowFile(1), onDone, retries=0, timeout=3)
+ clientFactory.domain = "example.org"
+ clock = task.Clock()
+ client = clientFactory.buildProtocol(
+ address.IPv4Address('TCP', 'example.net', 25))
+ client.callLater = clock.callLater
+ transport = StringTransport()
+ client.makeConnection(transport)
+
+ client.dataReceived(
+ "220 Ok\r\n" # Greet the client
+ "250 Ok\r\n" # Respond to HELO
+ "250 Ok\r\n" # Respond to MAIL FROM
+ "250 Ok\r\n" # Respond to RCPT TO
+ "354 Ok\r\n" # Respond to DATA
+ )
+
+ # Now the client is producing data to the server. Any time
+ # resumeProducing is called on the producer, the timeout should be
+ # extended. First, a sanity check. This test is only written to
+ # handle pull producers.
+ self.assertNotIdentical(transport.producer, None)
+ self.assertFalse(transport.streaming)
+
+ # Now, allow 2 seconds (1 less than the timeout of 3 seconds) to
+ # elapse.
+ clock.advance(2)
+
+ # The timeout has not expired, so the failure should not have happened.
+ self.assertEqual(failed, [])
+
+ # Let some bytes be produced, extending the timeout. Then advance the
+ # clock some more and verify that the timeout still hasn't happened.
+ transport.producer.resumeProducing()
+ clock.advance(2)
+ self.assertEqual(failed, [])
+
+ # The file has been completely produced - the next resume producing
+ # finishes the upload, successfully.
+ transport.producer.resumeProducing()
+ client.dataReceived("250 Ok\r\n")
+ self.assertEqual(failed, [])
+
+ # Verify that the client actually did send the things expected.
+ self.assertEqual(
+ transport.value(),
+ "HELO example.org\r\n"
+ "MAIL FROM:<source@address>\r\n"
+ "RCPT TO:<recipient@address>\r\n"
+ "DATA\r\n"
+ "x\r\n"
+ ".\r\n"
+ # This RSET is just an implementation detail. It's nice, but this
+ # test doesn't really care about it.
+ "RSET\r\n")
+
+
+
+class MultipleDeliveryFactorySMTPServerFactory(protocol.ServerFactory):
+ """
+ L{MultipleDeliveryFactorySMTPServerFactory} creates SMTP server protocol
+ instances with message delivery factory objects supplied to it. Each
+ factory is used for one connection and then discarded. Factories are used
+ in the order they are supplied.
+ """
+ def __init__(self, messageFactories):
+ self._messageFactories = messageFactories
+
+
+ def buildProtocol(self, addr):
+ p = protocol.ServerFactory.buildProtocol(self, addr)
+ p.delivery = SimpleDelivery(self._messageFactories.pop(0))
+ return p
+
+
+
+class SMTPSenderFactoryRetryTestCase(unittest.TestCase):
+ """
+ Tests for the retry behavior of L{smtp.SMTPSenderFactory}.
+ """
+ def test_retryAfterDisconnect(self):
+ """
+ If the protocol created by L{SMTPSenderFactory} loses its connection
+ before receiving confirmation of message delivery, it reconnects and
+ tries to deliver the message again.
+ """
+ recipient = 'alice'
+ message = "some message text"
+ domain = DummyDomain([recipient])
+
+ class CleanSMTP(smtp.SMTP):
+ """
+ An SMTP subclass which ensures that its transport will be
+ disconnected before the test ends.
+ """
+ def makeConnection(innerSelf, transport):
+ self.addCleanup(transport.loseConnection)
+ smtp.SMTP.makeConnection(innerSelf, transport)
+
+ # Create a server which will fail the first message deliver attempt to
+ # it with a 500 and a disconnect, but which will accept a message
+ # delivered over the 2nd connection to it.
+ serverFactory = MultipleDeliveryFactorySMTPServerFactory([
+ BrokenMessage,
+ lambda user: DummyMessage(domain, user)])
+ serverFactory.protocol = CleanSMTP
+ serverPort = reactor.listenTCP(0, serverFactory, interface='127.0.0.1')
+ serverHost = serverPort.getHost()
+ self.addCleanup(serverPort.stopListening)
+
+ # Set up a client to try to deliver a message to the above created
+ # server.
+ sentDeferred = defer.Deferred()
+ clientFactory = smtp.SMTPSenderFactory(
+ "bob@example.org", recipient + "@example.com",
+ StringIO(message), sentDeferred)
+ clientFactory.domain = "example.org"
+ clientConnector = reactor.connectTCP(
+ serverHost.host, serverHost.port, clientFactory)
+ self.addCleanup(clientConnector.disconnect)
+
+ def cbSent(ignored):
+ """
+ Verify that the message was successfully delivered and flush the
+ error which caused the first attempt to fail.
+ """
+ self.assertEquals(
+ domain.messages,
+ {recipient: ["\n%s\n" % (message,)]})
+ # Flush the RuntimeError that BrokenMessage caused to be logged.
+ self.assertEqual(len(self.flushLoggedErrors(RuntimeError)), 1)
+ sentDeferred.addCallback(cbSent)
+ return sentDeferred
+
+
+
+class SingletonRealm(object):
+ """
+ Trivial realm implementation which is constructed with an interface and an
+ avatar and returns that avatar when asked for that interface.
+ """
+ implements(IRealm)
+
+ def __init__(self, interface, avatar):
+ self.interface = interface
+ self.avatar = avatar
+
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ for iface in interfaces:
+ if iface is self.interface:
+ return iface, self.avatar, lambda: None
+
+
+
+class NotImplementedDelivery(object):
+ """
+ Non-implementation of L{smtp.IMessageDelivery} which only has methods which
+ raise L{NotImplementedError}. Subclassed by various tests to provide the
+ particular behavior being tested.
+ """
+ def validateFrom(self, helo, origin):
+ raise NotImplementedError("This oughtn't be called in the course of this test.")
+
+
+ def validateTo(self, user):
+ raise NotImplementedError("This oughtn't be called in the course of this test.")
+
+
+ def receivedHeader(self, helo, origin, recipients):
+ raise NotImplementedError("This oughtn't be called in the course of this test.")
+
+
+
+class SMTPServerTestCase(unittest.TestCase):
+ """
+ Test various behaviors of L{twisted.mail.smtp.SMTP} and
+ L{twisted.mail.smtp.ESMTP}.
+ """
+ def testSMTPGreetingHost(self, serverClass=smtp.SMTP):
+ """
+ Test that the specified hostname shows up in the SMTP server's
+ greeting.
+ """
+ s = serverClass()
+ s.host = "example.com"
+ t = StringTransport()
+ s.makeConnection(t)
+ s.connectionLost(error.ConnectionDone())
+ self.assertIn("example.com", t.value())
+
+
+ def testSMTPGreetingNotExtended(self):
+ """
+ Test that the string "ESMTP" does not appear in the SMTP server's
+ greeting since that string strongly suggests the presence of support
+ for various SMTP extensions which are not supported by L{smtp.SMTP}.
+ """
+ s = smtp.SMTP()
+ t = StringTransport()
+ s.makeConnection(t)
+ s.connectionLost(error.ConnectionDone())
+ self.assertNotIn("ESMTP", t.value())
+
+
+ def testESMTPGreetingHost(self):
+ """
+ Similar to testSMTPGreetingHost, but for the L{smtp.ESMTP} class.
+ """
+ self.testSMTPGreetingHost(smtp.ESMTP)
+
+
+ def testESMTPGreetingExtended(self):
+ """
+ Test that the string "ESMTP" does appear in the ESMTP server's
+ greeting since L{smtp.ESMTP} does support the SMTP extensions which
+ that advertises to the client.
+ """
+ s = smtp.ESMTP()
+ t = StringTransport()
+ s.makeConnection(t)
+ s.connectionLost(error.ConnectionDone())
+ self.assertIn("ESMTP", t.value())
+
+
+ def test_acceptSenderAddress(self):
+ """
+ Test that a C{MAIL FROM} command with an acceptable address is
+ responded to with the correct success code.
+ """
+ class AcceptanceDelivery(NotImplementedDelivery):
+ """
+ Delivery object which accepts all senders as valid.
+ """
+ def validateFrom(self, helo, origin):
+ return origin
+
+ realm = SingletonRealm(smtp.IMessageDelivery, AcceptanceDelivery())
+ portal = Portal(realm, [AllowAnonymousAccess()])
+ proto = smtp.SMTP()
+ proto.portal = portal
+ trans = StringTransport()
+ proto.makeConnection(trans)
+
+ # Deal with the necessary preliminaries
+ proto.dataReceived('HELO example.com\r\n')
+ trans.clear()
+
+ # Try to specify our sender address
+ proto.dataReceived('MAIL FROM:<alice@example.com>\r\n')
+
+ # Clean up the protocol before doing anything that might raise an
+ # exception.
+ proto.connectionLost(error.ConnectionLost())
+
+ # Make sure that we received exactly the correct response
+ self.assertEqual(
+ trans.value(),
+ '250 Sender address accepted\r\n')
+
+
+ def test_deliveryRejectedSenderAddress(self):
+ """
+ Test that a C{MAIL FROM} command with an address rejected by a
+ L{smtp.IMessageDelivery} instance is responded to with the correct
+ error code.
+ """
+ class RejectionDelivery(NotImplementedDelivery):
+ """
+ Delivery object which rejects all senders as invalid.
+ """
+ def validateFrom(self, helo, origin):
+ raise smtp.SMTPBadSender(origin)
+
+ realm = SingletonRealm(smtp.IMessageDelivery, RejectionDelivery())
+ portal = Portal(realm, [AllowAnonymousAccess()])
+ proto = smtp.SMTP()
+ proto.portal = portal
+ trans = StringTransport()
+ proto.makeConnection(trans)
+
+ # Deal with the necessary preliminaries
+ proto.dataReceived('HELO example.com\r\n')
+ trans.clear()
+
+ # Try to specify our sender address
+ proto.dataReceived('MAIL FROM:<alice@example.com>\r\n')
+
+ # Clean up the protocol before doing anything that might raise an
+ # exception.
+ proto.connectionLost(error.ConnectionLost())
+
+ # Make sure that we received exactly the correct response
+ self.assertEqual(
+ trans.value(),
+ '550 Cannot receive from specified address '
+ '<alice@example.com>: Sender not acceptable\r\n')
+
+
+ def test_portalRejectedSenderAddress(self):
+ """
+ Test that a C{MAIL FROM} command with an address rejected by an
+ L{smtp.SMTP} instance's portal is responded to with the correct error
+ code.
+ """
+ class DisallowAnonymousAccess(object):
+ """
+ Checker for L{IAnonymous} which rejects authentication attempts.
+ """
+ implements(ICredentialsChecker)
+
+ credentialInterfaces = (IAnonymous,)
+
+ def requestAvatarId(self, credentials):
+ return defer.fail(UnauthorizedLogin())
+
+ realm = SingletonRealm(smtp.IMessageDelivery, NotImplementedDelivery())
+ portal = Portal(realm, [DisallowAnonymousAccess()])
+ proto = smtp.SMTP()
+ proto.portal = portal
+ trans = StringTransport()
+ proto.makeConnection(trans)
+
+ # Deal with the necessary preliminaries
+ proto.dataReceived('HELO example.com\r\n')
+ trans.clear()
+
+ # Try to specify our sender address
+ proto.dataReceived('MAIL FROM:<alice@example.com>\r\n')
+
+ # Clean up the protocol before doing anything that might raise an
+ # exception.
+ proto.connectionLost(error.ConnectionLost())
+
+ # Make sure that we received exactly the correct response
+ self.assertEqual(
+ trans.value(),
+ '550 Cannot receive from specified address '
+ '<alice@example.com>: Sender not acceptable\r\n')
+
+
+ def test_portalRejectedAnonymousSender(self):
+ """
+ Test that a C{MAIL FROM} command issued without first authenticating
+ when a portal has been configured to disallow anonymous logins is
+ responded to with the correct error code.
+ """
+ realm = SingletonRealm(smtp.IMessageDelivery, NotImplementedDelivery())
+ portal = Portal(realm, [])
+ proto = smtp.SMTP()
+ proto.portal = portal
+ trans = StringTransport()
+ proto.makeConnection(trans)
+
+ # Deal with the necessary preliminaries
+ proto.dataReceived('HELO example.com\r\n')
+ trans.clear()
+
+ # Try to specify our sender address
+ proto.dataReceived('MAIL FROM:<alice@example.com>\r\n')
+
+ # Clean up the protocol before doing anything that might raise an
+ # exception.
+ proto.connectionLost(error.ConnectionLost())
+
+ # Make sure that we received exactly the correct response
+ self.assertEqual(
+ trans.value(),
+ '550 Cannot receive from specified address '
+ '<alice@example.com>: Unauthenticated senders not allowed\r\n')
+
+
+
+class ESMTPAuthenticationTestCase(unittest.TestCase):
+ def assertServerResponse(self, bytes, response):
+ """
+ Assert that when the given bytes are delivered to the ESMTP server
+ instance, it responds with the indicated lines.
+
+ @type bytes: str
+ @type response: list of str
+ """
+ self.transport.clear()
+ self.server.dataReceived(bytes)
+ self.assertEqual(
+ response,
+ self.transport.value().splitlines())
+
+
+ def assertServerAuthenticated(self, loginArgs, username="username", password="password"):
+ """
+ Assert that a login attempt has been made, that the credentials and
+ interfaces passed to it are correct, and that when the login request
+ is satisfied, a successful response is sent by the ESMTP server
+ instance.
+
+ @param loginArgs: A C{list} previously passed to L{portalFactory}.
+ """
+ d, credentials, mind, interfaces = loginArgs.pop()
+ self.assertEqual(loginArgs, [])
+ self.failUnless(twisted.cred.credentials.IUsernamePassword.providedBy(credentials))
+ self.assertEqual(credentials.username, username)
+ self.failUnless(credentials.checkPassword(password))
+ self.assertIn(smtp.IMessageDeliveryFactory, interfaces)
+ self.assertIn(smtp.IMessageDelivery, interfaces)
+ d.callback((smtp.IMessageDeliveryFactory, None, lambda: None))
+
+ self.assertEqual(
+ ["235 Authentication successful."],
+ self.transport.value().splitlines())
+
+
+ def setUp(self):
+ """
+ Create an ESMTP instance attached to a StringTransport.
+ """
+ self.server = smtp.ESMTP({
+ 'LOGIN': imap4.LOGINCredentials})
+ self.server.host = 'localhost'
+ self.transport = StringTransport(
+ peerAddress=address.IPv4Address('TCP', '127.0.0.1', 12345))
+ self.server.makeConnection(self.transport)
+
+
+ def tearDown(self):
+ """
+ Disconnect the ESMTP instance to clean up its timeout DelayedCall.
+ """
+ self.server.connectionLost(error.ConnectionDone())
+
+
+ def portalFactory(self, loginList):
+ class DummyPortal:
+ def login(self, credentials, mind, *interfaces):
+ d = defer.Deferred()
+ loginList.append((d, credentials, mind, interfaces))
+ return d
+ return DummyPortal()
+
+
+ def test_authenticationCapabilityAdvertised(self):
+ """
+ Test that AUTH is advertised to clients which issue an EHLO command.
+ """
+ self.transport.clear()
+ self.server.dataReceived('EHLO\r\n')
+ responseLines = self.transport.value().splitlines()
+ self.assertEqual(
+ responseLines[0],
+ "250-localhost Hello 127.0.0.1, nice to meet you")
+ self.assertEqual(
+ responseLines[1],
+ "250 AUTH LOGIN")
+ self.assertEqual(len(responseLines), 2)
+
+
+ def test_plainAuthentication(self):
+ """
+ Test that the LOGIN authentication mechanism can be used
+ """
+ loginArgs = []
+ self.server.portal = self.portalFactory(loginArgs)
+
+ self.server.dataReceived('EHLO\r\n')
+ self.transport.clear()
+
+ self.assertServerResponse(
+ 'AUTH LOGIN\r\n',
+ ["334 " + "User Name\0".encode('base64').strip()])
+
+ self.assertServerResponse(
+ 'username'.encode('base64') + '\r\n',
+ ["334 " + "Password\0".encode('base64').strip()])
+
+ self.assertServerResponse(
+ 'password'.encode('base64').strip() + '\r\n',
+ [])
+
+ self.assertServerAuthenticated(loginArgs)
+
+
+ def test_plainAuthenticationEmptyPassword(self):
+ """
+ Test that giving an empty password for plain auth succeeds.
+ """
+ loginArgs = []
+ self.server.portal = self.portalFactory(loginArgs)
+
+ self.server.dataReceived('EHLO\r\n')
+ self.transport.clear()
+
+ self.assertServerResponse(
+ 'AUTH LOGIN\r\n',
+ ["334 " + "User Name\0".encode('base64').strip()])
+
+ self.assertServerResponse(
+ 'username'.encode('base64') + '\r\n',
+ ["334 " + "Password\0".encode('base64').strip()])
+
+ self.assertServerResponse('\r\n', [])
+ self.assertServerAuthenticated(loginArgs, password='')
+
+
+ def test_plainAuthenticationInitialResponse(self):
+ """
+ The response to the first challenge may be included on the AUTH command
+ line. Test that this is also supported.
+ """
+ loginArgs = []
+ self.server.portal = self.portalFactory(loginArgs)
+
+ self.server.dataReceived('EHLO\r\n')
+ self.transport.clear()
+
+ self.assertServerResponse(
+ 'AUTH LOGIN ' + "username".encode('base64').strip() + '\r\n',
+ ["334 " + "Password\0".encode('base64').strip()])
+
+ self.assertServerResponse(
+ 'password'.encode('base64').strip() + '\r\n',
+ [])
+
+ self.assertServerAuthenticated(loginArgs)
+
+
+ def test_abortAuthentication(self):
+ """
+ Test that a challenge/response sequence can be aborted by the client.
+ """
+ loginArgs = []
+ self.server.portal = self.portalFactory(loginArgs)
+
+ self.server.dataReceived('EHLO\r\n')
+ self.server.dataReceived('AUTH LOGIN\r\n')
+
+ self.assertServerResponse(
+ '*\r\n',
+ ['501 Authentication aborted'])
+
+
+ def test_invalidBase64EncodedResponse(self):
+ """
+ Test that a response which is not properly Base64 encoded results in
+ the appropriate error code.
+ """
+ loginArgs = []
+ self.server.portal = self.portalFactory(loginArgs)
+
+ self.server.dataReceived('EHLO\r\n')
+ self.server.dataReceived('AUTH LOGIN\r\n')
+
+ self.assertServerResponse(
+ 'x\r\n',
+ ['501 Syntax error in parameters or arguments'])
+
+ self.assertEqual(loginArgs, [])
+
+
+ def test_invalidBase64EncodedInitialResponse(self):
+ """
+ Like L{test_invalidBase64EncodedResponse} but for the case of an
+ initial response included with the C{AUTH} command.
+ """
+ loginArgs = []
+ self.server.portal = self.portalFactory(loginArgs)
+
+ self.server.dataReceived('EHLO\r\n')
+ self.assertServerResponse(
+ 'AUTH LOGIN x\r\n',
+ ['501 Syntax error in parameters or arguments'])
+
+ self.assertEqual(loginArgs, [])
+
+
+ def test_unexpectedLoginFailure(self):
+ """
+ If the L{Deferred} returned by L{Portal.login} fires with an
+ exception of any type other than L{UnauthorizedLogin}, the exception
+ is logged and the client is informed that the authentication attempt
+ has failed.
+ """
+ loginArgs = []
+ self.server.portal = self.portalFactory(loginArgs)
+
+ self.server.dataReceived('EHLO\r\n')
+ self.transport.clear()
+
+ self.assertServerResponse(
+ 'AUTH LOGIN ' + 'username'.encode('base64').strip() + '\r\n',
+ ['334 ' + 'Password\0'.encode('base64').strip()])
+ self.assertServerResponse(
+ 'password'.encode('base64').strip() + '\r\n',
+ [])
+
+ d, credentials, mind, interfaces = loginArgs.pop()
+ d.errback(RuntimeError("Something wrong with the server"))
+
+ self.assertEquals(
+ '451 Requested action aborted: local error in processing\r\n',
+ self.transport.value())
+
+ self.assertEquals(len(self.flushLoggedErrors(RuntimeError)), 1)
+
+
+
+class SMTPClientErrorTestCase(unittest.TestCase):
+ """
+ Tests for L{smtp.SMTPClientError}.
+ """
+ def test_str(self):
+ """
+ The string representation of a L{SMTPClientError} instance includes
+ the response code and response string.
+ """
+ err = smtp.SMTPClientError(123, "some text")
+ self.assertEquals(str(err), "123 some text")
+
+
+ def test_strWithNegativeCode(self):
+ """
+ If the response code supplied to L{SMTPClientError} is negative, it
+ is excluded from the string representation.
+ """
+ err = smtp.SMTPClientError(-1, "foo bar")
+ self.assertEquals(str(err), "foo bar")
+
+
+ def test_strWithLog(self):
+ """
+ If a line log is supplied to L{SMTPClientError}, its contents are
+ included in the string representation of the exception instance.
+ """
+ log = LineLog(10)
+ log.append("testlog")
+ log.append("secondline")
+ err = smtp.SMTPClientError(100, "test error", log=log.str())
+ self.assertEquals(
+ str(err),
+ "100 test error\n"
+ "testlog\n"
+ "secondline\n")
+
+
+
+class SenderMixinSentMailTests(unittest.TestCase):
+ """
+ Tests for L{smtp.SenderMixin.sentMail}, used in particular by
+ L{smtp.SMTPSenderFactory} and L{smtp.ESMTPSenderFactory}.
+ """
+ def test_onlyLogFailedAddresses(self):
+ """
+ L{smtp.SenderMixin.sentMail} adds only the addresses with failing
+ SMTP response codes to the log passed to the factory's errback.
+ """
+ onDone = self.assertFailure(defer.Deferred(), smtp.SMTPDeliveryError)
+ onDone.addCallback(lambda e: self.assertEquals(
+ e.log, "bob@example.com: 199 Error in sending.\n"))
+
+ clientFactory = smtp.SMTPSenderFactory(
+ 'source@address', 'recipient@address',
+ StringIO("Message body"), onDone,
+ retries=0, timeout=0.5)
+
+ client = clientFactory.buildProtocol(
+ address.IPv4Address('TCP', 'example.net', 25))
+
+ addresses = [("alice@example.com", 200, "No errors here!"),
+ ("bob@example.com", 199, "Error in sending.")]
+ client.sentMail(199, "Test response", 1, addresses, client.log)
+
+ return onDone
diff --git a/vendor/Twisted-10.0.0/twisted/mail/topfiles/NEWS b/vendor/Twisted-10.0.0/twisted/mail/topfiles/NEWS
new file mode 100644
index 0000000000..4ac317139e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/topfiles/NEWS
@@ -0,0 +1,191 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/<number>
+
+Twisted Mail 10.0.0 (2010-03-01)
+================================
+
+Bugfixes
+--------
+ - twisted.mail.smtp.ESMTPClient and
+ twisted.mail.smtp.LOGINAuthenticator now implement the (obsolete)
+ LOGIN SASL mechanism according to the draft specification. (#4031)
+
+ - twisted.mail.imap4.IMAP4Client will no longer misparse all html-
+ formatted message bodies received in response to a fetch command.
+ (#4049)
+
+ - The regression in IMAP4 search handling of "OR" and "NOT" terms has
+ been fixed. (#4178)
+
+Other
+-----
+ - #4028, #4170, #4200
+
+
+Twisted Mail 9.0.0 (2009-11-24)
+===============================
+
+Features
+--------
+ - maildir.StringListMailbox, an in-memory maildir mailbox, now supports
+ deletion, undeletion, and syncing (#3547)
+ - SMTPClient's callbacks are now more completely documented (#684)
+
+Fixes
+-----
+ - Parse UNSEEN response data and include it in the result of
+ IMAP4Client.examine (#3550)
+ - The IMAP4 client now delivers more unsolicited server responses to callbacks
+ rather than ignoring them, and also won't ignore solicited responses that
+ arrive on the same line as an unsolicited one (#1105)
+ - Several bugs in the SMTP client's idle timeout support were fixed (#3641,
+ #1219)
+ - A case where the SMTP client could skip some recipients when retrying
+ delivery has been fixed (#3638)
+ - Errors during certain data transfers will no longer be swallowed. They will
+ now bubble up to the higher-level API (such as the sendmail function) (#3642)
+ - Escape sequences inside quoted strings in IMAP4 should now be parsed
+ correctly by the IMAP4 server protocol (#3659)
+ - The "imap4-utf-7" codec that is registered by twisted.mail.imap4 had a number
+ of fixes that allow it to work better with the Python codecs system, and to
+ actually work (#3663)
+ - The Maildir implementation now ensures time-based ordering of filenames so
+ that the lexical sorting of messages matches the order in which they were
+ received (#3812)
+ - SASL PLAIN credentials generated by the IMAP4 protocol implementations
+ (client and server) should now be RFC-compliant (#3939)
+ - Searching for a set of sequences using the IMAP4 "SEARCH" command should
+ now work on the IMAP4 server protocol implementation. This at least improves
+ support for the Pine mail client (#1977)
+
+Other
+-----
+ - #2763, #3647, #3750, #3819, #3540, #3846, #2023, #4050
+
+
+Mail 8.2.0 (2008-12-16)
+=======================
+
+Fixes
+-----
+ - The mailmail tool now provides better error messages for usage errors (#3339)
+ - The SMTP protocol implementation now works on PyPy (#2976)
+
+Other
+-----
+ - #3475
+
+
+8.1.0 (2008-05-18)
+==================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+8.0.0 (2008-03-17)
+==================
+
+Features
+--------
+ - Support CAPABILITY responses that include atoms of the form "FOO" and
+ "FOO=BAR" in IMAP4 (#2695)
+ - Parameterize error handling behavior of imap4.encoder and imap4.decoder.
+ (#2929)
+
+Fixes
+-----
+ - Handle empty passwords in SMTP auth. (#2521)
+ - Fix IMAP4Client's parsing of literals which are not preceeded by whitespace.
+ (#2700)
+ - Handle MX lookup suceeding without answers. (#2807)
+ - Fix issues with aliases(5) process support. (#2729)
+
+Misc
+----
+ - #2371, #2123, #2378, #739, #2640, #2746, #1917, #2266, #2864, #2832, #2063,
+ #2865, #2847
+
+
+0.4.0 (2007-01-06)
+==================
+
+Features
+--------
+ - Plaintext POP3 logins are now possible over SSL or TLS (#1809)
+
+Fixes
+-----
+ - ESMTP servers now greet with an "ESMTP" string (#1891)
+ - The POP3 client can now correctly deal with concurrent POP3
+ retrievals (#1988, #1691)
+ - In the IMAP4 server, a bug involving retrieving the first part
+ of a single-part message was fixed. This improves compatibility
+ with Pine (#1978)
+ - A bug in the IMAP4 server which caused corruption under heavy
+ pipelining was fixed (#1992)
+ - More strict support for the AUTH command was added to the SMTP
+ server, to support the AUTH <mechanism>
+ <initial-authentication-data> form of the command (#1552)
+ - An SMTP bug involving the interaction with validateFrom, which
+ caused multiple conflicting SMTP messages to be sent over the wire,
+ was fixed (#2158)
+
+Misc
+----
+ - #1648, #1801, #1636, #2003, #1936, #1202, #2051, #2072, #2248, #2250
+
+0.3.0 (2006-05-21)
+==================
+
+Features
+--------
+ - Support Deferred results from POP3's IMailbox.listMessages (#1701).
+
+Fixes
+-----
+ - Quote usernames and passwords automatically in the IMAP client (#1411).
+ - Improved parsing of literals in IMAP4 client and server (#1417).
+ - Recognize unsolicted FLAGS response in IMAP4 client (#1105).
+ - Parse and respond to requests with multiple BODY arguments in IMAP4
+ server (#1307).
+ - Misc: #1356, #1290, #1602
+
+0.2.0:
+ - SMTP server:
+ - Now gives application-level code opportunity to set a different
+ Received header for each recipient of a multi-recipient message.
+ - IMAP client:
+ - New `startTLS' method to allow explicit negotiation of transport
+ security.
+- POP client:
+ - Support for per-command timeouts
+ - New `startTLS' method, similar to the one added to the IMAP
+ client.
+ - NOOP, RSET, and STAT support added
+- POP server:
+ - Bug handling passwords of "" fixed
+
+
+0.1.0:
+ - Tons of bugfixes in IMAP4, POP3, and SMTP protocols
+ - Maildir append support
+ - Brand new, improved POP3 client (twisted.mail.pop3.AdvancedPOP3Client)
+ - Deprecated the old POP3 client (twisted.mail.pop3.POP3Client)
+ - SMTP client:
+ - Support SMTP AUTH
+ - Allow user to supply SSL context
+ - Improved error handling, via new exception classes and an overridable
+ hook to customize handling.
+ - Order to try the authenication schemes is user-definable.
+ - Timeout support.
+ - SMTP server:
+ - Properly understand <> sender.
+ - Parameterize remote port
+ - IMAP4:
+ - LOGIN authentication compatibility improved
+ - Improved unicode mailbox support
+ - Fix parsing/handling of "FETCH BODY[HEADER]"
+ - Many many quoting fixes
+ - Timeout support on client
diff --git a/vendor/Twisted-10.0.0/twisted/mail/topfiles/README b/vendor/Twisted-10.0.0/twisted/mail/topfiles/README
new file mode 100644
index 0000000000..742da1295b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/topfiles/README
@@ -0,0 +1,3 @@
+Twisted Mail 10.0.0
+
+Twisted Mail depends on Twisted and (sometimes) Twisted Names.
diff --git a/vendor/Twisted-10.0.0/twisted/mail/topfiles/setup.py b/vendor/Twisted-10.0.0/twisted/mail/topfiles/setup.py
new file mode 100644
index 0000000000..c7028fd219
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/mail/topfiles/setup.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+
+try:
+ from twisted.python import dist
+except ImportError:
+ raise SystemExit("twisted.python.dist module not found. Make sure you "
+ "have installed the Twisted core package before "
+ "attempting to install any other Twisted projects.")
+
+if __name__ == '__main__':
+ if sys.version_info[:2] >= (2, 4):
+ extraMeta = dict(
+ classifiers=[
+ "Development Status :: 4 - Beta",
+ "Environment :: No Input/Output (Daemon)",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python",
+ "Topic :: Communications :: Email :: Post-Office :: IMAP",
+ "Topic :: Communications :: Email :: Post-Office :: POP3",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ ])
+ else:
+ extraMeta = {}
+
+ dist.setup(
+ twisted_subproject="mail",
+ scripts=dist.getScripts("mail"),
+ # metadata
+ name="Twisted Mail",
+ description="A Twisted Mail library, server and client.",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="Jp Calderone",
+ url="http://twistedmatrix.com/trac/wiki/TwistedMail",
+ license="MIT",
+ long_description="""\
+An SMTP, IMAP and POP protocol implementation together with clients
+and servers.
+
+Twisted Mail contains high-level, efficient protocol implementations
+for both clients and servers of SMTP, POP3, and IMAP4. Additionally,
+it contains an "out of the box" combination SMTP/POP3 virtual-hosting
+mail server. Also included is a read/write Maildir implementation and
+a basic Mail Exchange calculator.
+""",
+ **extraMeta)
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/__init__.py b/vendor/Twisted-10.0.0/twisted/manhole/__init__.py
new file mode 100644
index 0000000000..3647cf556a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/__init__.py
@@ -0,0 +1,8 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Twisted Manhole: interactive interpreter and direct manipulation support for Twisted.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/_inspectro.py b/vendor/Twisted-10.0.0/twisted/manhole/_inspectro.py
new file mode 100644
index 0000000000..12767ab1ee
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/_inspectro.py
@@ -0,0 +1,369 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""An input/output window for the glade reactor inspector.
+"""
+
+import time
+import gtk
+import gobject
+import gtk.glade
+from twisted.python.util import sibpath
+from twisted.python import reflect
+
+from twisted.manhole.ui import gtk2manhole
+from twisted.python.components import Adapter, registerAdapter
+from twisted.python import log
+from twisted.protocols import policies
+from zope.interface import implements, Interface
+
+# the glade file uses stock icons, which requires gnome to be installed
+import gnome
+version = "$Revision: 1.1 $"[11:-2]
+gnome.init("gladereactor Inspector", version)
+
+class ConsoleOutput(gtk2manhole.ConsoleOutput):
+ def _captureLocalLog(self):
+ self.fobs = log.FileLogObserver(gtk2manhole._Notafile(self, "log"))
+ self.fobs.start()
+
+ def stop(self):
+ self.fobs.stop()
+ del self.fobs
+
+class ConsoleInput(gtk2manhole.ConsoleInput):
+ def sendMessage(self):
+ buffer = self.textView.get_buffer()
+ iter1, iter2 = buffer.get_bounds()
+ text = buffer.get_text(iter1, iter2, False)
+ self.do(text)
+
+ def do(self, text):
+ self.toplevel.do(text)
+
+class INode(Interface):
+ """A node in the inspector tree model.
+ """
+
+ def __adapt__(adaptable, default):
+ if hasattr(adaptable, "__dict__"):
+ return InstanceNode(adaptable)
+ return AttributesNode(adaptable)
+
+class InspectorNode(Adapter):
+ implements(INode)
+
+ def postInit(self, offset, parent, slot):
+ self.offset = offset
+ self.parent = parent
+ self.slot = slot
+
+ def getPath(self):
+ L = []
+ x = self
+ while x.parent is not None:
+ L.append(x.offset)
+ x = x.parent
+ L.reverse()
+ return L
+
+ def __getitem__(self, index):
+ slot, o = self.get(index)
+ n = INode(o, persist=False)
+ n.postInit(index, self, slot)
+ return n
+
+ def origstr(self):
+ return str(self.original)
+
+ def format(self):
+ return (self.slot, self.origstr())
+
+
+class ConstantNode(InspectorNode):
+ def __len__(self):
+ return 0
+
+class DictionaryNode(InspectorNode):
+ def get(self, index):
+ L = self.original.items()
+ L.sort()
+ return L[index]
+
+ def __len__(self):
+ return len(self.original)
+
+ def origstr(self):
+ return "Dictionary"
+
+class ListNode(InspectorNode):
+ def get(self, index):
+ return index, self.original[index]
+
+ def origstr(self):
+ return "List"
+
+ def __len__(self):
+ return len(self.original)
+
+class AttributesNode(InspectorNode):
+ def __len__(self):
+ return len(dir(self.original))
+
+ def get(self, index):
+ L = dir(self.original)
+ L.sort()
+ return L[index], getattr(self.original, L[index])
+
+class InstanceNode(InspectorNode):
+ def __len__(self):
+ return len(self.original.__dict__) + 1
+
+ def get(self, index):
+ if index == 0:
+ if hasattr(self.original, "__class__"):
+ v = self.original.__class__
+ else:
+ v = type(self.original)
+ return "__class__", v
+ else:
+ index -= 1
+ L = self.original.__dict__.items()
+ L.sort()
+ return L[index]
+
+import types
+
+for x in dict, types.DictProxyType:
+ registerAdapter(DictionaryNode, x, INode)
+for x in list, tuple:
+ registerAdapter(ListNode, x, INode)
+for x in int, str:
+ registerAdapter(ConstantNode, x, INode)
+
+
+class InspectorTreeModel(gtk.GenericTreeModel):
+ def __init__(self, root):
+ gtk.GenericTreeModel.__init__(self)
+ self.root = INode(root, persist=False)
+ self.root.postInit(0, None, 'root')
+
+ def on_get_flags(self):
+ return 0
+
+ def on_get_n_columns(self):
+ return 1
+
+ def on_get_column_type(self, index):
+ return gobject.TYPE_STRING
+
+ def on_get_path(self, node):
+ return node.getPath()
+
+ def on_get_iter(self, path):
+ x = self.root
+ for elem in path:
+ x = x[elem]
+ return x
+
+ def on_get_value(self, node, column):
+ return node.format()[column]
+
+ def on_iter_next(self, node):
+ try:
+ return node.parent[node.offset + 1]
+ except IndexError:
+ return None
+
+ def on_iter_children(self, node):
+ return node[0]
+
+ def on_iter_has_child(self, node):
+ return len(node)
+
+ def on_iter_n_children(self, node):
+ return len(node)
+
+ def on_iter_nth_child(self, node, n):
+ if node is None:
+ return None
+ return node[n]
+
+ def on_iter_parent(self, node):
+ return node.parent
+
+
+class Inspectro:
+ selected = None
+ def __init__(self, o=None):
+ self.xml = x = gtk.glade.XML(sibpath(__file__, "inspectro.glade"))
+ self.tree_view = x.get_widget("treeview")
+ colnames = ["Name", "Value"]
+ for i in range(len(colnames)):
+ self.tree_view.append_column(
+ gtk.TreeViewColumn(
+ colnames[i], gtk.CellRendererText(), text=i))
+ d = {}
+ for m in reflect.prefixedMethods(self, "on_"):
+ d[m.im_func.__name__] = m
+ self.xml.signal_autoconnect(d)
+ if o is not None:
+ self.inspect(o)
+ self.ns = {'inspect': self.inspect}
+ iwidget = x.get_widget('input')
+ self.input = ConsoleInput(iwidget)
+ self.input.toplevel = self
+ iwidget.connect("key_press_event", self.input._on_key_press_event)
+ self.output = ConsoleOutput(x.get_widget('output'))
+
+ def select(self, o):
+ self.selected = o
+ self.ns['it'] = o
+ self.xml.get_widget("itname").set_text(repr(o))
+ self.xml.get_widget("itpath").set_text("???")
+
+ def inspect(self, o):
+ self.model = InspectorTreeModel(o)
+ self.tree_view.set_model(self.model)
+ self.inspected = o
+
+ def do(self, command):
+ filename = '<inspector>'
+ try:
+ print repr(command)
+ try:
+ code = compile(command, filename, 'eval')
+ except:
+ code = compile(command, filename, 'single')
+ val = eval(code, self.ns, self.ns)
+ if val is not None:
+ print repr(val)
+ self.ns['_'] = val
+ except:
+ log.err()
+
+ def on_inspect(self, *a):
+ self.inspect(self.selected)
+
+ def on_inspect_new(self, *a):
+ Inspectro(self.selected)
+
+ def on_row_activated(self, tv, path, column):
+ self.select(self.model.on_get_iter(path).original)
+
+
+class LoggingProtocol(policies.ProtocolWrapper):
+ """Log network traffic."""
+
+ logging = True
+ logViewer = None
+
+ def __init__(self, *args):
+ policies.ProtocolWrapper.__init__(self, *args)
+ self.inLog = []
+ self.outLog = []
+
+ def write(self, data):
+ if self.logging:
+ self.outLog.append((time.time(), data))
+ if self.logViewer:
+ self.logViewer.updateOut(self.outLog[-1])
+ policies.ProtocolWrapper.write(self, data)
+
+ def dataReceived(self, data):
+ if self.logging:
+ self.inLog.append((time.time(), data))
+ if self.logViewer:
+ self.logViewer.updateIn(self.inLog[-1])
+ policies.ProtocolWrapper.dataReceived(self, data)
+
+ def __repr__(self):
+ r = "wrapped " + repr(self.wrappedProtocol)
+ if self.logging:
+ r += " (logging)"
+ return r
+
+
+class LoggingFactory(policies.WrappingFactory):
+ """Wrap protocols with logging wrappers."""
+
+ protocol = LoggingProtocol
+ logging = True
+
+ def buildProtocol(self, addr):
+ p = self.protocol(self, self.wrappedFactory.buildProtocol(addr))
+ p.logging = self.logging
+ return p
+
+ def __repr__(self):
+ r = "wrapped " + repr(self.wrappedFactory)
+ if self.logging:
+ r += " (logging)"
+ return r
+
+
+class LogViewer:
+ """Display log of network traffic."""
+
+ def __init__(self, p):
+ self.p = p
+ vals = [time.time()]
+ if p.inLog:
+ vals.append(p.inLog[0][0])
+ if p.outLog:
+ vals.append(p.outLog[0][0])
+ self.startTime = min(vals)
+ p.logViewer = self
+ self.xml = x = gtk.glade.XML(sibpath(__file__, "logview.glade"))
+ self.xml.signal_autoconnect(self)
+ self.loglist = self.xml.get_widget("loglist")
+ # setup model, connect it to my treeview
+ self.model = gtk.ListStore(str, str, str)
+ self.loglist.set_model(self.model)
+ self.loglist.set_reorderable(1)
+ self.loglist.set_headers_clickable(1)
+ # self.servers.set_headers_draggable(1)
+ # add a column
+ for col in [
+ gtk.TreeViewColumn('Time',
+ gtk.CellRendererText(),
+ text=0),
+ gtk.TreeViewColumn('D',
+ gtk.CellRendererText(),
+ text=1),
+ gtk.TreeViewColumn('Data',
+ gtk.CellRendererText(),
+ text=2)]:
+ self.loglist.append_column(col)
+ col.set_resizable(1)
+ r = []
+ for t, data in p.inLog:
+ r.append(((str(t - self.startTime), "R", repr(data)[1:-1])))
+ for t, data in p.outLog:
+ r.append(((str(t - self.startTime), "S", repr(data)[1:-1])))
+ r.sort()
+ for i in r:
+ self.model.append(i)
+
+ def updateIn(self, (time, data)):
+ self.model.append((str(time - self.startTime), "R", repr(data)[1:-1]))
+
+ def updateOut(self, (time, data)):
+ self.model.append((str(time - self.startTime), "S", repr(data)[1:-1]))
+
+ def on_logview_destroy(self, w):
+ self.p.logViewer = None
+ del self.p
+
+
+def main():
+ x = Inspectro()
+ x.inspect(x)
+ gtk.main()
+
+if __name__ == '__main__':
+ import sys
+ log.startLogging(sys.stdout)
+ main()
+
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/explorer.py b/vendor/Twisted-10.0.0/twisted/manhole/explorer.py
new file mode 100644
index 0000000000..e1b8dc5954
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/explorer.py
@@ -0,0 +1,655 @@
+# -*- test-case-name: twisted.test.test_explorer -*-
+# $Id: explorer.py,v 1.6 2003/02/18 21:15:30 acapnotic Exp $
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Support for python object introspection and exploration.
+
+Note that Explorers, what with their list of attributes, are much like
+manhole.coil.Configurables. Someone should investigate this further. (TODO)
+
+Also TODO: Determine how much code in here (particularly the function
+signature stuff) can be replaced with functions available in the
+L{inspect} module available in Python 2.1.
+"""
+
+# System Imports
+import inspect, new, string, sys, types
+import UserDict
+
+# Twisted Imports
+from twisted.spread import pb
+from twisted.python import reflect
+
+
+True=(1==1)
+False=not True
+
+class Pool(UserDict.UserDict):
+ def getExplorer(self, object, identifier):
+ oid = id(object)
+ if self.data.has_key(oid):
+ # XXX: This potentially returns something with
+ # 'identifier' set to a different value.
+ return self.data[oid]
+ else:
+ klass = typeTable.get(type(object), ExplorerGeneric)
+ e = new.instance(klass, {})
+ self.data[oid] = e
+ klass.__init__(e, object, identifier)
+ return e
+
+explorerPool = Pool()
+
+class Explorer(pb.Cacheable):
+ properties = ["id", "identifier"]
+ attributeGroups = []
+ accessors = ["get_refcount"]
+
+ id = None
+ identifier = None
+
+ def __init__(self, object, identifier):
+ self.object = object
+ self.identifier = identifier
+ self.id = id(object)
+
+ self.properties = []
+ reflect.accumulateClassList(self.__class__, 'properties',
+ self.properties)
+
+ self.attributeGroups = []
+ reflect.accumulateClassList(self.__class__, 'attributeGroups',
+ self.attributeGroups)
+
+ self.accessors = []
+ reflect.accumulateClassList(self.__class__, 'accessors',
+ self.accessors)
+
+ def getStateToCopyFor(self, perspective):
+ all = ["properties", "attributeGroups", "accessors"]
+ all.extend(self.properties)
+ all.extend(self.attributeGroups)
+
+ state = {}
+ for key in all:
+ state[key] = getattr(self, key)
+
+ state['view'] = pb.ViewPoint(perspective, self)
+ state['explorerClass'] = self.__class__.__name__
+ return state
+
+ def view_get_refcount(self, perspective):
+ return sys.getrefcount(self)
+
+class ExplorerGeneric(Explorer):
+ properties = ["str", "repr", "typename"]
+
+ def __init__(self, object, identifier):
+ Explorer.__init__(self, object, identifier)
+ self.str = str(object)
+ self.repr = repr(object)
+ self.typename = type(object).__name__
+
+
+class ExplorerImmutable(Explorer):
+ properties = ["value"]
+
+ def __init__(self, object, identifier):
+ Explorer.__init__(self, object, identifier)
+ self.value = object
+
+
+class ExplorerSequence(Explorer):
+ properties = ["len"]
+ attributeGroups = ["elements"]
+ accessors = ["get_elements"]
+
+ def __init__(self, seq, identifier):
+ Explorer.__init__(self, seq, identifier)
+ self.seq = seq
+ self.len = len(seq)
+
+ # Use accessor method to fill me in.
+ self.elements = []
+
+ def get_elements(self):
+ self.len = len(self.seq)
+ l = []
+ for i in xrange(self.len):
+ identifier = "%s[%s]" % (self.identifier, i)
+
+ # GLOBAL: using global explorerPool
+ l.append(explorerPool.getExplorer(self.seq[i], identifier))
+
+ return l
+
+ def view_get_elements(self, perspective):
+ # XXX: set the .elements member of all my remoteCaches
+ return self.get_elements()
+
+
+class ExplorerMapping(Explorer):
+ properties = ["len"]
+ attributeGroups = ["keys"]
+ accessors = ["get_keys", "get_item"]
+
+ def __init__(self, dct, identifier):
+ Explorer.__init__(self, dct, identifier)
+
+ self.dct = dct
+ self.len = len(dct)
+
+ # Use accessor method to fill me in.
+ self.keys = []
+
+ def get_keys(self):
+ keys = self.dct.keys()
+ self.len = len(keys)
+ l = []
+ for i in xrange(self.len):
+ identifier = "%s.keys()[%s]" % (self.identifier, i)
+
+ # GLOBAL: using global explorerPool
+ l.append(explorerPool.getExplorer(keys[i], identifier))
+
+ return l
+
+ def view_get_keys(self, perspective):
+ # XXX: set the .keys member of all my remoteCaches
+ return self.get_keys()
+
+ def view_get_item(self, perspective, key):
+ if type(key) is types.InstanceType:
+ key = key.object
+
+ item = self.dct[key]
+
+ identifier = "%s[%s]" % (self.identifier, repr(key))
+ # GLOBAL: using global explorerPool
+ item = explorerPool.getExplorer(item, identifier)
+ return item
+
+
+class ExplorerBuiltin(Explorer):
+ """
+ @ivar name: the name the function was defined as
+ @ivar doc: function's docstring, or C{None} if unavailable
+ @ivar self: if not C{None}, the function is a method of this object.
+ """
+ properties = ["doc", "name", "self"]
+ def __init__(self, function, identifier):
+ Explorer.__init__(self, function, identifier)
+ self.doc = function.__doc__
+ self.name = function.__name__
+ self.self = function.__self__
+
+
+class ExplorerInstance(Explorer):
+ """
+ Attribute groups:
+ - B{methods} -- dictionary of methods
+ - B{data} -- dictionary of data members
+
+ Note these are only the *instance* methods and members --
+ if you want the class methods, you'll have to look up the class.
+
+ TODO: Detail levels (me, me & class, me & class ancestory)
+
+ @ivar klass: the class this is an instance of.
+ """
+ properties = ["klass"]
+ attributeGroups = ["methods", "data"]
+
+ def __init__(self, instance, identifier):
+ Explorer.__init__(self, instance, identifier)
+ members = {}
+ methods = {}
+ for i in dir(instance):
+ # TODO: Make screening of private attributes configurable.
+ if i[0] == '_':
+ continue
+ mIdentifier = string.join([identifier, i], ".")
+ member = getattr(instance, i)
+ mType = type(member)
+
+ if mType is types.MethodType:
+ methods[i] = explorerPool.getExplorer(member, mIdentifier)
+ else:
+ members[i] = explorerPool.getExplorer(member, mIdentifier)
+
+ self.klass = explorerPool.getExplorer(instance.__class__,
+ self.identifier +
+ '.__class__')
+ self.data = members
+ self.methods = methods
+
+
+class ExplorerClass(Explorer):
+ """
+ @ivar name: the name the class was defined with
+ @ivar doc: the class's docstring
+ @ivar bases: a list of this class's base classes.
+ @ivar module: the module the class is defined in
+
+ Attribute groups:
+ - B{methods} -- class methods
+ - B{data} -- other members of the class
+ """
+ properties = ["name", "doc", "bases", "module"]
+ attributeGroups = ["methods", "data"]
+ def __init__(self, theClass, identifier):
+ Explorer.__init__(self, theClass, identifier)
+ if not identifier:
+ identifier = theClass.__name__
+ members = {}
+ methods = {}
+ for i in dir(theClass):
+ if (i[0] == '_') and (i != '__init__'):
+ continue
+
+ mIdentifier = string.join([identifier, i], ".")
+ member = getattr(theClass, i)
+ mType = type(member)
+
+ if mType is types.MethodType:
+ methods[i] = explorerPool.getExplorer(member, mIdentifier)
+ else:
+ members[i] = explorerPool.getExplorer(member, mIdentifier)
+
+ self.name = theClass.__name__
+ self.doc = inspect.getdoc(theClass)
+ self.data = members
+ self.methods = methods
+ self.bases = explorerPool.getExplorer(theClass.__bases__,
+ identifier + ".__bases__")
+ self.module = getattr(theClass, '__module__', None)
+
+
+class ExplorerFunction(Explorer):
+ properties = ["name", "doc", "file", "line","signature"]
+ """
+ name -- the name the function was defined as
+ signature -- the function's calling signature (Signature instance)
+ doc -- the function's docstring
+ file -- the file the function is defined in
+ line -- the line in the file the function begins on
+ """
+ def __init__(self, function, identifier):
+ Explorer.__init__(self, function, identifier)
+ code = function.func_code
+ argcount = code.co_argcount
+ takesList = (code.co_flags & 0x04) and 1
+ takesKeywords = (code.co_flags & 0x08) and 1
+
+ n = (argcount + takesList + takesKeywords)
+ signature = Signature(code.co_varnames[:n])
+
+ if function.func_defaults:
+ i_d = 0
+ for i in xrange(argcount - len(function.func_defaults),
+ argcount):
+ default = function.func_defaults[i_d]
+ default = explorerPool.getExplorer(
+ default, '%s.func_defaults[%d]' % (identifier, i_d))
+ signature.set_default(i, default)
+
+ i_d = i_d + 1
+
+ if takesKeywords:
+ signature.set_keyword(n - 1)
+
+ if takesList:
+ signature.set_varlist(n - 1 - takesKeywords)
+
+ # maybe also: function.func_globals,
+ # or at least func_globals.__name__?
+ # maybe the bytecode, for disassembly-view?
+
+ self.name = function.__name__
+ self.signature = signature
+ self.doc = inspect.getdoc(function)
+ self.file = code.co_filename
+ self.line = code.co_firstlineno
+
+
+class ExplorerMethod(ExplorerFunction):
+ properties = ["self", "klass"]
+ """
+ In addition to ExplorerFunction properties:
+ self -- the object I am bound to, or None if unbound
+ klass -- the class I am a method of
+ """
+ def __init__(self, method, identifier):
+
+ function = method.im_func
+ if type(function) is types.InstanceType:
+ function = function.__call__.im_func
+
+ ExplorerFunction.__init__(self, function, identifier)
+ self.id = id(method)
+ self.klass = explorerPool.getExplorer(method.im_class,
+ identifier + '.im_class')
+ self.self = explorerPool.getExplorer(method.im_self,
+ identifier + '.im_self')
+
+ if method.im_self:
+ # I'm a bound method -- eat the 'self' arg.
+ self.signature.discardSelf()
+
+
+class ExplorerModule(Explorer):
+ """
+ @ivar name: the name the module was defined as
+ @ivar doc: documentation string for the module
+ @ivar file: the file the module is defined in
+
+ Attribute groups:
+ - B{classes} -- the public classes provided by the module
+ - B{functions} -- the public functions provided by the module
+ - B{data} -- the public data members provided by the module
+
+ (\"Public\" is taken to be \"anything that doesn't start with _\")
+ """
+ properties = ["name","doc","file"]
+ attributeGroups = ["classes", "functions", "data"]
+
+ def __init__(self, module, identifier):
+ Explorer.__init__(self, module, identifier)
+ functions = {}
+ classes = {}
+ data = {}
+ for key, value in module.__dict__.items():
+ if key[0] == '_':
+ continue
+
+ mIdentifier = "%s.%s" % (identifier, key)
+
+ if type(value) is types.ClassType:
+ classes[key] = explorerPool.getExplorer(value,
+ mIdentifier)
+ elif type(value) is types.FunctionType:
+ functions[key] = explorerPool.getExplorer(value,
+ mIdentifier)
+ elif type(value) is types.ModuleType:
+ pass # pass on imported modules
+ else:
+ data[key] = explorerPool.getExplorer(value, mIdentifier)
+
+ self.name = module.__name__
+ self.doc = inspect.getdoc(module)
+ self.file = getattr(module, '__file__', None)
+ self.classes = classes
+ self.functions = functions
+ self.data = data
+
+typeTable = {types.InstanceType: ExplorerInstance,
+ types.ClassType: ExplorerClass,
+ types.MethodType: ExplorerMethod,
+ types.FunctionType: ExplorerFunction,
+ types.ModuleType: ExplorerModule,
+ types.BuiltinFunctionType: ExplorerBuiltin,
+ types.ListType: ExplorerSequence,
+ types.TupleType: ExplorerSequence,
+ types.DictType: ExplorerMapping,
+ types.StringType: ExplorerImmutable,
+ types.NoneType: ExplorerImmutable,
+ types.IntType: ExplorerImmutable,
+ types.FloatType: ExplorerImmutable,
+ types.LongType: ExplorerImmutable,
+ types.ComplexType: ExplorerImmutable,
+ }
+
+class Signature(pb.Copyable):
+ """I represent the signature of a callable.
+
+ Signatures are immutable, so don't expect my contents to change once
+ they've been set.
+ """
+ _FLAVOURLESS = None
+ _HAS_DEFAULT = 2
+ _VAR_LIST = 4
+ _KEYWORD_DICT = 8
+
+ def __init__(self, argNames):
+ self.name = argNames
+ self.default = [None] * len(argNames)
+ self.flavour = [None] * len(argNames)
+
+ def get_name(self, arg):
+ return self.name[arg]
+
+ def get_default(self, arg):
+ if arg is types.StringType:
+ arg = self.name.index(arg)
+
+ # Wouldn't it be nice if we just returned "None" when there
+ # wasn't a default? Well, yes, but often times "None" *is*
+ # the default, so return a tuple instead.
+ if self.flavour[arg] == self._HAS_DEFAULT:
+ return (True, self.default[arg])
+ else:
+ return (False, None)
+
+ def set_default(self, arg, value):
+ if arg is types.StringType:
+ arg = self.name.index(arg)
+
+ self.flavour[arg] = self._HAS_DEFAULT
+ self.default[arg] = value
+
+ def set_varlist(self, arg):
+ if arg is types.StringType:
+ arg = self.name.index(arg)
+
+ self.flavour[arg] = self._VAR_LIST
+
+ def set_keyword(self, arg):
+ if arg is types.StringType:
+ arg = self.name.index(arg)
+
+ self.flavour[arg] = self._KEYWORD_DICT
+
+ def is_varlist(self, arg):
+ if arg is types.StringType:
+ arg = self.name.index(arg)
+
+ return (self.flavour[arg] == self._VAR_LIST)
+
+ def is_keyword(self, arg):
+ if arg is types.StringType:
+ arg = self.name.index(arg)
+
+ return (self.flavour[arg] == self._KEYWORD_DICT)
+
+ def discardSelf(self):
+ """Invoke me to discard the first argument if this is a bound method.
+ """
+ ## if self.name[0] != 'self':
+ ## log.msg("Warning: Told to discard self, but name is %s" %
+ ## self.name[0])
+ self.name = self.name[1:]
+ self.default.pop(0)
+ self.flavour.pop(0)
+
+ def getStateToCopy(self):
+ return {'name': tuple(self.name),
+ 'flavour': tuple(self.flavour),
+ 'default': tuple(self.default)}
+
+ def __len__(self):
+ return len(self.name)
+
+ def __str__(self):
+ arglist = []
+ for arg in xrange(len(self)):
+ name = self.get_name(arg)
+ hasDefault, default = self.get_default(arg)
+ if hasDefault:
+ a = "%s=%s" % (name, default)
+ elif self.is_varlist(arg):
+ a = "*%s" % (name,)
+ elif self.is_keyword(arg):
+ a = "**%s" % (name,)
+ else:
+ a = name
+ arglist.append(a)
+
+ return string.join(arglist,", ")
+
+
+
+
+
+class CRUFT_WatchyThingie:
+ # TODO:
+ #
+ # * an exclude mechanism for the watcher's browser, to avoid
+ # sending back large and uninteresting data structures.
+ #
+ # * an exclude mechanism for the watcher's trigger, to avoid
+ # triggering on some frequently-called-method-that-doesn't-
+ # actually-change-anything.
+ #
+ # * XXX! need removeWatch()
+
+ def watchIdentifier(self, identifier, callback):
+ """Watch the object returned by evaluating the identifier.
+
+ Whenever I think the object might have changed, I'll send an
+ ObjectLink of it to the callback.
+
+ WARNING: This calls eval() on its argument!
+ """
+ object = eval(identifier,
+ self.globalNamespace,
+ self.localNamespace)
+ return self.watchObject(object, identifier, callback)
+
+ def watchObject(self, object, identifier, callback):
+ """Watch the given object.
+
+ Whenever I think the object might have changed, I'll send an
+ ObjectLink of it to the callback.
+
+ The identifier argument is used to generate identifiers for
+ objects which are members of this one.
+ """
+ if type(object) is not types.InstanceType:
+ raise TypeError, "Sorry, can only place a watch on Instances."
+
+ # uninstallers = []
+
+ dct = {}
+ reflect.addMethodNamesToDict(object.__class__, dct, '')
+ for k in object.__dict__.keys():
+ dct[k] = 1
+
+ members = dct.keys()
+
+ clazzNS = {}
+ clazz = new.classobj('Watching%s%X' %
+ (object.__class__.__name__, id(object)),
+ (_MonkeysSetattrMixin, object.__class__,),
+ clazzNS)
+
+ clazzNS['_watchEmitChanged'] = new.instancemethod(
+ lambda slf, i=identifier, b=self, cb=callback:
+ cb(b.browseObject(slf, i)),
+ None, clazz)
+
+ # orig_class = object.__class__
+ object.__class__ = clazz
+
+ for name in members:
+ m = getattr(object, name)
+ # Only hook bound methods.
+ if ((type(m) is types.MethodType)
+ and (m.im_self is not None)):
+ # What's the use of putting watch monkeys on methods
+ # in addition to __setattr__? Well, um, uh, if the
+ # methods modify their attributes (i.e. add a key to
+ # a dictionary) instead of [re]setting them, then
+ # we wouldn't know about it unless we did this.
+ # (Is that convincing?)
+
+ monkey = _WatchMonkey(object)
+ monkey.install(name)
+ # uninstallers.append(monkey.uninstall)
+
+ # XXX: This probably prevents these objects from ever having a
+ # zero refcount. Leak, Leak!
+ ## self.watchUninstallers[object] = uninstallers
+
+
+class _WatchMonkey:
+ """I hang on a method and tell you what I see.
+
+ TODO: Aya! Now I just do browseObject all the time, but I could
+ tell you what got called with what when and returning what.
+ """
+ oldMethod = None
+
+ def __init__(self, instance):
+ """Make a monkey to hang on this instance object.
+ """
+ self.instance = instance
+
+ def install(self, methodIdentifier):
+ """Install myself on my instance in place of this method.
+ """
+ oldMethod = getattr(self.instance, methodIdentifier, None)
+
+ # XXX: this conditional probably isn't effective.
+ if oldMethod is not self:
+ # avoid triggering __setattr__
+ self.instance.__dict__[methodIdentifier] = (
+ new.instancemethod(self, self.instance,
+ self.instance.__class__))
+ self.oldMethod = (methodIdentifier, oldMethod)
+
+ def uninstall(self):
+ """Remove myself from this instance and restore the original method.
+
+ (I hope.)
+ """
+ if self.oldMethod is None:
+ return
+
+ # XXX: This probably doesn't work if multiple monkies are hanging
+ # on a method and they're not removed in order.
+ if self.oldMethod[1] is None:
+ delattr(self.instance, self.oldMethod[0])
+ else:
+ setattr(self.instance, self.oldMethod[0], self.oldMethod[1])
+
+ def __call__(self, instance, *a, **kw):
+ """Pretend to be the method I replaced, and ring the bell.
+ """
+ if self.oldMethod[1]:
+ rval = apply(self.oldMethod[1], a, kw)
+ else:
+ rval = None
+
+ instance._watchEmitChanged()
+ return rval
+
+
+class _MonkeysSetattrMixin:
+ """A mix-in class providing __setattr__ for objects being watched.
+ """
+ def __setattr__(self, k, v):
+ """Set the attribute and ring the bell.
+ """
+ if hasattr(self.__class__.__bases__[1], '__setattr__'):
+ # Hack! Using __bases__[1] is Bad, but since we created
+ # this class, we can be reasonably sure it'll work.
+ self.__class__.__bases__[1].__setattr__(self, k, v)
+ else:
+ self.__dict__[k] = v
+
+ # XXX: Hey, waitasec, did someone just hang a new method on me?
+ # Do I need to put a monkey on it?
+
+ self._watchEmitChanged()
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/gladereactor.glade b/vendor/Twisted-10.0.0/twisted/manhole/gladereactor.glade
new file mode 100644
index 0000000000..c78dd5a4c2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/gladereactor.glade
@@ -0,0 +1,342 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="window1">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Twisted Daemon</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">256</property>
+ <property name="default_height">300</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="servertree">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">True</property>
+ <property name="enable_search">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_DEFAULT_STYLE</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkButton" id="suspend">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <signal name="clicked" handler="on_suspend_clicked" last_modification_time="Sun, 22 Jun 2003 05:09:20 GMT"/>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="stock">gtk-undo</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Suspend</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="disconnect">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <signal name="clicked" handler="on_disconnect_clicked" last_modification_time="Sun, 22 Jun 2003 05:09:27 GMT"/>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-dialog-warning</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Disconnect</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="inspect">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <signal name="clicked" handler="on_inspect_clicked" last_modification_time="Wed, 17 Dec 2003 06:14:18 GMT"/>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="stock">gtk-open</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Inspect</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="viewlog">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <signal name="clicked" handler="on_viewlog_clicked" last_modification_time="Sun, 04 Jan 2004 22:28:19 GMT"/>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="stock">gtk-dialog-info</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">View Log</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="quit">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-quit</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <signal name="clicked" handler="on_quit_clicked" last_modification_time="Sun, 04 Jan 2004 22:26:43 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/gladereactor.py b/vendor/Twisted-10.0.0/twisted/manhole/gladereactor.py
new file mode 100644
index 0000000000..b9250d08df
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/gladereactor.py
@@ -0,0 +1,219 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+A modified gtk2 reactor with a Glade dialog in-process that allows you to stop,
+suspend, resume and inspect transports interactively.
+"""
+
+__all__ = ['install']
+
+# Twisted Imports
+from twisted.python import log, threadable, runtime, failure, util, reflect
+from twisted.internet.gtk2reactor import Gtk2Reactor as sup
+
+import gtk
+import gobject
+import gtk.glade
+
+COLUMN_DESCRIPTION = 0
+COLUMN_TRANSPORT = 1
+COLUMN_READING = 2
+COLUMN_WRITING = 3
+
+
+class GladeReactor(sup):
+ """GTK+-2 event loop reactor with GUI.
+ """
+
+ def listenTCP(self, port, factory, backlog=50, interface=''):
+ from _inspectro import LoggingFactory
+ factory = LoggingFactory(factory)
+ return sup.listenTCP(self, port, factory, backlog, interface)
+
+ def connectTCP(self, host, port, factory, timeout=30, bindAddress=None):
+ from _inspectro import LoggingFactory
+ factory = LoggingFactory(factory)
+ return sup.connectTCP(self, host, port, factory, timeout, bindAddress)
+
+ def listenSSL(self, port, factory, contextFactory, backlog=50, interface=''):
+ from _inspectro import LoggingFactory
+ factory = LoggingFactory(factory)
+ return sup.listenSSL(self, port, factory, contextFactory, backlog, interface)
+
+ def connectSSL(self, host, port, factory, contextFactory, timeout=30, bindAddress=None):
+ from _inspectro import LoggingFactory
+ factory = LoggingFactory(factory)
+ return sup.connectSSL(self, host, port, factory, contextFactory, timeout, bindAddress)
+
+ def connectUNIX(self, address, factory, timeout=30):
+ from _inspectro import LoggingFactory
+ factory = LoggingFactory(factory)
+ return sup.connectUNIX(self, address, factory, timeout)
+
+ def listenUNIX(self, address, factory, backlog=50, mode=0666):
+ from _inspectro import LoggingFactory
+ factory = LoggingFactory(factory)
+ return sup.listenUNIX(self, address, factory, backlog, mode)
+
+ def on_disconnect_clicked(self, w):
+ store, iter = self.servers.get_selection().get_selected()
+ store[iter][COLUMN_TRANSPORT].loseConnection()
+
+ def on_viewlog_clicked(self, w):
+ store, iter = self.servers.get_selection().get_selected()
+ data = store[iter][1]
+ from _inspectro import LogViewer
+ if hasattr(data, "protocol") and not data.protocol.logViewer:
+ LogViewer(data.protocol)
+
+ def on_inspect_clicked(self, w):
+ store, iter = self.servers.get_selection().get_selected()
+ data = store[iter]
+ from _inspectro import Inspectro
+ Inspectro(data[1])
+
+ def on_suspend_clicked(self, w):
+ store, iter = self.servers.get_selection().get_selected()
+ data = store[iter]
+ sup.removeReader(self, data[1])
+ sup.removeWriter(self, data[1])
+ if data[COLUMN_DESCRIPTION].endswith('(suspended)'):
+ if data[COLUMN_READING]:
+ sup.addReader(self, data[COLUMN_TRANSPORT])
+ if data[COLUMN_WRITING]:
+ sup.addWriter(self, data[COLUMN_TRANSPORT])
+ data[COLUMN_DESCRIPTION] = str(data[COLUMN_TRANSPORT])
+ self.toggle_suspend(1)
+ else:
+ data[0] += ' (suspended)'
+ self.toggle_suspend(0)
+
+ def toggle_suspend(self, suspending=0):
+ stock, nonstock = [('gtk-redo', 'Resume'),
+ ('gtk-undo', 'Suspend')][suspending]
+ b = self.xml.get_widget("suspend")
+ b.set_use_stock(1)
+ b.set_label(stock)
+ b.get_child().get_child().get_children()[1].set_label(nonstock)
+
+ def servers_selection_changed(self, w):
+ store, iter = w.get_selected()
+ if iter is None:
+ self.xml.get_widget("suspend").set_sensitive(0)
+ self.xml.get_widget('disconnect').set_sensitive(0)
+ else:
+ data = store[iter]
+ self.toggle_suspend(not
+ data[COLUMN_DESCRIPTION].endswith('(suspended)'))
+ self.xml.get_widget("suspend").set_sensitive(1)
+ self.xml.get_widget('disconnect').set_sensitive(1)
+
+ def on_quit_clicked(self, w):
+ self.stop()
+
+ def __init__(self):
+ self.xml = gtk.glade.XML(util.sibpath(__file__,"gladereactor.glade"))
+ d = {}
+ for m in reflect.prefixedMethods(self, "on_"):
+ d[m.im_func.__name__] = m
+ self.xml.signal_autoconnect(d)
+ self.xml.get_widget('window1').connect('destroy',
+ lambda w: self.stop())
+ self.servers = self.xml.get_widget("servertree")
+ sel = self.servers.get_selection()
+ sel.set_mode(gtk.SELECTION_SINGLE)
+ sel.connect("changed",
+ self.servers_selection_changed)
+ ## argh coredump: self.servers_selection_changed(sel)
+ self.xml.get_widget('suspend').set_sensitive(0)
+ self.xml.get_widget('disconnect').set_sensitive(0)
+ # setup model, connect it to my treeview
+ self.model = gtk.ListStore(str, object, gobject.TYPE_BOOLEAN,
+ gobject.TYPE_BOOLEAN)
+ self.servers.set_model(self.model)
+ self.servers.set_reorderable(1)
+ self.servers.set_headers_clickable(1)
+ # self.servers.set_headers_draggable(1)
+ # add a column
+ for col in [
+ gtk.TreeViewColumn('Server',
+ gtk.CellRendererText(),
+ text=0),
+ gtk.TreeViewColumn('Reading',
+ gtk.CellRendererToggle(),
+ active=2),
+ gtk.TreeViewColumn('Writing',
+ gtk.CellRendererToggle(),
+ active=3)]:
+
+ self.servers.append_column(col)
+ col.set_resizable(1)
+ sup.__init__(self)
+
+ def addReader(self, reader):
+ sup.addReader(self, reader)
+## gtk docs suggest this - but it's stupid
+## self.model.set(self.model.append(),
+## 0, str(reader),
+## 1, reader)
+ self._maybeAddServer(reader, read=1)
+
+ def _goAway(self,reader):
+ for p in range(len(self.model)):
+ if self.model[p][1] == reader:
+ self.model.remove(self.model.get_iter_from_string(str(p)))
+ return
+
+
+ def _maybeAddServer(self, reader, read=0, write=0):
+ p = 0
+ for x in self.model:
+ if x[1] == reader:
+ if reader == 0:
+ reader += 1
+ x[2] += read
+ x[3] += write
+ x[2] = max(x[2],0)
+ x[3] = max(x[3],0)
+
+ if not (x[2] or x[3]):
+ x[0] = x[0] + '(disconnected)'
+ self.callLater(5, self._goAway, reader)
+ return
+ p += 1
+ else:
+ read = max(read,0)
+ write = max(write, 0)
+ if read or write:
+ self.model.append((reader,reader,read,write))
+
+ def addWriter(self, writer):
+ sup.addWriter(self, writer)
+ self._maybeAddServer(writer, write=1)
+
+ def removeReader(self, reader):
+ sup.removeReader(self, reader)
+ self._maybeAddServer(reader, read=-1)
+
+ def removeWriter(self, writer):
+ sup.removeWriter(self, writer)
+ self._maybeAddServer(writer, write=-1)
+
+ def crash(self):
+ gtk.main_quit()
+
+ def run(self, installSignalHandlers=1):
+ self.startRunning(installSignalHandlers=installSignalHandlers)
+ self.simulate()
+ gtk.main()
+
+
+def install():
+ """Configure the twisted mainloop to be run inside the gtk mainloop.
+ """
+ reactor = GladeReactor()
+ from twisted.internet.main import installReactor
+ installReactor(reactor)
+ return reactor
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/inspectro.glade b/vendor/Twisted-10.0.0/twisted/manhole/inspectro.glade
new file mode 100644
index 0000000000..94b8717019
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/inspectro.glade
@@ -0,0 +1,510 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+<requires lib="gnome"/>
+<requires lib="bonobo"/>
+
+<widget class="GnomeApp" id="app1">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Inspectro</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">640</property>
+ <property name="default_height">480</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="enable_layout_config">True</property>
+
+ <child internal-child="dock">
+ <widget class="BonoboDock" id="bonobodock1">
+ <property name="visible">True</property>
+ <property name="allow_floating">True</property>
+
+ <child>
+ <widget class="BonoboDockItem" id="bonobodockitem1">
+ <property name="visible">True</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+
+ <child>
+ <widget class="GtkMenuItem" id="inspector1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Inspector</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="inspector1_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="select1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Select</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_select" last_modification_time="Wed, 17 Dec 2003 05:05:34 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="inspect1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Inspect</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_inspect" last_modification_time="Wed, 17 Dec 2003 05:05:34 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="inspect_new1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Inspect New</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_inspect_new" last_modification_time="Wed, 17 Dec 2003 05:05:34 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="help1">
+ <property name="visible">True</property>
+ <property name="stock_item">GNOMEUIINFO_MENU_HELP_TREE</property>
+
+ <child>
+ <widget class="GtkMenu" id="help1_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="about1">
+ <property name="visible">True</property>
+ <property name="stock_item">GNOMEUIINFO_MENU_ABOUT_ITEM</property>
+ <signal name="activate" handler="on_about1_activate" last_modification_time="Wed, 17 Dec 2003 04:48:59 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="placement">BONOBO_DOCK_TOP</property>
+ <property name="band">0</property>
+ <property name="position">0</property>
+ <property name="offset">0</property>
+ <property name="behavior">BONOBO_DOCK_ITEM_BEH_EXCLUSIVE|BONOBO_DOCK_ITEM_BEH_NEVER_VERTICAL|BONOBO_DOCK_ITEM_BEH_LOCKED</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="BonoboDockItem" id="bonobodockitem2">
+ <property name="visible">True</property>
+ <property name="shadow_type">GTK_SHADOW_OUT</property>
+
+ <child>
+ <widget class="GtkToolbar" id="toolbar2">
+ <property name="visible">True</property>
+ <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
+ <property name="toolbar_style">GTK_TOOLBAR_BOTH</property>
+ <property name="tooltips">True</property>
+
+ <child>
+ <widget class="button" id="button13">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Select</property>
+ <property name="use_underline">True</property>
+ <property name="stock_pixmap">gtk-convert</property>
+ <signal name="clicked" handler="on_select" last_modification_time="Wed, 17 Dec 2003 05:05:14 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="button" id="button14">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Inspect</property>
+ <property name="use_underline">True</property>
+ <property name="stock_pixmap">gtk-jump-to</property>
+ <signal name="clicked" handler="on_inspect" last_modification_time="Wed, 17 Dec 2003 05:05:02 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="button" id="button15">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Inspect New</property>
+ <property name="use_underline">True</property>
+ <property name="stock_pixmap">gtk-redo</property>
+ <signal name="clicked" handler="on_inspect_new" last_modification_time="Wed, 17 Dec 2003 05:04:50 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="placement">BONOBO_DOCK_TOP</property>
+ <property name="band">1</property>
+ <property name="position">0</property>
+ <property name="offset">0</property>
+ <property name="behavior">BONOBO_DOCK_ITEM_BEH_EXCLUSIVE</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHPaned" id="hpaned1">
+ <property name="width_request">350</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">250</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <signal name="row_activated" handler="on_row_activated" last_modification_time="Wed, 17 Dec 2003 05:07:55 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="itname">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">None</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="itpath">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">[]</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">It: </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_padding">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Path: </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_padding">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">3</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVPaned" id="vpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">303</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTextView" id="output">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="justification">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap_mode">GTK_WRAP_NONE</property>
+ <property name="cursor_visible">True</property>
+ <property name="pixels_above_lines">0</property>
+ <property name="pixels_below_lines">0</property>
+ <property name="pixels_inside_wrap">0</property>
+ <property name="left_margin">0</property>
+ <property name="right_margin">0</property>
+ <property name="indent">0</property>
+ <property name="text" translatable="yes"></property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">False</property>
+ <property name="resize">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkButton" id="button16">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <signal name="clicked" handler="on_execute" last_modification_time="Wed, 17 Dec 2003 05:06:44 GMT"/>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="stock">gtk-execute</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&gt;&gt;&gt;</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTextView" id="input">
+ <property name="height_request">25</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="editable">True</property>
+ <property name="justification">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap_mode">GTK_WRAP_NONE</property>
+ <property name="cursor_visible">True</property>
+ <property name="pixels_above_lines">0</property>
+ <property name="pixels_below_lines">0</property>
+ <property name="pixels_inside_wrap">0</property>
+ <property name="left_margin">0</property>
+ <property name="right_margin">0</property>
+ <property name="indent">0</property>
+ <property name="text" translatable="yes"></property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">False</property>
+ <property name="resize">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child internal-child="appbar">
+ <widget class="GnomeAppBar" id="appbar1">
+ <property name="visible">True</property>
+ <property name="has_progress">False</property>
+ <property name="has_status">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/logview.glade b/vendor/Twisted-10.0.0/twisted/manhole/logview.glade
new file mode 100644
index 0000000000..1ec0b1f61b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/logview.glade
@@ -0,0 +1,39 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="logview">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Log</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <signal name="destroy" handler="on_logview_destroy" last_modification_time="Sun, 04 Jan 2004 22:16:59 GMT"/>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="loglist">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/service.py b/vendor/Twisted-10.0.0/twisted/manhole/service.py
new file mode 100644
index 0000000000..28572ad022
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/service.py
@@ -0,0 +1,399 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""L{twisted.manhole} L{PB<twisted.spread.pb>} service implementation.
+"""
+
+# twisted imports
+from twisted import copyright
+from twisted.spread import pb
+from twisted.python import log, failure
+from twisted.cred import portal
+from twisted.application import service
+from zope.interface import implements, Interface
+
+# sibling imports
+import explorer
+
+# system imports
+from cStringIO import StringIO
+
+import string
+import sys
+import traceback
+import types
+
+
+class FakeStdIO:
+ def __init__(self, type_, list):
+ self.type = type_
+ self.list = list
+
+ def write(self, text):
+ log.msg("%s: %s" % (self.type, string.strip(str(text))))
+ self.list.append((self.type, text))
+
+ def flush(self):
+ pass
+
+ def consolidate(self):
+ """Concatenate adjacent messages of same type into one.
+
+ Greatly cuts down on the number of elements, increasing
+ network transport friendliness considerably.
+ """
+ if not self.list:
+ return
+
+ inlist = self.list
+ outlist = []
+ last_type = inlist[0]
+ block_begin = 0
+ for i in xrange(1, len(self.list)):
+ (mtype, message) = inlist[i]
+ if mtype == last_type:
+ continue
+ else:
+ if (i - block_begin) == 1:
+ outlist.append(inlist[block_begin])
+ else:
+ messages = map(lambda l: l[1],
+ inlist[block_begin:i])
+ message = string.join(messages, '')
+ outlist.append((last_type, message))
+ last_type = mtype
+ block_begin = i
+
+
+class IManholeClient(Interface):
+ def console(list_of_messages):
+ """Takes a list of (type, message) pairs to display.
+
+ Types include:
+ - \"stdout\" -- string sent to sys.stdout
+
+ - \"stderr\" -- string sent to sys.stderr
+
+ - \"result\" -- string repr of the resulting value
+ of the expression
+
+ - \"exception\" -- a L{failure.Failure}
+ """
+
+ def receiveExplorer(xplorer):
+ """Receives an explorer.Explorer
+ """
+
+ def listCapabilities():
+ """List what manholey things I am capable of doing.
+
+ i.e. C{\"Explorer\"}, C{\"Failure\"}
+ """
+
+def runInConsole(command, console, globalNS=None, localNS=None,
+ filename=None, args=None, kw=None, unsafeTracebacks=False):
+ """Run this, directing all output to the specified console.
+
+ If command is callable, it will be called with the args and keywords
+ provided. Otherwise, command will be compiled and eval'd.
+ (Wouldn't you like a macro?)
+
+ Returns the command's return value.
+
+ The console is called with a list of (type, message) pairs for
+ display, see L{IManholeClient.console}.
+ """
+ output = []
+ fakeout = FakeStdIO("stdout", output)
+ fakeerr = FakeStdIO("stderr", output)
+ errfile = FakeStdIO("exception", output)
+ code = None
+ val = None
+ if filename is None:
+ filename = str(console)
+ if args is None:
+ args = ()
+ if kw is None:
+ kw = {}
+ if localNS is None:
+ localNS = globalNS
+ if (globalNS is None) and (not callable(command)):
+ raise ValueError("Need a namespace to evaluate the command in.")
+
+ try:
+ out = sys.stdout
+ err = sys.stderr
+ sys.stdout = fakeout
+ sys.stderr = fakeerr
+ try:
+ if callable(command):
+ val = apply(command, args, kw)
+ else:
+ try:
+ code = compile(command, filename, 'eval')
+ except:
+ code = compile(command, filename, 'single')
+
+ if code:
+ val = eval(code, globalNS, localNS)
+ finally:
+ sys.stdout = out
+ sys.stderr = err
+ except:
+ (eType, eVal, tb) = sys.exc_info()
+ fail = failure.Failure(eVal, eType, tb)
+ del tb
+ # In CVS reversion 1.35, there was some code here to fill in the
+ # source lines in the traceback for frames in the local command
+ # buffer. But I can't figure out when that's triggered, so it's
+ # going away in the conversion to Failure, until you bring it back.
+ errfile.write(pb.failure2Copyable(fail, unsafeTracebacks))
+
+ if console:
+ fakeout.consolidate()
+ console(output)
+
+ return val
+
+def _failureOldStyle(fail):
+ """Pre-Failure manhole representation of exceptions.
+
+ For compatibility with manhole clients without the \"Failure\"
+ capability.
+
+ A dictionary with two members:
+ - \'traceback\' -- traceback.extract_tb output; a list of tuples
+ (filename, line number, function name, text) suitable for
+ feeding to traceback.format_list.
+
+ - \'exception\' -- a list of one or more strings, each
+ ending in a newline. (traceback.format_exception_only output)
+ """
+ import linecache
+ tb = []
+ for f in fail.frames:
+ # (filename, line number, function name, text)
+ tb.append((f[1], f[2], f[0], linecache.getline(f[1], f[2])))
+
+ return {
+ 'traceback': tb,
+ 'exception': traceback.format_exception_only(fail.type, fail.value)
+ }
+
+# Capabilities clients are likely to have before they knew how to answer a
+# "listCapabilities" query.
+_defaultCapabilities = {
+ "Explorer": 'Set'
+ }
+
+class Perspective(pb.Avatar):
+ lastDeferred = 0
+ def __init__(self, service):
+ self.localNamespace = {
+ "service": service,
+ "avatar": self,
+ "_": None,
+ }
+ self.clients = {}
+ self.service = service
+
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ state['clients'] = {}
+ if state['localNamespace'].has_key("__builtins__"):
+ del state['localNamespace']['__builtins__']
+ return state
+
+ def attached(self, client, identity):
+ """A client has attached -- welcome them and add them to the list.
+ """
+ self.clients[client] = identity
+
+ host = ':'.join(map(str, client.broker.transport.getHost()[1:]))
+
+ msg = self.service.welcomeMessage % {
+ 'you': getattr(identity, 'name', str(identity)),
+ 'host': host,
+ 'longversion': copyright.longversion,
+ }
+
+ client.callRemote('console', [("stdout", msg)])
+
+ client.capabilities = _defaultCapabilities
+ client.callRemote('listCapabilities').addCallbacks(
+ self._cbClientCapable, self._ebClientCapable,
+ callbackArgs=(client,),errbackArgs=(client,))
+
+ def detached(self, client, identity):
+ try:
+ del self.clients[client]
+ except KeyError:
+ pass
+
+ def runInConsole(self, command, *args, **kw):
+ """Convience method to \"runInConsole with my stuff\".
+ """
+ return runInConsole(command,
+ self.console,
+ self.service.namespace,
+ self.localNamespace,
+ str(self.service),
+ args=args,
+ kw=kw,
+ unsafeTracebacks=self.service.unsafeTracebacks)
+
+
+ ### Methods for communicating to my clients.
+
+ def console(self, message):
+ """Pass a message to my clients' console.
+ """
+ clients = self.clients.keys()
+ origMessage = message
+ compatMessage = None
+ for client in clients:
+ try:
+ if not client.capabilities.has_key("Failure"):
+ if compatMessage is None:
+ compatMessage = origMessage[:]
+ for i in xrange(len(message)):
+ if ((message[i][0] == "exception") and
+ isinstance(message[i][1], failure.Failure)):
+ compatMessage[i] = (
+ message[i][0],
+ _failureOldStyle(message[i][1]))
+ client.callRemote('console', compatMessage)
+ else:
+ client.callRemote('console', message)
+ except pb.ProtocolError:
+ # Stale broker.
+ self.detached(client, None)
+
+ def receiveExplorer(self, objectLink):
+ """Pass an Explorer on to my clients.
+ """
+ clients = self.clients.keys()
+ for client in clients:
+ try:
+ client.callRemote('receiveExplorer', objectLink)
+ except pb.ProtocolError:
+ # Stale broker.
+ self.detached(client, None)
+
+
+ def _cbResult(self, val, dnum):
+ self.console([('result', "Deferred #%s Result: %r\n" %(dnum, val))])
+ return val
+
+ def _cbClientCapable(self, capabilities, client):
+ log.msg("client %x has %s" % (id(client), capabilities))
+ client.capabilities = capabilities
+
+ def _ebClientCapable(self, reason, client):
+ reason.trap(AttributeError)
+ log.msg("Couldn't get capabilities from %s, assuming defaults." %
+ (client,))
+
+ ### perspective_ methods, commands used by the client.
+
+ def perspective_do(self, expr):
+ """Evaluate the given expression, with output to the console.
+
+ The result is stored in the local variable '_', and its repr()
+ string is sent to the console as a \"result\" message.
+ """
+ log.msg(">>> %s" % expr)
+ val = self.runInConsole(expr)
+ if val is not None:
+ self.localNamespace["_"] = val
+ from twisted.internet.defer import Deferred
+ # TODO: client support for Deferred.
+ if isinstance(val, Deferred):
+ self.lastDeferred += 1
+ self.console([('result', "Waiting for Deferred #%s...\n" % self.lastDeferred)])
+ val.addBoth(self._cbResult, self.lastDeferred)
+ else:
+ self.console([("result", repr(val) + '\n')])
+ log.msg("<<<")
+
+ def perspective_explore(self, identifier):
+ """Browse the object obtained by evaluating the identifier.
+
+ The resulting ObjectLink is passed back through the client's
+ receiveBrowserObject method.
+ """
+ object = self.runInConsole(identifier)
+ if object:
+ expl = explorer.explorerPool.getExplorer(object, identifier)
+ self.receiveExplorer(expl)
+
+ def perspective_watch(self, identifier):
+ """Watch the object obtained by evaluating the identifier.
+
+ Whenever I think this object might have changed, I will pass
+ an ObjectLink of it back to the client's receiveBrowserObject
+ method.
+ """
+ raise NotImplementedError
+ object = self.runInConsole(identifier)
+ if object:
+ # Return an ObjectLink of this right away, before the watch.
+ oLink = self.runInConsole(self.browser.browseObject,
+ object, identifier)
+ self.receiveExplorer(oLink)
+
+ self.runInConsole(self.browser.watchObject,
+ object, identifier,
+ self.receiveExplorer)
+
+
+class Realm:
+
+ implements(portal.IRealm)
+
+ def __init__(self, service):
+ self.service = service
+ self._cache = {}
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective not in interfaces:
+ raise NotImplementedError("no interface")
+ if avatarId in self._cache:
+ p = self._cache[avatarId]
+ else:
+ p = Perspective(self.service)
+ p.attached(mind, avatarId)
+ def detached():
+ p.detached(mind, avatarId)
+ return (pb.IPerspective, p, detached)
+
+
+class Service(service.Service):
+
+ welcomeMessage = (
+ "\nHello %(you)s, welcome to Manhole "
+ "on %(host)s.\n"
+ "%(longversion)s.\n\n")
+
+ def __init__(self, unsafeTracebacks=False, namespace=None):
+ self.unsafeTracebacks = unsafeTracebacks
+ self.namespace = {
+ '__name__': '__manhole%x__' % (id(self),),
+ 'sys': sys
+ }
+ if namespace:
+ self.namespace.update(namespace)
+
+ def __getstate__(self):
+ """This returns the persistent state of this shell factory.
+ """
+ # TODO -- refactor this and twisted.reality.author.Author to
+ # use common functionality (perhaps the 'code' module?)
+ dict = self.__dict__.copy()
+ ns = dict['namespace'].copy()
+ dict['namespace'] = ns
+ if ns.has_key('__builtins__'):
+ del ns['__builtins__']
+ return dict
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/telnet.py b/vendor/Twisted-10.0.0/twisted/manhole/telnet.py
new file mode 100644
index 0000000000..2e6236471b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/telnet.py
@@ -0,0 +1,117 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Telnet-based shell."""
+
+# twisted imports
+from twisted.protocols import telnet
+from twisted.internet import protocol
+from twisted.python import log, failure
+
+# system imports
+import string, copy, sys
+from cStringIO import StringIO
+
+
+class Shell(telnet.Telnet):
+ """A Python command-line shell."""
+
+ def connectionMade(self):
+ telnet.Telnet.connectionMade(self)
+ self.lineBuffer = []
+
+ def loggedIn(self):
+ self.transport.write(">>> ")
+
+ def checkUserAndPass(self, username, password):
+ return ((self.factory.username == username) and (password == self.factory.password))
+
+ def write(self, data):
+ """Write some data to the transport.
+ """
+ self.transport.write(data)
+
+ def telnet_Command(self, cmd):
+ if self.lineBuffer:
+ if not cmd:
+ cmd = string.join(self.lineBuffer, '\n') + '\n\n\n'
+ self.doCommand(cmd)
+ self.lineBuffer = []
+ return "Command"
+ else:
+ self.lineBuffer.append(cmd)
+ self.transport.write("... ")
+ return "Command"
+ else:
+ self.doCommand(cmd)
+ return "Command"
+
+ def doCommand(self, cmd):
+
+ # TODO -- refactor this, Reality.author.Author, and the manhole shell
+ #to use common functionality (perhaps a twisted.python.code module?)
+ fn = '$telnet$'
+ result = None
+ try:
+ out = sys.stdout
+ sys.stdout = self
+ try:
+ code = compile(cmd,fn,'eval')
+ result = eval(code, self.factory.namespace)
+ except:
+ try:
+ code = compile(cmd, fn, 'exec')
+ exec code in self.factory.namespace
+ except SyntaxError, e:
+ if not self.lineBuffer and str(e)[:14] == "unexpected EOF":
+ self.lineBuffer.append(cmd)
+ self.transport.write("... ")
+ return
+ else:
+ failure.Failure().printTraceback(file=self)
+ log.deferr()
+ self.write('\r\n>>> ')
+ return
+ except:
+ io = StringIO()
+ failure.Failure().printTraceback(file=self)
+ log.deferr()
+ self.write('\r\n>>> ')
+ return
+ finally:
+ sys.stdout = out
+
+ self.factory.namespace['_'] = result
+ if result is not None:
+ self.transport.write(repr(result))
+ self.transport.write('\r\n')
+ self.transport.write(">>> ")
+
+
+
+class ShellFactory(protocol.Factory):
+ username = "admin"
+ password = "admin"
+ protocol = Shell
+ service = None
+
+ def __init__(self):
+ self.namespace = {
+ 'factory': self,
+ 'service': None,
+ '_': None
+ }
+
+ def setService(self, service):
+ self.namespace['service'] = self.service = service
+
+ def __getstate__(self):
+ """This returns the persistent state of this shell factory.
+ """
+ dict = self.__dict__
+ ns = copy.copy(dict['namespace'])
+ dict['namespace'] = ns
+ if ns.has_key('__builtins__'):
+ del ns['__builtins__']
+ return dict
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/ui/__init__.py b/vendor/Twisted-10.0.0/twisted/manhole/ui/__init__.py
new file mode 100644
index 0000000000..adfa9acb3d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/ui/__init__.py
@@ -0,0 +1,7 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Twisted Manhole UI: User interface for direct manipulation in Twisted.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/ui/gtk2manhole.glade b/vendor/Twisted-10.0.0/twisted/manhole/ui/gtk2manhole.glade
new file mode 100644
index 0000000000..423b3fb8b9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/ui/gtk2manhole.glade
@@ -0,0 +1,268 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="manholeWindow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Manhole</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">620</property>
+ <property name="default_height">320</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <signal name="delete_event" handler="_on_manholeWindow_delete_event" last_modification_time="Mon, 27 Jan 2003 05:14:26 GMT"/>
+
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+
+ <child>
+ <widget class="GtkMenuItem" id="menuitem4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menuitem4_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="openMenuItem">
+ <property name="visible">True</property>
+ <property name="label">gtk-open</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="_on_openMenuItem_activate" last_modification_time="Sun, 02 Feb 2003 18:44:51 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="reload_self">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Reload the manhole client code. (Only useful for client development.)</property>
+ <property name="label" translatable="yes">_Reload self</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_reload_self_activate" last_modification_time="Mon, 24 Feb 2003 00:15:10 GMT"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-revert-to-saved</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="quitMenuItem">
+ <property name="visible">True</property>
+ <property name="label">gtk-quit</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="_on_quitMenuItem_activate" last_modification_time="Sun, 02 Feb 2003 18:48:12 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menuitem5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menuitem5_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="cut1">
+ <property name="visible">True</property>
+ <property name="label">gtk-cut</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_cut1_activate" last_modification_time="Mon, 27 Jan 2003 04:50:50 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="copy1">
+ <property name="visible">True</property>
+ <property name="label">gtk-copy</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_copy1_activate" last_modification_time="Mon, 27 Jan 2003 04:50:50 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="paste1">
+ <property name="visible">True</property>
+ <property name="label">gtk-paste</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_paste1_activate" last_modification_time="Mon, 27 Jan 2003 04:50:50 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="delete1">
+ <property name="visible">True</property>
+ <property name="label">gtk-delete</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_delete1_activate" last_modification_time="Mon, 27 Jan 2003 04:50:50 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menuitem7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menuitem7_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="aboutMenuItem">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_About</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="_on_aboutMenuItem_activate" last_modification_time="Thu, 06 Feb 2003 19:49:53 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVPaned" id="vpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTextView" id="output">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="overwrite">False</property>
+ <property name="accepts_tab">True</property>
+ <property name="justification">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap_mode">GTK_WRAP_WORD</property>
+ <property name="cursor_visible">True</property>
+ <property name="pixels_above_lines">0</property>
+ <property name="pixels_below_lines">0</property>
+ <property name="pixels_inside_wrap">0</property>
+ <property name="left_margin">0</property>
+ <property name="right_margin">0</property>
+ <property name="indent">0</property>
+ <property name="text" translatable="yes"></property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTextView" id="input">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="editable">True</property>
+ <property name="overwrite">False</property>
+ <property name="accepts_tab">True</property>
+ <property name="justification">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap_mode">GTK_WRAP_NONE</property>
+ <property name="cursor_visible">True</property>
+ <property name="pixels_above_lines">0</property>
+ <property name="pixels_below_lines">0</property>
+ <property name="pixels_inside_wrap">0</property>
+ <property name="left_margin">0</property>
+ <property name="right_margin">0</property>
+ <property name="indent">0</property>
+ <property name="text" translatable="yes"></property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkStatusbar" id="statusbar1">
+ <property name="visible">True</property>
+ <property name="has_resize_grip">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/ui/gtk2manhole.py b/vendor/Twisted-10.0.0/twisted/manhole/ui/gtk2manhole.py
new file mode 100644
index 0000000000..d92f55f943
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/ui/gtk2manhole.py
@@ -0,0 +1,375 @@
+# -*- test-case-name: twisted.manhole.ui.test.test_gtk2manhole -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Manhole client with a GTK v2.x front-end.
+"""
+
+__version__ = '$Revision: 1.9 $'[11:-2]
+
+from twisted import copyright
+from twisted.internet import reactor
+from twisted.python import components, failure, log, util
+from twisted.python.reflect import prefixedMethodNames
+from twisted.spread import pb
+from twisted.spread.ui import gtk2util
+
+from twisted.manhole.service import IManholeClient
+from zope.interface import implements
+
+# The pygtk.require for version 2.0 has already been done by the reactor.
+import gtk
+
+import code, types, inspect
+
+# TODO:
+# Make wrap-mode a run-time option.
+# Explorer.
+# Code doesn't cleanly handle opening a second connection. Fix that.
+# Make some acknowledgement of when a command has completed, even if
+# it has no return value so it doesn't print anything to the console.
+
+class OfflineError(Exception):
+ pass
+
+class ManholeWindow(components.Componentized, gtk2util.GladeKeeper):
+ gladefile = util.sibpath(__file__, "gtk2manhole.glade")
+
+ _widgets = ('input','output','manholeWindow')
+
+ def __init__(self):
+ self.defaults = {}
+ gtk2util.GladeKeeper.__init__(self)
+ components.Componentized.__init__(self)
+
+ self.input = ConsoleInput(self._input)
+ self.input.toplevel = self
+ self.output = ConsoleOutput(self._output)
+
+ # Ugh. GladeKeeper actually isn't so good for composite objects.
+ # I want this connected to the ConsoleInput's handler, not something
+ # on this class.
+ self._input.connect("key_press_event", self.input._on_key_press_event)
+
+ def setDefaults(self, defaults):
+ self.defaults = defaults
+
+ def login(self):
+ client = self.getComponent(IManholeClient)
+ d = gtk2util.login(client, **self.defaults)
+ d.addCallback(self._cbLogin)
+ d.addCallback(client._cbLogin)
+ d.addErrback(self._ebLogin)
+
+ def _cbDisconnected(self, perspective):
+ self.output.append("%s went away. :(\n" % (perspective,), "local")
+ self._manholeWindow.set_title("Manhole")
+
+ def _cbLogin(self, perspective):
+ peer = perspective.broker.transport.getPeer()
+ self.output.append("Connected to %s\n" % (peer,), "local")
+ perspective.notifyOnDisconnect(self._cbDisconnected)
+ self._manholeWindow.set_title("Manhole - %s" % (peer))
+ return perspective
+
+ def _ebLogin(self, reason):
+ self.output.append("Login FAILED %s\n" % (reason.value,), "exception")
+
+ def _on_aboutMenuItem_activate(self, widget, *unused):
+ import sys
+ from os import path
+ self.output.append("""\
+a Twisted Manhole client
+ Versions:
+ %(twistedVer)s
+ Python %(pythonVer)s on %(platform)s
+ GTK %(gtkVer)s / PyGTK %(pygtkVer)s
+ %(module)s %(modVer)s
+http://twistedmatrix.com/
+""" % {'twistedVer': copyright.longversion,
+ 'pythonVer': sys.version.replace('\n', '\n '),
+ 'platform': sys.platform,
+ 'gtkVer': ".".join(map(str, gtk.gtk_version)),
+ 'pygtkVer': ".".join(map(str, gtk.pygtk_version)),
+ 'module': path.basename(__file__),
+ 'modVer': __version__,
+ }, "local")
+
+ def _on_openMenuItem_activate(self, widget, userdata=None):
+ self.login()
+
+ def _on_manholeWindow_delete_event(self, widget, *unused):
+ reactor.stop()
+
+ def _on_quitMenuItem_activate(self, widget, *unused):
+ reactor.stop()
+
+ def on_reload_self_activate(self, *unused):
+ from twisted.python import rebuild
+ rebuild.rebuild(inspect.getmodule(self.__class__))
+
+
+tagdefs = {
+ 'default': {"family": "monospace"},
+ # These are message types we get from the server.
+ 'stdout': {"foreground": "black"},
+ 'stderr': {"foreground": "#AA8000"},
+ 'result': {"foreground": "blue"},
+ 'exception': {"foreground": "red"},
+ # Messages generate locally.
+ 'local': {"foreground": "#008000"},
+ 'log': {"foreground": "#000080"},
+ 'command': {"foreground": "#666666"},
+ }
+
+# TODO: Factor Python console stuff back out to pywidgets.
+
+class ConsoleOutput:
+ _willScroll = None
+ def __init__(self, textView):
+ self.textView = textView
+ self.buffer = textView.get_buffer()
+
+ # TODO: Make this a singleton tag table.
+ for name, props in tagdefs.iteritems():
+ tag = self.buffer.create_tag(name)
+ # This can be done in the constructor in newer pygtk (post 1.99.14)
+ for k, v in props.iteritems():
+ tag.set_property(k, v)
+
+ self.buffer.tag_table.lookup("default").set_priority(0)
+
+ self._captureLocalLog()
+
+ def _captureLocalLog(self):
+ return log.startLogging(_Notafile(self, "log"), setStdout=False)
+
+ def append(self, text, kind=None):
+ # XXX: It seems weird to have to do this thing with always applying
+ # a 'default' tag. Can't we change the fundamental look instead?
+ tags = ["default"]
+ if kind is not None:
+ tags.append(kind)
+
+ self.buffer.insert_with_tags_by_name(self.buffer.get_end_iter(),
+ text, *tags)
+ # Silly things, the TextView needs to update itself before it knows
+ # where the bottom is.
+ if self._willScroll is None:
+ self._willScroll = gtk.idle_add(self._scrollDown)
+
+ def _scrollDown(self, *unused):
+ self.textView.scroll_to_iter(self.buffer.get_end_iter(), 0,
+ True, 1.0, 1.0)
+ self._willScroll = None
+ return False
+
+class History:
+ def __init__(self, maxhist=10000):
+ self.ringbuffer = ['']
+ self.maxhist = maxhist
+ self.histCursor = 0
+
+ def append(self, htext):
+ self.ringbuffer.insert(-1, htext)
+ if len(self.ringbuffer) > self.maxhist:
+ self.ringbuffer.pop(0)
+ self.histCursor = len(self.ringbuffer) - 1
+ self.ringbuffer[-1] = ''
+
+ def move(self, prevnext=1):
+ '''
+ Return next/previous item in the history, stopping at top/bottom.
+ '''
+ hcpn = self.histCursor + prevnext
+ if hcpn >= 0 and hcpn < len(self.ringbuffer):
+ self.histCursor = hcpn
+ return self.ringbuffer[hcpn]
+ else:
+ return None
+
+ def histup(self, textbuffer):
+ if self.histCursor == len(self.ringbuffer) - 1:
+ si, ei = textbuffer.get_start_iter(), textbuffer.get_end_iter()
+ self.ringbuffer[-1] = textbuffer.get_text(si,ei)
+ newtext = self.move(-1)
+ if newtext is None:
+ return
+ textbuffer.set_text(newtext)
+
+ def histdown(self, textbuffer):
+ newtext = self.move(1)
+ if newtext is None:
+ return
+ textbuffer.set_text(newtext)
+
+
+class ConsoleInput:
+ toplevel, rkeymap = None, None
+ __debug = False
+
+ def __init__(self, textView):
+ self.textView=textView
+ self.rkeymap = {}
+ self.history = History()
+ for name in prefixedMethodNames(self.__class__, "key_"):
+ keysymName = name.split("_")[-1]
+ self.rkeymap[getattr(gtk.keysyms, keysymName)] = keysymName
+
+ def _on_key_press_event(self, entry, event):
+ stopSignal = False
+ ksym = self.rkeymap.get(event.keyval, None)
+
+ mods = []
+ for prefix, mask in [('ctrl', gtk.gdk.CONTROL_MASK), ('shift', gtk.gdk.SHIFT_MASK)]:
+ if event.state & mask:
+ mods.append(prefix)
+
+ if mods:
+ ksym = '_'.join(mods + [ksym])
+
+ if ksym:
+ rvalue = getattr(
+ self, 'key_%s' % ksym, lambda *a, **kw: None)(entry, event)
+
+ if self.__debug:
+ print ksym
+ return rvalue
+
+ def getText(self):
+ buffer = self.textView.get_buffer()
+ iter1, iter2 = buffer.get_bounds()
+ text = buffer.get_text(iter1, iter2, False)
+ return text
+
+ def setText(self, text):
+ self.textView.get_buffer().set_text(text)
+
+ def key_Return(self, entry, event):
+ text = self.getText()
+ # Figure out if that Return meant "next line" or "execute."
+ try:
+ c = code.compile_command(text)
+ except SyntaxError, e:
+ # This could conceivably piss you off if the client's python
+ # doesn't accept keywords that are known to the manhole's
+ # python.
+ point = buffer.get_iter_at_line_offset(e.lineno, e.offset)
+ buffer.place(point)
+ # TODO: Componentize!
+ self.toplevel.output.append(str(e), "exception")
+ except (OverflowError, ValueError), e:
+ self.toplevel.output.append(str(e), "exception")
+ else:
+ if c is not None:
+ self.sendMessage()
+ # Don't insert Return as a newline in the buffer.
+ self.history.append(text)
+ self.clear()
+ # entry.emit_stop_by_name("key_press_event")
+ return True
+ else:
+ # not a complete code block
+ return False
+
+ return False
+
+ def key_Up(self, entry, event):
+ # if I'm at the top, previous history item.
+ textbuffer = self.textView.get_buffer()
+ if textbuffer.get_iter_at_mark(textbuffer.get_insert()).get_line() == 0:
+ self.history.histup(textbuffer)
+ return True
+ return False
+
+ def key_Down(self, entry, event):
+ textbuffer = self.textView.get_buffer()
+ if textbuffer.get_iter_at_mark(textbuffer.get_insert()).get_line() == (
+ textbuffer.get_line_count() - 1):
+ self.history.histdown(textbuffer)
+ return True
+ return False
+
+ key_ctrl_p = key_Up
+ key_ctrl_n = key_Down
+
+ def key_ctrl_shift_F9(self, entry, event):
+ if self.__debug:
+ import pdb; pdb.set_trace()
+
+ def clear(self):
+ buffer = self.textView.get_buffer()
+ buffer.delete(*buffer.get_bounds())
+
+ def sendMessage(self):
+ buffer = self.textView.get_buffer()
+ iter1, iter2 = buffer.get_bounds()
+ text = buffer.get_text(iter1, iter2, False)
+ self.toplevel.output.append(pythonify(text), 'command')
+ # TODO: Componentize better!
+ try:
+ return self.toplevel.getComponent(IManholeClient).do(text)
+ except OfflineError:
+ self.toplevel.output.append("Not connected, command not sent.\n",
+ "exception")
+
+
+def pythonify(text):
+ '''
+ Make some text appear as though it was typed in at a Python prompt.
+ '''
+ lines = text.split('\n')
+ lines[0] = '>>> ' + lines[0]
+ return '\n... '.join(lines) + '\n'
+
+class _Notafile:
+ """Curry to make failure.printTraceback work with the output widget."""
+ def __init__(self, output, kind):
+ self.output = output
+ self.kind = kind
+
+ def write(self, txt):
+ self.output.append(txt, self.kind)
+
+ def flush(self):
+ pass
+
+class ManholeClient(components.Adapter, pb.Referenceable):
+ implements(IManholeClient)
+
+ capabilities = {
+# "Explorer": 'Set',
+ "Failure": 'Set'
+ }
+
+ def _cbLogin(self, perspective):
+ self.perspective = perspective
+ perspective.notifyOnDisconnect(self._cbDisconnected)
+ return perspective
+
+ def remote_console(self, messages):
+ for kind, content in messages:
+ if isinstance(content, types.StringTypes):
+ self.original.output.append(content, kind)
+ elif (kind == "exception") and isinstance(content, failure.Failure):
+ content.printTraceback(_Notafile(self.original.output,
+ "exception"))
+ else:
+ self.original.output.append(str(content), kind)
+
+ def remote_receiveExplorer(self, xplorer):
+ pass
+
+ def remote_listCapabilities(self):
+ return self.capabilities
+
+ def _cbDisconnected(self, perspective):
+ self.perspective = None
+
+ def do(self, text):
+ if self.perspective is None:
+ raise OfflineError
+ return self.perspective.callRemote("do", text)
+
+components.registerAdapter(ManholeClient, ManholeWindow, IManholeClient)
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/ui/test/__init__.py b/vendor/Twisted-10.0.0/twisted/manhole/ui/test/__init__.py
new file mode 100644
index 0000000000..36214ba77e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/ui/test/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+"""
+Tests for the L{twisted.manhole.ui} package.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/manhole/ui/test/test_gtk2manhole.py b/vendor/Twisted-10.0.0/twisted/manhole/ui/test/test_gtk2manhole.py
new file mode 100644
index 0000000000..b59f9370bc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/manhole/ui/test/test_gtk2manhole.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+"""
+Tests for GTK2 GUI manhole.
+"""
+
+skip = False
+
+try:
+ import pygtk
+ pygtk.require("2.0")
+except:
+ skip = "GTK 2.0 not available"
+else:
+ try:
+ import gtk
+ except ImportError:
+ skip = "GTK 2.0 not available"
+ except RuntimeError:
+ skip = "Old version of GTK 2.0 requires DISPLAY, and we don't have one."
+ else:
+ if gtk.gtk_version[0] == 1:
+ skip = "Requested GTK 2.0, but 1.0 was already imported."
+ else:
+ from twisted.manhole.ui.gtk2manhole import ConsoleInput
+
+from twisted.trial.unittest import TestCase
+
+from twisted.python.reflect import prefixedMethodNames
+
+class ConsoleInputTests(TestCase):
+ """
+ Tests for L{ConsoleInput}.
+ """
+
+ def test_reverseKeymap(self):
+ """
+ Verify that a L{ConsoleInput} has a reverse mapping of the keysym names
+ it needs for event handling to their corresponding keysym.
+ """
+ ci = ConsoleInput(None)
+ for eventName in prefixedMethodNames(ConsoleInput, 'key_'):
+ keysymName = eventName.split("_")[-1]
+ keysymValue = getattr(gtk.keysyms, keysymName)
+ self.assertEqual(ci.rkeymap[keysymValue], keysymName)
+
+
+ skip = skip
+
diff --git a/vendor/Twisted-10.0.0/twisted/names/__init__.py b/vendor/Twisted-10.0.0/twisted/names/__init__.py
new file mode 100644
index 0000000000..e6a29c1350
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/__init__.py
@@ -0,0 +1,7 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""Resolving Internet Names"""
+
+from twisted.names._version import version
+__version__ = version.short()
diff --git a/vendor/Twisted-10.0.0/twisted/names/_version.py b/vendor/Twisted-10.0.0/twisted/names/_version.py
new file mode 100644
index 0000000000..2983f5e3d9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/_version.py
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twisted.names', 10, 0, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/names/authority.py b/vendor/Twisted-10.0.0/twisted/names/authority.py
new file mode 100644
index 0000000000..991bdb6479
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/authority.py
@@ -0,0 +1,322 @@
+# -*- test-case-name: twisted.names.test.test_names -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from __future__ import nested_scopes
+
+import os
+import time
+
+from twisted.names import dns
+from twisted.internet import defer
+from twisted.python import failure
+
+import common
+
+def getSerial(filename = '/tmp/twisted-names.serial'):
+ """Return a monotonically increasing (across program runs) integer.
+
+ State is stored in the given file. If it does not exist, it is
+ created with rw-/---/--- permissions.
+ """
+ serial = time.strftime('%Y%m%d')
+
+ o = os.umask(0177)
+ try:
+ if not os.path.exists(filename):
+ f = file(filename, 'w')
+ f.write(serial + ' 0')
+ f.close()
+ finally:
+ os.umask(o)
+
+ serialFile = file(filename, 'r')
+ lastSerial, ID = serialFile.readline().split()
+ ID = (lastSerial == serial) and (int(ID) + 1) or 0
+ serialFile.close()
+ serialFile = file(filename, 'w')
+ serialFile.write('%s %d' % (serial, ID))
+ serialFile.close()
+ serial = serial + ('%02d' % (ID,))
+ return serial
+
+
+#class LookupCacherMixin(object):
+# _cache = None
+#
+# def _lookup(self, name, cls, type, timeout = 10):
+# if not self._cache:
+# self._cache = {}
+# self._meth = super(LookupCacherMixin, self)._lookup
+#
+# if self._cache.has_key((name, cls, type)):
+# return self._cache[(name, cls, type)]
+# else:
+# r = self._meth(name, cls, type, timeout)
+# self._cache[(name, cls, type)] = r
+# return r
+
+
+class FileAuthority(common.ResolverBase):
+ """An Authority that is loaded from a file."""
+
+ soa = None
+ records = None
+
+ def __init__(self, filename):
+ common.ResolverBase.__init__(self)
+ self.loadFile(filename)
+ self._cache = {}
+
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+# print 'setstate ', self.soa
+
+ def _lookup(self, name, cls, type, timeout = None):
+ cnames = []
+ results = []
+ authority = []
+ additional = []
+ default_ttl = max(self.soa[1].minimum, self.soa[1].expire)
+
+ domain_records = self.records.get(name.lower())
+
+ if domain_records:
+ for record in domain_records:
+ if record.ttl is not None:
+ ttl = record.ttl
+ else:
+ ttl = default_ttl
+
+ if record.TYPE == type or type == dns.ALL_RECORDS:
+ results.append(
+ dns.RRHeader(name, record.TYPE, dns.IN, ttl, record, auth=True)
+ )
+ elif record.TYPE == dns.NS and type != dns.ALL_RECORDS:
+ authority.append(
+ dns.RRHeader(name, record.TYPE, dns.IN, ttl, record, auth=True)
+ )
+ if record.TYPE == dns.CNAME:
+ cnames.append(
+ dns.RRHeader(name, record.TYPE, dns.IN, ttl, record, auth=True)
+ )
+ if not results:
+ results = cnames
+
+ for record in results + authority:
+ section = {dns.NS: additional, dns.CNAME: results, dns.MX: additional}.get(record.type)
+ if section is not None:
+ n = str(record.payload.name)
+ for rec in self.records.get(n.lower(), ()):
+ if rec.TYPE == dns.A:
+ section.append(
+ dns.RRHeader(n, dns.A, dns.IN, rec.ttl or default_ttl, rec, auth=True)
+ )
+
+ return defer.succeed((results, authority, additional))
+ else:
+ if name.lower().endswith(self.soa[0].lower()):
+ # We are the authority and we didn't find it. Goodbye.
+ return defer.fail(failure.Failure(dns.AuthoritativeDomainError(name)))
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+
+
+ def lookupZone(self, name, timeout = 10):
+ if self.soa[0].lower() == name.lower():
+ # Wee hee hee hooo yea
+ default_ttl = max(self.soa[1].minimum, self.soa[1].expire)
+ if self.soa[1].ttl is not None:
+ soa_ttl = self.soa[1].ttl
+ else:
+ soa_ttl = default_ttl
+ results = [dns.RRHeader(self.soa[0], dns.SOA, dns.IN, soa_ttl, self.soa[1], auth=True)]
+ for (k, r) in self.records.items():
+ for rec in r:
+ if rec.ttl is not None:
+ ttl = rec.ttl
+ else:
+ ttl = default_ttl
+ if rec.TYPE != dns.SOA:
+ results.append(dns.RRHeader(k, rec.TYPE, dns.IN, ttl, rec, auth=True))
+ results.append(results[0])
+ return defer.succeed((results, (), ()))
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+
+ def _cbAllRecords(self, results):
+ ans, auth, add = [], [], []
+ for res in results:
+ if res[0]:
+ ans.extend(res[1][0])
+ auth.extend(res[1][1])
+ add.extend(res[1][2])
+ return ans, auth, add
+
+
+class PySourceAuthority(FileAuthority):
+ """A FileAuthority that is built up from Python source code."""
+
+ def loadFile(self, filename):
+ g, l = self.setupConfigNamespace(), {}
+ execfile(filename, g, l)
+ if not l.has_key('zone'):
+ raise ValueError, "No zone defined in " + filename
+
+ self.records = {}
+ for rr in l['zone']:
+ if isinstance(rr[1], dns.Record_SOA):
+ self.soa = rr
+ self.records.setdefault(rr[0].lower(), []).append(rr[1])
+
+
+ def wrapRecord(self, type):
+ return lambda name, *arg, **kw: (name, type(*arg, **kw))
+
+
+ def setupConfigNamespace(self):
+ r = {}
+ items = dns.__dict__.iterkeys()
+ for record in [x for x in items if x.startswith('Record_')]:
+ type = getattr(dns, record)
+ f = self.wrapRecord(type)
+ r[record[len('Record_'):]] = f
+ return r
+
+
+class BindAuthority(FileAuthority):
+ """An Authority that loads BIND configuration files"""
+
+ def loadFile(self, filename):
+ self.origin = os.path.basename(filename) + '.' # XXX - this might suck
+ lines = open(filename).readlines()
+ lines = self.stripComments(lines)
+ lines = self.collapseContinuations(lines)
+ self.parseLines(lines)
+
+
+ def stripComments(self, lines):
+ return [
+ a.find(';') == -1 and a or a[:a.find(';')] for a in [
+ b.strip() for b in lines
+ ]
+ ]
+
+
+ def collapseContinuations(self, lines):
+ L = []
+ state = 0
+ for line in lines:
+ if state == 0:
+ if line.find('(') == -1:
+ L.append(line)
+ else:
+ L.append(line[:line.find('(')])
+ state = 1
+ else:
+ if line.find(')') != -1:
+ L[-1] += ' ' + line[:line.find(')')]
+ state = 0
+ else:
+ L[-1] += ' ' + line
+ lines = L
+ L = []
+ for line in lines:
+ L.append(line.split())
+ return filter(None, L)
+
+
+ def parseLines(self, lines):
+ TTL = 60 * 60 * 3
+ ORIGIN = self.origin
+
+ self.records = {}
+
+ for (line, index) in zip(lines, range(len(lines))):
+ if line[0] == '$TTL':
+ TTL = dns.str2time(line[1])
+ elif line[0] == '$ORIGIN':
+ ORIGIN = line[1]
+ elif line[0] == '$INCLUDE': # XXX - oh, fuck me
+ raise NotImplementedError('$INCLUDE directive not implemented')
+ elif line[0] == '$GENERATE':
+ raise NotImplementedError('$GENERATE directive not implemented')
+ else:
+ self.parseRecordLine(ORIGIN, TTL, line)
+
+
+ def addRecord(self, owner, ttl, type, domain, cls, rdata):
+ if not domain.endswith('.'):
+ domain = domain + '.' + owner
+ else:
+ domain = domain[:-1]
+ f = getattr(self, 'class_%s' % cls, None)
+ if f:
+ f(ttl, type, domain, rdata)
+ else:
+ raise NotImplementedError, "Record class %r not supported" % cls
+
+
+ def class_IN(self, ttl, type, domain, rdata):
+ record = getattr(dns, 'Record_%s' % type, None)
+ if record:
+ r = record(*rdata)
+ r.ttl = ttl
+ self.records.setdefault(domain.lower(), []).append(r)
+
+ print 'Adding IN Record', domain, ttl, r
+ if type == 'SOA':
+ self.soa = (domain, r)
+ else:
+ raise NotImplementedError, "Record type %r not supported" % type
+
+
+ #
+ # This file ends here. Read no further.
+ #
+ def parseRecordLine(self, origin, ttl, line):
+ MARKERS = dns.QUERY_CLASSES.values() + dns.QUERY_TYPES.values()
+ cls = 'IN'
+ owner = origin
+
+ if line[0] == '@':
+ line = line[1:]
+ owner = origin
+# print 'default owner'
+ elif not line[0].isdigit() and line[0] not in MARKERS:
+ owner = line[0]
+ line = line[1:]
+# print 'owner is ', owner
+
+ if line[0].isdigit() or line[0] in MARKERS:
+ domain = owner
+ owner = origin
+# print 'woops, owner is ', owner, ' domain is ', domain
+ else:
+ domain = line[0]
+ line = line[1:]
+# print 'domain is ', domain
+
+ if line[0] in dns.QUERY_CLASSES.values():
+ cls = line[0]
+ line = line[1:]
+# print 'cls is ', cls
+ if line[0].isdigit():
+ ttl = int(line[0])
+ line = line[1:]
+# print 'ttl is ', ttl
+ elif line[0].isdigit():
+ ttl = int(line[0])
+ line = line[1:]
+# print 'ttl is ', ttl
+ if line[0] in dns.QUERY_CLASSES.values():
+ cls = line[0]
+ line = line[1:]
+# print 'cls is ', cls
+
+ type = line[0]
+# print 'type is ', type
+ rdata = line[1:]
+# print 'rdata is ', rdata
+
+ self.addRecord(owner, ttl, type, domain, cls, rdata)
diff --git a/vendor/Twisted-10.0.0/twisted/names/cache.py b/vendor/Twisted-10.0.0/twisted/names/cache.py
new file mode 100644
index 0000000000..3f16d27996
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/cache.py
@@ -0,0 +1,96 @@
+# -*- test-case-name: twisted.names.test -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import time
+
+from zope.interface import implements
+
+from twisted.names import dns
+from twisted.python import failure, log
+from twisted.internet import interfaces, defer
+
+import common
+
+class CacheResolver(common.ResolverBase):
+ """A resolver that serves records from a local, memory cache."""
+
+ implements(interfaces.IResolver)
+
+ cache = None
+
+ def __init__(self, cache = None, verbose = 0):
+ common.ResolverBase.__init__(self)
+
+ if cache is None:
+ cache = {}
+ self.cache = cache
+ self.verbose = verbose
+ self.cancel = {}
+
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+
+ now = time.time()
+ for (k, (when, (ans, add, ns))) in self.cache.items():
+ diff = now - when
+ for rec in ans + add + ns:
+ if rec.ttl < diff:
+ del self.cache[k]
+ break
+
+
+ def __getstate__(self):
+ for c in self.cancel.values():
+ c.cancel()
+ self.cancel.clear()
+ return self.__dict__
+
+
+ def _lookup(self, name, cls, type, timeout):
+ now = time.time()
+ q = dns.Query(name, type, cls)
+ try:
+ when, (ans, auth, add) = self.cache[q]
+ except KeyError:
+ if self.verbose > 1:
+ log.msg('Cache miss for ' + repr(name))
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+ else:
+ if self.verbose:
+ log.msg('Cache hit for ' + repr(name))
+ diff = now - when
+ return defer.succeed((
+ [dns.RRHeader(str(r.name), r.type, r.cls, r.ttl - diff, r.payload) for r in ans],
+ [dns.RRHeader(str(r.name), r.type, r.cls, r.ttl - diff, r.payload) for r in auth],
+ [dns.RRHeader(str(r.name), r.type, r.cls, r.ttl - diff, r.payload) for r in add]
+ ))
+
+
+ def lookupAllRecords(self, name, timeout = None):
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+
+
+ def cacheResult(self, query, payload):
+ if self.verbose > 1:
+ log.msg('Adding %r to cache' % query)
+
+ self.cache[query] = (time.time(), payload)
+
+ if self.cancel.has_key(query):
+ self.cancel[query].cancel()
+
+ s = list(payload[0]) + list(payload[1]) + list(payload[2])
+ m = s[0].ttl
+ for r in s:
+ m = min(m, r.ttl)
+
+ from twisted.internet import reactor
+ self.cancel[query] = reactor.callLater(m, self.clearEntry, query)
+
+
+ def clearEntry(self, query):
+ del self.cache[query]
+ del self.cancel[query]
diff --git a/vendor/Twisted-10.0.0/twisted/names/client.py b/vendor/Twisted-10.0.0/twisted/names/client.py
new file mode 100644
index 0000000000..6bfdc9a20e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/client.py
@@ -0,0 +1,928 @@
+# -*- test-case-name: twisted.names.test.test_names -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Asynchronous client DNS
+
+The functions exposed in this module can be used for asynchronous name
+resolution and dns queries.
+
+If you need to create a resolver with specific requirements, such as needing to
+do queries against a particular host, the L{createResolver} function will
+return an C{IResolver}.
+
+Future plans: Proper nameserver acquisition on Windows/MacOS,
+better caching, respect timeouts
+
+@author: Jp Calderone
+"""
+
+import os
+import errno
+import warnings
+
+from zope.interface import implements
+
+# Twisted imports
+from twisted.python.runtime import platform
+from twisted.internet import error, defer, protocol, interfaces
+from twisted.python import log, failure
+from twisted.python.deprecate import getWarningMethod
+from twisted.names import dns, common
+
+
+class Resolver(common.ResolverBase):
+ """
+ @ivar _waiting: A C{dict} mapping tuple keys of query name/type/class to
+ Deferreds which will be called back with the result of those queries.
+ This is used to avoid issuing the same query more than once in
+ parallel. This is more efficient on the network and helps avoid a
+ "birthday paradox" attack by keeping the number of outstanding requests
+ for a particular query fixed at one instead of allowing the attacker to
+ raise it to an arbitrary number.
+
+ @ivar _reactor: A provider of L{IReactorTCP}, L{IReactorUDP}, and
+ L{IReactorTime} which will be used to set up network resources and
+ track timeouts.
+ """
+ implements(interfaces.IResolver)
+
+ index = 0
+ timeout = None
+
+ factory = None
+ servers = None
+ dynServers = ()
+ pending = None
+ connections = None
+
+ resolv = None
+ _lastResolvTime = None
+ _resolvReadInterval = 60
+
+ def _getProtocol(self):
+ getWarningMethod()(
+ "Resolver.protocol is deprecated; use Resolver.queryUDP instead.",
+ PendingDeprecationWarning,
+ stacklevel=0)
+ self.protocol = dns.DNSDatagramProtocol(self)
+ return self.protocol
+ protocol = property(_getProtocol)
+
+
+ def __init__(self, resolv=None, servers=None, timeout=(1, 3, 11, 45), reactor=None):
+ """
+ Construct a resolver which will query domain name servers listed in
+ the C{resolv.conf(5)}-format file given by C{resolv} as well as
+ those in the given C{servers} list. Servers are queried in a
+ round-robin fashion. If given, C{resolv} is periodically checked
+ for modification and re-parsed if it is noticed to have changed.
+
+ @type servers: C{list} of C{(str, int)} or C{None}
+ @param servers: If not None, interpreted as a list of (host, port)
+ pairs specifying addresses of domain name servers to attempt to use
+ for this lookup. Host addresses should be in IPv4 dotted-quad
+ form. If specified, overrides C{resolv}.
+
+ @type resolv: C{str}
+ @param resolv: Filename to read and parse as a resolver(5)
+ configuration file.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Default number of seconds after which to reissue the
+ query. When the last timeout expires, the query is considered
+ failed.
+
+ @param reactor: A provider of L{IReactorTime}, L{IReactorUDP}, and
+ L{IReactorTCP} which will be used to establish connections, listen
+ for DNS datagrams, and enforce timeouts. If not provided, the
+ global reactor will be used.
+
+ @raise ValueError: Raised if no nameserver addresses can be found.
+ """
+ common.ResolverBase.__init__(self)
+
+ if reactor is None:
+ from twisted.internet import reactor
+ self._reactor = reactor
+
+ self.timeout = timeout
+
+ if servers is None:
+ self.servers = []
+ else:
+ self.servers = servers
+
+ self.resolv = resolv
+
+ if not len(self.servers) and not resolv:
+ raise ValueError, "No nameservers specified"
+
+ self.factory = DNSClientFactory(self, timeout)
+ self.factory.noisy = 0 # Be quiet by default
+
+ self.connections = []
+ self.pending = []
+
+ self._waiting = {}
+
+ self.maybeParseConfig()
+
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ d['connections'] = []
+ d['_parseCall'] = None
+ return d
+
+
+ def __setstate__(self, state):
+ self.__dict__.update(state)
+ self.maybeParseConfig()
+
+
+ def maybeParseConfig(self):
+ if self.resolv is None:
+ # Don't try to parse it, don't set up a call loop
+ return
+
+ try:
+ resolvConf = file(self.resolv)
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ # Missing resolv.conf is treated the same as an empty resolv.conf
+ self.parseConfig(())
+ else:
+ raise
+ else:
+ mtime = os.fstat(resolvConf.fileno()).st_mtime
+ if mtime != self._lastResolvTime:
+ log.msg('%s changed, reparsing' % (self.resolv,))
+ self._lastResolvTime = mtime
+ self.parseConfig(resolvConf)
+
+ # Check again in a little while
+ self._parseCall = self._reactor.callLater(
+ self._resolvReadInterval, self.maybeParseConfig)
+
+
+ def parseConfig(self, resolvConf):
+ servers = []
+ for L in resolvConf:
+ L = L.strip()
+ if L.startswith('nameserver'):
+ resolver = (L.split()[1], dns.PORT)
+ servers.append(resolver)
+ log.msg("Resolver added %r to server list" % (resolver,))
+ elif L.startswith('domain'):
+ try:
+ self.domain = L.split()[1]
+ except IndexError:
+ self.domain = ''
+ self.search = None
+ elif L.startswith('search'):
+ try:
+ self.search = L.split()[1:]
+ except IndexError:
+ self.search = ''
+ self.domain = None
+ if not servers:
+ servers.append(('127.0.0.1', dns.PORT))
+ self.dynServers = servers
+
+
+ def pickServer(self):
+ """
+ Return the address of a nameserver.
+
+ TODO: Weight servers for response time so faster ones can be
+ preferred.
+ """
+ if not self.servers and not self.dynServers:
+ return None
+ serverL = len(self.servers)
+ dynL = len(self.dynServers)
+
+ self.index += 1
+ self.index %= (serverL + dynL)
+ if self.index < serverL:
+ return self.servers[self.index]
+ else:
+ return self.dynServers[self.index - serverL]
+
+
+ def _connectedProtocol(self):
+ """
+ Return a new L{DNSDatagramProtocol} bound to a randomly selected port
+ number.
+ """
+ if 'protocol' in self.__dict__:
+ # Some code previously asked for or set the deprecated `protocol`
+ # attribute, so it probably expects that object to be used for
+ # queries. Give it back and skip the super awesome source port
+ # randomization logic. This is actually a really good reason to
+ # remove this deprecated backward compatibility as soon as
+ # possible. -exarkun
+ return self.protocol
+ proto = dns.DNSDatagramProtocol(self)
+ while True:
+ try:
+ self._reactor.listenUDP(dns.randomSource(), proto)
+ except error.CannotListenError:
+ pass
+ else:
+ return proto
+
+
+ def connectionMade(self, protocol):
+ self.connections.append(protocol)
+ for (d, q, t) in self.pending:
+ self.queryTCP(q, t).chainDeferred(d)
+ del self.pending[:]
+
+
+ def messageReceived(self, message, protocol, address = None):
+ log.msg("Unexpected message (%d) received from %r" % (message.id, address))
+
+
+ def _query(self, *args):
+ """
+ Get a new L{DNSDatagramProtocol} instance from L{_connectedProtocol},
+ issue a query to it using C{*args}, and arrange for it to be
+ disconnected from its transport after the query completes.
+
+ @param *args: Positional arguments to be passed to
+ L{DNSDatagramProtocol.query}.
+
+ @return: A L{Deferred} which will be called back with the result of the
+ query.
+ """
+ protocol = self._connectedProtocol()
+ d = protocol.query(*args)
+ def cbQueried(result):
+ protocol.transport.stopListening()
+ return result
+ d.addBoth(cbQueried)
+ return d
+
+
+ def queryUDP(self, queries, timeout = None):
+ """
+ Make a number of DNS queries via UDP.
+
+ @type queries: A C{list} of C{dns.Query} instances
+ @param queries: The queries to make.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ @raise C{twisted.internet.defer.TimeoutError}: When the query times
+ out.
+ """
+ if timeout is None:
+ timeout = self.timeout
+
+ addresses = self.servers + list(self.dynServers)
+ if not addresses:
+ return defer.fail(IOError("No domain name servers available"))
+
+ # Make sure we go through servers in the list in the order they were
+ # specified.
+ addresses.reverse()
+
+ used = addresses.pop()
+ d = self._query(used, queries, timeout[0])
+ d.addErrback(self._reissue, addresses, [used], queries, timeout)
+ return d
+
+
+ def _reissue(self, reason, addressesLeft, addressesUsed, query, timeout):
+ reason.trap(dns.DNSQueryTimeoutError)
+
+ # If there are no servers left to be tried, adjust the timeout
+ # to the next longest timeout period and move all the
+ # "used" addresses back to the list of addresses to try.
+ if not addressesLeft:
+ addressesLeft = addressesUsed
+ addressesLeft.reverse()
+ addressesUsed = []
+ timeout = timeout[1:]
+
+ # If all timeout values have been used this query has failed. Tell the
+ # protocol we're giving up on it and return a terminal timeout failure
+ # to our caller.
+ if not timeout:
+ return failure.Failure(defer.TimeoutError(query))
+
+ # Get an address to try. Take it out of the list of addresses
+ # to try and put it ino the list of already tried addresses.
+ address = addressesLeft.pop()
+ addressesUsed.append(address)
+
+ # Issue a query to a server. Use the current timeout. Add this
+ # function as a timeout errback in case another retry is required.
+ d = self._query(address, query, timeout[0], reason.value.id)
+ d.addErrback(self._reissue, addressesLeft, addressesUsed, query, timeout)
+ return d
+
+
+ def queryTCP(self, queries, timeout = 10):
+ """
+ Make a number of DNS queries via TCP.
+
+ @type queries: Any non-zero number of C{dns.Query} instances
+ @param queries: The queries to make.
+
+ @type timeout: C{int}
+ @param timeout: The number of seconds after which to fail.
+
+ @rtype: C{Deferred}
+ """
+ if not len(self.connections):
+ address = self.pickServer()
+ if address is None:
+ return defer.fail(IOError("No domain name servers available"))
+ host, port = address
+ self._reactor.connectTCP(host, port, self.factory)
+ self.pending.append((defer.Deferred(), queries, timeout))
+ return self.pending[-1][0]
+ else:
+ return self.connections[0].query(queries, timeout)
+
+
+ def filterAnswers(self, message):
+ """
+ Extract results from the given message.
+
+ If the message was truncated, re-attempt the query over TCP and return
+ a Deferred which will fire with the results of that query.
+
+ If the message's result code is not L{dns.OK}, return a Failure
+ indicating the type of error which occurred.
+
+ Otherwise, return a three-tuple of lists containing the results from
+ the answers section, the authority section, and the additional section.
+ """
+ if message.trunc:
+ return self.queryTCP(message.queries).addCallback(self.filterAnswers)
+ if message.rCode != dns.OK:
+ return failure.Failure(self.exceptionForCode(message.rCode)(message))
+ return (message.answers, message.authority, message.additional)
+
+
+ def _lookup(self, name, cls, type, timeout):
+ """
+ Build a L{dns.Query} for the given parameters and dispatch it via UDP.
+
+ If this query is already outstanding, it will not be re-issued.
+ Instead, when the outstanding query receives a response, that response
+ will be re-used for this query as well.
+
+ @type name: C{str}
+ @type type: C{int}
+ @type cls: C{int}
+
+ @return: A L{Deferred} which fires with a three-tuple giving the
+ answer, authority, and additional sections of the response or with
+ a L{Failure} if the response code is anything other than C{dns.OK}.
+ """
+ key = (name, type, cls)
+ waiting = self._waiting.get(key)
+ if waiting is None:
+ self._waiting[key] = []
+ d = self.queryUDP([dns.Query(name, type, cls)], timeout)
+ def cbResult(result):
+ for d in self._waiting.pop(key):
+ d.callback(result)
+ return result
+ d.addCallback(self.filterAnswers)
+ d.addBoth(cbResult)
+ else:
+ d = defer.Deferred()
+ waiting.append(d)
+ return d
+
+
+ # This one doesn't ever belong on UDP
+ def lookupZone(self, name, timeout = 10):
+ """
+ Perform an AXFR request. This is quite different from usual
+ DNS requests. See http://cr.yp.to/djbdns/axfr-notes.html for
+ more information.
+ """
+ address = self.pickServer()
+ if address is None:
+ return defer.fail(IOError('No domain name servers available'))
+ host, port = address
+ d = defer.Deferred()
+ controller = AXFRController(name, d)
+ factory = DNSClientFactory(controller, timeout)
+ factory.noisy = False #stfu
+
+ connector = self._reactor.connectTCP(host, port, factory)
+ controller.timeoutCall = self._reactor.callLater(
+ timeout or 10, self._timeoutZone, d, controller,
+ connector, timeout or 10)
+ return d.addCallback(self._cbLookupZone, connector)
+
+ def _timeoutZone(self, d, controller, connector, seconds):
+ connector.disconnect()
+ controller.timeoutCall = None
+ controller.deferred = None
+ d.errback(error.TimeoutError("Zone lookup timed out after %d seconds" % (seconds,)))
+
+ def _cbLookupZone(self, result, connector):
+ connector.disconnect()
+ return (result, [], [])
+
+
+class AXFRController:
+ timeoutCall = None
+
+ def __init__(self, name, deferred):
+ self.name = name
+ self.deferred = deferred
+ self.soa = None
+ self.records = []
+
+ def connectionMade(self, protocol):
+ # dig saids recursion-desired to 0, so I will too
+ message = dns.Message(protocol.pickID(), recDes=0)
+ message.queries = [dns.Query(self.name, dns.AXFR, dns.IN)]
+ protocol.writeMessage(message)
+
+
+ def connectionLost(self, protocol):
+ # XXX Do something here - see #3428
+ pass
+
+
+ def messageReceived(self, message, protocol):
+ # Caveat: We have to handle two cases: All records are in 1
+ # message, or all records are in N messages.
+
+ # According to http://cr.yp.to/djbdns/axfr-notes.html,
+ # 'authority' and 'additional' are always empty, and only
+ # 'answers' is present.
+ self.records.extend(message.answers)
+ if not self.records:
+ return
+ if not self.soa:
+ if self.records[0].type == dns.SOA:
+ #print "first SOA!"
+ self.soa = self.records[0]
+ if len(self.records) > 1 and self.records[-1].type == dns.SOA:
+ #print "It's the second SOA! We're done."
+ if self.timeoutCall is not None:
+ self.timeoutCall.cancel()
+ self.timeoutCall = None
+ if self.deferred is not None:
+ self.deferred.callback(self.records)
+ self.deferred = None
+
+
+
+from twisted.internet.base import ThreadedResolver as _ThreadedResolverImpl
+
+class ThreadedResolver(_ThreadedResolverImpl):
+ def __init__(self, reactor=None):
+ if reactor is None:
+ from twisted.internet import reactor
+ _ThreadedResolverImpl.__init__(self, reactor)
+ warnings.warn(
+ "twisted.names.client.ThreadedResolver is deprecated since "
+ "Twisted 9.0, use twisted.internet.base.ThreadedResolver "
+ "instead.",
+ category=DeprecationWarning, stacklevel=2)
+
+class DNSClientFactory(protocol.ClientFactory):
+ def __init__(self, controller, timeout = 10):
+ self.controller = controller
+ self.timeout = timeout
+
+
+ def clientConnectionLost(self, connector, reason):
+ pass
+
+
+ def buildProtocol(self, addr):
+ p = dns.DNSProtocol(self.controller)
+ p.factory = self
+ return p
+
+
+
+def createResolver(servers=None, resolvconf=None, hosts=None):
+ """
+ Create and return a Resolver.
+
+ @type servers: C{list} of C{(str, int)} or C{None}
+ @param servers: If not C{None}, interpreted as a list of addresses of
+ domain name servers to attempt to use. Addresses should be in dotted-quad
+ form.
+
+ @type resolvconf: C{str} or C{None}
+ @param resolvconf: If not C{None}, on posix systems will be interpreted as
+ an alternate resolv.conf to use. Will do nothing on windows systems. If
+ C{None}, /etc/resolv.conf will be used.
+
+ @type hosts: C{str} or C{None}
+ @param hosts: If not C{None}, an alternate hosts file to use. If C{None}
+ on posix systems, /etc/hosts will be used. On windows, C:\windows\hosts
+ will be used.
+
+ @rtype: C{IResolver}
+ """
+ from twisted.names import resolve, cache, root, hosts as hostsModule
+ if platform.getType() == 'posix':
+ if resolvconf is None:
+ resolvconf = '/etc/resolv.conf'
+ if hosts is None:
+ hosts = '/etc/hosts'
+ theResolver = Resolver(resolvconf, servers)
+ hostResolver = hostsModule.Resolver(hosts)
+ else:
+ if hosts is None:
+ hosts = r'c:\windows\hosts'
+ from twisted.internet import reactor
+ bootstrap = _ThreadedResolverImpl(reactor)
+ hostResolver = hostsModule.Resolver(hosts)
+ theResolver = root.bootstrap(bootstrap)
+
+ L = [hostResolver, cache.CacheResolver(), theResolver]
+ return resolve.ResolverChain(L)
+
+theResolver = None
+def getResolver():
+ """
+ Get a Resolver instance.
+
+ Create twisted.names.client.theResolver if it is C{None}, and then return
+ that value.
+
+ @rtype: C{IResolver}
+ """
+ global theResolver
+ if theResolver is None:
+ try:
+ theResolver = createResolver()
+ except ValueError:
+ theResolver = createResolver(servers=[('127.0.0.1', 53)])
+ return theResolver
+
+def getHostByName(name, timeout=None, effort=10):
+ """
+ Resolve a name to a valid ipv4 or ipv6 address.
+
+ Will errback with C{DNSQueryTimeoutError} on a timeout, C{DomainError} or
+ C{AuthoritativeDomainError} (or subclasses) on other errors.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @type effort: C{int}
+ @param effort: How many times CNAME and NS records to follow while
+ resolving this name.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().getHostByName(name, timeout, effort)
+
+def lookupAddress(name, timeout=None):
+ """
+ Perform an A record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupAddress(name, timeout)
+
+def lookupIPV6Address(name, timeout=None):
+ """
+ Perform an AAAA record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupIPV6Address(name, timeout)
+
+def lookupAddress6(name, timeout=None):
+ """
+ Perform an A6 record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupAddress6(name, timeout)
+
+def lookupMailExchange(name, timeout=None):
+ """
+ Perform an MX record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupMailExchange(name, timeout)
+
+def lookupNameservers(name, timeout=None):
+ """
+ Perform an NS record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupNameservers(name, timeout)
+
+def lookupCanonicalName(name, timeout=None):
+ """
+ Perform a CNAME record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupCanonicalName(name, timeout)
+
+def lookupMailBox(name, timeout=None):
+ """
+ Perform an MB record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupMailBox(name, timeout)
+
+def lookupMailGroup(name, timeout=None):
+ """
+ Perform an MG record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupMailGroup(name, timeout)
+
+def lookupMailRename(name, timeout=None):
+ """
+ Perform an MR record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupMailRename(name, timeout)
+
+def lookupPointer(name, timeout=None):
+ """
+ Perform a PTR record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupPointer(name, timeout)
+
+def lookupAuthority(name, timeout=None):
+ """
+ Perform an SOA record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupAuthority(name, timeout)
+
+def lookupNull(name, timeout=None):
+ """
+ Perform a NULL record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupNull(name, timeout)
+
+def lookupWellKnownServices(name, timeout=None):
+ """
+ Perform a WKS record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupWellKnownServices(name, timeout)
+
+def lookupService(name, timeout=None):
+ """
+ Perform an SRV record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupService(name, timeout)
+
+def lookupHostInfo(name, timeout=None):
+ """
+ Perform a HINFO record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupHostInfo(name, timeout)
+
+def lookupMailboxInfo(name, timeout=None):
+ """
+ Perform an MINFO record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupMailboxInfo(name, timeout)
+
+def lookupText(name, timeout=None):
+ """
+ Perform a TXT record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupText(name, timeout)
+
+def lookupResponsibility(name, timeout=None):
+ """
+ Perform an RP record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupResponsibility(name, timeout)
+
+def lookupAFSDatabase(name, timeout=None):
+ """
+ Perform an AFSDB record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupAFSDatabase(name, timeout)
+
+def lookupZone(name, timeout=None):
+ """
+ Perform an AXFR record lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: C{int}
+ @param timeout: When this timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ # XXX: timeout here is not a list of ints, it is a single int.
+ return getResolver().lookupZone(name, timeout)
+
+def lookupAllRecords(name, timeout=None):
+ """
+ ALL_RECORD lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupAllRecords(name, timeout)
+
+
+
+def lookupNamingAuthorityPointer(name, timeout=None):
+ """
+ NAPTR lookup.
+
+ @type name: C{str}
+ @param name: DNS name to resolve.
+
+ @type timeout: Sequence of C{int}
+ @param timeout: Number of seconds after which to reissue the query.
+ When the last timeout expires, the query is considered failed.
+
+ @rtype: C{Deferred}
+ """
+ return getResolver().lookupNamingAuthorityPointer(name, timeout)
diff --git a/vendor/Twisted-10.0.0/twisted/names/common.py b/vendor/Twisted-10.0.0/twisted/names/common.py
new file mode 100644
index 0000000000..c76b2adf76
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/common.py
@@ -0,0 +1,265 @@
+# -*- test-case-name: twisted.names.test -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Base functionality useful to various parts of Twisted Names.
+"""
+
+import socket
+
+from twisted.names import dns
+from twisted.names.error import DNSFormatError, DNSServerError, DNSNameError
+from twisted.names.error import DNSNotImplementedError, DNSQueryRefusedError
+from twisted.names.error import DNSUnknownError
+
+from twisted.internet import defer, error
+from twisted.python import failure
+
+EMPTY_RESULT = (), (), ()
+
+class ResolverBase:
+ """
+ L{ResolverBase} is a base class for L{IResolver} implementations which
+ deals with a lot of the boilerplate of implementing all of the lookup
+ methods.
+
+ @cvar _errormap: A C{dict} mapping DNS protocol failure response codes
+ to exception classes which will be used to represent those failures.
+ """
+ _errormap = {
+ dns.EFORMAT: DNSFormatError,
+ dns.ESERVER: DNSServerError,
+ dns.ENAME: DNSNameError,
+ dns.ENOTIMP: DNSNotImplementedError,
+ dns.EREFUSED: DNSQueryRefusedError}
+
+ typeToMethod = None
+
+ def __init__(self):
+ self.typeToMethod = {}
+ for (k, v) in typeToMethod.items():
+ self.typeToMethod[k] = getattr(self, v)
+
+
+ def exceptionForCode(self, responseCode):
+ """
+ Convert a response code (one of the possible values of
+ L{dns.Message.rCode} to an exception instance representing it.
+
+ @since: 10.0
+ """
+ return self._errormap.get(responseCode, DNSUnknownError)
+
+
+ def query(self, query, timeout = None):
+ try:
+ return self.typeToMethod[query.type](str(query.name), timeout)
+ except KeyError, e:
+ return defer.fail(failure.Failure(NotImplementedError(str(self.__class__) + " " + str(query.type))))
+
+ def _lookup(self, name, cls, type, timeout):
+ return defer.fail(NotImplementedError("ResolverBase._lookup"))
+
+ def lookupAddress(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupAddress
+ """
+ return self._lookup(name, dns.IN, dns.A, timeout)
+
+ def lookupIPV6Address(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupIPV6Address
+ """
+ return self._lookup(name, dns.IN, dns.AAAA, timeout)
+
+ def lookupAddress6(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupAddress6
+ """
+ return self._lookup(name, dns.IN, dns.A6, timeout)
+
+ def lookupMailExchange(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupMailExchange
+ """
+ return self._lookup(name, dns.IN, dns.MX, timeout)
+
+ def lookupNameservers(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupNameservers
+ """
+ return self._lookup(name, dns.IN, dns.NS, timeout)
+
+ def lookupCanonicalName(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupCanonicalName
+ """
+ return self._lookup(name, dns.IN, dns.CNAME, timeout)
+
+ def lookupMailBox(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupMailBox
+ """
+ return self._lookup(name, dns.IN, dns.MB, timeout)
+
+ def lookupMailGroup(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupMailGroup
+ """
+ return self._lookup(name, dns.IN, dns.MG, timeout)
+
+ def lookupMailRename(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupMailRename
+ """
+ return self._lookup(name, dns.IN, dns.MR, timeout)
+
+ def lookupPointer(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupPointer
+ """
+ return self._lookup(name, dns.IN, dns.PTR, timeout)
+
+ def lookupAuthority(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupAuthority
+ """
+ return self._lookup(name, dns.IN, dns.SOA, timeout)
+
+ def lookupNull(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupNull
+ """
+ return self._lookup(name, dns.IN, dns.NULL, timeout)
+
+ def lookupWellKnownServices(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupWellKnownServices
+ """
+ return self._lookup(name, dns.IN, dns.WKS, timeout)
+
+ def lookupService(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupService
+ """
+ return self._lookup(name, dns.IN, dns.SRV, timeout)
+
+ def lookupHostInfo(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupHostInfo
+ """
+ return self._lookup(name, dns.IN, dns.HINFO, timeout)
+
+ def lookupMailboxInfo(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupMailboxInfo
+ """
+ return self._lookup(name, dns.IN, dns.MINFO, timeout)
+
+ def lookupText(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupText
+ """
+ return self._lookup(name, dns.IN, dns.TXT, timeout)
+
+ def lookupResponsibility(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupResponsibility
+ """
+ return self._lookup(name, dns.IN, dns.RP, timeout)
+
+ def lookupAFSDatabase(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupAFSDatabase
+ """
+ return self._lookup(name, dns.IN, dns.AFSDB, timeout)
+
+ def lookupZone(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupZone
+ """
+ return self._lookup(name, dns.IN, dns.AXFR, timeout)
+
+
+ def lookupNamingAuthorityPointer(self, name, timeout=None):
+ """
+ @see: twisted.names.client.lookupNamingAuthorityPointer
+ """
+ return self._lookup(name, dns.IN, dns.NAPTR, timeout)
+
+
+ def lookupAllRecords(self, name, timeout = None):
+ """
+ @see: twisted.names.client.lookupAllRecords
+ """
+ return self._lookup(name, dns.IN, dns.ALL_RECORDS, timeout)
+
+ def getHostByName(self, name, timeout = None, effort = 10):
+ """
+ @see: twisted.names.client.getHostByName
+ """
+ # XXX - respect timeout
+ return self.lookupAllRecords(name, timeout
+ ).addCallback(self._cbRecords, name, effort
+ )
+
+ def _cbRecords(self, (ans, auth, add), name, effort):
+ result = extractRecord(self, dns.Name(name), ans + auth + add, effort)
+ if not result:
+ raise error.DNSLookupError(name)
+ return result
+
+def extractRecord(resolver, name, answers, level = 10):
+ if not level:
+ return None
+ if hasattr(socket, 'inet_ntop'):
+ for r in answers:
+ if r.name == name and r.type == dns.A6:
+ return socket.inet_ntop(socket.AF_INET6, r.payload.address)
+ for r in answers:
+ if r.name == name and r.type == dns.AAAA:
+ return socket.inet_ntop(socket.AF_INET6, r.payload.address)
+ for r in answers:
+ if r.name == name and r.type == dns.A:
+ return socket.inet_ntop(socket.AF_INET, r.payload.address)
+ for r in answers:
+ if r.name == name and r.type == dns.CNAME:
+ result = extractRecord(resolver, r.payload.name, answers, level - 1)
+ if not result:
+ return resolver.getHostByName(str(r.payload.name), effort=level-1)
+ return result
+ # No answers, but maybe there's a hint at who we should be asking about this
+ for r in answers:
+ if r.type == dns.NS:
+ from twisted.names import client
+ r = client.Resolver(servers=[(str(r.payload.name), dns.PORT)])
+ return r.lookupAddress(str(name)
+ ).addCallback(lambda (ans, auth, add): extractRecord(r, name, ans + auth + add, level - 1)
+ ).addBoth(lambda passthrough: (r.protocol.transport.stopListening(), passthrough)[1])
+
+typeToMethod = {
+ dns.A: 'lookupAddress',
+ dns.AAAA: 'lookupIPV6Address',
+ dns.A6: 'lookupAddress6',
+ dns.NS: 'lookupNameservers',
+ dns.CNAME: 'lookupCanonicalName',
+ dns.SOA: 'lookupAuthority',
+ dns.MB: 'lookupMailBox',
+ dns.MG: 'lookupMailGroup',
+ dns.MR: 'lookupMailRename',
+ dns.NULL: 'lookupNull',
+ dns.WKS: 'lookupWellKnownServices',
+ dns.PTR: 'lookupPointer',
+ dns.HINFO: 'lookupHostInfo',
+ dns.MINFO: 'lookupMailboxInfo',
+ dns.MX: 'lookupMailExchange',
+ dns.TXT: 'lookupText',
+
+ dns.RP: 'lookupResponsibility',
+ dns.AFSDB: 'lookupAFSDatabase',
+ dns.SRV: 'lookupService',
+ dns.NAPTR: 'lookupNamingAuthorityPointer',
+ dns.AXFR: 'lookupZone',
+ dns.ALL_RECORDS: 'lookupAllRecords',
+}
diff --git a/vendor/Twisted-10.0.0/twisted/names/dns.py b/vendor/Twisted-10.0.0/twisted/names/dns.py
new file mode 100644
index 0000000000..9f3e7b0376
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/dns.py
@@ -0,0 +1,1822 @@
+# -*- test-case-name: twisted.names.test.test_dns -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+DNS protocol implementation.
+
+Future Plans:
+ - Get rid of some toplevels, maybe.
+ - Put in a better lookupRecordType implementation.
+
+@author: Moshe Zadka
+@author: Jp Calderone
+"""
+
+# System imports
+import warnings
+
+import struct, random, types, socket
+
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+AF_INET6 = socket.AF_INET6
+
+from zope.interface import implements, Interface, Attribute
+
+
+# Twisted imports
+from twisted.internet import protocol, defer
+from twisted.internet.error import CannotListenError
+from twisted.python import log, failure
+from twisted.python import util as tputil
+from twisted.python import randbytes
+
+
+def randomSource():
+ """
+ Wrapper around L{randbytes.secureRandom} to return 2 random chars.
+ """
+ return struct.unpack('H', randbytes.secureRandom(2, fallback=True))[0]
+
+
+PORT = 53
+
+(A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT,
+ RP, AFSDB) = range(1, 19)
+AAAA = 28
+SRV = 33
+NAPTR = 35
+A6 = 38
+DNAME = 39
+
+QUERY_TYPES = {
+ A: 'A',
+ NS: 'NS',
+ MD: 'MD',
+ MF: 'MF',
+ CNAME: 'CNAME',
+ SOA: 'SOA',
+ MB: 'MB',
+ MG: 'MG',
+ MR: 'MR',
+ NULL: 'NULL',
+ WKS: 'WKS',
+ PTR: 'PTR',
+ HINFO: 'HINFO',
+ MINFO: 'MINFO',
+ MX: 'MX',
+ TXT: 'TXT',
+ RP: 'RP',
+ AFSDB: 'AFSDB',
+
+ # 19 through 27? Eh, I'll get to 'em.
+
+ AAAA: 'AAAA',
+ SRV: 'SRV',
+ NAPTR: 'NAPTR',
+ A6: 'A6',
+ DNAME: 'DNAME'
+}
+
+IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
+
+# "Extended" queries (Hey, half of these are deprecated, good job)
+EXT_QUERIES = {
+ IXFR: 'IXFR',
+ AXFR: 'AXFR',
+ MAILB: 'MAILB',
+ MAILA: 'MAILA',
+ ALL_RECORDS: 'ALL_RECORDS'
+}
+
+REV_TYPES = dict([
+ (v, k) for (k, v) in QUERY_TYPES.items() + EXT_QUERIES.items()
+])
+
+IN, CS, CH, HS = range(1, 5)
+ANY = 255
+
+QUERY_CLASSES = {
+ IN: 'IN',
+ CS: 'CS',
+ CH: 'CH',
+ HS: 'HS',
+ ANY: 'ANY'
+}
+REV_CLASSES = dict([
+ (v, k) for (k, v) in QUERY_CLASSES.items()
+])
+
+
+# Opcodes
+OP_QUERY, OP_INVERSE, OP_STATUS = range(3)
+OP_NOTIFY = 4 # RFC 1996
+OP_UPDATE = 5 # RFC 2136
+
+
+# Response Codes
+OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
+
+class IRecord(Interface):
+ """
+ An single entry in a zone of authority.
+ """
+
+ TYPE = Attribute("An indicator of what kind of record this is.")
+
+
+# Backwards compatibility aliases - these should be deprecated or something I
+# suppose. -exarkun
+from twisted.names.error import DomainError, AuthoritativeDomainError
+from twisted.names.error import DNSQueryTimeoutError
+
+
+def str2time(s):
+ suffixes = (
+ ('S', 1), ('M', 60), ('H', 60 * 60), ('D', 60 * 60 * 24),
+ ('W', 60 * 60 * 24 * 7), ('Y', 60 * 60 * 24 * 365)
+ )
+ if isinstance(s, types.StringType):
+ s = s.upper().strip()
+ for (suff, mult) in suffixes:
+ if s.endswith(suff):
+ return int(float(s[:-1]) * mult)
+ try:
+ s = int(s)
+ except ValueError:
+ raise ValueError, "Invalid time interval specifier: " + s
+ return s
+
+
+def readPrecisely(file, l):
+ buff = file.read(l)
+ if len(buff) < l:
+ raise EOFError
+ return buff
+
+
+class IEncodable(Interface):
+ """
+ Interface for something which can be encoded to and decoded
+ from a file object.
+ """
+
+ def encode(strio, compDict = None):
+ """
+ Write a representation of this object to the given
+ file object.
+
+ @type strio: File-like object
+ @param strio: The stream to which to write bytes
+
+ @type compDict: C{dict} or C{None}
+ @param compDict: A dictionary of backreference addresses that have
+ have already been written to this stream and that may be used for
+ compression.
+ """
+
+ def decode(strio, length = None):
+ """
+ Reconstruct an object from data read from the given
+ file object.
+
+ @type strio: File-like object
+ @param strio: The stream from which bytes may be read
+
+ @type length: C{int} or C{None}
+ @param length: The number of bytes in this RDATA field. Most
+ implementations can ignore this value. Only in the case of
+ records similar to TXT where the total length is in no way
+ encoded in the data is it necessary.
+ """
+
+
+
+class Charstr(object):
+ implements(IEncodable)
+
+ def __init__(self, string=''):
+ if not isinstance(string, str):
+ raise ValueError("%r is not a string" % (string,))
+ self.string = string
+
+
+ def encode(self, strio, compDict=None):
+ """
+ Encode this Character string into the appropriate byte format.
+
+ @type strio: file
+ @param strio: The byte representation of this Charstr will be written
+ to this file.
+ """
+ string = self.string
+ ind = len(string)
+ strio.write(chr(ind))
+ strio.write(string)
+
+
+ def decode(self, strio, length=None):
+ """
+ Decode a byte string into this Name.
+
+ @type strio: file
+ @param strio: Bytes will be read from this file until the full string
+ is decoded.
+
+ @raise EOFError: Raised when there are not enough bytes available from
+ C{strio}.
+ """
+ self.string = ''
+ l = ord(readPrecisely(strio, 1))
+ self.string = readPrecisely(strio, l)
+
+
+ def __eq__(self, other):
+ if isinstance(other, Charstr):
+ return self.string == other.string
+ return False
+
+
+ def __hash__(self):
+ return hash(self.string)
+
+
+ def __str__(self):
+ return self.string
+
+
+
+class Name:
+ implements(IEncodable)
+
+ def __init__(self, name=''):
+ assert isinstance(name, types.StringTypes), "%r is not a string" % (name,)
+ self.name = name
+
+ def encode(self, strio, compDict=None):
+ """
+ Encode this Name into the appropriate byte format.
+
+ @type strio: file
+ @param strio: The byte representation of this Name will be written to
+ this file.
+
+ @type compDict: dict
+ @param compDict: dictionary of Names that have already been encoded
+ and whose addresses may be backreferenced by this Name (for the purpose
+ of reducing the message size).
+ """
+ name = self.name
+ while name:
+ if compDict is not None:
+ if name in compDict:
+ strio.write(
+ struct.pack("!H", 0xc000 | compDict[name]))
+ return
+ else:
+ compDict[name] = strio.tell() + Message.headerSize
+ ind = name.find('.')
+ if ind > 0:
+ label, name = name[:ind], name[ind + 1:]
+ else:
+ label, name = name, ''
+ ind = len(label)
+ strio.write(chr(ind))
+ strio.write(label)
+ strio.write(chr(0))
+
+
+ def decode(self, strio, length=None):
+ """
+ Decode a byte string into this Name.
+
+ @type strio: file
+ @param strio: Bytes will be read from this file until the full Name
+ is decoded.
+
+ @raise EOFError: Raised when there are not enough bytes available
+ from C{strio}.
+ """
+ self.name = ''
+ off = 0
+ while 1:
+ l = ord(readPrecisely(strio, 1))
+ if l == 0:
+ if off > 0:
+ strio.seek(off)
+ return
+ if (l >> 6) == 3:
+ new_off = ((l&63) << 8
+ | ord(readPrecisely(strio, 1)))
+ if off == 0:
+ off = strio.tell()
+ strio.seek(new_off)
+ continue
+ label = readPrecisely(strio, l)
+ if self.name == '':
+ self.name = label
+ else:
+ self.name = self.name + '.' + label
+
+ def __eq__(self, other):
+ if isinstance(other, Name):
+ return str(self) == str(other)
+ return 0
+
+
+ def __hash__(self):
+ return hash(str(self))
+
+
+ def __str__(self):
+ return self.name
+
+class Query:
+ """
+ Represent a single DNS query.
+
+ @ivar name: The name about which this query is requesting information.
+ @ivar type: The query type.
+ @ivar cls: The query class.
+ """
+
+ implements(IEncodable)
+
+ name = None
+ type = None
+ cls = None
+
+ def __init__(self, name='', type=A, cls=IN):
+ """
+ @type name: C{str}
+ @param name: The name about which to request information.
+
+ @type type: C{int}
+ @param type: The query type.
+
+ @type cls: C{int}
+ @param cls: The query class.
+ """
+ self.name = Name(name)
+ self.type = type
+ self.cls = cls
+
+
+ def encode(self, strio, compDict=None):
+ self.name.encode(strio, compDict)
+ strio.write(struct.pack("!HH", self.type, self.cls))
+
+
+ def decode(self, strio, length = None):
+ self.name.decode(strio)
+ buff = readPrecisely(strio, 4)
+ self.type, self.cls = struct.unpack("!HH", buff)
+
+
+ def __hash__(self):
+ return hash((str(self.name).lower(), self.type, self.cls))
+
+
+ def __cmp__(self, other):
+ return isinstance(other, Query) and cmp(
+ (str(self.name).lower(), self.type, self.cls),
+ (str(other.name).lower(), other.type, other.cls)
+ ) or cmp(self.__class__, other.__class__)
+
+
+ def __str__(self):
+ t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
+ c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
+ return '<Query %s %s %s>' % (self.name, t, c)
+
+
+ def __repr__(self):
+ return 'Query(%r, %r, %r)' % (str(self.name), self.type, self.cls)
+
+
+class RRHeader(tputil.FancyEqMixin):
+ """
+ A resource record header.
+
+ @cvar fmt: C{str} specifying the byte format of an RR.
+
+ @ivar name: The name about which this reply contains information.
+ @ivar type: The query type of the original request.
+ @ivar cls: The query class of the original request.
+ @ivar ttl: The time-to-live for this record.
+ @ivar payload: An object that implements the IEncodable interface
+ @ivar auth: Whether this header is authoritative or not.
+ """
+
+ implements(IEncodable)
+
+ compareAttributes = ('name', 'type', 'cls', 'ttl', 'payload', 'auth')
+
+ fmt = "!HHIH"
+
+ name = None
+ type = None
+ cls = None
+ ttl = None
+ payload = None
+ rdlength = None
+
+ cachedResponse = None
+
+ def __init__(self, name='', type=A, cls=IN, ttl=0, payload=None, auth=False):
+ """
+ @type name: C{str}
+ @param name: The name about which this reply contains information.
+
+ @type type: C{int}
+ @param type: The query type.
+
+ @type cls: C{int}
+ @param cls: The query class.
+
+ @type ttl: C{int}
+ @param ttl: Time to live for this record.
+
+ @type payload: An object implementing C{IEncodable}
+ @param payload: A Query Type specific data object.
+ """
+ assert (payload is None) or (payload.TYPE == type)
+
+ self.name = Name(name)
+ self.type = type
+ self.cls = cls
+ self.ttl = ttl
+ self.payload = payload
+ self.auth = auth
+
+
+ def encode(self, strio, compDict=None):
+ self.name.encode(strio, compDict)
+ strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0))
+ if self.payload:
+ prefix = strio.tell()
+ self.payload.encode(strio, compDict)
+ aft = strio.tell()
+ strio.seek(prefix - 2, 0)
+ strio.write(struct.pack('!H', aft - prefix))
+ strio.seek(aft, 0)
+
+
+ def decode(self, strio, length = None):
+ self.name.decode(strio)
+ l = struct.calcsize(self.fmt)
+ buff = readPrecisely(strio, l)
+ r = struct.unpack(self.fmt, buff)
+ self.type, self.cls, self.ttl, self.rdlength = r
+
+
+ def isAuthoritative(self):
+ return self.auth
+
+
+ def __str__(self):
+ t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
+ c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
+ return '<RR name=%s type=%s class=%s ttl=%ds auth=%s>' % (self.name, t, c, self.ttl, self.auth and 'True' or 'False')
+
+
+ __repr__ = __str__
+
+
+
+class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ A Resource Record which consists of a single RFC 1035 domain-name.
+
+ @type name: L{Name}
+ @ivar name: The name associated with this record.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ implements(IEncodable, IRecord)
+
+ showAttributes = (('name', 'name', '%s'), 'ttl')
+ compareAttributes = ('name', 'ttl')
+
+ TYPE = None
+ name = None
+
+ def __init__(self, name='', ttl=None):
+ self.name = Name(name)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ self.name.encode(strio, compDict)
+
+
+ def decode(self, strio, length = None):
+ self.name = Name()
+ self.name.decode(strio)
+
+
+ def __hash__(self):
+ return hash(self.name)
+
+
+# Kinds of RRs - oh my!
+class Record_NS(SimpleRecord):
+ """
+ An authoritative nameserver.
+ """
+ TYPE = NS
+ fancybasename = 'NS'
+
+
+
+class Record_MD(SimpleRecord):
+ """
+ A mail destination.
+
+ This record type is obsolete.
+
+ @see: L{Record_MX}
+ """
+ TYPE = MD
+ fancybasename = 'MD'
+
+
+
+class Record_MF(SimpleRecord):
+ """
+ A mail forwarder.
+
+ This record type is obsolete.
+
+ @see: L{Record_MX}
+ """
+ TYPE = MF
+ fancybasename = 'MF'
+
+
+
+class Record_CNAME(SimpleRecord):
+ """
+ The canonical name for an alias.
+ """
+ TYPE = CNAME
+ fancybasename = 'CNAME'
+
+
+
+class Record_MB(SimpleRecord):
+ """
+ A mailbox domain name.
+
+ This is an experimental record type.
+ """
+ TYPE = MB
+ fancybasename = 'MB'
+
+
+
+class Record_MG(SimpleRecord):
+ """
+ A mail group member.
+
+ This is an experimental record type.
+ """
+ TYPE = MG
+ fancybasename = 'MG'
+
+
+
+class Record_MR(SimpleRecord):
+ """
+ A mail rename domain name.
+
+ This is an experimental record type.
+ """
+ TYPE = MR
+ fancybasename = 'MR'
+
+
+
+class Record_PTR(SimpleRecord):
+ """
+ A domain name pointer.
+ """
+ TYPE = PTR
+ fancybasename = 'PTR'
+
+
+
+class Record_DNAME(SimpleRecord):
+ """
+ A non-terminal DNS name redirection.
+
+ This record type provides the capability to map an entire subtree of the
+ DNS name space to another domain. It differs from the CNAME record which
+ maps a single node of the name space.
+
+ @see: U{http://www.faqs.org/rfcs/rfc2672.html}
+ @see: U{http://www.faqs.org/rfcs/rfc3363.html}
+ """
+ TYPE = DNAME
+ fancybasename = 'DNAME'
+
+
+
+class Record_A(tputil.FancyEqMixin):
+ """
+ An IPv4 host address.
+
+ @type address: C{str}
+ @ivar address: The packed network-order representation of the IPv4 address
+ associated with this record.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ implements(IEncodable, IRecord)
+
+ compareAttributes = ('address', 'ttl')
+
+ TYPE = A
+ address = None
+
+ def __init__(self, address='0.0.0.0', ttl=None):
+ address = socket.inet_aton(address)
+ self.address = address
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(self.address)
+
+
+ def decode(self, strio, length = None):
+ self.address = readPrecisely(strio, 4)
+
+
+ def __hash__(self):
+ return hash(self.address)
+
+
+ def __str__(self):
+ return '<A address=%s ttl=%s>' % (self.dottedQuad(), self.ttl)
+ __repr__ = __str__
+
+
+ def dottedQuad(self):
+ return socket.inet_ntoa(self.address)
+
+
+
+class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ Marks the start of a zone of authority.
+
+ This record describes parameters which are shared by all records within a
+ particular zone.
+
+ @type mname: L{Name}
+ @ivar mname: The domain-name of the name server that was the original or
+ primary source of data for this zone.
+
+ @type rname: L{Name}
+ @ivar rname: A domain-name which specifies the mailbox of the person
+ responsible for this zone.
+
+ @type serial: C{int}
+ @ivar serial: The unsigned 32 bit version number of the original copy of
+ the zone. Zone transfers preserve this value. This value wraps and
+ should be compared using sequence space arithmetic.
+
+ @type refresh: C{int}
+ @ivar refresh: A 32 bit time interval before the zone should be refreshed.
+
+ @type minimum: C{int}
+ @ivar minimum: The unsigned 32 bit minimum TTL field that should be
+ exported with any RR from this zone.
+
+ @type expire: C{int}
+ @ivar expire: A 32 bit time value that specifies the upper limit on the
+ time interval that can elapse before the zone is no longer
+ authoritative.
+
+ @type retry: C{int}
+ @ivar retry: A 32 bit time interval that should elapse before a failed
+ refresh should be retried.
+
+ @type ttl: C{int}
+ @ivar ttl: The default TTL to use for records served from this zone.
+ """
+ implements(IEncodable, IRecord)
+
+ fancybasename = 'SOA'
+ compareAttributes = ('serial', 'mname', 'rname', 'refresh', 'expire', 'retry', 'minimum', 'ttl')
+ showAttributes = (('mname', 'mname', '%s'), ('rname', 'rname', '%s'), 'serial', 'refresh', 'retry', 'expire', 'minimum', 'ttl')
+
+ TYPE = SOA
+
+ def __init__(self, mname='', rname='', serial=0, refresh=0, retry=0, expire=0, minimum=0, ttl=None):
+ self.mname, self.rname = Name(mname), Name(rname)
+ self.serial, self.refresh = str2time(serial), str2time(refresh)
+ self.minimum, self.expire = str2time(minimum), str2time(expire)
+ self.retry = str2time(retry)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ self.mname.encode(strio, compDict)
+ self.rname.encode(strio, compDict)
+ strio.write(
+ struct.pack(
+ '!LlllL',
+ self.serial, self.refresh, self.retry, self.expire,
+ self.minimum
+ )
+ )
+
+
+ def decode(self, strio, length = None):
+ self.mname, self.rname = Name(), Name()
+ self.mname.decode(strio)
+ self.rname.decode(strio)
+ r = struct.unpack('!LlllL', readPrecisely(strio, 20))
+ self.serial, self.refresh, self.retry, self.expire, self.minimum = r
+
+
+ def __hash__(self):
+ return hash((
+ self.serial, self.mname, self.rname,
+ self.refresh, self.expire, self.retry
+ ))
+
+
+
+class Record_NULL(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ A null record.
+
+ This is an experimental record type.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ implements(IEncodable, IRecord)
+
+ fancybasename = 'NULL'
+ showAttributes = compareAttributes = ('payload', 'ttl')
+
+ TYPE = NULL
+
+ def __init__(self, payload=None, ttl=None):
+ self.payload = payload
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(self.payload)
+
+
+ def decode(self, strio, length = None):
+ self.payload = readPrecisely(strio, length)
+
+
+ def __hash__(self):
+ return hash(self.payload)
+
+
+
+class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ A well known service description.
+
+ This record type is obsolete. See L{Record_SRV}.
+
+ @type address: C{str}
+ @ivar address: The packed network-order representation of the IPv4 address
+ associated with this record.
+
+ @type protocol: C{int}
+ @ivar protocol: The 8 bit IP protocol number for which this service map is
+ relevant.
+
+ @type map: C{str}
+ @ivar map: A bitvector indicating the services available at the specified
+ address.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ implements(IEncodable, IRecord)
+
+ fancybasename = "WKS"
+ compareAttributes = ('address', 'protocol', 'map', 'ttl')
+ showAttributes = [('_address', 'address', '%s'), 'protocol', 'ttl']
+
+ TYPE = WKS
+
+ _address = property(lambda self: socket.inet_ntoa(self.address))
+
+ def __init__(self, address='0.0.0.0', protocol=0, map='', ttl=None):
+ self.address = socket.inet_aton(address)
+ self.protocol, self.map = protocol, map
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(self.address)
+ strio.write(struct.pack('!B', self.protocol))
+ strio.write(self.map)
+
+
+ def decode(self, strio, length = None):
+ self.address = readPrecisely(strio, 4)
+ self.protocol = struct.unpack('!B', readPrecisely(strio, 1))[0]
+ self.map = readPrecisely(strio, length - 5)
+
+
+ def __hash__(self):
+ return hash((self.address, self.protocol, self.map))
+
+
+
+class Record_AAAA(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ An IPv6 host address.
+
+ @type address: C{str}
+ @ivar address: The packed network-order representation of the IPv6 address
+ associated with this record.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc1886.html}
+ """
+ implements(IEncodable, IRecord)
+ TYPE = AAAA
+
+ fancybasename = 'AAAA'
+ showAttributes = (('_address', 'address', '%s'), 'ttl')
+ compareAttributes = ('address', 'ttl')
+
+ _address = property(lambda self: socket.inet_ntop(AF_INET6, self.address))
+
+ def __init__(self, address = '::', ttl=None):
+ self.address = socket.inet_pton(AF_INET6, address)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(self.address)
+
+
+ def decode(self, strio, length = None):
+ self.address = readPrecisely(strio, 16)
+
+
+ def __hash__(self):
+ return hash(self.address)
+
+
+
+class Record_A6(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ An IPv6 address.
+
+ This is an experimental record type.
+
+ @type prefixLen: C{int}
+ @ivar prefixLen: The length of the suffix.
+
+ @type suffix: C{str}
+ @ivar suffix: An IPv6 address suffix in network order.
+
+ @type prefix: L{Name}
+ @ivar prefix: If specified, a name which will be used as a prefix for other
+ A6 records.
+
+ @type bytes: C{int}
+ @ivar bytes: The length of the prefix.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc2874.html}
+ @see: U{http://www.faqs.org/rfcs/rfc3363.html}
+ @see: U{http://www.faqs.org/rfcs/rfc3364.html}
+ """
+ implements(IEncodable, IRecord)
+ TYPE = A6
+
+ fancybasename = 'A6'
+ showAttributes = (('_suffix', 'suffix', '%s'), ('prefix', 'prefix', '%s'), 'ttl')
+ compareAttributes = ('prefixLen', 'prefix', 'suffix', 'ttl')
+
+ _suffix = property(lambda self: socket.inet_ntop(AF_INET6, self.suffix))
+
+ def __init__(self, prefixLen=0, suffix='::', prefix='', ttl=None):
+ self.prefixLen = prefixLen
+ self.suffix = socket.inet_pton(AF_INET6, suffix)
+ self.prefix = Name(prefix)
+ self.bytes = int((128 - self.prefixLen) / 8.0)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(struct.pack('!B', self.prefixLen))
+ if self.bytes:
+ strio.write(self.suffix[-self.bytes:])
+ if self.prefixLen:
+ # This may not be compressed
+ self.prefix.encode(strio, None)
+
+
+ def decode(self, strio, length = None):
+ self.prefixLen = struct.unpack('!B', readPrecisely(strio, 1))[0]
+ self.bytes = int((128 - self.prefixLen) / 8.0)
+ if self.bytes:
+ self.suffix = '\x00' * (16 - self.bytes) + readPrecisely(strio, self.bytes)
+ if self.prefixLen:
+ self.prefix.decode(strio)
+
+
+ def __eq__(self, other):
+ if isinstance(other, Record_A6):
+ return (self.prefixLen == other.prefixLen and
+ self.suffix[-self.bytes:] == other.suffix[-self.bytes:] and
+ self.prefix == other.prefix and
+ self.ttl == other.ttl)
+ return NotImplemented
+
+
+ def __hash__(self):
+ return hash((self.prefixLen, self.suffix[-self.bytes:], self.prefix))
+
+
+ def __str__(self):
+ return '<A6 %s %s (%d) ttl=%s>' % (
+ self.prefix,
+ socket.inet_ntop(AF_INET6, self.suffix),
+ self.prefixLen, self.ttl
+ )
+
+
+
+class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ The location of the server(s) for a specific protocol and domain.
+
+ This is an experimental record type.
+
+ @type priority: C{int}
+ @ivar priority: The priority of this target host. A client MUST attempt to
+ contact the target host with the lowest-numbered priority it can reach;
+ target hosts with the same priority SHOULD be tried in an order defined
+ by the weight field.
+
+ @type weight: C{int}
+ @ivar weight: Specifies a relative weight for entries with the same
+ priority. Larger weights SHOULD be given a proportionately higher
+ probability of being selected.
+
+ @type port: C{int}
+ @ivar port: The port on this target host of this service.
+
+ @type target: L{Name}
+ @ivar target: The domain name of the target host. There MUST be one or
+ more address records for this name, the name MUST NOT be an alias (in
+ the sense of RFC 1034 or RFC 2181). Implementors are urged, but not
+ required, to return the address record(s) in the Additional Data
+ section. Unless and until permitted by future standards action, name
+ compression is not to be used for this field.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc2782.html}
+ """
+ implements(IEncodable, IRecord)
+ TYPE = SRV
+
+ fancybasename = 'SRV'
+ compareAttributes = ('priority', 'weight', 'target', 'port', 'ttl')
+ showAttributes = ('priority', 'weight', ('target', 'target', '%s'), 'port', 'ttl')
+
+ def __init__(self, priority=0, weight=0, port=0, target='', ttl=None):
+ self.priority = int(priority)
+ self.weight = int(weight)
+ self.port = int(port)
+ self.target = Name(target)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(struct.pack('!HHH', self.priority, self.weight, self.port))
+ # This can't be compressed
+ self.target.encode(strio, None)
+
+
+ def decode(self, strio, length = None):
+ r = struct.unpack('!HHH', readPrecisely(strio, struct.calcsize('!HHH')))
+ self.priority, self.weight, self.port = r
+ self.target = Name()
+ self.target.decode(strio)
+
+
+ def __hash__(self):
+ return hash((self.priority, self.weight, self.port, self.target))
+
+
+
+class Record_NAPTR(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ The location of the server(s) for a specific protocol and domain.
+
+ @type order: C{int}
+ @ivar order: An integer specifying the order in which the NAPTR records
+ MUST be processed to ensure the correct ordering of rules. Low numbers
+ are processed before high numbers.
+
+ @type preference: C{int}
+ @ivar preference: An integer that specifies the order in which NAPTR
+ records with equal "order" values SHOULD be processed, low numbers
+ being processed before high numbers.
+
+ @type flag: L{Charstr}
+ @ivar flag: A <character-string> containing flags to control aspects of the
+ rewriting and interpretation of the fields in the record. Flags
+ aresingle characters from the set [A-Z0-9]. The case of the alphabetic
+ characters is not significant.
+
+ At this time only four flags, "S", "A", "U", and "P", are defined.
+
+ @type service: L{Charstr}
+ @ivar service: Specifies the service(s) available down this rewrite path.
+ It may also specify the particular protocol that is used to talk with a
+ service. A protocol MUST be specified if the flags field states that
+ the NAPTR is terminal.
+
+ @type regexp: L{Charstr}
+ @ivar regexp: A STRING containing a substitution expression that is applied
+ to the original string held by the client in order to construct the
+ next domain name to lookup.
+
+ @type replacement: L{Name}
+ @ivar replacement: The next NAME to query for NAPTR, SRV, or address
+ records depending on the value of the flags field. This MUST be a
+ fully qualified domain-name.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc2915.html}
+ """
+ implements(IEncodable, IRecord)
+ TYPE = NAPTR
+
+ compareAttributes = ('order', 'preference', 'flags', 'service', 'regexp',
+ 'replacement')
+ fancybasename = 'NAPTR'
+ showAttributes = ('order', 'preference', ('flags', 'flags', '%s'),
+ ('service', 'service', '%s'), ('regexp', 'regexp', '%s'),
+ ('replacement', 'replacement', '%s'), 'ttl')
+
+ def __init__(self, order=0, preference=0, flags='', service='', regexp='',
+ replacement='', ttl=None):
+ self.order = int(order)
+ self.preference = int(preference)
+ self.flags = Charstr(flags)
+ self.service = Charstr(service)
+ self.regexp = Charstr(regexp)
+ self.replacement = Name(replacement)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict=None):
+ strio.write(struct.pack('!HH', self.order, self.preference))
+ # This can't be compressed
+ self.flags.encode(strio, None)
+ self.service.encode(strio, None)
+ self.regexp.encode(strio, None)
+ self.replacement.encode(strio, None)
+
+
+ def decode(self, strio, length=None):
+ r = struct.unpack('!HH', readPrecisely(strio, struct.calcsize('!HH')))
+ self.order, self.preference = r
+ self.flags = Charstr()
+ self.service = Charstr()
+ self.regexp = Charstr()
+ self.replacement = Name()
+ self.flags.decode(strio)
+ self.service.decode(strio)
+ self.regexp.decode(strio)
+ self.replacement.decode(strio)
+
+
+ def __hash__(self):
+ return hash((
+ self.order, self.preference, self.flags,
+ self.service, self.regexp, self.replacement))
+
+
+
+class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ Map from a domain name to the name of an AFS cell database server.
+
+ @type subtype: C{int}
+ @ivar subtype: In the case of subtype 1, the host has an AFS version 3.0
+ Volume Location Server for the named AFS cell. In the case of subtype
+ 2, the host has an authenticated name server holding the cell-root
+ directory node for the named DCE/NCA cell.
+
+ @type hostname: L{Name}
+ @ivar hostname: The domain name of a host that has a server for the cell
+ named by this record.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc1183.html}
+ """
+ implements(IEncodable, IRecord)
+ TYPE = AFSDB
+
+ fancybasename = 'AFSDB'
+ compareAttributes = ('subtype', 'hostname', 'ttl')
+ showAttributes = ('subtype', ('hostname', 'hostname', '%s'), 'ttl')
+
+ def __init__(self, subtype=0, hostname='', ttl=None):
+ self.subtype = int(subtype)
+ self.hostname = Name(hostname)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(struct.pack('!H', self.subtype))
+ self.hostname.encode(strio, compDict)
+
+
+ def decode(self, strio, length = None):
+ r = struct.unpack('!H', readPrecisely(strio, struct.calcsize('!H')))
+ self.subtype, = r
+ self.hostname.decode(strio)
+
+
+ def __hash__(self):
+ return hash((self.subtype, self.hostname))
+
+
+
+class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ The responsible person for a domain.
+
+ @type mbox: L{Name}
+ @ivar mbox: A domain name that specifies the mailbox for the responsible
+ person.
+
+ @type txt: L{Name}
+ @ivar txt: A domain name for which TXT RR's exist (indirection through
+ which allows information sharing about the contents of this RP record).
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+
+ @see: U{http://www.faqs.org/rfcs/rfc1183.html}
+ """
+ implements(IEncodable, IRecord)
+ TYPE = RP
+
+ fancybasename = 'RP'
+ compareAttributes = ('mbox', 'txt', 'ttl')
+ showAttributes = (('mbox', 'mbox', '%s'), ('txt', 'txt', '%s'), 'ttl')
+
+ def __init__(self, mbox='', txt='', ttl=None):
+ self.mbox = Name(mbox)
+ self.txt = Name(txt)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ self.mbox.encode(strio, compDict)
+ self.txt.encode(strio, compDict)
+
+
+ def decode(self, strio, length = None):
+ self.mbox = Name()
+ self.txt = Name()
+ self.mbox.decode(strio)
+ self.txt.decode(strio)
+
+
+ def __hash__(self):
+ return hash((self.mbox, self.txt))
+
+
+
+class Record_HINFO(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ Host information.
+
+ @type cpu: C{str}
+ @ivar cpu: Specifies the CPU type.
+
+ @type os: C{str}
+ @ivar os: Specifies the OS.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ implements(IEncodable, IRecord)
+ TYPE = HINFO
+
+ fancybasename = 'HINFO'
+ showAttributes = compareAttributes = ('cpu', 'os', 'ttl')
+
+ def __init__(self, cpu='', os='', ttl=None):
+ self.cpu, self.os = cpu, os
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ strio.write(struct.pack('!B', len(self.cpu)) + self.cpu)
+ strio.write(struct.pack('!B', len(self.os)) + self.os)
+
+
+ def decode(self, strio, length = None):
+ cpu = struct.unpack('!B', readPrecisely(strio, 1))[0]
+ self.cpu = readPrecisely(strio, cpu)
+ os = struct.unpack('!B', readPrecisely(strio, 1))[0]
+ self.os = readPrecisely(strio, os)
+
+
+ def __eq__(self, other):
+ if isinstance(other, Record_HINFO):
+ return (self.os.lower() == other.os.lower() and
+ self.cpu.lower() == other.cpu.lower() and
+ self.ttl == other.ttl)
+ return NotImplemented
+
+
+ def __hash__(self):
+ return hash((self.os.lower(), self.cpu.lower()))
+
+
+
+class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ Mailbox or mail list information.
+
+ This is an experimental record type.
+
+ @type rmailbx: L{Name}
+ @ivar rmailbx: A domain-name which specifies a mailbox which is responsible
+ for the mailing list or mailbox. If this domain name names the root,
+ the owner of the MINFO RR is responsible for itself.
+
+ @type emailbx: L{Name}
+ @ivar emailbx: A domain-name which specifies a mailbox which is to receive
+ error messages related to the mailing list or mailbox specified by the
+ owner of the MINFO record. If this domain name names the root, errors
+ should be returned to the sender of the message.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ implements(IEncodable, IRecord)
+ TYPE = MINFO
+
+ rmailbx = None
+ emailbx = None
+
+ fancybasename = 'MINFO'
+ compareAttributes = ('rmailbx', 'emailbx', 'ttl')
+ showAttributes = (('rmailbx', 'responsibility', '%s'),
+ ('emailbx', 'errors', '%s'),
+ 'ttl')
+
+ def __init__(self, rmailbx='', emailbx='', ttl=None):
+ self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx)
+ self.ttl = str2time(ttl)
+
+
+ def encode(self, strio, compDict = None):
+ self.rmailbx.encode(strio, compDict)
+ self.emailbx.encode(strio, compDict)
+
+
+ def decode(self, strio, length = None):
+ self.rmailbx, self.emailbx = Name(), Name()
+ self.rmailbx.decode(strio)
+ self.emailbx.decode(strio)
+
+
+ def __hash__(self):
+ return hash((self.rmailbx, self.emailbx))
+
+
+
+class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin):
+ """
+ Mail exchange.
+
+ @type preference: C{int}
+ @ivar preference: Specifies the preference given to this RR among others at
+ the same owner. Lower values are preferred.
+
+ @type name: L{Name}
+ @ivar name: A domain-name which specifies a host willing to act as a mail
+ exchange.
+
+ @type ttl: C{int}
+ @ivar ttl: The maximum number of seconds which this record should be
+ cached.
+ """
+ implements(IEncodable, IRecord)
+ TYPE = MX
+
+ fancybasename = 'MX'
+ compareAttributes = ('preference', 'name', 'ttl')
+ showAttributes = ('preference', ('name', 'name', '%s'), 'ttl')
+
+ def __init__(self, preference=0, name='', ttl=None, **kwargs):
+ self.preference, self.name = int(preference), Name(kwargs.get('exchange', name))
+ self.ttl = str2time(ttl)
+
+ def encode(self, strio, compDict = None):
+ strio.write(struct.pack('!H', self.preference))
+ self.name.encode(strio, compDict)
+
+
+ def decode(self, strio, length = None):
+ self.preference = struct.unpack('!H', readPrecisely(strio, 2))[0]
+ self.name = Name()
+ self.name.decode(strio)
+
+ def exchange(self):
+ warnings.warn("use Record_MX.name instead", DeprecationWarning, stacklevel=2)
+ return self.name
+
+ exchange = property(exchange)
+
+ def __hash__(self):
+ return hash((self.preference, self.name))
+
+
+
+# Oh god, Record_TXT how I hate thee.
+class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
+ """
+ Freeform text.
+
+ @type data: C{list} of C{str}
+ @ivar data: Freeform text which makes up this record.
+ """
+ implements(IEncodable, IRecord)
+
+ TYPE = TXT
+
+ fancybasename = 'TXT'
+ showAttributes = compareAttributes = ('data', 'ttl')
+
+ def __init__(self, *data, **kw):
+ self.data = list(data)
+ # arg man python sucks so bad
+ self.ttl = str2time(kw.get('ttl', None))
+
+
+ def encode(self, strio, compDict = None):
+ for d in self.data:
+ strio.write(struct.pack('!B', len(d)) + d)
+
+
+ def decode(self, strio, length = None):
+ soFar = 0
+ self.data = []
+ while soFar < length:
+ L = struct.unpack('!B', readPrecisely(strio, 1))[0]
+ self.data.append(readPrecisely(strio, L))
+ soFar += L + 1
+ if soFar != length:
+ log.msg(
+ "Decoded %d bytes in TXT record, but rdlength is %d" % (
+ soFar, length
+ )
+ )
+
+
+ def __hash__(self):
+ return hash(tuple(self.data))
+
+
+
+class Message:
+ headerFmt = "!H2B4H"
+ headerSize = struct.calcsize(headerFmt)
+
+ # Question, answer, additional, and nameserver lists
+ queries = answers = add = ns = None
+
+ def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0,
+ auth=0, rCode=OK, trunc=0, maxSize=512):
+ self.maxSize = maxSize
+ self.id = id
+ self.answer = answer
+ self.opCode = opCode
+ self.auth = auth
+ self.trunc = trunc
+ self.recDes = recDes
+ self.recAv = recAv
+ self.rCode = rCode
+ self.queries = []
+ self.answers = []
+ self.authority = []
+ self.additional = []
+
+
+ def addQuery(self, name, type=ALL_RECORDS, cls=IN):
+ """
+ Add another query to this Message.
+
+ @type name: C{str}
+ @param name: The name to query.
+
+ @type type: C{int}
+ @param type: Query type
+
+ @type cls: C{int}
+ @param cls: Query class
+ """
+ self.queries.append(Query(name, type, cls))
+
+
+ def encode(self, strio):
+ compDict = {}
+ body_tmp = StringIO.StringIO()
+ for q in self.queries:
+ q.encode(body_tmp, compDict)
+ for q in self.answers:
+ q.encode(body_tmp, compDict)
+ for q in self.authority:
+ q.encode(body_tmp, compDict)
+ for q in self.additional:
+ q.encode(body_tmp, compDict)
+ body = body_tmp.getvalue()
+ size = len(body) + self.headerSize
+ if self.maxSize and size > self.maxSize:
+ self.trunc = 1
+ body = body[:self.maxSize - self.headerSize]
+ byte3 = (( ( self.answer & 1 ) << 7 )
+ | ((self.opCode & 0xf ) << 3 )
+ | ((self.auth & 1 ) << 2 )
+ | ((self.trunc & 1 ) << 1 )
+ | ( self.recDes & 1 ) )
+ byte4 = ( ( (self.recAv & 1 ) << 7 )
+ | (self.rCode & 0xf ) )
+
+ strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4,
+ len(self.queries), len(self.answers),
+ len(self.authority), len(self.additional)))
+ strio.write(body)
+
+
+ def decode(self, strio, length=None):
+ self.maxSize = 0
+ header = readPrecisely(strio, self.headerSize)
+ r = struct.unpack(self.headerFmt, header)
+ self.id, byte3, byte4, nqueries, nans, nns, nadd = r
+ self.answer = ( byte3 >> 7 ) & 1
+ self.opCode = ( byte3 >> 3 ) & 0xf
+ self.auth = ( byte3 >> 2 ) & 1
+ self.trunc = ( byte3 >> 1 ) & 1
+ self.recDes = byte3 & 1
+ self.recAv = ( byte4 >> 7 ) & 1
+ self.rCode = byte4 & 0xf
+
+ self.queries = []
+ for i in range(nqueries):
+ q = Query()
+ try:
+ q.decode(strio)
+ except EOFError:
+ return
+ self.queries.append(q)
+
+ items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd))
+ for (l, n) in items:
+ self.parseRecords(l, n, strio)
+
+
+ def parseRecords(self, list, num, strio):
+ for i in range(num):
+ header = RRHeader()
+ try:
+ header.decode(strio)
+ except EOFError:
+ return
+ t = self.lookupRecordType(header.type)
+ if not t:
+ continue
+ header.payload = t(ttl=header.ttl)
+ try:
+ header.payload.decode(strio, header.rdlength)
+ except EOFError:
+ return
+ list.append(header)
+
+
+ def lookupRecordType(self, type):
+ return globals().get('Record_' + QUERY_TYPES.get(type, ''), None)
+
+
+ def toStr(self):
+ strio = StringIO.StringIO()
+ self.encode(strio)
+ return strio.getvalue()
+
+
+ def fromStr(self, str):
+ strio = StringIO.StringIO(str)
+ self.decode(strio)
+
+class DNSMixin(object):
+ """
+ DNS protocol mixin shared by UDP and TCP implementations.
+
+ @ivar _reactor: A L{IReactorTime} and L{IReactorUDP} provider which will
+ be used to issue DNS queries and manage request timeouts.
+ """
+ id = None
+ liveMessages = None
+
+ def __init__(self, controller, reactor=None):
+ self.controller = controller
+ self.id = random.randrange(2 ** 10, 2 ** 15)
+ if reactor is None:
+ from twisted.internet import reactor
+ self._reactor = reactor
+
+
+ def pickID(self):
+ """
+ Return a unique ID for queries.
+ """
+ while True:
+ id = randomSource()
+ if id not in self.liveMessages:
+ return id
+
+
+ def callLater(self, period, func, *args):
+ """
+ Wrapper around reactor.callLater, mainly for test purpose.
+ """
+ return self._reactor.callLater(period, func, *args)
+
+
+ def _query(self, queries, timeout, id, writeMessage):
+ """
+ Send out a message with the given queries.
+
+ @type queries: C{list} of C{Query} instances
+ @param queries: The queries to transmit
+
+ @type timeout: C{int} or C{float}
+ @param timeout: How long to wait before giving up
+
+ @type id: C{int}
+ @param id: Unique key for this request
+
+ @type writeMessage: C{callable}
+ @param writeMessage: One-parameter callback which writes the message
+
+ @rtype: C{Deferred}
+ @return: a C{Deferred} which will be fired with the result of the
+ query, or errbacked with any errors that could happen (exceptions
+ during writing of the query, timeout errors, ...).
+ """
+ m = Message(id, recDes=1)
+ m.queries = queries
+
+ try:
+ writeMessage(m)
+ except:
+ return defer.fail()
+
+ resultDeferred = defer.Deferred()
+ cancelCall = self.callLater(timeout, self._clearFailed, resultDeferred, id)
+ self.liveMessages[id] = (resultDeferred, cancelCall)
+
+ return resultDeferred
+
+ def _clearFailed(self, deferred, id):
+ """
+ Clean the Deferred after a timeout.
+ """
+ try:
+ del self.liveMessages[id]
+ except KeyError:
+ pass
+ deferred.errback(failure.Failure(DNSQueryTimeoutError(id)))
+
+
+class DNSDatagramProtocol(DNSMixin, protocol.DatagramProtocol):
+ """
+ DNS protocol over UDP.
+ """
+ resends = None
+
+ def stopProtocol(self):
+ """
+ Stop protocol: reset state variables.
+ """
+ self.liveMessages = {}
+ self.resends = {}
+ self.transport = None
+
+ def startProtocol(self):
+ """
+ Upon start, reset internal state.
+ """
+ self.liveMessages = {}
+ self.resends = {}
+
+ def writeMessage(self, message, address):
+ """
+ Send a message holding DNS queries.
+
+ @type message: L{Message}
+ """
+ self.transport.write(message.toStr(), address)
+
+ def startListening(self):
+ self._reactor.listenUDP(0, self, maxPacketSize=512)
+
+ def datagramReceived(self, data, addr):
+ """
+ Read a datagram, extract the message in it and trigger the associated
+ Deferred.
+ """
+ m = Message()
+ try:
+ m.fromStr(data)
+ except EOFError:
+ log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr))
+ return
+ except:
+ # Nothing should trigger this, but since we're potentially
+ # invoking a lot of different decoding methods, we might as well
+ # be extra cautious. Anything that triggers this is itself
+ # buggy.
+ log.err(failure.Failure(), "Unexpected decoding error")
+ return
+
+ if m.id in self.liveMessages:
+ d, canceller = self.liveMessages[m.id]
+ del self.liveMessages[m.id]
+ canceller.cancel()
+ # XXX we shouldn't need this hack of catching exception on callback()
+ try:
+ d.callback(m)
+ except:
+ log.err()
+ else:
+ if m.id not in self.resends:
+ self.controller.messageReceived(m, self, addr)
+
+
+ def removeResend(self, id):
+ """
+ Mark message ID as no longer having duplication suppression.
+ """
+ try:
+ del self.resends[id]
+ except KeyError:
+ pass
+
+ def query(self, address, queries, timeout=10, id=None):
+ """
+ Send out a message with the given queries.
+
+ @type address: C{tuple} of C{str} and C{int}
+ @param address: The address to which to send the query
+
+ @type queries: C{list} of C{Query} instances
+ @param queries: The queries to transmit
+
+ @rtype: C{Deferred}
+ """
+ if not self.transport:
+ # XXX transport might not get created automatically, use callLater?
+ try:
+ self.startListening()
+ except CannotListenError:
+ return defer.fail()
+
+ if id is None:
+ id = self.pickID()
+ else:
+ self.resends[id] = 1
+
+ def writeMessage(m):
+ self.writeMessage(m, address)
+
+ return self._query(queries, timeout, id, writeMessage)
+
+
+class DNSProtocol(DNSMixin, protocol.Protocol):
+ """
+ DNS protocol over TCP.
+ """
+ length = None
+ buffer = ''
+
+ def writeMessage(self, message):
+ """
+ Send a message holding DNS queries.
+
+ @type message: L{Message}
+ """
+ s = message.toStr()
+ self.transport.write(struct.pack('!H', len(s)) + s)
+
+ def connectionMade(self):
+ """
+ Connection is made: reset internal state, and notify the controller.
+ """
+ self.liveMessages = {}
+ self.controller.connectionMade(self)
+
+
+ def connectionLost(self, reason):
+ """
+ Notify the controller that this protocol is no longer
+ connected.
+ """
+ self.controller.connectionLost(self)
+
+
+ def dataReceived(self, data):
+ self.buffer += data
+
+ while self.buffer:
+ if self.length is None and len(self.buffer) >= 2:
+ self.length = struct.unpack('!H', self.buffer[:2])[0]
+ self.buffer = self.buffer[2:]
+
+ if len(self.buffer) >= self.length:
+ myChunk = self.buffer[:self.length]
+ m = Message()
+ m.fromStr(myChunk)
+
+ try:
+ d, canceller = self.liveMessages[m.id]
+ except KeyError:
+ self.controller.messageReceived(m, self)
+ else:
+ del self.liveMessages[m.id]
+ canceller.cancel()
+ # XXX we shouldn't need this hack
+ try:
+ d.callback(m)
+ except:
+ log.err()
+
+ self.buffer = self.buffer[self.length:]
+ self.length = None
+ else:
+ break
+
+
+ def query(self, queries, timeout=60):
+ """
+ Send out a message with the given queries.
+
+ @type queries: C{list} of C{Query} instances
+ @param queries: The queries to transmit
+
+ @rtype: C{Deferred}
+ """
+ id = self.pickID()
+ return self._query(queries, timeout, id, self.writeMessage)
+
diff --git a/vendor/Twisted-10.0.0/twisted/names/error.py b/vendor/Twisted-10.0.0/twisted/names/error.py
new file mode 100644
index 0000000000..379a7d4cc9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/error.py
@@ -0,0 +1,95 @@
+# -*- test-case-name: twisted.names.test -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Exception class definitions for Twisted Names.
+"""
+
+from twisted.internet.defer import TimeoutError
+
+
+class DomainError(ValueError):
+ """
+ Indicates a lookup failed because there were no records matching the given
+ C{name, class, type} triple.
+ """
+
+
+
+class AuthoritativeDomainError(ValueError):
+ """
+ Indicates a lookup failed for a name for which this server is authoritative
+ because there were no records matching the given C{name, class, type}
+ triple.
+ """
+
+
+
+class DNSQueryTimeoutError(TimeoutError):
+ """
+ Indicates a lookup failed due to a timeout.
+
+ @ivar id: The id of the message which timed out.
+ """
+ def __init__(self, id):
+ TimeoutError.__init__(self)
+ self.id = id
+
+
+
+class DNSFormatError(DomainError):
+ """
+ Indicates a query failed with a result of L{twisted.names.dns.EFORMAT}.
+ """
+
+
+
+class DNSServerError(DomainError):
+ """
+ Indicates a query failed with a result of L{twisted.names.dns.ESERVER}.
+ """
+
+
+
+class DNSNameError(DomainError):
+ """
+ Indicates a query failed with a result of L{twisted.names.dns.ENAME}.
+ """
+
+
+
+class DNSNotImplementedError(DomainError):
+ """
+ Indicates a query failed with a result of L{twisted.names.dns.ENOTIMP}.
+ """
+
+
+
+class DNSQueryRefusedError(DomainError):
+ """
+ Indicates a query failed with a result of L{twisted.names.dns.EREFUSED}.
+ """
+
+
+
+class DNSUnknownError(DomainError):
+ """
+ Indicates a query failed with an unknown result.
+ """
+
+
+
+class ResolverError(Exception):
+ """
+ Indicates a query failed because of a decision made by the local
+ resolver object.
+ """
+
+
+__all__ = [
+ 'DomainError', 'AuthoritativeDomainError', 'DNSQueryTimeoutError',
+
+ 'DNSFormatError', 'DNSServerError', 'DNSNameError',
+ 'DNSNotImplementedError', 'DNSQueryRefusedError',
+ 'DNSUnknownError', 'ResolverError']
diff --git a/vendor/Twisted-10.0.0/twisted/names/hosts.py b/vendor/Twisted-10.0.0/twisted/names/hosts.py
new file mode 100644
index 0000000000..fa0b340227
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/hosts.py
@@ -0,0 +1,61 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.names import dns
+from twisted.persisted import styles
+from twisted.python import failure
+from twisted.internet import defer
+
+from twisted.names import common
+
+def searchFileFor(file, name):
+ try:
+ fp = open(file)
+ except:
+ return None
+
+ lines = fp.readlines()
+ for line in lines:
+ idx = line.find('#')
+ if idx != -1:
+ line = line[:idx]
+ if not line:
+ continue
+ parts = line.split()
+ if name.lower() in [s.lower() for s in parts[1:]]:
+ return parts[0]
+ return None
+
+
+
+class Resolver(common.ResolverBase, styles.Versioned):
+ """A resolver that services hosts(5) format files."""
+ #TODO: IPv6 support
+
+ persistenceVersion = 1
+
+ def upgradeToVersion1(self):
+ # <3 exarkun
+ self.typeToMethod = {}
+ for (k, v) in common.typeToMethod.items():
+ self.typeToMethod[k] = getattr(self, v)
+
+
+ def __init__(self, file='/etc/hosts', ttl = 60 * 60):
+ common.ResolverBase.__init__(self)
+ self.file = file
+ self.ttl = ttl
+
+
+ def lookupAddress(self, name, timeout = None):
+ res = searchFileFor(self.file, name)
+ if res:
+ return defer.succeed([
+ (dns.RRHeader(name, dns.A, dns.IN, self.ttl, dns.Record_A(res, self.ttl)),), (), ()
+ ])
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+
+
+ # When we put IPv6 support in, this'll need a real impl
+ lookupAllRecords = lookupAddress
diff --git a/vendor/Twisted-10.0.0/twisted/names/resolve.py b/vendor/Twisted-10.0.0/twisted/names/resolve.py
new file mode 100644
index 0000000000..c677813819
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/resolve.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Lookup a name using multiple resolvers.
+
+Future Plans: This needs someway to specify which resolver answered
+the query, or someway to specify (authority|ttl|cache behavior|more?)
+
+@author: Jp Calderone
+"""
+
+from twisted.internet import defer, interfaces
+from twisted.names import dns
+from zope.interface import implements
+import common
+
+class FailureHandler:
+ def __init__(self, resolver, query, timeout):
+ self.resolver = resolver
+ self.query = query
+ self.timeout = timeout
+
+
+ def __call__(self, failure):
+ # AuthoritativeDomainErrors should halt resolution attempts
+ failure.trap(dns.DomainError, defer.TimeoutError, NotImplementedError)
+ return self.resolver(self.query, self.timeout)
+
+
+class ResolverChain(common.ResolverBase):
+ """Lookup an address using multiple C{IResolver}s"""
+
+ implements(interfaces.IResolver)
+
+
+ def __init__(self, resolvers):
+ common.ResolverBase.__init__(self)
+ self.resolvers = resolvers
+
+
+ def _lookup(self, name, cls, type, timeout):
+ q = dns.Query(name, type, cls)
+ d = self.resolvers[0].query(q, timeout)
+ for r in self.resolvers[1:]:
+ d = d.addErrback(
+ FailureHandler(r.query, q, timeout)
+ )
+ return d
+
+
+ def lookupAllRecords(self, name, timeout = None):
+ d = self.resolvers[0].lookupAllRecords(name, timeout)
+ for r in self.resolvers[1:]:
+ d = d.addErrback(
+ FailureHandler(r.lookupAllRecords, name, timeout)
+ )
+ return d
diff --git a/vendor/Twisted-10.0.0/twisted/names/root.py b/vendor/Twisted-10.0.0/twisted/names/root.py
new file mode 100644
index 0000000000..f1b067a384
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/root.py
@@ -0,0 +1,446 @@
+# -*- test-case-name: twisted.names.test.test_rootresolve -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Resolver implementation for querying successive authoritative servers to
+lookup a record, starting from the root nameservers.
+
+@author: Jp Calderone
+
+todo::
+ robustify it
+ documentation
+"""
+
+import warnings
+
+from twisted.python.failure import Failure
+from twisted.internet import defer
+from twisted.names import dns, common, error
+
+
+def retry(t, p, *args):
+ """
+ Issue a query one or more times.
+
+ This function is deprecated. Use one of the resolver classes for retry
+ logic, or implement it yourself.
+ """
+ warnings.warn(
+ "twisted.names.root.retry is deprecated since Twisted 10.0. Use a "
+ "Resolver object for retry logic.", category=DeprecationWarning,
+ stacklevel=2)
+
+ assert t, "Timeout is required"
+ t = list(t)
+ def errback(failure):
+ failure.trap(defer.TimeoutError)
+ if not t:
+ return failure
+ return p.query(timeout=t.pop(0), *args
+ ).addErrback(errback
+ )
+ return p.query(timeout=t.pop(0), *args
+ ).addErrback(errback
+ )
+
+
+
+class _DummyController:
+ """
+ A do-nothing DNS controller. This is useful when all messages received
+ will be responses to previously issued queries. Anything else received
+ will be ignored.
+ """
+ def messageReceived(self, *args):
+ pass
+
+
+
+class Resolver(common.ResolverBase):
+ """
+ L{Resolver} implements recursive lookup starting from a specified list of
+ root servers.
+
+ @ivar hints: A C{list} of C{str} giving the dotted quad representation
+ of IP addresses of root servers at which to begin resolving names.
+
+ @ivar _maximumQueries: A C{int} giving the maximum number of queries
+ which will be attempted to resolve a single name.
+
+ @ivar _reactor: A L{IReactorTime} and L{IReactorUDP} provider to use to
+ bind UDP ports and manage timeouts.
+ """
+ def __init__(self, hints, maximumQueries=10, reactor=None):
+ common.ResolverBase.__init__(self)
+ self.hints = hints
+ self._maximumQueries = maximumQueries
+ self._reactor = reactor
+
+
+ def _roots(self):
+ """
+ Return a list of two-tuples representing the addresses of the root
+ servers, as defined by C{self.hints}.
+ """
+ return [(ip, dns.PORT) for ip in self.hints]
+
+
+ def _query(self, query, servers, timeout, filter):
+ """
+ Issue one query and return a L{Deferred} which fires with its response.
+
+ @param query: The query to issue.
+ @type query: L{dns.Query}
+
+ @param servers: The servers which might have an answer for this
+ query.
+ @type servers: L{list} of L{tuple} of L{str} and L{int}
+
+ @param timeout: A timeout on how long to wait for the response.
+ @type timeout: L{tuple} of L{int}
+
+ @param filter: A flag indicating whether to filter the results. If
+ C{True}, the returned L{Deferred} will fire with a three-tuple of
+ lists of L{RRHeaders} (like the return value of the I{lookup*}
+ methods of L{IResolver}. IF C{False}, the result will be a
+ L{Message} instance.
+ @type filter: L{bool}
+
+ @return: A L{Deferred} which fires with the response or a timeout
+ error.
+ @rtype: L{Deferred}
+ """
+ from twisted.names import client
+ r = client.Resolver(servers=servers, reactor=self._reactor)
+ d = r.queryUDP([query], timeout)
+ if filter:
+ d.addCallback(r.filterAnswers)
+ return d
+
+
+ def _lookup(self, name, cls, type, timeout):
+ """
+ Implement name lookup by recursively discovering the authoritative
+ server for the name and then asking it, starting at one of the servers
+ in C{self.hints}.
+ """
+ if timeout is None:
+ # A series of timeouts for semi-exponential backoff, summing to an
+ # arbitrary total of 60 seconds.
+ timeout = (1, 3, 11, 45)
+ return self._discoverAuthority(
+ dns.Query(name, type, cls), self._roots(), timeout,
+ self._maximumQueries)
+
+
+ def _discoverAuthority(self, query, servers, timeout, queriesLeft):
+ """
+ Issue a query to a server and follow a delegation if necessary.
+
+ @param query: The query to issue.
+ @type query: L{dns.Query}
+
+ @param servers: The servers which might have an answer for this
+ query.
+ @type servers: L{list} of L{tuple} of L{str} and L{int}
+
+ @param timeout: A C{tuple} of C{int} giving the timeout to use for this
+ query.
+
+ @param queriesLeft: A C{int} giving the number of queries which may
+ yet be attempted to answer this query before the attempt will be
+ abandoned.
+
+ @return: A L{Deferred} which fires with a three-tuple of lists of
+ L{RRHeaders} giving the response, or with a L{Failure} if there is
+ a timeout or response error.
+ """
+ # Stop now if we've hit the query limit.
+ if queriesLeft <= 0:
+ return Failure(
+ error.ResolverError("Query limit reached without result"))
+
+ d = self._query(query, servers, timeout, False)
+ d.addCallback(
+ self._discoveredAuthority, query, timeout, queriesLeft - 1)
+ return d
+
+
+ def _discoveredAuthority(self, response, query, timeout, queriesLeft):
+ """
+ Interpret the response to a query, checking for error codes and
+ following delegations if necessary.
+
+ @param response: The L{Message} received in response to issuing C{query}.
+ @type response: L{Message}
+
+ @param query: The L{dns.Query} which was issued.
+ @type query: L{dns.Query}.
+
+ @param timeout: The timeout to use if another query is indicated by
+ this response.
+ @type timeout: L{tuple} of L{int}
+
+ @param queriesLeft: A C{int} giving the number of queries which may
+ yet be attempted to answer this query before the attempt will be
+ abandoned.
+
+ @return: A L{Failure} indicating a response error, a three-tuple of
+ lists of L{RRHeaders} giving the response to C{query} or a
+ L{Deferred} which will fire with one of those.
+ """
+ if response.rCode != dns.OK:
+ return Failure(self.exceptionForCode(response.rCode)(response))
+
+ # Turn the answers into a structure that's a little easier to work with.
+ records = {}
+ for answer in response.answers:
+ records.setdefault(answer.name, []).append(answer)
+
+ def findAnswerOrCName(name, type, cls):
+ cname = None
+ for record in records.get(name, []):
+ if record.cls == cls:
+ if record.type == type:
+ return record
+ elif record.type == dns.CNAME:
+ cname = record
+ # If there were any CNAME records, return the last one. There's
+ # only supposed to be zero or one, though.
+ return cname
+
+ seen = set()
+ name = query.name
+ record = None
+ while True:
+ seen.add(name)
+ previous = record
+ record = findAnswerOrCName(name, query.type, query.cls)
+ if record is None:
+ if name == query.name:
+ # If there's no answer for the original name, then this may
+ # be a delegation. Code below handles it.
+ break
+ else:
+ # Try to resolve the CNAME with another query.
+ d = self._discoverAuthority(
+ dns.Query(str(name), query.type, query.cls),
+ self._roots(), timeout, queriesLeft)
+ # We also want to include the CNAME in the ultimate result,
+ # otherwise this will be pretty confusing.
+ def cbResolved((answers, authority, additional)):
+ answers.insert(0, previous)
+ return (answers, authority, additional)
+ d.addCallback(cbResolved)
+ return d
+ elif record.type == query.type:
+ return (
+ response.answers,
+ response.authority,
+ response.additional)
+ else:
+ # It's a CNAME record. Try to resolve it from the records
+ # in this response with another iteration around the loop.
+ if record.payload.name in seen:
+ raise error.ResolverError("Cycle in CNAME processing")
+ name = record.payload.name
+
+
+ # Build a map to use to convert NS names into IP addresses.
+ addresses = {}
+ for rr in response.additional:
+ if rr.type == dns.A:
+ addresses[str(rr.name)] = rr.payload.dottedQuad()
+
+ hints = []
+ traps = []
+ for rr in response.authority:
+ if rr.type == dns.NS:
+ ns = str(rr.payload.name)
+ if ns in addresses:
+ hints.append((addresses[ns], dns.PORT))
+ else:
+ traps.append(ns)
+ if hints:
+ return self._discoverAuthority(
+ query, hints, timeout, queriesLeft)
+ elif traps:
+ d = self.lookupAddress(traps[0], timeout)
+ d.addCallback(
+ lambda (answers, authority, additional):
+ answers[0].payload.dottedQuad())
+ d.addCallback(
+ lambda hint: self._discoverAuthority(
+ query, [(hint, dns.PORT)], timeout, queriesLeft - 1))
+ return d
+ else:
+ return Failure(error.ResolverError(
+ "Stuck at response without answers or delegation"))
+
+
+ def discoveredAuthority(self, auth, name, cls, type, timeout):
+ warnings.warn(
+ 'twisted.names.root.Resolver.discoveredAuthority is deprecated since '
+ 'Twisted 10.0. Use twisted.names.client.Resolver directly, instead.',
+ category=DeprecationWarning, stacklevel=2)
+ from twisted.names import client
+ q = dns.Query(name, type, cls)
+ r = client.Resolver(servers=[(auth, dns.PORT)])
+ d = r.queryUDP([q], timeout)
+ d.addCallback(r.filterAnswers)
+ return d
+
+
+
+def lookupNameservers(host, atServer, p=None):
+ warnings.warn(
+ 'twisted.names.root.lookupNameservers is deprecated since Twisted '
+ '10.0. Use twisted.names.root.Resolver.lookupNameservers instead.',
+ category=DeprecationWarning, stacklevel=2)
+ # print 'Nameserver lookup for', host, 'at', atServer, 'with', p
+ if p is None:
+ p = dns.DNSDatagramProtocol(_DummyController())
+ p.noisy = False
+ return retry(
+ (1, 3, 11, 45), # Timeouts
+ p, # Protocol instance
+ (atServer, dns.PORT), # Server to query
+ [dns.Query(host, dns.NS, dns.IN)] # Question to ask
+ )
+
+def lookupAddress(host, atServer, p=None):
+ warnings.warn(
+ 'twisted.names.root.lookupAddress is deprecated since Twisted '
+ '10.0. Use twisted.names.root.Resolver.lookupAddress instead.',
+ category=DeprecationWarning, stacklevel=2)
+ # print 'Address lookup for', host, 'at', atServer, 'with', p
+ if p is None:
+ p = dns.DNSDatagramProtocol(_DummyController())
+ p.noisy = False
+ return retry(
+ (1, 3, 11, 45), # Timeouts
+ p, # Protocol instance
+ (atServer, dns.PORT), # Server to query
+ [dns.Query(host, dns.A, dns.IN)] # Question to ask
+ )
+
+def extractAuthority(msg, cache):
+ warnings.warn(
+ 'twisted.names.root.extractAuthority is deprecated since Twisted '
+ '10.0. Please inspect the Message object directly.',
+ category=DeprecationWarning, stacklevel=2)
+ records = msg.answers + msg.authority + msg.additional
+ nameservers = [r for r in records if r.type == dns.NS]
+
+ # print 'Records for', soFar, ':', records
+ # print 'NS for', soFar, ':', nameservers
+
+ if not nameservers:
+ return None, nameservers
+ if not records:
+ raise IOError("No records")
+ for r in records:
+ if r.type == dns.A:
+ cache[str(r.name)] = r.payload.dottedQuad()
+ for r in records:
+ if r.type == dns.NS:
+ if str(r.payload.name) in cache:
+ return cache[str(r.payload.name)], nameservers
+ for addr in records:
+ if addr.type == dns.A and addr.name == r.name:
+ return addr.payload.dottedQuad(), nameservers
+ return None, nameservers
+
+def discoverAuthority(host, roots, cache=None, p=None):
+ warnings.warn(
+ 'twisted.names.root.discoverAuthority is deprecated since Twisted '
+ '10.0. Use twisted.names.root.Resolver.lookupNameservers instead.',
+ category=DeprecationWarning, stacklevel=4)
+
+ if cache is None:
+ cache = {}
+
+ rootAuths = list(roots)
+
+ parts = host.rstrip('.').split('.')
+ parts.reverse()
+
+ authority = rootAuths.pop()
+
+ soFar = ''
+ for part in parts:
+ soFar = part + '.' + soFar
+ # print '///////', soFar, authority, p
+ msg = defer.waitForDeferred(lookupNameservers(soFar, authority, p))
+ yield msg
+ msg = msg.getResult()
+
+ newAuth, nameservers = extractAuthority(msg, cache)
+
+ if newAuth is not None:
+ # print "newAuth is not None"
+ authority = newAuth
+ else:
+ if nameservers:
+ r = str(nameservers[0].payload.name)
+ # print 'Recursively discovering authority for', r
+ authority = defer.waitForDeferred(discoverAuthority(r, roots, cache, p))
+ yield authority
+ authority = authority.getResult()
+ # print 'Discovered to be', authority, 'for', r
+## else:
+## # print 'Doing address lookup for', soFar, 'at', authority
+## msg = defer.waitForDeferred(lookupAddress(soFar, authority, p))
+## yield msg
+## msg = msg.getResult()
+## records = msg.answers + msg.authority + msg.additional
+## addresses = [r for r in records if r.type == dns.A]
+## if addresses:
+## authority = addresses[0].payload.dottedQuad()
+## else:
+## raise IOError("Resolution error")
+ # print "Yielding authority", authority
+ yield authority
+
+discoverAuthority = defer.deferredGenerator(discoverAuthority)
+
+def makePlaceholder(deferred, name):
+ def placeholder(*args, **kw):
+ deferred.addCallback(lambda r: getattr(r, name)(*args, **kw))
+ return deferred
+ return placeholder
+
+class DeferredResolver:
+ def __init__(self, resolverDeferred):
+ self.waiting = []
+ resolverDeferred.addCallback(self.gotRealResolver)
+
+ def gotRealResolver(self, resolver):
+ w = self.waiting
+ self.__dict__ = resolver.__dict__
+ self.__class__ = resolver.__class__
+ for d in w:
+ d.callback(resolver)
+
+ def __getattr__(self, name):
+ if name.startswith('lookup') or name in ('getHostByName', 'query'):
+ self.waiting.append(defer.Deferred())
+ return makePlaceholder(self.waiting[-1], name)
+ raise AttributeError(name)
+
+def bootstrap(resolver):
+ """Lookup the root nameserver addresses using the given resolver
+
+ Return a Resolver which will eventually become a C{root.Resolver}
+ instance that has references to all the root servers that we were able
+ to look up.
+ """
+ domains = [chr(ord('a') + i) for i in range(13)]
+ # f = lambda r: (log.msg('Root server address: ' + str(r)), r)[1]
+ f = lambda r: r
+ L = [resolver.getHostByName('%s.root-servers.net' % d).addCallback(f) for d in domains]
+ d = defer.DeferredList(L)
+ d.addCallback(lambda r: Resolver([e[1] for e in r if e[0]]))
+ return DeferredResolver(d)
diff --git a/vendor/Twisted-10.0.0/twisted/names/secondary.py b/vendor/Twisted-10.0.0/twisted/names/secondary.py
new file mode 100644
index 0000000000..19afa246e2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/secondary.py
@@ -0,0 +1,102 @@
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import task, defer
+from twisted.names import dns
+from twisted.names import common
+from twisted.names import client
+from twisted.names import resolve
+from twisted.python import log, failure
+from twisted.application import service
+
+class SecondaryAuthorityService(service.Service):
+ calls = None
+
+ def __init__(self, primary, domains):
+ """
+ @param primary: The IP address of the server from which to perform
+ zone transfers.
+
+ @param domains: A sequence of domain names for which to perform
+ zone transfers.
+ """
+ self.primary = primary
+ self.domains = [SecondaryAuthority(primary, d) for d in domains]
+
+ def getAuthority(self):
+ return resolve.ResolverChain(self.domains)
+
+ def startService(self):
+ service.Service.startService(self)
+ self.calls = [task.LoopingCall(d.transfer) for d in self.domains]
+ i = 0
+ from twisted.internet import reactor
+ for c in self.calls:
+ # XXX Add errbacks, respect proper timeouts
+ reactor.callLater(i, c.start, 60 * 60)
+ i += 1
+
+ def stopService(self):
+ service.Service.stopService(self)
+ for c in self.calls:
+ c.stop()
+
+
+from twisted.names.authority import FileAuthority
+
+class SecondaryAuthority(common.ResolverBase):
+ """An Authority that keeps itself updated by performing zone transfers"""
+
+ transferring = False
+
+ soa = records = None
+ def __init__(self, primaryIP, domain):
+ common.ResolverBase.__init__(self)
+ self.primary = primaryIP
+ self.domain = domain
+
+ def transfer(self):
+ if self.transferring:
+ return
+ self.transfering = True
+ return client.Resolver(servers=[(self.primary, dns.PORT)]
+ ).lookupZone(self.domain
+ ).addCallback(self._cbZone
+ ).addErrback(self._ebZone
+ )
+
+
+ def _lookup(self, name, cls, type, timeout=None):
+ if not self.soa or not self.records:
+ return defer.fail(failure.Failure(dns.DomainError(name)))
+
+
+ return FileAuthority.__dict__['_lookup'](self, name, cls, type, timeout)
+
+ #shouldn't we just subclass? :P
+
+ lookupZone = FileAuthority.__dict__['lookupZone']
+
+ def _cbZone(self, zone):
+ ans, _, _ = zone
+ self.records = r = {}
+ for rec in ans:
+ if not self.soa and rec.type == dns.SOA:
+ self.soa = (str(rec.name).lower(), rec.payload)
+ else:
+ r.setdefault(str(rec.name).lower(), []).append(rec.payload)
+
+ def _ebZone(self, failure):
+ log.msg("Updating %s from %s failed during zone transfer" % (self.domain, self.primary))
+ log.err(failure)
+
+ def update(self):
+ self.transfer().addCallbacks(self._cbTransferred, self._ebTransferred)
+
+ def _cbTransferred(self, result):
+ self.transferring = False
+
+ def _ebTransferred(self, failure):
+ self.transferred = False
+ log.msg("Transferring %s from %s failed after zone transfer" % (self.domain, self.primary))
+ log.err(failure)
diff --git a/vendor/Twisted-10.0.0/twisted/names/server.py b/vendor/Twisted-10.0.0/twisted/names/server.py
new file mode 100644
index 0000000000..d3ed07aa37
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/server.py
@@ -0,0 +1,205 @@
+# -*- test-case-name: twisted.names.test.test_names -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Async DNS server
+
+Future plans:
+ - Better config file format maybe
+ - Make sure to differentiate between different classes
+ - notice truncation bit
+
+Important: No additional processing is done on some of the record types.
+This violates the most basic RFC and is just plain annoying
+for resolvers to deal with. Fix it.
+
+@author: Jp Calderone
+"""
+
+import time
+
+from twisted.internet import protocol
+from twisted.names import dns, resolve
+from twisted.python import log
+
+
+class DNSServerFactory(protocol.ServerFactory):
+ """
+ Server factory and tracker for L{DNSProtocol} connections. This
+ class also provides records for responses to DNS queries.
+
+ @ivar connections: A list of all the connected L{DNSProtocol}
+ instances using this object as their controller.
+ @type connections: C{list} of L{DNSProtocol}
+ """
+
+ protocol = dns.DNSProtocol
+ cache = None
+
+ def __init__(self, authorities = None, caches = None, clients = None, verbose = 0):
+ resolvers = []
+ if authorities is not None:
+ resolvers.extend(authorities)
+ if caches is not None:
+ resolvers.extend(caches)
+ if clients is not None:
+ resolvers.extend(clients)
+
+ self.canRecurse = not not clients
+ self.resolver = resolve.ResolverChain(resolvers)
+ self.verbose = verbose
+ if caches:
+ self.cache = caches[-1]
+ self.connections = []
+
+
+ def buildProtocol(self, addr):
+ p = self.protocol(self)
+ p.factory = self
+ return p
+
+
+ def connectionMade(self, protocol):
+ """
+ Track a newly connected L{DNSProtocol}.
+ """
+ self.connections.append(protocol)
+
+
+ def connectionLost(self, protocol):
+ """
+ Stop tracking a no-longer connected L{DNSProtocol}.
+ """
+ self.connections.remove(protocol)
+
+
+ def sendReply(self, protocol, message, address):
+ if self.verbose > 1:
+ s = ' '.join([str(a.payload) for a in message.answers])
+ auth = ' '.join([str(a.payload) for a in message.authority])
+ add = ' '.join([str(a.payload) for a in message.additional])
+ if not s:
+ log.msg("Replying with no answers")
+ else:
+ log.msg("Answers are " + s)
+ log.msg("Authority is " + auth)
+ log.msg("Additional is " + add)
+
+ if address is None:
+ protocol.writeMessage(message)
+ else:
+ protocol.writeMessage(message, address)
+
+ if self.verbose > 1:
+ log.msg("Processed query in %0.3f seconds" % (time.time() - message.timeReceived))
+
+
+ def gotResolverResponse(self, (ans, auth, add), protocol, message, address):
+ message.rCode = dns.OK
+ message.answers = ans
+ for x in ans:
+ if x.isAuthoritative():
+ message.auth = 1
+ break
+ message.authority = auth
+ message.additional = add
+ self.sendReply(protocol, message, address)
+
+ l = len(ans) + len(auth) + len(add)
+ if self.verbose:
+ log.msg("Lookup found %d record%s" % (l, l != 1 and "s" or ""))
+
+ if self.cache and l:
+ self.cache.cacheResult(
+ message.queries[0], (ans, auth, add)
+ )
+
+
+ def gotResolverError(self, failure, protocol, message, address):
+ if failure.check(dns.DomainError, dns.AuthoritativeDomainError):
+ message.rCode = dns.ENAME
+ else:
+ message.rCode = dns.ESERVER
+ log.err(failure)
+
+ self.sendReply(protocol, message, address)
+ if self.verbose:
+ log.msg("Lookup failed")
+
+
+ def handleQuery(self, message, protocol, address):
+ # Discard all but the first query! HOO-AAH HOOOOO-AAAAH
+ # (no other servers implement multi-query messages, so we won't either)
+ query = message.queries[0]
+
+ return self.resolver.query(query).addCallback(
+ self.gotResolverResponse, protocol, message, address
+ ).addErrback(
+ self.gotResolverError, protocol, message, address
+ )
+
+
+ def handleInverseQuery(self, message, protocol, address):
+ message.rCode = dns.ENOTIMP
+ self.sendReply(protocol, message, address)
+ if self.verbose:
+ log.msg("Inverse query from %r" % (address,))
+
+
+ def handleStatus(self, message, protocol, address):
+ message.rCode = dns.ENOTIMP
+ self.sendReply(protocol, message, address)
+ if self.verbose:
+ log.msg("Status request from %r" % (address,))
+
+
+ def handleNotify(self, message, protocol, address):
+ message.rCode = dns.ENOTIMP
+ self.sendReply(protocol, message, address)
+ if self.verbose:
+ log.msg("Notify message from %r" % (address,))
+
+
+ def handleOther(self, message, protocol, address):
+ message.rCode = dns.ENOTIMP
+ self.sendReply(protocol, message, address)
+ if self.verbose:
+ log.msg("Unknown op code (%d) from %r" % (message.opCode, address))
+
+
+ def messageReceived(self, message, proto, address = None):
+ message.timeReceived = time.time()
+
+ if self.verbose:
+ if self.verbose > 1:
+ s = ' '.join([str(q) for q in message.queries])
+ elif self.verbose > 0:
+ s = ' '.join([dns.QUERY_TYPES.get(q.type, 'UNKNOWN') for q in message.queries])
+
+ if not len(s):
+ log.msg("Empty query from %r" % ((address or proto.transport.getPeer()),))
+ else:
+ log.msg("%s query from %r" % (s, address or proto.transport.getPeer()))
+
+ message.recAv = self.canRecurse
+ message.answer = 1
+
+ if not self.allowQuery(message, proto, address):
+ message.rCode = dns.EREFUSED
+ self.sendReply(proto, message, address)
+ elif message.opCode == dns.OP_QUERY:
+ self.handleQuery(message, proto, address)
+ elif message.opCode == dns.OP_INVERSE:
+ self.handleInverseQuery(message, proto, address)
+ elif message.opCode == dns.OP_STATUS:
+ self.handleStatus(message, proto, address)
+ elif message.opCode == dns.OP_NOTIFY:
+ self.handleNotify(message, proto, address)
+ else:
+ self.handleOther(message, proto, address)
+
+
+ def allowQuery(self, message, protocol, address):
+ # Allow anything but empty queries
+ return len(message.queries)
diff --git a/vendor/Twisted-10.0.0/twisted/names/srvconnect.py b/vendor/Twisted-10.0.0/twisted/names/srvconnect.py
new file mode 100644
index 0000000000..2b3220d6c8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/srvconnect.py
@@ -0,0 +1,186 @@
+# -*- test-case-name: twisted.names.test.test_srvconnect -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import random
+
+from zope.interface import implements
+
+from twisted.internet import error, interfaces
+
+from twisted.names import client, dns
+from twisted.names.error import DNSNameError
+from twisted.python.compat import reduce
+
+class _SRVConnector_ClientFactoryWrapper:
+ def __init__(self, connector, wrappedFactory):
+ self.__connector = connector
+ self.__wrappedFactory = wrappedFactory
+
+ def startedConnecting(self, connector):
+ self.__wrappedFactory.startedConnecting(self.__connector)
+
+ def clientConnectionFailed(self, connector, reason):
+ self.__connector.connectionFailed(reason)
+
+ def clientConnectionLost(self, connector, reason):
+ self.__connector.connectionLost(reason)
+
+ def __getattr__(self, key):
+ return getattr(self.__wrappedFactory, key)
+
+class SRVConnector:
+ """A connector that looks up DNS SRV records. See RFC2782."""
+
+ implements(interfaces.IConnector)
+
+ stopAfterDNS=0
+
+ def __init__(self, reactor, service, domain, factory,
+ protocol='tcp', connectFuncName='connectTCP',
+ connectFuncArgs=(),
+ connectFuncKwArgs={},
+ ):
+ self.reactor = reactor
+ self.service = service
+ self.domain = domain
+ self.factory = factory
+
+ self.protocol = protocol
+ self.connectFuncName = connectFuncName
+ self.connectFuncArgs = connectFuncArgs
+ self.connectFuncKwArgs = connectFuncKwArgs
+
+ self.connector = None
+ self.servers = None
+ self.orderedServers = None # list of servers already used in this round
+
+ def connect(self):
+ """Start connection to remote server."""
+ self.factory.doStart()
+ self.factory.startedConnecting(self)
+
+ if not self.servers:
+ if self.domain is None:
+ self.connectionFailed(error.DNSLookupError("Domain is not defined."))
+ return
+ d = client.lookupService('_%s._%s.%s' % (self.service,
+ self.protocol,
+ self.domain))
+ d.addCallbacks(self._cbGotServers, self._ebGotServers)
+ d.addCallback(lambda x, self=self: self._reallyConnect())
+ d.addErrback(self.connectionFailed)
+ elif self.connector is None:
+ self._reallyConnect()
+ else:
+ self.connector.connect()
+
+ def _ebGotServers(self, failure):
+ failure.trap(DNSNameError)
+
+ # Some DNS servers reply with NXDOMAIN when in fact there are
+ # just no SRV records for that domain. Act as if we just got an
+ # empty response and use fallback.
+
+ self.servers = []
+ self.orderedServers = []
+
+ def _cbGotServers(self, (answers, auth, add)):
+ if len(answers) == 1 and answers[0].type == dns.SRV \
+ and answers[0].payload \
+ and answers[0].payload.target == dns.Name('.'):
+ # decidedly not available
+ raise error.DNSLookupError("Service %s not available for domain %s."
+ % (repr(self.service), repr(self.domain)))
+
+ self.servers = []
+ self.orderedServers = []
+ for a in answers:
+ if a.type != dns.SRV or not a.payload:
+ continue
+
+ self.orderedServers.append((a.payload.priority, a.payload.weight,
+ str(a.payload.target), a.payload.port))
+
+ def _serverCmp(self, a, b):
+ if a[0]!=b[0]:
+ return cmp(a[0], b[0])
+ else:
+ return cmp(a[1], b[1])
+
+ def pickServer(self):
+ assert self.servers is not None
+ assert self.orderedServers is not None
+
+ if not self.servers and not self.orderedServers:
+ # no SRV record, fall back..
+ return self.domain, self.service
+
+ if not self.servers and self.orderedServers:
+ # start new round
+ self.servers = self.orderedServers
+ self.orderedServers = []
+
+ assert self.servers
+
+ self.servers.sort(self._serverCmp)
+ minPriority=self.servers[0][0]
+
+ weightIndex = zip(xrange(len(self.servers)), [x[1] for x in self.servers
+ if x[0]==minPriority])
+ weightSum = reduce(lambda x, y: (None, x[1]+y[1]), weightIndex, (None, 0))[1]
+ rand = random.randint(0, weightSum)
+
+ for index, weight in weightIndex:
+ weightSum -= weight
+ if weightSum <= 0:
+ chosen = self.servers[index]
+ del self.servers[index]
+ self.orderedServers.append(chosen)
+
+ p, w, host, port = chosen
+ return host, port
+
+ raise RuntimeError, 'Impossible %s pickServer result.' % self.__class__.__name__
+
+ def _reallyConnect(self):
+ if self.stopAfterDNS:
+ self.stopAfterDNS=0
+ return
+
+ self.host, self.port = self.pickServer()
+ assert self.host is not None, 'Must have a host to connect to.'
+ assert self.port is not None, 'Must have a port to connect to.'
+
+ connectFunc = getattr(self.reactor, self.connectFuncName)
+ self.connector=connectFunc(
+ self.host, self.port,
+ _SRVConnector_ClientFactoryWrapper(self, self.factory),
+ *self.connectFuncArgs, **self.connectFuncKwArgs)
+
+ def stopConnecting(self):
+ """Stop attempting to connect."""
+ if self.connector:
+ self.connector.stopConnecting()
+ else:
+ self.stopAfterDNS=1
+
+ def disconnect(self):
+ """Disconnect whatever our are state is."""
+ if self.connector is not None:
+ self.connector.disconnect()
+ else:
+ self.stopConnecting()
+
+ def getDestination(self):
+ assert self.connector
+ return self.connector.getDestination()
+
+ def connectionFailed(self, reason):
+ self.factory.clientConnectionFailed(self, reason)
+ self.factory.doStop()
+
+ def connectionLost(self, reason):
+ self.factory.clientConnectionLost(self, reason)
+ self.factory.doStop()
+
diff --git a/vendor/Twisted-10.0.0/twisted/names/tap.py b/vendor/Twisted-10.0.0/twisted/names/tap.py
new file mode 100644
index 0000000000..f22f6c6862
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/tap.py
@@ -0,0 +1,119 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Domain Name Server
+"""
+
+import os, traceback
+
+from twisted.python import usage
+from twisted.names import dns
+from twisted.application import internet, service
+
+from twisted.names import server
+from twisted.names import authority
+from twisted.names import secondary
+
+class Options(usage.Options):
+ optParameters = [
+ ["interface", "i", "", "The interface to which to bind"],
+ ["port", "p", "53", "The port on which to listen"],
+ ["resolv-conf", None, None,
+ "Override location of resolv.conf (implies --recursive)"],
+ ["hosts-file", None, None, "Perform lookups with a hosts file"],
+ ]
+
+ optFlags = [
+ ["cache", "c", "Enable record caching"],
+ ["recursive", "r", "Perform recursive lookups"],
+ ["verbose", "v", "Log verbosely"],
+ ]
+
+ zones = None
+ zonefiles = None
+
+ def __init__(self):
+ usage.Options.__init__(self)
+ self['verbose'] = 0
+ self.bindfiles = []
+ self.zonefiles = []
+ self.secondaries = []
+
+
+ def opt_pyzone(self, filename):
+ """Specify the filename of a Python syntax zone definition"""
+ if not os.path.exists(filename):
+ raise usage.UsageError(filename + ": No such file")
+ self.zonefiles.append(filename)
+
+ def opt_bindzone(self, filename):
+ """Specify the filename of a BIND9 syntax zone definition"""
+ if not os.path.exists(filename):
+ raise usage.UsageError(filename + ": No such file")
+ self.bindfiles.append(filename)
+
+
+ def opt_secondary(self, ip_domain):
+ """Act as secondary for the specified domain, performing
+ zone transfers from the specified IP (IP/domain)
+ """
+ args = ip_domain.split('/', 1)
+ if len(args) != 2:
+ raise usage.UsageError("Argument must be of the form IP/domain")
+ self.secondaries.append((args[0], [args[1]]))
+
+ def opt_verbose(self):
+ """Increment verbosity level"""
+ self['verbose'] += 1
+
+
+ def postOptions(self):
+ if self['resolv-conf']:
+ self['recursive'] = True
+
+ self.svcs = []
+ self.zones = []
+ for f in self.zonefiles:
+ try:
+ self.zones.append(authority.PySourceAuthority(f))
+ except Exception, e:
+ traceback.print_exc()
+ raise usage.UsageError("Invalid syntax in " + f)
+ for f in self.bindfiles:
+ try:
+ self.zones.append(authority.BindAuthority(f))
+ except Exception, e:
+ traceback.print_exc()
+ raise usage.UsageError("Invalid syntax in " + f)
+ for f in self.secondaries:
+ self.svcs.append(secondary.SecondaryAuthorityService(*f))
+ self.zones.append(self.svcs[-1].getAuthority())
+ try:
+ self['port'] = int(self['port'])
+ except ValueError:
+ raise usage.UsageError("Invalid port: %r" % (self['port'],))
+
+
+def makeService(config):
+ import client, cache, hosts
+
+ ca, cl = [], []
+ if config['cache']:
+ ca.append(cache.CacheResolver(verbose=config['verbose']))
+ if config['recursive']:
+ cl.append(client.createResolver(resolvconf=config['resolv-conf']))
+ if config['hosts-file']:
+ cl.append(hosts.Resolver(file=config['hosts-file']))
+
+ f = server.DNSServerFactory(config.zones, ca, cl, config['verbose'])
+ p = dns.DNSDatagramProtocol(f)
+ f.noisy = 0
+ ret = service.MultiService()
+ for (klass, arg) in [(internet.TCPServer, f), (internet.UDPServer, p)]:
+ s = klass(config['port'], arg, interface=config['interface'])
+ s.setServiceParent(ret)
+ for svc in config.svcs:
+ svc.setServiceParent(ret)
+ return ret
diff --git a/vendor/Twisted-10.0.0/twisted/names/test/__init__.py b/vendor/Twisted-10.0.0/twisted/names/test/__init__.py
new file mode 100644
index 0000000000..f6b7e3ae1f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/test/__init__.py
@@ -0,0 +1 @@
+"Tests for twisted.names"
diff --git a/vendor/Twisted-10.0.0/twisted/names/test/test_cache.py b/vendor/Twisted-10.0.0/twisted/names/test/test_cache.py
new file mode 100644
index 0000000000..00253e908d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/test/test_cache.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import time
+
+from twisted.trial import unittest
+
+from twisted.names import dns, cache
+
+class Caching(unittest.TestCase):
+ def testLookup(self):
+ c = cache.CacheResolver({
+ dns.Query(name='example.com', type=dns.MX, cls=dns.IN): (time.time(), ([], [], []))})
+ return c.lookupMailExchange('example.com').addCallback(self.assertEquals, ([], [], []))
diff --git a/vendor/Twisted-10.0.0/twisted/names/test/test_client.py b/vendor/Twisted-10.0.0/twisted/names/test/test_client.py
new file mode 100644
index 0000000000..c71febda09
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/test/test_client.py
@@ -0,0 +1,655 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for L{twisted.names.client}.
+"""
+
+from twisted.names import client, dns
+from twisted.names.error import DNSQueryTimeoutError
+from twisted.trial import unittest
+from twisted.names.common import ResolverBase
+from twisted.internet import defer, error
+from twisted.python import failure
+from twisted.python.deprecate import getWarningMethod, setWarningMethod
+from twisted.python.compat import set
+
+
+class FakeResolver(ResolverBase):
+
+ def _lookup(self, name, cls, qtype, timeout):
+ """
+ The getHostByNameTest does a different type of query that requires it
+ return an A record from an ALL_RECORDS lookup, so we accomodate that
+ here.
+ """
+ if name == 'getHostByNameTest':
+ rr = dns.RRHeader(name=name, type=dns.A, cls=cls, ttl=60,
+ payload=dns.Record_A(address='127.0.0.1', ttl=60))
+ else:
+ rr = dns.RRHeader(name=name, type=qtype, cls=cls, ttl=60)
+
+ results = [rr]
+ authority = []
+ addtional = []
+ return defer.succeed((results, authority, addtional))
+
+
+
+class StubPort(object):
+ """
+ A partial implementation of L{IListeningPort} which only keeps track of
+ whether it has been stopped.
+
+ @ivar disconnected: A C{bool} which is C{False} until C{stopListening} is
+ called, C{True} afterwards.
+ """
+ disconnected = False
+
+ def stopListening(self):
+ self.disconnected = True
+
+
+
+class StubDNSDatagramProtocol(object):
+ """
+ L{dns.DNSDatagramProtocol}-alike.
+
+ @ivar queries: A C{list} of tuples giving the arguments passed to
+ C{query} along with the L{defer.Deferred} which was returned from
+ the call.
+ """
+ def __init__(self):
+ self.queries = []
+ self.transport = StubPort()
+
+
+ def query(self, address, queries, timeout=10, id=None):
+ """
+ Record the given arguments and return a Deferred which will not be
+ called back by this code.
+ """
+ result = defer.Deferred()
+ self.queries.append((address, queries, timeout, id, result))
+ return result
+
+
+
+class ResolverTests(unittest.TestCase):
+ """
+ Tests for L{client.Resolver}.
+ """
+ def test_resolverProtocol(self):
+ """
+ Reading L{client.Resolver.protocol} causes a deprecation warning to be
+ emitted and evaluates to an instance of L{DNSDatagramProtocol}.
+ """
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ self.addCleanup(setWarningMethod, getWarningMethod())
+ warnings = []
+ setWarningMethod(
+ lambda message, category, stacklevel:
+ warnings.append((message, category, stacklevel)))
+ protocol = resolver.protocol
+ self.assertIsInstance(protocol, dns.DNSDatagramProtocol)
+ self.assertEqual(
+ warnings, [("Resolver.protocol is deprecated; use "
+ "Resolver.queryUDP instead.",
+ PendingDeprecationWarning, 0)])
+ self.assertIdentical(protocol, resolver.protocol)
+
+
+ def test_datagramQueryServerOrder(self):
+ """
+ L{client.Resolver.queryUDP} should issue queries to its
+ L{dns.DNSDatagramProtocol} with server addresses taken from its own
+ C{servers} and C{dynServers} lists, proceeding through them in order
+ as L{DNSQueryTimeoutError}s occur.
+ """
+ protocol = StubDNSDatagramProtocol()
+
+ servers = [object(), object()]
+ dynServers = [object(), object()]
+ resolver = client.Resolver(servers=servers)
+ resolver.dynServers = dynServers
+ resolver.protocol = protocol
+
+ expectedResult = object()
+ queryResult = resolver.queryUDP(None)
+ queryResult.addCallback(self.assertEqual, expectedResult)
+
+ self.assertEqual(len(protocol.queries), 1)
+ self.assertIdentical(protocol.queries[0][0], servers[0])
+ protocol.queries[0][-1].errback(DNSQueryTimeoutError(0))
+ self.assertEqual(len(protocol.queries), 2)
+ self.assertIdentical(protocol.queries[1][0], servers[1])
+ protocol.queries[1][-1].errback(DNSQueryTimeoutError(1))
+ self.assertEqual(len(protocol.queries), 3)
+ self.assertIdentical(protocol.queries[2][0], dynServers[0])
+ protocol.queries[2][-1].errback(DNSQueryTimeoutError(2))
+ self.assertEqual(len(protocol.queries), 4)
+ self.assertIdentical(protocol.queries[3][0], dynServers[1])
+ protocol.queries[3][-1].callback(expectedResult)
+
+ return queryResult
+
+
+ def test_singleConcurrentRequest(self):
+ """
+ L{client.Resolver.query} only issues one request at a time per query.
+ Subsequent requests made before responses to prior ones are received
+ are queued and given the same response as is given to the first one.
+ """
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ resolver.protocol = StubDNSDatagramProtocol()
+ queries = resolver.protocol.queries
+
+ query = dns.Query('foo.example.com', dns.A, dns.IN)
+ # The first query should be passed to the underlying protocol.
+ firstResult = resolver.query(query)
+ self.assertEqual(len(queries), 1)
+
+ # The same query again should not be passed to the underlying protocol.
+ secondResult = resolver.query(query)
+ self.assertEqual(len(queries), 1)
+
+ # The response to the first query should be sent in response to both
+ # queries.
+ answer = object()
+ response = dns.Message()
+ response.answers.append(answer)
+ queries.pop()[-1].callback(response)
+
+ d = defer.gatherResults([firstResult, secondResult])
+ def cbFinished((firstResponse, secondResponse)):
+ self.assertEqual(firstResponse, ([answer], [], []))
+ self.assertEqual(secondResponse, ([answer], [], []))
+ d.addCallback(cbFinished)
+ return d
+
+
+ def test_multipleConcurrentRequests(self):
+ """
+ L{client.Resolver.query} issues a request for each different concurrent
+ query.
+ """
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ resolver.protocol = StubDNSDatagramProtocol()
+ queries = resolver.protocol.queries
+
+ # The first query should be passed to the underlying protocol.
+ firstQuery = dns.Query('foo.example.com', dns.A)
+ resolver.query(firstQuery)
+ self.assertEqual(len(queries), 1)
+
+ # A query for a different name is also passed to the underlying
+ # protocol.
+ secondQuery = dns.Query('bar.example.com', dns.A)
+ resolver.query(secondQuery)
+ self.assertEqual(len(queries), 2)
+
+ # A query for a different type is also passed to the underlying
+ # protocol.
+ thirdQuery = dns.Query('foo.example.com', dns.A6)
+ resolver.query(thirdQuery)
+ self.assertEqual(len(queries), 3)
+
+
+ def test_multipleSequentialRequests(self):
+ """
+ After a response is received to a query issued with
+ L{client.Resolver.query}, another query with the same parameters
+ results in a new network request.
+ """
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ resolver.protocol = StubDNSDatagramProtocol()
+ queries = resolver.protocol.queries
+
+ query = dns.Query('foo.example.com', dns.A)
+
+ # The first query should be passed to the underlying protocol.
+ resolver.query(query)
+ self.assertEqual(len(queries), 1)
+
+ # Deliver the response.
+ queries.pop()[-1].callback(dns.Message())
+
+ # Repeating the first query should touch the protocol again.
+ resolver.query(query)
+ self.assertEqual(len(queries), 1)
+
+
+ def test_multipleConcurrentFailure(self):
+ """
+ If the result of a request is an error response, the Deferreds for all
+ concurrently issued requests associated with that result fire with the
+ L{Failure}.
+ """
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ resolver.protocol = StubDNSDatagramProtocol()
+ queries = resolver.protocol.queries
+
+ query = dns.Query('foo.example.com', dns.A)
+ firstResult = resolver.query(query)
+ secondResult = resolver.query(query)
+
+ class ExpectedException(Exception):
+ pass
+
+ queries.pop()[-1].errback(failure.Failure(ExpectedException()))
+
+ return defer.gatherResults([
+ self.assertFailure(firstResult, ExpectedException),
+ self.assertFailure(secondResult, ExpectedException)])
+
+
+ def test_connectedProtocol(self):
+ """
+ L{client.Resolver._connectedProtocol} returns a new
+ L{DNSDatagramProtocol} connected to a new address with a
+ cryptographically secure random port number.
+ """
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ firstProto = resolver._connectedProtocol()
+ secondProto = resolver._connectedProtocol()
+
+ self.assertNotIdentical(firstProto.transport, None)
+ self.assertNotIdentical(secondProto.transport, None)
+ self.assertNotEqual(
+ firstProto.transport.getHost().port,
+ secondProto.transport.getHost().port)
+
+ return defer.gatherResults([
+ defer.maybeDeferred(firstProto.transport.stopListening),
+ defer.maybeDeferred(secondProto.transport.stopListening)])
+
+
+ def test_differentProtocol(self):
+ """
+ L{client.Resolver._connectedProtocol} is called once each time a UDP
+ request needs to be issued and the resulting protocol instance is used
+ for that request.
+ """
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ protocols = []
+
+ class FakeProtocol(object):
+ def __init__(self):
+ self.transport = StubPort()
+
+ def query(self, address, query, timeout=10, id=None):
+ protocols.append(self)
+ return defer.succeed(dns.Message())
+
+ resolver._connectedProtocol = FakeProtocol
+ resolver.query(dns.Query('foo.example.com'))
+ resolver.query(dns.Query('bar.example.com'))
+ self.assertEqual(len(set(protocols)), 2)
+
+
+ def test_disallowedPort(self):
+ """
+ If a port number is initially selected which cannot be bound, the
+ L{CannotListenError} is handled and another port number is attempted.
+ """
+ ports = []
+
+ class FakeReactor(object):
+ def listenUDP(self, port, *args):
+ ports.append(port)
+ if len(ports) == 1:
+ raise error.CannotListenError(None, port, None)
+
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ resolver._reactor = FakeReactor()
+
+ proto = resolver._connectedProtocol()
+ self.assertEqual(len(set(ports)), 2)
+
+
+ def test_differentProtocolAfterTimeout(self):
+ """
+ When a query issued by L{client.Resolver.query} times out, the retry
+ uses a new protocol instance.
+ """
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ protocols = []
+ results = [defer.fail(failure.Failure(DNSQueryTimeoutError(None))),
+ defer.succeed(dns.Message())]
+
+ class FakeProtocol(object):
+ def __init__(self):
+ self.transport = StubPort()
+
+ def query(self, address, query, timeout=10, id=None):
+ protocols.append(self)
+ return results.pop(0)
+
+ resolver._connectedProtocol = FakeProtocol
+ resolver.query(dns.Query('foo.example.com'))
+ self.assertEqual(len(set(protocols)), 2)
+
+
+ def test_protocolShutDown(self):
+ """
+ After the L{Deferred} returned by L{DNSDatagramProtocol.query} is
+ called back, the L{DNSDatagramProtocol} is disconnected from its
+ transport.
+ """
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ protocols = []
+ result = defer.Deferred()
+
+ class FakeProtocol(object):
+ def __init__(self):
+ self.transport = StubPort()
+
+ def query(self, address, query, timeout=10, id=None):
+ protocols.append(self)
+ return result
+
+ resolver._connectedProtocol = FakeProtocol
+ resolver.query(dns.Query('foo.example.com'))
+
+ self.assertFalse(protocols[0].transport.disconnected)
+ result.callback(dns.Message())
+ self.assertTrue(protocols[0].transport.disconnected)
+
+
+ def test_protocolShutDownAfterTimeout(self):
+ """
+ The L{DNSDatagramProtocol} created when an interim timeout occurs is
+ also disconnected from its transport after the Deferred returned by its
+ query method completes.
+ """
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ protocols = []
+ result = defer.Deferred()
+ results = [defer.fail(failure.Failure(DNSQueryTimeoutError(None))),
+ result]
+
+ class FakeProtocol(object):
+ def __init__(self):
+ self.transport = StubPort()
+
+ def query(self, address, query, timeout=10, id=None):
+ protocols.append(self)
+ return results.pop(0)
+
+ resolver._connectedProtocol = FakeProtocol
+ resolver.query(dns.Query('foo.example.com'))
+
+ self.assertFalse(protocols[1].transport.disconnected)
+ result.callback(dns.Message())
+ self.assertTrue(protocols[1].transport.disconnected)
+
+
+ def test_protocolShutDownAfterFailure(self):
+ """
+ If the L{Deferred} returned by L{DNSDatagramProtocol.query} fires with
+ a failure, the L{DNSDatagramProtocol} is still disconnected from its
+ transport.
+ """
+ class ExpectedException(Exception):
+ pass
+
+ resolver = client.Resolver(servers=[('example.com', 53)])
+ protocols = []
+ result = defer.Deferred()
+
+ class FakeProtocol(object):
+ def __init__(self):
+ self.transport = StubPort()
+
+ def query(self, address, query, timeout=10, id=None):
+ protocols.append(self)
+ return result
+
+ resolver._connectedProtocol = FakeProtocol
+ queryResult = resolver.query(dns.Query('foo.example.com'))
+
+ self.assertFalse(protocols[0].transport.disconnected)
+ result.errback(failure.Failure(ExpectedException()))
+ self.assertTrue(protocols[0].transport.disconnected)
+
+ return self.assertFailure(queryResult, ExpectedException)
+
+
+
+class ClientTestCase(unittest.TestCase):
+
+ def setUp(self):
+ """
+ Replace the resolver with a FakeResolver
+ """
+ client.theResolver = FakeResolver()
+ self.hostname = 'example.com'
+ self.ghbntest = 'getHostByNameTest'
+
+ def tearDown(self):
+ """
+ By setting the resolver to None, it will be recreated next time a name
+ lookup is done.
+ """
+ client.theResolver = None
+
+ def checkResult(self, (results, authority, additional), qtype):
+ """
+ Verify that the result is the same query type as what is expected.
+ """
+ result = results[0]
+ self.assertEquals(str(result.name), self.hostname)
+ self.assertEquals(result.type, qtype)
+
+ def checkGetHostByName(self, result):
+ """
+ Test that the getHostByName query returns the 127.0.0.1 address.
+ """
+ self.assertEquals(result, '127.0.0.1')
+
+ def test_getHostByName(self):
+ """
+ do a getHostByName of a value that should return 127.0.0.1.
+ """
+ d = client.getHostByName(self.ghbntest)
+ d.addCallback(self.checkGetHostByName)
+ return d
+
+ def test_lookupAddress(self):
+ """
+ Do a lookup and test that the resolver will issue the correct type of
+ query type. We do this by checking that FakeResolver returns a result
+ record with the same query type as what we issued.
+ """
+ d = client.lookupAddress(self.hostname)
+ d.addCallback(self.checkResult, dns.A)
+ return d
+
+ def test_lookupIPV6Address(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupIPV6Address(self.hostname)
+ d.addCallback(self.checkResult, dns.AAAA)
+ return d
+
+ def test_lookupAddress6(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupAddress6(self.hostname)
+ d.addCallback(self.checkResult, dns.A6)
+ return d
+
+ def test_lookupNameservers(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupNameservers(self.hostname)
+ d.addCallback(self.checkResult, dns.NS)
+ return d
+
+ def test_lookupCanonicalName(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupCanonicalName(self.hostname)
+ d.addCallback(self.checkResult, dns.CNAME)
+ return d
+
+ def test_lookupAuthority(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupAuthority(self.hostname)
+ d.addCallback(self.checkResult, dns.SOA)
+ return d
+
+ def test_lookupMailBox(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupMailBox(self.hostname)
+ d.addCallback(self.checkResult, dns.MB)
+ return d
+
+ def test_lookupMailGroup(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupMailGroup(self.hostname)
+ d.addCallback(self.checkResult, dns.MG)
+ return d
+
+ def test_lookupMailRename(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupMailRename(self.hostname)
+ d.addCallback(self.checkResult, dns.MR)
+ return d
+
+ def test_lookupNull(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupNull(self.hostname)
+ d.addCallback(self.checkResult, dns.NULL)
+ return d
+
+ def test_lookupWellKnownServices(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupWellKnownServices(self.hostname)
+ d.addCallback(self.checkResult, dns.WKS)
+ return d
+
+ def test_lookupPointer(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupPointer(self.hostname)
+ d.addCallback(self.checkResult, dns.PTR)
+ return d
+
+ def test_lookupHostInfo(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupHostInfo(self.hostname)
+ d.addCallback(self.checkResult, dns.HINFO)
+ return d
+
+ def test_lookupMailboxInfo(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupMailboxInfo(self.hostname)
+ d.addCallback(self.checkResult, dns.MINFO)
+ return d
+
+ def test_lookupMailExchange(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupMailExchange(self.hostname)
+ d.addCallback(self.checkResult, dns.MX)
+ return d
+
+ def test_lookupText(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupText(self.hostname)
+ d.addCallback(self.checkResult, dns.TXT)
+ return d
+
+ def test_lookupResponsibility(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupResponsibility(self.hostname)
+ d.addCallback(self.checkResult, dns.RP)
+ return d
+
+ def test_lookupAFSDatabase(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupAFSDatabase(self.hostname)
+ d.addCallback(self.checkResult, dns.AFSDB)
+ return d
+
+ def test_lookupService(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupService(self.hostname)
+ d.addCallback(self.checkResult, dns.SRV)
+ return d
+
+ def test_lookupZone(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupZone(self.hostname)
+ d.addCallback(self.checkResult, dns.AXFR)
+ return d
+
+ def test_lookupAllRecords(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupAllRecords(self.hostname)
+ d.addCallback(self.checkResult, dns.ALL_RECORDS)
+ return d
+
+
+ def test_lookupNamingAuthorityPointer(self):
+ """
+ See L{test_lookupAddress}
+ """
+ d = client.lookupNamingAuthorityPointer(self.hostname)
+ d.addCallback(self.checkResult, dns.NAPTR)
+ return d
+
+
+class ThreadedResolverTests(unittest.TestCase):
+ """
+ Tests for L{client.ThreadedResolver}.
+ """
+ def test_deprecated(self):
+ """
+ L{client.ThreadedResolver} is deprecated. Instantiating it emits a
+ deprecation warning pointing at the code that does the instantiation.
+ """
+ client.ThreadedResolver()
+ warnings = self.flushWarnings(offendingFunctions=[self.test_deprecated])
+ self.assertEquals(
+ warnings[0]['message'],
+ "twisted.names.client.ThreadedResolver is deprecated since "
+ "Twisted 9.0, use twisted.internet.base.ThreadedResolver "
+ "instead.")
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(len(warnings), 1)
diff --git a/vendor/Twisted-10.0.0/twisted/names/test/test_common.py b/vendor/Twisted-10.0.0/twisted/names/test/test_common.py
new file mode 100644
index 0000000000..4907b469f9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/test/test_common.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.names.common}.
+"""
+
+from twisted.trial.unittest import TestCase
+from twisted.names.common import ResolverBase
+from twisted.names.dns import EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED
+from twisted.names.error import DNSFormatError, DNSServerError, DNSNameError
+from twisted.names.error import DNSNotImplementedError, DNSQueryRefusedError
+from twisted.names.error import DNSUnknownError
+
+
+class ExceptionForCodeTests(TestCase):
+ """
+ Tests for L{ResolverBase.exceptionForCode}.
+ """
+ def setUp(self):
+ self.exceptionForCode = ResolverBase().exceptionForCode
+
+
+ def test_eformat(self):
+ """
+ L{ResolverBase.exceptionForCode} converts L{EFORMAT} to
+ L{DNSFormatError}.
+ """
+ self.assertIdentical(self.exceptionForCode(EFORMAT), DNSFormatError)
+
+
+ def test_eserver(self):
+ """
+ L{ResolverBase.exceptionForCode} converts L{ESERVER} to
+ L{DNSServerError}.
+ """
+ self.assertIdentical(self.exceptionForCode(ESERVER), DNSServerError)
+
+
+ def test_ename(self):
+ """
+ L{ResolverBase.exceptionForCode} converts L{ENAME} to L{DNSNameError}.
+ """
+ self.assertIdentical(self.exceptionForCode(ENAME), DNSNameError)
+
+
+ def test_enotimp(self):
+ """
+ L{ResolverBase.exceptionForCode} converts L{ENOTIMP} to
+ L{DNSNotImplementedError}.
+ """
+ self.assertIdentical(
+ self.exceptionForCode(ENOTIMP), DNSNotImplementedError)
+
+
+ def test_erefused(self):
+ """
+ L{ResolverBase.exceptionForCode} converts L{EREFUSED} to
+ L{DNSQueryRefusedError}.
+ """
+ self.assertIdentical(
+ self.exceptionForCode(EREFUSED), DNSQueryRefusedError)
+
+
+ def test_other(self):
+ """
+ L{ResolverBase.exceptionForCode} converts any other response code to
+ L{DNSUnknownError}.
+ """
+ self.assertIdentical(
+ self.exceptionForCode(object()), DNSUnknownError)
diff --git a/vendor/Twisted-10.0.0/twisted/names/test/test_dns.py b/vendor/Twisted-10.0.0/twisted/names/test/test_dns.py
new file mode 100644
index 0000000000..f2f8aac14c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/test/test_dns.py
@@ -0,0 +1,1200 @@
+# test-case-name: twisted.names.test.test_dns
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for twisted.names.dns.
+"""
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+import struct
+
+from twisted.python.failure import Failure
+from twisted.internet import address, task
+from twisted.internet.error import CannotListenError, ConnectionDone
+from twisted.trial import unittest
+from twisted.names import dns
+
+from twisted.test import proto_helpers
+
+
+
+class RoundtripDNSTestCase(unittest.TestCase):
+ """Encoding and then decoding various objects."""
+
+ names = ["example.org", "go-away.fish.tv", "23strikesback.net"]
+
+ def testName(self):
+ for n in self.names:
+ # encode the name
+ f = StringIO()
+ dns.Name(n).encode(f)
+
+ # decode the name
+ f.seek(0, 0)
+ result = dns.Name()
+ result.decode(f)
+ self.assertEquals(result.name, n)
+
+ def testQuery(self):
+ for n in self.names:
+ for dnstype in range(1, 17):
+ for dnscls in range(1, 5):
+ # encode the query
+ f = StringIO()
+ dns.Query(n, dnstype, dnscls).encode(f)
+
+ # decode the result
+ f.seek(0, 0)
+ result = dns.Query()
+ result.decode(f)
+ self.assertEquals(result.name.name, n)
+ self.assertEquals(result.type, dnstype)
+ self.assertEquals(result.cls, dnscls)
+
+ def testRR(self):
+ # encode the RR
+ f = StringIO()
+ dns.RRHeader("test.org", 3, 4, 17).encode(f)
+
+ # decode the result
+ f.seek(0, 0)
+ result = dns.RRHeader()
+ result.decode(f)
+ self.assertEquals(str(result.name), "test.org")
+ self.assertEquals(result.type, 3)
+ self.assertEquals(result.cls, 4)
+ self.assertEquals(result.ttl, 17)
+
+
+ def testResources(self):
+ names = (
+ "this.are.test.name",
+ "will.compress.will.this.will.name.will.hopefully",
+ "test.CASE.preSErVatIOn.YeAH",
+ "a.s.h.o.r.t.c.a.s.e.t.o.t.e.s.t",
+ "singleton"
+ )
+ for s in names:
+ f = StringIO()
+ dns.SimpleRecord(s).encode(f)
+ f.seek(0, 0)
+ result = dns.SimpleRecord()
+ result.decode(f)
+ self.assertEquals(str(result.name), s)
+
+ def test_hashable(self):
+ """
+ Instances of all record types are hashable.
+ """
+ records = [
+ dns.Record_NS, dns.Record_MD, dns.Record_MF, dns.Record_CNAME,
+ dns.Record_MB, dns.Record_MG, dns.Record_MR, dns.Record_PTR,
+ dns.Record_DNAME, dns.Record_A, dns.Record_SOA, dns.Record_NULL,
+ dns.Record_WKS, dns.Record_SRV, dns.Record_AFSDB, dns.Record_RP,
+ dns.Record_HINFO, dns.Record_MINFO, dns.Record_MX, dns.Record_TXT,
+ dns.Record_AAAA, dns.Record_A6, dns.Record_NAPTR
+ ]
+
+ for k in records:
+ k1, k2 = k(), k()
+ hk1 = hash(k1)
+ hk2 = hash(k2)
+ self.assertEquals(hk1, hk2, "%s != %s (for %s)" % (hk1,hk2,k))
+
+
+ def test_Charstr(self):
+ """
+ Test L{dns.Charstr} encode and decode.
+ """
+ for n in self.names:
+ # encode the name
+ f = StringIO()
+ dns.Charstr(n).encode(f)
+
+ # decode the name
+ f.seek(0, 0)
+ result = dns.Charstr()
+ result.decode(f)
+ self.assertEquals(result.string, n)
+
+
+ def test_NAPTR(self):
+ """
+ Test L{dns.Record_NAPTR} encode and decode.
+ """
+ naptrs = [(100, 10, "u", "sip+E2U",
+ "!^.*$!sip:information@domain.tld!", ""),
+ (100, 50, "s", "http+I2L+I2C+I2R", "",
+ "_http._tcp.gatech.edu")]
+
+ for (order, preference, flags, service, regexp, replacement) in naptrs:
+ rin = dns.Record_NAPTR(order, preference, flags, service, regexp,
+ replacement)
+ e = StringIO()
+ rin.encode(e)
+ e.seek(0,0)
+ rout = dns.Record_NAPTR()
+ rout.decode(e)
+ self.assertEquals(rin.order, rout.order)
+ self.assertEquals(rin.preference, rout.preference)
+ self.assertEquals(rin.flags, rout.flags)
+ self.assertEquals(rin.service, rout.service)
+ self.assertEquals(rin.regexp, rout.regexp)
+ self.assertEquals(rin.replacement.name, rout.replacement.name)
+ self.assertEquals(rin.ttl, rout.ttl)
+
+
+
+class MessageTestCase(unittest.TestCase):
+ def testEmptyMessage(self):
+ """
+ Test that a message which has been truncated causes an EOFError to
+ be raised when it is parsed.
+ """
+ msg = dns.Message()
+ self.assertRaises(EOFError, msg.fromStr, '')
+
+
+ def testEmptyQuery(self):
+ """
+ Test that bytes representing an empty query message can be decoded
+ as such.
+ """
+ msg = dns.Message()
+ msg.fromStr(
+ '\x01\x00' # Message ID
+ '\x00' # answer bit, opCode nibble, auth bit, trunc bit, recursive bit
+ '\x00' # recursion bit, empty bit, empty bit, empty bit, response code nibble
+ '\x00\x00' # number of queries
+ '\x00\x00' # number of answers
+ '\x00\x00' # number of authorities
+ '\x00\x00' # number of additionals
+ )
+ self.assertEquals(msg.id, 256)
+ self.failIf(msg.answer, "Message was not supposed to be an answer.")
+ self.assertEquals(msg.opCode, dns.OP_QUERY)
+ self.failIf(msg.auth, "Message was not supposed to be authoritative.")
+ self.failIf(msg.trunc, "Message was not supposed to be truncated.")
+ self.assertEquals(msg.queries, [])
+ self.assertEquals(msg.answers, [])
+ self.assertEquals(msg.authority, [])
+ self.assertEquals(msg.additional, [])
+
+
+ def testNULL(self):
+ bytes = ''.join([chr(i) for i in range(256)])
+ rec = dns.Record_NULL(bytes)
+ rr = dns.RRHeader('testname', dns.NULL, payload=rec)
+ msg1 = dns.Message()
+ msg1.answers.append(rr)
+ s = StringIO()
+ msg1.encode(s)
+ s.seek(0, 0)
+ msg2 = dns.Message()
+ msg2.decode(s)
+
+ self.failUnless(isinstance(msg2.answers[0].payload, dns.Record_NULL))
+ self.assertEquals(msg2.answers[0].payload.payload, bytes)
+
+
+
+class TestController(object):
+ """
+ Pretend to be a DNS query processor for a DNSDatagramProtocol.
+
+ @ivar messages: the list of received messages.
+ @type messages: C{list} of (msg, protocol, address)
+ """
+
+ def __init__(self):
+ """
+ Initialize the controller: create a list of messages.
+ """
+ self.messages = []
+
+
+ def messageReceived(self, msg, proto, addr):
+ """
+ Save the message so that it can be checked during the tests.
+ """
+ self.messages.append((msg, proto, addr))
+
+
+
+class DatagramProtocolTestCase(unittest.TestCase):
+ """
+ Test various aspects of L{dns.DNSDatagramProtocol}.
+ """
+
+ def setUp(self):
+ """
+ Create a L{dns.DNSDatagramProtocol} with a deterministic clock.
+ """
+ self.clock = task.Clock()
+ self.controller = TestController()
+ self.proto = dns.DNSDatagramProtocol(self.controller)
+ transport = proto_helpers.FakeDatagramTransport()
+ self.proto.makeConnection(transport)
+ self.proto.callLater = self.clock.callLater
+
+
+ def test_truncatedPacket(self):
+ """
+ Test that when a short datagram is received, datagramReceived does
+ not raise an exception while processing it.
+ """
+ self.proto.datagramReceived('',
+ address.IPv4Address('UDP', '127.0.0.1', 12345))
+ self.assertEquals(self.controller.messages, [])
+
+
+ def test_simpleQuery(self):
+ """
+ Test content received after a query.
+ """
+ d = self.proto.query(('127.0.0.1', 21345), [dns.Query('foo')])
+ self.assertEquals(len(self.proto.liveMessages.keys()), 1)
+ m = dns.Message()
+ m.id = self.proto.liveMessages.items()[0][0]
+ m.answers = [dns.RRHeader(payload=dns.Record_A(address='1.2.3.4'))]
+ called = False
+ def cb(result):
+ self.assertEquals(result.answers[0].payload.dottedQuad(), '1.2.3.4')
+ d.addCallback(cb)
+ self.proto.datagramReceived(m.toStr(), ('127.0.0.1', 21345))
+ return d
+
+
+ def test_queryTimeout(self):
+ """
+ Test that query timeouts after some seconds.
+ """
+ d = self.proto.query(('127.0.0.1', 21345), [dns.Query('foo')])
+ self.assertEquals(len(self.proto.liveMessages), 1)
+ self.clock.advance(10)
+ self.assertFailure(d, dns.DNSQueryTimeoutError)
+ self.assertEquals(len(self.proto.liveMessages), 0)
+ return d
+
+
+ def test_writeError(self):
+ """
+ Exceptions raised by the transport's write method should be turned into
+ C{Failure}s passed to errbacks of the C{Deferred} returned by
+ L{DNSDatagramProtocol.query}.
+ """
+ def writeError(message, addr):
+ raise RuntimeError("bar")
+ self.proto.transport.write = writeError
+
+ d = self.proto.query(('127.0.0.1', 21345), [dns.Query('foo')])
+ return self.assertFailure(d, RuntimeError)
+
+
+ def test_listenError(self):
+ """
+ Exception L{CannotListenError} raised by C{listenUDP} should be turned
+ into a C{Failure} passed to errback of the C{Deferred} returned by
+ L{DNSDatagramProtocol.query}.
+ """
+ def startListeningError():
+ raise CannotListenError(None, None, None)
+ self.proto.startListening = startListeningError
+ # Clean up transport so that the protocol calls startListening again
+ self.proto.transport = None
+
+ d = self.proto.query(('127.0.0.1', 21345), [dns.Query('foo')])
+ return self.assertFailure(d, CannotListenError)
+
+
+
+class TestTCPController(TestController):
+ """
+ Pretend to be a DNS query processor for a DNSProtocol.
+
+ @ivar connections: A list of L{DNSProtocol} instances which have
+ notified this controller that they are connected and have not
+ yet notified it that their connection has been lost.
+ """
+ def __init__(self):
+ TestController.__init__(self)
+ self.connections = []
+
+
+ def connectionMade(self, proto):
+ self.connections.append(proto)
+
+
+ def connectionLost(self, proto):
+ self.connections.remove(proto)
+
+
+
+class DNSProtocolTestCase(unittest.TestCase):
+ """
+ Test various aspects of L{dns.DNSProtocol}.
+ """
+
+ def setUp(self):
+ """
+ Create a L{dns.DNSProtocol} with a deterministic clock.
+ """
+ self.clock = task.Clock()
+ self.controller = TestTCPController()
+ self.proto = dns.DNSProtocol(self.controller)
+ self.proto.makeConnection(proto_helpers.StringTransport())
+ self.proto.callLater = self.clock.callLater
+
+
+ def test_connectionTracking(self):
+ """
+ L{dns.DNSProtocol} calls its controller's C{connectionMade}
+ method with itself when it is connected to a transport and its
+ controller's C{connectionLost} method when it is disconnected.
+ """
+ self.assertEqual(self.controller.connections, [self.proto])
+ self.proto.connectionLost(
+ Failure(ConnectionDone("Fake Connection Done")))
+ self.assertEqual(self.controller.connections, [])
+
+
+ def test_queryTimeout(self):
+ """
+ Test that query timeouts after some seconds.
+ """
+ d = self.proto.query([dns.Query('foo')])
+ self.assertEquals(len(self.proto.liveMessages), 1)
+ self.clock.advance(60)
+ self.assertFailure(d, dns.DNSQueryTimeoutError)
+ self.assertEquals(len(self.proto.liveMessages), 0)
+ return d
+
+
+ def test_simpleQuery(self):
+ """
+ Test content received after a query.
+ """
+ d = self.proto.query([dns.Query('foo')])
+ self.assertEquals(len(self.proto.liveMessages.keys()), 1)
+ m = dns.Message()
+ m.id = self.proto.liveMessages.items()[0][0]
+ m.answers = [dns.RRHeader(payload=dns.Record_A(address='1.2.3.4'))]
+ called = False
+ def cb(result):
+ self.assertEquals(result.answers[0].payload.dottedQuad(), '1.2.3.4')
+ d.addCallback(cb)
+ s = m.toStr()
+ s = struct.pack('!H', len(s)) + s
+ self.proto.dataReceived(s)
+ return d
+
+
+ def test_writeError(self):
+ """
+ Exceptions raised by the transport's write method should be turned into
+ C{Failure}s passed to errbacks of the C{Deferred} returned by
+ L{DNSProtocol.query}.
+ """
+ def writeError(message):
+ raise RuntimeError("bar")
+ self.proto.transport.write = writeError
+
+ d = self.proto.query([dns.Query('foo')])
+ return self.assertFailure(d, RuntimeError)
+
+
+
+class ReprTests(unittest.TestCase):
+ """
+ Tests for the C{__repr__} implementation of record classes.
+ """
+ def test_ns(self):
+ """
+ The repr of a L{dns.Record_NS} instance includes the name of the
+ nameserver and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_NS('example.com', 4321)),
+ "<NS name=example.com ttl=4321>")
+
+
+ def test_md(self):
+ """
+ The repr of a L{dns.Record_MD} instance includes the name of the
+ mail destination and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_MD('example.com', 4321)),
+ "<MD name=example.com ttl=4321>")
+
+
+ def test_mf(self):
+ """
+ The repr of a L{dns.Record_MF} instance includes the name of the
+ mail forwarder and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_MF('example.com', 4321)),
+ "<MF name=example.com ttl=4321>")
+
+
+ def test_cname(self):
+ """
+ The repr of a L{dns.Record_CNAME} instance includes the name of the
+ mail forwarder and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_CNAME('example.com', 4321)),
+ "<CNAME name=example.com ttl=4321>")
+
+
+ def test_mb(self):
+ """
+ The repr of a L{dns.Record_MB} instance includes the name of the
+ mailbox and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_MB('example.com', 4321)),
+ "<MB name=example.com ttl=4321>")
+
+
+ def test_mg(self):
+ """
+ The repr of a L{dns.Record_MG} instance includes the name of the
+ mail group memeber and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_MG('example.com', 4321)),
+ "<MG name=example.com ttl=4321>")
+
+
+ def test_mr(self):
+ """
+ The repr of a L{dns.Record_MR} instance includes the name of the
+ mail rename domain and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_MR('example.com', 4321)),
+ "<MR name=example.com ttl=4321>")
+
+
+ def test_ptr(self):
+ """
+ The repr of a L{dns.Record_PTR} instance includes the name of the
+ pointer and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_PTR('example.com', 4321)),
+ "<PTR name=example.com ttl=4321>")
+
+
+ def test_dname(self):
+ """
+ The repr of a L{dns.Record_DNAME} instance includes the name of the
+ non-terminal DNS name redirection and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_DNAME('example.com', 4321)),
+ "<DNAME name=example.com ttl=4321>")
+
+
+ def test_a(self):
+ """
+ The repr of a L{dns.Record_A} instance includes the dotted-quad
+ string representation of the address it is for and the TTL of the
+ record.
+ """
+ self.assertEqual(
+ repr(dns.Record_A('1.2.3.4', 567)),
+ '<A address=1.2.3.4 ttl=567>')
+
+
+ def test_soa(self):
+ """
+ The repr of a L{dns.Record_SOA} instance includes all of the
+ authority fields.
+ """
+ self.assertEqual(
+ repr(dns.Record_SOA(mname='mName', rname='rName', serial=123,
+ refresh=456, retry=789, expire=10,
+ minimum=11, ttl=12)),
+ "<SOA mname=mName rname=rName serial=123 refresh=456 "
+ "retry=789 expire=10 minimum=11 ttl=12>")
+
+
+ def test_null(self):
+ """
+ The repr of a L{dns.Record_NULL} instance includes the repr of its
+ payload and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_NULL('abcd', 123)),
+ "<NULL payload='abcd' ttl=123>")
+
+
+ def test_wks(self):
+ """
+ The repr of a L{dns.Record_WKS} instance includes the dotted-quad
+ string representation of the address it is for, the IP protocol
+ number it is for, and the TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_WKS('2.3.4.5', 7, ttl=8)),
+ "<WKS address=2.3.4.5 protocol=7 ttl=8>")
+
+
+ def test_aaaa(self):
+ """
+ The repr of a L{dns.Record_AAAA} instance includes the colon-separated
+ hex string representation of the address it is for and the TTL of the
+ record.
+ """
+ self.assertEqual(
+ repr(dns.Record_AAAA('8765::1234', ttl=10)),
+ "<AAAA address=8765::1234 ttl=10>")
+
+
+ def test_a6(self):
+ """
+ The repr of a L{dns.Record_A6} instance includes the colon-separated
+ hex string representation of the address it is for and the TTL of the
+ record.
+ """
+ self.assertEqual(
+ repr(dns.Record_A6(0, '1234::5678', 'foo.bar', ttl=10)),
+ "<A6 suffix=1234::5678 prefix=foo.bar ttl=10>")
+
+
+ def test_srv(self):
+ """
+ The repr of a L{dns.Record_SRV} instance includes the name and port of
+ the target and the priority, weight, and TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_SRV(1, 2, 3, 'example.org', 4)),
+ "<SRV priority=1 weight=2 target=example.org port=3 ttl=4>")
+
+
+ def test_naptr(self):
+ """
+ The repr of a L{dns.Record_NAPTR} instance includes the order,
+ preference, flags, service, regular expression, replacement, and TTL of
+ the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_NAPTR(5, 9, "S", "http", "/foo/bar/i", "baz", 3)),
+ "<NAPTR order=5 preference=9 flags=S service=http "
+ "regexp=/foo/bar/i replacement=baz ttl=3>")
+
+
+ def test_afsdb(self):
+ """
+ The repr of a L{dns.Record_AFSDB} instance includes the subtype,
+ hostname, and TTL of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_AFSDB(3, 'example.org', 5)),
+ "<AFSDB subtype=3 hostname=example.org ttl=5>")
+
+
+ def test_rp(self):
+ """
+ The repr of a L{dns.Record_RP} instance includes the mbox, txt, and TTL
+ fields of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_RP('alice.example.com', 'admin.example.com', 3)),
+ "<RP mbox=alice.example.com txt=admin.example.com ttl=3>")
+
+
+ def test_hinfo(self):
+ """
+ The repr of a L{dns.Record_HINFO} instance includes the cpu, os, and
+ TTL fields of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_HINFO('sparc', 'minix', 12)),
+ "<HINFO cpu='sparc' os='minix' ttl=12>")
+
+
+ def test_minfo(self):
+ """
+ The repr of a L{dns.Record_MINFO} instance includes the rmailbx,
+ emailbx, and TTL fields of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_MINFO('alice.example.com', 'bob.example.com', 15)),
+ "<MINFO responsibility=alice.example.com "
+ "errors=bob.example.com ttl=15>")
+
+
+ def test_mx(self):
+ """
+ The repr of a L{dns.Record_MX} instance includes the preference, name,
+ and TTL fields of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_MX(13, 'mx.example.com', 2)),
+ "<MX preference=13 name=mx.example.com ttl=2>")
+
+
+ def test_txt(self):
+ """
+ The repr of a L{dns.Record_TXT} instance includes the data and ttl
+ fields of the record.
+ """
+ self.assertEqual(
+ repr(dns.Record_TXT("foo", "bar", ttl=15)),
+ "<TXT data=['foo', 'bar'] ttl=15>")
+
+
+
+class _Equal(object):
+ """
+ A class the instances of which are equal to anything and everything.
+ """
+ def __eq__(self, other):
+ return True
+
+
+ def __ne__(self, other):
+ return False
+
+
+
+class _NotEqual(object):
+ """
+ A class the instances of which are equal to nothing.
+ """
+ def __eq__(self, other):
+ return False
+
+
+ def __ne__(self, other):
+ return True
+
+
+
+class EqualityTests(unittest.TestCase):
+ """
+ Tests for the equality and non-equality behavior of record classes.
+ """
+ def _equalityTest(self, firstValueOne, secondValueOne, valueTwo):
+ """
+ Assert that C{firstValueOne} is equal to C{secondValueOne} but not
+ equal to C{valueOne} and that it defines equality cooperatively with
+ other types it doesn't know about.
+ """
+ # This doesn't use assertEqual and assertNotEqual because the exact
+ # operator those functions use is not very well defined. The point
+ # of these assertions is to check the results of the use of specific
+ # operators (precisely to ensure that using different permutations
+ # (eg "x == y" or "not (x != y)") which should yield the same results
+ # actually does yield the same result). -exarkun
+ self.assertTrue(firstValueOne == firstValueOne)
+ self.assertTrue(firstValueOne == secondValueOne)
+ self.assertFalse(firstValueOne == valueTwo)
+ self.assertFalse(firstValueOne != firstValueOne)
+ self.assertFalse(firstValueOne != secondValueOne)
+ self.assertTrue(firstValueOne != valueTwo)
+ self.assertTrue(firstValueOne == _Equal())
+ self.assertFalse(firstValueOne != _Equal())
+ self.assertFalse(firstValueOne == _NotEqual())
+ self.assertTrue(firstValueOne != _NotEqual())
+
+
+ def _simpleEqualityTest(self, cls):
+ # Vary the TTL
+ self._equalityTest(
+ cls('example.com', 123),
+ cls('example.com', 123),
+ cls('example.com', 321))
+ # Vary the name
+ self._equalityTest(
+ cls('example.com', 123),
+ cls('example.com', 123),
+ cls('example.org', 123))
+
+
+ def test_rrheader(self):
+ """
+ Two L{dns.RRHeader} instances compare equal if and only if they have
+ the same name, type, class, time to live, payload, and authoritative
+ bit.
+ """
+ # Vary the name
+ self._equalityTest(
+ dns.RRHeader('example.com', payload=dns.Record_A('1.2.3.4')),
+ dns.RRHeader('example.com', payload=dns.Record_A('1.2.3.4')),
+ dns.RRHeader('example.org', payload=dns.Record_A('1.2.3.4')))
+
+ # Vary the payload
+ self._equalityTest(
+ dns.RRHeader('example.com', payload=dns.Record_A('1.2.3.4')),
+ dns.RRHeader('example.com', payload=dns.Record_A('1.2.3.4')),
+ dns.RRHeader('example.com', payload=dns.Record_A('1.2.3.5')))
+
+ # Vary the type. Leave the payload as None so that we don't have to
+ # provide non-equal values.
+ self._equalityTest(
+ dns.RRHeader('example.com', dns.A),
+ dns.RRHeader('example.com', dns.A),
+ dns.RRHeader('example.com', dns.MX))
+
+ # Probably not likely to come up. Most people use the internet.
+ self._equalityTest(
+ dns.RRHeader('example.com', cls=dns.IN, payload=dns.Record_A('1.2.3.4')),
+ dns.RRHeader('example.com', cls=dns.IN, payload=dns.Record_A('1.2.3.4')),
+ dns.RRHeader('example.com', cls=dns.CS, payload=dns.Record_A('1.2.3.4')))
+
+ # Vary the ttl
+ self._equalityTest(
+ dns.RRHeader('example.com', ttl=60, payload=dns.Record_A('1.2.3.4')),
+ dns.RRHeader('example.com', ttl=60, payload=dns.Record_A('1.2.3.4')),
+ dns.RRHeader('example.com', ttl=120, payload=dns.Record_A('1.2.3.4')))
+
+ # Vary the auth bit
+ self._equalityTest(
+ dns.RRHeader('example.com', auth=1, payload=dns.Record_A('1.2.3.4')),
+ dns.RRHeader('example.com', auth=1, payload=dns.Record_A('1.2.3.4')),
+ dns.RRHeader('example.com', auth=0, payload=dns.Record_A('1.2.3.4')))
+
+
+ def test_ns(self):
+ """
+ Two L{dns.Record_NS} instances compare equal if and only if they have
+ the same name and TTL.
+ """
+ self._simpleEqualityTest(dns.Record_NS)
+
+
+ def test_md(self):
+ """
+ Two L{dns.Record_MD} instances compare equal if and only if they have
+ the same name and TTL.
+ """
+ self._simpleEqualityTest(dns.Record_MD)
+
+
+ def test_mf(self):
+ """
+ Two L{dns.Record_MF} instances compare equal if and only if they have
+ the same name and TTL.
+ """
+ self._simpleEqualityTest(dns.Record_MF)
+
+
+ def test_cname(self):
+ """
+ Two L{dns.Record_CNAME} instances compare equal if and only if they
+ have the same name and TTL.
+ """
+ self._simpleEqualityTest(dns.Record_CNAME)
+
+
+ def test_mb(self):
+ """
+ Two L{dns.Record_MB} instances compare equal if and only if they have
+ the same name and TTL.
+ """
+ self._simpleEqualityTest(dns.Record_MB)
+
+
+ def test_mg(self):
+ """
+ Two L{dns.Record_MG} instances compare equal if and only if they have
+ the same name and TTL.
+ """
+ self._simpleEqualityTest(dns.Record_MG)
+
+
+ def test_mr(self):
+ """
+ Two L{dns.Record_MR} instances compare equal if and only if they have
+ the same name and TTL.
+ """
+ self._simpleEqualityTest(dns.Record_MR)
+
+
+ def test_ptr(self):
+ """
+ Two L{dns.Record_PTR} instances compare equal if and only if they have
+ the same name and TTL.
+ """
+ self._simpleEqualityTest(dns.Record_PTR)
+
+
+ def test_dname(self):
+ """
+ Two L{dns.Record_MD} instances compare equal if and only if they have
+ the same name and TTL.
+ """
+ self._simpleEqualityTest(dns.Record_DNAME)
+
+
+ def test_a(self):
+ """
+ Two L{dns.Record_A} instances compare equal if and only if they have
+ the same address and TTL.
+ """
+ # Vary the TTL
+ self._equalityTest(
+ dns.Record_A('1.2.3.4', 5),
+ dns.Record_A('1.2.3.4', 5),
+ dns.Record_A('1.2.3.4', 6))
+ # Vary the address
+ self._equalityTest(
+ dns.Record_A('1.2.3.4', 5),
+ dns.Record_A('1.2.3.4', 5),
+ dns.Record_A('1.2.3.5', 5))
+
+
+ def test_soa(self):
+ """
+ Two L{dns.Record_SOA} instances compare equal if and only if they have
+ the same mname, rname, serial, refresh, minimum, expire, retry, and
+ ttl.
+ """
+ # Vary the mname
+ self._equalityTest(
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('xname', 'rname', 123, 456, 789, 10, 20, 30))
+ # Vary the rname
+ self._equalityTest(
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'xname', 123, 456, 789, 10, 20, 30))
+ # Vary the serial
+ self._equalityTest(
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 1, 456, 789, 10, 20, 30))
+ # Vary the refresh
+ self._equalityTest(
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 1, 789, 10, 20, 30))
+ # Vary the minimum
+ self._equalityTest(
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 1, 10, 20, 30))
+ # Vary the expire
+ self._equalityTest(
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 1, 20, 30))
+ # Vary the retry
+ self._equalityTest(
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 1, 30))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'rname', 123, 456, 789, 10, 20, 30),
+ dns.Record_SOA('mname', 'xname', 123, 456, 789, 10, 20, 1))
+
+
+ def test_null(self):
+ """
+ Two L{dns.Record_NULL} instances compare equal if and only if they have
+ the same payload and ttl.
+ """
+ # Vary the payload
+ self._equalityTest(
+ dns.Record_NULL('foo bar', 10),
+ dns.Record_NULL('foo bar', 10),
+ dns.Record_NULL('bar foo', 10))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_NULL('foo bar', 10),
+ dns.Record_NULL('foo bar', 10),
+ dns.Record_NULL('foo bar', 100))
+
+
+ def test_wks(self):
+ """
+ Two L{dns.Record_WKS} instances compare equal if and only if they have
+ the same address, protocol, map, and ttl.
+ """
+ # Vary the address
+ self._equalityTest(
+ dns.Record_WKS('1.2.3.4', 1, 'foo', 2),
+ dns.Record_WKS('1.2.3.4', 1, 'foo', 2),
+ dns.Record_WKS('4.3.2.1', 1, 'foo', 2))
+ # Vary the protocol
+ self._equalityTest(
+ dns.Record_WKS('1.2.3.4', 1, 'foo', 2),
+ dns.Record_WKS('1.2.3.4', 1, 'foo', 2),
+ dns.Record_WKS('1.2.3.4', 100, 'foo', 2))
+ # Vary the map
+ self._equalityTest(
+ dns.Record_WKS('1.2.3.4', 1, 'foo', 2),
+ dns.Record_WKS('1.2.3.4', 1, 'foo', 2),
+ dns.Record_WKS('1.2.3.4', 1, 'bar', 2))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_WKS('1.2.3.4', 1, 'foo', 2),
+ dns.Record_WKS('1.2.3.4', 1, 'foo', 2),
+ dns.Record_WKS('1.2.3.4', 1, 'foo', 200))
+
+
+ def test_aaaa(self):
+ """
+ Two L{dns.Record_AAAA} instances compare equal if and only if they have
+ the same address and ttl.
+ """
+ # Vary the address
+ self._equalityTest(
+ dns.Record_AAAA('1::2', 1),
+ dns.Record_AAAA('1::2', 1),
+ dns.Record_AAAA('2::1', 1))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_AAAA('1::2', 1),
+ dns.Record_AAAA('1::2', 1),
+ dns.Record_AAAA('1::2', 10))
+
+
+ def test_a6(self):
+ """
+ Two L{dns.Record_A6} instances compare equal if and only if they have
+ the same prefix, prefix length, suffix, and ttl.
+ """
+ # Note, A6 is crazy, I'm not sure these values are actually legal.
+ # Hopefully that doesn't matter for this test. -exarkun
+
+ # Vary the prefix length
+ self._equalityTest(
+ dns.Record_A6(16, '::abcd', 'example.com', 10),
+ dns.Record_A6(16, '::abcd', 'example.com', 10),
+ dns.Record_A6(32, '::abcd', 'example.com', 10))
+ # Vary the suffix
+ self._equalityTest(
+ dns.Record_A6(16, '::abcd', 'example.com', 10),
+ dns.Record_A6(16, '::abcd', 'example.com', 10),
+ dns.Record_A6(16, '::abcd:0', 'example.com', 10))
+ # Vary the prefix
+ self._equalityTest(
+ dns.Record_A6(16, '::abcd', 'example.com', 10),
+ dns.Record_A6(16, '::abcd', 'example.com', 10),
+ dns.Record_A6(16, '::abcd', 'example.org', 10))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_A6(16, '::abcd', 'example.com', 10),
+ dns.Record_A6(16, '::abcd', 'example.com', 10),
+ dns.Record_A6(16, '::abcd', 'example.com', 100))
+
+
+ def test_srv(self):
+ """
+ Two L{dns.Record_SRV} instances compare equal if and only if they have
+ the same priority, weight, port, target, and ttl.
+ """
+ # Vary the priority
+ self._equalityTest(
+ dns.Record_SRV(10, 20, 30, 'example.com', 40),
+ dns.Record_SRV(10, 20, 30, 'example.com', 40),
+ dns.Record_SRV(100, 20, 30, 'example.com', 40))
+ # Vary the weight
+ self._equalityTest(
+ dns.Record_SRV(10, 20, 30, 'example.com', 40),
+ dns.Record_SRV(10, 20, 30, 'example.com', 40),
+ dns.Record_SRV(10, 200, 30, 'example.com', 40))
+ # Vary the port
+ self._equalityTest(
+ dns.Record_SRV(10, 20, 30, 'example.com', 40),
+ dns.Record_SRV(10, 20, 30, 'example.com', 40),
+ dns.Record_SRV(10, 20, 300, 'example.com', 40))
+ # Vary the target
+ self._equalityTest(
+ dns.Record_SRV(10, 20, 30, 'example.com', 40),
+ dns.Record_SRV(10, 20, 30, 'example.com', 40),
+ dns.Record_SRV(10, 20, 30, 'example.org', 40))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_SRV(10, 20, 30, 'example.com', 40),
+ dns.Record_SRV(10, 20, 30, 'example.com', 40),
+ dns.Record_SRV(10, 20, 30, 'example.com', 400))
+
+
+ def test_naptr(self):
+ """
+ Two L{dns.Record_NAPTR} instances compare equal if and only if they
+ have the same order, preference, flags, service, regexp, replacement,
+ and ttl.
+ """
+ # Vary the order
+ self._equalityTest(
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(2, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12))
+ # Vary the preference
+ self._equalityTest(
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 3, "u", "sip+E2U", "/foo/bar/", "baz", 12))
+ # Vary the flags
+ self._equalityTest(
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "p", "sip+E2U", "/foo/bar/", "baz", 12))
+ # Vary the service
+ self._equalityTest(
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "http", "/foo/bar/", "baz", 12))
+ # Vary the regexp
+ self._equalityTest(
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/bar/foo/", "baz", 12))
+ # Vary the replacement
+ self._equalityTest(
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/bar/foo/", "quux", 12))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/foo/bar/", "baz", 12),
+ dns.Record_NAPTR(1, 2, "u", "sip+E2U", "/bar/foo/", "baz", 5))
+
+
+ def test_afsdb(self):
+ """
+ Two L{dns.Record_AFSDB} instances compare equal if and only if they
+ have the same subtype, hostname, and ttl.
+ """
+ # Vary the subtype
+ self._equalityTest(
+ dns.Record_AFSDB(1, 'example.com', 2),
+ dns.Record_AFSDB(1, 'example.com', 2),
+ dns.Record_AFSDB(2, 'example.com', 2))
+ # Vary the hostname
+ self._equalityTest(
+ dns.Record_AFSDB(1, 'example.com', 2),
+ dns.Record_AFSDB(1, 'example.com', 2),
+ dns.Record_AFSDB(1, 'example.org', 2))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_AFSDB(1, 'example.com', 2),
+ dns.Record_AFSDB(1, 'example.com', 2),
+ dns.Record_AFSDB(1, 'example.com', 3))
+
+
+ def test_rp(self):
+ """
+ Two L{Record_RP} instances compare equal if and only if they have the
+ same mbox, txt, and ttl.
+ """
+ # Vary the mbox
+ self._equalityTest(
+ dns.Record_RP('alice.example.com', 'alice is nice', 10),
+ dns.Record_RP('alice.example.com', 'alice is nice', 10),
+ dns.Record_RP('bob.example.com', 'alice is nice', 10))
+ # Vary the txt
+ self._equalityTest(
+ dns.Record_RP('alice.example.com', 'alice is nice', 10),
+ dns.Record_RP('alice.example.com', 'alice is nice', 10),
+ dns.Record_RP('alice.example.com', 'alice is not nice', 10))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_RP('alice.example.com', 'alice is nice', 10),
+ dns.Record_RP('alice.example.com', 'alice is nice', 10),
+ dns.Record_RP('alice.example.com', 'alice is nice', 100))
+
+
+ def test_hinfo(self):
+ """
+ Two L{dns.Record_HINFO} instances compare equal if and only if they
+ have the same cpu, os, and ttl.
+ """
+ # Vary the cpu
+ self._equalityTest(
+ dns.Record_HINFO('x86-64', 'plan9', 10),
+ dns.Record_HINFO('x86-64', 'plan9', 10),
+ dns.Record_HINFO('i386', 'plan9', 10))
+ # Vary the os
+ self._equalityTest(
+ dns.Record_HINFO('x86-64', 'plan9', 10),
+ dns.Record_HINFO('x86-64', 'plan9', 10),
+ dns.Record_HINFO('x86-64', 'plan11', 10))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_HINFO('x86-64', 'plan9', 10),
+ dns.Record_HINFO('x86-64', 'plan9', 10),
+ dns.Record_HINFO('x86-64', 'plan9', 100))
+
+
+ def test_minfo(self):
+ """
+ Two L{dns.Record_MINFO} instances compare equal if and only if they
+ have the same rmailbx, emailbx, and ttl.
+ """
+ # Vary the rmailbx
+ self._equalityTest(
+ dns.Record_MINFO('rmailbox', 'emailbox', 10),
+ dns.Record_MINFO('rmailbox', 'emailbox', 10),
+ dns.Record_MINFO('someplace', 'emailbox', 10))
+ # Vary the emailbx
+ self._equalityTest(
+ dns.Record_MINFO('rmailbox', 'emailbox', 10),
+ dns.Record_MINFO('rmailbox', 'emailbox', 10),
+ dns.Record_MINFO('rmailbox', 'something', 10))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_MINFO('rmailbox', 'emailbox', 10),
+ dns.Record_MINFO('rmailbox', 'emailbox', 10),
+ dns.Record_MINFO('rmailbox', 'emailbox', 100))
+
+
+ def test_mx(self):
+ """
+ Two L{dns.Record_MX} instances compare equal if and only if they have
+ the same preference, name, and ttl.
+ """
+ # Vary the preference
+ self._equalityTest(
+ dns.Record_MX(10, 'example.org', 20),
+ dns.Record_MX(10, 'example.org', 20),
+ dns.Record_MX(100, 'example.org', 20))
+ # Vary the name
+ self._equalityTest(
+ dns.Record_MX(10, 'example.org', 20),
+ dns.Record_MX(10, 'example.org', 20),
+ dns.Record_MX(10, 'example.net', 20))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_MX(10, 'example.org', 20),
+ dns.Record_MX(10, 'example.org', 20),
+ dns.Record_MX(10, 'example.org', 200))
+
+
+ def test_txt(self):
+ """
+ Two L{dns.Record_TXT} instances compare equal if and only if they have
+ the same data and ttl.
+ """
+ # Vary the length of the data
+ self._equalityTest(
+ dns.Record_TXT(['foo', 'bar'], 10),
+ dns.Record_TXT(['foo', 'bar'], 10),
+ dns.Record_TXT(['foo', 'bar', 'baz'], 10))
+ # Vary the value of the data
+ self._equalityTest(
+ dns.Record_TXT(['foo', 'bar'], 10),
+ dns.Record_TXT(['foo', 'bar'], 10),
+ dns.Record_TXT(['bar', 'foo'], 10))
+ # Vary the ttl
+ self._equalityTest(
+ dns.Record_TXT(['foo', 'bar'], 10),
+ dns.Record_TXT(['foo', 'bar'], 10),
+ dns.Record_TXT(['foo', 'bar'], 100))
diff --git a/vendor/Twisted-10.0.0/twisted/names/test/test_names.py b/vendor/Twisted-10.0.0/twisted/names/test/test_names.py
new file mode 100644
index 0000000000..ae85f00f64
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/test/test_names.py
@@ -0,0 +1,752 @@
+# -*- test-case-name: twisted.names.test.test_names -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for twisted.names.
+"""
+
+import socket, operator, copy
+
+from twisted.trial import unittest
+
+from twisted.internet import reactor, defer, error
+from twisted.internet.defer import succeed
+from twisted.names import client, server, common, authority, hosts, dns
+from twisted.python import failure
+from twisted.names.error import DNSFormatError, DNSServerError, DNSNameError
+from twisted.names.error import DNSNotImplementedError, DNSQueryRefusedError
+from twisted.names.error import DNSUnknownError
+from twisted.names.dns import EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED
+from twisted.names.dns import Message
+from twisted.names.client import Resolver
+
+from twisted.names.test.test_client import StubPort
+from twisted.python.compat import reduce
+
+def justPayload(results):
+ return [r.payload for r in results[0]]
+
+class NoFileAuthority(authority.FileAuthority):
+ def __init__(self, soa, records):
+ # Yes, skip FileAuthority
+ common.ResolverBase.__init__(self)
+ self.soa, self.records = soa, records
+
+
+soa_record = dns.Record_SOA(
+ mname = 'test-domain.com',
+ rname = 'root.test-domain.com',
+ serial = 100,
+ refresh = 1234,
+ minimum = 7654,
+ expire = 19283784,
+ retry = 15,
+ ttl=1
+ )
+
+reverse_soa = dns.Record_SOA(
+ mname = '93.84.28.in-addr.arpa',
+ rname = '93.84.28.in-addr.arpa',
+ serial = 120,
+ refresh = 54321,
+ minimum = 382,
+ expire = 11193983,
+ retry = 30,
+ ttl=3
+ )
+
+my_soa = dns.Record_SOA(
+ mname = 'my-domain.com',
+ rname = 'postmaster.test-domain.com',
+ serial = 130,
+ refresh = 12345,
+ minimum = 1,
+ expire = 999999,
+ retry = 100,
+ )
+
+test_domain_com = NoFileAuthority(
+ soa = ('test-domain.com', soa_record),
+ records = {
+ 'test-domain.com': [
+ soa_record,
+ dns.Record_A('127.0.0.1'),
+ dns.Record_NS('39.28.189.39'),
+ dns.Record_MX(10, 'host.test-domain.com'),
+ dns.Record_HINFO(os='Linux', cpu='A Fast One, Dontcha know'),
+ dns.Record_CNAME('canonical.name.com'),
+ dns.Record_MB('mailbox.test-domain.com'),
+ dns.Record_MG('mail.group.someplace'),
+ dns.Record_TXT('A First piece of Text', 'a SecoNd piece'),
+ dns.Record_A6(0, 'ABCD::4321', ''),
+ dns.Record_A6(12, '0:0069::0', 'some.network.tld'),
+ dns.Record_A6(8, '0:5634:1294:AFCB:56AC:48EF:34C3:01FF', 'tra.la.la.net'),
+ dns.Record_TXT('Some more text, haha! Yes. \0 Still here?'),
+ dns.Record_MR('mail.redirect.or.whatever'),
+ dns.Record_MINFO(rmailbx='r mail box', emailbx='e mail box'),
+ dns.Record_AFSDB(subtype=1, hostname='afsdb.test-domain.com'),
+ dns.Record_RP(mbox='whatever.i.dunno', txt='some.more.text'),
+ dns.Record_WKS('12.54.78.12', socket.IPPROTO_TCP,
+ '\x12\x01\x16\xfe\xc1\x00\x01'),
+ dns.Record_NAPTR(100, 10, "u", "sip+E2U",
+ "!^.*$!sip:information@domain.tld!"),
+ dns.Record_AAAA('AF43:5634:1294:AFCB:56AC:48EF:34C3:01FF')],
+ 'http.tcp.test-domain.com': [
+ dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool')
+ ],
+ 'host.test-domain.com': [
+ dns.Record_A('123.242.1.5'),
+ dns.Record_A('0.255.0.255'),
+ ],
+ 'host-two.test-domain.com': [
+#
+# Python bug
+# dns.Record_A('255.255.255.255'),
+#
+ dns.Record_A('255.255.255.254'),
+ dns.Record_A('0.0.0.0')
+ ],
+ 'cname.test-domain.com': [
+ dns.Record_CNAME('test-domain.com')
+ ],
+ 'anothertest-domain.com': [
+ dns.Record_A('1.2.3.4')],
+ }
+)
+
+reverse_domain = NoFileAuthority(
+ soa = ('93.84.28.in-addr.arpa', reverse_soa),
+ records = {
+ '123.93.84.28.in-addr.arpa': [
+ dns.Record_PTR('test.host-reverse.lookup.com'),
+ reverse_soa
+ ]
+ }
+)
+
+
+my_domain_com = NoFileAuthority(
+ soa = ('my-domain.com', my_soa),
+ records = {
+ 'my-domain.com': [
+ my_soa,
+ dns.Record_A('1.2.3.4', ttl='1S'),
+ dns.Record_NS('ns1.domain', ttl='2M'),
+ dns.Record_NS('ns2.domain', ttl='3H'),
+ dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool', ttl='4D')
+ ]
+ }
+ )
+
+
+class ServerDNSTestCase(unittest.TestCase):
+ """
+ Test cases for DNS server and client.
+ """
+
+ def setUp(self):
+ self.factory = server.DNSServerFactory([
+ test_domain_com, reverse_domain, my_domain_com
+ ], verbose=2)
+
+ p = dns.DNSDatagramProtocol(self.factory)
+
+ while 1:
+ listenerTCP = reactor.listenTCP(0, self.factory, interface="127.0.0.1")
+ # It's simpler to do the stop listening with addCleanup,
+ # even though we might not end up using this TCP port in
+ # the test (if the listenUDP below fails). Cleaning up
+ # this TCP port sooner than "cleanup time" would mean
+ # adding more code to keep track of the Deferred returned
+ # by stopListening.
+ self.addCleanup(listenerTCP.stopListening)
+ port = listenerTCP.getHost().port
+
+ try:
+ listenerUDP = reactor.listenUDP(port, p, interface="127.0.0.1")
+ except error.CannotListenError:
+ pass
+ else:
+ self.addCleanup(listenerUDP.stopListening)
+ break
+
+ self.listenerTCP = listenerTCP
+ self.listenerUDP = listenerUDP
+ self.resolver = client.Resolver(servers=[('127.0.0.1', port)])
+
+
+ def tearDown(self):
+ """
+ Clean up any server connections associated with the
+ L{DNSServerFactory} created in L{setUp}
+ """
+ # It'd be great if DNSServerFactory had a method that
+ # encapsulated this task. At least the necessary data is
+ # available, though.
+ for conn in self.factory.connections[:]:
+ conn.transport.loseConnection()
+
+
+ def namesTest(self, d, r):
+ self.response = None
+ def setDone(response):
+ self.response = response
+
+ def checkResults(ignored):
+ if isinstance(self.response, failure.Failure):
+ raise self.response
+ results = justPayload(self.response)
+ assert len(results) == len(r), "%s != %s" % (map(str, results), map(str, r))
+ for rec in results:
+ assert rec in r, "%s not in %s" % (rec, map(str, r))
+
+ d.addBoth(setDone)
+ d.addCallback(checkResults)
+ return d
+
+ def testAddressRecord1(self):
+ """Test simple DNS 'A' record queries"""
+ return self.namesTest(
+ self.resolver.lookupAddress('test-domain.com'),
+ [dns.Record_A('127.0.0.1', ttl=19283784)]
+ )
+
+
+ def testAddressRecord2(self):
+ """Test DNS 'A' record queries with multiple answers"""
+ return self.namesTest(
+ self.resolver.lookupAddress('host.test-domain.com'),
+ [dns.Record_A('123.242.1.5', ttl=19283784), dns.Record_A('0.255.0.255', ttl=19283784)]
+ )
+
+
+ def testAdressRecord3(self):
+ """Test DNS 'A' record queries with edge cases"""
+ return self.namesTest(
+ self.resolver.lookupAddress('host-two.test-domain.com'),
+ [dns.Record_A('255.255.255.254', ttl=19283784), dns.Record_A('0.0.0.0', ttl=19283784)]
+ )
+
+ def testAuthority(self):
+ """Test DNS 'SOA' record queries"""
+ return self.namesTest(
+ self.resolver.lookupAuthority('test-domain.com'),
+ [soa_record]
+ )
+
+
+ def testMailExchangeRecord(self):
+ """Test DNS 'MX' record queries"""
+ return self.namesTest(
+ self.resolver.lookupMailExchange('test-domain.com'),
+ [dns.Record_MX(10, 'host.test-domain.com', ttl=19283784)]
+ )
+
+
+ def testNameserver(self):
+ """Test DNS 'NS' record queries"""
+ return self.namesTest(
+ self.resolver.lookupNameservers('test-domain.com'),
+ [dns.Record_NS('39.28.189.39', ttl=19283784)]
+ )
+
+
+ def testHINFO(self):
+ """Test DNS 'HINFO' record queries"""
+ return self.namesTest(
+ self.resolver.lookupHostInfo('test-domain.com'),
+ [dns.Record_HINFO(os='Linux', cpu='A Fast One, Dontcha know', ttl=19283784)]
+ )
+
+ def testPTR(self):
+ """Test DNS 'PTR' record queries"""
+ return self.namesTest(
+ self.resolver.lookupPointer('123.93.84.28.in-addr.arpa'),
+ [dns.Record_PTR('test.host-reverse.lookup.com', ttl=11193983)]
+ )
+
+
+ def testCNAME(self):
+ """Test DNS 'CNAME' record queries"""
+ return self.namesTest(
+ self.resolver.lookupCanonicalName('test-domain.com'),
+ [dns.Record_CNAME('canonical.name.com', ttl=19283784)]
+ )
+
+ def testCNAMEAdditional(self):
+ """Test additional processing for CNAME records"""
+ return self.namesTest(
+ self.resolver.lookupAddress('cname.test-domain.com'),
+ [dns.Record_CNAME('test-domain.com', ttl=19283784), dns.Record_A('127.0.0.1', ttl=19283784)]
+ )
+
+ def testMB(self):
+ """Test DNS 'MB' record queries"""
+ return self.namesTest(
+ self.resolver.lookupMailBox('test-domain.com'),
+ [dns.Record_MB('mailbox.test-domain.com', ttl=19283784)]
+ )
+
+
+ def testMG(self):
+ """Test DNS 'MG' record queries"""
+ return self.namesTest(
+ self.resolver.lookupMailGroup('test-domain.com'),
+ [dns.Record_MG('mail.group.someplace', ttl=19283784)]
+ )
+
+
+ def testMR(self):
+ """Test DNS 'MR' record queries"""
+ return self.namesTest(
+ self.resolver.lookupMailRename('test-domain.com'),
+ [dns.Record_MR('mail.redirect.or.whatever', ttl=19283784)]
+ )
+
+
+ def testMINFO(self):
+ """Test DNS 'MINFO' record queries"""
+ return self.namesTest(
+ self.resolver.lookupMailboxInfo('test-domain.com'),
+ [dns.Record_MINFO(rmailbx='r mail box', emailbx='e mail box', ttl=19283784)]
+ )
+
+
+ def testSRV(self):
+ """Test DNS 'SRV' record queries"""
+ return self.namesTest(
+ self.resolver.lookupService('http.tcp.test-domain.com'),
+ [dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool', ttl=19283784)]
+ )
+
+ def testAFSDB(self):
+ """Test DNS 'AFSDB' record queries"""
+ return self.namesTest(
+ self.resolver.lookupAFSDatabase('test-domain.com'),
+ [dns.Record_AFSDB(subtype=1, hostname='afsdb.test-domain.com', ttl=19283784)]
+ )
+
+
+ def testRP(self):
+ """Test DNS 'RP' record queries"""
+ return self.namesTest(
+ self.resolver.lookupResponsibility('test-domain.com'),
+ [dns.Record_RP(mbox='whatever.i.dunno', txt='some.more.text', ttl=19283784)]
+ )
+
+
+ def testTXT(self):
+ """Test DNS 'TXT' record queries"""
+ return self.namesTest(
+ self.resolver.lookupText('test-domain.com'),
+ [dns.Record_TXT('A First piece of Text', 'a SecoNd piece', ttl=19283784),
+ dns.Record_TXT('Some more text, haha! Yes. \0 Still here?', ttl=19283784)]
+ )
+
+
+ def testWKS(self):
+ """Test DNS 'WKS' record queries"""
+ return self.namesTest(
+ self.resolver.lookupWellKnownServices('test-domain.com'),
+ [dns.Record_WKS('12.54.78.12', socket.IPPROTO_TCP, '\x12\x01\x16\xfe\xc1\x00\x01', ttl=19283784)]
+ )
+
+
+ def testSomeRecordsWithTTLs(self):
+ result_soa = copy.copy(my_soa)
+ result_soa.ttl = my_soa.expire
+ return self.namesTest(
+ self.resolver.lookupAllRecords('my-domain.com'),
+ [result_soa,
+ dns.Record_A('1.2.3.4', ttl='1S'),
+ dns.Record_NS('ns1.domain', ttl='2M'),
+ dns.Record_NS('ns2.domain', ttl='3H'),
+ dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool', ttl='4D')]
+ )
+
+
+ def testAAAA(self):
+ """Test DNS 'AAAA' record queries (IPv6)"""
+ return self.namesTest(
+ self.resolver.lookupIPV6Address('test-domain.com'),
+ [dns.Record_AAAA('AF43:5634:1294:AFCB:56AC:48EF:34C3:01FF', ttl=19283784)]
+ )
+
+ def testA6(self):
+ """Test DNS 'A6' record queries (IPv6)"""
+ return self.namesTest(
+ self.resolver.lookupAddress6('test-domain.com'),
+ [dns.Record_A6(0, 'ABCD::4321', '', ttl=19283784),
+ dns.Record_A6(12, '0:0069::0', 'some.network.tld', ttl=19283784),
+ dns.Record_A6(8, '0:5634:1294:AFCB:56AC:48EF:34C3:01FF', 'tra.la.la.net', ttl=19283784)]
+ )
+
+
+ def test_zoneTransfer(self):
+ """
+ Test DNS 'AXFR' queries (Zone transfer)
+ """
+ default_ttl = soa_record.expire
+ results = [copy.copy(r) for r in reduce(operator.add, test_domain_com.records.values())]
+ for r in results:
+ if r.ttl is None:
+ r.ttl = default_ttl
+ return self.namesTest(
+ self.resolver.lookupZone('test-domain.com').addCallback(lambda r: (r[0][:-1],)),
+ results
+ )
+
+
+ def testSimilarZonesDontInterfere(self):
+ """Tests that unrelated zones don't mess with each other."""
+ return self.namesTest(
+ self.resolver.lookupAddress("anothertest-domain.com"),
+ [dns.Record_A('1.2.3.4', ttl=19283784)]
+ )
+
+
+ def test_NAPTR(self):
+ """
+ Test DNS 'NAPTR' record queries.
+ """
+ return self.namesTest(
+ self.resolver.lookupNamingAuthorityPointer('test-domain.com'),
+ [dns.Record_NAPTR(100, 10, "u", "sip+E2U",
+ "!^.*$!sip:information@domain.tld!",
+ ttl=19283784)])
+
+
+
+class DNSServerFactoryTests(unittest.TestCase):
+ """
+ Tests for L{server.DNSServerFactory}.
+ """
+ def _messageReceivedTest(self, methodName, message):
+ """
+ Assert that the named method is called with the given message when
+ it is passed to L{DNSServerFactory.messageReceived}.
+ """
+ # Make it appear to have some queries so that
+ # DNSServerFactory.allowQuery allows it.
+ message.queries = [None]
+
+ receivedMessages = []
+ def fakeHandler(message, protocol, address):
+ receivedMessages.append((message, protocol, address))
+
+ class FakeProtocol(object):
+ def writeMessage(self, message):
+ pass
+
+ protocol = FakeProtocol()
+ factory = server.DNSServerFactory(None)
+ setattr(factory, methodName, fakeHandler)
+ factory.messageReceived(message, protocol)
+ self.assertEqual(receivedMessages, [(message, protocol, None)])
+
+
+ def test_notifyMessageReceived(self):
+ """
+ L{DNSServerFactory.messageReceived} passes messages with an opcode
+ of C{OP_NOTIFY} on to L{DNSServerFactory.handleNotify}.
+ """
+ # RFC 1996, section 4.5
+ opCode = 4
+ self._messageReceivedTest('handleNotify', Message(opCode=opCode))
+
+
+ def test_updateMessageReceived(self):
+ """
+ L{DNSServerFactory.messageReceived} passes messages with an opcode
+ of C{OP_UPDATE} on to L{DNSServerFactory.handleOther}.
+
+ This may change if the implementation ever covers update messages.
+ """
+ # RFC 2136, section 1.3
+ opCode = 5
+ self._messageReceivedTest('handleOther', Message(opCode=opCode))
+
+
+ def test_connectionTracking(self):
+ """
+ The C{connectionMade} and C{connectionLost} methods of
+ L{DNSServerFactory} cooperate to keep track of all
+ L{DNSProtocol} objects created by a factory which are
+ connected.
+ """
+ protoA, protoB = object(), object()
+ factory = server.DNSServerFactory()
+ factory.connectionMade(protoA)
+ self.assertEqual(factory.connections, [protoA])
+ factory.connectionMade(protoB)
+ self.assertEqual(factory.connections, [protoA, protoB])
+ factory.connectionLost(protoA)
+ self.assertEqual(factory.connections, [protoB])
+ factory.connectionLost(protoB)
+ self.assertEqual(factory.connections, [])
+
+
+class HelperTestCase(unittest.TestCase):
+ def testSerialGenerator(self):
+ f = self.mktemp()
+ a = authority.getSerial(f)
+ for i in range(20):
+ b = authority.getSerial(f)
+ self.failUnless(a < b)
+ a = b
+
+
+class AXFRTest(unittest.TestCase):
+ def setUp(self):
+ self.results = None
+ self.d = defer.Deferred()
+ self.d.addCallback(self._gotResults)
+ self.controller = client.AXFRController('fooby.com', self.d)
+
+ self.soa = dns.RRHeader(name='fooby.com', type=dns.SOA, cls=dns.IN, ttl=86400, auth=False,
+ payload=dns.Record_SOA(mname='fooby.com',
+ rname='hooj.fooby.com',
+ serial=100,
+ refresh=200,
+ retry=300,
+ expire=400,
+ minimum=500,
+ ttl=600))
+
+ self.records = [
+ self.soa,
+ dns.RRHeader(name='fooby.com', type=dns.NS, cls=dns.IN, ttl=700, auth=False,
+ payload=dns.Record_NS(name='ns.twistedmatrix.com', ttl=700)),
+
+ dns.RRHeader(name='fooby.com', type=dns.MX, cls=dns.IN, ttl=700, auth=False,
+ payload=dns.Record_MX(preference=10, exchange='mail.mv3d.com', ttl=700)),
+
+ dns.RRHeader(name='fooby.com', type=dns.A, cls=dns.IN, ttl=700, auth=False,
+ payload=dns.Record_A(address='64.123.27.105', ttl=700)),
+ self.soa
+ ]
+
+ def _makeMessage(self):
+ # hooray they all have the same message format
+ return dns.Message(id=999, answer=1, opCode=0, recDes=0, recAv=1, auth=1, rCode=0, trunc=0, maxSize=0)
+
+ def testBindAndTNamesStyle(self):
+ # Bind style = One big single message
+ m = self._makeMessage()
+ m.queries = [dns.Query('fooby.com', dns.AXFR, dns.IN)]
+ m.answers = self.records
+ self.controller.messageReceived(m, None)
+ self.assertEquals(self.results, self.records)
+
+ def _gotResults(self, result):
+ self.results = result
+
+ def testDJBStyle(self):
+ # DJB style = message per record
+ records = self.records[:]
+ while records:
+ m = self._makeMessage()
+ m.queries = [] # DJB *doesn't* specify any queries.. hmm..
+ m.answers = [records.pop(0)]
+ self.controller.messageReceived(m, None)
+ self.assertEquals(self.results, self.records)
+
+class HostsTestCase(unittest.TestCase):
+ def setUp(self):
+ f = open('EtcHosts', 'w')
+ f.write('''
+1.1.1.1 EXAMPLE EXAMPLE.EXAMPLETHING
+1.1.1.2 HOOJY
+::1 ip6thingy
+''')
+ f.close()
+ self.resolver = hosts.Resolver('EtcHosts')
+
+ def testGetHostByName(self):
+ data = [('EXAMPLE', '1.1.1.1'),
+ ('EXAMPLE.EXAMPLETHING', '1.1.1.1'),
+ ('HOOJY', '1.1.1.2'),
+ ]
+ ds = [self.resolver.getHostByName(n).addCallback(self.assertEqual, ip)
+ for n, ip in data]
+ return defer.gatherResults(ds)
+
+ def testLookupAddress(self):
+ d = self.resolver.lookupAddress('HOOJY')
+ d.addCallback(lambda x: self.assertEqual(x[0][0].payload.dottedQuad(),
+ '1.1.1.2'))
+ return d
+
+ def testIPv6(self):
+ d = self.resolver.lookupIPV6Address('ip6thingy')
+ d.addCallback(self.assertEqual, '::1')
+ return d
+
+ testIPv6.skip = 'IPv6 support is not in our hosts resolver yet'
+
+ def testNotImplemented(self):
+ return self.assertFailure(self.resolver.lookupMailExchange('EXAMPLE'),
+ NotImplementedError)
+
+ def testQuery(self):
+ d = self.resolver.query(dns.Query('EXAMPLE'))
+ d.addCallback(lambda x: self.assertEqual(x[0][0].payload.dottedQuad(),
+ '1.1.1.1'))
+ return d
+
+ def testNotFound(self):
+ return self.assertFailure(self.resolver.lookupAddress('foueoa'),
+ dns.DomainError)
+
+
+class FakeDNSDatagramProtocol(object):
+ def __init__(self):
+ self.queries = []
+ self.transport = StubPort()
+
+ def query(self, address, queries, timeout=10, id=None):
+ self.queries.append((address, queries, timeout, id))
+ return defer.fail(dns.DNSQueryTimeoutError(queries))
+
+ def removeResend(self, id):
+ # Ignore this for the time being.
+ pass
+
+class RetryLogic(unittest.TestCase):
+ testServers = [
+ '1.2.3.4',
+ '4.3.2.1',
+ 'a.b.c.d',
+ 'z.y.x.w']
+
+ def testRoundRobinBackoff(self):
+ addrs = [(x, 53) for x in self.testServers]
+ r = client.Resolver(resolv=None, servers=addrs)
+ r.protocol = proto = FakeDNSDatagramProtocol()
+ return r.lookupAddress("foo.example.com"
+ ).addCallback(self._cbRoundRobinBackoff
+ ).addErrback(self._ebRoundRobinBackoff, proto
+ )
+
+ def _cbRoundRobinBackoff(self, result):
+ raise unittest.FailTest("Lookup address succeeded, should have timed out")
+
+ def _ebRoundRobinBackoff(self, failure, fakeProto):
+ failure.trap(defer.TimeoutError)
+
+ # Assert that each server is tried with a particular timeout
+ # before the timeout is increased and the attempts are repeated.
+
+ for t in (1, 3, 11, 45):
+ tries = fakeProto.queries[:len(self.testServers)]
+ del fakeProto.queries[:len(self.testServers)]
+
+ tries.sort()
+ expected = list(self.testServers)
+ expected.sort()
+
+ for ((addr, query, timeout, id), expectedAddr) in zip(tries, expected):
+ self.assertEquals(addr, (expectedAddr, 53))
+ self.assertEquals(timeout, t)
+
+ self.failIf(fakeProto.queries)
+
+class ResolvConfHandling(unittest.TestCase):
+ def testMissing(self):
+ resolvConf = self.mktemp()
+ r = client.Resolver(resolv=resolvConf)
+ self.assertEquals(r.dynServers, [('127.0.0.1', 53)])
+ r._parseCall.cancel()
+
+ def testEmpty(self):
+ resolvConf = self.mktemp()
+ fObj = file(resolvConf, 'w')
+ fObj.close()
+ r = client.Resolver(resolv=resolvConf)
+ self.assertEquals(r.dynServers, [('127.0.0.1', 53)])
+ r._parseCall.cancel()
+
+
+
+class FilterAnswersTests(unittest.TestCase):
+ """
+ Test L{twisted.names.client.Resolver.filterAnswers}'s handling of various
+ error conditions it might encounter.
+ """
+ def setUp(self):
+ # Create a resolver pointed at an invalid server - we won't be hitting
+ # the network in any of these tests.
+ self.resolver = Resolver(servers=[('0.0.0.0', 0)])
+
+
+ def test_truncatedMessage(self):
+ """
+ Test that a truncated message results in an equivalent request made via
+ TCP.
+ """
+ m = Message(trunc=True)
+ m.addQuery('example.com')
+
+ def queryTCP(queries):
+ self.assertEqual(queries, m.queries)
+ response = Message()
+ response.answers = ['answer']
+ response.authority = ['authority']
+ response.additional = ['additional']
+ return succeed(response)
+ self.resolver.queryTCP = queryTCP
+ d = self.resolver.filterAnswers(m)
+ d.addCallback(
+ self.assertEqual, (['answer'], ['authority'], ['additional']))
+ return d
+
+
+ def _rcodeTest(self, rcode, exc):
+ m = Message(rCode=rcode)
+ err = self.resolver.filterAnswers(m)
+ err.trap(exc)
+
+
+ def test_formatError(self):
+ """
+ Test that a message with a result code of C{EFORMAT} results in a
+ failure wrapped around L{DNSFormatError}.
+ """
+ return self._rcodeTest(EFORMAT, DNSFormatError)
+
+
+ def test_serverError(self):
+ """
+ Like L{test_formatError} but for C{ESERVER}/L{DNSServerError}.
+ """
+ return self._rcodeTest(ESERVER, DNSServerError)
+
+
+ def test_nameError(self):
+ """
+ Like L{test_formatError} but for C{ENAME}/L{DNSNameError}.
+ """
+ return self._rcodeTest(ENAME, DNSNameError)
+
+
+ def test_notImplementedError(self):
+ """
+ Like L{test_formatError} but for C{ENOTIMP}/L{DNSNotImplementedError}.
+ """
+ return self._rcodeTest(ENOTIMP, DNSNotImplementedError)
+
+
+ def test_refusedError(self):
+ """
+ Like L{test_formatError} but for C{EREFUSED}/L{DNSQueryRefusedError}.
+ """
+ return self._rcodeTest(EREFUSED, DNSQueryRefusedError)
+
+
+ def test_refusedErrorUnknown(self):
+ """
+ Like L{test_formatError} but for an unrecognized error code and
+ L{DNSUnknownError}.
+ """
+ return self._rcodeTest(EREFUSED + 1, DNSUnknownError)
diff --git a/vendor/Twisted-10.0.0/twisted/names/test/test_rootresolve.py b/vendor/Twisted-10.0.0/twisted/names/test/test_rootresolve.py
new file mode 100644
index 0000000000..45c4499c91
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/test/test_rootresolve.py
@@ -0,0 +1,705 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for Twisted.names' root resolver.
+"""
+
+from random import randrange
+
+from zope.interface import implements
+from zope.interface.verify import verifyClass
+
+from twisted.python.log import msg
+from twisted.trial import util
+from twisted.trial.unittest import TestCase
+from twisted.internet.defer import Deferred, succeed, gatherResults
+from twisted.internet.task import Clock
+from twisted.internet.address import IPv4Address
+from twisted.internet.interfaces import IReactorUDP, IUDPTransport
+from twisted.names.root import Resolver, lookupNameservers, lookupAddress
+from twisted.names.root import extractAuthority, discoverAuthority, retry
+from twisted.names.dns import IN, HS, A, NS, CNAME, OK, ENAME, Record_CNAME
+from twisted.names.dns import Query, Message, RRHeader, Record_A, Record_NS
+from twisted.names.error import DNSNameError, ResolverError
+
+
+class MemoryDatagramTransport(object):
+ """
+ This L{IUDPTransport} implementation enforces the usual connection rules
+ and captures sent traffic in a list for later inspection.
+
+ @ivar _host: The host address to which this transport is bound.
+ @ivar _protocol: The protocol connected to this transport.
+ @ivar _sentPackets: A C{list} of two-tuples of the datagrams passed to
+ C{write} and the addresses to which they are destined.
+
+ @ivar _connectedTo: C{None} if this transport is unconnected, otherwise an
+ address to which all traffic is supposedly sent.
+
+ @ivar _maxPacketSize: An C{int} giving the maximum length of a datagram
+ which will be successfully handled by C{write}.
+ """
+ implements(IUDPTransport)
+
+ def __init__(self, host, protocol, maxPacketSize):
+ self._host = host
+ self._protocol = protocol
+ self._sentPackets = []
+ self._connectedTo = None
+ self._maxPacketSize = maxPacketSize
+
+
+ def getHost(self):
+ """
+ Return the address which this transport is pretending to be bound
+ to.
+ """
+ return IPv4Address('UDP', *self._host)
+
+
+ def connect(self, host, port):
+ """
+ Connect this transport to the given address.
+ """
+ if self._connectedTo is not None:
+ raise ValueError("Already connected")
+ self._connectedTo = (host, port)
+
+
+ def write(self, datagram, addr=None):
+ """
+ Send the given datagram.
+ """
+ if addr is None:
+ addr = self._connectedTo
+ if addr is None:
+ raise ValueError("Need an address")
+ if len(datagram) > self._maxPacketSize:
+ raise ValueError("Packet too big")
+ self._sentPackets.append((datagram, addr))
+
+
+ def stopListening(self):
+ """
+ Shut down this transport.
+ """
+ self._protocol.stopProtocol()
+ return succeed(None)
+
+verifyClass(IUDPTransport, MemoryDatagramTransport)
+
+
+
+class MemoryReactor(Clock):
+ """
+ An L{IReactorTime} and L{IReactorUDP} provider.
+
+ Time is controlled deterministically via the base class, L{Clock}. UDP is
+ handled in-memory by connecting protocols to instances of
+ L{MemoryDatagramTransport}.
+
+ @ivar udpPorts: A C{dict} mapping port numbers to instances of
+ L{MemoryDatagramTransport}.
+ """
+ implements(IReactorUDP)
+
+ def __init__(self):
+ Clock.__init__(self)
+ self.udpPorts = {}
+
+
+ def listenUDP(self, port, protocol, interface='', maxPacketSize=8192):
+ """
+ Pretend to bind a UDP port and connect the given protocol to it.
+ """
+ if port == 0:
+ while True:
+ port = randrange(1, 2 ** 16)
+ if port not in self.udpPorts:
+ break
+ if port in self.udpPorts:
+ raise ValueError("Address in use")
+ transport = MemoryDatagramTransport(
+ (interface, port), protocol, maxPacketSize)
+ self.udpPorts[port] = transport
+ protocol.makeConnection(transport)
+ return transport
+
+verifyClass(IReactorUDP, MemoryReactor)
+
+
+
+class RootResolverTests(TestCase):
+ """
+ Tests for L{twisted.names.root.Resolver}.
+ """
+ def _queryTest(self, filter):
+ """
+ Invoke L{Resolver._query} and verify that it sends the correct DNS
+ query. Deliver a canned response to the query and return whatever the
+ L{Deferred} returned by L{Resolver._query} fires with.
+
+ @param filter: The value to pass for the C{filter} parameter to
+ L{Resolver._query}.
+ """
+ reactor = MemoryReactor()
+ resolver = Resolver([], reactor=reactor)
+ d = resolver._query(
+ Query('foo.example.com', A, IN), [('1.1.2.3', 1053)], (30,),
+ filter)
+
+ # A UDP port should have been started.
+ portNumber, transport = reactor.udpPorts.popitem()
+
+ # And a DNS packet sent.
+ [(packet, address)] = transport._sentPackets
+
+ msg = Message()
+ msg.fromStr(packet)
+
+ # It should be a query with the parameters used above.
+ self.assertEquals(msg.queries, [Query('foo.example.com', A, IN)])
+ self.assertEquals(msg.answers, [])
+ self.assertEquals(msg.authority, [])
+ self.assertEquals(msg.additional, [])
+
+ response = []
+ d.addCallback(response.append)
+ self.assertEquals(response, [])
+
+ # Once a reply is received, the Deferred should fire.
+ del msg.queries[:]
+ msg.answer = 1
+ msg.answers.append(RRHeader('foo.example.com', payload=Record_A('5.8.13.21')))
+ transport._protocol.datagramReceived(msg.toStr(), ('1.1.2.3', 1053))
+ return response[0]
+
+
+ def test_filteredQuery(self):
+ """
+ L{Resolver._query} accepts a L{Query} instance and an address, issues
+ the query, and returns a L{Deferred} which fires with the response to
+ the query. If a true value is passed for the C{filter} parameter, the
+ result is a three-tuple of lists of records.
+ """
+ answer, authority, additional = self._queryTest(True)
+ self.assertEquals(
+ answer,
+ [RRHeader('foo.example.com', payload=Record_A('5.8.13.21', ttl=0))])
+ self.assertEquals(authority, [])
+ self.assertEquals(additional, [])
+
+
+ def test_unfilteredQuery(self):
+ """
+ Similar to L{test_filteredQuery}, but for the case where a false value
+ is passed for the C{filter} parameter. In this case, the result is a
+ L{Message} instance.
+ """
+ message = self._queryTest(False)
+ self.assertIsInstance(message, Message)
+ self.assertEquals(message.queries, [])
+ self.assertEquals(
+ message.answers,
+ [RRHeader('foo.example.com', payload=Record_A('5.8.13.21', ttl=0))])
+ self.assertEquals(message.authority, [])
+ self.assertEquals(message.additional, [])
+
+
+ def _respond(self, answers=[], authority=[], additional=[], rCode=OK):
+ """
+ Create a L{Message} suitable for use as a response to a query.
+
+ @param answers: A C{list} of two-tuples giving data for the answers
+ section of the message. The first element of each tuple is a name
+ for the L{RRHeader}. The second element is the payload.
+ @param authority: A C{list} like C{answers}, but for the authority
+ section of the response.
+ @param additional: A C{list} like C{answers}, but for the
+ additional section of the response.
+ @param rCode: The response code the message will be created with.
+
+ @return: A new L{Message} initialized with the given values.
+ """
+ response = Message(rCode=rCode)
+ for (section, data) in [(response.answers, answers),
+ (response.authority, authority),
+ (response.additional, additional)]:
+ section.extend([
+ RRHeader(name, record.TYPE, getattr(record, 'CLASS', IN),
+ payload=record)
+ for (name, record) in data])
+ return response
+
+
+ def _getResolver(self, serverResponses, maximumQueries=10):
+ """
+ Create and return a new L{root.Resolver} modified to resolve queries
+ against the record data represented by C{servers}.
+
+ @param serverResponses: A mapping from dns server addresses to
+ mappings. The inner mappings are from query two-tuples (name,
+ type) to dictionaries suitable for use as **arguments to
+ L{_respond}. See that method for details.
+ """
+ roots = ['1.1.2.3']
+ resolver = Resolver(roots, maximumQueries)
+
+ def query(query, serverAddresses, timeout, filter):
+ msg("Query for QNAME %s at %r" % (query.name, serverAddresses))
+ for addr in serverAddresses:
+ try:
+ server = serverResponses[addr]
+ except KeyError:
+ continue
+ records = server[str(query.name), query.type]
+ return succeed(self._respond(**records))
+ resolver._query = query
+ return resolver
+
+
+ def test_lookupAddress(self):
+ """
+ L{root.Resolver.lookupAddress} looks up the I{A} records for the
+ specified hostname by first querying one of the root servers the
+ resolver was created with and then following the authority delegations
+ until a result is received.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ ('foo.example.com', A): {
+ 'authority': [('foo.example.com', Record_NS('ns1.example.com'))],
+ 'additional': [('ns1.example.com', Record_A('34.55.89.144'))],
+ },
+ },
+ ('34.55.89.144', 53): {
+ ('foo.example.com', A): {
+ 'answers': [('foo.example.com', Record_A('10.0.0.1'))],
+ }
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupAddress('foo.example.com')
+ d.addCallback(lambda (ans, auth, add): ans[0].payload.dottedQuad())
+ d.addCallback(self.assertEquals, '10.0.0.1')
+ return d
+
+
+ def test_lookupChecksClass(self):
+ """
+ If a response includes a record with a class different from the one
+ in the query, it is ignored and lookup continues until a record with
+ the right class is found.
+ """
+ badClass = Record_A('10.0.0.1')
+ badClass.CLASS = HS
+ servers = {
+ ('1.1.2.3', 53): {
+ ('foo.example.com', A): {
+ 'answers': [('foo.example.com', badClass)],
+ 'authority': [('foo.example.com', Record_NS('ns1.example.com'))],
+ 'additional': [('ns1.example.com', Record_A('10.0.0.2'))],
+ },
+ },
+ ('10.0.0.2', 53): {
+ ('foo.example.com', A): {
+ 'answers': [('foo.example.com', Record_A('10.0.0.3'))],
+ },
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupAddress('foo.example.com')
+ d.addCallback(lambda (ans, auth, add): ans[0].payload)
+ d.addCallback(self.assertEquals, Record_A('10.0.0.3'))
+ return d
+
+
+ def test_missingGlue(self):
+ """
+ If an intermediate response includes no glue records for the
+ authorities, separate queries are made to find those addresses.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ ('foo.example.com', A): {
+ 'authority': [('foo.example.com', Record_NS('ns1.example.org'))],
+ # Conspicuous lack of an additional section naming ns1.example.com
+ },
+ ('ns1.example.org', A): {
+ 'answers': [('ns1.example.org', Record_A('10.0.0.1'))],
+ },
+ },
+ ('10.0.0.1', 53): {
+ ('foo.example.com', A): {
+ 'answers': [('foo.example.com', Record_A('10.0.0.2'))],
+ },
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupAddress('foo.example.com')
+ d.addCallback(lambda (ans, auth, add): ans[0].payload.dottedQuad())
+ d.addCallback(self.assertEquals, '10.0.0.2')
+ return d
+
+
+ def test_missingName(self):
+ """
+ If a name is missing, L{Resolver.lookupAddress} returns a L{Deferred}
+ which fails with L{DNSNameError}.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ ('foo.example.com', A): {
+ 'rCode': ENAME,
+ },
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupAddress('foo.example.com')
+ return self.assertFailure(d, DNSNameError)
+
+
+ def test_answerless(self):
+ """
+ If a query is responded to with no answers or nameserver records, the
+ L{Deferred} returned by L{Resolver.lookupAddress} fires with
+ L{ResolverError}.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ ('example.com', A): {
+ },
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupAddress('example.com')
+ return self.assertFailure(d, ResolverError)
+
+
+ def test_delegationLookupError(self):
+ """
+ If there is an error resolving the nameserver in a delegation response,
+ the L{Deferred} returned by L{Resolver.lookupAddress} fires with that
+ error.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ ('example.com', A): {
+ 'authority': [('example.com', Record_NS('ns1.example.com'))],
+ },
+ ('ns1.example.com', A): {
+ 'rCode': ENAME,
+ },
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupAddress('example.com')
+ return self.assertFailure(d, DNSNameError)
+
+
+ def test_delegationLookupEmpty(self):
+ """
+ If there are no records in the response to a lookup of a delegation
+ nameserver, the L{Deferred} returned by L{Resolver.lookupAddress} fires
+ with L{ResolverError}.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ ('example.com', A): {
+ 'authority': [('example.com', Record_NS('ns1.example.com'))],
+ },
+ ('ns1.example.com', A): {
+ },
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupAddress('example.com')
+ return self.assertFailure(d, ResolverError)
+
+
+ def test_lookupNameservers(self):
+ """
+ L{Resolver.lookupNameservers} is like L{Resolver.lookupAddress}, except
+ it queries for I{NS} records instead of I{A} records.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ ('example.com', A): {
+ 'rCode': ENAME,
+ },
+ ('example.com', NS): {
+ 'answers': [('example.com', Record_NS('ns1.example.com'))],
+ },
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupNameservers('example.com')
+ d.addCallback(lambda (ans, auth, add): str(ans[0].payload.name))
+ d.addCallback(self.assertEquals, 'ns1.example.com')
+ return d
+
+
+ def test_returnCanonicalName(self):
+ """
+ If a I{CNAME} record is encountered as the answer to a query for
+ another record type, that record is returned as the answer.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ ('example.com', A): {
+ 'answers': [('example.com', Record_CNAME('example.net')),
+ ('example.net', Record_A('10.0.0.7'))],
+ },
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupAddress('example.com')
+ d.addCallback(lambda (ans, auth, add): ans)
+ d.addCallback(
+ self.assertEquals,
+ [RRHeader('example.com', CNAME, payload=Record_CNAME('example.net')),
+ RRHeader('example.net', A, payload=Record_A('10.0.0.7'))])
+ return d
+
+
+ def test_followCanonicalName(self):
+ """
+ If no record of the requested type is included in a response, but a
+ I{CNAME} record for the query name is included, queries are made to
+ resolve the value of the I{CNAME}.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ ('example.com', A): {
+ 'answers': [('example.com', Record_CNAME('example.net'))],
+ },
+ ('example.net', A): {
+ 'answers': [('example.net', Record_A('10.0.0.5'))],
+ },
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupAddress('example.com')
+ d.addCallback(lambda (ans, auth, add): ans)
+ d.addCallback(
+ self.assertEquals,
+ [RRHeader('example.com', CNAME, payload=Record_CNAME('example.net')),
+ RRHeader('example.net', A, payload=Record_A('10.0.0.5'))])
+ return d
+
+
+ def test_detectCanonicalNameLoop(self):
+ """
+ If there is a cycle between I{CNAME} records in a response, this is
+ detected and the L{Deferred} returned by the lookup method fails
+ with L{ResolverError}.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ ('example.com', A): {
+ 'answers': [('example.com', Record_CNAME('example.net')),
+ ('example.net', Record_CNAME('example.com'))],
+ },
+ },
+ }
+ resolver = self._getResolver(servers)
+ d = resolver.lookupAddress('example.com')
+ return self.assertFailure(d, ResolverError)
+
+
+ def test_boundedQueries(self):
+ """
+ L{Resolver.lookupAddress} won't issue more queries following
+ delegations than the limit passed to its initializer.
+ """
+ servers = {
+ ('1.1.2.3', 53): {
+ # First query - force it to start over with a name lookup of
+ # ns1.example.com
+ ('example.com', A): {
+ 'authority': [('example.com', Record_NS('ns1.example.com'))],
+ },
+ # Second query - let it resume the original lookup with the
+ # address of the nameserver handling the delegation.
+ ('ns1.example.com', A): {
+ 'answers': [('ns1.example.com', Record_A('10.0.0.2'))],
+ },
+ },
+ ('10.0.0.2', 53): {
+ # Third query - let it jump straight to asking the
+ # delegation server by including its address here (different
+ # case from the first query).
+ ('example.com', A): {
+ 'authority': [('example.com', Record_NS('ns2.example.com'))],
+ 'additional': [('ns2.example.com', Record_A('10.0.0.3'))],
+ },
+ },
+ ('10.0.0.3', 53): {
+ # Fourth query - give it the answer, we're done.
+ ('example.com', A): {
+ 'answers': [('example.com', Record_A('10.0.0.4'))],
+ },
+ },
+ }
+
+ # Make two resolvers. One which is allowed to make 3 queries
+ # maximum, and so will fail, and on which may make 4, and so should
+ # succeed.
+ failer = self._getResolver(servers, 3)
+ failD = self.assertFailure(
+ failer.lookupAddress('example.com'), ResolverError)
+
+ succeeder = self._getResolver(servers, 4)
+ succeedD = succeeder.lookupAddress('example.com')
+ succeedD.addCallback(lambda (ans, auth, add): ans[0].payload)
+ succeedD.addCallback(self.assertEquals, Record_A('10.0.0.4'))
+
+ return gatherResults([failD, succeedD])
+
+
+ def test_discoveredAuthorityDeprecated(self):
+ """
+ Calling L{Resolver.discoveredAuthority} produces a deprecation warning.
+ """
+ resolver = Resolver([])
+ d = resolver.discoveredAuthority('127.0.0.1', 'example.com', IN, A, (0,))
+
+ warnings = self.flushWarnings([
+ self.test_discoveredAuthorityDeprecated])
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warnings[0]['message'],
+ 'twisted.names.root.Resolver.discoveredAuthority is deprecated since '
+ 'Twisted 10.0. Use twisted.names.client.Resolver directly, instead.')
+ self.assertEquals(len(warnings), 1)
+
+ # This will time out quickly, but we need to wait for it because there
+ # are resources associated with.
+ d.addErrback(lambda ignored: None)
+ return d
+
+
+
+class StubDNSDatagramProtocol:
+ """
+ A do-nothing stand-in for L{DNSDatagramProtocol} which can be used to avoid
+ network traffic in tests where that kind of thing doesn't matter.
+ """
+ def query(self, *a, **kw):
+ return Deferred()
+
+
+
+_retrySuppression = util.suppress(
+ category=DeprecationWarning,
+ message=(
+ 'twisted.names.root.retry is deprecated since Twisted 10.0. Use a '
+ 'Resolver object for retry logic.'))
+
+
+class DiscoveryToolsTests(TestCase):
+ """
+ Tests for the free functions in L{twisted.names.root} which help out with
+ authority discovery. Since these are mostly deprecated, these are mostly
+ deprecation tests.
+ """
+ def test_lookupNameserversDeprecated(self):
+ """
+ Calling L{root.lookupNameservers} produces a deprecation warning.
+ """
+ # Don't care about the return value, since it will never have a result,
+ # since StubDNSDatagramProtocol doesn't actually work.
+ lookupNameservers('example.com', '127.0.0.1', StubDNSDatagramProtocol())
+
+ warnings = self.flushWarnings([
+ self.test_lookupNameserversDeprecated])
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warnings[0]['message'],
+ 'twisted.names.root.lookupNameservers is deprecated since Twisted '
+ '10.0. Use twisted.names.root.Resolver.lookupNameservers '
+ 'instead.')
+ self.assertEquals(len(warnings), 1)
+ test_lookupNameserversDeprecated.suppress = [_retrySuppression]
+
+
+ def test_lookupAddressDeprecated(self):
+ """
+ Calling L{root.lookupAddress} produces a deprecation warning.
+ """
+ # Don't care about the return value, since it will never have a result,
+ # since StubDNSDatagramProtocol doesn't actually work.
+ lookupAddress('example.com', '127.0.0.1', StubDNSDatagramProtocol())
+
+ warnings = self.flushWarnings([
+ self.test_lookupAddressDeprecated])
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warnings[0]['message'],
+ 'twisted.names.root.lookupAddress is deprecated since Twisted '
+ '10.0. Use twisted.names.root.Resolver.lookupAddress '
+ 'instead.')
+ self.assertEquals(len(warnings), 1)
+ test_lookupAddressDeprecated.suppress = [_retrySuppression]
+
+
+ def test_extractAuthorityDeprecated(self):
+ """
+ Calling L{root.extractAuthority} produces a deprecation warning.
+ """
+ extractAuthority(Message(), {})
+
+ warnings = self.flushWarnings([
+ self.test_extractAuthorityDeprecated])
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warnings[0]['message'],
+ 'twisted.names.root.extractAuthority is deprecated since Twisted '
+ '10.0. Please inspect the Message object directly.')
+ self.assertEquals(len(warnings), 1)
+
+
+ def test_discoverAuthorityDeprecated(self):
+ """
+ Calling L{root.discoverAuthority} produces a deprecation warning.
+ """
+ discoverAuthority(
+ 'example.com', ['10.0.0.1'], p=StubDNSDatagramProtocol())
+
+ warnings = self.flushWarnings([
+ self.test_discoverAuthorityDeprecated])
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warnings[0]['message'],
+ 'twisted.names.root.discoverAuthority is deprecated since Twisted '
+ '10.0. Use twisted.names.root.Resolver.lookupNameservers '
+ 'instead.')
+ self.assertEquals(len(warnings), 1)
+
+ # discoverAuthority is implemented in terms of deprecated functions,
+ # too. Ignore those.
+ test_discoverAuthorityDeprecated.suppress = [
+ util.suppress(
+ category=DeprecationWarning,
+ message=(
+ 'twisted.names.root.lookupNameservers is deprecated since '
+ 'Twisted 10.0. Use '
+ 'twisted.names.root.Resolver.lookupNameservers instead.')),
+ _retrySuppression]
+
+
+ def test_retryDeprecated(self):
+ """
+ Calling L{root.retry} produces a deprecation warning.
+ """
+ retry([0], StubDNSDatagramProtocol())
+
+ warnings = self.flushWarnings([
+ self.test_retryDeprecated])
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warnings[0]['message'],
+ 'twisted.names.root.retry is deprecated since Twisted '
+ '10.0. Use a Resolver object for retry logic.')
+ self.assertEquals(len(warnings), 1)
diff --git a/vendor/Twisted-10.0.0/twisted/names/test/test_srvconnect.py b/vendor/Twisted-10.0.0/twisted/names/test/test_srvconnect.py
new file mode 100644
index 0000000000..4733a7451d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/test/test_srvconnect.py
@@ -0,0 +1,133 @@
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for L{twisted.names.srvconnect}.
+"""
+
+from twisted.internet import defer, protocol
+from twisted.names import client, dns, srvconnect
+from twisted.names.common import ResolverBase
+from twisted.names.error import DNSNameError
+from twisted.internet.error import DNSLookupError
+from twisted.trial import unittest
+from twisted.test.proto_helpers import MemoryReactor
+
+
+class FakeResolver(ResolverBase):
+ """
+ Resolver that only gives out one given result.
+
+ Either L{results} or L{failure} must be set and will be used for
+ the return value of L{_lookup}
+
+ @ivar results: List of L{dns.RRHeader} for the desired result.
+ @type results: C{list}
+ @ivar failure: Failure with an exception from L{twisted.names.error}.
+ @type failure: L{Failure<twisted.python.failure.Failure>}
+ """
+
+ def __init__(self, results=None, failure=None):
+ self.results = results
+ self.failure = failure
+
+ def _lookup(self, name, cls, qtype, timeout):
+ """
+ Return the result or failure on lookup.
+ """
+ if self.results is not None:
+ return defer.succeed((self.results, [], []))
+ else:
+ return defer.fail(self.failure)
+
+
+
+class DummyFactory(protocol.ClientFactory):
+ """
+ Dummy client factory that stores the reason of connection failure.
+ """
+ def __init__(self):
+ self.reason = None
+
+ def clientConnectionFailed(self, connector, reason):
+ self.reason = reason
+
+class SRVConnectorTest(unittest.TestCase):
+
+ def setUp(self):
+ self.patch(client, 'theResolver', FakeResolver())
+ self.reactor = MemoryReactor()
+ self.factory = DummyFactory()
+ self.connector = srvconnect.SRVConnector(self.reactor, 'xmpp-server',
+ 'example.org', self.factory)
+
+
+ def test_SRVPresent(self):
+ """
+ Test connectTCP gets called with the address from the SRV record.
+ """
+ payload = dns.Record_SRV(port=6269, target='host.example.org', ttl=60)
+ client.theResolver.results = [dns.RRHeader(name='example.org',
+ type=dns.SRV,
+ cls=dns.IN, ttl=60,
+ payload=payload)]
+ self.connector.connect()
+
+ self.assertIdentical(None, self.factory.reason)
+ self.assertEquals(
+ self.reactor.tcpClients.pop()[:2], ('host.example.org', 6269))
+
+
+ def test_SRVNotPresent(self):
+ """
+ Test connectTCP gets called with fallback parameters on NXDOMAIN.
+ """
+ client.theResolver.failure = DNSNameError('example.org')
+ self.connector.connect()
+
+ self.assertIdentical(None, self.factory.reason)
+ self.assertEquals(
+ self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server'))
+
+
+ def test_SRVNoResult(self):
+ """
+ Test connectTCP gets called with fallback parameters on empty result.
+ """
+ client.theResolver.results = []
+ self.connector.connect()
+
+ self.assertIdentical(None, self.factory.reason)
+ self.assertEquals(
+ self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server'))
+
+
+ def test_SRVBadResult(self):
+ """
+ Test connectTCP gets called with fallback parameters on bad result.
+ """
+ client.theResolver.results = [dns.RRHeader(name='example.org',
+ type=dns.CNAME,
+ cls=dns.IN, ttl=60,
+ payload=None)]
+ self.connector.connect()
+
+ self.assertIdentical(None, self.factory.reason)
+ self.assertEquals(
+ self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server'))
+
+
+ def test_SRVNoService(self):
+ """
+ Test that connecting fails when no service is present.
+ """
+ payload = dns.Record_SRV(port=5269, target='.', ttl=60)
+ client.theResolver.results = [dns.RRHeader(name='example.org',
+ type=dns.SRV,
+ cls=dns.IN, ttl=60,
+ payload=payload)]
+ self.connector.connect()
+
+ self.assertNotIdentical(None, self.factory.reason)
+ self.factory.reason.trap(DNSLookupError)
+ self.assertEquals(self.reactor.tcpClients, [])
diff --git a/vendor/Twisted-10.0.0/twisted/names/topfiles/NEWS b/vendor/Twisted-10.0.0/twisted/names/topfiles/NEWS
new file mode 100644
index 0000000000..2efdee8059
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/topfiles/NEWS
@@ -0,0 +1,131 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/<number>
+
+Twisted Names 10.0.0 (2010-03-01)
+=================================
+
+Bugfixes
+--------
+ - twisted.names.root.Resolver no longer leaks UDP sockets while
+ resolving names. (#970)
+
+Deprecations and Removals
+-------------------------
+ - Several top-level functions in twisted.names.root are now
+ deprecated. (#970)
+
+Other
+-----
+ - #4066
+
+
+Twisted Names 9.0.0 (2009-11-24)
+================================
+
+Deprecations and Removals
+-------------------------
+ - client.ThreadedResolver is deprecated in favor of
+ twisted.internet.base.ThreadedResolver (#3710)
+
+Other
+-----
+ - #3540, #3560, #3712, #3750, #3990
+
+
+Names 8.2.0 (2008-12-16)
+========================
+
+Features
+--------
+ - The NAPTR record type is now supported (#2276)
+
+Fixes
+-----
+ - Make client.Resolver less vulnerable to the Birthday Paradox attack by
+ avoiding sending duplicate queries when it's not necessary (#3347)
+ - client.Resolver now uses a random source port for each DNS request (#3342)
+ - client.Resolver now uses a full 16 bits of randomness for message IDs,
+ instead of 10 which it previously used (#3342)
+ - All record types now have value-based equality and a string representation
+ (#2935)
+
+Other
+-----
+ - #1622, #3424
+
+
+8.1.0 (2008-05-18)
+==================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+8.0.0 (2008-03-17)
+==================
+
+Fixes
+-----
+
+ - Refactor DNSDatagramProtocol and DNSProtocol to use same base class (#2414)
+ - Change Resolver to query specified nameservers in specified order, instead
+ of reverse order. (#2290)
+ - Make SRVConnector work with bad results and NXDOMAIN responses.
+ (#1908, #2777)
+ - Handle write errors happening in dns queries, to have correct deferred
+ failures. (#2492)
+ - Fix the value of OP_NOTIFY and add a definition for OP_UPDATE. (#2945)
+
+Misc
+----
+ - #2685, #2936, #2581, #2847
+
+
+0.4.0 (2007-01-06)
+==================
+
+Features
+--------
+
+ - In the twisted.names client, DNS responses which represent errors
+ are now translated to informative exception objects, rather than
+ empty lists. This means that client requests which fail will now
+ errback their Deferreds (#2248)
+
+Fixes
+-----
+ - A major DoS vulnerability in the UDP DNS server was fixed (#1708)
+
+Misc
+----
+ - #1799, #1636, #2149, #2181
+
+
+0.3.0 (2006-05-21)
+==================
+
+Features
+--------
+ - Some docstring improvements
+
+Fixes
+-----
+ - Fix a problem where the response for the first query with a
+ newly-created Resolver object would be dropped.(#1447)
+ - Misc: #1581, #1583
+
+
+0.2.0
+=====
+ - Fix occassional TCP connection leak in gethostbyname()
+ - Fix TCP connection leak in recursive lookups
+ - Remove deprecated use of Deferred.setTimeout
+ - Improved test coverage for zone transfers
+
+0.1.0
+=====
+ - Fix TCP connection leak in zone transfers
+ - Handle empty or missing resolv.conf as if 127.0.0.1 was specified
+ - Don't use blocking kernel entropy sources
+ - Retry logic now properly tries all specified servers.
diff --git a/vendor/Twisted-10.0.0/twisted/names/topfiles/README b/vendor/Twisted-10.0.0/twisted/names/topfiles/README
new file mode 100644
index 0000000000..553f803b49
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/topfiles/README
@@ -0,0 +1,3 @@
+Twisted Names 10.0.0
+
+Twisted Names depends on Twisted Core.
diff --git a/vendor/Twisted-10.0.0/twisted/names/topfiles/setup.py b/vendor/Twisted-10.0.0/twisted/names/topfiles/setup.py
new file mode 100644
index 0000000000..164986d233
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/names/topfiles/setup.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+
+try:
+ from twisted.python import dist
+except ImportError:
+ raise SystemExit("twisted.python.dist module not found. Make sure you "
+ "have installed the Twisted core package before "
+ "attempting to install any other Twisted projects.")
+
+if __name__ == '__main__':
+ if sys.version_info[:2] >= (2, 4):
+ extraMeta = dict(
+ classifiers=[
+ "Development Status :: 4 - Beta",
+ "Environment :: No Input/Output (Daemon)",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python",
+ "Topic :: Internet :: Name Service (DNS)",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ ])
+ else:
+ extraMeta = {}
+
+ dist.setup(
+ twisted_subproject="names",
+ # metadata
+ name="Twisted Names",
+ description="A Twisted DNS implementation.",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="Jp Calderone",
+ url="http://twistedmatrix.com/trac/wiki/TwistedNames",
+ license="MIT",
+ long_description="""\
+Twisted Names is both a domain name server as well as a client
+resolver library. Twisted Names comes with an "out of the box"
+nameserver which can read most BIND-syntax zone files as well as a
+simple Python-based configuration format. Twisted Names can act as an
+authoritative server, perform zone transfers from a master to act as a
+secondary, act as a caching nameserver, or any combination of
+these. Twisted Names' client resolver library provides functions to
+query for all commonly used record types as well as a replacement for
+the blocking gethostbyname() function provided by the Python stdlib
+socket module.
+""",
+ **extraMeta)
diff --git a/vendor/Twisted-10.0.0/twisted/news/__init__.py b/vendor/Twisted-10.0.0/twisted/news/__init__.py
new file mode 100644
index 0000000000..78cb8483c0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+
+Twisted News: an NNTP-based news service.
+
+"""
+
+from twisted.news._version import version
+__version__ = version.short()
diff --git a/vendor/Twisted-10.0.0/twisted/news/_version.py b/vendor/Twisted-10.0.0/twisted/news/_version.py
new file mode 100644
index 0000000000..85b5afdde7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/_version.py
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twisted.news', 10, 0, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/news/database.py b/vendor/Twisted-10.0.0/twisted/news/database.py
new file mode 100644
index 0000000000..69f7627e69
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/database.py
@@ -0,0 +1,998 @@
+# -*- test-case-name: twisted.news.test.test_news -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+News server backend implementations
+
+Maintainer: Jp Calderone
+
+Future Plans: A PyFramer-based backend and a new backend interface that is
+less NNTP specific
+"""
+
+
+import getpass, pickle, time, socket
+import os
+import StringIO
+
+from zope.interface import implements, Interface
+
+from twisted.news.nntp import NNTPError
+from twisted.mail import smtp
+from twisted.internet import defer
+from twisted.enterprise import adbapi
+from twisted.persisted import dirdbm
+from twisted.python.hashlib import md5
+
+
+
+ERR_NOGROUP, ERR_NOARTICLE = range(2, 4) # XXX - put NNTP values here (I guess?)
+
+OVERVIEW_FMT = [
+ 'Subject', 'From', 'Date', 'Message-ID', 'References',
+ 'Bytes', 'Lines', 'Xref'
+]
+
+def hexdigest(md5): #XXX: argh. 1.5.2 doesn't have this.
+ return ''.join(map(lambda x: hex(ord(x))[2:], md5.digest()))
+
+class Article:
+ def __init__(self, head, body):
+ self.body = body
+ self.headers = {}
+ header = None
+ for line in head.split('\r\n'):
+ if line[0] in ' \t':
+ i = list(self.headers[header])
+ i[1] += '\r\n' + line
+ else:
+ i = line.split(': ', 1)
+ header = i[0].lower()
+ self.headers[header] = tuple(i)
+
+ if not self.getHeader('Message-ID'):
+ s = str(time.time()) + self.body
+ id = hexdigest(md5(s)) + '@' + socket.gethostname()
+ self.putHeader('Message-ID', '<%s>' % id)
+
+ if not self.getHeader('Bytes'):
+ self.putHeader('Bytes', str(len(self.body)))
+
+ if not self.getHeader('Lines'):
+ self.putHeader('Lines', str(self.body.count('\n')))
+
+ if not self.getHeader('Date'):
+ self.putHeader('Date', time.ctime(time.time()))
+
+
+ def getHeader(self, header):
+ h = header.lower()
+ if self.headers.has_key(h):
+ return self.headers[h][1]
+ else:
+ return ''
+
+
+ def putHeader(self, header, value):
+ self.headers[header.lower()] = (header, value)
+
+
+ def textHeaders(self):
+ headers = []
+ for i in self.headers.values():
+ headers.append('%s: %s' % i)
+ return '\r\n'.join(headers) + '\r\n'
+
+ def overview(self):
+ xover = []
+ for i in OVERVIEW_FMT:
+ xover.append(self.getHeader(i))
+ return xover
+
+
+class NewsServerError(Exception):
+ pass
+
+
+class INewsStorage(Interface):
+ """
+ An interface for storing and requesting news articles
+ """
+
+ def listRequest():
+ """
+ Returns a deferred whose callback will be passed a list of 4-tuples
+ containing (name, max index, min index, flags) for each news group
+ """
+
+
+ def subscriptionRequest():
+ """
+ Returns a deferred whose callback will be passed the list of
+ recommended subscription groups for new server users
+ """
+
+
+ def postRequest(message):
+ """
+ Returns a deferred whose callback will be invoked if 'message'
+ is successfully posted to one or more specified groups and
+ whose errback will be invoked otherwise.
+ """
+
+
+ def overviewRequest():
+ """
+ Returns a deferred whose callback will be passed the a list of
+ headers describing this server's overview format.
+ """
+
+
+ def xoverRequest(group, low, high):
+ """
+ Returns a deferred whose callback will be passed a list of xover
+ headers for the given group over the given range. If low is None,
+ the range starts at the first article. If high is None, the range
+ ends at the last article.
+ """
+
+
+ def xhdrRequest(group, low, high, header):
+ """
+ Returns a deferred whose callback will be passed a list of XHDR data
+ for the given group over the given range. If low is None,
+ the range starts at the first article. If high is None, the range
+ ends at the last article.
+ """
+
+
+ def listGroupRequest(group):
+ """
+ Returns a deferred whose callback will be passed a two-tuple of
+ (group name, [article indices])
+ """
+
+
+ def groupRequest(group):
+ """
+ Returns a deferred whose callback will be passed a five-tuple of
+ (group name, article count, highest index, lowest index, group flags)
+ """
+
+
+ def articleExistsRequest(id):
+ """
+ Returns a deferred whose callback will be passed with a true value
+ if a message with the specified Message-ID exists in the database
+ and with a false value otherwise.
+ """
+
+
+ def articleRequest(group, index, id = None):
+ """
+ Returns a deferred whose callback will be passed a file-like object
+ containing the full article text (headers and body) for the article
+ of the specified index in the specified group, and whose errback
+ will be invoked if the article or group does not exist. If id is
+ not None, index is ignored and the article with the given Message-ID
+ will be returned instead, along with its index in the specified
+ group.
+ """
+
+
+ def headRequest(group, index):
+ """
+ Returns a deferred whose callback will be passed the header for
+ the article of the specified index in the specified group, and
+ whose errback will be invoked if the article or group does not
+ exist.
+ """
+
+
+ def bodyRequest(group, index):
+ """
+ Returns a deferred whose callback will be passed the body for
+ the article of the specified index in the specified group, and
+ whose errback will be invoked if the article or group does not
+ exist.
+ """
+
+class NewsStorage:
+ """
+ Backwards compatibility class -- There is no reason to inherit from this,
+ just implement INewsStorage instead.
+ """
+ def listRequest(self):
+ raise NotImplementedError()
+ def subscriptionRequest(self):
+ raise NotImplementedError()
+ def postRequest(self, message):
+ raise NotImplementedError()
+ def overviewRequest(self):
+ return defer.succeed(OVERVIEW_FMT)
+ def xoverRequest(self, group, low, high):
+ raise NotImplementedError()
+ def xhdrRequest(self, group, low, high, header):
+ raise NotImplementedError()
+ def listGroupRequest(self, group):
+ raise NotImplementedError()
+ def groupRequest(self, group):
+ raise NotImplementedError()
+ def articleExistsRequest(self, id):
+ raise NotImplementedError()
+ def articleRequest(self, group, index, id = None):
+ raise NotImplementedError()
+ def headRequest(self, group, index):
+ raise NotImplementedError()
+ def bodyRequest(self, group, index):
+ raise NotImplementedError()
+
+
+class PickleStorage:
+ """A trivial NewsStorage implementation using pickles
+
+ Contains numerous flaws and is generally unsuitable for any
+ real applications. Consider yourself warned!
+ """
+
+ implements(INewsStorage)
+
+ sharedDBs = {}
+
+ def __init__(self, filename, groups = None, moderators = ()):
+ self.datafile = filename
+ self.load(filename, groups, moderators)
+
+
+ def getModerators(self, groups):
+ # first see if any groups are moderated. if so, nothing gets posted,
+ # but the whole messages gets forwarded to the moderator address
+ moderators = []
+ for group in groups:
+ moderators.append(self.db['moderators'].get(group, None))
+ return filter(None, moderators)
+
+
+ def notifyModerators(self, moderators, article):
+ # Moderated postings go through as long as they have an Approved
+ # header, regardless of what the value is
+ article.putHeader('To', ', '.join(moderators))
+ return smtp.sendEmail(
+ 'twisted@' + socket.gethostname(),
+ moderators,
+ article.body,
+ dict(article.headers.values())
+ )
+
+
+ def listRequest(self):
+ "Returns a list of 4-tuples: (name, max index, min index, flags)"
+ l = self.db['groups']
+ r = []
+ for i in l:
+ if len(self.db[i].keys()):
+ low = min(self.db[i].keys())
+ high = max(self.db[i].keys()) + 1
+ else:
+ low = high = 0
+ if self.db['moderators'].has_key(i):
+ flags = 'm'
+ else:
+ flags = 'y'
+ r.append((i, high, low, flags))
+ return defer.succeed(r)
+
+ def subscriptionRequest(self):
+ return defer.succeed(['alt.test'])
+
+ def postRequest(self, message):
+ cleave = message.find('\r\n\r\n')
+ headers, article = message[:cleave], message[cleave + 4:]
+
+ a = Article(headers, article)
+ groups = a.getHeader('Newsgroups').split()
+ xref = []
+
+ # Check moderated status
+ moderators = self.getModerators(groups)
+ if moderators and not a.getHeader('Approved'):
+ return self.notifyModerators(moderators, a)
+
+ for group in groups:
+ if self.db.has_key(group):
+ if len(self.db[group].keys()):
+ index = max(self.db[group].keys()) + 1
+ else:
+ index = 1
+ xref.append((group, str(index)))
+ self.db[group][index] = a
+
+ if len(xref) == 0:
+ return defer.fail(None)
+
+ a.putHeader('Xref', '%s %s' % (
+ socket.gethostname().split()[0],
+ ''.join(map(lambda x: ':'.join(x), xref))
+ ))
+
+ self.flush()
+ return defer.succeed(None)
+
+
+ def overviewRequest(self):
+ return defer.succeed(OVERVIEW_FMT)
+
+
+ def xoverRequest(self, group, low, high):
+ if not self.db.has_key(group):
+ return defer.succeed([])
+ r = []
+ for i in self.db[group].keys():
+ if (low is None or i >= low) and (high is None or i <= high):
+ r.append([str(i)] + self.db[group][i].overview())
+ return defer.succeed(r)
+
+
+ def xhdrRequest(self, group, low, high, header):
+ if not self.db.has_key(group):
+ return defer.succeed([])
+ r = []
+ for i in self.db[group].keys():
+ if low is None or i >= low and high is None or i <= high:
+ r.append((i, self.db[group][i].getHeader(header)))
+ return defer.succeed(r)
+
+
+ def listGroupRequest(self, group):
+ if self.db.has_key(group):
+ return defer.succeed((group, self.db[group].keys()))
+ else:
+ return defer.fail(None)
+
+ def groupRequest(self, group):
+ if self.db.has_key(group):
+ if len(self.db[group].keys()):
+ num = len(self.db[group].keys())
+ low = min(self.db[group].keys())
+ high = max(self.db[group].keys())
+ else:
+ num = low = high = 0
+ flags = 'y'
+ return defer.succeed((group, num, high, low, flags))
+ else:
+ return defer.fail(ERR_NOGROUP)
+
+
+ def articleExistsRequest(self, id):
+ for g in self.db.values():
+ for a in g.values():
+ if a.getHeader('Message-ID') == id:
+ return defer.succeed(1)
+ return defer.succeed(0)
+
+
+ def articleRequest(self, group, index, id = None):
+ if id is not None:
+ raise NotImplementedError
+
+ if self.db.has_key(group):
+ if self.db[group].has_key(index):
+ a = self.db[group][index]
+ return defer.succeed((
+ index,
+ a.getHeader('Message-ID'),
+ StringIO.StringIO(a.textHeaders() + '\r\n' + a.body)
+ ))
+ else:
+ return defer.fail(ERR_NOARTICLE)
+ else:
+ return defer.fail(ERR_NOGROUP)
+
+
+ def headRequest(self, group, index):
+ if self.db.has_key(group):
+ if self.db[group].has_key(index):
+ a = self.db[group][index]
+ return defer.succeed((index, a.getHeader('Message-ID'), a.textHeaders()))
+ else:
+ return defer.fail(ERR_NOARTICLE)
+ else:
+ return defer.fail(ERR_NOGROUP)
+
+
+ def bodyRequest(self, group, index):
+ if self.db.has_key(group):
+ if self.db[group].has_key(index):
+ a = self.db[group][index]
+ return defer.succeed((index, a.getHeader('Message-ID'), StringIO.StringIO(a.body)))
+ else:
+ return defer.fail(ERR_NOARTICLE)
+ else:
+ return defer.fail(ERR_NOGROUP)
+
+
+ def flush(self):
+ f = open(self.datafile, 'w')
+ pickle.dump(self.db, f)
+ f.close()
+
+
+ def load(self, filename, groups = None, moderators = ()):
+ if PickleStorage.sharedDBs.has_key(filename):
+ self.db = PickleStorage.sharedDBs[filename]
+ else:
+ try:
+ self.db = pickle.load(open(filename))
+ PickleStorage.sharedDBs[filename] = self.db
+ except IOError, e:
+ self.db = PickleStorage.sharedDBs[filename] = {}
+ self.db['groups'] = groups
+ if groups is not None:
+ for i in groups:
+ self.db[i] = {}
+ self.db['moderators'] = dict(moderators)
+ self.flush()
+
+
+class Group:
+ name = None
+ flags = ''
+ minArticle = 1
+ maxArticle = 0
+ articles = None
+
+ def __init__(self, name, flags = 'y'):
+ self.name = name
+ self.flags = flags
+ self.articles = {}
+
+
+class NewsShelf:
+ """
+ A NewStorage implementation using Twisted's dirdbm persistence module.
+ """
+
+ implements(INewsStorage)
+
+ def __init__(self, mailhost, path):
+ self.path = path
+ self.mailhost = mailhost
+
+ if not os.path.exists(path):
+ os.mkdir(path)
+
+ self.dbm = dirdbm.Shelf(os.path.join(path, "newsshelf"))
+ if not len(self.dbm.keys()):
+ self.initialize()
+
+
+ def initialize(self):
+ # A dictionary of group name/Group instance items
+ self.dbm['groups'] = dirdbm.Shelf(os.path.join(self.path, 'groups'))
+
+ # A dictionary of group name/email address
+ self.dbm['moderators'] = dirdbm.Shelf(os.path.join(self.path, 'moderators'))
+
+ # A list of group names
+ self.dbm['subscriptions'] = []
+
+ # A dictionary of MessageID strings/xref lists
+ self.dbm['Message-IDs'] = dirdbm.Shelf(os.path.join(self.path, 'Message-IDs'))
+
+
+ def addGroup(self, name, flags):
+ self.dbm['groups'][name] = Group(name, flags)
+
+
+ def addSubscription(self, name):
+ self.dbm['subscriptions'] = self.dbm['subscriptions'] + [name]
+
+
+ def addModerator(self, group, email):
+ self.dbm['moderators'][group] = email
+
+
+ def listRequest(self):
+ result = []
+ for g in self.dbm['groups'].values():
+ result.append((g.name, g.maxArticle, g.minArticle, g.flags))
+ return defer.succeed(result)
+
+
+ def subscriptionRequest(self):
+ return defer.succeed(self.dbm['subscriptions'])
+
+
+ def getModerator(self, groups):
+ # first see if any groups are moderated. if so, nothing gets posted,
+ # but the whole messages gets forwarded to the moderator address
+ for group in groups:
+ try:
+ return self.dbm['moderators'][group]
+ except KeyError:
+ pass
+ return None
+
+
+ def notifyModerator(self, moderator, article):
+ # Moderated postings go through as long as they have an Approved
+ # header, regardless of what the value is
+ print 'To is ', moderator
+ article.putHeader('To', moderator)
+ return smtp.sendEmail(
+ self.mailhost,
+ 'twisted-news@' + socket.gethostname(),
+ moderator,
+ article.body,
+ dict(article.headers.values())
+ )
+
+
+ def postRequest(self, message):
+ cleave = message.find('\r\n\r\n')
+ headers, article = message[:cleave], message[cleave + 4:]
+
+ article = Article(headers, article)
+ groups = article.getHeader('Newsgroups').split()
+ xref = []
+
+ # Check for moderated status
+ moderator = self.getModerator(groups)
+ if moderator and not article.getHeader('Approved'):
+ return self.notifyModerator(moderator, article)
+
+
+ for group in groups:
+ try:
+ g = self.dbm['groups'][group]
+ except KeyError:
+ pass
+ else:
+ index = g.maxArticle + 1
+ g.maxArticle += 1
+ g.articles[index] = article
+ xref.append((group, str(index)))
+ self.dbm['groups'][group] = g
+
+ if not xref:
+ return defer.fail(NewsServerError("No groups carried: " + ' '.join(groups)))
+
+ article.putHeader('Xref', '%s %s' % (socket.gethostname().split()[0], ' '.join(map(lambda x: ':'.join(x), xref))))
+ self.dbm['Message-IDs'][article.getHeader('Message-ID')] = xref
+ return defer.succeed(None)
+
+
+ def overviewRequest(self):
+ return defer.succeed(OVERVIEW_FMT)
+
+
+ def xoverRequest(self, group, low, high):
+ if not self.dbm['groups'].has_key(group):
+ return defer.succeed([])
+
+ if low is None:
+ low = 0
+ if high is None:
+ high = self.dbm['groups'][group].maxArticle
+ r = []
+ for i in range(low, high + 1):
+ if self.dbm['groups'][group].articles.has_key(i):
+ r.append([str(i)] + self.dbm['groups'][group].articles[i].overview())
+ return defer.succeed(r)
+
+
+ def xhdrRequest(self, group, low, high, header):
+ if group not in self.dbm['groups']:
+ return defer.succeed([])
+
+ if low is None:
+ low = 0
+ if high is None:
+ high = self.dbm['groups'][group].maxArticle
+ r = []
+ for i in range(low, high + 1):
+ if self.dbm['groups'][group].articles.has_key(i):
+ r.append((i, self.dbm['groups'][group].articles[i].getHeader(header)))
+ return defer.succeed(r)
+
+
+ def listGroupRequest(self, group):
+ if self.dbm['groups'].has_key(group):
+ return defer.succeed((group, self.dbm['groups'][group].articles.keys()))
+ return defer.fail(NewsServerError("No such group: " + group))
+
+
+ def groupRequest(self, group):
+ try:
+ g = self.dbm['groups'][group]
+ except KeyError:
+ return defer.fail(NewsServerError("No such group: " + group))
+ else:
+ flags = g.flags
+ low = g.minArticle
+ high = g.maxArticle
+ num = high - low + 1
+ return defer.succeed((group, num, high, low, flags))
+
+
+ def articleExistsRequest(self, id):
+ return defer.succeed(id in self.dbm['Message-IDs'])
+
+
+ def articleRequest(self, group, index, id = None):
+ if id is not None:
+ try:
+ xref = self.dbm['Message-IDs'][id]
+ except KeyError:
+ return defer.fail(NewsServerError("No such article: " + id))
+ else:
+ group, index = xref[0]
+ index = int(index)
+
+ try:
+ a = self.dbm['groups'][group].articles[index]
+ except KeyError:
+ return defer.fail(NewsServerError("No such group: " + group))
+ else:
+ return defer.succeed((
+ index,
+ a.getHeader('Message-ID'),
+ StringIO.StringIO(a.textHeaders() + '\r\n' + a.body)
+ ))
+
+
+ def headRequest(self, group, index, id = None):
+ if id is not None:
+ try:
+ xref = self.dbm['Message-IDs'][id]
+ except KeyError:
+ return defer.fail(NewsServerError("No such article: " + id))
+ else:
+ group, index = xref[0]
+ index = int(index)
+
+ try:
+ a = self.dbm['groups'][group].articles[index]
+ except KeyError:
+ return defer.fail(NewsServerError("No such group: " + group))
+ else:
+ return defer.succeed((index, a.getHeader('Message-ID'), a.textHeaders()))
+
+
+ def bodyRequest(self, group, index, id = None):
+ if id is not None:
+ try:
+ xref = self.dbm['Message-IDs'][id]
+ except KeyError:
+ return defer.fail(NewsServerError("No such article: " + id))
+ else:
+ group, index = xref[0]
+ index = int(index)
+
+ try:
+ a = self.dbm['groups'][group].articles[index]
+ except KeyError:
+ return defer.fail(NewsServerError("No such group: " + group))
+ else:
+ return defer.succeed((index, a.getHeader('Message-ID'), StringIO.StringIO(a.body)))
+
+
+class NewsStorageAugmentation:
+ """
+ A NewsStorage implementation using Twisted's asynchronous DB-API
+ """
+
+ implements(INewsStorage)
+
+ schema = """
+
+ CREATE TABLE groups (
+ group_id SERIAL,
+ name VARCHAR(80) NOT NULL,
+
+ flags INTEGER DEFAULT 0 NOT NULL
+ );
+
+ CREATE UNIQUE INDEX group_id_index ON groups (group_id);
+ CREATE UNIQUE INDEX name_id_index ON groups (name);
+
+ CREATE TABLE articles (
+ article_id SERIAL,
+ message_id TEXT,
+
+ header TEXT,
+ body TEXT
+ );
+
+ CREATE UNIQUE INDEX article_id_index ON articles (article_id);
+ CREATE UNIQUE INDEX article_message_index ON articles (message_id);
+
+ CREATE TABLE postings (
+ group_id INTEGER,
+ article_id INTEGER,
+ article_index INTEGER NOT NULL
+ );
+
+ CREATE UNIQUE INDEX posting_article_index ON postings (article_id);
+
+ CREATE TABLE subscriptions (
+ group_id INTEGER
+ );
+
+ CREATE TABLE overview (
+ header TEXT
+ );
+ """
+
+ def __init__(self, info):
+ self.info = info
+ self.dbpool = adbapi.ConnectionPool(**self.info)
+
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+ self.info['password'] = getpass.getpass('Database password for %s: ' % (self.info['user'],))
+ self.dbpool = adbapi.ConnectionPool(**self.info)
+ del self.info['password']
+
+
+ def listRequest(self):
+ # COALESCE may not be totally portable
+ # it is shorthand for
+ # CASE WHEN (first parameter) IS NOT NULL then (first parameter) ELSE (second parameter) END
+ sql = """
+ SELECT groups.name,
+ COALESCE(MAX(postings.article_index), 0),
+ COALESCE(MIN(postings.article_index), 0),
+ groups.flags
+ FROM groups LEFT OUTER JOIN postings
+ ON postings.group_id = groups.group_id
+ GROUP BY groups.name, groups.flags
+ ORDER BY groups.name
+ """
+ return self.dbpool.runQuery(sql)
+
+
+ def subscriptionRequest(self):
+ sql = """
+ SELECT groups.name FROM groups,subscriptions WHERE groups.group_id = subscriptions.group_id
+ """
+ return self.dbpool.runQuery(sql)
+
+
+ def postRequest(self, message):
+ cleave = message.find('\r\n\r\n')
+ headers, article = message[:cleave], message[cleave + 4:]
+ article = Article(headers, article)
+ return self.dbpool.runInteraction(self._doPost, article)
+
+
+ def _doPost(self, transaction, article):
+ # Get the group ids
+ groups = article.getHeader('Newsgroups').split()
+ if not len(groups):
+ raise NNTPError('Missing Newsgroups header')
+
+ sql = """
+ SELECT name, group_id FROM groups
+ WHERE name IN (%s)
+ """ % (', '.join([("'%s'" % (adbapi.safe(group),)) for group in groups]),)
+
+ transaction.execute(sql)
+ result = transaction.fetchall()
+
+ # No relevant groups, bye bye!
+ if not len(result):
+ raise NNTPError('None of groups in Newsgroup header carried')
+
+ # Got some groups, now find the indices this article will have in each
+ sql = """
+ SELECT groups.group_id, COALESCE(MAX(postings.article_index), 0) + 1
+ FROM groups LEFT OUTER JOIN postings
+ ON postings.group_id = groups.group_id
+ WHERE groups.group_id IN (%s)
+ GROUP BY groups.group_id
+ """ % (', '.join([("%d" % (id,)) for (group, id) in result]),)
+
+ transaction.execute(sql)
+ indices = transaction.fetchall()
+
+ if not len(indices):
+ raise NNTPError('Internal server error - no indices found')
+
+ # Associate indices with group names
+ gidToName = dict([(b, a) for (a, b) in result])
+ gidToIndex = dict(indices)
+
+ nameIndex = []
+ for i in gidToName:
+ nameIndex.append((gidToName[i], gidToIndex[i]))
+
+ # Build xrefs
+ xrefs = socket.gethostname().split()[0]
+ xrefs = xrefs + ' ' + ' '.join([('%s:%d' % (group, id)) for (group, id) in nameIndex])
+ article.putHeader('Xref', xrefs)
+
+ # Hey! The article is ready to be posted! God damn f'in finally.
+ sql = """
+ INSERT INTO articles (message_id, header, body)
+ VALUES ('%s', '%s', '%s')
+ """ % (
+ adbapi.safe(article.getHeader('Message-ID')),
+ adbapi.safe(article.textHeaders()),
+ adbapi.safe(article.body)
+ )
+
+ transaction.execute(sql)
+
+ # Now update the posting to reflect the groups to which this belongs
+ for gid in gidToName:
+ sql = """
+ INSERT INTO postings (group_id, article_id, article_index)
+ VALUES (%d, (SELECT last_value FROM articles_article_id_seq), %d)
+ """ % (gid, gidToIndex[gid])
+ transaction.execute(sql)
+
+ return len(nameIndex)
+
+
+ def overviewRequest(self):
+ sql = """
+ SELECT header FROM overview
+ """
+ return self.dbpool.runQuery(sql).addCallback(lambda result: [header[0] for header in result])
+
+
+ def xoverRequest(self, group, low, high):
+ sql = """
+ SELECT postings.article_index, articles.header
+ FROM articles,postings,groups
+ WHERE postings.group_id = groups.group_id
+ AND groups.name = '%s'
+ AND postings.article_id = articles.article_id
+ %s
+ %s
+ """ % (
+ adbapi.safe(group),
+ low is not None and "AND postings.article_index >= %d" % (low,) or "",
+ high is not None and "AND postings.article_index <= %d" % (high,) or ""
+ )
+
+ return self.dbpool.runQuery(sql).addCallback(
+ lambda results: [
+ [id] + Article(header, None).overview() for (id, header) in results
+ ]
+ )
+
+
+ def xhdrRequest(self, group, low, high, header):
+ sql = """
+ SELECT articles.header
+ FROM groups,postings,articles
+ WHERE groups.name = '%s' AND postings.group_id = groups.group_id
+ AND postings.article_index >= %d
+ AND postings.article_index <= %d
+ """ % (adbapi.safe(group), low, high)
+
+ return self.dbpool.runQuery(sql).addCallback(
+ lambda results: [
+ (i, Article(h, None).getHeader(h)) for (i, h) in results
+ ]
+ )
+
+
+ def listGroupRequest(self, group):
+ sql = """
+ SELECT postings.article_index FROM postings,groups
+ WHERE postings.group_id = groups.group_id
+ AND groups.name = '%s'
+ """ % (adbapi.safe(group),)
+
+ return self.dbpool.runQuery(sql).addCallback(
+ lambda results, group = group: (group, [res[0] for res in results])
+ )
+
+
+ def groupRequest(self, group):
+ sql = """
+ SELECT groups.name,
+ COUNT(postings.article_index),
+ COALESCE(MAX(postings.article_index), 0),
+ COALESCE(MIN(postings.article_index), 0),
+ groups.flags
+ FROM groups LEFT OUTER JOIN postings
+ ON postings.group_id = groups.group_id
+ WHERE groups.name = '%s'
+ GROUP BY groups.name, groups.flags
+ """ % (adbapi.safe(group),)
+
+ return self.dbpool.runQuery(sql).addCallback(
+ lambda results: tuple(results[0])
+ )
+
+
+ def articleExistsRequest(self, id):
+ sql = """
+ SELECT COUNT(message_id) FROM articles
+ WHERE message_id = '%s'
+ """ % (adbapi.safe(id),)
+
+ return self.dbpool.runQuery(sql).addCallback(
+ lambda result: bool(result[0][0])
+ )
+
+
+ def articleRequest(self, group, index, id = None):
+ if id is not None:
+ sql = """
+ SELECT postings.article_index, articles.message_id, articles.header, articles.body
+ FROM groups,postings LEFT OUTER JOIN articles
+ ON articles.message_id = '%s'
+ WHERE groups.name = '%s'
+ AND groups.group_id = postings.group_id
+ """ % (adbapi.safe(id), adbapi.safe(group))
+ else:
+ sql = """
+ SELECT postings.article_index, articles.message_id, articles.header, articles.body
+ FROM groups,articles LEFT OUTER JOIN postings
+ ON postings.article_id = articles.article_id
+ WHERE postings.article_index = %d
+ AND postings.group_id = groups.group_id
+ AND groups.name = '%s'
+ """ % (index, adbapi.safe(group))
+
+ return self.dbpool.runQuery(sql).addCallback(
+ lambda result: (
+ result[0][0],
+ result[0][1],
+ StringIO.StringIO(result[0][2] + '\r\n' + result[0][3])
+ )
+ )
+
+
+ def headRequest(self, group, index):
+ sql = """
+ SELECT postings.article_index, articles.message_id, articles.header
+ FROM groups,articles LEFT OUTER JOIN postings
+ ON postings.article_id = articles.article_id
+ WHERE postings.article_index = %d
+ AND postings.group_id = groups.group_id
+ AND groups.name = '%s'
+ """ % (index, adbapi.safe(group))
+
+ return self.dbpool.runQuery(sql).addCallback(lambda result: result[0])
+
+
+ def bodyRequest(self, group, index):
+ sql = """
+ SELECT postings.article_index, articles.message_id, articles.body
+ FROM groups,articles LEFT OUTER JOIN postings
+ ON postings.article_id = articles.article_id
+ WHERE postings.article_index = %d
+ AND postings.group_id = groups.group_id
+ AND groups.name = '%s'
+ """ % (index, adbapi.safe(group))
+
+ return self.dbpool.runQuery(sql).addCallback(
+ lambda result: result[0]
+ ).addCallback(
+ lambda (index, id, body): (index, id, StringIO.StringIO(body))
+ )
+
+####
+#### XXX - make these static methods some day
+####
+def makeGroupSQL(groups):
+ res = ''
+ for g in groups:
+ res = res + """\n INSERT INTO groups (name) VALUES ('%s');\n""" % (adbapi.safe(g),)
+ return res
+
+
+def makeOverviewSQL():
+ res = ''
+ for o in OVERVIEW_FMT:
+ res = res + """\n INSERT INTO overview (header) VALUES ('%s');\n""" % (adbapi.safe(o),)
+ return res
diff --git a/vendor/Twisted-10.0.0/twisted/news/news.py b/vendor/Twisted-10.0.0/twisted/news/news.py
new file mode 100644
index 0000000000..a1f00ed694
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/news.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Maintainer: Jp Calderone
+"""
+
+from twisted.news import nntp
+from twisted.internet import protocol, reactor
+
+import time
+
+class NNTPFactory(protocol.ServerFactory):
+ """A factory for NNTP server protocols."""
+
+ protocol = nntp.NNTPServer
+
+ def __init__(self, backend):
+ self.backend = backend
+
+ def buildProtocol(self, connection):
+ p = self.protocol()
+ p.factory = self
+ return p
+
+
+class UsenetClientFactory(protocol.ClientFactory):
+ def __init__(self, groups, storage):
+ self.lastChecks = {}
+ self.groups = groups
+ self.storage = storage
+
+
+ def clientConnectionLost(self, connector, reason):
+ pass
+
+
+ def clientConnectionFailed(self, connector, reason):
+ print 'Connection failed: ', reason
+
+
+ def updateChecks(self, addr):
+ self.lastChecks[addr] = time.mktime(time.gmtime())
+
+
+ def buildProtocol(self, addr):
+ last = self.lastChecks.setdefault(addr, time.mktime(time.gmtime()) - (60 * 60 * 24 * 7))
+ p = nntp.UsenetClientProtocol(self.groups, last, self.storage)
+ p.factory = self
+ return p
+
+
+# XXX - Maybe this inheritence doesn't make so much sense?
+class UsenetServerFactory(NNTPFactory):
+ """A factory for NNTP Usenet server protocols."""
+
+ protocol = nntp.NNTPServer
+
+ def __init__(self, backend, remoteHosts = None, updatePeriod = 60):
+ NNTPFactory.__init__(self, backend)
+ self.updatePeriod = updatePeriod
+ self.remoteHosts = remoteHosts or []
+ self.clientFactory = UsenetClientFactory(self.remoteHosts, self.backend)
+
+
+ def startFactory(self):
+ self._updateCall = reactor.callLater(0, self.syncWithRemotes)
+
+
+ def stopFactory(self):
+ if self._updateCall:
+ self._updateCall.cancel()
+ self._updateCall = None
+
+
+ def buildProtocol(self, connection):
+ p = self.protocol()
+ p.factory = self
+ return p
+
+
+ def syncWithRemotes(self):
+ for remote in self.remoteHosts:
+ reactor.connectTCP(remote, 119, self.clientFactory)
+ self._updateCall = reactor.callLater(self.updatePeriod, self.syncWithRemotes)
+
+
+# backwards compatability
+Factory = UsenetServerFactory
diff --git a/vendor/Twisted-10.0.0/twisted/news/nntp.py b/vendor/Twisted-10.0.0/twisted/news/nntp.py
new file mode 100644
index 0000000000..30863b72f0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/nntp.py
@@ -0,0 +1,1069 @@
+# -*- test-case-name: twisted.news.test.test_nntp -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+NNTP protocol support.
+
+Maintainer: Jp Calderone
+
+The following protocol commands are currently understood::
+
+ LIST LISTGROUP XOVER XHDR
+ POST GROUP ARTICLE STAT HEAD
+ BODY NEXT MODE STREAM MODE READER SLAVE
+ LAST QUIT HELP IHAVE XPATH
+ XINDEX XROVER TAKETHIS CHECK
+
+The following protocol commands require implementation::
+
+ NEWNEWS
+ XGTITLE XPAT
+ XTHREAD AUTHINFO NEWGROUPS
+
+
+Other desired features:
+
+ - A real backend
+ - More robust client input handling
+ - A control protocol
+"""
+
+import time
+import types
+
+try:
+ import cStringIO as StringIO
+except:
+ import StringIO
+
+from twisted.protocols import basic
+from twisted.python import log
+
+def parseRange(text):
+ articles = text.split('-')
+ if len(articles) == 1:
+ try:
+ a = int(articles[0])
+ return a, a
+ except ValueError, e:
+ return None, None
+ elif len(articles) == 2:
+ try:
+ if len(articles[0]):
+ l = int(articles[0])
+ else:
+ l = None
+ if len(articles[1]):
+ h = int(articles[1])
+ else:
+ h = None
+ except ValueError, e:
+ return None, None
+ return l, h
+
+
+def extractCode(line):
+ line = line.split(' ', 1)
+ if len(line) != 2:
+ return None
+ try:
+ return int(line[0]), line[1]
+ except ValueError:
+ return None
+
+
+class NNTPError(Exception):
+ def __init__(self, string):
+ self.string = string
+
+ def __str__(self):
+ return 'NNTPError: %s' % self.string
+
+
+class NNTPClient(basic.LineReceiver):
+ MAX_COMMAND_LENGTH = 510
+
+ def __init__(self):
+ self.currentGroup = None
+
+ self._state = []
+ self._error = []
+ self._inputBuffers = []
+ self._responseCodes = []
+ self._responseHandlers = []
+
+ self._postText = []
+
+ self._newState(self._statePassive, None, self._headerInitial)
+
+
+ def gotAllGroups(self, groups):
+ "Override for notification when fetchGroups() action is completed"
+
+
+ def getAllGroupsFailed(self, error):
+ "Override for notification when fetchGroups() action fails"
+
+
+ def gotOverview(self, overview):
+ "Override for notification when fetchOverview() action is completed"
+
+
+ def getOverviewFailed(self, error):
+ "Override for notification when fetchOverview() action fails"
+
+
+ def gotSubscriptions(self, subscriptions):
+ "Override for notification when fetchSubscriptions() action is completed"
+
+
+ def getSubscriptionsFailed(self, error):
+ "Override for notification when fetchSubscriptions() action fails"
+
+
+ def gotGroup(self, group):
+ "Override for notification when fetchGroup() action is completed"
+
+
+ def getGroupFailed(self, error):
+ "Override for notification when fetchGroup() action fails"
+
+
+ def gotArticle(self, article):
+ "Override for notification when fetchArticle() action is completed"
+
+
+ def getArticleFailed(self, error):
+ "Override for notification when fetchArticle() action fails"
+
+
+ def gotHead(self, head):
+ "Override for notification when fetchHead() action is completed"
+
+
+ def getHeadFailed(self, error):
+ "Override for notification when fetchHead() action fails"
+
+
+ def gotBody(self, info):
+ "Override for notification when fetchBody() action is completed"
+
+
+ def getBodyFailed(self, body):
+ "Override for notification when fetchBody() action fails"
+
+
+ def postedOk(self):
+ "Override for notification when postArticle() action is successful"
+
+
+ def postFailed(self, error):
+ "Override for notification when postArticle() action fails"
+
+
+ def gotXHeader(self, headers):
+ "Override for notification when getXHeader() action is successful"
+
+
+ def getXHeaderFailed(self, error):
+ "Override for notification when getXHeader() action fails"
+
+
+ def gotNewNews(self, news):
+ "Override for notification when getNewNews() action is successful"
+
+
+ def getNewNewsFailed(self, error):
+ "Override for notification when getNewNews() action fails"
+
+
+ def gotNewGroups(self, groups):
+ "Override for notification when getNewGroups() action is successful"
+
+
+ def getNewGroupsFailed(self, error):
+ "Override for notification when getNewGroups() action fails"
+
+
+ def setStreamSuccess(self):
+ "Override for notification when setStream() action is successful"
+
+
+ def setStreamFailed(self, error):
+ "Override for notification when setStream() action fails"
+
+
+ def fetchGroups(self):
+ """
+ Request a list of all news groups from the server. gotAllGroups()
+ is called on success, getGroupsFailed() on failure
+ """
+ self.sendLine('LIST')
+ self._newState(self._stateList, self.getAllGroupsFailed)
+
+
+ def fetchOverview(self):
+ """
+ Request the overview format from the server. gotOverview() is called
+ on success, getOverviewFailed() on failure
+ """
+ self.sendLine('LIST OVERVIEW.FMT')
+ self._newState(self._stateOverview, self.getOverviewFailed)
+
+
+ def fetchSubscriptions(self):
+ """
+ Request a list of the groups it is recommended a new user subscribe to.
+ gotSubscriptions() is called on success, getSubscriptionsFailed() on
+ failure
+ """
+ self.sendLine('LIST SUBSCRIPTIONS')
+ self._newState(self._stateSubscriptions, self.getSubscriptionsFailed)
+
+
+ def fetchGroup(self, group):
+ """
+ Get group information for the specified group from the server. gotGroup()
+ is called on success, getGroupFailed() on failure.
+ """
+ self.sendLine('GROUP %s' % (group,))
+ self._newState(None, self.getGroupFailed, self._headerGroup)
+
+
+ def fetchHead(self, index = ''):
+ """
+ Get the header for the specified article (or the currently selected
+ article if index is '') from the server. gotHead() is called on
+ success, getHeadFailed() on failure
+ """
+ self.sendLine('HEAD %s' % (index,))
+ self._newState(self._stateHead, self.getHeadFailed)
+
+
+ def fetchBody(self, index = ''):
+ """
+ Get the body for the specified article (or the currently selected
+ article if index is '') from the server. gotBody() is called on
+ success, getBodyFailed() on failure
+ """
+ self.sendLine('BODY %s' % (index,))
+ self._newState(self._stateBody, self.getBodyFailed)
+
+
+ def fetchArticle(self, index = ''):
+ """
+ Get the complete article with the specified index (or the currently
+ selected article if index is '') or Message-ID from the server.
+ gotArticle() is called on success, getArticleFailed() on failure.
+ """
+ self.sendLine('ARTICLE %s' % (index,))
+ self._newState(self._stateArticle, self.getArticleFailed)
+
+
+ def postArticle(self, text):
+ """
+ Attempt to post an article with the specified text to the server. 'text'
+ must consist of both head and body data, as specified by RFC 850. If the
+ article is posted successfully, postedOk() is called, otherwise postFailed()
+ is called.
+ """
+ self.sendLine('POST')
+ self._newState(None, self.postFailed, self._headerPost)
+ self._postText.append(text)
+
+
+ def fetchNewNews(self, groups, date, distributions = ''):
+ """
+ Get the Message-IDs for all new news posted to any of the given
+ groups since the specified date - in seconds since the epoch, GMT -
+ optionally restricted to the given distributions. gotNewNews() is
+ called on success, getNewNewsFailed() on failure.
+
+ One invocation of this function may result in multiple invocations
+ of gotNewNews()/getNewNewsFailed().
+ """
+ date, timeStr = time.strftime('%y%m%d %H%M%S', time.gmtime(date)).split()
+ line = 'NEWNEWS %%s %s %s %s' % (date, timeStr, distributions)
+ groupPart = ''
+ while len(groups) and len(line) + len(groupPart) + len(groups[-1]) + 1 < NNTPClient.MAX_COMMAND_LENGTH:
+ group = groups.pop()
+ groupPart = groupPart + ',' + group
+
+ self.sendLine(line % (groupPart,))
+ self._newState(self._stateNewNews, self.getNewNewsFailed)
+
+ if len(groups):
+ self.fetchNewNews(groups, date, distributions)
+
+
+ def fetchNewGroups(self, date, distributions):
+ """
+ Get the names of all new groups created/added to the server since
+ the specified date - in seconds since the ecpoh, GMT - optionally
+ restricted to the given distributions. gotNewGroups() is called
+ on success, getNewGroupsFailed() on failure.
+ """
+ date, timeStr = time.strftime('%y%m%d %H%M%S', time.gmtime(date)).split()
+ self.sendLine('NEWGROUPS %s %s %s' % (date, timeStr, distributions))
+ self._newState(self._stateNewGroups, self.getNewGroupsFailed)
+
+
+ def fetchXHeader(self, header, low = None, high = None, id = None):
+ """
+ Request a specific header from the server for an article or range
+ of articles. If 'id' is not None, a header for only the article
+ with that Message-ID will be requested. If both low and high are
+ None, a header for the currently selected article will be selected;
+ If both low and high are zero-length strings, headers for all articles
+ in the currently selected group will be requested; Otherwise, high
+ and low will be used as bounds - if one is None the first or last
+ article index will be substituted, as appropriate.
+ """
+ if id is not None:
+ r = header + ' <%s>' % (id,)
+ elif low is high is None:
+ r = header
+ elif high is None:
+ r = header + ' %d-' % (low,)
+ elif low is None:
+ r = header + ' -%d' % (high,)
+ else:
+ r = header + ' %d-%d' % (low, high)
+ self.sendLine('XHDR ' + r)
+ self._newState(self._stateXHDR, self.getXHeaderFailed)
+
+
+ def setStream(self):
+ """
+ Set the mode to STREAM, suspending the normal "lock-step" mode of
+ communications. setStreamSuccess() is called on success,
+ setStreamFailed() on failure.
+ """
+ self.sendLine('MODE STREAM')
+ self._newState(None, self.setStreamFailed, self._headerMode)
+
+
+ def quit(self):
+ self.sendLine('QUIT')
+ self.transport.loseConnection()
+
+
+ def _newState(self, method, error, responseHandler = None):
+ self._inputBuffers.append([])
+ self._responseCodes.append(None)
+ self._state.append(method)
+ self._error.append(error)
+ self._responseHandlers.append(responseHandler)
+
+
+ def _endState(self):
+ buf = self._inputBuffers[0]
+ del self._responseCodes[0]
+ del self._inputBuffers[0]
+ del self._state[0]
+ del self._error[0]
+ del self._responseHandlers[0]
+ return buf
+
+
+ def _newLine(self, line, check = 1):
+ if check and line and line[0] == '.':
+ line = line[1:]
+ self._inputBuffers[0].append(line)
+
+
+ def _setResponseCode(self, code):
+ self._responseCodes[0] = code
+
+
+ def _getResponseCode(self):
+ return self._responseCodes[0]
+
+
+ def lineReceived(self, line):
+ if not len(self._state):
+ self._statePassive(line)
+ elif self._getResponseCode() is None:
+ code = extractCode(line)
+ if code is None or not (200 <= code[0] < 400): # An error!
+ self._error[0](line)
+ self._endState()
+ else:
+ self._setResponseCode(code)
+ if self._responseHandlers[0]:
+ self._responseHandlers[0](code)
+ else:
+ self._state[0](line)
+
+
+ def _statePassive(self, line):
+ log.msg('Server said: %s' % line)
+
+
+ def _passiveError(self, error):
+ log.err('Passive Error: %s' % (error,))
+
+
+ def _headerInitial(self, (code, message)):
+ if code == 200:
+ self.canPost = 1
+ else:
+ self.canPost = 0
+ self._endState()
+
+
+ def _stateList(self, line):
+ if line != '.':
+ data = filter(None, line.strip().split())
+ self._newLine((data[0], int(data[1]), int(data[2]), data[3]), 0)
+ else:
+ self.gotAllGroups(self._endState())
+
+
+ def _stateOverview(self, line):
+ if line != '.':
+ self._newLine(filter(None, line.strip().split()), 0)
+ else:
+ self.gotOverview(self._endState())
+
+
+ def _stateSubscriptions(self, line):
+ if line != '.':
+ self._newLine(line.strip(), 0)
+ else:
+ self.gotSubscriptions(self._endState())
+
+
+ def _headerGroup(self, (code, line)):
+ self.gotGroup(tuple(line.split()))
+ self._endState()
+
+
+ def _stateArticle(self, line):
+ if line != '.':
+ if line.startswith('.'):
+ line = line[1:]
+ self._newLine(line, 0)
+ else:
+ self.gotArticle('\n'.join(self._endState())+'\n')
+
+
+ def _stateHead(self, line):
+ if line != '.':
+ self._newLine(line, 0)
+ else:
+ self.gotHead('\n'.join(self._endState()))
+
+
+ def _stateBody(self, line):
+ if line != '.':
+ if line.startswith('.'):
+ line = line[1:]
+ self._newLine(line, 0)
+ else:
+ self.gotBody('\n'.join(self._endState())+'\n')
+
+
+ def _headerPost(self, (code, message)):
+ if code == 340:
+ self.transport.write(self._postText[0].replace('\n', '\r\n').replace('\r\n.', '\r\n..'))
+ if self._postText[0][-1:] != '\n':
+ self.sendLine('')
+ self.sendLine('.')
+ del self._postText[0]
+ self._newState(None, self.postFailed, self._headerPosted)
+ else:
+ self.postFailed('%d %s' % (code, message))
+ self._endState()
+
+
+ def _headerPosted(self, (code, message)):
+ if code == 240:
+ self.postedOk()
+ else:
+ self.postFailed('%d %s' % (code, message))
+ self._endState()
+
+
+ def _stateXHDR(self, line):
+ if line != '.':
+ self._newLine(line.split(), 0)
+ else:
+ self._gotXHeader(self._endState())
+
+
+ def _stateNewNews(self, line):
+ if line != '.':
+ self._newLine(line, 0)
+ else:
+ self.gotNewNews(self._endState())
+
+
+ def _stateNewGroups(self, line):
+ if line != '.':
+ self._newLine(line, 0)
+ else:
+ self.gotNewGroups(self._endState())
+
+
+ def _headerMode(self, (code, message)):
+ if code == 203:
+ self.setStreamSuccess()
+ else:
+ self.setStreamFailed((code, message))
+ self._endState()
+
+
+class NNTPServer(basic.LineReceiver):
+ COMMANDS = [
+ 'LIST', 'GROUP', 'ARTICLE', 'STAT', 'MODE', 'LISTGROUP', 'XOVER',
+ 'XHDR', 'HEAD', 'BODY', 'NEXT', 'LAST', 'POST', 'QUIT', 'IHAVE',
+ 'HELP', 'SLAVE', 'XPATH', 'XINDEX', 'XROVER', 'TAKETHIS', 'CHECK'
+ ]
+
+ def __init__(self):
+ self.servingSlave = 0
+
+
+ def connectionMade(self):
+ self.inputHandler = None
+ self.currentGroup = None
+ self.currentIndex = None
+ self.sendLine('200 server ready - posting allowed')
+
+ def lineReceived(self, line):
+ if self.inputHandler is not None:
+ self.inputHandler(line)
+ else:
+ parts = line.strip().split()
+ if len(parts):
+ cmd, parts = parts[0].upper(), parts[1:]
+ if cmd in NNTPServer.COMMANDS:
+ func = getattr(self, 'do_%s' % cmd)
+ try:
+ func(*parts)
+ except TypeError:
+ self.sendLine('501 command syntax error')
+ log.msg("501 command syntax error")
+ log.msg("command was", line)
+ log.deferr()
+ except:
+ self.sendLine('503 program fault - command not performed')
+ log.msg("503 program fault")
+ log.msg("command was", line)
+ log.deferr()
+ else:
+ self.sendLine('500 command not recognized')
+
+
+ def do_LIST(self, subcmd = '', *dummy):
+ subcmd = subcmd.strip().lower()
+ if subcmd == 'newsgroups':
+ # XXX - this could use a real implementation, eh?
+ self.sendLine('215 Descriptions in form "group description"')
+ self.sendLine('.')
+ elif subcmd == 'overview.fmt':
+ defer = self.factory.backend.overviewRequest()
+ defer.addCallbacks(self._gotOverview, self._errOverview)
+ log.msg('overview')
+ elif subcmd == 'subscriptions':
+ defer = self.factory.backend.subscriptionRequest()
+ defer.addCallbacks(self._gotSubscription, self._errSubscription)
+ log.msg('subscriptions')
+ elif subcmd == '':
+ defer = self.factory.backend.listRequest()
+ defer.addCallbacks(self._gotList, self._errList)
+ else:
+ self.sendLine('500 command not recognized')
+
+
+ def _gotList(self, list):
+ self.sendLine('215 newsgroups in form "group high low flags"')
+ for i in list:
+ self.sendLine('%s %d %d %s' % tuple(i))
+ self.sendLine('.')
+
+
+ def _errList(self, failure):
+ print 'LIST failed: ', failure
+ self.sendLine('503 program fault - command not performed')
+
+
+ def _gotSubscription(self, parts):
+ self.sendLine('215 information follows')
+ for i in parts:
+ self.sendLine(i)
+ self.sendLine('.')
+
+
+ def _errSubscription(self, failure):
+ print 'SUBSCRIPTIONS failed: ', failure
+ self.sendLine('503 program fault - comand not performed')
+
+
+ def _gotOverview(self, parts):
+ self.sendLine('215 Order of fields in overview database.')
+ for i in parts:
+ self.sendLine(i + ':')
+ self.sendLine('.')
+
+
+ def _errOverview(self, failure):
+ print 'LIST OVERVIEW.FMT failed: ', failure
+ self.sendLine('503 program fault - command not performed')
+
+
+ def do_LISTGROUP(self, group = None):
+ group = group or self.currentGroup
+ if group is None:
+ self.sendLine('412 Not currently in newsgroup')
+ else:
+ defer = self.factory.backend.listGroupRequest(group)
+ defer.addCallbacks(self._gotListGroup, self._errListGroup)
+
+
+ def _gotListGroup(self, (group, articles)):
+ self.currentGroup = group
+ if len(articles):
+ self.currentIndex = int(articles[0])
+ else:
+ self.currentIndex = None
+
+ self.sendLine('211 list of article numbers follow')
+ for i in articles:
+ self.sendLine(str(i))
+ self.sendLine('.')
+
+
+ def _errListGroup(self, failure):
+ print 'LISTGROUP failed: ', failure
+ self.sendLine('502 no permission')
+
+
+ def do_XOVER(self, range):
+ if self.currentGroup is None:
+ self.sendLine('412 No news group currently selected')
+ else:
+ l, h = parseRange(range)
+ defer = self.factory.backend.xoverRequest(self.currentGroup, l, h)
+ defer.addCallbacks(self._gotXOver, self._errXOver)
+
+
+ def _gotXOver(self, parts):
+ self.sendLine('224 Overview information follows')
+ for i in parts:
+ self.sendLine('\t'.join(map(str, i)))
+ self.sendLine('.')
+
+
+ def _errXOver(self, failure):
+ print 'XOVER failed: ', failure
+ self.sendLine('420 No article(s) selected')
+
+
+ def xhdrWork(self, header, range):
+ if self.currentGroup is None:
+ self.sendLine('412 No news group currently selected')
+ else:
+ if range is None:
+ if self.currentIndex is None:
+ self.sendLine('420 No current article selected')
+ return
+ else:
+ l = h = self.currentIndex
+ else:
+ # FIXME: articles may be a message-id
+ l, h = parseRange(range)
+
+ if l is h is None:
+ self.sendLine('430 no such article')
+ else:
+ return self.factory.backend.xhdrRequest(self.currentGroup, l, h, header)
+
+
+ def do_XHDR(self, header, range = None):
+ d = self.xhdrWork(header, range)
+ if d:
+ d.addCallbacks(self._gotXHDR, self._errXHDR)
+
+
+ def _gotXHDR(self, parts):
+ self.sendLine('221 Header follows')
+ for i in parts:
+ self.sendLine('%d %s' % i)
+ self.sendLine('.')
+
+ def _errXHDR(self, failure):
+ print 'XHDR failed: ', failure
+ self.sendLine('502 no permission')
+
+
+ def do_XROVER(self, header, range = None):
+ d = self.xhdrWork(header, range)
+ if d:
+ d.addCallbacks(self._gotXROVER, self._errXROVER)
+
+
+ def _gotXROVER(self, parts):
+ self.sendLine('224 Overview information follows')
+ for i in parts:
+ self.sendLine('%d %s' % i)
+ self.sendLine('.')
+
+
+ def _errXROVER(self, failure):
+ print 'XROVER failed: ',
+ self._errXHDR(failure)
+
+
+ def do_POST(self):
+ self.inputHandler = self._doingPost
+ self.message = ''
+ self.sendLine('340 send article to be posted. End with <CR-LF>.<CR-LF>')
+
+
+ def _doingPost(self, line):
+ if line == '.':
+ self.inputHandler = None
+ group, article = self.currentGroup, self.message
+ self.message = ''
+
+ defer = self.factory.backend.postRequest(article)
+ defer.addCallbacks(self._gotPost, self._errPost)
+ else:
+ self.message = self.message + line + '\r\n'
+
+
+ def _gotPost(self, parts):
+ self.sendLine('240 article posted ok')
+
+
+ def _errPost(self, failure):
+ print 'POST failed: ', failure
+ self.sendLine('441 posting failed')
+
+
+ def do_CHECK(self, id):
+ d = self.factory.backend.articleExistsRequest(id)
+ d.addCallbacks(self._gotCheck, self._errCheck)
+
+
+ def _gotCheck(self, result):
+ if result:
+ self.sendLine("438 already have it, please don't send it to me")
+ else:
+ self.sendLine('238 no such article found, please send it to me')
+
+
+ def _errCheck(self, failure):
+ print 'CHECK failed: ', failure
+ self.sendLine('431 try sending it again later')
+
+
+ def do_TAKETHIS(self, id):
+ self.inputHandler = self._doingTakeThis
+ self.message = ''
+
+
+ def _doingTakeThis(self, line):
+ if line == '.':
+ self.inputHandler = None
+ article = self.message
+ self.message = ''
+ d = self.factory.backend.postRequest(article)
+ d.addCallbacks(self._didTakeThis, self._errTakeThis)
+ else:
+ self.message = self.message + line + '\r\n'
+
+
+ def _didTakeThis(self, result):
+ self.sendLine('239 article transferred ok')
+
+
+ def _errTakeThis(self, failure):
+ print 'TAKETHIS failed: ', failure
+ self.sendLine('439 article transfer failed')
+
+
+ def do_GROUP(self, group):
+ defer = self.factory.backend.groupRequest(group)
+ defer.addCallbacks(self._gotGroup, self._errGroup)
+
+
+ def _gotGroup(self, (name, num, high, low, flags)):
+ self.currentGroup = name
+ self.currentIndex = low
+ self.sendLine('211 %d %d %d %s group selected' % (num, low, high, name))
+
+
+ def _errGroup(self, failure):
+ print 'GROUP failed: ', failure
+ self.sendLine('411 no such group')
+
+
+ def articleWork(self, article, cmd, func):
+ if self.currentGroup is None:
+ self.sendLine('412 no newsgroup has been selected')
+ else:
+ if not article:
+ if self.currentIndex is None:
+ self.sendLine('420 no current article has been selected')
+ else:
+ article = self.currentIndex
+ else:
+ if article[0] == '<':
+ return func(self.currentGroup, index = None, id = article)
+ else:
+ try:
+ article = int(article)
+ return func(self.currentGroup, article)
+ except ValueError, e:
+ self.sendLine('501 command syntax error')
+
+
+ def do_ARTICLE(self, article = None):
+ defer = self.articleWork(article, 'ARTICLE', self.factory.backend.articleRequest)
+ if defer:
+ defer.addCallbacks(self._gotArticle, self._errArticle)
+
+
+ def _gotArticle(self, (index, id, article)):
+ if isinstance(article, types.StringType):
+ import warnings
+ warnings.warn(
+ "Returning the article as a string from `articleRequest' "
+ "is deprecated. Return a file-like object instead."
+ )
+ article = StringIO.StringIO(article)
+ self.currentIndex = index
+ self.sendLine('220 %d %s article' % (index, id))
+ s = basic.FileSender()
+ d = s.beginFileTransfer(article, self.transport)
+ d.addCallback(self.finishedFileTransfer)
+
+ ##
+ ## Helper for FileSender
+ ##
+ def finishedFileTransfer(self, lastsent):
+ if lastsent != '\n':
+ line = '\r\n.'
+ else:
+ line = '.'
+ self.sendLine(line)
+ ##
+
+ def _errArticle(self, failure):
+ print 'ARTICLE failed: ', failure
+ self.sendLine('423 bad article number')
+
+
+ def do_STAT(self, article = None):
+ defer = self.articleWork(article, 'STAT', self.factory.backend.articleRequest)
+ if defer:
+ defer.addCallbacks(self._gotStat, self._errStat)
+
+
+ def _gotStat(self, (index, id, article)):
+ self.currentIndex = index
+ self.sendLine('223 %d %s article retreived - request text separately' % (index, id))
+
+
+ def _errStat(self, failure):
+ print 'STAT failed: ', failure
+ self.sendLine('423 bad article number')
+
+
+ def do_HEAD(self, article = None):
+ defer = self.articleWork(article, 'HEAD', self.factory.backend.headRequest)
+ if defer:
+ defer.addCallbacks(self._gotHead, self._errHead)
+
+
+ def _gotHead(self, (index, id, head)):
+ self.currentIndex = index
+ self.sendLine('221 %d %s article retrieved' % (index, id))
+ self.transport.write(head + '\r\n')
+ self.sendLine('.')
+
+
+ def _errHead(self, failure):
+ print 'HEAD failed: ', failure
+ self.sendLine('423 no such article number in this group')
+
+
+ def do_BODY(self, article):
+ defer = self.articleWork(article, 'BODY', self.factory.backend.bodyRequest)
+ if defer:
+ defer.addCallbacks(self._gotBody, self._errBody)
+
+
+ def _gotBody(self, (index, id, body)):
+ if isinstance(body, types.StringType):
+ import warnings
+ warnings.warn(
+ "Returning the article as a string from `articleRequest' "
+ "is deprecated. Return a file-like object instead."
+ )
+ body = StringIO.StringIO(body)
+ self.currentIndex = index
+ self.sendLine('221 %d %s article retrieved' % (index, id))
+ self.lastsent = ''
+ s = basic.FileSender()
+ d = s.beginFileTransfer(body, self.transport)
+ d.addCallback(self.finishedFileTransfer)
+
+ def _errBody(self, failure):
+ print 'BODY failed: ', failure
+ self.sendLine('423 no such article number in this group')
+
+
+ # NEXT and LAST are just STATs that increment currentIndex first.
+ # Accordingly, use the STAT callbacks.
+ def do_NEXT(self):
+ i = self.currentIndex + 1
+ defer = self.factory.backend.articleRequest(self.currentGroup, i)
+ defer.addCallbacks(self._gotStat, self._errStat)
+
+
+ def do_LAST(self):
+ i = self.currentIndex - 1
+ defer = self.factory.backend.articleRequest(self.currentGroup, i)
+ defer.addCallbacks(self._gotStat, self._errStat)
+
+
+ def do_MODE(self, cmd):
+ cmd = cmd.strip().upper()
+ if cmd == 'READER':
+ self.servingSlave = 0
+ self.sendLine('200 Hello, you can post')
+ elif cmd == 'STREAM':
+ self.sendLine('500 Command not understood')
+ else:
+ # This is not a mistake
+ self.sendLine('500 Command not understood')
+
+
+ def do_QUIT(self):
+ self.sendLine('205 goodbye')
+ self.transport.loseConnection()
+
+
+ def do_HELP(self):
+ self.sendLine('100 help text follows')
+ self.sendLine('Read the RFC.')
+ self.sendLine('.')
+
+
+ def do_SLAVE(self):
+ self.sendLine('202 slave status noted')
+ self.servingeSlave = 1
+
+
+ def do_XPATH(self, article):
+ # XPATH is a silly thing to have. No client has the right to ask
+ # for this piece of information from me, and so that is what I'll
+ # tell them.
+ self.sendLine('502 access restriction or permission denied')
+
+
+ def do_XINDEX(self, article):
+ # XINDEX is another silly command. The RFC suggests it be relegated
+ # to the history books, and who am I to disagree?
+ self.sendLine('502 access restriction or permission denied')
+
+
+ def do_XROVER(self, range = None):
+ self.do_XHDR(self, 'References', range)
+
+
+ def do_IHAVE(self, id):
+ self.factory.backend.articleExistsRequest(id).addCallback(self._foundArticle)
+
+
+ def _foundArticle(self, result):
+ if result:
+ self.sendLine('437 article rejected - do not try again')
+ else:
+ self.sendLine('335 send article to be transferred. End with <CR-LF>.<CR-LF>')
+ self.inputHandler = self._handleIHAVE
+ self.message = ''
+
+
+ def _handleIHAVE(self, line):
+ if line == '.':
+ self.inputHandler = None
+ self.factory.backend.postRequest(
+ self.message
+ ).addCallbacks(self._gotIHAVE, self._errIHAVE)
+
+ self.message = ''
+ else:
+ self.message = self.message + line + '\r\n'
+
+
+ def _gotIHAVE(self, result):
+ self.sendLine('235 article transferred ok')
+
+
+ def _errIHAVE(self, failure):
+ print 'IHAVE failed: ', failure
+ self.sendLine('436 transfer failed - try again later')
+
+
+class UsenetClientProtocol(NNTPClient):
+ """
+ A client that connects to an NNTP server and asks for articles new
+ since a certain time.
+ """
+
+ def __init__(self, groups, date, storage):
+ """
+ Fetch all new articles from the given groups since the
+ given date and dump them into the given storage. groups
+ is a list of group names. date is an integer or floating
+ point representing seconds since the epoch (GMT). storage is
+ any object that implements the NewsStorage interface.
+ """
+ NNTPClient.__init__(self)
+ self.groups, self.date, self.storage = groups, date, storage
+
+
+ def connectionMade(self):
+ NNTPClient.connectionMade(self)
+ log.msg("Initiating update with remote host: " + str(self.transport.getPeer()))
+ self.setStream()
+ self.fetchNewNews(self.groups, self.date, '')
+
+
+ def articleExists(self, exists, article):
+ if exists:
+ self.fetchArticle(article)
+ else:
+ self.count = self.count - 1
+ self.disregard = self.disregard + 1
+
+
+ def gotNewNews(self, news):
+ self.disregard = 0
+ self.count = len(news)
+ log.msg("Transfering " + str(self.count) + " articles from remote host: " + str(self.transport.getPeer()))
+ for i in news:
+ self.storage.articleExistsRequest(i).addCallback(self.articleExists, i)
+
+
+ def getNewNewsFailed(self, reason):
+ log.msg("Updated failed (" + reason + ") with remote host: " + str(self.transport.getPeer()))
+ self.quit()
+
+
+ def gotArticle(self, article):
+ self.storage.postRequest(article)
+ self.count = self.count - 1
+ if not self.count:
+ log.msg("Completed update with remote host: " + str(self.transport.getPeer()))
+ if self.disregard:
+ log.msg("Disregarded %d articles." % (self.disregard,))
+ self.factory.updateChecks(self.transport.getPeer())
+ self.quit()
diff --git a/vendor/Twisted-10.0.0/twisted/news/tap.py b/vendor/Twisted-10.0.0/twisted/news/tap.py
new file mode 100644
index 0000000000..a45f2b5078
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/tap.py
@@ -0,0 +1,134 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.news import news, database
+from twisted.application import strports
+from twisted.python import usage, log
+
+class DBOptions(usage.Options):
+ optParameters = [
+ ['module', None, 'pyPgSQL.PgSQL', "DB-API 2.0 module to use"],
+ ['dbhost', None, 'localhost', "Host where database manager is listening"],
+ ['dbuser', None, 'news', "Username with which to connect to database"],
+ ['database', None, 'news', "Database name to use"],
+ ['schema', None, 'schema.sql', "File to which to write SQL schema initialisation"],
+
+ # XXX - Hrm.
+ ["groups", "g", "groups.list", "File containing group list"],
+ ["servers", "s", "servers.list", "File containing server list"]
+ ]
+
+ def postOptions(self):
+ # XXX - Hmmm.
+ self['groups'] = [g.strip() for g in open(self['groups']).readlines() if not g.startswith('#')]
+ self['servers'] = [s.strip() for s in open(self['servers']).readlines() if not s.startswith('#')]
+
+ try:
+ __import__(self['module'])
+ except ImportError:
+ log.msg("Warning: Cannot import %s" % (self['module'],))
+
+ f = open(self['schema'], 'w')
+ f.write(
+ database.NewsStorageAugmentation.schema + '\n' +
+ database.makeGroupSQL(self['groups']) + '\n' +
+ database.makeOverviewSQL()
+ )
+ f.close()
+
+ info = {
+ 'host': self['dbhost'], 'user': self['dbuser'],
+ 'database': self['database'], 'dbapiName': self['module']
+ }
+ self.db = database.NewsStorageAugmentation(info)
+
+
+class PickleOptions(usage.Options):
+ optParameters = [
+ ['file', None, 'news.pickle', "File to which to save pickle"],
+
+ # XXX - Hrm.
+ ["groups", "g", "groups.list", "File containing group list"],
+ ["servers", "s", "servers.list", "File containing server list"],
+ ["moderators", "m", "moderators.list",
+ "File containing moderators list"],
+ ]
+
+ subCommands = None
+
+ def postOptions(self):
+ # XXX - Hmmm.
+ filename = self['file']
+ self['groups'] = [g.strip() for g in open(self['groups']).readlines()
+ if not g.startswith('#')]
+ self['servers'] = [s.strip() for s in open(self['servers']).readlines()
+ if not s.startswith('#')]
+ self['moderators'] = [s.split()
+ for s in open(self['moderators']).readlines()
+ if not s.startswith('#')]
+ self.db = database.PickleStorage(filename, self['groups'],
+ self['moderators'])
+
+
+class Options(usage.Options):
+ synopsis = "[options]"
+
+ groups = None
+ servers = None
+ subscriptions = None
+
+ optParameters = [
+ ["port", "p", "119", "Listen port"],
+ ["interface", "i", "", "Interface to which to bind"],
+ ["datadir", "d", "news.db", "Root data storage path"],
+ ["mailhost", "m", "localhost", "Host of SMTP server to use"]
+ ]
+ zsh_actions = {"datadir" : "_dirs", "mailhost" : "_hosts"}
+
+ def __init__(self):
+ usage.Options.__init__(self)
+ self.groups = []
+ self.servers = []
+ self.subscriptions = []
+
+
+ def opt_group(self, group):
+ """The name of a newsgroup to carry."""
+ self.groups.append([group, None])
+
+
+ def opt_moderator(self, moderator):
+ """The email of the moderator for the most recently passed group."""
+ self.groups[-1][1] = moderator
+
+
+ def opt_subscription(self, group):
+ """A newsgroup to list as a recommended subscription."""
+ self.subscriptions.append(group)
+
+
+ def opt_server(self, server):
+ """The address of a Usenet server to pass messages to and receive messages from."""
+ self.servers.append(server)
+
+
+def makeService(config):
+ if not len(config.groups):
+ raise usage.UsageError("No newsgroups specified")
+
+ db = database.NewsShelf(config['mailhost'], config['datadir'])
+ for (g, m) in config.groups:
+ if m:
+ db.addGroup(g, 'm')
+ db.addModerator(g, m)
+ else:
+ db.addGroup(g, 'y')
+ for s in config.subscriptions:
+ print s
+ db.addSubscription(s)
+ s = config['port']
+ if config['interface']:
+ # Add a warning here
+ s += ':interface='+config['interface']
+ return strports.service(s, news.UsenetServerFactory(db, config.servers))
diff --git a/vendor/Twisted-10.0.0/twisted/news/test/__init__.py b/vendor/Twisted-10.0.0/twisted/news/test/__init__.py
new file mode 100644
index 0000000000..677518dd94
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/test/__init__.py
@@ -0,0 +1 @@
+"""News Tests"""
diff --git a/vendor/Twisted-10.0.0/twisted/news/test/test_news.py b/vendor/Twisted-10.0.0/twisted/news/test/test_news.py
new file mode 100644
index 0000000000..f1fdaa780c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/test/test_news.py
@@ -0,0 +1,107 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys, types
+from pprint import pformat
+
+from twisted.trial import unittest
+from twisted.news import database
+from twisted.internet import reactor
+
+MESSAGE_ID = "f83ba57450ed0fd8ac9a472b847e830e"
+
+POST_STRING = """Path: not-for-mail
+From: <exarkun@somehost.domain.com>
+Subject: a test
+Newsgroups: alt.test.nntp
+Organization:
+Summary:
+Keywords:
+Message-Id: %s
+User-Agent: tin/1.4.5-20010409 ("One More Nightmare") (UNIX) (Linux/2.4.17 (i686))
+
+this is a test
+...
+lala
+moo
+--
+"One World, one Web, one Program." - Microsoft(R) promotional ad
+"Ein Volk, ein Reich, ein Fuhrer." - Adolf Hitler
+--
+ 10:56pm up 4 days, 4:42, 1 user, load average: 0.08, 0.08, 0.12
+""" % (MESSAGE_ID)
+
+class NewsTestCase(unittest.TestCase):
+ def setUp(self):
+ self.backend = database.NewsShelf(None, 'news2.db')
+ self.backend.addGroup('alt.test.nntp', 'y')
+ self.backend.postRequest(POST_STRING.replace('\n', '\r\n'))
+
+
+ def testArticleExists(self):
+ d = self.backend.articleExistsRequest(MESSAGE_ID)
+ d.addCallback(self.failUnless)
+ return d
+
+
+ def testArticleRequest(self):
+ d = self.backend.articleRequest(None, None, MESSAGE_ID)
+
+ def cbArticle(result):
+ self.failUnless(isinstance(result, tuple),
+ 'callback result is wrong type: ' + str(result))
+ self.assertEquals(len(result), 3,
+ 'callback result list should have three entries: ' +
+ str(result))
+ self.assertEquals(result[1], MESSAGE_ID,
+ "callback result Message-Id doesn't match: %s vs %s" %
+ (MESSAGE_ID, result[1]))
+ body = result[2].read()
+ self.failIfEqual(body.find('\r\n\r\n'), -1,
+ "Can't find \\r\\n\\r\\n between header and body")
+ return result
+
+ d.addCallback(cbArticle)
+ return d
+
+
+ def testHeadRequest(self):
+ d = self.testArticleRequest()
+
+ def cbArticle(result):
+ index = result[0]
+
+ d = self.backend.headRequest("alt.test.nntp", index)
+ d.addCallback(cbHead)
+ return d
+
+ def cbHead(result):
+ self.assertEquals(result[1], MESSAGE_ID,
+ "callback result Message-Id doesn't match: %s vs %s" %
+ (MESSAGE_ID, result[1]))
+
+ self.assertEquals(result[2][-2:], '\r\n',
+ "headers must be \\r\\n terminated.")
+
+ d.addCallback(cbArticle)
+ return d
+
+
+ def testBodyRequest(self):
+ d = self.testArticleRequest()
+
+ def cbArticle(result):
+ index = result[0]
+
+ d = self.backend.bodyRequest("alt.test.nntp", index)
+ d.addCallback(cbBody)
+ return d
+
+ def cbBody(result):
+ body = result[2].read()
+ self.assertEquals(body[0:4], 'this',
+ "message body has been altered: " +
+ pformat(body[0:4]))
+
+ d.addCallback(cbArticle)
+ return d
diff --git a/vendor/Twisted-10.0.0/twisted/news/test/test_nntp.py b/vendor/Twisted-10.0.0/twisted/news/test/test_nntp.py
new file mode 100644
index 0000000000..5a061db96d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/test/test_nntp.py
@@ -0,0 +1,124 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.trial import unittest
+from twisted.news import database
+from twisted.news import nntp
+from twisted.protocols import loopback
+
+ALL_GROUPS = ('alt.test.nntp', 0, 1, 'y'),
+GROUP = ('0', '1', '0', 'alt.test.nntp', 'group', 'selected')
+SUBSCRIPTIONS = ['alt.test.nntp', 'news.testgroup']
+
+POST_STRING = """Path: not-for-mail
+From: <exarkun@somehost.domain.com>
+Subject: a test
+Newsgroups: alt.test.nntp
+Organization:
+Summary:
+Keywords:
+User-Agent: tin/1.4.5-20010409 ("One More Nightmare") (UNIX) (Linux/2.4.17 (i686))
+
+this is a test
+.
+..
+...
+lala
+moo
+--
+"One World, one Web, one Program." - Microsoft(R) promotional ad
+"Ein Volk, ein Reich, ein Fuhrer." - Adolf Hitler
+--
+ 10:56pm up 4 days, 4:42, 1 user, load average: 0.08, 0.08, 0.12
+"""
+
+class TestNNTPClient(nntp.NNTPClient):
+ def __init__(self):
+ nntp.NNTPClient.__init__(self)
+
+ def assertEquals(self, foo, bar):
+ if foo != bar: raise AssertionError("%r != %r!" % (foo, bar))
+
+ def connectionMade(self):
+ nntp.NNTPClient.connectionMade(self)
+ self.fetchSubscriptions()
+
+
+ def gotSubscriptions(self, subscriptions):
+ self.assertEquals(len(subscriptions), len(SUBSCRIPTIONS))
+ for s in subscriptions:
+ assert s in SUBSCRIPTIONS
+
+ self.fetchGroups()
+
+ def gotAllGroups(self, info):
+ self.assertEquals(len(info), len(ALL_GROUPS))
+ self.assertEquals(info[0], ALL_GROUPS[0])
+
+ self.fetchGroup('alt.test.nntp')
+
+
+ def getAllGroupsFailed(self, error):
+ raise AssertionError("fetchGroups() failed: %s" % (error,))
+
+
+ def gotGroup(self, info):
+ self.assertEquals(len(info), 6)
+ self.assertEquals(info, GROUP)
+
+ self.postArticle(POST_STRING)
+
+
+ def getSubscriptionsFailed(self, error):
+ raise AssertionError("fetchSubscriptions() failed: %s" % (error,))
+
+
+ def getGroupFailed(self, error):
+ raise AssertionError("fetchGroup() failed: %s" % (error,))
+
+
+ def postFailed(self, error):
+ raise AssertionError("postArticle() failed: %s" % (error,))
+
+
+ def postedOk(self):
+ self.fetchArticle(1)
+
+
+ def gotArticle(self, info):
+ origBody = POST_STRING.split('\n\n')[1]
+ newBody = info.split('\n\n', 1)[1]
+
+ self.assertEquals(origBody, newBody)
+
+ # We're done
+ self.transport.loseConnection()
+
+
+ def getArticleFailed(self, error):
+ raise AssertionError("fetchArticle() failed: %s" % (error,))
+
+
+class NNTPTestCase(unittest.TestCase):
+ def setUp(self):
+ self.server = nntp.NNTPServer()
+ self.server.factory = self
+ self.backend = database.NewsShelf(None, 'news.db')
+ self.backend.addGroup('alt.test.nntp', 'y')
+
+ for s in SUBSCRIPTIONS:
+ self.backend.addSubscription(s)
+
+ self.client = TestNNTPClient()
+
+ def testLoopback(self):
+ return loopback.loopbackAsync(self.server, self.client)
+
+ # XXX This test is woefully incomplete. It tests the single
+ # most common code path and nothing else. Expand it and the
+ # test fairy will leave you a surprise.
+
+ # reactor.iterate(1) # fetchGroups()
+ # reactor.iterate(1) # fetchGroup()
+ # reactor.iterate(1) # postArticle()
+
diff --git a/vendor/Twisted-10.0.0/twisted/news/topfiles/NEWS b/vendor/Twisted-10.0.0/twisted/news/topfiles/NEWS
new file mode 100644
index 0000000000..944c867f60
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/topfiles/NEWS
@@ -0,0 +1,54 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/<number>
+
+Twisted News 10.0.0 (2010-03-01)
+================================
+
+No interesting changes since Twisted 9.0.
+
+
+Twisted News 9.0.0 (2009-11-24)
+===============================
+
+Other
+-----
+ - #2763, #3540
+
+
+News 8.2.0 (2008-12-16)
+=======================
+
+No interesting changes since Twisted 8.0.
+
+
+8.1.0 (2008-05-18)
+==================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+8.0.0 (2008-03-17)
+==================
+
+Misc
+----
+ - Remove all "API Stability" markers (#2847)
+
+
+0.3.0 (2007-01-06)
+==================
+Fixes
+-----
+ - News was updated to work with the latest twisted.components changes
+ to Twisted (#1636)
+ - The 'ip' attribute is no longer available on NNTP protocols (#1936)
+
+
+0.2.0 (2006-05-24)
+==================
+
+Fixes:
+ - Fixed a critical bug in moderation support.
+
diff --git a/vendor/Twisted-10.0.0/twisted/news/topfiles/README b/vendor/Twisted-10.0.0/twisted/news/topfiles/README
new file mode 100644
index 0000000000..a9c1832771
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/topfiles/README
@@ -0,0 +1,4 @@
+Twisted News 10.0.0
+
+News depends on Twisted, and, if you want to use the moderation
+features, Twisted Mail.
diff --git a/vendor/Twisted-10.0.0/twisted/news/topfiles/setup.py b/vendor/Twisted-10.0.0/twisted/news/topfiles/setup.py
new file mode 100644
index 0000000000..c0a8abeb43
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/news/topfiles/setup.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+try:
+ from twisted.python import dist
+except ImportError:
+ raise SystemExit("twisted.python.dist module not found. Make sure you "
+ "have installed the Twisted core package before "
+ "attempting to install any other Twisted projects.")
+
+if __name__ == '__main__':
+ dist.setup(
+ twisted_subproject="news",
+ # metadata
+ name="Twisted News",
+ description="Twisted News is an NNTP server and programming library.",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="Jp Calderone",
+ url="http://twistedmatrix.com/trac/wiki/TwistedNews",
+ license="MIT",
+ long_description="""\
+Twisted News is an NNTP protocol (Usenet) programming library. The
+library contains server and client protocol implementations. A simple
+NNTP server is also provided.
+""",
+ )
+
diff --git a/vendor/Twisted-10.0.0/twisted/pair/__init__.py b/vendor/Twisted-10.0.0/twisted/pair/__init__.py
new file mode 100644
index 0000000000..9cf6a5f01f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/__init__.py
@@ -0,0 +1,20 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+
+Twisted Pair: The framework of your ethernet.
+
+Low-level networking transports and utilities.
+
+See also twisted.protocols.ethernet, twisted.protocols.ip,
+twisted.protocols.raw and twisted.protocols.rawudp.
+
+Maintainer: Tommi Virtanen
+
+"""
+
+from twisted.pair._version import version
+__version__ = version.short()
diff --git a/vendor/Twisted-10.0.0/twisted/pair/_version.py b/vendor/Twisted-10.0.0/twisted/pair/_version.py
new file mode 100644
index 0000000000..293c2c85cd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/_version.py
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twisted.pair', 10, 0, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/pair/ethernet.py b/vendor/Twisted-10.0.0/twisted/pair/ethernet.py
new file mode 100644
index 0000000000..6d5c36ed9e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/ethernet.py
@@ -0,0 +1,56 @@
+# -*- test-case-name: twisted.pair.test.test_ethernet -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+
+"""Support for working directly with ethernet frames"""
+
+import struct
+
+
+from twisted.internet import protocol
+from twisted.pair import raw
+from zope.interface import implements, Interface
+
+
+class IEthernetProtocol(Interface):
+ """An interface for protocols that handle Ethernet frames"""
+ def addProto():
+ """Add an IRawPacketProtocol protocol"""
+
+ def datagramReceived():
+ """An Ethernet frame has been received"""
+
+class EthernetHeader:
+ def __init__(self, data):
+
+ (self.dest, self.source, self.proto) \
+ = struct.unpack("!6s6sH", data[:6+6+2])
+
+class EthernetProtocol(protocol.AbstractDatagramProtocol):
+
+ implements(IEthernetProtocol)
+
+ def __init__(self):
+ self.etherProtos = {}
+
+ def addProto(self, num, proto):
+ proto = raw.IRawPacketProtocol(proto)
+ if num < 0:
+ raise TypeError, 'Added protocol must be positive or zero'
+ if num >= 2**16:
+ raise TypeError, 'Added protocol must fit in 16 bits'
+ if num not in self.etherProtos:
+ self.etherProtos[num] = []
+ self.etherProtos[num].append(proto)
+
+ def datagramReceived(self, data, partial=0):
+ header = EthernetHeader(data[:14])
+ for proto in self.etherProtos.get(header.proto, ()):
+ proto.datagramReceived(data=data[14:],
+ partial=partial,
+ dest=header.dest,
+ source=header.source,
+ protocol=header.proto)
diff --git a/vendor/Twisted-10.0.0/twisted/pair/ip.py b/vendor/Twisted-10.0.0/twisted/pair/ip.py
new file mode 100644
index 0000000000..245a7d92b8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/ip.py
@@ -0,0 +1,72 @@
+# -*- test-case-name: twisted.pair.test.test_ip -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+
+"""Support for working directly with IP packets"""
+
+import struct
+import socket
+
+from twisted.internet import protocol
+from twisted.pair import raw
+from zope.interface import implements
+
+
+class IPHeader:
+ def __init__(self, data):
+
+ (ihlversion, self.tos, self.tot_len, self.fragment_id, frag_off,
+ self.ttl, self.protocol, self.check, saddr, daddr) \
+ = struct.unpack("!BBHHHBBH4s4s", data[:20])
+ self.saddr = socket.inet_ntoa(saddr)
+ self.daddr = socket.inet_ntoa(daddr)
+ self.version = ihlversion & 0x0F
+ self.ihl = ((ihlversion & 0xF0) >> 4) << 2
+ self.fragment_offset = frag_off & 0x1FFF
+ self.dont_fragment = (frag_off & 0x4000 != 0)
+ self.more_fragments = (frag_off & 0x2000 != 0)
+
+MAX_SIZE = 2L**32
+
+class IPProtocol(protocol.AbstractDatagramProtocol):
+ implements(raw.IRawPacketProtocol)
+
+ def __init__(self):
+ self.ipProtos = {}
+
+ def addProto(self, num, proto):
+ proto = raw.IRawDatagramProtocol(proto)
+ if num < 0:
+ raise TypeError, 'Added protocol must be positive or zero'
+ if num >= MAX_SIZE:
+ raise TypeError, 'Added protocol must fit in 32 bits'
+ if num not in self.ipProtos:
+ self.ipProtos[num] = []
+ self.ipProtos[num].append(proto)
+
+ def datagramReceived(self,
+ data,
+ partial,
+ dest,
+ source,
+ protocol):
+ header = IPHeader(data)
+ for proto in self.ipProtos.get(header.protocol, ()):
+ proto.datagramReceived(data=data[20:],
+ partial=partial,
+ source=header.saddr,
+ dest=header.daddr,
+ protocol=header.protocol,
+ version=header.version,
+ ihl=header.ihl,
+ tos=header.tos,
+ tot_len=header.tot_len,
+ fragment_id=header.fragment_id,
+ fragment_offset=header.fragment_offset,
+ dont_fragment=header.dont_fragment,
+ more_fragments=header.more_fragments,
+ ttl=header.ttl,
+ )
diff --git a/vendor/Twisted-10.0.0/twisted/pair/raw.py b/vendor/Twisted-10.0.0/twisted/pair/raw.py
new file mode 100644
index 0000000000..a1866a833c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/raw.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""Interface definitions for working with raw packets"""
+
+from twisted.internet import protocol
+from zope.interface import Interface
+
+class IRawDatagramProtocol(Interface):
+ """An interface for protocols such as UDP, ICMP and TCP."""
+
+ def addProto():
+ """
+ Add a protocol on top of this one.
+ """
+
+ def datagramReceived():
+ """
+ An IP datagram has been received. Parse and process it.
+ """
+
+class IRawPacketProtocol(Interface):
+ """An interface for low-level protocols such as IP and ARP."""
+
+ def addProto():
+ """
+ Add a protocol on top of this one.
+ """
+
+ def datagramReceived():
+ """
+ An IP datagram has been received. Parse and process it.
+ """
diff --git a/vendor/Twisted-10.0.0/twisted/pair/rawudp.py b/vendor/Twisted-10.0.0/twisted/pair/rawudp.py
new file mode 100644
index 0000000000..42f1d2fe91
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/rawudp.py
@@ -0,0 +1,55 @@
+# -*- test-case-name: twisted.pair.test.test_rawudp -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""Implementation of raw packet interfaces for UDP"""
+
+import struct
+
+from twisted.internet import protocol
+from twisted.pair import raw
+from zope.interface import implements
+
+class UDPHeader:
+ def __init__(self, data):
+
+ (self.source, self.dest, self.len, self.check) \
+ = struct.unpack("!HHHH", data[:8])
+
+class RawUDPProtocol(protocol.AbstractDatagramProtocol):
+ implements(raw.IRawDatagramProtocol)
+ def __init__(self):
+ self.udpProtos = {}
+
+ def addProto(self, num, proto):
+ if not isinstance(proto, protocol.DatagramProtocol):
+ raise TypeError, 'Added protocol must be an instance of DatagramProtocol'
+ if num < 0:
+ raise TypeError, 'Added protocol must be positive or zero'
+ if num >= 2**16:
+ raise TypeError, 'Added protocol must fit in 16 bits'
+ if num not in self.udpProtos:
+ self.udpProtos[num] = []
+ self.udpProtos[num].append(proto)
+
+ def datagramReceived(self,
+ data,
+ partial,
+ source,
+ dest,
+ protocol,
+ version,
+ ihl,
+ tos,
+ tot_len,
+ fragment_id,
+ fragment_offset,
+ dont_fragment,
+ more_fragments,
+ ttl):
+ header = UDPHeader(data)
+ for proto in self.udpProtos.get(header.dest, ()):
+ proto.datagramReceived(data[8:],
+ (source, header.source))
diff --git a/vendor/Twisted-10.0.0/twisted/pair/test/__init__.py b/vendor/Twisted-10.0.0/twisted/pair/test/__init__.py
new file mode 100644
index 0000000000..5aa286eb4e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/test/__init__.py
@@ -0,0 +1 @@
+'pair tests'
diff --git a/vendor/Twisted-10.0.0/twisted/pair/test/test_ethernet.py b/vendor/Twisted-10.0.0/twisted/pair/test/test_ethernet.py
new file mode 100644
index 0000000000..1cd1619371
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/test/test_ethernet.py
@@ -0,0 +1,226 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.trial import unittest
+
+from twisted.internet import protocol, reactor, error
+from twisted.python import failure, components
+from twisted.pair import ethernet, raw
+from zope.interface import implements
+
+class MyProtocol:
+ implements(raw.IRawPacketProtocol)
+
+ def __init__(self, expecting):
+ self.expecting = list(expecting)
+
+ def datagramReceived(self, data, **kw):
+ assert self.expecting, 'Got a packet when not expecting anymore.'
+ expect = self.expecting.pop(0)
+ assert expect == (data, kw), \
+ "Expected %r, got %r" % (
+ expect, (data, kw),
+ )
+
+class EthernetTestCase(unittest.TestCase):
+ def testPacketParsing(self):
+ proto = ethernet.EthernetProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': "123456",
+ 'source': "987654",
+ 'protocol': 0x0800,
+ }),
+
+ ])
+ proto.addProto(0x0800, p1)
+
+ proto.datagramReceived("123456987654\x08\x00foobar",
+ partial=0)
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+
+
+ def testMultiplePackets(self):
+ proto = ethernet.EthernetProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': "123456",
+ 'source': "987654",
+ 'protocol': 0x0800,
+ }),
+
+ ('quux', {
+ 'partial': 1,
+ 'dest': "012345",
+ 'source': "abcdef",
+ 'protocol': 0x0800,
+ }),
+
+ ])
+ proto.addProto(0x0800, p1)
+
+ proto.datagramReceived("123456987654\x08\x00foobar",
+ partial=0)
+ proto.datagramReceived("012345abcdef\x08\x00quux",
+ partial=1)
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+
+
+ def testMultipleSameProtos(self):
+ proto = ethernet.EthernetProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': "123456",
+ 'source': "987654",
+ 'protocol': 0x0800,
+ }),
+
+ ])
+
+ p2 = MyProtocol([
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': "123456",
+ 'source': "987654",
+ 'protocol': 0x0800,
+ }),
+
+ ])
+
+ proto.addProto(0x0800, p1)
+ proto.addProto(0x0800, p2)
+
+ proto.datagramReceived("123456987654\x08\x00foobar",
+ partial=0)
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+ assert not p2.expecting, \
+ 'Should not expect any more packets, but still want %r' % p2.expecting
+
+ def testWrongProtoNotSeen(self):
+ proto = ethernet.EthernetProtocol()
+ p1 = MyProtocol([])
+ proto.addProto(0x0801, p1)
+
+ proto.datagramReceived("123456987654\x08\x00foobar",
+ partial=0)
+ proto.datagramReceived("012345abcdef\x08\x00quux",
+ partial=1)
+
+ def testDemuxing(self):
+ proto = ethernet.EthernetProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': "123456",
+ 'source': "987654",
+ 'protocol': 0x0800,
+ }),
+
+ ('quux', {
+ 'partial': 1,
+ 'dest': "012345",
+ 'source': "abcdef",
+ 'protocol': 0x0800,
+ }),
+
+ ])
+ proto.addProto(0x0800, p1)
+
+ p2 = MyProtocol([
+
+ ('quux', {
+ 'partial': 1,
+ 'dest': "012345",
+ 'source': "abcdef",
+ 'protocol': 0x0806,
+ }),
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': "123456",
+ 'source': "987654",
+ 'protocol': 0x0806,
+ }),
+
+ ])
+ proto.addProto(0x0806, p2)
+
+ proto.datagramReceived("123456987654\x08\x00foobar",
+ partial=0)
+ proto.datagramReceived("012345abcdef\x08\x06quux",
+ partial=1)
+ proto.datagramReceived("123456987654\x08\x06foobar",
+ partial=0)
+ proto.datagramReceived("012345abcdef\x08\x00quux",
+ partial=1)
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+ assert not p2.expecting, \
+ 'Should not expect any more packets, but still want %r' % p2.expecting
+
+ def testAddingBadProtos_WrongLevel(self):
+ """Adding a wrong level protocol raises an exception."""
+ e = ethernet.EthernetProtocol()
+ try:
+ e.addProto(42, "silliness")
+ except components.CannotAdapt:
+ pass
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
+
+
+ def testAddingBadProtos_TooSmall(self):
+ """Adding a protocol with a negative number raises an exception."""
+ e = ethernet.EthernetProtocol()
+ try:
+ e.addProto(-1, MyProtocol([]))
+ except TypeError, e:
+ if e.args == ('Added protocol must be positive or zero',):
+ pass
+ else:
+ raise
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
+
+
+ def testAddingBadProtos_TooBig(self):
+ """Adding a protocol with a number >=2**16 raises an exception."""
+ e = ethernet.EthernetProtocol()
+ try:
+ e.addProto(2**16, MyProtocol([]))
+ except TypeError, e:
+ if e.args == ('Added protocol must fit in 16 bits',):
+ pass
+ else:
+ raise
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
+
+ def testAddingBadProtos_TooBig2(self):
+ """Adding a protocol with a number >=2**16 raises an exception."""
+ e = ethernet.EthernetProtocol()
+ try:
+ e.addProto(2**16+1, MyProtocol([]))
+ except TypeError, e:
+ if e.args == ('Added protocol must fit in 16 bits',):
+ pass
+ else:
+ raise
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
diff --git a/vendor/Twisted-10.0.0/twisted/pair/test/test_ip.py b/vendor/Twisted-10.0.0/twisted/pair/test/test_ip.py
new file mode 100644
index 0000000000..321ba10c00
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/test/test_ip.py
@@ -0,0 +1,417 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.trial import unittest
+
+from twisted.internet import protocol, reactor, error
+from twisted.python import failure, components
+from twisted.pair import ip, raw
+from zope import interface
+
+class MyProtocol:
+ interface.implements(raw.IRawDatagramProtocol)
+
+ def __init__(self, expecting):
+ self.expecting = list(expecting)
+
+ def datagramReceived(self, data, **kw):
+ assert self.expecting, 'Got a packet when not expecting anymore.'
+ expectData, expectKw = self.expecting.pop(0)
+
+ expectKwKeys = expectKw.keys(); expectKwKeys.sort()
+ kwKeys = kw.keys(); kwKeys.sort()
+ assert expectKwKeys == kwKeys, "Expected %r, got %r" % (expectKwKeys, kwKeys)
+
+ for k in expectKwKeys:
+ assert expectKw[k] == kw[k], "Expected %s=%r, got %r" % (k, expectKw[k], kw[k])
+ assert expectKw == kw, "Expected %r, got %r" % (expectKw, kw)
+ assert expectData == data, "Expected %r, got %r" % (expectData, data)
+
+class IPTestCase(unittest.TestCase):
+ def testPacketParsing(self):
+ proto = ip.IPProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': '1.2.3.4',
+ 'source': '5.6.7.8',
+ 'protocol': 0x0F,
+ 'version': 4,
+ 'ihl': 20,
+ 'tos': 7,
+ 'tot_len': 20+6,
+ 'fragment_id': 0xDEAD,
+ 'fragment_offset': 0x1EEF,
+ 'dont_fragment': 0,
+ 'more_fragments': 1,
+ 'ttl': 0xC0,
+ }),
+
+ ])
+ proto.addProto(0x0F, p1)
+
+ proto.datagramReceived("\x54" #ihl version
+ + "\x07" #tos
+ + "\x00\x1a" #tot_len
+ + "\xDE\xAD" #id
+ + "\xBE\xEF" #frag_off
+ + "\xC0" #ttl
+ + "\x0F" #protocol
+ + "FE" #checksum
+ + "\x05\x06\x07\x08" + "\x01\x02\x03\x04" + "foobar",
+ partial=0,
+ dest='dummy',
+ source='dummy',
+ protocol='dummy',
+ )
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+
+ def testMultiplePackets(self):
+ proto = ip.IPProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': '1.2.3.4',
+ 'source': '5.6.7.8',
+ 'protocol': 0x0F,
+ 'version': 4,
+ 'ihl': 20,
+ 'tos': 7,
+ 'tot_len': 20+6,
+ 'fragment_id': 0xDEAD,
+ 'fragment_offset': 0x1EEF,
+ 'dont_fragment': 0,
+ 'more_fragments': 1,
+ 'ttl': 0xC0,
+ }),
+
+ ('quux', {
+ 'partial': 1,
+ 'dest': '5.4.3.2',
+ 'source': '6.7.8.9',
+ 'protocol': 0x0F,
+ 'version': 4,
+ 'ihl': 20,
+ 'tos': 7,
+ 'tot_len': 20+6,
+ 'fragment_id': 0xDEAD,
+ 'fragment_offset': 0x1EEF,
+ 'dont_fragment': 0,
+ 'more_fragments': 1,
+ 'ttl': 0xC0,
+ }),
+
+ ])
+ proto.addProto(0x0F, p1)
+ proto.datagramReceived("\x54" #ihl version
+ + "\x07" #tos
+ + "\x00\x1a" #tot_len
+ + "\xDE\xAD" #id
+ + "\xBE\xEF" #frag_off
+ + "\xC0" #ttl
+ + "\x0F" #protocol
+ + "FE" #checksum
+ + "\x05\x06\x07\x08" + "\x01\x02\x03\x04" + "foobar",
+ partial=0,
+ dest='dummy',
+ source='dummy',
+ protocol='dummy',
+ )
+ proto.datagramReceived("\x54" #ihl version
+ + "\x07" #tos
+ + "\x00\x1a" #tot_len
+ + "\xDE\xAD" #id
+ + "\xBE\xEF" #frag_off
+ + "\xC0" #ttl
+ + "\x0F" #protocol
+ + "FE" #checksum
+ + "\x06\x07\x08\x09" + "\x05\x04\x03\x02" + "quux",
+ partial=1,
+ dest='dummy',
+ source='dummy',
+ protocol='dummy',
+ )
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+
+
+ def testMultipleSameProtos(self):
+ proto = ip.IPProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': '1.2.3.4',
+ 'source': '5.6.7.8',
+ 'protocol': 0x0F,
+ 'version': 4,
+ 'ihl': 20,
+ 'tos': 7,
+ 'tot_len': 20+6,
+ 'fragment_id': 0xDEAD,
+ 'fragment_offset': 0x1EEF,
+ 'dont_fragment': 0,
+ 'more_fragments': 1,
+ 'ttl': 0xC0,
+ }),
+
+ ])
+
+ p2 = MyProtocol([
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': '1.2.3.4',
+ 'source': '5.6.7.8',
+ 'protocol': 0x0F,
+ 'version': 4,
+ 'ihl': 20,
+ 'tos': 7,
+ 'tot_len': 20+6,
+ 'fragment_id': 0xDEAD,
+ 'fragment_offset': 0x1EEF,
+ 'dont_fragment': 0,
+ 'more_fragments': 1,
+ 'ttl': 0xC0,
+ }),
+
+ ])
+
+ proto.addProto(0x0F, p1)
+ proto.addProto(0x0F, p2)
+
+ proto.datagramReceived("\x54" #ihl version
+ + "\x07" #tos
+ + "\x00\x1a" #tot_len
+ + "\xDE\xAD" #id
+ + "\xBE\xEF" #frag_off
+ + "\xC0" #ttl
+ + "\x0F" #protocol
+ + "FE" #checksum
+ + "\x05\x06\x07\x08" + "\x01\x02\x03\x04" + "foobar",
+ partial=0,
+ dest='dummy',
+ source='dummy',
+ protocol='dummy',
+ )
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+ assert not p2.expecting, \
+ 'Should not expect any more packets, but still want %r' % p2.expecting
+
+ def testWrongProtoNotSeen(self):
+ proto = ip.IPProtocol()
+ p1 = MyProtocol([])
+ proto.addProto(1, p1)
+
+ proto.datagramReceived("\x54" #ihl version
+ + "\x07" #tos
+ + "\x00\x1a" #tot_len
+ + "\xDE\xAD" #id
+ + "\xBE\xEF" #frag_off
+ + "\xC0" #ttl
+ + "\x0F" #protocol
+ + "FE" #checksum
+ + "\x05\x06\x07\x08" + "\x01\x02\x03\x04" + "foobar",
+ partial=0,
+ dest='dummy',
+ source='dummy',
+ protocol='dummy',
+ )
+
+ def testDemuxing(self):
+ proto = ip.IPProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': '1.2.3.4',
+ 'source': '5.6.7.8',
+ 'protocol': 0x0F,
+ 'version': 4,
+ 'ihl': 20,
+ 'tos': 7,
+ 'tot_len': 20+6,
+ 'fragment_id': 0xDEAD,
+ 'fragment_offset': 0x1EEF,
+ 'dont_fragment': 0,
+ 'more_fragments': 1,
+ 'ttl': 0xC0,
+ }),
+
+ ('quux', {
+ 'partial': 1,
+ 'dest': '5.4.3.2',
+ 'source': '6.7.8.9',
+ 'protocol': 0x0F,
+ 'version': 4,
+ 'ihl': 20,
+ 'tos': 7,
+ 'tot_len': 20+6,
+ 'fragment_id': 0xDEAD,
+ 'fragment_offset': 0x1EEF,
+ 'dont_fragment': 0,
+ 'more_fragments': 1,
+ 'ttl': 0xC0,
+ }),
+
+ ])
+ proto.addProto(0x0F, p1)
+
+ p2 = MyProtocol([
+
+ ('quux', {
+ 'partial': 1,
+ 'dest': '5.4.3.2',
+ 'source': '6.7.8.9',
+ 'protocol': 0x0A,
+ 'version': 4,
+ 'ihl': 20,
+ 'tos': 7,
+ 'tot_len': 20+6,
+ 'fragment_id': 0xDEAD,
+ 'fragment_offset': 0x1EEF,
+ 'dont_fragment': 0,
+ 'more_fragments': 1,
+ 'ttl': 0xC0,
+ }),
+
+ ('foobar', {
+ 'partial': 0,
+ 'dest': '1.2.3.4',
+ 'source': '5.6.7.8',
+ 'protocol': 0x0A,
+ 'version': 4,
+ 'ihl': 20,
+ 'tos': 7,
+ 'tot_len': 20+6,
+ 'fragment_id': 0xDEAD,
+ 'fragment_offset': 0x1EEF,
+ 'dont_fragment': 0,
+ 'more_fragments': 1,
+ 'ttl': 0xC0,
+ }),
+
+
+ ])
+ proto.addProto(0x0A, p2)
+
+ proto.datagramReceived("\x54" #ihl version
+ + "\x07" #tos
+ + "\x00\x1a" #tot_len
+ + "\xDE\xAD" #id
+ + "\xBE\xEF" #frag_off
+ + "\xC0" #ttl
+ + "\x0A" #protocol
+ + "FE" #checksum
+ + "\x06\x07\x08\x09" + "\x05\x04\x03\x02" + "quux",
+ partial=1,
+ dest='dummy',
+ source='dummy',
+ protocol='dummy',
+ )
+ proto.datagramReceived("\x54" #ihl version
+ + "\x07" #tos
+ + "\x00\x1a" #tot_len
+ + "\xDE\xAD" #id
+ + "\xBE\xEF" #frag_off
+ + "\xC0" #ttl
+ + "\x0F" #protocol
+ + "FE" #checksum
+ + "\x05\x06\x07\x08" + "\x01\x02\x03\x04" + "foobar",
+ partial=0,
+ dest='dummy',
+ source='dummy',
+ protocol='dummy',
+ )
+ proto.datagramReceived("\x54" #ihl version
+ + "\x07" #tos
+ + "\x00\x1a" #tot_len
+ + "\xDE\xAD" #id
+ + "\xBE\xEF" #frag_off
+ + "\xC0" #ttl
+ + "\x0F" #protocol
+ + "FE" #checksum
+ + "\x06\x07\x08\x09" + "\x05\x04\x03\x02" + "quux",
+ partial=1,
+ dest='dummy',
+ source='dummy',
+ protocol='dummy',
+ )
+ proto.datagramReceived("\x54" #ihl version
+ + "\x07" #tos
+ + "\x00\x1a" #tot_len
+ + "\xDE\xAD" #id
+ + "\xBE\xEF" #frag_off
+ + "\xC0" #ttl
+ + "\x0A" #protocol
+ + "FE" #checksum
+ + "\x05\x06\x07\x08" + "\x01\x02\x03\x04" + "foobar",
+ partial=0,
+ dest='dummy',
+ source='dummy',
+ protocol='dummy',
+ )
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+ assert not p2.expecting, \
+ 'Should not expect any more packets, but still want %r' % p2.expecting
+
+ def testAddingBadProtos_WrongLevel(self):
+ """Adding a wrong level protocol raises an exception."""
+ e = ip.IPProtocol()
+ try:
+ e.addProto(42, "silliness")
+ except components.CannotAdapt:
+ pass
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
+
+
+ def testAddingBadProtos_TooSmall(self):
+ """Adding a protocol with a negative number raises an exception."""
+ e = ip.IPProtocol()
+ try:
+ e.addProto(-1, MyProtocol([]))
+ except TypeError, e:
+ if e.args == ('Added protocol must be positive or zero',):
+ pass
+ else:
+ raise
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
+
+
+ def testAddingBadProtos_TooBig(self):
+ """Adding a protocol with a number >=2**32 raises an exception."""
+ e = ip.IPProtocol()
+ try:
+ e.addProto(2L**32, MyProtocol([]))
+ except TypeError, e:
+ if e.args == ('Added protocol must fit in 32 bits',):
+ pass
+ else:
+ raise
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
+
+ def testAddingBadProtos_TooBig2(self):
+ """Adding a protocol with a number >=2**32 raises an exception."""
+ e = ip.IPProtocol()
+ try:
+ e.addProto(2L**32+1, MyProtocol([]))
+ except TypeError, e:
+ if e.args == ('Added protocol must fit in 32 bits',):
+ pass
+ else:
+ raise
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
diff --git a/vendor/Twisted-10.0.0/twisted/pair/test/test_rawudp.py b/vendor/Twisted-10.0.0/twisted/pair/test/test_rawudp.py
new file mode 100644
index 0000000000..8e4bc4ddd7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/test/test_rawudp.py
@@ -0,0 +1,327 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.trial import unittest
+
+from twisted.internet import protocol, reactor, error
+from twisted.python import failure
+from twisted.pair import rawudp
+
+class MyProtocol(protocol.DatagramProtocol):
+ def __init__(self, expecting):
+ self.expecting = list(expecting)
+
+ def datagramReceived(self, data, (host, port)):
+ assert self.expecting, 'Got a packet when not expecting anymore.'
+ expectData, expectHost, expectPort = self.expecting.pop(0)
+
+ assert expectData == data, "Expected data %r, got %r" % (expectData, data)
+ assert expectHost == host, "Expected host %r, got %r" % (expectHost, host)
+ assert expectPort == port, "Expected port %d=0x%04x, got %d=0x%04x" % (expectPort, expectPort, port, port)
+
+class RawUDPTestCase(unittest.TestCase):
+ def testPacketParsing(self):
+ proto = rawudp.RawUDPProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', 'testHost', 0x43A2),
+
+ ])
+ proto.addProto(0xF00F, p1)
+
+ proto.datagramReceived("\x43\xA2" #source
+ + "\xf0\x0f" #dest
+ + "\x00\x06" #len
+ + "\xDE\xAD" #check
+ + "foobar",
+ partial=0,
+ dest='dummy',
+ source='testHost',
+ protocol='dummy',
+ version='dummy',
+ ihl='dummy',
+ tos='dummy',
+ tot_len='dummy',
+ fragment_id='dummy',
+ fragment_offset='dummy',
+ dont_fragment='dummy',
+ more_fragments='dummy',
+ ttl='dummy',
+ )
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+
+ def testMultiplePackets(self):
+ proto = rawudp.RawUDPProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', 'testHost', 0x43A2),
+ ('quux', 'otherHost', 0x33FE),
+
+ ])
+ proto.addProto(0xF00F, p1)
+ proto.datagramReceived("\x43\xA2" #source
+ + "\xf0\x0f" #dest
+ + "\x00\x06" #len
+ + "\xDE\xAD" #check
+ + "foobar",
+ partial=0,
+ dest='dummy',
+ source='testHost',
+ protocol='dummy',
+ version='dummy',
+ ihl='dummy',
+ tos='dummy',
+ tot_len='dummy',
+ fragment_id='dummy',
+ fragment_offset='dummy',
+ dont_fragment='dummy',
+ more_fragments='dummy',
+ ttl='dummy',
+ )
+ proto.datagramReceived("\x33\xFE" #source
+ + "\xf0\x0f" #dest
+ + "\x00\x05" #len
+ + "\xDE\xAD" #check
+ + "quux",
+ partial=0,
+ dest='dummy',
+ source='otherHost',
+ protocol='dummy',
+ version='dummy',
+ ihl='dummy',
+ tos='dummy',
+ tot_len='dummy',
+ fragment_id='dummy',
+ fragment_offset='dummy',
+ dont_fragment='dummy',
+ more_fragments='dummy',
+ ttl='dummy',
+ )
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+
+
+ def testMultipleSameProtos(self):
+ proto = rawudp.RawUDPProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', 'testHost', 0x43A2),
+
+ ])
+
+ p2 = MyProtocol([
+
+ ('foobar', 'testHost', 0x43A2),
+
+ ])
+
+ proto.addProto(0xF00F, p1)
+ proto.addProto(0xF00F, p2)
+
+ proto.datagramReceived("\x43\xA2" #source
+ + "\xf0\x0f" #dest
+ + "\x00\x06" #len
+ + "\xDE\xAD" #check
+ + "foobar",
+ partial=0,
+ dest='dummy',
+ source='testHost',
+ protocol='dummy',
+ version='dummy',
+ ihl='dummy',
+ tos='dummy',
+ tot_len='dummy',
+ fragment_id='dummy',
+ fragment_offset='dummy',
+ dont_fragment='dummy',
+ more_fragments='dummy',
+ ttl='dummy',
+ )
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+ assert not p2.expecting, \
+ 'Should not expect any more packets, but still want %r' % p2.expecting
+
+ def testWrongProtoNotSeen(self):
+ proto = rawudp.RawUDPProtocol()
+ p1 = MyProtocol([])
+ proto.addProto(1, p1)
+
+ proto.datagramReceived("\x43\xA2" #source
+ + "\xf0\x0f" #dest
+ + "\x00\x06" #len
+ + "\xDE\xAD" #check
+ + "foobar",
+ partial=0,
+ dest='dummy',
+ source='testHost',
+ protocol='dummy',
+ version='dummy',
+ ihl='dummy',
+ tos='dummy',
+ tot_len='dummy',
+ fragment_id='dummy',
+ fragment_offset='dummy',
+ dont_fragment='dummy',
+ more_fragments='dummy',
+ ttl='dummy',
+ )
+
+ def testDemuxing(self):
+ proto = rawudp.RawUDPProtocol()
+ p1 = MyProtocol([
+
+ ('foobar', 'testHost', 0x43A2),
+ ('quux', 'otherHost', 0x33FE),
+
+ ])
+ proto.addProto(0xF00F, p1)
+
+ p2 = MyProtocol([
+
+ ('quux', 'otherHost', 0xA401),
+ ('foobar', 'testHost', 0xA302),
+
+ ])
+ proto.addProto(0xB050, p2)
+
+ proto.datagramReceived("\xA4\x01" #source
+ + "\xB0\x50" #dest
+ + "\x00\x05" #len
+ + "\xDE\xAD" #check
+ + "quux",
+ partial=0,
+ dest='dummy',
+ source='otherHost',
+ protocol='dummy',
+ version='dummy',
+ ihl='dummy',
+ tos='dummy',
+ tot_len='dummy',
+ fragment_id='dummy',
+ fragment_offset='dummy',
+ dont_fragment='dummy',
+ more_fragments='dummy',
+ ttl='dummy',
+ )
+ proto.datagramReceived("\x43\xA2" #source
+ + "\xf0\x0f" #dest
+ + "\x00\x06" #len
+ + "\xDE\xAD" #check
+ + "foobar",
+ partial=0,
+ dest='dummy',
+ source='testHost',
+ protocol='dummy',
+ version='dummy',
+ ihl='dummy',
+ tos='dummy',
+ tot_len='dummy',
+ fragment_id='dummy',
+ fragment_offset='dummy',
+ dont_fragment='dummy',
+ more_fragments='dummy',
+ ttl='dummy',
+ )
+ proto.datagramReceived("\x33\xFE" #source
+ + "\xf0\x0f" #dest
+ + "\x00\x05" #len
+ + "\xDE\xAD" #check
+ + "quux",
+ partial=0,
+ dest='dummy',
+ source='otherHost',
+ protocol='dummy',
+ version='dummy',
+ ihl='dummy',
+ tos='dummy',
+ tot_len='dummy',
+ fragment_id='dummy',
+ fragment_offset='dummy',
+ dont_fragment='dummy',
+ more_fragments='dummy',
+ ttl='dummy',
+ )
+ proto.datagramReceived("\xA3\x02" #source
+ + "\xB0\x50" #dest
+ + "\x00\x06" #len
+ + "\xDE\xAD" #check
+ + "foobar",
+ partial=0,
+ dest='dummy',
+ source='testHost',
+ protocol='dummy',
+ version='dummy',
+ ihl='dummy',
+ tos='dummy',
+ tot_len='dummy',
+ fragment_id='dummy',
+ fragment_offset='dummy',
+ dont_fragment='dummy',
+ more_fragments='dummy',
+ ttl='dummy',
+ )
+
+ assert not p1.expecting, \
+ 'Should not expect any more packets, but still want %r' % p1.expecting
+ assert not p2.expecting, \
+ 'Should not expect any more packets, but still want %r' % p2.expecting
+
+ def testAddingBadProtos_WrongLevel(self):
+ """Adding a wrong level protocol raises an exception."""
+ e = rawudp.RawUDPProtocol()
+ try:
+ e.addProto(42, "silliness")
+ except TypeError, e:
+ if e.args == ('Added protocol must be an instance of DatagramProtocol',):
+ pass
+ else:
+ raise
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
+
+
+ def testAddingBadProtos_TooSmall(self):
+ """Adding a protocol with a negative number raises an exception."""
+ e = rawudp.RawUDPProtocol()
+ try:
+ e.addProto(-1, protocol.DatagramProtocol())
+ except TypeError, e:
+ if e.args == ('Added protocol must be positive or zero',):
+ pass
+ else:
+ raise
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
+
+
+ def testAddingBadProtos_TooBig(self):
+ """Adding a protocol with a number >=2**16 raises an exception."""
+ e = rawudp.RawUDPProtocol()
+ try:
+ e.addProto(2**16, protocol.DatagramProtocol())
+ except TypeError, e:
+ if e.args == ('Added protocol must fit in 16 bits',):
+ pass
+ else:
+ raise
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
+
+ def testAddingBadProtos_TooBig2(self):
+ """Adding a protocol with a number >=2**16 raises an exception."""
+ e = rawudp.RawUDPProtocol()
+ try:
+ e.addProto(2**16+1, protocol.DatagramProtocol())
+ except TypeError, e:
+ if e.args == ('Added protocol must fit in 16 bits',):
+ pass
+ else:
+ raise
+ else:
+ raise AssertionError, 'addProto must raise an exception for bad protocols'
diff --git a/vendor/Twisted-10.0.0/twisted/pair/topfiles/NEWS b/vendor/Twisted-10.0.0/twisted/pair/topfiles/NEWS
new file mode 100644
index 0000000000..e3ec6dec30
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/topfiles/NEWS
@@ -0,0 +1,20 @@
+Twisted Pair 10.0.0 (2010-03-01)
+================================
+
+Other
+-----
+ - #4170
+
+
+Twisted Pair 9.0.0 (2009-11-24)
+===============================
+
+Other
+-----
+ - #3540, #4050
+
+
+Pair 8.2.0 (2008-12-16)
+=======================
+
+No interesting changes since Twisted 8.0.
diff --git a/vendor/Twisted-10.0.0/twisted/pair/topfiles/README b/vendor/Twisted-10.0.0/twisted/pair/topfiles/README
new file mode 100644
index 0000000000..2a8b08f8d1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/topfiles/README
@@ -0,0 +1 @@
+Twisted Pair 10.0.0
diff --git a/vendor/Twisted-10.0.0/twisted/pair/topfiles/setup.py b/vendor/Twisted-10.0.0/twisted/pair/topfiles/setup.py
new file mode 100644
index 0000000000..1346279645
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/topfiles/setup.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+
+try:
+ from twisted.python import dist
+except ImportError:
+ raise SystemExit("twisted.python.dist module not found. Make sure you "
+ "have installed the Twisted core package before "
+ "attempting to install any other Twisted projects.")
+
+if __name__ == '__main__':
+ dist.setup(
+ twisted_subproject="pair",
+ # metadata
+ name="Twisted Pair",
+ description="Twisted Pair contains low-level networking support.",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="Tommi Virtanen",
+ url="http://twistedmatrix.com/trac/wiki/TwistedPair",
+ license="MIT",
+ long_description="""
+Raw network packet parsing routines, including ethernet, IP and UDP
+packets, and tuntap support.
+""",
+ )
diff --git a/vendor/Twisted-10.0.0/twisted/pair/tuntap.py b/vendor/Twisted-10.0.0/twisted/pair/tuntap.py
new file mode 100644
index 0000000000..a8da85e084
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/pair/tuntap.py
@@ -0,0 +1,170 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+import errno, os
+from twisted.python import log, reflect, components
+from twisted.internet import base, fdesc, error
+from twisted.pair import ethernet, ip
+
+"""
+You need Eunuchs for twisted.pair.tuntap to work.
+
+Eunuchs is a library containing the missing manly parts of
+UNIX API for Python.
+
+Eunuchs is a library of Python extension that complement the standard
+libraries in parts where full support for the UNIX API (or the Linux
+API) is missing.
+
+Most of the functions wrapped by Eunuchs are low-level, dirty, but
+absolutely necessary functions for real systems programming. The aim is
+to have the functions added to mainstream Python libraries.
+
+Current list of functions included:
+
+ - fchdir(2)
+ - recvmsg(2) and sendmsg(2), including use of cmsg(3)
+ - socketpair(2)
+ - support for TUN/TAP virtual network interfaces
+
+Eunuchs doesn't have a proper web home right now, but you can fetch
+the source from http://ftp.debian.org/debian/pool/main/e/eunuch
+-- debian users can just use 'apt-get install python-eunuchs'.
+
+"""
+from eunuchs.tuntap import opentuntap, TuntapPacketInfo, makePacketInfo
+
+class TuntapPort(base.BasePort):
+ """A Port that reads and writes packets from/to a TUN/TAP-device.
+
+ TODO: Share general start/stop etc implementation details with
+ twisted.internet.udp.Port.
+ """
+ maxThroughput = 256 * 1024 # max bytes we read in one eventloop iteration
+
+ def __init__(self, interface, proto, maxPacketSize=8192, reactor=None):
+ if components.implements(proto, ethernet.IEthernetProtocol):
+ self.ethernet = 1
+ else:
+ self.ethernet = 0
+ assert components.implements(proto, ip.IIPProtocol) # XXX: fix me
+ base.BasePort.__init__(self, reactor)
+ self.interface = interface
+ self.protocol = proto
+ self.maxPacketSize = maxPacketSize
+ self.setLogStr()
+
+ def __repr__(self):
+ return "<%s on %s>" % (self.protocol.__class__, self.interface)
+
+ def startListening(self):
+ """Create and bind my socket, and begin listening on it.
+
+ This is called on unserialization, and must be called after creating a
+ server to begin listening on the specified port.
+ """
+ self._bindSocket()
+ self._connectToProtocol()
+
+ def _bindSocket(self):
+ log.msg("%s starting on %s"%(self.protocol.__class__, self.interface))
+ try:
+ fd, name = opentuntap(name=self.interface,
+ ethernet=self.ethernet,
+ packetinfo=0)
+ except OSError, e:
+ raise error.CannotListenError, (None, self.interface, e)
+ fdesc.setNonBlocking(fd)
+ self.interface = name
+ self.connected = 1
+ self.fd = fd
+
+ def fileno(self):
+ return self.fd
+
+ def _connectToProtocol(self):
+ self.protocol.makeConnection(self)
+ self.startReading()
+
+ def doRead(self):
+ """Called when my socket is ready for reading."""
+ read = 0
+ while read < self.maxThroughput:
+ try:
+ data = os.read(self.fd, self.maxPacketSize)
+ read += len(data)
+# pkt = TuntapPacketInfo(data)
+ self.protocol.datagramReceived(data,
+ partial=0 # pkt.isPartial(),
+ )
+ except OSError, e:
+ if e.errno in (errno.EWOULDBLOCK,):
+ return
+ else:
+ raise
+ except IOError, e:
+ if e.errno in (errno.EAGAIN, errno.EINTR):
+ return
+ else:
+ raise
+ except:
+ log.deferr()
+
+ def write(self, datagram):
+ """Write a datagram."""
+# header = makePacketInfo(0, 0)
+ try:
+ return os.write(self.fd, datagram)
+ except IOError, e:
+ if e.errno == errno.EINTR:
+ return self.write(datagram)
+ elif e.errno == errno.EMSGSIZE:
+ raise error.MessageLengthError, "message too long"
+ elif e.errno == errno.ECONNREFUSED:
+ raise error.ConnectionRefusedError
+ else:
+ raise
+
+ def writeSequence(self, seq):
+ self.write("".join(seq))
+
+ def loseConnection(self):
+ """Stop accepting connections on this port.
+
+ This will shut down my socket and call self.connectionLost().
+ """
+ self.stopReading()
+ if self.connected:
+ from twisted.internet import reactor
+ reactor.callLater(0, self.connectionLost)
+
+ stopListening = loseConnection
+
+ def connectionLost(self, reason=None):
+ """Cleans up my socket.
+ """
+ log.msg('(Tuntap %s Closed)' % self.interface)
+ base.BasePort.connectionLost(self, reason)
+ if hasattr(self, "protocol"):
+ # we won't have attribute in ConnectedPort, in cases
+ # where there was an error in connection process
+ self.protocol.doStop()
+ self.connected = 0
+ os.close(self.fd)
+ del self.fd
+
+ def setLogStr(self):
+ self.logstr = reflect.qual(self.protocol.__class__) + " (TUNTAP)"
+
+ def logPrefix(self):
+ """Returns the name of my class, to prefix log entries with.
+ """
+ return self.logstr
+
+ def getHost(self):
+ """
+ Returns a tuple of ('TUNTAP', interface), indicating
+ the servers address
+ """
+ return ('TUNTAP',)+self.interface
diff --git a/vendor/Twisted-10.0.0/twisted/persisted/__init__.py b/vendor/Twisted-10.0.0/twisted/persisted/__init__.py
new file mode 100644
index 0000000000..a00b854a32
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/persisted/__init__.py
@@ -0,0 +1,10 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+
+Twisted Persisted: utilities for managing persistence.
+
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/persisted/aot.py b/vendor/Twisted-10.0.0/twisted/persisted/aot.py
new file mode 100644
index 0000000000..a4c92346ba
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/persisted/aot.py
@@ -0,0 +1,560 @@
+# -*- test-case-name: twisted.test.test_persisted -*-
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+
+"""
+AOT: Abstract Object Trees
+The source-code-marshallin'est abstract-object-serializin'est persister
+this side of Marmalade!
+"""
+
+import types, new, string, copy_reg, tokenize, re
+
+from twisted.python import reflect, log
+from twisted.persisted import crefutil
+
+###########################
+# Abstract Object Classes #
+###########################
+
+#"\0" in a getSource means "insert variable-width indention here".
+#see `indentify'.
+
+class Named:
+ def __init__(self, name):
+ self.name = name
+
+class Class(Named):
+ def getSource(self):
+ return "Class(%r)" % self.name
+
+class Function(Named):
+ def getSource(self):
+ return "Function(%r)" % self.name
+
+class Module(Named):
+ def getSource(self):
+ return "Module(%r)" % self.name
+
+
+class InstanceMethod:
+ def __init__(self, name, klass, inst):
+ if not (isinstance(inst, Ref) or isinstance(inst, Instance) or isinstance(inst, Deref)):
+ raise TypeError("%s isn't an Instance, Ref, or Deref!" % inst)
+ self.name = name
+ self.klass = klass
+ self.instance = inst
+
+ def getSource(self):
+ return "InstanceMethod(%r, %r, \n\0%s)" % (self.name, self.klass, prettify(self.instance))
+
+
+class _NoStateObj:
+ pass
+NoStateObj = _NoStateObj()
+
+_SIMPLE_BUILTINS = [
+ types.StringType, types.UnicodeType, types.IntType, types.FloatType,
+ types.ComplexType, types.LongType, types.NoneType, types.SliceType,
+ types.EllipsisType]
+
+try:
+ _SIMPLE_BUILTINS.append(types.BooleanType)
+except AttributeError:
+ pass
+
+class Instance:
+ def __init__(self, className, __stateObj__=NoStateObj, **state):
+ if not isinstance(className, types.StringType):
+ raise TypeError("%s isn't a string!" % className)
+ self.klass = className
+ if __stateObj__ is not NoStateObj:
+ self.state = __stateObj__
+ self.stateIsDict = 0
+ else:
+ self.state = state
+ self.stateIsDict = 1
+
+ def getSource(self):
+ #XXX make state be foo=bar instead of a dict.
+ if self.stateIsDict:
+ stateDict = self.state
+ elif isinstance(self.state, Ref) and isinstance(self.state.obj, types.DictType):
+ stateDict = self.state.obj
+ else:
+ stateDict = None
+ if stateDict is not None:
+ try:
+ return "Instance(%r, %s)" % (self.klass, dictToKW(stateDict))
+ except NonFormattableDict:
+ return "Instance(%r, %s)" % (self.klass, prettify(stateDict))
+ return "Instance(%r, %s)" % (self.klass, prettify(self.state))
+
+class Ref:
+
+ def __init__(self, *args):
+ #blargh, lame.
+ if len(args) == 2:
+ self.refnum = args[0]
+ self.obj = args[1]
+ elif not args:
+ self.refnum = None
+ self.obj = None
+
+ def setRef(self, num):
+ if self.refnum:
+ raise ValueError("Error setting id %s, I already have %s" % (num, self.refnum))
+ self.refnum = num
+
+ def setObj(self, obj):
+ if self.obj:
+ raise ValueError("Error setting obj %s, I already have %s" % (obj, self.obj))
+ self.obj = obj
+
+ def getSource(self):
+ if self.obj is None:
+ raise RuntimeError("Don't try to display me before setting an object on me!")
+ if self.refnum:
+ return "Ref(%d, \n\0%s)" % (self.refnum, prettify(self.obj))
+ return prettify(self.obj)
+
+
+class Deref:
+ def __init__(self, num):
+ self.refnum = num
+
+ def getSource(self):
+ return "Deref(%d)" % self.refnum
+
+ __repr__ = getSource
+
+
+class Copyreg:
+ def __init__(self, loadfunc, state):
+ self.loadfunc = loadfunc
+ self.state = state
+
+ def getSource(self):
+ return "Copyreg(%r, %s)" % (self.loadfunc, prettify(self.state))
+
+
+
+###############
+# Marshalling #
+###############
+
+
+def getSource(ao):
+ """Pass me an AO, I'll return a nicely-formatted source representation."""
+ return indentify("app = " + prettify(ao))
+
+
+class NonFormattableDict(Exception):
+ """A dictionary was not formattable.
+ """
+
+r = re.compile('[a-zA-Z_][a-zA-Z0-9_]*$')
+
+def dictToKW(d):
+ out = []
+ items = d.items()
+ items.sort()
+ for k,v in items:
+ if not isinstance(k, types.StringType):
+ raise NonFormattableDict("%r ain't a string" % k)
+ if not r.match(k):
+ raise NonFormattableDict("%r ain't an identifier" % k)
+ out.append(
+ "\n\0%s=%s," % (k, prettify(v))
+ )
+ return string.join(out, '')
+
+
+def prettify(obj):
+ if hasattr(obj, 'getSource'):
+ return obj.getSource()
+ else:
+ #basic type
+ t = type(obj)
+
+ if t in _SIMPLE_BUILTINS:
+ return repr(obj)
+
+ elif t is types.DictType:
+ out = ['{']
+ for k,v in obj.items():
+ out.append('\n\0%s: %s,' % (prettify(k), prettify(v)))
+ out.append(len(obj) and '\n\0}' or '}')
+ return string.join(out, '')
+
+ elif t is types.ListType:
+ out = ["["]
+ for x in obj:
+ out.append('\n\0%s,' % prettify(x))
+ out.append(len(obj) and '\n\0]' or ']')
+ return string.join(out, '')
+
+ elif t is types.TupleType:
+ out = ["("]
+ for x in obj:
+ out.append('\n\0%s,' % prettify(x))
+ out.append(len(obj) and '\n\0)' or ')')
+ return string.join(out, '')
+ else:
+ raise TypeError("Unsupported type %s when trying to prettify %s." % (t, obj))
+
+def indentify(s):
+ out = []
+ stack = []
+ def eater(type, val, r, c, l, out=out, stack=stack):
+ #import sys
+ #sys.stdout.write(val)
+ if val in ['[', '(', '{']:
+ stack.append(val)
+ elif val in [']', ')', '}']:
+ stack.pop()
+ if val == '\0':
+ out.append(' '*len(stack))
+ else:
+ out.append(val)
+ l = ['', s]
+ tokenize.tokenize(l.pop, eater)
+ return string.join(out, '')
+
+
+
+
+
+###########
+# Unjelly #
+###########
+
+def unjellyFromAOT(aot):
+ """
+ Pass me an Abstract Object Tree, and I'll unjelly it for you.
+ """
+ return AOTUnjellier().unjelly(aot)
+
+def unjellyFromSource(stringOrFile):
+ """
+ Pass me a string of code or a filename that defines an 'app' variable (in
+ terms of Abstract Objects!), and I'll execute it and unjelly the resulting
+ AOT for you, returning a newly unpersisted Application object!
+ """
+
+ ns = {"Instance": Instance,
+ "InstanceMethod": InstanceMethod,
+ "Class": Class,
+ "Function": Function,
+ "Module": Module,
+ "Ref": Ref,
+ "Deref": Deref,
+ "Copyreg": Copyreg,
+ }
+
+ if hasattr(stringOrFile, "read"):
+ exec stringOrFile.read() in ns
+ else:
+ exec stringOrFile in ns
+
+ if ns.has_key('app'):
+ return unjellyFromAOT(ns['app'])
+ else:
+ raise ValueError("%s needs to define an 'app', it didn't!" % stringOrFile)
+
+
+class AOTUnjellier:
+ """I handle the unjellying of an Abstract Object Tree.
+ See AOTUnjellier.unjellyAO
+ """
+ def __init__(self):
+ self.references = {}
+ self.stack = []
+ self.afterUnjelly = []
+
+ ##
+ # unjelly helpers (copied pretty much directly from (now deleted) marmalade)
+ ##
+ def unjellyLater(self, node):
+ """Unjelly a node, later.
+ """
+ d = crefutil._Defer()
+ self.unjellyInto(d, 0, node)
+ return d
+
+ def unjellyInto(self, obj, loc, ao):
+ """Utility method for unjellying one object into another.
+ This automates the handling of backreferences.
+ """
+ o = self.unjellyAO(ao)
+ obj[loc] = o
+ if isinstance(o, crefutil.NotKnown):
+ o.addDependant(obj, loc)
+ return o
+
+ def callAfter(self, callable, result):
+ if isinstance(result, crefutil.NotKnown):
+ l = [None]
+ result.addDependant(l, 1)
+ else:
+ l = [result]
+ self.afterUnjelly.append((callable, l))
+
+ def unjellyAttribute(self, instance, attrName, ao):
+ #XXX this is unused????
+ """Utility method for unjellying into instances of attributes.
+
+ Use this rather than unjellyAO unless you like surprising bugs!
+ Alternatively, you can use unjellyInto on your instance's __dict__.
+ """
+ self.unjellyInto(instance.__dict__, attrName, ao)
+
+ def unjellyAO(self, ao):
+ """Unjelly an Abstract Object and everything it contains.
+ I return the real object.
+ """
+ self.stack.append(ao)
+ t = type(ao)
+ if t is types.InstanceType:
+ #Abstract Objects
+ c = ao.__class__
+ if c is Module:
+ return reflect.namedModule(ao.name)
+
+ elif c in [Class, Function] or issubclass(c, type):
+ return reflect.namedObject(ao.name)
+
+ elif c is InstanceMethod:
+ im_name = ao.name
+ im_class = reflect.namedObject(ao.klass)
+ im_self = self.unjellyAO(ao.instance)
+ if im_name in im_class.__dict__:
+ if im_self is None:
+ return getattr(im_class, im_name)
+ elif isinstance(im_self, crefutil.NotKnown):
+ return crefutil._InstanceMethod(im_name, im_self, im_class)
+ else:
+ return new.instancemethod(im_class.__dict__[im_name],
+ im_self,
+ im_class)
+ else:
+ raise TypeError("instance method changed")
+
+ elif c is Instance:
+ klass = reflect.namedObject(ao.klass)
+ state = self.unjellyAO(ao.state)
+ if hasattr(klass, "__setstate__"):
+ inst = new.instance(klass, {})
+ self.callAfter(inst.__setstate__, state)
+ else:
+ inst = new.instance(klass, state)
+ return inst
+
+ elif c is Ref:
+ o = self.unjellyAO(ao.obj) #THIS IS CHANGING THE REF OMG
+ refkey = ao.refnum
+ ref = self.references.get(refkey)
+ if ref is None:
+ self.references[refkey] = o
+ elif isinstance(ref, crefutil.NotKnown):
+ ref.resolveDependants(o)
+ self.references[refkey] = o
+ elif refkey is None:
+ # This happens when you're unjellying from an AOT not read from source
+ pass
+ else:
+ raise ValueError("Multiple references with the same ID: %s, %s, %s!" % (ref, refkey, ao))
+ return o
+
+ elif c is Deref:
+ num = ao.refnum
+ ref = self.references.get(num)
+ if ref is None:
+ der = crefutil._Dereference(num)
+ self.references[num] = der
+ return der
+ return ref
+
+ elif c is Copyreg:
+ loadfunc = reflect.namedObject(ao.loadfunc)
+ d = self.unjellyLater(ao.state).addCallback(
+ lambda result, _l: apply(_l, result), loadfunc)
+ return d
+
+ #Types
+
+ elif t in _SIMPLE_BUILTINS:
+ return ao
+
+ elif t is types.ListType:
+ l = []
+ for x in ao:
+ l.append(None)
+ self.unjellyInto(l, len(l)-1, x)
+ return l
+
+ elif t is types.TupleType:
+ l = []
+ tuple_ = tuple
+ for x in ao:
+ l.append(None)
+ if isinstance(self.unjellyInto(l, len(l)-1, x), crefutil.NotKnown):
+ tuple_ = crefutil._Tuple
+ return tuple_(l)
+
+ elif t is types.DictType:
+ d = {}
+ for k,v in ao.items():
+ kvd = crefutil._DictKeyAndValue(d)
+ self.unjellyInto(kvd, 0, k)
+ self.unjellyInto(kvd, 1, v)
+ return d
+
+ else:
+ raise TypeError("Unsupported AOT type: %s" % t)
+
+ del self.stack[-1]
+
+
+ def unjelly(self, ao):
+ try:
+ l = [None]
+ self.unjellyInto(l, 0, ao)
+ for callable, v in self.afterUnjelly:
+ callable(v[0])
+ return l[0]
+ except:
+ log.msg("Error jellying object! Stacktrace follows::")
+ log.msg(string.join(map(repr, self.stack), "\n"))
+ raise
+#########
+# Jelly #
+#########
+
+
+def jellyToAOT(obj):
+ """Convert an object to an Abstract Object Tree."""
+ return AOTJellier().jelly(obj)
+
+def jellyToSource(obj, file=None):
+ """
+ Pass me an object and, optionally, a file object.
+ I'll convert the object to an AOT either return it (if no file was
+ specified) or write it to the file.
+ """
+
+ aot = jellyToAOT(obj)
+ if file:
+ file.write(getSource(aot))
+ else:
+ return getSource(aot)
+
+
+class AOTJellier:
+ def __init__(self):
+ # dict of {id(obj): (obj, node)}
+ self.prepared = {}
+ self._ref_id = 0
+ self.stack = []
+
+ def prepareForRef(self, aoref, object):
+ """I prepare an object for later referencing, by storing its id()
+ and its _AORef in a cache."""
+ self.prepared[id(object)] = aoref
+
+ def jellyToAO(self, obj):
+ """I turn an object into an AOT and return it."""
+ objType = type(obj)
+ self.stack.append(repr(obj))
+
+ #immutable: We don't care if these have multiple refs!
+ if objType in _SIMPLE_BUILTINS:
+ retval = obj
+
+ elif objType is types.MethodType:
+ # TODO: make methods 'prefer' not to jelly the object internally,
+ # so that the object will show up where it's referenced first NOT
+ # by a method.
+ retval = InstanceMethod(obj.im_func.__name__, reflect.qual(obj.im_class),
+ self.jellyToAO(obj.im_self))
+
+ elif objType is types.ModuleType:
+ retval = Module(obj.__name__)
+
+ elif objType is types.ClassType:
+ retval = Class(reflect.qual(obj))
+
+ elif issubclass(objType, type):
+ retval = Class(reflect.qual(obj))
+
+ elif objType is types.FunctionType:
+ retval = Function(reflect.fullFuncName(obj))
+
+ else: #mutable! gotta watch for refs.
+
+#Marmalade had the nicety of being able to just stick a 'reference' attribute
+#on any Node object that was referenced, but in AOT, the referenced object
+#is *inside* of a Ref call (Ref(num, obj) instead of
+#<objtype ... reference="1">). The problem is, especially for built-in types,
+#I can't just assign some attribute to them to give them a refnum. So, I have
+#to "wrap" a Ref(..) around them later -- that's why I put *everything* that's
+#mutable inside one. The Ref() class will only print the "Ref(..)" around an
+#object if it has a Reference explicitly attached.
+
+ if self.prepared.has_key(id(obj)):
+ oldRef = self.prepared[id(obj)]
+ if oldRef.refnum:
+ # it's been referenced already
+ key = oldRef.refnum
+ else:
+ # it hasn't been referenced yet
+ self._ref_id = self._ref_id + 1
+ key = self._ref_id
+ oldRef.setRef(key)
+ return Deref(key)
+
+ retval = Ref()
+ self.prepareForRef(retval, obj)
+
+ if objType is types.ListType:
+ retval.setObj(map(self.jellyToAO, obj)) #hah!
+
+ elif objType is types.TupleType:
+ retval.setObj(tuple(map(self.jellyToAO, obj)))
+
+ elif objType is types.DictionaryType:
+ d = {}
+ for k,v in obj.items():
+ d[self.jellyToAO(k)] = self.jellyToAO(v)
+ retval.setObj(d)
+
+ elif objType is types.InstanceType:
+ if hasattr(obj, "__getstate__"):
+ state = self.jellyToAO(obj.__getstate__())
+ else:
+ state = self.jellyToAO(obj.__dict__)
+ retval.setObj(Instance(reflect.qual(obj.__class__), state))
+
+ elif copy_reg.dispatch_table.has_key(objType):
+ unpickleFunc, state = copy_reg.dispatch_table[objType](obj)
+
+ retval.setObj(Copyreg( reflect.fullFuncName(unpickleFunc),
+ self.jellyToAO(state)))
+
+ else:
+ raise TypeError("Unsupported type: %s" % objType.__name__)
+
+ del self.stack[-1]
+ return retval
+
+ def jelly(self, obj):
+ try:
+ ao = self.jellyToAO(obj)
+ return ao
+ except:
+ log.msg("Error jellying object! Stacktrace follows::")
+ log.msg(string.join(self.stack, '\n'))
+ raise
diff --git a/vendor/Twisted-10.0.0/twisted/persisted/crefutil.py b/vendor/Twisted-10.0.0/twisted/persisted/crefutil.py
new file mode 100644
index 0000000000..59e04fed3e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/persisted/crefutil.py
@@ -0,0 +1,167 @@
+# -*- test-case-name: twisted.test.test_persisted -*-
+
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Utility classes for dealing with circular references.
+"""
+
+from twisted.python import log, reflect
+
+try:
+ from new import instancemethod
+except:
+ from org.python.core import PyMethod
+ instancemethod = PyMethod
+
+
+class NotKnown:
+ def __init__(self):
+ self.dependants = []
+ self.resolved = 0
+
+ def addDependant(self, mutableObject, key):
+ assert not self.resolved
+ self.dependants.append( (mutableObject, key) )
+
+ resolvedObject = None
+
+ def resolveDependants(self, newObject):
+ self.resolved = 1
+ self.resolvedObject = newObject
+ for mut, key in self.dependants:
+ mut[key] = newObject
+ if isinstance(newObject, NotKnown):
+ newObject.addDependant(mut, key)
+
+ def __hash__(self):
+ assert 0, "I am not to be used as a dictionary key."
+
+
+
+class _Container(NotKnown):
+ """
+ Helper class to resolve circular references on container objects.
+ """
+
+ def __init__(self, l, containerType):
+ """
+ @param l: The list of object which may contain some not yet referenced
+ objects.
+
+ @param containerType: A type of container objects (e.g., C{tuple} or
+ C{set}).
+ """
+ NotKnown.__init__(self)
+ self.containerType = containerType
+ self.l = l
+ self.locs = range(len(l))
+ for idx in xrange(len(l)):
+ if not isinstance(l[idx], NotKnown):
+ self.locs.remove(idx)
+ else:
+ l[idx].addDependant(self, idx)
+ if not self.locs:
+ self.resolveDependants(self.containerType(self.l))
+
+
+ def __setitem__(self, n, obj):
+ """
+ Change the value of one contained objects, and resolve references if
+ all objects have been referenced.
+ """
+ self.l[n] = obj
+ if not isinstance(obj, NotKnown):
+ self.locs.remove(n)
+ if not self.locs:
+ self.resolveDependants(self.containerType(self.l))
+
+
+
+class _Tuple(_Container):
+ """
+ Manage tuple containing circular references. Deprecated: use C{_Container}
+ instead.
+ """
+
+ def __init__(self, l):
+ """
+ @param l: The list of object which may contain some not yet referenced
+ objects.
+ """
+ _Container.__init__(self, l, tuple)
+
+
+
+class _InstanceMethod(NotKnown):
+ def __init__(self, im_name, im_self, im_class):
+ NotKnown.__init__(self)
+ self.my_class = im_class
+ self.name = im_name
+ # im_self _must_ be a
+ im_self.addDependant(self, 0)
+
+ def __call__(self, *args, **kw):
+ import traceback
+ log.msg('instance method %s.%s' % (reflect.qual(self.my_class), self.name))
+ log.msg('being called with %r %r' % (args, kw))
+ traceback.print_stack(file=log.logfile)
+ assert 0
+
+ def __setitem__(self, n, obj):
+ assert n == 0, "only zero index allowed"
+ if not isinstance(obj, NotKnown):
+ self.resolveDependants(instancemethod(self.my_class.__dict__[self.name],
+ obj,
+ self.my_class))
+
+class _DictKeyAndValue:
+ def __init__(self, dict):
+ self.dict = dict
+ def __setitem__(self, n, obj):
+ if n not in (1, 0):
+ raise RuntimeError("DictKeyAndValue should only ever be called with 0 or 1")
+ if n: # value
+ self.value = obj
+ else:
+ self.key = obj
+ if hasattr(self, "key") and hasattr(self, "value"):
+ self.dict[self.key] = self.value
+
+
+class _Dereference(NotKnown):
+ def __init__(self, id):
+ NotKnown.__init__(self)
+ self.id = id
+
+
+from twisted.internet.defer import Deferred
+
+class _Catcher:
+ def catch(self, value):
+ self.value = value
+
+class _Defer(Deferred, NotKnown):
+ def __init__(self):
+ Deferred.__init__(self)
+ NotKnown.__init__(self)
+ self.pause()
+
+ wasset = 0
+
+ def __setitem__(self, n, obj):
+ if self.wasset:
+ raise RuntimeError('setitem should only be called once, setting %r to %r' % (n, obj))
+ else:
+ self.wasset = 1
+ self.callback(obj)
+
+ def addDependant(self, dep, key):
+ # by the time I'm adding a dependant, I'm *not* adding any more
+ # callbacks
+ NotKnown.addDependant(self, dep, key)
+ self.unpause()
+ resovd = self.result
+ self.resolveDependants(resovd)
diff --git a/vendor/Twisted-10.0.0/twisted/persisted/dirdbm.py b/vendor/Twisted-10.0.0/twisted/persisted/dirdbm.py
new file mode 100644
index 0000000000..4505d1fa63
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/persisted/dirdbm.py
@@ -0,0 +1,358 @@
+# -*- test-case-name: twisted.test.test_dirdbm -*-
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+
+"""
+DBM-style interface to a directory.
+
+Each key is stored as a single file. This is not expected to be very fast or
+efficient, but it's good for easy debugging.
+
+DirDBMs are *not* thread-safe, they should only be accessed by one thread at
+a time.
+
+No files should be placed in the working directory of a DirDBM save those
+created by the DirDBM itself!
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+
+import os
+import types
+import base64
+import glob
+
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
+try:
+ _open
+except NameError:
+ _open = open
+
+
+class DirDBM:
+ """A directory with a DBM interface.
+
+ This class presents a hash-like interface to a directory of small,
+ flat files. It can only use strings as keys or values.
+ """
+
+ def __init__(self, name):
+ """
+ @type name: str
+ @param name: Base path to use for the directory storage.
+ """
+ self.dname = os.path.abspath(name)
+ if not os.path.isdir(self.dname):
+ os.mkdir(self.dname)
+ else:
+ # Run recovery, in case we crashed. we delete all files ending
+ # with ".new". Then we find all files who end with ".rpl". If a
+ # corresponding file exists without ".rpl", we assume the write
+ # failed and delete the ".rpl" file. If only a ".rpl" exist we
+ # assume the program crashed right after deleting the old entry
+ # but before renaming the replacement entry.
+ #
+ # NOTE: '.' is NOT in the base64 alphabet!
+ for f in glob.glob(os.path.join(self.dname, "*.new")):
+ os.remove(f)
+ replacements = glob.glob(os.path.join(self.dname, "*.rpl"))
+ for f in replacements:
+ old = f[:-4]
+ if os.path.exists(old):
+ os.remove(f)
+ else:
+ os.rename(f, old)
+
+ def _encode(self, k):
+ """Encode a key so it can be used as a filename.
+ """
+ # NOTE: '_' is NOT in the base64 alphabet!
+ return base64.encodestring(k).replace('\n', '_').replace("/", "-")
+
+ def _decode(self, k):
+ """Decode a filename to get the key.
+ """
+ return base64.decodestring(k.replace('_', '\n').replace("-", "/"))
+
+ def _readFile(self, path):
+ """Read in the contents of a file.
+
+ Override in subclasses to e.g. provide transparently encrypted dirdbm.
+ """
+ f = _open(path, "rb")
+ s = f.read()
+ f.close()
+ return s
+
+ def _writeFile(self, path, data):
+ """Write data to a file.
+
+ Override in subclasses to e.g. provide transparently encrypted dirdbm.
+ """
+ f = _open(path, "wb")
+ f.write(data)
+ f.flush()
+ f.close()
+
+ def __len__(self):
+ """
+ @return: The number of key/value pairs in this Shelf
+ """
+ return len(os.listdir(self.dname))
+
+ def __setitem__(self, k, v):
+ """
+ C{dirdbm[k] = v}
+ Create or modify a textfile in this directory
+
+ @type k: str
+ @param k: key to set
+
+ @type v: str
+ @param v: value to associate with C{k}
+ """
+ assert type(k) == types.StringType, "DirDBM key must be a string"
+ assert type(v) == types.StringType, "DirDBM value must be a string"
+ k = self._encode(k)
+
+ # we create a new file with extension .new, write the data to it, and
+ # if the write succeeds delete the old file and rename the new one.
+ old = os.path.join(self.dname, k)
+ if os.path.exists(old):
+ new = old + ".rpl" # replacement entry
+ else:
+ new = old + ".new" # new entry
+ try:
+ self._writeFile(new, v)
+ except:
+ os.remove(new)
+ raise
+ else:
+ if os.path.exists(old): os.remove(old)
+ os.rename(new, old)
+
+ def __getitem__(self, k):
+ """
+ C{dirdbm[k]}
+ Get the contents of a file in this directory as a string.
+
+ @type k: str
+ @param k: key to lookup
+
+ @return: The value associated with C{k}
+ @raise KeyError: Raised when there is no such key
+ """
+ assert type(k) == types.StringType, "DirDBM key must be a string"
+ path = os.path.join(self.dname, self._encode(k))
+ try:
+ return self._readFile(path)
+ except:
+ raise KeyError, k
+
+ def __delitem__(self, k):
+ """
+ C{del dirdbm[foo]}
+ Delete a file in this directory.
+
+ @type k: str
+ @param k: key to delete
+
+ @raise KeyError: Raised when there is no such key
+ """
+ assert type(k) == types.StringType, "DirDBM key must be a string"
+ k = self._encode(k)
+ try: os.remove(os.path.join(self.dname, k))
+ except (OSError, IOError): raise KeyError(self._decode(k))
+
+ def keys(self):
+ """
+ @return: a C{list} of filenames (keys).
+ """
+ return map(self._decode, os.listdir(self.dname))
+
+ def values(self):
+ """
+ @return: a C{list} of file-contents (values).
+ """
+ vals = []
+ keys = self.keys()
+ for key in keys:
+ vals.append(self[key])
+ return vals
+
+ def items(self):
+ """
+ @return: a C{list} of 2-tuples containing key/value pairs.
+ """
+ items = []
+ keys = self.keys()
+ for key in keys:
+ items.append((key, self[key]))
+ return items
+
+ def has_key(self, key):
+ """
+ @type key: str
+ @param key: The key to test
+
+ @return: A true value if this dirdbm has the specified key, a faluse
+ value otherwise.
+ """
+ assert type(key) == types.StringType, "DirDBM key must be a string"
+ key = self._encode(key)
+ return os.path.isfile(os.path.join(self.dname, key))
+
+ def setdefault(self, key, value):
+ """
+ @type key: str
+ @param key: The key to lookup
+
+ @param value: The value to associate with key if key is not already
+ associated with a value.
+ """
+ if not self.has_key(key):
+ self[key] = value
+ return value
+ return self[key]
+
+ def get(self, key, default = None):
+ """
+ @type key: str
+ @param key: The key to lookup
+
+ @param default: The value to return if the given key does not exist
+
+ @return: The value associated with C{key} or C{default} if not
+ C{self.has_key(key)}
+ """
+ if self.has_key(key):
+ return self[key]
+ else:
+ return default
+
+ def __contains__(self, key):
+ """
+ C{key in dirdbm}
+
+ @type key: str
+ @param key: The key to test
+
+ @return: A true value if C{self.has_key(key)}, a false value otherwise.
+ """
+ assert type(key) == types.StringType, "DirDBM key must be a string"
+ key = self._encode(key)
+ return os.path.isfile(os.path.join(self.dname, key))
+
+ def update(self, dict):
+ """
+ Add all the key/value pairs in C{dict} to this dirdbm. Any conflicting
+ keys will be overwritten with the values from C{dict}.
+
+ @type dict: mapping
+ @param dict: A mapping of key/value pairs to add to this dirdbm.
+ """
+ for key, val in dict.items():
+ self[key]=val
+
+ def copyTo(self, path):
+ """
+ Copy the contents of this dirdbm to the dirdbm at C{path}.
+
+ @type path: C{str}
+ @param path: The path of the dirdbm to copy to. If a dirdbm
+ exists at the destination path, it is cleared first.
+
+ @rtype: C{DirDBM}
+ @return: The dirdbm this dirdbm was copied to.
+ """
+ path = os.path.abspath(path)
+ assert path != self.dname
+
+ d = self.__class__(path)
+ d.clear()
+ for k in self.keys():
+ d[k] = self[k]
+ return d
+
+ def clear(self):
+ """
+ Delete all key/value pairs in this dirdbm.
+ """
+ for k in self.keys():
+ del self[k]
+
+ def close(self):
+ """
+ Close this dbm: no-op, for dbm-style interface compliance.
+ """
+
+ def getModificationTime(self, key):
+ """
+ Returns modification time of an entry.
+
+ @return: Last modification date (seconds since epoch) of entry C{key}
+ @raise KeyError: Raised when there is no such key
+ """
+ assert type(key) == types.StringType, "DirDBM key must be a string"
+ path = os.path.join(self.dname, self._encode(key))
+ if os.path.isfile(path):
+ return os.path.getmtime(path)
+ else:
+ raise KeyError, key
+
+
+class Shelf(DirDBM):
+ """A directory with a DBM shelf interface.
+
+ This class presents a hash-like interface to a directory of small,
+ flat files. Keys must be strings, but values can be any given object.
+ """
+
+ def __setitem__(self, k, v):
+ """
+ C{shelf[foo] = bar}
+ Create or modify a textfile in this directory.
+
+ @type k: str
+ @param k: The key to set
+
+ @param v: The value to associate with C{key}
+ """
+ v = pickle.dumps(v)
+ DirDBM.__setitem__(self, k, v)
+
+ def __getitem__(self, k):
+ """
+ C{dirdbm[foo]}
+ Get and unpickle the contents of a file in this directory.
+
+ @type k: str
+ @param k: The key to lookup
+
+ @return: The value associated with the given key
+ @raise KeyError: Raised if the given key does not exist
+ """
+ return pickle.loads(DirDBM.__getitem__(self, k))
+
+
+def open(file, flag = None, mode = None):
+ """
+ This is for 'anydbm' compatibility.
+
+ @param file: The parameter to pass to the DirDBM constructor.
+
+ @param flag: ignored
+ @param mode: ignored
+ """
+ return DirDBM(file)
+
+
+__all__ = ["open", "DirDBM", "Shelf"]
diff --git a/vendor/Twisted-10.0.0/twisted/persisted/journal/__init__.py b/vendor/Twisted-10.0.0/twisted/persisted/journal/__init__.py
new file mode 100644
index 0000000000..f27aa4e65a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/persisted/journal/__init__.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+Command-journalling persistence framework inspired by Prevayler.
+
+Maintainer: Itamar Shtull-Trauring
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/persisted/journal/base.py b/vendor/Twisted-10.0.0/twisted/persisted/journal/base.py
new file mode 100644
index 0000000000..196a47cec2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/persisted/journal/base.py
@@ -0,0 +1,226 @@
+# -*- test-case-name: twisted.test.test_journal -*-
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+
+"""Basic classes and interfaces for journal."""
+
+from __future__ import nested_scopes
+
+# system imports
+import os, time
+
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
+# twisted imports
+from zope.interface import implements, Interface
+
+
+class Journal:
+ """All commands to the system get routed through here.
+
+ Subclasses should implement the actual snapshotting capability.
+ """
+
+ def __init__(self, log, journaledService):
+ self.log = log
+ self.journaledService = journaledService
+ self.latestIndex = self.log.getCurrentIndex()
+
+ def updateFromLog(self):
+ """Run all commands from log that haven't been run yet.
+
+ This method should be run on startup to ensure the snapshot
+ is up-to-date.
+ """
+ snapshotIndex = self.getLastSnapshot()
+ if snapshotIndex < self.latestIndex:
+ for cmdtime, command in self.log.getCommandsSince(snapshotIndex + 1):
+ command.execute(self.journaledService, cmdtime)
+
+ def executeCommand(self, command):
+ """Log and execute a command."""
+ runTime = time.time()
+ d = self.log.logCommand(command, runTime)
+ d.addCallback(self._reallyExecute, command, runTime)
+ return d
+
+ def _reallyExecute(self, index, command, runTime):
+ """Callback called when logging command is done."""
+ result = command.execute(self.journaledService, runTime)
+ self.latestIndex = index
+ return result
+
+ def getLastSnapshot(self):
+ """Return command index of the last snapshot taken."""
+ raise NotImplementedError
+
+ def sync(self, *args, **kwargs):
+ """Save journal to disk, returns Deferred of finish status.
+
+ Subclasses may choose whatever signature is appropriate, or may
+ not implement this at all.
+ """
+ raise NotImplementedError
+
+
+
+class MemoryJournal(Journal):
+ """Prevayler-like journal that dumps from memory to disk."""
+
+ def __init__(self, log, journaledService, path, loadedCallback):
+ self.path = path
+ if os.path.exists(path):
+ try:
+ self.lastSync, obj = pickle.load(open(path, "rb"))
+ except (IOError, OSError, pickle.UnpicklingError):
+ self.lastSync, obj = 0, None
+ loadedCallback(obj)
+ else:
+ self.lastSync = 0
+ loadedCallback(None)
+ Journal.__init__(self, log, journaledService)
+
+ def getLastSnapshot(self):
+ return self.lastSync
+
+ def sync(self, obj):
+ # make this more reliable at some point
+ f = open(self.path, "wb")
+ pickle.dump((self.latestIndex, obj), f, 1)
+ f.close()
+ self.lastSync = self.latestIndex
+
+
+class ICommand(Interface):
+ """A serializable command which interacts with a journaled service."""
+
+ def execute(journaledService, runTime):
+ """Run the command and return result."""
+
+
+class ICommandLog(Interface):
+ """Interface for command log."""
+
+ def logCommand(command, runTime):
+ """Add a command and its run time to the log.
+
+ @return: Deferred of command index.
+ """
+
+ def getCurrentIndex():
+ """Return index of last command that was logged."""
+
+ def getCommandsSince(index):
+ """Return commands who's index >= the given one.
+
+ @return: list of (time, command) tuples, sorted with ascending times.
+ """
+
+
+class LoadingService:
+ """Base class for journalled service used with Wrappables."""
+
+ def loadObject(self, objType, objId):
+ """Return object of specified type and id."""
+ raise NotImplementedError
+
+
+class Wrappable:
+ """Base class for objects used with LoadingService."""
+
+ objectType = None # override in base class
+
+ def getUid(self):
+ """Return uid for loading with LoadingService.loadObject"""
+ raise NotImplementedError
+
+
+class WrapperCommand:
+
+ implements(ICommand)
+
+ def __init__(self, methodName, obj, args=(), kwargs={}):
+ self.obj = obj
+ self.objId = obj.getUid()
+ self.objType = obj.objectType
+ self.methodName = methodName
+ self.args = args
+ self.kwargs = kwargs
+
+ def execute(self, svc, commandTime):
+ if not hasattr(self, "obj"):
+ obj = svc.loadObject(self.objType, self.objId)
+ else:
+ obj = self.obj
+ return getattr(obj, self.methodName)(*self.args, **self.kwargs)
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ del d["obj"]
+ return d
+
+
+def command(methodName, cmdClass=WrapperCommand):
+ """Wrap a method so it gets turned into command automatically.
+
+ For use with Wrappables.
+
+ Usage::
+
+ | class Foo(Wrappable):
+ | objectType = "foo"
+ | def getUid(self):
+ | return self.id
+ | def _bar(self, x):
+ | return x + 1
+ |
+ | bar = command('_bar')
+
+ The resulting callable will have signature identical to wrapped
+ function, except that it expects journal as first argument, and
+ returns a Deferred.
+ """
+ def wrapper(obj, journal, *args, **kwargs):
+ return journal.executeCommand(cmdClass(methodName, obj, args, kwargs))
+ return wrapper
+
+
+class ServiceWrapperCommand:
+
+ implements(ICommand)
+
+ def __init__(self, methodName, args=(), kwargs={}):
+ self.methodName = methodName
+ self.args = args
+ self.kwargs = kwargs
+
+ def execute(self, svc, commandTime):
+ return getattr(svc, self.methodName)(*self.args, **self.kwargs)
+
+ def __repr__(self):
+ return "<ServiceWrapperCommand: %s, %s, %s>" % (self.methodName, self.args, self.kwargs)
+
+ def __cmp__(self, other):
+ if hasattr(other, "__dict__"):
+ return cmp(self.__dict__, other.__dict__)
+ else:
+ return 0
+
+
+def serviceCommand(methodName, cmdClass=ServiceWrapperCommand):
+ """Wrap methods into commands for a journalled service.
+
+ The resulting callable will have signature identical to wrapped
+ function, except that it expects journal as first argument, and
+ returns a Deferred.
+ """
+ def wrapper(obj, journal, *args, **kwargs):
+ return journal.executeCommand(cmdClass(methodName, args, kwargs))
+ return wrapper
diff --git a/vendor/Twisted-10.0.0/twisted/persisted/journal/picklelog.py b/vendor/Twisted-10.0.0/twisted/persisted/journal/picklelog.py
new file mode 100644
index 0000000000..eae6a30bcf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/persisted/journal/picklelog.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+# -*- test-case-name: twisted.test.test_journal -*-
+
+"""Logging that uses pickles.
+
+TODO: add log that logs to a file.
+"""
+
+# twisted imports
+from twisted.persisted import dirdbm
+from twisted.internet import defer
+from zope.interface import implements
+
+# sibling imports
+import base
+
+
+class DirDBMLog:
+ """Log pickles to DirDBM directory."""
+
+ implements(base.ICommandLog)
+
+ def __init__(self, logPath):
+ self.db = dirdbm.Shelf(logPath)
+ indexs = map(int, self.db.keys())
+ if indexs:
+ self.currentIndex = max(indexs)
+ else:
+ self.currentIndex = 0
+
+ def logCommand(self, command, time):
+ """Log a command."""
+ self.currentIndex += 1
+ self.db[str(self.currentIndex)] = (time, command)
+ return defer.succeed(1)
+
+ def getCurrentIndex(self):
+ """Return index of last command logged."""
+ return self.currentIndex
+
+ def getCommandsSince(self, index):
+ result = []
+ for i in range(index, self.currentIndex + 1):
+ result.append(self.db[str(i)])
+ return result
diff --git a/vendor/Twisted-10.0.0/twisted/persisted/journal/rowjournal.py b/vendor/Twisted-10.0.0/twisted/persisted/journal/rowjournal.py
new file mode 100644
index 0000000000..0246222d6b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/persisted/journal/rowjournal.py
@@ -0,0 +1,99 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""Journal using twisted.enterprise.row RDBMS support.
+
+You're going to need the following table in your database::
+
+ | CREATE TABLE journalinfo
+ | (
+ | commandIndex int
+ | );
+ | INSERT INTO journalinfo VALUES (0);
+
+"""
+
+from __future__ import nested_scopes
+
+# twisted imports
+from twisted.internet import defer
+
+# sibling imports
+import base
+
+
+# constants for command list
+INSERT, DELETE, UPDATE = range(3)
+
+
+class RowJournal(base.Journal):
+ """Journal that stores data 'snapshot' in using twisted.enterprise.row.
+
+ Use this as the reflector instead of the original reflector.
+
+ It may block on creation, if it has to run recovery.
+ """
+
+ def __init__(self, log, journaledService, reflector):
+ self.reflector = reflector
+ self.commands = []
+ self.syncing = 0
+ base.Journal.__init__(self, log, journaledService)
+
+ def updateRow(self, obj):
+ """Mark on object for updating when sync()ing."""
+ self.commands.append((UPDATE, obj))
+
+ def insertRow(self, obj):
+ """Mark on object for inserting when sync()ing."""
+ self.commands.append((INSERT, obj))
+
+ def deleteRow(self, obj):
+ """Mark on object for deleting when sync()ing."""
+ self.commands.append((DELETE, obj))
+
+ def loadObjectsFrom(self, tableName, parentRow=None, data=None, whereClause=None, forceChildren=0):
+ """Flush all objects to the database and then load objects."""
+ d = self.sync()
+ d.addCallback(lambda result: self.reflector.loadObjectsFrom(
+ tableName, parentRow=parentRow, data=data, whereClause=whereClause,
+ forceChildren=forceChildren))
+ return d
+
+ def sync(self):
+ """Commit changes to database."""
+ if self.syncing:
+ raise ValueError, "sync already in progress"
+ comandMap = {INSERT : self.reflector.insertRowSQL,
+ UPDATE : self.reflector.updateRowSQL,
+ DELETE : self.reflector.deleteRowSQL}
+ sqlCommands = []
+ for kind, obj in self.commands:
+ sqlCommands.append(comandMap[kind](obj))
+ self.commands = []
+ if sqlCommands:
+ self.syncing = 1
+ d = self.reflector.dbpool.runInteraction(self._sync, self.latestIndex, sqlCommands)
+ d.addCallback(self._syncDone)
+ return d
+ else:
+ return defer.succeed(1)
+
+ def _sync(self, txn, index, commands):
+ """Do the actual database synchronization."""
+ for c in commands:
+ txn.execute(c)
+ txn.update("UPDATE journalinfo SET commandIndex = %d" % index)
+
+ def _syncDone(self, result):
+ self.syncing = 0
+ return result
+
+ def getLastSnapshot(self):
+ """Return command index of last snapshot."""
+ conn = self.reflector.dbpool.connect()
+ cursor = conn.cursor()
+ cursor.execute("SELECT commandIndex FROM journalinfo")
+ return cursor.fetchall()[0][0]
diff --git a/vendor/Twisted-10.0.0/twisted/persisted/sob.py b/vendor/Twisted-10.0.0/twisted/persisted/sob.py
new file mode 100644
index 0000000000..e709c6f450
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/persisted/sob.py
@@ -0,0 +1,227 @@
+# -*- test-case-name: twisted.test.test_sob -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""
+Save and load Small OBjects to and from files, using various formats.
+
+Maintainer: Moshe Zadka
+"""
+
+import os, sys
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+from twisted.python import log, runtime
+from twisted.python.hashlib import md5
+from twisted.persisted import styles
+from zope.interface import implements, Interface
+
+# Note:
+# These encrypt/decrypt functions only work for data formats
+# which are immune to having spaces tucked at the end.
+# All data formats which persist saves hold that condition.
+def _encrypt(passphrase, data):
+ from Crypto.Cipher import AES as cipher
+ leftover = len(data) % cipher.block_size
+ if leftover:
+ data += ' '*(cipher.block_size - leftover)
+ return cipher.new(md5(passphrase).digest()[:16]).encrypt(data)
+
+def _decrypt(passphrase, data):
+ from Crypto.Cipher import AES
+ return AES.new(md5(passphrase).digest()[:16]).decrypt(data)
+
+
+class IPersistable(Interface):
+
+ """An object which can be saved in several formats to a file"""
+
+ def setStyle(style):
+ """Set desired format.
+
+ @type style: string (one of 'pickle' or 'source')
+ """
+
+ def save(tag=None, filename=None, passphrase=None):
+ """Save object to file.
+
+ @type tag: string
+ @type filename: string
+ @type passphrase: string
+ """
+
+
+class Persistent:
+
+ implements(IPersistable)
+
+ style = "pickle"
+
+ def __init__(self, original, name):
+ self.original = original
+ self.name = name
+
+ def setStyle(self, style):
+ """Set desired format.
+
+ @type style: string (one of 'pickle' or 'source')
+ """
+ self.style = style
+
+ def _getFilename(self, filename, ext, tag):
+ if filename:
+ finalname = filename
+ filename = finalname + "-2"
+ elif tag:
+ filename = "%s-%s-2.%s" % (self.name, tag, ext)
+ finalname = "%s-%s.%s" % (self.name, tag, ext)
+ else:
+ filename = "%s-2.%s" % (self.name, ext)
+ finalname = "%s.%s" % (self.name, ext)
+ return finalname, filename
+
+ def _saveTemp(self, filename, passphrase, dumpFunc):
+ f = open(filename, 'wb')
+ if passphrase is None:
+ dumpFunc(self.original, f)
+ else:
+ s = StringIO.StringIO()
+ dumpFunc(self.original, s)
+ f.write(_encrypt(passphrase, s.getvalue()))
+ f.close()
+
+ def _getStyle(self):
+ if self.style == "source":
+ from twisted.persisted.aot import jellyToSource as dumpFunc
+ ext = "tas"
+ else:
+ def dumpFunc(obj, file):
+ pickle.dump(obj, file, 2)
+ ext = "tap"
+ return ext, dumpFunc
+
+ def save(self, tag=None, filename=None, passphrase=None):
+ """Save object to file.
+
+ @type tag: string
+ @type filename: string
+ @type passphrase: string
+ """
+ ext, dumpFunc = self._getStyle()
+ if passphrase:
+ ext = 'e' + ext
+ finalname, filename = self._getFilename(filename, ext, tag)
+ log.msg("Saving "+self.name+" application to "+finalname+"...")
+ self._saveTemp(filename, passphrase, dumpFunc)
+ if runtime.platformType == "win32" and os.path.isfile(finalname):
+ os.remove(finalname)
+ os.rename(filename, finalname)
+ log.msg("Saved.")
+
+# "Persistant" has been present since 1.0.7, so retain it for compatibility
+Persistant = Persistent
+
+class _EverythingEphemeral(styles.Ephemeral):
+
+ initRun = 0
+
+ def __init__(self, mainMod):
+ """
+ @param mainMod: The '__main__' module that this class will proxy.
+ """
+ self.mainMod = mainMod
+
+ def __getattr__(self, key):
+ try:
+ return getattr(self.mainMod, key)
+ except AttributeError:
+ if self.initRun:
+ raise
+ else:
+ log.msg("Warning! Loading from __main__: %s" % key)
+ return styles.Ephemeral()
+
+
+def load(filename, style, passphrase=None):
+ """Load an object from a file.
+
+ Deserialize an object from a file. The file can be encrypted.
+
+ @param filename: string
+ @param style: string (one of 'pickle' or 'source')
+ @param passphrase: string
+ """
+ mode = 'r'
+ if style=='source':
+ from twisted.persisted.aot import unjellyFromSource as _load
+ else:
+ _load, mode = pickle.load, 'rb'
+ if passphrase:
+ fp = StringIO.StringIO(_decrypt(passphrase,
+ open(filename, 'rb').read()))
+ else:
+ fp = open(filename, mode)
+ ee = _EverythingEphemeral(sys.modules['__main__'])
+ sys.modules['__main__'] = ee
+ ee.initRun = 1
+ try:
+ value = _load(fp)
+ finally:
+ # restore __main__ if an exception is raised.
+ sys.modules['__main__'] = ee.mainMod
+
+ styles.doUpgrade()
+ ee.initRun = 0
+ persistable = IPersistable(value, None)
+ if persistable is not None:
+ persistable.setStyle(style)
+ return value
+
+
+def loadValueFromFile(filename, variable, passphrase=None):
+ """Load the value of a variable in a Python file.
+
+ Run the contents of the file, after decrypting if C{passphrase} is
+ given, in a namespace and return the result of the variable
+ named C{variable}.
+
+ @param filename: string
+ @param variable: string
+ @param passphrase: string
+ """
+ if passphrase:
+ mode = 'rb'
+ else:
+ mode = 'r'
+ fileObj = open(filename, mode)
+ d = {'__file__': filename}
+ if passphrase:
+ data = fileObj.read()
+ data = _decrypt(passphrase, data)
+ exec data in d, d
+ else:
+ exec fileObj in d, d
+ value = d[variable]
+ return value
+
+def guessType(filename):
+ ext = os.path.splitext(filename)[1]
+ return {
+ '.tac': 'python',
+ '.etac': 'python',
+ '.py': 'python',
+ '.tap': 'pickle',
+ '.etap': 'pickle',
+ '.tas': 'source',
+ '.etas': 'source',
+ }[ext]
+
+__all__ = ['loadValueFromFile', 'load', 'Persistent', 'Persistant',
+ 'IPersistable', 'guessType']
diff --git a/vendor/Twisted-10.0.0/twisted/persisted/styles.py b/vendor/Twisted-10.0.0/twisted/persisted/styles.py
new file mode 100644
index 0000000000..b1f30b3a4c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/persisted/styles.py
@@ -0,0 +1,257 @@
+# -*- test-case-name: twisted.test.test_persisted -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+
+"""
+Different styles of persisted objects.
+"""
+
+# System Imports
+import types
+import copy_reg
+import copy
+
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+# Twisted Imports
+from twisted.python import log
+
+try:
+ from new import instancemethod
+except:
+ from org.python.core import PyMethod
+ instancemethod = PyMethod
+
+oldModules = {}
+
+## First, let's register support for some stuff that really ought to
+## be registerable...
+
+def pickleMethod(method):
+ 'support function for copy_reg to pickle method refs'
+ return unpickleMethod, (method.im_func.__name__,
+ method.im_self,
+ method.im_class)
+
+def unpickleMethod(im_name,
+ im_self,
+ im_class):
+ 'support function for copy_reg to unpickle method refs'
+ try:
+ unbound = getattr(im_class,im_name)
+ if im_self is None:
+ return unbound
+ bound=instancemethod(unbound.im_func,
+ im_self,
+ im_class)
+ return bound
+ except AttributeError:
+ log.msg("Method",im_name,"not on class",im_class)
+ assert im_self is not None,"No recourse: no instance to guess from."
+ # Attempt a common fix before bailing -- if classes have
+ # changed around since we pickled this method, we may still be
+ # able to get it by looking on the instance's current class.
+ unbound = getattr(im_self.__class__,im_name)
+ log.msg("Attempting fixup with",unbound)
+ if im_self is None:
+ return unbound
+ bound=instancemethod(unbound.im_func,
+ im_self,
+ im_self.__class__)
+ return bound
+
+copy_reg.pickle(types.MethodType,
+ pickleMethod,
+ unpickleMethod)
+
+def pickleModule(module):
+ 'support function for copy_reg to pickle module refs'
+ return unpickleModule, (module.__name__,)
+
+def unpickleModule(name):
+ 'support function for copy_reg to unpickle module refs'
+ if oldModules.has_key(name):
+ log.msg("Module has moved: %s" % name)
+ name = oldModules[name]
+ log.msg(name)
+ return __import__(name,{},{},'x')
+
+
+copy_reg.pickle(types.ModuleType,
+ pickleModule,
+ unpickleModule)
+
+def pickleStringO(stringo):
+ 'support function for copy_reg to pickle StringIO.OutputTypes'
+ return unpickleStringO, (stringo.getvalue(), stringo.tell())
+
+def unpickleStringO(val, sek):
+ x = StringIO.StringIO()
+ x.write(val)
+ x.seek(sek)
+ return x
+
+if hasattr(StringIO, 'OutputType'):
+ copy_reg.pickle(StringIO.OutputType,
+ pickleStringO,
+ unpickleStringO)
+
+def pickleStringI(stringi):
+ return unpickleStringI, (stringi.getvalue(), stringi.tell())
+
+def unpickleStringI(val, sek):
+ x = StringIO.StringIO(val)
+ x.seek(sek)
+ return x
+
+
+if hasattr(StringIO, 'InputType'):
+ copy_reg.pickle(StringIO.InputType,
+ pickleStringI,
+ unpickleStringI)
+
+class Ephemeral:
+ """
+ This type of object is never persisted; if possible, even references to it
+ are eliminated.
+ """
+
+ def __getstate__(self):
+ log.msg( "WARNING: serializing ephemeral %s" % self )
+ import gc
+ if getattr(gc, 'get_referrers', None):
+ for r in gc.get_referrers(self):
+ log.msg( " referred to by %s" % (r,))
+ return None
+
+ def __setstate__(self, state):
+ log.msg( "WARNING: unserializing ephemeral %s" % self.__class__ )
+ self.__class__ = Ephemeral
+
+
+versionedsToUpgrade = {}
+upgraded = {}
+
+def doUpgrade():
+ global versionedsToUpgrade, upgraded
+ for versioned in versionedsToUpgrade.values():
+ requireUpgrade(versioned)
+ versionedsToUpgrade = {}
+ upgraded = {}
+
+def requireUpgrade(obj):
+ """Require that a Versioned instance be upgraded completely first.
+ """
+ objID = id(obj)
+ if objID in versionedsToUpgrade and objID not in upgraded:
+ upgraded[objID] = 1
+ obj.versionUpgrade()
+ return obj
+
+from twisted.python import reflect
+
+def _aybabtu(c):
+ l = []
+ for b in reflect.allYourBase(c, Versioned):
+ if b not in l and b is not Versioned:
+ l.append(b)
+ return l
+
+class Versioned:
+ """
+ This type of object is persisted with versioning information.
+
+ I have a single class attribute, the int persistenceVersion. After I am
+ unserialized (and styles.doUpgrade() is called), self.upgradeToVersionX()
+ will be called for each version upgrade I must undergo.
+
+ For example, if I serialize an instance of a Foo(Versioned) at version 4
+ and then unserialize it when the code is at version 9, the calls::
+
+ self.upgradeToVersion5()
+ self.upgradeToVersion6()
+ self.upgradeToVersion7()
+ self.upgradeToVersion8()
+ self.upgradeToVersion9()
+
+ will be made. If any of these methods are undefined, a warning message
+ will be printed.
+ """
+ persistenceVersion = 0
+ persistenceForgets = ()
+
+ def __setstate__(self, state):
+ versionedsToUpgrade[id(self)] = self
+ self.__dict__ = state
+
+ def __getstate__(self, dict=None):
+ """Get state, adding a version number to it on its way out.
+ """
+ dct = copy.copy(dict or self.__dict__)
+ bases = _aybabtu(self.__class__)
+ bases.reverse()
+ bases.append(self.__class__) # don't forget me!!
+ for base in bases:
+ if base.__dict__.has_key('persistenceForgets'):
+ for slot in base.persistenceForgets:
+ if dct.has_key(slot):
+ del dct[slot]
+ if base.__dict__.has_key('persistenceVersion'):
+ dct['%s.persistenceVersion' % reflect.qual(base)] = base.persistenceVersion
+ return dct
+
+ def versionUpgrade(self):
+ """(internal) Do a version upgrade.
+ """
+ bases = _aybabtu(self.__class__)
+ # put the bases in order so superclasses' persistenceVersion methods
+ # will be called first.
+ bases.reverse()
+ bases.append(self.__class__) # don't forget me!!
+ # first let's look for old-skool versioned's
+ if self.__dict__.has_key("persistenceVersion"):
+
+ # Hacky heuristic: if more than one class subclasses Versioned,
+ # we'll assume that the higher version number wins for the older
+ # class, so we'll consider the attribute the version of the older
+ # class. There are obviously possibly times when this will
+ # eventually be an incorrect assumption, but hopefully old-school
+ # persistenceVersion stuff won't make it that far into multiple
+ # classes inheriting from Versioned.
+
+ pver = self.__dict__['persistenceVersion']
+ del self.__dict__['persistenceVersion']
+ highestVersion = 0
+ highestBase = None
+ for base in bases:
+ if not base.__dict__.has_key('persistenceVersion'):
+ continue
+ if base.persistenceVersion > highestVersion:
+ highestBase = base
+ highestVersion = base.persistenceVersion
+ if highestBase:
+ self.__dict__['%s.persistenceVersion' % reflect.qual(highestBase)] = pver
+ for base in bases:
+ # ugly hack, but it's what the user expects, really
+ if (Versioned not in base.__bases__ and
+ not base.__dict__.has_key('persistenceVersion')):
+ continue
+ currentVers = base.persistenceVersion
+ pverName = '%s.persistenceVersion' % reflect.qual(base)
+ persistVers = (self.__dict__.get(pverName) or 0)
+ if persistVers:
+ del self.__dict__[pverName]
+ assert persistVers <= currentVers, "Sorry, can't go backwards in time."
+ while persistVers < currentVers:
+ persistVers = persistVers + 1
+ method = base.__dict__.get('upgradeToVersion%s' % persistVers, None)
+ if method:
+ log.msg( "Upgrading %s (of %s @ %s) to version %s" % (reflect.qual(base), reflect.qual(self.__class__), id(self), persistVers) )
+ method(self)
+ else:
+ log.msg( 'Warning: cannot upgrade %s to version %s' % (base, persistVers) )
diff --git a/vendor/Twisted-10.0.0/twisted/plugin.py b/vendor/Twisted-10.0.0/twisted/plugin.py
new file mode 100644
index 0000000000..28d93b7141
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugin.py
@@ -0,0 +1,246 @@
+# -*- test-case-name: twisted.test.test_plugin -*-
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Plugin system for Twisted.
+
+@author: Jp Calderone
+@author: Glyph Lefkowitz
+"""
+
+import os
+import sys
+
+from zope.interface import Interface, providedBy
+
+def _determinePickleModule():
+ """
+ Determine which 'pickle' API module to use.
+ """
+ try:
+ import cPickle
+ return cPickle
+ except ImportError:
+ import pickle
+ return pickle
+
+pickle = _determinePickleModule()
+
+from twisted.python.components import getAdapterFactory
+from twisted.python.reflect import namedAny
+from twisted.python import log
+from twisted.python.modules import getModule
+
+
+
+class IPlugin(Interface):
+ """
+ Interface that must be implemented by all plugins.
+
+ Only objects which implement this interface will be considered for return
+ by C{getPlugins}. To be useful, plugins should also implement some other
+ application-specific interface.
+ """
+
+
+
+class CachedPlugin(object):
+ def __init__(self, dropin, name, description, provided):
+ self.dropin = dropin
+ self.name = name
+ self.description = description
+ self.provided = provided
+ self.dropin.plugins.append(self)
+
+ def __repr__(self):
+ return '<CachedPlugin %r/%r (provides %r)>' % (
+ self.name, self.dropin.moduleName,
+ ', '.join([i.__name__ for i in self.provided]))
+
+ def load(self):
+ return namedAny(self.dropin.moduleName + '.' + self.name)
+
+ def __conform__(self, interface, registry=None, default=None):
+ for providedInterface in self.provided:
+ if providedInterface.isOrExtends(interface):
+ return self.load()
+ if getAdapterFactory(providedInterface, interface, None) is not None:
+ return interface(self.load(), default)
+ return default
+
+ # backwards compat HOORJ
+ getComponent = __conform__
+
+
+
+class CachedDropin(object):
+ """
+ A collection of L{CachedPlugin} instances from a particular module in a
+ plugin package.
+
+ @type moduleName: C{str}
+ @ivar moduleName: The fully qualified name of the plugin module this
+ represents.
+
+ @type description: C{str} or C{NoneType}
+ @ivar description: A brief explanation of this collection of plugins
+ (probably the plugin module's docstring).
+
+ @type plugins: C{list}
+ @ivar plugins: The L{CachedPlugin} instances which were loaded from this
+ dropin.
+ """
+ def __init__(self, moduleName, description):
+ self.moduleName = moduleName
+ self.description = description
+ self.plugins = []
+
+
+
+def _generateCacheEntry(provider):
+ dropin = CachedDropin(provider.__name__,
+ provider.__doc__)
+ for k, v in provider.__dict__.iteritems():
+ plugin = IPlugin(v, None)
+ if plugin is not None:
+ cachedPlugin = CachedPlugin(dropin, k, v.__doc__, list(providedBy(plugin)))
+ return dropin
+
+try:
+ fromkeys = dict.fromkeys
+except AttributeError:
+ def fromkeys(keys, value=None):
+ d = {}
+ for k in keys:
+ d[k] = value
+ return d
+
+def getCache(module):
+ """
+ Compute all the possible loadable plugins, while loading as few as
+ possible and hitting the filesystem as little as possible.
+
+ @param module: a Python module object. This represents a package to search
+ for plugins.
+
+ @return: a dictionary mapping module names to CachedDropin instances.
+ """
+ allCachesCombined = {}
+ mod = getModule(module.__name__)
+ # don't want to walk deep, only immediate children.
+ lastPath = None
+ buckets = {}
+ # Fill buckets with modules by related entry on the given package's
+ # __path__. There's an abstraction inversion going on here, because this
+ # information is already represented internally in twisted.python.modules,
+ # but it's simple enough that I'm willing to live with it. If anyone else
+ # wants to fix up this iteration so that it's one path segment at a time,
+ # be my guest. --glyph
+ for plugmod in mod.iterModules():
+ fpp = plugmod.filePath.parent()
+ if fpp not in buckets:
+ buckets[fpp] = []
+ bucket = buckets[fpp]
+ bucket.append(plugmod)
+ for pseudoPackagePath, bucket in buckets.iteritems():
+ dropinPath = pseudoPackagePath.child('dropin.cache')
+ try:
+ lastCached = dropinPath.getModificationTime()
+ dropinDotCache = pickle.load(dropinPath.open('rb'))
+ except:
+ dropinDotCache = {}
+ lastCached = 0
+
+ needsWrite = False
+ existingKeys = {}
+ for pluginModule in bucket:
+ pluginKey = pluginModule.name.split('.')[-1]
+ existingKeys[pluginKey] = True
+ if ((pluginKey not in dropinDotCache) or
+ (pluginModule.filePath.getModificationTime() >= lastCached)):
+ needsWrite = True
+ try:
+ provider = pluginModule.load()
+ except:
+ # dropinDotCache.pop(pluginKey, None)
+ log.err()
+ else:
+ entry = _generateCacheEntry(provider)
+ dropinDotCache[pluginKey] = entry
+ # Make sure that the cache doesn't contain any stale plugins.
+ for pluginKey in dropinDotCache.keys():
+ if pluginKey not in existingKeys:
+ del dropinDotCache[pluginKey]
+ needsWrite = True
+ if needsWrite:
+ try:
+ dropinPath.setContent(pickle.dumps(dropinDotCache))
+ except:
+ log.err()
+ allCachesCombined.update(dropinDotCache)
+ return allCachesCombined
+
+
+def getPlugins(interface, package=None):
+ """
+ Retrieve all plugins implementing the given interface beneath the given module.
+
+ @param interface: An interface class. Only plugins which implement this
+ interface will be returned.
+
+ @param package: A package beneath which plugins are installed. For
+ most uses, the default value is correct.
+
+ @return: An iterator of plugins.
+ """
+ if package is None:
+ import twisted.plugins as package
+ allDropins = getCache(package)
+ for dropin in allDropins.itervalues():
+ for plugin in dropin.plugins:
+ try:
+ adapted = interface(plugin, None)
+ except:
+ log.err()
+ else:
+ if adapted is not None:
+ yield adapted
+
+
+# Old, backwards compatible name. Don't use this.
+getPlugIns = getPlugins
+
+
+def pluginPackagePaths(name):
+ """
+ Return a list of additional directories which should be searched for
+ modules to be included as part of the named plugin package.
+
+ @type name: C{str}
+ @param name: The fully-qualified Python name of a plugin package, eg
+ C{'twisted.plugins'}.
+
+ @rtype: C{list} of C{str}
+ @return: The absolute paths to other directories which may contain plugin
+ modules for the named plugin package.
+ """
+ package = name.split('.')
+ # Note that this may include directories which do not exist. It may be
+ # preferable to remove such directories at this point, rather than allow
+ # them to be searched later on.
+ #
+ # Note as well that only '__init__.py' will be considered to make a
+ # directory a package (and thus exclude it from this list). This means
+ # that if you create a master plugin package which has some other kind of
+ # __init__ (eg, __init__.pyc) it will be incorrectly treated as a
+ # supplementary plugin directory.
+ return [
+ os.path.abspath(os.path.join(x, *package))
+ for x
+ in sys.path
+ if
+ not os.path.exists(os.path.join(x, *package + ['__init__.py']))]
+
+__all__ = ['getPlugins', 'pluginPackagePaths']
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/__init__.py b/vendor/Twisted-10.0.0/twisted/plugins/__init__.py
new file mode 100644
index 0000000000..9ff4e58507
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/__init__.py
@@ -0,0 +1,17 @@
+# -*- test-case-name: twisted.test.test_plugin -*-
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Plugins go in directories on your PYTHONPATH named twisted/plugins:
+this is the only place where an __init__.py is necessary, thanks to
+the __path__ variable.
+
+@author: Jp Calderone
+@author: Glyph Lefkowitz
+"""
+
+from twisted.plugin import pluginPackagePaths
+__path__.extend(pluginPackagePaths(__name__))
+__all__ = [] # nothing to see here, move along, move along
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/cred_anonymous.py b/vendor/Twisted-10.0.0/twisted/plugins/cred_anonymous.py
new file mode 100644
index 0000000000..8b4c00bea2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/cred_anonymous.py
@@ -0,0 +1,40 @@
+# -*- test-case-name: twisted.test.test_strcred -*-
+#
+# Copyright (c) 2007-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Cred plugin for anonymous logins.
+"""
+
+from zope.interface import implements
+
+from twisted import plugin
+from twisted.cred.checkers import AllowAnonymousAccess
+from twisted.cred.strcred import ICheckerFactory
+from twisted.cred.credentials import IAnonymous
+
+
+anonymousCheckerFactoryHelp = """
+This allows anonymous authentication for servers that support it.
+"""
+
+
+class AnonymousCheckerFactory(object):
+ """
+ Generates checkers that will authenticate an anonymous request.
+ """
+ implements(ICheckerFactory, plugin.IPlugin)
+ authType = 'anonymous'
+ authHelp = anonymousCheckerFactoryHelp
+ argStringFormat = 'No argstring required.'
+ credentialInterfaces = (IAnonymous,)
+
+
+ def generateChecker(self, argstring=''):
+ return AllowAnonymousAccess()
+
+
+
+theAnonymousCheckerFactory = AnonymousCheckerFactory()
+
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/cred_file.py b/vendor/Twisted-10.0.0/twisted/plugins/cred_file.py
new file mode 100644
index 0000000000..fbc6145e3f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/cred_file.py
@@ -0,0 +1,60 @@
+# -*- test-case-name: twisted.test.test_strcred -*-
+#
+# Copyright (c) 2007-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Cred plugin for a file of the format 'username:password'.
+"""
+
+import sys
+
+from zope.interface import implements
+
+from twisted import plugin
+from twisted.cred.checkers import FilePasswordDB
+from twisted.cred.strcred import ICheckerFactory
+from twisted.cred.credentials import IUsernamePassword, IUsernameHashedPassword
+
+
+
+fileCheckerFactoryHelp = """
+This checker expects to receive the location of a file that
+conforms to the FilePasswordDB format. Each line in the file
+should be of the format 'username:password', in plain text.
+"""
+
+invalidFileWarning = 'Warning: not a valid file'
+
+
+
+class FileCheckerFactory(object):
+ """
+ A factory for instances of L{FilePasswordDB}.
+ """
+ implements(ICheckerFactory, plugin.IPlugin)
+ authType = 'file'
+ authHelp = fileCheckerFactoryHelp
+ argStringFormat = 'Location of a FilePasswordDB-formatted file.'
+ # Explicitly defined here because FilePasswordDB doesn't do it for us
+ credentialInterfaces = (IUsernamePassword, IUsernameHashedPassword)
+
+ errorOutput = sys.stderr
+
+ def generateChecker(self, argstring):
+ """
+ This checker factory expects to get the location of a file.
+ The file should conform to the format required by
+ L{FilePasswordDB} (using defaults for all
+ initialization parameters).
+ """
+ from twisted.python.filepath import FilePath
+ if not argstring.strip():
+ raise ValueError, '%r requires a filename' % self.authType
+ elif not FilePath(argstring).isfile():
+ self.errorOutput.write('%s: %s\n' % (invalidFileWarning, argstring))
+ return FilePasswordDB(argstring)
+
+
+
+theFileCheckerFactory = FileCheckerFactory()
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/cred_memory.py b/vendor/Twisted-10.0.0/twisted/plugins/cred_memory.py
new file mode 100644
index 0000000000..33153f7970
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/cred_memory.py
@@ -0,0 +1,68 @@
+# -*- test-case-name: twisted.test.test_strcred -*-
+#
+# Copyright (c) 2007-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Cred plugin for an in-memory user database.
+"""
+
+from zope.interface import implements
+
+from twisted import plugin
+from twisted.cred.strcred import ICheckerFactory
+from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+from twisted.cred.credentials import IUsernamePassword, IUsernameHashedPassword
+
+
+
+inMemoryCheckerFactoryHelp = """
+A checker that uses an in-memory user database.
+
+This is only of use in one-off test programs or examples which
+don't want to focus too much on how credentials are verified. You
+really don't want to use this for anything else. It is a toy.
+"""
+
+
+
+class InMemoryCheckerFactory(object):
+ """
+ A factory for in-memory credentials checkers.
+
+ This is only of use in one-off test programs or examples which don't
+ want to focus too much on how credentials are verified.
+
+ You really don't want to use this for anything else. It is, at best, a
+ toy. If you need a simple credentials checker for a real application,
+ see L{cred_passwd.PasswdCheckerFactory}.
+ """
+ implements(ICheckerFactory, plugin.IPlugin)
+ authType = 'memory'
+ authHelp = inMemoryCheckerFactoryHelp
+ argStringFormat = 'A colon-separated list (name:password:...)'
+ credentialInterfaces = (IUsernamePassword,
+ IUsernameHashedPassword)
+
+ def generateChecker(self, argstring):
+ """
+ This checker factory expects to get a list of
+ username:password pairs, with each pair also separated by a
+ colon. For example, the string 'alice:f:bob:g' would generate
+ two users, one named 'alice' and one named 'bob'.
+ """
+ checker = InMemoryUsernamePasswordDatabaseDontUse()
+ if argstring:
+ pieces = argstring.split(':')
+ if len(pieces) % 2:
+ from twisted.cred.strcred import InvalidAuthArgumentString
+ raise InvalidAuthArgumentString(
+ "argstring must be in format U:P:...")
+ for i in range(0, len(pieces), 2):
+ username, password = pieces[i], pieces[i+1]
+ checker.addUser(username, password)
+ return checker
+
+
+
+theInMemoryCheckerFactory = InMemoryCheckerFactory()
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/cred_unix.py b/vendor/Twisted-10.0.0/twisted/plugins/cred_unix.py
new file mode 100644
index 0000000000..b67df8f62e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/cred_unix.py
@@ -0,0 +1,138 @@
+# -*- test-case-name: twisted.test.test_strcred -*-
+#
+# Copyright (c) 2007-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Cred plugin for UNIX user accounts.
+"""
+
+from zope.interface import implements
+
+from twisted import plugin
+from twisted.cred.strcred import ICheckerFactory
+from twisted.cred.checkers import ICredentialsChecker
+from twisted.cred.credentials import IUsernamePassword
+from twisted.cred.error import UnauthorizedLogin
+from twisted.internet import defer
+
+
+
+def verifyCryptedPassword(crypted, pw):
+ if crypted[0] == '$': # md5_crypt encrypted
+ salt = '$1$' + crypted.split('$')[2]
+ else:
+ salt = crypted[:2]
+ try:
+ import crypt
+ except ImportError:
+ crypt = None
+
+ if crypt is None:
+ raise NotImplementedError("cred_unix not supported on this platform")
+ return crypt.crypt(pw, salt) == crypted
+
+
+
+class UNIXChecker(object):
+ """
+ A credentials checker for a UNIX server. This will check that
+ an authenticating username/password is a valid user on the system.
+
+ Does not work on Windows.
+
+ Right now this supports Python's pwd and spwd modules, if they are
+ installed. It does not support PAM.
+ """
+ implements(ICredentialsChecker)
+ credentialInterfaces = (IUsernamePassword,)
+
+
+ def checkPwd(self, pwd, username, password):
+ try:
+ cryptedPass = pwd.getpwnam(username)[1]
+ except KeyError:
+ return defer.fail(UnauthorizedLogin())
+ else:
+ if cryptedPass in ('*', 'x'):
+ # Allow checkSpwd to take over
+ return None
+ elif verifyCryptedPassword(cryptedPass, password):
+ return defer.succeed(username)
+
+
+ def checkSpwd(self, spwd, username, password):
+ try:
+ cryptedPass = spwd.getspnam(username)[1]
+ except KeyError:
+ return defer.fail(UnauthorizedLogin())
+ else:
+ if verifyCryptedPassword(cryptedPass, password):
+ return defer.succeed(username)
+
+
+ def requestAvatarId(self, credentials):
+ username, password = credentials.username, credentials.password
+
+ try:
+ import pwd
+ except ImportError:
+ pwd = None
+
+ if pwd is not None:
+ checked = self.checkPwd(pwd, username, password)
+ if checked is not None:
+ return checked
+
+ try:
+ import spwd
+ except ImportError:
+ spwd = None
+
+ if spwd is not None:
+ checked = self.checkSpwd(spwd, username, password)
+ if checked is not None:
+ return checked
+ # TODO: check_pam?
+ # TODO: check_shadow?
+ return defer.fail(UnauthorizedLogin())
+
+
+
+unixCheckerFactoryHelp = """
+This checker will attempt to use every resource available to
+authenticate against the list of users on the local UNIX system.
+(This does not support Windows servers for very obvious reasons.)
+
+Right now, this includes support for:
+
+ * Python's pwd module (which checks /etc/passwd)
+ * Python's spwd module (which checks /etc/shadow)
+
+Future versions may include support for PAM authentication.
+"""
+
+
+
+class UNIXCheckerFactory(object):
+ """
+ A factory for L{UNIXChecker}.
+ """
+ implements(ICheckerFactory, plugin.IPlugin)
+ authType = 'unix'
+ authHelp = unixCheckerFactoryHelp
+ argStringFormat = 'No argstring required.'
+ credentialInterfaces = UNIXChecker.credentialInterfaces
+
+ def generateChecker(self, argstring):
+ """
+ This checker factory ignores the argument string. Everything
+ needed to generate a user database is pulled out of the local
+ UNIX environment.
+ """
+ return UNIXChecker()
+
+
+
+theUnixCheckerFactory = UNIXCheckerFactory()
+
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_conch.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_conch.py
new file mode 100644
index 0000000000..3214866b98
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_conch.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedSSH = ServiceMaker(
+ "Twisted Conch Server",
+ "twisted.conch.tap",
+ "A Conch SSH service.",
+ "conch")
+
+TwistedManhole = ServiceMaker(
+ "Twisted Manhole (new)",
+ "twisted.conch.manhole_tap",
+ ("An interactive remote debugger service accessible via telnet "
+ "and ssh and providing syntax coloring and basic line editing "
+ "functionality."),
+ "manhole")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_ftp.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_ftp.py
new file mode 100644
index 0000000000..a3d2a1f7cb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_ftp.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedFTP = ServiceMaker(
+ "Twisted FTP",
+ "twisted.tap.ftp",
+ "An FTP server.",
+ "ftp")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_inet.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_inet.py
new file mode 100644
index 0000000000..b05df88efe
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_inet.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedINETD = ServiceMaker(
+ "Twisted INETD Server",
+ "twisted.runner.inetdtap",
+ "An inetd(8) replacement.",
+ "inetd")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_lore.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_lore.py
new file mode 100644
index 0000000000..43bb913656
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_lore.py
@@ -0,0 +1,38 @@
+
+from zope.interface import implements
+
+from twisted.lore.scripts.lore import IProcessor
+from twisted.plugin import IPlugin
+
+class _LorePlugin(object):
+ implements(IPlugin, IProcessor)
+
+ def __init__(self, name, moduleName, description):
+ self.name = name
+ self.moduleName = moduleName
+ self.description = description
+
+DefaultProcessor = _LorePlugin(
+ "lore",
+ "twisted.lore.default",
+ "Lore format")
+
+MathProcessor = _LorePlugin(
+ "mlore",
+ "twsited.lore.lmath",
+ "Lore format with LaTeX formula")
+
+SlideProcessor = _LorePlugin(
+ "lore-slides",
+ "twisted.lore.slides",
+ "Lore for slides")
+
+ManProcessor = _LorePlugin(
+ "man",
+ "twisted.lore.man2lore",
+ "UNIX Man pages")
+
+NevowProcessor = _LorePlugin(
+ "nevow",
+ "twisted.lore.nevowlore",
+ "Nevow for Lore")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_mail.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_mail.py
new file mode 100644
index 0000000000..869b0cb5f0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_mail.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedMail = ServiceMaker(
+ "Twisted Mail",
+ "twisted.mail.tap",
+ "An email service",
+ "mail")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_manhole.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_manhole.py
new file mode 100644
index 0000000000..126f251893
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_manhole.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedManhole = ServiceMaker(
+ "Twisted Manhole (old)",
+ "twisted.tap.manhole",
+ "An interactive remote debugger service.",
+ "manhole-old")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_names.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_names.py
new file mode 100644
index 0000000000..e48edaf356
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_names.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedNames = ServiceMaker(
+ "Twisted DNS Server",
+ "twisted.names.tap",
+ "A domain name server.",
+ "dns")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_news.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_news.py
new file mode 100644
index 0000000000..8970ae254a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_news.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedNews = ServiceMaker(
+ "Twisted News",
+ "twisted.news.tap",
+ "A news server.",
+ "news")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_portforward.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_portforward.py
new file mode 100644
index 0000000000..77d605ed6a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_portforward.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedPortForward = ServiceMaker(
+ "Twisted Port-Forwarding",
+ "twisted.tap.portforward",
+ "A simple port-forwarder.",
+ "portforward")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_qtstub.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_qtstub.py
new file mode 100644
index 0000000000..1b9b08a03b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_qtstub.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Backwards-compatibility plugin for the Qt reactor.
+
+This provides a Qt reactor plugin named C{qt} which emits a deprecation
+warning and a pointer to the separately distributed Qt reactor plugins.
+"""
+
+import warnings
+
+from twisted.application.reactors import Reactor, NoSuchReactor
+
+wikiURL = 'http://twistedmatrix.com/trac/wiki/QTReactor'
+errorMessage = ('qtreactor is no longer a part of Twisted due to licensing '
+ 'issues. Please see %s for details.' % (wikiURL,))
+
+class QTStub(Reactor):
+ """
+ Reactor plugin which emits a deprecation warning on the successful
+ installation of its reactor or a pointer to further information if an
+ ImportError occurs while attempting to install it.
+ """
+ def __init__(self):
+ super(QTStub, self).__init__(
+ 'qt', 'qtreactor', 'QT integration reactor')
+
+
+ def install(self):
+ """
+ Install the Qt reactor with a deprecation warning or try to point
+ the user to further information if it cannot be installed.
+ """
+ try:
+ super(QTStub, self).install()
+ except (ValueError, ImportError):
+ raise NoSuchReactor(errorMessage)
+ else:
+ warnings.warn(
+ "Please use -r qt3 to import qtreactor",
+ category=DeprecationWarning)
+
+
+qt = QTStub()
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_reactors.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_reactors.py
new file mode 100644
index 0000000000..428e96c407
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_reactors.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.reactors import Reactor
+
+default = Reactor(
+ 'default', 'twisted.internet.default',
+ 'The best reactor for the current platform.')
+
+select = Reactor(
+ 'select', 'twisted.internet.selectreactor', 'select(2)-based reactor.')
+wx = Reactor(
+ 'wx', 'twisted.internet.wxreactor', 'wxPython integration reactor.')
+gtk = Reactor(
+ 'gtk', 'twisted.internet.gtkreactor', 'Gtk1 integration reactor.')
+gtk2 = Reactor(
+ 'gtk2', 'twisted.internet.gtk2reactor', 'Gtk2 integration reactor.')
+glib2 = Reactor(
+ 'glib2', 'twisted.internet.glib2reactor',
+ 'GLib2 event-loop integration reactor.')
+glade = Reactor(
+ 'debug-gui', 'twisted.manhole.gladereactor',
+ 'Semi-functional debugging/introspection reactor.')
+win32er = Reactor(
+ 'win32', 'twisted.internet.win32eventreactor',
+ 'Win32 WaitForMultipleObjects-based reactor.')
+poll = Reactor(
+ 'poll', 'twisted.internet.pollreactor', 'poll(2)-based reactor.')
+epoll = Reactor(
+ 'epoll', 'twisted.internet.epollreactor', 'epoll(4)-based reactor.')
+cf = Reactor(
+ 'cf' , 'twisted.internet.cfreactor',
+ 'CoreFoundation integration reactor.')
+kqueue = Reactor(
+ 'kqueue', 'twisted.internet.kqreactor', 'kqueue(2)-based reactor.')
+iocp = Reactor(
+ 'iocp', 'twisted.internet.iocpreactor',
+ 'Win32 IO Completion Ports-based reactor.')
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_socks.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_socks.py
new file mode 100644
index 0000000000..354a4de9b4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_socks.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedSOCKS = ServiceMaker(
+ "Twisted SOCKS",
+ "twisted.tap.socks",
+ "A SOCKSv4 proxy service.",
+ "socks")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_telnet.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_telnet.py
new file mode 100644
index 0000000000..62be6027e7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_telnet.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedTelnet = ServiceMaker(
+ "Twisted Telnet Shell Server",
+ "twisted.tap.telnet",
+ "A simple, telnet-based remote debugging service.",
+ "telnet")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_trial.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_trial.py
new file mode 100644
index 0000000000..debc8afa03
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_trial.py
@@ -0,0 +1,59 @@
+
+from zope.interface import implements
+
+from twisted.trial.itrial import IReporter
+from twisted.plugin import IPlugin
+
+class _Reporter(object):
+ implements(IPlugin, IReporter)
+
+ def __init__(self, name, module, description, longOpt, shortOpt, klass):
+ self.name = name
+ self.module = module
+ self.description = description
+ self.longOpt = longOpt
+ self.shortOpt = shortOpt
+ self.klass = klass
+
+
+Tree = _Reporter("Tree Reporter",
+ "twisted.trial.reporter",
+ description="verbose color output (default reporter)",
+ longOpt="verbose",
+ shortOpt="v",
+ klass="TreeReporter")
+
+BlackAndWhite = _Reporter("Black-And-White Reporter",
+ "twisted.trial.reporter",
+ description="Colorless verbose output",
+ longOpt="bwverbose",
+ shortOpt="o",
+ klass="VerboseTextReporter")
+
+Minimal = _Reporter("Minimal Reporter",
+ "twisted.trial.reporter",
+ description="minimal summary output",
+ longOpt="summary",
+ shortOpt="s",
+ klass="MinimalReporter")
+
+Classic = _Reporter("Classic Reporter",
+ "twisted.trial.reporter",
+ description="terse text output",
+ longOpt="text",
+ shortOpt="t",
+ klass="TextReporter")
+
+Timing = _Reporter("Timing Reporter",
+ "twisted.trial.reporter",
+ description="Timing output",
+ longOpt="timing",
+ shortOpt=None,
+ klass="TimingTextReporter")
+
+Subunit = _Reporter("Subunit Reporter",
+ "twisted.trial.reporter",
+ description="subunit output",
+ longOpt="subunit",
+ shortOpt=None,
+ klass="SubunitReporter")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_web.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_web.py
new file mode 100644
index 0000000000..861e4dfa25
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_web.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application.service import ServiceMaker
+
+TwistedWeb = ServiceMaker(
+ "Twisted Web",
+ "twisted.web.tap",
+ ("A general-purpose web server which can serve from a "
+ "filesystem or application resource."),
+ "web")
diff --git a/vendor/Twisted-10.0.0/twisted/plugins/twisted_words.py b/vendor/Twisted-10.0.0/twisted/plugins/twisted_words.py
new file mode 100644
index 0000000000..d1d0a77699
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/plugins/twisted_words.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import classProvides
+
+from twisted.plugin import IPlugin
+
+from twisted.application.service import ServiceMaker
+from twisted.words import iwords
+
+TwistedTOC = ServiceMaker(
+ "Twisted TOC Server",
+ "twisted.words.toctap",
+ "An AIM TOC service.",
+ "toc")
+
+NewTwistedWords = ServiceMaker(
+ "New Twisted Words",
+ "twisted.words.tap",
+ "A modern words server",
+ "words")
+
+TwistedXMPPRouter = ServiceMaker(
+ "XMPP Router",
+ "twisted.words.xmpproutertap",
+ "An XMPP Router server",
+ "xmpp-router")
+
+class RelayChatInterface(object):
+ classProvides(IPlugin, iwords.IProtocolPlugin)
+
+ name = 'irc'
+
+ def getFactory(cls, realm, portal):
+ from twisted.words import service
+ return service.IRCFactory(realm, portal)
+ getFactory = classmethod(getFactory)
+
+class PBChatInterface(object):
+ classProvides(IPlugin, iwords.IProtocolPlugin)
+
+ name = 'pb'
+
+ def getFactory(cls, realm, portal):
+ from twisted.spread import pb
+ return pb.PBServerFactory(portal, True)
+ getFactory = classmethod(getFactory)
+
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/__init__.py b/vendor/Twisted-10.0.0/twisted/protocols/__init__.py
new file mode 100644
index 0000000000..f6afcbfb43
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/__init__.py
@@ -0,0 +1,7 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Twisted Protocols: a collection of internet protocol implementations.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/_c_urlarg.c b/vendor/Twisted-10.0.0/twisted/protocols/_c_urlarg.c
new file mode 100644
index 0000000000..8e2353b342
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/_c_urlarg.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+ * See LICENSE for details.
+
+ *
+ */
+/* _c_urlarg.c */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <Python.h>
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef __GNUC__
+# define TM_INLINE inline
+#else
+# define TM_INLINE /* */
+#endif
+
+static PyObject* UrlargError;
+
+#define OUTPUTCHAR(c,n) do{memcpy(output, c, n);output+=n;}while(0)
+
+#define STATE_INITIAL 0
+#define STATE_PERCENT 1
+#define STATE_HEXDIGIT 2
+
+#define NOT_HEXDIGIT 255
+unsigned char hexdigits[256];
+
+TM_INLINE int ishexdigit(unsigned char c) {
+ return hexdigits[c];
+}
+
+static PyObject *unquote(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ unsigned char *s, *r, *end, *output_start, *output;
+ unsigned char quotedchar, quotedchartmp = 0, tmp;
+ unsigned char escchar = '%'; /* the character we use to begin %AB sequences */
+ static char *kwlist[] = {"s", "escchar", NULL};
+ int state = STATE_INITIAL, length;
+ PyObject *str;
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|c:unquote", kwlist, &s, &length, &escchar)) {
+ return NULL;
+ }
+ if (length == 0) {
+ return PyString_FromStringAndSize("", 0);
+ }
+ /* Allocating an output buffer of length will be sufficient,
+ as the output can only be smaller. We resize the output in the end. */
+ str = PyString_FromStringAndSize(NULL, length);
+ if (str == NULL) {
+ return NULL;
+ }
+ output = output_start = (unsigned char*)PyString_AsString(str);
+ end = s + length;
+ s = s - 1;
+ while ((++s) < end) {
+ switch(state) {
+ case STATE_INITIAL:
+ if (*s == escchar) {
+ state = STATE_PERCENT;
+ } else {
+ r = s - 1;
+ while (*(++r) != escchar && r < end);
+ OUTPUTCHAR(s, r-s);
+ s = r-1;
+ }
+ break;
+ case STATE_PERCENT:
+ if ((quotedchartmp = ishexdigit(*s)) != NOT_HEXDIGIT) {
+ tmp = *s;
+ state = STATE_HEXDIGIT;
+ } else {
+ state = STATE_INITIAL;
+ OUTPUTCHAR(&escchar, 1);
+ s--;
+ }
+ break;
+ case STATE_HEXDIGIT:
+ state = STATE_INITIAL;
+ if ((quotedchar = ishexdigit(*s)) != NOT_HEXDIGIT) {
+ quotedchar |= (quotedchartmp << 4);
+ OUTPUTCHAR(&quotedchar, 1);
+ } else {
+ OUTPUTCHAR(&escchar, 1);
+ s -= 2;
+ }
+ break;
+ }
+ }
+ switch(state) {
+ case STATE_PERCENT:
+ OUTPUTCHAR(&escchar, 1);
+ break;
+ case STATE_HEXDIGIT:
+ OUTPUTCHAR(&escchar, 1);
+ OUTPUTCHAR(&tmp, 1);
+ break;
+ }
+
+ _PyString_Resize(&str, output-output_start);
+ return str;
+}
+
+static PyMethodDef _c_urlarg_methods[] = {
+ {"unquote", (PyCFunction)unquote, METH_VARARGS|METH_KEYWORDS},
+ {NULL, NULL} /* sentinel */
+};
+
+DL_EXPORT(void) init_c_urlarg(void)
+{
+ PyObject* m;
+ PyObject* d;
+ unsigned char i;
+
+ m = Py_InitModule("_c_urlarg", _c_urlarg_methods);
+ d = PyModule_GetDict(m);
+
+ /* add our base exception class */
+ UrlargError = PyErr_NewException("urlarg.UrlargError", PyExc_Exception, NULL);
+ PyDict_SetItemString(d, "UrlargError", UrlargError);
+
+ /* initialize hexdigits */
+ for(i = 0; i < 255; i++) {
+ hexdigits[i] = NOT_HEXDIGIT;
+ }
+ hexdigits[255] = NOT_HEXDIGIT;
+ for(i = '0'; i <= '9'; i++) {
+ hexdigits[i] = i - '0';
+ }
+ for(i = 'a'; i <= 'f'; i++) {
+ hexdigits[i] = 10 + (i - 'a');
+ }
+ for(i = 'A'; i <= 'F'; i++) {
+ hexdigits[i] = 10 + (i - 'A');
+ }
+ /* Check for errors */
+ if (PyErr_Occurred()) {
+ PyErr_Print();
+ Py_FatalError("can't initialize module _c_urlarg");
+ }
+}
+
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/amp.py b/vendor/Twisted-10.0.0/twisted/protocols/amp.py
new file mode 100644
index 0000000000..355e2decad
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/amp.py
@@ -0,0 +1,2394 @@
+# -*- test-case-name: twisted.test.test_amp -*-
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module implements AMP, the Asynchronous Messaging Protocol.
+
+AMP is a protocol for sending multiple asynchronous request/response pairs over
+the same connection. Requests and responses are both collections of key/value
+pairs.
+
+AMP is a very simple protocol which is not an application. This module is a
+"protocol construction kit" of sorts; it attempts to be the simplest wire-level
+implementation of Deferreds. AMP provides the following base-level features:
+
+ - Asynchronous request/response handling (hence the name)
+
+ - Requests and responses are both key/value pairs
+
+ - Binary transfer of all data: all data is length-prefixed. Your
+ application will never need to worry about quoting.
+
+ - Command dispatching (like HTTP Verbs): the protocol is extensible, and
+ multiple AMP sub-protocols can be grouped together easily.
+
+The protocol implementation also provides a few additional features which are
+not part of the core wire protocol, but are nevertheless very useful:
+
+ - Tight TLS integration, with an included StartTLS command.
+
+ - Handshaking to other protocols: because AMP has well-defined message
+ boundaries and maintains all incoming and outgoing requests for you, you
+ can start a connection over AMP and then switch to another protocol.
+ This makes it ideal for firewall-traversal applications where you may
+ have only one forwarded port but multiple applications that want to use
+ it.
+
+Using AMP with Twisted is simple. Each message is a command, with a response.
+You begin by defining a command type. Commands specify their input and output
+in terms of the types that they expect to see in the request and response
+key-value pairs. Here's an example of a command that adds two integers, 'a'
+and 'b'::
+
+ class Sum(amp.Command):
+ arguments = [('a', amp.Integer()),
+ ('b', amp.Integer())]
+ response = [('total', amp.Integer())]
+
+Once you have specified a command, you need to make it part of a protocol, and
+define a responder for it. Here's a 'JustSum' protocol that includes a
+responder for our 'Sum' command::
+
+ class JustSum(amp.AMP):
+ def sum(self, a, b):
+ total = a + b
+ print 'Did a sum: %d + %d = %d' % (a, b, total)
+ return {'total': total}
+ Sum.responder(sum)
+
+Later, when you want to actually do a sum, the following expression will return
+a L{Deferred} which will fire with the result::
+
+ ClientCreator(reactor, amp.AMP).connectTCP(...).addCallback(
+ lambda p: p.callRemote(Sum, a=13, b=81)).addCallback(
+ lambda result: result['total'])
+
+You can also define the propagation of specific errors in AMP. For example,
+for the slightly more complicated case of division, we might have to deal with
+division by zero::
+
+ class Divide(amp.Command):
+ arguments = [('numerator', amp.Integer()),
+ ('denominator', amp.Integer())]
+ response = [('result', amp.Float())]
+ errors = {ZeroDivisionError: 'ZERO_DIVISION'}
+
+The 'errors' mapping here tells AMP that if a responder to Divide emits a
+L{ZeroDivisionError}, then the other side should be informed that an error of
+the type 'ZERO_DIVISION' has occurred. Writing a responder which takes
+advantage of this is very simple - just raise your exception normally::
+
+ class JustDivide(amp.AMP):
+ def divide(self, numerator, denominator):
+ result = numerator / denominator
+ print 'Divided: %d / %d = %d' % (numerator, denominator, total)
+ return {'result': result}
+ Divide.responder(divide)
+
+On the client side, the errors mapping will be used to determine what the
+'ZERO_DIVISION' error means, and translated into an asynchronous exception,
+which can be handled normally as any L{Deferred} would be::
+
+ def trapZero(result):
+ result.trap(ZeroDivisionError)
+ print "Divided by zero: returning INF"
+ return 1e1000
+ ClientCreator(reactor, amp.AMP).connectTCP(...).addCallback(
+ lambda p: p.callRemote(Divide, numerator=1234,
+ denominator=0)
+ ).addErrback(trapZero)
+
+For a complete, runnable example of both of these commands, see the files in
+the Twisted repository::
+
+ doc/core/examples/ampserver.py
+ doc/core/examples/ampclient.py
+
+On the wire, AMP is a protocol which uses 2-byte lengths to prefix keys and
+values, and empty keys to separate messages::
+
+ <2-byte length><key><2-byte length><value>
+ <2-byte length><key><2-byte length><value>
+ ...
+ <2-byte length><key><2-byte length><value>
+ <NUL><NUL> # Empty Key == End of Message
+
+And so on. Because it's tedious to refer to lengths and NULs constantly, the
+documentation will refer to packets as if they were newline delimited, like
+so::
+
+ C: _command: sum
+ C: _ask: ef639e5c892ccb54
+ C: a: 13
+ C: b: 81
+
+ S: _answer: ef639e5c892ccb54
+ S: total: 94
+
+Notes:
+
+In general, the order of keys is arbitrary. Specific uses of AMP may impose an
+ordering requirement, but unless this is specified explicitly, any ordering may
+be generated and any ordering must be accepted. This applies to the
+command-related keys I{_command} and I{_ask} as well as any other keys.
+
+Values are limited to the maximum encodable size in a 16-bit length, 65535
+bytes.
+
+Keys are limited to the maximum encodable size in a 8-bit length, 255 bytes.
+Note that we still use 2-byte lengths to encode keys. This small redundancy
+has several features:
+
+ - If an implementation becomes confused and starts emitting corrupt data,
+ or gets keys confused with values, many common errors will be signalled
+ immediately instead of delivering obviously corrupt packets.
+
+ - A single NUL will separate every key, and a double NUL separates
+ messages. This provides some redundancy when debugging traffic dumps.
+
+ - NULs will be present at regular intervals along the protocol, providing
+ some padding for otherwise braindead C implementations of the protocol,
+ so that <stdio.h> string functions will see the NUL and stop.
+
+ - This makes it possible to run an AMP server on a port also used by a
+ plain-text protocol, and easily distinguish between non-AMP clients (like
+ web browsers) which issue non-NUL as the first byte, and AMP clients,
+ which always issue NUL as the first byte.
+"""
+
+__metaclass__ = type
+
+import types, warnings
+
+from cStringIO import StringIO
+from struct import pack
+
+from zope.interface import Interface, implements
+
+from twisted.python.compat import set
+from twisted.python.util import unsignedID
+from twisted.python.reflect import accumulateClassDict
+from twisted.python.failure import Failure
+from twisted.python import log, filepath
+
+from twisted.internet.main import CONNECTION_LOST
+from twisted.internet.error import PeerVerifyError, ConnectionLost
+from twisted.internet.error import ConnectionClosed
+from twisted.internet.defer import Deferred, maybeDeferred, fail
+from twisted.protocols.basic import Int16StringReceiver, StatefulStringProtocol
+
+try:
+ from twisted.internet import ssl
+except ImportError:
+ ssl = None
+
+if ssl and not ssl.supported:
+ ssl = None
+
+if ssl is not None:
+ from twisted.internet.ssl import CertificateOptions, Certificate, DN, KeyPair
+
+ASK = '_ask'
+ANSWER = '_answer'
+COMMAND = '_command'
+ERROR = '_error'
+ERROR_CODE = '_error_code'
+ERROR_DESCRIPTION = '_error_description'
+UNKNOWN_ERROR_CODE = 'UNKNOWN'
+UNHANDLED_ERROR_CODE = 'UNHANDLED'
+
+MAX_KEY_LENGTH = 0xff
+MAX_VALUE_LENGTH = 0xffff
+
+
+class IArgumentType(Interface):
+ """
+ An L{IArgumentType} can serialize a Python object into an AMP box and
+ deserialize information from an AMP box back into a Python object.
+
+ @since: 9.0
+ """
+ def fromBox(name, strings, objects, proto):
+ """
+ Given an argument name and an AMP box containing serialized values,
+ extract one or more Python objects and add them to the C{objects}
+ dictionary.
+
+ @param name: The name associated with this argument. Most commonly,
+ this is the key which can be used to find a serialized value in
+ C{strings} and which should be used as the key in C{objects} to
+ associate with a structured Python object.
+ @type name: C{str}
+
+ @param strings: The AMP box from which to extract one or more
+ values.
+ @type strings: C{dict}
+
+ @param objects: The output dictionary to populate with the value for
+ this argument.
+ @type objects: C{dict}
+
+ @param proto: The protocol instance which received the AMP box being
+ interpreted. Most likely this is an instance of L{AMP}, but
+ this is not guaranteed.
+
+ @return: C{None}
+ """
+
+
+ def toBox(name, strings, objects, proto):
+ """
+ Given an argument name and a dictionary containing structured Python
+ objects, serialize values into one or more strings and add them to
+ the C{strings} dictionary.
+
+ @param name: The name associated with this argument. Most commonly,
+ this is the key which can be used to find an object in
+ C{objects} and which should be used as the key in C{strings} to
+ associate with a C{str} giving the serialized form of that
+ object.
+ @type name: C{str}
+
+ @param strings: The AMP box into which to insert one or more
+ strings.
+ @type strings: C{dict}
+
+ @param objects: The input dictionary from which to extract Python
+ objects to serialize.
+ @type objects: C{dict}
+
+ @param proto: The protocol instance which will send the AMP box once
+ it is fully populated. Most likely this is an instance of
+ L{AMP}, but this is not guaranteed.
+
+ @return: C{None}
+ """
+
+
+
+class IBoxSender(Interface):
+ """
+ A transport which can send L{AmpBox} objects.
+ """
+
+ def sendBox(box):
+ """
+ Send an L{AmpBox}.
+
+ @raise ProtocolSwitched: if the underlying protocol has been
+ switched.
+
+ @raise ConnectionLost: if the underlying connection has already been
+ lost.
+ """
+
+ def unhandledError(failure):
+ """
+ An unhandled error occurred in response to a box. Log it
+ appropriately.
+
+ @param failure: a L{Failure} describing the error that occurred.
+ """
+
+
+
+class IBoxReceiver(Interface):
+ """
+ An application object which can receive L{AmpBox} objects and dispatch them
+ appropriately.
+ """
+
+ def startReceivingBoxes(boxSender):
+ """
+ The L{ampBoxReceived} method will start being called; boxes may be
+ responded to by responding to the given L{IBoxSender}.
+
+ @param boxSender: an L{IBoxSender} provider.
+ """
+
+
+ def ampBoxReceived(box):
+ """
+ A box was received from the transport; dispatch it appropriately.
+ """
+
+
+ def stopReceivingBoxes(reason):
+ """
+ No further boxes will be received on this connection.
+
+ @type reason: L{Failure}
+ """
+
+
+
+class IResponderLocator(Interface):
+ """
+ An application object which can look up appropriate responder methods for
+ AMP commands.
+ """
+
+ def locateResponder(self, name):
+ """
+ Locate a responder method appropriate for the named command.
+
+ @param name: the wire-level name (commandName) of the AMP command to be
+ responded to.
+
+ @return: a 1-argument callable that takes an L{AmpBox} with argument
+ values for the given command, and returns an L{AmpBox} containing
+ argument values for the named command, or a L{Deferred} that fires the
+ same.
+ """
+
+
+
+class AmpError(Exception):
+ """
+ Base class of all Amp-related exceptions.
+ """
+
+
+
+class ProtocolSwitched(Exception):
+ """
+ Connections which have been switched to other protocols can no longer
+ accept traffic at the AMP level. This is raised when you try to send it.
+ """
+
+
+
+class OnlyOneTLS(AmpError):
+ """
+ This is an implementation limitation; TLS may only be started once per
+ connection.
+ """
+
+
+
+class NoEmptyBoxes(AmpError):
+ """
+ You can't have empty boxes on the connection. This is raised when you
+ receive or attempt to send one.
+ """
+
+
+
+class InvalidSignature(AmpError):
+ """
+ You didn't pass all the required arguments.
+ """
+
+
+
+class TooLong(AmpError):
+ """
+ One of the protocol's length limitations was violated.
+
+ @ivar isKey: true if the string being encoded in a key position, false if
+ it was in a value position.
+
+ @ivar isLocal: Was the string encoded locally, or received too long from
+ the network? (It's only physically possible to encode "too long" values on
+ the network for keys.)
+
+ @ivar value: The string that was too long.
+
+ @ivar keyName: If the string being encoded was in a value position, what
+ key was it being encoded for?
+ """
+
+ def __init__(self, isKey, isLocal, value, keyName=None):
+ AmpError.__init__(self)
+ self.isKey = isKey
+ self.isLocal = isLocal
+ self.value = value
+ self.keyName = keyName
+
+
+ def __repr__(self):
+ hdr = self.isKey and "key" or "value"
+ if not self.isKey:
+ hdr += ' ' + repr(self.keyName)
+ lcl = self.isLocal and "local" or "remote"
+ return "%s %s too long: %d" % (lcl, hdr, len(self.value))
+
+
+
+class BadLocalReturn(AmpError):
+ """
+ A bad value was returned from a local command; we were unable to coerce it.
+ """
+ def __init__(self, message, enclosed):
+ AmpError.__init__(self)
+ self.message = message
+ self.enclosed = enclosed
+
+
+ def __repr__(self):
+ return self.message + " " + self.enclosed.getBriefTraceback()
+
+ __str__ = __repr__
+
+
+
+class RemoteAmpError(AmpError):
+ """
+ This error indicates that something went wrong on the remote end of the
+ connection, and the error was serialized and transmitted to you.
+ """
+ def __init__(self, errorCode, description, fatal=False, local=None):
+ """Create a remote error with an error code and description.
+
+ @param errorCode: the AMP error code of this error.
+
+ @param description: some text to show to the user.
+
+ @param fatal: a boolean, true if this error should terminate the
+ connection.
+
+ @param local: a local Failure, if one exists.
+ """
+ if local:
+ localwhat = ' (local)'
+ othertb = local.getBriefTraceback()
+ else:
+ localwhat = ''
+ othertb = ''
+ Exception.__init__(self, "Code<%s>%s: %s%s" % (
+ errorCode, localwhat,
+ description, othertb))
+ self.local = local
+ self.errorCode = errorCode
+ self.description = description
+ self.fatal = fatal
+
+
+
+class UnknownRemoteError(RemoteAmpError):
+ """
+ This means that an error whose type we can't identify was raised from the
+ other side.
+ """
+ def __init__(self, description):
+ errorCode = UNKNOWN_ERROR_CODE
+ RemoteAmpError.__init__(self, errorCode, description)
+
+
+
+class MalformedAmpBox(AmpError):
+ """
+ This error indicates that the wire-level protocol was malformed.
+ """
+
+
+
+class UnhandledCommand(AmpError):
+ """
+ A command received via amp could not be dispatched.
+ """
+
+
+
+class IncompatibleVersions(AmpError):
+ """
+ It was impossible to negotiate a compatible version of the protocol with
+ the other end of the connection.
+ """
+
+
+PROTOCOL_ERRORS = {UNHANDLED_ERROR_CODE: UnhandledCommand}
+
+class AmpBox(dict):
+ """
+ I am a packet in the AMP protocol, much like a regular str:str dictionary.
+ """
+ __slots__ = [] # be like a regular dictionary, don't magically
+ # acquire a __dict__...
+
+
+ def copy(self):
+ """
+ Return another AmpBox just like me.
+ """
+ newBox = self.__class__()
+ newBox.update(self)
+ return newBox
+
+
+ def serialize(self):
+ """
+ Convert me into a wire-encoded string.
+
+ @return: a str encoded according to the rules described in the module
+ docstring.
+ """
+ i = self.items()
+ i.sort()
+ L = []
+ w = L.append
+ for k, v in i:
+ if len(k) > MAX_KEY_LENGTH:
+ raise TooLong(True, True, k, None)
+ if len(v) > MAX_VALUE_LENGTH:
+ raise TooLong(False, True, v, k)
+ for kv in k, v:
+ w(pack("!H", len(kv)))
+ w(kv)
+ w(pack("!H", 0))
+ return ''.join(L)
+
+
+ def _sendTo(self, proto):
+ """
+ Serialize and send this box to a Amp instance. By the time it is being
+ sent, several keys are required. I must have exactly ONE of::
+
+ _ask
+ _answer
+ _error
+
+ If the '_ask' key is set, then the '_command' key must also be
+ set.
+
+ @param proto: an AMP instance.
+ """
+ proto.sendBox(self)
+
+ def __repr__(self):
+ return 'AmpBox(%s)' % (dict.__repr__(self),)
+
+# amp.Box => AmpBox
+
+Box = AmpBox
+
+class QuitBox(AmpBox):
+ """
+ I am an AmpBox that, upon being sent, terminates the connection.
+ """
+ __slots__ = []
+
+
+ def __repr__(self):
+ return 'QuitBox(**%s)' % (super(QuitBox, self).__repr__(),)
+
+
+ def _sendTo(self, proto):
+ """
+ Immediately call loseConnection after sending.
+ """
+ super(QuitBox, self)._sendTo(proto)
+ proto.transport.loseConnection()
+
+
+
+class _SwitchBox(AmpBox):
+ """
+ Implementation detail of ProtocolSwitchCommand: I am a AmpBox which sets
+ up state for the protocol to switch.
+ """
+
+ # DON'T set __slots__ here; we do have an attribute.
+
+ def __init__(self, innerProto, **kw):
+ """
+ Create a _SwitchBox with the protocol to switch to after being sent.
+
+ @param innerProto: the protocol instance to switch to.
+ @type innerProto: an IProtocol provider.
+ """
+ super(_SwitchBox, self).__init__(**kw)
+ self.innerProto = innerProto
+
+
+ def __repr__(self):
+ return '_SwitchBox(%r, **%s)' % (self.innerProto,
+ dict.__repr__(self),)
+
+
+ def _sendTo(self, proto):
+ """
+ Send me; I am the last box on the connection. All further traffic will be
+ over the new protocol.
+ """
+ super(_SwitchBox, self)._sendTo(proto)
+ proto._lockForSwitch()
+ proto._switchTo(self.innerProto)
+
+
+
+class BoxDispatcher:
+ """
+ A L{BoxDispatcher} dispatches '_ask', '_answer', and '_error' L{AmpBox}es,
+ both incoming and outgoing, to their appropriate destinations.
+
+ Outgoing commands are converted into L{Deferred}s and outgoing boxes, and
+ associated tracking state to fire those L{Deferred} when '_answer' boxes
+ come back. Incoming '_answer' and '_error' boxes are converted into
+ callbacks and errbacks on those L{Deferred}s, respectively.
+
+ Incoming '_ask' boxes are converted into method calls on a supplied method
+ locator.
+
+ @ivar _outstandingRequests: a dictionary mapping request IDs to
+ L{Deferred}s which were returned for those requests.
+
+ @ivar locator: an object with a L{locateResponder} method that locates a
+ responder function that takes a Box and returns a result (either a Box or a
+ Deferred which fires one).
+
+ @ivar boxSender: an object which can send boxes, via the L{_sendBox}
+ method, such as an L{AMP} instance.
+ @type boxSender: L{IBoxSender}
+ """
+
+ implements(IBoxReceiver)
+
+ _failAllReason = None
+ _outstandingRequests = None
+ _counter = 0L
+ boxSender = None
+
+ def __init__(self, locator):
+ self._outstandingRequests = {}
+ self.locator = locator
+
+
+ def startReceivingBoxes(self, boxSender):
+ """
+ The given boxSender is going to start calling boxReceived on this
+ L{BoxDispatcher}.
+
+ @param boxSender: The L{IBoxSender} to send command responses to.
+ """
+ self.boxSender = boxSender
+
+
+ def stopReceivingBoxes(self, reason):
+ """
+ No further boxes will be received here. Terminate all currently
+ oustanding command deferreds with the given reason.
+ """
+ self.failAllOutgoing(reason)
+
+
+ def failAllOutgoing(self, reason):
+ """
+ Call the errback on all outstanding requests awaiting responses.
+
+ @param reason: the Failure instance to pass to those errbacks.
+ """
+ self._failAllReason = reason
+ OR = self._outstandingRequests.items()
+ self._outstandingRequests = None # we can never send another request
+ for key, value in OR:
+ value.errback(reason)
+
+
+ def _nextTag(self):
+ """
+ Generate protocol-local serial numbers for _ask keys.
+
+ @return: a string that has not yet been used on this connection.
+ """
+ self._counter += 1
+ return '%x' % (self._counter,)
+
+
+ def _sendBoxCommand(self, command, box, requiresAnswer=True):
+ """
+ Send a command across the wire with the given C{amp.Box}.
+
+ Mutate the given box to give it any additional keys (_command, _ask)
+ required for the command and request/response machinery, then send it.
+
+ If requiresAnswer is True, returns a C{Deferred} which fires when a
+ response is received. The C{Deferred} is fired with an C{amp.Box} on
+ success, or with an C{amp.RemoteAmpError} if an error is received.
+
+ If the Deferred fails and the error is not handled by the caller of
+ this method, the failure will be logged and the connection dropped.
+
+ @param command: a str, the name of the command to issue.
+
+ @param box: an AmpBox with the arguments for the command.
+
+ @param requiresAnswer: a boolean. Defaults to True. If True, return a
+ Deferred which will fire when the other side responds to this command.
+ If False, return None and do not ask the other side for acknowledgement.
+
+ @return: a Deferred which fires the AmpBox that holds the response to
+ this command, or None, as specified by requiresAnswer.
+
+ @raise ProtocolSwitched: if the protocol has been switched.
+ """
+ if self._failAllReason is not None:
+ return fail(self._failAllReason)
+ box[COMMAND] = command
+ tag = self._nextTag()
+ if requiresAnswer:
+ box[ASK] = tag
+ box._sendTo(self.boxSender)
+ if requiresAnswer:
+ result = self._outstandingRequests[tag] = Deferred()
+ else:
+ result = None
+ return result
+
+
+ def callRemoteString(self, command, requiresAnswer=True, **kw):
+ """
+ This is a low-level API, designed only for optimizing simple messages
+ for which the overhead of parsing is too great.
+
+ @param command: a str naming the command.
+
+ @param kw: arguments to the amp box.
+
+ @param requiresAnswer: a boolean. Defaults to True. If True, return a
+ Deferred which will fire when the other side responds to this command.
+ If False, return None and do not ask the other side for acknowledgement.
+
+ @return: a Deferred which fires the AmpBox that holds the response to
+ this command, or None, as specified by requiresAnswer.
+ """
+ box = Box(kw)
+ return self._sendBoxCommand(command, box, requiresAnswer)
+
+
+ def callRemote(self, commandType, *a, **kw):
+ """
+ This is the primary high-level API for sending messages via AMP. Invoke it
+ with a command and appropriate arguments to send a message to this
+ connection's peer.
+
+ @param commandType: a subclass of Command.
+ @type commandType: L{type}
+
+ @param a: Positional (special) parameters taken by the command.
+ Positional parameters will typically not be sent over the wire. The
+ only command included with AMP which uses positional parameters is
+ L{ProtocolSwitchCommand}, which takes the protocol that will be
+ switched to as its first argument.
+
+ @param kw: Keyword arguments taken by the command. These are the
+ arguments declared in the command's 'arguments' attribute. They will
+ be encoded and sent to the peer as arguments for the L{commandType}.
+
+ @return: If L{commandType} has a C{requiresAnswer} attribute set to
+ L{False}, then return L{None}. Otherwise, return a L{Deferred} which
+ fires with a dictionary of objects representing the result of this
+ call. Additionally, this L{Deferred} may fail with an exception
+ representing a connection failure, with L{UnknownRemoteError} if the
+ other end of the connection fails for an unknown reason, or with any
+ error specified as a key in L{commandType}'s C{errors} dictionary.
+ """
+
+ # XXX this takes command subclasses and not command objects on purpose.
+ # There's really no reason to have all this back-and-forth between
+ # command objects and the protocol, and the extra object being created
+ # (the Command instance) is pointless. Command is kind of like
+ # Interface, and should be more like it.
+
+ # In other words, the fact that commandType is instantiated here is an
+ # implementation detail. Don't rely on it.
+
+ try:
+ co = commandType(*a, **kw)
+ except:
+ return fail()
+ return co._doCommand(self)
+
+
+ def unhandledError(self, failure):
+ """
+ This is a terminal callback called after application code has had a
+ chance to quash any errors.
+ """
+ return self.boxSender.unhandledError(failure)
+
+
+ def _answerReceived(self, box):
+ """
+ An AMP box was received that answered a command previously sent with
+ L{callRemote}.
+
+ @param box: an AmpBox with a value for its L{ANSWER} key.
+ """
+ question = self._outstandingRequests.pop(box[ANSWER])
+ question.addErrback(self.unhandledError)
+ question.callback(box)
+
+
+ def _errorReceived(self, box):
+ """
+ An AMP box was received that answered a command previously sent with
+ L{callRemote}, with an error.
+
+ @param box: an L{AmpBox} with a value for its L{ERROR}, L{ERROR_CODE},
+ and L{ERROR_DESCRIPTION} keys.
+ """
+ question = self._outstandingRequests.pop(box[ERROR])
+ question.addErrback(self.unhandledError)
+ errorCode = box[ERROR_CODE]
+ description = box[ERROR_DESCRIPTION]
+ if errorCode in PROTOCOL_ERRORS:
+ exc = PROTOCOL_ERRORS[errorCode](errorCode, description)
+ else:
+ exc = RemoteAmpError(errorCode, description)
+ question.errback(Failure(exc))
+
+
+ def _commandReceived(self, box):
+ """
+ @param box: an L{AmpBox} with a value for its L{COMMAND} and L{ASK}
+ keys.
+ """
+ cmd = box[COMMAND]
+ def formatAnswer(answerBox):
+ answerBox[ANSWER] = box[ASK]
+ return answerBox
+ def formatError(error):
+ if error.check(RemoteAmpError):
+ code = error.value.errorCode
+ desc = error.value.description
+ if error.value.fatal:
+ errorBox = QuitBox()
+ else:
+ errorBox = AmpBox()
+ else:
+ errorBox = QuitBox()
+ log.err(error) # here is where server-side logging happens
+ # if the error isn't handled
+ code = UNKNOWN_ERROR_CODE
+ desc = "Unknown Error"
+ errorBox[ERROR] = box[ASK]
+ errorBox[ERROR_DESCRIPTION] = desc
+ errorBox[ERROR_CODE] = code
+ return errorBox
+ deferred = self.dispatchCommand(box)
+ if ASK in box:
+ deferred.addCallbacks(formatAnswer, formatError)
+ deferred.addCallback(self._safeEmit)
+ deferred.addErrback(self.unhandledError)
+
+
+ def ampBoxReceived(self, box):
+ """
+ An AmpBox was received, representing a command, or an answer to a
+ previously issued command (either successful or erroneous). Respond to
+ it according to its contents.
+
+ @param box: an AmpBox
+
+ @raise NoEmptyBoxes: when a box is received that does not contain an
+ '_answer', '_command' / '_ask', or '_error' key; i.e. one which does not
+ fit into the command / response protocol defined by AMP.
+ """
+ if ANSWER in box:
+ self._answerReceived(box)
+ elif ERROR in box:
+ self._errorReceived(box)
+ elif COMMAND in box:
+ self._commandReceived(box)
+ else:
+ raise NoEmptyBoxes(box)
+
+
+ def _safeEmit(self, aBox):
+ """
+ Emit a box, ignoring L{ProtocolSwitched} and L{ConnectionLost} errors
+ which cannot be usefully handled.
+ """
+ try:
+ aBox._sendTo(self.boxSender)
+ except (ProtocolSwitched, ConnectionLost):
+ pass
+
+
+ def dispatchCommand(self, box):
+ """
+ A box with a _command key was received.
+
+ Dispatch it to a local handler call it.
+
+ @param proto: an AMP instance.
+ @param box: an AmpBox to be dispatched.
+ """
+ cmd = box[COMMAND]
+ responder = self.locator.locateResponder(cmd)
+ if responder is None:
+ return fail(RemoteAmpError(
+ UNHANDLED_ERROR_CODE,
+ "Unhandled Command: %r" % (cmd,),
+ False,
+ local=Failure(UnhandledCommand())))
+ return maybeDeferred(responder, box)
+
+
+
+class CommandLocator:
+ """
+ A L{CommandLocator} is a collection of responders to AMP L{Command}s, with
+ the help of the L{Command.responder} decorator.
+ """
+
+ class __metaclass__(type):
+ """
+ This metaclass keeps track of all of the Command.responder-decorated
+ methods defined since the last CommandLocator subclass was defined. It
+ assumes (usually correctly, but unfortunately not necessarily so) that
+ those commands responders were all declared as methods of the class
+ being defined. Note that this list can be incorrect if users use the
+ Command.responder decorator outside the context of a CommandLocator
+ class declaration.
+
+ The Command.responder decorator explicitly cooperates with this
+ metaclass.
+ """
+
+ _currentClassCommands = []
+ def __new__(cls, name, bases, attrs):
+ commands = cls._currentClassCommands[:]
+ cls._currentClassCommands[:] = []
+ cd = attrs['_commandDispatch'] = {}
+ for base in bases:
+ cls._grabFromBase(cd, base)
+ for commandClass, responderFunc in commands:
+ cd[commandClass.commandName] = (commandClass, responderFunc)
+ subcls = type.__new__(cls, name, bases, attrs)
+ if (bases and (
+ subcls.lookupFunction != CommandLocator.lookupFunction)):
+ def locateResponder(self, name):
+ warnings.warn(
+ "Override locateResponder, not lookupFunction.",
+ category=PendingDeprecationWarning,
+ stacklevel=2)
+ return self.lookupFunction(name)
+ subcls.locateResponder = locateResponder
+ return subcls
+
+ def _grabFromBase(cls, cd, base):
+ if hasattr(base, "_commandDispatch"):
+ cd.update(base._commandDispatch)
+ for subbase in base.__bases__:
+ cls._grabFromBase(cd, subbase)
+ _grabFromBase = classmethod(_grabFromBase)
+
+ implements(IResponderLocator)
+
+
+ def _wrapWithSerialization(self, aCallable, command):
+ """
+ Wrap aCallable with its command's argument de-serialization
+ and result serialization logic.
+
+ @param aCallable: a callable with a 'command' attribute, designed to be
+ called with keyword arguments.
+
+ @param command: the command class whose serialization to use.
+
+ @return: a 1-arg callable which, when invoked with an AmpBox, will
+ deserialize the argument list and invoke appropriate user code for the
+ callable's command, returning a Deferred which fires with the result or
+ fails with an error.
+ """
+ def doit(box):
+ kw = command.parseArguments(box, self)
+ def checkKnownErrors(error):
+ key = error.trap(*command.allErrors)
+ code = command.allErrors[key]
+ desc = str(error.value)
+ return Failure(RemoteAmpError(
+ code, desc, key in command.fatalErrors, local=error))
+ def makeResponseFor(objects):
+ try:
+ return command.makeResponse(objects, self)
+ except:
+ # let's helpfully log this.
+ originalFailure = Failure()
+ raise BadLocalReturn(
+ "%r returned %r and %r could not serialize it" % (
+ aCallable,
+ objects,
+ command),
+ originalFailure)
+ return maybeDeferred(aCallable, **kw).addCallback(
+ makeResponseFor).addErrback(
+ checkKnownErrors)
+ return doit
+
+
+ def lookupFunction(self, name):
+ """
+ Deprecated synonym for L{locateResponder}
+ """
+ if self.__class__.lookupFunction != CommandLocator.lookupFunction:
+ return CommandLocator.locateResponder(self, name)
+ else:
+ warnings.warn("Call locateResponder, not lookupFunction.",
+ category=PendingDeprecationWarning,
+ stacklevel=2)
+ return self.locateResponder(name)
+
+
+ def locateResponder(self, name):
+ """
+ Locate a callable to invoke when executing the named command.
+
+ @param name: the normalized name (from the wire) of the command.
+
+ @return: a 1-argument function that takes a Box and returns a box or a
+ Deferred which fires a Box, for handling the command identified by the
+ given name, or None, if no appropriate responder can be found.
+ """
+ # Try to find a high-level method to invoke, and if we can't find one,
+ # fall back to a low-level one.
+ cd = self._commandDispatch
+ if name in cd:
+ commandClass, responderFunc = cd[name]
+ responderMethod = types.MethodType(
+ responderFunc, self, self.__class__)
+ return self._wrapWithSerialization(responderMethod, commandClass)
+
+
+
+class SimpleStringLocator(object):
+ """
+ Implement the L{locateResponder} method to do simple, string-based
+ dispatch.
+ """
+
+ implements(IResponderLocator)
+
+ baseDispatchPrefix = 'amp_'
+
+ def locateResponder(self, name):
+ """
+ Locate a callable to invoke when executing the named command.
+
+ @return: a function with the name C{"amp_" + name} on L{self}, or None
+ if no such function exists. This function will then be called with the
+ L{AmpBox} itself as an argument.
+
+ @param name: the normalized name (from the wire) of the command.
+ """
+ fName = self.baseDispatchPrefix + (name.upper())
+ return getattr(self, fName, None)
+
+
+
+PYTHON_KEYWORDS = [
+ 'and', 'del', 'for', 'is', 'raise', 'assert', 'elif', 'from', 'lambda',
+ 'return', 'break', 'else', 'global', 'not', 'try', 'class', 'except',
+ 'if', 'or', 'while', 'continue', 'exec', 'import', 'pass', 'yield',
+ 'def', 'finally', 'in', 'print']
+
+
+
+def _wireNameToPythonIdentifier(key):
+ """
+ (Private) Normalize an argument name from the wire for use with Python
+ code. If the return value is going to be a python keyword it will be
+ capitalized. If it contains any dashes they will be replaced with
+ underscores.
+
+ The rationale behind this method is that AMP should be an inherently
+ multi-language protocol, so message keys may contain all manner of bizarre
+ bytes. This is not a complete solution; there are still forms of arguments
+ that this implementation will be unable to parse. However, Python
+ identifiers share a huge raft of properties with identifiers from many
+ other languages, so this is a 'good enough' effort for now. We deal
+ explicitly with dashes because that is the most likely departure: Lisps
+ commonly use dashes to separate method names, so protocols initially
+ implemented in a lisp amp dialect may use dashes in argument or command
+ names.
+
+ @param key: a str, looking something like 'foo-bar-baz' or 'from'
+
+ @return: a str which is a valid python identifier, looking something like
+ 'foo_bar_baz' or 'From'.
+ """
+ lkey = key.replace("-", "_")
+ if lkey in PYTHON_KEYWORDS:
+ return lkey.title()
+ return lkey
+
+
+
+class Argument:
+ """
+ Base-class of all objects that take values from Amp packets and convert
+ them into objects for Python functions.
+
+ This implementation of L{IArgumentType} provides several higher-level
+ hooks for subclasses to override. See L{toString} and L{fromString}
+ which will be used to define the behavior of L{IArgumentType.toBox} and
+ L{IArgumentType.fromBox}, respectively.
+ """
+ implements(IArgumentType)
+
+ optional = False
+
+
+ def __init__(self, optional=False):
+ """
+ Create an Argument.
+
+ @param optional: a boolean indicating whether this argument can be
+ omitted in the protocol.
+ """
+ self.optional = optional
+
+
+ def retrieve(self, d, name, proto):
+ """
+ Retrieve the given key from the given dictionary, removing it if found.
+
+ @param d: a dictionary.
+
+ @param name: a key in L{d}.
+
+ @param proto: an instance of an AMP.
+
+ @raise KeyError: if I am not optional and no value was found.
+
+ @return: d[name].
+ """
+ if self.optional:
+ value = d.get(name)
+ if value is not None:
+ del d[name]
+ else:
+ value = d.pop(name)
+ return value
+
+
+ def fromBox(self, name, strings, objects, proto):
+ """
+ Populate an 'out' dictionary with mapping names to Python values
+ decoded from an 'in' AmpBox mapping strings to string values.
+
+ @param name: the argument name to retrieve
+ @type name: str
+
+ @param strings: The AmpBox to read string(s) from, a mapping of
+ argument names to string values.
+ @type strings: AmpBox
+
+ @param objects: The dictionary to write object(s) to, a mapping of
+ names to Python objects.
+ @type objects: dict
+
+ @param proto: an AMP instance.
+ """
+ st = self.retrieve(strings, name, proto)
+ nk = _wireNameToPythonIdentifier(name)
+ if self.optional and st is None:
+ objects[nk] = None
+ else:
+ objects[nk] = self.fromStringProto(st, proto)
+
+
+ def toBox(self, name, strings, objects, proto):
+ """
+ Populate an 'out' AmpBox with strings encoded from an 'in' dictionary
+ mapping names to Python values.
+
+ @param name: the argument name to retrieve
+ @type name: str
+
+ @param strings: The AmpBox to write string(s) to, a mapping of
+ argument names to string values.
+ @type strings: AmpBox
+
+ @param objects: The dictionary to read object(s) from, a mapping of
+ names to Python objects.
+
+ @type objects: dict
+
+ @param proto: the protocol we are converting for.
+ @type proto: AMP
+ """
+ obj = self.retrieve(objects, _wireNameToPythonIdentifier(name), proto)
+ if self.optional and obj is None:
+ # strings[name] = None
+ pass
+ else:
+ strings[name] = self.toStringProto(obj, proto)
+
+
+ def fromStringProto(self, inString, proto):
+ """
+ Convert a string to a Python value.
+
+ @param inString: the string to convert.
+
+ @param proto: the protocol we are converting for.
+ @type proto: AMP
+
+ @return: a Python object.
+ """
+ return self.fromString(inString)
+
+
+ def toStringProto(self, inObject, proto):
+ """
+ Convert a Python object to a string.
+
+ @param inObject: the object to convert.
+
+ @param proto: the protocol we are converting for.
+ @type proto: AMP
+ """
+ return self.toString(inObject)
+
+
+ def fromString(self, inString):
+ """
+ Convert a string to a Python object. Subclasses must implement this.
+
+ @param inString: the string to convert.
+ @type inString: str
+
+ @return: the decoded value from inString
+ """
+
+
+ def toString(self, inObject):
+ """
+ Convert a Python object into a string for passing over the network.
+
+ @param inObject: an object of the type that this Argument is intended
+ to deal with.
+
+ @return: the wire encoding of inObject
+ @rtype: str
+ """
+
+
+
+class Integer(Argument):
+ """
+ Convert to and from 'int'.
+ """
+ fromString = int
+ def toString(self, inObject):
+ return str(int(inObject))
+
+
+
+class String(Argument):
+ """
+ Don't do any conversion at all; just pass through 'str'.
+ """
+ def toString(self, inObject):
+ return inObject
+
+
+ def fromString(self, inString):
+ return inString
+
+
+
+class Float(Argument):
+ """
+ Encode floating-point values on the wire as their repr.
+ """
+ fromString = float
+ toString = repr
+
+
+
+class Boolean(Argument):
+ """
+ Encode True or False as "True" or "False" on the wire.
+ """
+ def fromString(self, inString):
+ if inString == 'True':
+ return True
+ elif inString == 'False':
+ return False
+ else:
+ raise TypeError("Bad boolean value: %r" % (inString,))
+
+
+ def toString(self, inObject):
+ if inObject:
+ return 'True'
+ else:
+ return 'False'
+
+
+
+class Unicode(String):
+ """
+ Encode a unicode string on the wire as UTF-8.
+ """
+
+ def toString(self, inObject):
+ # assert isinstance(inObject, unicode)
+ return String.toString(self, inObject.encode('utf-8'))
+
+
+ def fromString(self, inString):
+ # assert isinstance(inString, str)
+ return String.fromString(self, inString).decode('utf-8')
+
+
+
+class Path(Unicode):
+ """
+ Encode and decode L{filepath.FilePath} instances as paths on the wire.
+
+ This is really intended for use with subprocess communication tools:
+ exchanging pathnames on different machines over a network is not generally
+ meaningful, but neither is it disallowed; you can use this to communicate
+ about NFS paths, for example.
+ """
+ def fromString(self, inString):
+ return filepath.FilePath(Unicode.fromString(self, inString))
+
+
+ def toString(self, inObject):
+ return Unicode.toString(self, inObject.path)
+
+
+
+class ListOf(Argument):
+ """
+ Encode and decode lists of instances of a single other argument type.
+
+ For example, if you want to pass::
+
+ [3, 7, 9, 15]
+
+ You can create an argument like this::
+
+ ListOf(Integer())
+
+ The serialized form of the entire list is subject to the limit imposed by
+ L{MAX_VALUE_LENGTH}. List elements are represented as 16-bit length
+ prefixed strings. The argument type passed to the L{ListOf} initializer is
+ responsible for producing the serialized form of each element.
+
+ @ivar elementType: The L{Argument} instance used to encode and decode list
+ elements (note, not an arbitrary L{IArgument} implementation:
+ arguments must be implemented using only the C{fromString} and
+ C{toString} methods, not the C{fromBox} and C{toBox} methods).
+
+ @since: 10.0
+ """
+ def __init__(self, elementType):
+ self.elementType = elementType
+
+
+ def fromString(self, inString):
+ """
+ Convert the serialized form of a list of instances of some type back
+ into that list.
+ """
+ strings = []
+ parser = Int16StringReceiver()
+ parser.stringReceived = strings.append
+ parser.dataReceived(inString)
+ return map(self.elementType.fromString, strings)
+
+
+ def toString(self, inObject):
+ """
+ Serialize the given list of objects to a single string.
+ """
+ strings = []
+ for obj in inObject:
+ serialized = self.elementType.toString(obj)
+ strings.append(pack('!H', len(serialized)))
+ strings.append(serialized)
+ return ''.join(strings)
+
+
+
+class AmpList(Argument):
+ """
+ Convert a list of dictionaries into a list of AMP boxes on the wire.
+
+ For example, if you want to pass::
+
+ [{'a': 7, 'b': u'hello'}, {'a': 9, 'b': u'goodbye'}]
+
+ You might use an AmpList like this in your arguments or response list::
+
+ AmpList([('a', Integer()),
+ ('b', Unicode())])
+ """
+ def __init__(self, subargs, optional=False):
+ """
+ Create an AmpList.
+
+ @param subargs: a list of 2-tuples of ('name', argument) describing the
+ schema of the dictionaries in the sequence of amp boxes.
+
+ @param optional: a boolean indicating whether this argument can be
+ omitted in the protocol.
+ """
+ self.subargs = subargs
+ Argument.__init__(self, optional)
+
+
+ def fromStringProto(self, inString, proto):
+ boxes = parseString(inString)
+ values = [_stringsToObjects(box, self.subargs, proto)
+ for box in boxes]
+ return values
+
+
+ def toStringProto(self, inObject, proto):
+ return ''.join([_objectsToStrings(
+ objects, self.subargs, Box(), proto
+ ).serialize() for objects in inObject])
+
+class Command:
+ """
+ Subclass me to specify an AMP Command.
+
+ @cvar arguments: A list of 2-tuples of (name, Argument-subclass-instance),
+ specifying the names and values of the parameters which are required for
+ this command.
+
+ @cvar response: A list like L{arguments}, but instead used for the return
+ value.
+
+ @cvar errors: A mapping of subclasses of L{Exception} to wire-protocol tags
+ for errors represented as L{str}s. Responders which raise keys from this
+ dictionary will have the error translated to the corresponding tag on the
+ wire. Invokers which receive Deferreds from invoking this command with
+ L{AMP.callRemote} will potentially receive Failures with keys from this
+ mapping as their value. This mapping is inherited; if you declare a
+ command which handles C{FooError} as 'FOO_ERROR', then subclass it and
+ specify C{BarError} as 'BAR_ERROR', responders to the subclass may raise
+ either C{FooError} or C{BarError}, and invokers must be able to deal with
+ either of those exceptions.
+
+ @cvar fatalErrors: like 'errors', but errors in this list will always
+ terminate the connection, despite being of a recognizable error type.
+
+ @cvar commandType: The type of Box used to issue commands; useful only for
+ protocol-modifying behavior like startTLS or protocol switching. Defaults
+ to a plain vanilla L{Box}.
+
+ @cvar responseType: The type of Box used to respond to this command; only
+ useful for protocol-modifying behavior like startTLS or protocol switching.
+ Defaults to a plain vanilla L{Box}.
+
+ @ivar requiresAnswer: a boolean; defaults to True. Set it to False on your
+ subclass if you want callRemote to return None. Note: this is a hint only
+ to the client side of the protocol. The return-type of a command responder
+ method must always be a dictionary adhering to the contract specified by
+ L{response}, because clients are always free to request a response if they
+ want one.
+ """
+
+ class __metaclass__(type):
+ """
+ Metaclass hack to establish reverse-mappings for 'errors' and
+ 'fatalErrors' as class vars.
+ """
+ def __new__(cls, name, bases, attrs):
+ re = attrs['reverseErrors'] = {}
+ er = attrs['allErrors'] = {}
+ if 'commandName' not in attrs:
+ attrs['commandName'] = name
+ newtype = type.__new__(cls, name, bases, attrs)
+ errors = {}
+ fatalErrors = {}
+ accumulateClassDict(newtype, 'errors', errors)
+ accumulateClassDict(newtype, 'fatalErrors', fatalErrors)
+ for v, k in errors.iteritems():
+ re[k] = v
+ er[v] = k
+ for v, k in fatalErrors.iteritems():
+ re[k] = v
+ er[v] = k
+ return newtype
+
+ arguments = []
+ response = []
+ extra = []
+ errors = {}
+ fatalErrors = {}
+
+ commandType = Box
+ responseType = Box
+
+ requiresAnswer = True
+
+
+ def __init__(self, **kw):
+ """
+ Create an instance of this command with specified values for its
+ parameters.
+
+ @param kw: a dict containing an appropriate value for each name
+ specified in the L{arguments} attribute of my class.
+
+ @raise InvalidSignature: if you forgot any required arguments.
+ """
+ self.structured = kw
+ givenArgs = kw.keys()
+ forgotten = []
+ for name, arg in self.arguments:
+ pythonName = _wireNameToPythonIdentifier(name)
+ if pythonName not in givenArgs and not arg.optional:
+ forgotten.append(pythonName)
+ if forgotten:
+ raise InvalidSignature("forgot %s for %s" % (
+ ', '.join(forgotten), self.commandName))
+ forgotten = []
+
+
+ def makeResponse(cls, objects, proto):
+ """
+ Serialize a mapping of arguments using this L{Command}'s
+ response schema.
+
+ @param objects: a dict with keys matching the names specified in
+ self.response, having values of the types that the Argument objects in
+ self.response can format.
+
+ @param proto: an L{AMP}.
+
+ @return: an L{AmpBox}.
+ """
+ try:
+ responseType = cls.responseType()
+ except:
+ return fail()
+ return _objectsToStrings(objects, cls.response, responseType, proto)
+ makeResponse = classmethod(makeResponse)
+
+
+ def makeArguments(cls, objects, proto):
+ """
+ Serialize a mapping of arguments using this L{Command}'s
+ argument schema.
+
+ @param objects: a dict with keys similar to the names specified in
+ self.arguments, having values of the types that the Argument objects in
+ self.arguments can parse.
+
+ @param proto: an L{AMP}.
+
+ @return: An instance of this L{Command}'s C{commandType}.
+ """
+ allowedNames = set()
+ for (argName, ignored) in cls.arguments:
+ allowedNames.add(_wireNameToPythonIdentifier(argName))
+
+ for intendedArg in objects:
+ if intendedArg not in allowedNames:
+ raise InvalidSignature(
+ "%s is not a valid argument" % (intendedArg,))
+ return _objectsToStrings(objects, cls.arguments, cls.commandType(),
+ proto)
+ makeArguments = classmethod(makeArguments)
+
+
+ def parseResponse(cls, box, protocol):
+ """
+ Parse a mapping of serialized arguments using this
+ L{Command}'s response schema.
+
+ @param box: A mapping of response-argument names to the
+ serialized forms of those arguments.
+ @param protocol: The L{AMP} protocol.
+
+ @return: A mapping of response-argument names to the parsed
+ forms.
+ """
+ return _stringsToObjects(box, cls.response, protocol)
+ parseResponse = classmethod(parseResponse)
+
+
+ def parseArguments(cls, box, protocol):
+ """
+ Parse a mapping of serialized arguments using this
+ L{Command}'s argument schema.
+
+ @param box: A mapping of argument names to the seralized forms
+ of those arguments.
+ @param protocol: The L{AMP} protocol.
+
+ @return: A mapping of argument names to the parsed forms.
+ """
+ return _stringsToObjects(box, cls.arguments, protocol)
+ parseArguments = classmethod(parseArguments)
+
+
+ def responder(cls, methodfunc):
+ """
+ Declare a method to be a responder for a particular command.
+
+ This is a decorator.
+
+ Use like so::
+
+ class MyCommand(Command):
+ arguments = [('a', ...), ('b', ...)]
+
+ class MyProto(AMP):
+ def myFunMethod(self, a, b):
+ ...
+ MyCommand.responder(myFunMethod)
+
+ Notes: Although decorator syntax is not used within Twisted, this
+ function returns its argument and is therefore safe to use with
+ decorator syntax.
+
+ This is not thread safe. Don't declare AMP subclasses in other
+ threads. Don't declare responders outside the scope of AMP subclasses;
+ the behavior is undefined.
+
+ @param methodfunc: A function which will later become a method, which
+ has a keyword signature compatible with this command's L{argument} list
+ and returns a dictionary with a set of keys compatible with this
+ command's L{response} list.
+
+ @return: the methodfunc parameter.
+ """
+ CommandLocator._currentClassCommands.append((cls, methodfunc))
+ return methodfunc
+ responder = classmethod(responder)
+
+
+ # Our only instance method
+ def _doCommand(self, proto):
+ """
+ Encode and send this Command to the given protocol.
+
+ @param proto: an AMP, representing the connection to send to.
+
+ @return: a Deferred which will fire or error appropriately when the
+ other side responds to the command (or error if the connection is lost
+ before it is responded to).
+ """
+
+ def _massageError(error):
+ error.trap(RemoteAmpError)
+ rje = error.value
+ errorType = self.reverseErrors.get(rje.errorCode,
+ UnknownRemoteError)
+ return Failure(errorType(rje.description))
+
+ d = proto._sendBoxCommand(self.commandName,
+ self.makeArguments(self.structured, proto),
+ self.requiresAnswer)
+
+ if self.requiresAnswer:
+ d.addCallback(self.parseResponse, proto)
+ d.addErrback(_massageError)
+
+ return d
+
+
+
+class _NoCertificate:
+ """
+ This is for peers which don't want to use a local certificate. Used by
+ AMP because AMP's internal language is all about certificates and this
+ duck-types in the appropriate place; this API isn't really stable though,
+ so it's not exposed anywhere public.
+
+ For clients, it will use ephemeral DH keys, or whatever the default is for
+ certificate-less clients in OpenSSL. For servers, it will generate a
+ temporary self-signed certificate with garbage values in the DN and use
+ that.
+ """
+
+ def __init__(self, client):
+ """
+ Create a _NoCertificate which either is or isn't for the client side of
+ the connection.
+
+ @param client: True if we are a client and should truly have no
+ certificate and be anonymous, False if we are a server and actually
+ have to generate a temporary certificate.
+
+ @type client: bool
+ """
+ self.client = client
+
+
+ def options(self, *authorities):
+ """
+ Behaves like L{twisted.internet.ssl.PrivateCertificate.options}().
+ """
+ if not self.client:
+ # do some crud with sslverify to generate a temporary self-signed
+ # certificate. This is SLOOOWWWWW so it is only in the absolute
+ # worst, most naive case.
+
+ # We have to do this because OpenSSL will not let both the server
+ # and client be anonymous.
+ sharedDN = DN(CN='TEMPORARY CERTIFICATE')
+ key = KeyPair.generate()
+ cr = key.certificateRequest(sharedDN)
+ sscrd = key.signCertificateRequest(sharedDN, cr, lambda dn: True, 1)
+ cert = key.newCertificate(sscrd)
+ return cert.options(*authorities)
+ options = dict()
+ if authorities:
+ options.update(dict(verify=True,
+ requireCertificate=True,
+ caCerts=[auth.original for auth in authorities]))
+ occo = CertificateOptions(**options)
+ return occo
+
+
+
+class _TLSBox(AmpBox):
+ """
+ I am an AmpBox that, upon being sent, initiates a TLS connection.
+ """
+ __slots__ = []
+
+ def __init__(self):
+ if ssl is None:
+ raise RemoteAmpError("TLS_ERROR", "TLS not available")
+ AmpBox.__init__(self)
+
+
+ def _keyprop(k, default):
+ return property(lambda self: self.get(k, default))
+
+
+ # These properties are described in startTLS
+ certificate = _keyprop('tls_localCertificate', _NoCertificate(False))
+ verify = _keyprop('tls_verifyAuthorities', None)
+
+ def _sendTo(self, proto):
+ """
+ Send my encoded value to the protocol, then initiate TLS.
+ """
+ ab = AmpBox(self)
+ for k in ['tls_localCertificate',
+ 'tls_verifyAuthorities']:
+ ab.pop(k, None)
+ ab._sendTo(proto)
+ proto._startTLS(self.certificate, self.verify)
+
+
+
+class _LocalArgument(String):
+ """
+ Local arguments are never actually relayed across the wire. This is just a
+ shim so that StartTLS can pretend to have some arguments: if arguments
+ acquire documentation properties, replace this with something nicer later.
+ """
+
+ def fromBox(self, name, strings, objects, proto):
+ pass
+
+
+
+class StartTLS(Command):
+ """
+ Use, or subclass, me to implement a command that starts TLS.
+
+ Callers of StartTLS may pass several special arguments, which affect the
+ TLS negotiation:
+
+ - tls_localCertificate: This is a
+ twisted.internet.ssl.PrivateCertificate which will be used to secure
+ the side of the connection it is returned on.
+
+ - tls_verifyAuthorities: This is a list of
+ twisted.internet.ssl.Certificate objects that will be used as the
+ certificate authorities to verify our peer's certificate.
+
+ Each of those special parameters may also be present as a key in the
+ response dictionary.
+ """
+
+ arguments = [("tls_localCertificate", _LocalArgument(optional=True)),
+ ("tls_verifyAuthorities", _LocalArgument(optional=True))]
+
+ response = [("tls_localCertificate", _LocalArgument(optional=True)),
+ ("tls_verifyAuthorities", _LocalArgument(optional=True))]
+
+ responseType = _TLSBox
+
+ def __init__(self, **kw):
+ """
+ Create a StartTLS command. (This is private. Use AMP.callRemote.)
+
+ @param tls_localCertificate: the PrivateCertificate object to use to
+ secure the connection. If it's None, or unspecified, an ephemeral DH
+ key is used instead.
+
+ @param tls_verifyAuthorities: a list of Certificate objects which
+ represent root certificates to verify our peer with.
+ """
+ if ssl is None:
+ raise RuntimeError("TLS not available.")
+ self.certificate = kw.pop('tls_localCertificate', _NoCertificate(True))
+ self.authorities = kw.pop('tls_verifyAuthorities', None)
+ Command.__init__(self, **kw)
+
+
+ def _doCommand(self, proto):
+ """
+ When a StartTLS command is sent, prepare to start TLS, but don't actually
+ do it; wait for the acknowledgement, then initiate the TLS handshake.
+ """
+ d = Command._doCommand(self, proto)
+ proto._prepareTLS(self.certificate, self.authorities)
+ # XXX before we get back to user code we are going to start TLS...
+ def actuallystart(response):
+ proto._startTLS(self.certificate, self.authorities)
+ return response
+ d.addCallback(actuallystart)
+ return d
+
+
+
+class ProtocolSwitchCommand(Command):
+ """
+ Use this command to switch from something Amp-derived to a different
+ protocol mid-connection. This can be useful to use amp as the
+ connection-startup negotiation phase. Since TLS is a different layer
+ entirely, you can use Amp to negotiate the security parameters of your
+ connection, then switch to a different protocol, and the connection will
+ remain secured.
+ """
+
+ def __init__(self, _protoToSwitchToFactory, **kw):
+ """
+ Create a ProtocolSwitchCommand.
+
+ @param _protoToSwitchToFactory: a ProtocolFactory which will generate
+ the Protocol to switch to.
+
+ @param kw: Keyword arguments, encoded and handled normally as
+ L{Command} would.
+ """
+
+ self.protoToSwitchToFactory = _protoToSwitchToFactory
+ super(ProtocolSwitchCommand, self).__init__(**kw)
+
+
+ def makeResponse(cls, innerProto, proto):
+ return _SwitchBox(innerProto)
+ makeResponse = classmethod(makeResponse)
+
+
+ def _doCommand(self, proto):
+ """
+ When we emit a ProtocolSwitchCommand, lock the protocol, but don't actually
+ switch to the new protocol unless an acknowledgement is received. If
+ an error is received, switch back.
+ """
+ d = super(ProtocolSwitchCommand, self)._doCommand(proto)
+ proto._lockForSwitch()
+ def switchNow(ign):
+ innerProto = self.protoToSwitchToFactory.buildProtocol(
+ proto.transport.getPeer())
+ proto._switchTo(innerProto, self.protoToSwitchToFactory)
+ return ign
+ def handle(ign):
+ proto._unlockFromSwitch()
+ self.protoToSwitchToFactory.clientConnectionFailed(
+ None, Failure(CONNECTION_LOST))
+ return ign
+ return d.addCallbacks(switchNow, handle)
+
+
+
+class BinaryBoxProtocol(StatefulStringProtocol, Int16StringReceiver):
+ """
+ A protocol for receving L{Box}es - key/value pairs - via length-prefixed
+ strings. A box is composed of:
+
+ - any number of key-value pairs, described by:
+ - a 2-byte network-endian packed key length (of which the first
+ byte must be null, and the second must be non-null: i.e. the
+ value of the length must be 1-255)
+ - a key, comprised of that many bytes
+ - a 2-byte network-endian unsigned value length (up to the maximum
+ of 65535)
+ - a value, comprised of that many bytes
+ - 2 null bytes
+
+ In other words, an even number of strings prefixed with packed unsigned
+ 16-bit integers, and then a 0-length string to indicate the end of the box.
+
+ This protocol also implements 2 extra private bits of functionality related
+ to the byte boundaries between messages; it can start TLS between two given
+ boxes or switch to an entirely different protocol. However, due to some
+ tricky elements of the implementation, the public interface to this
+ functionality is L{ProtocolSwitchCommand} and L{StartTLS}.
+
+ @ivar _keyLengthLimitExceeded: A flag which is only true when the
+ connection is being closed because a key length prefix which was longer
+ than allowed by the protocol was received.
+
+ @ivar boxReceiver: an L{IBoxReceiver} provider, whose L{ampBoxReceived}
+ method will be invoked for each L{Box} that is received.
+ """
+
+ implements(IBoxSender)
+
+ _justStartedTLS = False
+ _startingTLSBuffer = None
+ _locked = False
+ _currentKey = None
+ _currentBox = None
+
+ _keyLengthLimitExceeded = False
+
+ hostCertificate = None
+ noPeerCertificate = False # for tests
+ innerProtocol = None
+ innerProtocolClientFactory = None
+
+ def __init__(self, boxReceiver):
+ self.boxReceiver = boxReceiver
+
+
+ def _switchTo(self, newProto, clientFactory=None):
+ """
+ Switch this BinaryBoxProtocol's transport to a new protocol. You need
+ to do this 'simultaneously' on both ends of a connection; the easiest
+ way to do this is to use a subclass of ProtocolSwitchCommand.
+
+ @param newProto: the new protocol instance to switch to.
+
+ @param clientFactory: the ClientFactory to send the
+ L{clientConnectionLost} notification to.
+ """
+ # All the data that Int16Receiver has not yet dealt with belongs to our
+ # new protocol: luckily it's keeping that in a handy (although
+ # ostensibly internal) variable for us:
+ newProtoData = self.recvd
+ # We're quite possibly in the middle of a 'dataReceived' loop in
+ # Int16StringReceiver: let's make sure that the next iteration, the
+ # loop will break and not attempt to look at something that isn't a
+ # length prefix.
+ self.recvd = ''
+ # Finally, do the actual work of setting up the protocol and delivering
+ # its first chunk of data, if one is available.
+ self.innerProtocol = newProto
+ self.innerProtocolClientFactory = clientFactory
+ newProto.makeConnection(self.transport)
+ newProto.dataReceived(newProtoData)
+
+
+ def sendBox(self, box):
+ """
+ Send a amp.Box to my peer.
+
+ Note: transport.write is never called outside of this method.
+
+ @param box: an AmpBox.
+
+ @raise ProtocolSwitched: if the protocol has previously been switched.
+
+ @raise ConnectionLost: if the connection has previously been lost.
+ """
+ if self._locked:
+ raise ProtocolSwitched(
+ "This connection has switched: no AMP traffic allowed.")
+ if self.transport is None:
+ raise ConnectionLost()
+ if self._startingTLSBuffer is not None:
+ self._startingTLSBuffer.append(box)
+ else:
+ self.transport.write(box.serialize())
+
+
+ def makeConnection(self, transport):
+ """
+ Notify L{boxReceiver} that it is about to receive boxes from this
+ protocol by invoking L{startReceivingBoxes}.
+ """
+ self.transport = transport
+ self.boxReceiver.startReceivingBoxes(self)
+ self.connectionMade()
+
+
+ def dataReceived(self, data):
+ """
+ Either parse incoming data as L{AmpBox}es or relay it to our nested
+ protocol.
+ """
+ if self._justStartedTLS:
+ self._justStartedTLS = False
+ # If we already have an inner protocol, then we don't deliver data to
+ # the protocol parser any more; we just hand it off.
+ if self.innerProtocol is not None:
+ self.innerProtocol.dataReceived(data)
+ return
+ return Int16StringReceiver.dataReceived(self, data)
+
+
+ def connectionLost(self, reason):
+ """
+ The connection was lost; notify any nested protocol.
+ """
+ if self.innerProtocol is not None:
+ self.innerProtocol.connectionLost(reason)
+ if self.innerProtocolClientFactory is not None:
+ self.innerProtocolClientFactory.clientConnectionLost(None, reason)
+ if self._keyLengthLimitExceeded:
+ failReason = Failure(TooLong(True, False, None, None))
+ elif reason.check(ConnectionClosed) and self._justStartedTLS:
+ # We just started TLS and haven't received any data. This means
+ # the other connection didn't like our cert (although they may not
+ # have told us why - later Twisted should make 'reason' into a TLS
+ # error.)
+ failReason = PeerVerifyError(
+ "Peer rejected our certificate for an unknown reason.")
+ else:
+ failReason = reason
+ self.boxReceiver.stopReceivingBoxes(failReason)
+
+
+ # The longest key allowed
+ _MAX_KEY_LENGTH = 255
+
+ # The longest value allowed (this is somewhat redundant, as longer values
+ # cannot be encoded - ah well).
+ _MAX_VALUE_LENGTH = 65535
+
+ # The first thing received is a key.
+ MAX_LENGTH = _MAX_KEY_LENGTH
+
+ def proto_init(self, string):
+ """
+ String received in the 'init' state.
+ """
+ self._currentBox = AmpBox()
+ return self.proto_key(string)
+
+
+ def proto_key(self, string):
+ """
+ String received in the 'key' state. If the key is empty, a complete
+ box has been received.
+ """
+ if string:
+ self._currentKey = string
+ self.MAX_LENGTH = self._MAX_VALUE_LENGTH
+ return 'value'
+ else:
+ self.boxReceiver.ampBoxReceived(self._currentBox)
+ self._currentBox = None
+ return 'init'
+
+
+ def proto_value(self, string):
+ """
+ String received in the 'value' state.
+ """
+ self._currentBox[self._currentKey] = string
+ self._currentKey = None
+ self.MAX_LENGTH = self._MAX_KEY_LENGTH
+ return 'key'
+
+
+ def lengthLimitExceeded(self, length):
+ """
+ The key length limit was exceeded. Disconnect the transport and make
+ sure a meaningful exception is reported.
+ """
+ self._keyLengthLimitExceeded = True
+ self.transport.loseConnection()
+
+
+ def _lockForSwitch(self):
+ """
+ Lock this binary protocol so that no further boxes may be sent. This
+ is used when sending a request to switch underlying protocols. You
+ probably want to subclass ProtocolSwitchCommand rather than calling
+ this directly.
+ """
+ self._locked = True
+
+
+ def _unlockFromSwitch(self):
+ """
+ Unlock this locked binary protocol so that further boxes may be sent
+ again. This is used after an attempt to switch protocols has failed
+ for some reason.
+ """
+ if self.innerProtocol is not None:
+ raise ProtocolSwitched("Protocol already switched. Cannot unlock.")
+ self._locked = False
+
+
+ def _prepareTLS(self, certificate, verifyAuthorities):
+ """
+ Used by StartTLSCommand to put us into the state where we don't
+ actually send things that get sent, instead we buffer them. see
+ L{_sendBox}.
+ """
+ self._startingTLSBuffer = []
+ if self.hostCertificate is not None:
+ raise OnlyOneTLS(
+ "Previously authenticated connection between %s and %s "
+ "is trying to re-establish as %s" % (
+ self.hostCertificate,
+ self.peerCertificate,
+ (certificate, verifyAuthorities)))
+
+
+ def _startTLS(self, certificate, verifyAuthorities):
+ """
+ Used by TLSBox to initiate the SSL handshake.
+
+ @param certificate: a L{twisted.internet.ssl.PrivateCertificate} for
+ use locally.
+
+ @param verifyAuthorities: L{twisted.internet.ssl.Certificate} instances
+ representing certificate authorities which will verify our peer.
+ """
+ self.hostCertificate = certificate
+ self._justStartedTLS = True
+ if verifyAuthorities is None:
+ verifyAuthorities = ()
+ self.transport.startTLS(certificate.options(*verifyAuthorities))
+ stlsb = self._startingTLSBuffer
+ if stlsb is not None:
+ self._startingTLSBuffer = None
+ for box in stlsb:
+ self.sendBox(box)
+
+
+ def _getPeerCertificate(self):
+ if self.noPeerCertificate:
+ return None
+ return Certificate.peerFromTransport(self.transport)
+ peerCertificate = property(_getPeerCertificate)
+
+
+ def unhandledError(self, failure):
+ """
+ The buck stops here. This error was completely unhandled, time to
+ terminate the connection.
+ """
+ log.msg("Amp server or network failure "
+ "unhandled by client application:")
+ log.err(failure)
+ log.msg(
+ "Dropping connection! "
+ "To avoid, add errbacks to ALL remote commands!")
+ if self.transport is not None:
+ self.transport.loseConnection()
+
+
+ def _defaultStartTLSResponder(self):
+ """
+ The default TLS responder doesn't specify any certificate or anything.
+
+ From a security perspective, it's little better than a plain-text
+ connection - but it is still a *bit* better, so it's included for
+ convenience.
+
+ You probably want to override this by providing your own StartTLS.responder.
+ """
+ return {}
+ StartTLS.responder(_defaultStartTLSResponder)
+
+
+
+class AMP(BinaryBoxProtocol, BoxDispatcher,
+ CommandLocator, SimpleStringLocator):
+ """
+ This protocol is an AMP connection. See the module docstring for protocol
+ details.
+ """
+
+ _ampInitialized = False
+
+ def __init__(self, boxReceiver=None, locator=None):
+ # For backwards compatibility. When AMP did not separate parsing logic
+ # (L{BinaryBoxProtocol}), request-response logic (L{BoxDispatcher}) and
+ # command routing (L{CommandLocator}), it did not have a constructor.
+ # Now it does, so old subclasses might have defined their own that did
+ # not upcall. If this flag isn't set, we'll call the constructor in
+ # makeConnection before anything actually happens.
+ self._ampInitialized = True
+ if boxReceiver is None:
+ boxReceiver = self
+ if locator is None:
+ locator = self
+ BoxDispatcher.__init__(self, locator)
+ BinaryBoxProtocol.__init__(self, boxReceiver)
+
+
+ def locateResponder(self, name):
+ """
+ Unify the implementations of L{CommandLocator} and
+ L{SimpleStringLocator} to perform both kinds of dispatch, preferring
+ L{CommandLocator}.
+ """
+ firstResponder = CommandLocator.locateResponder(self, name)
+ if firstResponder is not None:
+ return firstResponder
+ secondResponder = SimpleStringLocator.locateResponder(self, name)
+ return secondResponder
+
+
+ def __repr__(self):
+ """
+ A verbose string representation which gives us information about this
+ AMP connection.
+ """
+ if self.innerProtocol is not None:
+ innerRepr = ' inner %r' % (self.innerProtocol,)
+ else:
+ innerRepr = ''
+ return '<%s%s at 0x%x>' % (
+ self.__class__.__name__, innerRepr, unsignedID(self))
+
+
+ def makeConnection(self, transport):
+ """
+ Emit a helpful log message when the connection is made.
+ """
+ if not self._ampInitialized:
+ # See comment in the constructor re: backward compatibility. I
+ # should probably emit a deprecation warning here.
+ AMP.__init__(self)
+ # Save these so we can emit a similar log message in L{connectionLost}.
+ self._transportPeer = transport.getPeer()
+ self._transportHost = transport.getHost()
+ log.msg("%s connection established (HOST:%s PEER:%s)" % (
+ self.__class__.__name__,
+ self._transportHost,
+ self._transportPeer))
+ BinaryBoxProtocol.makeConnection(self, transport)
+
+
+ def connectionLost(self, reason):
+ """
+ Emit a helpful log message when the connection is lost.
+ """
+ log.msg("%s connection lost (HOST:%s PEER:%s)" %
+ (self.__class__.__name__,
+ self._transportHost,
+ self._transportPeer))
+ BinaryBoxProtocol.connectionLost(self, reason)
+ self.transport = None
+
+
+
+class _ParserHelper:
+ """
+ A box receiver which records all boxes received.
+ """
+ def __init__(self):
+ self.boxes = []
+
+
+ def getPeer(self):
+ return 'string'
+
+
+ def getHost(self):
+ return 'string'
+
+ disconnecting = False
+
+
+ def startReceivingBoxes(self, sender):
+ """
+ No initialization is required.
+ """
+
+
+ def ampBoxReceived(self, box):
+ self.boxes.append(box)
+
+
+ # Synchronous helpers
+ def parse(cls, fileObj):
+ """
+ Parse some amp data stored in a file.
+
+ @param fileObj: a file-like object.
+
+ @return: a list of AmpBoxes encoded in the given file.
+ """
+ parserHelper = cls()
+ bbp = BinaryBoxProtocol(boxReceiver=parserHelper)
+ bbp.makeConnection(parserHelper)
+ bbp.dataReceived(fileObj.read())
+ return parserHelper.boxes
+ parse = classmethod(parse)
+
+
+ def parseString(cls, data):
+ """
+ Parse some amp data stored in a string.
+
+ @param data: a str holding some amp-encoded data.
+
+ @return: a list of AmpBoxes encoded in the given string.
+ """
+ return cls.parse(StringIO(data))
+ parseString = classmethod(parseString)
+
+
+
+parse = _ParserHelper.parse
+parseString = _ParserHelper.parseString
+
+def _stringsToObjects(strings, arglist, proto):
+ """
+ Convert an AmpBox to a dictionary of python objects, converting through a
+ given arglist.
+
+ @param strings: an AmpBox (or dict of strings)
+
+ @param arglist: a list of 2-tuples of strings and Argument objects, as
+ described in L{Command.arguments}.
+
+ @param proto: an L{AMP} instance.
+
+ @return: the converted dictionary mapping names to argument objects.
+ """
+ objects = {}
+ myStrings = strings.copy()
+ for argname, argparser in arglist:
+ argparser.fromBox(argname, myStrings, objects, proto)
+ return objects
+
+
+
+def _objectsToStrings(objects, arglist, strings, proto):
+ """
+ Convert a dictionary of python objects to an AmpBox, converting through a
+ given arglist.
+
+ @param objects: a dict mapping names to python objects
+
+ @param arglist: a list of 2-tuples of strings and Argument objects, as
+ described in L{Command.arguments}.
+
+ @param strings: [OUT PARAMETER] An object providing the L{dict}
+ interface which will be populated with serialized data.
+
+ @param proto: an L{AMP} instance.
+
+ @return: The converted dictionary mapping names to encoded argument
+ strings (identical to C{strings}).
+ """
+ myObjects = {}
+ for (k, v) in objects.items():
+ myObjects[k] = v
+
+ for argname, argparser in arglist:
+ argparser.toBox(argname, strings, myObjects, proto)
+ return strings
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/basic.py b/vendor/Twisted-10.0.0/twisted/protocols/basic.py
new file mode 100644
index 0000000000..9e4afdb993
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/basic.py
@@ -0,0 +1,519 @@
+# -*- test-case-name: twisted.test.test_protocols -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Basic protocols, such as line-oriented, netstring, and int prefixed strings.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+# System imports
+import re
+import struct
+import warnings
+
+from zope.interface import implements
+
+# Twisted imports
+from twisted.internet import protocol, defer, interfaces, error
+from twisted.python import log
+
+LENGTH, DATA, COMMA = range(3)
+NUMBER = re.compile('(\d*)(:?)')
+DEBUG = 0
+
+class NetstringParseError(ValueError):
+ """The incoming data is not in valid Netstring format."""
+ pass
+
+
+class NetstringReceiver(protocol.Protocol):
+ """This uses djb's Netstrings protocol to break up the input into strings.
+
+ Each string makes a callback to stringReceived, with a single
+ argument of that string.
+
+ Security features:
+ 1. Messages are limited in size, useful if you don't want someone
+ sending you a 500MB netstring (change MAX_LENGTH to the maximum
+ length you wish to accept).
+ 2. The connection is lost if an illegal message is received.
+ """
+
+ MAX_LENGTH = 99999
+ brokenPeer = 0
+ _readerState = LENGTH
+ _readerLength = 0
+
+ def stringReceived(self, line):
+ """
+ Override this.
+ """
+ raise NotImplementedError
+
+ def doData(self):
+ buffer,self.__data = self.__data[:int(self._readerLength)],self.__data[int(self._readerLength):]
+ self._readerLength = self._readerLength - len(buffer)
+ self.__buffer = self.__buffer + buffer
+ if self._readerLength != 0:
+ return
+ self.stringReceived(self.__buffer)
+ self._readerState = COMMA
+
+ def doComma(self):
+ self._readerState = LENGTH
+ if self.__data[0] != ',':
+ if DEBUG:
+ raise NetstringParseError(repr(self.__data))
+ else:
+ raise NetstringParseError
+ self.__data = self.__data[1:]
+
+
+ def doLength(self):
+ m = NUMBER.match(self.__data)
+ if not m.end():
+ if DEBUG:
+ raise NetstringParseError(repr(self.__data))
+ else:
+ raise NetstringParseError
+ self.__data = self.__data[m.end():]
+ if m.group(1):
+ try:
+ self._readerLength = self._readerLength * (10**len(m.group(1))) + long(m.group(1))
+ except OverflowError:
+ raise NetstringParseError, "netstring too long"
+ if self._readerLength > self.MAX_LENGTH:
+ raise NetstringParseError, "netstring too long"
+ if m.group(2):
+ self.__buffer = ''
+ self._readerState = DATA
+
+ def dataReceived(self, data):
+ self.__data = data
+ try:
+ while self.__data:
+ if self._readerState == DATA:
+ self.doData()
+ elif self._readerState == COMMA:
+ self.doComma()
+ elif self._readerState == LENGTH:
+ self.doLength()
+ else:
+ raise RuntimeError, "mode is not DATA, COMMA or LENGTH"
+ except NetstringParseError:
+ self.transport.loseConnection()
+ self.brokenPeer = 1
+
+ def sendString(self, data):
+ """
+ A method for sending a Netstring. This method accepts a string and
+ writes it to the transport.
+
+ @type data: C{str}
+ """
+ if not isinstance(data, str):
+ warnings.warn(
+ "data passed to sendString() must be a string. Non-string "
+ "support is deprecated since Twisted 10.0",
+ DeprecationWarning, 2)
+ data = str(data)
+ self.transport.write('%d:%s,' % (len(data), data))
+
+
+class SafeNetstringReceiver(NetstringReceiver):
+ """This class is deprecated, use NetstringReceiver instead.
+ """
+
+
+class LineOnlyReceiver(protocol.Protocol):
+ """A protocol that receives only lines.
+
+ This is purely a speed optimisation over LineReceiver, for the
+ cases that raw mode is known to be unnecessary.
+
+ @cvar delimiter: The line-ending delimiter to use. By default this is
+ '\\r\\n'.
+ @cvar MAX_LENGTH: The maximum length of a line to allow (If a
+ sent line is longer than this, the connection is dropped).
+ Default is 16384.
+ """
+ _buffer = ''
+ delimiter = '\r\n'
+ MAX_LENGTH = 16384
+
+ def dataReceived(self, data):
+ """Translates bytes into lines, and calls lineReceived."""
+ lines = (self._buffer+data).split(self.delimiter)
+ self._buffer = lines.pop(-1)
+ for line in lines:
+ if self.transport.disconnecting:
+ # this is necessary because the transport may be told to lose
+ # the connection by a line within a larger packet, and it is
+ # important to disregard all the lines in that packet following
+ # the one that told it to close.
+ return
+ if len(line) > self.MAX_LENGTH:
+ return self.lineLengthExceeded(line)
+ else:
+ self.lineReceived(line)
+ if len(self._buffer) > self.MAX_LENGTH:
+ return self.lineLengthExceeded(self._buffer)
+
+ def lineReceived(self, line):
+ """Override this for when each line is received.
+ """
+ raise NotImplementedError
+
+ def sendLine(self, line):
+ """Sends a line to the other end of the connection.
+ """
+ return self.transport.writeSequence((line,self.delimiter))
+
+ def lineLengthExceeded(self, line):
+ """Called when the maximum line length has been reached.
+ Override if it needs to be dealt with in some special way.
+ """
+ return error.ConnectionLost('Line length exceeded')
+
+
+class _PauseableMixin:
+ paused = False
+
+ def pauseProducing(self):
+ self.paused = True
+ self.transport.pauseProducing()
+
+ def resumeProducing(self):
+ self.paused = False
+ self.transport.resumeProducing()
+ self.dataReceived('')
+
+ def stopProducing(self):
+ self.paused = True
+ self.transport.stopProducing()
+
+
+class LineReceiver(protocol.Protocol, _PauseableMixin):
+ """A protocol that receives lines and/or raw data, depending on mode.
+
+ In line mode, each line that's received becomes a callback to
+ L{lineReceived}. In raw data mode, each chunk of raw data becomes a
+ callback to L{rawDataReceived}. The L{setLineMode} and L{setRawMode}
+ methods switch between the two modes.
+
+ This is useful for line-oriented protocols such as IRC, HTTP, POP, etc.
+
+ @cvar delimiter: The line-ending delimiter to use. By default this is
+ '\\r\\n'.
+ @cvar MAX_LENGTH: The maximum length of a line to allow (If a
+ sent line is longer than this, the connection is dropped).
+ Default is 16384.
+ """
+ line_mode = 1
+ __buffer = ''
+ delimiter = '\r\n'
+ MAX_LENGTH = 16384
+
+ def clearLineBuffer(self):
+ """
+ Clear buffered data.
+
+ @return: All of the cleared buffered data.
+ @rtype: C{str}
+ """
+ b = self.__buffer
+ self.__buffer = ""
+ return b
+
+ def dataReceived(self, data):
+ """Protocol.dataReceived.
+ Translates bytes into lines, and calls lineReceived (or
+ rawDataReceived, depending on mode.)
+ """
+ self.__buffer = self.__buffer+data
+ while self.line_mode and not self.paused:
+ try:
+ line, self.__buffer = self.__buffer.split(self.delimiter, 1)
+ except ValueError:
+ if len(self.__buffer) > self.MAX_LENGTH:
+ line, self.__buffer = self.__buffer, ''
+ return self.lineLengthExceeded(line)
+ break
+ else:
+ linelength = len(line)
+ if linelength > self.MAX_LENGTH:
+ exceeded = line + self.__buffer
+ self.__buffer = ''
+ return self.lineLengthExceeded(exceeded)
+ why = self.lineReceived(line)
+ if why or self.transport and self.transport.disconnecting:
+ return why
+ else:
+ if not self.paused:
+ data=self.__buffer
+ self.__buffer=''
+ if data:
+ return self.rawDataReceived(data)
+
+ def setLineMode(self, extra=''):
+ """Sets the line-mode of this receiver.
+
+ If you are calling this from a rawDataReceived callback,
+ you can pass in extra unhandled data, and that data will
+ be parsed for lines. Further data received will be sent
+ to lineReceived rather than rawDataReceived.
+
+ Do not pass extra data if calling this function from
+ within a lineReceived callback.
+ """
+ self.line_mode = 1
+ if extra:
+ return self.dataReceived(extra)
+
+ def setRawMode(self):
+ """Sets the raw mode of this receiver.
+ Further data received will be sent to rawDataReceived rather
+ than lineReceived.
+ """
+ self.line_mode = 0
+
+ def rawDataReceived(self, data):
+ """Override this for when raw data is received.
+ """
+ raise NotImplementedError
+
+ def lineReceived(self, line):
+ """Override this for when each line is received.
+ """
+ raise NotImplementedError
+
+ def sendLine(self, line):
+ """Sends a line to the other end of the connection.
+ """
+ return self.transport.write(line + self.delimiter)
+
+ def lineLengthExceeded(self, line):
+ """Called when the maximum line length has been reached.
+ Override if it needs to be dealt with in some special way.
+
+ The argument 'line' contains the remainder of the buffer, starting
+ with (at least some part) of the line which is too long. This may
+ be more than one line, or may be only the initial portion of the
+ line.
+ """
+ return self.transport.loseConnection()
+
+
+class StringTooLongError(AssertionError):
+ """
+ Raised when trying to send a string too long for a length prefixed
+ protocol.
+ """
+
+
+class IntNStringReceiver(protocol.Protocol, _PauseableMixin):
+ """
+ Generic class for length prefixed protocols.
+
+ @ivar recvd: buffer holding received data when splitted.
+ @type recvd: C{str}
+
+ @ivar structFormat: format used for struct packing/unpacking. Define it in
+ subclass.
+ @type structFormat: C{str}
+
+ @ivar prefixLength: length of the prefix, in bytes. Define it in subclass,
+ using C{struct.calcsize(structFormat)}
+ @type prefixLength: C{int}
+ """
+ MAX_LENGTH = 99999
+ recvd = ""
+
+ def stringReceived(self, msg):
+ """
+ Override this.
+ """
+ raise NotImplementedError
+
+
+ def lengthLimitExceeded(self, length):
+ """
+ Callback invoked when a length prefix greater than C{MAX_LENGTH} is
+ received. The default implementation disconnects the transport.
+ Override this.
+
+ @param length: The length prefix which was received.
+ @type length: C{int}
+ """
+ self.transport.loseConnection()
+
+
+ def dataReceived(self, recd):
+ """
+ Convert int prefixed strings into calls to stringReceived.
+ """
+ self.recvd = self.recvd + recd
+ while len(self.recvd) >= self.prefixLength and not self.paused:
+ length ,= struct.unpack(
+ self.structFormat, self.recvd[:self.prefixLength])
+ if length > self.MAX_LENGTH:
+ self.lengthLimitExceeded(length)
+ return
+ if len(self.recvd) < length + self.prefixLength:
+ break
+ packet = self.recvd[self.prefixLength:length + self.prefixLength]
+ self.recvd = self.recvd[length + self.prefixLength:]
+ self.stringReceived(packet)
+
+ def sendString(self, data):
+ """
+ Send an prefixed string to the other end of the connection.
+
+ @type data: C{str}
+ """
+ if len(data) >= 2 ** (8 * self.prefixLength):
+ raise StringTooLongError(
+ "Try to send %s bytes whereas maximum is %s" % (
+ len(data), 2 ** (8 * self.prefixLength)))
+ self.transport.write(struct.pack(self.structFormat, len(data)) + data)
+
+
+class Int32StringReceiver(IntNStringReceiver):
+ """
+ A receiver for int32-prefixed strings.
+
+ An int32 string is a string prefixed by 4 bytes, the 32-bit length of
+ the string encoded in network byte order.
+
+ This class publishes the same interface as NetstringReceiver.
+ """
+ structFormat = "!I"
+ prefixLength = struct.calcsize(structFormat)
+
+
+class Int16StringReceiver(IntNStringReceiver):
+ """
+ A receiver for int16-prefixed strings.
+
+ An int16 string is a string prefixed by 2 bytes, the 16-bit length of
+ the string encoded in network byte order.
+
+ This class publishes the same interface as NetstringReceiver.
+ """
+ structFormat = "!H"
+ prefixLength = struct.calcsize(structFormat)
+
+
+class Int8StringReceiver(IntNStringReceiver):
+ """
+ A receiver for int8-prefixed strings.
+
+ An int8 string is a string prefixed by 1 byte, the 8-bit length of
+ the string.
+
+ This class publishes the same interface as NetstringReceiver.
+ """
+ structFormat = "!B"
+ prefixLength = struct.calcsize(structFormat)
+
+
+class StatefulStringProtocol:
+ """
+ A stateful string protocol.
+
+ This is a mixin for string protocols (Int32StringReceiver,
+ NetstringReceiver) which translates stringReceived into a callback
+ (prefixed with 'proto_') depending on state.
+
+ The state 'done' is special; if a proto_* method returns it, the
+ connection will be closed immediately.
+ """
+
+ state = 'init'
+
+ def stringReceived(self,string):
+ """Choose a protocol phase function and call it.
+
+ Call back to the appropriate protocol phase; this begins with
+ the function proto_init and moves on to proto_* depending on
+ what each proto_* function returns. (For example, if
+ self.proto_init returns 'foo', then self.proto_foo will be the
+ next function called when a protocol message is received.
+ """
+ try:
+ pto = 'proto_'+self.state
+ statehandler = getattr(self,pto)
+ except AttributeError:
+ log.msg('callback',self.state,'not found')
+ else:
+ self.state = statehandler(string)
+ if self.state == 'done':
+ self.transport.loseConnection()
+
+class FileSender:
+ """A producer that sends the contents of a file to a consumer.
+
+ This is a helper for protocols that, at some point, will take a
+ file-like object, read its contents, and write them out to the network,
+ optionally performing some transformation on the bytes in between.
+ """
+ implements(interfaces.IProducer)
+
+ CHUNK_SIZE = 2 ** 14
+
+ lastSent = ''
+ deferred = None
+
+ def beginFileTransfer(self, file, consumer, transform = None):
+ """Begin transferring a file
+
+ @type file: Any file-like object
+ @param file: The file object to read data from
+
+ @type consumer: Any implementor of IConsumer
+ @param consumer: The object to write data to
+
+ @param transform: A callable taking one string argument and returning
+ the same. All bytes read from the file are passed through this before
+ being written to the consumer.
+
+ @rtype: C{Deferred}
+ @return: A deferred whose callback will be invoked when the file has been
+ completely written to the consumer. The last byte written to the consumer
+ is passed to the callback.
+ """
+ self.file = file
+ self.consumer = consumer
+ self.transform = transform
+
+ self.deferred = deferred = defer.Deferred()
+ self.consumer.registerProducer(self, False)
+ return deferred
+
+ def resumeProducing(self):
+ chunk = ''
+ if self.file:
+ chunk = self.file.read(self.CHUNK_SIZE)
+ if not chunk:
+ self.file = None
+ self.consumer.unregisterProducer()
+ if self.deferred:
+ self.deferred.callback(self.lastSent)
+ self.deferred = None
+ return
+
+ if self.transform:
+ chunk = self.transform(chunk)
+ self.consumer.write(chunk)
+ self.lastSent = chunk[-1]
+
+ def pauseProducing(self):
+ pass
+
+ def stopProducing(self):
+ if self.deferred:
+ self.deferred.errback(Exception("Consumer asked us to stop producing"))
+ self.deferred = None
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/dict.py b/vendor/Twisted-10.0.0/twisted/protocols/dict.py
new file mode 100644
index 0000000000..7733874c99
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/dict.py
@@ -0,0 +1,362 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Dict client protocol implementation.
+
+@author: Pavel Pergamenshchik
+"""
+
+from twisted.protocols import basic
+from twisted.internet import defer, protocol
+from twisted.python import log
+from StringIO import StringIO
+
+def parseParam(line):
+ """Chew one dqstring or atom from beginning of line and return (param, remaningline)"""
+ if line == '':
+ return (None, '')
+ elif line[0] != '"': # atom
+ mode = 1
+ else: # dqstring
+ mode = 2
+ res = ""
+ io = StringIO(line)
+ if mode == 2: # skip the opening quote
+ io.read(1)
+ while 1:
+ a = io.read(1)
+ if a == '"':
+ if mode == 2:
+ io.read(1) # skip the separating space
+ return (res, io.read())
+ elif a == '\\':
+ a = io.read(1)
+ if a == '':
+ return (None, line) # unexpected end of string
+ elif a == '':
+ if mode == 1:
+ return (res, io.read())
+ else:
+ return (None, line) # unexpected end of string
+ elif a == ' ':
+ if mode == 1:
+ return (res, io.read())
+ res += a
+
+def makeAtom(line):
+ """Munch a string into an 'atom'"""
+ # FIXME: proper quoting
+ return filter(lambda x: not (x in map(chr, range(33)+[34, 39, 92])), line)
+
+def makeWord(s):
+ mustquote = range(33)+[34, 39, 92]
+ result = []
+ for c in s:
+ if ord(c) in mustquote:
+ result.append("\\")
+ result.append(c)
+ s = "".join(result)
+ return s
+
+def parseText(line):
+ if len(line) == 1 and line == '.':
+ return None
+ else:
+ if len(line) > 1 and line[0:2] == '..':
+ line = line[1:]
+ return line
+
+class Definition:
+ """A word definition"""
+ def __init__(self, name, db, dbdesc, text):
+ self.name = name
+ self.db = db
+ self.dbdesc = dbdesc
+ self.text = text # list of strings not terminated by newline
+
+class DictClient(basic.LineReceiver):
+ """dict (RFC2229) client"""
+
+ data = None # multiline data
+ MAX_LENGTH = 1024
+ state = None
+ mode = None
+ result = None
+ factory = None
+
+ def __init__(self):
+ self.data = None
+ self.result = None
+
+ def connectionMade(self):
+ self.state = "conn"
+ self.mode = "command"
+
+ def sendLine(self, line):
+ """Throw up if the line is longer than 1022 characters"""
+ if len(line) > self.MAX_LENGTH - 2:
+ raise ValueError("DictClient tried to send a too long line")
+ basic.LineReceiver.sendLine(self, line)
+
+ def lineReceived(self, line):
+ try:
+ line = line.decode("UTF-8")
+ except UnicodeError: # garbage received, skip
+ return
+ if self.mode == "text": # we are receiving textual data
+ code = "text"
+ else:
+ if len(line) < 4:
+ log.msg("DictClient got invalid line from server -- %s" % line)
+ self.protocolError("Invalid line from server")
+ self.transport.LoseConnection()
+ return
+ code = int(line[:3])
+ line = line[4:]
+ method = getattr(self, 'dictCode_%s_%s' % (code, self.state), self.dictCode_default)
+ method(line)
+
+ def dictCode_default(self, line):
+ """Unkown message"""
+ log.msg("DictClient got unexpected message from server -- %s" % line)
+ self.protocolError("Unexpected server message")
+ self.transport.loseConnection()
+
+ def dictCode_221_ready(self, line):
+ """We are about to get kicked off, do nothing"""
+ pass
+
+ def dictCode_220_conn(self, line):
+ """Greeting message"""
+ self.state = "ready"
+ self.dictConnected()
+
+ def dictCode_530_conn(self):
+ self.protocolError("Access denied")
+ self.transport.loseConnection()
+
+ def dictCode_420_conn(self):
+ self.protocolError("Server temporarily unavailable")
+ self.transport.loseConnection()
+
+ def dictCode_421_conn(self):
+ self.protocolError("Server shutting down at operator request")
+ self.transport.loseConnection()
+
+ def sendDefine(self, database, word):
+ """Send a dict DEFINE command"""
+ assert self.state == "ready", "DictClient.sendDefine called when not in ready state"
+ self.result = None # these two are just in case. In "ready" state, result and data
+ self.data = None # should be None
+ self.state = "define"
+ command = "DEFINE %s %s" % (makeAtom(database.encode("UTF-8")), makeWord(word.encode("UTF-8")))
+ self.sendLine(command)
+
+ def sendMatch(self, database, strategy, word):
+ """Send a dict MATCH command"""
+ assert self.state == "ready", "DictClient.sendMatch called when not in ready state"
+ self.result = None
+ self.data = None
+ self.state = "match"
+ command = "MATCH %s %s %s" % (makeAtom(database), makeAtom(strategy), makeAtom(word))
+ self.sendLine(command.encode("UTF-8"))
+
+ def dictCode_550_define(self, line):
+ """Invalid database"""
+ self.mode = "ready"
+ self.defineFailed("Invalid database")
+
+ def dictCode_550_match(self, line):
+ """Invalid database"""
+ self.mode = "ready"
+ self.matchFailed("Invalid database")
+
+ def dictCode_551_match(self, line):
+ """Invalid strategy"""
+ self.mode = "ready"
+ self.matchFailed("Invalid strategy")
+
+ def dictCode_552_define(self, line):
+ """No match"""
+ self.mode = "ready"
+ self.defineFailed("No match")
+
+ def dictCode_552_match(self, line):
+ """No match"""
+ self.mode = "ready"
+ self.matchFailed("No match")
+
+ def dictCode_150_define(self, line):
+ """n definitions retrieved"""
+ self.result = []
+
+ def dictCode_151_define(self, line):
+ """Definition text follows"""
+ self.mode = "text"
+ (word, line) = parseParam(line)
+ (db, line) = parseParam(line)
+ (dbdesc, line) = parseParam(line)
+ if not (word and db and dbdesc):
+ self.protocolError("Invalid server response")
+ self.transport.loseConnection()
+ else:
+ self.result.append(Definition(word, db, dbdesc, []))
+ self.data = []
+
+ def dictCode_152_match(self, line):
+ """n matches found, text follows"""
+ self.mode = "text"
+ self.result = []
+ self.data = []
+
+ def dictCode_text_define(self, line):
+ """A line of definition text received"""
+ res = parseText(line)
+ if res == None:
+ self.mode = "command"
+ self.result[-1].text = self.data
+ self.data = None
+ else:
+ self.data.append(line)
+
+ def dictCode_text_match(self, line):
+ """One line of match text received"""
+ def l(s):
+ p1, t = parseParam(s)
+ p2, t = parseParam(t)
+ return (p1, p2)
+ res = parseText(line)
+ if res == None:
+ self.mode = "command"
+ self.result = map(l, self.data)
+ self.data = None
+ else:
+ self.data.append(line)
+
+ def dictCode_250_define(self, line):
+ """ok"""
+ t = self.result
+ self.result = None
+ self.state = "ready"
+ self.defineDone(t)
+
+ def dictCode_250_match(self, line):
+ """ok"""
+ t = self.result
+ self.result = None
+ self.state = "ready"
+ self.matchDone(t)
+
+ def protocolError(self, reason):
+ """override to catch unexpected dict protocol conditions"""
+ pass
+
+ def dictConnected(self):
+ """override to be notified when the server is ready to accept commands"""
+ pass
+
+ def defineFailed(self, reason):
+ """override to catch reasonable failure responses to DEFINE"""
+ pass
+
+ def defineDone(self, result):
+ """override to catch succesful DEFINE"""
+ pass
+
+ def matchFailed(self, reason):
+ """override to catch resonable failure responses to MATCH"""
+ pass
+
+ def matchDone(self, result):
+ """override to catch succesful MATCH"""
+ pass
+
+
+class InvalidResponse(Exception):
+ pass
+
+
+class DictLookup(DictClient):
+ """Utility class for a single dict transaction. To be used with DictLookupFactory"""
+
+ def protocolError(self, reason):
+ if not self.factory.done:
+ self.factory.d.errback(InvalidResponse(reason))
+ self.factory.clientDone()
+
+ def dictConnected(self):
+ if self.factory.queryType == "define":
+ apply(self.sendDefine, self.factory.param)
+ elif self.factory.queryType == "match":
+ apply(self.sendMatch, self.factory.param)
+
+ def defineFailed(self, reason):
+ self.factory.d.callback([])
+ self.factory.clientDone()
+ self.transport.loseConnection()
+
+ def defineDone(self, result):
+ self.factory.d.callback(result)
+ self.factory.clientDone()
+ self.transport.loseConnection()
+
+ def matchFailed(self, reason):
+ self.factory.d.callback([])
+ self.factory.clientDone()
+ self.transport.loseConnection()
+
+ def matchDone(self, result):
+ self.factory.d.callback(result)
+ self.factory.clientDone()
+ self.transport.loseConnection()
+
+
+class DictLookupFactory(protocol.ClientFactory):
+ """Utility factory for a single dict transaction"""
+ protocol = DictLookup
+ done = None
+
+ def __init__(self, queryType, param, d):
+ self.queryType = queryType
+ self.param = param
+ self.d = d
+ self.done = 0
+
+ def clientDone(self):
+ """Called by client when done."""
+ self.done = 1
+ del self.d
+
+ def clientConnectionFailed(self, connector, error):
+ self.d.errback(error)
+
+ def clientConnectionLost(self, connector, error):
+ if not self.done:
+ self.d.errback(error)
+
+ def buildProtocol(self, addr):
+ p = self.protocol()
+ p.factory = self
+ return p
+
+
+def define(host, port, database, word):
+ """Look up a word using a dict server"""
+ d = defer.Deferred()
+ factory = DictLookupFactory("define", (database, word), d)
+
+ from twisted.internet import reactor
+ reactor.connectTCP(host, port, factory)
+ return d
+
+def match(host, port, database, strategy, word):
+ """Match a word using a dict server"""
+ d = defer.Deferred()
+ factory = DictLookupFactory("match", (database, strategy, word), d)
+
+ from twisted.internet import reactor
+ reactor.connectTCP(host, port, factory)
+ return d
+
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/finger.py b/vendor/Twisted-10.0.0/twisted/protocols/finger.py
new file mode 100644
index 0000000000..409b82c5a3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/finger.py
@@ -0,0 +1,43 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""The Finger User Information Protocol (RFC 1288)"""
+
+from twisted.protocols import basic
+import string
+
+class Finger(basic.LineReceiver):
+
+ def lineReceived(self, line):
+ parts = string.split(line)
+ if not parts:
+ parts = ['']
+ if len(parts) == 1:
+ slash_w = 0
+ else:
+ slash_w = 1
+ user = parts[-1]
+ if '@' in user:
+ host_place = string.rfind(user, '@')
+ user = user[:host_place]
+ host = user[host_place+1:]
+ return self.forwardQuery(slash_w, user, host)
+ if user:
+ return self.getUser(slash_w, user)
+ else:
+ return self.getDomain(slash_w)
+
+ def _refuseMessage(self, message):
+ self.transport.write(message+"\n")
+ self.transport.loseConnection()
+
+ def forwardQuery(self, slash_w, user, host):
+ self._refuseMessage('Finger forwarding service denied')
+
+ def getDomain(self, slash_w):
+ self._refuseMessage('Finger online list denied')
+
+ def getUser(self, slash_w, user):
+ self.transport.write('Login: '+user+'\n')
+ self._refuseMessage('No such user')
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/ftp.py b/vendor/Twisted-10.0.0/twisted/protocols/ftp.py
new file mode 100644
index 0000000000..d94b630174
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/ftp.py
@@ -0,0 +1,2814 @@
+# -*- test-case-name: twisted.test.test_ftp -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An FTP protocol implementation
+
+@author: Itamar Shtull-Trauring
+@author: Jp Calderone
+@author: Andrew Bennetts
+"""
+
+# System Imports
+import os
+import time
+import re
+import operator
+import stat
+import errno
+import fnmatch
+import warnings
+
+try:
+ import pwd, grp
+except ImportError:
+ pwd = grp = None
+
+from zope.interface import Interface, implements
+
+# Twisted Imports
+from twisted import copyright
+from twisted.internet import reactor, interfaces, protocol, error, defer
+from twisted.protocols import basic, policies
+
+from twisted.python import log, failure, filepath
+from twisted.python.compat import reduce
+
+from twisted.cred import error as cred_error, portal, credentials, checkers
+
+# constants
+# response codes
+
+RESTART_MARKER_REPLY = "100"
+SERVICE_READY_IN_N_MINUTES = "120"
+DATA_CNX_ALREADY_OPEN_START_XFR = "125"
+FILE_STATUS_OK_OPEN_DATA_CNX = "150"
+
+CMD_OK = "200.1"
+TYPE_SET_OK = "200.2"
+ENTERING_PORT_MODE = "200.3"
+CMD_NOT_IMPLMNTD_SUPERFLUOUS = "202"
+SYS_STATUS_OR_HELP_REPLY = "211"
+DIR_STATUS = "212"
+FILE_STATUS = "213"
+HELP_MSG = "214"
+NAME_SYS_TYPE = "215"
+SVC_READY_FOR_NEW_USER = "220.1"
+WELCOME_MSG = "220.2"
+SVC_CLOSING_CTRL_CNX = "221"
+GOODBYE_MSG = "221"
+DATA_CNX_OPEN_NO_XFR_IN_PROGRESS = "225"
+CLOSING_DATA_CNX = "226"
+TXFR_COMPLETE_OK = "226"
+ENTERING_PASV_MODE = "227"
+ENTERING_EPSV_MODE = "229"
+USR_LOGGED_IN_PROCEED = "230.1" # v1 of code 230
+GUEST_LOGGED_IN_PROCEED = "230.2" # v2 of code 230
+REQ_FILE_ACTN_COMPLETED_OK = "250"
+PWD_REPLY = "257.1"
+MKD_REPLY = "257.2"
+
+USR_NAME_OK_NEED_PASS = "331.1" # v1 of Code 331
+GUEST_NAME_OK_NEED_EMAIL = "331.2" # v2 of code 331
+NEED_ACCT_FOR_LOGIN = "332"
+REQ_FILE_ACTN_PENDING_FURTHER_INFO = "350"
+
+SVC_NOT_AVAIL_CLOSING_CTRL_CNX = "421.1"
+TOO_MANY_CONNECTIONS = "421.2"
+CANT_OPEN_DATA_CNX = "425"
+CNX_CLOSED_TXFR_ABORTED = "426"
+REQ_ACTN_ABRTD_FILE_UNAVAIL = "450"
+REQ_ACTN_ABRTD_LOCAL_ERR = "451"
+REQ_ACTN_ABRTD_INSUFF_STORAGE = "452"
+
+SYNTAX_ERR = "500"
+SYNTAX_ERR_IN_ARGS = "501"
+CMD_NOT_IMPLMNTD = "502"
+BAD_CMD_SEQ = "503"
+CMD_NOT_IMPLMNTD_FOR_PARAM = "504"
+NOT_LOGGED_IN = "530.1" # v1 of code 530 - please log in
+AUTH_FAILURE = "530.2" # v2 of code 530 - authorization failure
+NEED_ACCT_FOR_STOR = "532"
+FILE_NOT_FOUND = "550.1" # no such file or directory
+PERMISSION_DENIED = "550.2" # permission denied
+ANON_USER_DENIED = "550.3" # anonymous users can't alter filesystem
+IS_NOT_A_DIR = "550.4" # rmd called on a path that is not a directory
+REQ_ACTN_NOT_TAKEN = "550.5"
+FILE_EXISTS = "550.6"
+IS_A_DIR = "550.7"
+PAGE_TYPE_UNK = "551"
+EXCEEDED_STORAGE_ALLOC = "552"
+FILENAME_NOT_ALLOWED = "553"
+
+
+RESPONSE = {
+ # -- 100's --
+ RESTART_MARKER_REPLY: '110 MARK yyyy-mmmm', # TODO: this must be fixed
+ SERVICE_READY_IN_N_MINUTES: '120 service ready in %s minutes',
+ DATA_CNX_ALREADY_OPEN_START_XFR: '125 Data connection already open, starting transfer',
+ FILE_STATUS_OK_OPEN_DATA_CNX: '150 File status okay; about to open data connection.',
+
+ # -- 200's --
+ CMD_OK: '200 Command OK',
+ TYPE_SET_OK: '200 Type set to %s.',
+ ENTERING_PORT_MODE: '200 PORT OK',
+ CMD_NOT_IMPLMNTD_SUPERFLUOUS: '202 Command not implemented, superfluous at this site',
+ SYS_STATUS_OR_HELP_REPLY: '211 System status reply',
+ DIR_STATUS: '212 %s',
+ FILE_STATUS: '213 %s',
+ HELP_MSG: '214 help: %s',
+ NAME_SYS_TYPE: '215 UNIX Type: L8',
+ WELCOME_MSG: "220 %s",
+ SVC_READY_FOR_NEW_USER: '220 Service ready',
+ GOODBYE_MSG: '221 Goodbye.',
+ DATA_CNX_OPEN_NO_XFR_IN_PROGRESS: '225 data connection open, no transfer in progress',
+ CLOSING_DATA_CNX: '226 Abort successful',
+ TXFR_COMPLETE_OK: '226 Transfer Complete.',
+ ENTERING_PASV_MODE: '227 Entering Passive Mode (%s).',
+ ENTERING_EPSV_MODE: '229 Entering Extended Passive Mode (|||%s|).', # where is epsv defined in the rfc's?
+ USR_LOGGED_IN_PROCEED: '230 User logged in, proceed',
+ GUEST_LOGGED_IN_PROCEED: '230 Anonymous login ok, access restrictions apply.',
+ REQ_FILE_ACTN_COMPLETED_OK: '250 Requested File Action Completed OK', #i.e. CWD completed ok
+ PWD_REPLY: '257 "%s"',
+ MKD_REPLY: '257 "%s" created',
+
+ # -- 300's --
+ 'userotp': '331 Response to %s.', # ???
+ USR_NAME_OK_NEED_PASS: '331 Password required for %s.',
+ GUEST_NAME_OK_NEED_EMAIL: '331 Guest login ok, type your email address as password.',
+
+ REQ_FILE_ACTN_PENDING_FURTHER_INFO: '350 Requested file action pending further information.',
+
+# -- 400's --
+ SVC_NOT_AVAIL_CLOSING_CTRL_CNX: '421 Service not available, closing control connection.',
+ TOO_MANY_CONNECTIONS: '421 Too many users right now, try again in a few minutes.',
+ CANT_OPEN_DATA_CNX: "425 Can't open data connection.",
+ CNX_CLOSED_TXFR_ABORTED: '426 Transfer aborted. Data connection closed.',
+
+ REQ_ACTN_ABRTD_LOCAL_ERR: '451 Requested action aborted. Local error in processing.',
+
+
+ # -- 500's --
+ SYNTAX_ERR: "500 Syntax error: %s",
+ SYNTAX_ERR_IN_ARGS: '501 syntax error in argument(s) %s.',
+ CMD_NOT_IMPLMNTD: "502 Command '%s' not implemented",
+ BAD_CMD_SEQ: '503 Incorrect sequence of commands: %s',
+ CMD_NOT_IMPLMNTD_FOR_PARAM: "504 Not implemented for parameter '%s'.",
+ NOT_LOGGED_IN: '530 Please login with USER and PASS.',
+ AUTH_FAILURE: '530 Sorry, Authentication failed.',
+ NEED_ACCT_FOR_STOR: '532 Need an account for storing files',
+ FILE_NOT_FOUND: '550 %s: No such file or directory.',
+ PERMISSION_DENIED: '550 %s: Permission denied.',
+ ANON_USER_DENIED: '550 Anonymous users are forbidden to change the filesystem',
+ IS_NOT_A_DIR: '550 Cannot rmd, %s is not a directory',
+ FILE_EXISTS: '550 %s: File exists',
+ IS_A_DIR: '550 %s: is a directory',
+ REQ_ACTN_NOT_TAKEN: '550 Requested action not taken: %s',
+ EXCEEDED_STORAGE_ALLOC: '552 Requested file action aborted, exceeded file storage allocation',
+ FILENAME_NOT_ALLOWED: '553 Requested action not taken, file name not allowed'
+}
+
+
+
+class InvalidPath(Exception):
+ """
+ Internal exception used to signify an error during parsing a path.
+ """
+
+
+
+def toSegments(cwd, path):
+ """
+ Normalize a path, as represented by a list of strings each
+ representing one segment of the path.
+ """
+ if path.startswith('/'):
+ segs = []
+ else:
+ segs = cwd[:]
+
+ for s in path.split('/'):
+ if s == '.' or s == '':
+ continue
+ elif s == '..':
+ if segs:
+ segs.pop()
+ else:
+ raise InvalidPath(cwd, path)
+ elif '\0' in s or '/' in s:
+ raise InvalidPath(cwd, path)
+ else:
+ segs.append(s)
+ return segs
+
+
+def errnoToFailure(e, path):
+ """
+ Map C{OSError} and C{IOError} to standard FTP errors.
+ """
+ if e == errno.ENOENT:
+ return defer.fail(FileNotFoundError(path))
+ elif e == errno.EACCES or e == errno.EPERM:
+ return defer.fail(PermissionDeniedError(path))
+ elif e == errno.ENOTDIR:
+ return defer.fail(IsNotADirectoryError(path))
+ elif e == errno.EEXIST:
+ return defer.fail(FileExistsError(path))
+ elif e == errno.EISDIR:
+ return defer.fail(IsADirectoryError(path))
+ else:
+ return defer.fail()
+
+
+
+class FTPCmdError(Exception):
+ """
+ Generic exception for FTP commands.
+ """
+ def __init__(self, *msg):
+ Exception.__init__(self, *msg)
+ self.errorMessage = msg
+
+
+ def response(self):
+ """
+ Generate a FTP response message for this error.
+ """
+ return RESPONSE[self.errorCode] % self.errorMessage
+
+
+
+class FileNotFoundError(FTPCmdError):
+ """
+ Raised when trying to access a non existent file or directory.
+ """
+ errorCode = FILE_NOT_FOUND
+
+
+
+class AnonUserDeniedError(FTPCmdError):
+ """
+ Raised when an anonymous user issues a command that will alter the
+ filesystem
+ """
+ def __init__(self):
+ # No message
+ FTPCmdError.__init__(self, None)
+
+ errorCode = ANON_USER_DENIED
+
+
+
+class PermissionDeniedError(FTPCmdError):
+ """
+ Raised when access is attempted to a resource to which access is
+ not allowed.
+ """
+ errorCode = PERMISSION_DENIED
+
+
+
+class IsNotADirectoryError(FTPCmdError):
+ """
+ Raised when RMD is called on a path that isn't a directory.
+ """
+ errorCode = IS_NOT_A_DIR
+
+
+
+class FileExistsError(FTPCmdError):
+ """
+ Raised when attempted to override an existing resource.
+ """
+ errorCode = FILE_EXISTS
+
+
+
+class IsADirectoryError(FTPCmdError):
+ """
+ Raised when DELE is called on a path that is a directory.
+ """
+ errorCode = IS_A_DIR
+
+
+
+class CmdSyntaxError(FTPCmdError):
+ """
+ Raised when a command syntax is wrong.
+ """
+ errorCode = SYNTAX_ERR
+
+
+
+class CmdArgSyntaxError(FTPCmdError):
+ """
+ Raised when a command is called with wrong value or a wrong number of
+ arguments.
+ """
+ errorCode = SYNTAX_ERR_IN_ARGS
+
+
+
+class CmdNotImplementedError(FTPCmdError):
+ """
+ Raised when an unimplemented command is given to the server.
+ """
+ errorCode = CMD_NOT_IMPLMNTD
+
+
+
+class CmdNotImplementedForArgError(FTPCmdError):
+ """
+ Raised when the handling of a parameter for a command is not implemented by
+ the server.
+ """
+ errorCode = CMD_NOT_IMPLMNTD_FOR_PARAM
+
+
+
+class FTPError(Exception):
+ pass
+
+
+
+class PortConnectionError(Exception):
+ pass
+
+
+
+class BadCmdSequenceError(FTPCmdError):
+ """
+ Raised when a client sends a series of commands in an illogical sequence.
+ """
+ errorCode = BAD_CMD_SEQ
+
+
+
+class AuthorizationError(FTPCmdError):
+ """
+ Raised when client authentication fails.
+ """
+ errorCode = AUTH_FAILURE
+
+
+
+def debugDeferred(self, *_):
+ log.msg('debugDeferred(): %s' % str(_), debug=True)
+
+
+# -- DTP Protocol --
+
+
+_months = [
+ None,
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+
+class DTP(object, protocol.Protocol):
+ implements(interfaces.IConsumer)
+
+ isConnected = False
+
+ _cons = None
+ _onConnLost = None
+ _buffer = None
+
+ def connectionMade(self):
+ self.isConnected = True
+ self.factory.deferred.callback(None)
+ self._buffer = []
+
+ def connectionLost(self, reason):
+ self.isConnected = False
+ if self._onConnLost is not None:
+ self._onConnLost.callback(None)
+
+ def sendLine(self, line):
+ self.transport.write(line + '\r\n')
+
+
+ def _formatOneListResponse(self, name, size, directory, permissions, hardlinks, modified, owner, group):
+ def formatMode(mode):
+ return ''.join([mode & (256 >> n) and 'rwx'[n % 3] or '-' for n in range(9)])
+
+ def formatDate(mtime):
+ now = time.gmtime()
+ info = {
+ 'month': _months[mtime.tm_mon],
+ 'day': mtime.tm_mday,
+ 'year': mtime.tm_year,
+ 'hour': mtime.tm_hour,
+ 'minute': mtime.tm_min
+ }
+ if now.tm_year != mtime.tm_year:
+ return '%(month)s %(day)02d %(year)5d' % info
+ else:
+ return '%(month)s %(day)02d %(hour)02d:%(minute)02d' % info
+
+ format = ('%(directory)s%(permissions)s%(hardlinks)4d '
+ '%(owner)-9s %(group)-9s %(size)15d %(date)12s '
+ '%(name)s')
+
+ return format % {
+ 'directory': directory and 'd' or '-',
+ 'permissions': formatMode(permissions),
+ 'hardlinks': hardlinks,
+ 'owner': owner[:8],
+ 'group': group[:8],
+ 'size': size,
+ 'date': formatDate(time.gmtime(modified)),
+ 'name': name}
+
+ def sendListResponse(self, name, response):
+ self.sendLine(self._formatOneListResponse(name, *response))
+
+
+ # Proxy IConsumer to our transport
+ def registerProducer(self, producer, streaming):
+ return self.transport.registerProducer(producer, streaming)
+
+ def unregisterProducer(self):
+ self.transport.unregisterProducer()
+ self.transport.loseConnection()
+
+ def write(self, data):
+ if self.isConnected:
+ return self.transport.write(data)
+ raise Exception("Crap damn crap damn crap damn")
+
+
+ # Pretend to be a producer, too.
+ def _conswrite(self, bytes):
+ try:
+ self._cons.write(bytes)
+ except:
+ self._onConnLost.errback()
+
+ def dataReceived(self, bytes):
+ if self._cons is not None:
+ self._conswrite(bytes)
+ else:
+ self._buffer.append(bytes)
+
+ def _unregConsumer(self, ignored):
+ self._cons.unregisterProducer()
+ self._cons = None
+ del self._onConnLost
+ return ignored
+
+ def registerConsumer(self, cons):
+ assert self._cons is None
+ self._cons = cons
+ self._cons.registerProducer(self, True)
+ for chunk in self._buffer:
+ self._conswrite(chunk)
+ self._buffer = None
+ if self.isConnected:
+ self._onConnLost = d = defer.Deferred()
+ d.addBoth(self._unregConsumer)
+ return d
+ else:
+ self._cons.unregisterProducer()
+ self._cons = None
+ return defer.succeed(None)
+
+ def resumeProducing(self):
+ self.transport.resumeProducing()
+
+ def pauseProducing(self):
+ self.transport.pauseProducing()
+
+ def stopProducing(self):
+ self.transport.stopProducing()
+
+class DTPFactory(protocol.ClientFactory):
+ """
+ Client factory for I{data transfer process} protocols.
+
+ @ivar peerCheck: perform checks to make sure the ftp-pi's peer is the same
+ as the dtp's
+ @ivar pi: a reference to this factory's protocol interpreter
+
+ @ivar _state: Indicates the current state of the DTPFactory. Initially,
+ this is L{_IN_PROGRESS}. If the connection fails or times out, it is
+ L{_FAILED}. If the connection succeeds before the timeout, it is
+ L{_FINISHED}.
+ """
+
+ _IN_PROGRESS = object()
+ _FAILED = object()
+ _FINISHED = object()
+
+ _state = _IN_PROGRESS
+
+ # -- configuration variables --
+ peerCheck = False
+
+ # -- class variables --
+ def __init__(self, pi, peerHost=None, reactor=None):
+ """Constructor
+ @param pi: this factory's protocol interpreter
+ @param peerHost: if peerCheck is True, this is the tuple that the
+ generated instance will use to perform security checks
+ """
+ self.pi = pi # the protocol interpreter that is using this factory
+ self.peerHost = peerHost # the from FTP.transport.peerHost()
+ self.deferred = defer.Deferred() # deferred will fire when instance is connected
+ self.delayedCall = None
+ if reactor is None:
+ from twisted.internet import reactor
+ self._reactor = reactor
+
+
+ def buildProtocol(self, addr):
+ log.msg('DTPFactory.buildProtocol', debug=True)
+
+ if self._state is not self._IN_PROGRESS:
+ return None
+ self._state = self._FINISHED
+
+ self.cancelTimeout()
+ p = DTP()
+ p.factory = self
+ p.pi = self.pi
+ self.pi.dtpInstance = p
+ return p
+
+
+ def stopFactory(self):
+ log.msg('dtpFactory.stopFactory', debug=True)
+ self.cancelTimeout()
+
+
+ def timeoutFactory(self):
+ log.msg('timed out waiting for DTP connection')
+ if self._state is not self._IN_PROGRESS:
+ return
+ self._state = self._FAILED
+
+ d = self.deferred
+ self.deferred = None
+ d.errback(
+ PortConnectionError(defer.TimeoutError("DTPFactory timeout")))
+
+
+ def cancelTimeout(self):
+ if self.delayedCall is not None and self.delayedCall.active():
+ log.msg('cancelling DTP timeout', debug=True)
+ self.delayedCall.cancel()
+
+
+ def setTimeout(self, seconds):
+ log.msg('DTPFactory.setTimeout set to %s seconds' % seconds)
+ self.delayedCall = self._reactor.callLater(seconds, self.timeoutFactory)
+
+
+ def clientConnectionFailed(self, connector, reason):
+ if self._state is not self._IN_PROGRESS:
+ return
+ self._state = self._FAILED
+ d = self.deferred
+ self.deferred = None
+ d.errback(PortConnectionError(reason))
+
+
+# -- FTP-PI (Protocol Interpreter) --
+
+class ASCIIConsumerWrapper(object):
+ def __init__(self, cons):
+ self.cons = cons
+ self.registerProducer = cons.registerProducer
+ self.unregisterProducer = cons.unregisterProducer
+
+ assert os.linesep == "\r\n" or len(os.linesep) == 1, "Unsupported platform (yea right like this even exists)"
+
+ if os.linesep == "\r\n":
+ self.write = cons.write
+
+ def write(self, bytes):
+ return self.cons.write(bytes.replace(os.linesep, "\r\n"))
+
+
+
+class FileConsumer(object):
+ """
+ A consumer for FTP input that writes data to a file.
+
+ @ivar fObj: a file object opened for writing, used to write data received.
+ @type fObj: C{file}
+ """
+
+ implements(interfaces.IConsumer)
+
+ def __init__(self, fObj):
+ self.fObj = fObj
+
+
+ def registerProducer(self, producer, streaming):
+ self.producer = producer
+ assert streaming
+
+
+ def unregisterProducer(self):
+ self.producer = None
+ self.fObj.close()
+
+
+ def write(self, bytes):
+ self.fObj.write(bytes)
+
+
+
+class FTPOverflowProtocol(basic.LineReceiver):
+ """FTP mini-protocol for when there are too many connections."""
+ def connectionMade(self):
+ self.sendLine(RESPONSE[TOO_MANY_CONNECTIONS])
+ self.transport.loseConnection()
+
+
+class FTP(object, basic.LineReceiver, policies.TimeoutMixin):
+ """
+ Protocol Interpreter for the File Transfer Protocol
+
+ @ivar state: The current server state. One of L{UNAUTH},
+ L{INAUTH}, L{AUTHED}, L{RENAMING}.
+
+ @ivar shell: The connected avatar
+ @ivar binary: The transfer mode. If false, ASCII.
+ @ivar dtpFactory: Generates a single DTP for this session
+ @ivar dtpPort: Port returned from listenTCP
+ @ivar listenFactory: A callable with the signature of
+ L{twisted.internet.interfaces.IReactorTCP.listenTCP} which will be used
+ to create Ports for passive connections (mainly for testing).
+
+ @ivar passivePortRange: iterator used as source of passive port numbers.
+ @type passivePortRange: C{iterator}
+ """
+
+ disconnected = False
+
+ # States an FTP can be in
+ UNAUTH, INAUTH, AUTHED, RENAMING = range(4)
+
+ # how long the DTP waits for a connection
+ dtpTimeout = 10
+
+ portal = None
+ shell = None
+ dtpFactory = None
+ dtpPort = None
+ dtpInstance = None
+ binary = True
+
+ passivePortRange = xrange(0, 1)
+
+ listenFactory = reactor.listenTCP
+
+ def reply(self, key, *args):
+ msg = RESPONSE[key] % args
+ self.sendLine(msg)
+
+
+ def connectionMade(self):
+ self.state = self.UNAUTH
+ self.setTimeout(self.timeOut)
+ self.reply(WELCOME_MSG, self.factory.welcomeMessage)
+
+ def connectionLost(self, reason):
+ # if we have a DTP protocol instance running and
+ # we lose connection to the client's PI, kill the
+ # DTP connection and close the port
+ if self.dtpFactory:
+ self.cleanupDTP()
+ self.setTimeout(None)
+ if hasattr(self.shell, 'logout') and self.shell.logout is not None:
+ self.shell.logout()
+ self.shell = None
+ self.transport = None
+
+ def timeoutConnection(self):
+ self.transport.loseConnection()
+
+ def lineReceived(self, line):
+ self.resetTimeout()
+ self.pauseProducing()
+
+ def processFailed(err):
+ if err.check(FTPCmdError):
+ self.sendLine(err.value.response())
+ elif (err.check(TypeError) and
+ err.value.args[0].find('takes exactly') != -1):
+ self.reply(SYNTAX_ERR, "%s requires an argument." % (cmd,))
+ else:
+ log.msg("Unexpected FTP error")
+ log.err(err)
+ self.reply(REQ_ACTN_NOT_TAKEN, "internal server error")
+
+ def processSucceeded(result):
+ if isinstance(result, tuple):
+ self.reply(*result)
+ elif result is not None:
+ self.reply(result)
+
+ def allDone(ignored):
+ if not self.disconnected:
+ self.resumeProducing()
+
+ spaceIndex = line.find(' ')
+ if spaceIndex != -1:
+ cmd = line[:spaceIndex]
+ args = (line[spaceIndex + 1:],)
+ else:
+ cmd = line
+ args = ()
+ d = defer.maybeDeferred(self.processCommand, cmd, *args)
+ d.addCallbacks(processSucceeded, processFailed)
+ d.addErrback(log.err)
+
+ # XXX It burnsss
+ # LineReceiver doesn't let you resumeProducing inside
+ # lineReceived atm
+ from twisted.internet import reactor
+ reactor.callLater(0, d.addBoth, allDone)
+
+
+ def processCommand(self, cmd, *params):
+ cmd = cmd.upper()
+
+ if self.state == self.UNAUTH:
+ if cmd == 'USER':
+ return self.ftp_USER(*params)
+ elif cmd == 'PASS':
+ return BAD_CMD_SEQ, "USER required before PASS"
+ else:
+ return NOT_LOGGED_IN
+
+ elif self.state == self.INAUTH:
+ if cmd == 'PASS':
+ return self.ftp_PASS(*params)
+ else:
+ return BAD_CMD_SEQ, "PASS required after USER"
+
+ elif self.state == self.AUTHED:
+ method = getattr(self, "ftp_" + cmd, None)
+ if method is not None:
+ return method(*params)
+ return defer.fail(CmdNotImplementedError(cmd))
+
+ elif self.state == self.RENAMING:
+ if cmd == 'RNTO':
+ return self.ftp_RNTO(*params)
+ else:
+ return BAD_CMD_SEQ, "RNTO required after RNFR"
+
+
+ def getDTPPort(self, factory):
+ """
+ Return a port for passive access, using C{self.passivePortRange}
+ attribute.
+ """
+ for portn in self.passivePortRange:
+ try:
+ dtpPort = self.listenFactory(portn, factory)
+ except error.CannotListenError:
+ continue
+ else:
+ return dtpPort
+ raise error.CannotListenError('', portn,
+ "No port available in range %s" %
+ (self.passivePortRange,))
+
+
+ def ftp_USER(self, username):
+ """
+ First part of login. Get the username the peer wants to
+ authenticate as.
+ """
+ if not username:
+ return defer.fail(CmdSyntaxError('USER requires an argument'))
+
+ self._user = username
+ self.state = self.INAUTH
+ if self.factory.allowAnonymous and self._user == self.factory.userAnonymous:
+ return GUEST_NAME_OK_NEED_EMAIL
+ else:
+ return (USR_NAME_OK_NEED_PASS, username)
+
+ # TODO: add max auth try before timeout from ip...
+ # TODO: need to implement minimal ABOR command
+
+ def ftp_PASS(self, password):
+ """
+ Second part of login. Get the password the peer wants to
+ authenticate with.
+ """
+ if self.factory.allowAnonymous and self._user == self.factory.userAnonymous:
+ # anonymous login
+ creds = credentials.Anonymous()
+ reply = GUEST_LOGGED_IN_PROCEED
+ else:
+ # user login
+ creds = credentials.UsernamePassword(self._user, password)
+ reply = USR_LOGGED_IN_PROCEED
+ del self._user
+
+ def _cbLogin((interface, avatar, logout)):
+ assert interface is IFTPShell, "The realm is busted, jerk."
+ self.shell = avatar
+ self.logout = logout
+ self.workingDirectory = []
+ self.state = self.AUTHED
+ return reply
+
+ def _ebLogin(failure):
+ failure.trap(cred_error.UnauthorizedLogin, cred_error.UnhandledCredentials)
+ self.state = self.UNAUTH
+ raise AuthorizationError
+
+ d = self.portal.login(creds, None, IFTPShell)
+ d.addCallbacks(_cbLogin, _ebLogin)
+ return d
+
+
+ def ftp_PASV(self):
+ """Request for a passive connection
+
+ from the rfc::
+
+ This command requests the server-DTP to \"listen\" on a data port
+ (which is not its default data port) and to wait for a connection
+ rather than initiate one upon receipt of a transfer command. The
+ response to this command includes the host and port address this
+ server is listening on.
+ """
+ # if we have a DTP port set up, lose it.
+ if self.dtpFactory is not None:
+ # cleanupDTP sets dtpFactory to none. Later we'll do
+ # cleanup here or something.
+ self.cleanupDTP()
+ self.dtpFactory = DTPFactory(pi=self)
+ self.dtpFactory.setTimeout(self.dtpTimeout)
+ self.dtpPort = self.getDTPPort(self.dtpFactory)
+
+ host = self.transport.getHost().host
+ port = self.dtpPort.getHost().port
+ self.reply(ENTERING_PASV_MODE, encodeHostPort(host, port))
+ return self.dtpFactory.deferred.addCallback(lambda ign: None)
+
+
+ def ftp_PORT(self, address):
+ addr = map(int, address.split(','))
+ ip = '%d.%d.%d.%d' % tuple(addr[:4])
+ port = addr[4] << 8 | addr[5]
+
+ # if we have a DTP port set up, lose it.
+ if self.dtpFactory is not None:
+ self.cleanupDTP()
+
+ self.dtpFactory = DTPFactory(pi=self, peerHost=self.transport.getPeer().host)
+ self.dtpFactory.setTimeout(self.dtpTimeout)
+ self.dtpPort = reactor.connectTCP(ip, port, self.dtpFactory)
+
+ def connected(ignored):
+ return ENTERING_PORT_MODE
+ def connFailed(err):
+ err.trap(PortConnectionError)
+ return CANT_OPEN_DATA_CNX
+ return self.dtpFactory.deferred.addCallbacks(connected, connFailed)
+
+
+ def ftp_LIST(self, path=''):
+ """ This command causes a list to be sent from the server to the
+ passive DTP. If the pathname specifies a directory or other
+ group of files, the server should transfer a list of files
+ in the specified directory. If the pathname specifies a
+ file then the server should send current information on the
+ file. A null argument implies the user's current working or
+ default directory.
+ """
+ # Uh, for now, do this retarded thing.
+ if self.dtpInstance is None or not self.dtpInstance.isConnected:
+ return defer.fail(BadCmdSequenceError('must send PORT or PASV before RETR'))
+
+ # bug in konqueror
+ if path == "-a":
+ path = ''
+ # bug in gFTP 2.0.15
+ if path == "-aL":
+ path = ''
+ # bug in Nautilus 2.10.0
+ if path == "-L":
+ path = ''
+ # bug in ange-ftp
+ if path == "-la":
+ path = ''
+
+ def gotListing(results):
+ self.reply(DATA_CNX_ALREADY_OPEN_START_XFR)
+ for (name, attrs) in results:
+ self.dtpInstance.sendListResponse(name, attrs)
+ self.dtpInstance.transport.loseConnection()
+ return (TXFR_COMPLETE_OK,)
+
+ try:
+ segments = toSegments(self.workingDirectory, path)
+ except InvalidPath, e:
+ return defer.fail(FileNotFoundError(path))
+
+ d = self.shell.list(
+ segments,
+ ('size', 'directory', 'permissions', 'hardlinks',
+ 'modified', 'owner', 'group'))
+ d.addCallback(gotListing)
+ return d
+
+
+ def ftp_NLST(self, path):
+ """
+ This command causes a directory listing to be sent from the server to
+ the client. The pathname should specify a directory or other
+ system-specific file group descriptor. An empty path implies the current
+ working directory. If the path is non-existent, send nothing. If the
+ path is to a file, send only the file name.
+
+ @type path: C{str}
+ @param path: The path for which a directory listing should be returned.
+
+ @rtype: L{Deferred}
+ @return: a L{Deferred} which will be fired when the listing request
+ is finished.
+ """
+ # XXX: why is this check different from ftp_RETR/ftp_STOR? See #4180
+ if self.dtpInstance is None or not self.dtpInstance.isConnected:
+ return defer.fail(
+ BadCmdSequenceError('must send PORT or PASV before RETR'))
+
+ try:
+ segments = toSegments(self.workingDirectory, path)
+ except InvalidPath, e:
+ return defer.fail(FileNotFoundError(path))
+
+ def cbList(results):
+ """
+ Send, line by line, each file in the directory listing, and then
+ close the connection.
+
+ @type results: A C{list} of C{tuple}. The first element of each
+ C{tuple} is a C{str} and the second element is a C{list}.
+ @param results: The names of the files in the directory.
+
+ @rtype: C{tuple}
+ @return: A C{tuple} containing the status code for a successful
+ transfer.
+ """
+ self.reply(DATA_CNX_ALREADY_OPEN_START_XFR)
+ for (name, ignored) in results:
+ self.dtpInstance.sendLine(name)
+ self.dtpInstance.transport.loseConnection()
+ return (TXFR_COMPLETE_OK,)
+
+ def cbGlob(results):
+ self.reply(DATA_CNX_ALREADY_OPEN_START_XFR)
+ for (name, ignored) in results:
+ if fnmatch.fnmatch(name, segments[-1]):
+ self.dtpInstance.sendLine(name)
+ self.dtpInstance.transport.loseConnection()
+ return (TXFR_COMPLETE_OK,)
+
+ def listErr(results):
+ """
+ RFC 959 specifies that an NLST request may only return directory
+ listings. Thus, send nothing and just close the connection.
+
+ @type results: L{Failure}
+ @param results: The L{Failure} wrapping a L{FileNotFoundError} that
+ occurred while trying to list the contents of a nonexistent
+ directory.
+
+ @rtype: C{tuple}
+ @returns: A C{tuple} containing the status code for a successful
+ transfer.
+ """
+ self.dtpInstance.transport.loseConnection()
+ return (TXFR_COMPLETE_OK,)
+
+ # XXX This globbing may be incomplete: see #4181
+ if segments and (
+ '*' in segments[-1] or '?' in segments[-1] or
+ ('[' in segments[-1] and ']' in segments[-1])):
+ d = self.shell.list(segments[:-1])
+ d.addCallback(cbGlob)
+ else:
+ d = self.shell.list(segments)
+ d.addCallback(cbList)
+ # self.shell.list will generate an error if the path is invalid
+ d.addErrback(listErr)
+ return d
+
+
+ def ftp_CWD(self, path):
+ try:
+ segments = toSegments(self.workingDirectory, path)
+ except InvalidPath, e:
+ # XXX Eh, what to fail with here?
+ return defer.fail(FileNotFoundError(path))
+
+ def accessGranted(result):
+ self.workingDirectory = segments
+ return (REQ_FILE_ACTN_COMPLETED_OK,)
+
+ return self.shell.access(segments).addCallback(accessGranted)
+
+
+ def ftp_CDUP(self):
+ return self.ftp_CWD('..')
+
+
+ def ftp_PWD(self):
+ return (PWD_REPLY, '/' + '/'.join(self.workingDirectory))
+
+
+ def ftp_RETR(self, path):
+ if self.dtpInstance is None:
+ raise BadCmdSequenceError('PORT or PASV required before RETR')
+
+ try:
+ newsegs = toSegments(self.workingDirectory, path)
+ except InvalidPath:
+ return defer.fail(FileNotFoundError(path))
+
+ # XXX For now, just disable the timeout. Later we'll want to
+ # leave it active and have the DTP connection reset it
+ # periodically.
+ self.setTimeout(None)
+
+ # Put it back later
+ def enableTimeout(result):
+ self.setTimeout(self.factory.timeOut)
+ return result
+
+ # And away she goes
+ if not self.binary:
+ cons = ASCIIConsumerWrapper(self.dtpInstance)
+ else:
+ cons = self.dtpInstance
+
+ def cbSent(result):
+ return (TXFR_COMPLETE_OK,)
+
+ def ebSent(err):
+ log.msg("Unexpected error attempting to transmit file to client:")
+ log.err(err)
+ return (CNX_CLOSED_TXFR_ABORTED,)
+
+ def cbOpened(file):
+ # Tell them what to doooo
+ if self.dtpInstance.isConnected:
+ self.reply(DATA_CNX_ALREADY_OPEN_START_XFR)
+ else:
+ self.reply(FILE_STATUS_OK_OPEN_DATA_CNX)
+
+ d = file.send(cons)
+ d.addCallbacks(cbSent, ebSent)
+ return d
+
+ def ebOpened(err):
+ if not err.check(PermissionDeniedError, FileNotFoundError, IsNotADirectoryError):
+ log.msg("Unexpected error attempting to open file for transmission:")
+ log.err(err)
+ if err.check(FTPCmdError):
+ return (err.value.errorCode, '/'.join(newsegs))
+ return (FILE_NOT_FOUND, '/'.join(newsegs))
+
+ d = self.shell.openForReading(newsegs)
+ d.addCallbacks(cbOpened, ebOpened)
+ d.addBoth(enableTimeout)
+
+ # Pass back Deferred that fires when the transfer is done
+ return d
+
+
+ def ftp_STOR(self, path):
+ if self.dtpInstance is None:
+ raise BadCmdSequenceError('PORT or PASV required before STOR')
+
+ try:
+ newsegs = toSegments(self.workingDirectory, path)
+ except InvalidPath:
+ return defer.fail(FileNotFoundError(path))
+
+ # XXX For now, just disable the timeout. Later we'll want to
+ # leave it active and have the DTP connection reset it
+ # periodically.
+ self.setTimeout(None)
+
+ # Put it back later
+ def enableTimeout(result):
+ self.setTimeout(self.factory.timeOut)
+ return result
+
+ def cbSent(result):
+ return (TXFR_COMPLETE_OK,)
+
+ def ebSent(err):
+ log.msg("Unexpected error receiving file from client:")
+ log.err(err)
+ return (CNX_CLOSED_TXFR_ABORTED,)
+
+ def cbConsumer(cons):
+ if not self.binary:
+ cons = ASCIIConsumerWrapper(cons)
+
+ d = self.dtpInstance.registerConsumer(cons)
+ d.addCallbacks(cbSent, ebSent)
+
+ # Tell them what to doooo
+ if self.dtpInstance.isConnected:
+ self.reply(DATA_CNX_ALREADY_OPEN_START_XFR)
+ else:
+ self.reply(FILE_STATUS_OK_OPEN_DATA_CNX)
+
+ return d
+
+ def cbOpened(file):
+ d = file.receive()
+ d.addCallback(cbConsumer)
+ return d
+
+ def ebOpened(err):
+ if not err.check(PermissionDeniedError, FileNotFoundError, IsNotADirectoryError):
+ log.msg("Unexpected error attempting to open file for upload:")
+ log.err(err)
+ if isinstance(err.value, FTPCmdError):
+ return (err.value.errorCode, '/'.join(newsegs))
+ return (FILE_NOT_FOUND, '/'.join(newsegs))
+
+ d = self.shell.openForWriting(newsegs)
+ d.addCallbacks(cbOpened, ebOpened)
+ d.addBoth(enableTimeout)
+
+ # Pass back Deferred that fires when the transfer is done
+ return d
+
+
+ def ftp_SIZE(self, path):
+ try:
+ newsegs = toSegments(self.workingDirectory, path)
+ except InvalidPath:
+ return defer.fail(FileNotFoundError(path))
+
+ def cbStat((size,)):
+ return (FILE_STATUS, str(size))
+
+ return self.shell.stat(newsegs, ('size',)).addCallback(cbStat)
+
+
+ def ftp_MDTM(self, path):
+ try:
+ newsegs = toSegments(self.workingDirectory, path)
+ except InvalidPath:
+ return defer.fail(FileNotFoundError(path))
+
+ def cbStat((modified,)):
+ return (FILE_STATUS, time.strftime('%Y%m%d%H%M%S', time.gmtime(modified)))
+
+ return self.shell.stat(newsegs, ('modified',)).addCallback(cbStat)
+
+
+ def ftp_TYPE(self, type):
+ p = type.upper()
+ if p:
+ f = getattr(self, 'type_' + p[0], None)
+ if f is not None:
+ return f(p[1:])
+ return self.type_UNKNOWN(p)
+ return (SYNTAX_ERR,)
+
+ def type_A(self, code):
+ if code == '' or code == 'N':
+ self.binary = False
+ return (TYPE_SET_OK, 'A' + code)
+ else:
+ return defer.fail(CmdArgSyntaxError(code))
+
+ def type_I(self, code):
+ if code == '':
+ self.binary = True
+ return (TYPE_SET_OK, 'I')
+ else:
+ return defer.fail(CmdArgSyntaxError(code))
+
+ def type_UNKNOWN(self, code):
+ return defer.fail(CmdNotImplementedForArgError(code))
+
+
+
+ def ftp_SYST(self):
+ return NAME_SYS_TYPE
+
+
+ def ftp_STRU(self, structure):
+ p = structure.upper()
+ if p == 'F':
+ return (CMD_OK,)
+ return defer.fail(CmdNotImplementedForArgError(structure))
+
+
+ def ftp_MODE(self, mode):
+ p = mode.upper()
+ if p == 'S':
+ return (CMD_OK,)
+ return defer.fail(CmdNotImplementedForArgError(mode))
+
+
+ def ftp_MKD(self, path):
+ try:
+ newsegs = toSegments(self.workingDirectory, path)
+ except InvalidPath:
+ return defer.fail(FileNotFoundError(path))
+ return self.shell.makeDirectory(newsegs).addCallback(lambda ign: (MKD_REPLY, path))
+
+
+ def ftp_RMD(self, path):
+ try:
+ newsegs = toSegments(self.workingDirectory, path)
+ except InvalidPath:
+ return defer.fail(FileNotFoundError(path))
+ return self.shell.removeDirectory(newsegs).addCallback(lambda ign: (REQ_FILE_ACTN_COMPLETED_OK,))
+
+
+ def ftp_DELE(self, path):
+ try:
+ newsegs = toSegments(self.workingDirectory, path)
+ except InvalidPath:
+ return defer.fail(FileNotFoundError(path))
+ return self.shell.removeFile(newsegs).addCallback(lambda ign: (REQ_FILE_ACTN_COMPLETED_OK,))
+
+
+ def ftp_NOOP(self):
+ return (CMD_OK,)
+
+
+ def ftp_RNFR(self, fromName):
+ self._fromName = fromName
+ self.state = self.RENAMING
+ return (REQ_FILE_ACTN_PENDING_FURTHER_INFO,)
+
+
+ def ftp_RNTO(self, toName):
+ fromName = self._fromName
+ del self._fromName
+ self.state = self.AUTHED
+
+ try:
+ fromsegs = toSegments(self.workingDirectory, fromName)
+ tosegs = toSegments(self.workingDirectory, toName)
+ except InvalidPath:
+ return defer.fail(FileNotFoundError(fromName))
+ return self.shell.rename(fromsegs, tosegs).addCallback(lambda ign: (REQ_FILE_ACTN_COMPLETED_OK,))
+
+
+ def ftp_QUIT(self):
+ self.reply(GOODBYE_MSG)
+ self.transport.loseConnection()
+ self.disconnected = True
+
+
+ def cleanupDTP(self):
+ """call when DTP connection exits
+ """
+ log.msg('cleanupDTP', debug=True)
+
+ log.msg(self.dtpPort)
+ dtpPort, self.dtpPort = self.dtpPort, None
+ if interfaces.IListeningPort.providedBy(dtpPort):
+ dtpPort.stopListening()
+ elif interfaces.IConnector.providedBy(dtpPort):
+ dtpPort.disconnect()
+ else:
+ assert False, "dtpPort should be an IListeningPort or IConnector, instead is %r" % (dtpPort,)
+
+ self.dtpFactory.stopFactory()
+ self.dtpFactory = None
+
+ if self.dtpInstance is not None:
+ self.dtpInstance = None
+
+
+class FTPFactory(policies.LimitTotalConnectionsFactory):
+ """
+ A factory for producing ftp protocol instances
+
+ @ivar timeOut: the protocol interpreter's idle timeout time in seconds,
+ default is 600 seconds.
+
+ @ivar passivePortRange: value forwarded to C{protocol.passivePortRange}.
+ @type passivePortRange: C{iterator}
+ """
+ protocol = FTP
+ overflowProtocol = FTPOverflowProtocol
+ allowAnonymous = True
+ userAnonymous = 'anonymous'
+ timeOut = 600
+
+ welcomeMessage = "Twisted %s FTP Server" % (copyright.version,)
+
+ passivePortRange = xrange(0, 1)
+
+ def __init__(self, portal=None, userAnonymous='anonymous'):
+ self.portal = portal
+ self.userAnonymous = userAnonymous
+ self.instances = []
+
+ def buildProtocol(self, addr):
+ p = policies.LimitTotalConnectionsFactory.buildProtocol(self, addr)
+ if p is not None:
+ p.wrappedProtocol.portal = self.portal
+ p.wrappedProtocol.timeOut = self.timeOut
+ p.wrappedProtocol.passivePortRange = self.passivePortRange
+ return p
+
+ def stopFactory(self):
+ # make sure ftp instance's timeouts are set to None
+ # to avoid reactor complaints
+ [p.setTimeout(None) for p in self.instances if p.timeOut is not None]
+ policies.LimitTotalConnectionsFactory.stopFactory(self)
+
+# -- Cred Objects --
+
+
+class IFTPShell(Interface):
+ """
+ An abstraction of the shell commands used by the FTP protocol for
+ a given user account.
+
+ All path names must be absolute.
+ """
+
+ def makeDirectory(path):
+ """
+ Create a directory.
+
+ @param path: The path, as a list of segments, to create
+ @type path: C{list} of C{unicode}
+
+ @return: A Deferred which fires when the directory has been
+ created, or which fails if the directory cannot be created.
+ """
+
+
+ def removeDirectory(path):
+ """
+ Remove a directory.
+
+ @param path: The path, as a list of segments, to remove
+ @type path: C{list} of C{unicode}
+
+ @return: A Deferred which fires when the directory has been
+ removed, or which fails if the directory cannot be removed.
+ """
+
+
+ def removeFile(path):
+ """
+ Remove a file.
+
+ @param path: The path, as a list of segments, to remove
+ @type path: C{list} of C{unicode}
+
+ @return: A Deferred which fires when the file has been
+ removed, or which fails if the file cannot be removed.
+ """
+
+
+ def rename(fromPath, toPath):
+ """
+ Rename a file or directory.
+
+ @param fromPath: The current name of the path.
+ @type fromPath: C{list} of C{unicode}
+
+ @param toPath: The desired new name of the path.
+ @type toPath: C{list} of C{unicode}
+
+ @return: A Deferred which fires when the path has been
+ renamed, or which fails if the path cannot be renamed.
+ """
+
+
+ def access(path):
+ """
+ Determine whether access to the given path is allowed.
+
+ @param path: The path, as a list of segments
+
+ @return: A Deferred which fires with None if access is allowed
+ or which fails with a specific exception type if access is
+ denied.
+ """
+
+
+ def stat(path, keys=()):
+ """
+ Retrieve information about the given path.
+
+ This is like list, except it will never return results about
+ child paths.
+ """
+
+
+ def list(path, keys=()):
+ """
+ Retrieve information about the given path.
+
+ If the path represents a non-directory, the result list should
+ have only one entry with information about that non-directory.
+ Otherwise, the result list should have an element for each
+ child of the directory.
+
+ @param path: The path, as a list of segments, to list
+ @type path: C{list} of C{unicode}
+
+ @param keys: A tuple of keys desired in the resulting
+ dictionaries.
+
+ @return: A Deferred which fires with a list of (name, list),
+ where the name is the name of the entry as a unicode string
+ and each list contains values corresponding to the requested
+ keys. The following are possible elements of keys, and the
+ values which should be returned for them:
+
+ - C{'size'}: size in bytes, as an integer (this is kinda required)
+
+ - C{'directory'}: boolean indicating the type of this entry
+
+ - C{'permissions'}: a bitvector (see os.stat(foo).st_mode)
+
+ - C{'hardlinks'}: Number of hard links to this entry
+
+ - C{'modified'}: number of seconds since the epoch since entry was
+ modified
+
+ - C{'owner'}: string indicating the user owner of this entry
+
+ - C{'group'}: string indicating the group owner of this entry
+ """
+
+
+ def openForReading(path):
+ """
+ @param path: The path, as a list of segments, to open
+ @type path: C{list} of C{unicode}
+
+ @rtype: C{Deferred} which will fire with L{IReadFile}
+ """
+
+
+ def openForWriting(path):
+ """
+ @param path: The path, as a list of segments, to open
+ @type path: C{list} of C{unicode}
+
+ @rtype: C{Deferred} which will fire with L{IWriteFile}
+ """
+
+
+
+class IReadFile(Interface):
+ """
+ A file out of which bytes may be read.
+ """
+
+ def send(consumer):
+ """
+ Produce the contents of the given path to the given consumer. This
+ method may only be invoked once on each provider.
+
+ @type consumer: C{IConsumer}
+
+ @return: A Deferred which fires when the file has been
+ consumed completely.
+ """
+
+
+
+class IWriteFile(Interface):
+ """
+ A file into which bytes may be written.
+ """
+
+ def receive():
+ """
+ Create a consumer which will write to this file. This method may
+ only be invoked once on each provider.
+
+ @rtype: C{Deferred} of C{IConsumer}
+ """
+
+
+
+def _getgroups(uid):
+ """Return the primary and supplementary groups for the given UID.
+
+ @type uid: C{int}
+ """
+ result = []
+ pwent = pwd.getpwuid(uid)
+
+ result.append(pwent.pw_gid)
+
+ for grent in grp.getgrall():
+ if pwent.pw_name in grent.gr_mem:
+ result.append(grent.gr_gid)
+
+ return result
+
+
+def _testPermissions(uid, gid, spath, mode='r'):
+ """
+ checks to see if uid has proper permissions to access path with mode
+
+ @type uid: C{int}
+ @param uid: numeric user id
+
+ @type gid: C{int}
+ @param gid: numeric group id
+
+ @type spath: C{str}
+ @param spath: the path on the server to test
+
+ @type mode: C{str}
+ @param mode: 'r' or 'w' (read or write)
+
+ @rtype: C{bool}
+ @return: True if the given credentials have the specified form of
+ access to the given path
+ """
+ if mode == 'r':
+ usr = stat.S_IRUSR
+ grp = stat.S_IRGRP
+ oth = stat.S_IROTH
+ amode = os.R_OK
+ elif mode == 'w':
+ usr = stat.S_IWUSR
+ grp = stat.S_IWGRP
+ oth = stat.S_IWOTH
+ amode = os.W_OK
+ else:
+ raise ValueError("Invalid mode %r: must specify 'r' or 'w'" % (mode,))
+
+ access = False
+ if os.path.exists(spath):
+ if uid == 0:
+ access = True
+ else:
+ s = os.stat(spath)
+ if usr & s.st_mode and uid == s.st_uid:
+ access = True
+ elif grp & s.st_mode and gid in _getgroups(uid):
+ access = True
+ elif oth & s.st_mode:
+ access = True
+
+ if access:
+ if not os.access(spath, amode):
+ access = False
+ log.msg("Filesystem grants permission to UID %d but it is inaccessible to me running as UID %d" % (
+ uid, os.getuid()))
+ return access
+
+
+
+class FTPAnonymousShell(object):
+ """
+ An anonymous implementation of IFTPShell
+
+ @type filesystemRoot: L{twisted.python.filepath.FilePath}
+ @ivar filesystemRoot: The path which is considered the root of
+ this shell.
+ """
+ implements(IFTPShell)
+
+ def __init__(self, filesystemRoot):
+ self.filesystemRoot = filesystemRoot
+
+
+ def _path(self, path):
+ return reduce(filepath.FilePath.child, path, self.filesystemRoot)
+
+
+ def makeDirectory(self, path):
+ return defer.fail(AnonUserDeniedError())
+
+
+ def removeDirectory(self, path):
+ return defer.fail(AnonUserDeniedError())
+
+
+ def removeFile(self, path):
+ return defer.fail(AnonUserDeniedError())
+
+
+ def rename(self, fromPath, toPath):
+ return defer.fail(AnonUserDeniedError())
+
+
+ def receive(self, path):
+ path = self._path(path)
+ return defer.fail(AnonUserDeniedError())
+
+
+ def openForReading(self, path):
+ p = self._path(path)
+ if p.isdir():
+ # Normally, we would only check for EISDIR in open, but win32
+ # returns EACCES in this case, so we check before
+ return defer.fail(IsADirectoryError(path))
+ try:
+ f = p.open('rb')
+ except (IOError, OSError), e:
+ return errnoToFailure(e.errno, path)
+ except:
+ return defer.fail()
+ else:
+ return defer.succeed(_FileReader(f))
+
+
+ def openForWriting(self, path):
+ """
+ Reject write attempts by anonymous users with
+ L{PermissionDeniedError}.
+ """
+ return defer.fail(PermissionDeniedError("STOR not allowed"))
+
+
+ def access(self, path):
+ p = self._path(path)
+ if not p.exists():
+ # Again, win32 doesn't report a sane error after, so let's fail
+ # early if we can
+ return defer.fail(FileNotFoundError(path))
+ # For now, just see if we can os.listdir() it
+ try:
+ p.listdir()
+ except (IOError, OSError), e:
+ return errnoToFailure(e.errno, path)
+ except:
+ return defer.fail()
+ else:
+ return defer.succeed(None)
+
+
+ def stat(self, path, keys=()):
+ p = self._path(path)
+ if p.isdir():
+ try:
+ statResult = self._statNode(p, keys)
+ except (IOError, OSError), e:
+ return errnoToFailure(e.errno, path)
+ except:
+ return defer.fail()
+ else:
+ return defer.succeed(statResult)
+ else:
+ return self.list(path, keys).addCallback(lambda res: res[0][1])
+
+
+ def list(self, path, keys=()):
+ """
+ Return the list of files at given C{path}, adding C{keys} stat
+ informations if specified.
+
+ @param path: the directory or file to check.
+ @type path: C{str}
+
+ @param keys: the list of desired metadata
+ @type keys: C{list} of C{str}
+ """
+ filePath = self._path(path)
+ if filePath.isdir():
+ entries = filePath.listdir()
+ fileEntries = [filePath.child(p) for p in entries]
+ elif filePath.isfile():
+ entries = [os.path.join(*filePath.segmentsFrom(self.filesystemRoot))]
+ fileEntries = [filePath]
+ else:
+ return defer.fail(FileNotFoundError(path))
+
+ results = []
+ for fileName, filePath in zip(entries, fileEntries):
+ ent = []
+ results.append((fileName, ent))
+ if keys:
+ try:
+ ent.extend(self._statNode(filePath, keys))
+ except (IOError, OSError), e:
+ return errnoToFailure(e.errno, fileName)
+ except:
+ return defer.fail()
+
+ return defer.succeed(results)
+
+
+ def _statNode(self, filePath, keys):
+ """
+ Shortcut method to get stat info on a node.
+
+ @param filePath: the node to stat.
+ @type filePath: C{filepath.FilePath}
+
+ @param keys: the stat keys to get.
+ @type keys: C{iterable}
+ """
+ filePath.restat()
+ return [getattr(self, '_stat_' + k)(filePath.statinfo) for k in keys]
+
+ _stat_size = operator.attrgetter('st_size')
+ _stat_permissions = operator.attrgetter('st_mode')
+ _stat_hardlinks = operator.attrgetter('st_nlink')
+ _stat_modified = operator.attrgetter('st_mtime')
+
+
+ def _stat_owner(self, st):
+ if pwd is not None:
+ try:
+ return pwd.getpwuid(st.st_uid)[0]
+ except KeyError:
+ pass
+ return str(st.st_uid)
+
+
+ def _stat_group(self, st):
+ if grp is not None:
+ try:
+ return grp.getgrgid(st.st_gid)[0]
+ except KeyError:
+ pass
+ return str(st.st_gid)
+
+
+ def _stat_directory(self, st):
+ return bool(st.st_mode & stat.S_IFDIR)
+
+
+
+class _FileReader(object):
+ implements(IReadFile)
+
+ def __init__(self, fObj):
+ self.fObj = fObj
+ self._send = False
+
+ def _close(self, passthrough):
+ self._send = True
+ self.fObj.close()
+ return passthrough
+
+ def send(self, consumer):
+ assert not self._send, "Can only call IReadFile.send *once* per instance"
+ self._send = True
+ d = basic.FileSender().beginFileTransfer(self.fObj, consumer)
+ d.addBoth(self._close)
+ return d
+
+
+
+class FTPShell(FTPAnonymousShell):
+ """
+ An authenticated implementation of L{IFTPShell}.
+ """
+
+ def makeDirectory(self, path):
+ p = self._path(path)
+ try:
+ p.makedirs()
+ except (IOError, OSError), e:
+ return errnoToFailure(e.errno, path)
+ except:
+ return defer.fail()
+ else:
+ return defer.succeed(None)
+
+
+ def removeDirectory(self, path):
+ p = self._path(path)
+ if p.isfile():
+ # Win32 returns the wrong errno when rmdir is called on a file
+ # instead of a directory, so as we have the info here, let's fail
+ # early with a pertinent error
+ return defer.fail(IsNotADirectoryError(path))
+ try:
+ os.rmdir(p.path)
+ except (IOError, OSError), e:
+ return errnoToFailure(e.errno, path)
+ except:
+ return defer.fail()
+ else:
+ return defer.succeed(None)
+
+
+ def removeFile(self, path):
+ p = self._path(path)
+ if p.isdir():
+ # Win32 returns the wrong errno when remove is called on a
+ # directory instead of a file, so as we have the info here,
+ # let's fail early with a pertinent error
+ return defer.fail(IsADirectoryError(path))
+ try:
+ p.remove()
+ except (IOError, OSError), e:
+ return errnoToFailure(e.errno, path)
+ except:
+ return defer.fail()
+ else:
+ return defer.succeed(None)
+
+
+ def rename(self, fromPath, toPath):
+ fp = self._path(fromPath)
+ tp = self._path(toPath)
+ try:
+ os.rename(fp.path, tp.path)
+ except (IOError, OSError), e:
+ return errnoToFailure(e.errno, fromPath)
+ except:
+ return defer.fail()
+ else:
+ return defer.succeed(None)
+
+
+ def openForWriting(self, path):
+ p = self._path(path)
+ if p.isdir():
+ # Normally, we would only check for EISDIR in open, but win32
+ # returns EACCES in this case, so we check before
+ return defer.fail(IsADirectoryError(path))
+ try:
+ fObj = p.open('wb')
+ except (IOError, OSError), e:
+ return errnoToFailure(e.errno, path)
+ except:
+ return defer.fail()
+ return defer.succeed(_FileWriter(fObj))
+
+
+
+class _FileWriter(object):
+ implements(IWriteFile)
+
+ def __init__(self, fObj):
+ self.fObj = fObj
+ self._receive = False
+
+ def receive(self):
+ assert not self._receive, "Can only call IWriteFile.receive *once* per instance"
+ self._receive = True
+ # FileConsumer will close the file object
+ return defer.succeed(FileConsumer(self.fObj))
+
+
+
+class FTPRealm:
+ """
+ @type anonymousRoot: L{twisted.python.filepath.FilePath}
+ @ivar anonymousRoot: Root of the filesystem to which anonymous
+ users will be granted access.
+ """
+ implements(portal.IRealm)
+
+ def __init__(self, anonymousRoot):
+ self.anonymousRoot = filepath.FilePath(anonymousRoot)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ for iface in interfaces:
+ if iface is IFTPShell:
+ if avatarId is checkers.ANONYMOUS:
+ avatar = FTPAnonymousShell(self.anonymousRoot)
+ else:
+ avatar = FTPShell(filepath.FilePath("/home/" + avatarId))
+ return IFTPShell, avatar, getattr(avatar, 'logout', lambda: None)
+ raise NotImplementedError("Only IFTPShell interface is supported by this realm")
+
+# --- FTP CLIENT -------------------------------------------------------------
+
+####
+# And now for the client...
+
+# Notes:
+# * Reference: http://cr.yp.to/ftp.html
+# * FIXME: Does not support pipelining (which is not supported by all
+# servers anyway). This isn't a functionality limitation, just a
+# small performance issue.
+# * Only has a rudimentary understanding of FTP response codes (although
+# the full response is passed to the caller if they so choose).
+# * Assumes that USER and PASS should always be sent
+# * Always sets TYPE I (binary mode)
+# * Doesn't understand any of the weird, obscure TELNET stuff (\377...)
+# * FIXME: Doesn't share any code with the FTPServer
+
+class ConnectionLost(FTPError):
+ pass
+
+class CommandFailed(FTPError):
+ pass
+
+class BadResponse(FTPError):
+ pass
+
+class UnexpectedResponse(FTPError):
+ pass
+
+class UnexpectedData(FTPError):
+ pass
+
+class FTPCommand:
+ def __init__(self, text=None, public=0):
+ self.text = text
+ self.deferred = defer.Deferred()
+ self.ready = 1
+ self.public = public
+ self.transferDeferred = None
+
+ def fail(self, failure):
+ if self.public:
+ self.deferred.errback(failure)
+
+
+class ProtocolWrapper(protocol.Protocol):
+ def __init__(self, original, deferred):
+ self.original = original
+ self.deferred = deferred
+ def makeConnection(self, transport):
+ self.original.makeConnection(transport)
+ def dataReceived(self, data):
+ self.original.dataReceived(data)
+ def connectionLost(self, reason):
+ self.original.connectionLost(reason)
+ # Signal that transfer has completed
+ self.deferred.callback(None)
+
+
+class SenderProtocol(protocol.Protocol):
+ implements(interfaces.IFinishableConsumer)
+
+ def __init__(self):
+ # Fired upon connection
+ self.connectedDeferred = defer.Deferred()
+
+ # Fired upon disconnection
+ self.deferred = defer.Deferred()
+
+ #Protocol stuff
+ def dataReceived(self, data):
+ raise UnexpectedData(
+ "Received data from the server on a "
+ "send-only data-connection"
+ )
+
+ def makeConnection(self, transport):
+ protocol.Protocol.makeConnection(self, transport)
+ self.connectedDeferred.callback(self)
+
+ def connectionLost(self, reason):
+ if reason.check(error.ConnectionDone):
+ self.deferred.callback('connection done')
+ else:
+ self.deferred.errback(reason)
+
+ #IFinishableConsumer stuff
+ def write(self, data):
+ self.transport.write(data)
+
+ def registerProducer(self, producer, streaming):
+ """
+ Register the given producer with our transport.
+ """
+ self.transport.registerProducer(producer, streaming)
+
+ def unregisterProducer(self):
+ """
+ Unregister the previously registered producer.
+ """
+ self.transport.unregisterProducer()
+
+ def finish(self):
+ self.transport.loseConnection()
+
+
+def decodeHostPort(line):
+ """Decode an FTP response specifying a host and port.
+
+ @return: a 2-tuple of (host, port).
+ """
+ abcdef = re.sub('[^0-9, ]', '', line)
+ parsed = [int(p.strip()) for p in abcdef.split(',')]
+ for x in parsed:
+ if x < 0 or x > 255:
+ raise ValueError("Out of range", line, x)
+ a, b, c, d, e, f = parsed
+ host = "%s.%s.%s.%s" % (a, b, c, d)
+ port = (int(e) << 8) + int(f)
+ return host, port
+
+def encodeHostPort(host, port):
+ numbers = host.split('.') + [str(port >> 8), str(port % 256)]
+ return ','.join(numbers)
+
+def _unwrapFirstError(failure):
+ failure.trap(defer.FirstError)
+ return failure.value.subFailure
+
+class FTPDataPortFactory(protocol.ServerFactory):
+ """Factory for data connections that use the PORT command
+
+ (i.e. "active" transfers)
+ """
+ noisy = 0
+ def buildProtocol(self, addr):
+ # This is a bit hackish -- we already have a Protocol instance,
+ # so just return it instead of making a new one
+ # FIXME: Reject connections from the wrong address/port
+ # (potential security problem)
+ self.protocol.factory = self
+ self.port.loseConnection()
+ return self.protocol
+
+
+class FTPClientBasic(basic.LineReceiver):
+ """
+ Foundations of an FTP client.
+ """
+ debug = False
+
+ def __init__(self):
+ self.actionQueue = []
+ self.greeting = None
+ self.nextDeferred = defer.Deferred().addCallback(self._cb_greeting)
+ self.nextDeferred.addErrback(self.fail)
+ self.response = []
+ self._failed = 0
+
+ def fail(self, error):
+ """
+ Give an error to any queued deferreds.
+ """
+ self._fail(error)
+
+ def _fail(self, error):
+ """
+ Errback all queued deferreds.
+ """
+ if self._failed:
+ # We're recursing; bail out here for simplicity
+ return error
+ self._failed = 1
+ if self.nextDeferred:
+ try:
+ self.nextDeferred.errback(failure.Failure(ConnectionLost('FTP connection lost', error)))
+ except defer.AlreadyCalledError:
+ pass
+ for ftpCommand in self.actionQueue:
+ ftpCommand.fail(failure.Failure(ConnectionLost('FTP connection lost', error)))
+ return error
+
+ def _cb_greeting(self, greeting):
+ self.greeting = greeting
+
+ def sendLine(self, line):
+ """
+ (Private) Sends a line, unless line is None.
+ """
+ if line is None:
+ return
+ basic.LineReceiver.sendLine(self, line)
+
+ def sendNextCommand(self):
+ """
+ (Private) Processes the next command in the queue.
+ """
+ ftpCommand = self.popCommandQueue()
+ if ftpCommand is None:
+ self.nextDeferred = None
+ return
+ if not ftpCommand.ready:
+ self.actionQueue.insert(0, ftpCommand)
+ reactor.callLater(1.0, self.sendNextCommand)
+ self.nextDeferred = None
+ return
+
+ # FIXME: this if block doesn't belong in FTPClientBasic, it belongs in
+ # FTPClient.
+ if ftpCommand.text == 'PORT':
+ self.generatePortCommand(ftpCommand)
+
+ if self.debug:
+ log.msg('<-- %s' % ftpCommand.text)
+ self.nextDeferred = ftpCommand.deferred
+ self.sendLine(ftpCommand.text)
+
+ def queueCommand(self, ftpCommand):
+ """
+ Add an FTPCommand object to the queue.
+
+ If it's the only thing in the queue, and we are connected and we aren't
+ waiting for a response of an earlier command, the command will be sent
+ immediately.
+
+ @param ftpCommand: an L{FTPCommand}
+ """
+ self.actionQueue.append(ftpCommand)
+ if (len(self.actionQueue) == 1 and self.transport is not None and
+ self.nextDeferred is None):
+ self.sendNextCommand()
+
+ def queueStringCommand(self, command, public=1):
+ """
+ Queues a string to be issued as an FTP command
+
+ @param command: string of an FTP command to queue
+ @param public: a flag intended for internal use by FTPClient. Don't
+ change it unless you know what you're doing.
+
+ @return: a L{Deferred} that will be called when the response to the
+ command has been received.
+ """
+ ftpCommand = FTPCommand(command, public)
+ self.queueCommand(ftpCommand)
+ return ftpCommand.deferred
+
+ def popCommandQueue(self):
+ """
+ Return the front element of the command queue, or None if empty.
+ """
+ if self.actionQueue:
+ return self.actionQueue.pop(0)
+ else:
+ return None
+
+ def queueLogin(self, username, password):
+ """
+ Login: send the username, send the password.
+
+ If the password is C{None}, the PASS command won't be sent. Also, if
+ the response to the USER command has a response code of 230 (User logged
+ in), then PASS won't be sent either.
+ """
+ # Prepare the USER command
+ deferreds = []
+ userDeferred = self.queueStringCommand('USER ' + username, public=0)
+ deferreds.append(userDeferred)
+
+ # Prepare the PASS command (if a password is given)
+ if password is not None:
+ passwordCmd = FTPCommand('PASS ' + password, public=0)
+ self.queueCommand(passwordCmd)
+ deferreds.append(passwordCmd.deferred)
+
+ # Avoid sending PASS if the response to USER is 230.
+ # (ref: http://cr.yp.to/ftp/user.html#user)
+ def cancelPasswordIfNotNeeded(response):
+ if response[0].startswith('230'):
+ # No password needed!
+ self.actionQueue.remove(passwordCmd)
+ return response
+ userDeferred.addCallback(cancelPasswordIfNotNeeded)
+
+ # Error handling.
+ for deferred in deferreds:
+ # If something goes wrong, call fail
+ deferred.addErrback(self.fail)
+ # But also swallow the error, so we don't cause spurious errors
+ deferred.addErrback(lambda x: None)
+
+ def lineReceived(self, line):
+ """
+ (Private) Parses the response messages from the FTP server.
+ """
+ # Add this line to the current response
+ if self.debug:
+ log.msg('--> %s' % line)
+ self.response.append(line)
+
+ # Bail out if this isn't the last line of a response
+ # The last line of response starts with 3 digits followed by a space
+ codeIsValid = re.match(r'\d{3} ', line)
+ if not codeIsValid:
+ return
+
+ code = line[0:3]
+
+ # Ignore marks
+ if code[0] == '1':
+ return
+
+ # Check that we were expecting a response
+ if self.nextDeferred is None:
+ self.fail(UnexpectedResponse(self.response))
+ return
+
+ # Reset the response
+ response = self.response
+ self.response = []
+
+ # Look for a success or error code, and call the appropriate callback
+ if code[0] in ('2', '3'):
+ # Success
+ self.nextDeferred.callback(response)
+ elif code[0] in ('4', '5'):
+ # Failure
+ self.nextDeferred.errback(failure.Failure(CommandFailed(response)))
+ else:
+ # This shouldn't happen unless something screwed up.
+ log.msg('Server sent invalid response code %s' % (code,))
+ self.nextDeferred.errback(failure.Failure(BadResponse(response)))
+
+ # Run the next command
+ self.sendNextCommand()
+
+ def connectionLost(self, reason):
+ self._fail(reason)
+
+
+
+class _PassiveConnectionFactory(protocol.ClientFactory):
+ noisy = False
+
+ def __init__(self, protoInstance):
+ self.protoInstance = protoInstance
+
+ def buildProtocol(self, ignored):
+ self.protoInstance.factory = self
+ return self.protoInstance
+
+ def clientConnectionFailed(self, connector, reason):
+ e = FTPError('Connection Failed', reason)
+ self.protoInstance.deferred.errback(e)
+
+
+
+class FTPClient(FTPClientBasic):
+ """
+ L{FTPClient} is a client implementation of the FTP protocol which
+ exposes FTP commands as methods which return L{Deferred}s.
+
+ Each command method returns a L{Deferred} which is called back when a
+ successful response code (2xx or 3xx) is received from the server or
+ which is error backed if an error response code (4xx or 5xx) is received
+ from the server or if a protocol violation occurs. If an error response
+ code is received, the L{Deferred} fires with a L{Failure} wrapping a
+ L{CommandFailed} instance. The L{CommandFailed} instance is created
+ with a list of the response lines received from the server.
+
+ See U{RFC 959<http://www.ietf.org/rfc/rfc959.txt>} for error code
+ definitions.
+
+ Both active and passive transfers are supported.
+
+ @ivar passive: See description in __init__.
+ """
+ connectFactory = reactor.connectTCP
+
+ def __init__(self, username='anonymous',
+ password='twisted@twistedmatrix.com',
+ passive=1):
+ """
+ Constructor.
+
+ I will login as soon as I receive the welcome message from the server.
+
+ @param username: FTP username
+ @param password: FTP password
+ @param passive: flag that controls if I use active or passive data
+ connections. You can also change this after construction by
+ assigning to C{self.passive}.
+ """
+ FTPClientBasic.__init__(self)
+ self.queueLogin(username, password)
+
+ self.passive = passive
+
+ def fail(self, error):
+ """
+ Disconnect, and also give an error to any queued deferreds.
+ """
+ self.transport.loseConnection()
+ self._fail(error)
+
+ def receiveFromConnection(self, commands, protocol):
+ """
+ Retrieves a file or listing generated by the given command,
+ feeding it to the given protocol.
+
+ @param commands: list of strings of FTP commands to execute then receive
+ the results of (e.g. C{LIST}, C{RETR})
+ @param protocol: A L{Protocol} B{instance} e.g. an
+ L{FTPFileListProtocol}, or something that can be adapted to one.
+ Typically this will be an L{IConsumer} implementation.
+
+ @return: L{Deferred}.
+ """
+ protocol = interfaces.IProtocol(protocol)
+ wrapper = ProtocolWrapper(protocol, defer.Deferred())
+ return self._openDataConnection(commands, wrapper)
+
+ def queueLogin(self, username, password):
+ """
+ Login: send the username, send the password, and
+ set retrieval mode to binary
+ """
+ FTPClientBasic.queueLogin(self, username, password)
+ d = self.queueStringCommand('TYPE I', public=0)
+ # If something goes wrong, call fail
+ d.addErrback(self.fail)
+ # But also swallow the error, so we don't cause spurious errors
+ d.addErrback(lambda x: None)
+
+ def sendToConnection(self, commands):
+ """
+ XXX
+
+ @return: A tuple of two L{Deferred}s:
+ - L{Deferred} L{IFinishableConsumer}. You must call
+ the C{finish} method on the IFinishableConsumer when the file
+ is completely transferred.
+ - L{Deferred} list of control-connection responses.
+ """
+ s = SenderProtocol()
+ r = self._openDataConnection(commands, s)
+ return (s.connectedDeferred, r)
+
+ def _openDataConnection(self, commands, protocol):
+ """
+ This method returns a DeferredList.
+ """
+ cmds = [FTPCommand(command, public=1) for command in commands]
+ cmdsDeferred = defer.DeferredList([cmd.deferred for cmd in cmds],
+ fireOnOneErrback=True, consumeErrors=True)
+ cmdsDeferred.addErrback(_unwrapFirstError)
+
+ if self.passive:
+ # Hack: use a mutable object to sneak a variable out of the
+ # scope of doPassive
+ _mutable = [None]
+ def doPassive(response):
+ """Connect to the port specified in the response to PASV"""
+ host, port = decodeHostPort(response[-1][4:])
+
+ f = _PassiveConnectionFactory(protocol)
+ _mutable[0] = self.connectFactory(host, port, f)
+
+ pasvCmd = FTPCommand('PASV')
+ self.queueCommand(pasvCmd)
+ pasvCmd.deferred.addCallback(doPassive).addErrback(self.fail)
+
+ results = [cmdsDeferred, pasvCmd.deferred, protocol.deferred]
+ d = defer.DeferredList(results, fireOnOneErrback=True, consumeErrors=True)
+ d.addErrback(_unwrapFirstError)
+
+ # Ensure the connection is always closed
+ def close(x, m=_mutable):
+ m[0] and m[0].disconnect()
+ return x
+ d.addBoth(close)
+
+ else:
+ # We just place a marker command in the queue, and will fill in
+ # the host and port numbers later (see generatePortCommand)
+ portCmd = FTPCommand('PORT')
+
+ # Ok, now we jump through a few hoops here.
+ # This is the problem: a transfer is not to be trusted as complete
+ # until we get both the "226 Transfer complete" message on the
+ # control connection, and the data socket is closed. Thus, we use
+ # a DeferredList to make sure we only fire the callback at the
+ # right time.
+
+ portCmd.transferDeferred = protocol.deferred
+ portCmd.protocol = protocol
+ portCmd.deferred.addErrback(portCmd.transferDeferred.errback)
+ self.queueCommand(portCmd)
+
+ # Create dummy functions for the next callback to call.
+ # These will also be replaced with real functions in
+ # generatePortCommand.
+ portCmd.loseConnection = lambda result: result
+ portCmd.fail = lambda error: error
+
+ # Ensure that the connection always gets closed
+ cmdsDeferred.addErrback(lambda e, pc=portCmd: pc.fail(e) or e)
+
+ results = [cmdsDeferred, portCmd.deferred, portCmd.transferDeferred]
+ d = defer.DeferredList(results, fireOnOneErrback=True, consumeErrors=True)
+ d.addErrback(_unwrapFirstError)
+
+ for cmd in cmds:
+ self.queueCommand(cmd)
+ return d
+
+ def generatePortCommand(self, portCmd):
+ """
+ (Private) Generates the text of a given PORT command.
+ """
+
+ # The problem is that we don't create the listening port until we need
+ # it for various reasons, and so we have to muck about to figure out
+ # what interface and port it's listening on, and then finally we can
+ # create the text of the PORT command to send to the FTP server.
+
+ # FIXME: This method is far too ugly.
+
+ # FIXME: The best solution is probably to only create the data port
+ # once per FTPClient, and just recycle it for each new download.
+ # This should be ok, because we don't pipeline commands.
+
+ # Start listening on a port
+ factory = FTPDataPortFactory()
+ factory.protocol = portCmd.protocol
+ listener = reactor.listenTCP(0, factory)
+ factory.port = listener
+
+ # Ensure we close the listening port if something goes wrong
+ def listenerFail(error, listener=listener):
+ if listener.connected:
+ listener.loseConnection()
+ return error
+ portCmd.fail = listenerFail
+
+ # Construct crufty FTP magic numbers that represent host & port
+ host = self.transport.getHost().host
+ port = listener.getHost().port
+ portCmd.text = 'PORT ' + encodeHostPort(host, port)
+
+ def escapePath(self, path):
+ """
+ Returns a FTP escaped path (replace newlines with nulls).
+ """
+ # Escape newline characters
+ return path.replace('\n', '\0')
+
+ def retrieveFile(self, path, protocol, offset=0):
+ """
+ Retrieve a file from the given path
+
+ This method issues the 'RETR' FTP command.
+
+ The file is fed into the given Protocol instance. The data connection
+ will be passive if self.passive is set.
+
+ @param path: path to file that you wish to receive.
+ @param protocol: a L{Protocol} instance.
+ @param offset: offset to start downloading from
+
+ @return: L{Deferred}
+ """
+ cmds = ['RETR ' + self.escapePath(path)]
+ if offset:
+ cmds.insert(0, ('REST ' + str(offset)))
+ return self.receiveFromConnection(cmds, protocol)
+
+ retr = retrieveFile
+
+ def storeFile(self, path, offset=0):
+ """
+ Store a file at the given path.
+
+ This method issues the 'STOR' FTP command.
+
+ @return: A tuple of two L{Deferred}s:
+ - L{Deferred} L{IFinishableConsumer}. You must call
+ the C{finish} method on the IFinishableConsumer when the file
+ is completely transferred.
+ - L{Deferred} list of control-connection responses.
+ """
+ cmds = ['STOR ' + self.escapePath(path)]
+ if offset:
+ cmds.insert(0, ('REST ' + str(offset)))
+ return self.sendToConnection(cmds)
+
+ stor = storeFile
+
+
+ def rename(self, pathFrom, pathTo):
+ """
+ Rename a file.
+
+ This method issues the I{RNFR}/I{RNTO} command sequence to rename
+ C{pathFrom} to C{pathTo}.
+
+ @param: pathFrom: the absolute path to the file to be renamed
+ @type pathFrom: C{str}
+
+ @param: pathTo: the absolute path to rename the file to.
+ @type pathTo: C{str}
+
+ @return: A L{Deferred} which fires when the rename operation has
+ succeeded or failed. If it succeeds, the L{Deferred} is called
+ back with a two-tuple of lists. The first list contains the
+ responses to the I{RNFR} command. The second list contains the
+ responses to the I{RNTO} command. If either I{RNFR} or I{RNTO}
+ fails, the L{Deferred} is errbacked with L{CommandFailed} or
+ L{BadResponse}.
+ @rtype: L{Deferred}
+
+ @since: 8.2
+ """
+ renameFrom = self.queueStringCommand('RNFR ' + self.escapePath(pathFrom))
+ renameTo = self.queueStringCommand('RNTO ' + self.escapePath(pathTo))
+
+ fromResponse = []
+
+ # Use a separate Deferred for the ultimate result so that Deferred
+ # chaining can't interfere with its result.
+ result = defer.Deferred()
+ # Bundle up all the responses
+ result.addCallback(lambda toResponse: (fromResponse, toResponse))
+
+ def ebFrom(failure):
+ # Make sure the RNTO doesn't run if the RNFR failed.
+ self.popCommandQueue()
+ result.errback(failure)
+
+ # Save the RNFR response to pass to the result Deferred later
+ renameFrom.addCallbacks(fromResponse.extend, ebFrom)
+
+ # Hook up the RNTO to the result Deferred as well
+ renameTo.chainDeferred(result)
+
+ return result
+
+
+ def list(self, path, protocol):
+ """
+ Retrieve a file listing into the given protocol instance.
+
+ This method issues the 'LIST' FTP command.
+
+ @param path: path to get a file listing for.
+ @param protocol: a L{Protocol} instance, probably a
+ L{FTPFileListProtocol} instance. It can cope with most common file
+ listing formats.
+
+ @return: L{Deferred}
+ """
+ if path is None:
+ path = ''
+ return self.receiveFromConnection(['LIST ' + self.escapePath(path)], protocol)
+
+
+ def nlst(self, path, protocol):
+ """
+ Retrieve a short file listing into the given protocol instance.
+
+ This method issues the 'NLST' FTP command.
+
+ NLST (should) return a list of filenames, one per line.
+
+ @param path: path to get short file listing for.
+ @param protocol: a L{Protocol} instance.
+ """
+ if path is None:
+ path = ''
+ return self.receiveFromConnection(['NLST ' + self.escapePath(path)], protocol)
+
+
+ def cwd(self, path):
+ """
+ Issues the CWD (Change Working Directory) command. It's also
+ available as changeDirectory, which parses the result.
+
+ @return: a L{Deferred} that will be called when done.
+ """
+ return self.queueStringCommand('CWD ' + self.escapePath(path))
+
+
+ def changeDirectory(self, path):
+ """
+ Change the directory on the server and parse the result to determine
+ if it was successful or not.
+
+ @type path: C{str}
+ @param path: The path to which to change.
+
+ @return: a L{Deferred} which will be called back when the directory
+ change has succeeded or errbacked if an error occurrs.
+ """
+ warnings.warn(
+ "FTPClient.changeDirectory is deprecated in Twisted 8.2 and "
+ "newer. Use FTPClient.cwd instead.",
+ category=DeprecationWarning,
+ stacklevel=2)
+
+ def cbResult(result):
+ if result[-1][:3] != '250':
+ return failure.Failure(CommandFailed(result))
+ return True
+ return self.cwd(path).addCallback(cbResult)
+
+
+ def makeDirectory(self, path):
+ """
+ Make a directory
+
+ This method issues the MKD command.
+
+ @param path: The path to the directory to create.
+ @type path: C{str}
+
+ @return: A L{Deferred} which fires when the server responds. If the
+ directory is created, the L{Deferred} is called back with the
+ server response. If the server response indicates the directory
+ was not created, the L{Deferred} is errbacked with a L{Failure}
+ wrapping L{CommandFailed} or L{BadResponse}.
+ @rtype: L{Deferred}
+
+ @since: 8.2
+ """
+ return self.queueStringCommand('MKD ' + self.escapePath(path))
+
+
+ def removeFile(self, path):
+ """
+ Delete a file on the server.
+
+ L{removeFile} issues a I{DELE} command to the server to remove the
+ indicated file. Note that this command cannot remove a directory.
+
+ @param path: The path to the file to delete. May be relative to the
+ current dir.
+ @type path: C{str}
+
+ @return: A L{Deferred} which fires when the server responds. On error,
+ it is errbacked with either L{CommandFailed} or L{BadResponse}. On
+ success, it is called back with a list of response lines.
+ @rtype: L{Deferred}
+
+ @since: 8.2
+ """
+ return self.queueStringCommand('DELE ' + self.escapePath(path))
+
+
+ def cdup(self):
+ """
+ Issues the CDUP (Change Directory UP) command.
+
+ @return: a L{Deferred} that will be called when done.
+ """
+ return self.queueStringCommand('CDUP')
+
+
+ def pwd(self):
+ """
+ Issues the PWD (Print Working Directory) command.
+
+ The L{getDirectory} does the same job but automatically parses the
+ result.
+
+ @return: a L{Deferred} that will be called when done. It is up to the
+ caller to interpret the response, but the L{parsePWDResponse} method
+ in this module should work.
+ """
+ return self.queueStringCommand('PWD')
+
+ def getDirectory(self):
+ """
+ Returns the current remote directory.
+
+ @return: a L{Deferred} that will be called back with a C{str} giving
+ the remote directory or which will errback with L{CommandFailed}
+ if an error response is returned.
+ """
+ def cbParse(result):
+ try:
+ # The only valid code is 257
+ if int(result[0].split(' ', 1)[0]) != 257:
+ raise ValueError
+ except (IndexError, ValueError), e:
+ return failure.Failure(CommandFailed(result))
+ path = parsePWDResponse(result[0])
+ if path is None:
+ return failure.Failure(CommandFailed(result))
+ return path
+ return self.pwd().addCallback(cbParse)
+
+
+ def quit(self):
+ """
+ Issues the I{QUIT} command.
+
+ @return: A L{Deferred} that fires when the server acknowledges the
+ I{QUIT} command. The transport should not be disconnected until
+ this L{Deferred} fires.
+ """
+ return self.queueStringCommand('QUIT')
+
+
+
+class FTPFileListProtocol(basic.LineReceiver):
+ """Parser for standard FTP file listings
+
+ This is the evil required to match::
+
+ -rw-r--r-- 1 root other 531 Jan 29 03:26 README
+
+ If you need different evil for a wacky FTP server, you can
+ override either C{fileLinePattern} or C{parseDirectoryLine()}.
+
+ It populates the instance attribute self.files, which is a list containing
+ dicts with the following keys (examples from the above line):
+ - filetype: e.g. 'd' for directories, or '-' for an ordinary file
+ - perms: e.g. 'rw-r--r--'
+ - nlinks: e.g. 1
+ - owner: e.g. 'root'
+ - group: e.g. 'other'
+ - size: e.g. 531
+ - date: e.g. 'Jan 29 03:26'
+ - filename: e.g. 'README'
+ - linktarget: e.g. 'some/file'
+
+ Note that the 'date' value will be formatted differently depending on the
+ date. Check U{http://cr.yp.to/ftp.html} if you really want to try to parse
+ it.
+
+ @ivar files: list of dicts describing the files in this listing
+ """
+ fileLinePattern = re.compile(
+ r'^(?P<filetype>.)(?P<perms>.{9})\s+(?P<nlinks>\d*)\s*'
+ r'(?P<owner>\S+)\s+(?P<group>\S+)\s+(?P<size>\d+)\s+'
+ r'(?P<date>...\s+\d+\s+[\d:]+)\s+(?P<filename>([^ ]|\\ )*?)'
+ r'( -> (?P<linktarget>[^\r]*))?\r?$'
+ )
+ delimiter = '\n'
+
+ def __init__(self):
+ self.files = []
+
+ def lineReceived(self, line):
+ d = self.parseDirectoryLine(line)
+ if d is None:
+ self.unknownLine(line)
+ else:
+ self.addFile(d)
+
+ def parseDirectoryLine(self, line):
+ """Return a dictionary of fields, or None if line cannot be parsed.
+
+ @param line: line of text expected to contain a directory entry
+ @type line: str
+
+ @return: dict
+ """
+ match = self.fileLinePattern.match(line)
+ if match is None:
+ return None
+ else:
+ d = match.groupdict()
+ d['filename'] = d['filename'].replace(r'\ ', ' ')
+ d['nlinks'] = int(d['nlinks'])
+ d['size'] = int(d['size'])
+ if d['linktarget']:
+ d['linktarget'] = d['linktarget'].replace(r'\ ', ' ')
+ return d
+
+ def addFile(self, info):
+ """Append file information dictionary to the list of known files.
+
+ Subclasses can override or extend this method to handle file
+ information differently without affecting the parsing of data
+ from the server.
+
+ @param info: dictionary containing the parsed representation
+ of the file information
+ @type info: dict
+ """
+ self.files.append(info)
+
+ def unknownLine(self, line):
+ """Deal with received lines which could not be parsed as file
+ information.
+
+ Subclasses can override this to perform any special processing
+ needed.
+
+ @param line: unparsable line as received
+ @type line: str
+ """
+ pass
+
+def parsePWDResponse(response):
+ """Returns the path from a response to a PWD command.
+
+ Responses typically look like::
+
+ 257 "/home/andrew" is current directory.
+
+ For this example, I will return C{'/home/andrew'}.
+
+ If I can't find the path, I return C{None}.
+ """
+ match = re.search('"(.*)"', response)
+ if match:
+ return match.groups()[0]
+ else:
+ return None
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/gps/__init__.py b/vendor/Twisted-10.0.0/twisted/protocols/gps/__init__.py
new file mode 100644
index 0000000000..278648c3c2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/gps/__init__.py
@@ -0,0 +1 @@
+"""Global Positioning System protocols."""
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/gps/nmea.py b/vendor/Twisted-10.0.0/twisted/protocols/gps/nmea.py
new file mode 100644
index 0000000000..67cbe6b4ed
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/gps/nmea.py
@@ -0,0 +1,209 @@
+# -*- test-case-name: twisted.test.test_nmea -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""NMEA 0183 implementation
+
+Maintainer: Bob Ippolito
+
+The following NMEA 0183 sentences are currently understood::
+ GPGGA (fix)
+ GPGLL (position)
+ GPRMC (position and time)
+ GPGSA (active satellites)
+
+The following NMEA 0183 sentences require implementation::
+ None really, the others aren't generally useful or implemented in most devices anyhow
+
+Other desired features::
+ - A NMEA 0183 producer to emulate GPS devices (?)
+"""
+
+import operator
+from twisted.protocols import basic
+from twisted.python.compat import reduce
+
+POSFIX_INVALID, POSFIX_SPS, POSFIX_DGPS, POSFIX_PPS = 0, 1, 2, 3
+MODE_AUTO, MODE_FORCED = 'A', 'M'
+MODE_NOFIX, MODE_2D, MODE_3D = 1, 2, 3
+
+class InvalidSentence(Exception):
+ pass
+
+class InvalidChecksum(Exception):
+ pass
+
+class NMEAReceiver(basic.LineReceiver):
+ """This parses most common NMEA-0183 messages, presumably from a serial GPS device at 4800 bps
+ """
+ delimiter = '\r\n'
+ dispatch = {
+ 'GPGGA': 'fix',
+ 'GPGLL': 'position',
+ 'GPGSA': 'activesatellites',
+ 'GPRMC': 'positiontime',
+ 'GPGSV': 'viewsatellites', # not implemented
+ 'GPVTG': 'course', # not implemented
+ 'GPALM': 'almanac', # not implemented
+ 'GPGRS': 'range', # not implemented
+ 'GPGST': 'noise', # not implemented
+ 'GPMSS': 'beacon', # not implemented
+ 'GPZDA': 'time', # not implemented
+ }
+ # generally you may miss the beginning of the first message
+ ignore_invalid_sentence = 1
+ # checksums shouldn't be invalid
+ ignore_checksum_mismatch = 0
+ # ignore unknown sentence types
+ ignore_unknown_sentencetypes = 0
+ # do we want to even bother checking to see if it's from the 20th century?
+ convert_dates_before_y2k = 1
+
+ def lineReceived(self, line):
+ if not line.startswith('$'):
+ if self.ignore_invalid_sentence:
+ return
+ raise InvalidSentence("%r does not begin with $" % (line,))
+ # message is everything between $ and *, checksum is xor of all ASCII values of the message
+ strmessage, checksum = line[1:].strip().split('*')
+ message = strmessage.split(',')
+ sentencetype, message = message[0], message[1:]
+ dispatch = self.dispatch.get(sentencetype, None)
+ if (not dispatch) and (not self.ignore_unknown_sentencetypes):
+ raise InvalidSentence("sentencetype %r" % (sentencetype,))
+ if not self.ignore_checksum_mismatch:
+ checksum, calculated_checksum = int(checksum, 16), reduce(operator.xor, map(ord, strmessage))
+ if checksum != calculated_checksum:
+ raise InvalidChecksum("Given 0x%02X != 0x%02X" % (checksum, calculated_checksum))
+ handler = getattr(self, "handle_%s" % dispatch, None)
+ decoder = getattr(self, "decode_%s" % dispatch, None)
+ if not (dispatch and handler and decoder):
+ # missing dispatch, handler, or decoder
+ return
+ # return handler(*decoder(*message))
+ try:
+ decoded = decoder(*message)
+ except Exception, e:
+ raise InvalidSentence("%r is not a valid %s (%s) sentence" % (line, sentencetype, dispatch))
+ return handler(*decoded)
+
+ def decode_position(self, latitude, ns, longitude, ew, utc, status):
+ latitude, longitude = self._decode_latlon(latitude, ns, longitude, ew)
+ utc = self._decode_utc(utc)
+ if status == 'A':
+ status = 1
+ else:
+ status = 0
+ return (
+ latitude,
+ longitude,
+ utc,
+ status,
+ )
+
+ def decode_positiontime(self, utc, status, latitude, ns, longitude, ew, speed, course, utcdate, magvar, magdir):
+ utc = self._decode_utc(utc)
+ latitude, longitude = self._decode_latlon(latitude, ns, longitude, ew)
+ if speed != '':
+ speed = float(speed)
+ else:
+ speed = None
+ if course != '':
+ course = float(course)
+ else:
+ course = None
+ utcdate = 2000+int(utcdate[4:6]), int(utcdate[2:4]), int(utcdate[0:2])
+ if self.convert_dates_before_y2k and utcdate[0] > 2073:
+ # GPS was invented by the US DoD in 1973, but NMEA uses 2 digit year.
+ # Highly unlikely that we'll be using NMEA or this twisted module in 70 years,
+ # but remotely possible that you'll be using it to play back data from the 20th century.
+ utcdate = (utcdate[0] - 100, utcdate[1], utcdate[2])
+ if magvar != '':
+ magvar = float(magvar)
+ if magdir == 'W':
+ magvar = -magvar
+ else:
+ magvar = None
+ return (
+ latitude,
+ longitude,
+ speed,
+ course,
+ # UTC seconds past utcdate
+ utc,
+ # UTC (year, month, day)
+ utcdate,
+ # None or magnetic variation in degrees (west is negative)
+ magvar,
+ )
+
+ def _decode_utc(self, utc):
+ utc_hh, utc_mm, utc_ss = map(float, (utc[:2], utc[2:4], utc[4:]))
+ return utc_hh * 3600.0 + utc_mm * 60.0 + utc_ss
+
+ def _decode_latlon(self, latitude, ns, longitude, ew):
+ latitude = float(latitude[:2]) + float(latitude[2:])/60.0
+ if ns == 'S':
+ latitude = -latitude
+ longitude = float(longitude[:3]) + float(longitude[3:])/60.0
+ if ew == 'W':
+ longitude = -longitude
+ return (latitude, longitude)
+
+ def decode_activesatellites(self, mode1, mode2, *args):
+ satellites, (pdop, hdop, vdop) = args[:12], map(float, args[12:])
+ satlist = []
+ for n in satellites:
+ if n:
+ satlist.append(int(n))
+ else:
+ satlist.append(None)
+ mode = (mode1, int(mode2))
+ return (
+ # satellite list by channel
+ tuple(satlist),
+ # (MODE_AUTO/MODE_FORCED, MODE_NOFIX/MODE_2DFIX/MODE_3DFIX)
+ mode,
+ # position dilution of precision
+ pdop,
+ # horizontal dilution of precision
+ hdop,
+ # vertical dilution of precision
+ vdop,
+ )
+
+ def decode_fix(self, utc, latitude, ns, longitude, ew, posfix, satellites, hdop, altitude, altitude_units, geoid_separation, geoid_separation_units, dgps_age, dgps_station_id):
+ latitude, longitude = self._decode_latlon(latitude, ns, longitude, ew)
+ utc = self._decode_utc(utc)
+ posfix = int(posfix)
+ satellites = int(satellites)
+ hdop = float(hdop)
+ altitude = (float(altitude), altitude_units)
+ if geoid_separation != '':
+ geoid = (float(geoid_separation), geoid_separation_units)
+ else:
+ geoid = None
+ if dgps_age != '':
+ dgps = (float(dgps_age), dgps_station_id)
+ else:
+ dgps = None
+ return (
+ # seconds since 00:00 UTC
+ utc,
+ # latitude (degrees)
+ latitude,
+ # longitude (degrees)
+ longitude,
+ # position fix status (POSFIX_INVALID, POSFIX_SPS, POSFIX_DGPS, POSFIX_PPS)
+ posfix,
+ # number of satellites used for fix 0 <= satellites <= 12
+ satellites,
+ # horizontal dilution of precision
+ hdop,
+ # None or (altitude according to WGS-84 ellipsoid, units (typically 'M' for meters))
+ altitude,
+ # None or (geoid separation according to WGS-84 ellipsoid, units (typically 'M' for meters))
+ geoid,
+ # (age of dgps data in seconds, dgps station id)
+ dgps,
+ )
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/gps/rockwell.py b/vendor/Twisted-10.0.0/twisted/protocols/gps/rockwell.py
new file mode 100644
index 0000000000..840754376c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/gps/rockwell.py
@@ -0,0 +1,268 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Rockwell Semiconductor Zodiac Serial Protocol
+Coded from official protocol specs (Order No. GPS-25, 09/24/1996, Revision 11)
+
+Maintainer: Bob Ippolito
+
+The following Rockwell Zodiac messages are currently understood::
+ EARTHA\\r\\n (a hack to "turn on" a DeLorme Earthmate)
+ 1000 (Geodesic Position Status Output)
+ 1002 (Channel Summary)
+ 1003 (Visible Satellites)
+ 1011 (Receiver ID)
+
+The following Rockwell Zodiac messages require implementation::
+ None really, the others aren't quite so useful and require bidirectional communication w/ the device
+
+Other desired features::
+ - Compatability with the DeLorme Tripmate and other devices with this chipset (?)
+"""
+
+import struct, operator, math
+from twisted.internet import protocol
+from twisted.python import log
+
+DEBUG = 1
+
+class ZodiacParseError(ValueError):
+ pass
+
+class Zodiac(protocol.Protocol):
+ dispatch = {
+ # Output Messages (* means they get sent by the receiver by default periodically)
+ 1000: 'fix', # *Geodesic Position Status Output
+ 1001: 'ecef', # ECEF Position Status Output
+ 1002: 'channels', # *Channel Summary
+ 1003: 'satellites', # *Visible Satellites
+ 1005: 'dgps', # Differential GPS Status
+ 1007: 'channelmeas', # Channel Measurement
+ 1011: 'id', # *Receiver ID
+ 1012: 'usersettings', # User-Settings Output
+ 1100: 'testresults', # Built-In Test Results
+ 1102: 'meastimemark', # Measurement Time Mark
+ 1108: 'utctimemark', # UTC Time Mark Pulse Output
+ 1130: 'serial', # Serial Port Communication Parameters In Use
+ 1135: 'eepromupdate', # EEPROM Update
+ 1136: 'eepromstatus', # EEPROM Status
+ }
+ # these aren't used for anything yet, just sitting here for reference
+ messages = {
+ # Input Messages
+ 'fix': 1200, # Geodesic Position and Velocity Initialization
+ 'udatum': 1210, # User-Defined Datum Definition
+ 'mdatum': 1211, # Map Datum Select
+ 'smask': 1212, # Satellite Elevation Mask Control
+ 'sselect': 1213, # Satellite Candidate Select
+ 'dgpsc': 1214, # Differential GPS Control
+ 'startc': 1216, # Cold Start Control
+ 'svalid': 1217, # Solution Validity Control
+ 'antenna': 1218, # Antenna Type Select
+ 'altinput': 1219, # User-Entered Altitude Input
+ 'appctl': 1220, # Application Platform Control
+ 'navcfg': 1221, # Nav Configuration
+ 'test': 1300, # Perform Built-In Test Command
+ 'restart': 1303, # Restart Command
+ 'serial': 1330, # Serial Port Communications Parameters
+ 'msgctl': 1331, # Message Protocol Control
+ 'dgpsd': 1351, # Raw DGPS RTCM SC-104 Data
+ }
+ MAX_LENGTH = 296
+ allow_earthmate_hack = 1
+ recvd = ""
+
+ def dataReceived(self, recd):
+ self.recvd = self.recvd + recd
+ while len(self.recvd) >= 10:
+
+ # hack for DeLorme EarthMate
+ if self.recvd[:8] == 'EARTHA\r\n':
+ if self.allow_earthmate_hack:
+ self.allow_earthmate_hack = 0
+ self.transport.write('EARTHA\r\n')
+ self.recvd = self.recvd[8:]
+ continue
+
+ if self.recvd[0:2] != '\xFF\x81':
+ if DEBUG:
+ raise ZodiacParseError('Invalid Sync %r' % self.recvd)
+ else:
+ raise ZodiacParseError
+ sync, msg_id, length, acknak, checksum = struct.unpack('<HHHHh', self.recvd[:10])
+
+ # verify checksum
+ cksum = -(reduce(operator.add, (sync, msg_id, length, acknak)) & 0xFFFF)
+ cksum, = struct.unpack('<h', struct.pack('<h', cksum))
+ if cksum != checksum:
+ if DEBUG:
+ raise ZodiacParseError('Invalid Header Checksum %r != %r %r' % (checksum, cksum, self.recvd[:8]))
+ else:
+ raise ZodiacParseError
+
+ # length was in words, now it's bytes
+ length = length * 2
+
+ # do we need more data ?
+ neededBytes = 10
+ if length:
+ neededBytes += length + 2
+ if len(self.recvd) < neededBytes:
+ break
+
+ if neededBytes > self.MAX_LENGTH:
+ raise ZodiacParseError("Invalid Header??")
+
+ # empty messages pass empty strings
+ message = ''
+
+ # does this message have data ?
+ if length:
+ message, checksum = self.recvd[10:10+length], struct.unpack('<h', self.recvd[10+length:neededBytes])[0]
+ cksum = 0x10000 - (reduce(operator.add, struct.unpack('<%dH' % (length/2), message)) & 0xFFFF)
+ cksum, = struct.unpack('<h', struct.pack('<h', cksum))
+ if cksum != checksum:
+ if DEBUG:
+ log.dmsg('msg_id = %r length = %r' % (msg_id, length), debug=True)
+ raise ZodiacParseError('Invalid Data Checksum %r != %r %r' % (checksum, cksum, message))
+ else:
+ raise ZodiacParseError
+
+ # discard used buffer, dispatch message
+ self.recvd = self.recvd[neededBytes:]
+ self.receivedMessage(msg_id, message, acknak)
+
+ def receivedMessage(self, msg_id, message, acknak):
+ dispatch = self.dispatch.get(msg_id, None)
+ if not dispatch:
+ raise ZodiacParseError('Unknown msg_id = %r' % msg_id)
+ handler = getattr(self, 'handle_%s' % dispatch, None)
+ decoder = getattr(self, 'decode_%s' % dispatch, None)
+ if not (handler and decoder):
+ # missing handler or decoder
+ #if DEBUG:
+ # log.msg('MISSING HANDLER/DECODER PAIR FOR: %r' % (dispatch,), debug=True)
+ return
+ decoded = decoder(message)
+ return handler(*decoded)
+
+ def decode_fix(self, message):
+ assert len(message) == 98, "Geodesic Position Status Output should be 55 words total (98 byte message)"
+ (ticks, msgseq, satseq, navstatus, navtype, nmeasure, polar, gpswk, gpses, gpsns, utcdy, utcmo, utcyr, utchr, utcmn, utcsc, utcns, latitude, longitude, height, geoidalsep, speed, course, magvar, climb, mapdatum, exhposerr, exvposerr, extimeerr, exphvelerr, clkbias, clkbiasdev, clkdrift, clkdriftdev) = struct.unpack('<LhhHHHHHLLHHHHHHLlllhLHhhHLLLHllll', message)
+
+ # there's a lot of shit in here..
+ # I'll just snag the important stuff and spit it out like my NMEA decoder
+ utc = (utchr * 3600.0) + (utcmn * 60.0) + utcsc + (float(utcns) * 0.000000001)
+
+ log.msg('utchr, utcmn, utcsc, utcns = ' + repr((utchr, utcmn, utcsc, utcns)), debug=True)
+
+ latitude = float(latitude) * 0.00000180 / math.pi
+ longitude = float(longitude) * 0.00000180 / math.pi
+ posfix = not (navstatus & 0x001c)
+ satellites = nmeasure
+ hdop = float(exhposerr) * 0.01
+ altitude = float(height) * 0.01, 'M'
+ geoid = float(geoidalsep) * 0.01, 'M'
+ dgps = None
+ return (
+ # seconds since 00:00 UTC
+ utc,
+ # latitude (degrees)
+ latitude,
+ # longitude (degrees)
+ longitude,
+ # position fix status (invalid = False, valid = True)
+ posfix,
+ # number of satellites [measurements] used for fix 0 <= satellites <= 12
+ satellites,
+ # horizontal dilution of precision
+ hdop,
+ # (altitude according to WGS-84 ellipsoid, units (always 'M' for meters))
+ altitude,
+ # (geoid separation according to WGS-84 ellipsoid, units (always 'M' for meters))
+ geoid,
+ # None, for compatability w/ NMEA code
+ dgps,
+ )
+
+ def decode_id(self, message):
+ assert len(message) == 106, "Receiver ID Message should be 59 words total (106 byte message)"
+ ticks, msgseq, channels, software_version, software_date, options_list, reserved = struct.unpack('<Lh20s20s20s20s20s', message)
+ channels, software_version, software_date, options_list = map(lambda s: s.split('\0')[0], (channels, software_version, software_date, options_list))
+ software_version = float(software_version)
+ channels = int(channels) # 0-12 .. but ALWAYS 12, so we ignore.
+ options_list = int(options_list[:4], 16) # only two bitflags, others are reserved
+ minimize_rom = (options_list & 0x01) > 0
+ minimize_ram = (options_list & 0x02) > 0
+ # (version info), (options info)
+ return ((software_version, software_date), (minimize_rom, minimize_ram))
+
+ def decode_channels(self, message):
+ assert len(message) == 90, "Channel Summary Message should be 51 words total (90 byte message)"
+ ticks, msgseq, satseq, gpswk, gpsws, gpsns = struct.unpack('<LhhHLL', message[:18])
+ channels = []
+ message = message[18:]
+ for i in range(12):
+ flags, prn, cno = struct.unpack('<HHH', message[6 * i:6 * (i + 1)])
+ # measurement used, ephemeris available, measurement valid, dgps corrections available
+ flags = (flags & 0x01, flags & 0x02, flags & 0x04, flags & 0x08)
+ channels.append((flags, prn, cno))
+ # ((flags, satellite PRN, C/No in dbHz)) for 12 channels
+ # satellite message sequence number
+ # gps week number, gps seconds in week (??), gps nanoseconds from Epoch
+ return (tuple(channels),) #, satseq, (gpswk, gpsws, gpsns))
+
+ def decode_satellites(self, message):
+ assert len(message) == 90, "Visible Satellites Message should be 51 words total (90 byte message)"
+ ticks, msgseq, gdop, pdop, hdop, vdop, tdop, numsatellites = struct.unpack('<LhhhhhhH', message[:18])
+ gdop, pdop, hdop, vdop, tdop = map(lambda n: float(n) * 0.01, (gdop, pdop, hdop, vdop, tdop))
+ satellites = []
+ message = message[18:]
+ for i in range(numsatellites):
+ prn, azi, elev = struct.unpack('<Hhh', message[6 * i:6 * (i + 1)])
+ azi, elev = map(lambda n: (float(n) * 0.0180 / math.pi), (azi, elev))
+ satellites.push((prn, azi, elev))
+ # ((PRN [0, 32], azimuth +=[0.0, 180.0] deg, elevation +-[0.0, 90.0] deg)) satellite info (0-12)
+ # (geometric, position, horizontal, vertical, time) dilution of precision
+ return (tuple(satellites), (gdop, pdop, hdop, vdop, tdop))
+
+ def decode_dgps(self, message):
+ assert len(message) == 38, "Differential GPS Status Message should be 25 words total (38 byte message)"
+ raise NotImplementedError
+
+ def decode_ecef(self, message):
+ assert len(message) == 96, "ECEF Position Status Output Message should be 54 words total (96 byte message)"
+ raise NotImplementedError
+
+ def decode_channelmeas(self, message):
+ assert len(message) == 296, "Channel Measurement Message should be 154 words total (296 byte message)"
+ raise NotImplementedError
+
+ def decode_usersettings(self, message):
+ assert len(message) == 32, "User-Settings Output Message should be 22 words total (32 byte message)"
+ raise NotImplementedError
+
+ def decode_testresults(self, message):
+ assert len(message) == 28, "Built-In Test Results Message should be 20 words total (28 byte message)"
+ raise NotImplementedError
+
+ def decode_meastimemark(self, message):
+ assert len(message) == 494, "Measurement Time Mark Message should be 253 words total (494 byte message)"
+ raise NotImplementedError
+
+ def decode_utctimemark(self, message):
+ assert len(message) == 28, "UTC Time Mark Pulse Output Message should be 20 words total (28 byte message)"
+ raise NotImplementedError
+
+ def decode_serial(self, message):
+ assert len(message) == 30, "Serial Port Communication Paramaters In Use Message should be 21 words total (30 byte message)"
+ raise NotImplementedError
+
+ def decode_eepromupdate(self, message):
+ assert len(message) == 8, "EEPROM Update Message should be 10 words total (8 byte message)"
+ raise NotImplementedError
+
+ def decode_eepromstatus(self, message):
+ assert len(message) == 24, "EEPROM Status Message should be 18 words total (24 byte message)"
+ raise NotImplementedError
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/htb.py b/vendor/Twisted-10.0.0/twisted/protocols/htb.py
new file mode 100644
index 0000000000..2255815b4c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/htb.py
@@ -0,0 +1,269 @@
+# -*- test-case-name: twisted.test.test_htb -*-
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Hierarchical Token Bucket traffic shaping.
+
+Patterned after U{Martin Devera's Hierarchical Token Bucket traffic
+shaper for the Linux kernel<http://luxik.cdi.cz/~devik/qos/htb/>}.
+
+@seealso: U{HTB Linux queuing discipline manual - user guide
+ <http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm>}
+@seealso: U{Token Bucket Filter in Linux Advanced Routing & Traffic Control
+ HOWTO<http://lartc.org/howto/lartc.qdisc.classless.html#AEN682>}
+@author: Kevin Turner
+"""
+
+from __future__ import nested_scopes
+
+__version__ = '$Revision: 1.5 $'[11:-2]
+
+
+# TODO: Investigate whether we should be using os.times()[-1] instead of
+# time.time. time.time, it has been pointed out, can go backwards. Is
+# the same true of os.times?
+from time import time
+from zope.interface import implements, Interface
+
+from twisted.protocols import pcp
+
+
+class Bucket:
+ """Token bucket, or something like it.
+
+ I can hold up to a certain number of tokens, and I drain over time.
+
+ @cvar maxburst: Size of the bucket, in bytes. If None, the bucket is
+ never full.
+ @type maxburst: int
+ @cvar rate: Rate the bucket drains, in bytes per second. If None,
+ the bucket drains instantaneously.
+ @type rate: int
+ """
+
+ maxburst = None
+ rate = None
+
+ _refcount = 0
+
+ def __init__(self, parentBucket=None):
+ self.content = 0
+ self.parentBucket=parentBucket
+ self.lastDrip = time()
+
+ def add(self, amount):
+ """Add tokens to me.
+
+ @param amount: A quanity of tokens to add.
+ @type amount: int
+
+ @returns: The number of tokens that fit.
+ @returntype: int
+ """
+ self.drip()
+ if self.maxburst is None:
+ allowable = amount
+ else:
+ allowable = min(amount, self.maxburst - self.content)
+
+ if self.parentBucket is not None:
+ allowable = self.parentBucket.add(allowable)
+ self.content += allowable
+ return allowable
+
+ def drip(self):
+ """Let some of the bucket drain.
+
+ How much of the bucket drains depends on how long it has been
+ since I was last called.
+
+ @returns: True if I am now empty.
+ @returntype: bool
+ """
+ if self.parentBucket is not None:
+ self.parentBucket.drip()
+
+ if self.rate is None:
+ self.content = 0
+ return True
+ else:
+ now = time()
+ deltaT = now - self.lastDrip
+ self.content = long(max(0, self.content - deltaT * self.rate))
+ self.lastDrip = now
+ return False
+
+
+class IBucketFilter(Interface):
+ def getBucketFor(*somethings, **some_kw):
+ """I'll give you a bucket for something.
+
+ @returntype: L{Bucket}
+ """
+
+class HierarchicalBucketFilter:
+ """I filter things into buckets, and I am nestable.
+
+ @cvar bucketFactory: Class of buckets to make.
+ @type bucketFactory: L{Bucket} class
+ @cvar sweepInterval: Seconds between sweeping out the bucket cache.
+ @type sweepInterval: int
+ """
+
+ implements(IBucketFilter)
+
+ bucketFactory = Bucket
+ sweepInterval = None
+
+ def __init__(self, parentFilter=None):
+ self.buckets = {}
+ self.parentFilter = parentFilter
+ self.lastSweep = time()
+
+ def getBucketFor(self, *a, **kw):
+ """You want a bucket for that? I'll give you a bucket.
+
+ Any parameters are passed on to L{getBucketKey}, from them it
+ decides which bucket you get.
+
+ @returntype: L{Bucket}
+ """
+ if ((self.sweepInterval is not None)
+ and ((time() - self.lastSweep) > self.sweepInterval)):
+ self.sweep()
+
+ if self.parentFilter:
+ parentBucket = self.parentFilter.getBucketFor(self, *a, **kw)
+ else:
+ parentBucket = None
+
+ key = self.getBucketKey(*a, **kw)
+ bucket = self.buckets.get(key)
+ if bucket is None:
+ bucket = self.bucketFactory(parentBucket)
+ self.buckets[key] = bucket
+ return bucket
+
+ def getBucketKey(self, *a, **kw):
+ """I determine who gets which bucket.
+
+ Unless I'm overridden, everything gets the same bucket.
+
+ @returns: something to be used as a key in the bucket cache.
+ """
+ return None
+
+ def sweep(self):
+ """I throw away references to empty buckets."""
+ for key, bucket in self.buckets.items():
+ if (bucket._refcount == 0) and bucket.drip():
+ del self.buckets[key]
+
+ self.lastSweep = time()
+
+
+class FilterByHost(HierarchicalBucketFilter):
+ """A bucket filter with a bucket for each host.
+ """
+ sweepInterval = 60 * 20
+
+ def getBucketKey(self, transport):
+ return transport.getPeer()[1]
+
+
+class FilterByServer(HierarchicalBucketFilter):
+ """A bucket filter with a bucket for each service.
+ """
+ sweepInterval = None
+
+ def getBucketKey(self, transport):
+ return transport.getHost()[2]
+
+
+class ShapedConsumer(pcp.ProducerConsumerProxy):
+ """I wrap a Consumer and shape the rate at which it receives data.
+ """
+ # Providing a Pull interface means I don't have to try to schedule
+ # traffic with callLaters.
+ iAmStreaming = False
+
+ def __init__(self, consumer, bucket):
+ pcp.ProducerConsumerProxy.__init__(self, consumer)
+ self.bucket = bucket
+ self.bucket._refcount += 1
+
+ def _writeSomeData(self, data):
+ # In practice, this actually results in obscene amounts of
+ # overhead, as a result of generating lots and lots of packets
+ # with twelve-byte payloads. We may need to do a version of
+ # this with scheduled writes after all.
+ amount = self.bucket.add(len(data))
+ return pcp.ProducerConsumerProxy._writeSomeData(self, data[:amount])
+
+ def stopProducing(self):
+ pcp.ProducerConsumerProxy.stopProducing(self)
+ self.bucket._refcount -= 1
+
+
+class ShapedTransport(ShapedConsumer):
+ """I wrap a Transport and shape the rate at which it receives data.
+
+ I am a L{ShapedConsumer} with a little bit of magic to provide for
+ the case where the consumer I wrap is also a Transport and people
+ will be attempting to access attributes I do not proxy as a
+ Consumer (e.g. loseConnection).
+ """
+ # Ugh. We only wanted to filter IConsumer, not ITransport.
+
+ iAmStreaming = False
+ def __getattr__(self, name):
+ # Because people will be doing things like .getPeer and
+ # .loseConnection on me.
+ return getattr(self.consumer, name)
+
+
+class ShapedProtocolFactory:
+ """I dispense Protocols with traffic shaping on their transports.
+
+ Usage::
+
+ myserver = SomeFactory()
+ myserver.protocol = ShapedProtocolFactory(myserver.protocol,
+ bucketFilter)
+
+ Where SomeServerFactory is a L{twisted.internet.protocol.Factory}, and
+ bucketFilter is an instance of L{HierarchicalBucketFilter}.
+ """
+ def __init__(self, protoClass, bucketFilter):
+ """Tell me what to wrap and where to get buckets.
+
+ @param protoClass: The class of Protocol I will generate
+ wrapped instances of.
+ @type protoClass: L{Protocol<twisted.internet.interfaces.IProtocol>}
+ class
+ @param bucketFilter: The filter which will determine how
+ traffic is shaped.
+ @type bucketFilter: L{HierarchicalBucketFilter}.
+ """
+ # More precisely, protoClass can be any callable that will return
+ # instances of something that implements IProtocol.
+ self.protocol = protoClass
+ self.bucketFilter = bucketFilter
+
+ def __call__(self, *a, **kw):
+ """Make a Protocol instance with a shaped transport.
+
+ Any parameters will be passed on to the protocol's initializer.
+
+ @returns: a Protocol instance with a L{ShapedTransport}.
+ """
+ proto = self.protocol(*a, **kw)
+ origMakeConnection = proto.makeConnection
+ def makeConnection(transport):
+ bucket = self.bucketFilter.getBucketFor(transport)
+ shapedTransport = ShapedTransport(transport, bucket)
+ return origMakeConnection(shapedTransport)
+ proto.makeConnection = makeConnection
+ return proto
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/ident.py b/vendor/Twisted-10.0.0/twisted/protocols/ident.py
new file mode 100644
index 0000000000..9df8891b28
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/ident.py
@@ -0,0 +1,227 @@
+# -*- test-case-name: twisted.test.test_ident -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Ident protocol implementation.
+
+@author: Jp Calderone
+"""
+
+from __future__ import generators
+
+import struct
+
+from twisted.internet import defer
+from twisted.protocols import basic
+from twisted.python import log, failure
+
+_MIN_PORT = 1
+_MAX_PORT = 2 ** 16 - 1
+
+class IdentError(Exception):
+ """
+ Can't determine connection owner; reason unknown.
+ """
+
+ identDescription = 'UNKNOWN-ERROR'
+
+ def __str__(self):
+ return self.identDescription
+
+
+class NoUser(IdentError):
+ """
+ The connection specified by the port pair is not currently in use or
+ currently not owned by an identifiable entity.
+ """
+ identDescription = 'NO-USER'
+
+
+class InvalidPort(IdentError):
+ """
+ Either the local or foreign port was improperly specified. This should
+ be returned if either or both of the port ids were out of range (TCP
+ port numbers are from 1-65535), negative integers, reals or in any
+ fashion not recognized as a non-negative integer.
+ """
+ identDescription = 'INVALID-PORT'
+
+
+class HiddenUser(IdentError):
+ """
+ The server was able to identify the user of this port, but the
+ information was not returned at the request of the user.
+ """
+ identDescription = 'HIDDEN-USER'
+
+
+class IdentServer(basic.LineOnlyReceiver):
+ """
+ The Identification Protocol (a.k.a., "ident", a.k.a., "the Ident
+ Protocol") provides a means to determine the identity of a user of a
+ particular TCP connection. Given a TCP port number pair, it returns a
+ character string which identifies the owner of that connection on the
+ server's system.
+
+ Server authors should subclass this class and override the lookup method.
+ The default implementation returns an UNKNOWN-ERROR response for every
+ query.
+ """
+
+ def lineReceived(self, line):
+ parts = line.split(',')
+ if len(parts) != 2:
+ self.invalidQuery()
+ else:
+ try:
+ portOnServer, portOnClient = map(int, parts)
+ except ValueError:
+ self.invalidQuery()
+ else:
+ if _MIN_PORT <= portOnServer <= _MAX_PORT and _MIN_PORT <= portOnClient <= _MAX_PORT:
+ self.validQuery(portOnServer, portOnClient)
+ else:
+ self._ebLookup(failure.Failure(InvalidPort()), portOnServer, portOnClient)
+
+ def invalidQuery(self):
+ self.transport.loseConnection()
+
+ def validQuery(self, portOnServer, portOnClient):
+ serverAddr = self.transport.getHost()[1], portOnServer
+ clientAddr = self.transport.getPeer()[1], portOnClient
+ defer.maybeDeferred(self.lookup, serverAddr, clientAddr
+ ).addCallback(self._cbLookup, portOnServer, portOnClient
+ ).addErrback(self._ebLookup, portOnServer, portOnClient
+ )
+
+ def _cbLookup(self, (sysName, userId), sport, cport):
+ self.sendLine('%d, %d : USERID : %s : %s' % (sport, cport, sysName, userId))
+
+ def _ebLookup(self, failure, sport, cport):
+ if failure.check(IdentError):
+ self.sendLine('%d, %d : ERROR : %s' % (sport, cport, failure.value))
+ else:
+ log.err(failure)
+ self.sendLine('%d, %d : ERROR : %s' % (sport, cport, IdentError(failure.value)))
+
+ def lookup(self, serverAddress, clientAddress):
+ """Lookup user information about the specified address pair.
+
+ Return value should be a two-tuple of system name and username.
+ Acceptable values for the system name may be found online at::
+
+ U{http://www.iana.org/assignments/operating-system-names}
+
+ This method may also raise any IdentError subclass (or IdentError
+ itself) to indicate user information will not be provided for the
+ given query.
+
+ A Deferred may also be returned.
+
+ @param serverAddress: A two-tuple representing the server endpoint
+ of the address being queried. The first element is a string holding
+ a dotted-quad IP address. The second element is an integer
+ representing the port.
+
+ @param clientAddress: Like L{serverAddress}, but represents the
+ client endpoint of the address being queried.
+ """
+ raise IdentError()
+
+class ProcServerMixin:
+ """Implements lookup() to grab entries for responses from /proc/net/tcp
+ """
+
+ SYSTEM_NAME = 'LINUX'
+
+ try:
+ from pwd import getpwuid
+ def getUsername(self, uid, getpwuid=getpwuid):
+ return getpwuid(uid)[0]
+ del getpwuid
+ except ImportError:
+ def getUsername(self, uid):
+ raise IdentError()
+
+ def entries(self):
+ f = file('/proc/net/tcp')
+ f.readline()
+ for L in f:
+ yield L.strip()
+
+ def dottedQuadFromHexString(self, hexstr):
+ return '.'.join(map(str, struct.unpack('4B', struct.pack('=L', int(hexstr, 16)))))
+
+ def unpackAddress(self, packed):
+ addr, port = packed.split(':')
+ addr = self.dottedQuadFromHexString(addr)
+ port = int(port, 16)
+ return addr, port
+
+ def parseLine(self, line):
+ parts = line.strip().split()
+ localAddr, localPort = self.unpackAddress(parts[1])
+ remoteAddr, remotePort = self.unpackAddress(parts[2])
+ uid = int(parts[7])
+ return (localAddr, localPort), (remoteAddr, remotePort), uid
+
+ def lookup(self, serverAddress, clientAddress):
+ for ent in self.entries():
+ localAddr, remoteAddr, uid = self.parseLine(ent)
+ if remoteAddr == clientAddress and localAddr[1] == serverAddress[1]:
+ return (self.SYSTEM_NAME, self.getUsername(uid))
+
+ raise NoUser()
+
+
+class IdentClient(basic.LineOnlyReceiver):
+
+ errorTypes = (IdentError, NoUser, InvalidPort, HiddenUser)
+
+ def __init__(self):
+ self.queries = []
+
+ def lookup(self, portOnServer, portOnClient):
+ """Lookup user information about the specified address pair.
+ """
+ self.queries.append((defer.Deferred(), portOnServer, portOnClient))
+ if len(self.queries) > 1:
+ return self.queries[-1][0]
+
+ self.sendLine('%d, %d' % (portOnServer, portOnClient))
+ return self.queries[-1][0]
+
+ def lineReceived(self, line):
+ if not self.queries:
+ log.msg("Unexpected server response: %r" % (line,))
+ else:
+ d, _, _ = self.queries.pop(0)
+ self.parseResponse(d, line)
+ if self.queries:
+ self.sendLine('%d, %d' % (self.queries[0][1], self.queries[0][2]))
+
+ def connectionLost(self, reason):
+ for q in self.queries:
+ q[0].errback(IdentError(reason))
+ self.queries = []
+
+ def parseResponse(self, deferred, line):
+ parts = line.split(':', 2)
+ if len(parts) != 3:
+ deferred.errback(IdentError(line))
+ else:
+ ports, type, addInfo = map(str.strip, parts)
+ if type == 'ERROR':
+ for et in self.errorTypes:
+ if et.identDescription == addInfo:
+ deferred.errback(et(line))
+ return
+ deferred.errback(IdentError(line))
+ else:
+ deferred.callback((type, addInfo))
+
+__all__ = ['IdentError', 'NoUser', 'InvalidPort', 'HiddenUser',
+ 'IdentServer', 'IdentClient',
+ 'ProcServerMixin']
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/loopback.py b/vendor/Twisted-10.0.0/twisted/protocols/loopback.py
new file mode 100644
index 0000000000..62a7a95c41
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/loopback.py
@@ -0,0 +1,397 @@
+# -*- test-case-name: twisted.test.test_loopback -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Testing support for protocols -- loopback between client and server.
+"""
+
+# system imports
+import tempfile
+from zope.interface import implements
+
+# Twisted Imports
+from twisted.protocols import policies
+from twisted.internet import interfaces, protocol, main, defer
+from twisted.internet.task import deferLater
+from twisted.python import failure
+from twisted.internet.interfaces import IAddress
+
+
+class _LoopbackQueue(object):
+ """
+ Trivial wrapper around a list to give it an interface like a queue, which
+ the addition of also sending notifications by way of a Deferred whenever
+ the list has something added to it.
+ """
+
+ _notificationDeferred = None
+ disconnect = False
+
+ def __init__(self):
+ self._queue = []
+
+
+ def put(self, v):
+ self._queue.append(v)
+ if self._notificationDeferred is not None:
+ d, self._notificationDeferred = self._notificationDeferred, None
+ d.callback(None)
+
+
+ def __nonzero__(self):
+ return bool(self._queue)
+
+
+ def get(self):
+ return self._queue.pop(0)
+
+
+
+class _LoopbackAddress(object):
+ implements(IAddress)
+
+
+class _LoopbackTransport(object):
+ implements(interfaces.ITransport, interfaces.IConsumer)
+
+ disconnecting = False
+ producer = None
+
+ # ITransport
+ def __init__(self, q):
+ self.q = q
+
+ def write(self, bytes):
+ self.q.put(bytes)
+
+ def writeSequence(self, iovec):
+ self.q.put(''.join(iovec))
+
+ def loseConnection(self):
+ self.q.disconnect = True
+ self.q.put(None)
+
+ def getPeer(self):
+ return _LoopbackAddress()
+
+ def getHost(self):
+ return _LoopbackAddress()
+
+ # IConsumer
+ def registerProducer(self, producer, streaming):
+ assert self.producer is None
+ self.producer = producer
+ self.streamingProducer = streaming
+ self._pollProducer()
+
+ def unregisterProducer(self):
+ assert self.producer is not None
+ self.producer = None
+
+ def _pollProducer(self):
+ if self.producer is not None and not self.streamingProducer:
+ self.producer.resumeProducing()
+
+
+
+def identityPumpPolicy(queue, target):
+ """
+ L{identityPumpPolicy} is a policy which delivers each chunk of data written
+ to the given queue as-is to the target.
+
+ This isn't a particularly realistic policy.
+
+ @see: L{loopbackAsync}
+ """
+ while queue:
+ bytes = queue.get()
+ if bytes is None:
+ break
+ target.dataReceived(bytes)
+
+
+
+def collapsingPumpPolicy(queue, target):
+ """
+ L{collapsingPumpPolicy} is a policy which collapses all outstanding chunks
+ into a single string and delivers it to the target.
+
+ @see: L{loopbackAsync}
+ """
+ bytes = []
+ while queue:
+ chunk = queue.get()
+ if chunk is None:
+ break
+ bytes.append(chunk)
+ if bytes:
+ target.dataReceived(''.join(bytes))
+
+
+
+def loopbackAsync(server, client, pumpPolicy=identityPumpPolicy):
+ """
+ Establish a connection between C{server} and C{client} then transfer data
+ between them until the connection is closed. This is often useful for
+ testing a protocol.
+
+ @param server: The protocol instance representing the server-side of this
+ connection.
+
+ @param client: The protocol instance representing the client-side of this
+ connection.
+
+ @param pumpPolicy: When either C{server} or C{client} writes to its
+ transport, the string passed in is added to a queue of data for the
+ other protocol. Eventually, C{pumpPolicy} will be called with one such
+ queue and the corresponding protocol object. The pump policy callable
+ is responsible for emptying the queue and passing the strings it
+ contains to the given protocol's C{dataReceived} method. The signature
+ of C{pumpPolicy} is C{(queue, protocol)}. C{queue} is an object with a
+ C{get} method which will return the next string written to the
+ transport, or C{None} if the transport has been disconnected, and which
+ evaluates to C{True} if and only if there are more items to be
+ retrieved via C{get}.
+
+ @return: A L{Deferred} which fires when the connection has been closed and
+ both sides have received notification of this.
+ """
+ serverToClient = _LoopbackQueue()
+ clientToServer = _LoopbackQueue()
+
+ server.makeConnection(_LoopbackTransport(serverToClient))
+ client.makeConnection(_LoopbackTransport(clientToServer))
+
+ return _loopbackAsyncBody(
+ server, serverToClient, client, clientToServer, pumpPolicy)
+
+
+
+def _loopbackAsyncBody(server, serverToClient, client, clientToServer,
+ pumpPolicy):
+ """
+ Transfer bytes from the output queue of each protocol to the input of the other.
+
+ @param server: The protocol instance representing the server-side of this
+ connection.
+
+ @param serverToClient: The L{_LoopbackQueue} holding the server's output.
+
+ @param client: The protocol instance representing the client-side of this
+ connection.
+
+ @param clientToServer: The L{_LoopbackQueue} holding the client's output.
+
+ @param pumpPolicy: See L{loopbackAsync}.
+
+ @return: A L{Deferred} which fires when the connection has been closed and
+ both sides have received notification of this.
+ """
+ def pump(source, q, target):
+ sent = False
+ if q:
+ pumpPolicy(q, target)
+ sent = True
+ if sent and not q:
+ # A write buffer has now been emptied. Give any producer on that
+ # side an opportunity to produce more data.
+ source.transport._pollProducer()
+
+ return sent
+
+ while 1:
+ disconnect = clientSent = serverSent = False
+
+ # Deliver the data which has been written.
+ serverSent = pump(server, serverToClient, client)
+ clientSent = pump(client, clientToServer, server)
+
+ if not clientSent and not serverSent:
+ # Neither side wrote any data. Wait for some new data to be added
+ # before trying to do anything further.
+ d = defer.Deferred()
+ clientToServer._notificationDeferred = d
+ serverToClient._notificationDeferred = d
+ d.addCallback(
+ _loopbackAsyncContinue,
+ server, serverToClient, client, clientToServer, pumpPolicy)
+ return d
+ if serverToClient.disconnect:
+ # The server wants to drop the connection. Flush any remaining
+ # data it has.
+ disconnect = True
+ pump(server, serverToClient, client)
+ elif clientToServer.disconnect:
+ # The client wants to drop the connection. Flush any remaining
+ # data it has.
+ disconnect = True
+ pump(client, clientToServer, server)
+ if disconnect:
+ # Someone wanted to disconnect, so okay, the connection is gone.
+ server.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ client.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ return defer.succeed(None)
+
+
+
+def _loopbackAsyncContinue(ignored, server, serverToClient, client,
+ clientToServer, pumpPolicy):
+ # Clear the Deferred from each message queue, since it has already fired
+ # and cannot be used again.
+ clientToServer._notificationDeferred = None
+ serverToClient._notificationDeferred = None
+
+ # Schedule some more byte-pushing to happen. This isn't done
+ # synchronously because no actual transport can re-enter dataReceived as
+ # a result of calling write, and doing this synchronously could result
+ # in that.
+ from twisted.internet import reactor
+ return deferLater(
+ reactor, 0,
+ _loopbackAsyncBody,
+ server, serverToClient, client, clientToServer, pumpPolicy)
+
+
+
+class LoopbackRelay:
+
+ implements(interfaces.ITransport, interfaces.IConsumer)
+
+ buffer = ''
+ shouldLose = 0
+ disconnecting = 0
+ producer = None
+
+ def __init__(self, target, logFile=None):
+ self.target = target
+ self.logFile = logFile
+
+ def write(self, data):
+ self.buffer = self.buffer + data
+ if self.logFile:
+ self.logFile.write("loopback writing %s\n" % repr(data))
+
+ def writeSequence(self, iovec):
+ self.write("".join(iovec))
+
+ def clearBuffer(self):
+ if self.shouldLose == -1:
+ return
+
+ if self.producer:
+ self.producer.resumeProducing()
+ if self.buffer:
+ if self.logFile:
+ self.logFile.write("loopback receiving %s\n" % repr(self.buffer))
+ buffer = self.buffer
+ self.buffer = ''
+ self.target.dataReceived(buffer)
+ if self.shouldLose == 1:
+ self.shouldLose = -1
+ self.target.connectionLost(failure.Failure(main.CONNECTION_DONE))
+
+ def loseConnection(self):
+ if self.shouldLose != -1:
+ self.shouldLose = 1
+
+ def getHost(self):
+ return 'loopback'
+
+ def getPeer(self):
+ return 'loopback'
+
+ def registerProducer(self, producer, streaming):
+ self.producer = producer
+
+ def unregisterProducer(self):
+ self.producer = None
+
+ def logPrefix(self):
+ return 'Loopback(%r)' % (self.target.__class__.__name__,)
+
+def loopback(server, client, logFile=None):
+ """Run session between server and client.
+ DEPRECATED in Twisted 2.5. Use loopbackAsync instead.
+ """
+ import warnings
+ warnings.warn('loopback() is deprecated (since Twisted 2.5). '
+ 'Use loopbackAsync() instead.',
+ stacklevel=2, category=DeprecationWarning)
+ from twisted.internet import reactor
+ serverToClient = LoopbackRelay(client, logFile)
+ clientToServer = LoopbackRelay(server, logFile)
+ server.makeConnection(serverToClient)
+ client.makeConnection(clientToServer)
+ while 1:
+ reactor.iterate(0.01) # this is to clear any deferreds
+ serverToClient.clearBuffer()
+ clientToServer.clearBuffer()
+ if serverToClient.shouldLose:
+ serverToClient.clearBuffer()
+ server.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ break
+ elif clientToServer.shouldLose:
+ client.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ break
+ reactor.iterate() # last gasp before I go away
+
+
+class LoopbackClientFactory(protocol.ClientFactory):
+
+ def __init__(self, protocol):
+ self.disconnected = 0
+ self.deferred = defer.Deferred()
+ self.protocol = protocol
+
+ def buildProtocol(self, addr):
+ return self.protocol
+
+ def clientConnectionLost(self, connector, reason):
+ self.disconnected = 1
+ self.deferred.callback(None)
+
+
+class _FireOnClose(policies.ProtocolWrapper):
+ def __init__(self, protocol, factory):
+ policies.ProtocolWrapper.__init__(self, protocol, factory)
+ self.deferred = defer.Deferred()
+
+ def connectionLost(self, reason):
+ policies.ProtocolWrapper.connectionLost(self, reason)
+ self.deferred.callback(None)
+
+
+def loopbackTCP(server, client, port=0, noisy=True):
+ """Run session between server and client protocol instances over TCP."""
+ from twisted.internet import reactor
+ f = policies.WrappingFactory(protocol.Factory())
+ serverWrapper = _FireOnClose(f, server)
+ f.noisy = noisy
+ f.buildProtocol = lambda addr: serverWrapper
+ serverPort = reactor.listenTCP(port, f, interface='127.0.0.1')
+ clientF = LoopbackClientFactory(client)
+ clientF.noisy = noisy
+ reactor.connectTCP('127.0.0.1', serverPort.getHost().port, clientF)
+ d = clientF.deferred
+ d.addCallback(lambda x: serverWrapper.deferred)
+ d.addCallback(lambda x: serverPort.stopListening())
+ return d
+
+
+def loopbackUNIX(server, client, noisy=True):
+ """Run session between server and client protocol instances over UNIX socket."""
+ path = tempfile.mktemp()
+ from twisted.internet import reactor
+ f = policies.WrappingFactory(protocol.Factory())
+ serverWrapper = _FireOnClose(f, server)
+ f.noisy = noisy
+ f.buildProtocol = lambda addr: serverWrapper
+ serverPort = reactor.listenUNIX(path, f)
+ clientF = LoopbackClientFactory(client)
+ clientF.noisy = noisy
+ reactor.connectUNIX(path, clientF)
+ d = clientF.deferred
+ d.addCallback(lambda x: serverWrapper.deferred)
+ d.addCallback(lambda x: serverPort.stopListening())
+ return d
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/memcache.py b/vendor/Twisted-10.0.0/twisted/protocols/memcache.py
new file mode 100644
index 0000000000..8167e04c3d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/memcache.py
@@ -0,0 +1,758 @@
+# -*- test-case-name: twisted.test.test_memcache -*-
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Memcache client protocol. Memcached is a caching server, storing data in the
+form of pairs key/value, and memcache is the protocol to talk with it.
+
+To connect to a server, create a factory for L{MemCacheProtocol}::
+
+ from twisted.internet import reactor, protocol
+ from twisted.protocols.memcache import MemCacheProtocol, DEFAULT_PORT
+ d = protocol.ClientCreator(reactor, MemCacheProtocol
+ ).connectTCP("localhost", DEFAULT_PORT)
+ def doSomething(proto):
+ # Here you call the memcache operations
+ return proto.set("mykey", "a lot of data")
+ d.addCallback(doSomething)
+ reactor.run()
+
+All the operations of the memcache protocol are present, but
+L{MemCacheProtocol.set} and L{MemCacheProtocol.get} are the more important.
+
+See U{http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt} for
+more information about the protocol.
+"""
+
+try:
+ from collections import deque
+except ImportError:
+ class deque(list):
+ def popleft(self):
+ return self.pop(0)
+
+
+from twisted.protocols.basic import LineReceiver
+from twisted.protocols.policies import TimeoutMixin
+from twisted.internet.defer import Deferred, fail, TimeoutError
+from twisted.python import log
+
+
+
+DEFAULT_PORT = 11211
+
+
+
+class NoSuchCommand(Exception):
+ """
+ Exception raised when a non existent command is called.
+ """
+
+
+
+class ClientError(Exception):
+ """
+ Error caused by an invalid client call.
+ """
+
+
+
+class ServerError(Exception):
+ """
+ Problem happening on the server.
+ """
+
+
+
+class Command(object):
+ """
+ Wrap a client action into an object, that holds the values used in the
+ protocol.
+
+ @ivar _deferred: the L{Deferred} object that will be fired when the result
+ arrives.
+ @type _deferred: L{Deferred}
+
+ @ivar command: name of the command sent to the server.
+ @type command: C{str}
+ """
+
+ def __init__(self, command, **kwargs):
+ """
+ Create a command.
+
+ @param command: the name of the command.
+ @type command: C{str}
+
+ @param kwargs: this values will be stored as attributes of the object
+ for future use
+ """
+ self.command = command
+ self._deferred = Deferred()
+ for k, v in kwargs.items():
+ setattr(self, k, v)
+
+
+ def success(self, value):
+ """
+ Shortcut method to fire the underlying deferred.
+ """
+ self._deferred.callback(value)
+
+
+ def fail(self, error):
+ """
+ Make the underlying deferred fails.
+ """
+ self._deferred.errback(error)
+
+
+
+class MemCacheProtocol(LineReceiver, TimeoutMixin):
+ """
+ MemCache protocol: connect to a memcached server to store/retrieve values.
+
+ @ivar persistentTimeOut: the timeout period used to wait for a response.
+ @type persistentTimeOut: C{int}
+
+ @ivar _current: current list of requests waiting for an answer from the
+ server.
+ @type _current: C{deque} of L{Command}
+
+ @ivar _lenExpected: amount of data expected in raw mode, when reading for
+ a value.
+ @type _lenExpected: C{int}
+
+ @ivar _getBuffer: current buffer of data, used to store temporary data
+ when reading in raw mode.
+ @type _getBuffer: C{list}
+
+ @ivar _bufferLength: the total amount of bytes in C{_getBuffer}.
+ @type _bufferLength: C{int}
+
+ @ivar _disconnected: indicate if the connectionLost has been called or not.
+ @type _disconnected: C{bool}
+ """
+ MAX_KEY_LENGTH = 250
+ _disconnected = False
+
+ def __init__(self, timeOut=60):
+ """
+ Create the protocol.
+
+ @param timeOut: the timeout to wait before detecting that the
+ connection is dead and close it. It's expressed in seconds.
+ @type timeOut: C{int}
+ """
+ self._current = deque()
+ self._lenExpected = None
+ self._getBuffer = None
+ self._bufferLength = None
+ self.persistentTimeOut = self.timeOut = timeOut
+
+
+ def _cancelCommands(self, reason):
+ """
+ Cancel all the outstanding commands, making them fail with C{reason}.
+ """
+ while self._current:
+ cmd = self._current.popleft()
+ cmd.fail(reason)
+
+
+ def timeoutConnection(self):
+ """
+ Close the connection in case of timeout.
+ """
+ self._cancelCommands(TimeoutError("Connection timeout"))
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ """
+ Cause any outstanding commands to fail.
+ """
+ self._disconnected = True
+ self._cancelCommands(reason)
+ LineReceiver.connectionLost(self, reason)
+
+
+ def sendLine(self, line):
+ """
+ Override sendLine to add a timeout to response.
+ """
+ if not self._current:
+ self.setTimeout(self.persistentTimeOut)
+ LineReceiver.sendLine(self, line)
+
+
+ def rawDataReceived(self, data):
+ """
+ Collect data for a get.
+ """
+ self.resetTimeout()
+ self._getBuffer.append(data)
+ self._bufferLength += len(data)
+ if self._bufferLength >= self._lenExpected + 2:
+ data = "".join(self._getBuffer)
+ buf = data[:self._lenExpected]
+ rem = data[self._lenExpected + 2:]
+ val = buf
+ self._lenExpected = None
+ self._getBuffer = None
+ self._bufferLength = None
+ cmd = self._current[0]
+ if cmd.multiple:
+ flags, cas = cmd.values[cmd.currentKey]
+ cmd.values[cmd.currentKey] = (flags, cas, val)
+ else:
+ cmd.value = val
+ self.setLineMode(rem)
+
+
+ def cmd_STORED(self):
+ """
+ Manage a success response to a set operation.
+ """
+ self._current.popleft().success(True)
+
+
+ def cmd_NOT_STORED(self):
+ """
+ Manage a specific 'not stored' response to a set operation: this is not
+ an error, but some condition wasn't met.
+ """
+ self._current.popleft().success(False)
+
+
+ def cmd_END(self):
+ """
+ This the end token to a get or a stat operation.
+ """
+ cmd = self._current.popleft()
+ if cmd.command == "get":
+ if cmd.multiple:
+ values = dict([(key, val[::2]) for key, val in
+ cmd.values.iteritems()])
+ cmd.success(values)
+ else:
+ cmd.success((cmd.flags, cmd.value))
+ elif cmd.command == "gets":
+ if cmd.multiple:
+ cmd.success(cmd.values)
+ else:
+ cmd.success((cmd.flags, cmd.cas, cmd.value))
+ elif cmd.command == "stats":
+ cmd.success(cmd.values)
+
+
+ def cmd_NOT_FOUND(self):
+ """
+ Manage error response for incr/decr/delete.
+ """
+ self._current.popleft().success(False)
+
+
+ def cmd_VALUE(self, line):
+ """
+ Prepare the reading a value after a get.
+ """
+ cmd = self._current[0]
+ if cmd.command == "get":
+ key, flags, length = line.split()
+ cas = ""
+ else:
+ key, flags, length, cas = line.split()
+ self._lenExpected = int(length)
+ self._getBuffer = []
+ self._bufferLength = 0
+ if cmd.multiple:
+ if key not in cmd.keys:
+ raise RuntimeError("Unexpected commands answer.")
+ cmd.currentKey = key
+ cmd.values[key] = [int(flags), cas]
+ else:
+ if cmd.key != key:
+ raise RuntimeError("Unexpected commands answer.")
+ cmd.flags = int(flags)
+ cmd.cas = cas
+ self.setRawMode()
+
+
+ def cmd_STAT(self, line):
+ """
+ Reception of one stat line.
+ """
+ cmd = self._current[0]
+ key, val = line.split(" ", 1)
+ cmd.values[key] = val
+
+
+ def cmd_VERSION(self, versionData):
+ """
+ Read version token.
+ """
+ self._current.popleft().success(versionData)
+
+
+ def cmd_ERROR(self):
+ """
+ An non-existent command has been sent.
+ """
+ log.err("Non-existent command sent.")
+ cmd = self._current.popleft()
+ cmd.fail(NoSuchCommand())
+
+
+ def cmd_CLIENT_ERROR(self, errText):
+ """
+ An invalid input as been sent.
+ """
+ log.err("Invalid input: %s" % (errText,))
+ cmd = self._current.popleft()
+ cmd.fail(ClientError(errText))
+
+
+ def cmd_SERVER_ERROR(self, errText):
+ """
+ An error has happened server-side.
+ """
+ log.err("Server error: %s" % (errText,))
+ cmd = self._current.popleft()
+ cmd.fail(ServerError(errText))
+
+
+ def cmd_DELETED(self):
+ """
+ A delete command has completed successfully.
+ """
+ self._current.popleft().success(True)
+
+
+ def cmd_OK(self):
+ """
+ The last command has been completed.
+ """
+ self._current.popleft().success(True)
+
+
+ def cmd_EXISTS(self):
+ """
+ A C{checkAndSet} update has failed.
+ """
+ self._current.popleft().success(False)
+
+
+ def lineReceived(self, line):
+ """
+ Receive line commands from the server.
+ """
+ self.resetTimeout()
+ token = line.split(" ", 1)[0]
+ # First manage standard commands without space
+ cmd = getattr(self, "cmd_%s" % (token,), None)
+ if cmd is not None:
+ args = line.split(" ", 1)[1:]
+ if args:
+ cmd(args[0])
+ else:
+ cmd()
+ else:
+ # Then manage commands with space in it
+ line = line.replace(" ", "_")
+ cmd = getattr(self, "cmd_%s" % (line,), None)
+ if cmd is not None:
+ cmd()
+ else:
+ # Increment/Decrement response
+ cmd = self._current.popleft()
+ val = int(line)
+ cmd.success(val)
+ if not self._current:
+ # No pending request, remove timeout
+ self.setTimeout(None)
+
+
+ def increment(self, key, val=1):
+ """
+ Increment the value of C{key} by given value (default to 1).
+ C{key} must be consistent with an int. Return the new value.
+
+ @param key: the key to modify.
+ @type key: C{str}
+
+ @param val: the value to increment.
+ @type val: C{int}
+
+ @return: a deferred with will be called back with the new value
+ associated with the key (after the increment).
+ @rtype: L{Deferred}
+ """
+ return self._incrdecr("incr", key, val)
+
+
+ def decrement(self, key, val=1):
+ """
+ Decrement the value of C{key} by given value (default to 1).
+ C{key} must be consistent with an int. Return the new value, coerced to
+ 0 if negative.
+
+ @param key: the key to modify.
+ @type key: C{str}
+
+ @param val: the value to decrement.
+ @type val: C{int}
+
+ @return: a deferred with will be called back with the new value
+ associated with the key (after the decrement).
+ @rtype: L{Deferred}
+ """
+ return self._incrdecr("decr", key, val)
+
+
+ def _incrdecr(self, cmd, key, val):
+ """
+ Internal wrapper for incr/decr.
+ """
+ if self._disconnected:
+ return fail(RuntimeError("not connected"))
+ if not isinstance(key, str):
+ return fail(ClientError(
+ "Invalid type for key: %s, expecting a string" % (type(key),)))
+ if len(key) > self.MAX_KEY_LENGTH:
+ return fail(ClientError("Key too long"))
+ fullcmd = "%s %s %d" % (cmd, key, int(val))
+ self.sendLine(fullcmd)
+ cmdObj = Command(cmd, key=key)
+ self._current.append(cmdObj)
+ return cmdObj._deferred
+
+
+ def replace(self, key, val, flags=0, expireTime=0):
+ """
+ Replace the given C{key}. It must already exist in the server.
+
+ @param key: the key to replace.
+ @type key: C{str}
+
+ @param val: the new value associated with the key.
+ @type val: C{str}
+
+ @param flags: the flags to store with the key.
+ @type flags: C{int}
+
+ @param expireTime: if different from 0, the relative time in seconds
+ when the key will be deleted from the store.
+ @type expireTime: C{int}
+
+ @return: a deferred that will fire with C{True} if the operation has
+ succeeded, and C{False} with the key didn't previously exist.
+ @rtype: L{Deferred}
+ """
+ return self._set("replace", key, val, flags, expireTime, "")
+
+
+ def add(self, key, val, flags=0, expireTime=0):
+ """
+ Add the given C{key}. It must not exist in the server.
+
+ @param key: the key to add.
+ @type key: C{str}
+
+ @param val: the value associated with the key.
+ @type val: C{str}
+
+ @param flags: the flags to store with the key.
+ @type flags: C{int}
+
+ @param expireTime: if different from 0, the relative time in seconds
+ when the key will be deleted from the store.
+ @type expireTime: C{int}
+
+ @return: a deferred that will fire with C{True} if the operation has
+ succeeded, and C{False} with the key already exists.
+ @rtype: L{Deferred}
+ """
+ return self._set("add", key, val, flags, expireTime, "")
+
+
+ def set(self, key, val, flags=0, expireTime=0):
+ """
+ Set the given C{key}.
+
+ @param key: the key to set.
+ @type key: C{str}
+
+ @param val: the value associated with the key.
+ @type val: C{str}
+
+ @param flags: the flags to store with the key.
+ @type flags: C{int}
+
+ @param expireTime: if different from 0, the relative time in seconds
+ when the key will be deleted from the store.
+ @type expireTime: C{int}
+
+ @return: a deferred that will fire with C{True} if the operation has
+ succeeded.
+ @rtype: L{Deferred}
+ """
+ return self._set("set", key, val, flags, expireTime, "")
+
+
+ def checkAndSet(self, key, val, cas, flags=0, expireTime=0):
+ """
+ Change the content of C{key} only if the C{cas} value matches the
+ current one associated with the key. Use this to store a value which
+ hasn't been modified since last time you fetched it.
+
+ @param key: The key to set.
+ @type key: C{str}
+
+ @param val: The value associated with the key.
+ @type val: C{str}
+
+ @param cas: Unique 64-bit value returned by previous call of C{get}.
+ @type cas: C{str}
+
+ @param flags: The flags to store with the key.
+ @type flags: C{int}
+
+ @param expireTime: If different from 0, the relative time in seconds
+ when the key will be deleted from the store.
+ @type expireTime: C{int}
+
+ @return: A deferred that will fire with C{True} if the operation has
+ succeeded, C{False} otherwise.
+ @rtype: L{Deferred}
+ """
+ return self._set("cas", key, val, flags, expireTime, cas)
+
+
+ def _set(self, cmd, key, val, flags, expireTime, cas):
+ """
+ Internal wrapper for setting values.
+ """
+ if self._disconnected:
+ return fail(RuntimeError("not connected"))
+ if not isinstance(key, str):
+ return fail(ClientError(
+ "Invalid type for key: %s, expecting a string" % (type(key),)))
+ if len(key) > self.MAX_KEY_LENGTH:
+ return fail(ClientError("Key too long"))
+ if not isinstance(val, str):
+ return fail(ClientError(
+ "Invalid type for value: %s, expecting a string" %
+ (type(val),)))
+ if cas:
+ cas = " " + cas
+ length = len(val)
+ fullcmd = "%s %s %d %d %d%s" % (
+ cmd, key, flags, expireTime, length, cas)
+ self.sendLine(fullcmd)
+ self.sendLine(val)
+ cmdObj = Command(cmd, key=key, flags=flags, length=length)
+ self._current.append(cmdObj)
+ return cmdObj._deferred
+
+
+ def append(self, key, val):
+ """
+ Append given data to the value of an existing key.
+
+ @param key: The key to modify.
+ @type key: C{str}
+
+ @param val: The value to append to the current value associated with
+ the key.
+ @type val: C{str}
+
+ @return: A deferred that will fire with C{True} if the operation has
+ succeeded, C{False} otherwise.
+ @rtype: L{Deferred}
+ """
+ # Even if flags and expTime values are ignored, we have to pass them
+ return self._set("append", key, val, 0, 0, "")
+
+
+ def prepend(self, key, val):
+ """
+ Prepend given data to the value of an existing key.
+
+ @param key: The key to modify.
+ @type key: C{str}
+
+ @param val: The value to prepend to the current value associated with
+ the key.
+ @type val: C{str}
+
+ @return: A deferred that will fire with C{True} if the operation has
+ succeeded, C{False} otherwise.
+ @rtype: L{Deferred}
+ """
+ # Even if flags and expTime values are ignored, we have to pass them
+ return self._set("prepend", key, val, 0, 0, "")
+
+
+ def get(self, key, withIdentifier=False):
+ """
+ Get the given C{key}. It doesn't support multiple keys. If
+ C{withIdentifier} is set to C{True}, the command issued is a C{gets},
+ that will return the current identifier associated with the value. This
+ identifier has to be used when issuing C{checkAndSet} update later,
+ using the corresponding method.
+
+ @param key: The key to retrieve.
+ @type key: C{str}
+
+ @param withIdentifier: If set to C{True}, retrieve the current
+ identifier along with the value and the flags.
+ @type withIdentifier: C{bool}
+
+ @return: A deferred that will fire with the tuple (flags, value) if
+ C{withIdentifier} is C{False}, or (flags, cas identifier, value)
+ if C{True}. If the server indicates there is no value
+ associated with C{key}, the returned value will be C{None} and
+ the returned flags will be C{0}.
+ @rtype: L{Deferred}
+ """
+ return self._get([key], withIdentifier, False)
+
+
+ def getMultiple(self, keys, withIdentifier=False):
+ """
+ Get the given list of C{keys}. If C{withIdentifier} is set to C{True},
+ the command issued is a C{gets}, that will return the identifiers
+ associated with each values. This identifier has to be used when
+ issuing C{checkAndSet} update later, using the corresponding method.
+
+ @param keys: The keys to retrieve.
+ @type keys: C{list} of C{str}
+
+ @param withIdentifier: If set to C{True}, retrieve the identifiers
+ along with the values and the flags.
+ @type withIdentifier: C{bool}
+
+ @return: A deferred that will fire with a dictionary with the elements
+ of C{keys} as keys and the tuples (flags, value) as values if
+ C{withIdentifier} is C{False}, or (flags, cas identifier, value) if
+ C{True}. If the server indicates there is no value associated with
+ C{key}, the returned values will be C{None} and the returned flags
+ will be C{0}.
+ @rtype: L{Deferred}
+
+ @since: 9.0
+ """
+ return self._get(keys, withIdentifier, True)
+
+ def _get(self, keys, withIdentifier, multiple):
+ """
+ Helper method for C{get} and C{getMultiple}.
+ """
+ if self._disconnected:
+ return fail(RuntimeError("not connected"))
+ for key in keys:
+ if not isinstance(key, str):
+ return fail(ClientError(
+ "Invalid type for key: %s, expecting a string" % (type(key),)))
+ if len(key) > self.MAX_KEY_LENGTH:
+ return fail(ClientError("Key too long"))
+ if withIdentifier:
+ cmd = "gets"
+ else:
+ cmd = "get"
+ fullcmd = "%s %s" % (cmd, " ".join(keys))
+ self.sendLine(fullcmd)
+ if multiple:
+ values = dict([(key, (0, "", None)) for key in keys])
+ cmdObj = Command(cmd, keys=keys, values=values, multiple=True)
+ else:
+ cmdObj = Command(cmd, key=keys[0], value=None, flags=0, cas="",
+ multiple=False)
+ self._current.append(cmdObj)
+ return cmdObj._deferred
+
+ def stats(self, arg=None):
+ """
+ Get some stats from the server. It will be available as a dict.
+
+ @param arg: An optional additional string which will be sent along
+ with the I{stats} command. The interpretation of this value by
+ the server is left undefined by the memcache protocol
+ specification.
+ @type arg: L{NoneType} or L{str}
+
+ @return: a deferred that will fire with a C{dict} of the available
+ statistics.
+ @rtype: L{Deferred}
+ """
+ if arg:
+ cmd = "stats " + arg
+ else:
+ cmd = "stats"
+ if self._disconnected:
+ return fail(RuntimeError("not connected"))
+ self.sendLine(cmd)
+ cmdObj = Command("stats", values={})
+ self._current.append(cmdObj)
+ return cmdObj._deferred
+
+
+ def version(self):
+ """
+ Get the version of the server.
+
+ @return: a deferred that will fire with the string value of the
+ version.
+ @rtype: L{Deferred}
+ """
+ if self._disconnected:
+ return fail(RuntimeError("not connected"))
+ self.sendLine("version")
+ cmdObj = Command("version")
+ self._current.append(cmdObj)
+ return cmdObj._deferred
+
+
+ def delete(self, key):
+ """
+ Delete an existing C{key}.
+
+ @param key: the key to delete.
+ @type key: C{str}
+
+ @return: a deferred that will be called back with C{True} if the key
+ was successfully deleted, or C{False} if not.
+ @rtype: L{Deferred}
+ """
+ if self._disconnected:
+ return fail(RuntimeError("not connected"))
+ if not isinstance(key, str):
+ return fail(ClientError(
+ "Invalid type for key: %s, expecting a string" % (type(key),)))
+ self.sendLine("delete %s" % key)
+ cmdObj = Command("delete", key=key)
+ self._current.append(cmdObj)
+ return cmdObj._deferred
+
+
+ def flushAll(self):
+ """
+ Flush all cached values.
+
+ @return: a deferred that will be called back with C{True} when the
+ operation has succeeded.
+ @rtype: L{Deferred}
+ """
+ if self._disconnected:
+ return fail(RuntimeError("not connected"))
+ self.sendLine("flush_all")
+ cmdObj = Command("flush_all")
+ self._current.append(cmdObj)
+ return cmdObj._deferred
+
+
+
+__all__ = ["MemCacheProtocol", "DEFAULT_PORT", "NoSuchCommand", "ClientError",
+ "ServerError"]
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/mice/__init__.py b/vendor/Twisted-10.0.0/twisted/protocols/mice/__init__.py
new file mode 100644
index 0000000000..fda89c5898
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/mice/__init__.py
@@ -0,0 +1 @@
+"""Mice Protocols."""
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/mice/mouseman.py b/vendor/Twisted-10.0.0/twisted/protocols/mice/mouseman.py
new file mode 100644
index 0000000000..d22c48a66e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/mice/mouseman.py
@@ -0,0 +1,127 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""Logictech MouseMan serial protocol.
+
+http://www.softnco.demon.co.uk/SerialMouse.txt
+"""
+
+from twisted.internet import protocol
+
+class MouseMan(protocol.Protocol):
+ """
+
+ Parser for Logitech MouseMan serial mouse protocol (compatible
+ with Microsoft Serial Mouse).
+
+ """
+
+ state = 'initial'
+
+ leftbutton=None
+ rightbutton=None
+ middlebutton=None
+
+ leftold=None
+ rightold=None
+ middleold=None
+
+ horiz=None
+ vert=None
+ horizold=None
+ vertold=None
+
+ def down_left(self):
+ pass
+
+ def up_left(self):
+ pass
+
+ def down_middle(self):
+ pass
+
+ def up_middle(self):
+ pass
+
+ def down_right(self):
+ pass
+
+ def up_right(self):
+ pass
+
+ def move(self, x, y):
+ pass
+
+ horiz=None
+ vert=None
+
+ def state_initial(self, byte):
+ if byte & 1<<6:
+ self.word1=byte
+ self.leftbutton = byte & 1<<5
+ self.rightbutton = byte & 1<<4
+ return 'horiz'
+ else:
+ return 'initial'
+
+ def state_horiz(self, byte):
+ if byte & 1<<6:
+ return self.state_initial(byte)
+ else:
+ x=(self.word1 & 0x03)<<6 | (byte & 0x3f)
+ if x>=128:
+ x=-256+x
+ self.horiz = x
+ return 'vert'
+
+ def state_vert(self, byte):
+ if byte & 1<<6:
+ # short packet
+ return self.state_initial(byte)
+ else:
+ x = (self.word1 & 0x0c)<<4 | (byte & 0x3f)
+ if x>=128:
+ x=-256+x
+ self.vert = x
+ self.snapshot()
+ return 'maybemiddle'
+
+ def state_maybemiddle(self, byte):
+ if byte & 1<<6:
+ self.snapshot()
+ return self.state_initial(byte)
+ else:
+ self.middlebutton=byte & 1<<5
+ self.snapshot()
+ return 'initial'
+
+ def snapshot(self):
+ if self.leftbutton and not self.leftold:
+ self.down_left()
+ self.leftold=1
+ if not self.leftbutton and self.leftold:
+ self.up_left()
+ self.leftold=0
+
+ if self.middlebutton and not self.middleold:
+ self.down_middle()
+ self.middleold=1
+ if not self.middlebutton and self.middleold:
+ self.up_middle()
+ self.middleold=0
+
+ if self.rightbutton and not self.rightold:
+ self.down_right()
+ self.rightold=1
+ if not self.rightbutton and self.rightold:
+ self.up_right()
+ self.rightold=0
+
+ if self.horiz or self.vert:
+ self.move(self.horiz, self.vert)
+
+ def dataReceived(self, data):
+ for c in data:
+ byte = ord(c)
+ self.state = getattr(self, 'state_'+self.state)(byte)
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/pcp.py b/vendor/Twisted-10.0.0/twisted/protocols/pcp.py
new file mode 100644
index 0000000000..7464dac736
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/pcp.py
@@ -0,0 +1,204 @@
+# -*- test-case-name: twisted.test.test_pcp -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Producer-Consumer Proxy.
+"""
+
+from zope.interface import implements
+
+from twisted.internet import interfaces
+
+
+class BasicProducerConsumerProxy:
+ """
+ I can act as a man in the middle between any Producer and Consumer.
+
+ @ivar producer: the Producer I subscribe to.
+ @type producer: L{IProducer<interfaces.IProducer>}
+ @ivar consumer: the Consumer I publish to.
+ @type consumer: L{IConsumer<interfaces.IConsumer>}
+ @ivar paused: As a Producer, am I paused?
+ @type paused: bool
+ """
+ implements(interfaces.IProducer, interfaces.IConsumer)
+
+ consumer = None
+ producer = None
+ producerIsStreaming = None
+ iAmStreaming = True
+ outstandingPull = False
+ paused = False
+ stopped = False
+
+ def __init__(self, consumer):
+ self._buffer = []
+ if consumer is not None:
+ self.consumer = consumer
+ consumer.registerProducer(self, self.iAmStreaming)
+
+ # Producer methods:
+
+ def pauseProducing(self):
+ self.paused = True
+ if self.producer:
+ self.producer.pauseProducing()
+
+ def resumeProducing(self):
+ self.paused = False
+ if self._buffer:
+ # TODO: Check to see if consumer supports writeSeq.
+ self.consumer.write(''.join(self._buffer))
+ self._buffer[:] = []
+ else:
+ if not self.iAmStreaming:
+ self.outstandingPull = True
+
+ if self.producer is not None:
+ self.producer.resumeProducing()
+
+ def stopProducing(self):
+ if self.producer is not None:
+ self.producer.stopProducing()
+ if self.consumer is not None:
+ del self.consumer
+
+ # Consumer methods:
+
+ def write(self, data):
+ if self.paused or (not self.iAmStreaming and not self.outstandingPull):
+ # We could use that fifo queue here.
+ self._buffer.append(data)
+
+ elif self.consumer is not None:
+ self.consumer.write(data)
+ self.outstandingPull = False
+
+ def finish(self):
+ if self.consumer is not None:
+ self.consumer.finish()
+ self.unregisterProducer()
+
+ def registerProducer(self, producer, streaming):
+ self.producer = producer
+ self.producerIsStreaming = streaming
+
+ def unregisterProducer(self):
+ if self.producer is not None:
+ del self.producer
+ del self.producerIsStreaming
+ if self.consumer:
+ self.consumer.unregisterProducer()
+
+ def __repr__(self):
+ return '<%s@%x around %s>' % (self.__class__, id(self), self.consumer)
+
+
+class ProducerConsumerProxy(BasicProducerConsumerProxy):
+ """ProducerConsumerProxy with a finite buffer.
+
+ When my buffer fills up, I have my parent Producer pause until my buffer
+ has room in it again.
+ """
+ # Copies much from abstract.FileDescriptor
+ bufferSize = 2**2**2**2
+
+ producerPaused = False
+ unregistered = False
+
+ def pauseProducing(self):
+ # Does *not* call up to ProducerConsumerProxy to relay the pause
+ # message through to my parent Producer.
+ self.paused = True
+
+ def resumeProducing(self):
+ self.paused = False
+ if self._buffer:
+ data = ''.join(self._buffer)
+ bytesSent = self._writeSomeData(data)
+ if bytesSent < len(data):
+ unsent = data[bytesSent:]
+ assert not self.iAmStreaming, (
+ "Streaming producer did not write all its data.")
+ self._buffer[:] = [unsent]
+ else:
+ self._buffer[:] = []
+ else:
+ bytesSent = 0
+
+ if (self.unregistered and bytesSent and not self._buffer and
+ self.consumer is not None):
+ self.consumer.unregisterProducer()
+
+ if not self.iAmStreaming:
+ self.outstandingPull = not bytesSent
+
+ if self.producer is not None:
+ bytesBuffered = sum([len(s) for s in self._buffer])
+ # TODO: You can see here the potential for high and low
+ # watermarks, where bufferSize would be the high mark when we
+ # ask the upstream producer to pause, and we wouldn't have
+ # it resume again until it hit the low mark. Or if producer
+ # is Pull, maybe we'd like to pull from it as much as necessary
+ # to keep our buffer full to the low mark, so we're never caught
+ # without something to send.
+ if self.producerPaused and (bytesBuffered < self.bufferSize):
+ # Now that our buffer is empty,
+ self.producerPaused = False
+ self.producer.resumeProducing()
+ elif self.outstandingPull:
+ # I did not have any data to write in response to a pull,
+ # so I'd better pull some myself.
+ self.producer.resumeProducing()
+
+ def write(self, data):
+ if self.paused or (not self.iAmStreaming and not self.outstandingPull):
+ # We could use that fifo queue here.
+ self._buffer.append(data)
+
+ elif self.consumer is not None:
+ assert not self._buffer, (
+ "Writing fresh data to consumer before my buffer is empty!")
+ # I'm going to use _writeSomeData here so that there is only one
+ # path to self.consumer.write. But it doesn't actually make sense,
+ # if I am streaming, for some data to not be all data. But maybe I
+ # am not streaming, but I am writing here anyway, because there was
+ # an earlier request for data which was not answered.
+ bytesSent = self._writeSomeData(data)
+ self.outstandingPull = False
+ if not bytesSent == len(data):
+ assert not self.iAmStreaming, (
+ "Streaming producer did not write all its data.")
+ self._buffer.append(data[bytesSent:])
+
+ if (self.producer is not None) and self.producerIsStreaming:
+ bytesBuffered = sum([len(s) for s in self._buffer])
+ if bytesBuffered >= self.bufferSize:
+
+ self.producer.pauseProducing()
+ self.producerPaused = True
+
+ def registerProducer(self, producer, streaming):
+ self.unregistered = False
+ BasicProducerConsumerProxy.registerProducer(self, producer, streaming)
+ if not streaming:
+ producer.resumeProducing()
+
+ def unregisterProducer(self):
+ if self.producer is not None:
+ del self.producer
+ del self.producerIsStreaming
+ self.unregistered = True
+ if self.consumer and not self._buffer:
+ self.consumer.unregisterProducer()
+
+ def _writeSomeData(self, data):
+ """Write as much of this data as possible.
+
+ @returns: The number of bytes written.
+ """
+ if self.consumer is None:
+ return 0
+ self.consumer.write(data)
+ return len(data)
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/policies.py b/vendor/Twisted-10.0.0/twisted/protocols/policies.py
new file mode 100644
index 0000000000..49ec743774
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/policies.py
@@ -0,0 +1,645 @@
+# -*- test-case-name: twisted.test.test_policies -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Resource limiting policies.
+
+@seealso: See also L{twisted.protocols.htb} for rate limiting.
+"""
+
+# system imports
+import sys, operator
+
+from zope.interface import directlyProvides, providedBy
+
+# twisted imports
+from twisted.internet.protocol import ServerFactory, Protocol, ClientFactory
+from twisted.internet import error
+from twisted.python import log
+
+
+class ProtocolWrapper(Protocol):
+ """
+ Wraps protocol instances and acts as their transport as well.
+
+ @ivar wrappedProtocol: An L{IProtocol} provider to which L{IProtocol}
+ method calls onto this L{ProtocolWrapper} will be proxied.
+
+ @ivar factory: The L{WrappingFactory} which created this
+ L{ProtocolWrapper}.
+ """
+
+ disconnecting = 0
+
+ def __init__(self, factory, wrappedProtocol):
+ self.wrappedProtocol = wrappedProtocol
+ self.factory = factory
+
+ def makeConnection(self, transport):
+ """
+ When a connection is made, register this wrapper with its factory,
+ save the real transport, and connect the wrapped protocol to this
+ L{ProtocolWrapper} to intercept any transport calls it makes.
+ """
+ directlyProvides(self, providedBy(transport))
+ Protocol.makeConnection(self, transport)
+ self.factory.registerProtocol(self)
+ self.wrappedProtocol.makeConnection(self)
+
+ # Transport relaying
+
+ def write(self, data):
+ self.transport.write(data)
+
+ def writeSequence(self, data):
+ self.transport.writeSequence(data)
+
+ def loseConnection(self):
+ self.disconnecting = 1
+ self.transport.loseConnection()
+
+ def getPeer(self):
+ return self.transport.getPeer()
+
+ def getHost(self):
+ return self.transport.getHost()
+
+ def registerProducer(self, producer, streaming):
+ self.transport.registerProducer(producer, streaming)
+
+ def unregisterProducer(self):
+ self.transport.unregisterProducer()
+
+ def stopConsuming(self):
+ self.transport.stopConsuming()
+
+ def __getattr__(self, name):
+ return getattr(self.transport, name)
+
+ # Protocol relaying
+
+ def dataReceived(self, data):
+ self.wrappedProtocol.dataReceived(data)
+
+ def connectionLost(self, reason):
+ self.factory.unregisterProtocol(self)
+ self.wrappedProtocol.connectionLost(reason)
+
+
+class WrappingFactory(ClientFactory):
+ """Wraps a factory and its protocols, and keeps track of them."""
+
+ protocol = ProtocolWrapper
+
+ def __init__(self, wrappedFactory):
+ self.wrappedFactory = wrappedFactory
+ self.protocols = {}
+
+ def doStart(self):
+ self.wrappedFactory.doStart()
+ ClientFactory.doStart(self)
+
+ def doStop(self):
+ self.wrappedFactory.doStop()
+ ClientFactory.doStop(self)
+
+ def startedConnecting(self, connector):
+ self.wrappedFactory.startedConnecting(connector)
+
+ def clientConnectionFailed(self, connector, reason):
+ self.wrappedFactory.clientConnectionFailed(connector, reason)
+
+ def clientConnectionLost(self, connector, reason):
+ self.wrappedFactory.clientConnectionLost(connector, reason)
+
+ def buildProtocol(self, addr):
+ return self.protocol(self, self.wrappedFactory.buildProtocol(addr))
+
+ def registerProtocol(self, p):
+ """Called by protocol to register itself."""
+ self.protocols[p] = 1
+
+ def unregisterProtocol(self, p):
+ """Called by protocols when they go away."""
+ del self.protocols[p]
+
+
+class ThrottlingProtocol(ProtocolWrapper):
+ """Protocol for ThrottlingFactory."""
+
+ # wrap API for tracking bandwidth
+
+ def write(self, data):
+ self.factory.registerWritten(len(data))
+ ProtocolWrapper.write(self, data)
+
+ def writeSequence(self, seq):
+ self.factory.registerWritten(reduce(operator.add, map(len, seq)))
+ ProtocolWrapper.writeSequence(self, seq)
+
+ def dataReceived(self, data):
+ self.factory.registerRead(len(data))
+ ProtocolWrapper.dataReceived(self, data)
+
+ def registerProducer(self, producer, streaming):
+ self.producer = producer
+ ProtocolWrapper.registerProducer(self, producer, streaming)
+
+ def unregisterProducer(self):
+ del self.producer
+ ProtocolWrapper.unregisterProducer(self)
+
+
+ def throttleReads(self):
+ self.transport.pauseProducing()
+
+ def unthrottleReads(self):
+ self.transport.resumeProducing()
+
+ def throttleWrites(self):
+ if hasattr(self, "producer"):
+ self.producer.pauseProducing()
+
+ def unthrottleWrites(self):
+ if hasattr(self, "producer"):
+ self.producer.resumeProducing()
+
+
+class ThrottlingFactory(WrappingFactory):
+ """
+ Throttles bandwidth and number of connections.
+
+ Write bandwidth will only be throttled if there is a producer
+ registered.
+ """
+
+ protocol = ThrottlingProtocol
+
+ def __init__(self, wrappedFactory, maxConnectionCount=sys.maxint,
+ readLimit=None, writeLimit=None):
+ WrappingFactory.__init__(self, wrappedFactory)
+ self.connectionCount = 0
+ self.maxConnectionCount = maxConnectionCount
+ self.readLimit = readLimit # max bytes we should read per second
+ self.writeLimit = writeLimit # max bytes we should write per second
+ self.readThisSecond = 0
+ self.writtenThisSecond = 0
+ self.unthrottleReadsID = None
+ self.checkReadBandwidthID = None
+ self.unthrottleWritesID = None
+ self.checkWriteBandwidthID = None
+
+
+ def callLater(self, period, func):
+ """
+ Wrapper around L{reactor.callLater} for test purpose.
+ """
+ from twisted.internet import reactor
+ return reactor.callLater(period, func)
+
+
+ def registerWritten(self, length):
+ """
+ Called by protocol to tell us more bytes were written.
+ """
+ self.writtenThisSecond += length
+
+
+ def registerRead(self, length):
+ """
+ Called by protocol to tell us more bytes were read.
+ """
+ self.readThisSecond += length
+
+
+ def checkReadBandwidth(self):
+ """
+ Checks if we've passed bandwidth limits.
+ """
+ if self.readThisSecond > self.readLimit:
+ self.throttleReads()
+ throttleTime = (float(self.readThisSecond) / self.readLimit) - 1.0
+ self.unthrottleReadsID = self.callLater(throttleTime,
+ self.unthrottleReads)
+ self.readThisSecond = 0
+ self.checkReadBandwidthID = self.callLater(1, self.checkReadBandwidth)
+
+
+ def checkWriteBandwidth(self):
+ if self.writtenThisSecond > self.writeLimit:
+ self.throttleWrites()
+ throttleTime = (float(self.writtenThisSecond) / self.writeLimit) - 1.0
+ self.unthrottleWritesID = self.callLater(throttleTime,
+ self.unthrottleWrites)
+ # reset for next round
+ self.writtenThisSecond = 0
+ self.checkWriteBandwidthID = self.callLater(1, self.checkWriteBandwidth)
+
+
+ def throttleReads(self):
+ """
+ Throttle reads on all protocols.
+ """
+ log.msg("Throttling reads on %s" % self)
+ for p in self.protocols.keys():
+ p.throttleReads()
+
+
+ def unthrottleReads(self):
+ """
+ Stop throttling reads on all protocols.
+ """
+ self.unthrottleReadsID = None
+ log.msg("Stopped throttling reads on %s" % self)
+ for p in self.protocols.keys():
+ p.unthrottleReads()
+
+
+ def throttleWrites(self):
+ """
+ Throttle writes on all protocols.
+ """
+ log.msg("Throttling writes on %s" % self)
+ for p in self.protocols.keys():
+ p.throttleWrites()
+
+
+ def unthrottleWrites(self):
+ """
+ Stop throttling writes on all protocols.
+ """
+ self.unthrottleWritesID = None
+ log.msg("Stopped throttling writes on %s" % self)
+ for p in self.protocols.keys():
+ p.unthrottleWrites()
+
+
+ def buildProtocol(self, addr):
+ if self.connectionCount == 0:
+ if self.readLimit is not None:
+ self.checkReadBandwidth()
+ if self.writeLimit is not None:
+ self.checkWriteBandwidth()
+
+ if self.connectionCount < self.maxConnectionCount:
+ self.connectionCount += 1
+ return WrappingFactory.buildProtocol(self, addr)
+ else:
+ log.msg("Max connection count reached!")
+ return None
+
+
+ def unregisterProtocol(self, p):
+ WrappingFactory.unregisterProtocol(self, p)
+ self.connectionCount -= 1
+ if self.connectionCount == 0:
+ if self.unthrottleReadsID is not None:
+ self.unthrottleReadsID.cancel()
+ if self.checkReadBandwidthID is not None:
+ self.checkReadBandwidthID.cancel()
+ if self.unthrottleWritesID is not None:
+ self.unthrottleWritesID.cancel()
+ if self.checkWriteBandwidthID is not None:
+ self.checkWriteBandwidthID.cancel()
+
+
+
+class SpewingProtocol(ProtocolWrapper):
+ def dataReceived(self, data):
+ log.msg("Received: %r" % data)
+ ProtocolWrapper.dataReceived(self,data)
+
+ def write(self, data):
+ log.msg("Sending: %r" % data)
+ ProtocolWrapper.write(self,data)
+
+
+
+class SpewingFactory(WrappingFactory):
+ protocol = SpewingProtocol
+
+
+
+class LimitConnectionsByPeer(WrappingFactory):
+
+ maxConnectionsPerPeer = 5
+
+ def startFactory(self):
+ self.peerConnections = {}
+
+ def buildProtocol(self, addr):
+ peerHost = addr[0]
+ connectionCount = self.peerConnections.get(peerHost, 0)
+ if connectionCount >= self.maxConnectionsPerPeer:
+ return None
+ self.peerConnections[peerHost] = connectionCount + 1
+ return WrappingFactory.buildProtocol(self, addr)
+
+ def unregisterProtocol(self, p):
+ peerHost = p.getPeer()[1]
+ self.peerConnections[peerHost] -= 1
+ if self.peerConnections[peerHost] == 0:
+ del self.peerConnections[peerHost]
+
+
+class LimitTotalConnectionsFactory(ServerFactory):
+ """
+ Factory that limits the number of simultaneous connections.
+
+ @type connectionCount: C{int}
+ @ivar connectionCount: number of current connections.
+ @type connectionLimit: C{int} or C{None}
+ @cvar connectionLimit: maximum number of connections.
+ @type overflowProtocol: L{Protocol} or C{None}
+ @cvar overflowProtocol: Protocol to use for new connections when
+ connectionLimit is exceeded. If C{None} (the default value), excess
+ connections will be closed immediately.
+ """
+ connectionCount = 0
+ connectionLimit = None
+ overflowProtocol = None
+
+ def buildProtocol(self, addr):
+ if (self.connectionLimit is None or
+ self.connectionCount < self.connectionLimit):
+ # Build the normal protocol
+ wrappedProtocol = self.protocol()
+ elif self.overflowProtocol is None:
+ # Just drop the connection
+ return None
+ else:
+ # Too many connections, so build the overflow protocol
+ wrappedProtocol = self.overflowProtocol()
+
+ wrappedProtocol.factory = self
+ protocol = ProtocolWrapper(self, wrappedProtocol)
+ self.connectionCount += 1
+ return protocol
+
+ def registerProtocol(self, p):
+ pass
+
+ def unregisterProtocol(self, p):
+ self.connectionCount -= 1
+
+
+
+class TimeoutProtocol(ProtocolWrapper):
+ """
+ Protocol that automatically disconnects when the connection is idle.
+ """
+
+ def __init__(self, factory, wrappedProtocol, timeoutPeriod):
+ """
+ Constructor.
+
+ @param factory: An L{IFactory}.
+ @param wrappedProtocol: A L{Protocol} to wrapp.
+ @param timeoutPeriod: Number of seconds to wait for activity before
+ timing out.
+ """
+ ProtocolWrapper.__init__(self, factory, wrappedProtocol)
+ self.timeoutCall = None
+ self.setTimeout(timeoutPeriod)
+
+
+ def setTimeout(self, timeoutPeriod=None):
+ """
+ Set a timeout.
+
+ This will cancel any existing timeouts.
+
+ @param timeoutPeriod: If not C{None}, change the timeout period.
+ Otherwise, use the existing value.
+ """
+ self.cancelTimeout()
+ if timeoutPeriod is not None:
+ self.timeoutPeriod = timeoutPeriod
+ self.timeoutCall = self.factory.callLater(self.timeoutPeriod, self.timeoutFunc)
+
+
+ def cancelTimeout(self):
+ """
+ Cancel the timeout.
+
+ If the timeout was already cancelled, this does nothing.
+ """
+ if self.timeoutCall:
+ try:
+ self.timeoutCall.cancel()
+ except error.AlreadyCalled:
+ pass
+ self.timeoutCall = None
+
+
+ def resetTimeout(self):
+ """
+ Reset the timeout, usually because some activity just happened.
+ """
+ if self.timeoutCall:
+ self.timeoutCall.reset(self.timeoutPeriod)
+
+
+ def write(self, data):
+ self.resetTimeout()
+ ProtocolWrapper.write(self, data)
+
+
+ def writeSequence(self, seq):
+ self.resetTimeout()
+ ProtocolWrapper.writeSequence(self, seq)
+
+
+ def dataReceived(self, data):
+ self.resetTimeout()
+ ProtocolWrapper.dataReceived(self, data)
+
+
+ def connectionLost(self, reason):
+ self.cancelTimeout()
+ ProtocolWrapper.connectionLost(self, reason)
+
+
+ def timeoutFunc(self):
+ """
+ This method is called when the timeout is triggered.
+
+ By default it calls L{loseConnection}. Override this if you want
+ something else to happen.
+ """
+ self.loseConnection()
+
+
+
+class TimeoutFactory(WrappingFactory):
+ """
+ Factory for TimeoutWrapper.
+ """
+ protocol = TimeoutProtocol
+
+
+ def __init__(self, wrappedFactory, timeoutPeriod=30*60):
+ self.timeoutPeriod = timeoutPeriod
+ WrappingFactory.__init__(self, wrappedFactory)
+
+
+ def buildProtocol(self, addr):
+ return self.protocol(self, self.wrappedFactory.buildProtocol(addr),
+ timeoutPeriod=self.timeoutPeriod)
+
+
+ def callLater(self, period, func):
+ """
+ Wrapper around L{reactor.callLater} for test purpose.
+ """
+ from twisted.internet import reactor
+ return reactor.callLater(period, func)
+
+
+
+class TrafficLoggingProtocol(ProtocolWrapper):
+
+ def __init__(self, factory, wrappedProtocol, logfile, lengthLimit=None,
+ number=0):
+ """
+ @param factory: factory which created this protocol.
+ @type factory: C{protocol.Factory}.
+ @param wrappedProtocol: the underlying protocol.
+ @type wrappedProtocol: C{protocol.Protocol}.
+ @param logfile: file opened for writing used to write log messages.
+ @type logfile: C{file}
+ @param lengthLimit: maximum size of the datareceived logged.
+ @type lengthLimit: C{int}
+ @param number: identifier of the connection.
+ @type number: C{int}.
+ """
+ ProtocolWrapper.__init__(self, factory, wrappedProtocol)
+ self.logfile = logfile
+ self.lengthLimit = lengthLimit
+ self._number = number
+
+
+ def _log(self, line):
+ self.logfile.write(line + '\n')
+ self.logfile.flush()
+
+
+ def _mungeData(self, data):
+ if self.lengthLimit and len(data) > self.lengthLimit:
+ data = data[:self.lengthLimit - 12] + '<... elided>'
+ return data
+
+
+ # IProtocol
+ def connectionMade(self):
+ self._log('*')
+ return ProtocolWrapper.connectionMade(self)
+
+
+ def dataReceived(self, data):
+ self._log('C %d: %r' % (self._number, self._mungeData(data)))
+ return ProtocolWrapper.dataReceived(self, data)
+
+
+ def connectionLost(self, reason):
+ self._log('C %d: %r' % (self._number, reason))
+ return ProtocolWrapper.connectionLost(self, reason)
+
+
+ # ITransport
+ def write(self, data):
+ self._log('S %d: %r' % (self._number, self._mungeData(data)))
+ return ProtocolWrapper.write(self, data)
+
+
+ def writeSequence(self, iovec):
+ self._log('SV %d: %r' % (self._number, [self._mungeData(d) for d in iovec]))
+ return ProtocolWrapper.writeSequence(self, iovec)
+
+
+ def loseConnection(self):
+ self._log('S %d: *' % (self._number,))
+ return ProtocolWrapper.loseConnection(self)
+
+
+
+class TrafficLoggingFactory(WrappingFactory):
+ protocol = TrafficLoggingProtocol
+
+ _counter = 0
+
+ def __init__(self, wrappedFactory, logfilePrefix, lengthLimit=None):
+ self.logfilePrefix = logfilePrefix
+ self.lengthLimit = lengthLimit
+ WrappingFactory.__init__(self, wrappedFactory)
+
+
+ def open(self, name):
+ return file(name, 'w')
+
+
+ def buildProtocol(self, addr):
+ self._counter += 1
+ logfile = self.open(self.logfilePrefix + '-' + str(self._counter))
+ return self.protocol(self, self.wrappedFactory.buildProtocol(addr),
+ logfile, self.lengthLimit, self._counter)
+
+
+ def resetCounter(self):
+ """
+ Reset the value of the counter used to identify connections.
+ """
+ self._counter = 0
+
+
+
+class TimeoutMixin:
+ """Mixin for protocols which wish to timeout connections
+
+ @cvar timeOut: The number of seconds after which to timeout the connection.
+ """
+ timeOut = None
+
+ __timeoutCall = None
+
+ def callLater(self, period, func):
+ from twisted.internet import reactor
+ return reactor.callLater(period, func)
+
+
+ def resetTimeout(self):
+ """Reset the timeout count down"""
+ if self.__timeoutCall is not None and self.timeOut is not None:
+ self.__timeoutCall.reset(self.timeOut)
+
+ def setTimeout(self, period):
+ """Change the timeout period
+
+ @type period: C{int} or C{NoneType}
+ @param period: The period, in seconds, to change the timeout to, or
+ C{None} to disable the timeout.
+ """
+ prev = self.timeOut
+ self.timeOut = period
+
+ if self.__timeoutCall is not None:
+ if period is None:
+ self.__timeoutCall.cancel()
+ self.__timeoutCall = None
+ else:
+ self.__timeoutCall.reset(period)
+ elif period is not None:
+ self.__timeoutCall = self.callLater(period, self.__timedOut)
+
+ return prev
+
+ def __timedOut(self):
+ self.__timeoutCall = None
+ self.timeoutConnection()
+
+ def timeoutConnection(self):
+ """Called when the connection times out.
+ Override to define behavior other than dropping the connection.
+ """
+ self.transport.loseConnection()
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/portforward.py b/vendor/Twisted-10.0.0/twisted/protocols/portforward.py
new file mode 100644
index 0000000000..62f2aa372b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/portforward.py
@@ -0,0 +1,76 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A simple port forwarder.
+"""
+
+# Twisted imports
+from twisted.internet import protocol
+from twisted.python import log
+
+class Proxy(protocol.Protocol):
+ noisy = True
+
+ peer = None
+
+ def setPeer(self, peer):
+ self.peer = peer
+
+ def connectionLost(self, reason):
+ if self.peer is not None:
+ self.peer.transport.loseConnection()
+ self.peer = None
+ elif self.noisy:
+ log.msg("Unable to connect to peer: %s" % (reason,))
+
+ def dataReceived(self, data):
+ self.peer.transport.write(data)
+
+class ProxyClient(Proxy):
+ def connectionMade(self):
+ self.peer.setPeer(self)
+ # We're connected, everybody can read to their hearts content.
+ self.peer.transport.resumeProducing()
+
+class ProxyClientFactory(protocol.ClientFactory):
+
+ protocol = ProxyClient
+
+ def setServer(self, server):
+ self.server = server
+
+ def buildProtocol(self, *args, **kw):
+ prot = protocol.ClientFactory.buildProtocol(self, *args, **kw)
+ prot.setPeer(self.server)
+ return prot
+
+ def clientConnectionFailed(self, connector, reason):
+ self.server.transport.loseConnection()
+
+
+class ProxyServer(Proxy):
+
+ clientProtocolFactory = ProxyClientFactory
+
+ def connectionMade(self):
+ # Don't read anything from the connecting client until we have
+ # somewhere to send it to.
+ self.transport.pauseProducing()
+
+ client = self.clientProtocolFactory()
+ client.setServer(self)
+
+ from twisted.internet import reactor
+ reactor.connectTCP(self.factory.host, self.factory.port, client)
+
+
+class ProxyFactory(protocol.Factory):
+ """Factory for port forwarder."""
+
+ protocol = ProxyServer
+
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/postfix.py b/vendor/Twisted-10.0.0/twisted/protocols/postfix.py
new file mode 100644
index 0000000000..73888ea401
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/postfix.py
@@ -0,0 +1,112 @@
+# -*- test-case-name: twisted.test.test_postfix -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Postfix mail transport agent related protocols.
+"""
+
+import sys
+import UserDict
+import urllib
+
+from twisted.protocols import basic
+from twisted.protocols import policies
+from twisted.internet import protocol, defer
+from twisted.python import log
+
+# urllib's quote functions just happen to match
+# the postfix semantics.
+def quote(s):
+ return urllib.quote(s)
+
+def unquote(s):
+ return urllib.unquote(s)
+
+class PostfixTCPMapServer(basic.LineReceiver, policies.TimeoutMixin):
+ """Postfix mail transport agent TCP map protocol implementation.
+
+ Receive requests for data matching given key via lineReceived,
+ asks it's factory for the data with self.factory.get(key), and
+ returns the data to the requester. None means no entry found.
+
+ You can use postfix's postmap to test the map service::
+
+ /usr/sbin/postmap -q KEY tcp:localhost:4242
+
+ """
+
+ timeout = 600
+ delimiter = '\n'
+
+ def connectionMade(self):
+ self.setTimeout(self.timeout)
+
+ def sendCode(self, code, message=''):
+ "Send an SMTP-like code with a message."
+ self.sendLine('%3.3d %s' % (code, message or ''))
+
+ def lineReceived(self, line):
+ self.resetTimeout()
+ try:
+ request, params = line.split(None, 1)
+ except ValueError:
+ request = line
+ params = None
+ try:
+ f = getattr(self, 'do_' + request)
+ except AttributeError:
+ self.sendCode(400, 'unknown command')
+ else:
+ try:
+ f(params)
+ except:
+ self.sendCode(400, 'Command %r failed: %s.' % (request, sys.exc_info()[1]))
+
+ def do_get(self, key):
+ if key is None:
+ self.sendCode(400, 'Command %r takes 1 parameters.' % 'get')
+ else:
+ d = defer.maybeDeferred(self.factory.get, key)
+ d.addCallbacks(self._cbGot, self._cbNot)
+ d.addErrback(log.err)
+
+ def _cbNot(self, fail):
+ self.sendCode(400, fail.getErrorMessage())
+
+ def _cbGot(self, value):
+ if value is None:
+ self.sendCode(500)
+ else:
+ self.sendCode(200, quote(value))
+
+ def do_put(self, keyAndValue):
+ if keyAndValue is None:
+ self.sendCode(400, 'Command %r takes 2 parameters.' % 'put')
+ else:
+ try:
+ key, value = keyAndValue.split(None, 1)
+ except ValueError:
+ self.sendCode(400, 'Command %r takes 2 parameters.' % 'put')
+ else:
+ self.sendCode(500, 'put is not implemented yet.')
+
+
+class PostfixTCPMapDictServerFactory(protocol.ServerFactory,
+ UserDict.UserDict):
+ """An in-memory dictionary factory for PostfixTCPMapServer."""
+
+ protocol = PostfixTCPMapServer
+
+class PostfixTCPMapDeferringDictServerFactory(protocol.ServerFactory):
+ """An in-memory dictionary factory for PostfixTCPMapServer."""
+
+ protocol = PostfixTCPMapServer
+
+ def __init__(self, data=None):
+ self.data = {}
+ if data is not None:
+ self.data.update(data)
+
+ def get(self, key):
+ return defer.succeed(self.data.get(key))
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/shoutcast.py b/vendor/Twisted-10.0.0/twisted/protocols/shoutcast.py
new file mode 100644
index 0000000000..db21fb5a5b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/shoutcast.py
@@ -0,0 +1,111 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Chop up shoutcast stream into MP3s and metadata, if available.
+"""
+
+from twisted.web import http
+from twisted import copyright
+
+
+class ShoutcastClient(http.HTTPClient):
+ """
+ Shoutcast HTTP stream.
+
+ Modes can be 'length', 'meta' and 'mp3'.
+
+ See U{http://www.smackfu.com/stuff/programming/shoutcast.html}
+ for details on the protocol.
+ """
+
+ userAgent = "Twisted Shoutcast client " + copyright.version
+
+ def __init__(self, path="/"):
+ self.path = path
+ self.got_metadata = False
+ self.metaint = None
+ self.metamode = "mp3"
+ self.databuffer = ""
+
+ def connectionMade(self):
+ self.sendCommand("GET", self.path)
+ self.sendHeader("User-Agent", self.userAgent)
+ self.sendHeader("Icy-MetaData", "1")
+ self.endHeaders()
+
+ def lineReceived(self, line):
+ # fix shoutcast crappiness
+ if not self.firstLine and line:
+ if len(line.split(": ", 1)) == 1:
+ line = line.replace(":", ": ", 1)
+ http.HTTPClient.lineReceived(self, line)
+
+ def handleHeader(self, key, value):
+ if key.lower() == 'icy-metaint':
+ self.metaint = int(value)
+ self.got_metadata = True
+
+ def handleEndHeaders(self):
+ # Lets check if we got metadata, and set the
+ # appropriate handleResponsePart method.
+ if self.got_metadata:
+ # if we have metadata, then it has to be parsed out of the data stream
+ self.handleResponsePart = self.handleResponsePart_with_metadata
+ else:
+ # otherwise, all the data is MP3 data
+ self.handleResponsePart = self.gotMP3Data
+
+ def handleResponsePart_with_metadata(self, data):
+ self.databuffer += data
+ while self.databuffer:
+ stop = getattr(self, "handle_%s" % self.metamode)()
+ if stop:
+ return
+
+ def handle_length(self):
+ self.remaining = ord(self.databuffer[0]) * 16
+ self.databuffer = self.databuffer[1:]
+ self.metamode = "meta"
+
+ def handle_mp3(self):
+ if len(self.databuffer) > self.metaint:
+ self.gotMP3Data(self.databuffer[:self.metaint])
+ self.databuffer = self.databuffer[self.metaint:]
+ self.metamode = "length"
+ else:
+ return 1
+
+ def handle_meta(self):
+ if len(self.databuffer) >= self.remaining:
+ if self.remaining:
+ data = self.databuffer[:self.remaining]
+ self.gotMetaData(self.parseMetadata(data))
+ self.databuffer = self.databuffer[self.remaining:]
+ self.metamode = "mp3"
+ else:
+ return 1
+
+ def parseMetadata(self, data):
+ meta = []
+ for chunk in data.split(';'):
+ chunk = chunk.strip().replace("\x00", "")
+ if not chunk:
+ continue
+ key, value = chunk.split('=', 1)
+ if value.startswith("'") and value.endswith("'"):
+ value = value[1:-1]
+ meta.append((key, value))
+ return meta
+
+ def gotMetaData(self, metadata):
+ """Called with a list of (key, value) pairs of metadata,
+ if metadata is available on the server.
+
+ Will only be called on non-empty metadata.
+ """
+ raise NotImplementedError, "implement in subclass"
+
+ def gotMP3Data(self, data):
+ """Called with chunk of MP3 data."""
+ raise NotImplementedError, "implement in subclass"
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/sip.py b/vendor/Twisted-10.0.0/twisted/protocols/sip.py
new file mode 100644
index 0000000000..07d10a9548
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/sip.py
@@ -0,0 +1,1334 @@
+# -*- test-case-name: twisted.test.test_sip -*-
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Session Initialization Protocol.
+
+Documented in RFC 2543.
+[Superceded by 3261]
+
+
+This module contains a deprecated implementation of HTTP Digest authentication.
+See L{twisted.cred.credentials} and L{twisted.cred._digest} for its new home.
+"""
+
+# system imports
+import socket, time, sys, random, warnings
+from zope.interface import implements, Interface
+
+# twisted imports
+from twisted.python import log, util
+from twisted.python.deprecate import deprecated
+from twisted.python.versions import Version
+from twisted.python.hashlib import md5
+from twisted.internet import protocol, defer, reactor
+
+from twisted import cred
+import twisted.cred.error
+from twisted.cred.credentials import UsernameHashedPassword, UsernamePassword
+
+
+# sibling imports
+from twisted.protocols import basic
+
+PORT = 5060
+
+# SIP headers have short forms
+shortHeaders = {"call-id": "i",
+ "contact": "m",
+ "content-encoding": "e",
+ "content-length": "l",
+ "content-type": "c",
+ "from": "f",
+ "subject": "s",
+ "to": "t",
+ "via": "v",
+ }
+
+longHeaders = {}
+for k, v in shortHeaders.items():
+ longHeaders[v] = k
+del k, v
+
+statusCodes = {
+ 100: "Trying",
+ 180: "Ringing",
+ 181: "Call Is Being Forwarded",
+ 182: "Queued",
+ 183: "Session Progress",
+
+ 200: "OK",
+
+ 300: "Multiple Choices",
+ 301: "Moved Permanently",
+ 302: "Moved Temporarily",
+ 303: "See Other",
+ 305: "Use Proxy",
+ 380: "Alternative Service",
+
+ 400: "Bad Request",
+ 401: "Unauthorized",
+ 402: "Payment Required",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 406: "Not Acceptable",
+ 407: "Proxy Authentication Required",
+ 408: "Request Timeout",
+ 409: "Conflict", # Not in RFC3261
+ 410: "Gone",
+ 411: "Length Required", # Not in RFC3261
+ 413: "Request Entity Too Large",
+ 414: "Request-URI Too Large",
+ 415: "Unsupported Media Type",
+ 416: "Unsupported URI Scheme",
+ 420: "Bad Extension",
+ 421: "Extension Required",
+ 423: "Interval Too Brief",
+ 480: "Temporarily Unavailable",
+ 481: "Call/Transaction Does Not Exist",
+ 482: "Loop Detected",
+ 483: "Too Many Hops",
+ 484: "Address Incomplete",
+ 485: "Ambiguous",
+ 486: "Busy Here",
+ 487: "Request Terminated",
+ 488: "Not Acceptable Here",
+ 491: "Request Pending",
+ 493: "Undecipherable",
+
+ 500: "Internal Server Error",
+ 501: "Not Implemented",
+ 502: "Bad Gateway", # no donut
+ 503: "Service Unavailable",
+ 504: "Server Time-out",
+ 505: "SIP Version not supported",
+ 513: "Message Too Large",
+
+ 600: "Busy Everywhere",
+ 603: "Decline",
+ 604: "Does not exist anywhere",
+ 606: "Not Acceptable",
+}
+
+specialCases = {
+ 'cseq': 'CSeq',
+ 'call-id': 'Call-ID',
+ 'www-authenticate': 'WWW-Authenticate',
+}
+
+
+def dashCapitalize(s):
+ ''' Capitalize a string, making sure to treat - as a word seperator '''
+ return '-'.join([ x.capitalize() for x in s.split('-')])
+
+def unq(s):
+ if s[0] == s[-1] == '"':
+ return s[1:-1]
+ return s
+
+def DigestCalcHA1(
+ pszAlg,
+ pszUserName,
+ pszRealm,
+ pszPassword,
+ pszNonce,
+ pszCNonce,
+):
+ m = md5()
+ m.update(pszUserName)
+ m.update(":")
+ m.update(pszRealm)
+ m.update(":")
+ m.update(pszPassword)
+ HA1 = m.digest()
+ if pszAlg == "md5-sess":
+ m = md5()
+ m.update(HA1)
+ m.update(":")
+ m.update(pszNonce)
+ m.update(":")
+ m.update(pszCNonce)
+ HA1 = m.digest()
+ return HA1.encode('hex')
+
+
+DigestCalcHA1 = deprecated(Version("Twisted", 9, 0, 0))(DigestCalcHA1)
+
+def DigestCalcResponse(
+ HA1,
+ pszNonce,
+ pszNonceCount,
+ pszCNonce,
+ pszQop,
+ pszMethod,
+ pszDigestUri,
+ pszHEntity,
+):
+ m = md5()
+ m.update(pszMethod)
+ m.update(":")
+ m.update(pszDigestUri)
+ if pszQop == "auth-int":
+ m.update(":")
+ m.update(pszHEntity)
+ HA2 = m.digest().encode('hex')
+
+ m = md5()
+ m.update(HA1)
+ m.update(":")
+ m.update(pszNonce)
+ m.update(":")
+ if pszNonceCount and pszCNonce: # pszQop:
+ m.update(pszNonceCount)
+ m.update(":")
+ m.update(pszCNonce)
+ m.update(":")
+ m.update(pszQop)
+ m.update(":")
+ m.update(HA2)
+ hash = m.digest().encode('hex')
+ return hash
+
+
+DigestCalcResponse = deprecated(Version("Twisted", 9, 0, 0))(DigestCalcResponse)
+
+_absent = object()
+
+class Via(object):
+ """
+ A L{Via} is a SIP Via header, representing a segment of the path taken by
+ the request.
+
+ See RFC 3261, sections 8.1.1.7, 18.2.2, and 20.42.
+
+ @ivar transport: Network protocol used for this leg. (Probably either "TCP"
+ or "UDP".)
+ @type transport: C{str}
+ @ivar branch: Unique identifier for this request.
+ @type branch: C{str}
+ @ivar host: Hostname or IP for this leg.
+ @type host: C{str}
+ @ivar port: Port used for this leg.
+ @type port C{int}, or None.
+ @ivar rportRequested: Whether to request RFC 3581 client processing or not.
+ @type rportRequested: C{bool}
+ @ivar rportValue: Servers wishing to honor requests for RFC 3581 processing
+ should set this parameter to the source port the request was received
+ from.
+ @type rportValue: C{int}, or None.
+
+ @ivar ttl: Time-to-live for requests on multicast paths.
+ @type ttl: C{int}, or None.
+ @ivar maddr: The destination multicast address, if any.
+ @type maddr: C{str}, or None.
+ @ivar hidden: Obsolete in SIP 2.0.
+ @type hidden: C{bool}
+ @ivar otherParams: Any other parameters in the header.
+ @type otherParams: C{dict}
+ """
+
+ def __init__(self, host, port=PORT, transport="UDP", ttl=None,
+ hidden=False, received=None, rport=_absent, branch=None,
+ maddr=None, **kw):
+ """
+ Set parameters of this Via header. All arguments correspond to
+ attributes of the same name.
+
+ To maintain compatibility with old SIP
+ code, the 'rport' argument is used to determine the values of
+ C{rportRequested} and C{rportValue}. If None, C{rportRequested} is set
+ to True. (The deprecated method for doing this is to pass True.) If an
+ integer, C{rportValue} is set to the given value.
+
+ Any arguments not explicitly named here are collected into the
+ C{otherParams} dict.
+ """
+ self.transport = transport
+ self.host = host
+ self.port = port
+ self.ttl = ttl
+ self.hidden = hidden
+ self.received = received
+ if rport is True:
+ warnings.warn(
+ "rport=True is deprecated since Twisted 9.0.",
+ DeprecationWarning,
+ stacklevel=2)
+ self.rportValue = None
+ self.rportRequested = True
+ elif rport is None:
+ self.rportValue = None
+ self.rportRequested = True
+ elif rport is _absent:
+ self.rportValue = None
+ self.rportRequested = False
+ else:
+ self.rportValue = rport
+ self.rportRequested = False
+
+ self.branch = branch
+ self.maddr = maddr
+ self.otherParams = kw
+
+
+ def _getrport(self):
+ """
+ Returns the rport value expected by the old SIP code.
+ """
+ if self.rportRequested == True:
+ return True
+ elif self.rportValue is not None:
+ return self.rportValue
+ else:
+ return None
+
+
+ def _setrport(self, newRPort):
+ """
+ L{Base._fixupNAT} sets C{rport} directly, so this method sets
+ C{rportValue} based on that.
+
+ @param newRPort: The new rport value.
+ @type newRPort: C{int}
+ """
+ self.rportValue = newRPort
+ self.rportRequested = False
+
+
+ rport = property(_getrport, _setrport)
+
+ def toString(self):
+ """
+ Serialize this header for use in a request or response.
+ """
+ s = "SIP/2.0/%s %s:%s" % (self.transport, self.host, self.port)
+ if self.hidden:
+ s += ";hidden"
+ for n in "ttl", "branch", "maddr", "received":
+ value = getattr(self, n)
+ if value is not None:
+ s += ";%s=%s" % (n, value)
+ if self.rportRequested:
+ s += ";rport"
+ elif self.rportValue is not None:
+ s += ";rport=%s" % (self.rport,)
+
+ etc = self.otherParams.items()
+ etc.sort()
+ for k, v in etc:
+ if v is None:
+ s += ";" + k
+ else:
+ s += ";%s=%s" % (k, v)
+ return s
+
+
+def parseViaHeader(value):
+ """
+ Parse a Via header.
+
+ @return: The parsed version of this header.
+ @rtype: L{Via}
+ """
+ parts = value.split(";")
+ sent, params = parts[0], parts[1:]
+ protocolinfo, by = sent.split(" ", 1)
+ by = by.strip()
+ result = {}
+ pname, pversion, transport = protocolinfo.split("/")
+ if pname != "SIP" or pversion != "2.0":
+ raise ValueError, "wrong protocol or version: %r" % value
+ result["transport"] = transport
+ if ":" in by:
+ host, port = by.split(":")
+ result["port"] = int(port)
+ result["host"] = host
+ else:
+ result["host"] = by
+ for p in params:
+ # it's the comment-striping dance!
+ p = p.strip().split(" ", 1)
+ if len(p) == 1:
+ p, comment = p[0], ""
+ else:
+ p, comment = p
+ if p == "hidden":
+ result["hidden"] = True
+ continue
+ parts = p.split("=", 1)
+ if len(parts) == 1:
+ name, value = parts[0], None
+ else:
+ name, value = parts
+ if name in ("rport", "ttl"):
+ value = int(value)
+ result[name] = value
+ return Via(**result)
+
+
+class URL:
+ """A SIP URL."""
+
+ def __init__(self, host, username=None, password=None, port=None,
+ transport=None, usertype=None, method=None,
+ ttl=None, maddr=None, tag=None, other=None, headers=None):
+ self.username = username
+ self.host = host
+ self.password = password
+ self.port = port
+ self.transport = transport
+ self.usertype = usertype
+ self.method = method
+ self.tag = tag
+ self.ttl = ttl
+ self.maddr = maddr
+ if other == None:
+ self.other = []
+ else:
+ self.other = other
+ if headers == None:
+ self.headers = {}
+ else:
+ self.headers = headers
+
+ def toString(self):
+ l = []; w = l.append
+ w("sip:")
+ if self.username != None:
+ w(self.username)
+ if self.password != None:
+ w(":%s" % self.password)
+ w("@")
+ w(self.host)
+ if self.port != None:
+ w(":%d" % self.port)
+ if self.usertype != None:
+ w(";user=%s" % self.usertype)
+ for n in ("transport", "ttl", "maddr", "method", "tag"):
+ v = getattr(self, n)
+ if v != None:
+ w(";%s=%s" % (n, v))
+ for v in self.other:
+ w(";%s" % v)
+ if self.headers:
+ w("?")
+ w("&".join([("%s=%s" % (specialCases.get(h) or dashCapitalize(h), v)) for (h, v) in self.headers.items()]))
+ return "".join(l)
+
+ def __str__(self):
+ return self.toString()
+
+ def __repr__(self):
+ return '<URL %s:%s@%s:%r/%s>' % (self.username, self.password, self.host, self.port, self.transport)
+
+
+def parseURL(url, host=None, port=None):
+ """Return string into URL object.
+
+ URIs are of of form 'sip:user@example.com'.
+ """
+ d = {}
+ if not url.startswith("sip:"):
+ raise ValueError("unsupported scheme: " + url[:4])
+ parts = url[4:].split(";")
+ userdomain, params = parts[0], parts[1:]
+ udparts = userdomain.split("@", 1)
+ if len(udparts) == 2:
+ userpass, hostport = udparts
+ upparts = userpass.split(":", 1)
+ if len(upparts) == 1:
+ d["username"] = upparts[0]
+ else:
+ d["username"] = upparts[0]
+ d["password"] = upparts[1]
+ else:
+ hostport = udparts[0]
+ hpparts = hostport.split(":", 1)
+ if len(hpparts) == 1:
+ d["host"] = hpparts[0]
+ else:
+ d["host"] = hpparts[0]
+ d["port"] = int(hpparts[1])
+ if host != None:
+ d["host"] = host
+ if port != None:
+ d["port"] = port
+ for p in params:
+ if p == params[-1] and "?" in p:
+ d["headers"] = h = {}
+ p, headers = p.split("?", 1)
+ for header in headers.split("&"):
+ k, v = header.split("=")
+ h[k] = v
+ nv = p.split("=", 1)
+ if len(nv) == 1:
+ d.setdefault("other", []).append(p)
+ continue
+ name, value = nv
+ if name == "user":
+ d["usertype"] = value
+ elif name in ("transport", "ttl", "maddr", "method", "tag"):
+ if name == "ttl":
+ value = int(value)
+ d[name] = value
+ else:
+ d.setdefault("other", []).append(p)
+ return URL(**d)
+
+
+def cleanRequestURL(url):
+ """Clean a URL from a Request line."""
+ url.transport = None
+ url.maddr = None
+ url.ttl = None
+ url.headers = {}
+
+
+def parseAddress(address, host=None, port=None, clean=0):
+ """Return (name, uri, params) for From/To/Contact header.
+
+ @param clean: remove unnecessary info, usually for From and To headers.
+ """
+ address = address.strip()
+ # simple 'sip:foo' case
+ if address.startswith("sip:"):
+ return "", parseURL(address, host=host, port=port), {}
+ params = {}
+ name, url = address.split("<", 1)
+ name = name.strip()
+ if name.startswith('"'):
+ name = name[1:]
+ if name.endswith('"'):
+ name = name[:-1]
+ url, paramstring = url.split(">", 1)
+ url = parseURL(url, host=host, port=port)
+ paramstring = paramstring.strip()
+ if paramstring:
+ for l in paramstring.split(";"):
+ if not l:
+ continue
+ k, v = l.split("=")
+ params[k] = v
+ if clean:
+ # rfc 2543 6.21
+ url.ttl = None
+ url.headers = {}
+ url.transport = None
+ url.maddr = None
+ return name, url, params
+
+
+class SIPError(Exception):
+ def __init__(self, code, phrase=None):
+ if phrase is None:
+ phrase = statusCodes[code]
+ Exception.__init__(self, "SIP error (%d): %s" % (code, phrase))
+ self.code = code
+ self.phrase = phrase
+
+
+class RegistrationError(SIPError):
+ """Registration was not possible."""
+
+
+class Message:
+ """A SIP message."""
+
+ length = None
+
+ def __init__(self):
+ self.headers = util.OrderedDict() # map name to list of values
+ self.body = ""
+ self.finished = 0
+
+ def addHeader(self, name, value):
+ name = name.lower()
+ name = longHeaders.get(name, name)
+ if name == "content-length":
+ self.length = int(value)
+ self.headers.setdefault(name,[]).append(value)
+
+ def bodyDataReceived(self, data):
+ self.body += data
+
+ def creationFinished(self):
+ if (self.length != None) and (self.length != len(self.body)):
+ raise ValueError, "wrong body length"
+ self.finished = 1
+
+ def toString(self):
+ s = "%s\r\n" % self._getHeaderLine()
+ for n, vs in self.headers.items():
+ for v in vs:
+ s += "%s: %s\r\n" % (specialCases.get(n) or dashCapitalize(n), v)
+ s += "\r\n"
+ s += self.body
+ return s
+
+ def _getHeaderLine(self):
+ raise NotImplementedError
+
+
+class Request(Message):
+ """A Request for a URI"""
+
+
+ def __init__(self, method, uri, version="SIP/2.0"):
+ Message.__init__(self)
+ self.method = method
+ if isinstance(uri, URL):
+ self.uri = uri
+ else:
+ self.uri = parseURL(uri)
+ cleanRequestURL(self.uri)
+
+ def __repr__(self):
+ return "<SIP Request %d:%s %s>" % (id(self), self.method, self.uri.toString())
+
+ def _getHeaderLine(self):
+ return "%s %s SIP/2.0" % (self.method, self.uri.toString())
+
+
+class Response(Message):
+ """A Response to a URI Request"""
+
+ def __init__(self, code, phrase=None, version="SIP/2.0"):
+ Message.__init__(self)
+ self.code = code
+ if phrase == None:
+ phrase = statusCodes[code]
+ self.phrase = phrase
+
+ def __repr__(self):
+ return "<SIP Response %d:%s>" % (id(self), self.code)
+
+ def _getHeaderLine(self):
+ return "SIP/2.0 %s %s" % (self.code, self.phrase)
+
+
+class MessagesParser(basic.LineReceiver):
+ """A SIP messages parser.
+
+ Expects dataReceived, dataDone repeatedly,
+ in that order. Shouldn't be connected to actual transport.
+ """
+
+ version = "SIP/2.0"
+ acceptResponses = 1
+ acceptRequests = 1
+ state = "firstline" # or "headers", "body" or "invalid"
+
+ debug = 0
+
+ def __init__(self, messageReceivedCallback):
+ self.messageReceived = messageReceivedCallback
+ self.reset()
+
+ def reset(self, remainingData=""):
+ self.state = "firstline"
+ self.length = None # body length
+ self.bodyReceived = 0 # how much of the body we received
+ self.message = None
+ self.setLineMode(remainingData)
+
+ def invalidMessage(self):
+ self.state = "invalid"
+ self.setRawMode()
+
+ def dataDone(self):
+ # clear out any buffered data that may be hanging around
+ self.clearLineBuffer()
+ if self.state == "firstline":
+ return
+ if self.state != "body":
+ self.reset()
+ return
+ if self.length == None:
+ # no content-length header, so end of data signals message done
+ self.messageDone()
+ elif self.length < self.bodyReceived:
+ # aborted in the middle
+ self.reset()
+ else:
+ # we have enough data and message wasn't finished? something is wrong
+ raise RuntimeError, "this should never happen"
+
+ def dataReceived(self, data):
+ try:
+ basic.LineReceiver.dataReceived(self, data)
+ except:
+ log.err()
+ self.invalidMessage()
+
+ def handleFirstLine(self, line):
+ """Expected to create self.message."""
+ raise NotImplementedError
+
+ def lineLengthExceeded(self, line):
+ self.invalidMessage()
+
+ def lineReceived(self, line):
+ if self.state == "firstline":
+ while line.startswith("\n") or line.startswith("\r"):
+ line = line[1:]
+ if not line:
+ return
+ try:
+ a, b, c = line.split(" ", 2)
+ except ValueError:
+ self.invalidMessage()
+ return
+ if a == "SIP/2.0" and self.acceptResponses:
+ # response
+ try:
+ code = int(b)
+ except ValueError:
+ self.invalidMessage()
+ return
+ self.message = Response(code, c)
+ elif c == "SIP/2.0" and self.acceptRequests:
+ self.message = Request(a, b)
+ else:
+ self.invalidMessage()
+ return
+ self.state = "headers"
+ return
+ else:
+ assert self.state == "headers"
+ if line:
+ # XXX support multi-line headers
+ try:
+ name, value = line.split(":", 1)
+ except ValueError:
+ self.invalidMessage()
+ return
+ self.message.addHeader(name, value.lstrip())
+ if name.lower() == "content-length":
+ try:
+ self.length = int(value.lstrip())
+ except ValueError:
+ self.invalidMessage()
+ return
+ else:
+ # CRLF, we now have message body until self.length bytes,
+ # or if no length was given, until there is no more data
+ # from the connection sending us data.
+ self.state = "body"
+ if self.length == 0:
+ self.messageDone()
+ return
+ self.setRawMode()
+
+ def messageDone(self, remainingData=""):
+ assert self.state == "body"
+ self.message.creationFinished()
+ self.messageReceived(self.message)
+ self.reset(remainingData)
+
+ def rawDataReceived(self, data):
+ assert self.state in ("body", "invalid")
+ if self.state == "invalid":
+ return
+ if self.length == None:
+ self.message.bodyDataReceived(data)
+ else:
+ dataLen = len(data)
+ expectedLen = self.length - self.bodyReceived
+ if dataLen > expectedLen:
+ self.message.bodyDataReceived(data[:expectedLen])
+ self.messageDone(data[expectedLen:])
+ return
+ else:
+ self.bodyReceived += dataLen
+ self.message.bodyDataReceived(data)
+ if self.bodyReceived == self.length:
+ self.messageDone()
+
+
+class Base(protocol.DatagramProtocol):
+ """Base class for SIP clients and servers."""
+
+ PORT = PORT
+ debug = False
+
+ def __init__(self):
+ self.messages = []
+ self.parser = MessagesParser(self.addMessage)
+
+ def addMessage(self, msg):
+ self.messages.append(msg)
+
+ def datagramReceived(self, data, addr):
+ self.parser.dataReceived(data)
+ self.parser.dataDone()
+ for m in self.messages:
+ self._fixupNAT(m, addr)
+ if self.debug:
+ log.msg("Received %r from %r" % (m.toString(), addr))
+ if isinstance(m, Request):
+ self.handle_request(m, addr)
+ else:
+ self.handle_response(m, addr)
+ self.messages[:] = []
+
+ def _fixupNAT(self, message, (srcHost, srcPort)):
+ # RFC 2543 6.40.2,
+ senderVia = parseViaHeader(message.headers["via"][0])
+ if senderVia.host != srcHost:
+ senderVia.received = srcHost
+ if senderVia.port != srcPort:
+ senderVia.rport = srcPort
+ message.headers["via"][0] = senderVia.toString()
+ elif senderVia.rport == True:
+ senderVia.received = srcHost
+ senderVia.rport = srcPort
+ message.headers["via"][0] = senderVia.toString()
+
+ def deliverResponse(self, responseMessage):
+ """Deliver response.
+
+ Destination is based on topmost Via header."""
+ destVia = parseViaHeader(responseMessage.headers["via"][0])
+ # XXX we don't do multicast yet
+ host = destVia.received or destVia.host
+ port = destVia.rport or destVia.port or self.PORT
+ destAddr = URL(host=host, port=port)
+ self.sendMessage(destAddr, responseMessage)
+
+ def responseFromRequest(self, code, request):
+ """Create a response to a request message."""
+ response = Response(code)
+ for name in ("via", "to", "from", "call-id", "cseq"):
+ response.headers[name] = request.headers.get(name, [])[:]
+
+ return response
+
+ def sendMessage(self, destURL, message):
+ """Send a message.
+
+ @param destURL: C{URL}. This should be a *physical* URL, not a logical one.
+ @param message: The message to send.
+ """
+ if destURL.transport not in ("udp", None):
+ raise RuntimeError, "only UDP currently supported"
+ if self.debug:
+ log.msg("Sending %r to %r" % (message.toString(), destURL))
+ self.transport.write(message.toString(), (destURL.host, destURL.port or self.PORT))
+
+ def handle_request(self, message, addr):
+ """Override to define behavior for requests received
+
+ @type message: C{Message}
+ @type addr: C{tuple}
+ """
+ raise NotImplementedError
+
+ def handle_response(self, message, addr):
+ """Override to define behavior for responses received.
+
+ @type message: C{Message}
+ @type addr: C{tuple}
+ """
+ raise NotImplementedError
+
+
+class IContact(Interface):
+ """A user of a registrar or proxy"""
+
+
+class Registration:
+ def __init__(self, secondsToExpiry, contactURL):
+ self.secondsToExpiry = secondsToExpiry
+ self.contactURL = contactURL
+
+class IRegistry(Interface):
+ """Allows registration of logical->physical URL mapping."""
+
+ def registerAddress(domainURL, logicalURL, physicalURL):
+ """Register the physical address of a logical URL.
+
+ @return: Deferred of C{Registration} or failure with RegistrationError.
+ """
+
+ def unregisterAddress(domainURL, logicalURL, physicalURL):
+ """Unregister the physical address of a logical URL.
+
+ @return: Deferred of C{Registration} or failure with RegistrationError.
+ """
+
+ def getRegistrationInfo(logicalURL):
+ """Get registration info for logical URL.
+
+ @return: Deferred of C{Registration} object or failure of LookupError.
+ """
+
+
+class ILocator(Interface):
+ """Allow looking up physical address for logical URL."""
+
+ def getAddress(logicalURL):
+ """Return physical URL of server for logical URL of user.
+
+ @param logicalURL: a logical C{URL}.
+ @return: Deferred which becomes URL or fails with LookupError.
+ """
+
+
+class Proxy(Base):
+ """SIP proxy."""
+
+ PORT = PORT
+
+ locator = None # object implementing ILocator
+
+ def __init__(self, host=None, port=PORT):
+ """Create new instance.
+
+ @param host: our hostname/IP as set in Via headers.
+ @param port: our port as set in Via headers.
+ """
+ self.host = host or socket.getfqdn()
+ self.port = port
+ Base.__init__(self)
+
+ def getVia(self):
+ """Return value of Via header for this proxy."""
+ return Via(host=self.host, port=self.port)
+
+ def handle_request(self, message, addr):
+ # send immediate 100/trying message before processing
+ #self.deliverResponse(self.responseFromRequest(100, message))
+ f = getattr(self, "handle_%s_request" % message.method, None)
+ if f is None:
+ f = self.handle_request_default
+ try:
+ d = f(message, addr)
+ except SIPError, e:
+ self.deliverResponse(self.responseFromRequest(e.code, message))
+ except:
+ log.err()
+ self.deliverResponse(self.responseFromRequest(500, message))
+ else:
+ if d is not None:
+ d.addErrback(lambda e:
+ self.deliverResponse(self.responseFromRequest(e.code, message))
+ )
+
+ def handle_request_default(self, message, (srcHost, srcPort)):
+ """Default request handler.
+
+ Default behaviour for OPTIONS and unknown methods for proxies
+ is to forward message on to the client.
+
+ Since at the moment we are stateless proxy, thats basically
+ everything.
+ """
+ def _mungContactHeader(uri, message):
+ message.headers['contact'][0] = uri.toString()
+ return self.sendMessage(uri, message)
+
+ viaHeader = self.getVia()
+ if viaHeader.toString() in message.headers["via"]:
+ # must be a loop, so drop message
+ log.msg("Dropping looped message.")
+ return
+
+ message.headers["via"].insert(0, viaHeader.toString())
+ name, uri, tags = parseAddress(message.headers["to"][0], clean=1)
+
+ # this is broken and needs refactoring to use cred
+ d = self.locator.getAddress(uri)
+ d.addCallback(self.sendMessage, message)
+ d.addErrback(self._cantForwardRequest, message)
+
+ def _cantForwardRequest(self, error, message):
+ error.trap(LookupError)
+ del message.headers["via"][0] # this'll be us
+ self.deliverResponse(self.responseFromRequest(404, message))
+
+ def deliverResponse(self, responseMessage):
+ """Deliver response.
+
+ Destination is based on topmost Via header."""
+ destVia = parseViaHeader(responseMessage.headers["via"][0])
+ # XXX we don't do multicast yet
+ host = destVia.received or destVia.host
+ port = destVia.rport or destVia.port or self.PORT
+
+ destAddr = URL(host=host, port=port)
+ self.sendMessage(destAddr, responseMessage)
+
+ def responseFromRequest(self, code, request):
+ """Create a response to a request message."""
+ response = Response(code)
+ for name in ("via", "to", "from", "call-id", "cseq"):
+ response.headers[name] = request.headers.get(name, [])[:]
+ return response
+
+ def handle_response(self, message, addr):
+ """Default response handler."""
+ v = parseViaHeader(message.headers["via"][0])
+ if (v.host, v.port) != (self.host, self.port):
+ # we got a message not intended for us?
+ # XXX note this check breaks if we have multiple external IPs
+ # yay for suck protocols
+ log.msg("Dropping incorrectly addressed message")
+ return
+ del message.headers["via"][0]
+ if not message.headers["via"]:
+ # this message is addressed to us
+ self.gotResponse(message, addr)
+ return
+ self.deliverResponse(message)
+
+ def gotResponse(self, message, addr):
+ """Called with responses that are addressed at this server."""
+ pass
+
+class IAuthorizer(Interface):
+ def getChallenge(peer):
+ """Generate a challenge the client may respond to.
+
+ @type peer: C{tuple}
+ @param peer: The client's address
+
+ @rtype: C{str}
+ @return: The challenge string
+ """
+
+ def decode(response):
+ """Create a credentials object from the given response.
+
+ @type response: C{str}
+ """
+
+class BasicAuthorizer:
+ """Authorizer for insecure Basic (base64-encoded plaintext) authentication.
+
+ This form of authentication is broken and insecure. Do not use it.
+ """
+
+ implements(IAuthorizer)
+
+ def __init__(self):
+ """
+ This method exists solely to issue a deprecation warning.
+ """
+ warnings.warn(
+ "twisted.protocols.sip.BasicAuthorizer was deprecated "
+ "in Twisted 9.0.0",
+ category=DeprecationWarning,
+ stacklevel=2)
+
+
+ def getChallenge(self, peer):
+ return None
+
+ def decode(self, response):
+ # At least one SIP client improperly pads its Base64 encoded messages
+ for i in range(3):
+ try:
+ creds = (response + ('=' * i)).decode('base64')
+ except:
+ pass
+ else:
+ break
+ else:
+ # Totally bogus
+ raise SIPError(400)
+ p = creds.split(':', 1)
+ if len(p) == 2:
+ return UsernamePassword(*p)
+ raise SIPError(400)
+
+
+
+class DigestedCredentials(UsernameHashedPassword):
+ """Yet Another Simple Digest-MD5 authentication scheme"""
+
+ def __init__(self, username, fields, challenges):
+ warnings.warn(
+ "twisted.protocols.sip.DigestedCredentials was deprecated "
+ "in Twisted 9.0.0",
+ category=DeprecationWarning,
+ stacklevel=2)
+ self.username = username
+ self.fields = fields
+ self.challenges = challenges
+
+ def checkPassword(self, password):
+ method = 'REGISTER'
+ response = self.fields.get('response')
+ uri = self.fields.get('uri')
+ nonce = self.fields.get('nonce')
+ cnonce = self.fields.get('cnonce')
+ nc = self.fields.get('nc')
+ algo = self.fields.get('algorithm', 'MD5')
+ qop = self.fields.get('qop-options', 'auth')
+ opaque = self.fields.get('opaque')
+
+ if opaque not in self.challenges:
+ return False
+ del self.challenges[opaque]
+
+ user, domain = self.username.split('@', 1)
+ if uri is None:
+ uri = 'sip:' + domain
+
+ expected = DigestCalcResponse(
+ DigestCalcHA1(algo, user, domain, password, nonce, cnonce),
+ nonce, nc, cnonce, qop, method, uri, None,
+ )
+
+ return expected == response
+
+class DigestAuthorizer:
+ CHALLENGE_LIFETIME = 15
+
+ implements(IAuthorizer)
+
+ def __init__(self):
+ warnings.warn(
+ "twisted.protocols.sip.DigestAuthorizer was deprecated "
+ "in Twisted 9.0.0",
+ category=DeprecationWarning,
+ stacklevel=2)
+
+ self.outstanding = {}
+
+
+
+ def generateNonce(self):
+ c = tuple([random.randrange(sys.maxint) for _ in range(3)])
+ c = '%d%d%d' % c
+ return c
+
+ def generateOpaque(self):
+ return str(random.randrange(sys.maxint))
+
+ def getChallenge(self, peer):
+ c = self.generateNonce()
+ o = self.generateOpaque()
+ self.outstanding[o] = c
+ return ','.join((
+ 'nonce="%s"' % c,
+ 'opaque="%s"' % o,
+ 'qop-options="auth"',
+ 'algorithm="MD5"',
+ ))
+
+ def decode(self, response):
+ response = ' '.join(response.splitlines())
+ parts = response.split(',')
+ auth = dict([(k.strip(), unq(v.strip())) for (k, v) in [p.split('=', 1) for p in parts]])
+ try:
+ username = auth['username']
+ except KeyError:
+ raise SIPError(401)
+ try:
+ return DigestedCredentials(username, auth, self.outstanding)
+ except:
+ raise SIPError(400)
+
+
+class RegisterProxy(Proxy):
+ """A proxy that allows registration for a specific domain.
+
+ Unregistered users won't be handled.
+ """
+
+ portal = None
+
+ registry = None # should implement IRegistry
+
+ authorizers = {}
+
+ def __init__(self, *args, **kw):
+ Proxy.__init__(self, *args, **kw)
+ self.liveChallenges = {}
+ if "digest" not in self.authorizers:
+ self.authorizers["digest"] = DigestAuthorizer()
+
+ def handle_ACK_request(self, message, (host, port)):
+ # XXX
+ # ACKs are a client's way of indicating they got the last message
+ # Responding to them is not a good idea.
+ # However, we should keep track of terminal messages and re-transmit
+ # if no ACK is received.
+ pass
+
+ def handle_REGISTER_request(self, message, (host, port)):
+ """Handle a registration request.
+
+ Currently registration is not proxied.
+ """
+ if self.portal is None:
+ # There is no portal. Let anyone in.
+ self.register(message, host, port)
+ else:
+ # There is a portal. Check for credentials.
+ if not message.headers.has_key("authorization"):
+ return self.unauthorized(message, host, port)
+ else:
+ return self.login(message, host, port)
+
+ def unauthorized(self, message, host, port):
+ m = self.responseFromRequest(401, message)
+ for (scheme, auth) in self.authorizers.iteritems():
+ chal = auth.getChallenge((host, port))
+ if chal is None:
+ value = '%s realm="%s"' % (scheme.title(), self.host)
+ else:
+ value = '%s %s,realm="%s"' % (scheme.title(), chal, self.host)
+ m.headers.setdefault('www-authenticate', []).append(value)
+ self.deliverResponse(m)
+
+
+ def login(self, message, host, port):
+ parts = message.headers['authorization'][0].split(None, 1)
+ a = self.authorizers.get(parts[0].lower())
+ if a:
+ try:
+ c = a.decode(parts[1])
+ except SIPError:
+ raise
+ except:
+ log.err()
+ self.deliverResponse(self.responseFromRequest(500, message))
+ else:
+ c.username += '@' + self.host
+ self.portal.login(c, None, IContact
+ ).addCallback(self._cbLogin, message, host, port
+ ).addErrback(self._ebLogin, message, host, port
+ ).addErrback(log.err
+ )
+ else:
+ self.deliverResponse(self.responseFromRequest(501, message))
+
+ def _cbLogin(self, (i, a, l), message, host, port):
+ # It's stateless, matey. What a joke.
+ self.register(message, host, port)
+
+ def _ebLogin(self, failure, message, host, port):
+ failure.trap(cred.error.UnauthorizedLogin)
+ self.unauthorized(message, host, port)
+
+ def register(self, message, host, port):
+ """Allow all users to register"""
+ name, toURL, params = parseAddress(message.headers["to"][0], clean=1)
+ contact = None
+ if message.headers.has_key("contact"):
+ contact = message.headers["contact"][0]
+
+ if message.headers.get("expires", [None])[0] == "0":
+ self.unregister(message, toURL, contact)
+ else:
+ # XXX Check expires on appropriate URL, and pass it to registry
+ # instead of having registry hardcode it.
+ if contact is not None:
+ name, contactURL, params = parseAddress(contact, host=host, port=port)
+ d = self.registry.registerAddress(message.uri, toURL, contactURL)
+ else:
+ d = self.registry.getRegistrationInfo(toURL)
+ d.addCallbacks(self._cbRegister, self._ebRegister,
+ callbackArgs=(message,),
+ errbackArgs=(message,)
+ )
+
+ def _cbRegister(self, registration, message):
+ response = self.responseFromRequest(200, message)
+ if registration.contactURL != None:
+ response.addHeader("contact", registration.contactURL.toString())
+ response.addHeader("expires", "%d" % registration.secondsToExpiry)
+ response.addHeader("content-length", "0")
+ self.deliverResponse(response)
+
+ def _ebRegister(self, error, message):
+ error.trap(RegistrationError, LookupError)
+ # XXX return error message, and alter tests to deal with
+ # this, currently tests assume no message sent on failure
+
+ def unregister(self, message, toURL, contact):
+ try:
+ expires = int(message.headers["expires"][0])
+ except ValueError:
+ self.deliverResponse(self.responseFromRequest(400, message))
+ else:
+ if expires == 0:
+ if contact == "*":
+ contactURL = "*"
+ else:
+ name, contactURL, params = parseAddress(contact)
+ d = self.registry.unregisterAddress(message.uri, toURL, contactURL)
+ d.addCallback(self._cbUnregister, message
+ ).addErrback(self._ebUnregister, message
+ )
+
+ def _cbUnregister(self, registration, message):
+ msg = self.responseFromRequest(200, message)
+ msg.headers.setdefault('contact', []).append(registration.contactURL.toString())
+ msg.addHeader("expires", "0")
+ self.deliverResponse(msg)
+
+ def _ebUnregister(self, registration, message):
+ pass
+
+
+class InMemoryRegistry:
+ """A simplistic registry for a specific domain."""
+
+ implements(IRegistry, ILocator)
+
+ def __init__(self, domain):
+ self.domain = domain # the domain we handle registration for
+ self.users = {} # map username to (IDelayedCall for expiry, address URI)
+
+ def getAddress(self, userURI):
+ if userURI.host != self.domain:
+ return defer.fail(LookupError("unknown domain"))
+ if self.users.has_key(userURI.username):
+ dc, url = self.users[userURI.username]
+ return defer.succeed(url)
+ else:
+ return defer.fail(LookupError("no such user"))
+
+ def getRegistrationInfo(self, userURI):
+ if userURI.host != self.domain:
+ return defer.fail(LookupError("unknown domain"))
+ if self.users.has_key(userURI.username):
+ dc, url = self.users[userURI.username]
+ return defer.succeed(Registration(int(dc.getTime() - time.time()), url))
+ else:
+ return defer.fail(LookupError("no such user"))
+
+ def _expireRegistration(self, username):
+ try:
+ dc, url = self.users[username]
+ except KeyError:
+ return defer.fail(LookupError("no such user"))
+ else:
+ dc.cancel()
+ del self.users[username]
+ return defer.succeed(Registration(0, url))
+
+ def registerAddress(self, domainURL, logicalURL, physicalURL):
+ if domainURL.host != self.domain:
+ log.msg("Registration for domain we don't handle.")
+ return defer.fail(RegistrationError(404))
+ if logicalURL.host != self.domain:
+ log.msg("Registration for domain we don't handle.")
+ return defer.fail(RegistrationError(404))
+ if self.users.has_key(logicalURL.username):
+ dc, old = self.users[logicalURL.username]
+ dc.reset(3600)
+ else:
+ dc = reactor.callLater(3600, self._expireRegistration, logicalURL.username)
+ log.msg("Registered %s at %s" % (logicalURL.toString(), physicalURL.toString()))
+ self.users[logicalURL.username] = (dc, physicalURL)
+ return defer.succeed(Registration(int(dc.getTime() - time.time()), physicalURL))
+
+ def unregisterAddress(self, domainURL, logicalURL, physicalURL):
+ return self._expireRegistration(logicalURL.username)
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/socks.py b/vendor/Twisted-10.0.0/twisted/protocols/socks.py
new file mode 100644
index 0000000000..95fe436753
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/socks.py
@@ -0,0 +1,240 @@
+# -*- test-case-name: twisted.test.test_socks -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implementation of the SOCKSv4 protocol.
+"""
+
+# python imports
+import struct
+import string
+import socket
+import time
+
+# twisted imports
+from twisted.internet import reactor, protocol, defer
+from twisted.python import log
+
+
+class SOCKSv4Outgoing(protocol.Protocol):
+
+ def __init__(self,socks):
+ self.socks=socks
+
+ def connectionMade(self):
+ peer = self.transport.getPeer()
+ self.socks.makeReply(90, 0, port=peer.port, ip=peer.host)
+ self.socks.otherConn=self
+
+ def connectionLost(self, reason):
+ self.socks.transport.loseConnection()
+
+ def dataReceived(self,data):
+ self.socks.write(data)
+
+ def write(self,data):
+ self.socks.log(self,data)
+ self.transport.write(data)
+
+
+
+class SOCKSv4Incoming(protocol.Protocol):
+
+ def __init__(self,socks):
+ self.socks=socks
+ self.socks.otherConn=self
+
+ def connectionLost(self, reason):
+ self.socks.transport.loseConnection()
+
+ def dataReceived(self,data):
+ self.socks.write(data)
+
+ def write(self,data):
+ self.socks.log(self,data)
+ self.transport.write(data)
+
+
+class SOCKSv4(protocol.Protocol):
+ """
+ An implementation of the SOCKSv4 protocol.
+
+ @type logging: C{str} or C{None}
+ @ivar logging: If not C{None}, the name of the logfile to which connection
+ information will be written.
+
+ @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
+ @ivar reactor: The reactor used to create connections.
+
+ @type buf: C{str}
+ @ivar buf: Part of a SOCKSv4 connection request.
+
+ @type otherConn: C{SOCKSv4Incoming}, C{SOCKSv4Outgoing} or C{None}
+ @ivar otherConn: Until the connection has been established, C{otherConn} is
+ C{None}. After that, it is the proxy-to-destination protocol instance
+ along which the client's connection is being forwarded.
+ """
+ def __init__(self, logging=None, reactor=reactor):
+ self.logging = logging
+ self.reactor = reactor
+
+ def connectionMade(self):
+ self.buf = ""
+ self.otherConn = None
+
+ def dataReceived(self, data):
+ """
+ Called whenever data is received.
+
+ @type data: C{str}
+ @param data: Part or all of a SOCKSv4 packet.
+ """
+ if self.otherConn:
+ self.otherConn.write(data)
+ return
+ self.buf = self.buf + data
+ completeBuffer = self.buf
+ if "\000" in self.buf[8:]:
+ head, self.buf = self.buf[:8], self.buf[8:]
+ version, code, port = struct.unpack("!BBH", head[:4])
+ user, self.buf = self.buf.split("\000", 1)
+ if head[4:7] == "\000\000\000" and head[7] != "\000":
+ # An IP address of the form 0.0.0.X, where X is non-zero,
+ # signifies that this is a SOCKSv4a packet.
+ # If the complete packet hasn't been received, restore the
+ # buffer and wait for it.
+ if "\000" not in self.buf:
+ self.buf = completeBuffer
+ return
+ server, self.buf = self.buf.split("\000", 1)
+ d = self.reactor.resolve(server)
+ d.addCallback(self._dataReceived2, user,
+ version, code, port)
+ d.addErrback(lambda result, self = self: self.makeReply(91))
+ return
+ else:
+ server = socket.inet_ntoa(head[4:8])
+
+ self._dataReceived2(server, user, version, code, port)
+
+ def _dataReceived2(self, server, user, version, code, port):
+ """
+ The second half of the SOCKS connection setup. For a SOCKSv4 packet this
+ is after the server address has been extracted from the header. For a
+ SOCKSv4a packet this is after the host name has been resolved.
+
+ @type server: C{str}
+ @param server: The IP address of the destination, represented as a
+ dotted quad.
+
+ @type user: C{str}
+ @param user: The username associated with the connection.
+
+ @type version: C{int}
+ @param version: The SOCKS protocol version number.
+
+ @type code: C{int}
+ @param code: The comand code. 1 means establish a TCP/IP stream
+ connection, and 2 means establish a TCP/IP port binding.
+
+ @type port: C{int}
+ @param port: The port number associated with the connection.
+ """
+ assert version == 4, "Bad version code: %s" % version
+ if not self.authorize(code, server, port, user):
+ self.makeReply(91)
+ return
+ if code == 1: # CONNECT
+ d = self.connectClass(server, port, SOCKSv4Outgoing, self)
+ d.addErrback(lambda result, self = self: self.makeReply(91))
+ elif code == 2: # BIND
+ d = self.listenClass(0, SOCKSv4IncomingFactory, self, server)
+ d.addCallback(lambda (h, p),
+ self = self: self.makeReply(90, 0, p, h))
+ else:
+ raise RuntimeError, "Bad Connect Code: %s" % code
+ assert self.buf == "", "hmm, still stuff in buffer... %s" % repr(
+ self.buf)
+
+ def connectionLost(self, reason):
+ if self.otherConn:
+ self.otherConn.transport.loseConnection()
+
+ def authorize(self,code,server,port,user):
+ log.msg("code %s connection to %s:%s (user %s) authorized" % (code,server,port,user))
+ return 1
+
+ def connectClass(self, host, port, klass, *args):
+ return protocol.ClientCreator(reactor, klass, *args).connectTCP(host,port)
+
+ def listenClass(self, port, klass, *args):
+ serv = reactor.listenTCP(port, klass(*args))
+ return defer.succeed(serv.getHost()[1:])
+
+ def makeReply(self,reply,version=0,port=0,ip="0.0.0.0"):
+ self.transport.write(struct.pack("!BBH",version,reply,port)+socket.inet_aton(ip))
+ if reply!=90: self.transport.loseConnection()
+
+ def write(self,data):
+ self.log(self,data)
+ self.transport.write(data)
+
+ def log(self,proto,data):
+ if not self.logging: return
+ peer = self.transport.getPeer()
+ their_peer = self.otherConn.transport.getPeer()
+ f=open(self.logging,"a")
+ f.write("%s\t%s:%d %s %s:%d\n"%(time.ctime(),
+ peer.host,peer.port,
+ ((proto==self and '<') or '>'),
+ their_peer.host,their_peer.port))
+ while data:
+ p,data=data[:16],data[16:]
+ f.write(string.join(map(lambda x:'%02X'%ord(x),p),' ')+' ')
+ f.write((16-len(p))*3*' ')
+ for c in p:
+ if len(repr(c))>3: f.write('.')
+ else: f.write(c)
+ f.write('\n')
+ f.write('\n')
+ f.close()
+
+
+
+class SOCKSv4Factory(protocol.Factory):
+ """
+ A factory for a SOCKSv4 proxy.
+
+ Constructor accepts one argument, a log file name.
+ """
+
+ def __init__(self, log):
+ self.logging = log
+
+ def buildProtocol(self, addr):
+ return SOCKSv4(self.logging, reactor)
+
+
+
+class SOCKSv4IncomingFactory(protocol.Factory):
+ """
+ A utility class for building protocols for incoming connections.
+ """
+
+ def __init__(self, socks, ip):
+ self.socks = socks
+ self.ip = ip
+
+
+ def buildProtocol(self, addr):
+ if addr[0] == self.ip:
+ self.ip = ""
+ self.socks.makeReply(90, 0)
+ return SOCKSv4Incoming(self.socks)
+ elif self.ip == "":
+ return None
+ else:
+ self.socks.makeReply(91, 0)
+ self.ip = ""
+ return None
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/stateful.py b/vendor/Twisted-10.0.0/twisted/protocols/stateful.py
new file mode 100644
index 0000000000..9f3992c577
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/stateful.py
@@ -0,0 +1,52 @@
+# -*- test-case-name: twisted.test.test_stateful -*-
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import protocol
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+class StatefulProtocol(protocol.Protocol):
+ """A Protocol that stores state for you.
+
+ state is a pair (function, num_bytes). When num_bytes bytes of data arrives
+ from the network, function is called. It is expected to return the next
+ state or None to keep same state. Initial state is returned by
+ getInitialState (override it).
+ """
+ _sful_data = None, None, 0
+
+ def makeConnection(self, transport):
+ protocol.Protocol.makeConnection(self, transport)
+ self._sful_data = self.getInitialState(), StringIO(), 0
+
+ def getInitialState(self):
+ raise NotImplementedError
+
+ def dataReceived(self, data):
+ state, buffer, offset = self._sful_data
+ buffer.seek(0, 2)
+ buffer.write(data)
+ blen = buffer.tell() # how many bytes total is in the buffer
+ buffer.seek(offset)
+ while blen - offset >= state[1]:
+ d = buffer.read(state[1])
+ offset += state[1]
+ next = state[0](d)
+ if self.transport.disconnecting: # XXX: argh stupid hack borrowed right from LineReceiver
+ return # dataReceived won't be called again, so who cares about consistent state
+ if next:
+ state = next
+ if offset != 0:
+ b = buffer.read()
+ buffer.seek(0)
+ buffer.truncate()
+ buffer.write(b)
+ offset = 0
+ self._sful_data = state, buffer, offset
+
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/telnet.py b/vendor/Twisted-10.0.0/twisted/protocols/telnet.py
new file mode 100644
index 0000000000..592d93aa32
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/telnet.py
@@ -0,0 +1,325 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""TELNET implementation, with line-oriented command handling.
+"""
+
+import warnings
+warnings.warn(
+ "As of Twisted 2.1, twisted.protocols.telnet is deprecated. "
+ "See twisted.conch.telnet for the current, supported API.",
+ DeprecationWarning,
+ stacklevel=2)
+
+
+# System Imports
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+# Twisted Imports
+from twisted import copyright
+from twisted.internet import protocol
+
+# Some utility chars.
+ESC = chr(27) # ESC for doing fanciness
+BOLD_MODE_ON = ESC+"[1m" # turn bold on
+BOLD_MODE_OFF= ESC+"[m" # no char attributes
+
+
+# Characters gleaned from the various (and conflicting) RFCs. Not all of these are correct.
+
+NULL = chr(0) # No operation.
+LF = chr(10) # Moves the printer to the
+ # next print line, keeping the
+ # same horizontal position.
+CR = chr(13) # Moves the printer to the left
+ # margin of the current line.
+BEL = chr(7) # Produces an audible or
+ # visible signal (which does
+ # NOT move the print head).
+BS = chr(8) # Moves the print head one
+ # character position towards
+ # the left margin.
+HT = chr(9) # Moves the printer to the
+ # next horizontal tab stop.
+ # It remains unspecified how
+ # either party determines or
+ # establishes where such tab
+ # stops are located.
+VT = chr(11) # Moves the printer to the
+ # next vertical tab stop. It
+ # remains unspecified how
+ # either party determines or
+ # establishes where such tab
+ # stops are located.
+FF = chr(12) # Moves the printer to the top
+ # of the next page, keeping
+ # the same horizontal position.
+SE = chr(240) # End of subnegotiation parameters.
+NOP= chr(241) # No operation.
+DM = chr(242) # "Data Mark": The data stream portion
+ # of a Synch. This should always be
+ # accompanied by a TCP Urgent
+ # notification.
+BRK= chr(243) # NVT character Break.
+IP = chr(244) # The function Interrupt Process.
+AO = chr(245) # The function Abort Output
+AYT= chr(246) # The function Are You There.
+EC = chr(247) # The function Erase Character.
+EL = chr(248) # The function Erase Line
+GA = chr(249) # The Go Ahead signal.
+SB = chr(250) # Indicates that what follows is
+ # subnegotiation of the indicated
+ # option.
+WILL = chr(251) # Indicates the desire to begin
+ # performing, or confirmation that
+ # you are now performing, the
+ # indicated option.
+WONT = chr(252) # Indicates the refusal to perform,
+ # or continue performing, the
+ # indicated option.
+DO = chr(253) # Indicates the request that the
+ # other party perform, or
+ # confirmation that you are expecting
+ # the other party to perform, the
+ # indicated option.
+DONT = chr(254) # Indicates the demand that the
+ # other party stop performing,
+ # or confirmation that you are no
+ # longer expecting the other party
+ # to perform, the indicated option.
+IAC = chr(255) # Data Byte 255.
+
+# features
+
+ECHO = chr(1) # User-to-Server: Asks the server to send
+ # Echos of the transmitted data.
+
+ # Server-to User: States that the server is
+ # sending echos of the transmitted data.
+ # Sent only as a reply to ECHO or NO ECHO.
+
+SUPGA = chr(3) # Supress Go Ahead...? "Modern" telnet servers
+ # are supposed to do this.
+
+LINEMODE = chr(34) # I don't care that Jon Postel is dead.
+
+HIDE = chr(133) # The intention is that a server will send
+ # this signal to a user system which is
+ # echoing locally (to the user) when the user
+ # is about to type something secret (e.g. a
+ # password). In this case, the user system
+ # is to suppress local echoing or overprint
+ # the input (or something) until the server
+ # sends a NOECHO signal. In situations where
+ # the user system is not echoing locally,
+ # this signal must not be sent by the server.
+
+
+NOECHO= chr(131) # User-to-Server: Asks the server not to
+ # return Echos of the transmitted data.
+ #
+ # Server-to-User: States that the server is
+ # not sending echos of the transmitted data.
+ # Sent only as a reply to ECHO or NO ECHO,
+ # or to end the hide your input.
+
+
+
+iacBytes = {
+ DO: 'DO',
+ DONT: 'DONT',
+ WILL: 'WILL',
+ WONT: 'WONT',
+ IP: 'IP'
+ }
+
+def multireplace(st, dct):
+ for k, v in dct.items():
+ st = st.replace(k, v)
+ return st
+
+class Telnet(protocol.Protocol):
+ """I am a Protocol for handling Telnet connections. I have two
+ sets of special methods, telnet_* and iac_*.
+
+ telnet_* methods get called on every line sent to me. The method
+ to call is decided by the current mode. The initial mode is 'User';
+ this means that telnet_User is the first telnet_* method to be called.
+ All telnet_* methods should return a string which specifies the mode
+ to go into next; thus dictating which telnet_* method to call next.
+ For example, the default telnet_User method returns 'Password' to go
+ into Password mode, and the default telnet_Password method returns
+ 'Command' to go into Command mode.
+
+ The iac_* methods are less-used; they are called when an IAC telnet
+ byte is received. You can define iac_DO, iac_DONT, iac_WILL, iac_WONT,
+ and iac_IP methods to do what you want when one of these bytes is
+ received."""
+
+
+ gotIAC = 0
+ iacByte = None
+ lastLine = None
+ buffer = ''
+ echo = 0
+ delimiters = ['\r\n', '\r\000']
+ mode = "User"
+
+ def write(self, data):
+ """Send the given data over my transport."""
+ self.transport.write(data)
+
+
+ def connectionMade(self):
+ """I will write a welcomeMessage and loginPrompt to the client."""
+ self.write(self.welcomeMessage() + self.loginPrompt())
+
+ def welcomeMessage(self):
+ """Override me to return a string which will be sent to the client
+ before login."""
+ x = self.factory.__class__
+ return ("\r\n" + x.__module__ + '.' + x.__name__ +
+ '\r\nTwisted %s\r\n' % copyright.version
+ )
+
+ def loginPrompt(self):
+ """Override me to return a 'login:'-type prompt."""
+ return "username: "
+
+ def iacSBchunk(self, chunk):
+ pass
+
+ def iac_DO(self, feature):
+ pass
+
+ def iac_DONT(self, feature):
+ pass
+
+ def iac_WILL(self, feature):
+ pass
+
+ def iac_WONT(self, feature):
+ pass
+
+ def iac_IP(self, feature):
+ pass
+
+ def processLine(self, line):
+ """I call a method that looks like 'telnet_*' where '*' is filled
+ in by the current mode. telnet_* methods should return a string which
+ will become the new mode. If None is returned, the mode will not change.
+ """
+ mode = getattr(self, "telnet_"+self.mode)(line)
+ if mode is not None:
+ self.mode = mode
+
+ def telnet_User(self, user):
+ """I take a username, set it to the 'self.username' attribute,
+ print out a password prompt, and switch to 'Password' mode. If
+ you want to do something else when the username is received (ie,
+ create a new user if the user doesn't exist), override me."""
+ self.username = user
+ self.write(IAC+WILL+ECHO+"password: ")
+ return "Password"
+
+ def telnet_Password(self, paswd):
+ """I accept a password as an argument, and check it with the
+ checkUserAndPass method. If the login is successful, I call
+ loggedIn()."""
+ self.write(IAC+WONT+ECHO+"*****\r\n")
+ try:
+ checked = self.checkUserAndPass(self.username, paswd)
+ except:
+ return "Done"
+ if not checked:
+ return "Done"
+ self.loggedIn()
+ return "Command"
+
+ def telnet_Command(self, cmd):
+ """The default 'command processing' mode. You probably want to
+ override me."""
+ return "Command"
+
+ def processChunk(self, chunk):
+ """I take a chunk of data and delegate out to telnet_* methods
+ by way of processLine. If the current mode is 'Done', I'll close
+ the connection. """
+ self.buffer = self.buffer + chunk
+
+ #yech.
+ for delim in self.delimiters:
+ idx = self.buffer.find(delim)
+ if idx != -1:
+ break
+
+ while idx != -1:
+ buf, self.buffer = self.buffer[:idx], self.buffer[idx+2:]
+ self.processLine(buf)
+ if self.mode == 'Done':
+ self.transport.loseConnection()
+
+ for delim in self.delimiters:
+ idx = self.buffer.find(delim)
+ if idx != -1:
+ break
+
+ def dataReceived(self, data):
+ chunk = StringIO()
+ # silly little IAC state-machine
+ for char in data:
+ if self.gotIAC:
+ # working on an IAC request state
+ if self.iacByte:
+ # we're in SB mode, getting a chunk
+ if self.iacByte == SB:
+ if char == SE:
+ self.iacSBchunk(chunk.getvalue())
+ chunk = StringIO()
+ del self.iacByte
+ del self.gotIAC
+ else:
+ chunk.write(char)
+ else:
+ # got all I need to know state
+ try:
+ getattr(self, 'iac_%s' % iacBytes[self.iacByte])(char)
+ except KeyError:
+ pass
+ del self.iacByte
+ del self.gotIAC
+ else:
+ # got IAC, this is my W/W/D/D (or perhaps sb)
+ self.iacByte = char
+ elif char == IAC:
+ # Process what I've got so far before going into
+ # the IAC state; don't want to process characters
+ # in an inconsistent state with what they were
+ # received in.
+ c = chunk.getvalue()
+ if c:
+ why = self.processChunk(c)
+ if why:
+ return why
+ chunk = StringIO()
+ self.gotIAC = 1
+ else:
+ chunk.write(char)
+ # chunks are of a relatively indeterminate size.
+ c = chunk.getvalue()
+ if c:
+ why = self.processChunk(c)
+ if why:
+ return why
+
+ def loggedIn(self):
+ """Called after the user succesfully logged in.
+
+ Override in subclasses.
+ """
+ pass
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/test/__init__.py b/vendor/Twisted-10.0.0/twisted/protocols/test/__init__.py
new file mode 100644
index 0000000000..c580b94ba0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/test/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Unit tests for L{twisted.protocols}.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/test/test_tls.py b/vendor/Twisted-10.0.0/twisted/protocols/test/test_tls.py
new file mode 100644
index 0000000000..66b8a06e96
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/test/test_tls.py
@@ -0,0 +1,566 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.protocols.tls}.
+"""
+
+try:
+ from twisted.protocols.tls import TLSMemoryBIOProtocol, TLSMemoryBIOFactory
+except ImportError:
+ # Skip the whole test module if it can't be imported.
+ skip = "pyOpenSSL 0.10 or newer required for twisted.protocol.tls"
+else:
+ # Otherwise, the pyOpenSSL dependency must be satisfied, so all these
+ # imports will work.
+ from OpenSSL.crypto import X509Type
+ from OpenSSL.SSL import TLSv1_METHOD, Error, Context, ConnectionType
+ from twisted.internet.ssl import ClientContextFactory, PrivateCertificate
+ from twisted.internet.ssl import DefaultOpenSSLContextFactory
+
+from twisted.python.filepath import FilePath
+from twisted.internet.interfaces import ISystemHandle, ISSLTransport
+from twisted.internet.error import ConnectionDone
+from twisted.internet.defer import Deferred, gatherResults
+from twisted.internet.protocol import Protocol, ClientFactory, ServerFactory
+from twisted.protocols.loopback import loopbackAsync, collapsingPumpPolicy
+from twisted.trial.unittest import TestCase
+from twisted.test.test_tcp import ConnectionLostNotifyingProtocol
+from twisted.test.test_ssl import certPath
+from twisted.test.proto_helpers import StringTransport
+
+
+class HandshakeCallbackContextFactory:
+ """
+ L{HandshakeCallbackContextFactory} is a factory for SSL contexts which
+ allows applications to get notification when the SSL handshake completes.
+
+ @ivar _finished: A L{Deferred} which will be called back when the handshake
+ is done.
+ """
+ # pyOpenSSL needs to expose this.
+ # https://bugs.launchpad.net/pyopenssl/+bug/372832
+ SSL_CB_HANDSHAKE_DONE = 0x20
+
+ def __init__(self):
+ self._finished = Deferred()
+
+
+ def factoryAndDeferred(cls):
+ """
+ Create a new L{HandshakeCallbackContextFactory} and return a two-tuple
+ of it and a L{Deferred} which will fire when a connection created with
+ it completes a TLS handshake.
+ """
+ contextFactory = cls()
+ return contextFactory, contextFactory._finished
+ factoryAndDeferred = classmethod(factoryAndDeferred)
+
+
+ def _info(self, connection, where, ret):
+ """
+ This is the "info callback" on the context. It will be called
+ periodically by pyOpenSSL with information about the state of a
+ connection. When it indicates the handshake is complete, it will fire
+ C{self._finished}.
+ """
+ if where & self.SSL_CB_HANDSHAKE_DONE:
+ self._finished.callback(None)
+
+
+ def getContext(self):
+ """
+ Create and return an SSL context configured to use L{self._info} as the
+ info callback.
+ """
+ context = Context(TLSv1_METHOD)
+ context.set_info_callback(self._info)
+ return context
+
+
+
+class AccumulatingProtocol(Protocol):
+ """
+ A protocol which collects the bytes it receives and closes its connection
+ after receiving a certain minimum of data.
+
+ @ivar howMany: The number of bytes of data to wait for before closing the connection.
+ @ivar receiving: A C{list} of C{str} of the bytes received so far.
+ """
+ def __init__(self, howMany):
+ self.howMany = howMany
+
+
+ def connectionMade(self):
+ self.received = []
+
+
+ def dataReceived(self, bytes):
+ self.received.append(bytes)
+ if sum(map(len, self.received)) >= self.howMany:
+ self.transport.loseConnection()
+
+
+
+class TLSMemoryBIOTests(TestCase):
+ """
+ Tests for the implementation of L{ISSLTransport} which runs over another
+ L{ITransport}.
+ """
+ def test_interfaces(self):
+ """
+ L{TLSMemoryBIOProtocol} instances provide L{ISSLTransport} and
+ L{ISystemHandle}.
+ """
+ proto = TLSMemoryBIOProtocol(None, None)
+ self.assertTrue(ISSLTransport.providedBy(proto))
+ self.assertTrue(ISystemHandle.providedBy(proto))
+
+
+ def test_getHandle(self):
+ """
+ L{TLSMemoryBIOProtocol.getHandle} returns the L{OpenSSL.SSL.Connection}
+ instance it uses to actually implement TLS.
+
+ This may seem odd. In fact, it is. The L{OpenSSL.SSL.Connection} is
+ not actually the "system handle" here, nor even an object the reactor
+ knows about directly. However, L{twisted.internet.ssl.Certificate}'s
+ C{peerFromTransport} and C{hostFromTransport} methods depend on being
+ able to get an L{OpenSSL.SSL.Connection} object in order to work
+ properly. Implementing L{ISystemHandle.getHandle} like this is the
+ easiest way for those APIs to be made to work. If they are changed,
+ then it may make sense to get rid of this implementation of
+ L{ISystemHandle} and return the underlying socket instead.
+ """
+ factory = ClientFactory()
+ contextFactory = ClientContextFactory()
+ wrapperFactory = TLSMemoryBIOFactory(contextFactory, True, factory)
+ proto = TLSMemoryBIOProtocol(wrapperFactory, Protocol())
+ transport = StringTransport()
+ proto.makeConnection(transport)
+ self.assertIsInstance(proto.getHandle(), ConnectionType)
+
+
+ def test_makeConnection(self):
+ """
+ When L{TLSMemoryBIOProtocol} is connected to a transport, it connects
+ the protocol it wraps to a transport.
+ """
+ clientProtocol = Protocol()
+ clientFactory = ClientFactory()
+ clientFactory.protocol = lambda: clientProtocol
+
+ contextFactory = ClientContextFactory()
+ wrapperFactory = TLSMemoryBIOFactory(
+ contextFactory, True, clientFactory)
+ sslProtocol = wrapperFactory.buildProtocol(None)
+
+ transport = StringTransport()
+ sslProtocol.makeConnection(transport)
+
+ self.assertNotIdentical(clientProtocol.transport, None)
+ self.assertNotIdentical(clientProtocol.transport, transport)
+
+
+ def test_handshake(self):
+ """
+ The TLS handshake is performed when L{TLSMemoryBIOProtocol} is
+ connected to a transport.
+ """
+ clientFactory = ClientFactory()
+ clientFactory.protocol = Protocol
+
+ clientContextFactory, handshakeDeferred = (
+ HandshakeCallbackContextFactory.factoryAndDeferred())
+ wrapperFactory = TLSMemoryBIOFactory(
+ clientContextFactory, True, clientFactory)
+ sslClientProtocol = wrapperFactory.buildProtocol(None)
+
+ serverFactory = ServerFactory()
+ serverFactory.protocol = Protocol
+
+ serverContextFactory = DefaultOpenSSLContextFactory(certPath, certPath)
+ wrapperFactory = TLSMemoryBIOFactory(
+ serverContextFactory, False, serverFactory)
+ sslServerProtocol = wrapperFactory.buildProtocol(None)
+
+ connectionDeferred = loopbackAsync(sslServerProtocol, sslClientProtocol)
+
+ # Only wait for the handshake to complete. Anything after that isn't
+ # important here.
+ return handshakeDeferred
+
+
+ def test_handshakeFailure(self):
+ """
+ L{TLSMemoryBIOProtocol} reports errors in the handshake process to the
+ application-level protocol object using its C{connectionLost} method
+ and disconnects the underlying transport.
+ """
+ clientConnectionLost = Deferred()
+ clientFactory = ClientFactory()
+ clientFactory.protocol = (
+ lambda: ConnectionLostNotifyingProtocol(
+ clientConnectionLost))
+
+ clientContextFactory = HandshakeCallbackContextFactory()
+ wrapperFactory = TLSMemoryBIOFactory(
+ clientContextFactory, True, clientFactory)
+ sslClientProtocol = wrapperFactory.buildProtocol(None)
+
+ serverConnectionLost = Deferred()
+ serverFactory = ServerFactory()
+ serverFactory.protocol = (
+ lambda: ConnectionLostNotifyingProtocol(
+ serverConnectionLost))
+
+ # This context factory rejects any clients which do not present a
+ # certificate.
+ certificateData = FilePath(certPath).getContent()
+ certificate = PrivateCertificate.loadPEM(certificateData)
+ serverContextFactory = certificate.options(certificate)
+ wrapperFactory = TLSMemoryBIOFactory(
+ serverContextFactory, False, serverFactory)
+ sslServerProtocol = wrapperFactory.buildProtocol(None)
+
+ connectionDeferred = loopbackAsync(sslServerProtocol, sslClientProtocol)
+
+ def cbConnectionLost(protocol):
+ # The connection should close on its own in response to the error
+ # induced by the client not supplying the required certificate.
+ # After that, check to make sure the protocol's connectionLost was
+ # called with the right thing.
+ protocol.lostConnectionReason.trap(Error)
+ clientConnectionLost.addCallback(cbConnectionLost)
+ serverConnectionLost.addCallback(cbConnectionLost)
+
+ # Additionally, the underlying transport should have been told to
+ # go away.
+ return gatherResults([
+ clientConnectionLost, serverConnectionLost,
+ connectionDeferred])
+
+
+ def test_getPeerCertificate(self):
+ """
+ L{TLSMemoryBIOFactory.getPeerCertificate} returns the
+ L{OpenSSL.crypto.X509Type} instance representing the peer's
+ certificate.
+ """
+ # Set up a client and server so there's a certificate to grab.
+ clientFactory = ClientFactory()
+ clientFactory.protocol = Protocol
+
+ clientContextFactory, handshakeDeferred = (
+ HandshakeCallbackContextFactory.factoryAndDeferred())
+ wrapperFactory = TLSMemoryBIOFactory(
+ clientContextFactory, True, clientFactory)
+ sslClientProtocol = wrapperFactory.buildProtocol(None)
+
+ serverFactory = ServerFactory()
+ serverFactory.protocol = Protocol
+
+ serverContextFactory = DefaultOpenSSLContextFactory(certPath, certPath)
+ wrapperFactory = TLSMemoryBIOFactory(
+ serverContextFactory, False, serverFactory)
+ sslServerProtocol = wrapperFactory.buildProtocol(None)
+
+ connectionDeferred = loopbackAsync(
+ sslServerProtocol, sslClientProtocol)
+
+ # Wait for the handshake
+ def cbHandshook(ignored):
+ # Grab the server's certificate and check it out
+ cert = sslClientProtocol.getPeerCertificate()
+ self.assertIsInstance(cert, X509Type)
+ self.assertEquals(
+ cert.digest('md5'),
+ '9B:A4:AB:43:10:BE:82:AE:94:3E:6B:91:F2:F3:40:E8')
+ handshakeDeferred.addCallback(cbHandshook)
+ return handshakeDeferred
+
+
+ def test_writeAfterHandshake(self):
+ """
+ Bytes written to L{TLSMemoryBIOProtocol} before the handshake is
+ complete are received by the protocol on the other side of the
+ connection once the handshake succeeds.
+ """
+ bytes = "some bytes"
+
+ clientProtocol = Protocol()
+ clientFactory = ClientFactory()
+ clientFactory.protocol = lambda: clientProtocol
+
+ clientContextFactory, handshakeDeferred = (
+ HandshakeCallbackContextFactory.factoryAndDeferred())
+ wrapperFactory = TLSMemoryBIOFactory(
+ clientContextFactory, True, clientFactory)
+ sslClientProtocol = wrapperFactory.buildProtocol(None)
+
+ serverProtocol = AccumulatingProtocol(len(bytes))
+ serverFactory = ServerFactory()
+ serverFactory.protocol = lambda: serverProtocol
+
+ serverContextFactory = DefaultOpenSSLContextFactory(certPath, certPath)
+ wrapperFactory = TLSMemoryBIOFactory(
+ serverContextFactory, False, serverFactory)
+ sslServerProtocol = wrapperFactory.buildProtocol(None)
+
+ connectionDeferred = loopbackAsync(sslServerProtocol, sslClientProtocol)
+
+ # Wait for the handshake to finish before writing anything.
+ def cbHandshook(ignored):
+ clientProtocol.transport.write(bytes)
+
+ # The server will drop the connection once it gets the bytes.
+ return connectionDeferred
+ handshakeDeferred.addCallback(cbHandshook)
+
+ # Once the connection is lost, make sure the server received the
+ # expected bytes.
+ def cbDisconnected(ignored):
+ self.assertEquals("".join(serverProtocol.received), bytes)
+ handshakeDeferred.addCallback(cbDisconnected)
+
+ return handshakeDeferred
+
+
+ def test_writeBeforeHandshake(self):
+ """
+ Bytes written to L{TLSMemoryBIOProtocol} before the handshake is
+ complete are received by the protocol on the other side of the
+ connection once the handshake succeeds.
+ """
+ bytes = "some bytes"
+
+ class SimpleSendingProtocol(Protocol):
+ def connectionMade(self):
+ self.transport.write(bytes)
+
+ clientFactory = ClientFactory()
+ clientFactory.protocol = SimpleSendingProtocol
+
+ clientContextFactory, handshakeDeferred = (
+ HandshakeCallbackContextFactory.factoryAndDeferred())
+ wrapperFactory = TLSMemoryBIOFactory(
+ clientContextFactory, True, clientFactory)
+ sslClientProtocol = wrapperFactory.buildProtocol(None)
+
+ serverProtocol = AccumulatingProtocol(len(bytes))
+ serverFactory = ServerFactory()
+ serverFactory.protocol = lambda: serverProtocol
+
+ serverContextFactory = DefaultOpenSSLContextFactory(certPath, certPath)
+ wrapperFactory = TLSMemoryBIOFactory(
+ serverContextFactory, False, serverFactory)
+ sslServerProtocol = wrapperFactory.buildProtocol(None)
+
+ connectionDeferred = loopbackAsync(sslServerProtocol, sslClientProtocol)
+
+ # Wait for the connection to end, then make sure the server received
+ # the bytes sent by the client.
+ def cbConnectionDone(ignored):
+ self.assertEquals("".join(serverProtocol.received), bytes)
+ connectionDeferred.addCallback(cbConnectionDone)
+ return connectionDeferred
+
+
+ def test_writeSequence(self):
+ """
+ Bytes written to L{TLSMemoryBIOProtocol} with C{writeSequence} are
+ received by the protocol on the other side of the connection.
+ """
+ bytes = "some bytes"
+ class SimpleSendingProtocol(Protocol):
+ def connectionMade(self):
+ self.transport.writeSequence(list(bytes))
+
+ clientFactory = ClientFactory()
+ clientFactory.protocol = SimpleSendingProtocol
+
+ clientContextFactory = HandshakeCallbackContextFactory()
+ wrapperFactory = TLSMemoryBIOFactory(
+ clientContextFactory, True, clientFactory)
+ sslClientProtocol = wrapperFactory.buildProtocol(None)
+
+ serverProtocol = AccumulatingProtocol(len(bytes))
+ serverFactory = ServerFactory()
+ serverFactory.protocol = lambda: serverProtocol
+
+ serverContextFactory = DefaultOpenSSLContextFactory(certPath, certPath)
+ wrapperFactory = TLSMemoryBIOFactory(
+ serverContextFactory, False, serverFactory)
+ sslServerProtocol = wrapperFactory.buildProtocol(None)
+
+ connectionDeferred = loopbackAsync(sslServerProtocol, sslClientProtocol)
+
+ # Wait for the connection to end, then make sure the server received
+ # the bytes sent by the client.
+ def cbConnectionDone(ignored):
+ self.assertEquals("".join(serverProtocol.received), bytes)
+ connectionDeferred.addCallback(cbConnectionDone)
+ return connectionDeferred
+
+
+ def test_multipleWrites(self):
+ """
+ If multiple separate TLS messages are received in a single chunk from
+ the underlying transport, all of the application bytes from each
+ message are delivered to the application-level protocol.
+ """
+ bytes = [str(i) for i in range(10)]
+ class SimpleSendingProtocol(Protocol):
+ def connectionMade(self):
+ for b in bytes:
+ self.transport.write(b)
+
+ clientFactory = ClientFactory()
+ clientFactory.protocol = SimpleSendingProtocol
+
+ clientContextFactory = HandshakeCallbackContextFactory()
+ wrapperFactory = TLSMemoryBIOFactory(
+ clientContextFactory, True, clientFactory)
+ sslClientProtocol = wrapperFactory.buildProtocol(None)
+
+ serverProtocol = AccumulatingProtocol(sum(map(len, bytes)))
+ serverFactory = ServerFactory()
+ serverFactory.protocol = lambda: serverProtocol
+
+ serverContextFactory = DefaultOpenSSLContextFactory(certPath, certPath)
+ wrapperFactory = TLSMemoryBIOFactory(
+ serverContextFactory, False, serverFactory)
+ sslServerProtocol = wrapperFactory.buildProtocol(None)
+
+ connectionDeferred = loopbackAsync(sslServerProtocol, sslClientProtocol, collapsingPumpPolicy)
+
+ # Wait for the connection to end, then make sure the server received
+ # the bytes sent by the client.
+ def cbConnectionDone(ignored):
+ self.assertEquals("".join(serverProtocol.received), ''.join(bytes))
+ connectionDeferred.addCallback(cbConnectionDone)
+ return connectionDeferred
+
+
+ def test_hugeWrite(self):
+ """
+ If a very long string is passed to L{TLSMemoryBIOProtocol.write}, any
+ trailing part of it which cannot be send immediately is buffered and
+ sent later.
+ """
+ bytes = "some bytes"
+ factor = 8192
+ class SimpleSendingProtocol(Protocol):
+ def connectionMade(self):
+ self.transport.write(bytes * factor)
+
+ clientFactory = ClientFactory()
+ clientFactory.protocol = SimpleSendingProtocol
+
+ clientContextFactory = HandshakeCallbackContextFactory()
+ wrapperFactory = TLSMemoryBIOFactory(
+ clientContextFactory, True, clientFactory)
+ sslClientProtocol = wrapperFactory.buildProtocol(None)
+
+ serverProtocol = AccumulatingProtocol(len(bytes) * factor)
+ serverFactory = ServerFactory()
+ serverFactory.protocol = lambda: serverProtocol
+
+ serverContextFactory = DefaultOpenSSLContextFactory(certPath, certPath)
+ wrapperFactory = TLSMemoryBIOFactory(
+ serverContextFactory, False, serverFactory)
+ sslServerProtocol = wrapperFactory.buildProtocol(None)
+
+ connectionDeferred = loopbackAsync(sslServerProtocol, sslClientProtocol)
+
+ # Wait for the connection to end, then make sure the server received
+ # the bytes sent by the client.
+ def cbConnectionDone(ignored):
+ self.assertEquals("".join(serverProtocol.received), bytes * factor)
+ connectionDeferred.addCallback(cbConnectionDone)
+ return connectionDeferred
+
+
+ def test_disorderlyShutdown(self):
+ """
+ If a L{TLSMemoryBIOProtocol} loses its connection unexpectedly, this is
+ reported to the application.
+ """
+ clientConnectionLost = Deferred()
+ clientFactory = ClientFactory()
+ clientFactory.protocol = (
+ lambda: ConnectionLostNotifyingProtocol(
+ clientConnectionLost))
+
+ clientContextFactory = HandshakeCallbackContextFactory()
+ wrapperFactory = TLSMemoryBIOFactory(
+ clientContextFactory, True, clientFactory)
+ sslClientProtocol = wrapperFactory.buildProtocol(None)
+
+ # Client speaks first, so the server can be dumb.
+ serverProtocol = Protocol()
+
+ connectionDeferred = loopbackAsync(serverProtocol, sslClientProtocol)
+
+ # Now destroy the connection.
+ serverProtocol.transport.loseConnection()
+
+ # And when the connection completely dies, check the reason.
+ def cbDisconnected(clientProtocol):
+ clientProtocol.lostConnectionReason.trap(Error)
+ clientConnectionLost.addCallback(cbDisconnected)
+ return clientConnectionLost
+
+
+ def test_loseConnectionAfterHandshake(self):
+ """
+ L{TLSMemoryBIOProtocol.loseConnection} sends a TLS close alert and
+ shuts down the underlying connection.
+ """
+ clientConnectionLost = Deferred()
+ clientFactory = ClientFactory()
+ clientFactory.protocol = (
+ lambda: ConnectionLostNotifyingProtocol(
+ clientConnectionLost))
+
+ clientContextFactory, handshakeDeferred = (
+ HandshakeCallbackContextFactory.factoryAndDeferred())
+ wrapperFactory = TLSMemoryBIOFactory(
+ clientContextFactory, True, clientFactory)
+ sslClientProtocol = wrapperFactory.buildProtocol(None)
+
+ serverProtocol = Protocol()
+ serverFactory = ServerFactory()
+ serverFactory.protocol = lambda: serverProtocol
+
+ serverContextFactory = DefaultOpenSSLContextFactory(certPath, certPath)
+ wrapperFactory = TLSMemoryBIOFactory(
+ serverContextFactory, False, serverFactory)
+ sslServerProtocol = wrapperFactory.buildProtocol(None)
+
+ connectionDeferred = loopbackAsync(sslServerProtocol, sslClientProtocol)
+
+ # Wait for the handshake before dropping the connection.
+ def cbHandshake(ignored):
+ serverProtocol.transport.loseConnection()
+
+ # Now wait for the client to notice.
+ return clientConnectionLost
+ handshakeDeferred.addCallback(cbHandshake)
+
+ # Wait for the connection to end, then make sure the client was
+ # notified of a handshake failure.
+ def cbConnectionDone(clientProtocol):
+ clientProtocol.lostConnectionReason.trap(ConnectionDone)
+
+ # The server should have closed its underlying transport, in
+ # addition to whatever it did to shut down the TLS layer.
+ self.assertTrue(serverProtocol.transport.q.disconnect)
+
+ # The client should also have closed its underlying transport once
+ # it saw the server shut down the TLS layer, so as to avoid relying
+ # on the server to close the underlying connection.
+ self.assertTrue(clientProtocol.transport.q.disconnect)
+ handshakeDeferred.addCallback(cbConnectionDone)
+ return handshakeDeferred
+
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/tls.py b/vendor/Twisted-10.0.0/twisted/protocols/tls.py
new file mode 100644
index 0000000000..8ec360d49a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/tls.py
@@ -0,0 +1,345 @@
+# -*- test-case-name: twisted.protocols.test.test_tls,twisted.internet.test.test_tls,twisted.test.test_sslverify -*-
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implementation of a TLS transport (L{ISSLTransport}) as an L{IProtocol}
+layered on top of any L{ITransport} implementation, based on OpenSSL's
+memory BIO features.
+
+L{TLSMemoryBIOFactory} is a L{WrappingFactory} which wraps protocols created by
+the factory it wraps with L{TLSMemoryBIOProtocol}. L{TLSMemoryBIOProtocol}
+intercedes between the underlying transport and the wrapped protocol to
+implement SSL and TLS. Typical usage of this module looks like this::
+
+ from twisted.protocols.tls import TLSMemoryBIOFactory
+ from twisted.internet.protocol import ServerFactory
+ from twisted.internet.ssl import PrivateCertificate
+ from twisted.internet import reactor
+
+ from someapplication import ApplicationProtocol
+
+ serverFactory = ServerFactory()
+ serverFactory.protocol = ApplicationProtocol
+ certificate = PrivateCertificate.loadPEM(certPEMData)
+ contextFactory = certificate.options()
+ tlsFactory = TLSMemoryBIOFactory(contextFactory, False, serverFactory)
+ reactor.listenTCP(12345, tlsFactory)
+ reactor.run()
+
+Because the reactor's SSL and TLS APIs are likely implemented in a more
+efficient way, it is more common to use them (see L{IReactorSSL} and
+L{ITLSTransport}). However, this API offers somewhat more flexibility; for
+example, a L{TLSMemoryBIOProtocol} instance can use another instance of
+L{TLSMemoryBIOProtocol} as its transport, yielding TLS over TLS - useful to
+implement onion routing. Or it can be used to run TLS over a UNIX socket,
+or over stdio to a child process.
+"""
+
+
+from OpenSSL.SSL import Error, ZeroReturnError, WantReadError
+from OpenSSL.SSL import TLSv1_METHOD, Context, Connection
+
+try:
+ Connection(Context(TLSv1_METHOD), None)
+except TypeError, e:
+ if str(e) != "argument must be an int, or have a fileno() method.":
+ raise
+ raise ImportError("twisted.protocols.tls requires pyOpenSSL 0.10 or newer.")
+
+from zope.interface import implements
+
+from twisted.python.failure import Failure
+from twisted.internet.interfaces import ISystemHandle, ISSLTransport
+from twisted.internet.main import CONNECTION_DONE, CONNECTION_LOST
+from twisted.internet.protocol import Protocol
+from twisted.protocols.policies import ProtocolWrapper, WrappingFactory
+
+
+class TLSMemoryBIOProtocol(ProtocolWrapper):
+ """
+ L{TLSMemoryBIOProtocol} is a protocol wrapper which uses OpenSSL via a
+ memory BIO to encrypt bytes written to it before sending them on to the
+ underlying transport and decrypts bytes received from the underlying
+ transport before delivering them to the wrapped protocol.
+
+ @ivar _tlsConnection: The L{OpenSSL.SSL.Connection} instance which is
+ encrypted and decrypting this connection.
+
+ @ivar _lostConnection: A flag indicating whether connection loss has
+ already been dealt with (C{True}) or not (C{False}).
+
+ @ivar _writeBlockedOnRead: A flag indicating whether further writing must
+ wait for data to be received (C{True}) or not (C{False}).
+
+ @ivar _appSendBuffer: A C{list} of C{str} of application-level (cleartext)
+ data which is waiting for C{_writeBlockedOnRead} to be reset to
+ C{False} so it can be passed to and perhaps accepted by
+ C{_tlsConnection.send}.
+
+ @ivar _connectWrapped: A flag indicating whether or not to call
+ C{makeConnection} on the wrapped protocol. This is for the reactor's
+ L{ITLSTransport.startTLS} implementation, since it has a protocol which
+ it has already called C{makeConnection} on, and which has no interest
+ in a new transport. See #3821.
+
+ @ivar _handshakeDone: A flag indicating whether or not the handshake is
+ known to have completed successfully (C{True}) or not (C{False}). This
+ is used to control error reporting behavior. If the handshake has not
+ completed, the underlying L{OpenSSL.SSL.Error} will be passed to the
+ application's C{connectionLost} method. If it has completed, any
+ unexpected L{OpenSSL.SSL.Error} will be turned into a
+ L{ConnectionLost}. This is weird; however, it is simply an attempt at
+ a faithful re-implementation of the behavior provided by
+ L{twisted.internet.ssl}.
+
+ @ivar _reason: If an unexpected L{OpenSSL.SSL.Error} occurs which causes
+ the connection to be lost, it is saved here. If appropriate, this may
+ be used as the reason passed to the application protocol's
+ C{connectionLost} method.
+ """
+ implements(ISystemHandle, ISSLTransport)
+
+ _reason = None
+ _handshakeDone = False
+ _lostConnection = False
+ _writeBlockedOnRead = False
+
+ def __init__(self, factory, wrappedProtocol, _connectWrapped=True):
+ ProtocolWrapper.__init__(self, factory, wrappedProtocol)
+ self._connectWrapped = _connectWrapped
+
+
+ def getHandle(self):
+ """
+ Return the L{OpenSSL.SSL.Connection} object being used to encrypt and
+ decrypt this connection.
+
+ This is done for the benefit of L{twisted.internet.ssl.Certificate}'s
+ C{peerFromTransport} and C{hostFromTransport} methods only. A
+ different system handle may be returned by future versions of this
+ method.
+ """
+ return self._tlsConnection
+
+
+ def makeConnection(self, transport):
+ """
+ Connect this wrapper to the given transport and initialize the
+ necessary L{OpenSSL.SSL.Connection} with a memory BIO.
+ """
+ tlsContext = self.factory._contextFactory.getContext()
+ self._tlsConnection = Connection(tlsContext, None)
+ if self.factory._isClient:
+ self._tlsConnection.set_connect_state()
+ else:
+ self._tlsConnection.set_accept_state()
+ self._appSendBuffer = []
+
+ # Intentionally skip ProtocolWrapper.makeConnection - it might call
+ # wrappedProtocol.makeConnection, which we want to make conditional.
+ Protocol.makeConnection(self, transport)
+ self.factory.registerProtocol(self)
+ if self._connectWrapped:
+ # Now that the TLS layer is initialized, notify the application of
+ # the connection.
+ ProtocolWrapper.makeConnection(self, transport)
+
+ # Now that we ourselves have a transport (initialized by the
+ # ProtocolWrapper.makeConnection call above), kick off the TLS
+ # handshake.
+ try:
+ self._tlsConnection.do_handshake()
+ except WantReadError:
+ # This is the expected case - there's no data in the connection's
+ # input buffer yet, so it won't be able to complete the whole
+ # handshake now. If this is the speak-first side of the
+ # connection, then some bytes will be in the send buffer now; flush
+ # them.
+ self._flushSendBIO()
+
+
+ def _flushSendBIO(self):
+ """
+ Read any bytes out of the send BIO and write them to the underlying
+ transport.
+ """
+ try:
+ bytes = self._tlsConnection.bio_read(2 ** 15)
+ except WantReadError:
+ # There may be nothing in the send BIO right now.
+ pass
+ else:
+ self.transport.write(bytes)
+
+
+ def _flushReceiveBIO(self):
+ """
+ Try to receive any application-level bytes which are now available
+ because of a previous write into the receive BIO. This will take
+ care of delivering any application-level bytes which are received to
+ the protocol, as well as handling of the various exceptions which
+ can come from trying to get such bytes.
+ """
+ # Keep trying this until an error indicates we should stop or we
+ # close the connection. Looping is necessary to make sure we
+ # process all of the data which was put into the receive BIO, as
+ # there is no guarantee that a single recv call will do it all.
+ while not self._lostConnection:
+ try:
+ bytes = self._tlsConnection.recv(2 ** 15)
+ except WantReadError:
+ # The newly received bytes might not have been enough to produce
+ # any application data.
+ break
+ except ZeroReturnError:
+ # TLS has shut down and no more TLS data will be received over
+ # this connection.
+ self._lostConnection = True
+ self.transport.loseConnection()
+ if not self._handshakeDone and self._reason is not None:
+ failure = self._reason
+ else:
+ failure = Failure(CONNECTION_DONE)
+ # Failure's are fat. Drop the reference.
+ self._reason = None
+ ProtocolWrapper.connectionLost(self, failure)
+ except Error, e:
+ # Something went pretty wrong. For example, this might be a
+ # handshake failure (because there were no shared ciphers, because
+ # a certificate failed to verify, etc). TLS can no longer proceed.
+ self._flushSendBIO()
+ self._lostConnection = True
+
+ # Squash EOF in violation of protocol into ConnectionLost
+ if e.args[0] == -1 and e.args[1] == 'Unexpected EOF':
+ failure = Failure(CONNECTION_LOST)
+ else:
+ failure = Failure()
+ ProtocolWrapper.connectionLost(self, failure)
+ # This loseConnection call is basically tested by
+ # test_handshakeFailure. At least one side will need to do it
+ # or the test never finishes.
+ self.transport.loseConnection()
+ else:
+ # If we got application bytes, the handshake must be done by
+ # now. Keep track of this to control error reporting later.
+ self._handshakeDone = True
+ ProtocolWrapper.dataReceived(self, bytes)
+
+ # The received bytes might have generated a response which needs to be
+ # sent now. For example, the handshake involves several round-trip
+ # exchanges without ever producing application-bytes.
+ self._flushSendBIO()
+
+
+ def dataReceived(self, bytes):
+ """
+ Deliver any received bytes to the receive BIO and then read and deliver
+ to the application any application-level data which becomes available
+ as a result of this.
+ """
+ self._tlsConnection.bio_write(bytes)
+
+ if self._writeBlockedOnRead:
+ # A read just happened, so we might not be blocked anymore. Try to
+ # flush all the pending application bytes.
+ self._writeBlockedOnRead = False
+ appSendBuffer = self._appSendBuffer
+ self._appSendBuffer = []
+ for bytes in appSendBuffer:
+ self.write(bytes)
+ if not self._writeBlockedOnRead and self.disconnecting:
+ self.loseConnection()
+
+ self._flushReceiveBIO()
+
+
+ def connectionLost(self, reason):
+ """
+ Handle the possible repetition of calls to this method (due to either
+ the underlying transport going away or due to an error at the TLS
+ layer) and make sure the base implementation only gets invoked once.
+ """
+ if not self._lostConnection:
+ # Tell the TLS connection that it's not going to get any more data
+ # and give it a chance to finish reading.
+ self._tlsConnection.bio_shutdown()
+ self._flushReceiveBIO()
+
+
+ def loseConnection(self):
+ """
+ Send a TLS close alert and close the underlying connection.
+ """
+ self.disconnecting = True
+ if not self._writeBlockedOnRead:
+ self._tlsConnection.shutdown()
+ self._flushSendBIO()
+ self.transport.loseConnection()
+
+
+ def write(self, bytes):
+ """
+ Process the given application bytes and send any resulting TLS traffic
+ which arrives in the send BIO.
+ """
+ if self._lostConnection:
+ return
+
+ leftToSend = bytes
+ while leftToSend:
+ try:
+ sent = self._tlsConnection.send(leftToSend)
+ except WantReadError:
+ self._writeBlockedOnRead = True
+ self._appSendBuffer.append(leftToSend)
+ break
+ except Error, e:
+ # Just drop the connection. This has two useful consequences.
+ # First, for the application protocol's connectionLost method,
+ # it will squash any error into connection lost. We *could*
+ # let the real exception propagate to application code, but the
+ # other SSL implementation doesn't. Second, it causes the
+ # protocol's connectionLost method to be invoked
+ # non-reentrantly, which is always a nice feature.
+ self._reason = Failure()
+ self.transport.loseConnection()
+ break
+ else:
+ # If we sent some bytes, the handshake must be done. Keep
+ # track of this to control error reporting behavior.
+ self._handshakeDone = True
+ self._flushSendBIO()
+ leftToSend = leftToSend[sent:]
+
+
+ def writeSequence(self, iovec):
+ """
+ Write a sequence of application bytes by joining them into one string
+ and passing them to L{write}.
+ """
+ self.write("".join(iovec))
+
+
+ def getPeerCertificate(self):
+ return self._tlsConnection.get_peer_certificate()
+
+
+
+class TLSMemoryBIOFactory(WrappingFactory):
+ """
+ L{TLSMemoryBIOFactory} adds TLS to connections.
+
+ @ivar _contextFactory: The TLS context factory which will be used to define
+ certain TLS connection parameters.
+
+ @ivar _isClient: A flag which is C{True} if this is a client TLS
+ connection, C{False} if it is a server TLS connection.
+ """
+ protocol = TLSMemoryBIOProtocol
+
+ def __init__(self, contextFactory, isClient, wrappedFactory):
+ WrappingFactory.__init__(self, wrappedFactory)
+ self._contextFactory = contextFactory
+ self._isClient = isClient
diff --git a/vendor/Twisted-10.0.0/twisted/protocols/wire.py b/vendor/Twisted-10.0.0/twisted/protocols/wire.py
new file mode 100644
index 0000000000..3bcbec114f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/protocols/wire.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""Implement standard (and unused) TCP protocols.
+
+These protocols are either provided by inetd, or are not provided at all.
+"""
+
+# system imports
+import time, struct
+from zope.interface import implements
+
+# twisted import
+from twisted.internet import protocol, interfaces
+
+
+class Echo(protocol.Protocol):
+ """As soon as any data is received, write it back (RFC 862)"""
+
+ def dataReceived(self, data):
+ self.transport.write(data)
+
+
+class Discard(protocol.Protocol):
+ """Discard any received data (RFC 863)"""
+
+ def dataReceived(self, data):
+ # I'm ignoring you, nyah-nyah
+ pass
+
+
+class Chargen(protocol.Protocol):
+ """Generate repeating noise (RFC 864)"""
+ noise = r'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ !"#$%&?'
+
+ implements(interfaces.IProducer)
+
+ def connectionMade(self):
+ self.transport.registerProducer(self, 0)
+
+ def resumeProducing(self):
+ self.transport.write(self.noise)
+
+ def pauseProducing(self):
+ pass
+
+ def stopProducing(self):
+ pass
+
+
+class QOTD(protocol.Protocol):
+ """Return a quote of the day (RFC 865)"""
+
+ def connectionMade(self):
+ self.transport.write(self.getQuote())
+ self.transport.loseConnection()
+
+ def getQuote(self):
+ """Return a quote. May be overrriden in subclasses."""
+ return "An apple a day keeps the doctor away.\r\n"
+
+class Who(protocol.Protocol):
+ """Return list of active users (RFC 866)"""
+
+ def connectionMade(self):
+ self.transport.write(self.getUsers())
+ self.transport.loseConnection()
+
+ def getUsers(self):
+ """Return active users. Override in subclasses."""
+ return "root\r\n"
+
+
+class Daytime(protocol.Protocol):
+ """Send back the daytime in ASCII form (RFC 867)"""
+
+ def connectionMade(self):
+ self.transport.write(time.asctime(time.gmtime(time.time())) + '\r\n')
+ self.transport.loseConnection()
+
+
+class Time(protocol.Protocol):
+ """Send back the time in machine readable form (RFC 868)"""
+
+ def connectionMade(self):
+ # is this correct only for 32-bit machines?
+ result = struct.pack("!i", int(time.time()))
+ self.transport.write(result)
+ self.transport.loseConnection()
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/__init__.py b/vendor/Twisted-10.0.0/twisted/python/__init__.py
new file mode 100644
index 0000000000..6fc1955a36
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/__init__.py
@@ -0,0 +1,13 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+
+Twisted Python: Utilities and Enhancements for Python.
+
+"""
+
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/_epoll.c b/vendor/Twisted-10.0.0/twisted/python/_epoll.c
new file mode 100644
index 0000000000..00300c6e0f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/_epoll.c
@@ -0,0 +1,925 @@
+/* Generated by Pyrex 0.9.4.1 on Sun Oct 15 15:04:09 2006 */
+
+#define PY_SSIZE_T_CLEAN
+#include "Python.h"
+#include "structmember.h"
+#ifndef PY_LONG_LONG
+ #define PY_LONG_LONG LONG_LONG
+#endif
+#if PY_VERSION_HEX < 0x02050000
+ typedef int Py_ssize_t;
+ #define PY_SSIZE_T_MAX INT_MAX
+ #define PY_SSIZE_T_MIN INT_MIN
+ #define PyInt_FromSsize_t(z) PyInt_FromLong(z)
+ #define PyInt_AsSsize_t(o) PyInt_AsLong(o)
+#endif
+#ifdef __cplusplus
+#define __PYX_EXTERN_C extern "C"
+#else
+#define __PYX_EXTERN_C extern
+#endif
+__PYX_EXTERN_C double pow(double, double);
+#include "stdio.h"
+#include "errno.h"
+#include "string.h"
+#include "stdint.h"
+#include "sys/epoll.h"
+
+
+typedef struct {const char *s; const void **p;} __Pyx_CApiTabEntry; /*proto*/
+typedef struct {PyObject **p; char *s;} __Pyx_InternTabEntry; /*proto*/
+typedef struct {PyObject **p; char *s; long n;} __Pyx_StringTabEntry; /*proto*/
+static PyObject *__Pyx_UnpackItem(PyObject *, Py_ssize_t); /*proto*/
+static int __Pyx_EndUnpack(PyObject *, Py_ssize_t); /*proto*/
+static int __Pyx_PrintItem(PyObject *); /*proto*/
+static int __Pyx_PrintNewline(void); /*proto*/
+static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb); /*proto*/
+static void __Pyx_ReRaise(void); /*proto*/
+static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list); /*proto*/
+static PyObject *__Pyx_GetExcValue(void); /*proto*/
+static int __Pyx_ArgTypeTest(PyObject *obj, PyTypeObject *type, int none_allowed, char *name); /*proto*/
+static int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type); /*proto*/
+static int __Pyx_GetStarArgs(PyObject **args, PyObject **kwds, char *kwd_list[], Py_ssize_t nargs, PyObject **args2, PyObject **kwds2); /*proto*/
+static void __Pyx_WriteUnraisable(char *name); /*proto*/
+static void __Pyx_AddTraceback(char *funcname); /*proto*/
+static PyTypeObject *__Pyx_ImportType(char *module_name, char *class_name, long size); /*proto*/
+static int __Pyx_SetVtable(PyObject *dict, void *vtable); /*proto*/
+static int __Pyx_GetVtable(PyObject *dict, void *vtabptr); /*proto*/
+static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, char *modname); /*proto*/
+static int __Pyx_InternStrings(__Pyx_InternTabEntry *t); /*proto*/
+static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); /*proto*/
+static int __Pyx_InitCApi(PyObject *module); /*proto*/
+static int __Pyx_ImportModuleCApi(__Pyx_CApiTabEntry *t); /*proto*/
+static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name); /*proto*/
+
+static PyObject *__pyx_m;
+static PyObject *__pyx_b;
+static int __pyx_lineno;
+static char *__pyx_filename;
+static char **__pyx_f;
+
+static char __pyx_mdoc[] = "\nInterface to epoll I/O event notification facility.\n";
+
+/* Declarations from _epoll */
+
+
+struct __pyx_obj_6_epoll_epoll {
+ PyObject_HEAD
+ int fd;
+ int initialized;
+};
+
+static PyTypeObject *__pyx_ptype_6_epoll_epoll = 0;
+
+/* Implementation of _epoll */
+
+static PyObject *__pyx_n_CTL_ADD;
+static PyObject *__pyx_n_CTL_DEL;
+static PyObject *__pyx_n_CTL_MOD;
+static PyObject *__pyx_n_IN;
+static PyObject *__pyx_n_OUT;
+static PyObject *__pyx_n_PRI;
+static PyObject *__pyx_n_ERR;
+static PyObject *__pyx_n_HUP;
+static PyObject *__pyx_n_ET;
+static PyObject *__pyx_n_RDNORM;
+static PyObject *__pyx_n_RDBAND;
+static PyObject *__pyx_n_WRNORM;
+static PyObject *__pyx_n_WRBAND;
+static PyObject *__pyx_n_MSG;
+
+static PyObject *__pyx_n_IOError;
+
+static int __pyx_f_6_epoll_5epoll___init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static int __pyx_f_6_epoll_5epoll___init__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ int __pyx_v_size;
+ int __pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ static char *__pyx_argnames[] = {"size",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "i", __pyx_argnames, &__pyx_v_size)) return -1;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":77 */
+ ((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->fd = epoll_create(__pyx_v_size);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":78 */
+ __pyx_1 = (((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->fd == (-1));
+ if (__pyx_1) {
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":79 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_IOError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 79; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(errno); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 79; goto __pyx_L1;}
+ __pyx_4 = PyString_FromString(strerror(errno)); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 79; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(2); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 79; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_4);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_3 = PyObject_Call(__pyx_2, __pyx_5, 0); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 79; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 79; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":80 */
+ ((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->initialized = 1;
+
+ __pyx_r = 0;
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("_epoll.epoll.__init__");
+ __pyx_r = -1;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static void __pyx_f_6_epoll_5epoll___dealloc__(PyObject *__pyx_v_self); /*proto*/
+static void __pyx_f_6_epoll_5epoll___dealloc__(PyObject *__pyx_v_self) {
+ int __pyx_1;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":83 */
+ __pyx_1 = ((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->initialized;
+ if (__pyx_1) {
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":84 */
+ close(((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->fd);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":85 */
+ ((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->initialized = 0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ goto __pyx_L0;
+ __pyx_L1:;
+ __Pyx_AddTraceback("_epoll.epoll.__dealloc__");
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+}
+
+static PyObject *__pyx_f_6_epoll_5epoll_close(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static char __pyx_doc_6_epoll_5epoll_close[] = "\n Close the epoll file descriptor.\n ";
+static PyObject *__pyx_f_6_epoll_5epoll_close(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":91 */
+ __pyx_1 = ((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->initialized;
+ if (__pyx_1) {
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":92 */
+ __pyx_1 = (close(((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->fd) == (-1));
+ if (__pyx_1) {
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":93 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_IOError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 93; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(errno); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 93; goto __pyx_L1;}
+ __pyx_4 = PyString_FromString(strerror(errno)); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 93; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(2); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 93; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_4);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_3 = PyObject_Call(__pyx_2, __pyx_5, 0); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 93; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 93; goto __pyx_L1;}
+ goto __pyx_L3;
+ }
+ __pyx_L3:;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":94 */
+ ((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->initialized = 0;
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("_epoll.epoll.close");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_6_epoll_5epoll_fileno(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static char __pyx_doc_6_epoll_5epoll_fileno[] = "\n Return the epoll file descriptor number.\n ";
+static PyObject *__pyx_f_6_epoll_5epoll_fileno(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":100 */
+ __pyx_1 = PyInt_FromLong(((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->fd); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 100; goto __pyx_L1;}
+ __pyx_r = __pyx_1;
+ __pyx_1 = 0;
+ goto __pyx_L0;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ __Pyx_AddTraceback("_epoll.epoll.fileno");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_f_6_epoll_5epoll__control(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static char __pyx_doc_6_epoll_5epoll__control[] = "\n Modify the monitored state of a particular file descriptor.\n \n Wrap epoll_ctl(2).\n\n @type op: C{int}\n @param op: One of CTL_ADD, CTL_DEL, or CTL_MOD\n\n @type fd: C{int}\n @param fd: File descriptor to modify\n\n @type events: C{int}\n @param events: A bit set of IN, OUT, PRI, ERR, HUP, and ET.\n\n @raise IOError: Raised if the underlying epoll_ctl() call fails.\n ";
+static PyObject *__pyx_f_6_epoll_5epoll__control(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ int __pyx_v_op;
+ int __pyx_v_fd;
+ int __pyx_v_events;
+ int __pyx_v_result;
+ struct epoll_event __pyx_v_evt;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ static char *__pyx_argnames[] = {"op","fd","events",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "iii", __pyx_argnames, &__pyx_v_op, &__pyx_v_fd, &__pyx_v_events)) return 0;
+ Py_INCREF(__pyx_v_self);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":121 */
+ __pyx_v_evt.events = __pyx_v_events;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":122 */
+ __pyx_v_evt.data.fd = __pyx_v_fd;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":123 */
+ __pyx_v_result = epoll_ctl(((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->fd,__pyx_v_op,__pyx_v_fd,(&__pyx_v_evt));
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":124 */
+ __pyx_1 = (__pyx_v_result == (-1));
+ if (__pyx_1) {
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":125 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_IOError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 125; goto __pyx_L1;}
+ __pyx_3 = PyInt_FromLong(errno); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 125; goto __pyx_L1;}
+ __pyx_4 = PyString_FromString(strerror(errno)); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 125; goto __pyx_L1;}
+ __pyx_5 = PyTuple_New(2); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 125; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_4);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_3 = PyObject_Call(__pyx_2, __pyx_5, 0); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 125; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 125; goto __pyx_L1;}
+ goto __pyx_L2;
+ }
+ __pyx_L2:;
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("_epoll.epoll._control");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static PyObject *__pyx_n_append;
+
+static PyObject *__pyx_f_6_epoll_5epoll_wait(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static char __pyx_doc_6_epoll_5epoll_wait[] = "\n Wait for an I/O event, wrap epoll_wait(2).\n\n @type maxevents: C{int}\n @param maxevents: Maximum number of events returned.\n\n @type timeout: C{int}\n @param timeout: Maximum time waiting for events. 0 makes it return\n immediately whereas -1 makes it wait indefinitely.\n \n @raise IOError: Raised if the underlying epoll_wait() call fails.\n ";
+static PyObject *__pyx_f_6_epoll_5epoll_wait(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ unsigned int __pyx_v_maxevents;
+ int __pyx_v_timeout;
+ struct epoll_event (*__pyx_v_events);
+ int __pyx_v_result;
+ int __pyx_v_nbytes;
+ int __pyx_v_fd;
+ PyThreadState (*__pyx_v__save);
+ PyObject *__pyx_v_results;
+ PyObject *__pyx_v_i;
+ PyObject *__pyx_r;
+ int __pyx_1;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ PyObject *__pyx_4 = 0;
+ PyObject *__pyx_5 = 0;
+ long __pyx_6;
+ Py_ssize_t __pyx_7;
+ static char *__pyx_argnames[] = {"maxevents","timeout",0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "Ii", __pyx_argnames, &__pyx_v_maxevents, &__pyx_v_timeout)) return 0;
+ Py_INCREF(__pyx_v_self);
+ __pyx_v_results = Py_None; Py_INCREF(Py_None);
+ __pyx_v_i = Py_None; Py_INCREF(Py_None);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":146 */
+ __pyx_v_nbytes = ((sizeof(struct epoll_event )) * __pyx_v_maxevents);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":147 */
+ __pyx_v_events = ((struct epoll_event (*))malloc(__pyx_v_nbytes));
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":148 */
+ memset(__pyx_v_events,0,__pyx_v_nbytes);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":149 */
+ /*try:*/ {
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":150 */
+ __pyx_v_fd = ((struct __pyx_obj_6_epoll_epoll *)__pyx_v_self)->fd;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":152 */
+ __pyx_v__save = PyEval_SaveThread();
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":153 */
+ __pyx_v_result = epoll_wait(__pyx_v_fd,__pyx_v_events,__pyx_v_maxevents,__pyx_v_timeout);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":154 */
+ PyEval_RestoreThread(__pyx_v__save);
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":156 */
+ __pyx_1 = (__pyx_v_result == (-1));
+ if (__pyx_1) {
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":157 */
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_IOError); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 157; goto __pyx_L3;}
+ __pyx_3 = PyInt_FromLong(errno); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 157; goto __pyx_L3;}
+ __pyx_4 = PyString_FromString(strerror(errno)); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 157; goto __pyx_L3;}
+ __pyx_5 = PyTuple_New(2); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 157; goto __pyx_L3;}
+ PyTuple_SET_ITEM(__pyx_5, 0, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_5, 1, __pyx_4);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_3 = PyObject_Call(__pyx_2, __pyx_5, 0); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 157; goto __pyx_L3;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 157; goto __pyx_L3;}
+ goto __pyx_L5;
+ }
+ __pyx_L5:;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":158 */
+ __pyx_4 = PyList_New(0); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 158; goto __pyx_L3;}
+ Py_DECREF(__pyx_v_results);
+ __pyx_v_results = __pyx_4;
+ __pyx_4 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":159 */
+ for (__pyx_6 = 0; __pyx_6 < __pyx_v_result; ++__pyx_6) {
+ __pyx_2 = PyInt_FromLong(__pyx_6); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 159; goto __pyx_L3;}
+ Py_DECREF(__pyx_v_i);
+ __pyx_v_i = __pyx_2;
+ __pyx_2 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":160 */
+ __pyx_5 = PyObject_GetAttr(__pyx_v_results, __pyx_n_append); if (!__pyx_5) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 160; goto __pyx_L3;}
+ __pyx_7 = PyInt_AsSsize_t(__pyx_v_i); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 160; goto __pyx_L3;}
+ __pyx_3 = PyInt_FromLong((__pyx_v_events[__pyx_7]).data.fd); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 160; goto __pyx_L3;}
+ __pyx_7 = PyInt_AsSsize_t(__pyx_v_i); if (PyErr_Occurred()) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 160; goto __pyx_L3;}
+ __pyx_4 = PyInt_FromLong(((int )(__pyx_v_events[__pyx_7]).events)); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 160; goto __pyx_L3;}
+ __pyx_2 = PyTuple_New(2); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 160; goto __pyx_L3;}
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_3);
+ PyTuple_SET_ITEM(__pyx_2, 1, __pyx_4);
+ __pyx_3 = 0;
+ __pyx_4 = 0;
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 160; goto __pyx_L3;}
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_2);
+ __pyx_2 = 0;
+ __pyx_4 = PyObject_Call(__pyx_5, __pyx_3, 0); if (!__pyx_4) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 160; goto __pyx_L3;}
+ Py_DECREF(__pyx_5); __pyx_5 = 0;
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ Py_DECREF(__pyx_4); __pyx_4 = 0;
+ __pyx_L6:;
+ }
+ __pyx_L7:;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":161 */
+ Py_INCREF(__pyx_v_results);
+ __pyx_r = __pyx_v_results;
+ goto __pyx_L2;
+ }
+ /*finally:*/ {
+ int __pyx_why;
+ __pyx_why = 0; goto __pyx_L4;
+ __pyx_L2: __pyx_why = 3; goto __pyx_L4;
+ __pyx_L3: {
+ __pyx_why = 4;
+ Py_XDECREF(__pyx_2); __pyx_2 = 0;
+ Py_XDECREF(__pyx_5); __pyx_5 = 0;
+ Py_XDECREF(__pyx_3); __pyx_3 = 0;
+ Py_XDECREF(__pyx_4); __pyx_4 = 0;
+ PyErr_Fetch(&__pyx_2, &__pyx_5, &__pyx_3);
+ __pyx_1 = __pyx_lineno;
+ goto __pyx_L4;
+ }
+ __pyx_L4:;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":163 */
+ free(__pyx_v_events);
+ switch (__pyx_why) {
+ case 3: goto __pyx_L0;
+ case 4: {
+ PyErr_Restore(__pyx_2, __pyx_5, __pyx_3);
+ __pyx_lineno = __pyx_1;
+ __pyx_2 = 0;
+ __pyx_5 = 0;
+ __pyx_3 = 0;
+ goto __pyx_L1;
+ }
+ }
+ }
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ Py_XDECREF(__pyx_4);
+ Py_XDECREF(__pyx_5);
+ __Pyx_AddTraceback("_epoll.epoll.wait");
+ __pyx_r = 0;
+ __pyx_L0:;
+ Py_DECREF(__pyx_v_results);
+ Py_DECREF(__pyx_v_i);
+ Py_DECREF(__pyx_v_self);
+ return __pyx_r;
+}
+
+static __Pyx_InternTabEntry __pyx_intern_tab[] = {
+ {&__pyx_n_CTL_ADD, "CTL_ADD"},
+ {&__pyx_n_CTL_DEL, "CTL_DEL"},
+ {&__pyx_n_CTL_MOD, "CTL_MOD"},
+ {&__pyx_n_ERR, "ERR"},
+ {&__pyx_n_ET, "ET"},
+ {&__pyx_n_HUP, "HUP"},
+ {&__pyx_n_IN, "IN"},
+ {&__pyx_n_IOError, "IOError"},
+ {&__pyx_n_MSG, "MSG"},
+ {&__pyx_n_OUT, "OUT"},
+ {&__pyx_n_PRI, "PRI"},
+ {&__pyx_n_RDBAND, "RDBAND"},
+ {&__pyx_n_RDNORM, "RDNORM"},
+ {&__pyx_n_WRBAND, "WRBAND"},
+ {&__pyx_n_WRNORM, "WRNORM"},
+ {&__pyx_n_append, "append"},
+ {0, 0}
+};
+
+static PyObject *__pyx_tp_new_6_epoll_epoll(PyTypeObject *t, PyObject *a, PyObject *k) {
+ PyObject *o = (*t->tp_alloc)(t, 0);
+ struct __pyx_obj_6_epoll_epoll *p = (struct __pyx_obj_6_epoll_epoll *)o;
+ return o;
+}
+
+static void __pyx_tp_dealloc_6_epoll_epoll(PyObject *o) {
+ struct __pyx_obj_6_epoll_epoll *p = (struct __pyx_obj_6_epoll_epoll *)o;
+ {
+ PyObject *etype, *eval, *etb;
+ PyErr_Fetch(&etype, &eval, &etb);
+ ++o->ob_refcnt;
+ __pyx_f_6_epoll_5epoll___dealloc__(o);
+ if (PyErr_Occurred()) PyErr_WriteUnraisable(o);
+ --o->ob_refcnt;
+ PyErr_Restore(etype, eval, etb);
+ }
+ (*o->ob_type->tp_free)(o);
+}
+
+static int __pyx_tp_traverse_6_epoll_epoll(PyObject *o, visitproc v, void *a) {
+ int e;
+ struct __pyx_obj_6_epoll_epoll *p = (struct __pyx_obj_6_epoll_epoll *)o;
+ return 0;
+}
+
+static int __pyx_tp_clear_6_epoll_epoll(PyObject *o) {
+ struct __pyx_obj_6_epoll_epoll *p = (struct __pyx_obj_6_epoll_epoll *)o;
+ return 0;
+}
+
+static struct PyMethodDef __pyx_methods_6_epoll_epoll[] = {
+ {"close", (PyCFunction)__pyx_f_6_epoll_5epoll_close, METH_VARARGS|METH_KEYWORDS, __pyx_doc_6_epoll_5epoll_close},
+ {"fileno", (PyCFunction)__pyx_f_6_epoll_5epoll_fileno, METH_VARARGS|METH_KEYWORDS, __pyx_doc_6_epoll_5epoll_fileno},
+ {"_control", (PyCFunction)__pyx_f_6_epoll_5epoll__control, METH_VARARGS|METH_KEYWORDS, __pyx_doc_6_epoll_5epoll__control},
+ {"wait", (PyCFunction)__pyx_f_6_epoll_5epoll_wait, METH_VARARGS|METH_KEYWORDS, __pyx_doc_6_epoll_5epoll_wait},
+ {0, 0, 0, 0}
+};
+
+static PyNumberMethods __pyx_tp_as_number_epoll = {
+ 0, /*nb_add*/
+ 0, /*nb_subtract*/
+ 0, /*nb_multiply*/
+ 0, /*nb_divide*/
+ 0, /*nb_remainder*/
+ 0, /*nb_divmod*/
+ 0, /*nb_power*/
+ 0, /*nb_negative*/
+ 0, /*nb_positive*/
+ 0, /*nb_absolute*/
+ 0, /*nb_nonzero*/
+ 0, /*nb_invert*/
+ 0, /*nb_lshift*/
+ 0, /*nb_rshift*/
+ 0, /*nb_and*/
+ 0, /*nb_xor*/
+ 0, /*nb_or*/
+ 0, /*nb_coerce*/
+ 0, /*nb_int*/
+ 0, /*nb_long*/
+ 0, /*nb_float*/
+ 0, /*nb_oct*/
+ 0, /*nb_hex*/
+ 0, /*nb_inplace_add*/
+ 0, /*nb_inplace_subtract*/
+ 0, /*nb_inplace_multiply*/
+ 0, /*nb_inplace_divide*/
+ 0, /*nb_inplace_remainder*/
+ 0, /*nb_inplace_power*/
+ 0, /*nb_inplace_lshift*/
+ 0, /*nb_inplace_rshift*/
+ 0, /*nb_inplace_and*/
+ 0, /*nb_inplace_xor*/
+ 0, /*nb_inplace_or*/
+ 0, /*nb_floor_divide*/
+ 0, /*nb_true_divide*/
+ 0, /*nb_inplace_floor_divide*/
+ 0, /*nb_inplace_true_divide*/
+};
+
+static PySequenceMethods __pyx_tp_as_sequence_epoll = {
+ 0, /*sq_length*/
+ 0, /*sq_concat*/
+ 0, /*sq_repeat*/
+ 0, /*sq_item*/
+ 0, /*sq_slice*/
+ 0, /*sq_ass_item*/
+ 0, /*sq_ass_slice*/
+ 0, /*sq_contains*/
+ 0, /*sq_inplace_concat*/
+ 0, /*sq_inplace_repeat*/
+};
+
+static PyMappingMethods __pyx_tp_as_mapping_epoll = {
+ 0, /*mp_length*/
+ 0, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
+};
+
+static PyBufferProcs __pyx_tp_as_buffer_epoll = {
+ 0, /*bf_getreadbuffer*/
+ 0, /*bf_getwritebuffer*/
+ 0, /*bf_getsegcount*/
+ 0, /*bf_getcharbuffer*/
+};
+
+PyTypeObject __pyx_type_6_epoll_epoll = {
+ PyObject_HEAD_INIT(0)
+ 0, /*ob_size*/
+ "_epoll.epoll", /*tp_name*/
+ sizeof(struct __pyx_obj_6_epoll_epoll), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ __pyx_tp_dealloc_6_epoll_epoll, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ &__pyx_tp_as_number_epoll, /*tp_as_number*/
+ &__pyx_tp_as_sequence_epoll, /*tp_as_sequence*/
+ &__pyx_tp_as_mapping_epoll, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ &__pyx_tp_as_buffer_epoll, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_CHECKTYPES|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+ "\n Represent a set of file descriptors being monitored for events.\n ", /*tp_doc*/
+ __pyx_tp_traverse_6_epoll_epoll, /*tp_traverse*/
+ __pyx_tp_clear_6_epoll_epoll, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ __pyx_methods_6_epoll_epoll, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ __pyx_f_6_epoll_5epoll___init__, /*tp_init*/
+ 0, /*tp_alloc*/
+ __pyx_tp_new_6_epoll_epoll, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+ 0, /*tp_bases*/
+ 0, /*tp_mro*/
+ 0, /*tp_cache*/
+ 0, /*tp_subclasses*/
+ 0, /*tp_weaklist*/
+};
+
+static struct PyMethodDef __pyx_methods[] = {
+ {0, 0, 0, 0}
+};
+
+static void __pyx_init_filenames(void); /*proto*/
+
+PyMODINIT_FUNC init_epoll(void); /*proto*/
+PyMODINIT_FUNC init_epoll(void) {
+ PyObject *__pyx_1 = 0;
+ __pyx_init_filenames();
+ __pyx_m = Py_InitModule4("_epoll", __pyx_methods, __pyx_mdoc, 0, PYTHON_API_VERSION);
+ if (!__pyx_m) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
+ __pyx_b = PyImport_AddModule("__builtin__");
+ if (!__pyx_b) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
+ if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
+ if (__Pyx_InternStrings(__pyx_intern_tab) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
+ if (PyType_Ready(&__pyx_type_6_epoll_epoll) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 68; goto __pyx_L1;}
+ if (PyObject_SetAttrString(__pyx_m, "epoll", (PyObject *)&__pyx_type_6_epoll_epoll) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 68; goto __pyx_L1;}
+ __pyx_ptype_6_epoll_epoll = &__pyx_type_6_epoll_epoll;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":165 */
+ __pyx_1 = PyInt_FromLong(EPOLL_CTL_ADD); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 165; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_CTL_ADD, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 165; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":166 */
+ __pyx_1 = PyInt_FromLong(EPOLL_CTL_DEL); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 166; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_CTL_DEL, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 166; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":167 */
+ __pyx_1 = PyInt_FromLong(EPOLL_CTL_MOD); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 167; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_CTL_MOD, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 167; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":169 */
+ __pyx_1 = PyInt_FromLong(EPOLLIN); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 169; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_IN, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 169; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":170 */
+ __pyx_1 = PyInt_FromLong(EPOLLOUT); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 170; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_OUT, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 170; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":171 */
+ __pyx_1 = PyInt_FromLong(EPOLLPRI); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 171; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_PRI, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 171; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":172 */
+ __pyx_1 = PyInt_FromLong(EPOLLERR); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 172; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ERR, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 172; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":173 */
+ __pyx_1 = PyInt_FromLong(EPOLLHUP); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 173; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_HUP, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 173; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":174 */
+ __pyx_1 = PyInt_FromLong(EPOLLET); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 174; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_ET, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 174; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":176 */
+ __pyx_1 = PyInt_FromLong(EPOLLRDNORM); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 176; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_RDNORM, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 176; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":177 */
+ __pyx_1 = PyInt_FromLong(EPOLLRDBAND); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 177; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_RDBAND, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 177; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":178 */
+ __pyx_1 = PyInt_FromLong(EPOLLWRNORM); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 178; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_WRNORM, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 178; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":179 */
+ __pyx_1 = PyInt_FromLong(EPOLLWRBAND); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 179; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_WRBAND, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 179; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/exarkun/Projects/Twisted/branches/epollreactor-1953-2/twisted/python/_epoll.pyx":180 */
+ __pyx_1 = PyInt_FromLong(EPOLLMSG); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 180; goto __pyx_L1;}
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_MSG, __pyx_1) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 180; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ return;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ __Pyx_AddTraceback("_epoll");
+}
+
+static char *__pyx_filenames[] = {
+ "_epoll.pyx",
+};
+
+/* Runtime support code */
+
+static void __pyx_init_filenames(void) {
+ __pyx_f = __pyx_filenames;
+}
+
+static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name) {
+ PyObject *result;
+ result = PyObject_GetAttr(dict, name);
+ if (!result)
+ PyErr_SetObject(PyExc_NameError, name);
+ return result;
+}
+
+static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb) {
+ Py_XINCREF(type);
+ Py_XINCREF(value);
+ Py_XINCREF(tb);
+ /* First, check the traceback argument, replacing None with NULL. */
+ if (tb == Py_None) {
+ Py_DECREF(tb);
+ tb = 0;
+ }
+ else if (tb != NULL && !PyTraceBack_Check(tb)) {
+ PyErr_SetString(PyExc_TypeError,
+ "raise: arg 3 must be a traceback or None");
+ goto raise_error;
+ }
+ /* Next, replace a missing value with None */
+ if (value == NULL) {
+ value = Py_None;
+ Py_INCREF(value);
+ }
+ /* Next, repeatedly, replace a tuple exception with its first item */
+ while (PyTuple_Check(type) && PyTuple_Size(type) > 0) {
+ PyObject *tmp = type;
+ type = PyTuple_GET_ITEM(type, 0);
+ Py_INCREF(type);
+ Py_DECREF(tmp);
+ }
+ if (PyString_CheckExact(type)) {
+ /* Raising builtin string is deprecated but still allowed --
+ * do nothing. Raising an instance of a new-style str
+ * subclass is right out. */
+ if (PyErr_Warn(PyExc_DeprecationWarning,
+ "raising a string exception is deprecated"))
+ goto raise_error;
+ }
+ else if (PyType_Check(type) || PyClass_Check(type))
+ ; /* PyErr_NormalizeException(&type, &value, &tb); */
+ else if (PyInstance_Check(type)) {
+ /* Raising an instance. The value should be a dummy. */
+ if (value != Py_None) {
+ PyErr_SetString(PyExc_TypeError,
+ "instance exception may not have a separate value");
+ goto raise_error;
+ }
+ else {
+ /* Normalize to raise <class>, <instance> */
+ Py_DECREF(value);
+ value = type;
+ type = (PyObject*) ((PyInstanceObject*)type)->in_class;
+ Py_INCREF(type);
+ }
+ }
+ else if (PyType_IsSubtype(type->ob_type, (PyTypeObject*)PyExc_Exception)) {
+ /* Raising a new-style object (in Py2.5).
+ The value should be a dummy. */
+ if (value != Py_None) {
+ PyErr_SetString(PyExc_TypeError,
+ "instance exception may not have a separate value");
+ goto raise_error;
+ }
+ else {
+ /* Normalize to raise <class>, <instance> */
+ Py_DECREF(value);
+ value = type;
+ type = (PyObject*) type->ob_type;
+ Py_INCREF(type);
+ }
+ }
+ else {
+ /* Not something you can raise. You get an exception
+ anyway, just not what you specified :-) */
+ PyErr_Format(PyExc_TypeError,
+ "exceptions must be classes, instances, or "
+ "strings (deprecated), not %s",
+ type->ob_type->tp_name);
+ goto raise_error;
+ }
+ PyErr_Restore(type, value, tb);
+ return;
+raise_error:
+ Py_XDECREF(value);
+ Py_XDECREF(type);
+ Py_XDECREF(tb);
+ return;
+}
+
+static int __Pyx_InternStrings(__Pyx_InternTabEntry *t) {
+ while (t->p) {
+ *t->p = PyString_InternFromString(t->s);
+ if (!*t->p)
+ return -1;
+ ++t;
+ }
+ return 0;
+}
+
+#include "compile.h"
+#include "frameobject.h"
+#include "traceback.h"
+
+static void __Pyx_AddTraceback(char *funcname) {
+ PyObject *py_srcfile = 0;
+ PyObject *py_funcname = 0;
+ PyObject *py_globals = 0;
+ PyObject *empty_tuple = 0;
+ PyObject *empty_string = 0;
+ PyCodeObject *py_code = 0;
+ PyFrameObject *py_frame = 0;
+
+ py_srcfile = PyString_FromString(__pyx_filename);
+ if (!py_srcfile) goto bad;
+ py_funcname = PyString_FromString(funcname);
+ if (!py_funcname) goto bad;
+ py_globals = PyModule_GetDict(__pyx_m);
+ if (!py_globals) goto bad;
+ empty_tuple = PyTuple_New(0);
+ if (!empty_tuple) goto bad;
+ empty_string = PyString_FromString("");
+ if (!empty_string) goto bad;
+ py_code = PyCode_New(
+ 0, /*int argcount,*/
+ 0, /*int nlocals,*/
+ 0, /*int stacksize,*/
+ 0, /*int flags,*/
+ empty_string, /*PyObject *code,*/
+ empty_tuple, /*PyObject *consts,*/
+ empty_tuple, /*PyObject *names,*/
+ empty_tuple, /*PyObject *varnames,*/
+ empty_tuple, /*PyObject *freevars,*/
+ empty_tuple, /*PyObject *cellvars,*/
+ py_srcfile, /*PyObject *filename,*/
+ py_funcname, /*PyObject *name,*/
+ __pyx_lineno, /*int firstlineno,*/
+ empty_string /*PyObject *lnotab*/
+ );
+ if (!py_code) goto bad;
+ py_frame = PyFrame_New(
+ PyThreadState_Get(), /*PyThreadState *tstate,*/
+ py_code, /*PyCodeObject *code,*/
+ py_globals, /*PyObject *globals,*/
+ 0 /*PyObject *locals*/
+ );
+ if (!py_frame) goto bad;
+ py_frame->f_lineno = __pyx_lineno;
+ PyTraceBack_Here(py_frame);
+bad:
+ Py_XDECREF(py_srcfile);
+ Py_XDECREF(py_funcname);
+ Py_XDECREF(empty_tuple);
+ Py_XDECREF(empty_string);
+ Py_XDECREF(py_code);
+ Py_XDECREF(py_frame);
+}
diff --git a/vendor/Twisted-10.0.0/twisted/python/_epoll.pyx b/vendor/Twisted-10.0.0/twisted/python/_epoll.pyx
new file mode 100644
index 0000000000..3a3b1cecac
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/_epoll.pyx
@@ -0,0 +1,181 @@
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Interface to epoll I/O event notification facility.
+"""
+
+# NOTE: The version of Pyrex you are using probably _does not work_ with
+# Python 2.5. If you need to recompile this file, _make sure you are using
+# a version of Pyrex which works with Python 2.5_. I am using 0.9.4.1 from
+# <http://codespeak.net/svn/lxml/pyrex/>. -exarkun
+
+cdef extern from "stdio.h":
+ cdef extern void *malloc(int)
+ cdef extern void free(void *)
+ cdef extern int close(int)
+
+cdef extern from "errno.h":
+ cdef extern int errno
+ cdef extern char *strerror(int)
+
+cdef extern from "string.h":
+ cdef extern void *memset(void* s, int c, int n)
+
+cdef extern from "stdint.h":
+ ctypedef unsigned long uint32_t
+ ctypedef unsigned long long uint64_t
+
+cdef extern from "sys/epoll.h":
+
+ cdef enum:
+ EPOLL_CTL_ADD = 1
+ EPOLL_CTL_DEL = 2
+ EPOLL_CTL_MOD = 3
+
+ cdef enum EPOLL_EVENTS:
+ EPOLLIN = 0x001
+ EPOLLPRI = 0x002
+ EPOLLOUT = 0x004
+ EPOLLRDNORM = 0x040
+ EPOLLRDBAND = 0x080
+ EPOLLWRNORM = 0x100
+ EPOLLWRBAND = 0x200
+ EPOLLMSG = 0x400
+ EPOLLERR = 0x008
+ EPOLLHUP = 0x010
+ EPOLLET = (1 << 31)
+
+ ctypedef union epoll_data_t:
+ void *ptr
+ int fd
+ uint32_t u32
+ uint64_t u64
+
+ cdef struct epoll_event:
+ uint32_t events
+ epoll_data_t data
+
+ int epoll_create(int size)
+ int epoll_ctl(int epfd, int op, int fd, epoll_event *event)
+ int epoll_wait(int epfd, epoll_event *events, int maxevents, int timeout)
+
+cdef extern from "Python.h":
+ ctypedef struct PyThreadState
+ cdef extern PyThreadState *PyEval_SaveThread()
+ cdef extern void PyEval_RestoreThread(PyThreadState*)
+
+cdef class epoll:
+ """
+ Represent a set of file descriptors being monitored for events.
+ """
+
+ cdef int fd
+ cdef int initialized
+
+ def __init__(self, int size):
+ self.fd = epoll_create(size)
+ if self.fd == -1:
+ raise IOError(errno, strerror(errno))
+ self.initialized = 1
+
+ def __dealloc__(self):
+ if self.initialized:
+ close(self.fd)
+ self.initialized = 0
+
+ def close(self):
+ """
+ Close the epoll file descriptor.
+ """
+ if self.initialized:
+ if close(self.fd) == -1:
+ raise IOError(errno, strerror(errno))
+ self.initialized = 0
+
+ def fileno(self):
+ """
+ Return the epoll file descriptor number.
+ """
+ return self.fd
+
+ def _control(self, int op, int fd, int events):
+ """
+ Modify the monitored state of a particular file descriptor.
+
+ Wrap epoll_ctl(2).
+
+ @type op: C{int}
+ @param op: One of CTL_ADD, CTL_DEL, or CTL_MOD
+
+ @type fd: C{int}
+ @param fd: File descriptor to modify
+
+ @type events: C{int}
+ @param events: A bit set of IN, OUT, PRI, ERR, HUP, and ET.
+
+ @raise IOError: Raised if the underlying epoll_ctl() call fails.
+ """
+ cdef int result
+ cdef epoll_event evt
+ evt.events = events
+ evt.data.fd = fd
+ result = epoll_ctl(self.fd, op, fd, &evt)
+ if result == -1:
+ raise IOError(errno, strerror(errno))
+
+ def wait(self, unsigned int maxevents, int timeout):
+ """
+ Wait for an I/O event, wrap epoll_wait(2).
+
+ @type maxevents: C{int}
+ @param maxevents: Maximum number of events returned.
+
+ @type timeout: C{int}
+ @param timeout: Maximum time waiting for events. 0 makes it return
+ immediately whereas -1 makes it wait indefinitely.
+
+ @raise IOError: Raised if the underlying epoll_wait() call fails.
+ """
+ cdef epoll_event *events
+ cdef int result
+ cdef int nbytes
+ cdef int fd
+ cdef PyThreadState *_save
+
+ nbytes = sizeof(epoll_event) * maxevents
+ events = <epoll_event*>malloc(nbytes)
+ memset(events, 0, nbytes)
+ try:
+ fd = self.fd
+
+ _save = PyEval_SaveThread()
+ result = epoll_wait(fd, events, maxevents, timeout)
+ PyEval_RestoreThread(_save)
+
+ if result == -1:
+ raise IOError(errno, strerror(errno))
+ results = []
+ for i from 0 <= i < result:
+ results.append((events[i].data.fd, <int>events[i].events))
+ return results
+ finally:
+ free(events)
+
+CTL_ADD = EPOLL_CTL_ADD
+CTL_DEL = EPOLL_CTL_DEL
+CTL_MOD = EPOLL_CTL_MOD
+
+IN = EPOLLIN
+OUT = EPOLLOUT
+PRI = EPOLLPRI
+ERR = EPOLLERR
+HUP = EPOLLHUP
+ET = EPOLLET
+
+RDNORM = EPOLLRDNORM
+RDBAND = EPOLLRDBAND
+WRNORM = EPOLLWRNORM
+WRBAND = EPOLLWRBAND
+MSG = EPOLLMSG
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/_initgroups.c b/vendor/Twisted-10.0.0/twisted/python/_initgroups.c
new file mode 100644
index 0000000000..93500b524e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/_initgroups.c
@@ -0,0 +1,66 @@
+/*****************************************************************************
+
+ Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
+
+ This software is subject to the provisions of the Zope Public License,
+ Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+ WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+ FOR A PARTICULAR PURPOSE
+
+ ****************************************************************************/
+
+/*
+ * This has been reported for inclusion in Python here: http://bugs.python.org/issue7333
+ * Hopefully we may be able to remove this file in some years.
+ */
+
+#include "Python.h"
+
+#if defined(__unix__) || defined(unix) || defined(__NetBSD__) || defined(__MACH__) /* Mac OS X */
+
+#include <grp.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+static PyObject *
+initgroups_initgroups(PyObject *self, PyObject *args)
+{
+ char *username;
+ unsigned int igid;
+ gid_t gid;
+
+ if (!PyArg_ParseTuple(args, "sI:initgroups", &username, &igid))
+ return NULL;
+
+ gid = igid;
+
+ if (initgroups(username, gid) == -1)
+ return PyErr_SetFromErrno(PyExc_OSError);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyMethodDef InitgroupsMethods[] = {
+ {"initgroups", initgroups_initgroups, METH_VARARGS},
+ {NULL, NULL}
+};
+
+#else
+
+/* This module is empty on non-UNIX systems. */
+
+static PyMethodDef InitgroupsMethods[] = {
+ {NULL, NULL}
+};
+
+#endif /* defined(__unix__) || defined(unix) */
+
+void
+init_initgroups(void)
+{
+ Py_InitModule("_initgroups", InitgroupsMethods);
+}
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/_release.py b/vendor/Twisted-10.0.0/twisted/python/_release.py
new file mode 100644
index 0000000000..ceb5ef5b79
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/_release.py
@@ -0,0 +1,1265 @@
+# -*- test-case-name: twisted.python.test.test_release -*-
+# Copyright (c) 2007-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Twisted's automated release system.
+
+This module is only for use within Twisted's release system. If you are anyone
+else, do not use it. The interface and behaviour will change without notice.
+
+Only Linux is supported by this code. It should not be used by any tools
+which must run on multiple platforms (eg the setup.py script).
+"""
+
+import textwrap
+from datetime import date
+import sys
+import os
+from tempfile import mkdtemp
+import tarfile
+
+# Popen4 isn't available on Windows. BookBuilder won't work on Windows, but
+# we don't care. -exarkun
+try:
+ from popen2 import Popen4
+except ImportError:
+ Popen4 = None
+
+from twisted.python.versions import Version
+from twisted.python.filepath import FilePath
+from twisted.python.dist import twisted_subprojects
+
+# This import is an example of why you shouldn't use this module unless you're
+# radix
+try:
+ from twisted.lore.scripts import lore
+except ImportError:
+ pass
+
+# The offset between a year and the corresponding major version number.
+VERSION_OFFSET = 2000
+
+
+def runCommand(args):
+ """
+ Execute a vector of arguments.
+
+ @type args: C{list} of C{str}
+ @param args: A list of arguments, the first of which will be used as the
+ executable to run.
+
+ @rtype: C{str}
+ @return: All of the standard output.
+
+ @raise CommandFailed: when the program exited with a non-0 exit code.
+ """
+ process = Popen4(args)
+ stdout = process.fromchild.read()
+ exitCode = process.wait()
+ if os.WIFSIGNALED(exitCode) or os.WEXITSTATUS(exitCode):
+ raise CommandFailed(exitCode, stdout)
+ return stdout
+
+
+class CommandFailed(Exception):
+ """
+ Raised when a child process exits unsuccessfully.
+
+ @type exitCode: C{int}
+ @ivar exitCode: The exit code for the child process.
+
+ @type output: C{str}
+ @ivar output: The bytes read from stdout and stderr of the child process.
+ """
+ def __init__(self, exitCode, output):
+ Exception.__init__(self, exitCode, output)
+ self.exitCode = exitCode
+ self.output = output
+
+
+
+def _changeVersionInFile(old, new, filename):
+ """
+ Replace the C{old} version number with the C{new} one in the given
+ C{filename}.
+ """
+ replaceInFile(filename, {old.base(): new.base()})
+
+
+
+def getNextVersion(version, now=None):
+ """
+ Calculate the version number for a new release of Twisted based on
+ the previous version number.
+
+ @param version: The previous version number.
+ @param now: (optional) The current date.
+ """
+ # XXX: This has no way of incrementing the patch number. Currently, we
+ # don't need it. See bug 2915. Jonathan Lange, 2007-11-20.
+ if now is None:
+ now = date.today()
+ major = now.year - VERSION_OFFSET
+ if major != version.major:
+ minor = 0
+ else:
+ minor = version.minor + 1
+ return Version(version.package, major, minor, 0)
+
+
+def changeAllProjectVersions(root, versionTemplate):
+ """
+ Change the version of all projects (including core and all subprojects).
+
+ @type root: L{FilePath}
+ @param root: The root of the Twisted source tree.
+ @type versionTemplate: L{Version}
+ @param versionTemplate: The version of all projects. The name will be
+ replaced for each respective project.
+ """
+ for project in findTwistedProjects(root):
+ if project.directory.basename() == "twisted":
+ packageName = "twisted"
+ else:
+ packageName = "twisted." + project.directory.basename()
+ version = Version(packageName, versionTemplate.major,
+ versionTemplate.minor, versionTemplate.micro,
+ prerelease=versionTemplate.prerelease)
+
+ # The placement of the top-level README with respect to other files (eg
+ # _version.py) is sufficiently different from the others that we just
+ # have to handle it specially.
+ if packageName == "twisted":
+ _changeVersionInFile(
+ project.getVersion(), version, root.child('README').path)
+
+ project.updateVersion(version)
+
+
+
+
+class Project(object):
+ """
+ A representation of a project that has a version.
+
+ @ivar directory: A L{twisted.python.filepath.FilePath} pointing to the base
+ directory of a Twisted-style Python package. The package should contain
+ a C{_version.py} file and a C{topfiles} directory that contains a
+ C{README} file.
+ """
+
+ def __init__(self, directory):
+ self.directory = directory
+
+
+ def __repr__(self):
+ return '%s(%r)' % (
+ self.__class__.__name__, self.directory)
+
+
+ def getVersion(self):
+ """
+ @return: A L{Version} specifying the version number of the project
+ based on live python modules.
+ """
+ namespace = {}
+ execfile(self.directory.child("_version.py").path, namespace)
+ return namespace["version"]
+
+
+ def updateVersion(self, version):
+ """
+ Replace the existing version numbers in _version.py and README files
+ with the specified version.
+ """
+ oldVersion = self.getVersion()
+ replaceProjectVersion(self.directory.child("_version.py").path,
+ version)
+ _changeVersionInFile(
+ oldVersion, version,
+ self.directory.child("topfiles").child("README").path)
+
+
+
+def findTwistedProjects(baseDirectory):
+ """
+ Find all Twisted-style projects beneath a base directory.
+
+ @param baseDirectory: A L{twisted.python.filepath.FilePath} to look inside.
+ @return: A list of L{Project}.
+ """
+ projects = []
+ for filePath in baseDirectory.walk():
+ if filePath.basename() == 'topfiles':
+ projectDirectory = filePath.parent()
+ projects.append(Project(projectDirectory))
+ return projects
+
+
+
+def updateTwistedVersionInformation(baseDirectory, now):
+ """
+ Update the version information for Twisted and all subprojects to the
+ date-based version number.
+
+ @param baseDirectory: Where to look for Twisted. If None, the function
+ infers the information from C{twisted.__file__}.
+ @param now: The current date (as L{datetime.date}). If None, it defaults
+ to today.
+ """
+ for project in findTwistedProjects(baseDirectory):
+ project.updateVersion(getNextVersion(project.getVersion(), now=now))
+
+
+def generateVersionFileData(version):
+ """
+ Generate the data to be placed into a _version.py file.
+
+ @param version: A version object.
+ """
+ if version.prerelease is not None:
+ prerelease = ", prerelease=%r" % (version.prerelease,)
+ else:
+ prerelease = ""
+ data = '''\
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version(%r, %s, %s, %s%s)
+''' % (version.package, version.major, version.minor, version.micro, prerelease)
+ return data
+
+
+def replaceProjectVersion(filename, newversion):
+ """
+ Write version specification code into the given filename, which
+ sets the version to the given version number.
+
+ @param filename: A filename which is most likely a "_version.py"
+ under some Twisted project.
+ @param newversion: A version object.
+ """
+ # XXX - this should be moved to Project and renamed to writeVersionFile.
+ # jml, 2007-11-15.
+ f = open(filename, 'w')
+ f.write(generateVersionFileData(newversion))
+ f.close()
+
+
+
+def replaceInFile(filename, oldToNew):
+ """
+ I replace the text `oldstr' with `newstr' in `filename' using science.
+ """
+ os.rename(filename, filename+'.bak')
+ f = open(filename+'.bak')
+ d = f.read()
+ f.close()
+ for k,v in oldToNew.items():
+ d = d.replace(k, v)
+ f = open(filename + '.new', 'w')
+ f.write(d)
+ f.close()
+ os.rename(filename+'.new', filename)
+ os.unlink(filename+'.bak')
+
+
+
+class NoDocumentsFound(Exception):
+ """
+ Raised when no input documents are found.
+ """
+
+
+
+class LoreBuilderMixin(object):
+ """
+ Base class for builders which invoke lore.
+ """
+ def lore(self, arguments):
+ """
+ Run lore with the given arguments.
+
+ @param arguments: A C{list} of C{str} giving command line arguments to
+ lore which should be used.
+ """
+ options = lore.Options()
+ options.parseOptions(["--null"] + arguments)
+ lore.runGivenOptions(options)
+
+
+
+class DocBuilder(LoreBuilderMixin):
+ """
+ Generate HTML documentation for projects.
+ """
+
+ def build(self, version, resourceDir, docDir, template, apiBaseURL=None,
+ deleteInput=False):
+ """
+ Build the documentation in C{docDir} with Lore.
+
+ Input files ending in .xhtml will be considered. Output will written as
+ .html files.
+
+ @param version: the version of the documentation to pass to lore.
+ @type version: C{str}
+
+ @param resourceDir: The directory which contains the toplevel index and
+ stylesheet file for this section of documentation.
+ @type resourceDir: L{twisted.python.filepath.FilePath}
+
+ @param docDir: The directory of the documentation.
+ @type docDir: L{twisted.python.filepath.FilePath}
+
+ @param template: The template used to generate the documentation.
+ @type template: L{twisted.python.filepath.FilePath}
+
+ @type apiBaseURL: C{str} or C{NoneType}
+ @param apiBaseURL: A format string which will be interpolated with the
+ fully-qualified Python name for each API link. For example, to
+ generate the Twisted 8.0.0 documentation, pass
+ C{"http://twistedmatrix.com/documents/8.0.0/api/%s.html"}.
+
+ @param deleteInput: If True, the input documents will be deleted after
+ their output is generated.
+ @type deleteInput: C{bool}
+
+ @raise NoDocumentsFound: When there are no .xhtml files in the given
+ C{docDir}.
+ """
+ linkrel = self.getLinkrel(resourceDir, docDir)
+ inputFiles = docDir.globChildren("*.xhtml")
+ filenames = [x.path for x in inputFiles]
+ if not filenames:
+ raise NoDocumentsFound("No input documents found in %s" % (docDir,))
+ if apiBaseURL is not None:
+ arguments = ["--config", "baseurl=" + apiBaseURL]
+ else:
+ arguments = []
+ arguments.extend(["--config", "template=%s" % (template.path,),
+ "--config", "ext=.html",
+ "--config", "version=%s" % (version,),
+ "--linkrel", linkrel] + filenames)
+ self.lore(arguments)
+ if deleteInput:
+ for inputFile in inputFiles:
+ inputFile.remove()
+
+
+ def getLinkrel(self, resourceDir, docDir):
+ """
+ Calculate a value appropriate for Lore's --linkrel option.
+
+ Lore's --linkrel option defines how to 'find' documents that are
+ linked to from TEMPLATE files (NOT document bodies). That is, it's a
+ prefix for links ('a' and 'link') in the template.
+
+ @param resourceDir: The directory which contains the toplevel index and
+ stylesheet file for this section of documentation.
+ @type resourceDir: L{twisted.python.filepath.FilePath}
+
+ @param docDir: The directory containing documents that must link to
+ C{resourceDir}.
+ @type docDir: L{twisted.python.filepath.FilePath}
+ """
+ if resourceDir != docDir:
+ return '/'.join(filePathDelta(docDir, resourceDir)) + "/"
+ else:
+ return ""
+
+
+
+class ManBuilder(LoreBuilderMixin):
+ """
+ Generate man pages of the different existing scripts.
+ """
+
+ def build(self, manDir):
+ """
+ Generate Lore input files from the man pages in C{manDir}.
+
+ Input files ending in .1 will be considered. Output will written as
+ -man.xhtml files.
+
+ @param manDir: The directory of the man pages.
+ @type manDir: L{twisted.python.filepath.FilePath}
+
+ @raise NoDocumentsFound: When there are no .1 files in the given
+ C{manDir}.
+ """
+ inputFiles = manDir.globChildren("*.1")
+ filenames = [x.path for x in inputFiles]
+ if not filenames:
+ raise NoDocumentsFound("No manual pages found in %s" % (manDir,))
+ arguments = ["--input", "man",
+ "--output", "lore",
+ "--config", "ext=-man.xhtml"] + filenames
+ self.lore(arguments)
+
+
+
+class APIBuilder(object):
+ """
+ Generate API documentation from source files using
+ U{pydoctor<http://codespeak.net/~mwh/pydoctor/>}. This requires
+ pydoctor to be installed and usable (which means you won't be able to
+ use it with Python 2.3).
+ """
+ def build(self, projectName, projectURL, sourceURL, packagePath,
+ outputPath):
+ """
+ Call pydoctor's entry point with options which will generate HTML
+ documentation for the specified package's API.
+
+ @type projectName: C{str}
+ @param projectName: The name of the package for which to generate
+ documentation.
+
+ @type projectURL: C{str}
+ @param projectURL: The location (probably an HTTP URL) of the project
+ on the web.
+
+ @type sourceURL: C{str}
+ @param sourceURL: The location (probably an HTTP URL) of the root of
+ the source browser for the project.
+
+ @type packagePath: L{FilePath}
+ @param packagePath: The path to the top-level of the package named by
+ C{projectName}.
+
+ @type outputPath: L{FilePath}
+ @param outputPath: An existing directory to which the generated API
+ documentation will be written.
+ """
+ from pydoctor.driver import main
+ main(
+ ["--project-name", projectName,
+ "--project-url", projectURL,
+ "--system-class", "pydoctor.twistedmodel.TwistedSystem",
+ "--project-base-dir", packagePath.parent().path,
+ "--html-viewsource-base", sourceURL,
+ "--add-package", packagePath.path,
+ "--html-output", outputPath.path,
+ "--html-write-function-pages", "--quiet", "--make-html"])
+
+
+
+class BookBuilder(LoreBuilderMixin):
+ """
+ Generate the LaTeX and PDF documentation.
+
+ The book is built by assembling a number of LaTeX documents. Only the
+ overall document which describes how to assemble the documents is stored
+ in LaTeX in the source. The rest of the documentation is generated from
+ Lore input files. These are primarily XHTML files (of the particular
+ Lore subset), but man pages are stored in GROFF format. BookBuilder
+ expects all of its input to be Lore XHTML format, so L{ManBuilder}
+ should be invoked first if the man pages are to be included in the
+ result (this is determined by the book LaTeX definition file).
+ Therefore, a sample usage of BookBuilder may look something like this::
+
+ man = ManBuilder()
+ man.build(FilePath("doc/core/man"))
+ book = BookBuilder()
+ book.build(
+ FilePath('doc/core/howto'),
+ [FilePath('doc/core/howto'), FilePath('doc/core/howto/tutorial'),
+ FilePath('doc/core/man'), FilePath('doc/core/specifications')],
+ FilePath('doc/core/howto/book.tex'), FilePath('/tmp/book.pdf'))
+ """
+ def run(self, command):
+ """
+ Execute a command in a child process and return the output.
+
+ @type command: C{str}
+ @param command: The shell command to run.
+
+ @raise CommandFailed: If the child process exits with an error.
+ """
+ return runCommand(command)
+
+
+ def buildTeX(self, howtoDir):
+ """
+ Build LaTeX files for lore input files in the given directory.
+
+ Input files ending in .xhtml will be considered. Output will written as
+ .tex files.
+
+ @type howtoDir: L{FilePath}
+ @param howtoDir: A directory containing lore input files.
+
+ @raise ValueError: If C{howtoDir} does not exist.
+ """
+ if not howtoDir.exists():
+ raise ValueError("%r does not exist." % (howtoDir.path,))
+ self.lore(
+ ["--output", "latex",
+ "--config", "section"] +
+ [child.path for child in howtoDir.globChildren("*.xhtml")])
+
+
+ def buildPDF(self, bookPath, inputDirectory, outputPath):
+ """
+ Build a PDF from the given a LaTeX book document.
+
+ @type bookPath: L{FilePath}
+ @param bookPath: The location of a LaTeX document defining a book.
+
+ @type inputDirectory: L{FilePath}
+ @param inputDirectory: The directory which the inputs of the book are
+ relative to.
+
+ @type outputPath: L{FilePath}
+ @param outputPath: The location to which to write the resulting book.
+ """
+ if not bookPath.basename().endswith(".tex"):
+ raise ValueError("Book filename must end with .tex")
+
+ workPath = FilePath(mkdtemp())
+ try:
+ startDir = os.getcwd()
+ try:
+ os.chdir(inputDirectory.path)
+
+ texToDVI = (
+ "latex -interaction=nonstopmode "
+ "-output-directory=%s %s") % (
+ workPath.path, bookPath.path)
+
+ # What I tell you three times is true!
+ # The first two invocations of latex on the book file allows it
+ # correctly create page numbers for in-text references. Why this is
+ # the case, I could not tell you. -exarkun
+ for i in range(3):
+ self.run(texToDVI)
+
+ bookBaseWithoutExtension = bookPath.basename()[:-4]
+ dviPath = workPath.child(bookBaseWithoutExtension + ".dvi")
+ psPath = workPath.child(bookBaseWithoutExtension + ".ps")
+ pdfPath = workPath.child(bookBaseWithoutExtension + ".pdf")
+ self.run(
+ "dvips -o %(postscript)s -t letter -Ppdf %(dvi)s" % {
+ 'postscript': psPath.path,
+ 'dvi': dviPath.path})
+ self.run("ps2pdf13 %(postscript)s %(pdf)s" % {
+ 'postscript': psPath.path,
+ 'pdf': pdfPath.path})
+ pdfPath.moveTo(outputPath)
+ workPath.remove()
+ finally:
+ os.chdir(startDir)
+ except:
+ workPath.moveTo(bookPath.parent().child(workPath.basename()))
+ raise
+
+
+ def build(self, baseDirectory, inputDirectories, bookPath, outputPath):
+ """
+ Build a PDF book from the given TeX book definition and directories
+ containing lore inputs.
+
+ @type baseDirectory: L{FilePath}
+ @param baseDirectory: The directory which the inputs of the book are
+ relative to.
+
+ @type inputDirectories: C{list} of L{FilePath}
+ @param inputDirectories: The paths which contain lore inputs to be
+ converted to LaTeX.
+
+ @type bookPath: L{FilePath}
+ @param bookPath: The location of a LaTeX document defining a book.
+
+ @type outputPath: L{FilePath}
+ @param outputPath: The location to which to write the resulting book.
+ """
+ for inputDir in inputDirectories:
+ self.buildTeX(inputDir)
+ self.buildPDF(bookPath, baseDirectory, outputPath)
+ for inputDirectory in inputDirectories:
+ for child in inputDirectory.children():
+ if child.splitext()[1] == ".tex" and child != bookPath:
+ child.remove()
+
+
+
+class NewsBuilder(object):
+ """
+ Generate the new section of a NEWS file.
+
+ The C{_FEATURE}, C{_BUGFIX}, C{_REMOVAL}, and C{_MISC} attributes of
+ this class are symbolic names for the news entry types which are
+ supported. Conveniently, they each also take on the value of the file
+ name extension which indicates a news entry of that type.
+
+ @cvar _headings: A C{dict} mapping one of the news entry types to the
+ heading to write out for that type of news entry.
+
+ @cvar _TICKET_HINT: A C{str} giving the text which appears at the top of
+ each news file and which should be kept at the top, not shifted down
+ with all the other content. Put another way, this is the text after
+ which the new news text is inserted.
+ """
+ _FEATURE = ".feature"
+ _BUGFIX = ".bugfix"
+ _REMOVAL = ".removal"
+ _MISC = ".misc"
+
+ _headings = {
+ _FEATURE: "Features",
+ _BUGFIX: "Bugfixes",
+ _REMOVAL: "Deprecations and Removals",
+ _MISC: "Other",
+ }
+
+ _TICKET_HINT = (
+ 'Ticket numbers in this file can be looked up by visiting\n'
+ 'http://twistedmatrix.com/trac/ticket/<number>\n'
+ '\n')
+
+ def _today(self):
+ """
+ Return today's date as a string in YYYY-MM-DD format.
+ """
+ return date.today().strftime('%Y-%m-%d')
+
+
+ def _findChanges(self, path, ticketType):
+ """
+ Load all the feature ticket summaries.
+
+ @param path: A L{FilePath} the direct children of which to search
+ for news entries.
+
+ @param ticketType: The type of news entries to search for. One of
+ L{NewsBuilder._FEATURE}, L{NewsBuilder._BUGFIX},
+ L{NewsBuilder._REMOVAL}, or L{NewsBuilder._MISC}.
+
+ @return: A C{list} of two-tuples. The first element is the ticket
+ number as an C{int}. The second element of each tuple is the
+ description of the feature.
+ """
+ results = []
+ for child in path.children():
+ base, ext = os.path.splitext(child.basename())
+ if ext == ticketType:
+ results.append((
+ int(base),
+ ' '.join(child.getContent().splitlines())))
+ results.sort()
+ return results
+
+
+ def _writeHeader(self, fileObj, header):
+ """
+ Write a version header to the given file.
+
+ @param fileObj: A file-like object to which to write the header.
+ @param header: The header to write to the file.
+ @type header: C{str}
+ """
+ fileObj.write(header + '\n' + '=' * len(header) + '\n\n')
+
+
+ def _writeSection(self, fileObj, header, tickets):
+ """
+ Write out one section (features, bug fixes, etc) to the given file.
+
+ @param fileObj: A file-like object to which to write the news section.
+
+ @param header: The header for the section to write.
+ @type header: C{str}
+
+ @param tickets: A C{list} of ticket information of the sort returned
+ by L{NewsBuilder._findChanges}.
+ """
+ if not tickets:
+ return
+
+ reverse = {}
+ for (ticket, description) in tickets:
+ reverse.setdefault(description, []).append(ticket)
+ for description in reverse:
+ reverse[description].sort()
+ reverse = reverse.items()
+ reverse.sort(key=lambda (descr, tickets): tickets[0])
+
+ fileObj.write(header + '\n' + '-' * len(header) + '\n')
+ for (description, relatedTickets) in reverse:
+ ticketList = ', '.join([
+ '#' + str(ticket) for ticket in relatedTickets])
+ entry = ' - %s (%s)' % (description, ticketList)
+ entry = textwrap.fill(entry, subsequent_indent=' ')
+ fileObj.write(entry + '\n')
+ fileObj.write('\n')
+
+
+ def _writeMisc(self, fileObj, header, tickets):
+ """
+ Write out a miscellaneous-changes section to the given file.
+
+ @param fileObj: A file-like object to which to write the news section.
+
+ @param header: The header for the section to write.
+ @type header: C{str}
+
+ @param tickets: A C{list} of ticket information of the sort returned
+ by L{NewsBuilder._findChanges}.
+ """
+ if not tickets:
+ return
+
+ fileObj.write(header + '\n' + '-' * len(header) + '\n')
+ formattedTickets = []
+ for (ticket, ignored) in tickets:
+ formattedTickets.append('#' + str(ticket))
+ entry = ' - ' + ', '.join(formattedTickets)
+ entry = textwrap.fill(entry, subsequent_indent=' ')
+ fileObj.write(entry + '\n\n')
+
+
+ def build(self, path, output, header):
+ """
+ Load all of the change information from the given directory and write
+ it out to the given output file.
+
+ @param path: A directory (probably a I{topfiles} directory) containing
+ change information in the form of <ticket>.<change type> files.
+ @type path: L{FilePath}
+
+ @param output: The NEWS file to which the results will be prepended.
+ @type output: L{FilePath}
+
+ @param header: The top-level header to use when writing the news.
+ @type header: L{str}
+ """
+ oldNews = output.getContent()
+ newNews = output.sibling('NEWS.new').open('w')
+ if oldNews.startswith(self._TICKET_HINT):
+ newNews.write(self._TICKET_HINT)
+ oldNews = oldNews[len(self._TICKET_HINT):]
+
+ self._writeHeader(newNews, header)
+ for part in (self._FEATURE, self._BUGFIX, self._REMOVAL):
+ tickets = self._findChanges(path, part)
+ self._writeSection(newNews, self._headings.get(part), tickets)
+ self._writeMisc(
+ newNews,
+ self._headings.get(self._MISC),
+ self._findChanges(path, self._MISC))
+ newNews.write(oldNews)
+ newNews.close()
+ output.sibling('NEWS.new').moveTo(output)
+
+
+ def buildAll(self, baseDirectory):
+ """
+ Find all of the Twisted subprojects beneath C{baseDirectory} and update
+ their news files from the ticket change description files in their
+ I{topfiles} directories and update the news file in C{baseDirectory}
+ with all of the news.
+
+ @param baseDirectory: A L{FilePath} representing the root directory
+ beneath which to find Twisted projects for which to generate
+ news (see L{findTwistedProjects}).
+ """
+ today = self._today()
+
+ # Get all the subprojects to generate news for
+ projects = findTwistedProjects(baseDirectory)
+ # And order them alphabetically for ease of reading
+ projects.sort(key=lambda proj: proj.directory.path)
+ # And generate them backwards since we write news by prepending to
+ # files.
+ projects.reverse()
+
+ for aggregateNews in [False, True]:
+ for project in projects:
+ topfiles = project.directory.child("topfiles")
+ if aggregateNews:
+ news = baseDirectory.child("NEWS")
+ else:
+ news = topfiles.child("NEWS")
+ name = project.directory.basename().title()
+ if name == 'Twisted':
+ name = 'Core'
+ version = project.getVersion().base()
+ self.build(
+ topfiles, news, "Twisted %s %s (%s)" % (name, version, today))
+
+
+ def main(self, args):
+ """
+ Build all news files.
+
+ @param args: The command line arguments to process. This must contain
+ one string, the path to the base of the Twisted checkout for which
+ to build the news.
+ @type args: C{list} of C{str}
+ """
+ if len(args) != 1:
+ sys.exit("Must specify one argument: the path to the Twisted checkout")
+ self.buildAll(FilePath(args[0]))
+
+
+
+def filePathDelta(origin, destination):
+ """
+ Return a list of strings that represent C{destination} as a path relative
+ to C{origin}.
+
+ It is assumed that both paths represent directories, not files. That is to
+ say, the delta of L{twisted.python.filepath.FilePath} /foo/bar to
+ L{twisted.python.filepath.FilePath} /foo/baz will be C{../baz},
+ not C{baz}.
+
+ @type origin: L{twisted.python.filepath.FilePath}
+ @param origin: The origin of the relative path.
+
+ @type destination: L{twisted.python.filepath.FilePath}
+ @param destination: The destination of the relative path.
+ """
+ commonItems = 0
+ path1 = origin.path.split(os.sep)
+ path2 = destination.path.split(os.sep)
+ for elem1, elem2 in zip(path1, path2):
+ if elem1 == elem2:
+ commonItems += 1
+ else:
+ break
+ path = [".."] * (len(path1) - commonItems)
+ return path + path2[commonItems:]
+
+
+
+class DistributionBuilder(object):
+ """
+ A builder of Twisted distributions.
+
+ This knows how to build tarballs for Twisted and all of its subprojects.
+
+ @type blacklist: C{list} of C{str}
+ @cvar blacklist: The list subproject names to exclude from the main Twisted
+ tarball and for which no individual project tarballs will be built.
+ """
+
+ from twisted.python.dist import twisted_subprojects as subprojects
+ blacklist = ["vfs", "web2"]
+
+ def __init__(self, rootDirectory, outputDirectory, apiBaseURL=None):
+ """
+ Create a distribution builder.
+
+ @param rootDirectory: root of a Twisted export which will populate
+ subsequent tarballs.
+ @type rootDirectory: L{FilePath}.
+
+ @param outputDirectory: The directory in which to create the tarballs.
+ @type outputDirectory: L{FilePath}
+
+ @type apiBaseURL: C{str} or C{NoneType}
+ @param apiBaseURL: A format string which will be interpolated with the
+ fully-qualified Python name for each API link. For example, to
+ generate the Twisted 8.0.0 documentation, pass
+ C{"http://twistedmatrix.com/documents/8.0.0/api/%s.html"}.
+ """
+ self.rootDirectory = rootDirectory
+ self.outputDirectory = outputDirectory
+ self.apiBaseURL = apiBaseURL
+ self.manBuilder = ManBuilder()
+ self.docBuilder = DocBuilder()
+
+
+ def _buildDocInDir(self, path, version, howtoPath):
+ """
+ Generate documentation in the given path, building man pages first if
+ necessary and swallowing errors (so that directories without lore
+ documentation in them are ignored).
+
+ @param path: The path containing documentation to build.
+ @type path: L{FilePath}
+ @param version: The version of the project to include in all generated
+ pages.
+ @type version: C{str}
+ @param howtoPath: The "resource path" as L{DocBuilder} describes it.
+ @type howtoPath: L{FilePath}
+ """
+ templatePath = self.rootDirectory.child("doc").child("core"
+ ).child("howto").child("template.tpl")
+ if path.basename() == "man":
+ self.manBuilder.build(path)
+ if path.isdir():
+ try:
+ self.docBuilder.build(version, howtoPath, path,
+ templatePath, self.apiBaseURL, True)
+ except NoDocumentsFound:
+ pass
+
+
+ def buildTwisted(self, version):
+ """
+ Build the main Twisted distribution in C{Twisted-<version>.tar.bz2}.
+
+ Projects listed in in L{blacklist} will not have their plugins, code,
+ documentation, or bin directories included.
+
+ bin/admin is also excluded.
+
+ @type version: C{str}
+ @param version: The version of Twisted to build.
+
+ @return: The tarball file.
+ @rtype: L{FilePath}.
+ """
+ releaseName = "Twisted-%s" % (version,)
+ buildPath = lambda *args: '/'.join((releaseName,) + args)
+
+ outputFile = self.outputDirectory.child(releaseName + ".tar.bz2")
+ tarball = tarfile.TarFile.open(outputFile.path, 'w:bz2')
+
+ docPath = self.rootDirectory.child("doc")
+
+ # Generate docs!
+ if docPath.isdir():
+ for subProjectDir in docPath.children():
+ if (subProjectDir.isdir()
+ and subProjectDir.basename() not in self.blacklist):
+ for child in subProjectDir.walk():
+ self._buildDocInDir(child, version,
+ subProjectDir.child("howto"))
+
+ # Now, this part is nasty. We need to exclude blacklisted subprojects
+ # from the main Twisted distribution. This means we need to exclude
+ # their bin directories, their documentation directories, their
+ # plugins, and their python packages. Given that there's no "add all
+ # but exclude these particular paths" functionality in tarfile, we have
+ # to walk through all these directories and add things that *aren't*
+ # part of the blacklisted projects.
+
+ for binthing in self.rootDirectory.child("bin").children():
+ # bin/admin should also not be included.
+ if binthing.basename() not in self.blacklist + ["admin"]:
+ tarball.add(binthing.path,
+ buildPath("bin", binthing.basename()))
+
+ bad_plugins = ["twisted_%s.py" % (blacklisted,)
+ for blacklisted in self.blacklist]
+
+ for submodule in self.rootDirectory.child("twisted").children():
+ if submodule.basename() == "plugins":
+ for plugin in submodule.children():
+ if plugin.basename() not in bad_plugins:
+ tarball.add(plugin.path, buildPath("twisted", "plugins",
+ plugin.basename()))
+ elif submodule.basename() not in self.blacklist:
+ tarball.add(submodule.path, buildPath("twisted",
+ submodule.basename()))
+
+ for docDir in self.rootDirectory.child("doc").children():
+ if docDir.basename() not in self.blacklist:
+ tarball.add(docDir.path, buildPath("doc", docDir.basename()))
+
+ for toplevel in self.rootDirectory.children():
+ if not toplevel.isdir():
+ tarball.add(toplevel.path, buildPath(toplevel.basename()))
+
+ tarball.close()
+
+ return outputFile
+
+
+ def buildCore(self, version):
+ """
+ Build a core distribution in C{TwistedCore-<version>.tar.bz2}.
+
+ This is very similar to L{buildSubProject}, but core tarballs and the
+ input are laid out slightly differently.
+
+ - scripts are in the top level of the C{bin} directory.
+ - code is included directly from the C{twisted} directory, excluding
+ subprojects.
+ - all plugins except the subproject plugins are included.
+
+ @type version: C{str}
+ @param version: The version of Twisted to build.
+
+ @return: The tarball file.
+ @rtype: L{FilePath}.
+ """
+ releaseName = "TwistedCore-%s" % (version,)
+ outputFile = self.outputDirectory.child(releaseName + ".tar.bz2")
+ buildPath = lambda *args: '/'.join((releaseName,) + args)
+ tarball = self._createBasicSubprojectTarball(
+ "core", version, outputFile)
+
+ # Include the bin directory for the subproject.
+ for path in self.rootDirectory.child("bin").children():
+ if not path.isdir():
+ tarball.add(path.path, buildPath("bin", path.basename()))
+
+ # Include all files within twisted/ that aren't part of a subproject.
+ for path in self.rootDirectory.child("twisted").children():
+ if path.basename() == "plugins":
+ for plugin in path.children():
+ for subproject in self.subprojects:
+ if plugin.basename() == "twisted_%s.py" % (subproject,):
+ break
+ else:
+ tarball.add(plugin.path,
+ buildPath("twisted", "plugins",
+ plugin.basename()))
+ elif not path.basename() in self.subprojects + ["topfiles"]:
+ tarball.add(path.path, buildPath("twisted", path.basename()))
+
+ tarball.add(self.rootDirectory.child("twisted").child("topfiles").path,
+ releaseName)
+ tarball.close()
+
+ return outputFile
+
+
+ def buildSubProject(self, projectName, version):
+ """
+ Build a subproject distribution in
+ C{Twisted<Projectname>-<version>.tar.bz2}.
+
+ @type projectName: C{str}
+ @param projectName: The lowercase name of the subproject to build.
+ @type version: C{str}
+ @param version: The version of Twisted to build.
+
+ @return: The tarball file.
+ @rtype: L{FilePath}.
+ """
+ releaseName = "Twisted%s-%s" % (projectName.capitalize(), version)
+ outputFile = self.outputDirectory.child(releaseName + ".tar.bz2")
+ buildPath = lambda *args: '/'.join((releaseName,) + args)
+ subProjectDir = self.rootDirectory.child("twisted").child(projectName)
+
+ tarball = self._createBasicSubprojectTarball(projectName, version,
+ outputFile)
+
+ tarball.add(subProjectDir.child("topfiles").path, releaseName)
+
+ # Include all files in the subproject package except for topfiles.
+ for child in subProjectDir.children():
+ name = child.basename()
+ if name != "topfiles":
+ tarball.add(
+ child.path,
+ buildPath("twisted", projectName, name))
+
+ pluginsDir = self.rootDirectory.child("twisted").child("plugins")
+ # Include the plugin for the subproject.
+ pluginFileName = "twisted_%s.py" % (projectName,)
+ pluginFile = pluginsDir.child(pluginFileName)
+ if pluginFile.exists():
+ tarball.add(pluginFile.path,
+ buildPath("twisted", "plugins", pluginFileName))
+
+ # Include the bin directory for the subproject.
+ binPath = self.rootDirectory.child("bin").child(projectName)
+ if binPath.isdir():
+ tarball.add(binPath.path, buildPath("bin"))
+ tarball.close()
+
+ return outputFile
+
+
+ def _createBasicSubprojectTarball(self, projectName, version, outputFile):
+ """
+ Helper method to create and fill a tarball with things common between
+ subprojects and core.
+
+ @param projectName: The subproject's name.
+ @type projectName: C{str}
+ @param version: The version of the release.
+ @type version: C{str}
+ @param outputFile: The location of the tar file to create.
+ @type outputFile: L{FilePath}
+ """
+ releaseName = "Twisted%s-%s" % (projectName.capitalize(), version)
+ buildPath = lambda *args: '/'.join((releaseName,) + args)
+
+ tarball = tarfile.TarFile.open(outputFile.path, 'w:bz2')
+
+ tarball.add(self.rootDirectory.child("LICENSE").path,
+ buildPath("LICENSE"))
+
+ docPath = self.rootDirectory.child("doc").child(projectName)
+
+ if docPath.isdir():
+ for child in docPath.walk():
+ self._buildDocInDir(child, version, docPath.child("howto"))
+ tarball.add(docPath.path, buildPath("doc"))
+
+ return tarball
+
+
+
+class UncleanWorkingDirectory(Exception):
+ """
+ Raised when the working directory of an SVN checkout is unclean.
+ """
+
+
+class NotWorkingDirectory(Exception):
+ """
+ Raised when a directory does not appear to be an SVN working directory.
+ """
+
+
+def buildAllTarballs(checkout, destination):
+ """
+ Build complete tarballs (including documentation) for Twisted and all
+ subprojects.
+
+ This should be called after the version numbers have been updated and
+ NEWS files created.
+
+ @type checkout: L{FilePath}
+ @param checkout: The SVN working copy from which a pristine source tree
+ will be exported.
+ @type destination: L{FilePath}
+ @param destination: The directory in which tarballs will be placed.
+
+ @raise UncleanWorkingDirectory: if there are modifications to the
+ working directory of C{checkout}.
+ @raise NotWorkingDirectory: if the checkout path is not an SVN checkout.
+ """
+ if not checkout.child(".svn").exists():
+ raise NotWorkingDirectory(
+ "%s does not appear to be an SVN working directory."
+ % (checkout.path,))
+ if runCommand(["svn", "st", checkout.path]).strip():
+ raise UncleanWorkingDirectory(
+ "There are local modifications to the SVN checkout in %s."
+ % (checkout.path,))
+
+ workPath = FilePath(mkdtemp())
+ export = workPath.child("export")
+ runCommand(["svn", "export", checkout.path, export.path])
+ twistedPath = export.child("twisted")
+ version = Project(twistedPath).getVersion()
+ versionString = version.base()
+
+ apiBaseURL = "http://twistedmatrix.com/documents/%s/api/%%s.html" % (
+ versionString)
+ if not destination.exists():
+ destination.createDirectory()
+ db = DistributionBuilder(export, destination, apiBaseURL=apiBaseURL)
+
+ db.buildCore(versionString)
+ for subproject in twisted_subprojects:
+ if (subproject not in db.blacklist
+ and twistedPath.child(subproject).exists()):
+ db.buildSubProject(subproject, versionString)
+
+ db.buildTwisted(versionString)
+ workPath.remove()
+
+
+class ChangeVersionsScript(object):
+ """
+ A thing for changing version numbers. See L{main}.
+ """
+ changeAllProjectVersions = staticmethod(changeAllProjectVersions)
+
+ def main(self, args):
+ """
+ Given a list of command-line arguments, change all the Twisted versions
+ in the current directory.
+
+ @type args: list of str
+ @param args: List of command line arguments. This should only
+ contain the version number.
+ """
+ version_format = (
+ "Version should be in a form kind of like '1.2.3[pre4]'")
+ if len(args) != 1:
+ sys.exit("Must specify exactly one argument to change-versions")
+ version = args[0]
+ try:
+ major, minor, micro_and_pre = version.split(".")
+ except ValueError:
+ raise SystemExit(version_format)
+ if "pre" in micro_and_pre:
+ micro, pre = micro_and_pre.split("pre")
+ else:
+ micro = micro_and_pre
+ pre = None
+ try:
+ major = int(major)
+ minor = int(minor)
+ micro = int(micro)
+ if pre is not None:
+ pre = int(pre)
+ except ValueError:
+ raise SystemExit(version_format)
+ version_template = Version("Whatever",
+ major, minor, micro, prerelease=pre)
+ self.changeAllProjectVersions(FilePath("."), version_template)
+
+
+
+class BuildTarballsScript(object):
+ """
+ A thing for building release tarballs. See L{main}.
+ """
+ buildAllTarballs = staticmethod(buildAllTarballs)
+
+ def main(self, args):
+ """
+ Build all release tarballs.
+
+ @type args: list of str
+ @param args: The command line arguments to process. This must contain
+ two strings: the checkout directory and the destination directory.
+ """
+ if len(args) != 2:
+ sys.exit("Must specify two arguments: "
+ "Twisted checkout and destination path")
+ self.buildAllTarballs(FilePath(args[0]), FilePath(args[1]))
+
+
+
+class BuildAPIDocsScript(object):
+ """
+ A thing for building API documentation. See L{main}.
+ """
+
+ def buildAPIDocs(self, projectRoot, output):
+ """
+ Build the API documentation of Twisted, with our project policy.
+
+ @param projectRoot: A L{FilePath} representing the root of the Twisted
+ checkout.
+ @param output: A L{FilePath} pointing to the desired output directory.
+ """
+ version = Project(projectRoot.child("twisted")).getVersion()
+ versionString = version.base()
+ sourceURL = ("http://twistedmatrix.com/trac/browser/tags/releases/"
+ "twisted-%s" % (versionString,))
+ apiBuilder = APIBuilder()
+ apiBuilder.build(
+ "Twisted",
+ "http://twistedmatrix.com/",
+ sourceURL,
+ projectRoot.child("twisted"),
+ output)
+
+
+ def main(self, args):
+ """
+ Build API documentation.
+
+ @type args: list of str
+ @param args: The command line arguments to process. This must contain
+ two strings: the path to the root of the Twisted checkout, and a
+ path to an output directory.
+ """
+ if len(args) != 2:
+ sys.exit("Must specify two arguments: "
+ "Twisted checkout and distination path")
+ self.buildAPIDocs(FilePath(args[0]), FilePath(args[1]))
diff --git a/vendor/Twisted-10.0.0/twisted/python/_twisted_zsh_stub b/vendor/Twisted-10.0.0/twisted/python/_twisted_zsh_stub
new file mode 100644
index 0000000000..dd2fc64c64
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/_twisted_zsh_stub
@@ -0,0 +1,89 @@
+#compdef trial conch mktap cftp tapconvert twistd ckeygen lore pyhtmlizer tap2deb tkconch manhole tap2rpm
+
+# Copyright (c) 2005 Eric Mangold
+# See LICENSE for details.
+#
+# Maintainer: Eric Mangold <teratorn@twistedmatrix.com>
+
+# This file is meant to be in your zsh function path. i.e. in one of those
+# directories listed in your $fpath variable.
+#
+# e.g. /usr/local/share/zsh/site-functions/
+#
+# It is responsible for passing completion control to the correct
+# completion function for the version of Twisted that is
+# currently in use.
+#
+# Goals:
+#
+# We want to detect any changes to PYTHONPATH since the last time we ran.
+# That way we can re-locate the completion functions so that we are sure
+# to be completing for the right version of twisted.
+
+local dir old_fpath python_code run shebang
+
+function debug () {
+ echo $@ >> /tmp/debug
+}
+
+#debug "START _twisted_stub"
+
+function load_twisted_completions() {
+ [[ -z $commands[twistd] ]] && echo 'ERROR: test command "twistd" not found in path' && return 1
+ shebang=$(head -1 $commands[twistd])
+ [[ $shebang != \#\!* ]] && echo 'ERROR: invalid shebang line for test script "twistd"' && return 1
+ PYTHON=$shebang[3,-1]
+ PYTHON=${PYTHON# *}
+
+ #debug PYTHON -$PYTHON-
+ python_code='
+import twisted, os.path
+dir = os.path.dirname(twisted.__file__)
+print dir + os.sep + os.path.join("python", "zsh")
+'
+ dir=$($PYTHON -c "$python_code")
+ #debug "Trying to load twisted functions from $dir"
+ if [[ -r $dir/_twistd ]]; then
+ old_fpath=($fpath)
+ fpath=( $dir $fpath )
+ autoload +X _trial _conch _mktap _cftp _tapconvert _twistd _ckeygen
+ autoload +X _lore _pyhtmlizer _tap2deb _tkconch
+ autoload +X _manhole _tap2rpm
+ fpath=($old_fpath)
+ else
+ echo 'ERROR: Cannot find twisted completion function files in:'
+ echo "$dir"
+ return 1
+ fi
+}
+
+function twisted_run () {
+ # run completion function for current command
+ # the :t modifier strips off any leading pathname components
+ eval _$words[1]:t
+}
+
+function twisted_save_load_run () {
+ # save PYTHONPATH, load twisted completions, and run the completion
+ # function for the current command
+ load_twisted_completions && twisted_run && PYTHONPATH_last=$PYTHONPATH
+}
+
+
+if [[ -n $PYTHONPATH_last ]]; then
+ #debug "PYTHONPATH_last is set to $PYTHONPATH_last"
+ #check if it's the same as the last time we ran
+ if [[ $PYTHONPATH == $PYTHONPATH_last ]]; then
+ #debug "PYTHONPATH == PYTHONPATH_last"
+ # it's the same, which means we've already loaded completion
+ # functions and nothing has changed.
+ twisted_run
+ else
+ twisted_save_load_run
+ fi
+else
+ twisted_save_load_run
+fi
+
+#debug "END _twisted_stub"
+#
diff --git a/vendor/Twisted-10.0.0/twisted/python/compat.py b/vendor/Twisted-10.0.0/twisted/python/compat.py
new file mode 100644
index 0000000000..8b9ffcdc97
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/compat.py
@@ -0,0 +1,173 @@
+# -*- test-case-name: twisted.test.test_compat -*-
+#
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Compatibility module to provide backwards compatibility for useful Python
+features.
+
+This is mainly for use of internal Twisted code. We encourage you to use
+the latest version of Python directly from your code, if possible.
+"""
+
+import sys, string, socket, struct
+
+def inet_pton(af, addr):
+ if af == socket.AF_INET:
+ return socket.inet_aton(addr)
+ elif af == getattr(socket, 'AF_INET6', 'AF_INET6'):
+ if [x for x in addr if x not in string.hexdigits + ':.']:
+ raise ValueError("Illegal characters: %r" % (''.join(x),))
+
+ parts = addr.split(':')
+ elided = parts.count('')
+ ipv4Component = '.' in parts[-1]
+
+ if len(parts) > (8 - ipv4Component) or elided > 3:
+ raise ValueError("Syntactically invalid address")
+
+ if elided == 3:
+ return '\x00' * 16
+
+ if elided:
+ zeros = ['0'] * (8 - len(parts) - ipv4Component + elided)
+
+ if addr.startswith('::'):
+ parts[:2] = zeros
+ elif addr.endswith('::'):
+ parts[-2:] = zeros
+ else:
+ idx = parts.index('')
+ parts[idx:idx+1] = zeros
+
+ if len(parts) != 8 - ipv4Component:
+ raise ValueError("Syntactically invalid address")
+ else:
+ if len(parts) != (8 - ipv4Component):
+ raise ValueError("Syntactically invalid address")
+
+ if ipv4Component:
+ if parts[-1].count('.') != 3:
+ raise ValueError("Syntactically invalid address")
+ rawipv4 = socket.inet_aton(parts[-1])
+ unpackedipv4 = struct.unpack('!HH', rawipv4)
+ parts[-1:] = [hex(x)[2:] for x in unpackedipv4]
+
+ parts = [int(x, 16) for x in parts]
+ return struct.pack('!8H', *parts)
+ else:
+ raise socket.error(97, 'Address family not supported by protocol')
+
+def inet_ntop(af, addr):
+ if af == socket.AF_INET:
+ return socket.inet_ntoa(addr)
+ elif af == socket.AF_INET6:
+ if len(addr) != 16:
+ raise ValueError("address length incorrect")
+ parts = struct.unpack('!8H', addr)
+ curBase = bestBase = None
+ for i in range(8):
+ if not parts[i]:
+ if curBase is None:
+ curBase = i
+ curLen = 0
+ curLen += 1
+ else:
+ if curBase is not None:
+ if bestBase is None or curLen > bestLen:
+ bestBase = curBase
+ bestLen = curLen
+ curBase = None
+ if curBase is not None and (bestBase is None or curLen > bestLen):
+ bestBase = curBase
+ bestLen = curLen
+ parts = [hex(x)[2:] for x in parts]
+ if bestBase is not None:
+ parts[bestBase:bestBase + bestLen] = ['']
+ if parts[0] == '':
+ parts.insert(0, '')
+ if parts[-1] == '':
+ parts.insert(len(parts) - 1, '')
+ return ':'.join(parts)
+ else:
+ raise socket.error(97, 'Address family not supported by protocol')
+
+try:
+ socket.inet_pton(socket.AF_INET6, "::")
+except (AttributeError, NameError, socket.error):
+ socket.inet_pton = inet_pton
+ socket.inet_ntop = inet_ntop
+ socket.AF_INET6 = 'AF_INET6'
+
+adict = dict
+
+# OpenSSL/__init__.py imports OpenSSL.tsafe. OpenSSL/tsafe.py imports
+# threading. threading imports thread. All to make this stupid threadsafe
+# version of its Connection class. We don't even care about threadsafe
+# Connections. In the interest of not screwing over some crazy person
+# calling into OpenSSL from another thread and trying to use Twisted's SSL
+# support, we don't totally destroy OpenSSL.tsafe, but we will replace it
+# with our own version which imports threading as late as possible.
+
+class tsafe(object):
+ class Connection:
+ """
+ OpenSSL.tsafe.Connection, defined in such a way as to not blow.
+ """
+ __module__ = 'OpenSSL.tsafe'
+
+ def __init__(self, *args):
+ from OpenSSL import SSL as _ssl
+ self._ssl_conn = apply(_ssl.Connection, args)
+ from threading import _RLock
+ self._lock = _RLock()
+
+ for f in ('get_context', 'pending', 'send', 'write', 'recv',
+ 'read', 'renegotiate', 'bind', 'listen', 'connect',
+ 'accept', 'setblocking', 'fileno', 'shutdown',
+ 'close', 'get_cipher_list', 'getpeername',
+ 'getsockname', 'getsockopt', 'setsockopt',
+ 'makefile', 'get_app_data', 'set_app_data',
+ 'state_string', 'sock_shutdown',
+ 'get_peer_certificate', 'want_read', 'want_write',
+ 'set_connect_state', 'set_accept_state',
+ 'connect_ex', 'sendall'):
+
+ exec """def %s(self, *args):
+ self._lock.acquire()
+ try:
+ return apply(self._ssl_conn.%s, args)
+ finally:
+ self._lock.release()\n""" % (f, f)
+sys.modules['OpenSSL.tsafe'] = tsafe
+
+import operator
+try:
+ operator.attrgetter
+except AttributeError:
+ class attrgetter(object):
+ def __init__(self, name):
+ self.name = name
+ def __call__(self, obj):
+ return getattr(obj, self.name)
+ operator.attrgetter = attrgetter
+
+
+try:
+ set = set
+except NameError:
+ from sets import Set as set
+
+
+try:
+ frozenset = frozenset
+except NameError:
+ from sets import ImmutableSet as frozenset
+
+
+try:
+ from functools import reduce
+except ImportError:
+ reduce = reduce
diff --git a/vendor/Twisted-10.0.0/twisted/python/components.py b/vendor/Twisted-10.0.0/twisted/python/components.py
new file mode 100644
index 0000000000..30b7ee2e14
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/components.py
@@ -0,0 +1,448 @@
+# -*- test-case-name: twisted.python.test.test_components -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Component architecture for Twisted, based on Zope3 components.
+
+Using the Zope3 API directly is strongly recommended. Everything
+you need is in the top-level of the zope.interface package, e.g.::
+
+ from zope.interface import Interface, implements
+
+ class IFoo(Interface):
+ pass
+
+ class Foo:
+ implements(IFoo)
+
+ print IFoo.implementedBy(Foo) # True
+ print IFoo.providedBy(Foo()) # True
+
+L{twisted.python.components.registerAdapter} from this module may be used to
+add to Twisted's global adapter registry.
+
+L{twisted.python.components.proxyForInterface} is a factory for classes
+which allow access to only the parts of another class defined by a specified
+interface.
+"""
+
+# system imports
+import warnings
+
+# zope3 imports
+from zope.interface import interface, declarations
+from zope.interface.adapter import AdapterRegistry
+
+# twisted imports
+from twisted.python import reflect
+from twisted.persisted import styles
+
+
+class ComponentsDeprecationWarning(DeprecationWarning):
+ """
+ Nothing emits this warning anymore.
+ """
+
+
+# Twisted's global adapter registry
+globalRegistry = AdapterRegistry()
+
+# Attribute that registerAdapter looks at. Is this supposed to be public?
+ALLOW_DUPLICATES = 0
+
+# Define a function to find the registered adapter factory, using either a
+# version of Zope Interface which has the `registered' method or an older
+# version which does not.
+if getattr(AdapterRegistry, 'registered', None) is None:
+ def _registered(registry, required, provided):
+ """
+ Return the adapter factory for the given parameters in the given
+ registry, or None if there is not one.
+ """
+ return registry.get(required).selfImplied.get(provided, {}).get('')
+else:
+ def _registered(registry, required, provided):
+ """
+ Return the adapter factory for the given parameters in the given
+ registry, or None if there is not one.
+ """
+ return registry.registered([required], provided)
+
+
+def registerAdapter(adapterFactory, origInterface, *interfaceClasses):
+ """Register an adapter class.
+
+ An adapter class is expected to implement the given interface, by
+ adapting instances implementing 'origInterface'. An adapter class's
+ __init__ method should accept one parameter, an instance implementing
+ 'origInterface'.
+ """
+ self = globalRegistry
+ assert interfaceClasses, "You need to pass an Interface"
+ global ALLOW_DUPLICATES
+
+ # deal with class->interface adapters:
+ if not isinstance(origInterface, interface.InterfaceClass):
+ origInterface = declarations.implementedBy(origInterface)
+
+ for interfaceClass in interfaceClasses:
+ factory = _registered(self, origInterface, interfaceClass)
+ if factory is not None and not ALLOW_DUPLICATES:
+ raise ValueError("an adapter (%s) was already registered." % (factory, ))
+ for interfaceClass in interfaceClasses:
+ self.register([origInterface], interfaceClass, '', adapterFactory)
+
+
+def getAdapterFactory(fromInterface, toInterface, default):
+ """Return registered adapter for a given class and interface.
+
+ Note that is tied to the *Twisted* global registry, and will
+ thus not find adapters registered elsewhere.
+ """
+ self = globalRegistry
+ if not isinstance(fromInterface, interface.InterfaceClass):
+ fromInterface = declarations.implementedBy(fromInterface)
+ factory = self.lookup1(fromInterface, toInterface)
+ if factory is None:
+ factory = default
+ return factory
+
+
+# add global adapter lookup hook for our newly created registry
+def _hook(iface, ob, lookup=globalRegistry.lookup1):
+ factory = lookup(declarations.providedBy(ob), iface)
+ if factory is None:
+ return None
+ else:
+ return factory(ob)
+interface.adapter_hooks.append(_hook)
+
+## backwardsCompatImplements and fixClassImplements should probably stick around for another
+## release cycle. No harm doing so in any case.
+
+def backwardsCompatImplements(klass):
+ """DEPRECATED.
+
+ Does nothing. Previously handled backwards compat from a
+ zope.interface using class to a class wanting old twisted
+ components interface behaviors.
+ """
+ warnings.warn("components.backwardsCompatImplements doesn't do anything in Twisted 2.3, stop calling it.", ComponentsDeprecationWarning, stacklevel=2)
+
+def fixClassImplements(klass):
+ """DEPRECATED.
+
+ Does nothing. Previously converted class from __implements__ to
+ zope implementation.
+ """
+ warnings.warn("components.fixClassImplements doesn't do anything in Twisted 2.3, stop calling it.", ComponentsDeprecationWarning, stacklevel=2)
+
+
+def getRegistry():
+ """Returns the Twisted global
+ C{zope.interface.adapter.AdapterRegistry} instance.
+ """
+ return globalRegistry
+
+# FIXME: deprecate attribute somehow?
+CannotAdapt = TypeError
+
+class Adapter:
+ """I am the default implementation of an Adapter for some interface.
+
+ This docstring contains a limerick, by popular demand::
+
+ Subclassing made Zope and TR
+ much harder to work with by far.
+ So before you inherit,
+ be sure to declare it
+ Adapter, not PyObject*
+
+ @cvar temporaryAdapter: If this is True, the adapter will not be
+ persisted on the Componentized.
+ @cvar multiComponent: If this adapter is persistent, should it be
+ automatically registered for all appropriate interfaces.
+ """
+
+ # These attributes are used with Componentized.
+
+ temporaryAdapter = 0
+ multiComponent = 1
+
+ def __init__(self, original):
+ """Set my 'original' attribute to be the object I am adapting.
+ """
+ self.original = original
+
+ def __conform__(self, interface):
+ """
+ I forward __conform__ to self.original if it has it, otherwise I
+ simply return None.
+ """
+ if hasattr(self.original, "__conform__"):
+ return self.original.__conform__(interface)
+ return None
+
+ def isuper(self, iface, adapter):
+ """
+ Forward isuper to self.original
+ """
+ return self.original.isuper(iface, adapter)
+
+
+class Componentized(styles.Versioned):
+ """I am a mixin to allow you to be adapted in various ways persistently.
+
+ I define a list of persistent adapters. This is to allow adapter classes
+ to store system-specific state, and initialized on demand. The
+ getComponent method implements this. You must also register adapters for
+ this class for the interfaces that you wish to pass to getComponent.
+
+ Many other classes and utilities listed here are present in Zope3; this one
+ is specific to Twisted.
+ """
+
+ persistenceVersion = 1
+
+ def __init__(self):
+ self._adapterCache = {}
+
+ def locateAdapterClass(self, klass, interfaceClass, default):
+ return getAdapterFactory(klass, interfaceClass, default)
+
+ def setAdapter(self, interfaceClass, adapterClass):
+ self.setComponent(interfaceClass, adapterClass(self))
+
+ def addAdapter(self, adapterClass, ignoreClass=0):
+ """Utility method that calls addComponent. I take an adapter class and
+ instantiate it with myself as the first argument.
+
+ @return: The adapter instantiated.
+ """
+ adapt = adapterClass(self)
+ self.addComponent(adapt, ignoreClass)
+ return adapt
+
+ def setComponent(self, interfaceClass, component):
+ """
+ """
+ self._adapterCache[reflect.qual(interfaceClass)] = component
+
+ def addComponent(self, component, ignoreClass=0):
+ """
+ Add a component to me, for all appropriate interfaces.
+
+ In order to determine which interfaces are appropriate, the component's
+ provided interfaces will be scanned.
+
+ If the argument 'ignoreClass' is True, then all interfaces are
+ considered appropriate.
+
+ Otherwise, an 'appropriate' interface is one for which its class has
+ been registered as an adapter for my class according to the rules of
+ getComponent.
+
+ @return: the list of appropriate interfaces
+ """
+ for iface in declarations.providedBy(component):
+ if (ignoreClass or
+ (self.locateAdapterClass(self.__class__, iface, None)
+ == component.__class__)):
+ self._adapterCache[reflect.qual(iface)] = component
+
+ def unsetComponent(self, interfaceClass):
+ """Remove my component specified by the given interface class."""
+ del self._adapterCache[reflect.qual(interfaceClass)]
+
+ def removeComponent(self, component):
+ """
+ Remove the given component from me entirely, for all interfaces for which
+ it has been registered.
+
+ @return: a list of the interfaces that were removed.
+ """
+ l = []
+ for k, v in self._adapterCache.items():
+ if v is component:
+ del self._adapterCache[k]
+ l.append(reflect.namedObject(k))
+ return l
+
+ def getComponent(self, interface, default=None):
+ """Create or retrieve an adapter for the given interface.
+
+ If such an adapter has already been created, retrieve it from the cache
+ that this instance keeps of all its adapters. Adapters created through
+ this mechanism may safely store system-specific state.
+
+ If you want to register an adapter that will be created through
+ getComponent, but you don't require (or don't want) your adapter to be
+ cached and kept alive for the lifetime of this Componentized object,
+ set the attribute 'temporaryAdapter' to True on your adapter class.
+
+ If you want to automatically register an adapter for all appropriate
+ interfaces (with addComponent), set the attribute 'multiComponent' to
+ True on your adapter class.
+ """
+ k = reflect.qual(interface)
+ if self._adapterCache.has_key(k):
+ return self._adapterCache[k]
+ else:
+ adapter = interface.__adapt__(self)
+ if adapter is not None and not (
+ hasattr(adapter, "temporaryAdapter") and
+ adapter.temporaryAdapter):
+ self._adapterCache[k] = adapter
+ if (hasattr(adapter, "multiComponent") and
+ adapter.multiComponent):
+ self.addComponent(adapter)
+ if adapter is None:
+ return default
+ return adapter
+
+
+ def __conform__(self, interface):
+ return self.getComponent(interface)
+
+
+class ReprableComponentized(Componentized):
+ def __init__(self):
+ Componentized.__init__(self)
+
+ def __repr__(self):
+ from cStringIO import StringIO
+ from pprint import pprint
+ sio = StringIO()
+ pprint(self._adapterCache, sio)
+ return sio.getvalue()
+
+
+
+def proxyForInterface(iface, originalAttribute='original'):
+ """
+ Create a class which proxies all method calls which adhere to an interface
+ to another provider of that interface.
+
+ This function is intended for creating specialized proxies. The typical way
+ to use it is by subclassing the result::
+
+ class MySpecializedProxy(proxyForInterface(IFoo)):
+ def someInterfaceMethod(self, arg):
+ if arg == 3:
+ return 3
+ return self.original.someInterfaceMethod(arg)
+
+ @param iface: The Interface to which the resulting object will conform, and
+ which the wrapped object must provide.
+
+ @param originalAttribute: name of the attribute used to save the original
+ object in the resulting class. Default to C{original}.
+ @type originalAttribute: C{str}
+
+ @return: A class whose constructor takes the original object as its only
+ argument. Constructing the class creates the proxy.
+ """
+ def __init__(self, original):
+ setattr(self, originalAttribute, original)
+ contents = {"__init__": __init__}
+ for name in iface:
+ contents[name] = _ProxyDescriptor(name, originalAttribute)
+ proxy = type("(Proxy for %s)"
+ % (reflect.qual(iface),), (object,), contents)
+ declarations.classImplements(proxy, iface)
+ return proxy
+
+
+
+class _ProxiedClassMethod(object):
+ """
+ A proxied class method.
+
+ @ivar methodName: the name of the method which this should invoke when
+ called.
+ @type methodName: C{str}
+
+ @ivar originalAttribute: name of the attribute of the proxy where the
+ original object is stored.
+ @type orginalAttribute: C{str}
+ """
+ def __init__(self, methodName, originalAttribute):
+ self.methodName = methodName
+ self.originalAttribute = originalAttribute
+
+
+ def __call__(self, oself, *args, **kw):
+ """
+ Invoke the specified L{methodName} method of the C{original} attribute
+ for proxyForInterface.
+
+ @param oself: an instance of a L{proxyForInterface} object.
+
+ @return: the result of the underlying method.
+ """
+ original = getattr(oself, self.originalAttribute)
+ actualMethod = getattr(original, self.methodName)
+ return actualMethod(*args, **kw)
+
+
+
+class _ProxyDescriptor(object):
+ """
+ A descriptor which will proxy attribute access, mutation, and
+ deletion to the L{original} attribute of the object it is being accessed
+ from.
+
+ @ivar attributeName: the name of the attribute which this descriptor will
+ retrieve from instances' C{original} attribute.
+ @type attributeName: C{str}
+
+ @ivar originalAttribute: name of the attribute of the proxy where the
+ original object is stored.
+ @type orginalAttribute: C{str}
+ """
+ def __init__(self, attributeName, originalAttribute):
+ self.attributeName = attributeName
+ self.originalAttribute = originalAttribute
+
+
+ def __get__(self, oself, type=None):
+ """
+ Retrieve the C{self.attributeName} property from L{oself}.
+ """
+ if oself is None:
+ return _ProxiedClassMethod(self.attributeName,
+ self.originalAttribute)
+ original = getattr(oself, self.originalAttribute)
+ return getattr(original, self.attributeName)
+
+
+ def __set__(self, oself, value):
+ """
+ Set the C{self.attributeName} property of L{oself}.
+ """
+ original = getattr(oself, self.originalAttribute)
+ setattr(original, self.attributeName, value)
+
+
+ def __delete__(self, oself):
+ """
+ Delete the C{self.attributeName} property of L{oself}.
+ """
+ original = getattr(oself, self.originalAttribute)
+ delattr(original, self.attributeName)
+
+
+
+__all__ = [
+ # Sticking around:
+ "ComponentsDeprecationWarning",
+ "registerAdapter", "getAdapterFactory",
+ "Adapter", "Componentized", "ReprableComponentized", "getRegistry",
+ "proxyForInterface",
+
+ # Deprecated:
+ "backwardsCompatImplements",
+ "fixClassImplements",
+]
diff --git a/vendor/Twisted-10.0.0/twisted/python/context.py b/vendor/Twisted-10.0.0/twisted/python/context.py
new file mode 100644
index 0000000000..e4f7e9fd93
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/context.py
@@ -0,0 +1,90 @@
+# -*- test-case-name: twisted.test.test_context -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+Dynamic pseudo-scoping for Python.
+
+Call functions with context.call({key: value}, func); func and
+functions that it calls will be able to use 'context.get(key)' to
+retrieve 'value'.
+
+This is thread-safe.
+"""
+
+try:
+ from threading import local
+except ImportError:
+ local = None
+
+from twisted.python import threadable
+
+defaultContextDict = {}
+
+setDefault = defaultContextDict.__setitem__
+
+class ContextTracker:
+ def __init__(self):
+ self.contexts = [defaultContextDict]
+
+ def callWithContext(self, ctx, func, *args, **kw):
+ newContext = self.contexts[-1].copy()
+ newContext.update(ctx)
+ self.contexts.append(newContext)
+ try:
+ return func(*args,**kw)
+ finally:
+ self.contexts.pop()
+
+ def getContext(self, key, default=None):
+ return self.contexts[-1].get(key, default)
+
+
+class _ThreadedContextTracker:
+ def __init__(self):
+ self.threadId = threadable.getThreadID
+ self.contextPerThread = {}
+
+ def currentContext(self):
+ tkey = self.threadId()
+ try:
+ return self.contextPerThread[tkey]
+ except KeyError:
+ ct = self.contextPerThread[tkey] = ContextTracker()
+ return ct
+
+ def callWithContext(self, ctx, func, *args, **kw):
+ return self.currentContext().callWithContext(ctx, func, *args, **kw)
+
+ def getContext(self, key, default=None):
+ return self.currentContext().getContext(key, default)
+
+
+class _TLSContextTracker(_ThreadedContextTracker):
+ def __init__(self):
+ self.storage = local()
+
+ def currentContext(self):
+ try:
+ return self.storage.ct
+ except AttributeError:
+ ct = self.storage.ct = ContextTracker()
+ return ct
+
+if local is None:
+ ThreadedContextTracker = _ThreadedContextTracker
+else:
+ ThreadedContextTracker = _TLSContextTracker
+
+def installContextTracker(ctr):
+ global theContextTracker
+ global call
+ global get
+
+ theContextTracker = ctr
+ call = theContextTracker.callWithContext
+ get = theContextTracker.getContext
+
+installContextTracker(ThreadedContextTracker())
diff --git a/vendor/Twisted-10.0.0/twisted/python/deprecate.py b/vendor/Twisted-10.0.0/twisted/python/deprecate.py
new file mode 100644
index 0000000000..00a1603709
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/deprecate.py
@@ -0,0 +1,375 @@
+# -*- test-case-name: twisted.python.test.test_deprecate -*-
+# Copyright (c) 2008-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Deprecation framework for Twisted.
+
+To mark a method or function as being deprecated do this::
+
+ def badAPI(self, first, second):
+ '''
+ Docstring for badAPI.
+ '''
+ ...
+ badAPI = deprecate(Version("Twisted", 8, 0, 0))(badAPI)
+
+The newly-decorated badAPI will issue a warning when called. It will also have
+a deprecation notice appended to its docstring.
+
+To mark module-level attributes as being deprecated you can use::
+
+ badAttribute = "someValue"
+
+ ...
+
+ deprecatedModuleAttribute(
+ Version("Twisted", 8, 0, 0),
+ "Use goodAttribute instead.",
+ "your.full.module.name",
+ "badAttribute")
+
+The deprecated attributes will issue a warning whenever they are accessed. If
+the attributes being deprecated are in the same module as the
+L{deprecatedModuleAttribute} call is being made from, the C{__name__} global
+can be used as the C{moduleName} parameter.
+
+See also L{Version}.
+
+@type DEPRECATION_WARNING_FORMAT: C{str}
+@var DEPRECATION_WARNING_FORMAT: The default deprecation warning string format
+ to use when one is not provided by the user.
+"""
+
+
+__all__ = [
+ 'deprecated',
+ 'getDeprecationWarningString',
+ 'getWarningMethod',
+ 'setWarningMethod',
+ 'deprecatedModuleAttribute',
+ ]
+
+
+import sys, inspect
+from warnings import warn
+
+from twisted.python.versions import getVersionString
+from twisted.python.util import mergeFunctionMetadata
+
+
+
+DEPRECATION_WARNING_FORMAT = '%(fqpn)s was deprecated in %(version)s'
+
+
+# Notionally, part of twisted.python.reflect, but defining it there causes a
+# cyclic dependency between this module and that module. Define it here,
+# instead, and let reflect import it to re-expose to the public.
+def _fullyQualifiedName(obj):
+ """
+ Return the fully qualified name of a module, class, method or function.
+ Classes and functions need to be module level ones to be correctly
+ qualified.
+
+ @rtype: C{str}.
+ """
+ name = obj.__name__
+ if inspect.isclass(obj) or inspect.isfunction(obj):
+ moduleName = obj.__module__
+ return "%s.%s" % (moduleName, name)
+ elif inspect.ismethod(obj):
+ className = _fullyQualifiedName(obj.im_class)
+ return "%s.%s" % (className, name)
+ return name
+# Try to keep it looking like something in twisted.python.reflect.
+_fullyQualifiedName.__module__ = 'twisted.python.reflect'
+_fullyQualifiedName.__name__ = 'fullyQualifiedName'
+
+
+def getWarningMethod():
+ """
+ Return the warning method currently used to record deprecation warnings.
+ """
+ return warn
+
+
+
+def setWarningMethod(newMethod):
+ """
+ Set the warning method to use to record deprecation warnings.
+
+ The callable should take message, category and stacklevel. The return
+ value is ignored.
+ """
+ global warn
+ warn = newMethod
+
+
+
+def _getDeprecationDocstring(version):
+ return "Deprecated in %s." % getVersionString(version)
+
+
+
+def _getDeprecationWarningString(fqpn, version, format=None):
+ """
+ Return a string indicating that the Python name was deprecated in the given
+ version.
+
+ @type fqpn: C{str}
+ @param fqpn: Fully qualified Python name of the thing being deprecated
+
+ @type version: L{twisted.python.versions.Version}
+ @param version: Version that C{fqpn} was deprecated in
+
+ @type format: C{str}
+ @param format: A user-provided format to interpolate warning values into,
+ or L{DEPRECATION_WARNING_FORMAT} if C{None} is given
+
+ @rtype: C{str}
+ @return: A textual description of the deprecation
+ """
+ if format is None:
+ format = DEPRECATION_WARNING_FORMAT
+ return format % {
+ 'fqpn': fqpn,
+ 'version': getVersionString(version)}
+
+
+
+def getDeprecationWarningString(callableThing, version, format=None):
+ """
+ Return a string indicating that the callable was deprecated in the given
+ version.
+
+ @type callableThing: C{callable}
+ @param callableThing: Callable object to be deprecated
+
+ @type version: L{twisted.python.versions.Version}
+ @param version: Version that C{fqpn} was deprecated in
+
+ @type format: C{str}
+ @param format: A user-provided format to interpolate warning values into,
+ or L{DEPRECATION_WARNING_FORMAT} if C{None} is given
+
+ @rtype: C{str}
+ @return: A textual description of the deprecation
+ """
+ return _getDeprecationWarningString(
+ _fullyQualifiedName(callableThing), version, format)
+
+
+
+def deprecated(version):
+ """
+ Return a decorator that marks callables as deprecated.
+
+ @type version: L{twisted.python.versions.Version}
+ @param version: The version in which the callable will be marked as
+ having been deprecated. The decorated function will be annotated
+ with this version, having it set as its C{deprecatedVersion}
+ attribute.
+ """
+ def deprecationDecorator(function):
+ """
+ Decorator that marks C{function} as deprecated.
+ """
+ warningString = getDeprecationWarningString(function, version)
+
+ def deprecatedFunction(*args, **kwargs):
+ warn(
+ warningString,
+ DeprecationWarning,
+ stacklevel=2)
+ return function(*args, **kwargs)
+
+ deprecatedFunction = mergeFunctionMetadata(
+ function, deprecatedFunction)
+ _appendToDocstring(deprecatedFunction,
+ _getDeprecationDocstring(version))
+ deprecatedFunction.deprecatedVersion = version
+ return deprecatedFunction
+
+ return deprecationDecorator
+
+
+
+def _appendToDocstring(thingWithDoc, textToAppend):
+ """
+ Append the given text to the docstring of C{thingWithDoc}.
+
+ If C{thingWithDoc} has no docstring, then the text just replaces the
+ docstring. If it has a single-line docstring then it appends a blank line
+ and the message text. If it has a multi-line docstring, then in appends a
+ blank line a the message text, and also does the indentation correctly.
+ """
+ if thingWithDoc.__doc__:
+ docstringLines = thingWithDoc.__doc__.splitlines()
+ else:
+ docstringLines = []
+
+ if len(docstringLines) == 0:
+ docstringLines.append(textToAppend)
+ elif len(docstringLines) == 1:
+ docstringLines.extend(['', textToAppend, ''])
+ else:
+ spaces = docstringLines.pop()
+ docstringLines.extend(['',
+ spaces + textToAppend,
+ spaces])
+ thingWithDoc.__doc__ = '\n'.join(docstringLines)
+
+
+
+class _ModuleProxy(object):
+ """
+ Python module wrapper to hook module-level attribute access.
+
+ Access to deprecated attributes first checks L{_deprecatedAttributes}, if
+ the attribute does not appear there then access falls through to L{_module},
+ the wrapped module object.
+
+ @type _module: C{module}
+ @ivar _module: Module on which to hook attribute access.
+
+ @type _deprecatedAttributes: C{dict} mapping C{str} to
+ L{_DeprecatedAttribute}
+ @ivar _deprecatedAttributes: Mapping of attribute names to objects that
+ retrieve the module attribute's original value.
+ """
+ def __init__(self, module):
+ object.__setattr__(self, '_module', module)
+ object.__setattr__(self, '_deprecatedAttributes', {})
+
+
+ def __repr__(self):
+ """
+ Get a string containing the type of the module proxy and a
+ representation of the wrapped module object.
+ """
+ _module = object.__getattribute__(self, '_module')
+ return '<%s module=%r>' % (
+ type(self).__name__,
+ _module)
+
+
+ def __setattr__(self, name, value):
+ """
+ Set an attribute on the wrapped module object.
+ """
+ _module = object.__getattribute__(self, '_module')
+ setattr(_module, name, value)
+
+
+ def __getattribute__(self, name):
+ """
+ Get an attribute on the wrapped module object.
+
+ If the specified name has been deprecated then a warning is issued.
+ """
+ _module = object.__getattribute__(self, '_module')
+ _deprecatedAttributes = object.__getattribute__(
+ self, '_deprecatedAttributes')
+
+ getter = _deprecatedAttributes.get(name)
+ if getter is not None:
+ value = getter.get()
+ else:
+ value = getattr(_module, name)
+ return value
+
+
+
+class _DeprecatedAttribute(object):
+ """
+ Wrapper for deprecated attributes.
+
+ This is intended to be used by L{_ModuleProxy}. Calling
+ L{_DeprecatedAttribute.get} will issue a warning and retrieve the
+ underlying attribute's value.
+
+ @type module: C{module}
+ @ivar module: The original module instance containing this attribute
+
+ @type fqpn: C{str}
+ @ivar fqpn: Fully qualified Python name for the deprecated attribute
+
+ @type version: L{twisted.python.versions.Version}
+ @ivar version: Version that the attribute was deprecated in
+
+ @type message: C{str}
+ @ivar message: Deprecation message
+ """
+ def __init__(self, module, name, version, message):
+ """
+ Initialise a deprecated name wrapper.
+ """
+ self.module = module
+ self.__name__ = name
+ self.fqpn = module.__name__ + '.' + name
+ self.version = version
+ self.message = message
+
+
+ def get(self):
+ """
+ Get the underlying attribute value and issue a deprecation warning.
+ """
+ message = _getDeprecationWarningString(self.fqpn, self.version,
+ DEPRECATION_WARNING_FORMAT + ': ' + self.message)
+ warn(message, DeprecationWarning, stacklevel=3)
+ return getattr(self.module, self.__name__)
+
+
+
+def _deprecateAttribute(proxy, name, version, message):
+ """
+ Mark a module-level attribute as being deprecated.
+
+ @type proxy: L{_ModuleProxy}
+ @param proxy: The module proxy instance proxying the deprecated attributes
+
+ @type name: C{str}
+ @param name: Attribute name
+
+ @type version: L{twisted.python.versions.Version}
+ @param version: Version that the attribute was deprecated in
+
+ @type message: C{str}
+ @param message: Deprecation message
+ """
+ _module = object.__getattribute__(proxy, '_module')
+ attr = _DeprecatedAttribute(_module, name, version, message)
+ # Add a deprecated attribute marker for this module's attribute. When this
+ # attribute is accessed via _ModuleProxy a warning is emitted.
+ _deprecatedAttributes = object.__getattribute__(
+ proxy, '_deprecatedAttributes')
+ _deprecatedAttributes[name] = attr
+
+
+
+def deprecatedModuleAttribute(version, message, moduleName, name):
+ """
+ Declare a module-level attribute as being deprecated.
+
+ @type version: L{twisted.python.versions.Version}
+ @param version: Version that the attribute was deprecated in
+
+ @type message: C{str}
+ @param message: Deprecation message
+
+ @type moduleName: C{str}
+ @param moduleName: Fully-qualified Python name of the module containing
+ the deprecated attribute; if called from the same module as the
+ attributes are being deprecated in, using the C{__name__} global can
+ be helpful
+
+ @type name: C{str}
+ @param name: Attribute name to deprecate
+ """
+ module = sys.modules[moduleName]
+ if not isinstance(module, _ModuleProxy):
+ module = _ModuleProxy(module)
+ sys.modules[moduleName] = module
+
+ _deprecateAttribute(module, name, version, message)
diff --git a/vendor/Twisted-10.0.0/twisted/python/dispatch.py b/vendor/Twisted-10.0.0/twisted/python/dispatch.py
new file mode 100644
index 0000000000..4e28632173
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/dispatch.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import warnings
+warnings.warn(
+ "Create your own event dispatching mechanism, "
+ "twisted.python.dispatch will soon be no more.",
+ DeprecationWarning, 2)
+
+
+class EventDispatcher:
+ """
+ A global event dispatcher for events.
+ I'm used for any events that need to span disparate objects in the client.
+
+ I should only be used when one object needs to signal an object that it's
+ not got a direct reference to (unless you really want to pass it through
+ here, in which case I won't mind).
+
+ I'm mainly useful for complex GUIs.
+ """
+
+ def __init__(self, prefix="event_"):
+ self.prefix = prefix
+ self.callbacks = {}
+
+
+ def registerHandler(self, name, meth):
+ self.callbacks.setdefault(name, []).append(meth)
+
+
+ def autoRegister(self, obj):
+ from twisted.python import reflect
+ d = {}
+ reflect.accumulateMethods(obj, d, self.prefix)
+ for k,v in d.items():
+ self.registerHandler(k, v)
+
+
+ def publishEvent(self, name, *args, **kwargs):
+ for cb in self.callbacks[name]:
+ cb(*args, **kwargs)
diff --git a/vendor/Twisted-10.0.0/twisted/python/dist.py b/vendor/Twisted-10.0.0/twisted/python/dist.py
new file mode 100644
index 0000000000..5727065c4f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/dist.py
@@ -0,0 +1,361 @@
+"""
+Distutils convenience functionality.
+
+Don't use this outside of Twisted.
+
+Maintainer: Christopher Armstrong
+"""
+
+import sys, os
+from distutils.command import build_scripts, install_data, build_ext, build_py
+from distutils.errors import CompileError
+from distutils import core
+from distutils.core import Extension
+
+twisted_subprojects = ["conch", "lore", "mail", "names",
+ "news", "pair", "runner", "web", "web2",
+ "words", "vfs"]
+
+
+class ConditionalExtension(Extension):
+ """
+ An extension module that will only be compiled if certain conditions are
+ met.
+
+ @param condition: A callable of one argument which returns True or False to
+ indicate whether the extension should be built. The argument is an
+ instance of L{build_ext_twisted}, which has useful methods for checking
+ things about the platform.
+ """
+ def __init__(self, *args, **kwargs):
+ self.condition = kwargs.pop("condition", lambda builder: True)
+ Extension.__init__(self, *args, **kwargs)
+
+
+
+def setup(**kw):
+ """
+ An alternative to distutils' setup() which is specially designed
+ for Twisted subprojects.
+
+ Pass twisted_subproject=projname if you want package and data
+ files to automatically be found for you.
+
+ @param conditionalExtensions: Extensions to optionally build.
+ @type conditionalExtensions: C{list} of L{ConditionalExtension}
+ """
+ return core.setup(**get_setup_args(**kw))
+
+def get_setup_args(**kw):
+ if 'twisted_subproject' in kw:
+ if 'twisted' not in os.listdir('.'):
+ raise RuntimeError("Sorry, you need to run setup.py from the "
+ "toplevel source directory.")
+ projname = kw['twisted_subproject']
+ projdir = os.path.join('twisted', projname)
+
+ kw['packages'] = getPackages(projdir, parent='twisted')
+ kw['version'] = getVersion(projname)
+
+ plugin = "twisted/plugins/twisted_" + projname + ".py"
+ if os.path.exists(plugin):
+ kw.setdefault('py_modules', []).append(
+ plugin.replace("/", ".")[:-3])
+
+ kw['data_files'] = getDataFiles(projdir, parent='twisted')
+
+ del kw['twisted_subproject']
+ else:
+ if 'plugins' in kw:
+ py_modules = []
+ for plg in kw['plugins']:
+ py_modules.append("twisted.plugins." + plg)
+ kw.setdefault('py_modules', []).extend(py_modules)
+ del kw['plugins']
+
+ if 'cmdclass' not in kw:
+ kw['cmdclass'] = {
+ 'install_data': install_data_twisted,
+ 'build_scripts': build_scripts_twisted}
+ if sys.version_info[:3] < (2, 3, 0):
+ kw['cmdclass']['build_py'] = build_py_twisted
+
+ if "conditionalExtensions" in kw:
+ extensions = kw["conditionalExtensions"]
+ del kw["conditionalExtensions"]
+
+ if 'ext_modules' not in kw:
+ # This is a workaround for distutils behavior; ext_modules isn't
+ # actually used by our custom builder. distutils deep-down checks
+ # to see if there are any ext_modules defined before invoking
+ # the build_ext command. We need to trigger build_ext regardless
+ # because it is the thing that does the conditional checks to see
+ # if it should build any extensions. The reason we have to delay
+ # the conditional checks until then is that the compiler objects
+ # are not yet set up when this code is executed.
+ kw["ext_modules"] = extensions
+
+ class my_build_ext(build_ext_twisted):
+ conditionalExtensions = extensions
+ kw.setdefault('cmdclass', {})['build_ext'] = my_build_ext
+ return kw
+
+def getVersion(proj, base="twisted"):
+ """
+ Extract the version number for a given project.
+
+ @param proj: the name of the project. Examples are "core",
+ "conch", "words", "mail".
+
+ @rtype: str
+ @returns: The version number of the project, as a string like
+ "2.0.0".
+ """
+ if proj == 'core':
+ vfile = os.path.join(base, '_version.py')
+ else:
+ vfile = os.path.join(base, proj, '_version.py')
+ ns = {'__name__': 'Nothing to see here'}
+ execfile(vfile, ns)
+ return ns['version'].base()
+
+
+# Names that are exluded from globbing results:
+EXCLUDE_NAMES = ["{arch}", "CVS", ".cvsignore", "_darcs",
+ "RCS", "SCCS", ".svn"]
+EXCLUDE_PATTERNS = ["*.py[cdo]", "*.s[ol]", ".#*", "*~", "*.py"]
+
+import fnmatch
+
+def _filterNames(names):
+ """Given a list of file names, return those names that should be copied.
+ """
+ names = [n for n in names
+ if n not in EXCLUDE_NAMES]
+ # This is needed when building a distro from a working
+ # copy (likely a checkout) rather than a pristine export:
+ for pattern in EXCLUDE_PATTERNS:
+ names = [n for n in names
+ if (not fnmatch.fnmatch(n, pattern))
+ and (not n.endswith('.py'))]
+ return names
+
+def relativeTo(base, relativee):
+ """
+ Gets 'relativee' relative to 'basepath'.
+
+ i.e.,
+
+ >>> relativeTo('/home/', '/home/radix/')
+ 'radix'
+ >>> relativeTo('.', '/home/radix/Projects/Twisted') # curdir is /home/radix
+ 'Projects/Twisted'
+
+ The 'relativee' must be a child of 'basepath'.
+ """
+ basepath = os.path.abspath(base)
+ relativee = os.path.abspath(relativee)
+ if relativee.startswith(basepath):
+ relative = relativee[len(basepath):]
+ if relative.startswith(os.sep):
+ relative = relative[1:]
+ return os.path.join(base, relative)
+ raise ValueError("%s is not a subpath of %s" % (relativee, basepath))
+
+
+def getDataFiles(dname, ignore=None, parent=None):
+ """
+ Get all the data files that should be included in this distutils Project.
+
+ 'dname' should be the path to the package that you're distributing.
+
+ 'ignore' is a list of sub-packages to ignore. This facilitates
+ disparate package hierarchies. That's a fancy way of saying that
+ the 'twisted' package doesn't want to include the 'twisted.conch'
+ package, so it will pass ['conch'] as the value.
+
+ 'parent' is necessary if you're distributing a subpackage like
+ twisted.conch. 'dname' should point to 'twisted/conch' and 'parent'
+ should point to 'twisted'. This ensures that your data_files are
+ generated correctly, only using relative paths for the first element
+ of the tuple ('twisted/conch/*').
+ The default 'parent' is the current working directory.
+ """
+ parent = parent or "."
+ ignore = ignore or []
+ result = []
+ for directory, subdirectories, filenames in os.walk(dname):
+ resultfiles = []
+ for exname in EXCLUDE_NAMES:
+ if exname in subdirectories:
+ subdirectories.remove(exname)
+ for ig in ignore:
+ if ig in subdirectories:
+ subdirectories.remove(ig)
+ for filename in _filterNames(filenames):
+ resultfiles.append(filename)
+ if resultfiles:
+ result.append((relativeTo(parent, directory),
+ [relativeTo(parent,
+ os.path.join(directory, filename))
+ for filename in resultfiles]))
+ return result
+
+def getPackages(dname, pkgname=None, results=None, ignore=None, parent=None):
+ """
+ Get all packages which are under dname. This is necessary for
+ Python 2.2's distutils. Pretty similar arguments to getDataFiles,
+ including 'parent'.
+ """
+ parent = parent or ""
+ prefix = []
+ if parent:
+ prefix = [parent]
+ bname = os.path.basename(dname)
+ ignore = ignore or []
+ if bname in ignore:
+ return []
+ if results is None:
+ results = []
+ if pkgname is None:
+ pkgname = []
+ subfiles = os.listdir(dname)
+ abssubfiles = [os.path.join(dname, x) for x in subfiles]
+ if '__init__.py' in subfiles:
+ results.append(prefix + pkgname + [bname])
+ for subdir in filter(os.path.isdir, abssubfiles):
+ getPackages(subdir, pkgname=pkgname + [bname],
+ results=results, ignore=ignore,
+ parent=parent)
+ res = ['.'.join(result) for result in results]
+ return res
+
+
+
+def getScripts(projname, basedir=''):
+ """
+ Returns a list of scripts for a Twisted subproject; this works in
+ any of an SVN checkout, a project-specific tarball.
+ """
+ scriptdir = os.path.join(basedir, 'bin', projname)
+ if not os.path.isdir(scriptdir):
+ # Probably a project-specific tarball, in which case only this
+ # project's bins are included in 'bin'
+ scriptdir = os.path.join(basedir, 'bin')
+ if not os.path.isdir(scriptdir):
+ return []
+ thingies = os.listdir(scriptdir)
+ if '.svn' in thingies:
+ thingies.remove('.svn')
+ return filter(os.path.isfile,
+ [os.path.join(scriptdir, x) for x in thingies])
+
+
+## Helpers and distutil tweaks
+
+class build_py_twisted(build_py.build_py):
+ """
+ Changes behavior in Python 2.2 to support simultaneous specification of
+ `packages' and `py_modules'.
+ """
+ def run(self):
+ if self.py_modules:
+ self.build_modules()
+ if self.packages:
+ self.build_packages()
+ self.byte_compile(self.get_outputs(include_bytecode=0))
+
+
+
+class build_scripts_twisted(build_scripts.build_scripts):
+ """Renames scripts so they end with '.py' on Windows."""
+
+ def run(self):
+ build_scripts.build_scripts.run(self)
+ if not os.name == "nt":
+ return
+ for f in os.listdir(self.build_dir):
+ fpath=os.path.join(self.build_dir, f)
+ if not fpath.endswith(".py"):
+ try:
+ os.unlink(fpath + ".py")
+ except EnvironmentError, e:
+ if e.args[1]=='No such file or directory':
+ pass
+ os.rename(fpath, fpath + ".py")
+
+
+
+class install_data_twisted(install_data.install_data):
+ """I make sure data files are installed in the package directory."""
+ def finalize_options(self):
+ self.set_undefined_options('install',
+ ('install_lib', 'install_dir')
+ )
+ install_data.install_data.finalize_options(self)
+
+
+
+class build_ext_twisted(build_ext.build_ext):
+ """
+ Allow subclasses to easily detect and customize Extensions to
+ build at install-time.
+ """
+
+ def prepare_extensions(self):
+ """
+ Prepare the C{self.extensions} attribute (used by
+ L{build_ext.build_ext}) by checking which extensions in
+ L{conditionalExtensions} should be built. In addition, if we are
+ building on NT, define the WIN32 macro to 1.
+ """
+ # always define WIN32 under Windows
+ if os.name == 'nt':
+ self.define_macros = [("WIN32", 1)]
+ else:
+ self.define_macros = []
+ self.extensions = [x for x in self.conditionalExtensions
+ if x.condition(self)]
+ for ext in self.extensions:
+ ext.define_macros.extend(self.define_macros)
+
+
+ def build_extensions(self):
+ """
+ Check to see which extension modules to build and then build them.
+ """
+ self.prepare_extensions()
+ build_ext.build_ext.build_extensions(self)
+
+
+ def _remove_conftest(self):
+ for filename in ("conftest.c", "conftest.o", "conftest.obj"):
+ try:
+ os.unlink(filename)
+ except EnvironmentError:
+ pass
+
+
+ def _compile_helper(self, content):
+ conftest = open("conftest.c", "w")
+ try:
+ conftest.write(content)
+ conftest.close()
+
+ try:
+ self.compiler.compile(["conftest.c"], output_dir='')
+ except CompileError:
+ return False
+ return True
+ finally:
+ self._remove_conftest()
+
+
+ def _check_header(self, header_name):
+ """
+ Check if the given header can be included by trying to compile a file
+ that contains only an #include line.
+ """
+ self.compiler.announce("checking for %s ..." % header_name, 0)
+ return self._compile_helper("#include <%s>\n" % header_name)
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/dxprofile.py b/vendor/Twisted-10.0.0/twisted/python/dxprofile.py
new file mode 100644
index 0000000000..19398932dc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/dxprofile.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+DEPRECATED since Twisted 8.0.
+
+Utility functions for reporting bytecode frequencies to Skip Montanaro's
+stat collector.
+
+This module requires a version of Python build with DYNAMIC_EXCUTION_PROFILE,
+and optionally DXPAIRS, defined to be useful.
+"""
+
+import sys, types, xmlrpclib, warnings
+
+
+warnings.warn("twisted.python.dxprofile is deprecated since Twisted 8.0.",
+ category=DeprecationWarning)
+
+
+def rle(iterable):
+ """
+ Run length encode a list.
+ """
+ iterable = iter(iterable)
+ runlen = 1
+ result = []
+ try:
+ previous = iterable.next()
+ except StopIteration:
+ return []
+ for element in iterable:
+ if element == previous:
+ runlen = runlen + 1
+ continue
+ else:
+ if isinstance(previous, (types.ListType, types.TupleType)):
+ previous = rle(previous)
+ result.append([previous, runlen])
+ previous = element
+ runlen = 1
+ if isinstance(previous, (types.ListType, types.TupleType)):
+ previous = rle(previous)
+ result.append([previous, runlen])
+ return result
+
+
+
+def report(email, appname):
+ """
+ Send an RLE encoded version of sys.getdxp() off to our Top Men (tm)
+ for analysis.
+ """
+ if hasattr(sys, 'getdxp') and appname:
+ dxp = xmlrpclib.ServerProxy("http://manatee.mojam.com:7304")
+ dxp.add_dx_info(appname, email, sys.version_info[:3], rle(sys.getdxp()))
diff --git a/vendor/Twisted-10.0.0/twisted/python/failure.py b/vendor/Twisted-10.0.0/twisted/python/failure.py
new file mode 100644
index 0000000000..d5738cd21b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/failure.py
@@ -0,0 +1,557 @@
+# -*- test-case-name: twisted.test.test_failure -*-
+# See also test suite twisted.test.test_pbfailure
+
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Asynchronous-friendly error mechanism.
+
+See L{Failure}.
+"""
+
+# System Imports
+import sys
+import linecache
+import inspect
+import opcode
+from cStringIO import StringIO
+
+from twisted.python import reflect
+
+count = 0
+traceupLength = 4
+
+class DefaultException(Exception):
+ pass
+
+def format_frames(frames, write, detail="default"):
+ """Format and write frames.
+
+ @param frames: is a list of frames as used by Failure.frames, with
+ each frame being a list of
+ (funcName, fileName, lineNumber, locals.items(), globals.items())
+ @type frames: list
+ @param write: this will be called with formatted strings.
+ @type write: callable
+ @param detail: Three detail levels are available:
+ default, brief, and verbose.
+ @type detail: string
+ """
+ if detail not in ('default', 'brief', 'verbose'):
+ raise ValueError, "Detail must be default, brief, or verbose. (not %r)" % (detail,)
+ w = write
+ if detail == "brief":
+ for method, filename, lineno, localVars, globalVars in frames:
+ w('%s:%s:%s\n' % (filename, lineno, method))
+ elif detail == "default":
+ for method, filename, lineno, localVars, globalVars in frames:
+ w( ' File "%s", line %s, in %s\n' % (filename, lineno, method))
+ w( ' %s\n' % linecache.getline(filename, lineno).strip())
+ elif detail == "verbose":
+ for method, filename, lineno, localVars, globalVars in frames:
+ w("%s:%d: %s(...)\n" % (filename, lineno, method))
+ w(' [ Locals ]\n')
+ # Note: the repr(val) was (self.pickled and val) or repr(val)))
+ for name, val in localVars:
+ w(" %s : %s\n" % (name, repr(val)))
+ w(' ( Globals )\n')
+ for name, val in globalVars:
+ w(" %s : %s\n" % (name, repr(val)))
+
+# slyphon: i have a need to check for this value in trial
+# so I made it a module-level constant
+EXCEPTION_CAUGHT_HERE = "--- <exception caught here> ---"
+
+
+
+class NoCurrentExceptionError(Exception):
+ """
+ Raised when trying to create a Failure from the current interpreter
+ exception state and there is no current exception state.
+ """
+
+
+class _Traceback(object):
+ """
+ Fake traceback object which can be passed to functions in the standard
+ library L{traceback} module.
+ """
+
+ def __init__(self, frames):
+ """
+ Construct a fake traceback object using a list of frames. Note that
+ although frames generally include locals and globals, this information
+ is not kept by this object, since locals and globals are not used in
+ standard tracebacks.
+
+ @param frames: [(methodname, filename, lineno, locals, globals), ...]
+ """
+ assert len(frames) > 0, "Must pass some frames"
+ head, frames = frames[0], frames[1:]
+ name, filename, lineno, localz, globalz = head
+ self.tb_frame = _Frame(name, filename)
+ self.tb_lineno = lineno
+ if len(frames) == 0:
+ self.tb_next = None
+ else:
+ self.tb_next = _Traceback(frames)
+
+
+class _Frame(object):
+ """
+ A fake frame object, used by L{_Traceback}.
+ """
+
+ def __init__(self, name, filename):
+ self.f_code = _Code(name, filename)
+ self.f_globals = {}
+
+
+class _Code(object):
+ """
+ A fake code object, used by L{_Traceback} via L{_Frame}.
+ """
+ def __init__(self, name, filename):
+ self.co_name = name
+ self.co_filename = filename
+
+
+class Failure:
+ """
+ A basic abstraction for an error that has occurred.
+
+ This is necessary because Python's built-in error mechanisms are
+ inconvenient for asynchronous communication.
+
+ @ivar value: The exception instance responsible for this failure.
+ @ivar type: The exception's class.
+ """
+
+ pickled = 0
+ stack = None
+
+ # The opcode of "yield" in Python bytecode. We need this in _findFailure in
+ # order to identify whether an exception was thrown by a
+ # throwExceptionIntoGenerator.
+ _yieldOpcode = chr(opcode.opmap["YIELD_VALUE"])
+
+ def __init__(self, exc_value=None, exc_type=None, exc_tb=None):
+ """
+ Initialize me with an explanation of the error.
+
+ By default, this will use the current C{exception}
+ (L{sys.exc_info}()). However, if you want to specify a
+ particular kind of failure, you can pass an exception as an
+ argument.
+
+ If no C{exc_value} is passed, then an "original" C{Failure} will
+ be searched for. If the current exception handler that this
+ C{Failure} is being constructed in is handling an exception
+ raised by L{raiseException}, then this C{Failure} will act like
+ the original C{Failure}.
+
+ For C{exc_tb} only L{traceback} instances or C{None} are allowed.
+ If C{None} is supplied for C{exc_value}, the value of C{exc_tb} is
+ ignored, otherwise if C{exc_tb} is C{None}, it will be found from
+ execution context (ie, L{sys.exc_info}).
+ """
+ global count
+ count = count + 1
+ self.count = count
+ self.type = self.value = tb = None
+
+ #strings Exceptions/Failures are bad, mmkay?
+ if isinstance(exc_value, (str, unicode)) and exc_type is None:
+ import warnings
+ warnings.warn(
+ "Don't pass strings (like %r) to failure.Failure (replacing with a DefaultException)." %
+ exc_value, DeprecationWarning, stacklevel=2)
+ exc_value = DefaultException(exc_value)
+
+ stackOffset = 0
+
+ if exc_value is None:
+ exc_value = self._findFailure()
+
+ if exc_value is None:
+ self.type, self.value, tb = sys.exc_info()
+ if self.type is None:
+ raise NoCurrentExceptionError()
+ stackOffset = 1
+ elif exc_type is None:
+ if isinstance(exc_value, Exception):
+ self.type = exc_value.__class__
+ else: #allow arbitrary objects.
+ self.type = type(exc_value)
+ self.value = exc_value
+ else:
+ self.type = exc_type
+ self.value = exc_value
+ if isinstance(self.value, Failure):
+ self.__dict__ = self.value.__dict__
+ return
+ if tb is None:
+ if exc_tb:
+ tb = exc_tb
+# else:
+# log.msg("Erf, %r created with no traceback, %s %s." % (
+# repr(self), repr(exc_value), repr(exc_type)))
+# for s in traceback.format_stack():
+# log.msg(s)
+
+ frames = self.frames = []
+ stack = self.stack = []
+
+ # added 2003-06-23 by Chris Armstrong. Yes, I actually have a
+ # use case where I need this traceback object, and I've made
+ # sure that it'll be cleaned up.
+ self.tb = tb
+
+ if tb:
+ f = tb.tb_frame
+ elif not isinstance(self.value, Failure):
+ # we don't do frame introspection since it's expensive,
+ # and if we were passed a plain exception with no
+ # traceback, it's not useful anyway
+ f = stackOffset = None
+
+ while stackOffset and f:
+ # This excludes this Failure.__init__ frame from the
+ # stack, leaving it to start with our caller instead.
+ f = f.f_back
+ stackOffset -= 1
+
+ # Keeps the *full* stack. Formerly in spread.pb.print_excFullStack:
+ #
+ # The need for this function arises from the fact that several
+ # PB classes have the peculiar habit of discarding exceptions
+ # with bareword "except:"s. This premature exception
+ # catching means tracebacks generated here don't tend to show
+ # what called upon the PB object.
+
+ while f:
+ localz = f.f_locals.copy()
+ if f.f_locals is f.f_globals:
+ globalz = {}
+ else:
+ globalz = f.f_globals.copy()
+ for d in globalz, localz:
+ if d.has_key("__builtins__"):
+ del d["__builtins__"]
+ stack.insert(0, [
+ f.f_code.co_name,
+ f.f_code.co_filename,
+ f.f_lineno,
+ localz.items(),
+ globalz.items(),
+ ])
+ f = f.f_back
+
+ while tb is not None:
+ f = tb.tb_frame
+ localz = f.f_locals.copy()
+ if f.f_locals is f.f_globals:
+ globalz = {}
+ else:
+ globalz = f.f_globals.copy()
+ for d in globalz, localz:
+ if d.has_key("__builtins__"):
+ del d["__builtins__"]
+
+ frames.append([
+ f.f_code.co_name,
+ f.f_code.co_filename,
+ tb.tb_lineno,
+ localz.items(),
+ globalz.items(),
+ ])
+ tb = tb.tb_next
+ if inspect.isclass(self.type) and issubclass(self.type, Exception):
+ parentCs = reflect.allYourBase(self.type)
+ self.parents = map(reflect.qual, parentCs)
+ self.parents.append(reflect.qual(self.type))
+ else:
+ self.parents = [self.type]
+
+ def trap(self, *errorTypes):
+ """Trap this failure if its type is in a predetermined list.
+
+ This allows you to trap a Failure in an error callback. It will be
+ automatically re-raised if it is not a type that you expect.
+
+ The reason for having this particular API is because it's very useful
+ in Deferred errback chains::
+
+ def _ebFoo(self, failure):
+ r = failure.trap(Spam, Eggs)
+ print 'The Failure is due to either Spam or Eggs!'
+ if r == Spam:
+ print 'Spam did it!'
+ elif r == Eggs:
+ print 'Eggs did it!'
+
+ If the failure is not a Spam or an Eggs, then the Failure
+ will be 'passed on' to the next errback.
+
+ @type errorTypes: L{Exception}
+ """
+ error = self.check(*errorTypes)
+ if not error:
+ raise self
+ return error
+
+ def check(self, *errorTypes):
+ """Check if this failure's type is in a predetermined list.
+
+ @type errorTypes: list of L{Exception} classes or
+ fully-qualified class names.
+ @returns: the matching L{Exception} type, or None if no match.
+ """
+ for error in errorTypes:
+ err = error
+ if inspect.isclass(error) and issubclass(error, Exception):
+ err = reflect.qual(error)
+ if err in self.parents:
+ return error
+ return None
+
+
+ def raiseException(self):
+ """
+ raise the original exception, preserving traceback
+ information if available.
+ """
+ raise self.type, self.value, self.tb
+
+
+ def throwExceptionIntoGenerator(self, g):
+ """
+ Throw the original exception into the given generator,
+ preserving traceback information if available.
+
+ @return: The next value yielded from the generator.
+ @raise StopIteration: If there are no more values in the generator.
+ @raise anything else: Anything that the generator raises.
+ """
+ return g.throw(self.type, self.value, self.tb)
+
+
+ def _findFailure(cls):
+ """
+ Find the failure that represents the exception currently in context.
+ """
+ tb = sys.exc_info()[-1]
+ if not tb:
+ return
+
+ secondLastTb = None
+ lastTb = tb
+ while lastTb.tb_next:
+ secondLastTb = lastTb
+ lastTb = lastTb.tb_next
+
+ lastFrame = lastTb.tb_frame
+
+ # NOTE: f_locals.get('self') is used rather than
+ # f_locals['self'] because psyco frames do not contain
+ # anything in their locals() dicts. psyco makes debugging
+ # difficult anyhow, so losing the Failure objects (and thus
+ # the tracebacks) here when it is used is not that big a deal.
+
+ # handle raiseException-originated exceptions
+ if lastFrame.f_code is cls.raiseException.func_code:
+ return lastFrame.f_locals.get('self')
+
+ # handle throwExceptionIntoGenerator-originated exceptions
+ # this is tricky, and differs if the exception was caught
+ # inside the generator, or above it:
+
+ # it is only really originating from
+ # throwExceptionIntoGenerator if the bottom of the traceback
+ # is a yield.
+ # Pyrex and Cython extensions create traceback frames
+ # with no co_code, but they can't yield so we know it's okay to just return here.
+ if ((not lastFrame.f_code.co_code) or
+ lastFrame.f_code.co_code[lastTb.tb_lasti] != cls._yieldOpcode):
+ return
+
+ # if the exception was caught above the generator.throw
+ # (outside the generator), it will appear in the tb (as the
+ # second last item):
+ if secondLastTb:
+ frame = secondLastTb.tb_frame
+ if frame.f_code is cls.throwExceptionIntoGenerator.func_code:
+ return frame.f_locals.get('self')
+
+ # if the exception was caught below the generator.throw
+ # (inside the generator), it will appear in the frames' linked
+ # list, above the top-level traceback item (which must be the
+ # generator frame itself, thus its caller is
+ # throwExceptionIntoGenerator).
+ frame = tb.tb_frame.f_back
+ if frame and frame.f_code is cls.throwExceptionIntoGenerator.func_code:
+ return frame.f_locals.get('self')
+
+ _findFailure = classmethod(_findFailure)
+
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__, self.type)
+
+ def __str__(self):
+ return "[Failure instance: %s]" % self.getBriefTraceback()
+
+ def __getstate__(self):
+ """Avoid pickling objects in the traceback.
+ """
+ if self.pickled:
+ return self.__dict__
+ c = self.__dict__.copy()
+
+ c['frames'] = [
+ [
+ v[0], v[1], v[2],
+ [(j[0], reflect.safe_repr(j[1])) for j in v[3]],
+ [(j[0], reflect.safe_repr(j[1])) for j in v[4]]
+ ] for v in self.frames
+ ]
+
+ # added 2003-06-23. See comment above in __init__
+ c['tb'] = None
+
+ if self.stack is not None:
+ # XXX: This is a band-aid. I can't figure out where these
+ # (failure.stack is None) instances are coming from.
+ c['stack'] = [
+ [
+ v[0], v[1], v[2],
+ [(j[0], reflect.safe_repr(j[1])) for j in v[3]],
+ [(j[0], reflect.safe_repr(j[1])) for j in v[4]]
+ ] for v in self.stack
+ ]
+
+ c['pickled'] = 1
+ return c
+
+ def cleanFailure(self):
+ """Remove references to other objects, replacing them with strings.
+ """
+ self.__dict__ = self.__getstate__()
+
+ def getTracebackObject(self):
+ """
+ Get an object that represents this Failure's stack that can be passed
+ to traceback.extract_tb.
+
+ If the original traceback object is still present, return that. If this
+ traceback object has been lost but we still have the information,
+ return a fake traceback object (see L{_Traceback}). If there is no
+ traceback information at all, return None.
+ """
+ if self.tb is not None:
+ return self.tb
+ elif len(self.frames) > 0:
+ return _Traceback(self.frames)
+ else:
+ return None
+
+ def getErrorMessage(self):
+ """Get a string of the exception which caused this Failure."""
+ if isinstance(self.value, Failure):
+ return self.value.getErrorMessage()
+ return reflect.safe_str(self.value)
+
+ def getBriefTraceback(self):
+ io = StringIO()
+ self.printBriefTraceback(file=io)
+ return io.getvalue()
+
+ def getTraceback(self, elideFrameworkCode=0, detail='default'):
+ io = StringIO()
+ self.printTraceback(file=io, elideFrameworkCode=elideFrameworkCode, detail=detail)
+ return io.getvalue()
+
+ def printTraceback(self, file=None, elideFrameworkCode=0, detail='default'):
+ """Emulate Python's standard error reporting mechanism.
+ """
+ if file is None:
+ file = log.logerr
+ w = file.write
+
+ # Preamble
+ if detail == 'verbose':
+ w( '*--- Failure #%d%s---\n' %
+ (self.count,
+ (self.pickled and ' (pickled) ') or ' '))
+ elif detail == 'brief':
+ if self.frames:
+ hasFrames = 'Traceback'
+ else:
+ hasFrames = 'Traceback (failure with no frames)'
+ w("%s: %s: %s\n" % (hasFrames, self.type, self.value))
+ else:
+ w( 'Traceback (most recent call last):\n')
+
+ # Frames, formatted in appropriate style
+ if self.frames:
+ if not elideFrameworkCode:
+ format_frames(self.stack[-traceupLength:], w, detail)
+ w("%s\n" % (EXCEPTION_CAUGHT_HERE,))
+ format_frames(self.frames, w, detail)
+ elif not detail == 'brief':
+ # Yeah, it's not really a traceback, despite looking like one...
+ w("Failure: ")
+
+ # postamble, if any
+ if not detail == 'brief':
+ # Unfortunately, self.type will not be a class object if this
+ # Failure was created implicitly from a string exception.
+ # qual() doesn't make any sense on a string, so check for this
+ # case here and just write out the string if that's what we
+ # have.
+ if isinstance(self.type, (str, unicode)):
+ w(self.type + "\n")
+ else:
+ w("%s: %s\n" % (reflect.qual(self.type),
+ reflect.safe_str(self.value)))
+ # chaining
+ if isinstance(self.value, Failure):
+ # TODO: indentation for chained failures?
+ file.write(" (chained Failure)\n")
+ self.value.printTraceback(file, elideFrameworkCode, detail)
+ if detail == 'verbose':
+ w('*--- End of Failure #%d ---\n' % self.count)
+
+ def printBriefTraceback(self, file=None, elideFrameworkCode=0):
+ """Print a traceback as densely as possible.
+ """
+ self.printTraceback(file, elideFrameworkCode, detail='brief')
+
+ def printDetailedTraceback(self, file=None, elideFrameworkCode=0):
+ """Print a traceback with detailed locals and globals information.
+ """
+ self.printTraceback(file, elideFrameworkCode, detail='verbose')
+
+# slyphon: make post-morteming exceptions tweakable
+
+DO_POST_MORTEM = True
+
+def _debuginit(self, exc_value=None, exc_type=None, exc_tb=None,
+ Failure__init__=Failure.__init__.im_func):
+ if (exc_value, exc_type, exc_tb) == (None, None, None):
+ exc = sys.exc_info()
+ if not exc[0] == self.__class__ and DO_POST_MORTEM:
+ print "Jumping into debugger for post-mortem of exception '%s':" % exc[1]
+ import pdb
+ pdb.post_mortem(exc[2])
+ Failure__init__(self, exc_value, exc_type, exc_tb)
+
+def startDebugMode():
+ """Enable debug hooks for Failures."""
+ Failure.__init__ = _debuginit
+
+
+# Sibling imports - at the bottom and unqualified to avoid unresolvable
+# circularity
+import log
diff --git a/vendor/Twisted-10.0.0/twisted/python/fakepwd.py b/vendor/Twisted-10.0.0/twisted/python/fakepwd.py
new file mode 100644
index 0000000000..0bc6295dd1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/fakepwd.py
@@ -0,0 +1,112 @@
+# -*- test-case-name: twisted.python.test.test_fakepwd -*-
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+L{twisted.python.fakepwd} provides a fake implementation of the L{pwd} API.
+"""
+
+
+__all__ = ['UserDatabase']
+
+
+class _UserRecord(object):
+ """
+ L{_UserRecord} holds the user data for a single user in L{UserDatabase}.
+ It corresponds to L{pwd.struct_passwd}. See that class for attribute
+ documentation.
+ """
+ def __init__(self, name, password, uid, gid, gecos, home, shell):
+ self.pw_name = name
+ self.pw_passwd = password
+ self.pw_uid = uid
+ self.pw_gid = gid
+ self.pw_gecos = gecos
+ self.pw_dir = home
+ self.pw_shell = shell
+
+
+ def __len__(self):
+ return 7
+
+
+ def __getitem__(self, index):
+ return (
+ self.pw_name, self.pw_passwd, self.pw_uid,
+ self.pw_gid, self.pw_gecos, self.pw_dir, self.pw_shell)[index]
+
+
+
+class UserDatabase(object):
+ """
+ L{UserDatabase} holds a traditional POSIX user data in memory and makes it
+ available via the same API as L{pwd}.
+
+ @ivar _users: A C{list} of L{_UserRecord} instances holding all user data
+ added to this database.
+ """
+ def __init__(self):
+ self._users = []
+
+
+ def addUser(self, username, password, uid, gid, gecos, home, shell):
+ """
+ Add a new user record to this database.
+
+ @param username: The value for the C{pw_name} field of the user
+ record to add.
+ @type username: C{str}
+
+ @param password: The value for the C{pw_passwd} field of the user
+ record to add.
+ @type password: C{str}
+
+ @param uid: The value for the C{pw_uid} field of the user record to
+ add.
+ @type uid: C{int}
+
+ @param gid: The value for the C{pw_gid} field of the user record to
+ add.
+ @type gid: C{int}
+
+ @param gecos: The value for the C{pw_gecos} field of the user record
+ to add.
+ @type gecos: C{str}
+
+ @param home: The value for the C{pw_dir} field of the user record to
+ add.
+ @type home: C{str}
+
+ @param shell: The value for the C{pw_shell} field of the user record to
+ add.
+ @type shell: C{str}
+ """
+ self._users.append(_UserRecord(
+ username, password, uid, gid, gecos, home, shell))
+
+
+ def getpwuid(self, uid):
+ """
+ Return the user record corresponding to the given uid.
+ """
+ for entry in self._users:
+ if entry.pw_uid == uid:
+ return entry
+ raise KeyError()
+
+
+ def getpwnam(self, name):
+ """
+ Return the user record corresponding to the given username.
+ """
+ for entry in self._users:
+ if entry.pw_name == name:
+ return entry
+ raise KeyError()
+
+
+ def getpwall(self):
+ """
+ Return a list of all user records.
+ """
+ return self._users
diff --git a/vendor/Twisted-10.0.0/twisted/python/filepath.py b/vendor/Twisted-10.0.0/twisted/python/filepath.py
new file mode 100644
index 0000000000..d00b22ed7c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/filepath.py
@@ -0,0 +1,802 @@
+# -*- test-case-name: twisted.test.test_paths -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Object-oriented filesystem path representation.
+"""
+
+import os
+import errno
+import random
+import base64
+
+from os.path import isabs, exists, normpath, abspath, splitext
+from os.path import basename, dirname
+from os.path import join as joinpath
+from os import sep as slash
+from os import listdir, utime, stat
+
+from stat import S_ISREG, S_ISDIR
+
+# Please keep this as light as possible on other Twisted imports; many, many
+# things import this module, and it would be good if it could easily be
+# modified for inclusion in the standard library. --glyph
+
+from twisted.python.runtime import platform
+from twisted.python.hashlib import sha1
+
+from twisted.python.win32 import ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND
+from twisted.python.win32 import ERROR_INVALID_NAME, ERROR_DIRECTORY
+from twisted.python.win32 import WindowsError
+
+def _stub_islink(path):
+ """
+ Always return 'false' if the operating system does not support symlinks.
+
+ @param path: a path string.
+ @type path: L{str}
+ @return: false
+ """
+ return False
+
+
+def _stub_urandom(n):
+ """
+ Provide random data in versions of Python prior to 2.4. This is an
+ effectively compatible replacement for 'os.urandom'.
+
+ @type n: L{int}
+ @param n: the number of bytes of data to return
+ @return: C{n} bytes of random data.
+ @rtype: str
+ """
+ randomData = [random.randrange(256) for n in xrange(n)]
+ return ''.join(map(chr, randomData))
+
+
+def _stub_armor(s):
+ """
+ ASCII-armor for random data. This uses a hex encoding, although we will
+ prefer url-safe base64 encoding for features in this module if it is
+ available.
+ """
+ return s.encode('hex')
+
+islink = getattr(os.path, 'islink', _stub_islink)
+randomBytes = getattr(os, 'urandom', _stub_urandom)
+armor = getattr(base64, 'urlsafe_b64encode', _stub_armor)
+
+class InsecurePath(Exception):
+ """
+ Error that is raised when the path provided to FilePath is invalid.
+ """
+
+
+
+class LinkError(Exception):
+ """
+ An error with symlinks - either that there are cyclical symlinks or that
+ symlink are not supported on this platform.
+ """
+
+
+
+class UnlistableError(OSError):
+ """
+ An exception which is used to distinguish between errors which mean 'this
+ is not a directory you can list' and other, more catastrophic errors.
+
+ This error will try to look as much like the original error as possible,
+ while still being catchable as an independent type.
+
+ @ivar originalException: the actual original exception instance, either an
+ L{OSError} or a L{WindowsError}.
+ """
+ def __init__(self, originalException):
+ """
+ Create an UnlistableError exception.
+
+ @param originalException: an instance of OSError.
+ """
+ self.__dict__.update(originalException.__dict__)
+ self.originalException = originalException
+
+
+
+class _WindowsUnlistableError(UnlistableError, WindowsError):
+ """
+ This exception is raised on Windows, for compatibility with previous
+ releases of FilePath where unportable programs may have done "except
+ WindowsError:" around a call to children().
+
+ It is private because all application code may portably catch
+ L{UnlistableError} instead.
+ """
+
+
+
+def _secureEnoughString():
+ """
+ Create a pseudorandom, 16-character string for use in secure filenames.
+ """
+ return armor(sha1(randomBytes(64)).digest())[:16]
+
+
+
+class _PathHelper:
+ """
+ Abstract helper class also used by ZipPath; implements certain utility
+ methods.
+ """
+
+ def getContent(self):
+ return self.open().read()
+
+
+ def parents(self):
+ """
+ @return: an iterator of all the ancestors of this path, from the most
+ recent (its immediate parent) to the root of its filesystem.
+ """
+ path = self
+ parent = path.parent()
+ # root.parent() == root, so this means "are we the root"
+ while path != parent:
+ yield parent
+ path = parent
+ parent = parent.parent()
+
+
+ def children(self):
+ """
+ List the chilren of this path object.
+
+ @raise OSError: If an error occurs while listing the directory. If the
+ error is 'serious', meaning that the operation failed due to an access
+ violation, exhaustion of some kind of resource (file descriptors or
+ memory), OSError or a platform-specific variant will be raised.
+
+ @raise UnlistableError: If the inability to list the directory is due
+ to this path not existing or not being a directory, the more specific
+ OSError subclass L{UnlistableError} is raised instead.
+
+ @return: an iterable of all currently-existing children of this object
+ accessible with L{_PathHelper.child}.
+ """
+ try:
+ subnames = self.listdir()
+ except WindowsError, winErrObj:
+ # WindowsError is an OSError subclass, so if not for this clause
+ # the OSError clause below would be handling these. Windows error
+ # codes aren't the same as POSIX error codes, so we need to handle
+ # them differently.
+
+ # Under Python 2.5 on Windows, WindowsError has a winerror
+ # attribute and an errno attribute. The winerror attribute is
+ # bound to the Windows error code while the errno attribute is
+ # bound to a translation of that code to a perhaps equivalent POSIX
+ # error number.
+
+ # Under Python 2.4 on Windows, WindowsError only has an errno
+ # attribute. It is bound to the Windows error code.
+
+ # For simplicity of code and to keep the number of paths through
+ # this suite minimal, we grab the Windows error code under either
+ # version.
+
+ # Furthermore, attempting to use os.listdir on a non-existent path
+ # in Python 2.4 will result in a Windows error code of
+ # ERROR_PATH_NOT_FOUND. However, in Python 2.5,
+ # ERROR_FILE_NOT_FOUND results instead. -exarkun
+ winerror = getattr(winErrObj, 'winerror', winErrObj.errno)
+ if winerror not in (ERROR_PATH_NOT_FOUND,
+ ERROR_FILE_NOT_FOUND,
+ ERROR_INVALID_NAME,
+ ERROR_DIRECTORY):
+ raise
+ raise _WindowsUnlistableError(winErrObj)
+ except OSError, ose:
+ if ose.errno not in (errno.ENOENT, errno.ENOTDIR):
+ # Other possible errors here, according to linux manpages:
+ # EACCES, EMIFLE, ENFILE, ENOMEM. None of these seem like the
+ # sort of thing which should be handled normally. -glyph
+ raise
+ raise UnlistableError(ose)
+ return map(self.child, subnames)
+
+ def walk(self, descend=None):
+ """
+ Yield myself, then each of my children, and each of those children's
+ children in turn. The optional argument C{descend} is a predicate that
+ takes a FilePath, and determines whether or not that FilePath is
+ traversed/descended into. It will be called with each path for which
+ C{isdir} returns C{True}. If C{descend} is not specified, all
+ directories will be traversed (including symbolic links which refer to
+ directories).
+
+ @param descend: A one-argument callable that will return True for
+ FilePaths that should be traversed, False otherwise.
+
+ @return: a generator yielding FilePath-like objects.
+ """
+ yield self
+ if self.isdir():
+ for c in self.children():
+ # we should first see if it's what we want, then we
+ # can walk through the directory
+ if (descend is None or descend(c)):
+ for subc in c.walk(descend):
+ if os.path.realpath(self.path).startswith(
+ os.path.realpath(subc.path)):
+ raise LinkError("Cycle in file graph.")
+ yield subc
+ else:
+ yield c
+
+
+ def sibling(self, path):
+ return self.parent().child(path)
+
+ def segmentsFrom(self, ancestor):
+ """
+ Return a list of segments between a child and its ancestor.
+
+ For example, in the case of a path X representing /a/b/c/d and a path Y
+ representing /a/b, C{Y.segmentsFrom(X)} will return C{['c',
+ 'd']}.
+
+ @param ancestor: an instance of the same class as self, ostensibly an
+ ancestor of self.
+
+ @raise: ValueError if the 'ancestor' parameter is not actually an
+ ancestor, i.e. a path for /x/y/z is passed as an ancestor for /a/b/c/d.
+
+ @return: a list of strs
+ """
+ # this might be an unnecessarily inefficient implementation but it will
+ # work on win32 and for zipfiles; later I will deterimine if the
+ # obvious fast implemenation does the right thing too
+ f = self
+ p = f.parent()
+ segments = []
+ while f != ancestor and p != f:
+ segments[0:0] = [f.basename()]
+ f = p
+ p = p.parent()
+ if f == ancestor and segments:
+ return segments
+ raise ValueError("%r not parent of %r" % (ancestor, self))
+
+
+ # new in 8.0
+ def __hash__(self):
+ """
+ Hash the same as another FilePath with the same path as mine.
+ """
+ return hash((self.__class__, self.path))
+
+
+ # pending deprecation in 8.0
+ def getmtime(self):
+ """
+ Deprecated. Use getModificationTime instead.
+ """
+ return int(self.getModificationTime())
+
+
+ def getatime(self):
+ """
+ Deprecated. Use getAccessTime instead.
+ """
+ return int(self.getAccessTime())
+
+
+ def getctime(self):
+ """
+ Deprecated. Use getStatusChangeTime instead.
+ """
+ return int(self.getStatusChangeTime())
+
+
+
+class FilePath(_PathHelper):
+ """
+ I am a path on the filesystem that only permits 'downwards' access.
+
+ Instantiate me with a pathname (for example,
+ FilePath('/home/myuser/public_html')) and I will attempt to only provide
+ access to files which reside inside that path. I may be a path to a file,
+ a directory, or a file which does not exist.
+
+ The correct way to use me is to instantiate me, and then do ALL filesystem
+ access through me. In other words, do not import the 'os' module; if you
+ need to open a file, call my 'open' method. If you need to list a
+ directory, call my 'path' method.
+
+ Even if you pass me a relative path, I will convert that to an absolute
+ path internally.
+
+ Note: although time-related methods do return floating-point results, they
+ may still be only second resolution depending on the platform and the last
+ value passed to L{os.stat_float_times}. If you want greater-than-second
+ precision, call C{os.stat_float_times(True)}, or use Python 2.5.
+ Greater-than-second precision is only available in Windows on Python2.5 and
+ later.
+
+ @type alwaysCreate: C{bool}
+ @ivar alwaysCreate: When opening this file, only succeed if the file does not
+ already exist.
+ """
+
+ statinfo = None
+ path = None
+
+ def __init__(self, path, alwaysCreate=False):
+ self.path = abspath(path)
+ self.alwaysCreate = alwaysCreate
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ if d.has_key('statinfo'):
+ del d['statinfo']
+ return d
+
+ def child(self, path):
+ if platform.isWindows() and path.count(":"):
+ # Catch paths like C:blah that don't have a slash
+ raise InsecurePath("%r contains a colon." % (path,))
+ norm = normpath(path)
+ if slash in norm:
+ raise InsecurePath("%r contains one or more directory separators" % (path,))
+ newpath = abspath(joinpath(self.path, norm))
+ if not newpath.startswith(self.path):
+ raise InsecurePath("%r is not a child of %s" % (newpath, self.path))
+ return self.clonePath(newpath)
+
+ def preauthChild(self, path):
+ """
+ Use me if `path' might have slashes in it, but you know they're safe.
+
+ (NOT slashes at the beginning. It still needs to be a _child_).
+ """
+ newpath = abspath(joinpath(self.path, normpath(path)))
+ if not newpath.startswith(self.path):
+ raise InsecurePath("%s is not a child of %s" % (newpath, self.path))
+ return self.clonePath(newpath)
+
+ def childSearchPreauth(self, *paths):
+ """Return my first existing child with a name in 'paths'.
+
+ paths is expected to be a list of *pre-secured* path fragments; in most
+ cases this will be specified by a system administrator and not an
+ arbitrary user.
+
+ If no appropriately-named children exist, this will return None.
+ """
+ p = self.path
+ for child in paths:
+ jp = joinpath(p, child)
+ if exists(jp):
+ return self.clonePath(jp)
+
+ def siblingExtensionSearch(self, *exts):
+ """Attempt to return a path with my name, given multiple possible
+ extensions.
+
+ Each extension in exts will be tested and the first path which exists
+ will be returned. If no path exists, None will be returned. If '' is
+ in exts, then if the file referred to by this path exists, 'self' will
+ be returned.
+
+ The extension '*' has a magic meaning, which means "any path that
+ begins with self.path+'.' is acceptable".
+ """
+ p = self.path
+ for ext in exts:
+ if not ext and self.exists():
+ return self
+ if ext == '*':
+ basedot = basename(p)+'.'
+ for fn in listdir(dirname(p)):
+ if fn.startswith(basedot):
+ return self.clonePath(joinpath(dirname(p), fn))
+ p2 = p + ext
+ if exists(p2):
+ return self.clonePath(p2)
+
+
+ def realpath(self):
+ """
+ Returns the absolute target as a FilePath if self is a link, self
+ otherwise. The absolute link is the ultimate file or directory the
+ link refers to (for instance, if the link refers to another link, and
+ another...). If the filesystem does not support symlinks, or
+ if the link is cyclical, raises a LinkError.
+
+ Behaves like L{os.path.realpath} in that it does not resolve link
+ names in the middle (ex. /x/y/z, y is a link to w - realpath on z
+ will return /x/y/z, not /x/w/z).
+
+ @return: FilePath of the target path
+ @raises LinkError: if links are not supported or links are cyclical.
+ """
+ if self.islink():
+ result = os.path.realpath(self.path)
+ if result == self.path:
+ raise LinkError("Cyclical link - will loop forever")
+ return self.clonePath(result)
+ return self
+
+
+ def siblingExtension(self, ext):
+ return self.clonePath(self.path+ext)
+
+
+ def linkTo(self, linkFilePath):
+ """
+ Creates a symlink to self to at the path in the L{FilePath}
+ C{linkFilePath}. Only works on posix systems due to its dependence on
+ C{os.symlink}. Propagates C{OSError}s up from C{os.symlink} if
+ C{linkFilePath.parent()} does not exist, or C{linkFilePath} already
+ exists.
+
+ @param linkFilePath: a FilePath representing the link to be created
+ @type linkFilePath: L{FilePath}
+ """
+ os.symlink(self.path, linkFilePath.path)
+
+
+ def open(self, mode='r'):
+ if self.alwaysCreate:
+ assert 'a' not in mode, "Appending not supported when alwaysCreate == True"
+ return self.create()
+ return open(self.path, mode+'b')
+
+ # stat methods below
+
+ def restat(self, reraise=True):
+ """
+ Re-calculate cached effects of 'stat'. To refresh information on this path
+ after you know the filesystem may have changed, call this method.
+
+ @param reraise: a boolean. If true, re-raise exceptions from
+ L{os.stat}; otherwise, mark this path as not existing, and remove any
+ cached stat information.
+ """
+ try:
+ self.statinfo = stat(self.path)
+ except OSError:
+ self.statinfo = 0
+ if reraise:
+ raise
+
+
+ def chmod(self, mode):
+ """
+ Changes the permissions on self, if possible. Propagates errors from
+ C{os.chmod} up.
+
+ @param mode: integer representing the new permissions desired (same as
+ the command line chmod)
+ @type mode: C{int}
+ """
+ os.chmod(self.path, mode)
+
+
+ def getsize(self):
+ st = self.statinfo
+ if not st:
+ self.restat()
+ st = self.statinfo
+ return st.st_size
+
+
+ def getModificationTime(self):
+ """
+ Retrieve the time of last access from this file.
+
+ @return: a number of seconds from the epoch.
+ @rtype: float
+ """
+ st = self.statinfo
+ if not st:
+ self.restat()
+ st = self.statinfo
+ return float(st.st_mtime)
+
+
+ def getStatusChangeTime(self):
+ """
+ Retrieve the time of the last status change for this file.
+
+ @return: a number of seconds from the epoch.
+ @rtype: float
+ """
+ st = self.statinfo
+ if not st:
+ self.restat()
+ st = self.statinfo
+ return float(st.st_ctime)
+
+
+ def getAccessTime(self):
+ """
+ Retrieve the time that this file was last accessed.
+
+ @return: a number of seconds from the epoch.
+ @rtype: float
+ """
+ st = self.statinfo
+ if not st:
+ self.restat()
+ st = self.statinfo
+ return float(st.st_atime)
+
+
+ def exists(self):
+ """
+ Check if the C{path} exists.
+
+ @return: C{True} if the stats of C{path} can be retrieved successfully,
+ C{False} in the other cases.
+ @rtype: C{bool}
+ """
+ if self.statinfo:
+ return True
+ else:
+ self.restat(False)
+ if self.statinfo:
+ return True
+ else:
+ return False
+
+
+ def isdir(self):
+ st = self.statinfo
+ if not st:
+ self.restat(False)
+ st = self.statinfo
+ if not st:
+ return False
+ return S_ISDIR(st.st_mode)
+
+ def isfile(self):
+ st = self.statinfo
+ if not st:
+ self.restat(False)
+ st = self.statinfo
+ if not st:
+ return False
+ return S_ISREG(st.st_mode)
+
+ def islink(self):
+ # We can't use cached stat results here, because that is the stat of
+ # the destination - (see #1773) which in *every case* but this one is
+ # the right thing to use. We could call lstat here and use that, but
+ # it seems unlikely we'd actually save any work that way. -glyph
+ return islink(self.path)
+
+ def isabs(self):
+ return isabs(self.path)
+
+ def listdir(self):
+ return listdir(self.path)
+
+ def splitext(self):
+ return splitext(self.path)
+
+ def __repr__(self):
+ return 'FilePath(%r)' % (self.path,)
+
+ def touch(self):
+ try:
+ self.open('a').close()
+ except IOError:
+ pass
+ utime(self.path, None)
+
+ def remove(self):
+ """
+ Removes the file or directory that is represented by self. If
+ C{self.path} is a directory, recursively remove all its children
+ before removing the directory. If it's a file or link, just delete
+ it.
+ """
+ if self.isdir() and not self.islink():
+ for child in self.children():
+ child.remove()
+ os.rmdir(self.path)
+ else:
+ os.remove(self.path)
+ self.restat(False)
+
+
+ def makedirs(self):
+ """
+ Create all directories not yet existing in C{path} segments, using
+ C{os.makedirs}.
+ """
+ return os.makedirs(self.path)
+
+
+ def globChildren(self, pattern):
+ """
+ Assuming I am representing a directory, return a list of
+ FilePaths representing my children that match the given
+ pattern.
+ """
+ import glob
+ path = self.path[-1] == '/' and self.path + pattern or slash.join([self.path, pattern])
+ return map(self.clonePath, glob.glob(path))
+
+ def basename(self):
+ return basename(self.path)
+
+ def dirname(self):
+ return dirname(self.path)
+
+ def parent(self):
+ return self.clonePath(self.dirname())
+
+ def setContent(self, content, ext='.new'):
+ sib = self.siblingExtension(ext)
+ f = sib.open('w')
+ f.write(content)
+ f.close()
+ if platform.isWindows() and exists(self.path):
+ os.unlink(self.path)
+ os.rename(sib.path, self.path)
+
+ # new in 2.2.0
+
+ def __cmp__(self, other):
+ if not isinstance(other, FilePath):
+ return NotImplemented
+ return cmp(self.path, other.path)
+
+ def createDirectory(self):
+ os.mkdir(self.path)
+
+ def requireCreate(self, val=1):
+ self.alwaysCreate = val
+
+ def create(self):
+ """Exclusively create a file, only if this file previously did not exist.
+ """
+ fdint = os.open(self.path, (os.O_EXCL |
+ os.O_CREAT |
+ os.O_RDWR))
+
+ # XXX TODO: 'name' attribute of returned files is not mutable or
+ # settable via fdopen, so this file is slighly less functional than the
+ # one returned from 'open' by default. send a patch to Python...
+
+ return os.fdopen(fdint, 'w+b')
+
+ def temporarySibling(self):
+ """
+ Create a path naming a temporary sibling of this path in a secure fashion.
+ """
+ sib = self.sibling(_secureEnoughString() + self.basename())
+ sib.requireCreate()
+ return sib
+
+ _chunkSize = 2 ** 2 ** 2 ** 2
+
+
+ def copyTo(self, destination, followLinks=True):
+ """
+ Copies self to destination.
+
+ If self is a directory, this method copies its children (but not
+ itself) recursively to destination - if destination does not exist as a
+ directory, this method creates it. If destination is a file, an
+ IOError will be raised.
+
+ If self is a file, this method copies it to destination. If
+ destination is a file, this method overwrites it. If destination is a
+ directory, an IOError will be raised.
+
+ If self is a link (and followLinks is False), self will be copied
+ over as a new symlink with the same target as returned by os.readlink.
+ That means that if it is absolute, both the old and new symlink will
+ link to the same thing. If it's relative, then perhaps not (and
+ it's also possible that this relative link will be broken).
+
+ File/directory permissions and ownership will NOT be copied over.
+
+ If followLinks is True, symlinks are followed so that they're treated
+ as their targets. In other words, if self is a link, the link's target
+ will be copied. If destination is a link, self will be copied to the
+ destination's target (the actual destination will be destination's
+ target). Symlinks under self (if self is a directory) will be
+ followed and its target's children be copied recursively.
+
+ If followLinks is False, symlinks will be copied over as symlinks.
+
+ @param destination: the destination (a FilePath) to which self
+ should be copied
+ @param followLinks: whether symlinks in self should be treated as links
+ or as their targets
+ """
+ if self.islink() and not followLinks:
+ os.symlink(os.readlink(self.path), destination.path)
+ return
+ # XXX TODO: *thorough* audit and documentation of the exact desired
+ # semantics of this code. Right now the behavior of existent
+ # destination symlinks is convenient, and quite possibly correct, but
+ # its security properties need to be explained.
+ if self.isdir():
+ if not destination.exists():
+ destination.createDirectory()
+ for child in self.children():
+ destChild = destination.child(child.basename())
+ child.copyTo(destChild, followLinks)
+ elif self.isfile():
+ writefile = destination.open('w')
+ readfile = self.open()
+ while 1:
+ # XXX TODO: optionally use os.open, os.read and O_DIRECT and
+ # use os.fstatvfs to determine chunk sizes and make
+ # *****sure**** copy is page-atomic; the following is good
+ # enough for 99.9% of everybody and won't take a week to audit
+ # though.
+ chunk = readfile.read(self._chunkSize)
+ writefile.write(chunk)
+ if len(chunk) < self._chunkSize:
+ break
+ writefile.close()
+ readfile.close()
+ else:
+ # If you see the following message because you want to copy
+ # symlinks, fifos, block devices, character devices, or unix
+ # sockets, please feel free to add support to do sensible things in
+ # reaction to those types!
+ raise NotImplementedError(
+ "Only copying of files and directories supported")
+
+
+ def moveTo(self, destination, followLinks=True):
+ """
+ Move self to destination - basically renaming self to whatever
+ destination is named. If destination is an already-existing directory,
+ moves all children to destination if destination is empty. If
+ destination is a non-empty directory, or destination is a file, an
+ OSError will be raised.
+
+ If moving between filesystems, self needs to be copied, and everything
+ that applies to copyTo applies to moveTo.
+
+ @param destination: the destination (a FilePath) to which self
+ should be copied
+ @param followLinks: whether symlinks in self should be treated as links
+ or as their targets (only applicable when moving between
+ filesystems)
+ """
+ try:
+ os.rename(self.path, destination.path)
+ self.restat(False)
+ except OSError, ose:
+ if ose.errno == errno.EXDEV:
+ # man 2 rename, ubuntu linux 5.10 "breezy":
+
+ # oldpath and newpath are not on the same mounted filesystem.
+ # (Linux permits a filesystem to be mounted at multiple
+ # points, but rename(2) does not work across different mount
+ # points, even if the same filesystem is mounted on both.)
+
+ # that means it's time to copy trees of directories!
+ secsib = destination.temporarySibling()
+ self.copyTo(secsib, followLinks) # slow
+ secsib.moveTo(destination, followLinks) # visible
+
+ # done creating new stuff. let's clean me up.
+ mysecsib = self.temporarySibling()
+ self.moveTo(mysecsib, followLinks) # visible
+ mysecsib.remove() # slow
+ else:
+ raise
+
+
+FilePath.clonePath = FilePath
diff --git a/vendor/Twisted-10.0.0/twisted/python/finalize.py b/vendor/Twisted-10.0.0/twisted/python/finalize.py
new file mode 100644
index 0000000000..8b99bf6aa8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/finalize.py
@@ -0,0 +1,46 @@
+
+"""
+A module for externalized finalizers.
+"""
+
+import weakref
+
+garbageKey = 0
+
+def callbackFactory(num, fins):
+ def _cb(w):
+ del refs[num]
+ for fx in fins:
+ fx()
+ return _cb
+
+refs = {}
+
+def register(inst):
+ global garbageKey
+ garbageKey += 1
+ r = weakref.ref(inst, callbackFactory(garbageKey, inst.__finalizers__()))
+ refs[garbageKey] = r
+
+if __name__ == '__main__':
+ def fin():
+ print 'I am _so_ dead.'
+
+ class Finalizeable:
+ """
+ An un-sucky __del__
+ """
+
+ def __finalizers__(self):
+ """
+ I'm going away.
+ """
+ return [fin]
+
+ f = Finalizeable()
+ f.f2 = f
+ register(f)
+ del f
+ import gc
+ gc.collect()
+ print 'deled'
diff --git a/vendor/Twisted-10.0.0/twisted/python/formmethod.py b/vendor/Twisted-10.0.0/twisted/python/formmethod.py
new file mode 100644
index 0000000000..1b685cec7e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/formmethod.py
@@ -0,0 +1,363 @@
+# -*- test-case-name: twisted.test.test_formmethod -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Form-based method objects.
+
+This module contains support for descriptive method signatures that can be used
+to format methods.
+"""
+
+import calendar
+
+class FormException(Exception):
+ """An error occurred calling the form method.
+ """
+ def __init__(self, *args, **kwargs):
+ Exception.__init__(self, *args)
+ self.descriptions = kwargs
+
+
+class InputError(FormException):
+ """
+ An error occurred with some input.
+ """
+
+
+class Argument:
+ """Base class for form arguments."""
+
+ # default value for argument, if no other default is given
+ defaultDefault = None
+
+ def __init__(self, name, default=None, shortDesc=None,
+ longDesc=None, hints=None, allowNone=1):
+ self.name = name
+ self.allowNone = allowNone
+ if default is None:
+ default = self.defaultDefault
+ self.default = default
+ self.shortDesc = shortDesc
+ self.longDesc = longDesc
+ if not hints:
+ hints = {}
+ self.hints = hints
+
+ def addHints(self, **kwargs):
+ self.hints.update(kwargs)
+
+ def getHint(self, name, default=None):
+ return self.hints.get(name, default)
+
+ def getShortDescription(self):
+ return self.shortDesc or self.name.capitalize()
+
+ def getLongDescription(self):
+ return self.longDesc or '' #self.shortDesc or "The %s." % self.name
+
+ def coerce(self, val):
+ """Convert the value to the correct format."""
+ raise NotImplementedError, "implement in subclass"
+
+
+class String(Argument):
+ """A single string.
+ """
+ defaultDefault = ''
+ min = 0
+ max = None
+
+ def __init__(self, name, default=None, shortDesc=None,
+ longDesc=None, hints=None, allowNone=1, min=0, max=None):
+ Argument.__init__(self, name, default=default, shortDesc=shortDesc,
+ longDesc=longDesc, hints=hints, allowNone=allowNone)
+ self.min = min
+ self.max = max
+
+ def coerce(self, val):
+ s = str(val)
+ if len(s) < self.min:
+ raise InputError, "Value must be at least %s characters long" % self.min
+ if self.max != None and len(s) > self.max:
+ raise InputError, "Value must be at most %s characters long" % self.max
+ return str(val)
+
+
+class Text(String):
+ """A long string.
+ """
+
+
+class Password(String):
+ """A string which should be obscured when input.
+ """
+
+
+class VerifiedPassword(String):
+ """A string that should be obscured when input and needs verification."""
+
+ def coerce(self, vals):
+ if len(vals) != 2 or vals[0] != vals[1]:
+ raise InputError, "Please enter the same password twice."
+ s = str(vals[0])
+ if len(s) < self.min:
+ raise InputError, "Value must be at least %s characters long" % self.min
+ if self.max != None and len(s) > self.max:
+ raise InputError, "Value must be at most %s characters long" % self.max
+ return s
+
+
+class Hidden(String):
+ """A string which is not displayed.
+
+ The passed default is used as the value.
+ """
+
+
+class Integer(Argument):
+ """A single integer.
+ """
+ defaultDefault = None
+
+ def __init__(self, name, allowNone=1, default=None, shortDesc=None,
+ longDesc=None, hints=None):
+ #although Argument now has allowNone, that was recently added, and
+ #putting it at the end kept things which relied on argument order
+ #from breaking. However, allowNone originally was in here, so
+ #I have to keep the same order, to prevent breaking code that
+ #depends on argument order only
+ Argument.__init__(self, name, default, shortDesc, longDesc, hints,
+ allowNone)
+
+ def coerce(self, val):
+ if not val.strip() and self.allowNone:
+ return None
+ try:
+ return int(val)
+ except ValueError:
+ raise InputError, "%s is not valid, please enter a whole number, e.g. 10" % val
+
+
+class IntegerRange(Integer):
+
+ def __init__(self, name, min, max, allowNone=1, default=None, shortDesc=None,
+ longDesc=None, hints=None):
+ self.min = min
+ self.max = max
+ Integer.__init__(self, name, allowNone=allowNone, default=default, shortDesc=shortDesc,
+ longDesc=longDesc, hints=hints)
+
+ def coerce(self, val):
+ result = Integer.coerce(self, val)
+ if self.allowNone and result == None:
+ return result
+ if result < self.min:
+ raise InputError, "Value %s is too small, it should be at least %s" % (result, self.min)
+ if result > self.max:
+ raise InputError, "Value %s is too large, it should be at most %s" % (result, self.max)
+ return result
+
+
+class Float(Argument):
+
+ defaultDefault = None
+
+ def __init__(self, name, allowNone=1, default=None, shortDesc=None,
+ longDesc=None, hints=None):
+ #although Argument now has allowNone, that was recently added, and
+ #putting it at the end kept things which relied on argument order
+ #from breaking. However, allowNone originally was in here, so
+ #I have to keep the same order, to prevent breaking code that
+ #depends on argument order only
+ Argument.__init__(self, name, default, shortDesc, longDesc, hints,
+ allowNone)
+
+
+ def coerce(self, val):
+ if not val.strip() and self.allowNone:
+ return None
+ try:
+ return float(val)
+ except ValueError:
+ raise InputError, "Invalid float: %s" % val
+
+
+class Choice(Argument):
+ """
+ The result of a choice between enumerated types. The choices should
+ be a list of tuples of tag, value, and description. The tag will be
+ the value returned if the user hits "Submit", and the description
+ is the bale for the enumerated type. default is a list of all the
+ values (seconds element in choices). If no defaults are specified,
+ initially the first item will be selected. Only one item can (should)
+ be selected at once.
+ """
+ def __init__(self, name, choices=[], default=[], shortDesc=None,
+ longDesc=None, hints=None, allowNone=1):
+ self.choices = choices
+ if choices and not default:
+ default.append(choices[0][1])
+ Argument.__init__(self, name, default, shortDesc, longDesc, hints, allowNone=allowNone)
+
+ def coerce(self, inIdent):
+ for ident, val, desc in self.choices:
+ if ident == inIdent:
+ return val
+ else:
+ raise InputError("Invalid Choice: %s" % inIdent)
+
+
+class Flags(Argument):
+ """
+ The result of a checkbox group or multi-menu. The flags should be a
+ list of tuples of tag, value, and description. The tag will be
+ the value returned if the user hits "Submit", and the description
+ is the bale for the enumerated type. default is a list of all the
+ values (second elements in flags). If no defaults are specified,
+ initially nothing will be selected. Several items may be selected at
+ once.
+ """
+ def __init__(self, name, flags=(), default=(), shortDesc=None,
+ longDesc=None, hints=None, allowNone=1):
+ self.flags = flags
+ Argument.__init__(self, name, default, shortDesc, longDesc, hints, allowNone=allowNone)
+
+ def coerce(self, inFlagKeys):
+ if not inFlagKeys:
+ return []
+ outFlags = []
+ for inFlagKey in inFlagKeys:
+ for flagKey, flagVal, flagDesc in self.flags:
+ if inFlagKey == flagKey:
+ outFlags.append(flagVal)
+ break
+ else:
+ raise InputError("Invalid Flag: %s" % inFlagKey)
+ return outFlags
+
+
+class CheckGroup(Flags):
+ pass
+
+
+class RadioGroup(Choice):
+ pass
+
+
+class Boolean(Argument):
+ def coerce(self, inVal):
+ if not inVal:
+ return 0
+ lInVal = str(inVal).lower()
+ if lInVal in ('no', 'n', 'f', 'false', '0'):
+ return 0
+ return 1
+
+class File(Argument):
+ def __init__(self, name, allowNone=1, shortDesc=None, longDesc=None,
+ hints=None):
+ self.allowNone = allowNone
+ Argument.__init__(self, name, None, shortDesc, longDesc, hints)
+
+ def coerce(self, file):
+ if not file and self.allowNone:
+ return None
+ elif file:
+ return file
+ else:
+ raise InputError, "Invalid File"
+
+def positiveInt(x):
+ x = int(x)
+ if x <= 0: raise ValueError
+ return x
+
+class Date(Argument):
+ """A date -- (year, month, day) tuple."""
+
+ defaultDefault = None
+
+ def __init__(self, name, allowNone=1, default=None, shortDesc=None,
+ longDesc=None, hints=None):
+ Argument.__init__(self, name, default, shortDesc, longDesc, hints)
+ self.allowNone = allowNone
+ if not allowNone:
+ self.defaultDefault = (1970, 1, 1)
+
+ def coerce(self, args):
+ """Return tuple of ints (year, month, day)."""
+ if tuple(args) == ("", "", "") and self.allowNone:
+ return None
+
+ try:
+ year, month, day = map(positiveInt, args)
+ except ValueError:
+ raise InputError, "Invalid date"
+ if (month, day) == (2, 29):
+ if not calendar.isleap(year):
+ raise InputError, "%d was not a leap year" % year
+ else:
+ return year, month, day
+ try:
+ mdays = calendar.mdays[month]
+ except IndexError:
+ raise InputError, "Invalid date"
+ if day > mdays:
+ raise InputError, "Invalid date"
+ return year, month, day
+
+
+class Submit(Choice):
+ """Submit button or a reasonable facsimile thereof."""
+
+ def __init__(self, name, choices=[("Submit", "submit", "Submit form")],
+ reset=0, shortDesc=None, longDesc=None, allowNone=0, hints=None):
+ Choice.__init__(self, name, choices=choices, shortDesc=shortDesc,
+ longDesc=longDesc, hints=hints)
+ self.allowNone = allowNone
+ self.reset = reset
+
+ def coerce(self, value):
+ if self.allowNone and not value:
+ return None
+ else:
+ return Choice.coerce(self, value)
+
+
+class PresentationHint:
+ """
+ A hint to a particular system.
+ """
+
+
+class MethodSignature:
+
+ def __init__(self, *sigList):
+ """
+ """
+ self.methodSignature = sigList
+
+ def getArgument(self, name):
+ for a in self.methodSignature:
+ if a.name == name:
+ return a
+
+ def method(self, callable, takesRequest=False):
+ return FormMethod(self, callable, takesRequest)
+
+
+class FormMethod:
+ """A callable object with a signature."""
+
+ def __init__(self, signature, callable, takesRequest=False):
+ self.signature = signature
+ self.callable = callable
+ self.takesRequest = takesRequest
+
+ def getArgs(self):
+ return tuple(self.signature.methodSignature)
+
+ def call(self,*args,**kw):
+ return self.callable(*args,**kw)
diff --git a/vendor/Twisted-10.0.0/twisted/python/hashlib.py b/vendor/Twisted-10.0.0/twisted/python/hashlib.py
new file mode 100644
index 0000000000..bca3f22b1d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/hashlib.py
@@ -0,0 +1,24 @@
+# -*- test-case-name: twisted.python.test.test_hashlib -*-
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+L{twisted.python.hashlib} presents a subset of the interface provided by
+U{hashlib<http://docs.python.org/library/hashlib.html>}. The subset is the
+interface required by various parts of Twisted. This allows application code
+to transparently use APIs which existed before C{hashlib} was introduced or to
+use C{hashlib} if it is available.
+"""
+
+
+try:
+ _hashlib = __import__("hashlib")
+except ImportError:
+ from md5 import md5
+ from sha import sha as sha1
+else:
+ md5 = _hashlib.md5
+ sha1 = _hashlib.sha1
+
+
+__all__ = ["md5", "sha1"]
diff --git a/vendor/Twisted-10.0.0/twisted/python/hook.py b/vendor/Twisted-10.0.0/twisted/python/hook.py
new file mode 100644
index 0000000000..3c85ca74c0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/hook.py
@@ -0,0 +1,177 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+
+"""
+I define support for hookable instance methods.
+
+These are methods which you can register pre-call and post-call external
+functions to augment their functionality. People familiar with more esoteric
+languages may think of these as \"method combinations\".
+
+This could be used to add optional preconditions, user-extensible callbacks
+(a-la emacs) or a thread-safety mechanism.
+
+The four exported calls are:
+
+ - L{addPre}
+ - L{addPost}
+ - L{removePre}
+ - L{removePost}
+
+All have the signature (class, methodName, callable), and the callable they
+take must always have the signature (instance, *args, **kw) unless the
+particular signature of the method they hook is known.
+
+Hooks should typically not throw exceptions, however, no effort will be made by
+this module to prevent them from doing so. Pre-hooks will always be called,
+but post-hooks will only be called if the pre-hooks do not raise any exceptions
+(they will still be called if the main method raises an exception). The return
+values and exception status of the main method will be propogated (assuming
+none of the hooks raise an exception). Hooks will be executed in the order in
+which they are added.
+
+"""
+
+# System Imports
+import string
+
+### Public Interface
+
+class HookError(Exception):
+ "An error which will fire when an invariant is violated."
+
+def addPre(klass, name, func):
+ """hook.addPre(klass, name, func) -> None
+
+ Add a function to be called before the method klass.name is invoked.
+ """
+
+ _addHook(klass, name, PRE, func)
+
+def addPost(klass, name, func):
+ """hook.addPost(klass, name, func) -> None
+
+ Add a function to be called after the method klass.name is invoked.
+ """
+ _addHook(klass, name, POST, func)
+
+def removePre(klass, name, func):
+ """hook.removePre(klass, name, func) -> None
+
+ Remove a function (previously registered with addPre) so that it
+ is no longer executed before klass.name.
+ """
+
+ _removeHook(klass, name, PRE, func)
+
+def removePost(klass, name, func):
+ """hook.removePre(klass, name, func) -> None
+
+ Remove a function (previously registered with addPost) so that it
+ is no longer executed after klass.name.
+ """
+ _removeHook(klass, name, POST, func)
+
+### "Helper" functions.
+
+hooked_func = """
+
+import %(module)s
+
+def %(name)s(*args, **kw):
+ klazz = %(module)s.%(klass)s
+ for preMethod in klazz.%(preName)s:
+ preMethod(*args, **kw)
+ try:
+ return klazz.%(originalName)s(*args, **kw)
+ finally:
+ for postMethod in klazz.%(postName)s:
+ postMethod(*args, **kw)
+"""
+
+_PRE = '__hook_pre_%s_%s_%s__'
+_POST = '__hook_post_%s_%s_%s__'
+_ORIG = '__hook_orig_%s_%s_%s__'
+
+
+def _XXX(k,n,s):
+ "string manipulation garbage"
+ x = s % (string.replace(k.__module__,'.','_'), k.__name__, n)
+ return x
+
+def PRE(k,n):
+ "(private) munging to turn a method name into a pre-hook-method-name"
+ return _XXX(k,n,_PRE)
+
+def POST(k,n):
+ "(private) munging to turn a method name into a post-hook-method-name"
+ return _XXX(k,n,_POST)
+
+def ORIG(k,n):
+ "(private) munging to turn a method name into an `original' identifier"
+ return _XXX(k,n,_ORIG)
+
+
+def _addHook(klass, name, phase, func):
+ "(private) adds a hook to a method on a class"
+ _enhook(klass, name)
+
+ if not hasattr(klass, phase(klass, name)):
+ setattr(klass, phase(klass, name), [])
+
+ phaselist = getattr(klass, phase(klass, name))
+ phaselist.append(func)
+
+
+def _removeHook(klass, name, phase, func):
+ "(private) removes a hook from a method on a class"
+ phaselistname = phase(klass, name)
+ if not hasattr(klass, ORIG(klass,name)):
+ raise HookError("no hooks present!")
+
+ phaselist = getattr(klass, phase(klass, name))
+ try: phaselist.remove(func)
+ except ValueError:
+ raise HookError("hook %s not found in removal list for %s"%
+ (name,klass))
+
+ if not getattr(klass, PRE(klass,name)) and not getattr(klass, POST(klass, name)):
+ _dehook(klass, name)
+
+def _enhook(klass, name):
+ "(private) causes a certain method name to be hooked on a class"
+ if hasattr(klass, ORIG(klass, name)):
+ return
+
+ def newfunc(*args, **kw):
+ for preMethod in getattr(klass, PRE(klass, name)):
+ preMethod(*args, **kw)
+ try:
+ return getattr(klass, ORIG(klass, name))(*args, **kw)
+ finally:
+ for postMethod in getattr(klass, POST(klass, name)):
+ postMethod(*args, **kw)
+ try:
+ newfunc.func_name = name
+ except TypeError:
+ # Older python's don't let you do this
+ pass
+
+ oldfunc = getattr(klass, name).im_func
+ setattr(klass, ORIG(klass, name), oldfunc)
+ setattr(klass, PRE(klass, name), [])
+ setattr(klass, POST(klass, name), [])
+ setattr(klass, name, newfunc)
+
+def _dehook(klass, name):
+ "(private) causes a certain method name no longer to be hooked on a class"
+
+ if not hasattr(klass, ORIG(klass, name)):
+ raise HookError("Cannot unhook!")
+ setattr(klass, name, getattr(klass, ORIG(klass,name)))
+ delattr(klass, PRE(klass,name))
+ delattr(klass, POST(klass,name))
+ delattr(klass, ORIG(klass,name))
diff --git a/vendor/Twisted-10.0.0/twisted/python/htmlizer.py b/vendor/Twisted-10.0.0/twisted/python/htmlizer.py
new file mode 100644
index 0000000000..cf803ee76c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/htmlizer.py
@@ -0,0 +1,91 @@
+# -*- test-case-name: twisted.python.test.test_htmlizer -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+HTML rendering of Python source.
+"""
+
+import tokenize, cgi, keyword
+import reflect
+
+class TokenPrinter:
+
+ currentCol, currentLine = 0, 1
+ lastIdentifier = parameters = 0
+
+ def __init__(self, writer):
+ self.writer = writer
+
+ def printtoken(self, type, token, (srow, scol), (erow, ecol), line):
+ #print "printtoken(%r,%r,%r,(%r,%r),(%r,%r),%r), row=%r,col=%r" % (
+ # self, type, token, srow,scol, erow,ecol, line,
+ # self.currentLine, self.currentCol)
+ if self.currentLine < srow:
+ self.writer('\n'*(srow-self.currentLine))
+ self.currentLine, self.currentCol = srow, 0
+ self.writer(' '*(scol-self.currentCol))
+ if self.lastIdentifier:
+ type = "identifier"
+ self.parameters = 1
+ elif type == tokenize.NAME:
+ if keyword.iskeyword(token):
+ type = 'keyword'
+ else:
+ if self.parameters:
+ type = 'parameter'
+ else:
+ type = 'variable'
+ else:
+ type = tokenize.tok_name.get(type).lower()
+ self.writer(token, type)
+ self.currentCol = ecol
+ self.currentLine += token.count('\n')
+ if self.currentLine != erow:
+ self.currentCol = 0
+ self.lastIdentifier = token in ('def', 'class')
+ if token == ':':
+ self.parameters = 0
+
+
+class HTMLWriter:
+
+ noSpan = []
+
+ def __init__(self, writer):
+ self.writer = writer
+ noSpan = []
+ reflect.accumulateClassList(self.__class__, "noSpan", noSpan)
+ self.noSpan = noSpan
+
+ def write(self, token, type=None):
+ token = cgi.escape(token)
+ if (type is None) or (type in self.noSpan):
+ self.writer(token)
+ else:
+ self.writer('<span class="py-src-%s">%s</span>' %
+ (type, token))
+
+
+class SmallerHTMLWriter(HTMLWriter):
+ """HTMLWriter that doesn't generate spans for some junk.
+
+ Results in much smaller HTML output.
+ """
+ noSpan = ["endmarker", "indent", "dedent", "op", "newline", "nl"]
+
+def filter(inp, out, writer=HTMLWriter):
+ out.write('<pre>')
+ printer = TokenPrinter(writer(out.write).write).printtoken
+ try:
+ tokenize.tokenize(inp.readline, printer)
+ except tokenize.TokenError:
+ pass
+ out.write('</pre>\n')
+
+def main():
+ import sys
+ filter(open(sys.argv[1]), sys.stdout)
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/twisted/python/lockfile.py b/vendor/Twisted-10.0.0/twisted/python/lockfile.py
new file mode 100644
index 0000000000..df8d9a804f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/lockfile.py
@@ -0,0 +1,212 @@
+# -*- test-case-name: twisted.test.test_lockfile -*-
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Filesystem-based interprocess mutex.
+"""
+
+__metaclass__ = type
+
+import errno, os
+
+from time import time as _uniquefloat
+
+def unique():
+ return str(long(_uniquefloat() * 1000))
+
+from os import rename
+try:
+ from os import kill
+ from os import symlink
+ from os import readlink
+ from os import remove as rmlink
+ _windows = False
+except:
+ _windows = True
+
+ try:
+ from win32api import OpenProcess
+ import pywintypes
+ except ImportError:
+ kill = None
+ else:
+ ERROR_ACCESS_DENIED = 5
+ ERROR_INVALID_PARAMETER = 87
+
+ def kill(pid, signal):
+ try:
+ OpenProcess(0, 0, pid)
+ except pywintypes.error, e:
+ if e.args[0] == ERROR_ACCESS_DENIED:
+ return
+ elif e.args[0] == ERROR_INVALID_PARAMETER:
+ raise OSError(errno.ESRCH, None)
+ raise
+ else:
+ raise RuntimeError("OpenProcess is required to fail.")
+
+ _open = file
+
+ # XXX Implement an atomic thingamajig for win32
+ def symlink(value, filename):
+ newlinkname = filename+"."+unique()+'.newlink'
+ newvalname = os.path.join(newlinkname,"symlink")
+ os.mkdir(newlinkname)
+ f = _open(newvalname,'wcb')
+ f.write(value)
+ f.flush()
+ f.close()
+ try:
+ rename(newlinkname, filename)
+ except:
+ os.remove(newvalname)
+ os.rmdir(newlinkname)
+ raise
+
+ def readlink(filename):
+ try:
+ fObj = _open(os.path.join(filename,'symlink'), 'rb')
+ except IOError, e:
+ if e.errno == errno.ENOENT or e.errno == errno.EIO:
+ raise OSError(e.errno, None)
+ raise
+ else:
+ result = fObj.read()
+ fObj.close()
+ return result
+
+ def rmlink(filename):
+ os.remove(os.path.join(filename, 'symlink'))
+ os.rmdir(filename)
+
+
+
+class FilesystemLock:
+ """
+ A mutex.
+
+ This relies on the filesystem property that creating
+ a symlink is an atomic operation and that it will
+ fail if the symlink already exists. Deleting the
+ symlink will release the lock.
+
+ @ivar name: The name of the file associated with this lock.
+
+ @ivar clean: Indicates whether this lock was released cleanly by its
+ last owner. Only meaningful after C{lock} has been called and
+ returns True.
+
+ @ivar locked: Indicates whether the lock is currently held by this
+ object.
+ """
+
+ clean = None
+ locked = False
+
+ def __init__(self, name):
+ self.name = name
+
+
+ def lock(self):
+ """
+ Acquire this lock.
+
+ @rtype: C{bool}
+ @return: True if the lock is acquired, false otherwise.
+
+ @raise: Any exception os.symlink() may raise, other than
+ EEXIST.
+ """
+ clean = True
+ while True:
+ try:
+ symlink(str(os.getpid()), self.name)
+ except OSError, e:
+ if _windows and e.errno in (errno.EACCES, errno.EIO):
+ # The lock is in the middle of being deleted because we're
+ # on Windows where lock removal isn't atomic. Give up, we
+ # don't know how long this is going to take.
+ return False
+ if e.errno == errno.EEXIST:
+ try:
+ pid = readlink(self.name)
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ # The lock has vanished, try to claim it in the
+ # next iteration through the loop.
+ continue
+ raise
+ except IOError, e:
+ if _windows and e.errno == errno.EACCES:
+ # The lock is in the middle of being
+ # deleted because we're on Windows where
+ # lock removal isn't atomic. Give up, we
+ # don't know how long this is going to
+ # take.
+ return False
+ raise
+ try:
+ if kill is not None:
+ kill(int(pid), 0)
+ except OSError, e:
+ if e.errno == errno.ESRCH:
+ # The owner has vanished, try to claim it in the next
+ # iteration through the loop.
+ try:
+ rmlink(self.name)
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ # Another process cleaned up the lock.
+ # Race them to acquire it in the next
+ # iteration through the loop.
+ continue
+ raise
+ clean = False
+ continue
+ raise
+ return False
+ raise
+ self.locked = True
+ self.clean = clean
+ return True
+
+
+ def unlock(self):
+ """
+ Release this lock.
+
+ This deletes the directory with the given name.
+
+ @raise: Any exception os.readlink() may raise, or
+ ValueError if the lock is not owned by this process.
+ """
+ pid = readlink(self.name)
+ if int(pid) != os.getpid():
+ raise ValueError("Lock %r not owned by this process" % (self.name,))
+ rmlink(self.name)
+ self.locked = False
+
+
+def isLocked(name):
+ """Determine if the lock of the given name is held or not.
+
+ @type name: C{str}
+ @param name: The filesystem path to the lock to test
+
+ @rtype: C{bool}
+ @return: True if the lock is held, False otherwise.
+ """
+ l = FilesystemLock(name)
+ result = None
+ try:
+ result = l.lock()
+ finally:
+ if result:
+ l.unlock()
+ return not result
+
+
+__all__ = ['FilesystemLock', 'isLocked']
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/log.py b/vendor/Twisted-10.0.0/twisted/python/log.py
new file mode 100644
index 0000000000..cb8d459acc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/log.py
@@ -0,0 +1,665 @@
+# -*- test-case-name: twisted.test.test_log -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Logging and metrics infrastructure.
+"""
+
+from __future__ import division
+
+import sys
+import time
+import warnings
+from datetime import datetime
+import logging
+
+from zope.interface import Interface
+
+from twisted.python import util, context, reflect
+
+
+
+class ILogContext:
+ """
+ Actually, this interface is just a synoym for the dictionary interface,
+ but it serves as a key for the default information in a log.
+
+ I do not inherit from Interface because the world is a cruel place.
+ """
+
+
+
+class ILogObserver(Interface):
+ """
+ An observer which can do something with log events.
+
+ Given that most log observers are actually bound methods, it's okay to not
+ explicitly declare provision of this interface.
+ """
+ def __call__(eventDict):
+ """
+ Log an event.
+
+ @type eventDict: C{dict} with C{str} keys.
+ @param eventDict: A dictionary with arbitrary keys. However, these
+ keys are often available:
+ - C{message}: A C{tuple} of C{str} containing messages to be
+ logged.
+ - C{system}: A C{str} which indicates the "system" which is
+ generating this event.
+ - C{isError}: A C{bool} indicating whether this event represents
+ an error.
+ - C{failure}: A L{failure.Failure} instance
+ - C{why}: Used as header of the traceback in case of errors.
+ - C{format}: A string format used in place of C{message} to
+ customize the event. The intent is for the observer to format
+ a message by doing something like C{format % eventDict}.
+ """
+
+
+
+context.setDefault(ILogContext,
+ {"isError": 0,
+ "system": "-"})
+
+def callWithContext(ctx, func, *args, **kw):
+ newCtx = context.get(ILogContext).copy()
+ newCtx.update(ctx)
+ return context.call({ILogContext: newCtx}, func, *args, **kw)
+
+def callWithLogger(logger, func, *args, **kw):
+ """
+ Utility method which wraps a function in a try:/except:, logs a failure if
+ one occurrs, and uses the system's logPrefix.
+ """
+ try:
+ lp = logger.logPrefix()
+ except KeyboardInterrupt:
+ raise
+ except:
+ lp = '(buggy logPrefix method)'
+ err(system=lp)
+ try:
+ return callWithContext({"system": lp}, func, *args, **kw)
+ except KeyboardInterrupt:
+ raise
+ except:
+ err(system=lp)
+
+
+
+_keepErrors = 0
+_keptErrors = []
+_ignoreErrors = []
+
+def startKeepingErrors():
+ """
+ DEPRECATED in Twisted 2.5.
+
+ Support function for testing frameworks.
+
+ Start keeping errors in a buffer which can be retrieved (and emptied) with
+ flushErrors.
+ """
+ warnings.warn("log.startKeepingErrors is deprecated since Twisted 2.5",
+ category=DeprecationWarning, stacklevel=2)
+ global _keepErrors
+ _keepErrors = 1
+
+
+def flushErrors(*errorTypes):
+ """
+ DEPRECATED in Twisted 2.5. See L{TestCase.flushLoggedErrors}.
+
+ Support function for testing frameworks.
+
+ Return a list of errors that occurred since the last call to flushErrors().
+ (This will return None unless startKeepingErrors has been called.)
+ """
+
+ warnings.warn("log.flushErrors is deprecated since Twisted 2.5. "
+ "If you need to flush errors from within a unittest, "
+ "use TestCase.flushLoggedErrors instead.",
+ category=DeprecationWarning, stacklevel=2)
+ return _flushErrors(*errorTypes)
+
+
+def _flushErrors(*errorTypes):
+ """
+ PRIVATE. DEPRECATED. DON'T USE.
+ """
+ global _keptErrors
+ k = _keptErrors
+ _keptErrors = []
+ if errorTypes:
+ for erk in k:
+ shouldReLog = 1
+ for errT in errorTypes:
+ if erk.check(errT):
+ shouldReLog = 0
+ if shouldReLog:
+ err(erk)
+ return k
+
+def ignoreErrors(*types):
+ """
+ DEPRECATED
+ """
+ warnings.warn("log.ignoreErrors is deprecated since Twisted 2.5",
+ category=DeprecationWarning, stacklevel=2)
+ _ignore(*types)
+
+def _ignore(*types):
+ """
+ PRIVATE. DEPRECATED. DON'T USE.
+ """
+ for type in types:
+ _ignoreErrors.append(type)
+
+def clearIgnores():
+ """
+ DEPRECATED
+ """
+ warnings.warn("log.clearIgnores is deprecated since Twisted 2.5",
+ category=DeprecationWarning, stacklevel=2)
+ _clearIgnores()
+
+def _clearIgnores():
+ """
+ PRIVATE. DEPRECATED. DON'T USE.
+ """
+ global _ignoreErrors
+ _ignoreErrors = []
+
+
+def err(_stuff=None, _why=None, **kw):
+ """
+ Write a failure to the log.
+
+ The C{_stuff} and C{_why} parameters use an underscore prefix to lessen
+ the chance of colliding with a keyword argument the application wishes
+ to pass. It is intended that they be supplied with arguments passed
+ positionally, not by keyword.
+
+ @param _stuff: The failure to log. If C{_stuff} is C{None} a new
+ L{Failure} will be created from the current exception state. If
+ C{_stuff} is an C{Exception} instance it will be wrapped in a
+ L{Failure}.
+ @type _stuff: C{NoneType}, C{Exception}, or L{Failure}.
+
+ @param _why: The source of this failure. This will be logged along with
+ C{_stuff} and should describe the context in which the failure
+ occurred.
+ @type _why: C{str}
+ """
+ if _stuff is None:
+ _stuff = failure.Failure()
+ if isinstance(_stuff, failure.Failure):
+ if _keepErrors:
+ if _ignoreErrors:
+ keep = 0
+ for err in _ignoreErrors:
+ r = _stuff.check(err)
+ if r:
+ keep = 0
+ break
+ else:
+ keep = 1
+ if keep:
+ _keptErrors.append(_stuff)
+ else:
+ _keptErrors.append(_stuff)
+ msg(failure=_stuff, why=_why, isError=1, **kw)
+ elif isinstance(_stuff, Exception):
+ msg(failure=failure.Failure(_stuff), why=_why, isError=1, **kw)
+ else:
+ msg(repr(_stuff), why=_why, isError=1, **kw)
+
+deferr = err
+
+
+class Logger:
+ """
+ This represents a class which may 'own' a log. Used by subclassing.
+ """
+ def logPrefix(self):
+ """
+ Override this method to insert custom logging behavior. Its
+ return value will be inserted in front of every line. It may
+ be called more times than the number of output lines.
+ """
+ return '-'
+
+
+class LogPublisher:
+ """
+ Class for singleton log message publishing.
+ """
+
+ synchronized = ['msg']
+
+ def __init__(self):
+ self.observers = []
+
+ def addObserver(self, other):
+ """
+ Add a new observer.
+
+ @type other: Provider of L{ILogObserver}
+ @param other: A callable object that will be called with each new log
+ message (a dict).
+ """
+ assert callable(other)
+ self.observers.append(other)
+
+ def removeObserver(self, other):
+ """
+ Remove an observer.
+ """
+ self.observers.remove(other)
+
+ def msg(self, *message, **kw):
+ """
+ Log a new message.
+
+ For example::
+
+ >>> log.msg('Hello, world.')
+
+ In particular, you MUST avoid the forms::
+
+ >>> log.msg(u'Hello, world.')
+ >>> log.msg('Hello ', 'world.')
+
+ These forms work (sometimes) by accident and will be disabled
+ entirely in the future.
+ """
+ actualEventDict = (context.get(ILogContext) or {}).copy()
+ actualEventDict.update(kw)
+ actualEventDict['message'] = message
+ actualEventDict['time'] = time.time()
+ for i in xrange(len(self.observers) - 1, -1, -1):
+ try:
+ self.observers[i](actualEventDict)
+ except KeyboardInterrupt:
+ # Don't swallow keyboard interrupt!
+ raise
+ except UnicodeEncodeError:
+ raise
+ except:
+ observer = self.observers[i]
+ self.observers[i] = lambda event: None
+ err(failure.Failure(),
+ "Log observer %s failed." % (observer,))
+ self.observers[i] = observer
+
+
+ def showwarning(self, message, category, filename, lineno, file=None,
+ line=None):
+ """
+ Twisted-enabled wrapper around L{warnings.showwarning}.
+
+ If C{file} is C{None}, the default behaviour is to emit the warning to
+ the log system, otherwise the original L{warnings.showwarning} Python
+ function is called.
+ """
+ if file is None:
+ self.msg(warning=message, category=reflect.qual(category),
+ filename=filename, lineno=lineno,
+ format="%(filename)s:%(lineno)s: %(category)s: %(warning)s")
+ else:
+ if sys.version_info < (2, 6):
+ _oldshowwarning(message, category, filename, lineno, file)
+ else:
+ _oldshowwarning(message, category, filename, lineno, file, line)
+
+
+
+
+try:
+ theLogPublisher
+except NameError:
+ theLogPublisher = LogPublisher()
+ addObserver = theLogPublisher.addObserver
+ removeObserver = theLogPublisher.removeObserver
+ msg = theLogPublisher.msg
+ showwarning = theLogPublisher.showwarning
+
+
+def _safeFormat(fmtString, fmtDict):
+ """
+ Try to format the string C{fmtString} using C{fmtDict} arguments,
+ swallowing all errors to always return a string.
+ """
+ # There's a way we could make this if not safer at least more
+ # informative: perhaps some sort of str/repr wrapper objects
+ # could be wrapped around the things inside of C{fmtDict}. That way
+ # if the event dict contains an object with a bad __repr__, we
+ # can only cry about that individual object instead of the
+ # entire event dict.
+ try:
+ text = fmtString % fmtDict
+ except KeyboardInterrupt:
+ raise
+ except:
+ try:
+ text = ('Invalid format string or unformattable object in log message: %r, %s' % (fmtString, fmtDict))
+ except:
+ try:
+ text = 'UNFORMATTABLE OBJECT WRITTEN TO LOG with fmt %r, MESSAGE LOST' % (fmtString,)
+ except:
+ text = 'PATHOLOGICAL ERROR IN BOTH FORMAT STRING AND MESSAGE DETAILS, MESSAGE LOST'
+ return text
+
+
+def textFromEventDict(eventDict):
+ """
+ Extract text from an event dict passed to a log observer. If it cannot
+ handle the dict, it returns None.
+
+ The possible keys of eventDict are:
+ - C{message}: by default, it holds the final text. It's required, but can
+ be empty if either C{isError} or C{format} is provided (the first
+ having the priority).
+ - C{isError}: boolean indicating the nature of the event.
+ - C{failure}: L{failure.Failure} instance, required if the event is an
+ error.
+ - C{why}: if defined, used as header of the traceback in case of errors.
+ - C{format}: string format used in place of C{message} to customize
+ the event. It uses all keys present in C{eventDict} to format
+ the text.
+ Other keys will be used when applying the C{format}, or ignored.
+ """
+ edm = eventDict['message']
+ if not edm:
+ if eventDict['isError'] and 'failure' in eventDict:
+ text = ((eventDict.get('why') or 'Unhandled Error')
+ + '\n' + eventDict['failure'].getTraceback())
+ elif 'format' in eventDict:
+ text = _safeFormat(eventDict['format'], eventDict)
+ else:
+ # we don't know how to log this
+ return
+ else:
+ text = ' '.join(map(reflect.safe_str, edm))
+ return text
+
+
+class FileLogObserver:
+ """
+ Log observer that writes to a file-like object.
+
+ @type timeFormat: C{str} or C{NoneType}
+ @ivar timeFormat: If not C{None}, the format string passed to strftime().
+ """
+ timeFormat = None
+
+ def __init__(self, f):
+ self.write = f.write
+ self.flush = f.flush
+
+ def getTimezoneOffset(self, when):
+ """
+ Return the current local timezone offset from UTC.
+
+ @type when: C{int}
+ @param when: POSIX (ie, UTC) timestamp for which to find the offset.
+
+ @rtype: C{int}
+ @return: The number of seconds offset from UTC. West is positive,
+ east is negative.
+ """
+ offset = datetime.utcfromtimestamp(when) - datetime.fromtimestamp(when)
+ return offset.days * (60 * 60 * 24) + offset.seconds
+
+ def formatTime(self, when):
+ """
+ Format the given UTC value as a string representing that time in the
+ local timezone.
+
+ By default it's formatted as a ISO8601-like string (ISO8601 date and
+ ISO8601 time separated by a space). It can be customized using the
+ C{timeFormat} attribute, which will be used as input for the underlying
+ C{time.strftime} call.
+
+ @type when: C{int}
+ @param when: POSIX (ie, UTC) timestamp for which to find the offset.
+
+ @rtype: C{str}
+ """
+ if self.timeFormat is not None:
+ return time.strftime(self.timeFormat, time.localtime(when))
+
+ tzOffset = -self.getTimezoneOffset(when)
+ when = datetime.utcfromtimestamp(when + tzOffset)
+ tzHour = abs(int(tzOffset / 60 / 60))
+ tzMin = abs(int(tzOffset / 60 % 60))
+ if tzOffset < 0:
+ tzSign = '-'
+ else:
+ tzSign = '+'
+ return '%d-%02d-%02d %02d:%02d:%02d%s%02d%02d' % (
+ when.year, when.month, when.day,
+ when.hour, when.minute, when.second,
+ tzSign, tzHour, tzMin)
+
+ def emit(self, eventDict):
+ text = textFromEventDict(eventDict)
+ if text is None:
+ return
+
+ timeStr = self.formatTime(eventDict['time'])
+ fmtDict = {'system': eventDict['system'], 'text': text.replace("\n", "\n\t")}
+ msgStr = _safeFormat("[%(system)s] %(text)s\n", fmtDict)
+
+ util.untilConcludes(self.write, timeStr + " " + msgStr)
+ util.untilConcludes(self.flush) # Hoorj!
+
+ def start(self):
+ """
+ Start observing log events.
+ """
+ addObserver(self.emit)
+
+ def stop(self):
+ """
+ Stop observing log events.
+ """
+ removeObserver(self.emit)
+
+
+class PythonLoggingObserver(object):
+ """
+ Output twisted messages to Python standard library L{logging} module.
+
+ WARNING: specific logging configurations (example: network) can lead to
+ a blocking system. Nothing is done here to prevent that, so be sure to not
+ use this: code within Twisted, such as twisted.web, assumes that logging
+ does not block.
+ """
+
+ def __init__(self, loggerName="twisted"):
+ """
+ @param loggerName: identifier used for getting logger.
+ @type loggerName: C{str}
+ """
+ self.logger = logging.getLogger(loggerName)
+
+ def emit(self, eventDict):
+ """
+ Receive a twisted log entry, format it and bridge it to python.
+
+ By default the logging level used is info; log.err produces error
+ level, and you can customize the level by using the C{logLevel} key::
+
+ >>> log.msg('debugging', logLevel=logging.DEBUG)
+
+ """
+ if 'logLevel' in eventDict:
+ level = eventDict['logLevel']
+ elif eventDict['isError']:
+ level = logging.ERROR
+ else:
+ level = logging.INFO
+ text = textFromEventDict(eventDict)
+ if text is None:
+ return
+ self.logger.log(level, text)
+
+ def start(self):
+ """
+ Start observing log events.
+ """
+ addObserver(self.emit)
+
+ def stop(self):
+ """
+ Stop observing log events.
+ """
+ removeObserver(self.emit)
+
+
+class StdioOnnaStick:
+ """
+ Class that pretends to be stout/err.
+ """
+
+ closed = 0
+ softspace = 0
+ mode = 'wb'
+ name = '<stdio (log)>'
+
+ def __init__(self, isError=0):
+ self.isError = isError
+ self.buf = ''
+
+ def close(self):
+ pass
+
+ def fileno(self):
+ return -1
+
+ def flush(self):
+ pass
+
+ def read(self):
+ raise IOError("can't read from the log!")
+
+ readline = read
+ readlines = read
+ seek = read
+ tell = read
+
+ def write(self, data):
+ d = (self.buf + data).split('\n')
+ self.buf = d[-1]
+ messages = d[0:-1]
+ for message in messages:
+ msg(message, printed=1, isError=self.isError)
+
+ def writelines(self, lines):
+ for line in lines:
+ msg(line, printed=1, isError=self.isError)
+
+
+try:
+ _oldshowwarning
+except NameError:
+ _oldshowwarning = None
+
+
+def startLogging(file, *a, **kw):
+ """
+ Initialize logging to a specified file.
+
+ @return: A L{FileLogObserver} if a new observer is added, None otherwise.
+ """
+ if isinstance(file, StdioOnnaStick):
+ return
+ flo = FileLogObserver(file)
+ startLoggingWithObserver(flo.emit, *a, **kw)
+ return flo
+
+
+
+def startLoggingWithObserver(observer, setStdout=1):
+ """
+ Initialize logging to a specified observer. If setStdout is true
+ (defaults to yes), also redirect sys.stdout and sys.stderr
+ to the specified file.
+ """
+ global defaultObserver, _oldshowwarning
+ if not _oldshowwarning:
+ _oldshowwarning = warnings.showwarning
+ warnings.showwarning = showwarning
+ if defaultObserver:
+ defaultObserver.stop()
+ defaultObserver = None
+ addObserver(observer)
+ msg("Log opened.")
+ if setStdout:
+ sys.stdout = logfile
+ sys.stderr = logerr
+
+
+class NullFile:
+ softspace = 0
+ def read(self): pass
+ def write(self, bytes): pass
+ def flush(self): pass
+ def close(self): pass
+
+
+def discardLogs():
+ """
+ Throw away all logs.
+ """
+ global logfile
+ logfile = NullFile()
+
+
+# Prevent logfile from being erased on reload. This only works in cpython.
+try:
+ logfile
+except NameError:
+ logfile = StdioOnnaStick(0)
+ logerr = StdioOnnaStick(1)
+
+
+class DefaultObserver:
+ """
+ Default observer.
+
+ Will ignore all non-error messages and send error messages to sys.stderr.
+ Will be removed when startLogging() is called for the first time.
+ """
+
+ def _emit(self, eventDict):
+ if eventDict["isError"]:
+ if 'failure' in eventDict:
+ text = eventDict['failure'].getTraceback()
+ else:
+ text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
+ sys.stderr.write(text)
+ sys.stderr.flush()
+
+ def start(self):
+ addObserver(self._emit)
+
+ def stop(self):
+ removeObserver(self._emit)
+
+
+# Some more sibling imports, at the bottom and unqualified to avoid
+# unresolvable circularity
+import threadable, failure
+threadable.synchronize(LogPublisher)
+
+
+try:
+ defaultObserver
+except NameError:
+ defaultObserver = DefaultObserver()
+ defaultObserver.start()
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/logfile.py b/vendor/Twisted-10.0.0/twisted/python/logfile.py
new file mode 100644
index 0000000000..bd55e51623
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/logfile.py
@@ -0,0 +1,324 @@
+# -*- test-case-name: twisted.test.test_logfile -*-
+
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A rotating, browsable log file.
+"""
+
+# System Imports
+import os, glob, time, stat
+
+from twisted.python import threadable
+
+
+
+class BaseLogFile:
+ """
+ The base class for a log file that can be rotated.
+ """
+
+ synchronized = ["write", "rotate"]
+
+ def __init__(self, name, directory, defaultMode=None):
+ """
+ Create a log file.
+
+ @param name: name of the file
+ @param directory: directory holding the file
+ @param defaultMode: permissions used to create the file. Default to
+ current permissions of the file if the file exists.
+ """
+ self.directory = directory
+ assert os.path.isdir(self.directory)
+ self.name = name
+ self.path = os.path.join(directory, name)
+ if defaultMode is None and os.path.exists(self.path):
+ self.defaultMode = stat.S_IMODE(os.stat(self.path)[stat.ST_MODE])
+ else:
+ self.defaultMode = defaultMode
+ self._openFile()
+
+ def fromFullPath(cls, filename, *args, **kwargs):
+ """
+ Construct a log file from a full file path.
+ """
+ logPath = os.path.abspath(filename)
+ return cls(os.path.basename(logPath),
+ os.path.dirname(logPath), *args, **kwargs)
+ fromFullPath = classmethod(fromFullPath)
+
+ def shouldRotate(self):
+ """
+ Override with a method to that returns true if the log
+ should be rotated.
+ """
+ raise NotImplementedError
+
+ def _openFile(self):
+ """
+ Open the log file.
+ """
+ self.closed = False
+ if os.path.exists(self.path):
+ self._file = file(self.path, "r+", 1)
+ self._file.seek(0, 2)
+ else:
+ if self.defaultMode is not None:
+ # Set the lowest permissions
+ oldUmask = os.umask(0777)
+ try:
+ self._file = file(self.path, "w+", 1)
+ finally:
+ os.umask(oldUmask)
+ else:
+ self._file = file(self.path, "w+", 1)
+ if self.defaultMode is not None:
+ try:
+ os.chmod(self.path, self.defaultMode)
+ except OSError:
+ # Probably /dev/null or something?
+ pass
+
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ del state["_file"]
+ return state
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+ self._openFile()
+
+ def write(self, data):
+ """
+ Write some data to the file.
+ """
+ if self.shouldRotate():
+ self.flush()
+ self.rotate()
+ self._file.write(data)
+
+ def flush(self):
+ """
+ Flush the file.
+ """
+ self._file.flush()
+
+ def close(self):
+ """
+ Close the file.
+
+ The file cannot be used once it has been closed.
+ """
+ self.closed = True
+ self._file.close()
+ self._file = None
+
+
+ def reopen(self):
+ """
+ Reopen the log file. This is mainly useful if you use an external log
+ rotation tool, which moves under your feet.
+
+ Note that on Windows you probably need a specific API to rename the
+ file, as it's not supported to simply use os.rename, for example.
+ """
+ self.close()
+ self._openFile()
+
+
+ def getCurrentLog(self):
+ """
+ Return a LogReader for the current log file.
+ """
+ return LogReader(self.path)
+
+
+class LogFile(BaseLogFile):
+ """
+ A log file that can be rotated.
+
+ A rotateLength of None disables automatic log rotation.
+ """
+ def __init__(self, name, directory, rotateLength=1000000, defaultMode=None,
+ maxRotatedFiles=None):
+ """
+ Create a log file rotating on length.
+
+ @param name: file name.
+ @type name: C{str}
+ @param directory: path of the log file.
+ @type directory: C{str}
+ @param rotateLength: size of the log file where it rotates. Default to
+ 1M.
+ @type rotateLength: C{int}
+ @param defaultMode: mode used to create the file.
+ @type defaultMode: C{int}
+ @param maxRotatedFiles: if not None, max number of log files the class
+ creates. Warning: it removes all log files above this number.
+ @type maxRotatedFiles: C{int}
+ """
+ BaseLogFile.__init__(self, name, directory, defaultMode)
+ self.rotateLength = rotateLength
+ self.maxRotatedFiles = maxRotatedFiles
+
+ def _openFile(self):
+ BaseLogFile._openFile(self)
+ self.size = self._file.tell()
+
+ def shouldRotate(self):
+ """
+ Rotate when the log file size is larger than rotateLength.
+ """
+ return self.rotateLength and self.size >= self.rotateLength
+
+ def getLog(self, identifier):
+ """
+ Given an integer, return a LogReader for an old log file.
+ """
+ filename = "%s.%d" % (self.path, identifier)
+ if not os.path.exists(filename):
+ raise ValueError, "no such logfile exists"
+ return LogReader(filename)
+
+ def write(self, data):
+ """
+ Write some data to the file.
+ """
+ BaseLogFile.write(self, data)
+ self.size += len(data)
+
+ def rotate(self):
+ """
+ Rotate the file and create a new one.
+
+ If it's not possible to open new logfile, this will fail silently,
+ and continue logging to old logfile.
+ """
+ if not (os.access(self.directory, os.W_OK) and os.access(self.path, os.W_OK)):
+ return
+ logs = self.listLogs()
+ logs.reverse()
+ for i in logs:
+ if self.maxRotatedFiles is not None and i >= self.maxRotatedFiles:
+ os.remove("%s.%d" % (self.path, i))
+ else:
+ os.rename("%s.%d" % (self.path, i), "%s.%d" % (self.path, i + 1))
+ self._file.close()
+ os.rename(self.path, "%s.1" % self.path)
+ self._openFile()
+
+ def listLogs(self):
+ """
+ Return sorted list of integers - the old logs' identifiers.
+ """
+ result = []
+ for name in glob.glob("%s.*" % self.path):
+ try:
+ counter = int(name.split('.')[-1])
+ if counter:
+ result.append(counter)
+ except ValueError:
+ pass
+ result.sort()
+ return result
+
+ def __getstate__(self):
+ state = BaseLogFile.__getstate__(self)
+ del state["size"]
+ return state
+
+threadable.synchronize(LogFile)
+
+
+class DailyLogFile(BaseLogFile):
+ """A log file that is rotated daily (at or after midnight localtime)
+ """
+ def _openFile(self):
+ BaseLogFile._openFile(self)
+ self.lastDate = self.toDate(os.stat(self.path)[8])
+
+ def shouldRotate(self):
+ """Rotate when the date has changed since last write"""
+ return self.toDate() > self.lastDate
+
+ def toDate(self, *args):
+ """Convert a unixtime to (year, month, day) localtime tuple,
+ or return the current (year, month, day) localtime tuple.
+
+ This function primarily exists so you may overload it with
+ gmtime, or some cruft to make unit testing possible.
+ """
+ # primarily so this can be unit tested easily
+ return time.localtime(*args)[:3]
+
+ def suffix(self, tupledate):
+ """Return the suffix given a (year, month, day) tuple or unixtime"""
+ try:
+ return '_'.join(map(str, tupledate))
+ except:
+ # try taking a float unixtime
+ return '_'.join(map(str, self.toDate(tupledate)))
+
+ def getLog(self, identifier):
+ """Given a unix time, return a LogReader for an old log file."""
+ if self.toDate(identifier) == self.lastDate:
+ return self.getCurrentLog()
+ filename = "%s.%s" % (self.path, self.suffix(identifier))
+ if not os.path.exists(filename):
+ raise ValueError, "no such logfile exists"
+ return LogReader(filename)
+
+ def write(self, data):
+ """Write some data to the log file"""
+ BaseLogFile.write(self, data)
+ # Guard against a corner case where time.time()
+ # could potentially run backwards to yesterday.
+ # Primarily due to network time.
+ self.lastDate = max(self.lastDate, self.toDate())
+
+ def rotate(self):
+ """Rotate the file and create a new one.
+
+ If it's not possible to open new logfile, this will fail silently,
+ and continue logging to old logfile.
+ """
+ if not (os.access(self.directory, os.W_OK) and os.access(self.path, os.W_OK)):
+ return
+ newpath = "%s.%s" % (self.path, self.suffix(self.lastDate))
+ if os.path.exists(newpath):
+ return
+ self._file.close()
+ os.rename(self.path, newpath)
+ self._openFile()
+
+ def __getstate__(self):
+ state = BaseLogFile.__getstate__(self)
+ del state["lastDate"]
+ return state
+
+threadable.synchronize(DailyLogFile)
+
+
+class LogReader:
+ """Read from a log file."""
+
+ def __init__(self, name):
+ self._file = file(name, "r")
+
+ def readLines(self, lines=10):
+ """Read a list of lines from the log file.
+
+ This doesn't returns all of the files lines - call it multiple times.
+ """
+ result = []
+ for i in range(lines):
+ line = self._file.readline()
+ if not line:
+ break
+ result.append(line)
+ return result
+
+ def close(self):
+ self._file.close()
diff --git a/vendor/Twisted-10.0.0/twisted/python/modules.py b/vendor/Twisted-10.0.0/twisted/python/modules.py
new file mode 100644
index 0000000000..2d32bba70f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/modules.py
@@ -0,0 +1,747 @@
+# -*- test-case-name: twisted.test.test_modules -*-
+# Copyright (c) 2006-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module aims to provide a unified, object-oriented view of Python's
+runtime hierarchy.
+
+Python is a very dynamic language with wide variety of introspection utilities.
+However, these utilities can be hard to use, because there is no consistent
+API. The introspection API in python is made up of attributes (__name__,
+__module__, func_name, etc) on instances, modules, classes and functions which
+vary between those four types, utility modules such as 'inspect' which provide
+some functionality, the 'imp' module, the "compiler" module, the semantics of
+PEP 302 support, and setuptools, among other things.
+
+At the top, you have "PythonPath", an abstract representation of sys.path which
+includes methods to locate top-level modules, with or without loading them.
+The top-level exposed functions in this module for accessing the system path
+are "walkModules", "iterModules", and "getModule".
+
+From most to least specific, here are the objects provided::
+
+ PythonPath # sys.path
+ |
+ v
+ PathEntry # one entry on sys.path: an importer
+ |
+ v
+ PythonModule # a module or package that can be loaded
+ |
+ v
+ PythonAttribute # an attribute of a module (function or class)
+ |
+ v
+ PythonAttribute # an attribute of a function or class
+ |
+ v
+ ...
+
+Here's an example of idiomatic usage: this is what you would do to list all of
+the modules outside the standard library's python-files directory::
+
+ import os
+ stdlibdir = os.path.dirname(os.__file__)
+
+ from twisted.python.modules import iterModules
+
+ for modinfo in iterModules():
+ if (modinfo.pathEntry.filePath.path != stdlibdir
+ and not modinfo.isPackage()):
+ print 'unpackaged: %s: %s' % (
+ modinfo.name, modinfo.filePath.path)
+"""
+
+__metaclass__ = type
+
+# let's try to keep path imports to a minimum...
+from os.path import dirname, split as splitpath
+
+import sys
+import zipimport
+import inspect
+import warnings
+from zope.interface import Interface, implements
+
+from twisted.python.components import registerAdapter
+from twisted.python.filepath import FilePath, UnlistableError
+from twisted.python.zippath import ZipArchive
+from twisted.python.reflect import namedAny
+
+_nothing = object()
+
+PYTHON_EXTENSIONS = ['.py']
+OPTIMIZED_MODE = __doc__ is None
+if OPTIMIZED_MODE:
+ PYTHON_EXTENSIONS.append('.pyo')
+else:
+ PYTHON_EXTENSIONS.append('.pyc')
+
+def _isPythonIdentifier(string):
+ """
+ cheezy fake test for proper identifier-ness.
+
+ @param string: a str which might or might not be a valid python identifier.
+
+ @return: True or False
+ """
+ return (' ' not in string and
+ '.' not in string and
+ '-' not in string)
+
+
+
+def _isPackagePath(fpath):
+ # Determine if a FilePath-like object is a Python package. TODO: deal with
+ # __init__module.(so|dll|pyd)?
+ extless = fpath.splitext()[0]
+ basend = splitpath(extless)[1]
+ return basend == "__init__"
+
+
+
+class _ModuleIteratorHelper:
+ """
+ This mixin provides common behavior between python module and path entries,
+ since the mechanism for searching sys.path and __path__ attributes is
+ remarkably similar.
+ """
+
+ def iterModules(self):
+ """
+ Loop over the modules present below this entry or package on PYTHONPATH.
+
+ For modules which are not packages, this will yield nothing.
+
+ For packages and path entries, this will only yield modules one level
+ down; i.e. if there is a package a.b.c, iterModules on a will only
+ return a.b. If you want to descend deeply, use walkModules.
+
+ @return: a generator which yields PythonModule instances that describe
+ modules which can be, or have been, imported.
+ """
+ yielded = {}
+ if not self.filePath.exists():
+ return
+
+ for placeToLook in self._packagePaths():
+ try:
+ children = placeToLook.children()
+ except UnlistableError:
+ continue
+
+ children.sort()
+ for potentialTopLevel in children:
+ ext = potentialTopLevel.splitext()[1]
+ potentialBasename = potentialTopLevel.basename()[:-len(ext)]
+ if ext in PYTHON_EXTENSIONS:
+ # TODO: this should be a little choosier about which path entry
+ # it selects first, and it should do all the .so checking and
+ # crud
+ if not _isPythonIdentifier(potentialBasename):
+ continue
+ modname = self._subModuleName(potentialBasename)
+ if modname.split(".")[-1] == '__init__':
+ # This marks the directory as a package so it can't be
+ # a module.
+ continue
+ if modname not in yielded:
+ yielded[modname] = True
+ pm = PythonModule(modname, potentialTopLevel, self._getEntry())
+ assert pm != self
+ yield pm
+ else:
+ if (ext or not _isPythonIdentifier(potentialBasename)
+ or not potentialTopLevel.isdir()):
+ continue
+ modname = self._subModuleName(potentialTopLevel.basename())
+ for ext in PYTHON_EXTENSIONS:
+ initpy = potentialTopLevel.child("__init__"+ext)
+ if initpy.exists():
+ yielded[modname] = True
+ pm = PythonModule(modname, initpy, self._getEntry())
+ assert pm != self
+ yield pm
+ break
+
+ def walkModules(self, importPackages=False):
+ """
+ Similar to L{iterModules}, this yields self, and then every module in my
+ package or entry, and every submodule in each package or entry.
+
+ In other words, this is deep, and L{iterModules} is shallow.
+ """
+ yield self
+ for package in self.iterModules():
+ for module in package.walkModules(importPackages=importPackages):
+ yield module
+
+ def _subModuleName(self, mn):
+ """
+ This is a hook to provide packages with the ability to specify their names
+ as a prefix to submodules here.
+ """
+ return mn
+
+ def _packagePaths(self):
+ """
+ Implement in subclasses to specify where to look for modules.
+
+ @return: iterable of FilePath-like objects.
+ """
+ raise NotImplementedError()
+
+ def _getEntry(self):
+ """
+ Implement in subclasses to specify what path entry submodules will come
+ from.
+
+ @return: a PathEntry instance.
+ """
+ raise NotImplementedError()
+
+
+ def __getitem__(self, modname):
+ """
+ Retrieve a module from below this path or package.
+
+ @param modname: a str naming a module to be loaded. For entries, this
+ is a top-level, undotted package name, and for packages it is the name
+ of the module without the package prefix. For example, if you have a
+ PythonModule representing the 'twisted' package, you could use::
+
+ twistedPackageObj['python']['modules']
+
+ to retrieve this module.
+
+ @raise: KeyError if the module is not found.
+
+ @return: a PythonModule.
+ """
+ for module in self.iterModules():
+ if module.name == self._subModuleName(modname):
+ return module
+ raise KeyError(modname)
+
+ def __iter__(self):
+ """
+ Implemented to raise NotImplementedError for clarity, so that attempting to
+ loop over this object won't call __getitem__.
+
+ Note: in the future there might be some sensible default for iteration,
+ like 'walkEverything', so this is deliberately untested and undefined
+ behavior.
+ """
+ raise NotImplementedError()
+
+class PythonAttribute:
+ """
+ I represent a function, class, or other object that is present.
+
+ @ivar name: the fully-qualified python name of this attribute.
+
+ @ivar onObject: a reference to a PythonModule or other PythonAttribute that
+ is this attribute's logical parent.
+
+ @ivar name: the fully qualified python name of the attribute represented by
+ this class.
+ """
+ def __init__(self, name, onObject, loaded, pythonValue):
+ """
+ Create a PythonAttribute. This is a private constructor. Do not construct
+ me directly, use PythonModule.iterAttributes.
+
+ @param name: the FQPN
+ @param onObject: see ivar
+ @param loaded: always True, for now
+ @param pythonValue: the value of the attribute we're pointing to.
+ """
+ self.name = name
+ self.onObject = onObject
+ self._loaded = loaded
+ self.pythonValue = pythonValue
+
+ def __repr__(self):
+ return 'PythonAttribute<%r>'%(self.name,)
+
+ def isLoaded(self):
+ """
+ Return a boolean describing whether the attribute this describes has
+ actually been loaded into memory by importing its module.
+
+ Note: this currently always returns true; there is no Python parser
+ support in this module yet.
+ """
+ return self._loaded
+
+ def load(self, default=_nothing):
+ """
+ Load the value associated with this attribute.
+
+ @return: an arbitrary Python object, or 'default' if there is an error
+ loading it.
+ """
+ return self.pythonValue
+
+ def iterAttributes(self):
+ for name, val in inspect.getmembers(self.load()):
+ yield PythonAttribute(self.name+'.'+name, self, True, val)
+
+class PythonModule(_ModuleIteratorHelper):
+ """
+ Representation of a module which could be imported from sys.path.
+
+ @ivar name: the fully qualified python name of this module.
+
+ @ivar filePath: a FilePath-like object which points to the location of this
+ module.
+
+ @ivar pathEntry: a L{PathEntry} instance which this module was located
+ from.
+ """
+
+ def __init__(self, name, filePath, pathEntry):
+ """
+ Create a PythonModule. Do not construct this directly, instead inspect a
+ PythonPath or other PythonModule instances.
+
+ @param name: see ivar
+ @param filePath: see ivar
+ @param pathEntry: see ivar
+ """
+ assert not name.endswith(".__init__")
+ self.name = name
+ self.filePath = filePath
+ self.parentPath = filePath.parent()
+ self.pathEntry = pathEntry
+
+ def _getEntry(self):
+ return self.pathEntry
+
+ def __repr__(self):
+ """
+ Return a string representation including the module name.
+ """
+ return 'PythonModule<%r>' % (self.name,)
+
+ def isLoaded(self):
+ """
+ Determine if the module is loaded into sys.modules.
+
+ @return: a boolean: true if loaded, false if not.
+ """
+ return self.name in self.pathEntry.pythonPath.moduleDict
+
+ def iterAttributes(self):
+ """
+ List all the attributes defined in this module.
+
+ Note: Future work is planned here to make it possible to list python
+ attributes on a module without loading the module by inspecting ASTs or
+ bytecode, but currently any iteration of PythonModule objects insists
+ they must be loaded, and will use inspect.getmodule.
+
+ @raise NotImplementedError: if this module is not loaded.
+
+ @return: a generator yielding PythonAttribute instances describing the
+ attributes of this module.
+ """
+ if not self.isLoaded():
+ raise NotImplementedError(
+ "You can't load attributes from non-loaded modules yet.")
+ for name, val in inspect.getmembers(self.load()):
+ yield PythonAttribute(self.name+'.'+name, self, True, val)
+
+ def isPackage(self):
+ """
+ Returns true if this module is also a package, and might yield something
+ from iterModules.
+ """
+ return _isPackagePath(self.filePath)
+
+ def load(self, default=_nothing):
+ """
+ Load this module.
+
+ @param default: if specified, the value to return in case of an error.
+
+ @return: a genuine python module.
+
+ @raise: any type of exception. Importing modules is a risky business;
+ the erorrs of any code run at module scope may be raised from here, as
+ well as ImportError if something bizarre happened to the system path
+ between the discovery of this PythonModule object and the attempt to
+ import it. If you specify a default, the error will be swallowed
+ entirely, and not logged.
+
+ @rtype: types.ModuleType.
+ """
+ try:
+ return self.pathEntry.pythonPath.moduleLoader(self.name)
+ except: # this needs more thought...
+ if default is not _nothing:
+ return default
+ raise
+
+ def __eq__(self, other):
+ """
+ PythonModules with the same name are equal.
+ """
+ if not isinstance(other, PythonModule):
+ return False
+ return other.name == self.name
+
+ def __ne__(self, other):
+ """
+ PythonModules with different names are not equal.
+ """
+ if not isinstance(other, PythonModule):
+ return True
+ return other.name != self.name
+
+ def walkModules(self, importPackages=False):
+ if importPackages and self.isPackage():
+ self.load()
+ return super(PythonModule, self).walkModules(importPackages=importPackages)
+
+ def _subModuleName(self, mn):
+ """
+ submodules of this module are prefixed with our name.
+ """
+ return self.name + '.' + mn
+
+ def _packagePaths(self):
+ """
+ Yield a sequence of FilePath-like objects which represent path segments.
+ """
+ if not self.isPackage():
+ return
+ if self.isLoaded():
+ load = self.load()
+ if hasattr(load, '__path__'):
+ for fn in load.__path__:
+ if fn == self.parentPath.path:
+ # this should _really_ exist.
+ assert self.parentPath.exists()
+ yield self.parentPath
+ else:
+ smp = self.pathEntry.pythonPath._smartPath(fn)
+ if smp.exists():
+ yield smp
+ else:
+ yield self.parentPath
+
+
+class PathEntry(_ModuleIteratorHelper):
+ """
+ I am a proxy for a single entry on sys.path.
+
+ @ivar filePath: a FilePath-like object pointing at the filesystem location
+ or archive file where this path entry is stored.
+
+ @ivar pythonPath: a PythonPath instance.
+ """
+ def __init__(self, filePath, pythonPath):
+ """
+ Create a PathEntry. This is a private constructor.
+ """
+ self.filePath = filePath
+ self.pythonPath = pythonPath
+
+ def _getEntry(self):
+ return self
+
+ def __repr__(self):
+ return 'PathEntry<%r>' % (self.filePath,)
+
+ def _packagePaths(self):
+ yield self.filePath
+
+class IPathImportMapper(Interface):
+ """
+ This is an internal interface, used to map importers to factories for
+ FilePath-like objects.
+ """
+ def mapPath(self, pathLikeString):
+ """
+ Return a FilePath-like object.
+
+ @param pathLikeString: a path-like string, like one that might be
+ passed to an import hook.
+
+ @return: a L{FilePath}, or something like it (currently only a
+ L{ZipPath}, but more might be added later).
+ """
+
+class _DefaultMapImpl:
+ """ Wrapper for the default importer, i.e. None. """
+ implements(IPathImportMapper)
+ def mapPath(self, fsPathString):
+ return FilePath(fsPathString)
+_theDefaultMapper = _DefaultMapImpl()
+
+class _ZipMapImpl:
+ """ IPathImportMapper implementation for zipimport.ZipImporter. """
+ implements(IPathImportMapper)
+ def __init__(self, importer):
+ self.importer = importer
+
+ def mapPath(self, fsPathString):
+ """
+ Map the given FS path to a ZipPath, by looking at the ZipImporter's
+ "archive" attribute and using it as our ZipArchive root, then walking
+ down into the archive from there.
+
+ @return: a L{zippath.ZipPath} or L{zippath.ZipArchive} instance.
+ """
+ za = ZipArchive(self.importer.archive)
+ myPath = FilePath(self.importer.archive)
+ itsPath = FilePath(fsPathString)
+ if myPath == itsPath:
+ return za
+ # This is NOT a general-purpose rule for sys.path or __file__:
+ # zipimport specifically uses regular OS path syntax in its pathnames,
+ # even though zip files specify that slashes are always the separator,
+ # regardless of platform.
+ segs = itsPath.segmentsFrom(myPath)
+ zp = za
+ for seg in segs:
+ zp = zp.child(seg)
+ return zp
+
+registerAdapter(_ZipMapImpl, zipimport.zipimporter, IPathImportMapper)
+
+def _defaultSysPathFactory():
+ """
+ Provide the default behavior of PythonPath's sys.path factory, which is to
+ return the current value of sys.path.
+
+ @return: L{sys.path}
+ """
+ return sys.path
+
+
+class PythonPath:
+ """
+ I represent the very top of the Python object-space, the module list in
+ sys.path and the modules list in sys.modules.
+
+ @ivar _sysPath: a sequence of strings like sys.path. This attribute is
+ read-only.
+
+ @ivar moduleDict: a dictionary mapping string module names to module
+ objects, like sys.modules.
+
+ @ivar sysPathHooks: a list of PEP-302 path hooks, like sys.path_hooks.
+
+ @ivar moduleLoader: a function that takes a fully-qualified python name and
+ returns a module, like twisted.python.reflect.namedAny.
+ """
+
+ def __init__(self,
+ sysPath=None,
+ moduleDict=sys.modules,
+ sysPathHooks=sys.path_hooks,
+ importerCache=sys.path_importer_cache,
+ moduleLoader=namedAny,
+ sysPathFactory=None):
+ """
+ Create a PythonPath. You almost certainly want to use
+ modules.theSystemPath, or its aliased methods, rather than creating a
+ new instance yourself, though.
+
+ All parameters are optional, and if unspecified, will use 'system'
+ equivalents that makes this PythonPath like the global L{theSystemPath}
+ instance.
+
+ @param sysPath: a sys.path-like list to use for this PythonPath, to
+ specify where to load modules from.
+
+ @param moduleDict: a sys.modules-like dictionary to use for keeping
+ track of what modules this PythonPath has loaded.
+
+ @param sysPathHooks: sys.path_hooks-like list of PEP-302 path hooks to
+ be used for this PythonPath, to determie which importers should be
+ used.
+
+ @param importerCache: a sys.path_importer_cache-like list of PEP-302
+ importers. This will be used in conjunction with the given
+ sysPathHooks.
+
+ @param moduleLoader: a module loader function which takes a string and
+ returns a module. That is to say, it is like L{namedAny} - *not* like
+ L{__import__}.
+
+ @param sysPathFactory: a 0-argument callable which returns the current
+ value of a sys.path-like list of strings. Specify either this, or
+ sysPath, not both. This alternative interface is provided because the
+ way the Python import mechanism works, you can re-bind the 'sys.path'
+ name and that is what is used for current imports, so it must be a
+ factory rather than a value to deal with modification by rebinding
+ rather than modification by mutation. Note: it is not recommended to
+ rebind sys.path. Although this mechanism can deal with that, it is a
+ subtle point which some tools that it is easy for tools which interact
+ with sys.path to miss.
+ """
+ if sysPath is not None:
+ sysPathFactory = lambda : sysPath
+ elif sysPathFactory is None:
+ sysPathFactory = _defaultSysPathFactory
+ self._sysPathFactory = sysPathFactory
+ self._sysPath = sysPath
+ self.moduleDict = moduleDict
+ self.sysPathHooks = sysPathHooks
+ self.importerCache = importerCache
+ self.moduleLoader = moduleLoader
+
+
+ def _getSysPath(self):
+ """
+ Retrieve the current value of the module search path list.
+ """
+ return self._sysPathFactory()
+
+ sysPath = property(_getSysPath)
+
+ def _findEntryPathString(self, modobj):
+ """
+ Determine where a given Python module object came from by looking at path
+ entries.
+ """
+ topPackageObj = modobj
+ while '.' in topPackageObj.__name__:
+ topPackageObj = self.moduleDict['.'.join(
+ topPackageObj.__name__.split('.')[:-1])]
+ if _isPackagePath(FilePath(topPackageObj.__file__)):
+ # if package 'foo' is on sys.path at /a/b/foo, package 'foo's
+ # __file__ will be /a/b/foo/__init__.py, and we are looking for
+ # /a/b here, the path-entry; so go up two steps.
+ rval = dirname(dirname(topPackageObj.__file__))
+ else:
+ # the module is completely top-level, not within any packages. The
+ # path entry it's on is just its dirname.
+ rval = dirname(topPackageObj.__file__)
+
+ # There are probably some awful tricks that an importer could pull
+ # which would break this, so let's just make sure... it's a loaded
+ # module after all, which means that its path MUST be in
+ # path_importer_cache according to PEP 302 -glyph
+ if rval not in self.importerCache:
+ warnings.warn(
+ "%s (for module %s) not in path importer cache "
+ "(PEP 302 violation - check your local configuration)." % (
+ rval, modobj.__name__),
+ stacklevel=3)
+
+ return rval
+
+ def _smartPath(self, pathName):
+ """
+ Given a path entry from sys.path which may refer to an importer,
+ return the appropriate FilePath-like instance.
+
+ @param pathName: a str describing the path.
+
+ @return: a FilePath-like object.
+ """
+ importr = self.importerCache.get(pathName, _nothing)
+ if importr is _nothing:
+ for hook in self.sysPathHooks:
+ try:
+ importr = hook(pathName)
+ except ImportError, ie:
+ pass
+ if importr is _nothing: # still
+ importr = None
+ return IPathImportMapper(importr, _theDefaultMapper).mapPath(pathName)
+
+ def iterEntries(self):
+ """
+ Iterate the entries on my sysPath.
+
+ @return: a generator yielding PathEntry objects
+ """
+ for pathName in self.sysPath:
+ fp = self._smartPath(pathName)
+ yield PathEntry(fp, self)
+
+ def __getitem__(self, modname):
+ """
+ Get a python module by a given fully-qualified name.
+
+ @return: a PythonModule object.
+
+ @raise: KeyError, if the module name is a module name.
+ """
+ # See if the module is already somewhere in Python-land.
+ if modname in self.moduleDict:
+ # we need 2 paths; one of the path entry and one for the module.
+ moduleObject = self.moduleDict[modname]
+ pe = PathEntry(
+ self._smartPath(
+ self._findEntryPathString(moduleObject)),
+ self)
+ mp = self._smartPath(moduleObject.__file__)
+ return PythonModule(modname, mp, pe)
+
+ # Recurse if we're trying to get a submodule.
+ if '.' in modname:
+ pkg = self
+ for name in modname.split('.'):
+ pkg = pkg[name]
+ return pkg
+
+ # Finally do the slowest possible thing and iterate
+ for module in self.iterModules():
+ if module.name == modname:
+ return module
+ raise KeyError(modname)
+
+ def __repr__(self):
+ """
+ Display my sysPath and moduleDict in a string representation.
+ """
+ return "PythonPath(%r,%r)" % (self.sysPath, self.moduleDict)
+
+ def iterModules(self):
+ """
+ Yield all top-level modules on my sysPath.
+ """
+ for entry in self.iterEntries():
+ for module in entry.iterModules():
+ yield module
+
+ def walkModules(self, importPackages=False):
+ """
+ Similar to L{iterModules}, this yields every module on the path, then every
+ submodule in each package or entry.
+ """
+ for package in self.iterModules():
+ for module in package.walkModules(importPackages=False):
+ yield module
+
+theSystemPath = PythonPath()
+
+def walkModules(importPackages=False):
+ """
+ Deeply iterate all modules on the global python path.
+
+ @param importPackages: Import packages as they are seen.
+ """
+ return theSystemPath.walkModules(importPackages=importPackages)
+
+def iterModules():
+ """
+ Iterate all modules and top-level packages on the global Python path, but
+ do not descend into packages.
+
+ @param importPackages: Import packages as they are seen.
+ """
+ return theSystemPath.iterModules()
+
+def getModule(moduleName):
+ """
+ Retrieve a module from the system path.
+ """
+ return theSystemPath[moduleName]
diff --git a/vendor/Twisted-10.0.0/twisted/python/monkey.py b/vendor/Twisted-10.0.0/twisted/python/monkey.py
new file mode 100644
index 0000000000..4c1a507ecd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/monkey.py
@@ -0,0 +1,73 @@
+# -*- test-case-name: twisted.test.test_monkey -*-
+
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+class MonkeyPatcher(object):
+ """
+ Cover up attributes with new objects. Neat for monkey-patching things for
+ unit-testing purposes.
+ """
+
+ def __init__(self, *patches):
+ # List of patches to apply in (obj, name, value).
+ self._patchesToApply = []
+ # List of the original values for things that have been patched.
+ # (obj, name, value) format.
+ self._originals = []
+ for patch in patches:
+ self.addPatch(*patch)
+
+
+ def addPatch(self, obj, name, value):
+ """
+ Add a patch so that the attribute C{name} on C{obj} will be assigned to
+ C{value} when C{patch} is called or during C{runWithPatches}.
+
+ You can restore the original values with a call to restore().
+ """
+ self._patchesToApply.append((obj, name, value))
+
+
+ def _alreadyPatched(self, obj, name):
+ """
+ Has the C{name} attribute of C{obj} already been patched by this
+ patcher?
+ """
+ for o, n, v in self._originals:
+ if (o, n) == (obj, name):
+ return True
+ return False
+
+
+ def patch(self):
+ """
+ Apply all of the patches that have been specified with L{addPatch}.
+ Reverse this operation using L{restore}.
+ """
+ for obj, name, value in self._patchesToApply:
+ if not self._alreadyPatched(obj, name):
+ self._originals.append((obj, name, getattr(obj, name)))
+ setattr(obj, name, value)
+
+
+ def restore(self):
+ """
+ Restore all original values to any patched objects.
+ """
+ while self._originals:
+ obj, name, value = self._originals.pop()
+ setattr(obj, name, value)
+
+
+ def runWithPatches(self, f, *args, **kw):
+ """
+ Apply each patch already specified. Then run the function f with the
+ given args and kwargs. Restore everything when done.
+ """
+ self.patch()
+ try:
+ return f(*args, **kw)
+ finally:
+ self.restore()
diff --git a/vendor/Twisted-10.0.0/twisted/python/otp.py b/vendor/Twisted-10.0.0/twisted/python/otp.py
new file mode 100644
index 0000000000..a5a9ec3ff8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/otp.py
@@ -0,0 +1,496 @@
+# -*- test-case-name: twisted.python.test.test_otp -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A One-Time Password System based on RFC 2289
+
+The class Authenticator contains the hashing-logic, and the parser for the
+readable output. It also contains challenge which returns a string describing
+the authentication scheme for a client.
+
+OTP is a password container for an user on a server.
+
+NOTE: Does not take care of transmitting the shared secret password.
+
+At the end there's a dict called dict which is dictionary contain 2048
+words for storing pronouncable 11-bit values. Taken from RFC 1760.
+
+Uses the MD5- and SHA-algorithms for hashing
+
+Todo: RFC2444, SASL (perhaps), parsing hex-responses
+
+This module is deprecated. Consider using U{another Python OTP
+library<http://labix.org/python-otp>} instead.
+"""
+
+import warnings
+import string
+import random
+
+warnings.warn(
+ "twisted.python.otp is deprecated since Twisted 8.3.",
+ category=DeprecationWarning,
+ stacklevel=2)
+
+
+def stringToLong(s):
+ """ Convert digest to long """
+ result = 0L
+ for byte in s:
+ result = (256 * result) + ord(byte)
+ return result
+
+def stringToDWords(s):
+ """ Convert digest to a list of four 32-bits words """
+ result = []
+ for a in xrange(len(s) / 4):
+ tmp = 0L
+ for byte in s[-4:]:
+ tmp = (256 * tmp) + ord(byte)
+ result.append(tmp)
+ s = s[:-4]
+ return result
+
+def longToString(l):
+ """ Convert long to digest """
+ result = ""
+ while l > 0L:
+ result = chr(l % 256) + result
+ l = l / 256L
+ return result
+
+from twisted.python.hashlib import md5, sha1
+hashid = {md5: 'md5', sha1: 'sha1'}
+
+INITIALSEQUENCE = 1000
+MINIMUMSEQUENCE = 50
+
+class Unauthorized(Exception):
+ """the Unauthorized exception
+
+ This exception is raised when an action is not allowed, or a user is not
+ authenticated properly.
+ """
+
+class OTPAuthenticator:
+ """
+ A One Time Password System
+
+ Based on RFC 2289, which is based on a the S/KEY Authentication-scheme.
+ It uses the MD5- and SHA-algorithms for hashing
+
+ The variable OTP is at all times a 64bit string.
+
+ @ivar hash: An object which can be used to compute hashes. This is either
+ L{md5} or L{sha1}.
+ """
+ def __init__(self, hash = md5):
+ "Set the hash to either md5 or sha1"
+ self.hash = hash
+
+
+ def generateSeed(self):
+ "Return a 10 char random seed, with 6 lowercase chars and 4 digits"
+ seed = ''
+ for x in range(6):
+ seed = seed + chr(random.randrange(97,122))
+ for x in range(4):
+ seed = seed + chr(random.randrange(48,57))
+ return seed
+
+ def foldDigest(self, otp):
+ if self.hash == md5:
+ return self.foldDigest128(otp)
+ if self.hash == sha1:
+ return self.foldDigest160(otp)
+
+ def foldDigest128(self, otp128):
+ "Fold a 128 bit digest to 64 bit"
+ regs = stringToDWords(otp128)
+ p0 = regs[0] ^ regs[2]
+ p1 = regs[1] ^ regs[3]
+ S = ''
+ for a in xrange(4):
+ S = chr(p0 & 0xFF) + S
+ p0 = p0 >> 8
+ for a in xrange(4):
+ S = chr(p1 & 0xFF) + S
+ p1 = p1 >> 8
+ return S
+
+ def foldDigest160(self, otp160):
+ "Fold a 160 bit digest to 64 bit"
+ regs = stringToDWords(otp160)
+ p0 = regs[0] ^ regs[2]
+ p1 = regs[1] ^ regs[3]
+ p0 = regs[0] ^ regs[4]
+ S = ''
+ for a in xrange(4):
+ S = chr(p0 & 0xFF) + S
+ p0 = p0 >> 8
+ for a in xrange(4):
+ S = chr(p1 & 0xFF) + S
+ p1 = p1 >> 8
+ return S
+
+ def hashUpdate(self, digest):
+ "Run through the hash and fold to 64 bit"
+ h = self.hash(digest)
+ return self.foldDigest(h.digest())
+
+ def generateOTP(self, seed, passwd, sequence):
+ """Return a 64 bit OTP based on inputs
+ Run through makeReadable to get a 6 word pass-phrase"""
+ seed = string.lower(seed)
+ otp = self.hashUpdate(seed + passwd)
+ for a in xrange(sequence):
+ otp = self.hashUpdate(otp)
+ return otp
+
+ def calculateParity(self, otp):
+ "Calculate the parity from a 64bit OTP"
+ parity = 0
+ for i in xrange(0, 64, 2):
+ parity = parity + otp & 0x3
+ otp = otp >> 2
+ return parity
+
+ def makeReadable(self, otp):
+ "Returns a 6 word pass-phrase from a 64bit OTP"
+ digest = stringToLong(otp)
+ list = []
+ parity = self.calculateParity(digest)
+ for i in xrange(4,-1, -1):
+ list.append(dict[(digest >> (i * 11 + 9)) & 0x7FF])
+ list.append(dict[(digest << 2) & 0x7FC | (parity & 0x03)])
+ return string.join(list)
+
+ def challenge(self, seed, sequence):
+ """Return a challenge in the format otp-<hash> <sequence> <seed>"""
+ return "otp-%s %i %s" % (hashid[self.hash], sequence, seed)
+
+ def parsePhrase(self, phrase):
+ """Decode the phrase, and return a 64bit OTP
+ I will raise Unauthorized if the parity is wrong
+ TODO: Add support for hex (MUST) and the '2nd scheme'(SHOULD)"""
+ words = string.split(phrase)
+ for i in xrange(len(words)):
+ words[i] = string.upper(words[i])
+ b = 0L
+ for i in xrange(0,5):
+ b = b | ((long(dict.index(words[i])) << ((4-i)*11L+9L)))
+ tmp = dict.index(words[5])
+ b = b | (tmp & 0x7FC ) >> 2
+ if (tmp & 3) <> self.calculateParity(b):
+ raise Unauthorized("Parity error")
+ digest = longToString(b)
+ return digest
+
+class OTP(OTPAuthenticator):
+ """An automatic version of the OTP-Authenticator
+
+ Updates the sequence and the keeps last approved password on success
+ On the next authentication, the stored password is hashed and checked
+ up against the one given by the user. If they match, the sequencecounter
+ is decreased and the circle is closed.
+
+ This object should be glued to each user
+
+ Note:
+ It does NOT reset the sequence when the combinations left approach zero,
+ This has to be done manuelly by instancing a new object
+ """
+ seed = None
+ sequence = 0
+ lastotp = None
+
+ def __init__(self, passwd, sequence = INITIALSEQUENCE, hash=md5):
+ """Initialize the OTP-Sequence, and discard the password"""
+ OTPAuthenticator.__init__(self, hash)
+ seed = self.generateSeed()
+ # Generate the 'last' password
+ self.lastotp = OTPAuthenticator.generateOTP(self, seed, passwd, sequence+1)
+ self.seed = seed
+ self.sequence = sequence
+
+ def challenge(self):
+ """Return a challenge string"""
+ result = OTPAuthenticator.challenge(self, self.seed, self.sequence)
+ return result
+
+ def authenticate(self, phrase):
+ """Test the phrase against the last challenge issued"""
+ try:
+ digest = self.parsePhrase(phrase)
+ hasheddigest = self.hashUpdate(digest)
+ if (self.lastotp == hasheddigest):
+ self.lastotp = digest
+ if self.sequence > MINIMUMSEQUENCE:
+ self.sequence = self.sequence - 1
+ return "ok"
+ else:
+ raise Unauthorized("Failed")
+ except Unauthorized, msg:
+ raise Unauthorized(msg)
+
+#
+# The 2048 word standard dictionary from RFC 1760
+#
+dict = ["A", "ABE", "ACE", "ACT", "AD", "ADA", "ADD",
+"AGO", "AID", "AIM", "AIR", "ALL", "ALP", "AM", "AMY",
+"AN", "ANA", "AND", "ANN", "ANT", "ANY", "APE", "APS",
+"APT", "ARC", "ARE", "ARK", "ARM", "ART", "AS", "ASH",
+"ASK", "AT", "ATE", "AUG", "AUK", "AVE", "AWE", "AWK",
+"AWL", "AWN", "AX", "AYE", "BAD", "BAG", "BAH", "BAM",
+"BAN", "BAR", "BAT", "BAY", "BE", "BED", "BEE", "BEG",
+"BEN", "BET", "BEY", "BIB", "BID", "BIG", "BIN", "BIT",
+"BOB", "BOG", "BON", "BOO", "BOP", "BOW", "BOY", "BUB",
+"BUD", "BUG", "BUM", "BUN", "BUS", "BUT", "BUY", "BY",
+"BYE", "CAB", "CAL", "CAM", "CAN", "CAP", "CAR", "CAT",
+"CAW", "COD", "COG", "COL", "CON", "COO", "COP", "COT",
+"COW", "COY", "CRY", "CUB", "CUE", "CUP", "CUR", "CUT",
+"DAB", "DAD", "DAM", "DAN", "DAR", "DAY", "DEE", "DEL",
+"DEN", "DES", "DEW", "DID", "DIE", "DIG", "DIN", "DIP",
+"DO", "DOE", "DOG", "DON", "DOT", "DOW", "DRY", "DUB",
+"DUD", "DUE", "DUG", "DUN", "EAR", "EAT", "ED", "EEL",
+"EGG", "EGO", "ELI", "ELK", "ELM", "ELY", "EM", "END",
+"EST", "ETC", "EVA", "EVE", "EWE", "EYE", "FAD", "FAN",
+"FAR", "FAT", "FAY", "FED", "FEE", "FEW", "FIB", "FIG",
+"FIN", "FIR", "FIT", "FLO", "FLY", "FOE", "FOG", "FOR",
+"FRY", "FUM", "FUN", "FUR", "GAB", "GAD", "GAG", "GAL",
+"GAM", "GAP", "GAS", "GAY", "GEE", "GEL", "GEM", "GET",
+"GIG", "GIL", "GIN", "GO", "GOT", "GUM", "GUN", "GUS",
+"GUT", "GUY", "GYM", "GYP", "HA", "HAD", "HAL", "HAM",
+"HAN", "HAP", "HAS", "HAT", "HAW", "HAY", "HE", "HEM",
+"HEN", "HER", "HEW", "HEY", "HI", "HID", "HIM", "HIP",
+"HIS", "HIT", "HO", "HOB", "HOC", "HOE", "HOG", "HOP",
+"HOT", "HOW", "HUB", "HUE", "HUG", "HUH", "HUM", "HUT",
+"I", "ICY", "IDA", "IF", "IKE", "ILL", "INK", "INN",
+"IO", "ION", "IQ", "IRA", "IRE", "IRK", "IS", "IT",
+"ITS", "IVY", "JAB", "JAG", "JAM", "JAN", "JAR", "JAW",
+"JAY", "JET", "JIG", "JIM", "JO", "JOB", "JOE", "JOG",
+"JOT", "JOY", "JUG", "JUT", "KAY", "KEG", "KEN", "KEY",
+"KID", "KIM", "KIN", "KIT", "LA", "LAB", "LAC", "LAD",
+"LAG", "LAM", "LAP", "LAW", "LAY", "LEA", "LED", "LEE",
+"LEG", "LEN", "LEO", "LET", "LEW", "LID", "LIE", "LIN",
+"LIP", "LIT", "LO", "LOB", "LOG", "LOP", "LOS", "LOT",
+"LOU", "LOW", "LOY", "LUG", "LYE", "MA", "MAC", "MAD",
+"MAE", "MAN", "MAO", "MAP", "MAT", "MAW", "MAY", "ME",
+"MEG", "MEL", "MEN", "MET", "MEW", "MID", "MIN", "MIT",
+"MOB", "MOD", "MOE", "MOO", "MOP", "MOS", "MOT", "MOW",
+"MUD", "MUG", "MUM", "MY", "NAB", "NAG", "NAN", "NAP",
+"NAT", "NAY", "NE", "NED", "NEE", "NET", "NEW", "NIB",
+"NIL", "NIP", "NIT", "NO", "NOB", "NOD", "NON", "NOR",
+"NOT", "NOV", "NOW", "NU", "NUN", "NUT", "O", "OAF",
+"OAK", "OAR", "OAT", "ODD", "ODE", "OF", "OFF", "OFT",
+"OH", "OIL", "OK", "OLD", "ON", "ONE", "OR", "ORB",
+"ORE", "ORR", "OS", "OTT", "OUR", "OUT", "OVA", "OW",
+"OWE", "OWL", "OWN", "OX", "PA", "PAD", "PAL", "PAM",
+"PAN", "PAP", "PAR", "PAT", "PAW", "PAY", "PEA", "PEG",
+"PEN", "PEP", "PER", "PET", "PEW", "PHI", "PI", "PIE",
+"PIN", "PIT", "PLY", "PO", "POD", "POE", "POP", "POT",
+"POW", "PRO", "PRY", "PUB", "PUG", "PUN", "PUP", "PUT",
+"QUO", "RAG", "RAM", "RAN", "RAP", "RAT", "RAW", "RAY",
+"REB", "RED", "REP", "RET", "RIB", "RID", "RIG", "RIM",
+"RIO", "RIP", "ROB", "ROD", "ROE", "RON", "ROT", "ROW",
+"ROY", "RUB", "RUE", "RUG", "RUM", "RUN", "RYE", "SAC",
+"SAD", "SAG", "SAL", "SAM", "SAN", "SAP", "SAT", "SAW",
+"SAY", "SEA", "SEC", "SEE", "SEN", "SET", "SEW", "SHE",
+"SHY", "SIN", "SIP", "SIR", "SIS", "SIT", "SKI", "SKY",
+"SLY", "SO", "SOB", "SOD", "SON", "SOP", "SOW", "SOY",
+"SPA", "SPY", "SUB", "SUD", "SUE", "SUM", "SUN", "SUP",
+"TAB", "TAD", "TAG", "TAN", "TAP", "TAR", "TEA", "TED",
+"TEE", "TEN", "THE", "THY", "TIC", "TIE", "TIM", "TIN",
+"TIP", "TO", "TOE", "TOG", "TOM", "TON", "TOO", "TOP",
+"TOW", "TOY", "TRY", "TUB", "TUG", "TUM", "TUN", "TWO",
+"UN", "UP", "US", "USE", "VAN", "VAT", "VET", "VIE",
+"WAD", "WAG", "WAR", "WAS", "WAY", "WE", "WEB", "WED",
+"WEE", "WET", "WHO", "WHY", "WIN", "WIT", "WOK", "WON",
+"WOO", "WOW", "WRY", "WU", "YAM", "YAP", "YAW", "YE",
+"YEA", "YES", "YET", "YOU", "ABED", "ABEL", "ABET", "ABLE",
+"ABUT", "ACHE", "ACID", "ACME", "ACRE", "ACTA", "ACTS", "ADAM",
+"ADDS", "ADEN", "AFAR", "AFRO", "AGEE", "AHEM", "AHOY", "AIDA",
+"AIDE", "AIDS", "AIRY", "AJAR", "AKIN", "ALAN", "ALEC", "ALGA",
+"ALIA", "ALLY", "ALMA", "ALOE", "ALSO", "ALTO", "ALUM", "ALVA",
+"AMEN", "AMES", "AMID", "AMMO", "AMOK", "AMOS", "AMRA", "ANDY",
+"ANEW", "ANNA", "ANNE", "ANTE", "ANTI", "AQUA", "ARAB", "ARCH",
+"AREA", "ARGO", "ARID", "ARMY", "ARTS", "ARTY", "ASIA", "ASKS",
+"ATOM", "AUNT", "AURA", "AUTO", "AVER", "AVID", "AVIS", "AVON",
+"AVOW", "AWAY", "AWRY", "BABE", "BABY", "BACH", "BACK", "BADE",
+"BAIL", "BAIT", "BAKE", "BALD", "BALE", "BALI", "BALK", "BALL",
+"BALM", "BAND", "BANE", "BANG", "BANK", "BARB", "BARD", "BARE",
+"BARK", "BARN", "BARR", "BASE", "BASH", "BASK", "BASS", "BATE",
+"BATH", "BAWD", "BAWL", "BEAD", "BEAK", "BEAM", "BEAN", "BEAR",
+"BEAT", "BEAU", "BECK", "BEEF", "BEEN", "BEER", "BEET", "BELA",
+"BELL", "BELT", "BEND", "BENT", "BERG", "BERN", "BERT", "BESS",
+"BEST", "BETA", "BETH", "BHOY", "BIAS", "BIDE", "BIEN", "BILE",
+"BILK", "BILL", "BIND", "BING", "BIRD", "BITE", "BITS", "BLAB",
+"BLAT", "BLED", "BLEW", "BLOB", "BLOC", "BLOT", "BLOW", "BLUE",
+"BLUM", "BLUR", "BOAR", "BOAT", "BOCA", "BOCK", "BODE", "BODY",
+"BOGY", "BOHR", "BOIL", "BOLD", "BOLO", "BOLT", "BOMB", "BONA",
+"BOND", "BONE", "BONG", "BONN", "BONY", "BOOK", "BOOM", "BOON",
+"BOOT", "BORE", "BORG", "BORN", "BOSE", "BOSS", "BOTH", "BOUT",
+"BOWL", "BOYD", "BRAD", "BRAE", "BRAG", "BRAN", "BRAY", "BRED",
+"BREW", "BRIG", "BRIM", "BROW", "BUCK", "BUDD", "BUFF", "BULB",
+"BULK", "BULL", "BUNK", "BUNT", "BUOY", "BURG", "BURL", "BURN",
+"BURR", "BURT", "BURY", "BUSH", "BUSS", "BUST", "BUSY", "BYTE",
+"CADY", "CAFE", "CAGE", "CAIN", "CAKE", "CALF", "CALL", "CALM",
+"CAME", "CANE", "CANT", "CARD", "CARE", "CARL", "CARR", "CART",
+"CASE", "CASH", "CASK", "CAST", "CAVE", "CEIL", "CELL", "CENT",
+"CERN", "CHAD", "CHAR", "CHAT", "CHAW", "CHEF", "CHEN", "CHEW",
+"CHIC", "CHIN", "CHOU", "CHOW", "CHUB", "CHUG", "CHUM", "CITE",
+"CITY", "CLAD", "CLAM", "CLAN", "CLAW", "CLAY", "CLOD", "CLOG",
+"CLOT", "CLUB", "CLUE", "COAL", "COAT", "COCA", "COCK", "COCO",
+"CODA", "CODE", "CODY", "COED", "COIL", "COIN", "COKE", "COLA",
+"COLD", "COLT", "COMA", "COMB", "COME", "COOK", "COOL", "COON",
+"COOT", "CORD", "CORE", "CORK", "CORN", "COST", "COVE", "COWL",
+"CRAB", "CRAG", "CRAM", "CRAY", "CREW", "CRIB", "CROW", "CRUD",
+"CUBA", "CUBE", "CUFF", "CULL", "CULT", "CUNY", "CURB", "CURD",
+"CURE", "CURL", "CURT", "CUTS", "DADE", "DALE", "DAME", "DANA",
+"DANE", "DANG", "DANK", "DARE", "DARK", "DARN", "DART", "DASH",
+"DATA", "DATE", "DAVE", "DAVY", "DAWN", "DAYS", "DEAD", "DEAF",
+"DEAL", "DEAN", "DEAR", "DEBT", "DECK", "DEED", "DEEM", "DEER",
+"DEFT", "DEFY", "DELL", "DENT", "DENY", "DESK", "DIAL", "DICE",
+"DIED", "DIET", "DIME", "DINE", "DING", "DINT", "DIRE", "DIRT",
+"DISC", "DISH", "DISK", "DIVE", "DOCK", "DOES", "DOLE", "DOLL",
+"DOLT", "DOME", "DONE", "DOOM", "DOOR", "DORA", "DOSE", "DOTE",
+"DOUG", "DOUR", "DOVE", "DOWN", "DRAB", "DRAG", "DRAM", "DRAW",
+"DREW", "DRUB", "DRUG", "DRUM", "DUAL", "DUCK", "DUCT", "DUEL",
+"DUET", "DUKE", "DULL", "DUMB", "DUNE", "DUNK", "DUSK", "DUST",
+"DUTY", "EACH", "EARL", "EARN", "EASE", "EAST", "EASY", "EBEN",
+"ECHO", "EDDY", "EDEN", "EDGE", "EDGY", "EDIT", "EDNA", "EGAN",
+"ELAN", "ELBA", "ELLA", "ELSE", "EMIL", "EMIT", "EMMA", "ENDS",
+"ERIC", "EROS", "EVEN", "EVER", "EVIL", "EYED", "FACE", "FACT",
+"FADE", "FAIL", "FAIN", "FAIR", "FAKE", "FALL", "FAME", "FANG",
+"FARM", "FAST", "FATE", "FAWN", "FEAR", "FEAT", "FEED", "FEEL",
+"FEET", "FELL", "FELT", "FEND", "FERN", "FEST", "FEUD", "FIEF",
+"FIGS", "FILE", "FILL", "FILM", "FIND", "FINE", "FINK", "FIRE",
+"FIRM", "FISH", "FISK", "FIST", "FITS", "FIVE", "FLAG", "FLAK",
+"FLAM", "FLAT", "FLAW", "FLEA", "FLED", "FLEW", "FLIT", "FLOC",
+"FLOG", "FLOW", "FLUB", "FLUE", "FOAL", "FOAM", "FOGY", "FOIL",
+"FOLD", "FOLK", "FOND", "FONT", "FOOD", "FOOL", "FOOT", "FORD",
+"FORE", "FORK", "FORM", "FORT", "FOSS", "FOUL", "FOUR", "FOWL",
+"FRAU", "FRAY", "FRED", "FREE", "FRET", "FREY", "FROG", "FROM",
+"FUEL", "FULL", "FUME", "FUND", "FUNK", "FURY", "FUSE", "FUSS",
+"GAFF", "GAGE", "GAIL", "GAIN", "GAIT", "GALA", "GALE", "GALL",
+"GALT", "GAME", "GANG", "GARB", "GARY", "GASH", "GATE", "GAUL",
+"GAUR", "GAVE", "GAWK", "GEAR", "GELD", "GENE", "GENT", "GERM",
+"GETS", "GIBE", "GIFT", "GILD", "GILL", "GILT", "GINA", "GIRD",
+"GIRL", "GIST", "GIVE", "GLAD", "GLEE", "GLEN", "GLIB", "GLOB",
+"GLOM", "GLOW", "GLUE", "GLUM", "GLUT", "GOAD", "GOAL", "GOAT",
+"GOER", "GOES", "GOLD", "GOLF", "GONE", "GONG", "GOOD", "GOOF",
+"GORE", "GORY", "GOSH", "GOUT", "GOWN", "GRAB", "GRAD", "GRAY",
+"GREG", "GREW", "GREY", "GRID", "GRIM", "GRIN", "GRIT", "GROW",
+"GRUB", "GULF", "GULL", "GUNK", "GURU", "GUSH", "GUST", "GWEN",
+"GWYN", "HAAG", "HAAS", "HACK", "HAIL", "HAIR", "HALE", "HALF",
+"HALL", "HALO", "HALT", "HAND", "HANG", "HANK", "HANS", "HARD",
+"HARK", "HARM", "HART", "HASH", "HAST", "HATE", "HATH", "HAUL",
+"HAVE", "HAWK", "HAYS", "HEAD", "HEAL", "HEAR", "HEAT", "HEBE",
+"HECK", "HEED", "HEEL", "HEFT", "HELD", "HELL", "HELM", "HERB",
+"HERD", "HERE", "HERO", "HERS", "HESS", "HEWN", "HICK", "HIDE",
+"HIGH", "HIKE", "HILL", "HILT", "HIND", "HINT", "HIRE", "HISS",
+"HIVE", "HOBO", "HOCK", "HOFF", "HOLD", "HOLE", "HOLM", "HOLT",
+"HOME", "HONE", "HONK", "HOOD", "HOOF", "HOOK", "HOOT", "HORN",
+"HOSE", "HOST", "HOUR", "HOVE", "HOWE", "HOWL", "HOYT", "HUCK",
+"HUED", "HUFF", "HUGE", "HUGH", "HUGO", "HULK", "HULL", "HUNK",
+"HUNT", "HURD", "HURL", "HURT", "HUSH", "HYDE", "HYMN", "IBIS",
+"ICON", "IDEA", "IDLE", "IFFY", "INCA", "INCH", "INTO", "IONS",
+"IOTA", "IOWA", "IRIS", "IRMA", "IRON", "ISLE", "ITCH", "ITEM",
+"IVAN", "JACK", "JADE", "JAIL", "JAKE", "JANE", "JAVA", "JEAN",
+"JEFF", "JERK", "JESS", "JEST", "JIBE", "JILL", "JILT", "JIVE",
+"JOAN", "JOBS", "JOCK", "JOEL", "JOEY", "JOHN", "JOIN", "JOKE",
+"JOLT", "JOVE", "JUDD", "JUDE", "JUDO", "JUDY", "JUJU", "JUKE",
+"JULY", "JUNE", "JUNK", "JUNO", "JURY", "JUST", "JUTE", "KAHN",
+"KALE", "KANE", "KANT", "KARL", "KATE", "KEEL", "KEEN", "KENO",
+"KENT", "KERN", "KERR", "KEYS", "KICK", "KILL", "KIND", "KING",
+"KIRK", "KISS", "KITE", "KLAN", "KNEE", "KNEW", "KNIT", "KNOB",
+"KNOT", "KNOW", "KOCH", "KONG", "KUDO", "KURD", "KURT", "KYLE",
+"LACE", "LACK", "LACY", "LADY", "LAID", "LAIN", "LAIR", "LAKE",
+"LAMB", "LAME", "LAND", "LANE", "LANG", "LARD", "LARK", "LASS",
+"LAST", "LATE", "LAUD", "LAVA", "LAWN", "LAWS", "LAYS", "LEAD",
+"LEAF", "LEAK", "LEAN", "LEAR", "LEEK", "LEER", "LEFT", "LEND",
+"LENS", "LENT", "LEON", "LESK", "LESS", "LEST", "LETS", "LIAR",
+"LICE", "LICK", "LIED", "LIEN", "LIES", "LIEU", "LIFE", "LIFT",
+"LIKE", "LILA", "LILT", "LILY", "LIMA", "LIMB", "LIME", "LIND",
+"LINE", "LINK", "LINT", "LION", "LISA", "LIST", "LIVE", "LOAD",
+"LOAF", "LOAM", "LOAN", "LOCK", "LOFT", "LOGE", "LOIS", "LOLA",
+"LONE", "LONG", "LOOK", "LOON", "LOOT", "LORD", "LORE", "LOSE",
+"LOSS", "LOST", "LOUD", "LOVE", "LOWE", "LUCK", "LUCY", "LUGE",
+"LUKE", "LULU", "LUND", "LUNG", "LURA", "LURE", "LURK", "LUSH",
+"LUST", "LYLE", "LYNN", "LYON", "LYRA", "MACE", "MADE", "MAGI",
+"MAID", "MAIL", "MAIN", "MAKE", "MALE", "MALI", "MALL", "MALT",
+"MANA", "MANN", "MANY", "MARC", "MARE", "MARK", "MARS", "MART",
+"MARY", "MASH", "MASK", "MASS", "MAST", "MATE", "MATH", "MAUL",
+"MAYO", "MEAD", "MEAL", "MEAN", "MEAT", "MEEK", "MEET", "MELD",
+"MELT", "MEMO", "MEND", "MENU", "MERT", "MESH", "MESS", "MICE",
+"MIKE", "MILD", "MILE", "MILK", "MILL", "MILT", "MIMI", "MIND",
+"MINE", "MINI", "MINK", "MINT", "MIRE", "MISS", "MIST", "MITE",
+"MITT", "MOAN", "MOAT", "MOCK", "MODE", "MOLD", "MOLE", "MOLL",
+"MOLT", "MONA", "MONK", "MONT", "MOOD", "MOON", "MOOR", "MOOT",
+"MORE", "MORN", "MORT", "MOSS", "MOST", "MOTH", "MOVE", "MUCH",
+"MUCK", "MUDD", "MUFF", "MULE", "MULL", "MURK", "MUSH", "MUST",
+"MUTE", "MUTT", "MYRA", "MYTH", "NAGY", "NAIL", "NAIR", "NAME",
+"NARY", "NASH", "NAVE", "NAVY", "NEAL", "NEAR", "NEAT", "NECK",
+"NEED", "NEIL", "NELL", "NEON", "NERO", "NESS", "NEST", "NEWS",
+"NEWT", "NIBS", "NICE", "NICK", "NILE", "NINA", "NINE", "NOAH",
+"NODE", "NOEL", "NOLL", "NONE", "NOOK", "NOON", "NORM", "NOSE",
+"NOTE", "NOUN", "NOVA", "NUDE", "NULL", "NUMB", "OATH", "OBEY",
+"OBOE", "ODIN", "OHIO", "OILY", "OINT", "OKAY", "OLAF", "OLDY",
+"OLGA", "OLIN", "OMAN", "OMEN", "OMIT", "ONCE", "ONES", "ONLY",
+"ONTO", "ONUS", "ORAL", "ORGY", "OSLO", "OTIS", "OTTO", "OUCH",
+"OUST", "OUTS", "OVAL", "OVEN", "OVER", "OWLY", "OWNS", "QUAD",
+"QUIT", "QUOD", "RACE", "RACK", "RACY", "RAFT", "RAGE", "RAID",
+"RAIL", "RAIN", "RAKE", "RANK", "RANT", "RARE", "RASH", "RATE",
+"RAVE", "RAYS", "READ", "REAL", "REAM", "REAR", "RECK", "REED",
+"REEF", "REEK", "REEL", "REID", "REIN", "RENA", "REND", "RENT",
+"REST", "RICE", "RICH", "RICK", "RIDE", "RIFT", "RILL", "RIME",
+"RING", "RINK", "RISE", "RISK", "RITE", "ROAD", "ROAM", "ROAR",
+"ROBE", "ROCK", "RODE", "ROIL", "ROLL", "ROME", "ROOD", "ROOF",
+"ROOK", "ROOM", "ROOT", "ROSA", "ROSE", "ROSS", "ROSY", "ROTH",
+"ROUT", "ROVE", "ROWE", "ROWS", "RUBE", "RUBY", "RUDE", "RUDY",
+"RUIN", "RULE", "RUNG", "RUNS", "RUNT", "RUSE", "RUSH", "RUSK",
+"RUSS", "RUST", "RUTH", "SACK", "SAFE", "SAGE", "SAID", "SAIL",
+"SALE", "SALK", "SALT", "SAME", "SAND", "SANE", "SANG", "SANK",
+"SARA", "SAUL", "SAVE", "SAYS", "SCAN", "SCAR", "SCAT", "SCOT",
+"SEAL", "SEAM", "SEAR", "SEAT", "SEED", "SEEK", "SEEM", "SEEN",
+"SEES", "SELF", "SELL", "SEND", "SENT", "SETS", "SEWN", "SHAG",
+"SHAM", "SHAW", "SHAY", "SHED", "SHIM", "SHIN", "SHOD", "SHOE",
+"SHOT", "SHOW", "SHUN", "SHUT", "SICK", "SIDE", "SIFT", "SIGH",
+"SIGN", "SILK", "SILL", "SILO", "SILT", "SINE", "SING", "SINK",
+"SIRE", "SITE", "SITS", "SITU", "SKAT", "SKEW", "SKID", "SKIM",
+"SKIN", "SKIT", "SLAB", "SLAM", "SLAT", "SLAY", "SLED", "SLEW",
+"SLID", "SLIM", "SLIT", "SLOB", "SLOG", "SLOT", "SLOW", "SLUG",
+"SLUM", "SLUR", "SMOG", "SMUG", "SNAG", "SNOB", "SNOW", "SNUB",
+"SNUG", "SOAK", "SOAR", "SOCK", "SODA", "SOFA", "SOFT", "SOIL",
+"SOLD", "SOME", "SONG", "SOON", "SOOT", "SORE", "SORT", "SOUL",
+"SOUR", "SOWN", "STAB", "STAG", "STAN", "STAR", "STAY", "STEM",
+"STEW", "STIR", "STOW", "STUB", "STUN", "SUCH", "SUDS", "SUIT",
+"SULK", "SUMS", "SUNG", "SUNK", "SURE", "SURF", "SWAB", "SWAG",
+"SWAM", "SWAN", "SWAT", "SWAY", "SWIM", "SWUM", "TACK", "TACT",
+"TAIL", "TAKE", "TALE", "TALK", "TALL", "TANK", "TASK", "TATE",
+"TAUT", "TEAL", "TEAM", "TEAR", "TECH", "TEEM", "TEEN", "TEET",
+"TELL", "TEND", "TENT", "TERM", "TERN", "TESS", "TEST", "THAN",
+"THAT", "THEE", "THEM", "THEN", "THEY", "THIN", "THIS", "THUD",
+"THUG", "TICK", "TIDE", "TIDY", "TIED", "TIER", "TILE", "TILL",
+"TILT", "TIME", "TINA", "TINE", "TINT", "TINY", "TIRE", "TOAD",
+"TOGO", "TOIL", "TOLD", "TOLL", "TONE", "TONG", "TONY", "TOOK",
+"TOOL", "TOOT", "TORE", "TORN", "TOTE", "TOUR", "TOUT", "TOWN",
+"TRAG", "TRAM", "TRAY", "TREE", "TREK", "TRIG", "TRIM", "TRIO",
+"TROD", "TROT", "TROY", "TRUE", "TUBA", "TUBE", "TUCK", "TUFT",
+"TUNA", "TUNE", "TUNG", "TURF", "TURN", "TUSK", "TWIG", "TWIN",
+"TWIT", "ULAN", "UNIT", "URGE", "USED", "USER", "USES", "UTAH",
+"VAIL", "VAIN", "VALE", "VARY", "VASE", "VAST", "VEAL", "VEDA",
+"VEIL", "VEIN", "VEND", "VENT", "VERB", "VERY", "VETO", "VICE",
+"VIEW", "VINE", "VISE", "VOID", "VOLT", "VOTE", "WACK", "WADE",
+"WAGE", "WAIL", "WAIT", "WAKE", "WALE", "WALK", "WALL", "WALT",
+"WAND", "WANE", "WANG", "WANT", "WARD", "WARM", "WARN", "WART",
+"WASH", "WAST", "WATS", "WATT", "WAVE", "WAVY", "WAYS", "WEAK",
+"WEAL", "WEAN", "WEAR", "WEED", "WEEK", "WEIR", "WELD", "WELL",
+"WELT", "WENT", "WERE", "WERT", "WEST", "WHAM", "WHAT", "WHEE",
+"WHEN", "WHET", "WHOA", "WHOM", "WICK", "WIFE", "WILD", "WILL",
+"WIND", "WINE", "WING", "WINK", "WINO", "WIRE", "WISE", "WISH",
+"WITH", "WOLF", "WONT", "WOOD", "WOOL", "WORD", "WORE", "WORK",
+"WORM", "WORN", "WOVE", "WRIT", "WYNN", "YALE", "YANG", "YANK",
+"YARD", "YARN", "YAWL", "YAWN", "YEAH", "YEAR", "YELL", "YOGA",
+"YOKE"]
diff --git a/vendor/Twisted-10.0.0/twisted/python/procutils.py b/vendor/Twisted-10.0.0/twisted/python/procutils.py
new file mode 100644
index 0000000000..c96a1604bf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/procutils.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Utilities for dealing with processes.
+"""
+
+import os
+
+def which(name, flags=os.X_OK):
+ """Search PATH for executable files with the given name.
+
+ On newer versions of MS-Windows, the PATHEXT environment variable will be
+ set to the list of file extensions for files considered executable. This
+ will normally include things like ".EXE". This fuction will also find files
+ with the given name ending with any of these extensions.
+
+ On MS-Windows the only flag that has any meaning is os.F_OK. Any other
+ flags will be ignored.
+
+ @type name: C{str}
+ @param name: The name for which to search.
+
+ @type flags: C{int}
+ @param flags: Arguments to L{os.access}.
+
+ @rtype: C{list}
+ @param: A list of the full paths to files found, in the
+ order in which they were found.
+ """
+ result = []
+ exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep))
+ path = os.environ.get('PATH', None)
+ if path is None:
+ return []
+ for p in os.environ.get('PATH', '').split(os.pathsep):
+ p = os.path.join(p, name)
+ if os.access(p, flags):
+ result.append(p)
+ for e in exts:
+ pext = p + e
+ if os.access(pext, flags):
+ result.append(pext)
+ return result
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/randbytes.py b/vendor/Twisted-10.0.0/twisted/python/randbytes.py
new file mode 100644
index 0000000000..1484146940
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/randbytes.py
@@ -0,0 +1,177 @@
+# -*- test-case-name: twisted.test.test_randbytes -*-
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Cryptographically secure random implementation, with fallback on normal random.
+"""
+
+# System imports
+import warnings, os, random
+
+getrandbits = getattr(random, 'getrandbits', None)
+
+try:
+ from Crypto.Util import randpool
+except ImportError:
+ randpool = None
+
+
+
+class SecureRandomNotAvailable(RuntimeError):
+ """
+ Exception raised when no secure random algorithm is found.
+ """
+
+
+
+class SourceNotAvailable(RuntimeError):
+ """
+ Internal exception used when a specific random source is not available.
+ """
+
+
+
+class RandomFactory(object):
+ """
+ Factory providing L{secureRandom} and L{insecureRandom} methods.
+
+ You shouldn't have to instantiate this class, use the module level
+ functions instead: it is an implementation detail and could be removed or
+ changed arbitrarily.
+
+ @ivar randomPool: pool of random data.
+ @type randomPool: C{randpool.RandomPool}
+
+ @cvar randomSources: list of file sources used when os.urandom is not
+ available.
+ @type randomSources: C{tuple}
+ """
+ randpool = randpool
+ randomPool = None
+ randomSources = ('/dev/urandom',)
+ getrandbits = getrandbits
+
+
+ def _osUrandom(self, nbytes):
+ """
+ Wrapper around C{os.urandom} that cleanly manage its absence.
+ """
+ try:
+ return os.urandom(nbytes)
+ except (AttributeError, NotImplementedError), e:
+ raise SourceNotAvailable(e)
+
+
+ def _fileUrandom(self, nbytes):
+ """
+ Wrapper around random file sources.
+
+ This method isn't meant to be call out of the class and could be
+ removed arbitrarily.
+ """
+ for src in self.randomSources:
+ try:
+ f = file(src, 'rb')
+ except (IOError, OSError):
+ pass
+ else:
+ bytes = f.read(nbytes)
+ f.close()
+ return bytes
+ raise SourceNotAvailable("File sources not available: %s" %
+ (self.randomSources,))
+
+
+ def _cryptoRandom(self, nbytes):
+ """
+ Wrapper around Crypo random pool.
+ """
+ if self.randpool is not None:
+ if self.randomPool is None:
+ self.randomPool = self.randpool.RandomPool()
+ self.randomPool.stir()
+ return self.randomPool.get_bytes(nbytes)
+ raise SourceNotAvailable("PyCrypto isn't installed")
+
+
+ def secureRandom(self, nbytes, fallback=False):
+ """
+ Return a number of secure random bytes.
+
+ @param nbytes: number of bytes to generate.
+ @type nbytes: C{int}
+ @param fallback: Whether the function should fallback on non-secure
+ random or not. Default to C{False}.
+ @type fallback: C{bool}
+
+ @return: a string of random bytes.
+ @rtype: C{str}
+ """
+ for src in ("_osUrandom", "_fileUrandom", "_cryptoRandom"):
+ try:
+ return getattr(self, src)(nbytes)
+ except SourceNotAvailable:
+ pass
+ if fallback:
+ warnings.warn(
+ "Neither PyCrypto nor urandom available - "
+ "proceeding with non-cryptographically secure random source",
+ category=RuntimeWarning,
+ stacklevel=2
+ )
+ return self.insecureRandom(nbytes)
+ else:
+ raise SecureRandomNotAvailable("No secure random source available")
+
+
+ def _randBits(self, nbytes):
+ """
+ Wrapper around C{os.getrandbits}.
+ """
+ if self.getrandbits is not None:
+ n = self.getrandbits(nbytes * 8)
+ hexBytes = ("%%0%dx" % (nbytes * 2)) % n
+ return hexBytes.decode('hex')
+ raise SourceNotAvailable("random.getrandbits is not available")
+
+
+ def _randRange(self, nbytes):
+ """
+ Wrapper around C{random.randrange}.
+ """
+ bytes = ""
+ for i in xrange(nbytes):
+ bytes += chr(random.randrange(0, 255))
+ return bytes
+
+
+ def insecureRandom(self, nbytes):
+ """
+ Return a number of non secure random bytes.
+
+ @param nbytes: number of bytes to generate.
+ @type nbytes: C{int}
+
+ @return: a string of random bytes.
+ @rtype: C{str}
+ """
+ for src in ("_randBits", "_randRange"):
+ try:
+ return getattr(self, src)(nbytes)
+ except SourceNotAvailable:
+ pass
+
+
+
+factory = RandomFactory()
+
+secureRandom = factory.secureRandom
+
+insecureRandom = factory.insecureRandom
+
+del factory
+
+
+__all__ = ["secureRandom", "insecureRandom", "SecureRandomNotAvailable"]
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/rebuild.py b/vendor/Twisted-10.0.0/twisted/python/rebuild.py
new file mode 100644
index 0000000000..dc8822906b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/rebuild.py
@@ -0,0 +1,264 @@
+# -*- test-case-name: twisted.test.test_rebuild -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+*Real* reloading support for Python.
+"""
+
+# System Imports
+import sys
+import types
+import time
+import linecache
+
+# Sibling Imports
+from twisted.python import log, reflect
+
+lastRebuild = time.time()
+
+
+class Sensitive:
+ """
+ A utility mixin that's sensitive to rebuilds.
+
+ This is a mixin for classes (usually those which represent collections of
+ callbacks) to make sure that their code is up-to-date before running.
+ """
+
+ lastRebuild = lastRebuild
+
+ def needRebuildUpdate(self):
+ yn = (self.lastRebuild < lastRebuild)
+ return yn
+
+ def rebuildUpToDate(self):
+ self.lastRebuild = time.time()
+
+ def latestVersionOf(self, anObject):
+ """
+ Get the latest version of an object.
+
+ This can handle just about anything callable; instances, functions,
+ methods, and classes.
+ """
+ t = type(anObject)
+ if t == types.FunctionType:
+ return latestFunction(anObject)
+ elif t == types.MethodType:
+ if anObject.im_self is None:
+ return getattr(anObject.im_class, anObject.__name__)
+ else:
+ return getattr(anObject.im_self, anObject.__name__)
+ elif t == types.InstanceType:
+ # Kick it, if it's out of date.
+ getattr(anObject, 'nothing', None)
+ return anObject
+ elif t == types.ClassType:
+ return latestClass(anObject)
+ else:
+ log.msg('warning returning anObject!')
+ return anObject
+
+_modDictIDMap = {}
+
+def latestFunction(oldFunc):
+ """
+ Get the latest version of a function.
+ """
+ # This may be CPython specific, since I believe jython instantiates a new
+ # module upon reload.
+ dictID = id(oldFunc.func_globals)
+ module = _modDictIDMap.get(dictID)
+ if module is None:
+ return oldFunc
+ return getattr(module, oldFunc.__name__)
+
+
+def latestClass(oldClass):
+ """
+ Get the latest version of a class.
+ """
+ module = reflect.namedModule(oldClass.__module__)
+ newClass = getattr(module, oldClass.__name__)
+ newBases = [latestClass(base) for base in newClass.__bases__]
+
+ try:
+ # This makes old-style stuff work
+ newClass.__bases__ = tuple(newBases)
+ return newClass
+ except TypeError:
+ if newClass.__module__ == "__builtin__":
+ # __builtin__ members can't be reloaded sanely
+ return newClass
+ ctor = getattr(newClass, '__metaclass__', type)
+ return ctor(newClass.__name__, tuple(newBases), dict(newClass.__dict__))
+
+
+class RebuildError(Exception):
+ """
+ Exception raised when trying to rebuild a class whereas it's not possible.
+ """
+
+
+def updateInstance(self):
+ """
+ Updates an instance to be current.
+ """
+ try:
+ self.__class__ = latestClass(self.__class__)
+ except TypeError:
+ if hasattr(self.__class__, '__slots__'):
+ raise RebuildError("Can't rebuild class with __slots__ on Python < 2.6")
+ else:
+ raise
+
+
+def __getattr__(self, name):
+ """
+ A getattr method to cause a class to be refreshed.
+ """
+ if name == '__del__':
+ raise AttributeError("Without this, Python segfaults.")
+ updateInstance(self)
+ log.msg("(rebuilding stale %s instance (%s))" % (reflect.qual(self.__class__), name))
+ result = getattr(self, name)
+ return result
+
+
+def rebuild(module, doLog=1):
+ """
+ Reload a module and do as much as possible to replace its references.
+ """
+ global lastRebuild
+ lastRebuild = time.time()
+ if hasattr(module, 'ALLOW_TWISTED_REBUILD'):
+ # Is this module allowed to be rebuilt?
+ if not module.ALLOW_TWISTED_REBUILD:
+ raise RuntimeError("I am not allowed to be rebuilt.")
+ if doLog:
+ log.msg('Rebuilding %s...' % str(module.__name__))
+
+ ## Safely handle adapter re-registration
+ from twisted.python import components
+ components.ALLOW_DUPLICATES = True
+
+ d = module.__dict__
+ _modDictIDMap[id(d)] = module
+ newclasses = {}
+ classes = {}
+ functions = {}
+ values = {}
+ if doLog:
+ log.msg(' (scanning %s): ' % str(module.__name__))
+ for k, v in d.items():
+ if type(v) == types.ClassType:
+ # Failure condition -- instances of classes with buggy
+ # __hash__/__cmp__ methods referenced at the module level...
+ if v.__module__ == module.__name__:
+ classes[v] = 1
+ if doLog:
+ log.logfile.write("c")
+ log.logfile.flush()
+ elif type(v) == types.FunctionType:
+ if v.func_globals is module.__dict__:
+ functions[v] = 1
+ if doLog:
+ log.logfile.write("f")
+ log.logfile.flush()
+ elif isinstance(v, type):
+ if v.__module__ == module.__name__:
+ newclasses[v] = 1
+ if doLog:
+ log.logfile.write("o")
+ log.logfile.flush()
+
+ values.update(classes)
+ values.update(functions)
+ fromOldModule = values.has_key
+ newclasses = newclasses.keys()
+ classes = classes.keys()
+ functions = functions.keys()
+
+ if doLog:
+ log.msg('')
+ log.msg(' (reload %s)' % str(module.__name__))
+
+ # Boom.
+ reload(module)
+ # Make sure that my traceback printing will at least be recent...
+ linecache.clearcache()
+
+ if doLog:
+ log.msg(' (cleaning %s): ' % str(module.__name__))
+
+ for clazz in classes:
+ if getattr(module, clazz.__name__) is clazz:
+ log.msg("WARNING: class %s not replaced by reload!" % reflect.qual(clazz))
+ else:
+ if doLog:
+ log.logfile.write("x")
+ log.logfile.flush()
+ clazz.__bases__ = ()
+ clazz.__dict__.clear()
+ clazz.__getattr__ = __getattr__
+ clazz.__module__ = module.__name__
+ if newclasses:
+ import gc
+ for nclass in newclasses:
+ ga = getattr(module, nclass.__name__)
+ if ga is nclass:
+ log.msg("WARNING: new-class %s not replaced by reload!" % reflect.qual(nclass))
+ else:
+ for r in gc.get_referrers(nclass):
+ if getattr(r, '__class__', None) is nclass:
+ r.__class__ = ga
+ if doLog:
+ log.msg('')
+ log.msg(' (fixing %s): ' % str(module.__name__))
+ modcount = 0
+ for mk, mod in sys.modules.items():
+ modcount = modcount + 1
+ if mod == module or mod is None:
+ continue
+
+ if not hasattr(mod, '__file__'):
+ # It's a builtin module; nothing to replace here.
+ continue
+ changed = 0
+
+ for k, v in mod.__dict__.items():
+ try:
+ hash(v)
+ except Exception:
+ continue
+ if fromOldModule(v):
+ if type(v) == types.ClassType:
+ if doLog:
+ log.logfile.write("c")
+ log.logfile.flush()
+ nv = latestClass(v)
+ else:
+ if doLog:
+ log.logfile.write("f")
+ log.logfile.flush()
+ nv = latestFunction(v)
+ changed = 1
+ setattr(mod, k, nv)
+ else:
+ # Replace bases of non-module classes just to be sure.
+ if type(v) == types.ClassType:
+ for base in v.__bases__:
+ if fromOldModule(base):
+ latestClass(v)
+ if doLog and not changed and ((modcount % 10) ==0) :
+ log.logfile.write(".")
+ log.logfile.flush()
+
+ components.ALLOW_DUPLICATES = False
+ if doLog:
+ log.msg('')
+ log.msg(' Rebuilt %s.' % str(module.__name__))
+ return module
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/reflect.py b/vendor/Twisted-10.0.0/twisted/python/reflect.py
new file mode 100644
index 0000000000..a5d55eac65
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/reflect.py
@@ -0,0 +1,812 @@
+# -*- test-case-name: twisted.test.test_reflect -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Standardized versions of various cool and/or strange things that you can do
+with Python's reflection capabilities.
+"""
+
+import sys
+import os
+import types
+import pickle
+import traceback
+import weakref
+import re
+import warnings
+import new
+try:
+ from collections import deque
+except ImportError:
+ deque = list
+
+RegexType = type(re.compile(""))
+
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+from twisted.python.util import unsignedID
+from twisted.python.deprecate import deprecated
+from twisted.python.deprecate import _fullyQualifiedName as fullyQualifiedName
+from twisted.python.versions import Version
+
+
+
+class Settable:
+ """
+ A mixin class for syntactic sugar. Lets you assign attributes by
+ calling with keyword arguments; for example, C{x(a=b,c=d,y=z)} is the
+ same as C{x.a=b;x.c=d;x.y=z}. The most useful place for this is
+ where you don't want to name a variable, but you do want to set
+ some attributes; for example, C{X()(y=z,a=b)}.
+ """
+ def __init__(self, **kw):
+ self(**kw)
+
+ def __call__(self,**kw):
+ for key,val in kw.items():
+ setattr(self,key,val)
+ return self
+
+
+class AccessorType(type):
+ """Metaclass that generates properties automatically.
+
+ This is for Python 2.2 and up.
+
+ Using this metaclass for your class will give you explicit accessor
+ methods; a method called set_foo, will automatically create a property
+ 'foo' that uses set_foo as a setter method. Same for get_foo and del_foo.
+
+ Note that this will only work on methods that are present on class
+ creation. If you add methods after the class is defined they will not
+ automatically become properties. Likewise, class attributes will only
+ be used if they are present upon class creation, and no getter function
+ was set - if a getter is present, the class attribute will be ignored.
+
+ This is a 2.2-only alternative to the Accessor mixin - just set in your
+ class definition::
+
+ __metaclass__ = AccessorType
+
+ """
+
+ def __init__(self, name, bases, d):
+ type.__init__(self, name, bases, d)
+ accessors = {}
+ prefixs = ["get_", "set_", "del_"]
+ for k in d.keys():
+ v = getattr(self, k)
+ for i in range(3):
+ if k.startswith(prefixs[i]):
+ accessors.setdefault(k[4:], [None, None, None])[i] = v
+ for name, (getter, setter, deler) in accessors.items():
+ # create default behaviours for the property - if we leave
+ # the getter as None we won't be able to getattr, etc..
+ if getter is None:
+ if hasattr(self, name):
+ value = getattr(self, name)
+ def getter(this, value=value, name=name):
+ if name in this.__dict__:
+ return this.__dict__[name]
+ else:
+ return value
+ else:
+ def getter(this, name=name):
+ if name in this.__dict__:
+ return this.__dict__[name]
+ else:
+ raise AttributeError("no such attribute %r" % name)
+ if setter is None:
+ def setter(this, value, name=name):
+ this.__dict__[name] = value
+ if deler is None:
+ def deler(this, name=name):
+ del this.__dict__[name]
+ setattr(self, name, property(getter, setter, deler, ""))
+
+
+class PropertyAccessor(object):
+ """A mixin class for Python 2.2 that uses AccessorType.
+
+ This provides compatability with the pre-2.2 Accessor mixin, up
+ to a point.
+
+ Extending this class will give you explicit accessor methods; a
+ method called set_foo, for example, is the same as an if statement
+ in __setattr__ looking for 'foo'. Same for get_foo and del_foo.
+
+ There are also reallyDel and reallySet methods, so you can
+ override specifics in subclasses without clobbering __setattr__
+ and __getattr__, or using non-2.1 compatible code.
+
+ There is are incompatibilities with Accessor - accessor
+ methods added after class creation will *not* be detected. OTOH,
+ this method is probably way faster.
+
+ In addition, class attributes will only be used if no getter
+ was defined, and instance attributes will not override getter methods
+ whereas in original Accessor the class attribute or instance attribute
+ would override the getter method.
+ """
+ # addendum to above:
+ # The behaviour of Accessor is wrong IMHO, and I've found bugs
+ # caused by it.
+ # -- itamar
+
+ __metaclass__ = AccessorType
+
+ def reallySet(self, k, v):
+ self.__dict__[k] = v
+
+ def reallyDel(self, k):
+ del self.__dict__[k]
+
+
+class Accessor:
+ """
+ Extending this class will give you explicit accessor methods; a
+ method called C{set_foo}, for example, is the same as an if statement
+ in L{__setattr__} looking for C{'foo'}. Same for C{get_foo} and
+ C{del_foo}. There are also L{reallyDel} and L{reallySet} methods,
+ so you can override specifics in subclasses without clobbering
+ L{__setattr__} and L{__getattr__}.
+
+ This implementation is for Python 2.1.
+ """
+
+ def __setattr__(self, k,v):
+ kstring='set_%s'%k
+ if hasattr(self.__class__,kstring):
+ return getattr(self,kstring)(v)
+ else:
+ self.reallySet(k,v)
+
+ def __getattr__(self, k):
+ kstring='get_%s'%k
+ if hasattr(self.__class__,kstring):
+ return getattr(self,kstring)()
+ raise AttributeError("%s instance has no accessor for: %s" % (qual(self.__class__),k))
+
+ def __delattr__(self, k):
+ kstring='del_%s'%k
+ if hasattr(self.__class__,kstring):
+ getattr(self,kstring)()
+ return
+ self.reallyDel(k)
+
+ def reallySet(self, k,v):
+ """
+ *actually* set self.k to v without incurring side-effects.
+ This is a hook to be overridden by subclasses.
+ """
+ if k == "__dict__":
+ self.__dict__.clear()
+ self.__dict__.update(v)
+ else:
+ self.__dict__[k]=v
+
+ def reallyDel(self, k):
+ """
+ *actually* del self.k without incurring side-effects. This is a
+ hook to be overridden by subclasses.
+ """
+ del self.__dict__[k]
+
+# just in case
+OriginalAccessor = Accessor
+
+
+class Summer(Accessor):
+ """
+ Extend from this class to get the capability to maintain 'related
+ sums'. Have a tuple in your class like the following::
+
+ sums=(('amount','credit','credit_total'),
+ ('amount','debit','debit_total'))
+
+ and the 'credit_total' member of the 'credit' member of self will
+ always be incremented when the 'amount' member of self is
+ incremented, similiarly for the debit versions.
+ """
+
+ def reallySet(self, k,v):
+ "This method does the work."
+ for sum in self.sums:
+ attr=sum[0]
+ obj=sum[1]
+ objattr=sum[2]
+ if k == attr:
+ try:
+ oldval=getattr(self, attr)
+ except:
+ oldval=0
+ diff=v-oldval
+ if hasattr(self, obj):
+ ob=getattr(self,obj)
+ if ob is not None:
+ try:oldobjval=getattr(ob, objattr)
+ except:oldobjval=0.0
+ setattr(ob,objattr,oldobjval+diff)
+
+ elif k == obj:
+ if hasattr(self, attr):
+ x=getattr(self,attr)
+ setattr(self,attr,0)
+ y=getattr(self,k)
+ Accessor.reallySet(self,k,v)
+ setattr(self,attr,x)
+ Accessor.reallySet(self,y,v)
+ Accessor.reallySet(self,k,v)
+
+
+class QueueMethod:
+ """ I represent a method that doesn't exist yet."""
+ def __init__(self, name, calls):
+ self.name = name
+ self.calls = calls
+ def __call__(self, *args):
+ self.calls.append((self.name, args))
+
+
+def funcinfo(function):
+ """
+ this is more documentation for myself than useful code.
+ """
+ warnings.warn(
+ "[v2.5] Use inspect.getargspec instead of twisted.python.reflect.funcinfo",
+ DeprecationWarning,
+ stacklevel=2)
+ code=function.func_code
+ name=function.func_name
+ argc=code.co_argcount
+ argv=code.co_varnames[:argc]
+ defaults=function.func_defaults
+
+ out = []
+
+ out.append('The function %s accepts %s arguments' % (name ,argc))
+ if defaults:
+ required=argc-len(defaults)
+ out.append('It requires %s arguments' % required)
+ out.append('The arguments required are: %s' % argv[:required])
+ out.append('additional arguments are:')
+ for i in range(argc-required):
+ j=i+required
+ out.append('%s which has a default of' % (argv[j], defaults[i]))
+ return out
+
+
+ISNT=0
+WAS=1
+IS=2
+
+
+def fullFuncName(func):
+ qualName = (str(pickle.whichmodule(func, func.__name__)) + '.' + func.__name__)
+ if namedObject(qualName) is not func:
+ raise Exception("Couldn't find %s as %s." % (func, qualName))
+ return qualName
+
+
+def qual(clazz):
+ """Return full import path of a class."""
+ return clazz.__module__ + '.' + clazz.__name__
+
+
+def getcurrent(clazz):
+ assert type(clazz) == types.ClassType, 'must be a class...'
+ module = namedModule(clazz.__module__)
+ currclass = getattr(module, clazz.__name__, None)
+ if currclass is None:
+ return clazz
+ return currclass
+
+
+def getClass(obj):
+ """Return the class or type of object 'obj'.
+ Returns sensible result for oldstyle and newstyle instances and types."""
+ if hasattr(obj, '__class__'):
+ return obj.__class__
+ else:
+ return type(obj)
+
+# class graph nonsense
+
+# I should really have a better name for this...
+def isinst(inst,clazz):
+ if type(inst) != types.InstanceType or type(clazz)!= types.ClassType:
+ return isinstance(inst,clazz)
+ cl = inst.__class__
+ cl2 = getcurrent(cl)
+ clazz = getcurrent(clazz)
+ if issubclass(cl2,clazz):
+ if cl == cl2:
+ return WAS
+ else:
+ inst.__class__ = cl2
+ return IS
+ else:
+ return ISNT
+
+
+def namedModule(name):
+ """Return a module given its name."""
+ topLevel = __import__(name)
+ packages = name.split(".")[1:]
+ m = topLevel
+ for p in packages:
+ m = getattr(m, p)
+ return m
+
+
+def namedObject(name):
+ """Get a fully named module-global object.
+ """
+ classSplit = name.split('.')
+ module = namedModule('.'.join(classSplit[:-1]))
+ return getattr(module, classSplit[-1])
+
+namedClass = namedObject # backwards compat
+
+
+
+class _NoModuleFound(Exception):
+ """
+ No module was found because none exists.
+ """
+
+
+class InvalidName(ValueError):
+ """
+ The given name is not a dot-separated list of Python objects.
+ """
+
+
+class ModuleNotFound(InvalidName):
+ """
+ The module associated with the given name doesn't exist and it can't be
+ imported.
+ """
+
+
+class ObjectNotFound(InvalidName):
+ """
+ The object associated with the given name doesn't exist and it can't be
+ imported.
+ """
+
+
+def _importAndCheckStack(importName):
+ """
+ Import the given name as a module, then walk the stack to determine whether
+ the failure was the module not existing, or some code in the module (for
+ example a dependent import) failing. This can be helpful to determine
+ whether any actual application code was run. For example, to distiguish
+ administrative error (entering the wrong module name), from programmer
+ error (writing buggy code in a module that fails to import).
+
+ @raise Exception: if something bad happens. This can be any type of
+ exception, since nobody knows what loading some arbitrary code might do.
+
+ @raise _NoModuleFound: if no module was found.
+ """
+ try:
+ try:
+ return __import__(importName)
+ except ImportError:
+ excType, excValue, excTraceback = sys.exc_info()
+ while excTraceback:
+ execName = excTraceback.tb_frame.f_globals["__name__"]
+ if (execName is None or # python 2.4+, post-cleanup
+ execName == importName): # python 2.3, no cleanup
+ raise excType, excValue, excTraceback
+ excTraceback = excTraceback.tb_next
+ raise _NoModuleFound()
+ except:
+ # Necessary for cleaning up modules in 2.3.
+ sys.modules.pop(importName, None)
+ raise
+
+
+
+def namedAny(name):
+ """
+ Retrieve a Python object by its fully qualified name from the global Python
+ module namespace. The first part of the name, that describes a module,
+ will be discovered and imported. Each subsequent part of the name is
+ treated as the name of an attribute of the object specified by all of the
+ name which came before it. For example, the fully-qualified name of this
+ object is 'twisted.python.reflect.namedAny'.
+
+ @type name: L{str}
+ @param name: The name of the object to return.
+
+ @raise InvalidName: If the name is an empty string, starts or ends with
+ a '.', or is otherwise syntactically incorrect.
+
+ @raise ModuleNotFound: If the name is syntactically correct but the
+ module it specifies cannot be imported because it does not appear to
+ exist.
+
+ @raise ObjectNotFound: If the name is syntactically correct, includes at
+ least one '.', but the module it specifies cannot be imported because
+ it does not appear to exist.
+
+ @raise AttributeError: If an attribute of an object along the way cannot be
+ accessed, or a module along the way is not found.
+
+ @return: the Python object identified by 'name'.
+ """
+ if not name:
+ raise InvalidName('Empty module name')
+
+ names = name.split('.')
+
+ # if the name starts or ends with a '.' or contains '..', the __import__
+ # will raise an 'Empty module name' error. This will provide a better error
+ # message.
+ if '' in names:
+ raise InvalidName(
+ "name must be a string giving a '.'-separated list of Python "
+ "identifiers, not %r" % (name,))
+
+ topLevelPackage = None
+ moduleNames = names[:]
+ while not topLevelPackage:
+ if moduleNames:
+ trialname = '.'.join(moduleNames)
+ try:
+ topLevelPackage = _importAndCheckStack(trialname)
+ except _NoModuleFound:
+ moduleNames.pop()
+ else:
+ if len(names) == 1:
+ raise ModuleNotFound("No module named %r" % (name,))
+ else:
+ raise ObjectNotFound('%r does not name an object' % (name,))
+
+ obj = topLevelPackage
+ for n in names[1:]:
+ obj = getattr(obj, n)
+
+ return obj
+
+
+
+def macro(name, filename, source, **identifiers):
+ """macro(name, source, **identifiers)
+
+ This allows you to create macro-like behaviors in python.
+ """
+ if not identifiers.has_key('name'):
+ identifiers['name'] = name
+ source = source % identifiers
+ codeplace = "<%s (macro)>" % filename
+ code = compile(source, codeplace, 'exec')
+
+ # shield your eyes!
+ sm = sys.modules
+ tprm = "twisted.python.reflect.macros"
+ if not sm.has_key(tprm):
+ macros = new.module(tprm)
+ sm[tprm] = macros
+ macros.count = 0
+ macros = sm[tprm]
+ macros.count += 1
+ macroname = 'macro_' + str(macros.count)
+ tprmm = tprm + '.' + macroname
+ mymod = new.module(tprmm)
+ sys.modules[tprmm] = mymod
+ setattr(macros, macroname, mymod)
+ dict = mymod.__dict__
+
+ # Before we go on, I guess I should explain why I just did that. Basically
+ # it's a gross hack to get epydoc to work right, but the general idea is
+ # that it will be a useful aid in debugging in _any_ app which expects
+ # sys.modules to have the same globals as some function. For example, it
+ # would be useful if you were foolishly trying to pickle a wrapped function
+ # directly from a class that had been hooked.
+
+ exec code in dict, dict
+ return dict[name]
+macro = deprecated(Version("Twisted", 8, 2, 0))(macro)
+
+
+
+def _determineClass(x):
+ try:
+ return x.__class__
+ except:
+ return type(x)
+
+
+
+def _determineClassName(x):
+ c = _determineClass(x)
+ try:
+ return c.__name__
+ except:
+ try:
+ return str(c)
+ except:
+ return '<BROKEN CLASS AT 0x%x>' % unsignedID(c)
+
+
+
+def _safeFormat(formatter, o):
+ """
+ Helper function for L{safe_repr} and L{safe_str}.
+ """
+ try:
+ return formatter(o)
+ except:
+ io = StringIO()
+ traceback.print_exc(file=io)
+ className = _determineClassName(o)
+ tbValue = io.getvalue()
+ return "<%s instance at 0x%x with %s error:\n %s>" % (
+ className, unsignedID(o), formatter.__name__, tbValue)
+
+
+
+def safe_repr(o):
+ """
+ safe_repr(anything) -> string
+
+ Returns a string representation of an object, or a string containing a
+ traceback, if that object's __repr__ raised an exception.
+ """
+ return _safeFormat(repr, o)
+
+
+
+def safe_str(o):
+ """
+ safe_str(anything) -> string
+
+ Returns a string representation of an object, or a string containing a
+ traceback, if that object's __str__ raised an exception.
+ """
+ return _safeFormat(str, o)
+
+
+
+##the following were factored out of usage
+
+def allYourBase(classObj, baseClass=None):
+ """allYourBase(classObj, baseClass=None) -> list of all base
+ classes that are subclasses of baseClass, unless it is None,
+ in which case all bases will be added.
+ """
+ l = []
+ accumulateBases(classObj, l, baseClass)
+ return l
+
+
+def accumulateBases(classObj, l, baseClass=None):
+ for base in classObj.__bases__:
+ if baseClass is None or issubclass(base, baseClass):
+ l.append(base)
+ accumulateBases(base, l, baseClass)
+
+
+def prefixedMethodNames(classObj, prefix):
+ """A list of method names with a given prefix in a given class.
+ """
+ dct = {}
+ addMethodNamesToDict(classObj, dct, prefix)
+ return dct.keys()
+
+
+def addMethodNamesToDict(classObj, dict, prefix, baseClass=None):
+ """
+ addMethodNamesToDict(classObj, dict, prefix, baseClass=None) -> dict
+ this goes through 'classObj' (and its bases) and puts method names
+ starting with 'prefix' in 'dict' with a value of 1. if baseClass isn't
+ None, methods will only be added if classObj is-a baseClass
+
+ If the class in question has the methods 'prefix_methodname' and
+ 'prefix_methodname2', the resulting dict should look something like:
+ {"methodname": 1, "methodname2": 1}.
+ """
+ for base in classObj.__bases__:
+ addMethodNamesToDict(base, dict, prefix, baseClass)
+
+ if baseClass is None or baseClass in classObj.__bases__:
+ for name, method in classObj.__dict__.items():
+ optName = name[len(prefix):]
+ if ((type(method) is types.FunctionType)
+ and (name[:len(prefix)] == prefix)
+ and (len(optName))):
+ dict[optName] = 1
+
+
+def prefixedMethods(obj, prefix=''):
+ """A list of methods with a given prefix on a given instance.
+ """
+ dct = {}
+ accumulateMethods(obj, dct, prefix)
+ return dct.values()
+
+
+def accumulateMethods(obj, dict, prefix='', curClass=None):
+ """accumulateMethods(instance, dict, prefix)
+ I recurse through the bases of instance.__class__, and add methods
+ beginning with 'prefix' to 'dict', in the form of
+ {'methodname':*instance*method_object}.
+ """
+ if not curClass:
+ curClass = obj.__class__
+ for base in curClass.__bases__:
+ accumulateMethods(obj, dict, prefix, base)
+
+ for name, method in curClass.__dict__.items():
+ optName = name[len(prefix):]
+ if ((type(method) is types.FunctionType)
+ and (name[:len(prefix)] == prefix)
+ and (len(optName))):
+ dict[optName] = getattr(obj, name)
+
+
+def accumulateClassDict(classObj, attr, adict, baseClass=None):
+ """Accumulate all attributes of a given name in a class heirarchy into a single dictionary.
+
+ Assuming all class attributes of this name are dictionaries.
+ If any of the dictionaries being accumulated have the same key, the
+ one highest in the class heirarchy wins.
+ (XXX: If \"higest\" means \"closest to the starting class\".)
+
+ Ex::
+
+ | class Soy:
+ | properties = {\"taste\": \"bland\"}
+ |
+ | class Plant:
+ | properties = {\"colour\": \"green\"}
+ |
+ | class Seaweed(Plant):
+ | pass
+ |
+ | class Lunch(Soy, Seaweed):
+ | properties = {\"vegan\": 1 }
+ |
+ | dct = {}
+ |
+ | accumulateClassDict(Lunch, \"properties\", dct)
+ |
+ | print dct
+
+ {\"taste\": \"bland\", \"colour\": \"green\", \"vegan\": 1}
+ """
+ for base in classObj.__bases__:
+ accumulateClassDict(base, attr, adict)
+ if baseClass is None or baseClass in classObj.__bases__:
+ adict.update(classObj.__dict__.get(attr, {}))
+
+
+def accumulateClassList(classObj, attr, listObj, baseClass=None):
+ """Accumulate all attributes of a given name in a class heirarchy into a single list.
+
+ Assuming all class attributes of this name are lists.
+ """
+ for base in classObj.__bases__:
+ accumulateClassList(base, attr, listObj)
+ if baseClass is None or baseClass in classObj.__bases__:
+ listObj.extend(classObj.__dict__.get(attr, []))
+
+
+def isSame(a, b):
+ return (a is b)
+
+
+def isLike(a, b):
+ return (a == b)
+
+
+def modgrep(goal):
+ return objgrep(sys.modules, goal, isLike, 'sys.modules')
+
+
+def isOfType(start, goal):
+ return ((type(start) is goal) or
+ (isinstance(start, types.InstanceType) and
+ start.__class__ is goal))
+
+
+def findInstances(start, t):
+ return objgrep(start, t, isOfType)
+
+
+def objgrep(start, goal, eq=isLike, path='', paths=None, seen=None, showUnknowns=0, maxDepth=None):
+ '''An insanely CPU-intensive process for finding stuff.
+ '''
+ if paths is None:
+ paths = []
+ if seen is None:
+ seen = {}
+ if eq(start, goal):
+ paths.append(path)
+ if id(start) in seen:
+ if seen[id(start)] is start:
+ return
+ if maxDepth is not None:
+ if maxDepth == 0:
+ return
+ maxDepth -= 1
+ seen[id(start)] = start
+ if isinstance(start, types.DictionaryType):
+ for k, v in start.items():
+ objgrep(k, goal, eq, path+'{'+repr(v)+'}', paths, seen, showUnknowns, maxDepth)
+ objgrep(v, goal, eq, path+'['+repr(k)+']', paths, seen, showUnknowns, maxDepth)
+ elif isinstance(start, (list, tuple, deque)):
+ for idx in xrange(len(start)):
+ objgrep(start[idx], goal, eq, path+'['+str(idx)+']', paths, seen, showUnknowns, maxDepth)
+ elif isinstance(start, types.MethodType):
+ objgrep(start.im_self, goal, eq, path+'.im_self', paths, seen, showUnknowns, maxDepth)
+ objgrep(start.im_func, goal, eq, path+'.im_func', paths, seen, showUnknowns, maxDepth)
+ objgrep(start.im_class, goal, eq, path+'.im_class', paths, seen, showUnknowns, maxDepth)
+ elif hasattr(start, '__dict__'):
+ for k, v in start.__dict__.items():
+ objgrep(v, goal, eq, path+'.'+k, paths, seen, showUnknowns, maxDepth)
+ if isinstance(start, types.InstanceType):
+ objgrep(start.__class__, goal, eq, path+'.__class__', paths, seen, showUnknowns, maxDepth)
+ elif isinstance(start, weakref.ReferenceType):
+ objgrep(start(), goal, eq, path+'()', paths, seen, showUnknowns, maxDepth)
+ elif (isinstance(start, types.StringTypes+
+ (types.IntType, types.FunctionType,
+ types.BuiltinMethodType, RegexType, types.FloatType,
+ types.NoneType, types.FileType)) or
+ type(start).__name__ in ('wrapper_descriptor', 'method_descriptor',
+ 'member_descriptor', 'getset_descriptor')):
+ pass
+ elif showUnknowns:
+ print 'unknown type', type(start), start
+ return paths
+
+
+def filenameToModuleName(fn):
+ """
+ Convert a name in the filesystem to the name of the Python module it is.
+
+ This is agressive about getting a module name back from a file; it will
+ always return a string. Agressive means 'sometimes wrong'; it won't look
+ at the Python path or try to do any error checking: don't use this method
+ unless you already know that the filename you're talking about is a Python
+ module.
+ """
+ fullName = os.path.abspath(fn)
+ base = os.path.basename(fn)
+ if not base:
+ # this happens when fn ends with a path separator, just skit it
+ base = os.path.basename(fn[:-1])
+ modName = os.path.splitext(base)[0]
+ while 1:
+ fullName = os.path.dirname(fullName)
+ if os.path.exists(os.path.join(fullName, "__init__.py")):
+ modName = "%s.%s" % (os.path.basename(fullName), modName)
+ else:
+ break
+ return modName
+
+
+
+__all__ = [
+ 'InvalidName', 'ModuleNotFound', 'ObjectNotFound',
+
+ 'ISNT', 'WAS', 'IS',
+
+ 'Settable', 'AccessorType', 'PropertyAccessor', 'Accessor', 'Summer',
+ 'QueueMethod', 'OriginalAccessor',
+
+ 'funcinfo', 'fullFuncName', 'qual', 'getcurrent', 'getClass', 'isinst',
+ 'namedModule', 'namedObject', 'namedClass', 'namedAny', 'macro',
+ 'safe_repr', 'safe_str', 'allYourBase', 'accumulateBases',
+ 'prefixedMethodNames', 'addMethodNamesToDict', 'prefixedMethods',
+ 'accumulateClassDict', 'accumulateClassList', 'isSame', 'isLike',
+ 'modgrep', 'isOfType', 'findInstances', 'objgrep', 'filenameToModuleName',
+ 'fullyQualifiedName']
diff --git a/vendor/Twisted-10.0.0/twisted/python/release.py b/vendor/Twisted-10.0.0/twisted/python/release.py
new file mode 100644
index 0000000000..1ff83a8557
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/release.py
@@ -0,0 +1,57 @@
+"""
+A release-automation toolkit.
+
+Don't use this outside of Twisted.
+
+Maintainer: Christopher Armstrong
+"""
+
+import os
+import re
+
+
+# errors
+
+class DirectoryExists(OSError):
+ """Some directory exists when it shouldn't."""
+ pass
+
+
+
+class DirectoryDoesntExist(OSError):
+ """Some directory doesn't exist when it should."""
+ pass
+
+
+
+class CommandFailed(OSError):
+ pass
+
+
+
+# utilities
+
+def sh(command, null=True, prompt=False):
+ """
+ I'll try to execute `command', and if `prompt' is true, I'll
+ ask before running it. If the command returns something other
+ than 0, I'll raise CommandFailed(command).
+ """
+ print "--$", command
+
+ if prompt:
+ if raw_input("run ?? ").startswith('n'):
+ return
+ if null:
+ command = "%s > /dev/null" % command
+ if os.system(command) != 0:
+ raise CommandFailed(command)
+
+
+
+def runChdirSafe(f, *args, **kw):
+ origdir = os.path.abspath('.')
+ try:
+ return f(*args, **kw)
+ finally:
+ os.chdir(origdir)
diff --git a/vendor/Twisted-10.0.0/twisted/python/roots.py b/vendor/Twisted-10.0.0/twisted/python/roots.py
new file mode 100644
index 0000000000..61c0a3353f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/roots.py
@@ -0,0 +1,248 @@
+# -*- test-case-name: twisted.test.test_roots -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Twisted Python Roots: an abstract hierarchy representation for Twisted.
+
+Maintainer: Glyph Lefkowitz
+"""
+
+# System imports
+import types
+from twisted.python import reflect
+
+class NotSupportedError(NotImplementedError):
+ """
+ An exception meaning that the tree-manipulation operation
+ you're attempting to perform is not supported.
+ """
+
+
+class Request:
+ """I am an abstract representation of a request for an entity.
+
+ I also function as the response. The request is responded to by calling
+ self.write(data) until there is no data left and then calling
+ self.finish().
+ """
+ # This attribute should be set to the string name of the protocol being
+ # responded to (e.g. HTTP or FTP)
+ wireProtocol = None
+ def write(self, data):
+ """Add some data to the response to this request.
+ """
+ raise NotImplementedError("%s.write" % reflect.qual(self.__class__))
+
+ def finish(self):
+ """The response to this request is finished; flush all data to the network stream.
+ """
+ raise NotImplementedError("%s.finish" % reflect.qual(self.__class__))
+
+
+class Entity:
+ """I am a terminal object in a hierarchy, with no children.
+
+ I represent a null interface; certain non-instance objects (strings and
+ integers, notably) are Entities.
+
+ Methods on this class are suggested to be implemented, but are not
+ required, and will be emulated on a per-protocol basis for types which do
+ not handle them.
+ """
+ def render(self, request):
+ """
+ I produce a stream of bytes for the request, by calling request.write()
+ and request.finish().
+ """
+ raise NotImplementedError("%s.render" % reflect.qual(self.__class__))
+
+
+class Collection:
+ """I represent a static collection of entities.
+
+ I contain methods designed to represent collections that can be dynamically
+ created.
+ """
+
+ def __init__(self, entities=None):
+ """Initialize me.
+ """
+ if entities is not None:
+ self.entities = entities
+ else:
+ self.entities = {}
+
+ def getStaticEntity(self, name):
+ """Get an entity that was added to me using putEntity.
+
+ This method will return 'None' if it fails.
+ """
+ return self.entities.get(name)
+
+ def getDynamicEntity(self, name, request):
+ """Subclass this to generate an entity on demand.
+
+ This method should return 'None' if it fails.
+ """
+
+ def getEntity(self, name, request):
+ """Retrieve an entity from me.
+
+ I will first attempt to retrieve an entity statically; static entities
+ will obscure dynamic ones. If that fails, I will retrieve the entity
+ dynamically.
+
+ If I cannot retrieve an entity, I will return 'None'.
+ """
+ ent = self.getStaticEntity(name)
+ if ent is not None:
+ return ent
+ ent = self.getDynamicEntity(name, request)
+ if ent is not None:
+ return ent
+ return None
+
+ def putEntity(self, name, entity):
+ """Store a static reference on 'name' for 'entity'.
+
+ Raises a KeyError if the operation fails.
+ """
+ self.entities[name] = entity
+
+ def delEntity(self, name):
+ """Remove a static reference for 'name'.
+
+ Raises a KeyError if the operation fails.
+ """
+ del self.entities[name]
+
+ def storeEntity(self, name, request):
+ """Store an entity for 'name', based on the content of 'request'.
+ """
+ raise NotSupportedError("%s.storeEntity" % reflect.qual(self.__class__))
+
+ def removeEntity(self, name, request):
+ """Remove an entity for 'name', based on the content of 'request'.
+ """
+ raise NotSupportedError("%s.removeEntity" % reflect.qual(self.__class__))
+
+ def listStaticEntities(self):
+ """Retrieve a list of all name, entity pairs that I store references to.
+
+ See getStaticEntity.
+ """
+ return self.entities.items()
+
+ def listDynamicEntities(self, request):
+ """A list of all name, entity that I can generate on demand.
+
+ See getDynamicEntity.
+ """
+ return []
+
+ def listEntities(self, request):
+ """Retrieve a list of all name, entity pairs I contain.
+
+ See getEntity.
+ """
+ return self.listStaticEntities() + self.listDynamicEntities(request)
+
+ def listStaticNames(self):
+ """Retrieve a list of the names of entities that I store references to.
+
+ See getStaticEntity.
+ """
+ return self.entities.keys()
+
+
+ def listDynamicNames(self):
+ """Retrieve a list of the names of entities that I store references to.
+
+ See getDynamicEntity.
+ """
+ return []
+
+
+ def listNames(self, request):
+ """Retrieve a list of all names for entities that I contain.
+
+ See getEntity.
+ """
+ return self.listStaticNames()
+
+
+class ConstraintViolation(Exception):
+ """An exception raised when a constraint is violated.
+ """
+
+
+class Constrained(Collection):
+ """A collection that has constraints on its names and/or entities."""
+
+ def nameConstraint(self, name):
+ """A method that determines whether an entity may be added to me with a given name.
+
+ If the constraint is satisfied, return 1; if the constraint is not
+ satisfied, either return 0 or raise a descriptive ConstraintViolation.
+ """
+ return 1
+
+ def entityConstraint(self, entity):
+ """A method that determines whether an entity may be added to me.
+
+ If the constraint is satisfied, return 1; if the constraint is not
+ satisfied, either return 0 or raise a descriptive ConstraintViolation.
+ """
+ return 1
+
+ def reallyPutEntity(self, name, entity):
+ Collection.putEntity(self, name, entity)
+
+ def putEntity(self, name, entity):
+ """Store an entity if it meets both constraints.
+
+ Otherwise raise a ConstraintViolation.
+ """
+ if self.nameConstraint(name):
+ if self.entityConstraint(entity):
+ self.reallyPutEntity(name, entity)
+ else:
+ raise ConstraintViolation("Entity constraint violated.")
+ else:
+ raise ConstraintViolation("Name constraint violated.")
+
+
+class Locked(Constrained):
+ """A collection that can be locked from adding entities."""
+
+ locked = 0
+
+ def lock(self):
+ self.locked = 1
+
+ def entityConstraint(self, entity):
+ return not self.locked
+
+
+class Homogenous(Constrained):
+ """A homogenous collection of entities.
+
+ I will only contain entities that are an instance of the class or type
+ specified by my 'entityType' attribute.
+ """
+
+ entityType = types.InstanceType
+
+ def entityConstraint(self, entity):
+ if isinstance(entity, self.entityType):
+ return 1
+ else:
+ raise ConstraintViolation("%s of incorrect type (%s)" %
+ (entity, self.entityType))
+
+ def getNameType(self):
+ return "Name"
+
+ def getEntityType(self):
+ return self.entityType.__name__
diff --git a/vendor/Twisted-10.0.0/twisted/python/runtime.py b/vendor/Twisted-10.0.0/twisted/python/runtime.py
new file mode 100644
index 0000000000..9e5b7a503c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/runtime.py
@@ -0,0 +1,97 @@
+# -*- test-case-name: twisted.python.test.test_runtime -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+# System imports
+import os
+import sys
+import time
+import imp
+
+
+def shortPythonVersion():
+ hv = sys.hexversion
+ major = (hv & 0xff000000L) >> 24
+ minor = (hv & 0x00ff0000L) >> 16
+ teeny = (hv & 0x0000ff00L) >> 8
+ return "%s.%s.%s" % (major,minor,teeny)
+
+knownPlatforms = {
+ 'nt': 'win32',
+ 'ce': 'win32',
+ 'posix': 'posix',
+ 'java': 'java',
+ 'org.python.modules.os': 'java',
+ }
+
+_timeFunctions = {
+ #'win32': time.clock,
+ 'win32': time.time,
+ }
+
+class Platform:
+ """Gives us information about the platform we're running on"""
+
+ type = knownPlatforms.get(os.name)
+ seconds = staticmethod(_timeFunctions.get(type, time.time))
+
+ def __init__(self, name=None):
+ if name is not None:
+ self.type = knownPlatforms.get(name)
+ self.seconds = _timeFunctions.get(self.type, time.time)
+
+ def isKnown(self):
+ """Do we know about this platform?"""
+ return self.type != None
+
+ def getType(self):
+ """Return 'posix', 'win32' or 'java'"""
+ return self.type
+
+ def isMacOSX(self):
+ """Return if we are runnng on Mac OS X."""
+ return sys.platform == "darwin"
+
+ def isWinNT(self):
+ """Are we running in Windows NT?"""
+ if self.getType() == 'win32':
+ import _winreg
+ try:
+ k=_winreg.OpenKeyEx(_winreg.HKEY_LOCAL_MACHINE,
+ r'Software\Microsoft\Windows NT\CurrentVersion')
+ _winreg.QueryValueEx(k, 'SystemRoot')
+ return 1
+ except WindowsError:
+ return 0
+ # not windows NT
+ return 0
+
+ def isWindows(self):
+ return self.getType() == 'win32'
+
+
+ def isVista(self):
+ """
+ Check if current platform is Windows Vista or Windows Server 2008.
+
+ @return: C{True} if the current platform has been detected as Vista
+ @rtype: C{bool}
+ """
+ if getattr(sys, "getwindowsversion", None) is not None:
+ return sys.getwindowsversion()[0] == 6
+ else:
+ return False
+
+
+ def supportsThreads(self):
+ """Can threads be created?
+ """
+ try:
+ return imp.find_module('thread')[0] is None
+ except ImportError:
+ return False
+
+platform = Platform()
+platformType = platform.getType()
+seconds = platform.seconds
diff --git a/vendor/Twisted-10.0.0/twisted/python/shortcut.py b/vendor/Twisted-10.0.0/twisted/python/shortcut.py
new file mode 100644
index 0000000000..5465d86bba
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/shortcut.py
@@ -0,0 +1,76 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Creation of Windows shortcuts.
+
+Requires win32all.
+"""
+
+from win32com.shell import shell
+import pythoncom
+import os
+
+
+def open(filename):
+ """Open an existing shortcut for reading.
+
+ @return: The shortcut object
+ @rtype: Shortcut
+ """
+ sc=Shortcut()
+ sc.load(filename)
+ return sc
+
+
+class Shortcut:
+ """A shortcut on Win32.
+ >>> sc=Shortcut(path, arguments, description, workingdir, iconpath, iconidx)
+ @param path: Location of the target
+ @param arguments: If path points to an executable, optional arguments to
+ pass
+ @param description: Human-readable decription of target
+ @param workingdir: Directory from which target is launched
+ @param iconpath: Filename that contains an icon for the shortcut
+ @param iconidx: If iconpath is set, optional index of the icon desired
+ """
+
+ def __init__(self,
+ path=None,
+ arguments=None,
+ description=None,
+ workingdir=None,
+ iconpath=None,
+ iconidx=0):
+ self._base = pythoncom.CoCreateInstance(
+ shell.CLSID_ShellLink, None,
+ pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink
+ )
+ data = map(None,
+ ['"%s"' % os.path.abspath(path), arguments, description,
+ os.path.abspath(workingdir), os.path.abspath(iconpath)],
+ ("SetPath", "SetArguments", "SetDescription",
+ "SetWorkingDirectory") )
+ for value, function in data:
+ if value and function:
+ # call function on each non-null value
+ getattr(self, function)(value)
+ if iconpath:
+ self.SetIconLocation(iconpath, iconidx)
+
+ def load( self, filename ):
+ """Read a shortcut file from disk."""
+ self._base.QueryInterface(pythoncom.IID_IPersistFile).Load(filename)
+
+ def save( self, filename ):
+ """Write the shortcut to disk.
+
+ The file should be named something.lnk.
+ """
+ self._base.QueryInterface(pythoncom.IID_IPersistFile).Save(filename, 0)
+
+ def __getattr__( self, name ):
+ if name != "_base":
+ return getattr(self._base, name)
+ raise AttributeError, "%s instance has no attribute %s" % \
+ (self.__class__.__name__, name)
diff --git a/vendor/Twisted-10.0.0/twisted/python/syslog.py b/vendor/Twisted-10.0.0/twisted/python/syslog.py
new file mode 100644
index 0000000000..349c3ca4bd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/syslog.py
@@ -0,0 +1,107 @@
+# -*- test-case-name: twisted.python.test.test_syslog -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Classes and utility functions for integrating Twisted and syslog.
+
+You probably want to call L{startLogging}.
+"""
+
+syslog = __import__('syslog')
+
+from twisted.python import log
+
+# These defaults come from the Python 2.3 syslog docs.
+DEFAULT_OPTIONS = 0
+DEFAULT_FACILITY = syslog.LOG_USER
+
+
+
+class SyslogObserver:
+ """
+ A log observer for logging to syslog.
+
+ See L{twisted.python.log} for context.
+
+ This logObserver will automatically use LOG_ALERT priority for logged
+ failures (such as from C{log.err()}), but you can use any priority and
+ facility by setting the 'C{syslogPriority}' and 'C{syslogFacility}' keys in
+ the event dict.
+ """
+ openlog = syslog.openlog
+ syslog = syslog.syslog
+
+ def __init__(self, prefix, options=DEFAULT_OPTIONS,
+ facility=DEFAULT_FACILITY):
+ """
+ @type prefix: C{str}
+ @param prefix: The syslog prefix to use.
+
+ @type options: C{int}
+ @param options: A bitvector represented as an integer of the syslog
+ options to use.
+
+ @type facility: C{int}
+ @param facility: An indication to the syslog daemon of what sort of
+ program this is (essentially, an additional arbitrary metadata
+ classification for messages sent to syslog by this observer).
+ """
+ self.openlog(prefix, options, facility)
+
+
+ def emit(self, eventDict):
+ """
+ Send a message event to the I{syslog}.
+
+ @param eventDict: The event to send. If it has no C{'message'} key, it
+ will be ignored. Otherwise, if it has C{'syslogPriority'} and/or
+ C{'syslogFacility'} keys, these will be used as the syslog priority
+ and facility. If it has no C{'syslogPriority'} key but a true
+ value for the C{'isError'} key, the B{LOG_ALERT} priority will be
+ used; if it has a false value for C{'isError'}, B{LOG_INFO} will be
+ used. If the C{'message'} key is multiline, each line will be sent
+ to the syslog separately.
+ """
+ # Figure out what the message-text is.
+ text = log.textFromEventDict(eventDict)
+ if text is None:
+ return
+
+ # Figure out what syslog parameters we might need to use.
+ priority = syslog.LOG_INFO
+ facility = 0
+ if eventDict['isError']:
+ priority = syslog.LOG_ALERT
+ if 'syslogPriority' in eventDict:
+ priority = int(eventDict['syslogPriority'])
+ if 'syslogFacility' in eventDict:
+ facility = int(eventDict['syslogFacility'])
+
+ # Break the message up into lines and send them.
+ lines = text.split('\n')
+ while lines[-1:] == ['']:
+ lines.pop()
+
+ firstLine = True
+ for line in lines:
+ if firstLine:
+ firstLine = False
+ else:
+ line = '\t' + line
+ self.syslog(priority | facility,
+ '[%s] %s' % (eventDict['system'], line))
+
+
+
+def startLogging(prefix='Twisted', options=DEFAULT_OPTIONS,
+ facility=DEFAULT_FACILITY, setStdout=1):
+ """
+ Send all Twisted logging output to syslog from now on.
+
+ The prefix, options and facility arguments are passed to
+ C{syslog.openlog()}, see the Python syslog documentation for details. For
+ other parameters, see L{twisted.python.log.startLoggingWithObserver}.
+ """
+ obs = SyslogObserver(prefix, options, facility)
+ log.startLoggingWithObserver(obs.emit, setStdout=setStdout)
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/__init__.py b/vendor/Twisted-10.0.0/twisted/python/test/__init__.py
new file mode 100644
index 0000000000..cfdc40d132
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/__init__.py
@@ -0,0 +1,3 @@
+"""
+Unit tests for L{twisted.python}.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/deprecatedattributes.py b/vendor/Twisted-10.0.0/twisted/python/test/deprecatedattributes.py
new file mode 100644
index 0000000000..b94c36175d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/deprecatedattributes.py
@@ -0,0 +1,21 @@
+# Import reflect first, so that circular imports (between deprecate and
+# reflect) don't cause headaches.
+import twisted.python.reflect
+from twisted.python.versions import Version
+from twisted.python.deprecate import deprecatedModuleAttribute
+
+
+# Known module-level attributes.
+DEPRECATED_ATTRIBUTE = 42
+ANOTHER_ATTRIBUTE = 'hello'
+
+
+version = Version('Twisted', 8, 0, 0)
+message = 'Oh noes!'
+
+
+deprecatedModuleAttribute(
+ version,
+ message,
+ __name__,
+ 'DEPRECATED_ATTRIBUTE')
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_components.py b/vendor/Twisted-10.0.0/twisted/python/test/test_components.py
new file mode 100644
index 0000000000..4eb6bf52e7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_components.py
@@ -0,0 +1,741 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Test cases for Twisted component architecture.
+"""
+
+from zope.interface import Interface, implements, Attribute
+
+from twisted.trial import unittest
+from twisted.python import components
+from twisted.python.components import proxyForInterface
+
+
+class InterfacesTestCase(unittest.TestCase):
+ """Test interfaces."""
+
+class Compo(components.Componentized):
+ num = 0
+ def inc(self):
+ self.num = self.num + 1
+ return self.num
+
+class IAdept(Interface):
+ def adaptorFunc():
+ raise NotImplementedError()
+
+class IElapsed(Interface):
+ def elapsedFunc():
+ """
+ 1!
+ """
+
+class Adept(components.Adapter):
+ implements(IAdept)
+ def __init__(self, orig):
+ self.original = orig
+ self.num = 0
+ def adaptorFunc(self):
+ self.num = self.num + 1
+ return self.num, self.original.inc()
+
+class Elapsed(components.Adapter):
+ implements(IElapsed)
+ def elapsedFunc(self):
+ return 1
+
+components.registerAdapter(Adept, Compo, IAdept)
+components.registerAdapter(Elapsed, Compo, IElapsed)
+
+class AComp(components.Componentized):
+ pass
+class BComp(AComp):
+ pass
+class CComp(BComp):
+ pass
+
+class ITest(Interface):
+ pass
+class ITest2(Interface):
+ pass
+class ITest3(Interface):
+ pass
+class ITest4(Interface):
+ pass
+class Test(components.Adapter):
+ implements(ITest, ITest3, ITest4)
+ def __init__(self, orig):
+ pass
+class Test2:
+ implements(ITest2)
+ temporaryAdapter = 1
+ def __init__(self, orig):
+ pass
+
+components.registerAdapter(Test, AComp, ITest)
+components.registerAdapter(Test, AComp, ITest3)
+components.registerAdapter(Test2, AComp, ITest2)
+
+
+
+
+class ComponentizedTestCase(unittest.TestCase):
+ """Simple test case for caching in Componentized.
+ """
+ def testComponentized(self):
+ c = Compo()
+ assert c.getComponent(IAdept).adaptorFunc() == (1, 1)
+ assert c.getComponent(IAdept).adaptorFunc() == (2, 2)
+ assert IElapsed(IAdept(c)).elapsedFunc() == 1
+
+ def testInheritanceAdaptation(self):
+ c = CComp()
+ co1 = c.getComponent(ITest)
+ co2 = c.getComponent(ITest)
+ co3 = c.getComponent(ITest2)
+ co4 = c.getComponent(ITest2)
+ assert co1 is co2
+ assert co3 is not co4
+ c.removeComponent(co1)
+ co5 = c.getComponent(ITest)
+ co6 = c.getComponent(ITest)
+ assert co5 is co6
+ assert co1 is not co5
+
+ def testMultiAdapter(self):
+ c = CComp()
+ co1 = c.getComponent(ITest)
+ co2 = c.getComponent(ITest2)
+ co3 = c.getComponent(ITest3)
+ co4 = c.getComponent(ITest4)
+ assert co4 == None
+ assert co1 is co3
+
+
+ def test_getComponentDefaults(self):
+ """
+ Test that a default value specified to Componentized.getComponent if
+ there is no component for the requested interface.
+ """
+ componentized = components.Componentized()
+ default = object()
+ self.assertIdentical(
+ componentized.getComponent(ITest, default),
+ default)
+ self.assertIdentical(
+ componentized.getComponent(ITest, default=default),
+ default)
+ self.assertIdentical(
+ componentized.getComponent(ITest),
+ None)
+
+
+
+class AdapterTestCase(unittest.TestCase):
+ """Test adapters."""
+
+ def testAdapterGetComponent(self):
+ o = object()
+ a = Adept(o)
+ self.assertRaises(components.CannotAdapt, ITest, a)
+ self.assertEquals(ITest(a, None), None)
+
+
+
+class IMeta(Interface):
+ pass
+
+class MetaAdder(components.Adapter):
+ implements(IMeta)
+ def add(self, num):
+ return self.original.num + num
+
+class BackwardsAdder(components.Adapter):
+ implements(IMeta)
+ def add(self, num):
+ return self.original.num - num
+
+class MetaNumber:
+ def __init__(self, num):
+ self.num = num
+
+class FakeAdder:
+ def add(self, num):
+ return num + 5
+
+class FakeNumber:
+ num = 3
+
+class ComponentNumber(components.Componentized):
+ def __init__(self):
+ self.num = 0
+ components.Componentized.__init__(self)
+
+class ComponentMeta(components.Adapter):
+ implements(IMeta)
+ def __init__(self, original):
+ components.Adapter.__init__(self, original)
+ self.num = self.original.num
+
+class ComponentAdder(ComponentMeta):
+ def add(self, num):
+ self.num += num
+ return self.num
+
+class ComponentDoubler(ComponentMeta):
+ def add(self, num):
+ self.num += (num * 2)
+ return self.original.num
+
+components.registerAdapter(MetaAdder, MetaNumber, IMeta)
+components.registerAdapter(ComponentAdder, ComponentNumber, IMeta)
+
+class IAttrX(Interface):
+ def x():
+ pass
+
+class IAttrXX(Interface):
+ def xx():
+ pass
+
+class Xcellent:
+ implements(IAttrX)
+ def x(self):
+ return 'x!'
+
+class DoubleXAdapter:
+ num = 42
+ def __init__(self, original):
+ self.original = original
+ def xx(self):
+ return (self.original.x(), self.original.x())
+ def __cmp__(self, other):
+ return cmp(self.num, other.num)
+
+components.registerAdapter(DoubleXAdapter, IAttrX, IAttrXX)
+
+class TestMetaInterface(unittest.TestCase):
+
+ def testBasic(self):
+ n = MetaNumber(1)
+ self.assertEquals(IMeta(n).add(1), 2)
+
+ def testComponentizedInteraction(self):
+ c = ComponentNumber()
+ IMeta(c).add(1)
+ IMeta(c).add(1)
+ self.assertEquals(IMeta(c).add(1), 3)
+
+ def testAdapterWithCmp(self):
+ # Make sure that a __cmp__ on an adapter doesn't break anything
+ xx = IAttrXX(Xcellent())
+ self.assertEqual(('x!', 'x!'), xx.xx())
+
+
+class RegistrationTestCase(unittest.TestCase):
+ """
+ Tests for adapter registration.
+ """
+ def _registerAdapterForClassOrInterface(self, original):
+ adapter = lambda o: None
+ class TheInterface(Interface):
+ pass
+ components.registerAdapter(adapter, original, TheInterface)
+ self.assertIdentical(
+ components.getAdapterFactory(original, TheInterface, None),
+ adapter)
+
+
+ def test_registerAdapterForClass(self):
+ """
+ Test that an adapter from a class can be registered and then looked
+ up.
+ """
+ class TheOriginal(object):
+ pass
+ return self._registerAdapterForClassOrInterface(TheOriginal)
+
+
+ def test_registerAdapterForInterface(self):
+ """
+ Test that an adapter from an interface can be registered and then
+ looked up.
+ """
+ class TheOriginal(Interface):
+ pass
+ return self._registerAdapterForClassOrInterface(TheOriginal)
+
+
+ def _duplicateAdapterForClassOrInterface(self, original):
+ firstAdapter = lambda o: False
+ secondAdapter = lambda o: True
+ class TheInterface(Interface):
+ pass
+ components.registerAdapter(firstAdapter, original, TheInterface)
+ self.assertRaises(
+ ValueError,
+ components.registerAdapter,
+ secondAdapter, original, TheInterface)
+ # Make sure that the original adapter is still around as well
+ self.assertIdentical(
+ components.getAdapterFactory(original, TheInterface, None),
+ firstAdapter)
+
+
+ def test_duplicateAdapterForClass(self):
+ """
+ Test that attempting to register a second adapter from a class
+ raises the appropriate exception.
+ """
+ class TheOriginal(object):
+ pass
+ return self._duplicateAdapterForClassOrInterface(TheOriginal)
+
+
+ def test_duplicateAdapterForInterface(self):
+ """
+ Test that attempting to register a second adapter from an interface
+ raises the appropriate exception.
+ """
+ class TheOriginal(Interface):
+ pass
+ return self._duplicateAdapterForClassOrInterface(TheOriginal)
+
+
+ def _duplicateAdapterForClassOrInterfaceAllowed(self, original):
+ firstAdapter = lambda o: False
+ secondAdapter = lambda o: True
+ class TheInterface(Interface):
+ pass
+ components.registerAdapter(firstAdapter, original, TheInterface)
+ components.ALLOW_DUPLICATES = True
+ try:
+ components.registerAdapter(secondAdapter, original, TheInterface)
+ self.assertIdentical(
+ components.getAdapterFactory(original, TheInterface, None),
+ secondAdapter)
+ finally:
+ components.ALLOW_DUPLICATES = False
+
+ # It should be rejected again at this point
+ self.assertRaises(
+ ValueError,
+ components.registerAdapter,
+ firstAdapter, original, TheInterface)
+
+ self.assertIdentical(
+ components.getAdapterFactory(original, TheInterface, None),
+ secondAdapter)
+
+ def test_duplicateAdapterForClassAllowed(self):
+ """
+ Test that when L{components.ALLOW_DUPLICATES} is set to a true
+ value, duplicate registrations from classes are allowed to override
+ the original registration.
+ """
+ class TheOriginal(object):
+ pass
+ return self._duplicateAdapterForClassOrInterfaceAllowed(TheOriginal)
+
+
+ def test_duplicateAdapterForInterfaceAllowed(self):
+ """
+ Test that when L{components.ALLOW_DUPLICATES} is set to a true
+ value, duplicate registrations from interfaces are allowed to
+ override the original registration.
+ """
+ class TheOriginal(Interface):
+ pass
+ return self._duplicateAdapterForClassOrInterfaceAllowed(TheOriginal)
+
+
+ def _multipleInterfacesForClassOrInterface(self, original):
+ adapter = lambda o: None
+ class FirstInterface(Interface):
+ pass
+ class SecondInterface(Interface):
+ pass
+ components.registerAdapter(adapter, original, FirstInterface, SecondInterface)
+ self.assertIdentical(
+ components.getAdapterFactory(original, FirstInterface, None),
+ adapter)
+ self.assertIdentical(
+ components.getAdapterFactory(original, SecondInterface, None),
+ adapter)
+
+
+ def test_multipleInterfacesForClass(self):
+ """
+ Test the registration of an adapter from a class to several
+ interfaces at once.
+ """
+ class TheOriginal(object):
+ pass
+ return self._multipleInterfacesForClassOrInterface(TheOriginal)
+
+
+ def test_multipleInterfacesForInterface(self):
+ """
+ Test the registration of an adapter from an interface to several
+ interfaces at once.
+ """
+ class TheOriginal(Interface):
+ pass
+ return self._multipleInterfacesForClassOrInterface(TheOriginal)
+
+
+ def _subclassAdapterRegistrationForClassOrInterface(self, original):
+ firstAdapter = lambda o: True
+ secondAdapter = lambda o: False
+ class TheSubclass(original):
+ pass
+ class TheInterface(Interface):
+ pass
+ components.registerAdapter(firstAdapter, original, TheInterface)
+ components.registerAdapter(secondAdapter, TheSubclass, TheInterface)
+ self.assertIdentical(
+ components.getAdapterFactory(original, TheInterface, None),
+ firstAdapter)
+ self.assertIdentical(
+ components.getAdapterFactory(TheSubclass, TheInterface, None),
+ secondAdapter)
+
+
+ def test_subclassAdapterRegistrationForClass(self):
+ """
+ Test that an adapter to a particular interface can be registered
+ from both a class and its subclass.
+ """
+ class TheOriginal(object):
+ pass
+ return self._subclassAdapterRegistrationForClassOrInterface(TheOriginal)
+
+
+ def test_subclassAdapterRegistrationForInterface(self):
+ """
+ Test that an adapter to a particular interface can be registered
+ from both an interface and its subclass.
+ """
+ class TheOriginal(Interface):
+ pass
+ return self._subclassAdapterRegistrationForClassOrInterface(TheOriginal)
+
+
+
+class IProxiedInterface(Interface):
+ """
+ An interface class for use by L{proxyForInterface}.
+ """
+
+ ifaceAttribute = Attribute("""
+ An example declared attribute, which should be proxied.""")
+
+ def yay(*a, **kw):
+ """
+ A sample method which should be proxied.
+ """
+
+class IProxiedSubInterface(IProxiedInterface):
+ """
+ An interface that derives from another for use with L{proxyForInterface}.
+ """
+
+ def boo(self):
+ """
+ A different sample method which should be proxied.
+ """
+
+
+
+class Yayable(object):
+ """
+ A provider of L{IProxiedInterface} which increments a counter for
+ every call to C{yay}.
+
+ @ivar yays: The number of times C{yay} has been called.
+ """
+ implements(IProxiedInterface)
+
+ def __init__(self):
+ self.yays = 0
+ self.yayArgs = []
+
+ def yay(self, *a, **kw):
+ """
+ Increment C{self.yays}.
+ """
+ self.yays += 1
+ self.yayArgs.append((a, kw))
+ return self.yays
+
+
+class Booable(object):
+ """
+ An implementation of IProxiedSubInterface
+ """
+ implements(IProxiedSubInterface)
+ yayed = False
+ booed = False
+ def yay(self):
+ """
+ Mark the fact that 'yay' has been called.
+ """
+ self.yayed = True
+
+
+ def boo(self):
+ """
+ Mark the fact that 'boo' has been called.1
+ """
+ self.booed = True
+
+
+
+class IMultipleMethods(Interface):
+ """
+ An interface with multiple methods.
+ """
+
+ def methodOne():
+ """
+ The first method. Should return 1.
+ """
+
+ def methodTwo():
+ """
+ The second method. Should return 2.
+ """
+
+
+
+class MultipleMethodImplementor(object):
+ """
+ A precise implementation of L{IMultipleMethods}.
+ """
+
+ def methodOne(self):
+ """
+ @return: 1
+ """
+ return 1
+
+
+ def methodTwo(self):
+ """
+ @return: 2
+ """
+ return 2
+
+
+
+class ProxyForInterfaceTests(unittest.TestCase):
+ """
+ Tests for L{proxyForInterface}.
+ """
+
+ def test_original(self):
+ """
+ Proxy objects should have an C{original} attribute which refers to the
+ original object passed to the constructor.
+ """
+ original = object()
+ proxy = proxyForInterface(IProxiedInterface)(original)
+ self.assertIdentical(proxy.original, original)
+
+
+ def test_proxyMethod(self):
+ """
+ The class created from L{proxyForInterface} passes methods on an
+ interface to the object which is passed to its constructor.
+ """
+ klass = proxyForInterface(IProxiedInterface)
+ yayable = Yayable()
+ proxy = klass(yayable)
+ proxy.yay()
+ self.assertEquals(proxy.yay(), 2)
+ self.assertEquals(yayable.yays, 2)
+
+
+ def test_proxyAttribute(self):
+ """
+ Proxy objects should proxy declared attributes, but not other
+ attributes.
+ """
+ yayable = Yayable()
+ yayable.ifaceAttribute = object()
+ proxy = proxyForInterface(IProxiedInterface)(yayable)
+ self.assertIdentical(proxy.ifaceAttribute, yayable.ifaceAttribute)
+ self.assertRaises(AttributeError, lambda: proxy.yays)
+
+
+ def test_proxySetAttribute(self):
+ """
+ The attributes that proxy objects proxy should be assignable and affect
+ the original object.
+ """
+ yayable = Yayable()
+ proxy = proxyForInterface(IProxiedInterface)(yayable)
+ thingy = object()
+ proxy.ifaceAttribute = thingy
+ self.assertIdentical(yayable.ifaceAttribute, thingy)
+
+
+ def test_proxyDeleteAttribute(self):
+ """
+ The attributes that proxy objects proxy should be deletable and affect
+ the original object.
+ """
+ yayable = Yayable()
+ yayable.ifaceAttribute = None
+ proxy = proxyForInterface(IProxiedInterface)(yayable)
+ del proxy.ifaceAttribute
+ self.assertFalse(hasattr(yayable, 'ifaceAttribute'))
+
+
+ def test_multipleMethods(self):
+ """
+ [Regression test] The proxy should send its method calls to the correct
+ method, not the incorrect one.
+ """
+ multi = MultipleMethodImplementor()
+ proxy = proxyForInterface(IMultipleMethods)(multi)
+ self.assertEquals(proxy.methodOne(), 1)
+ self.assertEquals(proxy.methodTwo(), 2)
+
+
+ def test_subclassing(self):
+ """
+ It is possible to subclass the result of L{proxyForInterface}.
+ """
+
+ class SpecializedProxy(proxyForInterface(IProxiedInterface)):
+ """
+ A specialized proxy which can decrement the number of yays.
+ """
+ def boo(self):
+ """
+ Decrement the number of yays.
+ """
+ self.original.yays -= 1
+
+ yayable = Yayable()
+ special = SpecializedProxy(yayable)
+ self.assertEquals(yayable.yays, 0)
+ special.boo()
+ self.assertEquals(yayable.yays, -1)
+
+
+ def test_proxyName(self):
+ """
+ The name of a proxy class indicates which interface it proxies.
+ """
+ proxy = proxyForInterface(IProxiedInterface)
+ self.assertEquals(
+ proxy.__name__,
+ "(Proxy for "
+ "twisted.python.test.test_components.IProxiedInterface)")
+
+
+ def test_implements(self):
+ """
+ The resulting proxy implements the interface that it proxies.
+ """
+ proxy = proxyForInterface(IProxiedInterface)
+ self.assertTrue(IProxiedInterface.implementedBy(proxy))
+
+
+ def test_proxyDescriptorGet(self):
+ """
+ _ProxyDescriptor's __get__ method should return the appropriate
+ attribute of its argument's 'original' attribute if it is invoked with
+ an object. If it is invoked with None, it should return a false
+ class-method emulator instead.
+
+ For some reason, Python's documentation recommends to define
+ descriptors' __get__ methods with the 'type' parameter as optional,
+ despite the fact that Python itself never actually calls the descriptor
+ that way. This is probably do to support 'foo.__get__(bar)' as an
+ idiom. Let's make sure that the behavior is correct. Since we don't
+ actually use the 'type' argument at all, this test calls it the
+ idiomatic way to ensure that signature works; test_proxyInheritance
+ verifies the how-Python-actually-calls-it signature.
+ """
+ class Sample:
+ called = False
+ def hello(self):
+ self.called = True
+ fakeProxy = Sample()
+ testObject = Sample()
+ fakeProxy.original = testObject
+ pd = components._ProxyDescriptor("hello", "original")
+ self.assertEquals(pd.__get__(fakeProxy), testObject.hello)
+ fakeClassMethod = pd.__get__(None)
+ fakeClassMethod(fakeProxy)
+ self.failUnless(testObject.called)
+
+
+ def test_proxyInheritance(self):
+ """
+ Subclasses of the class returned from L{proxyForInterface} should be
+ able to upcall methods by reference to their superclass, as any normal
+ Python class can.
+ """
+ class YayableWrapper(proxyForInterface(IProxiedInterface)):
+ """
+ This class does not override any functionality.
+ """
+
+ class EnhancedWrapper(YayableWrapper):
+ """
+ This class overrides the 'yay' method.
+ """
+ wrappedYays = 1
+ def yay(self, *a, **k):
+ self.wrappedYays += 1
+ return YayableWrapper.yay(self, *a, **k) + 7
+
+ yayable = Yayable()
+ wrapper = EnhancedWrapper(yayable)
+ self.assertEquals(wrapper.yay(3, 4, x=5, y=6), 8)
+ self.assertEquals(yayable.yayArgs,
+ [((3, 4), dict(x=5, y=6))])
+
+
+ def test_interfaceInheritance(self):
+ """
+ Proxies of subinterfaces generated with proxyForInterface should allow
+ access to attributes of both the child and the base interfaces.
+ """
+ proxyClass = proxyForInterface(IProxiedSubInterface)
+ booable = Booable()
+ proxy = proxyClass(booable)
+ proxy.yay()
+ proxy.boo()
+ self.failUnless(booable.yayed)
+ self.failUnless(booable.booed)
+
+
+ def test_attributeCustomization(self):
+ """
+ The original attribute name can be customized via the
+ C{originalAttribute} argument of L{proxyForInterface}: the attribute
+ should change, but the methods of the original object should still be
+ callable, and the attributes still accessible.
+ """
+ yayable = Yayable()
+ yayable.ifaceAttribute = object()
+ proxy = proxyForInterface(
+ IProxiedInterface, originalAttribute='foo')(yayable)
+ self.assertIdentical(proxy.foo, yayable)
+
+ # Check the behavior
+ self.assertEquals(proxy.yay(), 1)
+ self.assertIdentical(proxy.ifaceAttribute, yayable.ifaceAttribute)
+ thingy = object()
+ proxy.ifaceAttribute = thingy
+ self.assertIdentical(yayable.ifaceAttribute, thingy)
+ del proxy.ifaceAttribute
+ self.assertFalse(hasattr(yayable, 'ifaceAttribute'))
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_deprecate.py b/vendor/Twisted-10.0.0/twisted/python/test/test_deprecate.py
new file mode 100644
index 0000000000..c5c48889cf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_deprecate.py
@@ -0,0 +1,399 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for Twisted's deprecation framework, L{twisted.python.deprecate}.
+"""
+
+import sys, types
+
+from twisted.trial.unittest import TestCase
+
+from twisted.python import deprecate
+from twisted.python.deprecate import _appendToDocstring
+from twisted.python.deprecate import _getDeprecationDocstring
+from twisted.python.deprecate import deprecated, getDeprecationWarningString
+from twisted.python.deprecate import _getDeprecationWarningString
+from twisted.python.deprecate import DEPRECATION_WARNING_FORMAT
+from twisted.python.reflect import fullyQualifiedName
+from twisted.python.versions import Version
+
+from twisted.python.test import deprecatedattributes
+
+
+
+def dummyCallable():
+ """
+ Do nothing.
+
+ This is used to test the deprecation decorators.
+ """
+
+
+
+class TestDeprecationWarnings(TestCase):
+ def test_getDeprecationWarningString(self):
+ """
+ L{getDeprecationWarningString} returns a string that tells us that a
+ callable was deprecated at a certain released version of Twisted.
+ """
+ version = Version('Twisted', 8, 0, 0)
+ self.assertEqual(
+ getDeprecationWarningString(self.test_getDeprecationWarningString,
+ version),
+ "twisted.python.test.test_deprecate.TestDeprecationWarnings."
+ "test_getDeprecationWarningString was deprecated in "
+ "Twisted 8.0.0")
+
+
+ def test_getDeprecationWarningStringWithFormat(self):
+ """
+ L{getDeprecationWarningString} returns a string that tells us that a
+ callable was deprecated at a certain released version of Twisted, with
+ a message containing additional information about the deprecation.
+ """
+ version = Version('Twisted', 8, 0, 0)
+ format = deprecate.DEPRECATION_WARNING_FORMAT + ': This is a message'
+ self.assertEquals(
+ getDeprecationWarningString(self.test_getDeprecationWarningString,
+ version, format),
+ 'twisted.python.test.test_deprecate.TestDeprecationWarnings.'
+ 'test_getDeprecationWarningString was deprecated in '
+ 'Twisted 8.0.0: This is a message')
+
+
+ def test_deprecateEmitsWarning(self):
+ """
+ Decorating a callable with L{deprecated} emits a warning.
+ """
+ version = Version('Twisted', 8, 0, 0)
+ dummy = deprecated(version)(dummyCallable)
+ def addStackLevel():
+ dummy()
+ self.assertWarns(
+ DeprecationWarning,
+ getDeprecationWarningString(dummyCallable, version),
+ __file__,
+ addStackLevel)
+
+
+ def test_deprecatedPreservesName(self):
+ """
+ The decorated function has the same name as the original.
+ """
+ version = Version('Twisted', 8, 0, 0)
+ dummy = deprecated(version)(dummyCallable)
+ self.assertEqual(dummyCallable.__name__, dummy.__name__)
+ self.assertEqual(fullyQualifiedName(dummyCallable),
+ fullyQualifiedName(dummy))
+
+
+ def test_getDeprecationDocstring(self):
+ """
+ L{_getDeprecationDocstring} returns a note about the deprecation to go
+ into a docstring.
+ """
+ version = Version('Twisted', 8, 0, 0)
+ self.assertEqual(
+ "Deprecated in Twisted 8.0.0.", _getDeprecationDocstring(version))
+
+
+ def test_deprecatedUpdatesDocstring(self):
+ """
+ The docstring of the deprecated function is appended with information
+ about the deprecation.
+ """
+
+ version = Version('Twisted', 8, 0, 0)
+ dummy = deprecated(version)(dummyCallable)
+
+ _appendToDocstring(
+ dummyCallable,
+ _getDeprecationDocstring(version))
+
+ self.assertEqual(dummyCallable.__doc__, dummy.__doc__)
+
+
+ def test_versionMetadata(self):
+ """
+ Deprecating a function adds version information to the decorated
+ version of that function.
+ """
+ version = Version('Twisted', 8, 0, 0)
+ dummy = deprecated(version)(dummyCallable)
+ self.assertEqual(version, dummy.deprecatedVersion)
+
+
+
+class TestAppendToDocstring(TestCase):
+ """
+ Test the _appendToDocstring function.
+
+ _appendToDocstring is used to add text to a docstring.
+ """
+
+ def test_appendToEmptyDocstring(self):
+ """
+ Appending to an empty docstring simply replaces the docstring.
+ """
+
+ def noDocstring():
+ pass
+
+ _appendToDocstring(noDocstring, "Appended text.")
+ self.assertEqual("Appended text.", noDocstring.__doc__)
+
+
+ def test_appendToSingleLineDocstring(self):
+ """
+ Appending to a single line docstring places the message on a new line,
+ with a blank line separating it from the rest of the docstring.
+
+ The docstring ends with a newline, conforming to Twisted and PEP 8
+ standards. Unfortunately, the indentation is incorrect, since the
+ existing docstring doesn't have enough info to help us indent
+ properly.
+ """
+
+ def singleLineDocstring():
+ """This doesn't comply with standards, but is here for a test."""
+
+ _appendToDocstring(singleLineDocstring, "Appended text.")
+ self.assertEqual(
+ ["This doesn't comply with standards, but is here for a test.",
+ "",
+ "Appended text."],
+ singleLineDocstring.__doc__.splitlines())
+ self.assertTrue(singleLineDocstring.__doc__.endswith('\n'))
+
+
+ def test_appendToMultilineDocstring(self):
+ """
+ Appending to a multi-line docstring places the messade on a new line,
+ with a blank line separating it from the rest of the docstring.
+
+ Because we have multiple lines, we have enough information to do
+ indentation.
+ """
+
+ def multiLineDocstring():
+ """
+ This is a multi-line docstring.
+ """
+
+ def expectedDocstring():
+ """
+ This is a multi-line docstring.
+
+ Appended text.
+ """
+
+ _appendToDocstring(multiLineDocstring, "Appended text.")
+ self.assertEqual(
+ expectedDocstring.__doc__, multiLineDocstring.__doc__)
+
+
+
+class _MockDeprecatedAttribute(object):
+ """
+ Mock of L{twisted.python.deprecate._DeprecatedAttribute}.
+
+ @ivar value: The value of the attribute.
+ """
+ def __init__(self, value):
+ self.value = value
+
+
+ def get(self):
+ """
+ Get a known value.
+ """
+ return self.value
+
+
+
+class ModuleProxyTests(TestCase):
+ """
+ Tests for L{twisted.python.deprecate._ModuleProxy}, which proxies
+ access to module-level attributes, intercepting access to deprecated
+ attributes and passing through access to normal attributes.
+ """
+ def _makeProxy(self, **attrs):
+ """
+ Create a temporary module proxy object.
+
+ @param **kw: Attributes to initialise on the temporary module object
+
+ @rtype: L{twistd.python.deprecate._ModuleProxy}
+ """
+ mod = types.ModuleType('foo')
+ for key, value in attrs.iteritems():
+ setattr(mod, key, value)
+ return deprecate._ModuleProxy(mod)
+
+
+ def test_getattrPassthrough(self):
+ """
+ Getting a normal attribute on a L{twisted.python.deprecate._ModuleProxy}
+ retrieves the underlying attribute's value, and raises C{AttributeError}
+ if a non-existant attribute is accessed.
+ """
+ proxy = self._makeProxy(SOME_ATTRIBUTE='hello')
+ self.assertIdentical(proxy.SOME_ATTRIBUTE, 'hello')
+ self.assertRaises(AttributeError, getattr, proxy, 'DOES_NOT_EXIST')
+
+
+ def test_getattrIntercept(self):
+ """
+ Getting an attribute marked as being deprecated on
+ L{twisted.python.deprecate._ModuleProxy} results in calling the
+ deprecated wrapper's C{get} method.
+ """
+ proxy = self._makeProxy()
+ _deprecatedAttributes = object.__getattribute__(
+ proxy, '_deprecatedAttributes')
+ _deprecatedAttributes['foo'] = _MockDeprecatedAttribute(42)
+ self.assertEquals(proxy.foo, 42)
+
+
+ def test_privateAttributes(self):
+ """
+ Private attributes of L{twisted.python.deprecate._ModuleProxy} are
+ inaccessible when regular attribute access is used.
+ """
+ proxy = self._makeProxy()
+ self.assertRaises(AttributeError, getattr, proxy, '_module')
+ self.assertRaises(
+ AttributeError, getattr, proxy, '_deprecatedAttributes')
+
+
+ def test_setattr(self):
+ """
+ Setting attributes on L{twisted.python.deprecate._ModuleProxy} proxies
+ them through to the wrapped module.
+ """
+ proxy = self._makeProxy()
+ proxy._module = 1
+ self.assertNotEquals(object.__getattribute__(proxy, '_module'), 1)
+ self.assertEquals(proxy._module, 1)
+
+
+ def test_repr(self):
+ """
+ L{twisted.python.deprecated._ModuleProxy.__repr__} produces a string
+ containing the proxy type and a representation of the wrapped module
+ object.
+ """
+ proxy = self._makeProxy()
+ realModule = object.__getattribute__(proxy, '_module')
+ self.assertEquals(
+ repr(proxy), '<%s module=%r>' % (type(proxy).__name__, realModule))
+
+
+
+class DeprecatedAttributeTests(TestCase):
+ """
+ Tests for L{twisted.python.deprecate._DeprecatedAttribute} and
+ L{twisted.python.deprecate.deprecatedModuleAttribute}, which issue
+ warnings for deprecated module-level attributes.
+ """
+ def setUp(self):
+ self.version = deprecatedattributes.version
+ self.message = deprecatedattributes.message
+ self._testModuleName = __name__ + '.foo'
+
+
+ def _getWarningString(self, attr):
+ """
+ Create the warning string used by deprecated attributes.
+ """
+ return _getDeprecationWarningString(
+ deprecatedattributes.__name__ + '.' + attr,
+ deprecatedattributes.version,
+ DEPRECATION_WARNING_FORMAT + ': ' + deprecatedattributes.message)
+
+
+ def test_deprecatedAttributeHelper(self):
+ """
+ L{twisted.python.deprecate._DeprecatedAttribute} correctly sets its
+ __name__ to match that of the deprecated attribute and emits a warning
+ when the original attribute value is accessed.
+ """
+ name = 'ANOTHER_DEPRECATED_ATTRIBUTE'
+ setattr(deprecatedattributes, name, 42)
+ attr = deprecate._DeprecatedAttribute(
+ deprecatedattributes, name, self.version, self.message)
+
+ self.assertEquals(attr.__name__, name)
+
+ # Since we're accessing the value getter directly, as opposed to via
+ # the module proxy, we need to match the warning's stack level.
+ def addStackLevel():
+ attr.get()
+
+ # Access the deprecated attribute.
+ addStackLevel()
+ warningsShown = self.flushWarnings([
+ self.test_deprecatedAttributeHelper])
+ self.assertIdentical(warningsShown[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warningsShown[0]['message'],
+ self._getWarningString(name))
+ self.assertEquals(len(warningsShown), 1)
+
+
+ def test_deprecatedAttribute(self):
+ """
+ L{twisted.python.deprecate.deprecatedModuleAttribute} wraps a
+ module-level attribute in an object that emits a deprecation warning
+ when it is accessed the first time only, while leaving other unrelated
+ attributes alone.
+ """
+ # Accessing non-deprecated attributes does not issue a warning.
+ deprecatedattributes.ANOTHER_ATTRIBUTE
+ warningsShown = self.flushWarnings([self.test_deprecatedAttribute])
+ self.assertEquals(len(warningsShown), 0)
+
+ name = 'DEPRECATED_ATTRIBUTE'
+
+ # Access the deprecated attribute. This uses getattr to avoid repeating
+ # the attribute name.
+ getattr(deprecatedattributes, name)
+
+ warningsShown = self.flushWarnings([self.test_deprecatedAttribute])
+ self.assertEquals(len(warningsShown), 1)
+ self.assertIdentical(warningsShown[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warningsShown[0]['message'],
+ self._getWarningString(name))
+
+
+ def test_wrappedModule(self):
+ """
+ Deprecating an attribute in a module replaces and wraps that module
+ instance, in C{sys.modules}, with a
+ L{twisted.python.deprecate._ModuleProxy} instance but only if it hasn't
+ already been wrapped.
+ """
+ sys.modules[self._testModuleName] = mod = types.ModuleType('foo')
+ self.addCleanup(sys.modules.pop, self._testModuleName)
+
+ setattr(mod, 'first', 1)
+ setattr(mod, 'second', 2)
+
+ deprecate.deprecatedModuleAttribute(
+ Version('Twisted', 8, 0, 0),
+ 'message',
+ self._testModuleName,
+ 'first')
+
+ proxy = sys.modules[self._testModuleName]
+ self.assertNotEqual(proxy, mod)
+
+ deprecate.deprecatedModuleAttribute(
+ Version('Twisted', 8, 0, 0),
+ 'message',
+ self._testModuleName,
+ 'second')
+
+ self.assertIdentical(proxy, sys.modules[self._testModuleName])
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_dist.py b/vendor/Twisted-10.0.0/twisted/python/test/test_dist.py
new file mode 100644
index 0000000000..c69717daf9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_dist.py
@@ -0,0 +1,173 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for parts of our release automation system.
+"""
+
+
+import os
+
+from distutils.core import Distribution
+
+from twisted.trial.unittest import TestCase
+
+from twisted.python import dist
+from twisted.python.dist import get_setup_args, ConditionalExtension
+from twisted.python.filepath import FilePath
+
+
+class SetupTest(TestCase):
+ """
+ Tests for L{get_setup_args}.
+ """
+ def test_conditionalExtensions(self):
+ """
+ Passing C{conditionalExtensions} as a list of L{ConditionalExtension}
+ objects to get_setup_args inserts a custom build_ext into the result
+ which knows how to check whether they should be
+ """
+ good_ext = ConditionalExtension("whatever", ["whatever.c"],
+ condition=lambda b: True)
+ bad_ext = ConditionalExtension("whatever", ["whatever.c"],
+ condition=lambda b: False)
+ args = get_setup_args(conditionalExtensions=[good_ext, bad_ext])
+ # ext_modules should be set even though it's not used. See comment
+ # in get_setup_args
+ self.assertEquals(args["ext_modules"], [good_ext, bad_ext])
+ cmdclass = args["cmdclass"]
+ build_ext = cmdclass["build_ext"]
+ builder = build_ext(Distribution())
+ builder.prepare_extensions()
+ self.assertEquals(builder.extensions, [good_ext])
+
+
+ def test_win32Definition(self):
+ """
+ When building on Windows NT, the WIN32 macro will be defined as 1.
+ """
+ ext = ConditionalExtension("whatever", ["whatever.c"],
+ define_macros=[("whatever", 2)])
+ args = get_setup_args(conditionalExtensions=[ext])
+ builder = args["cmdclass"]["build_ext"](Distribution())
+ self.patch(os, "name", "nt")
+ builder.prepare_extensions()
+ self.assertEquals(ext.define_macros, [("whatever", 2), ("WIN32", 1)])
+
+
+
+class GetVersionTest(TestCase):
+ """
+ Tests for L{dist.getVersion}.
+ """
+
+ def setUp(self):
+ self.dirname = self.mktemp()
+ os.mkdir(self.dirname)
+
+ def test_getVersionCore(self):
+ """
+ Test that getting the version of core reads from the
+ [base]/_version.py file.
+ """
+ f = open(os.path.join(self.dirname, "_version.py"), "w")
+ f.write("""
+from twisted.python import versions
+version = versions.Version("twisted", 0, 1, 2)
+""")
+ f.close()
+ self.assertEquals(dist.getVersion("core", base=self.dirname), "0.1.2")
+
+ def test_getVersionOther(self):
+ """
+ Test that getting the version of a non-core project reads from
+ the [base]/[projname]/_version.py file.
+ """
+ os.mkdir(os.path.join(self.dirname, "blat"))
+ f = open(os.path.join(self.dirname, "blat", "_version.py"), "w")
+ f.write("""
+from twisted.python import versions
+version = versions.Version("twisted.blat", 9, 8, 10)
+""")
+ f.close()
+ self.assertEquals(dist.getVersion("blat", base=self.dirname), "9.8.10")
+
+
+class GetScriptsTest(TestCase):
+
+ def test_scriptsInSVN(self):
+ """
+ getScripts should return the scripts associated with a project
+ in the context of Twisted SVN.
+ """
+ basedir = self.mktemp()
+ os.mkdir(basedir)
+ os.mkdir(os.path.join(basedir, 'bin'))
+ os.mkdir(os.path.join(basedir, 'bin', 'proj'))
+ f = open(os.path.join(basedir, 'bin', 'proj', 'exy'), 'w')
+ f.write('yay')
+ f.close()
+ scripts = dist.getScripts('proj', basedir=basedir)
+ self.assertEquals(len(scripts), 1)
+ self.assertEquals(os.path.basename(scripts[0]), 'exy')
+
+
+ def test_scriptsInRelease(self):
+ """
+ getScripts should return the scripts associated with a project
+ in the context of a released subproject tarball.
+ """
+ basedir = self.mktemp()
+ os.mkdir(basedir)
+ os.mkdir(os.path.join(basedir, 'bin'))
+ f = open(os.path.join(basedir, 'bin', 'exy'), 'w')
+ f.write('yay')
+ f.close()
+ scripts = dist.getScripts('proj', basedir=basedir)
+ self.assertEquals(len(scripts), 1)
+ self.assertEquals(os.path.basename(scripts[0]), 'exy')
+
+
+ def test_noScriptsInSVN(self):
+ """
+ When calling getScripts for a project which doesn't actually
+ have any scripts, in the context of an SVN checkout, an
+ empty list should be returned.
+ """
+ basedir = self.mktemp()
+ os.mkdir(basedir)
+ os.mkdir(os.path.join(basedir, 'bin'))
+ os.mkdir(os.path.join(basedir, 'bin', 'otherproj'))
+ scripts = dist.getScripts('noscripts', basedir=basedir)
+ self.assertEquals(scripts, [])
+
+
+ def test_getScriptsTopLevel(self):
+ """
+ Passing the empty string to getScripts returns scripts that are (only)
+ in the top level bin directory.
+ """
+ basedir = FilePath(self.mktemp())
+ basedir.createDirectory()
+ bindir = basedir.child("bin")
+ bindir.createDirectory()
+ included = bindir.child("included")
+ included.setContent("yay included")
+ subdir = bindir.child("subdir")
+ subdir.createDirectory()
+ subdir.child("not-included").setContent("not included")
+
+ scripts = dist.getScripts("", basedir=basedir.path)
+ self.assertEquals(scripts, [included.path])
+
+
+ def test_noScriptsInSubproject(self):
+ """
+ When calling getScripts for a project which doesn't actually
+ have any scripts in the context of that project's individual
+ project structure, an empty list should be returned.
+ """
+ basedir = self.mktemp()
+ os.mkdir(basedir)
+ scripts = dist.getScripts('noscripts', basedir=basedir)
+ self.assertEquals(scripts, [])
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_fakepwd.py b/vendor/Twisted-10.0.0/twisted/python/test/test_fakepwd.py
new file mode 100644
index 0000000000..52edd25365
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_fakepwd.py
@@ -0,0 +1,216 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.python.fakepwd}.
+"""
+
+try:
+ import pwd
+except ImportError:
+ pwd = None
+
+
+from operator import getitem
+
+from twisted.trial.unittest import TestCase
+from twisted.python.fakepwd import UserDatabase
+from twisted.python.compat import set
+
+
+class UserDatabaseTestsMixin:
+ """
+ L{UserDatabaseTestsMixin} defines tests which apply to any user database
+ implementation. Subclasses should mix it in, implement C{setUp} to create
+ C{self.database} bound to a user database instance, and implement
+ C{getExistingUserInfo} to return information about a user (such information
+ should be unique per test method).
+ """
+ def test_getpwuid(self):
+ """
+ I{getpwuid} accepts a uid and returns the user record associated with
+ it.
+ """
+ for i in range(2):
+ # Get some user which exists in the database.
+ username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
+
+ # Now try to look it up and make sure the result is correct.
+ entry = self.database.getpwuid(uid)
+ self.assertEquals(entry.pw_name, username)
+ self.assertEquals(entry.pw_passwd, password)
+ self.assertEquals(entry.pw_uid, uid)
+ self.assertEquals(entry.pw_gid, gid)
+ self.assertEquals(entry.pw_gecos, gecos)
+ self.assertEquals(entry.pw_dir, dir)
+ self.assertEquals(entry.pw_shell, shell)
+
+
+ def test_noSuchUID(self):
+ """
+ I{getpwuid} raises L{KeyError} when passed a uid which does not exist
+ in the user database.
+ """
+ self.assertRaises(KeyError, self.database.getpwuid, -13)
+
+
+ def test_getpwnam(self):
+ """
+ I{getpwnam} accepts a username and returns the user record associated
+ with it.
+ """
+ for i in range(2):
+ # Get some user which exists in the database.
+ username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
+
+ # Now try to look it up and make sure the result is correct.
+ entry = self.database.getpwnam(username)
+ self.assertEquals(entry.pw_name, username)
+ self.assertEquals(entry.pw_passwd, password)
+ self.assertEquals(entry.pw_uid, uid)
+ self.assertEquals(entry.pw_gid, gid)
+ self.assertEquals(entry.pw_gecos, gecos)
+ self.assertEquals(entry.pw_dir, dir)
+ self.assertEquals(entry.pw_shell, shell)
+
+
+ def test_noSuchName(self):
+ """
+ I{getpwnam} raises L{KeyError} when passed a username which does not
+ exist in the user database.
+ """
+ self.assertRaises(
+ KeyError, self.database.getpwnam,
+ 'no' 'such' 'user' 'exists' 'the' 'name' 'is' 'too' 'long' 'and' 'has'
+ '\1' 'in' 'it' 'too')
+
+
+ def test_recordLength(self):
+ """
+ The user record returned by I{getpwuid}, I{getpwnam}, and I{getpwall}
+ has a length.
+ """
+ db = self.database
+ username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
+ for entry in [db.getpwuid(uid), db.getpwnam(username), db.getpwall()[0]]:
+ self.assertIsInstance(len(entry), int)
+
+
+ def test_recordIndexable(self):
+ """
+ The user record returned by I{getpwuid}, I{getpwnam}, and I{getpwall}
+ is indexable, with successive indexes starting from 0 corresponding to
+ the values of the C{pw_name}, C{pw_passwd}, C{pw_uid}, C{pw_gid},
+ C{pw_gecos}, C{pw_dir}, and C{pw_shell} attributes, respectively.
+ """
+ db = self.database
+ username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
+ for entry in [db.getpwuid(uid), db.getpwnam(username), db.getpwall()[0]]:
+ self.assertEquals(entry[0], username)
+ self.assertEquals(entry[1], password)
+ self.assertEquals(entry[2], uid)
+ self.assertEquals(entry[3], gid)
+ self.assertEquals(entry[4], gecos)
+ self.assertEquals(entry[5], dir)
+ self.assertEquals(entry[6], shell)
+
+ self.assertEquals(len(entry), len(list(entry)))
+ self.assertRaises(IndexError, getitem, entry, 7)
+
+
+
+class UserDatabaseTests(TestCase, UserDatabaseTestsMixin):
+ """
+ Tests for L{UserDatabase}.
+ """
+ def setUp(self):
+ """
+ Create a L{UserDatabase} with no user data in it.
+ """
+ self.database = UserDatabase()
+ self._counter = 0
+
+
+ def getExistingUserInfo(self):
+ """
+ Add a new user to C{self.database} and return its information.
+ """
+ self._counter += 1
+ suffix = '_' + str(self._counter)
+ username = 'username' + suffix
+ password = 'password' + suffix
+ uid = self._counter
+ gid = self._counter + 1000
+ gecos = 'gecos' + suffix
+ dir = 'dir' + suffix
+ shell = 'shell' + suffix
+
+ self.database.addUser(username, password, uid, gid, gecos, dir, shell)
+ return (username, password, uid, gid, gecos, dir, shell)
+
+
+ def test_addUser(self):
+ """
+ L{UserDatabase.addUser} accepts seven arguments, one for each field of
+ a L{pwd.struct_passwd}, and makes the new record available via
+ L{UserDatabase.getpwuid}, L{UserDatabase.getpwnam}, and
+ L{UserDatabase.getpwall}.
+ """
+ username = 'alice'
+ password = 'secr3t'
+ uid = 123
+ gid = 456
+ gecos = 'Alice,,,'
+ home = '/users/alice'
+ shell = '/usr/bin/foosh'
+
+ db = self.database
+ db.addUser(username, password, uid, gid, gecos, home, shell)
+
+ for entry in [db.getpwuid(uid), db.getpwnam(username)]:
+ self.assertEquals(entry.pw_name, username)
+ self.assertEquals(entry.pw_passwd, password)
+ self.assertEquals(entry.pw_uid, uid)
+ self.assertEquals(entry.pw_gid, gid)
+ self.assertEquals(entry.pw_gecos, gecos)
+ self.assertEquals(entry.pw_dir, home)
+ self.assertEquals(entry.pw_shell, shell)
+
+ [entry] = db.getpwall()
+ self.assertEquals(entry.pw_name, username)
+ self.assertEquals(entry.pw_passwd, password)
+ self.assertEquals(entry.pw_uid, uid)
+ self.assertEquals(entry.pw_gid, gid)
+ self.assertEquals(entry.pw_gecos, gecos)
+ self.assertEquals(entry.pw_dir, home)
+ self.assertEquals(entry.pw_shell, shell)
+
+
+
+class PwdModuleTests(TestCase, UserDatabaseTestsMixin):
+ """
+ L{PwdModuleTests} runs the tests defined by L{UserDatabaseTestsMixin}
+ against the built-in C{pwd} module. This serves to verify that
+ L{UserDatabase} is really a fake of that API.
+ """
+ if pwd is None:
+ skip = "Cannot verify UserDatabase against pwd without pwd"
+
+
+ def setUp(self):
+ self.database = pwd
+ self._users = iter(self.database.getpwall())
+ self._uids = set()
+
+
+ def getExistingUserInfo(self):
+ """
+ Read and return the next record from C{self._users}, filtering out
+ any records with previously seen uid values (as these cannot be
+ found with C{getpwuid} and only cause trouble).
+ """
+ while True:
+ entry = self._users.next()
+ if entry.pw_uid not in self._uids:
+ self._uids.add(entry.pw_uid)
+ return entry
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_hashlib.py b/vendor/Twisted-10.0.0/twisted/python/test/test_hashlib.py
new file mode 100644
index 0000000000..a9b5da5e7a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_hashlib.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.python.hashlib}
+"""
+
+from twisted.trial.unittest import TestCase
+
+from twisted.python.hashlib import md5, sha1
+
+
+class HashObjectTests(TestCase):
+ """
+ Tests for the hash object APIs presented by L{hashlib}, C{md5} and C{sha1}.
+ """
+ def test_md5(self):
+ """
+ L{hashlib.md5} returns an object which can be used to compute an MD5
+ hash as defined by U{RFC 1321<http://www.ietf.org/rfc/rfc1321.txt>}.
+ """
+ # Test the result using values from section A.5 of the RFC.
+ self.assertEqual(
+ md5().hexdigest(), "d41d8cd98f00b204e9800998ecf8427e")
+ self.assertEqual(
+ md5("a").hexdigest(), "0cc175b9c0f1b6a831c399e269772661")
+ self.assertEqual(
+ md5("abc").hexdigest(), "900150983cd24fb0d6963f7d28e17f72")
+ self.assertEqual(
+ md5("message digest").hexdigest(),
+ "f96b697d7cb7938d525a2f31aaf161d0")
+ self.assertEqual(
+ md5("abcdefghijklmnopqrstuvwxyz").hexdigest(),
+ "c3fcd3d76192e4007dfb496cca67e13b")
+ self.assertEqual(
+ md5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789").hexdigest(),
+ "d174ab98d277d9f5a5611c2c9f419d9f")
+ self.assertEqual(
+ md5("1234567890123456789012345678901234567890123456789012345678901"
+ "2345678901234567890").hexdigest(),
+ "57edf4a22be3c955ac49da2e2107b67a")
+
+ # It should have digest and update methods, too.
+ self.assertEqual(
+ md5().digest().encode('hex'),
+ "d41d8cd98f00b204e9800998ecf8427e")
+ hash = md5()
+ hash.update("a")
+ self.assertEqual(
+ hash.digest().encode('hex'),
+ "0cc175b9c0f1b6a831c399e269772661")
+
+ # Instances of it should have a digest_size attribute
+ self.assertEqual(md5().digest_size, 16)
+
+
+ def test_sha1(self):
+ """
+ L{hashlib.sha1} returns an object which can be used to compute a SHA1
+ hash as defined by U{RFC 3174<http://tools.ietf.org/rfc/rfc3174.txt>}.
+ """
+ def format(s):
+ return ''.join(s.split()).lower()
+ # Test the result using values from section 7.3 of the RFC.
+ self.assertEqual(
+ sha1("abc").hexdigest(),
+ format(
+ "A9 99 3E 36 47 06 81 6A BA 3E 25 71 78 50 C2 6C 9C D0 D8 9D"))
+ self.assertEqual(
+ sha1("abcdbcdecdefdefgefghfghighijhi"
+ "jkijkljklmklmnlmnomnopnopq").hexdigest(),
+ format(
+ "84 98 3E 44 1C 3B D2 6E BA AE 4A A1 F9 51 29 E5 E5 46 70 F1"))
+
+ # It should have digest and update methods, too.
+ self.assertEqual(
+ sha1("abc").digest().encode('hex'),
+ format(
+ "A9 99 3E 36 47 06 81 6A BA 3E 25 71 78 50 C2 6C 9C D0 D8 9D"))
+ hash = sha1()
+ hash.update("abc")
+ self.assertEqual(
+ hash.digest().encode('hex'),
+ format(
+ "A9 99 3E 36 47 06 81 6A BA 3E 25 71 78 50 C2 6C 9C D0 D8 9D"))
+
+ # Instances of it should have a digest_size attribute.
+ self.assertEqual(
+ sha1().digest_size, 20)
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_htmlizer.py b/vendor/Twisted-10.0.0/twisted/python/test/test_htmlizer.py
new file mode 100644
index 0000000000..9eedd41eb5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_htmlizer.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.python.htmlizer}.
+"""
+
+from StringIO import StringIO
+
+from twisted.trial.unittest import TestCase
+from twisted.python.htmlizer import filter
+
+
+class FilterTests(TestCase):
+ """
+ Tests for L{twisted.python.htmlizer.filter}.
+ """
+ def test_empty(self):
+ """
+ If passed an empty input file, L{filter} writes a I{pre} tag containing
+ only an end marker to the output file.
+ """
+ input = StringIO("")
+ output = StringIO()
+ filter(input, output)
+ self.assertEqual(output.getvalue(), '<pre><span class="py-src-endmarker"></span></pre>\n')
+
+
+ def test_variable(self):
+ """
+ If passed an input file containing a variable access, L{filter} writes
+ a I{pre} tag containing a I{py-src-variable} span containing the
+ variable.
+ """
+ input = StringIO("foo\n")
+ output = StringIO()
+ filter(input, output)
+ self.assertEqual(
+ output.getvalue(),
+ '<pre><span class="py-src-variable">foo</span><span class="py-src-newline">\n'
+ '</span><span class="py-src-endmarker"></span></pre>\n')
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_release.py b/vendor/Twisted-10.0.0/twisted/python/test/test_release.py
new file mode 100644
index 0000000000..25170f4abb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_release.py
@@ -0,0 +1,2476 @@
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.python.release} and L{twisted.python._release}.
+
+All of these tests are skipped on platforms other than Linux, as the release is
+only ever performed on Linux.
+"""
+
+
+import warnings
+import operator
+import os, sys, signal
+from StringIO import StringIO
+import tarfile
+from xml.dom import minidom as dom
+
+from datetime import date
+
+from twisted.trial.unittest import TestCase
+
+from twisted.python.compat import set
+from twisted.python.procutils import which
+from twisted.python import release
+from twisted.python.filepath import FilePath
+from twisted.python.util import dsu
+from twisted.python.versions import Version
+from twisted.python._release import _changeVersionInFile, getNextVersion
+from twisted.python._release import findTwistedProjects, replaceInFile
+from twisted.python._release import replaceProjectVersion
+from twisted.python._release import updateTwistedVersionInformation, Project
+from twisted.python._release import generateVersionFileData
+from twisted.python._release import changeAllProjectVersions
+from twisted.python._release import VERSION_OFFSET, DocBuilder, ManBuilder
+from twisted.python._release import NoDocumentsFound, filePathDelta
+from twisted.python._release import CommandFailed, BookBuilder
+from twisted.python._release import DistributionBuilder, APIBuilder
+from twisted.python._release import BuildAPIDocsScript
+from twisted.python._release import buildAllTarballs, runCommand
+from twisted.python._release import UncleanWorkingDirectory, NotWorkingDirectory
+from twisted.python._release import ChangeVersionsScript, BuildTarballsScript
+from twisted.python._release import NewsBuilder
+
+if sys.platform != 'linux2':
+ skip = "Release toolchain only supported on Linux."
+else:
+ skip = None
+
+
+# Check a bunch of dependencies to skip tests if necessary.
+try:
+ from twisted.lore.scripts import lore
+except ImportError:
+ loreSkip = "Lore is not present."
+else:
+ loreSkip = skip
+
+
+try:
+ from popen2 import Popen4
+except ImportError:
+ popen4Skip = "popen2.Popen4 is not available."
+else:
+ popen4Skip = skip
+
+
+try:
+ import pydoctor.driver
+ # it might not be installed, or it might use syntax not available in
+ # this version of Python.
+except (ImportError, SyntaxError):
+ pydoctorSkip = "Pydoctor is not present."
+else:
+ if getattr(pydoctor, "version_info", (0,)) < (0, 1):
+ pydoctorSkip = "Pydoctor is too old."
+ else:
+ pydoctorSkip = skip
+
+
+if which("latex") and which("dvips") and which("ps2pdf13"):
+ latexSkip = skip
+else:
+ latexSkip = "LaTeX is not available."
+
+
+if which("svn") and which("svnadmin"):
+ svnSkip = skip
+else:
+ svnSkip = "svn or svnadmin is not present."
+
+
+def genVersion(*args, **kwargs):
+ """
+ A convenience for generating _version.py data.
+
+ @param args: Arguments to pass to L{Version}.
+ @param kwargs: Keyword arguments to pass to L{Version}.
+ """
+ return generateVersionFileData(Version(*args, **kwargs))
+
+
+
+class StructureAssertingMixin(object):
+ """
+ A mixin for L{TestCase} subclasses which provides some methods for asserting
+ the structure and contents of directories and files on the filesystem.
+ """
+ def createStructure(self, root, dirDict):
+ """
+ Create a set of directories and files given a dict defining their
+ structure.
+
+ @param root: The directory in which to create the structure. It must
+ already exist.
+ @type root: L{FilePath}
+
+ @param dirDict: The dict defining the structure. Keys should be strings
+ naming files, values should be strings describing file contents OR
+ dicts describing subdirectories. All files are written in binary
+ mode. Any string values are assumed to describe text files and
+ will have their newlines replaced with the platform-native newline
+ convention. For example::
+
+ {"foofile": "foocontents",
+ "bardir": {"barfile": "bar\ncontents"}}
+ @type dirDict: C{dict}
+ """
+ for x in dirDict:
+ child = root.child(x)
+ if isinstance(dirDict[x], dict):
+ child.createDirectory()
+ self.createStructure(child, dirDict[x])
+ else:
+ child.setContent(dirDict[x].replace('\n', os.linesep))
+
+ def assertStructure(self, root, dirDict):
+ """
+ Assert that a directory is equivalent to one described by a dict.
+
+ @param root: The filesystem directory to compare.
+ @type root: L{FilePath}
+ @param dirDict: The dict that should describe the contents of the
+ directory. It should be the same structure as the C{dirDict}
+ parameter to L{createStructure}.
+ @type dirDict: C{dict}
+ """
+ children = [x.basename() for x in root.children()]
+ for x in dirDict:
+ child = root.child(x)
+ if isinstance(dirDict[x], dict):
+ self.assertTrue(child.isdir(), "%s is not a dir!"
+ % (child.path,))
+ self.assertStructure(child, dirDict[x])
+ else:
+ a = child.getContent().replace(os.linesep, '\n')
+ self.assertEquals(a, dirDict[x], child.path)
+ children.remove(x)
+ if children:
+ self.fail("There were extra children in %s: %s"
+ % (root.path, children))
+
+
+ def assertExtractedStructure(self, outputFile, dirDict):
+ """
+ Assert that a tarfile content is equivalent to one described by a dict.
+
+ @param outputFile: The tar file built by L{DistributionBuilder}.
+ @type outputFile: L{FilePath}.
+ @param dirDict: The dict that should describe the contents of the
+ directory. It should be the same structure as the C{dirDict}
+ parameter to L{createStructure}.
+ @type dirDict: C{dict}
+ """
+ tarFile = tarfile.TarFile.open(outputFile.path, "r:bz2")
+ extracted = FilePath(self.mktemp())
+ extracted.createDirectory()
+ for info in tarFile:
+ tarFile.extract(info, path=extracted.path)
+ self.assertStructure(extracted.children()[0], dirDict)
+
+
+
+class ChangeVersionTest(TestCase, StructureAssertingMixin):
+ """
+ Twisted has the ability to change versions.
+ """
+
+ def makeFile(self, relativePath, content):
+ """
+ Create a file with the given content relative to a temporary directory.
+
+ @param relativePath: The basename of the file to create.
+ @param content: The content that the file will have.
+ @return: The filename.
+ """
+ baseDirectory = FilePath(self.mktemp())
+ directory, filename = os.path.split(relativePath)
+ directory = baseDirectory.preauthChild(directory)
+ directory.makedirs()
+ file = directory.child(filename)
+ directory.child(filename).setContent(content)
+ return file
+
+
+ def test_getNextVersion(self):
+ """
+ When calculating the next version to release when a release is
+ happening in the same year as the last release, the minor version
+ number is incremented.
+ """
+ now = date.today()
+ major = now.year - VERSION_OFFSET
+ version = Version("twisted", major, 9, 0)
+ self.assertEquals(getNextVersion(version, now=now),
+ Version("twisted", major, 10, 0))
+
+
+ def test_getNextVersionAfterYearChange(self):
+ """
+ When calculating the next version to release when a release is
+ happening in a later year, the minor version number is reset to 0.
+ """
+ now = date.today()
+ major = now.year - VERSION_OFFSET
+ version = Version("twisted", major - 1, 9, 0)
+ self.assertEquals(getNextVersion(version, now=now),
+ Version("twisted", major, 0, 0))
+
+
+ def test_changeVersionInFile(self):
+ """
+ _changeVersionInFile replaces the old version information in a file
+ with the given new version information.
+ """
+ # The version numbers are arbitrary, the name is only kind of
+ # arbitrary.
+ packageName = 'foo'
+ oldVersion = Version(packageName, 2, 5, 0)
+ file = self.makeFile('README',
+ "Hello and welcome to %s." % oldVersion.base())
+
+ newVersion = Version(packageName, 7, 6, 0)
+ _changeVersionInFile(oldVersion, newVersion, file.path)
+
+ self.assertEqual(file.getContent(),
+ "Hello and welcome to %s." % newVersion.base())
+
+
+ def test_changeAllProjectVersions(self):
+ """
+ L{changeAllProjectVersions} changes all version numbers in _version.py
+ and README files for all projects as well as in the the top-level
+ README file.
+ """
+ root = FilePath(self.mktemp())
+ root.createDirectory()
+ structure = {
+ "README": "Hi this is 1.0.0.",
+ "twisted": {
+ "topfiles": {
+ "README": "Hi this is 1.0.0"},
+ "_version.py":
+ genVersion("twisted", 1, 0, 0),
+ "web": {
+ "topfiles": {
+ "README": "Hi this is 1.0.0"},
+ "_version.py": genVersion("twisted.web", 1, 0, 0)
+ }}}
+ self.createStructure(root, structure)
+ changeAllProjectVersions(root, Version("lol", 1, 0, 2))
+ outStructure = {
+ "README": "Hi this is 1.0.2.",
+ "twisted": {
+ "topfiles": {
+ "README": "Hi this is 1.0.2"},
+ "_version.py":
+ genVersion("twisted", 1, 0, 2),
+ "web": {
+ "topfiles": {
+ "README": "Hi this is 1.0.2"},
+ "_version.py": genVersion("twisted.web", 1, 0, 2),
+ }}}
+ self.assertStructure(root, outStructure)
+
+
+
+class ProjectTest(TestCase):
+ """
+ There is a first-class representation of a project.
+ """
+
+ def assertProjectsEqual(self, observedProjects, expectedProjects):
+ """
+ Assert that two lists of L{Project}s are equal.
+ """
+ self.assertEqual(len(observedProjects), len(expectedProjects))
+ observedProjects = dsu(observedProjects,
+ key=operator.attrgetter('directory'))
+ expectedProjects = dsu(expectedProjects,
+ key=operator.attrgetter('directory'))
+ for observed, expected in zip(observedProjects, expectedProjects):
+ self.assertEqual(observed.directory, expected.directory)
+
+
+ def makeProject(self, version, baseDirectory=None):
+ """
+ Make a Twisted-style project in the given base directory.
+
+ @param baseDirectory: The directory to create files in
+ (as a L{FilePath).
+ @param version: The version information for the project.
+ @return: L{Project} pointing to the created project.
+ """
+ if baseDirectory is None:
+ baseDirectory = FilePath(self.mktemp())
+ baseDirectory.createDirectory()
+ segments = version.package.split('.')
+ directory = baseDirectory
+ for segment in segments:
+ directory = directory.child(segment)
+ if not directory.exists():
+ directory.createDirectory()
+ directory.child('__init__.py').setContent('')
+ directory.child('topfiles').createDirectory()
+ directory.child('topfiles').child('README').setContent(version.base())
+ replaceProjectVersion(
+ directory.child('_version.py').path, version)
+ return Project(directory)
+
+
+ def makeProjects(self, *versions):
+ """
+ Create a series of projects underneath a temporary base directory.
+
+ @return: A L{FilePath} for the base directory.
+ """
+ baseDirectory = FilePath(self.mktemp())
+ baseDirectory.createDirectory()
+ for version in versions:
+ self.makeProject(version, baseDirectory)
+ return baseDirectory
+
+
+ def test_getVersion(self):
+ """
+ Project objects know their version.
+ """
+ version = Version('foo', 2, 1, 0)
+ project = self.makeProject(version)
+ self.assertEquals(project.getVersion(), version)
+
+
+ def test_updateVersion(self):
+ """
+ Project objects know how to update the version numbers in those
+ projects.
+ """
+ project = self.makeProject(Version("bar", 2, 1, 0))
+ newVersion = Version("bar", 3, 2, 9)
+ project.updateVersion(newVersion)
+ self.assertEquals(project.getVersion(), newVersion)
+ self.assertEquals(
+ project.directory.child("topfiles").child("README").getContent(),
+ "3.2.9")
+
+
+ def test_repr(self):
+ """
+ The representation of a Project is Project(directory).
+ """
+ foo = Project(FilePath('bar'))
+ self.assertEqual(
+ repr(foo), 'Project(%r)' % (foo.directory))
+
+
+ def test_findTwistedStyleProjects(self):
+ """
+ findTwistedStyleProjects finds all projects underneath a particular
+ directory. A 'project' is defined by the existence of a 'topfiles'
+ directory and is returned as a Project object.
+ """
+ baseDirectory = self.makeProjects(
+ Version('foo', 2, 3, 0), Version('foo.bar', 0, 7, 4))
+ projects = findTwistedProjects(baseDirectory)
+ self.assertProjectsEqual(
+ projects,
+ [Project(baseDirectory.child('foo')),
+ Project(baseDirectory.child('foo').child('bar'))])
+
+
+ def test_updateTwistedVersionInformation(self):
+ """
+ Update Twisted version information in the top-level project and all of
+ the subprojects.
+ """
+ baseDirectory = FilePath(self.mktemp())
+ baseDirectory.createDirectory()
+ now = date.today()
+
+ projectName = 'foo'
+ oldVersion = Version(projectName, 2, 5, 0)
+ newVersion = getNextVersion(oldVersion, now=now)
+
+ project = self.makeProject(oldVersion, baseDirectory)
+
+ updateTwistedVersionInformation(baseDirectory, now=now)
+
+ self.assertEqual(project.getVersion(), newVersion)
+ self.assertEqual(
+ project.directory.child('topfiles').child('README').getContent(),
+ newVersion.base())
+
+
+
+class UtilityTest(TestCase):
+ """
+ Tests for various utility functions for releasing.
+ """
+
+ def test_chdir(self):
+ """
+ Test that the runChdirSafe is actually safe, i.e., it still
+ changes back to the original directory even if an error is
+ raised.
+ """
+ cwd = os.getcwd()
+ def chAndBreak():
+ os.mkdir('releaseCh')
+ os.chdir('releaseCh')
+ 1/0
+ self.assertRaises(ZeroDivisionError,
+ release.runChdirSafe, chAndBreak)
+ self.assertEquals(cwd, os.getcwd())
+
+
+
+ def test_replaceInFile(self):
+ """
+ L{replaceInFile} replaces data in a file based on a dict. A key from
+ the dict that is found in the file is replaced with the corresponding
+ value.
+ """
+ in_ = 'foo\nhey hey $VER\nbar\n'
+ outf = open('release.replace', 'w')
+ outf.write(in_)
+ outf.close()
+
+ expected = in_.replace('$VER', '2.0.0')
+ replaceInFile('release.replace', {'$VER': '2.0.0'})
+ self.assertEquals(open('release.replace').read(), expected)
+
+
+ expected = expected.replace('2.0.0', '3.0.0')
+ replaceInFile('release.replace', {'2.0.0': '3.0.0'})
+ self.assertEquals(open('release.replace').read(), expected)
+
+
+
+class VersionWritingTest(TestCase):
+ """
+ Tests for L{replaceProjectVersion}.
+ """
+
+ def test_replaceProjectVersion(self):
+ """
+ L{replaceProjectVersion} writes a Python file that defines a
+ C{version} variable that corresponds to the given name and version
+ number.
+ """
+ replaceProjectVersion("test_project",
+ Version("twisted.test_project", 0, 82, 7))
+ ns = {'__name___': 'twisted.test_project'}
+ execfile("test_project", ns)
+ self.assertEquals(ns["version"].base(), "0.82.7")
+
+
+ def test_replaceProjectVersionWithPrerelease(self):
+ """
+ L{replaceProjectVersion} will write a Version instantiation that
+ includes a prerelease parameter if necessary.
+ """
+ replaceProjectVersion("test_project",
+ Version("twisted.test_project", 0, 82, 7,
+ prerelease=8))
+ ns = {'__name___': 'twisted.test_project'}
+ execfile("test_project", ns)
+ self.assertEquals(ns["version"].base(), "0.82.7pre8")
+
+
+
+class BuilderTestsMixin(object):
+ """
+ A mixin class which provides various methods for creating sample Lore input
+ and output.
+
+ @cvar template: The lore template that will be used to prepare sample
+ output.
+ @type template: C{str}
+
+ @ivar docCounter: A counter which is incremented every time input is
+ generated and which is included in the documents.
+ @type docCounter: C{int}
+ """
+ template = '''
+ <html>
+ <head><title>Yo:</title></head>
+ <body>
+ <div class="body" />
+ <a href="index.html">Index</a>
+ <span class="version">Version: </span>
+ </body>
+ </html>
+ '''
+
+ def setUp(self):
+ """
+ Initialize the doc counter which ensures documents are unique.
+ """
+ self.docCounter = 0
+
+
+ def assertXMLEqual(self, first, second):
+ """
+ Verify that two strings represent the same XML document.
+ """
+ self.assertEqual(
+ dom.parseString(first).toxml(),
+ dom.parseString(second).toxml())
+
+
+ def getArbitraryOutput(self, version, counter, prefix="", apiBaseURL="%s"):
+ """
+ Get the correct HTML output for the arbitrary input returned by
+ L{getArbitraryLoreInput} for the given parameters.
+
+ @param version: The version string to include in the output.
+ @type version: C{str}
+ @param counter: A counter to include in the output.
+ @type counter: C{int}
+ """
+ document = """\
+<?xml version="1.0"?><html>
+ <head><title>Yo:Hi! Title: %(count)d</title></head>
+ <body>
+ <div class="content">Hi! %(count)d<div class="API"><a href="%(foobarLink)s" title="foobar">foobar</a></div></div>
+ <a href="%(prefix)sindex.html">Index</a>
+ <span class="version">Version: %(version)s</span>
+ </body>
+ </html>"""
+ # Try to normalize irrelevant whitespace.
+ return dom.parseString(
+ document % {"count": counter, "prefix": prefix,
+ "version": version,
+ "foobarLink": apiBaseURL % ("foobar",)}).toxml('utf-8')
+
+
+ def getArbitraryLoreInput(self, counter):
+ """
+ Get an arbitrary, unique (for this test case) string of lore input.
+
+ @param counter: A counter to include in the input.
+ @type counter: C{int}
+ """
+ template = (
+ '<html>'
+ '<head><title>Hi! Title: %(count)s</title></head>'
+ '<body>'
+ 'Hi! %(count)s'
+ '<div class="API">foobar</div>'
+ '</body>'
+ '</html>')
+ return template % {"count": counter}
+
+
+ def getArbitraryLoreInputAndOutput(self, version, prefix="",
+ apiBaseURL="%s"):
+ """
+ Get an input document along with expected output for lore run on that
+ output document, assuming an appropriately-specified C{self.template}.
+
+ @param version: A version string to include in the input and output.
+ @type version: C{str}
+ @param prefix: The prefix to include in the link to the index.
+ @type prefix: C{str}
+
+ @return: A two-tuple of input and expected output.
+ @rtype: C{(str, str)}.
+ """
+ self.docCounter += 1
+ return (self.getArbitraryLoreInput(self.docCounter),
+ self.getArbitraryOutput(version, self.docCounter,
+ prefix=prefix, apiBaseURL=apiBaseURL))
+
+
+ def getArbitraryManInput(self):
+ """
+ Get an arbitrary man page content.
+ """
+ return """.TH MANHOLE "1" "August 2001" "" ""
+.SH NAME
+manhole \- Connect to a Twisted Manhole service
+.SH SYNOPSIS
+.B manhole
+.SH DESCRIPTION
+manhole is a GTK interface to Twisted Manhole services. You can execute python
+code as if at an interactive Python console inside a running Twisted process
+with this."""
+
+
+ def getArbitraryManLoreOutput(self):
+ """
+ Get an arbitrary lore input document which represents man-to-lore
+ output based on the man page returned from L{getArbitraryManInput}
+ """
+ return """\
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html><head>
+<title>MANHOLE.1</title></head>
+<body>
+
+<h1>MANHOLE.1</h1>
+
+<h2>NAME</h2>
+
+<p>manhole - Connect to a Twisted Manhole service
+</p>
+
+<h2>SYNOPSIS</h2>
+
+<p><strong>manhole</strong> </p>
+
+<h2>DESCRIPTION</h2>
+
+<p>manhole is a GTK interface to Twisted Manhole services. You can execute python
+code as if at an interactive Python console inside a running Twisted process
+with this.</p>
+
+</body>
+</html>
+"""
+
+ def getArbitraryManHTMLOutput(self, version, prefix=""):
+ """
+ Get an arbitrary lore output document which represents the lore HTML
+ output based on the input document returned from
+ L{getArbitraryManLoreOutput}.
+
+ @param version: A version string to include in the document.
+ @type version: C{str}
+ @param prefix: The prefix to include in the link to the index.
+ @type prefix: C{str}
+ """
+ # Try to normalize the XML a little bit.
+ return dom.parseString("""\
+<?xml version="1.0" ?><html>
+ <head><title>Yo:MANHOLE.1</title></head>
+ <body>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>manhole - Connect to a Twisted Manhole service
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>manhole</strong> </p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>manhole is a GTK interface to Twisted Manhole services. You can execute python
+code as if at an interactive Python console inside a running Twisted process
+with this.</p>
+
+</div>
+ <a href="%(prefix)sindex.html">Index</a>
+ <span class="version">Version: %(version)s</span>
+ </body>
+ </html>""" % {
+ 'prefix': prefix, 'version': version}).toxml("utf-8")
+
+
+
+class DocBuilderTestCase(TestCase, BuilderTestsMixin):
+ """
+ Tests for L{DocBuilder}.
+
+ Note for future maintainers: The exact byte equality assertions throughout
+ this suite may need to be updated due to minor differences in lore. They
+ should not be taken to mean that Lore must maintain the same byte format
+ forever. Feel free to update the tests when Lore changes, but please be
+ careful.
+ """
+ skip = loreSkip
+
+ def setUp(self):
+ """
+ Set up a few instance variables that will be useful.
+
+ @ivar builder: A plain L{DocBuilder}.
+ @ivar docCounter: An integer to be used as a counter by the
+ C{getArbitrary...} methods.
+ @ivar howtoDir: A L{FilePath} representing a directory to be used for
+ containing Lore documents.
+ @ivar templateFile: A L{FilePath} representing a file with
+ C{self.template} as its content.
+ """
+ BuilderTestsMixin.setUp(self)
+ self.builder = DocBuilder()
+ self.howtoDir = FilePath(self.mktemp())
+ self.howtoDir.createDirectory()
+ self.templateFile = self.howtoDir.child("template.tpl")
+ self.templateFile.setContent(self.template)
+
+
+ def test_build(self):
+ """
+ The L{DocBuilder} runs lore on all .xhtml files within a directory.
+ """
+ version = "1.2.3"
+ input1, output1 = self.getArbitraryLoreInputAndOutput(version)
+ input2, output2 = self.getArbitraryLoreInputAndOutput(version)
+
+ self.howtoDir.child("one.xhtml").setContent(input1)
+ self.howtoDir.child("two.xhtml").setContent(input2)
+
+ self.builder.build(version, self.howtoDir, self.howtoDir,
+ self.templateFile)
+ out1 = self.howtoDir.child('one.html')
+ out2 = self.howtoDir.child('two.html')
+ self.assertXMLEqual(out1.getContent(), output1)
+ self.assertXMLEqual(out2.getContent(), output2)
+
+
+ def test_noDocumentsFound(self):
+ """
+ The C{build} method raises L{NoDocumentsFound} if there are no
+ .xhtml files in the given directory.
+ """
+ self.assertRaises(
+ NoDocumentsFound,
+ self.builder.build, "1.2.3", self.howtoDir, self.howtoDir,
+ self.templateFile)
+
+
+ def test_parentDocumentLinking(self):
+ """
+ The L{DocBuilder} generates correct links from documents to
+ template-generated links like stylesheets and index backreferences.
+ """
+ input = self.getArbitraryLoreInput(0)
+ tutoDir = self.howtoDir.child("tutorial")
+ tutoDir.createDirectory()
+ tutoDir.child("child.xhtml").setContent(input)
+ self.builder.build("1.2.3", self.howtoDir, tutoDir, self.templateFile)
+ outFile = tutoDir.child('child.html')
+ self.assertIn('<a href="../index.html">Index</a>',
+ outFile.getContent())
+
+
+ def test_siblingDirectoryDocumentLinking(self):
+ """
+ It is necessary to generate documentation in a directory foo/bar where
+ stylesheet and indexes are located in foo/baz. Such resources should be
+ appropriately linked to.
+ """
+ input = self.getArbitraryLoreInput(0)
+ resourceDir = self.howtoDir.child("resources")
+ docDir = self.howtoDir.child("docs")
+ docDir.createDirectory()
+ docDir.child("child.xhtml").setContent(input)
+ self.builder.build("1.2.3", resourceDir, docDir, self.templateFile)
+ outFile = docDir.child('child.html')
+ self.assertIn('<a href="../resources/index.html">Index</a>',
+ outFile.getContent())
+
+
+ def test_apiLinking(self):
+ """
+ The L{DocBuilder} generates correct links from documents to API
+ documentation.
+ """
+ version = "1.2.3"
+ input, output = self.getArbitraryLoreInputAndOutput(version)
+ self.howtoDir.child("one.xhtml").setContent(input)
+
+ self.builder.build(version, self.howtoDir, self.howtoDir,
+ self.templateFile, "scheme:apilinks/%s.ext")
+ out = self.howtoDir.child('one.html')
+ self.assertIn(
+ '<a href="scheme:apilinks/foobar.ext" title="foobar">foobar</a>',
+ out.getContent())
+
+
+ def test_deleteInput(self):
+ """
+ L{DocBuilder.build} can be instructed to delete the input files after
+ generating the output based on them.
+ """
+ input1 = self.getArbitraryLoreInput(0)
+ self.howtoDir.child("one.xhtml").setContent(input1)
+ self.builder.build("whatever", self.howtoDir, self.howtoDir,
+ self.templateFile, deleteInput=True)
+ self.assertTrue(self.howtoDir.child('one.html').exists())
+ self.assertFalse(self.howtoDir.child('one.xhtml').exists())
+
+
+ def test_doNotDeleteInput(self):
+ """
+ Input will not be deleted by default.
+ """
+ input1 = self.getArbitraryLoreInput(0)
+ self.howtoDir.child("one.xhtml").setContent(input1)
+ self.builder.build("whatever", self.howtoDir, self.howtoDir,
+ self.templateFile)
+ self.assertTrue(self.howtoDir.child('one.html').exists())
+ self.assertTrue(self.howtoDir.child('one.xhtml').exists())
+
+
+ def test_getLinkrelToSameDirectory(self):
+ """
+ If the doc and resource directories are the same, the linkrel should be
+ an empty string.
+ """
+ linkrel = self.builder.getLinkrel(FilePath("/foo/bar"),
+ FilePath("/foo/bar"))
+ self.assertEquals(linkrel, "")
+
+
+ def test_getLinkrelToParentDirectory(self):
+ """
+ If the doc directory is a child of the resource directory, the linkrel
+ should make use of '..'.
+ """
+ linkrel = self.builder.getLinkrel(FilePath("/foo"),
+ FilePath("/foo/bar"))
+ self.assertEquals(linkrel, "../")
+
+
+ def test_getLinkrelToSibling(self):
+ """
+ If the doc directory is a sibling of the resource directory, the
+ linkrel should make use of '..' and a named segment.
+ """
+ linkrel = self.builder.getLinkrel(FilePath("/foo/howto"),
+ FilePath("/foo/examples"))
+ self.assertEquals(linkrel, "../howto/")
+
+
+ def test_getLinkrelToUncle(self):
+ """
+ If the doc directory is a sibling of the parent of the resource
+ directory, the linkrel should make use of multiple '..'s and a named
+ segment.
+ """
+ linkrel = self.builder.getLinkrel(FilePath("/foo/howto"),
+ FilePath("/foo/examples/quotes"))
+ self.assertEquals(linkrel, "../../howto/")
+
+
+
+class APIBuilderTestCase(TestCase):
+ """
+ Tests for L{APIBuilder}.
+ """
+ skip = pydoctorSkip
+
+ def test_build(self):
+ """
+ L{APIBuilder.build} writes an index file which includes the name of the
+ project specified.
+ """
+ stdout = StringIO()
+ self.patch(sys, 'stdout', stdout)
+
+ projectName = "Foobar"
+ packageName = "quux"
+ projectURL = "scheme:project"
+ sourceURL = "scheme:source"
+ docstring = "text in docstring"
+ privateDocstring = "should also appear in output"
+
+ inputPath = FilePath(self.mktemp()).child(packageName)
+ inputPath.makedirs()
+ inputPath.child("__init__.py").setContent(
+ "def foo():\n"
+ " '%s'\n"
+ "def _bar():\n"
+ " '%s'" % (docstring, privateDocstring))
+
+ outputPath = FilePath(self.mktemp())
+ outputPath.makedirs()
+
+ builder = APIBuilder()
+ builder.build(projectName, projectURL, sourceURL, inputPath, outputPath)
+
+ indexPath = outputPath.child("index.html")
+ self.assertTrue(
+ indexPath.exists(),
+ "API index %r did not exist." % (outputPath.path,))
+ self.assertIn(
+ '<a href="%s">%s</a>' % (projectURL, projectName),
+ indexPath.getContent(),
+ "Project name/location not in file contents.")
+
+ quuxPath = outputPath.child("quux.html")
+ self.assertTrue(
+ quuxPath.exists(),
+ "Package documentation file %r did not exist." % (quuxPath.path,))
+ self.assertIn(
+ docstring, quuxPath.getContent(),
+ "Docstring not in package documentation file.")
+ self.assertIn(
+ '<a href="%s/%s">View Source</a>' % (sourceURL, packageName),
+ quuxPath.getContent())
+ self.assertIn(
+ '<a href="%s/%s/__init__.py#L1" class="functionSourceLink">' % (
+ sourceURL, packageName),
+ quuxPath.getContent())
+ self.assertIn(privateDocstring, quuxPath.getContent())
+
+ # There should also be a page for the foo function in quux.
+ self.assertTrue(quuxPath.sibling('quux.foo.html').exists())
+
+ self.assertEqual(stdout.getvalue(), '')
+
+
+ def test_buildWithPolicy(self):
+ """
+ L{BuildAPIDocsScript.buildAPIDocs} builds the API docs with values
+ appropriate for the Twisted project.
+ """
+ stdout = StringIO()
+ self.patch(sys, 'stdout', stdout)
+ docstring = "text in docstring"
+
+ projectRoot = FilePath(self.mktemp())
+ packagePath = projectRoot.child("twisted")
+ packagePath.makedirs()
+ packagePath.child("__init__.py").setContent(
+ "def foo():\n"
+ " '%s'\n" % (docstring,))
+ packagePath.child("_version.py").setContent(
+ genVersion("twisted", 1, 0, 0))
+ outputPath = FilePath(self.mktemp())
+
+ script = BuildAPIDocsScript()
+ script.buildAPIDocs(projectRoot, outputPath)
+
+ indexPath = outputPath.child("index.html")
+ self.assertTrue(
+ indexPath.exists(),
+ "API index %r did not exist." % (outputPath.path,))
+ self.assertIn(
+ '<a href="http://twistedmatrix.com/">Twisted</a>',
+ indexPath.getContent(),
+ "Project name/location not in file contents.")
+
+ twistedPath = outputPath.child("twisted.html")
+ self.assertTrue(
+ twistedPath.exists(),
+ "Package documentation file %r did not exist."
+ % (twistedPath.path,))
+ self.assertIn(
+ docstring, twistedPath.getContent(),
+ "Docstring not in package documentation file.")
+ #Here we check that it figured out the correct version based on the
+ #source code.
+ self.assertIn(
+ '<a href="http://twistedmatrix.com/trac/browser/tags/releases/'
+ 'twisted-1.0.0/twisted">View Source</a>',
+ twistedPath.getContent())
+
+ self.assertEqual(stdout.getvalue(), '')
+
+
+ def test_apiBuilderScriptMainRequiresTwoArguments(self):
+ """
+ SystemExit is raised when the incorrect number of command line
+ arguments are passed to the API building script.
+ """
+ script = BuildAPIDocsScript()
+ self.assertRaises(SystemExit, script.main, [])
+ self.assertRaises(SystemExit, script.main, ["foo"])
+ self.assertRaises(SystemExit, script.main, ["foo", "bar", "baz"])
+
+
+ def test_apiBuilderScriptMain(self):
+ """
+ The API building script invokes the same code that
+ L{test_buildWithPolicy} tests.
+ """
+ script = BuildAPIDocsScript()
+ calls = []
+ script.buildAPIDocs = lambda a, b: calls.append((a, b))
+ script.main(["hello", "there"])
+ self.assertEquals(calls, [(FilePath("hello"), FilePath("there"))])
+
+
+
+class ManBuilderTestCase(TestCase, BuilderTestsMixin):
+ """
+ Tests for L{ManBuilder}.
+ """
+ skip = loreSkip
+
+ def setUp(self):
+ """
+ Set up a few instance variables that will be useful.
+
+ @ivar builder: A plain L{ManBuilder}.
+ @ivar manDir: A L{FilePath} representing a directory to be used for
+ containing man pages.
+ """
+ BuilderTestsMixin.setUp(self)
+ self.builder = ManBuilder()
+ self.manDir = FilePath(self.mktemp())
+ self.manDir.createDirectory()
+
+
+ def test_noDocumentsFound(self):
+ """
+ L{ManBuilder.build} raises L{NoDocumentsFound} if there are no
+ .1 files in the given directory.
+ """
+ self.assertRaises(NoDocumentsFound, self.builder.build, self.manDir)
+
+
+ def test_build(self):
+ """
+ Check that L{ManBuilder.build} find the man page in the directory, and
+ successfully produce a Lore content.
+ """
+ manContent = self.getArbitraryManInput()
+ self.manDir.child('test1.1').setContent(manContent)
+ self.builder.build(self.manDir)
+ output = self.manDir.child('test1-man.xhtml').getContent()
+ expected = self.getArbitraryManLoreOutput()
+ # No-op on *nix, fix for windows
+ expected = expected.replace('\n', os.linesep)
+ self.assertEquals(output, expected)
+
+
+ def test_toHTML(self):
+ """
+ Check that the content output by C{build} is compatible as input of
+ L{DocBuilder.build}.
+ """
+ manContent = self.getArbitraryManInput()
+ self.manDir.child('test1.1').setContent(manContent)
+ self.builder.build(self.manDir)
+
+ templateFile = self.manDir.child("template.tpl")
+ templateFile.setContent(DocBuilderTestCase.template)
+ docBuilder = DocBuilder()
+ docBuilder.build("1.2.3", self.manDir, self.manDir,
+ templateFile)
+ output = self.manDir.child('test1-man.html').getContent()
+
+ self.assertXMLEqual(
+ output,
+ """\
+<?xml version="1.0" ?><html>
+ <head><title>Yo:MANHOLE.1</title></head>
+ <body>
+ <div class="content">
+
+<span/>
+
+<h2>NAME<a name="auto0"/></h2>
+
+<p>manhole - Connect to a Twisted Manhole service
+</p>
+
+<h2>SYNOPSIS<a name="auto1"/></h2>
+
+<p><strong>manhole</strong> </p>
+
+<h2>DESCRIPTION<a name="auto2"/></h2>
+
+<p>manhole is a GTK interface to Twisted Manhole services. You can execute python
+code as if at an interactive Python console inside a running Twisted process
+with this.</p>
+
+</div>
+ <a href="index.html">Index</a>
+ <span class="version">Version: 1.2.3</span>
+ </body>
+ </html>""")
+
+
+
+class BookBuilderTests(TestCase, BuilderTestsMixin):
+ """
+ Tests for L{BookBuilder}.
+ """
+ skip = latexSkip or popen4Skip or loreSkip
+
+ def setUp(self):
+ """
+ Make a directory into which to place temporary files.
+ """
+ self.docCounter = 0
+ self.howtoDir = FilePath(self.mktemp())
+ self.howtoDir.makedirs()
+ self.oldHandler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
+
+ def tearDown(self):
+ signal.signal(signal.SIGCHLD, self.oldHandler)
+
+
+ def getArbitraryOutput(self, version, counter, prefix="", apiBaseURL=None):
+ """
+ Create and return a C{str} containing the LaTeX document which is
+ expected as the output for processing the result of the document
+ returned by C{self.getArbitraryLoreInput(counter)}.
+ """
+ path = self.howtoDir.child("%d.xhtml" % (counter,)).path
+ return (
+ r'\section{Hi! Title: %(count)s\label{%(path)s}}'
+ '\n'
+ r'Hi! %(count)sfoobar') % {'count': counter, 'path': path}
+
+
+ def test_runSuccess(self):
+ """
+ L{BookBuilder.run} executes the command it is passed and returns a
+ string giving the stdout and stderr of the command if it completes
+ successfully.
+ """
+ builder = BookBuilder()
+ self.assertEqual(builder.run("echo hi; echo bye 1>&2"), "hi\nbye\n")
+
+
+ def test_runFailed(self):
+ """
+ L{BookBuilder.run} executes the command it is passed and raises
+ L{CommandFailed} if it completes unsuccessfully.
+ """
+ builder = BookBuilder()
+ exc = self.assertRaises(CommandFailed, builder.run, "echo hi; false")
+ self.assertNotEqual(os.WEXITSTATUS(exc.exitCode), 0)
+ self.assertEqual(exc.output, "hi\n")
+
+
+ def test_runSignaled(self):
+ """
+ L{BookBuilder.run} executes the command it is passed and raises
+ L{CommandFailed} if it exits due to a signal.
+ """
+ builder = BookBuilder()
+ exc = self.assertRaises(
+ # This is only a little bit too tricky.
+ CommandFailed, builder.run, "echo hi; exec kill -9 $$")
+ self.assertTrue(os.WIFSIGNALED(exc.exitCode))
+ self.assertEqual(os.WTERMSIG(exc.exitCode), signal.SIGKILL)
+ self.assertEqual(exc.output, "hi\n")
+
+
+ def test_buildTeX(self):
+ """
+ L{BookBuilder.buildTeX} writes intermediate TeX files for all lore
+ input files in a directory.
+ """
+ version = "3.2.1"
+ input1, output1 = self.getArbitraryLoreInputAndOutput(version)
+ input2, output2 = self.getArbitraryLoreInputAndOutput(version)
+
+ # Filenames are chosen by getArbitraryOutput to match the counter used
+ # by getArbitraryLoreInputAndOutput.
+ self.howtoDir.child("1.xhtml").setContent(input1)
+ self.howtoDir.child("2.xhtml").setContent(input2)
+
+ builder = BookBuilder()
+ builder.buildTeX(self.howtoDir)
+ self.assertEqual(self.howtoDir.child("1.tex").getContent(), output1)
+ self.assertEqual(self.howtoDir.child("2.tex").getContent(), output2)
+
+
+ def test_buildTeXRejectsInvalidDirectory(self):
+ """
+ L{BookBuilder.buildTeX} raises L{ValueError} if passed a directory
+ which does not exist.
+ """
+ builder = BookBuilder()
+ self.assertRaises(
+ ValueError, builder.buildTeX, self.howtoDir.temporarySibling())
+
+
+ def test_buildTeXOnlyBuildsXHTML(self):
+ """
+ L{BookBuilder.buildTeX} ignores files which which don't end with
+ ".xhtml".
+ """
+ # Hopefully ">" is always a parse error from microdom!
+ self.howtoDir.child("not-input.dat").setContent(">")
+ self.test_buildTeX()
+
+
+ def test_stdout(self):
+ """
+ L{BookBuilder.buildTeX} does not write to stdout.
+ """
+ stdout = StringIO()
+ self.patch(sys, 'stdout', stdout)
+
+ # Suppress warnings so that if there are any old-style plugins that
+ # lore queries for don't confuse the assertion below. See #3070.
+ self.patch(warnings, 'warn', lambda *a, **kw: None)
+ self.test_buildTeX()
+ self.assertEqual(stdout.getvalue(), '')
+
+
+ def test_buildPDFRejectsInvalidBookFilename(self):
+ """
+ L{BookBuilder.buildPDF} raises L{ValueError} if the book filename does
+ not end with ".tex".
+ """
+ builder = BookBuilder()
+ self.assertRaises(
+ ValueError,
+ builder.buildPDF,
+ FilePath(self.mktemp()).child("foo"),
+ None,
+ None)
+
+
+ def _setupTeXFiles(self):
+ sections = range(3)
+ self._setupTeXSections(sections)
+ return self._setupTeXBook(sections)
+
+
+ def _setupTeXSections(self, sections):
+ for texSectionNumber in sections:
+ texPath = self.howtoDir.child("%d.tex" % (texSectionNumber,))
+ texPath.setContent(self.getArbitraryOutput(
+ "1.2.3", texSectionNumber))
+
+
+ def _setupTeXBook(self, sections):
+ bookTeX = self.howtoDir.child("book.tex")
+ bookTeX.setContent(
+ r"\documentclass{book}" "\n"
+ r"\begin{document}" "\n" +
+ "\n".join([r"\input{%d.tex}" % (n,) for n in sections]) +
+ r"\end{document}" "\n")
+ return bookTeX
+
+
+ def test_buildPDF(self):
+ """
+ L{BookBuilder.buildPDF} creates a PDF given an index tex file and a
+ directory containing .tex files.
+ """
+ bookPath = self._setupTeXFiles()
+ outputPath = FilePath(self.mktemp())
+
+ builder = BookBuilder()
+ builder.buildPDF(bookPath, self.howtoDir, outputPath)
+
+ self.assertTrue(outputPath.exists())
+
+
+ def test_buildPDFLongPath(self):
+ """
+ L{BookBuilder.buildPDF} succeeds even if the paths it is operating on
+ are very long.
+
+ C{ps2pdf13} seems to have problems when path names are long. This test
+ verifies that even if inputs have long paths, generation still
+ succeeds.
+ """
+ # Make it long.
+ self.howtoDir = self.howtoDir.child("x" * 128).child("x" * 128).child("x" * 128)
+ self.howtoDir.makedirs()
+
+ # This will use the above long path.
+ bookPath = self._setupTeXFiles()
+ outputPath = FilePath(self.mktemp())
+
+ builder = BookBuilder()
+ builder.buildPDF(bookPath, self.howtoDir, outputPath)
+
+ self.assertTrue(outputPath.exists())
+
+
+ def test_buildPDFRunsLaTeXThreeTimes(self):
+ """
+ L{BookBuilder.buildPDF} runs C{latex} three times.
+ """
+ class InspectableBookBuilder(BookBuilder):
+ def __init__(self):
+ BookBuilder.__init__(self)
+ self.commands = []
+
+ def run(self, command):
+ """
+ Record the command and then execute it.
+ """
+ self.commands.append(command)
+ return BookBuilder.run(self, command)
+
+ bookPath = self._setupTeXFiles()
+ outputPath = FilePath(self.mktemp())
+
+ builder = InspectableBookBuilder()
+ builder.buildPDF(bookPath, self.howtoDir, outputPath)
+
+ # These string comparisons are very fragile. It would be better to
+ # have a test which asserted the correctness of the contents of the
+ # output files. I don't know how one could do that, though. -exarkun
+ latex1, latex2, latex3, dvips, ps2pdf13 = builder.commands
+ self.assertEqual(latex1, latex2)
+ self.assertEqual(latex2, latex3)
+ self.assertTrue(
+ latex1.startswith("latex "),
+ "LaTeX command %r does not start with 'latex '" % (latex1,))
+ self.assertTrue(
+ latex1.endswith(" " + bookPath.path),
+ "LaTeX command %r does not end with the book path (%r)." % (
+ latex1, bookPath.path))
+
+ self.assertTrue(
+ dvips.startswith("dvips "),
+ "dvips command %r does not start with 'dvips '" % (dvips,))
+ self.assertTrue(
+ ps2pdf13.startswith("ps2pdf13 "),
+ "ps2pdf13 command %r does not start with 'ps2pdf13 '" % (
+ ps2pdf13,))
+
+
+ def test_noSideEffects(self):
+ """
+ The working directory is the same before and after a call to
+ L{BookBuilder.buildPDF}. Also the contents of the directory containing
+ the input book are the same before and after the call.
+ """
+ startDir = os.getcwd()
+ bookTeX = self._setupTeXFiles()
+ startTeXSiblings = bookTeX.parent().children()
+ startHowtoChildren = self.howtoDir.children()
+
+ builder = BookBuilder()
+ builder.buildPDF(bookTeX, self.howtoDir, FilePath(self.mktemp()))
+
+ self.assertEqual(startDir, os.getcwd())
+ self.assertEqual(startTeXSiblings, bookTeX.parent().children())
+ self.assertEqual(startHowtoChildren, self.howtoDir.children())
+
+
+ def test_failedCommandProvidesOutput(self):
+ """
+ If a subprocess fails, L{BookBuilder.buildPDF} raises L{CommandFailed}
+ with the subprocess's output and leaves the temporary directory as a
+ sibling of the book path.
+ """
+ bookTeX = FilePath(self.mktemp() + ".tex")
+ builder = BookBuilder()
+ inputState = bookTeX.parent().children()
+ exc = self.assertRaises(
+ CommandFailed,
+ builder.buildPDF,
+ bookTeX, self.howtoDir, FilePath(self.mktemp()))
+ self.assertTrue(exc.output)
+ newOutputState = set(bookTeX.parent().children()) - set(inputState)
+ self.assertEqual(len(newOutputState), 1)
+ workPath = newOutputState.pop()
+ self.assertTrue(
+ workPath.isdir(),
+ "Expected work path %r was not a directory." % (workPath.path,))
+
+
+ def test_build(self):
+ """
+ L{BookBuilder.build} generates a pdf book file from some lore input
+ files.
+ """
+ sections = range(1, 4)
+ for sectionNumber in sections:
+ self.howtoDir.child("%d.xhtml" % (sectionNumber,)).setContent(
+ self.getArbitraryLoreInput(sectionNumber))
+ bookTeX = self._setupTeXBook(sections)
+ bookPDF = FilePath(self.mktemp())
+
+ builder = BookBuilder()
+ builder.build(self.howtoDir, [self.howtoDir], bookTeX, bookPDF)
+
+ self.assertTrue(bookPDF.exists())
+
+
+ def test_buildRemovesTemporaryLaTeXFiles(self):
+ """
+ L{BookBuilder.build} removes the intermediate LaTeX files it creates.
+ """
+ sections = range(1, 4)
+ for sectionNumber in sections:
+ self.howtoDir.child("%d.xhtml" % (sectionNumber,)).setContent(
+ self.getArbitraryLoreInput(sectionNumber))
+ bookTeX = self._setupTeXBook(sections)
+ bookPDF = FilePath(self.mktemp())
+
+ builder = BookBuilder()
+ builder.build(self.howtoDir, [self.howtoDir], bookTeX, bookPDF)
+
+ self.assertEqual(
+ set(self.howtoDir.listdir()),
+ set([bookTeX.basename()] + ["%d.xhtml" % (n,) for n in sections]))
+
+
+
+class FilePathDeltaTest(TestCase):
+ """
+ Tests for L{filePathDelta}.
+ """
+
+ def test_filePathDeltaSubdir(self):
+ """
+ L{filePathDelta} can create a simple relative path to a child path.
+ """
+ self.assertEquals(filePathDelta(FilePath("/foo/bar"),
+ FilePath("/foo/bar/baz")),
+ ["baz"])
+
+
+ def test_filePathDeltaSiblingDir(self):
+ """
+ L{filePathDelta} can traverse upwards to create relative paths to
+ siblings.
+ """
+ self.assertEquals(filePathDelta(FilePath("/foo/bar"),
+ FilePath("/foo/baz")),
+ ["..", "baz"])
+
+
+ def test_filePathNoCommonElements(self):
+ """
+ L{filePathDelta} can create relative paths to totally unrelated paths
+ for maximum portability.
+ """
+ self.assertEquals(filePathDelta(FilePath("/foo/bar"),
+ FilePath("/baz/quux")),
+ ["..", "..", "baz", "quux"])
+
+
+ def test_filePathDeltaSimilarEndElements(self):
+ """
+ L{filePathDelta} doesn't take into account final elements when
+ comparing 2 paths, but stops at the first difference.
+ """
+ self.assertEquals(filePathDelta(FilePath("/foo/bar/bar/spam"),
+ FilePath("/foo/bar/baz/spam")),
+ ["..", "..", "baz", "spam"])
+
+
+
+class NewsBuilderTests(TestCase, StructureAssertingMixin):
+ """
+ Tests for L{NewsBuilder}.
+ """
+ def setUp(self):
+ """
+ Create a fake project and stuff some basic structure and content into
+ it.
+ """
+ self.builder = NewsBuilder()
+ self.project = FilePath(self.mktemp())
+ self.project.createDirectory()
+ self.existingText = 'Here is stuff which was present previously.\n'
+ self.createStructure(self.project, {
+ 'NEWS': self.existingText,
+ '5.feature': 'We now support the web.\n',
+ '12.feature': 'The widget is more robust.\n',
+ '15.feature': (
+ 'A very long feature which takes many words to '
+ 'describe with any accuracy was introduced so that '
+ 'the line wrapping behavior of the news generating '
+ 'code could be verified.\n'),
+ '16.feature': (
+ 'A simpler feature\ndescribed on multiple lines\n'
+ 'was added.\n'),
+ '23.bugfix': 'Broken stuff was fixed.\n',
+ '25.removal': 'Stupid stuff was deprecated.\n',
+ '30.misc': '',
+ '35.misc': ''})
+
+
+ def test_today(self):
+ """
+ L{NewsBuilder._today} returns today's date in YYYY-MM-DD form.
+ """
+ self.assertEquals(
+ self.builder._today(), date.today().strftime('%Y-%m-%d'))
+
+
+ def test_findFeatures(self):
+ """
+ When called with L{NewsBuilder._FEATURE}, L{NewsBuilder._findChanges}
+ returns a list of bugfix ticket numbers and descriptions as a list of
+ two-tuples.
+ """
+ features = self.builder._findChanges(
+ self.project, self.builder._FEATURE)
+ self.assertEquals(
+ features,
+ [(5, "We now support the web."),
+ (12, "The widget is more robust."),
+ (15,
+ "A very long feature which takes many words to describe with "
+ "any accuracy was introduced so that the line wrapping behavior "
+ "of the news generating code could be verified."),
+ (16, "A simpler feature described on multiple lines was added.")])
+
+
+ def test_findBugfixes(self):
+ """
+ When called with L{NewsBuilder._BUGFIX}, L{NewsBuilder._findChanges}
+ returns a list of bugfix ticket numbers and descriptions as a list of
+ two-tuples.
+ """
+ bugfixes = self.builder._findChanges(
+ self.project, self.builder._BUGFIX)
+ self.assertEquals(
+ bugfixes,
+ [(23, 'Broken stuff was fixed.')])
+
+
+ def test_findRemovals(self):
+ """
+ When called with L{NewsBuilder._REMOVAL}, L{NewsBuilder._findChanges}
+ returns a list of removal/deprecation ticket numbers and descriptions
+ as a list of two-tuples.
+ """
+ removals = self.builder._findChanges(
+ self.project, self.builder._REMOVAL)
+ self.assertEquals(
+ removals,
+ [(25, 'Stupid stuff was deprecated.')])
+
+
+ def test_findMiscellaneous(self):
+ """
+ When called with L{NewsBuilder._MISC}, L{NewsBuilder._findChanges}
+ returns a list of removal/deprecation ticket numbers and descriptions
+ as a list of two-tuples.
+ """
+ misc = self.builder._findChanges(
+ self.project, self.builder._MISC)
+ self.assertEquals(
+ misc,
+ [(30, ''),
+ (35, '')])
+
+
+ def test_writeHeader(self):
+ """
+ L{NewsBuilder._writeHeader} accepts a file-like object opened for
+ writing and a header string and writes out a news file header to it.
+ """
+ output = StringIO()
+ self.builder._writeHeader(output, "Super Awesometastic 32.16")
+ self.assertEquals(
+ output.getvalue(),
+ "Super Awesometastic 32.16\n"
+ "=========================\n"
+ "\n")
+
+
+ def test_writeSection(self):
+ """
+ L{NewsBuilder._writeSection} accepts a file-like object opened for
+ writing, a section name, and a list of ticket information (as returned
+ by L{NewsBuilder._findChanges}) and writes out a section header and all
+ of the given ticket information.
+ """
+ output = StringIO()
+ self.builder._writeSection(
+ output, "Features",
+ [(3, "Great stuff."),
+ (17, "Very long line which goes on and on and on, seemingly "
+ "without end until suddenly without warning it does end.")])
+ self.assertEquals(
+ output.getvalue(),
+ "Features\n"
+ "--------\n"
+ " - Great stuff. (#3)\n"
+ " - Very long line which goes on and on and on, seemingly without end\n"
+ " until suddenly without warning it does end. (#17)\n"
+ "\n")
+
+
+ def test_writeMisc(self):
+ """
+ L{NewsBuilder._writeMisc} accepts a file-like object opened for
+ writing, a section name, and a list of ticket information (as returned
+ by L{NewsBuilder._findChanges} and writes out a section header and all
+ of the ticket numbers, but excludes any descriptions.
+ """
+ output = StringIO()
+ self.builder._writeMisc(
+ output, "Other",
+ [(x, "") for x in range(2, 50, 3)])
+ self.assertEquals(
+ output.getvalue(),
+ "Other\n"
+ "-----\n"
+ " - #2, #5, #8, #11, #14, #17, #20, #23, #26, #29, #32, #35, #38, #41,\n"
+ " #44, #47\n"
+ "\n")
+
+
+ def test_build(self):
+ """
+ L{NewsBuilder.build} updates a NEWS file with new features based on the
+ I{<ticket>.feature} files found in the directory specified.
+ """
+ self.builder.build(
+ self.project, self.project.child('NEWS'),
+ "Super Awesometastic 32.16")
+
+ results = self.project.child('NEWS').getContent()
+ self.assertEquals(
+ results,
+ 'Super Awesometastic 32.16\n'
+ '=========================\n'
+ '\n'
+ 'Features\n'
+ '--------\n'
+ ' - We now support the web. (#5)\n'
+ ' - The widget is more robust. (#12)\n'
+ ' - A very long feature which takes many words to describe with any\n'
+ ' accuracy was introduced so that the line wrapping behavior of the\n'
+ ' news generating code could be verified. (#15)\n'
+ ' - A simpler feature described on multiple lines was added. (#16)\n'
+ '\n'
+ 'Bugfixes\n'
+ '--------\n'
+ ' - Broken stuff was fixed. (#23)\n'
+ '\n'
+ 'Deprecations and Removals\n'
+ '-------------------------\n'
+ ' - Stupid stuff was deprecated. (#25)\n'
+ '\n'
+ 'Other\n'
+ '-----\n'
+ ' - #30, #35\n'
+ '\n' + self.existingText)
+
+
+ def test_preserveTicketHint(self):
+ """
+ If a I{NEWS} file begins with the two magic lines which point readers
+ at the issue tracker, those lines are kept at the top of the new file.
+ """
+ news = self.project.child('NEWS')
+ news.setContent(
+ 'Ticket numbers in this file can be looked up by visiting\n'
+ 'http://twistedmatrix.com/trac/ticket/<number>\n'
+ '\n'
+ 'Blah blah other stuff.\n')
+
+ self.builder.build(self.project, news, "Super Awesometastic 32.16")
+
+ self.assertEquals(
+ news.getContent(),
+ 'Ticket numbers in this file can be looked up by visiting\n'
+ 'http://twistedmatrix.com/trac/ticket/<number>\n'
+ '\n'
+ 'Super Awesometastic 32.16\n'
+ '=========================\n'
+ '\n'
+ 'Features\n'
+ '--------\n'
+ ' - We now support the web. (#5)\n'
+ ' - The widget is more robust. (#12)\n'
+ ' - A very long feature which takes many words to describe with any\n'
+ ' accuracy was introduced so that the line wrapping behavior of the\n'
+ ' news generating code could be verified. (#15)\n'
+ ' - A simpler feature described on multiple lines was added. (#16)\n'
+ '\n'
+ 'Bugfixes\n'
+ '--------\n'
+ ' - Broken stuff was fixed. (#23)\n'
+ '\n'
+ 'Deprecations and Removals\n'
+ '-------------------------\n'
+ ' - Stupid stuff was deprecated. (#25)\n'
+ '\n'
+ 'Other\n'
+ '-----\n'
+ ' - #30, #35\n'
+ '\n'
+ 'Blah blah other stuff.\n')
+
+
+ def test_emptySectionsOmitted(self):
+ """
+ If there are no changes of a particular type (feature, bugfix, etc), no
+ section for that type is written by L{NewsBuilder.build}.
+ """
+ for ticket in self.project.children():
+ basename = ticket.basename()
+ if basename.endswith('.feature') or basename.endswith('.misc'):
+ ticket.remove()
+
+ self.builder.build(
+ self.project, self.project.child('NEWS'),
+ 'Some Thing 1.2')
+
+ self.assertEquals(
+ self.project.child('NEWS').getContent(),
+ 'Some Thing 1.2\n'
+ '==============\n'
+ '\n'
+ 'Bugfixes\n'
+ '--------\n'
+ ' - Broken stuff was fixed. (#23)\n'
+ '\n'
+ 'Deprecations and Removals\n'
+ '-------------------------\n'
+ ' - Stupid stuff was deprecated. (#25)\n'
+ '\n'
+ 'Here is stuff which was present previously.\n')
+
+
+ def test_duplicatesMerged(self):
+ """
+ If two change files have the same contents, they are merged in the
+ generated news entry.
+ """
+ def feature(s):
+ return self.project.child(s + '.feature')
+ feature('5').copyTo(feature('15'))
+ feature('5').copyTo(feature('16'))
+
+ self.builder.build(
+ self.project, self.project.child('NEWS'),
+ 'Project Name 5.0')
+
+ self.assertEquals(
+ self.project.child('NEWS').getContent(),
+ 'Project Name 5.0\n'
+ '================\n'
+ '\n'
+ 'Features\n'
+ '--------\n'
+ ' - We now support the web. (#5, #15, #16)\n'
+ ' - The widget is more robust. (#12)\n'
+ '\n'
+ 'Bugfixes\n'
+ '--------\n'
+ ' - Broken stuff was fixed. (#23)\n'
+ '\n'
+ 'Deprecations and Removals\n'
+ '-------------------------\n'
+ ' - Stupid stuff was deprecated. (#25)\n'
+ '\n'
+ 'Other\n'
+ '-----\n'
+ ' - #30, #35\n'
+ '\n'
+ 'Here is stuff which was present previously.\n')
+
+
+ def test_buildAll(self):
+ """
+ L{NewsBuilder.buildAll} calls L{NewsBuilder.build} once for each
+ subproject, passing that subproject's I{topfiles} directory as C{path},
+ the I{NEWS} file in that directory as C{output}, and the subproject's
+ name as C{header}, and then again for each subproject with the
+ top-level I{NEWS} file for C{output}.
+ """
+ builds = []
+ builder = NewsBuilder()
+ builder.build = lambda path, output, header: builds.append((
+ path, output, header))
+ builder._today = lambda: '2009-12-01'
+
+ # Create a fake looking Twisted project to build from
+ project = FilePath(self.mktemp()).child("twisted")
+ project.makedirs()
+ self.createStructure(project, {
+ 'NEWS': 'Old boring stuff from the past.\n',
+ '_version.py': genVersion("twisted", 1, 2, 3),
+ 'topfiles': {
+ 'NEWS': 'Old core news.\n',
+ '3.feature': 'Third feature addition.\n',
+ '5.misc': ''},
+ 'conch': {
+ '_version.py': genVersion("twisted.conch", 3, 4, 5),
+ 'topfiles': {
+ 'NEWS': 'Old conch news.\n',
+ '7.bugfix': 'Fixed that bug.\n'}}})
+
+ builder.buildAll(project)
+
+ coreTopfiles = project.child("topfiles")
+ coreNews = coreTopfiles.child("NEWS")
+ coreHeader = "Twisted Core 1.2.3 (2009-12-01)"
+
+ conchTopfiles = project.child("conch").child("topfiles")
+ conchNews = conchTopfiles.child("NEWS")
+ conchHeader = "Twisted Conch 3.4.5 (2009-12-01)"
+
+ aggregateNews = project.child("NEWS")
+
+ self.assertEquals(
+ builds,
+ [(conchTopfiles, conchNews, conchHeader),
+ (coreTopfiles, coreNews, coreHeader),
+ (conchTopfiles, aggregateNews, conchHeader),
+ (coreTopfiles, aggregateNews, coreHeader)])
+
+
+
+class DistributionBuilderTestBase(BuilderTestsMixin, StructureAssertingMixin,
+ TestCase):
+ """
+ Base for tests of L{DistributionBuilder}.
+ """
+ skip = loreSkip
+
+ def setUp(self):
+ BuilderTestsMixin.setUp(self)
+
+ self.rootDir = FilePath(self.mktemp())
+ self.rootDir.createDirectory()
+
+ self.outputDir = FilePath(self.mktemp())
+ self.outputDir.createDirectory()
+ self.builder = DistributionBuilder(self.rootDir, self.outputDir)
+
+
+
+class DistributionBuilderTest(DistributionBuilderTestBase):
+
+ def test_twistedDistribution(self):
+ """
+ The Twisted tarball contains everything in the source checkout, with
+ built documentation.
+ """
+ loreInput, loreOutput = self.getArbitraryLoreInputAndOutput("10.0.0")
+ manInput1 = self.getArbitraryManInput()
+ manOutput1 = self.getArbitraryManHTMLOutput("10.0.0", "../howto/")
+ manInput2 = self.getArbitraryManInput()
+ manOutput2 = self.getArbitraryManHTMLOutput("10.0.0", "../howto/")
+ coreIndexInput, coreIndexOutput = self.getArbitraryLoreInputAndOutput(
+ "10.0.0", prefix="howto/")
+
+ structure = {
+ "README": "Twisted",
+ "unrelated": "x",
+ "LICENSE": "copyright!",
+ "setup.py": "import toplevel",
+ "bin": {"web": {"websetroot": "SET ROOT"},
+ "twistd": "TWISTD"},
+ "twisted":
+ {"web":
+ {"__init__.py": "import WEB",
+ "topfiles": {"setup.py": "import WEBINSTALL",
+ "README": "WEB!"}},
+ "words": {"__init__.py": "import WORDS"},
+ "plugins": {"twisted_web.py": "import WEBPLUG",
+ "twisted_words.py": "import WORDPLUG"}},
+ "doc": {"web": {"howto": {"index.xhtml": loreInput},
+ "man": {"websetroot.1": manInput2}},
+ "core": {"howto": {"template.tpl": self.template},
+ "man": {"twistd.1": manInput1},
+ "index.xhtml": coreIndexInput}}}
+
+ outStructure = {
+ "README": "Twisted",
+ "unrelated": "x",
+ "LICENSE": "copyright!",
+ "setup.py": "import toplevel",
+ "bin": {"web": {"websetroot": "SET ROOT"},
+ "twistd": "TWISTD"},
+ "twisted":
+ {"web": {"__init__.py": "import WEB",
+ "topfiles": {"setup.py": "import WEBINSTALL",
+ "README": "WEB!"}},
+ "words": {"__init__.py": "import WORDS"},
+ "plugins": {"twisted_web.py": "import WEBPLUG",
+ "twisted_words.py": "import WORDPLUG"}},
+ "doc": {"web": {"howto": {"index.html": loreOutput},
+ "man": {"websetroot.1": manInput2,
+ "websetroot-man.html": manOutput2}},
+ "core": {"howto": {"template.tpl": self.template},
+ "man": {"twistd.1": manInput1,
+ "twistd-man.html": manOutput1},
+ "index.html": coreIndexOutput}}}
+
+ self.createStructure(self.rootDir, structure)
+
+ outputFile = self.builder.buildTwisted("10.0.0")
+
+ self.assertExtractedStructure(outputFile, outStructure)
+
+
+ def test_twistedDistributionExcludesWeb2AndVFSAndAdmin(self):
+ """
+ The main Twisted distribution does not include web2 or vfs, or the
+ bin/admin directory.
+ """
+ loreInput, loreOutput = self.getArbitraryLoreInputAndOutput("10.0.0")
+ coreIndexInput, coreIndexOutput = self.getArbitraryLoreInputAndOutput(
+ "10.0.0", prefix="howto/")
+
+ structure = {
+ "README": "Twisted",
+ "unrelated": "x",
+ "LICENSE": "copyright!",
+ "setup.py": "import toplevel",
+ "bin": {"web2": {"websetroot": "SET ROOT"},
+ "vfs": {"vfsitup": "hee hee"},
+ "twistd": "TWISTD",
+ "admin": {"build-a-thing": "yay"}},
+ "twisted":
+ {"web2":
+ {"__init__.py": "import WEB",
+ "topfiles": {"setup.py": "import WEBINSTALL",
+ "README": "WEB!"}},
+ "vfs":
+ {"__init__.py": "import VFS",
+ "blah blah": "blah blah"},
+ "words": {"__init__.py": "import WORDS"},
+ "plugins": {"twisted_web.py": "import WEBPLUG",
+ "twisted_words.py": "import WORDPLUG",
+ "twisted_web2.py": "import WEB2",
+ "twisted_vfs.py": "import VFS"}},
+ "doc": {"web2": {"excluded!": "yay"},
+ "vfs": {"unrelated": "whatever"},
+ "core": {"howto": {"template.tpl": self.template},
+ "index.xhtml": coreIndexInput}}}
+
+ outStructure = {
+ "README": "Twisted",
+ "unrelated": "x",
+ "LICENSE": "copyright!",
+ "setup.py": "import toplevel",
+ "bin": {"twistd": "TWISTD"},
+ "twisted":
+ {"words": {"__init__.py": "import WORDS"},
+ "plugins": {"twisted_web.py": "import WEBPLUG",
+ "twisted_words.py": "import WORDPLUG"}},
+ "doc": {"core": {"howto": {"template.tpl": self.template},
+ "index.html": coreIndexOutput}}}
+ self.createStructure(self.rootDir, structure)
+
+ outputFile = self.builder.buildTwisted("10.0.0")
+
+ self.assertExtractedStructure(outputFile, outStructure)
+
+
+ def test_subProjectLayout(self):
+ """
+ The subproject tarball includes files like so:
+
+ 1. twisted/<subproject>/topfiles defines the files that will be in the
+ top level in the tarball, except LICENSE, which comes from the real
+ top-level directory.
+ 2. twisted/<subproject> is included, but without the topfiles entry
+ in that directory. No other twisted subpackages are included.
+ 3. twisted/plugins/twisted_<subproject>.py is included, but nothing
+ else in plugins is.
+ """
+ structure = {
+ "README": "HI!@",
+ "unrelated": "x",
+ "LICENSE": "copyright!",
+ "setup.py": "import toplevel",
+ "bin": {"web": {"websetroot": "SET ROOT"},
+ "words": {"im": "#!im"}},
+ "twisted":
+ {"web":
+ {"__init__.py": "import WEB",
+ "topfiles": {"setup.py": "import WEBINSTALL",
+ "README": "WEB!"}},
+ "words": {"__init__.py": "import WORDS"},
+ "plugins": {"twisted_web.py": "import WEBPLUG",
+ "twisted_words.py": "import WORDPLUG"}}}
+
+ outStructure = {
+ "README": "WEB!",
+ "LICENSE": "copyright!",
+ "setup.py": "import WEBINSTALL",
+ "bin": {"websetroot": "SET ROOT"},
+ "twisted": {"web": {"__init__.py": "import WEB"},
+ "plugins": {"twisted_web.py": "import WEBPLUG"}}}
+
+ self.createStructure(self.rootDir, structure)
+
+ outputFile = self.builder.buildSubProject("web", "0.3.0")
+
+ self.assertExtractedStructure(outputFile, outStructure)
+
+
+ def test_minimalSubProjectLayout(self):
+ """
+ buildSubProject should work with minimal subprojects.
+ """
+ structure = {
+ "LICENSE": "copyright!",
+ "bin": {},
+ "twisted":
+ {"web": {"__init__.py": "import WEB",
+ "topfiles": {"setup.py": "import WEBINSTALL"}},
+ "plugins": {}}}
+
+ outStructure = {
+ "setup.py": "import WEBINSTALL",
+ "LICENSE": "copyright!",
+ "twisted": {"web": {"__init__.py": "import WEB"}}}
+
+ self.createStructure(self.rootDir, structure)
+
+ outputFile = self.builder.buildSubProject("web", "0.3.0")
+
+ self.assertExtractedStructure(outputFile, outStructure)
+
+
+ def test_subProjectDocBuilding(self):
+ """
+ When building a subproject release, documentation should be built with
+ lore.
+ """
+ loreInput, loreOutput = self.getArbitraryLoreInputAndOutput("0.3.0")
+ manInput = self.getArbitraryManInput()
+ manOutput = self.getArbitraryManHTMLOutput("0.3.0", "../howto/")
+ structure = {
+ "LICENSE": "copyright!",
+ "twisted": {"web": {"__init__.py": "import WEB",
+ "topfiles": {"setup.py": "import WEBINST"}}},
+ "doc": {"web": {"howto": {"index.xhtml": loreInput},
+ "man": {"twistd.1": manInput}},
+ "core": {"howto": {"template.tpl": self.template}}
+ }
+ }
+
+ outStructure = {
+ "LICENSE": "copyright!",
+ "setup.py": "import WEBINST",
+ "twisted": {"web": {"__init__.py": "import WEB"}},
+ "doc": {"howto": {"index.html": loreOutput},
+ "man": {"twistd.1": manInput,
+ "twistd-man.html": manOutput}}}
+
+ self.createStructure(self.rootDir, structure)
+
+ outputFile = self.builder.buildSubProject("web", "0.3.0")
+
+ self.assertExtractedStructure(outputFile, outStructure)
+
+
+ def test_coreProjectLayout(self):
+ """
+ The core tarball looks a lot like a subproject tarball, except it
+ doesn't include:
+
+ - Python packages from other subprojects
+ - plugins from other subprojects
+ - scripts from other subprojects
+ """
+ indexInput, indexOutput = self.getArbitraryLoreInputAndOutput(
+ "8.0.0", prefix="howto/")
+ howtoInput, howtoOutput = self.getArbitraryLoreInputAndOutput("8.0.0")
+ specInput, specOutput = self.getArbitraryLoreInputAndOutput(
+ "8.0.0", prefix="../howto/")
+ upgradeInput, upgradeOutput = self.getArbitraryLoreInputAndOutput(
+ "8.0.0", prefix="../howto/")
+ tutorialInput, tutorialOutput = self.getArbitraryLoreInputAndOutput(
+ "8.0.0", prefix="../")
+
+ structure = {
+ "LICENSE": "copyright!",
+ "twisted": {"__init__.py": "twisted",
+ "python": {"__init__.py": "python",
+ "roots.py": "roots!"},
+ "conch": {"__init__.py": "conch",
+ "unrelated.py": "import conch"},
+ "plugin.py": "plugin",
+ "plugins": {"twisted_web.py": "webplug",
+ "twisted_whatever.py": "include!",
+ "cred.py": "include!"},
+ "topfiles": {"setup.py": "import CORE",
+ "README": "core readme"}},
+ "doc": {"core": {"howto": {"template.tpl": self.template,
+ "index.xhtml": howtoInput,
+ "tutorial":
+ {"index.xhtml": tutorialInput}},
+ "specifications": {"index.xhtml": specInput},
+ "upgrades": {"index.xhtml": upgradeInput},
+ "examples": {"foo.py": "foo.py"},
+ "index.xhtml": indexInput},
+ "web": {"howto": {"index.xhtml": "webindex"}}},
+ "bin": {"twistd": "TWISTD",
+ "web": {"websetroot": "websetroot"}}
+ }
+
+ outStructure = {
+ "LICENSE": "copyright!",
+ "setup.py": "import CORE",
+ "README": "core readme",
+ "twisted": {"__init__.py": "twisted",
+ "python": {"__init__.py": "python",
+ "roots.py": "roots!"},
+ "plugin.py": "plugin",
+ "plugins": {"twisted_whatever.py": "include!",
+ "cred.py": "include!"}},
+ "doc": {"howto": {"template.tpl": self.template,
+ "index.html": howtoOutput,
+ "tutorial": {"index.html": tutorialOutput}},
+ "specifications": {"index.html": specOutput},
+ "upgrades": {"index.html": upgradeOutput},
+ "examples": {"foo.py": "foo.py"},
+ "index.html": indexOutput},
+ "bin": {"twistd": "TWISTD"},
+ }
+
+ self.createStructure(self.rootDir, structure)
+ outputFile = self.builder.buildCore("8.0.0")
+ self.assertExtractedStructure(outputFile, outStructure)
+
+
+ def test_apiBaseURL(self):
+ """
+ DistributionBuilder builds documentation with the specified
+ API base URL.
+ """
+ apiBaseURL = "http://%s"
+ builder = DistributionBuilder(self.rootDir, self.outputDir,
+ apiBaseURL=apiBaseURL)
+ loreInput, loreOutput = self.getArbitraryLoreInputAndOutput(
+ "0.3.0", apiBaseURL=apiBaseURL)
+ structure = {
+ "LICENSE": "copyright!",
+ "twisted": {"web": {"__init__.py": "import WEB",
+ "topfiles": {"setup.py": "import WEBINST"}}},
+ "doc": {"web": {"howto": {"index.xhtml": loreInput}},
+ "core": {"howto": {"template.tpl": self.template}}
+ }
+ }
+
+ outStructure = {
+ "LICENSE": "copyright!",
+ "setup.py": "import WEBINST",
+ "twisted": {"web": {"__init__.py": "import WEB"}},
+ "doc": {"howto": {"index.html": loreOutput}}}
+
+ self.createStructure(self.rootDir, structure)
+ outputFile = builder.buildSubProject("web", "0.3.0")
+ self.assertExtractedStructure(outputFile, outStructure)
+
+
+
+class BuildAllTarballsTest(DistributionBuilderTestBase):
+ """
+ Tests for L{DistributionBuilder.buildAllTarballs}.
+ """
+ skip = svnSkip or popen4Skip
+
+ def setUp(self):
+ self.oldHandler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+ DistributionBuilderTestBase.setUp(self)
+
+
+ def tearDown(self):
+ signal.signal(signal.SIGCHLD, self.oldHandler)
+ DistributionBuilderTestBase.tearDown(self)
+
+
+ def test_buildAllTarballs(self):
+ """
+ L{buildAllTarballs} builds tarballs for Twisted and all of its
+ subprojects based on an SVN checkout; the resulting tarballs contain
+ no SVN metadata. This involves building documentation, which it will
+ build with the correct API documentation reference base URL.
+ """
+ repositoryPath = self.mktemp()
+ repository = FilePath(repositoryPath)
+ checkoutPath = self.mktemp()
+ checkout = FilePath(checkoutPath)
+ self.outputDir.remove()
+
+ runCommand(["svnadmin", "create", repositoryPath])
+ runCommand(["svn", "checkout", "file://" + repository.path,
+ checkout.path])
+ coreIndexInput, coreIndexOutput = self.getArbitraryLoreInputAndOutput(
+ "1.2.0", prefix="howto/",
+ apiBaseURL="http://twistedmatrix.com/documents/1.2.0/api/%s.html")
+
+ structure = {
+ "README": "Twisted",
+ "unrelated": "x",
+ "LICENSE": "copyright!",
+ "setup.py": "import toplevel",
+ "bin": {"web2": {"websetroot": "SET ROOT"},
+ "vfs": {"vfsitup": "hee hee"},
+ "words": {"im": "import im"},
+ "twistd": "TWISTD"},
+ "twisted":
+ {
+ "topfiles": {"setup.py": "import TOPINSTALL",
+ "README": "CORE!"},
+ "_version.py": genVersion("twisted", 1, 2, 0),
+ "web2":
+ {"__init__.py": "import WEB",
+ "topfiles": {"setup.py": "import WEBINSTALL",
+ "README": "WEB!"}},
+ "vfs":
+ {"__init__.py": "import VFS",
+ "blah blah": "blah blah"},
+ "words": {"__init__.py": "import WORDS",
+ "_version.py":
+ genVersion("twisted.words", 1, 2, 0),
+ "topfiles": {"setup.py": "import WORDSINSTALL",
+ "README": "WORDS!"},
+ },
+ "plugins": {"twisted_web.py": "import WEBPLUG",
+ "twisted_words.py": "import WORDPLUG",
+ "twisted_web2.py": "import WEB2",
+ "twisted_vfs.py": "import VFS",
+ "twisted_yay.py": "import YAY"}},
+ "doc": {"web2": {"excluded!": "yay"},
+ "vfs": {"unrelated": "whatever"},
+ "core": {"howto": {"template.tpl": self.template},
+ "index.xhtml": coreIndexInput}}}
+
+ twistedStructure = {
+ "README": "Twisted",
+ "unrelated": "x",
+ "LICENSE": "copyright!",
+ "setup.py": "import toplevel",
+ "bin": {"twistd": "TWISTD",
+ "words": {"im": "import im"}},
+ "twisted":
+ {
+ "topfiles": {"setup.py": "import TOPINSTALL",
+ "README": "CORE!"},
+ "_version.py": genVersion("twisted", 1, 2, 0),
+ "words": {"__init__.py": "import WORDS",
+ "_version.py":
+ genVersion("twisted.words", 1, 2, 0),
+ "topfiles": {"setup.py": "import WORDSINSTALL",
+ "README": "WORDS!"},
+ },
+ "plugins": {"twisted_web.py": "import WEBPLUG",
+ "twisted_words.py": "import WORDPLUG",
+ "twisted_yay.py": "import YAY"}},
+ "doc": {"core": {"howto": {"template.tpl": self.template},
+ "index.html": coreIndexOutput}}}
+
+ coreStructure = {
+ "setup.py": "import TOPINSTALL",
+ "README": "CORE!",
+ "LICENSE": "copyright!",
+ "bin": {"twistd": "TWISTD"},
+ "twisted": {
+ "_version.py": genVersion("twisted", 1, 2, 0),
+ "plugins": {"twisted_yay.py": "import YAY"}},
+ "doc": {"howto": {"template.tpl": self.template},
+ "index.html": coreIndexOutput}}
+
+ wordsStructure = {
+ "README": "WORDS!",
+ "LICENSE": "copyright!",
+ "setup.py": "import WORDSINSTALL",
+ "bin": {"im": "import im"},
+ "twisted":
+ {
+ "words": {"__init__.py": "import WORDS",
+ "_version.py":
+ genVersion("twisted.words", 1, 2, 0),
+ },
+ "plugins": {"twisted_words.py": "import WORDPLUG"}}}
+
+ self.createStructure(checkout, structure)
+ childs = [x.path for x in checkout.children()]
+ runCommand(["svn", "add"] + childs)
+ runCommand(["svn", "commit", checkout.path, "-m", "yay"])
+
+ buildAllTarballs(checkout, self.outputDir)
+ self.assertEquals(
+ set(self.outputDir.children()),
+ set([self.outputDir.child("Twisted-1.2.0.tar.bz2"),
+ self.outputDir.child("TwistedCore-1.2.0.tar.bz2"),
+ self.outputDir.child("TwistedWords-1.2.0.tar.bz2")]))
+
+ self.assertExtractedStructure(
+ self.outputDir.child("Twisted-1.2.0.tar.bz2"),
+ twistedStructure)
+ self.assertExtractedStructure(
+ self.outputDir.child("TwistedCore-1.2.0.tar.bz2"),
+ coreStructure)
+ self.assertExtractedStructure(
+ self.outputDir.child("TwistedWords-1.2.0.tar.bz2"),
+ wordsStructure)
+
+
+ def test_buildAllTarballsEnsuresCleanCheckout(self):
+ """
+ L{UncleanWorkingDirectory} is raised by L{buildAllTarballs} when the
+ SVN checkout provided has uncommitted changes.
+ """
+ repositoryPath = self.mktemp()
+ repository = FilePath(repositoryPath)
+ checkoutPath = self.mktemp()
+ checkout = FilePath(checkoutPath)
+
+ runCommand(["svnadmin", "create", repositoryPath])
+ runCommand(["svn", "checkout", "file://" + repository.path,
+ checkout.path])
+
+ checkout.child("foo").setContent("whatever")
+ self.assertRaises(UncleanWorkingDirectory,
+ buildAllTarballs, checkout, FilePath(self.mktemp()))
+
+
+ def test_buildAllTarballsEnsuresExistingCheckout(self):
+ """
+ L{NotWorkingDirectory} is raised by L{buildAllTarballs} when the
+ checkout passed does not exist or is not an SVN checkout.
+ """
+ checkout = FilePath(self.mktemp())
+ self.assertRaises(NotWorkingDirectory,
+ buildAllTarballs,
+ checkout, FilePath(self.mktemp()))
+ checkout.createDirectory()
+ self.assertRaises(NotWorkingDirectory,
+ buildAllTarballs,
+ checkout, FilePath(self.mktemp()))
+
+
+
+class ScriptTests(BuilderTestsMixin, StructureAssertingMixin, TestCase):
+ """
+ Tests for the release script functionality.
+ """
+
+ def _testVersionChanging(self, major, minor, micro, prerelease=None):
+ """
+ Check that L{ChangeVersionsScript.main} calls the version-changing
+ function with the appropriate version data and filesystem path.
+ """
+ versionUpdates = []
+ def myVersionChanger(sourceTree, versionTemplate):
+ versionUpdates.append((sourceTree, versionTemplate))
+ versionChanger = ChangeVersionsScript()
+ versionChanger.changeAllProjectVersions = myVersionChanger
+ version = "%d.%d.%d" % (major, minor, micro)
+ if prerelease is not None:
+ version += "pre%d" % (prerelease,)
+ versionChanger.main([version])
+ self.assertEquals(len(versionUpdates), 1)
+ self.assertEquals(versionUpdates[0][0], FilePath("."))
+ self.assertEquals(versionUpdates[0][1].major, major)
+ self.assertEquals(versionUpdates[0][1].minor, minor)
+ self.assertEquals(versionUpdates[0][1].micro, micro)
+ self.assertEquals(versionUpdates[0][1].prerelease, prerelease)
+
+
+ def test_changeVersions(self):
+ """
+ L{ChangeVersionsScript.main} changes version numbers for all Twisted
+ projects.
+ """
+ self._testVersionChanging(8, 2, 3)
+
+
+ def test_changeVersionsWithPrerelease(self):
+ """
+ A prerelease can be specified to L{changeVersionsScript}.
+ """
+ self._testVersionChanging(9, 2, 7, 38)
+
+
+ def test_defaultChangeVersionsVersionChanger(self):
+ """
+ The default implementation of C{changeAllProjectVersions} is
+ L{changeAllProjectVersions}.
+ """
+ versionChanger = ChangeVersionsScript()
+ self.assertEquals(versionChanger.changeAllProjectVersions,
+ changeAllProjectVersions)
+
+
+ def test_badNumberOfArgumentsToChangeVersionsScript(self):
+ """
+ L{changeVersionsScript} raises SystemExit when the wrong number of
+ arguments are passed.
+ """
+ versionChanger = ChangeVersionsScript()
+ self.assertRaises(SystemExit, versionChanger.main, [])
+
+
+ def test_tooManyDotsToChangeVersionsScript(self):
+ """
+ L{changeVersionsScript} raises SystemExit when there are the wrong
+ number of segments in the version number passed.
+ """
+ versionChanger = ChangeVersionsScript()
+ self.assertRaises(SystemExit, versionChanger.main,
+ ["3.2.1.0"])
+
+
+ def test_nonIntPartsToChangeVersionsScript(self):
+ """
+ L{changeVersionsScript} raises SystemExit when the version number isn't
+ made out of numbers.
+ """
+ versionChanger = ChangeVersionsScript()
+ self.assertRaises(SystemExit, versionChanger.main,
+ ["my united.states.of prewhatever"])
+
+
+ def test_buildTarballsScript(self):
+ """
+ L{BuildTarballsScript.main} invokes L{buildAllTarballs} with
+ L{FilePath} instances representing the paths passed to it.
+ """
+ builds = []
+ def myBuilder(checkout, destination):
+ builds.append((checkout, destination))
+ tarballBuilder = BuildTarballsScript()
+ tarballBuilder.buildAllTarballs = myBuilder
+
+ tarballBuilder.main(["checkoutDir", "destinationDir"])
+ self.assertEquals(
+ builds,
+ [(FilePath("checkoutDir"), FilePath("destinationDir"))])
+
+
+ def test_defaultBuildTarballsScriptBuilder(self):
+ """
+ The default implementation of L{BuildTarballsScript.buildAllTarballs}
+ is L{buildAllTarballs}.
+ """
+ tarballBuilder = BuildTarballsScript()
+ self.assertEquals(tarballBuilder.buildAllTarballs, buildAllTarballs)
+
+
+ def test_badNumberOfArgumentsToBuildTarballs(self):
+ """
+ L{BuildTarballsScript.main} raises SystemExit when the wrong number of
+ arguments are passed.
+ """
+ tarballBuilder = BuildTarballsScript()
+ self.assertRaises(SystemExit, tarballBuilder.main, [])
+
+
+ def test_badNumberOfArgumentsToBuildNews(self):
+ """
+ L{NewsBuilder.main} raises L{SystemExit} when other than 1 argument is
+ passed to it.
+ """
+ newsBuilder = NewsBuilder()
+ self.assertRaises(SystemExit, newsBuilder.main, [])
+ self.assertRaises(SystemExit, newsBuilder.main, ["hello", "world"])
+
+
+ def test_buildNews(self):
+ """
+ L{NewsBuilder.main} calls L{NewsBuilder.buildAll} with a L{FilePath}
+ instance constructed from the path passed to it.
+ """
+ builds = []
+ newsBuilder = NewsBuilder()
+ newsBuilder.buildAll = builds.append
+ newsBuilder.main(["/foo/bar/baz"])
+ self.assertEquals(builds, [FilePath("/foo/bar/baz")])
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_runtime.py b/vendor/Twisted-10.0.0/twisted/python/test/test_runtime.py
new file mode 100644
index 0000000000..aa2e08186b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_runtime.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for runtime checks.
+"""
+
+
+
+from twisted.python.runtime import Platform
+from twisted.trial.unittest import TestCase
+
+
+
+class PlatformTests(TestCase):
+ """
+ Tests for L{Platform}.
+ """
+
+ def test_isVistaConsistency(self):
+ """
+ Verify consistency of L{Platform.isVista}: it can only be C{True} if
+ L{Platform.isWinNT} and L{Platform.isWindows} are C{True}.
+ """
+ platform = Platform()
+ if platform.isVista():
+ self.assertTrue(platform.isWinNT())
+ self.assertTrue(platform.isWindows())
+ self.assertFalse(platform.isMacOSX())
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_syslog.py b/vendor/Twisted-10.0.0/twisted/python/test/test_syslog.py
new file mode 100644
index 0000000000..cda040ff2b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_syslog.py
@@ -0,0 +1,151 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.trial.unittest import TestCase
+from twisted.python.failure import Failure
+
+try:
+ import syslog as stdsyslog
+except ImportError:
+ stdsyslog = None
+else:
+ from twisted.python import syslog
+
+
+
+class SyslogObserverTests(TestCase):
+ """
+ Tests for L{SyslogObserver} which sends Twisted log events to the syslog.
+ """
+ events = None
+
+ if stdsyslog is None:
+ skip = "syslog is not supported on this platform"
+
+ def setUp(self):
+ self.patch(syslog.SyslogObserver, 'openlog', self.openlog)
+ self.patch(syslog.SyslogObserver, 'syslog', self.syslog)
+ self.observer = syslog.SyslogObserver('SyslogObserverTests')
+
+
+ def openlog(self, prefix, options, facility):
+ self.logOpened = (prefix, options, facility)
+ self.events = []
+
+
+ def syslog(self, options, message):
+ self.events.append((options, message))
+
+
+ def test_emitWithoutMessage(self):
+ """
+ L{SyslogObserver.emit} ignores events with an empty value for the
+ C{'message'} key.
+ """
+ self.observer.emit({'message': (), 'isError': False, 'system': '-'})
+ self.assertEqual(self.events, [])
+
+
+ def test_emitCustomPriority(self):
+ """
+ L{SyslogObserver.emit} uses the value of the C{'syslogPriority'} as the
+ syslog priority, if that key is present in the event dictionary.
+ """
+ self.observer.emit({
+ 'message': ('hello, world',), 'isError': False, 'system': '-',
+ 'syslogPriority': stdsyslog.LOG_DEBUG})
+ self.assertEqual(
+ self.events,
+ [(stdsyslog.LOG_DEBUG, '[-] hello, world')])
+
+
+ def test_emitErrorPriority(self):
+ """
+ L{SyslogObserver.emit} uses C{LOG_ALERT} if the event represents an
+ error.
+ """
+ self.observer.emit({
+ 'message': ('hello, world',), 'isError': True, 'system': '-',
+ 'failure': Failure(Exception("foo"))})
+ self.assertEqual(
+ self.events,
+ [(stdsyslog.LOG_ALERT, '[-] hello, world')])
+
+
+ def test_emitCustomPriorityOverridesError(self):
+ """
+ L{SyslogObserver.emit} uses the value of the C{'syslogPriority'} key if
+ it is specified even if the event dictionary represents an error.
+ """
+ self.observer.emit({
+ 'message': ('hello, world',), 'isError': True, 'system': '-',
+ 'syslogPriority': stdsyslog.LOG_NOTICE,
+ 'failure': Failure(Exception("bar"))})
+ self.assertEqual(
+ self.events,
+ [(stdsyslog.LOG_NOTICE, '[-] hello, world')])
+
+
+ def test_emitCustomFacility(self):
+ """
+ L{SyslogObserver.emit} uses the value of the C{'syslogPriority'} as the
+ syslog priority, if that key is present in the event dictionary.
+ """
+ self.observer.emit({
+ 'message': ('hello, world',), 'isError': False, 'system': '-',
+ 'syslogFacility': stdsyslog.LOG_CRON})
+ self.assertEqual(
+ self.events,
+ [(stdsyslog.LOG_INFO | stdsyslog.LOG_CRON, '[-] hello, world')])
+
+
+ def test_emitCustomSystem(self):
+ """
+ L{SyslogObserver.emit} uses the value of the C{'system'} key to prefix
+ the logged message.
+ """
+ self.observer.emit({'message': ('hello, world',), 'isError': False,
+ 'system': 'nonDefaultSystem'})
+ self.assertEqual(
+ self.events,
+ [(stdsyslog.LOG_INFO, "[nonDefaultSystem] hello, world")])
+
+
+ def test_emitMessage(self):
+ """
+ L{SyslogObserver.emit} logs the value of the C{'message'} key of the
+ event dictionary it is passed to the syslog.
+ """
+ self.observer.emit({
+ 'message': ('hello, world',), 'isError': False,
+ 'system': '-'})
+ self.assertEqual(
+ self.events,
+ [(stdsyslog.LOG_INFO, "[-] hello, world")])
+
+
+ def test_emitMultilineMessage(self):
+ """
+ Each line of a multiline message is emitted separately to the syslog.
+ """
+ self.observer.emit({
+ 'message': ('hello,\nworld',), 'isError': False,
+ 'system': '-'})
+ self.assertEqual(
+ self.events,
+ [(stdsyslog.LOG_INFO, '[-] hello,'),
+ (stdsyslog.LOG_INFO, '[-] \tworld')])
+
+
+ def test_emitStripsTrailingEmptyLines(self):
+ """
+ Trailing empty lines of a multiline message are omitted from the
+ messages sent to the syslog.
+ """
+ self.observer.emit({
+ 'message': ('hello,\nworld\n\n',), 'isError': False,
+ 'system': '-'})
+ self.assertEqual(
+ self.events,
+ [(stdsyslog.LOG_INFO, '[-] hello,'),
+ (stdsyslog.LOG_INFO, '[-] \tworld')])
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_util.py b/vendor/Twisted-10.0.0/twisted/python/test/test_util.py
new file mode 100644
index 0000000000..51c6b2cc71
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_util.py
@@ -0,0 +1,834 @@
+# -*- test-case-name: twisted.test.test_util -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import os.path, sys
+import shutil, errno
+try:
+ import pwd, grp
+except ImportError:
+ pwd = grp = None
+
+from twisted.trial import unittest
+
+from twisted.python import util
+from twisted.internet import reactor
+from twisted.internet.interfaces import IReactorProcess
+from twisted.internet.protocol import ProcessProtocol
+from twisted.internet.defer import Deferred
+from twisted.internet.error import ProcessDone
+
+from twisted.test.test_process import MockOS
+
+
+
+class UtilTestCase(unittest.TestCase):
+
+ def testUniq(self):
+ l = ["a", 1, "ab", "a", 3, 4, 1, 2, 2, 4, 6]
+ self.assertEquals(util.uniquify(l), ["a", 1, "ab", 3, 4, 2, 6])
+
+ def testRaises(self):
+ self.failUnless(util.raises(ZeroDivisionError, divmod, 1, 0))
+ self.failIf(util.raises(ZeroDivisionError, divmod, 0, 1))
+
+ try:
+ util.raises(TypeError, divmod, 1, 0)
+ except ZeroDivisionError:
+ pass
+ else:
+ raise unittest.FailTest, "util.raises didn't raise when it should have"
+
+ def testUninterruptably(self):
+ def f(a, b):
+ self.calls += 1
+ exc = self.exceptions.pop()
+ if exc is not None:
+ raise exc(errno.EINTR, "Interrupted system call!")
+ return a + b
+
+ self.exceptions = [None]
+ self.calls = 0
+ self.assertEquals(util.untilConcludes(f, 1, 2), 3)
+ self.assertEquals(self.calls, 1)
+
+ self.exceptions = [None, OSError, IOError]
+ self.calls = 0
+ self.assertEquals(util.untilConcludes(f, 2, 3), 5)
+ self.assertEquals(self.calls, 3)
+
+ def testNameToLabel(self):
+ """
+ Test the various kinds of inputs L{nameToLabel} supports.
+ """
+ nameData = [
+ ('f', 'F'),
+ ('fo', 'Fo'),
+ ('foo', 'Foo'),
+ ('fooBar', 'Foo Bar'),
+ ('fooBarBaz', 'Foo Bar Baz'),
+ ]
+ for inp, out in nameData:
+ got = util.nameToLabel(inp)
+ self.assertEquals(
+ got, out,
+ "nameToLabel(%r) == %r != %r" % (inp, got, out))
+
+
+ def test_uidFromNumericString(self):
+ """
+ When L{uidFromString} is called with a base-ten string representation
+ of an integer, it returns the integer.
+ """
+ self.assertEqual(util.uidFromString("100"), 100)
+
+
+ def test_uidFromUsernameString(self):
+ """
+ When L{uidFromString} is called with a base-ten string representation
+ of an integer, it returns the integer.
+ """
+ pwent = pwd.getpwuid(os.getuid())
+ self.assertEqual(util.uidFromString(pwent.pw_name), pwent.pw_uid)
+ if pwd is None:
+ test_uidFromUsernameString.skip = (
+ "Username/UID conversion requires the pwd module.")
+
+
+ def test_gidFromNumericString(self):
+ """
+ When L{gidFromString} is called with a base-ten string representation
+ of an integer, it returns the integer.
+ """
+ self.assertEqual(util.gidFromString("100"), 100)
+
+
+ def test_gidFromGroupnameString(self):
+ """
+ When L{gidFromString} is called with a base-ten string representation
+ of an integer, it returns the integer.
+ """
+ grent = grp.getgrgid(os.getgid())
+ self.assertEqual(util.gidFromString(grent.gr_name), grent.gr_gid)
+ if grp is None:
+ test_gidFromGroupnameString.skip = (
+ "Group Name/GID conversion requires the grp module.")
+
+
+ def test_moduleMovedForSplitDeprecation(self):
+ """
+ Calling L{moduleMovedForSplit} results in a deprecation warning.
+ """
+ util.moduleMovedForSplit("foo", "bar", "baz", "quux", "corge", {})
+ warnings = self.flushWarnings(
+ offendingFunctions=[self.test_moduleMovedForSplitDeprecation])
+ self.assertEquals(
+ warnings[0]['message'],
+ "moduleMovedForSplit is deprecated since Twisted 9.0.")
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(len(warnings), 1)
+
+
+
+class TestMergeFunctionMetadata(unittest.TestCase):
+ """
+ Tests for L{mergeFunctionMetadata}.
+ """
+
+ def test_mergedFunctionBehavesLikeMergeTarget(self):
+ """
+ After merging C{foo}'s data into C{bar}, the returned function behaves
+ as if it is C{bar}.
+ """
+ foo_object = object()
+ bar_object = object()
+
+ def foo():
+ return foo_object
+
+ def bar(x, y, (a, b), c=10, *d, **e):
+ return bar_object
+
+ baz = util.mergeFunctionMetadata(foo, bar)
+ self.assertIdentical(baz(1, 2, (3, 4), quux=10), bar_object)
+
+
+ def test_moduleIsMerged(self):
+ """
+ Merging C{foo} into C{bar} returns a function with C{foo}'s
+ C{__module__}.
+ """
+ def foo():
+ pass
+
+ def bar():
+ pass
+ bar.__module__ = 'somewhere.else'
+
+ baz = util.mergeFunctionMetadata(foo, bar)
+ self.assertEqual(baz.__module__, foo.__module__)
+
+
+ def test_docstringIsMerged(self):
+ """
+ Merging C{foo} into C{bar} returns a function with C{foo}'s docstring.
+ """
+
+ def foo():
+ """
+ This is foo.
+ """
+
+ def bar():
+ """
+ This is bar.
+ """
+
+ baz = util.mergeFunctionMetadata(foo, bar)
+ self.assertEqual(baz.__doc__, foo.__doc__)
+
+
+ def test_nameIsMerged(self):
+ """
+ Merging C{foo} into C{bar} returns a function with C{foo}'s name.
+ """
+
+ def foo():
+ pass
+
+ def bar():
+ pass
+
+ baz = util.mergeFunctionMetadata(foo, bar)
+ self.assertEqual(baz.__name__, foo.__name__)
+
+
+ def test_instanceDictionaryIsMerged(self):
+ """
+ Merging C{foo} into C{bar} returns a function with C{bar}'s
+ dictionary, updated by C{foo}'s.
+ """
+
+ def foo():
+ pass
+ foo.a = 1
+ foo.b = 2
+
+ def bar():
+ pass
+ bar.b = 3
+ bar.c = 4
+
+ baz = util.mergeFunctionMetadata(foo, bar)
+ self.assertEqual(foo.a, baz.a)
+ self.assertEqual(foo.b, baz.b)
+ self.assertEqual(bar.c, baz.c)
+
+
+
+class OrderedDictTest(unittest.TestCase):
+ def testOrderedDict(self):
+ d = util.OrderedDict()
+ d['a'] = 'b'
+ d['b'] = 'a'
+ d[3] = 12
+ d[1234] = 4321
+ self.assertEquals(repr(d), "{'a': 'b', 'b': 'a', 3: 12, 1234: 4321}")
+ self.assertEquals(d.values(), ['b', 'a', 12, 4321])
+ del d[3]
+ self.assertEquals(repr(d), "{'a': 'b', 'b': 'a', 1234: 4321}")
+ self.assertEquals(d, {'a': 'b', 'b': 'a', 1234:4321})
+ self.assertEquals(d.keys(), ['a', 'b', 1234])
+ self.assertEquals(list(d.iteritems()),
+ [('a', 'b'), ('b','a'), (1234, 4321)])
+ item = d.popitem()
+ self.assertEquals(item, (1234, 4321))
+
+ def testInitialization(self):
+ d = util.OrderedDict({'monkey': 'ook',
+ 'apple': 'red'})
+ self.failUnless(d._order)
+
+ d = util.OrderedDict(((1,1),(3,3),(2,2),(0,0)))
+ self.assertEquals(repr(d), "{1: 1, 3: 3, 2: 2, 0: 0}")
+
+class InsensitiveDictTest(unittest.TestCase):
+ def testPreserve(self):
+ InsensitiveDict=util.InsensitiveDict
+ dct=InsensitiveDict({'Foo':'bar', 1:2, 'fnz':{1:2}}, preserve=1)
+ self.assertEquals(dct['fnz'], {1:2})
+ self.assertEquals(dct['foo'], 'bar')
+ self.assertEquals(dct.copy(), dct)
+ self.assertEquals(dct['foo'], dct.get('Foo'))
+ assert 1 in dct and 'foo' in dct
+ self.assertEquals(eval(repr(dct)), dct)
+ keys=['Foo', 'fnz', 1]
+ for x in keys:
+ assert x in dct.keys()
+ assert (x, dct[x]) in dct.items()
+ self.assertEquals(len(keys), len(dct))
+ del dct[1]
+ del dct['foo']
+
+ def testNoPreserve(self):
+ InsensitiveDict=util.InsensitiveDict
+ dct=InsensitiveDict({'Foo':'bar', 1:2, 'fnz':{1:2}}, preserve=0)
+ keys=['foo', 'fnz', 1]
+ for x in keys:
+ assert x in dct.keys()
+ assert (x, dct[x]) in dct.items()
+ self.assertEquals(len(keys), len(dct))
+ del dct[1]
+ del dct['foo']
+
+
+
+
+class PasswordTestingProcessProtocol(ProcessProtocol):
+ """
+ Write the string C{"secret\n"} to a subprocess and then collect all of
+ its output and fire a Deferred with it when the process ends.
+ """
+ def connectionMade(self):
+ self.output = []
+ self.transport.write('secret\n')
+
+ def childDataReceived(self, fd, output):
+ self.output.append((fd, output))
+
+ def processEnded(self, reason):
+ self.finished.callback((reason, self.output))
+
+
+class GetPasswordTest(unittest.TestCase):
+ if not IReactorProcess.providedBy(reactor):
+ skip = "Process support required to test getPassword"
+
+ def test_stdin(self):
+ """
+ Making sure getPassword accepts a password from standard input by
+ running a child process which uses getPassword to read in a string
+ which it then writes it out again. Write a string to the child
+ process and then read one and make sure it is the right string.
+ """
+ p = PasswordTestingProcessProtocol()
+ p.finished = Deferred()
+ reactor.spawnProcess(
+ p,
+ sys.executable,
+ [sys.executable,
+ '-c',
+ ('import sys\n'
+ 'from twisted.python.util import getPassword\n'
+ 'sys.stdout.write(getPassword())\n'
+ 'sys.stdout.flush()\n')],
+ env={'PYTHONPATH': os.pathsep.join(sys.path)})
+
+ def processFinished((reason, output)):
+ reason.trap(ProcessDone)
+ self.assertIn((1, 'secret'), output)
+
+ return p.finished.addCallback(processFinished)
+
+
+
+class SearchUpwardsTest(unittest.TestCase):
+ def testSearchupwards(self):
+ os.makedirs('searchupwards/a/b/c')
+ file('searchupwards/foo.txt', 'w').close()
+ file('searchupwards/a/foo.txt', 'w').close()
+ file('searchupwards/a/b/c/foo.txt', 'w').close()
+ os.mkdir('searchupwards/bar')
+ os.mkdir('searchupwards/bam')
+ os.mkdir('searchupwards/a/bar')
+ os.mkdir('searchupwards/a/b/bam')
+ actual=util.searchupwards('searchupwards/a/b/c',
+ files=['foo.txt'],
+ dirs=['bar', 'bam'])
+ expected=os.path.abspath('searchupwards') + os.sep
+ self.assertEqual(actual, expected)
+ shutil.rmtree('searchupwards')
+ actual=util.searchupwards('searchupwards/a/b/c',
+ files=['foo.txt'],
+ dirs=['bar', 'bam'])
+ expected=None
+ self.assertEqual(actual, expected)
+
+class Foo:
+ def __init__(self, x):
+ self.x = x
+
+class DSU(unittest.TestCase):
+ def testDSU(self):
+ L = [Foo(x) for x in range(20, 9, -1)]
+ L2 = util.dsu(L, lambda o: o.x)
+ self.assertEquals(range(10, 21), [o.x for o in L2])
+
+class IntervalDifferentialTestCase(unittest.TestCase):
+ def testDefault(self):
+ d = iter(util.IntervalDifferential([], 10))
+ for i in range(100):
+ self.assertEquals(d.next(), (10, None))
+
+ def testSingle(self):
+ d = iter(util.IntervalDifferential([5], 10))
+ for i in range(100):
+ self.assertEquals(d.next(), (5, 0))
+
+ def testPair(self):
+ d = iter(util.IntervalDifferential([5, 7], 10))
+ for i in range(100):
+ self.assertEquals(d.next(), (5, 0))
+ self.assertEquals(d.next(), (2, 1))
+ self.assertEquals(d.next(), (3, 0))
+ self.assertEquals(d.next(), (4, 1))
+ self.assertEquals(d.next(), (1, 0))
+ self.assertEquals(d.next(), (5, 0))
+ self.assertEquals(d.next(), (1, 1))
+ self.assertEquals(d.next(), (4, 0))
+ self.assertEquals(d.next(), (3, 1))
+ self.assertEquals(d.next(), (2, 0))
+ self.assertEquals(d.next(), (5, 0))
+ self.assertEquals(d.next(), (0, 1))
+
+ def testTriple(self):
+ d = iter(util.IntervalDifferential([2, 4, 5], 10))
+ for i in range(100):
+ self.assertEquals(d.next(), (2, 0))
+ self.assertEquals(d.next(), (2, 0))
+ self.assertEquals(d.next(), (0, 1))
+ self.assertEquals(d.next(), (1, 2))
+ self.assertEquals(d.next(), (1, 0))
+ self.assertEquals(d.next(), (2, 0))
+ self.assertEquals(d.next(), (0, 1))
+ self.assertEquals(d.next(), (2, 0))
+ self.assertEquals(d.next(), (0, 2))
+ self.assertEquals(d.next(), (2, 0))
+ self.assertEquals(d.next(), (0, 1))
+ self.assertEquals(d.next(), (2, 0))
+ self.assertEquals(d.next(), (1, 2))
+ self.assertEquals(d.next(), (1, 0))
+ self.assertEquals(d.next(), (0, 1))
+ self.assertEquals(d.next(), (2, 0))
+ self.assertEquals(d.next(), (2, 0))
+ self.assertEquals(d.next(), (0, 1))
+ self.assertEquals(d.next(), (0, 2))
+
+ def testInsert(self):
+ d = iter(util.IntervalDifferential([], 10))
+ self.assertEquals(d.next(), (10, None))
+ d.addInterval(3)
+ self.assertEquals(d.next(), (3, 0))
+ self.assertEquals(d.next(), (3, 0))
+ d.addInterval(6)
+ self.assertEquals(d.next(), (3, 0))
+ self.assertEquals(d.next(), (3, 0))
+ self.assertEquals(d.next(), (0, 1))
+ self.assertEquals(d.next(), (3, 0))
+ self.assertEquals(d.next(), (3, 0))
+ self.assertEquals(d.next(), (0, 1))
+
+ def testRemove(self):
+ d = iter(util.IntervalDifferential([3, 5], 10))
+ self.assertEquals(d.next(), (3, 0))
+ self.assertEquals(d.next(), (2, 1))
+ self.assertEquals(d.next(), (1, 0))
+ d.removeInterval(3)
+ self.assertEquals(d.next(), (4, 0))
+ self.assertEquals(d.next(), (5, 0))
+ d.removeInterval(5)
+ self.assertEquals(d.next(), (10, None))
+ self.assertRaises(ValueError, d.removeInterval, 10)
+
+
+
+class Record(util.FancyEqMixin):
+ """
+ Trivial user of L{FancyEqMixin} used by tests.
+ """
+ compareAttributes = ('a', 'b')
+
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+
+
+
+class DifferentRecord(util.FancyEqMixin):
+ """
+ Trivial user of L{FancyEqMixin} which is not related to L{Record}.
+ """
+ compareAttributes = ('a', 'b')
+
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+
+
+
+class DerivedRecord(Record):
+ """
+ A class with an inheritance relationship to L{Record}.
+ """
+
+
+
+class EqualToEverything(object):
+ """
+ A class the instances of which consider themselves equal to everything.
+ """
+ def __eq__(self, other):
+ return True
+
+
+ def __ne__(self, other):
+ return False
+
+
+
+class EqualToNothing(object):
+ """
+ A class the instances of which consider themselves equal to nothing.
+ """
+ def __eq__(self, other):
+ return False
+
+
+ def __ne__(self, other):
+ return True
+
+
+
+class EqualityTests(unittest.TestCase):
+ """
+ Tests for L{FancyEqMixin}.
+ """
+ def test_identity(self):
+ """
+ Instances of a class which mixes in L{FancyEqMixin} but which
+ defines no comparison attributes compare by identity.
+ """
+ class Empty(util.FancyEqMixin):
+ pass
+
+ self.assertFalse(Empty() == Empty())
+ self.assertTrue(Empty() != Empty())
+ empty = Empty()
+ self.assertTrue(empty == empty)
+ self.assertFalse(empty != empty)
+
+
+ def test_equality(self):
+ """
+ Instances of a class which mixes in L{FancyEqMixin} should compare
+ equal if all of their attributes compare equal. They should not
+ compare equal if any of their attributes do not compare equal.
+ """
+ self.assertTrue(Record(1, 2) == Record(1, 2))
+ self.assertFalse(Record(1, 2) == Record(1, 3))
+ self.assertFalse(Record(1, 2) == Record(2, 2))
+ self.assertFalse(Record(1, 2) == Record(3, 4))
+
+
+ def test_unequality(self):
+ """
+ Unequality between instances of a particular L{record} should be
+ defined as the negation of equality.
+ """
+ self.assertFalse(Record(1, 2) != Record(1, 2))
+ self.assertTrue(Record(1, 2) != Record(1, 3))
+ self.assertTrue(Record(1, 2) != Record(2, 2))
+ self.assertTrue(Record(1, 2) != Record(3, 4))
+
+
+ def test_differentClassesEquality(self):
+ """
+ Instances of different classes which mix in L{FancyEqMixin} should not
+ compare equal.
+ """
+ self.assertFalse(Record(1, 2) == DifferentRecord(1, 2))
+
+
+ def test_differentClassesInequality(self):
+ """
+ Instances of different classes which mix in L{FancyEqMixin} should
+ compare unequal.
+ """
+ self.assertTrue(Record(1, 2) != DifferentRecord(1, 2))
+
+
+ def test_inheritedClassesEquality(self):
+ """
+ An instance of a class which derives from a class which mixes in
+ L{FancyEqMixin} should compare equal to an instance of the base class
+ if and only if all of their attributes compare equal.
+ """
+ self.assertTrue(Record(1, 2) == DerivedRecord(1, 2))
+ self.assertFalse(Record(1, 2) == DerivedRecord(1, 3))
+ self.assertFalse(Record(1, 2) == DerivedRecord(2, 2))
+ self.assertFalse(Record(1, 2) == DerivedRecord(3, 4))
+
+
+ def test_inheritedClassesInequality(self):
+ """
+ An instance of a class which derives from a class which mixes in
+ L{FancyEqMixin} should compare unequal to an instance of the base
+ class if any of their attributes compare unequal.
+ """
+ self.assertFalse(Record(1, 2) != DerivedRecord(1, 2))
+ self.assertTrue(Record(1, 2) != DerivedRecord(1, 3))
+ self.assertTrue(Record(1, 2) != DerivedRecord(2, 2))
+ self.assertTrue(Record(1, 2) != DerivedRecord(3, 4))
+
+
+ def test_rightHandArgumentImplementsEquality(self):
+ """
+ The right-hand argument to the equality operator is given a chance
+ to determine the result of the operation if it is of a type
+ unrelated to the L{FancyEqMixin}-based instance on the left-hand
+ side.
+ """
+ self.assertTrue(Record(1, 2) == EqualToEverything())
+ self.assertFalse(Record(1, 2) == EqualToNothing())
+
+
+ def test_rightHandArgumentImplementsUnequality(self):
+ """
+ The right-hand argument to the non-equality operator is given a
+ chance to determine the result of the operation if it is of a type
+ unrelated to the L{FancyEqMixin}-based instance on the left-hand
+ side.
+ """
+ self.assertFalse(Record(1, 2) != EqualToEverything())
+ self.assertTrue(Record(1, 2) != EqualToNothing())
+
+
+
+class RunAsEffectiveUserTests(unittest.TestCase):
+ """
+ Test for the L{util.runAsEffectiveUser} function.
+ """
+
+ if getattr(os, "geteuid", None) is None:
+ skip = "geteuid/seteuid not available"
+
+ def setUp(self):
+ self.mockos = MockOS()
+ self.patch(os, "geteuid", self.mockos.geteuid)
+ self.patch(os, "getegid", self.mockos.getegid)
+ self.patch(os, "seteuid", self.mockos.seteuid)
+ self.patch(os, "setegid", self.mockos.setegid)
+
+
+ def _securedFunction(self, startUID, startGID, wantUID, wantGID):
+ """
+ Check if wanted UID/GID matched start or saved ones.
+ """
+ self.assertTrue(wantUID == startUID or
+ wantUID == self.mockos.seteuidCalls[-1])
+ self.assertTrue(wantGID == startGID or
+ wantGID == self.mockos.setegidCalls[-1])
+
+
+ def test_forwardResult(self):
+ """
+ L{util.runAsEffectiveUser} forwards the result obtained by calling the
+ given function
+ """
+ result = util.runAsEffectiveUser(0, 0, lambda: 1)
+ self.assertEquals(result, 1)
+
+
+ def test_takeParameters(self):
+ """
+ L{util.runAsEffectiveUser} pass the given parameters to the given
+ function.
+ """
+ result = util.runAsEffectiveUser(0, 0, lambda x: 2*x, 3)
+ self.assertEquals(result, 6)
+
+
+ def test_takesKeyworkArguments(self):
+ """
+ L{util.runAsEffectiveUser} pass the keyword parameters to the given
+ function.
+ """
+ result = util.runAsEffectiveUser(0, 0, lambda x, y=1, z=1: x*y*z, 2, z=3)
+ self.assertEquals(result, 6)
+
+
+ def _testUIDGIDSwitch(self, startUID, startGID, wantUID, wantGID,
+ expectedUIDSwitches, expectedGIDSwitches):
+ """
+ Helper method checking the calls to C{os.seteuid} and C{os.setegid}
+ made by L{util.runAsEffectiveUser}, when switching from startUID to
+ wantUID and from startGID to wantGID.
+ """
+ self.mockos.euid = startUID
+ self.mockos.egid = startGID
+ util.runAsEffectiveUser(
+ wantUID, wantGID,
+ self._securedFunction, startUID, startGID, wantUID, wantGID)
+ self.assertEquals(self.mockos.seteuidCalls, expectedUIDSwitches)
+ self.assertEquals(self.mockos.setegidCalls, expectedGIDSwitches)
+ self.mockos.seteuidCalls = []
+ self.mockos.setegidCalls = []
+
+
+ def test_root(self):
+ """
+ Check UID/GID switches when current effective UID is root.
+ """
+ self._testUIDGIDSwitch(0, 0, 0, 0, [], [])
+ self._testUIDGIDSwitch(0, 0, 1, 0, [1, 0], [])
+ self._testUIDGIDSwitch(0, 0, 0, 1, [], [1, 0])
+ self._testUIDGIDSwitch(0, 0, 1, 1, [1, 0], [1, 0])
+
+
+ def test_UID(self):
+ """
+ Check UID/GID switches when current effective UID is non-root.
+ """
+ self._testUIDGIDSwitch(1, 0, 0, 0, [0, 1], [])
+ self._testUIDGIDSwitch(1, 0, 1, 0, [], [])
+ self._testUIDGIDSwitch(1, 0, 1, 1, [0, 1, 0, 1], [1, 0])
+ self._testUIDGIDSwitch(1, 0, 2, 1, [0, 2, 0, 1], [1, 0])
+
+
+ def test_GID(self):
+ """
+ Check UID/GID switches when current effective GID is non-root.
+ """
+ self._testUIDGIDSwitch(0, 1, 0, 0, [], [0, 1])
+ self._testUIDGIDSwitch(0, 1, 0, 1, [], [])
+ self._testUIDGIDSwitch(0, 1, 1, 1, [1, 0], [])
+ self._testUIDGIDSwitch(0, 1, 1, 2, [1, 0], [2, 1])
+
+
+ def test_UIDGID(self):
+ """
+ Check UID/GID switches when current effective UID/GID is non-root.
+ """
+ self._testUIDGIDSwitch(1, 1, 0, 0, [0, 1], [0, 1])
+ self._testUIDGIDSwitch(1, 1, 0, 1, [0, 1], [])
+ self._testUIDGIDSwitch(1, 1, 1, 0, [0, 1, 0, 1], [0, 1])
+ self._testUIDGIDSwitch(1, 1, 1, 1, [], [])
+ self._testUIDGIDSwitch(1, 1, 2, 1, [0, 2, 0, 1], [])
+ self._testUIDGIDSwitch(1, 1, 1, 2, [0, 1, 0, 1], [2, 1])
+ self._testUIDGIDSwitch(1, 1, 2, 2, [0, 2, 0, 1], [2, 1])
+
+
+
+class UnsignedIDTests(unittest.TestCase):
+ """
+ Tests for L{util.unsignedID} and L{util.setIDFunction}.
+ """
+ def setUp(self):
+ """
+ Save the value of L{util._idFunction} and arrange for it to be restored
+ after the test runs.
+ """
+ self.addCleanup(setattr, util, '_idFunction', util._idFunction)
+
+
+ def test_setIDFunction(self):
+ """
+ L{util.setIDFunction} returns the last value passed to it.
+ """
+ value = object()
+ previous = util.setIDFunction(value)
+ result = util.setIDFunction(previous)
+ self.assertIdentical(value, result)
+
+
+ def test_unsignedID(self):
+ """
+ L{util.unsignedID} uses the function passed to L{util.setIDFunction} to
+ determine the unique integer id of an object and then adjusts it to be
+ positive if necessary.
+ """
+ foo = object()
+ bar = object()
+
+ # A fake object identity mapping
+ objects = {foo: 17, bar: -73}
+ def fakeId(obj):
+ return objects[obj]
+
+ util.setIDFunction(fakeId)
+
+ self.assertEquals(util.unsignedID(foo), 17)
+ self.assertEquals(util.unsignedID(bar), (sys.maxint + 1) * 2 - 73)
+
+
+ def test_defaultIDFunction(self):
+ """
+ L{util.unsignedID} uses the built in L{id} by default.
+ """
+ obj = object()
+ idValue = id(obj)
+ if idValue < 0:
+ idValue += (sys.maxint + 1) * 2
+
+ self.assertEquals(util.unsignedID(obj), idValue)
+
+
+
+class InitGroupsTests(unittest.TestCase):
+ """
+ Tests for L{util.initgroups}.
+ """
+
+ if pwd is None:
+ skip = "pwd not available"
+
+
+ def setUp(self):
+ self.addCleanup(setattr, util, "_c_initgroups", util._c_initgroups)
+ self.addCleanup(setattr, util, "setgroups", util.setgroups)
+
+
+ def test_initgroupsForceC(self):
+ """
+ If we fake the presence of the C extension, it's called instead of the
+ Python implementation.
+ """
+ calls = []
+ util._c_initgroups = lambda x, y: calls.append((x, y))
+ setgroupsCalls = []
+ util.setgroups = calls.append
+
+ util.initgroups(os.getuid(), 4)
+ self.assertEquals(calls, [(pwd.getpwuid(os.getuid())[0], 4)])
+ self.assertFalse(setgroupsCalls)
+
+
+ def test_initgroupsForcePython(self):
+ """
+ If we fake the absence of the C extension, the Python implementation is
+ called instead, calling C{os.setgroups}.
+ """
+ util._c_initgroups = None
+ calls = []
+ util.setgroups = calls.append
+ util.initgroups(os.getuid(), os.getgid())
+ # Something should be in the calls, we don't really care what
+ self.assertTrue(calls)
+
+
+ def test_initgroupsInC(self):
+ """
+ If the C extension is present, it's called instead of the Python
+ version. We check that by making sure C{os.setgroups} is not called.
+ """
+ calls = []
+ util.setgroups = calls.append
+ try:
+ util.initgroups(os.getuid(), os.getgid())
+ except OSError:
+ pass
+ self.assertFalse(calls)
+
+
+ if util._c_initgroups is None:
+ test_initgroupsInC.skip = "C initgroups not available"
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_versions.py b/vendor/Twisted-10.0.0/twisted/python/test/test_versions.py
new file mode 100644
index 0000000000..840a4914b9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_versions.py
@@ -0,0 +1,323 @@
+# Copyright (c) 2006-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+from cStringIO import StringIO
+
+from twisted.python.versions import getVersionString, IncomparableVersions
+from twisted.python.versions import Version, _inf
+from twisted.python.filepath import FilePath
+
+from twisted.trial import unittest
+
+
+
+VERSION_4_ENTRIES = """\
+<?xml version="1.0" encoding="utf-8"?>
+<wc-entries
+ xmlns="svn:">
+<entry
+ committed-rev="18210"
+ name=""
+ committed-date="2006-09-21T04:43:09.542953Z"
+ url="svn+ssh://svn.twistedmatrix.com/svn/Twisted/trunk/twisted"
+ last-author="exarkun"
+ kind="dir"
+ uuid="bbbe8e31-12d6-0310-92fd-ac37d47ddeeb"
+ repos="svn+ssh://svn.twistedmatrix.com/svn/Twisted"
+ revision="18211"/>
+</wc-entries>
+"""
+
+
+
+VERSION_8_ENTRIES = """\
+8
+
+dir
+22715
+svn+ssh://svn.twistedmatrix.com/svn/Twisted/trunk
+"""
+
+
+VERSION_9_ENTRIES = """\
+9
+
+dir
+22715
+svn+ssh://svn.twistedmatrix.com/svn/Twisted/trunk
+"""
+
+
+VERSION_10_ENTRIES = """\
+10
+
+dir
+22715
+svn+ssh://svn.twistedmatrix.com/svn/Twisted/trunk
+"""
+
+
+class VersionsTest(unittest.TestCase):
+
+ def test_versionComparison(self):
+ """
+ Versions can be compared for equality and order.
+ """
+ va = Version("dummy", 1, 0, 0)
+ vb = Version("dummy", 0, 1, 0)
+ self.failUnless(va > vb)
+ self.failUnless(vb < va)
+ self.failUnless(va >= vb)
+ self.failUnless(vb <= va)
+ self.failUnless(va != vb)
+ self.failUnless(vb == Version("dummy", 0, 1, 0))
+ self.failUnless(vb == vb)
+
+ # BREAK IT DOWN@!!
+ self.failIf(va < vb)
+ self.failIf(vb > va)
+ self.failIf(va <= vb)
+ self.failIf(vb >= va)
+ self.failIf(va == vb)
+ self.failIf(vb != Version("dummy", 0, 1, 0))
+ self.failIf(vb != vb)
+
+
+ def test_comparingPrereleasesWithReleases(self):
+ """
+ Prereleases are always less than versions without prereleases.
+ """
+ va = Version("whatever", 1, 0, 0, prerelease=1)
+ vb = Version("whatever", 1, 0, 0)
+ self.assertTrue(va < vb)
+ self.assertFalse(va > vb)
+ self.assertNotEquals(vb, va)
+
+
+ def test_comparingPrereleases(self):
+ """
+ The value specified as the prerelease is used in version comparisons.
+ """
+ va = Version("whatever", 1, 0, 0, prerelease=1)
+ vb = Version("whatever", 1, 0, 0, prerelease=2)
+ self.assertTrue(va < vb)
+ self.assertFalse(va > vb)
+ self.assertNotEqual(va, vb)
+
+
+ def test_infComparison(self):
+ """
+ L{_inf} is equal to L{_inf}.
+
+ This is a regression test.
+ """
+ self.assertEquals(_inf, _inf)
+
+
+ def testDontAllowBuggyComparisons(self):
+ self.assertRaises(IncomparableVersions,
+ cmp,
+ Version("dummy", 1, 0, 0),
+ Version("dumym", 1, 0, 0))
+
+
+ def test_repr(self):
+ """
+ Calling C{repr} on a version returns a human-readable string
+ representation of the version.
+ """
+ self.assertEquals(repr(Version("dummy", 1, 2, 3)),
+ "Version('dummy', 1, 2, 3)")
+
+
+ def test_reprWithPrerelease(self):
+ """
+ Calling C{repr} on a version with a prerelease returns a human-readable
+ string representation of the version including the prerelease.
+ """
+ self.assertEquals(repr(Version("dummy", 1, 2, 3, prerelease=4)),
+ "Version('dummy', 1, 2, 3, prerelease=4)")
+
+
+ def test_str(self):
+ """
+ Calling C{str} on a version returns a human-readable string
+ representation of the version.
+ """
+ self.assertEquals(str(Version("dummy", 1, 2, 3)),
+ "[dummy, version 1.2.3]")
+
+
+ def test_strWithPrerelease(self):
+ """
+ Calling C{str} on a version with a prerelease includes the prerelease.
+ """
+ self.assertEquals(str(Version("dummy", 1, 0, 0, prerelease=1)),
+ "[dummy, version 1.0.0pre1]")
+
+
+ def testShort(self):
+ self.assertEquals(Version('dummy', 1, 2, 3).short(), '1.2.3')
+
+
+ def test_goodSVNEntries_4(self):
+ """
+ Version should be able to parse an SVN format 4 entries file.
+ """
+ version = Version("dummy", 1, 0, 0)
+ self.assertEquals(
+ version._parseSVNEntries_4(StringIO(VERSION_4_ENTRIES)), '18211')
+
+
+ def test_goodSVNEntries_8(self):
+ """
+ Version should be able to parse an SVN format 8 entries file.
+ """
+ version = Version("dummy", 1, 0, 0)
+ self.assertEqual(
+ version._parseSVNEntries_8(StringIO(VERSION_8_ENTRIES)), '22715')
+
+
+ def test_goodSVNEntries_9(self):
+ """
+ Version should be able to parse an SVN format 9 entries file.
+ """
+ version = Version("dummy", 1, 0, 0)
+ self.assertEqual(
+ version._parseSVNEntries_9(StringIO(VERSION_9_ENTRIES)), '22715')
+
+
+ def test_goodSVNEntriesTenPlus(self):
+ """
+ Version should be able to parse an SVN format 10 entries file.
+ """
+ version = Version("dummy", 1, 0, 0)
+ self.assertEqual(
+ version._parseSVNEntriesTenPlus(StringIO(VERSION_10_ENTRIES)), '22715')
+
+
+ def test_getVersionString(self):
+ """
+ L{getVersionString} returns a string with the package name and the
+ short version number.
+ """
+ self.assertEqual(
+ 'Twisted 8.0.0', getVersionString(Version('Twisted', 8, 0, 0)))
+
+
+ def test_getVersionStringWithPrerelease(self):
+ """
+ L{getVersionString} includes the prerelease, if any.
+ """
+ self.assertEqual(
+ getVersionString(Version("whatever", 8, 0, 0, prerelease=1)),
+ "whatever 8.0.0pre1")
+
+
+ def test_base(self):
+ """
+ The L{base} method returns a very simple representation of the version.
+ """
+ self.assertEquals(Version("foo", 1, 0, 0).base(), "1.0.0")
+
+
+ def test_baseWithPrerelease(self):
+ """
+ The base version includes 'preX' for versions with prereleases.
+ """
+ self.assertEquals(Version("foo", 1, 0, 0, prerelease=8).base(),
+ "1.0.0pre8")
+
+
+
+class FormatDiscoveryTests(unittest.TestCase):
+ """
+ Tests which discover the parsing method based on the imported module name.
+ """
+
+ def setUp(self):
+ """
+ Create a temporary directory with a package structure in it.
+ """
+ self.entry = FilePath(self.mktemp())
+ self.preTestModules = sys.modules.copy()
+ sys.path.append(self.entry.path)
+ pkg = self.entry.child("twisted_python_versions_package")
+ pkg.makedirs()
+ pkg.child("__init__.py").setContent(
+ "from twisted.python.versions import Version\n"
+ "version = Version('twisted_python_versions_package', 1, 0, 0)\n")
+ self.svnEntries = pkg.child(".svn")
+ self.svnEntries.makedirs()
+
+
+ def tearDown(self):
+ """
+ Remove the imported modules and sys.path modifications.
+ """
+ sys.modules.clear()
+ sys.modules.update(self.preTestModules)
+ sys.path.remove(self.entry.path)
+
+
+ def checkSVNFormat(self, formatVersion, entriesText, expectedRevision):
+ """
+ Check for the given revision being detected after setting the SVN
+ entries text and format version of the test directory structure.
+ """
+ self.svnEntries.child("format").setContent(formatVersion+"\n")
+ self.svnEntries.child("entries").setContent(entriesText)
+ self.assertEqual(self.getVersion()._getSVNVersion(), expectedRevision)
+
+
+ def getVersion(self):
+ """
+ Import and retrieve the Version object from our dynamically created
+ package.
+ """
+ import twisted_python_versions_package
+ return twisted_python_versions_package.version
+
+
+ def test_detectVersion4(self):
+ """
+ Verify that version 4 format file will be properly detected and parsed.
+ """
+ self.checkSVNFormat("4", VERSION_4_ENTRIES, '18211')
+
+
+ def test_detectVersion8(self):
+ """
+ Verify that version 8 format files will be properly detected and
+ parsed.
+ """
+ self.checkSVNFormat("8", VERSION_8_ENTRIES, '22715')
+
+
+ def test_detectVersion9(self):
+ """
+ Verify that version 9 format files will be properly detected and
+ parsed.
+ """
+ self.checkSVNFormat("9", VERSION_9_ENTRIES, '22715')
+
+
+ def test_detectVersion10(self):
+ """
+ Verify that version 10 format files will be properly detected and
+ parsed.
+
+ Differing from previous formats, the version 10 format lacks a
+ I{format} file and B{only} has the version information on the first
+ line of the I{entries} file.
+ """
+ self.svnEntries.child("entries").setContent(VERSION_10_ENTRIES)
+ self.assertEquals(self.getVersion()._getSVNVersion(), '22715')
+
+
+ def test_detectUnknownVersion(self):
+ """
+ Verify that a new version of SVN will result in the revision 'Unknown'.
+ """
+ self.checkSVNFormat("some-random-new-version", "ooga booga!", 'Unknown')
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_win32.py b/vendor/Twisted-10.0.0/twisted/python/test/test_win32.py
new file mode 100644
index 0000000000..5ed693ce56
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_win32.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.trial import unittest
+from twisted.python.runtime import platform
+from twisted.python.win32 import cmdLineQuote
+
+
+class CommandLineQuotingTests(unittest.TestCase):
+ """
+ Tests for L{cmdLineQuote}.
+ """
+
+ def test_argWithoutSpaces(self):
+ """
+ Calling C{cmdLineQuote} with an argument with no spaces should
+ return the argument unchanged.
+ """
+ self.assertEquals(cmdLineQuote('an_argument'), 'an_argument')
+
+
+ def test_argWithSpaces(self):
+ """
+ Calling C{cmdLineQuote} with an argument containing spaces should
+ return the argument surrounded by quotes.
+ """
+ self.assertEquals(cmdLineQuote('An Argument'), '"An Argument"')
+
+
+ def test_emptyStringArg(self):
+ """
+ Calling C{cmdLineQuote} with an empty string should return a
+ quoted empty string.
+ """
+ self.assertEquals(cmdLineQuote(''), '""')
diff --git a/vendor/Twisted-10.0.0/twisted/python/test/test_zipstream.py b/vendor/Twisted-10.0.0/twisted/python/test/test_zipstream.py
new file mode 100644
index 0000000000..e082361f16
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/test/test_zipstream.py
@@ -0,0 +1,455 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.python.zipstream}
+"""
+import sys
+import random
+import zipfile
+
+from twisted.python.compat import set
+from twisted.python import zipstream, filepath
+from twisted.python.hashlib import md5
+from twisted.trial import unittest
+
+class FileEntryMixin:
+ """
+ File entry classes should behave as file-like objects
+ """
+ def getFileEntry(self, contents):
+ """
+ Return an appropriate zip file entry
+ """
+ filename = self.mktemp()
+ z = zipfile.ZipFile(filename, 'w', self.compression)
+ z.writestr('content', contents)
+ z.close()
+ z = zipstream.ChunkingZipFile(filename, 'r')
+ return z.readfile('content')
+
+
+ def test_isatty(self):
+ """
+ zip files should not be ttys, so isatty() should be false
+ """
+ self.assertEquals(self.getFileEntry('').isatty(), False)
+
+
+ def test_closed(self):
+ """
+ The C{closed} attribute should reflect whether C{close()} has been
+ called.
+ """
+ fileEntry = self.getFileEntry('')
+ self.assertEquals(fileEntry.closed, False)
+ fileEntry.close()
+ self.assertEquals(fileEntry.closed, True)
+
+
+ def test_readline(self):
+ """
+ C{readline()} should mirror L{file.readline} and return up to a single
+ deliminter.
+ """
+ fileEntry = self.getFileEntry('hoho\nho')
+ self.assertEquals(fileEntry.readline(), 'hoho\n')
+ self.assertEquals(fileEntry.readline(), 'ho')
+ self.assertEquals(fileEntry.readline(), '')
+
+
+ def test_next(self):
+ """
+ Zip file entries should implement the iterator protocol as files do.
+ """
+ fileEntry = self.getFileEntry('ho\nhoho')
+ self.assertEquals(fileEntry.next(), 'ho\n')
+ self.assertEquals(fileEntry.next(), 'hoho')
+ self.assertRaises(StopIteration, fileEntry.next)
+
+
+ def test_readlines(self):
+ """
+ C{readlines()} should return a list of all the lines.
+ """
+ fileEntry = self.getFileEntry('ho\nho\nho')
+ self.assertEquals(fileEntry.readlines(), ['ho\n', 'ho\n', 'ho'])
+
+
+ def test_iteration(self):
+ """
+ C{__iter__()} and C{xreadlines()} should return C{self}.
+ """
+ fileEntry = self.getFileEntry('')
+ self.assertIdentical(iter(fileEntry), fileEntry)
+ self.assertIdentical(fileEntry.xreadlines(), fileEntry)
+
+
+ def test_readWhole(self):
+ """
+ C{.read()} should read the entire file.
+ """
+ contents = "Hello, world!"
+ entry = self.getFileEntry(contents)
+ self.assertEquals(entry.read(), contents)
+
+
+ def test_readPartial(self):
+ """
+ C{.read(num)} should read num bytes from the file.
+ """
+ contents = "0123456789"
+ entry = self.getFileEntry(contents)
+ one = entry.read(4)
+ two = entry.read(200)
+ self.assertEquals(one, "0123")
+ self.assertEquals(two, "456789")
+
+
+ def test_tell(self):
+ """
+ C{.tell()} should return the number of bytes that have been read so
+ far.
+ """
+ contents = "x" * 100
+ entry = self.getFileEntry(contents)
+ entry.read(2)
+ self.assertEquals(entry.tell(), 2)
+ entry.read(4)
+ self.assertEquals(entry.tell(), 6)
+
+
+
+class DeflatedZipFileEntryTest(FileEntryMixin, unittest.TestCase):
+ """
+ DeflatedZipFileEntry should be file-like
+ """
+ compression = zipfile.ZIP_DEFLATED
+
+
+
+class ZipFileEntryTest(FileEntryMixin, unittest.TestCase):
+ """
+ ZipFileEntry should be file-like
+ """
+ compression = zipfile.ZIP_STORED
+
+
+
+class ZipstreamTest(unittest.TestCase):
+ """
+ Tests for twisted.python.zipstream
+ """
+ def setUp(self):
+ """
+ Creates junk data that can be compressed and a test directory for any
+ files that will be created
+ """
+ self.testdir = filepath.FilePath(self.mktemp())
+ self.testdir.makedirs()
+ self.unzipdir = self.testdir.child('unzipped')
+ self.unzipdir.makedirs()
+
+
+ def makeZipFile(self, contents, directory=''):
+ """
+ Makes a zip file archive containing len(contents) files. Contents
+ should be a list of strings, each string being the content of one file.
+ """
+ zpfilename = self.testdir.child('zipfile.zip').path
+ zpfile = zipfile.ZipFile(zpfilename, 'w')
+ for i, content in enumerate(contents):
+ filename = str(i)
+ if directory:
+ filename = directory + "/" + filename
+ zpfile.writestr(filename, content)
+ zpfile.close()
+ return zpfilename
+
+
+ def test_countEntries(self):
+ """
+ Make sure the deprecated L{countZipFileEntries} returns the correct
+ number of entries for a zip file.
+ """
+ name = self.makeZipFile(["one", "two", "three", "four", "five"])
+ result = self.assertWarns(DeprecationWarning,
+ "countZipFileEntries is deprecated.",
+ __file__, lambda :
+ zipstream.countZipFileEntries(name))
+ self.assertEquals(result, 5)
+
+
+ def test_invalidMode(self):
+ """
+ A ChunkingZipFile opened in write-mode should not allow .readfile(),
+ and raise a RuntimeError instead.
+ """
+ czf = zipstream.ChunkingZipFile(self.mktemp(), "w")
+ self.assertRaises(RuntimeError, czf.readfile, "something")
+
+
+ def test_closedArchive(self):
+ """
+ A closed ChunkingZipFile should raise a L{RuntimeError} when
+ .readfile() is invoked.
+ """
+ czf = zipstream.ChunkingZipFile(self.makeZipFile(["something"]), "r")
+ czf.close()
+ self.assertRaises(RuntimeError, czf.readfile, "something")
+
+
+ def test_invalidHeader(self):
+ """
+ A zipfile entry with the wrong magic number should raise BadZipfile for
+ readfile(), but that should not affect other files in the archive.
+ """
+ fn = self.makeZipFile(["test contents",
+ "more contents"])
+ zf = zipfile.ZipFile(fn, "r")
+ zeroOffset = zf.getinfo("0").header_offset
+ zf.close()
+ # Zero out just the one header.
+ scribble = file(fn, "r+b")
+ scribble.seek(zeroOffset, 0)
+ scribble.write(chr(0) * 4)
+ scribble.close()
+ czf = zipstream.ChunkingZipFile(fn)
+ self.assertRaises(zipfile.BadZipfile, czf.readfile, "0")
+ self.assertEquals(czf.readfile("1").read(), "more contents")
+
+
+ def test_filenameMismatch(self):
+ """
+ A zipfile entry with a different filename than is found in the central
+ directory should raise BadZipfile.
+ """
+ fn = self.makeZipFile(["test contents",
+ "more contents"])
+ zf = zipfile.ZipFile(fn, "r")
+ info = zf.getinfo("0")
+ info.filename = "not zero"
+ zf.close()
+ scribble = file(fn, "r+b")
+ scribble.seek(info.header_offset, 0)
+ scribble.write(info.FileHeader())
+ scribble.close()
+
+ czf = zipstream.ChunkingZipFile(fn)
+ self.assertRaises(zipfile.BadZipfile, czf.readfile, "0")
+ self.assertEquals(czf.readfile("1").read(), "more contents")
+
+
+ if sys.version_info < (2, 5):
+ # In python 2.4 and earlier, consistency between the directory and the
+ # file header are verified at archive-opening time. In python 2.5
+ # (and, presumably, later) it is readzipfile's responsibility.
+ message = "Consistency-checking only necessary in 2.5."
+ test_invalidHeader.skip = message
+ test_filenameMismatch.skip = message
+
+
+
+ def test_unsupportedCompression(self):
+ """
+ A zipfile which describes an unsupported compression mechanism should
+ raise BadZipfile.
+ """
+ fn = self.mktemp()
+ zf = zipfile.ZipFile(fn, "w")
+ zi = zipfile.ZipInfo("0")
+ zf.writestr(zi, "some data")
+ # Mangle its compression type in the central directory; can't do this
+ # before the writestr call or zipfile will (correctly) tell us not to
+ # pass bad compression types :)
+ zi.compress_type = 1234
+ zf.close()
+
+ czf = zipstream.ChunkingZipFile(fn)
+ self.assertRaises(zipfile.BadZipfile, czf.readfile, "0")
+
+
+ def test_extraData(self):
+ """
+ readfile() should skip over 'extra' data present in the zip metadata.
+ """
+ fn = self.mktemp()
+ zf = zipfile.ZipFile(fn, 'w')
+ zi = zipfile.ZipInfo("0")
+ zi.extra = "hello, extra"
+ zf.writestr(zi, "the real data")
+ zf.close()
+ czf = zipstream.ChunkingZipFile(fn)
+ self.assertEquals(czf.readfile("0").read(), "the real data")
+
+
+ def test_unzipIter(self):
+ """
+ L{twisted.python.zipstream.unzipIter} should unzip a file for each
+ iteration and yield the number of files left to unzip after that
+ iteration
+ """
+ numfiles = 10
+ contents = ['This is test file %d!' % i for i in range(numfiles)]
+ zpfilename = self.makeZipFile(contents)
+ uziter = zipstream.unzipIter(zpfilename, self.unzipdir.path)
+ for i in range(numfiles):
+ self.assertEquals(len(list(self.unzipdir.children())), i)
+ self.assertEquals(uziter.next(), numfiles - i - 1)
+ self.assertEquals(len(list(self.unzipdir.children())), numfiles)
+
+ for child in self.unzipdir.children():
+ num = int(child.basename())
+ self.assertEquals(child.open().read(), contents[num])
+
+
+ def test_unzipIterChunky(self):
+ """
+ L{twisted.python.zipstream.unzipIterChunky} returns an iterator which
+ must be exhausted to completely unzip the input archive.
+ """
+ numfiles = 10
+ contents = ['This is test file %d!' % i for i in range(numfiles)]
+ zpfilename = self.makeZipFile(contents)
+ list(zipstream.unzipIterChunky(zpfilename, self.unzipdir.path))
+ self.assertEquals(
+ set(self.unzipdir.listdir()),
+ set(map(str, range(numfiles))))
+
+ for child in self.unzipdir.children():
+ num = int(child.basename())
+ self.assertEquals(child.getContent(), contents[num])
+
+
+ def test_unzipIterChunkyDirectory(self):
+ """
+ The path to which a file is extracted by L{zipstream.unzipIterChunky}
+ is determined by joining the C{directory} argument to C{unzip} with the
+ path within the archive of the file being extracted.
+ """
+ numfiles = 10
+ contents = ['This is test file %d!' % i for i in range(numfiles)]
+ zpfilename = self.makeZipFile(contents, 'foo')
+ list(zipstream.unzipIterChunky(zpfilename, self.unzipdir.path))
+ self.assertEquals(
+ set(self.unzipdir.child('foo').listdir()),
+ set(map(str, range(numfiles))))
+
+ for child in self.unzipdir.child('foo').children():
+ num = int(child.basename())
+ self.assertEquals(child.getContent(), contents[num])
+
+
+ def test_unzip(self):
+ """
+ L{twisted.python.zipstream.unzip} should extract all files from a zip
+ archive
+ """
+ numfiles = 3
+ zpfilename = self.makeZipFile([str(i) for i in range(numfiles)])
+ zipstream.unzip(zpfilename, self.unzipdir.path)
+ self.assertEqual(
+ set(self.unzipdir.listdir()),
+ set(map(str, range(numfiles))))
+ for i in range(numfiles):
+ self.assertEqual(self.unzipdir.child(str(i)).getContent(), str(i))
+
+
+ def test_unzipDirectory(self):
+ """
+ The path to which a file is extracted by L{zipstream.unzip} is
+ determined by joining the C{directory} argument to C{unzip} with the
+ path within the archive of the file being extracted.
+ """
+ numfiles = 3
+ zpfilename = self.makeZipFile([str(i) for i in range(numfiles)], 'foo')
+ zipstream.unzip(zpfilename, self.unzipdir.path)
+ self.assertEqual(
+ set(self.unzipdir.child('foo').listdir()),
+ set(map(str, range(numfiles))))
+ for i in range(numfiles):
+ self.assertEqual(
+ self.unzipdir.child('foo').child(str(i)).getContent(), str(i))
+
+
+ def test_overwrite(self):
+ """
+ L{twisted.python.zipstream.unzip} and
+ L{twisted.python.zipstream.unzipIter} shouldn't overwrite files unless
+ the 'overwrite' flag is passed
+ """
+ testfile = self.unzipdir.child('0')
+ zpfilename = self.makeZipFile(['OVERWRITTEN'])
+
+ testfile.setContent('NOT OVERWRITTEN')
+ zipstream.unzip(zpfilename, self.unzipdir.path)
+ self.assertEquals(testfile.open().read(), 'NOT OVERWRITTEN')
+ zipstream.unzip(zpfilename, self.unzipdir.path, overwrite=True)
+ self.assertEquals(testfile.open().read(), 'OVERWRITTEN')
+
+ testfile.setContent('NOT OVERWRITTEN')
+ uziter = zipstream.unzipIter(zpfilename, self.unzipdir.path)
+ uziter.next()
+ self.assertEquals(testfile.open().read(), 'NOT OVERWRITTEN')
+ uziter = zipstream.unzipIter(zpfilename, self.unzipdir.path,
+ overwrite=True)
+ uziter.next()
+ self.assertEquals(testfile.open().read(), 'OVERWRITTEN')
+
+
+ # XXX these tests are kind of gross and old, but I think unzipIterChunky is
+ # kind of a gross function anyway. We should really write an abstract
+ # copyTo/moveTo that operates on FilePath and make sure ZipPath can support
+ # it, then just deprecate / remove this stuff.
+ def _unzipIterChunkyTest(self, compression, chunksize, lower, upper):
+ """
+ unzipIterChunky should unzip the given number of bytes per iteration.
+ """
+ junk = ' '.join([str(random.random()) for n in xrange(1000)])
+ junkmd5 = md5(junk).hexdigest()
+
+ tempdir = filepath.FilePath(self.mktemp())
+ tempdir.makedirs()
+ zfpath = tempdir.child('bigfile.zip').path
+ self._makebigfile(zfpath, compression, junk)
+ uziter = zipstream.unzipIterChunky(zfpath, tempdir.path,
+ chunksize=chunksize)
+ r = uziter.next()
+ # test that the number of chunks is in the right ballpark;
+ # this could theoretically be any number but statistically it
+ # should always be in this range
+ approx = lower < r < upper
+ self.failUnless(approx)
+ for r in uziter:
+ pass
+ self.assertEqual(r, 0)
+ newmd5 = md5(
+ tempdir.child("zipstreamjunk").open().read()).hexdigest()
+ self.assertEqual(newmd5, junkmd5)
+
+ def test_unzipIterChunkyStored(self):
+ """
+ unzipIterChunky should unzip the given number of bytes per iteration on
+ a stored archive.
+ """
+ self._unzipIterChunkyTest(zipfile.ZIP_STORED, 500, 35, 45)
+
+
+ def test_chunkyDeflated(self):
+ """
+ unzipIterChunky should unzip the given number of bytes per iteration on
+ a deflated archive.
+ """
+ self._unzipIterChunkyTest(zipfile.ZIP_DEFLATED, 972, 23, 27)
+
+
+ def _makebigfile(self, filename, compression, junk):
+ """
+ Create a zip file with the given file name and compression scheme.
+ """
+ zf = zipfile.ZipFile(filename, 'w', compression)
+ for i in range(10):
+ fn = 'zipstream%d' % i
+ zf.writestr(fn, "")
+ zf.writestr('zipstreamjunk', junk)
+ zf.close()
diff --git a/vendor/Twisted-10.0.0/twisted/python/text.py b/vendor/Twisted-10.0.0/twisted/python/text.py
new file mode 100644
index 0000000000..b50287050c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/text.py
@@ -0,0 +1,227 @@
+# -*- test-case-name: twisted.test.test_text -*-
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Miscellany of text-munging functions.
+"""
+
+import string, types
+
+def stringyString(object, indentation=''):
+ """Expansive string formatting for sequence types.
+
+ list.__str__ and dict.__str__ use repr() to display their
+ elements. This function also turns these sequence types
+ into strings, but uses str() on their elements instead.
+
+ Sequence elements are also displayed on seperate lines,
+ and nested sequences have nested indentation.
+ """
+ braces = ''
+ sl = []
+
+ if type(object) is types.DictType:
+ braces = '{}'
+ for key, value in object.items():
+ value = stringyString(value, indentation + ' ')
+ if isMultiline(value):
+ if endsInNewline(value):
+ value = value[:-len('\n')]
+ sl.append("%s %s:\n%s" % (indentation, key, value))
+ else:
+ # Oops. Will have to move that indentation.
+ sl.append("%s %s: %s" % (indentation, key,
+ value[len(indentation) + 3:]))
+
+ elif type(object) in (types.TupleType, types.ListType):
+ if type(object) is types.TupleType:
+ braces = '()'
+ else:
+ braces = '[]'
+
+ for element in object:
+ element = stringyString(element, indentation + ' ')
+ sl.append(string.rstrip(element) + ',')
+ else:
+ sl[:] = map(lambda s, i=indentation: i+s,
+ string.split(str(object),'\n'))
+
+ if not sl:
+ sl.append(indentation)
+
+ if braces:
+ sl[0] = indentation + braces[0] + sl[0][len(indentation) + 1:]
+ sl[-1] = sl[-1] + braces[-1]
+
+ s = string.join(sl, "\n")
+
+ if isMultiline(s) and not endsInNewline(s):
+ s = s + '\n'
+
+ return s
+
+def isMultiline(s):
+ """Returns True if this string has a newline in it."""
+ return (string.find(s, '\n') != -1)
+
+def endsInNewline(s):
+ """Returns True if this string ends in a newline."""
+ return (s[-len('\n'):] == '\n')
+
+def docstringLStrip(docstring):
+ """Gets rid of unsightly lefthand docstring whitespace residue.
+
+ You'd think someone would have done this already, but apparently
+ not in 1.5.2.
+
+ BUT since we're all using Python 2.1 now, use L{inspect.getdoc}
+ instead. I{This function should go away soon.}
+ """
+
+ if not docstring:
+ return docstring
+
+ docstring = string.replace(docstring, '\t', ' ' * 8)
+ lines = string.split(docstring,'\n')
+
+ leading = 0
+ for l in xrange(1,len(lines)):
+ line = lines[l]
+ if string.strip(line):
+ while 1:
+ if line[leading] == ' ':
+ leading = leading + 1
+ else:
+ break
+ if leading:
+ break
+
+ outlines = lines[0:1]
+ for l in xrange(1,len(lines)):
+ outlines.append(lines[l][leading:])
+
+ return string.join(outlines, '\n')
+
+def greedyWrap(inString, width=80):
+ """Given a string and a column width, return a list of lines.
+
+ Caveat: I'm use a stupid greedy word-wrapping
+ algorythm. I won't put two spaces at the end
+ of a sentence. I don't do full justification.
+ And no, I've never even *heard* of hypenation.
+ """
+
+ outLines = []
+
+ #eww, evil hacks to allow paragraphs delimited by two \ns :(
+ if inString.find('\n\n') >= 0:
+ paragraphs = string.split(inString, '\n\n')
+ for para in paragraphs:
+ outLines.extend(greedyWrap(para) + [''])
+ return outLines
+ inWords = string.split(inString)
+
+ column = 0
+ ptr_line = 0
+ while inWords:
+ column = column + len(inWords[ptr_line])
+ ptr_line = ptr_line + 1
+
+ if (column > width):
+ if ptr_line == 1:
+ # This single word is too long, it will be the whole line.
+ pass
+ else:
+ # We've gone too far, stop the line one word back.
+ ptr_line = ptr_line - 1
+ (l, inWords) = (inWords[0:ptr_line], inWords[ptr_line:])
+ outLines.append(string.join(l,' '))
+
+ ptr_line = 0
+ column = 0
+ elif not (len(inWords) > ptr_line):
+ # Clean up the last bit.
+ outLines.append(string.join(inWords, ' '))
+ del inWords[:]
+ else:
+ # Space
+ column = column + 1
+ # next word
+
+ return outLines
+
+
+wordWrap = greedyWrap
+
+def removeLeadingBlanks(lines):
+ ret = []
+ for line in lines:
+ if ret or line.strip():
+ ret.append(line)
+ return ret
+
+def removeLeadingTrailingBlanks(s):
+ lines = removeLeadingBlanks(s.split('\n'))
+ lines.reverse()
+ lines = removeLeadingBlanks(lines)
+ lines.reverse()
+ return '\n'.join(lines)+'\n'
+
+def splitQuoted(s):
+ """Like string.split, but don't break substrings inside quotes.
+
+ >>> splitQuoted('the \"hairy monkey\" likes pie')
+ ['the', 'hairy monkey', 'likes', 'pie']
+
+ Another one of those \"someone must have a better solution for
+ this\" things. This implementation is a VERY DUMB hack done too
+ quickly.
+ """
+ out = []
+ quot = None
+ phrase = None
+ for word in s.split():
+ if phrase is None:
+ if word and (word[0] in ("\"", "'")):
+ quot = word[0]
+ word = word[1:]
+ phrase = []
+
+ if phrase is None:
+ out.append(word)
+ else:
+ if word and (word[-1] == quot):
+ word = word[:-1]
+ phrase.append(word)
+ out.append(" ".join(phrase))
+ phrase = None
+ else:
+ phrase.append(word)
+
+ return out
+
+def strFile(p, f, caseSensitive=True):
+ """Find whether string p occurs in a read()able object f
+ @rtype: C{bool}
+ """
+ buf = ""
+ buf_len = max(len(p), 2**2**2**2)
+ if not caseSensitive:
+ p = p.lower()
+ while 1:
+ r = f.read(buf_len-len(p))
+ if not caseSensitive:
+ r = r.lower()
+ bytes_read = len(r)
+ if bytes_read == 0:
+ return False
+ l = len(buf)+bytes_read-buf_len
+ if l <= 0:
+ buf = buf + r
+ else:
+ buf = buf[l:] + r
+ if buf.find(p) != -1:
+ return True
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/threadable.py b/vendor/Twisted-10.0.0/twisted/python/threadable.py
new file mode 100644
index 0000000000..93303204ea
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/threadable.py
@@ -0,0 +1,120 @@
+# -*- test-case-name: twisted.python.threadable -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+A module that will allow your program to be multi-threaded,
+micro-threaded, and single-threaded. Currently microthreads are
+unimplemented. The idea is to abstract away some commonly used
+functionality so that I don't have to special-case it in all programs.
+"""
+
+import warnings
+
+from twisted.python import hook
+
+class DummyLock(object):
+ """
+ Hack to allow locks to be unpickled on an unthreaded system.
+ """
+
+ def __reduce__(self):
+ return (unpickle_lock, ())
+
+def unpickle_lock():
+ if threadingmodule is not None:
+ return XLock()
+ else:
+ return DummyLock()
+unpickle_lock.__safe_for_unpickling__ = True
+
+def _synchPre(self, *a, **b):
+ if '_threadable_lock' not in self.__dict__:
+ _synchLockCreator.acquire()
+ if '_threadable_lock' not in self.__dict__:
+ self.__dict__['_threadable_lock'] = XLock()
+ _synchLockCreator.release()
+ self._threadable_lock.acquire()
+
+def _synchPost(self, *a, **b):
+ self._threadable_lock.release()
+
+def synchronize(*klasses):
+ """Make all methods listed in each class' synchronized attribute synchronized.
+
+ The synchronized attribute should be a list of strings, consisting of the
+ names of methods that must be synchronized. If we are running in threaded
+ mode these methods will be wrapped with a lock.
+ """
+ if threadmodule is not None:
+ for klass in klasses:
+ for methodName in klass.synchronized:
+ hook.addPre(klass, methodName, _synchPre)
+ hook.addPost(klass, methodName, _synchPost)
+
+def init(with_threads=1):
+ """Initialize threading.
+
+ Don't bother calling this. If it needs to happen, it will happen.
+ """
+ global threaded, _synchLockCreator, XLock
+
+ if with_threads:
+ if not threaded:
+ if threadmodule is not None:
+ threaded = True
+
+ class XLock(threadingmodule._RLock, object):
+ def __reduce__(self):
+ return (unpickle_lock, ())
+
+ _synchLockCreator = XLock()
+ else:
+ raise RuntimeError("Cannot initialize threading, platform lacks thread support")
+ else:
+ if threaded:
+ raise RuntimeError("Cannot uninitialize threads")
+ else:
+ pass
+
+_dummyID = object()
+def getThreadID():
+ if threadmodule is None:
+ return _dummyID
+ return threadmodule.get_ident()
+
+
+def isInIOThread():
+ """Are we in the thread responsable for I/O requests (the event loop)?
+ """
+ return ioThread == getThreadID()
+
+
+def registerAsIOThread():
+ """Mark the current thread as responsable for I/O requests.
+ """
+ global ioThread
+ ioThread = getThreadID()
+
+
+ioThread = None
+threaded = False
+
+def whenThreaded(cb):
+ warnings.warn("threadable.whenThreaded is deprecated. "
+ "Use application-level logic instead.",
+ DeprecationWarning, stacklevel=2)
+ cb()
+
+try:
+ import thread as threadmodule
+ import threading as threadingmodule
+except ImportError:
+ threadmodule = None
+ threadingmodule = None
+else:
+ init(True)
+
+__all__ = ['isInIOThread', 'registerAsIOThread', 'getThreadID', 'XLock',
+ 'whenThreaded']
diff --git a/vendor/Twisted-10.0.0/twisted/python/threadpool.py b/vendor/Twisted-10.0.0/twisted/python/threadpool.py
new file mode 100644
index 0000000000..4475d5aa57
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/threadpool.py
@@ -0,0 +1,308 @@
+# -*- test-case-name: twisted.test.test_threadpool -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+twisted.threadpool: a pool of threads to which we dispatch tasks.
+
+In most cases you can just use reactor.callInThread and friends
+instead of creating a thread pool directly.
+"""
+
+# System Imports
+import Queue
+import threading
+import copy
+import sys
+import warnings
+
+
+# Twisted Imports
+from twisted.python import log, runtime, context, failure
+
+WorkerStop = object()
+
+
+class ThreadPool:
+ """
+ This class (hopefully) generalizes the functionality of a pool of
+ threads to which work can be dispatched.
+
+ callInThread() and stop() should only be called from
+ a single thread, unless you make a subclass where stop() and
+ _startSomeWorkers() are synchronized.
+ """
+ min = 5
+ max = 20
+ joined = False
+ started = False
+ workers = 0
+ name = None
+
+ threadFactory = threading.Thread
+ currentThread = staticmethod(threading.currentThread)
+
+ def __init__(self, minthreads=5, maxthreads=20, name=None):
+ """
+ Create a new threadpool.
+
+ @param minthreads: minimum number of threads in the pool
+
+ @param maxthreads: maximum number of threads in the pool
+ """
+ assert minthreads >= 0, 'minimum is negative'
+ assert minthreads <= maxthreads, 'minimum is greater than maximum'
+ self.q = Queue.Queue(0)
+ self.min = minthreads
+ self.max = maxthreads
+ self.name = name
+ if runtime.platform.getType() != "java":
+ self.waiters = []
+ self.threads = []
+ self.working = []
+ else:
+ self.waiters = ThreadSafeList()
+ self.threads = ThreadSafeList()
+ self.working = ThreadSafeList()
+
+ def start(self):
+ """
+ Start the threadpool.
+ """
+ self.joined = False
+ self.started = True
+ # Start some threads.
+ self.adjustPoolsize()
+
+ def startAWorker(self):
+ self.workers += 1
+ name = "PoolThread-%s-%s" % (self.name or id(self), self.workers)
+ newThread = self.threadFactory(target=self._worker, name=name)
+ self.threads.append(newThread)
+ newThread.start()
+
+ def stopAWorker(self):
+ self.q.put(WorkerStop)
+ self.workers -= 1
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+ ThreadPool.__init__(self, self.min, self.max)
+
+ def __getstate__(self):
+ state = {}
+ state['min'] = self.min
+ state['max'] = self.max
+ return state
+
+ def _startSomeWorkers(self):
+ neededSize = self.q.qsize() + len(self.working)
+ # Create enough, but not too many
+ while self.workers < min(self.max, neededSize):
+ self.startAWorker()
+
+
+ def dispatch(self, owner, func, *args, **kw):
+ """
+ DEPRECATED: use L{callInThread} instead.
+
+ Dispatch a function to be a run in a thread.
+ """
+ warnings.warn("dispatch() is deprecated since Twisted 8.0, "
+ "use callInThread() instead",
+ DeprecationWarning, stacklevel=2)
+ self.callInThread(func, *args, **kw)
+
+
+ def callInThread(self, func, *args, **kw):
+ """
+ Call a callable object in a separate thread.
+
+ @param func: callable object to be called in separate thread
+
+ @param *args: positional arguments to be passed to func
+
+ @param **kw: keyword args to be passed to func
+ """
+ self.callInThreadWithCallback(None, func, *args, **kw)
+
+
+ def callInThreadWithCallback(self, onResult, func, *args, **kw):
+ """
+ Call a callable object in a separate thread and call onResult
+ with the return value, or a L{twisted.python.failure.Failure}
+ if the callable raises an exception.
+
+ The callable is allowed to block, but the onResult function
+ must not block and should perform as little work as possible.
+
+ A typical action for onResult for a threadpool used with a
+ Twisted reactor would be to schedule a Deferred to fire in the
+ main reactor thread using C{.callFromThread}. Note that
+ onResult is called inside the separate thread, not inside the
+ reactor thread.
+
+ @param onResult: a callable with the signature (success, result).
+ If the callable returns normally, onResult is called with
+ (True, result) where result is the return value of the callable.
+ If the callable throws an exception, onResult is called with
+ (False, failure).
+
+ Optionally, onResult may be None, in which case it is not
+ called at all.
+
+ @param func: callable object to be called in separate thread
+
+ @param *args: positional arguments to be passed to func
+
+ @param **kwargs: keyword arguments to be passed to func
+ """
+ if self.joined:
+ return
+ ctx = context.theContextTracker.currentContext().contexts[-1]
+ o = (ctx, func, args, kw, onResult)
+ self.q.put(o)
+ if self.started:
+ self._startSomeWorkers()
+
+
+ def _runWithCallback(self, callback, errback, func, args, kwargs):
+ try:
+ result = apply(func, args, kwargs)
+ except:
+ errback(sys.exc_info()[1])
+ else:
+ callback(result)
+
+
+ def dispatchWithCallback(self, owner, callback, errback, func, *args, **kw):
+ """
+ DEPRECATED: use L{twisted.internet.threads.deferToThread} instead.
+
+ Dispatch a function, returning the result to a callback function.
+
+ The callback function will be called in the thread - make sure it is
+ thread-safe.
+ """
+ warnings.warn("dispatchWithCallback() is deprecated since Twisted 8.0, "
+ "use twisted.internet.threads.deferToThread() instead.",
+ DeprecationWarning, stacklevel=2)
+ self.callInThread(
+ self._runWithCallback, callback, errback, func, args, kw
+ )
+
+
+ def _worker(self):
+ """
+ Method used as target of the created threads: retrieve task to run
+ from the threadpool, run it, and proceed to the next task until
+ threadpool is stopped.
+ """
+ ct = self.currentThread()
+ o = self.q.get()
+ while o is not WorkerStop:
+ self.working.append(ct)
+ ctx, function, args, kwargs, onResult = o
+ del o
+
+ try:
+ result = context.call(ctx, function, *args, **kwargs)
+ success = True
+ except:
+ success = False
+ if onResult is None:
+ context.call(ctx, log.err)
+ result = None
+ else:
+ result = failure.Failure()
+
+ del function, args, kwargs
+
+ self.working.remove(ct)
+
+ if onResult is not None:
+ try:
+ context.call(ctx, onResult, success, result)
+ except:
+ context.call(ctx, log.err)
+
+ del ctx, onResult, result
+
+ self.waiters.append(ct)
+ o = self.q.get()
+ self.waiters.remove(ct)
+
+ self.threads.remove(ct)
+
+ def stop(self):
+ """
+ Shutdown the threads in the threadpool.
+ """
+ self.joined = True
+ threads = copy.copy(self.threads)
+ while self.workers:
+ self.q.put(WorkerStop)
+ self.workers -= 1
+
+ # and let's just make sure
+ # FIXME: threads that have died before calling stop() are not joined.
+ for thread in threads:
+ thread.join()
+
+ def adjustPoolsize(self, minthreads=None, maxthreads=None):
+ if minthreads is None:
+ minthreads = self.min
+ if maxthreads is None:
+ maxthreads = self.max
+
+ assert minthreads >= 0, 'minimum is negative'
+ assert minthreads <= maxthreads, 'minimum is greater than maximum'
+
+ self.min = minthreads
+ self.max = maxthreads
+ if not self.started:
+ return
+
+ # Kill of some threads if we have too many.
+ while self.workers > self.max:
+ self.stopAWorker()
+ # Start some threads if we have too few.
+ while self.workers < self.min:
+ self.startAWorker()
+ # Start some threads if there is a need.
+ self._startSomeWorkers()
+
+ def dumpStats(self):
+ log.msg('queue: %s' % self.q.queue)
+ log.msg('waiters: %s' % self.waiters)
+ log.msg('workers: %s' % self.working)
+ log.msg('total: %s' % self.threads)
+
+
+class ThreadSafeList:
+ """
+ In Jython 2.1 lists aren't thread-safe, so this wraps it.
+ """
+
+ def __init__(self):
+ self.lock = threading.Lock()
+ self.l = []
+
+ def append(self, i):
+ self.lock.acquire()
+ try:
+ self.l.append(i)
+ finally:
+ self.lock.release()
+
+ def remove(self, i):
+ self.lock.acquire()
+ try:
+ self.l.remove(i)
+ finally:
+ self.lock.release()
+
+ def __len__(self):
+ return len(self.l)
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/timeoutqueue.py b/vendor/Twisted-10.0.0/twisted/python/timeoutqueue.py
new file mode 100644
index 0000000000..9252dc3120
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/timeoutqueue.py
@@ -0,0 +1,49 @@
+# -*- test-case-name: twisted.test.test_timeoutqueue -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A Queue subclass that supports timeouts.
+"""
+
+# System Imports
+import Queue, time, warnings
+
+
+_time = time.time
+_sleep = time.sleep
+
+
+class TimedOut(Exception):
+ pass
+
+
+class TimeoutQueue(Queue.Queue):
+ """
+ A thread-safe queue that supports timeouts.
+ """
+
+ def __init__(self, max=0):
+ warnings.warn("timeoutqueue is deprecated since Twisted 8.0",
+ category=DeprecationWarning, stacklevel=2)
+ Queue.Queue.__init__(self, max)
+
+ def wait(self, timeout):
+ """
+ Wait until the queue isn't empty. Raises TimedOut if still empty.
+ """
+ endtime = _time() + timeout
+ delay = 0.0005 # 500 us -> initial delay of 1 ms
+ while 1:
+ gotit = not self.empty()
+ if gotit:
+ break
+ remaining = endtime - _time()
+ if remaining <= 0:
+ raise TimedOut, "timed out."
+ delay = min(delay * 2, remaining, .05)
+ _sleep(delay)
+
+
+__all__ = ["TimeoutQueue", "TimedOut"]
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/urlpath.py b/vendor/Twisted-10.0.0/twisted/python/urlpath.py
new file mode 100644
index 0000000000..bee8357abf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/urlpath.py
@@ -0,0 +1,122 @@
+# -*- test-case-name: twisted.test.test_paths -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+import urlparse
+import urllib
+
+class URLPath:
+ def __init__(self, scheme='', netloc='localhost', path='',
+ query='', fragment=''):
+ self.scheme = scheme or 'http'
+ self.netloc = netloc
+ self.path = path or '/'
+ self.query = query
+ self.fragment = fragment
+
+ _qpathlist = None
+ _uqpathlist = None
+
+ def pathList(self, unquote=0, copy=1):
+ if self._qpathlist is None:
+ self._qpathlist = self.path.split('/')
+ self._uqpathlist = map(urllib.unquote, self._qpathlist)
+ if unquote:
+ result = self._uqpathlist
+ else:
+ result = self._qpathlist
+ if copy:
+ return result[:]
+ else:
+ return result
+
+ def fromString(klass, st):
+ t = urlparse.urlsplit(st)
+ u = klass(*t)
+ return u
+
+ fromString = classmethod(fromString)
+
+ def fromRequest(klass, request):
+ return klass.fromString(request.prePathURL())
+
+ fromRequest = classmethod(fromRequest)
+
+ def _pathMod(self, newpathsegs, keepQuery):
+ if keepQuery:
+ query = self.query
+ else:
+ query = ''
+ return URLPath(self.scheme,
+ self.netloc,
+ '/'.join(newpathsegs),
+ query)
+
+ def sibling(self, path, keepQuery=0):
+ l = self.pathList()
+ l[-1] = path
+ return self._pathMod(l, keepQuery)
+
+ def child(self, path, keepQuery=0):
+ l = self.pathList()
+ if l[-1] == '':
+ l[-1] = path
+ else:
+ l.append(path)
+ return self._pathMod(l, keepQuery)
+
+ def parent(self, keepQuery=0):
+ l = self.pathList()
+ if l[-1] == '':
+ del l[-2]
+ else:
+ # We are a file, such as http://example.com/foo/bar
+ # our parent directory is http://example.com/
+ l.pop()
+ l[-1] = ''
+ return self._pathMod(l, keepQuery)
+
+ def here(self, keepQuery=0):
+ l = self.pathList()
+ if l[-1] != '':
+ l[-1] = ''
+ return self._pathMod(l, keepQuery)
+
+ def click(self, st):
+ """Return a path which is the URL where a browser would presumably take
+ you if you clicked on a link with an HREF as given.
+ """
+ scheme, netloc, path, query, fragment = urlparse.urlsplit(st)
+ if not scheme:
+ scheme = self.scheme
+ if not netloc:
+ netloc = self.netloc
+ if not path:
+ path = self.path
+ if not query:
+ query = self.query
+ elif path[0] != '/':
+ l = self.pathList()
+ l[-1] = path
+ path = '/'.join(l)
+
+ return URLPath(scheme,
+ netloc,
+ path,
+ query,
+ fragment)
+
+
+
+ def __str__(self):
+ x = urlparse.urlunsplit((
+ self.scheme, self.netloc, self.path,
+ self.query, self.fragment))
+ return x
+
+ def __repr__(self):
+ return ('URLPath(scheme=%r, netloc=%r, path=%r, query=%r, fragment=%r)'
+ % (self.scheme, self.netloc, self.path, self.query, self.fragment))
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/usage.py b/vendor/Twisted-10.0.0/twisted/python/usage.py
new file mode 100644
index 0000000000..7abe3557d0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/usage.py
@@ -0,0 +1,631 @@
+# -*- test-case-name: twisted.test.test_usage -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+twisted.python.usage is a module for parsing/handling the
+command line of your program.
+
+For information on how to use it, see
+U{http://twistedmatrix.com/projects/core/documentation/howto/options.html},
+or doc/howto/options.html in your Twisted directory.
+"""
+
+# System Imports
+import os
+import sys
+import getopt
+from os import path
+
+# Sibling Imports
+from twisted.python import reflect, text, util
+
+
+class UsageError(Exception):
+ pass
+
+
+error = UsageError
+
+
+class CoerceParameter(object):
+ """
+ Utility class that can corce a parameter before storing it.
+ """
+ def __init__(self, options, coerce):
+ """
+ @param options: parent Options object
+ @param coerce: callable used to coerce the value.
+ """
+ self.options = options
+ self.coerce = coerce
+ self.doc = getattr(self.coerce, 'coerceDoc', '')
+
+ def dispatch(self, parameterName, value):
+ """
+ When called in dispatch, do the coerce for C{value} and save the
+ returned value.
+ """
+ if value is None:
+ raise UsageError("Parameter '%s' requires an argument."
+ % (parameterName,))
+ try:
+ value = self.coerce(value)
+ except ValueError, e:
+ raise UsageError("Parameter type enforcement failed: %s" % (e,))
+
+ self.options.opts[parameterName] = value
+
+
+class Options(dict):
+ """
+ An option list parser class
+
+ C{optFlags} and C{optParameters} are lists of available parameters
+ which your program can handle. The difference between the two
+ is the 'flags' have an on(1) or off(0) state (off by default)
+ whereas 'parameters' have an assigned value, with an optional
+ default. (Compare '--verbose' and '--verbosity=2')
+
+ optFlags is assigned a list of lists. Each list represents
+ a flag parameter, as so::
+
+ | optFlags = [['verbose', 'v', 'Makes it tell you what it doing.'],
+ | ['quiet', 'q', 'Be vewy vewy quiet.']]
+
+ As you can see, the first item is the long option name
+ (prefixed with '--' on the command line), followed by the
+ short option name (prefixed with '-'), and the description.
+ The description is used for the built-in handling of the
+ --help switch, which prints a usage summary.
+
+ C{optParameters} is much the same, except the list also contains
+ a default value::
+
+ | optParameters = [['outfile', 'O', 'outfile.log', 'Description...']]
+
+ A coerce function can also be specified as the last element: it will be
+ called with the argument and should return the value that will be stored
+ for the option. This function can have a C{coerceDoc} attribute which
+ will be appended to the documentation of the option.
+
+ subCommands is a list of 4-tuples of (command name, command shortcut,
+ parser class, documentation). If the first non-option argument found is
+ one of the given command names, an instance of the given parser class is
+ instantiated and given the remainder of the arguments to parse and
+ self.opts[command] is set to the command name. For example::
+
+ | subCommands = [
+ | ['inquisition', 'inquest', InquisitionOptions,
+ | 'Perform an inquisition'],
+ | ['holyquest', 'quest', HolyQuestOptions,
+ | 'Embark upon a holy quest']
+ | ]
+
+ In this case, C{"<program> holyquest --horseback --for-grail"} will cause
+ C{HolyQuestOptions} to be instantiated and asked to parse
+ C{['--horseback', '--for-grail']}. Currently, only the first sub-command
+ is parsed, and all options following it are passed to its parser. If a
+ subcommand is found, the subCommand attribute is set to its name and the
+ subOptions attribute is set to the Option instance that parses the
+ remaining options. If a subcommand is not given to parseOptions,
+ the subCommand attribute will be None. You can also mark one of
+ the subCommands to be the default.
+
+ | defaultSubCommand = 'holyquest'
+
+ In this case, the subCommand attribute will never be None, and
+ the subOptions attribute will always be set.
+
+ If you want to handle your own options, define a method named
+ C{opt_paramname} that takes C{(self, option)} as arguments. C{option}
+ will be whatever immediately follows the parameter on the
+ command line. Options fully supports the mapping interface, so you
+ can do things like C{'self["option"] = val'} in these methods.
+
+ Advanced functionality is covered in the howto documentation,
+ available at
+ U{http://twistedmatrix.com/projects/core/documentation/howto/options.html},
+ or doc/howto/options.html in your Twisted directory.
+ """
+
+ subCommand = None
+ defaultSubCommand = None
+ parent = None
+ def __init__(self):
+ super(Options, self).__init__()
+
+ self.opts = self
+ self.defaults = {}
+
+ # These are strings/lists we will pass to getopt
+ self.longOpt = []
+ self.shortOpt = ''
+ self.docs = {}
+ self.synonyms = {}
+ self._dispatch = {}
+
+
+ collectors = [
+ self._gather_flags,
+ self._gather_parameters,
+ self._gather_handlers,
+ ]
+
+ for c in collectors:
+ (longOpt, shortOpt, docs, settings, synonyms, dispatch) = c()
+ self.longOpt.extend(longOpt)
+ self.shortOpt = self.shortOpt + shortOpt
+ self.docs.update(docs)
+
+ self.opts.update(settings)
+ self.defaults.update(settings)
+
+ self.synonyms.update(synonyms)
+ self._dispatch.update(dispatch)
+
+ def __hash__(self):
+ """
+ Define a custom hash function so that Options instances can be used
+ as dictionary keys. This is an internal feature used to implement
+ the parser. Do not rely on it in application code.
+ """
+ return int(id(self) % sys.maxint)
+
+ def opt_help(self):
+ """
+ Display this help and exit.
+ """
+ print self.__str__()
+ sys.exit(0)
+
+ def opt_version(self):
+ from twisted import copyright
+ print "Twisted version:", copyright.version
+ sys.exit(0)
+
+ #opt_h = opt_help # this conflicted with existing 'host' options.
+
+ def parseOptions(self, options=None):
+ """
+ The guts of the command-line parser.
+ """
+
+ if options is None:
+ options = sys.argv[1:]
+ try:
+ opts, args = getopt.getopt(options,
+ self.shortOpt, self.longOpt)
+ except getopt.error, e:
+ raise UsageError(str(e))
+
+ for opt, arg in opts:
+ if opt[1] == '-':
+ opt = opt[2:]
+ else:
+ opt = opt[1:]
+
+ optMangled = opt
+ if optMangled not in self.synonyms:
+ optMangled = opt.replace("-", "_")
+ if optMangled not in self.synonyms:
+ raise UsageError("No such option '%s'" % (opt,))
+
+ optMangled = self.synonyms[optMangled]
+ if isinstance(self._dispatch[optMangled], CoerceParameter):
+ self._dispatch[optMangled].dispatch(optMangled, arg)
+ else:
+ self._dispatch[optMangled](optMangled, arg)
+
+ if (getattr(self, 'subCommands', None)
+ and (args or self.defaultSubCommand is not None)):
+ if not args:
+ args = [self.defaultSubCommand]
+ sub, rest = args[0], args[1:]
+ for (cmd, short, parser, doc) in self.subCommands:
+ if sub == cmd or sub == short:
+ self.subCommand = cmd
+ self.subOptions = parser()
+ self.subOptions.parent = self
+ self.subOptions.parseOptions(rest)
+ break
+ else:
+ raise UsageError("Unknown command: %s" % sub)
+ else:
+ try:
+ self.parseArgs(*args)
+ except TypeError:
+ raise UsageError("Wrong number of arguments.")
+
+ self.postOptions()
+
+ def postOptions(self):
+ """
+ I am called after the options are parsed.
+
+ Override this method in your subclass to do something after
+ the options have been parsed and assigned, like validate that
+ all options are sane.
+ """
+
+ def parseArgs(self):
+ """
+ I am called with any leftover arguments which were not options.
+
+ Override me to do something with the remaining arguments on
+ the command line, those which were not flags or options. e.g.
+ interpret them as a list of files to operate on.
+
+ Note that if there more arguments on the command line
+ than this method accepts, parseArgs will blow up with
+ a getopt.error. This means if you don't override me,
+ parseArgs will blow up if I am passed any arguments at
+ all!
+ """
+
+ def _generic_flag(self, flagName, value=None):
+ if value not in ('', None):
+ raise UsageError("Flag '%s' takes no argument."
+ " Not even \"%s\"." % (flagName, value))
+
+ self.opts[flagName] = 1
+
+ def _gather_flags(self):
+ """
+ Gather up boolean (flag) options.
+ """
+
+ longOpt, shortOpt = [], ''
+ docs, settings, synonyms, dispatch = {}, {}, {}, {}
+
+ flags = []
+ reflect.accumulateClassList(self.__class__, 'optFlags', flags)
+
+ for flag in flags:
+ long, short, doc = util.padTo(3, flag)
+ if not long:
+ raise ValueError("A flag cannot be without a name.")
+
+ docs[long] = doc
+ settings[long] = 0
+ if short:
+ shortOpt = shortOpt + short
+ synonyms[short] = long
+ longOpt.append(long)
+ synonyms[long] = long
+ dispatch[long] = self._generic_flag
+
+ return longOpt, shortOpt, docs, settings, synonyms, dispatch
+
+ def _gather_parameters(self):
+ """
+ Gather options which take a value.
+ """
+ longOpt, shortOpt = [], ''
+ docs, settings, synonyms, dispatch = {}, {}, {}, {}
+
+ parameters = []
+
+ reflect.accumulateClassList(self.__class__, 'optStrings',
+ parameters)
+ if parameters:
+ import warnings
+ warnings.warn("Options.optStrings is deprecated, "
+ "please use optParameters instead.", stacklevel=2)
+
+ reflect.accumulateClassList(self.__class__, 'optParameters',
+ parameters)
+
+ synonyms = {}
+
+ for parameter in parameters:
+ long, short, default, doc, paramType = util.padTo(5, parameter)
+ if not long:
+ raise ValueError("A parameter cannot be without a name.")
+
+ docs[long] = doc
+ settings[long] = default
+ if short:
+ shortOpt = shortOpt + short + ':'
+ synonyms[short] = long
+ longOpt.append(long + '=')
+ synonyms[long] = long
+ if paramType is not None:
+ dispatch[long] = CoerceParameter(self, paramType)
+ else:
+ dispatch[long] = CoerceParameter(self, str)
+
+ return longOpt, shortOpt, docs, settings, synonyms, dispatch
+
+
+ def _gather_handlers(self):
+ """
+ Gather up options with their own handler methods.
+ """
+
+ longOpt, shortOpt = [], ''
+ docs, settings, synonyms, dispatch = {}, {}, {}, {}
+
+ dct = {}
+ reflect.addMethodNamesToDict(self.__class__, dct, "opt_")
+
+ for name in dct.keys():
+ method = getattr(self, 'opt_'+name)
+
+ takesArg = not flagFunction(method, name)
+
+ prettyName = name.replace('_', '-')
+ doc = getattr(method, '__doc__', None)
+ if doc:
+ ## Only use the first line.
+ #docs[name] = doc.split('\n')[0]
+ docs[prettyName] = doc
+ else:
+ docs[prettyName] = self.docs.get(prettyName)
+
+ synonyms[prettyName] = prettyName
+
+ # A little slight-of-hand here makes dispatching much easier
+ # in parseOptions, as it makes all option-methods have the
+ # same signature.
+ if takesArg:
+ fn = lambda name, value, m=method: m(value)
+ else:
+ # XXX: This won't raise a TypeError if it's called
+ # with a value when it shouldn't be.
+ fn = lambda name, value=None, m=method: m()
+
+ dispatch[prettyName] = fn
+
+ if len(name) == 1:
+ shortOpt = shortOpt + name
+ if takesArg:
+ shortOpt = shortOpt + ':'
+ else:
+ if takesArg:
+ prettyName = prettyName + '='
+ longOpt.append(prettyName)
+
+ reverse_dct = {}
+ # Map synonyms
+ for name in dct.keys():
+ method = getattr(self, 'opt_' + name)
+ if method not in reverse_dct:
+ reverse_dct[method] = []
+ reverse_dct[method].append(name)
+
+ cmpLength = lambda a, b: cmp(len(a), len(b))
+
+ for method, names in reverse_dct.items():
+ if len(names) < 2:
+ continue
+ names_ = names[:]
+ names_.sort(cmpLength)
+ longest = names_.pop()
+ for name in names_:
+ synonyms[name] = longest
+
+ return longOpt, shortOpt, docs, settings, synonyms, dispatch
+
+
+ def __str__(self):
+ return self.getSynopsis() + '\n' + self.getUsage(width=None)
+
+ def getSynopsis(self):
+ """
+ Returns a string containing a description of these options and how to
+ pass them to the executed file.
+ """
+
+ default = "%s%s" % (path.basename(sys.argv[0]),
+ (self.longOpt and " [options]") or '')
+ if self.parent is None:
+ default = "Usage: %s%s" % (path.basename(sys.argv[0]),
+ (self.longOpt and " [options]") or '')
+ else:
+ default = '%s' % ((self.longOpt and "[options]") or '')
+ synopsis = getattr(self, "synopsis", default)
+
+ synopsis = synopsis.rstrip()
+
+ if self.parent is not None:
+ synopsis = ' '.join((self.parent.getSynopsis(),
+ self.parent.subCommand, synopsis))
+
+ return synopsis
+
+ def getUsage(self, width=None):
+ # If subOptions exists by now, then there was probably an error while
+ # parsing its options.
+ if hasattr(self, 'subOptions'):
+ return self.subOptions.getUsage(width=width)
+
+ if not width:
+ width = int(os.environ.get('COLUMNS', '80'))
+
+ if hasattr(self, 'subCommands'):
+ cmdDicts = []
+ for (cmd, short, parser, desc) in self.subCommands:
+ cmdDicts.append(
+ {'long': cmd,
+ 'short': short,
+ 'doc': desc,
+ 'optType': 'command',
+ 'default': None
+ })
+ chunks = docMakeChunks(cmdDicts, width)
+ commands = 'Commands:\n' + ''.join(chunks)
+ else:
+ commands = ''
+
+ longToShort = {}
+ for key, value in self.synonyms.items():
+ longname = value
+ if (key != longname) and (len(key) == 1):
+ longToShort[longname] = key
+ else:
+ if longname not in longToShort:
+ longToShort[longname] = None
+ else:
+ pass
+
+ optDicts = []
+ for opt in self.longOpt:
+ if opt[-1] == '=':
+ optType = 'parameter'
+ opt = opt[:-1]
+ else:
+ optType = 'flag'
+
+ optDicts.append(
+ {'long': opt,
+ 'short': longToShort[opt],
+ 'doc': self.docs[opt],
+ 'optType': optType,
+ 'default': self.defaults.get(opt, None),
+ 'dispatch': self._dispatch.get(opt, None)
+ })
+
+ if not (getattr(self, "longdesc", None) is None):
+ longdesc = self.longdesc
+ else:
+ import __main__
+ if getattr(__main__, '__doc__', None):
+ longdesc = __main__.__doc__
+ else:
+ longdesc = ''
+
+ if longdesc:
+ longdesc = ('\n' +
+ '\n'.join(text.wordWrap(longdesc, width)).strip()
+ + '\n')
+
+ if optDicts:
+ chunks = docMakeChunks(optDicts, width)
+ s = "Options:\n%s" % (''.join(chunks))
+ else:
+ s = "Options: None\n"
+
+ return s + longdesc + commands
+
+ #def __repr__(self):
+ # XXX: It'd be cool if we could return a succinct representation
+ # of which flags and options are set here.
+
+
+def docMakeChunks(optList, width=80):
+ """
+ Makes doc chunks for option declarations.
+
+ Takes a list of dictionaries, each of which may have one or more
+ of the keys 'long', 'short', 'doc', 'default', 'optType'.
+
+ Returns a list of strings.
+ The strings may be multiple lines,
+ all of them end with a newline.
+ """
+
+ # XXX: sanity check to make sure we have a sane combination of keys.
+
+ maxOptLen = 0
+ for opt in optList:
+ optLen = len(opt.get('long', ''))
+ if optLen:
+ if opt.get('optType', None) == "parameter":
+ # these take up an extra character
+ optLen = optLen + 1
+ maxOptLen = max(optLen, maxOptLen)
+
+ colWidth1 = maxOptLen + len(" -s, -- ")
+ colWidth2 = width - colWidth1
+ # XXX - impose some sane minimum limit.
+ # Then if we don't have enough room for the option and the doc
+ # to share one line, they can take turns on alternating lines.
+
+ colFiller1 = " " * colWidth1
+
+ optChunks = []
+ seen = {}
+ for opt in optList:
+ if opt.get('short', None) in seen or opt.get('long', None) in seen:
+ continue
+ for x in opt.get('short', None), opt.get('long', None):
+ if x is not None:
+ seen[x] = 1
+
+ optLines = []
+ comma = " "
+ if opt.get('short', None):
+ short = "-%c" % (opt['short'],)
+ else:
+ short = ''
+
+ if opt.get('long', None):
+ long = opt['long']
+ if opt.get("optType", None) == "parameter":
+ long = long + '='
+
+ long = "%-*s" % (maxOptLen, long)
+ if short:
+ comma = ","
+ else:
+ long = " " * (maxOptLen + len('--'))
+
+ if opt.get('optType', None) == 'command':
+ column1 = ' %s ' % long
+ else:
+ column1 = " %2s%c --%s " % (short, comma, long)
+
+ if opt.get('doc', ''):
+ doc = opt['doc'].strip()
+ else:
+ doc = ''
+
+ if (opt.get("optType", None) == "parameter") \
+ and not (opt.get('default', None) is None):
+ doc = "%s [default: %s]" % (doc, opt['default'])
+
+ if (opt.get("optType", None) == "parameter") \
+ and opt.get('dispatch', None) is not None:
+ d = opt['dispatch']
+ if isinstance(d, CoerceParameter) and d.doc:
+ doc = "%s. %s" % (doc, d.doc)
+
+ if doc:
+ column2_l = text.wordWrap(doc, colWidth2)
+ else:
+ column2_l = ['']
+
+ optLines.append("%s%s\n" % (column1, column2_l.pop(0)))
+
+ for line in column2_l:
+ optLines.append("%s%s\n" % (colFiller1, line))
+
+ optChunks.append(''.join(optLines))
+
+ return optChunks
+
+
+def flagFunction(method, name=None):
+ reqArgs = method.im_func.func_code.co_argcount
+ if reqArgs > 2:
+ raise UsageError('Invalid Option function for %s' %
+ (name or method.func_name))
+ if reqArgs == 2:
+ # argName = method.im_func.func_code.co_varnames[1]
+ return 0
+ return 1
+
+
+def portCoerce(value):
+ """
+ Coerce a string value to an int port number, and checks the validity.
+ """
+ value = int(value)
+ if value < 0 or value > 65535:
+ raise ValueError("Port number not in range: %s" % (value,))
+ return value
+portCoerce.coerceDoc = "Must be an int between 0 and 65535."
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/util.py b/vendor/Twisted-10.0.0/twisted/python/util.py
new file mode 100644
index 0000000000..a937ad6294
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/util.py
@@ -0,0 +1,968 @@
+# -*- test-case-name: twisted.python.test.test_util -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import os, sys, hmac, errno, new, inspect, warnings
+try:
+ import pwd, grp
+except ImportError:
+ pwd = grp = None
+try:
+ from os import setgroups, getgroups
+except ImportError:
+ setgroups = getgroups = None
+from UserDict import UserDict
+
+
+class InsensitiveDict:
+ """Dictionary, that has case-insensitive keys.
+
+ Normally keys are retained in their original form when queried with
+ .keys() or .items(). If initialized with preserveCase=0, keys are both
+ looked up in lowercase and returned in lowercase by .keys() and .items().
+ """
+ """
+ Modified recipe at
+ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66315 originally
+ contributed by Sami Hangaslammi.
+ """
+
+ def __init__(self, dict=None, preserve=1):
+ """Create an empty dictionary, or update from 'dict'."""
+ self.data = {}
+ self.preserve=preserve
+ if dict:
+ self.update(dict)
+
+ def __delitem__(self, key):
+ k=self._lowerOrReturn(key)
+ del self.data[k]
+
+ def _lowerOrReturn(self, key):
+ if isinstance(key, str) or isinstance(key, unicode):
+ return key.lower()
+ else:
+ return key
+
+ def __getitem__(self, key):
+ """Retrieve the value associated with 'key' (in any case)."""
+ k = self._lowerOrReturn(key)
+ return self.data[k][1]
+
+ def __setitem__(self, key, value):
+ """Associate 'value' with 'key'. If 'key' already exists, but
+ in different case, it will be replaced."""
+ k = self._lowerOrReturn(key)
+ self.data[k] = (key, value)
+
+ def has_key(self, key):
+ """Case insensitive test whether 'key' exists."""
+ k = self._lowerOrReturn(key)
+ return self.data.has_key(k)
+ __contains__=has_key
+
+ def _doPreserve(self, key):
+ if not self.preserve and (isinstance(key, str)
+ or isinstance(key, unicode)):
+ return key.lower()
+ else:
+ return key
+
+ def keys(self):
+ """List of keys in their original case."""
+ return list(self.iterkeys())
+
+ def values(self):
+ """List of values."""
+ return list(self.itervalues())
+
+ def items(self):
+ """List of (key,value) pairs."""
+ return list(self.iteritems())
+
+ def get(self, key, default=None):
+ """Retrieve value associated with 'key' or return default value
+ if 'key' doesn't exist."""
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def setdefault(self, key, default):
+ """If 'key' doesn't exists, associate it with the 'default' value.
+ Return value associated with 'key'."""
+ if not self.has_key(key):
+ self[key] = default
+ return self[key]
+
+ def update(self, dict):
+ """Copy (key,value) pairs from 'dict'."""
+ for k,v in dict.items():
+ self[k] = v
+
+ def __repr__(self):
+ """String representation of the dictionary."""
+ items = ", ".join([("%r: %r" % (k,v)) for k,v in self.items()])
+ return "InsensitiveDict({%s})" % items
+
+ def iterkeys(self):
+ for v in self.data.itervalues():
+ yield self._doPreserve(v[0])
+
+ def itervalues(self):
+ for v in self.data.itervalues():
+ yield v[1]
+
+ def iteritems(self):
+ for (k, v) in self.data.itervalues():
+ yield self._doPreserve(k), v
+
+ def popitem(self):
+ i=self.items()[0]
+ del self[i[0]]
+ return i
+
+ def clear(self):
+ for k in self.keys():
+ del self[k]
+
+ def copy(self):
+ return InsensitiveDict(self, self.preserve)
+
+ def __len__(self):
+ return len(self.data)
+
+ def __eq__(self, other):
+ for k,v in self.items():
+ if not (k in other) or not (other[k]==v):
+ return 0
+ return len(self)==len(other)
+
+class OrderedDict(UserDict):
+ """A UserDict that preserves insert order whenever possible."""
+ def __init__(self, dict=None, **kwargs):
+ self._order = []
+ self.data = {}
+ if dict is not None:
+ if hasattr(dict,'keys'):
+ self.update(dict)
+ else:
+ for k,v in dict: # sequence
+ self[k] = v
+ if len(kwargs):
+ self.update(kwargs)
+ def __repr__(self):
+ return '{'+', '.join([('%r: %r' % item) for item in self.items()])+'}'
+
+ def __setitem__(self, key, value):
+ if not self.has_key(key):
+ self._order.append(key)
+ UserDict.__setitem__(self, key, value)
+
+ def copy(self):
+ return self.__class__(self)
+
+ def __delitem__(self, key):
+ UserDict.__delitem__(self, key)
+ self._order.remove(key)
+
+ def iteritems(self):
+ for item in self._order:
+ yield (item, self[item])
+
+ def items(self):
+ return list(self.iteritems())
+
+ def itervalues(self):
+ for item in self._order:
+ yield self[item]
+
+ def values(self):
+ return list(self.itervalues())
+
+ def iterkeys(self):
+ return iter(self._order)
+
+ def keys(self):
+ return list(self._order)
+
+ def popitem(self):
+ key = self._order[-1]
+ value = self[key]
+ del self[key]
+ return (key, value)
+
+ def setdefault(self, item, default):
+ if self.has_key(item):
+ return self[item]
+ self[item] = default
+ return default
+
+ def update(self, d):
+ for k, v in d.items():
+ self[k] = v
+
+def uniquify(lst):
+ """Make the elements of a list unique by inserting them into a dictionary.
+ This must not change the order of the input lst.
+ """
+ dct = {}
+ result = []
+ for k in lst:
+ if not dct.has_key(k): result.append(k)
+ dct[k] = 1
+ return result
+
+def padTo(n, seq, default=None):
+ """Pads a sequence out to n elements,
+
+ filling in with a default value if it is not long enough.
+
+ If the input sequence is longer than n, raises ValueError.
+
+ Details, details:
+ This returns a new list; it does not extend the original sequence.
+ The new list contains the values of the original sequence, not copies.
+ """
+
+ if len(seq) > n:
+ raise ValueError, "%d elements is more than %d." % (len(seq), n)
+
+ blank = [default] * n
+
+ blank[:len(seq)] = list(seq)
+
+ return blank
+
+def getPluginDirs():
+ import twisted
+ systemPlugins = os.path.join(os.path.dirname(os.path.dirname(
+ os.path.abspath(twisted.__file__))), 'plugins')
+ userPlugins = os.path.expanduser("~/TwistedPlugins")
+ confPlugins = os.path.expanduser("~/.twisted")
+ allPlugins = filter(os.path.isdir, [systemPlugins, userPlugins, confPlugins])
+ return allPlugins
+
+def addPluginDir():
+ sys.path.extend(getPluginDirs())
+
+def sibpath(path, sibling):
+ """Return the path to a sibling of a file in the filesystem.
+
+ This is useful in conjunction with the special __file__ attribute
+ that Python provides for modules, so modules can load associated
+ resource files.
+ """
+ return os.path.join(os.path.dirname(os.path.abspath(path)), sibling)
+
+
+def _getpass(prompt):
+ """Helper to turn IOErrors into KeyboardInterrupts"""
+ import getpass
+ try:
+ return getpass.getpass(prompt)
+ except IOError, e:
+ if e.errno == errno.EINTR:
+ raise KeyboardInterrupt
+ raise
+ except EOFError:
+ raise KeyboardInterrupt
+
+def getPassword(prompt = 'Password: ', confirm = 0, forceTTY = 0,
+ confirmPrompt = 'Confirm password: ',
+ mismatchMessage = "Passwords don't match."):
+ """Obtain a password by prompting or from stdin.
+
+ If stdin is a terminal, prompt for a new password, and confirm (if
+ C{confirm} is true) by asking again to make sure the user typed the same
+ thing, as keystrokes will not be echoed.
+
+ If stdin is not a terminal, and C{forceTTY} is not true, read in a line
+ and use it as the password, less the trailing newline, if any. If
+ C{forceTTY} is true, attempt to open a tty and prompt for the password
+ using it. Raise a RuntimeError if this is not possible.
+
+ @returns: C{str}
+ """
+ isaTTY = hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()
+
+ old = None
+ try:
+ if not isaTTY:
+ if forceTTY:
+ try:
+ old = sys.stdin, sys.stdout
+ sys.stdin = sys.stdout = open('/dev/tty', 'r+')
+ except:
+ raise RuntimeError("Cannot obtain a TTY")
+ else:
+ password = sys.stdin.readline()
+ if password[-1] == '\n':
+ password = password[:-1]
+ return password
+
+ while 1:
+ try1 = _getpass(prompt)
+ if not confirm:
+ return try1
+ try2 = _getpass(confirmPrompt)
+ if try1 == try2:
+ return try1
+ else:
+ sys.stderr.write(mismatchMessage + "\n")
+ finally:
+ if old:
+ sys.stdin.close()
+ sys.stdin, sys.stdout = old
+
+
+def dict(*a, **k):
+ import __builtin__
+ warnings.warn('twisted.python.util.dict is deprecated. Use __builtin__.dict instead')
+ return __builtin__.dict(*a, **k)
+
+def println(*a):
+ sys.stdout.write(' '.join(map(str, a))+'\n')
+
+# XXX
+# This does not belong here
+# But where does it belong?
+
+def str_xor(s, b):
+ return ''.join([chr(ord(c) ^ b) for c in s])
+
+def keyed_md5(secret, challenge):
+ """
+ Create the keyed MD5 string for the given secret and challenge.
+ """
+ warnings.warn(
+ "keyed_md5() is deprecated. Use the stdlib module hmac instead.",
+ DeprecationWarning, stacklevel=2
+ )
+ return hmac.HMAC(secret, challenge).hexdigest()
+
+def makeStatBar(width, maxPosition, doneChar = '=', undoneChar = '-', currentChar = '>'):
+ """Creates a function that will return a string representing a progress bar.
+ """
+ aValue = width / float(maxPosition)
+ def statBar(position, force = 0, last = ['']):
+ assert len(last) == 1, "Don't mess with the last parameter."
+ done = int(aValue * position)
+ toDo = width - done - 2
+ result = "[%s%s%s]" % (doneChar * done, currentChar, undoneChar * toDo)
+ if force:
+ last[0] = result
+ return result
+ if result == last[0]:
+ return ''
+ last[0] = result
+ return result
+
+ statBar.__doc__ = """statBar(position, force = 0) -> '[%s%s%s]'-style progress bar
+
+ returned string is %d characters long, and the range goes from 0..%d.
+ The 'position' argument is where the '%s' will be drawn. If force is false,
+ '' will be returned instead if the resulting progress bar is identical to the
+ previously returned progress bar.
+""" % (doneChar * 3, currentChar, undoneChar * 3, width, maxPosition, currentChar)
+ return statBar
+
+def spewer(frame, s, ignored):
+ """A trace function for sys.settrace that prints every function or method call."""
+ from twisted.python import reflect
+ if frame.f_locals.has_key('self'):
+ se = frame.f_locals['self']
+ if hasattr(se, '__class__'):
+ k = reflect.qual(se.__class__)
+ else:
+ k = reflect.qual(type(se))
+ print 'method %s of %s at %s' % (
+ frame.f_code.co_name, k, id(se)
+ )
+ else:
+ print 'function %s in %s, line %s' % (
+ frame.f_code.co_name,
+ frame.f_code.co_filename,
+ frame.f_lineno)
+
+def searchupwards(start, files=[], dirs=[]):
+ """Walk upwards from start, looking for a directory containing
+ all files and directories given as arguments::
+ >>> searchupwards('.', ['foo.txt'], ['bar', 'bam'])
+
+ If not found, return None
+ """
+ start=os.path.abspath(start)
+ parents=start.split(os.sep)
+ exists=os.path.exists; join=os.sep.join; isdir=os.path.isdir
+ while len(parents):
+ candidate=join(parents)+os.sep
+ allpresent=1
+ for f in files:
+ if not exists("%s%s" % (candidate, f)):
+ allpresent=0
+ break
+ if allpresent:
+ for d in dirs:
+ if not isdir("%s%s" % (candidate, d)):
+ allpresent=0
+ break
+ if allpresent: return candidate
+ parents.pop(-1)
+ return None
+
+
+class LineLog:
+ """
+ A limited-size line-based log, useful for logging line-based
+ protocols such as SMTP.
+
+ When the log fills up, old entries drop off the end.
+ """
+ def __init__(self, size=10):
+ """
+ Create a new log, with size lines of storage (default 10).
+ A log size of 0 (or less) means an infinite log.
+ """
+ if size < 0:
+ size = 0
+ self.log = [None]*size
+ self.size = size
+
+ def append(self,line):
+ if self.size:
+ self.log[:-1] = self.log[1:]
+ self.log[-1] = line
+ else:
+ self.log.append(line)
+
+ def str(self):
+ return '\n'.join(filter(None,self.log))
+
+ def __getitem__(self, item):
+ return filter(None,self.log)[item]
+
+ def clear(self):
+ """Empty the log"""
+ self.log = [None]*self.size
+
+def raises(exception, f, *args, **kwargs):
+ """Determine whether the given call raises the given exception"""
+ try:
+ f(*args, **kwargs)
+ except exception:
+ return 1
+ return 0
+
+class IntervalDifferential:
+ """
+ Given a list of intervals, generate the amount of time to sleep between
+ \"instants\".
+
+ For example, given 7, 11 and 13, the three (infinite) sequences::
+
+ 7 14 21 28 35 ...
+ 11 22 33 44 ...
+ 13 26 39 52 ...
+
+ will be generated, merged, and used to produce::
+
+ (7, 0) (4, 1) (2, 2) (1, 0) (7, 0) (1, 1) (4, 2) (2, 0) (5, 1) (2, 0)
+
+ New intervals may be added or removed as iteration proceeds using the
+ proper methods.
+ """
+
+ def __init__(self, intervals, default=60):
+ """
+ @type intervals: C{list} of C{int}, C{long}, or C{float} param
+ @param intervals: The intervals between instants.
+
+ @type default: C{int}, C{long}, or C{float}
+ @param default: The duration to generate if the intervals list
+ becomes empty.
+ """
+ self.intervals = intervals[:]
+ self.default = default
+
+ def __iter__(self):
+ return _IntervalDifferentialIterator(self.intervals, self.default)
+
+class _IntervalDifferentialIterator:
+ def __init__(self, i, d):
+
+ self.intervals = [[e, e, n] for (e, n) in zip(i, range(len(i)))]
+ self.default = d
+ self.last = 0
+
+ def next(self):
+ if not self.intervals:
+ return (self.default, None)
+ last, index = self.intervals[0][0], self.intervals[0][2]
+ self.intervals[0][0] += self.intervals[0][1]
+ self.intervals.sort()
+ result = last - self.last
+ self.last = last
+ return result, index
+
+ def addInterval(self, i):
+ if self.intervals:
+ delay = self.intervals[0][0] - self.intervals[0][1]
+ self.intervals.append([delay + i, i, len(self.intervals)])
+ self.intervals.sort()
+ else:
+ self.intervals.append([i, i, 0])
+
+ def removeInterval(self, interval):
+ for i in range(len(self.intervals)):
+ if self.intervals[i][1] == interval:
+ index = self.intervals[i][2]
+ del self.intervals[i]
+ for i in self.intervals:
+ if i[2] > index:
+ i[2] -= 1
+ return
+ raise ValueError, "Specified interval not in IntervalDifferential"
+
+
+class FancyStrMixin:
+ """
+ Set showAttributes to a sequence of strings naming attributes, OR
+ sequences of (attributeName, displayName, formatCharacter)
+ """
+ showAttributes = ()
+ def __str__(self):
+ r = ['<', hasattr(self, 'fancybasename') and self.fancybasename or self.__class__.__name__]
+ for attr in self.showAttributes:
+ if isinstance(attr, str):
+ r.append(' %s=%r' % (attr, getattr(self, attr)))
+ else:
+ r.append((' %s=' + attr[2]) % (attr[1], getattr(self, attr[0])))
+ r.append('>')
+ return ''.join(r)
+ __repr__ = __str__
+
+
+
+class FancyEqMixin:
+ compareAttributes = ()
+ def __eq__(self, other):
+ if not self.compareAttributes:
+ return self is other
+ if isinstance(self, other.__class__):
+ return (
+ [getattr(self, name) for name in self.compareAttributes] ==
+ [getattr(other, name) for name in self.compareAttributes])
+ return NotImplemented
+
+
+ def __ne__(self, other):
+ result = self.__eq__(other)
+ if result is NotImplemented:
+ return result
+ return not result
+
+
+
+def dsu(list, key):
+ L2 = [(key(e), i, e) for (i, e) in zip(range(len(list)), list)]
+ L2.sort()
+ return [e[2] for e in L2]
+
+try:
+ from twisted.python._initgroups import initgroups as _c_initgroups
+except ImportError:
+ _c_initgroups = None
+
+
+
+if pwd is None or grp is None or setgroups is None or getgroups is None:
+ def initgroups(uid, primaryGid):
+ """
+ Do nothing.
+
+ Underlying platform support require to manipulate groups is missing.
+ """
+else:
+ # Fallback to the inefficient Python version
+ def _setgroups_until_success(l):
+ while(1):
+ # NASTY NASTY HACK (but glibc does it so it must be okay):
+ # In case sysconfig didn't give the right answer, find the limit
+ # on max groups by just looping, trying to set fewer and fewer
+ # groups each time until it succeeds.
+ try:
+ setgroups(l)
+ except ValueError:
+ # This exception comes from python itself restricting
+ # number of groups allowed.
+ if len(l) > 1:
+ del l[-1]
+ else:
+ raise
+ except OSError, e:
+ if e.errno == errno.EINVAL and len(l) > 1:
+ # This comes from the OS saying too many groups
+ del l[-1]
+ else:
+ raise
+ else:
+ # Success, yay!
+ return
+
+ def initgroups(uid, primaryGid):
+ """
+ Initializes the group access list.
+
+ If the C extension is present, we're calling it, which in turn calls
+ initgroups(3).
+
+ If not, this is done by reading the group database /etc/group and using
+ all groups of which C{uid} is a member. The additional group
+ C{primaryGid} is also added to the list.
+
+ If the given user is a member of more than C{NGROUPS}, arbitrary
+ groups will be silently discarded to bring the number below that
+ limit.
+
+ @type uid: C{int}
+ @param uid: The UID for which to look up group information.
+
+ @type primaryGid: C{int} or C{NoneType}
+ @param primaryGid: If provided, an additional GID to include when
+ setting the groups.
+ """
+ if _c_initgroups is not None:
+ return _c_initgroups(pwd.getpwuid(uid)[0], primaryGid)
+ try:
+ # Try to get the maximum number of groups
+ max_groups = os.sysconf("SC_NGROUPS_MAX")
+ except:
+ # No predefined limit
+ max_groups = 0
+
+ username = pwd.getpwuid(uid)[0]
+ l = []
+ if primaryGid is not None:
+ l.append(primaryGid)
+ for groupname, password, gid, userlist in grp.getgrall():
+ if username in userlist:
+ l.append(gid)
+ if len(l) == max_groups:
+ break # No more groups, ignore any more
+ try:
+ _setgroups_until_success(l)
+ except OSError, e:
+ # We might be able to remove this code now that we
+ # don't try to setgid/setuid even when not asked to.
+ if e.errno == errno.EPERM:
+ for g in getgroups():
+ if g not in l:
+ raise
+ else:
+ raise
+
+
+
+def switchUID(uid, gid, euid=False):
+ if euid:
+ setuid = os.seteuid
+ setgid = os.setegid
+ else:
+ setuid = os.setuid
+ setgid = os.setgid
+ if gid is not None:
+ setgid(gid)
+ if uid is not None:
+ initgroups(uid, gid)
+ setuid(uid)
+
+
+class SubclassableCStringIO(object):
+ """A wrapper around cStringIO to allow for subclassing"""
+ __csio = None
+
+ def __init__(self, *a, **kw):
+ from cStringIO import StringIO
+ self.__csio = StringIO(*a, **kw)
+
+ def __iter__(self):
+ return self.__csio.__iter__()
+
+ def next(self):
+ return self.__csio.next()
+
+ def close(self):
+ return self.__csio.close()
+
+ def isatty(self):
+ return self.__csio.isatty()
+
+ def seek(self, pos, mode=0):
+ return self.__csio.seek(pos, mode)
+
+ def tell(self):
+ return self.__csio.tell()
+
+ def read(self, n=-1):
+ return self.__csio.read(n)
+
+ def readline(self, length=None):
+ return self.__csio.readline(length)
+
+ def readlines(self, sizehint=0):
+ return self.__csio.readlines(sizehint)
+
+ def truncate(self, size=None):
+ return self.__csio.truncate(size)
+
+ def write(self, s):
+ return self.__csio.write(s)
+
+ def writelines(self, list):
+ return self.__csio.writelines(list)
+
+ def flush(self):
+ return self.__csio.flush()
+
+ def getvalue(self):
+ return self.__csio.getvalue()
+
+def moduleMovedForSplit(origModuleName, newModuleName, moduleDesc,
+ projectName, projectURL, globDict):
+ """
+ No-op function; only present for backwards compatibility. There is no
+ reason to call this function.
+ """
+ warnings.warn(
+ "moduleMovedForSplit is deprecated since Twisted 9.0.",
+ DeprecationWarning, stacklevel=2)
+
+
+def untilConcludes(f, *a, **kw):
+ while True:
+ try:
+ return f(*a, **kw)
+ except (IOError, OSError), e:
+ if e.args[0] == errno.EINTR:
+ continue
+ raise
+
+_idFunction = id
+
+def setIDFunction(idFunction):
+ """
+ Change the function used by L{unsignedID} to determine the integer id value
+ of an object. This is largely useful for testing to give L{unsignedID}
+ deterministic, easily-controlled behavior.
+
+ @param idFunction: A function with the signature of L{id}.
+ @return: The previous function being used by L{unsignedID}.
+ """
+ global _idFunction
+ oldIDFunction = _idFunction
+ _idFunction = idFunction
+ return oldIDFunction
+
+
+# A value about twice as large as any Python int, to which negative values
+# from id() will be added, moving them into a range which should begin just
+# above where positive values from id() leave off.
+_HUGEINT = (sys.maxint + 1L) * 2L
+def unsignedID(obj):
+ """
+ Return the id of an object as an unsigned number so that its hex
+ representation makes sense.
+
+ This is mostly necessary in Python 2.4 which implements L{id} to sometimes
+ return a negative value. Python 2.3 shares this behavior, but also
+ implements hex and the %x format specifier to represent negative values as
+ though they were positive ones, obscuring the behavior of L{id}. Python
+ 2.5's implementation of L{id} always returns positive values.
+ """
+ rval = _idFunction(obj)
+ if rval < 0:
+ rval += _HUGEINT
+ return rval
+
+
+def mergeFunctionMetadata(f, g):
+ """
+ Overwrite C{g}'s name and docstring with values from C{f}. Update
+ C{g}'s instance dictionary with C{f}'s.
+
+ To use this function safely you must use the return value. In Python 2.3,
+ L{mergeFunctionMetadata} will create a new function. In later versions of
+ Python, C{g} will be mutated and returned.
+
+ @return: A function that has C{g}'s behavior and metadata merged from
+ C{f}.
+ """
+ try:
+ g.__name__ = f.__name__
+ except TypeError:
+ try:
+ merged = new.function(
+ g.func_code, g.func_globals,
+ f.__name__, inspect.getargspec(g)[-1],
+ g.func_closure)
+ except TypeError:
+ pass
+ else:
+ merged = g
+ try:
+ merged.__doc__ = f.__doc__
+ except (TypeError, AttributeError):
+ pass
+ try:
+ merged.__dict__.update(g.__dict__)
+ merged.__dict__.update(f.__dict__)
+ except (TypeError, AttributeError):
+ pass
+ merged.__module__ = f.__module__
+ return merged
+
+
+def nameToLabel(mname):
+ """
+ Convert a string like a variable name into a slightly more human-friendly
+ string with spaces and capitalized letters.
+
+ @type mname: C{str}
+ @param mname: The name to convert to a label. This must be a string
+ which could be used as a Python identifier. Strings which do not take
+ this form will result in unpredictable behavior.
+
+ @rtype: C{str}
+ """
+ labelList = []
+ word = ''
+ lastWasUpper = False
+ for letter in mname:
+ if letter.isupper() == lastWasUpper:
+ # Continuing a word.
+ word += letter
+ else:
+ # breaking a word OR beginning a word
+ if lastWasUpper:
+ # could be either
+ if len(word) == 1:
+ # keep going
+ word += letter
+ else:
+ # acronym
+ # we're processing the lowercase letter after the acronym-then-capital
+ lastWord = word[:-1]
+ firstLetter = word[-1]
+ labelList.append(lastWord)
+ word = firstLetter + letter
+ else:
+ # definitely breaking: lower to upper
+ labelList.append(word)
+ word = letter
+ lastWasUpper = letter.isupper()
+ if labelList:
+ labelList[0] = labelList[0].capitalize()
+ else:
+ return mname.capitalize()
+ labelList.append(word)
+ return ' '.join(labelList)
+
+
+
+def uidFromString(uidString):
+ """
+ Convert a user identifier, as a string, into an integer UID.
+
+ @type uid: C{str}
+ @param uid: A string giving the base-ten representation of a UID or the
+ name of a user which can be converted to a UID via L{pwd.getpwnam}.
+
+ @rtype: C{int}
+ @return: The integer UID corresponding to the given string.
+
+ @raise ValueError: If the user name is supplied and L{pwd} is not
+ available.
+ """
+ try:
+ return int(uidString)
+ except ValueError:
+ if pwd is None:
+ raise
+ return pwd.getpwnam(uidString)[2]
+
+
+
+def gidFromString(gidString):
+ """
+ Convert a group identifier, as a string, into an integer GID.
+
+ @type uid: C{str}
+ @param uid: A string giving the base-ten representation of a GID or the
+ name of a group which can be converted to a GID via L{grp.getgrnam}.
+
+ @rtype: C{int}
+ @return: The integer GID corresponding to the given string.
+
+ @raise ValueError: If the group name is supplied and L{grp} is not
+ available.
+ """
+ try:
+ return int(gidString)
+ except ValueError:
+ if grp is None:
+ raise
+ return grp.getgrnam(gidString)[2]
+
+
+
+def runAsEffectiveUser(euid, egid, function, *args, **kwargs):
+ """
+ Run the given function wrapped with seteuid/setegid calls.
+
+ This will try to minimize the number of seteuid/setegid calls, comparing
+ current and wanted permissions
+
+ @param euid: effective UID used to call the function.
+ @type euid: C{int}
+
+ @type egid: effective GID used to call the function.
+ @param egid: C{int}
+
+ @param function: the function run with the specific permission.
+ @type function: any callable
+
+ @param *args: arguments passed to C{function}
+ @param **kwargs: keyword arguments passed to C{function}
+ """
+ uid, gid = os.geteuid(), os.getegid()
+ if uid == euid and gid == egid:
+ return function(*args, **kwargs)
+ else:
+ if uid != 0 and (uid != euid or gid != egid):
+ os.seteuid(0)
+ if gid != egid:
+ os.setegid(egid)
+ if euid != 0 and (euid != uid or gid != egid):
+ os.seteuid(euid)
+ try:
+ return function(*args, **kwargs)
+ finally:
+ if euid != 0 and (uid != euid or gid != egid):
+ os.seteuid(0)
+ if gid != egid:
+ os.setegid(gid)
+ if uid != 0 and (uid != euid or gid != egid):
+ os.seteuid(uid)
+
+
+
+__all__ = [
+ "uniquify", "padTo", "getPluginDirs", "addPluginDir", "sibpath",
+ "getPassword", "dict", "println", "keyed_md5", "makeStatBar",
+ "OrderedDict", "InsensitiveDict", "spewer", "searchupwards", "LineLog",
+ "raises", "IntervalDifferential", "FancyStrMixin", "FancyEqMixin",
+ "dsu", "switchUID", "SubclassableCStringIO", "moduleMovedForSplit",
+ "unsignedID", "mergeFunctionMetadata", "nameToLabel", "uidFromString",
+ "gidFromString", "runAsEffectiveUser", "moduleMovedForSplit",
+]
diff --git a/vendor/Twisted-10.0.0/twisted/python/versions.py b/vendor/Twisted-10.0.0/twisted/python/versions.py
new file mode 100644
index 0000000000..4ebac5d044
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/versions.py
@@ -0,0 +1,249 @@
+# -*- test-case-name: twisted.python.test.test_versions -*-
+# Copyright (c) 2006-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Versions for Python packages.
+
+See L{Version}.
+"""
+
+import sys, os
+
+
+class _inf(object):
+ """
+ An object that is bigger than all other objects.
+ """
+ def __cmp__(self, other):
+ """
+ @param other: Another object.
+ @type other: any
+
+ @return: 0 if other is inf, 1 otherwise.
+ @rtype: C{int}
+ """
+ if other is _inf:
+ return 0
+ return 1
+
+_inf = _inf()
+
+
+class IncomparableVersions(TypeError):
+ """
+ Two versions could not be compared.
+ """
+
+class Version(object):
+ """
+ An object that represents a three-part version number.
+
+ If running from an svn checkout, include the revision number in
+ the version string.
+ """
+ def __init__(self, package, major, minor, micro, prerelease=None):
+ """
+ @param package: Name of the package that this is a version of.
+ @type package: C{str}
+ @param major: The major version number.
+ @type major: C{int}
+ @param minor: The minor version number.
+ @type minor: C{int}
+ @param micro: The micro version number.
+ @type micro: C{int}
+ @param prerelease: The prerelease number.
+ @type prerelease: C{int}
+ """
+ self.package = package
+ self.major = major
+ self.minor = minor
+ self.micro = micro
+ self.prerelease = prerelease
+
+
+ def short(self):
+ """
+ Return a string in canonical short version format,
+ <major>.<minor>.<micro>[+rSVNVer].
+ """
+ s = self.base()
+ svnver = self._getSVNVersion()
+ if svnver:
+ s += '+r' + str(svnver)
+ return s
+
+
+ def base(self):
+ """
+ Like L{short}, but without the +rSVNVer.
+ """
+ if self.prerelease is None:
+ pre = ""
+ else:
+ pre = "pre%s" % (self.prerelease,)
+ return '%d.%d.%d%s' % (self.major,
+ self.minor,
+ self.micro,
+ pre)
+
+
+ def __repr__(self):
+ svnver = self._formatSVNVersion()
+ if svnver:
+ svnver = ' #' + svnver
+ if self.prerelease is None:
+ prerelease = ""
+ else:
+ prerelease = ", prerelease=%r" % (self.prerelease,)
+ return '%s(%r, %d, %d, %d%s)%s' % (
+ self.__class__.__name__,
+ self.package,
+ self.major,
+ self.minor,
+ self.micro,
+ prerelease,
+ svnver)
+
+
+ def __str__(self):
+ return '[%s, version %s]' % (
+ self.package,
+ self.short())
+
+
+ def __cmp__(self, other):
+ """
+ Compare two versions, considering major versions, minor versions, micro
+ versions, then prereleases.
+
+ A version with a prerelease is always less than a version without a
+ prerelease. If both versions have prereleases, they will be included in
+ the comparison.
+
+ @param other: Another version.
+ @type other: L{Version}
+
+ @return: NotImplemented when the other object is not a Version, or one
+ of -1, 0, or 1.
+
+ @raise IncomparableVersions: when the package names of the versions
+ differ.
+ """
+ if not isinstance(other, self.__class__):
+ return NotImplemented
+ if self.package != other.package:
+ raise IncomparableVersions("%r != %r"
+ % (self.package, other.package))
+
+ if self.prerelease is None:
+ prerelease = _inf
+ else:
+ prerelease = self.prerelease
+
+ if other.prerelease is None:
+ otherpre = _inf
+ else:
+ otherpre = other.prerelease
+
+ x = cmp((self.major,
+ self.minor,
+ self.micro,
+ prerelease),
+ (other.major,
+ other.minor,
+ other.micro,
+ otherpre))
+ return x
+
+
+ def _parseSVNEntries_4(self, entriesFile):
+ """
+ Given a readable file object which represents a .svn/entries file in
+ format version 4, return the revision as a string. We do this by
+ reading first XML element in the document that has a 'revision'
+ attribute.
+ """
+ from xml.dom.minidom import parse
+ doc = parse(entriesFile).documentElement
+ for node in doc.childNodes:
+ if hasattr(node, 'getAttribute'):
+ rev = node.getAttribute('revision')
+ if rev is not None:
+ return rev.encode('ascii')
+
+
+ def _parseSVNEntries_8(self, entriesFile):
+ """
+ Given a readable file object which represents a .svn/entries file in
+ format version 8, return the revision as a string.
+ """
+ entriesFile.readline()
+ entriesFile.readline()
+ entriesFile.readline()
+ return entriesFile.readline().strip()
+
+
+ # Add handlers for version 9 and 10 formats, which are the same as
+ # version 8 as far as revision information is concerned.
+ _parseSVNEntries_9 = _parseSVNEntries_8
+ _parseSVNEntriesTenPlus = _parseSVNEntries_8
+
+
+ def _getSVNVersion(self):
+ """
+ Figure out the SVN revision number based on the existance of
+ <package>/.svn/entries, and its contents. This requires discovering the
+ format version from the 'format' file and parsing the entries file
+ accordingly.
+
+ @return: None or string containing SVN Revision number.
+ """
+ mod = sys.modules.get(self.package)
+ if mod:
+ svn = os.path.join(os.path.dirname(mod.__file__), '.svn')
+ if not os.path.exists(svn):
+ # It's not an svn working copy
+ return None
+
+ formatFile = os.path.join(svn, 'format')
+ if os.path.exists(formatFile):
+ # It looks like a less-than-version-10 working copy.
+ format = file(formatFile).read().strip()
+ parser = getattr(self, '_parseSVNEntries_' + format, None)
+ else:
+ # It looks like a version-10-or-greater working copy, which
+ # has version information in the entries file.
+ parser = self._parseSVNEntriesTenPlus
+
+ if parser is None:
+ return 'Unknown'
+
+ entriesFile = os.path.join(svn, 'entries')
+ entries = file(entriesFile)
+ try:
+ try:
+ return parser(entries)
+ finally:
+ entries.close()
+ except:
+ return 'Unknown'
+
+
+ def _formatSVNVersion(self):
+ ver = self._getSVNVersion()
+ if ver is None:
+ return ''
+ return ' (SVN r%s)' % (ver,)
+
+
+
+def getVersionString(version):
+ """
+ Get a friendly string for the given version object.
+
+ @param version: A L{Version} object.
+ @return: A string containing the package and short version number.
+ """
+ result = '%s %s' % (version.package, version.short())
+ return result
diff --git a/vendor/Twisted-10.0.0/twisted/python/win32.py b/vendor/Twisted-10.0.0/twisted/python/win32.py
new file mode 100644
index 0000000000..884b041b79
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/win32.py
@@ -0,0 +1,163 @@
+# -*- test-case-name: twisted.python.test.test_win32 -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Win32 utilities.
+
+See also twisted.python.shortcut.
+"""
+
+import re
+import exceptions
+import os
+
+try:
+ import win32api
+ import win32con
+except ImportError:
+ pass
+
+from twisted.python.runtime import platform
+
+# http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/system_error_codes.asp
+ERROR_FILE_NOT_FOUND = 2
+ERROR_PATH_NOT_FOUND = 3
+ERROR_INVALID_NAME = 123
+ERROR_DIRECTORY = 267
+
+def _determineWindowsError():
+ """
+ Determine which WindowsError name to export.
+ """
+ return getattr(exceptions, 'WindowsError', FakeWindowsError)
+
+class FakeWindowsError(OSError):
+ """
+ Stand-in for sometimes-builtin exception on platforms for which it
+ is missing.
+ """
+
+WindowsError = _determineWindowsError()
+
+# XXX fix this to use python's builtin _winreg?
+
+def getProgramsMenuPath():
+ """Get the path to the Programs menu.
+
+ Probably will break on non-US Windows.
+
+ @returns: the filesystem location of the common Start Menu->Programs.
+ """
+ if not platform.isWinNT():
+ return "C:\\Windows\\Start Menu\\Programs"
+ keyname = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders'
+ hShellFolders = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE,
+ keyname, 0, win32con.KEY_READ)
+ return win32api.RegQueryValueEx(hShellFolders, 'Common Programs')[0]
+
+
+def getProgramFilesPath():
+ """Get the path to the Program Files folder."""
+ keyname = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion'
+ currentV = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE,
+ keyname, 0, win32con.KEY_READ)
+ return win32api.RegQueryValueEx(currentV, 'ProgramFilesDir')[0]
+
+_cmdLineQuoteRe = re.compile(r'(\\*)"')
+_cmdLineQuoteRe2 = re.compile(r'(\\+)\Z')
+def cmdLineQuote(s):
+ """
+ Internal method for quoting a single command-line argument.
+
+ @param s: an unquoted string that you want to quote so that something that
+ does cmd.exe-style unquoting will interpret it as a single argument,
+ even if it contains spaces.
+ @type s: C{str}
+
+ @return: a quoted string.
+ @rtype: C{str}
+ """
+ quote = ((" " in s) or ("\t" in s) or ('"' in s) or s == '') and '"' or ''
+ return quote + _cmdLineQuoteRe2.sub(r"\1\1", _cmdLineQuoteRe.sub(r'\1\1\\"', s)) + quote
+
+def quoteArguments(arguments):
+ """
+ Quote an iterable of command-line arguments for passing to CreateProcess or
+ a similar API. This allows the list passed to C{reactor.spawnProcess} to
+ match the child process's C{sys.argv} properly.
+
+ @param arglist: an iterable of C{str}, each unquoted.
+
+ @return: a single string, with the given sequence quoted as necessary.
+ """
+ return ' '.join([cmdLineQuote(a) for a in arguments])
+
+
+class _ErrorFormatter(object):
+ """
+ Formatter for Windows error messages.
+
+ @ivar winError: A callable which takes one integer error number argument
+ and returns an L{exceptions.WindowsError} instance for that error (like
+ L{ctypes.WinError}).
+
+ @ivar formatMessage: A callable which takes one integer error number
+ argument and returns a C{str} giving the message for that error (like
+ L{win32api.FormatMessage}).
+
+ @ivar errorTab: A mapping from integer error numbers to C{str} messages
+ which correspond to those erorrs (like L{socket.errorTab}).
+ """
+ def __init__(self, WinError, FormatMessage, errorTab):
+ self.winError = WinError
+ self.formatMessage = FormatMessage
+ self.errorTab = errorTab
+
+ def fromEnvironment(cls):
+ """
+ Get as many of the platform-specific error translation objects as
+ possible and return an instance of C{cls} created with them.
+ """
+ try:
+ from ctypes import WinError
+ except ImportError:
+ WinError = None
+ try:
+ from win32api import FormatMessage
+ except ImportError:
+ FormatMessage = None
+ try:
+ from socket import errorTab
+ except ImportError:
+ errorTab = None
+ return cls(WinError, FormatMessage, errorTab)
+ fromEnvironment = classmethod(fromEnvironment)
+
+
+ def formatError(self, errorcode):
+ """
+ Returns the string associated with a Windows error message, such as the
+ ones found in socket.error.
+
+ Attempts direct lookup against the win32 API via ctypes and then
+ pywin32 if available), then in the error table in the socket module,
+ then finally defaulting to C{os.strerror}.
+
+ @param errorcode: the Windows error code
+ @type errorcode: C{int}
+
+ @return: The error message string
+ @rtype: C{str}
+ """
+ if self.winError is not None:
+ return self.winError(errorcode)[1]
+ if self.formatMessage is not None:
+ return self.formatMessage(errorcode)
+ if self.errorTab is not None:
+ result = self.errorTab.get(errorcode)
+ if result is not None:
+ return result
+ return os.strerror(errorcode)
+
+formatError = _ErrorFormatter.fromEnvironment().formatError
diff --git a/vendor/Twisted-10.0.0/twisted/python/zippath.py b/vendor/Twisted-10.0.0/twisted/python/zippath.py
new file mode 100644
index 0000000000..7fa8a93db9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zippath.py
@@ -0,0 +1,217 @@
+# -*- test-case-name: twisted.test.test_paths.ZipFilePathTestCase -*-
+
+"""
+
+This module contains partial re-implementations of FilePath, pending some
+specification of formal interfaces it is a duck-typing attempt to emulate them
+for certain restricted uses.
+
+See the constructor for ZipArchive for use.
+
+"""
+
+__metaclass__ = type
+
+import os
+import time
+import errno
+
+from twisted.python.zipstream import ChunkingZipFile
+
+from twisted.python.filepath import FilePath, _PathHelper
+
+# using FilePath here exclusively rather than os to make sure that we don't do
+# anything OS-path-specific here.
+
+ZIP_PATH_SEP = '/' # In zipfiles, "/" is universally used as the
+ # path separator, regardless of platform.
+
+
+class ZipPath(_PathHelper):
+ """
+ I represent a file or directory contained within a zip file.
+ """
+ def __init__(self, archive, pathInArchive):
+ """
+ Don't construct me directly. Use ZipArchive.child().
+
+ @param archive: a ZipArchive instance.
+
+ @param pathInArchive: a ZIP_PATH_SEP-separated string.
+ """
+ self.archive = archive
+ self.pathInArchive = pathInArchive
+ # self.path pretends to be os-specific because that's the way the
+ # 'zipimport' module does it.
+ self.path = os.path.join(archive.zipfile.filename,
+ *(self.pathInArchive.split(ZIP_PATH_SEP)))
+
+ def __cmp__(self, other):
+ if not isinstance(other, ZipPath):
+ return NotImplemented
+ return cmp((self.archive, self.pathInArchive),
+ (other.archive, other.pathInArchive))
+
+ def __repr__(self):
+ return 'ZipPath(%r)' % (os.path.abspath(self.path),)
+
+ def parent(self):
+ splitup = self.pathInArchive.split(ZIP_PATH_SEP)
+ if len(splitup) == 1:
+ return self.archive
+ return ZipPath(self.archive, ZIP_PATH_SEP.join(splitup[:-1]))
+
+ def child(self, path):
+ return ZipPath(self.archive, ZIP_PATH_SEP.join([self.pathInArchive, path]))
+
+ def sibling(self, path):
+ return self.parent().child(path)
+
+ # preauthChild = child
+
+ def exists(self):
+ return self.isdir() or self.isfile()
+
+ def isdir(self):
+ return self.pathInArchive in self.archive.childmap
+
+ def isfile(self):
+ return self.pathInArchive in self.archive.zipfile.NameToInfo
+
+ def islink(self):
+ return False
+
+ def listdir(self):
+ if self.exists():
+ if self.isdir():
+ return self.archive.childmap[self.pathInArchive].keys()
+ else:
+ raise OSError(errno.ENOTDIR, "Leaf zip entry listed")
+ else:
+ raise OSError(errno.ENOENT, "Non-existent zip entry listed")
+
+
+ def splitext(self):
+ """
+ Return a value similar to that returned by os.path.splitext.
+ """
+ # This happens to work out because of the fact that we use OS-specific
+ # path separators in the constructor to construct our fake 'path'
+ # attribute.
+ return os.path.splitext(self.path)
+
+
+ def basename(self):
+ return self.pathInArchive.split(ZIP_PATH_SEP)[-1]
+
+ def dirname(self):
+ # XXX NOTE: This API isn't a very good idea on filepath, but it's even
+ # less meaningful here.
+ return self.parent().path
+
+ def open(self):
+ return self.archive.zipfile.readfile(self.pathInArchive)
+
+ def restat(self):
+ pass
+
+
+ def getAccessTime(self):
+ """
+ Retrieve this file's last access-time. This is the same as the last access
+ time for the archive.
+
+ @return: a number of seconds since the epoch
+ """
+ return self.archive.getAccessTime()
+
+
+ def getModificationTime(self):
+ """
+ Retrieve this file's last modification time. This is the time of
+ modification recorded in the zipfile.
+
+ @return: a number of seconds since the epoch.
+ """
+ return time.mktime(
+ self.archive.zipfile.NameToInfo[self.pathInArchive].date_time
+ + (0, 0, 0))
+
+
+ def getStatusChangeTime(self):
+ """
+ Retrieve this file's last modification time. This name is provided for
+ compatibility, and returns the same value as getmtime.
+
+ @return: a number of seconds since the epoch.
+ """
+ return self.getModificationTime()
+
+
+
+class ZipArchive(ZipPath):
+ """ I am a FilePath-like object which can wrap a zip archive as if it were a
+ directory.
+ """
+ archive = property(lambda self: self)
+ def __init__(self, archivePathname):
+ """Create a ZipArchive, treating the archive at archivePathname as a zip file.
+
+ @param archivePathname: a str, naming a path in the filesystem.
+ """
+ self.zipfile = ChunkingZipFile(archivePathname)
+ self.path = archivePathname
+ self.pathInArchive = ''
+ # zipfile is already wasting O(N) memory on cached ZipInfo instances,
+ # so there's no sense in trying to do this lazily or intelligently
+ self.childmap = {} # map parent: list of children
+
+ for name in self.zipfile.namelist():
+ name = name.split(ZIP_PATH_SEP)
+ for x in range(len(name)):
+ child = name[-x]
+ parent = ZIP_PATH_SEP.join(name[:-x])
+ if parent not in self.childmap:
+ self.childmap[parent] = {}
+ self.childmap[parent][child] = 1
+ parent = ''
+
+ def child(self, path):
+ """
+ Create a ZipPath pointing at a path within the archive.
+
+ @param path: a str with no path separators in it, either '/' or the
+ system path separator, if it's different.
+ """
+ return ZipPath(self, path)
+
+ def exists(self):
+ """
+ Returns true if the underlying archive exists.
+ """
+ return FilePath(self.zipfile.filename).exists()
+
+
+ def getAccessTime(self):
+ """
+ Return the archive file's last access time.
+ """
+ return FilePath(self.zipfile.filename).getAccessTime()
+
+
+ def getModificationTime(self):
+ """
+ Return the archive file's modification time.
+ """
+ return FilePath(self.zipfile.filename).getModificationTime()
+
+
+ def getStatusChangeTime(self):
+ """
+ Return the archive file's status change time.
+ """
+ return FilePath(self.zipfile.filename).getStatusChangeTime()
+
+
+ def __repr__(self):
+ return 'ZipArchive(%r)' % (os.path.abspath(self.path),)
diff --git a/vendor/Twisted-10.0.0/twisted/python/zipstream.py b/vendor/Twisted-10.0.0/twisted/python/zipstream.py
new file mode 100644
index 0000000000..ad014c9046
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zipstream.py
@@ -0,0 +1,377 @@
+# -*- test-case-name: twisted.python.test.test_zipstream -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An incremental approach to unzipping files. This allows you to unzip a little
+bit of a file at a time, which means you can report progress as a file unzips.
+"""
+
+import warnings
+import zipfile
+import os.path
+import zlib
+import struct
+
+_fileHeaderSize = struct.calcsize(zipfile.structFileHeader)
+
+class ChunkingZipFile(zipfile.ZipFile):
+ """
+ A ZipFile object which, with readfile(), also gives you access to a
+ filelike object for each entry.
+ """
+
+ def readfile(self, name):
+ """
+ Return file-like object for name.
+ """
+ if self.mode not in ("r", "a"):
+ raise RuntimeError('read() requires mode "r" or "a"')
+ if not self.fp:
+ raise RuntimeError(
+ "Attempt to read ZIP archive that was already closed")
+ zinfo = self.getinfo(name)
+
+ self.fp.seek(zinfo.header_offset, 0)
+
+ fheader = self.fp.read(_fileHeaderSize)
+ if fheader[0:4] != zipfile.stringFileHeader:
+ raise zipfile.BadZipfile("Bad magic number for file header")
+
+ fheader = struct.unpack(zipfile.structFileHeader, fheader)
+ fname = self.fp.read(fheader[zipfile._FH_FILENAME_LENGTH])
+
+ if fheader[zipfile._FH_EXTRA_FIELD_LENGTH]:
+ self.fp.read(fheader[zipfile._FH_EXTRA_FIELD_LENGTH])
+
+ if fname != zinfo.orig_filename:
+ raise zipfile.BadZipfile(
+ 'File name in directory "%s" and header "%s" differ.' % (
+ zinfo.orig_filename, fname))
+
+ if zinfo.compress_type == zipfile.ZIP_STORED:
+ return ZipFileEntry(self, zinfo.compress_size)
+ elif zinfo.compress_type == zipfile.ZIP_DEFLATED:
+ return DeflatedZipFileEntry(self, zinfo.compress_size)
+ else:
+ raise zipfile.BadZipfile(
+ "Unsupported compression method %d for file %s" %
+ (zinfo.compress_type, name))
+
+
+
+class _FileEntry(object):
+ """
+ Abstract superclass of both compressed and uncompressed variants of
+ file-like objects within a zip archive.
+
+ @ivar chunkingZipFile: a chunking zip file.
+ @type chunkingZipFile: L{ChunkingZipFile}
+
+ @ivar length: The number of bytes within the zip file that represent this
+ file. (This is the size on disk, not the number of decompressed bytes
+ which will result from reading it.)
+
+ @ivar fp: the underlying file object (that contains pkzip data). Do not
+ touch this, please. It will quite likely move or go away.
+
+ @ivar closed: File-like 'closed' attribute; True before this file has been
+ closed, False after.
+ @type closed: L{bool}
+
+ @ivar finished: An older, broken synonym for 'closed'. Do not touch this,
+ please.
+ @type finished: L{int}
+ """
+ def __init__(self, chunkingZipFile, length):
+ """
+ Create a L{_FileEntry} from a L{ChunkingZipFile}.
+ """
+ self.chunkingZipFile = chunkingZipFile
+ self.fp = self.chunkingZipFile.fp
+ self.length = length
+ self.finished = 0
+ self.closed = False
+
+
+ def isatty(self):
+ """
+ Returns false because zip files should not be ttys
+ """
+ return False
+
+
+ def close(self):
+ """
+ Close self (file-like object)
+ """
+ self.closed = True
+ self.finished = 1
+ del self.fp
+
+
+ def readline(self):
+ """
+ Read a line.
+ """
+ bytes = ""
+ for byte in iter(lambda : self.read(1), ""):
+ bytes += byte
+ if byte == "\n":
+ break
+ return bytes
+
+
+ def next(self):
+ """
+ Implement next as file does (like readline, except raises StopIteration
+ at EOF)
+ """
+ nextline = self.readline()
+ if nextline:
+ return nextline
+ raise StopIteration()
+
+
+ def readlines(self):
+ """
+ Returns a list of all the lines
+ """
+ return list(self)
+
+
+ def xreadlines(self):
+ """
+ Returns an iterator (so self)
+ """
+ return self
+
+
+ def __iter__(self):
+ """
+ Returns an iterator (so self)
+ """
+ return self
+
+
+
+class ZipFileEntry(_FileEntry):
+ """
+ File-like object used to read an uncompressed entry in a ZipFile
+ """
+
+ def __init__(self, chunkingZipFile, length):
+ _FileEntry.__init__(self, chunkingZipFile, length)
+ self.readBytes = 0
+
+
+ def tell(self):
+ return self.readBytes
+
+
+ def read(self, n=None):
+ if n is None:
+ n = self.length - self.readBytes
+ if n == 0 or self.finished:
+ return ''
+ data = self.chunkingZipFile.fp.read(
+ min(n, self.length - self.readBytes))
+ self.readBytes += len(data)
+ if self.readBytes == self.length or len(data) < n:
+ self.finished = 1
+ return data
+
+
+
+class DeflatedZipFileEntry(_FileEntry):
+ """
+ File-like object used to read a deflated entry in a ZipFile
+ """
+
+ def __init__(self, chunkingZipFile, length):
+ _FileEntry.__init__(self, chunkingZipFile, length)
+ self.returnedBytes = 0
+ self.readBytes = 0
+ self.decomp = zlib.decompressobj(-15)
+ self.buffer = ""
+
+
+ def tell(self):
+ return self.returnedBytes
+
+
+ def read(self, n=None):
+ if self.finished:
+ return ""
+ if n is None:
+ result = [self.buffer,]
+ result.append(
+ self.decomp.decompress(
+ self.chunkingZipFile.fp.read(
+ self.length - self.readBytes)))
+ result.append(self.decomp.decompress("Z"))
+ result.append(self.decomp.flush())
+ self.buffer = ""
+ self.finished = 1
+ result = "".join(result)
+ self.returnedBytes += len(result)
+ return result
+ else:
+ while len(self.buffer) < n:
+ data = self.chunkingZipFile.fp.read(
+ min(n, 1024, self.length - self.readBytes))
+ self.readBytes += len(data)
+ if not data:
+ result = (self.buffer
+ + self.decomp.decompress("Z")
+ + self.decomp.flush())
+ self.finished = 1
+ self.buffer = ""
+ self.returnedBytes += len(result)
+ return result
+ else:
+ self.buffer += self.decomp.decompress(data)
+ result = self.buffer[:n]
+ self.buffer = self.buffer[n:]
+ self.returnedBytes += len(result)
+ return result
+
+
+
+def unzip(filename, directory=".", overwrite=0):
+ """
+ Unzip the file
+
+ @param filename: the name of the zip file
+ @param directory: the directory into which the files will be
+ extracted
+ @param overwrite: if on, overwrite files when they exist. You can
+ still get an error if you try to create a directory over a file
+ with the same name or vice-versa.
+ """
+ for i in unzipIter(filename, directory, overwrite):
+ pass
+
+DIR_BIT = 16
+
+def unzipIter(filename, directory='.', overwrite=0):
+ """
+ Return a generator for the zipfile. This implementation will yield
+ after every file.
+
+ The value it yields is the number of files left to unzip.
+ """
+ zf = zipfile.ZipFile(filename, 'r')
+ names = zf.namelist()
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+ remaining = len(zf.namelist())
+ for entry in names:
+ remaining -= 1
+ isdir = zf.getinfo(entry).external_attr & DIR_BIT
+ f = os.path.join(directory, entry)
+ if isdir:
+ # overwrite flag only applies to files
+ if not os.path.exists(f):
+ os.makedirs(f)
+ else:
+ # create the directory the file will be in first,
+ # since we can't guarantee it exists
+ fdir = os.path.split(f)[0]
+ if not os.path.exists(fdir):
+ os.makedirs(fdir)
+ if overwrite or not os.path.exists(f):
+ outfile = file(f, 'wb')
+ outfile.write(zf.read(entry))
+ outfile.close()
+ yield remaining
+
+
+def countZipFileChunks(filename, chunksize):
+ """
+ Predict the number of chunks that will be extracted from the entire
+ zipfile, given chunksize blocks.
+ """
+ totalchunks = 0
+ zf = ChunkingZipFile(filename)
+ for info in zf.infolist():
+ totalchunks += countFileChunks(info, chunksize)
+ return totalchunks
+
+
+def countFileChunks(zipinfo, chunksize):
+ """
+ Count the number of chunks that will result from the given L{ZipInfo}.
+
+ @param zipinfo: a L{zipfile.ZipInfo} instance describing an entry in a zip
+ archive to be counted.
+
+ @return: the number of chunks present in the zip file. (Even an empty file
+ counts as one chunk.)
+ @rtype: L{int}
+ """
+ count, extra = divmod(zipinfo.file_size, chunksize)
+ if extra > 0:
+ count += 1
+ return count or 1
+
+
+def countZipFileEntries(filename):
+ """
+ Count the number of entries in a zip archive. (Don't use this function.)
+
+ @param filename: The filename of a zip archive.
+ @type filename: L{str}
+ """
+ warnings.warn("countZipFileEntries is deprecated.",
+ DeprecationWarning, 2)
+ zf = zipfile.ZipFile(filename)
+ return len(zf.namelist())
+
+
+def unzipIterChunky(filename, directory='.', overwrite=0,
+ chunksize=4096):
+ """
+ Return a generator for the zipfile. This implementation will yield after
+ every chunksize uncompressed bytes, or at the end of a file, whichever
+ comes first.
+
+ The value it yields is the number of chunks left to unzip.
+ """
+ czf = ChunkingZipFile(filename, 'r')
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+ remaining = countZipFileChunks(filename, chunksize)
+ names = czf.namelist()
+ infos = czf.infolist()
+
+ for entry, info in zip(names, infos):
+ isdir = info.external_attr & DIR_BIT
+ f = os.path.join(directory, entry)
+ if isdir:
+ # overwrite flag only applies to files
+ if not os.path.exists(f):
+ os.makedirs(f)
+ remaining -= 1
+ yield remaining
+ else:
+ # create the directory the file will be in first,
+ # since we can't guarantee it exists
+ fdir = os.path.split(f)[0]
+ if not os.path.exists(fdir):
+ os.makedirs(fdir)
+ if overwrite or not os.path.exists(f):
+ outfile = file(f, 'wb')
+ fp = czf.readfile(entry)
+ if info.file_size == 0:
+ remaining -= 1
+ yield remaining
+ while fp.tell() < info.file_size:
+ hunk = fp.read(chunksize)
+ outfile.write(hunk)
+ remaining -= 1
+ yield remaining
+ outfile.close()
+ else:
+ remaining -= countFileChunks(info, chunksize)
+ yield remaining
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/README b/vendor/Twisted-10.0.0/twisted/python/zsh/README
new file mode 100644
index 0000000000..b076ed4eb1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/README
@@ -0,0 +1,8 @@
+This directory holds the auto-generated zsh completion
+functions for twisted commands. Re-generate with
+python zshcomp.py path/to/this/dir
+
+This directory needs to be under the twisted package so
+that is can be dynamically located on the filesystem
+by _twisted_zsh_stub
+
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_cftp b/vendor/Twisted-10.0.0/twisted/python/zsh/_cftp
new file mode 100644
index 0000000000..342bbf8be1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_cftp
@@ -0,0 +1,48 @@
+#compdef cftp
+_arguments -s -A "-*" \
+'1:host | user@host:{_ssh;if compset -P "*@"; then _wanted hosts expl "remote host name" _ssh_hosts && ret=0 elif compset -S "@*"; then _wanted users expl "login name" _ssh_users -S "" && ret=0 else if (( $+opt_args[-l] )); then tmp=() else tmp=( "users:login name:_ssh_users -qS@" ) fi; _alternative "hosts:remote host name:_ssh_hosts" "$tmp[@]" && ret=0 fi}' \
+'2::localfile:{if [[ $words[1] == *:* ]]; then; _files; fi}' \
+'(--noagent -a --agent)-A[Enable authentication agent forwarding]' \
+'(--noagent -a -A)--agent[Enable authentication agent forwarding]' \
+"(--batchfile)-b[File to read commands from, or '-' for stdin.]:batchfile:_files" \
+"(-b)--batchfile=[File to read commands from, or '-' for stdin.]:batchfile:_files" \
+'(--buffersize)-B[Size of send/receive buffer (default: 32768)]:buffersize:_files' \
+'(-B)--buffersize=[Size of send/receive buffer (default: 32768)]:buffersize:_files' \
+"(--ciphers)-c[Select encryption algorithms]:ciphers:_values -s , 'ciphers to choose from' idea-ctr blowfish-ctr none arcfour aes256-ctr cast128-ctr idea-cbc blowfish-cbc 3des-cbc aes256-cbc 3des-ctr cast128-cbc aes128-ctr aes192-cbc aes192-ctr aes128-cbc" \
+"(-c)--ciphers=[Select encryption algorithms]:ciphers:_values -s , 'ciphers to choose from' idea-ctr blowfish-ctr none arcfour aes256-ctr cast128-ctr idea-cbc blowfish-cbc 3des-cbc aes256-cbc 3des-ctr cast128-cbc aes128-ctr aes192-cbc aes192-ctr aes128-cbc" \
+'(--compress)-C[Enable compression.]' \
+'(-C)--compress[Enable compression.]' \
+"(--connection-usage)-K[Connection types to use]:connection-usage:_values -s , 'connection types to choose from' unix direct" \
+"(-K)--connection-usage=[Connection types to use]:connection-usage:_values -s , 'connection types to choose from' unix direct" \
+'--help[Display this help and exit.]' \
+"--host-key-algorithms=[Select host key algorithms]:host-key-algorithms:_values -s , 'host key algorithms to choose from' ssh-rsa ssh-dss" \
+'(--identity)-i[Identity for public-key authentication]:identity:_files' \
+'(-i)--identity=[Identity for public-key authentication]:identity:_files' \
+'--known-hosts=[File to check for host keys]:known-hosts:_files' \
+'(--log)-v[Enable logging (defaults to stderr)]' \
+'(-v)--log[Enable logging (defaults to stderr)]' \
+'--logfile=[File to log to, or - for stdout]:logfile:_files' \
+"(--macs)-m[Specify MAC algorithms]:macs:_values -s , 'macs to choose from' hmac-sha1 none hmac-md5" \
+"(-m)--macs=[Specify MAC algorithms]:macs:_values -s , 'macs to choose from' hmac-sha1 none hmac-md5" \
+'(--agent -A --noagent)-a[Disable authentication agent forwarding (default)]' \
+'(--agent -A -a)--noagent[Disable authentication agent forwarding (default)]' \
+'(--nocache)-I[Do not allow connection sharing over this connection.]' \
+'(-I)--nocache[Do not allow connection sharing over this connection.]' \
+'(--nox11)-x[Disable X11 connection forwarding (default)]' \
+'(-x)--nox11[Disable X11 connection forwarding (default)]' \
+'(--option)-o[Ignored OpenSSH options]:option:_files' \
+'(-o)--option=[Ignored OpenSSH options]:option:_files' \
+'(--port)-p[Connect to this port. Server must be on the same port.]:port:_files' \
+'(-p)--port=[Connect to this port. Server must be on the same port.]:port:_files' \
+'(--reconnect)-r[Reconnect to the server if the connection is lost.]' \
+'(-r)--reconnect[Reconnect to the server if the connection is lost.]' \
+'(--requests)-R[Number of requests to make before waiting for a reply.]:requests:_files' \
+'(-R)--requests=[Number of requests to make before waiting for a reply.]:requests:_files' \
+'(--subsystem)-s[Subsystem/server program to connect to.]:subsystem:_files' \
+'(-s)--subsystem=[Subsystem/server program to connect to.]:subsystem:_files' \
+'(--user)-l[Log in using this user name.]:user:_users' \
+'(-l)--user=[Log in using this user name.]:user:_users' \
+'--user-authentications=[Types of user authentications to use.]:user-authentications:_files' \
+'(--version)-V[Display version number only.]' \
+'(-V)--version[Display version number only.]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_ckeygen b/vendor/Twisted-10.0.0/twisted/python/zsh/_ckeygen
new file mode 100644
index 0000000000..7842b50085
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_ckeygen
@@ -0,0 +1,25 @@
+#compdef ckeygen
+_arguments -s -A "-*" \
+'(--bits)-b[Number of bits in the key to create.]:bits:_files' \
+'(-b)--bits=[Number of bits in the key to create.]:bits:_files' \
+'(--changepass)-p[Change passphrase of private key file.]' \
+'(-p)--changepass[Change passphrase of private key file.]' \
+'(--comment)-C[Provide new comment.]:comment:_files' \
+'(-C)--comment=[Provide new comment.]:comment:_files' \
+'(--filename)-f[Filename of the key file.]:filename:_files' \
+'(-f)--filename=[Filename of the key file.]:filename:_files' \
+'(--fingerprint)-l[Show fingerprint of key file.]' \
+'(-l)--fingerprint[Show fingerprint of key file.]' \
+'--help[Display this help and exit.]' \
+'(--newpass)-N[Provide new passphrase.]:newpass:_files' \
+'(-N)--newpass=[Provide new passphrase.]:newpass:_files' \
+'(--pass)-P[Provide old passphrase]:pass:_files' \
+'(-P)--pass=[Provide old passphrase]:pass:_files' \
+'(--quiet)-q[Quiet.]' \
+'(-q)--quiet[Quiet.]' \
+'(--showpub)-y[Read private key file and print public key.]' \
+'(-y)--showpub[Read private key file and print public key.]' \
+'(--type)-t[Specify type of key to create.]:type:(rsa dsa)' \
+'(-t)--type=[Specify type of key to create.]:type:(rsa dsa)' \
+'--version[version]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_conch b/vendor/Twisted-10.0.0/twisted/python/zsh/_conch
new file mode 100644
index 0000000000..b68088ad2f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_conch
@@ -0,0 +1,58 @@
+#compdef conch
+_arguments -s -A "-*" \
+'1:host | user@host:{_ssh;if compset -P "*@"; then _wanted hosts expl "remote host name" _ssh_hosts && ret=0 elif compset -S "@*"; then _wanted users expl "login name" _ssh_users -S "" && ret=0 else if (( $+opt_args[-l] )); then tmp=() else tmp=( "users:login name:_ssh_users -qS@" ) fi; _alternative "hosts:remote host name:_ssh_hosts" "$tmp[@]" && ret=0 fi}' \
+'*:command: ' \
+'(--noagent -a --agent)-A[Enable authentication agent forwarding]' \
+'(--noagent -a -A)--agent[Enable authentication agent forwarding]' \
+"(--ciphers)-c[Select encryption algorithms]:ciphers:_values -s , 'ciphers to choose from' idea-ctr blowfish-ctr none arcfour aes256-ctr cast128-ctr idea-cbc blowfish-cbc 3des-cbc aes256-cbc 3des-ctr cast128-cbc aes128-ctr aes192-cbc aes192-ctr aes128-cbc" \
+"(-c)--ciphers=[Select encryption algorithms]:ciphers:_values -s , 'ciphers to choose from' idea-ctr blowfish-ctr none arcfour aes256-ctr cast128-ctr idea-cbc blowfish-cbc 3des-cbc aes256-cbc 3des-ctr cast128-cbc aes128-ctr aes192-cbc aes192-ctr aes128-cbc" \
+'(--compress)-C[Enable compression.]' \
+'(-C)--compress[Enable compression.]' \
+"(--connection-usage)-K[Connection types to use]:connection-usage:_values -s , 'connection types to choose from' unix direct" \
+"(-K)--connection-usage=[Connection types to use]:connection-usage:_values -s , 'connection types to choose from' unix direct" \
+"(--escape)-e[Set escape character; \`\`none'' = disable]:escape:_files" \
+"(-e)--escape=[Set escape character; \`\`none'' = disable]:escape:_files" \
+'(--fork)-f[Fork to background after authentication.]' \
+'(-f)--fork[Fork to background after authentication.]' \
+'--help[Display this help and exit.]' \
+"--host-key-algorithms=[Select host key algorithms]:host-key-algorithms:_values -s , 'host key algorithms to choose from' ssh-rsa ssh-dss" \
+'(--identity)-i[Identity for public-key authentication]:identity:_files' \
+'(-i)--identity=[Identity for public-key authentication]:identity:_files' \
+'--known-hosts=[File to check for host keys]:known-hosts:_files' \
+'(--localforward)-L[listen-port:host:port Forward local port to remote address]:listen-port:host:port:_files' \
+'(-L)--localforward=[listen-port:host:port Forward local port to remote address]:listen-port:host:port:_files' \
+'(--log)-v[Enable logging (defaults to stderr)]' \
+'(-v)--log[Enable logging (defaults to stderr)]' \
+'--logfile=[File to log to, or - for stdout]:logfile:_files' \
+"(--macs)-m[Specify MAC algorithms]:macs:_values -s , 'macs to choose from' hmac-sha1 none hmac-md5" \
+"(-m)--macs=[Specify MAC algorithms]:macs:_values -s , 'macs to choose from' hmac-sha1 none hmac-md5" \
+'(--agent -A --noagent)-a[Disable authentication agent forwarding (default)]' \
+'(--agent -A -a)--noagent[Disable authentication agent forwarding (default)]' \
+'(--nocache)-I[Do not allow connection sharing over this connection.]' \
+'(-I)--nocache[Do not allow connection sharing over this connection.]' \
+'(--noshell)-N[Do not execute a shell or command.]' \
+'(-N)--noshell[Do not execute a shell or command.]' \
+'(--notty)-T[Do not allocate a tty.]' \
+'(-T)--notty[Do not allocate a tty.]' \
+'(--nox11)-x[Disable X11 connection forwarding (default)]' \
+'(-x)--nox11[Disable X11 connection forwarding (default)]' \
+'(--null)-n[Redirect input from /dev/null.]' \
+'(-n)--null[Redirect input from /dev/null.]' \
+'(--option)-o[Ignored OpenSSH options]:option:_files' \
+'(-o)--option=[Ignored OpenSSH options]:option:_files' \
+'(--port)-p[Connect to this port. Server must be on the same port.]:port:_files' \
+'(-p)--port=[Connect to this port. Server must be on the same port.]:port:_files' \
+'(--reconnect)-r[Reconnect to the server if the connection is lost.]' \
+'(-r)--reconnect[Reconnect to the server if the connection is lost.]' \
+'(--remoteforward)-R[listen-port:host:port Forward remote port to local address]:listen-port:host:port:_files' \
+'(-R)--remoteforward=[listen-port:host:port Forward remote port to local address]:listen-port:host:port:_files' \
+'(--subsystem)-s[Invoke command (mandatory) as SSH2 subsystem.]' \
+'(-s)--subsystem[Invoke command (mandatory) as SSH2 subsystem.]' \
+'(--tty)-t[Tty; allocate a tty even if command is given.]' \
+'(-t)--tty[Tty; allocate a tty even if command is given.]' \
+'(--user)-l[Log in using this user name.]:user:_users' \
+'(-l)--user=[Log in using this user name.]:user:_users' \
+'--user-authentications=[Types of user authentications to use.]:user-authentications:_files' \
+'(--version)-V[Display version number only.]' \
+'(-V)--version[Display version number only.]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_lore b/vendor/Twisted-10.0.0/twisted/python/zsh/_lore
new file mode 100644
index 0000000000..012790cd1b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_lore
@@ -0,0 +1,28 @@
+#compdef lore
+_arguments -s -A "-*" \
+'*:files:_files' \
+'(--book)-b[The book file to generate a book from]:book:_files' \
+'(-b)--book=[The book file to generate a book from]:book:_files' \
+'--config=[config]:config:_files' \
+'(--docsdir)-d[docsdir]:docsdir:_files' \
+'(-d)--docsdir=[docsdir]:docsdir:_files' \
+'--help[Display this help and exit.]' \
+'(--index)-x[The base filename you want to give your index file]:index:_files' \
+'(-x)--index=[The base filename you want to give your index file]:index:_files' \
+'(--input)-i[input]:input:_files' \
+'(-i)--input=[input]:input:_files' \
+'(--inputext)-e[The extension that your Lore input files have]:inputext:_files' \
+'(-e)--inputext=[The extension that your Lore input files have]:inputext:_files' \
+'(--linkrel)-l[linkrel]:linkrel:_files' \
+'(-l)--linkrel=[linkrel]:linkrel:_files' \
+'(--null)-n[Do not report filenames]' \
+'(-n)--null[Do not report filenames]' \
+'(--number)-N[Add chapter/section numbers to section headings]' \
+'(-N)--number[Add chapter/section numbers to section headings]' \
+'(--output)-o[output]:output:_files' \
+'(-o)--output=[output]:output:_files' \
+'(--plain)-p[Report filenames without progress bar]' \
+'(-p)--plain[Report filenames without progress bar]' \
+'--prefixurl=[The prefix to stick on to relative links; only useful when processing directories]:prefixurl:_files' \
+'--version[version]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_manhole b/vendor/Twisted-10.0.0/twisted/python/zsh/_manhole
new file mode 100644
index 0000000000..fffeef089d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_manhole
@@ -0,0 +1,19 @@
+#compdef manhole
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'(--host)-h[host]:host:_hosts' \
+'(-h)--host=[host]:host:_hosts' \
+'(--password)-w[password]:password:_files' \
+'(-w)--password=[password]:password:_files' \
+'(--perspective)-P[PB Perspective to ask for (if different than username)]:perspective:_files' \
+'(-P)--perspective=[PB Perspective to ask for (if different than username)]:perspective:_files' \
+'(--port)-p[port]:port:_files' \
+'(-p)--port=[port]:port:_files' \
+'(--service)-s[PB Service]:service:_files' \
+'(-s)--service=[PB Service]:service:_files' \
+'(--toolkit)-t[Front-end to use; one of gtk2]:toolkit:(gtk1 gtk2)' \
+'(-t)--toolkit=[Front-end to use; one of gtk2]:toolkit:(gtk1 gtk2)' \
+'(--user)-u[username]:user:_files' \
+'(-u)--user=[username]:user:_files' \
+'--version[version]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_mktap b/vendor/Twisted-10.0.0/twisted/python/zsh/_mktap
new file mode 100644
index 0000000000..b00bf50fcf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_mktap
@@ -0,0 +1,304 @@
+#compdef mktap
+local _zsh_subcmds_array
+_zsh_subcmds_array=(
+"web2:An HTTP/1.1 web server that can serve from a filesystem or application resource."
+"ftp:An FTP server."
+"telnet:A simple, telnet-based remote debugging service."
+"socks:A SOCKSv4 proxy service."
+"manhole-old:An interactive remote debugger service."
+"portforward:A simple port-forwarder."
+"web:A general-purpose web server which can serve from a filesystem or application resource."
+"inetd:An inetd(8) replacement."
+"news:A news server."
+"words:A modern words server"
+"toc:An AIM TOC service."
+"dns:A domain name server."
+"mail:An email service"
+"manhole:An interactive remote debugger service accessible via telnet and ssh and providing syntax coloring and basic line editing functionality."
+"conch:A Conch SSH service."
+)
+
+_arguments -s -A "-*" \
+'*::subcmd:->subcmd' \
+'(--append)-a[An existing .tap file to append the plugin to, rather than creating a new one.]:tap file to append to:_files -g "*.tap"' \
+'(-a)--append=[An existing .tap file to append the plugin to, rather than creating a new one.]:tap file to append to:_files -g "*.tap"' \
+'(--appname)-n[The process name to use for this application.]:appname:_files' \
+'(-n)--appname=[The process name to use for this application.]:appname:_files' \
+'(--debug)-d[Show debug information for plugin loading]' \
+'(-d)--debug[Show debug information for plugin loading]' \
+"(--encrypted)-e[Encrypt file before writing (will make the extension of the resultant file begin with 'e')]" \
+"(-e)--encrypted[Encrypt file before writing (will make the extension of the resultant file begin with 'e')]" \
+'(--gid)-g[The gid to run as.]:gid to run as:_files' \
+'(-g)--gid=[The gid to run as.]:gid to run as:_files' \
+'(--help)-h[Display this message]' \
+'(-h)--help[Display this message]' \
+'(--progress)-p[Show progress information for plugin loading]' \
+'(-p)--progress[Show progress information for plugin loading]' \
+"(--type)-t[The output format to use; this can be 'pickle', 'xml', or 'source'.]:output format:(pickle xml source)" \
+"(-t)--type=[The output format to use; this can be 'pickle', 'xml', or 'source'.]:output format:(pickle xml source)" \
+'(--uid)-u[The uid to run as.]:uid to run as:_files' \
+'(-u)--uid=[The uid to run as.]:uid to run as:_files' \
+'--version[version]' \
+&& return 0
+if (( CURRENT == 1 )); then
+ _describe "tap to build" _zsh_subcmds_array && ret=0
+fi
+(( ret )) || return 0
+
+service="$words[1]"
+
+case $service in
+web2)
+_arguments -s -A "-*" \
+"--allow-ignore-ext[Specify whether or not a request for 'foo' should return 'foo.ext']" \
+"(--certificate)-c[SSL certificate to use for HTTPS.]:certificate:_files -g '*.pem'" \
+"(-c)--certificate=[SSL certificate to use for HTTPS.]:certificate:_files -g '*.pem'" \
+'--class=[A class that will be used to serve the root resource. Must implement twisted.web2.iweb.IResource and take no arguments.]:class:_files' \
+'--dav=[A path that will be used to serve the root resource as a DAV Collection.]:dav:_files' \
+'--help[Display this help and exit.]' \
+'--https=[Port to listen on for Secure HTTP.]:https:_files' \
+'--ignore-ext=[Specify an extension to ignore. These will be processed in order.]:ignore-ext:_files' \
+'(--index)-i[Add the name of a file used to check for directory indexes.]:index:_files' \
+'(-i)--index=[Add the name of a file used to check for directory indexes.]:index:_files' \
+'(--logfile)-l[Common Access Logging Format file to write to if unspecified access log information will be written to the standard twisted log file.]:logfile:_files' \
+'(-l)--logfile=[Common Access Logging Format file to write to if unspecified access log information will be written to the standard twisted log file.]:logfile:_files' \
+"--mimetype=[Mapping from file extension to MIME Type in the form of 'ext=type'.]:mimetype:_files" \
+'--path=[A path that will be used to serve the root resource as a raw file]:path:_files' \
+'(--port)-p[Port to start the server on.]:port:_files' \
+'(-p)--port=[Port to start the server on.]:port:_files' \
+"(--privkey)-k[SSL certificate to use for HTTPS.]:privkey:_files -g '*.pem'" \
+"(-k)--privkey=[SSL certificate to use for HTTPS.]:privkey:_files -g '*.pem'" \
+"--processor=[\`ext=class' where \`class' is added as a Processor for files ending]:processor:_files" \
+'--version[version]' \
+'--vhost-class=[Specify a virtual host in the form of domain=class,]:vhost-class:_files' \
+'--vhost-dav=[Specify a virtual host in the form of domain=path,]:vhost-dav:_files' \
+'--vhost-path=[Specify a directory to use for automatic named virtual hosts.]:vhost-path:_files' \
+'--vhost-static=[Specify a virtual host in the form of domain=path to be served as]:vhost-static:_files' \
+&& return 0
+;;
+ftp)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'--password-file=[username:password-style credentials database]:password-file:_files' \
+'(--port)-p[set the port number]:port:_files' \
+'(-p)--port=[set the port number]:port:_files' \
+'(--root)-r[define the root of the ftp-site.]:root:_files' \
+'(-r)--root=[define the root of the ftp-site.]:root:_files' \
+'--userAnonymous=[Name of the anonymous user.]:userAnonymous:_files' \
+'--version[version]' \
+&& return 0
+;;
+telnet)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'(--password)-w[set the password]:password:_files' \
+'(-w)--password=[set the password]:password:_files' \
+'(--port)-p[port to listen on]:port:_files' \
+'(-p)--port=[port to listen on]:port:_files' \
+'(--username)-u[set the login username]:username:_users' \
+'(-u)--username=[set the login username]:username:_users' \
+'--version[version]' \
+&& return 0
+;;
+socks)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'(--interface)-i[local interface to which we listen]:interface:_files' \
+'(-i)--interface=[local interface to which we listen]:interface:_files' \
+"(--log)-l[file to log connection data to]:log:_files -g '*.log'" \
+"(-l)--log=[file to log connection data to]:log:_files -g '*.log'" \
+'(--port)-p[Port on which to listen]:port:_files' \
+'(-p)--port=[Port on which to listen]:port:_files' \
+'--version[version]' \
+&& return 0
+;;
+manhole-old)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+"(--password)-w[Required. '-' will prompt or read a password from stdin.]:password:_files" \
+"(-w)--password=[Required. '-' will prompt or read a password from stdin.]:password:_files" \
+'(--port)-p[Port to listen on]:port:_files' \
+'(-p)--port=[Port to listen on]:port:_files' \
+'(--tracebacks)-T[Allow tracebacks to be sent over the network]' \
+'(-T)--tracebacks[Allow tracebacks to be sent over the network]' \
+'(--user)-u[Name of user to allow to log in]:user:_users' \
+'(-u)--user=[Name of user to allow to log in]:user:_users' \
+'--version[version]' \
+&& return 0
+;;
+portforward)
+_arguments -s -A "-*" \
+'(--dest_port)-d[Set the destination port.]:dest_port:_files' \
+'(-d)--dest_port=[Set the destination port.]:dest_port:_files' \
+'--help[Display this help and exit.]' \
+'(--host)-h[Set the host.]:host:_hosts' \
+'(-h)--host=[Set the host.]:host:_hosts' \
+'(--port)-p[Set the port number.]:port:_files' \
+'(-p)--port=[Set the port number.]:port:_files' \
+'--version[version]' \
+&& return 0
+;;
+web)
+_arguments -s -A "-*" \
+"--allow-ignore-ext[Specify whether or not a request for 'foo' should return 'foo.ext']" \
+"(--certificate)-c[SSL certificate to use for HTTPS. ]:certificate:_files -g '*.pem'" \
+"(-c)--certificate=[SSL certificate to use for HTTPS. ]:certificate:_files -g '*.pem'" \
+'--class=[Create a Resource subclass with a zero-argument constructor.]:class:_files' \
+'--flashconduit=[Start a flashconduit on the specified port.]:flashconduit:_files' \
+'--help[Display this help and exit.]' \
+'--https=[Port to listen on for Secure HTTP.]:https:_files' \
+'--ignore-ext=[Specify an extension to ignore. These will be processed in order.]:ignore-ext:_files' \
+'(--index)-i[Add the name of a file used to check for directory indexes.]:index:_files' \
+'(-i)--index=[Add the name of a file used to check for directory indexes.]:index:_files' \
+"(--logfile)-l[Path to web CLF (Combined Log Format) log file.]:logfile:_files -g '*.log'" \
+"(-l)--logfile=[Path to web CLF (Combined Log Format) log file.]:logfile:_files -g '*.log'" \
+'(--mime-type)-m[Specify the default mime-type for static files.]:mime-type:_files' \
+'(-m)--mime-type=[Specify the default mime-type for static files.]:mime-type:_files' \
+'(--notracebacks)-n[Do not display tracebacks in broken web pages. Displaying tracebacks to users may be security risk!]' \
+'(-n)--notracebacks[Do not display tracebacks in broken web pages. Displaying tracebacks to users may be security risk!]' \
+'--path=[<path> is either a specific file or a directory to]:path:_files' \
+'--personal[Instead of generating a webserver, generate a ResourcePublisher which listens on ~/.twistd-web-pb]' \
+'(--port)-p[Port to start the server on.]:port:_files' \
+'(-p)--port=[Port to start the server on.]:port:_files' \
+"(--privkey)-k[SSL certificate to use for HTTPS.]:privkey:_files -g '*.pem'" \
+"(-k)--privkey=[SSL certificate to use for HTTPS.]:privkey:_files -g '*.pem'" \
+"--processor=[\`ext=class' where \`class' is added as a Processor for files ending]:processor:_files" \
+'--resource-script=[An .rpy file to be used as the root resource of the webserver.]:resource-script:_files' \
+'(--static)-s[Same as --path, this is deprecated and will be removed in a]:static:_files' \
+'(-s)--static=[Same as --path, this is deprecated and will be removed in a]:static:_files' \
+'(--user)-u[Makes a server with ~/public_html and ~/.twistd-web-pb support for]' \
+'(-u)--user[Makes a server with ~/public_html and ~/.twistd-web-pb support for]' \
+'--version[version]' \
+&& return 0
+;;
+inetd)
+_arguments -s -A "-*" \
+"(--file)-f[Service configuration file]:file:_files -g '*.conf'" \
+"(-f)--file=[Service configuration file]:file:_files -g '*.conf'" \
+'--help[Display this help and exit.]' \
+"(--nointernal)-i[Don't run internal services]" \
+"(-i)--nointernal[Don't run internal services]" \
+'(--rpc)-r[RPC procedure table file]:rpc:_files' \
+'(-r)--rpc=[RPC procedure table file]:rpc:_files' \
+'--version[version]' \
+&& return 0
+;;
+news)
+_arguments -s -A "-*" \
+'(--datadir)-d[Root data storage path]:datadir:_dirs' \
+'(-d)--datadir=[Root data storage path]:datadir:_dirs' \
+'--group=[The name of a newsgroup to carry.]:group:_files' \
+'--help[Display this help and exit.]' \
+'(--interface)-i[Interface to which to bind]:interface:_files' \
+'(-i)--interface=[Interface to which to bind]:interface:_files' \
+'(--mailhost)-m[Host of SMTP server to use]:mailhost:_hosts' \
+'(-m)--mailhost=[Host of SMTP server to use]:mailhost:_hosts' \
+'--moderator=[The email of the moderator for the most recently passed group.]:moderator:_files' \
+'(--port)-p[Listen port]:port:_files' \
+'(-p)--port=[Listen port]:port:_files' \
+'--server=[The address of a Usenet server to pass messages to and receive messages from.]:server:_files' \
+'--subscription=[A newsgroup to list as a recommended subscription.]:subscription:_files' \
+'--version[version]' \
+&& return 0
+;;
+words)
+_arguments -s -A "-*" \
+'--group=[Specify a group which should exist]:group:_files' \
+'--help[Display this help and exit.]' \
+'--hostname=[Name of this server; purely an informative]:hostname:_files' \
+'--irc-port=[strports description of the port to bind for the irc server]:irc-port:_files' \
+'--passwd=[Name of a passwd-style password file. (REQUIRED)]:passwd:_files' \
+'--pb-port=[strports description of the port to bind for the pb server]:pb-port:_files' \
+'--version[version]' \
+&& return 0
+;;
+toc)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'(--port)-p[port]:port:_files' \
+'(-p)--port=[port]:port:_files' \
+'--version[version]' \
+&& return 0
+;;
+dns)
+_arguments -s -A "-*" \
+'--bindzone=[Specify the filename of a BIND9 syntax zone definition]:bindzone:_files' \
+'(--cache)-c[Enable record caching]' \
+'(-c)--cache[Enable record caching]' \
+'--help[Display this help and exit.]' \
+'--hosts-file=[Perform lookups with a hosts file]:hosts-file:_files' \
+'(--interface)-i[The interface to which to bind]:interface:_files' \
+'(-i)--interface=[The interface to which to bind]:interface:_files' \
+'(--port)-p[The port on which to listen]:port:_files' \
+'(-p)--port=[The port on which to listen]:port:_files' \
+'--pyzone=[Specify the filename of a Python syntax zone definition]:pyzone:_files' \
+'(--recursive)-r[Perform recursive lookups]' \
+'(-r)--recursive[Perform recursive lookups]' \
+'--resolv-conf=[Override location of resolv.conf (implies --recursive)]:resolv-conf:_files' \
+'--secondary=[Act as secondary for the specified domain, performing]:secondary:_files' \
+'(--verbose)-v[Log verbosely]' \
+'(-v)--verbose[Log verbosely]' \
+'--version[version]' \
+&& return 0
+;;
+mail)
+_arguments -s -A "-*" \
+'(--aliases)-A[Specify an aliases(5) file to use for this domain]:aliases:_files' \
+'(-A)--aliases=[Specify an aliases(5) file to use for this domain]:aliases:_files' \
+'(--bounce-to-postmaster)-b[undelivered mails are sent to the postmaster]' \
+'(-b)--bounce-to-postmaster[undelivered mails are sent to the postmaster]' \
+'(--certificate)-c[Certificate file to use for SSL connections]:certificate:_files' \
+'(-c)--certificate=[Certificate file to use for SSL connections]:certificate:_files' \
+'(--default)-D[Make the most recently specified domain the default domain.]' \
+'(-D)--default[Make the most recently specified domain the default domain.]' \
+'--disable-anonymous[Disallow non-authenticated SMTP connections]' \
+'(--esmtp)-E[Use RFC 1425/1869 SMTP extensions]' \
+'(-E)--esmtp[Use RFC 1425/1869 SMTP extensions]' \
+'--help[Display this help and exit.]' \
+'(--hostname)-H[The hostname by which to identify this server.]:hostname:_hosts' \
+'(-H)--hostname=[The hostname by which to identify this server.]:hostname:_hosts' \
+'(--maildirdbmdomain)-d[generate an SMTP/POP3 virtual domain which saves to "path"]:maildirdbmdomain:_files' \
+'(-d)--maildirdbmdomain=[generate an SMTP/POP3 virtual domain which saves to "path"]:maildirdbmdomain:_files' \
+'(--passwordfile)-P[Specify a file containing username:password login info for authenticated ESMTP connections.]:passwordfile:_files' \
+'(-P)--passwordfile=[Specify a file containing username:password login info for authenticated ESMTP connections.]:passwordfile:_files' \
+'(--pop3)-p[Port to start the POP3 server on (0 to disable).]:pop3:_files' \
+'(-p)--pop3=[Port to start the POP3 server on (0 to disable).]:pop3:_files' \
+'(--pop3s)-S[Port to start the POP3-over-SSL server on (0 to disable).]:pop3s:_files' \
+'(-S)--pop3s=[Port to start the POP3-over-SSL server on (0 to disable).]:pop3s:_files' \
+"(--relay)-R[Relay messages according to their envelope 'To', using the givenpath as a queue directory.]:relay:_files" \
+"(-R)--relay=[Relay messages according to their envelope 'To', using the givenpath as a queue directory.]:relay:_files" \
+'(--smtp)-s[Port to start the SMTP server on (0 to disable).]:smtp:_files' \
+'(-s)--smtp=[Port to start the SMTP server on (0 to disable).]:smtp:_files' \
+'(--user)-u[add a user/password to the last specified domains]:user:_files' \
+'(-u)--user=[add a user/password to the last specified domains]:user:_files' \
+'--version[version]' \
+&& return 0
+;;
+manhole)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'(--passwd)-p[name of a passwd(5)-format username/password file]:passwd:_files' \
+'(-p)--passwd=[name of a passwd(5)-format username/password file]:passwd:_files' \
+'(--sshPort)-s[strports description of the address on which to listen for ssh connections]:sshPort:_files' \
+'(-s)--sshPort=[strports description of the address on which to listen for ssh connections]:sshPort:_files' \
+'(--telnetPort)-t[strports description of the address on which to listen for telnet connections]:telnetPort:_files' \
+'(-t)--telnetPort=[strports description of the address on which to listen for telnet connections]:telnetPort:_files' \
+'--user=[user]:user:_files' \
+'--version[version]' \
+&& return 0
+;;
+conch)
+_arguments -s -A "-*" \
+'(--data)-d[directory to look for host keys in]:data:_dirs' \
+'(-d)--data=[directory to look for host keys in]:data:_dirs' \
+'--help[Display this help and exit.]' \
+'(--interface)-i[local interface to which we listen]:interface:_files' \
+'(-i)--interface=[local interface to which we listen]:interface:_files' \
+'--moduli=[directory to look for moduli in (if different from --data)]:moduli:_dirs' \
+'(--port)-p[Port on which to listen]:port:_files' \
+'(-p)--port=[Port on which to listen]:port:_files' \
+'--version[version]' \
+&& return 0
+;;
+*) _message "don't know how to complete $service";;
+esac \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_pyhtmlizer b/vendor/Twisted-10.0.0/twisted/python/zsh/_pyhtmlizer
new file mode 100644
index 0000000000..8bbfc83331
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_pyhtmlizer
@@ -0,0 +1,8 @@
+#compdef pyhtmlizer
+_arguments -s -A "-*" \
+"1:source python file:_files -g '*.py'" \
+'--help[Display this help and exit.]' \
+'(--stylesheet)-s[URL of stylesheet to link to.]:stylesheet:_files' \
+'(-s)--stylesheet=[URL of stylesheet to link to.]:stylesheet:_files' \
+'--version[version]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_tap2deb b/vendor/Twisted-10.0.0/twisted/python/zsh/_tap2deb
new file mode 100644
index 0000000000..4c5f6da57f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_tap2deb
@@ -0,0 +1,23 @@
+#compdef tap2deb
+_arguments -s -A "-*" \
+'(--debfile)-d[debfile]:debfile:_files' \
+'(-d)--debfile=[debfile]:debfile:_files' \
+'(--description)-e[description]:description:_files' \
+'(-e)--description=[description]:description:_files' \
+'--help[Display this help and exit.]' \
+'(--long_description)-l[long_description]:long_description:_files' \
+'(-l)--long_description=[long_description]:long_description:_files' \
+"(--maintainer)-m[The maintainer's name and email in a specific format: 'John Doe <johndoe@example.com>']:maintainer:_files" \
+"(-m)--maintainer=[The maintainer's name and email in a specific format: 'John Doe <johndoe@example.com>']:maintainer:_files" \
+'(--protocol)-p[protocol]:protocol:_files' \
+'(-p)--protocol=[protocol]:protocol:_files' \
+'(--set-version)-V[set-version]:set-version:_files' \
+'(-V)--set-version=[set-version]:set-version:_files' \
+'(--tapfile)-t[tapfile]:tapfile:_files' \
+'(-t)--tapfile=[tapfile]:tapfile:_files' \
+"(--type)-y[type of configuration: 'tap', 'xml, 'source' or 'python' for .tac files]:type:(tap xml source python)" \
+"(-y)--type=[type of configuration: 'tap', 'xml, 'source' or 'python' for .tac files]:type:(tap xml source python)" \
+'(--unsigned)-u[unsigned]' \
+'(-u)--unsigned[unsigned]' \
+'--version[version]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_tap2rpm b/vendor/Twisted-10.0.0/twisted/python/zsh/_tap2rpm
new file mode 100644
index 0000000000..e3f289f17c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_tap2rpm
@@ -0,0 +1,23 @@
+#compdef tap2rpm
+_arguments -s -A "-*" \
+'(--description)-e[description]:description:_files' \
+'(-e)--description=[description]:description:_files' \
+'--help[Display this help and exit.]' \
+'(--long_description)-l[long_description]:long_description:_files' \
+'(-l)--long_description=[long_description]:long_description:_files' \
+'(--maintainer)-m[maintainer]:maintainer:_files' \
+'(-m)--maintainer=[maintainer]:maintainer:_files' \
+'(--protocol)-p[protocol]:protocol:_files' \
+'(-p)--protocol=[protocol]:protocol:_files' \
+'(--rpmfile)-r[rpmfile]:rpmfile:_files -g "*.rpm"' \
+'(-r)--rpmfile=[rpmfile]:rpmfile:_files -g "*.rpm"' \
+'(--set-version)-V[set-version]:set-version:_files' \
+'(-V)--set-version=[set-version]:set-version:_files' \
+'(--tapfile)-t[tapfile]:tapfile:_files' \
+'(-t)--tapfile=[tapfile]:tapfile:_files' \
+"(--type)-y[type of configuration: 'tap', 'xml, 'source' or 'python']:type:(tap xml source python)" \
+"(-y)--type=[type of configuration: 'tap', 'xml, 'source' or 'python']:type:(tap xml source python)" \
+'(--unsigned)-u[unsigned]' \
+'(-u)--unsigned[unsigned]' \
+'--version[version]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_tapconvert b/vendor/Twisted-10.0.0/twisted/python/zsh/_tapconvert
new file mode 100644
index 0000000000..82d2facdf8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_tapconvert
@@ -0,0 +1,17 @@
+#compdef tapconvert
+_arguments -s -A "-*" \
+'(--decrypt)-d[The specified tap/aos/xml file is encrypted.]' \
+'(-d)--decrypt[The specified tap/aos/xml file is encrypted.]' \
+'(--encrypt)-e[Encrypt file before writing]' \
+'(-e)--encrypt[Encrypt file before writing]' \
+'--help[Display this help and exit.]' \
+'(--in)-i[The filename of the tap to read from]:tap file to read from:_files' \
+'(-i)--in=[The filename of the tap to read from]:tap file to read from:_files' \
+'(--out)-o[A filename to write the tap to]:tap file to write to:_files' \
+'(-o)--out=[A filename to write the tap to]:tap file to write to:_files' \
+"(--typein)-f[The format to use; this can be 'guess', 'python', 'pickle', 'xml', or 'source'.]:typein:(guess python pickle xml source)" \
+"(-f)--typein=[The format to use; this can be 'guess', 'python', 'pickle', 'xml', or 'source'.]:typein:(guess python pickle xml source)" \
+"(--typeout)-t[The output format to use; this can be 'pickle', 'xml', or 'source'.]:typeout:(pickle xml source)" \
+"(-t)--typeout=[The output format to use; this can be 'pickle', 'xml', or 'source'.]:typeout:(pickle xml source)" \
+'--version[version]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_tkconch b/vendor/Twisted-10.0.0/twisted/python/zsh/_tkconch
new file mode 100644
index 0000000000..81a16439d1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_tkconch
@@ -0,0 +1,38 @@
+#compdef tkconch
+_arguments -s -A "-*" \
+'1:host | user@host:{_ssh;if compset -P "*@"; then _wanted hosts expl "remote host name" _ssh_hosts && ret=0 elif compset -S "@*"; then _wanted users expl "login name" _ssh_users -S "" && ret=0 else if (( $+opt_args[-l] )); then tmp=() else tmp=( "users:login name:_ssh_users -qS@" ) fi; _alternative "hosts:remote host name:_ssh_hosts" "$tmp[@]" && ret=0 fi}' \
+'*:command: ' \
+'(--ansilog)-a[Print the receieved data to stdout]' \
+'(-a)--ansilog[Print the receieved data to stdout]' \
+'(--cipher)-c[Select encryption algorithm.]:cipher:(aes256-ctr aes256-cbc aes192-ctr aes192-cbc aes128-ctr aes128-cbc cast128-ctr cast128-cbc blowfish-ctr blowfish idea-ctridea-cbc 3des-ctr 3des-cbc)' \
+'(-c)--cipher=[Select encryption algorithm.]:cipher:(aes256-ctr aes256-cbc aes192-ctr aes192-cbc aes128-ctr aes128-cbc cast128-ctr cast128-cbc blowfish-ctr blowfish idea-ctridea-cbc 3des-ctr 3des-cbc)' \
+'(--compress)-C[Enable compression.]' \
+'(-C)--compress[Enable compression.]' \
+"(--escape)-e[Set escape character; \`\`none'' = disable]:escape:_files" \
+"(-e)--escape=[Set escape character; \`\`none'' = disable]:escape:_files" \
+'--help[Display this help and exit.]' \
+'(--identity)-i[Identity for public key authentication]:identity:_files' \
+'(-i)--identity=[Identity for public key authentication]:identity:_files' \
+'(--localforward)-L[listen-port:host:port Forward local port to remote address]:listen-port:host:port:_files' \
+'(-L)--localforward=[listen-port:host:port Forward local port to remote address]:listen-port:host:port:_files' \
+'(--log)-v[Log to stderr]' \
+'(-v)--log[Log to stderr]' \
+'(--macs)-m[Specify MAC algorithms for protocol version 2.]:macs:(hmac-sha1 hmac-md5)' \
+'(-m)--macs=[Specify MAC algorithms for protocol version 2.]:macs:(hmac-sha1 hmac-md5)' \
+'(--noshell)-N[Do not execute a shell or command.]' \
+'(-N)--noshell[Do not execute a shell or command.]' \
+'(--tty -t --notty)-T[Do not allocate a tty.]' \
+'(--tty -t -T)--notty[Do not allocate a tty.]' \
+'(--port)-p[Connect to this port. Server must be on the same port.]:port:_files' \
+'(-p)--port=[Connect to this port. Server must be on the same port.]:port:_files' \
+'(--remoteforward)-R[listen-port:host:port Forward remote port to local address]:listen-port:host:port:_files' \
+'(-R)--remoteforward=[listen-port:host:port Forward remote port to local address]:listen-port:host:port:_files' \
+'(--subsystem)-s[Invoke command (mandatory) as SSH2 subsystem.]' \
+'(-s)--subsystem[Invoke command (mandatory) as SSH2 subsystem.]' \
+'(--notty -T --tty)-t[Tty; allocate a tty even if command is given.]' \
+'(--notty -T -t)--tty[Tty; allocate a tty even if command is given.]' \
+'(--user)-l[Log in using this user name.]:user:_files' \
+'(-l)--user=[Log in using this user name.]:user:_files' \
+'(--version)-V[Display version number only.]' \
+'(-V)--version[Display version number only.]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_tkmktap b/vendor/Twisted-10.0.0/twisted/python/zsh/_tkmktap
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_tkmktap
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_trial b/vendor/Twisted-10.0.0/twisted/python/zsh/_trial
new file mode 100644
index 0000000000..01547aecb5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_trial
@@ -0,0 +1,40 @@
+#compdef trial
+_arguments -s -A "-*" \
+"*:file|module|package|TestCase|testMethod:_files -g '*.py'" \
+'--coverage[Generate coverage information in the given directory (relative to]' \
+"(--debug)-b[Run tests in the Python debugger. Will load '.pdbrc' from current directory if it exists.]" \
+"(-b)--debug[Run tests in the Python debugger. Will load '.pdbrc' from current directory if it exists.]" \
+'(--debug-stacktraces)-B[Report Deferred creation and callback stack traces]' \
+'(-B)--debug-stacktraces[Report Deferred creation and callback stack traces]' \
+'--disablegc[Disable the garbage collector]' \
+'(--dry-run)-n[do everything but run the tests]' \
+'(-n)--dry-run[do everything but run the tests]' \
+'(--extra)-x[Add an extra argument. (This is a hack necessary for interfacing with]:extra:_files' \
+'(-x)--extra=[Add an extra argument. (This is a hack necessary for interfacing with]:extra:_files' \
+'--force-gc[Have Trial run gc.collect() before and after each test case.]' \
+'(--help)-h[Display this help and exit.]' \
+'(-h)--help[Display this help and exit.]' \
+'--help-reactors[Display a list of possibly available reactor names.]' \
+'--help-reporters[Help on available output plugins (reporters)]' \
+'(--logfile)-l[log file name]:log file name:_files' \
+'(-l)--logfile=[log file name]:log file name:_files' \
+"(--no-recurse)-N[Don't recurse into packages]" \
+"(-N)--no-recurse[Don't recurse into packages]" \
+"--nopm[don't automatically jump into debugger for postmorteming of exceptions]" \
+'--profile[Run tests under the Python profiler]' \
+'(--random)-z[Run tests in random order using the specified seed]:random seed:_files' \
+'(-z)--random=[Run tests in random order using the specified seed]:random seed:_files' \
+'(--reactor)-r[Which reactor to use (see --help-reactors for a list of possibilities)]:reactor:(kqueue win32 epoll iocp gtk cf gtk2 default debug-gui poll glib2 select wx)' \
+'(-r)--reactor=[Which reactor to use (see --help-reactors for a list of possibilities)]:reactor:(kqueue win32 epoll iocp gtk cf gtk2 default debug-gui poll glib2 select wx)' \
+'--recursionlimit=[see sys.setrecursionlimit()]:recursionlimit:_files' \
+'--reporter=[The reporter to use for this test run. See --help-reporters for more info.]:reporter:(bwverbose text verbose timing summary)' \
+'(--rterrors)-e[realtime errors, print out tracebacks as soon as they occur]' \
+'(-e)--rterrors[realtime errors, print out tracebacks as soon as they occur]' \
+'--spew[Print an insanely verbose log of everything that happens. Useful]' \
+'--tbformat=[Specify the format to display tracebacks with. Valid formats are]:tbformat:(plain emacs cgitb)' \
+'--temp-directory=[Path to use as working directory for tests.]:temp-directory:_files' \
+'--testmodule=[Filename to grep for test cases (-*- test-case-name)]:testmodule:_files' \
+'(--until-failure)-u[Repeat test until it fails]' \
+'(-u)--until-failure[Repeat test until it fails]' \
+'--version[version]' \
+&& return 0
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_twistd b/vendor/Twisted-10.0.0/twisted/python/zsh/_twistd
new file mode 100644
index 0000000000..ed9edcd5b3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_twistd
@@ -0,0 +1,328 @@
+#compdef twistd
+local _zsh_subcmds_array
+_zsh_subcmds_array=(
+"web2:An HTTP/1.1 web server that can serve from a filesystem or application resource."
+"ftp:An FTP server."
+"telnet:A simple, telnet-based remote debugging service."
+"socks:A SOCKSv4 proxy service."
+"manhole-old:An interactive remote debugger service."
+"portforward:A simple port-forwarder."
+"web:A general-purpose web server which can serve from a filesystem or application resource."
+"inetd:An inetd(8) replacement."
+"news:A news server."
+"words:A modern words server"
+"toc:An AIM TOC service."
+"dns:A domain name server."
+"mail:An email service"
+"manhole:An interactive remote debugger service accessible via telnet and ssh and providing syntax coloring and basic line editing functionality."
+"conch:A Conch SSH service."
+)
+
+_arguments -s -A "-*" \
+'*::subcmd:->subcmd' \
+'--chroot=[Chroot to a supplied directory before running]:chroot directory:_dirs' \
+'(--debug)-b[run the application in the Python Debugger (implies nodaemon),]' \
+'(-b)--debug[run the application in the Python Debugger (implies nodaemon),]' \
+'(--encrypted)-e[The specified tap/aos/xml file is encrypted.]' \
+'(-e)--encrypted[The specified tap/aos/xml file is encrypted.]' \
+'--euid[Set only effective user-id rather than real user-id. (This option has no effect unless the server is running as root, in which case it means not to shed all privileges after binding ports, retaining the option to regain privileges in cases such as spawning processes. Use with caution.)]' \
+'(--python --xml --source -y -x -s --file)-f[read the given .tap file]:file:_files -g "*.tap"' \
+'(--python --xml --source -y -x -s -f)--file=[read the given .tap file]:file:_files -g "*.tap"' \
+'(--gid)-g[The gid to run as.]:gid:_files' \
+'(-g)--gid=[The gid to run as.]:gid:_files' \
+'--help[Display this help and exit.]' \
+'--help-reactors[Display a list of possibly available reactor names.]' \
+'(--logfile)-l[log to a specified file, - for stdout]:logfile:_files' \
+'(-l)--logfile=[log to a specified file, - for stdout]:logfile:_files' \
+'(--no_save)-o[do not save state on shutdown]' \
+'(-o)--no_save[do not save state on shutdown]' \
+"(--nodaemon)-n[don't daemonize]" \
+"(-n)--nodaemon[don't daemonize]" \
+"--nothotshot[Don't use the 'hotshot' profiler even if it's available.]" \
+"--originalname[Don't try to change the process name]" \
+'--pidfile=[Name of the pidfile (default: twistd.pid)]:pidfile:_files -g "*.pid"' \
+'--prefix=[Use the given prefix when syslogging (default: twisted)]:prefix:_files' \
+'(--profile)-p[Run in profile mode, dumping results to specified file]:profile:_files' \
+'(-p)--profile=[Run in profile mode, dumping results to specified file]:profile:_files' \
+'(--file --xml --source -f -x -s --python)-y[read an application from within a Python file (implies -o)]:python:_files -g "*.(tac|py)"' \
+'(--file --xml --source -f -x -s -y)--python=[read an application from within a Python file (implies -o)]:python:_files -g "*.(tac|py)"' \
+'(--quiet)-q[No-op for backwards compatability.]' \
+'(-q)--quiet[No-op for backwards compatability.]' \
+'(--reactor)-r[Which reactor to use (see --help-reactors for a list of possibilities)]:reactor:(kqueue win32 epoll iocp gtk cf gtk2 default debug-gui poll glib2 select wx)' \
+'(-r)--reactor=[Which reactor to use (see --help-reactors for a list of possibilities)]:reactor:(kqueue win32 epoll iocp gtk cf gtk2 default debug-gui poll glib2 select wx)' \
+'--report-profile=[E-mail address to use when reporting dynamic execution profiler stats. This should not be combined with other profiling options. This will only take effect if the application to be run has an application name.]:report-profile:_files' \
+'(--rundir)-d[Change to a supplied directory before running]:rundir:_dirs' \
+'(-d)--rundir=[Change to a supplied directory before running]:rundir:_dirs' \
+'--savestats[save the Stats object rather than the text output of the profiler.]' \
+'(--file --python --xml -f -y -x --source)-s[Read an application from a .tas file (AOT format).]:source:_files -g "*.tas"' \
+'(--file --python --xml -f -y -x -s)--source=[Read an application from a .tas file (AOT format).]:source:_files -g "*.tas"' \
+'--spew[Print an insanely verbose log of everything that happens.]' \
+'--syslog[Log to syslog, not to file]' \
+'(--uid)-u[The uid to run as.]:uid:_files' \
+'(-u)--uid=[The uid to run as.]:uid:_files' \
+'--version[Print version information and exit.]' \
+'(--file --python --source -f -y -s --xml)-x[Read an application from a .tax file (Marmalade format).]:xml:_files -g "*.tax"' \
+'(--file --python --source -f -y -s -x)--xml=[Read an application from a .tax file (Marmalade format).]:xml:_files -g "*.tax"' \
+&& return 0
+if (( CURRENT == 1 )); then
+ _describe "service to run" _zsh_subcmds_array && ret=0
+fi
+(( ret )) || return 0
+
+service="$words[1]"
+
+case $service in
+web2)
+_arguments -s -A "-*" \
+"--allow-ignore-ext[Specify whether or not a request for 'foo' should return 'foo.ext']" \
+"(--certificate)-c[SSL certificate to use for HTTPS.]:certificate:_files -g '*.pem'" \
+"(-c)--certificate=[SSL certificate to use for HTTPS.]:certificate:_files -g '*.pem'" \
+'--class=[A class that will be used to serve the root resource. Must implement twisted.web2.iweb.IResource and take no arguments.]:class:_files' \
+'--dav=[A path that will be used to serve the root resource as a DAV Collection.]:dav:_files' \
+'--help[Display this help and exit.]' \
+'--https=[Port to listen on for Secure HTTP.]:https:_files' \
+'--ignore-ext=[Specify an extension to ignore. These will be processed in order.]:ignore-ext:_files' \
+'(--index)-i[Add the name of a file used to check for directory indexes.]:index:_files' \
+'(-i)--index=[Add the name of a file used to check for directory indexes.]:index:_files' \
+'(--logfile)-l[Common Access Logging Format file to write to if unspecified access log information will be written to the standard twisted log file.]:logfile:_files' \
+'(-l)--logfile=[Common Access Logging Format file to write to if unspecified access log information will be written to the standard twisted log file.]:logfile:_files' \
+"--mimetype=[Mapping from file extension to MIME Type in the form of 'ext=type'.]:mimetype:_files" \
+'--path=[A path that will be used to serve the root resource as a raw file]:path:_files' \
+'(--port)-p[Port to start the server on.]:port:_files' \
+'(-p)--port=[Port to start the server on.]:port:_files' \
+"(--privkey)-k[SSL certificate to use for HTTPS.]:privkey:_files -g '*.pem'" \
+"(-k)--privkey=[SSL certificate to use for HTTPS.]:privkey:_files -g '*.pem'" \
+"--processor=[\`ext=class' where \`class' is added as a Processor for files ending]:processor:_files" \
+'--version[version]' \
+'--vhost-class=[Specify a virtual host in the form of domain=class,]:vhost-class:_files' \
+'--vhost-dav=[Specify a virtual host in the form of domain=path,]:vhost-dav:_files' \
+'--vhost-path=[Specify a directory to use for automatic named virtual hosts.]:vhost-path:_files' \
+'--vhost-static=[Specify a virtual host in the form of domain=path to be served as]:vhost-static:_files' \
+&& return 0
+;;
+ftp)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'--password-file=[username:password-style credentials database]:password-file:_files' \
+'(--port)-p[set the port number]:port:_files' \
+'(-p)--port=[set the port number]:port:_files' \
+'(--root)-r[define the root of the ftp-site.]:root:_files' \
+'(-r)--root=[define the root of the ftp-site.]:root:_files' \
+'--userAnonymous=[Name of the anonymous user.]:userAnonymous:_files' \
+'--version[version]' \
+&& return 0
+;;
+telnet)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'(--password)-w[set the password]:password:_files' \
+'(-w)--password=[set the password]:password:_files' \
+'(--port)-p[port to listen on]:port:_files' \
+'(-p)--port=[port to listen on]:port:_files' \
+'(--username)-u[set the login username]:username:_users' \
+'(-u)--username=[set the login username]:username:_users' \
+'--version[version]' \
+&& return 0
+;;
+socks)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'(--interface)-i[local interface to which we listen]:interface:_files' \
+'(-i)--interface=[local interface to which we listen]:interface:_files' \
+"(--log)-l[file to log connection data to]:log:_files -g '*.log'" \
+"(-l)--log=[file to log connection data to]:log:_files -g '*.log'" \
+'(--port)-p[Port on which to listen]:port:_files' \
+'(-p)--port=[Port on which to listen]:port:_files' \
+'--version[version]' \
+&& return 0
+;;
+manhole-old)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+"(--password)-w[Required. '-' will prompt or read a password from stdin.]:password:_files" \
+"(-w)--password=[Required. '-' will prompt or read a password from stdin.]:password:_files" \
+'(--port)-p[Port to listen on]:port:_files' \
+'(-p)--port=[Port to listen on]:port:_files' \
+'(--tracebacks)-T[Allow tracebacks to be sent over the network]' \
+'(-T)--tracebacks[Allow tracebacks to be sent over the network]' \
+'(--user)-u[Name of user to allow to log in]:user:_users' \
+'(-u)--user=[Name of user to allow to log in]:user:_users' \
+'--version[version]' \
+&& return 0
+;;
+portforward)
+_arguments -s -A "-*" \
+'(--dest_port)-d[Set the destination port.]:dest_port:_files' \
+'(-d)--dest_port=[Set the destination port.]:dest_port:_files' \
+'--help[Display this help and exit.]' \
+'(--host)-h[Set the host.]:host:_hosts' \
+'(-h)--host=[Set the host.]:host:_hosts' \
+'(--port)-p[Set the port number.]:port:_files' \
+'(-p)--port=[Set the port number.]:port:_files' \
+'--version[version]' \
+&& return 0
+;;
+web)
+_arguments -s -A "-*" \
+"--allow-ignore-ext[Specify whether or not a request for 'foo' should return 'foo.ext']" \
+"(--certificate)-c[SSL certificate to use for HTTPS. ]:certificate:_files -g '*.pem'" \
+"(-c)--certificate=[SSL certificate to use for HTTPS. ]:certificate:_files -g '*.pem'" \
+'--class=[Create a Resource subclass with a zero-argument constructor.]:class:_files' \
+'--flashconduit=[Start a flashconduit on the specified port.]:flashconduit:_files' \
+'--help[Display this help and exit.]' \
+'--https=[Port to listen on for Secure HTTP.]:https:_files' \
+'--ignore-ext=[Specify an extension to ignore. These will be processed in order.]:ignore-ext:_files' \
+'(--index)-i[Add the name of a file used to check for directory indexes.]:index:_files' \
+'(-i)--index=[Add the name of a file used to check for directory indexes.]:index:_files' \
+"(--logfile)-l[Path to web CLF (Combined Log Format) log file.]:logfile:_files -g '*.log'" \
+"(-l)--logfile=[Path to web CLF (Combined Log Format) log file.]:logfile:_files -g '*.log'" \
+'(--mime-type)-m[Specify the default mime-type for static files.]:mime-type:_files' \
+'(-m)--mime-type=[Specify the default mime-type for static files.]:mime-type:_files' \
+'(--notracebacks)-n[Do not display tracebacks in broken web pages. Displaying tracebacks to users may be security risk!]' \
+'(-n)--notracebacks[Do not display tracebacks in broken web pages. Displaying tracebacks to users may be security risk!]' \
+'--path=[<path> is either a specific file or a directory to]:path:_files' \
+'--personal[Instead of generating a webserver, generate a ResourcePublisher which listens on ~/.twistd-web-pb]' \
+'(--port)-p[Port to start the server on.]:port:_files' \
+'(-p)--port=[Port to start the server on.]:port:_files' \
+"(--privkey)-k[SSL certificate to use for HTTPS.]:privkey:_files -g '*.pem'" \
+"(-k)--privkey=[SSL certificate to use for HTTPS.]:privkey:_files -g '*.pem'" \
+"--processor=[\`ext=class' where \`class' is added as a Processor for files ending]:processor:_files" \
+'--resource-script=[An .rpy file to be used as the root resource of the webserver.]:resource-script:_files' \
+'(--static)-s[Same as --path, this is deprecated and will be removed in a]:static:_files' \
+'(-s)--static=[Same as --path, this is deprecated and will be removed in a]:static:_files' \
+'(--user)-u[Makes a server with ~/public_html and ~/.twistd-web-pb support for]' \
+'(-u)--user[Makes a server with ~/public_html and ~/.twistd-web-pb support for]' \
+'--version[version]' \
+&& return 0
+;;
+inetd)
+_arguments -s -A "-*" \
+"(--file)-f[Service configuration file]:file:_files -g '*.conf'" \
+"(-f)--file=[Service configuration file]:file:_files -g '*.conf'" \
+'--help[Display this help and exit.]' \
+"(--nointernal)-i[Don't run internal services]" \
+"(-i)--nointernal[Don't run internal services]" \
+'(--rpc)-r[RPC procedure table file]:rpc:_files' \
+'(-r)--rpc=[RPC procedure table file]:rpc:_files' \
+'--version[version]' \
+&& return 0
+;;
+news)
+_arguments -s -A "-*" \
+'(--datadir)-d[Root data storage path]:datadir:_dirs' \
+'(-d)--datadir=[Root data storage path]:datadir:_dirs' \
+'--group=[The name of a newsgroup to carry.]:group:_files' \
+'--help[Display this help and exit.]' \
+'(--interface)-i[Interface to which to bind]:interface:_files' \
+'(-i)--interface=[Interface to which to bind]:interface:_files' \
+'(--mailhost)-m[Host of SMTP server to use]:mailhost:_hosts' \
+'(-m)--mailhost=[Host of SMTP server to use]:mailhost:_hosts' \
+'--moderator=[The email of the moderator for the most recently passed group.]:moderator:_files' \
+'(--port)-p[Listen port]:port:_files' \
+'(-p)--port=[Listen port]:port:_files' \
+'--server=[The address of a Usenet server to pass messages to and receive messages from.]:server:_files' \
+'--subscription=[A newsgroup to list as a recommended subscription.]:subscription:_files' \
+'--version[version]' \
+&& return 0
+;;
+words)
+_arguments -s -A "-*" \
+'--group=[Specify a group which should exist]:group:_files' \
+'--help[Display this help and exit.]' \
+'--hostname=[Name of this server; purely an informative]:hostname:_files' \
+'--irc-port=[strports description of the port to bind for the irc server]:irc-port:_files' \
+'--passwd=[Name of a passwd-style password file. (REQUIRED)]:passwd:_files' \
+'--pb-port=[strports description of the port to bind for the pb server]:pb-port:_files' \
+'--version[version]' \
+&& return 0
+;;
+toc)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'(--port)-p[port]:port:_files' \
+'(-p)--port=[port]:port:_files' \
+'--version[version]' \
+&& return 0
+;;
+dns)
+_arguments -s -A "-*" \
+'--bindzone=[Specify the filename of a BIND9 syntax zone definition]:bindzone:_files' \
+'(--cache)-c[Enable record caching]' \
+'(-c)--cache[Enable record caching]' \
+'--help[Display this help and exit.]' \
+'--hosts-file=[Perform lookups with a hosts file]:hosts-file:_files' \
+'(--interface)-i[The interface to which to bind]:interface:_files' \
+'(-i)--interface=[The interface to which to bind]:interface:_files' \
+'(--port)-p[The port on which to listen]:port:_files' \
+'(-p)--port=[The port on which to listen]:port:_files' \
+'--pyzone=[Specify the filename of a Python syntax zone definition]:pyzone:_files' \
+'(--recursive)-r[Perform recursive lookups]' \
+'(-r)--recursive[Perform recursive lookups]' \
+'--resolv-conf=[Override location of resolv.conf (implies --recursive)]:resolv-conf:_files' \
+'--secondary=[Act as secondary for the specified domain, performing]:secondary:_files' \
+'(--verbose)-v[Log verbosely]' \
+'(-v)--verbose[Log verbosely]' \
+'--version[version]' \
+&& return 0
+;;
+mail)
+_arguments -s -A "-*" \
+'(--aliases)-A[Specify an aliases(5) file to use for this domain]:aliases:_files' \
+'(-A)--aliases=[Specify an aliases(5) file to use for this domain]:aliases:_files' \
+'(--bounce-to-postmaster)-b[undelivered mails are sent to the postmaster]' \
+'(-b)--bounce-to-postmaster[undelivered mails are sent to the postmaster]' \
+'(--certificate)-c[Certificate file to use for SSL connections]:certificate:_files' \
+'(-c)--certificate=[Certificate file to use for SSL connections]:certificate:_files' \
+'(--default)-D[Make the most recently specified domain the default domain.]' \
+'(-D)--default[Make the most recently specified domain the default domain.]' \
+'--disable-anonymous[Disallow non-authenticated SMTP connections]' \
+'(--esmtp)-E[Use RFC 1425/1869 SMTP extensions]' \
+'(-E)--esmtp[Use RFC 1425/1869 SMTP extensions]' \
+'--help[Display this help and exit.]' \
+'(--hostname)-H[The hostname by which to identify this server.]:hostname:_hosts' \
+'(-H)--hostname=[The hostname by which to identify this server.]:hostname:_hosts' \
+'(--maildirdbmdomain)-d[generate an SMTP/POP3 virtual domain which saves to "path"]:maildirdbmdomain:_files' \
+'(-d)--maildirdbmdomain=[generate an SMTP/POP3 virtual domain which saves to "path"]:maildirdbmdomain:_files' \
+'(--passwordfile)-P[Specify a file containing username:password login info for authenticated ESMTP connections.]:passwordfile:_files' \
+'(-P)--passwordfile=[Specify a file containing username:password login info for authenticated ESMTP connections.]:passwordfile:_files' \
+'(--pop3)-p[Port to start the POP3 server on (0 to disable).]:pop3:_files' \
+'(-p)--pop3=[Port to start the POP3 server on (0 to disable).]:pop3:_files' \
+'(--pop3s)-S[Port to start the POP3-over-SSL server on (0 to disable).]:pop3s:_files' \
+'(-S)--pop3s=[Port to start the POP3-over-SSL server on (0 to disable).]:pop3s:_files' \
+"(--relay)-R[Relay messages according to their envelope 'To', using the givenpath as a queue directory.]:relay:_files" \
+"(-R)--relay=[Relay messages according to their envelope 'To', using the givenpath as a queue directory.]:relay:_files" \
+'(--smtp)-s[Port to start the SMTP server on (0 to disable).]:smtp:_files' \
+'(-s)--smtp=[Port to start the SMTP server on (0 to disable).]:smtp:_files' \
+'(--user)-u[add a user/password to the last specified domains]:user:_files' \
+'(-u)--user=[add a user/password to the last specified domains]:user:_files' \
+'--version[version]' \
+&& return 0
+;;
+manhole)
+_arguments -s -A "-*" \
+'--help[Display this help and exit.]' \
+'(--passwd)-p[name of a passwd(5)-format username/password file]:passwd:_files' \
+'(-p)--passwd=[name of a passwd(5)-format username/password file]:passwd:_files' \
+'(--sshPort)-s[strports description of the address on which to listen for ssh connections]:sshPort:_files' \
+'(-s)--sshPort=[strports description of the address on which to listen for ssh connections]:sshPort:_files' \
+'(--telnetPort)-t[strports description of the address on which to listen for telnet connections]:telnetPort:_files' \
+'(-t)--telnetPort=[strports description of the address on which to listen for telnet connections]:telnetPort:_files' \
+'--user=[user]:user:_files' \
+'--version[version]' \
+&& return 0
+;;
+conch)
+_arguments -s -A "-*" \
+'(--data)-d[directory to look for host keys in]:data:_dirs' \
+'(-d)--data=[directory to look for host keys in]:data:_dirs' \
+'--help[Display this help and exit.]' \
+'(--interface)-i[local interface to which we listen]:interface:_files' \
+'(-i)--interface=[local interface to which we listen]:interface:_files' \
+'--moduli=[directory to look for moduli in (if different from --data)]:moduli:_dirs' \
+'(--port)-p[Port on which to listen]:port:_files' \
+'(-p)--port=[Port on which to listen]:port:_files' \
+'--version[version]' \
+&& return 0
+;;
+*) _message "don't know how to complete $service";;
+esac \ No newline at end of file
diff --git a/vendor/Twisted-10.0.0/twisted/python/zsh/_websetroot b/vendor/Twisted-10.0.0/twisted/python/zsh/_websetroot
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zsh/_websetroot
diff --git a/vendor/Twisted-10.0.0/twisted/python/zshcomp.py b/vendor/Twisted-10.0.0/twisted/python/zshcomp.py
new file mode 100644
index 0000000000..4910cfd59f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/python/zshcomp.py
@@ -0,0 +1,780 @@
+# -*- test-case-name: twisted.test.test_zshcomp -*-
+# Copyright (c) 2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Rebuild the completion functions for the currently active version of Twisted::
+ $ python zshcomp.py -i
+
+This module implements a zsh code generator which generates completion code for
+commands that use twisted.python.usage. This is the stuff that makes pressing
+Tab at the command line work.
+
+Maintainer: Eric Mangold
+
+To build completion functions for your own commands, and not Twisted commands,
+then just do something like this::
+
+ o = mymodule.MyOptions()
+ f = file('_mycommand', 'w')
+ Builder("mycommand", o, f).write()
+
+Then all you have to do is place the generated file somewhere in your
+C{$fpath}, and restart zsh. Note the "site-functions" directory in your
+C{$fpath} where you may install 3rd-party completion functions (like the one
+you're building). Call C{siteFunctionsPath} to locate this directory
+programmatically.
+
+SPECIAL CLASS VARIABLES. You may set these on your usage.Options subclass::
+
+ zsh_altArgDescr
+ zsh_multiUse
+ zsh_mutuallyExclusive
+ zsh_actions
+ zsh_actionDescr
+ zsh_extras
+
+Here is what they mean (with examples)::
+
+ zsh_altArgDescr = {"foo":"use this description for foo instead"}
+ A dict mapping long option names to alternate descriptions. When this
+ variable is present, the descriptions contained here will override
+ those descriptions provided in the optFlags and optParameters
+ variables.
+
+ zsh_multiUse = ["foo", "bar"]
+ A sequence containing those long option names which may appear on the
+ command line more than once. By default, options will only be completed
+ one time.
+
+ zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
+ A sequence of sequences, with each sub-sequence containing those long
+ option names that are mutually exclusive. That is, those options that
+ cannot appear on the command line together.
+
+ zsh_actions = {"foo":'_files -g "*.foo"', "bar":"(one two three)",
+ "colors":"_values -s , 'colors to use' red green blue"}
+ A dict mapping long option names to Zsh "actions". These actions
+ define what will be completed as the argument to the given option. By
+ default, all files/dirs will be completed if no action is given.
+
+ Callables may instead be given for the values in this dict. The
+ callable should accept no arguments, and return a string that will be
+ used as the zsh "action" in the same way as the literal strings in the
+ examples above.
+
+ As you can see in the example above. The "foo" option will have files
+ that end in .foo completed when the user presses Tab. The "bar"
+ option will have either of the strings "one", "two", or "three"
+ completed when the user presses Tab.
+
+ "colors" will allow multiple arguments to be completed, seperated by
+ commas. The possible arguments are red, green, and blue. Examples::
+
+ my_command --foo some-file.foo --colors=red,green
+ my_command --colors=green
+ my_command --colors=green,blue
+
+ Actions may take many forms, and it is beyond the scope of this
+ document to illustrate them all. Please refer to the documention for
+ the Zsh _arguments function. zshcomp is basically a front-end to Zsh's
+ _arguments completion function.
+
+ That documentation is available on the zsh web site at this URL:
+ U{http://zsh.sunsite.dk/Doc/Release/zsh_19.html#SEC124}
+
+ zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
+ A dict mapping long option names to a description for the corresponding
+ zsh "action". These descriptions are show above the generated matches
+ when the user is doing completions for this option.
+
+ Normally Zsh does not show these descriptions unless you have
+ "verbose" completion turned on. Turn on verbosity with this in your
+ ~/.zshrc::
+
+ zstyle ':completion:*' verbose yes
+ zstyle ':completion:*:descriptions' format '%B%d%b'
+
+ zsh_extras = [":file to read from:action", ":file to write to:action"]
+ A sequence of extra arguments that will be passed verbatim to Zsh's
+ _arguments completion function. The _arguments function does all the
+ hard work of doing command line completions. You can see how zshcomp
+ invokes the _arguments call by looking at the generated completion
+ files that this module creates.
+
+ *** NOTE ***
+
+ You will need to use this variable to describe completions for normal
+ command line arguments. That is, those arguments that are not
+ associated with an option. That is, the arguments that are given to the
+ parseArgs method of your usage.Options subclass.
+
+ In the example above, the 1st non-option argument will be described as
+ "file to read from" and completion options will be generated in
+ accordance with the "action". (See above about zsh "actions") The
+ 2nd non-option argument will be described as "file to write to" and
+ the action will be interpreted likewise.
+
+ Things you can put here are all documented under the _arguments
+ function here: U{http://zsh.sunsite.dk/Doc/Release/zsh_19.html#SEC124}
+
+Zsh Notes:
+
+To enable advanced completion add something like this to your ~/.zshrc::
+
+ autoload -U compinit
+ compinit
+
+For some extra verbosity, and general niceness add these lines too::
+
+ zstyle ':completion:*' verbose yes
+ zstyle ':completion:*:descriptions' format '%B%d%b'
+ zstyle ':completion:*:messages' format '%d'
+ zstyle ':completion:*:warnings' format 'No matches for: %d'
+
+Have fun!
+"""
+import itertools, sys, commands, os.path
+
+from twisted.python import reflect, util, usage
+from twisted.scripts.mktap import IServiceMaker
+
+class MyOptions(usage.Options):
+ """
+ Options for this file
+ """
+ longdesc = ""
+ synopsis = "Usage: python zshcomp.py [--install | -i] | <output directory>"
+ optFlags = [["install", "i",
+ 'Output files to the "installation" directory ' \
+ '(twisted/python/zsh in the currently active ' \
+ 'Twisted package)']]
+ optParameters = [["directory", "d", None,
+ "Output files to this directory"]]
+ def postOptions(self):
+ if self['install'] and self['directory']:
+ raise usage.UsageError, "Can't have --install and " \
+ "--directory at the same time"
+ if not self['install'] and not self['directory']:
+ raise usage.UsageError, "Not enough arguments"
+ if self['directory'] and not os.path.isdir(self['directory']):
+ raise usage.UsageError, "%s is not a directory" % self['directory']
+
+class Builder:
+ def __init__(self, cmd_name, options, file):
+ """
+ @type cmd_name: C{str}
+ @param cmd_name: The name of the command
+
+ @type options: C{twisted.usage.Options}
+ @param options: The C{twisted.usage.Options} instance defined for
+ this command
+
+ @type file: C{file}
+ @param file: The C{file} to write the completion function to
+ """
+
+ self.cmd_name = cmd_name
+ self.options = options
+ self.file = file
+
+ def write(self):
+ """
+ Write the completion function to the file given to __init__
+ @return: C{None}
+ """
+ # by default, we just write out a single call to _arguments
+ self.file.write('#compdef %s\n' % (self.cmd_name,))
+ gen = ArgumentsGenerator(self.cmd_name, self.options, self.file)
+ gen.write()
+
+class SubcommandBuilder(Builder):
+ """
+ Use this builder for commands that have sub-commands. twisted.python.usage
+ has the notion of sub-commands that are defined using an entirely seperate
+ Options class.
+ """
+ interface = None
+ subcmdLabel = None
+
+ def write(self):
+ """
+ Write the completion function to the file given to __init__
+ @return: C{None}
+ """
+ self.file.write('#compdef %s\n' % (self.cmd_name,))
+ self.file.write('local _zsh_subcmds_array\n_zsh_subcmds_array=(\n')
+ from twisted import plugin as newplugin
+ plugins = newplugin.getPlugins(self.interface)
+
+ for p in plugins:
+ self.file.write('"%s:%s"\n' % (p.tapname, p.description))
+ self.file.write(")\n\n")
+
+ self.options.__class__.zsh_extras = ['*::subcmd:->subcmd']
+ gen = ArgumentsGenerator(self.cmd_name, self.options, self.file)
+ gen.write()
+
+ self.file.write("""if (( CURRENT == 1 )); then
+ _describe "%s" _zsh_subcmds_array && ret=0
+fi
+(( ret )) || return 0
+
+service="$words[1]"
+
+case $service in\n""" % (self.subcmdLabel,))
+
+ plugins = newplugin.getPlugins(self.interface)
+ for p in plugins:
+ self.file.write(p.tapname + ")\n")
+ gen = ArgumentsGenerator(p.tapname, p.options(), self.file)
+ gen.write()
+ self.file.write(";;\n")
+ self.file.write("*) _message \"don't know how to" \
+ " complete $service\";;\nesac")
+
+class MktapBuilder(SubcommandBuilder):
+ """
+ Builder for the mktap command
+ """
+ interface = IServiceMaker
+ subcmdLabel = 'tap to build'
+
+class TwistdBuilder(SubcommandBuilder):
+ """
+ Builder for the twistd command
+ """
+ interface = IServiceMaker
+ subcmdLabel = 'service to run'
+
+class ArgumentsGenerator:
+ """
+ Generate a call to the zsh _arguments completion function
+ based on data in a usage.Options subclass
+ """
+ def __init__(self, cmd_name, options, file):
+ """
+ @type cmd_name: C{str}
+ @param cmd_name: The name of the command
+
+ @type options: C{twisted.usage.Options}
+ @param options: The C{twisted.usage.Options} instance defined
+ for this command
+
+ @type file: C{file}
+ @param file: The C{file} to write the completion function to
+ """
+ self.cmd_name = cmd_name
+ self.options = options
+ self.file = file
+
+ self.altArgDescr = {}
+ self.actionDescr = {}
+ self.multiUse = []
+ self.mutuallyExclusive = []
+ self.actions = {}
+ self.extras = []
+
+ aCL = reflect.accumulateClassList
+ aCD = reflect.accumulateClassDict
+
+ aCD(options.__class__, 'zsh_altArgDescr', self.altArgDescr)
+ aCD(options.__class__, 'zsh_actionDescr', self.actionDescr)
+ aCL(options.__class__, 'zsh_multiUse', self.multiUse)
+ aCL(options.__class__, 'zsh_mutuallyExclusive',
+ self.mutuallyExclusive)
+ aCD(options.__class__, 'zsh_actions', self.actions)
+ aCL(options.__class__, 'zsh_extras', self.extras)
+
+ optFlags = []
+ optParams = []
+
+ aCL(options.__class__, 'optFlags', optFlags)
+ aCL(options.__class__, 'optParameters', optParams)
+
+ for i, optList in enumerate(optFlags):
+ if len(optList) != 3:
+ optFlags[i] = util.padTo(3, optList)
+
+ for i, optList in enumerate(optParams):
+ if len(optList) != 4:
+ optParams[i] = util.padTo(4, optList)
+
+
+ self.optFlags = optFlags
+ self.optParams = optParams
+
+ optParams_d = {}
+ for optList in optParams:
+ optParams_d[optList[0]] = optList[1:]
+ self.optParams_d = optParams_d
+
+ optFlags_d = {}
+ for optList in optFlags:
+ optFlags_d[optList[0]] = optList[1:]
+ self.optFlags_d = optFlags_d
+
+ optAll_d = {}
+ optAll_d.update(optParams_d)
+ optAll_d.update(optFlags_d)
+ self.optAll_d = optAll_d
+
+ self.addAdditionalOptions()
+
+ # makes sure none of the zsh_ data structures reference option
+ # names that don't exist. (great for catching typos)
+ self.verifyZshNames()
+
+ self.excludes = self.makeExcludesDict()
+
+ def write(self):
+ """
+ Write the zsh completion code to the file given to __init__
+ @return: C{None}
+ """
+ self.writeHeader()
+ self.writeExtras()
+ self.writeOptions()
+ self.writeFooter()
+
+ def writeHeader(self):
+ """
+ This is the start of the code that calls _arguments
+ @return: C{None}
+ """
+ self.file.write('_arguments -s -A "-*" \\\n')
+
+ def writeOptions(self):
+ """
+ Write out zsh code for each option in this command
+ @return: C{None}
+ """
+ optNames = self.optAll_d.keys()
+ optNames.sort()
+ for long in optNames:
+ self.writeOpt(long)
+
+ def writeExtras(self):
+ """
+ Write out the "extras" list. These are just passed verbatim to the
+ _arguments call
+ @return: C{None}
+ """
+ for s in self.extras:
+ self.file.write(escape(s))
+ self.file.write(' \\\n')
+
+ def writeFooter(self):
+ """
+ Write the last bit of code that finishes the call to _arguments
+ @return: C{None}
+ """
+ self.file.write('&& return 0\n')
+
+ def verifyZshNames(self):
+ """
+ Ensure that none of the names given in zsh_* variables are typoed
+ @return: C{None}
+ @raise ValueError: Raised if unknown option names have been given in
+ zsh_* variables
+ """
+ def err(name):
+ raise ValueError, "Unknown option name \"%s\" found while\n" \
+ "examining zsh_ attributes for the %s command" % (
+ name, self.cmd_name)
+
+ for name in itertools.chain(self.altArgDescr, self.actionDescr,
+ self.actions, self.multiUse):
+ if name not in self.optAll_d:
+ err(name)
+
+ for seq in self.mutuallyExclusive:
+ for name in seq:
+ if name not in self.optAll_d:
+ err(name)
+
+ def excludeStr(self, long, buildShort=False):
+ """
+ Generate an "exclusion string" for the given option
+
+ @type long: C{str}
+ @param long: The long name of the option
+ (i.e. "verbose" instead of "v")
+
+ @type buildShort: C{bool}
+ @param buildShort: May be True to indicate we're building an excludes
+ string for the short option that correspondes to
+ the given long opt
+
+ @return: The generated C{str}
+ """
+ if long in self.excludes:
+ exclusions = self.excludes[long][:]
+ else:
+ exclusions = []
+
+ # if long isn't a multiUse option (can't appear on the cmd line more
+ # than once), then we have to exclude the short option if we're
+ # building for the long option, and vice versa.
+ if long not in self.multiUse:
+ if buildShort is False:
+ short = self.getShortOption(long)
+ if short is not None:
+ exclusions.append(short)
+ else:
+ exclusions.append(long)
+
+ if not exclusions:
+ return ''
+
+ strings = []
+ for optName in exclusions:
+ if len(optName) == 1:
+ # short option
+ strings.append("-" + optName)
+ else:
+ strings.append("--" + optName)
+ return "(%s)" % " ".join(strings)
+
+ def makeExcludesDict(self):
+ """
+ @return: A C{dict} that maps each option name appearing in
+ self.mutuallyExclusive to a list of those option names that
+ is it mutually exclusive with (can't appear on the cmd line with)
+ """
+
+ #create a mapping of long option name -> single character name
+ longToShort = {}
+ for optList in itertools.chain(self.optParams, self.optFlags):
+ try:
+ if optList[1] != None:
+ longToShort[optList[0]] = optList[1]
+ except IndexError:
+ pass
+
+ excludes = {}
+ for lst in self.mutuallyExclusive:
+ for i, long in enumerate(lst):
+ tmp = []
+ tmp.extend(lst[:i])
+ tmp.extend(lst[i+1:])
+ for name in tmp[:]:
+ if name in longToShort:
+ tmp.append(longToShort[name])
+
+ if long in excludes:
+ excludes[long].extend(tmp)
+ else:
+ excludes[long] = tmp
+ return excludes
+
+ def writeOpt(self, long):
+ """
+ Write out the zsh code for the given argument. This is just part of the
+ one big call to _arguments
+
+ @type long: C{str}
+ @param long: The long name of the option
+ (i.e. "verbose" instead of "v")
+
+ @return: C{None}
+ """
+ if long in self.optFlags_d:
+ # It's a flag option. Not one that takes a parameter.
+ long_field = "--%s" % long
+ else:
+ long_field = "--%s=" % long
+
+ short = self.getShortOption(long)
+ if short != None:
+ short_field = "-" + short
+ else:
+ short_field = ''
+
+ descr = self.getDescription(long)
+ descr_field = descr.replace("[", "\[")
+ descr_field = descr_field.replace("]", "\]")
+ descr_field = '[%s]' % descr_field
+
+ if long in self.actionDescr:
+ actionDescr_field = self.actionDescr[long]
+ else:
+ actionDescr_field = descr
+
+ action_field = self.getAction(long)
+ if long in self.multiUse:
+ multi_field = '*'
+ else:
+ multi_field = ''
+
+ longExclusions_field = self.excludeStr(long)
+
+ if short:
+ #we have to write an extra line for the short option if we have one
+ shortExclusions_field = self.excludeStr(long, buildShort=True)
+ self.file.write(escape('%s%s%s%s%s' % (shortExclusions_field,
+ multi_field, short_field, descr_field, action_field)))
+ self.file.write(' \\\n')
+
+ self.file.write(escape('%s%s%s%s%s' % (longExclusions_field,
+ multi_field, long_field, descr_field, action_field)))
+ self.file.write(' \\\n')
+
+ def getAction(self, long):
+ """
+ Return a zsh "action" string for the given argument
+ @return: C{str}
+ """
+ if long in self.actions:
+ if callable(self.actions[long]):
+ action = self.actions[long]()
+ else:
+ action = self.actions[long]
+ return ":%s:%s" % (self.getActionDescr(long), action)
+ if long in self.optParams_d:
+ return ':%s:_files' % self.getActionDescr(long)
+ return ''
+
+ def getActionDescr(self, long):
+ """
+ Return the description to be used when this argument is completed
+ @return: C{str}
+ """
+ if long in self.actionDescr:
+ return self.actionDescr[long]
+ else:
+ return long
+
+ def getDescription(self, long):
+ """
+ Return the description to be used for this argument
+ @return: C{str}
+ """
+ #check if we have an alternate descr for this arg, and if so use it
+ if long in self.altArgDescr:
+ return self.altArgDescr[long]
+
+ #otherwise we have to get it from the optFlags or optParams
+ try:
+ descr = self.optFlags_d[long][1]
+ except KeyError:
+ try:
+ descr = self.optParams_d[long][2]
+ except KeyError:
+ descr = None
+
+ if descr is not None:
+ return descr
+
+ # lets try to get it from the opt_foo method doc string if there is one
+ longMangled = long.replace('-', '_') # this is what t.p.usage does
+ obj = getattr(self.options, 'opt_%s' % longMangled, None)
+ if obj:
+ descr = descrFromDoc(obj)
+ if descr is not None:
+ return descr
+
+ return long # we really ought to have a good description to use
+
+ def getShortOption(self, long):
+ """
+ Return the short option letter or None
+ @return: C{str} or C{None}
+ """
+ optList = self.optAll_d[long]
+ try:
+ return optList[0] or None
+ except IndexError:
+ pass
+
+ def addAdditionalOptions(self):
+ """
+ Add additional options to the optFlags and optParams lists.
+ These will be defined by 'opt_foo' methods of the Options subclass
+ @return: C{None}
+ """
+ methodsDict = {}
+ reflect.accumulateMethods(self.options, methodsDict, 'opt_')
+ methodToShort = {}
+ for name in methodsDict.copy():
+ if len(name) == 1:
+ methodToShort[methodsDict[name]] = name
+ del methodsDict[name]
+
+ for methodName, methodObj in methodsDict.items():
+ long = methodName.replace('_', '-') # t.p.usage does this
+ # if this option is already defined by the optFlags or
+ # optParameters then we don't want to override that data
+ if long in self.optAll_d:
+ continue
+
+ descr = self.getDescription(long)
+
+ short = None
+ if methodObj in methodToShort:
+ short = methodToShort[methodObj]
+
+ reqArgs = methodObj.im_func.func_code.co_argcount
+ if reqArgs == 2:
+ self.optParams.append([long, short, None, descr])
+ self.optParams_d[long] = [short, None, descr]
+ self.optAll_d[long] = [short, None, descr]
+ elif reqArgs == 1:
+ self.optFlags.append([long, short, descr])
+ self.optFlags_d[long] = [short, descr]
+ self.optAll_d[long] = [short, None, descr]
+ else:
+ raise TypeError, '%r has wrong number ' \
+ 'of arguments' % (methodObj,)
+
+def descrFromDoc(obj):
+ """
+ Generate an appropriate description from docstring of the given object
+ """
+ if obj.__doc__ is None:
+ return None
+
+ lines = obj.__doc__.split("\n")
+ descr = None
+ try:
+ if lines[0] != "" and not lines[0].isspace():
+ descr = lines[0].lstrip()
+ # skip first line if it's blank
+ elif lines[1] != "" and not lines[1].isspace():
+ descr = lines[1].lstrip()
+ except IndexError:
+ pass
+ return descr
+
+def firstLine(s):
+ """
+ Return the first line of the given string
+ """
+ try:
+ i = s.index('\n')
+ return s[:i]
+ except ValueError:
+ return s
+
+def escape(str):
+ """
+ Shell escape the given string
+ """
+ return commands.mkarg(str)[1:]
+
+def siteFunctionsPath():
+ """
+ Return the path to the system-wide site-functions directory or
+ C{None} if it cannot be determined
+ """
+ try:
+ cmd = "zsh -f -c 'echo ${(M)fpath:#/*/site-functions}'"
+ output = commands.getoutput(cmd)
+ if os.path.isdir(output):
+ return output
+ except:
+ pass
+
+generateFor = [('conch', 'twisted.conch.scripts.conch', 'ClientOptions'),
+ ('mktap', 'twisted.scripts.mktap', 'FirstPassOptions'),
+ ('trial', 'twisted.scripts.trial', 'Options'),
+ ('cftp', 'twisted.conch.scripts.cftp', 'ClientOptions'),
+ ('tapconvert', 'twisted.scripts.tapconvert', 'ConvertOptions'),
+ ('twistd', 'twisted.scripts.twistd', 'ServerOptions'),
+ ('ckeygen', 'twisted.conch.scripts.ckeygen', 'GeneralOptions'),
+ ('lore', 'twisted.lore.scripts.lore', 'Options'),
+ ('pyhtmlizer', 'twisted.scripts.htmlizer', 'Options'),
+ ('tap2deb', 'twisted.scripts.tap2deb', 'MyOptions'),
+ ('tkconch', 'twisted.conch.scripts.tkconch', 'GeneralOptions'),
+ ('manhole', 'twisted.scripts.manhole', 'MyOptions'),
+ ('tap2rpm', 'twisted.scripts.tap2rpm', 'MyOptions'),
+ ('websetroot', None, None),
+ ('tkmktap', None, None),
+ ]
+# NOTE: the commands using None above are no longer included in Twisted.
+# However due to limitations in zsh's completion system the version of
+# _twisted_zsh_stub shipped with zsh contains a static list of Twisted's
+# commands. It will display errors if completion functions for these missing
+# commands are not found :( So we just include dummy (empty) completion
+# function files
+
+specialBuilders = {'mktap' : MktapBuilder,
+ 'twistd' : TwistdBuilder}
+
+def makeCompFunctionFiles(out_path, generateFor=generateFor,
+ specialBuilders=specialBuilders):
+ """
+ Generate completion function files in the given directory for all
+ twisted commands
+
+ @type out_path: C{str}
+ @param out_path: The path to the directory to generate completion function
+ fils in
+
+ @param generateFor: Sequence in the form of the 'generateFor' top-level
+ variable as defined in this module. Indicates what
+ commands to build completion files for.
+
+ @param specialBuilders: Sequence in the form of the 'specialBuilders'
+ top-level variable as defined in this module.
+ Indicates what commands require a special
+ Builder class.
+
+ @return: C{list} of 2-tuples of the form (cmd_name, error) indicating
+ commands that we skipped building completions for. cmd_name
+ is the name of the skipped command, and error is the Exception
+ that was raised when trying to import the script module.
+ Commands are usually skipped due to a missing dependency,
+ e.g. Tkinter.
+ """
+ skips = []
+ for cmd_name, module_name, class_name in generateFor:
+ if module_name is None:
+ # create empty file
+ f = _openCmdFile(out_path, cmd_name)
+ f.close()
+ continue
+ try:
+ m = __import__('%s' % (module_name,), None, None, (class_name))
+ f = _openCmdFile(out_path, cmd_name)
+ o = getattr(m, class_name)() # instantiate Options class
+
+ if cmd_name in specialBuilders:
+ b = specialBuilders[cmd_name](cmd_name, o, f)
+ b.write()
+ else:
+ b = Builder(cmd_name, o, f)
+ b.write()
+ except Exception, e:
+ skips.append( (cmd_name, e) )
+ continue
+ return skips
+
+def _openCmdFile(out_path, cmd_name):
+ return file(os.path.join(out_path, '_'+cmd_name), 'w')
+
+def run():
+ options = MyOptions()
+ try:
+ options.parseOptions(sys.argv[1:])
+ except usage.UsageError, e:
+ print e
+ print options.getUsage()
+ sys.exit(2)
+
+ if options['install']:
+ import twisted
+ dir = os.path.join(os.path.dirname(twisted.__file__), "python", "zsh")
+ skips = makeCompFunctionFiles(dir)
+ else:
+ skips = makeCompFunctionFiles(options['directory'])
+
+ for cmd_name, error in skips:
+ sys.stderr.write("zshcomp: Skipped building for %s. Script module " \
+ "could not be imported:\n" % (cmd_name,))
+ sys.stderr.write(str(error)+'\n')
+ if skips:
+ sys.exit(3)
+
+if __name__ == '__main__':
+ run()
diff --git a/vendor/Twisted-10.0.0/twisted/runner/__init__.py b/vendor/Twisted-10.0.0/twisted/runner/__init__.py
new file mode 100644
index 0000000000..06b5d4be0c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/__init__.py
@@ -0,0 +1,15 @@
+"""
+Twisted runer: run and monitor processes
+
+Maintainer: Andrew Bennetts
+
+classic inetd(8) support:
+Future Plans: The basic design should be final. There are some bugs that need
+fixing regarding UDP and Sun-RPC support. Perhaps some day xinetd
+compatibility will be added.
+
+procmon:monitor and restart processes
+"""
+
+from twisted.runner._version import version
+__version__ = version.short()
diff --git a/vendor/Twisted-10.0.0/twisted/runner/_version.py b/vendor/Twisted-10.0.0/twisted/runner/_version.py
new file mode 100644
index 0000000000..e27a3e5148
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/_version.py
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twisted.runner', 10, 0, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/runner/inetd.py b/vendor/Twisted-10.0.0/twisted/runner/inetd.py
new file mode 100644
index 0000000000..737d07813c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/inetd.py
@@ -0,0 +1,70 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+Twisted inetd.
+
+Maintainer: Andrew Bennetts
+
+Future Plans: Bugfixes. Specifically for UDP and Sun-RPC, which don't work
+correctly yet.
+"""
+
+import os
+
+from twisted.internet import process, reactor, fdesc
+from twisted.internet.protocol import Protocol, ServerFactory
+from twisted.protocols import wire
+
+# A dict of known 'internal' services (i.e. those that don't involve spawning
+# another process.
+internalProtocols = {
+ 'echo': wire.Echo,
+ 'chargen': wire.Chargen,
+ 'discard': wire.Discard,
+ 'daytime': wire.Daytime,
+ 'time': wire.Time,
+}
+
+
+class InetdProtocol(Protocol):
+ """Forks a child process on connectionMade, passing the socket as fd 0."""
+ def connectionMade(self):
+ sockFD = self.transport.fileno()
+ childFDs = {0: sockFD, 1: sockFD}
+ if self.factory.stderrFile:
+ childFDs[2] = self.factory.stderrFile.fileno()
+
+ # processes run by inetd expect blocking sockets
+ # FIXME: maybe this should be done in process.py? are other uses of
+ # Process possibly affected by this?
+ fdesc.setBlocking(sockFD)
+ if childFDs.has_key(2):
+ fdesc.setBlocking(childFDs[2])
+
+ service = self.factory.service
+ uid = service.user
+ gid = service.group
+
+ # don't tell Process to change our UID/GID if it's what we
+ # already are
+ if uid == os.getuid():
+ uid = None
+ if gid == os.getgid():
+ gid = None
+
+ process.Process(None, service.program, service.programArgs, os.environ,
+ None, None, uid, gid, childFDs)
+
+ reactor.removeReader(self.transport)
+ reactor.removeWriter(self.transport)
+
+
+class InetdFactory(ServerFactory):
+ protocol = InetdProtocol
+ stderrFile = None
+
+ def __init__(self, service):
+ self.service = service
diff --git a/vendor/Twisted-10.0.0/twisted/runner/inetdconf.py b/vendor/Twisted-10.0.0/twisted/runner/inetdconf.py
new file mode 100644
index 0000000000..d7a72febcd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/inetdconf.py
@@ -0,0 +1,194 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""
+Parser for inetd.conf files
+
+Maintainer: Andrew Bennetts
+
+Future Plans: xinetd configuration file support?
+"""
+
+# Various exceptions
+class InvalidConfError(Exception):
+ """Invalid configuration file"""
+
+
+class InvalidInetdConfError(InvalidConfError):
+ """Invalid inetd.conf file"""
+
+
+class InvalidServicesConfError(InvalidConfError):
+ """Invalid services file"""
+
+
+class InvalidRPCServicesConfError(InvalidConfError):
+ """Invalid rpc services file"""
+
+
+class UnknownService(Exception):
+ """Unknown service name"""
+
+
+class SimpleConfFile:
+ """Simple configuration file parser superclass.
+
+ Filters out comments and empty lines (which includes lines that only
+ contain comments).
+
+ To use this class, override parseLine or parseFields.
+ """
+
+ commentChar = '#'
+ defaultFilename = None
+
+ def parseFile(self, file=None):
+ """Parse a configuration file
+
+ If file is None and self.defaultFilename is set, it will open
+ defaultFilename and use it.
+ """
+ if file is None and self.defaultFilename:
+ file = open(self.defaultFilename,'r')
+
+ for line in file.readlines():
+ # Strip out comments
+ comment = line.find(self.commentChar)
+ if comment != -1:
+ line = line[:comment]
+
+ # Strip whitespace
+ line = line.strip()
+
+ # Skip empty lines (and lines which only contain comments)
+ if not line:
+ continue
+
+ self.parseLine(line)
+
+ def parseLine(self, line):
+ """Override this.
+
+ By default, this will split the line on whitespace and call
+ self.parseFields (catching any errors).
+ """
+ try:
+ self.parseFields(*line.split())
+ except ValueError:
+ raise InvalidInetdConfError, 'Invalid line: ' + repr(line)
+
+ def parseFields(self, *fields):
+ """Override this."""
+
+
+class InetdService:
+ """A simple description of an inetd service."""
+ name = None
+ port = None
+ socketType = None
+ protocol = None
+ wait = None
+ user = None
+ group = None
+ program = None
+ programArgs = None
+
+ def __init__(self, name, port, socketType, protocol, wait, user, group,
+ program, programArgs):
+ self.name = name
+ self.port = port
+ self.socketType = socketType
+ self.protocol = protocol
+ self.wait = wait
+ self.user = user
+ self.group = group
+ self.program = program
+ self.programArgs = programArgs
+
+
+class InetdConf(SimpleConfFile):
+ """Configuration parser for a traditional UNIX inetd(8)"""
+
+ defaultFilename = '/etc/inetd.conf'
+
+ def __init__(self, knownServices=None):
+ self.services = []
+
+ if knownServices is None:
+ knownServices = ServicesConf()
+ knownServices.parseFile()
+ self.knownServices = knownServices
+
+ def parseFields(self, serviceName, socketType, protocol, wait, user,
+ program, *programArgs):
+ """Parse an inetd.conf file.
+
+ Implemented from the description in the Debian inetd.conf man page.
+ """
+ # Extract user (and optional group)
+ user, group = (user.split('.') + [None])[:2]
+
+ # Find the port for a service
+ port = self.knownServices.services.get((serviceName, protocol), None)
+ if not port and not protocol.startswith('rpc/'):
+ # FIXME: Should this be discarded/ignored, rather than throwing
+ # an exception?
+ try:
+ port = int(serviceName)
+ serviceName = 'unknown'
+ except:
+ raise UnknownService, "Unknown service: %s (%s)" \
+ % (serviceName, protocol)
+
+ self.services.append(InetdService(serviceName, port, socketType,
+ protocol, wait, user, group, program,
+ programArgs))
+
+
+class ServicesConf(SimpleConfFile):
+ """/etc/services parser
+
+ @ivar services: dict mapping service names to (port, protocol) tuples.
+ """
+
+ defaultFilename = '/etc/services'
+
+ def __init__(self):
+ self.services = {}
+
+ def parseFields(self, name, portAndProtocol, *aliases):
+ try:
+ port, protocol = portAndProtocol.split('/')
+ port = long(port)
+ except:
+ raise InvalidServicesConfError, 'Invalid port/protocol:' + \
+ repr(portAndProtocol)
+
+ self.services[(name, protocol)] = port
+ for alias in aliases:
+ self.services[(alias, protocol)] = port
+
+
+class RPCServicesConf(SimpleConfFile):
+ """/etc/rpc parser
+
+ @ivar self.services: dict mapping rpc service names to rpc ports.
+ """
+
+ defaultFilename = '/etc/rpc'
+
+ def __init__(self):
+ self.services = {}
+
+ def parseFields(self, name, port, *aliases):
+ try:
+ port = long(port)
+ except:
+ raise InvalidRPCServicesConfError, 'Invalid port:' + repr(port)
+
+ self.services[name] = port
+ for alias in aliases:
+ self.services[alias] = port
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/runner/inetdtap.py b/vendor/Twisted-10.0.0/twisted/runner/inetdtap.py
new file mode 100644
index 0000000000..1dabc3d149
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/inetdtap.py
@@ -0,0 +1,160 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""
+Twisted inetd TAP support
+
+Maintainer: Andrew Bennetts
+
+Future Plans: more configurability.
+"""
+
+import os, pwd, grp, socket
+
+from twisted.runner import inetd, inetdconf
+from twisted.python import log, usage
+from twisted.internet.protocol import ServerFactory
+from twisted.application import internet, service as appservice
+
+try:
+ import portmap
+ rpcOk = 1
+except ImportError:
+ rpcOk = 0
+
+
+# Protocol map
+protocolDict = {'tcp': socket.IPPROTO_TCP, 'udp': socket.IPPROTO_UDP}
+
+
+class Options(usage.Options):
+
+ optParameters = [
+ ['rpc', 'r', '/etc/rpc', 'RPC procedure table file'],
+ ['file', 'f', '/etc/inetd.conf', 'Service configuration file']
+ ]
+
+ optFlags = [['nointernal', 'i', "Don't run internal services"]]
+ zsh_actions = {"file" : "_files -g '*.conf'"}
+
+class RPCServer(internet.TCPServer):
+
+ def __init__(self, rpcVersions, rpcConf, proto, service):
+ internet.TCPServer.__init__(0, ServerFactory())
+ self.rpcConf = rpcConf
+ self.proto = proto
+ self.service = service
+
+ def startService(self):
+ internet.TCPServer.startService(self)
+ import portmap
+ portNo = self._port.getHost()[2]
+ service = self.service
+ for version in rpcVersions:
+ portmap.set(self.rpcConf.services[name], version, self.proto,
+ portNo)
+ inetd.forkPassingFD(service.program, service.programArgs,
+ os.environ, service.user, service.group, p)
+
+def makeService(config):
+ s = appservice.MultiService()
+ conf = inetdconf.InetdConf()
+ conf.parseFile(open(config['file']))
+
+ rpcConf = inetdconf.RPCServicesConf()
+ try:
+ rpcConf.parseFile(open(config['rpc']))
+ except:
+ # We'll survive even if we can't read /etc/rpc
+ log.deferr()
+
+ for service in conf.services:
+ rpc = service.protocol.startswith('rpc/')
+ protocol = service.protocol
+
+ if rpc and not rpcOk:
+ log.msg('Skipping rpc service due to lack of rpc support')
+ continue
+
+ if rpc:
+ # RPC has extra options, so extract that
+ protocol = protocol[4:] # trim 'rpc/'
+ if not protocolDict.has_key(protocol):
+ log.msg('Bad protocol: ' + protocol)
+ continue
+
+ try:
+ name, rpcVersions = service.name.split('/')
+ except ValueError:
+ log.msg('Bad RPC service/version: ' + service.name)
+ continue
+
+ if not rpcConf.services.has_key(name):
+ log.msg('Unknown RPC service: ' + repr(service.name))
+ continue
+
+ try:
+ if '-' in rpcVersions:
+ start, end = map(int, rpcVersions.split('-'))
+ rpcVersions = range(start, end+1)
+ else:
+ rpcVersions = [int(rpcVersions)]
+ except ValueError:
+ log.msg('Bad RPC versions: ' + str(rpcVersions))
+ continue
+
+ if (protocol, service.socketType) not in [('tcp', 'stream'),
+ ('udp', 'dgram')]:
+ log.msg('Skipping unsupported type/protocol: %s/%s'
+ % (service.socketType, service.protocol))
+ continue
+
+ # Convert the username into a uid (if necessary)
+ try:
+ service.user = int(service.user)
+ except ValueError:
+ try:
+ service.user = pwd.getpwnam(service.user)[2]
+ except KeyError:
+ log.msg('Unknown user: ' + service.user)
+ continue
+
+ # Convert the group name into a gid (if necessary)
+ if service.group is None:
+ # If no group was specified, use the user's primary group
+ service.group = pwd.getpwuid(service.user)[3]
+ else:
+ try:
+ service.group = int(service.group)
+ except ValueError:
+ try:
+ service.group = grp.getgrnam(service.group)[2]
+ except KeyError:
+ log.msg('Unknown group: ' + service.group)
+ continue
+
+ if service.program == 'internal':
+ if config['nointernal']:
+ continue
+
+ # Internal services can use a standard ServerFactory
+ if not inetd.internalProtocols.has_key(service.name):
+ log.msg('Unknown internal service: ' + service.name)
+ continue
+ factory = ServerFactory()
+ factory.protocol = inetd.internalProtocols[service.name]
+ elif rpc:
+ i = RPCServer(rpcVersions, rpcConf, proto, service)
+ i.setServiceParent(s)
+ continue
+ else:
+ # Non-internal non-rpc services use InetdFactory
+ factory = inetd.InetdFactory(service)
+
+ if protocol == 'tcp':
+ internet.TCPServer(service.port, factory).setServiceParent(s)
+ elif protocol == 'udp':
+ raise RuntimeError("not supporting UDP")
+ return s
diff --git a/vendor/Twisted-10.0.0/twisted/runner/portmap.c b/vendor/Twisted-10.0.0/twisted/runner/portmap.c
new file mode 100644
index 0000000000..ca0c1c93b0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/portmap.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+ * See LICENSE for details.
+
+ *
+ */
+
+/* portmap.c: A simple Python wrapper for pmap_set(3) and pmap_unset(3) */
+
+#include <Python.h>
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+
+static PyObject * portmap_set(PyObject *self, PyObject *args)
+{
+ unsigned long program, version;
+ int protocol;
+ unsigned short port;
+
+ if (!PyArg_ParseTuple(args, "llih:set",
+ &program, &version, &protocol, &port))
+ return NULL;
+
+ pmap_unset(program, version);
+ pmap_set(program, version, protocol, port);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject * portmap_unset(PyObject *self, PyObject *args)
+{
+ unsigned long program, version;
+
+ if (!PyArg_ParseTuple(args, "ll:unset",
+ &program, &version))
+ return NULL;
+
+ pmap_unset(program, version);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyMethodDef PortmapMethods[] = {
+ {"set", portmap_set, METH_VARARGS,
+ "Set an entry in the portmapper."},
+ {"unset", portmap_unset, METH_VARARGS,
+ "Unset an entry in the portmapper."},
+ {NULL, NULL, 0, NULL}
+};
+
+void initportmap(void)
+{
+ (void) Py_InitModule("portmap", PortmapMethods);
+}
+
diff --git a/vendor/Twisted-10.0.0/twisted/runner/procmon.py b/vendor/Twisted-10.0.0/twisted/runner/procmon.py
new file mode 100644
index 0000000000..ff2faca586
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/procmon.py
@@ -0,0 +1,264 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Support for starting, monitoring, and restarting child process.
+"""
+
+import os, time
+
+from twisted.python import log
+from twisted.internet import error, protocol, reactor
+from twisted.application import service
+from twisted.protocols import basic
+
+class DummyTransport:
+
+ disconnecting = 0
+
+transport = DummyTransport()
+
+class LineLogger(basic.LineReceiver):
+
+ tag = None
+ delimiter = '\n'
+
+ def lineReceived(self, line):
+ log.msg('[%s] %s' % (self.tag, line))
+
+class LoggingProtocol(protocol.ProcessProtocol):
+
+ service = None
+ name = None
+ empty = 1
+
+ def connectionMade(self):
+ self.output = LineLogger()
+ self.output.tag = self.name
+ self.output.makeConnection(transport)
+
+ def outReceived(self, data):
+ self.output.dataReceived(data)
+ self.empty = data[-1] == '\n'
+
+ errReceived = outReceived
+
+ def processEnded(self, reason):
+ if not self.empty:
+ self.output.dataReceived('\n')
+ self.service.connectionLost(self.name)
+
+
+class ProcessMonitor(service.Service):
+ """
+ ProcessMonitor runs processes, monitors their progress, and restarts them
+ when they die.
+
+ The ProcessMonitor will not attempt to restart a process that appears to die
+ instantly -- with each "instant" death (less than 1 second, by default), it
+ will delay approximately twice as long before restarting it. A successful
+ run will reset the counter.
+
+ The primary interface is L{addProcess} and L{removeProcess}. When the service
+ is active (that is, when the application it is attached to is running),
+ adding a process automatically starts it.
+
+ Each process has a name. This name string must uniquely identify the
+ process. In particular, attempting to add two processes with the same name
+ will result in a C{KeyError}.
+
+ @type threshold: C{float}
+ @ivar threshold: How long a process has to live before the death is
+ considered instant, in seconds. The default value is 1 second.
+
+ @type killTime: C{float}
+ @ivar killTime: How long a process being killed has to get its affairs in
+ order before it gets killed with an unmaskable signal. The default value
+ is 5 seconds.
+
+ @type consistencyDelay: C{float}
+ @ivar consistencyDelay: The time between consistency checks. The default
+ value is 60 seconds.
+ """
+ threshold = 1
+ active = 0
+ killTime = 5
+ consistency = None
+ consistencyDelay = 60
+
+ def __init__(self):
+ self.processes = {}
+ self.protocols = {}
+ self.delay = {}
+ self.timeStarted = {}
+ self.murder = {}
+
+ def __getstate__(self):
+ dct = service.Service.__getstate__(self)
+ for k in ('active', 'consistency'):
+ if dct.has_key(k):
+ del dct[k]
+ dct['protocols'] = {}
+ dct['delay'] = {}
+ dct['timeStarted'] = {}
+ dct['murder'] = {}
+ return dct
+
+ def _checkConsistency(self):
+ for name, protocol in self.protocols.items():
+ proc = protocol.transport
+ try:
+ proc.signalProcess(0)
+ except (OSError, error.ProcessExitedAlready):
+ log.msg("Lost process %r somehow, restarting." % name)
+ del self.protocols[name]
+ self.startProcess(name)
+ self.consistency = reactor.callLater(self.consistencyDelay,
+ self._checkConsistency)
+
+
+ def addProcess(self, name, args, uid=None, gid=None, env={}):
+ """
+ Add a new process to launch, monitor, and restart when necessary.
+
+ Note that args are passed to the system call, not to the shell. If
+ running the shell is desired, the common idiom is to use
+ C{.addProcess("name", ['/bin/sh', '-c', shell_script])}
+
+ See L{removeProcess} for removing processes from the monitor.
+
+ @param name: A label for this process. This value must be unique
+ across all processes added to this monitor.
+ @type name: C{str}
+ @param args: The argv sequence for the process to launch.
+ @param uid: The user ID to use to run the process. If C{None}, the
+ current UID is used.
+ @type uid: C{int}
+ @param gid: The group ID to use to run the process. If C{None}, the
+ current GID is used.
+ @type uid: C{int}
+ @param env: The environment to give to the launched process. See
+ L{IReactorProcess.spawnProcess}'s C{env} parameter.
+ @type env: C{dict}
+ """
+ if name in self.processes:
+ raise KeyError("remove %s first" % name)
+ self.processes[name] = args, uid, gid, env
+ if self.active:
+ self.startProcess(name)
+
+
+ def removeProcess(self, name):
+ """
+ If the process is started, kill it. It will never get restarted.
+
+ See L{addProcess} for adding processes to the monitor.
+
+ @type name: C{str}
+ @param name: The string that uniquely identifies the process.
+ """
+ del self.processes[name]
+ self.stopProcess(name)
+
+
+ def startService(self):
+ service.Service.startService(self)
+ self.active = 1
+ for name in self.processes.keys():
+ reactor.callLater(0, self.startProcess, name)
+ self.consistency = reactor.callLater(self.consistencyDelay,
+ self._checkConsistency)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.active = 0
+ for name in self.processes.keys():
+ self.stopProcess(name)
+ self.consistency.cancel()
+
+ def connectionLost(self, name):
+ if self.murder.has_key(name):
+ self.murder[name].cancel()
+ del self.murder[name]
+ if self.protocols.has_key(name):
+ del self.protocols[name]
+ if time.time()-self.timeStarted[name]<self.threshold:
+ delay = self.delay[name] = min(1+2*self.delay.get(name, 0), 3600)
+ else:
+ delay = self.delay[name] = 0
+ if self.active and self.processes.has_key(name):
+ reactor.callLater(delay, self.startProcess, name)
+
+ def startProcess(self, name):
+ if self.protocols.has_key(name):
+ return
+ p = self.protocols[name] = LoggingProtocol()
+ p.service = self
+ p.name = name
+ args, uid, gid, env = self.processes[name]
+ self.timeStarted[name] = time.time()
+ reactor.spawnProcess(p, args[0], args, uid=uid, gid=gid, env=env)
+
+ def _forceStopProcess(self, proc):
+ try:
+ proc.signalProcess('KILL')
+ except error.ProcessExitedAlready:
+ pass
+
+ def stopProcess(self, name):
+ if not self.protocols.has_key(name):
+ return
+ proc = self.protocols[name].transport
+ del self.protocols[name]
+ try:
+ proc.signalProcess('TERM')
+ except error.ProcessExitedAlready:
+ pass
+ else:
+ self.murder[name] = reactor.callLater(self.killTime, self._forceStopProcess, proc)
+
+
+ def restartAll(self):
+ """
+ Restart all processes. This is useful for third party management
+ services to allow a user to restart servers because of an outside change
+ in circumstances -- for example, a new version of a library is
+ installed.
+ """
+ for name in self.processes.keys():
+ self.stopProcess(name)
+
+
+ def __repr__(self):
+ l = []
+ for name, proc in self.processes.items():
+ uidgid = ''
+ if proc[1] is not None:
+ uidgid = str(proc[1])
+ if proc[2] is not None:
+ uidgid += ':'+str(proc[2])
+
+ if uidgid:
+ uidgid = '(' + uidgid + ')'
+ l.append('%r%s: %r' % (name, uidgid, proc[0]))
+ return ('<' + self.__class__.__name__ + ' '
+ + ' '.join(l)
+ + '>')
+
+def main():
+ from signal import SIGTERM
+ mon = ProcessMonitor()
+ mon.addProcess('foo', ['/bin/sh', '-c', 'sleep 2;echo hello'])
+ mon.addProcess('qux', ['/bin/sh', '-c', 'sleep 2;printf pilim'])
+ mon.addProcess('bar', ['/bin/sh', '-c', 'echo goodbye'])
+ mon.addProcess('baz', ['/bin/sh', '-c',
+ 'echo welcome;while :;do echo blah;sleep 5;done'])
+ reactor.callLater(30, lambda mon=mon:
+ os.kill(mon.protocols['baz'].transport.pid, SIGTERM))
+ reactor.callLater(60, mon.restartAll)
+ mon.startService()
+ reactor.addSystemEventTrigger('before', 'shutdown', mon.stopService)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/twisted/runner/procutils.py b/vendor/Twisted-10.0.0/twisted/runner/procutils.py
new file mode 100644
index 0000000000..2b6bc2fbe5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/procutils.py
@@ -0,0 +1,5 @@
+import warnings
+warnings.warn("twisted.runner.procutils is DEPRECATED. import twisted.python.procutils instead.",
+ DeprecationWarning, stacklevel=2)
+
+from twisted.python.procutils import which
diff --git a/vendor/Twisted-10.0.0/twisted/runner/test/__init__.py b/vendor/Twisted-10.0.0/twisted/runner/test/__init__.py
new file mode 100644
index 0000000000..17ec071676
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/test/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test package for Twisted Runner.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/runner/test/test_procmon.py b/vendor/Twisted-10.0.0/twisted/runner/test/test_procmon.py
new file mode 100644
index 0000000000..320a6f51b4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/test/test_procmon.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.runner.procmon}.
+"""
+
+from twisted.trial import unittest
+from twisted.runner.procmon import ProcessMonitor
+from twisted.internet import reactor
+
+
+class ProcmonTests(unittest.TestCase):
+ """
+ Tests for L{ProcessMonitor}.
+ """
+ def test_addProcess(self):
+ """
+ L{ProcessMonitor.addProcess} starts the named program and tracks the
+ resulting process, a protocol for collecting its stdout, and the time
+ it was started.
+ """
+ spawnedProcesses = []
+ def fakeSpawnProcess(*args, **kwargs):
+ spawnedProcesses.append((args, kwargs))
+ self.patch(reactor, "spawnProcess", fakeSpawnProcess)
+ pm = ProcessMonitor()
+ pm.active = True
+ pm.addProcess("foo", ["arg1", "arg2"], uid=1, gid=2)
+ self.assertEquals(pm.processes, {"foo": (["arg1", "arg2"], 1, 2, {})})
+ self.assertEquals(pm.protocols.keys(), ["foo"])
+ lp = pm.protocols["foo"]
+ self.assertEquals(
+ spawnedProcesses,
+ [((lp, "arg1", ["arg1", "arg2"]),
+ {"uid": 1, "gid": 2, "env": {}})])
+
+
+ def test_addProcessEnv(self):
+ """
+ L{ProcessMonitor.addProcess} takes an C{env} parameter that is passed
+ to C{spawnProcess}.
+ """
+ spawnedProcesses = []
+ def fakeSpawnProcess(*args, **kwargs):
+ spawnedProcesses.append((args, kwargs))
+ self.patch(reactor, "spawnProcess", fakeSpawnProcess)
+ pm = ProcessMonitor()
+ pm.active = True
+ fakeEnv = {"KEY": "value"}
+ pm.addProcess("foo", ["foo"], uid=1, gid=2, env=fakeEnv)
+ self.assertEquals(
+ spawnedProcesses,
+ [((pm.protocols["foo"], "foo", ["foo"]),
+ {"uid": 1, "gid": 2, "env": fakeEnv})])
diff --git a/vendor/Twisted-10.0.0/twisted/runner/topfiles/NEWS b/vendor/Twisted-10.0.0/twisted/runner/topfiles/NEWS
new file mode 100644
index 0000000000..fda1bab198
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/topfiles/NEWS
@@ -0,0 +1,49 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/<number>
+
+Twisted Runner 10.0.0 (2010-03-01)
+==================================
+
+Other
+-----
+ - #3961
+
+
+Twisted Runner 9.0.0 (2009-11-24)
+=================================
+
+Features
+--------
+ - procmon.ProcessMonitor.addProcess now accepts an 'env' parameter which allows
+ users to specify the environment in which a process will be run (#3691)
+
+Other
+-----
+ - #3540
+
+
+Runner 8.2.0 (2008-12-16)
+=========================
+
+No interesting changes since Twisted 8.0.
+
+8.0.0 (2008-03-17)
+==================
+
+Misc
+----
+ - Remove all "API Stability" markers (#2847)
+
+
+0.2.0 (2006-05-24)
+==================
+
+Fixes
+-----
+ - Fix a bug that broke inetdtap.RPCServer.
+ - Misc: #1142
+
+
+0.1.0
+=====
+ - Pass *blocking* sockets to subprocesses run by inetd
diff --git a/vendor/Twisted-10.0.0/twisted/runner/topfiles/README b/vendor/Twisted-10.0.0/twisted/runner/topfiles/README
new file mode 100644
index 0000000000..bdfc881f4b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/topfiles/README
@@ -0,0 +1,2 @@
+Twisted Runner 10.0.0
+
diff --git a/vendor/Twisted-10.0.0/twisted/runner/topfiles/setup.py b/vendor/Twisted-10.0.0/twisted/runner/topfiles/setup.py
new file mode 100644
index 0000000000..bf765b75ca
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/runner/topfiles/setup.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+try:
+ from twisted.python.dist import setup, ConditionalExtension as Extension
+except ImportError:
+ raise SystemExit("twisted.python.dist module not found. Make sure you "
+ "have installed the Twisted core package before "
+ "attempting to install any other Twisted projects.")
+
+extensions = [
+ Extension("twisted.runner.portmap",
+ ["twisted/runner/portmap.c"],
+ condition=lambda builder: builder._check_header("rpc/rpc.h")),
+]
+
+if __name__ == '__main__':
+ setup(
+ twisted_subproject="runner",
+ # metadata
+ name="Twisted Runner",
+ description="Twisted Runner is a process management library and inetd "
+ "replacement.",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="Andrew Bennetts",
+ url="http://twistedmatrix.com/trac/wiki/TwistedRunner",
+ license="MIT",
+ long_description="""\
+Twisted Runner contains code useful for persistent process management
+with Python and Twisted, and has an almost full replacement for inetd.
+""",
+ # build stuff
+ conditionalExtensions=extensions,
+ )
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/__init__.py b/vendor/Twisted-10.0.0/twisted/scripts/__init__.py
new file mode 100644
index 0000000000..28ce5b622b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/__init__.py
@@ -0,0 +1,12 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+# $Id: __init__.py,v 1.1 2002/03/18 23:33:48 jh Exp $
+
+__doc__ = '''
+Subpackage containing the modules that implement the command line tools.
+Note that these are imported by script stubs generated by "setup.py".
+'''
+
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/_twistd_unix.py b/vendor/Twisted-10.0.0/twisted/scripts/_twistd_unix.py
new file mode 100644
index 0000000000..0a58b9b472
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/_twistd_unix.py
@@ -0,0 +1,317 @@
+# -*- test-case-name: twisted.test.test_twistd -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import os, errno, sys
+
+from twisted.python import log, syslog, logfile
+from twisted.python.util import switchUID, uidFromString, gidFromString
+from twisted.application import app, service
+from twisted import copyright
+
+
+def _umask(value):
+ return int(value, 8)
+
+
+class ServerOptions(app.ServerOptions):
+ synopsis = "Usage: twistd [options]"
+
+ optFlags = [['nodaemon','n', "don't daemonize, don't use default umask of 0077"],
+ ['originalname', None, "Don't try to change the process name"],
+ ['syslog', None, "Log to syslog, not to file"],
+ ['euid', '',
+ "Set only effective user-id rather than real user-id. "
+ "(This option has no effect unless the server is running as "
+ "root, in which case it means not to shed all privileges "
+ "after binding ports, retaining the option to regain "
+ "privileges in cases such as spawning processes. "
+ "Use with caution.)"],
+ ]
+
+ optParameters = [
+ ['prefix', None,'twisted',
+ "use the given prefix when syslogging"],
+ ['pidfile','','twistd.pid',
+ "Name of the pidfile"],
+ ['chroot', None, None,
+ 'Chroot to a supplied directory before running'],
+ ['uid', 'u', None, "The uid to run as.", uidFromString],
+ ['gid', 'g', None, "The gid to run as.", gidFromString],
+ ['umask', None, None,
+ "The (octal) file creation mask to apply.", _umask],
+ ]
+ zsh_altArgDescr = {"prefix":"Use the given prefix when syslogging (default: twisted)",
+ "pidfile":"Name of the pidfile (default: twistd.pid)",}
+ #zsh_multiUse = ["foo", "bar"]
+ #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
+ zsh_actions = {"pidfile":'_files -g "*.pid"', "chroot":'_dirs'}
+ zsh_actionDescr = {"chroot":"chroot directory"}
+
+ def opt_version(self):
+ """Print version information and exit.
+ """
+ print 'twistd (the Twisted daemon) %s' % copyright.version
+ print copyright.copyright
+ sys.exit()
+
+
+ def postOptions(self):
+ app.ServerOptions.postOptions(self)
+ if self['pidfile']:
+ self['pidfile'] = os.path.abspath(self['pidfile'])
+
+
+def checkPID(pidfile):
+ if not pidfile:
+ return
+ if os.path.exists(pidfile):
+ try:
+ pid = int(open(pidfile).read())
+ except ValueError:
+ sys.exit('Pidfile %s contains non-numeric value' % pidfile)
+ try:
+ os.kill(pid, 0)
+ except OSError, why:
+ if why[0] == errno.ESRCH:
+ # The pid doesnt exists.
+ log.msg('Removing stale pidfile %s' % pidfile, isError=True)
+ os.remove(pidfile)
+ else:
+ sys.exit("Can't check status of PID %s from pidfile %s: %s" %
+ (pid, pidfile, why[1]))
+ else:
+ sys.exit("""\
+Another twistd server is running, PID %s\n
+This could either be a previously started instance of your application or a
+different application entirely. To start a new one, either run it in some other
+directory, or use the --pidfile and --logfile parameters to avoid clashes.
+""" % pid)
+
+
+
+class UnixAppLogger(app.AppLogger):
+ """
+ A logger able to log to syslog, to files, and to stdout.
+
+ @ivar _syslog: A flag indicating whether to use syslog instead of file
+ logging.
+ @type _syslog: C{bool}
+
+ @ivar _syslogPrefix: If C{sysLog} is C{True}, the string prefix to use for
+ syslog messages.
+ @type _syslogPrefix: C{str}
+
+ @ivar _nodaemon: A flag indicating the process will not be daemonizing.
+ @type _nodaemon: C{bool}
+ """
+
+ def __init__(self, options):
+ app.AppLogger.__init__(self, options)
+ self._syslog = options.get("syslog", False)
+ self._syslogPrefix = options.get("prefix", "")
+ self._nodaemon = options.get("nodaemon", False)
+
+
+ def _getLogObserver(self):
+ """
+ Create and return a suitable log observer for the given configuration.
+
+ The observer will go to syslog using the prefix C{_syslogPrefix} if
+ C{_syslog} is true. Otherwise, it will go to the file named
+ C{_logfilename} or, if C{_nodaemon} is true and C{_logfilename} is
+ C{"-"}, to stdout.
+
+ @return: An object suitable to be passed to C{log.addObserver}.
+ """
+ if self._syslog:
+ return syslog.SyslogObserver(self._syslogPrefix).emit
+
+ if self._logfilename == '-':
+ if not self._nodaemon:
+ sys.exit('Daemons cannot log to stdout, exiting!')
+ logFile = sys.stdout
+ elif self._nodaemon and not self._logfilename:
+ logFile = sys.stdout
+ else:
+ if not self._logfilename:
+ self._logfilename = 'twistd.log'
+ logFile = logfile.LogFile.fromFullPath(self._logfilename)
+ try:
+ import signal
+ except ImportError:
+ pass
+ else:
+ # Override if signal is set to None or SIG_DFL (0)
+ if not signal.getsignal(signal.SIGUSR1):
+ def rotateLog(signal, frame):
+ from twisted.internet import reactor
+ reactor.callFromThread(logFile.rotate)
+ signal.signal(signal.SIGUSR1, rotateLog)
+ return log.FileLogObserver(logFile).emit
+
+
+
+def daemonize():
+ # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16
+ if os.fork(): # launch child and...
+ os._exit(0) # kill off parent
+ os.setsid()
+ if os.fork(): # launch child and...
+ os._exit(0) # kill off parent again.
+ null = os.open('/dev/null', os.O_RDWR)
+ for i in range(3):
+ try:
+ os.dup2(null, i)
+ except OSError, e:
+ if e.errno != errno.EBADF:
+ raise
+ os.close(null)
+
+
+
+def launchWithName(name):
+ if name and name != sys.argv[0]:
+ exe = os.path.realpath(sys.executable)
+ log.msg('Changing process name to ' + name)
+ os.execv(exe, [name, sys.argv[0], '--originalname'] + sys.argv[1:])
+
+
+
+class UnixApplicationRunner(app.ApplicationRunner):
+ """
+ An ApplicationRunner which does Unix-specific things, like fork,
+ shed privileges, and maintain a PID file.
+ """
+ loggerFactory = UnixAppLogger
+
+ def preApplication(self):
+ """
+ Do pre-application-creation setup.
+ """
+ checkPID(self.config['pidfile'])
+ self.config['nodaemon'] = (self.config['nodaemon']
+ or self.config['debug'])
+ self.oldstdout = sys.stdout
+ self.oldstderr = sys.stderr
+
+
+ def postApplication(self):
+ """
+ To be called after the application is created: start the
+ application and run the reactor. After the reactor stops,
+ clean up PID files and such.
+ """
+ self.startApplication(self.application)
+ self.startReactor(None, self.oldstdout, self.oldstderr)
+ self.removePID(self.config['pidfile'])
+
+
+ def removePID(self, pidfile):
+ """
+ Remove the specified PID file, if possible. Errors are logged, not
+ raised.
+
+ @type pidfile: C{str}
+ @param pidfile: The path to the PID tracking file.
+ """
+ if not pidfile:
+ return
+ try:
+ os.unlink(pidfile)
+ except OSError, e:
+ if e.errno == errno.EACCES or e.errno == errno.EPERM:
+ log.msg("Warning: No permission to delete pid file")
+ else:
+ log.err(e, "Failed to unlink PID file")
+ except:
+ log.err(None, "Failed to unlink PID file")
+
+
+ def setupEnvironment(self, chroot, rundir, nodaemon, umask, pidfile):
+ """
+ Set the filesystem root, the working directory, and daemonize.
+
+ @type chroot: C{str} or L{NoneType}
+ @param chroot: If not None, a path to use as the filesystem root (using
+ L{os.chroot}).
+
+ @type rundir: C{str}
+ @param rundir: The path to set as the working directory.
+
+ @type nodaemon: C{bool}
+ @param nodaemon: A flag which, if set, indicates that daemonization
+ should not be done.
+
+ @type umask: C{int} or L{NoneType}
+ @param umask: The value to which to change the process umask.
+
+ @type pidfile: C{str} or L{NoneType}
+ @param pidfile: If not C{None}, the path to a file into which to put
+ the PID of this process.
+ """
+ daemon = not nodaemon
+
+ if chroot is not None:
+ os.chroot(chroot)
+ if rundir == '.':
+ rundir = '/'
+ os.chdir(rundir)
+ if daemon and umask is None:
+ umask = 077
+ if umask is not None:
+ os.umask(umask)
+ if daemon:
+ daemonize()
+ if pidfile:
+ f = open(pidfile,'wb')
+ f.write(str(os.getpid()))
+ f.close()
+
+
+ def shedPrivileges(self, euid, uid, gid):
+ """
+ Change the UID and GID or the EUID and EGID of this process.
+
+ @type euid: C{bool}
+ @param euid: A flag which, if set, indicates that only the I{effective}
+ UID and GID should be set.
+
+ @type uid: C{int} or C{NoneType}
+ @param uid: If not C{None}, the UID to which to switch.
+
+ @type gid: C{int} or C{NoneType}
+ @param gid: If not C{None}, the GID to which to switch.
+ """
+ if uid is not None or gid is not None:
+ switchUID(uid, gid, euid)
+ extra = euid and 'e' or ''
+ log.msg('set %suid/%sgid %s/%s' % (extra, extra, uid, gid))
+
+
+ def startApplication(self, application):
+ """
+ Configure global process state based on the given application and run
+ the application.
+
+ @param application: An object which can be adapted to
+ L{service.IProcess} and L{service.IService}.
+ """
+ process = service.IProcess(application)
+ if not self.config['originalname']:
+ launchWithName(process.processName)
+ self.setupEnvironment(
+ self.config['chroot'], self.config['rundir'],
+ self.config['nodaemon'], self.config['umask'],
+ self.config['pidfile'])
+
+ service.IService(application).privilegedStartService()
+
+ uid, gid = self.config['uid'], self.config['gid']
+ if uid is None:
+ uid = process.uid
+ if gid is None:
+ gid = process.gid
+
+ self.shedPrivileges(self.config['euid'], uid, gid)
+ app.startApplication(application, not self.config['no_save'])
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/_twistw.py b/vendor/Twisted-10.0.0/twisted/scripts/_twistw.py
new file mode 100644
index 0000000000..df240fe0c3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/_twistw.py
@@ -0,0 +1,50 @@
+# -*- test-case-name: twisted.test.test_twistd -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.python import log
+from twisted.application import app, service, internet
+from twisted import copyright
+import sys, os
+
+
+
+class ServerOptions(app.ServerOptions):
+ synopsis = "Usage: twistd [options]"
+
+ optFlags = [['nodaemon','n', "(for backwards compatability)."],
+ ]
+
+ def opt_version(self):
+ """Print version information and exit.
+ """
+ print 'twistd (the Twisted Windows runner) %s' % copyright.version
+ print copyright.copyright
+ sys.exit()
+
+
+
+class WindowsApplicationRunner(app.ApplicationRunner):
+ """
+ An ApplicationRunner which avoids unix-specific things. No
+ forking, no PID files, no privileges.
+ """
+
+ def preApplication(self):
+ """
+ Do pre-application-creation setup.
+ """
+ self.oldstdout = sys.stdout
+ self.oldstderr = sys.stderr
+ os.chdir(self.config['rundir'])
+
+
+ def postApplication(self):
+ """
+ Start the application and run the reactor.
+ """
+ service.IService(self.application).privilegedStartService()
+ app.startApplication(self.application, not self.config['no_save'])
+ app.startApplication(internet.TimerService(0.1, lambda:None), 0)
+ self.startReactor(None, self.oldstdout, self.oldstderr)
+ log.msg("Server Shut Down.")
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/htmlizer.py b/vendor/Twisted-10.0.0/twisted/scripts/htmlizer.py
new file mode 100644
index 0000000000..5bbc1e989d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/htmlizer.py
@@ -0,0 +1,66 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""HTML pretty-printing for Python source code."""
+
+__version__ = '$Revision: 1.8 $'[11:-2]
+
+from twisted.python import htmlizer, usage
+from twisted import copyright
+
+import os, sys
+
+header = '''<html><head>
+<title>%(title)s</title>
+<meta name=\"Generator\" content="%(generator)s" />
+%(alternate)s
+%(stylesheet)s
+</head>
+<body>
+'''
+footer = """</body>"""
+
+styleLink = '<link rel="stylesheet" href="%s" type="text/css" />'
+alternateLink = '<link rel="alternate" href="%(source)s" type="text/x-python" />'
+
+class Options(usage.Options):
+ synopsis = """%s [options] source.py
+ """ % (
+ os.path.basename(sys.argv[0]),)
+
+ optParameters = [
+ ('stylesheet', 's', None, "URL of stylesheet to link to."),
+ ]
+ zsh_extras = ["1:source python file:_files -g '*.py'"]
+
+ def parseArgs(self, filename):
+ self['filename'] = filename
+
+def run():
+ options = Options()
+ try:
+ options.parseOptions()
+ except usage.UsageError, e:
+ print str(e)
+ sys.exit(1)
+ filename = options['filename']
+ if options.get('stylesheet') is not None:
+ stylesheet = styleLink % (options['stylesheet'],)
+ else:
+ stylesheet = ''
+
+ output = open(filename + '.html', 'w')
+ try:
+ output.write(header % {
+ 'title': filename,
+ 'generator': 'htmlizer/%s' % (copyright.longversion,),
+ 'alternate': alternateLink % {'source': filename},
+ 'stylesheet': stylesheet
+ })
+ htmlizer.filter(open(filename), output,
+ htmlizer.SmallerHTMLWriter)
+ output.write(footer)
+ finally:
+ output.close()
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/manhole.py b/vendor/Twisted-10.0.0/twisted/scripts/manhole.py
new file mode 100644
index 0000000000..c32dd0890e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/manhole.py
@@ -0,0 +1,65 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Start a L{twisted.manhole} client.
+"""
+
+import sys
+
+from twisted.python import usage
+
+def run():
+ config = MyOptions()
+ try:
+ config.parseOptions()
+ except usage.UsageError, e:
+ print str(e)
+ print str(config)
+ sys.exit(1)
+
+ run_gtk2(config)
+
+ from twisted.internet import reactor
+ reactor.run()
+
+
+def run_gtk2(config):
+ # Put these off until after we parse options, so we know what reactor
+ # to load.
+ from twisted.internet import gtk2reactor
+ gtk2reactor.install()
+
+ # Put this off until after we parse options, or else gnome eats them.
+ sys.argv[:] = ['manhole']
+ from twisted.manhole.ui import gtk2manhole
+
+ o = config.opts
+ defaults = {
+ 'host': o['host'],
+ 'port': o['port'],
+ 'identityName': o['user'],
+ 'password': o['password'],
+ 'serviceName': o['service'],
+ 'perspectiveName': o['perspective']
+ }
+ w = gtk2manhole.ManholeWindow()
+ w.setDefaults(defaults)
+ w.login()
+
+
+pbportno = 8787
+
+class MyOptions(usage.Options):
+ optParameters=[("user", "u", "guest", "username"),
+ ("password", "w", "guest"),
+ ("service", "s", "twisted.manhole", "PB Service"),
+ ("host", "h", "localhost"),
+ ("port", "p", str(pbportno)),
+ ("perspective", "P", "",
+ "PB Perspective to ask for "
+ "(if different than username)")]
+ zsh_actions = {"host":"_hosts"}
+
+if __name__ == '__main__':
+ run()
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/mktap.py b/vendor/Twisted-10.0.0/twisted/scripts/mktap.py
new file mode 100644
index 0000000000..59ea8f3fc3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/mktap.py
@@ -0,0 +1,182 @@
+# -*- test-case-name: twisted.scripts.test.test_mktap -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import warnings, sys, os
+
+
+from twisted.application import service, app
+from twisted.persisted import sob
+from twisted.python import usage, util
+from twisted import plugin
+from twisted.python.util import uidFromString, gidFromString
+
+# API COMPATIBILITY
+IServiceMaker = service.IServiceMaker
+_tapHelper = service.ServiceMaker
+
+warnings.warn(
+ "mktap and related support modules are deprecated as of Twisted 8.0. "
+ "Use Twisted Application Plugins with the 'twistd' command directly, "
+ "as described in 'Writing a Twisted Application Plugin for twistd' "
+ "chapter of the Developer Guide.", DeprecationWarning, stacklevel=2)
+
+
+
+def getid(uid, gid):
+ """
+ Convert one or both of a string representation of a UID and GID into
+ integer form. On platforms where L{pwd} and L{grp} is available, user and
+ group names can be converted.
+
+ @type uid: C{str} or C{NoneType}
+ @param uid: A string giving the base-ten representation of a UID or the
+ name of a user which can be converted to a UID via L{pwd.getpwnam},
+ or None if no UID value is to be obtained.
+
+ @type gid: C{str} or C{NoneType}
+ @param uid: A string giving the base-ten representation of a GID or the
+ name of a group which can be converted to a GID via
+ L{grp.getgrnam}, or None if no UID value is to be obtained.
+
+ @return: A two-tuple giving integer UID and GID information for
+ whichever (or both) parameter is provided with a non-C{None} value.
+
+ @raise ValueError: If a user or group name is supplied and L{pwd} or L{grp}
+ is not available.
+ """
+ if uid is not None:
+ uid = uidFromString(uid)
+ if gid is not None:
+ gid = gidFromString(gid)
+ return (uid, gid)
+
+
+
+def loadPlugins(debug = None, progress = None):
+ tapLookup = {}
+
+ plugins = plugin.getPlugins(IServiceMaker)
+ for plug in plugins:
+ tapLookup[plug.tapname] = plug
+
+ return tapLookup
+
+def addToApplication(ser, name, append, procname, type, encrypted, uid, gid):
+ if append and os.path.exists(append):
+ a = service.loadApplication(append, 'pickle', None)
+ else:
+ a = service.Application(name, uid, gid)
+ if procname:
+ service.IProcess(a).processName = procname
+ ser.setServiceParent(service.IServiceCollection(a))
+ sob.IPersistable(a).setStyle(type)
+ passphrase = app.getSavePassphrase(encrypted)
+ if passphrase:
+ append = None
+ sob.IPersistable(a).save(filename=append, passphrase=passphrase)
+
+class FirstPassOptions(usage.Options):
+ synopsis = """Usage: mktap [options] <command> [command options] """
+
+ recursing = 0
+ params = ()
+
+ optParameters = [
+ ['uid', 'u', None, "The uid to run as.", uidFromString],
+ ['gid', 'g', None, "The gid to run as.", gidFromString],
+ ['append', 'a', None,
+ "An existing .tap file to append the plugin to, rather than "
+ "creating a new one."],
+ ['type', 't', 'pickle',
+ "The output format to use; this can be 'pickle', 'xml', "
+ "or 'source'."],
+ ['appname', 'n', None, "The process name to use for this application."]
+ ]
+
+ optFlags = [
+ ['encrypted', 'e', "Encrypt file before writing "
+ "(will make the extension of the resultant "
+ "file begin with 'e')"],
+ ['debug', 'd', "Show debug information for plugin loading"],
+ ['progress', 'p', "Show progress information for plugin loading"],
+ ['help', 'h', "Display this message"],
+ ]
+ #zsh_altArgDescr = {"foo":"use this description for foo instead"}
+ #zsh_multiUse = ["foo", "bar"]
+ #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
+ zsh_actions = {"append":'_files -g "*.tap"',
+ "type":"(pickle xml source)"}
+ zsh_actionDescr = {"append":"tap file to append to", "uid":"uid to run as",
+ "gid":"gid to run as", "type":"output format"}
+
+ def init(self, tapLookup):
+ sc = []
+ for (name, module) in tapLookup.iteritems():
+ if IServiceMaker.providedBy(module):
+ sc.append((
+ name, None, lambda m=module: m.options(), module.description))
+ else:
+ sc.append((
+ name, None, lambda obj=module: obj.load().Options(),
+ getattr(module, 'description', '')))
+
+ sc.sort()
+ self.subCommands = sc
+
+ def parseArgs(self, *rest):
+ self.params += rest
+
+ def _reportDebug(self, info):
+ print 'Debug: ', info
+
+ def _reportProgress(self, info):
+ s = self.pb(info)
+ if s:
+ print '\rProgress: ', s,
+ if info == 1.0:
+ print '\r' + (' ' * 79) + '\r',
+
+ def postOptions(self):
+ if self.recursing:
+ return
+ debug = progress = None
+ if self['debug']:
+ debug = self._reportDebug
+ if self['progress']:
+ progress = self._reportProgress
+ self.pb = util.makeStatBar(60, 1.0)
+ try:
+ self.tapLookup = loadPlugins(debug, progress)
+ except IOError:
+ raise usage.UsageError("Couldn't load the plugins file!")
+ self.init(self.tapLookup)
+ self.recursing = 1
+ self.parseOptions(self.params)
+ if not hasattr(self, 'subOptions') or self['help']:
+ raise usage.UsageError(str(self))
+ if hasattr(self, 'subOptions') and self.subOptions.get('help'):
+ raise usage.UsageError(str(self.subOptions))
+ if not self.tapLookup.has_key(self.subCommand):
+ raise usage.UsageError("Please select one of: "+
+ ' '.join(self.tapLookup))
+
+
+def run():
+ options = FirstPassOptions()
+ try:
+ options.parseOptions(sys.argv[1:])
+ except usage.UsageError, e:
+ print e
+ sys.exit(2)
+ except KeyboardInterrupt:
+ sys.exit(1)
+
+ plg = options.tapLookup[options.subCommand]
+ if not IServiceMaker.providedBy(plg):
+ plg = plg.load()
+ ser = plg.makeService(options.subOptions)
+ addToApplication(ser,
+ options.subCommand, options['append'], options['appname'],
+ options['type'], options['encrypted'],
+ options['uid'], options['gid'])
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/tap2deb.py b/vendor/Twisted-10.0.0/twisted/scripts/tap2deb.py
new file mode 100644
index 0000000000..21c91eb18c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/tap2deb.py
@@ -0,0 +1,281 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+
+import sys, os, string, shutil
+
+from twisted.python import usage
+
+class MyOptions(usage.Options):
+ optFlags = [["unsigned", "u"]]
+ optParameters = [["tapfile", "t", "twistd.tap"],
+ ["maintainer", "m", "", "The maintainer's name and email in a specific format: "
+ "'John Doe <johndoe@example.com>'"],
+ ["protocol", "p", ""],
+ ["description", "e", ""],
+ ["long_description", "l", ""],
+ ["set-version", "V", "1.0"],
+ ["debfile", "d", None],
+ ["type", "y", "tap", "type of configuration: 'tap', 'xml, 'source' or 'python' for .tac files"]]
+
+ #zsh_altArgDescr = {"foo":"use this description for foo instead"}
+ #zsh_multiUse = ["foo", "bar"]
+ #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
+ zsh_actions = {"type":"(tap xml source python)"}
+ #zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
+
+ def postOptions(self):
+ if not self["maintainer"]:
+ raise usage.UsageError, "maintainer must be specified."
+
+
+type_dict = {
+'tap': 'file',
+'python': 'python',
+'source': 'source',
+'xml': 'xml',
+}
+
+def save_to_file(file, text):
+ f = open(file, 'w')
+ f.write(text)
+ f.close()
+
+
+def run():
+
+ try:
+ config = MyOptions()
+ config.parseOptions()
+ except usage.error, ue:
+ sys.exit("%s: %s" % (sys.argv[0], ue))
+
+ tap_file = config['tapfile']
+ base_tap_file = os.path.basename(config['tapfile'])
+ protocol = (config['protocol'] or os.path.splitext(base_tap_file)[0])
+ deb_file = config['debfile'] or 'twisted-'+protocol
+ version = config['set-version']
+ maintainer = config['maintainer']
+ description = config['description'] or ('A Twisted-based server for %(protocol)s' %
+ vars())
+ long_description = config['long_description'] or 'Automatically created by tap2deb'
+ twistd_option = type_dict[config['type']]
+ date = string.strip(os.popen('822-date').read())
+ directory = deb_file + '-' + version
+ python_version = '%s.%s' % sys.version_info[:2]
+
+ if os.path.exists(os.path.join('.build', directory)):
+ os.system('rm -rf %s' % os.path.join('.build', directory))
+ os.makedirs(os.path.join('.build', directory, 'debian'))
+
+ shutil.copy(tap_file, os.path.join('.build', directory))
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'README.Debian'),
+ '''This package was auto-generated by tap2deb\n''')
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'conffiles'),
+ '''\
+/etc/init.d/%(deb_file)s
+/etc/default/%(deb_file)s
+/etc/%(base_tap_file)s
+''' % vars())
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'default'),
+ '''\
+pidfile=/var/run/%(deb_file)s.pid
+rundir=/var/lib/%(deb_file)s/
+file=/etc/%(tap_file)s
+logfile=/var/log/%(deb_file)s.log
+ ''' % vars())
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'init.d'),
+ '''\
+#!/bin/sh
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin
+
+pidfile=/var/run/%(deb_file)s.pid \
+rundir=/var/lib/%(deb_file)s/ \
+file=/etc/%(tap_file)s \
+logfile=/var/log/%(deb_file)s.log
+
+[ -r /etc/default/%(deb_file)s ] && . /etc/default/%(deb_file)s
+
+test -x /usr/bin/twistd%(python_version)s || exit 0
+test -r $file || exit 0
+test -r /usr/share/%(deb_file)s/package-installed || exit 0
+
+
+case "$1" in
+ start)
+ echo -n "Starting %(deb_file)s: twistd"
+ start-stop-daemon --start --quiet --exec /usr/bin/twistd%(python_version)s -- \
+ --pidfile=$pidfile \
+ --rundir=$rundir \
+ --%(twistd_option)s=$file \
+ --logfile=$logfile
+ echo "."
+ ;;
+
+ stop)
+ echo -n "Stopping %(deb_file)s: twistd"
+ start-stop-daemon --stop --quiet \
+ --pidfile $pidfile
+ echo "."
+ ;;
+
+ restart)
+ $0 stop
+ $0 start
+ ;;
+
+ force-reload)
+ $0 restart
+ ;;
+
+ *)
+ echo "Usage: /etc/init.d/%(deb_file)s {start|stop|restart|force-reload}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
+''' % vars())
+
+ os.chmod(os.path.join('.build', directory, 'debian', 'init.d'), 0755)
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'postinst'),
+ '''\
+#!/bin/sh
+update-rc.d %(deb_file)s defaults >/dev/null
+invoke-rc.d %(deb_file)s start
+''' % vars())
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'prerm'),
+ '''\
+#!/bin/sh
+invoke-rc.d %(deb_file)s stop
+''' % vars())
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'postrm'),
+ '''\
+#!/bin/sh
+if [ "$1" = purge ]; then
+ update-rc.d %(deb_file)s remove >/dev/null
+fi
+''' % vars())
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'changelog'),
+ '''\
+%(deb_file)s (%(version)s) unstable; urgency=low
+
+ * Created by tap2deb
+
+ -- %(maintainer)s %(date)s
+
+''' % vars())
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'control'),
+ '''\
+Source: %(deb_file)s
+Section: net
+Priority: extra
+Maintainer: %(maintainer)s
+Build-Depends-Indep: debhelper
+Standards-Version: 3.5.6
+
+Package: %(deb_file)s
+Architecture: all
+Depends: python%(python_version)s-twisted
+Description: %(description)s
+ %(long_description)s
+''' % vars())
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'copyright'),
+ '''\
+This package was auto-debianized by %(maintainer)s on
+%(date)s
+
+It was auto-generated by tap2deb
+
+Upstream Author(s):
+Moshe Zadka <moshez@twistedmatrix.com> -- tap2deb author
+
+Copyright:
+
+Insert copyright here.
+''' % vars())
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'dirs'),
+ '''\
+etc/init.d
+etc/default
+var/lib/%(deb_file)s
+usr/share/doc/%(deb_file)s
+usr/share/%(deb_file)s
+''' % vars())
+
+ save_to_file(os.path.join('.build', directory, 'debian', 'rules'),
+ '''\
+#!/usr/bin/make -f
+
+export DH_COMPAT=1
+
+build: build-stamp
+build-stamp:
+ dh_testdir
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp install-stamp
+ dh_clean
+
+install: install-stamp
+install-stamp: build-stamp
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install the package into debian/tmp.
+ cp %(base_tap_file)s debian/tmp/etc/
+ cp debian/init.d debian/tmp/etc/init.d/%(deb_file)s
+ cp debian/default debian/tmp/etc/default/%(deb_file)s
+ cp debian/copyright debian/tmp/usr/share/doc/%(deb_file)s/
+ cp debian/README.Debian debian/tmp/usr/share/doc/%(deb_file)s/
+ touch debian/tmp/usr/share/%(deb_file)s/package-installed
+ touch install-stamp
+
+binary-arch: build install
+
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_strip
+ dh_compress
+ dh_installchangelogs
+ dh_fixperms
+ dh_installdeb
+ dh_shlibdeps
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+source diff:
+ @echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install
+''' % vars())
+
+ os.chmod(os.path.join('.build', directory, 'debian', 'rules'), 0755)
+
+ os.chdir('.build/%(directory)s' % vars())
+ os.system('dpkg-buildpackage -rfakeroot'+ ['', ' -uc -us'][config['unsigned']])
+
+if __name__ == '__main__':
+ run()
+
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/tap2rpm.py b/vendor/Twisted-10.0.0/twisted/scripts/tap2rpm.py
new file mode 100755
index 0000000000..8a179ee2ce
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/tap2rpm.py
@@ -0,0 +1,273 @@
+# based off the tap2deb.py file
+# tap2rpm.py built by Sean Reifschneider, <jafo@tummy.com>
+
+# TODO: need to implement log-file rotation
+
+import sys, os, shutil, time, glob
+
+from twisted.python import usage
+from twisted.scripts import tap2deb
+
+
+#################################
+# data that goes in /etc/inittab
+initFileData = '''\
+#!/bin/sh
+#
+# Startup script for a Twisted service.
+#
+# chkconfig: - 85 15
+# description: Start-up script for the Twisted service "%(tap_file)s".
+
+PATH=/usr/bin:/bin:/usr/sbin:/sbin
+
+pidfile=/var/run/%(rpm_file)s.pid
+rundir=/var/lib/twisted-taps/%(rpm_file)s/
+file=/etc/twisted-taps/%(tap_file)s
+logfile=/var/log/%(rpm_file)s.log
+
+# load init function library
+. /etc/init.d/functions
+
+[ -r /etc/default/%(rpm_file)s ] && . /etc/default/%(rpm_file)s
+
+# check for required files
+if [ ! -x /usr/bin/twistd ]
+then
+ echo "$0: Aborting, no /usr/bin/twistd found"
+ exit 0
+fi
+if [ ! -r "$file" ]
+then
+ echo "$0: Aborting, no file $file found."
+ exit 0
+fi
+
+# set up run directory if necessary
+if [ ! -d "${rundir}" ]
+then
+ mkdir -p "${rundir}"
+fi
+
+
+case "$1" in
+ start)
+ echo -n "Starting %(rpm_file)s: twistd"
+ daemon twistd \\
+ --pidfile=$pidfile \\
+ --rundir=$rundir \\
+ --%(twistd_option)s=$file \\
+ --logfile=$logfile
+ status %(rpm_file)s
+ ;;
+
+ stop)
+ echo -n "Stopping %(rpm_file)s: twistd"
+ kill `cat "${pidfile}"`
+ status %(rpm_file)s
+ ;;
+
+ restart)
+ "${0}" stop
+ "${0}" start
+ ;;
+
+ *)
+ echo "Usage: ${0} {start|stop|restart|}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
+'''
+
+#######################################
+# the data for creating the spec file
+specFileData = '''\
+Summary: %(description)s
+Name: %(rpm_file)s
+Version: %(version)s
+Release: 1
+Copyright: Unknown
+Group: Networking/Daemons
+Source: %(tarfile_basename)s
+BuildRoot: /var/tmp/%%{name}-%%{version}-root
+Requires: /usr/bin/twistd
+BuildArch: noarch
+
+%%description
+%(long_description)s
+
+%%prep
+%%setup
+%%build
+
+%%install
+[ ! -z "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != '/' ] \
+ && rm -rf "$RPM_BUILD_ROOT"
+mkdir -p "$RPM_BUILD_ROOT"/etc/twisted-taps
+mkdir -p "$RPM_BUILD_ROOT"/etc/init.d
+mkdir -p "$RPM_BUILD_ROOT"/var/lib/twisted-taps
+cp "%(tap_file)s" "$RPM_BUILD_ROOT"/etc/twisted-taps/
+cp "%(rpm_file)s.init" "$RPM_BUILD_ROOT"/etc/init.d/"%(rpm_file)s"
+
+%%clean
+[ ! -z "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != '/' ] \
+ && rm -rf "$RPM_BUILD_ROOT"
+
+%%post
+/sbin/chkconfig --add %(rpm_file)s
+/sbin/chkconfig --level 35 %(rpm_file)s
+/etc/init.d/%(rpm_file)s start
+
+%%preun
+/etc/init.d/%(rpm_file)s stop
+/sbin/chkconfig --del %(rpm_file)s
+
+%%files
+%%defattr(-,root,root)
+%%attr(0755,root,root) /etc/init.d/%(rpm_file)s
+%%attr(0660,root,root) /etc/twisted-taps/%(tap_file)s
+
+%%changelog
+* %(date)s %(maintainer)s
+- Created by tap2rpm: %(rpm_file)s (%(version)s)
+'''
+
+###############################
+class MyOptions(usage.Options):
+ optFlags = [["unsigned", "u"]]
+ optParameters = [
+ ["tapfile", "t", "twistd.tap"],
+ ["maintainer", "m", ""],
+ ["protocol", "p", ""],
+ ["description", "e", ""],
+ ["long_description", "l", ""],
+ ["set-version", "V", "1.0"],
+ ["rpmfile", "r", None],
+ ["type", "y", "tap", "type of configuration: 'tap', 'xml, "
+ "'source' or 'python'"],
+ ]
+
+ #zsh_altArgDescr = {"foo":"use this description for foo instead"}
+ #zsh_multiUse = ["foo", "bar"]
+ #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
+ zsh_actions = {"type":"(tap xml source python)",
+ "rpmfile":'_files -g "*.rpm"'}
+ #zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
+
+
+type_dict = {
+ 'tap': 'file',
+ 'python': 'python',
+ 'source': 'source',
+ 'xml': 'xml',
+}
+
+
+##########################
+def makeBuildDir(baseDir):
+ '''Set up the temporary directory for building RPMs.
+ Returns: Tuple: ( buildDir, rpmrcFile )
+ '''
+ import random, string
+
+ # make top directory
+ oldMask = os.umask(0077)
+ while 1:
+ tmpDir = os.path.join(baseDir, 'tap2rpm-%s-%s' % ( os.getpid(),
+ random.randint(0, 999999999) ))
+ if not os.path.exists(tmpDir):
+ os.makedirs(tmpDir)
+ break
+ os.umask(oldMask)
+
+ # set up initial directory contents
+ os.makedirs(os.path.join(tmpDir, 'RPMS', 'noarch'))
+ os.makedirs(os.path.join(tmpDir, 'SPECS'))
+ os.makedirs(os.path.join(tmpDir, 'BUILD'))
+ os.makedirs(os.path.join(tmpDir, 'SOURCES'))
+ os.makedirs(os.path.join(tmpDir, 'SRPMS'))
+
+ # set up rpmmacros file
+ macroFile = os.path.join(tmpDir, 'rpmmacros')
+ rcFile = os.path.join(tmpDir, 'rpmrc')
+ rpmrcData = open('/usr/lib/rpm/rpmrc', 'r').read()
+ rpmrcData = string.replace(rpmrcData, '~/.rpmmacros', macroFile)
+ fp = open(macroFile, 'w')
+ fp.write('%%_topdir %s\n' % tmpDir)
+ fp.close()
+
+ # set up the rpmrc file
+ fp = open(rcFile, 'w')
+ fp.write(rpmrcData)
+ fp.close()
+
+ return(( tmpDir, rcFile ))
+
+
+##########
+def run():
+ # parse options
+ try:
+ config = MyOptions()
+ config.parseOptions()
+ except usage.error, ue:
+ sys.exit("%s: %s" % (sys.argv[0], ue))
+
+ # set up some useful local variables
+ tap_file = config['tapfile']
+ base_tap_file = os.path.basename(config['tapfile'])
+ protocol = (config['protocol'] or os.path.splitext(base_tap_file)[0])
+ rpm_file = config['rpmfile'] or 'twisted-'+protocol
+ version = config['set-version']
+ maintainer = config['maintainer']
+ description = config['description'] or ('A TCP server for %(protocol)s' %
+ vars())
+ long_description = (config['long_description']
+ or "Automatically created by tap2rpm")
+ twistd_option = type_dict[config['type']]
+ date = time.strftime('%a %b %d %Y', time.localtime(time.time()))
+ directory = rpm_file + '-' + version
+ python_version = '%s.%s' % sys.version_info[:2]
+
+ # set up a blank maintainer if not present
+ if not maintainer:
+ maintainer = 'tap2rpm'
+
+ # create source archive directory
+ tmp_dir, rpmrc_file = makeBuildDir('/var/tmp')
+ source_dir = os.path.join(tmp_dir, directory)
+ os.makedirs(source_dir)
+
+ # populate source directory
+ tarfile_name = source_dir + '.tar.gz'
+ tarfile_basename = os.path.basename(tarfile_name)
+ tap2deb.save_to_file(os.path.join(source_dir, '%s.spec' % rpm_file),
+ specFileData % vars())
+ tap2deb.save_to_file(os.path.join(source_dir, '%s.init' % rpm_file),
+ initFileData % vars())
+ shutil.copy(tap_file, source_dir)
+
+ # create source tar
+ os.system('cd "%(tmp_dir)s"; tar cfz "%(tarfile_name)s" "%(directory)s"'
+ % vars())
+
+ # build rpm
+ print 'Starting build...'
+ print '=' * 70
+ sys.stdout.flush()
+ os.system('rpmbuild -ta --rcfile "%s" %s' % ( rpmrc_file, tarfile_name ))
+ print 'Done with build...'
+ print '=' * 70
+
+ # copy the RPMs to the local directory
+ rpm_path = glob.glob(os.path.join(tmp_dir, 'RPMS', 'noarch', '*'))[0]
+ srpm_path = glob.glob(os.path.join(tmp_dir, 'SRPMS', '*'))[0]
+ print 'Writing "%s"...' % os.path.basename(rpm_path)
+ shutil.copy(rpm_path, '.')
+ print 'Writing "%s"...' % os.path.basename(srpm_path)
+ shutil.copy(srpm_path, '.')
+
+ # remove the build directory
+ shutil.rmtree(tmp_dir)
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/tapconvert.py b/vendor/Twisted-10.0.0/twisted/scripts/tapconvert.py
new file mode 100644
index 0000000000..4616e6dfb8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/tapconvert.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.python import usage
+from twisted.application import app
+from twisted.persisted import sob
+import sys, getpass
+
+class ConvertOptions(usage.Options):
+ synopsis = "Usage: tapconvert [options]"
+ optParameters = [
+ ['in', 'i', None, "The filename of the tap to read from"],
+ ['out', 'o', None, "A filename to write the tap to"],
+ ['typein', 'f', 'guess',
+ "The format to use; this can be 'guess', 'python', "
+ "'pickle', 'xml', or 'source'."],
+ ['typeout', 't', 'source',
+ "The output format to use; this can be 'pickle', 'xml', or 'source'."],
+ ]
+
+ optFlags = [
+ ['decrypt', 'd', "The specified tap/aos/xml file is encrypted."],
+ ['encrypt', 'e', "Encrypt file before writing"]
+ ]
+ #zsh_altArgDescr = {"foo":"use this description for foo instead"}
+ #zsh_multiUse = ["foo", "bar"]
+ #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
+ zsh_actions = {"typein":"(guess python pickle xml source)",
+ "typeout":"(pickle xml source)"}
+ zsh_actionDescr = {"in":"tap file to read from",
+ "out":"tap file to write to"}
+
+ def postOptions(self):
+ if self['in'] is None:
+ raise usage.UsageError("%s\nYou must specify the input filename."
+ % self)
+ if self["typein"] == "guess":
+ try:
+ self["typein"] = sob.guessType(self["in"])
+ except KeyError:
+ raise usage.UsageError("Could not guess type for '%s'" %
+ self["typein"])
+
+def run():
+ options = ConvertOptions()
+ try:
+ options.parseOptions(sys.argv[1:])
+ except usage.UsageError, e:
+ print e
+ else:
+ app.convertStyle(options["in"], options["typein"],
+ options.opts['decrypt'] or getpass.getpass('Passphrase: '),
+ options["out"], options['typeout'], options["encrypt"])
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/test/__init__.py b/vendor/Twisted-10.0.0/twisted/scripts/test/__init__.py
new file mode 100644
index 0000000000..d903f60322
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/test/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test package for L{twisted.scripts}.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/test/test_mktap.py b/vendor/Twisted-10.0.0/twisted/scripts/test/test_mktap.py
new file mode 100644
index 0000000000..df3d4f202a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/test/test_mktap.py
@@ -0,0 +1,122 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.scripts.mktap}.
+"""
+
+import sys
+try:
+ import pwd, grp
+except ImportError:
+ pwd = None
+
+from twisted.trial.unittest import TestCase
+
+from twisted.scripts.mktap import run, getid, loadPlugins
+from twisted.application.service import IProcess, loadApplication
+from twisted.test.test_twistd import patchUserDatabase
+from twisted.plugins.twisted_ftp import TwistedFTP
+
+
+class RunTests(TestCase):
+ """
+ Tests for L{twisted.scripts.mktap.run}.
+ """
+ def setUp(self):
+ """
+ Save the original value of L{sys.argv} so that tests can change it
+ as necessary.
+ """
+ self.argv = sys.argv[:]
+
+
+ def tearDown(self):
+ """
+ Restore the original value of L{sys.argv}.
+ """
+ sys.argv[:] = self.argv
+
+
+ def _saveConfiguredIDTest(self, argv, uid, gid):
+ """
+ Test that when L{run} is invoked and L{sys.argv} has the given
+ value, the resulting application has the specified UID and GID.
+
+ @type argv: C{list} of C{str}
+ @param argv: The value to which to set L{sys.argv} before calling L{run}.
+
+ @type uid: C{int}
+ @param uid: The expected value for the resulting application's
+ L{IProcess.uid}.
+
+ @type gid: C{int}
+ @param gid: The expected value for the resulting application's
+ L{IProcess.gid}.
+ """
+ sys.argv = argv
+ run()
+ app = loadApplication("ftp.tap", "pickle", None)
+ process = IProcess(app)
+ self.assertEqual(process.uid, uid)
+ self.assertEqual(process.gid, gid)
+
+
+ def test_getNumericID(self):
+ """
+ L{run} extracts numeric UID and GID information from the command
+ line and persists it with the application object.
+ """
+ uid = 1234
+ gid = 4321
+ self._saveConfiguredIDTest(
+ ["mktap", "--uid", str(uid), "--gid", str(gid), "ftp"],
+ uid, gid)
+
+
+ def test_getNameID(self):
+ """
+ L{run} extracts name UID and GID information from the command
+ line and persists it with the application object.
+ """
+ user = "foo"
+ uid = 1234
+ group = "bar"
+ gid = 4321
+ patchUserDatabase(self.patch, user, uid, group, gid)
+ self._saveConfiguredIDTest(
+ ["mktap", "--uid", user, "--gid", group, "ftp"],
+ uid, gid)
+ if pwd is None:
+ test_getNameID.skip = (
+ "Username/UID Group name/GID translation requires pwd and grp "
+ "modules.")
+
+
+
+class HelperTests(TestCase):
+ """
+ Tests for miscellaneous utility functions related to mktap.
+ """
+ def test_getid(self):
+ """
+ L{getid} returns a two-tuple of integers giving the numeric values of
+ the strings it is passed.
+ """
+ uid = 1234
+ gid = 4321
+ self.assertEqual(getid(str(uid), str(gid)), (uid, gid))
+
+
+ def test_loadPlugins(self):
+ """
+ L{loadPlugins} returns a C{dict} mapping tap names to tap plugins.
+ """
+ plugins = loadPlugins()
+ self.assertTrue(plugins, "There should be at least one plugin.")
+ # Make sure the mapping is set up properly.
+ for k, v in plugins.iteritems():
+ self.assertEqual(k, v.tapname)
+
+ # Make sure one of the always-available builtin plugins is there.
+ self.assertIdentical(plugins['ftp'], TwistedFTP)
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/tkunzip.py b/vendor/Twisted-10.0.0/twisted/scripts/tkunzip.py
new file mode 100644
index 0000000000..6332c12247
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/tkunzip.py
@@ -0,0 +1,286 @@
+"""Post-install GUI to compile to pyc and unpack twisted doco"""
+
+from __future__ import generators
+
+import sys
+import zipfile
+import py_compile
+
+# we're going to ignore failures to import tkinter and fall back
+# to using the console if the required dll is not found
+
+# Scary kludge to work around tk84.dll bug:
+# https://sourceforge.net/tracker/index.php?func=detail&aid=814654&group_id=5470&atid=105470
+# Without which(): you get a windows missing-dll popup message
+from twisted.python.procutils import which
+tkdll='tk84.dll'
+if which(tkdll) or which('DLLs/%s' % tkdll):
+ try:
+ import Tkinter
+ from Tkinter import *
+ from twisted.internet import tksupport
+ except ImportError:
+ pass
+
+# twisted
+from twisted.internet import reactor, defer
+from twisted.python import failure, log, zipstream, util, usage, log
+# local
+import os.path
+
+class ProgressBar:
+ def __init__(self, master=None, orientation="horizontal",
+ min=0, max=100, width=100, height=18,
+ doLabel=1, appearance="sunken",
+ fillColor="blue", background="gray",
+ labelColor="yellow", labelFont="Arial",
+ labelText="", labelFormat="%d%%",
+ value=0, bd=2):
+ # preserve various values
+ self.master=master
+ self.orientation=orientation
+ self.min=min
+ self.max=max
+ self.width=width
+ self.height=height
+ self.doLabel=doLabel
+ self.fillColor=fillColor
+ self.labelFont= labelFont
+ self.labelColor=labelColor
+ self.background=background
+ self.labelText=labelText
+ self.labelFormat=labelFormat
+ self.value=value
+ self.frame=Frame(master, relief=appearance, bd=bd)
+ self.canvas=Canvas(self.frame, height=height, width=width, bd=0,
+ highlightthickness=0, background=background)
+ self.scale=self.canvas.create_rectangle(0, 0, width, height,
+ fill=fillColor)
+ self.label=self.canvas.create_text(self.canvas.winfo_reqwidth() / 2,
+ height / 2, text=labelText,
+ anchor="c", fill=labelColor,
+ font=self.labelFont)
+ self.update()
+ self.canvas.pack(side='top', fill='x', expand='no')
+
+ def pack(self, *args, **kwargs):
+ self.frame.pack(*args, **kwargs)
+
+ def updateProgress(self, newValue, newMax=None):
+ if newMax:
+ self.max = newMax
+ self.value = newValue
+ self.update()
+
+ def update(self):
+ # Trim the values to be between min and max
+ value=self.value
+ if value > self.max:
+ value = self.max
+ if value < self.min:
+ value = self.min
+ # Adjust the rectangle
+ if self.orientation == "horizontal":
+ self.canvas.coords(self.scale, 0, 0,
+ float(value) / self.max * self.width, self.height)
+ else:
+ self.canvas.coords(self.scale, 0,
+ self.height - (float(value) /
+ self.max*self.height),
+ self.width, self.height)
+ # Now update the colors
+ self.canvas.itemconfig(self.scale, fill=self.fillColor)
+ self.canvas.itemconfig(self.label, fill=self.labelColor)
+ # And update the label
+ if self.doLabel:
+ if value:
+ if value >= 0:
+ pvalue = int((float(value) / float(self.max)) *
+ 100.0)
+ else:
+ pvalue = 0
+ self.canvas.itemconfig(self.label, text=self.labelFormat
+ % pvalue)
+ else:
+ self.canvas.itemconfig(self.label, text='')
+ else:
+ self.canvas.itemconfig(self.label, text=self.labelFormat %
+ self.labelText)
+ self.canvas.update_idletasks()
+
+
+class Progressor:
+ """A base class to make it simple to hook a progress bar up to a process.
+ """
+ def __init__(self, title, *args, **kwargs):
+ self.title=title
+ self.stopping=0
+ self.bar=None
+ self.iterator=None
+ self.remaining=1000
+
+ def setBar(self, bar, max):
+ self.bar=bar
+ bar.updateProgress(0, max)
+ return self
+
+ def setIterator(self, iterator):
+ self.iterator=iterator
+ return self
+
+ def updateBar(self, deferred):
+ b=self.bar
+ try:
+ b.updateProgress(b.max - self.remaining)
+ except TclError:
+ self.stopping=1
+ except:
+ deferred.errback(failure.Failure())
+
+ def processAll(self, root):
+ assert self.bar and self.iterator, "must setBar and setIterator"
+ self.root=root
+ root.title(self.title)
+ d=defer.Deferred()
+ d.addErrback(log.err)
+ reactor.callLater(0.1, self.processOne, d)
+ return d
+
+ def processOne(self, deferred):
+ if self.stopping:
+ deferred.callback(self.root)
+ return
+
+ try:
+ self.remaining=self.iterator.next()
+ except StopIteration:
+ self.stopping=1
+ except:
+ deferred.errback(failure.Failure())
+
+ if self.remaining%10==0:
+ reactor.callLater(0, self.updateBar, deferred)
+ if self.remaining%100==0:
+ log.msg(self.remaining)
+ reactor.callLater(0, self.processOne, deferred)
+
+def compiler(path):
+ """A generator for compiling files to .pyc"""
+ def justlist(arg, directory, names):
+ pynames=[os.path.join(directory, n) for n in names
+ if n.endswith('.py')]
+ arg.extend(pynames)
+ all=[]
+ os.path.walk(path, justlist, all)
+
+ remaining=len(all)
+ i=zip(all, range(remaining-1, -1, -1))
+ for f, remaining in i:
+ py_compile.compile(f)
+ yield remaining
+
+class TkunzipOptions(usage.Options):
+ optParameters=[["zipfile", "z", "", "a zipfile"],
+ ["ziptargetdir", "t", ".", "where to extract zipfile"],
+ ["compiledir", "c", "", "a directory to compile"],
+ ]
+ optFlags=[["use-console", "C", "show in the console, not graphically"],
+ ["shell-exec", "x", """\
+spawn a new console to show output (implies -C)"""],
+ ]
+
+def countPys(countl, directory, names):
+ sofar=countl[0]
+ sofar=sofar+len([f for f in names if f.endswith('.py')])
+ countl[0]=sofar
+ return sofar
+
+def countPysRecursive(path):
+ countl=[0]
+ os.path.walk(path, countPys, countl)
+ return countl[0]
+
+def run(argv=sys.argv):
+ log.startLogging(file('tkunzip.log', 'w'))
+ opt=TkunzipOptions()
+ try:
+ opt.parseOptions(argv[1:])
+ except usage.UsageError, e:
+ print str(opt)
+ print str(e)
+ sys.exit(1)
+
+ if opt['use-console']:
+ # this should come before shell-exec to prevent infinite loop
+ return doItConsolicious(opt)
+ if opt['shell-exec'] or not 'Tkinter' in sys.modules:
+ from distutils import sysconfig
+ from twisted.scripts import tkunzip
+ myfile=tkunzip.__file__
+ exe=os.path.join(sysconfig.get_config_var('prefix'), 'python.exe')
+ return os.system('%s %s --use-console %s' % (exe, myfile,
+ ' '.join(argv[1:])))
+ return doItTkinterly(opt)
+
+def doItConsolicious(opt):
+ # reclaim stdout/stderr from log
+ sys.stdout = sys.__stdout__
+ sys.stderr = sys.__stderr__
+ if opt['zipfile']:
+ print 'Unpacking documentation...'
+ for n in zipstream.unzipIter(opt['zipfile'], opt['ziptargetdir']):
+ if n % 100 == 0:
+ print n,
+ if n % 1000 == 0:
+ print
+ print 'Done unpacking.'
+
+ if opt['compiledir']:
+ print 'Compiling to pyc...'
+ import compileall
+ compileall.compile_dir(opt["compiledir"])
+ print 'Done compiling.'
+
+def doItTkinterly(opt):
+ root=Tkinter.Tk()
+ root.withdraw()
+ root.title('One Moment.')
+ root.protocol('WM_DELETE_WINDOW', reactor.stop)
+ tksupport.install(root)
+
+ prog=ProgressBar(root, value=0, labelColor="black", width=200)
+ prog.pack()
+
+ # callback immediately
+ d=defer.succeed(root).addErrback(log.err)
+
+ def deiconify(root):
+ root.deiconify()
+ return root
+
+ d.addCallback(deiconify)
+
+ if opt['zipfile']:
+ uz=Progressor('Unpacking documentation...')
+ max=zipstream.countZipFileChunks(opt['zipfile'], 4096)
+ uz.setBar(prog, max)
+ uz.setIterator(zipstream.unzipIterChunky(opt['zipfile'],
+ opt['ziptargetdir']))
+ d.addCallback(uz.processAll)
+
+ if opt['compiledir']:
+ comp=Progressor('Compiling to pyc...')
+ comp.setBar(prog, countPysRecursive(opt['compiledir']))
+ comp.setIterator(compiler(opt['compiledir']))
+ d.addCallback(comp.processAll)
+
+ def stop(ignore):
+ reactor.stop()
+ root.destroy()
+ d.addCallback(stop)
+
+ reactor.run()
+
+
+if __name__=='__main__':
+ run()
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/trial.py b/vendor/Twisted-10.0.0/twisted/scripts/trial.py
new file mode 100644
index 0000000000..a454d170e1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/trial.py
@@ -0,0 +1,370 @@
+# -*- test-case-name: twisted.trial.test.test_script -*-
+
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import sys, os, random, gc, time, warnings
+
+from twisted.internet import defer
+from twisted.application import app
+from twisted.python import usage, reflect, failure
+from twisted import plugin
+from twisted.python.util import spewer
+from twisted.python.compat import set
+from twisted.trial import runner, itrial, reporter
+
+
+# Yea, this is stupid. Leave it for for command-line compatibility for a
+# while, though.
+TBFORMAT_MAP = {
+ 'plain': 'default',
+ 'default': 'default',
+ 'emacs': 'brief',
+ 'brief': 'brief',
+ 'cgitb': 'verbose',
+ 'verbose': 'verbose'
+ }
+
+
+def _parseLocalVariables(line):
+ """Accepts a single line in Emacs local variable declaration format and
+ returns a dict of all the variables {name: value}.
+ Raises ValueError if 'line' is in the wrong format.
+
+ See http://www.gnu.org/software/emacs/manual/html_node/File-Variables.html
+ """
+ paren = '-*-'
+ start = line.find(paren) + len(paren)
+ end = line.rfind(paren)
+ if start == -1 or end == -1:
+ raise ValueError("%r not a valid local variable declaration" % (line,))
+ items = line[start:end].split(';')
+ localVars = {}
+ for item in items:
+ if len(item.strip()) == 0:
+ continue
+ split = item.split(':')
+ if len(split) != 2:
+ raise ValueError("%r contains invalid declaration %r"
+ % (line, item))
+ localVars[split[0].strip()] = split[1].strip()
+ return localVars
+
+
+def loadLocalVariables(filename):
+ """Accepts a filename and attempts to load the Emacs variable declarations
+ from that file, simulating what Emacs does.
+
+ See http://www.gnu.org/software/emacs/manual/html_node/File-Variables.html
+ """
+ f = file(filename, "r")
+ lines = [f.readline(), f.readline()]
+ f.close()
+ for line in lines:
+ try:
+ return _parseLocalVariables(line)
+ except ValueError:
+ pass
+ return {}
+
+
+def getTestModules(filename):
+ testCaseVar = loadLocalVariables(filename).get('test-case-name', None)
+ if testCaseVar is None:
+ return []
+ return testCaseVar.split(',')
+
+
+def isTestFile(filename):
+ """Returns true if 'filename' looks like a file containing unit tests.
+ False otherwise. Doesn't care whether filename exists.
+ """
+ basename = os.path.basename(filename)
+ return (basename.startswith('test_')
+ and os.path.splitext(basename)[1] == ('.py'))
+
+
+def _zshReporterAction():
+ return "(%s)" % (" ".join([p.longOpt for p in plugin.getPlugins(itrial.IReporter)]),)
+
+class Options(usage.Options, app.ReactorSelectionMixin):
+ synopsis = """%s [options] [[file|package|module|TestCase|testmethod]...]
+ """ % (os.path.basename(sys.argv[0]),)
+ longdesc = ("trial loads and executes a suite of unit tests, obtained "
+ "from modules, packages and files listed on the command line.")
+
+ optFlags = [["help", "h"],
+ ["rterrors", "e", "realtime errors, print out tracebacks as "
+ "soon as they occur"],
+ ["debug", "b", "Run tests in the Python debugger. Will load "
+ "'.pdbrc' from current directory if it exists."],
+ ["debug-stacktraces", "B", "Report Deferred creation and "
+ "callback stack traces"],
+ ["nopm", None, "don't automatically jump into debugger for "
+ "postmorteming of exceptions"],
+ ["dry-run", 'n', "do everything but run the tests"],
+ ["force-gc", None, "Have Trial run gc.collect() before and "
+ "after each test case."],
+ ["profile", None, "Run tests under the Python profiler"],
+ ["unclean-warnings", None,
+ "Turn dirty reactor errors into warnings"],
+ ["until-failure", "u", "Repeat test until it fails"],
+ ["no-recurse", "N", "Don't recurse into packages"],
+ ['help-reporters', None,
+ "Help on available output plugins (reporters)"]
+ ]
+
+ optParameters = [
+ ["logfile", "l", "test.log", "log file name"],
+ ["random", "z", None,
+ "Run tests in random order using the specified seed"],
+ ['temp-directory', None, '_trial_temp',
+ 'Path to use as working directory for tests.'],
+ ['reporter', None, 'verbose',
+ 'The reporter to use for this test run. See --help-reporters for '
+ 'more info.']]
+
+ zsh_actions = {"tbformat":"(plain emacs cgitb)",
+ "reporter":_zshReporterAction}
+ zsh_actionDescr = {"logfile":"log file name",
+ "random":"random seed"}
+ zsh_extras = ["*:file|module|package|TestCase|testMethod:_files -g '*.py'"]
+
+ fallbackReporter = reporter.TreeReporter
+ extra = None
+ tracer = None
+
+ def __init__(self):
+ self['tests'] = set()
+ usage.Options.__init__(self)
+
+ def opt_coverage(self):
+ """
+ Generate coverage information in the _trial_temp/coverage. Requires
+ Python 2.3.3.
+ """
+ coverdir = 'coverage'
+ print "Setting coverage directory to %s." % (coverdir,)
+ import trace
+
+ # begin monkey patch ---------------------------
+ # Before Python 2.4, this function asserted that 'filename' had
+ # to end with '.py' This is wrong for at least two reasons:
+ # 1. We might be wanting to find executable line nos in a script
+ # 2. The implementation should use os.splitext
+ # This monkey patch is the same function as in the stdlib (v2.3)
+ # but with the assertion removed.
+ def find_executable_linenos(filename):
+ """Return dict where keys are line numbers in the line number
+ table.
+ """
+ #assert filename.endswith('.py') # YOU BASTARDS
+ try:
+ prog = open(filename).read()
+ prog = '\n'.join(prog.splitlines()) + '\n'
+ except IOError, err:
+ sys.stderr.write("Not printing coverage data for %r: %s\n"
+ % (filename, err))
+ sys.stderr.flush()
+ return {}
+ code = compile(prog, filename, "exec")
+ strs = trace.find_strings(filename)
+ return trace.find_lines(code, strs)
+
+ trace.find_executable_linenos = find_executable_linenos
+ # end monkey patch ------------------------------
+
+ self.coverdir = os.path.abspath(os.path.join(self['temp-directory'], coverdir))
+ self.tracer = trace.Trace(count=1, trace=0)
+ sys.settrace(self.tracer.globaltrace)
+
+ def opt_testmodule(self, filename):
+ "Filename to grep for test cases (-*- test-case-name)"
+ # If the filename passed to this parameter looks like a test module
+ # we just add that to the test suite.
+ #
+ # If not, we inspect it for an Emacs buffer local variable called
+ # 'test-case-name'. If that variable is declared, we try to add its
+ # value to the test suite as a module.
+ #
+ # This parameter allows automated processes (like Buildbot) to pass
+ # a list of files to Trial with the general expectation of "these files,
+ # whatever they are, will get tested"
+ if not os.path.isfile(filename):
+ sys.stderr.write("File %r doesn't exist\n" % (filename,))
+ return
+ filename = os.path.abspath(filename)
+ if isTestFile(filename):
+ self['tests'].add(filename)
+ else:
+ self['tests'].update(getTestModules(filename))
+
+ def opt_spew(self):
+ """Print an insanely verbose log of everything that happens. Useful
+ when debugging freezes or locks in complex code."""
+ sys.settrace(spewer)
+
+
+ def opt_help_reporters(self):
+ synopsis = ("Trial's output can be customized using plugins called "
+ "Reporters. You can\nselect any of the following "
+ "reporters using --reporter=<foo>\n")
+ print synopsis
+ for p in plugin.getPlugins(itrial.IReporter):
+ print ' ', p.longOpt, '\t', p.description
+ print
+ sys.exit(0)
+
+ def opt_disablegc(self):
+ """Disable the garbage collector"""
+ gc.disable()
+
+ def opt_tbformat(self, opt):
+ """Specify the format to display tracebacks with. Valid formats are
+ 'plain', 'emacs', and 'cgitb' which uses the nicely verbose stdlib
+ cgitb.text function"""
+ try:
+ self['tbformat'] = TBFORMAT_MAP[opt]
+ except KeyError:
+ raise usage.UsageError(
+ "tbformat must be 'plain', 'emacs', or 'cgitb'.")
+
+ def opt_extra(self, arg):
+ """
+ Add an extra argument. (This is a hack necessary for interfacing with
+ emacs's `gud'.)
+ """
+ if self.extra is None:
+ self.extra = []
+ self.extra.append(arg)
+ opt_x = opt_extra
+
+ def opt_recursionlimit(self, arg):
+ """see sys.setrecursionlimit()"""
+ try:
+ sys.setrecursionlimit(int(arg))
+ except (TypeError, ValueError):
+ raise usage.UsageError(
+ "argument to recursionlimit must be an integer")
+
+ def opt_random(self, option):
+ try:
+ self['random'] = long(option)
+ except ValueError:
+ raise usage.UsageError(
+ "Argument to --random must be a positive integer")
+ else:
+ if self['random'] < 0:
+ raise usage.UsageError(
+ "Argument to --random must be a positive integer")
+ elif self['random'] == 0:
+ self['random'] = long(time.time() * 100)
+
+ def opt_without_module(self, option):
+ """
+ Fake the lack of the specified modules, separated with commas.
+ """
+ for module in option.split(","):
+ if module in sys.modules:
+ warnings.warn("Module '%s' already imported, "
+ "disabling anyway." % (module,),
+ category=RuntimeWarning)
+ sys.modules[module] = None
+
+ def parseArgs(self, *args):
+ self['tests'].update(args)
+ if self.extra is not None:
+ self['tests'].update(self.extra)
+
+ def _loadReporterByName(self, name):
+ for p in plugin.getPlugins(itrial.IReporter):
+ qual = "%s.%s" % (p.module, p.klass)
+ if p.longOpt == name:
+ return reflect.namedAny(qual)
+ raise usage.UsageError("Only pass names of Reporter plugins to "
+ "--reporter. See --help-reporters for "
+ "more info.")
+
+
+ def postOptions(self):
+
+ # Only load reporters now, as opposed to any earlier, to avoid letting
+ # application-defined plugins muck up reactor selecting by importing
+ # t.i.reactor and causing the default to be installed.
+ self['reporter'] = self._loadReporterByName(self['reporter'])
+
+ if 'tbformat' not in self:
+ self['tbformat'] = 'default'
+ if self['nopm']:
+ if not self['debug']:
+ raise usage.UsageError("you must specify --debug when using "
+ "--nopm ")
+ failure.DO_POST_MORTEM = False
+
+
+def _initialDebugSetup(config):
+ # do this part of debug setup first for easy debugging of import failures
+ if config['debug']:
+ failure.startDebugMode()
+ if config['debug'] or config['debug-stacktraces']:
+ defer.setDebugging(True)
+
+
+def _getSuite(config):
+ loader = _getLoader(config)
+ recurse = not config['no-recurse']
+ return loader.loadByNames(config['tests'], recurse)
+
+
+def _getLoader(config):
+ loader = runner.TestLoader()
+ if config['random']:
+ randomer = random.Random()
+ randomer.seed(config['random'])
+ loader.sorter = lambda x : randomer.random()
+ print 'Running tests shuffled with seed %d\n' % config['random']
+ if not config['until-failure']:
+ loader.suiteFactory = runner.DestructiveTestSuite
+ return loader
+
+
+def _makeRunner(config):
+ mode = None
+ if config['debug']:
+ mode = runner.TrialRunner.DEBUG
+ if config['dry-run']:
+ mode = runner.TrialRunner.DRY_RUN
+ return runner.TrialRunner(config['reporter'],
+ mode=mode,
+ profile=config['profile'],
+ logfile=config['logfile'],
+ tracebackFormat=config['tbformat'],
+ realTimeErrors=config['rterrors'],
+ uncleanWarnings=config['unclean-warnings'],
+ workingDirectory=config['temp-directory'],
+ forceGarbageCollection=config['force-gc'])
+
+
+def run():
+ if len(sys.argv) == 1:
+ sys.argv.append("--help")
+ config = Options()
+ try:
+ config.parseOptions()
+ except usage.error, ue:
+ raise SystemExit, "%s: %s" % (sys.argv[0], ue)
+ _initialDebugSetup(config)
+ trialRunner = _makeRunner(config)
+ suite = _getSuite(config)
+ if config['until-failure']:
+ test_result = trialRunner.runUntilFailure(suite)
+ else:
+ test_result = trialRunner.run(suite)
+ if config.tracer:
+ sys.settrace(None)
+ results = config.tracer.results()
+ results.write_results(show_missing=1, summary=False,
+ coverdir=config.coverdir)
+ sys.exit(not test_result.wasSuccessful())
+
diff --git a/vendor/Twisted-10.0.0/twisted/scripts/twistd.py b/vendor/Twisted-10.0.0/twisted/scripts/twistd.py
new file mode 100644
index 0000000000..cd61afcc56
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/scripts/twistd.py
@@ -0,0 +1,30 @@
+# -*- test-case-name: twisted.test.test_twistd -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+The Twisted Daemon: platform-independent interface.
+
+@author: Christopher Armstrong
+"""
+
+from twisted.application import app
+
+from twisted.python.runtime import platformType
+if platformType == "win32":
+ from twisted.scripts._twistw import ServerOptions, \
+ WindowsApplicationRunner as _SomeApplicationRunner
+else:
+ from twisted.scripts._twistd_unix import ServerOptions, \
+ UnixApplicationRunner as _SomeApplicationRunner
+
+
+def runApp(config):
+ _SomeApplicationRunner(config).run()
+
+
+def run():
+ app.run(runApp, ServerOptions)
+
+
+__all__ = ['run', 'runApp']
diff --git a/vendor/Twisted-10.0.0/twisted/spread/__init__.py b/vendor/Twisted-10.0.0/twisted/spread/__init__.py
new file mode 100644
index 0000000000..9f86731e02
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/__init__.py
@@ -0,0 +1,12 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Twisted Spread: Spreadable (Distributed) Computing.
+
+Future Plans: PB, Jelly and Banana need to be optimized.
+
+@author: Glyph Lefkowitz
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/spread/banana.py b/vendor/Twisted-10.0.0/twisted/spread/banana.py
new file mode 100644
index 0000000000..8be99afcfc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/banana.py
@@ -0,0 +1,358 @@
+# -*- test-case-name: twisted.test.test_banana -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Banana -- s-exp based protocol.
+
+Future Plans: This module is almost entirely stable. The same caveat applies
+to it as applies to L{twisted.spread.jelly}, however. Read its future plans
+for more details.
+
+@author: Glyph Lefkowitz
+"""
+
+import copy, cStringIO, struct
+
+from twisted.internet import protocol
+from twisted.persisted import styles
+from twisted.python import log
+
+class BananaError(Exception):
+ pass
+
+def int2b128(integer, stream):
+ if integer == 0:
+ stream(chr(0))
+ return
+ assert integer > 0, "can only encode positive integers"
+ while integer:
+ stream(chr(integer & 0x7f))
+ integer = integer >> 7
+
+
+def b1282int(st):
+ """
+ Convert an integer represented as a base 128 string into an C{int} or
+ C{long}.
+
+ @param st: The integer encoded in a string.
+ @type st: C{str}
+
+ @return: The integer value extracted from the string.
+ @rtype: C{int} or C{long}
+ """
+ e = 1
+ i = 0
+ for char in st:
+ n = ord(char)
+ i += (n * e)
+ e <<= 7
+ return i
+
+
+# delimiter characters.
+LIST = chr(0x80)
+INT = chr(0x81)
+STRING = chr(0x82)
+NEG = chr(0x83)
+FLOAT = chr(0x84)
+# "optional" -- these might be refused by a low-level implementation.
+LONGINT = chr(0x85)
+LONGNEG = chr(0x86)
+# really optional; this is is part of the 'pb' vocabulary
+VOCAB = chr(0x87)
+
+HIGH_BIT_SET = chr(0x80)
+
+def setPrefixLimit(limit):
+ """
+ Set the limit on the prefix length for all Banana connections
+ established after this call.
+
+ The prefix length limit determines how many bytes of prefix a banana
+ decoder will allow before rejecting a potential object as too large.
+
+ @type limit: C{int}
+ @param limit: The number of bytes of prefix for banana to allow when
+ decoding.
+ """
+ global _PREFIX_LIMIT
+ _PREFIX_LIMIT = limit
+setPrefixLimit(64)
+
+SIZE_LIMIT = 640 * 1024 # 640k is all you'll ever need :-)
+
+class Banana(protocol.Protocol, styles.Ephemeral):
+ knownDialects = ["pb", "none"]
+
+ prefixLimit = None
+ sizeLimit = SIZE_LIMIT
+
+ def setPrefixLimit(self, limit):
+ """
+ Set the prefix limit for decoding done by this protocol instance.
+
+ @see: L{setPrefixLimit}
+ """
+ self.prefixLimit = limit
+ self._smallestLongInt = -2 ** (limit * 7) + 1
+ self._smallestInt = -2 ** 31
+ self._largestInt = 2 ** 31 - 1
+ self._largestLongInt = 2 ** (limit * 7) - 1
+
+
+ def connectionReady(self):
+ """Surrogate for connectionMade
+ Called after protocol negotiation.
+ """
+
+ def _selectDialect(self, dialect):
+ self.currentDialect = dialect
+ self.connectionReady()
+
+ def callExpressionReceived(self, obj):
+ if self.currentDialect:
+ self.expressionReceived(obj)
+ else:
+ # this is the first message we've received
+ if self.isClient:
+ # if I'm a client I have to respond
+ for serverVer in obj:
+ if serverVer in self.knownDialects:
+ self.sendEncoded(serverVer)
+ self._selectDialect(serverVer)
+ break
+ else:
+ # I can't speak any of those dialects.
+ log.msg("The client doesn't speak any of the protocols "
+ "offered by the server: disconnecting.")
+ self.transport.loseConnection()
+ else:
+ if obj in self.knownDialects:
+ self._selectDialect(obj)
+ else:
+ # the client just selected a protocol that I did not suggest.
+ log.msg("The client selected a protocol the server didn't "
+ "suggest and doesn't know: disconnecting.")
+ self.transport.loseConnection()
+
+
+ def connectionMade(self):
+ self.setPrefixLimit(_PREFIX_LIMIT)
+ self.currentDialect = None
+ if not self.isClient:
+ self.sendEncoded(self.knownDialects)
+
+
+ def gotItem(self, item):
+ l = self.listStack
+ if l:
+ l[-1][1].append(item)
+ else:
+ self.callExpressionReceived(item)
+
+ buffer = ''
+
+ def dataReceived(self, chunk):
+ buffer = self.buffer + chunk
+ listStack = self.listStack
+ gotItem = self.gotItem
+ while buffer:
+ assert self.buffer != buffer, "This ain't right: %s %s" % (repr(self.buffer), repr(buffer))
+ self.buffer = buffer
+ pos = 0
+ for ch in buffer:
+ if ch >= HIGH_BIT_SET:
+ break
+ pos = pos + 1
+ else:
+ if pos > self.prefixLimit:
+ raise BananaError("Security precaution: more than %d bytes of prefix" % (self.prefixLimit,))
+ return
+ num = buffer[:pos]
+ typebyte = buffer[pos]
+ rest = buffer[pos+1:]
+ if len(num) > self.prefixLimit:
+ raise BananaError("Security precaution: longer than %d bytes worth of prefix" % (self.prefixLimit,))
+ if typebyte == LIST:
+ num = b1282int(num)
+ if num > SIZE_LIMIT:
+ raise BananaError("Security precaution: List too long.")
+ listStack.append((num, []))
+ buffer = rest
+ elif typebyte == STRING:
+ num = b1282int(num)
+ if num > SIZE_LIMIT:
+ raise BananaError("Security precaution: String too long.")
+ if len(rest) >= num:
+ buffer = rest[num:]
+ gotItem(rest[:num])
+ else:
+ return
+ elif typebyte == INT:
+ buffer = rest
+ num = b1282int(num)
+ gotItem(num)
+ elif typebyte == LONGINT:
+ buffer = rest
+ num = b1282int(num)
+ gotItem(num)
+ elif typebyte == LONGNEG:
+ buffer = rest
+ num = b1282int(num)
+ gotItem(-num)
+ elif typebyte == NEG:
+ buffer = rest
+ num = -b1282int(num)
+ gotItem(num)
+ elif typebyte == VOCAB:
+ buffer = rest
+ num = b1282int(num)
+ gotItem(self.incomingVocabulary[num])
+ elif typebyte == FLOAT:
+ if len(rest) >= 8:
+ buffer = rest[8:]
+ gotItem(struct.unpack("!d", rest[:8])[0])
+ else:
+ return
+ else:
+ raise NotImplementedError(("Invalid Type Byte %r" % (typebyte,)))
+ while listStack and (len(listStack[-1][1]) == listStack[-1][0]):
+ item = listStack.pop()[1]
+ gotItem(item)
+ self.buffer = ''
+
+
+ def expressionReceived(self, lst):
+ """Called when an expression (list, string, or int) is received.
+ """
+ raise NotImplementedError()
+
+
+ outgoingVocabulary = {
+ # Jelly Data Types
+ 'None' : 1,
+ 'class' : 2,
+ 'dereference' : 3,
+ 'reference' : 4,
+ 'dictionary' : 5,
+ 'function' : 6,
+ 'instance' : 7,
+ 'list' : 8,
+ 'module' : 9,
+ 'persistent' : 10,
+ 'tuple' : 11,
+ 'unpersistable' : 12,
+
+ # PB Data Types
+ 'copy' : 13,
+ 'cache' : 14,
+ 'cached' : 15,
+ 'remote' : 16,
+ 'local' : 17,
+ 'lcache' : 18,
+
+ # PB Protocol Messages
+ 'version' : 19,
+ 'login' : 20,
+ 'password' : 21,
+ 'challenge' : 22,
+ 'logged_in' : 23,
+ 'not_logged_in' : 24,
+ 'cachemessage' : 25,
+ 'message' : 26,
+ 'answer' : 27,
+ 'error' : 28,
+ 'decref' : 29,
+ 'decache' : 30,
+ 'uncache' : 31,
+ }
+
+ incomingVocabulary = {}
+ for k, v in outgoingVocabulary.items():
+ incomingVocabulary[v] = k
+
+ def __init__(self, isClient=1):
+ self.listStack = []
+ self.outgoingSymbols = copy.copy(self.outgoingVocabulary)
+ self.outgoingSymbolCount = 0
+ self.isClient = isClient
+
+ def sendEncoded(self, obj):
+ io = cStringIO.StringIO()
+ self._encode(obj, io.write)
+ value = io.getvalue()
+ self.transport.write(value)
+
+ def _encode(self, obj, write):
+ if isinstance(obj, (list, tuple)):
+ if len(obj) > SIZE_LIMIT:
+ raise BananaError(
+ "list/tuple is too long to send (%d)" % (len(obj),))
+ int2b128(len(obj), write)
+ write(LIST)
+ for elem in obj:
+ self._encode(elem, write)
+ elif isinstance(obj, (int, long)):
+ if obj < self._smallestLongInt or obj > self._largestLongInt:
+ raise BananaError(
+ "int/long is too large to send (%d)" % (obj,))
+ if obj < self._smallestInt:
+ int2b128(-obj, write)
+ write(LONGNEG)
+ elif obj < 0:
+ int2b128(-obj, write)
+ write(NEG)
+ elif obj <= self._largestInt:
+ int2b128(obj, write)
+ write(INT)
+ else:
+ int2b128(obj, write)
+ write(LONGINT)
+ elif isinstance(obj, float):
+ write(FLOAT)
+ write(struct.pack("!d", obj))
+ elif isinstance(obj, str):
+ # TODO: an API for extending banana...
+ if self.currentDialect == "pb" and obj in self.outgoingSymbols:
+ symbolID = self.outgoingSymbols[obj]
+ int2b128(symbolID, write)
+ write(VOCAB)
+ else:
+ if len(obj) > SIZE_LIMIT:
+ raise BananaError(
+ "string is too long to send (%d)" % (len(obj),))
+ int2b128(len(obj), write)
+ write(STRING)
+ write(obj)
+ else:
+ raise BananaError("could not send object: %r" % (obj,))
+
+
+# For use from the interactive interpreter
+_i = Banana()
+_i.connectionMade()
+_i._selectDialect("none")
+
+
+def encode(lst):
+ """Encode a list s-expression."""
+ io = cStringIO.StringIO()
+ _i.transport = io
+ _i.sendEncoded(lst)
+ return io.getvalue()
+
+
+def decode(st):
+ """
+ Decode a banana-encoded string.
+ """
+ l = []
+ _i.expressionReceived = l.append
+ try:
+ _i.dataReceived(st)
+ finally:
+ _i.buffer = ''
+ del _i.expressionReceived
+ return l[0]
diff --git a/vendor/Twisted-10.0.0/twisted/spread/flavors.py b/vendor/Twisted-10.0.0/twisted/spread/flavors.py
new file mode 100644
index 0000000000..ac8791e890
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/flavors.py
@@ -0,0 +1,600 @@
+# -*- test-case-name: twisted.test.test_pb -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module represents flavors of remotely acessible objects.
+
+Currently this is only objects accessible through Perspective Broker, but will
+hopefully encompass all forms of remote access which can emulate subsets of PB
+(such as XMLRPC or SOAP).
+
+Future Plans: Optimization. Exploitation of new-style object model.
+Optimizations to this module should not affect external-use semantics at all,
+but may have a small impact on users who subclass and override methods.
+
+@author: Glyph Lefkowitz
+"""
+
+# NOTE: this module should NOT import pb; it is supposed to be a module which
+# abstractly defines remotely accessible types. Many of these types expect to
+# be serialized by Jelly, but they ought to be accessible through other
+# mechanisms (like XMLRPC)
+
+# system imports
+import sys
+from zope.interface import implements, Interface
+
+# twisted imports
+from twisted.python import log, reflect
+
+# sibling imports
+from jelly import setUnjellyableForClass, setUnjellyableForClassTree, setUnjellyableFactoryForClass, unjellyableRegistry
+from jelly import Jellyable, Unjellyable, _Dummy, _DummyNewStyle
+from jelly import setInstanceState, getInstanceState
+
+# compatibility
+setCopierForClass = setUnjellyableForClass
+setCopierForClassTree = setUnjellyableForClassTree
+setFactoryForClass = setUnjellyableFactoryForClass
+copyTags = unjellyableRegistry
+
+copy_atom = "copy"
+cache_atom = "cache"
+cached_atom = "cached"
+remote_atom = "remote"
+
+
+class NoSuchMethod(AttributeError):
+ """Raised if there is no such remote method"""
+
+
+class IPBRoot(Interface):
+ """Factory for root Referenceable objects for PB servers."""
+
+ def rootObject(broker):
+ """Return root Referenceable for broker."""
+
+
+class Serializable(Jellyable):
+ """An object that can be passed remotely.
+
+ I am a style of object which can be serialized by Perspective
+ Broker. Objects which wish to be referenceable or copied remotely
+ have to subclass Serializable. However, clients of Perspective
+ Broker will probably not want to directly subclass Serializable; the
+ Flavors of transferable objects are listed below.
+
+ What it means to be \"Serializable\" is that an object can be
+ passed to or returned from a remote method. Certain basic types
+ (dictionaries, lists, tuples, numbers, strings) are serializable by
+ default; however, classes need to choose a specific serialization
+ style: L{Referenceable}, L{Viewable}, L{Copyable} or L{Cacheable}.
+
+ You may also pass C{[lists, dictionaries, tuples]} of L{Serializable}
+ instances to or return them from remote methods, as many levels deep
+ as you like.
+ """
+
+ def processUniqueID(self):
+ """Return an ID which uniquely represents this object for this process.
+
+ By default, this uses the 'id' builtin, but can be overridden to
+ indicate that two values are identity-equivalent (such as proxies
+ for the same object).
+ """
+
+ return id(self)
+
+class Referenceable(Serializable):
+ perspective = None
+ """I am an object sent remotely as a direct reference.
+
+ When one of my subclasses is sent as an argument to or returned
+ from a remote method call, I will be serialized by default as a
+ direct reference.
+
+ This means that the peer will be able to call methods on me;
+ a method call xxx() from my peer will be resolved to methods
+ of the name remote_xxx.
+ """
+
+ def remoteMessageReceived(self, broker, message, args, kw):
+ """A remote message has been received. Dispatch it appropriately.
+
+ The default implementation is to dispatch to a method called
+ 'remote_messagename' and call it with the same arguments.
+ """
+ args = broker.unserialize(args)
+ kw = broker.unserialize(kw)
+ method = getattr(self, "remote_%s" % message, None)
+ if method is None:
+ raise NoSuchMethod("No such method: remote_%s" % (message,))
+ try:
+ state = method(*args, **kw)
+ except TypeError:
+ log.msg("%s didn't accept %s and %s" % (method, args, kw))
+ raise
+ return broker.serialize(state, self.perspective)
+
+ def jellyFor(self, jellier):
+ """(internal)
+
+ Return a tuple which will be used as the s-expression to
+ serialize this to a peer.
+ """
+
+ return "remote", jellier.invoker.registerReference(self)
+
+
+class Root(Referenceable):
+ """I provide a root object to L{pb.Broker}s for a L{pb.BrokerFactory}.
+
+ When a L{pb.BrokerFactory} produces a L{pb.Broker}, it supplies that
+ L{pb.Broker} with an object named \"root\". That object is obtained
+ by calling my rootObject method.
+
+ See also: L{pb.getObjectAt}
+ """
+
+ implements(IPBRoot)
+
+ def rootObject(self, broker):
+ """A L{pb.BrokerFactory} is requesting to publish me as a root object.
+
+ When a L{pb.BrokerFactory} is sending me as the root object, this
+ method will be invoked to allow per-broker versions of an
+ object. By default I return myself.
+ """
+ return self
+
+
+class ViewPoint(Referenceable):
+ """
+ I act as an indirect reference to an object accessed through a
+ L{pb.Perspective}.
+
+ Simply put, I combine an object with a perspective so that when a
+ peer calls methods on the object I refer to, the method will be
+ invoked with that perspective as a first argument, so that it can
+ know who is calling it.
+
+ While L{Viewable} objects will be converted to ViewPoints by default
+ when they are returned from or sent as arguments to a remote
+ method, any object may be manually proxied as well. (XXX: Now that
+ this class is no longer named C{Proxy}, this is the only occourance
+ of the term 'proxied' in this docstring, and may be unclear.)
+
+ This can be useful when dealing with L{pb.Perspective}s, L{Copyable}s,
+ and L{Cacheable}s. It is legal to implement a method as such on
+ a perspective::
+
+ | def perspective_getViewPointForOther(self, name):
+ | defr = self.service.getPerspectiveRequest(name)
+ | defr.addCallbacks(lambda x, self=self: ViewPoint(self, x), log.msg)
+ | return defr
+
+ This will allow you to have references to Perspective objects in two
+ different ways. One is through the initial 'attach' call -- each
+ peer will have a L{pb.RemoteReference} to their perspective directly. The
+ other is through this method; each peer can get a L{pb.RemoteReference} to
+ all other perspectives in the service; but that L{pb.RemoteReference} will
+ be to a L{ViewPoint}, not directly to the object.
+
+ The practical offshoot of this is that you can implement 2 varieties
+ of remotely callable methods on this Perspective; view_xxx and
+ C{perspective_xxx}. C{view_xxx} methods will follow the rules for
+ ViewPoint methods (see ViewPoint.L{remoteMessageReceived}), and
+ C{perspective_xxx} methods will follow the rules for Perspective
+ methods.
+ """
+
+ def __init__(self, perspective, object):
+ """Initialize me with a Perspective and an Object.
+ """
+ self.perspective = perspective
+ self.object = object
+
+ def processUniqueID(self):
+ """Return an ID unique to a proxy for this perspective+object combination.
+ """
+ return (id(self.perspective), id(self.object))
+
+ def remoteMessageReceived(self, broker, message, args, kw):
+ """A remote message has been received. Dispatch it appropriately.
+
+ The default implementation is to dispatch to a method called
+ 'C{view_messagename}' to my Object and call it on my object with
+ the same arguments, modified by inserting my Perspective as
+ the first argument.
+ """
+ args = broker.unserialize(args, self.perspective)
+ kw = broker.unserialize(kw, self.perspective)
+ method = getattr(self.object, "view_%s" % message)
+ try:
+ state = apply(method, (self.perspective,)+args, kw)
+ except TypeError:
+ log.msg("%s didn't accept %s and %s" % (method, args, kw))
+ raise
+ rv = broker.serialize(state, self.perspective, method, args, kw)
+ return rv
+
+
+class Viewable(Serializable):
+ """I will be converted to a L{ViewPoint} when passed to or returned from a remote method.
+
+ The beginning of a peer's interaction with a PB Service is always
+ through a perspective. However, if a C{perspective_xxx} method returns
+ a Viewable, it will be serialized to the peer as a response to that
+ method.
+ """
+
+ def jellyFor(self, jellier):
+ """Serialize a L{ViewPoint} for me and the perspective of the given broker.
+ """
+ return ViewPoint(jellier.invoker.serializingPerspective, self).jellyFor(jellier)
+
+
+
+class Copyable(Serializable):
+ """Subclass me to get copied each time you are returned from or passed to a remote method.
+
+ When I am returned from or passed to a remote method call, I will be
+ converted into data via a set of callbacks (see my methods for more
+ info). That data will then be serialized using Jelly, and sent to
+ the peer.
+
+ The peer will then look up the type to represent this with; see
+ L{RemoteCopy} for details.
+ """
+
+ def getStateToCopy(self):
+ """Gather state to send when I am serialized for a peer.
+
+ I will default to returning self.__dict__. Override this to
+ customize this behavior.
+ """
+
+ return self.__dict__
+
+ def getStateToCopyFor(self, perspective):
+ """
+ Gather state to send when I am serialized for a particular
+ perspective.
+
+ I will default to calling L{getStateToCopy}. Override this to
+ customize this behavior.
+ """
+
+ return self.getStateToCopy()
+
+ def getTypeToCopy(self):
+ """Determine what type tag to send for me.
+
+ By default, send the string representation of my class
+ (package.module.Class); normally this is adequate, but
+ you may override this to change it.
+ """
+
+ return reflect.qual(self.__class__)
+
+ def getTypeToCopyFor(self, perspective):
+ """Determine what type tag to send for me.
+
+ By default, defer to self.L{getTypeToCopy}() normally this is
+ adequate, but you may override this to change it.
+ """
+
+ return self.getTypeToCopy()
+
+ def jellyFor(self, jellier):
+ """Assemble type tag and state to copy for this broker.
+
+ This will call L{getTypeToCopyFor} and L{getStateToCopy}, and
+ return an appropriate s-expression to represent me.
+ """
+
+ if jellier.invoker is None:
+ return getInstanceState(self, jellier)
+ p = jellier.invoker.serializingPerspective
+ t = self.getTypeToCopyFor(p)
+ state = self.getStateToCopyFor(p)
+ sxp = jellier.prepare(self)
+ sxp.extend([t, jellier.jelly(state)])
+ return jellier.preserve(self, sxp)
+
+
+class Cacheable(Copyable):
+ """A cached instance.
+
+ This means that it's copied; but there is some logic to make sure
+ that it's only copied once. Additionally, when state is retrieved,
+ it is passed a "proto-reference" to the state as it will exist on
+ the client.
+
+ XXX: The documentation for this class needs work, but it's the most
+ complex part of PB and it is inherently difficult to explain.
+ """
+
+ def getStateToCacheAndObserveFor(self, perspective, observer):
+ """
+ Get state to cache on the client and client-cache reference
+ to observe locally.
+
+ This is similiar to getStateToCopyFor, but it additionally
+ passes in a reference to the client-side RemoteCache instance
+ that will be created when it is unserialized. This allows
+ Cacheable instances to keep their RemoteCaches up to date when
+ they change, such that no changes can occur between the point
+ at which the state is initially copied and the client receives
+ it that are not propogated.
+ """
+
+ return self.getStateToCopyFor(perspective)
+
+ def jellyFor(self, jellier):
+ """Return an appropriate tuple to serialize me.
+
+ Depending on whether this broker has cached me or not, this may
+ return either a full state or a reference to an existing cache.
+ """
+ if jellier.invoker is None:
+ return getInstanceState(self, jellier)
+ luid = jellier.invoker.cachedRemotelyAs(self, 1)
+ if luid is None:
+ luid = jellier.invoker.cacheRemotely(self)
+ p = jellier.invoker.serializingPerspective
+ type_ = self.getTypeToCopyFor(p)
+ observer = RemoteCacheObserver(jellier.invoker, self, p)
+ state = self.getStateToCacheAndObserveFor(p, observer)
+ l = jellier.prepare(self)
+ jstate = jellier.jelly(state)
+ l.extend([type_, luid, jstate])
+ return jellier.preserve(self, l)
+ else:
+ return cached_atom, luid
+
+ def stoppedObserving(self, perspective, observer):
+ """This method is called when a client has stopped observing me.
+
+ The 'observer' argument is the same as that passed in to
+ getStateToCacheAndObserveFor.
+ """
+
+
+
+class RemoteCopy(Unjellyable):
+ """I am a remote copy of a Copyable object.
+
+ When the state from a L{Copyable} object is received, an instance will
+ be created based on the copy tags table (see setUnjellyableForClass) and
+ sent the L{setCopyableState} message. I provide a reasonable default
+ implementation of that message; subclass me if you wish to serve as
+ a copier for remote data.
+
+ NOTE: copiers are invoked with no arguments. Do not implement a
+ constructor which requires args in a subclass of L{RemoteCopy}!
+ """
+
+ def setCopyableState(self, state):
+ """I will be invoked with the state to copy locally.
+
+ 'state' is the data returned from the remote object's
+ 'getStateToCopyFor' method, which will often be the remote
+ object's dictionary (or a filtered approximation of it depending
+ on my peer's perspective).
+ """
+
+ self.__dict__ = state
+
+ def unjellyFor(self, unjellier, jellyList):
+ if unjellier.invoker is None:
+ return setInstanceState(self, unjellier, jellyList)
+ self.setCopyableState(unjellier.unjelly(jellyList[1]))
+ return self
+
+
+
+class RemoteCache(RemoteCopy, Serializable):
+ """A cache is a local representation of a remote L{Cacheable} object.
+
+ This represents the last known state of this object. It may
+ also have methods invoked on it -- in order to update caches,
+ the cached class generates a L{pb.RemoteReference} to this object as
+ it is originally sent.
+
+ Much like copy, I will be invoked with no arguments. Do not
+ implement a constructor that requires arguments in one of my
+ subclasses.
+ """
+
+ def remoteMessageReceived(self, broker, message, args, kw):
+ """A remote message has been received. Dispatch it appropriately.
+
+ The default implementation is to dispatch to a method called
+ 'C{observe_messagename}' and call it on my with the same arguments.
+ """
+
+ args = broker.unserialize(args)
+ kw = broker.unserialize(kw)
+ method = getattr(self, "observe_%s" % message)
+ try:
+ state = apply(method, args, kw)
+ except TypeError:
+ log.msg("%s didn't accept %s and %s" % (method, args, kw))
+ raise
+ return broker.serialize(state, None, method, args, kw)
+
+ def jellyFor(self, jellier):
+ """serialize me (only for the broker I'm for) as the original cached reference
+ """
+ if jellier.invoker is None:
+ return getInstanceState(self, jellier)
+ assert jellier.invoker is self.broker, "You cannot exchange cached proxies between brokers."
+ return 'lcache', self.luid
+
+
+ def unjellyFor(self, unjellier, jellyList):
+ if unjellier.invoker is None:
+ return setInstanceState(self, unjellier, jellyList)
+ self.broker = unjellier.invoker
+ self.luid = jellyList[1]
+ if isinstance(self.__class__, type): #new-style class
+ cProxy = _DummyNewStyle()
+ else:
+ cProxy = _Dummy()
+ cProxy.__class__ = self.__class__
+ cProxy.__dict__ = self.__dict__
+ # XXX questionable whether this was a good design idea...
+ init = getattr(cProxy, "__init__", None)
+ if init:
+ init()
+ unjellier.invoker.cacheLocally(jellyList[1], self)
+ cProxy.setCopyableState(unjellier.unjelly(jellyList[2]))
+ # Might have changed due to setCopyableState method; we'll assume that
+ # it's bad form to do so afterwards.
+ self.__dict__ = cProxy.__dict__
+ # chomp, chomp -- some existing code uses "self.__dict__ =", some uses
+ # "__dict__.update". This is here in order to handle both cases.
+ self.broker = unjellier.invoker
+ self.luid = jellyList[1]
+ return cProxy
+
+## def __really_del__(self):
+## """Final finalization call, made after all remote references have been lost.
+## """
+
+ def __cmp__(self, other):
+ """Compare me [to another RemoteCache.
+ """
+ if isinstance(other, self.__class__):
+ return cmp(id(self.__dict__), id(other.__dict__))
+ else:
+ return cmp(id(self.__dict__), other)
+
+ def __hash__(self):
+ """Hash me.
+ """
+ return int(id(self.__dict__) % sys.maxint)
+
+ broker = None
+ luid = None
+
+ def __del__(self):
+ """Do distributed reference counting on finalize.
+ """
+ try:
+ # log.msg( ' --- decache: %s %s' % (self, self.luid) )
+ if self.broker:
+ self.broker.decCacheRef(self.luid)
+ except:
+ log.deferr()
+
+def unjellyCached(unjellier, unjellyList):
+ luid = unjellyList[1]
+ cNotProxy = unjellier.invoker.cachedLocallyAs(luid)
+
+ cProxy = _Dummy()
+ cProxy.__class__ = cNotProxy.__class__
+ cProxy.__dict__ = cNotProxy.__dict__
+ return cProxy
+
+setUnjellyableForClass("cached", unjellyCached)
+
+def unjellyLCache(unjellier, unjellyList):
+ luid = unjellyList[1]
+ obj = unjellier.invoker.remotelyCachedForLUID(luid)
+ return obj
+
+setUnjellyableForClass("lcache", unjellyLCache)
+
+def unjellyLocal(unjellier, unjellyList):
+ obj = unjellier.invoker.localObjectForID(unjellyList[1])
+ return obj
+
+setUnjellyableForClass("local", unjellyLocal)
+
+class RemoteCacheMethod:
+ """A method on a reference to a L{RemoteCache}.
+ """
+
+ def __init__(self, name, broker, cached, perspective):
+ """(internal) initialize.
+ """
+ self.name = name
+ self.broker = broker
+ self.perspective = perspective
+ self.cached = cached
+
+ def __cmp__(self, other):
+ return cmp((self.name, self.broker, self.perspective, self.cached), other)
+
+ def __hash__(self):
+ return hash((self.name, self.broker, self.perspective, self.cached))
+
+ def __call__(self, *args, **kw):
+ """(internal) action method.
+ """
+ cacheID = self.broker.cachedRemotelyAs(self.cached)
+ if cacheID is None:
+ from pb import ProtocolError
+ raise ProtocolError("You can't call a cached method when the object hasn't been given to the peer yet.")
+ return self.broker._sendMessage('cache', self.perspective, cacheID, self.name, args, kw)
+
+class RemoteCacheObserver:
+ """I am a reverse-reference to the peer's L{RemoteCache}.
+
+ I am generated automatically when a cache is serialized. I
+ represent a reference to the client's L{RemoteCache} object that
+ will represent a particular L{Cacheable}; I am the additional
+ object passed to getStateToCacheAndObserveFor.
+ """
+
+ def __init__(self, broker, cached, perspective):
+ """(internal) Initialize me.
+
+ @param broker: a L{pb.Broker} instance.
+
+ @param cached: a L{Cacheable} instance that this L{RemoteCacheObserver}
+ corresponds to.
+
+ @param perspective: a reference to the perspective who is observing this.
+ """
+
+ self.broker = broker
+ self.cached = cached
+ self.perspective = perspective
+
+ def __repr__(self):
+ return "<RemoteCacheObserver(%s, %s, %s) at %s>" % (
+ self.broker, self.cached, self.perspective, id(self))
+
+ def __hash__(self):
+ """Generate a hash unique to all L{RemoteCacheObserver}s for this broker/perspective/cached triplet
+ """
+
+ return ( (hash(self.broker) % 2**10)
+ + (hash(self.perspective) % 2**10)
+ + (hash(self.cached) % 2**10))
+
+ def __cmp__(self, other):
+ """Compare me to another L{RemoteCacheObserver}.
+ """
+
+ return cmp((self.broker, self.perspective, self.cached), other)
+
+ def callRemote(self, _name, *args, **kw):
+ """(internal) action method.
+ """
+ cacheID = self.broker.cachedRemotelyAs(self.cached)
+ if cacheID is None:
+ from pb import ProtocolError
+ raise ProtocolError("You can't call a cached method when the "
+ "object hasn't been given to the peer yet.")
+ return self.broker._sendMessage('cache', self.perspective, cacheID,
+ _name, args, kw)
+
+ def remoteMethod(self, key):
+ """Get a L{pb.RemoteMethod} for this key.
+ """
+ return RemoteCacheMethod(key, self.broker, self.cached, self.perspective)
diff --git a/vendor/Twisted-10.0.0/twisted/spread/interfaces.py b/vendor/Twisted-10.0.0/twisted/spread/interfaces.py
new file mode 100644
index 0000000000..6d48d002d8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/interfaces.py
@@ -0,0 +1,28 @@
+"""
+Twisted Spread Interfaces.
+
+This module is unused so far. It's also undecided whether this module
+will remain monolithic.
+"""
+
+from zope.interface import Interface
+
+class IJellyable(Interface):
+ def jellyFor(jellier):
+ """
+ Jelly myself for jellier.
+ """
+
+class IUnjellyable(Interface):
+ def unjellyFor(jellier, jellyList):
+ """
+ Unjelly myself for the jellier.
+
+ @param jellier: A stateful object which exists for the lifetime of a
+ single call to L{unjelly}.
+
+ @param jellyList: The C{list} which represents the jellied state of the
+ object to be unjellied.
+
+ @return: The object which results from unjellying.
+ """
diff --git a/vendor/Twisted-10.0.0/twisted/spread/jelly.py b/vendor/Twisted-10.0.0/twisted/spread/jelly.py
new file mode 100644
index 0000000000..78c08d2d96
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/jelly.py
@@ -0,0 +1,1134 @@
+# -*- test-case-name: twisted.test.test_jelly -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+S-expression-based persistence of python objects.
+
+It does something very much like L{Pickle<pickle>}; however, pickle's main goal
+seems to be efficiency (both in space and time); jelly's main goals are
+security, human readability, and portability to other environments.
+
+This is how Jelly converts various objects to s-expressions.
+
+Boolean::
+ True --> ['boolean', 'true']
+
+Integer::
+ 1 --> 1
+
+List::
+ [1, 2] --> ['list', 1, 2]
+
+String::
+ \"hello\" --> \"hello\"
+
+Float::
+ 2.3 --> 2.3
+
+Dictionary::
+ {'a': 1, 'b': 'c'} --> ['dictionary', ['b', 'c'], ['a', 1]]
+
+Module::
+ UserString --> ['module', 'UserString']
+
+Class::
+ UserString.UserString --> ['class', ['module', 'UserString'], 'UserString']
+
+Function::
+ string.join --> ['function', 'join', ['module', 'string']]
+
+Instance: s is an instance of UserString.UserString, with a __dict__
+{'data': 'hello'}::
+ [\"UserString.UserString\", ['dictionary', ['data', 'hello']]]
+
+Class Method: UserString.UserString.center::
+ ['method', 'center', ['None'], ['class', ['module', 'UserString'],
+ 'UserString']]
+
+Instance Method: s.center, where s is an instance of UserString.UserString::
+ ['method', 'center', ['instance', ['reference', 1, ['class',
+ ['module', 'UserString'], 'UserString']], ['dictionary', ['data', 'd']]],
+ ['dereference', 1]]
+
+The C{set} builtin and the C{sets.Set} class are serialized to the same
+thing, and unserialized to C{set} if available, else to C{sets.Set}. It means
+that there's a possibility of type switching in the serialization process. The
+solution is to always use C{set} if possible, and only use C{sets.Set} under
+Python 2.3; this can be accomplished by using L{twisted.python.compat.set}.
+
+The same rule applies for C{frozenset} and C{sets.ImmutableSet}.
+
+@author: Glyph Lefkowitz
+"""
+
+# System Imports
+import pickle
+import types
+import warnings
+from types import StringType
+from types import UnicodeType
+from types import IntType
+from types import TupleType
+from types import ListType
+from types import LongType
+from types import FloatType
+from types import FunctionType
+from types import MethodType
+from types import ModuleType
+from types import DictionaryType
+from types import InstanceType
+from types import NoneType
+from types import ClassType
+import copy
+
+import datetime
+from types import BooleanType
+
+try:
+ import decimal
+except ImportError:
+ decimal = None
+
+try:
+ _set = set
+except NameError:
+ _set = None
+
+try:
+ # Filter out deprecation warning for Python >= 2.6
+ warnings.filterwarnings("ignore", category=DeprecationWarning,
+ message="the sets module is deprecated", append=True)
+ import sets as _sets
+finally:
+ warnings.filters.pop()
+
+
+from new import instance
+from new import instancemethod
+from zope.interface import implements
+
+# Twisted Imports
+from twisted.python.reflect import namedObject, qual
+from twisted.persisted.crefutil import NotKnown, _Tuple, _InstanceMethod
+from twisted.persisted.crefutil import _DictKeyAndValue, _Dereference
+from twisted.persisted.crefutil import _Container
+from twisted.python import runtime
+from twisted.python.compat import reduce
+
+from twisted.spread.interfaces import IJellyable, IUnjellyable
+
+
+if runtime.platform.getType() == "java":
+ from org.python.core import PyStringMap
+ DictTypes = (DictionaryType, PyStringMap)
+else:
+ DictTypes = (DictionaryType,)
+
+
+None_atom = "None" # N
+# code
+class_atom = "class" # c
+module_atom = "module" # m
+function_atom = "function" # f
+
+# references
+dereference_atom = 'dereference' # D
+persistent_atom = 'persistent' # p
+reference_atom = 'reference' # r
+
+# mutable collections
+dictionary_atom = "dictionary" # d
+list_atom = 'list' # l
+set_atom = 'set'
+
+# immutable collections
+# (assignment to __dict__ and __class__ still might go away!)
+tuple_atom = "tuple" # t
+instance_atom = 'instance' # i
+frozenset_atom = 'frozenset'
+
+
+# errors
+unpersistable_atom = "unpersistable"# u
+unjellyableRegistry = {}
+unjellyableFactoryRegistry = {}
+
+
+
+def _newInstance(cls, state):
+ """
+ Make a new instance of a class without calling its __init__ method.
+ 'state' will be used to update inst.__dict__ . Supports both new- and
+ old-style classes.
+ """
+ if not isinstance(cls, types.ClassType):
+ # new-style
+ inst = cls.__new__(cls)
+ inst.__dict__.update(state) # Copy 'instance' behaviour
+ else:
+ inst = instance(cls, state)
+ return inst
+
+
+
+def _maybeClass(classnamep):
+ try:
+ object
+ except NameError:
+ isObject = 0
+ else:
+ isObject = isinstance(classnamep, type)
+ if isinstance(classnamep, ClassType) or isObject:
+ return qual(classnamep)
+ return classnamep
+
+
+
+def setUnjellyableForClass(classname, unjellyable):
+ """
+ Set which local class will represent a remote type.
+
+ If you have written a Copyable class that you expect your client to be
+ receiving, write a local "copy" class to represent it, then call::
+
+ jellier.setUnjellyableForClass('module.package.Class', MyCopier).
+
+ Call this at the module level immediately after its class
+ definition. MyCopier should be a subclass of RemoteCopy.
+
+ The classname may be a special tag returned by
+ 'Copyable.getTypeToCopyFor' rather than an actual classname.
+
+ This call is also for cached classes, since there will be no
+ overlap. The rules are the same.
+ """
+
+ global unjellyableRegistry
+ classname = _maybeClass(classname)
+ unjellyableRegistry[classname] = unjellyable
+ globalSecurity.allowTypes(classname)
+
+
+
+def setUnjellyableFactoryForClass(classname, copyFactory):
+ """
+ Set the factory to construct a remote instance of a type::
+
+ jellier.setUnjellyableFactoryForClass('module.package.Class', MyFactory)
+
+ Call this at the module level immediately after its class definition.
+ C{copyFactory} should return an instance or subclass of
+ L{RemoteCopy<pb.RemoteCopy>}.
+
+ Similar to L{setUnjellyableForClass} except it uses a factory instead
+ of creating an instance.
+ """
+
+ global unjellyableFactoryRegistry
+ classname = _maybeClass(classname)
+ unjellyableFactoryRegistry[classname] = copyFactory
+ globalSecurity.allowTypes(classname)
+
+
+
+def setUnjellyableForClassTree(module, baseClass, prefix=None):
+ """
+ Set all classes in a module derived from C{baseClass} as copiers for
+ a corresponding remote class.
+
+ When you have a heirarchy of Copyable (or Cacheable) classes on one
+ side, and a mirror structure of Copied (or RemoteCache) classes on the
+ other, use this to setUnjellyableForClass all your Copieds for the
+ Copyables.
+
+ Each copyTag (the \"classname\" argument to getTypeToCopyFor, and
+ what the Copyable's getTypeToCopyFor returns) is formed from
+ adding a prefix to the Copied's class name. The prefix defaults
+ to module.__name__. If you wish the copy tag to consist of solely
+ the classname, pass the empty string \'\'.
+
+ @param module: a module object from which to pull the Copied classes.
+ (passing sys.modules[__name__] might be useful)
+
+ @param baseClass: the base class from which all your Copied classes derive.
+
+ @param prefix: the string prefixed to classnames to form the
+ unjellyableRegistry.
+ """
+ if prefix is None:
+ prefix = module.__name__
+
+ if prefix:
+ prefix = "%s." % prefix
+
+ for i in dir(module):
+ i_ = getattr(module, i)
+ if type(i_) == types.ClassType:
+ if issubclass(i_, baseClass):
+ setUnjellyableForClass('%s%s' % (prefix, i), i_)
+
+
+
+def getInstanceState(inst, jellier):
+ """
+ Utility method to default to 'normal' state rules in serialization.
+ """
+ if hasattr(inst, "__getstate__"):
+ state = inst.__getstate__()
+ else:
+ state = inst.__dict__
+ sxp = jellier.prepare(inst)
+ sxp.extend([qual(inst.__class__), jellier.jelly(state)])
+ return jellier.preserve(inst, sxp)
+
+
+
+def setInstanceState(inst, unjellier, jellyList):
+ """
+ Utility method to default to 'normal' state rules in unserialization.
+ """
+ state = unjellier.unjelly(jellyList[1])
+ if hasattr(inst, "__setstate__"):
+ inst.__setstate__(state)
+ else:
+ inst.__dict__ = state
+ return inst
+
+
+
+class Unpersistable:
+ """
+ This is an instance of a class that comes back when something couldn't be
+ unpersisted.
+ """
+
+ def __init__(self, reason):
+ """
+ Initialize an unpersistable object with a descriptive C{reason} string.
+ """
+ self.reason = reason
+
+
+ def __repr__(self):
+ return "Unpersistable(%s)" % repr(self.reason)
+
+
+
+class Jellyable:
+ """
+ Inherit from me to Jelly yourself directly with the `getStateFor'
+ convenience method.
+ """
+ implements(IJellyable)
+
+ def getStateFor(self, jellier):
+ return self.__dict__
+
+
+ def jellyFor(self, jellier):
+ """
+ @see: L{twisted.spread.interfaces.IJellyable.jellyFor}
+ """
+ sxp = jellier.prepare(self)
+ sxp.extend([
+ qual(self.__class__),
+ jellier.jelly(self.getStateFor(jellier))])
+ return jellier.preserve(self, sxp)
+
+
+
+class Unjellyable:
+ """
+ Inherit from me to Unjelly yourself directly with the
+ C{setStateFor} convenience method.
+ """
+ implements(IUnjellyable)
+
+ def setStateFor(self, unjellier, state):
+ self.__dict__ = state
+
+
+ def unjellyFor(self, unjellier, jellyList):
+ """
+ Perform the inverse operation of L{Jellyable.jellyFor}.
+
+ @see: L{twisted.spread.interfaces.IUnjellyable.unjellyFor}
+ """
+ state = unjellier.unjelly(jellyList[1])
+ self.setStateFor(unjellier, state)
+ return self
+
+
+
+class _Jellier:
+ """
+ (Internal) This class manages state for a call to jelly()
+ """
+
+ def __init__(self, taster, persistentStore, invoker):
+ """
+ Initialize.
+ """
+ self.taster = taster
+ # `preserved' is a dict of previously seen instances.
+ self.preserved = {}
+ # `cooked' is a dict of previously backreferenced instances to their
+ # `ref' lists.
+ self.cooked = {}
+ self.cooker = {}
+ self._ref_id = 1
+ self.persistentStore = persistentStore
+ self.invoker = invoker
+
+
+ def _cook(self, object):
+ """
+ (internal) Backreference an object.
+
+ Notes on this method for the hapless future maintainer: If I've already
+ gone through the prepare/preserve cycle on the specified object (it is
+ being referenced after the serializer is \"done with\" it, e.g. this
+ reference is NOT circular), the copy-in-place of aList is relevant,
+ since the list being modified is the actual, pre-existing jelly
+ expression that was returned for that object. If not, it's technically
+ superfluous, since the value in self.preserved didn't need to be set,
+ but the invariant that self.preserved[id(object)] is a list is
+ convenient because that means we don't have to test and create it or
+ not create it here, creating fewer code-paths. that's why
+ self.preserved is always set to a list.
+
+ Sorry that this code is so hard to follow, but Python objects are
+ tricky to persist correctly. -glyph
+ """
+ aList = self.preserved[id(object)]
+ newList = copy.copy(aList)
+ # make a new reference ID
+ refid = self._ref_id
+ self._ref_id = self._ref_id + 1
+ # replace the old list in-place, so that we don't have to track the
+ # previous reference to it.
+ aList[:] = [reference_atom, refid, newList]
+ self.cooked[id(object)] = [dereference_atom, refid]
+ return aList
+
+
+ def prepare(self, object):
+ """
+ (internal) Create a list for persisting an object to. This will allow
+ backreferences to be made internal to the object. (circular
+ references).
+
+ The reason this needs to happen is that we don't generate an ID for
+ every object, so we won't necessarily know which ID the object will
+ have in the future. When it is 'cooked' ( see _cook ), it will be
+ assigned an ID, and the temporary placeholder list created here will be
+ modified in-place to create an expression that gives this object an ID:
+ [reference id# [object-jelly]].
+ """
+
+ # create a placeholder list to be preserved
+ self.preserved[id(object)] = []
+ # keep a reference to this object around, so it doesn't disappear!
+ # (This isn't always necessary, but for cases where the objects are
+ # dynamically generated by __getstate__ or getStateToCopyFor calls, it
+ # is; id() will return the same value for a different object if it gets
+ # garbage collected. This may be optimized later.)
+ self.cooker[id(object)] = object
+ return []
+
+
+ def preserve(self, object, sexp):
+ """
+ (internal) Mark an object's persistent list for later referral.
+ """
+ # if I've been cooked in the meanwhile,
+ if id(object) in self.cooked:
+ # replace the placeholder empty list with the real one
+ self.preserved[id(object)][2] = sexp
+ # but give this one back.
+ sexp = self.preserved[id(object)]
+ else:
+ self.preserved[id(object)] = sexp
+ return sexp
+
+ constantTypes = {types.StringType : 1, types.IntType : 1,
+ types.FloatType : 1, types.LongType : 1}
+
+
+ def _checkMutable(self,obj):
+ objId = id(obj)
+ if objId in self.cooked:
+ return self.cooked[objId]
+ if objId in self.preserved:
+ self._cook(obj)
+ return self.cooked[objId]
+
+
+ def jelly(self, obj):
+ if isinstance(obj, Jellyable):
+ preRef = self._checkMutable(obj)
+ if preRef:
+ return preRef
+ return obj.jellyFor(self)
+ objType = type(obj)
+ if self.taster.isTypeAllowed(qual(objType)):
+ # "Immutable" Types
+ if ((objType is StringType) or
+ (objType is IntType) or
+ (objType is LongType) or
+ (objType is FloatType)):
+ return obj
+ elif objType is MethodType:
+ return ["method",
+ obj.im_func.__name__,
+ self.jelly(obj.im_self),
+ self.jelly(obj.im_class)]
+
+ elif UnicodeType and objType is UnicodeType:
+ return ['unicode', obj.encode('UTF-8')]
+ elif objType is NoneType:
+ return ['None']
+ elif objType is FunctionType:
+ name = obj.__name__
+ return ['function', str(pickle.whichmodule(obj, obj.__name__))
+ + '.' +
+ name]
+ elif objType is ModuleType:
+ return ['module', obj.__name__]
+ elif objType is BooleanType:
+ return ['boolean', obj and 'true' or 'false']
+ elif objType is datetime.datetime:
+ if obj.tzinfo:
+ raise NotImplementedError(
+ "Currently can't jelly datetime objects with tzinfo")
+ return ['datetime', '%s %s %s %s %s %s %s' % (
+ obj.year, obj.month, obj.day, obj.hour,
+ obj.minute, obj.second, obj.microsecond)]
+ elif objType is datetime.time:
+ if obj.tzinfo:
+ raise NotImplementedError(
+ "Currently can't jelly datetime objects with tzinfo")
+ return ['time', '%s %s %s %s' % (obj.hour, obj.minute,
+ obj.second, obj.microsecond)]
+ elif objType is datetime.date:
+ return ['date', '%s %s %s' % (obj.year, obj.month, obj.day)]
+ elif objType is datetime.timedelta:
+ return ['timedelta', '%s %s %s' % (obj.days, obj.seconds,
+ obj.microseconds)]
+ elif objType is ClassType or issubclass(objType, type):
+ return ['class', qual(obj)]
+ elif decimal is not None and objType is decimal.Decimal:
+ return self.jelly_decimal(obj)
+ else:
+ preRef = self._checkMutable(obj)
+ if preRef:
+ return preRef
+ # "Mutable" Types
+ sxp = self.prepare(obj)
+ if objType is ListType:
+ sxp.extend(self._jellyIterable(list_atom, obj))
+ elif objType is TupleType:
+ sxp.extend(self._jellyIterable(tuple_atom, obj))
+ elif objType in DictTypes:
+ sxp.append(dictionary_atom)
+ for key, val in obj.items():
+ sxp.append([self.jelly(key), self.jelly(val)])
+ elif (_set is not None and objType is set or
+ objType is _sets.Set):
+ sxp.extend(self._jellyIterable(set_atom, obj))
+ elif (_set is not None and objType is frozenset or
+ objType is _sets.ImmutableSet):
+ sxp.extend(self._jellyIterable(frozenset_atom, obj))
+ else:
+ className = qual(obj.__class__)
+ persistent = None
+ if self.persistentStore:
+ persistent = self.persistentStore(obj, self)
+ if persistent is not None:
+ sxp.append(persistent_atom)
+ sxp.append(persistent)
+ elif self.taster.isClassAllowed(obj.__class__):
+ sxp.append(className)
+ if hasattr(obj, "__getstate__"):
+ state = obj.__getstate__()
+ else:
+ state = obj.__dict__
+ sxp.append(self.jelly(state))
+ else:
+ self.unpersistable(
+ "instance of class %s deemed insecure" %
+ qual(obj.__class__), sxp)
+ return self.preserve(obj, sxp)
+ else:
+ if objType is InstanceType:
+ raise InsecureJelly("Class not allowed for instance: %s %s" %
+ (obj.__class__, obj))
+ raise InsecureJelly("Type not allowed for object: %s %s" %
+ (objType, obj))
+
+
+ def _jellyIterable(self, atom, obj):
+ """
+ Jelly an iterable object.
+
+ @param atom: the identifier atom of the object.
+ @type atom: C{str}
+
+ @param obj: any iterable object.
+ @type obj: C{iterable}
+
+ @return: a generator of jellied data.
+ @rtype: C{generator}
+ """
+ yield atom
+ for item in obj:
+ yield self.jelly(item)
+
+
+ def jelly_decimal(self, d):
+ """
+ Jelly a decimal object.
+
+ @param d: a decimal object to serialize.
+ @type d: C{decimal.Decimal}
+
+ @return: jelly for the decimal object.
+ @rtype: C{list}
+ """
+ sign, guts, exponent = d.as_tuple()
+ value = reduce(lambda left, right: left * 10 + right, guts)
+ if sign:
+ value = -value
+ return ['decimal', value, exponent]
+
+
+ def unpersistable(self, reason, sxp=None):
+ """
+ (internal) Returns an sexp: (unpersistable "reason"). Utility method
+ for making note that a particular object could not be serialized.
+ """
+ if sxp is None:
+ sxp = []
+ sxp.append(unpersistable_atom)
+ sxp.append(reason)
+ return sxp
+
+
+
+class _Unjellier:
+
+ def __init__(self, taster, persistentLoad, invoker):
+ self.taster = taster
+ self.persistentLoad = persistentLoad
+ self.references = {}
+ self.postCallbacks = []
+ self.invoker = invoker
+
+
+ def unjellyFull(self, obj):
+ o = self.unjelly(obj)
+ for m in self.postCallbacks:
+ m()
+ return o
+
+
+ def unjelly(self, obj):
+ if type(obj) is not types.ListType:
+ return obj
+ jelType = obj[0]
+ if not self.taster.isTypeAllowed(jelType):
+ raise InsecureJelly(jelType)
+ regClass = unjellyableRegistry.get(jelType)
+ if regClass is not None:
+ if isinstance(regClass, ClassType):
+ inst = _Dummy() # XXX chomp, chomp
+ inst.__class__ = regClass
+ method = inst.unjellyFor
+ elif isinstance(regClass, type):
+ # regClass.__new__ does not call regClass.__init__
+ inst = regClass.__new__(regClass)
+ method = inst.unjellyFor
+ else:
+ method = regClass # this is how it ought to be done
+ val = method(self, obj)
+ if hasattr(val, 'postUnjelly'):
+ self.postCallbacks.append(inst.postUnjelly)
+ return val
+ regFactory = unjellyableFactoryRegistry.get(jelType)
+ if regFactory is not None:
+ state = self.unjelly(obj[1])
+ inst = regFactory(state)
+ if hasattr(inst, 'postUnjelly'):
+ self.postCallbacks.append(inst.postUnjelly)
+ return inst
+ thunk = getattr(self, '_unjelly_%s'%jelType, None)
+ if thunk is not None:
+ ret = thunk(obj[1:])
+ else:
+ nameSplit = jelType.split('.')
+ modName = '.'.join(nameSplit[:-1])
+ if not self.taster.isModuleAllowed(modName):
+ raise InsecureJelly(
+ "Module %s not allowed (in type %s)." % (modName, jelType))
+ clz = namedObject(jelType)
+ if not self.taster.isClassAllowed(clz):
+ raise InsecureJelly("Class %s not allowed." % jelType)
+ if hasattr(clz, "__setstate__"):
+ ret = _newInstance(clz, {})
+ state = self.unjelly(obj[1])
+ ret.__setstate__(state)
+ else:
+ state = self.unjelly(obj[1])
+ ret = _newInstance(clz, state)
+ if hasattr(clz, 'postUnjelly'):
+ self.postCallbacks.append(ret.postUnjelly)
+ return ret
+
+
+ def _unjelly_None(self, exp):
+ return None
+
+
+ def _unjelly_unicode(self, exp):
+ if UnicodeType:
+ return unicode(exp[0], "UTF-8")
+ else:
+ return Unpersistable("Could not unpersist unicode: %s" % (exp[0],))
+
+
+ def _unjelly_decimal(self, exp):
+ """
+ Unjelly decimal objects, if decimal is available. If not, return a
+ L{Unpersistable} object instead.
+ """
+ if decimal is None:
+ return Unpersistable(
+ "Could not unpersist decimal: %s" % (exp[0] * (10**exp[1]),))
+ value = exp[0]
+ exponent = exp[1]
+ if value < 0:
+ sign = 1
+ else:
+ sign = 0
+ guts = decimal.Decimal(value).as_tuple()[1]
+ return decimal.Decimal((sign, guts, exponent))
+
+
+ def _unjelly_boolean(self, exp):
+ if BooleanType:
+ assert exp[0] in ('true', 'false')
+ return exp[0] == 'true'
+ else:
+ return Unpersistable("Could not unpersist boolean: %s" % (exp[0],))
+
+
+ def _unjelly_datetime(self, exp):
+ return datetime.datetime(*map(int, exp[0].split()))
+
+
+ def _unjelly_date(self, exp):
+ return datetime.date(*map(int, exp[0].split()))
+
+
+ def _unjelly_time(self, exp):
+ return datetime.time(*map(int, exp[0].split()))
+
+
+ def _unjelly_timedelta(self, exp):
+ days, seconds, microseconds = map(int, exp[0].split())
+ return datetime.timedelta(
+ days=days, seconds=seconds, microseconds=microseconds)
+
+
+ def unjellyInto(self, obj, loc, jel):
+ o = self.unjelly(jel)
+ if isinstance(o, NotKnown):
+ o.addDependant(obj, loc)
+ obj[loc] = o
+ return o
+
+
+ def _unjelly_dereference(self, lst):
+ refid = lst[0]
+ x = self.references.get(refid)
+ if x is not None:
+ return x
+ der = _Dereference(refid)
+ self.references[refid] = der
+ return der
+
+
+ def _unjelly_reference(self, lst):
+ refid = lst[0]
+ exp = lst[1]
+ o = self.unjelly(exp)
+ ref = self.references.get(refid)
+ if (ref is None):
+ self.references[refid] = o
+ elif isinstance(ref, NotKnown):
+ ref.resolveDependants(o)
+ self.references[refid] = o
+ else:
+ assert 0, "Multiple references with same ID!"
+ return o
+
+
+ def _unjelly_tuple(self, lst):
+ l = range(len(lst))
+ finished = 1
+ for elem in l:
+ if isinstance(self.unjellyInto(l, elem, lst[elem]), NotKnown):
+ finished = 0
+ if finished:
+ return tuple(l)
+ else:
+ return _Tuple(l)
+
+
+ def _unjelly_list(self, lst):
+ l = range(len(lst))
+ for elem in l:
+ self.unjellyInto(l, elem, lst[elem])
+ return l
+
+
+ def _unjellySetOrFrozenset(self, lst, containerType):
+ """
+ Helper method to unjelly set or frozenset.
+
+ @param lst: the content of the set.
+ @type lst: C{list}
+
+ @param containerType: the type of C{set} to use.
+ """
+ l = range(len(lst))
+ finished = True
+ for elem in l:
+ data = self.unjellyInto(l, elem, lst[elem])
+ if isinstance(data, NotKnown):
+ finished = False
+ if not finished:
+ return _Container(l, containerType)
+ else:
+ return containerType(l)
+
+
+ def _unjelly_set(self, lst):
+ """
+ Unjelly set using either the C{set} builtin if available, or
+ C{sets.Set} as fallback.
+ """
+ if _set is not None:
+ containerType = set
+ else:
+ containerType = _sets.Set
+ return self._unjellySetOrFrozenset(lst, containerType)
+
+
+ def _unjelly_frozenset(self, lst):
+ """
+ Unjelly frozenset using either the C{frozenset} builtin if available,
+ or C{sets.ImmutableSet} as fallback.
+ """
+ if _set is not None:
+ containerType = frozenset
+ else:
+ containerType = _sets.ImmutableSet
+ return self._unjellySetOrFrozenset(lst, containerType)
+
+
+ def _unjelly_dictionary(self, lst):
+ d = {}
+ for k, v in lst:
+ kvd = _DictKeyAndValue(d)
+ self.unjellyInto(kvd, 0, k)
+ self.unjellyInto(kvd, 1, v)
+ return d
+
+
+ def _unjelly_module(self, rest):
+ moduleName = rest[0]
+ if type(moduleName) != types.StringType:
+ raise InsecureJelly(
+ "Attempted to unjelly a module with a non-string name.")
+ if not self.taster.isModuleAllowed(moduleName):
+ raise InsecureJelly(
+ "Attempted to unjelly module named %r" % (moduleName,))
+ mod = __import__(moduleName, {}, {},"x")
+ return mod
+
+
+ def _unjelly_class(self, rest):
+ clist = rest[0].split('.')
+ modName = '.'.join(clist[:-1])
+ if not self.taster.isModuleAllowed(modName):
+ raise InsecureJelly("module %s not allowed" % modName)
+ klaus = namedObject(rest[0])
+ objType = type(klaus)
+ if objType not in (types.ClassType, types.TypeType):
+ raise InsecureJelly(
+ "class %r unjellied to something that isn't a class: %r" % (
+ rest[0], klaus))
+ if not self.taster.isClassAllowed(klaus):
+ raise InsecureJelly("class not allowed: %s" % qual(klaus))
+ return klaus
+
+
+ def _unjelly_function(self, rest):
+ modSplit = rest[0].split('.')
+ modName = '.'.join(modSplit[:-1])
+ if not self.taster.isModuleAllowed(modName):
+ raise InsecureJelly("Module not allowed: %s"% modName)
+ # XXX do I need an isFunctionAllowed?
+ function = namedObject(rest[0])
+ return function
+
+
+ def _unjelly_persistent(self, rest):
+ if self.persistentLoad:
+ pload = self.persistentLoad(rest[0], self)
+ return pload
+ else:
+ return Unpersistable("Persistent callback not found")
+
+
+ def _unjelly_instance(self, rest):
+ clz = self.unjelly(rest[0])
+ if type(clz) is not types.ClassType:
+ raise InsecureJelly("Instance found with non-class class.")
+ if hasattr(clz, "__setstate__"):
+ inst = _newInstance(clz, {})
+ state = self.unjelly(rest[1])
+ inst.__setstate__(state)
+ else:
+ state = self.unjelly(rest[1])
+ inst = _newInstance(clz, state)
+ if hasattr(clz, 'postUnjelly'):
+ self.postCallbacks.append(inst.postUnjelly)
+ return inst
+
+
+ def _unjelly_unpersistable(self, rest):
+ return Unpersistable("Unpersistable data: %s" % (rest[0],))
+
+
+ def _unjelly_method(self, rest):
+ """
+ (internal) Unjelly a method.
+ """
+ im_name = rest[0]
+ im_self = self.unjelly(rest[1])
+ im_class = self.unjelly(rest[2])
+ if type(im_class) is not types.ClassType:
+ raise InsecureJelly("Method found with non-class class.")
+ if im_name in im_class.__dict__:
+ if im_self is None:
+ im = getattr(im_class, im_name)
+ elif isinstance(im_self, NotKnown):
+ im = _InstanceMethod(im_name, im_self, im_class)
+ else:
+ im = instancemethod(im_class.__dict__[im_name],
+ im_self,
+ im_class)
+ else:
+ raise TypeError('instance method changed')
+ return im
+
+
+
+class _Dummy:
+ """
+ (Internal) Dummy class, used for unserializing instances.
+ """
+
+
+
+class _DummyNewStyle(object):
+ """
+ (Internal) Dummy class, used for unserializing instances of new-style
+ classes.
+ """
+
+
+
+#### Published Interface.
+
+
+class InsecureJelly(Exception):
+ """
+ This exception will be raised when a jelly is deemed `insecure'; e.g. it
+ contains a type, class, or module disallowed by the specified `taster'
+ """
+
+
+
+class DummySecurityOptions:
+ """
+ DummySecurityOptions() -> insecure security options
+ Dummy security options -- this class will allow anything.
+ """
+
+ def isModuleAllowed(self, moduleName):
+ """
+ DummySecurityOptions.isModuleAllowed(moduleName) -> boolean
+ returns 1 if a module by that name is allowed, 0 otherwise
+ """
+ return 1
+
+
+ def isClassAllowed(self, klass):
+ """
+ DummySecurityOptions.isClassAllowed(class) -> boolean
+ Assumes the module has already been allowed. Returns 1 if the given
+ class is allowed, 0 otherwise.
+ """
+ return 1
+
+
+ def isTypeAllowed(self, typeName):
+ """
+ DummySecurityOptions.isTypeAllowed(typeName) -> boolean
+ Returns 1 if the given type is allowed, 0 otherwise.
+ """
+ return 1
+
+
+
+class SecurityOptions:
+ """
+ This will by default disallow everything, except for 'none'.
+ """
+
+ basicTypes = ["dictionary", "list", "tuple",
+ "reference", "dereference", "unpersistable",
+ "persistent", "long_int", "long", "dict"]
+
+ def __init__(self):
+ """
+ SecurityOptions() initialize.
+ """
+ # I don't believe any of these types can ever pose a security hazard,
+ # except perhaps "reference"...
+ self.allowedTypes = {"None": 1,
+ "bool": 1,
+ "boolean": 1,
+ "string": 1,
+ "str": 1,
+ "int": 1,
+ "float": 1,
+ "datetime": 1,
+ "time": 1,
+ "date": 1,
+ "timedelta": 1,
+ "NoneType": 1}
+ if hasattr(types, 'UnicodeType'):
+ self.allowedTypes['unicode'] = 1
+ if decimal is not None:
+ self.allowedTypes['decimal'] = 1
+ self.allowedTypes['set'] = 1
+ self.allowedTypes['frozenset'] = 1
+ self.allowedModules = {}
+ self.allowedClasses = {}
+
+
+ def allowBasicTypes(self):
+ """
+ Allow all `basic' types. (Dictionary and list. Int, string, and float
+ are implicitly allowed.)
+ """
+ self.allowTypes(*self.basicTypes)
+
+
+ def allowTypes(self, *types):
+ """
+ SecurityOptions.allowTypes(typeString): Allow a particular type, by its
+ name.
+ """
+ for typ in types:
+ if not isinstance(typ, str):
+ typ = qual(typ)
+ self.allowedTypes[typ] = 1
+
+
+ def allowInstancesOf(self, *classes):
+ """
+ SecurityOptions.allowInstances(klass, klass, ...): allow instances
+ of the specified classes
+
+ This will also allow the 'instance', 'class' (renamed 'classobj' in
+ Python 2.3), and 'module' types, as well as basic types.
+ """
+ self.allowBasicTypes()
+ self.allowTypes("instance", "class", "classobj", "module")
+ for klass in classes:
+ self.allowTypes(qual(klass))
+ self.allowModules(klass.__module__)
+ self.allowedClasses[klass] = 1
+
+
+ def allowModules(self, *modules):
+ """
+ SecurityOptions.allowModules(module, module, ...): allow modules by
+ name. This will also allow the 'module' type.
+ """
+ for module in modules:
+ if type(module) == types.ModuleType:
+ module = module.__name__
+ self.allowedModules[module] = 1
+
+
+ def isModuleAllowed(self, moduleName):
+ """
+ SecurityOptions.isModuleAllowed(moduleName) -> boolean
+ returns 1 if a module by that name is allowed, 0 otherwise
+ """
+ return moduleName in self.allowedModules
+
+
+ def isClassAllowed(self, klass):
+ """
+ SecurityOptions.isClassAllowed(class) -> boolean
+ Assumes the module has already been allowed. Returns 1 if the given
+ class is allowed, 0 otherwise.
+ """
+ return klass in self.allowedClasses
+
+
+ def isTypeAllowed(self, typeName):
+ """
+ SecurityOptions.isTypeAllowed(typeName) -> boolean
+ Returns 1 if the given type is allowed, 0 otherwise.
+ """
+ return (typeName in self.allowedTypes or '.' in typeName)
+
+
+globalSecurity = SecurityOptions()
+globalSecurity.allowBasicTypes()
+
+
+
+def jelly(object, taster=DummySecurityOptions(), persistentStore=None,
+ invoker=None):
+ """
+ Serialize to s-expression.
+
+ Returns a list which is the serialized representation of an object. An
+ optional 'taster' argument takes a SecurityOptions and will mark any
+ insecure objects as unpersistable rather than serializing them.
+ """
+ return _Jellier(taster, persistentStore, invoker).jelly(object)
+
+
+
+def unjelly(sexp, taster=DummySecurityOptions(), persistentLoad=None,
+ invoker=None):
+ """
+ Unserialize from s-expression.
+
+ Takes an list that was the result from a call to jelly() and unserializes
+ an arbitrary object from it. The optional 'taster' argument, an instance
+ of SecurityOptions, will cause an InsecureJelly exception to be raised if a
+ disallowed type, module, or class attempted to unserialize.
+ """
+ return _Unjellier(taster, persistentLoad, invoker).unjellyFull(sexp)
diff --git a/vendor/Twisted-10.0.0/twisted/spread/pb.py b/vendor/Twisted-10.0.0/twisted/spread/pb.py
new file mode 100644
index 0000000000..0ccee9e782
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/pb.py
@@ -0,0 +1,1380 @@
+# -*- test-case-name: twisted.test.test_pb -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Perspective Broker
+
+\"This isn\'t a professional opinion, but it's probably got enough
+internet to kill you.\" --glyph
+
+Introduction
+============
+
+This is a broker for proxies for and copies of objects. It provides a
+translucent interface layer to those proxies.
+
+The protocol is not opaque, because it provides objects which represent the
+remote proxies and require no context (server references, IDs) to operate on.
+
+It is not transparent because it does I{not} attempt to make remote objects
+behave identically, or even similiarly, to local objects. Method calls are
+invoked asynchronously, and specific rules are applied when serializing
+arguments.
+
+To get started, begin with L{PBClientFactory} and L{PBServerFactory}.
+
+@author: Glyph Lefkowitz
+"""
+
+import random
+import new
+import types
+
+from zope.interface import implements, Interface
+
+# Twisted Imports
+from twisted.python import log, failure, reflect
+from twisted.python.versions import Version
+from twisted.python.deprecate import deprecated
+from twisted.python.hashlib import md5
+from twisted.internet import defer, protocol
+from twisted.cred.portal import Portal
+from twisted.cred.credentials import IAnonymous, ICredentials
+from twisted.cred.credentials import IUsernameHashedPassword, Anonymous
+from twisted.persisted import styles
+from twisted.python.components import registerAdapter
+
+from twisted.spread.interfaces import IJellyable, IUnjellyable
+from twisted.spread.jelly import jelly, unjelly, globalSecurity
+from twisted.spread import banana
+
+from twisted.spread.flavors import Serializable
+from twisted.spread.flavors import Referenceable, NoSuchMethod
+from twisted.spread.flavors import Root, IPBRoot
+from twisted.spread.flavors import ViewPoint
+from twisted.spread.flavors import Viewable
+from twisted.spread.flavors import Copyable
+from twisted.spread.flavors import Jellyable
+from twisted.spread.flavors import Cacheable
+from twisted.spread.flavors import RemoteCopy
+from twisted.spread.flavors import RemoteCache
+from twisted.spread.flavors import RemoteCacheObserver
+from twisted.spread.flavors import copyTags
+
+from twisted.spread.flavors import setUnjellyableForClass
+from twisted.spread.flavors import setUnjellyableFactoryForClass
+from twisted.spread.flavors import setUnjellyableForClassTree
+# These three are backwards compatibility aliases for the previous three.
+# Ultimately they should be deprecated. -exarkun
+from twisted.spread.flavors import setCopierForClass
+from twisted.spread.flavors import setFactoryForClass
+from twisted.spread.flavors import setCopierForClassTree
+
+
+MAX_BROKER_REFS = 1024
+
+portno = 8787
+
+
+class ProtocolError(Exception):
+ """
+ This error is raised when an invalid protocol statement is received.
+ """
+
+class DeadReferenceError(ProtocolError):
+ """
+ This error is raised when a method is called on a dead reference (one whose
+ broker has been disconnected).
+ """
+
+class Error(Exception):
+ """
+ This error can be raised to generate known error conditions.
+
+ When a PB callable method (perspective_, remote_, view_) raises
+ this error, it indicates that a traceback should not be printed,
+ but instead, the string representation of the exception should be
+ sent.
+ """
+
+class RemoteMethod:
+ """This is a translucent reference to a remote message.
+ """
+ def __init__(self, obj, name):
+ """Initialize with a L{RemoteReference} and the name of this message.
+ """
+ self.obj = obj
+ self.name = name
+
+ def __cmp__(self, other):
+ return cmp((self.obj, self.name), other)
+
+ def __hash__(self):
+ return hash((self.obj, self.name))
+
+ def __call__(self, *args, **kw):
+ """Asynchronously invoke a remote method.
+ """
+ return self.obj.broker._sendMessage('',self.obj.perspective, self.obj.luid, self.name, args, kw)
+
+
+
+def noOperation(*args, **kw):
+ """
+ Do nothing.
+
+ Neque porro quisquam est qui dolorem ipsum quia dolor sit amet,
+ consectetur, adipisci velit...
+ """
+noOperation = deprecated(Version("twisted", 8, 2, 0))(noOperation)
+
+
+
+class PBConnectionLost(Exception):
+ pass
+
+
+
+def printTraceback(tb):
+ """
+ Print a traceback (string) to the standard log.
+ """
+ log.msg('Perspective Broker Traceback:' )
+ log.msg(tb)
+printTraceback = deprecated(Version("twisted", 8, 2, 0))(printTraceback)
+
+
+class IPerspective(Interface):
+ """
+ per*spec*tive, n. : The relationship of aspects of a subject to each
+ other and to a whole: 'a perspective of history'; 'a need to view
+ the problem in the proper perspective'.
+
+ This is a Perspective Broker-specific wrapper for an avatar. That
+ is to say, a PB-published view on to the business logic for the
+ system's concept of a 'user'.
+
+ The concept of attached/detached is no longer implemented by the
+ framework. The realm is expected to implement such semantics if
+ needed.
+ """
+
+ def perspectiveMessageReceived(broker, message, args, kwargs):
+ """
+ This method is called when a network message is received.
+
+ @arg broker: The Perspective Broker.
+
+ @type message: str
+ @arg message: The name of the method called by the other end.
+
+ @type args: list in jelly format
+ @arg args: The arguments that were passed by the other end. It
+ is recommend that you use the `unserialize' method of the
+ broker to decode this.
+
+ @type kwargs: dict in jelly format
+ @arg kwargs: The keyword arguments that were passed by the
+ other end. It is recommended that you use the
+ `unserialize' method of the broker to decode this.
+
+ @rtype: A jelly list.
+ @return: It is recommended that you use the `serialize' method
+ of the broker on whatever object you need to return to
+ generate the return value.
+ """
+
+
+
+class Avatar:
+ """
+ A default IPerspective implementor.
+
+ This class is intended to be subclassed, and a realm should return
+ an instance of such a subclass when IPerspective is requested of
+ it.
+
+ A peer requesting a perspective will receive only a
+ L{RemoteReference} to a pb.Avatar. When a method is called on
+ that L{RemoteReference}, it will translate to a method on the
+ remote perspective named 'perspective_methodname'. (For more
+ information on invoking methods on other objects, see
+ L{flavors.ViewPoint}.)
+ """
+
+ implements(IPerspective)
+
+ def perspectiveMessageReceived(self, broker, message, args, kw):
+ """
+ This method is called when a network message is received.
+
+ This will call::
+
+ self.perspective_%(message)s(*broker.unserialize(args),
+ **broker.unserialize(kw))
+
+ to handle the method; subclasses of Avatar are expected to
+ implement methods using this naming convention.
+ """
+
+ args = broker.unserialize(args, self)
+ kw = broker.unserialize(kw, self)
+ method = getattr(self, "perspective_%s" % message)
+ try:
+ state = method(*args, **kw)
+ except TypeError:
+ log.msg("%s didn't accept %s and %s" % (method, args, kw))
+ raise
+ return broker.serialize(state, self, method, args, kw)
+
+
+
+class AsReferenceable(Referenceable):
+ """
+ A reference directed towards another object.
+ """
+
+ def __init__(self, object, messageType="remote"):
+ self.remoteMessageReceived = getattr(
+ object, messageType + "MessageReceived")
+
+
+
+class RemoteReference(Serializable, styles.Ephemeral):
+ """
+ A translucent reference to a remote object.
+
+ I may be a reference to a L{flavors.ViewPoint}, a
+ L{flavors.Referenceable}, or an L{IPerspective} implementor (e.g.,
+ pb.Avatar). From the client's perspective, it is not possible to
+ tell which except by convention.
+
+ I am a \"translucent\" reference because although no additional
+ bookkeeping overhead is given to the application programmer for
+ manipulating a reference, return values are asynchronous.
+
+ See also L{twisted.internet.defer}.
+
+ @ivar broker: The broker I am obtained through.
+ @type broker: L{Broker}
+ """
+
+ implements(IUnjellyable)
+
+ def __init__(self, perspective, broker, luid, doRefCount):
+ """(internal) Initialize me with a broker and a locally-unique ID.
+
+ The ID is unique only to the particular Perspective Broker
+ instance.
+ """
+ self.luid = luid
+ self.broker = broker
+ self.doRefCount = doRefCount
+ self.perspective = perspective
+ self.disconnectCallbacks = []
+
+ def notifyOnDisconnect(self, callback):
+ """Register a callback to be called if our broker gets disconnected.
+
+ This callback will be called with one argument, this instance.
+ """
+ assert callable(callback)
+ self.disconnectCallbacks.append(callback)
+ if len(self.disconnectCallbacks) == 1:
+ self.broker.notifyOnDisconnect(self._disconnected)
+
+ def dontNotifyOnDisconnect(self, callback):
+ """Remove a callback that was registered with notifyOnDisconnect."""
+ self.disconnectCallbacks.remove(callback)
+ if not self.disconnectCallbacks:
+ self.broker.dontNotifyOnDisconnect(self._disconnected)
+
+ def _disconnected(self):
+ """Called if we are disconnected and have callbacks registered."""
+ for callback in self.disconnectCallbacks:
+ callback(self)
+ self.disconnectCallbacks = None
+
+ def jellyFor(self, jellier):
+ """If I am being sent back to where I came from, serialize as a local backreference.
+ """
+ if jellier.invoker:
+ assert self.broker == jellier.invoker, "Can't send references to brokers other than their own."
+ return "local", self.luid
+ else:
+ return "unpersistable", "References cannot be serialized"
+
+ def unjellyFor(self, unjellier, unjellyList):
+ self.__init__(unjellier.invoker.unserializingPerspective, unjellier.invoker, unjellyList[1], 1)
+ return self
+
+ def callRemote(self, _name, *args, **kw):
+ """Asynchronously invoke a remote method.
+
+ @type _name: C{string}
+ @param _name: the name of the remote method to invoke
+ @param args: arguments to serialize for the remote function
+ @param kw: keyword arguments to serialize for the remote function.
+ @rtype: L{twisted.internet.defer.Deferred}
+ @returns: a Deferred which will be fired when the result of
+ this remote call is received.
+ """
+ # note that we use '_name' instead of 'name' so the user can call
+ # remote methods with 'name' as a keyword parameter, like this:
+ # ref.callRemote("getPeopleNamed", count=12, name="Bob")
+
+ return self.broker._sendMessage('',self.perspective, self.luid,
+ _name, args, kw)
+
+ def remoteMethod(self, key):
+ """Get a L{RemoteMethod} for this key.
+ """
+ return RemoteMethod(self, key)
+
+ def __cmp__(self,other):
+ """Compare me [to another L{RemoteReference}].
+ """
+ if isinstance(other, RemoteReference):
+ if other.broker == self.broker:
+ return cmp(self.luid, other.luid)
+ return cmp(self.broker, other)
+
+ def __hash__(self):
+ """Hash me.
+ """
+ return self.luid
+
+ def __del__(self):
+ """Do distributed reference counting on finalization.
+ """
+ if self.doRefCount:
+ self.broker.sendDecRef(self.luid)
+
+setUnjellyableForClass("remote", RemoteReference)
+
+class Local:
+ """(internal) A reference to a local object.
+ """
+
+ def __init__(self, object, perspective=None):
+ """Initialize.
+ """
+ self.object = object
+ self.perspective = perspective
+ self.refcount = 1
+
+ def __repr__(self):
+ return "<pb.Local %r ref:%s>" % (self.object, self.refcount)
+
+ def incref(self):
+ """Increment and return my reference count.
+ """
+ self.refcount = self.refcount + 1
+ return self.refcount
+
+ def decref(self):
+ """Decrement and return my reference count.
+ """
+ self.refcount = self.refcount - 1
+ return self.refcount
+
+
+##
+# Failure
+##
+
+class CopyableFailure(failure.Failure, Copyable):
+ """
+ A L{flavors.RemoteCopy} and L{flavors.Copyable} version of
+ L{twisted.python.failure.Failure} for serialization.
+ """
+
+ unsafeTracebacks = 0
+
+ def getStateToCopy(self):
+ """
+ Collect state related to the exception which occurred, discarding
+ state which cannot reasonably be serialized.
+ """
+ state = self.__dict__.copy()
+ state['tb'] = None
+ state['frames'] = []
+ state['stack'] = []
+ if isinstance(self.value, failure.Failure):
+ state['value'] = failure2Copyable(self.value, self.unsafeTracebacks)
+ else:
+ state['value'] = str(self.value) # Exception instance
+ if isinstance(self.type, str):
+ state['type'] = self.type
+ else:
+ state['type'] = reflect.qual(self.type) # Exception class
+ if self.unsafeTracebacks:
+ state['traceback'] = self.getTraceback()
+ else:
+ state['traceback'] = 'Traceback unavailable\n'
+ return state
+
+
+class CopiedFailure(RemoteCopy, failure.Failure):
+ def printTraceback(self, file=None, elideFrameworkCode=0, detail='default'):
+ if file is None:
+ file = log.logfile
+ file.write("Traceback from remote host -- ")
+ file.write(self.traceback)
+
+ printBriefTraceback = printTraceback
+ printDetailedTraceback = printTraceback
+
+setUnjellyableForClass(CopyableFailure, CopiedFailure)
+
+def failure2Copyable(fail, unsafeTracebacks=0):
+ f = new.instance(CopyableFailure, fail.__dict__)
+ f.unsafeTracebacks = unsafeTracebacks
+ return f
+
+class Broker(banana.Banana):
+ """I am a broker for objects.
+ """
+
+ version = 6
+ username = None
+ factory = None
+
+ def __init__(self, isClient=1, security=globalSecurity):
+ banana.Banana.__init__(self, isClient)
+ self.disconnected = 0
+ self.disconnects = []
+ self.failures = []
+ self.connects = []
+ self.localObjects = {}
+ self.security = security
+ self.pageProducers = []
+ self.currentRequestID = 0
+ self.currentLocalID = 0
+ # Some terms:
+ # PUID: process unique ID; return value of id() function. type "int".
+ # LUID: locally unique ID; an ID unique to an object mapped over this
+ # connection. type "int"
+ # GUID: (not used yet) globally unique ID; an ID for an object which
+ # may be on a redirected or meta server. Type as yet undecided.
+ # Dictionary mapping LUIDs to local objects.
+ # set above to allow root object to be assigned before connection is made
+ # self.localObjects = {}
+ # Dictionary mapping PUIDs to LUIDs.
+ self.luids = {}
+ # Dictionary mapping LUIDs to local (remotely cached) objects. Remotely
+ # cached means that they're objects which originate here, and were
+ # copied remotely.
+ self.remotelyCachedObjects = {}
+ # Dictionary mapping PUIDs to (cached) LUIDs
+ self.remotelyCachedLUIDs = {}
+ # Dictionary mapping (remote) LUIDs to (locally cached) objects.
+ self.locallyCachedObjects = {}
+ self.waitingForAnswers = {}
+
+ # Mapping from LUIDs to weakref objects with callbacks for performing
+ # any local cleanup which may be necessary for the corresponding
+ # object once it no longer exists.
+ self._localCleanup = {}
+
+
+ def resumeProducing(self):
+ """Called when the consumer attached to me runs out of buffer.
+ """
+ # Go backwards over the list so we can remove indexes from it as we go
+ for pageridx in xrange(len(self.pageProducers)-1, -1, -1):
+ pager = self.pageProducers[pageridx]
+ pager.sendNextPage()
+ if not pager.stillPaging():
+ del self.pageProducers[pageridx]
+ if not self.pageProducers:
+ self.transport.unregisterProducer()
+
+ # Streaming producer methods; not necessary to implement.
+ def pauseProducing(self):
+ pass
+
+ def stopProducing(self):
+ pass
+
+ def registerPageProducer(self, pager):
+ self.pageProducers.append(pager)
+ if len(self.pageProducers) == 1:
+ self.transport.registerProducer(self, 0)
+
+ def expressionReceived(self, sexp):
+ """Evaluate an expression as it's received.
+ """
+ if isinstance(sexp, types.ListType):
+ command = sexp[0]
+ methodName = "proto_%s" % command
+ method = getattr(self, methodName, None)
+ if method:
+ method(*sexp[1:])
+ else:
+ self.sendCall("didNotUnderstand", command)
+ else:
+ raise ProtocolError("Non-list expression received.")
+
+
+ def proto_version(self, vnum):
+ """Protocol message: (version version-number)
+
+ Check to make sure that both ends of the protocol are speaking
+ the same version dialect.
+ """
+
+ if vnum != self.version:
+ raise ProtocolError("Version Incompatibility: %s %s" % (self.version, vnum))
+
+
+ def sendCall(self, *exp):
+ """Utility method to send an expression to the other side of the connection.
+ """
+ self.sendEncoded(exp)
+
+ def proto_didNotUnderstand(self, command):
+ """Respond to stock 'C{didNotUnderstand}' message.
+
+ Log the command that was not understood and continue. (Note:
+ this will probably be changed to close the connection or raise
+ an exception in the future.)
+ """
+ log.msg("Didn't understand command: %r" % command)
+
+ def connectionReady(self):
+ """Initialize. Called after Banana negotiation is done.
+ """
+ self.sendCall("version", self.version)
+ for notifier in self.connects:
+ try:
+ notifier()
+ except:
+ log.deferr()
+ self.connects = None
+ if self.factory: # in tests we won't have factory
+ self.factory.clientConnectionMade(self)
+
+ def connectionFailed(self):
+ # XXX should never get called anymore? check!
+ for notifier in self.failures:
+ try:
+ notifier()
+ except:
+ log.deferr()
+ self.failures = None
+
+ waitingForAnswers = None
+
+ def connectionLost(self, reason):
+ """The connection was lost.
+ """
+ self.disconnected = 1
+ # nuke potential circular references.
+ self.luids = None
+ if self.waitingForAnswers:
+ for d in self.waitingForAnswers.values():
+ try:
+ d.errback(failure.Failure(PBConnectionLost(reason)))
+ except:
+ log.deferr()
+ # Assure all Cacheable.stoppedObserving are called
+ for lobj in self.remotelyCachedObjects.values():
+ cacheable = lobj.object
+ perspective = lobj.perspective
+ try:
+ cacheable.stoppedObserving(perspective, RemoteCacheObserver(self, cacheable, perspective))
+ except:
+ log.deferr()
+ # Loop on a copy to prevent notifiers to mixup
+ # the list by calling dontNotifyOnDisconnect
+ for notifier in self.disconnects[:]:
+ try:
+ notifier()
+ except:
+ log.deferr()
+ self.disconnects = None
+ self.waitingForAnswers = None
+ self.localSecurity = None
+ self.remoteSecurity = None
+ self.remotelyCachedObjects = None
+ self.remotelyCachedLUIDs = None
+ self.locallyCachedObjects = None
+ self.localObjects = None
+
+ def notifyOnDisconnect(self, notifier):
+ """Call the given callback when the Broker disconnects."""
+ assert callable(notifier)
+ self.disconnects.append(notifier)
+
+ def notifyOnFail(self, notifier):
+ """Call the given callback if the Broker fails to connect."""
+ assert callable(notifier)
+ self.failures.append(notifier)
+
+ def notifyOnConnect(self, notifier):
+ """Call the given callback when the Broker connects."""
+ assert callable(notifier)
+ if self.connects is None:
+ try:
+ notifier()
+ except:
+ log.err()
+ else:
+ self.connects.append(notifier)
+
+ def dontNotifyOnDisconnect(self, notifier):
+ """Remove a callback from list of disconnect callbacks."""
+ try:
+ self.disconnects.remove(notifier)
+ except ValueError:
+ pass
+
+ def localObjectForID(self, luid):
+ """
+ Get a local object for a locally unique ID.
+
+ @return: An object previously stored with L{registerReference} or
+ C{None} if there is no object which corresponds to the given
+ identifier.
+ """
+ lob = self.localObjects.get(luid)
+ if lob is None:
+ return
+ return lob.object
+
+ maxBrokerRefsViolations = 0
+
+ def registerReference(self, object):
+ """Get an ID for a local object.
+
+ Store a persistent reference to a local object and map its id()
+ to a generated, session-unique ID and return that ID.
+ """
+
+ assert object is not None
+ puid = object.processUniqueID()
+ luid = self.luids.get(puid)
+ if luid is None:
+ if len(self.localObjects) > MAX_BROKER_REFS:
+ self.maxBrokerRefsViolations = self.maxBrokerRefsViolations + 1
+ if self.maxBrokerRefsViolations > 3:
+ self.transport.loseConnection()
+ raise Error("Maximum PB reference count exceeded. "
+ "Goodbye.")
+ raise Error("Maximum PB reference count exceeded.")
+
+ luid = self.newLocalID()
+ self.localObjects[luid] = Local(object)
+ self.luids[puid] = luid
+ else:
+ self.localObjects[luid].incref()
+ return luid
+
+ def setNameForLocal(self, name, object):
+ """Store a special (string) ID for this object.
+
+ This is how you specify a 'base' set of objects that the remote
+ protocol can connect to.
+ """
+ assert object is not None
+ self.localObjects[name] = Local(object)
+
+ def remoteForName(self, name):
+ """Returns an object from the remote name mapping.
+
+ Note that this does not check the validity of the name, only
+ creates a translucent reference for it.
+ """
+ return RemoteReference(None, self, name, 0)
+
+ def cachedRemotelyAs(self, instance, incref=0):
+ """Returns an ID that says what this instance is cached as remotely, or C{None} if it's not.
+ """
+
+ puid = instance.processUniqueID()
+ luid = self.remotelyCachedLUIDs.get(puid)
+ if (luid is not None) and (incref):
+ self.remotelyCachedObjects[luid].incref()
+ return luid
+
+ def remotelyCachedForLUID(self, luid):
+ """Returns an instance which is cached remotely, with this LUID.
+ """
+ return self.remotelyCachedObjects[luid].object
+
+ def cacheRemotely(self, instance):
+ """
+ XXX"""
+ puid = instance.processUniqueID()
+ luid = self.newLocalID()
+ if len(self.remotelyCachedObjects) > MAX_BROKER_REFS:
+ self.maxBrokerRefsViolations = self.maxBrokerRefsViolations + 1
+ if self.maxBrokerRefsViolations > 3:
+ self.transport.loseConnection()
+ raise Error("Maximum PB cache count exceeded. "
+ "Goodbye.")
+ raise Error("Maximum PB cache count exceeded.")
+
+ self.remotelyCachedLUIDs[puid] = luid
+ # This table may not be necessary -- for now, it's to make sure that no
+ # monkey business happens with id(instance)
+ self.remotelyCachedObjects[luid] = Local(instance, self.serializingPerspective)
+ return luid
+
+ def cacheLocally(self, cid, instance):
+ """(internal)
+
+ Store a non-filled-out cached instance locally.
+ """
+ self.locallyCachedObjects[cid] = instance
+
+ def cachedLocallyAs(self, cid):
+ instance = self.locallyCachedObjects[cid]
+ return instance
+
+ def serialize(self, object, perspective=None, method=None, args=None, kw=None):
+ """Jelly an object according to the remote security rules for this broker.
+ """
+
+ if isinstance(object, defer.Deferred):
+ object.addCallbacks(self.serialize, lambda x: x,
+ callbackKeywords={
+ 'perspective': perspective,
+ 'method': method,
+ 'args': args,
+ 'kw': kw
+ })
+ return object
+
+ # XXX This call is NOT REENTRANT and testing for reentrancy is just
+ # crazy, so it likely won't be. Don't ever write methods that call the
+ # broker's serialize() method recursively (e.g. sending a method call
+ # from within a getState (this causes concurrency problems anyway so
+ # you really, really shouldn't do it))
+
+ # self.jellier = _NetJellier(self)
+ self.serializingPerspective = perspective
+ self.jellyMethod = method
+ self.jellyArgs = args
+ self.jellyKw = kw
+ try:
+ return jelly(object, self.security, None, self)
+ finally:
+ self.serializingPerspective = None
+ self.jellyMethod = None
+ self.jellyArgs = None
+ self.jellyKw = None
+
+ def unserialize(self, sexp, perspective = None):
+ """Unjelly an sexp according to the local security rules for this broker.
+ """
+
+ self.unserializingPerspective = perspective
+ try:
+ return unjelly(sexp, self.security, None, self)
+ finally:
+ self.unserializingPerspective = None
+
+ def newLocalID(self):
+ """Generate a new LUID.
+ """
+ self.currentLocalID = self.currentLocalID + 1
+ return self.currentLocalID
+
+ def newRequestID(self):
+ """Generate a new request ID.
+ """
+ self.currentRequestID = self.currentRequestID + 1
+ return self.currentRequestID
+
+ def _sendMessage(self, prefix, perspective, objectID, message, args, kw):
+ pbc = None
+ pbe = None
+ answerRequired = 1
+ if kw.has_key('pbcallback'):
+ pbc = kw['pbcallback']
+ del kw['pbcallback']
+ if kw.has_key('pberrback'):
+ pbe = kw['pberrback']
+ del kw['pberrback']
+ if kw.has_key('pbanswer'):
+ assert (not pbe) and (not pbc), "You can't specify a no-answer requirement."
+ answerRequired = kw['pbanswer']
+ del kw['pbanswer']
+ if self.disconnected:
+ raise DeadReferenceError("Calling Stale Broker")
+ try:
+ netArgs = self.serialize(args, perspective=perspective, method=message)
+ netKw = self.serialize(kw, perspective=perspective, method=message)
+ except:
+ return defer.fail(failure.Failure())
+ requestID = self.newRequestID()
+ if answerRequired:
+ rval = defer.Deferred()
+ self.waitingForAnswers[requestID] = rval
+ if pbc or pbe:
+ log.msg('warning! using deprecated "pbcallback"')
+ rval.addCallbacks(pbc, pbe)
+ else:
+ rval = None
+ self.sendCall(prefix+"message", requestID, objectID, message, answerRequired, netArgs, netKw)
+ return rval
+
+ def proto_message(self, requestID, objectID, message, answerRequired, netArgs, netKw):
+ self._recvMessage(self.localObjectForID, requestID, objectID, message, answerRequired, netArgs, netKw)
+ def proto_cachemessage(self, requestID, objectID, message, answerRequired, netArgs, netKw):
+ self._recvMessage(self.cachedLocallyAs, requestID, objectID, message, answerRequired, netArgs, netKw)
+
+ def _recvMessage(self, findObjMethod, requestID, objectID, message, answerRequired, netArgs, netKw):
+ """Received a message-send.
+
+ Look up message based on object, unserialize the arguments, and
+ invoke it with args, and send an 'answer' or 'error' response.
+ """
+ try:
+ object = findObjMethod(objectID)
+ if object is None:
+ raise Error("Invalid Object ID")
+ netResult = object.remoteMessageReceived(self, message, netArgs, netKw)
+ except Error, e:
+ if answerRequired:
+ # If the error is Jellyable or explicitly allowed via our
+ # security options, send it back and let the code on the
+ # other end deal with unjellying. If it isn't Jellyable,
+ # wrap it in a CopyableFailure, which ensures it can be
+ # unjellied on the other end. We have to do this because
+ # all errors must be sent back.
+ if isinstance(e, Jellyable) or self.security.isClassAllowed(e.__class__):
+ self._sendError(e, requestID)
+ else:
+ self._sendError(CopyableFailure(e), requestID)
+ except:
+ if answerRequired:
+ log.msg("Peer will receive following PB traceback:", isError=True)
+ f = CopyableFailure()
+ self._sendError(f, requestID)
+ log.err()
+ else:
+ if answerRequired:
+ if isinstance(netResult, defer.Deferred):
+ args = (requestID,)
+ netResult.addCallbacks(self._sendAnswer, self._sendFailureOrError,
+ callbackArgs=args, errbackArgs=args)
+ # XXX Should this be done somewhere else?
+ else:
+ self._sendAnswer(netResult, requestID)
+ ##
+ # success
+ ##
+
+ def _sendAnswer(self, netResult, requestID):
+ """(internal) Send an answer to a previously sent message.
+ """
+ self.sendCall("answer", requestID, netResult)
+
+ def proto_answer(self, requestID, netResult):
+ """(internal) Got an answer to a previously sent message.
+
+ Look up the appropriate callback and call it.
+ """
+ d = self.waitingForAnswers[requestID]
+ del self.waitingForAnswers[requestID]
+ d.callback(self.unserialize(netResult))
+
+ ##
+ # failure
+ ##
+ def _sendFailureOrError(self, fail, requestID):
+ """
+ Call L{_sendError} or L{_sendFailure}, depending on whether C{fail}
+ represents an L{Error} subclass or not.
+ """
+ if fail.check(Error) is None:
+ self._sendFailure(fail, requestID)
+ else:
+ self._sendError(fail, requestID)
+
+
+ def _sendFailure(self, fail, requestID):
+ """Log error and then send it."""
+ log.msg("Peer will receive following PB traceback:")
+ log.err(fail)
+ self._sendError(fail, requestID)
+
+ def _sendError(self, fail, requestID):
+ """(internal) Send an error for a previously sent message.
+ """
+ if isinstance(fail, failure.Failure):
+ # If the failures value is jellyable or allowed through security,
+ # send the value
+ if (isinstance(fail.value, Jellyable) or
+ self.security.isClassAllowed(fail.value.__class__)):
+ fail = fail.value
+ elif not isinstance(fail, CopyableFailure):
+ fail = failure2Copyable(fail, self.factory.unsafeTracebacks)
+ if isinstance(fail, CopyableFailure):
+ fail.unsafeTracebacks = self.factory.unsafeTracebacks
+ self.sendCall("error", requestID, self.serialize(fail))
+
+ def proto_error(self, requestID, fail):
+ """(internal) Deal with an error.
+ """
+ d = self.waitingForAnswers[requestID]
+ del self.waitingForAnswers[requestID]
+ d.errback(self.unserialize(fail))
+
+ ##
+ # refcounts
+ ##
+
+ def sendDecRef(self, objectID):
+ """(internal) Send a DECREF directive.
+ """
+ self.sendCall("decref", objectID)
+
+ def proto_decref(self, objectID):
+ """(internal) Decrement the reference count of an object.
+
+ If the reference count is zero, it will free the reference to this
+ object.
+ """
+ refs = self.localObjects[objectID].decref()
+ if refs == 0:
+ puid = self.localObjects[objectID].object.processUniqueID()
+ del self.luids[puid]
+ del self.localObjects[objectID]
+ self._localCleanup.pop(puid, lambda: None)()
+
+ ##
+ # caching
+ ##
+
+ def decCacheRef(self, objectID):
+ """(internal) Send a DECACHE directive.
+ """
+ self.sendCall("decache", objectID)
+
+ def proto_decache(self, objectID):
+ """(internal) Decrement the reference count of a cached object.
+
+ If the reference count is zero, free the reference, then send an
+ 'uncached' directive.
+ """
+ refs = self.remotelyCachedObjects[objectID].decref()
+ # log.msg('decaching: %s #refs: %s' % (objectID, refs))
+ if refs == 0:
+ lobj = self.remotelyCachedObjects[objectID]
+ cacheable = lobj.object
+ perspective = lobj.perspective
+ # TODO: force_decache needs to be able to force-invalidate a
+ # cacheable reference.
+ try:
+ cacheable.stoppedObserving(perspective, RemoteCacheObserver(self, cacheable, perspective))
+ except:
+ log.deferr()
+ puid = cacheable.processUniqueID()
+ del self.remotelyCachedLUIDs[puid]
+ del self.remotelyCachedObjects[objectID]
+ self.sendCall("uncache", objectID)
+
+ def proto_uncache(self, objectID):
+ """(internal) Tell the client it is now OK to uncache an object.
+ """
+ # log.msg("uncaching locally %d" % objectID)
+ obj = self.locallyCachedObjects[objectID]
+ obj.broker = None
+## def reallyDel(obj=obj):
+## obj.__really_del__()
+## obj.__del__ = reallyDel
+ del self.locallyCachedObjects[objectID]
+
+
+
+def respond(challenge, password):
+ """Respond to a challenge.
+
+ This is useful for challenge/response authentication.
+ """
+ m = md5()
+ m.update(password)
+ hashedPassword = m.digest()
+ m = md5()
+ m.update(hashedPassword)
+ m.update(challenge)
+ doubleHashedPassword = m.digest()
+ return doubleHashedPassword
+
+def challenge():
+ """I return some random data."""
+ crap = ''
+ for x in range(random.randrange(15,25)):
+ crap = crap + chr(random.randint(65,90))
+ crap = md5(crap).digest()
+ return crap
+
+
+class PBClientFactory(protocol.ClientFactory):
+ """
+ Client factory for PB brokers.
+
+ As with all client factories, use with reactor.connectTCP/SSL/etc..
+ getPerspective and getRootObject can be called either before or
+ after the connect.
+ """
+
+ protocol = Broker
+ unsafeTracebacks = False
+
+ def __init__(self, unsafeTracebacks=False, security=globalSecurity):
+ """
+ @param unsafeTracebacks: if set, tracebacks for exceptions will be sent
+ over the wire.
+ @type unsafeTracebacks: C{bool}
+
+ @param security: security options used by the broker, default to
+ C{globalSecurity}.
+ @type security: L{twisted.spread.jelly.SecurityOptions}
+ """
+ self.unsafeTracebacks = unsafeTracebacks
+ self.security = security
+ self._reset()
+
+
+ def buildProtocol(self, addr):
+ """
+ Build the broker instance, passing the security options to it.
+ """
+ p = self.protocol(isClient=True, security=self.security)
+ p.factory = self
+ return p
+
+
+ def _reset(self):
+ self.rootObjectRequests = [] # list of deferred
+ self._broker = None
+ self._root = None
+
+ def _failAll(self, reason):
+ deferreds = self.rootObjectRequests
+ self._reset()
+ for d in deferreds:
+ d.errback(reason)
+
+ def clientConnectionFailed(self, connector, reason):
+ self._failAll(reason)
+
+ def clientConnectionLost(self, connector, reason, reconnecting=0):
+ """Reconnecting subclasses should call with reconnecting=1."""
+ if reconnecting:
+ # any pending requests will go to next connection attempt
+ # so we don't fail them.
+ self._broker = None
+ self._root = None
+ else:
+ self._failAll(reason)
+
+ def clientConnectionMade(self, broker):
+ self._broker = broker
+ self._root = broker.remoteForName("root")
+ ds = self.rootObjectRequests
+ self.rootObjectRequests = []
+ for d in ds:
+ d.callback(self._root)
+
+ def getRootObject(self):
+ """Get root object of remote PB server.
+
+ @return: Deferred of the root object.
+ """
+ if self._broker and not self._broker.disconnected:
+ return defer.succeed(self._root)
+ d = defer.Deferred()
+ self.rootObjectRequests.append(d)
+ return d
+
+ def disconnect(self):
+ """If the factory is connected, close the connection.
+
+ Note that if you set up the factory to reconnect, you will need to
+ implement extra logic to prevent automatic reconnection after this
+ is called.
+ """
+ if self._broker:
+ self._broker.transport.loseConnection()
+
+ def _cbSendUsername(self, root, username, password, client):
+ return root.callRemote("login", username).addCallback(
+ self._cbResponse, password, client)
+
+ def _cbResponse(self, (challenge, challenger), password, client):
+ return challenger.callRemote("respond", respond(challenge, password), client)
+
+
+ def _cbLoginAnonymous(self, root, client):
+ """
+ Attempt an anonymous login on the given remote root object.
+
+ @type root: L{RemoteReference}
+ @param root: The object on which to attempt the login, most likely
+ returned by a call to L{PBClientFactory.getRootObject}.
+
+ @param client: A jellyable object which will be used as the I{mind}
+ parameter for the login attempt.
+
+ @rtype: L{Deferred}
+ @return: A L{Deferred} which will be called back with a
+ L{RemoteReference} to an avatar when anonymous login succeeds, or
+ which will errback if anonymous login fails.
+ """
+ return root.callRemote("loginAnonymous", client)
+
+
+ def login(self, credentials, client=None):
+ """
+ Login and get perspective from remote PB server.
+
+ Currently the following credentials are supported::
+
+ L{twisted.cred.credentials.IUsernamePassword}
+ L{twisted.cred.credentials.IAnonymous}
+
+ @rtype: L{Deferred}
+ @return: A L{Deferred} which will be called back with a
+ L{RemoteReference} for the avatar logged in to, or which will
+ errback if login fails.
+ """
+ d = self.getRootObject()
+
+ if IAnonymous.providedBy(credentials):
+ d.addCallback(self._cbLoginAnonymous, client)
+ else:
+ d.addCallback(
+ self._cbSendUsername, credentials.username,
+ credentials.password, client)
+ return d
+
+
+
+class PBServerFactory(protocol.ServerFactory):
+ """
+ Server factory for perspective broker.
+
+ Login is done using a Portal object, whose realm is expected to return
+ avatars implementing IPerspective. The credential checkers in the portal
+ should accept IUsernameHashedPassword or IUsernameMD5Password.
+
+ Alternatively, any object providing or adaptable to L{IPBRoot} can be
+ used instead of a portal to provide the root object of the PB server.
+ """
+
+ unsafeTracebacks = False
+
+ # object broker factory
+ protocol = Broker
+
+ def __init__(self, root, unsafeTracebacks=False, security=globalSecurity):
+ """
+ @param root: factory providing the root Referenceable used by the broker.
+ @type root: object providing or adaptable to L{IPBRoot}.
+
+ @param unsafeTracebacks: if set, tracebacks for exceptions will be sent
+ over the wire.
+ @type unsafeTracebacks: C{bool}
+
+ @param security: security options used by the broker, default to
+ C{globalSecurity}.
+ @type security: L{twisted.spread.jelly.SecurityOptions}
+ """
+ self.root = IPBRoot(root)
+ self.unsafeTracebacks = unsafeTracebacks
+ self.security = security
+
+
+ def buildProtocol(self, addr):
+ """
+ Return a Broker attached to the factory (as the service provider).
+ """
+ proto = self.protocol(isClient=False, security=self.security)
+ proto.factory = self
+ proto.setNameForLocal("root", self.root.rootObject(proto))
+ return proto
+
+ def clientConnectionMade(self, protocol):
+ # XXX does this method make any sense?
+ pass
+
+
+class IUsernameMD5Password(ICredentials):
+ """
+ I encapsulate a username and a hashed password.
+
+ This credential is used for username/password over PB. CredentialCheckers
+ which check this kind of credential must store the passwords in plaintext
+ form or as a MD5 digest.
+
+ @type username: C{str} or C{Deferred}
+ @ivar username: The username associated with these credentials.
+ """
+
+ def checkPassword(password):
+ """
+ Validate these credentials against the correct password.
+
+ @type password: C{str}
+ @param password: The correct, plaintext password against which to
+ check.
+
+ @rtype: C{bool} or L{Deferred}
+ @return: C{True} if the credentials represented by this object match the
+ given password, C{False} if they do not, or a L{Deferred} which will
+ be called back with one of these values.
+ """
+
+ def checkMD5Password(password):
+ """
+ Validate these credentials against the correct MD5 digest of the
+ password.
+
+ @type password: C{str}
+ @param password: The correct MD5 digest of a password against which to
+ check.
+
+ @rtype: C{bool} or L{Deferred}
+ @return: C{True} if the credentials represented by this object match the
+ given digest, C{False} if they do not, or a L{Deferred} which will
+ be called back with one of these values.
+ """
+
+
+class _PortalRoot:
+ """Root object, used to login to portal."""
+
+ implements(IPBRoot)
+
+ def __init__(self, portal):
+ self.portal = portal
+
+ def rootObject(self, broker):
+ return _PortalWrapper(self.portal, broker)
+
+registerAdapter(_PortalRoot, Portal, IPBRoot)
+
+
+
+class _JellyableAvatarMixin:
+ """
+ Helper class for code which deals with avatars which PB must be capable of
+ sending to a peer.
+ """
+ def _cbLogin(self, (interface, avatar, logout)):
+ """
+ Ensure that the avatar to be returned to the client is jellyable and
+ set up disconnection notification to call the realm's logout object.
+ """
+ if not IJellyable.providedBy(avatar):
+ avatar = AsReferenceable(avatar, "perspective")
+
+ puid = avatar.processUniqueID()
+
+ def dereferenceLogout():
+ self.broker.dontNotifyOnDisconnect(logout)
+ logout()
+
+ self.broker._localCleanup[puid] = dereferenceLogout
+ # No special helper function is necessary for notifyOnDisconnect
+ # because dereference callbacks won't be invoked if the connection is
+ # randomly dropped. I'm not sure those are ideal semantics, but this
+ # is the only user of the (private) API at the moment and it works just
+ # fine as things are. -exarkun
+ self.broker.notifyOnDisconnect(logout)
+ return avatar
+
+
+
+class _PortalWrapper(Referenceable, _JellyableAvatarMixin):
+ """
+ Root Referenceable object, used to login to portal.
+ """
+
+ def __init__(self, portal, broker):
+ self.portal = portal
+ self.broker = broker
+
+
+ def remote_login(self, username):
+ """
+ Start of username/password login.
+ """
+ c = challenge()
+ return c, _PortalAuthChallenger(self.portal, self.broker, username, c)
+
+
+ def remote_loginAnonymous(self, mind):
+ """
+ Attempt an anonymous login.
+
+ @param mind: An object to use as the mind parameter to the portal login
+ call (possibly None).
+
+ @rtype: L{Deferred}
+ @return: A Deferred which will be called back with an avatar when login
+ succeeds or which will be errbacked if login fails somehow.
+ """
+ d = self.portal.login(Anonymous(), mind, IPerspective)
+ d.addCallback(self._cbLogin)
+ return d
+
+
+
+class _PortalAuthChallenger(Referenceable, _JellyableAvatarMixin):
+ """
+ Called with response to password challenge.
+ """
+ implements(IUsernameHashedPassword, IUsernameMD5Password)
+
+ def __init__(self, portal, broker, username, challenge):
+ self.portal = portal
+ self.broker = broker
+ self.username = username
+ self.challenge = challenge
+
+
+ def remote_respond(self, response, mind):
+ self.response = response
+ d = self.portal.login(self, mind, IPerspective)
+ d.addCallback(self._cbLogin)
+ return d
+
+
+ # IUsernameHashedPassword:
+ def checkPassword(self, password):
+ return self.checkMD5Password(md5(password).digest())
+
+
+ # IUsernameMD5Password
+ def checkMD5Password(self, md5Password):
+ md = md5()
+ md.update(md5Password)
+ md.update(self.challenge)
+ correct = md.digest()
+ return self.response == correct
+
+
+__all__ = [
+ # Everything from flavors is exposed publically here.
+ 'IPBRoot', 'Serializable', 'Referenceable', 'NoSuchMethod', 'Root',
+ 'ViewPoint', 'Viewable', 'Copyable', 'Jellyable', 'Cacheable',
+ 'RemoteCopy', 'RemoteCache', 'RemoteCacheObserver', 'copyTags',
+ 'setUnjellyableForClass', 'setUnjellyableFactoryForClass',
+ 'setUnjellyableForClassTree',
+
+ 'MAX_BROKER_REFS', 'portno',
+
+ 'ProtocolError', 'DeadReferenceError', 'Error', 'PBConnectionLost',
+ 'RemoteMethod', 'IPerspective', 'Avatar', 'AsReferenceable',
+ 'RemoteReference', 'CopyableFailure', 'CopiedFailure', 'failure2Copyable',
+ 'Broker', 'respond', 'challenge', 'PBClientFactory', 'PBServerFactory',
+ 'IUsernameMD5Password',
+ ]
diff --git a/vendor/Twisted-10.0.0/twisted/spread/publish.py b/vendor/Twisted-10.0.0/twisted/spread/publish.py
new file mode 100644
index 0000000000..08908fdd81
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/publish.py
@@ -0,0 +1,142 @@
+# -*- test-case-name: twisted.test.test_pb -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Persistently cached objects for PB.
+
+Maintainer: Glyph Lefkowitz
+
+Future Plans: None known.
+"""
+
+import time
+
+from twisted.internet import defer
+from twisted.spread import banana, jelly, flavors
+
+
+class Publishable(flavors.Cacheable):
+ """An object whose cached state persists across sessions.
+ """
+ def __init__(self, publishedID):
+ self.republish()
+ self.publishedID = publishedID
+
+ def republish(self):
+ """Set the timestamp to current and (TODO) update all observers.
+ """
+ self.timestamp = time.time()
+
+ def view_getStateToPublish(self, perspective):
+ '(internal)'
+ return self.getStateToPublishFor(perspective)
+
+ def getStateToPublishFor(self, perspective):
+ """Implement me to special-case your state for a perspective.
+ """
+ return self.getStateToPublish()
+
+ def getStateToPublish(self):
+ """Implement me to return state to copy as part of the publish phase.
+ """
+ raise NotImplementedError("%s.getStateToPublishFor" % self.__class__)
+
+ def getStateToCacheAndObserveFor(self, perspective, observer):
+ """Get all necessary metadata to keep a clientside cache.
+ """
+ if perspective:
+ pname = perspective.perspectiveName
+ sname = perspective.getService().serviceName
+ else:
+ pname = "None"
+ sname = "None"
+
+ return {"remote": flavors.ViewPoint(perspective, self),
+ "publishedID": self.publishedID,
+ "perspective": pname,
+ "service": sname,
+ "timestamp": self.timestamp}
+
+class RemotePublished(flavors.RemoteCache):
+ """The local representation of remote Publishable object.
+ """
+ isActivated = 0
+ _wasCleanWhenLoaded = 0
+ def getFileName(self, ext='pub'):
+ return ("%s-%s-%s.%s" %
+ (self.service, self.perspective, str(self.publishedID), ext))
+
+ def setCopyableState(self, state):
+ self.__dict__.update(state)
+ self._activationListeners = []
+ try:
+ dataFile = file(self.getFileName(), "rb")
+ data = dataFile.read()
+ dataFile.close()
+ except IOError:
+ recent = 0
+ else:
+ newself = jelly.unjelly(banana.decode(data))
+ recent = (newself.timestamp == self.timestamp)
+ if recent:
+ self._cbGotUpdate(newself.__dict__)
+ self._wasCleanWhenLoaded = 1
+ else:
+ self.remote.callRemote('getStateToPublish').addCallbacks(self._cbGotUpdate)
+
+ def __getstate__(self):
+ other = self.__dict__.copy()
+ # Remove PB-specific attributes
+ del other['broker']
+ del other['remote']
+ del other['luid']
+ # remove my own runtime-tracking stuff
+ del other['_activationListeners']
+ del other['isActivated']
+ return other
+
+ def _cbGotUpdate(self, newState):
+ self.__dict__.update(newState)
+ self.isActivated = 1
+ # send out notifications
+ for listener in self._activationListeners:
+ listener(self)
+ self._activationListeners = []
+ self.activated()
+ dataFile = file(self.getFileName(), "wb")
+ dataFile.write(banana.encode(jelly.jelly(self)))
+ dataFile.close()
+
+
+ def activated(self):
+ """Implement this method if you want to be notified when your
+ publishable subclass is activated.
+ """
+
+ def callWhenActivated(self, callback):
+ """Externally register for notification when this publishable has received all relevant data.
+ """
+ if self.isActivated:
+ callback(self)
+ else:
+ self._activationListeners.append(callback)
+
+def whenReady(d):
+ """
+ Wrap a deferred returned from a pb method in another deferred that
+ expects a RemotePublished as a result. This will allow you to wait until
+ the result is really available.
+
+ Idiomatic usage would look like::
+
+ publish.whenReady(serverObject.getMeAPublishable()).addCallback(lookAtThePublishable)
+ """
+ d2 = defer.Deferred()
+ d.addCallbacks(_pubReady, d2.errback,
+ callbackArgs=(d2,))
+ return d2
+
+def _pubReady(result, d2):
+ '(internal)'
+ result.callWhenActivated(d2.callback)
diff --git a/vendor/Twisted-10.0.0/twisted/spread/refpath.py b/vendor/Twisted-10.0.0/twisted/spread/refpath.py
new file mode 100644
index 0000000000..a72638e139
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/refpath.py
@@ -0,0 +1,95 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Path-based references for PB, and other reference-based protocols.
+
+Maintainer: Glyph Lefkowitz
+"""
+
+
+from copy import copy
+import os, warnings
+
+from twisted.python import log
+from twisted.spread.flavors import Referenceable, Viewable
+
+warnings.warn(
+ "twisted.spread.refpath is deprecated since Twisted 9.0.",
+ category=DeprecationWarning, stacklevel=2)
+
+### "Server"-side objects
+
+class PathReferenceContext:
+ def __init__(self, path, root):
+ self.metadata = {}
+ self.path = path
+ self.root = root
+
+ def __setitem__(self, key, item):
+ self.metadata[key] = item
+
+ def __getitem__(self, key):
+ return self.metadata[key]
+
+ def getObject(self):
+ o = self.root
+ for p in self.path:
+ o = o.getChild(p, self)
+ return o
+
+class PathReference:
+ def __init__(self):
+ self.children = {}
+ def getChild(self, child, ctx):
+ return self.children[child]
+
+class PathReferenceDirectory(Referenceable):
+ def __init__(self, root, prefix="remote"):
+ self.root = root
+ self.prefix = prefix
+ def remote_callPath(self, path, name, *args, **kw):
+ ctx = PathReferenceContext(path, self)
+ obj = ctx.getObject()
+ return apply(getattr(obj, "%s_%s" % (self.prefix, name)), args, kw)
+
+class PathReferenceContextDirectory(Referenceable):
+ def __init__(self, root, prefix="remote"):
+ self.root = root
+ self.prefix = prefix
+ def remote_callPath(self, path, name, *args, **kw):
+ ctx = PathReferenceContext(path, self)
+ obj = ctx.getObject()
+ return apply(getattr(obj, "%s_%s" % (self.prefix, name)),
+ (ctx,)+args, kw)
+
+class PathViewDirectory(Viewable):
+ def __init__(self, root, prefix="view"):
+ self.root = root
+ self.prefix = prefix
+ def view_callPath(self, perspective, path, name, *args, **kw):
+ ctx = PathReferenceContext(path, self)
+ obj = ctx.getObject()
+ return apply(getattr(obj, "%s_%s" % (self.prefix, name)),
+ (perspective,)+args, kw)
+
+class PathViewContextDirectory(Viewable):
+ def __init__(self, root, prefix="view"):
+ self.root = root
+ self.prefix = prefix
+ def view_callPath(self, perspective, path, name, *args, **kw):
+ ctx = PathReferenceContext(path, self)
+ obj = ctx.getObject()
+ return apply(getattr(obj, "%s_%s" % (self.prefix, name)),
+ (perspective,ctx)+args, kw)
+
+### "Client"-side objects
+
+class RemotePathReference:
+ def __init__(self, ref, path):
+ self.ref = ref
+ self.path = path
+
+ def callRemote(self, name, *args, **kw):
+ apply(self.ref.callRemote,
+ ("callPath", self.path, name)+args, kw)
diff --git a/vendor/Twisted-10.0.0/twisted/spread/ui/__init__.py b/vendor/Twisted-10.0.0/twisted/spread/ui/__init__.py
new file mode 100644
index 0000000000..4dfc58952d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/ui/__init__.py
@@ -0,0 +1,12 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Twisted Spread UI: UI utilities for various toolkits connecting to PB.
+"""
+
+# Undeprecating this until someone figures out a real plan for alternatives to spread.ui.
+##import warnings
+##warnings.warn("twisted.spread.ui is deprecated. Please do not use.", DeprecationWarning)
diff --git a/vendor/Twisted-10.0.0/twisted/spread/ui/gtk2util.py b/vendor/Twisted-10.0.0/twisted/spread/ui/gtk2util.py
new file mode 100644
index 0000000000..07e7ef6616
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/ui/gtk2util.py
@@ -0,0 +1,215 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from __future__ import nested_scopes
+
+import gtk
+
+from twisted import copyright
+from twisted.internet import defer
+from twisted.python import failure, log, util
+from twisted.spread import pb
+from twisted.cred.credentials import UsernamePassword
+
+from twisted.internet import error as netError
+
+def login(client=None, **defaults):
+ """
+ @param host:
+ @param port:
+ @param identityName:
+ @param password:
+ @param serviceName:
+ @param perspectiveName:
+
+ @returntype: Deferred RemoteReference of Perspective
+ """
+ d = defer.Deferred()
+ LoginDialog(client, d, defaults)
+ return d
+
+class GladeKeeper:
+ """
+ @cvar gladefile: The file in which the glade GUI definition is kept.
+ @type gladefile: str
+
+ @cvar _widgets: Widgets that should be attached to me as attributes.
+ @type _widgets: list of strings
+ """
+
+ gladefile = None
+ _widgets = ()
+
+ def __init__(self):
+ from gtk import glade
+ self.glade = glade.XML(self.gladefile)
+
+ # mold can go away when we get a newer pygtk (post 1.99.14)
+ mold = {}
+ for k in dir(self):
+ mold[k] = getattr(self, k)
+ self.glade.signal_autoconnect(mold)
+ self._setWidgets()
+
+ def _setWidgets(self):
+ get_widget = self.glade.get_widget
+ for widgetName in self._widgets:
+ setattr(self, "_" + widgetName, get_widget(widgetName))
+
+
+class LoginDialog(GladeKeeper):
+ # IdentityConnector host port identityName password
+ # requestLogin -> identityWrapper or login failure
+ # requestService serviceName perspectiveName client
+
+ # window killed
+ # cancel button pressed
+ # login button activated
+
+ fields = ['host','port','identityName','password',
+ 'perspectiveName']
+
+ _widgets = ("hostEntry", "portEntry", "identityNameEntry", "passwordEntry",
+ "perspectiveNameEntry", "statusBar",
+ "loginDialog")
+
+ _advancedControls = ['perspectiveLabel', 'perspectiveNameEntry',
+ 'protocolLabel', 'versionLabel']
+
+ gladefile = util.sibpath(__file__, "login2.glade")
+
+ def __init__(self, client, deferred, defaults):
+ self.client = client
+ self.deferredResult = deferred
+
+ GladeKeeper.__init__(self)
+
+ self.setDefaults(defaults)
+ self._loginDialog.show()
+
+
+ def setDefaults(self, defaults):
+ if not defaults.has_key('port'):
+ defaults['port'] = str(pb.portno)
+ elif isinstance(defaults['port'], (int, long)):
+ defaults['port'] = str(defaults['port'])
+
+ for k, v in defaults.iteritems():
+ if k in self.fields:
+ widget = getattr(self, "_%sEntry" % (k,))
+ widget.set_text(v)
+
+ def _setWidgets(self):
+ GladeKeeper._setWidgets(self)
+ self._statusContext = self._statusBar.get_context_id("Login dialog.")
+ get_widget = self.glade.get_widget
+ get_widget("versionLabel").set_text(copyright.longversion)
+ get_widget("protocolLabel").set_text("Protocol PB-%s" %
+ (pb.Broker.version,))
+
+ def _on_loginDialog_response(self, widget, response):
+ handlers = {gtk.RESPONSE_NONE: self._windowClosed,
+ gtk.RESPONSE_DELETE_EVENT: self._windowClosed,
+ gtk.RESPONSE_OK: self._doLogin,
+ gtk.RESPONSE_CANCEL: self._cancelled}
+ handler = handlers.get(response)
+ if handler is not None:
+ handler()
+ else:
+ log.msg("Unexpected dialog response %r from %s" % (response,
+ widget))
+
+ def _on_loginDialog_close(self, widget, userdata=None):
+ self._windowClosed()
+
+ def _on_loginDialog_destroy_event(self, widget, userdata=None):
+ self._windowClosed()
+
+ def _cancelled(self):
+ if not self.deferredResult.called:
+ self.deferredResult.errback(netError.UserError("User hit Cancel."))
+ self._loginDialog.destroy()
+
+ def _windowClosed(self, reason=None):
+ if not self.deferredResult.called:
+ self.deferredResult.errback(netError.UserError("Window closed."))
+
+ def _doLogin(self):
+ idParams = {}
+
+ idParams['host'] = self._hostEntry.get_text()
+ idParams['port'] = self._portEntry.get_text()
+ idParams['identityName'] = self._identityNameEntry.get_text()
+ idParams['password'] = self._passwordEntry.get_text()
+
+ try:
+ idParams['port'] = int(idParams['port'])
+ except ValueError:
+ pass
+
+ f = pb.PBClientFactory()
+ from twisted.internet import reactor
+ reactor.connectTCP(idParams['host'], idParams['port'], f)
+ creds = UsernamePassword(idParams['identityName'], idParams['password'])
+ f.login(creds, self.client
+ ).addCallbacks(self._cbGotPerspective, self._ebFailedLogin
+ ).setTimeout(30
+ )
+ self.statusMsg("Contacting server...")
+
+ # serviceName = self._serviceNameEntry.get_text()
+ # perspectiveName = self._perspectiveNameEntry.get_text()
+ # if not perspectiveName:
+ # perspectiveName = idParams['identityName']
+
+ # d = _identityConnector.requestService(serviceName, perspectiveName,
+ # self.client)
+ # d.addCallbacks(self._cbGotPerspective, self._ebFailedLogin)
+ # setCursor to waiting
+
+ def _cbGotPerspective(self, perspective):
+ self.statusMsg("Connected to server.")
+ self.deferredResult.callback(perspective)
+ # clear waiting cursor
+ self._loginDialog.destroy()
+
+ def _ebFailedLogin(self, reason):
+ if isinstance(reason, failure.Failure):
+ reason = reason.value
+ self.statusMsg(reason)
+ if isinstance(reason, (unicode, str)):
+ text = reason
+ else:
+ text = unicode(reason)
+ msg = gtk.MessageDialog(self._loginDialog,
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_ERROR,
+ gtk.BUTTONS_CLOSE,
+ text)
+ msg.show_all()
+ msg.connect("response", lambda *a: msg.destroy())
+
+ # hostname not found
+ # host unreachable
+ # connection refused
+ # authentication failed
+ # no such service
+ # no such perspective
+ # internal server error
+
+ def _on_advancedButton_toggled(self, widget, userdata=None):
+ active = widget.get_active()
+ if active:
+ op = "show"
+ else:
+ op = "hide"
+ for widgetName in self._advancedControls:
+ widget = self.glade.get_widget(widgetName)
+ getattr(widget, op)()
+
+ def statusMsg(self, text):
+ if not isinstance(text, (unicode, str)):
+ text = unicode(text)
+ return self._statusBar.push(self._statusContext, text)
diff --git a/vendor/Twisted-10.0.0/twisted/spread/ui/login2.glade b/vendor/Twisted-10.0.0/twisted/spread/ui/login2.glade
new file mode 100644
index 0000000000..af8c53da88
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/ui/login2.glade
@@ -0,0 +1,461 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="loginDialog">
+ <property name="title" translatable="yes">Login</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="has_separator">True</property>
+ <signal name="response" handler="_on_loginDialog_response" last_modification_time="Sat, 25 Jan 2003 13:52:57 GMT"/>
+ <signal name="close" handler="_on_loginDialog_close" last_modification_time="Sat, 25 Jan 2003 13:53:04 GMT"/>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="cancelbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="loginButton">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="response_id">-5</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-ok</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Login</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkStatusbar" id="statusBar">
+ <property name="visible">True</property>
+ <property name="has_resize_grip">False</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">6</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">2</property>
+ <property name="column_spacing">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="hostLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Host:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.9</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">hostEntry</property>
+ <accessibility>
+ <atkrelation target="hostEntry" type="label-for"/>
+ <atkrelation target="portEntry" type="label-for"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkEntry" id="hostEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">The name of a host to connect to.</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">localhost</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ <accessibility>
+ <atkrelation target="hostLabel" type="labelled-by"/>
+ </accessibility>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="portEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">The number of a port to connect on.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">8787</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">5</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="nameLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Name:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.9</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">identityNameEntry</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="identityNameEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">An identity to log in as.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="passwordEntry">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">The Identity's log-in password.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">False</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="passwordLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Password:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.9</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">passwordEntry</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="perspectiveLabel">
+ <property name="label" translatable="yes">Perspective:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.9</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="perspectiveNameEntry">
+ <property name="tooltip" translatable="yes">The name of a Perspective to request.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char" translatable="yes">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="protocolLabel">
+ <property name="label" translatable="yes">Insert Protocol Version Here</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="versionLabel">
+ <property name="label" translatable="yes">Insert Twisted Version Here</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">1</property>
+
+ <child>
+ <widget class="GtkToggleButton" id="advancedButton">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Advanced options.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Advanced &gt;&gt;</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <signal name="toggled" handler="_on_advancedButton_toggled" object="Login" last_modification_time="Sat, 25 Jan 2003 13:47:17 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/vendor/Twisted-10.0.0/twisted/spread/ui/tktree.py b/vendor/Twisted-10.0.0/twisted/spread/ui/tktree.py
new file mode 100644
index 0000000000..1b34e0e4fb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/ui/tktree.py
@@ -0,0 +1,204 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+What I want it to look like:
+
++- One
+| \- Two
+| |- Three
+| |- Four
+| +- Five
+| | \- Six
+| |- Seven
++- Eight
+| \- Nine
+"""
+
+import os
+from Tkinter import *
+
+class Node:
+ def __init__(self):
+ """
+ Do whatever you want here.
+ """
+ self.item=None
+ def getName(self):
+ """
+ Return the name of this node in the tree.
+ """
+ pass
+ def isExpandable(self):
+ """
+ Return true if this node is expandable.
+ """
+ return len(self.getSubNodes())>0
+ def getSubNodes(self):
+ """
+ Return the sub nodes of this node.
+ """
+ return []
+ def gotDoubleClick(self):
+ """
+ Called when we are double clicked.
+ """
+ pass
+ def updateMe(self):
+ """
+ Call me when something about me changes, so that my representation
+ changes.
+ """
+ if self.item:
+ self.item.update()
+
+class FileNode(Node):
+ def __init__(self,name):
+ Node.__init__(self)
+ self.name=name
+ def getName(self):
+ return os.path.basename(self.name)
+ def isExpandable(self):
+ return os.path.isdir(self.name)
+ def getSubNodes(self):
+ names=map(lambda x,n=self.name:os.path.join(n,x),os.listdir(self.name))
+ return map(FileNode,names)
+
+class TreeItem:
+ def __init__(self,widget,parent,node):
+ self.widget=widget
+ self.node=node
+ node.item=self
+ if self.node.isExpandable():
+ self.expand=0
+ else:
+ self.expand=None
+ self.parent=parent
+ if parent:
+ self.level=self.parent.level+1
+ else:
+ self.level=0
+ self.first=0 # gets set in Tree.expand()
+ self.subitems=[]
+ def __del__(self):
+ del self.node
+ del self.widget
+ def __repr__(self):
+ return "<Item for Node %s at level %s>"%(self.node.getName(),self.level)
+ def render(self):
+ """
+ Override in a subclass.
+ """
+ raise NotImplementedError
+ def update(self):
+ self.widget.update(self)
+
+class ListboxTreeItem(TreeItem):
+ def render(self):
+ start=self.level*"| "
+ if self.expand==None and not self.first:
+ start=start+"|"
+ elif self.expand==0:
+ start=start+"L"
+ elif self.expand==1:
+ start=start+"+"
+ else:
+ start=start+"\\"
+ r=[start+"- "+self.node.getName()]
+ if self.expand:
+ for i in self.subitems:
+ r.extend(i.render())
+ return r
+
+class ListboxTree:
+ def __init__(self,parent=None,**options):
+ self.box=apply(Listbox,[parent],options)
+ self.box.bind("<Double-1>",self.flip)
+ self.roots=[]
+ self.items=[]
+ def pack(self,*args,**kw):
+ """
+ for packing.
+ """
+ apply(self.box.pack,args,kw)
+ def grid(self,*args,**kw):
+ """
+ for gridding.
+ """
+ apply(self.box.grid,args,kw)
+ def yview(self,*args,**kw):
+ """
+ for scrolling.
+ """
+ apply(self.box.yview,args,kw)
+ def addRoot(self,node):
+ r=ListboxTreeItem(self,None,node)
+ self.roots.append(r)
+ self.items.append(r)
+ self.box.insert(END,r.render()[0])
+ return r
+ def curselection(self):
+ c=self.box.curselection()
+ if not c: return
+ return self.items[int(c[0])]
+ def flip(self,*foo):
+ if not self.box.curselection(): return
+ item=self.items[int(self.box.curselection()[0])]
+ if item.expand==None: return
+ if not item.expand:
+ self.expand(item)
+ else:
+ self.close(item)
+ item.node.gotDoubleClick()
+ def expand(self,item):
+ if item.expand or item.expand==None: return
+ item.expand=1
+ item.subitems=map(lambda x,i=item,s=self:ListboxTreeItem(s,i,x),item.node.getSubNodes())
+ if item.subitems:
+ item.subitems[0].first=1
+ i=self.items.index(item)
+ self.items,after=self.items[:i+1],self.items[i+1:]
+ self.items=self.items+item.subitems+after
+ c=self.items.index(item)
+ self.box.delete(c)
+ r=item.render()
+ for i in r:
+ self.box.insert(c,i)
+ c=c+1
+ def close(self,item):
+ if not item.expand: return
+ item.expand=0
+ length=len(item.subitems)
+ for i in item.subitems:
+ self.close(i)
+ c=self.items.index(item)
+ del self.items[c+1:c+1+length]
+ for i in range(length+1):
+ self.box.delete(c)
+ self.box.insert(c,item.render()[0])
+ def remove(self,item):
+ if item.expand:
+ self.close(item)
+ c=self.items.index(item)
+ del self.items[c]
+ if item.parent:
+ item.parent.subitems.remove(item)
+ self.box.delete(c)
+ def update(self,item):
+ if item.expand==None:
+ c=self.items.index(item)
+ self.box.delete(c)
+ self.box.insert(c,item.render()[0])
+ elif item.expand:
+ self.close(item)
+ self.expand(item)
+
+if __name__=="__main__":
+ tk=Tk()
+ s=Scrollbar()
+ t=ListboxTree(tk,yscrollcommand=s.set)
+ t.pack(side=LEFT,fill=BOTH)
+ s.config(command=t.yview)
+ s.pack(side=RIGHT,fill=Y)
+ t.addRoot(FileNode("C:/"))
+ #mainloop()
diff --git a/vendor/Twisted-10.0.0/twisted/spread/ui/tkutil.py b/vendor/Twisted-10.0.0/twisted/spread/ui/tkutil.py
new file mode 100644
index 0000000000..dd9902c9bd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/ui/tkutil.py
@@ -0,0 +1,397 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""Utilities for building L{PB<twisted.spread.pb>} clients with L{Tkinter}.
+"""
+from Tkinter import *
+from tkSimpleDialog import _QueryString
+from tkFileDialog import _Dialog
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted import copyright
+
+import string
+
+#normalFont = Font("-adobe-courier-medium-r-normal-*-*-120-*-*-m-*-iso8859-1")
+#boldFont = Font("-adobe-courier-bold-r-normal-*-*-120-*-*-m-*-iso8859-1")
+#errorFont = Font("-adobe-courier-medium-o-normal-*-*-120-*-*-m-*-iso8859-1")
+
+class _QueryPassword(_QueryString):
+ def body(self, master):
+
+ w = Label(master, text=self.prompt, justify=LEFT)
+ w.grid(row=0, padx=5, sticky=W)
+
+ self.entry = Entry(master, name="entry",show="*")
+ self.entry.grid(row=1, padx=5, sticky=W+E)
+
+ if self.initialvalue:
+ self.entry.insert(0, self.initialvalue)
+ self.entry.select_range(0, END)
+
+ return self.entry
+
+def askpassword(title, prompt, **kw):
+ '''get a password from the user
+
+ @param title: the dialog title
+ @param prompt: the label text
+ @param **kw: see L{SimpleDialog} class
+
+ @returns: a string
+ '''
+ d = apply(_QueryPassword, (title, prompt), kw)
+ return d.result
+
+def grid_setexpand(widget):
+ cols,rows=widget.grid_size()
+ for i in range(cols):
+ widget.columnconfigure(i,weight=1)
+ for i in range(rows):
+ widget.rowconfigure(i,weight=1)
+
+class CList(Frame):
+ def __init__(self,parent,labels,disablesorting=0,**kw):
+ Frame.__init__(self,parent)
+ self.labels=labels
+ self.lists=[]
+ self.disablesorting=disablesorting
+ kw["exportselection"]=0
+ for i in range(len(labels)):
+ b=Button(self,text=labels[i],anchor=W,height=1,pady=0)
+ b.config(command=lambda s=self,i=i:s.setSort(i))
+ b.grid(column=i,row=0,sticky=N+E+W)
+ box=apply(Listbox,(self,),kw)
+ box.grid(column=i,row=1,sticky=N+E+S+W)
+ self.lists.append(box)
+ grid_setexpand(self)
+ self.rowconfigure(0,weight=0)
+ self._callall("bind",'<Button-1>',self.Button1)
+ self._callall("bind",'<B1-Motion>',self.Button1)
+ self.bind('<Up>',self.UpKey)
+ self.bind('<Down>',self.DownKey)
+ self.sort=None
+
+ def _callall(self,funcname,*args,**kw):
+ rets=[]
+ for l in self.lists:
+ func=getattr(l,funcname)
+ ret=apply(func,args,kw)
+ if ret!=None: rets.append(ret)
+ if rets: return rets
+
+ def Button1(self,e):
+ index=self.nearest(e.y)
+ self.select_clear(0,END)
+ self.select_set(index)
+ self.activate(index)
+ return "break"
+
+ def UpKey(self,e):
+ index=self.index(ACTIVE)
+ if index:
+ self.select_clear(0,END)
+ self.select_set(index-1)
+ return "break"
+
+ def DownKey(self,e):
+ index=self.index(ACTIVE)
+ if index!=self.size()-1:
+ self.select_clear(0,END)
+ self.select_set(index+1)
+ return "break"
+
+ def setSort(self,index):
+ if self.sort==None:
+ self.sort=[index,1]
+ elif self.sort[0]==index:
+ self.sort[1]=-self.sort[1]
+ else:
+ self.sort=[index,1]
+ self._sort()
+
+ def _sort(self):
+ if self.disablesorting:
+ return
+ if self.sort==None:
+ return
+ ind,direc=self.sort
+ li=list(self.get(0,END))
+ li.sort(lambda x,y,i=ind,d=direc:d*cmp(x[i],y[i]))
+ self.delete(0,END)
+ for l in li:
+ self._insert(END,l)
+ def activate(self,index):
+ self._callall("activate",index)
+
+ # def bbox(self,index):
+ # return self._callall("bbox",index)
+
+ def curselection(self):
+ return self.lists[0].curselection()
+
+ def delete(self,*args):
+ apply(self._callall,("delete",)+args)
+
+ def get(self,*args):
+ bad=apply(self._callall,("get",)+args)
+ if len(args)==1:
+ return bad
+ ret=[]
+ for i in range(len(bad[0])):
+ r=[]
+ for j in range(len(bad)):
+ r.append(bad[j][i])
+ ret.append(r)
+ return ret
+
+ def index(self,index):
+ return self.lists[0].index(index)
+
+ def insert(self,index,items):
+ self._insert(index,items)
+ self._sort()
+
+ def _insert(self,index,items):
+ for i in range(len(items)):
+ self.lists[i].insert(index,items[i])
+
+ def nearest(self,y):
+ return self.lists[0].nearest(y)
+
+ def see(self,index):
+ self._callall("see",index)
+
+ def size(self):
+ return self.lists[0].size()
+
+ def selection_anchor(self,index):
+ self._callall("selection_anchor",index)
+
+ select_anchor=selection_anchor
+
+ def selection_clear(self,*args):
+ apply(self._callall,("selection_clear",)+args)
+
+ select_clear=selection_clear
+
+ def selection_includes(self,index):
+ return self.lists[0].select_includes(index)
+
+ select_includes=selection_includes
+
+ def selection_set(self,*args):
+ apply(self._callall,("selection_set",)+args)
+
+ select_set=selection_set
+
+ def xview(self,*args):
+ if not args: return self.lists[0].xview()
+ apply(self._callall,("xview",)+args)
+
+ def yview(self,*args):
+ if not args: return self.lists[0].yview()
+ apply(self._callall,("yview",)+args)
+
+class ProgressBar:
+ def __init__(self, master=None, orientation="horizontal",
+ min=0, max=100, width=100, height=18,
+ doLabel=1, appearance="sunken",
+ fillColor="blue", background="gray",
+ labelColor="yellow", labelFont="Verdana",
+ labelText="", labelFormat="%d%%",
+ value=0, bd=2):
+ # preserve various values
+ self.master=master
+ self.orientation=orientation
+ self.min=min
+ self.max=max
+ self.width=width
+ self.height=height
+ self.doLabel=doLabel
+ self.fillColor=fillColor
+ self.labelFont= labelFont
+ self.labelColor=labelColor
+ self.background=background
+ self.labelText=labelText
+ self.labelFormat=labelFormat
+ self.value=value
+ self.frame=Frame(master, relief=appearance, bd=bd)
+ self.canvas=Canvas(self.frame, height=height, width=width, bd=0,
+ highlightthickness=0, background=background)
+ self.scale=self.canvas.create_rectangle(0, 0, width, height,
+ fill=fillColor)
+ self.label=self.canvas.create_text(self.canvas.winfo_reqwidth() / 2,
+ height / 2, text=labelText,
+ anchor="c", fill=labelColor,
+ font=self.labelFont)
+ self.update()
+ self.canvas.pack(side='top', fill='x', expand='no')
+
+ def updateProgress(self, newValue, newMax=None):
+ if newMax:
+ self.max = newMax
+ self.value = newValue
+ self.update()
+
+ def update(self):
+ # Trim the values to be between min and max
+ value=self.value
+ if value > self.max:
+ value = self.max
+ if value < self.min:
+ value = self.min
+ # Adjust the rectangle
+ if self.orientation == "horizontal":
+ self.canvas.coords(self.scale, 0, 0,
+ float(value) / self.max * self.width, self.height)
+ else:
+ self.canvas.coords(self.scale, 0,
+ self.height - (float(value) /
+ self.max*self.height),
+ self.width, self.height)
+ # Now update the colors
+ self.canvas.itemconfig(self.scale, fill=self.fillColor)
+ self.canvas.itemconfig(self.label, fill=self.labelColor)
+ # And update the label
+ if self.doLabel:
+ if value:
+ if value >= 0:
+ pvalue = int((float(value) / float(self.max)) *
+ 100.0)
+ else:
+ pvalue = 0
+ self.canvas.itemconfig(self.label, text=self.labelFormat
+ % pvalue)
+ else:
+ self.canvas.itemconfig(self.label, text='')
+ else:
+ self.canvas.itemconfig(self.label, text=self.labelFormat %
+ self.labelText)
+ self.canvas.update_idletasks()
+
+class DirectoryBrowser(_Dialog):
+ command = "tk_chooseDirectory"
+
+def askdirectory(**options):
+ "Ask for a directory to save to."
+
+ return apply(DirectoryBrowser, (), options).show()
+
+class GenericLogin(Toplevel):
+ def __init__(self,callback,buttons):
+ Toplevel.__init__(self)
+ self.callback=callback
+ Label(self,text="Twisted v%s"%copyright.version).grid(column=0,row=0,columnspan=2)
+ self.entries={}
+ row=1
+ for stuff in buttons:
+ label,value=stuff[:2]
+ if len(stuff)==3:
+ dict=stuff[2]
+ else: dict={}
+ Label(self,text=label+": ").grid(column=0,row=row)
+ e=apply(Entry,(self,),dict)
+ e.grid(column=1,row=row)
+ e.insert(0,value)
+ self.entries[label]=e
+ row=row+1
+ Button(self,text="Login",command=self.doLogin).grid(column=0,row=row)
+ Button(self,text="Cancel",command=self.close).grid(column=1,row=row)
+ self.protocol('WM_DELETE_WINDOW',self.close)
+
+ def close(self):
+ self.tk.quit()
+ self.destroy()
+
+ def doLogin(self):
+ values={}
+ for k in self.entries.keys():
+ values[string.lower(k)]=self.entries[k].get()
+ self.callback(values)
+ self.destroy()
+
+class Login(Toplevel):
+ def __init__(self,
+ callback,
+ referenced = None,
+ initialUser = "guest",
+ initialPassword = "guest",
+ initialHostname = "localhost",
+ initialService = "",
+ initialPortno = pb.portno):
+ Toplevel.__init__(self)
+ version_label = Label(self,text="Twisted v%s" % copyright.version)
+ self.pbReferenceable = referenced
+ self.pbCallback = callback
+ # version_label.show()
+ self.username = Entry(self)
+ self.password = Entry(self,show='*')
+ self.hostname = Entry(self)
+ self.service = Entry(self)
+ self.port = Entry(self)
+
+ self.username.insert(0,initialUser)
+ self.password.insert(0,initialPassword)
+ self.service.insert(0,initialService)
+ self.hostname.insert(0,initialHostname)
+ self.port.insert(0,str(initialPortno))
+
+ userlbl=Label(self,text="Username:")
+ passlbl=Label(self,text="Password:")
+ servicelbl=Label(self,text="Service:")
+ hostlbl=Label(self,text="Hostname:")
+ portlbl=Label(self,text="Port #:")
+ self.logvar=StringVar()
+ self.logvar.set("Protocol PB-%s"%pb.Broker.version)
+ self.logstat = Label(self,textvariable=self.logvar)
+ self.okbutton = Button(self,text="Log In", command=self.login)
+
+ version_label.grid(column=0,row=0,columnspan=2)
+ z=0
+ for i in [[userlbl,self.username],
+ [passlbl,self.password],
+ [hostlbl,self.hostname],
+ [servicelbl,self.service],
+ [portlbl,self.port]]:
+ i[0].grid(column=0,row=z+1)
+ i[1].grid(column=1,row=z+1)
+ z = z+1
+
+ self.logstat.grid(column=0,row=6,columnspan=2)
+ self.okbutton.grid(column=0,row=7,columnspan=2)
+
+ self.protocol('WM_DELETE_WINDOW',self.tk.quit)
+
+ def loginReset(self):
+ self.logvar.set("Idle.")
+
+ def loginReport(self, txt):
+ self.logvar.set(txt)
+ self.after(30000, self.loginReset)
+
+ def login(self):
+ host = self.hostname.get()
+ port = self.port.get()
+ service = self.service.get()
+ try:
+ port = int(port)
+ except:
+ pass
+ user = self.username.get()
+ pswd = self.password.get()
+ pb.connect(host, port, user, pswd, service,
+ client=self.pbReferenceable).addCallback(self.pbCallback).addErrback(
+ self.couldNotConnect)
+
+ def couldNotConnect(self,f):
+ self.loginReport("could not connect:"+f.getErrorMessage())
+
+if __name__=="__main__":
+ root=Tk()
+ o=CList(root,["Username","Online","Auto-Logon","Gateway"])
+ o.pack()
+ for i in range(0,16,4):
+ o.insert(END,[i,i+1,i+2,i+3])
+ mainloop()
diff --git a/vendor/Twisted-10.0.0/twisted/spread/util.py b/vendor/Twisted-10.0.0/twisted/spread/util.py
new file mode 100644
index 0000000000..d4f44d51d2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/spread/util.py
@@ -0,0 +1,215 @@
+# -*- test-case-name: twisted.test.test_pb -*-
+
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Utility classes for spread.
+"""
+
+from twisted.internet import defer
+from twisted.python.failure import Failure
+from twisted.spread import pb
+from twisted.protocols import basic
+from twisted.internet import interfaces
+
+from zope.interface import implements
+
+
+class LocalMethod:
+ def __init__(self, local, name):
+ self.local = local
+ self.name = name
+
+ def __call__(self, *args, **kw):
+ return self.local.callRemote(self.name, *args, **kw)
+
+
+class LocalAsRemote:
+ """
+ A class useful for emulating the effects of remote behavior locally.
+ """
+ reportAllTracebacks = 1
+
+ def callRemote(self, name, *args, **kw):
+ """
+ Call a specially-designated local method.
+
+ self.callRemote('x') will first try to invoke a method named
+ sync_x and return its result (which should probably be a
+ Deferred). Second, it will look for a method called async_x,
+ which will be called and then have its result (or Failure)
+ automatically wrapped in a Deferred.
+ """
+ if hasattr(self, 'sync_'+name):
+ return getattr(self, 'sync_'+name)(*args, **kw)
+ try:
+ method = getattr(self, "async_" + name)
+ return defer.succeed(method(*args, **kw))
+ except:
+ f = Failure()
+ if self.reportAllTracebacks:
+ f.printTraceback()
+ return defer.fail(f)
+
+ def remoteMethod(self, name):
+ return LocalMethod(self, name)
+
+
+class LocalAsyncForwarder:
+ """
+ A class useful for forwarding a locally-defined interface.
+ """
+
+ def __init__(self, forwarded, interfaceClass, failWhenNotImplemented=0):
+ assert interfaceClass.providedBy(forwarded)
+ self.forwarded = forwarded
+ self.interfaceClass = interfaceClass
+ self.failWhenNotImplemented = failWhenNotImplemented
+
+ def _callMethod(self, method, *args, **kw):
+ return getattr(self.forwarded, method)(*args, **kw)
+
+ def callRemote(self, method, *args, **kw):
+ if self.interfaceClass.queryDescriptionFor(method):
+ result = defer.maybeDeferred(self._callMethod, method, *args, **kw)
+ return result
+ elif self.failWhenNotImplemented:
+ return defer.fail(
+ Failure(NotImplementedError,
+ "No Such Method in Interface: %s" % method))
+ else:
+ return defer.succeed(None)
+
+
+class Pager:
+ """
+ I am an object which pages out information.
+ """
+ def __init__(self, collector, callback=None, *args, **kw):
+ """
+ Create a pager with a Reference to a remote collector and
+ an optional callable to invoke upon completion.
+ """
+ if callable(callback):
+ self.callback = callback
+ self.callbackArgs = args
+ self.callbackKeyword = kw
+ else:
+ self.callback = None
+ self._stillPaging = 1
+ self.collector = collector
+ collector.broker.registerPageProducer(self)
+
+ def stillPaging(self):
+ """
+ (internal) Method called by Broker.
+ """
+ if not self._stillPaging:
+ self.collector.callRemote("endedPaging")
+ if self.callback is not None:
+ self.callback(*self.callbackArgs, **self.callbackKeyword)
+ return self._stillPaging
+
+ def sendNextPage(self):
+ """
+ (internal) Method called by Broker.
+ """
+ self.collector.callRemote("gotPage", self.nextPage())
+
+ def nextPage(self):
+ """
+ Override this to return an object to be sent to my collector.
+ """
+ raise NotImplementedError()
+
+ def stopPaging(self):
+ """
+ Call this when you're done paging.
+ """
+ self._stillPaging = 0
+
+
+class StringPager(Pager):
+ """
+ A simple pager that splits a string into chunks.
+ """
+ def __init__(self, collector, st, chunkSize=8192, callback=None, *args, **kw):
+ self.string = st
+ self.pointer = 0
+ self.chunkSize = chunkSize
+ Pager.__init__(self, collector, callback, *args, **kw)
+
+ def nextPage(self):
+ val = self.string[self.pointer:self.pointer+self.chunkSize]
+ self.pointer += self.chunkSize
+ if self.pointer >= len(self.string):
+ self.stopPaging()
+ return val
+
+
+class FilePager(Pager):
+ """
+ Reads a file in chunks and sends the chunks as they come.
+ """
+ implements(interfaces.IConsumer)
+
+ def __init__(self, collector, fd, callback=None, *args, **kw):
+ self.chunks = []
+ Pager.__init__(self, collector, callback, *args, **kw)
+ self.startProducing(fd)
+
+ def startProducing(self, fd):
+ self.deferred = basic.FileSender().beginFileTransfer(fd, self)
+ self.deferred.addBoth(lambda x : self.stopPaging())
+
+ def registerProducer(self, producer, streaming):
+ self.producer = producer
+ if not streaming:
+ self.producer.resumeProducing()
+
+ def unregisterProducer(self):
+ self.producer = None
+
+ def write(self, chunk):
+ self.chunks.append(chunk)
+
+ def sendNextPage(self):
+ """
+ Get the first chunk read and send it to collector.
+ """
+ if not self.chunks:
+ return
+ val = self.chunks.pop(0)
+ self.producer.resumeProducing()
+ self.collector.callRemote("gotPage", val)
+
+
+# Utility paging stuff.
+class CallbackPageCollector(pb.Referenceable):
+ """
+ I receive pages from the peer. You may instantiate a Pager with a
+ remote reference to me. I will call the callback with a list of pages
+ once they are all received.
+ """
+ def __init__(self, callback):
+ self.pages = []
+ self.callback = callback
+
+ def remote_gotPage(self, page):
+ self.pages.append(page)
+
+ def remote_endedPaging(self):
+ self.callback(self.pages)
+
+
+def getAllPages(referenceable, methodName, *args, **kw):
+ """
+ A utility method that will call a remote method which expects a
+ PageCollector as the first argument.
+ """
+ d = defer.Deferred()
+ referenceable.callRemote(methodName, CallbackPageCollector(d.callback), *args, **kw)
+ return d
+
diff --git a/vendor/Twisted-10.0.0/twisted/tap/__init__.py b/vendor/Twisted-10.0.0/twisted/tap/__init__.py
new file mode 100644
index 0000000000..2428714a18
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/tap/__init__.py
@@ -0,0 +1,10 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+
+Twisted TAP: Twisted Application Persistence builders for other Twisted servers.
+
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/tap/ftp.py b/vendor/Twisted-10.0.0/twisted/tap/ftp.py
new file mode 100644
index 0000000000..283437d09e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/tap/ftp.py
@@ -0,0 +1,51 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+I am the support module for making a ftp server with twistd.
+"""
+
+from twisted.protocols import ftp
+from twisted.python import usage
+from twisted.application import internet
+from twisted.cred import error, portal, checkers, credentials
+
+import os.path
+
+
+class Options(usage.Options):
+ synopsis = """[options].
+ WARNING: This FTP server is probably INSECURE do not use it.
+ """
+ optParameters = [
+ ["port", "p", "2121", "set the port number"],
+ ["root", "r", "/usr/local/ftp", "define the root of the ftp-site."],
+ ["userAnonymous", "", "anonymous", "Name of the anonymous user."],
+ ["password-file", "", None, "username:password-style credentials database"],
+ ]
+
+ longdesc = ''
+
+
+def makeService(config):
+ f = ftp.FTPFactory()
+
+ r = ftp.FTPRealm(config['root'])
+ p = portal.Portal(r)
+ p.registerChecker(checkers.AllowAnonymousAccess(), credentials.IAnonymous)
+
+ if config['password-file'] is not None:
+ p.registerChecker(checkers.FilePasswordDB(config['password-file'], cache=True))
+
+ f.tld = config['root']
+ f.userAnonymous = config['userAnonymous']
+ f.portal = p
+ f.protocol = ftp.FTP
+
+ try:
+ portno = int(config['port'])
+ except KeyError:
+ portno = 2121
+ return internet.TCPServer(portno, f)
diff --git a/vendor/Twisted-10.0.0/twisted/tap/manhole.py b/vendor/Twisted-10.0.0/twisted/tap/manhole.py
new file mode 100644
index 0000000000..203154da75
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/tap/manhole.py
@@ -0,0 +1,51 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+I am the support module for making a manhole server with twistd.
+"""
+
+from twisted.manhole import service
+from twisted.spread import pb
+from twisted.python import usage, util
+from twisted.cred import portal, checkers
+from twisted.application import strports
+import os, sys
+
+class Options(usage.Options):
+ synopsis = "[options]"
+ optParameters = [
+ ["user", "u", "admin", "Name of user to allow to log in"],
+ ["port", "p", str(pb.portno), "Port to listen on"],
+ ]
+
+ optFlags = [
+ ["tracebacks", "T", "Allow tracebacks to be sent over the network"],
+ ]
+ zsh_actions = {"user" : "_users"}
+
+ def opt_password(self, password):
+ """Required. '-' will prompt or read a password from stdin.
+ """
+ # If standard input is a terminal, I prompt for a password and
+ # confirm it. Otherwise, I use the first line from standard
+ # input, stripping off a trailing newline if there is one.
+ if password in ('', '-'):
+ self['password'] = util.getPassword(confirm=1)
+ else:
+ self['password'] = password
+ opt_w = opt_password
+
+ def postOptions(self):
+ if not self.has_key('password'):
+ self.opt_password('-')
+
+def makeService(config):
+ port, user, password = config['port'], config['user'], config['password']
+ p = portal.Portal(
+ service.Realm(service.Service(config["tracebacks"], config.get('namespace'))),
+ [checkers.InMemoryUsernamePasswordDatabaseDontUse(**{user: password})]
+ )
+ return strports.service(port, pb.PBServerFactory(p, config["tracebacks"]))
diff --git a/vendor/Twisted-10.0.0/twisted/tap/portforward.py b/vendor/Twisted-10.0.0/twisted/tap/portforward.py
new file mode 100644
index 0000000000..48faad7fff
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/tap/portforward.py
@@ -0,0 +1,24 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Support module for making a port forwarder with twistd.
+"""
+from twisted.protocols import portforward
+from twisted.python import usage
+from twisted.application import strports
+
+class Options(usage.Options):
+ synopsis = "[options]"
+ longdesc = 'Port Forwarder.'
+ optParameters = [
+ ["port", "p", "6666","Set the port number."],
+ ["host", "h", "localhost","Set the host."],
+ ["dest_port", "d", 6665,"Set the destination port."],
+ ]
+ zsh_actions = {"host" : "_hosts"}
+
+def makeService(config):
+ f = portforward.ProxyFactory(config['host'], int(config['dest_port']))
+ return strports.service(config['port'], f)
diff --git a/vendor/Twisted-10.0.0/twisted/tap/socks.py b/vendor/Twisted-10.0.0/twisted/tap/socks.py
new file mode 100644
index 0000000000..99c0df80b1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/tap/socks.py
@@ -0,0 +1,34 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+I am a support module for making SOCKSv4 servers with twistd.
+"""
+
+from twisted.protocols import socks
+from twisted.python import usage
+from twisted.application import internet
+import sys
+
+class Options(usage.Options):
+ synopsis = "[-i <interface>] [-p <port>] [-l <file>]"
+ optParameters = [["interface", "i", "127.0.0.1", "local interface to which we listen"],
+ ["port", "p", 1080, "Port on which to listen"],
+ ["log", "l", None, "file to log connection data to"]]
+ zsh_actions = {"log" : "_files -g '*.log'"}
+
+ longdesc = "Makes a SOCKSv4 server."
+
+def makeService(config):
+ if config["interface"] != "127.0.0.1":
+ print
+ print "WARNING:"
+ print " You have chosen to listen on a non-local interface."
+ print " This may allow intruders to access your local network"
+ print " if you run this on a firewall."
+ print
+ t = socks.SOCKSv4Factory(config['log'])
+ portno = int(config['port'])
+ return internet.TCPServer(portno, t, interface=config['interface'])
diff --git a/vendor/Twisted-10.0.0/twisted/tap/telnet.py b/vendor/Twisted-10.0.0/twisted/tap/telnet.py
new file mode 100644
index 0000000000..c13d0bb2c1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/tap/telnet.py
@@ -0,0 +1,29 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Support module for making a telnet server with twistd.
+"""
+
+from twisted.manhole import telnet
+from twisted.python import usage
+from twisted.application import strports
+
+class Options(usage.Options):
+ synopsis = "[options]"
+ longdesc = "Makes a telnet server to a Python shell."
+ optParameters = [
+ ["username", "u", "admin","set the login username"],
+ ["password", "w", "changeme","set the password"],
+ ["port", "p", "4040", "port to listen on"],
+ ]
+ zsh_actions = {"username":"_users"}
+
+def makeService(config):
+ t = telnet.ShellFactory()
+ t.username, t.password = config['username'], config['password']
+ s = strports.service(config['port'], t)
+ t.setService(s)
+ return s
diff --git a/vendor/Twisted-10.0.0/twisted/test/__init__.py b/vendor/Twisted-10.0.0/twisted/test/__init__.py
new file mode 100644
index 0000000000..85a53c7a6d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/__init__.py
@@ -0,0 +1,10 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+
+Twisted Test: Unit Tests for Twisted.
+
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/test/crash_test_dummy.py b/vendor/Twisted-10.0.0/twisted/test/crash_test_dummy.py
new file mode 100644
index 0000000000..a508c60e1e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/crash_test_dummy.py
@@ -0,0 +1,34 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.python import components
+from zope.interface import implements, Interface
+
+def foo():
+ return 2
+
+class X:
+ def __init__(self, x):
+ self.x = x
+
+ def do(self):
+ #print 'X',self.x,'doing!'
+ pass
+
+
+class XComponent(components.Componentized):
+ pass
+
+class IX(Interface):
+ pass
+
+class XA(components.Adapter):
+ implements(IX)
+
+ def method(self):
+ # Kick start :(
+ pass
+
+components.registerAdapter(XA, X, IX)
diff --git a/vendor/Twisted-10.0.0/twisted/test/generator_failure_tests.py b/vendor/Twisted-10.0.0/twisted/test/generator_failure_tests.py
new file mode 100644
index 0000000000..8c31c271b0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/generator_failure_tests.py
@@ -0,0 +1,169 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Python 2.5 test cases for failures thrown into generators.
+"""
+
+import sys
+import traceback
+
+from twisted.trial.unittest import TestCase
+
+from twisted.python.failure import Failure
+from twisted.test.test_failure import getDivisionFailure
+from twisted.internet import defer
+
+
+class TwoPointFiveFailureTests(TestCase):
+
+ def test_inlineCallbacksTracebacks(self):
+ """
+ inlineCallbacks that re-raise tracebacks into their deferred
+ should not lose their tracebacsk.
+ """
+ f = getDivisionFailure()
+ d = defer.Deferred()
+ try:
+ f.raiseException()
+ except:
+ d.errback()
+
+ failures = []
+ def collect_error(result):
+ failures.append(result)
+
+ def ic(d):
+ yield d
+ ic = defer.inlineCallbacks(ic)
+ ic(d).addErrback(collect_error)
+
+ newFailure, = failures
+ self.assertEquals(
+ traceback.extract_tb(newFailure.getTracebackObject())[-1][-1],
+ "1/0"
+ )
+
+
+ def _throwIntoGenerator(self, f, g):
+ try:
+ f.throwExceptionIntoGenerator(g)
+ except StopIteration:
+ pass
+ else:
+ self.fail("throwExceptionIntoGenerator should have raised "
+ "StopIteration")
+
+ def test_throwExceptionIntoGenerator(self):
+ """
+ It should be possible to throw the exception that a Failure
+ represents into a generator.
+ """
+ stuff = []
+ def generator():
+ try:
+ yield
+ except:
+ stuff.append(sys.exc_info())
+ else:
+ self.fail("Yield should have yielded exception.")
+ g = generator()
+ f = getDivisionFailure()
+ g.next()
+ self._throwIntoGenerator(f, g)
+
+ self.assertEquals(stuff[0][0], ZeroDivisionError)
+ self.assertTrue(isinstance(stuff[0][1], ZeroDivisionError))
+
+ self.assertEquals(traceback.extract_tb(stuff[0][2])[-1][-1], "1/0")
+
+
+ def test_findFailureInGenerator(self):
+ """
+ Within an exception handler, it should be possible to find the
+ original Failure that caused the current exception (if it was
+ caused by throwExceptionIntoGenerator).
+ """
+ f = getDivisionFailure()
+ f.cleanFailure()
+
+ foundFailures = []
+ def generator():
+ try:
+ yield
+ except:
+ foundFailures.append(Failure._findFailure())
+ else:
+ self.fail("No exception sent to generator")
+
+ g = generator()
+ g.next()
+ self._throwIntoGenerator(f, g)
+
+ self.assertEqual(foundFailures, [f])
+
+
+ def test_failureConstructionFindsOriginalFailure(self):
+ """
+ When a Failure is constructed in the context of an exception
+ handler that is handling an exception raised by
+ throwExceptionIntoGenerator, the new Failure should be chained to that
+ original Failure.
+ """
+ f = getDivisionFailure()
+ f.cleanFailure()
+
+ newFailures = []
+
+ def generator():
+ try:
+ yield
+ except:
+ newFailures.append(Failure())
+ else:
+ self.fail("No exception sent to generator")
+ g = generator()
+ g.next()
+ self._throwIntoGenerator(f, g)
+
+ self.assertEqual(len(newFailures), 1)
+ self.assertEqual(newFailures[0].getTraceback(), f.getTraceback())
+
+ def test_ambiguousFailureInGenerator(self):
+ """
+ When a generator reraises a different exception,
+ L{Failure._findFailure} inside the generator should find the reraised
+ exception rather than original one.
+ """
+ def generator():
+ try:
+ try:
+ yield
+ except:
+ [][1]
+ except:
+ self.assertIsInstance(Failure().value, IndexError)
+ g = generator()
+ g.next()
+ f = getDivisionFailure()
+ self._throwIntoGenerator(f, g)
+
+ def test_ambiguousFailureFromGenerator(self):
+ """
+ When a generator reraises a different exception,
+ L{Failure._findFailure} above the generator should find the reraised
+ exception rather than original one.
+ """
+ def generator():
+ try:
+ yield
+ except:
+ [][1]
+ g = generator()
+ g.next()
+ f = getDivisionFailure()
+ try:
+ self._throwIntoGenerator(f, g)
+ except:
+ self.assertIsInstance(Failure().value, IndexError)
diff --git a/vendor/Twisted-10.0.0/twisted/test/iosim.py b/vendor/Twisted-10.0.0/twisted/test/iosim.py
new file mode 100644
index 0000000000..11a4893713
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/iosim.py
@@ -0,0 +1,270 @@
+# -*- test-case-name: twisted.test.test_amp.TLSTest -*-
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Utilities and helpers for simulating a network
+"""
+
+import itertools
+
+try:
+ from OpenSSL.SSL import Error as NativeOpenSSLError
+except ImportError:
+ pass
+
+from zope.interface import implements, directlyProvides
+
+from twisted.python.failure import Failure
+from twisted.internet import error
+from twisted.internet import interfaces
+
+class TLSNegotiation:
+ def __init__(self, obj, connectState):
+ self.obj = obj
+ self.connectState = connectState
+ self.sent = False
+ self.readyToSend = connectState
+
+ def __repr__(self):
+ return 'TLSNegotiation(%r)' % (self.obj,)
+
+ def pretendToVerify(self, other, tpt):
+ # Set the transport problems list here? disconnections?
+ # hmmmmm... need some negative path tests.
+
+ if not self.obj.iosimVerify(other.obj):
+ tpt.disconnectReason = NativeOpenSSLError()
+ tpt.loseConnection()
+
+
+class FakeTransport:
+ """
+ A wrapper around a file-like object to make it behave as a Transport.
+
+ This doesn't actually stream the file to the attached protocol,
+ and is thus useful mainly as a utility for debugging protocols.
+ """
+ implements(interfaces.ITransport,
+ interfaces.ITLSTransport) # ha ha not really
+
+ _nextserial = itertools.count().next
+ closed = 0
+ disconnecting = 0
+ disconnected = 0
+ disconnectReason = error.ConnectionDone("Connection done")
+ producer = None
+ streamingProducer = 0
+ tls = None
+
+ def __init__(self):
+ self.stream = []
+ self.serial = self._nextserial()
+
+ def __repr__(self):
+ return 'FakeTransport<%s,%s,%s>' % (
+ self.isServer and 'S' or 'C', self.serial,
+ self.protocol.__class__.__name__)
+
+ def write(self, data):
+ if self.tls is not None:
+ self.tlsbuf.append(data)
+ else:
+ self.stream.append(data)
+
+ def _checkProducer(self):
+ # Cheating; this is called at "idle" times to allow producers to be
+ # found and dealt with
+ if self.producer:
+ self.producer.resumeProducing()
+
+ def registerProducer(self, producer, streaming):
+ """From abstract.FileDescriptor
+ """
+ self.producer = producer
+ self.streamingProducer = streaming
+ if not streaming:
+ producer.resumeProducing()
+
+ def unregisterProducer(self):
+ self.producer = None
+
+ def stopConsuming(self):
+ self.unregisterProducer()
+ self.loseConnection()
+
+ def writeSequence(self, iovec):
+ self.write("".join(iovec))
+
+ def loseConnection(self):
+ self.disconnecting = True
+
+ def reportDisconnect(self):
+ if self.tls is not None:
+ # We were in the middle of negotiating! Must have been a TLS problem.
+ err = NativeOpenSSLError()
+ else:
+ err = self.disconnectReason
+ self.protocol.connectionLost(Failure(err))
+
+ def getPeer(self):
+ # XXX: According to ITransport, this should return an IAddress!
+ return 'file', 'file'
+
+ def getHost(self):
+ # XXX: According to ITransport, this should return an IAddress!
+ return 'file'
+
+ def resumeProducing(self):
+ # Never sends data anyways
+ pass
+
+ def pauseProducing(self):
+ # Never sends data anyways
+ pass
+
+ def stopProducing(self):
+ self.loseConnection()
+
+ def startTLS(self, contextFactory, beNormal=True):
+ # Nothing's using this feature yet, but startTLS has an undocumented
+ # second argument which defaults to true; if set to False, servers will
+ # behave like clients and clients will behave like servers.
+ connectState = self.isServer ^ beNormal
+ self.tls = TLSNegotiation(contextFactory, connectState)
+ self.tlsbuf = []
+
+ def getOutBuffer(self):
+ S = self.stream
+ if S:
+ self.stream = []
+ return ''.join(S)
+ elif self.tls is not None:
+ if self.tls.readyToSend:
+ # Only _send_ the TLS negotiation "packet" if I'm ready to.
+ self.tls.sent = True
+ return self.tls
+ else:
+ return None
+ else:
+ return None
+
+ def bufferReceived(self, buf):
+ if isinstance(buf, TLSNegotiation):
+ assert self.tls is not None # By the time you're receiving a
+ # negotiation, you have to have called
+ # startTLS already.
+ if self.tls.sent:
+ self.tls.pretendToVerify(buf, self)
+ self.tls = None # we're done with the handshake if we've gotten
+ # this far... although maybe it failed...?
+ # TLS started! Unbuffer...
+ b, self.tlsbuf = self.tlsbuf, None
+ self.writeSequence(b)
+ directlyProvides(self, interfaces.ISSLTransport)
+ else:
+ # We haven't sent our own TLS negotiation: time to do that!
+ self.tls.readyToSend = True
+ else:
+ self.protocol.dataReceived(buf)
+
+
+
+def makeFakeClient(c):
+ ft = FakeTransport()
+ ft.isServer = False
+ ft.protocol = c
+ return ft
+
+def makeFakeServer(s):
+ ft = FakeTransport()
+ ft.isServer = True
+ ft.protocol = s
+ return ft
+
+class IOPump:
+ """Utility to pump data between clients and servers for protocol testing.
+
+ Perhaps this is a utility worthy of being in protocol.py?
+ """
+ def __init__(self, client, server, clientIO, serverIO, debug):
+ self.client = client
+ self.server = server
+ self.clientIO = clientIO
+ self.serverIO = serverIO
+ self.debug = debug
+
+ def flush(self, debug=False):
+ """Pump until there is no more input or output.
+
+ Returns whether any data was moved.
+ """
+ result = False
+ for x in range(1000):
+ if self.pump(debug):
+ result = True
+ else:
+ break
+ else:
+ assert 0, "Too long"
+ return result
+
+
+ def pump(self, debug=False):
+ """Move data back and forth.
+
+ Returns whether any data was moved.
+ """
+ if self.debug or debug:
+ print '-- GLUG --'
+ sData = self.serverIO.getOutBuffer()
+ cData = self.clientIO.getOutBuffer()
+ self.clientIO._checkProducer()
+ self.serverIO._checkProducer()
+ if self.debug or debug:
+ print '.'
+ # XXX slightly buggy in the face of incremental output
+ if cData:
+ print 'C: '+repr(cData)
+ if sData:
+ print 'S: '+repr(sData)
+ if cData:
+ self.serverIO.bufferReceived(cData)
+ if sData:
+ self.clientIO.bufferReceived(sData)
+ if cData or sData:
+ return True
+ if (self.serverIO.disconnecting and
+ not self.serverIO.disconnected):
+ if self.debug or debug:
+ print '* C'
+ self.serverIO.disconnected = True
+ self.clientIO.disconnecting = True
+ self.clientIO.reportDisconnect()
+ return True
+ if self.clientIO.disconnecting and not self.clientIO.disconnected:
+ if self.debug or debug:
+ print '* S'
+ self.clientIO.disconnected = True
+ self.serverIO.disconnecting = True
+ self.serverIO.reportDisconnect()
+ return True
+ return False
+
+
+def connectedServerAndClient(ServerClass, ClientClass,
+ clientTransportFactory=makeFakeClient,
+ serverTransportFactory=makeFakeServer,
+ debug=False):
+ """Returns a 3-tuple: (client, server, pump)
+ """
+ c = ClientClass()
+ s = ServerClass()
+ cio = clientTransportFactory(c)
+ sio = serverTransportFactory(s)
+ c.makeConnection(cio)
+ s.makeConnection(sio)
+ pump = IOPump(c, s, cio, sio, debug)
+ # kick off server greeting, etc
+ pump.flush()
+ return c, s, pump
diff --git a/vendor/Twisted-10.0.0/twisted/test/mock_win32process.py b/vendor/Twisted-10.0.0/twisted/test/mock_win32process.py
new file mode 100644
index 0000000000..49ed953591
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/mock_win32process.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2007-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This is a mock win32process module.
+
+The purpose of this module is mock process creation for the PID test.
+
+CreateProcess(...) will spawn a process, and always return a PID of 42.
+"""
+
+import win32process
+GetExitCodeProcess = win32process.GetExitCodeProcess
+STARTUPINFO = win32process.STARTUPINFO
+
+STARTF_USESTDHANDLES = win32process.STARTF_USESTDHANDLES
+
+
+def CreateProcess(appName,
+ cmdline,
+ procSecurity,
+ threadSecurity,
+ inheritHandles,
+ newEnvironment,
+ env,
+ workingDir,
+ startupInfo):
+ """
+ This function mocks the generated pid aspect of the win32.CreateProcess
+ function.
+ - the true win32process.CreateProcess is called
+ - return values are harvested in a tuple.
+ - all return values from createProcess are passed back to the calling
+ function except for the pid, the returned pid is hardcoded to 42
+ """
+
+ hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(
+ appName,
+ cmdline,
+ procSecurity,
+ threadSecurity,
+ inheritHandles,
+ newEnvironment,
+ env,
+ workingDir,
+ startupInfo)
+ dwPid = 42
+ return (hProcess, hThread, dwPid, dwTid)
diff --git a/vendor/Twisted-10.0.0/twisted/test/myrebuilder1.py b/vendor/Twisted-10.0.0/twisted/test/myrebuilder1.py
new file mode 100644
index 0000000000..f53e8c7e78
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/myrebuilder1.py
@@ -0,0 +1,15 @@
+
+class A:
+ def a(self):
+ return 'a'
+try:
+ object
+except NameError:
+ pass
+else:
+ class B(object, A):
+ def b(self):
+ return 'b'
+class Inherit(A):
+ def a(self):
+ return 'c'
diff --git a/vendor/Twisted-10.0.0/twisted/test/myrebuilder2.py b/vendor/Twisted-10.0.0/twisted/test/myrebuilder2.py
new file mode 100644
index 0000000000..d2a0d10d77
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/myrebuilder2.py
@@ -0,0 +1,16 @@
+
+class A:
+ def a(self):
+ return 'b'
+try:
+ object
+except NameError:
+ pass
+else:
+ class B(A, object):
+ def b(self):
+ return 'c'
+
+class Inherit(A):
+ def a(self):
+ return 'd'
diff --git a/vendor/Twisted-10.0.0/twisted/test/plugin_basic.py b/vendor/Twisted-10.0.0/twisted/test/plugin_basic.py
new file mode 100644
index 0000000000..343a3c5125
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/plugin_basic.py
@@ -0,0 +1,57 @@
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# Don't change the docstring, it's part of the tests
+"""
+I'm a test drop-in. The plugin system's unit tests use me. No one
+else should.
+"""
+
+from zope.interface import classProvides
+
+from twisted.plugin import IPlugin
+from twisted.test.test_plugin import ITestPlugin, ITestPlugin2
+
+
+
+class TestPlugin:
+ """
+ A plugin used solely for testing purposes.
+ """
+
+ classProvides(ITestPlugin,
+ IPlugin)
+
+ def test1():
+ pass
+ test1 = staticmethod(test1)
+
+
+
+class AnotherTestPlugin:
+ """
+ Another plugin used solely for testing purposes.
+ """
+
+ classProvides(ITestPlugin2,
+ IPlugin)
+
+ def test():
+ pass
+ test = staticmethod(test)
+
+
+
+class ThirdTestPlugin:
+ """
+ Another plugin used solely for testing purposes.
+ """
+
+ classProvides(ITestPlugin2,
+ IPlugin)
+
+ def test():
+ pass
+ test = staticmethod(test)
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/plugin_extra1.py b/vendor/Twisted-10.0.0/twisted/test/plugin_extra1.py
new file mode 100644
index 0000000000..bc1b3c0d5d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/plugin_extra1.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test plugin used in L{twisted.test.test_plugin}.
+"""
+
+from zope.interface import classProvides
+
+from twisted.plugin import IPlugin
+from twisted.test.test_plugin import ITestPlugin
+
+
+
+class FourthTestPlugin:
+ classProvides(ITestPlugin,
+ IPlugin)
+
+ def test1():
+ pass
+ test1 = staticmethod(test1)
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/plugin_extra2.py b/vendor/Twisted-10.0.0/twisted/test/plugin_extra2.py
new file mode 100644
index 0000000000..05ff992645
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/plugin_extra2.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test plugin used in L{twisted.test.test_plugin}.
+"""
+
+from zope.interface import classProvides
+
+from twisted.plugin import IPlugin
+from twisted.test.test_plugin import ITestPlugin
+
+
+
+class FourthTestPlugin:
+ classProvides(ITestPlugin,
+ IPlugin)
+
+ def test1():
+ pass
+ test1 = staticmethod(test1)
+
+
+
+class FifthTestPlugin:
+ """
+ More documentation: I hate you.
+ """
+ classProvides(ITestPlugin,
+ IPlugin)
+
+ def test1():
+ pass
+ test1 = staticmethod(test1)
diff --git a/vendor/Twisted-10.0.0/twisted/test/process_cmdline.py b/vendor/Twisted-10.0.0/twisted/test/process_cmdline.py
new file mode 100644
index 0000000000..bd250ded31
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/process_cmdline.py
@@ -0,0 +1,5 @@
+"""Write to stdout the command line args it received, one per line."""
+
+import sys
+for x in sys.argv[1:]:
+ print x
diff --git a/vendor/Twisted-10.0.0/twisted/test/process_echoer.py b/vendor/Twisted-10.0.0/twisted/test/process_echoer.py
new file mode 100644
index 0000000000..8a7bf6dc0d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/process_echoer.py
@@ -0,0 +1,11 @@
+"""Write back all data it receives."""
+
+import sys
+
+data = sys.stdin.read(1)
+while data:
+ sys.stdout.write(data)
+ sys.stdout.flush()
+ data = sys.stdin.read(1)
+sys.stderr.write("byebye")
+sys.stderr.flush()
diff --git a/vendor/Twisted-10.0.0/twisted/test/process_fds.py b/vendor/Twisted-10.0.0/twisted/test/process_fds.py
new file mode 100644
index 0000000000..e2273c1de2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/process_fds.py
@@ -0,0 +1,40 @@
+
+"""Write to a handful of file descriptors, to test the childFDs= argument of
+reactor.spawnProcess()
+"""
+
+import os, sys
+
+debug = 0
+
+if debug: stderr = os.fdopen(2, "w")
+
+if debug: print >>stderr, "this is stderr"
+
+abcd = os.read(0, 4)
+if debug: print >>stderr, "read(0):", abcd
+if abcd != "abcd":
+ sys.exit(1)
+
+if debug: print >>stderr, "os.write(1, righto)"
+
+os.write(1, "righto")
+
+efgh = os.read(3, 4)
+if debug: print >>stderr, "read(3):", efgh
+if efgh != "efgh":
+ sys.exit(2)
+
+if debug: print >>stderr, "os.close(4)"
+os.close(4)
+
+eof = os.read(5, 4)
+if debug: print >>stderr, "read(5):", eof
+if eof != "":
+ sys.exit(3)
+
+if debug: print >>stderr, "os.write(1, closed)"
+os.write(1, "closed")
+
+if debug: print >>stderr, "sys.exit(0)"
+sys.exit(0)
diff --git a/vendor/Twisted-10.0.0/twisted/test/process_linger.py b/vendor/Twisted-10.0.0/twisted/test/process_linger.py
new file mode 100644
index 0000000000..a95a8d2fcd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/process_linger.py
@@ -0,0 +1,17 @@
+
+"""Write to a file descriptor and then close it, waiting a few seconds before
+quitting. This serves to make sure SIGCHLD is actually being noticed.
+"""
+
+import os, sys, time
+
+print "here is some text"
+time.sleep(1)
+print "goodbye"
+os.close(1)
+os.close(2)
+
+time.sleep(2)
+
+sys.exit(0)
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/process_reader.py b/vendor/Twisted-10.0.0/twisted/test/process_reader.py
new file mode 100644
index 0000000000..be37a7c42f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/process_reader.py
@@ -0,0 +1,12 @@
+"""Script used by test_process.TestTwoProcesses"""
+
+# run until stdin is closed, then quit
+
+import sys
+
+while 1:
+ d = sys.stdin.read()
+ if len(d) == 0:
+ sys.exit(0)
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/process_signal.py b/vendor/Twisted-10.0.0/twisted/test/process_signal.py
new file mode 100644
index 0000000000..f2ff108f85
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/process_signal.py
@@ -0,0 +1,8 @@
+import sys, signal
+
+signal.signal(signal.SIGINT, signal.SIG_DFL)
+if getattr(signal, "SIGHUP", None) is not None:
+ signal.signal(signal.SIGHUP, signal.SIG_DFL)
+print 'ok, signal us'
+sys.stdin.read()
+sys.exit(1)
diff --git a/vendor/Twisted-10.0.0/twisted/test/process_stdinreader.py b/vendor/Twisted-10.0.0/twisted/test/process_stdinreader.py
new file mode 100644
index 0000000000..9de453d8e1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/process_stdinreader.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""Script used by twisted.test.test_process on win32."""
+
+import sys, time, os, msvcrt
+msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
+msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
+
+
+sys.stdout.write("out\n")
+sys.stdout.flush()
+sys.stderr.write("err\n")
+sys.stderr.flush()
+
+data = sys.stdin.read()
+
+sys.stdout.write(data)
+sys.stdout.write("\nout\n")
+sys.stderr.write("err\n")
+
+sys.stdout.flush()
+sys.stderr.flush()
diff --git a/vendor/Twisted-10.0.0/twisted/test/process_tester.py b/vendor/Twisted-10.0.0/twisted/test/process_tester.py
new file mode 100644
index 0000000000..d9779b1900
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/process_tester.py
@@ -0,0 +1,37 @@
+"""Test program for processes."""
+
+import sys, os
+
+test_file_match = "process_test.log.*"
+test_file = "process_test.log.%d" % os.getpid()
+
+def main():
+ f = open(test_file, 'wb')
+
+ # stage 1
+ bytes = sys.stdin.read(4)
+ f.write("one: %r\n" % bytes)
+ # stage 2
+ sys.stdout.write(bytes)
+ sys.stdout.flush()
+ os.close(sys.stdout.fileno())
+
+ # and a one, and a two, and a...
+ bytes = sys.stdin.read(4)
+ f.write("two: %r\n" % bytes)
+
+ # stage 3
+ sys.stderr.write(bytes)
+ sys.stderr.flush()
+ os.close(sys.stderr.fileno())
+
+ # stage 4
+ bytes = sys.stdin.read(4)
+ f.write("three: %r\n" % bytes)
+
+ # exit with status code 23
+ sys.exit(23)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/Twisted-10.0.0/twisted/test/process_tty.py b/vendor/Twisted-10.0.0/twisted/test/process_tty.py
new file mode 100644
index 0000000000..9dab63892e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/process_tty.py
@@ -0,0 +1,6 @@
+"""Test to make sure we can open /dev/tty"""
+
+f = open("/dev/tty", "r+")
+a = f.readline()
+f.write(a)
+f.close()
diff --git a/vendor/Twisted-10.0.0/twisted/test/process_twisted.py b/vendor/Twisted-10.0.0/twisted/test/process_twisted.py
new file mode 100644
index 0000000000..e31c7e2d9b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/process_twisted.py
@@ -0,0 +1,43 @@
+"""A process that reads from stdin and out using Twisted."""
+
+### Twisted Preamble
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+import sys, os, string
+pos = string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted')
+if pos != -1:
+ sys.path.insert(0, os.path.abspath(sys.argv[0])[:pos+8])
+sys.path.insert(0, os.curdir)
+### end of preamble
+
+
+from twisted.python import log
+from zope.interface import implements
+from twisted.internet import interfaces
+
+log.startLogging(sys.stderr)
+
+from twisted.internet import protocol, reactor, stdio
+
+
+class Echo(protocol.Protocol):
+ implements(interfaces.IHalfCloseableProtocol)
+
+ def connectionMade(self):
+ print "connection made"
+
+ def dataReceived(self, data):
+ self.transport.write(data)
+
+ def readConnectionLost(self):
+ print "readConnectionLost"
+ self.transport.loseConnection()
+ def writeConnectionLost(self):
+ print "writeConnectionLost"
+
+ def connectionLost(self, reason):
+ print "connectionLost", reason
+ reactor.stop()
+
+stdio.StandardIO(Echo())
+reactor.run()
diff --git a/vendor/Twisted-10.0.0/twisted/test/proto_helpers.py b/vendor/Twisted-10.0.0/twisted/test/proto_helpers.py
new file mode 100644
index 0000000000..87f3936a7c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/proto_helpers.py
@@ -0,0 +1,299 @@
+# -*- test-case-name: twisted.test.test_stringtransport -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Assorted functionality which is commonly useful when writing unit tests.
+"""
+
+from StringIO import StringIO
+
+from zope.interface import implements
+from zope.interface.verify import verifyObject
+
+from twisted.internet.interfaces import ITransport, IConsumer, IPushProducer
+from twisted.internet.interfaces import IReactorTCP
+from twisted.protocols import basic
+from twisted.internet import protocol, error
+
+
+class AccumulatingProtocol(protocol.Protocol):
+ """
+ L{AccumulatingProtocol} is an L{IProtocol} implementation which collects
+ the data delivered to it and can fire a Deferred when it is connected or
+ disconnected.
+
+ @ivar made: A flag indicating whether C{connectionMade} has been called.
+ @ivar data: A string giving all the data passed to C{dataReceived}.
+ @ivar closed: A flag indicated whether C{connectionLost} has been called.
+ @ivar closedReason: The value of the I{reason} parameter passed to
+ C{connectionLost}.
+ @ivar closedDeferred: If set to a L{Deferred}, this will be fired when
+ C{connectionLost} is called.
+ """
+ made = closed = 0
+ closedReason = None
+
+ closedDeferred = None
+
+ data = ""
+
+ factory = None
+
+ def connectionMade(self):
+ self.made = 1
+ if (self.factory is not None and
+ self.factory.protocolConnectionMade is not None):
+ d = self.factory.protocolConnectionMade
+ self.factory.protocolConnectionMade = None
+ d.callback(self)
+
+ def dataReceived(self, data):
+ self.data += data
+
+ def connectionLost(self, reason):
+ self.closed = 1
+ self.closedReason = reason
+ if self.closedDeferred is not None:
+ d, self.closedDeferred = self.closedDeferred, None
+ d.callback(None)
+
+
+class LineSendingProtocol(basic.LineReceiver):
+ lostConn = False
+
+ def __init__(self, lines, start = True):
+ self.lines = lines[:]
+ self.response = []
+ self.start = start
+
+ def connectionMade(self):
+ if self.start:
+ map(self.sendLine, self.lines)
+
+ def lineReceived(self, line):
+ if not self.start:
+ map(self.sendLine, self.lines)
+ self.lines = []
+ self.response.append(line)
+
+ def connectionLost(self, reason):
+ self.lostConn = True
+
+
+class FakeDatagramTransport:
+ noAddr = object()
+
+ def __init__(self):
+ self.written = []
+
+ def write(self, packet, addr=noAddr):
+ self.written.append((packet, addr))
+
+
+class StringTransport:
+ """
+ A transport implementation which buffers data in memory and keeps track of
+ its other state without providing any behavior.
+
+ L{StringTransport} has a number of attributes which are not part of any of
+ the interfaces it claims to implement. These attributes are provided for
+ testing purposes. Implementation code should not use any of these
+ attributes; they are not provided by other transports.
+
+ @ivar disconnecting: A C{bool} which is C{False} until L{loseConnection} is
+ called, then C{True}.
+
+ @ivar producer: If a producer is currently registered, C{producer} is a
+ reference to it. Otherwise, C{None}.
+
+ @ivar streaming: If a producer is currently registered, C{streaming} refers
+ to the value of the second parameter passed to C{registerProducer}.
+
+ @ivar hostAddr: C{None} or an object which will be returned as the host
+ address of this transport. If C{None}, a nasty tuple will be returned
+ instead.
+
+ @ivar peerAddr: C{None} or an object which will be returned as the peer
+ address of this transport. If C{None}, a nasty tuple will be returned
+ instead.
+
+ @ivar producerState: The state of this L{StringTransport} in its capacity
+ as an L{IPushProducer}. One of C{'producing'}, C{'paused'}, or
+ C{'stopped'}.
+
+ @ivar io: A L{StringIO} which holds the data which has been written to this
+ transport since the last call to L{clear}. Use L{value} instead of
+ accessing this directly.
+ """
+ implements(ITransport, IConsumer, IPushProducer)
+
+ disconnecting = False
+
+ producer = None
+ streaming = None
+
+ hostAddr = None
+ peerAddr = None
+
+ producerState = 'producing'
+
+ def __init__(self, hostAddress=None, peerAddress=None):
+ self.clear()
+ if hostAddress is not None:
+ self.hostAddr = hostAddress
+ if peerAddress is not None:
+ self.peerAddr = peerAddress
+ self.connected = True
+
+ def clear(self):
+ """
+ Discard all data written to this transport so far.
+
+ This is not a transport method. It is intended for tests. Do not use
+ it in implementation code.
+ """
+ self.io = StringIO()
+
+
+ def value(self):
+ """
+ Retrieve all data which has been buffered by this transport.
+
+ This is not a transport method. It is intended for tests. Do not use
+ it in implementation code.
+
+ @return: A C{str} giving all data written to this transport since the
+ last call to L{clear}.
+ @rtype: C{str}
+ """
+ return self.io.getvalue()
+
+
+ # ITransport
+ def write(self, data):
+ if isinstance(data, unicode): # no, really, I mean it
+ raise TypeError("Data must not be unicode")
+ self.io.write(data)
+
+
+ def writeSequence(self, data):
+ self.io.write(''.join(data))
+
+
+ def loseConnection(self):
+ """
+ Close the connection. Does nothing besides toggle the C{disconnecting}
+ instance variable to C{True}.
+ """
+ self.disconnecting = True
+
+
+ def getPeer(self):
+ if self.peerAddr is None:
+ return ('StringIO', repr(self.io))
+ return self.peerAddr
+
+
+ def getHost(self):
+ if self.hostAddr is None:
+ return ('StringIO', repr(self.io))
+ return self.hostAddr
+
+
+ # IConsumer
+ def registerProducer(self, producer, streaming):
+ if self.producer is not None:
+ raise RuntimeError("Cannot register two producers")
+ self.producer = producer
+ self.streaming = streaming
+
+
+ def unregisterProducer(self):
+ if self.producer is None:
+ raise RuntimeError(
+ "Cannot unregister a producer unless one is registered")
+ self.producer = None
+ self.streaming = None
+
+
+ # IPushProducer
+ def _checkState(self):
+ if self.disconnecting:
+ raise RuntimeError(
+ "Cannot resume producing after loseConnection")
+ if self.producerState == 'stopped':
+ raise RuntimeError("Cannot resume a stopped producer")
+
+
+ def pauseProducing(self):
+ self._checkState()
+ self.producerState = 'paused'
+
+
+ def stopProducing(self):
+ self.producerState = 'stopped'
+
+
+ def resumeProducing(self):
+ self._checkState()
+ self.producerState = 'producing'
+
+
+
+class StringTransportWithDisconnection(StringTransport):
+ def loseConnection(self):
+ if self.connected:
+ self.connected = False
+ self.protocol.connectionLost(error.ConnectionDone("Bye."))
+
+
+
+class StringIOWithoutClosing(StringIO):
+ """
+ A StringIO that can't be closed.
+ """
+ def close(self):
+ """
+ Do nothing.
+ """
+
+
+class MemoryReactor(object):
+ """
+ A fake reactor to be used in tests. This reactor doesn't actually do
+ much that's useful yet. It accepts TCP connection setup attempts, but
+ they will never succeed.
+
+ @ivar tcpClients: a list that keeps track of connection attempts (ie, calls
+ to C{connectTCP}).
+ @type tcpClients: C{list}
+
+ @ivar tcpServers: a list that keeps track of server listen attempts (ie, calls
+ to C{listenTCP}).
+ @type tcpServers: C{list}
+ """
+ implements(IReactorTCP)
+
+ def __init__(self):
+ """
+ Initialize the tracking lists.
+ """
+ self.tcpClients = []
+ self.tcpServers = []
+
+
+ def listenTCP(self, port, factory, backlog=50, interface=''):
+ """
+ Fake L{reactor.listenTCP}, that does nothing but log the call.
+ """
+ self.tcpServers.append((port, factory, backlog, interface))
+
+
+ def connectTCP(self, host, port, factory, timeout=30, bindAddress=None):
+ """
+ Fake L{reactor.connectTCP}, that does nothing but log the call.
+ """
+ self.tcpClients.append((host, port, factory, timeout, bindAddress))
+
+verifyObject(IReactorTCP, MemoryReactor())
diff --git a/vendor/Twisted-10.0.0/twisted/test/raiser.c b/vendor/Twisted-10.0.0/twisted/test/raiser.c
new file mode 100644
index 0000000000..d4c800c88a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/raiser.c
@@ -0,0 +1,316 @@
+/* Generated by Pyrex 0.9.5.1a on Wed Apr 9 19:55:33 2008 */
+
+#include "Python.h"
+#include "structmember.h"
+#ifndef PY_LONG_LONG
+ #define PY_LONG_LONG LONG_LONG
+#endif
+#ifdef __cplusplus
+#define __PYX_EXTERN_C extern "C"
+#else
+#define __PYX_EXTERN_C extern
+#endif
+__PYX_EXTERN_C double pow(double, double);
+
+
+typedef struct {PyObject **p; char *s;} __Pyx_InternTabEntry; /*proto*/
+typedef struct {PyObject **p; char *s; long n;} __Pyx_StringTabEntry; /*proto*/
+
+static PyObject *__pyx_m;
+static PyObject *__pyx_b;
+static int __pyx_lineno;
+static char *__pyx_filename;
+static char **__pyx_f;
+
+static char __pyx_mdoc[] = "\nA trivial extension that just raises an exception.\nSee L{twisted.test.test_failure.test_failureConstructionWithMungedStackSucceeds}.\n";
+
+static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name); /*proto*/
+
+static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, char *modname); /*proto*/
+
+static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb); /*proto*/
+
+static int __Pyx_InternStrings(__Pyx_InternTabEntry *t); /*proto*/
+
+static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); /*proto*/
+
+static void __Pyx_AddTraceback(char *funcname); /*proto*/
+
+/* Declarations from raiser */
+
+
+
+/* Implementation of raiser */
+
+static char (__pyx_k2[]) = "\n A speficic exception only used to be identified in tests.\n ";
+
+static PyObject *__pyx_n_RaiserException;
+static PyObject *__pyx_n_raiseException;
+static PyObject *__pyx_n_Exception;
+
+static PyObject *__pyx_k2p;
+
+static PyObject *__pyx_k3p;
+
+static char (__pyx_k3[]) = "This function is intentionally broken";
+
+static PyObject *__pyx_f_6raiser_raiseException(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
+static char __pyx_doc_6raiser_raiseException[] = "\n Raise L{RaiserException}.\n ";
+static PyObject *__pyx_f_6raiser_raiseException(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
+ PyObject *__pyx_r;
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ static char *__pyx_argnames[] = {0};
+ if (!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames)) return 0;
+
+ /* "/home/therve/Projects/Twisted/branches/failure-c-extension-3132/twisted/test/raiser.pyx":21 */
+ __pyx_1 = __Pyx_GetName(__pyx_m, __pyx_n_RaiserException); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
+ __pyx_2 = PyTuple_New(1); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
+ Py_INCREF(__pyx_k3p);
+ PyTuple_SET_ITEM(__pyx_2, 0, __pyx_k3p);
+ __pyx_3 = PyObject_CallObject(__pyx_1, __pyx_2); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ __Pyx_Raise(__pyx_3, 0, 0);
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ {__pyx_filename = __pyx_f[0]; __pyx_lineno = 21; goto __pyx_L1;}
+
+ __pyx_r = Py_None; Py_INCREF(Py_None);
+ goto __pyx_L0;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("raiser.raiseException");
+ __pyx_r = 0;
+ __pyx_L0:;
+ return __pyx_r;
+}
+
+static __Pyx_InternTabEntry __pyx_intern_tab[] = {
+ {&__pyx_n_Exception, "Exception"},
+ {&__pyx_n_RaiserException, "RaiserException"},
+ {&__pyx_n_raiseException, "raiseException"},
+ {0, 0}
+};
+
+static __Pyx_StringTabEntry __pyx_string_tab[] = {
+ {&__pyx_k2p, __pyx_k2, sizeof(__pyx_k2)},
+ {&__pyx_k3p, __pyx_k3, sizeof(__pyx_k3)},
+ {0, 0, 0}
+};
+
+static struct PyMethodDef __pyx_methods[] = {
+ {"raiseException", (PyCFunction)__pyx_f_6raiser_raiseException, METH_VARARGS|METH_KEYWORDS, __pyx_doc_6raiser_raiseException},
+ {0, 0, 0, 0}
+};
+
+static void __pyx_init_filenames(void); /*proto*/
+
+PyMODINIT_FUNC initraiser(void); /*proto*/
+PyMODINIT_FUNC initraiser(void) {
+ PyObject *__pyx_1 = 0;
+ PyObject *__pyx_2 = 0;
+ PyObject *__pyx_3 = 0;
+ __pyx_init_filenames();
+ __pyx_m = Py_InitModule4("raiser", __pyx_methods, __pyx_mdoc, 0, PYTHON_API_VERSION);
+ if (!__pyx_m) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
+ __pyx_b = PyImport_AddModule("__builtin__");
+ if (!__pyx_b) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
+ if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
+ if (__Pyx_InternStrings(__pyx_intern_tab) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
+ if (__Pyx_InitStrings(__pyx_string_tab) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 4; goto __pyx_L1;};
+
+ /* "/home/therve/Projects/Twisted/branches/failure-c-extension-3132/twisted/test/raiser.pyx":11 */
+ __pyx_1 = PyDict_New(); if (!__pyx_1) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 11; goto __pyx_L1;}
+ __pyx_2 = __Pyx_GetName(__pyx_b, __pyx_n_Exception); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 11; goto __pyx_L1;}
+ __pyx_3 = PyTuple_New(1); if (!__pyx_3) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 11; goto __pyx_L1;}
+ PyTuple_SET_ITEM(__pyx_3, 0, __pyx_2);
+ __pyx_2 = 0;
+ if (PyDict_SetItemString(__pyx_1, "__doc__", __pyx_k2p) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 11; goto __pyx_L1;}
+ __pyx_2 = __Pyx_CreateClass(__pyx_3, __pyx_1, __pyx_n_RaiserException, "raiser"); if (!__pyx_2) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 11; goto __pyx_L1;}
+ Py_DECREF(__pyx_3); __pyx_3 = 0;
+ if (PyObject_SetAttr(__pyx_m, __pyx_n_RaiserException, __pyx_2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 11; goto __pyx_L1;}
+ Py_DECREF(__pyx_2); __pyx_2 = 0;
+ Py_DECREF(__pyx_1); __pyx_1 = 0;
+
+ /* "/home/therve/Projects/Twisted/branches/failure-c-extension-3132/twisted/test/raiser.pyx":17 */
+ return;
+ __pyx_L1:;
+ Py_XDECREF(__pyx_1);
+ Py_XDECREF(__pyx_2);
+ Py_XDECREF(__pyx_3);
+ __Pyx_AddTraceback("raiser");
+}
+
+static char *__pyx_filenames[] = {
+ "raiser.pyx",
+};
+
+/* Runtime support code */
+
+static void __pyx_init_filenames(void) {
+ __pyx_f = __pyx_filenames;
+}
+
+static PyObject *__Pyx_GetName(PyObject *dict, PyObject *name) {
+ PyObject *result;
+ result = PyObject_GetAttr(dict, name);
+ if (!result)
+ PyErr_SetObject(PyExc_NameError, name);
+ return result;
+}
+
+static PyObject *__Pyx_CreateClass(
+ PyObject *bases, PyObject *dict, PyObject *name, char *modname)
+{
+ PyObject *py_modname;
+ PyObject *result = 0;
+
+ py_modname = PyString_FromString(modname);
+ if (!py_modname)
+ goto bad;
+ if (PyDict_SetItemString(dict, "__module__", py_modname) < 0)
+ goto bad;
+ result = PyClass_New(bases, dict, name);
+bad:
+ Py_XDECREF(py_modname);
+ return result;
+}
+
+static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb) {
+ Py_XINCREF(type);
+ Py_XINCREF(value);
+ Py_XINCREF(tb);
+ /* First, check the traceback argument, replacing None with NULL. */
+ if (tb == Py_None) {
+ Py_DECREF(tb);
+ tb = 0;
+ }
+ else if (tb != NULL && !PyTraceBack_Check(tb)) {
+ PyErr_SetString(PyExc_TypeError,
+ "raise: arg 3 must be a traceback or None");
+ goto raise_error;
+ }
+ /* Next, replace a missing value with None */
+ if (value == NULL) {
+ value = Py_None;
+ Py_INCREF(value);
+ }
+ /* Next, repeatedly, replace a tuple exception with its first item */
+ while (PyTuple_Check(type) && PyTuple_Size(type) > 0) {
+ PyObject *tmp = type;
+ type = PyTuple_GET_ITEM(type, 0);
+ Py_INCREF(type);
+ Py_DECREF(tmp);
+ }
+ if (PyString_Check(type)) {
+ if (PyErr_Warn(PyExc_DeprecationWarning,
+ "raising a string exception is deprecated"))
+ goto raise_error;
+ }
+ else if (PyType_Check(type) || PyClass_Check(type))
+ ; /*PyErr_NormalizeException(&type, &value, &tb);*/
+ else {
+ /* Raising an instance. The value should be a dummy. */
+ if (value != Py_None) {
+ PyErr_SetString(PyExc_TypeError,
+ "instance exception may not have a separate value");
+ goto raise_error;
+ }
+ /* Normalize to raise <class>, <instance> */
+ Py_DECREF(value);
+ value = type;
+ if (PyInstance_Check(type))
+ type = (PyObject*) ((PyInstanceObject*)type)->in_class;
+ else
+ type = (PyObject*) type->ob_type;
+ Py_INCREF(type);
+ }
+ PyErr_Restore(type, value, tb);
+ return;
+raise_error:
+ Py_XDECREF(value);
+ Py_XDECREF(type);
+ Py_XDECREF(tb);
+ return;
+}
+
+static int __Pyx_InternStrings(__Pyx_InternTabEntry *t) {
+ while (t->p) {
+ *t->p = PyString_InternFromString(t->s);
+ if (!*t->p)
+ return -1;
+ ++t;
+ }
+ return 0;
+}
+
+static int __Pyx_InitStrings(__Pyx_StringTabEntry *t) {
+ while (t->p) {
+ *t->p = PyString_FromStringAndSize(t->s, t->n - 1);
+ if (!*t->p)
+ return -1;
+ ++t;
+ }
+ return 0;
+}
+
+#include "compile.h"
+#include "frameobject.h"
+#include "traceback.h"
+
+static void __Pyx_AddTraceback(char *funcname) {
+ PyObject *py_srcfile = 0;
+ PyObject *py_funcname = 0;
+ PyObject *py_globals = 0;
+ PyObject *empty_tuple = 0;
+ PyObject *empty_string = 0;
+ PyCodeObject *py_code = 0;
+ PyFrameObject *py_frame = 0;
+
+ py_srcfile = PyString_FromString(__pyx_filename);
+ if (!py_srcfile) goto bad;
+ py_funcname = PyString_FromString(funcname);
+ if (!py_funcname) goto bad;
+ py_globals = PyModule_GetDict(__pyx_m);
+ if (!py_globals) goto bad;
+ empty_tuple = PyTuple_New(0);
+ if (!empty_tuple) goto bad;
+ empty_string = PyString_FromString("");
+ if (!empty_string) goto bad;
+ py_code = PyCode_New(
+ 0, /*int argcount,*/
+ 0, /*int nlocals,*/
+ 0, /*int stacksize,*/
+ 0, /*int flags,*/
+ empty_string, /*PyObject *code,*/
+ empty_tuple, /*PyObject *consts,*/
+ empty_tuple, /*PyObject *names,*/
+ empty_tuple, /*PyObject *varnames,*/
+ empty_tuple, /*PyObject *freevars,*/
+ empty_tuple, /*PyObject *cellvars,*/
+ py_srcfile, /*PyObject *filename,*/
+ py_funcname, /*PyObject *name,*/
+ __pyx_lineno, /*int firstlineno,*/
+ empty_string /*PyObject *lnotab*/
+ );
+ if (!py_code) goto bad;
+ py_frame = PyFrame_New(
+ PyThreadState_Get(), /*PyThreadState *tstate,*/
+ py_code, /*PyCodeObject *code,*/
+ py_globals, /*PyObject *globals,*/
+ 0 /*PyObject *locals*/
+ );
+ if (!py_frame) goto bad;
+ py_frame->f_lineno = __pyx_lineno;
+ PyTraceBack_Here(py_frame);
+bad:
+ Py_XDECREF(py_srcfile);
+ Py_XDECREF(py_funcname);
+ Py_XDECREF(empty_tuple);
+ Py_XDECREF(empty_string);
+ Py_XDECREF(py_code);
+ Py_XDECREF(py_frame);
+}
diff --git a/vendor/Twisted-10.0.0/twisted/test/raiser.pyx b/vendor/Twisted-10.0.0/twisted/test/raiser.pyx
new file mode 100644
index 0000000000..a21ce09e7a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/raiser.pyx
@@ -0,0 +1,21 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A trivial extension that just raises an exception.
+See L{twisted.test.test_failure.test_failureConstructionWithMungedStackSucceeds}.
+"""
+
+
+
+class RaiserException(Exception):
+ """
+ A speficic exception only used to be identified in tests.
+ """
+
+
+def raiseException():
+ """
+ Raise L{RaiserException}.
+ """
+ raise RaiserException("This function is intentionally broken")
diff --git a/vendor/Twisted-10.0.0/twisted/test/reflect_helper_IE.py b/vendor/Twisted-10.0.0/twisted/test/reflect_helper_IE.py
new file mode 100644
index 0000000000..614d948308
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/reflect_helper_IE.py
@@ -0,0 +1,4 @@
+
+# Helper for a test_reflect test
+
+import idonotexist
diff --git a/vendor/Twisted-10.0.0/twisted/test/reflect_helper_VE.py b/vendor/Twisted-10.0.0/twisted/test/reflect_helper_VE.py
new file mode 100644
index 0000000000..e19507f21e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/reflect_helper_VE.py
@@ -0,0 +1,4 @@
+
+# Helper for a test_reflect test
+
+raise ValueError("Stuff is broken and things")
diff --git a/vendor/Twisted-10.0.0/twisted/test/reflect_helper_ZDE.py b/vendor/Twisted-10.0.0/twisted/test/reflect_helper_ZDE.py
new file mode 100644
index 0000000000..bd05fbca51
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/reflect_helper_ZDE.py
@@ -0,0 +1,4 @@
+
+# Helper module for a test_reflect test
+
+1/0
diff --git a/vendor/Twisted-10.0.0/twisted/test/server.pem b/vendor/Twisted-10.0.0/twisted/test/server.pem
new file mode 100644
index 0000000000..80ef9dcf3b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv
+c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB
+BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC
+MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
+MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7
+hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT
+CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw
+dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
+LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6
+BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++
+7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE
+WUQ9Ho4EzbYCOQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx
+OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT
+ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4
+nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2
+HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA
+oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBDTCBuAIBADBTMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQ
+BgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20w
+XDANBgkqhkiG9w0BAQEFAANLADBIAkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5Ad
+NgLzJ1/MfsQQJ7hHVeHmTAjM664V+fXvwUGJLziCeBo1ysWLRnl8CQIDAQABoAAw
+DQYJKoZIhvcNAQEEBQADQQA7uqbrNTjVWpF6By5ZNPvhZ4YdFgkeXFVWi5ao/TaP
+Vq4BG021fJ9nlHRtr4rotpgHDX1rr+iWeHKsx4+5DRSy
+-----END CERTIFICATE REQUEST-----
diff --git a/vendor/Twisted-10.0.0/twisted/test/ssl_helpers.py b/vendor/Twisted-10.0.0/twisted/test/ssl_helpers.py
new file mode 100644
index 0000000000..aa609a84eb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/ssl_helpers.py
@@ -0,0 +1,26 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import ssl
+from twisted.python.util import sibpath
+
+from OpenSSL import SSL
+
+class ClientTLSContext(ssl.ClientContextFactory):
+ isClient = 1
+ def getContext(self):
+ return SSL.Context(SSL.TLSv1_METHOD)
+
+class ServerTLSContext:
+ isClient = 0
+
+ def __init__(self, filename = sibpath(__file__, 'server.pem')):
+ self.filename = filename
+
+ def getContext(self):
+ ctx = SSL.Context(SSL.TLSv1_METHOD)
+ ctx.use_certificate_file(self.filename)
+ ctx.use_privatekey_file(self.filename)
+ return ctx
diff --git a/vendor/Twisted-10.0.0/twisted/test/stdio_test_consumer.py b/vendor/Twisted-10.0.0/twisted/test/stdio_test_consumer.py
new file mode 100644
index 0000000000..97ddca2f93
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/stdio_test_consumer.py
@@ -0,0 +1,39 @@
+# -*- test-case-name: twisted.test.test_stdio.StandardInputOutputTestCase.test_consumer -*-
+# Copyright (c) 2006-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Main program for the child process run by
+L{twisted.test.test_stdio.StandardInputOutputTestCase.test_consumer} to test
+that process transports implement IConsumer properly.
+"""
+
+import sys
+
+from twisted.python import log, reflect
+from twisted.internet import stdio, protocol
+from twisted.protocols import basic
+
+def failed(err):
+ log.startLogging(sys.stderr)
+ log.err(err)
+
+class ConsumerChild(protocol.Protocol):
+ def __init__(self, junkPath):
+ self.junkPath = junkPath
+
+ def connectionMade(self):
+ d = basic.FileSender().beginFileTransfer(file(self.junkPath), self.transport)
+ d.addErrback(failed)
+ d.addCallback(lambda ign: self.transport.loseConnection())
+
+
+ def connectionLost(self, reason):
+ reactor.stop()
+
+
+if __name__ == '__main__':
+ reflect.namedAny(sys.argv[1]).install()
+ from twisted.internet import reactor
+ stdio.StandardIO(ConsumerChild(sys.argv[2]))
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/twisted/test/stdio_test_hostpeer.py b/vendor/Twisted-10.0.0/twisted/test/stdio_test_hostpeer.py
new file mode 100644
index 0000000000..fb2937c9f5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/stdio_test_hostpeer.py
@@ -0,0 +1,32 @@
+# -*- test-case-name: twisted.test.test_stdio.StandardInputOutputTestCase.test_hostAndPeer -*-
+# Copyright (c) 2006-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Main program for the child process run by
+L{twisted.test.test_stdio.StandardInputOutputTestCase.test_hostAndPeer} to test
+that ITransport.getHost() and ITransport.getPeer() work for process transports.
+"""
+
+import sys
+
+from twisted.internet import stdio, protocol
+from twisted.python import reflect
+
+class HostPeerChild(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.write('\n'.join([
+ str(self.transport.getHost()),
+ str(self.transport.getPeer())]))
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ reactor.stop()
+
+
+if __name__ == '__main__':
+ reflect.namedAny(sys.argv[1]).install()
+ from twisted.internet import reactor
+ stdio.StandardIO(HostPeerChild())
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/twisted/test/stdio_test_lastwrite.py b/vendor/Twisted-10.0.0/twisted/test/stdio_test_lastwrite.py
new file mode 100644
index 0000000000..d9da17aa2b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/stdio_test_lastwrite.py
@@ -0,0 +1,45 @@
+# -*- test-case-name: twisted.test.test_stdio.StandardInputOutputTestCase.test_lastWriteReceived -*-
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Main program for the child process run by
+L{twisted.test.test_stdio.StandardInputOutputTestCase.test_lastWriteReceived}
+to test that L{os.write} can be reliably used after
+L{twisted.internet.stdio.StandardIO} has finished.
+"""
+
+import sys
+
+from twisted.internet.protocol import Protocol
+from twisted.internet.stdio import StandardIO
+from twisted.python.reflect import namedAny
+
+
+class LastWriteChild(Protocol):
+ def __init__(self, reactor, magicString):
+ self.reactor = reactor
+ self.magicString = magicString
+
+
+ def connectionMade(self):
+ self.transport.write(self.magicString)
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ self.reactor.stop()
+
+
+
+def main(reactor, magicString):
+ p = LastWriteChild(reactor, magicString)
+ StandardIO(p)
+ reactor.run()
+
+
+
+if __name__ == '__main__':
+ namedAny(sys.argv[1]).install()
+ from twisted.internet import reactor
+ main(reactor, sys.argv[2])
diff --git a/vendor/Twisted-10.0.0/twisted/test/stdio_test_loseconn.py b/vendor/Twisted-10.0.0/twisted/test/stdio_test_loseconn.py
new file mode 100644
index 0000000000..0e6c856abc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/stdio_test_loseconn.py
@@ -0,0 +1,48 @@
+# -*- test-case-name: twisted.test.test_stdio.StandardInputOutputTestCase.test_loseConnection -*-
+# Copyright (c) 2006-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Main program for the child process run by
+L{twisted.test.test_stdio.StandardInputOutputTestCase.test_loseConnection} to
+test that ITransport.loseConnection() works for process transports.
+"""
+
+import sys
+
+from twisted.internet.error import ConnectionDone
+from twisted.internet import stdio, protocol
+from twisted.python import reflect, log
+
+class LoseConnChild(protocol.Protocol):
+ exitCode = 0
+
+ def connectionMade(self):
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ """
+ Check that C{reason} is a L{Failure} wrapping a L{ConnectionDone}
+ instance and stop the reactor. If C{reason} is wrong for some reason,
+ log something about that in C{self.errorLogFile} and make sure the
+ process exits with a non-zero status.
+ """
+ try:
+ try:
+ reason.trap(ConnectionDone)
+ except:
+ log.err(None, "Problem with reason passed to connectionLost")
+ self.exitCode = 1
+ finally:
+ reactor.stop()
+
+
+if __name__ == '__main__':
+ reflect.namedAny(sys.argv[1]).install()
+ log.startLogging(file(sys.argv[2], 'w'))
+ from twisted.internet import reactor
+ protocol = LoseConnChild()
+ stdio.StandardIO(protocol)
+ reactor.run()
+ sys.exit(protocol.exitCode)
diff --git a/vendor/Twisted-10.0.0/twisted/test/stdio_test_producer.py b/vendor/Twisted-10.0.0/twisted/test/stdio_test_producer.py
new file mode 100644
index 0000000000..00db9f79b1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/stdio_test_producer.py
@@ -0,0 +1,55 @@
+# -*- test-case-name: twisted.test.test_stdio.StandardInputOutputTestCase.test_producer -*-
+# Copyright (c) 2006-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Main program for the child process run by
+L{twisted.test.test_stdio.StandardInputOutputTestCase.test_producer} to test
+that process transports implement IProducer properly.
+"""
+
+import sys
+
+from twisted.internet import stdio, protocol
+from twisted.python import log, reflect
+
+class ProducerChild(protocol.Protocol):
+ _paused = False
+ buf = ''
+
+ def connectionLost(self, reason):
+ log.msg("*****OVER*****")
+ reactor.callLater(1, reactor.stop)
+ # reactor.stop()
+
+
+ def dataReceived(self, bytes):
+ self.buf += bytes
+ if self._paused:
+ log.startLogging(sys.stderr)
+ log.msg("dataReceived while transport paused!")
+ self.transport.loseConnection()
+ else:
+ self.transport.write(bytes)
+ if self.buf.endswith('\n0\n'):
+ self.transport.loseConnection()
+ else:
+ self.pause()
+
+
+ def pause(self):
+ self._paused = True
+ self.transport.pauseProducing()
+ reactor.callLater(0.01, self.unpause)
+
+
+ def unpause(self):
+ self._paused = False
+ self.transport.resumeProducing()
+
+
+if __name__ == '__main__':
+ reflect.namedAny(sys.argv[1]).install()
+ from twisted.internet import reactor
+ stdio.StandardIO(ProducerChild())
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/twisted/test/stdio_test_write.py b/vendor/Twisted-10.0.0/twisted/test/stdio_test_write.py
new file mode 100644
index 0000000000..e15233182d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/stdio_test_write.py
@@ -0,0 +1,32 @@
+# -*- test-case-name: twisted.test.test_stdio.StandardInputOutputTestCase.test_write -*-
+# Copyright (c) 2006-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Main program for the child process run by
+L{twisted.test.test_stdio.StandardInputOutputTestCase.test_write} to test that
+ITransport.write() works for process transports.
+"""
+
+import sys
+
+from twisted.internet import stdio, protocol
+from twisted.python import reflect
+
+class WriteChild(protocol.Protocol):
+ def connectionMade(self):
+ for ch in 'ok!':
+ self.transport.write(ch)
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ reactor.stop()
+
+
+if __name__ == '__main__':
+ reflect.namedAny(sys.argv[1]).install()
+ from twisted.internet import reactor
+ stdio.StandardIO(WriteChild())
+ reactor.run()
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/stdio_test_writeseq.py b/vendor/Twisted-10.0.0/twisted/test/stdio_test_writeseq.py
new file mode 100644
index 0000000000..5a499ba974
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/stdio_test_writeseq.py
@@ -0,0 +1,30 @@
+# -*- test-case-name: twisted.test.test_stdio.StandardInputOutputTestCase.test_writeSequence -*-
+# Copyright (c) 2006-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Main program for the child process run by
+L{twisted.test.test_stdio.StandardInputOutputTestCase.test_writeSequence} to test that
+ITransport.writeSequence() works for process transports.
+"""
+
+import sys
+
+from twisted.internet import stdio, protocol
+from twisted.python import reflect
+
+class WriteSequenceChild(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.writeSequence(list('ok!'))
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ reactor.stop()
+
+
+if __name__ == '__main__':
+ reflect.namedAny(sys.argv[1]).install()
+ from twisted.internet import reactor
+ stdio.StandardIO(WriteSequenceChild())
+ reactor.run()
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_abstract.py b/vendor/Twisted-10.0.0/twisted/test/test_abstract.py
new file mode 100644
index 0000000000..44b4646cbb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_abstract.py
@@ -0,0 +1,83 @@
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for generic file descriptor based reactor support code.
+"""
+
+from twisted.trial.unittest import TestCase
+
+from twisted.internet.abstract import isIPAddress
+
+
+class AddressTests(TestCase):
+ """
+ Tests for address-related functionality.
+ """
+ def test_decimalDotted(self):
+ """
+ L{isIPAddress} should return C{True} for any decimal dotted
+ representation of an IPv4 address.
+ """
+ self.assertTrue(isIPAddress('0.1.2.3'))
+ self.assertTrue(isIPAddress('252.253.254.255'))
+
+
+ def test_shortDecimalDotted(self):
+ """
+ L{isIPAddress} should return C{False} for a dotted decimal
+ representation with fewer or more than four octets.
+ """
+ self.assertFalse(isIPAddress('0'))
+ self.assertFalse(isIPAddress('0.1'))
+ self.assertFalse(isIPAddress('0.1.2'))
+ self.assertFalse(isIPAddress('0.1.2.3.4'))
+
+
+ def test_invalidLetters(self):
+ """
+ L{isIPAddress} should return C{False} for any non-decimal dotted
+ representation including letters.
+ """
+ self.assertFalse(isIPAddress('a.2.3.4'))
+ self.assertFalse(isIPAddress('1.b.3.4'))
+
+
+ def test_invalidPunctuation(self):
+ """
+ L{isIPAddress} should return C{False} for a string containing
+ strange punctuation.
+ """
+ self.assertFalse(isIPAddress(','))
+ self.assertFalse(isIPAddress('1,2'))
+ self.assertFalse(isIPAddress('1,2,3'))
+ self.assertFalse(isIPAddress('1.,.3,4'))
+
+
+ def test_emptyString(self):
+ """
+ L{isIPAddress} should return C{False} for the empty string.
+ """
+ self.assertFalse(isIPAddress(''))
+
+
+ def test_invalidNegative(self):
+ """
+ L{isIPAddress} should return C{False} for negative decimal values.
+ """
+ self.assertFalse(isIPAddress('-1'))
+ self.assertFalse(isIPAddress('1.-2'))
+ self.assertFalse(isIPAddress('1.2.-3'))
+ self.assertFalse(isIPAddress('1.2.-3.4'))
+
+
+ def test_invalidPositive(self):
+ """
+ L{isIPAddress} should return C{False} for a string containing
+ positive decimal values greater than 255.
+ """
+ self.assertFalse(isIPAddress('256.0.0.0'))
+ self.assertFalse(isIPAddress('0.256.0.0'))
+ self.assertFalse(isIPAddress('0.0.256.0'))
+ self.assertFalse(isIPAddress('0.0.0.256'))
+ self.assertFalse(isIPAddress('256.256.256.256'))
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_adbapi.py b/vendor/Twisted-10.0.0/twisted/test/test_adbapi.py
new file mode 100644
index 0000000000..5e887cbe8e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_adbapi.py
@@ -0,0 +1,774 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Tests for twisted.enterprise.adbapi.
+"""
+
+from twisted.trial import unittest
+
+import os, stat, new
+
+from twisted.enterprise.adbapi import ConnectionPool, ConnectionLost, safe
+from twisted.enterprise.adbapi import Connection, Transaction
+from twisted.enterprise.adbapi import _unreleasedVersion
+from twisted.internet import reactor, defer, interfaces
+from twisted.python.failure import Failure
+
+
+simple_table_schema = """
+CREATE TABLE simple (
+ x integer
+)
+"""
+
+
+class ADBAPITestBase:
+ """Test the asynchronous DB-API code."""
+
+ openfun_called = {}
+
+ if interfaces.IReactorThreads(reactor, None) is None:
+ skip = "ADB-API requires threads, no way to test without them"
+
+ def extraSetUp(self):
+ """
+ Set up the database and create a connection pool pointing at it.
+ """
+ self.startDB()
+ self.dbpool = self.makePool(cp_openfun=self.openfun)
+ self.dbpool.start()
+
+
+ def tearDown(self):
+ d = self.dbpool.runOperation('DROP TABLE simple')
+ d.addCallback(lambda res: self.dbpool.close())
+ d.addCallback(lambda res: self.stopDB())
+ return d
+
+ def openfun(self, conn):
+ self.openfun_called[conn] = True
+
+ def checkOpenfunCalled(self, conn=None):
+ if not conn:
+ self.failUnless(self.openfun_called)
+ else:
+ self.failUnless(self.openfun_called.has_key(conn))
+
+ def testPool(self):
+ d = self.dbpool.runOperation(simple_table_schema)
+ if self.test_failures:
+ d.addCallback(self._testPool_1_1)
+ d.addCallback(self._testPool_1_2)
+ d.addCallback(self._testPool_1_3)
+ d.addCallback(self._testPool_1_4)
+ d.addCallback(lambda res: self.flushLoggedErrors())
+ d.addCallback(self._testPool_2)
+ d.addCallback(self._testPool_3)
+ d.addCallback(self._testPool_4)
+ d.addCallback(self._testPool_5)
+ d.addCallback(self._testPool_6)
+ d.addCallback(self._testPool_7)
+ d.addCallback(self._testPool_8)
+ d.addCallback(self._testPool_9)
+ return d
+
+ def _testPool_1_1(self, res):
+ d = defer.maybeDeferred(self.dbpool.runQuery, "select * from NOTABLE")
+ d.addCallbacks(lambda res: self.fail('no exception'),
+ lambda f: None)
+ return d
+
+ def _testPool_1_2(self, res):
+ d = defer.maybeDeferred(self.dbpool.runOperation,
+ "deletexxx from NOTABLE")
+ d.addCallbacks(lambda res: self.fail('no exception'),
+ lambda f: None)
+ return d
+
+ def _testPool_1_3(self, res):
+ d = defer.maybeDeferred(self.dbpool.runInteraction,
+ self.bad_interaction)
+ d.addCallbacks(lambda res: self.fail('no exception'),
+ lambda f: None)
+ return d
+
+ def _testPool_1_4(self, res):
+ d = defer.maybeDeferred(self.dbpool.runWithConnection,
+ self.bad_withConnection)
+ d.addCallbacks(lambda res: self.fail('no exception'),
+ lambda f: None)
+ return d
+
+ def _testPool_2(self, res):
+ # verify simple table is empty
+ sql = "select count(1) from simple"
+ d = self.dbpool.runQuery(sql)
+ def _check(row):
+ self.failUnless(int(row[0][0]) == 0, "Interaction not rolled back")
+ self.checkOpenfunCalled()
+ d.addCallback(_check)
+ return d
+
+ def _testPool_3(self, res):
+ sql = "select count(1) from simple"
+ inserts = []
+ # add some rows to simple table (runOperation)
+ for i in range(self.num_iterations):
+ sql = "insert into simple(x) values(%d)" % i
+ inserts.append(self.dbpool.runOperation(sql))
+ d = defer.gatherResults(inserts)
+
+ def _select(res):
+ # make sure they were added (runQuery)
+ sql = "select x from simple order by x";
+ d = self.dbpool.runQuery(sql)
+ return d
+ d.addCallback(_select)
+
+ def _check(rows):
+ self.failUnless(len(rows) == self.num_iterations,
+ "Wrong number of rows")
+ for i in range(self.num_iterations):
+ self.failUnless(len(rows[i]) == 1, "Wrong size row")
+ self.failUnless(rows[i][0] == i, "Values not returned.")
+ d.addCallback(_check)
+
+ return d
+
+ def _testPool_4(self, res):
+ # runInteraction
+ d = self.dbpool.runInteraction(self.interaction)
+ d.addCallback(lambda res: self.assertEquals(res, "done"))
+ return d
+
+ def _testPool_5(self, res):
+ # withConnection
+ d = self.dbpool.runWithConnection(self.withConnection)
+ d.addCallback(lambda res: self.assertEquals(res, "done"))
+ return d
+
+ def _testPool_6(self, res):
+ # Test a withConnection cannot be closed
+ d = self.dbpool.runWithConnection(self.close_withConnection)
+ return d
+
+ def _testPool_7(self, res):
+ # give the pool a workout
+ ds = []
+ for i in range(self.num_iterations):
+ sql = "select x from simple where x = %d" % i
+ ds.append(self.dbpool.runQuery(sql))
+ dlist = defer.DeferredList(ds, fireOnOneErrback=True)
+ def _check(result):
+ for i in range(self.num_iterations):
+ self.failUnless(result[i][1][0][0] == i, "Value not returned")
+ dlist.addCallback(_check)
+ return dlist
+
+ def _testPool_8(self, res):
+ # now delete everything
+ ds = []
+ for i in range(self.num_iterations):
+ sql = "delete from simple where x = %d" % i
+ ds.append(self.dbpool.runOperation(sql))
+ dlist = defer.DeferredList(ds, fireOnOneErrback=True)
+ return dlist
+
+ def _testPool_9(self, res):
+ # verify simple table is empty
+ sql = "select count(1) from simple"
+ d = self.dbpool.runQuery(sql)
+ def _check(row):
+ self.failUnless(int(row[0][0]) == 0,
+ "Didn't successfully delete table contents")
+ self.checkConnect()
+ d.addCallback(_check)
+ return d
+
+ def checkConnect(self):
+ """Check the connect/disconnect synchronous calls."""
+ conn = self.dbpool.connect()
+ self.checkOpenfunCalled(conn)
+ curs = conn.cursor()
+ curs.execute("insert into simple(x) values(1)")
+ curs.execute("select x from simple")
+ res = curs.fetchall()
+ self.failUnlessEqual(len(res), 1)
+ self.failUnlessEqual(len(res[0]), 1)
+ self.failUnlessEqual(res[0][0], 1)
+ curs.execute("delete from simple")
+ curs.execute("select x from simple")
+ self.failUnlessEqual(len(curs.fetchall()), 0)
+ curs.close()
+ self.dbpool.disconnect(conn)
+
+ def interaction(self, transaction):
+ transaction.execute("select x from simple order by x")
+ for i in range(self.num_iterations):
+ row = transaction.fetchone()
+ self.failUnless(len(row) == 1, "Wrong size row")
+ self.failUnless(row[0] == i, "Value not returned.")
+ # should test this, but gadfly throws an exception instead
+ #self.failUnless(transaction.fetchone() is None, "Too many rows")
+ return "done"
+
+ def bad_interaction(self, transaction):
+ if self.can_rollback:
+ transaction.execute("insert into simple(x) values(0)")
+
+ transaction.execute("select * from NOTABLE")
+
+ def withConnection(self, conn):
+ curs = conn.cursor()
+ try:
+ curs.execute("select x from simple order by x")
+ for i in range(self.num_iterations):
+ row = curs.fetchone()
+ self.failUnless(len(row) == 1, "Wrong size row")
+ self.failUnless(row[0] == i, "Value not returned.")
+ # should test this, but gadfly throws an exception instead
+ #self.failUnless(transaction.fetchone() is None, "Too many rows")
+ finally:
+ curs.close()
+ return "done"
+
+ def close_withConnection(self, conn):
+ conn.close()
+
+ def bad_withConnection(self, conn):
+ curs = conn.cursor()
+ try:
+ curs.execute("select * from NOTABLE")
+ finally:
+ curs.close()
+
+
+class ReconnectTestBase:
+ """Test the asynchronous DB-API code with reconnect."""
+
+ if interfaces.IReactorThreads(reactor, None) is None:
+ skip = "ADB-API requires threads, no way to test without them"
+
+ def extraSetUp(self):
+ """
+ Skip the test if C{good_sql} is unavailable. Otherwise, set up the
+ database, create a connection pool pointed at it, and set up a simple
+ schema in it.
+ """
+ if self.good_sql is None:
+ raise unittest.SkipTest('no good sql for reconnect test')
+ self.startDB()
+ self.dbpool = self.makePool(cp_max=1, cp_reconnect=True,
+ cp_good_sql=self.good_sql)
+ self.dbpool.start()
+ return self.dbpool.runOperation(simple_table_schema)
+
+
+ def tearDown(self):
+ d = self.dbpool.runOperation('DROP TABLE simple')
+ d.addCallback(lambda res: self.dbpool.close())
+ d.addCallback(lambda res: self.stopDB())
+ return d
+
+ def testPool(self):
+ d = defer.succeed(None)
+ d.addCallback(self._testPool_1)
+ d.addCallback(self._testPool_2)
+ if not self.early_reconnect:
+ d.addCallback(self._testPool_3)
+ d.addCallback(self._testPool_4)
+ d.addCallback(self._testPool_5)
+ return d
+
+ def _testPool_1(self, res):
+ sql = "select count(1) from simple"
+ d = self.dbpool.runQuery(sql)
+ def _check(row):
+ self.failUnless(int(row[0][0]) == 0, "Table not empty")
+ d.addCallback(_check)
+ return d
+
+ def _testPool_2(self, res):
+ # reach in and close the connection manually
+ self.dbpool.connections.values()[0].close()
+
+ def _testPool_3(self, res):
+ sql = "select count(1) from simple"
+ d = defer.maybeDeferred(self.dbpool.runQuery, sql)
+ d.addCallbacks(lambda res: self.fail('no exception'),
+ lambda f: None)
+ return d
+
+ def _testPool_4(self, res):
+ sql = "select count(1) from simple"
+ d = self.dbpool.runQuery(sql)
+ def _check(row):
+ self.failUnless(int(row[0][0]) == 0, "Table not empty")
+ d.addCallback(_check)
+ return d
+
+ def _testPool_5(self, res):
+ self.flushLoggedErrors()
+ sql = "select * from NOTABLE" # bad sql
+ d = defer.maybeDeferred(self.dbpool.runQuery, sql)
+ d.addCallbacks(lambda res: self.fail('no exception'),
+ lambda f: self.failIf(f.check(ConnectionLost)))
+ return d
+
+
+class DBTestConnector:
+ """A class which knows how to test for the presence of
+ and establish a connection to a relational database.
+
+ To enable test cases which use a central, system database,
+ you must create a database named DB_NAME with a user DB_USER
+ and password DB_PASS with full access rights to database DB_NAME.
+ """
+
+ TEST_PREFIX = None # used for creating new test cases
+
+ DB_NAME = "twisted_test"
+ DB_USER = 'twisted_test'
+ DB_PASS = 'twisted_test'
+
+ DB_DIR = None # directory for database storage
+
+ nulls_ok = True # nulls supported
+ trailing_spaces_ok = True # trailing spaces in strings preserved
+ can_rollback = True # rollback supported
+ test_failures = True # test bad sql?
+ escape_slashes = True # escape \ in sql?
+ good_sql = ConnectionPool.good_sql
+ early_reconnect = True # cursor() will fail on closed connection
+ can_clear = True # can try to clear out tables when starting
+
+ num_iterations = 50 # number of iterations for test loops
+ # (lower this for slow db's)
+
+ def setUp(self):
+ self.DB_DIR = self.mktemp()
+ os.mkdir(self.DB_DIR)
+ if not self.can_connect():
+ raise unittest.SkipTest('%s: Cannot access db' % self.TEST_PREFIX)
+ return self.extraSetUp()
+
+ def can_connect(self):
+ """Return true if this database is present on the system
+ and can be used in a test."""
+ raise NotImplementedError()
+
+ def startDB(self):
+ """Take any steps needed to bring database up."""
+ pass
+
+ def stopDB(self):
+ """Bring database down, if needed."""
+ pass
+
+ def makePool(self, **newkw):
+ """Create a connection pool with additional keyword arguments."""
+ args, kw = self.getPoolArgs()
+ kw = kw.copy()
+ kw.update(newkw)
+ return ConnectionPool(*args, **kw)
+
+ def getPoolArgs(self):
+ """Return a tuple (args, kw) of list and keyword arguments
+ that need to be passed to ConnectionPool to create a connection
+ to this database."""
+ raise NotImplementedError()
+
+class GadflyConnector(DBTestConnector):
+ TEST_PREFIX = 'Gadfly'
+
+ nulls_ok = False
+ can_rollback = False
+ escape_slashes = False
+ good_sql = 'select * from simple where 1=0'
+
+ num_iterations = 1 # slow
+
+ def can_connect(self):
+ try: import gadfly
+ except: return False
+ if not getattr(gadfly, 'connect', None):
+ gadfly.connect = gadfly.gadfly
+ return True
+
+ def startDB(self):
+ import gadfly
+ conn = gadfly.gadfly()
+ conn.startup(self.DB_NAME, self.DB_DIR)
+
+ # gadfly seems to want us to create something to get the db going
+ cursor = conn.cursor()
+ cursor.execute("create table x (x integer)")
+ conn.commit()
+ conn.close()
+
+ def getPoolArgs(self):
+ args = ('gadfly', self.DB_NAME, self.DB_DIR)
+ kw = {'cp_max': 1}
+ return args, kw
+
+class SQLiteConnector(DBTestConnector):
+ TEST_PREFIX = 'SQLite'
+
+ escape_slashes = False
+
+ num_iterations = 1 # slow
+
+ def can_connect(self):
+ try: import sqlite
+ except: return False
+ return True
+
+ def startDB(self):
+ self.database = os.path.join(self.DB_DIR, self.DB_NAME)
+ if os.path.exists(self.database):
+ os.unlink(self.database)
+
+ def getPoolArgs(self):
+ args = ('sqlite',)
+ kw = {'database': self.database, 'cp_max': 1}
+ return args, kw
+
+class PyPgSQLConnector(DBTestConnector):
+ TEST_PREFIX = "PyPgSQL"
+
+ def can_connect(self):
+ try: from pyPgSQL import PgSQL
+ except: return False
+ try:
+ conn = PgSQL.connect(database=self.DB_NAME, user=self.DB_USER,
+ password=self.DB_PASS)
+ conn.close()
+ return True
+ except:
+ return False
+
+ def getPoolArgs(self):
+ args = ('pyPgSQL.PgSQL',)
+ kw = {'database': self.DB_NAME, 'user': self.DB_USER,
+ 'password': self.DB_PASS, 'cp_min': 0}
+ return args, kw
+
+class PsycopgConnector(DBTestConnector):
+ TEST_PREFIX = 'Psycopg'
+
+ def can_connect(self):
+ try: import psycopg
+ except: return False
+ try:
+ conn = psycopg.connect(database=self.DB_NAME, user=self.DB_USER,
+ password=self.DB_PASS)
+ conn.close()
+ return True
+ except:
+ return False
+
+ def getPoolArgs(self):
+ args = ('psycopg',)
+ kw = {'database': self.DB_NAME, 'user': self.DB_USER,
+ 'password': self.DB_PASS, 'cp_min': 0}
+ return args, kw
+
+class MySQLConnector(DBTestConnector):
+ TEST_PREFIX = 'MySQL'
+
+ trailing_spaces_ok = False
+ can_rollback = False
+ early_reconnect = False
+
+ def can_connect(self):
+ try: import MySQLdb
+ except: return False
+ try:
+ conn = MySQLdb.connect(db=self.DB_NAME, user=self.DB_USER,
+ passwd=self.DB_PASS)
+ conn.close()
+ return True
+ except:
+ return False
+
+ def getPoolArgs(self):
+ args = ('MySQLdb',)
+ kw = {'db': self.DB_NAME, 'user': self.DB_USER, 'passwd': self.DB_PASS}
+ return args, kw
+
+class FirebirdConnector(DBTestConnector):
+ TEST_PREFIX = 'Firebird'
+
+ test_failures = False # failure testing causes problems
+ escape_slashes = False
+ good_sql = None # firebird doesn't handle failed sql well
+ can_clear = False # firebird is not so good
+
+ num_iterations = 5 # slow
+
+ def can_connect(self):
+ try: import kinterbasdb
+ except: return False
+ try:
+ self.startDB()
+ self.stopDB()
+ return True
+ except:
+ return False
+
+
+ def startDB(self):
+ import kinterbasdb
+ self.DB_NAME = os.path.join(self.DB_DIR, DBTestConnector.DB_NAME)
+ os.chmod(self.DB_DIR, stat.S_IRWXU + stat.S_IRWXG + stat.S_IRWXO)
+ sql = 'create database "%s" user "%s" password "%s"'
+ sql %= (self.DB_NAME, self.DB_USER, self.DB_PASS);
+ conn = kinterbasdb.create_database(sql)
+ conn.close()
+
+
+ def getPoolArgs(self):
+ args = ('kinterbasdb',)
+ kw = {'database': self.DB_NAME, 'host': '127.0.0.1',
+ 'user': self.DB_USER, 'password': self.DB_PASS}
+ return args, kw
+
+ def stopDB(self):
+ import kinterbasdb
+ conn = kinterbasdb.connect(database=self.DB_NAME,
+ host='127.0.0.1', user=self.DB_USER,
+ password=self.DB_PASS)
+ conn.drop_database()
+
+def makeSQLTests(base, suffix, globals):
+ """
+ Make a test case for every db connector which can connect.
+
+ @param base: Base class for test case. Additional base classes
+ will be a DBConnector subclass and unittest.TestCase
+ @param suffix: A suffix used to create test case names. Prefixes
+ are defined in the DBConnector subclasses.
+ """
+ connectors = [GadflyConnector, SQLiteConnector, PyPgSQLConnector,
+ PsycopgConnector, MySQLConnector, FirebirdConnector]
+ for connclass in connectors:
+ name = connclass.TEST_PREFIX + suffix
+ klass = new.classobj(name, (connclass, base, unittest.TestCase), base.__dict__)
+ globals[name] = klass
+
+# GadflyADBAPITestCase SQLiteADBAPITestCase PyPgSQLADBAPITestCase
+# PsycopgADBAPITestCase MySQLADBAPITestCase FirebirdADBAPITestCase
+makeSQLTests(ADBAPITestBase, 'ADBAPITestCase', globals())
+
+# GadflyReconnectTestCase SQLiteReconnectTestCase PyPgSQLReconnectTestCase
+# PsycopgReconnectTestCase MySQLReconnectTestCase FirebirdReconnectTestCase
+makeSQLTests(ReconnectTestBase, 'ReconnectTestCase', globals())
+
+
+
+class DeprecationTestCase(unittest.TestCase):
+ """
+ Test deprecations in twisted.enterprise.adbapi
+ """
+
+ def test_safe(self):
+ """
+ Test deprecation of twisted.enterprise.adbapi.safe()
+ """
+ result = self.callDeprecated(_unreleasedVersion,
+ safe, "test'")
+
+ # make sure safe still behaves like the original
+ self.assertEqual(result, "test''")
+
+
+
+class FakePool(object):
+ """
+ A fake L{ConnectionPool} for tests.
+
+ @ivar connectionFactory: factory for making connections returned by the
+ C{connect} method.
+ @type connectionFactory: any callable
+ """
+ reconnect = True
+ noisy = True
+
+ def __init__(self, connectionFactory):
+ self.connectionFactory = connectionFactory
+
+
+ def connect(self):
+ """
+ Return an instance of C{self.connectionFactory}.
+ """
+ return self.connectionFactory()
+
+
+ def disconnect(self, connection):
+ """
+ Do nothing.
+ """
+
+
+
+class ConnectionTestCase(unittest.TestCase):
+ """
+ Tests for the L{Connection} class.
+ """
+
+ def test_rollbackErrorLogged(self):
+ """
+ If an error happens during rollback, L{ConnectionLost} is raised but
+ the original error is logged.
+ """
+ class ConnectionRollbackRaise(object):
+ def rollback(self):
+ raise RuntimeError("problem!")
+
+ pool = FakePool(ConnectionRollbackRaise)
+ connection = Connection(pool)
+ self.assertRaises(ConnectionLost, connection.rollback)
+ errors = self.flushLoggedErrors(RuntimeError)
+ self.assertEquals(len(errors), 1)
+ self.assertEquals(errors[0].value.args[0], "problem!")
+
+
+
+class TransactionTestCase(unittest.TestCase):
+ """
+ Tests for the L{Transaction} class.
+ """
+
+ def test_reopenLogErrorIfReconnect(self):
+ """
+ If the cursor creation raises an error in L{Transaction.reopen}, it
+ reconnects but log the error occurred.
+ """
+ class ConnectionCursorRaise(object):
+ count = 0
+
+ def reconnect(self):
+ pass
+
+ def cursor(self):
+ if self.count == 0:
+ self.count += 1
+ raise RuntimeError("problem!")
+
+ pool = FakePool(None)
+ transaction = Transaction(pool, ConnectionCursorRaise())
+ transaction.reopen()
+ errors = self.flushLoggedErrors(RuntimeError)
+ self.assertEquals(len(errors), 1)
+ self.assertEquals(errors[0].value.args[0], "problem!")
+
+
+
+class NonThreadPool(object):
+ def callInThreadWithCallback(self, onResult, f, *a, **kw):
+ success = True
+ try:
+ result = f(*a, **kw)
+ except Exception, e:
+ success = False
+ result = Failure()
+ onResult(success, result)
+
+
+
+class DummyConnectionPool(ConnectionPool):
+ """
+ A testable L{ConnectionPool};
+ """
+ threadpool = NonThreadPool()
+
+ def __init__(self):
+ """
+ Don't forward init call.
+ """
+
+
+
+class ConnectionPoolTestCase(unittest.TestCase):
+ """
+ Unit tests for L{ConnectionPool}.
+ """
+
+ def test_runWithConnectionRaiseOriginalError(self):
+ """
+ If rollback fails, L{ConnectionPool.runWithConnection} raises the
+ original exception and log the error of the rollback.
+ """
+ class ConnectionRollbackRaise(object):
+ def __init__(self, pool):
+ pass
+
+ def rollback(self):
+ raise RuntimeError("problem!")
+
+ def raisingFunction(connection):
+ raise ValueError("foo")
+
+ pool = DummyConnectionPool()
+ pool.connectionFactory = ConnectionRollbackRaise
+ d = pool.runWithConnection(raisingFunction)
+ d = self.assertFailure(d, ValueError)
+ def cbFailed(ignored):
+ errors = self.flushLoggedErrors(RuntimeError)
+ self.assertEquals(len(errors), 1)
+ self.assertEquals(errors[0].value.args[0], "problem!")
+ d.addCallback(cbFailed)
+ return d
+
+
+ def test_closeLogError(self):
+ """
+ L{ConnectionPool._close} logs exceptions.
+ """
+ class ConnectionCloseRaise(object):
+ def close(self):
+ raise RuntimeError("problem!")
+
+ pool = DummyConnectionPool()
+ pool._close(ConnectionCloseRaise())
+
+ errors = self.flushLoggedErrors(RuntimeError)
+ self.assertEquals(len(errors), 1)
+ self.assertEquals(errors[0].value.args[0], "problem!")
+
+
+ def test_runWithInteractionRaiseOriginalError(self):
+ """
+ If rollback fails, L{ConnectionPool.runInteraction} raises the
+ original exception and log the error of the rollback.
+ """
+ class ConnectionRollbackRaise(object):
+ def __init__(self, pool):
+ pass
+
+ def rollback(self):
+ raise RuntimeError("problem!")
+
+ class DummyTransaction(object):
+ def __init__(self, pool, connection):
+ pass
+
+ def raisingFunction(transaction):
+ raise ValueError("foo")
+
+ pool = DummyConnectionPool()
+ pool.connectionFactory = ConnectionRollbackRaise
+ pool.transactionFactory = DummyTransaction
+
+ d = pool.runInteraction(raisingFunction)
+ d = self.assertFailure(d, ValueError)
+ def cbFailed(ignored):
+ errors = self.flushLoggedErrors(RuntimeError)
+ self.assertEquals(len(errors), 1)
+ self.assertEquals(errors[0].value.args[0], "problem!")
+ d.addCallback(cbFailed)
+ return d
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_amp.py b/vendor/Twisted-10.0.0/twisted/test/test_amp.py
new file mode 100644
index 0000000000..db0a493abe
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_amp.py
@@ -0,0 +1,2555 @@
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.protocols.amp}.
+"""
+
+from zope.interface.verify import verifyObject
+
+from twisted.python.util import setIDFunction
+from twisted.python import filepath
+from twisted.python.failure import Failure
+from twisted.protocols import amp
+from twisted.trial import unittest
+from twisted.internet import protocol, defer, error, reactor, interfaces
+from twisted.test import iosim
+from twisted.test.proto_helpers import StringTransport
+
+try:
+ from twisted.internet import ssl
+except ImportError:
+ ssl = None
+if ssl and not ssl.supported:
+ ssl = None
+
+if ssl is None:
+ skipSSL = "SSL not available"
+else:
+ skipSSL = None
+
+
+class TestProto(protocol.Protocol):
+ """
+ A trivial protocol for use in testing where a L{Protocol} is expected.
+
+ @ivar instanceId: the id of this instance
+ @ivar onConnLost: deferred that will fired when the connection is lost
+ @ivar dataToSend: data to send on the protocol
+ """
+
+ instanceCount = 0
+
+ def __init__(self, onConnLost, dataToSend):
+ self.onConnLost = onConnLost
+ self.dataToSend = dataToSend
+ self.instanceId = TestProto.instanceCount
+ TestProto.instanceCount = TestProto.instanceCount + 1
+
+ def connectionMade(self):
+ self.data = []
+ self.transport.write(self.dataToSend)
+
+ def dataReceived(self, bytes):
+ self.data.append(bytes)
+ # self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ self.onConnLost.callback(self.data)
+
+
+ def __repr__(self):
+ """
+ Custom repr for testing to avoid coupling amp tests with repr from
+ L{Protocol}
+
+ Returns a string which contains a unique identifier that can be looked
+ up using the instanceId property::
+
+ <TestProto #3>
+ """
+ return "<TestProto #%d>" % (self.instanceId,)
+
+
+
+class SimpleSymmetricProtocol(amp.AMP):
+
+ def sendHello(self, text):
+ return self.callRemoteString(
+ "hello",
+ hello=text)
+
+ def amp_HELLO(self, box):
+ return amp.Box(hello=box['hello'])
+
+ def amp_HOWDOYOUDO(self, box):
+ return amp.QuitBox(howdoyoudo='world')
+
+
+
+class UnfriendlyGreeting(Exception):
+ """Greeting was insufficiently kind.
+ """
+
+class DeathThreat(Exception):
+ """Greeting was insufficiently kind.
+ """
+
+class UnknownProtocol(Exception):
+ """Asked to switch to the wrong protocol.
+ """
+
+
+class TransportPeer(amp.Argument):
+ # this serves as some informal documentation for how to get variables from
+ # the protocol or your environment and pass them to methods as arguments.
+ def retrieve(self, d, name, proto):
+ return ''
+
+ def fromStringProto(self, notAString, proto):
+ return proto.transport.getPeer()
+
+ def toBox(self, name, strings, objects, proto):
+ return
+
+
+
+class Hello(amp.Command):
+
+ commandName = 'hello'
+
+ arguments = [('hello', amp.String()),
+ ('optional', amp.Boolean(optional=True)),
+ ('print', amp.Unicode(optional=True)),
+ ('from', TransportPeer(optional=True)),
+ ('mixedCase', amp.String(optional=True)),
+ ('dash-arg', amp.String(optional=True)),
+ ('underscore_arg', amp.String(optional=True))]
+
+ response = [('hello', amp.String()),
+ ('print', amp.Unicode(optional=True))]
+
+ errors = {UnfriendlyGreeting: 'UNFRIENDLY'}
+
+ fatalErrors = {DeathThreat: 'DEAD'}
+
+class NoAnswerHello(Hello):
+ commandName = Hello.commandName
+ requiresAnswer = False
+
+class FutureHello(amp.Command):
+ commandName = 'hello'
+
+ arguments = [('hello', amp.String()),
+ ('optional', amp.Boolean(optional=True)),
+ ('print', amp.Unicode(optional=True)),
+ ('from', TransportPeer(optional=True)),
+ ('bonus', amp.String(optional=True)), # addt'l arguments
+ # should generally be
+ # added at the end, and
+ # be optional...
+ ]
+
+ response = [('hello', amp.String()),
+ ('print', amp.Unicode(optional=True))]
+
+ errors = {UnfriendlyGreeting: 'UNFRIENDLY'}
+
+class WTF(amp.Command):
+ """
+ An example of an invalid command.
+ """
+
+
+class BrokenReturn(amp.Command):
+ """ An example of a perfectly good command, but the handler is going to return
+ None...
+ """
+
+ commandName = 'broken_return'
+
+class Goodbye(amp.Command):
+ # commandName left blank on purpose: this tests implicit command names.
+ response = [('goodbye', amp.String())]
+ responseType = amp.QuitBox
+
+class Howdoyoudo(amp.Command):
+ commandName = 'howdoyoudo'
+ # responseType = amp.QuitBox
+
+class WaitForever(amp.Command):
+ commandName = 'wait_forever'
+
+class GetList(amp.Command):
+ commandName = 'getlist'
+ arguments = [('length', amp.Integer())]
+ response = [('body', amp.AmpList([('x', amp.Integer())]))]
+
+class DontRejectMe(amp.Command):
+ commandName = 'dontrejectme'
+ arguments = [
+ ('magicWord', amp.Unicode()),
+ ('list', amp.AmpList([('name', amp.Unicode())], optional=True)),
+ ]
+ response = [('response', amp.Unicode())]
+
+class SecuredPing(amp.Command):
+ # XXX TODO: actually make this refuse to send over an insecure connection
+ response = [('pinged', amp.Boolean())]
+
+class TestSwitchProto(amp.ProtocolSwitchCommand):
+ commandName = 'Switch-Proto'
+
+ arguments = [
+ ('name', amp.String()),
+ ]
+ errors = {UnknownProtocol: 'UNKNOWN'}
+
+class SingleUseFactory(protocol.ClientFactory):
+ def __init__(self, proto):
+ self.proto = proto
+ self.proto.factory = self
+
+ def buildProtocol(self, addr):
+ p, self.proto = self.proto, None
+ return p
+
+ reasonFailed = None
+
+ def clientConnectionFailed(self, connector, reason):
+ self.reasonFailed = reason
+ return
+
+THING_I_DONT_UNDERSTAND = 'gwebol nargo'
+class ThingIDontUnderstandError(Exception):
+ pass
+
+class FactoryNotifier(amp.AMP):
+ factory = None
+ def connectionMade(self):
+ if self.factory is not None:
+ self.factory.theProto = self
+ if hasattr(self.factory, 'onMade'):
+ self.factory.onMade.callback(None)
+
+ def emitpong(self):
+ from twisted.internet.interfaces import ISSLTransport
+ if not ISSLTransport.providedBy(self.transport):
+ raise DeathThreat("only send secure pings over secure channels")
+ return {'pinged': True}
+ SecuredPing.responder(emitpong)
+
+
+class SimpleSymmetricCommandProtocol(FactoryNotifier):
+ maybeLater = None
+ def __init__(self, onConnLost=None):
+ amp.AMP.__init__(self)
+ self.onConnLost = onConnLost
+
+ def sendHello(self, text):
+ return self.callRemote(Hello, hello=text)
+
+ def sendUnicodeHello(self, text, translation):
+ return self.callRemote(Hello, hello=text, Print=translation)
+
+ greeted = False
+
+ def cmdHello(self, hello, From, optional=None, Print=None,
+ mixedCase=None, dash_arg=None, underscore_arg=None):
+ assert From == self.transport.getPeer()
+ if hello == THING_I_DONT_UNDERSTAND:
+ raise ThingIDontUnderstandError()
+ if hello.startswith('fuck'):
+ raise UnfriendlyGreeting("Don't be a dick.")
+ if hello == 'die':
+ raise DeathThreat("aieeeeeeeee")
+ result = dict(hello=hello)
+ if Print is not None:
+ result.update(dict(Print=Print))
+ self.greeted = True
+ return result
+ Hello.responder(cmdHello)
+
+ def cmdGetlist(self, length):
+ return {'body': [dict(x=1)] * length}
+ GetList.responder(cmdGetlist)
+
+ def okiwont(self, magicWord, list):
+ return dict(response=u'%s accepted' % (list[0]['name']))
+ DontRejectMe.responder(okiwont)
+
+ def waitforit(self):
+ self.waiting = defer.Deferred()
+ return self.waiting
+ WaitForever.responder(waitforit)
+
+ def howdo(self):
+ return dict(howdoyoudo='world')
+ Howdoyoudo.responder(howdo)
+
+ def saybye(self):
+ return dict(goodbye="everyone")
+ Goodbye.responder(saybye)
+
+ def switchToTestProtocol(self, fail=False):
+ if fail:
+ name = 'no-proto'
+ else:
+ name = 'test-proto'
+ p = TestProto(self.onConnLost, SWITCH_CLIENT_DATA)
+ return self.callRemote(
+ TestSwitchProto,
+ SingleUseFactory(p), name=name).addCallback(lambda ign: p)
+
+ def switchit(self, name):
+ if name == 'test-proto':
+ return TestProto(self.onConnLost, SWITCH_SERVER_DATA)
+ raise UnknownProtocol(name)
+ TestSwitchProto.responder(switchit)
+
+ def donothing(self):
+ return None
+ BrokenReturn.responder(donothing)
+
+
+class DeferredSymmetricCommandProtocol(SimpleSymmetricCommandProtocol):
+ def switchit(self, name):
+ if name == 'test-proto':
+ self.maybeLaterProto = TestProto(self.onConnLost, SWITCH_SERVER_DATA)
+ self.maybeLater = defer.Deferred()
+ return self.maybeLater
+ raise UnknownProtocol(name)
+ TestSwitchProto.responder(switchit)
+
+class BadNoAnswerCommandProtocol(SimpleSymmetricCommandProtocol):
+ def badResponder(self, hello, From, optional=None, Print=None,
+ mixedCase=None, dash_arg=None, underscore_arg=None):
+ """
+ This responder does nothing and forgets to return a dictionary.
+ """
+ NoAnswerHello.responder(badResponder)
+
+class NoAnswerCommandProtocol(SimpleSymmetricCommandProtocol):
+ def goodNoAnswerResponder(self, hello, From, optional=None, Print=None,
+ mixedCase=None, dash_arg=None, underscore_arg=None):
+ return dict(hello=hello+"-noanswer")
+ NoAnswerHello.responder(goodNoAnswerResponder)
+
+def connectedServerAndClient(ServerClass=SimpleSymmetricProtocol,
+ ClientClass=SimpleSymmetricProtocol,
+ *a, **kw):
+ """Returns a 3-tuple: (client, server, pump)
+ """
+ return iosim.connectedServerAndClient(
+ ServerClass, ClientClass,
+ *a, **kw)
+
+class TotallyDumbProtocol(protocol.Protocol):
+ buf = ''
+ def dataReceived(self, data):
+ self.buf += data
+
+class LiteralAmp(amp.AMP):
+ def __init__(self):
+ self.boxes = []
+
+ def ampBoxReceived(self, box):
+ self.boxes.append(box)
+ return
+
+class ParsingTest(unittest.TestCase):
+
+ def test_booleanValues(self):
+ """
+ Verify that the Boolean parser parses 'True' and 'False', but nothing
+ else.
+ """
+ b = amp.Boolean()
+ self.assertEquals(b.fromString("True"), True)
+ self.assertEquals(b.fromString("False"), False)
+ self.assertRaises(TypeError, b.fromString, "ninja")
+ self.assertRaises(TypeError, b.fromString, "true")
+ self.assertRaises(TypeError, b.fromString, "TRUE")
+ self.assertEquals(b.toString(True), 'True')
+ self.assertEquals(b.toString(False), 'False')
+
+ def test_pathValueRoundTrip(self):
+ """
+ Verify the 'Path' argument can parse and emit a file path.
+ """
+ fp = filepath.FilePath(self.mktemp())
+ p = amp.Path()
+ s = p.toString(fp)
+ v = p.fromString(s)
+ self.assertNotIdentical(fp, v) # sanity check
+ self.assertEquals(fp, v)
+
+
+ def test_sillyEmptyThing(self):
+ """
+ Test that empty boxes raise an error; they aren't supposed to be sent
+ on purpose.
+ """
+ a = amp.AMP()
+ return self.assertRaises(amp.NoEmptyBoxes, a.ampBoxReceived, amp.Box())
+
+
+ def test_ParsingRoundTrip(self):
+ """
+ Verify that various kinds of data make it through the encode/parse
+ round-trip unharmed.
+ """
+ c, s, p = connectedServerAndClient(ClientClass=LiteralAmp,
+ ServerClass=LiteralAmp)
+
+ SIMPLE = ('simple', 'test')
+ CE = ('ceq', ': ')
+ CR = ('crtest', 'test\r')
+ LF = ('lftest', 'hello\n')
+ NEWLINE = ('newline', 'test\r\none\r\ntwo')
+ NEWLINE2 = ('newline2', 'test\r\none\r\n two')
+ BLANKLINE = ('newline3', 'test\r\n\r\nblank\r\n\r\nline')
+ BODYTEST = ('body', 'blah\r\n\r\ntesttest')
+
+ testData = [
+ [SIMPLE],
+ [SIMPLE, BODYTEST],
+ [SIMPLE, CE],
+ [SIMPLE, CR],
+ [SIMPLE, CE, CR, LF],
+ [CE, CR, LF],
+ [SIMPLE, NEWLINE, CE, NEWLINE2],
+ [BODYTEST, SIMPLE, NEWLINE]
+ ]
+
+ for test in testData:
+ jb = amp.Box()
+ jb.update(dict(test))
+ jb._sendTo(c)
+ p.flush()
+ self.assertEquals(s.boxes[-1], jb)
+
+
+
+class FakeLocator(object):
+ """
+ This is a fake implementation of the interface implied by
+ L{CommandLocator}.
+ """
+ def __init__(self):
+ """
+ Remember the given keyword arguments as a set of responders.
+ """
+ self.commands = {}
+
+
+ def locateResponder(self, commandName):
+ """
+ Look up and return a function passed as a keyword argument of the given
+ name to the constructor.
+ """
+ return self.commands[commandName]
+
+
+class FakeSender:
+ """
+ This is a fake implementation of the 'box sender' interface implied by
+ L{AMP}.
+ """
+ def __init__(self):
+ """
+ Create a fake sender and initialize the list of received boxes and
+ unhandled errors.
+ """
+ self.sentBoxes = []
+ self.unhandledErrors = []
+ self.expectedErrors = 0
+
+
+ def expectError(self):
+ """
+ Expect one error, so that the test doesn't fail.
+ """
+ self.expectedErrors += 1
+
+
+ def sendBox(self, box):
+ """
+ Accept a box, but don't do anything.
+ """
+ self.sentBoxes.append(box)
+
+
+ def unhandledError(self, failure):
+ """
+ Deal with failures by instantly re-raising them for easier debugging.
+ """
+ self.expectedErrors -= 1
+ if self.expectedErrors < 0:
+ failure.raiseException()
+ else:
+ self.unhandledErrors.append(failure)
+
+
+
+class CommandDispatchTests(unittest.TestCase):
+ """
+ The AMP CommandDispatcher class dispatches converts AMP boxes into commands
+ and responses using Command.responder decorator.
+
+ Note: Originally, AMP's factoring was such that many tests for this
+ functionality are now implemented as full round-trip tests in L{AMPTest}.
+ Future tests should be written at this level instead, to ensure API
+ compatibility and to provide more granular, readable units of test
+ coverage.
+ """
+
+ def setUp(self):
+ """
+ Create a dispatcher to use.
+ """
+ self.locator = FakeLocator()
+ self.sender = FakeSender()
+ self.dispatcher = amp.BoxDispatcher(self.locator)
+ self.dispatcher.startReceivingBoxes(self.sender)
+
+
+ def test_receivedAsk(self):
+ """
+ L{CommandDispatcher.ampBoxReceived} should locate the appropriate
+ command in its responder lookup, based on the '_ask' key.
+ """
+ received = []
+ def thunk(box):
+ received.append(box)
+ return amp.Box({"hello": "goodbye"})
+ input = amp.Box(_command="hello",
+ _ask="test-command-id",
+ hello="world")
+ self.locator.commands['hello'] = thunk
+ self.dispatcher.ampBoxReceived(input)
+ self.assertEquals(received, [input])
+
+
+ def test_sendUnhandledError(self):
+ """
+ L{CommandDispatcher} should relay its unhandled errors in responding to
+ boxes to its boxSender.
+ """
+ err = RuntimeError("something went wrong, oh no")
+ self.sender.expectError()
+ self.dispatcher.unhandledError(Failure(err))
+ self.assertEqual(len(self.sender.unhandledErrors), 1)
+ self.assertEqual(self.sender.unhandledErrors[0].value, err)
+
+
+ def test_unhandledSerializationError(self):
+ """
+ Errors during serialization ought to be relayed to the sender's
+ unhandledError method.
+ """
+ err = RuntimeError("something undefined went wrong")
+ def thunk(result):
+ class BrokenBox(amp.Box):
+ def _sendTo(self, proto):
+ raise err
+ return BrokenBox()
+ self.locator.commands['hello'] = thunk
+ input = amp.Box(_command="hello",
+ _ask="test-command-id",
+ hello="world")
+ self.sender.expectError()
+ self.dispatcher.ampBoxReceived(input)
+ self.assertEquals(len(self.sender.unhandledErrors), 1)
+ self.assertEquals(self.sender.unhandledErrors[0].value, err)
+
+
+ def test_callRemote(self):
+ """
+ L{CommandDispatcher.callRemote} should emit a properly formatted '_ask'
+ box to its boxSender and record an outstanding L{Deferred}. When a
+ corresponding '_answer' packet is received, the L{Deferred} should be
+ fired, and the results translated via the given L{Command}'s response
+ de-serialization.
+ """
+ D = self.dispatcher.callRemote(Hello, hello='world')
+ self.assertEquals(self.sender.sentBoxes,
+ [amp.AmpBox(_command="hello",
+ _ask="1",
+ hello="world")])
+ answers = []
+ D.addCallback(answers.append)
+ self.assertEquals(answers, [])
+ self.dispatcher.ampBoxReceived(amp.AmpBox({'hello': "yay",
+ 'print': "ignored",
+ '_answer': "1"}))
+ self.assertEquals(answers, [dict(hello="yay",
+ Print=u"ignored")])
+
+
+class SimpleGreeting(amp.Command):
+ """
+ A very simple greeting command that uses a few basic argument types.
+ """
+ commandName = 'simple'
+ arguments = [('greeting', amp.Unicode()),
+ ('cookie', amp.Integer())]
+ response = [('cookieplus', amp.Integer())]
+
+
+class TestLocator(amp.CommandLocator):
+ """
+ A locator which implements a responder to a 'hello' command.
+ """
+ def __init__(self):
+ self.greetings = []
+
+
+ def greetingResponder(self, greeting, cookie):
+ self.greetings.append((greeting, cookie))
+ return dict(cookieplus=cookie + 3)
+ greetingResponder = SimpleGreeting.responder(greetingResponder)
+
+
+
+class OverrideLocatorAMP(amp.AMP):
+ def __init__(self):
+ amp.AMP.__init__(self)
+ self.customResponder = object()
+ self.expectations = {"custom": self.customResponder}
+ self.greetings = []
+
+
+ def lookupFunction(self, name):
+ """
+ Override the deprecated lookupFunction function.
+ """
+ if name in self.expectations:
+ result = self.expectations[name]
+ return result
+ else:
+ return super(OverrideLocatorAMP, self).lookupFunction(name)
+
+
+ def greetingResponder(self, greeting, cookie):
+ self.greetings.append((greeting, cookie))
+ return dict(cookieplus=cookie + 3)
+ greetingResponder = SimpleGreeting.responder(greetingResponder)
+
+
+
+
+class CommandLocatorTests(unittest.TestCase):
+ """
+ The CommandLocator should enable users to specify responders to commands as
+ functions that take structured objects, annotated with metadata.
+ """
+
+ def test_responderDecorator(self):
+ """
+ A method on a L{CommandLocator} subclass decorated with a L{Command}
+ subclass's L{responder} decorator should be returned from
+ locateResponder, wrapped in logic to serialize and deserialize its
+ arguments.
+ """
+ locator = TestLocator()
+ responderCallable = locator.locateResponder("simple")
+ result = responderCallable(amp.Box(greeting="ni hao", cookie="5"))
+ def done(values):
+ self.assertEquals(values, amp.AmpBox(cookieplus='8'))
+ return result.addCallback(done)
+
+
+ def test_lookupFunctionDeprecatedOverride(self):
+ """
+ Subclasses which override locateResponder under its old name,
+ lookupFunction, should have the override invoked instead. (This tests
+ an AMP subclass, because in the version of the code that could invoke
+ this deprecated code path, there was no L{CommandLocator}.)
+ """
+ locator = OverrideLocatorAMP()
+ customResponderObject = self.assertWarns(
+ PendingDeprecationWarning,
+ "Override locateResponder, not lookupFunction.",
+ __file__, lambda : locator.locateResponder("custom"))
+ self.assertEquals(locator.customResponder, customResponderObject)
+ # Make sure upcalling works too
+ normalResponderObject = self.assertWarns(
+ PendingDeprecationWarning,
+ "Override locateResponder, not lookupFunction.",
+ __file__, lambda : locator.locateResponder("simple"))
+ result = normalResponderObject(amp.Box(greeting="ni hao", cookie="5"))
+ def done(values):
+ self.assertEquals(values, amp.AmpBox(cookieplus='8'))
+ return result.addCallback(done)
+
+
+ def test_lookupFunctionDeprecatedInvoke(self):
+ """
+ Invoking locateResponder under its old name, lookupFunction, should
+ emit a deprecation warning, but do the same thing.
+ """
+ locator = TestLocator()
+ responderCallable = self.assertWarns(
+ PendingDeprecationWarning,
+ "Call locateResponder, not lookupFunction.", __file__,
+ lambda : locator.lookupFunction("simple"))
+ result = responderCallable(amp.Box(greeting="ni hao", cookie="5"))
+ def done(values):
+ self.assertEquals(values, amp.AmpBox(cookieplus='8'))
+ return result.addCallback(done)
+
+
+
+SWITCH_CLIENT_DATA = 'Success!'
+SWITCH_SERVER_DATA = 'No, really. Success.'
+
+
+class BinaryProtocolTests(unittest.TestCase):
+ """
+ Tests for L{amp.BinaryBoxProtocol}.
+
+ @ivar _boxSender: After C{startReceivingBoxes} is called, the L{IBoxSender}
+ which was passed to it.
+ """
+
+ def setUp(self):
+ """
+ Keep track of all boxes received by this test in its capacity as an
+ L{IBoxReceiver} implementor.
+ """
+ self.boxes = []
+ self.data = []
+
+
+ def startReceivingBoxes(self, sender):
+ """
+ Implement L{IBoxReceiver.startReceivingBoxes} to just remember the
+ value passed in.
+ """
+ self._boxSender = sender
+
+
+ def ampBoxReceived(self, box):
+ """
+ A box was received by the protocol.
+ """
+ self.boxes.append(box)
+
+ stopReason = None
+ def stopReceivingBoxes(self, reason):
+ """
+ Record the reason that we stopped receiving boxes.
+ """
+ self.stopReason = reason
+
+
+ # fake ITransport
+ def getPeer(self):
+ return 'no peer'
+
+
+ def getHost(self):
+ return 'no host'
+
+
+ def write(self, data):
+ self.data.append(data)
+
+
+ def test_startReceivingBoxes(self):
+ """
+ When L{amp.BinaryBoxProtocol} is connected to a transport, it calls
+ C{startReceivingBoxes} on its L{IBoxReceiver} with itself as the
+ L{IBoxSender} parameter.
+ """
+ protocol = amp.BinaryBoxProtocol(self)
+ protocol.makeConnection(None)
+ self.assertIdentical(self._boxSender, protocol)
+
+
+ def test_sendBoxInStartReceivingBoxes(self):
+ """
+ The L{IBoxReceiver} which is started when L{amp.BinaryBoxProtocol} is
+ connected to a transport can call C{sendBox} on the L{IBoxSender}
+ passed to it before C{startReceivingBoxes} returns and have that box
+ sent.
+ """
+ class SynchronouslySendingReceiver:
+ def startReceivingBoxes(self, sender):
+ sender.sendBox(amp.Box({'foo': 'bar'}))
+
+ transport = StringTransport()
+ protocol = amp.BinaryBoxProtocol(SynchronouslySendingReceiver())
+ protocol.makeConnection(transport)
+ self.assertEqual(
+ transport.value(),
+ '\x00\x03foo\x00\x03bar\x00\x00')
+
+
+ def test_receiveBoxStateMachine(self):
+ """
+ When a binary box protocol receives:
+ * a key
+ * a value
+ * an empty string
+ it should emit a box and send it to its boxReceiver.
+ """
+ a = amp.BinaryBoxProtocol(self)
+ a.stringReceived("hello")
+ a.stringReceived("world")
+ a.stringReceived("")
+ self.assertEquals(self.boxes, [amp.AmpBox(hello="world")])
+
+
+ def test_firstBoxFirstKeyExcessiveLength(self):
+ """
+ L{amp.BinaryBoxProtocol} drops its connection if the length prefix for
+ the first a key it receives is larger than 255.
+ """
+ transport = StringTransport()
+ protocol = amp.BinaryBoxProtocol(self)
+ protocol.makeConnection(transport)
+ protocol.dataReceived('\x01\x00')
+ self.assertTrue(transport.disconnecting)
+
+
+ def test_firstBoxSubsequentKeyExcessiveLength(self):
+ """
+ L{amp.BinaryBoxProtocol} drops its connection if the length prefix for
+ a subsequent key in the first box it receives is larger than 255.
+ """
+ transport = StringTransport()
+ protocol = amp.BinaryBoxProtocol(self)
+ protocol.makeConnection(transport)
+ protocol.dataReceived('\x00\x01k\x00\x01v')
+ self.assertFalse(transport.disconnecting)
+ protocol.dataReceived('\x01\x00')
+ self.assertTrue(transport.disconnecting)
+
+
+ def test_subsequentBoxFirstKeyExcessiveLength(self):
+ """
+ L{amp.BinaryBoxProtocol} drops its connection if the length prefix for
+ the first key in a subsequent box it receives is larger than 255.
+ """
+ transport = StringTransport()
+ protocol = amp.BinaryBoxProtocol(self)
+ protocol.makeConnection(transport)
+ protocol.dataReceived('\x00\x01k\x00\x01v\x00\x00')
+ self.assertFalse(transport.disconnecting)
+ protocol.dataReceived('\x01\x00')
+ self.assertTrue(transport.disconnecting)
+
+
+ def test_excessiveKeyFailure(self):
+ """
+ If L{amp.BinaryBoxProtocol} disconnects because it received a key
+ length prefix which was too large, the L{IBoxReceiver}'s
+ C{stopReceivingBoxes} method is called with a L{TooLong} failure.
+ """
+ protocol = amp.BinaryBoxProtocol(self)
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('\x01\x00')
+ protocol.connectionLost(
+ Failure(error.ConnectionDone("simulated connection done")))
+ self.stopReason.trap(amp.TooLong)
+ self.assertTrue(self.stopReason.value.isKey)
+ self.assertFalse(self.stopReason.value.isLocal)
+ self.assertIdentical(self.stopReason.value.value, None)
+ self.assertIdentical(self.stopReason.value.keyName, None)
+
+
+ def test_receiveBoxData(self):
+ """
+ When a binary box protocol receives the serialized form of an AMP box,
+ it should emit a similar box to its boxReceiver.
+ """
+ a = amp.BinaryBoxProtocol(self)
+ a.dataReceived(amp.Box({"testKey": "valueTest",
+ "anotherKey": "anotherValue"}).serialize())
+ self.assertEquals(self.boxes,
+ [amp.Box({"testKey": "valueTest",
+ "anotherKey": "anotherValue"})])
+
+
+ def test_receiveLongerBoxData(self):
+ """
+ An L{amp.BinaryBoxProtocol} can receive serialized AMP boxes with
+ values of up to (2 ** 16 - 1) bytes.
+ """
+ length = (2 ** 16 - 1)
+ value = 'x' * length
+ transport = StringTransport()
+ protocol = amp.BinaryBoxProtocol(self)
+ protocol.makeConnection(transport)
+ protocol.dataReceived(amp.Box({'k': value}).serialize())
+ self.assertEqual(self.boxes, [amp.Box({'k': value})])
+ self.assertFalse(transport.disconnecting)
+
+
+ def test_sendBox(self):
+ """
+ When a binary box protocol sends a box, it should emit the serialized
+ bytes of that box to its transport.
+ """
+ a = amp.BinaryBoxProtocol(self)
+ a.makeConnection(self)
+ aBox = amp.Box({"testKey": "valueTest",
+ "someData": "hello"})
+ a.makeConnection(self)
+ a.sendBox(aBox)
+ self.assertEquals(''.join(self.data), aBox.serialize())
+
+
+ def test_connectionLostStopSendingBoxes(self):
+ """
+ When a binary box protocol loses its connection, it should notify its
+ box receiver that it has stopped receiving boxes.
+ """
+ a = amp.BinaryBoxProtocol(self)
+ a.makeConnection(self)
+ aBox = amp.Box({"sample": "data"})
+ a.makeConnection(self)
+ connectionFailure = Failure(RuntimeError())
+ a.connectionLost(connectionFailure)
+ self.assertIdentical(self.stopReason, connectionFailure)
+
+
+ def test_protocolSwitch(self):
+ """
+ L{BinaryBoxProtocol} has the capacity to switch to a different protocol
+ on a box boundary. When a protocol is in the process of switching, it
+ cannot receive traffic.
+ """
+ otherProto = TestProto(None, "outgoing data")
+ test = self
+ class SwitchyReceiver:
+ switched = False
+ def startReceivingBoxes(self, sender):
+ pass
+ def ampBoxReceived(self, box):
+ test.assertFalse(self.switched,
+ "Should only receive one box!")
+ self.switched = True
+ a._lockForSwitch()
+ a._switchTo(otherProto)
+ a = amp.BinaryBoxProtocol(SwitchyReceiver())
+ anyOldBox = amp.Box({"include": "lots",
+ "of": "data"})
+ a.makeConnection(self)
+ # Include a 0-length box at the beginning of the next protocol's data,
+ # to make sure that AMP doesn't eat the data or try to deliver extra
+ # boxes either...
+ moreThanOneBox = anyOldBox.serialize() + "\x00\x00Hello, world!"
+ a.dataReceived(moreThanOneBox)
+ self.assertIdentical(otherProto.transport, self)
+ self.assertEquals("".join(otherProto.data), "\x00\x00Hello, world!")
+ self.assertEquals(self.data, ["outgoing data"])
+ a.dataReceived("more data")
+ self.assertEquals("".join(otherProto.data),
+ "\x00\x00Hello, world!more data")
+ self.assertRaises(amp.ProtocolSwitched, a.sendBox, anyOldBox)
+
+
+ def test_protocolSwitchInvalidStates(self):
+ """
+ In order to make sure the protocol never gets any invalid data sent
+ into the middle of a box, it must be locked for switching before it is
+ switched. It can only be unlocked if the switch failed, and attempting
+ to send a box while it is locked should raise an exception.
+ """
+ a = amp.BinaryBoxProtocol(self)
+ a.makeConnection(self)
+ sampleBox = amp.Box({"some": "data"})
+ a._lockForSwitch()
+ self.assertRaises(amp.ProtocolSwitched, a.sendBox, sampleBox)
+ a._unlockFromSwitch()
+ a.sendBox(sampleBox)
+ self.assertEquals(''.join(self.data), sampleBox.serialize())
+ a._lockForSwitch()
+ otherProto = TestProto(None, "outgoing data")
+ a._switchTo(otherProto)
+ self.assertRaises(amp.ProtocolSwitched, a._unlockFromSwitch)
+
+
+ def test_protocolSwitchLoseConnection(self):
+ """
+ When the protocol is switched, it should notify its nested protocol of
+ disconnection.
+ """
+ class Loser(protocol.Protocol):
+ reason = None
+ def connectionLost(self, reason):
+ self.reason = reason
+ connectionLoser = Loser()
+ a = amp.BinaryBoxProtocol(self)
+ a.makeConnection(self)
+ a._lockForSwitch()
+ a._switchTo(connectionLoser)
+ connectionFailure = Failure(RuntimeError())
+ a.connectionLost(connectionFailure)
+ self.assertEquals(connectionLoser.reason, connectionFailure)
+
+
+ def test_protocolSwitchLoseClientConnection(self):
+ """
+ When the protocol is switched, it should notify its nested client
+ protocol factory of disconnection.
+ """
+ class ClientLoser:
+ reason = None
+ def clientConnectionLost(self, connector, reason):
+ self.reason = reason
+ a = amp.BinaryBoxProtocol(self)
+ connectionLoser = protocol.Protocol()
+ clientLoser = ClientLoser()
+ a.makeConnection(self)
+ a._lockForSwitch()
+ a._switchTo(connectionLoser, clientLoser)
+ connectionFailure = Failure(RuntimeError())
+ a.connectionLost(connectionFailure)
+ self.assertEquals(clientLoser.reason, connectionFailure)
+
+
+
+class AMPTest(unittest.TestCase):
+
+ def test_interfaceDeclarations(self):
+ """
+ The classes in the amp module ought to implement the interfaces that
+ are declared for their benefit.
+ """
+ for interface, implementation in [(amp.IBoxSender, amp.BinaryBoxProtocol),
+ (amp.IBoxReceiver, amp.BoxDispatcher),
+ (amp.IResponderLocator, amp.CommandLocator),
+ (amp.IResponderLocator, amp.SimpleStringLocator),
+ (amp.IBoxSender, amp.AMP),
+ (amp.IBoxReceiver, amp.AMP),
+ (amp.IResponderLocator, amp.AMP)]:
+ self.failUnless(interface.implementedBy(implementation),
+ "%s does not implements(%s)" % (implementation, interface))
+
+
+ def test_helloWorld(self):
+ """
+ Verify that a simple command can be sent and its response received with
+ the simple low-level string-based API.
+ """
+ c, s, p = connectedServerAndClient()
+ L = []
+ HELLO = 'world'
+ c.sendHello(HELLO).addCallback(L.append)
+ p.flush()
+ self.assertEquals(L[0]['hello'], HELLO)
+
+
+ def test_wireFormatRoundTrip(self):
+ """
+ Verify that mixed-case, underscored and dashed arguments are mapped to
+ their python names properly.
+ """
+ c, s, p = connectedServerAndClient()
+ L = []
+ HELLO = 'world'
+ c.sendHello(HELLO).addCallback(L.append)
+ p.flush()
+ self.assertEquals(L[0]['hello'], HELLO)
+
+
+ def test_helloWorldUnicode(self):
+ """
+ Verify that unicode arguments can be encoded and decoded.
+ """
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ L = []
+ HELLO = 'world'
+ HELLO_UNICODE = 'wor\u1234ld'
+ c.sendUnicodeHello(HELLO, HELLO_UNICODE).addCallback(L.append)
+ p.flush()
+ self.assertEquals(L[0]['hello'], HELLO)
+ self.assertEquals(L[0]['Print'], HELLO_UNICODE)
+
+
+ def test_callRemoteStringRequiresAnswerFalse(self):
+ """
+ L{BoxDispatcher.callRemoteString} returns C{None} if C{requiresAnswer}
+ is C{False}.
+ """
+ c, s, p = connectedServerAndClient()
+ ret = c.callRemoteString("WTF", requiresAnswer=False)
+ self.assertIdentical(ret, None)
+
+
+ def test_unknownCommandLow(self):
+ """
+ Verify that unknown commands using low-level APIs will be rejected with an
+ error, but will NOT terminate the connection.
+ """
+ c, s, p = connectedServerAndClient()
+ L = []
+ def clearAndAdd(e):
+ """
+ You can't propagate the error...
+ """
+ e.trap(amp.UnhandledCommand)
+ return "OK"
+ c.callRemoteString("WTF").addErrback(clearAndAdd).addCallback(L.append)
+ p.flush()
+ self.assertEquals(L.pop(), "OK")
+ HELLO = 'world'
+ c.sendHello(HELLO).addCallback(L.append)
+ p.flush()
+ self.assertEquals(L[0]['hello'], HELLO)
+
+
+ def test_unknownCommandHigh(self):
+ """
+ Verify that unknown commands using high-level APIs will be rejected with an
+ error, but will NOT terminate the connection.
+ """
+ c, s, p = connectedServerAndClient()
+ L = []
+ def clearAndAdd(e):
+ """
+ You can't propagate the error...
+ """
+ e.trap(amp.UnhandledCommand)
+ return "OK"
+ c.callRemote(WTF).addErrback(clearAndAdd).addCallback(L.append)
+ p.flush()
+ self.assertEquals(L.pop(), "OK")
+ HELLO = 'world'
+ c.sendHello(HELLO).addCallback(L.append)
+ p.flush()
+ self.assertEquals(L[0]['hello'], HELLO)
+
+
+ def test_brokenReturnValue(self):
+ """
+ It can be very confusing if you write some code which responds to a
+ command, but gets the return value wrong. Most commonly you end up
+ returning None instead of a dictionary.
+
+ Verify that if that happens, the framework logs a useful error.
+ """
+ L = []
+ SimpleSymmetricCommandProtocol().dispatchCommand(
+ amp.AmpBox(_command=BrokenReturn.commandName)).addErrback(L.append)
+ blr = L[0].trap(amp.BadLocalReturn)
+ self.failUnlessIn('None', repr(L[0].value))
+
+
+ def test_unknownArgument(self):
+ """
+ Verify that unknown arguments are ignored, and not passed to a Python
+ function which can't accept them.
+ """
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ L = []
+ HELLO = 'world'
+ # c.sendHello(HELLO).addCallback(L.append)
+ c.callRemote(FutureHello,
+ hello=HELLO,
+ bonus="I'm not in the book!").addCallback(
+ L.append)
+ p.flush()
+ self.assertEquals(L[0]['hello'], HELLO)
+
+
+ def test_simpleReprs(self):
+ """
+ Verify that the various Box objects repr properly, for debugging.
+ """
+ self.assertEquals(type(repr(amp._SwitchBox('a'))), str)
+ self.assertEquals(type(repr(amp.QuitBox())), str)
+ self.assertEquals(type(repr(amp.AmpBox())), str)
+ self.failUnless("AmpBox" in repr(amp.AmpBox()))
+
+
+ def test_innerProtocolInRepr(self):
+ """
+ Verify that L{AMP} objects output their innerProtocol when set.
+ """
+ otherProto = TestProto(None, "outgoing data")
+ a = amp.AMP()
+ a.innerProtocol = otherProto
+ def fakeID(obj):
+ return {a: 0x1234}.get(obj, id(obj))
+ self.addCleanup(setIDFunction, setIDFunction(fakeID))
+
+ self.assertEquals(
+ repr(a), "<AMP inner <TestProto #%d> at 0x1234>" % (
+ otherProto.instanceId,))
+
+
+ def test_innerProtocolNotInRepr(self):
+ """
+ Verify that L{AMP} objects do not output 'inner' when no innerProtocol
+ is set.
+ """
+ a = amp.AMP()
+ def fakeID(obj):
+ return {a: 0x4321}.get(obj, id(obj))
+ self.addCleanup(setIDFunction, setIDFunction(fakeID))
+ self.assertEquals(repr(a), "<AMP at 0x4321>")
+
+
+ def test_simpleSSLRepr(self):
+ """
+ L{amp._TLSBox.__repr__} returns a string.
+ """
+ self.assertEquals(type(repr(amp._TLSBox())), str)
+
+ test_simpleSSLRepr.skip = skipSSL
+
+
+ def test_keyTooLong(self):
+ """
+ Verify that a key that is too long will immediately raise a synchronous
+ exception.
+ """
+ c, s, p = connectedServerAndClient()
+ L = []
+ x = "H" * (0xff+1)
+ tl = self.assertRaises(amp.TooLong,
+ c.callRemoteString, "Hello",
+ **{x: "hi"})
+ self.failUnless(tl.isKey)
+ self.failUnless(tl.isLocal)
+ self.failUnlessIdentical(tl.keyName, None)
+ self.failUnlessIdentical(tl.value, x)
+ self.failUnless(str(len(x)) in repr(tl))
+ self.failUnless("key" in repr(tl))
+
+
+ def test_valueTooLong(self):
+ """
+ Verify that attempting to send value longer than 64k will immediately
+ raise an exception.
+ """
+ c, s, p = connectedServerAndClient()
+ L = []
+ x = "H" * (0xffff+1)
+ tl = self.assertRaises(amp.TooLong, c.sendHello, x)
+ p.flush()
+ self.failIf(tl.isKey)
+ self.failUnless(tl.isLocal)
+ self.assertEquals(tl.keyName, 'hello')
+ self.failUnlessIdentical(tl.value, x)
+ self.failUnless(str(len(x)) in repr(tl))
+ self.failUnless("value" in repr(tl))
+ self.failUnless('hello' in repr(tl))
+
+
+ def test_helloWorldCommand(self):
+ """
+ Verify that a simple command can be sent and its response received with
+ the high-level value parsing API.
+ """
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ L = []
+ HELLO = 'world'
+ c.sendHello(HELLO).addCallback(L.append)
+ p.flush()
+ self.assertEquals(L[0]['hello'], HELLO)
+
+
+ def test_helloErrorHandling(self):
+ """
+ Verify that if a known error type is raised and handled, it will be
+ properly relayed to the other end of the connection and translated into
+ an exception, and no error will be logged.
+ """
+ L=[]
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ HELLO = 'fuck you'
+ c.sendHello(HELLO).addErrback(L.append)
+ p.flush()
+ L[0].trap(UnfriendlyGreeting)
+ self.assertEquals(str(L[0].value), "Don't be a dick.")
+
+
+ def test_helloFatalErrorHandling(self):
+ """
+ Verify that if a known, fatal error type is raised and handled, it will
+ be properly relayed to the other end of the connection and translated
+ into an exception, no error will be logged, and the connection will be
+ terminated.
+ """
+ L=[]
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ HELLO = 'die'
+ c.sendHello(HELLO).addErrback(L.append)
+ p.flush()
+ L.pop().trap(DeathThreat)
+ c.sendHello(HELLO).addErrback(L.append)
+ p.flush()
+ L.pop().trap(error.ConnectionDone)
+
+
+
+ def test_helloNoErrorHandling(self):
+ """
+ Verify that if an unknown error type is raised, it will be relayed to
+ the other end of the connection and translated into an exception, it
+ will be logged, and then the connection will be dropped.
+ """
+ L=[]
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ HELLO = THING_I_DONT_UNDERSTAND
+ c.sendHello(HELLO).addErrback(L.append)
+ p.flush()
+ ure = L.pop()
+ ure.trap(amp.UnknownRemoteError)
+ c.sendHello(HELLO).addErrback(L.append)
+ cl = L.pop()
+ cl.trap(error.ConnectionDone)
+ # The exception should have been logged.
+ self.failUnless(self.flushLoggedErrors(ThingIDontUnderstandError))
+
+
+
+ def test_lateAnswer(self):
+ """
+ Verify that a command that does not get answered until after the
+ connection terminates will not cause any errors.
+ """
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ L = []
+ HELLO = 'world'
+ c.callRemote(WaitForever).addErrback(L.append)
+ p.flush()
+ self.assertEquals(L, [])
+ s.transport.loseConnection()
+ p.flush()
+ L.pop().trap(error.ConnectionDone)
+ # Just make sure that it doesn't error...
+ s.waiting.callback({})
+ return s.waiting
+
+
+ def test_requiresNoAnswer(self):
+ """
+ Verify that a command that requires no answer is run.
+ """
+ L=[]
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ HELLO = 'world'
+ c.callRemote(NoAnswerHello, hello=HELLO)
+ p.flush()
+ self.failUnless(s.greeted)
+
+
+ def test_requiresNoAnswerFail(self):
+ """
+ Verify that commands sent after a failed no-answer request do not complete.
+ """
+ L=[]
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ HELLO = 'fuck you'
+ c.callRemote(NoAnswerHello, hello=HELLO)
+ p.flush()
+ # This should be logged locally.
+ self.failUnless(self.flushLoggedErrors(amp.RemoteAmpError))
+ HELLO = 'world'
+ c.callRemote(Hello, hello=HELLO).addErrback(L.append)
+ p.flush()
+ L.pop().trap(error.ConnectionDone)
+ self.failIf(s.greeted)
+
+
+ def test_noAnswerResponderBadAnswer(self):
+ """
+ Verify that responders of requiresAnswer=False commands have to return
+ a dictionary anyway.
+
+ (requiresAnswer is a hint from the _client_ - the server may be called
+ upon to answer commands in any case, if the client wants to know when
+ they complete.)
+ """
+ c, s, p = connectedServerAndClient(
+ ServerClass=BadNoAnswerCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ c.callRemote(NoAnswerHello, hello="hello")
+ p.flush()
+ le = self.flushLoggedErrors(amp.BadLocalReturn)
+ self.assertEquals(len(le), 1)
+
+
+ def test_noAnswerResponderAskedForAnswer(self):
+ """
+ Verify that responders with requiresAnswer=False will actually respond
+ if the client sets requiresAnswer=True. In other words, verify that
+ requiresAnswer is a hint honored only by the client.
+ """
+ c, s, p = connectedServerAndClient(
+ ServerClass=NoAnswerCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ L = []
+ c.callRemote(Hello, hello="Hello!").addCallback(L.append)
+ p.flush()
+ self.assertEquals(len(L), 1)
+ self.assertEquals(L, [dict(hello="Hello!-noanswer",
+ Print=None)]) # Optional response argument
+
+
+ def test_ampListCommand(self):
+ """
+ Test encoding of an argument that uses the AmpList encoding.
+ """
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ L = []
+ c.callRemote(GetList, length=10).addCallback(L.append)
+ p.flush()
+ values = L.pop().get('body')
+ self.assertEquals(values, [{'x': 1}] * 10)
+
+
+ def test_optionalAmpListOmitted(self):
+ """
+ Test that sending a command with an omitted AmpList argument that is
+ designated as optional does not raise an InvalidSignature error.
+ """
+ dontRejectMeCommand = DontRejectMe(magicWord=u'please')
+
+
+ def test_optionalAmpListPresent(self):
+ """
+ Sanity check that optional AmpList arguments are processed normally.
+ """
+ dontRejectMeCommand = DontRejectMe(magicWord=u'please',
+ list=[{'name': 'foo'}])
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+ L = []
+ c.callRemote(DontRejectMe, magicWord=u'please',
+ list=[{'name': 'foo'}]).addCallback(L.append)
+ p.flush()
+ response = L.pop().get('response')
+ self.assertEquals(response, 'foo accepted')
+
+
+ def test_failEarlyOnArgSending(self):
+ """
+ Verify that if we pass an invalid argument list (omitting an argument), an
+ exception will be raised.
+ """
+ okayCommand = Hello(hello="What?")
+ self.assertRaises(amp.InvalidSignature, Hello)
+
+
+ def test_doubleProtocolSwitch(self):
+ """
+ As a debugging aid, a protocol system should raise a
+ L{ProtocolSwitched} exception when asked to switch a protocol that is
+ already switched.
+ """
+ serverDeferred = defer.Deferred()
+ serverProto = SimpleSymmetricCommandProtocol(serverDeferred)
+ clientDeferred = defer.Deferred()
+ clientProto = SimpleSymmetricCommandProtocol(clientDeferred)
+ c, s, p = connectedServerAndClient(ServerClass=lambda: serverProto,
+ ClientClass=lambda: clientProto)
+ def switched(result):
+ self.assertRaises(amp.ProtocolSwitched, c.switchToTestProtocol)
+ self.testSucceeded = True
+ c.switchToTestProtocol().addCallback(switched)
+ p.flush()
+ self.failUnless(self.testSucceeded)
+
+
+ def test_protocolSwitch(self, switcher=SimpleSymmetricCommandProtocol,
+ spuriousTraffic=False,
+ spuriousError=False):
+ """
+ Verify that it is possible to switch to another protocol mid-connection and
+ send data to it successfully.
+ """
+ self.testSucceeded = False
+
+ serverDeferred = defer.Deferred()
+ serverProto = switcher(serverDeferred)
+ clientDeferred = defer.Deferred()
+ clientProto = switcher(clientDeferred)
+ c, s, p = connectedServerAndClient(ServerClass=lambda: serverProto,
+ ClientClass=lambda: clientProto)
+
+ if spuriousTraffic:
+ wfdr = [] # remote
+ wfd = c.callRemote(WaitForever).addErrback(wfdr.append)
+ switchDeferred = c.switchToTestProtocol()
+ if spuriousTraffic:
+ self.assertRaises(amp.ProtocolSwitched, c.sendHello, 'world')
+
+ def cbConnsLost(((serverSuccess, serverData),
+ (clientSuccess, clientData))):
+ self.failUnless(serverSuccess)
+ self.failUnless(clientSuccess)
+ self.assertEquals(''.join(serverData), SWITCH_CLIENT_DATA)
+ self.assertEquals(''.join(clientData), SWITCH_SERVER_DATA)
+ self.testSucceeded = True
+
+ def cbSwitch(proto):
+ return defer.DeferredList(
+ [serverDeferred, clientDeferred]).addCallback(cbConnsLost)
+
+ switchDeferred.addCallback(cbSwitch)
+ p.flush()
+ if serverProto.maybeLater is not None:
+ serverProto.maybeLater.callback(serverProto.maybeLaterProto)
+ p.flush()
+ if spuriousTraffic:
+ # switch is done here; do this here to make sure that if we're
+ # going to corrupt the connection, we do it before it's closed.
+ if spuriousError:
+ s.waiting.errback(amp.RemoteAmpError(
+ "SPURIOUS",
+ "Here's some traffic in the form of an error."))
+ else:
+ s.waiting.callback({})
+ p.flush()
+ c.transport.loseConnection() # close it
+ p.flush()
+ self.failUnless(self.testSucceeded)
+
+
+ def test_protocolSwitchDeferred(self):
+ """
+ Verify that protocol-switching even works if the value returned from
+ the command that does the switch is deferred.
+ """
+ return self.test_protocolSwitch(switcher=DeferredSymmetricCommandProtocol)
+
+
+ def test_protocolSwitchFail(self, switcher=SimpleSymmetricCommandProtocol):
+ """
+ Verify that if we try to switch protocols and it fails, the connection
+ stays up and we can go back to speaking AMP.
+ """
+ self.testSucceeded = False
+
+ serverDeferred = defer.Deferred()
+ serverProto = switcher(serverDeferred)
+ clientDeferred = defer.Deferred()
+ clientProto = switcher(clientDeferred)
+ c, s, p = connectedServerAndClient(ServerClass=lambda: serverProto,
+ ClientClass=lambda: clientProto)
+ L = []
+ switchDeferred = c.switchToTestProtocol(fail=True).addErrback(L.append)
+ p.flush()
+ L.pop().trap(UnknownProtocol)
+ self.failIf(self.testSucceeded)
+ # It's a known error, so let's send a "hello" on the same connection;
+ # it should work.
+ c.sendHello('world').addCallback(L.append)
+ p.flush()
+ self.assertEqual(L.pop()['hello'], 'world')
+
+
+ def test_trafficAfterSwitch(self):
+ """
+ Verify that attempts to send traffic after a switch will not corrupt
+ the nested protocol.
+ """
+ return self.test_protocolSwitch(spuriousTraffic=True)
+
+
+ def test_errorAfterSwitch(self):
+ """
+ Returning an error after a protocol switch should record the underlying
+ error.
+ """
+ return self.test_protocolSwitch(spuriousTraffic=True,
+ spuriousError=True)
+
+
+ def test_quitBoxQuits(self):
+ """
+ Verify that commands with a responseType of QuitBox will in fact
+ terminate the connection.
+ """
+ c, s, p = connectedServerAndClient(
+ ServerClass=SimpleSymmetricCommandProtocol,
+ ClientClass=SimpleSymmetricCommandProtocol)
+
+ L = []
+ HELLO = 'world'
+ GOODBYE = 'everyone'
+ c.sendHello(HELLO).addCallback(L.append)
+ p.flush()
+ self.assertEquals(L.pop()['hello'], HELLO)
+ c.callRemote(Goodbye).addCallback(L.append)
+ p.flush()
+ self.assertEquals(L.pop()['goodbye'], GOODBYE)
+ c.sendHello(HELLO).addErrback(L.append)
+ L.pop().trap(error.ConnectionDone)
+
+
+ def test_basicLiteralEmit(self):
+ """
+ Verify that the command dictionaries for a callRemoteN look correct
+ after being serialized and parsed.
+ """
+ c, s, p = connectedServerAndClient()
+ L = []
+ s.ampBoxReceived = L.append
+ c.callRemote(Hello, hello='hello test', mixedCase='mixed case arg test',
+ dash_arg='x', underscore_arg='y')
+ p.flush()
+ self.assertEquals(len(L), 1)
+ for k, v in [('_command', Hello.commandName),
+ ('hello', 'hello test'),
+ ('mixedCase', 'mixed case arg test'),
+ ('dash-arg', 'x'),
+ ('underscore_arg', 'y')]:
+ self.assertEquals(L[-1].pop(k), v)
+ L[-1].pop('_ask')
+ self.assertEquals(L[-1], {})
+
+
+ def test_basicStructuredEmit(self):
+ """
+ Verify that a call similar to basicLiteralEmit's is handled properly with
+ high-level quoting and passing to Python methods, and that argument
+ names are correctly handled.
+ """
+ L = []
+ class StructuredHello(amp.AMP):
+ def h(self, *a, **k):
+ L.append((a, k))
+ return dict(hello='aaa')
+ Hello.responder(h)
+ c, s, p = connectedServerAndClient(ServerClass=StructuredHello)
+ c.callRemote(Hello, hello='hello test', mixedCase='mixed case arg test',
+ dash_arg='x', underscore_arg='y').addCallback(L.append)
+ p.flush()
+ self.assertEquals(len(L), 2)
+ self.assertEquals(L[0],
+ ((), dict(
+ hello='hello test',
+ mixedCase='mixed case arg test',
+ dash_arg='x',
+ underscore_arg='y',
+
+ # XXX - should optional arguments just not be passed?
+ # passing None seems a little odd, looking at the way it
+ # turns out here... -glyph
+ From=('file', 'file'),
+ Print=None,
+ optional=None,
+ )))
+ self.assertEquals(L[1], dict(Print=None, hello='aaa'))
+
+class PretendRemoteCertificateAuthority:
+ def checkIsPretendRemote(self):
+ return True
+
+class IOSimCert:
+ verifyCount = 0
+
+ def options(self, *ign):
+ return self
+
+ def iosimVerify(self, otherCert):
+ """
+ This isn't a real certificate, and wouldn't work on a real socket, but
+ iosim specifies a different API so that we don't have to do any crypto
+ math to demonstrate that the right functions get called in the right
+ places.
+ """
+ assert otherCert is self
+ self.verifyCount += 1
+ return True
+
+class OKCert(IOSimCert):
+ def options(self, x):
+ assert x.checkIsPretendRemote()
+ return self
+
+class GrumpyCert(IOSimCert):
+ def iosimVerify(self, otherCert):
+ self.verifyCount += 1
+ return False
+
+class DroppyCert(IOSimCert):
+ def __init__(self, toDrop):
+ self.toDrop = toDrop
+
+ def iosimVerify(self, otherCert):
+ self.verifyCount += 1
+ self.toDrop.loseConnection()
+ return True
+
+class SecurableProto(FactoryNotifier):
+
+ factory = None
+
+ def verifyFactory(self):
+ return [PretendRemoteCertificateAuthority()]
+
+ def getTLSVars(self):
+ cert = self.certFactory()
+ verify = self.verifyFactory()
+ return dict(
+ tls_localCertificate=cert,
+ tls_verifyAuthorities=verify)
+ amp.StartTLS.responder(getTLSVars)
+
+
+
+class TLSTest(unittest.TestCase):
+ def test_startingTLS(self):
+ """
+ Verify that starting TLS and succeeding at handshaking sends all the
+ notifications to all the right places.
+ """
+ cli, svr, p = connectedServerAndClient(
+ ServerClass=SecurableProto,
+ ClientClass=SecurableProto)
+
+ okc = OKCert()
+ svr.certFactory = lambda : okc
+
+ cli.callRemote(
+ amp.StartTLS, tls_localCertificate=okc,
+ tls_verifyAuthorities=[PretendRemoteCertificateAuthority()])
+
+ # let's buffer something to be delivered securely
+ L = []
+ d = cli.callRemote(SecuredPing).addCallback(L.append)
+ p.flush()
+ # once for client once for server
+ self.assertEquals(okc.verifyCount, 2)
+ L = []
+ d = cli.callRemote(SecuredPing).addCallback(L.append)
+ p.flush()
+ self.assertEqual(L[0], {'pinged': True})
+
+
+ def test_startTooManyTimes(self):
+ """
+ Verify that the protocol will complain if we attempt to renegotiate TLS,
+ which we don't support.
+ """
+ cli, svr, p = connectedServerAndClient(
+ ServerClass=SecurableProto,
+ ClientClass=SecurableProto)
+
+ okc = OKCert()
+ svr.certFactory = lambda : okc
+
+ cli.callRemote(amp.StartTLS,
+ tls_localCertificate=okc,
+ tls_verifyAuthorities=[PretendRemoteCertificateAuthority()])
+ p.flush()
+ cli.noPeerCertificate = True # this is totally fake
+ self.assertRaises(
+ amp.OnlyOneTLS,
+ cli.callRemote,
+ amp.StartTLS,
+ tls_localCertificate=okc,
+ tls_verifyAuthorities=[PretendRemoteCertificateAuthority()])
+
+
+ def test_negotiationFailed(self):
+ """
+ Verify that starting TLS and failing on both sides at handshaking sends
+ notifications to all the right places and terminates the connection.
+ """
+
+ badCert = GrumpyCert()
+
+ cli, svr, p = connectedServerAndClient(
+ ServerClass=SecurableProto,
+ ClientClass=SecurableProto)
+ svr.certFactory = lambda : badCert
+
+ cli.callRemote(amp.StartTLS,
+ tls_localCertificate=badCert)
+
+ p.flush()
+ # once for client once for server - but both fail
+ self.assertEquals(badCert.verifyCount, 2)
+ d = cli.callRemote(SecuredPing)
+ p.flush()
+ self.assertFailure(d, iosim.NativeOpenSSLError)
+
+
+ def test_negotiationFailedByClosing(self):
+ """
+ Verify that starting TLS and failing by way of a lost connection
+ notices that it is probably an SSL problem.
+ """
+
+ cli, svr, p = connectedServerAndClient(
+ ServerClass=SecurableProto,
+ ClientClass=SecurableProto)
+ droppyCert = DroppyCert(svr.transport)
+ svr.certFactory = lambda : droppyCert
+
+ secure = cli.callRemote(amp.StartTLS,
+ tls_localCertificate=droppyCert)
+
+ p.flush()
+
+ self.assertEquals(droppyCert.verifyCount, 2)
+
+ d = cli.callRemote(SecuredPing)
+ p.flush()
+
+ # it might be a good idea to move this exception somewhere more
+ # reasonable.
+ self.assertFailure(d, error.PeerVerifyError)
+
+ skip = skipSSL
+
+
+
+class TLSNotAvailableTest(unittest.TestCase):
+ """
+ Tests what happened when ssl is not available in current installation.
+ """
+
+ def setUp(self):
+ """
+ Disable ssl in amp.
+ """
+ self.ssl = amp.ssl
+ amp.ssl = None
+
+
+ def tearDown(self):
+ """
+ Restore ssl module.
+ """
+ amp.ssl = self.ssl
+
+
+ def test_callRemoteError(self):
+ """
+ Check that callRemote raises an exception when called with a
+ L{amp.StartTLS}.
+ """
+ cli, svr, p = connectedServerAndClient(
+ ServerClass=SecurableProto,
+ ClientClass=SecurableProto)
+
+ okc = OKCert()
+ svr.certFactory = lambda : okc
+
+ return self.assertFailure(cli.callRemote(
+ amp.StartTLS, tls_localCertificate=okc,
+ tls_verifyAuthorities=[PretendRemoteCertificateAuthority()]),
+ RuntimeError)
+
+
+ def test_messageReceivedError(self):
+ """
+ When a client with SSL enabled talks to a server without SSL, it
+ should return a meaningful error.
+ """
+ svr = SecurableProto()
+ okc = OKCert()
+ svr.certFactory = lambda : okc
+ box = amp.Box()
+ box['_command'] = 'StartTLS'
+ box['_ask'] = '1'
+ boxes = []
+ svr.sendBox = boxes.append
+ svr.makeConnection(StringTransport())
+ svr.ampBoxReceived(box)
+ self.assertEquals(boxes,
+ [{'_error_code': 'TLS_ERROR',
+ '_error': '1',
+ '_error_description': 'TLS not available'}])
+
+
+
+class InheritedError(Exception):
+ """
+ This error is used to check inheritance.
+ """
+
+
+
+class OtherInheritedError(Exception):
+ """
+ This is a distinct error for checking inheritance.
+ """
+
+
+
+class BaseCommand(amp.Command):
+ """
+ This provides a command that will be subclassed.
+ """
+ errors = {InheritedError: 'INHERITED_ERROR'}
+
+
+
+class InheritedCommand(BaseCommand):
+ """
+ This is a command which subclasses another command but does not override
+ anything.
+ """
+
+
+
+class AddErrorsCommand(BaseCommand):
+ """
+ This is a command which subclasses another command but adds errors to the
+ list.
+ """
+ arguments = [('other', amp.Boolean())]
+ errors = {OtherInheritedError: 'OTHER_INHERITED_ERROR'}
+
+
+
+class NormalCommandProtocol(amp.AMP):
+ """
+ This is a protocol which responds to L{BaseCommand}, and is used to test
+ that inheritance does not interfere with the normal handling of errors.
+ """
+ def resp(self):
+ raise InheritedError()
+ BaseCommand.responder(resp)
+
+
+
+class InheritedCommandProtocol(amp.AMP):
+ """
+ This is a protocol which responds to L{InheritedCommand}, and is used to
+ test that inherited commands inherit their bases' errors if they do not
+ respond to any of their own.
+ """
+ def resp(self):
+ raise InheritedError()
+ InheritedCommand.responder(resp)
+
+
+
+class AddedCommandProtocol(amp.AMP):
+ """
+ This is a protocol which responds to L{AddErrorsCommand}, and is used to
+ test that inherited commands can add their own new types of errors, but
+ still respond in the same way to their parents types of errors.
+ """
+ def resp(self, other):
+ if other:
+ raise OtherInheritedError()
+ else:
+ raise InheritedError()
+ AddErrorsCommand.responder(resp)
+
+
+
+class CommandInheritanceTests(unittest.TestCase):
+ """
+ These tests verify that commands inherit error conditions properly.
+ """
+
+ def errorCheck(self, err, proto, cmd, **kw):
+ """
+ Check that the appropriate kind of error is raised when a given command
+ is sent to a given protocol.
+ """
+ c, s, p = connectedServerAndClient(ServerClass=proto,
+ ClientClass=proto)
+ d = c.callRemote(cmd, **kw)
+ d2 = self.failUnlessFailure(d, err)
+ p.flush()
+ return d2
+
+
+ def test_basicErrorPropagation(self):
+ """
+ Verify that errors specified in a superclass are respected normally
+ even if it has subclasses.
+ """
+ return self.errorCheck(
+ InheritedError, NormalCommandProtocol, BaseCommand)
+
+
+ def test_inheritedErrorPropagation(self):
+ """
+ Verify that errors specified in a superclass command are propagated to
+ its subclasses.
+ """
+ return self.errorCheck(
+ InheritedError, InheritedCommandProtocol, InheritedCommand)
+
+
+ def test_inheritedErrorAddition(self):
+ """
+ Verify that new errors specified in a subclass of an existing command
+ are honored even if the superclass defines some errors.
+ """
+ return self.errorCheck(
+ OtherInheritedError, AddedCommandProtocol, AddErrorsCommand, other=True)
+
+
+ def test_additionWithOriginalError(self):
+ """
+ Verify that errors specified in a command's superclass are respected
+ even if that command defines new errors itself.
+ """
+ return self.errorCheck(
+ InheritedError, AddedCommandProtocol, AddErrorsCommand, other=False)
+
+
+def _loseAndPass(err, proto):
+ # be specific, pass on the error to the client.
+ err.trap(error.ConnectionLost, error.ConnectionDone)
+ del proto.connectionLost
+ proto.connectionLost(err)
+
+
+class LiveFireBase:
+ """
+ Utility for connected reactor-using tests.
+ """
+
+ def setUp(self):
+ """
+ Create an amp server and connect a client to it.
+ """
+ from twisted.internet import reactor
+ self.serverFactory = protocol.ServerFactory()
+ self.serverFactory.protocol = self.serverProto
+ self.clientFactory = protocol.ClientFactory()
+ self.clientFactory.protocol = self.clientProto
+ self.clientFactory.onMade = defer.Deferred()
+ self.serverFactory.onMade = defer.Deferred()
+ self.serverPort = reactor.listenTCP(0, self.serverFactory)
+ self.addCleanup(self.serverPort.stopListening)
+ self.clientConn = reactor.connectTCP(
+ '127.0.0.1', self.serverPort.getHost().port,
+ self.clientFactory)
+ self.addCleanup(self.clientConn.disconnect)
+ def getProtos(rlst):
+ self.cli = self.clientFactory.theProto
+ self.svr = self.serverFactory.theProto
+ dl = defer.DeferredList([self.clientFactory.onMade,
+ self.serverFactory.onMade])
+ return dl.addCallback(getProtos)
+
+ def tearDown(self):
+ """
+ Cleanup client and server connections, and check the error got at
+ C{connectionLost}.
+ """
+ L = []
+ for conn in self.cli, self.svr:
+ if conn.transport is not None:
+ # depend on amp's function connection-dropping behavior
+ d = defer.Deferred().addErrback(_loseAndPass, conn)
+ conn.connectionLost = d.errback
+ conn.transport.loseConnection()
+ L.append(d)
+ return defer.gatherResults(L
+ ).addErrback(lambda first: first.value.subFailure)
+
+
+def show(x):
+ import sys
+ sys.stdout.write(x+'\n')
+ sys.stdout.flush()
+
+
+def tempSelfSigned():
+ from twisted.internet import ssl
+
+ sharedDN = ssl.DN(CN='shared')
+ key = ssl.KeyPair.generate()
+ cr = key.certificateRequest(sharedDN)
+ sscrd = key.signCertificateRequest(
+ sharedDN, cr, lambda dn: True, 1234567)
+ cert = key.newCertificate(sscrd)
+ return cert
+
+if ssl is not None:
+ tempcert = tempSelfSigned()
+
+
+class LiveFireTLSTestCase(LiveFireBase, unittest.TestCase):
+ clientProto = SecurableProto
+ serverProto = SecurableProto
+ def test_liveFireCustomTLS(self):
+ """
+ Using real, live TLS, actually negotiate a connection.
+
+ This also looks at the 'peerCertificate' attribute's correctness, since
+ that's actually loaded using OpenSSL calls, but the main purpose is to
+ make sure that we didn't miss anything obvious in iosim about TLS
+ negotiations.
+ """
+
+ cert = tempcert
+
+ self.svr.verifyFactory = lambda : [cert]
+ self.svr.certFactory = lambda : cert
+ # only needed on the server, we specify the client below.
+
+ def secured(rslt):
+ x = cert.digest()
+ def pinged(rslt2):
+ # Interesting. OpenSSL won't even _tell_ us about the peer
+ # cert until we negotiate. we should be able to do this in
+ # 'secured' instead, but it looks like we can't. I think this
+ # is a bug somewhere far deeper than here.
+ self.failUnlessEqual(x, self.cli.hostCertificate.digest())
+ self.failUnlessEqual(x, self.cli.peerCertificate.digest())
+ self.failUnlessEqual(x, self.svr.hostCertificate.digest())
+ self.failUnlessEqual(x, self.svr.peerCertificate.digest())
+ return self.cli.callRemote(SecuredPing).addCallback(pinged)
+ return self.cli.callRemote(amp.StartTLS,
+ tls_localCertificate=cert,
+ tls_verifyAuthorities=[cert]).addCallback(secured)
+
+ skip = skipSSL
+
+
+
+class SlightlySmartTLS(SimpleSymmetricCommandProtocol):
+ """
+ Specific implementation of server side protocol with different
+ management of TLS.
+ """
+ def getTLSVars(self):
+ """
+ @return: the global C{tempcert} certificate as local certificate.
+ """
+ return dict(tls_localCertificate=tempcert)
+ amp.StartTLS.responder(getTLSVars)
+
+
+class PlainVanillaLiveFire(LiveFireBase, unittest.TestCase):
+
+ clientProto = SimpleSymmetricCommandProtocol
+ serverProto = SimpleSymmetricCommandProtocol
+
+ def test_liveFireDefaultTLS(self):
+ """
+ Verify that out of the box, we can start TLS to at least encrypt the
+ connection, even if we don't have any certificates to use.
+ """
+ def secured(result):
+ return self.cli.callRemote(SecuredPing)
+ return self.cli.callRemote(amp.StartTLS).addCallback(secured)
+
+ skip = skipSSL
+
+
+
+class WithServerTLSVerification(LiveFireBase, unittest.TestCase):
+ clientProto = SimpleSymmetricCommandProtocol
+ serverProto = SlightlySmartTLS
+
+ def test_anonymousVerifyingClient(self):
+ """
+ Verify that anonymous clients can verify server certificates.
+ """
+ def secured(result):
+ return self.cli.callRemote(SecuredPing)
+ return self.cli.callRemote(amp.StartTLS,
+ tls_verifyAuthorities=[tempcert]
+ ).addCallback(secured)
+
+ skip = skipSSL
+
+
+
+class ProtocolIncludingArgument(amp.Argument):
+ """
+ An L{amp.Argument} which encodes its parser and serializer
+ arguments *including the protocol* into its parsed and serialized
+ forms.
+ """
+
+ def fromStringProto(self, string, protocol):
+ """
+ Don't decode anything; just return all possible information.
+
+ @return: A two-tuple of the input string and the protocol.
+ """
+ return (string, protocol)
+
+ def toStringProto(self, obj, protocol):
+ """
+ Encode identifying information about L{object} and protocol
+ into a string for later verification.
+
+ @type obj: L{object}
+ @type protocol: L{amp.AMP}
+ """
+ return "%s:%s" % (id(obj), id(protocol))
+
+
+
+class ProtocolIncludingCommand(amp.Command):
+ """
+ A command that has argument and response schemas which use
+ L{ProtocolIncludingArgument}.
+ """
+ arguments = [('weird', ProtocolIncludingArgument())]
+ response = [('weird', ProtocolIncludingArgument())]
+
+
+
+class MagicSchemaCommand(amp.Command):
+ """
+ A command which overrides L{parseResponse}, L{parseArguments}, and
+ L{makeResponse}.
+ """
+ def parseResponse(self, strings, protocol):
+ """
+ Don't do any parsing, just jam the input strings and protocol
+ onto the C{protocol.parseResponseArguments} attribute as a
+ two-tuple. Return the original strings.
+ """
+ protocol.parseResponseArguments = (strings, protocol)
+ return strings
+ parseResponse = classmethod(parseResponse)
+
+
+ def parseArguments(cls, strings, protocol):
+ """
+ Don't do any parsing, just jam the input strings and protocol
+ onto the C{protocol.parseArgumentsArguments} attribute as a
+ two-tuple. Return the original strings.
+ """
+ protocol.parseArgumentsArguments = (strings, protocol)
+ return strings
+ parseArguments = classmethod(parseArguments)
+
+
+ def makeArguments(cls, objects, protocol):
+ """
+ Don't do any serializing, just jam the input strings and protocol
+ onto the C{protocol.makeArgumentsArguments} attribute as a
+ two-tuple. Return the original strings.
+ """
+ protocol.makeArgumentsArguments = (objects, protocol)
+ return objects
+ makeArguments = classmethod(makeArguments)
+
+
+
+class NoNetworkProtocol(amp.AMP):
+ """
+ An L{amp.AMP} subclass which overrides private methods to avoid
+ testing the network. It also provides a responder for
+ L{MagicSchemaCommand} that does nothing, so that tests can test
+ aspects of the interaction of L{amp.Command}s and L{amp.AMP}.
+
+ @ivar parseArgumentsArguments: Arguments that have been passed to any
+ L{MagicSchemaCommand}, if L{MagicSchemaCommand} has been handled by
+ this protocol.
+
+ @ivar parseResponseArguments: Responses that have been returned from a
+ L{MagicSchemaCommand}, if L{MagicSchemaCommand} has been handled by
+ this protocol.
+
+ @ivar makeArgumentsArguments: Arguments that have been serialized by any
+ L{MagicSchemaCommand}, if L{MagicSchemaCommand} has been handled by
+ this protocol.
+ """
+ def _sendBoxCommand(self, commandName, strings, requiresAnswer):
+ """
+ Return a Deferred which fires with the original strings.
+ """
+ return defer.succeed(strings)
+
+ MagicSchemaCommand.responder(lambda s, weird: {})
+
+
+
+class MyBox(dict):
+ """
+ A unique dict subclass.
+ """
+
+
+
+class ProtocolIncludingCommandWithDifferentCommandType(
+ ProtocolIncludingCommand):
+ """
+ A L{ProtocolIncludingCommand} subclass whose commandType is L{MyBox}
+ """
+ commandType = MyBox
+
+
+
+class CommandTestCase(unittest.TestCase):
+ """
+ Tests for L{amp.Argument} and L{amp.Command}.
+ """
+ def test_argumentInterface(self):
+ """
+ L{Argument} instances provide L{amp.IArgumentType}.
+ """
+ self.assertTrue(verifyObject(amp.IArgumentType, amp.Argument()))
+
+
+ def test_parseResponse(self):
+ """
+ There should be a class method of Command which accepts a
+ mapping of argument names to serialized forms and returns a
+ similar mapping whose values have been parsed via the
+ Command's response schema.
+ """
+ protocol = object()
+ result = 'whatever'
+ strings = {'weird': result}
+ self.assertEqual(
+ ProtocolIncludingCommand.parseResponse(strings, protocol),
+ {'weird': (result, protocol)})
+
+
+ def test_callRemoteCallsParseResponse(self):
+ """
+ Making a remote call on a L{amp.Command} subclass which
+ overrides the C{parseResponse} method should call that
+ C{parseResponse} method to get the response.
+ """
+ client = NoNetworkProtocol()
+ thingy = "weeoo"
+ response = client.callRemote(MagicSchemaCommand, weird=thingy)
+ def gotResponse(ign):
+ self.assertEquals(client.parseResponseArguments,
+ ({"weird": thingy}, client))
+ response.addCallback(gotResponse)
+ return response
+
+
+ def test_parseArguments(self):
+ """
+ There should be a class method of L{amp.Command} which accepts
+ a mapping of argument names to serialized forms and returns a
+ similar mapping whose values have been parsed via the
+ command's argument schema.
+ """
+ protocol = object()
+ result = 'whatever'
+ strings = {'weird': result}
+ self.assertEqual(
+ ProtocolIncludingCommand.parseArguments(strings, protocol),
+ {'weird': (result, protocol)})
+
+
+ def test_responderCallsParseArguments(self):
+ """
+ Making a remote call on a L{amp.Command} subclass which
+ overrides the C{parseArguments} method should call that
+ C{parseArguments} method to get the arguments.
+ """
+ protocol = NoNetworkProtocol()
+ responder = protocol.locateResponder(MagicSchemaCommand.commandName)
+ argument = object()
+ response = responder(dict(weird=argument))
+ response.addCallback(
+ lambda ign: self.assertEqual(protocol.parseArgumentsArguments,
+ ({"weird": argument}, protocol)))
+ return response
+
+
+ def test_makeArguments(self):
+ """
+ There should be a class method of L{amp.Command} which accepts
+ a mapping of argument names to objects and returns a similar
+ mapping whose values have been serialized via the command's
+ argument schema.
+ """
+ protocol = object()
+ argument = object()
+ objects = {'weird': argument}
+ self.assertEqual(
+ ProtocolIncludingCommand.makeArguments(objects, protocol),
+ {'weird': "%d:%d" % (id(argument), id(protocol))})
+
+
+ def test_makeArgumentsUsesCommandType(self):
+ """
+ L{amp.Command.makeArguments}'s return type should be the type
+ of the result of L{amp.Command.commandType}.
+ """
+ protocol = object()
+ objects = {"weird": "whatever"}
+
+ result = ProtocolIncludingCommandWithDifferentCommandType.makeArguments(
+ objects, protocol)
+ self.assertIdentical(type(result), MyBox)
+
+
+ def test_callRemoteCallsMakeArguments(self):
+ """
+ Making a remote call on a L{amp.Command} subclass which
+ overrides the C{makeArguments} method should call that
+ C{makeArguments} method to get the response.
+ """
+ client = NoNetworkProtocol()
+ argument = object()
+ response = client.callRemote(MagicSchemaCommand, weird=argument)
+ def gotResponse(ign):
+ self.assertEqual(client.makeArgumentsArguments,
+ ({"weird": argument}, client))
+ response.addCallback(gotResponse)
+ return response
+
+
+ def test_extraArgumentsDisallowed(self):
+ """
+ L{Command.makeArguments} raises L{amp.InvalidSignature} if the objects
+ dictionary passed to it includes a key which does not correspond to the
+ Python identifier for a defined argument.
+ """
+ self.assertRaises(
+ amp.InvalidSignature,
+ Hello.makeArguments,
+ dict(hello="hello", bogusArgument=object()), None)
+
+
+ def test_wireSpellingDisallowed(self):
+ """
+ If a command argument conflicts with a Python keyword, the
+ untransformed argument name is not allowed as a key in the dictionary
+ passed to L{Command.makeArguments}. If it is supplied,
+ L{amp.InvalidSignature} is raised.
+
+ This may be a pointless implementation restriction which may be lifted.
+ The current behavior is tested to verify that such arguments are not
+ silently dropped on the floor (the previous behavior).
+ """
+ self.assertRaises(
+ amp.InvalidSignature,
+ Hello.makeArguments,
+ dict(hello="required", **{"print": "print value"}),
+ None)
+
+
+class ListOfTestsMixin:
+ """
+ Base class for testing L{ListOf}, a parameterized zero-or-more argument
+ type.
+
+ @ivar elementType: Subclasses should set this to an L{Argument}
+ instance. The tests will make a L{ListOf} using this.
+
+ @ivar strings: Subclasses should set this to a dictionary mapping some
+ number of keys to the correct serialized form for some example
+ values. These should agree with what L{elementType}
+ produces/accepts.
+
+ @ivar objects: Subclasses should set this to a dictionary with the same
+ keys as C{strings} and with values which are the lists which should
+ serialize to the values in the C{strings} dictionary.
+ """
+ def test_toBox(self):
+ """
+ L{ListOf.toBox} extracts the list of objects from the C{objects}
+ dictionary passed to it, using the C{name} key also passed to it,
+ serializes each of the elements in that list using the L{Argument}
+ instance previously passed to its initializer, combines the serialized
+ results, and inserts the result into the C{strings} dictionary using
+ the same C{name} key.
+ """
+ stringList = amp.ListOf(self.elementType)
+ strings = amp.AmpBox()
+ for key in self.objects:
+ stringList.toBox(key, strings, self.objects.copy(), None)
+ self.assertEquals(strings, self.strings)
+
+
+ def test_fromBox(self):
+ """
+ L{ListOf.fromBox} reverses the operation performed by L{ListOf.toBox}.
+ """
+ stringList = amp.ListOf(self.elementType)
+ objects = {}
+ for key in self.strings:
+ stringList.fromBox(key, self.strings.copy(), objects, None)
+ self.assertEquals(objects, self.objects)
+
+
+
+class ListOfStringsTests(unittest.TestCase, ListOfTestsMixin):
+ """
+ Tests for L{ListOf} combined with L{String}.
+ """
+ elementType = amp.String()
+
+ strings = {
+ "empty": "",
+ "single": "\x00\x03foo",
+ "multiple": "\x00\x03bar\x00\x03baz\x00\x04quux"}
+
+ objects = {
+ "empty": [],
+ "single": ["foo"],
+ "multiple": ["bar", "baz", "quux"]}
+
+
+class ListOfIntegersTests(unittest.TestCase, ListOfTestsMixin):
+ """
+ Tests for L{ListOf} combined with L{Integer}.
+ """
+ elementType = amp.Integer()
+
+ strings = {
+ "empty": "",
+ "single": "\x00\x0210",
+ "multiple": "\x00\x011\x00\x0220\x00\x03500"}
+
+ objects = {
+ "empty": [],
+ "single": [10],
+ "multiple": [1, 20, 500]}
+
+
+class ListOfUnicodeTests(unittest.TestCase, ListOfTestsMixin):
+ """
+ Tests for L{ListOf} combined with L{Unicode}.
+ """
+ elementType = amp.Unicode()
+
+ strings = {
+ "empty": "",
+ "single": "\x00\x03foo",
+ "multiple": "\x00\x03\xe2\x98\x83\x00\x05Hello\x00\x05world"}
+
+ objects = {
+ "empty": [],
+ "single": [u"foo"],
+ "multiple": [u"\N{SNOWMAN}", u"Hello", u"world"]}
+
+
+
+if not interfaces.IReactorSSL.providedBy(reactor):
+ skipMsg = 'This test case requires SSL support in the reactor'
+ TLSTest.skip = skipMsg
+ LiveFireTLSTestCase.skip = skipMsg
+ PlainVanillaLiveFire.skip = skipMsg
+ WithServerTLSVerification.skip = skipMsg
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_application.py b/vendor/Twisted-10.0.0/twisted/test/test_application.py
new file mode 100644
index 0000000000..953509e15a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_application.py
@@ -0,0 +1,867 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.application} and its interaction with
+L{twisted.persisted.sob}.
+"""
+
+import sys, copy, os, pickle
+from StringIO import StringIO
+
+from twisted.trial import unittest, util
+from twisted.application import service, internet, app
+from twisted.persisted import sob
+from twisted.python import usage
+from twisted.python.runtime import platform
+from twisted.internet import interfaces, defer
+from twisted.protocols import wire, basic
+from twisted.internet import protocol, reactor
+from twisted.internet.utils import getProcessOutputAndValue
+from twisted.application import reactors
+from twisted.test.proto_helpers import MemoryReactor
+
+
+oldAppSuppressions = [util.suppress(message='twisted.internet.app is deprecated',
+ category=DeprecationWarning)]
+
+skipWindowsNopywin32 = None
+if platform.isWindows():
+ try:
+ import win32process
+ except ImportError:
+ skipWindowsNopywin32 = ("On windows, spawnProcess is not available "
+ "in the absence of win32process.")
+
+class Dummy:
+ processName=None
+
+class TestService(unittest.TestCase):
+
+ def testName(self):
+ s = service.Service()
+ s.setName("hello")
+ self.failUnlessEqual(s.name, "hello")
+
+ def testParent(self):
+ s = service.Service()
+ p = service.MultiService()
+ s.setServiceParent(p)
+ self.failUnlessEqual(list(p), [s])
+ self.failUnlessEqual(s.parent, p)
+
+ def testApplicationAsParent(self):
+ s = service.Service()
+ p = service.Application("")
+ s.setServiceParent(p)
+ self.failUnlessEqual(list(service.IServiceCollection(p)), [s])
+ self.failUnlessEqual(s.parent, service.IServiceCollection(p))
+
+ def testNamedChild(self):
+ s = service.Service()
+ p = service.MultiService()
+ s.setName("hello")
+ s.setServiceParent(p)
+ self.failUnlessEqual(list(p), [s])
+ self.failUnlessEqual(s.parent, p)
+ self.failUnlessEqual(p.getServiceNamed("hello"), s)
+
+ def testDoublyNamedChild(self):
+ s = service.Service()
+ p = service.MultiService()
+ s.setName("hello")
+ s.setServiceParent(p)
+ self.failUnlessRaises(RuntimeError, s.setName, "lala")
+
+ def testDuplicateNamedChild(self):
+ s = service.Service()
+ p = service.MultiService()
+ s.setName("hello")
+ s.setServiceParent(p)
+ s = service.Service()
+ s.setName("hello")
+ self.failUnlessRaises(RuntimeError, s.setServiceParent, p)
+
+ def testDisowning(self):
+ s = service.Service()
+ p = service.MultiService()
+ s.setServiceParent(p)
+ self.failUnlessEqual(list(p), [s])
+ self.failUnlessEqual(s.parent, p)
+ s.disownServiceParent()
+ self.failUnlessEqual(list(p), [])
+ self.failUnlessEqual(s.parent, None)
+
+ def testRunning(self):
+ s = service.Service()
+ self.assert_(not s.running)
+ s.startService()
+ self.assert_(s.running)
+ s.stopService()
+ self.assert_(not s.running)
+
+ def testRunningChildren1(self):
+ s = service.Service()
+ p = service.MultiService()
+ s.setServiceParent(p)
+ self.assert_(not s.running)
+ self.assert_(not p.running)
+ p.startService()
+ self.assert_(s.running)
+ self.assert_(p.running)
+ p.stopService()
+ self.assert_(not s.running)
+ self.assert_(not p.running)
+
+ def testRunningChildren2(self):
+ s = service.Service()
+ def checkRunning():
+ self.assert_(s.running)
+ t = service.Service()
+ t.stopService = checkRunning
+ t.startService = checkRunning
+ p = service.MultiService()
+ s.setServiceParent(p)
+ t.setServiceParent(p)
+ p.startService()
+ p.stopService()
+
+ def testAddingIntoRunning(self):
+ p = service.MultiService()
+ p.startService()
+ s = service.Service()
+ self.assert_(not s.running)
+ s.setServiceParent(p)
+ self.assert_(s.running)
+ s.disownServiceParent()
+ self.assert_(not s.running)
+
+ def testPrivileged(self):
+ s = service.Service()
+ def pss():
+ s.privilegedStarted = 1
+ s.privilegedStartService = pss
+ s1 = service.Service()
+ p = service.MultiService()
+ s.setServiceParent(p)
+ s1.setServiceParent(p)
+ p.privilegedStartService()
+ self.assert_(s.privilegedStarted)
+
+ def testCopying(self):
+ s = service.Service()
+ s.startService()
+ s1 = copy.copy(s)
+ self.assert_(not s1.running)
+ self.assert_(s.running)
+
+
+if hasattr(os, "getuid"):
+ curuid = os.getuid()
+ curgid = os.getgid()
+else:
+ curuid = curgid = 0
+
+
+class TestProcess(unittest.TestCase):
+
+ def testID(self):
+ p = service.Process(5, 6)
+ self.assertEqual(p.uid, 5)
+ self.assertEqual(p.gid, 6)
+
+ def testDefaults(self):
+ p = service.Process(5)
+ self.assertEqual(p.uid, 5)
+ self.assertEqual(p.gid, None)
+ p = service.Process(gid=5)
+ self.assertEqual(p.uid, None)
+ self.assertEqual(p.gid, 5)
+ p = service.Process()
+ self.assertEqual(p.uid, None)
+ self.assertEqual(p.gid, None)
+
+ def testProcessName(self):
+ p = service.Process()
+ self.assertEqual(p.processName, None)
+ p.processName = 'hello'
+ self.assertEqual(p.processName, 'hello')
+
+
+class TestInterfaces(unittest.TestCase):
+
+ def testService(self):
+ self.assert_(service.IService.providedBy(service.Service()))
+
+ def testMultiService(self):
+ self.assert_(service.IService.providedBy(service.MultiService()))
+ self.assert_(service.IServiceCollection.providedBy(service.MultiService()))
+
+ def testProcess(self):
+ self.assert_(service.IProcess.providedBy(service.Process()))
+
+
+class TestApplication(unittest.TestCase):
+
+ def testConstructor(self):
+ service.Application("hello")
+ service.Application("hello", 5)
+ service.Application("hello", 5, 6)
+
+ def testProcessComponent(self):
+ a = service.Application("hello")
+ self.assertEqual(service.IProcess(a).uid, None)
+ self.assertEqual(service.IProcess(a).gid, None)
+ a = service.Application("hello", 5)
+ self.assertEqual(service.IProcess(a).uid, 5)
+ self.assertEqual(service.IProcess(a).gid, None)
+ a = service.Application("hello", 5, 6)
+ self.assertEqual(service.IProcess(a).uid, 5)
+ self.assertEqual(service.IProcess(a).gid, 6)
+
+ def testServiceComponent(self):
+ a = service.Application("hello")
+ self.assert_(service.IService(a) is service.IServiceCollection(a))
+ self.assertEqual(service.IService(a).name, "hello")
+ self.assertEqual(service.IService(a).parent, None)
+
+ def testPersistableComponent(self):
+ a = service.Application("hello")
+ p = sob.IPersistable(a)
+ self.assertEqual(p.style, 'pickle')
+ self.assertEqual(p.name, 'hello')
+ self.assert_(p.original is a)
+
+class TestLoading(unittest.TestCase):
+
+ def test_simpleStoreAndLoad(self):
+ a = service.Application("hello")
+ p = sob.IPersistable(a)
+ for style in 'source pickle'.split():
+ p.setStyle(style)
+ p.save()
+ a1 = service.loadApplication("hello.ta"+style[0], style)
+ self.assertEqual(service.IService(a1).name, "hello")
+ f = open("hello.tac", 'w')
+ f.writelines([
+ "from twisted.application import service\n",
+ "application = service.Application('hello')\n",
+ ])
+ f.close()
+ a1 = service.loadApplication("hello.tac", 'python')
+ self.assertEqual(service.IService(a1).name, "hello")
+
+
+
+class TestAppSupport(unittest.TestCase):
+
+ def testPassphrase(self):
+ self.assertEqual(app.getPassphrase(0), None)
+
+ def testLoadApplication(self):
+ """
+ Test loading an application file in different dump format.
+ """
+ a = service.Application("hello")
+ baseconfig = {'file': None, 'source': None, 'python':None}
+ for style in 'source pickle'.split():
+ config = baseconfig.copy()
+ config[{'pickle': 'file'}.get(style, style)] = 'helloapplication'
+ sob.IPersistable(a).setStyle(style)
+ sob.IPersistable(a).save(filename='helloapplication')
+ a1 = app.getApplication(config, None)
+ self.assertEqual(service.IService(a1).name, "hello")
+ config = baseconfig.copy()
+ config['python'] = 'helloapplication'
+ f = open("helloapplication", 'w')
+ f.writelines([
+ "from twisted.application import service\n",
+ "application = service.Application('hello')\n",
+ ])
+ f.close()
+ a1 = app.getApplication(config, None)
+ self.assertEqual(service.IService(a1).name, "hello")
+
+ def test_convertStyle(self):
+ appl = service.Application("lala")
+ for instyle in 'source pickle'.split():
+ for outstyle in 'source pickle'.split():
+ sob.IPersistable(appl).setStyle(instyle)
+ sob.IPersistable(appl).save(filename="converttest")
+ app.convertStyle("converttest", instyle, None,
+ "converttest.out", outstyle, 0)
+ appl2 = service.loadApplication("converttest.out", outstyle)
+ self.assertEqual(service.IService(appl2).name, "lala")
+
+ def test_getLogFile(self):
+ """
+ Test L{app.getLogFile}, veryfying the LogFile instance it returns.
+ """
+ os.mkdir("logfiledir")
+ l = app.getLogFile(os.path.join("logfiledir", "lala"))
+ self.assertEqual(l.path,
+ os.path.abspath(os.path.join("logfiledir", "lala")))
+ self.assertEqual(l.name, "lala")
+ self.assertEqual(l.directory, os.path.abspath("logfiledir"))
+
+ test_getLogFile.suppress = [
+ util.suppress(message="app.getLogFile is deprecated. Use "
+ "twisted.python.logfile.LogFile.fromFullPath instead",
+ category=DeprecationWarning)]
+
+ def test_startApplication(self):
+ appl = service.Application("lala")
+ app.startApplication(appl, 0)
+ self.assert_(service.IService(appl).running)
+
+
+class Foo(basic.LineReceiver):
+ def connectionMade(self):
+ self.transport.write('lalala\r\n')
+ def lineReceived(self, line):
+ self.factory.line = line
+ self.transport.loseConnection()
+ def connectionLost(self, reason):
+ self.factory.d.callback(self.factory.line)
+
+
+class DummyApp:
+ processName = None
+ def addService(self, service):
+ self.services[service.name] = service
+ def removeService(self, service):
+ del self.services[service.name]
+
+
+class TimerTarget:
+ def __init__(self):
+ self.l = []
+ def append(self, what):
+ self.l.append(what)
+
+class TestEcho(wire.Echo):
+ def connectionLost(self, reason):
+ self.d.callback(True)
+
+class TestInternet2(unittest.TestCase):
+
+ def testTCP(self):
+ s = service.MultiService()
+ s.startService()
+ factory = protocol.ServerFactory()
+ factory.protocol = TestEcho
+ TestEcho.d = defer.Deferred()
+ t = internet.TCPServer(0, factory)
+ t.setServiceParent(s)
+ num = t._port.getHost().port
+ factory = protocol.ClientFactory()
+ factory.d = defer.Deferred()
+ factory.protocol = Foo
+ factory.line = None
+ internet.TCPClient('127.0.0.1', num, factory).setServiceParent(s)
+ factory.d.addCallback(self.assertEqual, 'lalala')
+ factory.d.addCallback(lambda x : s.stopService())
+ factory.d.addCallback(lambda x : TestEcho.d)
+ return factory.d
+
+
+ def test_UDP(self):
+ """
+ Test L{internet.UDPServer} with a random port: starting the service
+ should give it valid port, and stopService should free it so that we
+ can start a server on the same port again.
+ """
+ if not interfaces.IReactorUDP(reactor, None):
+ raise unittest.SkipTest("This reactor does not support UDP sockets")
+ p = protocol.DatagramProtocol()
+ t = internet.UDPServer(0, p)
+ t.startService()
+ num = t._port.getHost().port
+ self.assertNotEquals(num, 0)
+ def onStop(ignored):
+ t = internet.UDPServer(num, p)
+ t.startService()
+ return t.stopService()
+ return defer.maybeDeferred(t.stopService).addCallback(onStop)
+
+
+ def testPrivileged(self):
+ factory = protocol.ServerFactory()
+ factory.protocol = TestEcho
+ TestEcho.d = defer.Deferred()
+ t = internet.TCPServer(0, factory)
+ t.privileged = 1
+ t.privilegedStartService()
+ num = t._port.getHost().port
+ factory = protocol.ClientFactory()
+ factory.d = defer.Deferred()
+ factory.protocol = Foo
+ factory.line = None
+ c = internet.TCPClient('127.0.0.1', num, factory)
+ c.startService()
+ factory.d.addCallback(self.assertEqual, 'lalala')
+ factory.d.addCallback(lambda x : c.stopService())
+ factory.d.addCallback(lambda x : t.stopService())
+ factory.d.addCallback(lambda x : TestEcho.d)
+ return factory.d
+
+ def testConnectionGettingRefused(self):
+ factory = protocol.ServerFactory()
+ factory.protocol = wire.Echo
+ t = internet.TCPServer(0, factory)
+ t.startService()
+ num = t._port.getHost().port
+ t.stopService()
+ d = defer.Deferred()
+ factory = protocol.ClientFactory()
+ factory.clientConnectionFailed = lambda *args: d.callback(None)
+ c = internet.TCPClient('127.0.0.1', num, factory)
+ c.startService()
+ return d
+
+ def testUNIX(self):
+ # FIXME: This test is far too dense. It needs comments.
+ # -- spiv, 2004-11-07
+ if not interfaces.IReactorUNIX(reactor, None):
+ raise unittest.SkipTest, "This reactor does not support UNIX domain sockets"
+ s = service.MultiService()
+ s.startService()
+ factory = protocol.ServerFactory()
+ factory.protocol = TestEcho
+ TestEcho.d = defer.Deferred()
+ t = internet.UNIXServer('echo.skt', factory)
+ t.setServiceParent(s)
+ factory = protocol.ClientFactory()
+ factory.protocol = Foo
+ factory.d = defer.Deferred()
+ factory.line = None
+ internet.UNIXClient('echo.skt', factory).setServiceParent(s)
+ factory.d.addCallback(self.assertEqual, 'lalala')
+ factory.d.addCallback(lambda x : s.stopService())
+ factory.d.addCallback(lambda x : TestEcho.d)
+ factory.d.addCallback(self._cbTestUnix, factory, s)
+ return factory.d
+
+ def _cbTestUnix(self, ignored, factory, s):
+ TestEcho.d = defer.Deferred()
+ factory.line = None
+ factory.d = defer.Deferred()
+ s.startService()
+ factory.d.addCallback(self.assertEqual, 'lalala')
+ factory.d.addCallback(lambda x : s.stopService())
+ factory.d.addCallback(lambda x : TestEcho.d)
+ return factory.d
+
+ def testVolatile(self):
+ if not interfaces.IReactorUNIX(reactor, None):
+ raise unittest.SkipTest, "This reactor does not support UNIX domain sockets"
+ factory = protocol.ServerFactory()
+ factory.protocol = wire.Echo
+ t = internet.UNIXServer('echo.skt', factory)
+ t.startService()
+ self.failIfIdentical(t._port, None)
+ t1 = copy.copy(t)
+ self.assertIdentical(t1._port, None)
+ t.stopService()
+ self.assertIdentical(t._port, None)
+ self.failIf(t.running)
+
+ factory = protocol.ClientFactory()
+ factory.protocol = wire.Echo
+ t = internet.UNIXClient('echo.skt', factory)
+ t.startService()
+ self.failIfIdentical(t._connection, None)
+ t1 = copy.copy(t)
+ self.assertIdentical(t1._connection, None)
+ t.stopService()
+ self.assertIdentical(t._connection, None)
+ self.failIf(t.running)
+
+ def testStoppingServer(self):
+ if not interfaces.IReactorUNIX(reactor, None):
+ raise unittest.SkipTest, "This reactor does not support UNIX domain sockets"
+ factory = protocol.ServerFactory()
+ factory.protocol = wire.Echo
+ t = internet.UNIXServer('echo.skt', factory)
+ t.startService()
+ t.stopService()
+ self.failIf(t.running)
+ factory = protocol.ClientFactory()
+ d = defer.Deferred()
+ factory.clientConnectionFailed = lambda *args: d.callback(None)
+ reactor.connectUNIX('echo.skt', factory)
+ return d
+
+ def testPickledTimer(self):
+ target = TimerTarget()
+ t0 = internet.TimerService(1, target.append, "hello")
+ t0.startService()
+ s = pickle.dumps(t0)
+ t0.stopService()
+
+ t = pickle.loads(s)
+ self.failIf(t.running)
+
+ def testBrokenTimer(self):
+ d = defer.Deferred()
+ t = internet.TimerService(1, lambda: 1 / 0)
+ oldFailed = t._failed
+ def _failed(why):
+ oldFailed(why)
+ d.callback(None)
+ t._failed = _failed
+ t.startService()
+ d.addCallback(lambda x : t.stopService)
+ d.addCallback(lambda x : self.assertEqual(
+ [ZeroDivisionError],
+ [o.value.__class__ for o in self.flushLoggedErrors(ZeroDivisionError)]))
+ return d
+
+ def testEverythingThere(self):
+ trans = 'TCP UNIX SSL UDP UNIXDatagram Multicast'.split()
+ for tran in trans[:]:
+ if not getattr(interfaces, "IReactor"+tran)(reactor, None):
+ trans.remove(tran)
+ if interfaces.IReactorArbitrary(reactor, None) is not None:
+ trans.insert(0, "Generic")
+ for tran in trans:
+ for side in 'Server Client'.split():
+ if tran == "Multicast" and side == "Client":
+ continue
+ self.assert_(hasattr(internet, tran+side))
+ method = getattr(internet, tran+side).method
+ prefix = {'Server': 'listen', 'Client': 'connect'}[side]
+ self.assert_(hasattr(reactor, prefix+method) or
+ (prefix == "connect" and method == "UDP"))
+ o = getattr(internet, tran+side)()
+ self.assertEqual(service.IService(o), o)
+
+
+ def test_reactorParametrizationInServer(self):
+ """
+ L{internet._AbstractServer} supports a C{reactor} keyword argument
+ that can be used to parametrize the reactor used to listen for
+ connections.
+ """
+ reactor = MemoryReactor()
+
+ factory = object()
+ t = internet.TCPServer(1234, factory, reactor=reactor)
+ t.startService()
+ self.assertEquals(reactor.tcpServers.pop()[:2], (1234, factory))
+
+
+ def test_reactorParametrizationInClient(self):
+ """
+ L{internet._AbstractClient} supports a C{reactor} keyword arguments
+ that can be used to parametrize the reactor used to create new client
+ connections.
+ """
+ reactor = MemoryReactor()
+
+ factory = object()
+ t = internet.TCPClient('127.0.0.1', 1234, factory, reactor=reactor)
+ t.startService()
+ self.assertEquals(
+ reactor.tcpClients.pop()[:3], ('127.0.0.1', 1234, factory))
+
+
+ def test_reactorParametrizationInServerMultipleStart(self):
+ """
+ Like L{test_reactorParametrizationInServer}, but stop and restart the
+ service and check that the given reactor is still used.
+ """
+ reactor = MemoryReactor()
+
+ factory = object()
+ t = internet.TCPServer(1234, factory, reactor=reactor)
+ t.startService()
+ self.assertEquals(reactor.tcpServers.pop()[:2], (1234, factory))
+ t.stopService()
+ t.startService()
+ self.assertEquals(reactor.tcpServers.pop()[:2], (1234, factory))
+
+
+ def test_reactorParametrizationInClientMultipleStart(self):
+ """
+ Like L{test_reactorParametrizationInClient}, but stop and restart the
+ service and check that the given reactor is still used.
+ """
+ reactor = MemoryReactor()
+
+ factory = object()
+ t = internet.TCPClient('127.0.0.1', 1234, factory, reactor=reactor)
+ t.startService()
+ self.assertEquals(
+ reactor.tcpClients.pop()[:3], ('127.0.0.1', 1234, factory))
+ t.stopService()
+ t.startService()
+ self.assertEquals(
+ reactor.tcpClients.pop()[:3], ('127.0.0.1', 1234, factory))
+
+
+
+class TestTimerBasic(unittest.TestCase):
+
+ def testTimerRuns(self):
+ d = defer.Deferred()
+ self.t = internet.TimerService(1, d.callback, 'hello')
+ self.t.startService()
+ d.addCallback(self.assertEqual, 'hello')
+ d.addCallback(lambda x : self.t.stopService())
+ d.addCallback(lambda x : self.failIf(self.t.running))
+ return d
+
+ def tearDown(self):
+ return self.t.stopService()
+
+ def testTimerRestart(self):
+ # restart the same TimerService
+ d1 = defer.Deferred()
+ d2 = defer.Deferred()
+ work = [(d2, "bar"), (d1, "foo")]
+ def trigger():
+ d, arg = work.pop()
+ d.callback(arg)
+ self.t = internet.TimerService(1, trigger)
+ self.t.startService()
+ def onFirstResult(result):
+ self.assertEqual(result, 'foo')
+ return self.t.stopService()
+ def onFirstStop(ignored):
+ self.failIf(self.t.running)
+ self.t.startService()
+ return d2
+ def onSecondResult(result):
+ self.assertEqual(result, 'bar')
+ self.t.stopService()
+ d1.addCallback(onFirstResult)
+ d1.addCallback(onFirstStop)
+ d1.addCallback(onSecondResult)
+ return d1
+
+ def testTimerLoops(self):
+ l = []
+ def trigger(data, number, d):
+ l.append(data)
+ if len(l) == number:
+ d.callback(l)
+ d = defer.Deferred()
+ self.t = internet.TimerService(0.01, trigger, "hello", 10, d)
+ self.t.startService()
+ d.addCallback(self.assertEqual, ['hello'] * 10)
+ d.addCallback(lambda x : self.t.stopService())
+ return d
+
+
+class FakeReactor(reactors.Reactor):
+ """
+ A fake reactor with a hooked install method.
+ """
+
+ def __init__(self, install, *args, **kwargs):
+ """
+ @param install: any callable that will be used as install method.
+ @type install: C{callable}
+ """
+ reactors.Reactor.__init__(self, *args, **kwargs)
+ self.install = install
+
+
+
+class PluggableReactorTestCase(unittest.TestCase):
+ """
+ Tests for the reactor discovery/inspection APIs.
+ """
+
+ def setUp(self):
+ """
+ Override the L{reactors.getPlugins} function, normally bound to
+ L{twisted.plugin.getPlugins}, in order to control which
+ L{IReactorInstaller} plugins are seen as available.
+
+ C{self.pluginResults} can be customized and will be used as the
+ result of calls to C{reactors.getPlugins}.
+ """
+ self.pluginCalls = []
+ self.pluginResults = []
+ self.originalFunction = reactors.getPlugins
+ reactors.getPlugins = self._getPlugins
+
+
+ def tearDown(self):
+ """
+ Restore the original L{reactors.getPlugins}.
+ """
+ reactors.getPlugins = self.originalFunction
+
+
+ def _getPlugins(self, interface, package=None):
+ """
+ Stand-in for the real getPlugins method which records its arguments
+ and returns a fixed result.
+ """
+ self.pluginCalls.append((interface, package))
+ return list(self.pluginResults)
+
+
+ def test_getPluginReactorTypes(self):
+ """
+ Test that reactor plugins are returned from L{getReactorTypes}
+ """
+ name = 'fakereactortest'
+ package = __name__ + '.fakereactor'
+ description = 'description'
+ self.pluginResults = [reactors.Reactor(name, package, description)]
+ reactorTypes = reactors.getReactorTypes()
+
+ self.assertEqual(
+ self.pluginCalls,
+ [(reactors.IReactorInstaller, None)])
+
+ for r in reactorTypes:
+ if r.shortName == name:
+ self.assertEqual(r.description, description)
+ break
+ else:
+ self.fail("Reactor plugin not present in getReactorTypes() result")
+
+
+ def test_reactorInstallation(self):
+ """
+ Test that L{reactors.Reactor.install} loads the correct module and
+ calls its install attribute.
+ """
+ installed = []
+ def install():
+ installed.append(True)
+ installer = FakeReactor(install,
+ 'fakereactortest', __name__, 'described')
+ installer.install()
+ self.assertEqual(installed, [True])
+
+
+ def test_installReactor(self):
+ """
+ Test that the L{reactors.installReactor} function correctly installs
+ the specified reactor.
+ """
+ installed = []
+ def install():
+ installed.append(True)
+ name = 'fakereactortest'
+ package = __name__
+ description = 'description'
+ self.pluginResults = [FakeReactor(install, name, package, description)]
+ reactors.installReactor(name)
+ self.assertEqual(installed, [True])
+
+
+ def test_installNonExistentReactor(self):
+ """
+ Test that L{reactors.installReactor} raises L{reactors.NoSuchReactor}
+ when asked to install a reactor which it cannot find.
+ """
+ self.pluginResults = []
+ self.assertRaises(
+ reactors.NoSuchReactor,
+ reactors.installReactor, 'somereactor')
+
+
+ def test_installNotAvailableReactor(self):
+ """
+ Test that L{reactors.installReactor} raises an exception when asked to
+ install a reactor which doesn't work in this environment.
+ """
+ def install():
+ raise ImportError("Missing foo bar")
+ name = 'fakereactortest'
+ package = __name__
+ description = 'description'
+ self.pluginResults = [FakeReactor(install, name, package, description)]
+ self.assertRaises(ImportError, reactors.installReactor, name)
+
+
+ def test_reactorSelectionMixin(self):
+ """
+ Test that the reactor selected is installed as soon as possible, ie
+ when the option is parsed.
+ """
+ executed = []
+ INSTALL_EVENT = 'reactor installed'
+ SUBCOMMAND_EVENT = 'subcommands loaded'
+
+ class ReactorSelectionOptions(usage.Options, app.ReactorSelectionMixin):
+ def subCommands(self):
+ executed.append(SUBCOMMAND_EVENT)
+ return [('subcommand', None, lambda: self, 'test subcommand')]
+ subCommands = property(subCommands)
+
+ def install():
+ executed.append(INSTALL_EVENT)
+ self.pluginResults = [
+ FakeReactor(install, 'fakereactortest', __name__, 'described')
+ ]
+
+ options = ReactorSelectionOptions()
+ options.parseOptions(['--reactor', 'fakereactortest', 'subcommand'])
+ self.assertEqual(executed[0], INSTALL_EVENT)
+ self.assertEqual(executed.count(INSTALL_EVENT), 1)
+
+
+ def test_reactorSelectionMixinNonExistent(self):
+ """
+ Test that the usage mixin exits when trying to use a non existent
+ reactor (the name not matching to any reactor), giving an error
+ message.
+ """
+ class ReactorSelectionOptions(usage.Options, app.ReactorSelectionMixin):
+ pass
+ self.pluginResults = []
+
+ options = ReactorSelectionOptions()
+ options.messageOutput = StringIO()
+ e = self.assertRaises(usage.UsageError, options.parseOptions,
+ ['--reactor', 'fakereactortest', 'subcommand'])
+ self.assertIn("fakereactortest", e.args[0])
+ self.assertIn("help-reactors", e.args[0])
+
+
+ def test_reactorSelectionMixinNotAvailable(self):
+ """
+ Test that the usage mixin exits when trying to use a reactor not
+ available (the reactor raises an error at installation), giving an
+ error message.
+ """
+ class ReactorSelectionOptions(usage.Options, app.ReactorSelectionMixin):
+ pass
+ message = "Missing foo bar"
+ def install():
+ raise ImportError(message)
+
+ name = 'fakereactortest'
+ package = __name__
+ description = 'description'
+ self.pluginResults = [FakeReactor(install, name, package, description)]
+
+ options = ReactorSelectionOptions()
+ options.messageOutput = StringIO()
+ e = self.assertRaises(usage.UsageError, options.parseOptions,
+ ['--reactor', 'fakereactortest', 'subcommand'])
+ self.assertIn(message, e.args[0])
+ self.assertIn("help-reactors", e.args[0])
+
+
+
+class ReportProfileTestCase(unittest.TestCase):
+ """
+ Tests for L{app.reportProfile}.
+ """
+
+ def test_deprecation(self):
+ """
+ Check that L{app.reportProfile} prints a warning and does nothing else.
+ """
+ self.assertWarns(DeprecationWarning,
+ "reportProfile is deprecated and a no-op since Twisted 8.0.",
+ app.__file__, app.reportProfile, None, None)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_banana.py b/vendor/Twisted-10.0.0/twisted/test/test_banana.py
new file mode 100644
index 0000000000..370ffb9f2d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_banana.py
@@ -0,0 +1,278 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import StringIO
+import sys
+
+# Twisted Imports
+from twisted.trial import unittest
+from twisted.spread import banana
+from twisted.python import failure
+from twisted.internet import protocol, main
+
+
+class MathTestCase(unittest.TestCase):
+ def testInt2b128(self):
+ funkylist = range(0,100) + range(1000,1100) + range(1000000,1000100) + [1024 **10l]
+ for i in funkylist:
+ x = StringIO.StringIO()
+ banana.int2b128(i, x.write)
+ v = x.getvalue()
+ y = banana.b1282int(v)
+ assert y == i, "y = %s; i = %s" % (y,i)
+
+class BananaTestCase(unittest.TestCase):
+
+ encClass = banana.Banana
+
+ def setUp(self):
+ self.io = StringIO.StringIO()
+ self.enc = self.encClass()
+ self.enc.makeConnection(protocol.FileWrapper(self.io))
+ self.enc._selectDialect("none")
+ self.enc.expressionReceived = self.putResult
+
+ def putResult(self, result):
+ self.result = result
+
+ def tearDown(self):
+ self.enc.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ del self.enc
+
+ def testString(self):
+ self.enc.sendEncoded("hello")
+ l = []
+ self.enc.dataReceived(self.io.getvalue())
+ assert self.result == 'hello'
+
+ def test_int(self):
+ """
+ A positive integer less than 2 ** 32 should round-trip through
+ banana without changing value and should come out represented
+ as an C{int} (regardless of the type which was encoded).
+ """
+ for value in (10151, 10151L):
+ self.enc.sendEncoded(value)
+ self.enc.dataReceived(self.io.getvalue())
+ self.assertEquals(self.result, 10151)
+ self.assertIsInstance(self.result, int)
+
+
+ def test_largeLong(self):
+ """
+ Integers greater than 2 ** 32 and less than -2 ** 32 should
+ round-trip through banana without changing value and should
+ come out represented as C{int} instances if the value fits
+ into that type on the receiving platform.
+ """
+ for exp in (32, 64, 128, 256):
+ for add in (0, 1):
+ m = 2 ** exp + add
+ for n in (m, -m-1):
+ self.io.truncate(0)
+ self.enc.sendEncoded(n)
+ self.enc.dataReceived(self.io.getvalue())
+ self.assertEquals(self.result, n)
+ if n > sys.maxint or n < -sys.maxint - 1:
+ self.assertIsInstance(self.result, long)
+ else:
+ self.assertIsInstance(self.result, int)
+
+
+ def _getSmallest(self):
+ # How many bytes of prefix our implementation allows
+ bytes = self.enc.prefixLimit
+ # How many useful bits we can extract from that based on Banana's
+ # base-128 representation.
+ bits = bytes * 7
+ # The largest number we _should_ be able to encode
+ largest = 2 ** bits - 1
+ # The smallest number we _shouldn't_ be able to encode
+ smallest = largest + 1
+ return smallest
+
+
+ def test_encodeTooLargeLong(self):
+ """
+ Test that a long above the implementation-specific limit is rejected
+ as too large to be encoded.
+ """
+ smallest = self._getSmallest()
+ self.assertRaises(banana.BananaError, self.enc.sendEncoded, smallest)
+
+
+ def test_decodeTooLargeLong(self):
+ """
+ Test that a long above the implementation specific limit is rejected
+ as too large to be decoded.
+ """
+ smallest = self._getSmallest()
+ self.enc.setPrefixLimit(self.enc.prefixLimit * 2)
+ self.enc.sendEncoded(smallest)
+ encoded = self.io.getvalue()
+ self.io.truncate(0)
+ self.enc.setPrefixLimit(self.enc.prefixLimit / 2)
+
+ self.assertRaises(banana.BananaError, self.enc.dataReceived, encoded)
+
+
+ def _getLargest(self):
+ return -self._getSmallest()
+
+
+ def test_encodeTooSmallLong(self):
+ """
+ Test that a negative long below the implementation-specific limit is
+ rejected as too small to be encoded.
+ """
+ largest = self._getLargest()
+ self.assertRaises(banana.BananaError, self.enc.sendEncoded, largest)
+
+
+ def test_decodeTooSmallLong(self):
+ """
+ Test that a negative long below the implementation specific limit is
+ rejected as too small to be decoded.
+ """
+ largest = self._getLargest()
+ self.enc.setPrefixLimit(self.enc.prefixLimit * 2)
+ self.enc.sendEncoded(largest)
+ encoded = self.io.getvalue()
+ self.io.truncate(0)
+ self.enc.setPrefixLimit(self.enc.prefixLimit / 2)
+
+ self.assertRaises(banana.BananaError, self.enc.dataReceived, encoded)
+
+
+ def testNegativeLong(self):
+ self.enc.sendEncoded(-1015l)
+ self.enc.dataReceived(self.io.getvalue())
+ assert self.result == -1015l, "should be -1015l, got %s" % self.result
+
+ def testInteger(self):
+ self.enc.sendEncoded(1015)
+ self.enc.dataReceived(self.io.getvalue())
+ assert self.result == 1015, "should be 1015, got %s" % self.result
+
+ def testNegative(self):
+ self.enc.sendEncoded(-1015)
+ self.enc.dataReceived(self.io.getvalue())
+ assert self.result == -1015, "should be -1015, got %s" % self.result
+
+ def testFloat(self):
+ self.enc.sendEncoded(1015.)
+ self.enc.dataReceived(self.io.getvalue())
+ assert self.result == 1015.
+
+ def testList(self):
+ foo = [1, 2, [3, 4], [30.5, 40.2], 5, ["six", "seven", ["eight", 9]], [10], []]
+ self.enc.sendEncoded(foo)
+ self.enc.dataReceived(self.io.getvalue())
+ assert self.result == foo, "%s!=%s" % (repr(self.result), repr(self.result))
+
+ def testPartial(self):
+ foo = [1, 2, [3, 4], [30.5, 40.2], 5,
+ ["six", "seven", ["eight", 9]], [10],
+ # TODO: currently the C implementation's a bit buggy...
+ sys.maxint * 3l, sys.maxint * 2l, sys.maxint * -2l]
+ self.enc.sendEncoded(foo)
+ for byte in self.io.getvalue():
+ self.enc.dataReceived(byte)
+ assert self.result == foo, "%s!=%s" % (repr(self.result), repr(foo))
+
+ def feed(self, data):
+ for byte in data:
+ self.enc.dataReceived(byte)
+ def testOversizedList(self):
+ data = '\x02\x01\x01\x01\x01\x80'
+ # list(size=0x0101010102, about 4.3e9)
+ self.failUnlessRaises(banana.BananaError, self.feed, data)
+ def testOversizedString(self):
+ data = '\x02\x01\x01\x01\x01\x82'
+ # string(size=0x0101010102, about 4.3e9)
+ self.failUnlessRaises(banana.BananaError, self.feed, data)
+
+ def testCrashString(self):
+ crashString = '\x00\x00\x00\x00\x04\x80'
+ # string(size=0x0400000000, about 17.2e9)
+
+ # cBanana would fold that into a 32-bit 'int', then try to allocate
+ # a list with PyList_New(). cBanana ignored the NULL return value,
+ # so it would segfault when trying to free the imaginary list.
+
+ # This variant doesn't segfault straight out in my environment.
+ # Instead, it takes up large amounts of CPU and memory...
+ #crashString = '\x00\x00\x00\x00\x01\x80'
+ # print repr(crashString)
+ #self.failUnlessRaises(Exception, self.enc.dataReceived, crashString)
+ try:
+ # should now raise MemoryError
+ self.enc.dataReceived(crashString)
+ except banana.BananaError:
+ pass
+
+ def testCrashNegativeLong(self):
+ # There was a bug in cBanana which relied on negating a negative integer
+ # always giving a postive result, but for the lowest possible number in
+ # 2s-complement arithmetic, that's not true, i.e.
+ # long x = -2147483648;
+ # long y = -x;
+ # x == y; /* true! */
+ # (assuming 32-bit longs)
+ self.enc.sendEncoded(-2147483648)
+ self.enc.dataReceived(self.io.getvalue())
+ assert self.result == -2147483648, "should be -2147483648, got %s" % self.result
+
+
+ def test_sizedIntegerTypes(self):
+ """
+ Test that integers below the maximum C{INT} token size cutoff are
+ serialized as C{INT} or C{NEG} and that larger integers are
+ serialized as C{LONGINT} or C{LONGNEG}.
+ """
+ def encoded(n):
+ self.io.seek(0)
+ self.io.truncate()
+ self.enc.sendEncoded(n)
+ return self.io.getvalue()
+
+ baseIntIn = +2147483647
+ baseNegIn = -2147483648
+
+ baseIntOut = '\x7f\x7f\x7f\x07\x81'
+ self.assertEqual(encoded(baseIntIn - 2), '\x7d' + baseIntOut)
+ self.assertEqual(encoded(baseIntIn - 1), '\x7e' + baseIntOut)
+ self.assertEqual(encoded(baseIntIn - 0), '\x7f' + baseIntOut)
+
+ baseLongIntOut = '\x00\x00\x00\x08\x85'
+ self.assertEqual(encoded(baseIntIn + 1), '\x00' + baseLongIntOut)
+ self.assertEqual(encoded(baseIntIn + 2), '\x01' + baseLongIntOut)
+ self.assertEqual(encoded(baseIntIn + 3), '\x02' + baseLongIntOut)
+
+ baseNegOut = '\x7f\x7f\x7f\x07\x83'
+ self.assertEqual(encoded(baseNegIn + 2), '\x7e' + baseNegOut)
+ self.assertEqual(encoded(baseNegIn + 1), '\x7f' + baseNegOut)
+ self.assertEqual(encoded(baseNegIn + 0), '\x00\x00\x00\x00\x08\x83')
+
+ baseLongNegOut = '\x00\x00\x00\x08\x86'
+ self.assertEqual(encoded(baseNegIn - 1), '\x01' + baseLongNegOut)
+ self.assertEqual(encoded(baseNegIn - 2), '\x02' + baseLongNegOut)
+ self.assertEqual(encoded(baseNegIn - 3), '\x03' + baseLongNegOut)
+
+
+
+class GlobalCoderTests(unittest.TestCase):
+ """
+ Tests for the free functions L{banana.encode} and L{banana.decode}.
+ """
+ def test_statelessDecode(self):
+ """
+ Test that state doesn't carry over between calls to L{banana.decode}.
+ """
+ # Banana encoding of 2 ** 449
+ undecodable = '\x7f' * 65 + '\x85'
+ self.assertRaises(banana.BananaError, banana.decode, undecodable)
+
+ # Banana encoding of 1
+ decodable = '\x01\x81'
+ self.assertEqual(banana.decode(decodable), 1)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_compat.py b/vendor/Twisted-10.0.0/twisted/test/test_compat.py
new file mode 100644
index 0000000000..5126f0fcfe
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_compat.py
@@ -0,0 +1,199 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Tests for L{twisted.python.compat}.
+"""
+
+import types, socket
+
+from twisted.trial import unittest
+
+from twisted.python.compat import set, frozenset, reduce
+
+
+
+class IterableCounter:
+ def __init__(self, lim=0):
+ self.lim = lim
+ self.i = -1
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ self.i += 1
+ if self.i >= self.lim:
+ raise StopIteration
+ return self.i
+
+class CompatTestCase(unittest.TestCase):
+ def testDict(self):
+ d1 = {'a': 'b'}
+ d2 = dict(d1)
+ self.assertEquals(d1, d2)
+ d1['a'] = 'c'
+ self.assertNotEquals(d1, d2)
+ d2 = dict(d1.items())
+ self.assertEquals(d1, d2)
+
+ def testBool(self):
+ self.assertEquals(bool('hi'), True)
+ self.assertEquals(bool(True), True)
+ self.assertEquals(bool(''), False)
+ self.assertEquals(bool(False), False)
+
+ def testIteration(self):
+ lst1, lst2 = range(10), []
+
+ for i in iter(lst1):
+ lst2.append(i)
+ self.assertEquals(lst1, lst2)
+ del lst2[:]
+
+ try:
+ iterable = iter(lst1)
+ while 1:
+ lst2.append(iterable.next())
+ except StopIteration:
+ pass
+ self.assertEquals(lst1, lst2)
+ del lst2[:]
+
+ for i in iter(IterableCounter(10)):
+ lst2.append(i)
+ self.assertEquals(lst1, lst2)
+ del lst2[:]
+
+ try:
+ iterable = iter(IterableCounter(10))
+ while 1:
+ lst2.append(iterable.next())
+ except StopIteration:
+ pass
+ self.assertEquals(lst1, lst2)
+ del lst2[:]
+
+ for i in iter(IterableCounter(20).next, 10):
+ lst2.append(i)
+ self.assertEquals(lst1, lst2)
+
+ def testIsinstance(self):
+ self.assert_(isinstance(u'hi', types.StringTypes))
+ self.assert_(isinstance(self, unittest.TestCase))
+ # I'm pretty sure it's impossible to implement this
+ # without replacing isinstance on 2.2 as well :(
+ # self.assert_(isinstance({}, dict))
+
+ def testStrip(self):
+ self.assertEquals(' x '.lstrip(' '), 'x ')
+ self.assertEquals(' x x'.lstrip(' '), 'x x')
+ self.assertEquals(' x '.rstrip(' '), ' x')
+ self.assertEquals('x x '.rstrip(' '), 'x x')
+
+ self.assertEquals('\t x '.lstrip('\t '), 'x ')
+ self.assertEquals(' \tx x'.lstrip('\t '), 'x x')
+ self.assertEquals(' x\t '.rstrip(' \t'), ' x')
+ self.assertEquals('x x \t'.rstrip(' \t'), 'x x')
+
+ self.assertEquals('\t x '.strip('\t '), 'x')
+ self.assertEquals(' \tx x'.strip('\t '), 'x x')
+ self.assertEquals(' x\t '.strip(' \t'), 'x')
+ self.assertEquals('x x \t'.strip(' \t'), 'x x')
+
+ def testNToP(self):
+ from twisted.python.compat import inet_ntop
+
+ f = lambda a: inet_ntop(socket.AF_INET6, a)
+ g = lambda a: inet_ntop(socket.AF_INET, a)
+
+ self.assertEquals('::', f('\x00' * 16))
+ self.assertEquals('::1', f('\x00' * 15 + '\x01'))
+ self.assertEquals(
+ 'aef:b01:506:1001:ffff:9997:55:170',
+ f('\x0a\xef\x0b\x01\x05\x06\x10\x01\xff\xff\x99\x97\x00\x55\x01\x70'))
+
+ self.assertEquals('1.0.1.0', g('\x01\x00\x01\x00'))
+ self.assertEquals('170.85.170.85', g('\xaa\x55\xaa\x55'))
+ self.assertEquals('255.255.255.255', g('\xff\xff\xff\xff'))
+
+ self.assertEquals('100::', f('\x01' + '\x00' * 15))
+ self.assertEquals('100::1', f('\x01' + '\x00' * 14 + '\x01'))
+
+ def testPToN(self):
+ from twisted.python.compat import inet_pton
+
+ f = lambda a: inet_pton(socket.AF_INET6, a)
+ g = lambda a: inet_pton(socket.AF_INET, a)
+
+ self.assertEquals('\x00\x00\x00\x00', g('0.0.0.0'))
+ self.assertEquals('\xff\x00\xff\x00', g('255.0.255.0'))
+ self.assertEquals('\xaa\xaa\xaa\xaa', g('170.170.170.170'))
+
+ self.assertEquals('\x00' * 16, f('::'))
+ self.assertEquals('\x00' * 16, f('0::0'))
+ self.assertEquals('\x00\x01' + '\x00' * 14, f('1::'))
+ self.assertEquals(
+ '\x45\xef\x76\xcb\x00\x1a\x56\xef\xaf\xeb\x0b\xac\x19\x24\xae\xae',
+ f('45ef:76cb:1a:56ef:afeb:bac:1924:aeae'))
+
+ self.assertEquals('\x00' * 14 + '\x00\x01', f('::1'))
+ self.assertEquals('\x00' * 12 + '\x01\x02\x03\x04', f('::1.2.3.4'))
+ self.assertEquals(
+ '\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x01\x02\x03\xff',
+ f('1:2:3:4:5:6:1.2.3.255'))
+
+ for badaddr in ['1:2:3:4:5:6:7:8:', ':1:2:3:4:5:6:7:8', '1::2::3',
+ '1:::3', ':::', '1:2', '::1.2', '1.2.3.4::',
+ 'abcd:1.2.3.4:abcd:abcd:abcd:abcd:abcd',
+ '1234:1.2.3.4:1234:1234:1234:1234:1234:1234',
+ '1.2.3.4']:
+ self.assertRaises(ValueError, f, badaddr)
+
+ def test_set(self):
+ """
+ L{set} should behave like the expected set interface.
+ """
+ a = set()
+ a.add('b')
+ a.add('c')
+ a.add('a')
+ b = list(a)
+ b.sort()
+ self.assertEquals(b, ['a', 'b', 'c'])
+ a.remove('b')
+ b = list(a)
+ b.sort()
+ self.assertEquals(b, ['a', 'c'])
+
+ a.discard('d')
+
+ b = set(['r', 's'])
+ d = a.union(b)
+ b = list(d)
+ b.sort()
+ self.assertEquals(b, ['a', 'c', 'r', 's'])
+
+
+ def test_frozenset(self):
+ """
+ L{frozenset} should behave like the expected frozenset interface.
+ """
+ a = frozenset(['a', 'b'])
+ self.assertRaises(AttributeError, getattr, a, "add")
+ self.assertEquals(list(a), ['a', 'b'])
+
+ b = frozenset(['r', 's'])
+ d = a.union(b)
+ b = list(d)
+ b.sort()
+ self.assertEquals(b, ['a', 'b', 'r', 's'])
+
+
+ def test_reduce(self):
+ """
+ L{reduce} should behave like the builtin reduce.
+ """
+ self.assertEquals(15, reduce(lambda x, y: x + y, [1, 2, 3, 4, 5]))
+ self.assertEquals(16, reduce(lambda x, y: x + y, [1, 2, 3, 4, 5], 1))
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_context.py b/vendor/Twisted-10.0.0/twisted/test/test_context.py
new file mode 100644
index 0000000000..1f03daef8e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_context.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+from twisted.trial.unittest import TestCase
+
+from twisted.python import context
+
+class ContextTest(TestCase):
+
+ def testBasicContext(self):
+ self.assertEquals(context.get("x"), None)
+ self.assertEquals(context.call({"x": "y"}, context.get, "x"), "y")
+ self.assertEquals(context.get("x"), None)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_cooperator.py b/vendor/Twisted-10.0.0/twisted/test/test_cooperator.py
new file mode 100644
index 0000000000..0a28a8593a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_cooperator.py
@@ -0,0 +1,634 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module contains tests for L{twisted.internet.task.Cooperator} and
+related functionality.
+"""
+
+from twisted.internet import reactor, defer, task
+from twisted.trial import unittest
+
+
+
+class FakeDelayedCall(object):
+ """
+ Fake delayed call which lets us simulate the scheduler.
+ """
+ def __init__(self, func):
+ """
+ A function to run, later.
+ """
+ self.func = func
+ self.cancelled = False
+
+
+ def cancel(self):
+ """
+ Don't run my function later.
+ """
+ self.cancelled = True
+
+
+
+class FakeScheduler(object):
+ """
+ A fake scheduler for testing against.
+ """
+ def __init__(self):
+ """
+ Create a fake scheduler with a list of work to do.
+ """
+ self.work = []
+
+
+ def __call__(self, thunk):
+ """
+ Schedule a unit of work to be done later.
+ """
+ unit = FakeDelayedCall(thunk)
+ self.work.append(unit)
+ return unit
+
+
+ def pump(self):
+ """
+ Do all of the work that is currently available to be done.
+ """
+ work, self.work = self.work, []
+ for unit in work:
+ if not unit.cancelled:
+ unit.func()
+
+
+
+class TestCooperator(unittest.TestCase):
+ RESULT = 'done'
+
+ def ebIter(self, err):
+ err.trap(task.SchedulerStopped)
+ return self.RESULT
+
+
+ def cbIter(self, ign):
+ self.fail()
+
+
+ def testStoppedRejectsNewTasks(self):
+ """
+ Test that Cooperators refuse new tasks when they have been stopped.
+ """
+ def testwith(stuff):
+ c = task.Cooperator()
+ c.stop()
+ d = c.coiterate(iter(()), stuff)
+ d.addCallback(self.cbIter)
+ d.addErrback(self.ebIter)
+ return d.addCallback(lambda result:
+ self.assertEquals(result, self.RESULT))
+ return testwith(None).addCallback(lambda ign: testwith(defer.Deferred()))
+
+
+ def testStopRunning(self):
+ """
+ Test that a running iterator will not run to completion when the
+ cooperator is stopped.
+ """
+ c = task.Cooperator()
+ def myiter():
+ for myiter.value in range(3):
+ yield myiter.value
+ myiter.value = -1
+ d = c.coiterate(myiter())
+ d.addCallback(self.cbIter)
+ d.addErrback(self.ebIter)
+ c.stop()
+ def doasserts(result):
+ self.assertEquals(result, self.RESULT)
+ self.assertEquals(myiter.value, -1)
+ d.addCallback(doasserts)
+ return d
+
+
+ def testStopOutstanding(self):
+ """
+ An iterator run with L{Cooperator.coiterate} paused on a L{Deferred}
+ yielded by that iterator will fire its own L{Deferred} (the one
+ returned by C{coiterate}) when L{Cooperator.stop} is called.
+ """
+ testControlD = defer.Deferred()
+ outstandingD = defer.Deferred()
+ def myiter():
+ reactor.callLater(0, testControlD.callback, None)
+ yield outstandingD
+ self.fail()
+ c = task.Cooperator()
+ d = c.coiterate(myiter())
+ def stopAndGo(ign):
+ c.stop()
+ outstandingD.callback('arglebargle')
+
+ testControlD.addCallback(stopAndGo)
+ d.addCallback(self.cbIter)
+ d.addErrback(self.ebIter)
+
+ return d.addCallback(
+ lambda result: self.assertEquals(result, self.RESULT))
+
+
+ def testUnexpectedError(self):
+ c = task.Cooperator()
+ def myiter():
+ if 0:
+ yield None
+ else:
+ raise RuntimeError()
+ d = c.coiterate(myiter())
+ return self.assertFailure(d, RuntimeError)
+
+
+ def testUnexpectedErrorActuallyLater(self):
+ def myiter():
+ D = defer.Deferred()
+ reactor.callLater(0, D.errback, RuntimeError())
+ yield D
+
+ c = task.Cooperator()
+ d = c.coiterate(myiter())
+ return self.assertFailure(d, RuntimeError)
+
+
+ def testUnexpectedErrorNotActuallyLater(self):
+ def myiter():
+ yield defer.fail(RuntimeError())
+
+ c = task.Cooperator()
+ d = c.coiterate(myiter())
+ return self.assertFailure(d, RuntimeError)
+
+
+ def testCooperation(self):
+ L = []
+ def myiter(things):
+ for th in things:
+ L.append(th)
+ yield None
+
+ groupsOfThings = ['abc', (1, 2, 3), 'def', (4, 5, 6)]
+
+ c = task.Cooperator()
+ tasks = []
+ for stuff in groupsOfThings:
+ tasks.append(c.coiterate(myiter(stuff)))
+
+ return defer.DeferredList(tasks).addCallback(
+ lambda ign: self.assertEquals(tuple(L), sum(zip(*groupsOfThings), ())))
+
+
+ def testResourceExhaustion(self):
+ output = []
+ def myiter():
+ for i in range(100):
+ output.append(i)
+ if i == 9:
+ _TPF.stopped = True
+ yield i
+
+ class _TPF:
+ stopped = False
+ def __call__(self):
+ return self.stopped
+
+ c = task.Cooperator(terminationPredicateFactory=_TPF)
+ c.coiterate(myiter()).addErrback(self.ebIter)
+ c._delayedCall.cancel()
+ # testing a private method because only the test case will ever care
+ # about this, so we have to carefully clean up after ourselves.
+ c._tick()
+ c.stop()
+ self.failUnless(_TPF.stopped)
+ self.assertEquals(output, range(10))
+
+
+ def testCallbackReCoiterate(self):
+ """
+ If a callback to a deferred returned by coiterate calls coiterate on
+ the same Cooperator, we should make sure to only do the minimal amount
+ of scheduling work. (This test was added to demonstrate a specific bug
+ that was found while writing the scheduler.)
+ """
+ calls = []
+
+ class FakeCall:
+ def __init__(self, func):
+ self.func = func
+
+ def __repr__(self):
+ return '<FakeCall %r>' % (self.func,)
+
+ def sched(f):
+ self.failIf(calls, repr(calls))
+ calls.append(FakeCall(f))
+ return calls[-1]
+
+ c = task.Cooperator(scheduler=sched, terminationPredicateFactory=lambda: lambda: True)
+ d = c.coiterate(iter(()))
+
+ done = []
+ def anotherTask(ign):
+ c.coiterate(iter(())).addBoth(done.append)
+
+ d.addCallback(anotherTask)
+
+ work = 0
+ while not done:
+ work += 1
+ while calls:
+ calls.pop(0).func()
+ work += 1
+ if work > 50:
+ self.fail("Cooperator took too long")
+
+
+
+class UnhandledException(Exception):
+ """
+ An exception that should go unhandled.
+ """
+
+
+
+class AliasTests(unittest.TestCase):
+ """
+ Integration test to verify that the global singleton aliases do what
+ they're supposed to.
+ """
+
+ def test_cooperate(self):
+ """
+ L{twisted.internet.task.cooperate} ought to run the generator that it is
+ """
+ d = defer.Deferred()
+ def doit():
+ yield 1
+ yield 2
+ yield 3
+ d.callback("yay")
+ it = doit()
+ theTask = task.cooperate(it)
+ self.assertIn(theTask, task._theCooperator._tasks)
+ return d
+
+
+
+class RunStateTests(unittest.TestCase):
+ """
+ Tests to verify the behavior of L{CooperativeTask.pause},
+ L{CooperativeTask.resume}, L{CooperativeTask.stop}, exhausting the
+ underlying iterator, and their interactions with each other.
+ """
+
+ def setUp(self):
+ """
+ Create a cooperator with a fake scheduler and a termination predicate
+ that ensures only one unit of work will take place per tick.
+ """
+ self._doDeferNext = False
+ self._doStopNext = False
+ self._doDieNext = False
+ self.work = []
+ self.scheduler = FakeScheduler()
+ self.cooperator = task.Cooperator(
+ scheduler=self.scheduler,
+ # Always stop after one iteration of work (return a function which
+ # returns a function which always returns True)
+ terminationPredicateFactory=lambda: lambda: True)
+ self.task = self.cooperator.cooperate(self.worker())
+ self.cooperator.start()
+
+
+ def worker(self):
+ """
+ This is a sample generator which yields Deferreds when we are testing
+ deferral and an ascending integer count otherwise.
+ """
+ i = 0
+ while True:
+ i += 1
+ if self._doDeferNext:
+ self._doDeferNext = False
+ d = defer.Deferred()
+ self.work.append(d)
+ yield d
+ elif self._doStopNext:
+ return
+ elif self._doDieNext:
+ raise UnhandledException()
+ else:
+ self.work.append(i)
+ yield i
+
+
+ def tearDown(self):
+ """
+ Drop references to interesting parts of the fixture to allow Deferred
+ errors to be noticed when things start failing.
+ """
+ del self.task
+ del self.scheduler
+
+
+ def deferNext(self):
+ """
+ Defer the next result from my worker iterator.
+ """
+ self._doDeferNext = True
+
+
+ def stopNext(self):
+ """
+ Make the next result from my worker iterator be completion (raising
+ StopIteration).
+ """
+ self._doStopNext = True
+
+
+ def dieNext(self):
+ """
+ Make the next result from my worker iterator be raising an
+ L{UnhandledException}.
+ """
+ def ignoreUnhandled(failure):
+ failure.trap(UnhandledException)
+ return None
+ self._doDieNext = True
+
+
+ def test_pauseResume(self):
+ """
+ Cooperators should stop running their tasks when they're paused, and
+ start again when they're resumed.
+ """
+ # first, sanity check
+ self.scheduler.pump()
+ self.assertEquals(self.work, [1])
+ self.scheduler.pump()
+ self.assertEquals(self.work, [1, 2])
+
+ # OK, now for real
+ self.task.pause()
+ self.scheduler.pump()
+ self.assertEquals(self.work, [1, 2])
+ self.task.resume()
+ # Resuming itself shoult not do any work
+ self.assertEquals(self.work, [1, 2])
+ self.scheduler.pump()
+ # But when the scheduler rolls around again...
+ self.assertEquals(self.work, [1, 2, 3])
+
+
+ def test_resumeNotPaused(self):
+ """
+ L{CooperativeTask.resume} should raise a L{TaskNotPaused} exception if
+ it was not paused; e.g. if L{CooperativeTask.pause} was not invoked
+ more times than L{CooperativeTask.resume} on that object.
+ """
+ self.assertRaises(task.NotPaused, self.task.resume)
+ self.task.pause()
+ self.task.resume()
+ self.assertRaises(task.NotPaused, self.task.resume)
+
+
+ def test_pauseTwice(self):
+ """
+ Pauses on tasks should behave like a stack. If a task is paused twice,
+ it needs to be resumed twice.
+ """
+ # pause once
+ self.task.pause()
+ self.scheduler.pump()
+ self.assertEquals(self.work, [])
+ # pause twice
+ self.task.pause()
+ self.scheduler.pump()
+ self.assertEquals(self.work, [])
+ # resume once (it shouldn't)
+ self.task.resume()
+ self.scheduler.pump()
+ self.assertEquals(self.work, [])
+ # resume twice (now it should go)
+ self.task.resume()
+ self.scheduler.pump()
+ self.assertEquals(self.work, [1])
+
+
+ def test_pauseWhileDeferred(self):
+ """
+ C{pause()}ing a task while it is waiting on an outstanding
+ L{defer.Deferred} should put the task into a state where the
+ outstanding L{defer.Deferred} must be called back I{and} the task is
+ C{resume}d before it will continue processing.
+ """
+ self.deferNext()
+ self.scheduler.pump()
+ self.assertEquals(len(self.work), 1)
+ self.failUnless(isinstance(self.work[0], defer.Deferred))
+ self.scheduler.pump()
+ self.assertEquals(len(self.work), 1)
+ self.task.pause()
+ self.scheduler.pump()
+ self.assertEquals(len(self.work), 1)
+ self.task.resume()
+ self.scheduler.pump()
+ self.assertEquals(len(self.work), 1)
+ self.work[0].callback("STUFF!")
+ self.scheduler.pump()
+ self.assertEquals(len(self.work), 2)
+ self.assertEquals(self.work[1], 2)
+
+
+ def test_whenDone(self):
+ """
+ L{CooperativeTask.whenDone} returns a Deferred which fires when the
+ Cooperator's iterator is exhausted. It returns a new Deferred each
+ time it is called; callbacks added to other invocations will not modify
+ the value that subsequent invocations will fire with.
+ """
+
+ deferred1 = self.task.whenDone()
+ deferred2 = self.task.whenDone()
+ results1 = []
+ results2 = []
+ final1 = []
+ final2 = []
+
+ def callbackOne(result):
+ results1.append(result)
+ return 1
+
+ def callbackTwo(result):
+ results2.append(result)
+ return 2
+
+ deferred1.addCallback(callbackOne)
+ deferred2.addCallback(callbackTwo)
+
+ deferred1.addCallback(final1.append)
+ deferred2.addCallback(final2.append)
+
+ # exhaust the task iterator
+ # callbacks fire
+ self.stopNext()
+ self.scheduler.pump()
+
+ self.assertEquals(len(results1), 1)
+ self.assertEquals(len(results2), 1)
+
+ self.assertIdentical(results1[0], self.task._iterator)
+ self.assertIdentical(results2[0], self.task._iterator)
+
+ self.assertEquals(final1, [1])
+ self.assertEquals(final2, [2])
+
+
+ def test_whenDoneError(self):
+ """
+ L{CooperativeTask.whenDone} returns a L{defer.Deferred} that will fail
+ when the iterable's C{next} method raises an exception, with that
+ exception.
+ """
+ deferred1 = self.task.whenDone()
+ results = []
+ deferred1.addErrback(results.append)
+ self.dieNext()
+ self.scheduler.pump()
+ self.assertEquals(len(results), 1)
+ self.assertEquals(results[0].check(UnhandledException), UnhandledException)
+
+
+ def test_whenDoneStop(self):
+ """
+ L{CooperativeTask.whenDone} returns a L{defer.Deferred} that fails with
+ L{TaskStopped} when the C{stop} method is called on that
+ L{CooperativeTask}.
+ """
+ deferred1 = self.task.whenDone()
+ errors = []
+ deferred1.addErrback(errors.append)
+ self.task.stop()
+ self.assertEquals(len(errors), 1)
+ self.assertEquals(errors[0].check(task.TaskStopped), task.TaskStopped)
+
+
+ def test_whenDoneAlreadyDone(self):
+ """
+ L{CooperativeTask.whenDone} will return a L{defer.Deferred} that will
+ succeed immediately if its iterator has already completed.
+ """
+ self.stopNext()
+ self.scheduler.pump()
+ results = []
+ self.task.whenDone().addCallback(results.append)
+ self.assertEquals(results, [self.task._iterator])
+
+
+ def test_stopStops(self):
+ """
+ C{stop()}ping a task should cause it to be removed from the run just as
+ C{pause()}ing, with the distinction that C{resume()} will raise a
+ L{TaskStopped} exception.
+ """
+ self.task.stop()
+ self.scheduler.pump()
+ self.assertEquals(len(self.work), 0)
+ self.assertRaises(task.TaskStopped, self.task.stop)
+ self.assertRaises(task.TaskStopped, self.task.pause)
+ # Sanity check - it's still not scheduled, is it?
+ self.scheduler.pump()
+ self.assertEquals(self.work, [])
+
+
+ def test_pauseStopResume(self):
+ """
+ C{resume()}ing a paused, stopped task should be a no-op; it should not
+ raise an exception, because it's paused, but neither should it actually
+ do more work from the task.
+ """
+ self.task.pause()
+ self.task.stop()
+ self.task.resume()
+ self.scheduler.pump()
+ self.assertEquals(self.work, [])
+
+
+ def test_stopDeferred(self):
+ """
+ As a corrolary of the interaction of C{pause()} and C{unpause()},
+ C{stop()}ping a task which is waiting on a L{Deferred} should cause the
+ task to gracefully shut down, meaning that it should not be unpaused
+ when the deferred fires.
+ """
+ self.deferNext()
+ self.scheduler.pump()
+ d = self.work.pop()
+ self.assertEquals(self.task._pauseCount, 1)
+ results = []
+ d.addBoth(results.append)
+ self.scheduler.pump()
+ self.task.stop()
+ self.scheduler.pump()
+ d.callback(7)
+ self.scheduler.pump()
+ # Let's make sure that Deferred doesn't come out fried with an
+ # unhandled error that will be logged. The value is None, rather than
+ # our test value, 7, because this Deferred is returned to and consumed
+ # by the cooperator code. Its callback therefore has no contract.
+ self.assertEquals(results, [None])
+ # But more importantly, no further work should have happened.
+ self.assertEquals(self.work, [])
+
+
+ def test_stopExhausted(self):
+ """
+ C{stop()}ping a L{CooperativeTask} whose iterator has been exhausted
+ should raise L{TaskDone}.
+ """
+ self.stopNext()
+ self.scheduler.pump()
+ self.assertRaises(task.TaskDone, self.task.stop)
+
+
+ def test_stopErrored(self):
+ """
+ C{stop()}ping a L{CooperativeTask} whose iterator has encountered an
+ error should raise L{TaskFailed}.
+ """
+ self.dieNext()
+ self.scheduler.pump()
+ self.assertRaises(task.TaskFailed, self.task.stop)
+
+
+ def test_stopCooperatorReentrancy(self):
+ """
+ If a callback of a L{Deferred} from L{CooperativeTask.whenDone} calls
+ C{Cooperator.stop} on its L{CooperativeTask._cooperator}, the
+ L{Cooperator} will stop, but the L{CooperativeTask} whose callback is
+ calling C{stop} should already be considered 'stopped' by the time the
+ callback is running, and therefore removed from the
+ L{CoooperativeTask}.
+ """
+ callbackPhases = []
+ def stopit(result):
+ callbackPhases.append(result)
+ self.cooperator.stop()
+ # "done" here is a sanity check to make sure that we get all the
+ # way through the callback; i.e. stop() shouldn't be raising an
+ # exception due to the stopped-ness of our main task.
+ callbackPhases.append("done")
+ self.task.whenDone().addCallback(stopit)
+ self.stopNext()
+ self.scheduler.pump()
+ self.assertEquals(callbackPhases, [self.task._iterator, "done"])
+
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_defer.py b/vendor/Twisted-10.0.0/twisted/test/test_defer.py
new file mode 100644
index 0000000000..8bcac05793
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_defer.py
@@ -0,0 +1,950 @@
+
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Test cases for defer module.
+"""
+
+import gc
+
+from twisted.trial import unittest, util
+from twisted.internet import reactor, defer
+from twisted.python import failure, log
+
+from twisted.internet.task import Clock
+
+class GenericError(Exception):
+ pass
+
+
+_setTimeoutSuppression = util.suppress(
+ message="Deferred.setTimeout is deprecated. Look for timeout "
+ "support specific to the API you are using instead.",
+ category=DeprecationWarning)
+
+
+class DeferredTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.callback_results = None
+ self.errback_results = None
+ self.callback2_results = None
+
+ def _callback(self, *args, **kw):
+ self.callback_results = args, kw
+ return args[0]
+
+ def _callback2(self, *args, **kw):
+ self.callback2_results = args, kw
+
+ def _errback(self, *args, **kw):
+ self.errback_results = args, kw
+
+ def testCallbackWithoutArgs(self):
+ deferred = defer.Deferred()
+ deferred.addCallback(self._callback)
+ deferred.callback("hello")
+ self.failUnlessEqual(self.errback_results, None)
+ self.failUnlessEqual(self.callback_results, (('hello',), {}))
+
+ def testCallbackWithArgs(self):
+ deferred = defer.Deferred()
+ deferred.addCallback(self._callback, "world")
+ deferred.callback("hello")
+ self.failUnlessEqual(self.errback_results, None)
+ self.failUnlessEqual(self.callback_results, (('hello', 'world'), {}))
+
+ def testCallbackWithKwArgs(self):
+ deferred = defer.Deferred()
+ deferred.addCallback(self._callback, world="world")
+ deferred.callback("hello")
+ self.failUnlessEqual(self.errback_results, None)
+ self.failUnlessEqual(self.callback_results,
+ (('hello',), {'world': 'world'}))
+
+ def testTwoCallbacks(self):
+ deferred = defer.Deferred()
+ deferred.addCallback(self._callback)
+ deferred.addCallback(self._callback2)
+ deferred.callback("hello")
+ self.failUnlessEqual(self.errback_results, None)
+ self.failUnlessEqual(self.callback_results,
+ (('hello',), {}))
+ self.failUnlessEqual(self.callback2_results,
+ (('hello',), {}))
+
+ def testDeferredList(self):
+ defr1 = defer.Deferred()
+ defr2 = defer.Deferred()
+ defr3 = defer.Deferred()
+ dl = defer.DeferredList([defr1, defr2, defr3])
+ result = []
+ def cb(resultList, result=result):
+ result.extend(resultList)
+ def catch(err):
+ return None
+ dl.addCallbacks(cb, cb)
+ defr1.callback("1")
+ defr2.addErrback(catch)
+ # "catch" is added to eat the GenericError that will be passed on by
+ # the DeferredList's callback on defr2. If left unhandled, the
+ # Failure object would cause a log.err() warning about "Unhandled
+ # error in Deferred". Twisted's pyunit watches for log.err calls and
+ # treats them as failures. So "catch" must eat the error to prevent
+ # it from flunking the test.
+ defr2.errback(GenericError("2"))
+ defr3.callback("3")
+ self.failUnlessEqual([result[0],
+ #result[1][1] is now a Failure instead of an Exception
+ (result[1][0], str(result[1][1].value)),
+ result[2]],
+
+ [(defer.SUCCESS, "1"),
+ (defer.FAILURE, "2"),
+ (defer.SUCCESS, "3")])
+
+ def testEmptyDeferredList(self):
+ result = []
+ def cb(resultList, result=result):
+ result.append(resultList)
+
+ dl = defer.DeferredList([])
+ dl.addCallbacks(cb)
+ self.failUnlessEqual(result, [[]])
+
+ result[:] = []
+ dl = defer.DeferredList([], fireOnOneCallback=1)
+ dl.addCallbacks(cb)
+ self.failUnlessEqual(result, [])
+
+ def testDeferredListFireOnOneError(self):
+ defr1 = defer.Deferred()
+ defr2 = defer.Deferred()
+ defr3 = defer.Deferred()
+ dl = defer.DeferredList([defr1, defr2, defr3], fireOnOneErrback=1)
+ result = []
+ dl.addErrback(result.append)
+
+ # consume errors after they pass through the DeferredList (to avoid
+ # 'Unhandled error in Deferred'.
+ def catch(err):
+ return None
+ defr2.addErrback(catch)
+
+ # fire one Deferred's callback, no result yet
+ defr1.callback("1")
+ self.failUnlessEqual(result, [])
+
+ # fire one Deferred's errback -- now we have a result
+ defr2.errback(GenericError("from def2"))
+ self.failUnlessEqual(len(result), 1)
+
+ # extract the result from the list
+ failure = result[0]
+
+ # the type of the failure is a FirstError
+ self.failUnless(issubclass(failure.type, defer.FirstError),
+ 'issubclass(failure.type, defer.FirstError) failed: '
+ 'failure.type is %r' % (failure.type,)
+ )
+
+ firstError = failure.value
+
+ # check that the GenericError("2") from the deferred at index 1
+ # (defr2) is intact inside failure.value
+ self.failUnlessEqual(firstError.subFailure.type, GenericError)
+ self.failUnlessEqual(firstError.subFailure.value.args, ("from def2",))
+ self.failUnlessEqual(firstError.index, 1)
+
+
+ def testDeferredListDontConsumeErrors(self):
+ d1 = defer.Deferred()
+ dl = defer.DeferredList([d1])
+
+ errorTrap = []
+ d1.addErrback(errorTrap.append)
+
+ result = []
+ dl.addCallback(result.append)
+
+ d1.errback(GenericError('Bang'))
+ self.failUnlessEqual('Bang', errorTrap[0].value.args[0])
+ self.failUnlessEqual(1, len(result))
+ self.failUnlessEqual('Bang', result[0][0][1].value.args[0])
+
+ def testDeferredListConsumeErrors(self):
+ d1 = defer.Deferred()
+ dl = defer.DeferredList([d1], consumeErrors=True)
+
+ errorTrap = []
+ d1.addErrback(errorTrap.append)
+
+ result = []
+ dl.addCallback(result.append)
+
+ d1.errback(GenericError('Bang'))
+ self.failUnlessEqual([], errorTrap)
+ self.failUnlessEqual(1, len(result))
+ self.failUnlessEqual('Bang', result[0][0][1].value.args[0])
+
+ def testDeferredListFireOnOneErrorWithAlreadyFiredDeferreds(self):
+ # Create some deferreds, and errback one
+ d1 = defer.Deferred()
+ d2 = defer.Deferred()
+ d1.errback(GenericError('Bang'))
+
+ # *Then* build the DeferredList, with fireOnOneErrback=True
+ dl = defer.DeferredList([d1, d2], fireOnOneErrback=True)
+ result = []
+ dl.addErrback(result.append)
+ self.failUnlessEqual(1, len(result))
+
+ d1.addErrback(lambda e: None) # Swallow error
+
+ def testDeferredListWithAlreadyFiredDeferreds(self):
+ # Create some deferreds, and err one, call the other
+ d1 = defer.Deferred()
+ d2 = defer.Deferred()
+ d1.errback(GenericError('Bang'))
+ d2.callback(2)
+
+ # *Then* build the DeferredList
+ dl = defer.DeferredList([d1, d2])
+
+ result = []
+ dl.addCallback(result.append)
+
+ self.failUnlessEqual(1, len(result))
+
+ d1.addErrback(lambda e: None) # Swallow error
+
+ def testTimeOut(self):
+ """
+ Test that a Deferred which has setTimeout called on it and never has
+ C{callback} or C{errback} called on it eventually fails with a
+ L{error.TimeoutError}.
+ """
+ L = []
+ d = defer.Deferred()
+ d.setTimeout(0.01)
+ self.assertFailure(d, defer.TimeoutError)
+ d.addCallback(L.append)
+ self.failIf(L, "Deferred failed too soon.")
+ return d
+ testTimeOut.suppress = [_setTimeoutSuppression]
+
+
+ def testImmediateSuccess(self):
+ l = []
+ d = defer.succeed("success")
+ d.addCallback(l.append)
+ self.assertEquals(l, ["success"])
+
+
+ def test_immediateSuccessBeforeTimeout(self):
+ """
+ Test that a synchronously successful Deferred is not affected by a
+ C{setTimeout} call.
+ """
+ l = []
+ d = defer.succeed("success")
+ d.setTimeout(1.0)
+ d.addCallback(l.append)
+ self.assertEquals(l, ["success"])
+ test_immediateSuccessBeforeTimeout.suppress = [_setTimeoutSuppression]
+
+
+ def testImmediateFailure(self):
+ l = []
+ d = defer.fail(GenericError("fail"))
+ d.addErrback(l.append)
+ self.assertEquals(str(l[0].value), "fail")
+
+ def testPausedFailure(self):
+ l = []
+ d = defer.fail(GenericError("fail"))
+ d.pause()
+ d.addErrback(l.append)
+ self.assertEquals(l, [])
+ d.unpause()
+ self.assertEquals(str(l[0].value), "fail")
+
+ def testCallbackErrors(self):
+ l = []
+ d = defer.Deferred().addCallback(lambda _: 1/0).addErrback(l.append)
+ d.callback(1)
+ self.assert_(isinstance(l[0].value, ZeroDivisionError))
+ l = []
+ d = defer.Deferred().addCallback(
+ lambda _: failure.Failure(ZeroDivisionError())).addErrback(l.append)
+ d.callback(1)
+ self.assert_(isinstance(l[0].value, ZeroDivisionError))
+
+ def testUnpauseBeforeCallback(self):
+ d = defer.Deferred()
+ d.pause()
+ d.addCallback(self._callback)
+ d.unpause()
+
+ def testReturnDeferred(self):
+ d = defer.Deferred()
+ d2 = defer.Deferred()
+ d2.pause()
+ d.addCallback(lambda r, d2=d2: d2)
+ d.addCallback(self._callback)
+ d.callback(1)
+ assert self.callback_results is None, "Should not have been called yet."
+ d2.callback(2)
+ assert self.callback_results is None, "Still should not have been called yet."
+ d2.unpause()
+ assert self.callback_results[0][0] == 2, "Result should have been from second deferred:%s"% (self.callback_results,)
+
+ def testGatherResults(self):
+ # test successful list of deferreds
+ l = []
+ defer.gatherResults([defer.succeed(1), defer.succeed(2)]).addCallback(l.append)
+ self.assertEquals(l, [[1, 2]])
+ # test failing list of deferreds
+ l = []
+ dl = [defer.succeed(1), defer.fail(ValueError)]
+ defer.gatherResults(dl).addErrback(l.append)
+ self.assertEquals(len(l), 1)
+ self.assert_(isinstance(l[0], failure.Failure))
+ # get rid of error
+ dl[1].addErrback(lambda e: 1)
+
+
+ def test_maybeDeferredSync(self):
+ """
+ L{defer.maybeDeferred} should retrieve the result of a synchronous
+ function and pass it to its resulting L{defer.Deferred}.
+ """
+ S, E = [], []
+ d = defer.maybeDeferred((lambda x: x + 5), 10)
+ d.addCallbacks(S.append, E.append)
+ self.assertEquals(E, [])
+ self.assertEquals(S, [15])
+ return d
+
+
+ def test_maybeDeferredSyncError(self):
+ """
+ L{defer.maybeDeferred} should catch exception raised by a synchronous
+ function and errback its resulting L{defer.Deferred} with it.
+ """
+ S, E = [], []
+ try:
+ '10' + 5
+ except TypeError, e:
+ expected = str(e)
+ d = defer.maybeDeferred((lambda x: x + 5), '10')
+ d.addCallbacks(S.append, E.append)
+ self.assertEquals(S, [])
+ self.assertEquals(len(E), 1)
+ self.assertEquals(str(E[0].value), expected)
+ return d
+
+
+ def test_maybeDeferredAsync(self):
+ """
+ L{defer.maybeDeferred} should let L{defer.Deferred} instance pass by
+ so that original result is the same.
+ """
+ d = defer.Deferred()
+ d2 = defer.maybeDeferred(lambda: d)
+ d.callback('Success')
+ return d2.addCallback(self.assertEquals, 'Success')
+
+
+ def test_maybeDeferredAsyncError(self):
+ """
+ L{defer.maybeDeferred} should let L{defer.Deferred} instance pass by
+ so that L{failure.Failure} returned by the original instance is the
+ same.
+ """
+ d = defer.Deferred()
+ d2 = defer.maybeDeferred(lambda: d)
+ d.errback(failure.Failure(RuntimeError()))
+ return self.assertFailure(d2, RuntimeError)
+
+
+ def test_reentrantRunCallbacks(self):
+ """
+ A callback added to a L{Deferred} by a callback on that L{Deferred}
+ should be added to the end of the callback chain.
+ """
+ deferred = defer.Deferred()
+ called = []
+ def callback3(result):
+ called.append(3)
+ def callback2(result):
+ called.append(2)
+ def callback1(result):
+ called.append(1)
+ deferred.addCallback(callback3)
+ deferred.addCallback(callback1)
+ deferred.addCallback(callback2)
+ deferred.callback(None)
+ self.assertEqual(called, [1, 2, 3])
+
+
+ def test_nonReentrantCallbacks(self):
+ """
+ A callback added to a L{Deferred} by a callback on that L{Deferred}
+ should not be executed until the running callback returns.
+ """
+ deferred = defer.Deferred()
+ called = []
+ def callback2(result):
+ called.append(2)
+ def callback1(result):
+ called.append(1)
+ deferred.addCallback(callback2)
+ self.assertEquals(called, [1])
+ deferred.addCallback(callback1)
+ deferred.callback(None)
+ self.assertEqual(called, [1, 2])
+
+
+ def test_reentrantRunCallbacksWithFailure(self):
+ """
+ After an exception is raised by a callback which was added to a
+ L{Deferred} by a callback on that L{Deferred}, the L{Deferred} should
+ call the first errback with a L{Failure} wrapping that exception.
+ """
+ exceptionMessage = "callback raised exception"
+ deferred = defer.Deferred()
+ def callback2(result):
+ raise Exception(exceptionMessage)
+ def callback1(result):
+ deferred.addCallback(callback2)
+ deferred.addCallback(callback1)
+ deferred.callback(None)
+ self.assertFailure(deferred, Exception)
+ def cbFailed(exception):
+ self.assertEqual(exception.args, (exceptionMessage,))
+ deferred.addCallback(cbFailed)
+ return deferred
+
+
+
+class FirstErrorTests(unittest.TestCase):
+ """
+ Tests for L{FirstError}.
+ """
+ def test_repr(self):
+ """
+ The repr of a L{FirstError} instance includes the repr of the value of
+ the sub-failure and the index which corresponds to the L{FirstError}.
+ """
+ exc = ValueError("some text")
+ try:
+ raise exc
+ except:
+ f = failure.Failure()
+
+ error = defer.FirstError(f, 3)
+ self.assertEqual(
+ repr(error),
+ "FirstError[#3, %s]" % (repr(exc),))
+
+
+ def test_str(self):
+ """
+ The str of a L{FirstError} instance includes the str of the
+ sub-failure and the index which corresponds to the L{FirstError}.
+ """
+ exc = ValueError("some text")
+ try:
+ raise exc
+ except:
+ f = failure.Failure()
+
+ error = defer.FirstError(f, 5)
+ self.assertEqual(
+ str(error),
+ "FirstError[#5, %s]" % (str(f),))
+
+
+ def test_comparison(self):
+ """
+ L{FirstError} instances compare equal to each other if and only if
+ their failure and index compare equal. L{FirstError} instances do not
+ compare equal to instances of other types.
+ """
+ try:
+ 1 / 0
+ except:
+ firstFailure = failure.Failure()
+
+ one = defer.FirstError(firstFailure, 13)
+ anotherOne = defer.FirstError(firstFailure, 13)
+
+ try:
+ raise ValueError("bar")
+ except:
+ secondFailure = failure.Failure()
+
+ another = defer.FirstError(secondFailure, 9)
+
+ self.assertTrue(one == anotherOne)
+ self.assertFalse(one == another)
+ self.assertTrue(one != another)
+ self.assertFalse(one != anotherOne)
+
+ self.assertFalse(one == 10)
+
+
+
+class AlreadyCalledTestCase(unittest.TestCase):
+ def setUp(self):
+ self._deferredWasDebugging = defer.getDebugging()
+ defer.setDebugging(True)
+
+ def tearDown(self):
+ defer.setDebugging(self._deferredWasDebugging)
+
+ def _callback(self, *args, **kw):
+ pass
+ def _errback(self, *args, **kw):
+ pass
+
+ def _call_1(self, d):
+ d.callback("hello")
+ def _call_2(self, d):
+ d.callback("twice")
+ def _err_1(self, d):
+ d.errback(failure.Failure(RuntimeError()))
+ def _err_2(self, d):
+ d.errback(failure.Failure(RuntimeError()))
+
+ def testAlreadyCalled_CC(self):
+ d = defer.Deferred()
+ d.addCallbacks(self._callback, self._errback)
+ self._call_1(d)
+ self.failUnlessRaises(defer.AlreadyCalledError, self._call_2, d)
+
+ def testAlreadyCalled_CE(self):
+ d = defer.Deferred()
+ d.addCallbacks(self._callback, self._errback)
+ self._call_1(d)
+ self.failUnlessRaises(defer.AlreadyCalledError, self._err_2, d)
+
+ def testAlreadyCalled_EE(self):
+ d = defer.Deferred()
+ d.addCallbacks(self._callback, self._errback)
+ self._err_1(d)
+ self.failUnlessRaises(defer.AlreadyCalledError, self._err_2, d)
+
+ def testAlreadyCalled_EC(self):
+ d = defer.Deferred()
+ d.addCallbacks(self._callback, self._errback)
+ self._err_1(d)
+ self.failUnlessRaises(defer.AlreadyCalledError, self._call_2, d)
+
+
+ def _count(self, linetype, func, lines, expected):
+ count = 0
+ for line in lines:
+ if (line.startswith(' %s:' % linetype) and
+ line.endswith(' %s' % func)):
+ count += 1
+ self.failUnless(count == expected)
+
+ def _check(self, e, caller, invoker1, invoker2):
+ # make sure the debugging information is vaguely correct
+ lines = e.args[0].split("\n")
+ # the creator should list the creator (testAlreadyCalledDebug) but not
+ # _call_1 or _call_2 or other invokers
+ self._count('C', caller, lines, 1)
+ self._count('C', '_call_1', lines, 0)
+ self._count('C', '_call_2', lines, 0)
+ self._count('C', '_err_1', lines, 0)
+ self._count('C', '_err_2', lines, 0)
+ # invoker should list the first invoker but not the second
+ self._count('I', invoker1, lines, 1)
+ self._count('I', invoker2, lines, 0)
+
+ def testAlreadyCalledDebug_CC(self):
+ d = defer.Deferred()
+ d.addCallbacks(self._callback, self._errback)
+ self._call_1(d)
+ try:
+ self._call_2(d)
+ except defer.AlreadyCalledError, e:
+ self._check(e, "testAlreadyCalledDebug_CC", "_call_1", "_call_2")
+ else:
+ self.fail("second callback failed to raise AlreadyCalledError")
+
+ def testAlreadyCalledDebug_CE(self):
+ d = defer.Deferred()
+ d.addCallbacks(self._callback, self._errback)
+ self._call_1(d)
+ try:
+ self._err_2(d)
+ except defer.AlreadyCalledError, e:
+ self._check(e, "testAlreadyCalledDebug_CE", "_call_1", "_err_2")
+ else:
+ self.fail("second errback failed to raise AlreadyCalledError")
+
+ def testAlreadyCalledDebug_EC(self):
+ d = defer.Deferred()
+ d.addCallbacks(self._callback, self._errback)
+ self._err_1(d)
+ try:
+ self._call_2(d)
+ except defer.AlreadyCalledError, e:
+ self._check(e, "testAlreadyCalledDebug_EC", "_err_1", "_call_2")
+ else:
+ self.fail("second callback failed to raise AlreadyCalledError")
+
+ def testAlreadyCalledDebug_EE(self):
+ d = defer.Deferred()
+ d.addCallbacks(self._callback, self._errback)
+ self._err_1(d)
+ try:
+ self._err_2(d)
+ except defer.AlreadyCalledError, e:
+ self._check(e, "testAlreadyCalledDebug_EE", "_err_1", "_err_2")
+ else:
+ self.fail("second errback failed to raise AlreadyCalledError")
+
+ def testNoDebugging(self):
+ defer.setDebugging(False)
+ d = defer.Deferred()
+ d.addCallbacks(self._callback, self._errback)
+ self._call_1(d)
+ try:
+ self._call_2(d)
+ except defer.AlreadyCalledError, e:
+ self.failIf(e.args)
+ else:
+ self.fail("second callback failed to raise AlreadyCalledError")
+
+
+ def testSwitchDebugging(self):
+ # Make sure Deferreds can deal with debug state flipping
+ # around randomly. This is covering a particular fixed bug.
+ defer.setDebugging(False)
+ d = defer.Deferred()
+ d.addBoth(lambda ign: None)
+ defer.setDebugging(True)
+ d.callback(None)
+
+ defer.setDebugging(False)
+ d = defer.Deferred()
+ d.callback(None)
+ defer.setDebugging(True)
+ d.addBoth(lambda ign: None)
+
+
+
+class LogTestCase(unittest.TestCase):
+ """
+ Test logging of unhandled errors.
+ """
+
+ def setUp(self):
+ """
+ Add a custom observer to observer logging.
+ """
+ self.c = []
+ log.addObserver(self.c.append)
+
+ def tearDown(self):
+ """
+ Remove the observer.
+ """
+ log.removeObserver(self.c.append)
+
+ def _check(self):
+ """
+ Check the output of the log observer to see if the error is present.
+ """
+ c2 = [e for e in self.c if e["isError"]]
+ self.assertEquals(len(c2), 2)
+ c2[1]["failure"].trap(ZeroDivisionError)
+ self.flushLoggedErrors(ZeroDivisionError)
+
+ def test_errorLog(self):
+ """
+ Verify that when a Deferred with no references to it is fired, and its
+ final result (the one not handled by any callback) is an exception,
+ that exception will be logged immediately.
+ """
+ defer.Deferred().addCallback(lambda x: 1/0).callback(1)
+ gc.collect()
+ self._check()
+
+ def test_errorLogWithInnerFrameRef(self):
+ """
+ Same as L{test_errorLog}, but with an inner frame.
+ """
+ def _subErrorLogWithInnerFrameRef():
+ d = defer.Deferred()
+ d.addCallback(lambda x: 1/0)
+ d.callback(1)
+
+ _subErrorLogWithInnerFrameRef()
+ gc.collect()
+ self._check()
+
+ def test_errorLogWithInnerFrameCycle(self):
+ """
+ Same as L{test_errorLogWithInnerFrameRef}, plus create a cycle.
+ """
+ def _subErrorLogWithInnerFrameCycle():
+ d = defer.Deferred()
+ d.addCallback(lambda x, d=d: 1/0)
+ d._d = d
+ d.callback(1)
+
+ _subErrorLogWithInnerFrameCycle()
+ gc.collect()
+ self._check()
+
+
+class DeferredTestCaseII(unittest.TestCase):
+ def setUp(self):
+ self.callbackRan = 0
+
+ def testDeferredListEmpty(self):
+ """Testing empty DeferredList."""
+ dl = defer.DeferredList([])
+ dl.addCallback(self.cb_empty)
+
+ def cb_empty(self, res):
+ self.callbackRan = 1
+ self.failUnlessEqual([], res)
+
+ def tearDown(self):
+ self.failUnless(self.callbackRan, "Callback was never run.")
+
+class OtherPrimitives(unittest.TestCase):
+ def _incr(self, result):
+ self.counter += 1
+
+ def setUp(self):
+ self.counter = 0
+
+ def testLock(self):
+ lock = defer.DeferredLock()
+ lock.acquire().addCallback(self._incr)
+ self.failUnless(lock.locked)
+ self.assertEquals(self.counter, 1)
+
+ lock.acquire().addCallback(self._incr)
+ self.failUnless(lock.locked)
+ self.assertEquals(self.counter, 1)
+
+ lock.release()
+ self.failUnless(lock.locked)
+ self.assertEquals(self.counter, 2)
+
+ lock.release()
+ self.failIf(lock.locked)
+ self.assertEquals(self.counter, 2)
+
+ self.assertRaises(TypeError, lock.run)
+
+ firstUnique = object()
+ secondUnique = object()
+
+ controlDeferred = defer.Deferred()
+ def helper(self, b):
+ self.b = b
+ return controlDeferred
+
+ resultDeferred = lock.run(helper, self=self, b=firstUnique)
+ self.failUnless(lock.locked)
+ self.assertEquals(self.b, firstUnique)
+
+ resultDeferred.addCallback(lambda x: setattr(self, 'result', x))
+
+ lock.acquire().addCallback(self._incr)
+ self.failUnless(lock.locked)
+ self.assertEquals(self.counter, 2)
+
+ controlDeferred.callback(secondUnique)
+ self.assertEquals(self.result, secondUnique)
+ self.failUnless(lock.locked)
+ self.assertEquals(self.counter, 3)
+
+ lock.release()
+ self.failIf(lock.locked)
+
+ def testSemaphore(self):
+ N = 13
+ sem = defer.DeferredSemaphore(N)
+
+ controlDeferred = defer.Deferred()
+ def helper(self, arg):
+ self.arg = arg
+ return controlDeferred
+
+ results = []
+ uniqueObject = object()
+ resultDeferred = sem.run(helper, self=self, arg=uniqueObject)
+ resultDeferred.addCallback(results.append)
+ resultDeferred.addCallback(self._incr)
+ self.assertEquals(results, [])
+ self.assertEquals(self.arg, uniqueObject)
+ controlDeferred.callback(None)
+ self.assertEquals(results.pop(), None)
+ self.assertEquals(self.counter, 1)
+
+ self.counter = 0
+ for i in range(1, 1 + N):
+ sem.acquire().addCallback(self._incr)
+ self.assertEquals(self.counter, i)
+
+ sem.acquire().addCallback(self._incr)
+ self.assertEquals(self.counter, N)
+
+ sem.release()
+ self.assertEquals(self.counter, N + 1)
+
+ for i in range(1, 1 + N):
+ sem.release()
+ self.assertEquals(self.counter, N + 1)
+
+ def testQueue(self):
+ N, M = 2, 2
+ queue = defer.DeferredQueue(N, M)
+
+ gotten = []
+
+ for i in range(M):
+ queue.get().addCallback(gotten.append)
+ self.assertRaises(defer.QueueUnderflow, queue.get)
+
+ for i in range(M):
+ queue.put(i)
+ self.assertEquals(gotten, range(i + 1))
+ for i in range(N):
+ queue.put(N + i)
+ self.assertEquals(gotten, range(M))
+ self.assertRaises(defer.QueueOverflow, queue.put, None)
+
+ gotten = []
+ for i in range(N):
+ queue.get().addCallback(gotten.append)
+ self.assertEquals(gotten, range(N, N + i + 1))
+
+ queue = defer.DeferredQueue()
+ gotten = []
+ for i in range(N):
+ queue.get().addCallback(gotten.append)
+ for i in range(N):
+ queue.put(i)
+ self.assertEquals(gotten, range(N))
+
+ queue = defer.DeferredQueue(size=0)
+ self.assertRaises(defer.QueueOverflow, queue.put, None)
+
+ queue = defer.DeferredQueue(backlog=0)
+ self.assertRaises(defer.QueueUnderflow, queue.get)
+
+
+
+class DeferredFilesystemLockTestCase(unittest.TestCase):
+ """
+ Test the behavior of L{DeferredFilesystemLock}
+ """
+ def setUp(self):
+ self.clock = Clock()
+ self.lock = defer.DeferredFilesystemLock(self.mktemp(),
+ scheduler=self.clock)
+
+
+ def test_waitUntilLockedWithNoLock(self):
+ """
+ Test that the lock can be acquired when no lock is held
+ """
+ d = self.lock.deferUntilLocked(timeout=1)
+
+ return d
+
+
+ def test_waitUntilLockedWithTimeoutLocked(self):
+ """
+ Test that the lock can not be acquired when the lock is held
+ for longer than the timeout.
+ """
+ self.failUnless(self.lock.lock())
+
+ d = self.lock.deferUntilLocked(timeout=5.5)
+ self.assertFailure(d, defer.TimeoutError)
+
+ self.clock.pump([1]*10)
+
+ return d
+
+
+ def test_waitUntilLockedWithTimeoutUnlocked(self):
+ """
+ Test that a lock can be acquired while a lock is held
+ but the lock is unlocked before our timeout.
+ """
+ def onTimeout(f):
+ f.trap(defer.TimeoutError)
+ self.fail("Should not have timed out")
+
+ self.failUnless(self.lock.lock())
+
+ self.clock.callLater(1, self.lock.unlock)
+ d = self.lock.deferUntilLocked(timeout=10)
+ d.addErrback(onTimeout)
+
+ self.clock.pump([1]*10)
+
+ return d
+
+
+ def test_defaultScheduler(self):
+ """
+ Test that the default scheduler is set up properly.
+ """
+ lock = defer.DeferredFilesystemLock(self.mktemp())
+
+ self.assertEquals(lock._scheduler, reactor)
+
+
+ def test_concurrentUsage(self):
+ """
+ Test that an appropriate exception is raised when attempting
+ to use deferUntilLocked concurrently.
+ """
+ self.lock.lock()
+ self.clock.callLater(1, self.lock.unlock)
+
+ d = self.lock.deferUntilLocked()
+ d2 = self.lock.deferUntilLocked()
+
+ self.assertFailure(d2, defer.AlreadyTryingToLockError)
+
+ self.clock.advance(1)
+
+ return d
+
+
+ def test_multipleUsages(self):
+ """
+ Test that a DeferredFilesystemLock can be used multiple times
+ """
+ def lockAquired(ign):
+ self.lock.unlock()
+ d = self.lock.deferUntilLocked()
+ return d
+
+ self.lock.lock()
+ self.clock.callLater(1, self.lock.unlock)
+
+ d = self.lock.deferUntilLocked()
+ d.addCallback(lockAquired)
+
+ self.clock.advance(1)
+
+ return d
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_defgen.py b/vendor/Twisted-10.0.0/twisted/test/test_defgen.py
new file mode 100644
index 0000000000..399e9dbcf7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_defgen.py
@@ -0,0 +1,283 @@
+from __future__ import generators, nested_scopes
+
+import sys
+
+from twisted.internet import reactor
+
+from twisted.trial import unittest, util
+
+from twisted.internet.defer import waitForDeferred, deferredGenerator, Deferred
+from twisted.internet import defer
+
+def getThing():
+ d = Deferred()
+ reactor.callLater(0, d.callback, "hi")
+ return d
+
+def getOwie():
+ d = Deferred()
+ def CRAP():
+ d.errback(ZeroDivisionError('OMG'))
+ reactor.callLater(0, CRAP)
+ return d
+
+# NOTE: most of the tests in DeferredGeneratorTests are duplicated
+# with slightly different syntax for the InlineCallbacksTests below.
+
+class TerminalException(Exception):
+ pass
+
+class BaseDefgenTests:
+ """
+ This class sets up a bunch of test cases which will test both
+ deferredGenerator and inlineCallbacks based generators. The subclasses
+ DeferredGeneratorTests and InlineCallbacksTests each provide the actual
+ generator implementations tested.
+ """
+
+ def testBasics(self):
+ """
+ Test that a normal deferredGenerator works. Tests yielding a
+ deferred which callbacks, as well as a deferred errbacks. Also
+ ensures returning a final value works.
+ """
+
+ return self._genBasics().addCallback(self.assertEqual, 'WOOSH')
+
+ def testBuggy(self):
+ """
+ Ensure that a buggy generator properly signals a Failure
+ condition on result deferred.
+ """
+ return self.assertFailure(self._genBuggy(), ZeroDivisionError)
+
+ def testNothing(self):
+ """Test that a generator which never yields results in None."""
+
+ return self._genNothing().addCallback(self.assertEqual, None)
+
+ def testHandledTerminalFailure(self):
+ """
+ Create a Deferred Generator which yields a Deferred which fails and
+ handles the exception which results. Assert that the Deferred
+ Generator does not errback its Deferred.
+ """
+ return self._genHandledTerminalFailure().addCallback(self.assertEqual, None)
+
+ def testHandledTerminalAsyncFailure(self):
+ """
+ Just like testHandledTerminalFailure, only with a Deferred which fires
+ asynchronously with an error.
+ """
+ d = defer.Deferred()
+ deferredGeneratorResultDeferred = self._genHandledTerminalAsyncFailure(d)
+ d.errback(TerminalException("Handled Terminal Failure"))
+ return deferredGeneratorResultDeferred.addCallback(
+ self.assertEqual, None)
+
+ def testStackUsage(self):
+ """
+ Make sure we don't blow the stack when yielding immediately
+ available deferreds.
+ """
+ return self._genStackUsage().addCallback(self.assertEqual, 0)
+
+ def testStackUsage2(self):
+ """
+ Make sure we don't blow the stack when yielding immediately
+ available values.
+ """
+ return self._genStackUsage2().addCallback(self.assertEqual, 0)
+
+
+
+
+class DeferredGeneratorTests(BaseDefgenTests, unittest.TestCase):
+
+ # First provide all the generator impls necessary for BaseDefgenTests
+ def _genBasics(self):
+
+ x = waitForDeferred(getThing())
+ yield x
+ x = x.getResult()
+
+ self.assertEquals(x, "hi")
+
+ ow = waitForDeferred(getOwie())
+ yield ow
+ try:
+ ow.getResult()
+ except ZeroDivisionError, e:
+ self.assertEquals(str(e), 'OMG')
+ yield "WOOSH"
+ return
+ _genBasics = deferredGenerator(_genBasics)
+
+ def _genBuggy(self):
+ yield waitForDeferred(getThing())
+ 1/0
+ _genBuggy = deferredGenerator(_genBuggy)
+
+
+ def _genNothing(self):
+ if 0: yield 1
+ _genNothing = deferredGenerator(_genNothing)
+
+ def _genHandledTerminalFailure(self):
+ x = waitForDeferred(defer.fail(TerminalException("Handled Terminal Failure")))
+ yield x
+ try:
+ x.getResult()
+ except TerminalException:
+ pass
+ _genHandledTerminalFailure = deferredGenerator(_genHandledTerminalFailure)
+
+
+ def _genHandledTerminalAsyncFailure(self, d):
+ x = waitForDeferred(d)
+ yield x
+ try:
+ x.getResult()
+ except TerminalException:
+ pass
+ _genHandledTerminalAsyncFailure = deferredGenerator(_genHandledTerminalAsyncFailure)
+
+
+ def _genStackUsage(self):
+ for x in range(5000):
+ # Test with yielding a deferred
+ x = waitForDeferred(defer.succeed(1))
+ yield x
+ x = x.getResult()
+ yield 0
+ _genStackUsage = deferredGenerator(_genStackUsage)
+
+ def _genStackUsage2(self):
+ for x in range(5000):
+ # Test with yielding a random value
+ yield 1
+ yield 0
+ _genStackUsage2 = deferredGenerator(_genStackUsage2)
+
+ # Tests unique to deferredGenerator
+
+ def testDeferredYielding(self):
+ """
+ Ensure that yielding a Deferred directly is trapped as an
+ error.
+ """
+ # See the comment _deferGenerator about d.callback(Deferred).
+ def _genDeferred():
+ yield getThing()
+ _genDeferred = deferredGenerator(_genDeferred)
+
+ return self.assertFailure(_genDeferred(), TypeError)
+
+
+
+## This has to be in a string so the new yield syntax doesn't cause a
+## syntax error in Python 2.4 and before.
+inlineCallbacksTestsSource = '''
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+class InlineCallbacksTests(BaseDefgenTests, unittest.TestCase):
+ # First provide all the generator impls necessary for BaseDefgenTests
+
+ def _genBasics(self):
+
+ x = yield getThing()
+
+ self.assertEquals(x, "hi")
+
+ try:
+ ow = yield getOwie()
+ except ZeroDivisionError, e:
+ self.assertEquals(str(e), 'OMG')
+ returnValue("WOOSH")
+ _genBasics = inlineCallbacks(_genBasics)
+
+ def _genBuggy(self):
+ yield getThing()
+ 1/0
+ _genBuggy = inlineCallbacks(_genBuggy)
+
+
+ def _genNothing(self):
+ if 0: yield 1
+ _genNothing = inlineCallbacks(_genNothing)
+
+
+ def _genHandledTerminalFailure(self):
+ try:
+ x = yield defer.fail(TerminalException("Handled Terminal Failure"))
+ except TerminalException:
+ pass
+ _genHandledTerminalFailure = inlineCallbacks(_genHandledTerminalFailure)
+
+
+ def _genHandledTerminalAsyncFailure(self, d):
+ try:
+ x = yield d
+ except TerminalException:
+ pass
+ _genHandledTerminalAsyncFailure = inlineCallbacks(
+ _genHandledTerminalAsyncFailure)
+
+
+ def _genStackUsage(self):
+ for x in range(5000):
+ # Test with yielding a deferred
+ x = yield defer.succeed(1)
+ returnValue(0)
+ _genStackUsage = inlineCallbacks(_genStackUsage)
+
+ def _genStackUsage2(self):
+ for x in range(5000):
+ # Test with yielding a random value
+ yield 1
+ returnValue(0)
+ _genStackUsage2 = inlineCallbacks(_genStackUsage2)
+
+ # Tests unique to inlineCallbacks
+
+ def testYieldNonDeferrred(self):
+ """
+ Ensure that yielding a non-deferred passes it back as the
+ result of the yield expression.
+ """
+ def _test():
+ x = yield 5
+ returnValue(5)
+ _test = inlineCallbacks(_test)
+
+ return _test().addCallback(self.assertEqual, 5)
+
+ def testReturnNoValue(self):
+ """Ensure a standard python return results in a None result."""
+ def _noReturn():
+ yield 5
+ return
+ _noReturn = inlineCallbacks(_noReturn)
+
+ return _noReturn().addCallback(self.assertEqual, None)
+
+ def testReturnValue(self):
+ """Ensure that returnValue works."""
+ def _return():
+ yield 5
+ returnValue(6)
+ _return = inlineCallbacks(_return)
+
+ return _return().addCallback(self.assertEqual, 6)
+
+'''
+
+if sys.version_info > (2, 5):
+ # Load tests
+ exec inlineCallbacksTestsSource
+else:
+ # Make a placeholder test case
+ class InlineCallbacksTests(unittest.TestCase):
+ skip = "defer.defgen doesn't run on python < 2.5."
+ def test_everything(self):
+ pass
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_dict.py b/vendor/Twisted-10.0.0/twisted/test/test_dict.py
new file mode 100644
index 0000000000..154f66b9b8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_dict.py
@@ -0,0 +1,22 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.trial import unittest
+from twisted.protocols import dict
+
+paramString = "\"This is a dqstring \\w\\i\\t\\h boring stuff like: \\\"\" and t\\hes\\\"e are a\\to\\ms"
+goodparams = ["This is a dqstring with boring stuff like: \"", "and", "thes\"e", "are", "atoms"]
+
+class ParamTest(unittest.TestCase):
+ def testParseParam(self):
+ """Testing command response handling"""
+ params = []
+ rest = paramString
+ while 1:
+ (param, rest) = dict.parseParam(rest)
+ if param == None:
+ break
+ params.append(param)
+ self.failUnlessEqual(params, goodparams)#, "DictClient.parseParam returns unexpected results")
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_digestauth.py b/vendor/Twisted-10.0.0/twisted/test/test_digestauth.py
new file mode 100644
index 0000000000..ddf760d714
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_digestauth.py
@@ -0,0 +1,671 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.cred._digest} and the associated bits in
+L{twisted.cred.credentials}.
+"""
+
+from zope.interface.verify import verifyObject
+from twisted.trial.unittest import TestCase
+from twisted.python.hashlib import md5, sha1
+from twisted.internet.address import IPv4Address
+from twisted.cred.error import LoginFailed
+from twisted.cred.credentials import calcHA1, calcHA2, IUsernameDigestHash
+from twisted.cred.credentials import calcResponse, DigestCredentialFactory
+
+def b64encode(s):
+ return s.encode('base64').strip()
+
+
+class FakeDigestCredentialFactory(DigestCredentialFactory):
+ """
+ A Fake Digest Credential Factory that generates a predictable
+ nonce and opaque
+ """
+ def __init__(self, *args, **kwargs):
+ super(FakeDigestCredentialFactory, self).__init__(*args, **kwargs)
+ self.privateKey = "0"
+
+
+ def _generateNonce(self):
+ """
+ Generate a static nonce
+ """
+ return '178288758716122392881254770685'
+
+
+ def _getTime(self):
+ """
+ Return a stable time
+ """
+ return 0
+
+
+
+class DigestAuthTests(TestCase):
+ """
+ L{TestCase} mixin class which defines a number of tests for
+ L{DigestCredentialFactory}. Because this mixin defines C{setUp}, it
+ must be inherited before L{TestCase}.
+ """
+ def setUp(self):
+ """
+ Create a DigestCredentialFactory for testing
+ """
+ self.username = "foobar"
+ self.password = "bazquux"
+ self.realm = "test realm"
+ self.algorithm = "md5"
+ self.cnonce = "29fc54aa1641c6fa0e151419361c8f23"
+ self.qop = "auth"
+ self.uri = "/write/"
+ self.clientAddress = IPv4Address('TCP', '10.2.3.4', 43125)
+ self.method = 'GET'
+ self.credentialFactory = DigestCredentialFactory(
+ self.algorithm, self.realm)
+
+
+ def test_MD5HashA1(self, _algorithm='md5', _hash=md5):
+ """
+ L{calcHA1} accepts the C{'md5'} algorithm and returns an MD5 hash of
+ its parameters, excluding the nonce and cnonce.
+ """
+ nonce = 'abc123xyz'
+ hashA1 = calcHA1(_algorithm, self.username, self.realm, self.password,
+ nonce, self.cnonce)
+ a1 = '%s:%s:%s' % (self.username, self.realm, self.password)
+ expected = _hash(a1).hexdigest()
+ self.assertEqual(hashA1, expected)
+
+
+ def test_MD5SessionHashA1(self):
+ """
+ L{calcHA1} accepts the C{'md5-sess'} algorithm and returns an MD5 hash
+ of its parameters, including the nonce and cnonce.
+ """
+ nonce = 'xyz321abc'
+ hashA1 = calcHA1('md5-sess', self.username, self.realm, self.password,
+ nonce, self.cnonce)
+ a1 = '%s:%s:%s' % (self.username, self.realm, self.password)
+ ha1 = md5(a1).digest()
+ a1 = '%s:%s:%s' % (ha1, nonce, self.cnonce)
+ expected = md5(a1).hexdigest()
+ self.assertEqual(hashA1, expected)
+
+
+ def test_SHAHashA1(self):
+ """
+ L{calcHA1} accepts the C{'sha'} algorithm and returns a SHA hash of its
+ parameters, excluding the nonce and cnonce.
+ """
+ self.test_MD5HashA1('sha', sha1)
+
+
+ def test_MD5HashA2Auth(self, _algorithm='md5', _hash=md5):
+ """
+ L{calcHA2} accepts the C{'md5'} algorithm and returns an MD5 hash of
+ its arguments, excluding the entity hash for QOP other than
+ C{'auth-int'}.
+ """
+ method = 'GET'
+ hashA2 = calcHA2(_algorithm, method, self.uri, 'auth', None)
+ a2 = '%s:%s' % (method, self.uri)
+ expected = _hash(a2).hexdigest()
+ self.assertEqual(hashA2, expected)
+
+
+ def test_MD5HashA2AuthInt(self, _algorithm='md5', _hash=md5):
+ """
+ L{calcHA2} accepts the C{'md5'} algorithm and returns an MD5 hash of
+ its arguments, including the entity hash for QOP of C{'auth-int'}.
+ """
+ method = 'GET'
+ hentity = 'foobarbaz'
+ hashA2 = calcHA2(_algorithm, method, self.uri, 'auth-int', hentity)
+ a2 = '%s:%s:%s' % (method, self.uri, hentity)
+ expected = _hash(a2).hexdigest()
+ self.assertEqual(hashA2, expected)
+
+
+ def test_MD5SessHashA2Auth(self):
+ """
+ L{calcHA2} accepts the C{'md5-sess'} algorithm and QOP of C{'auth'} and
+ returns the same value as it does for the C{'md5'} algorithm.
+ """
+ self.test_MD5HashA2Auth('md5-sess')
+
+
+ def test_MD5SessHashA2AuthInt(self):
+ """
+ L{calcHA2} accepts the C{'md5-sess'} algorithm and QOP of C{'auth-int'}
+ and returns the same value as it does for the C{'md5'} algorithm.
+ """
+ self.test_MD5HashA2AuthInt('md5-sess')
+
+
+ def test_SHAHashA2Auth(self):
+ """
+ L{calcHA2} accepts the C{'sha'} algorithm and returns a SHA hash of
+ its arguments, excluding the entity hash for QOP other than
+ C{'auth-int'}.
+ """
+ self.test_MD5HashA2Auth('sha', sha1)
+
+
+ def test_SHAHashA2AuthInt(self):
+ """
+ L{calcHA2} accepts the C{'sha'} algorithm and returns a SHA hash of
+ its arguments, including the entity hash for QOP of C{'auth-int'}.
+ """
+ self.test_MD5HashA2AuthInt('sha', sha1)
+
+
+ def test_MD5HashResponse(self, _algorithm='md5', _hash=md5):
+ """
+ L{calcResponse} accepts the C{'md5'} algorithm and returns an MD5 hash
+ of its parameters, excluding the nonce count, client nonce, and QoP
+ value if the nonce count and client nonce are C{None}
+ """
+ hashA1 = 'abc123'
+ hashA2 = '789xyz'
+ nonce = 'lmnopq'
+
+ response = '%s:%s:%s' % (hashA1, nonce, hashA2)
+ expected = _hash(response).hexdigest()
+
+ digest = calcResponse(hashA1, hashA2, _algorithm, nonce, None, None,
+ None)
+ self.assertEqual(expected, digest)
+
+
+ def test_MD5SessionHashResponse(self):
+ """
+ L{calcResponse} accepts the C{'md5-sess'} algorithm and returns an MD5
+ hash of its parameters, excluding the nonce count, client nonce, and
+ QoP value if the nonce count and client nonce are C{None}
+ """
+ self.test_MD5HashResponse('md5-sess')
+
+
+ def test_SHAHashResponse(self):
+ """
+ L{calcResponse} accepts the C{'sha'} algorithm and returns a SHA hash
+ of its parameters, excluding the nonce count, client nonce, and QoP
+ value if the nonce count and client nonce are C{None}
+ """
+ self.test_MD5HashResponse('sha', sha1)
+
+
+ def test_MD5HashResponseExtra(self, _algorithm='md5', _hash=md5):
+ """
+ L{calcResponse} accepts the C{'md5'} algorithm and returns an MD5 hash
+ of its parameters, including the nonce count, client nonce, and QoP
+ value if they are specified.
+ """
+ hashA1 = 'abc123'
+ hashA2 = '789xyz'
+ nonce = 'lmnopq'
+ nonceCount = '00000004'
+ clientNonce = 'abcxyz123'
+ qop = 'auth'
+
+ response = '%s:%s:%s:%s:%s:%s' % (
+ hashA1, nonce, nonceCount, clientNonce, qop, hashA2)
+ expected = _hash(response).hexdigest()
+
+ digest = calcResponse(
+ hashA1, hashA2, _algorithm, nonce, nonceCount, clientNonce, qop)
+ self.assertEqual(expected, digest)
+
+
+ def test_MD5SessionHashResponseExtra(self):
+ """
+ L{calcResponse} accepts the C{'md5-sess'} algorithm and returns an MD5
+ hash of its parameters, including the nonce count, client nonce, and
+ QoP value if they are specified.
+ """
+ self.test_MD5HashResponseExtra('md5-sess')
+
+
+ def test_SHAHashResponseExtra(self):
+ """
+ L{calcResponse} accepts the C{'sha'} algorithm and returns a SHA hash
+ of its parameters, including the nonce count, client nonce, and QoP
+ value if they are specified.
+ """
+ self.test_MD5HashResponseExtra('sha', sha1)
+
+
+ def formatResponse(self, quotes=True, **kw):
+ """
+ Format all given keyword arguments and their values suitably for use as
+ the value of an HTTP header.
+
+ @types quotes: C{bool}
+ @param quotes: A flag indicating whether to quote the values of each
+ field in the response.
+
+ @param **kw: Keywords and C{str} values which will be treated as field
+ name/value pairs to include in the result.
+
+ @rtype: C{str}
+ @return: The given fields formatted for use as an HTTP header value.
+ """
+ if 'username' not in kw:
+ kw['username'] = self.username
+ if 'realm' not in kw:
+ kw['realm'] = self.realm
+ if 'algorithm' not in kw:
+ kw['algorithm'] = self.algorithm
+ if 'qop' not in kw:
+ kw['qop'] = self.qop
+ if 'cnonce' not in kw:
+ kw['cnonce'] = self.cnonce
+ if 'uri' not in kw:
+ kw['uri'] = self.uri
+ if quotes:
+ quote = '"'
+ else:
+ quote = ''
+ return ', '.join([
+ '%s=%s%s%s' % (k, quote, v, quote)
+ for (k, v)
+ in kw.iteritems()
+ if v is not None])
+
+
+ def getDigestResponse(self, challenge, ncount):
+ """
+ Calculate the response for the given challenge
+ """
+ nonce = challenge.get('nonce')
+ algo = challenge.get('algorithm').lower()
+ qop = challenge.get('qop')
+
+ ha1 = calcHA1(
+ algo, self.username, self.realm, self.password, nonce, self.cnonce)
+ ha2 = calcHA2(algo, "GET", self.uri, qop, None)
+ expected = calcResponse(ha1, ha2, algo, nonce, ncount, self.cnonce, qop)
+ return expected
+
+
+ def test_response(self, quotes=True):
+ """
+ L{DigestCredentialFactory.decode} accepts a digest challenge response
+ and parses it into an L{IUsernameHashedPassword} provider.
+ """
+ challenge = self.credentialFactory.getChallenge(self.clientAddress.host)
+
+ nc = "00000001"
+ clientResponse = self.formatResponse(
+ quotes=quotes,
+ nonce=challenge['nonce'],
+ response=self.getDigestResponse(challenge, nc),
+ nc=nc,
+ opaque=challenge['opaque'])
+ creds = self.credentialFactory.decode(
+ clientResponse, self.method, self.clientAddress.host)
+ self.assertTrue(creds.checkPassword(self.password))
+ self.assertFalse(creds.checkPassword(self.password + 'wrong'))
+
+
+ def test_responseWithoutQuotes(self):
+ """
+ L{DigestCredentialFactory.decode} accepts a digest challenge response
+ which does not quote the values of its fields and parses it into an
+ L{IUsernameHashedPassword} provider in the same way it would a
+ response which included quoted field values.
+ """
+ self.test_response(False)
+
+
+ def test_caseInsensitiveAlgorithm(self):
+ """
+ The case of the algorithm value in the response is ignored when
+ checking the credentials.
+ """
+ self.algorithm = 'MD5'
+ self.test_response()
+
+
+ def test_md5DefaultAlgorithm(self):
+ """
+ The algorithm defaults to MD5 if it is not supplied in the response.
+ """
+ self.algorithm = None
+ self.test_response()
+
+
+ def test_responseWithoutClientIP(self):
+ """
+ L{DigestCredentialFactory.decode} accepts a digest challenge response
+ even if the client address it is passed is C{None}.
+ """
+ challenge = self.credentialFactory.getChallenge(None)
+
+ nc = "00000001"
+ clientResponse = self.formatResponse(
+ nonce=challenge['nonce'],
+ response=self.getDigestResponse(challenge, nc),
+ nc=nc,
+ opaque=challenge['opaque'])
+ creds = self.credentialFactory.decode(clientResponse, self.method, None)
+ self.assertTrue(creds.checkPassword(self.password))
+ self.assertFalse(creds.checkPassword(self.password + 'wrong'))
+
+
+ def test_multiResponse(self):
+ """
+ L{DigestCredentialFactory.decode} handles multiple responses to a
+ single challenge.
+ """
+ challenge = self.credentialFactory.getChallenge(self.clientAddress.host)
+
+ nc = "00000001"
+ clientResponse = self.formatResponse(
+ nonce=challenge['nonce'],
+ response=self.getDigestResponse(challenge, nc),
+ nc=nc,
+ opaque=challenge['opaque'])
+
+ creds = self.credentialFactory.decode(clientResponse, self.method,
+ self.clientAddress.host)
+ self.assertTrue(creds.checkPassword(self.password))
+ self.assertFalse(creds.checkPassword(self.password + 'wrong'))
+
+ nc = "00000002"
+ clientResponse = self.formatResponse(
+ nonce=challenge['nonce'],
+ response=self.getDigestResponse(challenge, nc),
+ nc=nc,
+ opaque=challenge['opaque'])
+
+ creds = self.credentialFactory.decode(clientResponse, self.method,
+ self.clientAddress.host)
+ self.assertTrue(creds.checkPassword(self.password))
+ self.assertFalse(creds.checkPassword(self.password + 'wrong'))
+
+
+ def test_failsWithDifferentMethod(self):
+ """
+ L{DigestCredentialFactory.decode} returns an L{IUsernameHashedPassword}
+ provider which rejects a correct password for the given user if the
+ challenge response request is made using a different HTTP method than
+ was used to request the initial challenge.
+ """
+ challenge = self.credentialFactory.getChallenge(self.clientAddress.host)
+
+ nc = "00000001"
+ clientResponse = self.formatResponse(
+ nonce=challenge['nonce'],
+ response=self.getDigestResponse(challenge, nc),
+ nc=nc,
+ opaque=challenge['opaque'])
+ creds = self.credentialFactory.decode(clientResponse, 'POST',
+ self.clientAddress.host)
+ self.assertFalse(creds.checkPassword(self.password))
+ self.assertFalse(creds.checkPassword(self.password + 'wrong'))
+
+
+ def test_noUsername(self):
+ """
+ L{DigestCredentialFactory.decode} raises L{LoginFailed} if the response
+ has no username field or if the username field is empty.
+ """
+ # Check for no username
+ e = self.assertRaises(
+ LoginFailed,
+ self.credentialFactory.decode,
+ self.formatResponse(username=None),
+ self.method, self.clientAddress.host)
+ self.assertEqual(str(e), "Invalid response, no username given.")
+
+ # Check for an empty username
+ e = self.assertRaises(
+ LoginFailed,
+ self.credentialFactory.decode,
+ self.formatResponse(username=""),
+ self.method, self.clientAddress.host)
+ self.assertEqual(str(e), "Invalid response, no username given.")
+
+
+ def test_noNonce(self):
+ """
+ L{DigestCredentialFactory.decode} raises L{LoginFailed} if the response
+ has no nonce.
+ """
+ e = self.assertRaises(
+ LoginFailed,
+ self.credentialFactory.decode,
+ self.formatResponse(opaque="abc123"),
+ self.method, self.clientAddress.host)
+ self.assertEqual(str(e), "Invalid response, no nonce given.")
+
+
+ def test_noOpaque(self):
+ """
+ L{DigestCredentialFactory.decode} raises L{LoginFailed} if the response
+ has no opaque.
+ """
+ e = self.assertRaises(
+ LoginFailed,
+ self.credentialFactory.decode,
+ self.formatResponse(),
+ self.method, self.clientAddress.host)
+ self.assertEqual(str(e), "Invalid response, no opaque given.")
+
+
+ def test_checkHash(self):
+ """
+ L{DigestCredentialFactory.decode} returns an L{IUsernameDigestHash}
+ provider which can verify a hash of the form 'username:realm:password'.
+ """
+ challenge = self.credentialFactory.getChallenge(self.clientAddress.host)
+
+ nc = "00000001"
+ clientResponse = self.formatResponse(
+ nonce=challenge['nonce'],
+ response=self.getDigestResponse(challenge, nc),
+ nc=nc,
+ opaque=challenge['opaque'])
+
+ creds = self.credentialFactory.decode(clientResponse, self.method,
+ self.clientAddress.host)
+ self.assertTrue(verifyObject(IUsernameDigestHash, creds))
+
+ cleartext = '%s:%s:%s' % (self.username, self.realm, self.password)
+ hash = md5(cleartext)
+ self.assertTrue(creds.checkHash(hash.hexdigest()))
+ hash.update('wrong')
+ self.assertFalse(creds.checkHash(hash.hexdigest()))
+
+
+ def test_invalidOpaque(self):
+ """
+ L{DigestCredentialFactory.decode} raises L{LoginFailed} when the opaque
+ value does not contain all the required parts.
+ """
+ credentialFactory = FakeDigestCredentialFactory(self.algorithm,
+ self.realm)
+ challenge = credentialFactory.getChallenge(self.clientAddress.host)
+
+ exc = self.assertRaises(
+ LoginFailed,
+ credentialFactory._verifyOpaque,
+ 'badOpaque',
+ challenge['nonce'],
+ self.clientAddress.host)
+ self.assertEqual(str(exc), 'Invalid response, invalid opaque value')
+
+ badOpaque = 'foo-' + b64encode('nonce,clientip')
+
+ exc = self.assertRaises(
+ LoginFailed,
+ credentialFactory._verifyOpaque,
+ badOpaque,
+ challenge['nonce'],
+ self.clientAddress.host)
+ self.assertEqual(str(exc), 'Invalid response, invalid opaque value')
+
+ exc = self.assertRaises(
+ LoginFailed,
+ credentialFactory._verifyOpaque,
+ '',
+ challenge['nonce'],
+ self.clientAddress.host)
+ self.assertEqual(str(exc), 'Invalid response, invalid opaque value')
+
+ badOpaque = (
+ 'foo-' + b64encode('%s,%s,foobar' % (
+ challenge['nonce'],
+ self.clientAddress.host)))
+ exc = self.assertRaises(
+ LoginFailed,
+ credentialFactory._verifyOpaque,
+ badOpaque,
+ challenge['nonce'],
+ self.clientAddress.host)
+ self.assertEqual(
+ str(exc), 'Invalid response, invalid opaque/time values')
+
+
+ def test_incompatibleNonce(self):
+ """
+ L{DigestCredentialFactory.decode} raises L{LoginFailed} when the given
+ nonce from the response does not match the nonce encoded in the opaque.
+ """
+ credentialFactory = FakeDigestCredentialFactory(self.algorithm, self.realm)
+ challenge = credentialFactory.getChallenge(self.clientAddress.host)
+
+ badNonceOpaque = credentialFactory._generateOpaque(
+ '1234567890',
+ self.clientAddress.host)
+
+ exc = self.assertRaises(
+ LoginFailed,
+ credentialFactory._verifyOpaque,
+ badNonceOpaque,
+ challenge['nonce'],
+ self.clientAddress.host)
+ self.assertEqual(
+ str(exc),
+ 'Invalid response, incompatible opaque/nonce values')
+
+ exc = self.assertRaises(
+ LoginFailed,
+ credentialFactory._verifyOpaque,
+ badNonceOpaque,
+ '',
+ self.clientAddress.host)
+ self.assertEqual(
+ str(exc),
+ 'Invalid response, incompatible opaque/nonce values')
+
+
+ def test_incompatibleClientIP(self):
+ """
+ L{DigestCredentialFactory.decode} raises L{LoginFailed} when the
+ request comes from a client IP other than what is encoded in the
+ opaque.
+ """
+ credentialFactory = FakeDigestCredentialFactory(self.algorithm, self.realm)
+ challenge = credentialFactory.getChallenge(self.clientAddress.host)
+
+ badAddress = '10.0.0.1'
+ # Sanity check
+ self.assertNotEqual(self.clientAddress.host, badAddress)
+
+ badNonceOpaque = credentialFactory._generateOpaque(
+ challenge['nonce'], badAddress)
+
+ self.assertRaises(
+ LoginFailed,
+ credentialFactory._verifyOpaque,
+ badNonceOpaque,
+ challenge['nonce'],
+ self.clientAddress.host)
+
+
+ def test_oldNonce(self):
+ """
+ L{DigestCredentialFactory.decode} raises L{LoginFailed} when the given
+ opaque is older than C{DigestCredentialFactory.CHALLENGE_LIFETIME_SECS}
+ """
+ credentialFactory = FakeDigestCredentialFactory(self.algorithm,
+ self.realm)
+ challenge = credentialFactory.getChallenge(self.clientAddress.host)
+
+ key = '%s,%s,%s' % (challenge['nonce'],
+ self.clientAddress.host,
+ '-137876876')
+ digest = md5(key + credentialFactory.privateKey).hexdigest()
+ ekey = b64encode(key)
+
+ oldNonceOpaque = '%s-%s' % (digest, ekey.strip('\n'))
+
+ self.assertRaises(
+ LoginFailed,
+ credentialFactory._verifyOpaque,
+ oldNonceOpaque,
+ challenge['nonce'],
+ self.clientAddress.host)
+
+
+ def test_mismatchedOpaqueChecksum(self):
+ """
+ L{DigestCredentialFactory.decode} raises L{LoginFailed} when the opaque
+ checksum fails verification.
+ """
+ credentialFactory = FakeDigestCredentialFactory(self.algorithm,
+ self.realm)
+ challenge = credentialFactory.getChallenge(self.clientAddress.host)
+
+ key = '%s,%s,%s' % (challenge['nonce'],
+ self.clientAddress.host,
+ '0')
+
+ digest = md5(key + 'this is not the right pkey').hexdigest()
+ badChecksum = '%s-%s' % (digest, b64encode(key))
+
+ self.assertRaises(
+ LoginFailed,
+ credentialFactory._verifyOpaque,
+ badChecksum,
+ challenge['nonce'],
+ self.clientAddress.host)
+
+
+ def test_incompatibleCalcHA1Options(self):
+ """
+ L{calcHA1} raises L{TypeError} when any of the pszUsername, pszRealm,
+ or pszPassword arguments are specified with the preHA1 keyword
+ argument.
+ """
+ arguments = (
+ ("user", "realm", "password", "preHA1"),
+ (None, "realm", None, "preHA1"),
+ (None, None, "password", "preHA1"),
+ )
+
+ for pszUsername, pszRealm, pszPassword, preHA1 in arguments:
+ self.assertRaises(
+ TypeError,
+ calcHA1,
+ "md5",
+ pszUsername,
+ pszRealm,
+ pszPassword,
+ "nonce",
+ "cnonce",
+ preHA1=preHA1)
+
+
+ def test_noNewlineOpaque(self):
+ """
+ L{DigestCredentialFactory._generateOpaque} returns a value without
+ newlines, regardless of the length of the nonce.
+ """
+ opaque = self.credentialFactory._generateOpaque(
+ "long nonce " * 10, None)
+ self.assertNotIn('\n', opaque)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_dirdbm.py b/vendor/Twisted-10.0.0/twisted/test/test_dirdbm.py
new file mode 100644
index 0000000000..91fe904366
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_dirdbm.py
@@ -0,0 +1,176 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for dirdbm module.
+"""
+
+import os, shutil, glob
+
+from twisted.trial import unittest
+from twisted.persisted import dirdbm
+
+
+
+class DirDbmTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.path = self.mktemp()
+ self.dbm = dirdbm.open(self.path)
+ self.items = (('abc', 'foo'), ('/lalal', '\000\001'), ('\000\012', 'baz'))
+
+
+ def testAll(self):
+ k = "//==".decode("base64")
+ self.dbm[k] = "a"
+ self.dbm[k] = "a"
+ self.assertEquals(self.dbm[k], "a")
+
+
+ def testRebuildInteraction(self):
+ from twisted.persisted import dirdbm
+ from twisted.python import rebuild
+
+ s = dirdbm.Shelf('dirdbm.rebuild.test')
+ s['key'] = 'value'
+ rebuild.rebuild(dirdbm)
+ # print s['key']
+
+
+ def testDbm(self):
+ d = self.dbm
+
+ # insert keys
+ keys = []
+ values = []
+ for k, v in self.items:
+ d[k] = v
+ keys.append(k)
+ values.append(v)
+ keys.sort()
+ values.sort()
+
+ # check they exist
+ for k, v in self.items:
+ assert d.has_key(k), "has_key() failed"
+ assert d[k] == v, "database has wrong value"
+
+ # check non existent key
+ try:
+ d["XXX"]
+ except KeyError:
+ pass
+ else:
+ assert 0, "didn't raise KeyError on non-existent key"
+
+ # check keys(), values() and items()
+ dbkeys = list(d.keys())
+ dbvalues = list(d.values())
+ dbitems = list(d.items())
+ dbkeys.sort()
+ dbvalues.sort()
+ dbitems.sort()
+ items = list(self.items)
+ items.sort()
+ assert keys == dbkeys, ".keys() output didn't match: %s != %s" % (repr(keys), repr(dbkeys))
+ assert values == dbvalues, ".values() output didn't match: %s != %s" % (repr(values), repr(dbvalues))
+ assert items == dbitems, "items() didn't match: %s != %s" % (repr(items), repr(dbitems))
+
+ copyPath = self.mktemp()
+ d2 = d.copyTo(copyPath)
+
+ copykeys = list(d.keys())
+ copyvalues = list(d.values())
+ copyitems = list(d.items())
+ copykeys.sort()
+ copyvalues.sort()
+ copyitems.sort()
+
+ assert dbkeys == copykeys, ".copyTo().keys() didn't match: %s != %s" % (repr(dbkeys), repr(copykeys))
+ assert dbvalues == copyvalues, ".copyTo().values() didn't match: %s != %s" % (repr(dbvalues), repr(copyvalues))
+ assert dbitems == copyitems, ".copyTo().items() didn't match: %s != %s" % (repr(dbkeys), repr(copyitems))
+
+ d2.clear()
+ assert len(d2.keys()) == len(d2.values()) == len(d2.items()) == 0, ".clear() failed"
+ shutil.rmtree(copyPath)
+
+ # delete items
+ for k, v in self.items:
+ del d[k]
+ assert not d.has_key(k), "has_key() even though we deleted it"
+ assert len(d.keys()) == 0, "database has keys"
+ assert len(d.values()) == 0, "database has values"
+ assert len(d.items()) == 0, "database has items"
+
+
+ def testModificationTime(self):
+ import time
+ # the mtime value for files comes from a different place than the
+ # gettimeofday() system call. On linux, gettimeofday() can be
+ # slightly ahead (due to clock drift which gettimeofday() takes into
+ # account but which open()/write()/close() do not), and if we are
+ # close to the edge of the next second, time.time() can give a value
+ # which is larger than the mtime which results from a subsequent
+ # write(). I consider this a kernel bug, but it is beyond the scope
+ # of this test. Thus we keep the range of acceptability to 3 seconds time.
+ # -warner
+ self.dbm["k"] = "v"
+ self.assert_(abs(time.time() - self.dbm.getModificationTime("k")) <= 3)
+
+
+ def testRecovery(self):
+ """DirDBM: test recovery from directory after a faked crash"""
+ k = self.dbm._encode("key1")
+ f = open(os.path.join(self.path, k + ".rpl"), "wb")
+ f.write("value")
+ f.close()
+
+ k2 = self.dbm._encode("key2")
+ f = open(os.path.join(self.path, k2), "wb")
+ f.write("correct")
+ f.close()
+ f = open(os.path.join(self.path, k2 + ".rpl"), "wb")
+ f.write("wrong")
+ f.close()
+
+ f = open(os.path.join(self.path, "aa.new"), "wb")
+ f.write("deleted")
+ f.close()
+
+ dbm = dirdbm.DirDBM(self.path)
+ assert dbm["key1"] == "value"
+ assert dbm["key2"] == "correct"
+ assert not glob.glob(os.path.join(self.path, "*.new"))
+ assert not glob.glob(os.path.join(self.path, "*.rpl"))
+
+
+ def test_nonStringKeys(self):
+ """
+ L{dirdbm.DirDBM} operations only support string keys: other types
+ should raise a C{AssertionError}. This really ought to be a
+ C{TypeError}, but it'll stay like this for backward compatibility.
+ """
+ self.assertRaises(AssertionError, self.dbm.__setitem__, 2, "3")
+ try:
+ self.assertRaises(AssertionError, self.dbm.__setitem__, "2", 3)
+ except unittest.FailTest:
+ # dirdbm.Shelf.__setitem__ supports non-string values
+ self.assertIsInstance(self.dbm, dirdbm.Shelf)
+ self.assertRaises(AssertionError, self.dbm.__getitem__, 2)
+ self.assertRaises(AssertionError, self.dbm.__delitem__, 2)
+ self.assertRaises(AssertionError, self.dbm.has_key, 2)
+ self.assertRaises(AssertionError, self.dbm.__contains__, 2)
+ self.assertRaises(AssertionError, self.dbm.getModificationTime, 2)
+
+
+
+class ShelfTestCase(DirDbmTestCase):
+
+ def setUp(self):
+ self.path = self.mktemp()
+ self.dbm = dirdbm.Shelf(self.path)
+ self.items = (('abc', 'foo'), ('/lalal', '\000\001'), ('\000\012', 'baz'),
+ ('int', 12), ('float', 12.0), ('tuple', (None, 12)))
+
+
+testCases = [DirDbmTestCase, ShelfTestCase]
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_doc.py b/vendor/Twisted-10.0.0/twisted/test/test_doc.py
new file mode 100644
index 0000000000..1d4121b5d7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_doc.py
@@ -0,0 +1,92 @@
+from twisted.trial import unittest
+import inspect, glob, os
+from os import path
+
+from twisted.python import reflect
+
+import twisted
+
+def errorInFile(f, line=17, name=''):
+ """Return a filename formatted so emacs will recognize it as an error point
+
+ @param line: Line number in file. Defaults to 17 because that's about how
+ long the copyright headers are.
+ """
+ return '%s:%d:%s' % (f, line, name)
+ # return 'File "%s", line %d, in %s' % (f, line, name)
+
+class DocCoverage(unittest.TestCase):
+ def setUp(self):
+ remove = len(os.path.dirname(os.path.dirname(twisted.__file__)))+1
+ def visit(dirlist, directory, files):
+ if '__init__.py' in files:
+ d = directory[remove:].replace('/','.')
+ dirlist.append(d)
+ self.packageNames = []
+ os.path.walk(os.path.dirname(twisted.__file__),
+ visit, self.packageNames)
+
+ def testModules(self):
+ """Looking for docstrings in all modules."""
+ docless = []
+ for packageName in self.packageNames:
+ if packageName in ('twisted.test',):
+ # because some stuff in here behaves oddly when imported
+ continue
+ try:
+ package = reflect.namedModule(packageName)
+ except ImportError, e:
+ # This is testing doc coverage, not importability.
+ # (Really, I don't want to deal with the fact that I don't
+ # have pyserial installed.)
+ # print e
+ pass
+ else:
+ docless.extend(self.modulesInPackage(packageName, package))
+ self.failIf(docless, "No docstrings in module files:\n"
+ "%s" % ('\n'.join(map(errorInFile, docless)),))
+
+ def modulesInPackage(self, packageName, package):
+ docless = []
+ directory = path.dirname(package.__file__)
+ for modfile in glob.glob(path.join(directory, '*.py')):
+ moduleName = inspect.getmodulename(modfile)
+ if moduleName == '__init__':
+ # These are tested by test_packages.
+ continue
+ elif moduleName in ('spelunk_gnome','gtkmanhole'):
+ # argh special case pygtk evil argh. How does epydoc deal
+ # with this?
+ continue
+ try:
+ module = reflect.namedModule('.'.join([packageName,
+ moduleName]))
+ except Exception, e:
+ # print moduleName, "misbehaved:", e
+ pass
+ else:
+ if not inspect.getdoc(module):
+ docless.append(modfile)
+ return docless
+
+ def testPackages(self):
+ """Looking for docstrings in all packages."""
+ docless = []
+ for packageName in self.packageNames:
+ try:
+ package = reflect.namedModule(packageName)
+ except Exception, e:
+ # This is testing doc coverage, not importability.
+ # (Really, I don't want to deal with the fact that I don't
+ # have pyserial installed.)
+ # print e
+ pass
+ else:
+ if not inspect.getdoc(package):
+ docless.append(package.__file__.replace('.pyc','.py'))
+ self.failIf(docless, "No docstrings for package files\n"
+ "%s" % ('\n'.join(map(errorInFile, docless),)))
+
+
+ # This test takes a while and doesn't come close to passing. :(
+ testModules.skip = "Activate me when you feel like writing docstrings, and fixing GTK crashing bugs."
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_enterprise.py b/vendor/Twisted-10.0.0/twisted/test/test_enterprise.py
new file mode 100644
index 0000000000..780c4fb631
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_enterprise.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+General tests for twisted.enterprise.
+"""
+
+from twisted.trial import unittest
+
+from twisted.enterprise import util
+
+class QuotingTestCase(unittest.TestCase):
+
+ def testQuoting(self):
+ for value, typ, expected in [
+ (12, "integer", "12"),
+ ("foo'd", "text", "'foo''d'"),
+ ("\x00abc\\s\xFF", "bytea", "'\\\\000abc\\\\\\\\s\\377'"),
+ (12, "text", "'12'"),
+ (u"123'456", "text", u"'123''456'")
+ ]:
+ self.assertEquals(
+ self.callDeprecated(util._deprecatedVersion, util.quote, value,
+ typ),
+ expected)
+
+
+ def test_safeDeprecation(self):
+ """
+ L{safe} is deprecated.
+ """
+ self.callDeprecated(util._deprecatedVersion, util.safe, "foo")
+
+
+ def test_getKeyColumnDeprecation(self):
+ """
+ L{getKeyColumn} is deprecated.
+ """
+ class Row(object):
+ rowKeyColumns = ()
+ self.callDeprecated(util._deprecatedVersion, util.getKeyColumn, Row, "foo")
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_epoll.py b/vendor/Twisted-10.0.0/twisted/test/test_epoll.py
new file mode 100644
index 0000000000..a5912a7b20
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_epoll.py
@@ -0,0 +1,159 @@
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for epoll wrapper.
+"""
+
+import socket, errno, time
+
+from twisted.trial import unittest
+from twisted.python.util import untilConcludes
+
+try:
+ from twisted.python import _epoll
+except ImportError:
+ _epoll = None
+
+
+class EPoll(unittest.TestCase):
+ """
+ Tests for the low-level epoll bindings.
+ """
+ def setUp(self):
+ """
+ Create a listening server port and a list with which to keep track
+ of created sockets.
+ """
+ self.serverSocket = socket.socket()
+ self.serverSocket.bind(('127.0.0.1', 0))
+ self.serverSocket.listen(1)
+ self.connections = [self.serverSocket]
+
+
+ def tearDown(self):
+ """
+ Close any sockets which were opened by the test.
+ """
+ for skt in self.connections:
+ skt.close()
+
+
+ def _connectedPair(self):
+ """
+ Return the two sockets which make up a new TCP connection.
+ """
+ client = socket.socket()
+ client.setblocking(False)
+ try:
+ client.connect(('127.0.0.1', self.serverSocket.getsockname()[1]))
+ except socket.error, e:
+ self.assertEquals(e.args[0], errno.EINPROGRESS)
+ else:
+ raise unittest.FailTest("Connect should have raised EINPROGRESS")
+ server, addr = self.serverSocket.accept()
+
+ self.connections.extend((client, server))
+ return client, server
+
+
+ def test_create(self):
+ """
+ Test the creation of an epoll object.
+ """
+ try:
+ p = _epoll.epoll(16)
+ except OSError, e:
+ raise unittest.FailTest(str(e))
+ else:
+ p.close()
+
+
+ def test_badCreate(self):
+ """
+ Test that attempting to create an epoll object with some random
+ objects raises a TypeError.
+ """
+ self.assertRaises(TypeError, _epoll.epoll, 1, 2, 3)
+ self.assertRaises(TypeError, _epoll.epoll, 'foo')
+ self.assertRaises(TypeError, _epoll.epoll, None)
+ self.assertRaises(TypeError, _epoll.epoll, ())
+ self.assertRaises(TypeError, _epoll.epoll, ['foo'])
+ self.assertRaises(TypeError, _epoll.epoll, {})
+ self.assertRaises(TypeError, _epoll.epoll)
+
+
+ def test_add(self):
+ """
+ Test adding a socket to an epoll object.
+ """
+ server, client = self._connectedPair()
+
+ p = _epoll.epoll(2)
+ try:
+ p._control(_epoll.CTL_ADD, server.fileno(), _epoll.IN | _epoll.OUT)
+ p._control(_epoll.CTL_ADD, client.fileno(), _epoll.IN | _epoll.OUT)
+ finally:
+ p.close()
+
+
+ def test_controlAndWait(self):
+ """
+ Test waiting on an epoll object which has had some sockets added to
+ it.
+ """
+ client, server = self._connectedPair()
+
+ p = _epoll.epoll(16)
+ p._control(_epoll.CTL_ADD, client.fileno(), _epoll.IN | _epoll.OUT |
+ _epoll.ET)
+ p._control(_epoll.CTL_ADD, server.fileno(), _epoll.IN | _epoll.OUT |
+ _epoll.ET)
+
+ now = time.time()
+ events = untilConcludes(p.wait, 4, 1000)
+ then = time.time()
+ self.failIf(then - now > 0.01)
+
+ events.sort()
+ expected = [(client.fileno(), _epoll.OUT),
+ (server.fileno(), _epoll.OUT)]
+ expected.sort()
+
+ self.assertEquals(events, expected)
+
+ now = time.time()
+ events = untilConcludes(p.wait, 4, 200)
+ then = time.time()
+ self.failUnless(then - now > 0.1)
+ self.failIf(events)
+
+ client.send("Hello!")
+ server.send("world!!!")
+
+ now = time.time()
+ events = untilConcludes(p.wait, 4, 1000)
+ then = time.time()
+ self.failIf(then - now > 0.01)
+
+ events.sort()
+ expected = [(client.fileno(), _epoll.IN | _epoll.OUT),
+ (server.fileno(), _epoll.IN | _epoll.OUT)]
+ expected.sort()
+
+ self.assertEquals(events, expected)
+
+if _epoll is None:
+ EPoll.skip = "_epoll module unavailable"
+else:
+ try:
+ e = _epoll.epoll(16)
+ except IOError, exc:
+ if exc.errno == errno.ENOSYS:
+ del exc
+ EPoll.skip = "epoll support missing from platform"
+ else:
+ raise
+ else:
+ e.close()
+ del e
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_error.py b/vendor/Twisted-10.0.0/twisted/test/test_error.py
new file mode 100644
index 0000000000..d300c46586
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_error.py
@@ -0,0 +1,170 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.trial import unittest
+from twisted.internet import error
+import socket
+
+class TestStringification(unittest.TestCase):
+ """Test that the exceptions have useful stringifications.
+ """
+
+ listOfTests = [
+ #(output, exception[, args[, kwargs]]),
+
+ ("An error occurred binding to an interface.",
+ error.BindError),
+
+ ("An error occurred binding to an interface: foo.",
+ error.BindError, ['foo']),
+
+ ("An error occurred binding to an interface: foo bar.",
+ error.BindError, ['foo', 'bar']),
+
+ ("Couldn't listen on eth0:4242: Foo.",
+ error.CannotListenError,
+ ('eth0', 4242, socket.error('Foo'))),
+
+ ("Message is too long to send.",
+ error.MessageLengthError),
+
+ ("Message is too long to send: foo bar.",
+ error.MessageLengthError, ['foo', 'bar']),
+
+ ("DNS lookup failed.",
+ error.DNSLookupError),
+
+ ("DNS lookup failed: foo bar.",
+ error.DNSLookupError, ['foo', 'bar']),
+
+ ("An error occurred while connecting.",
+ error.ConnectError),
+
+ ("An error occurred while connecting: someOsError.",
+ error.ConnectError, ['someOsError']),
+
+ ("An error occurred while connecting: foo.",
+ error.ConnectError, [], {'string': 'foo'}),
+
+ ("An error occurred while connecting: someOsError: foo.",
+ error.ConnectError, ['someOsError', 'foo']),
+
+ ("Couldn't bind.",
+ error.ConnectBindError),
+
+ ("Couldn't bind: someOsError.",
+ error.ConnectBindError, ['someOsError']),
+
+ ("Couldn't bind: someOsError: foo.",
+ error.ConnectBindError, ['someOsError', 'foo']),
+
+ ("Hostname couldn't be looked up.",
+ error.UnknownHostError),
+
+ ("No route to host.",
+ error.NoRouteError),
+
+ ("Connection was refused by other side.",
+ error.ConnectionRefusedError),
+
+ ("TCP connection timed out.",
+ error.TCPTimedOutError),
+
+ ("File used for UNIX socket is no good.",
+ error.BadFileError),
+
+ ("Service name given as port is unknown.",
+ error.ServiceNameUnknownError),
+
+ ("User aborted connection.",
+ error.UserError),
+
+ ("User timeout caused connection failure.",
+ error.TimeoutError),
+
+ ("An SSL error occurred.",
+ error.SSLError),
+
+ ("Connection to the other side was lost in a non-clean fashion.",
+ error.ConnectionLost),
+
+ ("Connection to the other side was lost in a non-clean fashion: foo bar.",
+ error.ConnectionLost, ['foo', 'bar']),
+
+ ("Connection was closed cleanly.",
+ error.ConnectionDone),
+
+ ("Connection was closed cleanly: foo bar.",
+ error.ConnectionDone, ['foo', 'bar']),
+
+ ("Uh.", #TODO nice docstring, you've got there.
+ error.ConnectionFdescWentAway),
+
+ ("Tried to cancel an already-called event.",
+ error.AlreadyCalled),
+
+ ("Tried to cancel an already-called event: foo bar.",
+ error.AlreadyCalled, ['foo', 'bar']),
+
+ ("Tried to cancel an already-cancelled event.",
+ error.AlreadyCancelled),
+
+ ("A process has ended without apparent errors: process finished with exit code 0.",
+ error.ProcessDone,
+ [None]),
+
+ ("A process has ended with a probable error condition: process ended.",
+ error.ProcessTerminated),
+
+ ("A process has ended with a probable error condition: process ended with exit code 42.",
+ error.ProcessTerminated,
+ [],
+ {'exitCode': 42}),
+
+ ("A process has ended with a probable error condition: process ended by signal SIGBUS.",
+ error.ProcessTerminated,
+ [],
+ {'signal': 'SIGBUS'}),
+
+ ("The Connector was not connecting when it was asked to stop connecting.",
+ error.NotConnectingError),
+
+ ("The Port was not listening when it was asked to stop listening.",
+ error.NotListeningError),
+
+ ]
+
+ def testThemAll(self):
+ for entry in self.listOfTests:
+ output = entry[0]
+ exception = entry[1]
+ try:
+ args = entry[2]
+ except IndexError:
+ args = ()
+ try:
+ kwargs = entry[3]
+ except IndexError:
+ kwargs = {}
+
+ self.failUnlessEqual(
+ str(exception(*args, **kwargs)),
+ output)
+
+
+ def test_connectionLostSubclassOfConnectionClosed(self):
+ """
+ L{error.ConnectionClosed} is a superclass of L{error.ConnectionLost}.
+ """
+ self.assertTrue(issubclass(error.ConnectionLost,
+ error.ConnectionClosed))
+
+
+ def test_connectionDoneSubclassOfConnectionClosed(self):
+ """
+ L{error.ConnectionClosed} is a superclass of L{error.ConnectionDone}.
+ """
+ self.assertTrue(issubclass(error.ConnectionDone,
+ error.ConnectionClosed))
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_explorer.py b/vendor/Twisted-10.0.0/twisted/test/test_explorer.py
new file mode 100644
index 0000000000..cf3bf96f94
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_explorer.py
@@ -0,0 +1,236 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Test cases for explorer
+"""
+
+from twisted.trial import unittest
+
+from twisted.manhole import explorer
+
+import types, string
+
+"""
+# Tests:
+
+ Get an ObjectLink. Browse ObjectLink.identifier. Is it the same?
+
+ Watch Object. Make sure an ObjectLink is received when:
+ Call a method.
+ Set an attribute.
+
+ Have an Object with a setattr class. Watch it.
+ Do both the navite setattr and the watcher get called?
+
+ Sequences with circular references. Does it blow up?
+"""
+
+class SomeDohickey:
+ def __init__(self, *a):
+ self.__dict__['args'] = a
+
+ def bip(self):
+ return self.args
+
+
+class TestBrowser(unittest.TestCase):
+ def setUp(self):
+ self.pool = explorer.explorerPool
+ self.pool.clear()
+ self.testThing = ["How many stairs must a man climb down?",
+ SomeDohickey(42)]
+
+ def test_chain(self):
+ "Following a chain of Explorers."
+ xplorer = self.pool.getExplorer(self.testThing, 'testThing')
+ self.failUnlessEqual(xplorer.id, id(self.testThing))
+ self.failUnlessEqual(xplorer.identifier, 'testThing')
+
+ dxplorer = xplorer.get_elements()[1]
+ self.failUnlessEqual(dxplorer.id, id(self.testThing[1]))
+
+class Watcher:
+ zero = 0
+ def __init__(self):
+ self.links = []
+
+ def receiveBrowserObject(self, olink):
+ self.links.append(olink)
+
+ def setZero(self):
+ self.zero = len(self.links)
+
+ def len(self):
+ return len(self.links) - self.zero
+
+
+class SetattrDohickey:
+ def __setattr__(self, k, v):
+ v = list(str(v))
+ v.reverse()
+ self.__dict__[k] = string.join(v, '')
+
+class MiddleMan(SomeDohickey, SetattrDohickey):
+ pass
+
+# class TestWatch(unittest.TestCase):
+class FIXME_Watch:
+ def setUp(self):
+ self.globalNS = globals().copy()
+ self.localNS = {}
+ self.browser = explorer.ObjectBrowser(self.globalNS, self.localNS)
+ self.watcher = Watcher()
+
+ def test_setAttrPlain(self):
+ "Triggering a watcher response by setting an attribute."
+
+ testThing = SomeDohickey('pencil')
+ self.browser.watchObject(testThing, 'testThing',
+ self.watcher.receiveBrowserObject)
+ self.watcher.setZero()
+
+ testThing.someAttr = 'someValue'
+
+ self.failUnlessEqual(testThing.someAttr, 'someValue')
+ self.failUnless(self.watcher.len())
+ olink = self.watcher.links[-1]
+ self.failUnlessEqual(olink.id, id(testThing))
+
+ def test_setAttrChain(self):
+ "Setting an attribute on a watched object that has __setattr__"
+ testThing = MiddleMan('pencil')
+
+ self.browser.watchObject(testThing, 'testThing',
+ self.watcher.receiveBrowserObject)
+ self.watcher.setZero()
+
+ testThing.someAttr = 'ZORT'
+
+ self.failUnlessEqual(testThing.someAttr, 'TROZ')
+ self.failUnless(self.watcher.len())
+ olink = self.watcher.links[-1]
+ self.failUnlessEqual(olink.id, id(testThing))
+
+
+ def test_method(self):
+ "Triggering a watcher response by invoking a method."
+
+ for testThing in (SomeDohickey('pencil'), MiddleMan('pencil')):
+ self.browser.watchObject(testThing, 'testThing',
+ self.watcher.receiveBrowserObject)
+ self.watcher.setZero()
+
+ rval = testThing.bip()
+ self.failUnlessEqual(rval, ('pencil',))
+
+ self.failUnless(self.watcher.len())
+ olink = self.watcher.links[-1]
+ self.failUnlessEqual(olink.id, id(testThing))
+
+
+def function_noArgs():
+ "A function which accepts no arguments at all."
+ return
+
+def function_simple(a, b, c):
+ "A function which accepts several arguments."
+ return a, b, c
+
+def function_variable(*a, **kw):
+ "A function which accepts a variable number of args and keywords."
+ return a, kw
+
+def function_crazy((alpha, beta), c, d=range(4), **kw):
+ "A function with a mad crazy signature."
+ return alpha, beta, c, d, kw
+
+class TestBrowseFunction(unittest.TestCase):
+
+ def setUp(self):
+ self.pool = explorer.explorerPool
+ self.pool.clear()
+
+ def test_sanity(self):
+ """Basic checks for browse_function.
+
+ Was the proper type returned? Does it have the right name and ID?
+ """
+ for f_name in ('function_noArgs', 'function_simple',
+ 'function_variable', 'function_crazy'):
+ f = eval(f_name)
+
+ xplorer = self.pool.getExplorer(f, f_name)
+
+ self.failUnlessEqual(xplorer.id, id(f))
+
+ self.failUnless(isinstance(xplorer, explorer.ExplorerFunction))
+
+ self.failUnlessEqual(xplorer.name, f_name)
+
+ def test_signature_noArgs(self):
+ """Testing zero-argument function signature.
+ """
+
+ xplorer = self.pool.getExplorer(function_noArgs, 'function_noArgs')
+
+ self.failUnlessEqual(len(xplorer.signature), 0)
+
+ def test_signature_simple(self):
+ """Testing simple function signature.
+ """
+
+ xplorer = self.pool.getExplorer(function_simple, 'function_simple')
+
+ expected_signature = ('a','b','c')
+
+ self.failUnlessEqual(xplorer.signature.name, expected_signature)
+
+ def test_signature_variable(self):
+ """Testing variable-argument function signature.
+ """
+
+ xplorer = self.pool.getExplorer(function_variable,
+ 'function_variable')
+
+ expected_names = ('a','kw')
+ signature = xplorer.signature
+
+ self.failUnlessEqual(signature.name, expected_names)
+ self.failUnless(signature.is_varlist(0))
+ self.failUnless(signature.is_keyword(1))
+
+ def test_signature_crazy(self):
+ """Testing function with crazy signature.
+ """
+ xplorer = self.pool.getExplorer(function_crazy, 'function_crazy')
+
+ signature = xplorer.signature
+
+ expected_signature = [{'name': 'c'},
+ {'name': 'd',
+ 'default': range(4)},
+ {'name': 'kw',
+ 'keywords': 1}]
+
+ # The name of the first argument seems to be indecipherable,
+ # but make sure it has one (and no default).
+ self.failUnless(signature.get_name(0))
+ self.failUnless(not signature.get_default(0)[0])
+
+ self.failUnlessEqual(signature.get_name(1), 'c')
+
+ # Get a list of values from a list of ExplorerImmutables.
+ arg_2_default = map(lambda l: l.value,
+ signature.get_default(2)[1].get_elements())
+
+ self.failUnlessEqual(signature.get_name(2), 'd')
+ self.failUnlessEqual(arg_2_default, range(4))
+
+ self.failUnlessEqual(signature.get_name(3), 'kw')
+ self.failUnless(signature.is_keyword(3))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_extensions.py b/vendor/Twisted-10.0.0/twisted/test/test_extensions.py
new file mode 100644
index 0000000000..e2ecd63950
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_extensions.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+import os
+from os.path import join as opj
+
+from twisted.trial import unittest
+
+from twisted.python import util
+
+
+class CorrectComments(unittest.TestCase):
+ def testNoSlashSlashComments(self):
+ urlarg = util.sibpath(__file__, opj(os.pardir, 'protocols', '_c_urlarg.c'))
+ contents = file(urlarg).read()
+ self.assertEquals(contents.find('//'), -1)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_factories.py b/vendor/Twisted-10.0.0/twisted/test/test_factories.py
new file mode 100644
index 0000000000..8e4cbdaaef
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_factories.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test code for basic Factory classes.
+"""
+
+import pickle
+
+from twisted.trial.unittest import TestCase
+
+from twisted.internet import reactor, defer
+from twisted.internet.task import Clock
+from twisted.internet.protocol import Factory, ReconnectingClientFactory
+from twisted.protocols.basic import Int16StringReceiver
+
+
+
+class In(Int16StringReceiver):
+ def __init__(self):
+ self.msgs = {}
+
+ def connectionMade(self):
+ self.factory.connections += 1
+
+ def stringReceived(self, msg):
+ n, msg = pickle.loads(msg)
+ self.msgs[n] = msg
+ self.sendString(pickle.dumps(n))
+
+ def connectionLost(self, reason):
+ self.factory.allMessages.append(self.msgs)
+ if len(self.factory.allMessages) >= self.factory.goal:
+ self.factory.d.callback(None)
+
+
+
+class Out(Int16StringReceiver):
+ msgs = dict([(x, 'X' * x) for x in range(10)])
+
+ def __init__(self):
+ self.msgs = Out.msgs.copy()
+
+ def connectionMade(self):
+ for i in self.msgs.keys():
+ self.sendString(pickle.dumps( (i, self.msgs[i])))
+
+ def stringReceived(self, msg):
+ n = pickle.loads(msg)
+ del self.msgs[n]
+ if not self.msgs:
+ self.transport.loseConnection()
+ self.factory.howManyTimes -= 1
+ if self.factory.howManyTimes <= 0:
+ self.factory.stopTrying()
+
+
+
+class FakeConnector(object):
+ """
+ A fake connector class, to be used to mock connections failed or lost.
+ """
+
+ def stopConnecting(self):
+ pass
+
+
+ def connect(self):
+ pass
+
+
+
+class ReconnectingFactoryTestCase(TestCase):
+ """
+ Tests for L{ReconnectingClientFactory}.
+ """
+
+ def testStopTrying(self):
+ f = Factory()
+ f.protocol = In
+ f.connections = 0
+ f.allMessages = []
+ f.goal = 2
+ f.d = defer.Deferred()
+
+ c = ReconnectingClientFactory()
+ c.initialDelay = c.delay = 0.2
+ c.protocol = Out
+ c.howManyTimes = 2
+
+ port = reactor.listenTCP(0, f)
+ self.addCleanup(port.stopListening)
+ PORT = port.getHost().port
+ reactor.connectTCP('127.0.0.1', PORT, c)
+
+ f.d.addCallback(self._testStopTrying_1, f, c)
+ return f.d
+ testStopTrying.timeout = 10
+
+
+ def _testStopTrying_1(self, res, f, c):
+ self.assertEquals(len(f.allMessages), 2,
+ "not enough messages -- %s" % f.allMessages)
+ self.assertEquals(f.connections, 2,
+ "Number of successful connections incorrect %d" %
+ f.connections)
+ self.assertEquals(f.allMessages, [Out.msgs] * 2)
+ self.failIf(c.continueTrying, "stopTrying never called or ineffective")
+
+
+ def test_serializeUnused(self):
+ """
+ A L{ReconnectingClientFactory} which hasn't been used for anything
+ can be pickled and unpickled and end up with the same state.
+ """
+ original = ReconnectingClientFactory()
+ reconstituted = pickle.loads(pickle.dumps(original))
+ self.assertEqual(original.__dict__, reconstituted.__dict__)
+
+
+ def test_serializeWithClock(self):
+ """
+ The clock attribute of L{ReconnectingClientFactory} is not serialized,
+ and the restored value sets it to the default value, the reactor.
+ """
+ clock = Clock()
+ original = ReconnectingClientFactory()
+ original.clock = clock
+ reconstituted = pickle.loads(pickle.dumps(original))
+ self.assertIdentical(reconstituted.clock, None)
+
+
+ def test_deserializationResetsParameters(self):
+ """
+ A L{ReconnectingClientFactory} which is unpickled does not have an
+ L{IConnector} and has its reconnecting timing parameters reset to their
+ initial values.
+ """
+ factory = ReconnectingClientFactory()
+ factory.clientConnectionFailed(FakeConnector(), None)
+ self.addCleanup(factory.stopTrying)
+
+ serialized = pickle.dumps(factory)
+ unserialized = pickle.loads(serialized)
+ self.assertEqual(unserialized.connector, None)
+ self.assertEqual(unserialized._callID, None)
+ self.assertEqual(unserialized.retries, 0)
+ self.assertEqual(unserialized.delay, factory.initialDelay)
+ self.assertEqual(unserialized.continueTrying, True)
+
+
+ def test_parametrizedClock(self):
+ """
+ The clock used by L{ReconnectingClientFactory} can be parametrized, so
+ that one can cleanly test reconnections.
+ """
+ clock = Clock()
+ factory = ReconnectingClientFactory()
+ factory.clock = clock
+
+ factory.clientConnectionLost(FakeConnector(), None)
+ self.assertEquals(len(clock.calls), 1)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_failure.py b/vendor/Twisted-10.0.0/twisted/test/test_failure.py
new file mode 100644
index 0000000000..7dc0ab19e9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_failure.py
@@ -0,0 +1,318 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Test cases for failure module.
+"""
+
+import sys
+import StringIO
+import traceback
+
+from twisted.trial import unittest, util
+
+from twisted.python import failure
+
+try:
+ from twisted.test import raiser
+except ImportError:
+ raiser = None
+
+
+class BrokenStr(Exception):
+ def __str__(self):
+ raise self
+
+
+def getDivisionFailure():
+ try:
+ 1/0
+ except:
+ f = failure.Failure()
+ return f
+
+
+class FailureTestCase(unittest.TestCase):
+
+ def testFailAndTrap(self):
+ """Trapping a failure."""
+ try:
+ raise NotImplementedError('test')
+ except:
+ f = failure.Failure()
+ error = f.trap(SystemExit, RuntimeError)
+ self.assertEquals(error, RuntimeError)
+ self.assertEquals(f.type, NotImplementedError)
+
+ def test_notTrapped(self):
+ """Making sure trap doesn't trap what it shouldn't."""
+ try:
+ raise ValueError()
+ except:
+ f = failure.Failure()
+ self.assertRaises(failure.Failure, f.trap, OverflowError)
+
+ def testPrinting(self):
+ out = StringIO.StringIO()
+ try:
+ 1/0
+ except:
+ f = failure.Failure()
+ f.printDetailedTraceback(out)
+ f.printBriefTraceback(out)
+ f.printTraceback(out)
+
+ def testExplictPass(self):
+ e = RuntimeError()
+ f = failure.Failure(e)
+ f.trap(RuntimeError)
+ self.assertEquals(f.value, e)
+
+
+ def _getInnermostFrameLine(self, f):
+ try:
+ f.raiseException()
+ except ZeroDivisionError:
+ tb = traceback.extract_tb(sys.exc_info()[2])
+ return tb[-1][-1]
+ else:
+ raise Exception(
+ "f.raiseException() didn't raise ZeroDivisionError!?")
+
+
+ def testRaiseExceptionWithTB(self):
+ f = getDivisionFailure()
+ innerline = self._getInnermostFrameLine(f)
+ self.assertEquals(innerline, '1/0')
+
+
+ def testLackOfTB(self):
+ f = getDivisionFailure()
+ f.cleanFailure()
+ innerline = self._getInnermostFrameLine(f)
+ self.assertEquals(innerline, '1/0')
+
+ testLackOfTB.todo = "the traceback is not preserved, exarkun said he'll try to fix this! god knows how"
+
+
+ _stringException = "bugger off"
+ def _getStringFailure(self):
+ try:
+ raise self._stringException
+ except:
+ f = failure.Failure()
+ return f
+
+ def test_raiseStringExceptions(self):
+ # String exceptions used to totally bugged f.raiseException
+ f = self._getStringFailure()
+ try:
+ f.raiseException()
+ except:
+ self.assertEquals(sys.exc_info()[0], self._stringException)
+ else:
+ raise AssertionError("Should have raised")
+ test_raiseStringExceptions.suppress = [
+ util.suppress(message='raising a string exception is deprecated')]
+
+
+ def test_printStringExceptions(self):
+ """
+ L{Failure.printTraceback} should write out stack and exception
+ information, even for string exceptions.
+ """
+ failure = self._getStringFailure()
+ output = StringIO.StringIO()
+ failure.printTraceback(file=output)
+ lines = output.getvalue().splitlines()
+ # The last line should be the value of the raised string
+ self.assertEqual(lines[-1], self._stringException)
+
+ test_printStringExceptions.suppress = [
+ util.suppress(message='raising a string exception is deprecated')]
+
+ if sys.version_info[:2] >= (2, 6):
+ skipMsg = ("String exceptions aren't supported anymore starting "
+ "Python 2.6")
+ test_raiseStringExceptions.skip = skipMsg
+ test_printStringExceptions.skip = skipMsg
+
+
+ def testBrokenStr(self):
+ """
+ Formatting a traceback of a Failure which refers to an object
+ that has a broken __str__ implementation should not cause
+ getTraceback to raise an exception.
+ """
+ x = BrokenStr()
+ try:
+ str(x)
+ except:
+ f = failure.Failure()
+ self.assertEquals(f.value, x)
+ try:
+ f.getTraceback()
+ except:
+ self.fail("getTraceback() shouldn't raise an exception")
+
+
+ def testConstructionFails(self):
+ """
+ Creating a Failure with no arguments causes it to try to discover the
+ current interpreter exception state. If no such state exists, creating
+ the Failure should raise a synchronous exception.
+ """
+ self.assertRaises(failure.NoCurrentExceptionError, failure.Failure)
+
+ def test_getTracebackObject(self):
+ """
+ If the C{Failure} has not been cleaned, then C{getTracebackObject}
+ should return the traceback object that it was given in the
+ constructor.
+ """
+ f = getDivisionFailure()
+ self.assertEqual(f.getTracebackObject(), f.tb)
+
+ def test_getTracebackObjectFromClean(self):
+ """
+ If the Failure has been cleaned, then C{getTracebackObject} should
+ return an object that looks the same to L{traceback.extract_tb}.
+ """
+ f = getDivisionFailure()
+ expected = traceback.extract_tb(f.getTracebackObject())
+ f.cleanFailure()
+ observed = traceback.extract_tb(f.getTracebackObject())
+ self.assertEqual(expected, observed)
+
+ def test_getTracebackObjectWithoutTraceback(self):
+ """
+ L{failure.Failure}s need not be constructed with traceback objects. If
+ a C{Failure} has no traceback information at all, C{getTracebackObject}
+ should just return None.
+
+ None is a good value, because traceback.extract_tb(None) -> [].
+ """
+ f = failure.Failure(Exception("some error"))
+ self.assertEqual(f.getTracebackObject(), None)
+
+class FindFailureTests(unittest.TestCase):
+ """
+ Tests for functionality related to L{Failure._findFailure}.
+ """
+
+ def test_findNoFailureInExceptionHandler(self):
+ """
+ Within an exception handler, _findFailure should return
+ C{None} in case no Failure is associated with the current
+ exception.
+ """
+ try:
+ 1/0
+ except:
+ self.assertEqual(failure.Failure._findFailure(), None)
+ else:
+ self.fail("No exception raised from 1/0!?")
+
+
+ def test_findNoFailure(self):
+ """
+ Outside of an exception handler, _findFailure should return None.
+ """
+ self.assertEqual(sys.exc_info()[-1], None) #environment sanity check
+ self.assertEqual(failure.Failure._findFailure(), None)
+
+
+ def test_findFailure(self):
+ """
+ Within an exception handler, it should be possible to find the
+ original Failure that caused the current exception (if it was
+ caused by raiseException).
+ """
+ f = getDivisionFailure()
+ f.cleanFailure()
+ try:
+ f.raiseException()
+ except:
+ self.assertEqual(failure.Failure._findFailure(), f)
+ else:
+ self.fail("No exception raised from raiseException!?")
+
+
+ def test_failureConstructionFindsOriginalFailure(self):
+ """
+ When a Failure is constructed in the context of an exception
+ handler that is handling an exception raised by
+ raiseException, the new Failure should be chained to that
+ original Failure.
+ """
+ f = getDivisionFailure()
+ f.cleanFailure()
+ try:
+ f.raiseException()
+ except:
+ newF = failure.Failure()
+ self.assertEqual(f.getTraceback(), newF.getTraceback())
+ else:
+ self.fail("No exception raised from raiseException!?")
+
+
+ def test_failureConstructionWithMungedStackSucceeds(self):
+ """
+ Pyrex and Cython are known to insert fake stack frames so as to give
+ more Python-like tracebacks. These stack frames with empty code objects
+ should not break extraction of the exception.
+ """
+ try:
+ raiser.raiseException()
+ except raiser.RaiserException:
+ f = failure.Failure()
+ self.assertTrue(f.check(raiser.RaiserException))
+ else:
+ self.fail("No exception raised from extension?!")
+
+
+ if raiser is None:
+ skipMsg = "raiser extension not available"
+ test_failureConstructionWithMungedStackSucceeds.skip = skipMsg
+
+
+
+class TestFormattableTraceback(unittest.TestCase):
+ """
+ Whitebox tests that show that L{failure._Traceback} constructs objects that
+ can be used by L{traceback.extract_tb}.
+
+ If the objects can be used by L{traceback.extract_tb}, then they can be
+ formatted using L{traceback.format_tb} and friends.
+ """
+
+ def test_singleFrame(self):
+ """
+ A C{_Traceback} object constructed with a single frame should be able
+ to be passed to L{traceback.extract_tb}, and we should get a singleton
+ list containing a (filename, lineno, methodname, line) tuple.
+ """
+ tb = failure._Traceback([['method', 'filename.py', 123, {}, {}]])
+ # Note that we don't need to test that extract_tb correctly extracts
+ # the line's contents. In this case, since filename.py doesn't exist,
+ # it will just use None.
+ self.assertEqual(traceback.extract_tb(tb),
+ [('filename.py', 123, 'method', None)])
+
+ def test_manyFrames(self):
+ """
+ A C{_Traceback} object constructed with multiple frames should be able
+ to be passed to L{traceback.extract_tb}, and we should get a list
+ containing a tuple for each frame.
+ """
+ tb = failure._Traceback([
+ ['method1', 'filename.py', 123, {}, {}],
+ ['method2', 'filename.py', 235, {}, {}]])
+ self.assertEqual(traceback.extract_tb(tb),
+ [('filename.py', 123, 'method1', None),
+ ('filename.py', 235, 'method2', None)])
+
+
+if sys.version_info[:2] >= (2, 5):
+ from twisted.test.generator_failure_tests import TwoPointFiveFailureTests
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_fdesc.py b/vendor/Twisted-10.0.0/twisted/test/test_fdesc.py
new file mode 100644
index 0000000000..7ce484135b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_fdesc.py
@@ -0,0 +1,235 @@
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.internet.fdesc}.
+"""
+
+import os, sys
+import errno
+
+try:
+ import fcntl
+except ImportError:
+ skip = "not supported on this platform"
+else:
+ from twisted.internet import fdesc
+
+from twisted.python.util import untilConcludes
+from twisted.trial import unittest
+
+
+class ReadWriteTestCase(unittest.TestCase):
+ """
+ Tests for fdesc.readFromFD, fdesc.writeToFD.
+ """
+
+ def setUp(self):
+ """
+ Create two non-blocking pipes that can be used in tests.
+ """
+ self.r, self.w = os.pipe()
+ fdesc.setNonBlocking(self.r)
+ fdesc.setNonBlocking(self.w)
+
+
+ def tearDown(self):
+ """
+ Close pipes.
+ """
+ try:
+ os.close(self.w)
+ except OSError:
+ pass
+ try:
+ os.close(self.r)
+ except OSError:
+ pass
+
+
+ def write(self, d):
+ """
+ Write data to the pipe.
+ """
+ return fdesc.writeToFD(self.w, d)
+
+
+ def read(self):
+ """
+ Read data from the pipe.
+ """
+ l = []
+ res = fdesc.readFromFD(self.r, l.append)
+ if res is None:
+ if l:
+ return l[0]
+ else:
+ return ""
+ else:
+ return res
+
+
+ def test_writeAndRead(self):
+ """
+ Test that the number of bytes L{fdesc.writeToFD} reports as written
+ with its return value are seen by L{fdesc.readFromFD}.
+ """
+ n = self.write("hello")
+ self.failUnless(n > 0)
+ s = self.read()
+ self.assertEquals(len(s), n)
+ self.assertEquals("hello"[:n], s)
+
+
+ def test_writeAndReadLarge(self):
+ """
+ Similar to L{test_writeAndRead}, but use a much larger string to verify
+ the behavior for that case.
+ """
+ orig = "0123456879" * 10000
+ written = self.write(orig)
+ self.failUnless(written > 0)
+ result = []
+ resultlength = 0
+ i = 0
+ while resultlength < written or i < 50:
+ result.append(self.read())
+ resultlength += len(result[-1])
+ # Increment a counter to be sure we'll exit at some point
+ i += 1
+ result = "".join(result)
+ self.assertEquals(len(result), written)
+ self.assertEquals(orig[:written], result)
+
+
+ def test_readFromEmpty(self):
+ """
+ Verify that reading from a file descriptor with no data does not raise
+ an exception and does not result in the callback function being called.
+ """
+ l = []
+ result = fdesc.readFromFD(self.r, l.append)
+ self.assertEquals(l, [])
+ self.assertEquals(result, None)
+
+
+ def test_readFromCleanClose(self):
+ """
+ Test that using L{fdesc.readFromFD} on a cleanly closed file descriptor
+ returns a connection done indicator.
+ """
+ os.close(self.w)
+ self.assertEquals(self.read(), fdesc.CONNECTION_DONE)
+
+
+ def test_writeToClosed(self):
+ """
+ Verify that writing with L{fdesc.writeToFD} when the read end is closed
+ results in a connection lost indicator.
+ """
+ os.close(self.r)
+ self.assertEquals(self.write("s"), fdesc.CONNECTION_LOST)
+
+
+ def test_readFromInvalid(self):
+ """
+ Verify that reading with L{fdesc.readFromFD} when the read end is
+ closed results in a connection lost indicator.
+ """
+ os.close(self.r)
+ self.assertEquals(self.read(), fdesc.CONNECTION_LOST)
+
+
+ def test_writeToInvalid(self):
+ """
+ Verify that writing with L{fdesc.writeToFD} when the write end is
+ closed results in a connection lost indicator.
+ """
+ os.close(self.w)
+ self.assertEquals(self.write("s"), fdesc.CONNECTION_LOST)
+
+
+ def test_writeErrors(self):
+ """
+ Test error path for L{fdesc.writeTod}.
+ """
+ oldOsWrite = os.write
+ def eagainWrite(fd, data):
+ err = OSError()
+ err.errno = errno.EAGAIN
+ raise err
+ os.write = eagainWrite
+ try:
+ self.assertEquals(self.write("s"), 0)
+ finally:
+ os.write = oldOsWrite
+
+ def eintrWrite(fd, data):
+ err = OSError()
+ err.errno = errno.EINTR
+ raise err
+ os.write = eintrWrite
+ try:
+ self.assertEquals(self.write("s"), 0)
+ finally:
+ os.write = oldOsWrite
+
+
+
+class CloseOnExecTests(unittest.TestCase):
+ """
+ Tests for L{fdesc._setCloseOnExec} and L{fdesc._unsetCloseOnExec}.
+ """
+ program = '''
+import os, errno
+try:
+ os.write(%d, 'lul')
+except OSError, e:
+ if e.errno == errno.EBADF:
+ os._exit(0)
+ os._exit(5)
+except:
+ os._exit(10)
+else:
+ os._exit(20)
+'''
+
+ def _execWithFileDescriptor(self, fObj):
+ pid = os.fork()
+ if pid == 0:
+ try:
+ os.execv(sys.executable, [sys.executable, '-c', self.program % (fObj.fileno(),)])
+ except:
+ import traceback
+ traceback.print_exc()
+ os._exit(30)
+ else:
+ # On Linux wait(2) doesn't seem ever able to fail with EINTR but
+ # POSIX seems to allow it and on OS X it happens quite a lot.
+ return untilConcludes(os.waitpid, pid, 0)[1]
+
+
+ def test_setCloseOnExec(self):
+ """
+ A file descriptor passed to L{fdesc._setCloseOnExec} is not inherited
+ by a new process image created with one of the exec family of
+ functions.
+ """
+ fObj = file(self.mktemp(), 'w')
+ fdesc._setCloseOnExec(fObj.fileno())
+ status = self._execWithFileDescriptor(fObj)
+ self.assertTrue(os.WIFEXITED(status))
+ self.assertEqual(os.WEXITSTATUS(status), 0)
+
+
+ def test_unsetCloseOnExec(self):
+ """
+ A file descriptor passed to L{fdesc._unsetCloseOnExec} is inherited by
+ a new process image created with one of the exec family of functions.
+ """
+ fObj = file(self.mktemp(), 'w')
+ fdesc._setCloseOnExec(fObj.fileno())
+ fdesc._unsetCloseOnExec(fObj.fileno())
+ status = self._execWithFileDescriptor(fObj)
+ self.assertTrue(os.WIFEXITED(status))
+ self.assertEqual(os.WEXITSTATUS(status), 20)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_finger.py b/vendor/Twisted-10.0.0/twisted/test/test_finger.py
new file mode 100644
index 0000000000..395d1d9cb5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_finger.py
@@ -0,0 +1,67 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.protocols.finger}.
+"""
+
+from twisted.trial import unittest
+from twisted.protocols import finger
+from twisted.test.proto_helpers import StringTransport
+
+
+class FingerTestCase(unittest.TestCase):
+ """
+ Tests for L{finger.Finger}.
+ """
+ def setUp(self):
+ """
+ Create and connect a L{finger.Finger} instance.
+ """
+ self.transport = StringTransport()
+ self.protocol = finger.Finger()
+ self.protocol.makeConnection(self.transport)
+
+
+ def test_simple(self):
+ """
+ When L{finger.Finger} receives a CR LF terminated line, it responds
+ with the default user status message - that no such user exists.
+ """
+ self.protocol.dataReceived("moshez\r\n")
+ self.assertEqual(
+ self.transport.value(),
+ "Login: moshez\nNo such user\n")
+
+
+ def test_simpleW(self):
+ """
+ The behavior for a query which begins with C{"/w"} is the same as the
+ behavior for one which does not. The user is reported as not existing.
+ """
+ self.protocol.dataReceived("/w moshez\r\n")
+ self.assertEqual(
+ self.transport.value(),
+ "Login: moshez\nNo such user\n")
+
+
+ def test_forwarding(self):
+ """
+ When L{finger.Finger} receives a request for a remote user, it responds
+ with a message rejecting the request.
+ """
+ self.protocol.dataReceived("moshez@example.com\r\n")
+ self.assertEqual(
+ self.transport.value(),
+ "Finger forwarding service denied\n")
+
+
+ def test_list(self):
+ """
+ When L{finger.Finger} receives a blank line, it responds with a message
+ rejecting the request for all online users.
+ """
+ self.protocol.dataReceived("\r\n")
+ self.assertEqual(
+ self.transport.value(),
+ "Finger online list denied\n")
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_formmethod.py b/vendor/Twisted-10.0.0/twisted/test/test_formmethod.py
new file mode 100644
index 0000000000..a28bb576d0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_formmethod.py
@@ -0,0 +1,77 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Test cases for formmethod module.
+"""
+
+from twisted.trial import unittest
+
+from twisted.python import formmethod
+
+
+class ArgumentTestCase(unittest.TestCase):
+
+ def argTest(self, argKlass, testPairs, badValues, *args, **kwargs):
+ arg = argKlass("name", *args, **kwargs)
+ for val, result in testPairs:
+ self.assertEquals(arg.coerce(val), result)
+ for val in badValues:
+ self.assertRaises(formmethod.InputError, arg.coerce, val)
+
+ def testString(self):
+ self.argTest(formmethod.String, [("a", "a"), (1, "1"), ("", "")], ())
+ self.argTest(formmethod.String, [("ab", "ab"), ("abc", "abc")], ("2", ""), min=2)
+ self.argTest(formmethod.String, [("ab", "ab"), ("a", "a")], ("223213", "345x"), max=3)
+ self.argTest(formmethod.String, [("ab", "ab"), ("add", "add")], ("223213", "x"), min=2, max=3)
+
+ def testInt(self):
+ self.argTest(formmethod.Integer, [("3", 3), ("-2", -2), ("", None)], ("q", "2.3"))
+ self.argTest(formmethod.Integer, [("3", 3), ("-2", -2)], ("q", "2.3", ""), allowNone=0)
+
+ def testFloat(self):
+ self.argTest(formmethod.Float, [("3", 3.0), ("-2.3", -2.3), ("", None)], ("q", "2.3z"))
+ self.argTest(formmethod.Float, [("3", 3.0), ("-2.3", -2.3)], ("q", "2.3z", ""),
+ allowNone=0)
+
+ def testChoice(self):
+ choices = [("a", "apple", "an apple"),
+ ("b", "banana", "ook")]
+ self.argTest(formmethod.Choice, [("a", "apple"), ("b", "banana")],
+ ("c", 1), choices=choices)
+
+ def testFlags(self):
+ flags = [("a", "apple", "an apple"),
+ ("b", "banana", "ook")]
+ self.argTest(formmethod.Flags,
+ [(["a"], ["apple"]), (["b", "a"], ["banana", "apple"])],
+ (["a", "c"], ["fdfs"]),
+ flags=flags)
+
+ def testBoolean(self):
+ tests = [("yes", 1), ("", 0), ("False", 0), ("no", 0)]
+ self.argTest(formmethod.Boolean, tests, ())
+
+ def testDate(self):
+ goodTests = {
+ ("2002", "12", "21"): (2002, 12, 21),
+ ("1996", "2", "29"): (1996, 2, 29),
+ ("", "", ""): None,
+ }.items()
+ badTests = [("2002", "2", "29"), ("xx", "2", "3"),
+ ("2002", "13", "1"), ("1999", "12","32"),
+ ("2002", "1"), ("2002", "2", "3", "4")]
+ self.argTest(formmethod.Date, goodTests, badTests)
+
+ def testRangedInteger(self):
+ goodTests = {"0": 0, "12": 12, "3": 3}.items()
+ badTests = ["-1", "x", "13", "-2000", "3.4"]
+ self.argTest(formmethod.IntegerRange, goodTests, badTests, 0, 12)
+
+ def testVerifiedPassword(self):
+ goodTests = {("foo", "foo"): "foo", ("ab", "ab"): "ab"}.items()
+ badTests = [("ab", "a"), ("12345", "12345"), ("", ""), ("a", "a"), ("a",), ("a", "a", "a")]
+ self.argTest(formmethod.VerifiedPassword, goodTests, badTests, min=2, max=4)
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_ftp.py b/vendor/Twisted-10.0.0/twisted/test/test_ftp.py
new file mode 100644
index 0000000000..abbc48ac00
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_ftp.py
@@ -0,0 +1,2671 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+FTP tests.
+
+Maintainer: Andrew Bennetts
+"""
+
+import os
+import errno
+
+from zope.interface import implements
+
+from twisted.trial import unittest, util
+from twisted.protocols import basic
+from twisted.internet import reactor, task, protocol, defer, error
+from twisted.internet.interfaces import IConsumer
+from twisted.cred import portal, checkers, credentials
+from twisted.python import failure, filepath
+from twisted.test import proto_helpers
+
+from twisted.protocols import ftp, loopback
+
+
+_changeDirectorySuppression = util.suppress(
+ category=DeprecationWarning,
+ message=(
+ r"FTPClient\.changeDirectory is deprecated in Twisted 8\.2 and "
+ r"newer\. Use FTPClient\.cwd instead\."))
+
+
+class Dummy(basic.LineReceiver):
+ logname = None
+ def __init__(self):
+ self.lines = []
+ self.rawData = []
+ def connectionMade(self):
+ self.f = self.factory # to save typing in pdb :-)
+ def lineReceived(self,line):
+ self.lines.append(line)
+ def rawDataReceived(self, data):
+ self.rawData.append(data)
+ def lineLengthExceeded(self, line):
+ pass
+
+
+class _BufferingProtocol(protocol.Protocol):
+ def connectionMade(self):
+ self.buffer = ''
+ self.d = defer.Deferred()
+ def dataReceived(self, data):
+ self.buffer += data
+ def connectionLost(self, reason):
+ self.d.callback(self)
+
+
+
+class FTPServerTestCase(unittest.TestCase):
+ """
+ Simple tests for an FTP server with the default settings.
+
+ @ivar clientFactory: class used as ftp client.
+ """
+ clientFactory = ftp.FTPClientBasic
+ userAnonymous = "anonymous"
+
+ def setUp(self):
+ # Create a directory
+ self.directory = self.mktemp()
+ os.mkdir(self.directory)
+ self.dirPath = filepath.FilePath(self.directory)
+
+ # Start the server
+ p = portal.Portal(ftp.FTPRealm(self.directory))
+ p.registerChecker(checkers.AllowAnonymousAccess(),
+ credentials.IAnonymous)
+ self.factory = ftp.FTPFactory(portal=p,
+ userAnonymous=self.userAnonymous)
+ port = reactor.listenTCP(0, self.factory, interface="127.0.0.1")
+ self.addCleanup(port.stopListening)
+
+ # Hook the server's buildProtocol to make the protocol instance
+ # accessible to tests.
+ buildProtocol = self.factory.buildProtocol
+ d1 = defer.Deferred()
+ def _rememberProtocolInstance(addr):
+ # Done hooking this.
+ del self.factory.buildProtocol
+
+ protocol = buildProtocol(addr)
+ self.serverProtocol = protocol.wrappedProtocol
+ def cleanupServer():
+ if self.serverProtocol.transport is not None:
+ self.serverProtocol.transport.loseConnection()
+ self.addCleanup(cleanupServer)
+ d1.callback(None)
+ return protocol
+ self.factory.buildProtocol = _rememberProtocolInstance
+
+ # Connect a client to it
+ portNum = port.getHost().port
+ clientCreator = protocol.ClientCreator(reactor, self.clientFactory)
+ d2 = clientCreator.connectTCP("127.0.0.1", portNum)
+ def gotClient(client):
+ self.client = client
+ self.addCleanup(self.client.transport.loseConnection)
+ d2.addCallback(gotClient)
+ return defer.gatherResults([d1, d2])
+
+ def assertCommandResponse(self, command, expectedResponseLines,
+ chainDeferred=None):
+ """Asserts that a sending an FTP command receives the expected
+ response.
+
+ Returns a Deferred. Optionally accepts a deferred to chain its actions
+ to.
+ """
+ if chainDeferred is None:
+ chainDeferred = defer.succeed(None)
+
+ def queueCommand(ignored):
+ d = self.client.queueStringCommand(command)
+ def gotResponse(responseLines):
+ self.assertEquals(expectedResponseLines, responseLines)
+ return d.addCallback(gotResponse)
+ return chainDeferred.addCallback(queueCommand)
+
+ def assertCommandFailed(self, command, expectedResponse=None,
+ chainDeferred=None):
+ if chainDeferred is None:
+ chainDeferred = defer.succeed(None)
+
+ def queueCommand(ignored):
+ return self.client.queueStringCommand(command)
+ chainDeferred.addCallback(queueCommand)
+ self.assertFailure(chainDeferred, ftp.CommandFailed)
+ def failed(exception):
+ if expectedResponse is not None:
+ self.failUnlessEqual(
+ expectedResponse, exception.args[0])
+ return chainDeferred.addCallback(failed)
+
+ def _anonymousLogin(self):
+ d = self.assertCommandResponse(
+ 'USER anonymous',
+ ['331 Guest login ok, type your email address as password.'])
+ return self.assertCommandResponse(
+ 'PASS test@twistedmatrix.com',
+ ['230 Anonymous login ok, access restrictions apply.'],
+ chainDeferred=d)
+
+
+
+class FTPAnonymousTestCase(FTPServerTestCase):
+ """
+ Simple tests for an FTP server with different anonymous username.
+ The new anonymous username used in this test case is "guest"
+ """
+ userAnonymous = "guest"
+
+ def test_anonymousLogin(self):
+ """
+ Tests whether the changing of the anonymous username is working or not.
+ The FTP server should not comply about the need of password for the
+ username 'guest', letting it login as anonymous asking just an email
+ address as password.
+ """
+ d = self.assertCommandResponse(
+ 'USER guest',
+ ['331 Guest login ok, type your email address as password.'])
+ return self.assertCommandResponse(
+ 'PASS test@twistedmatrix.com',
+ ['230 Anonymous login ok, access restrictions apply.'],
+ chainDeferred=d)
+
+
+
+class BasicFTPServerTestCase(FTPServerTestCase):
+ def testNotLoggedInReply(self):
+ """When not logged in, all commands other than USER and PASS should
+ get NOT_LOGGED_IN errors.
+ """
+ commandList = ['CDUP', 'CWD', 'LIST', 'MODE', 'PASV',
+ 'PWD', 'RETR', 'STRU', 'SYST', 'TYPE']
+
+ # Issue commands, check responses
+ def checkResponse(exception):
+ failureResponseLines = exception.args[0]
+ self.failUnless(failureResponseLines[-1].startswith("530"),
+ "Response didn't start with 530: %r"
+ % (failureResponseLines[-1],))
+ deferreds = []
+ for command in commandList:
+ deferred = self.client.queueStringCommand(command)
+ self.assertFailure(deferred, ftp.CommandFailed)
+ deferred.addCallback(checkResponse)
+ deferreds.append(deferred)
+ return defer.DeferredList(deferreds, fireOnOneErrback=True)
+
+ def testPASSBeforeUSER(self):
+ """Issuing PASS before USER should give an error."""
+ return self.assertCommandFailed(
+ 'PASS foo',
+ ["503 Incorrect sequence of commands: "
+ "USER required before PASS"])
+
+ def testNoParamsForUSER(self):
+ """Issuing USER without a username is a syntax error."""
+ return self.assertCommandFailed(
+ 'USER',
+ ['500 Syntax error: USER requires an argument.'])
+
+ def testNoParamsForPASS(self):
+ """Issuing PASS without a password is a syntax error."""
+ d = self.client.queueStringCommand('USER foo')
+ return self.assertCommandFailed(
+ 'PASS',
+ ['500 Syntax error: PASS requires an argument.'],
+ chainDeferred=d)
+
+ def testAnonymousLogin(self):
+ return self._anonymousLogin()
+
+ def testQuit(self):
+ """Issuing QUIT should return a 221 message."""
+ d = self._anonymousLogin()
+ return self.assertCommandResponse(
+ 'QUIT',
+ ['221 Goodbye.'],
+ chainDeferred=d)
+
+ def testAnonymousLoginDenied(self):
+ # Reconfigure the server to disallow anonymous access, and to have an
+ # IUsernamePassword checker that always rejects.
+ self.factory.allowAnonymous = False
+ denyAlwaysChecker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ self.factory.portal.registerChecker(denyAlwaysChecker,
+ credentials.IUsernamePassword)
+
+ # Same response code as allowAnonymous=True, but different text.
+ d = self.assertCommandResponse(
+ 'USER anonymous',
+ ['331 Password required for anonymous.'])
+
+ # It will be denied. No-one can login.
+ d = self.assertCommandFailed(
+ 'PASS test@twistedmatrix.com',
+ ['530 Sorry, Authentication failed.'],
+ chainDeferred=d)
+
+ # It's not just saying that. You aren't logged in.
+ d = self.assertCommandFailed(
+ 'PWD',
+ ['530 Please login with USER and PASS.'],
+ chainDeferred=d)
+ return d
+
+ def testUnknownCommand(self):
+ d = self._anonymousLogin()
+ return self.assertCommandFailed(
+ 'GIBBERISH',
+ ["502 Command 'GIBBERISH' not implemented"],
+ chainDeferred=d)
+
+ def testRETRBeforePORT(self):
+ d = self._anonymousLogin()
+ return self.assertCommandFailed(
+ 'RETR foo',
+ ["503 Incorrect sequence of commands: "
+ "PORT or PASV required before RETR"],
+ chainDeferred=d)
+
+ def testSTORBeforePORT(self):
+ d = self._anonymousLogin()
+ return self.assertCommandFailed(
+ 'STOR foo',
+ ["503 Incorrect sequence of commands: "
+ "PORT or PASV required before STOR"],
+ chainDeferred=d)
+
+ def testBadCommandArgs(self):
+ d = self._anonymousLogin()
+ self.assertCommandFailed(
+ 'MODE z',
+ ["504 Not implemented for parameter 'z'."],
+ chainDeferred=d)
+ self.assertCommandFailed(
+ 'STRU I',
+ ["504 Not implemented for parameter 'I'."],
+ chainDeferred=d)
+ return d
+
+ def testDecodeHostPort(self):
+ self.assertEquals(ftp.decodeHostPort('25,234,129,22,100,23'),
+ ('25.234.129.22', 25623))
+ nums = range(6)
+ for i in range(6):
+ badValue = list(nums)
+ badValue[i] = 256
+ s = ','.join(map(str, badValue))
+ self.assertRaises(ValueError, ftp.decodeHostPort, s)
+
+ def testPASV(self):
+ # Login
+ wfd = defer.waitForDeferred(self._anonymousLogin())
+ yield wfd
+ wfd.getResult()
+
+ # Issue a PASV command, and extract the host and port from the response
+ pasvCmd = defer.waitForDeferred(self.client.queueStringCommand('PASV'))
+ yield pasvCmd
+ responseLines = pasvCmd.getResult()
+ host, port = ftp.decodeHostPort(responseLines[-1][4:])
+
+ # Make sure the server is listening on the port it claims to be
+ self.assertEqual(port, self.serverProtocol.dtpPort.getHost().port)
+
+ # Semi-reasonable way to force cleanup
+ self.serverProtocol.transport.loseConnection()
+ testPASV = defer.deferredGenerator(testPASV)
+
+ def testSYST(self):
+ d = self._anonymousLogin()
+ self.assertCommandResponse('SYST', ["215 UNIX Type: L8"],
+ chainDeferred=d)
+ return d
+
+
+ def test_portRangeForwardError(self):
+ """
+ Exceptions other than L{error.CannotListenError} which are raised by
+ C{listenFactory} should be raised to the caller of L{FTP.getDTPPort}.
+ """
+ def listenFactory(portNumber, factory):
+ raise RuntimeError()
+ self.serverProtocol.listenFactory = listenFactory
+
+ self.assertRaises(RuntimeError, self.serverProtocol.getDTPPort,
+ protocol.Factory())
+
+
+ def test_portRange(self):
+ """
+ L{FTP.passivePortRange} should determine the ports which
+ L{FTP.getDTPPort} attempts to bind. If no port from that iterator can
+ be bound, L{error.CannotListenError} should be raised, otherwise the
+ first successful result from L{FTP.listenFactory} should be returned.
+ """
+ def listenFactory(portNumber, factory):
+ if portNumber in (22032, 22033, 22034):
+ raise error.CannotListenError('localhost', portNumber, 'error')
+ return portNumber
+ self.serverProtocol.listenFactory = listenFactory
+
+ port = self.serverProtocol.getDTPPort(protocol.Factory())
+ self.assertEquals(port, 0)
+
+ self.serverProtocol.passivePortRange = xrange(22032, 65536)
+ port = self.serverProtocol.getDTPPort(protocol.Factory())
+ self.assertEquals(port, 22035)
+
+ self.serverProtocol.passivePortRange = xrange(22032, 22035)
+ self.assertRaises(error.CannotListenError,
+ self.serverProtocol.getDTPPort,
+ protocol.Factory())
+
+
+ def test_portRangeInheritedFromFactory(self):
+ """
+ The L{FTP} instances created by L{ftp.FTPFactory.buildProtocol} have
+ their C{passivePortRange} attribute set to the same object the
+ factory's C{passivePortRange} attribute is set to.
+ """
+ portRange = xrange(2017, 2031)
+ self.factory.passivePortRange = portRange
+ protocol = self.factory.buildProtocol(None)
+ self.assertEqual(portRange, protocol.wrappedProtocol.passivePortRange)
+
+
+
+class FTPServerTestCaseAdvancedClient(FTPServerTestCase):
+ """
+ Test FTP server with the L{ftp.FTPClient} class.
+ """
+ clientFactory = ftp.FTPClient
+
+ def test_anonymousSTOR(self):
+ """
+ Try to make an STOR as anonymous, and check that we got a permission
+ denied error.
+ """
+ def eb(res):
+ res.trap(ftp.CommandFailed)
+ self.assertEquals(res.value.args[0][0],
+ '550 foo: Permission denied.')
+ d1, d2 = self.client.storeFile('foo')
+ d2.addErrback(eb)
+ return defer.gatherResults([d1, d2])
+
+
+class FTPServerPasvDataConnectionTestCase(FTPServerTestCase):
+ def _makeDataConnection(self, ignored=None):
+ # Establish a passive data connection (i.e. client connecting to
+ # server).
+ d = self.client.queueStringCommand('PASV')
+ def gotPASV(responseLines):
+ host, port = ftp.decodeHostPort(responseLines[-1][4:])
+ cc = protocol.ClientCreator(reactor, _BufferingProtocol)
+ return cc.connectTCP('127.0.0.1', port)
+ return d.addCallback(gotPASV)
+
+ def _download(self, command, chainDeferred=None):
+ if chainDeferred is None:
+ chainDeferred = defer.succeed(None)
+
+ chainDeferred.addCallback(self._makeDataConnection)
+ def queueCommand(downloader):
+ # wait for the command to return, and the download connection to be
+ # closed.
+ d1 = self.client.queueStringCommand(command)
+ d2 = downloader.d
+ return defer.gatherResults([d1, d2])
+ chainDeferred.addCallback(queueCommand)
+
+ def downloadDone((ignored, downloader)):
+ return downloader.buffer
+ return chainDeferred.addCallback(downloadDone)
+
+ def testEmptyLIST(self):
+ # Login
+ d = self._anonymousLogin()
+
+ # No files, so the file listing should be empty
+ self._download('LIST', chainDeferred=d)
+ def checkEmpty(result):
+ self.assertEqual('', result)
+ return d.addCallback(checkEmpty)
+
+ def testTwoDirLIST(self):
+ # Make some directories
+ os.mkdir(os.path.join(self.directory, 'foo'))
+ os.mkdir(os.path.join(self.directory, 'bar'))
+
+ # Login
+ d = self._anonymousLogin()
+
+ # We expect 2 lines because there are two files.
+ self._download('LIST', chainDeferred=d)
+ def checkDownload(download):
+ self.assertEqual(2, len(download[:-2].split('\r\n')))
+ d.addCallback(checkDownload)
+
+ # Download a names-only listing.
+ self._download('NLST ', chainDeferred=d)
+ def checkDownload(download):
+ filenames = download[:-2].split('\r\n')
+ filenames.sort()
+ self.assertEqual(['bar', 'foo'], filenames)
+ d.addCallback(checkDownload)
+
+ # Download a listing of the 'foo' subdirectory. 'foo' has no files, so
+ # the file listing should be empty.
+ self._download('LIST foo', chainDeferred=d)
+ def checkDownload(download):
+ self.assertEqual('', download)
+ d.addCallback(checkDownload)
+
+ # Change the current working directory to 'foo'.
+ def chdir(ignored):
+ return self.client.queueStringCommand('CWD foo')
+ d.addCallback(chdir)
+
+ # Download a listing from within 'foo', and again it should be empty,
+ # because LIST uses the working directory by default.
+ self._download('LIST', chainDeferred=d)
+ def checkDownload(download):
+ self.assertEqual('', download)
+ return d.addCallback(checkDownload)
+
+ def testManyLargeDownloads(self):
+ # Login
+ d = self._anonymousLogin()
+
+ # Download a range of different size files
+ for size in range(100000, 110000, 500):
+ fObj = file(os.path.join(self.directory, '%d.txt' % (size,)), 'wb')
+ fObj.write('x' * size)
+ fObj.close()
+
+ self._download('RETR %d.txt' % (size,), chainDeferred=d)
+ def checkDownload(download, size=size):
+ self.assertEqual('x' * size, download)
+ d.addCallback(checkDownload)
+ return d
+
+
+ def test_NLSTEmpty(self):
+ """
+ NLST with no argument returns the directory listing for the current
+ working directory.
+ """
+ # Login
+ d = self._anonymousLogin()
+
+ # Touch a file in the current working directory
+ self.dirPath.child('test.txt').touch()
+ # Make a directory in the current working directory
+ self.dirPath.child('foo').createDirectory()
+
+ self._download('NLST ', chainDeferred=d)
+ def checkDownload(download):
+ filenames = download[:-2].split('\r\n')
+ filenames.sort()
+ self.assertEquals(['foo', 'test.txt'], filenames)
+ return d.addCallback(checkDownload)
+
+
+ def test_NLSTNonexistent(self):
+ """
+ NLST on a non-existent file/directory returns nothing.
+ """
+ # Login
+ d = self._anonymousLogin()
+
+ self._download('NLST nonexistent.txt', chainDeferred=d)
+ def checkDownload(download):
+ self.assertEquals('', download)
+ return d.addCallback(checkDownload)
+
+
+ def test_NLSTOnPathToFile(self):
+ """
+ NLST on an existent file returns only the path to that file.
+ """
+ # Login
+ d = self._anonymousLogin()
+
+ # Touch a file in the current working directory
+ self.dirPath.child('test.txt').touch()
+
+ self._download('NLST test.txt', chainDeferred=d)
+ def checkDownload(download):
+ filenames = download[:-2].split('\r\n')
+ self.assertEquals(['test.txt'], filenames)
+ return d.addCallback(checkDownload)
+
+
+
+class FTPServerPortDataConnectionTestCase(FTPServerPasvDataConnectionTestCase):
+ def setUp(self):
+ self.dataPorts = []
+ return FTPServerPasvDataConnectionTestCase.setUp(self)
+
+ def _makeDataConnection(self, ignored=None):
+ # Establish an active data connection (i.e. server connecting to
+ # client).
+ deferred = defer.Deferred()
+ class DataFactory(protocol.ServerFactory):
+ protocol = _BufferingProtocol
+ def buildProtocol(self, addr):
+ p = protocol.ServerFactory.buildProtocol(self, addr)
+ reactor.callLater(0, deferred.callback, p)
+ return p
+ dataPort = reactor.listenTCP(0, DataFactory(), interface='127.0.0.1')
+ self.dataPorts.append(dataPort)
+ cmd = 'PORT ' + ftp.encodeHostPort('127.0.0.1', dataPort.getHost().port)
+ self.client.queueStringCommand(cmd)
+ return deferred
+
+ def tearDown(self):
+ l = [defer.maybeDeferred(port.stopListening) for port in self.dataPorts]
+ d = defer.maybeDeferred(
+ FTPServerPasvDataConnectionTestCase.tearDown, self)
+ l.append(d)
+ return defer.DeferredList(l, fireOnOneErrback=True)
+
+ def testPORTCannotConnect(self):
+ # Login
+ d = self._anonymousLogin()
+
+ # Listen on a port, and immediately stop listening as a way to find a
+ # port number that is definitely closed.
+ def loggedIn(ignored):
+ port = reactor.listenTCP(0, protocol.Factory(),
+ interface='127.0.0.1')
+ portNum = port.getHost().port
+ d = port.stopListening()
+ d.addCallback(lambda _: portNum)
+ return d
+ d.addCallback(loggedIn)
+
+ # Tell the server to connect to that port with a PORT command, and
+ # verify that it fails with the right error.
+ def gotPortNum(portNum):
+ return self.assertCommandFailed(
+ 'PORT ' + ftp.encodeHostPort('127.0.0.1', portNum),
+ ["425 Can't open data connection."])
+ return d.addCallback(gotPortNum)
+
+
+
+class DTPFactoryTests(unittest.TestCase):
+ """
+ Tests for L{ftp.DTPFactory}.
+ """
+ def setUp(self):
+ """
+ Create a fake protocol interpreter and a L{ftp.DTPFactory} instance to
+ test.
+ """
+ self.reactor = task.Clock()
+
+ class ProtocolInterpreter(object):
+ dtpInstance = None
+
+ self.protocolInterpreter = ProtocolInterpreter()
+ self.factory = ftp.DTPFactory(
+ self.protocolInterpreter, None, self.reactor)
+
+
+ def test_setTimeout(self):
+ """
+ L{ftp.DTPFactory.setTimeout} uses the reactor passed to its initializer
+ to set up a timed event to time out the DTP setup after the specified
+ number of seconds.
+ """
+ # Make sure the factory's deferred fails with the right exception, and
+ # make it so we can tell exactly when it fires.
+ finished = []
+ d = self.assertFailure(self.factory.deferred, ftp.PortConnectionError)
+ d.addCallback(finished.append)
+
+ self.factory.setTimeout(6)
+
+ # Advance the clock almost to the timeout
+ self.reactor.advance(5)
+
+ # Nothing should have happened yet.
+ self.assertFalse(finished)
+
+ # Advance it to the configured timeout.
+ self.reactor.advance(1)
+
+ # Now the Deferred should have failed with TimeoutError.
+ self.assertTrue(finished)
+
+ # There should also be no calls left in the reactor.
+ self.assertFalse(self.reactor.calls)
+
+
+ def test_buildProtocolOnce(self):
+ """
+ A L{ftp.DTPFactory} instance's C{buildProtocol} method can be used once
+ to create a L{ftp.DTP} instance.
+ """
+ protocol = self.factory.buildProtocol(None)
+ self.assertIsInstance(protocol, ftp.DTP)
+
+ # A subsequent call returns None.
+ self.assertIdentical(self.factory.buildProtocol(None), None)
+
+
+ def test_timeoutAfterConnection(self):
+ """
+ If a timeout has been set up using L{ftp.DTPFactory.setTimeout}, it is
+ cancelled by L{ftp.DTPFactory.buildProtocol}.
+ """
+ self.factory.setTimeout(10)
+ protocol = self.factory.buildProtocol(None)
+ # Make sure the call is no longer active.
+ self.assertFalse(self.reactor.calls)
+
+
+ def test_connectionAfterTimeout(self):
+ """
+ If L{ftp.DTPFactory.buildProtocol} is called after the timeout
+ specified by L{ftp.DTPFactory.setTimeout} has elapsed, C{None} is
+ returned.
+ """
+ # Handle the error so it doesn't get logged.
+ d = self.assertFailure(self.factory.deferred, ftp.PortConnectionError)
+
+ # Set up the timeout and then cause it to elapse so the Deferred does
+ # fail.
+ self.factory.setTimeout(10)
+ self.reactor.advance(10)
+
+ # Try to get a protocol - we should not be able to.
+ self.assertIdentical(self.factory.buildProtocol(None), None)
+
+ # Make sure the Deferred is doing the right thing.
+ return d
+
+
+ def test_timeoutAfterConnectionFailed(self):
+ """
+ L{ftp.DTPFactory.deferred} fails with L{PortConnectionError} when
+ L{ftp.DTPFactory.clientConnectionFailed} is called. If the timeout
+ specified with L{ftp.DTPFactory.setTimeout} expires after that, nothing
+ additional happens.
+ """
+ finished = []
+ d = self.assertFailure(self.factory.deferred, ftp.PortConnectionError)
+ d.addCallback(finished.append)
+
+ self.factory.setTimeout(10)
+ self.assertFalse(finished)
+ self.factory.clientConnectionFailed(None, None)
+ self.assertTrue(finished)
+ self.reactor.advance(10)
+ return d
+
+
+ def test_connectionFailedAfterTimeout(self):
+ """
+ If L{ftp.DTPFactory.clientConnectionFailed} is called after the timeout
+ specified by L{ftp.DTPFactory.setTimeout} has elapsed, nothing beyond
+ the normal timeout before happens.
+ """
+ # Handle the error so it doesn't get logged.
+ d = self.assertFailure(self.factory.deferred, ftp.PortConnectionError)
+
+ # Set up the timeout and then cause it to elapse so the Deferred does
+ # fail.
+ self.factory.setTimeout(10)
+ self.reactor.advance(10)
+
+ # Now fail the connection attempt. This should do nothing. In
+ # particular, it should not raise an exception.
+ self.factory.clientConnectionFailed(None, defer.TimeoutError("foo"))
+
+ # Give the Deferred to trial so it can make sure it did what we
+ # expected.
+ return d
+
+
+
+# -- Client Tests -----------------------------------------------------------
+
+class PrintLines(protocol.Protocol):
+ """Helper class used by FTPFileListingTests."""
+
+ def __init__(self, lines):
+ self._lines = lines
+
+ def connectionMade(self):
+ for line in self._lines:
+ self.transport.write(line + "\r\n")
+ self.transport.loseConnection()
+
+
+class MyFTPFileListProtocol(ftp.FTPFileListProtocol):
+ def __init__(self):
+ self.other = []
+ ftp.FTPFileListProtocol.__init__(self)
+
+ def unknownLine(self, line):
+ self.other.append(line)
+
+
+class FTPFileListingTests(unittest.TestCase):
+ def getFilesForLines(self, lines):
+ fileList = MyFTPFileListProtocol()
+ d = loopback.loopbackAsync(PrintLines(lines), fileList)
+ d.addCallback(lambda _: (fileList.files, fileList.other))
+ return d
+
+ def testOneLine(self):
+ # This example line taken from the docstring for FTPFileListProtocol
+ line = '-rw-r--r-- 1 root other 531 Jan 29 03:26 README'
+ def check(((file,), other)):
+ self.failIf(other, 'unexpect unparsable lines: %s' % repr(other))
+ self.failUnless(file['filetype'] == '-', 'misparsed fileitem')
+ self.failUnless(file['perms'] == 'rw-r--r--', 'misparsed perms')
+ self.failUnless(file['owner'] == 'root', 'misparsed fileitem')
+ self.failUnless(file['group'] == 'other', 'misparsed fileitem')
+ self.failUnless(file['size'] == 531, 'misparsed fileitem')
+ self.failUnless(file['date'] == 'Jan 29 03:26', 'misparsed fileitem')
+ self.failUnless(file['filename'] == 'README', 'misparsed fileitem')
+ self.failUnless(file['nlinks'] == 1, 'misparsed nlinks')
+ self.failIf(file['linktarget'], 'misparsed linktarget')
+ return self.getFilesForLines([line]).addCallback(check)
+
+ def testVariantLines(self):
+ line1 = 'drw-r--r-- 2 root other 531 Jan 9 2003 A'
+ line2 = 'lrw-r--r-- 1 root other 1 Jan 29 03:26 B -> A'
+ line3 = 'woohoo! '
+ def check(((file1, file2), (other,))):
+ self.failUnless(other == 'woohoo! \r', 'incorrect other line')
+ # file 1
+ self.failUnless(file1['filetype'] == 'd', 'misparsed fileitem')
+ self.failUnless(file1['perms'] == 'rw-r--r--', 'misparsed perms')
+ self.failUnless(file1['owner'] == 'root', 'misparsed owner')
+ self.failUnless(file1['group'] == 'other', 'misparsed group')
+ self.failUnless(file1['size'] == 531, 'misparsed size')
+ self.failUnless(file1['date'] == 'Jan 9 2003', 'misparsed date')
+ self.failUnless(file1['filename'] == 'A', 'misparsed filename')
+ self.failUnless(file1['nlinks'] == 2, 'misparsed nlinks')
+ self.failIf(file1['linktarget'], 'misparsed linktarget')
+ # file 2
+ self.failUnless(file2['filetype'] == 'l', 'misparsed fileitem')
+ self.failUnless(file2['perms'] == 'rw-r--r--', 'misparsed perms')
+ self.failUnless(file2['owner'] == 'root', 'misparsed owner')
+ self.failUnless(file2['group'] == 'other', 'misparsed group')
+ self.failUnless(file2['size'] == 1, 'misparsed size')
+ self.failUnless(file2['date'] == 'Jan 29 03:26', 'misparsed date')
+ self.failUnless(file2['filename'] == 'B', 'misparsed filename')
+ self.failUnless(file2['nlinks'] == 1, 'misparsed nlinks')
+ self.failUnless(file2['linktarget'] == 'A', 'misparsed linktarget')
+ return self.getFilesForLines([line1, line2, line3]).addCallback(check)
+
+ def testUnknownLine(self):
+ def check((files, others)):
+ self.failIf(files, 'unexpected file entries')
+ self.failUnless(others == ['ABC\r', 'not a file\r'],
+ 'incorrect unparsable lines: %s' % repr(others))
+ return self.getFilesForLines(['ABC', 'not a file']).addCallback(check)
+
+ def testYear(self):
+ # This example derived from bug description in issue 514.
+ fileList = ftp.FTPFileListProtocol()
+ exampleLine = (
+ '-rw-r--r-- 1 root other 531 Jan 29 2003 README\n')
+ class PrintLine(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.write(exampleLine)
+ self.transport.loseConnection()
+
+ def check(ignored):
+ file = fileList.files[0]
+ self.failUnless(file['size'] == 531, 'misparsed fileitem')
+ self.failUnless(file['date'] == 'Jan 29 2003', 'misparsed fileitem')
+ self.failUnless(file['filename'] == 'README', 'misparsed fileitem')
+
+ d = loopback.loopbackAsync(PrintLine(), fileList)
+ return d.addCallback(check)
+
+
+class FTPClientTests(unittest.TestCase):
+
+ def testFailedRETR(self):
+ f = protocol.Factory()
+ f.noisy = 0
+ port = reactor.listenTCP(0, f, interface="127.0.0.1")
+ self.addCleanup(port.stopListening)
+ portNum = port.getHost().port
+ # This test data derived from a bug report by ranty on #twisted
+ responses = ['220 ready, dude (vsFTPd 1.0.0: beat me, break me)',
+ # USER anonymous
+ '331 Please specify the password.',
+ # PASS twisted@twistedmatrix.com
+ '230 Login successful. Have fun.',
+ # TYPE I
+ '200 Binary it is, then.',
+ # PASV
+ '227 Entering Passive Mode (127,0,0,1,%d,%d)' %
+ (portNum >> 8, portNum & 0xff),
+ # RETR /file/that/doesnt/exist
+ '550 Failed to open file.']
+ f.buildProtocol = lambda addr: PrintLines(responses)
+
+ client = ftp.FTPClient(passive=1)
+ cc = protocol.ClientCreator(reactor, ftp.FTPClient, passive=1)
+ d = cc.connectTCP('127.0.0.1', portNum)
+ def gotClient(client):
+ p = protocol.Protocol()
+ return client.retrieveFile('/file/that/doesnt/exist', p)
+ d.addCallback(gotClient)
+ return self.assertFailure(d, ftp.CommandFailed)
+
+ def test_errbacksUponDisconnect(self):
+ """
+ Test the ftp command errbacks when a connection lost happens during
+ the operation.
+ """
+ ftpClient = ftp.FTPClient()
+ tr = proto_helpers.StringTransportWithDisconnection()
+ ftpClient.makeConnection(tr)
+ tr.protocol = ftpClient
+ d = ftpClient.list('some path', Dummy())
+ m = []
+ def _eb(failure):
+ m.append(failure)
+ return None
+ d.addErrback(_eb)
+ from twisted.internet.main import CONNECTION_LOST
+ ftpClient.connectionLost(failure.Failure(CONNECTION_LOST))
+ self.failUnless(m, m)
+ return d
+
+
+
+class FTPClientTestCase(unittest.TestCase):
+ """
+ Test advanced FTP client commands.
+ """
+ def setUp(self):
+ """
+ Create a FTP client and connect it to fake transport.
+ """
+ self.client = ftp.FTPClient()
+ self.transport = proto_helpers.StringTransportWithDisconnection()
+ self.client.makeConnection(self.transport)
+ self.transport.protocol = self.client
+
+
+ def tearDown(self):
+ """
+ Deliver disconnection notification to the client so that it can
+ perform any cleanup which may be required.
+ """
+ self.client.connectionLost(error.ConnectionLost())
+
+
+ def _testLogin(self):
+ """
+ Test the login part.
+ """
+ self.assertEquals(self.transport.value(), '')
+ self.client.lineReceived(
+ '331 Guest login ok, type your email address as password.')
+ self.assertEquals(self.transport.value(), 'USER anonymous\r\n')
+ self.transport.clear()
+ self.client.lineReceived(
+ '230 Anonymous login ok, access restrictions apply.')
+ self.assertEquals(self.transport.value(), 'TYPE I\r\n')
+ self.transport.clear()
+ self.client.lineReceived('200 Type set to I.')
+
+
+ def test_CDUP(self):
+ """
+ Test the CDUP command.
+
+ L{ftp.FTPClient.cdup} should return a Deferred which fires with a
+ sequence of one element which is the string the server sent
+ indicating that the command was executed successfully.
+
+ (XXX - This is a bad API)
+ """
+ def cbCdup(res):
+ self.assertEquals(res[0], '250 Requested File Action Completed OK')
+
+ self._testLogin()
+ d = self.client.cdup().addCallback(cbCdup)
+ self.assertEquals(self.transport.value(), 'CDUP\r\n')
+ self.transport.clear()
+ self.client.lineReceived('250 Requested File Action Completed OK')
+ return d
+
+
+ def test_failedCDUP(self):
+ """
+ Test L{ftp.FTPClient.cdup}'s handling of a failed CDUP command.
+
+ When the CDUP command fails, the returned Deferred should errback
+ with L{ftp.CommandFailed}.
+ """
+ self._testLogin()
+ d = self.client.cdup()
+ self.assertFailure(d, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'CDUP\r\n')
+ self.transport.clear()
+ self.client.lineReceived('550 ..: No such file or directory')
+ return d
+
+
+ def test_PWD(self):
+ """
+ Test the PWD command.
+
+ L{ftp.FTPClient.pwd} should return a Deferred which fires with a
+ sequence of one element which is a string representing the current
+ working directory on the server.
+
+ (XXX - This is a bad API)
+ """
+ def cbPwd(res):
+ self.assertEquals(ftp.parsePWDResponse(res[0]), "/bar/baz")
+
+ self._testLogin()
+ d = self.client.pwd().addCallback(cbPwd)
+ self.assertEquals(self.transport.value(), 'PWD\r\n')
+ self.client.lineReceived('257 "/bar/baz"')
+ return d
+
+
+ def test_failedPWD(self):
+ """
+ Test a failure in PWD command.
+
+ When the PWD command fails, the returned Deferred should errback
+ with L{ftp.CommandFailed}.
+ """
+ self._testLogin()
+ d = self.client.pwd()
+ self.assertFailure(d, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'PWD\r\n')
+ self.client.lineReceived('550 /bar/baz: No such file or directory')
+ return d
+
+
+ def test_CWD(self):
+ """
+ Test the CWD command.
+
+ L{ftp.FTPClient.cwd} should return a Deferred which fires with a
+ sequence of one element which is the string the server sent
+ indicating that the command was executed successfully.
+
+ (XXX - This is a bad API)
+ """
+ def cbCwd(res):
+ self.assertEquals(res[0], '250 Requested File Action Completed OK')
+
+ self._testLogin()
+ d = self.client.cwd("bar/foo").addCallback(cbCwd)
+ self.assertEquals(self.transport.value(), 'CWD bar/foo\r\n')
+ self.client.lineReceived('250 Requested File Action Completed OK')
+ return d
+
+
+ def test_failedCWD(self):
+ """
+ Test a failure in CWD command.
+
+ When the PWD command fails, the returned Deferred should errback
+ with L{ftp.CommandFailed}.
+ """
+ self._testLogin()
+ d = self.client.cwd("bar/foo")
+ self.assertFailure(d, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'CWD bar/foo\r\n')
+ self.client.lineReceived('550 bar/foo: No such file or directory')
+ return d
+
+
+ def test_passiveRETR(self):
+ """
+ Test the RETR command in passive mode: get a file and verify its
+ content.
+
+ L{ftp.FTPClient.retrieveFile} should return a Deferred which fires
+ with the protocol instance passed to it after the download has
+ completed.
+
+ (XXX - This API should be based on producers and consumers)
+ """
+ def cbRetr(res, proto):
+ self.assertEquals(proto.buffer, 'x' * 1000)
+
+ def cbConnect(host, port, factory):
+ self.assertEquals(host, '127.0.0.1')
+ self.assertEquals(port, 12345)
+ proto = factory.buildProtocol((host, port))
+ proto.makeConnection(proto_helpers.StringTransport())
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ proto.dataReceived("x" * 1000)
+ proto.connectionLost(failure.Failure(error.ConnectionDone("")))
+
+ self.client.connectFactory = cbConnect
+ self._testLogin()
+ proto = _BufferingProtocol()
+ d = self.client.retrieveFile("spam", proto)
+ d.addCallback(cbRetr, proto)
+ self.assertEquals(self.transport.value(), 'PASV\r\n')
+ self.transport.clear()
+ self.client.lineReceived('227 Entering Passive Mode (%s).' %
+ (ftp.encodeHostPort('127.0.0.1', 12345),))
+ self.assertEquals(self.transport.value(), 'RETR spam\r\n')
+ self.transport.clear()
+ self.client.lineReceived('226 Transfer Complete.')
+ return d
+
+
+ def test_RETR(self):
+ """
+ Test the RETR command in non-passive mode.
+
+ Like L{test_passiveRETR} but in the configuration where the server
+ establishes the data connection to the client, rather than the other
+ way around.
+ """
+ self.client.passive = False
+
+ def generatePort(portCmd):
+ portCmd.text = 'PORT %s' % (ftp.encodeHostPort('127.0.0.1', 9876),)
+ portCmd.protocol.makeConnection(proto_helpers.StringTransport())
+ portCmd.protocol.dataReceived("x" * 1000)
+ portCmd.protocol.connectionLost(
+ failure.Failure(error.ConnectionDone("")))
+
+ def cbRetr(res, proto):
+ self.assertEquals(proto.buffer, 'x' * 1000)
+
+ self.client.generatePortCommand = generatePort
+ self._testLogin()
+ proto = _BufferingProtocol()
+ d = self.client.retrieveFile("spam", proto)
+ d.addCallback(cbRetr, proto)
+ self.assertEquals(self.transport.value(), 'PORT %s\r\n' %
+ (ftp.encodeHostPort('127.0.0.1', 9876),))
+ self.transport.clear()
+ self.client.lineReceived('200 PORT OK')
+ self.assertEquals(self.transport.value(), 'RETR spam\r\n')
+ self.transport.clear()
+ self.client.lineReceived('226 Transfer Complete.')
+ return d
+
+
+ def test_failedRETR(self):
+ """
+ Try to RETR an unexisting file.
+
+ L{ftp.FTPClient.retrieveFile} should return a Deferred which
+ errbacks with L{ftp.CommandFailed} if the server indicates the file
+ cannot be transferred for some reason.
+ """
+ def cbConnect(host, port, factory):
+ self.assertEquals(host, '127.0.0.1')
+ self.assertEquals(port, 12345)
+ proto = factory.buildProtocol((host, port))
+ proto.makeConnection(proto_helpers.StringTransport())
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ proto.connectionLost(failure.Failure(error.ConnectionDone("")))
+
+ self.client.connectFactory = cbConnect
+ self._testLogin()
+ proto = _BufferingProtocol()
+ d = self.client.retrieveFile("spam", proto)
+ self.assertFailure(d, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'PASV\r\n')
+ self.transport.clear()
+ self.client.lineReceived('227 Entering Passive Mode (%s).' %
+ (ftp.encodeHostPort('127.0.0.1', 12345),))
+ self.assertEquals(self.transport.value(), 'RETR spam\r\n')
+ self.transport.clear()
+ self.client.lineReceived('550 spam: No such file or directory')
+ return d
+
+
+ def test_lostRETR(self):
+ """
+ Try a RETR, but disconnect during the transfer.
+ L{ftp.FTPClient.retrieveFile} should return a Deferred which
+ errbacks with L{ftp.ConnectionLost)
+ """
+ self.client.passive = False
+
+ l = []
+ def generatePort(portCmd):
+ portCmd.text = 'PORT %s' % (ftp.encodeHostPort('127.0.0.1', 9876),)
+ tr = proto_helpers.StringTransportWithDisconnection()
+ portCmd.protocol.makeConnection(tr)
+ tr.protocol = portCmd.protocol
+ portCmd.protocol.dataReceived("x" * 500)
+ l.append(tr)
+
+ self.client.generatePortCommand = generatePort
+ self._testLogin()
+ proto = _BufferingProtocol()
+ d = self.client.retrieveFile("spam", proto)
+ self.assertEquals(self.transport.value(), 'PORT %s\r\n' %
+ (ftp.encodeHostPort('127.0.0.1', 9876),))
+ self.transport.clear()
+ self.client.lineReceived('200 PORT OK')
+ self.assertEquals(self.transport.value(), 'RETR spam\r\n')
+
+ self.assert_(l)
+ l[0].loseConnection()
+ self.transport.loseConnection()
+ self.assertFailure(d, ftp.ConnectionLost)
+ return d
+
+
+ def test_passiveSTOR(self):
+ """
+ Test the STOR command: send a file and verify its content.
+
+ L{ftp.FTPClient.storeFile} should return a two-tuple of Deferreds.
+ The first of which should fire with a protocol instance when the
+ data connection has been established and is responsible for sending
+ the contents of the file. The second of which should fire when the
+ upload has completed, the data connection has been closed, and the
+ server has acknowledged receipt of the file.
+
+ (XXX - storeFile should take a producer as an argument, instead, and
+ only return a Deferred which fires when the upload has succeeded or
+ failed).
+ """
+ tr = proto_helpers.StringTransport()
+ def cbStore(sender):
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ sender.transport.write("x" * 1000)
+ sender.finish()
+ sender.connectionLost(failure.Failure(error.ConnectionDone("")))
+
+ def cbFinish(ign):
+ self.assertEquals(tr.value(), "x" * 1000)
+
+ def cbConnect(host, port, factory):
+ self.assertEquals(host, '127.0.0.1')
+ self.assertEquals(port, 12345)
+ proto = factory.buildProtocol((host, port))
+ proto.makeConnection(tr)
+
+ self.client.connectFactory = cbConnect
+ self._testLogin()
+ d1, d2 = self.client.storeFile("spam")
+ d1.addCallback(cbStore)
+ d2.addCallback(cbFinish)
+ self.assertEquals(self.transport.value(), 'PASV\r\n')
+ self.transport.clear()
+ self.client.lineReceived('227 Entering Passive Mode (%s).' %
+ (ftp.encodeHostPort('127.0.0.1', 12345),))
+ self.assertEquals(self.transport.value(), 'STOR spam\r\n')
+ self.transport.clear()
+ self.client.lineReceived('226 Transfer Complete.')
+ return defer.gatherResults([d1, d2])
+
+
+ def test_failedSTOR(self):
+ """
+ Test a failure in the STOR command.
+
+ If the server does not acknowledge successful receipt of the
+ uploaded file, the second Deferred returned by
+ L{ftp.FTPClient.storeFile} should errback with L{ftp.CommandFailed}.
+ """
+ tr = proto_helpers.StringTransport()
+ def cbStore(sender):
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ sender.transport.write("x" * 1000)
+ sender.finish()
+ sender.connectionLost(failure.Failure(error.ConnectionDone("")))
+
+ def cbConnect(host, port, factory):
+ self.assertEquals(host, '127.0.0.1')
+ self.assertEquals(port, 12345)
+ proto = factory.buildProtocol((host, port))
+ proto.makeConnection(tr)
+
+ self.client.connectFactory = cbConnect
+ self._testLogin()
+ d1, d2 = self.client.storeFile("spam")
+ d1.addCallback(cbStore)
+ self.assertFailure(d2, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'PASV\r\n')
+ self.transport.clear()
+ self.client.lineReceived('227 Entering Passive Mode (%s).' %
+ (ftp.encodeHostPort('127.0.0.1', 12345),))
+ self.assertEquals(self.transport.value(), 'STOR spam\r\n')
+ self.transport.clear()
+ self.client.lineReceived(
+ '426 Transfer aborted. Data connection closed.')
+ return defer.gatherResults([d1, d2])
+
+
+ def test_STOR(self):
+ """
+ Test the STOR command in non-passive mode.
+
+ Like L{test_passiveSTOR} but in the configuration where the server
+ establishes the data connection to the client, rather than the other
+ way around.
+ """
+ tr = proto_helpers.StringTransport()
+ self.client.passive = False
+ def generatePort(portCmd):
+ portCmd.text = 'PORT %s' % ftp.encodeHostPort('127.0.0.1', 9876)
+ portCmd.protocol.makeConnection(tr)
+
+ def cbStore(sender):
+ self.assertEquals(self.transport.value(), 'PORT %s\r\n' %
+ (ftp.encodeHostPort('127.0.0.1', 9876),))
+ self.transport.clear()
+ self.client.lineReceived('200 PORT OK')
+ self.assertEquals(self.transport.value(), 'STOR spam\r\n')
+ self.transport.clear()
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ sender.transport.write("x" * 1000)
+ sender.finish()
+ sender.connectionLost(failure.Failure(error.ConnectionDone("")))
+ self.client.lineReceived('226 Transfer Complete.')
+
+ def cbFinish(ign):
+ self.assertEquals(tr.value(), "x" * 1000)
+
+ self.client.generatePortCommand = generatePort
+ self._testLogin()
+ d1, d2 = self.client.storeFile("spam")
+ d1.addCallback(cbStore)
+ d2.addCallback(cbFinish)
+ return defer.gatherResults([d1, d2])
+
+
+ def test_passiveLIST(self):
+ """
+ Test the LIST command.
+
+ L{ftp.FTPClient.list} should return a Deferred which fires with a
+ protocol instance which was passed to list after the command has
+ succeeded.
+
+ (XXX - This is a very unfortunate API; if my understanding is
+ correct, the results are always at least line-oriented, so allowing
+ a per-line parser function to be specified would make this simpler,
+ but a default implementation should really be provided which knows
+ how to deal with all the formats used in real servers, so
+ application developers never have to care about this insanity. It
+ would also be nice to either get back a Deferred of a list of
+ filenames or to be able to consume the files as they are received
+ (which the current API does allow, but in a somewhat inconvenient
+ fashion) -exarkun)
+ """
+ def cbList(res, fileList):
+ fls = [f["filename"] for f in fileList.files]
+ expected = ["foo", "bar", "baz"]
+ expected.sort()
+ fls.sort()
+ self.assertEquals(fls, expected)
+
+ def cbConnect(host, port, factory):
+ self.assertEquals(host, '127.0.0.1')
+ self.assertEquals(port, 12345)
+ proto = factory.buildProtocol((host, port))
+ proto.makeConnection(proto_helpers.StringTransport())
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ sending = [
+ '-rw-r--r-- 0 spam egg 100 Oct 10 2006 foo\r\n',
+ '-rw-r--r-- 3 spam egg 100 Oct 10 2006 bar\r\n',
+ '-rw-r--r-- 4 spam egg 100 Oct 10 2006 baz\r\n',
+ ]
+ for i in sending:
+ proto.dataReceived(i)
+ proto.connectionLost(failure.Failure(error.ConnectionDone("")))
+
+ self.client.connectFactory = cbConnect
+ self._testLogin()
+ fileList = ftp.FTPFileListProtocol()
+ d = self.client.list('foo/bar', fileList).addCallback(cbList, fileList)
+ self.assertEquals(self.transport.value(), 'PASV\r\n')
+ self.transport.clear()
+ self.client.lineReceived('227 Entering Passive Mode (%s).' %
+ (ftp.encodeHostPort('127.0.0.1', 12345),))
+ self.assertEquals(self.transport.value(), 'LIST foo/bar\r\n')
+ self.client.lineReceived('226 Transfer Complete.')
+ return d
+
+
+ def test_LIST(self):
+ """
+ Test the LIST command in non-passive mode.
+
+ Like L{test_passiveLIST} but in the configuration where the server
+ establishes the data connection to the client, rather than the other
+ way around.
+ """
+ self.client.passive = False
+ def generatePort(portCmd):
+ portCmd.text = 'PORT %s' % (ftp.encodeHostPort('127.0.0.1', 9876),)
+ portCmd.protocol.makeConnection(proto_helpers.StringTransport())
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ sending = [
+ '-rw-r--r-- 0 spam egg 100 Oct 10 2006 foo\r\n',
+ '-rw-r--r-- 3 spam egg 100 Oct 10 2006 bar\r\n',
+ '-rw-r--r-- 4 spam egg 100 Oct 10 2006 baz\r\n',
+ ]
+ for i in sending:
+ portCmd.protocol.dataReceived(i)
+ portCmd.protocol.connectionLost(
+ failure.Failure(error.ConnectionDone("")))
+
+ def cbList(res, fileList):
+ fls = [f["filename"] for f in fileList.files]
+ expected = ["foo", "bar", "baz"]
+ expected.sort()
+ fls.sort()
+ self.assertEquals(fls, expected)
+
+ self.client.generatePortCommand = generatePort
+ self._testLogin()
+ fileList = ftp.FTPFileListProtocol()
+ d = self.client.list('foo/bar', fileList).addCallback(cbList, fileList)
+ self.assertEquals(self.transport.value(), 'PORT %s\r\n' %
+ (ftp.encodeHostPort('127.0.0.1', 9876),))
+ self.transport.clear()
+ self.client.lineReceived('200 PORT OK')
+ self.assertEquals(self.transport.value(), 'LIST foo/bar\r\n')
+ self.transport.clear()
+ self.client.lineReceived('226 Transfer Complete.')
+ return d
+
+
+ def test_failedLIST(self):
+ """
+ Test a failure in LIST command.
+
+ L{ftp.FTPClient.list} should return a Deferred which fails with
+ L{ftp.CommandFailed} if the server indicates the indicated path is
+ invalid for some reason.
+ """
+ def cbConnect(host, port, factory):
+ self.assertEquals(host, '127.0.0.1')
+ self.assertEquals(port, 12345)
+ proto = factory.buildProtocol((host, port))
+ proto.makeConnection(proto_helpers.StringTransport())
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ proto.connectionLost(failure.Failure(error.ConnectionDone("")))
+
+ self.client.connectFactory = cbConnect
+ self._testLogin()
+ fileList = ftp.FTPFileListProtocol()
+ d = self.client.list('foo/bar', fileList)
+ self.assertFailure(d, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'PASV\r\n')
+ self.transport.clear()
+ self.client.lineReceived('227 Entering Passive Mode (%s).' %
+ (ftp.encodeHostPort('127.0.0.1', 12345),))
+ self.assertEquals(self.transport.value(), 'LIST foo/bar\r\n')
+ self.client.lineReceived('550 foo/bar: No such file or directory')
+ return d
+
+
+ def test_NLST(self):
+ """
+ Test the NLST command in non-passive mode.
+
+ L{ftp.FTPClient.nlst} should return a Deferred which fires with a
+ list of filenames when the list command has completed.
+ """
+ self.client.passive = False
+ def generatePort(portCmd):
+ portCmd.text = 'PORT %s' % (ftp.encodeHostPort('127.0.0.1', 9876),)
+ portCmd.protocol.makeConnection(proto_helpers.StringTransport())
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ portCmd.protocol.dataReceived('foo\r\n')
+ portCmd.protocol.dataReceived('bar\r\n')
+ portCmd.protocol.dataReceived('baz\r\n')
+ portCmd.protocol.connectionLost(
+ failure.Failure(error.ConnectionDone("")))
+
+ def cbList(res, proto):
+ fls = proto.buffer.splitlines()
+ expected = ["foo", "bar", "baz"]
+ expected.sort()
+ fls.sort()
+ self.assertEquals(fls, expected)
+
+ self.client.generatePortCommand = generatePort
+ self._testLogin()
+ lstproto = _BufferingProtocol()
+ d = self.client.nlst('foo/bar', lstproto).addCallback(cbList, lstproto)
+ self.assertEquals(self.transport.value(), 'PORT %s\r\n' %
+ (ftp.encodeHostPort('127.0.0.1', 9876),))
+ self.transport.clear()
+ self.client.lineReceived('200 PORT OK')
+ self.assertEquals(self.transport.value(), 'NLST foo/bar\r\n')
+ self.client.lineReceived('226 Transfer Complete.')
+ return d
+
+
+ def test_passiveNLST(self):
+ """
+ Test the NLST command.
+
+ Like L{test_passiveNLST} but in the configuration where the server
+ establishes the data connection to the client, rather than the other
+ way around.
+ """
+ def cbList(res, proto):
+ fls = proto.buffer.splitlines()
+ expected = ["foo", "bar", "baz"]
+ expected.sort()
+ fls.sort()
+ self.assertEquals(fls, expected)
+
+ def cbConnect(host, port, factory):
+ self.assertEquals(host, '127.0.0.1')
+ self.assertEquals(port, 12345)
+ proto = factory.buildProtocol((host, port))
+ proto.makeConnection(proto_helpers.StringTransport())
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ proto.dataReceived('foo\r\n')
+ proto.dataReceived('bar\r\n')
+ proto.dataReceived('baz\r\n')
+ proto.connectionLost(failure.Failure(error.ConnectionDone("")))
+
+ self.client.connectFactory = cbConnect
+ self._testLogin()
+ lstproto = _BufferingProtocol()
+ d = self.client.nlst('foo/bar', lstproto).addCallback(cbList, lstproto)
+ self.assertEquals(self.transport.value(), 'PASV\r\n')
+ self.transport.clear()
+ self.client.lineReceived('227 Entering Passive Mode (%s).' %
+ (ftp.encodeHostPort('127.0.0.1', 12345),))
+ self.assertEquals(self.transport.value(), 'NLST foo/bar\r\n')
+ self.client.lineReceived('226 Transfer Complete.')
+ return d
+
+
+ def test_failedNLST(self):
+ """
+ Test a failure in NLST command.
+
+ L{ftp.FTPClient.nlst} should return a Deferred which fails with
+ L{ftp.CommandFailed} if the server indicates the indicated path is
+ invalid for some reason.
+ """
+ tr = proto_helpers.StringTransport()
+ def cbConnect(host, port, factory):
+ self.assertEquals(host, '127.0.0.1')
+ self.assertEquals(port, 12345)
+ proto = factory.buildProtocol((host, port))
+ proto.makeConnection(tr)
+ self.client.lineReceived(
+ '150 File status okay; about to open data connection.')
+ proto.connectionLost(failure.Failure(error.ConnectionDone("")))
+
+ self.client.connectFactory = cbConnect
+ self._testLogin()
+ lstproto = _BufferingProtocol()
+ d = self.client.nlst('foo/bar', lstproto)
+ self.assertFailure(d, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'PASV\r\n')
+ self.transport.clear()
+ self.client.lineReceived('227 Entering Passive Mode (%s).' %
+ (ftp.encodeHostPort('127.0.0.1', 12345),))
+ self.assertEquals(self.transport.value(), 'NLST foo/bar\r\n')
+ self.client.lineReceived('550 foo/bar: No such file or directory')
+ return d
+
+
+ def test_changeDirectoryDeprecated(self):
+ """
+ L{ftp.FTPClient.changeDirectory} is deprecated and the direct caller of
+ it is warned of this.
+ """
+ self._testLogin()
+ d = self.assertWarns(
+ DeprecationWarning,
+ "FTPClient.changeDirectory is deprecated in Twisted 8.2 and "
+ "newer. Use FTPClient.cwd instead.",
+ __file__,
+ lambda: self.client.changeDirectory('.'))
+ # This is necessary to make the Deferred fire. The Deferred needs
+ # to fire so that tearDown doesn't cause it to errback and fail this
+ # or (more likely) a later test.
+ self.client.lineReceived('250 success')
+ return d
+
+
+ def test_changeDirectory(self):
+ """
+ Test the changeDirectory method.
+
+ L{ftp.FTPClient.changeDirectory} should return a Deferred which fires
+ with True if succeeded.
+ """
+ def cbCd(res):
+ self.assertEquals(res, True)
+
+ self._testLogin()
+ d = self.client.changeDirectory("bar/foo").addCallback(cbCd)
+ self.assertEquals(self.transport.value(), 'CWD bar/foo\r\n')
+ self.client.lineReceived('250 Requested File Action Completed OK')
+ return d
+ test_changeDirectory.suppress = [_changeDirectorySuppression]
+
+
+ def test_failedChangeDirectory(self):
+ """
+ Test a failure in the changeDirectory method.
+
+ The behaviour here is the same as a failed CWD.
+ """
+ self._testLogin()
+ d = self.client.changeDirectory("bar/foo")
+ self.assertFailure(d, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'CWD bar/foo\r\n')
+ self.client.lineReceived('550 bar/foo: No such file or directory')
+ return d
+ test_failedChangeDirectory.suppress = [_changeDirectorySuppression]
+
+
+ def test_strangeFailedChangeDirectory(self):
+ """
+ Test a strange failure in changeDirectory method.
+
+ L{ftp.FTPClient.changeDirectory} is stricter than CWD as it checks
+ code 250 for success.
+ """
+ self._testLogin()
+ d = self.client.changeDirectory("bar/foo")
+ self.assertFailure(d, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'CWD bar/foo\r\n')
+ self.client.lineReceived('252 I do what I want !')
+ return d
+ test_strangeFailedChangeDirectory.suppress = [_changeDirectorySuppression]
+
+
+ def test_renameFromTo(self):
+ """
+ L{ftp.FTPClient.rename} issues I{RNTO} and I{RNFR} commands and returns
+ a L{Deferred} which fires when a file has successfully been renamed.
+ """
+ self._testLogin()
+
+ d = self.client.rename("/spam", "/ham")
+ self.assertEqual(self.transport.value(), 'RNFR /spam\r\n')
+ self.transport.clear()
+
+ fromResponse = (
+ '350 Requested file action pending further information.\r\n')
+ self.client.lineReceived(fromResponse)
+ self.assertEqual(self.transport.value(), 'RNTO /ham\r\n')
+ toResponse = (
+ '250 Requested File Action Completed OK')
+ self.client.lineReceived(toResponse)
+
+ d.addCallback(self.assertEqual, ([fromResponse], [toResponse]))
+ return d
+
+
+ def test_renameFromToEscapesPaths(self):
+ """
+ L{ftp.FTPClient.rename} issues I{RNTO} and I{RNFR} commands with paths
+ escaped according to U{http://cr.yp.to/ftp/filesystem.html}.
+ """
+ self._testLogin()
+
+ fromFile = "/foo/ba\nr/baz"
+ toFile = "/qu\nux"
+ self.client.rename(fromFile, toFile)
+ self.client.lineReceived("350 ")
+ self.client.lineReceived("250 ")
+ self.assertEqual(
+ self.transport.value(),
+ "RNFR /foo/ba\x00r/baz\r\n"
+ "RNTO /qu\x00ux\r\n")
+
+
+ def test_renameFromToFailingOnFirstError(self):
+ """
+ The L{Deferred} returned by L{ftp.FTPClient.rename} is errbacked with
+ L{CommandFailed} if the I{RNFR} command receives an error response code
+ (for example, because the file does not exist).
+ """
+ self._testLogin()
+
+ d = self.client.rename("/spam", "/ham")
+ self.assertEqual(self.transport.value(), 'RNFR /spam\r\n')
+ self.transport.clear()
+
+ self.client.lineReceived('550 Requested file unavailable.\r\n')
+ # The RNTO should not execute since the RNFR failed.
+ self.assertEqual(self.transport.value(), '')
+
+ return self.assertFailure(d, ftp.CommandFailed)
+
+
+ def test_renameFromToFailingOnRenameTo(self):
+ """
+ The L{Deferred} returned by L{ftp.FTPClient.rename} is errbacked with
+ L{CommandFailed} if the I{RNTO} command receives an error response code
+ (for example, because the destination directory does not exist).
+ """
+ self._testLogin()
+
+ d = self.client.rename("/spam", "/ham")
+ self.assertEqual(self.transport.value(), 'RNFR /spam\r\n')
+ self.transport.clear()
+
+ self.client.lineReceived('350 Requested file action pending further information.\r\n')
+ self.assertEqual(self.transport.value(), 'RNTO /ham\r\n')
+ self.client.lineReceived('550 Requested file unavailable.\r\n')
+ return self.assertFailure(d, ftp.CommandFailed)
+
+
+ def test_makeDirectory(self):
+ """
+ L{ftp.FTPClient.makeDirectory} issues a I{MKD} command and returns a
+ L{Deferred} which is called back with the server's response if the
+ directory is created.
+ """
+ self._testLogin()
+
+ d = self.client.makeDirectory("/spam")
+ self.assertEquals(self.transport.value(), 'MKD /spam\r\n')
+ self.client.lineReceived('257 "/spam" created.')
+ return d.addCallback(self.assertEqual, ['257 "/spam" created.'])
+
+
+ def test_makeDirectoryPathEscape(self):
+ """
+ L{ftp.FTPClient.makeDirectory} escapes the path name it sends according
+ to U{http://cr.yp.to/ftp/filesystem.html}.
+ """
+ self._testLogin()
+ d = self.client.makeDirectory("/sp\nam")
+ self.assertEqual(self.transport.value(), 'MKD /sp\x00am\r\n')
+ # This is necessary to make the Deferred fire. The Deferred needs
+ # to fire so that tearDown doesn't cause it to errback and fail this
+ # or (more likely) a later test.
+ self.client.lineReceived('257 win')
+ return d
+
+
+ def test_failedMakeDirectory(self):
+ """
+ L{ftp.FTPClient.makeDirectory} returns a L{Deferred} which is errbacked
+ with L{CommandFailed} if the server returns an error response code.
+ """
+ self._testLogin()
+
+ d = self.client.makeDirectory("/spam")
+ self.assertEquals(self.transport.value(), 'MKD /spam\r\n')
+ self.client.lineReceived('550 PERMISSION DENIED')
+ return self.assertFailure(d, ftp.CommandFailed)
+
+
+ def test_getDirectory(self):
+ """
+ Test the getDirectory method.
+
+ L{ftp.FTPClient.getDirectory} should return a Deferred which fires with
+ the current directory on the server. It wraps PWD command.
+ """
+ def cbGet(res):
+ self.assertEquals(res, "/bar/baz")
+
+ self._testLogin()
+ d = self.client.getDirectory().addCallback(cbGet)
+ self.assertEquals(self.transport.value(), 'PWD\r\n')
+ self.client.lineReceived('257 "/bar/baz"')
+ return d
+
+
+ def test_failedGetDirectory(self):
+ """
+ Test a failure in getDirectory method.
+
+ The behaviour should be the same as PWD.
+ """
+ self._testLogin()
+ d = self.client.getDirectory()
+ self.assertFailure(d, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'PWD\r\n')
+ self.client.lineReceived('550 /bar/baz: No such file or directory')
+ return d
+
+
+ def test_anotherFailedGetDirectory(self):
+ """
+ Test a different failure in getDirectory method.
+
+ The response should be quoted to be parsed, so it returns an error
+ otherwise.
+ """
+ self._testLogin()
+ d = self.client.getDirectory()
+ self.assertFailure(d, ftp.CommandFailed)
+ self.assertEquals(self.transport.value(), 'PWD\r\n')
+ self.client.lineReceived('257 /bar/baz')
+ return d
+
+
+ def test_removeFile(self):
+ """
+ L{ftp.FTPClient.removeFile} sends a I{DELE} command to the server for
+ the indicated file and returns a Deferred which fires after the server
+ sends a 250 response code.
+ """
+ self._testLogin()
+ d = self.client.removeFile("/tmp/test")
+ self.assertEquals(self.transport.value(), 'DELE /tmp/test\r\n')
+ response = '250 Requested file action okay, completed.'
+ self.client.lineReceived(response)
+ return d.addCallback(self.assertEqual, [response])
+
+
+ def test_failedRemoveFile(self):
+ """
+ If the server returns a response code other than 250 in response to a
+ I{DELE} sent by L{ftp.FTPClient.removeFile}, the L{Deferred} returned
+ by C{removeFile} is errbacked with a L{Failure} wrapping a
+ L{CommandFailed}.
+ """
+ self._testLogin()
+ d = self.client.removeFile("/tmp/test")
+ self.assertEquals(self.transport.value(), 'DELE /tmp/test\r\n')
+ response = '501 Syntax error in parameters or arguments.'
+ self.client.lineReceived(response)
+ d = self.assertFailure(d, ftp.CommandFailed)
+ d.addCallback(lambda exc: self.assertEqual(exc.args, ([response],)))
+ return d
+
+
+ def test_unparsableRemoveFileResponse(self):
+ """
+ If the server returns a response line which cannot be parsed, the
+ L{Deferred} returned by L{ftp.FTPClient.removeFile} is errbacked with a
+ L{BadResponse} containing the response.
+ """
+ self._testLogin()
+ d = self.client.removeFile("/tmp/test")
+ response = '765 blah blah blah'
+ self.client.lineReceived(response)
+ d = self.assertFailure(d, ftp.BadResponse)
+ d.addCallback(lambda exc: self.assertEqual(exc.args, ([response],)))
+ return d
+
+
+ def test_multilineRemoveFileResponse(self):
+ """
+ If the server returns multiple response lines, the L{Deferred} returned
+ by L{ftp.FTPClient.removeFile} is still fired with a true value if the
+ ultimate response code is 250.
+ """
+ self._testLogin()
+ d = self.client.removeFile("/tmp/test")
+ response = ['250-perhaps a progress report',
+ '250 okay']
+ map(self.client.lineReceived, response)
+ return d.addCallback(self.assertTrue)
+
+
+
+class FTPClientBasicTests(unittest.TestCase):
+
+ def testGreeting(self):
+ # The first response is captured as a greeting.
+ ftpClient = ftp.FTPClientBasic()
+ ftpClient.lineReceived('220 Imaginary FTP.')
+ self.failUnlessEqual(['220 Imaginary FTP.'], ftpClient.greeting)
+
+ def testResponseWithNoMessage(self):
+ # Responses with no message are still valid, i.e. three digits followed
+ # by a space is complete response.
+ ftpClient = ftp.FTPClientBasic()
+ ftpClient.lineReceived('220 ')
+ self.failUnlessEqual(['220 '], ftpClient.greeting)
+
+ def testMultilineResponse(self):
+ ftpClient = ftp.FTPClientBasic()
+ ftpClient.transport = proto_helpers.StringTransport()
+ ftpClient.lineReceived('220 Imaginary FTP.')
+
+ # Queue (and send) a dummy command, and set up a callback to capture the
+ # result
+ deferred = ftpClient.queueStringCommand('BLAH')
+ result = []
+ deferred.addCallback(result.append)
+ deferred.addErrback(self.fail)
+
+ # Send the first line of a multiline response.
+ ftpClient.lineReceived('210-First line.')
+ self.failUnlessEqual([], result)
+
+ # Send a second line, again prefixed with "nnn-".
+ ftpClient.lineReceived('123-Second line.')
+ self.failUnlessEqual([], result)
+
+ # Send a plain line of text, no prefix.
+ ftpClient.lineReceived('Just some text.')
+ self.failUnlessEqual([], result)
+
+ # Now send a short (less than 4 chars) line.
+ ftpClient.lineReceived('Hi')
+ self.failUnlessEqual([], result)
+
+ # Now send an empty line.
+ ftpClient.lineReceived('')
+ self.failUnlessEqual([], result)
+
+ # And a line with 3 digits in it, and nothing else.
+ ftpClient.lineReceived('321')
+ self.failUnlessEqual([], result)
+
+ # Now finish it.
+ ftpClient.lineReceived('210 Done.')
+ self.failUnlessEqual(
+ ['210-First line.',
+ '123-Second line.',
+ 'Just some text.',
+ 'Hi',
+ '',
+ '321',
+ '210 Done.'], result[0])
+
+
+ def test_noPasswordGiven(self):
+ """
+ Passing None as the password avoids sending the PASS command.
+ """
+ # Create a client, and give it a greeting.
+ ftpClient = ftp.FTPClientBasic()
+ ftpClient.transport = proto_helpers.StringTransport()
+ ftpClient.lineReceived('220 Welcome to Imaginary FTP.')
+
+ # Queue a login with no password
+ ftpClient.queueLogin('bob', None)
+ self.assertEquals('USER bob\r\n', ftpClient.transport.value())
+
+ # Clear the test buffer, acknowledge the USER command.
+ ftpClient.transport.clear()
+ ftpClient.lineReceived('200 Hello bob.')
+
+ # The client shouldn't have sent anything more (i.e. it shouldn't have
+ # sent a PASS command).
+ self.assertEquals('', ftpClient.transport.value())
+
+
+ def test_noPasswordNeeded(self):
+ """
+ Receiving a 230 response to USER prevents PASS from being sent.
+ """
+ # Create a client, and give it a greeting.
+ ftpClient = ftp.FTPClientBasic()
+ ftpClient.transport = proto_helpers.StringTransport()
+ ftpClient.lineReceived('220 Welcome to Imaginary FTP.')
+
+ # Queue a login with no password
+ ftpClient.queueLogin('bob', 'secret')
+ self.assertEquals('USER bob\r\n', ftpClient.transport.value())
+
+ # Clear the test buffer, acknowledge the USER command with a 230
+ # response code.
+ ftpClient.transport.clear()
+ ftpClient.lineReceived('230 Hello bob. No password needed.')
+
+ # The client shouldn't have sent anything more (i.e. it shouldn't have
+ # sent a PASS command).
+ self.assertEquals('', ftpClient.transport.value())
+
+
+
+class PathHandling(unittest.TestCase):
+ def testNormalizer(self):
+ for inp, outp in [('a', ['a']),
+ ('/a', ['a']),
+ ('/', []),
+ ('a/b/c', ['a', 'b', 'c']),
+ ('/a/b/c', ['a', 'b', 'c']),
+ ('/a/', ['a']),
+ ('a/', ['a'])]:
+ self.assertEquals(ftp.toSegments([], inp), outp)
+
+ for inp, outp in [('b', ['a', 'b']),
+ ('b/', ['a', 'b']),
+ ('/b', ['b']),
+ ('/b/', ['b']),
+ ('b/c', ['a', 'b', 'c']),
+ ('b/c/', ['a', 'b', 'c']),
+ ('/b/c', ['b', 'c']),
+ ('/b/c/', ['b', 'c'])]:
+ self.assertEquals(ftp.toSegments(['a'], inp), outp)
+
+ for inp, outp in [('//', []),
+ ('//a', ['a']),
+ ('a//', ['a']),
+ ('a//b', ['a', 'b'])]:
+ self.assertEquals(ftp.toSegments([], inp), outp)
+
+ for inp, outp in [('//', []),
+ ('//b', ['b']),
+ ('b//c', ['a', 'b', 'c'])]:
+ self.assertEquals(ftp.toSegments(['a'], inp), outp)
+
+ for inp, outp in [('..', []),
+ ('../', []),
+ ('a/..', ['x']),
+ ('/a/..', []),
+ ('/a/b/..', ['a']),
+ ('/a/b/../', ['a']),
+ ('/a/b/../c', ['a', 'c']),
+ ('/a/b/../c/', ['a', 'c']),
+ ('/a/b/../../c', ['c']),
+ ('/a/b/../../c/', ['c']),
+ ('/a/b/../../c/..', []),
+ ('/a/b/../../c/../', [])]:
+ self.assertEquals(ftp.toSegments(['x'], inp), outp)
+
+ for inp in ['..', '../', 'a/../..', 'a/../../',
+ '/..', '/../', '/a/../..', '/a/../../',
+ '/a/b/../../..']:
+ self.assertRaises(ftp.InvalidPath, ftp.toSegments, [], inp)
+
+ for inp in ['../..', '../../', '../a/../..']:
+ self.assertRaises(ftp.InvalidPath, ftp.toSegments, ['x'], inp)
+
+
+
+class ErrnoToFailureTestCase(unittest.TestCase):
+ """
+ Tests for L{ftp.errnoToFailure} errno checking.
+ """
+
+ def test_notFound(self):
+ """
+ C{errno.ENOENT} should be translated to L{ftp.FileNotFoundError}.
+ """
+ d = ftp.errnoToFailure(errno.ENOENT, "foo")
+ return self.assertFailure(d, ftp.FileNotFoundError)
+
+
+ def test_permissionDenied(self):
+ """
+ C{errno.EPERM} should be translated to L{ftp.PermissionDeniedError}.
+ """
+ d = ftp.errnoToFailure(errno.EPERM, "foo")
+ return self.assertFailure(d, ftp.PermissionDeniedError)
+
+
+ def test_accessDenied(self):
+ """
+ C{errno.EACCES} should be translated to L{ftp.PermissionDeniedError}.
+ """
+ d = ftp.errnoToFailure(errno.EACCES, "foo")
+ return self.assertFailure(d, ftp.PermissionDeniedError)
+
+
+ def test_notDirectory(self):
+ """
+ C{errno.ENOTDIR} should be translated to L{ftp.IsNotADirectoryError}.
+ """
+ d = ftp.errnoToFailure(errno.ENOTDIR, "foo")
+ return self.assertFailure(d, ftp.IsNotADirectoryError)
+
+
+ def test_fileExists(self):
+ """
+ C{errno.EEXIST} should be translated to L{ftp.FileExistsError}.
+ """
+ d = ftp.errnoToFailure(errno.EEXIST, "foo")
+ return self.assertFailure(d, ftp.FileExistsError)
+
+
+ def test_isDirectory(self):
+ """
+ C{errno.EISDIR} should be translated to L{ftp.IsADirectoryError}.
+ """
+ d = ftp.errnoToFailure(errno.EISDIR, "foo")
+ return self.assertFailure(d, ftp.IsADirectoryError)
+
+
+ def test_passThrough(self):
+ """
+ If an unknown errno is passed to L{ftp.errnoToFailure}, it should let
+ the originating exception pass through.
+ """
+ try:
+ raise RuntimeError("bar")
+ except:
+ d = ftp.errnoToFailure(-1, "foo")
+ return self.assertFailure(d, RuntimeError)
+
+
+
+class AnonymousFTPShellTestCase(unittest.TestCase):
+ """
+ Test anynomous shell properties.
+ """
+
+ def test_anonymousWrite(self):
+ """
+ Check that L{ftp.FTPAnonymousShell} returns an error when trying to
+ open it in write mode.
+ """
+ shell = ftp.FTPAnonymousShell('')
+ d = shell.openForWriting(('foo',))
+ self.assertFailure(d, ftp.PermissionDeniedError)
+ return d
+
+
+
+class IFTPShellTestsMixin:
+ """
+ Generic tests for the C{IFTPShell} interface.
+ """
+
+ def directoryExists(self, path):
+ """
+ Test if the directory exists at C{path}.
+
+ @param path: the relative path to check.
+ @type path: C{str}.
+
+ @return: C{True} if C{path} exists and is a directory, C{False} if
+ it's not the case
+ @rtype: C{bool}
+ """
+ raise NotImplementedError()
+
+
+ def createDirectory(self, path):
+ """
+ Create a directory in C{path}.
+
+ @param path: the relative path of the directory to create, with one
+ segment.
+ @type path: C{str}
+ """
+ raise NotImplementedError()
+
+
+ def fileExists(self, path):
+ """
+ Test if the file exists at C{path}.
+
+ @param path: the relative path to check.
+ @type path: C{str}.
+
+ @return: C{True} if C{path} exists and is a file, C{False} if it's not
+ the case.
+ @rtype: C{bool}
+ """
+ raise NotImplementedError()
+
+
+ def createFile(self, path, fileContent=''):
+ """
+ Create a file named C{path} with some content.
+
+ @param path: the relative path of the file to create, without
+ directory.
+ @type path: C{str}
+
+ @param fileContent: the content of the file.
+ @type fileContent: C{str}
+ """
+ raise NotImplementedError()
+
+
+ def test_createDirectory(self):
+ """
+ C{directoryExists} should report correctly about directory existence,
+ and C{createDirectory} should create a directory detectable by
+ C{directoryExists}.
+ """
+ self.assertFalse(self.directoryExists('bar'))
+ self.createDirectory('bar')
+ self.assertTrue(self.directoryExists('bar'))
+
+
+ def test_createFile(self):
+ """
+ C{fileExists} should report correctly about file existence, and
+ C{createFile} should create a file detectable by C{fileExists}.
+ """
+ self.assertFalse(self.fileExists('file.txt'))
+ self.createFile('file.txt')
+ self.assertTrue(self.fileExists('file.txt'))
+
+
+ def test_makeDirectory(self):
+ """
+ Create a directory and check it ends in the filesystem.
+ """
+ d = self.shell.makeDirectory(('foo',))
+ def cb(result):
+ self.assertTrue(self.directoryExists('foo'))
+ return d.addCallback(cb)
+
+
+ def test_makeDirectoryError(self):
+ """
+ Creating a directory that already exists should fail with a
+ C{ftp.FileExistsError}.
+ """
+ self.createDirectory('foo')
+ d = self.shell.makeDirectory(('foo',))
+ return self.assertFailure(d, ftp.FileExistsError)
+
+
+ def test_removeDirectory(self):
+ """
+ Try to remove a directory and check it's removed from the filesystem.
+ """
+ self.createDirectory('bar')
+ d = self.shell.removeDirectory(('bar',))
+ def cb(result):
+ self.assertFalse(self.directoryExists('bar'))
+ return d.addCallback(cb)
+
+
+ def test_removeDirectoryOnFile(self):
+ """
+ removeDirectory should not work in file and fail with a
+ C{ftp.IsNotADirectoryError}.
+ """
+ self.createFile('file.txt')
+ d = self.shell.removeDirectory(('file.txt',))
+ return self.assertFailure(d, ftp.IsNotADirectoryError)
+
+
+ def test_removeNotExistingDirectory(self):
+ """
+ Removing directory that doesn't exist should fail with a
+ C{ftp.FileNotFoundError}.
+ """
+ d = self.shell.removeDirectory(('bar',))
+ return self.assertFailure(d, ftp.FileNotFoundError)
+
+
+ def test_removeFile(self):
+ """
+ Try to remove a file and check it's removed from the filesystem.
+ """
+ self.createFile('file.txt')
+ d = self.shell.removeFile(('file.txt',))
+ def cb(res):
+ self.assertFalse(self.fileExists('file.txt'))
+ d.addCallback(cb)
+ return d
+
+
+ def test_removeFileOnDirectory(self):
+ """
+ removeFile should not work on directory.
+ """
+ self.createDirectory('ned')
+ d = self.shell.removeFile(('ned',))
+ return self.assertFailure(d, ftp.IsADirectoryError)
+
+
+ def test_removeNotExistingFile(self):
+ """
+ Try to remove a non existent file, and check it raises a
+ L{ivfs.NotFoundError}.
+ """
+ d = self.shell.removeFile(('foo',))
+ return self.assertFailure(d, ftp.FileNotFoundError)
+
+
+ def test_list(self):
+ """
+ Check the output of the list method.
+ """
+ self.createDirectory('ned')
+ self.createFile('file.txt')
+ d = self.shell.list(('.',))
+ def cb(l):
+ l.sort()
+ self.assertEquals(l,
+ [('file.txt', []), ('ned', [])])
+ return d.addCallback(cb)
+
+
+ def test_listWithStat(self):
+ """
+ Check the output of list with asked stats.
+ """
+ self.createDirectory('ned')
+ self.createFile('file.txt')
+ d = self.shell.list(('.',), ('size', 'permissions',))
+ def cb(l):
+ l.sort()
+ self.assertEquals(len(l), 2)
+ self.assertEquals(l[0][0], 'file.txt')
+ self.assertEquals(l[1][0], 'ned')
+ # Size and permissions are reported differently between platforms
+ # so just check they are present
+ self.assertEquals(len(l[0][1]), 2)
+ self.assertEquals(len(l[1][1]), 2)
+ return d.addCallback(cb)
+
+
+ def test_listWithInvalidStat(self):
+ """
+ Querying an invalid stat should result to a C{AttributeError}.
+ """
+ self.createDirectory('ned')
+ d = self.shell.list(('.',), ('size', 'whateverstat',))
+ return self.assertFailure(d, AttributeError)
+
+
+ def test_listFile(self):
+ """
+ Check the output of the list method on a file.
+ """
+ self.createFile('file.txt')
+ d = self.shell.list(('file.txt',))
+ def cb(l):
+ l.sort()
+ self.assertEquals(l,
+ [('file.txt', [])])
+ return d.addCallback(cb)
+
+
+ def test_listNotExistingDirectory(self):
+ """
+ list on a directory that doesn't exist should fail with a
+ L{ftp.FileNotFoundError}.
+ """
+ d = self.shell.list(('foo',))
+ return self.assertFailure(d, ftp.FileNotFoundError)
+
+
+ def test_access(self):
+ """
+ Try to access a resource.
+ """
+ self.createDirectory('ned')
+ d = self.shell.access(('ned',))
+ return d
+
+
+ def test_accessNotFound(self):
+ """
+ access should fail on a resource that doesn't exist.
+ """
+ d = self.shell.access(('foo',))
+ return self.assertFailure(d, ftp.FileNotFoundError)
+
+
+ def test_openForReading(self):
+ """
+ Check that openForReading returns an object providing C{ftp.IReadFile}.
+ """
+ self.createFile('file.txt')
+ d = self.shell.openForReading(('file.txt',))
+ def cb(res):
+ self.assertTrue(ftp.IReadFile.providedBy(res))
+ d.addCallback(cb)
+ return d
+
+
+ def test_openForReadingNotFound(self):
+ """
+ openForReading should fail with a C{ftp.FileNotFoundError} on a file
+ that doesn't exist.
+ """
+ d = self.shell.openForReading(('ned',))
+ return self.assertFailure(d, ftp.FileNotFoundError)
+
+
+ def test_openForReadingOnDirectory(self):
+ """
+ openForReading should not work on directory.
+ """
+ self.createDirectory('ned')
+ d = self.shell.openForReading(('ned',))
+ return self.assertFailure(d, ftp.IsADirectoryError)
+
+
+ def test_openForWriting(self):
+ """
+ Check that openForWriting returns an object providing C{ftp.IWriteFile}.
+ """
+ d = self.shell.openForWriting(('foo',))
+ def cb1(res):
+ self.assertTrue(ftp.IWriteFile.providedBy(res))
+ return res.receive().addCallback(cb2)
+ def cb2(res):
+ self.assertTrue(IConsumer.providedBy(res))
+ d.addCallback(cb1)
+ return d
+
+
+ def test_openForWritingExistingDirectory(self):
+ """
+ openForWriting should not be able to open a directory that already
+ exists.
+ """
+ self.createDirectory('ned')
+ d = self.shell.openForWriting(('ned',))
+ return self.assertFailure(d, ftp.IsADirectoryError)
+
+
+ def test_openForWritingInNotExistingDirectory(self):
+ """
+ openForWring should fail with a L{ftp.FileNotFoundError} if you specify
+ a file in a directory that doesn't exist.
+ """
+ self.createDirectory('ned')
+ d = self.shell.openForWriting(('ned', 'idonotexist', 'foo'))
+ return self.assertFailure(d, ftp.FileNotFoundError)
+
+
+ def test_statFile(self):
+ """
+ Check the output of the stat method on a file.
+ """
+ fileContent = 'wobble\n'
+ self.createFile('file.txt', fileContent)
+ d = self.shell.stat(('file.txt',), ('size', 'directory'))
+ def cb(res):
+ self.assertEquals(res[0], len(fileContent))
+ self.assertFalse(res[1])
+ d.addCallback(cb)
+ return d
+
+
+ def test_statDirectory(self):
+ """
+ Check the output of the stat method on a directory.
+ """
+ self.createDirectory('ned')
+ d = self.shell.stat(('ned',), ('size', 'directory'))
+ def cb(res):
+ self.assertTrue(res[1])
+ d.addCallback(cb)
+ return d
+
+
+ def test_statOwnerGroup(self):
+ """
+ Check the owner and groups stats.
+ """
+ self.createDirectory('ned')
+ d = self.shell.stat(('ned',), ('owner', 'group'))
+ def cb(res):
+ self.assertEquals(len(res), 2)
+ d.addCallback(cb)
+ return d
+
+
+ def test_statNotExisting(self):
+ """
+ stat should fail with L{ftp.FileNotFoundError} on a file that doesn't
+ exist.
+ """
+ d = self.shell.stat(('foo',), ('size', 'directory'))
+ return self.assertFailure(d, ftp.FileNotFoundError)
+
+
+ def test_invalidStat(self):
+ """
+ Querying an invalid stat should result to a C{AttributeError}.
+ """
+ self.createDirectory('ned')
+ d = self.shell.stat(('ned',), ('size', 'whateverstat'))
+ return self.assertFailure(d, AttributeError)
+
+
+ def test_rename(self):
+ """
+ Try to rename a directory.
+ """
+ self.createDirectory('ned')
+ d = self.shell.rename(('ned',), ('foo',))
+ def cb(res):
+ self.assertTrue(self.directoryExists('foo'))
+ self.assertFalse(self.directoryExists('ned'))
+ return d.addCallback(cb)
+
+
+ def test_renameNotExisting(self):
+ """
+ Renaming a directory that doesn't exist should fail with
+ L{ftp.FileNotFoundError}.
+ """
+ d = self.shell.rename(('foo',), ('bar',))
+ return self.assertFailure(d, ftp.FileNotFoundError)
+
+
+
+class FTPShellTestCase(unittest.TestCase, IFTPShellTestsMixin):
+ """
+ Tests for the C{ftp.FTPShell} object.
+ """
+
+ def setUp(self):
+ """
+ Create a root directory and instantiate a shell.
+ """
+ self.root = filepath.FilePath(self.mktemp())
+ self.root.createDirectory()
+ self.shell = ftp.FTPShell(self.root)
+
+
+ def directoryExists(self, path):
+ """
+ Test if the directory exists at C{path}.
+ """
+ return self.root.child(path).isdir()
+
+
+ def createDirectory(self, path):
+ """
+ Create a directory in C{path}.
+ """
+ return self.root.child(path).createDirectory()
+
+
+ def fileExists(self, path):
+ """
+ Test if the file exists at C{path}.
+ """
+ return self.root.child(path).isfile()
+
+
+ def createFile(self, path, fileContent=''):
+ """
+ Create a file named C{path} with some content.
+ """
+ return self.root.child(path).setContent(fileContent)
+
+
+
+class TestConsumer(object):
+ """
+ A simple consumer for tests. It only works with non-streaming producers.
+
+ @ivar producer: an object providing
+ L{twisted.internet.interfaces.IPullProducer}.
+ """
+
+ implements(IConsumer)
+ producer = None
+
+ def registerProducer(self, producer, streaming):
+ """
+ Simple register of producer, checks that no register has happened
+ before.
+ """
+ assert self.producer is None
+ self.buffer = []
+ self.producer = producer
+ self.producer.resumeProducing()
+
+
+ def unregisterProducer(self):
+ """
+ Unregister the producer, it should be done after a register.
+ """
+ assert self.producer is not None
+ self.producer = None
+
+
+ def write(self, data):
+ """
+ Save the data received.
+ """
+ self.buffer.append(data)
+ self.producer.resumeProducing()
+
+
+
+class TestProducer(object):
+ """
+ A dumb producer.
+ """
+
+ def __init__(self, toProduce, consumer):
+ """
+ @param toProduce: data to write
+ @type toProduce: C{str}
+ @param consumer: the consumer of data.
+ @type consumer: C{IConsumer}
+ """
+ self.toProduce = toProduce
+ self.consumer = consumer
+
+
+ def start(self):
+ """
+ Send the data to consume.
+ """
+ self.consumer.write(self.toProduce)
+
+
+
+class IReadWriteTestsMixin:
+ """
+ Generic tests for the C{IReadFile} and C{IWriteFile} interfaces.
+ """
+
+ def getFileReader(self, content):
+ """
+ Return an object providing C{IReadFile}, ready to send data C{content}.
+ """
+ raise NotImplementedError()
+
+
+ def getFileWriter(self):
+ """
+ Return an object providing C{IWriteFile}, ready to receive data.
+ """
+ raise NotImplementedError()
+
+
+ def getFileContent(self):
+ """
+ Return the content of the file used.
+ """
+ raise NotImplementedError()
+
+
+ def test_read(self):
+ """
+ Test L{ftp.IReadFile}: the implementation should have a send method
+ returning a C{Deferred} which fires when all the data has been sent
+ to the consumer, and the data should be correctly send to the consumer.
+ """
+ content = 'wobble\n'
+ consumer = TestConsumer()
+ def cbGet(reader):
+ return reader.send(consumer).addCallback(cbSend)
+ def cbSend(res):
+ self.assertEquals("".join(consumer.buffer), content)
+ return self.getFileReader(content).addCallback(cbGet)
+
+
+ def test_write(self):
+ """
+ Test L{ftp.IWriteFile}: the implementation should have a receive method
+ returning a C{Deferred} with fires with a consumer ready to receive
+ data to be written.
+ """
+ content = 'elbbow\n'
+ def cbGet(writer):
+ return writer.receive().addCallback(cbReceive)
+ def cbReceive(consumer):
+ producer = TestProducer(content, consumer)
+ consumer.registerProducer(None, True)
+ producer.start()
+ consumer.unregisterProducer()
+ self.assertEquals(self.getFileContent(), content)
+ return self.getFileWriter().addCallback(cbGet)
+
+
+
+class FTPReadWriteTestCase(unittest.TestCase, IReadWriteTestsMixin):
+ """
+ Tests for C{ftp._FileReader} and C{ftp._FileWriter}, the objects returned
+ by the shell in C{openForReading}/C{openForWriting}.
+ """
+
+ def setUp(self):
+ """
+ Create a temporary file used later.
+ """
+ self.root = filepath.FilePath(self.mktemp())
+ self.root.createDirectory()
+ self.shell = ftp.FTPShell(self.root)
+ self.filename = "file.txt"
+
+
+ def getFileReader(self, content):
+ """
+ Return a C{ftp._FileReader} instance with a file opened for reading.
+ """
+ self.root.child(self.filename).setContent(content)
+ return self.shell.openForReading((self.filename,))
+
+
+ def getFileWriter(self):
+ """
+ Return a C{ftp._FileWriter} instance with a file opened for writing.
+ """
+ return self.shell.openForWriting((self.filename,))
+
+
+ def getFileContent(self):
+ """
+ Return the content of the temporary file.
+ """
+ return self.root.child(self.filename).getContent()
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_hook.py b/vendor/Twisted-10.0.0/twisted/test/test_hook.py
new file mode 100644
index 0000000000..0118df12f8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_hook.py
@@ -0,0 +1,150 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Test cases for twisted.hook module.
+"""
+
+from twisted.python import hook
+from twisted.trial import unittest
+
+class BaseClass:
+ """
+ dummy class to help in testing.
+ """
+ def __init__(self):
+ """
+ dummy initializer
+ """
+ self.calledBasePre = 0
+ self.calledBasePost = 0
+ self.calledBase = 0
+
+ def func(self, a, b):
+ """
+ dummy method
+ """
+ assert a == 1
+ assert b == 2
+ self.calledBase = self.calledBase + 1
+
+
+class SubClass(BaseClass):
+ """
+ another dummy class
+ """
+ def __init__(self):
+ """
+ another dummy initializer
+ """
+ BaseClass.__init__(self)
+ self.calledSubPre = 0
+ self.calledSubPost = 0
+ self.calledSub = 0
+
+ def func(self, a, b):
+ """
+ another dummy function
+ """
+ assert a == 1
+ assert b == 2
+ BaseClass.func(self, a, b)
+ self.calledSub = self.calledSub + 1
+
+_clean_BaseClass = BaseClass.__dict__.copy()
+_clean_SubClass = SubClass.__dict__.copy()
+
+def basePre(base, a, b):
+ """
+ a pre-hook for the base class
+ """
+ base.calledBasePre = base.calledBasePre + 1
+
+def basePost(base, a, b):
+ """
+ a post-hook for the base class
+ """
+ base.calledBasePost = base.calledBasePost + 1
+
+def subPre(sub, a, b):
+ """
+ a pre-hook for the subclass
+ """
+ sub.calledSubPre = sub.calledSubPre + 1
+
+def subPost(sub, a, b):
+ """
+ a post-hook for the subclass
+ """
+ sub.calledSubPost = sub.calledSubPost + 1
+
+class HookTestCase(unittest.TestCase):
+ """
+ test case to make sure hooks are called
+ """
+ def setUp(self):
+ """Make sure we have clean versions of our classes."""
+ BaseClass.__dict__.clear()
+ BaseClass.__dict__.update(_clean_BaseClass)
+ SubClass.__dict__.clear()
+ SubClass.__dict__.update(_clean_SubClass)
+
+ def testBaseHook(self):
+ """make sure that the base class's hook is called reliably
+ """
+ base = BaseClass()
+ self.assertEquals(base.calledBase, 0)
+ self.assertEquals(base.calledBasePre, 0)
+ base.func(1,2)
+ self.assertEquals(base.calledBase, 1)
+ self.assertEquals(base.calledBasePre, 0)
+ hook.addPre(BaseClass, "func", basePre)
+ base.func(1, b=2)
+ self.assertEquals(base.calledBase, 2)
+ self.assertEquals(base.calledBasePre, 1)
+ hook.addPost(BaseClass, "func", basePost)
+ base.func(1, b=2)
+ self.assertEquals(base.calledBasePost, 1)
+ self.assertEquals(base.calledBase, 3)
+ self.assertEquals(base.calledBasePre, 2)
+ hook.removePre(BaseClass, "func", basePre)
+ hook.removePost(BaseClass, "func", basePost)
+ base.func(1, b=2)
+ self.assertEquals(base.calledBasePost, 1)
+ self.assertEquals(base.calledBase, 4)
+ self.assertEquals(base.calledBasePre, 2)
+
+ def testSubHook(self):
+ """test interactions between base-class hooks and subclass hooks
+ """
+ sub = SubClass()
+ self.assertEquals(sub.calledSub, 0)
+ self.assertEquals(sub.calledBase, 0)
+ sub.func(1, b=2)
+ self.assertEquals(sub.calledSub, 1)
+ self.assertEquals(sub.calledBase, 1)
+ hook.addPre(SubClass, 'func', subPre)
+ self.assertEquals(sub.calledSub, 1)
+ self.assertEquals(sub.calledBase, 1)
+ self.assertEquals(sub.calledSubPre, 0)
+ self.assertEquals(sub.calledBasePre, 0)
+ sub.func(1, b=2)
+ self.assertEquals(sub.calledSub, 2)
+ self.assertEquals(sub.calledBase, 2)
+ self.assertEquals(sub.calledSubPre, 1)
+ self.assertEquals(sub.calledBasePre, 0)
+ # let the pain begin
+ hook.addPre(BaseClass, 'func', basePre)
+ BaseClass.func(sub, 1, b=2)
+ # sub.func(1, b=2)
+ self.assertEquals(sub.calledBase, 3)
+ self.assertEquals(sub.calledBasePre, 1, str(sub.calledBasePre))
+ sub.func(1, b=2)
+ self.assertEquals(sub.calledBasePre, 2)
+ self.assertEquals(sub.calledBase, 4)
+ self.assertEquals(sub.calledSubPre, 2)
+ self.assertEquals(sub.calledSub, 3)
+
+testCases = [HookTestCase]
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_htb.py b/vendor/Twisted-10.0.0/twisted/test/test_htb.py
new file mode 100644
index 0000000000..72dc513873
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_htb.py
@@ -0,0 +1,96 @@
+# -*- Python -*-
+
+__version__ = '$Revision: 1.3 $'[11:-2]
+
+from twisted.trial import unittest
+from twisted.protocols import htb
+
+class DummyClock:
+ time = 0
+ def set(self, when):
+ self.time = when
+
+ def __call__(self):
+ return self.time
+
+class SomeBucket(htb.Bucket):
+ maxburst = 100
+ rate = 2
+
+class TestBucketBase(unittest.TestCase):
+ def setUp(self):
+ self._realTimeFunc = htb.time
+ self.clock = DummyClock()
+ htb.time = self.clock
+
+ def tearDown(self):
+ htb.time = self._realTimeFunc
+
+class TestBucket(TestBucketBase):
+ def testBucketSize(self):
+ """Testing the size of the bucket."""
+ b = SomeBucket()
+ fit = b.add(1000)
+ self.failUnlessEqual(100, fit)
+
+ def testBucketDrian(self):
+ """Testing the bucket's drain rate."""
+ b = SomeBucket()
+ fit = b.add(1000)
+ self.clock.set(10)
+ fit = b.add(1000)
+ self.failUnlessEqual(20, fit)
+
+class TestBucketNesting(TestBucketBase):
+ def setUp(self):
+ TestBucketBase.setUp(self)
+ self.parent = SomeBucket()
+ self.child1 = SomeBucket(self.parent)
+ self.child2 = SomeBucket(self.parent)
+
+ def testBucketParentSize(self):
+ # Use up most of the parent bucket.
+ self.child1.add(90)
+ fit = self.child2.add(90)
+ self.failUnlessEqual(10, fit)
+
+ def testBucketParentRate(self):
+ # Make the parent bucket drain slower.
+ self.parent.rate = 1
+ # Fill both child1 and parent.
+ self.child1.add(100)
+ self.clock.set(10)
+ fit = self.child1.add(100)
+ # How much room was there? The child bucket would have had 20,
+ # but the parent bucket only ten (so no, it wouldn't make too much
+ # sense to have a child bucket draining faster than its parent in a real
+ # application.)
+ self.failUnlessEqual(10, fit)
+
+
+# TODO: Test the Transport stuff?
+
+from test_pcp import DummyConsumer
+
+class ConsumerShaperTest(TestBucketBase):
+ def setUp(self):
+ TestBucketBase.setUp(self)
+ self.underlying = DummyConsumer()
+ self.bucket = SomeBucket()
+ self.shaped = htb.ShapedConsumer(self.underlying, self.bucket)
+
+ def testRate(self):
+ # Start off with a full bucket, so the burst-size dosen't factor in
+ # to the calculations.
+ delta_t = 10
+ self.bucket.add(100)
+ self.shaped.write("x" * 100)
+ self.clock.set(delta_t)
+ self.shaped.resumeProducing()
+ self.failUnlessEqual(len(self.underlying.getvalue()),
+ delta_t * self.bucket.rate)
+
+ def testBucketRefs(self):
+ self.failUnlessEqual(self.bucket._refcount, 1)
+ self.shaped.stopProducing()
+ self.failUnlessEqual(self.bucket._refcount, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_ident.py b/vendor/Twisted-10.0.0/twisted/test/test_ident.py
new file mode 100644
index 0000000000..91f4243608
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_ident.py
@@ -0,0 +1,194 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Test cases for twisted.protocols.ident module.
+"""
+
+import struct
+
+from twisted.protocols import ident
+from twisted.python import failure
+from twisted.internet import error
+from twisted.internet import defer
+
+from twisted.trial import unittest
+from twisted.test.proto_helpers import StringTransport
+
+
+
+class ClassParserTestCase(unittest.TestCase):
+ """
+ Test parsing of ident responses.
+ """
+
+ def setUp(self):
+ """
+ Create a ident client used in tests.
+ """
+ self.client = ident.IdentClient()
+
+
+ def test_indentError(self):
+ """
+ 'UNKNOWN-ERROR' error should map to the L{ident.IdentError} exception.
+ """
+ d = defer.Deferred()
+ self.client.queries.append((d, 123, 456))
+ self.client.lineReceived('123, 456 : ERROR : UNKNOWN-ERROR')
+ return self.assertFailure(d, ident.IdentError)
+
+
+ def test_noUSerError(self):
+ """
+ 'NO-USER' error should map to the L{ident.NoUser} exception.
+ """
+ d = defer.Deferred()
+ self.client.queries.append((d, 234, 456))
+ self.client.lineReceived('234, 456 : ERROR : NO-USER')
+ return self.assertFailure(d, ident.NoUser)
+
+
+ def test_invalidPortError(self):
+ """
+ 'INVALID-PORT' error should map to the L{ident.InvalidPort} exception.
+ """
+ d = defer.Deferred()
+ self.client.queries.append((d, 345, 567))
+ self.client.lineReceived('345, 567 : ERROR : INVALID-PORT')
+ return self.assertFailure(d, ident.InvalidPort)
+
+
+ def test_hiddenUserError(self):
+ """
+ 'HIDDEN-USER' error should map to the L{ident.HiddenUser} exception.
+ """
+ d = defer.Deferred()
+ self.client.queries.append((d, 567, 789))
+ self.client.lineReceived('567, 789 : ERROR : HIDDEN-USER')
+ return self.assertFailure(d, ident.HiddenUser)
+
+
+ def test_lostConnection(self):
+ """
+ A pending query which failed because of a ConnectionLost should
+ receive an L{ident.IdentError}.
+ """
+ d = defer.Deferred()
+ self.client.queries.append((d, 765, 432))
+ self.client.connectionLost(failure.Failure(error.ConnectionLost()))
+ return self.assertFailure(d, ident.IdentError)
+
+
+
+class TestIdentServer(ident.IdentServer):
+ def lookup(self, serverAddress, clientAddress):
+ return self.resultValue
+
+
+class TestErrorIdentServer(ident.IdentServer):
+ def lookup(self, serverAddress, clientAddress):
+ raise self.exceptionType()
+
+
+class NewException(RuntimeError):
+ pass
+
+
+class ServerParserTestCase(unittest.TestCase):
+ def testErrors(self):
+ p = TestErrorIdentServer()
+ p.makeConnection(StringTransport())
+ L = []
+ p.sendLine = L.append
+
+ p.exceptionType = ident.IdentError
+ p.lineReceived('123, 345')
+ self.assertEquals(L[0], '123, 345 : ERROR : UNKNOWN-ERROR')
+
+ p.exceptionType = ident.NoUser
+ p.lineReceived('432, 210')
+ self.assertEquals(L[1], '432, 210 : ERROR : NO-USER')
+
+ p.exceptionType = ident.InvalidPort
+ p.lineReceived('987, 654')
+ self.assertEquals(L[2], '987, 654 : ERROR : INVALID-PORT')
+
+ p.exceptionType = ident.HiddenUser
+ p.lineReceived('756, 827')
+ self.assertEquals(L[3], '756, 827 : ERROR : HIDDEN-USER')
+
+ p.exceptionType = NewException
+ p.lineReceived('987, 789')
+ self.assertEquals(L[4], '987, 789 : ERROR : UNKNOWN-ERROR')
+ errs = self.flushLoggedErrors(NewException)
+ self.assertEquals(len(errs), 1)
+
+ for port in -1, 0, 65536, 65537:
+ del L[:]
+ p.lineReceived('%d, 5' % (port,))
+ p.lineReceived('5, %d' % (port,))
+ self.assertEquals(
+ L, ['%d, 5 : ERROR : INVALID-PORT' % (port,),
+ '5, %d : ERROR : INVALID-PORT' % (port,)])
+
+ def testSuccess(self):
+ p = TestIdentServer()
+ p.makeConnection(StringTransport())
+ L = []
+ p.sendLine = L.append
+
+ p.resultValue = ('SYS', 'USER')
+ p.lineReceived('123, 456')
+ self.assertEquals(L[0], '123, 456 : USERID : SYS : USER')
+
+
+if struct.pack('=L', 1)[0] == '\x01':
+ _addr1 = '0100007F'
+ _addr2 = '04030201'
+else:
+ _addr1 = '7F000001'
+ _addr2 = '01020304'
+
+
+class ProcMixinTestCase(unittest.TestCase):
+ line = ('4: %s:0019 %s:02FA 0A 00000000:00000000 '
+ '00:00000000 00000000 0 0 10927 1 f72a5b80 '
+ '3000 0 0 2 -1') % (_addr1, _addr2)
+
+ def testDottedQuadFromHexString(self):
+ p = ident.ProcServerMixin()
+ self.assertEquals(p.dottedQuadFromHexString(_addr1), '127.0.0.1')
+
+ def testUnpackAddress(self):
+ p = ident.ProcServerMixin()
+ self.assertEquals(p.unpackAddress(_addr1 + ':0277'),
+ ('127.0.0.1', 631))
+
+ def testLineParser(self):
+ p = ident.ProcServerMixin()
+ self.assertEquals(
+ p.parseLine(self.line),
+ (('127.0.0.1', 25), ('1.2.3.4', 762), 0))
+
+ def testExistingAddress(self):
+ username = []
+ p = ident.ProcServerMixin()
+ p.entries = lambda: iter([self.line])
+ p.getUsername = lambda uid: (username.append(uid), 'root')[1]
+ self.assertEquals(
+ p.lookup(('127.0.0.1', 25), ('1.2.3.4', 762)),
+ (p.SYSTEM_NAME, 'root'))
+ self.assertEquals(username, [0])
+
+ def testNonExistingAddress(self):
+ p = ident.ProcServerMixin()
+ p.entries = lambda: iter([self.line])
+ self.assertRaises(ident.NoUser, p.lookup, ('127.0.0.1', 26),
+ ('1.2.3.4', 762))
+ self.assertRaises(ident.NoUser, p.lookup, ('127.0.0.1', 25),
+ ('1.2.3.5', 762))
+ self.assertRaises(ident.NoUser, p.lookup, ('127.0.0.1', 25),
+ ('1.2.3.4', 763))
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_import.py b/vendor/Twisted-10.0.0/twisted/test/test_import.py
new file mode 100644
index 0000000000..82f11f9d43
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_import.py
@@ -0,0 +1,78 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.trial import unittest
+from twisted.python.runtime import platformType
+
+class AtLeastImportTestCase(unittest.TestCase):
+
+ """I test that there are no syntax errors which will not allow importing.
+ """
+
+ failureException = ImportError
+
+ def test_misc(self):
+ """Test importing other misc. modules
+ """
+ from twisted import copyright
+
+ def test_persisted(self):
+ """Test importing persisted
+ """
+ from twisted.persisted import dirdbm
+ from twisted.persisted import styles
+
+ def test_internet(self):
+ """Test importing internet
+ """
+ from twisted.internet import tcp
+ from twisted.internet import main
+ # from twisted.internet import ssl
+ from twisted.internet import abstract
+ from twisted.internet import udp
+ from twisted.internet import protocol
+ from twisted.internet import defer
+
+ def test_unix(self):
+ """internet modules for unix."""
+ from twisted.internet import stdio
+ from twisted.internet import process
+ from twisted.internet import unix
+
+ if platformType != "posix":
+ test_unix.skip = "UNIX-only modules"
+
+ def test_spread(self):
+ """Test importing spreadables
+ """
+ from twisted.spread import pb
+ from twisted.spread import jelly
+ from twisted.spread import banana
+ from twisted.spread import flavors
+
+ def test_twistedPython(self):
+ """Test importing twisted.python
+ """
+ from twisted.python import hook
+ from twisted.python import log
+ from twisted.python import reflect
+ # from twisted.python import threadable
+ # from twisted.python import threadpool
+ from twisted.python import usage
+ from twisted.python import otp
+
+ def test_protocols(self):
+ """Test importing protocols
+ """
+ from twisted.protocols import basic
+ from twisted.protocols import ftp
+ from twisted.protocols import telnet
+ from twisted.protocols import policies
+
+ def test_enterprise(self):
+ from twisted.enterprise import adbapi
+ from twisted.enterprise import reflector
+ from twisted.enterprise import sqlreflector
+ from twisted.enterprise import row
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_internet.py b/vendor/Twisted-10.0.0/twisted/test/test_internet.py
new file mode 100644
index 0000000000..8b4d6f3d5f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_internet.py
@@ -0,0 +1,1396 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for lots of functionality provided by L{twisted.internet}.
+"""
+
+import os
+import sys
+import time
+
+from twisted.trial import unittest
+from twisted.internet import reactor, protocol, error, abstract, defer
+from twisted.internet import interfaces, base
+
+try:
+ from twisted.internet import ssl
+except ImportError:
+ ssl = None
+if ssl and not ssl.supported:
+ ssl = None
+
+from twisted.internet.defer import Deferred, maybeDeferred
+from twisted.python import util, runtime
+
+
+
+class ThreePhaseEventTests(unittest.TestCase):
+ """
+ Tests for the private implementation helpers for system event triggers.
+ """
+ def setUp(self):
+ """
+ Create a trigger, an argument, and an event to be used by tests.
+ """
+ self.trigger = lambda x: None
+ self.arg = object()
+ self.event = base._ThreePhaseEvent()
+
+
+ def test_addInvalidPhase(self):
+ """
+ L{_ThreePhaseEvent.addTrigger} should raise L{KeyError} when called
+ with an invalid phase.
+ """
+ self.assertRaises(
+ KeyError,
+ self.event.addTrigger, 'xxx', self.trigger, self.arg)
+
+
+ def test_addBeforeTrigger(self):
+ """
+ L{_ThreePhaseEvent.addTrigger} should accept C{'before'} as a phase, a
+ callable, and some arguments and add the callable with the arguments to
+ the before list.
+ """
+ self.event.addTrigger('before', self.trigger, self.arg)
+ self.assertEqual(
+ self.event.before,
+ [(self.trigger, (self.arg,), {})])
+
+
+ def test_addDuringTrigger(self):
+ """
+ L{_ThreePhaseEvent.addTrigger} should accept C{'during'} as a phase, a
+ callable, and some arguments and add the callable with the arguments to
+ the during list.
+ """
+ self.event.addTrigger('during', self.trigger, self.arg)
+ self.assertEqual(
+ self.event.during,
+ [(self.trigger, (self.arg,), {})])
+
+
+ def test_addAfterTrigger(self):
+ """
+ L{_ThreePhaseEvent.addTrigger} should accept C{'after'} as a phase, a
+ callable, and some arguments and add the callable with the arguments to
+ the after list.
+ """
+ self.event.addTrigger('after', self.trigger, self.arg)
+ self.assertEqual(
+ self.event.after,
+ [(self.trigger, (self.arg,), {})])
+
+
+ def test_removeTrigger(self):
+ """
+ L{_ThreePhaseEvent.removeTrigger} should accept an opaque object
+ previously returned by L{_ThreePhaseEvent.addTrigger} and remove the
+ associated trigger.
+ """
+ handle = self.event.addTrigger('before', self.trigger, self.arg)
+ self.event.removeTrigger(handle)
+ self.assertEqual(self.event.before, [])
+
+
+ def test_removeNonexistentTrigger(self):
+ """
+ L{_ThreePhaseEvent.removeTrigger} should raise L{ValueError} when given
+ an object not previously returned by L{_ThreePhaseEvent.addTrigger}.
+ """
+ self.assertRaises(ValueError, self.event.removeTrigger, object())
+
+
+ def test_removeRemovedTrigger(self):
+ """
+ L{_ThreePhaseEvent.removeTrigger} should raise L{ValueError} the second
+ time it is called with an object returned by
+ L{_ThreePhaseEvent.addTrigger}.
+ """
+ handle = self.event.addTrigger('before', self.trigger, self.arg)
+ self.event.removeTrigger(handle)
+ self.assertRaises(ValueError, self.event.removeTrigger, handle)
+
+
+ def test_removeAlmostValidTrigger(self):
+ """
+ L{_ThreePhaseEvent.removeTrigger} should raise L{ValueError} if it is
+ given a trigger handle which resembles a valid trigger handle aside
+ from its phase being incorrect.
+ """
+ self.assertRaises(
+ KeyError,
+ self.event.removeTrigger, ('xxx', self.trigger, (self.arg,), {}))
+
+
+ def test_fireEvent(self):
+ """
+ L{_ThreePhaseEvent.fireEvent} should call I{before}, I{during}, and
+ I{after} phase triggers in that order.
+ """
+ events = []
+ self.event.addTrigger('after', events.append, ('first', 'after'))
+ self.event.addTrigger('during', events.append, ('first', 'during'))
+ self.event.addTrigger('before', events.append, ('first', 'before'))
+ self.event.addTrigger('before', events.append, ('second', 'before'))
+ self.event.addTrigger('during', events.append, ('second', 'during'))
+ self.event.addTrigger('after', events.append, ('second', 'after'))
+
+ self.assertEqual(events, [])
+ self.event.fireEvent()
+ self.assertEqual(events,
+ [('first', 'before'), ('second', 'before'),
+ ('first', 'during'), ('second', 'during'),
+ ('first', 'after'), ('second', 'after')])
+
+
+ def test_asynchronousBefore(self):
+ """
+ L{_ThreePhaseEvent.fireEvent} should wait for any L{Deferred} returned
+ by a I{before} phase trigger before proceeding to I{during} events.
+ """
+ events = []
+ beforeResult = Deferred()
+ self.event.addTrigger('before', lambda: beforeResult)
+ self.event.addTrigger('during', events.append, 'during')
+ self.event.addTrigger('after', events.append, 'after')
+
+ self.assertEqual(events, [])
+ self.event.fireEvent()
+ self.assertEqual(events, [])
+ beforeResult.callback(None)
+ self.assertEqual(events, ['during', 'after'])
+
+
+ def test_beforeTriggerException(self):
+ """
+ If a before-phase trigger raises a synchronous exception, it should be
+ logged and the remaining triggers should be run.
+ """
+ events = []
+
+ class DummyException(Exception):
+ pass
+
+ def raisingTrigger():
+ raise DummyException()
+
+ self.event.addTrigger('before', raisingTrigger)
+ self.event.addTrigger('before', events.append, 'before')
+ self.event.addTrigger('during', events.append, 'during')
+ self.event.fireEvent()
+ self.assertEqual(events, ['before', 'during'])
+ errors = self.flushLoggedErrors(DummyException)
+ self.assertEqual(len(errors), 1)
+
+
+ def test_duringTriggerException(self):
+ """
+ If a during-phase trigger raises a synchronous exception, it should be
+ logged and the remaining triggers should be run.
+ """
+ events = []
+
+ class DummyException(Exception):
+ pass
+
+ def raisingTrigger():
+ raise DummyException()
+
+ self.event.addTrigger('during', raisingTrigger)
+ self.event.addTrigger('during', events.append, 'during')
+ self.event.addTrigger('after', events.append, 'after')
+ self.event.fireEvent()
+ self.assertEqual(events, ['during', 'after'])
+ errors = self.flushLoggedErrors(DummyException)
+ self.assertEqual(len(errors), 1)
+
+
+ def test_synchronousRemoveAlreadyExecutedBefore(self):
+ """
+ If a before-phase trigger tries to remove another before-phase trigger
+ which has already run, a warning should be emitted.
+ """
+ events = []
+
+ def removeTrigger():
+ self.event.removeTrigger(beforeHandle)
+
+ beforeHandle = self.event.addTrigger('before', events.append, ('first', 'before'))
+ self.event.addTrigger('before', removeTrigger)
+ self.event.addTrigger('before', events.append, ('second', 'before'))
+ self.assertWarns(
+ DeprecationWarning,
+ "Removing already-fired system event triggers will raise an "
+ "exception in a future version of Twisted.",
+ __file__,
+ self.event.fireEvent)
+ self.assertEqual(events, [('first', 'before'), ('second', 'before')])
+
+
+ def test_synchronousRemovePendingBefore(self):
+ """
+ If a before-phase trigger removes another before-phase trigger which
+ has not yet run, the removed trigger should not be run.
+ """
+ events = []
+ self.event.addTrigger(
+ 'before', lambda: self.event.removeTrigger(beforeHandle))
+ beforeHandle = self.event.addTrigger(
+ 'before', events.append, ('first', 'before'))
+ self.event.addTrigger('before', events.append, ('second', 'before'))
+ self.event.fireEvent()
+ self.assertEqual(events, [('second', 'before')])
+
+
+ def test_synchronousBeforeRemovesDuring(self):
+ """
+ If a before-phase trigger removes a during-phase trigger, the
+ during-phase trigger should not be run.
+ """
+ events = []
+ self.event.addTrigger(
+ 'before', lambda: self.event.removeTrigger(duringHandle))
+ duringHandle = self.event.addTrigger('during', events.append, 'during')
+ self.event.addTrigger('after', events.append, 'after')
+ self.event.fireEvent()
+ self.assertEqual(events, ['after'])
+
+
+ def test_asynchronousBeforeRemovesDuring(self):
+ """
+ If a before-phase trigger returns a L{Deferred} and later removes a
+ during-phase trigger before the L{Deferred} fires, the during-phase
+ trigger should not be run.
+ """
+ events = []
+ beforeResult = Deferred()
+ self.event.addTrigger('before', lambda: beforeResult)
+ duringHandle = self.event.addTrigger('during', events.append, 'during')
+ self.event.addTrigger('after', events.append, 'after')
+ self.event.fireEvent()
+ self.event.removeTrigger(duringHandle)
+ beforeResult.callback(None)
+ self.assertEqual(events, ['after'])
+
+
+ def test_synchronousBeforeRemovesConspicuouslySimilarDuring(self):
+ """
+ If a before-phase trigger removes a during-phase trigger which is
+ identical to an already-executed before-phase trigger aside from their
+ phases, no warning should be emitted and the during-phase trigger
+ should not be run.
+ """
+ events = []
+ def trigger():
+ events.append('trigger')
+ self.event.addTrigger('before', trigger)
+ self.event.addTrigger(
+ 'before', lambda: self.event.removeTrigger(duringTrigger))
+ duringTrigger = self.event.addTrigger('during', trigger)
+ self.event.fireEvent()
+ self.assertEqual(events, ['trigger'])
+
+
+ def test_synchronousRemovePendingDuring(self):
+ """
+ If a during-phase trigger removes another during-phase trigger which
+ has not yet run, the removed trigger should not be run.
+ """
+ events = []
+ self.event.addTrigger(
+ 'during', lambda: self.event.removeTrigger(duringHandle))
+ duringHandle = self.event.addTrigger(
+ 'during', events.append, ('first', 'during'))
+ self.event.addTrigger(
+ 'during', events.append, ('second', 'during'))
+ self.event.fireEvent()
+ self.assertEqual(events, [('second', 'during')])
+
+
+ def test_triggersRunOnce(self):
+ """
+ A trigger should only be called on the first call to
+ L{_ThreePhaseEvent.fireEvent}.
+ """
+ events = []
+ self.event.addTrigger('before', events.append, 'before')
+ self.event.addTrigger('during', events.append, 'during')
+ self.event.addTrigger('after', events.append, 'after')
+ self.event.fireEvent()
+ self.event.fireEvent()
+ self.assertEqual(events, ['before', 'during', 'after'])
+
+
+ def test_finishedBeforeTriggersCleared(self):
+ """
+ The temporary list L{_ThreePhaseEvent.finishedBefore} should be emptied
+ and the state reset to C{'BASE'} before the first during-phase trigger
+ executes.
+ """
+ events = []
+ def duringTrigger():
+ events.append('during')
+ self.assertEqual(self.event.finishedBefore, [])
+ self.assertEqual(self.event.state, 'BASE')
+ self.event.addTrigger('before', events.append, 'before')
+ self.event.addTrigger('during', duringTrigger)
+ self.event.fireEvent()
+ self.assertEqual(events, ['before', 'during'])
+
+
+
+class SystemEventTestCase(unittest.TestCase):
+ """
+ Tests for the reactor's implementation of the C{fireSystemEvent},
+ C{addSystemEventTrigger}, and C{removeSystemEventTrigger} methods of the
+ L{IReactorCore} interface.
+
+ @ivar triggers: A list of the handles to triggers which have been added to
+ the reactor.
+ """
+ def setUp(self):
+ """
+ Create an empty list in which to store trigger handles.
+ """
+ self.triggers = []
+
+
+ def tearDown(self):
+ """
+ Remove all remaining triggers from the reactor.
+ """
+ while self.triggers:
+ trigger = self.triggers.pop()
+ try:
+ reactor.removeSystemEventTrigger(trigger)
+ except (ValueError, KeyError):
+ pass
+
+
+ def addTrigger(self, event, phase, func):
+ """
+ Add a trigger to the reactor and remember it in C{self.triggers}.
+ """
+ t = reactor.addSystemEventTrigger(event, phase, func)
+ self.triggers.append(t)
+ return t
+
+
+ def removeTrigger(self, trigger):
+ """
+ Remove a trigger by its handle from the reactor and from
+ C{self.triggers}.
+ """
+ reactor.removeSystemEventTrigger(trigger)
+ self.triggers.remove(trigger)
+
+
+ def _addSystemEventTriggerTest(self, phase):
+ eventType = 'test'
+ events = []
+ def trigger():
+ events.append(None)
+ self.addTrigger(phase, eventType, trigger)
+ self.assertEqual(events, [])
+ reactor.fireSystemEvent(eventType)
+ self.assertEqual(events, [None])
+
+
+ def test_beforePhase(self):
+ """
+ L{IReactorCore.addSystemEventTrigger} should accept the C{'before'}
+ phase and not call the given object until the right event is fired.
+ """
+ self._addSystemEventTriggerTest('before')
+
+
+ def test_duringPhase(self):
+ """
+ L{IReactorCore.addSystemEventTrigger} should accept the C{'during'}
+ phase and not call the given object until the right event is fired.
+ """
+ self._addSystemEventTriggerTest('during')
+
+
+ def test_afterPhase(self):
+ """
+ L{IReactorCore.addSystemEventTrigger} should accept the C{'after'}
+ phase and not call the given object until the right event is fired.
+ """
+ self._addSystemEventTriggerTest('after')
+
+
+ def test_unknownPhase(self):
+ """
+ L{IReactorCore.addSystemEventTrigger} should reject phases other than
+ C{'before'}, C{'during'}, or C{'after'}.
+ """
+ eventType = 'test'
+ self.assertRaises(
+ KeyError, self.addTrigger, 'xxx', eventType, lambda: None)
+
+
+ def test_beforePreceedsDuring(self):
+ """
+ L{IReactorCore.addSystemEventTrigger} should call triggers added to the
+ C{'before'} phase before it calls triggers added to the C{'during'}
+ phase.
+ """
+ eventType = 'test'
+ events = []
+ def beforeTrigger():
+ events.append('before')
+ def duringTrigger():
+ events.append('during')
+ self.addTrigger('before', eventType, beforeTrigger)
+ self.addTrigger('during', eventType, duringTrigger)
+ self.assertEqual(events, [])
+ reactor.fireSystemEvent(eventType)
+ self.assertEqual(events, ['before', 'during'])
+
+
+ def test_duringPreceedsAfter(self):
+ """
+ L{IReactorCore.addSystemEventTrigger} should call triggers added to the
+ C{'during'} phase before it calls triggers added to the C{'after'}
+ phase.
+ """
+ eventType = 'test'
+ events = []
+ def duringTrigger():
+ events.append('during')
+ def afterTrigger():
+ events.append('after')
+ self.addTrigger('during', eventType, duringTrigger)
+ self.addTrigger('after', eventType, afterTrigger)
+ self.assertEqual(events, [])
+ reactor.fireSystemEvent(eventType)
+ self.assertEqual(events, ['during', 'after'])
+
+
+ def test_beforeReturnsDeferred(self):
+ """
+ If a trigger added to the C{'before'} phase of an event returns a
+ L{Deferred}, the C{'during'} phase should be delayed until it is called
+ back.
+ """
+ triggerDeferred = Deferred()
+ eventType = 'test'
+ events = []
+ def beforeTrigger():
+ return triggerDeferred
+ def duringTrigger():
+ events.append('during')
+ self.addTrigger('before', eventType, beforeTrigger)
+ self.addTrigger('during', eventType, duringTrigger)
+ self.assertEqual(events, [])
+ reactor.fireSystemEvent(eventType)
+ self.assertEqual(events, [])
+ triggerDeferred.callback(None)
+ self.assertEqual(events, ['during'])
+
+
+ def test_multipleBeforeReturnDeferred(self):
+ """
+ If more than one trigger added to the C{'before'} phase of an event
+ return L{Deferred}s, the C{'during'} phase should be delayed until they
+ are all called back.
+ """
+ firstDeferred = Deferred()
+ secondDeferred = Deferred()
+ eventType = 'test'
+ events = []
+ def firstBeforeTrigger():
+ return firstDeferred
+ def secondBeforeTrigger():
+ return secondDeferred
+ def duringTrigger():
+ events.append('during')
+ self.addTrigger('before', eventType, firstBeforeTrigger)
+ self.addTrigger('before', eventType, secondBeforeTrigger)
+ self.addTrigger('during', eventType, duringTrigger)
+ self.assertEqual(events, [])
+ reactor.fireSystemEvent(eventType)
+ self.assertEqual(events, [])
+ firstDeferred.callback(None)
+ self.assertEqual(events, [])
+ secondDeferred.callback(None)
+ self.assertEqual(events, ['during'])
+
+
+ def test_subsequentBeforeTriggerFiresPriorBeforeDeferred(self):
+ """
+ If a trigger added to the C{'before'} phase of an event calls back a
+ L{Deferred} returned by an earlier trigger in the C{'before'} phase of
+ the same event, the remaining C{'before'} triggers for that event
+ should be run and any further L{Deferred}s waited on before proceeding
+ to the C{'during'} events.
+ """
+ eventType = 'test'
+ events = []
+ firstDeferred = Deferred()
+ secondDeferred = Deferred()
+ def firstBeforeTrigger():
+ return firstDeferred
+ def secondBeforeTrigger():
+ firstDeferred.callback(None)
+ def thirdBeforeTrigger():
+ events.append('before')
+ return secondDeferred
+ def duringTrigger():
+ events.append('during')
+ self.addTrigger('before', eventType, firstBeforeTrigger)
+ self.addTrigger('before', eventType, secondBeforeTrigger)
+ self.addTrigger('before', eventType, thirdBeforeTrigger)
+ self.addTrigger('during', eventType, duringTrigger)
+ self.assertEqual(events, [])
+ reactor.fireSystemEvent(eventType)
+ self.assertEqual(events, ['before'])
+ secondDeferred.callback(None)
+ self.assertEqual(events, ['before', 'during'])
+
+
+ def test_removeSystemEventTrigger(self):
+ """
+ A trigger removed with L{IReactorCore.removeSystemEventTrigger} should
+ not be called when the event fires.
+ """
+ eventType = 'test'
+ events = []
+ def firstBeforeTrigger():
+ events.append('first')
+ def secondBeforeTrigger():
+ events.append('second')
+ self.addTrigger('before', eventType, firstBeforeTrigger)
+ self.removeTrigger(
+ self.addTrigger('before', eventType, secondBeforeTrigger))
+ self.assertEqual(events, [])
+ reactor.fireSystemEvent(eventType)
+ self.assertEqual(events, ['first'])
+
+
+ def test_removeNonExistentSystemEventTrigger(self):
+ """
+ Passing an object to L{IReactorCore.removeSystemEventTrigger} which was
+ not returned by a previous call to
+ L{IReactorCore.addSystemEventTrigger} or which has already been passed
+ to C{removeSystemEventTrigger} should result in L{TypeError},
+ L{KeyError}, or L{ValueError} being raised.
+ """
+ b = self.addTrigger('during', 'test', lambda: None)
+ self.removeTrigger(b)
+ self.assertRaises(
+ TypeError, reactor.removeSystemEventTrigger, None)
+ self.assertRaises(
+ ValueError, reactor.removeSystemEventTrigger, b)
+ self.assertRaises(
+ KeyError,
+ reactor.removeSystemEventTrigger,
+ (b[0], ('xxx',) + b[1][1:]))
+
+
+ def test_interactionBetweenDifferentEvents(self):
+ """
+ L{IReactorCore.fireSystemEvent} should behave the same way for a
+ particular system event regardless of whether Deferreds are being
+ waited on for a different system event.
+ """
+ events = []
+
+ firstEvent = 'first-event'
+ firstDeferred = Deferred()
+ def beforeFirstEvent():
+ events.append(('before', 'first'))
+ return firstDeferred
+ def afterFirstEvent():
+ events.append(('after', 'first'))
+
+ secondEvent = 'second-event'
+ secondDeferred = Deferred()
+ def beforeSecondEvent():
+ events.append(('before', 'second'))
+ return secondDeferred
+ def afterSecondEvent():
+ events.append(('after', 'second'))
+
+ self.addTrigger('before', firstEvent, beforeFirstEvent)
+ self.addTrigger('after', firstEvent, afterFirstEvent)
+ self.addTrigger('before', secondEvent, beforeSecondEvent)
+ self.addTrigger('after', secondEvent, afterSecondEvent)
+
+ self.assertEqual(events, [])
+
+ # After this, firstEvent should be stuck before 'during' waiting for
+ # firstDeferred.
+ reactor.fireSystemEvent(firstEvent)
+ self.assertEqual(events, [('before', 'first')])
+
+ # After this, secondEvent should be stuck before 'during' waiting for
+ # secondDeferred.
+ reactor.fireSystemEvent(secondEvent)
+ self.assertEqual(events, [('before', 'first'), ('before', 'second')])
+
+ # After this, firstEvent should have finished completely, but
+ # secondEvent should be at the same place.
+ firstDeferred.callback(None)
+ self.assertEqual(events, [('before', 'first'), ('before', 'second'),
+ ('after', 'first')])
+
+ # After this, secondEvent should have finished completely.
+ secondDeferred.callback(None)
+ self.assertEqual(events, [('before', 'first'), ('before', 'second'),
+ ('after', 'first'), ('after', 'second')])
+
+
+
+class TimeTestCase(unittest.TestCase):
+ """
+ Tests for the IReactorTime part of the reactor.
+ """
+
+
+ def test_seconds(self):
+ """
+ L{twisted.internet.reactor.seconds} should return something
+ like a number.
+
+ 1. This test specifically does not assert any relation to the
+ "system time" as returned by L{time.time} or
+ L{twisted.python.runtime.seconds}, because at some point we
+ may find a better option for scheduling calls than
+ wallclock-time.
+ 2. This test *also* does not assert anything about the type of
+ the result, because operations may not return ints or
+ floats: For example, datetime-datetime == timedelta(0).
+ """
+ now = reactor.seconds()
+ self.assertEquals(now-now+now, now)
+
+
+ def test_callLaterUsesReactorSecondsInDelayedCall(self):
+ """
+ L{reactor.callLater} should use the reactor's seconds factory
+ to produce the time at which the DelayedCall will be called.
+ """
+ oseconds = reactor.seconds
+ reactor.seconds = lambda: 100
+ try:
+ call = reactor.callLater(5, lambda: None)
+ self.assertEquals(call.getTime(), 105)
+ finally:
+ reactor.seconds = oseconds
+
+
+ def test_callLaterUsesReactorSecondsAsDelayedCallSecondsFactory(self):
+ """
+ L{reactor.callLater} should propagate its own seconds factory
+ to the DelayedCall to use as its own seconds factory.
+ """
+ oseconds = reactor.seconds
+ reactor.seconds = lambda: 100
+ try:
+ call = reactor.callLater(5, lambda: None)
+ self.assertEquals(call.seconds(), 100)
+ finally:
+ reactor.seconds = oseconds
+
+
+ def test_callLater(self):
+ """
+ Test that a DelayedCall really calls the function it is
+ supposed to call.
+ """
+ d = Deferred()
+ reactor.callLater(0, d.callback, None)
+ d.addCallback(self.assertEqual, None)
+ return d
+
+
+ def test_cancelDelayedCall(self):
+ """
+ Test that when a DelayedCall is cancelled it does not run.
+ """
+ called = []
+ def function():
+ called.append(None)
+ call = reactor.callLater(0, function)
+ call.cancel()
+
+ # Schedule a call in two "iterations" to check to make sure that the
+ # above call never ran.
+ d = Deferred()
+ def check():
+ try:
+ self.assertEqual(called, [])
+ except:
+ d.errback()
+ else:
+ d.callback(None)
+ reactor.callLater(0, reactor.callLater, 0, check)
+ return d
+
+
+ def test_cancelCancelledDelayedCall(self):
+ """
+ Test that cancelling a DelayedCall which has already been cancelled
+ raises the appropriate exception.
+ """
+ call = reactor.callLater(0, lambda: None)
+ call.cancel()
+ self.assertRaises(error.AlreadyCancelled, call.cancel)
+
+
+ def test_cancelCalledDelayedCallSynchronous(self):
+ """
+ Test that cancelling a DelayedCall in the DelayedCall's function as
+ that function is being invoked by the DelayedCall raises the
+ appropriate exception.
+ """
+ d = Deferred()
+ def later():
+ try:
+ self.assertRaises(error.AlreadyCalled, call.cancel)
+ except:
+ d.errback()
+ else:
+ d.callback(None)
+ call = reactor.callLater(0, later)
+ return d
+
+
+ def test_cancelCalledDelayedCallAsynchronous(self):
+ """
+ Test that cancelling a DelayedCall after it has run its function
+ raises the appropriate exception.
+ """
+ d = Deferred()
+ def check():
+ try:
+ self.assertRaises(error.AlreadyCalled, call.cancel)
+ except:
+ d.errback()
+ else:
+ d.callback(None)
+ def later():
+ reactor.callLater(0, check)
+ call = reactor.callLater(0, later)
+ return d
+
+
+ def testCallLaterTime(self):
+ d = reactor.callLater(10, lambda: None)
+ try:
+ self.failUnless(d.getTime() - (time.time() + 10) < 1)
+ finally:
+ d.cancel()
+
+ def testCallLaterOrder(self):
+ l = []
+ l2 = []
+ def f(x):
+ l.append(x)
+ def f2(x):
+ l2.append(x)
+ def done():
+ self.assertEquals(l, range(20))
+ def done2():
+ self.assertEquals(l2, range(10))
+
+ for n in range(10):
+ reactor.callLater(0, f, n)
+ for n in range(10):
+ reactor.callLater(0, f, n+10)
+ reactor.callLater(0.1, f2, n)
+
+ reactor.callLater(0, done)
+ reactor.callLater(0.1, done2)
+ d = Deferred()
+ reactor.callLater(0.2, d.callback, None)
+ return d
+
+ testCallLaterOrder.todo = "See bug 1396"
+ testCallLaterOrder.skip = "Trial bug, todo doesn't work! See bug 1397"
+ def testCallLaterOrder2(self):
+ # This time destroy the clock resolution so that it fails reliably
+ # even on systems that don't have a crappy clock resolution.
+
+ def seconds():
+ return int(time.time())
+
+ base_original = base.seconds
+ runtime_original = runtime.seconds
+ base.seconds = seconds
+ runtime.seconds = seconds
+
+ def cleanup(x):
+ runtime.seconds = runtime_original
+ base.seconds = base_original
+ return x
+ return maybeDeferred(self.testCallLaterOrder).addBoth(cleanup)
+
+ testCallLaterOrder2.todo = "See bug 1396"
+ testCallLaterOrder2.skip = "Trial bug, todo doesn't work! See bug 1397"
+
+ def testDelayedCallStringification(self):
+ # Mostly just make sure str() isn't going to raise anything for
+ # DelayedCalls within reason.
+ dc = reactor.callLater(0, lambda x, y: None, 'x', y=10)
+ str(dc)
+ dc.reset(5)
+ str(dc)
+ dc.cancel()
+ str(dc)
+
+ dc = reactor.callLater(0, lambda: None, x=[({'hello': u'world'}, 10j), reactor], *range(10))
+ str(dc)
+ dc.cancel()
+ str(dc)
+
+ def calledBack(ignored):
+ str(dc)
+ d = Deferred().addCallback(calledBack)
+ dc = reactor.callLater(0, d.callback, None)
+ str(dc)
+ return d
+
+
+ def testDelayedCallSecondsOverride(self):
+ """
+ Test that the C{seconds} argument to DelayedCall gets used instead of
+ the default timing function, if it is not None.
+ """
+ def seconds():
+ return 10
+ dc = base.DelayedCall(5, lambda: None, (), {}, lambda dc: None,
+ lambda dc: None, seconds)
+ self.assertEquals(dc.getTime(), 5)
+ dc.reset(3)
+ self.assertEquals(dc.getTime(), 13)
+
+
+class CallFromThreadTests(unittest.TestCase):
+ def testWakeUp(self):
+ # Make sure other threads can wake up the reactor
+ d = Deferred()
+ def wake():
+ time.sleep(0.1)
+ # callFromThread will call wakeUp for us
+ reactor.callFromThread(d.callback, None)
+ reactor.callInThread(wake)
+ return d
+
+ if interfaces.IReactorThreads(reactor, None) is None:
+ testWakeUp.skip = "Nothing to wake up for without thread support"
+
+ def _stopCallFromThreadCallback(self):
+ self.stopped = True
+
+ def _callFromThreadCallback(self, d):
+ reactor.callFromThread(self._callFromThreadCallback2, d)
+ reactor.callLater(0, self._stopCallFromThreadCallback)
+
+ def _callFromThreadCallback2(self, d):
+ try:
+ self.assert_(self.stopped)
+ except:
+ # Send the error to the deferred
+ d.errback()
+ else:
+ d.callback(None)
+
+ def testCallFromThreadStops(self):
+ """
+ Ensure that callFromThread from inside a callFromThread
+ callback doesn't sit in an infinite loop and lets other
+ things happen too.
+ """
+ self.stopped = False
+ d = defer.Deferred()
+ reactor.callFromThread(self._callFromThreadCallback, d)
+ return d
+
+
+class DelayedTestCase(unittest.TestCase):
+ def setUp(self):
+ self.finished = 0
+ self.counter = 0
+ self.timers = {}
+ self.deferred = defer.Deferred()
+
+ def tearDown(self):
+ for t in self.timers.values():
+ t.cancel()
+
+ def checkTimers(self):
+ l1 = self.timers.values()
+ l2 = list(reactor.getDelayedCalls())
+
+ # There should be at least the calls we put in. There may be other
+ # calls that are none of our business and that we should ignore,
+ # though.
+
+ missing = []
+ for dc in l1:
+ if dc not in l2:
+ missing.append(dc)
+ if missing:
+ self.finished = 1
+ self.failIf(missing, "Should have been missing no calls, instead was missing " + repr(missing))
+
+ def callback(self, tag):
+ del self.timers[tag]
+ self.checkTimers()
+
+ def addCallback(self, tag):
+ self.callback(tag)
+ self.addTimer(15, self.callback)
+
+ def done(self, tag):
+ self.finished = 1
+ self.callback(tag)
+ self.deferred.callback(None)
+
+ def addTimer(self, when, callback):
+ self.timers[self.counter] = reactor.callLater(when * 0.01, callback,
+ self.counter)
+ self.counter += 1
+ self.checkTimers()
+
+ def testGetDelayedCalls(self):
+ if not hasattr(reactor, "getDelayedCalls"):
+ return
+ # This is not a race because we don't do anything which might call
+ # the reactor until we have all the timers set up. If we did, this
+ # test might fail on slow systems.
+ self.checkTimers()
+ self.addTimer(35, self.done)
+ self.addTimer(20, self.callback)
+ self.addTimer(30, self.callback)
+ which = self.counter
+ self.addTimer(29, self.callback)
+ self.addTimer(25, self.addCallback)
+ self.addTimer(26, self.callback)
+
+ self.timers[which].cancel()
+ del self.timers[which]
+ self.checkTimers()
+
+ self.deferred.addCallback(lambda x : self.checkTimers())
+ return self.deferred
+
+
+ def test_active(self):
+ """
+ L{IDelayedCall.active} returns False once the call has run.
+ """
+ dcall = reactor.callLater(0.01, self.deferred.callback, True)
+ self.assertEquals(dcall.active(), True)
+
+ def checkDeferredCall(success):
+ self.assertEquals(dcall.active(), False)
+ return success
+
+ self.deferred.addCallback(checkDeferredCall)
+
+ return self.deferred
+
+
+
+resolve_helper = """
+import %(reactor)s
+%(reactor)s.install()
+from twisted.internet import reactor
+
+class Foo:
+ def __init__(self):
+ reactor.callWhenRunning(self.start)
+ self.timer = reactor.callLater(3, self.failed)
+ def start(self):
+ reactor.resolve('localhost').addBoth(self.done)
+ def done(self, res):
+ print 'done', res
+ reactor.stop()
+ def failed(self):
+ print 'failed'
+ self.timer = None
+ reactor.stop()
+f = Foo()
+reactor.run()
+"""
+
+class ChildResolveProtocol(protocol.ProcessProtocol):
+ def __init__(self, onCompletion):
+ self.onCompletion = onCompletion
+
+ def connectionMade(self):
+ self.output = []
+ self.error = []
+
+ def outReceived(self, out):
+ self.output.append(out)
+
+ def errReceived(self, err):
+ self.error.append(err)
+
+ def processEnded(self, reason):
+ self.onCompletion.callback((reason, self.output, self.error))
+ self.onCompletion = None
+
+
+class Resolve(unittest.TestCase):
+ def testChildResolve(self):
+ # I've seen problems with reactor.run under gtk2reactor. Spawn a
+ # child which just does reactor.resolve after the reactor has
+ # started, fail if it does not complete in a timely fashion.
+ helperPath = os.path.abspath(self.mktemp())
+ helperFile = open(helperPath, 'w')
+
+ # Eeueuuggg
+ reactorName = reactor.__module__
+
+ helperFile.write(resolve_helper % {'reactor': reactorName})
+ helperFile.close()
+
+ env = os.environ.copy()
+ env['PYTHONPATH'] = os.pathsep.join(sys.path)
+
+ helperDeferred = Deferred()
+ helperProto = ChildResolveProtocol(helperDeferred)
+
+ reactor.spawnProcess(helperProto, sys.executable, ("python", "-u", helperPath), env)
+
+ def cbFinished((reason, output, error)):
+ # If the output is "done 127.0.0.1\n" we don't really care what
+ # else happened.
+ output = ''.join(output)
+ if output != 'done 127.0.0.1\n':
+ self.fail((
+ "The child process failed to produce the desired results:\n"
+ " Reason for termination was: %r\n"
+ " Output stream was: %r\n"
+ " Error stream was: %r\n") % (reason.getErrorMessage(), output, ''.join(error)))
+
+ helperDeferred.addCallback(cbFinished)
+ return helperDeferred
+
+if not interfaces.IReactorProcess(reactor, None):
+ Resolve.skip = "cannot run test: reactor doesn't support IReactorProcess"
+
+
+
+class CallFromThreadTestCase(unittest.TestCase):
+ """
+ Task scheduling from threads tests.
+ """
+ if interfaces.IReactorThreads(reactor, None) is None:
+ skip = "Nothing to test without thread support"
+
+ def setUp(self):
+ self.counter = 0
+ self.deferred = Deferred()
+
+
+ def schedule(self, *args, **kwargs):
+ """
+ Override in subclasses.
+ """
+ reactor.callFromThread(*args, **kwargs)
+
+
+ def test_lotsOfThreadsAreScheduledCorrectly(self):
+ """
+ L{IReactorThreads.callFromThread} can be used to schedule a large
+ number of calls in the reactor thread.
+ """
+ def addAndMaybeFinish():
+ self.counter += 1
+ if self.counter == 100:
+ self.deferred.callback(True)
+
+ for i in xrange(100):
+ self.schedule(addAndMaybeFinish)
+
+ return self.deferred
+
+
+ def test_threadsAreRunInScheduledOrder(self):
+ """
+ Callbacks should be invoked in the order they were scheduled.
+ """
+ order = []
+
+ def check(_):
+ self.assertEquals(order, [1, 2, 3])
+
+ self.deferred.addCallback(check)
+ self.schedule(order.append, 1)
+ self.schedule(order.append, 2)
+ self.schedule(order.append, 3)
+ self.schedule(reactor.callFromThread, self.deferred.callback, None)
+
+ return self.deferred
+
+
+ def test_scheduledThreadsNotRunUntilReactorRuns(self):
+ """
+ Scheduled tasks should not be run until the reactor starts running.
+ """
+ def incAndFinish():
+ self.counter = 1
+ self.deferred.callback(True)
+ self.schedule(incAndFinish)
+
+ # Callback shouldn't have fired yet.
+ self.assertEquals(self.counter, 0)
+
+ return self.deferred
+
+
+
+class MyProtocol(protocol.Protocol):
+ """
+ Sample protocol.
+ """
+
+class MyFactory(protocol.Factory):
+ """
+ Sample factory.
+ """
+
+ protocol = MyProtocol
+
+
+class ProtocolTestCase(unittest.TestCase):
+
+ def testFactory(self):
+ factory = MyFactory()
+ protocol = factory.buildProtocol(None)
+ self.assertEquals(protocol.factory, factory)
+ self.assert_( isinstance(protocol, factory.protocol) )
+
+
+class DummyProducer(object):
+ """
+ Very uninteresting producer implementation used by tests to ensure the
+ right methods are called by the consumer with which it is registered.
+
+ @type events: C{list} of C{str}
+ @ivar events: The producer/consumer related events which have happened to
+ this producer. Strings in this list may be C{'resume'}, C{'stop'}, or
+ C{'pause'}. Elements are added as they occur.
+ """
+
+ def __init__(self):
+ self.events = []
+
+
+ def resumeProducing(self):
+ self.events.append('resume')
+
+
+ def stopProducing(self):
+ self.events.append('stop')
+
+
+ def pauseProducing(self):
+ self.events.append('pause')
+
+
+
+class SillyDescriptor(abstract.FileDescriptor):
+ """
+ A descriptor whose data buffer gets filled very fast.
+
+ Useful for testing FileDescriptor's IConsumer interface, since
+ the data buffer fills as soon as at least four characters are
+ written to it, and gets emptied in a single doWrite() cycle.
+ """
+ bufferSize = 3
+ connected = True
+
+ def writeSomeData(self, data):
+ """
+ Always write all data.
+ """
+ return len(data)
+
+
+ def startWriting(self):
+ """
+ Do nothing: bypass the reactor.
+ """
+ stopWriting = startWriting
+
+
+
+class ReentrantProducer(DummyProducer):
+ """
+ Similar to L{DummyProducer}, but with a resumeProducing method which calls
+ back into an L{IConsumer} method of the consumer against which it is
+ registered.
+
+ @ivar consumer: The consumer with which this producer has been or will
+ be registered.
+
+ @ivar methodName: The name of the method to call on the consumer inside
+ C{resumeProducing}.
+
+ @ivar methodArgs: The arguments to pass to the consumer method invoked in
+ C{resumeProducing}.
+ """
+ def __init__(self, consumer, methodName, *methodArgs):
+ super(ReentrantProducer, self).__init__()
+ self.consumer = consumer
+ self.methodName = methodName
+ self.methodArgs = methodArgs
+
+
+ def resumeProducing(self):
+ super(ReentrantProducer, self).resumeProducing()
+ getattr(self.consumer, self.methodName)(*self.methodArgs)
+
+
+
+class TestProducer(unittest.TestCase):
+ """
+ Test abstract.FileDescriptor's consumer interface.
+ """
+ def test_doubleProducer(self):
+ """
+ Verify that registering a non-streaming producer invokes its
+ resumeProducing() method and that you can only register one producer
+ at a time.
+ """
+ fd = abstract.FileDescriptor()
+ fd.connected = 1
+ dp = DummyProducer()
+ fd.registerProducer(dp, 0)
+ self.assertEquals(dp.events, ['resume'])
+ self.assertRaises(RuntimeError, fd.registerProducer, DummyProducer(), 0)
+
+
+ def test_unconnectedFileDescriptor(self):
+ """
+ Verify that registering a producer when the connection has already
+ been closed invokes its stopProducing() method.
+ """
+ fd = abstract.FileDescriptor()
+ fd.disconnected = 1
+ dp = DummyProducer()
+ fd.registerProducer(dp, 0)
+ self.assertEquals(dp.events, ['stop'])
+
+
+ def _dontPausePullConsumerTest(self, methodName):
+ descriptor = SillyDescriptor()
+ producer = DummyProducer()
+ descriptor.registerProducer(producer, streaming=False)
+ self.assertEqual(producer.events, ['resume'])
+ del producer.events[:]
+
+ # Fill up the descriptor's write buffer so we can observe whether or
+ # not it pauses its producer in that case.
+ getattr(descriptor, methodName)('1234')
+
+ self.assertEqual(producer.events, [])
+
+
+ def test_dontPausePullConsumerOnWrite(self):
+ """
+ Verify that FileDescriptor does not call producer.pauseProducing() on a
+ non-streaming pull producer in response to a L{IConsumer.write} call
+ which results in a full write buffer. Issue #2286.
+ """
+ return self._dontPausePullConsumerTest('write')
+
+
+ def test_dontPausePullConsumerOnWriteSequence(self):
+ """
+ Like L{test_dontPausePullConsumerOnWrite}, but for a call to
+ C{writeSequence} rather than L{IConsumer.write}.
+
+ C{writeSequence} is not part of L{IConsumer}, but
+ L{abstract.FileDescriptor} has supported consumery behavior in response
+ to calls to L{writeSequence} forever.
+ """
+ return self._dontPausePullConsumerTest('writeSequence')
+
+
+ def _reentrantStreamingProducerTest(self, methodName):
+ descriptor = SillyDescriptor()
+ producer = ReentrantProducer(descriptor, methodName, 'spam')
+ descriptor.registerProducer(producer, streaming=True)
+
+ # Start things off by filling up the descriptor's buffer so it will
+ # pause its producer.
+ getattr(descriptor, methodName)('spam')
+
+ # Sanity check - make sure that worked.
+ self.assertEqual(producer.events, ['pause'])
+ del producer.events[:]
+
+ # After one call to doWrite, the buffer has been emptied so the
+ # FileDescriptor should resume its producer. That will result in an
+ # immediate call to FileDescriptor.write which will again fill the
+ # buffer and result in the producer being paused.
+ descriptor.doWrite()
+ self.assertEqual(producer.events, ['resume', 'pause'])
+ del producer.events[:]
+
+ # After a second call to doWrite, the exact same thing should have
+ # happened. Prior to the bugfix for which this test was written,
+ # FileDescriptor would have incorrectly believed its producer was
+ # already resumed (it was paused) and so not resume it again.
+ descriptor.doWrite()
+ self.assertEqual(producer.events, ['resume', 'pause'])
+
+
+ def test_reentrantStreamingProducerUsingWrite(self):
+ """
+ Verify that FileDescriptor tracks producer's paused state correctly.
+ Issue #811, fixed in revision r12857.
+ """
+ return self._reentrantStreamingProducerTest('write')
+
+
+ def test_reentrantStreamingProducerUsingWriteSequence(self):
+ """
+ Like L{test_reentrantStreamingProducerUsingWrite}, but for calls to
+ C{writeSequence}.
+
+ C{writeSequence} is B{not} part of L{IConsumer}, however
+ C{abstract.FileDescriptor} has supported consumery behavior in response
+ to calls to C{writeSequence} forever.
+ """
+ return self._reentrantStreamingProducerTest('writeSequence')
+
+
+
+class PortStringification(unittest.TestCase):
+ if interfaces.IReactorTCP(reactor, None) is not None:
+ def testTCP(self):
+ p = reactor.listenTCP(0, protocol.ServerFactory())
+ portNo = p.getHost().port
+ self.assertNotEqual(str(p).find(str(portNo)), -1,
+ "%d not found in %s" % (portNo, p))
+ return p.stopListening()
+
+ if interfaces.IReactorUDP(reactor, None) is not None:
+ def testUDP(self):
+ p = reactor.listenUDP(0, protocol.DatagramProtocol())
+ portNo = p.getHost().port
+ self.assertNotEqual(str(p).find(str(portNo)), -1,
+ "%d not found in %s" % (portNo, p))
+ return p.stopListening()
+
+ if interfaces.IReactorSSL(reactor, None) is not None and ssl:
+ def testSSL(self, ssl=ssl):
+ pem = util.sibpath(__file__, 'server.pem')
+ p = reactor.listenSSL(0, protocol.ServerFactory(), ssl.DefaultOpenSSLContextFactory(pem, pem))
+ portNo = p.getHost().port
+ self.assertNotEqual(str(p).find(str(portNo)), -1,
+ "%d not found in %s" % (portNo, p))
+ return p.stopListening()
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_iutils.py b/vendor/Twisted-10.0.0/twisted/test/test_iutils.py
new file mode 100644
index 0000000000..c5fd01d9ad
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_iutils.py
@@ -0,0 +1,296 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test running processes with the APIs in L{twisted.internet.utils}.
+"""
+
+import warnings, os, stat, sys, signal
+
+from twisted.python.runtime import platform
+from twisted.trial import unittest
+from twisted.internet import error, reactor, utils, interfaces
+
+
+class ProcessUtilsTests(unittest.TestCase):
+ """
+ Test running a process using L{getProcessOutput}, L{getProcessValue}, and
+ L{getProcessOutputAndValue}.
+ """
+
+ if interfaces.IReactorProcess(reactor, None) is None:
+ skip = "reactor doesn't implement IReactorProcess"
+
+ output = None
+ value = None
+ exe = sys.executable
+
+ def makeSourceFile(self, sourceLines):
+ """
+ Write the given list of lines to a text file and return the absolute
+ path to it.
+ """
+ script = self.mktemp()
+ scriptFile = file(script, 'wt')
+ scriptFile.write(os.linesep.join(sourceLines) + os.linesep)
+ scriptFile.close()
+ return os.path.abspath(script)
+
+
+ def test_output(self):
+ """
+ L{getProcessOutput} returns a L{Deferred} which fires with the complete
+ output of the process it runs after that process exits.
+ """
+ scriptFile = self.makeSourceFile([
+ "import sys",
+ "for s in 'hello world\\n':",
+ " sys.stdout.write(s)",
+ " sys.stdout.flush()"])
+ d = utils.getProcessOutput(self.exe, ['-u', scriptFile])
+ return d.addCallback(self.assertEquals, "hello world\n")
+
+
+ def test_outputWithErrorIgnored(self):
+ """
+ The L{Deferred} returned by L{getProcessOutput} is fired with an
+ L{IOError} L{Failure} if the child process writes to stderr.
+ """
+ # make sure stderr raises an error normally
+ scriptFile = self.makeSourceFile([
+ 'import sys',
+ 'sys.stderr.write("hello world\\n")'
+ ])
+
+ d = utils.getProcessOutput(self.exe, ['-u', scriptFile])
+ d = self.assertFailure(d, IOError)
+ def cbFailed(err):
+ return self.assertFailure(err.processEnded, error.ProcessDone)
+ d.addCallback(cbFailed)
+ return d
+
+
+ def test_outputWithErrorCollected(self):
+ """
+ If a C{True} value is supplied for the C{errortoo} parameter to
+ L{getProcessOutput}, the returned L{Deferred} fires with the child's
+ stderr output as well as its stdout output.
+ """
+ scriptFile = self.makeSourceFile([
+ 'import sys',
+ # Write the same value to both because ordering isn't guaranteed so
+ # this simplifies the test.
+ 'sys.stdout.write("foo")',
+ 'sys.stdout.flush()',
+ 'sys.stderr.write("foo")',
+ 'sys.stderr.flush()'])
+
+ d = utils.getProcessOutput(self.exe, ['-u', scriptFile], errortoo=True)
+ return d.addCallback(self.assertEqual, "foofoo")
+
+
+ def test_value(self):
+ """
+ The L{Deferred} returned by L{getProcessValue} is fired with the exit
+ status of the child process.
+ """
+ scriptFile = self.makeSourceFile(["raise SystemExit(1)"])
+
+ d = utils.getProcessValue(self.exe, ['-u', scriptFile])
+ return d.addCallback(self.assertEqual, 1)
+
+
+ def test_outputAndValue(self):
+ """
+ The L{Deferred} returned by L{getProcessOutputAndValue} fires with a
+ three-tuple, the elements of which give the data written to the child's
+ stdout, the data written to the child's stderr, and the exit status of
+ the child.
+ """
+ exe = sys.executable
+ scriptFile = self.makeSourceFile([
+ "import sys",
+ "sys.stdout.write('hello world!\\n')",
+ "sys.stderr.write('goodbye world!\\n')",
+ "sys.exit(1)"
+ ])
+
+ def gotOutputAndValue((out, err, code)):
+ self.assertEquals(out, "hello world!\n")
+ self.assertEquals(err, "goodbye world!" + os.linesep)
+ self.assertEquals(code, 1)
+ d = utils.getProcessOutputAndValue(self.exe, ["-u", scriptFile])
+ return d.addCallback(gotOutputAndValue)
+
+
+ def test_outputSignal(self):
+ """
+ If the child process exits because of a signal, the L{Deferred}
+ returned by L{getProcessOutputAndValue} fires a L{Failure} of a tuple
+ containing the the child's stdout, stderr, and the signal which caused
+ it to exit.
+ """
+ # Use SIGKILL here because it's guaranteed to be delivered. Using
+ # SIGHUP might not work in, e.g., a buildbot slave run under the
+ # 'nohup' command.
+ scriptFile = self.makeSourceFile([
+ "import sys, os, signal",
+ "sys.stdout.write('stdout bytes\\n')",
+ "sys.stderr.write('stderr bytes\\n')",
+ "sys.stdout.flush()",
+ "sys.stderr.flush()",
+ "os.kill(os.getpid(), signal.SIGKILL)"])
+
+ def gotOutputAndValue((out, err, sig)):
+ self.assertEquals(out, "stdout bytes\n")
+ self.assertEquals(err, "stderr bytes\n")
+ self.assertEquals(sig, signal.SIGKILL)
+
+ d = utils.getProcessOutputAndValue(self.exe, ['-u', scriptFile])
+ d = self.assertFailure(d, tuple)
+ return d.addCallback(gotOutputAndValue)
+
+ if platform.isWindows():
+ test_outputSignal.skip = "Windows doesn't have real signals."
+
+
+ def _pathTest(self, utilFunc, check):
+ dir = os.path.abspath(self.mktemp())
+ os.makedirs(dir)
+ scriptFile = self.makeSourceFile([
+ "import os, sys",
+ "sys.stdout.write(os.getcwd())"])
+ d = utilFunc(self.exe, ['-u', scriptFile], path=dir)
+ d.addCallback(check, dir)
+ return d
+
+
+ def test_getProcessOutputPath(self):
+ """
+ L{getProcessOutput} runs the given command with the working directory
+ given by the C{path} parameter.
+ """
+ return self._pathTest(utils.getProcessOutput, self.assertEqual)
+
+
+ def test_getProcessValuePath(self):
+ """
+ L{getProcessValue} runs the given command with the working directory
+ given by the C{path} parameter.
+ """
+ def check(result, ignored):
+ self.assertEqual(result, 0)
+ return self._pathTest(utils.getProcessValue, check)
+
+
+ def test_getProcessOutputAndValuePath(self):
+ """
+ L{getProcessOutputAndValue} runs the given command with the working
+ directory given by the C{path} parameter.
+ """
+ def check((out, err, status), dir):
+ self.assertEqual(out, dir)
+ self.assertEqual(status, 0)
+ return self._pathTest(utils.getProcessOutputAndValue, check)
+
+
+ def _defaultPathTest(self, utilFunc, check):
+ # Make another directory to mess around with.
+ dir = os.path.abspath(self.mktemp())
+ os.makedirs(dir)
+
+ scriptFile = self.makeSourceFile([
+ "import os, sys, stat",
+ # Fix the permissions so we can report the working directory.
+ # On OS X (and maybe elsewhere), os.getcwd() fails with EACCES
+ # if +x is missing from the working directory.
+ "os.chmod(%r, stat.S_IXUSR)" % (dir,),
+ "sys.stdout.write(os.getcwd())"])
+
+ # Switch to it, but make sure we switch back
+ self.addCleanup(os.chdir, os.getcwd())
+ os.chdir(dir)
+
+ # Get rid of all its permissions, but make sure they get cleaned up
+ # later, because otherwise it might be hard to delete the trial
+ # temporary directory.
+ self.addCleanup(
+ os.chmod, dir, stat.S_IMODE(os.stat('.').st_mode))
+ os.chmod(dir, 0)
+
+ d = utilFunc(self.exe, ['-u', scriptFile])
+ d.addCallback(check, dir)
+ return d
+
+
+ def test_getProcessOutputDefaultPath(self):
+ """
+ If no value is supplied for the C{path} parameter, L{getProcessOutput}
+ runs the given command in the same working directory as the parent
+ process and succeeds even if the current working directory is not
+ accessible.
+ """
+ return self._defaultPathTest(utils.getProcessOutput, self.assertEqual)
+
+
+ def test_getProcessValueDefaultPath(self):
+ """
+ If no value is supplied for the C{path} parameter, L{getProcessValue}
+ runs the given command in the same working directory as the parent
+ process and succeeds even if the current working directory is not
+ accessible.
+ """
+ def check(result, ignored):
+ self.assertEqual(result, 0)
+ return self._defaultPathTest(utils.getProcessValue, check)
+
+
+ def test_getProcessOutputAndValueDefaultPath(self):
+ """
+ If no value is supplied for the C{path} parameter,
+ L{getProcessOutputAndValue} runs the given command in the same working
+ directory as the parent process and succeeds even if the current
+ working directory is not accessible.
+ """
+ def check((out, err, status), dir):
+ self.assertEqual(out, dir)
+ self.assertEqual(status, 0)
+ return self._defaultPathTest(
+ utils.getProcessOutputAndValue, check)
+
+
+
+class WarningSuppression(unittest.TestCase):
+ def setUp(self):
+ self.warnings = []
+ self.originalshow = warnings.showwarning
+ warnings.showwarning = self.showwarning
+
+
+ def tearDown(self):
+ warnings.showwarning = self.originalshow
+
+
+ def showwarning(self, *a, **kw):
+ self.warnings.append((a, kw))
+
+
+ def testSuppressWarnings(self):
+ def f(msg):
+ warnings.warn(msg)
+ g = utils.suppressWarnings(f, (('ignore',), dict(message="This is message")))
+
+ # Start off with a sanity check - calling the original function
+ # should emit the warning.
+ f("Sanity check message")
+ self.assertEquals(len(self.warnings), 1)
+
+ # Now that that's out of the way, call the wrapped function, and
+ # make sure no new warnings show up.
+ g("This is message")
+ self.assertEquals(len(self.warnings), 1)
+
+ # Finally, emit another warning which should not be ignored, and
+ # make sure it is not.
+ g("Unignored message")
+ self.assertEquals(len(self.warnings), 2)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_jelly.py b/vendor/Twisted-10.0.0/twisted/test/test_jelly.py
new file mode 100644
index 0000000000..6a49059530
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_jelly.py
@@ -0,0 +1,618 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for L{jelly} object serialization.
+"""
+
+import datetime
+
+try:
+ import decimal
+except ImportError:
+ decimal = None
+
+from twisted.spread import jelly, pb
+from twisted.python.compat import set, frozenset
+
+from twisted.trial import unittest
+
+
+
+class TestNode(object, jelly.Jellyable):
+ """
+ An object to test jellyfying of new style class instances.
+ """
+ classAttr = 4
+
+ def __init__(self, parent=None):
+ if parent:
+ self.id = parent.id + 1
+ parent.children.append(self)
+ else:
+ self.id = 1
+ self.parent = parent
+ self.children = []
+
+
+
+class A:
+ """
+ Dummy class.
+ """
+
+ def amethod(self):
+ """
+ Method tp be used in serialization tests.
+ """
+
+
+
+def afunc(self):
+ """
+ A dummy function to test function serialization.
+ """
+
+
+
+class B:
+ """
+ Dummy class.
+ """
+
+ def bmethod(self):
+ """
+ Method to be used in serialization tests.
+ """
+
+
+
+class C:
+ """
+ Dummy class.
+ """
+
+ def cmethod(self):
+ """
+ Method to be used in serialization tests.
+ """
+
+
+
+class D(object):
+ """
+ Dummy new-style class.
+ """
+
+
+
+class SimpleJellyTest:
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+ def isTheSameAs(self, other):
+ return self.__dict__ == other.__dict__
+
+
+
+class JellyTestCase(unittest.TestCase):
+ """
+ Testcases for L{jelly} module serialization.
+
+ @cvar decimalData: serialized version of decimal data, to be used in tests.
+ @type decimalData: C{list}
+ """
+
+ def _testSecurity(self, inputList, atom):
+ """
+ Helper test method to test security options for a type.
+
+ @param inputList: a sample input for the type.
+ @param inputList: C{list}
+
+ @param atom: atom identifier for the type.
+ @type atom: C{str}
+ """
+ c = jelly.jelly(inputList)
+ taster = jelly.SecurityOptions()
+ taster.allowBasicTypes()
+ # By default, it should succeed
+ jelly.unjelly(c, taster)
+ taster.allowedTypes.pop(atom)
+ # But it should raise an exception when disallowed
+ self.assertRaises(jelly.InsecureJelly, jelly.unjelly, c, taster)
+
+
+ def test_methodSelfIdentity(self):
+ a = A()
+ b = B()
+ a.bmethod = b.bmethod
+ b.a = a
+ im_ = jelly.unjelly(jelly.jelly(b)).a.bmethod
+ self.assertEquals(im_.im_class, im_.im_self.__class__)
+
+
+ def test_methodsNotSelfIdentity(self):
+ """
+ If a class change after an instance has been created, L{jelly.unjelly}
+ shoud raise a C{TypeError} when trying to unjelly the instance.
+ """
+ a = A()
+ b = B()
+ c = C()
+ a.bmethod = c.cmethod
+ b.a = a
+ savecmethod = C.cmethod
+ del C.cmethod
+ try:
+ self.assertRaises(TypeError, jelly.unjelly, jelly.jelly(b))
+ finally:
+ C.cmethod = savecmethod
+
+
+ def test_newStyle(self):
+ n = D()
+ n.x = 1
+ n2 = D()
+ n.n2 = n2
+ n.n3 = n2
+ c = jelly.jelly(n)
+ m = jelly.unjelly(c)
+ self.assertIsInstance(m, D)
+ self.assertIdentical(m.n2, m.n3)
+
+
+ def test_typeOldStyle(self):
+ """
+ Test that an old style class type can be jellied and unjellied
+ to the original type.
+ """
+ t = [C]
+ r = jelly.unjelly(jelly.jelly(t))
+ self.assertEquals(t, r)
+
+
+ def test_typeNewStyle(self):
+ """
+ Test that a new style class type can be jellied and unjellied
+ to the original type.
+ """
+ t = [D]
+ r = jelly.unjelly(jelly.jelly(t))
+ self.assertEquals(t, r)
+
+
+ def test_typeBuiltin(self):
+ """
+ Test that a builtin type can be jellied and unjellied to the original
+ type.
+ """
+ t = [str]
+ r = jelly.unjelly(jelly.jelly(t))
+ self.assertEquals(t, r)
+
+
+ def test_dateTime(self):
+ dtn = datetime.datetime.now()
+ dtd = datetime.datetime.now() - dtn
+ input = [dtn, dtd]
+ c = jelly.jelly(input)
+ output = jelly.unjelly(c)
+ self.assertEquals(input, output)
+ self.assertNotIdentical(input, output)
+
+
+ def test_decimal(self):
+ """
+ Jellying L{decimal.Decimal} instances and then unjellying the result
+ should produce objects which represent the values of the original
+ inputs.
+ """
+ inputList = [decimal.Decimal('9.95'),
+ decimal.Decimal(0),
+ decimal.Decimal(123456),
+ decimal.Decimal('-78.901')]
+ c = jelly.jelly(inputList)
+ output = jelly.unjelly(c)
+ self.assertEquals(inputList, output)
+ self.assertNotIdentical(inputList, output)
+
+
+ decimalData = ['list', ['decimal', 995, -2], ['decimal', 0, 0],
+ ['decimal', 123456, 0], ['decimal', -78901, -3]]
+
+
+ def test_decimalUnjelly(self):
+ """
+ Unjellying the s-expressions produced by jelly for L{decimal.Decimal}
+ instances should result in L{decimal.Decimal} instances with the values
+ represented by the s-expressions.
+
+ This test also verifies that C{self.decimalData} contains valid jellied
+ data. This is important since L{test_decimalMissing} re-uses
+ C{self.decimalData} and is expected to be unable to produce
+ L{decimal.Decimal} instances even though the s-expression correctly
+ represents a list of them.
+ """
+ expected = [decimal.Decimal('9.95'),
+ decimal.Decimal(0),
+ decimal.Decimal(123456),
+ decimal.Decimal('-78.901')]
+ output = jelly.unjelly(self.decimalData)
+ self.assertEquals(output, expected)
+
+
+ def test_decimalMissing(self):
+ """
+ If decimal is unavailable on the unjelly side, L{jelly.unjelly} should
+ gracefully return L{jelly.Unpersistable} objects.
+ """
+ self.patch(jelly, 'decimal', None)
+ output = jelly.unjelly(self.decimalData)
+ self.assertEquals(len(output), 4)
+ for i in range(4):
+ self.assertIsInstance(output[i], jelly.Unpersistable)
+ self.assertEquals(output[0].reason,
+ "Could not unpersist decimal: 9.95")
+ self.assertEquals(output[1].reason,
+ "Could not unpersist decimal: 0")
+ self.assertEquals(output[2].reason,
+ "Could not unpersist decimal: 123456")
+ self.assertEquals(output[3].reason,
+ "Could not unpersist decimal: -78.901")
+
+
+ def test_decimalSecurity(self):
+ """
+ By default, C{decimal} objects should be allowed by
+ L{jelly.SecurityOptions}. If not allowed, L{jelly.unjelly} should raise
+ L{jelly.InsecureJelly} when trying to unjelly it.
+ """
+ inputList = [decimal.Decimal('9.95')]
+ self._testSecurity(inputList, "decimal")
+
+ if decimal is None:
+ skipReason = "decimal not available"
+ test_decimal.skip = skipReason
+ test_decimalUnjelly.skip = skipReason
+ test_decimalSecurity.skip = skipReason
+
+
+ def test_set(self):
+ """
+ Jellying C{set} instances and then unjellying the result
+ should produce objects which represent the values of the original
+ inputs.
+ """
+ inputList = [set([1, 2, 3])]
+ output = jelly.unjelly(jelly.jelly(inputList))
+ self.assertEquals(inputList, output)
+ self.assertNotIdentical(inputList, output)
+
+
+ def test_frozenset(self):
+ """
+ Jellying C{frozenset} instances and then unjellying the result
+ should produce objects which represent the values of the original
+ inputs.
+ """
+ inputList = [frozenset([1, 2, 3])]
+ output = jelly.unjelly(jelly.jelly(inputList))
+ self.assertEquals(inputList, output)
+ self.assertNotIdentical(inputList, output)
+
+
+ def test_setSecurity(self):
+ """
+ By default, C{set} objects should be allowed by
+ L{jelly.SecurityOptions}. If not allowed, L{jelly.unjelly} should raise
+ L{jelly.InsecureJelly} when trying to unjelly it.
+ """
+ inputList = [set([1, 2, 3])]
+ self._testSecurity(inputList, "set")
+
+
+ def test_frozensetSecurity(self):
+ """
+ By default, C{frozenset} objects should be allowed by
+ L{jelly.SecurityOptions}. If not allowed, L{jelly.unjelly} should raise
+ L{jelly.InsecureJelly} when trying to unjelly it.
+ """
+ inputList = [frozenset([1, 2, 3])]
+ self._testSecurity(inputList, "frozenset")
+
+
+ def test_oldSets(self):
+ """
+ Test jellying C{sets.Set}: it should serialize to the same thing as
+ C{set} jelly, and be unjellied as C{set} if available.
+ """
+ inputList = [jelly._sets.Set([1, 2, 3])]
+ inputJelly = jelly.jelly(inputList)
+ self.assertEquals(inputJelly, jelly.jelly([set([1, 2, 3])]))
+ output = jelly.unjelly(inputJelly)
+ # Even if the class is different, it should coerce to the same list
+ self.assertEquals(list(inputList[0]), list(output[0]))
+ if set is jelly._sets.Set:
+ self.assertIsInstance(output[0], jelly._sets.Set)
+ else:
+ self.assertIsInstance(output[0], set)
+
+
+ def test_oldImmutableSets(self):
+ """
+ Test jellying C{sets.ImmutableSet}: it should serialize to the same
+ thing as C{frozenset} jelly, and be unjellied as C{frozenset} if
+ available.
+ """
+ inputList = [jelly._sets.ImmutableSet([1, 2, 3])]
+ inputJelly = jelly.jelly(inputList)
+ self.assertEquals(inputJelly, jelly.jelly([frozenset([1, 2, 3])]))
+ output = jelly.unjelly(inputJelly)
+ # Even if the class is different, it should coerce to the same list
+ self.assertEquals(list(inputList[0]), list(output[0]))
+ if frozenset is jelly._sets.ImmutableSet:
+ self.assertIsInstance(output[0], jelly._sets.ImmutableSet)
+ else:
+ self.assertIsInstance(output[0], frozenset)
+
+
+ def test_simple(self):
+ """
+ Simplest test case.
+ """
+ self.failUnless(SimpleJellyTest('a', 'b').isTheSameAs(
+ SimpleJellyTest('a', 'b')))
+ a = SimpleJellyTest(1, 2)
+ cereal = jelly.jelly(a)
+ b = jelly.unjelly(cereal)
+ self.failUnless(a.isTheSameAs(b))
+
+
+ def test_identity(self):
+ """
+ Test to make sure that objects retain identity properly.
+ """
+ x = []
+ y = (x)
+ x.append(y)
+ x.append(y)
+ self.assertIdentical(x[0], x[1])
+ self.assertIdentical(x[0][0], x)
+ s = jelly.jelly(x)
+ z = jelly.unjelly(s)
+ self.assertIdentical(z[0], z[1])
+ self.assertIdentical(z[0][0], z)
+
+
+ def test_unicode(self):
+ x = unicode('blah')
+ y = jelly.unjelly(jelly.jelly(x))
+ self.assertEquals(x, y)
+ self.assertEquals(type(x), type(y))
+
+
+ def test_stressReferences(self):
+ reref = []
+ toplevelTuple = ({'list': reref}, reref)
+ reref.append(toplevelTuple)
+ s = jelly.jelly(toplevelTuple)
+ z = jelly.unjelly(s)
+ self.assertIdentical(z[0]['list'], z[1])
+ self.assertIdentical(z[0]['list'][0], z)
+
+
+ def test_moreReferences(self):
+ a = []
+ t = (a,)
+ a.append((t,))
+ s = jelly.jelly(t)
+ z = jelly.unjelly(s)
+ self.assertIdentical(z[0][0][0], z)
+
+
+ def test_typeSecurity(self):
+ """
+ Test for type-level security of serialization.
+ """
+ taster = jelly.SecurityOptions()
+ dct = jelly.jelly({})
+ self.assertRaises(jelly.InsecureJelly, jelly.unjelly, dct, taster)
+
+
+ def test_newStyleClasses(self):
+ j = jelly.jelly(D)
+ uj = jelly.unjelly(D)
+ self.assertIdentical(D, uj)
+
+
+ def test_lotsaTypes(self):
+ """
+ Test for all types currently supported in jelly
+ """
+ a = A()
+ jelly.unjelly(jelly.jelly(a))
+ jelly.unjelly(jelly.jelly(a.amethod))
+ items = [afunc, [1, 2, 3], not bool(1), bool(1), 'test', 20.3,
+ (1, 2, 3), None, A, unittest, {'a': 1}, A.amethod]
+ for i in items:
+ self.assertEquals(i, jelly.unjelly(jelly.jelly(i)))
+
+
+ def test_setState(self):
+ global TupleState
+ class TupleState:
+ def __init__(self, other):
+ self.other = other
+ def __getstate__(self):
+ return (self.other,)
+ def __setstate__(self, state):
+ self.other = state[0]
+ def __hash__(self):
+ return hash(self.other)
+ a = A()
+ t1 = TupleState(a)
+ t2 = TupleState(a)
+ t3 = TupleState((t1, t2))
+ d = {t1: t1, t2: t2, t3: t3, "t3": t3}
+ t3prime = jelly.unjelly(jelly.jelly(d))["t3"]
+ self.assertIdentical(t3prime.other[0].other, t3prime.other[1].other)
+
+
+ def test_classSecurity(self):
+ """
+ Test for class-level security of serialization.
+ """
+ taster = jelly.SecurityOptions()
+ taster.allowInstancesOf(A, B)
+ a = A()
+ b = B()
+ c = C()
+ # add a little complexity to the data
+ a.b = b
+ a.c = c
+ # and a backreference
+ a.x = b
+ b.c = c
+ # first, a friendly insecure serialization
+ friendly = jelly.jelly(a, taster)
+ x = jelly.unjelly(friendly, taster)
+ self.assertIsInstance(x.c, jelly.Unpersistable)
+ # now, a malicious one
+ mean = jelly.jelly(a)
+ self.assertRaises(jelly.InsecureJelly, jelly.unjelly, mean, taster)
+ self.assertIdentical(x.x, x.b, "Identity mismatch")
+ # test class serialization
+ friendly = jelly.jelly(A, taster)
+ x = jelly.unjelly(friendly, taster)
+ self.assertIdentical(x, A, "A came back: %s" % x)
+
+
+ def test_unjellyable(self):
+ """
+ Test that if Unjellyable is used to deserialize a jellied object,
+ state comes out right.
+ """
+ class JellyableTestClass(jelly.Jellyable):
+ pass
+ jelly.setUnjellyableForClass(JellyableTestClass, jelly.Unjellyable)
+ input = JellyableTestClass()
+ input.attribute = 'value'
+ output = jelly.unjelly(jelly.jelly(input))
+ self.assertEquals(output.attribute, 'value')
+ self.assertIsInstance(output, jelly.Unjellyable)
+
+
+ def test_persistentStorage(self):
+ perst = [{}, 1]
+ def persistentStore(obj, jel, perst = perst):
+ perst[1] = perst[1] + 1
+ perst[0][perst[1]] = obj
+ return str(perst[1])
+
+ def persistentLoad(pidstr, unj, perst = perst):
+ pid = int(pidstr)
+ return perst[0][pid]
+
+ a = SimpleJellyTest(1, 2)
+ b = SimpleJellyTest(3, 4)
+ c = SimpleJellyTest(5, 6)
+
+ a.b = b
+ a.c = c
+ c.b = b
+
+ jel = jelly.jelly(a, persistentStore = persistentStore)
+ x = jelly.unjelly(jel, persistentLoad = persistentLoad)
+
+ self.assertIdentical(x.b, x.c.b)
+ self.failUnless(perst[0], "persistentStore was not called.")
+ self.assertIdentical(x.b, a.b, "Persistent storage identity failure.")
+
+
+ def test_newStyleClassesAttributes(self):
+ n = TestNode()
+ n1 = TestNode(n)
+ n11 = TestNode(n1)
+ n2 = TestNode(n)
+ # Jelly it
+ jel = jelly.jelly(n)
+ m = jelly.unjelly(jel)
+ # Check that it has been restored ok
+ self._check_newstyle(n, m)
+
+
+ def _check_newstyle(self, a, b):
+ self.assertEqual(a.id, b.id)
+ self.assertEqual(a.classAttr, 4)
+ self.assertEqual(b.classAttr, 4)
+ self.assertEqual(len(a.children), len(b.children))
+ for x, y in zip(a.children, b.children):
+ self._check_newstyle(x, y)
+
+
+
+class ClassA(pb.Copyable, pb.RemoteCopy):
+ def __init__(self):
+ self.ref = ClassB(self)
+
+
+
+class ClassB(pb.Copyable, pb.RemoteCopy):
+ def __init__(self, ref):
+ self.ref = ref
+
+
+
+class CircularReferenceTestCase(unittest.TestCase):
+ """
+ Tests for circular references handling in the jelly/unjelly process.
+ """
+
+ def test_simpleCircle(self):
+ jelly.setUnjellyableForClass(ClassA, ClassA)
+ jelly.setUnjellyableForClass(ClassB, ClassB)
+ a = jelly.unjelly(jelly.jelly(ClassA()))
+ self.assertIdentical(a.ref.ref, a,
+ "Identity not preserved in circular reference")
+
+
+ def test_circleWithInvoker(self):
+ class DummyInvokerClass:
+ pass
+ dummyInvoker = DummyInvokerClass()
+ dummyInvoker.serializingPerspective = None
+ a0 = ClassA()
+ jelly.setUnjellyableForClass(ClassA, ClassA)
+ jelly.setUnjellyableForClass(ClassB, ClassB)
+ j = jelly.jelly(a0, invoker=dummyInvoker)
+ a1 = jelly.unjelly(j)
+ self.failUnlessIdentical(a1.ref.ref, a1,
+ "Identity not preserved in circular reference")
+
+
+ def test_set(self):
+ """
+ Check that a C{set} can contain a circular reference and be serialized
+ and unserialized without losing the reference.
+ """
+ s = set()
+ a = SimpleJellyTest(s, None)
+ s.add(a)
+ res = jelly.unjelly(jelly.jelly(a))
+ self.assertIsInstance(res.x, set)
+ self.assertEquals(list(res.x), [res])
+
+
+ def test_frozenset(self):
+ """
+ Check that a C{frozenset} can contain a circular reference and be
+ serializeserialized without losing the reference.
+ """
+ a = SimpleJellyTest(None, None)
+ s = frozenset([a])
+ a.x = s
+ res = jelly.unjelly(jelly.jelly(a))
+ self.assertIsInstance(res.x, frozenset)
+ self.assertEquals(list(res.x), [res])
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_journal.py b/vendor/Twisted-10.0.0/twisted/test/test_journal.py
new file mode 100644
index 0000000000..20dc8a1c67
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_journal.py
@@ -0,0 +1,169 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Testing for twisted.persisted.journal."""
+
+from twisted.trial import unittest
+from twisted.persisted.journal.base import ICommand, MemoryJournal, serviceCommand, ServiceWrapperCommand, command, Wrappable
+from twisted.persisted.journal.picklelog import DirDBMLog
+from zope.interface import implements
+
+import shutil, os.path
+
+
+
+class AddTime:
+
+ implements(ICommand)
+
+ def execute(self, svc, cmdtime):
+ svc.values["time"] = cmdtime
+
+
+class Counter(Wrappable):
+
+ objectType = "counter"
+
+ def __init__(self, uid):
+ self.uid = uid
+ self.x = 0
+
+ def getUid(self):
+ return self.uid
+
+ def _increment(self):
+ self.x += 1
+
+ increment = command("_increment")
+
+
+class Service:
+
+ def __init__(self, logpath, journalpath):
+ log = DirDBMLog(logpath)
+ self.journal = MemoryJournal(log, self, journalpath, self._gotData)
+ self.journal.updateFromLog()
+
+ def _gotData(self, result):
+ if result is None:
+ self.values = {}
+ self.counters = {}
+ else:
+ self.values, self.counters = result
+
+ def _makeCounter(self, id):
+ c = Counter(id)
+ self.counters[id] = c
+ return c
+
+ makeCounter = serviceCommand("_makeCounter")
+
+ def loadObject(self, type, id):
+ if type != "counter": raise ValueError
+ return self.counters[id]
+
+ def _add(self, key, value):
+ """Add a new entry."""
+ self.values[key] = value
+
+ def _delete(self, key):
+ """Delete an entry."""
+ del self.values[key]
+
+ def get(self, key):
+ """Return value of an entry."""
+ return self.values[key]
+
+ def addtime(self, journal):
+ """Set a key 'time' with the current time."""
+ journal.executeCommand(AddTime())
+
+ # and now the command wrappers
+
+ add = serviceCommand("_add")
+
+ delete = serviceCommand("_delete")
+
+
+class JournalTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.logpath = self.mktemp()
+ self.journalpath = self.mktemp()
+ self.svc = Service(self.logpath, self.journalpath)
+
+ def tearDown(self):
+ if hasattr(self, "svc"):
+ del self.svc
+ # delete stuff? ...
+ if os.path.isdir(self.logpath):
+ shutil.rmtree(self.logpath)
+ if os.path.exists(self.logpath):
+ os.unlink(self.logpath)
+ if os.path.isdir(self.journalpath):
+ shutil.rmtree(self.journalpath)
+ if os.path.exists(self.journalpath):
+ os.unlink(self.journalpath)
+
+ def testCommandExecution(self):
+ svc = self.svc
+ svc.add(svc.journal, "foo", "bar")
+ self.assertEquals(svc.get("foo"), "bar")
+
+ svc.delete(svc.journal, "foo")
+ self.assertRaises(KeyError, svc.get, "foo")
+
+ def testLogging(self):
+ svc = self.svc
+ log = self.svc.journal.log
+ j = self.svc.journal
+ svc.add(j, "foo", "bar")
+ svc.add(j, 1, "hello")
+ svc.delete(j, "foo")
+
+ commands = [ServiceWrapperCommand("_add", ("foo", "bar")),
+ ServiceWrapperCommand("_add", (1, "hello")),
+ ServiceWrapperCommand("_delete", ("foo",))]
+
+ self.assertEquals(log.getCurrentIndex(), 3)
+ for i in range(1, 4):
+ for a, b in zip(commands[i-1:], [c for t, c in log.getCommandsSince(i)]):
+ self.assertEquals(a, b)
+
+ def testRecovery(self):
+ svc = self.svc
+ j = svc.journal
+ svc.add(j, "foo", "bar")
+ svc.add(j, 1, "hello")
+ # we sync *before* delete to make sure commands get executed
+ svc.journal.sync((svc.values, svc.counters))
+ svc.delete(j, "foo")
+ d = svc.makeCounter(j, 1)
+ d.addCallback(lambda c, j=j: c.increment(j))
+ del svc, self.svc
+
+ # first, load from snapshot
+ svc = Service(self.logpath, self.journalpath)
+ self.assertEquals(svc.values, {1: "hello"})
+ self.assertEquals(svc.counters[1].x, 1)
+ del svc
+
+ # now, tamper with log, and then try
+ f = open(self.journalpath, "w")
+ f.write("sfsdfsdfsd")
+ f.close()
+ svc = Service(self.logpath, self.journalpath)
+ self.assertEquals(svc.values, {1: "hello"})
+ self.assertEquals(svc.counters[1].x, 1)
+
+ def testTime(self):
+ svc = self.svc
+ svc.addtime(svc.journal)
+ t = svc.get("time")
+
+ log = self.svc.journal.log
+ (t2, c), = log.getCommandsSince(1)
+ self.assertEquals(t, t2)
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_lockfile.py b/vendor/Twisted-10.0.0/twisted/test/test_lockfile.py
new file mode 100644
index 0000000000..d2d0503053
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_lockfile.py
@@ -0,0 +1,445 @@
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.python.lockfile}.
+"""
+
+import os, errno
+
+from twisted.trial import unittest
+from twisted.python import lockfile
+from twisted.python.runtime import platform
+
+skipKill = None
+if platform.isWindows():
+ try:
+ from win32api import OpenProcess
+ import pywintypes
+ except ImportError:
+ skipKill = ("On windows, lockfile.kill is not implemented in the "
+ "absence of win32api and/or pywintypes.")
+
+class UtilTests(unittest.TestCase):
+ """
+ Tests for the helper functions used to implement L{FilesystemLock}.
+ """
+ def test_symlinkEEXIST(self):
+ """
+ L{lockfile.symlink} raises L{OSError} with C{errno} set to L{EEXIST}
+ when an attempt is made to create a symlink which already exists.
+ """
+ name = self.mktemp()
+ lockfile.symlink('foo', name)
+ exc = self.assertRaises(OSError, lockfile.symlink, 'foo', name)
+ self.assertEqual(exc.errno, errno.EEXIST)
+
+
+ def test_symlinkEIOWindows(self):
+ """
+ L{lockfile.symlink} raises L{OSError} with C{errno} set to L{EIO} when
+ the underlying L{rename} call fails with L{EIO}.
+
+ Renaming a file on Windows may fail if the target of the rename is in
+ the process of being deleted (directory deletion appears not to be
+ atomic).
+ """
+ name = self.mktemp()
+ def fakeRename(src, dst):
+ raise IOError(errno.EIO, None)
+ self.patch(lockfile, 'rename', fakeRename)
+ exc = self.assertRaises(IOError, lockfile.symlink, name, "foo")
+ self.assertEqual(exc.errno, errno.EIO)
+ if not platform.isWindows():
+ test_symlinkEIOWindows.skip = (
+ "special rename EIO handling only necessary and correct on "
+ "Windows.")
+
+
+ def test_readlinkENOENT(self):
+ """
+ L{lockfile.readlink} raises L{OSError} with C{errno} set to L{ENOENT}
+ when an attempt is made to read a symlink which does not exist.
+ """
+ name = self.mktemp()
+ exc = self.assertRaises(OSError, lockfile.readlink, name)
+ self.assertEqual(exc.errno, errno.ENOENT)
+
+
+ def test_readlinkEACCESWindows(self):
+ """
+ L{lockfile.readlink} raises L{OSError} with C{errno} set to L{EACCES}
+ on Windows when the underlying file open attempt fails with C{EACCES}.
+
+ Opening a file on Windows may fail if the path is inside a directory
+ which is in the process of being deleted (directory deletion appears
+ not to be atomic).
+ """
+ name = self.mktemp()
+ def fakeOpen(path, mode):
+ raise IOError(errno.EACCES, None)
+ self.patch(lockfile, '_open', fakeOpen)
+ exc = self.assertRaises(IOError, lockfile.readlink, name)
+ self.assertEqual(exc.errno, errno.EACCES)
+ if not platform.isWindows():
+ test_readlinkEACCESWindows.skip = (
+ "special readlink EACCES handling only necessary and correct on "
+ "Windows.")
+
+
+ def test_kill(self):
+ """
+ L{lockfile.kill} returns without error if passed the PID of a
+ process which exists and signal C{0}.
+ """
+ lockfile.kill(os.getpid(), 0)
+ test_kill.skip = skipKill
+
+
+ def test_killESRCH(self):
+ """
+ L{lockfile.kill} raises L{OSError} with errno of L{ESRCH} if
+ passed a PID which does not correspond to any process.
+ """
+ # Hopefully there is no process with PID 2 ** 31 - 1
+ exc = self.assertRaises(OSError, lockfile.kill, 2 ** 31 - 1, 0)
+ self.assertEqual(exc.errno, errno.ESRCH)
+ test_killESRCH.skip = skipKill
+
+
+ def test_noKillCall(self):
+ """
+ Verify that when L{lockfile.kill} does end up as None (e.g. on Windows
+ without pywin32), it doesn't end up being called and raising a
+ L{TypeError}.
+ """
+ self.patch(lockfile, "kill", None)
+ fl = lockfile.FilesystemLock(self.mktemp())
+ fl.lock()
+ self.assertFalse(fl.lock())
+
+
+
+class LockingTestCase(unittest.TestCase):
+ def _symlinkErrorTest(self, errno):
+ def fakeSymlink(source, dest):
+ raise OSError(errno, None)
+ self.patch(lockfile, 'symlink', fakeSymlink)
+
+ lockf = self.mktemp()
+ lock = lockfile.FilesystemLock(lockf)
+ exc = self.assertRaises(OSError, lock.lock)
+ self.assertEqual(exc.errno, errno)
+
+
+ def test_symlinkError(self):
+ """
+ An exception raised by C{symlink} other than C{EEXIST} is passed up to
+ the caller of L{FilesystemLock.lock}.
+ """
+ self._symlinkErrorTest(errno.ENOSYS)
+
+
+ def test_symlinkErrorPOSIX(self):
+ """
+ An L{OSError} raised by C{symlink} on a POSIX platform with an errno of
+ C{EACCES} or C{EIO} is passed to the caller of L{FilesystemLock.lock}.
+
+ On POSIX, unlike on Windows, these are unexpected errors which cannot
+ be handled by L{FilesystemLock}.
+ """
+ self._symlinkErrorTest(errno.EACCES)
+ self._symlinkErrorTest(errno.EIO)
+ if platform.isWindows():
+ test_symlinkErrorPOSIX.skip = (
+ "POSIX-specific error propagation not expected on Windows.")
+
+
+ def test_cleanlyAcquire(self):
+ """
+ If the lock has never been held, it can be acquired and the C{clean}
+ and C{locked} attributes are set to C{True}.
+ """
+ lockf = self.mktemp()
+ lock = lockfile.FilesystemLock(lockf)
+ self.assertTrue(lock.lock())
+ self.assertTrue(lock.clean)
+ self.assertTrue(lock.locked)
+
+
+ def test_cleanlyRelease(self):
+ """
+ If a lock is released cleanly, it can be re-acquired and the C{clean}
+ and C{locked} attributes are set to C{True}.
+ """
+ lockf = self.mktemp()
+ lock = lockfile.FilesystemLock(lockf)
+ self.assertTrue(lock.lock())
+ lock.unlock()
+ self.assertFalse(lock.locked)
+
+ lock = lockfile.FilesystemLock(lockf)
+ self.assertTrue(lock.lock())
+ self.assertTrue(lock.clean)
+ self.assertTrue(lock.locked)
+
+
+ def test_cannotLockLocked(self):
+ """
+ If a lock is currently locked, it cannot be locked again.
+ """
+ lockf = self.mktemp()
+ firstLock = lockfile.FilesystemLock(lockf)
+ self.assertTrue(firstLock.lock())
+
+ secondLock = lockfile.FilesystemLock(lockf)
+ self.assertFalse(secondLock.lock())
+ self.assertFalse(secondLock.locked)
+
+
+ def test_uncleanlyAcquire(self):
+ """
+ If a lock was held by a process which no longer exists, it can be
+ acquired, the C{clean} attribute is set to C{False}, and the
+ C{locked} attribute is set to C{True}.
+ """
+ owner = 12345
+
+ def fakeKill(pid, signal):
+ if signal != 0:
+ raise OSError(errno.EPERM, None)
+ if pid == owner:
+ raise OSError(errno.ESRCH, None)
+
+ lockf = self.mktemp()
+ self.patch(lockfile, 'kill', fakeKill)
+ lockfile.symlink(str(owner), lockf)
+
+ lock = lockfile.FilesystemLock(lockf)
+ self.assertTrue(lock.lock())
+ self.assertFalse(lock.clean)
+ self.assertTrue(lock.locked)
+
+ self.assertEqual(lockfile.readlink(lockf), str(os.getpid()))
+
+
+ def test_lockReleasedBeforeCheck(self):
+ """
+ If the lock is initially held but then released before it can be
+ examined to determine if the process which held it still exists, it is
+ acquired and the C{clean} and C{locked} attributes are set to C{True}.
+ """
+ def fakeReadlink(name):
+ # Pretend to be another process releasing the lock.
+ lockfile.rmlink(lockf)
+ # Fall back to the real implementation of readlink.
+ readlinkPatch.restore()
+ return lockfile.readlink(name)
+ readlinkPatch = self.patch(lockfile, 'readlink', fakeReadlink)
+
+ def fakeKill(pid, signal):
+ if signal != 0:
+ raise OSError(errno.EPERM, None)
+ if pid == 43125:
+ raise OSError(errno.ESRCH, None)
+ self.patch(lockfile, 'kill', fakeKill)
+
+ lockf = self.mktemp()
+ lock = lockfile.FilesystemLock(lockf)
+ lockfile.symlink(str(43125), lockf)
+ self.assertTrue(lock.lock())
+ self.assertTrue(lock.clean)
+ self.assertTrue(lock.locked)
+
+
+ def test_lockReleasedDuringAcquireSymlink(self):
+ """
+ If the lock is released while an attempt is made to acquire
+ it, the lock attempt fails and C{FilesystemLock.lock} returns
+ C{False}. This can happen on Windows when L{lockfile.symlink}
+ fails with L{IOError} of C{EIO} because another process is in
+ the middle of a call to L{os.rmdir} (implemented in terms of
+ RemoveDirectory) which is not atomic.
+ """
+ def fakeSymlink(src, dst):
+ # While another process id doing os.rmdir which the Windows
+ # implementation of rmlink does, a rename call will fail with EIO.
+ raise OSError(errno.EIO, None)
+
+ self.patch(lockfile, 'symlink', fakeSymlink)
+
+ lockf = self.mktemp()
+ lock = lockfile.FilesystemLock(lockf)
+ self.assertFalse(lock.lock())
+ self.assertFalse(lock.locked)
+ if not platform.isWindows():
+ test_lockReleasedDuringAcquireSymlink.skip = (
+ "special rename EIO handling only necessary and correct on "
+ "Windows.")
+
+
+ def test_lockReleasedDuringAcquireReadlink(self):
+ """
+ If the lock is initially held but is released while an attempt
+ is made to acquire it, the lock attempt fails and
+ L{FilesystemLock.lock} returns C{False}.
+ """
+ def fakeReadlink(name):
+ # While another process is doing os.rmdir which the
+ # Windows implementation of rmlink does, a readlink call
+ # will fail with EACCES.
+ raise IOError(errno.EACCES, None)
+ readlinkPatch = self.patch(lockfile, 'readlink', fakeReadlink)
+
+ lockf = self.mktemp()
+ lock = lockfile.FilesystemLock(lockf)
+ lockfile.symlink(str(43125), lockf)
+ self.assertFalse(lock.lock())
+ self.assertFalse(lock.locked)
+ if not platform.isWindows():
+ test_lockReleasedDuringAcquireReadlink.skip = (
+ "special readlink EACCES handling only necessary and correct on "
+ "Windows.")
+
+
+ def _readlinkErrorTest(self, exceptionType, errno):
+ def fakeReadlink(name):
+ raise exceptionType(errno, None)
+ self.patch(lockfile, 'readlink', fakeReadlink)
+
+ lockf = self.mktemp()
+
+ # Make it appear locked so it has to use readlink
+ lockfile.symlink(str(43125), lockf)
+
+ lock = lockfile.FilesystemLock(lockf)
+ exc = self.assertRaises(exceptionType, lock.lock)
+ self.assertEqual(exc.errno, errno)
+ self.assertFalse(lock.locked)
+
+
+ def test_readlinkError(self):
+ """
+ An exception raised by C{readlink} other than C{ENOENT} is passed up to
+ the caller of L{FilesystemLock.lock}.
+ """
+ self._readlinkErrorTest(OSError, errno.ENOSYS)
+ self._readlinkErrorTest(IOError, errno.ENOSYS)
+
+
+ def test_readlinkErrorPOSIX(self):
+ """
+ Any L{IOError} raised by C{readlink} on a POSIX platform passed to the
+ caller of L{FilesystemLock.lock}.
+
+ On POSIX, unlike on Windows, these are unexpected errors which cannot
+ be handled by L{FilesystemLock}.
+ """
+ self._readlinkErrorTest(IOError, errno.ENOSYS)
+ self._readlinkErrorTest(IOError, errno.EACCES)
+ if platform.isWindows():
+ test_readlinkErrorPOSIX.skip = (
+ "POSIX-specific error propagation not expected on Windows.")
+
+
+ def test_lockCleanedUpConcurrently(self):
+ """
+ If a second process cleans up the lock after a first one checks the
+ lock and finds that no process is holding it, the first process does
+ not fail when it tries to clean up the lock.
+ """
+ def fakeRmlink(name):
+ rmlinkPatch.restore()
+ # Pretend to be another process cleaning up the lock.
+ lockfile.rmlink(lockf)
+ # Fall back to the real implementation of rmlink.
+ return lockfile.rmlink(name)
+ rmlinkPatch = self.patch(lockfile, 'rmlink', fakeRmlink)
+
+ def fakeKill(pid, signal):
+ if signal != 0:
+ raise OSError(errno.EPERM, None)
+ if pid == 43125:
+ raise OSError(errno.ESRCH, None)
+ self.patch(lockfile, 'kill', fakeKill)
+
+ lockf = self.mktemp()
+ lock = lockfile.FilesystemLock(lockf)
+ lockfile.symlink(str(43125), lockf)
+ self.assertTrue(lock.lock())
+ self.assertTrue(lock.clean)
+ self.assertTrue(lock.locked)
+
+
+ def test_rmlinkError(self):
+ """
+ An exception raised by L{rmlink} other than C{ENOENT} is passed up
+ to the caller of L{FilesystemLock.lock}.
+ """
+ def fakeRmlink(name):
+ raise OSError(errno.ENOSYS, None)
+ self.patch(lockfile, 'rmlink', fakeRmlink)
+
+ def fakeKill(pid, signal):
+ if signal != 0:
+ raise OSError(errno.EPERM, None)
+ if pid == 43125:
+ raise OSError(errno.ESRCH, None)
+ self.patch(lockfile, 'kill', fakeKill)
+
+ lockf = self.mktemp()
+
+ # Make it appear locked so it has to use readlink
+ lockfile.symlink(str(43125), lockf)
+
+ lock = lockfile.FilesystemLock(lockf)
+ exc = self.assertRaises(OSError, lock.lock)
+ self.assertEqual(exc.errno, errno.ENOSYS)
+ self.assertFalse(lock.locked)
+
+
+ def test_killError(self):
+ """
+ If L{kill} raises an exception other than L{OSError} with errno set to
+ C{ESRCH}, the exception is passed up to the caller of
+ L{FilesystemLock.lock}.
+ """
+ def fakeKill(pid, signal):
+ raise OSError(errno.EPERM, None)
+ self.patch(lockfile, 'kill', fakeKill)
+
+ lockf = self.mktemp()
+
+ # Make it appear locked so it has to use readlink
+ lockfile.symlink(str(43125), lockf)
+
+ lock = lockfile.FilesystemLock(lockf)
+ exc = self.assertRaises(OSError, lock.lock)
+ self.assertEqual(exc.errno, errno.EPERM)
+ self.assertFalse(lock.locked)
+
+
+ def test_unlockOther(self):
+ """
+ L{FilesystemLock.unlock} raises L{ValueError} if called for a lock
+ which is held by a different process.
+ """
+ lockf = self.mktemp()
+ lockfile.symlink(str(os.getpid() + 1), lockf)
+ lock = lockfile.FilesystemLock(lockf)
+ self.assertRaises(ValueError, lock.unlock)
+
+
+ def test_isLocked(self):
+ """
+ L{isLocked} returns C{True} if the named lock is currently locked,
+ C{False} otherwise.
+ """
+ lockf = self.mktemp()
+ self.assertFalse(lockfile.isLocked(lockf))
+ lock = lockfile.FilesystemLock(lockf)
+ self.assertTrue(lock.lock())
+ self.assertTrue(lockfile.isLocked(lockf))
+ lock.unlock()
+ self.assertFalse(lockfile.isLocked(lockf))
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_log.py b/vendor/Twisted-10.0.0/twisted/test/test_log.py
new file mode 100644
index 0000000000..032eaf25d9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_log.py
@@ -0,0 +1,559 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.python.log}.
+"""
+
+import os, sys, time, logging, warnings
+from cStringIO import StringIO
+
+from twisted.trial import unittest
+
+from twisted.python import log, failure
+
+
+class FakeWarning(Warning):
+ """
+ A unique L{Warning} subclass used by tests for interactions of
+ L{twisted.python.log} with the L{warnings} module.
+ """
+
+
+
+class LogTest(unittest.TestCase):
+
+ def setUp(self):
+ self.catcher = []
+ self.observer = self.catcher.append
+ log.addObserver(self.observer)
+ self.addCleanup(log.removeObserver, self.observer)
+
+
+ def testObservation(self):
+ catcher = self.catcher
+ log.msg("test", testShouldCatch=True)
+ i = catcher.pop()
+ self.assertEquals(i["message"][0], "test")
+ self.assertEquals(i["testShouldCatch"], True)
+ self.failUnless(i.has_key("time"))
+ self.assertEquals(len(catcher), 0)
+
+
+ def testContext(self):
+ catcher = self.catcher
+ log.callWithContext({"subsystem": "not the default",
+ "subsubsystem": "a",
+ "other": "c"},
+ log.callWithContext,
+ {"subsubsystem": "b"}, log.msg, "foo", other="d")
+ i = catcher.pop()
+ self.assertEquals(i['subsubsystem'], 'b')
+ self.assertEquals(i['subsystem'], 'not the default')
+ self.assertEquals(i['other'], 'd')
+ self.assertEquals(i['message'][0], 'foo')
+
+ def testErrors(self):
+ for e, ig in [("hello world","hello world"),
+ (KeyError(), KeyError),
+ (failure.Failure(RuntimeError()), RuntimeError)]:
+ log.err(e)
+ i = self.catcher.pop()
+ self.assertEquals(i['isError'], 1)
+ self.flushLoggedErrors(ig)
+
+ def testErrorsWithWhy(self):
+ for e, ig in [("hello world","hello world"),
+ (KeyError(), KeyError),
+ (failure.Failure(RuntimeError()), RuntimeError)]:
+ log.err(e, 'foobar')
+ i = self.catcher.pop()
+ self.assertEquals(i['isError'], 1)
+ self.assertEquals(i['why'], 'foobar')
+ self.flushLoggedErrors(ig)
+
+
+ def test_erroneousErrors(self):
+ """
+ Exceptions raised by log observers are logged but the observer which
+ raised the exception remains registered with the publisher. These
+ exceptions do not prevent the event from being sent to other observers
+ registered with the publisher.
+ """
+ L1 = []
+ L2 = []
+ def broken(events):
+ 1 / 0
+
+ for observer in [L1.append, broken, L2.append]:
+ log.addObserver(observer)
+ self.addCleanup(log.removeObserver, observer)
+
+ for i in xrange(3):
+ # Reset the lists for simpler comparison.
+ L1[:] = []
+ L2[:] = []
+
+ # Send out the event which will break one of the observers.
+ log.msg("Howdy, y'all.")
+
+ # The broken observer should have caused this to be logged. There
+ # is a slight bug with LogPublisher - when it logs an error from an
+ # observer, it uses the global "err", which is not necessarily
+ # associated with it, but which may be associated with a different
+ # LogPublisher! See #3307.
+ excs = self.flushLoggedErrors(ZeroDivisionError)
+ self.assertEqual(len(excs), 1)
+
+ # Both other observers should have seen the message.
+ self.assertEquals(len(L1), 2)
+ self.assertEquals(len(L2), 2)
+
+ # The order is slightly wrong here. The first event should be
+ # delivered to all observers; then, errors should be delivered.
+ self.assertEquals(L1[1]['message'], ("Howdy, y'all.",))
+ self.assertEquals(L2[0]['message'], ("Howdy, y'all.",))
+
+
+ def test_showwarning(self):
+ """
+ L{twisted.python.log.showwarning} emits the warning as a message
+ to the Twisted logging system.
+ """
+ publisher = log.LogPublisher()
+ publisher.addObserver(self.observer)
+
+ publisher.showwarning(
+ FakeWarning("unique warning message"), FakeWarning,
+ "warning-filename.py", 27)
+ event = self.catcher.pop()
+ self.assertEqual(
+ event['format'] % event,
+ 'warning-filename.py:27: twisted.test.test_log.FakeWarning: '
+ 'unique warning message')
+ self.assertEqual(self.catcher, [])
+
+ # Python 2.6 requires that any function used to override the
+ # warnings.showwarning API accept a "line" parameter or a
+ # deprecation warning is emitted.
+ publisher.showwarning(
+ FakeWarning("unique warning message"), FakeWarning,
+ "warning-filename.py", 27, line=object())
+ event = self.catcher.pop()
+ self.assertEqual(
+ event['format'] % event,
+ 'warning-filename.py:27: twisted.test.test_log.FakeWarning: '
+ 'unique warning message')
+ self.assertEqual(self.catcher, [])
+
+
+ def test_warningToFile(self):
+ """
+ L{twisted.python.log.showwarning} passes warnings with an explicit file
+ target on to the underlying Python warning system.
+ """
+ message = "another unique message"
+ category = FakeWarning
+ filename = "warning-filename.py"
+ lineno = 31
+
+ output = StringIO()
+ log.showwarning(message, category, filename, lineno, file=output)
+
+ self.assertEqual(
+ output.getvalue(),
+ warnings.formatwarning(message, category, filename, lineno))
+
+ # In Python 2.6, warnings.showwarning accepts a "line" argument which
+ # gives the source line the warning message is to include.
+ if sys.version_info >= (2, 6):
+ line = "hello world"
+ output = StringIO()
+ log.showwarning(message, category, filename, lineno, file=output,
+ line=line)
+
+ self.assertEqual(
+ output.getvalue(),
+ warnings.formatwarning(message, category, filename, lineno,
+ line))
+
+
+class FakeFile(list):
+ def write(self, bytes):
+ self.append(bytes)
+
+ def flush(self):
+ pass
+
+class EvilStr:
+ def __str__(self):
+ 1/0
+
+class EvilRepr:
+ def __str__(self):
+ return "Happy Evil Repr"
+ def __repr__(self):
+ 1/0
+
+class EvilReprStr(EvilStr, EvilRepr):
+ pass
+
+class LogPublisherTestCaseMixin:
+ def setUp(self):
+ """
+ Add a log observer which records log events in C{self.out}. Also,
+ make sure the default string encoding is ASCII so that
+ L{testSingleUnicode} can test the behavior of logging unencodable
+ unicode messages.
+ """
+ self.out = FakeFile()
+ self.lp = log.LogPublisher()
+ self.flo = log.FileLogObserver(self.out)
+ self.lp.addObserver(self.flo.emit)
+
+ try:
+ str(u'\N{VULGAR FRACTION ONE HALF}')
+ except UnicodeEncodeError:
+ # This is the behavior we want - don't change anything.
+ self._origEncoding = None
+ else:
+ reload(sys)
+ self._origEncoding = sys.getdefaultencoding()
+ sys.setdefaultencoding('ascii')
+
+
+ def tearDown(self):
+ """
+ Verify that everything written to the fake file C{self.out} was a
+ C{str}. Also, restore the default string encoding to its previous
+ setting, if it was modified by L{setUp}.
+ """
+ for chunk in self.out:
+ self.failUnless(isinstance(chunk, str), "%r was not a string" % (chunk,))
+
+ if self._origEncoding is not None:
+ sys.setdefaultencoding(self._origEncoding)
+ del sys.setdefaultencoding
+
+
+
+class LogPublisherTestCase(LogPublisherTestCaseMixin, unittest.TestCase):
+ def testSingleString(self):
+ self.lp.msg("Hello, world.")
+ self.assertEquals(len(self.out), 1)
+
+
+ def testMultipleString(self):
+ # Test some stupid behavior that will be deprecated real soon.
+ # If you are reading this and trying to learn how the logging
+ # system works, *do not use this feature*.
+ self.lp.msg("Hello, ", "world.")
+ self.assertEquals(len(self.out), 1)
+
+
+ def testSingleUnicode(self):
+ self.lp.msg(u"Hello, \N{VULGAR FRACTION ONE HALF} world.")
+ self.assertEquals(len(self.out), 1)
+ self.assertIn('with str error', self.out[0])
+ self.assertIn('UnicodeEncodeError', self.out[0])
+
+
+
+class FileObserverTestCase(LogPublisherTestCaseMixin, unittest.TestCase):
+ def test_getTimezoneOffset(self):
+ """
+ Attempt to verify that L{FileLogObserver.getTimezoneOffset} returns
+ correct values for the current C{TZ} environment setting. Do this
+ by setting C{TZ} to various well-known values and asserting that the
+ reported offset is correct.
+ """
+ localDaylightTuple = (2006, 6, 30, 0, 0, 0, 4, 181, 1)
+ utcDaylightTimestamp = time.mktime(localDaylightTuple)
+ localStandardTuple = (2007, 1, 31, 0, 0, 0, 2, 31, 0)
+ utcStandardTimestamp = time.mktime(localStandardTuple)
+
+ originalTimezone = os.environ.get('TZ', None)
+ try:
+ # Test something west of UTC
+ os.environ['TZ'] = 'America/New_York'
+ time.tzset()
+ self.assertEqual(
+ self.flo.getTimezoneOffset(utcDaylightTimestamp),
+ 14400)
+ self.assertEqual(
+ self.flo.getTimezoneOffset(utcStandardTimestamp),
+ 18000)
+
+ # Test something east of UTC
+ os.environ['TZ'] = 'Europe/Berlin'
+ time.tzset()
+ self.assertEqual(
+ self.flo.getTimezoneOffset(utcDaylightTimestamp),
+ -7200)
+ self.assertEqual(
+ self.flo.getTimezoneOffset(utcStandardTimestamp),
+ -3600)
+
+ # Test a timezone that doesn't have DST
+ os.environ['TZ'] = 'Africa/Johannesburg'
+ time.tzset()
+ self.assertEqual(
+ self.flo.getTimezoneOffset(utcDaylightTimestamp),
+ -7200)
+ self.assertEqual(
+ self.flo.getTimezoneOffset(utcStandardTimestamp),
+ -7200)
+ finally:
+ if originalTimezone is None:
+ del os.environ['TZ']
+ else:
+ os.environ['TZ'] = originalTimezone
+ time.tzset()
+ if getattr(time, 'tzset', None) is None:
+ test_getTimezoneOffset.skip = (
+ "Platform cannot change timezone, cannot verify correct offsets "
+ "in well-known timezones.")
+
+
+ def test_timeFormatting(self):
+ """
+ Test the method of L{FileLogObserver} which turns a timestamp into a
+ human-readable string.
+ """
+ # There is no function in the time module which converts a UTC time
+ # tuple to a timestamp.
+ when = time.mktime((2001, 2, 3, 4, 5, 6, 7, 8, 0)) - time.timezone
+
+ # Pretend to be in US/Eastern for a moment
+ self.flo.getTimezoneOffset = lambda when: 18000
+ self.assertEquals(self.flo.formatTime(when), '2001-02-02 23:05:06-0500')
+
+ # Okay now we're in Eastern Europe somewhere
+ self.flo.getTimezoneOffset = lambda when: -3600
+ self.assertEquals(self.flo.formatTime(when), '2001-02-03 05:05:06+0100')
+
+ # And off in the Pacific or someplace like that
+ self.flo.getTimezoneOffset = lambda when: -39600
+ self.assertEquals(self.flo.formatTime(when), '2001-02-03 15:05:06+1100')
+
+ # One of those weird places with a half-hour offset timezone
+ self.flo.getTimezoneOffset = lambda when: 5400
+ self.assertEquals(self.flo.formatTime(when), '2001-02-03 02:35:06-0130')
+
+ # Half-hour offset in the other direction
+ self.flo.getTimezoneOffset = lambda when: -5400
+ self.assertEquals(self.flo.formatTime(when), '2001-02-03 05:35:06+0130')
+
+ # Test an offset which is between 0 and 60 minutes to make sure the
+ # sign comes out properly in that case.
+ self.flo.getTimezoneOffset = lambda when: 1800
+ self.assertEquals(self.flo.formatTime(when), '2001-02-03 03:35:06-0030')
+
+ # Test an offset between 0 and 60 minutes in the other direction.
+ self.flo.getTimezoneOffset = lambda when: -1800
+ self.assertEquals(self.flo.formatTime(when), '2001-02-03 04:35:06+0030')
+
+ # If a strftime-format string is present on the logger, it should
+ # use that instead. Note we don't assert anything about day, hour
+ # or minute because we cannot easily control what time.strftime()
+ # thinks the local timezone is.
+ self.flo.timeFormat = '%Y %m'
+ self.assertEquals(self.flo.formatTime(when), '2001 02')
+
+
+ def test_loggingAnObjectWithBroken__str__(self):
+ #HELLO, MCFLY
+ self.lp.msg(EvilStr())
+ self.assertEquals(len(self.out), 1)
+ # Logging system shouldn't need to crap itself for this trivial case
+ self.assertNotIn('UNFORMATTABLE', self.out[0])
+
+
+ def test_formattingAnObjectWithBroken__str__(self):
+ self.lp.msg(format='%(blat)s', blat=EvilStr())
+ self.assertEquals(len(self.out), 1)
+ self.assertIn('Invalid format string or unformattable object', self.out[0])
+
+
+ def test_brokenSystem__str__(self):
+ self.lp.msg('huh', system=EvilStr())
+ self.assertEquals(len(self.out), 1)
+ self.assertIn('Invalid format string or unformattable object', self.out[0])
+
+
+ def test_formattingAnObjectWithBroken__repr__Indirect(self):
+ self.lp.msg(format='%(blat)s', blat=[EvilRepr()])
+ self.assertEquals(len(self.out), 1)
+ self.assertIn('UNFORMATTABLE OBJECT', self.out[0])
+
+
+ def test_systemWithBroker__repr__Indirect(self):
+ self.lp.msg('huh', system=[EvilRepr()])
+ self.assertEquals(len(self.out), 1)
+ self.assertIn('UNFORMATTABLE OBJECT', self.out[0])
+
+
+ def test_simpleBrokenFormat(self):
+ self.lp.msg(format='hooj %s %s', blat=1)
+ self.assertEquals(len(self.out), 1)
+ self.assertIn('Invalid format string or unformattable object', self.out[0])
+
+
+ def test_ridiculousFormat(self):
+ self.lp.msg(format=42, blat=1)
+ self.assertEquals(len(self.out), 1)
+ self.assertIn('Invalid format string or unformattable object', self.out[0])
+
+
+ def test_evilFormat__repr__And__str__(self):
+ self.lp.msg(format=EvilReprStr(), blat=1)
+ self.assertEquals(len(self.out), 1)
+ self.assertIn('PATHOLOGICAL', self.out[0])
+
+
+ def test_strangeEventDict(self):
+ """
+ This kind of eventDict used to fail silently, so test it does.
+ """
+ self.lp.msg(message='', isError=False)
+ self.assertEquals(len(self.out), 0)
+
+
+ def test_startLoggingTwice(self):
+ """
+ There are some obscure error conditions that can occur when logging is
+ started twice. See http://twistedmatrix.com/trac/ticket/3289 for more
+ information.
+ """
+ # The bug is particular to the way that the t.p.log 'global' function
+ # handle stdout. If we use our own stream, the error doesn't occur. If
+ # we use our own LogPublisher, the error doesn't occur.
+ sys.stdout = StringIO()
+ self.addCleanup(setattr, sys, 'stdout', sys.__stdout__)
+
+ def showError(eventDict):
+ if eventDict['isError']:
+ sys.__stdout__.write(eventDict['failure'].getTraceback())
+
+ log.addObserver(showError)
+ self.addCleanup(log.removeObserver, showError)
+ observer = log.startLogging(sys.stdout)
+ self.addCleanup(observer.stop)
+ # At this point, we expect that sys.stdout is a StdioOnnaStick object.
+ self.assertIsInstance(sys.stdout, log.StdioOnnaStick)
+ fakeStdout = sys.stdout
+ observer = log.startLogging(sys.stdout)
+ self.assertIdentical(sys.stdout, fakeStdout)
+
+
+class PythonLoggingObserverTestCase(unittest.TestCase):
+ """
+ Test the bridge with python logging module.
+ """
+ def setUp(self):
+ self.out = StringIO()
+
+ rootLogger = logging.getLogger("")
+ self.originalLevel = rootLogger.getEffectiveLevel()
+ rootLogger.setLevel(logging.DEBUG)
+ self.hdlr = logging.StreamHandler(self.out)
+ fmt = logging.Formatter(logging.BASIC_FORMAT)
+ self.hdlr.setFormatter(fmt)
+ rootLogger.addHandler(self.hdlr)
+
+ self.lp = log.LogPublisher()
+ self.obs = log.PythonLoggingObserver()
+ self.lp.addObserver(self.obs.emit)
+
+ def tearDown(self):
+ rootLogger = logging.getLogger("")
+ rootLogger.removeHandler(self.hdlr)
+ rootLogger.setLevel(self.originalLevel)
+ logging.shutdown()
+
+ def test_singleString(self):
+ """
+ Test simple output, and default log level.
+ """
+ self.lp.msg("Hello, world.")
+ self.assertIn("Hello, world.", self.out.getvalue())
+ self.assertIn("INFO", self.out.getvalue())
+
+ def test_errorString(self):
+ """
+ Test error output.
+ """
+ self.lp.msg(failure=failure.Failure(ValueError("That is bad.")), isError=True)
+ self.assertIn("ERROR", self.out.getvalue())
+
+ def test_formatString(self):
+ """
+ Test logging with a format.
+ """
+ self.lp.msg(format="%(bar)s oo %(foo)s", bar="Hello", foo="world")
+ self.assertIn("Hello oo world", self.out.getvalue())
+
+ def test_customLevel(self):
+ """
+ Test the logLevel keyword for customizing level used.
+ """
+ self.lp.msg("Spam egg.", logLevel=logging.DEBUG)
+ self.assertIn("Spam egg.", self.out.getvalue())
+ self.assertIn("DEBUG", self.out.getvalue())
+ self.out.reset()
+ self.lp.msg("Foo bar.", logLevel=logging.WARNING)
+ self.assertIn("Foo bar.", self.out.getvalue())
+ self.assertIn("WARNING", self.out.getvalue())
+
+ def test_strangeEventDict(self):
+ """
+ Verify that an event dictionary which is not an error and has an empty
+ message isn't recorded.
+ """
+ self.lp.msg(message='', isError=False)
+ self.assertEquals(self.out.getvalue(), '')
+
+
+class PythonLoggingIntegrationTestCase(unittest.TestCase):
+ """
+ Test integration of python logging bridge.
+ """
+ def test_startStopObserver(self):
+ """
+ Test that start and stop methods of the observer actually register
+ and unregister to the log system.
+ """
+ oldAddObserver = log.addObserver
+ oldRemoveObserver = log.removeObserver
+ l = []
+ try:
+ log.addObserver = l.append
+ log.removeObserver = l.remove
+ obs = log.PythonLoggingObserver()
+ obs.start()
+ self.assertEquals(l[0], obs.emit)
+ obs.stop()
+ self.assertEquals(len(l), 0)
+ finally:
+ log.addObserver = oldAddObserver
+ log.removeObserver = oldRemoveObserver
+
+ def test_inheritance(self):
+ """
+ Test that we can inherit L{log.PythonLoggingObserver} and use super:
+ that's basically a validation that L{log.PythonLoggingObserver} is
+ new-style class.
+ """
+ class MyObserver(log.PythonLoggingObserver):
+ def emit(self, eventDict):
+ super(MyObserver, self).emit(eventDict)
+ obs = MyObserver()
+ l = []
+ oldEmit = log.PythonLoggingObserver.emit
+ try:
+ log.PythonLoggingObserver.emit = l.append
+ obs.emit('foo')
+ self.assertEquals(len(l), 1)
+ finally:
+ log.PythonLoggingObserver.emit = oldEmit
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_logfile.py b/vendor/Twisted-10.0.0/twisted/test/test_logfile.py
new file mode 100644
index 0000000000..2db6e76943
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_logfile.py
@@ -0,0 +1,314 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.trial import unittest
+
+# system imports
+import os, time, stat
+
+# twisted imports
+from twisted.python import logfile, runtime
+
+
+class LogFileTestCase(unittest.TestCase):
+ """
+ Test the rotating log file.
+ """
+
+ def setUp(self):
+ self.dir = self.mktemp()
+ os.makedirs(self.dir)
+ self.name = "test.log"
+ self.path = os.path.join(self.dir, self.name)
+
+
+ def tearDown(self):
+ """
+ Restore back write rights on created paths: if tests modified the
+ rights, that will allow the paths to be removed easily afterwards.
+ """
+ os.chmod(self.dir, 0777)
+ if os.path.exists(self.path):
+ os.chmod(self.path, 0777)
+
+
+ def testWriting(self):
+ log = logfile.LogFile(self.name, self.dir)
+ log.write("123")
+ log.write("456")
+ log.flush()
+ log.write("7890")
+ log.close()
+
+ f = open(self.path, "r")
+ self.assertEquals(f.read(), "1234567890")
+ f.close()
+
+ def testRotation(self):
+ # this logfile should rotate every 10 bytes
+ log = logfile.LogFile(self.name, self.dir, rotateLength=10)
+
+ # test automatic rotation
+ log.write("123")
+ log.write("4567890")
+ log.write("1" * 11)
+ self.assert_(os.path.exists("%s.1" % self.path))
+ self.assert_(not os.path.exists("%s.2" % self.path))
+ log.write('')
+ self.assert_(os.path.exists("%s.1" % self.path))
+ self.assert_(os.path.exists("%s.2" % self.path))
+ self.assert_(not os.path.exists("%s.3" % self.path))
+ log.write("3")
+ self.assert_(not os.path.exists("%s.3" % self.path))
+
+ # test manual rotation
+ log.rotate()
+ self.assert_(os.path.exists("%s.3" % self.path))
+ self.assert_(not os.path.exists("%s.4" % self.path))
+ log.close()
+
+ self.assertEquals(log.listLogs(), [1, 2, 3])
+
+ def testAppend(self):
+ log = logfile.LogFile(self.name, self.dir)
+ log.write("0123456789")
+ log.close()
+
+ log = logfile.LogFile(self.name, self.dir)
+ self.assertEquals(log.size, 10)
+ self.assertEquals(log._file.tell(), log.size)
+ log.write("abc")
+ self.assertEquals(log.size, 13)
+ self.assertEquals(log._file.tell(), log.size)
+ f = log._file
+ f.seek(0, 0)
+ self.assertEquals(f.read(), "0123456789abc")
+ log.close()
+
+ def testLogReader(self):
+ log = logfile.LogFile(self.name, self.dir)
+ log.write("abc\n")
+ log.write("def\n")
+ log.rotate()
+ log.write("ghi\n")
+ log.flush()
+
+ # check reading logs
+ self.assertEquals(log.listLogs(), [1])
+ reader = log.getCurrentLog()
+ reader._file.seek(0)
+ self.assertEquals(reader.readLines(), ["ghi\n"])
+ self.assertEquals(reader.readLines(), [])
+ reader.close()
+ reader = log.getLog(1)
+ self.assertEquals(reader.readLines(), ["abc\n", "def\n"])
+ self.assertEquals(reader.readLines(), [])
+ reader.close()
+
+ # check getting illegal log readers
+ self.assertRaises(ValueError, log.getLog, 2)
+ self.assertRaises(TypeError, log.getLog, "1")
+
+ # check that log numbers are higher for older logs
+ log.rotate()
+ self.assertEquals(log.listLogs(), [1, 2])
+ reader = log.getLog(1)
+ reader._file.seek(0)
+ self.assertEquals(reader.readLines(), ["ghi\n"])
+ self.assertEquals(reader.readLines(), [])
+ reader.close()
+ reader = log.getLog(2)
+ self.assertEquals(reader.readLines(), ["abc\n", "def\n"])
+ self.assertEquals(reader.readLines(), [])
+ reader.close()
+
+ def testModePreservation(self):
+ """
+ Check rotated files have same permissions as original.
+ """
+ f = open(self.path, "w").close()
+ os.chmod(self.path, 0707)
+ mode = os.stat(self.path)[stat.ST_MODE]
+ log = logfile.LogFile(self.name, self.dir)
+ log.write("abc")
+ log.rotate()
+ self.assertEquals(mode, os.stat(self.path)[stat.ST_MODE])
+
+
+ def test_noPermission(self):
+ """
+ Check it keeps working when permission on dir changes.
+ """
+ log = logfile.LogFile(self.name, self.dir)
+ log.write("abc")
+
+ # change permissions so rotation would fail
+ os.chmod(self.dir, 0555)
+
+ # if this succeeds, chmod doesn't restrict us, so we can't
+ # do the test
+ try:
+ f = open(os.path.join(self.dir,"xxx"), "w")
+ except (OSError, IOError):
+ pass
+ else:
+ f.close()
+ return
+
+ log.rotate() # this should not fail
+
+ log.write("def")
+ log.flush()
+
+ f = log._file
+ self.assertEquals(f.tell(), 6)
+ f.seek(0, 0)
+ self.assertEquals(f.read(), "abcdef")
+ log.close()
+
+
+ def test_maxNumberOfLog(self):
+ """
+ Test it respect the limit on the number of files when maxRotatedFiles
+ is not None.
+ """
+ log = logfile.LogFile(self.name, self.dir, rotateLength=10,
+ maxRotatedFiles=3)
+ log.write("1" * 11)
+ log.write("2" * 11)
+ self.failUnless(os.path.exists("%s.1" % self.path))
+
+ log.write("3" * 11)
+ self.failUnless(os.path.exists("%s.2" % self.path))
+
+ log.write("4" * 11)
+ self.failUnless(os.path.exists("%s.3" % self.path))
+ self.assertEquals(file("%s.3" % self.path).read(), "1" * 11)
+
+ log.write("5" * 11)
+ self.assertEquals(file("%s.3" % self.path).read(), "2" * 11)
+ self.failUnless(not os.path.exists("%s.4" % self.path))
+
+ def test_fromFullPath(self):
+ """
+ Test the fromFullPath method.
+ """
+ log1 = logfile.LogFile(self.name, self.dir, 10, defaultMode=0777)
+ log2 = logfile.LogFile.fromFullPath(self.path, 10, defaultMode=0777)
+ self.assertEquals(log1.name, log2.name)
+ self.assertEquals(os.path.abspath(log1.path), log2.path)
+ self.assertEquals(log1.rotateLength, log2.rotateLength)
+ self.assertEquals(log1.defaultMode, log2.defaultMode)
+
+ def test_defaultPermissions(self):
+ """
+ Test the default permission of the log file: if the file exist, it
+ should keep the permission.
+ """
+ f = file(self.path, "w")
+ os.chmod(self.path, 0707)
+ currentMode = stat.S_IMODE(os.stat(self.path)[stat.ST_MODE])
+ f.close()
+ log1 = logfile.LogFile(self.name, self.dir)
+ self.assertEquals(stat.S_IMODE(os.stat(self.path)[stat.ST_MODE]),
+ currentMode)
+
+
+ def test_specifiedPermissions(self):
+ """
+ Test specifying the permissions used on the log file.
+ """
+ log1 = logfile.LogFile(self.name, self.dir, defaultMode=0066)
+ mode = stat.S_IMODE(os.stat(self.path)[stat.ST_MODE])
+ if runtime.platform.isWindows():
+ # The only thing we can get here is global read-only
+ self.assertEquals(mode, 0444)
+ else:
+ self.assertEquals(mode, 0066)
+
+
+ def test_reopen(self):
+ """
+ L{logfile.LogFile.reopen} allows to rename the currently used file and
+ make L{logfile.LogFile} create a new file.
+ """
+ log1 = logfile.LogFile(self.name, self.dir)
+ log1.write("hello1")
+ savePath = os.path.join(self.dir, "save.log")
+ os.rename(self.path, savePath)
+ log1.reopen()
+ log1.write("hello2")
+ log1.close()
+
+ f = open(self.path, "r")
+ self.assertEquals(f.read(), "hello2")
+ f.close()
+ f = open(savePath, "r")
+ self.assertEquals(f.read(), "hello1")
+ f.close()
+
+ if runtime.platform.isWindows():
+ test_reopen.skip = "Can't test reopen on Windows"
+
+
+
+class RiggedDailyLogFile(logfile.DailyLogFile):
+ _clock = 0.0
+
+ def _openFile(self):
+ logfile.DailyLogFile._openFile(self)
+ # rig the date to match _clock, not mtime
+ self.lastDate = self.toDate()
+
+ def toDate(self, *args):
+ if args:
+ return time.gmtime(*args)[:3]
+ return time.gmtime(self._clock)[:3]
+
+class DailyLogFileTestCase(unittest.TestCase):
+ """
+ Test rotating log file.
+ """
+
+ def setUp(self):
+ self.dir = self.mktemp()
+ os.makedirs(self.dir)
+ self.name = "testdaily.log"
+ self.path = os.path.join(self.dir, self.name)
+
+
+ def testWriting(self):
+ log = RiggedDailyLogFile(self.name, self.dir)
+ log.write("123")
+ log.write("456")
+ log.flush()
+ log.write("7890")
+ log.close()
+
+ f = open(self.path, "r")
+ self.assertEquals(f.read(), "1234567890")
+ f.close()
+
+ def testRotation(self):
+ # this logfile should rotate every 10 bytes
+ log = RiggedDailyLogFile(self.name, self.dir)
+ days = [(self.path + '.' + log.suffix(day * 86400)) for day in range(3)]
+
+ # test automatic rotation
+ log._clock = 0.0 # 1970/01/01 00:00.00
+ log.write("123")
+ log._clock = 43200 # 1970/01/01 12:00.00
+ log.write("4567890")
+ log._clock = 86400 # 1970/01/02 00:00.00
+ log.write("1" * 11)
+ self.assert_(os.path.exists(days[0]))
+ self.assert_(not os.path.exists(days[1]))
+ log._clock = 172800 # 1970/01/03 00:00.00
+ log.write('')
+ self.assert_(os.path.exists(days[0]))
+ self.assert_(os.path.exists(days[1]))
+ self.assert_(not os.path.exists(days[2]))
+ log._clock = 259199 # 1970/01/03 23:59.59
+ log.write("3")
+ self.assert_(not os.path.exists(days[2]))
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_loopback.py b/vendor/Twisted-10.0.0/twisted/test/test_loopback.py
new file mode 100644
index 0000000000..4b741b78e3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_loopback.py
@@ -0,0 +1,433 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test case for L{twisted.protocols.loopback}.
+"""
+
+from zope.interface import implements
+
+from twisted.trial import unittest
+from twisted.trial.util import suppress as SUPPRESS
+from twisted.protocols import basic, loopback
+from twisted.internet import defer
+from twisted.internet.protocol import Protocol
+from twisted.internet.defer import Deferred
+from twisted.internet.interfaces import IAddress, IPushProducer, IPullProducer
+from twisted.internet import reactor, interfaces
+
+
+class SimpleProtocol(basic.LineReceiver):
+ def __init__(self):
+ self.conn = defer.Deferred()
+ self.lines = []
+ self.connLost = []
+
+ def connectionMade(self):
+ self.conn.callback(None)
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ self.connLost.append(reason)
+
+
+class DoomProtocol(SimpleProtocol):
+ i = 0
+ def lineReceived(self, line):
+ self.i += 1
+ if self.i < 4:
+ # by this point we should have connection closed,
+ # but just in case we didn't we won't ever send 'Hello 4'
+ self.sendLine("Hello %d" % self.i)
+ SimpleProtocol.lineReceived(self, line)
+ if self.lines[-1] == "Hello 3":
+ self.transport.loseConnection()
+
+
+class LoopbackTestCaseMixin:
+ def testRegularFunction(self):
+ s = SimpleProtocol()
+ c = SimpleProtocol()
+
+ def sendALine(result):
+ s.sendLine("THIS IS LINE ONE!")
+ s.transport.loseConnection()
+ s.conn.addCallback(sendALine)
+
+ def check(ignored):
+ self.assertEquals(c.lines, ["THIS IS LINE ONE!"])
+ self.assertEquals(len(s.connLost), 1)
+ self.assertEquals(len(c.connLost), 1)
+ d = defer.maybeDeferred(self.loopbackFunc, s, c)
+ d.addCallback(check)
+ return d
+
+ def testSneakyHiddenDoom(self):
+ s = DoomProtocol()
+ c = DoomProtocol()
+
+ def sendALine(result):
+ s.sendLine("DOOM LINE")
+ s.conn.addCallback(sendALine)
+
+ def check(ignored):
+ self.assertEquals(s.lines, ['Hello 1', 'Hello 2', 'Hello 3'])
+ self.assertEquals(c.lines, ['DOOM LINE', 'Hello 1', 'Hello 2', 'Hello 3'])
+ self.assertEquals(len(s.connLost), 1)
+ self.assertEquals(len(c.connLost), 1)
+ d = defer.maybeDeferred(self.loopbackFunc, s, c)
+ d.addCallback(check)
+ return d
+
+
+
+class LoopbackTestCase(LoopbackTestCaseMixin, unittest.TestCase):
+ loopbackFunc = staticmethod(loopback.loopback)
+
+ def testRegularFunction(self):
+ """
+ Suppress loopback deprecation warning.
+ """
+ return LoopbackTestCaseMixin.testRegularFunction(self)
+ testRegularFunction.suppress = [
+ SUPPRESS(message="loopback\(\) is deprecated",
+ category=DeprecationWarning)]
+
+
+
+class LoopbackAsyncTestCase(LoopbackTestCase):
+ loopbackFunc = staticmethod(loopback.loopbackAsync)
+
+
+ def test_makeConnection(self):
+ """
+ Test that the client and server protocol both have makeConnection
+ invoked on them by loopbackAsync.
+ """
+ class TestProtocol(Protocol):
+ transport = None
+ def makeConnection(self, transport):
+ self.transport = transport
+
+ server = TestProtocol()
+ client = TestProtocol()
+ loopback.loopbackAsync(server, client)
+ self.failIfEqual(client.transport, None)
+ self.failIfEqual(server.transport, None)
+
+
+ def _hostpeertest(self, get, testServer):
+ """
+ Test one of the permutations of client/server host/peer.
+ """
+ class TestProtocol(Protocol):
+ def makeConnection(self, transport):
+ Protocol.makeConnection(self, transport)
+ self.onConnection.callback(transport)
+
+ if testServer:
+ server = TestProtocol()
+ d = server.onConnection = Deferred()
+ client = Protocol()
+ else:
+ server = Protocol()
+ client = TestProtocol()
+ d = client.onConnection = Deferred()
+
+ loopback.loopbackAsync(server, client)
+
+ def connected(transport):
+ host = getattr(transport, get)()
+ self.failUnless(IAddress.providedBy(host))
+
+ return d.addCallback(connected)
+
+
+ def test_serverHost(self):
+ """
+ Test that the server gets a transport with a properly functioning
+ implementation of L{ITransport.getHost}.
+ """
+ return self._hostpeertest("getHost", True)
+
+
+ def test_serverPeer(self):
+ """
+ Like C{test_serverHost} but for L{ITransport.getPeer}
+ """
+ return self._hostpeertest("getPeer", True)
+
+
+ def test_clientHost(self, get="getHost"):
+ """
+ Test that the client gets a transport with a properly functioning
+ implementation of L{ITransport.getHost}.
+ """
+ return self._hostpeertest("getHost", False)
+
+
+ def test_clientPeer(self):
+ """
+ Like C{test_clientHost} but for L{ITransport.getPeer}.
+ """
+ return self._hostpeertest("getPeer", False)
+
+
+ def _greetingtest(self, write, testServer):
+ """
+ Test one of the permutations of write/writeSequence client/server.
+ """
+ class GreeteeProtocol(Protocol):
+ bytes = ""
+ def dataReceived(self, bytes):
+ self.bytes += bytes
+ if self.bytes == "bytes":
+ self.received.callback(None)
+
+ class GreeterProtocol(Protocol):
+ def connectionMade(self):
+ getattr(self.transport, write)("bytes")
+
+ if testServer:
+ server = GreeterProtocol()
+ client = GreeteeProtocol()
+ d = client.received = Deferred()
+ else:
+ server = GreeteeProtocol()
+ d = server.received = Deferred()
+ client = GreeterProtocol()
+
+ loopback.loopbackAsync(server, client)
+ return d
+
+
+ def test_clientGreeting(self):
+ """
+ Test that on a connection where the client speaks first, the server
+ receives the bytes sent by the client.
+ """
+ return self._greetingtest("write", False)
+
+
+ def test_clientGreetingSequence(self):
+ """
+ Like C{test_clientGreeting}, but use C{writeSequence} instead of
+ C{write} to issue the greeting.
+ """
+ return self._greetingtest("writeSequence", False)
+
+
+ def test_serverGreeting(self, write="write"):
+ """
+ Test that on a connection where the server speaks first, the client
+ receives the bytes sent by the server.
+ """
+ return self._greetingtest("write", True)
+
+
+ def test_serverGreetingSequence(self):
+ """
+ Like C{test_serverGreeting}, but use C{writeSequence} instead of
+ C{write} to issue the greeting.
+ """
+ return self._greetingtest("writeSequence", True)
+
+
+ def _producertest(self, producerClass):
+ toProduce = map(str, range(0, 10))
+
+ class ProducingProtocol(Protocol):
+ def connectionMade(self):
+ self.producer = producerClass(list(toProduce))
+ self.producer.start(self.transport)
+
+ class ReceivingProtocol(Protocol):
+ bytes = ""
+ def dataReceived(self, bytes):
+ self.bytes += bytes
+ if self.bytes == ''.join(toProduce):
+ self.received.callback((client, server))
+
+ server = ProducingProtocol()
+ client = ReceivingProtocol()
+ client.received = Deferred()
+
+ loopback.loopbackAsync(server, client)
+ return client.received
+
+
+ def test_pushProducer(self):
+ """
+ Test a push producer registered against a loopback transport.
+ """
+ class PushProducer(object):
+ implements(IPushProducer)
+ resumed = False
+
+ def __init__(self, toProduce):
+ self.toProduce = toProduce
+
+ def resumeProducing(self):
+ self.resumed = True
+
+ def start(self, consumer):
+ self.consumer = consumer
+ consumer.registerProducer(self, True)
+ self._produceAndSchedule()
+
+ def _produceAndSchedule(self):
+ if self.toProduce:
+ self.consumer.write(self.toProduce.pop(0))
+ reactor.callLater(0, self._produceAndSchedule)
+ else:
+ self.consumer.unregisterProducer()
+ d = self._producertest(PushProducer)
+
+ def finished((client, server)):
+ self.failIf(
+ server.producer.resumed,
+ "Streaming producer should not have been resumed.")
+ d.addCallback(finished)
+ return d
+
+
+ def test_pullProducer(self):
+ """
+ Test a pull producer registered against a loopback transport.
+ """
+ class PullProducer(object):
+ implements(IPullProducer)
+
+ def __init__(self, toProduce):
+ self.toProduce = toProduce
+
+ def start(self, consumer):
+ self.consumer = consumer
+ self.consumer.registerProducer(self, False)
+
+ def resumeProducing(self):
+ self.consumer.write(self.toProduce.pop(0))
+ if not self.toProduce:
+ self.consumer.unregisterProducer()
+ return self._producertest(PullProducer)
+
+
+ def test_writeNotReentrant(self):
+ """
+ L{loopback.loopbackAsync} does not call a protocol's C{dataReceived}
+ method while that protocol's transport's C{write} method is higher up
+ on the stack.
+ """
+ class Server(Protocol):
+ def dataReceived(self, bytes):
+ self.transport.write("bytes")
+
+ class Client(Protocol):
+ ready = False
+
+ def connectionMade(self):
+ reactor.callLater(0, self.go)
+
+ def go(self):
+ self.transport.write("foo")
+ self.ready = True
+
+ def dataReceived(self, bytes):
+ self.wasReady = self.ready
+ self.transport.loseConnection()
+
+
+ server = Server()
+ client = Client()
+ d = loopback.loopbackAsync(client, server)
+ def cbFinished(ignored):
+ self.assertTrue(client.wasReady)
+ d.addCallback(cbFinished)
+ return d
+
+
+ def test_pumpPolicy(self):
+ """
+ The callable passed as the value for the C{pumpPolicy} parameter to
+ L{loopbackAsync} is called with a L{_LoopbackQueue} of pending bytes
+ and a protocol to which they should be delivered.
+ """
+ pumpCalls = []
+ def dummyPolicy(queue, target):
+ bytes = []
+ while queue:
+ bytes.append(queue.get())
+ pumpCalls.append((target, bytes))
+
+ client = Protocol()
+ server = Protocol()
+
+ finished = loopback.loopbackAsync(server, client, dummyPolicy)
+ self.assertEquals(pumpCalls, [])
+
+ client.transport.write("foo")
+ client.transport.write("bar")
+ server.transport.write("baz")
+ server.transport.write("quux")
+ server.transport.loseConnection()
+
+ def cbComplete(ignored):
+ self.assertEquals(
+ pumpCalls,
+ # The order here is somewhat arbitrary. The implementation
+ # happens to always deliver data to the client first.
+ [(client, ["baz", "quux", None]),
+ (server, ["foo", "bar"])])
+ finished.addCallback(cbComplete)
+ return finished
+
+
+ def test_identityPumpPolicy(self):
+ """
+ L{identityPumpPolicy} is a pump policy which calls the target's
+ C{dataReceived} method one for each string in the queue passed to it.
+ """
+ bytes = []
+ client = Protocol()
+ client.dataReceived = bytes.append
+ queue = loopback._LoopbackQueue()
+ queue.put("foo")
+ queue.put("bar")
+ queue.put(None)
+
+ loopback.identityPumpPolicy(queue, client)
+
+ self.assertEquals(bytes, ["foo", "bar"])
+
+
+ def test_collapsingPumpPolicy(self):
+ """
+ L{collapsingPumpPolicy} is a pump policy which calls the target's
+ C{dataReceived} only once with all of the strings in the queue passed
+ to it joined together.
+ """
+ bytes = []
+ client = Protocol()
+ client.dataReceived = bytes.append
+ queue = loopback._LoopbackQueue()
+ queue.put("foo")
+ queue.put("bar")
+ queue.put(None)
+
+ loopback.collapsingPumpPolicy(queue, client)
+
+ self.assertEquals(bytes, ["foobar"])
+
+
+
+class LoopbackTCPTestCase(LoopbackTestCase):
+ loopbackFunc = staticmethod(loopback.loopbackTCP)
+
+
+class LoopbackUNIXTestCase(LoopbackTestCase):
+ loopbackFunc = staticmethod(loopback.loopbackUNIX)
+
+ if interfaces.IReactorUNIX(reactor, None) is None:
+ skip = "Current reactor does not support UNIX sockets"
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_manhole.py b/vendor/Twisted-10.0.0/twisted/test/test_manhole.py
new file mode 100644
index 0000000000..254df59a21
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_manhole.py
@@ -0,0 +1,75 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.trial import unittest
+from twisted.manhole import service
+from twisted.spread.util import LocalAsRemote
+
+class Dummy:
+ pass
+
+class DummyTransport:
+ def getHost(self):
+ return 'INET', '127.0.0.1', 0
+
+class DummyManholeClient(LocalAsRemote):
+ zero = 0
+ broker = Dummy()
+ broker.transport = DummyTransport()
+
+ def __init__(self):
+ self.messages = []
+
+ def console(self, messages):
+ self.messages.extend(messages)
+
+ def receiveExplorer(self, xplorer):
+ pass
+
+ def setZero(self):
+ self.zero = len(self.messages)
+
+ def getMessages(self):
+ return self.messages[self.zero:]
+
+ # local interface
+ sync_console = console
+ sync_receiveExplorer = receiveExplorer
+ sync_setZero = setZero
+ sync_getMessages = getMessages
+
+class ManholeTest(unittest.TestCase):
+ """Various tests for the manhole service.
+
+ Both the the importIdentity and importMain tests are known to fail
+ when the __name__ in the manhole namespace is set to certain
+ values.
+ """
+ def setUp(self):
+ self.service = service.Service()
+ self.p = service.Perspective(self.service)
+ self.client = DummyManholeClient()
+ self.p.attached(self.client, None)
+
+ def test_importIdentity(self):
+ """Making sure imported module is the same as one previously loaded.
+ """
+ self.p.perspective_do("from twisted.manhole import service")
+ self.client.setZero()
+ self.p.perspective_do("int(service is sys.modules['twisted.manhole.service'])")
+ msg = self.client.getMessages()[0]
+ self.failUnlessEqual(msg, ('result',"1\n"))
+
+ def test_importMain(self):
+ """Trying to import __main__"""
+ self.client.setZero()
+ self.p.perspective_do("import __main__")
+ if self.client.getMessages():
+ msg = self.client.getMessages()[0]
+ if msg[0] in ("exception","stderr"):
+ self.fail(msg[1])
+
+#if __name__=='__main__':
+# unittest.main()
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_memcache.py b/vendor/Twisted-10.0.0/twisted/test/test_memcache.py
new file mode 100644
index 0000000000..d60c48d2ae
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_memcache.py
@@ -0,0 +1,663 @@
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test the memcache client protocol.
+"""
+
+from twisted.internet.error import ConnectionDone
+
+from twisted.protocols.memcache import MemCacheProtocol, NoSuchCommand
+from twisted.protocols.memcache import ClientError, ServerError
+
+from twisted.trial.unittest import TestCase
+from twisted.test.proto_helpers import StringTransportWithDisconnection
+from twisted.internet.task import Clock
+from twisted.internet.defer import Deferred, gatherResults, TimeoutError
+from twisted.internet.defer import DeferredList
+
+
+
+class CommandMixin:
+ """
+ Setup and tests for basic invocation of L{MemCacheProtocol} commands.
+ """
+
+ def _test(self, d, send, recv, result):
+ """
+ Helper test method to test the resulting C{Deferred} of a
+ L{MemCacheProtocol} command.
+ """
+ raise NotImplementedError()
+
+
+ def test_get(self):
+ """
+ L{MemCacheProtocol.get} returns a L{Deferred} which is called back with
+ the value and the flag associated with the given key if the server
+ returns a successful result.
+ """
+ return self._test(self.proto.get("foo"), "get foo\r\n",
+ "VALUE foo 0 3\r\nbar\r\nEND\r\n", (0, "bar"))
+
+
+ def test_emptyGet(self):
+ """
+ Test getting a non-available key: it succeeds but return C{None} as
+ value and C{0} as flag.
+ """
+ return self._test(self.proto.get("foo"), "get foo\r\n",
+ "END\r\n", (0, None))
+
+
+ def test_getMultiple(self):
+ """
+ L{MemCacheProtocol.getMultiple} returns a L{Deferred} which is called
+ back with a dictionary of flag, value for each given key.
+ """
+ return self._test(self.proto.getMultiple(['foo', 'cow']),
+ "get foo cow\r\n",
+ "VALUE foo 0 3\r\nbar\r\nVALUE cow 0 7\r\nchicken\r\nEND\r\n",
+ {'cow': (0, 'chicken'), 'foo': (0, 'bar')})
+
+
+ def test_getMultipleWithEmpty(self):
+ """
+ When L{MemCacheProtocol.getMultiple} is called with non-available keys,
+ the corresponding tuples are (0, None).
+ """
+ return self._test(self.proto.getMultiple(['foo', 'cow']),
+ "get foo cow\r\n",
+ "VALUE cow 1 3\r\nbar\r\nEND\r\n",
+ {'cow': (1, 'bar'), 'foo': (0, None)})
+
+
+ def test_set(self):
+ """
+ L{MemCacheProtocol.set} returns a L{Deferred} which is called back with
+ C{True} when the operation succeeds.
+ """
+ return self._test(self.proto.set("foo", "bar"),
+ "set foo 0 0 3\r\nbar\r\n", "STORED\r\n", True)
+
+
+ def test_add(self):
+ """
+ L{MemCacheProtocol.add} returns a L{Deferred} which is called back with
+ C{True} when the operation succeeds.
+ """
+ return self._test(self.proto.add("foo", "bar"),
+ "add foo 0 0 3\r\nbar\r\n", "STORED\r\n", True)
+
+
+ def test_replace(self):
+ """
+ L{MemCacheProtocol.replace} returns a L{Deferred} which is called back
+ with C{True} when the operation succeeds.
+ """
+ return self._test(self.proto.replace("foo", "bar"),
+ "replace foo 0 0 3\r\nbar\r\n", "STORED\r\n", True)
+
+
+ def test_errorAdd(self):
+ """
+ Test an erroneous add: if a L{MemCacheProtocol.add} is called but the
+ key already exists on the server, it returns a B{NOT STORED} answer,
+ which calls back the resulting L{Deferred} with C{False}.
+ """
+ return self._test(self.proto.add("foo", "bar"),
+ "add foo 0 0 3\r\nbar\r\n", "NOT STORED\r\n", False)
+
+
+ def test_errorReplace(self):
+ """
+ Test an erroneous replace: if a L{MemCacheProtocol.replace} is called
+ but the key doesn't exist on the server, it returns a B{NOT STORED}
+ answer, which calls back the resulting L{Deferred} with C{False}.
+ """
+ return self._test(self.proto.replace("foo", "bar"),
+ "replace foo 0 0 3\r\nbar\r\n", "NOT STORED\r\n", False)
+
+
+ def test_delete(self):
+ """
+ L{MemCacheProtocol.delete} returns a L{Deferred} which is called back
+ with C{True} when the server notifies a success.
+ """
+ return self._test(self.proto.delete("bar"), "delete bar\r\n",
+ "DELETED\r\n", True)
+
+
+ def test_errorDelete(self):
+ """
+ Test a error during a delete: if key doesn't exist on the server, it
+ returns a B{NOT FOUND} answer which calls back the resulting L{Deferred}
+ with C{False}.
+ """
+ return self._test(self.proto.delete("bar"), "delete bar\r\n",
+ "NOT FOUND\r\n", False)
+
+
+ def test_increment(self):
+ """
+ Test incrementing a variable: L{MemCacheProtocol.increment} returns a
+ L{Deferred} which is called back with the incremented value of the
+ given key.
+ """
+ return self._test(self.proto.increment("foo"), "incr foo 1\r\n",
+ "4\r\n", 4)
+
+
+ def test_decrement(self):
+ """
+ Test decrementing a variable: L{MemCacheProtocol.decrement} returns a
+ L{Deferred} which is called back with the decremented value of the
+ given key.
+ """
+ return self._test(
+ self.proto.decrement("foo"), "decr foo 1\r\n", "5\r\n", 5)
+
+
+ def test_incrementVal(self):
+ """
+ L{MemCacheProtocol.increment} takes an optional argument C{value} which
+ replaces the default value of 1 when specified.
+ """
+ return self._test(self.proto.increment("foo", 8), "incr foo 8\r\n",
+ "4\r\n", 4)
+
+
+ def test_decrementVal(self):
+ """
+ L{MemCacheProtocol.decrement} takes an optional argument C{value} which
+ replaces the default value of 1 when specified.
+ """
+ return self._test(self.proto.decrement("foo", 3), "decr foo 3\r\n",
+ "5\r\n", 5)
+
+
+ def test_stats(self):
+ """
+ Test retrieving server statistics via the L{MemCacheProtocol.stats}
+ command: it parses the data sent by the server and calls back the
+ resulting L{Deferred} with a dictionary of the received statistics.
+ """
+ return self._test(self.proto.stats(), "stats\r\n",
+ "STAT foo bar\r\nSTAT egg spam\r\nEND\r\n",
+ {"foo": "bar", "egg": "spam"})
+
+
+ def test_statsWithArgument(self):
+ """
+ L{MemCacheProtocol.stats} takes an optional C{str} argument which,
+ if specified, is sent along with the I{STAT} command. The I{STAT}
+ responses from the server are parsed as key/value pairs and returned
+ as a C{dict} (as in the case where the argument is not specified).
+ """
+ return self._test(self.proto.stats("blah"), "stats blah\r\n",
+ "STAT foo bar\r\nSTAT egg spam\r\nEND\r\n",
+ {"foo": "bar", "egg": "spam"})
+
+
+ def test_version(self):
+ """
+ Test version retrieval via the L{MemCacheProtocol.version} command: it
+ returns a L{Deferred} which is called back with the version sent by the
+ server.
+ """
+ return self._test(self.proto.version(), "version\r\n",
+ "VERSION 1.1\r\n", "1.1")
+
+
+ def test_flushAll(self):
+ """
+ L{MemCacheProtocol.flushAll} returns a L{Deferred} which is called back
+ with C{True} if the server acknowledges success.
+ """
+ return self._test(self.proto.flushAll(), "flush_all\r\n",
+ "OK\r\n", True)
+
+
+
+class MemCacheTestCase(CommandMixin, TestCase):
+ """
+ Test client protocol class L{MemCacheProtocol}.
+ """
+
+ def setUp(self):
+ """
+ Create a memcache client, connect it to a string protocol, and make it
+ use a deterministic clock.
+ """
+ self.proto = MemCacheProtocol()
+ self.clock = Clock()
+ self.proto.callLater = self.clock.callLater
+ self.transport = StringTransportWithDisconnection()
+ self.transport.protocol = self.proto
+ self.proto.makeConnection(self.transport)
+
+
+ def _test(self, d, send, recv, result):
+ """
+ Implementation of C{_test} which checks that the command sends C{send}
+ data, and that upon reception of C{recv} the result is C{result}.
+
+ @param d: the resulting deferred from the memcache command.
+ @type d: C{Deferred}
+
+ @param send: the expected data to be sent.
+ @type send: C{str}
+
+ @param recv: the data to simulate as reception.
+ @type recv: C{str}
+
+ @param result: the expected result.
+ @type result: C{any}
+ """
+ def cb(res):
+ self.assertEquals(res, result)
+ self.assertEquals(self.transport.value(), send)
+ d.addCallback(cb)
+ self.proto.dataReceived(recv)
+ return d
+
+
+ def test_invalidGetResponse(self):
+ """
+ If the value returned doesn't match the expected key of the current
+ C{get} command, an error is raised in L{MemCacheProtocol.dataReceived}.
+ """
+ self.proto.get("foo")
+ s = "spamegg"
+ self.assertRaises(RuntimeError,
+ self.proto.dataReceived,
+ "VALUE bar 0 %s\r\n%s\r\nEND\r\n" % (len(s), s))
+
+
+ def test_invalidMultipleGetResponse(self):
+ """
+ If the value returned doesn't match one the expected keys of the
+ current multiple C{get} command, an error is raised error in
+ L{MemCacheProtocol.dataReceived}.
+ """
+ self.proto.getMultiple(["foo", "bar"])
+ s = "spamegg"
+ self.assertRaises(RuntimeError,
+ self.proto.dataReceived,
+ "VALUE egg 0 %s\r\n%s\r\nEND\r\n" % (len(s), s))
+
+
+ def test_timeOut(self):
+ """
+ Test the timeout on outgoing requests: when timeout is detected, all
+ current commands fail with a L{TimeoutError}, and the connection is
+ closed.
+ """
+ d1 = self.proto.get("foo")
+ d2 = self.proto.get("bar")
+ d3 = Deferred()
+ self.proto.connectionLost = d3.callback
+
+ self.clock.advance(self.proto.persistentTimeOut)
+ self.assertFailure(d1, TimeoutError)
+ self.assertFailure(d2, TimeoutError)
+ def checkMessage(error):
+ self.assertEquals(str(error), "Connection timeout")
+ d1.addCallback(checkMessage)
+ return gatherResults([d1, d2, d3])
+
+
+ def test_timeoutRemoved(self):
+ """
+ When a request gets a response, no pending timeout call remains around.
+ """
+ d = self.proto.get("foo")
+
+ self.clock.advance(self.proto.persistentTimeOut - 1)
+ self.proto.dataReceived("VALUE foo 0 3\r\nbar\r\nEND\r\n")
+
+ def check(result):
+ self.assertEquals(result, (0, "bar"))
+ self.assertEquals(len(self.clock.calls), 0)
+ d.addCallback(check)
+ return d
+
+
+ def test_timeOutRaw(self):
+ """
+ Test the timeout when raw mode was started: the timeout is not reset
+ until all the data has been received, so we can have a L{TimeoutError}
+ when waiting for raw data.
+ """
+ d1 = self.proto.get("foo")
+ d2 = Deferred()
+ self.proto.connectionLost = d2.callback
+
+ self.proto.dataReceived("VALUE foo 0 10\r\n12345")
+ self.clock.advance(self.proto.persistentTimeOut)
+ self.assertFailure(d1, TimeoutError)
+ return gatherResults([d1, d2])
+
+
+ def test_timeOutStat(self):
+ """
+ Test the timeout when stat command has started: the timeout is not
+ reset until the final B{END} is received.
+ """
+ d1 = self.proto.stats()
+ d2 = Deferred()
+ self.proto.connectionLost = d2.callback
+
+ self.proto.dataReceived("STAT foo bar\r\n")
+ self.clock.advance(self.proto.persistentTimeOut)
+ self.assertFailure(d1, TimeoutError)
+ return gatherResults([d1, d2])
+
+
+ def test_timeoutPipelining(self):
+ """
+ When two requests are sent, a timeout call remains around for the
+ second request, and its timeout time is correct.
+ """
+ d1 = self.proto.get("foo")
+ d2 = self.proto.get("bar")
+ d3 = Deferred()
+ self.proto.connectionLost = d3.callback
+
+ self.clock.advance(self.proto.persistentTimeOut - 1)
+ self.proto.dataReceived("VALUE foo 0 3\r\nbar\r\nEND\r\n")
+
+ def check(result):
+ self.assertEquals(result, (0, "bar"))
+ self.assertEquals(len(self.clock.calls), 1)
+ for i in range(self.proto.persistentTimeOut):
+ self.clock.advance(1)
+ return self.assertFailure(d2, TimeoutError).addCallback(checkTime)
+ def checkTime(ignored):
+ # Check that the timeout happened C{self.proto.persistentTimeOut}
+ # after the last response
+ self.assertEquals(
+ self.clock.seconds(), 2 * self.proto.persistentTimeOut - 1)
+ d1.addCallback(check)
+ return d1
+
+
+ def test_timeoutNotReset(self):
+ """
+ Check that timeout is not resetted for every command, but keep the
+ timeout from the first command without response.
+ """
+ d1 = self.proto.get("foo")
+ d3 = Deferred()
+ self.proto.connectionLost = d3.callback
+
+ self.clock.advance(self.proto.persistentTimeOut - 1)
+ d2 = self.proto.get("bar")
+ self.clock.advance(1)
+ self.assertFailure(d1, TimeoutError)
+ self.assertFailure(d2, TimeoutError)
+ return gatherResults([d1, d2, d3])
+
+
+ def test_timeoutCleanDeferreds(self):
+ """
+ C{timeoutConnection} cleans the list of commands that it fires with
+ C{TimeoutError}: C{connectionLost} doesn't try to fire them again, but
+ sets the disconnected state so that future commands fail with a
+ C{RuntimeError}.
+ """
+ d1 = self.proto.get("foo")
+ self.clock.advance(self.proto.persistentTimeOut)
+ self.assertFailure(d1, TimeoutError)
+ d2 = self.proto.get("bar")
+ self.assertFailure(d2, RuntimeError)
+ return gatherResults([d1, d2])
+
+
+ def test_connectionLost(self):
+ """
+ When disconnection occurs while commands are still outstanding, the
+ commands fail.
+ """
+ d1 = self.proto.get("foo")
+ d2 = self.proto.get("bar")
+ self.transport.loseConnection()
+ done = DeferredList([d1, d2], consumeErrors=True)
+ def checkFailures(results):
+ for success, result in results:
+ self.assertFalse(success)
+ result.trap(ConnectionDone)
+ return done.addCallback(checkFailures)
+
+
+ def test_tooLongKey(self):
+ """
+ An error is raised when trying to use a too long key: the called
+ command returns a L{Deferred} which fails with a L{ClientError}.
+ """
+ d1 = self.assertFailure(self.proto.set("a" * 500, "bar"), ClientError)
+ d2 = self.assertFailure(self.proto.increment("a" * 500), ClientError)
+ d3 = self.assertFailure(self.proto.get("a" * 500), ClientError)
+ d4 = self.assertFailure(
+ self.proto.append("a" * 500, "bar"), ClientError)
+ d5 = self.assertFailure(
+ self.proto.prepend("a" * 500, "bar"), ClientError)
+ d6 = self.assertFailure(
+ self.proto.getMultiple(["foo", "a" * 500]), ClientError)
+ return gatherResults([d1, d2, d3, d4, d5, d6])
+
+
+ def test_invalidCommand(self):
+ """
+ When an unknown command is sent directly (not through public API), the
+ server answers with an B{ERROR} token, and the command fails with
+ L{NoSuchCommand}.
+ """
+ d = self.proto._set("egg", "foo", "bar", 0, 0, "")
+ self.assertEquals(self.transport.value(), "egg foo 0 0 3\r\nbar\r\n")
+ self.assertFailure(d, NoSuchCommand)
+ self.proto.dataReceived("ERROR\r\n")
+ return d
+
+
+ def test_clientError(self):
+ """
+ Test the L{ClientError} error: when the server sends a B{CLIENT_ERROR}
+ token, the originating command fails with L{ClientError}, and the error
+ contains the text sent by the server.
+ """
+ a = "eggspamm"
+ d = self.proto.set("foo", a)
+ self.assertEquals(self.transport.value(),
+ "set foo 0 0 8\r\neggspamm\r\n")
+ self.assertFailure(d, ClientError)
+ def check(err):
+ self.assertEquals(str(err), "We don't like egg and spam")
+ d.addCallback(check)
+ self.proto.dataReceived("CLIENT_ERROR We don't like egg and spam\r\n")
+ return d
+
+
+ def test_serverError(self):
+ """
+ Test the L{ServerError} error: when the server sends a B{SERVER_ERROR}
+ token, the originating command fails with L{ServerError}, and the error
+ contains the text sent by the server.
+ """
+ a = "eggspamm"
+ d = self.proto.set("foo", a)
+ self.assertEquals(self.transport.value(),
+ "set foo 0 0 8\r\neggspamm\r\n")
+ self.assertFailure(d, ServerError)
+ def check(err):
+ self.assertEquals(str(err), "zomg")
+ d.addCallback(check)
+ self.proto.dataReceived("SERVER_ERROR zomg\r\n")
+ return d
+
+
+ def test_unicodeKey(self):
+ """
+ Using a non-string key as argument to commands raises an error.
+ """
+ d1 = self.assertFailure(self.proto.set(u"foo", "bar"), ClientError)
+ d2 = self.assertFailure(self.proto.increment(u"egg"), ClientError)
+ d3 = self.assertFailure(self.proto.get(1), ClientError)
+ d4 = self.assertFailure(self.proto.delete(u"bar"), ClientError)
+ d5 = self.assertFailure(self.proto.append(u"foo", "bar"), ClientError)
+ d6 = self.assertFailure(self.proto.prepend(u"foo", "bar"), ClientError)
+ d7 = self.assertFailure(
+ self.proto.getMultiple(["egg", 1]), ClientError)
+ return gatherResults([d1, d2, d3, d4, d5, d6, d7])
+
+
+ def test_unicodeValue(self):
+ """
+ Using a non-string value raises an error.
+ """
+ return self.assertFailure(self.proto.set("foo", u"bar"), ClientError)
+
+
+ def test_pipelining(self):
+ """
+ Multiple requests can be sent subsequently to the server, and the
+ protocol orders the responses correctly and dispatch to the
+ corresponding client command.
+ """
+ d1 = self.proto.get("foo")
+ d1.addCallback(self.assertEquals, (0, "bar"))
+ d2 = self.proto.set("bar", "spamspamspam")
+ d2.addCallback(self.assertEquals, True)
+ d3 = self.proto.get("egg")
+ d3.addCallback(self.assertEquals, (0, "spam"))
+ self.assertEquals(self.transport.value(),
+ "get foo\r\nset bar 0 0 12\r\nspamspamspam\r\nget egg\r\n")
+ self.proto.dataReceived("VALUE foo 0 3\r\nbar\r\nEND\r\n"
+ "STORED\r\n"
+ "VALUE egg 0 4\r\nspam\r\nEND\r\n")
+ return gatherResults([d1, d2, d3])
+
+
+ def test_getInChunks(self):
+ """
+ If the value retrieved by a C{get} arrive in chunks, the protocol
+ is able to reconstruct it and to produce the good value.
+ """
+ d = self.proto.get("foo")
+ d.addCallback(self.assertEquals, (0, "0123456789"))
+ self.assertEquals(self.transport.value(), "get foo\r\n")
+ self.proto.dataReceived("VALUE foo 0 10\r\n0123456")
+ self.proto.dataReceived("789")
+ self.proto.dataReceived("\r\nEND")
+ self.proto.dataReceived("\r\n")
+ return d
+
+
+ def test_append(self):
+ """
+ L{MemCacheProtocol.append} behaves like a L{MemCacheProtocol.set}
+ method: it returns a L{Deferred} which is called back with C{True} when
+ the operation succeeds.
+ """
+ return self._test(self.proto.append("foo", "bar"),
+ "append foo 0 0 3\r\nbar\r\n", "STORED\r\n", True)
+
+
+ def test_prepend(self):
+ """
+ L{MemCacheProtocol.prepend} behaves like a L{MemCacheProtocol.set}
+ method: it returns a L{Deferred} which is called back with C{True} when
+ the operation succeeds.
+ """
+ return self._test(self.proto.prepend("foo", "bar"),
+ "prepend foo 0 0 3\r\nbar\r\n", "STORED\r\n", True)
+
+
+ def test_gets(self):
+ """
+ L{MemCacheProtocol.get} handles an additional cas result when
+ C{withIdentifier} is C{True} and forward it in the resulting
+ L{Deferred}.
+ """
+ return self._test(self.proto.get("foo", True), "gets foo\r\n",
+ "VALUE foo 0 3 1234\r\nbar\r\nEND\r\n", (0, "1234", "bar"))
+
+
+ def test_emptyGets(self):
+ """
+ Test getting a non-available key with gets: it succeeds but return
+ C{None} as value, C{0} as flag and an empty cas value.
+ """
+ return self._test(self.proto.get("foo", True), "gets foo\r\n",
+ "END\r\n", (0, "", None))
+
+
+ def test_getsMultiple(self):
+ """
+ L{MemCacheProtocol.getMultiple} handles an additional cas field in the
+ returned tuples if C{withIdentifier} is C{True}.
+ """
+ return self._test(self.proto.getMultiple(["foo", "bar"], True),
+ "gets foo bar\r\n",
+ "VALUE foo 0 3 1234\r\negg\r\nVALUE bar 0 4 2345\r\nspam\r\nEND\r\n",
+ {'bar': (0, '2345', 'spam'), 'foo': (0, '1234', 'egg')})
+
+
+ def test_getsMultipleWithEmpty(self):
+ """
+ When getting a non-available key with L{MemCacheProtocol.getMultiple}
+ when C{withIdentifier} is C{True}, the other keys are retrieved
+ correctly, and the non-available key gets a tuple of C{0} as flag,
+ C{None} as value, and an empty cas value.
+ """
+ return self._test(self.proto.getMultiple(["foo", "bar"], True),
+ "gets foo bar\r\n",
+ "VALUE foo 0 3 1234\r\negg\r\nEND\r\n",
+ {'bar': (0, '', None), 'foo': (0, '1234', 'egg')})
+
+
+ def test_checkAndSet(self):
+ """
+ L{MemCacheProtocol.checkAndSet} passes an additional cas identifier
+ that the server handles to check if the data has to be updated.
+ """
+ return self._test(self.proto.checkAndSet("foo", "bar", cas="1234"),
+ "cas foo 0 0 3 1234\r\nbar\r\n", "STORED\r\n", True)
+
+
+ def test_casUnknowKey(self):
+ """
+ When L{MemCacheProtocol.checkAndSet} response is C{EXISTS}, the
+ resulting L{Deferred} fires with C{False}.
+ """
+ return self._test(self.proto.checkAndSet("foo", "bar", cas="1234"),
+ "cas foo 0 0 3 1234\r\nbar\r\n", "EXISTS\r\n", False)
+
+
+
+class CommandFailureTests(CommandMixin, TestCase):
+ """
+ Tests for correct failure of commands on a disconnected
+ L{MemCacheProtocol}.
+ """
+
+ def setUp(self):
+ """
+ Create a disconnected memcache client, using a deterministic clock.
+ """
+ self.proto = MemCacheProtocol()
+ self.clock = Clock()
+ self.proto.callLater = self.clock.callLater
+ self.transport = StringTransportWithDisconnection()
+ self.transport.protocol = self.proto
+ self.proto.makeConnection(self.transport)
+ self.transport.loseConnection()
+
+
+ def _test(self, d, send, recv, result):
+ """
+ Implementation of C{_test} which checks that the command fails with
+ C{RuntimeError} because the transport is disconnected. All the
+ parameters except C{d} are ignored.
+ """
+ return self.assertFailure(d, RuntimeError)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_modules.py b/vendor/Twisted-10.0.0/twisted/test/test_modules.py
new file mode 100644
index 0000000000..e8ac756a22
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_modules.py
@@ -0,0 +1,391 @@
+# Copyright (c) 2006-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for twisted.python.modules, abstract access to imported or importable
+objects.
+"""
+
+import os
+import sys
+import itertools
+import zipfile
+import compileall
+
+import twisted
+from twisted.trial.unittest import TestCase
+
+from twisted.python import modules
+from twisted.python.filepath import FilePath
+from twisted.python.reflect import namedAny
+
+from twisted.test.test_paths import zipit
+
+
+
+class PySpaceTestCase(TestCase):
+
+ def findByIteration(self, modname, where=modules, importPackages=False):
+ """
+ You don't ever actually want to do this, so it's not in the public API, but
+ sometimes we want to compare the result of an iterative call with a
+ lookup call and make sure they're the same for test purposes.
+ """
+ for modinfo in where.walkModules(importPackages=importPackages):
+ if modinfo.name == modname:
+ return modinfo
+ self.fail("Unable to find module %r through iteration." % (modname,))
+
+
+
+class BasicTests(PySpaceTestCase):
+ def test_nonexistentPaths(self):
+ """
+ Verify that L{modules.walkModules} ignores entries in sys.path which
+ do not exist in the filesystem.
+ """
+ existentPath = FilePath(self.mktemp())
+ os.makedirs(existentPath.child("test_package").path)
+ existentPath.child("test_package").child("__init__.py").setContent("")
+
+ nonexistentPath = FilePath(self.mktemp())
+ self.failIf(nonexistentPath.exists())
+
+ originalSearchPaths = sys.path[:]
+ sys.path[:] = [existentPath.path]
+ try:
+ expected = [modules.getModule("test_package")]
+
+ beforeModules = list(modules.walkModules())
+ sys.path.append(nonexistentPath.path)
+ afterModules = list(modules.walkModules())
+ finally:
+ sys.path[:] = originalSearchPaths
+
+ self.assertEqual(beforeModules, expected)
+ self.assertEqual(afterModules, expected)
+
+
+ def test_nonDirectoryPaths(self):
+ """
+ Verify that L{modules.walkModules} ignores entries in sys.path which
+ refer to regular files in the filesystem.
+ """
+ existentPath = FilePath(self.mktemp())
+ os.makedirs(existentPath.child("test_package").path)
+ existentPath.child("test_package").child("__init__.py").setContent("")
+
+ nonDirectoryPath = FilePath(self.mktemp())
+ self.failIf(nonDirectoryPath.exists())
+ nonDirectoryPath.setContent("zip file or whatever\n")
+
+ originalSearchPaths = sys.path[:]
+ sys.path[:] = [existentPath.path]
+ try:
+ beforeModules = list(modules.walkModules())
+ sys.path.append(nonDirectoryPath.path)
+ afterModules = list(modules.walkModules())
+ finally:
+ sys.path[:] = originalSearchPaths
+
+ self.assertEqual(beforeModules, afterModules)
+
+
+ def test_twistedShowsUp(self):
+ """
+ Scrounge around in the top-level module namespace and make sure that
+ Twisted shows up, and that the module thusly obtained is the same as
+ the module that we find when we look for it explicitly by name.
+ """
+ self.assertEquals(modules.getModule('twisted'),
+ self.findByIteration("twisted"))
+
+
+ def test_dottedNames(self):
+ """
+ Verify that the walkModules APIs will give us back subpackages, not just
+ subpackages.
+ """
+ self.assertEquals(
+ modules.getModule('twisted.python'),
+ self.findByIteration("twisted.python",
+ where=modules.getModule('twisted')))
+
+
+ def test_onlyTopModules(self):
+ """
+ Verify that the iterModules API will only return top-level modules and
+ packages, not submodules or subpackages.
+ """
+ for module in modules.iterModules():
+ self.failIf(
+ '.' in module.name,
+ "no nested modules should be returned from iterModules: %r"
+ % (module.filePath))
+
+
+ def test_loadPackagesAndModules(self):
+ """
+ Verify that we can locate and load packages, modules, submodules, and
+ subpackages.
+ """
+ for n in ['os',
+ 'twisted',
+ 'twisted.python',
+ 'twisted.python.reflect']:
+ m = namedAny(n)
+ self.failUnlessIdentical(
+ modules.getModule(n).load(),
+ m)
+ self.failUnlessIdentical(
+ self.findByIteration(n).load(),
+ m)
+
+
+ def test_pathEntriesOnPath(self):
+ """
+ Verify that path entries discovered via module loading are, in fact, on
+ sys.path somewhere.
+ """
+ for n in ['os',
+ 'twisted',
+ 'twisted.python',
+ 'twisted.python.reflect']:
+ self.failUnlessIn(
+ modules.getModule(n).pathEntry.filePath.path,
+ sys.path)
+
+
+ def test_alwaysPreferPy(self):
+ """
+ Verify that .py files will always be preferred to .pyc files, regardless of
+ directory listing order.
+ """
+ mypath = FilePath(self.mktemp())
+ mypath.createDirectory()
+ pp = modules.PythonPath(sysPath=[mypath.path])
+ originalSmartPath = pp._smartPath
+ def _evilSmartPath(pathName):
+ o = originalSmartPath(pathName)
+ originalChildren = o.children
+ def evilChildren():
+ # normally this order is random; let's make sure it always
+ # comes up .pyc-first.
+ x = originalChildren()
+ x.sort()
+ x.reverse()
+ return x
+ o.children = evilChildren
+ return o
+ mypath.child("abcd.py").setContent('\n')
+ compileall.compile_dir(mypath.path, quiet=True)
+ # sanity check
+ self.assertEquals(len(mypath.children()), 2)
+ pp._smartPath = _evilSmartPath
+ self.assertEquals(pp['abcd'].filePath,
+ mypath.child('abcd.py'))
+
+
+ def test_packageMissingPath(self):
+ """
+ A package can delete its __path__ for some reasons,
+ C{modules.PythonPath} should be able to deal with it.
+ """
+ mypath = FilePath(self.mktemp())
+ mypath.createDirectory()
+ pp = modules.PythonPath(sysPath=[mypath.path])
+ subpath = mypath.child("abcd")
+ subpath.createDirectory()
+ subpath.child("__init__.py").setContent('del __path__\n')
+ sys.path.append(mypath.path)
+ import abcd
+ try:
+ l = list(pp.walkModules())
+ self.assertEquals(len(l), 1)
+ self.assertEquals(l[0].name, 'abcd')
+ finally:
+ del abcd
+ del sys.modules['abcd']
+ sys.path.remove(mypath.path)
+
+
+
+class PathModificationTest(PySpaceTestCase):
+ """
+ These tests share setup/cleanup behavior of creating a dummy package and
+ stuffing some code in it.
+ """
+
+ _serialnum = itertools.count().next # used to generate serial numbers for
+ # package names.
+
+ def setUp(self):
+ self.pathExtensionName = self.mktemp()
+ self.pathExtension = FilePath(self.pathExtensionName)
+ self.pathExtension.createDirectory()
+ self.packageName = "pyspacetests%d" % (self._serialnum(),)
+ self.packagePath = self.pathExtension.child(self.packageName)
+ self.packagePath.createDirectory()
+ self.packagePath.child("__init__.py").setContent("")
+ self.packagePath.child("a.py").setContent("")
+ self.packagePath.child("b.py").setContent("")
+ self.packagePath.child("c__init__.py").setContent("")
+ self.pathSetUp = False
+
+
+ def _setupSysPath(self):
+ assert not self.pathSetUp
+ self.pathSetUp = True
+ sys.path.append(self.pathExtensionName)
+
+
+ def _underUnderPathTest(self, doImport=True):
+ moddir2 = self.mktemp()
+ fpmd = FilePath(moddir2)
+ fpmd.createDirectory()
+ fpmd.child("foozle.py").setContent("x = 123\n")
+ self.packagePath.child("__init__.py").setContent(
+ "__path__.append(%r)\n" % (moddir2,))
+ # Cut here
+ self._setupSysPath()
+ modinfo = modules.getModule(self.packageName)
+ self.assertEquals(
+ self.findByIteration(self.packageName+".foozle", modinfo,
+ importPackages=doImport),
+ modinfo['foozle'])
+ self.assertEquals(modinfo['foozle'].load().x, 123)
+
+
+ def test_underUnderPathAlreadyImported(self):
+ """
+ Verify that iterModules will honor the __path__ of already-loaded packages.
+ """
+ self._underUnderPathTest()
+
+
+ def test_underUnderPathNotAlreadyImported(self):
+ """
+ Verify that iterModules will honor the __path__ of already-loaded packages.
+ """
+ self._underUnderPathTest(False)
+
+
+ test_underUnderPathNotAlreadyImported.todo = (
+ "This may be impossible but it sure would be nice.")
+
+
+ def _listModules(self):
+ pkginfo = modules.getModule(self.packageName)
+ nfni = [modinfo.name.split(".")[-1] for modinfo in
+ pkginfo.iterModules()]
+ nfni.sort()
+ self.failUnlessEqual(nfni, ['a', 'b', 'c__init__'])
+
+
+ def test_listingModules(self):
+ """
+ Make sure the module list comes back as we expect from iterModules on a
+ package, whether zipped or not.
+ """
+ self._setupSysPath()
+ self._listModules()
+
+
+ def test_listingModulesAlreadyImported(self):
+ """
+ Make sure the module list comes back as we expect from iterModules on a
+ package, whether zipped or not, even if the package has already been
+ imported.
+ """
+ self._setupSysPath()
+ namedAny(self.packageName)
+ self._listModules()
+
+
+ def tearDown(self):
+ # Intentionally using 'assert' here, this is not a test assertion, this
+ # is just an "oh fuck what is going ON" assertion. -glyph
+ if self.pathSetUp:
+ HORK = "path cleanup failed: don't be surprised if other tests break"
+ assert sys.path.pop() is self.pathExtensionName, HORK+", 1"
+ assert self.pathExtensionName not in sys.path, HORK+", 2"
+
+
+
+class RebindingTest(PathModificationTest):
+ """
+ These tests verify that the default path interrogation API works properly
+ even when sys.path has been rebound to a different object.
+ """
+ def _setupSysPath(self):
+ assert not self.pathSetUp
+ self.pathSetUp = True
+ self.savedSysPath = sys.path
+ sys.path = sys.path[:]
+ sys.path.append(self.pathExtensionName)
+
+
+ def tearDown(self):
+ """
+ Clean up sys.path by re-binding our original object.
+ """
+ if self.pathSetUp:
+ sys.path = self.savedSysPath
+
+
+
+class ZipPathModificationTest(PathModificationTest):
+ def _setupSysPath(self):
+ assert not self.pathSetUp
+ zipit(self.pathExtensionName, self.pathExtensionName+'.zip')
+ self.pathExtensionName += '.zip'
+ assert zipfile.is_zipfile(self.pathExtensionName)
+ PathModificationTest._setupSysPath(self)
+
+
+class PythonPathTestCase(TestCase):
+ """
+ Tests for the class which provides the implementation for all of the
+ public API of L{twisted.python.modules}, L{PythonPath}.
+ """
+ def test_unhandledImporter(self):
+ """
+ Make sure that the behavior when encountering an unknown importer
+ type is not catastrophic failure.
+ """
+ class SecretImporter(object):
+ pass
+
+ def hook(name):
+ return SecretImporter()
+
+ syspath = ['example/path']
+ sysmodules = {}
+ syshooks = [hook]
+ syscache = {}
+ def sysloader(name):
+ return None
+ space = modules.PythonPath(
+ syspath, sysmodules, syshooks, syscache, sysloader)
+ entries = list(space.iterEntries())
+ self.assertEquals(len(entries), 1)
+ self.assertRaises(KeyError, lambda: entries[0]['module'])
+
+
+ def test_inconsistentImporterCache(self):
+ """
+ If the path a module loaded with L{PythonPath.__getitem__} is not
+ present in the path importer cache, a warning is emitted, but the
+ L{PythonModule} is returned as usual.
+ """
+ space = modules.PythonPath([], sys.modules, [], {})
+ thisModule = space[__name__]
+ warnings = self.flushWarnings([self.test_inconsistentImporterCache])
+ self.assertEquals(warnings[0]['category'], UserWarning)
+ self.assertEquals(
+ warnings[0]['message'],
+ FilePath(twisted.__file__).parent().dirname() +
+ " (for module " + __name__ + ") not in path importer cache "
+ "(PEP 302 violation - check your local configuration).")
+ self.assertEquals(len(warnings), 1)
+ self.assertEquals(thisModule.name, __name__)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_monkey.py b/vendor/Twisted-10.0.0/twisted/test/test_monkey.py
new file mode 100644
index 0000000000..7a446c56cc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_monkey.py
@@ -0,0 +1,161 @@
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.python.monkey}.
+"""
+
+from twisted.trial import unittest
+from twisted.python.monkey import MonkeyPatcher
+
+
+class TestObj:
+ def __init__(self):
+ self.foo = 'foo value'
+ self.bar = 'bar value'
+ self.baz = 'baz value'
+
+
+class MonkeyPatcherTest(unittest.TestCase):
+ """
+ Tests for L{MonkeyPatcher} monkey-patching class.
+ """
+
+ def setUp(self):
+ self.testObject = TestObj()
+ self.originalObject = TestObj()
+ self.monkeyPatcher = MonkeyPatcher()
+
+
+ def test_empty(self):
+ """
+ A monkey patcher without patches shouldn't change a thing.
+ """
+ self.monkeyPatcher.patch()
+
+ # We can't assert that all state is unchanged, but at least we can
+ # check our test object.
+ self.assertEquals(self.originalObject.foo, self.testObject.foo)
+ self.assertEquals(self.originalObject.bar, self.testObject.bar)
+ self.assertEquals(self.originalObject.baz, self.testObject.baz)
+
+
+ def test_constructWithPatches(self):
+ """
+ Constructing a L{MonkeyPatcher} with patches should add all of the
+ given patches to the patch list.
+ """
+ patcher = MonkeyPatcher((self.testObject, 'foo', 'haha'),
+ (self.testObject, 'bar', 'hehe'))
+ patcher.patch()
+ self.assertEquals('haha', self.testObject.foo)
+ self.assertEquals('hehe', self.testObject.bar)
+ self.assertEquals(self.originalObject.baz, self.testObject.baz)
+
+
+ def test_patchExisting(self):
+ """
+ Patching an attribute that exists sets it to the value defined in the
+ patch.
+ """
+ self.monkeyPatcher.addPatch(self.testObject, 'foo', 'haha')
+ self.monkeyPatcher.patch()
+ self.assertEquals(self.testObject.foo, 'haha')
+
+
+ def test_patchNonExisting(self):
+ """
+ Patching a non-existing attribute fails with an C{AttributeError}.
+ """
+ self.monkeyPatcher.addPatch(self.testObject, 'nowhere',
+ 'blow up please')
+ self.assertRaises(AttributeError, self.monkeyPatcher.patch)
+
+
+ def test_patchAlreadyPatched(self):
+ """
+ Adding a patch for an object and attribute that already have a patch
+ overrides the existing patch.
+ """
+ self.monkeyPatcher.addPatch(self.testObject, 'foo', 'blah')
+ self.monkeyPatcher.addPatch(self.testObject, 'foo', 'BLAH')
+ self.monkeyPatcher.patch()
+ self.assertEquals(self.testObject.foo, 'BLAH')
+ self.monkeyPatcher.restore()
+ self.assertEquals(self.testObject.foo, self.originalObject.foo)
+
+
+ def test_restoreTwiceIsANoOp(self):
+ """
+ Restoring an already-restored monkey patch is a no-op.
+ """
+ self.monkeyPatcher.addPatch(self.testObject, 'foo', 'blah')
+ self.monkeyPatcher.patch()
+ self.monkeyPatcher.restore()
+ self.assertEquals(self.testObject.foo, self.originalObject.foo)
+ self.monkeyPatcher.restore()
+ self.assertEquals(self.testObject.foo, self.originalObject.foo)
+
+
+ def test_runWithPatchesDecoration(self):
+ """
+ runWithPatches should run the given callable, passing in all arguments
+ and keyword arguments, and return the return value of the callable.
+ """
+ log = []
+
+ def f(a, b, c=None):
+ log.append((a, b, c))
+ return 'foo'
+
+ result = self.monkeyPatcher.runWithPatches(f, 1, 2, c=10)
+ self.assertEquals('foo', result)
+ self.assertEquals([(1, 2, 10)], log)
+
+
+ def test_repeatedRunWithPatches(self):
+ """
+ We should be able to call the same function with runWithPatches more
+ than once. All patches should apply for each call.
+ """
+ def f():
+ return (self.testObject.foo, self.testObject.bar,
+ self.testObject.baz)
+
+ self.monkeyPatcher.addPatch(self.testObject, 'foo', 'haha')
+ result = self.monkeyPatcher.runWithPatches(f)
+ self.assertEquals(
+ ('haha', self.originalObject.bar, self.originalObject.baz), result)
+ result = self.monkeyPatcher.runWithPatches(f)
+ self.assertEquals(
+ ('haha', self.originalObject.bar, self.originalObject.baz),
+ result)
+
+
+ def test_runWithPatchesRestores(self):
+ """
+ C{runWithPatches} should restore the original values after the function
+ has executed.
+ """
+ self.monkeyPatcher.addPatch(self.testObject, 'foo', 'haha')
+ self.assertEquals(self.originalObject.foo, self.testObject.foo)
+ self.monkeyPatcher.runWithPatches(lambda: None)
+ self.assertEquals(self.originalObject.foo, self.testObject.foo)
+
+
+ def test_runWithPatchesRestoresOnException(self):
+ """
+ Test runWithPatches restores the original values even when the function
+ raises an exception.
+ """
+ def _():
+ self.assertEquals(self.testObject.foo, 'haha')
+ self.assertEquals(self.testObject.bar, 'blahblah')
+ raise RuntimeError, "Something went wrong!"
+
+ self.monkeyPatcher.addPatch(self.testObject, 'foo', 'haha')
+ self.monkeyPatcher.addPatch(self.testObject, 'bar', 'blahblah')
+
+ self.assertRaises(RuntimeError, self.monkeyPatcher.runWithPatches, _)
+ self.assertEquals(self.testObject.foo, self.originalObject.foo)
+ self.assertEquals(self.testObject.bar, self.originalObject.bar)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_newcred.py b/vendor/Twisted-10.0.0/twisted/test/test_newcred.py
new file mode 100644
index 0000000000..29db20486f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_newcred.py
@@ -0,0 +1,487 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.cred}, now with 30% more starch.
+"""
+
+
+import hmac
+from zope.interface import implements, Interface
+
+from twisted.trial import unittest
+from twisted.cred import portal, checkers, credentials, error, util
+from twisted.python import components
+from twisted.internet import defer
+from twisted.internet.defer import deferredGenerator as dG, waitForDeferred as wFD
+
+try:
+ from crypt import crypt
+except ImportError:
+ crypt = None
+
+try:
+ from twisted.cred.pamauth import callIntoPAM
+except ImportError:
+ pamauth = None
+else:
+ from twisted.cred import pamauth
+
+
+class DeprecatedUtilTests(unittest.TestCase):
+ """
+ Tests for the deprecation of the functions in L{twisted.cred.util}.
+ """
+ def test_respond(self):
+ """
+ L{respond} applies a particular hashing to a challenge and a password
+ and returns the result. It is deprecated and calling it emits a
+ deprecation warning.
+ """
+ # Use some values and test against the known correct output.
+ self.assertEqual(
+ util.respond('foo', 'bar').encode('hex'),
+ 'ebe4a2902532198cafaa223fb5ac0f20')
+
+ warnings = self.flushWarnings(offendingFunctions=[self.test_respond])
+ self.assertEqual(
+ warnings[0]['message'],
+ 'twisted.cred.util.respond is deprecated since Twisted 8.3.')
+ self.assertEqual(
+ warnings[0]['category'],
+ PendingDeprecationWarning)
+ self.assertEqual(len(warnings), 1)
+
+
+ def test_challenge(self):
+ """
+ L{challenge} returns a different string each time it is called.
+ """
+ self.assertNotEqual(util.challenge(), util.challenge())
+ warnings = self.flushWarnings(offendingFunctions=[self.test_challenge])
+ for w in warnings:
+ self.assertEqual(
+ w['message'],
+ 'twisted.cred.util.challenge is deprecated since Twisted 8.3.')
+ self.assertEqual(
+ w['category'],
+ PendingDeprecationWarning)
+ self.assertEqual(len(warnings), 2)
+
+
+
+class ITestable(Interface):
+ pass
+
+class TestAvatar:
+ def __init__(self, name):
+ self.name = name
+ self.loggedIn = False
+ self.loggedOut = False
+
+ def login(self):
+ assert not self.loggedIn
+ self.loggedIn = True
+
+ def logout(self):
+ self.loggedOut = True
+
+class Testable(components.Adapter):
+ implements(ITestable)
+
+# components.Interface(TestAvatar).adaptWith(Testable, ITestable)
+
+components.registerAdapter(Testable, TestAvatar, ITestable)
+
+class IDerivedCredentials(credentials.IUsernamePassword):
+ pass
+
+class DerivedCredentials(object):
+ implements(IDerivedCredentials, ITestable)
+
+ def __init__(self, username, password):
+ self.username = username
+ self.password = password
+
+ def checkPassword(self, password):
+ return password == self.password
+
+
+class TestRealm:
+ implements(portal.IRealm)
+ def __init__(self):
+ self.avatars = {}
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if self.avatars.has_key(avatarId):
+ avatar = self.avatars[avatarId]
+ else:
+ avatar = TestAvatar(avatarId)
+ self.avatars[avatarId] = avatar
+ avatar.login()
+ return (interfaces[0], interfaces[0](avatar),
+ avatar.logout)
+
+class NewCredTest(unittest.TestCase):
+ def setUp(self):
+ r = self.realm = TestRealm()
+ p = self.portal = portal.Portal(r)
+ up = self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ up.addUser("bob", "hello")
+ p.registerChecker(up)
+
+ def testListCheckers(self):
+ expected = [credentials.IUsernamePassword, credentials.IUsernameHashedPassword]
+ got = self.portal.listCredentialsInterfaces()
+ expected.sort()
+ got.sort()
+ self.assertEquals(got, expected)
+
+ def testBasicLogin(self):
+ l = []; f = []
+ self.portal.login(credentials.UsernamePassword("bob", "hello"),
+ self, ITestable).addCallback(
+ l.append).addErrback(f.append)
+ if f:
+ raise f[0]
+ # print l[0].getBriefTraceback()
+ iface, impl, logout = l[0]
+ # whitebox
+ self.assertEquals(iface, ITestable)
+ self.failUnless(iface.providedBy(impl),
+ "%s does not implement %s" % (impl, iface))
+ # greybox
+ self.failUnless(impl.original.loggedIn)
+ self.failUnless(not impl.original.loggedOut)
+ logout()
+ self.failUnless(impl.original.loggedOut)
+
+ def test_derivedInterface(self):
+ """
+ Login with credentials implementing an interface inheriting from an
+ interface registered with a checker (but not itself registered).
+ """
+ l = []
+ f = []
+ self.portal.login(DerivedCredentials("bob", "hello"), self, ITestable
+ ).addCallback(l.append
+ ).addErrback(f.append)
+ if f:
+ raise f[0]
+ iface, impl, logout = l[0]
+ # whitebox
+ self.assertEquals(iface, ITestable)
+ self.failUnless(iface.providedBy(impl),
+ "%s does not implement %s" % (impl, iface))
+ # greybox
+ self.failUnless(impl.original.loggedIn)
+ self.failUnless(not impl.original.loggedOut)
+ logout()
+ self.failUnless(impl.original.loggedOut)
+
+ def testFailedLogin(self):
+ l = []
+ self.portal.login(credentials.UsernamePassword("bob", "h3llo"),
+ self, ITestable).addErrback(
+ lambda x: x.trap(error.UnauthorizedLogin)).addCallback(l.append)
+ self.failUnless(l)
+ self.failUnlessEqual(error.UnauthorizedLogin, l[0])
+
+ def testFailedLoginName(self):
+ l = []
+ self.portal.login(credentials.UsernamePassword("jay", "hello"),
+ self, ITestable).addErrback(
+ lambda x: x.trap(error.UnauthorizedLogin)).addCallback(l.append)
+ self.failUnless(l)
+ self.failUnlessEqual(error.UnauthorizedLogin, l[0])
+
+
+class CramMD5CredentialsTestCase(unittest.TestCase):
+ def testIdempotentChallenge(self):
+ c = credentials.CramMD5Credentials()
+ chal = c.getChallenge()
+ self.assertEquals(chal, c.getChallenge())
+
+ def testCheckPassword(self):
+ c = credentials.CramMD5Credentials()
+ chal = c.getChallenge()
+ c.response = hmac.HMAC('secret', chal).hexdigest()
+ self.failUnless(c.checkPassword('secret'))
+
+ def testWrongPassword(self):
+ c = credentials.CramMD5Credentials()
+ self.failIf(c.checkPassword('secret'))
+
+class OnDiskDatabaseTestCase(unittest.TestCase):
+ users = [
+ ('user1', 'pass1'),
+ ('user2', 'pass2'),
+ ('user3', 'pass3'),
+ ]
+
+
+ def testUserLookup(self):
+ dbfile = self.mktemp()
+ db = checkers.FilePasswordDB(dbfile)
+ f = file(dbfile, 'w')
+ for (u, p) in self.users:
+ f.write('%s:%s\n' % (u, p))
+ f.close()
+
+ for (u, p) in self.users:
+ self.failUnlessRaises(KeyError, db.getUser, u.upper())
+ self.assertEquals(db.getUser(u), (u, p))
+
+ def testCaseInSensitivity(self):
+ dbfile = self.mktemp()
+ db = checkers.FilePasswordDB(dbfile, caseSensitive=0)
+ f = file(dbfile, 'w')
+ for (u, p) in self.users:
+ f.write('%s:%s\n' % (u, p))
+ f.close()
+
+ for (u, p) in self.users:
+ self.assertEquals(db.getUser(u.upper()), (u, p))
+
+ def testRequestAvatarId(self):
+ dbfile = self.mktemp()
+ db = checkers.FilePasswordDB(dbfile, caseSensitive=0)
+ f = file(dbfile, 'w')
+ for (u, p) in self.users:
+ f.write('%s:%s\n' % (u, p))
+ f.close()
+ creds = [credentials.UsernamePassword(u, p) for u, p in self.users]
+ d = defer.gatherResults(
+ [defer.maybeDeferred(db.requestAvatarId, c) for c in creds])
+ d.addCallback(self.assertEquals, [u for u, p in self.users])
+ return d
+
+ def testRequestAvatarId_hashed(self):
+ dbfile = self.mktemp()
+ db = checkers.FilePasswordDB(dbfile, caseSensitive=0)
+ f = file(dbfile, 'w')
+ for (u, p) in self.users:
+ f.write('%s:%s\n' % (u, p))
+ f.close()
+ creds = [credentials.UsernameHashedPassword(u, p) for u, p in self.users]
+ d = defer.gatherResults(
+ [defer.maybeDeferred(db.requestAvatarId, c) for c in creds])
+ d.addCallback(self.assertEquals, [u for u, p in self.users])
+ return d
+
+
+
+class HashedPasswordOnDiskDatabaseTestCase(unittest.TestCase):
+ users = [
+ ('user1', 'pass1'),
+ ('user2', 'pass2'),
+ ('user3', 'pass3'),
+ ]
+
+
+ def hash(self, u, p, s):
+ return crypt(p, s)
+
+ def setUp(self):
+ dbfile = self.mktemp()
+ self.db = checkers.FilePasswordDB(dbfile, hash=self.hash)
+ f = file(dbfile, 'w')
+ for (u, p) in self.users:
+ f.write('%s:%s\n' % (u, crypt(p, u[:2])))
+ f.close()
+ r = TestRealm()
+ self.port = portal.Portal(r)
+ self.port.registerChecker(self.db)
+
+ def testGoodCredentials(self):
+ goodCreds = [credentials.UsernamePassword(u, p) for u, p in self.users]
+ d = defer.gatherResults([self.db.requestAvatarId(c) for c in goodCreds])
+ d.addCallback(self.assertEquals, [u for u, p in self.users])
+ return d
+
+ def testGoodCredentials_login(self):
+ goodCreds = [credentials.UsernamePassword(u, p) for u, p in self.users]
+ d = defer.gatherResults([self.port.login(c, None, ITestable)
+ for c in goodCreds])
+ d.addCallback(lambda x: [a.original.name for i, a, l in x])
+ d.addCallback(self.assertEquals, [u for u, p in self.users])
+ return d
+
+ def testBadCredentials(self):
+ badCreds = [credentials.UsernamePassword(u, 'wrong password')
+ for u, p in self.users]
+ d = defer.DeferredList([self.port.login(c, None, ITestable)
+ for c in badCreds], consumeErrors=True)
+ d.addCallback(self._assertFailures, error.UnauthorizedLogin)
+ return d
+
+ def testHashedCredentials(self):
+ hashedCreds = [credentials.UsernameHashedPassword(u, crypt(p, u[:2]))
+ for u, p in self.users]
+ d = defer.DeferredList([self.port.login(c, None, ITestable)
+ for c in hashedCreds], consumeErrors=True)
+ d.addCallback(self._assertFailures, error.UnhandledCredentials)
+ return d
+
+ def _assertFailures(self, failures, *expectedFailures):
+ for flag, failure in failures:
+ self.failUnlessEqual(flag, defer.FAILURE)
+ failure.trap(*expectedFailures)
+ return None
+
+ if crypt is None:
+ skip = "crypt module not available"
+
+class PluggableAuthenticationModulesTest(unittest.TestCase):
+
+ def setUp(self):
+ """
+ Replace L{pamauth.callIntoPAM} with a dummy implementation with
+ easily-controlled behavior.
+ """
+ self._oldCallIntoPAM = pamauth.callIntoPAM
+ pamauth.callIntoPAM = self.callIntoPAM
+
+
+ def tearDown(self):
+ """
+ Restore the original value of L{pamauth.callIntoPAM}.
+ """
+ pamauth.callIntoPAM = self._oldCallIntoPAM
+
+
+ def callIntoPAM(self, service, user, conv):
+ if service != 'Twisted':
+ raise error.UnauthorizedLogin('bad service: %s' % service)
+ if user != 'testuser':
+ raise error.UnauthorizedLogin('bad username: %s' % user)
+ questions = [
+ (1, "Password"),
+ (2, "Message w/ Input"),
+ (3, "Message w/o Input"),
+ ]
+ replies = conv(questions)
+ if replies != [
+ ("password", 0),
+ ("entry", 0),
+ ("", 0)
+ ]:
+ raise error.UnauthorizedLogin('bad conversion: %s' % repr(replies))
+ return 1
+
+ def _makeConv(self, d):
+ def conv(questions):
+ return defer.succeed([(d[t], 0) for t, q in questions])
+ return conv
+
+ def testRequestAvatarId(self):
+ db = checkers.PluggableAuthenticationModulesChecker()
+ conv = self._makeConv({1:'password', 2:'entry', 3:''})
+ creds = credentials.PluggableAuthenticationModules('testuser',
+ conv)
+ d = db.requestAvatarId(creds)
+ d.addCallback(self.assertEquals, 'testuser')
+ return d
+
+ def testBadCredentials(self):
+ db = checkers.PluggableAuthenticationModulesChecker()
+ conv = self._makeConv({1:'', 2:'', 3:''})
+ creds = credentials.PluggableAuthenticationModules('testuser',
+ conv)
+ d = db.requestAvatarId(creds)
+ self.assertFailure(d, error.UnauthorizedLogin)
+ return d
+
+ def testBadUsername(self):
+ db = checkers.PluggableAuthenticationModulesChecker()
+ conv = self._makeConv({1:'password', 2:'entry', 3:''})
+ creds = credentials.PluggableAuthenticationModules('baduser',
+ conv)
+ d = db.requestAvatarId(creds)
+ self.assertFailure(d, error.UnauthorizedLogin)
+ return d
+
+ if not pamauth:
+ skip = "Can't run without PyPAM"
+
+class CheckersMixin:
+ def testPositive(self):
+ for chk in self.getCheckers():
+ for (cred, avatarId) in self.getGoodCredentials():
+ r = wFD(chk.requestAvatarId(cred))
+ yield r
+ self.assertEquals(r.getResult(), avatarId)
+ testPositive = dG(testPositive)
+
+ def testNegative(self):
+ for chk in self.getCheckers():
+ for cred in self.getBadCredentials():
+ r = wFD(chk.requestAvatarId(cred))
+ yield r
+ self.assertRaises(error.UnauthorizedLogin, r.getResult)
+ testNegative = dG(testNegative)
+
+class HashlessFilePasswordDBMixin:
+ credClass = credentials.UsernamePassword
+ diskHash = None
+ networkHash = staticmethod(lambda x: x)
+
+ _validCredentials = [
+ ('user1', 'password1'),
+ ('user2', 'password2'),
+ ('user3', 'password3')]
+
+ def getGoodCredentials(self):
+ for u, p in self._validCredentials:
+ yield self.credClass(u, self.networkHash(p)), u
+
+ def getBadCredentials(self):
+ for u, p in [('user1', 'password3'),
+ ('user2', 'password1'),
+ ('bloof', 'blarf')]:
+ yield self.credClass(u, self.networkHash(p))
+
+ def getCheckers(self):
+ diskHash = self.diskHash or (lambda x: x)
+ hashCheck = self.diskHash and (lambda username, password, stored: self.diskHash(password))
+
+ for cache in True, False:
+ fn = self.mktemp()
+ fObj = file(fn, 'w')
+ for u, p in self._validCredentials:
+ fObj.write('%s:%s\n' % (u, diskHash(p)))
+ fObj.close()
+ yield checkers.FilePasswordDB(fn, cache=cache, hash=hashCheck)
+
+ fn = self.mktemp()
+ fObj = file(fn, 'w')
+ for u, p in self._validCredentials:
+ fObj.write('%s dingle dongle %s\n' % (diskHash(p), u))
+ fObj.close()
+ yield checkers.FilePasswordDB(fn, ' ', 3, 0, cache=cache, hash=hashCheck)
+
+ fn = self.mktemp()
+ fObj = file(fn, 'w')
+ for u, p in self._validCredentials:
+ fObj.write('zip,zap,%s,zup,%s\n' % (u.title(), diskHash(p)))
+ fObj.close()
+ yield checkers.FilePasswordDB(fn, ',', 2, 4, False, cache=cache, hash=hashCheck)
+
+class LocallyHashedFilePasswordDBMixin(HashlessFilePasswordDBMixin):
+ diskHash = staticmethod(lambda x: x.encode('hex'))
+
+class NetworkHashedFilePasswordDBMixin(HashlessFilePasswordDBMixin):
+ networkHash = staticmethod(lambda x: x.encode('hex'))
+ class credClass(credentials.UsernameHashedPassword):
+ def checkPassword(self, password):
+ return self.hashed.decode('hex') == password
+
+class HashlessFilePasswordDBCheckerTestCase(HashlessFilePasswordDBMixin, CheckersMixin, unittest.TestCase):
+ pass
+
+class LocallyHashedFilePasswordDBCheckerTestCase(LocallyHashedFilePasswordDBMixin, CheckersMixin, unittest.TestCase):
+ pass
+
+class NetworkHashedFilePasswordDBCheckerTestCase(NetworkHashedFilePasswordDBMixin, CheckersMixin, unittest.TestCase):
+ pass
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_nmea.py b/vendor/Twisted-10.0.0/twisted/test/test_nmea.py
new file mode 100644
index 0000000000..fafe6553f6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_nmea.py
@@ -0,0 +1,115 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Test cases for the NMEA GPS protocol"""
+
+import StringIO
+
+from twisted.trial import unittest
+from twisted.internet import reactor, protocol
+from twisted.python import reflect
+
+from twisted.protocols.gps import nmea
+
+class StringIOWithNoClose(StringIO.StringIO):
+ def close(self):
+ pass
+
+class ResultHarvester:
+ def __init__(self):
+ self.results = []
+
+ def __call__(self, *args):
+ self.results.append(args)
+
+ def performTest(self, function, *args, **kwargs):
+ l = len(self.results)
+ try:
+ function(*args, **kwargs)
+ except Exception, e:
+ self.results.append(e)
+ if l == len(self.results):
+ self.results.append(NotImplementedError())
+
+class NMEATester(nmea.NMEAReceiver):
+ ignore_invalid_sentence = 0
+ ignore_checksum_mismatch = 0
+ ignore_unknown_sentencetypes = 0
+ convert_dates_before_y2k = 1
+
+ def connectionMade(self):
+ self.resultHarvester = ResultHarvester()
+ for fn in reflect.prefixedMethodNames(self.__class__, 'decode_'):
+ setattr(self, 'handle_' + fn, self.resultHarvester)
+
+class NMEAReceiverTestCase(unittest.TestCase):
+ messages = (
+ # fix - signal acquired
+ "$GPGGA,231713.0,3910.413,N,07641.994,W,1,05,1.35,00044,M,-033,M,,*69",
+ # fix - signal not acquired
+ "$GPGGA,235947.000,0000.0000,N,00000.0000,E,0,00,0.0,0.0,M,,,,0000*00",
+ # junk
+ "lkjasdfkl!@#(*$!@(*#(ASDkfjasdfLMASDCVKAW!@#($)!(@#)(*",
+ # fix - signal acquired (invalid checksum)
+ "$GPGGA,231713.0,3910.413,N,07641.994,W,1,05,1.35,00044,M,-033,M,,*68",
+ # invalid sentence
+ "$GPGGX,231713.0,3910.413,N,07641.994,W,1,05,1.35,00044,M,-033,M,,*68",
+ # position acquired
+ "$GPGLL,4250.5589,S,14718.5084,E,092204.999,A*2D",
+ # position not acquired
+ "$GPGLL,0000.0000,N,00000.0000,E,235947.000,V*2D",
+ # active satellites (no fix)
+ "$GPGSA,A,1,,,,,,,,,,,,,0.0,0.0,0.0*30",
+ # active satellites
+ "$GPGSA,A,3,01,20,19,13,,,,,,,,,40.4,24.4,32.2*0A",
+ # positiontime (no fix)
+ "$GPRMC,235947.000,V,0000.0000,N,00000.0000,E,,,041299,,*1D",
+ # positiontime
+ "$GPRMC,092204.999,A,4250.5589,S,14718.5084,E,0.00,89.68,211200,,*25",
+ # course over ground (no fix - not implemented)
+ "$GPVTG,,T,,M,,N,,K*4E",
+ # course over ground (not implemented)
+ "$GPVTG,89.68,T,,M,0.00,N,0.0,K*5F",
+ )
+ results = (
+ (83833.0, 39.17355, -76.6999, nmea.POSFIX_SPS, 5, 1.35, (44.0, 'M'), (-33.0, 'M'), None),
+ (86387.0, 0.0, 0.0, 0, 0, 0.0, (0.0, 'M'), None, None),
+ nmea.InvalidSentence(),
+ nmea.InvalidChecksum(),
+ nmea.InvalidSentence(),
+ (-42.842648333333337, 147.30847333333332, 33724.999000000003, 1),
+ (0.0, 0.0, 86387.0, 0),
+ ((None, None, None, None, None, None, None, None, None, None, None, None), (nmea.MODE_AUTO, nmea.MODE_NOFIX), 0.0, 0.0, 0.0),
+ ((1, 20, 19, 13, None, None, None, None, None, None, None, None), (nmea.MODE_AUTO, nmea.MODE_3D), 40.4, 24.4, 32.2),
+ (0.0, 0.0, None, None, 86387.0, (1999, 12, 4), None),
+ (-42.842648333333337, 147.30847333333332, 0.0, 89.68, 33724.999, (2000, 12, 21), None),
+ NotImplementedError(),
+ NotImplementedError(),
+ )
+ def testGPSMessages(self):
+ dummy = NMEATester()
+ dummy.makeConnection(protocol.FileWrapper(StringIOWithNoClose()))
+ for line in self.messages:
+ dummy.resultHarvester.performTest(dummy.lineReceived, line)
+ def munge(myTuple):
+ if type(myTuple) != type(()):
+ return
+ newTuple = []
+ for v in myTuple:
+ if type(v) == type(1.1):
+ v = float(int(v * 10000.0)) * 0.0001
+ newTuple.append(v)
+ return tuple(newTuple)
+ for (message, expectedResult, actualResult) in zip(self.messages, self.results, dummy.resultHarvester.results):
+ expectedResult = munge(expectedResult)
+ actualResult = munge(actualResult)
+ if isinstance(expectedResult, Exception):
+ if isinstance(actualResult, Exception):
+ self.failUnlessEqual(expectedResult.__class__, actualResult.__class__, "\nInput:\n%s\nExpected:\n%s.%s\nResults:\n%s.%s\n" % (message, expectedResult.__class__.__module__, expectedResult.__class__.__name__, actualResult.__class__.__module__, actualResult.__class__.__name__))
+ else:
+ self.failUnlessEqual(1, 0, "\nInput:\n%s\nExpected:\n%s.%s\nResults:\n%r\n" % (message, expectedResult.__class__.__module__, expectedResult.__class__.__name__, actualResult))
+ else:
+ self.failUnlessEqual(expectedResult, actualResult, "\nInput:\n%s\nExpected: %r\nResults: %r\n" % (message, expectedResult, actualResult))
+
+testCases = [NMEAReceiverTestCase]
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_paths.py b/vendor/Twisted-10.0.0/twisted/test/test_paths.py
new file mode 100644
index 0000000000..8149bf5a2e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_paths.py
@@ -0,0 +1,896 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases covering L{twisted.python.filepath} and L{twisted.python.zippath}.
+"""
+
+import os, time, pickle, errno, zipfile, stat
+
+from twisted.python.compat import set
+from twisted.python.win32 import WindowsError, ERROR_DIRECTORY
+from twisted.python import filepath
+from twisted.python.zippath import ZipArchive
+from twisted.python.runtime import platform
+
+from twisted.trial import unittest
+
+
+class AbstractFilePathTestCase(unittest.TestCase):
+
+ f1content = "file 1"
+ f2content = "file 2"
+
+ def _mkpath(self, *p):
+ x = os.path.abspath(os.path.join(self.cmn, *p))
+ self.all.append(x)
+ return x
+
+
+ def subdir(self, *dirname):
+ os.mkdir(self._mkpath(*dirname))
+
+
+ def subfile(self, *dirname):
+ return open(self._mkpath(*dirname), "wb")
+
+
+ def setUp(self):
+ self.now = time.time()
+ cmn = self.cmn = os.path.abspath(self.mktemp())
+ self.all = [cmn]
+ os.mkdir(cmn)
+ self.subdir("sub1")
+ f = self.subfile("file1")
+ f.write(self.f1content)
+ f.close()
+ f = self.subfile("sub1", "file2")
+ f.write(self.f2content)
+ f.close()
+ self.subdir('sub3')
+ f = self.subfile("sub3", "file3.ext1")
+ f.close()
+ f = self.subfile("sub3", "file3.ext2")
+ f.close()
+ f = self.subfile("sub3", "file3.ext3")
+ f.close()
+ self.path = filepath.FilePath(cmn)
+ self.root = filepath.FilePath("/")
+
+
+ def test_segmentsFromPositive(self):
+ """
+ Verify that the segments between two paths are correctly identified.
+ """
+ self.assertEquals(
+ self.path.child("a").child("b").child("c").segmentsFrom(self.path),
+ ["a", "b", "c"])
+
+ def test_segmentsFromNegative(self):
+ """Verify that segmentsFrom notices when the ancestor isn't an ancestor.
+ """
+ self.assertRaises(
+ ValueError,
+ self.path.child("a").child("b").child("c").segmentsFrom,
+ self.path.child("d").child("c").child("e"))
+
+
+ def test_walk(self):
+ """
+ Verify that walking the path gives the same result as the known file
+ hierarchy.
+ """
+ x = [foo.path for foo in self.path.walk()]
+ self.assertEquals(set(x), set(self.all))
+
+
+ def test_parents(self):
+ """
+ L{FilePath.parents()} should return an iterator of every ancestor of
+ the L{FilePath} in question.
+ """
+ L = []
+ pathobj = self.path.child("a").child("b").child("c")
+ fullpath = pathobj.path
+ lastpath = fullpath
+ thispath = os.path.dirname(fullpath)
+ while lastpath != self.root.path:
+ L.append(thispath)
+ lastpath = thispath
+ thispath = os.path.dirname(thispath)
+ self.assertEquals([x.path for x in pathobj.parents()], L)
+
+
+ def test_validSubdir(self):
+ """Verify that a valid subdirectory will show up as a directory, but not as a
+ file, not as a symlink, and be listable.
+ """
+ sub1 = self.path.child('sub1')
+ self.failUnless(sub1.exists(),
+ "This directory does exist.")
+ self.failUnless(sub1.isdir(),
+ "It's a directory.")
+ self.failUnless(not sub1.isfile(),
+ "It's a directory.")
+ self.failUnless(not sub1.islink(),
+ "It's a directory.")
+ self.failUnlessEqual(sub1.listdir(),
+ ['file2'])
+
+
+ def test_invalidSubdir(self):
+ """
+ Verify that a subdirectory that doesn't exist is reported as such.
+ """
+ sub2 = self.path.child('sub2')
+ self.failIf(sub2.exists(),
+ "This directory does not exist.")
+
+ def test_validFiles(self):
+ """
+ Make sure that we can read existent non-empty files.
+ """
+ f1 = self.path.child('file1')
+ self.failUnlessEqual(f1.open().read(), self.f1content)
+ f2 = self.path.child('sub1').child('file2')
+ self.failUnlessEqual(f2.open().read(), self.f2content)
+
+
+ def test_dictionaryKeys(self):
+ """
+ Verify that path instances are usable as dictionary keys.
+ """
+ f1 = self.path.child('file1')
+ f1prime = self.path.child('file1')
+ f2 = self.path.child('file2')
+ dictoid = {}
+ dictoid[f1] = 3
+ dictoid[f1prime] = 4
+ self.assertEquals(dictoid[f1], 4)
+ self.assertEquals(dictoid.keys(), [f1])
+ self.assertIdentical(dictoid.keys()[0], f1)
+ self.assertNotIdentical(dictoid.keys()[0], f1prime) # sanity check
+ dictoid[f2] = 5
+ self.assertEquals(dictoid[f2], 5)
+ self.assertEquals(len(dictoid), 2)
+
+
+ def test_dictionaryKeyWithString(self):
+ """
+ Verify that path instances are usable as dictionary keys which do not clash
+ with their string counterparts.
+ """
+ f1 = self.path.child('file1')
+ dictoid = {f1: 'hello'}
+ dictoid[f1.path] = 'goodbye'
+ self.assertEquals(len(dictoid), 2)
+
+
+ def test_childrenNonexistentError(self):
+ """
+ Verify that children raises the appropriate exception for non-existent
+ directories.
+ """
+ self.assertRaises(filepath.UnlistableError,
+ self.path.child('not real').children)
+
+ def test_childrenNotDirectoryError(self):
+ """
+ Verify that listdir raises the appropriate exception for attempting to list
+ a file rather than a directory.
+ """
+ self.assertRaises(filepath.UnlistableError,
+ self.path.child('file1').children)
+
+
+ def test_newTimesAreFloats(self):
+ """
+ Verify that all times returned from the various new time functions are ints
+ (and hopefully therefore 'high precision').
+ """
+ for p in self.path, self.path.child('file1'):
+ self.failUnlessEqual(type(p.getAccessTime()), float)
+ self.failUnlessEqual(type(p.getModificationTime()), float)
+ self.failUnlessEqual(type(p.getStatusChangeTime()), float)
+
+
+ def test_oldTimesAreInts(self):
+ """
+ Verify that all times returned from the various time functions are
+ integers, for compatibility.
+ """
+ for p in self.path, self.path.child('file1'):
+ self.failUnlessEqual(type(p.getatime()), int)
+ self.failUnlessEqual(type(p.getmtime()), int)
+ self.failUnlessEqual(type(p.getctime()), int)
+
+
+
+class FakeWindowsPath(filepath.FilePath):
+ """
+ A test version of FilePath which overrides listdir to raise L{WindowsError}.
+ """
+
+ def listdir(self):
+ """
+ @raise WindowsError: always.
+ """
+ raise WindowsError(
+ ERROR_DIRECTORY,
+ "A directory's validness was called into question")
+
+
+class ListingCompatibilityTests(unittest.TestCase):
+ """
+ These tests verify compatibility with legacy behavior of directory listing.
+ """
+
+ def test_windowsErrorExcept(self):
+ """
+ Verify that when a WindowsError is raised from listdir, catching
+ WindowsError works.
+ """
+ fwp = FakeWindowsPath(self.mktemp())
+ self.assertRaises(filepath.UnlistableError, fwp.children)
+ self.assertRaises(WindowsError, fwp.children)
+
+
+ def test_alwaysCatchOSError(self):
+ """
+ Verify that in the normal case where a directory does not exist, we will
+ get an OSError.
+ """
+ fp = filepath.FilePath(self.mktemp())
+ self.assertRaises(OSError, fp.children)
+
+
+ def test_keepOriginalAttributes(self):
+ """
+ Verify that the Unlistable exception raised will preserve the attributes of
+ the previously-raised exception.
+ """
+ fp = filepath.FilePath(self.mktemp())
+ ose = self.assertRaises(OSError, fp.children)
+ d1 = ose.__dict__.keys()
+ d1.remove('originalException')
+ d2 = ose.originalException.__dict__.keys()
+ d1.sort()
+ d2.sort()
+ self.assertEquals(d1, d2)
+
+
+
+def zipit(dirname, zfname):
+ """
+ create a zipfile on zfname, containing the contents of dirname'
+ """
+ zf = zipfile.ZipFile(zfname, "w")
+ basedir = os.path.basename(dirname)
+ for root, dirs, files, in os.walk(dirname):
+ for fname in files:
+ fspath = os.path.join(root, fname)
+ arcpath = os.path.join(root, fname)[len(dirname)+1:]
+ # print fspath, '=>', arcpath
+ zf.write(fspath, arcpath)
+ zf.close()
+
+
+
+class ZipFilePathTestCase(AbstractFilePathTestCase):
+ """
+ Test various L{ZipPath} path manipulations as well as reprs for L{ZipPath}
+ and L{ZipArchive}.
+ """
+ def setUp(self):
+ AbstractFilePathTestCase.setUp(self)
+ zipit(self.cmn, self.cmn + '.zip')
+ self.path = ZipArchive(self.cmn + '.zip')
+ self.root = self.path
+ self.all = [x.replace(self.cmn, self.cmn + '.zip') for x in self.all]
+
+
+ def test_zipPathRepr(self):
+ """
+ Make sure that invoking ZipPath's repr prints the correct class name
+ and an absolute path to the zip file.
+ """
+ child = self.path.child("foo")
+ pathRepr = "ZipPath(%r)" % (
+ os.path.abspath(self.cmn + ".zip" + os.sep + 'foo'),)
+
+ # Check for an absolute path
+ self.assertEquals(repr(child), pathRepr)
+
+ # Create a path to the file rooted in the current working directory
+ relativeCommon = self.cmn.replace(os.getcwd() + os.sep, "", 1) + ".zip"
+ relpath = ZipArchive(relativeCommon)
+ child = relpath.child("foo")
+
+ # Check using a path without the cwd prepended
+ self.assertEquals(repr(child), pathRepr)
+
+
+ def test_zipArchiveRepr(self):
+ """
+ Make sure that invoking ZipArchive's repr prints the correct class
+ name and an absolute path to the zip file.
+ """
+ pathRepr = 'ZipArchive(%r)' % (os.path.abspath(self.cmn + '.zip'),)
+
+ # Check for an absolute path
+ self.assertEquals(repr(self.path), pathRepr)
+
+ # Create a path to the file rooted in the current working directory
+ relativeCommon = self.cmn.replace(os.getcwd() + os.sep, "", 1) + ".zip"
+ relpath = ZipArchive(relativeCommon)
+
+ # Check using a path without the cwd prepended
+ self.assertEquals(repr(relpath), pathRepr)
+
+
+
+class FilePathTestCase(AbstractFilePathTestCase):
+
+ def test_chmod(self):
+ """
+ Make sure that calling L{FilePath.chmod} modifies the permissions of
+ the passed file as expected (using C{os.stat} to check). We use some
+ basic modes that should work everywhere (even on Windows).
+ """
+ for mode in (0555, 0777):
+ self.path.child("sub1").chmod(mode)
+ self.assertEquals(
+ stat.S_IMODE(os.stat(self.path.child("sub1").path).st_mode),
+ mode)
+
+
+ def symlink(self, target, name):
+ """
+ Create a symbolic link named C{name} pointing at C{target}.
+
+ @type target: C{str}
+ @type name: C{str}
+ @raise SkipTest: raised if symbolic links are not supported on the
+ host platform.
+ """
+ if getattr(os, 'symlink', None) is None:
+ raise unittest.SkipTest(
+ "Platform does not support symbolic links.")
+ os.symlink(target, name)
+
+
+ def createLinks(self):
+ """
+ Create several symbolic links to files and directories.
+ """
+ subdir = self.path.child("sub1")
+ self.symlink(subdir.path, self._mkpath("sub1.link"))
+ self.symlink(subdir.child("file2").path, self._mkpath("file2.link"))
+ self.symlink(subdir.child("file2").path,
+ self._mkpath("sub1", "sub1.file2.link"))
+
+
+ def test_realpathSymlink(self):
+ """
+ L{FilePath.realpath} returns the path of the ultimate target of a
+ symlink.
+ """
+ self.createLinks()
+ self.symlink(self.path.child("file2.link").path,
+ self.path.child("link.link").path)
+ self.assertEquals(self.path.child("link.link").realpath(),
+ self.path.child("sub1").child("file2"))
+
+
+ def test_realpathCyclicalSymlink(self):
+ """
+ L{FilePath.realpath} raises L{filepath.LinkError} if the path is a
+ symbolic link which is part of a cycle.
+ """
+ self.symlink(self.path.child("link1").path, self.path.child("link2").path)
+ self.symlink(self.path.child("link2").path, self.path.child("link1").path)
+ self.assertRaises(filepath.LinkError,
+ self.path.child("link2").realpath)
+
+
+ def test_realpathNoSymlink(self):
+ """
+ L{FilePath.realpath} returns the path itself if the path is not a
+ symbolic link.
+ """
+ self.assertEquals(self.path.child("sub1").realpath(),
+ self.path.child("sub1"))
+
+
+ def test_walkCyclicalSymlink(self):
+ """
+ Verify that walking a path with a cyclical symlink raises an error
+ """
+ self.createLinks()
+ self.symlink(self.path.child("sub1").path,
+ self.path.child("sub1").child("sub1.loopylink").path)
+ def iterateOverPath():
+ return [foo.path for foo in self.path.walk()]
+ self.assertRaises(filepath.LinkError, iterateOverPath)
+
+
+ def test_walkObeysDescendWithCyclicalSymlinks(self):
+ """
+ Verify that, after making a path with cyclical symlinks, when the
+ supplied C{descend} predicate returns C{False}, the target is not
+ traversed, as if it was a simple symlink.
+ """
+ self.createLinks()
+ # we create cyclical symlinks
+ self.symlink(self.path.child("sub1").path,
+ self.path.child("sub1").child("sub1.loopylink").path)
+ def noSymLinks(path):
+ return not path.islink()
+ def iterateOverPath():
+ return [foo.path for foo in self.path.walk(descend=noSymLinks)]
+ self.assertTrue(iterateOverPath())
+
+
+ def test_walkObeysDescend(self):
+ """
+ Verify that when the supplied C{descend} predicate returns C{False},
+ the target is not traversed.
+ """
+ self.createLinks()
+ def noSymLinks(path):
+ return not path.islink()
+ x = [foo.path for foo in self.path.walk(descend=noSymLinks)]
+ self.assertEquals(set(x), set(self.all))
+
+
+ def test_getAndSet(self):
+ content = 'newcontent'
+ self.path.child('new').setContent(content)
+ newcontent = self.path.child('new').getContent()
+ self.failUnlessEqual(content, newcontent)
+ content = 'content'
+ self.path.child('new').setContent(content, '.tmp')
+ newcontent = self.path.child('new').getContent()
+ self.failUnlessEqual(content, newcontent)
+
+
+ def test_symbolicLink(self):
+ """
+ Verify the behavior of the C{isLink} method against links and
+ non-links. Also check that the symbolic link shares the directory
+ property with its target.
+ """
+ s4 = self.path.child("sub4")
+ s3 = self.path.child("sub3")
+ self.symlink(s3.path, s4.path)
+ self.assertTrue(s4.islink())
+ self.assertFalse(s3.islink())
+ self.assertTrue(s4.isdir())
+ self.assertTrue(s3.isdir())
+
+
+ def test_linkTo(self):
+ """
+ Verify that symlink creates a valid symlink that is both a link and a
+ file if its target is a file, or a directory if its target is a
+ directory.
+ """
+ targetLinks = [
+ (self.path.child("sub2"), self.path.child("sub2.link")),
+ (self.path.child("sub2").child("file3.ext1"),
+ self.path.child("file3.ext1.link"))
+ ]
+ for target, link in targetLinks:
+ target.linkTo(link)
+ self.assertTrue(link.islink(), "This is a link")
+ self.assertEquals(target.isdir(), link.isdir())
+ self.assertEquals(target.isfile(), link.isfile())
+
+
+ def test_linkToErrors(self):
+ """
+ Verify C{linkTo} fails in the following case:
+ - the target is in a directory that doesn't exist
+ - the target already exists
+ """
+ self.assertRaises(OSError, self.path.child("file1").linkTo,
+ self.path.child('nosub').child('file1'))
+ self.assertRaises(OSError, self.path.child("file1").linkTo,
+ self.path.child('sub1').child('file2'))
+
+
+ if not getattr(os, "symlink", None):
+ skipMsg = "Your platform does not support symbolic links."
+ test_symbolicLink.skip = skipMsg
+ test_linkTo.skip = skipMsg
+ test_linkToErrors.skip = skipMsg
+
+
+ def testMultiExt(self):
+ f3 = self.path.child('sub3').child('file3')
+ exts = '.foo','.bar', 'ext1','ext2','ext3'
+ self.failIf(f3.siblingExtensionSearch(*exts))
+ f3e = f3.siblingExtension(".foo")
+ f3e.touch()
+ self.failIf(not f3.siblingExtensionSearch(*exts).exists())
+ self.failIf(not f3.siblingExtensionSearch('*').exists())
+ f3e.remove()
+ self.failIf(f3.siblingExtensionSearch(*exts))
+
+ def testPreauthChild(self):
+ fp = filepath.FilePath('.')
+ fp.preauthChild('foo/bar')
+ self.assertRaises(filepath.InsecurePath, fp.child, '/foo')
+
+ def testStatCache(self):
+ p = self.path.child('stattest')
+ p.touch()
+ self.failUnlessEqual(p.getsize(), 0)
+ self.failUnlessEqual(abs(p.getmtime() - time.time()) // 20, 0)
+ self.failUnlessEqual(abs(p.getctime() - time.time()) // 20, 0)
+ self.failUnlessEqual(abs(p.getatime() - time.time()) // 20, 0)
+ self.failUnlessEqual(p.exists(), True)
+ self.failUnlessEqual(p.exists(), True)
+ # OOB removal: FilePath.remove() will automatically restat
+ os.remove(p.path)
+ # test caching
+ self.failUnlessEqual(p.exists(), True)
+ p.restat(reraise=False)
+ self.failUnlessEqual(p.exists(), False)
+ self.failUnlessEqual(p.islink(), False)
+ self.failUnlessEqual(p.isdir(), False)
+ self.failUnlessEqual(p.isfile(), False)
+
+ def testPersist(self):
+ newpath = pickle.loads(pickle.dumps(self.path))
+ self.failUnlessEqual(self.path.__class__, newpath.__class__)
+ self.failUnlessEqual(self.path.path, newpath.path)
+
+ def testInsecureUNIX(self):
+ self.assertRaises(filepath.InsecurePath, self.path.child, "..")
+ self.assertRaises(filepath.InsecurePath, self.path.child, "/etc")
+ self.assertRaises(filepath.InsecurePath, self.path.child, "../..")
+
+ def testInsecureWin32(self):
+ self.assertRaises(filepath.InsecurePath, self.path.child, r"..\..")
+ self.assertRaises(filepath.InsecurePath, self.path.child, r"C:randomfile")
+
+ if platform.getType() != 'win32':
+ testInsecureWin32.skip = "Consider yourself lucky."
+
+ def testInsecureWin32Whacky(self):
+ """Windows has 'special' filenames like NUL and CON and COM1 and LPR
+ and PRN and ... god knows what else. They can be located anywhere in
+ the filesystem. For obvious reasons, we do not wish to normally permit
+ access to these.
+ """
+ self.assertRaises(filepath.InsecurePath, self.path.child, "CON")
+ self.assertRaises(filepath.InsecurePath, self.path.child, "C:CON")
+ self.assertRaises(filepath.InsecurePath, self.path.child, r"C:\CON")
+
+ if platform.getType() != 'win32':
+ testInsecureWin32Whacky.skip = "Consider yourself lucky."
+
+ def testComparison(self):
+ self.assertEquals(filepath.FilePath('a'),
+ filepath.FilePath('a'))
+ self.failUnless(filepath.FilePath('z') >
+ filepath.FilePath('a'))
+ self.failUnless(filepath.FilePath('z') >=
+ filepath.FilePath('a'))
+ self.failUnless(filepath.FilePath('a') >=
+ filepath.FilePath('a'))
+ self.failUnless(filepath.FilePath('a') <=
+ filepath.FilePath('a'))
+ self.failUnless(filepath.FilePath('a') <
+ filepath.FilePath('z'))
+ self.failUnless(filepath.FilePath('a') <=
+ filepath.FilePath('z'))
+ self.failUnless(filepath.FilePath('a') !=
+ filepath.FilePath('z'))
+ self.failUnless(filepath.FilePath('z') !=
+ filepath.FilePath('a'))
+
+ self.failIf(filepath.FilePath('z') !=
+ filepath.FilePath('z'))
+
+ def testSibling(self):
+ p = self.path.child('sibling_start')
+ ts = p.sibling('sibling_test')
+ self.assertEquals(ts.dirname(), p.dirname())
+ self.assertEquals(ts.basename(), 'sibling_test')
+ ts.createDirectory()
+ self.assertIn(ts, self.path.children())
+
+ def testTemporarySibling(self):
+ ts = self.path.temporarySibling()
+ self.assertEquals(ts.dirname(), self.path.dirname())
+ self.assertNotIn(ts.basename(), self.path.listdir())
+ ts.createDirectory()
+ self.assertIn(ts, self.path.parent().children())
+
+ def testRemove(self):
+ self.path.remove()
+ self.failIf(self.path.exists())
+
+
+ def test_removeWithSymlink(self):
+ """
+ For a path which is a symbolic link, L{FilePath.remove} just deletes
+ the link, not the target.
+ """
+ link = self.path.child("sub1.link")
+ # setUp creates the sub1 child
+ self.symlink(self.path.child("sub1").path, link.path)
+ link.remove()
+ self.assertFalse(link.exists())
+ self.assertTrue(self.path.child("sub1").exists())
+
+
+ def test_copyTo(self):
+ self.assertRaises((OSError, IOError), self.path.copyTo, self.path.child('file1'))
+ oldPaths = list(self.path.walk()) # Record initial state
+ fp = filepath.FilePath(self.mktemp())
+ self.path.copyTo(fp)
+ self.path.remove()
+ fp.copyTo(self.path)
+ newPaths = list(self.path.walk()) # Record double-copy state
+ newPaths.sort()
+ oldPaths.sort()
+ self.assertEquals(newPaths, oldPaths)
+
+
+ def test_copyToWithSymlink(self):
+ """
+ Verify that copying with followLinks=True copies symlink targets
+ instead of symlinks
+ """
+ self.symlink(self.path.child("sub1").path,
+ self.path.child("link1").path)
+ fp = filepath.FilePath(self.mktemp())
+ self.path.copyTo(fp)
+ self.assertFalse(fp.child("link1").islink())
+ self.assertEquals([x.basename() for x in fp.child("sub1").children()],
+ [x.basename() for x in fp.child("link1").children()])
+
+
+ def test_copyToWithoutSymlink(self):
+ """
+ Verify that copying with followLinks=False copies symlinks as symlinks
+ """
+ self.symlink("sub1", self.path.child("link1").path)
+ fp = filepath.FilePath(self.mktemp())
+ self.path.copyTo(fp, followLinks=False)
+ self.assertTrue(fp.child("link1").islink())
+ self.assertEquals(os.readlink(self.path.child("link1").path),
+ os.readlink(fp.child("link1").path))
+
+
+ def test_moveTo(self):
+ """
+ Verify that moving an entire directory results into another directory
+ with the same content.
+ """
+ oldPaths = list(self.path.walk()) # Record initial state
+ fp = filepath.FilePath(self.mktemp())
+ self.path.moveTo(fp)
+ fp.moveTo(self.path)
+ newPaths = list(self.path.walk()) # Record double-move state
+ newPaths.sort()
+ oldPaths.sort()
+ self.assertEquals(newPaths, oldPaths)
+
+
+ def test_moveToError(self):
+ """
+ Verify error behavior of moveTo: it should raises one of OSError or
+ IOError if you want to move a path into one of its child. It's simply
+ the error raised by the underlying rename system call.
+ """
+ self.assertRaises((OSError, IOError), self.path.moveTo, self.path.child('file1'))
+
+
+ def setUpFaultyRename(self):
+ """
+ Set up a C{os.rename} that will fail with L{errno.EXDEV} on first call.
+ This is used to simulate a cross-device rename failure.
+
+ @return: a list of pair (src, dest) of calls to C{os.rename}
+ @rtype: C{list} of C{tuple}
+ """
+ invokedWith = []
+ def faultyRename(src, dest):
+ invokedWith.append((src, dest))
+ if len(invokedWith) == 1:
+ raise OSError(errno.EXDEV, 'Test-induced failure simulating '
+ 'cross-device rename failure')
+ return originalRename(src, dest)
+
+ originalRename = os.rename
+ self.patch(os, "rename", faultyRename)
+ return invokedWith
+
+
+ def test_crossMountMoveTo(self):
+ """
+ C{moveTo} should be able to handle C{EXDEV} error raised by
+ C{os.rename} when trying to move a file on a different mounted
+ filesystem.
+ """
+ invokedWith = self.setUpFaultyRename()
+ # Bit of a whitebox test - force os.rename, which moveTo tries
+ # before falling back to a slower method, to fail, forcing moveTo to
+ # use the slower behavior.
+ self.test_moveTo()
+ # A bit of a sanity check for this whitebox test - if our rename
+ # was never invoked, the test has probably fallen into disrepair!
+ self.assertTrue(invokedWith)
+
+
+ def test_crossMountMoveToWithSymlink(self):
+ """
+ By default, when moving a symlink, it should follow the link and
+ actually copy the content of the linked node.
+ """
+ invokedWith = self.setUpFaultyRename()
+ f2 = self.path.child('file2')
+ f3 = self.path.child('file3')
+ self.symlink(self.path.child('file1').path, f2.path)
+ f2.moveTo(f3)
+ self.assertFalse(f3.islink())
+ self.assertEquals(f3.getContent(), 'file 1')
+ self.assertTrue(invokedWith)
+
+
+ def test_crossMountMoveToWithoutSymlink(self):
+ """
+ Verify that moveTo called with followLinks=False actually create
+ another symlink.
+ """
+ invokedWith = self.setUpFaultyRename()
+ f2 = self.path.child('file2')
+ f3 = self.path.child('file3')
+ self.symlink(self.path.child('file1').path, f2.path)
+ f2.moveTo(f3, followLinks=False)
+ self.assertTrue(f3.islink())
+ self.assertEquals(f3.getContent(), 'file 1')
+ self.assertTrue(invokedWith)
+
+
+ def testOpen(self):
+ # Opening a file for reading when it does not already exist is an error
+ nonexistent = self.path.child('nonexistent')
+ e = self.assertRaises(IOError, nonexistent.open)
+ self.assertEquals(e.errno, errno.ENOENT)
+
+ # Opening a file for writing when it does not exist is okay
+ writer = self.path.child('writer')
+ f = writer.open('w')
+ f.write('abc\ndef')
+ f.close()
+
+ # Make sure those bytes ended up there - and test opening a file for
+ # reading when it does exist at the same time
+ f = writer.open()
+ self.assertEquals(f.read(), 'abc\ndef')
+ f.close()
+
+ # Re-opening that file in write mode should erase whatever was there.
+ f = writer.open('w')
+ f.close()
+ f = writer.open()
+ self.assertEquals(f.read(), '')
+ f.close()
+
+ # Put some bytes in a file so we can test that appending does not
+ # destroy them.
+ appender = self.path.child('appender')
+ f = appender.open('w')
+ f.write('abc')
+ f.close()
+
+ f = appender.open('a')
+ f.write('def')
+ f.close()
+
+ f = appender.open('r')
+ self.assertEquals(f.read(), 'abcdef')
+ f.close()
+
+ # read/write should let us do both without erasing those bytes
+ f = appender.open('r+')
+ self.assertEquals(f.read(), 'abcdef')
+ # ANSI C *requires* an fseek or an fgetpos between an fread and an
+ # fwrite or an fwrite and a fread. We can't reliable get Python to
+ # invoke fgetpos, so we seek to a 0 byte offset from the current
+ # position instead. Also, Python sucks for making this seek
+ # relative to 1 instead of a symbolic constant representing the
+ # current file position.
+ f.seek(0, 1)
+ # Put in some new bytes for us to test for later.
+ f.write('ghi')
+ f.close()
+
+ # Make sure those new bytes really showed up
+ f = appender.open('r')
+ self.assertEquals(f.read(), 'abcdefghi')
+ f.close()
+
+ # write/read should let us do both, but erase anything that's there
+ # already.
+ f = appender.open('w+')
+ self.assertEquals(f.read(), '')
+ f.seek(0, 1) # Don't forget this!
+ f.write('123')
+ f.close()
+
+ # super append mode should let us read and write and also position the
+ # cursor at the end of the file, without erasing everything.
+ f = appender.open('a+')
+
+ # The order of these lines may seem surprising, but it is necessary.
+ # The cursor is not at the end of the file until after the first write.
+ f.write('456')
+ f.seek(0, 1) # Asinine.
+ self.assertEquals(f.read(), '')
+
+ f.seek(0, 0)
+ self.assertEquals(f.read(), '123456')
+ f.close()
+
+ # Opening a file exclusively must fail if that file exists already.
+ nonexistent.requireCreate(True)
+ nonexistent.open('w').close()
+ existent = nonexistent
+ del nonexistent
+ self.assertRaises((OSError, IOError), existent.open)
+
+
+ def test_existsCache(self):
+ """
+ Check that C{filepath.FilePath.exists} correctly restat the object if
+ an operation has occurred in the mean time.
+ """
+ fp = filepath.FilePath(self.mktemp())
+ self.assertEquals(fp.exists(), False)
+
+ fp.makedirs()
+ self.assertEquals(fp.exists(), True)
+
+
+
+from twisted.python import urlpath
+
+class URLPathTestCase(unittest.TestCase):
+ def setUp(self):
+ self.path = urlpath.URLPath.fromString("http://example.com/foo/bar?yes=no&no=yes#footer")
+
+ def testStringConversion(self):
+ self.assertEquals(str(self.path), "http://example.com/foo/bar?yes=no&no=yes#footer")
+
+ def testChildString(self):
+ self.assertEquals(str(self.path.child('hello')), "http://example.com/foo/bar/hello")
+ self.assertEquals(str(self.path.child('hello').child('')), "http://example.com/foo/bar/hello/")
+
+ def testSiblingString(self):
+ self.assertEquals(str(self.path.sibling('baz')), 'http://example.com/foo/baz')
+
+ # The sibling of http://example.com/foo/bar/
+ # is http://example.comf/foo/bar/baz
+ # because really we are constructing a sibling of
+ # http://example.com/foo/bar/index.html
+ self.assertEquals(str(self.path.child('').sibling('baz')), 'http://example.com/foo/bar/baz')
+
+ def testParentString(self):
+ # parent should be equivalent to '..'
+ # 'foo' is the current directory, '/' is the parent directory
+ self.assertEquals(str(self.path.parent()), 'http://example.com/')
+ self.assertEquals(str(self.path.child('').parent()), 'http://example.com/foo/')
+ self.assertEquals(str(self.path.child('baz').parent()), 'http://example.com/foo/')
+ self.assertEquals(str(self.path.parent().parent().parent().parent().parent()), 'http://example.com/')
+
+ def testHereString(self):
+ # here should be equivalent to '.'
+ self.assertEquals(str(self.path.here()), 'http://example.com/foo/')
+ self.assertEquals(str(self.path.child('').here()), 'http://example.com/foo/bar/')
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_pb.py b/vendor/Twisted-10.0.0/twisted/test/test_pb.py
new file mode 100644
index 0000000000..9b9cdc4e90
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_pb.py
@@ -0,0 +1,1775 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for Perspective Broker module.
+
+TODO: update protocol level tests to use new connection API, leaving
+only specific tests for old API.
+"""
+
+# issue1195 TODOs: replace pump.pump() with something involving Deferreds.
+# Clean up warning suppression.
+
+import sys, os, time, gc
+
+from cStringIO import StringIO
+from zope.interface import implements, Interface
+
+from twisted.python.versions import Version
+from twisted.trial import unittest
+from twisted.spread import pb, util, publish, jelly
+from twisted.internet import protocol, main, reactor
+from twisted.internet.error import ConnectionRefusedError
+from twisted.internet.defer import Deferred, gatherResults, succeed
+from twisted.protocols.policies import WrappingFactory
+from twisted.python import failure, log
+from twisted.cred.error import UnauthorizedLogin, UnhandledCredentials
+from twisted.cred import portal, checkers, credentials
+
+
+class Dummy(pb.Viewable):
+ def view_doNothing(self, user):
+ if isinstance(user, DummyPerspective):
+ return 'hello world!'
+ else:
+ return 'goodbye, cruel world!'
+
+
+class DummyPerspective(pb.Avatar):
+ """
+ An L{IPerspective} avatar which will be used in some tests.
+ """
+ def perspective_getDummyViewPoint(self):
+ return Dummy()
+
+
+
+class DummyRealm(object):
+ implements(portal.IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ for iface in interfaces:
+ if iface is pb.IPerspective:
+ return iface, DummyPerspective(avatarId), lambda: None
+
+
+class IOPump:
+ """
+ Utility to pump data between clients and servers for protocol testing.
+
+ Perhaps this is a utility worthy of being in protocol.py?
+ """
+ def __init__(self, client, server, clientIO, serverIO):
+ self.client = client
+ self.server = server
+ self.clientIO = clientIO
+ self.serverIO = serverIO
+
+ def flush(self):
+ """
+ Pump until there is no more input or output. This does not run any
+ timers, so don't use it with any code that calls reactor.callLater.
+ """
+ # failsafe timeout
+ timeout = time.time() + 5
+ while self.pump():
+ if time.time() > timeout:
+ return
+
+ def pump(self):
+ """
+ Move data back and forth.
+
+ Returns whether any data was moved.
+ """
+ self.clientIO.seek(0)
+ self.serverIO.seek(0)
+ cData = self.clientIO.read()
+ sData = self.serverIO.read()
+ self.clientIO.seek(0)
+ self.serverIO.seek(0)
+ self.clientIO.truncate()
+ self.serverIO.truncate()
+ self.client.transport._checkProducer()
+ self.server.transport._checkProducer()
+ for byte in cData:
+ self.server.dataReceived(byte)
+ for byte in sData:
+ self.client.dataReceived(byte)
+ if cData or sData:
+ return 1
+ else:
+ return 0
+
+
+def connectedServerAndClient():
+ """
+ Returns a 3-tuple: (client, server, pump).
+ """
+ clientBroker = pb.Broker()
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(guest='guest')
+ factory = pb.PBServerFactory(portal.Portal(DummyRealm(), [checker]))
+ serverBroker = factory.buildProtocol(('127.0.0.1',))
+
+ clientTransport = StringIO()
+ serverTransport = StringIO()
+ clientBroker.makeConnection(protocol.FileWrapper(clientTransport))
+ serverBroker.makeConnection(protocol.FileWrapper(serverTransport))
+ pump = IOPump(clientBroker, serverBroker, clientTransport, serverTransport)
+ # Challenge-response authentication:
+ pump.flush()
+ return clientBroker, serverBroker, pump
+
+
+class SimpleRemote(pb.Referenceable):
+ def remote_thunk(self, arg):
+ self.arg = arg
+ return arg + 1
+
+ def remote_knuth(self, arg):
+ raise Exception()
+
+
+class NestedRemote(pb.Referenceable):
+ def remote_getSimple(self):
+ return SimpleRemote()
+
+
+class SimpleCopy(pb.Copyable):
+ def __init__(self):
+ self.x = 1
+ self.y = {"Hello":"World"}
+ self.z = ['test']
+
+
+class SimpleLocalCopy(pb.RemoteCopy):
+ pass
+
+pb.setUnjellyableForClass(SimpleCopy, SimpleLocalCopy)
+
+
+class SimpleFactoryCopy(pb.Copyable):
+ """
+ @cvar allIDs: hold every created instances of this class.
+ @type allIDs: C{dict}
+ """
+ allIDs = {}
+ def __init__(self, id):
+ self.id = id
+ SimpleFactoryCopy.allIDs[id] = self
+
+
+def createFactoryCopy(state):
+ """
+ Factory of L{SimpleFactoryCopy}, getting a created instance given the
+ C{id} found in C{state}.
+ """
+ stateId = state.get("id", None)
+ if stateId is None:
+ raise RuntimeError("factory copy state has no 'id' member %s" %
+ (repr(state),))
+ if not stateId in SimpleFactoryCopy.allIDs:
+ raise RuntimeError("factory class has no ID: %s" %
+ (SimpleFactoryCopy.allIDs,))
+ inst = SimpleFactoryCopy.allIDs[stateId]
+ if not inst:
+ raise RuntimeError("factory method found no object with id")
+ return inst
+
+pb.setUnjellyableFactoryForClass(SimpleFactoryCopy, createFactoryCopy)
+
+
+class NestedCopy(pb.Referenceable):
+ def remote_getCopy(self):
+ return SimpleCopy()
+
+ def remote_getFactory(self, value):
+ return SimpleFactoryCopy(value)
+
+
+
+class SimpleCache(pb.Cacheable):
+ def __init___(self):
+ self.x = 1
+ self.y = {"Hello":"World"}
+ self.z = ['test']
+
+
+class NestedComplicatedCache(pb.Referenceable):
+ def __init__(self):
+ self.c = VeryVeryComplicatedCacheable()
+
+ def remote_getCache(self):
+ return self.c
+
+
+class VeryVeryComplicatedCacheable(pb.Cacheable):
+ def __init__(self):
+ self.x = 1
+ self.y = 2
+ self.foo = 3
+
+ def setFoo4(self):
+ self.foo = 4
+ self.observer.callRemote('foo',4)
+
+ def getStateToCacheAndObserveFor(self, perspective, observer):
+ self.observer = observer
+ return {"x": self.x,
+ "y": self.y,
+ "foo": self.foo}
+
+ def stoppedObserving(self, perspective, observer):
+ log.msg("stopped observing")
+ observer.callRemote("end")
+ if observer == self.observer:
+ self.observer = None
+
+
+class RatherBaroqueCache(pb.RemoteCache):
+ def observe_foo(self, newFoo):
+ self.foo = newFoo
+
+ def observe_end(self):
+ log.msg("the end of things")
+
+pb.setUnjellyableForClass(VeryVeryComplicatedCacheable, RatherBaroqueCache)
+
+
+class SimpleLocalCache(pb.RemoteCache):
+ def setCopyableState(self, state):
+ self.__dict__.update(state)
+
+ def checkMethod(self):
+ return self.check
+
+ def checkSelf(self):
+ return self
+
+ def check(self):
+ return 1
+
+pb.setUnjellyableForClass(SimpleCache, SimpleLocalCache)
+
+
+class NestedCache(pb.Referenceable):
+ def __init__(self):
+ self.x = SimpleCache()
+
+ def remote_getCache(self):
+ return [self.x,self.x]
+
+ def remote_putCache(self, cache):
+ return (self.x is cache)
+
+
+class Observable(pb.Referenceable):
+ def __init__(self):
+ self.observers = []
+
+ def remote_observe(self, obs):
+ self.observers.append(obs)
+
+ def remote_unobserve(self, obs):
+ self.observers.remove(obs)
+
+ def notify(self, obj):
+ for observer in self.observers:
+ observer.callRemote('notify', self, obj)
+
+
+class DeferredRemote(pb.Referenceable):
+ def __init__(self):
+ self.run = 0
+
+ def runMe(self, arg):
+ self.run = arg
+ return arg + 1
+
+ def dontRunMe(self, arg):
+ assert 0, "shouldn't have been run!"
+
+ def remote_doItLater(self):
+ """
+ Return a L{Deferred} to be fired on client side. When fired,
+ C{self.runMe} is called.
+ """
+ d = Deferred()
+ d.addCallbacks(self.runMe, self.dontRunMe)
+ self.d = d
+ return d
+
+
+class Observer(pb.Referenceable):
+ notified = 0
+ obj = None
+ def remote_notify(self, other, obj):
+ self.obj = obj
+ self.notified = self.notified + 1
+ other.callRemote('unobserve',self)
+
+
+class NewStyleCopy(pb.Copyable, pb.RemoteCopy, object):
+ def __init__(self, s):
+ self.s = s
+pb.setUnjellyableForClass(NewStyleCopy, NewStyleCopy)
+
+
+class NewStyleCopy2(pb.Copyable, pb.RemoteCopy, object):
+ allocated = 0
+ initialized = 0
+ value = 1
+
+ def __new__(self):
+ NewStyleCopy2.allocated += 1
+ inst = object.__new__(self)
+ inst.value = 2
+ return inst
+
+ def __init__(self):
+ NewStyleCopy2.initialized += 1
+
+pb.setUnjellyableForClass(NewStyleCopy2, NewStyleCopy2)
+
+
+class NewStyleCacheCopy(pb.Cacheable, pb.RemoteCache, object):
+ def getStateToCacheAndObserveFor(self, perspective, observer):
+ return self.__dict__
+
+pb.setUnjellyableForClass(NewStyleCacheCopy, NewStyleCacheCopy)
+
+
+class Echoer(pb.Root):
+ def remote_echo(self, st):
+ return st
+
+
+class CachedReturner(pb.Root):
+ def __init__(self, cache):
+ self.cache = cache
+ def remote_giveMeCache(self, st):
+ return self.cache
+
+
+class NewStyleTestCase(unittest.TestCase):
+ def setUp(self):
+ """
+ Create a pb server using L{Echoer} protocol and connect a client to it.
+ """
+ self.serverFactory = pb.PBServerFactory(Echoer())
+ self.wrapper = WrappingFactory(self.serverFactory)
+ self.server = reactor.listenTCP(0, self.wrapper)
+ clientFactory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", self.server.getHost().port,
+ clientFactory)
+ def gotRoot(ref):
+ self.ref = ref
+ return clientFactory.getRootObject().addCallback(gotRoot)
+
+
+ def tearDown(self):
+ """
+ Close client and server connections, reset values of L{NewStyleCopy2}
+ class variables.
+ """
+ NewStyleCopy2.allocated = 0
+ NewStyleCopy2.initialized = 0
+ NewStyleCopy2.value = 1
+ self.ref.broker.transport.loseConnection()
+ # Disconnect any server-side connections too.
+ for proto in self.wrapper.protocols:
+ proto.transport.loseConnection()
+ return self.server.stopListening()
+
+ def test_newStyle(self):
+ """
+ Create a new style object, send it over the wire, and check the result.
+ """
+ orig = NewStyleCopy("value")
+ d = self.ref.callRemote("echo", orig)
+ def cb(res):
+ self.failUnless(isinstance(res, NewStyleCopy))
+ self.failUnlessEqual(res.s, "value")
+ self.failIf(res is orig) # no cheating :)
+ d.addCallback(cb)
+ return d
+
+ def test_alloc(self):
+ """
+ Send a new style object and check the number of allocations.
+ """
+ orig = NewStyleCopy2()
+ self.failUnlessEqual(NewStyleCopy2.allocated, 1)
+ self.failUnlessEqual(NewStyleCopy2.initialized, 1)
+ d = self.ref.callRemote("echo", orig)
+ def cb(res):
+ # receiving the response creates a third one on the way back
+ self.failUnless(isinstance(res, NewStyleCopy2))
+ self.failUnlessEqual(res.value, 2)
+ self.failUnlessEqual(NewStyleCopy2.allocated, 3)
+ self.failUnlessEqual(NewStyleCopy2.initialized, 1)
+ self.failIf(res is orig) # no cheating :)
+ # sending the object creates a second one on the far side
+ d.addCallback(cb)
+ return d
+
+
+
+class ConnectionNotifyServerFactory(pb.PBServerFactory):
+ """
+ A server factory which stores the last connection and fires a
+ L{Deferred} on connection made. This factory can handle only one
+ client connection.
+
+ @ivar protocolInstance: the last protocol instance.
+ @type protocolInstance: C{pb.Broker}
+
+ @ivar connectionMade: the deferred fired upon connection.
+ @type connectionMade: C{Deferred}
+ """
+ protocolInstance = None
+
+ def __init__(self, root):
+ """
+ Initialize the factory.
+ """
+ pb.PBServerFactory.__init__(self, root)
+ self.connectionMade = Deferred()
+
+
+ def clientConnectionMade(self, protocol):
+ """
+ Store the protocol and fire the connection deferred.
+ """
+ self.protocolInstance = protocol
+ d, self.connectionMade = self.connectionMade, None
+ if d is not None:
+ d.callback(None)
+
+
+
+class NewStyleCachedTestCase(unittest.TestCase):
+ def setUp(self):
+ """
+ Create a pb server using L{CachedReturner} protocol and connect a
+ client to it.
+ """
+ self.orig = NewStyleCacheCopy()
+ self.orig.s = "value"
+ self.server = reactor.listenTCP(0,
+ ConnectionNotifyServerFactory(CachedReturner(self.orig)))
+ clientFactory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", self.server.getHost().port,
+ clientFactory)
+ def gotRoot(ref):
+ self.ref = ref
+ d1 = clientFactory.getRootObject().addCallback(gotRoot)
+ d2 = self.server.factory.connectionMade
+ return gatherResults([d1, d2])
+
+
+ def tearDown(self):
+ """
+ Close client and server connections.
+ """
+ self.server.factory.protocolInstance.transport.loseConnection()
+ self.ref.broker.transport.loseConnection()
+ return self.server.stopListening()
+
+
+ def test_newStyleCache(self):
+ """
+ Get the object from the cache, and checks its properties.
+ """
+ d = self.ref.callRemote("giveMeCache", self.orig)
+ def cb(res):
+ self.failUnless(isinstance(res, NewStyleCacheCopy))
+ self.failUnlessEqual(res.s, "value")
+ self.failIf(res is self.orig) # no cheating :)
+ d.addCallback(cb)
+ return d
+
+
+
+class BrokerTestCase(unittest.TestCase):
+ thunkResult = None
+
+ def tearDown(self):
+ try:
+ # from RemotePublished.getFileName
+ os.unlink('None-None-TESTING.pub')
+ except OSError:
+ pass
+
+ def thunkErrorBad(self, error):
+ self.fail("This should cause a return value, not %s" % (error,))
+
+ def thunkResultGood(self, result):
+ self.thunkResult = result
+
+ def thunkErrorGood(self, tb):
+ pass
+
+ def thunkResultBad(self, result):
+ self.fail("This should cause an error, not %s" % (result,))
+
+ def test_reference(self):
+ c, s, pump = connectedServerAndClient()
+
+ class X(pb.Referenceable):
+ def remote_catch(self,arg):
+ self.caught = arg
+
+ class Y(pb.Referenceable):
+ def remote_throw(self, a, b):
+ a.callRemote('catch', b)
+
+ s.setNameForLocal("y", Y())
+ y = c.remoteForName("y")
+ x = X()
+ z = X()
+ y.callRemote('throw', x, z)
+ pump.pump()
+ pump.pump()
+ pump.pump()
+ self.assertIdentical(x.caught, z, "X should have caught Z")
+
+ # make sure references to remote methods are equals
+ self.assertEquals(y.remoteMethod('throw'), y.remoteMethod('throw'))
+
+ def test_result(self):
+ c, s, pump = connectedServerAndClient()
+ for x, y in (c, s), (s, c):
+ # test reflexivity
+ foo = SimpleRemote()
+ x.setNameForLocal("foo", foo)
+ bar = y.remoteForName("foo")
+ self.expectedThunkResult = 8
+ bar.callRemote('thunk',self.expectedThunkResult - 1
+ ).addCallbacks(self.thunkResultGood, self.thunkErrorBad)
+ # Send question.
+ pump.pump()
+ # Send response.
+ pump.pump()
+ # Shouldn't require any more pumping than that...
+ self.assertEquals(self.thunkResult, self.expectedThunkResult,
+ "result wasn't received.")
+
+ def refcountResult(self, result):
+ self.nestedRemote = result
+
+ def test_tooManyRefs(self):
+ l = []
+ e = []
+ c, s, pump = connectedServerAndClient()
+ foo = NestedRemote()
+ s.setNameForLocal("foo", foo)
+ x = c.remoteForName("foo")
+ for igno in xrange(pb.MAX_BROKER_REFS + 10):
+ if s.transport.closed or c.transport.closed:
+ break
+ x.callRemote("getSimple").addCallbacks(l.append, e.append)
+ pump.pump()
+ expected = (pb.MAX_BROKER_REFS - 1)
+ self.assertTrue(s.transport.closed, "transport was not closed")
+ self.assertEquals(len(l), expected,
+ "expected %s got %s" % (expected, len(l)))
+
+ def test_copy(self):
+ c, s, pump = connectedServerAndClient()
+ foo = NestedCopy()
+ s.setNameForLocal("foo", foo)
+ x = c.remoteForName("foo")
+ x.callRemote('getCopy'
+ ).addCallbacks(self.thunkResultGood, self.thunkErrorBad)
+ pump.pump()
+ pump.pump()
+ self.assertEquals(self.thunkResult.x, 1)
+ self.assertEquals(self.thunkResult.y['Hello'], 'World')
+ self.assertEquals(self.thunkResult.z[0], 'test')
+
+ def test_observe(self):
+ c, s, pump = connectedServerAndClient()
+
+ # this is really testing the comparison between remote objects, to make
+ # sure that you can *UN*observe when you have an observer architecture.
+ a = Observable()
+ b = Observer()
+ s.setNameForLocal("a", a)
+ ra = c.remoteForName("a")
+ ra.callRemote('observe',b)
+ pump.pump()
+ a.notify(1)
+ pump.pump()
+ pump.pump()
+ a.notify(10)
+ pump.pump()
+ pump.pump()
+ self.assertNotIdentical(b.obj, None, "didn't notify")
+ self.assertEquals(b.obj, 1, 'notified too much')
+
+ def test_defer(self):
+ c, s, pump = connectedServerAndClient()
+ d = DeferredRemote()
+ s.setNameForLocal("d", d)
+ e = c.remoteForName("d")
+ pump.pump(); pump.pump()
+ results = []
+ e.callRemote('doItLater').addCallback(results.append)
+ pump.pump(); pump.pump()
+ self.assertFalse(d.run, "Deferred method run too early.")
+ d.d.callback(5)
+ self.assertEquals(d.run, 5, "Deferred method run too late.")
+ pump.pump(); pump.pump()
+ self.assertEquals(results[0], 6, "Incorrect result.")
+
+
+ def test_refcount(self):
+ c, s, pump = connectedServerAndClient()
+ foo = NestedRemote()
+ s.setNameForLocal("foo", foo)
+ bar = c.remoteForName("foo")
+ bar.callRemote('getSimple'
+ ).addCallbacks(self.refcountResult, self.thunkErrorBad)
+
+ # send question
+ pump.pump()
+ # send response
+ pump.pump()
+
+ # delving into internal structures here, because GC is sort of
+ # inherently internal.
+ rluid = self.nestedRemote.luid
+ self.assertIn(rluid, s.localObjects)
+ del self.nestedRemote
+ # nudge the gc
+ if sys.hexversion >= 0x2000000 and os.name != "java":
+ gc.collect()
+ # try to nudge the GC even if we can't really
+ pump.pump()
+ pump.pump()
+ pump.pump()
+ self.assertNotIn(rluid, s.localObjects)
+
+ def test_cache(self):
+ c, s, pump = connectedServerAndClient()
+ obj = NestedCache()
+ obj2 = NestedComplicatedCache()
+ vcc = obj2.c
+ s.setNameForLocal("obj", obj)
+ s.setNameForLocal("xxx", obj2)
+ o2 = c.remoteForName("obj")
+ o3 = c.remoteForName("xxx")
+ coll = []
+ o2.callRemote("getCache"
+ ).addCallback(coll.append).addErrback(coll.append)
+ o2.callRemote("getCache"
+ ).addCallback(coll.append).addErrback(coll.append)
+ complex = []
+ o3.callRemote("getCache").addCallback(complex.append)
+ o3.callRemote("getCache").addCallback(complex.append)
+ pump.flush()
+ # `worst things first'
+ self.assertEquals(complex[0].x, 1)
+ self.assertEquals(complex[0].y, 2)
+ self.assertEquals(complex[0].foo, 3)
+
+ vcc.setFoo4()
+ pump.flush()
+ self.assertEquals(complex[0].foo, 4)
+ self.assertEquals(len(coll), 2)
+ cp = coll[0][0]
+ self.assertIdentical(cp.checkMethod().im_self, cp,
+ "potential refcounting issue")
+ self.assertIdentical(cp.checkSelf(), cp,
+ "other potential refcounting issue")
+ col2 = []
+ o2.callRemote('putCache',cp).addCallback(col2.append)
+ pump.flush()
+ # The objects were the same (testing lcache identity)
+ self.assertTrue(col2[0])
+ # test equality of references to methods
+ self.assertEquals(o2.remoteMethod("getCache"),
+ o2.remoteMethod("getCache"))
+
+ # now, refcounting (similiar to testRefCount)
+ luid = cp.luid
+ baroqueLuid = complex[0].luid
+ self.assertIn(luid, s.remotelyCachedObjects,
+ "remote cache doesn't have it")
+ del coll
+ del cp
+ pump.flush()
+ del complex
+ del col2
+ # extra nudge...
+ pump.flush()
+ # del vcc.observer
+ # nudge the gc
+ if sys.hexversion >= 0x2000000 and os.name != "java":
+ gc.collect()
+ # try to nudge the GC even if we can't really
+ pump.flush()
+ # The GC is done with it.
+ self.assertNotIn(luid, s.remotelyCachedObjects,
+ "Server still had it after GC")
+ self.assertNotIn(luid, c.locallyCachedObjects,
+ "Client still had it after GC")
+ self.assertNotIn(baroqueLuid, s.remotelyCachedObjects,
+ "Server still had complex after GC")
+ self.assertNotIn(baroqueLuid, c.locallyCachedObjects,
+ "Client still had complex after GC")
+ self.assertIdentical(vcc.observer, None, "observer was not removed")
+
+ def test_publishable(self):
+ try:
+ os.unlink('None-None-TESTING.pub') # from RemotePublished.getFileName
+ except OSError:
+ pass # Sometimes it's not there.
+ c, s, pump = connectedServerAndClient()
+ foo = GetPublisher()
+ # foo.pub.timestamp = 1.0
+ s.setNameForLocal("foo", foo)
+ bar = c.remoteForName("foo")
+ accum = []
+ bar.callRemote('getPub').addCallbacks(accum.append, self.thunkErrorBad)
+ pump.flush()
+ obj = accum.pop()
+ self.assertEquals(obj.activateCalled, 1)
+ self.assertEquals(obj.isActivated, 1)
+ self.assertEquals(obj.yayIGotPublished, 1)
+ # timestamp's dirty, we don't have a cache file
+ self.assertEquals(obj._wasCleanWhenLoaded, 0)
+ c, s, pump = connectedServerAndClient()
+ s.setNameForLocal("foo", foo)
+ bar = c.remoteForName("foo")
+ bar.callRemote('getPub').addCallbacks(accum.append, self.thunkErrorBad)
+ pump.flush()
+ obj = accum.pop()
+ # timestamp's clean, our cache file is up-to-date
+ self.assertEquals(obj._wasCleanWhenLoaded, 1)
+
+ def gotCopy(self, val):
+ self.thunkResult = val.id
+
+
+ def test_factoryCopy(self):
+ c, s, pump = connectedServerAndClient()
+ ID = 99
+ obj = NestedCopy()
+ s.setNameForLocal("foo", obj)
+ x = c.remoteForName("foo")
+ x.callRemote('getFactory', ID
+ ).addCallbacks(self.gotCopy, self.thunkResultBad)
+ pump.pump()
+ pump.pump()
+ pump.pump()
+ self.assertEquals(self.thunkResult, ID,
+ "ID not correct on factory object %s" % (self.thunkResult,))
+
+
+bigString = "helloworld" * 50
+
+callbackArgs = None
+callbackKeyword = None
+
+def finishedCallback(*args, **kw):
+ global callbackArgs, callbackKeyword
+ callbackArgs = args
+ callbackKeyword = kw
+
+
+class Pagerizer(pb.Referenceable):
+ def __init__(self, callback, *args, **kw):
+ self.callback, self.args, self.kw = callback, args, kw
+
+ def remote_getPages(self, collector):
+ util.StringPager(collector, bigString, 100,
+ self.callback, *self.args, **self.kw)
+ self.args = self.kw = None
+
+
+class FilePagerizer(pb.Referenceable):
+ pager = None
+
+ def __init__(self, filename, callback, *args, **kw):
+ self.filename = filename
+ self.callback, self.args, self.kw = callback, args, kw
+
+ def remote_getPages(self, collector):
+ self.pager = util.FilePager(collector, file(self.filename),
+ self.callback, *self.args, **self.kw)
+ self.args = self.kw = None
+
+
+
+class PagingTestCase(unittest.TestCase):
+ """
+ Test pb objects sending data by pages.
+ """
+
+ def setUp(self):
+ """
+ Create a file used to test L{util.FilePager}.
+ """
+ self.filename = self.mktemp()
+ fd = file(self.filename, 'w')
+ fd.write(bigString)
+ fd.close()
+
+
+ def test_pagingWithCallback(self):
+ """
+ Test L{util.StringPager}, passing a callback to fire when all pages
+ are sent.
+ """
+ c, s, pump = connectedServerAndClient()
+ s.setNameForLocal("foo", Pagerizer(finishedCallback, 'hello', value=10))
+ x = c.remoteForName("foo")
+ l = []
+ util.getAllPages(x, "getPages").addCallback(l.append)
+ while not l:
+ pump.pump()
+ self.assertEquals(''.join(l[0]), bigString,
+ "Pages received not equal to pages sent!")
+ self.assertEquals(callbackArgs, ('hello',),
+ "Completed callback not invoked")
+ self.assertEquals(callbackKeyword, {'value': 10},
+ "Completed callback not invoked")
+
+
+ def test_pagingWithoutCallback(self):
+ """
+ Test L{util.StringPager} without a callback.
+ """
+ c, s, pump = connectedServerAndClient()
+ s.setNameForLocal("foo", Pagerizer(None))
+ x = c.remoteForName("foo")
+ l = []
+ util.getAllPages(x, "getPages").addCallback(l.append)
+ while not l:
+ pump.pump()
+ self.assertEquals(''.join(l[0]), bigString,
+ "Pages received not equal to pages sent!")
+
+
+ def test_emptyFilePaging(self):
+ """
+ Test L{util.FilePager}, sending an empty file.
+ """
+ filenameEmpty = self.mktemp()
+ fd = file(filenameEmpty, 'w')
+ fd.close()
+ c, s, pump = connectedServerAndClient()
+ pagerizer = FilePagerizer(filenameEmpty, None)
+ s.setNameForLocal("bar", pagerizer)
+ x = c.remoteForName("bar")
+ l = []
+ util.getAllPages(x, "getPages").addCallback(l.append)
+ ttl = 10
+ while not l and ttl > 0:
+ pump.pump()
+ ttl -= 1
+ if not ttl:
+ self.fail('getAllPages timed out')
+ self.assertEquals(''.join(l[0]), '',
+ "Pages received not equal to pages sent!")
+
+
+ def test_filePagingWithCallback(self):
+ """
+ Test L{util.FilePager}, passing a callback to fire when all pages
+ are sent, and verify that the pager doesn't keep chunks in memory.
+ """
+ c, s, pump = connectedServerAndClient()
+ pagerizer = FilePagerizer(self.filename, finishedCallback,
+ 'frodo', value = 9)
+ s.setNameForLocal("bar", pagerizer)
+ x = c.remoteForName("bar")
+ l = []
+ util.getAllPages(x, "getPages").addCallback(l.append)
+ while not l:
+ pump.pump()
+ self.assertEquals(''.join(l[0]), bigString,
+ "Pages received not equal to pages sent!")
+ self.assertEquals(callbackArgs, ('frodo',),
+ "Completed callback not invoked")
+ self.assertEquals(callbackKeyword, {'value': 9},
+ "Completed callback not invoked")
+ self.assertEquals(pagerizer.pager.chunks, [])
+
+
+ def test_filePagingWithoutCallback(self):
+ """
+ Test L{util.FilePager} without a callback.
+ """
+ c, s, pump = connectedServerAndClient()
+ pagerizer = FilePagerizer(self.filename, None)
+ s.setNameForLocal("bar", pagerizer)
+ x = c.remoteForName("bar")
+ l = []
+ util.getAllPages(x, "getPages").addCallback(l.append)
+ while not l:
+ pump.pump()
+ self.assertEquals(''.join(l[0]), bigString,
+ "Pages received not equal to pages sent!")
+ self.assertEquals(pagerizer.pager.chunks, [])
+
+
+
+class DumbPublishable(publish.Publishable):
+ def getStateToPublish(self):
+ return {"yayIGotPublished": 1}
+
+
+class DumbPub(publish.RemotePublished):
+ def activated(self):
+ self.activateCalled = 1
+
+
+class GetPublisher(pb.Referenceable):
+ def __init__(self):
+ self.pub = DumbPublishable("TESTING")
+
+ def remote_getPub(self):
+ return self.pub
+
+
+pb.setUnjellyableForClass(DumbPublishable, DumbPub)
+
+class DisconnectionTestCase(unittest.TestCase):
+ """
+ Test disconnection callbacks.
+ """
+
+ def error(self, *args):
+ raise RuntimeError("I shouldn't have been called: %s" % (args,))
+
+
+ def gotDisconnected(self):
+ """
+ Called on broker disconnect.
+ """
+ self.gotCallback = 1
+
+ def objectDisconnected(self, o):
+ """
+ Called on RemoteReference disconnect.
+ """
+ self.assertEquals(o, self.remoteObject)
+ self.objectCallback = 1
+
+ def test_badSerialization(self):
+ c, s, pump = connectedServerAndClient()
+ pump.pump()
+ s.setNameForLocal("o", BadCopySet())
+ g = c.remoteForName("o")
+ l = []
+ g.callRemote("setBadCopy", BadCopyable()).addErrback(l.append)
+ pump.flush()
+ self.assertEquals(len(l), 1)
+
+ def test_disconnection(self):
+ c, s, pump = connectedServerAndClient()
+ pump.pump()
+ s.setNameForLocal("o", SimpleRemote())
+
+ # get a client reference to server object
+ r = c.remoteForName("o")
+ pump.pump()
+ pump.pump()
+ pump.pump()
+
+ # register and then unregister disconnect callbacks
+ # making sure they get unregistered
+ c.notifyOnDisconnect(self.error)
+ self.assertIn(self.error, c.disconnects)
+ c.dontNotifyOnDisconnect(self.error)
+ self.assertNotIn(self.error, c.disconnects)
+
+ r.notifyOnDisconnect(self.error)
+ self.assertIn(r._disconnected, c.disconnects)
+ self.assertIn(self.error, r.disconnectCallbacks)
+ r.dontNotifyOnDisconnect(self.error)
+ self.assertNotIn(r._disconnected, c.disconnects)
+ self.assertNotIn(self.error, r.disconnectCallbacks)
+
+ # register disconnect callbacks
+ c.notifyOnDisconnect(self.gotDisconnected)
+ r.notifyOnDisconnect(self.objectDisconnected)
+ self.remoteObject = r
+
+ # disconnect
+ c.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ self.assertTrue(self.gotCallback)
+ self.assertTrue(self.objectCallback)
+
+
+class FreakOut(Exception):
+ pass
+
+
+class BadCopyable(pb.Copyable):
+ def getStateToCopyFor(self, p):
+ raise FreakOut()
+
+
+class BadCopySet(pb.Referenceable):
+ def remote_setBadCopy(self, bc):
+ return None
+
+
+class LocalRemoteTest(util.LocalAsRemote):
+ reportAllTracebacks = 0
+
+ def sync_add1(self, x):
+ return x + 1
+
+ def async_add(self, x=0, y=1):
+ return x + y
+
+ def async_fail(self):
+ raise RuntimeError()
+
+
+
+class MyPerspective(pb.Avatar):
+ """
+ @ivar loggedIn: set to C{True} when the avatar is logged in.
+ @type loggedIn: C{bool}
+
+ @ivar loggedOut: set to C{True} when the avatar is logged out.
+ @type loggedOut: C{bool}
+ """
+ implements(pb.IPerspective)
+
+ loggedIn = loggedOut = False
+
+ def __init__(self, avatarId):
+ self.avatarId = avatarId
+
+
+ def perspective_getAvatarId(self):
+ """
+ Return the avatar identifier which was used to access this avatar.
+ """
+ return self.avatarId
+
+
+ def perspective_getViewPoint(self):
+ return MyView()
+
+
+ def perspective_add(self, a, b):
+ """
+ Add the given objects and return the result. This is a method
+ unavailable on L{Echoer}, so it can only be invoked by authenticated
+ users who received their avatar from L{TestRealm}.
+ """
+ return a + b
+
+
+ def logout(self):
+ self.loggedOut = True
+
+
+
+class TestRealm(object):
+ """
+ A realm which repeatedly gives out a single instance of L{MyPerspective}
+ for non-anonymous logins and which gives out a new instance of L{Echoer}
+ for each anonymous login.
+
+ @ivar lastPerspective: The L{MyPerspective} most recently created and
+ returned from C{requestAvatar}.
+
+ @ivar perspectiveFactory: A one-argument callable which will be used to
+ create avatars to be returned from C{requestAvatar}.
+ """
+ perspectiveFactory = MyPerspective
+
+ lastPerspective = None
+
+ def requestAvatar(self, avatarId, mind, interface):
+ """
+ Verify that the mind and interface supplied have the expected values
+ (this should really be done somewhere else, like inside a test method)
+ and return an avatar appropriate for the given identifier.
+ """
+ assert interface == pb.IPerspective
+ assert mind == "BRAINS!"
+ if avatarId is checkers.ANONYMOUS:
+ return pb.IPerspective, Echoer(), lambda: None
+ else:
+ self.lastPerspective = self.perspectiveFactory(avatarId)
+ self.lastPerspective.loggedIn = True
+ return (
+ pb.IPerspective, self.lastPerspective,
+ self.lastPerspective.logout)
+
+
+
+class MyView(pb.Viewable):
+
+ def view_check(self, user):
+ return isinstance(user, MyPerspective)
+
+
+
+class NewCredTestCase(unittest.TestCase):
+ """
+ Tests related to the L{twisted.cred} support in PB.
+ """
+ def setUp(self):
+ """
+ Create a portal with no checkers and wrap it around a simple test
+ realm. Set up a PB server on a TCP port which serves perspectives
+ using that portal.
+ """
+ self.realm = TestRealm()
+ self.portal = portal.Portal(self.realm)
+ self.factory = ConnectionNotifyServerFactory(self.portal)
+ self.port = reactor.listenTCP(0, self.factory, interface="127.0.0.1")
+ self.portno = self.port.getHost().port
+
+
+ def tearDown(self):
+ """
+ Shut down the TCP port created by L{setUp}.
+ """
+ return self.port.stopListening()
+
+
+ def getFactoryAndRootObject(self, clientFactory=pb.PBClientFactory):
+ """
+ Create a connection to the test server.
+
+ @param clientFactory: the factory class used to create the connection.
+
+ @return: a tuple (C{factory}, C{deferred}), where factory is an
+ instance of C{clientFactory} and C{deferred} the L{Deferred} firing
+ with the PB root object.
+ """
+ factory = clientFactory()
+ rootObjDeferred = factory.getRootObject()
+ connector = reactor.connectTCP('127.0.0.1', self.portno, factory)
+ self.addCleanup(connector.disconnect)
+ return factory, rootObjDeferred
+
+
+ def test_getRootObject(self):
+ """
+ Assert only that L{PBClientFactory.getRootObject}'s Deferred fires with
+ a L{RemoteReference}.
+ """
+ factory, rootObjDeferred = self.getFactoryAndRootObject()
+
+ def gotRootObject(rootObj):
+ self.assertIsInstance(rootObj, pb.RemoteReference)
+ disconnectedDeferred = Deferred()
+ rootObj.notifyOnDisconnect(disconnectedDeferred.callback)
+ factory.disconnect()
+ return disconnectedDeferred
+
+ return rootObjDeferred.addCallback(gotRootObject)
+
+
+ def test_deadReferenceError(self):
+ """
+ Test that when a connection is lost, calling a method on a
+ RemoteReference obtained from it raises DeadReferenceError.
+ """
+ factory, rootObjDeferred = self.getFactoryAndRootObject()
+
+ def gotRootObject(rootObj):
+ disconnectedDeferred = Deferred()
+ rootObj.notifyOnDisconnect(disconnectedDeferred.callback)
+
+ def lostConnection(ign):
+ self.assertRaises(
+ pb.DeadReferenceError,
+ rootObj.callRemote, 'method')
+
+ disconnectedDeferred.addCallback(lostConnection)
+ factory.disconnect()
+ return disconnectedDeferred
+
+ return rootObjDeferred.addCallback(gotRootObject)
+
+
+ def test_clientConnectionLost(self):
+ """
+ Test that if the L{reconnecting} flag is passed with a True value then
+ a remote call made from a disconnection notification callback gets a
+ result successfully.
+ """
+ class ReconnectOnce(pb.PBClientFactory):
+ reconnectedAlready = False
+ def clientConnectionLost(self, connector, reason):
+ reconnecting = not self.reconnectedAlready
+ self.reconnectedAlready = True
+ if reconnecting:
+ connector.connect()
+ return pb.PBClientFactory.clientConnectionLost(
+ self, connector, reason, reconnecting)
+
+ factory, rootObjDeferred = self.getFactoryAndRootObject(ReconnectOnce)
+
+ def gotRootObject(rootObj):
+ self.assertIsInstance(rootObj, pb.RemoteReference)
+
+ d = Deferred()
+ rootObj.notifyOnDisconnect(d.callback)
+ factory.disconnect()
+
+ def disconnected(ign):
+ d = factory.getRootObject()
+
+ def gotAnotherRootObject(anotherRootObj):
+ self.assertIsInstance(anotherRootObj, pb.RemoteReference)
+
+ d = Deferred()
+ anotherRootObj.notifyOnDisconnect(d.callback)
+ factory.disconnect()
+ return d
+ return d.addCallback(gotAnotherRootObject)
+ return d.addCallback(disconnected)
+ return rootObjDeferred.addCallback(gotRootObject)
+
+
+ def test_immediateClose(self):
+ """
+ Test that if a Broker loses its connection without receiving any bytes,
+ it doesn't raise any exceptions or log any errors.
+ """
+ serverProto = self.factory.buildProtocol(('127.0.0.1', 12345))
+ serverProto.makeConnection(protocol.FileWrapper(StringIO()))
+ serverProto.connectionLost(failure.Failure(main.CONNECTION_DONE))
+
+
+ def test_loginConnectionRefused(self):
+ """
+ L{PBClientFactory.login} returns a L{Deferred} which is errbacked
+ with the L{ConnectionRefusedError} if the underlying connection is
+ refused.
+ """
+ clientFactory = pb.PBClientFactory()
+ loginDeferred = clientFactory.login(
+ credentials.UsernamePassword("foo", "bar"))
+ clientFactory.clientConnectionFailed(
+ None,
+ failure.Failure(
+ ConnectionRefusedError("Test simulated refused connection")))
+ return self.assertFailure(loginDeferred, ConnectionRefusedError)
+
+
+ def _disconnect(self, ignore, factory):
+ """
+ Helper method disconnecting the given client factory and returning a
+ C{Deferred} that will fire when the server connection has noticed the
+ disconnection.
+ """
+ disconnectedDeferred = Deferred()
+ self.factory.protocolInstance.notifyOnDisconnect(
+ lambda: disconnectedDeferred.callback(None))
+ factory.disconnect()
+ return disconnectedDeferred
+
+
+ def test_loginLogout(self):
+ """
+ Test that login can be performed with IUsernamePassword credentials and
+ that when the connection is dropped the avatar is logged out.
+ """
+ self.portal.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass'))
+ factory = pb.PBClientFactory()
+ creds = credentials.UsernamePassword("user", "pass")
+
+ # NOTE: real code probably won't need anything where we have the
+ # "BRAINS!" argument, passing None is fine. We just do it here to
+ # test that it is being passed. It is used to give additional info to
+ # the realm to aid perspective creation, if you don't need that,
+ # ignore it.
+ mind = "BRAINS!"
+
+ d = factory.login(creds, mind)
+ def cbLogin(perspective):
+ self.assertTrue(self.realm.lastPerspective.loggedIn)
+ self.assertIsInstance(perspective, pb.RemoteReference)
+ return self._disconnect(None, factory)
+ d.addCallback(cbLogin)
+
+ def cbLogout(ignored):
+ self.assertTrue(self.realm.lastPerspective.loggedOut)
+ d.addCallback(cbLogout)
+
+ connector = reactor.connectTCP("127.0.0.1", self.portno, factory)
+ self.addCleanup(connector.disconnect)
+ return d
+
+
+ def test_logoutAfterDecref(self):
+ """
+ If a L{RemoteReference} to an L{IPerspective} avatar is decrefed and
+ there remain no other references to the avatar on the server, the
+ avatar is garbage collected and the logout method called.
+ """
+ loggedOut = Deferred()
+
+ class EventPerspective(pb.Avatar):
+ """
+ An avatar which fires a Deferred when it is logged out.
+ """
+ def __init__(self, avatarId):
+ pass
+
+ def logout(self):
+ loggedOut.callback(None)
+
+ self.realm.perspectiveFactory = EventPerspective
+
+ self.portal.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(foo='bar'))
+ factory = pb.PBClientFactory()
+ d = factory.login(
+ credentials.UsernamePassword('foo', 'bar'), "BRAINS!")
+ def cbLoggedIn(avatar):
+ # Just wait for the logout to happen, as it should since the
+ # reference to the avatar will shortly no longer exists.
+ return loggedOut
+ d.addCallback(cbLoggedIn)
+ def cbLoggedOut(ignored):
+ # Verify that the server broker's _localCleanup dict isn't growing
+ # without bound.
+ self.assertEqual(self.factory.protocolInstance._localCleanup, {})
+ d.addCallback(cbLoggedOut)
+ d.addCallback(self._disconnect, factory)
+ connector = reactor.connectTCP("127.0.0.1", self.portno, factory)
+ self.addCleanup(connector.disconnect)
+ return d
+
+
+ def test_concurrentLogin(self):
+ """
+ Two different correct login attempts can be made on the same root
+ object at the same time and produce two different resulting avatars.
+ """
+ self.portal.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(
+ foo='bar', baz='quux'))
+ factory = pb.PBClientFactory()
+
+ firstLogin = factory.login(
+ credentials.UsernamePassword('foo', 'bar'), "BRAINS!")
+ secondLogin = factory.login(
+ credentials.UsernamePassword('baz', 'quux'), "BRAINS!")
+ d = gatherResults([firstLogin, secondLogin])
+ def cbLoggedIn((first, second)):
+ return gatherResults([
+ first.callRemote('getAvatarId'),
+ second.callRemote('getAvatarId')])
+ d.addCallback(cbLoggedIn)
+ def cbAvatarIds((first, second)):
+ self.assertEqual(first, 'foo')
+ self.assertEqual(second, 'baz')
+ d.addCallback(cbAvatarIds)
+ d.addCallback(self._disconnect, factory)
+
+ connector = reactor.connectTCP('127.0.0.1', self.portno, factory)
+ self.addCleanup(connector.disconnect)
+ return d
+
+
+ def test_badUsernamePasswordLogin(self):
+ """
+ Test that a login attempt with an invalid user or invalid password
+ fails in the appropriate way.
+ """
+ self.portal.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass'))
+ factory = pb.PBClientFactory()
+
+ firstLogin = factory.login(
+ credentials.UsernamePassword('nosuchuser', 'pass'))
+ secondLogin = factory.login(
+ credentials.UsernamePassword('user', 'wrongpass'))
+
+ self.assertFailure(firstLogin, UnauthorizedLogin)
+ self.assertFailure(secondLogin, UnauthorizedLogin)
+ d = gatherResults([firstLogin, secondLogin])
+
+ def cleanup(ignore):
+ errors = self.flushLoggedErrors(UnauthorizedLogin)
+ self.assertEquals(len(errors), 2)
+ return self._disconnect(None, factory)
+ d.addCallback(cleanup)
+
+ connector = reactor.connectTCP("127.0.0.1", self.portno, factory)
+ self.addCleanup(connector.disconnect)
+ return d
+
+
+ def test_anonymousLogin(self):
+ """
+ Verify that a PB server using a portal configured with an checker which
+ allows IAnonymous credentials can be logged into using IAnonymous
+ credentials.
+ """
+ self.portal.registerChecker(checkers.AllowAnonymousAccess())
+ factory = pb.PBClientFactory()
+ d = factory.login(credentials.Anonymous(), "BRAINS!")
+
+ def cbLoggedIn(perspective):
+ return perspective.callRemote('echo', 123)
+ d.addCallback(cbLoggedIn)
+
+ d.addCallback(self.assertEqual, 123)
+
+ d.addCallback(self._disconnect, factory)
+
+ connector = reactor.connectTCP("127.0.0.1", self.portno, factory)
+ self.addCleanup(connector.disconnect)
+ return d
+
+
+ def test_anonymousLoginNotPermitted(self):
+ """
+ Verify that without an anonymous checker set up, anonymous login is
+ rejected.
+ """
+ self.portal.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass'))
+ factory = pb.PBClientFactory()
+ d = factory.login(credentials.Anonymous(), "BRAINS!")
+ self.assertFailure(d, UnhandledCredentials)
+
+ def cleanup(ignore):
+ errors = self.flushLoggedErrors(UnhandledCredentials)
+ self.assertEquals(len(errors), 1)
+ return self._disconnect(None, factory)
+ d.addCallback(cleanup)
+
+ connector = reactor.connectTCP('127.0.0.1', self.portno, factory)
+ self.addCleanup(connector.disconnect)
+ return d
+
+
+ def test_anonymousLoginWithMultipleCheckers(self):
+ """
+ Like L{test_anonymousLogin} but against a portal with a checker for
+ both IAnonymous and IUsernamePassword.
+ """
+ self.portal.registerChecker(checkers.AllowAnonymousAccess())
+ self.portal.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass'))
+ factory = pb.PBClientFactory()
+ d = factory.login(credentials.Anonymous(), "BRAINS!")
+
+ def cbLogin(perspective):
+ return perspective.callRemote('echo', 123)
+ d.addCallback(cbLogin)
+
+ d.addCallback(self.assertEqual, 123)
+
+ d.addCallback(self._disconnect, factory)
+
+ connector = reactor.connectTCP('127.0.0.1', self.portno, factory)
+ self.addCleanup(connector.disconnect)
+ return d
+
+
+ def test_authenticatedLoginWithMultipleCheckers(self):
+ """
+ Like L{test_anonymousLoginWithMultipleCheckers} but check that
+ username/password authentication works.
+ """
+ self.portal.registerChecker(checkers.AllowAnonymousAccess())
+ self.portal.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass'))
+ factory = pb.PBClientFactory()
+ d = factory.login(
+ credentials.UsernamePassword('user', 'pass'), "BRAINS!")
+
+ def cbLogin(perspective):
+ return perspective.callRemote('add', 100, 23)
+ d.addCallback(cbLogin)
+
+ d.addCallback(self.assertEqual, 123)
+
+ d.addCallback(self._disconnect, factory)
+
+ connector = reactor.connectTCP('127.0.0.1', self.portno, factory)
+ self.addCleanup(connector.disconnect)
+ return d
+
+
+ def test_view(self):
+ """
+ Verify that a viewpoint can be retrieved after authenticating with
+ cred.
+ """
+ self.portal.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass'))
+ factory = pb.PBClientFactory()
+ d = factory.login(
+ credentials.UsernamePassword("user", "pass"), "BRAINS!")
+
+ def cbLogin(perspective):
+ return perspective.callRemote("getViewPoint")
+ d.addCallback(cbLogin)
+
+ def cbView(viewpoint):
+ return viewpoint.callRemote("check")
+ d.addCallback(cbView)
+
+ d.addCallback(self.assertTrue)
+
+ d.addCallback(self._disconnect, factory)
+
+ connector = reactor.connectTCP("127.0.0.1", self.portno, factory)
+ self.addCleanup(connector.disconnect)
+ return d
+
+
+
+class NonSubclassingPerspective:
+ implements(pb.IPerspective)
+
+ def __init__(self, avatarId):
+ pass
+
+ # IPerspective implementation
+ def perspectiveMessageReceived(self, broker, message, args, kwargs):
+ args = broker.unserialize(args, self)
+ kwargs = broker.unserialize(kwargs, self)
+ return broker.serialize((message, args, kwargs))
+
+ # Methods required by TestRealm
+ def logout(self):
+ self.loggedOut = True
+
+
+
+class NSPTestCase(unittest.TestCase):
+ """
+ Tests for authentication against a realm where the L{IPerspective}
+ implementation is not a subclass of L{Avatar}.
+ """
+ def setUp(self):
+ self.realm = TestRealm()
+ self.realm.perspectiveFactory = NonSubclassingPerspective
+ self.portal = portal.Portal(self.realm)
+ self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ self.checker.addUser("user", "pass")
+ self.portal.registerChecker(self.checker)
+ self.factory = WrappingFactory(pb.PBServerFactory(self.portal))
+ self.port = reactor.listenTCP(0, self.factory, interface="127.0.0.1")
+ self.addCleanup(self.port.stopListening)
+ self.portno = self.port.getHost().port
+
+
+ def test_NSP(self):
+ """
+ An L{IPerspective} implementation which does not subclass
+ L{Avatar} can expose remote methods for the client to call.
+ """
+ factory = pb.PBClientFactory()
+ d = factory.login(credentials.UsernamePassword('user', 'pass'),
+ "BRAINS!")
+ reactor.connectTCP('127.0.0.1', self.portno, factory)
+ d.addCallback(lambda p: p.callRemote('ANYTHING', 'here', bar='baz'))
+ d.addCallback(self.assertEquals,
+ ('ANYTHING', ('here',), {'bar': 'baz'}))
+ def cleanup(ignored):
+ factory.disconnect()
+ for p in self.factory.protocols:
+ p.transport.loseConnection()
+ d.addCallback(cleanup)
+ return d
+
+
+
+class IForwarded(Interface):
+ """
+ Interface used for testing L{util.LocalAsyncForwarder}.
+ """
+
+ def forwardMe():
+ """
+ Simple synchronous method.
+ """
+
+ def forwardDeferred():
+ """
+ Simple asynchronous method.
+ """
+
+
+class Forwarded:
+ """
+ Test implementation of L{IForwarded}.
+
+ @ivar forwarded: set if C{forwardMe} is called.
+ @type forwarded: C{bool}
+ @ivar unforwarded: set if C{dontForwardMe} is called.
+ @type unforwarded: C{bool}
+ """
+ implements(IForwarded)
+ forwarded = False
+ unforwarded = False
+
+ def forwardMe(self):
+ """
+ Set a local flag to test afterwards.
+ """
+ self.forwarded = True
+
+ def dontForwardMe(self):
+ """
+ Set a local flag to test afterwards. This should not be called as it's
+ not in the interface.
+ """
+ self.unforwarded = True
+
+ def forwardDeferred(self):
+ """
+ Asynchronously return C{True}.
+ """
+ return succeed(True)
+
+
+class SpreadUtilTestCase(unittest.TestCase):
+ """
+ Tests for L{twisted.spread.util}.
+ """
+
+ def test_sync(self):
+ """
+ Call a synchronous method of a L{util.LocalAsRemote} object and check
+ the result.
+ """
+ o = LocalRemoteTest()
+ self.assertEquals(o.callRemote("add1", 2), 3)
+
+ def test_async(self):
+ """
+ Call an asynchronous method of a L{util.LocalAsRemote} object and check
+ the result.
+ """
+ o = LocalRemoteTest()
+ o = LocalRemoteTest()
+ d = o.callRemote("add", 2, y=4)
+ self.assertIsInstance(d, Deferred)
+ d.addCallback(self.assertEquals, 6)
+ return d
+
+ def test_asyncFail(self):
+ """
+ Test a asynchronous failure on a remote method call.
+ """
+ l = []
+ o = LocalRemoteTest()
+ d = o.callRemote("fail")
+ def eb(f):
+ self.assertTrue(isinstance(f, failure.Failure))
+ f.trap(RuntimeError)
+ d.addCallbacks(lambda res: self.fail("supposed to fail"), eb)
+ return d
+
+ def test_remoteMethod(self):
+ """
+ Test the C{remoteMethod} facility of L{util.LocalAsRemote}.
+ """
+ o = LocalRemoteTest()
+ m = o.remoteMethod("add1")
+ self.assertEquals(m(3), 4)
+
+ def test_localAsyncForwarder(self):
+ """
+ Test a call to L{util.LocalAsyncForwarder} using L{Forwarded} local
+ object.
+ """
+ f = Forwarded()
+ lf = util.LocalAsyncForwarder(f, IForwarded)
+ lf.callRemote("forwardMe")
+ self.assertTrue(f.forwarded)
+ lf.callRemote("dontForwardMe")
+ self.assertFalse(f.unforwarded)
+ rr = lf.callRemote("forwardDeferred")
+ l = []
+ rr.addCallback(l.append)
+ self.assertEqual(l[0], 1)
+
+
+
+class PBWithSecurityOptionsTest(unittest.TestCase):
+ """
+ Test security customization.
+ """
+
+ def test_clientDefaultSecurityOptions(self):
+ """
+ By default, client broker should use C{jelly.globalSecurity} as
+ security settings.
+ """
+ factory = pb.PBClientFactory()
+ broker = factory.buildProtocol(None)
+ self.assertIdentical(broker.security, jelly.globalSecurity)
+
+
+ def test_serverDefaultSecurityOptions(self):
+ """
+ By default, server broker should use C{jelly.globalSecurity} as
+ security settings.
+ """
+ factory = pb.PBServerFactory(Echoer())
+ broker = factory.buildProtocol(None)
+ self.assertIdentical(broker.security, jelly.globalSecurity)
+
+
+ def test_clientSecurityCustomization(self):
+ """
+ Check that the security settings are passed from the client factory to
+ the broker object.
+ """
+ security = jelly.SecurityOptions()
+ factory = pb.PBClientFactory(security=security)
+ broker = factory.buildProtocol(None)
+ self.assertIdentical(broker.security, security)
+
+
+ def test_serverSecurityCustomization(self):
+ """
+ Check that the security settings are passed from the server factory to
+ the broker object.
+ """
+ security = jelly.SecurityOptions()
+ factory = pb.PBServerFactory(Echoer(), security=security)
+ broker = factory.buildProtocol(None)
+ self.assertIdentical(broker.security, security)
+
+
+
+class DeprecationTests(unittest.TestCase):
+ """
+ Tests for certain deprecations of free-functions in L{twisted.spread.pb}.
+ """
+ def test_noOperationDeprecated(self):
+ """
+ L{pb.noOperation} is deprecated.
+ """
+ self.callDeprecated(
+ Version("twisted", 8, 2, 0),
+ pb.noOperation, 1, 2, x=3, y=4)
+
+
+ def test_printTraceback(self):
+ """
+ L{pb.printTraceback} is deprecated.
+ """
+ self.callDeprecated(
+ Version("twisted", 8, 2, 0),
+ pb.printTraceback,
+ "printTraceback deprecation fake traceback value")
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_pbfailure.py b/vendor/Twisted-10.0.0/twisted/test/test_pbfailure.py
new file mode 100644
index 0000000000..40781d0043
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_pbfailure.py
@@ -0,0 +1,424 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for error handling in PB.
+"""
+
+from twisted.trial import unittest
+
+from twisted.spread import pb, flavors, jelly
+from twisted.internet import reactor, defer
+from twisted.python import log
+
+##
+# test exceptions
+##
+class AsynchronousException(Exception):
+ """
+ Helper used to test remote methods which return Deferreds which fail with
+ exceptions which are not L{pb.Error} subclasses.
+ """
+
+
+class SynchronousException(Exception):
+ """
+ Helper used to test remote methods which raise exceptions which are not
+ L{pb.Error} subclasses.
+ """
+
+
+class AsynchronousError(pb.Error):
+ """
+ Helper used to test remote methods which return Deferreds which fail with
+ exceptions which are L{pb.Error} subclasses.
+ """
+
+
+class SynchronousError(pb.Error):
+ """
+ Helper used to test remote methods which raise exceptions which are
+ L{pb.Error} subclasses.
+ """
+
+
+#class JellyError(flavors.Jellyable, pb.Error): pass
+class JellyError(flavors.Jellyable, pb.Error, pb.RemoteCopy):
+ pass
+
+
+class SecurityError(pb.Error, pb.RemoteCopy):
+ pass
+
+pb.setUnjellyableForClass(JellyError, JellyError)
+pb.setUnjellyableForClass(SecurityError, SecurityError)
+pb.globalSecurity.allowInstancesOf(SecurityError)
+
+
+####
+# server-side
+####
+class SimpleRoot(pb.Root):
+ def remote_asynchronousException(self):
+ """
+ Fail asynchronously with a non-pb.Error exception.
+ """
+ return defer.fail(AsynchronousException("remote asynchronous exception"))
+
+ def remote_synchronousException(self):
+ """
+ Fail synchronously with a non-pb.Error exception.
+ """
+ raise SynchronousException("remote synchronous exception")
+
+ def remote_asynchronousError(self):
+ """
+ Fail asynchronously with a pb.Error exception.
+ """
+ return defer.fail(AsynchronousError("remote asynchronous error"))
+
+ def remote_synchronousError(self):
+ """
+ Fail synchronously with a pb.Error exception.
+ """
+ raise SynchronousError("remote synchronous error")
+
+ def remote_unknownError(self):
+ """
+ Fail with error that is not known to client.
+ """
+ class UnknownError(pb.Error):
+ pass
+ raise UnknownError("I'm not known to client!")
+
+ def remote_jelly(self):
+ self.raiseJelly()
+
+ def remote_security(self):
+ self.raiseSecurity()
+
+ def remote_deferredJelly(self):
+ d = defer.Deferred()
+ d.addCallback(self.raiseJelly)
+ d.callback(None)
+ return d
+
+ def remote_deferredSecurity(self):
+ d = defer.Deferred()
+ d.addCallback(self.raiseSecurity)
+ d.callback(None)
+ return d
+
+ def raiseJelly(self, results=None):
+ raise JellyError("I'm jellyable!")
+
+ def raiseSecurity(self, results=None):
+ raise SecurityError("I'm secure!")
+
+
+
+class SaveProtocolServerFactory(pb.PBServerFactory):
+ """
+ A L{pb.PBServerFactory} that saves the latest connected client in
+ C{protocolInstance}.
+ """
+ protocolInstance = None
+
+ def clientConnectionMade(self, protocol):
+ """
+ Keep track of the given protocol.
+ """
+ self.protocolInstance = protocol
+
+
+
+class PBConnTestCase(unittest.TestCase):
+ unsafeTracebacks = 0
+
+ def setUp(self):
+ self._setUpServer()
+ self._setUpClient()
+
+ def _setUpServer(self):
+ self.serverFactory = SaveProtocolServerFactory(SimpleRoot())
+ self.serverFactory.unsafeTracebacks = self.unsafeTracebacks
+ self.serverPort = reactor.listenTCP(0, self.serverFactory, interface="127.0.0.1")
+
+ def _setUpClient(self):
+ portNo = self.serverPort.getHost().port
+ self.clientFactory = pb.PBClientFactory()
+ self.clientConnector = reactor.connectTCP("127.0.0.1", portNo, self.clientFactory)
+
+ def tearDown(self):
+ if self.serverFactory.protocolInstance is not None:
+ self.serverFactory.protocolInstance.transport.loseConnection()
+ return defer.gatherResults([
+ self._tearDownServer(),
+ self._tearDownClient()])
+
+ def _tearDownServer(self):
+ return defer.maybeDeferred(self.serverPort.stopListening)
+
+ def _tearDownClient(self):
+ self.clientConnector.disconnect()
+ return defer.succeed(None)
+
+
+
+class PBFailureTest(PBConnTestCase):
+ compare = unittest.TestCase.assertEquals
+
+
+ def _exceptionTest(self, method, exceptionType, flush):
+ def eb(err):
+ err.trap(exceptionType)
+ self.compare(err.traceback, "Traceback unavailable\n")
+ if flush:
+ errs = self.flushLoggedErrors(exceptionType)
+ self.assertEqual(len(errs), 1)
+ return (err.type, err.value, err.traceback)
+ d = self.clientFactory.getRootObject()
+ def gotRootObject(root):
+ d = root.callRemote(method)
+ d.addErrback(eb)
+ return d
+ d.addCallback(gotRootObject)
+ return d
+
+
+ def test_asynchronousException(self):
+ """
+ Test that a Deferred returned by a remote method which already has a
+ Failure correctly has that error passed back to the calling side.
+ """
+ return self._exceptionTest(
+ 'asynchronousException', AsynchronousException, True)
+
+
+ def test_synchronousException(self):
+ """
+ Like L{test_asynchronousException}, but for a method which raises an
+ exception synchronously.
+ """
+ return self._exceptionTest(
+ 'synchronousException', SynchronousException, True)
+
+
+ def test_asynchronousError(self):
+ """
+ Like L{test_asynchronousException}, but for a method which returns a
+ Deferred failing with an L{pb.Error} subclass.
+ """
+ return self._exceptionTest(
+ 'asynchronousError', AsynchronousError, False)
+
+
+ def test_synchronousError(self):
+ """
+ Like L{test_asynchronousError}, but for a method which synchronously
+ raises a L{pb.Error} subclass.
+ """
+ return self._exceptionTest(
+ 'synchronousError', SynchronousError, False)
+
+
+ def _success(self, result, expectedResult):
+ self.assertEquals(result, expectedResult)
+ return result
+
+
+ def _addFailingCallbacks(self, remoteCall, expectedResult, eb):
+ remoteCall.addCallbacks(self._success, eb,
+ callbackArgs=(expectedResult,))
+ return remoteCall
+
+
+ def _testImpl(self, method, expected, eb, exc=None):
+ """
+ Call the given remote method and attach the given errback to the
+ resulting Deferred. If C{exc} is not None, also assert that one
+ exception of that type was logged.
+ """
+ rootDeferred = self.clientFactory.getRootObject()
+ def gotRootObj(obj):
+ failureDeferred = self._addFailingCallbacks(obj.callRemote(method), expected, eb)
+ if exc is not None:
+ def gotFailure(err):
+ self.assertEquals(len(self.flushLoggedErrors(exc)), 1)
+ return err
+ failureDeferred.addBoth(gotFailure)
+ return failureDeferred
+ rootDeferred.addCallback(gotRootObj)
+ return rootDeferred
+
+
+ def test_jellyFailure(self):
+ """
+ Test that an exception which is a subclass of L{pb.Error} has more
+ information passed across the network to the calling side.
+ """
+ def failureJelly(fail):
+ fail.trap(JellyError)
+ self.failIf(isinstance(fail.type, str))
+ self.failUnless(isinstance(fail.value, fail.type))
+ return 43
+ return self._testImpl('jelly', 43, failureJelly)
+
+
+ def test_deferredJellyFailure(self):
+ """
+ Test that a Deferred which fails with a L{pb.Error} is treated in
+ the same way as a synchronously raised L{pb.Error}.
+ """
+ def failureDeferredJelly(fail):
+ fail.trap(JellyError)
+ self.failIf(isinstance(fail.type, str))
+ self.failUnless(isinstance(fail.value, fail.type))
+ return 430
+ return self._testImpl('deferredJelly', 430, failureDeferredJelly)
+
+
+ def test_unjellyableFailure(self):
+ """
+ An non-jellyable L{pb.Error} subclass raised by a remote method is
+ turned into a Failure with a type set to the FQPN of the exception
+ type.
+ """
+ def failureUnjellyable(fail):
+ self.assertEqual(
+ fail.type, 'twisted.test.test_pbfailure.SynchronousError')
+ return 431
+ return self._testImpl('synchronousError', 431, failureUnjellyable)
+
+
+ def test_unknownFailure(self):
+ """
+ Test that an exception which is a subclass of L{pb.Error} but not
+ known on the client side has its type set properly.
+ """
+ def failureUnknown(fail):
+ self.assertEqual(
+ fail.type, 'twisted.test.test_pbfailure.UnknownError')
+ return 4310
+ return self._testImpl('unknownError', 4310, failureUnknown)
+
+
+ def test_securityFailure(self):
+ """
+ Test that even if an exception is not explicitly jellyable (by being
+ a L{pb.Jellyable} subclass), as long as it is an L{pb.Error}
+ subclass it receives the same special treatment.
+ """
+ def failureSecurity(fail):
+ fail.trap(SecurityError)
+ self.failIf(isinstance(fail.type, str))
+ self.failUnless(isinstance(fail.value, fail.type))
+ return 4300
+ return self._testImpl('security', 4300, failureSecurity)
+
+
+ def test_deferredSecurity(self):
+ """
+ Test that a Deferred which fails with a L{pb.Error} which is not
+ also a L{pb.Jellyable} is treated in the same way as a synchronously
+ raised exception of the same type.
+ """
+ def failureDeferredSecurity(fail):
+ fail.trap(SecurityError)
+ self.failIf(isinstance(fail.type, str))
+ self.failUnless(isinstance(fail.value, fail.type))
+ return 43000
+ return self._testImpl('deferredSecurity', 43000, failureDeferredSecurity)
+
+
+ def test_noSuchMethodFailure(self):
+ """
+ Test that attempting to call a method which is not defined correctly
+ results in an AttributeError on the calling side.
+ """
+ def failureNoSuch(fail):
+ fail.trap(pb.NoSuchMethod)
+ self.compare(fail.traceback, "Traceback unavailable\n")
+ return 42000
+ return self._testImpl('nosuch', 42000, failureNoSuch, AttributeError)
+
+
+ def test_copiedFailureLogging(self):
+ """
+ Test that a copied failure received from a PB call can be logged
+ locally.
+
+ Note: this test needs some serious help: all it really tests is that
+ log.err(copiedFailure) doesn't raise an exception.
+ """
+ d = self.clientFactory.getRootObject()
+
+ def connected(rootObj):
+ return rootObj.callRemote('synchronousException')
+ d.addCallback(connected)
+
+ def exception(failure):
+ log.err(failure)
+ errs = self.flushLoggedErrors(SynchronousException)
+ self.assertEquals(len(errs), 2)
+ d.addErrback(exception)
+
+ return d
+
+
+
+class PBFailureTestUnsafe(PBFailureTest):
+ compare = unittest.TestCase.failIfEquals
+ unsafeTracebacks = 1
+
+
+
+class DummyInvoker(object):
+ """
+ A behaviorless object to be used as the invoker parameter to
+ L{jelly.jelly}.
+ """
+ serializingPerspective = None
+
+
+
+class FailureJellyingTests(unittest.TestCase):
+ """
+ Tests for the interaction of jelly and failures.
+ """
+ def test_unjelliedFailureCheck(self):
+ """
+ An unjellied L{CopyableFailure} has a check method which behaves the
+ same way as the original L{CopyableFailure}'s check method.
+ """
+ original = pb.CopyableFailure(ZeroDivisionError())
+ self.assertIdentical(
+ original.check(ZeroDivisionError), ZeroDivisionError)
+ self.assertIdentical(original.check(ArithmeticError), ArithmeticError)
+ copied = jelly.unjelly(jelly.jelly(original, invoker=DummyInvoker()))
+ self.assertIdentical(
+ copied.check(ZeroDivisionError), ZeroDivisionError)
+ self.assertIdentical(copied.check(ArithmeticError), ArithmeticError)
+
+
+ def test_twiceUnjelliedFailureCheck(self):
+ """
+ The object which results from jellying a L{CopyableFailure}, unjellying
+ the result, creating a new L{CopyableFailure} from the result of that,
+ jellying it, and finally unjellying the result of that has a check
+ method which behaves the same way as the original L{CopyableFailure}'s
+ check method.
+ """
+ original = pb.CopyableFailure(ZeroDivisionError())
+ self.assertIdentical(
+ original.check(ZeroDivisionError), ZeroDivisionError)
+ self.assertIdentical(original.check(ArithmeticError), ArithmeticError)
+ copiedOnce = jelly.unjelly(
+ jelly.jelly(original, invoker=DummyInvoker()))
+ derivative = pb.CopyableFailure(copiedOnce)
+ copiedTwice = jelly.unjelly(
+ jelly.jelly(derivative, invoker=DummyInvoker()))
+ self.assertIdentical(
+ copiedTwice.check(ZeroDivisionError), ZeroDivisionError)
+ self.assertIdentical(
+ copiedTwice.check(ArithmeticError), ArithmeticError)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_pcp.py b/vendor/Twisted-10.0.0/twisted/test/test_pcp.py
new file mode 100644
index 0000000000..806d45bd9f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_pcp.py
@@ -0,0 +1,368 @@
+# -*- Python -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+__version__ = '$Revision: 1.5 $'[11:-2]
+
+from StringIO import StringIO
+from twisted.trial import unittest
+from twisted.protocols import pcp
+
+# Goal:
+
+# Take a Protocol instance. Own all outgoing data - anything that
+# would go to p.transport.write. Own all incoming data - anything
+# that comes to p.dataReceived.
+
+# I need:
+# Something with the AbstractFileDescriptor interface.
+# That is:
+# - acts as a Transport
+# - has a method write()
+# - which buffers
+# - acts as a Consumer
+# - has a registerProducer, unRegisterProducer
+# - tells the Producer to back off (pauseProducing) when its buffer is full.
+# - tells the Producer to resumeProducing when its buffer is not so full.
+# - acts as a Producer
+# - calls registerProducer
+# - calls write() on consumers
+# - honors requests to pause/resume producing
+# - honors stopProducing, and passes it along to upstream Producers
+
+
+class DummyTransport:
+ """A dumb transport to wrap around."""
+
+ def __init__(self):
+ self._writes = []
+
+ def write(self, data):
+ self._writes.append(data)
+
+ def getvalue(self):
+ return ''.join(self._writes)
+
+class DummyProducer:
+ resumed = False
+ stopped = False
+ paused = False
+
+ def __init__(self, consumer):
+ self.consumer = consumer
+
+ def resumeProducing(self):
+ self.resumed = True
+ self.paused = False
+
+ def pauseProducing(self):
+ self.paused = True
+
+ def stopProducing(self):
+ self.stopped = True
+
+
+class DummyConsumer(DummyTransport):
+ producer = None
+ finished = False
+ unregistered = True
+
+ def registerProducer(self, producer, streaming):
+ self.producer = (producer, streaming)
+
+ def unregisterProducer(self):
+ self.unregistered = True
+
+ def finish(self):
+ self.finished = True
+
+class TransportInterfaceTest(unittest.TestCase):
+ proxyClass = pcp.BasicProducerConsumerProxy
+
+ def setUp(self):
+ self.underlying = DummyConsumer()
+ self.transport = self.proxyClass(self.underlying)
+
+ def testWrite(self):
+ self.transport.write("some bytes")
+
+class ConsumerInterfaceTest:
+ """Test ProducerConsumerProxy as a Consumer.
+
+ Normally we have ProducingServer -> ConsumingTransport.
+
+ If I am to go between (Server -> Shaper -> Transport), I have to
+ play the role of Consumer convincingly for the ProducingServer.
+ """
+
+ def setUp(self):
+ self.underlying = DummyConsumer()
+ self.consumer = self.proxyClass(self.underlying)
+ self.producer = DummyProducer(self.consumer)
+
+ def testRegisterPush(self):
+ self.consumer.registerProducer(self.producer, True)
+ ## Consumer should NOT have called PushProducer.resumeProducing
+ self.failIf(self.producer.resumed)
+
+ ## I'm I'm just a proxy, should I only do resumeProducing when
+ ## I get poked myself?
+ #def testRegisterPull(self):
+ # self.consumer.registerProducer(self.producer, False)
+ # ## Consumer SHOULD have called PushProducer.resumeProducing
+ # self.failUnless(self.producer.resumed)
+
+ def testUnregister(self):
+ self.consumer.registerProducer(self.producer, False)
+ self.consumer.unregisterProducer()
+ # Now when the consumer would ordinarily want more data, it
+ # shouldn't ask producer for it.
+ # The most succinct way to trigger "want more data" is to proxy for
+ # a PullProducer and have someone ask me for data.
+ self.producer.resumed = False
+ self.consumer.resumeProducing()
+ self.failIf(self.producer.resumed)
+
+ def testFinish(self):
+ self.consumer.registerProducer(self.producer, False)
+ self.consumer.finish()
+ # I guess finish should behave like unregister?
+ self.producer.resumed = False
+ self.consumer.resumeProducing()
+ self.failIf(self.producer.resumed)
+
+
+class ProducerInterfaceTest:
+ """Test ProducerConsumerProxy as a Producer.
+
+ Normally we have ProducingServer -> ConsumingTransport.
+
+ If I am to go between (Server -> Shaper -> Transport), I have to
+ play the role of Producer convincingly for the ConsumingTransport.
+ """
+
+ def setUp(self):
+ self.consumer = DummyConsumer()
+ self.producer = self.proxyClass(self.consumer)
+
+ def testRegistersProducer(self):
+ self.failUnlessEqual(self.consumer.producer[0], self.producer)
+
+ def testPause(self):
+ self.producer.pauseProducing()
+ self.producer.write("yakkity yak")
+ self.failIf(self.consumer.getvalue(),
+ "Paused producer should not have sent data.")
+
+ def testResume(self):
+ self.producer.pauseProducing()
+ self.producer.resumeProducing()
+ self.producer.write("yakkity yak")
+ self.failUnlessEqual(self.consumer.getvalue(), "yakkity yak")
+
+ def testResumeNoEmptyWrite(self):
+ self.producer.pauseProducing()
+ self.producer.resumeProducing()
+ self.failUnlessEqual(len(self.consumer._writes), 0,
+ "Resume triggered an empty write.")
+
+ def testResumeBuffer(self):
+ self.producer.pauseProducing()
+ self.producer.write("buffer this")
+ self.producer.resumeProducing()
+ self.failUnlessEqual(self.consumer.getvalue(), "buffer this")
+
+ def testStop(self):
+ self.producer.stopProducing()
+ self.producer.write("yakkity yak")
+ self.failIf(self.consumer.getvalue(),
+ "Stopped producer should not have sent data.")
+
+
+class PCP_ConsumerInterfaceTest(ConsumerInterfaceTest, unittest.TestCase):
+ proxyClass = pcp.BasicProducerConsumerProxy
+
+class PCPII_ConsumerInterfaceTest(ConsumerInterfaceTest, unittest.TestCase):
+ proxyClass = pcp.ProducerConsumerProxy
+
+class PCP_ProducerInterfaceTest(ProducerInterfaceTest, unittest.TestCase):
+ proxyClass = pcp.BasicProducerConsumerProxy
+
+class PCPII_ProducerInterfaceTest(ProducerInterfaceTest, unittest.TestCase):
+ proxyClass = pcp.ProducerConsumerProxy
+
+class ProducerProxyTest(unittest.TestCase):
+ """Producer methods on me should be relayed to the Producer I proxy.
+ """
+ proxyClass = pcp.BasicProducerConsumerProxy
+
+ def setUp(self):
+ self.proxy = self.proxyClass(None)
+ self.parentProducer = DummyProducer(self.proxy)
+ self.proxy.registerProducer(self.parentProducer, True)
+
+ def testStop(self):
+ self.proxy.stopProducing()
+ self.failUnless(self.parentProducer.stopped)
+
+
+class ConsumerProxyTest(unittest.TestCase):
+ """Consumer methods on me should be relayed to the Consumer I proxy.
+ """
+ proxyClass = pcp.BasicProducerConsumerProxy
+
+ def setUp(self):
+ self.underlying = DummyConsumer()
+ self.consumer = self.proxyClass(self.underlying)
+
+ def testWrite(self):
+ # NOTE: This test only valid for streaming (Push) systems.
+ self.consumer.write("some bytes")
+ self.failUnlessEqual(self.underlying.getvalue(), "some bytes")
+
+ def testFinish(self):
+ self.consumer.finish()
+ self.failUnless(self.underlying.finished)
+
+ def testUnregister(self):
+ self.consumer.unregisterProducer()
+ self.failUnless(self.underlying.unregistered)
+
+
+class PullProducerTest:
+ def setUp(self):
+ self.underlying = DummyConsumer()
+ self.proxy = self.proxyClass(self.underlying)
+ self.parentProducer = DummyProducer(self.proxy)
+ self.proxy.registerProducer(self.parentProducer, True)
+
+ def testHoldWrites(self):
+ self.proxy.write("hello")
+ # Consumer should get no data before it says resumeProducing.
+ self.failIf(self.underlying.getvalue(),
+ "Pulling Consumer got data before it pulled.")
+
+ def testPull(self):
+ self.proxy.write("hello")
+ self.proxy.resumeProducing()
+ self.failUnlessEqual(self.underlying.getvalue(), "hello")
+
+ def testMergeWrites(self):
+ self.proxy.write("hello ")
+ self.proxy.write("sunshine")
+ self.proxy.resumeProducing()
+ nwrites = len(self.underlying._writes)
+ self.failUnlessEqual(nwrites, 1, "Pull resulted in %d writes instead "
+ "of 1." % (nwrites,))
+ self.failUnlessEqual(self.underlying.getvalue(), "hello sunshine")
+
+
+ def testLateWrite(self):
+ # consumer sends its initial pull before we have data
+ self.proxy.resumeProducing()
+ self.proxy.write("data")
+ # This data should answer that pull request.
+ self.failUnlessEqual(self.underlying.getvalue(), "data")
+
+class PCP_PullProducerTest(PullProducerTest, unittest.TestCase):
+ class proxyClass(pcp.BasicProducerConsumerProxy):
+ iAmStreaming = False
+
+class PCPII_PullProducerTest(PullProducerTest, unittest.TestCase):
+ class proxyClass(pcp.ProducerConsumerProxy):
+ iAmStreaming = False
+
+# Buffering!
+
+class BufferedConsumerTest(unittest.TestCase):
+ """As a consumer, ask the producer to pause after too much data."""
+
+ proxyClass = pcp.ProducerConsumerProxy
+
+ def setUp(self):
+ self.underlying = DummyConsumer()
+ self.proxy = self.proxyClass(self.underlying)
+ self.proxy.bufferSize = 100
+
+ self.parentProducer = DummyProducer(self.proxy)
+ self.proxy.registerProducer(self.parentProducer, True)
+
+ def testRegisterPull(self):
+ self.proxy.registerProducer(self.parentProducer, False)
+ ## Consumer SHOULD have called PushProducer.resumeProducing
+ self.failUnless(self.parentProducer.resumed)
+
+ def testPauseIntercept(self):
+ self.proxy.pauseProducing()
+ self.failIf(self.parentProducer.paused)
+
+ def testResumeIntercept(self):
+ self.proxy.pauseProducing()
+ self.proxy.resumeProducing()
+ # With a streaming producer, just because the proxy was resumed is
+ # not necessarily a reason to resume the parent producer. The state
+ # of the buffer should decide that.
+ self.failIf(self.parentProducer.resumed)
+
+ def testTriggerPause(self):
+ """Make sure I say \"when.\""""
+
+ # Pause the proxy so data sent to it builds up in its buffer.
+ self.proxy.pauseProducing()
+ self.failIf(self.parentProducer.paused, "don't pause yet")
+ self.proxy.write("x" * 51)
+ self.failIf(self.parentProducer.paused, "don't pause yet")
+ self.proxy.write("x" * 51)
+ self.failUnless(self.parentProducer.paused)
+
+ def testTriggerResume(self):
+ """Make sure I resumeProducing when my buffer empties."""
+ self.proxy.pauseProducing()
+ self.proxy.write("x" * 102)
+ self.failUnless(self.parentProducer.paused, "should be paused")
+ self.proxy.resumeProducing()
+ # Resuming should have emptied my buffer, so I should tell my
+ # parent to resume too.
+ self.failIf(self.parentProducer.paused,
+ "Producer should have resumed.")
+ self.failIf(self.proxy.producerPaused)
+
+class BufferedPullTests(unittest.TestCase):
+ class proxyClass(pcp.ProducerConsumerProxy):
+ iAmStreaming = False
+
+ def _writeSomeData(self, data):
+ pcp.ProducerConsumerProxy._writeSomeData(self, data[:100])
+ return min(len(data), 100)
+
+ def setUp(self):
+ self.underlying = DummyConsumer()
+ self.proxy = self.proxyClass(self.underlying)
+ self.proxy.bufferSize = 100
+
+ self.parentProducer = DummyProducer(self.proxy)
+ self.proxy.registerProducer(self.parentProducer, False)
+
+ def testResumePull(self):
+ # If proxy has no data to send on resumeProducing, it had better pull
+ # some from its PullProducer.
+ self.parentProducer.resumed = False
+ self.proxy.resumeProducing()
+ self.failUnless(self.parentProducer.resumed)
+
+ def testLateWriteBuffering(self):
+ # consumer sends its initial pull before we have data
+ self.proxy.resumeProducing()
+ self.proxy.write("datum" * 21)
+ # This data should answer that pull request.
+ self.failUnlessEqual(self.underlying.getvalue(), "datum" * 20)
+ # but there should be some left over
+ self.failUnlessEqual(self.proxy._buffer, ["datum"])
+
+
+# TODO:
+# test that web request finishing bug (when we weren't proxying
+# unregisterProducer but were proxying finish, web file transfers
+# would hang on the last block.)
+# test what happens if writeSomeBytes decided to write zero bytes.
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_persisted.py b/vendor/Twisted-10.0.0/twisted/test/test_persisted.py
new file mode 100644
index 0000000000..ea28d70763
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_persisted.py
@@ -0,0 +1,314 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+# System Imports
+import sys
+
+from twisted.trial import unittest
+
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+# Twisted Imports
+from twisted.persisted import styles, aot, crefutil
+
+
+class VersionTestCase(unittest.TestCase):
+ def testNullVersionUpgrade(self):
+ global NullVersioned
+ class NullVersioned:
+ ok = 0
+ pkcl = pickle.dumps(NullVersioned())
+ class NullVersioned(styles.Versioned):
+ persistenceVersion = 1
+ def upgradeToVersion1(self):
+ self.ok = 1
+ mnv = pickle.loads(pkcl)
+ styles.doUpgrade()
+ assert mnv.ok, "initial upgrade not run!"
+
+ def testVersionUpgrade(self):
+ global MyVersioned
+ class MyVersioned(styles.Versioned):
+ persistenceVersion = 2
+ persistenceForgets = ['garbagedata']
+ v3 = 0
+ v4 = 0
+
+ def __init__(self):
+ self.somedata = 'xxx'
+ self.garbagedata = lambda q: 'cant persist'
+
+ def upgradeToVersion3(self):
+ self.v3 += 1
+
+ def upgradeToVersion4(self):
+ self.v4 += 1
+ mv = MyVersioned()
+ assert not (mv.v3 or mv.v4), "hasn't been upgraded yet"
+ pickl = pickle.dumps(mv)
+ MyVersioned.persistenceVersion = 4
+ obj = pickle.loads(pickl)
+ styles.doUpgrade()
+ assert obj.v3, "didn't do version 3 upgrade"
+ assert obj.v4, "didn't do version 4 upgrade"
+ pickl = pickle.dumps(obj)
+ obj = pickle.loads(pickl)
+ styles.doUpgrade()
+ assert obj.v3 == 1, "upgraded unnecessarily"
+ assert obj.v4 == 1, "upgraded unnecessarily"
+
+ def testNonIdentityHash(self):
+ global ClassWithCustomHash
+ class ClassWithCustomHash(styles.Versioned):
+ def __init__(self, unique, hash):
+ self.unique = unique
+ self.hash = hash
+ def __hash__(self):
+ return self.hash
+
+ v1 = ClassWithCustomHash('v1', 0)
+ v2 = ClassWithCustomHash('v2', 0)
+
+ pkl = pickle.dumps((v1, v2))
+ del v1, v2
+ ClassWithCustomHash.persistenceVersion = 1
+ ClassWithCustomHash.upgradeToVersion1 = lambda self: setattr(self, 'upgraded', True)
+ v1, v2 = pickle.loads(pkl)
+ styles.doUpgrade()
+ self.assertEquals(v1.unique, 'v1')
+ self.assertEquals(v2.unique, 'v2')
+ self.failUnless(v1.upgraded)
+ self.failUnless(v2.upgraded)
+
+ def testUpgradeDeserializesObjectsRequiringUpgrade(self):
+ global ToyClassA, ToyClassB
+ class ToyClassA(styles.Versioned):
+ pass
+ class ToyClassB(styles.Versioned):
+ pass
+ x = ToyClassA()
+ y = ToyClassB()
+ pklA, pklB = pickle.dumps(x), pickle.dumps(y)
+ del x, y
+ ToyClassA.persistenceVersion = 1
+ def upgradeToVersion1(self):
+ self.y = pickle.loads(pklB)
+ styles.doUpgrade()
+ ToyClassA.upgradeToVersion1 = upgradeToVersion1
+ ToyClassB.persistenceVersion = 1
+ ToyClassB.upgradeToVersion1 = lambda self: setattr(self, 'upgraded', True)
+
+ x = pickle.loads(pklA)
+ styles.doUpgrade()
+ self.failUnless(x.y.upgraded)
+
+class MyEphemeral(styles.Ephemeral):
+
+ def __init__(self, x):
+ self.x = x
+
+
+class EphemeralTestCase(unittest.TestCase):
+
+ def testEphemeral(self):
+ o = MyEphemeral(3)
+ self.assertEquals(o.__class__, MyEphemeral)
+ self.assertEquals(o.x, 3)
+
+ pickl = pickle.dumps(o)
+ o = pickle.loads(pickl)
+
+ self.assertEquals(o.__class__, styles.Ephemeral)
+ self.assert_(not hasattr(o, 'x'))
+
+
+class Pickleable:
+
+ def __init__(self, x):
+ self.x = x
+
+ def getX(self):
+ return self.x
+
+class A:
+ """
+ dummy class
+ """
+ def amethod(self):
+ pass
+
+class B:
+ """
+ dummy class
+ """
+ def bmethod(self):
+ pass
+
+def funktion():
+ pass
+
+class PicklingTestCase(unittest.TestCase):
+ """Test pickling of extra object types."""
+
+ def testModule(self):
+ pickl = pickle.dumps(styles)
+ o = pickle.loads(pickl)
+ self.assertEquals(o, styles)
+
+ def testClassMethod(self):
+ pickl = pickle.dumps(Pickleable.getX)
+ o = pickle.loads(pickl)
+ self.assertEquals(o, Pickleable.getX)
+
+ def testInstanceMethod(self):
+ obj = Pickleable(4)
+ pickl = pickle.dumps(obj.getX)
+ o = pickle.loads(pickl)
+ self.assertEquals(o(), 4)
+ self.assertEquals(type(o), type(obj.getX))
+
+ def testStringIO(self):
+ f = StringIO.StringIO()
+ f.write("abc")
+ pickl = pickle.dumps(f)
+ o = pickle.loads(pickl)
+ self.assertEquals(type(o), type(f))
+ self.assertEquals(f.getvalue(), "abc")
+
+
+class EvilSourceror:
+ def __init__(self, x):
+ self.a = self
+ self.a.b = self
+ self.a.b.c = x
+
+class NonDictState:
+ def __getstate__(self):
+ return self.state
+ def __setstate__(self, state):
+ self.state = state
+
+class AOTTestCase(unittest.TestCase):
+ def testSimpleTypes(self):
+ obj = (1, 2.0, 3j, True, slice(1, 2, 3), 'hello', u'world', sys.maxint + 1, None, Ellipsis)
+ rtObj = aot.unjellyFromSource(aot.jellyToSource(obj))
+ self.assertEquals(obj, rtObj)
+
+ def testMethodSelfIdentity(self):
+ a = A()
+ b = B()
+ a.bmethod = b.bmethod
+ b.a = a
+ im_ = aot.unjellyFromSource(aot.jellyToSource(b)).a.bmethod
+ self.assertEquals(im_.im_class, im_.im_self.__class__)
+
+
+ def test_methodNotSelfIdentity(self):
+ """
+ If a class change after an instance has been created,
+ L{aot.unjellyFromSource} shoud raise a C{TypeError} when trying to
+ unjelly the instance.
+ """
+ a = A()
+ b = B()
+ a.bmethod = b.bmethod
+ b.a = a
+ savedbmethod = B.bmethod
+ del B.bmethod
+ try:
+ self.assertRaises(TypeError, aot.unjellyFromSource,
+ aot.jellyToSource(b))
+ finally:
+ B.bmethod = savedbmethod
+
+
+ def test_unsupportedType(self):
+ """
+ L{aot.jellyToSource} should raise a C{TypeError} when trying to jelly
+ an unknown type.
+ """
+ try:
+ set
+ except:
+ from sets import Set as set
+ self.assertRaises(TypeError, aot.jellyToSource, set())
+
+
+ def testBasicIdentity(self):
+ # Anyone wanting to make this datastructure more complex, and thus this
+ # test more comprehensive, is welcome to do so.
+ aj = aot.AOTJellier().jellyToAO
+ d = {'hello': 'world', "method": aj}
+ l = [1, 2, 3,
+ "he\tllo\n\n\"x world!",
+ u"goodbye \n\t\u1010 world!",
+ 1, 1.0, 100 ** 100l, unittest, aot.AOTJellier, d,
+ funktion
+ ]
+ t = tuple(l)
+ l.append(l)
+ l.append(t)
+ l.append(t)
+ uj = aot.unjellyFromSource(aot.jellyToSource([l, l]))
+ assert uj[0] is uj[1]
+ assert uj[1][0:5] == l[0:5]
+
+
+ def testNonDictState(self):
+ a = NonDictState()
+ a.state = "meringue!"
+ assert aot.unjellyFromSource(aot.jellyToSource(a)).state == a.state
+
+ def testCopyReg(self):
+ s = "foo_bar"
+ sio = StringIO.StringIO()
+ sio.write(s)
+ uj = aot.unjellyFromSource(aot.jellyToSource(sio))
+ # print repr(uj.__dict__)
+ assert uj.getvalue() == s
+
+ def testFunkyReferences(self):
+ o = EvilSourceror(EvilSourceror([]))
+ j1 = aot.jellyToAOT(o)
+ oj = aot.unjellyFromAOT(j1)
+
+ assert oj.a is oj
+ assert oj.a.b is oj.b
+ assert oj.c is not oj.c.c
+
+
+class CrefUtilTestCase(unittest.TestCase):
+ """
+ Tests for L{crefutil}.
+ """
+
+ def test_dictUnknownKey(self):
+ """
+ L{crefutil._DictKeyAndValue} only support keys C{0} and C{1}.
+ """
+ d = crefutil._DictKeyAndValue({})
+ self.assertRaises(RuntimeError, d.__setitem__, 2, 3)
+
+
+ def test_deferSetMultipleTimes(self):
+ """
+ L{crefutil._Defer} can be assigned a key only one time.
+ """
+ d = crefutil._Defer()
+ d[0] = 1
+ self.assertRaises(RuntimeError, d.__setitem__, 0, 1)
+
+
+
+testCases = [VersionTestCase, EphemeralTestCase, PicklingTestCase]
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_plugin.py b/vendor/Twisted-10.0.0/twisted/test/test_plugin.py
new file mode 100644
index 0000000000..48bb142bbf
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_plugin.py
@@ -0,0 +1,694 @@
+# Copyright (c) 2005 Divmod, Inc.
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for Twisted plugin system.
+"""
+
+import sys, errno, os, time
+import compileall
+
+from zope.interface import Interface
+
+from twisted.trial import unittest
+from twisted.python.filepath import FilePath
+from twisted.python.util import mergeFunctionMetadata
+
+from twisted import plugin
+
+
+
+class ITestPlugin(Interface):
+ """
+ A plugin for use by the plugin system's unit tests.
+
+ Do not use this.
+ """
+
+
+
+class ITestPlugin2(Interface):
+ """
+ See L{ITestPlugin}.
+ """
+
+
+
+class PluginTestCase(unittest.TestCase):
+ """
+ Tests which verify the behavior of the current, active Twisted plugins
+ directory.
+ """
+
+ def setUp(self):
+ """
+ Save C{sys.path} and C{sys.modules}, and create a package for tests.
+ """
+ self.originalPath = sys.path[:]
+ self.savedModules = sys.modules.copy()
+
+ self.root = FilePath(self.mktemp())
+ self.root.createDirectory()
+ self.package = self.root.child('mypackage')
+ self.package.createDirectory()
+ self.package.child('__init__.py').setContent("")
+
+ FilePath(__file__).sibling('plugin_basic.py'
+ ).copyTo(self.package.child('testplugin.py'))
+
+ self.originalPlugin = "testplugin"
+
+ sys.path.insert(0, self.root.path)
+ import mypackage
+ self.module = mypackage
+
+
+ def tearDown(self):
+ """
+ Restore C{sys.path} and C{sys.modules} to their original values.
+ """
+ sys.path[:] = self.originalPath
+ sys.modules.clear()
+ sys.modules.update(self.savedModules)
+
+
+ def _unimportPythonModule(self, module, deleteSource=False):
+ modulePath = module.__name__.split('.')
+ packageName = '.'.join(modulePath[:-1])
+ moduleName = modulePath[-1]
+
+ delattr(sys.modules[packageName], moduleName)
+ del sys.modules[module.__name__]
+ for ext in ['c', 'o'] + (deleteSource and [''] or []):
+ try:
+ os.remove(module.__file__ + ext)
+ except OSError, ose:
+ if ose.errno != errno.ENOENT:
+ raise
+
+
+ def _clearCache(self):
+ """
+ Remove the plugins B{droping.cache} file.
+ """
+ self.package.child('dropin.cache').remove()
+
+
+ def _withCacheness(meth):
+ """
+ This is a paranoid test wrapper, that calls C{meth} 2 times, clear the
+ cache, and calls it 2 other times. It's supposed to ensure that the
+ plugin system behaves correctly no matter what the state of the cache
+ is.
+ """
+ def wrapped(self):
+ meth(self)
+ meth(self)
+ self._clearCache()
+ meth(self)
+ meth(self)
+ return mergeFunctionMetadata(meth, wrapped)
+
+
+ def test_cache(self):
+ """
+ Check that the cache returned by L{plugin.getCache} hold the plugin
+ B{testplugin}, and that this plugin has the properties we expect:
+ provide L{TestPlugin}, has the good name and description, and can be
+ loaded successfully.
+ """
+ cache = plugin.getCache(self.module)
+
+ dropin = cache[self.originalPlugin]
+ self.assertEquals(dropin.moduleName,
+ 'mypackage.%s' % (self.originalPlugin,))
+ self.assertIn("I'm a test drop-in.", dropin.description)
+
+ # Note, not the preferred way to get a plugin by its interface.
+ p1 = [p for p in dropin.plugins if ITestPlugin in p.provided][0]
+ self.assertIdentical(p1.dropin, dropin)
+ self.assertEquals(p1.name, "TestPlugin")
+
+ # Check the content of the description comes from the plugin module
+ # docstring
+ self.assertEquals(
+ p1.description.strip(),
+ "A plugin used solely for testing purposes.")
+ self.assertEquals(p1.provided, [ITestPlugin, plugin.IPlugin])
+ realPlugin = p1.load()
+ # The plugin should match the class present in sys.modules
+ self.assertIdentical(
+ realPlugin,
+ sys.modules['mypackage.%s' % (self.originalPlugin,)].TestPlugin)
+
+ # And it should also match if we import it classicly
+ import mypackage.testplugin as tp
+ self.assertIdentical(realPlugin, tp.TestPlugin)
+
+ test_cache = _withCacheness(test_cache)
+
+
+ def test_plugins(self):
+ """
+ L{plugin.getPlugins} should return the list of plugins matching the
+ specified interface (here, L{ITestPlugin2}), and these plugins
+ should be instances of classes with a C{test} method, to be sure
+ L{plugin.getPlugins} load classes correctly.
+ """
+ plugins = list(plugin.getPlugins(ITestPlugin2, self.module))
+
+ self.assertEquals(len(plugins), 2)
+
+ names = ['AnotherTestPlugin', 'ThirdTestPlugin']
+ for p in plugins:
+ names.remove(p.__name__)
+ p.test()
+
+ test_plugins = _withCacheness(test_plugins)
+
+
+ def test_detectNewFiles(self):
+ """
+ Check that L{plugin.getPlugins} is able to detect plugins added at
+ runtime.
+ """
+ FilePath(__file__).sibling('plugin_extra1.py'
+ ).copyTo(self.package.child('pluginextra.py'))
+ try:
+ # Check that the current situation is clean
+ self.failIfIn('mypackage.pluginextra', sys.modules)
+ self.failIf(hasattr(sys.modules['mypackage'], 'pluginextra'),
+ "mypackage still has pluginextra module")
+
+ plgs = list(plugin.getPlugins(ITestPlugin, self.module))
+
+ # We should find 2 plugins: the one in testplugin, and the one in
+ # pluginextra
+ self.assertEquals(len(plgs), 2)
+
+ names = ['TestPlugin', 'FourthTestPlugin']
+ for p in plgs:
+ names.remove(p.__name__)
+ p.test1()
+ finally:
+ self._unimportPythonModule(
+ sys.modules['mypackage.pluginextra'],
+ True)
+
+ test_detectNewFiles = _withCacheness(test_detectNewFiles)
+
+
+ def test_detectFilesChanged(self):
+ """
+ Check that if the content of a plugin change, L{plugin.getPlugins} is
+ able to detect the new plugins added.
+ """
+ FilePath(__file__).sibling('plugin_extra1.py'
+ ).copyTo(self.package.child('pluginextra.py'))
+ try:
+ plgs = list(plugin.getPlugins(ITestPlugin, self.module))
+ # Sanity check
+ self.assertEquals(len(plgs), 2)
+
+ FilePath(__file__).sibling('plugin_extra2.py'
+ ).copyTo(self.package.child('pluginextra.py'))
+
+ # Fake out Python.
+ self._unimportPythonModule(sys.modules['mypackage.pluginextra'])
+
+ # Make sure additions are noticed
+ plgs = list(plugin.getPlugins(ITestPlugin, self.module))
+
+ self.assertEquals(len(plgs), 3)
+
+ names = ['TestPlugin', 'FourthTestPlugin', 'FifthTestPlugin']
+ for p in plgs:
+ names.remove(p.__name__)
+ p.test1()
+ finally:
+ self._unimportPythonModule(
+ sys.modules['mypackage.pluginextra'],
+ True)
+
+ test_detectFilesChanged = _withCacheness(test_detectFilesChanged)
+
+
+ def test_detectFilesRemoved(self):
+ """
+ Check that when a dropin file is removed, L{plugin.getPlugins} doesn't
+ return it anymore.
+ """
+ FilePath(__file__).sibling('plugin_extra1.py'
+ ).copyTo(self.package.child('pluginextra.py'))
+ try:
+ # Generate a cache with pluginextra in it.
+ list(plugin.getPlugins(ITestPlugin, self.module))
+
+ finally:
+ self._unimportPythonModule(
+ sys.modules['mypackage.pluginextra'],
+ True)
+ plgs = list(plugin.getPlugins(ITestPlugin, self.module))
+ self.assertEquals(1, len(plgs))
+
+ test_detectFilesRemoved = _withCacheness(test_detectFilesRemoved)
+
+
+ def test_nonexistentPathEntry(self):
+ """
+ Test that getCache skips over any entries in a plugin package's
+ C{__path__} which do not exist.
+ """
+ path = self.mktemp()
+ self.failIf(os.path.exists(path))
+ # Add the test directory to the plugins path
+ self.module.__path__.append(path)
+ try:
+ plgs = list(plugin.getPlugins(ITestPlugin, self.module))
+ self.assertEqual(len(plgs), 1)
+ finally:
+ self.module.__path__.remove(path)
+
+ test_nonexistentPathEntry = _withCacheness(test_nonexistentPathEntry)
+
+
+ def test_nonDirectoryChildEntry(self):
+ """
+ Test that getCache skips over any entries in a plugin package's
+ C{__path__} which refer to children of paths which are not directories.
+ """
+ path = FilePath(self.mktemp())
+ self.failIf(path.exists())
+ path.touch()
+ child = path.child("test_package").path
+ self.module.__path__.append(child)
+ try:
+ plgs = list(plugin.getPlugins(ITestPlugin, self.module))
+ self.assertEqual(len(plgs), 1)
+ finally:
+ self.module.__path__.remove(child)
+
+ test_nonDirectoryChildEntry = _withCacheness(test_nonDirectoryChildEntry)
+
+
+ def test_deployedMode(self):
+ """
+ The C{dropin.cache} file may not be writable: the cache should still be
+ attainable, but an error should be logged to show that the cache
+ couldn't be updated.
+ """
+ # Generate the cache
+ plugin.getCache(self.module)
+
+ # Add a new plugin
+ FilePath(__file__).sibling('plugin_extra1.py'
+ ).copyTo(self.package.child('pluginextra.py'))
+
+ os.chmod(self.package.path, 0500)
+ # Change the right of dropin.cache too for windows
+ os.chmod(self.package.child('dropin.cache').path, 0400)
+ self.addCleanup(os.chmod, self.package.path, 0700)
+ self.addCleanup(os.chmod,
+ self.package.child('dropin.cache').path, 0700)
+
+ cache = plugin.getCache(self.module)
+ # The new plugin should be reported
+ self.assertIn('pluginextra', cache)
+ self.assertIn(self.originalPlugin, cache)
+
+ errors = self.flushLoggedErrors()
+ self.assertEquals(len(errors), 1)
+ # Windows report OSError, others IOError
+ errors[0].trap(OSError, IOError)
+
+
+
+# This is something like the Twisted plugins file.
+pluginInitFile = """
+from twisted.plugin import pluginPackagePaths
+__path__.extend(pluginPackagePaths(__name__))
+__all__ = []
+"""
+
+def pluginFileContents(name):
+ return (
+ "from zope.interface import classProvides\n"
+ "from twisted.plugin import IPlugin\n"
+ "from twisted.test.test_plugin import ITestPlugin\n"
+ "\n"
+ "class %s(object):\n"
+ " classProvides(IPlugin, ITestPlugin)\n") % (name,)
+
+
+def _createPluginDummy(entrypath, pluginContent, real, pluginModule):
+ """
+ Create a plugindummy package.
+ """
+ entrypath.createDirectory()
+ pkg = entrypath.child('plugindummy')
+ pkg.createDirectory()
+ if real:
+ pkg.child('__init__.py').setContent('')
+ plugs = pkg.child('plugins')
+ plugs.createDirectory()
+ if real:
+ plugs.child('__init__.py').setContent(pluginInitFile)
+ plugs.child(pluginModule + '.py').setContent(pluginContent)
+ return plugs
+
+
+
+class DeveloperSetupTests(unittest.TestCase):
+ """
+ These tests verify things about the plugin system without actually
+ interacting with the deployed 'twisted.plugins' package, instead creating a
+ temporary package.
+ """
+
+ def setUp(self):
+ """
+ Create a complex environment with multiple entries on sys.path, akin to
+ a developer's environment who has a development (trunk) checkout of
+ Twisted, a system installed version of Twisted (for their operating
+ system's tools) and a project which provides Twisted plugins.
+ """
+ self.savedPath = sys.path[:]
+ self.savedModules = sys.modules.copy()
+ self.fakeRoot = FilePath(self.mktemp())
+ self.fakeRoot.createDirectory()
+ self.systemPath = self.fakeRoot.child('system_path')
+ self.devPath = self.fakeRoot.child('development_path')
+ self.appPath = self.fakeRoot.child('application_path')
+ self.systemPackage = _createPluginDummy(
+ self.systemPath, pluginFileContents('system'),
+ True, 'plugindummy_builtin')
+ self.devPackage = _createPluginDummy(
+ self.devPath, pluginFileContents('dev'),
+ True, 'plugindummy_builtin')
+ self.appPackage = _createPluginDummy(
+ self.appPath, pluginFileContents('app'),
+ False, 'plugindummy_app')
+
+ # Now we're going to do the system installation.
+ sys.path.extend([x.path for x in [self.systemPath,
+ self.appPath]])
+ # Run all the way through the plugins list to cause the
+ # L{plugin.getPlugins} generator to write cache files for the system
+ # installation.
+ self.getAllPlugins()
+ self.sysplug = self.systemPath.child('plugindummy').child('plugins')
+ self.syscache = self.sysplug.child('dropin.cache')
+ # Make sure there's a nice big difference in modification times so that
+ # we won't re-build the system cache.
+ now = time.time()
+ os.utime(
+ self.sysplug.child('plugindummy_builtin.py').path,
+ (now - 5000,) * 2)
+ os.utime(self.syscache.path, (now - 2000,) * 2)
+ # For extra realism, let's make sure that the system path is no longer
+ # writable.
+ self.lockSystem()
+ self.resetEnvironment()
+
+
+ def lockSystem(self):
+ """
+ Lock the system directories, as if they were unwritable by this user.
+ """
+ os.chmod(self.sysplug.path, 0555)
+ os.chmod(self.syscache.path, 0555)
+
+
+ def unlockSystem(self):
+ """
+ Unlock the system directories, as if they were writable by this user.
+ """
+ os.chmod(self.sysplug.path, 0777)
+ os.chmod(self.syscache.path, 0777)
+
+
+ def getAllPlugins(self):
+ """
+ Get all the plugins loadable from our dummy package, and return their
+ short names.
+ """
+ # Import the module we just added to our path. (Local scope because
+ # this package doesn't exist outside of this test.)
+ import plugindummy.plugins
+ x = list(plugin.getPlugins(ITestPlugin, plugindummy.plugins))
+ return [plug.__name__ for plug in x]
+
+
+ def resetEnvironment(self):
+ """
+ Change the environment to what it should be just as the test is
+ starting.
+ """
+ self.unsetEnvironment()
+ sys.path.extend([x.path for x in [self.devPath,
+ self.systemPath,
+ self.appPath]])
+
+ def unsetEnvironment(self):
+ """
+ Change the Python environment back to what it was before the test was
+ started.
+ """
+ sys.modules.clear()
+ sys.modules.update(self.savedModules)
+ sys.path[:] = self.savedPath
+
+
+ def tearDown(self):
+ """
+ Reset the Python environment to what it was before this test ran, and
+ restore permissions on files which were marked read-only so that the
+ directory may be cleanly cleaned up.
+ """
+ self.unsetEnvironment()
+ # Normally we wouldn't "clean up" the filesystem like this (leaving
+ # things for post-test inspection), but if we left the permissions the
+ # way they were, we'd be leaving files around that the buildbots
+ # couldn't delete, and that would be bad.
+ self.unlockSystem()
+
+
+ def test_developmentPluginAvailability(self):
+ """
+ Plugins added in the development path should be loadable, even when
+ the (now non-importable) system path contains its own idea of the
+ list of plugins for a package. Inversely, plugins added in the
+ system path should not be available.
+ """
+ # Run 3 times: uncached, cached, and then cached again to make sure we
+ # didn't overwrite / corrupt the cache on the cached try.
+ for x in range(3):
+ names = self.getAllPlugins()
+ names.sort()
+ self.assertEqual(names, ['app', 'dev'])
+
+
+ def test_freshPyReplacesStalePyc(self):
+ """
+ Verify that if a stale .pyc file on the PYTHONPATH is replaced by a
+ fresh .py file, the plugins in the new .py are picked up rather than
+ the stale .pyc, even if the .pyc is still around.
+ """
+ mypath = self.appPackage.child("stale.py")
+ mypath.setContent(pluginFileContents('one'))
+ # Make it super stale
+ x = time.time() - 1000
+ os.utime(mypath.path, (x, x))
+ pyc = mypath.sibling('stale.pyc')
+ # compile it
+ compileall.compile_dir(self.appPackage.path, quiet=1)
+ os.utime(pyc.path, (x, x))
+ # Eliminate the other option.
+ mypath.remove()
+ # Make sure it's the .pyc path getting cached.
+ self.resetEnvironment()
+ # Sanity check.
+ self.assertIn('one', self.getAllPlugins())
+ self.failIfIn('two', self.getAllPlugins())
+ self.resetEnvironment()
+ mypath.setContent(pluginFileContents('two'))
+ self.failIfIn('one', self.getAllPlugins())
+ self.assertIn('two', self.getAllPlugins())
+
+
+ def test_newPluginsOnReadOnlyPath(self):
+ """
+ Verify that a failure to write the dropin.cache file on a read-only
+ path will not affect the list of plugins returned.
+
+ Note: this test should pass on both Linux and Windows, but may not
+ provide useful coverage on Windows due to the different meaning of
+ "read-only directory".
+ """
+ self.unlockSystem()
+ self.sysplug.child('newstuff.py').setContent(pluginFileContents('one'))
+ self.lockSystem()
+
+ # Take the developer path out, so that the system plugins are actually
+ # examined.
+ sys.path.remove(self.devPath.path)
+
+ # Sanity check to make sure we're only flushing the error logged
+ # below...
+ self.assertEqual(len(self.flushLoggedErrors()), 0)
+ self.assertIn('one', self.getAllPlugins())
+ self.assertEqual(len(self.flushLoggedErrors()), 1)
+
+
+
+class AdjacentPackageTests(unittest.TestCase):
+ """
+ Tests for the behavior of the plugin system when there are multiple
+ installed copies of the package containing the plugins being loaded.
+ """
+
+ def setUp(self):
+ """
+ Save the elements of C{sys.path} and the items of C{sys.modules}.
+ """
+ self.originalPath = sys.path[:]
+ self.savedModules = sys.modules.copy()
+
+
+ def tearDown(self):
+ """
+ Restore C{sys.path} and C{sys.modules} to their original values.
+ """
+ sys.path[:] = self.originalPath
+ sys.modules.clear()
+ sys.modules.update(self.savedModules)
+
+
+ def createDummyPackage(self, root, name, pluginName):
+ """
+ Create a directory containing a Python package named I{dummy} with a
+ I{plugins} subpackage.
+
+ @type root: L{FilePath}
+ @param root: The directory in which to create the hierarchy.
+
+ @type name: C{str}
+ @param name: The name of the directory to create which will contain
+ the package.
+
+ @type pluginName: C{str}
+ @param pluginName: The name of a module to create in the
+ I{dummy.plugins} package.
+
+ @rtype: L{FilePath}
+ @return: The directory which was created to contain the I{dummy}
+ package.
+ """
+ directory = root.child(name)
+ package = directory.child('dummy')
+ package.makedirs()
+ package.child('__init__.py').setContent('')
+ plugins = package.child('plugins')
+ plugins.makedirs()
+ plugins.child('__init__.py').setContent(pluginInitFile)
+ pluginModule = plugins.child(pluginName + '.py')
+ pluginModule.setContent(pluginFileContents(name))
+ return directory
+
+
+ def test_hiddenPackageSamePluginModuleNameObscured(self):
+ """
+ Only plugins from the first package in sys.path should be returned by
+ getPlugins in the case where there are two Python packages by the same
+ name installed, each with a plugin module by a single name.
+ """
+ root = FilePath(self.mktemp())
+ root.makedirs()
+
+ firstDirectory = self.createDummyPackage(root, 'first', 'someplugin')
+ secondDirectory = self.createDummyPackage(root, 'second', 'someplugin')
+
+ sys.path.append(firstDirectory.path)
+ sys.path.append(secondDirectory.path)
+
+ import dummy.plugins
+
+ plugins = list(plugin.getPlugins(ITestPlugin, dummy.plugins))
+ self.assertEqual(['first'], [p.__name__ for p in plugins])
+
+
+ def test_hiddenPackageDifferentPluginModuleNameObscured(self):
+ """
+ Plugins from the first package in sys.path should be returned by
+ getPlugins in the case where there are two Python packages by the same
+ name installed, each with a plugin module by a different name.
+ """
+ root = FilePath(self.mktemp())
+ root.makedirs()
+
+ firstDirectory = self.createDummyPackage(root, 'first', 'thisplugin')
+ secondDirectory = self.createDummyPackage(root, 'second', 'thatplugin')
+
+ sys.path.append(firstDirectory.path)
+ sys.path.append(secondDirectory.path)
+
+ import dummy.plugins
+
+ plugins = list(plugin.getPlugins(ITestPlugin, dummy.plugins))
+ self.assertEqual(['first'], [p.__name__ for p in plugins])
+
+
+
+class PackagePathTests(unittest.TestCase):
+ """
+ Tests for L{plugin.pluginPackagePaths} which constructs search paths for
+ plugin packages.
+ """
+
+ def setUp(self):
+ """
+ Save the elements of C{sys.path}.
+ """
+ self.originalPath = sys.path[:]
+
+
+ def tearDown(self):
+ """
+ Restore C{sys.path} to its original value.
+ """
+ sys.path[:] = self.originalPath
+
+
+ def test_pluginDirectories(self):
+ """
+ L{plugin.pluginPackagePaths} should return a list containing each
+ directory in C{sys.path} with a suffix based on the supplied package
+ name.
+ """
+ foo = FilePath('foo')
+ bar = FilePath('bar')
+ sys.path = [foo.path, bar.path]
+ self.assertEqual(
+ plugin.pluginPackagePaths('dummy.plugins'),
+ [foo.child('dummy').child('plugins').path,
+ bar.child('dummy').child('plugins').path])
+
+
+ def test_pluginPackagesExcluded(self):
+ """
+ L{plugin.pluginPackagePaths} should exclude directories which are
+ Python packages. The only allowed plugin package (the only one
+ associated with a I{dummy} package which Python will allow to be
+ imported) will already be known to the caller of
+ L{plugin.pluginPackagePaths} and will most commonly already be in
+ the C{__path__} they are about to mutate.
+ """
+ root = FilePath(self.mktemp())
+ foo = root.child('foo').child('dummy').child('plugins')
+ foo.makedirs()
+ foo.child('__init__.py').setContent('')
+ sys.path = [root.child('foo').path, root.child('bar').path]
+ self.assertEqual(
+ plugin.pluginPackagePaths('dummy.plugins'),
+ [root.child('bar').child('dummy').child('plugins').path])
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_policies.py b/vendor/Twisted-10.0.0/twisted/test/test_policies.py
new file mode 100644
index 0000000000..d142078b44
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_policies.py
@@ -0,0 +1,683 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test code for policies.
+"""
+
+from zope.interface import Interface, implements, implementedBy
+
+from StringIO import StringIO
+
+from twisted.trial import unittest
+from twisted.test.proto_helpers import StringTransport
+from twisted.test.proto_helpers import StringTransportWithDisconnection
+
+from twisted.internet import protocol, reactor, address, defer, task
+from twisted.protocols import policies
+
+
+
+class SimpleProtocol(protocol.Protocol):
+
+ connected = disconnected = 0
+ buffer = ""
+
+ def __init__(self):
+ self.dConnected = defer.Deferred()
+ self.dDisconnected = defer.Deferred()
+
+ def connectionMade(self):
+ self.connected = 1
+ self.dConnected.callback('')
+
+ def connectionLost(self, reason):
+ self.disconnected = 1
+ self.dDisconnected.callback('')
+
+ def dataReceived(self, data):
+ self.buffer += data
+
+
+
+class SillyFactory(protocol.ClientFactory):
+
+ def __init__(self, p):
+ self.p = p
+
+ def buildProtocol(self, addr):
+ return self.p
+
+
+class EchoProtocol(protocol.Protocol):
+ paused = False
+
+ def pauseProducing(self):
+ self.paused = True
+
+ def resumeProducing(self):
+ self.paused = False
+
+ def stopProducing(self):
+ pass
+
+ def dataReceived(self, data):
+ self.transport.write(data)
+
+
+
+class Server(protocol.ServerFactory):
+ """
+ A simple server factory using L{EchoProtocol}.
+ """
+ protocol = EchoProtocol
+
+
+
+class TestableThrottlingFactory(policies.ThrottlingFactory):
+ """
+ L{policies.ThrottlingFactory} using a L{task.Clock} for tests.
+ """
+
+ def __init__(self, clock, *args, **kwargs):
+ """
+ @param clock: object providing a callLater method that can be used
+ for tests.
+ @type clock: C{task.Clock} or alike.
+ """
+ policies.ThrottlingFactory.__init__(self, *args, **kwargs)
+ self.clock = clock
+
+
+ def callLater(self, period, func):
+ """
+ Forward to the testable clock.
+ """
+ return self.clock.callLater(period, func)
+
+
+
+class TestableTimeoutFactory(policies.TimeoutFactory):
+ """
+ L{policies.TimeoutFactory} using a L{task.Clock} for tests.
+ """
+
+ def __init__(self, clock, *args, **kwargs):
+ """
+ @param clock: object providing a callLater method that can be used
+ for tests.
+ @type clock: C{task.Clock} or alike.
+ """
+ policies.TimeoutFactory.__init__(self, *args, **kwargs)
+ self.clock = clock
+
+
+ def callLater(self, period, func):
+ """
+ Forward to the testable clock.
+ """
+ return self.clock.callLater(period, func)
+
+
+
+class WrapperTestCase(unittest.TestCase):
+ """
+ Tests for L{WrappingFactory} and L{ProtocolWrapper}.
+ """
+ def test_protocolFactoryAttribute(self):
+ """
+ Make sure protocol.factory is the wrapped factory, not the wrapping
+ factory.
+ """
+ f = Server()
+ wf = policies.WrappingFactory(f)
+ p = wf.buildProtocol(address.IPv4Address('TCP', '127.0.0.1', 35))
+ self.assertIdentical(p.wrappedProtocol.factory, f)
+
+
+ def test_transportInterfaces(self):
+ """
+ The transport wrapper passed to the wrapped protocol's
+ C{makeConnection} provides the same interfaces as are provided by the
+ original transport.
+ """
+ class IStubTransport(Interface):
+ pass
+
+ class StubTransport:
+ implements(IStubTransport)
+
+ # Looking up what ProtocolWrapper implements also mutates the class.
+ # It adds __implemented__ and __providedBy__ attributes to it. These
+ # prevent __getattr__ from causing the IStubTransport.providedBy call
+ # below from returning True. If, by accident, nothing else causes
+ # these attributes to be added to ProtocolWrapper, the test will pass,
+ # but the interface will only be provided until something does trigger
+ # their addition. So we just trigger it right now to be sure.
+ implementedBy(policies.ProtocolWrapper)
+
+ proto = protocol.Protocol()
+ wrapper = policies.ProtocolWrapper(policies.WrappingFactory(None), proto)
+
+ wrapper.makeConnection(StubTransport())
+ self.assertTrue(IStubTransport.providedBy(proto.transport))
+
+
+
+class WrappingFactory(policies.WrappingFactory):
+ protocol = lambda s, f, p: p
+
+ def startFactory(self):
+ policies.WrappingFactory.startFactory(self)
+ self.deferred.callback(None)
+
+
+
+class ThrottlingTestCase(unittest.TestCase):
+ """
+ Tests for L{policies.ThrottlingFactory}.
+ """
+
+ def test_limit(self):
+ """
+ Full test using a custom server limiting number of connections.
+ """
+ server = Server()
+ c1, c2, c3, c4 = [SimpleProtocol() for i in range(4)]
+ tServer = policies.ThrottlingFactory(server, 2)
+ wrapTServer = WrappingFactory(tServer)
+ wrapTServer.deferred = defer.Deferred()
+
+ # Start listening
+ p = reactor.listenTCP(0, wrapTServer, interface="127.0.0.1")
+ n = p.getHost().port
+
+ def _connect123(results):
+ reactor.connectTCP("127.0.0.1", n, SillyFactory(c1))
+ c1.dConnected.addCallback(
+ lambda r: reactor.connectTCP("127.0.0.1", n, SillyFactory(c2)))
+ c2.dConnected.addCallback(
+ lambda r: reactor.connectTCP("127.0.0.1", n, SillyFactory(c3)))
+ return c3.dDisconnected
+
+ def _check123(results):
+ self.assertEquals([c.connected for c in c1, c2, c3], [1, 1, 1])
+ self.assertEquals([c.disconnected for c in c1, c2, c3], [0, 0, 1])
+ self.assertEquals(len(tServer.protocols.keys()), 2)
+ return results
+
+ def _lose1(results):
+ # disconnect one protocol and now another should be able to connect
+ c1.transport.loseConnection()
+ return c1.dDisconnected
+
+ def _connect4(results):
+ reactor.connectTCP("127.0.0.1", n, SillyFactory(c4))
+ return c4.dConnected
+
+ def _check4(results):
+ self.assertEquals(c4.connected, 1)
+ self.assertEquals(c4.disconnected, 0)
+ return results
+
+ def _cleanup(results):
+ for c in c2, c4:
+ c.transport.loseConnection()
+ return defer.DeferredList([
+ defer.maybeDeferred(p.stopListening),
+ c2.dDisconnected,
+ c4.dDisconnected])
+
+ wrapTServer.deferred.addCallback(_connect123)
+ wrapTServer.deferred.addCallback(_check123)
+ wrapTServer.deferred.addCallback(_lose1)
+ wrapTServer.deferred.addCallback(_connect4)
+ wrapTServer.deferred.addCallback(_check4)
+ wrapTServer.deferred.addCallback(_cleanup)
+ return wrapTServer.deferred
+
+
+ def test_writeLimit(self):
+ """
+ Check the writeLimit parameter: write data, and check for the pause
+ status.
+ """
+ server = Server()
+ tServer = TestableThrottlingFactory(task.Clock(), server, writeLimit=10)
+ port = tServer.buildProtocol(address.IPv4Address('TCP', '127.0.0.1', 0))
+ tr = StringTransportWithDisconnection()
+ tr.protocol = port
+ port.makeConnection(tr)
+ port.producer = port.wrappedProtocol
+
+ port.dataReceived("0123456789")
+ port.dataReceived("abcdefghij")
+ self.assertEquals(tr.value(), "0123456789abcdefghij")
+ self.assertEquals(tServer.writtenThisSecond, 20)
+ self.assertFalse(port.wrappedProtocol.paused)
+
+ # at this point server should've written 20 bytes, 10 bytes
+ # above the limit so writing should be paused around 1 second
+ # from 'now', and resumed a second after that
+ tServer.clock.advance(1.05)
+ self.assertEquals(tServer.writtenThisSecond, 0)
+ self.assertTrue(port.wrappedProtocol.paused)
+
+ tServer.clock.advance(1.05)
+ self.assertEquals(tServer.writtenThisSecond, 0)
+ self.assertFalse(port.wrappedProtocol.paused)
+
+
+ def test_readLimit(self):
+ """
+ Check the readLimit parameter: read data and check for the pause
+ status.
+ """
+ server = Server()
+ tServer = TestableThrottlingFactory(task.Clock(), server, readLimit=10)
+ port = tServer.buildProtocol(address.IPv4Address('TCP', '127.0.0.1', 0))
+ tr = StringTransportWithDisconnection()
+ tr.protocol = port
+ port.makeConnection(tr)
+
+ port.dataReceived("0123456789")
+ port.dataReceived("abcdefghij")
+ self.assertEquals(tr.value(), "0123456789abcdefghij")
+ self.assertEquals(tServer.readThisSecond, 20)
+
+ tServer.clock.advance(1.05)
+ self.assertEquals(tServer.readThisSecond, 0)
+ self.assertEquals(tr.producerState, 'paused')
+
+ tServer.clock.advance(1.05)
+ self.assertEquals(tServer.readThisSecond, 0)
+ self.assertEquals(tr.producerState, 'producing')
+
+ tr.clear()
+ port.dataReceived("0123456789")
+ port.dataReceived("abcdefghij")
+ self.assertEquals(tr.value(), "0123456789abcdefghij")
+ self.assertEquals(tServer.readThisSecond, 20)
+
+ tServer.clock.advance(1.05)
+ self.assertEquals(tServer.readThisSecond, 0)
+ self.assertEquals(tr.producerState, 'paused')
+
+ tServer.clock.advance(1.05)
+ self.assertEquals(tServer.readThisSecond, 0)
+ self.assertEquals(tr.producerState, 'producing')
+
+
+
+class TimeoutTestCase(unittest.TestCase):
+ """
+ Tests for L{policies.TimeoutFactory}.
+ """
+
+ def setUp(self):
+ """
+ Create a testable, deterministic clock, and a set of
+ server factory/protocol/transport.
+ """
+ self.clock = task.Clock()
+ wrappedFactory = protocol.ServerFactory()
+ wrappedFactory.protocol = SimpleProtocol
+ self.factory = TestableTimeoutFactory(self.clock, wrappedFactory, 3)
+ self.proto = self.factory.buildProtocol(
+ address.IPv4Address('TCP', '127.0.0.1', 12345))
+ self.transport = StringTransportWithDisconnection()
+ self.transport.protocol = self.proto
+ self.proto.makeConnection(self.transport)
+
+
+ def test_timeout(self):
+ """
+ Make sure that when a TimeoutFactory accepts a connection, it will
+ time out that connection if no data is read or written within the
+ timeout period.
+ """
+ # Let almost 3 time units pass
+ self.clock.pump([0.0, 0.5, 1.0, 1.0, 0.4])
+ self.failIf(self.proto.wrappedProtocol.disconnected)
+
+ # Now let the timer elapse
+ self.clock.pump([0.0, 0.2])
+ self.failUnless(self.proto.wrappedProtocol.disconnected)
+
+
+ def test_sendAvoidsTimeout(self):
+ """
+ Make sure that writing data to a transport from a protocol
+ constructed by a TimeoutFactory resets the timeout countdown.
+ """
+ # Let half the countdown period elapse
+ self.clock.pump([0.0, 0.5, 1.0])
+ self.failIf(self.proto.wrappedProtocol.disconnected)
+
+ # Send some data (self.proto is the /real/ proto's transport, so this
+ # is the write that gets called)
+ self.proto.write('bytes bytes bytes')
+
+ # More time passes, putting us past the original timeout
+ self.clock.pump([0.0, 1.0, 1.0])
+ self.failIf(self.proto.wrappedProtocol.disconnected)
+
+ # Make sure writeSequence delays timeout as well
+ self.proto.writeSequence(['bytes'] * 3)
+
+ # Tick tock
+ self.clock.pump([0.0, 1.0, 1.0])
+ self.failIf(self.proto.wrappedProtocol.disconnected)
+
+ # Don't write anything more, just let the timeout expire
+ self.clock.pump([0.0, 2.0])
+ self.failUnless(self.proto.wrappedProtocol.disconnected)
+
+
+ def test_receiveAvoidsTimeout(self):
+ """
+ Make sure that receiving data also resets the timeout countdown.
+ """
+ # Let half the countdown period elapse
+ self.clock.pump([0.0, 1.0, 0.5])
+ self.failIf(self.proto.wrappedProtocol.disconnected)
+
+ # Some bytes arrive, they should reset the counter
+ self.proto.dataReceived('bytes bytes bytes')
+
+ # We pass the original timeout
+ self.clock.pump([0.0, 1.0, 1.0])
+ self.failIf(self.proto.wrappedProtocol.disconnected)
+
+ # Nothing more arrives though, the new timeout deadline is passed,
+ # the connection should be dropped.
+ self.clock.pump([0.0, 1.0, 1.0])
+ self.failUnless(self.proto.wrappedProtocol.disconnected)
+
+
+
+class TimeoutTester(protocol.Protocol, policies.TimeoutMixin):
+ """
+ A testable protocol with timeout facility.
+
+ @ivar timedOut: set to C{True} if a timeout has been detected.
+ @type timedOut: C{bool}
+ """
+ timeOut = 3
+ timedOut = False
+
+ def __init__(self, clock):
+ """
+ Initialize the protocol with a C{task.Clock} object.
+ """
+ self.clock = clock
+
+
+ def connectionMade(self):
+ """
+ Upon connection, set the timeout.
+ """
+ self.setTimeout(self.timeOut)
+
+
+ def dataReceived(self, data):
+ """
+ Reset the timeout on data.
+ """
+ self.resetTimeout()
+ protocol.Protocol.dataReceived(self, data)
+
+
+ def connectionLost(self, reason=None):
+ """
+ On connection lost, cancel all timeout operations.
+ """
+ self.setTimeout(None)
+
+
+ def timeoutConnection(self):
+ """
+ Flags the timedOut variable to indicate the timeout of the connection.
+ """
+ self.timedOut = True
+
+
+ def callLater(self, timeout, func, *args, **kwargs):
+ """
+ Override callLater to use the deterministic clock.
+ """
+ return self.clock.callLater(timeout, func, *args, **kwargs)
+
+
+
+class TestTimeout(unittest.TestCase):
+ """
+ Tests for L{policies.TimeoutMixin}.
+ """
+
+ def setUp(self):
+ """
+ Create a testable, deterministic clock and a C{TimeoutTester} instance.
+ """
+ self.clock = task.Clock()
+ self.proto = TimeoutTester(self.clock)
+
+
+ def test_overriddenCallLater(self):
+ """
+ Test that the callLater of the clock is used instead of
+ C{reactor.callLater}.
+ """
+ self.proto.setTimeout(10)
+ self.assertEquals(len(self.clock.calls), 1)
+
+
+ def test_timeout(self):
+ """
+ Check that the protocol does timeout at the time specified by its
+ C{timeOut} attribute.
+ """
+ self.proto.makeConnection(StringTransport())
+
+ # timeOut value is 3
+ self.clock.pump([0, 0.5, 1.0, 1.0])
+ self.failIf(self.proto.timedOut)
+ self.clock.pump([0, 1.0])
+ self.failUnless(self.proto.timedOut)
+
+
+ def test_noTimeout(self):
+ """
+ Check that receiving data is delaying the timeout of the connection.
+ """
+ self.proto.makeConnection(StringTransport())
+
+ self.clock.pump([0, 0.5, 1.0, 1.0])
+ self.failIf(self.proto.timedOut)
+ self.proto.dataReceived('hello there')
+ self.clock.pump([0, 1.0, 1.0, 0.5])
+ self.failIf(self.proto.timedOut)
+ self.clock.pump([0, 1.0])
+ self.failUnless(self.proto.timedOut)
+
+
+ def test_resetTimeout(self):
+ """
+ Check that setting a new value for timeout cancel the previous value
+ and install a new timeout.
+ """
+ self.proto.timeOut = None
+ self.proto.makeConnection(StringTransport())
+
+ self.proto.setTimeout(1)
+ self.assertEquals(self.proto.timeOut, 1)
+
+ self.clock.pump([0, 0.9])
+ self.failIf(self.proto.timedOut)
+ self.clock.pump([0, 0.2])
+ self.failUnless(self.proto.timedOut)
+
+
+ def test_cancelTimeout(self):
+ """
+ Setting the timeout to C{None} cancel any timeout operations.
+ """
+ self.proto.timeOut = 5
+ self.proto.makeConnection(StringTransport())
+
+ self.proto.setTimeout(None)
+ self.assertEquals(self.proto.timeOut, None)
+
+ self.clock.pump([0, 5, 5, 5])
+ self.failIf(self.proto.timedOut)
+
+
+ def test_return(self):
+ """
+ setTimeout should return the value of the previous timeout.
+ """
+ self.proto.timeOut = 5
+
+ self.assertEquals(self.proto.setTimeout(10), 5)
+ self.assertEquals(self.proto.setTimeout(None), 10)
+ self.assertEquals(self.proto.setTimeout(1), None)
+ self.assertEquals(self.proto.timeOut, 1)
+
+ # Clean up the DelayedCall
+ self.proto.setTimeout(None)
+
+
+
+class LimitTotalConnectionsFactoryTestCase(unittest.TestCase):
+ """Tests for policies.LimitTotalConnectionsFactory"""
+ def testConnectionCounting(self):
+ # Make a basic factory
+ factory = policies.LimitTotalConnectionsFactory()
+ factory.protocol = protocol.Protocol
+
+ # connectionCount starts at zero
+ self.assertEqual(0, factory.connectionCount)
+
+ # connectionCount increments as connections are made
+ p1 = factory.buildProtocol(None)
+ self.assertEqual(1, factory.connectionCount)
+ p2 = factory.buildProtocol(None)
+ self.assertEqual(2, factory.connectionCount)
+
+ # and decrements as they are lost
+ p1.connectionLost(None)
+ self.assertEqual(1, factory.connectionCount)
+ p2.connectionLost(None)
+ self.assertEqual(0, factory.connectionCount)
+
+ def testConnectionLimiting(self):
+ # Make a basic factory with a connection limit of 1
+ factory = policies.LimitTotalConnectionsFactory()
+ factory.protocol = protocol.Protocol
+ factory.connectionLimit = 1
+
+ # Make a connection
+ p = factory.buildProtocol(None)
+ self.assertNotEqual(None, p)
+ self.assertEqual(1, factory.connectionCount)
+
+ # Try to make a second connection, which will exceed the connection
+ # limit. This should return None, because overflowProtocol is None.
+ self.assertEqual(None, factory.buildProtocol(None))
+ self.assertEqual(1, factory.connectionCount)
+
+ # Define an overflow protocol
+ class OverflowProtocol(protocol.Protocol):
+ def connectionMade(self):
+ factory.overflowed = True
+ factory.overflowProtocol = OverflowProtocol
+ factory.overflowed = False
+
+ # Try to make a second connection again, now that we have an overflow
+ # protocol. Note that overflow connections count towards the connection
+ # count.
+ op = factory.buildProtocol(None)
+ op.makeConnection(None) # to trigger connectionMade
+ self.assertEqual(True, factory.overflowed)
+ self.assertEqual(2, factory.connectionCount)
+
+ # Close the connections.
+ p.connectionLost(None)
+ self.assertEqual(1, factory.connectionCount)
+ op.connectionLost(None)
+ self.assertEqual(0, factory.connectionCount)
+
+
+class WriteSequenceEchoProtocol(EchoProtocol):
+ def dataReceived(self, bytes):
+ if bytes.find('vector!') != -1:
+ self.transport.writeSequence([bytes])
+ else:
+ EchoProtocol.dataReceived(self, bytes)
+
+class TestLoggingFactory(policies.TrafficLoggingFactory):
+ openFile = None
+ def open(self, name):
+ assert self.openFile is None, "open() called too many times"
+ self.openFile = StringIO()
+ return self.openFile
+
+
+
+class LoggingFactoryTestCase(unittest.TestCase):
+ """
+ Tests for L{policies.TrafficLoggingFactory}.
+ """
+
+ def test_thingsGetLogged(self):
+ """
+ Check the output produced by L{policies.TrafficLoggingFactory}.
+ """
+ wrappedFactory = Server()
+ wrappedFactory.protocol = WriteSequenceEchoProtocol
+ t = StringTransportWithDisconnection()
+ f = TestLoggingFactory(wrappedFactory, 'test')
+ p = f.buildProtocol(('1.2.3.4', 5678))
+ t.protocol = p
+ p.makeConnection(t)
+
+ v = f.openFile.getvalue()
+ self.failUnless('*' in v, "* not found in %r" % (v,))
+ self.failIf(t.value())
+
+ p.dataReceived('here are some bytes')
+
+ v = f.openFile.getvalue()
+ self.assertIn("C 1: 'here are some bytes'", v)
+ self.assertIn("S 1: 'here are some bytes'", v)
+ self.assertEquals(t.value(), 'here are some bytes')
+
+ t.clear()
+ p.dataReceived('prepare for vector! to the extreme')
+ v = f.openFile.getvalue()
+ self.assertIn("SV 1: ['prepare for vector! to the extreme']", v)
+ self.assertEquals(t.value(), 'prepare for vector! to the extreme')
+
+ p.loseConnection()
+
+ v = f.openFile.getvalue()
+ self.assertIn('ConnectionDone', v)
+
+
+ def test_counter(self):
+ """
+ Test counter management with the resetCounter method.
+ """
+ wrappedFactory = Server()
+ f = TestLoggingFactory(wrappedFactory, 'test')
+ self.assertEqual(f._counter, 0)
+ f.buildProtocol(('1.2.3.4', 5678))
+ self.assertEqual(f._counter, 1)
+ # Reset log file
+ f.openFile = None
+ f.buildProtocol(('1.2.3.4', 5679))
+ self.assertEqual(f._counter, 2)
+
+ f.resetCounter()
+ self.assertEqual(f._counter, 0)
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_postfix.py b/vendor/Twisted-10.0.0/twisted/test/test_postfix.py
new file mode 100644
index 0000000000..a165abc36b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_postfix.py
@@ -0,0 +1,108 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for twisted.protocols.postfix module.
+"""
+
+from twisted.trial import unittest
+from twisted.protocols import postfix
+from twisted.test.proto_helpers import StringTransport
+
+
+class PostfixTCPMapQuoteTestCase(unittest.TestCase):
+ data = [
+ # (raw, quoted, [aliasQuotedForms]),
+ ('foo', 'foo'),
+ ('foo bar', 'foo%20bar'),
+ ('foo\tbar', 'foo%09bar'),
+ ('foo\nbar', 'foo%0Abar', 'foo%0abar'),
+ ('foo\r\nbar', 'foo%0D%0Abar', 'foo%0D%0abar', 'foo%0d%0Abar', 'foo%0d%0abar'),
+ ('foo ', 'foo%20'),
+ (' foo', '%20foo'),
+ ]
+
+ def testData(self):
+ for entry in self.data:
+ raw = entry[0]
+ quoted = entry[1:]
+
+ self.assertEquals(postfix.quote(raw), quoted[0])
+ for q in quoted:
+ self.assertEquals(postfix.unquote(q), raw)
+
+class PostfixTCPMapServerTestCase:
+ data = {
+ # 'key': 'value',
+ }
+
+ chat = [
+ # (input, expected_output),
+ ]
+
+ def test_chat(self):
+ """
+ Test that I{get} and I{put} commands are responded to correctly by
+ L{postfix.PostfixTCPMapServer} when its factory is an instance of
+ L{postifx.PostfixTCPMapDictServerFactory}.
+ """
+ factory = postfix.PostfixTCPMapDictServerFactory(self.data)
+ transport = StringTransport()
+
+ protocol = postfix.PostfixTCPMapServer()
+ protocol.service = factory
+ protocol.factory = factory
+ protocol.makeConnection(transport)
+
+ for input, expected_output in self.chat:
+ protocol.lineReceived(input)
+ self.assertEquals(
+ transport.value(), expected_output,
+ 'For %r, expected %r but got %r' % (
+ input, expected_output, transport.value()))
+ transport.clear()
+ protocol.setTimeout(None)
+
+
+ def test_deferredChat(self):
+ """
+ Test that I{get} and I{put} commands are responded to correctly by
+ L{postfix.PostfixTCPMapServer} when its factory is an instance of
+ L{postifx.PostfixTCPMapDeferringDictServerFactory}.
+ """
+ factory = postfix.PostfixTCPMapDeferringDictServerFactory(self.data)
+ transport = StringTransport()
+
+ protocol = postfix.PostfixTCPMapServer()
+ protocol.service = factory
+ protocol.factory = factory
+ protocol.makeConnection(transport)
+
+ for input, expected_output in self.chat:
+ protocol.lineReceived(input)
+ self.assertEquals(
+ transport.value(), expected_output,
+ 'For %r, expected %r but got %r' % (
+ input, expected_output, transport.value()))
+ transport.clear()
+ protocol.setTimeout(None)
+
+
+
+class Valid(PostfixTCPMapServerTestCase, unittest.TestCase):
+ data = {
+ 'foo': 'ThisIs Foo',
+ 'bar': ' bar really is found\r\n',
+ }
+ chat = [
+ ('get', "400 Command 'get' takes 1 parameters.\n"),
+ ('get foo bar', "500 \n"),
+ ('put', "400 Command 'put' takes 2 parameters.\n"),
+ ('put foo', "400 Command 'put' takes 2 parameters.\n"),
+ ('put foo bar baz', "500 put is not implemented yet.\n"),
+ ('put foo bar', '500 put is not implemented yet.\n'),
+ ('get foo', '200 ThisIs%20Foo\n'),
+ ('get bar', '200 %20bar%20really%20is%20found%0D%0A\n'),
+ ('get baz', '500 \n'),
+ ('foo', '400 unknown command\n'),
+ ]
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_process.py b/vendor/Twisted-10.0.0/twisted/test/test_process.py
new file mode 100644
index 0000000000..90890a161e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_process.py
@@ -0,0 +1,2410 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test running processes.
+"""
+
+import gzip
+import os
+import popen2
+import sys
+import signal
+import StringIO
+import errno
+import gc
+import stat
+try:
+ import fcntl
+except ImportError:
+ fcntl = process = None
+else:
+ from twisted.internet import process
+
+
+from zope.interface.verify import verifyObject
+
+from twisted.python.log import msg
+from twisted.internet import reactor, protocol, error, interfaces, defer
+from twisted.trial import unittest
+from twisted.python import util, runtime, procutils
+from twisted.python.compat import set
+
+
+
+class StubProcessProtocol(protocol.ProcessProtocol):
+ """
+ ProcessProtocol counter-implementation: all methods on this class raise an
+ exception, so instances of this may be used to verify that only certain
+ methods are called.
+ """
+ def outReceived(self, data):
+ raise NotImplementedError()
+
+ def errReceived(self, data):
+ raise NotImplementedError()
+
+ def inConnectionLost(self):
+ raise NotImplementedError()
+
+ def outConnectionLost(self):
+ raise NotImplementedError()
+
+ def errConnectionLost(self):
+ raise NotImplementedError()
+
+
+
+class ProcessProtocolTests(unittest.TestCase):
+ """
+ Tests for behavior provided by the process protocol base class,
+ L{protocol.ProcessProtocol}.
+ """
+ def test_interface(self):
+ """
+ L{ProcessProtocol} implements L{IProcessProtocol}.
+ """
+ verifyObject(interfaces.IProcessProtocol, protocol.ProcessProtocol())
+
+
+ def test_outReceived(self):
+ """
+ Verify that when stdout is delivered to
+ L{ProcessProtocol.childDataReceived}, it is forwarded to
+ L{ProcessProtocol.outReceived}.
+ """
+ received = []
+ class OutProtocol(StubProcessProtocol):
+ def outReceived(self, data):
+ received.append(data)
+
+ bytes = "bytes"
+ p = OutProtocol()
+ p.childDataReceived(1, bytes)
+ self.assertEqual(received, [bytes])
+
+
+ def test_errReceived(self):
+ """
+ Similar to L{test_outReceived}, but for stderr.
+ """
+ received = []
+ class ErrProtocol(StubProcessProtocol):
+ def errReceived(self, data):
+ received.append(data)
+
+ bytes = "bytes"
+ p = ErrProtocol()
+ p.childDataReceived(2, bytes)
+ self.assertEqual(received, [bytes])
+
+
+ def test_inConnectionLost(self):
+ """
+ Verify that when stdin close notification is delivered to
+ L{ProcessProtocol.childConnectionLost}, it is forwarded to
+ L{ProcessProtocol.inConnectionLost}.
+ """
+ lost = []
+ class InLostProtocol(StubProcessProtocol):
+ def inConnectionLost(self):
+ lost.append(None)
+
+ p = InLostProtocol()
+ p.childConnectionLost(0)
+ self.assertEqual(lost, [None])
+
+
+ def test_outConnectionLost(self):
+ """
+ Similar to L{test_inConnectionLost}, but for stdout.
+ """
+ lost = []
+ class OutLostProtocol(StubProcessProtocol):
+ def outConnectionLost(self):
+ lost.append(None)
+
+ p = OutLostProtocol()
+ p.childConnectionLost(1)
+ self.assertEqual(lost, [None])
+
+
+ def test_errConnectionLost(self):
+ """
+ Similar to L{test_inConnectionLost}, but for stderr.
+ """
+ lost = []
+ class ErrLostProtocol(StubProcessProtocol):
+ def errConnectionLost(self):
+ lost.append(None)
+
+ p = ErrLostProtocol()
+ p.childConnectionLost(2)
+ self.assertEqual(lost, [None])
+
+
+
+class TrivialProcessProtocol(protocol.ProcessProtocol):
+ """
+ Simple process protocol for tests purpose.
+
+ @ivar outData: data received from stdin
+ @ivar errData: data received from stderr
+ """
+
+ def __init__(self, d):
+ """
+ Create the deferred that will be fired at the end, and initialize
+ data structures.
+ """
+ self.deferred = d
+ self.outData = []
+ self.errData = []
+
+ def processEnded(self, reason):
+ self.reason = reason
+ self.deferred.callback(None)
+
+ def outReceived(self, data):
+ self.outData.append(data)
+
+ def errReceived(self, data):
+ self.errData.append(data)
+
+
+class TestProcessProtocol(protocol.ProcessProtocol):
+
+ def connectionMade(self):
+ self.stages = [1]
+ self.data = ''
+ self.err = ''
+ self.transport.write("abcd")
+
+ def childDataReceived(self, childFD, data):
+ """
+ Override and disable the dispatch provided by the base class to ensure
+ that it is really this method which is being called, and the transport
+ is not going directly to L{outReceived} or L{errReceived}.
+ """
+ if childFD == 1:
+ self.data += data
+ elif childFD == 2:
+ self.err += data
+
+
+ def childConnectionLost(self, childFD):
+ """
+ Similarly to L{childDataReceived}, disable the automatic dispatch
+ provided by the base implementation to verify that the transport is
+ calling this method directly.
+ """
+ if childFD == 1:
+ self.stages.append(2)
+ if self.data != "abcd":
+ raise RuntimeError
+ self.transport.write("1234")
+ elif childFD == 2:
+ self.stages.append(3)
+ if self.err != "1234":
+ print 'err != 1234: ' + repr(self.err)
+ raise RuntimeError()
+ self.transport.write("abcd")
+ self.stages.append(4)
+ elif childFD == 0:
+ self.stages.append(5)
+
+ def processEnded(self, reason):
+ self.reason = reason
+ self.deferred.callback(None)
+
+
+class EchoProtocol(protocol.ProcessProtocol):
+
+ s = "1234567" * 1001
+ n = 10
+ finished = 0
+
+ failure = None
+
+ def __init__(self, onEnded):
+ self.onEnded = onEnded
+ self.count = 0
+
+ def connectionMade(self):
+ assert self.n > 2
+ for i in range(self.n - 2):
+ self.transport.write(self.s)
+ # test writeSequence
+ self.transport.writeSequence([self.s, self.s])
+ self.buffer = self.s * self.n
+
+ def outReceived(self, data):
+ if buffer(self.buffer, self.count, len(data)) != buffer(data):
+ self.failure = ("wrong bytes received", data, self.count)
+ self.transport.closeStdin()
+ else:
+ self.count += len(data)
+ if self.count == len(self.buffer):
+ self.transport.closeStdin()
+
+ def processEnded(self, reason):
+ self.finished = 1
+ if not reason.check(error.ProcessDone):
+ self.failure = "process didn't terminate normally: " + str(reason)
+ self.onEnded.callback(self)
+
+
+
+class SignalProtocol(protocol.ProcessProtocol):
+ """
+ A process protocol that sends a signal when data is first received.
+
+ @ivar deferred: deferred firing on C{processEnded}.
+ @type deferred: L{defer.Deferred}
+
+ @ivar signal: the signal to send to the process.
+ @type signal: C{str}
+
+ @ivar signaled: A flag tracking whether the signal has been sent to the
+ child or not yet. C{False} until it is sent, then C{True}.
+ @type signaled: C{bool}
+ """
+
+ def __init__(self, deferred, sig):
+ self.deferred = deferred
+ self.signal = sig
+ self.signaled = False
+
+
+ def outReceived(self, data):
+ """
+ Handle the first output from the child process (which indicates it
+ is set up and ready to receive the signal) by sending the signal to
+ it. Also log all output to help with debugging.
+ """
+ msg("Received %r from child stdout" % (data,))
+ if not self.signaled:
+ self.signaled = True
+ self.transport.signalProcess(self.signal)
+
+
+ def errReceived(self, data):
+ """
+ Log all data received from the child's stderr to help with
+ debugging.
+ """
+ msg("Received %r from child stderr" % (data,))
+
+
+ def processEnded(self, reason):
+ """
+ Callback C{self.deferred} with C{None} if C{reason} is a
+ L{error.ProcessTerminated} failure with C{exitCode} set to C{None},
+ C{signal} set to C{self.signal}, and C{status} holding the status code
+ of the exited process. Otherwise, errback with a C{ValueError}
+ describing the problem.
+ """
+ msg("Child exited: %r" % (reason.getTraceback(),))
+ if not reason.check(error.ProcessTerminated):
+ return self.deferred.errback(
+ ValueError("wrong termination: %s" % (reason,)))
+ v = reason.value
+ if isinstance(self.signal, str):
+ signalValue = getattr(signal, 'SIG' + self.signal)
+ else:
+ signalValue = self.signal
+ if v.exitCode is not None:
+ return self.deferred.errback(
+ ValueError("SIG%s: exitCode is %s, not None" %
+ (self.signal, v.exitCode)))
+ if v.signal != signalValue:
+ return self.deferred.errback(
+ ValueError("SIG%s: .signal was %s, wanted %s" %
+ (self.signal, v.signal, signalValue)))
+ if os.WTERMSIG(v.status) != signalValue:
+ return self.deferred.errback(
+ ValueError('SIG%s: %s' % (self.signal, os.WTERMSIG(v.status))))
+ self.deferred.callback(None)
+
+
+
+class TestManyProcessProtocol(TestProcessProtocol):
+ def __init__(self):
+ self.deferred = defer.Deferred()
+
+ def processEnded(self, reason):
+ self.reason = reason
+ if reason.check(error.ProcessDone):
+ self.deferred.callback(None)
+ else:
+ self.deferred.errback(reason)
+
+
+
+class UtilityProcessProtocol(protocol.ProcessProtocol):
+ """
+ Helper class for launching a Python process and getting a result from it.
+
+ @ivar program: A string giving a Python program for the child process to
+ run.
+ """
+ program = None
+
+ def run(cls, reactor, argv, env):
+ """
+ Run a Python process connected to a new instance of this protocol
+ class. Return the protocol instance.
+
+ The Python process is given C{self.program} on the command line to
+ execute, in addition to anything specified by C{argv}. C{env} is
+ the complete environment.
+ """
+ exe = sys.executable
+ self = cls()
+ reactor.spawnProcess(
+ self, exe, [exe, "-c", self.program] + argv, env=env)
+ return self
+ run = classmethod(run)
+
+
+ def __init__(self):
+ self.bytes = []
+ self.requests = []
+
+
+ def parseChunks(self, bytes):
+ """
+ Called with all bytes received on stdout when the process exits.
+ """
+ raise NotImplementedError()
+
+
+ def getResult(self):
+ """
+ Return a Deferred which will fire with the result of L{parseChunks}
+ when the child process exits.
+ """
+ d = defer.Deferred()
+ self.requests.append(d)
+ return d
+
+
+ def _fireResultDeferreds(self, result):
+ """
+ Callback all Deferreds returned up until now by L{getResult}
+ with the given result object.
+ """
+ requests = self.requests
+ self.requests = None
+ for d in requests:
+ d.callback(result)
+
+
+ def outReceived(self, bytes):
+ """
+ Accumulate output from the child process in a list.
+ """
+ self.bytes.append(bytes)
+
+
+ def processEnded(self, reason):
+ """
+ Handle process termination by parsing all received output and firing
+ any waiting Deferreds.
+ """
+ self._fireResultDeferreds(self.parseChunks(self.bytes))
+
+
+
+
+class GetArgumentVector(UtilityProcessProtocol):
+ """
+ Protocol which will read a serialized argv from a process and
+ expose it to interested parties.
+ """
+ program = (
+ "from sys import stdout, argv\n"
+ "stdout.write(chr(0).join(argv))\n"
+ "stdout.flush()\n")
+
+ def parseChunks(self, chunks):
+ """
+ Parse the output from the process to which this protocol was
+ connected, which is a single unterminated line of \\0-separated
+ strings giving the argv of that process. Return this as a list of
+ str objects.
+ """
+ return ''.join(chunks).split('\0')
+
+
+
+class GetEnvironmentDictionary(UtilityProcessProtocol):
+ """
+ Protocol which will read a serialized environment dict from a process
+ and expose it to interested parties.
+ """
+ program = (
+ "from sys import stdout\n"
+ "from os import environ\n"
+ "items = environ.iteritems()\n"
+ "stdout.write(chr(0).join([k + chr(0) + v for k, v in items]))\n"
+ "stdout.flush()\n")
+
+ def parseChunks(self, chunks):
+ """
+ Parse the output from the process to which this protocol was
+ connected, which is a single unterminated line of \\0-separated
+ strings giving key value pairs of the environment from that process.
+ Return this as a dictionary.
+ """
+ environString = ''.join(chunks)
+ if not environString:
+ return {}
+ environ = iter(environString.split('\0'))
+ d = {}
+ while 1:
+ try:
+ k = environ.next()
+ except StopIteration:
+ break
+ else:
+ v = environ.next()
+ d[k] = v
+ return d
+
+
+
+class ProcessTestCase(unittest.TestCase):
+ """Test running a process."""
+
+ usePTY = False
+
+ def testStdio(self):
+ """twisted.internet.stdio test."""
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_twisted.py")
+ p = Accumulator()
+ d = p.endedDeferred = defer.Deferred()
+ env = {"PYTHONPATH": os.pathsep.join(sys.path)}
+ reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=env,
+ path=None, usePTY=self.usePTY)
+ p.transport.write("hello, world")
+ p.transport.write("abc")
+ p.transport.write("123")
+ p.transport.closeStdin()
+
+ def processEnded(ign):
+ self.assertEquals(p.outF.getvalue(), "hello, worldabc123",
+ "Output follows:\n"
+ "%s\n"
+ "Error message from process_twisted follows:\n"
+ "%s\n" % (p.outF.getvalue(), p.errF.getvalue()))
+ return d.addCallback(processEnded)
+
+
+ def test_unsetPid(self):
+ """
+ Test if pid is None/non-None before/after process termination. This
+ reuses process_echoer.py to get a process that blocks on stdin.
+ """
+ finished = defer.Deferred()
+ p = TrivialProcessProtocol(finished)
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_echoer.py")
+ procTrans = reactor.spawnProcess(p, exe,
+ [exe, scriptPath], env=None)
+ self.failUnless(procTrans.pid)
+
+ def afterProcessEnd(ignored):
+ self.assertEqual(procTrans.pid, None)
+
+ p.transport.closeStdin()
+ return finished.addCallback(afterProcessEnd)
+
+
+ def test_process(self):
+ """
+ Test running a process: check its output, it exitCode, some property of
+ signalProcess.
+ """
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_tester.py")
+ d = defer.Deferred()
+ p = TestProcessProtocol()
+ p.deferred = d
+ reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None)
+ def check(ignored):
+ self.assertEquals(p.stages, [1, 2, 3, 4, 5])
+ f = p.reason
+ f.trap(error.ProcessTerminated)
+ self.assertEquals(f.value.exitCode, 23)
+ # would .signal be available on non-posix?
+ # self.assertEquals(f.value.signal, None)
+ self.assertRaises(
+ error.ProcessExitedAlready, p.transport.signalProcess, 'INT')
+ try:
+ import process_tester, glob
+ for f in glob.glob(process_tester.test_file_match):
+ os.remove(f)
+ except:
+ pass
+ d.addCallback(check)
+ return d
+
+ def testManyProcesses(self):
+
+ def _check(results, protocols):
+ for p in protocols:
+ self.assertEquals(p.stages, [1, 2, 3, 4, 5], "[%d] stages = %s" % (id(p.transport), str(p.stages)))
+ # test status code
+ f = p.reason
+ f.trap(error.ProcessTerminated)
+ self.assertEquals(f.value.exitCode, 23)
+
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_tester.py")
+ args = [exe, "-u", scriptPath]
+ protocols = []
+ deferreds = []
+
+ for i in xrange(50):
+ p = TestManyProcessProtocol()
+ protocols.append(p)
+ reactor.spawnProcess(p, exe, args, env=None)
+ deferreds.append(p.deferred)
+
+ deferredList = defer.DeferredList(deferreds, consumeErrors=True)
+ deferredList.addCallback(_check, protocols)
+ return deferredList
+
+
+ def test_echo(self):
+ """
+ A spawning a subprocess which echoes its stdin to its stdout via
+ C{reactor.spawnProcess} will result in that echoed output being
+ delivered to outReceived.
+ """
+ finished = defer.Deferred()
+ p = EchoProtocol(finished)
+
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_echoer.py")
+ reactor.spawnProcess(p, exe, [exe, scriptPath], env=None)
+
+ def asserts(ignored):
+ self.failIf(p.failure, p.failure)
+ self.failUnless(hasattr(p, 'buffer'))
+ self.assertEquals(len(''.join(p.buffer)), len(p.s * p.n))
+
+ def takedownProcess(err):
+ p.transport.closeStdin()
+ return err
+
+ return finished.addCallback(asserts).addErrback(takedownProcess)
+
+
+ def testCommandLine(self):
+ args = [r'a\"b ', r'a\b ', r' a\\"b', r' a\\b', r'"foo bar" "', '\tab', '"\\', 'a"b', "a'b"]
+ pyExe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_cmdline.py")
+ p = Accumulator()
+ d = p.endedDeferred = defer.Deferred()
+ reactor.spawnProcess(p, pyExe, [pyExe, "-u", scriptPath]+args, env=None,
+ path=None)
+
+ def processEnded(ign):
+ self.assertEquals(p.errF.getvalue(), "")
+ recvdArgs = p.outF.getvalue().splitlines()
+ self.assertEquals(recvdArgs, args)
+ return d.addCallback(processEnded)
+
+
+ def test_wrongArguments(self):
+ """
+ Test invalid arguments to spawnProcess: arguments and environment
+ must only contains string or unicode, and not null bytes.
+ """
+ exe = sys.executable
+ p = protocol.ProcessProtocol()
+
+ badEnvs = [
+ {"foo": 2},
+ {"foo": "egg\0a"},
+ {3: "bar"},
+ {"bar\0foo": "bar"}]
+
+ badArgs = [
+ [exe, 2],
+ "spam",
+ [exe, "foo\0bar"]]
+
+ # Sanity check - this will fail for people who have mucked with
+ # their site configuration in a stupid way, but there's nothing we
+ # can do about that.
+ badUnicode = u'\N{SNOWMAN}'
+ try:
+ badUnicode.encode(sys.getdefaultencoding())
+ except UnicodeEncodeError:
+ # Okay, that unicode doesn't encode, put it in as a bad environment
+ # key.
+ badEnvs.append({badUnicode: 'value for bad unicode key'})
+ badEnvs.append({'key for bad unicode value': badUnicode})
+ badArgs.append([exe, badUnicode])
+ else:
+ # It _did_ encode. Most likely, Gtk2 is being used and the
+ # default system encoding is UTF-8, which can encode anything.
+ # In any case, if implicit unicode -> str conversion works for
+ # that string, we can't test that TypeError gets raised instead,
+ # so just leave it off.
+ pass
+
+ for env in badEnvs:
+ self.assertRaises(
+ TypeError,
+ reactor.spawnProcess, p, exe, [exe, "-c", ""], env=env)
+
+ for args in badArgs:
+ self.assertRaises(
+ TypeError,
+ reactor.spawnProcess, p, exe, args, env=None)
+
+
+ # Use upper-case so that the environment key test uses an upper case
+ # name: some versions of Windows only support upper case environment
+ # variable names, and I think Python (as of 2.5) doesn't use the right
+ # syscall for lowercase or mixed case names to work anyway.
+ okayUnicode = u"UNICODE"
+ encodedValue = "UNICODE"
+
+ def _deprecatedUnicodeSupportTest(self, processProtocolClass, argv=[], env={}):
+ """
+ Check that a deprecation warning is emitted when passing unicode to
+ spawnProcess for an argv value or an environment key or value.
+ Check that the warning is of the right type, has the right message,
+ and refers to the correct file. Unfortunately, don't check that the
+ line number is correct, because that is too hard for me to figure
+ out.
+
+ @param processProtocolClass: A L{UtilityProcessProtocol} subclass
+ which will be instantiated to communicate with the child process.
+
+ @param argv: The argv argument to spawnProcess.
+
+ @param env: The env argument to spawnProcess.
+
+ @return: A Deferred which fires when the test is complete.
+ """
+ # Sanity to check to make sure we can actually encode this unicode
+ # with the default system encoding. This may be excessively
+ # paranoid. -exarkun
+ self.assertEqual(
+ self.okayUnicode.encode(sys.getdefaultencoding()),
+ self.encodedValue)
+
+ p = self.assertWarns(DeprecationWarning,
+ "Argument strings and environment keys/values passed to "
+ "reactor.spawnProcess should be str, not unicode.", __file__,
+ processProtocolClass.run, reactor, argv, env)
+ return p.getResult()
+
+
+ def test_deprecatedUnicodeArgvSupport(self):
+ """
+ Test that a unicode string passed for an argument value is allowed
+ if it can be encoded with the default system encoding, but that a
+ deprecation warning is emitted.
+ """
+ d = self._deprecatedUnicodeSupportTest(GetArgumentVector, argv=[self.okayUnicode])
+ def gotArgVector(argv):
+ self.assertEqual(argv, ['-c', self.encodedValue])
+ d.addCallback(gotArgVector)
+ return d
+
+
+ def test_deprecatedUnicodeEnvKeySupport(self):
+ """
+ Test that a unicode string passed for the key of the environment
+ dictionary is allowed if it can be encoded with the default system
+ encoding, but that a deprecation warning is emitted.
+ """
+ d = self._deprecatedUnicodeSupportTest(
+ GetEnvironmentDictionary, env={self.okayUnicode: self.encodedValue})
+ def gotEnvironment(environ):
+ self.assertEqual(environ[self.encodedValue], self.encodedValue)
+ d.addCallback(gotEnvironment)
+ return d
+
+
+ def test_deprecatedUnicodeEnvValueSupport(self):
+ """
+ Test that a unicode string passed for the value of the environment
+ dictionary is allowed if it can be encoded with the default system
+ encoding, but that a deprecation warning is emitted.
+ """
+ d = self._deprecatedUnicodeSupportTest(
+ GetEnvironmentDictionary, env={self.encodedValue: self.okayUnicode})
+ def gotEnvironment(environ):
+ # On Windows, the environment contains more things than we
+ # specified, so only make sure that at least the key we wanted
+ # is there, rather than testing the dictionary for exact
+ # equality.
+ self.assertEqual(environ[self.encodedValue], self.encodedValue)
+ d.addCallback(gotEnvironment)
+ return d
+
+
+
+class TwoProcessProtocol(protocol.ProcessProtocol):
+ num = -1
+ finished = 0
+ def __init__(self):
+ self.deferred = defer.Deferred()
+ def outReceived(self, data):
+ pass
+ def processEnded(self, reason):
+ self.finished = 1
+ self.deferred.callback(None)
+
+class TestTwoProcessesBase:
+ def setUp(self):
+ self.processes = [None, None]
+ self.pp = [None, None]
+ self.done = 0
+ self.verbose = 0
+
+ def createProcesses(self, usePTY=0):
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_reader.py")
+ for num in (0,1):
+ self.pp[num] = TwoProcessProtocol()
+ self.pp[num].num = num
+ p = reactor.spawnProcess(self.pp[num],
+ exe, [exe, "-u", scriptPath], env=None,
+ usePTY=usePTY)
+ self.processes[num] = p
+
+ def close(self, num):
+ if self.verbose: print "closing stdin [%d]" % num
+ p = self.processes[num]
+ pp = self.pp[num]
+ self.failIf(pp.finished, "Process finished too early")
+ p.loseConnection()
+ if self.verbose: print self.pp[0].finished, self.pp[1].finished
+
+ def _onClose(self):
+ return defer.gatherResults([ p.deferred for p in self.pp ])
+
+ def testClose(self):
+ if self.verbose: print "starting processes"
+ self.createProcesses()
+ reactor.callLater(1, self.close, 0)
+ reactor.callLater(2, self.close, 1)
+ return self._onClose()
+
+class TestTwoProcessesNonPosix(TestTwoProcessesBase, unittest.TestCase):
+ pass
+
+class TestTwoProcessesPosix(TestTwoProcessesBase, unittest.TestCase):
+ def tearDown(self):
+ for pp, pr in zip(self.pp, self.processes):
+ if not pp.finished:
+ try:
+ os.kill(pr.pid, signal.SIGTERM)
+ except OSError:
+ # If the test failed the process may already be dead
+ # The error here is only noise
+ pass
+ return self._onClose()
+
+ def kill(self, num):
+ if self.verbose: print "kill [%d] with SIGTERM" % num
+ p = self.processes[num]
+ pp = self.pp[num]
+ self.failIf(pp.finished, "Process finished too early")
+ os.kill(p.pid, signal.SIGTERM)
+ if self.verbose: print self.pp[0].finished, self.pp[1].finished
+
+ def testKill(self):
+ if self.verbose: print "starting processes"
+ self.createProcesses(usePTY=0)
+ reactor.callLater(1, self.kill, 0)
+ reactor.callLater(2, self.kill, 1)
+ return self._onClose()
+
+ def testClosePty(self):
+ if self.verbose: print "starting processes"
+ self.createProcesses(usePTY=1)
+ reactor.callLater(1, self.close, 0)
+ reactor.callLater(2, self.close, 1)
+ return self._onClose()
+
+ def testKillPty(self):
+ if self.verbose: print "starting processes"
+ self.createProcesses(usePTY=1)
+ reactor.callLater(1, self.kill, 0)
+ reactor.callLater(2, self.kill, 1)
+ return self._onClose()
+
+class FDChecker(protocol.ProcessProtocol):
+ state = 0
+ data = ""
+ failed = None
+
+ def __init__(self, d):
+ self.deferred = d
+
+ def fail(self, why):
+ self.failed = why
+ self.deferred.callback(None)
+
+ def connectionMade(self):
+ self.transport.writeToChild(0, "abcd")
+ self.state = 1
+
+ def childDataReceived(self, childFD, data):
+ if self.state == 1:
+ if childFD != 1:
+ self.fail("read '%s' on fd %d (not 1) during state 1" \
+ % (childFD, data))
+ return
+ self.data += data
+ #print "len", len(self.data)
+ if len(self.data) == 6:
+ if self.data != "righto":
+ self.fail("got '%s' on fd1, expected 'righto'" \
+ % self.data)
+ return
+ self.data = ""
+ self.state = 2
+ #print "state2", self.state
+ self.transport.writeToChild(3, "efgh")
+ return
+ if self.state == 2:
+ self.fail("read '%s' on fd %s during state 2" % (childFD, data))
+ return
+ if self.state == 3:
+ if childFD != 1:
+ self.fail("read '%s' on fd %s (not 1) during state 3" \
+ % (childFD, data))
+ return
+ self.data += data
+ if len(self.data) == 6:
+ if self.data != "closed":
+ self.fail("got '%s' on fd1, expected 'closed'" \
+ % self.data)
+ return
+ self.state = 4
+ return
+ if self.state == 4:
+ self.fail("read '%s' on fd %s during state 4" % (childFD, data))
+ return
+
+ def childConnectionLost(self, childFD):
+ if self.state == 1:
+ self.fail("got connectionLost(%d) during state 1" % childFD)
+ return
+ if self.state == 2:
+ if childFD != 4:
+ self.fail("got connectionLost(%d) (not 4) during state 2" \
+ % childFD)
+ return
+ self.state = 3
+ self.transport.closeChildFD(5)
+ return
+
+ def processEnded(self, status):
+ rc = status.value.exitCode
+ if self.state != 4:
+ self.fail("processEnded early, rc %d" % rc)
+ return
+ if status.value.signal != None:
+ self.fail("processEnded with signal %s" % status.value.signal)
+ return
+ if rc != 0:
+ self.fail("processEnded with rc %d" % rc)
+ return
+ self.deferred.callback(None)
+
+
+class FDTest(unittest.TestCase):
+
+ def testFD(self):
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_fds.py")
+ d = defer.Deferred()
+ p = FDChecker(d)
+ reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None,
+ path=None,
+ childFDs={0:"w", 1:"r", 2:2,
+ 3:"w", 4:"r", 5:"w"})
+ d.addCallback(lambda x : self.failIf(p.failed, p.failed))
+ return d
+
+ def testLinger(self):
+ # See what happens when all the pipes close before the process
+ # actually stops. This test *requires* SIGCHLD catching to work,
+ # as there is no other way to find out the process is done.
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_linger.py")
+ p = Accumulator()
+ d = p.endedDeferred = defer.Deferred()
+ reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None,
+ path=None,
+ childFDs={1:"r", 2:2},
+ )
+ def processEnded(ign):
+ self.failUnlessEqual(p.outF.getvalue(),
+ "here is some text\ngoodbye\n")
+ return d.addCallback(processEnded)
+
+
+
+class Accumulator(protocol.ProcessProtocol):
+ """Accumulate data from a process."""
+
+ closed = 0
+ endedDeferred = None
+
+ def connectionMade(self):
+ self.outF = StringIO.StringIO()
+ self.errF = StringIO.StringIO()
+
+ def outReceived(self, d):
+ self.outF.write(d)
+
+ def errReceived(self, d):
+ self.errF.write(d)
+
+ def outConnectionLost(self):
+ pass
+
+ def errConnectionLost(self):
+ pass
+
+ def processEnded(self, reason):
+ self.closed = 1
+ if self.endedDeferred is not None:
+ d, self.endedDeferred = self.endedDeferred, None
+ d.callback(None)
+
+
+class PosixProcessBase:
+ """
+ Test running processes.
+ """
+ usePTY = False
+
+ def getCommand(self, commandName):
+ """
+ Return the path of the shell command named C{commandName}, looking at
+ common locations.
+ """
+ if os.path.exists('/bin/%s' % (commandName,)):
+ cmd = '/bin/%s' % (commandName,)
+ elif os.path.exists('/usr/bin/%s' % (commandName,)):
+ cmd = '/usr/bin/%s' % (commandName,)
+ else:
+ raise RuntimeError(
+ "%s not found in /bin or /usr/bin" % (commandName,))
+ return cmd
+
+ def testNormalTermination(self):
+ cmd = self.getCommand('true')
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ reactor.spawnProcess(p, cmd, ['true'], env=None,
+ usePTY=self.usePTY)
+ def check(ignored):
+ p.reason.trap(error.ProcessDone)
+ self.assertEquals(p.reason.value.exitCode, 0)
+ self.assertEquals(p.reason.value.signal, None)
+ d.addCallback(check)
+ return d
+
+
+ def test_abnormalTermination(self):
+ """
+ When a process terminates with a system exit code set to 1,
+ C{processEnded} is called with a L{error.ProcessTerminated} error,
+ the C{exitCode} attribute reflecting the system exit code.
+ """
+ exe = sys.executable
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ reactor.spawnProcess(p, exe, [exe, '-c', 'import sys; sys.exit(1)'],
+ env=None, usePTY=self.usePTY)
+
+ def check(ignored):
+ p.reason.trap(error.ProcessTerminated)
+ self.assertEquals(p.reason.value.exitCode, 1)
+ self.assertEquals(p.reason.value.signal, None)
+ d.addCallback(check)
+ return d
+
+
+ def _testSignal(self, sig):
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_signal.py")
+ d = defer.Deferred()
+ p = SignalProtocol(d, sig)
+ reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None,
+ usePTY=self.usePTY)
+ return d
+
+
+ def test_signalHUP(self):
+ """
+ Sending the SIGHUP signal to a running process interrupts it, and
+ C{processEnded} is called with a L{error.ProcessTerminated} instance
+ with the C{exitCode} set to C{None} and the C{signal} attribute set to
+ C{signal.SIGHUP}. C{os.WTERMSIG} can also be used on the C{status}
+ attribute to extract the signal value.
+ """
+ return self._testSignal('HUP')
+
+
+ def test_signalINT(self):
+ """
+ Sending the SIGINT signal to a running process interrupts it, and
+ C{processEnded} is called with a L{error.ProcessTerminated} instance
+ with the C{exitCode} set to C{None} and the C{signal} attribute set to
+ C{signal.SIGINT}. C{os.WTERMSIG} can also be used on the C{status}
+ attribute to extract the signal value.
+ """
+ return self._testSignal('INT')
+
+
+ def test_signalKILL(self):
+ """
+ Sending the SIGKILL signal to a running process interrupts it, and
+ C{processEnded} is called with a L{error.ProcessTerminated} instance
+ with the C{exitCode} set to C{None} and the C{signal} attribute set to
+ C{signal.SIGKILL}. C{os.WTERMSIG} can also be used on the C{status}
+ attribute to extract the signal value.
+ """
+ return self._testSignal('KILL')
+
+
+ def test_signalTERM(self):
+ """
+ Sending the SIGTERM signal to a running process interrupts it, and
+ C{processEnded} is called with a L{error.ProcessTerminated} instance
+ with the C{exitCode} set to C{None} and the C{signal} attribute set to
+ C{signal.SIGTERM}. C{os.WTERMSIG} can also be used on the C{status}
+ attribute to extract the signal value.
+ """
+ return self._testSignal('TERM')
+
+
+ def test_childSignalHandling(self):
+ """
+ The disposition of signals which are ignored in the parent
+ process is reset to the default behavior for the child
+ process.
+ """
+ # Somewhat arbitrarily select SIGUSR1 here. It satisfies our
+ # requirements that:
+ # - The interpreter not fiddle around with the handler
+ # behind our backs at startup time (this disqualifies
+ # signals like SIGINT and SIGPIPE).
+ # - The default behavior is to exit.
+ #
+ # This lets us send the signal to the child and then verify
+ # that it exits with a status code indicating that it was
+ # indeed the signal which caused it to exit.
+ which = signal.SIGUSR1
+
+ # Ignore the signal in the parent (and make sure we clean it
+ # up).
+ handler = signal.signal(which, signal.SIG_IGN)
+ self.addCleanup(signal.signal, signal.SIGUSR1, handler)
+
+ # Now do the test.
+ return self._testSignal(signal.SIGUSR1)
+
+
+ def test_executionError(self):
+ """
+ Raise an error during execvpe to check error management.
+ """
+ cmd = self.getCommand('false')
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ def buggyexecvpe(command, args, environment):
+ raise RuntimeError("Ouch")
+ oldexecvpe = os.execvpe
+ os.execvpe = buggyexecvpe
+ try:
+ reactor.spawnProcess(p, cmd, ['false'], env=None,
+ usePTY=self.usePTY)
+
+ def check(ignored):
+ errData = "".join(p.errData + p.outData)
+ self.assertIn("Upon execvpe", errData)
+ self.assertIn("Ouch", errData)
+ d.addCallback(check)
+ finally:
+ os.execvpe = oldexecvpe
+ return d
+
+
+ def test_errorInProcessEnded(self):
+ """
+ The handler which reaps a process is removed when the process is
+ reaped, even if the protocol's C{processEnded} method raises an
+ exception.
+ """
+ connected = defer.Deferred()
+ ended = defer.Deferred()
+
+ # This script runs until we disconnect its transport.
+ pythonExecutable = sys.executable
+ scriptPath = util.sibpath(__file__, "process_twisted.py")
+
+ class ErrorInProcessEnded(protocol.ProcessProtocol):
+ """
+ A protocol that raises an error in C{processEnded}.
+ """
+ def makeConnection(self, transport):
+ connected.callback(transport)
+
+ def processEnded(self, reason):
+ reactor.callLater(0, ended.callback, None)
+ raise RuntimeError("Deliberate error")
+
+ # Launch the process.
+ reactor.spawnProcess(
+ ErrorInProcessEnded(), pythonExecutable,
+ [pythonExecutable, scriptPath],
+ env=None, path=None)
+
+ pid = []
+ def cbConnected(transport):
+ pid.append(transport.pid)
+ # There's now a reap process handler registered.
+ self.assertIn(transport.pid, process.reapProcessHandlers)
+
+ # Kill the process cleanly, triggering an error in the protocol.
+ transport.loseConnection()
+ connected.addCallback(cbConnected)
+
+ def checkTerminated(ignored):
+ # The exception was logged.
+ excs = self.flushLoggedErrors(RuntimeError)
+ self.assertEqual(len(excs), 1)
+ # The process is no longer scheduled for reaping.
+ self.assertNotIn(pid[0], process.reapProcessHandlers)
+ ended.addCallback(checkTerminated)
+
+ return ended
+
+
+
+class MockSignal(object):
+ """
+ Neuter L{signal.signal}, but pass other attributes unscathed
+ """
+ def signal(self, sig, action):
+ return signal.getsignal(sig)
+
+ def __getattr__(self, attr):
+ return getattr(signal, attr)
+
+
+class MockOS(object):
+ """
+ The mock OS: overwrite L{os}, L{fcntl} and {sys} functions with fake ones.
+
+ @ivar exited: set to True when C{_exit} is called.
+ @type exited: C{bool}
+
+ @ivar O_RDWR: dumb value faking C{os.O_RDWR}.
+ @type O_RDWR: C{int}
+
+ @ivar O_NOCTTY: dumb value faking C{os.O_NOCTTY}.
+ @type O_NOCTTY: C{int}
+
+ @ivar WNOHANG: dumb value faking C{os.WNOHANG}.
+ @type WNOHANG: C{int}
+
+ @ivar raiseFork: if not C{None}, subsequent calls to fork will raise this
+ object.
+ @type raiseFork: C{NoneType} or C{Exception}
+
+ @ivar raiseExec: if set, subsequent calls to execvpe will raise an error.
+ @type raiseExec: C{bool}
+
+ @ivar fdio: fake file object returned by calls to fdopen.
+ @type fdio: C{StringIO.StringIO}
+
+ @ivar actions: hold names of some actions executed by the object, in order
+ of execution.
+
+ @type actions: C{list} of C{str}
+
+ @ivar closed: keep track of the file descriptor closed.
+ @param closed: C{list} of C{int}
+
+ @ivar child: whether fork return for the child or the parent.
+ @type child: C{bool}
+
+ @ivar pipeCount: count the number of time that C{os.pipe} has been called.
+ @type pipeCount: C{int}
+
+ @ivar raiseWaitPid: if set, subsequent calls to waitpid will raise an
+ the error specified.
+ @type raiseWaitPid: C{None} or a class
+
+ @ivar waitChild: if set, subsequent calls to waitpid will return it.
+ @type waitChild: C{None} or a tuple
+
+ @ivar euid: the uid returned by the fake C{os.geteuid}
+ @type euid: C{int}
+
+ @ivar egid: the gid returned by the fake C{os.getegid}
+ @type egid: C{int}
+
+ @ivar seteuidCalls: stored results of C{os.seteuid} calls.
+ @type seteuidCalls: C{list}
+
+ @ivar setegidCalls: stored results of C{os.setegid} calls.
+ @type setegidCalls: C{list}
+
+ @ivar path: the path returned by C{os.path.expanduser}.
+ @type path: C{str}
+ """
+ exited = False
+ raiseExec = False
+ fdio = None
+ child = True
+ raiseWaitPid = None
+ raiseFork = None
+ waitChild = None
+ euid = 0
+ egid = 0
+ path = None
+
+ def __init__(self):
+ """
+ Initialize data structures.
+ """
+ self.actions = []
+ self.closed = []
+ self.pipeCount = 0
+ self.O_RDWR = -1
+ self.O_NOCTTY = -2
+ self.WNOHANG = -4
+ self.WEXITSTATUS = lambda x: 0
+ self.WIFEXITED = lambda x: 1
+ self.seteuidCalls = []
+ self.setegidCalls = []
+
+
+ def open(self, dev, flags):
+ """
+ Fake C{os.open}. Return a non fd number to be sure it's not used
+ elsewhere.
+ """
+ return -3
+
+
+ def fdopen(self, fd, flag):
+ """
+ Fake C{os.fdopen}. Return a StringIO object whose content can be tested
+ later via C{self.fdio}.
+ """
+ self.fdio = StringIO.StringIO()
+ return self.fdio
+
+
+ def setsid(self):
+ """
+ Fake C{os.setsid}. Do nothing.
+ """
+
+
+ def fork(self):
+ """
+ Fake C{os.fork}. Save the action in C{self.actions}, and return 0 if
+ C{self.child} is set, or a dumb number.
+ """
+ self.actions.append(('fork', gc.isenabled()))
+ if self.raiseFork is not None:
+ raise self.raiseFork
+ elif self.child:
+ # Child result is 0
+ return 0
+ else:
+ return 21
+
+
+ def close(self, fd):
+ """
+ Fake C{os.close}, saving the closed fd in C{self.closed}.
+ """
+ self.closed.append(fd)
+
+
+ def dup2(self, fd1, fd2):
+ """
+ Fake C{os.dup2}. Do nothing.
+ """
+
+
+ def write(self, fd, data):
+ """
+ Fake C{os.write}. Do nothing.
+ """
+
+
+ def execvpe(self, command, args, env):
+ """
+ Fake C{os.execvpe}. Save the action, and raise an error if
+ C{self.raiseExec} is set.
+ """
+ self.actions.append('exec')
+ if self.raiseExec:
+ raise RuntimeError("Bar")
+
+
+ def pipe(self):
+ """
+ Fake C{os.pipe}. Return non fd numbers to be sure it's not used
+ elsewhere, and increment C{self.pipeCount}. This is used to uniquify
+ the result.
+ """
+ self.pipeCount += 1
+ return - 2 * self.pipeCount + 1, - 2 * self.pipeCount
+
+
+ def ttyname(self, fd):
+ """
+ Fake C{os.ttyname}. Return a dumb string.
+ """
+ return "foo"
+
+
+ def _exit(self, code):
+ """
+ Fake C{os._exit}. Save the action, set the C{self.exited} flag, and
+ raise C{SystemError}.
+ """
+ self.actions.append('exit')
+ self.exited = True
+ # Don't forget to raise an error, or you'll end up in parent
+ # code path.
+ raise SystemError()
+
+
+ def ioctl(self, fd, flags, arg):
+ """
+ Override C{fcntl.ioctl}. Do nothing.
+ """
+
+
+ def setNonBlocking(self, fd):
+ """
+ Override C{fdesc.setNonBlocking}. Do nothing.
+ """
+
+
+ def waitpid(self, pid, options):
+ """
+ Override C{os.waitpid}. Return values meaning that the child process
+ has exited, save executed action.
+ """
+ self.actions.append('waitpid')
+ if self.raiseWaitPid is not None:
+ raise self.raiseWaitPid
+ if self.waitChild is not None:
+ return self.waitChild
+ return 1, 0
+
+
+ def settrace(self, arg):
+ """
+ Override C{sys.settrace} to keep coverage working.
+ """
+
+
+ def getgid(self):
+ """
+ Override C{os.getgid}. Return a dumb number.
+ """
+ return 1235
+
+
+ def getuid(self):
+ """
+ Override C{os.getuid}. Return a dumb number.
+ """
+ return 1237
+
+
+ def setuid(self, val):
+ """
+ Override C{os.setuid}. Do nothing.
+ """
+ self.actions.append(('setuid', val))
+
+
+ def setgid(self, val):
+ """
+ Override C{os.setgid}. Do nothing.
+ """
+ self.actions.append(('setgid', val))
+
+
+ def setregid(self, val1, val2):
+ """
+ Override C{os.setregid}. Do nothing.
+ """
+ self.actions.append(('setregid', val1, val2))
+
+
+ def setreuid(self, val1, val2):
+ """
+ Override C{os.setreuid}. Save the action.
+ """
+ self.actions.append(('setreuid', val1, val2))
+
+
+ def switchUID(self, uid, gid):
+ """
+ Override C{util.switchuid}. Save the action.
+ """
+ self.actions.append(('switchuid', uid, gid))
+
+
+ def openpty(self):
+ """
+ Override C{pty.openpty}, returning fake file descriptors.
+ """
+ return -12, -13
+
+
+ def geteuid(self):
+ """
+ Mock C{os.geteuid}, returning C{self.euid} instead.
+ """
+ return self.euid
+
+
+ def getegid(self):
+ """
+ Mock C{os.getegid}, returning C{self.egid} instead.
+ """
+ return self.egid
+
+
+ def seteuid(self, egid):
+ """
+ Mock C{os.seteuid}, store result.
+ """
+ self.seteuidCalls.append(egid)
+
+
+ def setegid(self, egid):
+ """
+ Mock C{os.setegid}, store result.
+ """
+ self.setegidCalls.append(egid)
+
+
+ def expanduser(self, path):
+ """
+ Mock C{os.path.expanduser}.
+ """
+ return self.path
+
+
+ def getpwnam(self, user):
+ """
+ Mock C{pwd.getpwnam}.
+ """
+ return 0, 0, 1, 2
+
+
+
+if process is not None:
+ class DumbProcessWriter(process.ProcessWriter):
+ """
+ A fake L{process.ProcessWriter} used for tests.
+ """
+
+ def startReading(self):
+ """
+ Here's the faking: don't do anything here.
+ """
+
+
+
+ class DumbProcessReader(process.ProcessReader):
+ """
+ A fake L{process.ProcessReader} used for tests.
+ """
+
+ def startReading(self):
+ """
+ Here's the faking: don't do anything here.
+ """
+
+
+
+ class DumbPTYProcess(process.PTYProcess):
+ """
+ A fake L{process.PTYProcess} used for tests.
+ """
+
+ def startReading(self):
+ """
+ Here's the faking: don't do anything here.
+ """
+
+
+
+class MockProcessTestCase(unittest.TestCase):
+ """
+ Mock a process runner to test forked child code path.
+ """
+ if process is None:
+ skip = "twisted.internet.process is never used on Windows"
+
+ def setUp(self):
+ """
+ Replace L{process} os, fcntl, sys, switchUID, fdesc and pty modules
+ with the mock class L{MockOS}.
+ """
+ if gc.isenabled():
+ self.addCleanup(gc.enable)
+ else:
+ self.addCleanup(gc.disable)
+ self.mockos = MockOS()
+ self.mockos.euid = 1236
+ self.mockos.egid = 1234
+ self.patch(process, "os", self.mockos)
+ self.patch(process, "fcntl", self.mockos)
+ self.patch(process, "sys", self.mockos)
+ self.patch(process, "switchUID", self.mockos.switchUID)
+ self.patch(process, "fdesc", self.mockos)
+ self.patch(process.Process, "processReaderFactory", DumbProcessReader)
+ self.patch(process.Process, "processWriterFactory", DumbProcessWriter)
+ self.patch(process, "pty", self.mockos)
+
+ self.mocksig = MockSignal()
+ self.patch(process, "signal", self.mocksig)
+
+
+ def tearDown(self):
+ """
+ Reset processes registered for reap.
+ """
+ process.reapProcessHandlers = {}
+
+
+ def test_mockFork(self):
+ """
+ Test a classic spawnProcess. Check the path of the client code:
+ fork, exec, exit.
+ """
+ gc.enable()
+
+ cmd = '/mock/ouch'
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ try:
+ reactor.spawnProcess(p, cmd, ['ouch'], env=None,
+ usePTY=False)
+ except SystemError:
+ self.assert_(self.mockos.exited)
+ self.assertEquals(
+ self.mockos.actions, [("fork", False), "exec", "exit"])
+ else:
+ self.fail("Should not be here")
+
+ # It should leave the garbage collector disabled.
+ self.assertFalse(gc.isenabled())
+
+
+ def _mockForkInParentTest(self):
+ """
+ Assert that in the main process, spawnProcess disables the garbage
+ collector, calls fork, closes the pipe file descriptors it created for
+ the child process, and calls waitpid.
+ """
+ self.mockos.child = False
+ cmd = '/mock/ouch'
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ reactor.spawnProcess(p, cmd, ['ouch'], env=None,
+ usePTY=False)
+ # It should close the first read pipe, and the 2 last write pipes
+ self.assertEqual(set(self.mockos.closed), set([-1, -4, -6]))
+ self.assertEquals(self.mockos.actions, [("fork", False), "waitpid"])
+
+
+ def test_mockForkInParentGarbageCollectorEnabled(self):
+ """
+ The garbage collector should be enabled when L{reactor.spawnProcess}
+ returns if it was initially enabled.
+
+ @see L{_mockForkInParentTest}
+ """
+ gc.enable()
+ self._mockForkInParentTest()
+ self.assertTrue(gc.isenabled())
+
+
+ def test_mockForkInParentGarbageCollectorDisabled(self):
+ """
+ The garbage collector should be disabled when L{reactor.spawnProcess}
+ returns if it was initially disabled.
+
+ @see L{_mockForkInParentTest}
+ """
+ gc.disable()
+ self._mockForkInParentTest()
+ self.assertFalse(gc.isenabled())
+
+
+ def test_mockForkTTY(self):
+ """
+ Test a TTY spawnProcess: check the path of the client code:
+ fork, exec, exit.
+ """
+ cmd = '/mock/ouch'
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ try:
+ reactor.spawnProcess(p, cmd, ['ouch'], env=None,
+ usePTY=True)
+ except SystemError:
+ self.assert_(self.mockos.exited)
+ self.assertEquals(
+ self.mockos.actions, [("fork", False), "exec", "exit"])
+ else:
+ self.fail("Should not be here")
+
+
+ def _mockWithForkError(self):
+ """
+ Assert that if the fork call fails, no other process setup calls are
+ made and that spawnProcess raises the exception fork raised.
+ """
+ self.mockos.raiseFork = OSError(errno.EAGAIN, None)
+ protocol = TrivialProcessProtocol(None)
+ self.assertRaises(OSError, reactor.spawnProcess, protocol, None)
+ self.assertEqual(self.mockos.actions, [("fork", False)])
+
+
+ def test_mockWithForkErrorGarbageCollectorEnabled(self):
+ """
+ The garbage collector should be enabled when L{reactor.spawnProcess}
+ raises because L{os.fork} raised, if it was initially enabled.
+ """
+ gc.enable()
+ self._mockWithForkError()
+ self.assertTrue(gc.isenabled())
+
+
+ def test_mockWithForkErrorGarbageCollectorDisabled(self):
+ """
+ The garbage collector should be disabled when
+ L{reactor.spawnProcess} raises because L{os.fork} raised, if it was
+ initially disabled.
+ """
+ gc.disable()
+ self._mockWithForkError()
+ self.assertFalse(gc.isenabled())
+
+
+ def test_mockForkErrorCloseFDs(self):
+ """
+ When C{os.fork} raises an exception, the file descriptors created
+ before are closed and don't leak.
+ """
+ self._mockWithForkError()
+ self.assertEqual(set(self.mockos.closed), set([-1, -4, -6, -2, -3, -5]))
+
+
+ def test_mockForkErrorGivenFDs(self):
+ """
+ When C{os.forks} raises an exception and that file descriptors have
+ been specified with the C{childFDs} arguments of
+ L{reactor.spawnProcess}, they are not closed.
+ """
+ self.mockos.raiseFork = OSError(errno.EAGAIN, None)
+ protocol = TrivialProcessProtocol(None)
+ self.assertRaises(OSError, reactor.spawnProcess, protocol, None,
+ childFDs={0: -10, 1: -11, 2: -13})
+ self.assertEqual(self.mockos.actions, [("fork", False)])
+ self.assertEqual(self.mockos.closed, [])
+
+ # We can also put "r" or "w" to let twisted create the pipes
+ self.assertRaises(OSError, reactor.spawnProcess, protocol, None,
+ childFDs={0: "r", 1: -11, 2: -13})
+ self.assertEqual(set(self.mockos.closed), set([-1, -2]))
+
+
+ def test_mockForkErrorClosePTY(self):
+ """
+ When C{os.fork} raises an exception, the file descriptors created by
+ C{pty.openpty} are closed and don't leak, when C{usePTY} is set to
+ C{True}.
+ """
+ self.mockos.raiseFork = OSError(errno.EAGAIN, None)
+ protocol = TrivialProcessProtocol(None)
+ self.assertRaises(OSError, reactor.spawnProcess, protocol, None,
+ usePTY=True)
+ self.assertEqual(self.mockos.actions, [("fork", False)])
+ self.assertEqual(set(self.mockos.closed), set([-12, -13]))
+
+
+ def test_mockForkErrorPTYGivenFDs(self):
+ """
+ If a tuple is passed to C{usePTY} to specify slave and master file
+ descriptors and that C{os.fork} raises an exception, these file
+ descriptors aren't closed.
+ """
+ self.mockos.raiseFork = OSError(errno.EAGAIN, None)
+ protocol = TrivialProcessProtocol(None)
+ self.assertRaises(OSError, reactor.spawnProcess, protocol, None,
+ usePTY=(-20, -21, 'foo'))
+ self.assertEqual(self.mockos.actions, [("fork", False)])
+ self.assertEqual(self.mockos.closed, [])
+
+
+ def test_mockWithExecError(self):
+ """
+ Spawn a process but simulate an error during execution in the client
+ path: C{os.execvpe} raises an error. It should close all the standard
+ fds, try to print the error encountered, and exit cleanly.
+ """
+ cmd = '/mock/ouch'
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ self.mockos.raiseExec = True
+ try:
+ reactor.spawnProcess(p, cmd, ['ouch'], env=None,
+ usePTY=False)
+ except SystemError:
+ self.assert_(self.mockos.exited)
+ self.assertEquals(
+ self.mockos.actions, [("fork", False), "exec", "exit"])
+ # Check that fd have been closed
+ self.assertIn(0, self.mockos.closed)
+ self.assertIn(1, self.mockos.closed)
+ self.assertIn(2, self.mockos.closed)
+ # Check content of traceback
+ self.assertIn("RuntimeError: Bar", self.mockos.fdio.getvalue())
+ else:
+ self.fail("Should not be here")
+
+
+ def test_mockSetUid(self):
+ """
+ Try creating a process with setting its uid: it's almost the same path
+ as the standard path, but with a C{switchUID} call before the exec.
+ """
+ cmd = '/mock/ouch'
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ try:
+ reactor.spawnProcess(p, cmd, ['ouch'], env=None,
+ usePTY=False, uid=8080)
+ except SystemError:
+ self.assert_(self.mockos.exited)
+ self.assertEquals(self.mockos.actions,
+ [('setuid', 0), ('setgid', 0), ('fork', False),
+ ('switchuid', 8080, 1234), 'exec', 'exit'])
+ else:
+ self.fail("Should not be here")
+
+
+ def test_mockSetUidInParent(self):
+ """
+ Try creating a process with setting its uid, in the parent path: it
+ should switch to root before fork, then restore initial uid/gids.
+ """
+ self.mockos.child = False
+ cmd = '/mock/ouch'
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ reactor.spawnProcess(p, cmd, ['ouch'], env=None,
+ usePTY=False, uid=8080)
+ self.assertEquals(self.mockos.actions,
+ [('setuid', 0), ('setgid', 0), ('fork', False),
+ ('setregid', 1235, 1234), ('setreuid', 1237, 1236), 'waitpid'])
+
+
+ def test_mockPTYSetUid(self):
+ """
+ Try creating a PTY process with setting its uid: it's almost the same
+ path as the standard path, but with a C{switchUID} call before the
+ exec.
+ """
+ cmd = '/mock/ouch'
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ try:
+ reactor.spawnProcess(p, cmd, ['ouch'], env=None,
+ usePTY=True, uid=8081)
+ except SystemError:
+ self.assert_(self.mockos.exited)
+ self.assertEquals(self.mockos.actions,
+ [('setuid', 0), ('setgid', 0), ('fork', False),
+ ('switchuid', 8081, 1234), 'exec', 'exit'])
+ else:
+ self.fail("Should not be here")
+
+
+ def test_mockPTYSetUidInParent(self):
+ """
+ Try creating a PTY process with setting its uid, in the parent path: it
+ should switch to root before fork, then restore initial uid/gids.
+ """
+ self.mockos.child = False
+ cmd = '/mock/ouch'
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ oldPTYProcess = process.PTYProcess
+ try:
+ process.PTYProcess = DumbPTYProcess
+ reactor.spawnProcess(p, cmd, ['ouch'], env=None,
+ usePTY=True, uid=8080)
+ finally:
+ process.PTYProcess = oldPTYProcess
+ self.assertEquals(self.mockos.actions,
+ [('setuid', 0), ('setgid', 0), ('fork', False),
+ ('setregid', 1235, 1234), ('setreuid', 1237, 1236), 'waitpid'])
+
+
+ def test_mockWithWaitError(self):
+ """
+ Test that reapProcess logs errors raised.
+ """
+ self.mockos.child = False
+ cmd = '/mock/ouch'
+ self.mockos.waitChild = (0, 0)
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ proc = reactor.spawnProcess(p, cmd, ['ouch'], env=None,
+ usePTY=False)
+ self.assertEquals(self.mockos.actions, [("fork", False), "waitpid"])
+
+ self.mockos.raiseWaitPid = OSError()
+ proc.reapProcess()
+ errors = self.flushLoggedErrors()
+ self.assertEquals(len(errors), 1)
+ errors[0].trap(OSError)
+
+
+ def test_mockErrorECHILDInReapProcess(self):
+ """
+ Test that reapProcess doesn't log anything when waitpid raises a
+ C{OSError} with errno C{ECHILD}.
+ """
+ self.mockos.child = False
+ cmd = '/mock/ouch'
+ self.mockos.waitChild = (0, 0)
+
+ d = defer.Deferred()
+ p = TrivialProcessProtocol(d)
+ proc = reactor.spawnProcess(p, cmd, ['ouch'], env=None,
+ usePTY=False)
+ self.assertEquals(self.mockos.actions, [("fork", False), "waitpid"])
+
+ self.mockos.raiseWaitPid = OSError()
+ self.mockos.raiseWaitPid.errno = errno.ECHILD
+ # This should not produce any errors
+ proc.reapProcess()
+
+
+ def test_mockErrorInPipe(self):
+ """
+ If C{os.pipe} raises an exception after some pipes where created, the
+ created pipes are closed and don't leak.
+ """
+ pipes = [-1, -2, -3, -4]
+ def pipe():
+ try:
+ return pipes.pop(0), pipes.pop(0)
+ except IndexError:
+ raise OSError()
+ self.mockos.pipe = pipe
+ protocol = TrivialProcessProtocol(None)
+ self.assertRaises(OSError, reactor.spawnProcess, protocol, None)
+ self.assertEqual(self.mockos.actions, [])
+ self.assertEqual(set(self.mockos.closed), set([-4, -3, -2, -1]))
+
+
+ def test_mockErrorInForkRestoreUID(self):
+ """
+ If C{os.fork} raises an exception and a UID change has been made, the
+ previous UID and GID are restored.
+ """
+ self.mockos.raiseFork = OSError(errno.EAGAIN, None)
+ protocol = TrivialProcessProtocol(None)
+ self.assertRaises(OSError, reactor.spawnProcess, protocol, None,
+ uid=8080)
+ self.assertEqual(self.mockos.actions,
+ [('setuid', 0), ('setgid', 0), ("fork", False),
+ ('setregid', 1235, 1234), ('setreuid', 1237, 1236)])
+
+
+
+class PosixProcessTestCase(unittest.TestCase, PosixProcessBase):
+ # add two non-pty test cases
+
+ def testStderr(self):
+ # we assume there is no file named ZZXXX..., both in . and in /tmp
+ cmd = self.getCommand('ls')
+
+ p = Accumulator()
+ d = p.endedDeferred = defer.Deferred()
+ reactor.spawnProcess(p, cmd,
+ [cmd,
+ "ZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"],
+ env=None, path="/tmp",
+ usePTY=self.usePTY)
+
+ def processEnded(ign):
+ self.assertEquals(lsOut, p.errF.getvalue())
+ return d.addCallback(processEnded)
+
+ def testProcess(self):
+ cmd = self.getCommand('gzip')
+ s = "there's no place like home!\n" * 3
+ p = Accumulator()
+ d = p.endedDeferred = defer.Deferred()
+ reactor.spawnProcess(p, cmd, [cmd, "-c"], env=None, path="/tmp",
+ usePTY=self.usePTY)
+ p.transport.write(s)
+ p.transport.closeStdin()
+
+ def processEnded(ign):
+ f = p.outF
+ f.seek(0, 0)
+ gf = gzip.GzipFile(fileobj=f)
+ self.assertEquals(gf.read(), s)
+ return d.addCallback(processEnded)
+
+
+
+class PosixProcessTestCasePTY(unittest.TestCase, PosixProcessBase):
+ """
+ Just like PosixProcessTestCase, but use ptys instead of pipes.
+ """
+ usePTY = True
+ # PTYs only offer one input and one output. What still makes sense?
+ # testNormalTermination
+ # test_abnormalTermination
+ # testSignal
+ # testProcess, but not without p.transport.closeStdin
+ # might be solveable: TODO: add test if so
+
+ def testOpeningTTY(self):
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_tty.py")
+ p = Accumulator()
+ d = p.endedDeferred = defer.Deferred()
+ reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None,
+ path=None, usePTY=self.usePTY)
+ p.transport.write("hello world!\n")
+
+ def processEnded(ign):
+ self.assertRaises(
+ error.ProcessExitedAlready, p.transport.signalProcess, 'HUP')
+ self.assertEquals(
+ p.outF.getvalue(),
+ "hello world!\r\nhello world!\r\n",
+ "Error message from process_tty follows:\n\n%s\n\n" % p.outF.getvalue())
+ return d.addCallback(processEnded)
+
+
+ def testBadArgs(self):
+ pyExe = sys.executable
+ pyArgs = [pyExe, "-u", "-c", "print 'hello'"]
+ p = Accumulator()
+ self.assertRaises(ValueError, reactor.spawnProcess, p, pyExe, pyArgs,
+ usePTY=1, childFDs={1:'r'})
+
+
+
+class Win32SignalProtocol(SignalProtocol):
+ """
+ A win32-specific process protocol that handles C{processEnded}
+ differently: processes should exit with exit code 1.
+ """
+
+ def processEnded(self, reason):
+ """
+ Callback C{self.deferred} with C{None} if C{reason} is a
+ L{error.ProcessTerminated} failure with C{exitCode} set to 1.
+ Otherwise, errback with a C{ValueError} describing the problem.
+ """
+ if not reason.check(error.ProcessTerminated):
+ return self.deferred.errback(
+ ValueError("wrong termination: %s" % (reason,)))
+ v = reason.value
+ if v.exitCode != 1:
+ return self.deferred.errback(
+ ValueError("Wrong exit code: %s" % (reason.exitCode,)))
+ self.deferred.callback(None)
+
+
+
+class Win32ProcessTestCase(unittest.TestCase):
+ """
+ Test process programs that are packaged with twisted.
+ """
+
+ def testStdinReader(self):
+ pyExe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_stdinreader.py")
+ p = Accumulator()
+ d = p.endedDeferred = defer.Deferred()
+ reactor.spawnProcess(p, pyExe, [pyExe, "-u", scriptPath], env=None,
+ path=None)
+ p.transport.write("hello, world")
+ p.transport.closeStdin()
+
+ def processEnded(ign):
+ self.assertEquals(p.errF.getvalue(), "err\nerr\n")
+ self.assertEquals(p.outF.getvalue(), "out\nhello, world\nout\n")
+ return d.addCallback(processEnded)
+
+
+ def testBadArgs(self):
+ pyExe = sys.executable
+ pyArgs = [pyExe, "-u", "-c", "print 'hello'"]
+ p = Accumulator()
+ self.assertRaises(ValueError,
+ reactor.spawnProcess, p, pyExe, pyArgs, uid=1)
+ self.assertRaises(ValueError,
+ reactor.spawnProcess, p, pyExe, pyArgs, gid=1)
+ self.assertRaises(ValueError,
+ reactor.spawnProcess, p, pyExe, pyArgs, usePTY=1)
+ self.assertRaises(ValueError,
+ reactor.spawnProcess, p, pyExe, pyArgs, childFDs={1:'r'})
+
+
+ def _testSignal(self, sig):
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_signal.py")
+ d = defer.Deferred()
+ p = Win32SignalProtocol(d, sig)
+ reactor.spawnProcess(p, exe, [exe, "-u", scriptPath], env=None)
+ return d
+
+
+ def test_signalTERM(self):
+ """
+ Sending the SIGTERM signal terminates a created process, and
+ C{processEnded} is called with a L{error.ProcessTerminated} instance
+ with the C{exitCode} attribute set to 1.
+ """
+ return self._testSignal('TERM')
+
+
+ def test_signalINT(self):
+ """
+ Sending the SIGINT signal terminates a created process, and
+ C{processEnded} is called with a L{error.ProcessTerminated} instance
+ with the C{exitCode} attribute set to 1.
+ """
+ return self._testSignal('INT')
+
+
+ def test_signalKILL(self):
+ """
+ Sending the SIGKILL signal terminates a created process, and
+ C{processEnded} is called with a L{error.ProcessTerminated} instance
+ with the C{exitCode} attribute set to 1.
+ """
+ return self._testSignal('KILL')
+
+
+ def test_closeHandles(self):
+ """
+ The win32 handles should be properly closed when the process exits.
+ """
+ import win32api
+
+ connected = defer.Deferred()
+ ended = defer.Deferred()
+
+ class SimpleProtocol(protocol.ProcessProtocol):
+ """
+ A protocol that fires deferreds when connected and disconnected.
+ """
+ def makeConnection(self, transport):
+ connected.callback(transport)
+
+ def processEnded(self, reason):
+ ended.callback(None)
+
+ p = SimpleProtocol()
+
+ pyExe = sys.executable
+ pyArgs = [pyExe, "-u", "-c", "print 'hello'"]
+ proc = reactor.spawnProcess(p, pyExe, pyArgs)
+
+ def cbConnected(transport):
+ self.assertIdentical(transport, proc)
+ # perform a basic validity test on the handles
+ win32api.GetHandleInformation(proc.hProcess)
+ win32api.GetHandleInformation(proc.hThread)
+ # And save their values for later
+ self.hProcess = proc.hProcess
+ self.hThread = proc.hThread
+ connected.addCallback(cbConnected)
+
+ def checkTerminated(ignored):
+ # The attributes on the process object must be reset...
+ self.assertIdentical(proc.pid, None)
+ self.assertIdentical(proc.hProcess, None)
+ self.assertIdentical(proc.hThread, None)
+ # ...and the handles must be closed.
+ self.assertRaises(win32api.error,
+ win32api.GetHandleInformation, self.hProcess)
+ self.assertRaises(win32api.error,
+ win32api.GetHandleInformation, self.hThread)
+ ended.addCallback(checkTerminated)
+
+ return defer.gatherResults([connected, ended])
+
+
+
+class Dumbwin32procPidTest(unittest.TestCase):
+ """
+ Simple test for the pid attribute of Process on win32.
+ """
+
+ def test_pid(self):
+ """
+ Launch process with mock win32process. The only mock aspect of this
+ module is that the pid of the process created will always be 42.
+ """
+ from twisted.internet import _dumbwin32proc
+ from twisted.test import mock_win32process
+ self.patch(_dumbwin32proc, "win32process", mock_win32process)
+ exe = sys.executable
+ scriptPath = util.sibpath(__file__, "process_cmdline.py")
+
+ d = defer.Deferred()
+ processProto = TrivialProcessProtocol(d)
+ comspec = str(os.environ["COMSPEC"])
+ cmd = [comspec, "/c", exe, scriptPath]
+
+ p = _dumbwin32proc.Process(reactor,
+ processProto,
+ None,
+ cmd,
+ {},
+ None)
+ self.assertEquals(42, p.pid)
+ self.assertEquals("<Process pid=42>", repr(p))
+
+ def pidCompleteCb(result):
+ self.assertEquals(None, p.pid)
+ return d.addCallback(pidCompleteCb)
+
+
+
+class UtilTestCase(unittest.TestCase):
+ """
+ Tests for process-related helper functions (currently only
+ L{procutils.which}.
+ """
+ def setUp(self):
+ """
+ Create several directories and files, some of which are executable
+ and some of which are not. Save the current PATH setting.
+ """
+ j = os.path.join
+
+ base = self.mktemp()
+
+ self.foo = j(base, "foo")
+ self.baz = j(base, "baz")
+ self.foobar = j(self.foo, "bar")
+ self.foobaz = j(self.foo, "baz")
+ self.bazfoo = j(self.baz, "foo")
+ self.bazbar = j(self.baz, "bar")
+
+ for d in self.foobar, self.foobaz, self.bazfoo, self.bazbar:
+ os.makedirs(d)
+
+ for name, mode in [(j(self.foobaz, "executable"), 0700),
+ (j(self.foo, "executable"), 0700),
+ (j(self.bazfoo, "executable"), 0700),
+ (j(self.bazfoo, "executable.bin"), 0700),
+ (j(self.bazbar, "executable"), 0)]:
+ f = file(name, "w")
+ f.close()
+ os.chmod(name, mode)
+
+ self.oldPath = os.environ.get('PATH', None)
+ os.environ['PATH'] = os.pathsep.join((
+ self.foobar, self.foobaz, self.bazfoo, self.bazbar))
+
+
+ def tearDown(self):
+ """
+ Restore the saved PATH setting, and set all created files readable
+ again so that they can be deleted easily.
+ """
+ os.chmod(os.path.join(self.bazbar, "executable"), stat.S_IWUSR)
+ if self.oldPath is None:
+ try:
+ del os.environ['PATH']
+ except KeyError:
+ pass
+ else:
+ os.environ['PATH'] = self.oldPath
+
+
+ def test_whichWithoutPATH(self):
+ """
+ Test that if C{os.environ} does not have a C{'PATH'} key,
+ L{procutils.which} returns an empty list.
+ """
+ del os.environ['PATH']
+ self.assertEqual(procutils.which("executable"), [])
+
+
+ def testWhich(self):
+ j = os.path.join
+ paths = procutils.which("executable")
+ expectedPaths = [j(self.foobaz, "executable"),
+ j(self.bazfoo, "executable")]
+ if runtime.platform.isWindows():
+ expectedPaths.append(j(self.bazbar, "executable"))
+ self.assertEquals(paths, expectedPaths)
+
+
+ def testWhichPathExt(self):
+ j = os.path.join
+ old = os.environ.get('PATHEXT', None)
+ os.environ['PATHEXT'] = os.pathsep.join(('.bin', '.exe', '.sh'))
+ try:
+ paths = procutils.which("executable")
+ finally:
+ if old is None:
+ del os.environ['PATHEXT']
+ else:
+ os.environ['PATHEXT'] = old
+ expectedPaths = [j(self.foobaz, "executable"),
+ j(self.bazfoo, "executable"),
+ j(self.bazfoo, "executable.bin")]
+ if runtime.platform.isWindows():
+ expectedPaths.append(j(self.bazbar, "executable"))
+ self.assertEquals(paths, expectedPaths)
+
+
+
+class ClosingPipesProcessProtocol(protocol.ProcessProtocol):
+ output = ''
+ errput = ''
+
+ def __init__(self, outOrErr):
+ self.deferred = defer.Deferred()
+ self.outOrErr = outOrErr
+
+ def processEnded(self, reason):
+ self.deferred.callback(reason)
+
+ def outReceived(self, data):
+ self.output += data
+
+ def errReceived(self, data):
+ self.errput += data
+
+
+class ClosingPipes(unittest.TestCase):
+
+ def doit(self, fd):
+ p = ClosingPipesProcessProtocol(True)
+ p.deferred.addCallbacks(
+ callback=lambda _: self.fail("I wanted an errback."),
+ errback=self._endProcess, errbackArgs=(p,))
+ reactor.spawnProcess(p, sys.executable,
+ [sys.executable, '-u', '-c',
+ r'raw_input(); import sys, os; os.write(%d, "foo\n"); sys.exit(42)' % fd],
+ env=None)
+ p.transport.write('go\n')
+
+ if fd == 1:
+ p.transport.closeStdout()
+ elif fd == 2:
+ p.transport.closeStderr()
+ else:
+ raise RuntimeError
+
+ # make the buggy case not hang
+ p.transport.closeStdin()
+ return p.deferred
+
+ def _endProcess(self, reason, p):
+ self.failIf(reason.check(error.ProcessDone),
+ 'Child should fail due to EPIPE.')
+ reason.trap(error.ProcessTerminated)
+ # child must not get past that write without raising
+ self.failIfEqual(reason.value.exitCode, 42,
+ 'process reason was %r' % reason)
+ self.failUnlessEqual(p.output, '')
+ return p.errput
+
+ def test_stdout(self):
+ """ProcessProtocol.transport.closeStdout actually closes the pipe."""
+ d = self.doit(1)
+ def _check(errput):
+ self.failIfEqual(errput.find('OSError'), -1)
+ if runtime.platform.getType() != 'win32':
+ self.failIfEqual(errput.find('Broken pipe'), -1)
+ d.addCallback(_check)
+ return d
+
+ def test_stderr(self):
+ """ProcessProtocol.transport.closeStderr actually closes the pipe."""
+ d = self.doit(2)
+ def _check(errput):
+ # there should be no stderr open, so nothing for it to
+ # write the error to.
+ self.failUnlessEqual(errput, '')
+ d.addCallback(_check)
+ return d
+
+
+skipMessage = "wrong platform or reactor doesn't support IReactorProcess"
+if (runtime.platform.getType() != 'posix') or (not interfaces.IReactorProcess(reactor, None)):
+ PosixProcessTestCase.skip = skipMessage
+ PosixProcessTestCasePTY.skip = skipMessage
+ TestTwoProcessesPosix.skip = skipMessage
+ FDTest.skip = skipMessage
+else:
+ # do this before running the tests: it uses SIGCHLD and stuff internally
+ lsOut = popen2.popen3("/bin/ls ZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")[2].read()
+
+if (runtime.platform.getType() != 'win32') or (not interfaces.IReactorProcess(reactor, None)):
+ Win32ProcessTestCase.skip = skipMessage
+ TestTwoProcessesNonPosix.skip = skipMessage
+ Dumbwin32procPidTest.skip = skipMessage
+
+if not interfaces.IReactorProcess(reactor, None):
+ ProcessTestCase.skip = skipMessage
+ ClosingPipes.skip = skipMessage
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_protocols.py b/vendor/Twisted-10.0.0/twisted/test/test_protocols.py
new file mode 100644
index 0000000000..4b540ccd3b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_protocols.py
@@ -0,0 +1,811 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for twisted.protocols package.
+"""
+
+import struct
+
+from twisted.trial import unittest
+from twisted.protocols import basic, wire, portforward
+from twisted.internet import reactor, protocol, defer, task, error
+from twisted.test import proto_helpers
+
+
+class LineTester(basic.LineReceiver):
+ """
+ A line receiver that parses data received and make actions on some tokens.
+
+ @type delimiter: C{str}
+ @ivar delimiter: character used between received lines.
+ @type MAX_LENGTH: C{int}
+ @ivar MAX_LENGTH: size of a line when C{lineLengthExceeded} will be called.
+ @type clock: L{twisted.internet.task.Clock}
+ @ivar clock: clock simulating reactor callLater. Pass it to constructor if
+ you want to use the pause/rawpause functionalities.
+ """
+
+ delimiter = '\n'
+ MAX_LENGTH = 64
+
+ def __init__(self, clock=None):
+ """
+ If given, use a clock to make callLater calls.
+ """
+ self.clock = clock
+
+ def connectionMade(self):
+ """
+ Create/clean data received on connection.
+ """
+ self.received = []
+
+ def lineReceived(self, line):
+ """
+ Receive line and make some action for some tokens: pause, rawpause,
+ stop, len, produce, unproduce.
+ """
+ self.received.append(line)
+ if line == '':
+ self.setRawMode()
+ elif line == 'pause':
+ self.pauseProducing()
+ self.clock.callLater(0, self.resumeProducing)
+ elif line == 'rawpause':
+ self.pauseProducing()
+ self.setRawMode()
+ self.received.append('')
+ self.clock.callLater(0, self.resumeProducing)
+ elif line == 'stop':
+ self.stopProducing()
+ elif line[:4] == 'len ':
+ self.length = int(line[4:])
+ elif line.startswith('produce'):
+ self.transport.registerProducer(self, False)
+ elif line.startswith('unproduce'):
+ self.transport.unregisterProducer()
+
+ def rawDataReceived(self, data):
+ """
+ Read raw data, until the quantity specified by a previous 'len' line is
+ reached.
+ """
+ data, rest = data[:self.length], data[self.length:]
+ self.length = self.length - len(data)
+ self.received[-1] = self.received[-1] + data
+ if self.length == 0:
+ self.setLineMode(rest)
+
+ def lineLengthExceeded(self, line):
+ """
+ Adjust line mode when long lines received.
+ """
+ if len(line) > self.MAX_LENGTH + 1:
+ self.setLineMode(line[self.MAX_LENGTH + 1:])
+
+
+class LineOnlyTester(basic.LineOnlyReceiver):
+ """
+ A buffering line only receiver.
+ """
+ delimiter = '\n'
+ MAX_LENGTH = 64
+
+ def connectionMade(self):
+ """
+ Create/clean data received on connection.
+ """
+ self.received = []
+
+ def lineReceived(self, line):
+ """
+ Save received data.
+ """
+ self.received.append(line)
+
+class WireTestCase(unittest.TestCase):
+ """
+ Test wire protocols.
+ """
+ def test_echo(self):
+ """
+ Test wire.Echo protocol: send some data and check it send it back.
+ """
+ t = proto_helpers.StringTransport()
+ a = wire.Echo()
+ a.makeConnection(t)
+ a.dataReceived("hello")
+ a.dataReceived("world")
+ a.dataReceived("how")
+ a.dataReceived("are")
+ a.dataReceived("you")
+ self.assertEquals(t.value(), "helloworldhowareyou")
+
+
+ def test_who(self):
+ """
+ Test wire.Who protocol.
+ """
+ t = proto_helpers.StringTransport()
+ a = wire.Who()
+ a.makeConnection(t)
+ self.assertEquals(t.value(), "root\r\n")
+
+
+ def test_QOTD(self):
+ """
+ Test wire.QOTD protocol.
+ """
+ t = proto_helpers.StringTransport()
+ a = wire.QOTD()
+ a.makeConnection(t)
+ self.assertEquals(t.value(),
+ "An apple a day keeps the doctor away.\r\n")
+
+
+ def test_discard(self):
+ """
+ Test wire.Discard protocol.
+ """
+ t = proto_helpers.StringTransport()
+ a = wire.Discard()
+ a.makeConnection(t)
+ a.dataReceived("hello")
+ a.dataReceived("world")
+ a.dataReceived("how")
+ a.dataReceived("are")
+ a.dataReceived("you")
+ self.assertEqual(t.value(), "")
+
+
+
+class LineReceiverTestCase(unittest.TestCase):
+ """
+ Test LineReceiver, using the C{LineTester} wrapper.
+ """
+ buffer = '''\
+len 10
+
+0123456789len 5
+
+1234
+len 20
+foo 123
+
+0123456789
+012345678len 0
+foo 5
+
+1234567890123456789012345678901234567890123456789012345678901234567890
+len 1
+
+a'''
+
+ output = ['len 10', '0123456789', 'len 5', '1234\n',
+ 'len 20', 'foo 123', '0123456789\n012345678',
+ 'len 0', 'foo 5', '', '67890', 'len 1', 'a']
+
+ def testBuffer(self):
+ """
+ Test buffering for different packet size, checking received matches
+ expected data.
+ """
+ for packet_size in range(1, 10):
+ t = proto_helpers.StringIOWithoutClosing()
+ a = LineTester()
+ a.makeConnection(protocol.FileWrapper(t))
+ for i in range(len(self.buffer)/packet_size + 1):
+ s = self.buffer[i*packet_size:(i+1)*packet_size]
+ a.dataReceived(s)
+ self.failUnlessEqual(self.output, a.received)
+
+
+ pause_buf = 'twiddle1\ntwiddle2\npause\ntwiddle3\n'
+
+ pause_output1 = ['twiddle1', 'twiddle2', 'pause']
+ pause_output2 = pause_output1+['twiddle3']
+
+ def test_pausing(self):
+ """
+ Test pause inside data receiving. It uses fake clock to see if
+ pausing/resuming work.
+ """
+ for packet_size in range(1, 10):
+ t = proto_helpers.StringIOWithoutClosing()
+ clock = task.Clock()
+ a = LineTester(clock)
+ a.makeConnection(protocol.FileWrapper(t))
+ for i in range(len(self.pause_buf)/packet_size + 1):
+ s = self.pause_buf[i*packet_size:(i+1)*packet_size]
+ a.dataReceived(s)
+ self.assertEquals(self.pause_output1, a.received)
+ clock.advance(0)
+ self.assertEquals(self.pause_output2, a.received)
+
+ rawpause_buf = 'twiddle1\ntwiddle2\nlen 5\nrawpause\n12345twiddle3\n'
+
+ rawpause_output1 = ['twiddle1', 'twiddle2', 'len 5', 'rawpause', '']
+ rawpause_output2 = ['twiddle1', 'twiddle2', 'len 5', 'rawpause', '12345',
+ 'twiddle3']
+
+ def test_rawPausing(self):
+ """
+ Test pause inside raw date receiving.
+ """
+ for packet_size in range(1, 10):
+ t = proto_helpers.StringIOWithoutClosing()
+ clock = task.Clock()
+ a = LineTester(clock)
+ a.makeConnection(protocol.FileWrapper(t))
+ for i in range(len(self.rawpause_buf)/packet_size + 1):
+ s = self.rawpause_buf[i*packet_size:(i+1)*packet_size]
+ a.dataReceived(s)
+ self.assertEquals(self.rawpause_output1, a.received)
+ clock.advance(0)
+ self.assertEquals(self.rawpause_output2, a.received)
+
+ stop_buf = 'twiddle1\ntwiddle2\nstop\nmore\nstuff\n'
+
+ stop_output = ['twiddle1', 'twiddle2', 'stop']
+
+ def test_stopProducing(self):
+ """
+ Test stop inside producing.
+ """
+ for packet_size in range(1, 10):
+ t = proto_helpers.StringIOWithoutClosing()
+ a = LineTester()
+ a.makeConnection(protocol.FileWrapper(t))
+ for i in range(len(self.stop_buf)/packet_size + 1):
+ s = self.stop_buf[i*packet_size:(i+1)*packet_size]
+ a.dataReceived(s)
+ self.assertEquals(self.stop_output, a.received)
+
+
+ def test_lineReceiverAsProducer(self):
+ """
+ Test produce/unproduce in receiving.
+ """
+ a = LineTester()
+ t = proto_helpers.StringIOWithoutClosing()
+ a.makeConnection(protocol.FileWrapper(t))
+ a.dataReceived('produce\nhello world\nunproduce\ngoodbye\n')
+ self.assertEquals(a.received,
+ ['produce', 'hello world', 'unproduce', 'goodbye'])
+
+
+ def test_clearLineBuffer(self):
+ """
+ L{LineReceiver.clearLineBuffer} removes all buffered data and returns
+ it as a C{str} and can be called from beneath C{dataReceived}.
+ """
+ class ClearingReceiver(basic.LineReceiver):
+ def lineReceived(self, line):
+ self.line = line
+ self.rest = self.clearLineBuffer()
+
+ protocol = ClearingReceiver()
+ protocol.dataReceived('foo\r\nbar\r\nbaz')
+ self.assertEqual(protocol.line, 'foo')
+ self.assertEqual(protocol.rest, 'bar\r\nbaz')
+
+ # Deliver another line to make sure the previously buffered data is
+ # really gone.
+ protocol.dataReceived('quux\r\n')
+ self.assertEqual(protocol.line, 'quux')
+ self.assertEqual(protocol.rest, '')
+
+
+
+class LineOnlyReceiverTestCase(unittest.TestCase):
+ """
+ Test line only receiveer.
+ """
+ buffer = """foo
+ bleakness
+ desolation
+ plastic forks
+ """
+
+ def test_buffer(self):
+ """
+ Test buffering over line protocol: data received should match buffer.
+ """
+ t = proto_helpers.StringTransport()
+ a = LineOnlyTester()
+ a.makeConnection(t)
+ for c in self.buffer:
+ a.dataReceived(c)
+ self.assertEquals(a.received, self.buffer.split('\n')[:-1])
+
+ def test_lineTooLong(self):
+ """
+ Test sending a line too long: it should close the connection.
+ """
+ t = proto_helpers.StringTransport()
+ a = LineOnlyTester()
+ a.makeConnection(t)
+ res = a.dataReceived('x'*200)
+ self.assertIsInstance(res, error.ConnectionLost)
+
+
+
+class TestMixin:
+
+ def connectionMade(self):
+ self.received = []
+
+ def stringReceived(self, s):
+ self.received.append(s)
+
+ MAX_LENGTH = 50
+ closed = 0
+
+ def connectionLost(self, reason):
+ self.closed = 1
+
+
+class TestNetstring(TestMixin, basic.NetstringReceiver):
+ pass
+
+
+class LPTestCaseMixin:
+
+ illegalStrings = []
+ protocol = None
+
+ def getProtocol(self):
+ """
+ Return a new instance of C{self.protocol} connected to a new instance
+ of L{proto_helpers.StringTransport}.
+ """
+ t = proto_helpers.StringTransport()
+ a = self.protocol()
+ a.makeConnection(t)
+ return a
+
+
+ def test_illegal(self):
+ """
+ Assert that illegal strings cause the transport to be closed.
+ """
+ for s in self.illegalStrings:
+ r = self.getProtocol()
+ for c in s:
+ r.dataReceived(c)
+ self.assertTrue(r.transport.disconnecting)
+
+
+class NetstringReceiverTestCase(unittest.TestCase, LPTestCaseMixin):
+
+ strings = ['hello', 'world', 'how', 'are', 'you123', ':today', "a"*515]
+
+ illegalStrings = [
+ '9999999999999999999999', 'abc', '4:abcde',
+ '51:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab,',]
+
+ protocol = TestNetstring
+
+ def test_buffer(self):
+ """
+ Test that when strings are received in chunks of different lengths,
+ they are still parsed correctly.
+ """
+ for packet_size in range(1, 10):
+ t = proto_helpers.StringTransport()
+ a = TestNetstring()
+ a.MAX_LENGTH = 699
+ a.makeConnection(t)
+ for s in self.strings:
+ a.sendString(s)
+ out = t.value()
+ for i in range(len(out)/packet_size + 1):
+ s = out[i*packet_size:(i+1)*packet_size]
+ if s:
+ a.dataReceived(s)
+ self.assertEquals(a.received, self.strings)
+
+ def test_sendNonStrings(self):
+ """
+ L{basic.NetstringReceiver.sendString} will send objects that are not
+ strings by sending their string representation according to str().
+ """
+ nonStrings = [ [], { 1 : 'a', 2 : 'b' }, ['a', 'b', 'c'], 673,
+ (12, "fine", "and", "you?") ]
+ a = TestNetstring()
+ t = proto_helpers.StringTransport()
+ a.MAX_LENGTH = 100
+ a.makeConnection(t)
+ for s in nonStrings:
+ a.sendString(s)
+ out = t.value()
+ t.clear()
+ length = out[:out.find(":")]
+ data = out[out.find(":") + 1:-1] #[:-1] to ignore the trailing ","
+ self.assertEquals(int(length), len(str(s)))
+ self.assertEquals(data, str(s))
+
+ warnings = self.flushWarnings(
+ offendingFunctions=[self.test_sendNonStrings])
+ self.assertEqual(len(warnings), 5)
+ self.assertEqual(
+ warnings[0]["message"],
+ "data passed to sendString() must be a string. Non-string support "
+ "is deprecated since Twisted 10.0")
+ self.assertEqual(
+ warnings[0]['category'],
+ DeprecationWarning)
+
+
+class IntNTestCaseMixin(LPTestCaseMixin):
+ """
+ TestCase mixin for int-prefixed protocols.
+ """
+
+ protocol = None
+ strings = None
+ illegalStrings = None
+ partialStrings = None
+
+ def test_receive(self):
+ """
+ Test receiving data find the same data send.
+ """
+ r = self.getProtocol()
+ for s in self.strings:
+ for c in struct.pack(r.structFormat,len(s)) + s:
+ r.dataReceived(c)
+ self.assertEquals(r.received, self.strings)
+
+ def test_partial(self):
+ """
+ Send partial data, nothing should be definitely received.
+ """
+ for s in self.partialStrings:
+ r = self.getProtocol()
+ for c in s:
+ r.dataReceived(c)
+ self.assertEquals(r.received, [])
+
+ def test_send(self):
+ """
+ Test sending data over protocol.
+ """
+ r = self.getProtocol()
+ r.sendString("b" * 16)
+ self.assertEquals(r.transport.value(),
+ struct.pack(r.structFormat, 16) + "b" * 16)
+
+
+ def test_lengthLimitExceeded(self):
+ """
+ When a length prefix is received which is greater than the protocol's
+ C{MAX_LENGTH} attribute, the C{lengthLimitExceeded} method is called
+ with the received length prefix.
+ """
+ length = []
+ r = self.getProtocol()
+ r.lengthLimitExceeded = length.append
+ r.MAX_LENGTH = 10
+ r.dataReceived(struct.pack(r.structFormat, 11))
+ self.assertEqual(length, [11])
+
+
+ def test_longStringNotDelivered(self):
+ """
+ If a length prefix for a string longer than C{MAX_LENGTH} is delivered
+ to C{dataReceived} at the same time as the entire string, the string is
+ not passed to C{stringReceived}.
+ """
+ r = self.getProtocol()
+ r.MAX_LENGTH = 10
+ r.dataReceived(
+ struct.pack(r.structFormat, 11) + 'x' * 11)
+ self.assertEqual(r.received, [])
+
+
+
+class TestInt32(TestMixin, basic.Int32StringReceiver):
+ """
+ A L{basic.Int32StringReceiver} storing received strings in an array.
+
+ @ivar received: array holding received strings.
+ """
+
+
+class Int32TestCase(unittest.TestCase, IntNTestCaseMixin):
+ """
+ Test case for int32-prefixed protocol
+ """
+ protocol = TestInt32
+ strings = ["a", "b" * 16]
+ illegalStrings = ["\x10\x00\x00\x00aaaaaa"]
+ partialStrings = ["\x00\x00\x00", "hello there", ""]
+
+ def test_data(self):
+ """
+ Test specific behavior of the 32-bits length.
+ """
+ r = self.getProtocol()
+ r.sendString("foo")
+ self.assertEquals(r.transport.value(), "\x00\x00\x00\x03foo")
+ r.dataReceived("\x00\x00\x00\x04ubar")
+ self.assertEquals(r.received, ["ubar"])
+
+
+class TestInt16(TestMixin, basic.Int16StringReceiver):
+ """
+ A L{basic.Int16StringReceiver} storing received strings in an array.
+
+ @ivar received: array holding received strings.
+ """
+
+
+class Int16TestCase(unittest.TestCase, IntNTestCaseMixin):
+ """
+ Test case for int16-prefixed protocol
+ """
+ protocol = TestInt16
+ strings = ["a", "b" * 16]
+ illegalStrings = ["\x10\x00aaaaaa"]
+ partialStrings = ["\x00", "hello there", ""]
+
+ def test_data(self):
+ """
+ Test specific behavior of the 16-bits length.
+ """
+ r = self.getProtocol()
+ r.sendString("foo")
+ self.assertEquals(r.transport.value(), "\x00\x03foo")
+ r.dataReceived("\x00\x04ubar")
+ self.assertEquals(r.received, ["ubar"])
+
+ def test_tooLongSend(self):
+ """
+ Send too much data: that should cause an error.
+ """
+ r = self.getProtocol()
+ tooSend = "b" * (2**(r.prefixLength*8) + 1)
+ self.assertRaises(AssertionError, r.sendString, tooSend)
+
+
+class TestInt8(TestMixin, basic.Int8StringReceiver):
+ """
+ A L{basic.Int8StringReceiver} storing received strings in an array.
+
+ @ivar received: array holding received strings.
+ """
+
+
+class Int8TestCase(unittest.TestCase, IntNTestCaseMixin):
+ """
+ Test case for int8-prefixed protocol
+ """
+ protocol = TestInt8
+ strings = ["a", "b" * 16]
+ illegalStrings = ["\x00\x00aaaaaa"]
+ partialStrings = ["\x08", "dzadz", ""]
+
+ def test_data(self):
+ """
+ Test specific behavior of the 8-bits length.
+ """
+ r = self.getProtocol()
+ r.sendString("foo")
+ self.assertEquals(r.transport.value(), "\x03foo")
+ r.dataReceived("\x04ubar")
+ self.assertEquals(r.received, ["ubar"])
+
+ def test_tooLongSend(self):
+ """
+ Send too much data: that should cause an error.
+ """
+ r = self.getProtocol()
+ tooSend = "b" * (2**(r.prefixLength*8) + 1)
+ self.assertRaises(AssertionError, r.sendString, tooSend)
+
+
+class OnlyProducerTransport(object):
+ # Transport which isn't really a transport, just looks like one to
+ # someone not looking very hard.
+
+ paused = False
+ disconnecting = False
+
+ def __init__(self):
+ self.data = []
+
+ def pauseProducing(self):
+ self.paused = True
+
+ def resumeProducing(self):
+ self.paused = False
+
+ def write(self, bytes):
+ self.data.append(bytes)
+
+
+class ConsumingProtocol(basic.LineReceiver):
+ # Protocol that really, really doesn't want any more bytes.
+
+ def lineReceived(self, line):
+ self.transport.write(line)
+ self.pauseProducing()
+
+
+class ProducerTestCase(unittest.TestCase):
+ def testPauseResume(self):
+ p = ConsumingProtocol()
+ t = OnlyProducerTransport()
+ p.makeConnection(t)
+
+ p.dataReceived('hello, ')
+ self.failIf(t.data)
+ self.failIf(t.paused)
+ self.failIf(p.paused)
+
+ p.dataReceived('world\r\n')
+
+ self.assertEquals(t.data, ['hello, world'])
+ self.failUnless(t.paused)
+ self.failUnless(p.paused)
+
+ p.resumeProducing()
+
+ self.failIf(t.paused)
+ self.failIf(p.paused)
+
+ p.dataReceived('hello\r\nworld\r\n')
+
+ self.assertEquals(t.data, ['hello, world', 'hello'])
+ self.failUnless(t.paused)
+ self.failUnless(p.paused)
+
+ p.resumeProducing()
+ p.dataReceived('goodbye\r\n')
+
+ self.assertEquals(t.data, ['hello, world', 'hello', 'world'])
+ self.failUnless(t.paused)
+ self.failUnless(p.paused)
+
+ p.resumeProducing()
+
+ self.assertEquals(t.data, ['hello, world', 'hello', 'world', 'goodbye'])
+ self.failUnless(t.paused)
+ self.failUnless(p.paused)
+
+ p.resumeProducing()
+
+ self.assertEquals(t.data, ['hello, world', 'hello', 'world', 'goodbye'])
+ self.failIf(t.paused)
+ self.failIf(p.paused)
+
+
+
+class TestableProxyClientFactory(portforward.ProxyClientFactory):
+ """
+ Test proxy client factory that keeps the last created protocol instance.
+
+ @ivar protoInstance: the last instance of the protocol.
+ @type protoInstance: L{portforward.ProxyClient}
+ """
+
+ def buildProtocol(self, addr):
+ """
+ Create the protocol instance and keeps track of it.
+ """
+ proto = portforward.ProxyClientFactory.buildProtocol(self, addr)
+ self.protoInstance = proto
+ return proto
+
+
+
+class TestableProxyFactory(portforward.ProxyFactory):
+ """
+ Test proxy factory that keeps the last created protocol instance.
+
+ @ivar protoInstance: the last instance of the protocol.
+ @type protoInstance: L{portforward.ProxyServer}
+
+ @ivar clientFactoryInstance: client factory used by C{protoInstance} to
+ create forward connections.
+ @type clientFactoryInstance: L{TestableProxyClientFactory}
+ """
+
+ def buildProtocol(self, addr):
+ """
+ Create the protocol instance, keeps track of it, and makes it use
+ C{clientFactoryInstance} as client factory.
+ """
+ proto = portforward.ProxyFactory.buildProtocol(self, addr)
+ self.clientFactoryInstance = TestableProxyClientFactory()
+ # Force the use of this specific instance
+ proto.clientProtocolFactory = lambda: self.clientFactoryInstance
+ self.protoInstance = proto
+ return proto
+
+
+
+class Portforwarding(unittest.TestCase):
+ """
+ Test port forwarding.
+ """
+
+ def setUp(self):
+ self.serverProtocol = wire.Echo()
+ self.clientProtocol = protocol.Protocol()
+ self.openPorts = []
+
+
+ def tearDown(self):
+ try:
+ self.proxyServerFactory.protoInstance.transport.loseConnection()
+ except AttributeError:
+ pass
+ try:
+ self.proxyServerFactory.clientFactoryInstance.protoInstance.transport.loseConnection()
+ except AttributeError:
+ pass
+ try:
+ self.clientProtocol.transport.loseConnection()
+ except AttributeError:
+ pass
+ try:
+ self.serverProtocol.transport.loseConnection()
+ except AttributeError:
+ pass
+ return defer.gatherResults(
+ [defer.maybeDeferred(p.stopListening) for p in self.openPorts])
+
+
+ def test_portforward(self):
+ """
+ Test port forwarding through Echo protocol.
+ """
+ realServerFactory = protocol.ServerFactory()
+ realServerFactory.protocol = lambda: self.serverProtocol
+ realServerPort = reactor.listenTCP(0, realServerFactory,
+ interface='127.0.0.1')
+ self.openPorts.append(realServerPort)
+ self.proxyServerFactory = TestableProxyFactory('127.0.0.1',
+ realServerPort.getHost().port)
+ proxyServerPort = reactor.listenTCP(0, self.proxyServerFactory,
+ interface='127.0.0.1')
+ self.openPorts.append(proxyServerPort)
+
+ nBytes = 1000
+ received = []
+ d = defer.Deferred()
+ def testDataReceived(data):
+ received.extend(data)
+ if len(received) >= nBytes:
+ self.assertEquals(''.join(received), 'x' * nBytes)
+ d.callback(None)
+ self.clientProtocol.dataReceived = testDataReceived
+
+ def testConnectionMade():
+ self.clientProtocol.transport.write('x' * nBytes)
+ self.clientProtocol.connectionMade = testConnectionMade
+
+ clientFactory = protocol.ClientFactory()
+ clientFactory.protocol = lambda: self.clientProtocol
+
+ reactor.connectTCP(
+ '127.0.0.1', proxyServerPort.getHost().port, clientFactory)
+
+ return d
+
+
+
+class StringTransportTestCase(unittest.TestCase):
+ """
+ Test L{proto_helpers.StringTransport} helper behaviour.
+ """
+
+ def test_noUnicode(self):
+ """
+ Test that L{proto_helpers.StringTransport} doesn't accept unicode data.
+ """
+ s = proto_helpers.StringTransport()
+ self.assertRaises(TypeError, s.write, u'foo')
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_randbytes.py b/vendor/Twisted-10.0.0/twisted/test/test_randbytes.py
new file mode 100644
index 0000000000..d62504f703
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_randbytes.py
@@ -0,0 +1,178 @@
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for L{twisted.python.randbytes}.
+"""
+
+import os, sys
+
+from twisted.trial import unittest
+from twisted.python import randbytes
+
+try:
+ from Crypto.Util import randpool
+except ImportError:
+ randpool = None
+
+
+
+class SecureRandomTestCaseBase(object):
+ """
+ Base class for secureRandom test cases.
+ """
+
+ def _check(self, source):
+ """
+ The given random bytes source should return the number of bytes
+ requested each time it is called and should probably not return the
+ same bytes on two consecutive calls (although this is a perfectly
+ legitimate occurrence and rejecting it may generate a spurious failure
+ -- maybe we'll get lucky and the heat death with come first).
+ """
+ for nbytes in range(17, 25):
+ s = source(nbytes)
+ self.assertEquals(len(s), nbytes)
+ s2 = source(nbytes)
+ self.assertEquals(len(s2), nbytes)
+ # This is crude but hey
+ self.assertNotEquals(s2, s)
+
+
+
+class SecureRandomTestCase(SecureRandomTestCaseBase, unittest.TestCase):
+ """
+ Test secureRandom under normal conditions.
+ """
+
+ def test_normal(self):
+ """
+ L{randbytes.secureRandom} should return a string of the requested
+ length and make some effort to make its result otherwise unpredictable.
+ """
+ self._check(randbytes.secureRandom)
+
+
+
+class ConditionalSecureRandomTestCase(SecureRandomTestCaseBase,
+ unittest.TestCase):
+ """
+ Test random sources one by one, then remove it to.
+ """
+
+ def setUp(self):
+ """
+ Create a L{randbytes.RandomFactory} to use in the tests.
+ """
+ self.factory = randbytes.RandomFactory()
+
+
+ def errorFactory(self, nbytes):
+ """
+ A factory raising an error when a source is not available.
+ """
+ raise randbytes.SourceNotAvailable()
+
+
+ def test_osUrandom(self):
+ """
+ L{RandomFactory._osUrandom} should work as a random source whenever
+ L{os.urandom} is available.
+ """
+ try:
+ self._check(self.factory._osUrandom)
+ except randbytes.SourceNotAvailable:
+ # Not available on Python 2.3
+ self.assertTrue(sys.version_info < (2, 4))
+
+
+ def test_fileUrandom(self):
+ """
+ L{RandomFactory._fileUrandom} should work as a random source whenever
+ C{/dev/urandom} is available.
+ """
+ try:
+ self._check(self.factory._fileUrandom)
+ except randbytes.SourceNotAvailable:
+ # The test should only fail in /dev/urandom doesn't exist
+ self.assertFalse(os.path.exists('/dev/urandom'))
+
+
+ def test_cryptoRandom(self):
+ """
+ L{RandomFactory._cryptoRandom} should work as a random source whenever
+ L{PyCrypto} is installed.
+ """
+ try:
+ self._check(self.factory._cryptoRandom)
+ except randbytes.SourceNotAvailable:
+ # It fails if PyCrypto is not here
+ self.assertIdentical(randpool, None)
+
+
+ def test_withoutOsUrandom(self):
+ """
+ If L{os.urandom} is not available but L{PyCrypto} is,
+ L{RandomFactory.secureRandom} should still work as a random source.
+ """
+ self.factory._osUrandom = self.errorFactory
+ self._check(self.factory.secureRandom)
+
+ if randpool is None:
+ test_withoutOsUrandom.skip = "PyCrypto not available"
+
+
+ def test_withoutOsAndFileUrandom(self):
+ """
+ Remove C{os.urandom} and /dev/urandom read.
+ """
+ self.factory._osUrandom = self.errorFactory
+ self.factory._fileUrandom = self.errorFactory
+ self._check(self.factory.secureRandom)
+
+ if randpool is None:
+ test_withoutOsAndFileUrandom.skip = "PyCrypto not available"
+
+
+ def test_withoutAnything(self):
+ """
+ Remove all secure sources and assert it raises a failure. Then try the
+ fallback parameter.
+ """
+ self.factory._osUrandom = self.errorFactory
+ self.factory._fileUrandom = self.errorFactory
+ self.factory._cryptoRandom = self.errorFactory
+ self.assertRaises(randbytes.SecureRandomNotAvailable,
+ self.factory.secureRandom, 18)
+ def wrapper():
+ return self.factory.secureRandom(18, fallback=True)
+ s = self.assertWarns(
+ RuntimeWarning,
+ "Neither PyCrypto nor urandom available - "
+ "proceeding with non-cryptographically secure random source",
+ __file__,
+ wrapper)
+ self.assertEquals(len(s), 18)
+
+
+
+class RandomTestCaseBase(SecureRandomTestCaseBase, unittest.TestCase):
+ """
+ 'Normal' random test cases.
+ """
+
+ def test_normal(self):
+ """
+ Test basic case.
+ """
+ self._check(randbytes.insecureRandom)
+
+
+ def test_withoutGetrandbits(self):
+ """
+ Test C{insecureRandom} without C{random.getrandbits}.
+ """
+ factory = randbytes.RandomFactory()
+ factory.getrandbits = None
+ self._check(factory.insecureRandom)
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_rebuild.py b/vendor/Twisted-10.0.0/twisted/test/test_rebuild.py
new file mode 100644
index 0000000000..f2b1ae7d33
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_rebuild.py
@@ -0,0 +1,252 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import sys, os
+import new
+
+from twisted.trial import unittest
+from twisted.python import rebuild
+
+import crash_test_dummy
+f = crash_test_dummy.foo
+
+class Foo: pass
+class Bar(Foo): pass
+class Baz(object): pass
+class Buz(Bar, Baz): pass
+
+class HashRaisesRuntimeError:
+ """
+ Things that don't hash (raise an Exception) should be ignored by the
+ rebuilder.
+
+ @ivar hashCalled: C{bool} set to True when __hash__ is called.
+ """
+ def __init__(self):
+ self.hashCalled = False
+
+ def __hash__(self):
+ self.hashCalled = True
+ raise RuntimeError('not a TypeError!')
+
+
+
+unhashableObject = None # set in test_hashException
+
+
+
+class RebuildTestCase(unittest.TestCase):
+ """
+ Simple testcase for rebuilding, to at least exercise the code.
+ """
+ def setUp(self):
+ self.libPath = self.mktemp()
+ os.mkdir(self.libPath)
+ self.fakelibPath = os.path.join(self.libPath, 'twisted_rebuild_fakelib')
+ os.mkdir(self.fakelibPath)
+ file(os.path.join(self.fakelibPath, '__init__.py'), 'w').close()
+ sys.path.insert(0, self.libPath)
+
+ def tearDown(self):
+ sys.path.remove(self.libPath)
+
+ def testFileRebuild(self):
+ from twisted.python.util import sibpath
+ import shutil, time
+ shutil.copyfile(sibpath(__file__, "myrebuilder1.py"),
+ os.path.join(self.fakelibPath, "myrebuilder.py"))
+ from twisted_rebuild_fakelib import myrebuilder
+ a = myrebuilder.A()
+ try:
+ object
+ except NameError:
+ pass
+ else:
+ from twisted.test import test_rebuild
+ b = myrebuilder.B()
+ class C(myrebuilder.B):
+ pass
+ test_rebuild.C = C
+ c = C()
+ i = myrebuilder.Inherit()
+ self.assertEquals(a.a(), 'a')
+ # necessary because the file has not "changed" if a second has not gone
+ # by in unix. This sucks, but it's not often that you'll be doing more
+ # than one reload per second.
+ time.sleep(1.1)
+ shutil.copyfile(sibpath(__file__, "myrebuilder2.py"),
+ os.path.join(self.fakelibPath, "myrebuilder.py"))
+ rebuild.rebuild(myrebuilder)
+ try:
+ object
+ except NameError:
+ pass
+ else:
+ b2 = myrebuilder.B()
+ self.assertEquals(b2.b(), 'c')
+ self.assertEquals(b.b(), 'c')
+ self.assertEquals(i.a(), 'd')
+ self.assertEquals(a.a(), 'b')
+ # more work to be done on new-style classes
+ # self.assertEquals(c.b(), 'c')
+
+ def testRebuild(self):
+ """
+ Rebuilding an unchanged module.
+ """
+ # This test would actually pass if rebuild was a no-op, but it
+ # ensures rebuild doesn't break stuff while being a less
+ # complex test than testFileRebuild.
+
+ x = crash_test_dummy.X('a')
+
+ rebuild.rebuild(crash_test_dummy, doLog=False)
+ # Instance rebuilding is triggered by attribute access.
+ x.do()
+ self.failUnlessIdentical(x.__class__, crash_test_dummy.X)
+
+ self.failUnlessIdentical(f, crash_test_dummy.foo)
+
+ def testComponentInteraction(self):
+ x = crash_test_dummy.XComponent()
+ x.setAdapter(crash_test_dummy.IX, crash_test_dummy.XA)
+ oldComponent = x.getComponent(crash_test_dummy.IX)
+ rebuild.rebuild(crash_test_dummy, 0)
+ newComponent = x.getComponent(crash_test_dummy.IX)
+
+ newComponent.method()
+
+ self.assertEquals(newComponent.__class__, crash_test_dummy.XA)
+
+ # Test that a duplicate registerAdapter is not allowed
+ from twisted.python import components
+ self.failUnlessRaises(ValueError, components.registerAdapter,
+ crash_test_dummy.XA, crash_test_dummy.X,
+ crash_test_dummy.IX)
+
+ def testUpdateInstance(self):
+ global Foo, Buz
+
+ b = Buz()
+
+ class Foo:
+ def foo(self):
+ pass
+ class Buz(Bar, Baz):
+ x = 10
+
+ rebuild.updateInstance(b)
+ assert hasattr(b, 'foo'), "Missing method on rebuilt instance"
+ assert hasattr(b, 'x'), "Missing class attribute on rebuilt instance"
+
+ def testBananaInteraction(self):
+ from twisted.python import rebuild
+ from twisted.spread import banana
+ rebuild.latestClass(banana.Banana)
+
+
+ def test_hashException(self):
+ """
+ Rebuilding something that has a __hash__ that raises a non-TypeError
+ shouldn't cause rebuild to die.
+ """
+ global unhashableObject
+ unhashableObject = HashRaisesRuntimeError()
+ def _cleanup():
+ global unhashableObject
+ unhashableObject = None
+ self.addCleanup(_cleanup)
+ rebuild.rebuild(rebuild)
+ self.assertEquals(unhashableObject.hashCalled, True)
+
+
+
+class NewStyleTestCase(unittest.TestCase):
+ """
+ Tests for rebuilding new-style classes of various sorts.
+ """
+ def setUp(self):
+ self.m = new.module('whipping')
+ sys.modules['whipping'] = self.m
+
+
+ def tearDown(self):
+ del sys.modules['whipping']
+ del self.m
+
+
+ def test_slots(self):
+ """
+ Try to rebuild a new style class with slots defined.
+ """
+ classDefinition = (
+ "class SlottedClass(object):\n"
+ " __slots__ = ['a']\n")
+
+ exec classDefinition in self.m.__dict__
+ inst = self.m.SlottedClass()
+ inst.a = 7
+ exec classDefinition in self.m.__dict__
+ rebuild.updateInstance(inst)
+ self.assertEqual(inst.a, 7)
+ self.assertIdentical(type(inst), self.m.SlottedClass)
+
+ if sys.version_info < (2, 6):
+ test_slots.skip = "__class__ assignment for class with slots is only available starting Python 2.6"
+
+
+ def test_errorSlots(self):
+ """
+ Try to rebuild a new style class with slots defined: this should fail.
+ """
+ classDefinition = (
+ "class SlottedClass(object):\n"
+ " __slots__ = ['a']\n")
+
+ exec classDefinition in self.m.__dict__
+ inst = self.m.SlottedClass()
+ inst.a = 7
+ exec classDefinition in self.m.__dict__
+ self.assertRaises(rebuild.RebuildError, rebuild.updateInstance, inst)
+
+ if sys.version_info >= (2, 6):
+ test_errorSlots.skip = "__class__ assignment for class with slots should work starting Python 2.6"
+
+
+ def test_typeSubclass(self):
+ """
+ Try to rebuild a base type subclass.
+ """
+ classDefinition = (
+ "class ListSubclass(list):\n"
+ " pass\n")
+
+ exec classDefinition in self.m.__dict__
+ inst = self.m.ListSubclass()
+ inst.append(2)
+ exec classDefinition in self.m.__dict__
+ rebuild.updateInstance(inst)
+ self.assertEqual(inst[0], 2)
+ self.assertIdentical(type(inst), self.m.ListSubclass)
+
+
+ def test_instanceSlots(self):
+ """
+ Test that when rebuilding an instance with a __slots__ attribute, it
+ fails accurately instead of giving a L{rebuild.RebuildError}.
+ """
+ classDefinition = (
+ "class NotSlottedClass(object):\n"
+ " pass\n")
+
+ exec classDefinition in self.m.__dict__
+ inst = self.m.NotSlottedClass()
+ inst.__slots__ = ['a']
+ classDefinition = (
+ "class NotSlottedClass:\n"
+ " pass\n")
+ exec classDefinition in self.m.__dict__
+ # Moving from new-style class to old-style should fail.
+ self.assertRaises(TypeError, rebuild.updateInstance, inst)
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_reflect.py b/vendor/Twisted-10.0.0/twisted/test/test_reflect.py
new file mode 100644
index 0000000000..fb298250a5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_reflect.py
@@ -0,0 +1,756 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for twisted.reflect module.
+"""
+
+import weakref, os
+from ihooks import ModuleImporter
+
+try:
+ from collections import deque
+except ImportError:
+ deque = None
+
+from twisted.trial import unittest
+from twisted.python import reflect, util
+from twisted.python.versions import Version
+
+
+
+class SettableTest(unittest.TestCase):
+ def setUp(self):
+ self.setter = reflect.Settable()
+
+ def tearDown(self):
+ del self.setter
+
+ def testSet(self):
+ self.setter(a=1, b=2)
+ self.failUnlessEqual(self.setter.a, 1)
+ self.failUnlessEqual(self.setter.b, 2)
+
+
+
+class AccessorTester(reflect.Accessor):
+
+ def set_x(self, x):
+ self.y = x
+ self.reallySet('x', x)
+
+
+ def get_z(self):
+ self.q = 1
+ return 1
+
+
+ def del_z(self):
+ self.reallyDel("q")
+
+
+
+class PropertyAccessorTester(reflect.PropertyAccessor):
+ """
+ Test class to check L{reflect.PropertyAccessor} functionalities.
+ """
+ r = 0
+
+ def set_r(self, r):
+ self.s = r
+
+
+ def set_x(self, x):
+ self.y = x
+ self.reallySet('x', x)
+
+
+ def get_z(self):
+ self.q = 1
+ return 1
+
+
+ def del_z(self):
+ self.reallyDel("q")
+
+
+
+class AccessorTest(unittest.TestCase):
+ def setUp(self):
+ self.tester = AccessorTester()
+
+ def testSet(self):
+ self.tester.x = 1
+ self.failUnlessEqual(self.tester.x, 1)
+ self.failUnlessEqual(self.tester.y, 1)
+
+ def testGet(self):
+ self.failUnlessEqual(self.tester.z, 1)
+ self.failUnlessEqual(self.tester.q, 1)
+
+ def testDel(self):
+ self.tester.z
+ self.failUnlessEqual(self.tester.q, 1)
+ del self.tester.z
+ self.failUnlessEqual(hasattr(self.tester, "q"), 0)
+ self.tester.x = 1
+ del self.tester.x
+ self.failUnlessEqual(hasattr(self.tester, "x"), 0)
+
+
+
+class PropertyAccessorTest(AccessorTest):
+ """
+ Tests for L{reflect.PropertyAccessor}, using L{PropertyAccessorTester}.
+ """
+
+ def setUp(self):
+ self.tester = PropertyAccessorTester()
+
+
+ def test_setWithDefaultValue(self):
+ """
+ If an attribute is present in the class, it can be retrieved by
+ default.
+ """
+ self.assertEquals(self.tester.r, 0)
+ self.tester.r = 1
+ self.assertEquals(self.tester.r, 0)
+ self.assertEquals(self.tester.s, 1)
+
+
+ def test_getValueInDict(self):
+ """
+ The attribute value can be overriden by directly modifying the value in
+ C{__dict__}.
+ """
+ self.tester.__dict__["r"] = 10
+ self.assertEquals(self.tester.r, 10)
+
+
+ def test_notYetInDict(self):
+ """
+ If a getter is defined on an attribute but without any default value,
+ it raises C{AttributeError} when trying to access it.
+ """
+ self.assertRaises(AttributeError, getattr, self.tester, "x")
+
+
+
+class LookupsTestCase(unittest.TestCase):
+ """
+ Tests for L{namedClass}, L{namedModule}, and L{namedAny}.
+ """
+
+ def test_namedClassLookup(self):
+ """
+ L{namedClass} should return the class object for the name it is passed.
+ """
+ self.assertIdentical(
+ reflect.namedClass("twisted.python.reflect.Summer"),
+ reflect.Summer)
+
+
+ def test_namedModuleLookup(self):
+ """
+ L{namedModule} should return the module object for the name it is
+ passed.
+ """
+ self.assertIdentical(
+ reflect.namedModule("twisted.python.reflect"), reflect)
+
+
+ def test_namedAnyPackageLookup(self):
+ """
+ L{namedAny} should return the package object for the name it is passed.
+ """
+ import twisted.python
+ self.assertIdentical(
+ reflect.namedAny("twisted.python"), twisted.python)
+
+ def test_namedAnyModuleLookup(self):
+ """
+ L{namedAny} should return the module object for the name it is passed.
+ """
+ self.assertIdentical(
+ reflect.namedAny("twisted.python.reflect"), reflect)
+
+
+ def test_namedAnyClassLookup(self):
+ """
+ L{namedAny} should return the class object for the name it is passed.
+ """
+ self.assertIdentical(
+ reflect.namedAny("twisted.python.reflect.Summer"), reflect.Summer)
+
+
+ def test_namedAnyAttributeLookup(self):
+ """
+ L{namedAny} should return the object an attribute of a non-module,
+ non-package object is bound to for the name it is passed.
+ """
+ # Note - not assertEqual because unbound method lookup creates a new
+ # object every time. This is a foolishness of Python's object
+ # implementation, not a bug in Twisted.
+ self.assertEqual(
+ reflect.namedAny("twisted.python.reflect.Summer.reallySet"),
+ reflect.Summer.reallySet)
+
+
+ def test_namedAnySecondAttributeLookup(self):
+ """
+ L{namedAny} should return the object an attribute of an object which
+ itself was an attribute of a non-module, non-package object is bound to
+ for the name it is passed.
+ """
+ self.assertIdentical(
+ reflect.namedAny(
+ "twisted.python.reflect.Summer.reallySet.__doc__"),
+ reflect.Summer.reallySet.__doc__)
+
+
+ def test_importExceptions(self):
+ """
+ Exceptions raised by modules which L{namedAny} causes to be imported
+ should pass through L{namedAny} to the caller.
+ """
+ self.assertRaises(
+ ZeroDivisionError,
+ reflect.namedAny, "twisted.test.reflect_helper_ZDE")
+ # Make sure that this behavior is *consistent* for 2.3, where there is
+ # no post-failed-import cleanup
+ self.assertRaises(
+ ZeroDivisionError,
+ reflect.namedAny, "twisted.test.reflect_helper_ZDE")
+ self.assertRaises(
+ ValueError,
+ reflect.namedAny, "twisted.test.reflect_helper_VE")
+ # Modules which themselves raise ImportError when imported should result in an ImportError
+ self.assertRaises(
+ ImportError,
+ reflect.namedAny, "twisted.test.reflect_helper_IE")
+
+
+ def test_attributeExceptions(self):
+ """
+ If segments on the end of a fully-qualified Python name represents
+ attributes which aren't actually present on the object represented by
+ the earlier segments, L{namedAny} should raise an L{AttributeError}.
+ """
+ self.assertRaises(
+ AttributeError,
+ reflect.namedAny, "twisted.nosuchmoduleintheworld")
+ # ImportError behaves somewhat differently between "import
+ # extant.nonextant" and "import extant.nonextant.nonextant", so test
+ # the latter as well.
+ self.assertRaises(
+ AttributeError,
+ reflect.namedAny, "twisted.nosuch.modulein.theworld")
+ self.assertRaises(
+ AttributeError,
+ reflect.namedAny, "twisted.python.reflect.Summer.nosuchattributeintheworld")
+
+
+ def test_invalidNames(self):
+ """
+ Passing a name which isn't a fully-qualified Python name to L{namedAny}
+ should result in one of the following exceptions:
+ - L{InvalidName}: the name is not a dot-separated list of Python objects
+ - L{ObjectNotFound}: the object doesn't exist
+ - L{ModuleNotFound}: the object doesn't exist and there is only one
+ component in the name
+ """
+ err = self.assertRaises(reflect.ModuleNotFound, reflect.namedAny,
+ 'nosuchmoduleintheworld')
+ self.assertEqual(str(err), "No module named 'nosuchmoduleintheworld'")
+
+ # This is a dot-separated list, but it isn't valid!
+ err = self.assertRaises(reflect.ObjectNotFound, reflect.namedAny,
+ "@#$@(#.!@(#!@#")
+ self.assertEqual(str(err), "'@#$@(#.!@(#!@#' does not name an object")
+
+ err = self.assertRaises(reflect.ObjectNotFound, reflect.namedAny,
+ "tcelfer.nohtyp.detsiwt")
+ self.assertEqual(
+ str(err),
+ "'tcelfer.nohtyp.detsiwt' does not name an object")
+
+ err = self.assertRaises(reflect.InvalidName, reflect.namedAny, '')
+ self.assertEqual(str(err), 'Empty module name')
+
+ for invalidName in ['.twisted', 'twisted.', 'twisted..python']:
+ err = self.assertRaises(
+ reflect.InvalidName, reflect.namedAny, invalidName)
+ self.assertEqual(
+ str(err),
+ "name must be a string giving a '.'-separated list of Python "
+ "identifiers, not %r" % (invalidName,))
+
+
+
+class ImportHooksLookupTests(LookupsTestCase):
+ """
+ Tests for lookup methods in the presence of L{ihooks}-style import hooks.
+ Runs all of the tests from L{LookupsTestCase} after installing a custom
+ import hook.
+ """
+ def setUp(self):
+ """
+ Perturb the normal import behavior subtly by installing an import
+ hook. No custom behavior is provided, but this adds some extra
+ frames to the call stack, which L{namedAny} must be able to account
+ for.
+ """
+ self.importer = ModuleImporter()
+ self.importer.install()
+
+
+ def tearDown(self):
+ """
+ Uninstall the custom import hook.
+ """
+ self.importer.uninstall()
+
+
+
+class ObjectGrep(unittest.TestCase):
+ def test_dictionary(self):
+ """
+ Test references search through a dictionnary, as a key or as a value.
+ """
+ o = object()
+ d1 = {None: o}
+ d2 = {o: None}
+
+ self.assertIn("[None]", reflect.objgrep(d1, o, reflect.isSame))
+ self.assertIn("{None}", reflect.objgrep(d2, o, reflect.isSame))
+
+ def test_list(self):
+ """
+ Test references search through a list.
+ """
+ o = object()
+ L = [None, o]
+
+ self.assertIn("[1]", reflect.objgrep(L, o, reflect.isSame))
+
+ def test_tuple(self):
+ """
+ Test references search through a tuple.
+ """
+ o = object()
+ T = (o, None)
+
+ self.assertIn("[0]", reflect.objgrep(T, o, reflect.isSame))
+
+ def test_instance(self):
+ """
+ Test references search through an object attribute.
+ """
+ class Dummy:
+ pass
+ o = object()
+ d = Dummy()
+ d.o = o
+
+ self.assertIn(".o", reflect.objgrep(d, o, reflect.isSame))
+
+ def test_weakref(self):
+ """
+ Test references search through a weakref object.
+ """
+ class Dummy:
+ pass
+ o = Dummy()
+ w1 = weakref.ref(o)
+
+ self.assertIn("()", reflect.objgrep(w1, o, reflect.isSame))
+
+ def test_boundMethod(self):
+ """
+ Test references search through method special attributes.
+ """
+ class Dummy:
+ def dummy(self):
+ pass
+ o = Dummy()
+ m = o.dummy
+
+ self.assertIn(".im_self", reflect.objgrep(m, m.im_self, reflect.isSame))
+ self.assertIn(".im_class", reflect.objgrep(m, m.im_class, reflect.isSame))
+ self.assertIn(".im_func", reflect.objgrep(m, m.im_func, reflect.isSame))
+
+ def test_everything(self):
+ """
+ Test references search using complex set of objects.
+ """
+ class Dummy:
+ def method(self):
+ pass
+
+ o = Dummy()
+ D1 = {(): "baz", None: "Quux", o: "Foosh"}
+ L = [None, (), D1, 3]
+ T = (L, {}, Dummy())
+ D2 = {0: "foo", 1: "bar", 2: T}
+ i = Dummy()
+ i.attr = D2
+ m = i.method
+ w = weakref.ref(m)
+
+ self.assertIn("().im_self.attr[2][0][2]{'Foosh'}", reflect.objgrep(w, o, reflect.isSame))
+
+ def test_depthLimit(self):
+ """
+ Test the depth of references search.
+ """
+ a = []
+ b = [a]
+ c = [a, b]
+ d = [a, c]
+
+ self.assertEquals(['[0]'], reflect.objgrep(d, a, reflect.isSame, maxDepth=1))
+ self.assertEquals(['[0]', '[1][0]'], reflect.objgrep(d, a, reflect.isSame, maxDepth=2))
+ self.assertEquals(['[0]', '[1][0]', '[1][1][0]'], reflect.objgrep(d, a, reflect.isSame, maxDepth=3))
+
+ def test_deque(self):
+ """
+ Test references search through a deque object. Only for Python > 2.3.
+ """
+ o = object()
+ D = deque()
+ D.append(None)
+ D.append(o)
+
+ self.assertIn("[1]", reflect.objgrep(D, o, reflect.isSame))
+
+ if deque is None:
+ test_deque.skip = "Deque not available"
+
+
+class GetClass(unittest.TestCase):
+ def testOld(self):
+ class OldClass:
+ pass
+ old = OldClass()
+ self.assertIn(reflect.getClass(OldClass).__name__, ('class', 'classobj'))
+ self.assertEquals(reflect.getClass(old).__name__, 'OldClass')
+
+ def testNew(self):
+ class NewClass(object):
+ pass
+ new = NewClass()
+ self.assertEquals(reflect.getClass(NewClass).__name__, 'type')
+ self.assertEquals(reflect.getClass(new).__name__, 'NewClass')
+
+
+
+class Breakable(object):
+
+ breakRepr = False
+ breakStr = False
+
+ def __str__(self):
+ if self.breakStr:
+ raise RuntimeError("str!")
+ else:
+ return '<Breakable>'
+
+ def __repr__(self):
+ if self.breakRepr:
+ raise RuntimeError("repr!")
+ else:
+ return 'Breakable()'
+
+
+
+class BrokenType(Breakable, type):
+ breakName = False
+
+ def get___name__(self):
+ if self.breakName:
+ raise RuntimeError("no name")
+ return 'BrokenType'
+ __name__ = property(get___name__)
+
+
+
+class BTBase(Breakable):
+ __metaclass__ = BrokenType
+ breakRepr = True
+ breakStr = True
+
+
+
+class NoClassAttr(Breakable):
+ __class__ = property(lambda x: x.not_class)
+
+
+
+class SafeRepr(unittest.TestCase):
+ """
+ Tests for L{reflect.safe_repr} function.
+ """
+
+ def test_workingRepr(self):
+ """
+ L{reflect.safe_repr} produces the same output as C{repr} on a working
+ object.
+ """
+ x = [1, 2, 3]
+ self.assertEquals(reflect.safe_repr(x), repr(x))
+
+
+ def test_brokenRepr(self):
+ """
+ L{reflect.safe_repr} returns a string with class name, address, and
+ traceback when the repr call failed.
+ """
+ b = Breakable()
+ b.breakRepr = True
+ bRepr = reflect.safe_repr(b)
+ self.assertIn("Breakable instance at 0x", bRepr)
+ # Check that the file is in the repr, but without the extension as it
+ # can be .py/.pyc
+ self.assertIn(os.path.splitext(__file__)[0], bRepr)
+ self.assertIn("RuntimeError: repr!", bRepr)
+
+
+ def test_brokenStr(self):
+ """
+ L{reflect.safe_repr} isn't affected by a broken C{__str__} method.
+ """
+ b = Breakable()
+ b.breakStr = True
+ self.assertEquals(reflect.safe_repr(b), repr(b))
+
+
+ def test_brokenClassRepr(self):
+ class X(BTBase):
+ breakRepr = True
+ reflect.safe_repr(X)
+ reflect.safe_repr(X())
+
+
+ def test_unsignedID(self):
+ """
+ L{unsignedID} is used to print ID of the object in case of error, not
+ standard ID value which can be negative.
+ """
+ class X(BTBase):
+ breakRepr = True
+
+ ids = {X: 100}
+ def fakeID(obj):
+ try:
+ return ids[obj]
+ except (TypeError, KeyError):
+ return id(obj)
+ self.addCleanup(util.setIDFunction, util.setIDFunction(fakeID))
+
+ xRepr = reflect.safe_repr(X)
+ self.assertIn("0x64", xRepr)
+
+
+ def test_brokenClassStr(self):
+ class X(BTBase):
+ breakStr = True
+ reflect.safe_repr(X)
+ reflect.safe_repr(X())
+
+
+ def test_brokenClassAttribute(self):
+ """
+ If an object raises an exception when accessing its C{__class__}
+ attribute, L{reflect.safe_repr} uses C{type} to retrieve the class
+ object.
+ """
+ b = NoClassAttr()
+ b.breakRepr = True
+ bRepr = reflect.safe_repr(b)
+ self.assertIn("NoClassAttr instance at 0x", bRepr)
+ self.assertIn(os.path.splitext(__file__)[0], bRepr)
+ self.assertIn("RuntimeError: repr!", bRepr)
+
+
+ def test_brokenClassNameAttribute(self):
+ """
+ If a class raises an exception when accessing its C{__name__} attribute
+ B{and} when calling its C{__str__} implementation, L{reflect.safe_repr}
+ returns 'BROKEN CLASS' instead of the class name.
+ """
+ class X(BTBase):
+ breakName = True
+ xRepr = reflect.safe_repr(X())
+ self.assertIn("<BROKEN CLASS AT 0x", xRepr)
+ self.assertIn(os.path.splitext(__file__)[0], xRepr)
+ self.assertIn("RuntimeError: repr!", xRepr)
+
+
+
+class SafeStr(unittest.TestCase):
+ """
+ Tests for L{reflect.safe_str} function.
+ """
+
+ def test_workingStr(self):
+ x = [1, 2, 3]
+ self.assertEquals(reflect.safe_str(x), str(x))
+
+
+ def test_brokenStr(self):
+ b = Breakable()
+ b.breakStr = True
+ reflect.safe_str(b)
+
+
+ def test_brokenRepr(self):
+ b = Breakable()
+ b.breakRepr = True
+ reflect.safe_str(b)
+
+
+ def test_brokenClassStr(self):
+ class X(BTBase):
+ breakStr = True
+ reflect.safe_str(X)
+ reflect.safe_str(X())
+
+
+ def test_brokenClassRepr(self):
+ class X(BTBase):
+ breakRepr = True
+ reflect.safe_str(X)
+ reflect.safe_str(X())
+
+
+ def test_brokenClassAttribute(self):
+ """
+ If an object raises an exception when accessing its C{__class__}
+ attribute, L{reflect.safe_str} uses C{type} to retrieve the class
+ object.
+ """
+ b = NoClassAttr()
+ b.breakStr = True
+ bStr = reflect.safe_str(b)
+ self.assertIn("NoClassAttr instance at 0x", bStr)
+ self.assertIn(os.path.splitext(__file__)[0], bStr)
+ self.assertIn("RuntimeError: str!", bStr)
+
+
+ def test_brokenClassNameAttribute(self):
+ """
+ If a class raises an exception when accessing its C{__name__} attribute
+ B{and} when calling its C{__str__} implementation, L{reflect.safe_str}
+ returns 'BROKEN CLASS' instead of the class name.
+ """
+ class X(BTBase):
+ breakName = True
+ xStr = reflect.safe_str(X())
+ self.assertIn("<BROKEN CLASS AT 0x", xStr)
+ self.assertIn(os.path.splitext(__file__)[0], xStr)
+ self.assertIn("RuntimeError: str!", xStr)
+
+
+
+class FilenameToModule(unittest.TestCase):
+ """
+ Test L{reflect.filenameToModuleName} detection.
+ """
+ def test_directory(self):
+ """
+ Tests it finds good name for directories/packages.
+ """
+ module = reflect.filenameToModuleName(os.path.join('twisted', 'test'))
+ self.assertEquals(module, 'test')
+ module = reflect.filenameToModuleName(os.path.join('twisted', 'test')
+ + os.path.sep)
+ self.assertEquals(module, 'test')
+
+ def test_file(self):
+ """
+ Test it finds good name for files.
+ """
+ module = reflect.filenameToModuleName(
+ os.path.join('twisted', 'test', 'test_reflect.py'))
+ self.assertEquals(module, 'test_reflect')
+
+
+
+class FullyQualifiedNameTests(unittest.TestCase):
+ """
+ Test for L{reflect.fullyQualifiedName}.
+ """
+
+ def _checkFullyQualifiedName(self, obj, expected):
+ """
+ Helper to check that fully qualified name of C{obj} results to
+ C{expected}.
+ """
+ self.assertEquals(
+ reflect.fullyQualifiedName(obj), expected)
+
+
+ def test_package(self):
+ """
+ L{reflect.fullyQualifiedName} returns the full name of a package and
+ a subpackage.
+ """
+ import twisted
+ self._checkFullyQualifiedName(twisted, 'twisted')
+ import twisted.python
+ self._checkFullyQualifiedName(twisted.python, 'twisted.python')
+
+
+ def test_module(self):
+ """
+ L{reflect.fullyQualifiedName} returns the name of a module inside a a
+ package.
+ """
+ self._checkFullyQualifiedName(reflect, 'twisted.python.reflect')
+ import twisted.trial.unittest
+ self._checkFullyQualifiedName(twisted.trial.unittest,
+ 'twisted.trial.unittest')
+
+
+ def test_class(self):
+ """
+ L{reflect.fullyQualifiedName} returns the name of a class and its
+ module.
+ """
+ self._checkFullyQualifiedName(reflect.Settable,
+ 'twisted.python.reflect.Settable')
+
+
+ def test_function(self):
+ """
+ L{reflect.fullyQualifiedName} returns the name of a function inside its
+ module.
+ """
+ self._checkFullyQualifiedName(reflect.fullyQualifiedName,
+ "twisted.python.reflect.fullyQualifiedName")
+
+
+ def test_method(self):
+ """
+ L{reflect.fullyQualifiedName} returns the name of a method inside its
+ class and its module.
+ """
+ self._checkFullyQualifiedName(reflect.PropertyAccessor.reallyDel,
+ "twisted.python.reflect.PropertyAccessor.reallyDel")
+ self._checkFullyQualifiedName(reflect.PropertyAccessor().reallyDel,
+ "twisted.python.reflect.PropertyAccessor.reallyDel")
+
+
+class DeprecationTestCase(unittest.TestCase):
+ """
+ Test deprecations in twisted.python.reflect
+ """
+
+ def test_macro(self):
+ """
+ Test deprecation of L{reflect.macro}.
+ """
+ result = self.callDeprecated(Version("Twisted", 8, 2, 0),
+ reflect.macro, "test", __file__, "test = 1")
+ self.assertEquals(result, 1)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_reflector.py b/vendor/Twisted-10.0.0/twisted/test/test_reflector.py
new file mode 100644
index 0000000000..0a1a16ca85
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_reflector.py
@@ -0,0 +1,401 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for twisted.enterprise reflectors.
+"""
+
+import random
+
+from twisted.internet import reactor, interfaces, defer
+from twisted.enterprise.row import RowObject
+from twisted.enterprise.reflector import EQUAL
+from twisted.enterprise.sqlreflector import SQLReflector
+from twisted.enterprise import util
+from twisted.test.test_adbapi import makeSQLTests
+from twisted.trial.util import suppress as suppressWarning
+from twisted.trial import unittest
+
+
+tableName = "testTable"
+childTableName = "childTable"
+
+
+class TestRow(RowObject):
+ rowColumns = [("key_string", "varchar"),
+ ("col2", "int"),
+ ("another_column", "varchar"),
+ ("Column4", "varchar"),
+ ("column_5_", "int")]
+ rowKeyColumns = [("key_string", "varchar")]
+ rowTableName = tableName
+
+
+class ChildRow(RowObject):
+ rowColumns = [("childId", "int"),
+ ("foo", "varchar"),
+ ("test_key", "varchar"),
+ ("stuff", "varchar"),
+ ("gogogo", "int"),
+ ("data", "varchar")]
+ rowKeyColumns = [("childId", "int")]
+ rowTableName = childTableName
+ rowForeignKeys = [(tableName,
+ [("test_key","varchar")],
+ [("key_string","varchar")],
+ None, 1)]
+
+
+main_table_schema = """
+CREATE TABLE testTable (
+ key_string varchar(64),
+ col2 integer,
+ another_column varchar(64),
+ Column4 varchar(64),
+ column_5_ integer
+)
+"""
+
+child_table_schema = """
+CREATE TABLE childTable (
+ childId integer,
+ foo varchar(64),
+ test_key varchar(64),
+ stuff varchar(64),
+ gogogo integer,
+ data varchar(64)
+)
+"""
+
+
+def randomizeRow(row, nulls_ok=True, trailing_spaces_ok=True):
+ values = {}
+ for name, type in row.rowColumns:
+ if util.getKeyColumn(row, name):
+ values[name] = getattr(row, name)
+ continue
+ elif nulls_ok and random.randint(0, 9) == 0:
+ value = None # null
+ elif type == 'int':
+ value = random.randint(-10000, 10000)
+ else:
+ if random.randint(0, 9) == 0:
+ value = ''
+ else:
+ value = ''.join(map(lambda i:chr(random.randrange(32,127)),
+ xrange(random.randint(1, 64))))
+ if not trailing_spaces_ok:
+ value = value.rstrip()
+ setattr(row, name, value)
+ values[name] = value
+ return values
+
+
+def rowMatches(row, values):
+ for name, type in row.rowColumns:
+ if getattr(row, name) != values[name]:
+ print ("Mismatch on column %s: |%s| (row) |%s| (values)" %
+ (name, getattr(row, name), values[name]))
+ return False
+ return True
+
+
+rowObjectSuppression = suppressWarning(
+ message="twisted.enterprise.row is deprecated since Twisted 8.0",
+ category=DeprecationWarning)
+
+
+reflectorSuppression = suppressWarning(
+ message="twisted.enterprise.reflector is deprecated since Twisted 8.0",
+ category=DeprecationWarning)
+
+
+class ReflectorTestBase:
+ """
+ Base class for testing reflectors.
+
+ @ivar reflector: The reflector created during setup.
+ """
+
+ if interfaces.IReactorThreads(reactor, None) is None:
+ skip = "No thread support, no reflector tests"
+
+ count = 100 # a parameter used for running iterative tests
+
+ def randomizeRow(self, row):
+ return randomizeRow(row, self.nulls_ok, self.trailing_spaces_ok)
+
+ def extraSetUp(self):
+ """
+ Create and store a reference to a SQL reflector for use by the tests.
+ """
+ d = self.createReflector()
+ d.addCallback(self._cbSetUp)
+ return d
+
+ def _cbSetUp(self, reflector):
+ self.reflector = reflector
+
+ def tearDown(self):
+ return self.destroyReflector()
+
+ def destroyReflector(self):
+ pass
+
+ def test_reflector(self):
+ """
+ Full featured tests of reflector.
+ """
+ # create one row to work with
+ row = TestRow()
+ row.assignKeyAttr("key_string", "first")
+ values = self.randomizeRow(row)
+
+ # save it
+ d = self.reflector.insertRow(row)
+
+ def _loadBack(_):
+ # now load it back in
+ whereClause = [("key_string", EQUAL, "first")]
+ d = self.reflector.loadObjectsFrom(tableName,
+ whereClause=whereClause)
+ return d.addCallback(self.gotData)
+
+ def _getParent(_):
+ # make sure it came back as what we saved
+ self.failUnless(len(self.data) == 1, "no row")
+ parent = self.data[0]
+ self.failUnless(rowMatches(parent, values), "no match")
+ return parent
+
+ d.addCallback(_loadBack)
+ d.addCallback(_getParent)
+ d.addCallback(self._cbTestReflector)
+ return d
+ test_reflector.suppress = [rowObjectSuppression, reflectorSuppression]
+
+ def _cbTestReflector(self, parent):
+ # create some child rows
+ test_values = {}
+ inserts = []
+ child_values = {}
+ for i in range(0, self.num_iterations):
+ row = ChildRow()
+ row.assignKeyAttr("childId", i)
+ values = self.randomizeRow(row)
+ values['test_key'] = row.test_key = "first"
+ child_values[i] = values
+ inserts.append(self.reflector.insertRow(row))
+ row = None
+ #del inserts
+ d = defer.gatherResults(inserts)
+ values = [None]
+
+ def _loadObjects(_):
+ d = self.reflector.loadObjectsFrom(childTableName, parentRow=parent)
+ return d.addCallback(self.gotData)
+
+ def _checkLoadObjects(_):
+ self.failUnless(len(self.data) == self.num_iterations,
+ "no rows on query")
+ self.failUnless(len(parent.childRows) == self.num_iterations,
+ "did not load child rows: %d" % len(parent.childRows))
+ for child in parent.childRows:
+ self.failUnless(rowMatches(child, child_values[child.childId]),
+ "child %d does not match" % child.childId)
+
+ def _checkLoadObjects2(_):
+ self.failUnless(len(self.data) == self.num_iterations,
+ "no rows on query")
+ self.failUnless(len(parent.childRows) == self.num_iterations,
+ "child rows added twice!: %d" % len(parent.childRows))
+
+ def _changeParent(_):
+ # now change the parent
+ values[0] = self.randomizeRow(parent)
+ return self.reflector.updateRow(parent)
+
+ def _loadBack(_):
+ # now load it back in
+ whereClause = [("key_string", EQUAL, "first")]
+ d = self.reflector.loadObjectsFrom(tableName, whereClause=whereClause)
+ return d.addCallback(self.gotData)
+
+ def _checkLoadBack(_):
+ # make sure it came back as what we saved
+ self.failUnless(len(self.data) == 1, "no row")
+ parent = self.data[0]
+ self.failUnless(rowMatches(parent, values[0]), "no match")
+ # save parent
+ test_values[parent.key_string] = values[0]
+ parent = None
+
+ def _saveMoreTestRows(_):
+ # save some more test rows
+ ds = []
+ for i in range(0, self.num_iterations):
+ row = TestRow()
+ row.assignKeyAttr("key_string", "bulk%d"%i)
+ test_values[row.key_string] = self.randomizeRow(row)
+ ds.append(self.reflector.insertRow(row))
+ return defer.gatherResults(ds)
+
+ def _loadRowsBack(_):
+ # now load them all back in
+ d = self.reflector.loadObjectsFrom("testTable")
+ return d.addCallback(self.gotData)
+
+ def _checkRowsBack(_):
+ # make sure they are the same
+ self.failUnless(len(self.data) == self.num_iterations + 1,
+ "query did not get rows")
+ for row in self.data:
+ self.failUnless(rowMatches(row, test_values[row.key_string]),
+ "child %s does not match" % row.key_string)
+
+ def _changeRows(_):
+ # now change them all
+ ds = []
+ for row in self.data:
+ test_values[row.key_string] = self.randomizeRow(row)
+ ds.append(self.reflector.updateRow(row))
+ d = defer.gatherResults(ds)
+ return d.addCallback(_cbChangeRows)
+
+ def _cbChangeRows(_):
+ self.data = None
+
+ def _deleteRows(_):
+ # now delete them
+ ds = []
+ for row in self.data:
+ ds.append(self.reflector.deleteRow(row))
+ d = defer.gatherResults(ds)
+ return d.addCallback(_cbChangeRows)
+
+ def _checkRowsDeleted(_):
+ self.failUnless(len(self.data) == 0, "rows were not deleted")
+
+ d.addCallback(_loadObjects)
+ d.addCallback(_checkLoadObjects)
+ d.addCallback(_loadObjects)
+ d.addCallback(_checkLoadObjects2)
+ d.addCallback(_changeParent)
+ d.addCallback(_loadBack)
+ d.addCallback(_checkLoadBack)
+ d.addCallback(_saveMoreTestRows)
+ d.addCallback(_loadRowsBack)
+ d.addCallback(_checkRowsBack)
+ d.addCallback(_changeRows)
+ d.addCallback(_loadRowsBack)
+ d.addCallback(_checkRowsBack)
+ d.addCallback(_deleteRows)
+ d.addCallback(_loadRowsBack)
+ d.addCallback(_checkRowsDeleted)
+ return d
+
+
+ def test_saveAndDelete(self):
+ """
+ Create a row and then try to delete it.
+ """
+ # create one row to work with
+ row = TestRow()
+ row.assignKeyAttr("key_string", "first")
+ values = self.randomizeRow(row)
+ # save it
+ d = self.reflector.insertRow(row)
+ def _deleteRow(_):
+ # delete it
+ return self.reflector.deleteRow(row)
+ d.addCallback(_deleteRow)
+ return d
+ test_saveAndDelete.suppress = [rowObjectSuppression, reflectorSuppression]
+
+
+ def gotData(self, data):
+ self.data = data
+
+
+ReflectorTestBase.timeout = 30.0
+
+
+class SQLReflectorTestBase(ReflectorTestBase):
+ """
+ Base class for the SQL reflector.
+ """
+
+ def createReflector(self):
+ self.startDB()
+ self.dbpool = self.makePool()
+ self.dbpool.start()
+
+ if self.can_clear:
+ d = self.dbpool.runOperation('DROP TABLE testTable')
+ d.addCallback(lambda _:
+ self.dbpool.runOperation('DROP TABLE childTable'))
+ d.addErrback(lambda _: None)
+ else:
+ d = defer.succeed(None)
+
+ d.addCallback(lambda _: self.dbpool.runOperation(main_table_schema))
+ d.addCallback(lambda _: self.dbpool.runOperation(child_table_schema))
+ reflectorClass = self.escape_slashes and SQLReflector \
+ or NoSlashSQLReflector
+ d.addCallback(lambda _:
+ reflectorClass(self.dbpool, [TestRow, ChildRow]))
+ return d
+
+ def destroyReflector(self):
+ d = self.dbpool.runOperation('DROP TABLE testTable')
+ d.addCallback(lambda _:
+ self.dbpool.runOperation('DROP TABLE childTable'))
+ def close(_):
+ self.dbpool.close()
+ self.stopDB()
+ d.addCallback(close)
+ return d
+
+
+class DeprecationTestCase(unittest.TestCase):
+ """
+ Test various deprecations of twisted.enterprise.
+ """
+
+ def test_rowDeprecation(self):
+ """
+ Test deprecation of L{RowObject}.
+ """
+ def wrapper():
+ return TestRow()
+ self.assertWarns(DeprecationWarning,
+ "twisted.enterprise.row is deprecated since Twisted 8.0",
+ __file__,
+ wrapper)
+
+ def test_reflectorDeprecation(self):
+ """
+ Test deprecation of L{SQLReflector}.
+ """
+ def wrapper():
+ return SQLReflector(None, ())
+ from twisted.enterprise import sqlreflector
+ self.assertWarns(DeprecationWarning,
+ "twisted.enterprise.reflector is deprecated since Twisted 8.0",
+ sqlreflector.__file__,
+ wrapper)
+
+
+# GadflyReflectorTestCase SQLiteReflectorTestCase PyPgSQLReflectorTestCase
+# PsycopgReflectorTestCase MySQLReflectorTestCase FirebirdReflectorTestCase
+makeSQLTests(SQLReflectorTestBase, 'ReflectorTestCase', globals())
+
+
+class NoSlashSQLReflector(SQLReflector):
+ """
+ An sql reflector that only escapes single quotes.
+ """
+
+ def escape_string(self, text):
+ return text.replace("'", "''")
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_roots.py b/vendor/Twisted-10.0.0/twisted/test/test_roots.py
new file mode 100644
index 0000000000..023b8fe176
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_roots.py
@@ -0,0 +1,63 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.trial import unittest
+from twisted.python import roots
+import types
+
+class RootsTest(unittest.TestCase):
+
+ def testExceptions(self):
+ request = roots.Request()
+ try:
+ request.write("blah")
+ except NotImplementedError:
+ pass
+ else:
+ self.fail()
+ try:
+ request.finish()
+ except NotImplementedError:
+ pass
+ else:
+ self.fail()
+
+ def testCollection(self):
+ collection = roots.Collection()
+ collection.putEntity("x", 'test')
+ self.failUnlessEqual(collection.getStaticEntity("x"),
+ 'test')
+ collection.delEntity("x")
+ self.failUnlessEqual(collection.getStaticEntity('x'),
+ None)
+ try:
+ collection.storeEntity("x", None)
+ except NotImplementedError:
+ pass
+ else:
+ self.fail()
+ try:
+ collection.removeEntity("x", None)
+ except NotImplementedError:
+ pass
+ else:
+ self.fail()
+
+ def testConstrained(self):
+ class const(roots.Constrained):
+ def nameConstraint(self, name):
+ return (name == 'x')
+ c = const()
+ self.failUnlessEqual(c.putEntity('x', 'test'), None)
+ self.failUnlessRaises(roots.ConstraintViolation,
+ c.putEntity, 'y', 'test')
+
+
+ def testHomogenous(self):
+ h = roots.Homogenous()
+ h.entityType = types.IntType
+ h.putEntity('a', 1)
+ self.failUnlessEqual(h.getStaticEntity('a'),1 )
+ self.failUnlessRaises(roots.ConstraintViolation,
+ h.putEntity, 'x', 'y')
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_shortcut.py b/vendor/Twisted-10.0.0/twisted/test/test_shortcut.py
new file mode 100644
index 0000000000..fdcb77537c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_shortcut.py
@@ -0,0 +1,26 @@
+"""Test win32 shortcut script
+"""
+
+from twisted.trial import unittest
+
+import os
+if os.name == 'nt':
+
+ skipWindowsNopywin32 = None
+ try:
+ from twisted.python import shortcut
+ except ImportError:
+ skipWindowsNopywin32 = ("On windows, twisted.python.shortcut is not "
+ "available in the absence of win32com.")
+ import os.path
+ import sys
+
+ class ShortcutTest(unittest.TestCase):
+ def testCreate(self):
+ s1=shortcut.Shortcut("test_shortcut.py")
+ tempname=self.mktemp() + '.lnk'
+ s1.save(tempname)
+ self.assert_(os.path.exists(tempname))
+ sc=shortcut.open(tempname)
+ self.assert_(sc.GetPath(0)[0].endswith('test_shortcut.py'))
+ ShortcutTest.skip = skipWindowsNopywin32
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_sip.py b/vendor/Twisted-10.0.0/twisted/test/test_sip.py
new file mode 100644
index 0000000000..465a0a4183
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_sip.py
@@ -0,0 +1,942 @@
+# -*- test-case-name: twisted.test.test_sip -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Session Initialization Protocol tests."""
+
+from twisted.trial import unittest, util
+from twisted.protocols import sip
+from twisted.internet import defer, reactor, utils
+from twisted.python.versions import Version
+
+from twisted.test import proto_helpers
+
+from twisted import cred
+import twisted.cred.portal
+import twisted.cred.checkers
+
+from zope.interface import implements
+
+
+# request, prefixed by random CRLFs
+request1 = "\n\r\n\n\r" + """\
+INVITE sip:foo SIP/2.0
+From: mo
+To: joe
+Content-Length: 4
+
+abcd""".replace("\n", "\r\n")
+
+# request, no content-length
+request2 = """INVITE sip:foo SIP/2.0
+From: mo
+To: joe
+
+1234""".replace("\n", "\r\n")
+
+# request, with garbage after
+request3 = """INVITE sip:foo SIP/2.0
+From: mo
+To: joe
+Content-Length: 4
+
+1234
+
+lalalal""".replace("\n", "\r\n")
+
+# three requests
+request4 = """INVITE sip:foo SIP/2.0
+From: mo
+To: joe
+Content-Length: 0
+
+INVITE sip:loop SIP/2.0
+From: foo
+To: bar
+Content-Length: 4
+
+abcdINVITE sip:loop SIP/2.0
+From: foo
+To: bar
+Content-Length: 4
+
+1234""".replace("\n", "\r\n")
+
+# response, no content
+response1 = """SIP/2.0 200 OK
+From: foo
+To:bar
+Content-Length: 0
+
+""".replace("\n", "\r\n")
+
+# short header version
+request_short = """\
+INVITE sip:foo SIP/2.0
+f: mo
+t: joe
+l: 4
+
+abcd""".replace("\n", "\r\n")
+
+request_natted = """\
+INVITE sip:foo SIP/2.0
+Via: SIP/2.0/UDP 10.0.0.1:5060;rport
+
+""".replace("\n", "\r\n")
+
+class TestRealm:
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ return sip.IContact, None, lambda: None
+
+class MessageParsingTestCase(unittest.TestCase):
+ def setUp(self):
+ self.l = []
+ self.parser = sip.MessagesParser(self.l.append)
+
+ def feedMessage(self, message):
+ self.parser.dataReceived(message)
+ self.parser.dataDone()
+
+ def validateMessage(self, m, method, uri, headers, body):
+ """Validate Requests."""
+ self.assertEquals(m.method, method)
+ self.assertEquals(m.uri.toString(), uri)
+ self.assertEquals(m.headers, headers)
+ self.assertEquals(m.body, body)
+ self.assertEquals(m.finished, 1)
+
+ def testSimple(self):
+ l = self.l
+ self.feedMessage(request1)
+ self.assertEquals(len(l), 1)
+ self.validateMessage(
+ l[0], "INVITE", "sip:foo",
+ {"from": ["mo"], "to": ["joe"], "content-length": ["4"]},
+ "abcd")
+
+ def testTwoMessages(self):
+ l = self.l
+ self.feedMessage(request1)
+ self.feedMessage(request2)
+ self.assertEquals(len(l), 2)
+ self.validateMessage(
+ l[0], "INVITE", "sip:foo",
+ {"from": ["mo"], "to": ["joe"], "content-length": ["4"]},
+ "abcd")
+ self.validateMessage(l[1], "INVITE", "sip:foo",
+ {"from": ["mo"], "to": ["joe"]},
+ "1234")
+
+ def testGarbage(self):
+ l = self.l
+ self.feedMessage(request3)
+ self.assertEquals(len(l), 1)
+ self.validateMessage(
+ l[0], "INVITE", "sip:foo",
+ {"from": ["mo"], "to": ["joe"], "content-length": ["4"]},
+ "1234")
+
+ def testThreeInOne(self):
+ l = self.l
+ self.feedMessage(request4)
+ self.assertEquals(len(l), 3)
+ self.validateMessage(
+ l[0], "INVITE", "sip:foo",
+ {"from": ["mo"], "to": ["joe"], "content-length": ["0"]},
+ "")
+ self.validateMessage(
+ l[1], "INVITE", "sip:loop",
+ {"from": ["foo"], "to": ["bar"], "content-length": ["4"]},
+ "abcd")
+ self.validateMessage(
+ l[2], "INVITE", "sip:loop",
+ {"from": ["foo"], "to": ["bar"], "content-length": ["4"]},
+ "1234")
+
+ def testShort(self):
+ l = self.l
+ self.feedMessage(request_short)
+ self.assertEquals(len(l), 1)
+ self.validateMessage(
+ l[0], "INVITE", "sip:foo",
+ {"from": ["mo"], "to": ["joe"], "content-length": ["4"]},
+ "abcd")
+
+ def testSimpleResponse(self):
+ l = self.l
+ self.feedMessage(response1)
+ self.assertEquals(len(l), 1)
+ m = l[0]
+ self.assertEquals(m.code, 200)
+ self.assertEquals(m.phrase, "OK")
+ self.assertEquals(
+ m.headers,
+ {"from": ["foo"], "to": ["bar"], "content-length": ["0"]})
+ self.assertEquals(m.body, "")
+ self.assertEquals(m.finished, 1)
+
+
+class MessageParsingTestCase2(MessageParsingTestCase):
+ """Same as base class, but feed data char by char."""
+
+ def feedMessage(self, message):
+ for c in message:
+ self.parser.dataReceived(c)
+ self.parser.dataDone()
+
+
+class MakeMessageTestCase(unittest.TestCase):
+
+ def testRequest(self):
+ r = sip.Request("INVITE", "sip:foo")
+ r.addHeader("foo", "bar")
+ self.assertEquals(
+ r.toString(),
+ "INVITE sip:foo SIP/2.0\r\nFoo: bar\r\n\r\n")
+
+ def testResponse(self):
+ r = sip.Response(200, "OK")
+ r.addHeader("foo", "bar")
+ r.addHeader("Content-Length", "4")
+ r.bodyDataReceived("1234")
+ self.assertEquals(
+ r.toString(),
+ "SIP/2.0 200 OK\r\nFoo: bar\r\nContent-Length: 4\r\n\r\n1234")
+
+ def testStatusCode(self):
+ r = sip.Response(200)
+ self.assertEquals(r.toString(), "SIP/2.0 200 OK\r\n\r\n")
+
+
+class ViaTestCase(unittest.TestCase):
+
+ def checkRoundtrip(self, v):
+ s = v.toString()
+ self.assertEquals(s, sip.parseViaHeader(s).toString())
+
+ def testExtraWhitespace(self):
+ v1 = sip.parseViaHeader('SIP/2.0/UDP 192.168.1.1:5060')
+ v2 = sip.parseViaHeader('SIP/2.0/UDP 192.168.1.1:5060')
+ self.assertEquals(v1.transport, v2.transport)
+ self.assertEquals(v1.host, v2.host)
+ self.assertEquals(v1.port, v2.port)
+
+ def test_complex(self):
+ """
+ Test parsing a Via header with one of everything.
+ """
+ s = ("SIP/2.0/UDP first.example.com:4000;ttl=16;maddr=224.2.0.1"
+ " ;branch=a7c6a8dlze (Example)")
+ v = sip.parseViaHeader(s)
+ self.assertEquals(v.transport, "UDP")
+ self.assertEquals(v.host, "first.example.com")
+ self.assertEquals(v.port, 4000)
+ self.assertEquals(v.rport, None)
+ self.assertEquals(v.rportValue, None)
+ self.assertEquals(v.rportRequested, False)
+ self.assertEquals(v.ttl, 16)
+ self.assertEquals(v.maddr, "224.2.0.1")
+ self.assertEquals(v.branch, "a7c6a8dlze")
+ self.assertEquals(v.hidden, 0)
+ self.assertEquals(v.toString(),
+ "SIP/2.0/UDP first.example.com:4000"
+ ";ttl=16;branch=a7c6a8dlze;maddr=224.2.0.1")
+ self.checkRoundtrip(v)
+
+ def test_simple(self):
+ """
+ Test parsing a simple Via header.
+ """
+ s = "SIP/2.0/UDP example.com;hidden"
+ v = sip.parseViaHeader(s)
+ self.assertEquals(v.transport, "UDP")
+ self.assertEquals(v.host, "example.com")
+ self.assertEquals(v.port, 5060)
+ self.assertEquals(v.rport, None)
+ self.assertEquals(v.rportValue, None)
+ self.assertEquals(v.rportRequested, False)
+ self.assertEquals(v.ttl, None)
+ self.assertEquals(v.maddr, None)
+ self.assertEquals(v.branch, None)
+ self.assertEquals(v.hidden, True)
+ self.assertEquals(v.toString(),
+ "SIP/2.0/UDP example.com:5060;hidden")
+ self.checkRoundtrip(v)
+
+ def testSimpler(self):
+ v = sip.Via("example.com")
+ self.checkRoundtrip(v)
+
+
+ def test_deprecatedRPort(self):
+ """
+ Setting rport to True is deprecated, but still produces a Via header
+ with the expected properties.
+ """
+ v = sip.Via("foo.bar", rport=True)
+
+ warnings = self.flushWarnings(
+ offendingFunctions=[self.test_deprecatedRPort])
+ self.assertEqual(len(warnings), 1)
+ self.assertEqual(
+ warnings[0]['message'],
+ 'rport=True is deprecated since Twisted 9.0.')
+ self.assertEqual(
+ warnings[0]['category'],
+ DeprecationWarning)
+
+ self.assertEqual(v.toString(), "SIP/2.0/UDP foo.bar:5060;rport")
+ self.assertEqual(v.rport, True)
+ self.assertEqual(v.rportRequested, True)
+ self.assertEqual(v.rportValue, None)
+
+
+ def test_rport(self):
+ """
+ An rport setting of None should insert the parameter with no value.
+ """
+ v = sip.Via("foo.bar", rport=None)
+ self.assertEqual(v.toString(), "SIP/2.0/UDP foo.bar:5060;rport")
+ self.assertEqual(v.rportRequested, True)
+ self.assertEqual(v.rportValue, None)
+
+
+ def test_rportValue(self):
+ """
+ An rport numeric setting should insert the parameter with the number
+ value given.
+ """
+ v = sip.Via("foo.bar", rport=1)
+ self.assertEqual(v.toString(), "SIP/2.0/UDP foo.bar:5060;rport=1")
+ self.assertEqual(v.rportRequested, False)
+ self.assertEqual(v.rportValue, 1)
+ self.assertEqual(v.rport, 1)
+
+
+ def testNAT(self):
+ s = "SIP/2.0/UDP 10.0.0.1:5060;received=22.13.1.5;rport=12345"
+ v = sip.parseViaHeader(s)
+ self.assertEquals(v.transport, "UDP")
+ self.assertEquals(v.host, "10.0.0.1")
+ self.assertEquals(v.port, 5060)
+ self.assertEquals(v.received, "22.13.1.5")
+ self.assertEquals(v.rport, 12345)
+
+ self.assertNotEquals(v.toString().find("rport=12345"), -1)
+
+
+ def test_unknownParams(self):
+ """
+ Parsing and serializing Via headers with unknown parameters should work.
+ """
+ s = "SIP/2.0/UDP example.com:5060;branch=a12345b;bogus;pie=delicious"
+ v = sip.parseViaHeader(s)
+ self.assertEqual(v.toString(), s)
+
+
+
+class URLTestCase(unittest.TestCase):
+
+ def testRoundtrip(self):
+ for url in [
+ "sip:j.doe@big.com",
+ "sip:j.doe:secret@big.com;transport=tcp",
+ "sip:j.doe@big.com?subject=project",
+ "sip:example.com",
+ ]:
+ self.assertEquals(sip.parseURL(url).toString(), url)
+
+ def testComplex(self):
+ s = ("sip:user:pass@hosta:123;transport=udp;user=phone;method=foo;"
+ "ttl=12;maddr=1.2.3.4;blah;goo=bar?a=b&c=d")
+ url = sip.parseURL(s)
+ for k, v in [("username", "user"), ("password", "pass"),
+ ("host", "hosta"), ("port", 123),
+ ("transport", "udp"), ("usertype", "phone"),
+ ("method", "foo"), ("ttl", 12),
+ ("maddr", "1.2.3.4"), ("other", ["blah", "goo=bar"]),
+ ("headers", {"a": "b", "c": "d"})]:
+ self.assertEquals(getattr(url, k), v)
+
+
+class ParseTestCase(unittest.TestCase):
+
+ def testParseAddress(self):
+ for address, name, urls, params in [
+ ('"A. G. Bell" <sip:foo@example.com>',
+ "A. G. Bell", "sip:foo@example.com", {}),
+ ("Anon <sip:foo@example.com>", "Anon", "sip:foo@example.com", {}),
+ ("sip:foo@example.com", "", "sip:foo@example.com", {}),
+ ("<sip:foo@example.com>", "", "sip:foo@example.com", {}),
+ ("foo <sip:foo@example.com>;tag=bar;foo=baz", "foo",
+ "sip:foo@example.com", {"tag": "bar", "foo": "baz"}),
+ ]:
+ gname, gurl, gparams = sip.parseAddress(address)
+ self.assertEquals(name, gname)
+ self.assertEquals(gurl.toString(), urls)
+ self.assertEquals(gparams, params)
+
+
+class DummyLocator:
+ implements(sip.ILocator)
+ def getAddress(self, logicalURL):
+ return defer.succeed(sip.URL("server.com", port=5060))
+
+class FailingLocator:
+ implements(sip.ILocator)
+ def getAddress(self, logicalURL):
+ return defer.fail(LookupError())
+
+
+class ProxyTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.proxy = sip.Proxy("127.0.0.1")
+ self.proxy.locator = DummyLocator()
+ self.sent = []
+ self.proxy.sendMessage = lambda dest, msg: self.sent.append((dest, msg))
+
+ def testRequestForward(self):
+ r = sip.Request("INVITE", "sip:foo")
+ r.addHeader("via", sip.Via("1.2.3.4").toString())
+ r.addHeader("via", sip.Via("1.2.3.5").toString())
+ r.addHeader("foo", "bar")
+ r.addHeader("to", "<sip:joe@server.com>")
+ r.addHeader("contact", "<sip:joe@1.2.3.5>")
+ self.proxy.datagramReceived(r.toString(), ("1.2.3.4", 5060))
+ self.assertEquals(len(self.sent), 1)
+ dest, m = self.sent[0]
+ self.assertEquals(dest.port, 5060)
+ self.assertEquals(dest.host, "server.com")
+ self.assertEquals(m.uri.toString(), "sip:foo")
+ self.assertEquals(m.method, "INVITE")
+ self.assertEquals(m.headers["via"],
+ ["SIP/2.0/UDP 127.0.0.1:5060",
+ "SIP/2.0/UDP 1.2.3.4:5060",
+ "SIP/2.0/UDP 1.2.3.5:5060"])
+
+
+ def testReceivedRequestForward(self):
+ r = sip.Request("INVITE", "sip:foo")
+ r.addHeader("via", sip.Via("1.2.3.4").toString())
+ r.addHeader("foo", "bar")
+ r.addHeader("to", "<sip:joe@server.com>")
+ r.addHeader("contact", "<sip:joe@1.2.3.4>")
+ self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060))
+ dest, m = self.sent[0]
+ self.assertEquals(m.headers["via"],
+ ["SIP/2.0/UDP 127.0.0.1:5060",
+ "SIP/2.0/UDP 1.2.3.4:5060;received=1.1.1.1"])
+
+
+ def testResponseWrongVia(self):
+ # first via must match proxy's address
+ r = sip.Response(200)
+ r.addHeader("via", sip.Via("foo.com").toString())
+ self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060))
+ self.assertEquals(len(self.sent), 0)
+
+ def testResponseForward(self):
+ r = sip.Response(200)
+ r.addHeader("via", sip.Via("127.0.0.1").toString())
+ r.addHeader("via", sip.Via("client.com", port=1234).toString())
+ self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060))
+ self.assertEquals(len(self.sent), 1)
+ dest, m = self.sent[0]
+ self.assertEquals((dest.host, dest.port), ("client.com", 1234))
+ self.assertEquals(m.code, 200)
+ self.assertEquals(m.headers["via"], ["SIP/2.0/UDP client.com:1234"])
+
+ def testReceivedResponseForward(self):
+ r = sip.Response(200)
+ r.addHeader("via", sip.Via("127.0.0.1").toString())
+ r.addHeader(
+ "via",
+ sip.Via("10.0.0.1", received="client.com").toString())
+ self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060))
+ self.assertEquals(len(self.sent), 1)
+ dest, m = self.sent[0]
+ self.assertEquals((dest.host, dest.port), ("client.com", 5060))
+
+ def testResponseToUs(self):
+ r = sip.Response(200)
+ r.addHeader("via", sip.Via("127.0.0.1").toString())
+ l = []
+ self.proxy.gotResponse = lambda *a: l.append(a)
+ self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060))
+ self.assertEquals(len(l), 1)
+ m, addr = l[0]
+ self.assertEquals(len(m.headers.get("via", [])), 0)
+ self.assertEquals(m.code, 200)
+
+ def testLoop(self):
+ r = sip.Request("INVITE", "sip:foo")
+ r.addHeader("via", sip.Via("1.2.3.4").toString())
+ r.addHeader("via", sip.Via("127.0.0.1").toString())
+ self.proxy.datagramReceived(r.toString(), ("client.com", 5060))
+ self.assertEquals(self.sent, [])
+
+ def testCantForwardRequest(self):
+ r = sip.Request("INVITE", "sip:foo")
+ r.addHeader("via", sip.Via("1.2.3.4").toString())
+ r.addHeader("to", "<sip:joe@server.com>")
+ self.proxy.locator = FailingLocator()
+ self.proxy.datagramReceived(r.toString(), ("1.2.3.4", 5060))
+ self.assertEquals(len(self.sent), 1)
+ dest, m = self.sent[0]
+ self.assertEquals((dest.host, dest.port), ("1.2.3.4", 5060))
+ self.assertEquals(m.code, 404)
+ self.assertEquals(m.headers["via"], ["SIP/2.0/UDP 1.2.3.4:5060"])
+
+ def testCantForwardResponse(self):
+ pass
+
+ #testCantForwardResponse.skip = "not implemented yet"
+
+
+class RegistrationTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.proxy = sip.RegisterProxy(host="127.0.0.1")
+ self.registry = sip.InMemoryRegistry("bell.example.com")
+ self.proxy.registry = self.proxy.locator = self.registry
+ self.sent = []
+ self.proxy.sendMessage = lambda dest, msg: self.sent.append((dest, msg))
+ setUp = utils.suppressWarnings(setUp,
+ util.suppress(category=DeprecationWarning,
+ message=r'twisted.protocols.sip.DigestAuthorizer was deprecated'))
+
+ def tearDown(self):
+ for d, uri in self.registry.users.values():
+ d.cancel()
+ del self.proxy
+
+ def register(self):
+ r = sip.Request("REGISTER", "sip:bell.example.com")
+ r.addHeader("to", "sip:joe@bell.example.com")
+ r.addHeader("contact", "sip:joe@client.com:1234")
+ r.addHeader("via", sip.Via("client.com").toString())
+ self.proxy.datagramReceived(r.toString(), ("client.com", 5060))
+
+ def unregister(self):
+ r = sip.Request("REGISTER", "sip:bell.example.com")
+ r.addHeader("to", "sip:joe@bell.example.com")
+ r.addHeader("contact", "*")
+ r.addHeader("via", sip.Via("client.com").toString())
+ r.addHeader("expires", "0")
+ self.proxy.datagramReceived(r.toString(), ("client.com", 5060))
+
+ def testRegister(self):
+ self.register()
+ dest, m = self.sent[0]
+ self.assertEquals((dest.host, dest.port), ("client.com", 5060))
+ self.assertEquals(m.code, 200)
+ self.assertEquals(m.headers["via"], ["SIP/2.0/UDP client.com:5060"])
+ self.assertEquals(m.headers["to"], ["sip:joe@bell.example.com"])
+ self.assertEquals(m.headers["contact"], ["sip:joe@client.com:5060"])
+ self.failUnless(
+ int(m.headers["expires"][0]) in (3600, 3601, 3599, 3598))
+ self.assertEquals(len(self.registry.users), 1)
+ dc, uri = self.registry.users["joe"]
+ self.assertEquals(uri.toString(), "sip:joe@client.com:5060")
+ d = self.proxy.locator.getAddress(sip.URL(username="joe",
+ host="bell.example.com"))
+ d.addCallback(lambda desturl : (desturl.host, desturl.port))
+ d.addCallback(self.assertEquals, ('client.com', 5060))
+ return d
+
+ def testUnregister(self):
+ self.register()
+ self.unregister()
+ dest, m = self.sent[1]
+ self.assertEquals((dest.host, dest.port), ("client.com", 5060))
+ self.assertEquals(m.code, 200)
+ self.assertEquals(m.headers["via"], ["SIP/2.0/UDP client.com:5060"])
+ self.assertEquals(m.headers["to"], ["sip:joe@bell.example.com"])
+ self.assertEquals(m.headers["contact"], ["sip:joe@client.com:5060"])
+ self.assertEquals(m.headers["expires"], ["0"])
+ self.assertEquals(self.registry.users, {})
+
+
+ def addPortal(self):
+ r = TestRealm()
+ p = cred.portal.Portal(r)
+ c = cred.checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ c.addUser('userXname@127.0.0.1', 'passXword')
+ p.registerChecker(c)
+ self.proxy.portal = p
+
+ def testFailedAuthentication(self):
+ self.addPortal()
+ self.register()
+
+ self.assertEquals(len(self.registry.users), 0)
+ self.assertEquals(len(self.sent), 1)
+ dest, m = self.sent[0]
+ self.assertEquals(m.code, 401)
+
+
+ def test_basicAuthentication(self):
+ """
+ Test that registration with basic authentication suceeds.
+ """
+ self.addPortal()
+ self.proxy.authorizers = self.proxy.authorizers.copy()
+ self.proxy.authorizers['basic'] = sip.BasicAuthorizer()
+ warnings = self.flushWarnings(
+ offendingFunctions=[self.test_basicAuthentication])
+ self.assertEqual(len(warnings), 1)
+ self.assertEqual(
+ warnings[0]['message'],
+ "twisted.protocols.sip.BasicAuthorizer was deprecated in "
+ "Twisted 9.0.0")
+ self.assertEqual(
+ warnings[0]['category'],
+ DeprecationWarning)
+ r = sip.Request("REGISTER", "sip:bell.example.com")
+ r.addHeader("to", "sip:joe@bell.example.com")
+ r.addHeader("contact", "sip:joe@client.com:1234")
+ r.addHeader("via", sip.Via("client.com").toString())
+ r.addHeader("authorization",
+ "Basic " + "userXname:passXword".encode('base64'))
+ self.proxy.datagramReceived(r.toString(), ("client.com", 5060))
+
+ self.assertEquals(len(self.registry.users), 1)
+ self.assertEquals(len(self.sent), 1)
+ dest, m = self.sent[0]
+ self.assertEquals(m.code, 200)
+
+
+ def test_failedBasicAuthentication(self):
+ """
+ Failed registration with basic authentication results in an
+ unauthorized error response.
+ """
+ self.addPortal()
+ self.proxy.authorizers = self.proxy.authorizers.copy()
+ self.proxy.authorizers['basic'] = sip.BasicAuthorizer()
+ warnings = self.flushWarnings(
+ offendingFunctions=[self.test_failedBasicAuthentication])
+ self.assertEqual(len(warnings), 1)
+ self.assertEqual(
+ warnings[0]['message'],
+ "twisted.protocols.sip.BasicAuthorizer was deprecated in "
+ "Twisted 9.0.0")
+ self.assertEqual(
+ warnings[0]['category'],
+ DeprecationWarning)
+ r = sip.Request("REGISTER", "sip:bell.example.com")
+ r.addHeader("to", "sip:joe@bell.example.com")
+ r.addHeader("contact", "sip:joe@client.com:1234")
+ r.addHeader("via", sip.Via("client.com").toString())
+ r.addHeader(
+ "authorization", "Basic " + "userXname:password".encode('base64'))
+ self.proxy.datagramReceived(r.toString(), ("client.com", 5060))
+
+ self.assertEquals(len(self.registry.users), 0)
+ self.assertEquals(len(self.sent), 1)
+ dest, m = self.sent[0]
+ self.assertEquals(m.code, 401)
+
+
+ def testWrongDomainRegister(self):
+ r = sip.Request("REGISTER", "sip:wrong.com")
+ r.addHeader("to", "sip:joe@bell.example.com")
+ r.addHeader("contact", "sip:joe@client.com:1234")
+ r.addHeader("via", sip.Via("client.com").toString())
+ self.proxy.datagramReceived(r.toString(), ("client.com", 5060))
+ self.assertEquals(len(self.sent), 0)
+
+ def testWrongToDomainRegister(self):
+ r = sip.Request("REGISTER", "sip:bell.example.com")
+ r.addHeader("to", "sip:joe@foo.com")
+ r.addHeader("contact", "sip:joe@client.com:1234")
+ r.addHeader("via", sip.Via("client.com").toString())
+ self.proxy.datagramReceived(r.toString(), ("client.com", 5060))
+ self.assertEquals(len(self.sent), 0)
+
+ def testWrongDomainLookup(self):
+ self.register()
+ url = sip.URL(username="joe", host="foo.com")
+ d = self.proxy.locator.getAddress(url)
+ self.assertFailure(d, LookupError)
+ return d
+
+ def testNoContactLookup(self):
+ self.register()
+ url = sip.URL(username="jane", host="bell.example.com")
+ d = self.proxy.locator.getAddress(url)
+ self.assertFailure(d, LookupError)
+ return d
+
+
+class Client(sip.Base):
+
+ def __init__(self):
+ sip.Base.__init__(self)
+ self.received = []
+ self.deferred = defer.Deferred()
+
+ def handle_response(self, response, addr):
+ self.received.append(response)
+ self.deferred.callback(self.received)
+
+
+class LiveTest(unittest.TestCase):
+
+ def setUp(self):
+ self.proxy = sip.RegisterProxy(host="127.0.0.1")
+ self.registry = sip.InMemoryRegistry("bell.example.com")
+ self.proxy.registry = self.proxy.locator = self.registry
+ self.serverPort = reactor.listenUDP(
+ 0, self.proxy, interface="127.0.0.1")
+ self.client = Client()
+ self.clientPort = reactor.listenUDP(
+ 0, self.client, interface="127.0.0.1")
+ self.serverAddress = (self.serverPort.getHost().host,
+ self.serverPort.getHost().port)
+ setUp = utils.suppressWarnings(setUp,
+ util.suppress(category=DeprecationWarning,
+ message=r'twisted.protocols.sip.DigestAuthorizer was deprecated'))
+
+ def tearDown(self):
+ for d, uri in self.registry.users.values():
+ d.cancel()
+ d1 = defer.maybeDeferred(self.clientPort.stopListening)
+ d2 = defer.maybeDeferred(self.serverPort.stopListening)
+ return defer.gatherResults([d1, d2])
+
+ def testRegister(self):
+ p = self.clientPort.getHost().port
+ r = sip.Request("REGISTER", "sip:bell.example.com")
+ r.addHeader("to", "sip:joe@bell.example.com")
+ r.addHeader("contact", "sip:joe@127.0.0.1:%d" % p)
+ r.addHeader("via", sip.Via("127.0.0.1", port=p).toString())
+ self.client.sendMessage(
+ sip.URL(host="127.0.0.1", port=self.serverAddress[1]), r)
+ d = self.client.deferred
+ def check(received):
+ self.assertEquals(len(received), 1)
+ r = received[0]
+ self.assertEquals(r.code, 200)
+ d.addCallback(check)
+ return d
+
+ def test_amoralRPort(self):
+ """
+ rport is allowed without a value, apparently because server
+ implementors might be too stupid to check the received port
+ against 5060 and see if they're equal, and because client
+ implementors might be too stupid to bind to port 5060, or set a
+ value on the rport parameter they send if they bind to another
+ port.
+ """
+ p = self.clientPort.getHost().port
+ r = sip.Request("REGISTER", "sip:bell.example.com")
+ r.addHeader("to", "sip:joe@bell.example.com")
+ r.addHeader("contact", "sip:joe@127.0.0.1:%d" % p)
+ r.addHeader("via", sip.Via("127.0.0.1", port=p, rport=True).toString())
+ warnings = self.flushWarnings(
+ offendingFunctions=[self.test_amoralRPort])
+ self.assertEqual(len(warnings), 1)
+ self.assertEqual(
+ warnings[0]['message'],
+ 'rport=True is deprecated since Twisted 9.0.')
+ self.assertEqual(
+ warnings[0]['category'],
+ DeprecationWarning)
+ self.client.sendMessage(sip.URL(host="127.0.0.1",
+ port=self.serverAddress[1]),
+ r)
+ d = self.client.deferred
+ def check(received):
+ self.assertEquals(len(received), 1)
+ r = received[0]
+ self.assertEquals(r.code, 200)
+ d.addCallback(check)
+ return d
+
+
+
+registerRequest = """
+REGISTER sip:intarweb.us SIP/2.0\r
+Via: SIP/2.0/UDP 192.168.1.100:50609\r
+From: <sip:exarkun@intarweb.us:50609>\r
+To: <sip:exarkun@intarweb.us:50609>\r
+Contact: "exarkun" <sip:exarkun@192.168.1.100:50609>\r
+Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r
+CSeq: 9898 REGISTER\r
+Expires: 500\r
+User-Agent: X-Lite build 1061\r
+Content-Length: 0\r
+\r
+"""
+
+challengeResponse = """\
+SIP/2.0 401 Unauthorized\r
+Via: SIP/2.0/UDP 192.168.1.100:50609;received=127.0.0.1;rport=5632\r
+To: <sip:exarkun@intarweb.us:50609>\r
+From: <sip:exarkun@intarweb.us:50609>\r
+Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r
+CSeq: 9898 REGISTER\r
+WWW-Authenticate: Digest nonce="92956076410767313901322208775",opaque="1674186428",qop-options="auth",algorithm="MD5",realm="intarweb.us"\r
+\r
+"""
+
+authRequest = """\
+REGISTER sip:intarweb.us SIP/2.0\r
+Via: SIP/2.0/UDP 192.168.1.100:50609\r
+From: <sip:exarkun@intarweb.us:50609>\r
+To: <sip:exarkun@intarweb.us:50609>\r
+Contact: "exarkun" <sip:exarkun@192.168.1.100:50609>\r
+Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r
+CSeq: 9899 REGISTER\r
+Expires: 500\r
+Authorization: Digest username="exarkun",realm="intarweb.us",nonce="92956076410767313901322208775",response="4a47980eea31694f997369214292374b",uri="sip:intarweb.us",algorithm=MD5,opaque="1674186428"\r
+User-Agent: X-Lite build 1061\r
+Content-Length: 0\r
+\r
+"""
+
+okResponse = """\
+SIP/2.0 200 OK\r
+Via: SIP/2.0/UDP 192.168.1.100:50609;received=127.0.0.1;rport=5632\r
+To: <sip:exarkun@intarweb.us:50609>\r
+From: <sip:exarkun@intarweb.us:50609>\r
+Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r
+CSeq: 9899 REGISTER\r
+Contact: sip:exarkun@127.0.0.1:5632\r
+Expires: 3600\r
+Content-Length: 0\r
+\r
+"""
+
+class FakeDigestAuthorizer(sip.DigestAuthorizer):
+ def generateNonce(self):
+ return '92956076410767313901322208775'
+ def generateOpaque(self):
+ return '1674186428'
+
+
+class FakeRegistry(sip.InMemoryRegistry):
+ """Make sure expiration is always seen to be 3600.
+
+ Otherwise slow reactors fail tests incorrectly.
+ """
+
+ def _cbReg(self, reg):
+ if 3600 < reg.secondsToExpiry or reg.secondsToExpiry < 3598:
+ raise RuntimeError(
+ "bad seconds to expire: %s" % reg.secondsToExpiry)
+ reg.secondsToExpiry = 3600
+ return reg
+
+ def getRegistrationInfo(self, uri):
+ d = sip.InMemoryRegistry.getRegistrationInfo(self, uri)
+ return d.addCallback(self._cbReg)
+
+ def registerAddress(self, domainURL, logicalURL, physicalURL):
+ d = sip.InMemoryRegistry.registerAddress(
+ self, domainURL, logicalURL, physicalURL)
+ return d.addCallback(self._cbReg)
+
+class AuthorizationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.proxy = sip.RegisterProxy(host="intarweb.us")
+ self.proxy.authorizers = self.proxy.authorizers.copy()
+ self.proxy.authorizers['digest'] = FakeDigestAuthorizer()
+
+ self.registry = FakeRegistry("intarweb.us")
+ self.proxy.registry = self.proxy.locator = self.registry
+ self.transport = proto_helpers.FakeDatagramTransport()
+ self.proxy.transport = self.transport
+
+ r = TestRealm()
+ p = cred.portal.Portal(r)
+ c = cred.checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ c.addUser('exarkun@intarweb.us', 'password')
+ p.registerChecker(c)
+ self.proxy.portal = p
+ setUp = utils.suppressWarnings(setUp,
+ util.suppress(category=DeprecationWarning,
+ message=r'twisted.protocols.sip.DigestAuthorizer was deprecated'))
+
+ def tearDown(self):
+ for d, uri in self.registry.users.values():
+ d.cancel()
+ del self.proxy
+
+ def testChallenge(self):
+ self.proxy.datagramReceived(registerRequest, ("127.0.0.1", 5632))
+
+ self.assertEquals(
+ self.transport.written[-1],
+ ((challengeResponse, ("127.0.0.1", 5632)))
+ )
+ self.transport.written = []
+
+ self.proxy.datagramReceived(authRequest, ("127.0.0.1", 5632))
+
+ self.assertEquals(
+ self.transport.written[-1],
+ ((okResponse, ("127.0.0.1", 5632)))
+ )
+ testChallenge.suppress = [
+ util.suppress(
+ category=DeprecationWarning,
+ message=r'twisted.protocols.sip.DigestAuthorizer was deprecated'),
+ util.suppress(
+ category=DeprecationWarning,
+ message=r'twisted.protocols.sip.DigestedCredentials was deprecated'),
+ util.suppress(
+ category=DeprecationWarning,
+ message=r'twisted.protocols.sip.DigestCalcHA1 was deprecated'),
+ util.suppress(
+ category=DeprecationWarning,
+ message=r'twisted.protocols.sip.DigestCalcResponse was deprecated')]
+
+
+
+class DeprecationTests(unittest.TestCase):
+ """
+ Tests for deprecation of obsolete components of L{twisted.protocols.sip}.
+ """
+
+ def test_deprecatedDigestCalcHA1(self):
+ """
+ L{sip.DigestCalcHA1} is deprecated.
+ """
+ self.callDeprecated(Version("Twisted", 9, 0, 0),
+ sip.DigestCalcHA1, '', '', '', '', '', '')
+
+
+ def test_deprecatedDigestCalcResponse(self):
+ """
+ L{sip.DigestCalcResponse} is deprecated.
+ """
+ self.callDeprecated(Version("Twisted", 9, 0, 0),
+ sip.DigestCalcResponse, '', '', '', '', '', '', '',
+ '')
+
+ def test_deprecatedBasicAuthorizer(self):
+ """
+ L{sip.BasicAuthorizer} is deprecated.
+ """
+ self.callDeprecated(Version("Twisted", 9, 0, 0), sip.BasicAuthorizer)
+
+
+ def test_deprecatedDigestAuthorizer(self):
+ """
+ L{sip.DigestAuthorizer} is deprecated.
+ """
+ self.callDeprecated(Version("Twisted", 9, 0, 0), sip.DigestAuthorizer)
+
+
+ def test_deprecatedDigestedCredentials(self):
+ """
+ L{sip.DigestedCredentials} is deprecated.
+ """
+ self.callDeprecated(Version("Twisted", 9, 0, 0),
+ sip.DigestedCredentials, '', {}, {})
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_sob.py b/vendor/Twisted-10.0.0/twisted/test/test_sob.py
new file mode 100644
index 0000000000..540b2cec8a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_sob.py
@@ -0,0 +1,172 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import sys, os
+
+try:
+ import Crypto.Cipher.AES
+except ImportError:
+ Crypto = None
+
+from twisted.trial import unittest
+from twisted.persisted import sob
+from twisted.python import components
+
+class Dummy(components.Componentized):
+ pass
+
+objects = [
+1,
+"hello",
+(1, "hello"),
+[1, "hello"],
+{1:"hello"},
+]
+
+class FakeModule(object):
+ pass
+
+class PersistTestCase(unittest.TestCase):
+ def testStyles(self):
+ for o in objects:
+ p = sob.Persistent(o, '')
+ for style in 'source pickle'.split():
+ p.setStyle(style)
+ p.save(filename='persisttest.'+style)
+ o1 = sob.load('persisttest.'+style, style)
+ self.failUnlessEqual(o, o1)
+
+ def testStylesBeingSet(self):
+ o = Dummy()
+ o.foo = 5
+ o.setComponent(sob.IPersistable, sob.Persistent(o, 'lala'))
+ for style in 'source pickle'.split():
+ sob.IPersistable(o).setStyle(style)
+ sob.IPersistable(o).save(filename='lala.'+style)
+ o1 = sob.load('lala.'+style, style)
+ self.failUnlessEqual(o.foo, o1.foo)
+ self.failUnlessEqual(sob.IPersistable(o1).style, style)
+
+
+ def testNames(self):
+ o = [1,2,3]
+ p = sob.Persistent(o, 'object')
+ for style in 'source pickle'.split():
+ p.setStyle(style)
+ p.save()
+ o1 = sob.load('object.ta'+style[0], style)
+ self.failUnlessEqual(o, o1)
+ for tag in 'lala lolo'.split():
+ p.save(tag)
+ o1 = sob.load('object-'+tag+'.ta'+style[0], style)
+ self.failUnlessEqual(o, o1)
+
+ def testEncryptedStyles(self):
+ for o in objects:
+ phrase='once I was the king of spain'
+ p = sob.Persistent(o, '')
+ for style in 'source pickle'.split():
+ p.setStyle(style)
+ p.save(filename='epersisttest.'+style, passphrase=phrase)
+ o1 = sob.load('epersisttest.'+style, style, phrase)
+ self.failUnlessEqual(o, o1)
+ if Crypto is None:
+ testEncryptedStyles.skip = "PyCrypto required for encrypted config"
+
+ def testPython(self):
+ f = open("persisttest.python", 'w')
+ f.write('foo=[1,2,3] ')
+ f.close()
+ o = sob.loadValueFromFile('persisttest.python', 'foo')
+ self.failUnlessEqual(o, [1,2,3])
+
+ def testEncryptedPython(self):
+ phrase='once I was the king of spain'
+ f = open("epersisttest.python", 'w')
+ f.write(
+ sob._encrypt(phrase, 'foo=[1,2,3]'))
+ f.close()
+ o = sob.loadValueFromFile('epersisttest.python', 'foo', phrase)
+ self.failUnlessEqual(o, [1,2,3])
+ if Crypto is None:
+ testEncryptedPython.skip = "PyCrypto required for encrypted config"
+
+ def testTypeGuesser(self):
+ self.assertRaises(KeyError, sob.guessType, "file.blah")
+ self.assertEqual('python', sob.guessType("file.py"))
+ self.assertEqual('python', sob.guessType("file.tac"))
+ self.assertEqual('python', sob.guessType("file.etac"))
+ self.assertEqual('pickle', sob.guessType("file.tap"))
+ self.assertEqual('pickle', sob.guessType("file.etap"))
+ self.assertEqual('source', sob.guessType("file.tas"))
+ self.assertEqual('source', sob.guessType("file.etas"))
+
+ def testEverythingEphemeralGetattr(self):
+ """
+ Verify that _EverythingEphermal.__getattr__ works.
+ """
+ self.fakeMain.testMainModGetattr = 1
+
+ dirname = self.mktemp()
+ os.mkdir(dirname)
+
+ filename = os.path.join(dirname, 'persisttest.ee_getattr')
+
+ f = file(filename, 'w')
+ f.write('import __main__\n')
+ f.write('if __main__.testMainModGetattr != 1: raise AssertionError\n')
+ f.write('app = None\n')
+ f.close()
+
+ sob.load(filename, 'source')
+
+ def testEverythingEphemeralSetattr(self):
+ """
+ Verify that _EverythingEphemeral.__setattr__ won't affect __main__.
+ """
+ self.fakeMain.testMainModSetattr = 1
+
+ dirname = self.mktemp()
+ os.mkdir(dirname)
+
+ filename = os.path.join(dirname, 'persisttest.ee_setattr')
+ f = file(filename, 'w')
+ f.write('import __main__\n')
+ f.write('__main__.testMainModSetattr = 2\n')
+ f.write('app = None\n')
+ f.close()
+
+ sob.load(filename, 'source')
+
+ self.assertEqual(self.fakeMain.testMainModSetattr, 1)
+
+ def testEverythingEphemeralException(self):
+ """
+ Test that an exception during load() won't cause _EE to mask __main__
+ """
+ dirname = self.mktemp()
+ os.mkdir(dirname)
+ filename = os.path.join(dirname, 'persisttest.ee_exception')
+
+ f = file(filename, 'w')
+ f.write('raise ValueError\n')
+ f.close()
+
+ self.assertRaises(ValueError, sob.load, filename, 'source')
+ self.assertEqual(type(sys.modules['__main__']), FakeModule)
+
+ def setUp(self):
+ """
+ Replace the __main__ module with a fake one, so that it can be mutated
+ in tests
+ """
+ self.realMain = sys.modules['__main__']
+ self.fakeMain = sys.modules['__main__'] = FakeModule()
+
+ def tearDown(self):
+ """
+ Restore __main__ to its original value
+ """
+ sys.modules['__main__'] = self.realMain
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_socks.py b/vendor/Twisted-10.0.0/twisted/test/test_socks.py
new file mode 100644
index 0000000000..bc1eee4ca9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_socks.py
@@ -0,0 +1,498 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.protocol.socks}, an implementation of the SOCKSv4 and
+SOCKSv4a protocols.
+"""
+
+import struct, socket
+
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+from twisted.internet import defer, address, reactor
+from twisted.internet.error import DNSLookupError
+from twisted.protocols import socks
+
+
+class StringTCPTransport(proto_helpers.StringTransport):
+ stringTCPTransport_closing = False
+ peer = None
+
+ def getPeer(self):
+ return self.peer
+
+ def getHost(self):
+ return address.IPv4Address('TCP', '2.3.4.5', 42)
+
+ def loseConnection(self):
+ self.stringTCPTransport_closing = True
+
+
+
+class FakeResolverReactor:
+ """
+ Bare-bones reactor with deterministic behavior for the resolve method.
+ """
+ def __init__(self, names):
+ """
+ @type names: C{dict} containing C{str} keys and C{str} values.
+ @param names: A hostname to IP address mapping. The IP addresses are
+ stringified dotted quads.
+ """
+ self.names = names
+
+
+ def resolve(self, hostname):
+ """
+ Resolve a hostname by looking it up in the C{names} dictionary.
+ """
+ try:
+ return defer.succeed(self.names[hostname])
+ except KeyError:
+ return defer.fail(
+ DNSLookupError("FakeResolverReactor couldn't find " + hostname))
+
+
+
+class SOCKSv4Driver(socks.SOCKSv4):
+ # last SOCKSv4Outgoing instantiated
+ driver_outgoing = None
+
+ # last SOCKSv4IncomingFactory instantiated
+ driver_listen = None
+
+ def connectClass(self, host, port, klass, *args):
+ # fake it
+ proto = klass(*args)
+ proto.transport = StringTCPTransport()
+ proto.transport.peer = address.IPv4Address('TCP', host, port)
+ proto.connectionMade()
+ self.driver_outgoing = proto
+ return defer.succeed(proto)
+
+ def listenClass(self, port, klass, *args):
+ # fake it
+ factory = klass(*args)
+ self.driver_listen = factory
+ if port == 0:
+ port = 1234
+ return defer.succeed(('6.7.8.9', port))
+
+
+
+class Connect(unittest.TestCase):
+ """
+ Tests for SOCKS and SOCKSv4a connect requests using the L{SOCKSv4} protocol.
+ """
+ def setUp(self):
+ self.sock = SOCKSv4Driver()
+ self.sock.transport = StringTCPTransport()
+ self.sock.connectionMade()
+ self.sock.reactor = FakeResolverReactor({"localhost":"127.0.0.1"})
+
+
+ def tearDown(self):
+ outgoing = self.sock.driver_outgoing
+ if outgoing is not None:
+ self.assert_(outgoing.transport.stringTCPTransport_closing,
+ "Outgoing SOCKS connections need to be closed.")
+
+
+ def test_simple(self):
+ self.sock.dataReceived(
+ struct.pack('!BBH', 4, 1, 34)
+ + socket.inet_aton('1.2.3.4')
+ + 'fooBAR'
+ + '\0')
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+ self.assertEqual(sent,
+ struct.pack('!BBH', 0, 90, 34)
+ + socket.inet_aton('1.2.3.4'))
+ self.assert_(not self.sock.transport.stringTCPTransport_closing)
+ self.assert_(self.sock.driver_outgoing is not None)
+
+ # pass some data through
+ self.sock.dataReceived('hello, world')
+ self.assertEqual(self.sock.driver_outgoing.transport.value(),
+ 'hello, world')
+
+ # the other way around
+ self.sock.driver_outgoing.dataReceived('hi there')
+ self.assertEqual(self.sock.transport.value(), 'hi there')
+
+ self.sock.connectionLost('fake reason')
+
+
+ def test_socks4aSuccessfulResolution(self):
+ """
+ If the destination IP address has zeros for the first three octets and
+ non-zero for the fourth octet, the client is attempting a v4a
+ connection. A hostname is specified after the user ID string and the
+ server connects to the address that hostname resolves to.
+
+ @see: U{http://en.wikipedia.org/wiki/SOCKS#SOCKS_4a_protocol}
+ """
+ # send the domain name "localhost" to be resolved
+ clientRequest = (
+ struct.pack('!BBH', 4, 1, 34)
+ + socket.inet_aton('0.0.0.1')
+ + 'fooBAZ\0'
+ + 'localhost\0')
+
+ # Deliver the bytes one by one to exercise the protocol's buffering
+ # logic. FakeResolverReactor's resolve method is invoked to "resolve"
+ # the hostname.
+ for byte in clientRequest:
+ self.sock.dataReceived(byte)
+
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+
+ # Verify that the server responded with the address which will be
+ # connected to.
+ self.assertEquals(
+ sent,
+ struct.pack('!BBH', 0, 90, 34) + socket.inet_aton('127.0.0.1'))
+ self.assertFalse(self.sock.transport.stringTCPTransport_closing)
+ self.assertNotIdentical(self.sock.driver_outgoing, None)
+
+ # Pass some data through and verify it is forwarded to the outgoing
+ # connection.
+ self.sock.dataReceived('hello, world')
+ self.assertEquals(
+ self.sock.driver_outgoing.transport.value(), 'hello, world')
+
+ # Deliver some data from the output connection and verify it is
+ # passed along to the incoming side.
+ self.sock.driver_outgoing.dataReceived('hi there')
+ self.assertEquals(self.sock.transport.value(), 'hi there')
+
+ self.sock.connectionLost('fake reason')
+
+
+ def test_socks4aFailedResolution(self):
+ """
+ Failed hostname resolution on a SOCKSv4a packet results in a 91 error
+ response and the connection getting closed.
+ """
+ # send the domain name "failinghost" to be resolved
+ clientRequest = (
+ struct.pack('!BBH', 4, 1, 34)
+ + socket.inet_aton('0.0.0.1')
+ + 'fooBAZ\0'
+ + 'failinghost\0')
+
+ # Deliver the bytes one by one to exercise the protocol's buffering
+ # logic. FakeResolverReactor's resolve method is invoked to "resolve"
+ # the hostname.
+ for byte in clientRequest:
+ self.sock.dataReceived(byte)
+
+ # Verify that the server responds with a 91 error.
+ sent = self.sock.transport.value()
+ self.assertEquals(
+ sent,
+ struct.pack('!BBH', 0, 91, 0) + socket.inet_aton('0.0.0.0'))
+
+ # A failed resolution causes the transport to drop the connection.
+ self.assertTrue(self.sock.transport.stringTCPTransport_closing)
+ self.assertIdentical(self.sock.driver_outgoing, None)
+
+
+ def test_accessDenied(self):
+ self.sock.authorize = lambda code, server, port, user: 0
+ self.sock.dataReceived(
+ struct.pack('!BBH', 4, 1, 4242)
+ + socket.inet_aton('10.2.3.4')
+ + 'fooBAR'
+ + '\0')
+ self.assertEqual(self.sock.transport.value(),
+ struct.pack('!BBH', 0, 91, 0)
+ + socket.inet_aton('0.0.0.0'))
+ self.assert_(self.sock.transport.stringTCPTransport_closing)
+ self.assertIdentical(self.sock.driver_outgoing, None)
+
+
+ def test_eofRemote(self):
+ self.sock.dataReceived(
+ struct.pack('!BBH', 4, 1, 34)
+ + socket.inet_aton('1.2.3.4')
+ + 'fooBAR'
+ + '\0')
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+
+ # pass some data through
+ self.sock.dataReceived('hello, world')
+ self.assertEqual(self.sock.driver_outgoing.transport.value(),
+ 'hello, world')
+
+ # now close it from the server side
+ self.sock.driver_outgoing.transport.loseConnection()
+ self.sock.driver_outgoing.connectionLost('fake reason')
+
+
+ def test_eofLocal(self):
+ self.sock.dataReceived(
+ struct.pack('!BBH', 4, 1, 34)
+ + socket.inet_aton('1.2.3.4')
+ + 'fooBAR'
+ + '\0')
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+
+ # pass some data through
+ self.sock.dataReceived('hello, world')
+ self.assertEqual(self.sock.driver_outgoing.transport.value(),
+ 'hello, world')
+
+ # now close it from the client side
+ self.sock.connectionLost('fake reason')
+
+
+
+class Bind(unittest.TestCase):
+ """
+ Tests for SOCKS and SOCKSv4a bind requests using the L{SOCKSv4} protocol.
+ """
+ def setUp(self):
+ self.sock = SOCKSv4Driver()
+ self.sock.transport = StringTCPTransport()
+ self.sock.connectionMade()
+ self.sock.reactor = FakeResolverReactor({"localhost":"127.0.0.1"})
+
+## def tearDown(self):
+## # TODO ensure the listen port is closed
+## listen = self.sock.driver_listen
+## if listen is not None:
+## self.assert_(incoming.transport.stringTCPTransport_closing,
+## "Incoming SOCKS connections need to be closed.")
+
+ def test_simple(self):
+ self.sock.dataReceived(
+ struct.pack('!BBH', 4, 2, 34)
+ + socket.inet_aton('1.2.3.4')
+ + 'fooBAR'
+ + '\0')
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+ self.assertEqual(sent,
+ struct.pack('!BBH', 0, 90, 1234)
+ + socket.inet_aton('6.7.8.9'))
+ self.assert_(not self.sock.transport.stringTCPTransport_closing)
+ self.assert_(self.sock.driver_listen is not None)
+
+ # connect
+ incoming = self.sock.driver_listen.buildProtocol(('1.2.3.4', 5345))
+ self.assertNotIdentical(incoming, None)
+ incoming.transport = StringTCPTransport()
+ incoming.connectionMade()
+
+ # now we should have the second reply packet
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+ self.assertEqual(sent,
+ struct.pack('!BBH', 0, 90, 0)
+ + socket.inet_aton('0.0.0.0'))
+ self.assert_(not self.sock.transport.stringTCPTransport_closing)
+
+ # pass some data through
+ self.sock.dataReceived('hello, world')
+ self.assertEqual(incoming.transport.value(),
+ 'hello, world')
+
+ # the other way around
+ incoming.dataReceived('hi there')
+ self.assertEqual(self.sock.transport.value(), 'hi there')
+
+ self.sock.connectionLost('fake reason')
+
+
+ def test_socks4a(self):
+ """
+ If the destination IP address has zeros for the first three octets and
+ non-zero for the fourth octet, the client is attempting a v4a
+ connection. A hostname is specified after the user ID string and the
+ server connects to the address that hostname resolves to.
+
+ @see: U{http://en.wikipedia.org/wiki/SOCKS#SOCKS_4a_protocol}
+ """
+ # send the domain name "localhost" to be resolved
+ clientRequest = (
+ struct.pack('!BBH', 4, 2, 34)
+ + socket.inet_aton('0.0.0.1')
+ + 'fooBAZ\0'
+ + 'localhost\0')
+
+ # Deliver the bytes one by one to exercise the protocol's buffering
+ # logic. FakeResolverReactor's resolve method is invoked to "resolve"
+ # the hostname.
+ for byte in clientRequest:
+ self.sock.dataReceived(byte)
+
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+
+ # Verify that the server responded with the address which will be
+ # connected to.
+ self.assertEquals(
+ sent,
+ struct.pack('!BBH', 0, 90, 1234) + socket.inet_aton('6.7.8.9'))
+ self.assertFalse(self.sock.transport.stringTCPTransport_closing)
+ self.assertNotIdentical(self.sock.driver_listen, None)
+
+ # connect
+ incoming = self.sock.driver_listen.buildProtocol(('127.0.0.1', 5345))
+ self.assertNotIdentical(incoming, None)
+ incoming.transport = StringTCPTransport()
+ incoming.connectionMade()
+
+ # now we should have the second reply packet
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+ self.assertEqual(sent,
+ struct.pack('!BBH', 0, 90, 0)
+ + socket.inet_aton('0.0.0.0'))
+ self.assertNotIdentical(
+ self.sock.transport.stringTCPTransport_closing, None)
+
+ # Deliver some data from the output connection and verify it is
+ # passed along to the incoming side.
+ self.sock.dataReceived('hi there')
+ self.assertEquals(incoming.transport.value(), 'hi there')
+
+ # the other way around
+ incoming.dataReceived('hi there')
+ self.assertEqual(self.sock.transport.value(), 'hi there')
+
+ self.sock.connectionLost('fake reason')
+
+
+ def test_socks4aFailedResolution(self):
+ """
+ Failed hostname resolution on a SOCKSv4a packet results in a 91 error
+ response and the connection getting closed.
+ """
+ # send the domain name "failinghost" to be resolved
+ clientRequest = (
+ struct.pack('!BBH', 4, 2, 34)
+ + socket.inet_aton('0.0.0.1')
+ + 'fooBAZ\0'
+ + 'failinghost\0')
+
+ # Deliver the bytes one by one to exercise the protocol's buffering
+ # logic. FakeResolverReactor's resolve method is invoked to "resolve"
+ # the hostname.
+ for byte in clientRequest:
+ self.sock.dataReceived(byte)
+
+ # Verify that the server responds with a 91 error.
+ sent = self.sock.transport.value()
+ self.assertEquals(
+ sent,
+ struct.pack('!BBH', 0, 91, 0) + socket.inet_aton('0.0.0.0'))
+
+ # A failed resolution causes the transport to drop the connection.
+ self.assertTrue(self.sock.transport.stringTCPTransport_closing)
+ self.assertIdentical(self.sock.driver_outgoing, None)
+
+
+ def test_accessDenied(self):
+ self.sock.authorize = lambda code, server, port, user: 0
+ self.sock.dataReceived(
+ struct.pack('!BBH', 4, 2, 4242)
+ + socket.inet_aton('10.2.3.4')
+ + 'fooBAR'
+ + '\0')
+ self.assertEqual(self.sock.transport.value(),
+ struct.pack('!BBH', 0, 91, 0)
+ + socket.inet_aton('0.0.0.0'))
+ self.assert_(self.sock.transport.stringTCPTransport_closing)
+ self.assertIdentical(self.sock.driver_listen, None)
+
+ def test_eofRemote(self):
+ self.sock.dataReceived(
+ struct.pack('!BBH', 4, 2, 34)
+ + socket.inet_aton('1.2.3.4')
+ + 'fooBAR'
+ + '\0')
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+
+ # connect
+ incoming = self.sock.driver_listen.buildProtocol(('1.2.3.4', 5345))
+ self.assertNotIdentical(incoming, None)
+ incoming.transport = StringTCPTransport()
+ incoming.connectionMade()
+
+ # now we should have the second reply packet
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+ self.assertEqual(sent,
+ struct.pack('!BBH', 0, 90, 0)
+ + socket.inet_aton('0.0.0.0'))
+ self.assert_(not self.sock.transport.stringTCPTransport_closing)
+
+ # pass some data through
+ self.sock.dataReceived('hello, world')
+ self.assertEqual(incoming.transport.value(),
+ 'hello, world')
+
+ # now close it from the server side
+ incoming.transport.loseConnection()
+ incoming.connectionLost('fake reason')
+
+ def test_eofLocal(self):
+ self.sock.dataReceived(
+ struct.pack('!BBH', 4, 2, 34)
+ + socket.inet_aton('1.2.3.4')
+ + 'fooBAR'
+ + '\0')
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+
+ # connect
+ incoming = self.sock.driver_listen.buildProtocol(('1.2.3.4', 5345))
+ self.assertNotIdentical(incoming, None)
+ incoming.transport = StringTCPTransport()
+ incoming.connectionMade()
+
+ # now we should have the second reply packet
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+ self.assertEqual(sent,
+ struct.pack('!BBH', 0, 90, 0)
+ + socket.inet_aton('0.0.0.0'))
+ self.assert_(not self.sock.transport.stringTCPTransport_closing)
+
+ # pass some data through
+ self.sock.dataReceived('hello, world')
+ self.assertEqual(incoming.transport.value(),
+ 'hello, world')
+
+ # now close it from the client side
+ self.sock.connectionLost('fake reason')
+
+ def test_badSource(self):
+ self.sock.dataReceived(
+ struct.pack('!BBH', 4, 2, 34)
+ + socket.inet_aton('1.2.3.4')
+ + 'fooBAR'
+ + '\0')
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+
+ # connect from WRONG address
+ incoming = self.sock.driver_listen.buildProtocol(('1.6.6.6', 666))
+ self.assertIdentical(incoming, None)
+
+ # Now we should have the second reply packet and it should
+ # be a failure. The connection should be closing.
+ sent = self.sock.transport.value()
+ self.sock.transport.clear()
+ self.assertEqual(sent,
+ struct.pack('!BBH', 0, 91, 0)
+ + socket.inet_aton('0.0.0.0'))
+ self.assert_(self.sock.transport.stringTCPTransport_closing)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_ssl.py b/vendor/Twisted-10.0.0/twisted/test/test_ssl.py
new file mode 100644
index 0000000000..7df6ca96a5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_ssl.py
@@ -0,0 +1,664 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for twisted SSL support.
+"""
+
+from twisted.trial import unittest
+from twisted.internet import protocol, reactor, interfaces, defer
+from twisted.protocols import basic
+from twisted.python import util
+from twisted.python.reflect import getClass, fullyQualifiedName
+from twisted.python.runtime import platform
+from twisted.test.test_tcp import WriteDataTestCase, ProperlyCloseFilesMixin
+
+import os, errno
+
+try:
+ from OpenSSL import SSL, crypto
+ from twisted.internet import ssl
+ from twisted.test.ssl_helpers import ClientTLSContext
+except ImportError:
+ def _noSSL():
+ # ugh, make pyflakes happy.
+ global SSL
+ global ssl
+ SSL = ssl = None
+ _noSSL()
+
+certPath = util.sibpath(__file__, "server.pem")
+
+
+
+class UnintelligentProtocol(basic.LineReceiver):
+ """
+ @ivar deferred: a deferred that will fire at connection lost.
+ @type deferred: L{defer.Deferred}
+
+ @cvar pretext: text sent before TLS is set up.
+ @type pretext: C{str}
+
+ @cvar posttext: text sent after TLS is set up.
+ @type posttext: C{str}
+ """
+ pretext = [
+ "first line",
+ "last thing before tls starts",
+ "STARTTLS"]
+
+ posttext = [
+ "first thing after tls started",
+ "last thing ever"]
+
+ def __init__(self):
+ self.deferred = defer.Deferred()
+
+
+ def connectionMade(self):
+ for l in self.pretext:
+ self.sendLine(l)
+
+
+ def lineReceived(self, line):
+ if line == "READY":
+ self.transport.startTLS(ClientTLSContext(), self.factory.client)
+ for l in self.posttext:
+ self.sendLine(l)
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ self.deferred.callback(None)
+
+
+
+class LineCollector(basic.LineReceiver):
+ """
+ @ivar deferred: a deferred that will fire at connection lost.
+ @type deferred: L{defer.Deferred}
+
+ @ivar doTLS: whether the protocol is initiate TLS or not.
+ @type doTLS: C{bool}
+
+ @ivar fillBuffer: if set to True, it will send lots of data once
+ C{STARTTLS} is received.
+ @type fillBuffer: C{bool}
+ """
+
+ def __init__(self, doTLS, fillBuffer=False):
+ self.doTLS = doTLS
+ self.fillBuffer = fillBuffer
+ self.deferred = defer.Deferred()
+
+
+ def connectionMade(self):
+ self.factory.rawdata = ''
+ self.factory.lines = []
+
+
+ def lineReceived(self, line):
+ self.factory.lines.append(line)
+ if line == 'STARTTLS':
+ if self.fillBuffer:
+ for x in range(500):
+ self.sendLine('X' * 1000)
+ self.sendLine('READY')
+ if self.doTLS:
+ ctx = ServerTLSContext(
+ privateKeyFileName=certPath,
+ certificateFileName=certPath,
+ )
+ self.transport.startTLS(ctx, self.factory.server)
+ else:
+ self.setRawMode()
+
+
+ def rawDataReceived(self, data):
+ self.factory.rawdata += data
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ self.deferred.callback(None)
+
+
+
+class SingleLineServerProtocol(protocol.Protocol):
+ """
+ A protocol that sends a single line of data at C{connectionMade}.
+ """
+
+ def connectionMade(self):
+ self.transport.write("+OK <some crap>\r\n")
+ self.transport.getPeerCertificate()
+
+
+
+class RecordingClientProtocol(protocol.Protocol):
+ """
+ @ivar deferred: a deferred that will fire with first received content.
+ @type deferred: L{defer.Deferred}
+ """
+
+ def __init__(self):
+ self.deferred = defer.Deferred()
+
+
+ def connectionMade(self):
+ self.transport.getPeerCertificate()
+
+
+ def dataReceived(self, data):
+ self.deferred.callback(data)
+
+
+
+class ImmediatelyDisconnectingProtocol(protocol.Protocol):
+ """
+ A protocol that disconnect immediately on connection. It fires the
+ C{connectionDisconnected} deferred of its factory on connetion lost.
+ """
+
+ def connectionMade(self):
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ self.factory.connectionDisconnected.callback(None)
+
+
+
+def generateCertificateObjects(organization, organizationalUnit):
+ """
+ Create a certificate for given C{organization} and C{organizationalUnit}.
+
+ @return: a tuple of (key, request, certificate) objects.
+ """
+ pkey = crypto.PKey()
+ pkey.generate_key(crypto.TYPE_RSA, 512)
+ req = crypto.X509Req()
+ subject = req.get_subject()
+ subject.O = organization
+ subject.OU = organizationalUnit
+ req.set_pubkey(pkey)
+ req.sign(pkey, "md5")
+
+ # Here comes the actual certificate
+ cert = crypto.X509()
+ cert.set_serial_number(1)
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(60) # Testing certificates need not be long lived
+ cert.set_issuer(req.get_subject())
+ cert.set_subject(req.get_subject())
+ cert.set_pubkey(req.get_pubkey())
+ cert.sign(pkey, "md5")
+
+ return pkey, req, cert
+
+
+
+def generateCertificateFiles(basename, organization, organizationalUnit):
+ """
+ Create certificate files key, req and cert prefixed by C{basename} for
+ given C{organization} and C{organizationalUnit}.
+ """
+ pkey, req, cert = generateCertificateObjects(organization, organizationalUnit)
+
+ for ext, obj, dumpFunc in [
+ ('key', pkey, crypto.dump_privatekey),
+ ('req', req, crypto.dump_certificate_request),
+ ('cert', cert, crypto.dump_certificate)]:
+ fName = os.extsep.join((basename, ext))
+ fObj = file(fName, 'w')
+ fObj.write(dumpFunc(crypto.FILETYPE_PEM, obj))
+ fObj.close()
+
+
+
+class ContextGeneratingMixin:
+ """
+ Offer methods to create L{ssl.DefaultOpenSSLContextFactory} for both client
+ and server.
+
+ @ivar clientBase: prefix of client certificate files.
+ @type clientBase: C{str}
+
+ @ivar serverBase: prefix of server certificate files.
+ @type serverBase: C{str}
+
+ @ivar clientCtxFactory: a generated context factory to be used in
+ C{reactor.connectSSL}.
+ @type clientCtxFactory: L{ssl.DefaultOpenSSLContextFactory}
+
+ @ivar serverCtxFactory: a generated context factory to be used in
+ C{reactor.listenSSL}.
+ @type serverCtxFactory: L{ssl.DefaultOpenSSLContextFactory}
+ """
+
+ def makeContextFactory(self, org, orgUnit, *args, **kwArgs):
+ base = self.mktemp()
+ generateCertificateFiles(base, org, orgUnit)
+ serverCtxFactory = ssl.DefaultOpenSSLContextFactory(
+ os.extsep.join((base, 'key')),
+ os.extsep.join((base, 'cert')),
+ *args, **kwArgs)
+
+ return base, serverCtxFactory
+
+
+ def setupServerAndClient(self, clientArgs, clientKwArgs, serverArgs,
+ serverKwArgs):
+ self.clientBase, self.clientCtxFactory = self.makeContextFactory(
+ *clientArgs, **clientKwArgs)
+ self.serverBase, self.serverCtxFactory = self.makeContextFactory(
+ *serverArgs, **serverKwArgs)
+
+
+
+if SSL is not None:
+ class ServerTLSContext(ssl.DefaultOpenSSLContextFactory):
+ """
+ A context factory with a default method set to L{SSL.TLSv1_METHOD}.
+ """
+ isClient = False
+
+ def __init__(self, *args, **kw):
+ kw['sslmethod'] = SSL.TLSv1_METHOD
+ ssl.DefaultOpenSSLContextFactory.__init__(self, *args, **kw)
+
+
+
+class StolenTCPTestCase(ProperlyCloseFilesMixin, unittest.TestCase):
+ """
+ For SSL transports, test many of the same things which are tested for
+ TCP transports.
+ """
+
+ def createServer(self, address, portNumber, factory):
+ """
+ Create an SSL server with a certificate using L{IReactorSSL.listenSSL}.
+ """
+ cert = ssl.PrivateCertificate.loadPEM(file(certPath).read())
+ contextFactory = cert.options()
+ return reactor.listenSSL(
+ portNumber, factory, contextFactory, interface=address)
+
+
+ def connectClient(self, address, portNumber, clientCreator):
+ """
+ Create an SSL client using L{IReactorSSL.connectSSL}.
+ """
+ contextFactory = ssl.CertificateOptions()
+ return clientCreator.connectSSL(address, portNumber, contextFactory)
+
+
+ def getHandleExceptionType(self):
+ """
+ Return L{SSL.Error} as the expected error type which will be raised by
+ a write to the L{OpenSSL.SSL.Connection} object after it has been
+ closed.
+ """
+ return SSL.Error
+
+
+ _iocp = 'twisted.internet.iocpreactor.reactor.IOCPReactor'
+
+ def getHandleErrorCode(self):
+ """
+ Return the argument L{SSL.Error} will be constructed with for this
+ case. This is basically just a random OpenSSL implementation detail.
+ It would be better if this test worked in a way which did not require
+ this.
+ """
+ # Windows 2000 SP 4 and Windows XP SP 2 give back WSAENOTSOCK for
+ # SSL.Connection.write for some reason. The twisted.protocols.tls
+ # implementation of IReactorSSL doesn't suffer from this imprecation,
+ # though, since it is isolated from the Windows I/O layer (I suppose?).
+
+ # If test_properlyCloseFiles waited for the SSL handshake to complete
+ # and performed an orderly shutdown, then this would probably be a
+ # little less weird: writing to a shutdown SSL connection has a more
+ # well-defined failure mode (or at least it should).
+ name = fullyQualifiedName(getClass(reactor))
+ if platform.getType() == 'win32' and name != self._iocp:
+ return errno.WSAENOTSOCK
+ # This is terribly implementation-specific.
+ return [('SSL routines', 'SSL_write', 'protocol is shutdown')]
+
+
+
+class TLSTestCase(unittest.TestCase):
+ """
+ Tests for startTLS support.
+
+ @ivar fillBuffer: forwarded to L{LineCollector.fillBuffer}
+ @type fillBuffer: C{bool}
+ """
+ fillBuffer = False
+
+ clientProto = None
+ serverProto = None
+
+
+ def tearDown(self):
+ if self.clientProto.transport is not None:
+ self.clientProto.transport.loseConnection()
+ if self.serverProto.transport is not None:
+ self.serverProto.transport.loseConnection()
+
+
+ def _runTest(self, clientProto, serverProto, clientIsServer=False):
+ """
+ Helper method to run TLS tests.
+
+ @param clientProto: protocol instance attached to the client
+ connection.
+ @param serverProto: protocol instance attached to the server
+ connection.
+ @param clientIsServer: flag indicated if client should initiate
+ startTLS instead of server.
+
+ @return: a L{defer.Deferred} that will fire when both connections are
+ lost.
+ """
+ self.clientProto = clientProto
+ cf = self.clientFactory = protocol.ClientFactory()
+ cf.protocol = lambda: clientProto
+ if clientIsServer:
+ cf.server = False
+ else:
+ cf.client = True
+
+ self.serverProto = serverProto
+ sf = self.serverFactory = protocol.ServerFactory()
+ sf.protocol = lambda: serverProto
+ if clientIsServer:
+ sf.client = False
+ else:
+ sf.server = True
+
+ port = reactor.listenTCP(0, sf, interface="127.0.0.1")
+ self.addCleanup(port.stopListening)
+
+ reactor.connectTCP('127.0.0.1', port.getHost().port, cf)
+
+ return defer.gatherResults([clientProto.deferred, serverProto.deferred])
+
+
+ def test_TLS(self):
+ """
+ Test for server and client startTLS: client should received data both
+ before and after the startTLS.
+ """
+ def check(ignore):
+ self.assertEquals(
+ self.serverFactory.lines,
+ UnintelligentProtocol.pretext + UnintelligentProtocol.posttext
+ )
+ d = self._runTest(UnintelligentProtocol(),
+ LineCollector(True, self.fillBuffer))
+ return d.addCallback(check)
+
+
+ def test_unTLS(self):
+ """
+ Test for server startTLS not followed by a startTLS in client: the data
+ received after server startTLS should be received as raw.
+ """
+ def check(ignored):
+ self.assertEquals(
+ self.serverFactory.lines,
+ UnintelligentProtocol.pretext
+ )
+ self.failUnless(self.serverFactory.rawdata,
+ "No encrypted bytes received")
+ d = self._runTest(UnintelligentProtocol(),
+ LineCollector(False, self.fillBuffer))
+ return d.addCallback(check)
+
+
+ def test_backwardsTLS(self):
+ """
+ Test startTLS first initiated by client.
+ """
+ def check(ignored):
+ self.assertEquals(
+ self.clientFactory.lines,
+ UnintelligentProtocol.pretext + UnintelligentProtocol.posttext
+ )
+ d = self._runTest(LineCollector(True, self.fillBuffer),
+ UnintelligentProtocol(), True)
+ return d.addCallback(check)
+
+
+
+class SpammyTLSTestCase(TLSTestCase):
+ """
+ Test TLS features with bytes sitting in the out buffer.
+ """
+ fillBuffer = True
+
+
+
+class BufferingTestCase(unittest.TestCase):
+ serverProto = None
+ clientProto = None
+
+
+ def tearDown(self):
+ if self.serverProto.transport is not None:
+ self.serverProto.transport.loseConnection()
+ if self.clientProto.transport is not None:
+ self.clientProto.transport.loseConnection()
+
+
+ def test_openSSLBuffering(self):
+ serverProto = self.serverProto = SingleLineServerProtocol()
+ clientProto = self.clientProto = RecordingClientProtocol()
+
+ server = protocol.ServerFactory()
+ client = self.client = protocol.ClientFactory()
+
+ server.protocol = lambda: serverProto
+ client.protocol = lambda: clientProto
+
+ sCTX = ssl.DefaultOpenSSLContextFactory(certPath, certPath)
+ cCTX = ssl.ClientContextFactory()
+
+ port = reactor.listenSSL(0, server, sCTX, interface='127.0.0.1')
+ self.addCleanup(port.stopListening)
+
+ reactor.connectSSL('127.0.0.1', port.getHost().port, client, cCTX)
+
+ return clientProto.deferred.addCallback(
+ self.assertEquals, "+OK <some crap>\r\n")
+
+
+
+class ConnectionLostTestCase(unittest.TestCase, ContextGeneratingMixin):
+
+ def testImmediateDisconnect(self):
+ org = "twisted.test.test_ssl"
+ self.setupServerAndClient(
+ (org, org + ", client"), {},
+ (org, org + ", server"), {})
+
+ # Set up a server, connect to it with a client, which should work since our verifiers
+ # allow anything, then disconnect.
+ serverProtocolFactory = protocol.ServerFactory()
+ serverProtocolFactory.protocol = protocol.Protocol
+ self.serverPort = serverPort = reactor.listenSSL(0,
+ serverProtocolFactory, self.serverCtxFactory)
+
+ clientProtocolFactory = protocol.ClientFactory()
+ clientProtocolFactory.protocol = ImmediatelyDisconnectingProtocol
+ clientProtocolFactory.connectionDisconnected = defer.Deferred()
+ clientConnector = reactor.connectSSL('127.0.0.1',
+ serverPort.getHost().port, clientProtocolFactory, self.clientCtxFactory)
+
+ return clientProtocolFactory.connectionDisconnected.addCallback(
+ lambda ignoredResult: self.serverPort.stopListening())
+
+
+ def testFailedVerify(self):
+ org = "twisted.test.test_ssl"
+ self.setupServerAndClient(
+ (org, org + ", client"), {},
+ (org, org + ", server"), {})
+
+ def verify(*a):
+ return False
+ self.clientCtxFactory.getContext().set_verify(SSL.VERIFY_PEER, verify)
+
+ serverConnLost = defer.Deferred()
+ serverProtocol = protocol.Protocol()
+ serverProtocol.connectionLost = serverConnLost.callback
+ serverProtocolFactory = protocol.ServerFactory()
+ serverProtocolFactory.protocol = lambda: serverProtocol
+ self.serverPort = serverPort = reactor.listenSSL(0,
+ serverProtocolFactory, self.serverCtxFactory)
+
+ clientConnLost = defer.Deferred()
+ clientProtocol = protocol.Protocol()
+ clientProtocol.connectionLost = clientConnLost.callback
+ clientProtocolFactory = protocol.ClientFactory()
+ clientProtocolFactory.protocol = lambda: clientProtocol
+ clientConnector = reactor.connectSSL('127.0.0.1',
+ serverPort.getHost().port, clientProtocolFactory, self.clientCtxFactory)
+
+ dl = defer.DeferredList([serverConnLost, clientConnLost], consumeErrors=True)
+ return dl.addCallback(self._cbLostConns)
+
+
+ def _cbLostConns(self, results):
+ (sSuccess, sResult), (cSuccess, cResult) = results
+
+ self.failIf(sSuccess)
+ self.failIf(cSuccess)
+
+ acceptableErrors = [SSL.Error]
+
+ # Rather than getting a verification failure on Windows, we are getting
+ # a connection failure. Without something like sslverify proxying
+ # in-between we can't fix up the platform's errors, so let's just
+ # specifically say it is only OK in this one case to keep the tests
+ # passing. Normally we'd like to be as strict as possible here, so
+ # we're not going to allow this to report errors incorrectly on any
+ # other platforms.
+
+ if platform.isWindows():
+ from twisted.internet.error import ConnectionLost
+ acceptableErrors.append(ConnectionLost)
+
+ sResult.trap(*acceptableErrors)
+ cResult.trap(*acceptableErrors)
+
+ return self.serverPort.stopListening()
+
+
+
+class FakeContext:
+ """
+ L{OpenSSL.SSL.Context} double which can more easily be inspected.
+ """
+ def __init__(self, method):
+ self._method = method
+ self._options = 0
+
+
+ def set_options(self, options):
+ self._options |= options
+
+
+ def use_certificate_file(self, fileName):
+ pass
+
+
+ def use_privatekey_file(self, fileName):
+ pass
+
+
+
+class DefaultOpenSSLContextFactoryTests(unittest.TestCase):
+ """
+ Tests for L{ssl.DefaultOpenSSLContextFactory}.
+ """
+ def setUp(self):
+ # pyOpenSSL Context objects aren't introspectable enough. Pass in
+ # an alternate context factory so we can inspect what is done to it.
+ self.contextFactory = ssl.DefaultOpenSSLContextFactory(
+ certPath, certPath, _contextFactory=FakeContext)
+ self.context = self.contextFactory.getContext()
+
+
+ def test_method(self):
+ """
+ L{ssl.DefaultOpenSSLContextFactory.getContext} returns an SSL context
+ which can use SSLv3 or TLSv1 but not SSLv2.
+ """
+ # SSLv23_METHOD allows SSLv2, SSLv3, or TLSv1
+ self.assertEqual(self.context._method, SSL.SSLv23_METHOD)
+
+ # And OP_NO_SSLv2 disables the SSLv2 support.
+ self.assertTrue(self.context._options & SSL.OP_NO_SSLv2)
+
+ # Make sure SSLv3 and TLSv1 aren't disabled though.
+ self.assertFalse(self.context._options & SSL.OP_NO_SSLv3)
+ self.assertFalse(self.context._options & SSL.OP_NO_TLSv1)
+
+
+ def test_missingCertificateFile(self):
+ """
+ Instantiating L{ssl.DefaultOpenSSLContextFactory} with a certificate
+ filename which does not identify an existing file results in the
+ initializer raising L{OpenSSL.SSL.Error}.
+ """
+ self.assertRaises(
+ SSL.Error,
+ ssl.DefaultOpenSSLContextFactory, certPath, self.mktemp())
+
+
+ def test_missingPrivateKeyFile(self):
+ """
+ Instantiating L{ssl.DefaultOpenSSLContextFactory} with a private key
+ filename which does not identify an existing file results in the
+ initializer raising L{OpenSSL.SSL.Error}.
+ """
+ self.assertRaises(
+ SSL.Error,
+ ssl.DefaultOpenSSLContextFactory, self.mktemp(), certPath)
+
+
+
+class ClientContextFactoryTests(unittest.TestCase):
+ """
+ Tests for L{ssl.ClientContextFactory}.
+ """
+ def setUp(self):
+ self.contextFactory = ssl.ClientContextFactory()
+ self.contextFactory._contextFactory = FakeContext
+ self.context = self.contextFactory.getContext()
+
+
+ def test_method(self):
+ """
+ L{ssl.ClientContextFactory.getContext} returns a context which can use
+ SSLv3 or TLSv1 but not SSLv2.
+ """
+ self.assertEqual(self.context._method, SSL.SSLv23_METHOD)
+ self.assertTrue(self.context._options & SSL.OP_NO_SSLv2)
+ self.assertFalse(self.context._options & SSL.OP_NO_SSLv3)
+ self.assertFalse(self.context._options & SSL.OP_NO_TLSv1)
+
+
+
+if interfaces.IReactorSSL(reactor, None) is None:
+ for tCase in [StolenTCPTestCase, TLSTestCase, SpammyTLSTestCase,
+ BufferingTestCase, ConnectionLostTestCase,
+ DefaultOpenSSLContextFactoryTests,
+ ClientContextFactoryTests]:
+ tCase.skip = "Reactor does not support SSL, cannot run SSL tests"
+
+# Otherwise trial will run this test here
+del WriteDataTestCase
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_sslverify.py b/vendor/Twisted-10.0.0/twisted/test/test_sslverify.py
new file mode 100644
index 0000000000..c4f5b6f1b4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_sslverify.py
@@ -0,0 +1,558 @@
+# Copyright 2005 Divmod, Inc. See LICENSE file for details
+# Copyright (c) 2006-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.internet._sslverify}.
+"""
+
+import itertools
+
+try:
+ from OpenSSL import SSL
+ from OpenSSL.crypto import PKey, X509, X509Req
+ from OpenSSL.crypto import TYPE_RSA
+ from twisted.internet import _sslverify as sslverify
+except ImportError:
+ pass
+
+from twisted.trial import unittest
+from twisted.internet import protocol, defer, reactor
+from twisted.python.reflect import objgrep, isSame
+from twisted.python import log
+
+from twisted.internet.error import CertificateError, ConnectionLost
+from twisted.internet import interfaces
+
+
+# A couple of static PEM-format certificates to be used by various tests.
+A_HOST_CERTIFICATE_PEM = """
+-----BEGIN CERTIFICATE-----
+ MIIC2jCCAkMCAjA5MA0GCSqGSIb3DQEBBAUAMIG0MQswCQYDVQQGEwJVUzEiMCAG
+ A1UEAxMZZXhhbXBsZS50d2lzdGVkbWF0cml4LmNvbTEPMA0GA1UEBxMGQm9zdG9u
+ MRwwGgYDVQQKExNUd2lzdGVkIE1hdHJpeCBMYWJzMRYwFAYDVQQIEw1NYXNzYWNo
+ dXNldHRzMScwJQYJKoZIhvcNAQkBFhhub2JvZHlAdHdpc3RlZG1hdHJpeC5jb20x
+ ETAPBgNVBAsTCFNlY3VyaXR5MB4XDTA2MDgxNjAxMDEwOFoXDTA3MDgxNjAxMDEw
+ OFowgbQxCzAJBgNVBAYTAlVTMSIwIAYDVQQDExlleGFtcGxlLnR3aXN0ZWRtYXRy
+ aXguY29tMQ8wDQYDVQQHEwZCb3N0b24xHDAaBgNVBAoTE1R3aXN0ZWQgTWF0cml4
+ IExhYnMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxJzAlBgkqhkiG9w0BCQEWGG5v
+ Ym9keUB0d2lzdGVkbWF0cml4LmNvbTERMA8GA1UECxMIU2VjdXJpdHkwgZ8wDQYJ
+ KoZIhvcNAQEBBQADgY0AMIGJAoGBAMzH8CDF/U91y/bdbdbJKnLgnyvQ9Ig9ZNZp
+ 8hpsu4huil60zF03+Lexg2l1FIfURScjBuaJMR6HiMYTMjhzLuByRZ17KW4wYkGi
+ KXstz03VIKy4Tjc+v4aXFI4XdRw10gGMGQlGGscXF/RSoN84VoDKBfOMWdXeConJ
+ VyC4w3iJAgMBAAEwDQYJKoZIhvcNAQEEBQADgYEAviMT4lBoxOgQy32LIgZ4lVCj
+ JNOiZYg8GMQ6y0ugp86X80UjOvkGtNf/R7YgED/giKRN/q/XJiLJDEhzknkocwmO
+ S+4b2XpiaZYxRyKWwL221O7CGmtWYyZl2+92YYmmCiNzWQPfP6BOMlfax0AGLHls
+ fXzCWdG0O/3Lk2SRM0I=
+-----END CERTIFICATE-----
+"""
+
+A_PEER_CERTIFICATE_PEM = """
+-----BEGIN CERTIFICATE-----
+ MIIC3jCCAkcCAjA6MA0GCSqGSIb3DQEBBAUAMIG2MQswCQYDVQQGEwJVUzEiMCAG
+ A1UEAxMZZXhhbXBsZS50d2lzdGVkbWF0cml4LmNvbTEPMA0GA1UEBxMGQm9zdG9u
+ MRwwGgYDVQQKExNUd2lzdGVkIE1hdHJpeCBMYWJzMRYwFAYDVQQIEw1NYXNzYWNo
+ dXNldHRzMSkwJwYJKoZIhvcNAQkBFhpzb21lYm9keUB0d2lzdGVkbWF0cml4LmNv
+ bTERMA8GA1UECxMIU2VjdXJpdHkwHhcNMDYwODE2MDEwMTU2WhcNMDcwODE2MDEw
+ MTU2WjCBtjELMAkGA1UEBhMCVVMxIjAgBgNVBAMTGWV4YW1wbGUudHdpc3RlZG1h
+ dHJpeC5jb20xDzANBgNVBAcTBkJvc3RvbjEcMBoGA1UEChMTVHdpc3RlZCBNYXRy
+ aXggTGFiczEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czEpMCcGCSqGSIb3DQEJARYa
+ c29tZWJvZHlAdHdpc3RlZG1hdHJpeC5jb20xETAPBgNVBAsTCFNlY3VyaXR5MIGf
+ MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnm+WBlgFNbMlHehib9ePGGDXF+Nz4
+ CjGuUmVBaXCRCiVjg3kSDecwqfb0fqTksBZ+oQ1UBjMcSh7OcvFXJZnUesBikGWE
+ JE4V8Bjh+RmbJ1ZAlUPZ40bAkww0OpyIRAGMvKG+4yLFTO4WDxKmfDcrOb6ID8WJ
+ e1u+i3XGkIf/5QIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAD4Oukm3YYkhedUepBEA
+ vvXIQhVDqL7mk6OqYdXmNj6R7ZMC8WWvGZxrzDI1bZuB+4aIxxd1FXC3UOHiR/xg
+ i9cDl1y8P/qRp4aEBNF6rI0D4AxTbfnHQx4ERDAOShJdYZs/2zifPJ6va6YvrEyr
+ yqDtGhklsWW3ZwBzEh5VEOUp
+-----END CERTIFICATE-----
+"""
+
+
+
+counter = itertools.count().next
+def makeCertificate(**kw):
+ keypair = PKey()
+ keypair.generate_key(TYPE_RSA, 512)
+
+ certificate = X509()
+ certificate.gmtime_adj_notBefore(0)
+ certificate.gmtime_adj_notAfter(60 * 60 * 24 * 365) # One year
+ for xname in certificate.get_issuer(), certificate.get_subject():
+ for (k, v) in kw.items():
+ setattr(xname, k, v)
+
+ certificate.set_serial_number(counter())
+ certificate.set_pubkey(keypair)
+ certificate.sign(keypair, "md5")
+
+ return keypair, certificate
+
+
+
+class DataCallbackProtocol(protocol.Protocol):
+ def dataReceived(self, data):
+ d, self.factory.onData = self.factory.onData, None
+ if d is not None:
+ d.callback(data)
+
+ def connectionLost(self, reason):
+ d, self.factory.onLost = self.factory.onLost, None
+ if d is not None:
+ d.errback(reason)
+
+class WritingProtocol(protocol.Protocol):
+ byte = 'x'
+ def connectionMade(self):
+ self.transport.write(self.byte)
+
+ def connectionLost(self, reason):
+ self.factory.onLost.errback(reason)
+
+
+class OpenSSLOptions(unittest.TestCase):
+ serverPort = clientConn = None
+ onServerLost = onClientLost = None
+
+ sKey = None
+ sCert = None
+ cKey = None
+ cCert = None
+
+ def setUp(self):
+ """
+ Create class variables of client and server certificates.
+ """
+ self.sKey, self.sCert = makeCertificate(
+ O="Server Test Certificate",
+ CN="server")
+ self.cKey, self.cCert = makeCertificate(
+ O="Client Test Certificate",
+ CN="client")
+
+ def tearDown(self):
+ if self.serverPort is not None:
+ self.serverPort.stopListening()
+ if self.clientConn is not None:
+ self.clientConn.disconnect()
+
+ L = []
+ if self.onServerLost is not None:
+ L.append(self.onServerLost)
+ if self.onClientLost is not None:
+ L.append(self.onClientLost)
+
+ return defer.DeferredList(L, consumeErrors=True)
+
+ def loopback(self, serverCertOpts, clientCertOpts,
+ onServerLost=None, onClientLost=None, onData=None):
+ if onServerLost is None:
+ self.onServerLost = onServerLost = defer.Deferred()
+ if onClientLost is None:
+ self.onClientLost = onClientLost = defer.Deferred()
+ if onData is None:
+ onData = defer.Deferred()
+
+ serverFactory = protocol.ServerFactory()
+ serverFactory.protocol = DataCallbackProtocol
+ serverFactory.onLost = onServerLost
+ serverFactory.onData = onData
+
+ clientFactory = protocol.ClientFactory()
+ clientFactory.protocol = WritingProtocol
+ clientFactory.onLost = onClientLost
+
+ self.serverPort = reactor.listenSSL(0, serverFactory, serverCertOpts)
+ self.clientConn = reactor.connectSSL('127.0.0.1',
+ self.serverPort.getHost().port, clientFactory, clientCertOpts)
+
+ def test_abbreviatingDistinguishedNames(self):
+ """
+ Check that abbreviations used in certificates correctly map to
+ complete names.
+ """
+ self.assertEquals(
+ sslverify.DN(CN='a', OU='hello'),
+ sslverify.DistinguishedName(commonName='a',
+ organizationalUnitName='hello'))
+ self.assertNotEquals(
+ sslverify.DN(CN='a', OU='hello'),
+ sslverify.DN(CN='a', OU='hello', emailAddress='xxx'))
+ dn = sslverify.DN(CN='abcdefg')
+ self.assertRaises(AttributeError, setattr, dn, 'Cn', 'x')
+ self.assertEquals(dn.CN, dn.commonName)
+ dn.CN = 'bcdefga'
+ self.assertEquals(dn.CN, dn.commonName)
+
+
+ def testInspectDistinguishedName(self):
+ n = sslverify.DN(commonName='common name',
+ organizationName='organization name',
+ organizationalUnitName='organizational unit name',
+ localityName='locality name',
+ stateOrProvinceName='state or province name',
+ countryName='country name',
+ emailAddress='email address')
+ s = n.inspect()
+ for k in [
+ 'common name',
+ 'organization name',
+ 'organizational unit name',
+ 'locality name',
+ 'state or province name',
+ 'country name',
+ 'email address']:
+ self.assertIn(k, s, "%r was not in inspect output." % (k,))
+ self.assertIn(k.title(), s, "%r was not in inspect output." % (k,))
+
+
+ def testInspectDistinguishedNameWithoutAllFields(self):
+ n = sslverify.DN(localityName='locality name')
+ s = n.inspect()
+ for k in [
+ 'common name',
+ 'organization name',
+ 'organizational unit name',
+ 'state or province name',
+ 'country name',
+ 'email address']:
+ self.assertNotIn(k, s, "%r was in inspect output." % (k,))
+ self.assertNotIn(k.title(), s, "%r was in inspect output." % (k,))
+ self.assertIn('locality name', s)
+ self.assertIn('Locality Name', s)
+
+
+ def test_inspectCertificate(self):
+ """
+ Test that the C{inspect} method of L{sslverify.Certificate} returns
+ a human-readable string containing some basic information about the
+ certificate.
+ """
+ c = sslverify.Certificate.loadPEM(A_HOST_CERTIFICATE_PEM)
+ self.assertEqual(
+ c.inspect().split('\n'),
+ ["Certificate For Subject:",
+ " Organizational Unit Name: Security",
+ " Organization Name: Twisted Matrix Labs",
+ " Common Name: example.twistedmatrix.com",
+ " State Or Province Name: Massachusetts",
+ " Country Name: US",
+ " Email Address: nobody@twistedmatrix.com",
+ " Locality Name: Boston",
+ "",
+ "Issuer:",
+ " Organizational Unit Name: Security",
+ " Organization Name: Twisted Matrix Labs",
+ " Common Name: example.twistedmatrix.com",
+ " State Or Province Name: Massachusetts",
+ " Country Name: US",
+ " Email Address: nobody@twistedmatrix.com",
+ " Locality Name: Boston",
+ "",
+ "Serial Number: 12345",
+ "Digest: C4:96:11:00:30:C3:EC:EE:A3:55:AA:ED:8C:84:85:18",
+ "Public Key with Hash: ff33994c80812aa95a79cdb85362d054"])
+
+
+ def test_certificateOptionsSerialization(self):
+ """
+ Test that __setstate__(__getstate__()) round-trips properly.
+ """
+ firstOpts = sslverify.OpenSSLCertificateOptions(
+ privateKey=self.sKey,
+ certificate=self.sCert,
+ method=SSL.SSLv3_METHOD,
+ verify=True,
+ caCerts=[self.sCert],
+ verifyDepth=2,
+ requireCertificate=False,
+ verifyOnce=False,
+ enableSingleUseKeys=False,
+ enableSessions=False,
+ fixBrokenPeers=True,
+ enableSessionTickets=True)
+ context = firstOpts.getContext()
+ state = firstOpts.__getstate__()
+
+ # The context shouldn't be in the state to serialize
+ self.failIf(objgrep(state, context, isSame),
+ objgrep(state, context, isSame))
+
+ opts = sslverify.OpenSSLCertificateOptions()
+ opts.__setstate__(state)
+ self.assertEqual(opts.privateKey, self.sKey)
+ self.assertEqual(opts.certificate, self.sCert)
+ self.assertEqual(opts.method, SSL.SSLv3_METHOD)
+ self.assertEqual(opts.verify, True)
+ self.assertEqual(opts.caCerts, [self.sCert])
+ self.assertEqual(opts.verifyDepth, 2)
+ self.assertEqual(opts.requireCertificate, False)
+ self.assertEqual(opts.verifyOnce, False)
+ self.assertEqual(opts.enableSingleUseKeys, False)
+ self.assertEqual(opts.enableSessions, False)
+ self.assertEqual(opts.fixBrokenPeers, True)
+ self.assertEqual(opts.enableSessionTickets, True)
+
+
+ def test_certificateOptionsSessionTickets(self):
+ """
+ Enabling session tickets should not set the OP_NO_TICKET option.
+ """
+ opts = sslverify.OpenSSLCertificateOptions(enableSessionTickets=True)
+ ctx = opts.getContext()
+ self.assertEquals(0, ctx.set_options(0) & 0x00004000)
+
+
+ def test_certificateOptionsSessionTicketsDisabled(self):
+ """
+ Enabling session tickets should set the OP_NO_TICKET option.
+ """
+ opts = sslverify.OpenSSLCertificateOptions(enableSessionTickets=False)
+ ctx = opts.getContext()
+ self.assertEquals(0x00004000, ctx.set_options(0) & 0x00004000)
+
+
+ def test_allowedAnonymousClientConnection(self):
+ """
+ Check that anonymous connections are allowed when certificates aren't
+ required on the server.
+ """
+ onData = defer.Deferred()
+ self.loopback(sslverify.OpenSSLCertificateOptions(privateKey=self.sKey,
+ certificate=self.sCert, requireCertificate=False),
+ sslverify.OpenSSLCertificateOptions(
+ requireCertificate=False),
+ onData=onData)
+
+ return onData.addCallback(
+ lambda result: self.assertEquals(result, WritingProtocol.byte))
+
+ def test_refusedAnonymousClientConnection(self):
+ """
+ Check that anonymous connections are refused when certificates are
+ required on the server.
+ """
+ onServerLost = defer.Deferred()
+ onClientLost = defer.Deferred()
+ self.loopback(sslverify.OpenSSLCertificateOptions(privateKey=self.sKey,
+ certificate=self.sCert, verify=True,
+ caCerts=[self.sCert], requireCertificate=True),
+ sslverify.OpenSSLCertificateOptions(
+ requireCertificate=False),
+ onServerLost=onServerLost,
+ onClientLost=onClientLost)
+
+ d = defer.DeferredList([onClientLost, onServerLost],
+ consumeErrors=True)
+
+
+ def afterLost(((cSuccess, cResult), (sSuccess, sResult))):
+
+ self.failIf(cSuccess)
+ self.failIf(sSuccess)
+ # Win32 fails to report the SSL Error, and report a connection lost
+ # instead: there is a race condition so that's not totally
+ # surprising (see ticket #2877 in the tracker)
+ self.assertIsInstance(cResult.value, (SSL.Error, ConnectionLost))
+ self.assertIsInstance(sResult.value, SSL.Error)
+
+ return d.addCallback(afterLost)
+
+ def test_failedCertificateVerification(self):
+ """
+ Check that connecting with a certificate not accepted by the server CA
+ fails.
+ """
+ onServerLost = defer.Deferred()
+ onClientLost = defer.Deferred()
+ self.loopback(sslverify.OpenSSLCertificateOptions(privateKey=self.sKey,
+ certificate=self.sCert, verify=False,
+ requireCertificate=False),
+ sslverify.OpenSSLCertificateOptions(verify=True,
+ requireCertificate=False, caCerts=[self.cCert]),
+ onServerLost=onServerLost,
+ onClientLost=onClientLost)
+
+ d = defer.DeferredList([onClientLost, onServerLost],
+ consumeErrors=True)
+ def afterLost(((cSuccess, cResult), (sSuccess, sResult))):
+
+ self.failIf(cSuccess)
+ self.failIf(sSuccess)
+
+ return d.addCallback(afterLost)
+
+ def test_successfulCertificateVerification(self):
+ """
+ Test a successful connection with client certificate validation on
+ server side.
+ """
+ onData = defer.Deferred()
+ self.loopback(sslverify.OpenSSLCertificateOptions(privateKey=self.sKey,
+ certificate=self.sCert, verify=False,
+ requireCertificate=False),
+ sslverify.OpenSSLCertificateOptions(verify=True,
+ requireCertificate=True, caCerts=[self.sCert]),
+ onData=onData)
+
+ return onData.addCallback(
+ lambda result: self.assertEquals(result, WritingProtocol.byte))
+
+ def test_successfulSymmetricSelfSignedCertificateVerification(self):
+ """
+ Test a successful connection with validation on both server and client
+ sides.
+ """
+ onData = defer.Deferred()
+ self.loopback(sslverify.OpenSSLCertificateOptions(privateKey=self.sKey,
+ certificate=self.sCert, verify=True,
+ requireCertificate=True, caCerts=[self.cCert]),
+ sslverify.OpenSSLCertificateOptions(privateKey=self.cKey,
+ certificate=self.cCert, verify=True,
+ requireCertificate=True, caCerts=[self.sCert]),
+ onData=onData)
+
+ return onData.addCallback(
+ lambda result: self.assertEquals(result, WritingProtocol.byte))
+
+ def test_verification(self):
+ """
+ Check certificates verification building custom certificates data.
+ """
+ clientDN = sslverify.DistinguishedName(commonName='client')
+ clientKey = sslverify.KeyPair.generate()
+ clientCertReq = clientKey.certificateRequest(clientDN)
+
+ serverDN = sslverify.DistinguishedName(commonName='server')
+ serverKey = sslverify.KeyPair.generate()
+ serverCertReq = serverKey.certificateRequest(serverDN)
+
+ clientSelfCertReq = clientKey.certificateRequest(clientDN)
+ clientSelfCertData = clientKey.signCertificateRequest(
+ clientDN, clientSelfCertReq, lambda dn: True, 132)
+ clientSelfCert = clientKey.newCertificate(clientSelfCertData)
+
+ serverSelfCertReq = serverKey.certificateRequest(serverDN)
+ serverSelfCertData = serverKey.signCertificateRequest(
+ serverDN, serverSelfCertReq, lambda dn: True, 516)
+ serverSelfCert = serverKey.newCertificate(serverSelfCertData)
+
+ clientCertData = serverKey.signCertificateRequest(
+ serverDN, clientCertReq, lambda dn: True, 7)
+ clientCert = clientKey.newCertificate(clientCertData)
+
+ serverCertData = clientKey.signCertificateRequest(
+ clientDN, serverCertReq, lambda dn: True, 42)
+ serverCert = serverKey.newCertificate(serverCertData)
+
+ onData = defer.Deferred()
+
+ serverOpts = serverCert.options(serverSelfCert)
+ clientOpts = clientCert.options(clientSelfCert)
+
+ self.loopback(serverOpts,
+ clientOpts,
+ onData=onData)
+
+ return onData.addCallback(
+ lambda result: self.assertEquals(result, WritingProtocol.byte))
+
+
+
+if interfaces.IReactorSSL(reactor, None) is None:
+ OpenSSLOptions.skip = "Reactor does not support SSL, cannot run SSL tests"
+
+
+
+class _NotSSLTransport:
+ def getHandle(self):
+ return self
+
+class _MaybeSSLTransport:
+ def getHandle(self):
+ return self
+
+ def get_peer_certificate(self):
+ return None
+
+ def get_host_certificate(self):
+ return None
+
+
+class _ActualSSLTransport:
+ def getHandle(self):
+ return self
+
+ def get_host_certificate(self):
+ return sslverify.Certificate.loadPEM(A_HOST_CERTIFICATE_PEM).original
+
+ def get_peer_certificate(self):
+ return sslverify.Certificate.loadPEM(A_PEER_CERTIFICATE_PEM).original
+
+
+class Constructors(unittest.TestCase):
+ def test_peerFromNonSSLTransport(self):
+ """
+ Verify that peerFromTransport raises an exception if the transport
+ passed is not actually an SSL transport.
+ """
+ x = self.assertRaises(CertificateError,
+ sslverify.Certificate.peerFromTransport,
+ _NotSSLTransport())
+ self.failUnless(str(x).startswith("non-TLS"))
+
+ def test_peerFromBlankSSLTransport(self):
+ """
+ Verify that peerFromTransport raises an exception if the transport
+ passed is an SSL transport, but doesn't have a peer certificate.
+ """
+ x = self.assertRaises(CertificateError,
+ sslverify.Certificate.peerFromTransport,
+ _MaybeSSLTransport())
+ self.failUnless(str(x).startswith("TLS"))
+
+ def test_hostFromNonSSLTransport(self):
+ """
+ Verify that hostFromTransport raises an exception if the transport
+ passed is not actually an SSL transport.
+ """
+ x = self.assertRaises(CertificateError,
+ sslverify.Certificate.hostFromTransport,
+ _NotSSLTransport())
+ self.failUnless(str(x).startswith("non-TLS"))
+
+ def test_hostFromBlankSSLTransport(self):
+ """
+ Verify that hostFromTransport raises an exception if the transport
+ passed is an SSL transport, but doesn't have a host certificate.
+ """
+ x = self.assertRaises(CertificateError,
+ sslverify.Certificate.hostFromTransport,
+ _MaybeSSLTransport())
+ self.failUnless(str(x).startswith("TLS"))
+
+
+ def test_hostFromSSLTransport(self):
+ """
+ Verify that hostFromTransport successfully creates the correct
+ certificate if passed a valid SSL transport.
+ """
+ self.assertEqual(
+ sslverify.Certificate.hostFromTransport(
+ _ActualSSLTransport()).serialNumber(),
+ 12345)
+
+ def test_peerFromSSLTransport(self):
+ """
+ Verify that peerFromTransport successfully creates the correct
+ certificate if passed a valid SSL transport.
+ """
+ self.assertEqual(
+ sslverify.Certificate.peerFromTransport(
+ _ActualSSLTransport()).serialNumber(),
+ 12346)
+
+
+
+if interfaces.IReactorSSL(reactor, None) is None:
+ Constructors.skip = "Reactor does not support SSL, cannot run SSL tests"
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_stateful.py b/vendor/Twisted-10.0.0/twisted/test/test_stateful.py
new file mode 100644
index 0000000000..709bdb1a0b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_stateful.py
@@ -0,0 +1,77 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Test cases for twisted.protocols.stateful
+"""
+
+from twisted.test import test_protocols
+from twisted.protocols.stateful import StatefulProtocol
+
+from struct import pack, unpack, calcsize
+
+
+class MyInt32StringReceiver(StatefulProtocol):
+ """
+ A stateful Int32StringReceiver.
+ """
+ MAX_LENGTH = 99999
+ structFormat = "!I"
+ prefixLength = calcsize(structFormat)
+
+ def getInitialState(self):
+ return self._getHeader, 4
+
+ def lengthLimitExceeded(self, length):
+ self.transport.loseConnection()
+
+ def _getHeader(self, msg):
+ length, = unpack("!i", msg)
+ if length > self.MAX_LENGTH:
+ self.lengthLimitExceeded(length)
+ return
+ return self._getString, length
+
+ def _getString(self, msg):
+ self.stringReceived(msg)
+ return self._getHeader, 4
+
+ def stringReceived(self, msg):
+ """
+ Override this.
+ """
+ raise NotImplementedError
+
+ def sendString(self, data):
+ """
+ Send an int32-prefixed string to the other end of the connection.
+ """
+ self.transport.write(pack(self.structFormat, len(data)) + data)
+
+
+class TestInt32(MyInt32StringReceiver):
+ def connectionMade(self):
+ self.received = []
+
+ def stringReceived(self, s):
+ self.received.append(s)
+
+ MAX_LENGTH = 50
+ closed = 0
+
+ def connectionLost(self, reason):
+ self.closed = 1
+
+
+class Int32TestCase(test_protocols.Int32TestCase):
+ protocol = TestInt32
+
+ def test_bigReceive(self):
+ r = self.getProtocol()
+ big = ""
+ for s in self.strings * 4:
+ big += pack("!i", len(s)) + s
+ r.dataReceived(big)
+ self.assertEquals(r.received, self.strings * 4)
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_stdio.py b/vendor/Twisted-10.0.0/twisted/test/test_stdio.py
new file mode 100644
index 0000000000..d832523ea1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_stdio.py
@@ -0,0 +1,287 @@
+# Copyright (c) 2006-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import os, sys
+
+from twisted.trial import unittest
+from twisted.python import filepath, log
+from twisted.python.runtime import platform
+from twisted.internet import error, defer, protocol, reactor
+
+
+# A short string which is intended to appear here and nowhere else,
+# particularly not in any random garbage output CPython unavoidable
+# generates (such as in warning text and so forth). This is searched
+# for in the output from stdio_test_lastwrite.py and if it is found at
+# the end, the functionality works.
+UNIQUE_LAST_WRITE_STRING = 'xyz123abc Twisted is great!'
+
+skipWindowsNopywin32 = None
+if platform.isWindows():
+ try:
+ import win32process
+ except ImportError:
+ skipWindowsNopywin32 = ("On windows, spawnProcess is not available "
+ "in the absence of win32process.")
+
+
+class StandardIOTestProcessProtocol(protocol.ProcessProtocol):
+ """
+ Test helper for collecting output from a child process and notifying
+ something when it exits.
+
+ @ivar onConnection: A L{defer.Deferred} which will be called back with
+ C{None} when the connection to the child process is established.
+
+ @ivar onCompletion: A L{defer.Deferred} which will be errbacked with the
+ failure associated with the child process exiting when it exits.
+
+ @ivar onDataReceived: A L{defer.Deferred} which will be called back with
+ this instance whenever C{childDataReceived} is called, or C{None} to
+ suppress these callbacks.
+
+ @ivar data: A C{dict} mapping file descriptors to strings containing all
+ bytes received from the child process on each file descriptor.
+ """
+ onDataReceived = None
+
+ def __init__(self):
+ self.onConnection = defer.Deferred()
+ self.onCompletion = defer.Deferred()
+ self.data = {}
+
+
+ def connectionMade(self):
+ self.onConnection.callback(None)
+
+
+ def childDataReceived(self, name, bytes):
+ """
+ Record all bytes received from the child process in the C{data}
+ dictionary. Fire C{onDataReceived} if it is not C{None}.
+ """
+ self.data[name] = self.data.get(name, '') + bytes
+ if self.onDataReceived is not None:
+ d, self.onDataReceived = self.onDataReceived, None
+ d.callback(self)
+
+
+ def processEnded(self, reason):
+ self.onCompletion.callback(reason)
+
+
+
+class StandardInputOutputTestCase(unittest.TestCase):
+ def _spawnProcess(self, proto, sibling, *args, **kw):
+ """
+ Launch a child Python process and communicate with it using the
+ given ProcessProtocol.
+
+ @param proto: A L{ProcessProtocol} instance which will be connected
+ to the child process.
+
+ @param sibling: The basename of a file containing the Python program
+ to run in the child process.
+
+ @param *args: strings which will be passed to the child process on
+ the command line as C{argv[2:]}.
+
+ @param **kw: additional arguments to pass to L{reactor.spawnProcess}.
+
+ @return: The L{IProcessTransport} provider for the spawned process.
+ """
+ import twisted
+ subenv = dict(os.environ)
+ subenv['PYTHONPATH'] = os.pathsep.join(
+ [os.path.abspath(
+ os.path.dirname(os.path.dirname(twisted.__file__))),
+ subenv.get('PYTHONPATH', '')
+ ])
+ args = [sys.executable,
+ filepath.FilePath(__file__).sibling(sibling).path,
+ reactor.__class__.__module__] + list(args)
+ return reactor.spawnProcess(
+ proto,
+ sys.executable,
+ args,
+ env=subenv,
+ **kw)
+
+
+ def _requireFailure(self, d, callback):
+ def cb(result):
+ self.fail("Process terminated with non-Failure: %r" % (result,))
+ def eb(err):
+ return callback(err)
+ return d.addCallbacks(cb, eb)
+
+
+ def test_loseConnection(self):
+ """
+ Verify that a protocol connected to L{StandardIO} can disconnect
+ itself using C{transport.loseConnection}.
+ """
+ errorLogFile = self.mktemp()
+ log.msg("Child process logging to " + errorLogFile)
+ p = StandardIOTestProcessProtocol()
+ d = p.onCompletion
+ self._spawnProcess(p, 'stdio_test_loseconn.py', errorLogFile)
+
+ def processEnded(reason):
+ # Copy the child's log to ours so it's more visible.
+ for line in file(errorLogFile):
+ log.msg("Child logged: " + line.rstrip())
+
+ self.failIfIn(1, p.data)
+ reason.trap(error.ProcessDone)
+ return self._requireFailure(d, processEnded)
+ test_loseConnection.skip = skipWindowsNopywin32
+
+
+ def test_lastWriteReceived(self):
+ """
+ Verify that a write made directly to stdout using L{os.write}
+ after StandardIO has finished is reliably received by the
+ process reading that stdout.
+ """
+ p = StandardIOTestProcessProtocol()
+
+ # Note: the OS X bug which prompted the addition of this test
+ # is an apparent race condition involving non-blocking PTYs.
+ # Delaying the parent process significantly increases the
+ # likelihood of the race going the wrong way. If you need to
+ # fiddle with this code at all, uncommenting the next line
+ # will likely make your life much easier. It is commented out
+ # because it makes the test quite slow.
+
+ # p.onConnection.addCallback(lambda ign: __import__('time').sleep(5))
+
+ try:
+ self._spawnProcess(
+ p, 'stdio_test_lastwrite.py', UNIQUE_LAST_WRITE_STRING,
+ usePTY=True)
+ except ValueError, e:
+ # Some platforms don't work with usePTY=True
+ raise unittest.SkipTest(str(e))
+
+ def processEnded(reason):
+ """
+ Asserts that the parent received the bytes written by the child
+ immediately after the child starts.
+ """
+ self.assertTrue(
+ p.data[1].endswith(UNIQUE_LAST_WRITE_STRING),
+ "Received %r from child, did not find expected bytes." % (
+ p.data,))
+ reason.trap(error.ProcessDone)
+ return self._requireFailure(p.onCompletion, processEnded)
+ test_lastWriteReceived.skip = skipWindowsNopywin32
+
+
+ def test_hostAndPeer(self):
+ """
+ Verify that the transport of a protocol connected to L{StandardIO}
+ has C{getHost} and C{getPeer} methods.
+ """
+ p = StandardIOTestProcessProtocol()
+ d = p.onCompletion
+ self._spawnProcess(p, 'stdio_test_hostpeer.py')
+
+ def processEnded(reason):
+ host, peer = p.data[1].splitlines()
+ self.failUnless(host)
+ self.failUnless(peer)
+ reason.trap(error.ProcessDone)
+ return self._requireFailure(d, processEnded)
+ test_hostAndPeer.skip = skipWindowsNopywin32
+
+
+ def test_write(self):
+ """
+ Verify that the C{write} method of the transport of a protocol
+ connected to L{StandardIO} sends bytes to standard out.
+ """
+ p = StandardIOTestProcessProtocol()
+ d = p.onCompletion
+
+ self._spawnProcess(p, 'stdio_test_write.py')
+
+ def processEnded(reason):
+ self.assertEquals(p.data[1], 'ok!')
+ reason.trap(error.ProcessDone)
+ return self._requireFailure(d, processEnded)
+ test_write.skip = skipWindowsNopywin32
+
+
+ def test_writeSequence(self):
+ """
+ Verify that the C{writeSequence} method of the transport of a
+ protocol connected to L{StandardIO} sends bytes to standard out.
+ """
+ p = StandardIOTestProcessProtocol()
+ d = p.onCompletion
+
+ self._spawnProcess(p, 'stdio_test_writeseq.py')
+
+ def processEnded(reason):
+ self.assertEquals(p.data[1], 'ok!')
+ reason.trap(error.ProcessDone)
+ return self._requireFailure(d, processEnded)
+ test_writeSequence.skip = skipWindowsNopywin32
+
+
+ def _junkPath(self):
+ junkPath = self.mktemp()
+ junkFile = file(junkPath, 'w')
+ for i in xrange(1024):
+ junkFile.write(str(i) + '\n')
+ junkFile.close()
+ return junkPath
+
+
+ def test_producer(self):
+ """
+ Verify that the transport of a protocol connected to L{StandardIO}
+ is a working L{IProducer} provider.
+ """
+ p = StandardIOTestProcessProtocol()
+ d = p.onCompletion
+
+ written = []
+ toWrite = range(100)
+
+ def connectionMade(ign):
+ if toWrite:
+ written.append(str(toWrite.pop()) + "\n")
+ proc.write(written[-1])
+ reactor.callLater(0.01, connectionMade, None)
+
+ proc = self._spawnProcess(p, 'stdio_test_producer.py')
+
+ p.onConnection.addCallback(connectionMade)
+
+ def processEnded(reason):
+ self.assertEquals(p.data[1], ''.join(written))
+ self.failIf(toWrite, "Connection lost with %d writes left to go." % (len(toWrite),))
+ reason.trap(error.ProcessDone)
+ return self._requireFailure(d, processEnded)
+ test_producer.skip = skipWindowsNopywin32
+
+
+ def test_consumer(self):
+ """
+ Verify that the transport of a protocol connected to L{StandardIO}
+ is a working L{IConsumer} provider.
+ """
+ p = StandardIOTestProcessProtocol()
+ d = p.onCompletion
+
+ junkPath = self._junkPath()
+
+ self._spawnProcess(p, 'stdio_test_consumer.py', junkPath)
+
+ def processEnded(reason):
+ self.assertEquals(p.data[1], file(junkPath).read())
+ reason.trap(error.ProcessDone)
+ return self._requireFailure(d, processEnded)
+ test_consumer.skip = skipWindowsNopywin32
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_strcred.py b/vendor/Twisted-10.0.0/twisted/test/test_strcred.py
new file mode 100644
index 0000000000..6b3fd23fd0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_strcred.py
@@ -0,0 +1,622 @@
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.cred.strcred}.
+"""
+
+import os
+import StringIO
+
+from twisted import plugin
+from twisted.trial import unittest
+from twisted.cred import credentials, checkers, error, strcred
+from twisted.plugins import cred_file, cred_anonymous
+from twisted.python import usage
+from twisted.python.filepath import FilePath
+from twisted.python.fakepwd import UserDatabase
+
+try:
+ import crypt
+except ImportError:
+ crypt = None
+
+try:
+ import pwd
+except ImportError:
+ pwd = None
+
+try:
+ import spwd
+except ImportError:
+ spwd = None
+
+
+
+def getInvalidAuthType():
+ """
+ Helper method to produce an auth type that doesn't exist.
+ """
+ invalidAuthType = 'ThisPluginDoesNotExist'
+ while (invalidAuthType in
+ [factory.authType for factory in strcred.findCheckerFactories()]):
+ invalidAuthType += '_'
+ return invalidAuthType
+
+
+
+class TestPublicAPI(unittest.TestCase):
+
+ def test_emptyDescription(self):
+ """
+ Test that the description string cannot be empty.
+ """
+ iat = getInvalidAuthType()
+ self.assertRaises(strcred.InvalidAuthType, strcred.makeChecker, iat)
+ self.assertRaises(strcred.InvalidAuthType, strcred.findCheckerFactory, iat)
+
+
+ def test_invalidAuthType(self):
+ """
+ Test that an unrecognized auth type raises an exception.
+ """
+ iat = getInvalidAuthType()
+ self.assertRaises(strcred.InvalidAuthType, strcred.makeChecker, iat)
+ self.assertRaises(strcred.InvalidAuthType, strcred.findCheckerFactory, iat)
+
+
+
+class TestStrcredFunctions(unittest.TestCase):
+
+ def test_findCheckerFactories(self):
+ """
+ Test that findCheckerFactories returns all available plugins.
+ """
+ availablePlugins = list(strcred.findCheckerFactories())
+ for plg in plugin.getPlugins(strcred.ICheckerFactory):
+ self.assertIn(plg, availablePlugins)
+
+
+ def test_findCheckerFactory(self):
+ """
+ Test that findCheckerFactory returns the first plugin
+ available for a given authentication type.
+ """
+ self.assertIdentical(strcred.findCheckerFactory('file'),
+ cred_file.theFileCheckerFactory)
+
+
+
+class TestMemoryChecker(unittest.TestCase):
+
+ def setUp(self):
+ self.admin = credentials.UsernamePassword('admin', 'asdf')
+ self.alice = credentials.UsernamePassword('alice', 'foo')
+ self.badPass = credentials.UsernamePassword('alice', 'foobar')
+ self.badUser = credentials.UsernamePassword('x', 'yz')
+ self.checker = strcred.makeChecker('memory:admin:asdf:alice:foo')
+
+
+ def test_isChecker(self):
+ """
+ Verifies that strcred.makeChecker('memory') returns an object
+ that implements the L{ICredentialsChecker} interface.
+ """
+ self.assertTrue(checkers.ICredentialsChecker.providedBy(self.checker))
+ self.assertIn(credentials.IUsernamePassword,
+ self.checker.credentialInterfaces)
+
+
+ def test_badFormatArgString(self):
+ """
+ Test that an argument string which does not contain user:pass
+ pairs (i.e., an odd number of ':' characters) raises an exception.
+ """
+ self.assertRaises(strcred.InvalidAuthArgumentString,
+ strcred.makeChecker, 'memory:a:b:c')
+
+
+ def test_memoryCheckerSucceeds(self):
+ """
+ Test that the checker works with valid credentials.
+ """
+ def _gotAvatar(username):
+ self.assertEquals(username, self.admin.username)
+ return (self.checker
+ .requestAvatarId(self.admin)
+ .addCallback(_gotAvatar))
+
+
+ def test_memoryCheckerFailsUsername(self):
+ """
+ Test that the checker fails with an invalid username.
+ """
+ return self.assertFailure(self.checker.requestAvatarId(self.badUser),
+ error.UnauthorizedLogin)
+
+
+ def test_memoryCheckerFailsPassword(self):
+ """
+ Test that the checker fails with an invalid password.
+ """
+ return self.assertFailure(self.checker.requestAvatarId(self.badPass),
+ error.UnauthorizedLogin)
+
+
+
+class TestAnonymousChecker(unittest.TestCase):
+
+ def test_isChecker(self):
+ """
+ Verifies that strcred.makeChecker('anonymous') returns an object
+ that implements the L{ICredentialsChecker} interface.
+ """
+ checker = strcred.makeChecker('anonymous')
+ self.assertTrue(checkers.ICredentialsChecker.providedBy(checker))
+ self.assertIn(credentials.IAnonymous, checker.credentialInterfaces)
+
+
+ def testAnonymousAccessSucceeds(self):
+ """
+ Test that we can log in anonymously using this checker.
+ """
+ checker = strcred.makeChecker('anonymous')
+ request = checker.requestAvatarId(credentials.Anonymous())
+ def _gotAvatar(avatar):
+ self.assertIdentical(checkers.ANONYMOUS, avatar)
+ return request.addCallback(_gotAvatar)
+
+
+
+class TestUnixChecker(unittest.TestCase):
+ users = {
+ 'admin': 'asdf',
+ 'alice': 'foo',
+ }
+
+
+ def _spwd(self, username):
+ return (username, crypt.crypt(self.users[username], 'F/'),
+ 0, 0, 99999, 7, -1, -1, -1)
+
+
+ def setUp(self):
+ self.admin = credentials.UsernamePassword('admin', 'asdf')
+ self.alice = credentials.UsernamePassword('alice', 'foo')
+ self.badPass = credentials.UsernamePassword('alice', 'foobar')
+ self.badUser = credentials.UsernamePassword('x', 'yz')
+ self.checker = strcred.makeChecker('unix')
+
+ # Hack around the pwd and spwd modules, since we can't really
+ # go about reading your /etc/passwd or /etc/shadow files
+ if pwd:
+ database = UserDatabase()
+ for username, password in self.users.items():
+ database.addUser(
+ username, crypt.crypt(password, 'F/'),
+ 1000, 1000, username, '/home/' + username, '/bin/sh')
+ self.patch(pwd, 'getpwnam', database.getpwnam)
+ if spwd:
+ self._spwd_getspnam = spwd.getspnam
+ spwd.getspnam = self._spwd
+
+
+ def tearDown(self):
+ if spwd:
+ spwd.getspnam = self._spwd_getspnam
+
+
+ def test_isChecker(self):
+ """
+ Verifies that strcred.makeChecker('unix') returns an object
+ that implements the L{ICredentialsChecker} interface.
+ """
+ self.assertTrue(checkers.ICredentialsChecker.providedBy(self.checker))
+ self.assertIn(credentials.IUsernamePassword,
+ self.checker.credentialInterfaces)
+
+
+ def test_unixCheckerSucceeds(self):
+ """
+ Test that the checker works with valid credentials.
+ """
+ def _gotAvatar(username):
+ self.assertEquals(username, self.admin.username)
+ return (self.checker
+ .requestAvatarId(self.admin)
+ .addCallback(_gotAvatar))
+
+
+ def test_unixCheckerFailsUsername(self):
+ """
+ Test that the checker fails with an invalid username.
+ """
+ return self.assertFailure(self.checker.requestAvatarId(self.badUser),
+ error.UnauthorizedLogin)
+
+
+ def test_unixCheckerFailsPassword(self):
+ """
+ Test that the checker fails with an invalid password.
+ """
+ return self.assertFailure(self.checker.requestAvatarId(self.badPass),
+ error.UnauthorizedLogin)
+
+
+ if None in (pwd, spwd, crypt):
+ for method in (test_unixCheckerSucceeds,
+ test_unixCheckerFailsUsername,
+ test_unixCheckerFailsPassword):
+ method.skip = 'pwd and spwd are both unavailable'
+
+
+
+class TestFileDBChecker(unittest.TestCase):
+ """
+ Test for the --auth=file:... file checker.
+ """
+
+ def setUp(self):
+ self.admin = credentials.UsernamePassword('admin', 'asdf')
+ self.alice = credentials.UsernamePassword('alice', 'foo')
+ self.badPass = credentials.UsernamePassword('alice', 'foobar')
+ self.badUser = credentials.UsernamePassword('x', 'yz')
+ self.filename = self.mktemp()
+ FilePath(self.filename).setContent('admin:asdf\nalice:foo\n')
+ self.checker = strcred.makeChecker('file:' + self.filename)
+
+
+ def _fakeFilename(self):
+ filename = '/DoesNotExist'
+ while os.path.exists(filename):
+ filename += '_'
+ return filename
+
+
+ def test_isChecker(self):
+ """
+ Verifies that strcred.makeChecker('memory') returns an object
+ that implements the L{ICredentialsChecker} interface.
+ """
+ self.assertTrue(checkers.ICredentialsChecker.providedBy(self.checker))
+ self.assertIn(credentials.IUsernamePassword,
+ self.checker.credentialInterfaces)
+
+
+ def test_fileCheckerSucceeds(self):
+ """
+ Test that the checker works with valid credentials.
+ """
+ def _gotAvatar(username):
+ self.assertEquals(username, self.admin.username)
+ return (self.checker
+ .requestAvatarId(self.admin)
+ .addCallback(_gotAvatar))
+
+
+ def test_fileCheckerFailsUsername(self):
+ """
+ Test that the checker fails with an invalid username.
+ """
+ return self.assertFailure(self.checker.requestAvatarId(self.badUser),
+ error.UnauthorizedLogin)
+
+
+ def test_fileCheckerFailsPassword(self):
+ """
+ Test that the checker fails with an invalid password.
+ """
+ return self.assertFailure(self.checker.requestAvatarId(self.badPass),
+ error.UnauthorizedLogin)
+
+
+ def test_failsWithEmptyFilename(self):
+ """
+ Test that an empty filename raises an error.
+ """
+ self.assertRaises(ValueError, strcred.makeChecker, 'file')
+ self.assertRaises(ValueError, strcred.makeChecker, 'file:')
+
+
+ def test_warnWithBadFilename(self):
+ """
+ When the file auth plugin is given a file that doesn't exist, it
+ should produce a warning.
+ """
+ oldOutput = cred_file.theFileCheckerFactory.errorOutput
+ newOutput = StringIO.StringIO()
+ cred_file.theFileCheckerFactory.errorOutput = newOutput
+ checker = strcred.makeChecker('file:' + self._fakeFilename())
+ cred_file.theFileCheckerFactory.errorOutput = oldOutput
+ self.assertIn(cred_file.invalidFileWarning, newOutput.getvalue())
+
+
+
+class DummyOptions(usage.Options, strcred.AuthOptionMixin):
+ """
+ Simple options for testing L{strcred.AuthOptionMixin}.
+ """
+
+
+
+class TestCheckerOptions(unittest.TestCase):
+
+ def test_createsList(self):
+ """
+ Test that the --auth command line creates a list in the
+ Options instance and appends values to it.
+ """
+ options = DummyOptions()
+ options.parseOptions(['--auth', 'memory'])
+ self.assertEqual(len(options['credCheckers']), 1)
+ options = DummyOptions()
+ options.parseOptions(['--auth', 'memory', '--auth', 'memory'])
+ self.assertEqual(len(options['credCheckers']), 2)
+
+
+ def test_invalidAuthError(self):
+ """
+ Test that the --auth command line raises an exception when it
+ gets a parameter it doesn't understand.
+ """
+ options = DummyOptions()
+ # If someone adds a 'ThisPluginDoesNotExist' then this unit
+ # test should still run.
+ invalidParameter = getInvalidAuthType()
+ self.assertRaises(
+ usage.UsageError,
+ options.parseOptions, ['--auth', invalidParameter])
+ self.assertRaises(
+ usage.UsageError,
+ options.parseOptions, ['--help-auth-type', invalidParameter])
+
+
+ def test_createsDictionary(self):
+ """
+ Test that the --auth command line creates a dictionary
+ mapping supported interfaces to the list of credentials
+ checkers that support it.
+ """
+ options = DummyOptions()
+ options.parseOptions(['--auth', 'memory', '--auth', 'anonymous'])
+ chd = options['credInterfaces']
+ self.assertEquals(len(chd[credentials.IAnonymous]), 1)
+ self.assertEquals(len(chd[credentials.IUsernamePassword]), 1)
+ chdAnonymous = chd[credentials.IAnonymous][0]
+ chdUserPass = chd[credentials.IUsernamePassword][0]
+ self.assertTrue(checkers.ICredentialsChecker.providedBy(chdAnonymous))
+ self.assertTrue(checkers.ICredentialsChecker.providedBy(chdUserPass))
+ self.assertIn(credentials.IAnonymous,
+ chdAnonymous.credentialInterfaces)
+ self.assertIn(credentials.IUsernamePassword,
+ chdUserPass.credentialInterfaces)
+
+
+ def test_credInterfacesProvidesLists(self):
+ """
+ Test that when two --auth arguments are passed along which
+ support the same interface, a list with both is created.
+ """
+ options = DummyOptions()
+ options.parseOptions(['--auth', 'memory', '--auth', 'unix'])
+ self.assertEquals(
+ options['credCheckers'],
+ options['credInterfaces'][credentials.IUsernamePassword])
+
+
+ def test_listDoesNotDisplayDuplicates(self):
+ """
+ Test that the list for --help-auth does not duplicate items.
+ """
+ authTypes = []
+ options = DummyOptions()
+ for cf in options._checkerFactoriesForOptHelpAuth():
+ self.assertNotIn(cf.authType, authTypes)
+ authTypes.append(cf.authType)
+
+
+ def test_displaysListCorrectly(self):
+ """
+ Test that the --help-auth argument correctly displays all
+ available authentication plugins, then exits.
+ """
+ newStdout = StringIO.StringIO()
+ options = DummyOptions()
+ options.authOutput = newStdout
+ self.assertRaises(SystemExit, options.parseOptions, ['--help-auth'])
+ for checkerFactory in strcred.findCheckerFactories():
+ self.assertIn(checkerFactory.authType, newStdout.getvalue())
+
+
+ def test_displaysHelpCorrectly(self):
+ """
+ Test that the --help-auth-for argument will correctly display
+ the help file for a particular authentication plugin.
+ """
+ newStdout = StringIO.StringIO()
+ options = DummyOptions()
+ options.authOutput = newStdout
+ self.assertRaises(
+ SystemExit, options.parseOptions, ['--help-auth-type', 'file'])
+ for line in cred_file.theFileCheckerFactory.authHelp:
+ if line.strip():
+ self.assertIn(line.strip(), newStdout.getvalue())
+
+
+ def test_unexpectedException(self):
+ """
+ When the checker specified by --auth raises an unexpected error, it
+ should be caught and re-raised within a L{usage.UsageError}.
+ """
+ options = DummyOptions()
+ err = self.assertRaises(usage.UsageError, options.parseOptions,
+ ['--auth', 'file'])
+ self.assertEquals(str(err),
+ "Unexpected error: 'file' requires a filename")
+
+
+
+class OptionsForUsernamePassword(usage.Options, strcred.AuthOptionMixin):
+ supportedInterfaces = (credentials.IUsernamePassword,)
+
+
+
+class OptionsForUsernameHashedPassword(usage.Options, strcred.AuthOptionMixin):
+ supportedInterfaces = (credentials.IUsernameHashedPassword,)
+
+
+
+class OptionsSupportsAllInterfaces(usage.Options, strcred.AuthOptionMixin):
+ supportedInterfaces = None
+
+
+
+class OptionsSupportsNoInterfaces(usage.Options, strcred.AuthOptionMixin):
+ supportedInterfaces = []
+
+
+
+class TestLimitingInterfaces(unittest.TestCase):
+ """
+ Tests functionality that allows an application to limit the
+ credential interfaces it can support. For the purposes of this
+ test, we use IUsernameHashedPassword, although this will never
+ really be used by the command line.
+
+ (I have, to date, not thought of a half-decent way for a user to
+ specify a hash algorithm via the command-line. Nor do I think it's
+ very useful.)
+
+ I should note that, at first, this test is counter-intuitive,
+ because we're using the checker with a pre-defined hash function
+ as the 'bad' checker. See the documentation for
+ L{twisted.cred.checkers.FilePasswordDB.hash} for more details.
+ """
+
+ def setUp(self):
+ self.filename = self.mktemp()
+ file(self.filename, 'w').write('admin:asdf\nalice:foo\n')
+ self.goodChecker = checkers.FilePasswordDB(self.filename)
+ self.badChecker = checkers.FilePasswordDB(self.filename, hash=self._hash)
+ self.anonChecker = checkers.AllowAnonymousAccess()
+
+
+ def _hash(self, networkUsername, networkPassword, storedPassword):
+ """
+ A dumb hash that doesn't really do anything.
+ """
+ return networkPassword
+
+
+ def test_supportsInterface(self):
+ """
+ Test that the supportsInterface method behaves appropriately.
+ """
+ options = OptionsForUsernamePassword()
+ self.assertTrue(
+ options.supportsInterface(credentials.IUsernamePassword))
+ self.assertFalse(
+ options.supportsInterface(credentials.IAnonymous))
+ self.assertRaises(
+ strcred.UnsupportedInterfaces, options.addChecker, self.anonChecker)
+
+
+ def test_supportsAllInterfaces(self):
+ """
+ Test that the supportsInterface method behaves appropriately
+ when the supportedInterfaces attribute is None.
+ """
+ options = OptionsSupportsAllInterfaces()
+ self.assertTrue(
+ options.supportsInterface(credentials.IUsernamePassword))
+ self.assertTrue(
+ options.supportsInterface(credentials.IAnonymous))
+
+
+ def test_supportsCheckerFactory(self):
+ """
+ Test that the supportsCheckerFactory method behaves appropriately.
+ """
+ options = OptionsForUsernamePassword()
+ fileCF = cred_file.theFileCheckerFactory
+ anonCF = cred_anonymous.theAnonymousCheckerFactory
+ self.assertTrue(options.supportsCheckerFactory(fileCF))
+ self.assertFalse(options.supportsCheckerFactory(anonCF))
+
+
+ def test_canAddSupportedChecker(self):
+ """
+ Test that when addChecker is called with a checker that
+ implements at least one of the interfaces our application
+ supports, it is successful.
+ """
+ options = OptionsForUsernamePassword()
+ options.addChecker(self.goodChecker)
+ iface = options.supportedInterfaces[0]
+ # Test that we did get IUsernamePassword
+ self.assertIdentical(options['credInterfaces'][iface][0], self.goodChecker)
+ self.assertIdentical(options['credCheckers'][0], self.goodChecker)
+ # Test that we didn't get IUsernameHashedPassword
+ self.assertEquals(len(options['credInterfaces'][iface]), 1)
+ self.assertEquals(len(options['credCheckers']), 1)
+
+
+ def test_failOnAddingUnsupportedChecker(self):
+ """
+ Test that when addChecker is called with a checker that does
+ not implement any supported interfaces, it fails.
+ """
+ options = OptionsForUsernameHashedPassword()
+ self.assertRaises(strcred.UnsupportedInterfaces,
+ options.addChecker, self.badChecker)
+
+
+ def test_unsupportedInterfaceError(self):
+ """
+ Test that the --auth command line raises an exception when it
+ gets a checker we don't support.
+ """
+ options = OptionsSupportsNoInterfaces()
+ authType = cred_anonymous.theAnonymousCheckerFactory.authType
+ self.assertRaises(
+ usage.UsageError,
+ options.parseOptions, ['--auth', authType])
+
+
+ def test_helpAuthLimitsOutput(self):
+ """
+ Test that --help-auth will only list checkers that purport to
+ supply at least one of the credential interfaces our
+ application can use.
+ """
+ options = OptionsForUsernamePassword()
+ for factory in options._checkerFactoriesForOptHelpAuth():
+ invalid = True
+ for interface in factory.credentialInterfaces:
+ if options.supportsInterface(interface):
+ invalid = False
+ if invalid:
+ raise strcred.UnsupportedInterfaces()
+
+
+ def test_helpAuthTypeLimitsOutput(self):
+ """
+ Test that --help-auth-type will display a warning if you get
+ help for an authType that does not supply at least one of the
+ credential interfaces our application can use.
+ """
+ options = OptionsForUsernamePassword()
+ # Find an interface that we can use for our test
+ invalidFactory = None
+ for factory in strcred.findCheckerFactories():
+ if not options.supportsCheckerFactory(factory):
+ invalidFactory = factory
+ break
+ self.assertNotIdentical(invalidFactory, None)
+ # Capture output and make sure the warning is there
+ newStdout = StringIO.StringIO()
+ options.authOutput = newStdout
+ self.assertRaises(SystemExit, options.parseOptions,
+ ['--help-auth-type', 'anonymous'])
+ self.assertIn(strcred.notSupportedWarning, newStdout.getvalue())
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_strerror.py b/vendor/Twisted-10.0.0/twisted/test/test_strerror.py
new file mode 100644
index 0000000000..8918cf5c83
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_strerror.py
@@ -0,0 +1,145 @@
+# Copyright (c) 2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test strerror
+"""
+
+import socket
+import os
+
+from twisted.trial.unittest import TestCase
+from twisted.internet.tcp import ECONNABORTED
+from twisted.python.win32 import _ErrorFormatter, formatError
+from twisted.python.runtime import platform
+
+
+
+class ErrorFormatingTestCase(TestCase):
+ """
+ Tests for C{_ErrorFormatter.formatError}.
+ """
+ probeErrorCode = ECONNABORTED
+ probeMessage = "correct message value"
+
+ def test_strerrorFormatting(self):
+ """
+ L{_ErrorFormatter.formatError} should use L{os.strerror} to format
+ error messages if it is constructed without any better mechanism.
+ """
+ formatter = _ErrorFormatter(None, None, None)
+ message = formatter.formatError(self.probeErrorCode)
+ self.assertEqual(message, os.strerror(self.probeErrorCode))
+
+
+ def test_emptyErrorTab(self):
+ """
+ L{_ErrorFormatter.formatError} should use L{os.strerror} to format
+ error messages if it is constructed with only an error tab which does
+ not contain the error code it is called with.
+ """
+ error = 1
+ # Sanity check
+ self.assertNotEqual(self.probeErrorCode, error)
+ formatter = _ErrorFormatter(None, None, {error: 'wrong message'})
+ message = formatter.formatError(self.probeErrorCode)
+ self.assertEqual(message, os.strerror(self.probeErrorCode))
+
+
+ def test_errorTab(self):
+ """
+ L{_ErrorFormatter.formatError} should use C{errorTab} if it is supplied
+ and contains the requested error code.
+ """
+ formatter = _ErrorFormatter(
+ None, None, {self.probeErrorCode: self.probeMessage})
+ message = formatter.formatError(self.probeErrorCode)
+ self.assertEqual(message, self.probeMessage)
+
+
+ def test_formatMessage(self):
+ """
+ L{_ErrorFormatter.formatError} should return the return value of
+ C{formatMessage} if it is supplied.
+ """
+ formatCalls = []
+ def formatMessage(errorCode):
+ formatCalls.append(errorCode)
+ return self.probeMessage
+ formatter = _ErrorFormatter(
+ None, formatMessage, {self.probeErrorCode: 'wrong message'})
+ message = formatter.formatError(self.probeErrorCode)
+ self.assertEqual(message, self.probeMessage)
+ self.assertEqual(formatCalls, [self.probeErrorCode])
+
+
+ def test_winError(self):
+ """
+ L{_ErrorFormatter.formatError} should return the message argument from
+ the exception L{winError} returns, if L{winError} is supplied.
+ """
+ winCalls = []
+ def winError(errorCode):
+ winCalls.append(errorCode)
+ return (errorCode, self.probeMessage)
+ formatter = _ErrorFormatter(
+ winError,
+ lambda error: 'formatMessage: wrong message',
+ {self.probeErrorCode: 'errorTab: wrong message'})
+ message = formatter.formatError(self.probeErrorCode)
+ self.assertEqual(message, self.probeMessage)
+
+
+ def test_fromEnvironment(self):
+ """
+ L{_ErrorFormatter.fromEnvironment} should create an L{_ErrorFormatter}
+ instance with attributes populated from available modules.
+ """
+ formatter = _ErrorFormatter.fromEnvironment()
+
+ if formatter.winError is not None:
+ from ctypes import WinError
+ self.assertEqual(
+ formatter.formatError(self.probeErrorCode),
+ WinError(self.probeErrorCode)[1])
+ formatter.winError = None
+
+ if formatter.formatMessage is not None:
+ from win32api import FormatMessage
+ self.assertEqual(
+ formatter.formatError(self.probeErrorCode),
+ FormatMessage(self.probeErrorCode))
+ formatter.formatMessage = None
+
+ if formatter.errorTab is not None:
+ from socket import errorTab
+ self.assertEqual(
+ formatter.formatError(self.probeErrorCode),
+ errorTab[self.probeErrorCode])
+
+ if platform.getType() != "win32":
+ test_fromEnvironment.skip = "This error lookup only works on Windows"
+
+
+ def test_correctLookups(self):
+ """
+ Given an known-good errno, make sure that formatMessage gives results
+ matching either C{socket.errorTab}, C{ctypes.WinError}, or
+ C{win32api.FormatMessage}.
+ """
+ acceptable = [socket.errorTab[ECONNABORTED]]
+ try:
+ from ctypes import WinError
+ acceptable.append(WinError(ECONNABORTED)[1])
+ except ImportError:
+ pass
+ try:
+ from win32api import FormatMessage
+ acceptable.append(FormatMessage(ECONNABORTED))
+ except ImportError:
+ pass
+
+ self.assertIn(formatError(ECONNABORTED), acceptable)
+
+ if platform.getType() != "win32":
+ test_correctLookups.skip = "This error lookup only works on Windows"
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_stringtransport.py b/vendor/Twisted-10.0.0/twisted/test/test_stringtransport.py
new file mode 100644
index 0000000000..91e14c1b31
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_stringtransport.py
@@ -0,0 +1,160 @@
+# Copyright (c) 2009-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.test.proto_helpers.StringTransport}.
+"""
+
+from zope.interface.verify import verifyObject
+
+from twisted.internet.interfaces import ITransport, IPushProducer, IConsumer
+from twisted.trial.unittest import TestCase
+from twisted.test.proto_helpers import StringTransport
+
+
+class StringTransportTests(TestCase):
+ """
+ Tests for L{twisted.test.proto_helpers.StringTransport}.
+ """
+ def setUp(self):
+ self.transport = StringTransport()
+
+
+ def test_interfaces(self):
+ """
+ L{StringTransport} instances provide L{ITransport}, L{IPushProducer},
+ and L{IConsumer}.
+ """
+ self.assertTrue(verifyObject(ITransport, self.transport))
+ self.assertTrue(verifyObject(IPushProducer, self.transport))
+ self.assertTrue(verifyObject(IConsumer, self.transport))
+
+
+ def test_registerProducer(self):
+ """
+ L{StringTransport.registerProducer} records the arguments supplied to
+ it as instance attributes.
+ """
+ producer = object()
+ streaming = object()
+ self.transport.registerProducer(producer, streaming)
+ self.assertIdentical(self.transport.producer, producer)
+ self.assertIdentical(self.transport.streaming, streaming)
+
+
+ def test_disallowedRegisterProducer(self):
+ """
+ L{StringTransport.registerProducer} raises L{RuntimeError} if a
+ producer is already registered.
+ """
+ producer = object()
+ self.transport.registerProducer(producer, True)
+ self.assertRaises(
+ RuntimeError, self.transport.registerProducer, object(), False)
+ self.assertIdentical(self.transport.producer, producer)
+ self.assertTrue(self.transport.streaming)
+
+
+ def test_unregisterProducer(self):
+ """
+ L{StringTransport.unregisterProducer} causes the transport to forget
+ about the registered producer and makes it possible to register a new
+ one.
+ """
+ oldProducer = object()
+ newProducer = object()
+ self.transport.registerProducer(oldProducer, False)
+ self.transport.unregisterProducer()
+ self.assertIdentical(self.transport.producer, None)
+ self.transport.registerProducer(newProducer, True)
+ self.assertIdentical(self.transport.producer, newProducer)
+ self.assertTrue(self.transport.streaming)
+
+
+ def test_invalidUnregisterProducer(self):
+ """
+ L{StringTransport.unregisterProducer} raises L{RuntimeError} if called
+ when no producer is registered.
+ """
+ self.assertRaises(RuntimeError, self.transport.unregisterProducer)
+
+
+ def test_initialProducerState(self):
+ """
+ L{StringTransport.producerState} is initially C{'producing'}.
+ """
+ self.assertEqual(self.transport.producerState, 'producing')
+
+
+ def test_pauseProducing(self):
+ """
+ L{StringTransport.pauseProducing} changes the C{producerState} of the
+ transport to C{'paused'}.
+ """
+ self.transport.pauseProducing()
+ self.assertEqual(self.transport.producerState, 'paused')
+
+
+ def test_resumeProducing(self):
+ """
+ L{StringTransport.resumeProducing} changes the C{producerState} of the
+ transport to C{'producing'}.
+ """
+ self.transport.pauseProducing()
+ self.transport.resumeProducing()
+ self.assertEqual(self.transport.producerState, 'producing')
+
+
+ def test_stopProducing(self):
+ """
+ L{StringTransport.stopProducing} changes the C{'producerState'} of the
+ transport to C{'stopped'}.
+ """
+ self.transport.stopProducing()
+ self.assertEqual(self.transport.producerState, 'stopped')
+
+
+ def test_stoppedTransportCannotPause(self):
+ """
+ L{StringTransport.pauseProducing} raises L{RuntimeError} if the
+ transport has been stopped.
+ """
+ self.transport.stopProducing()
+ self.assertRaises(RuntimeError, self.transport.pauseProducing)
+
+
+ def test_stoppedTransportCannotResume(self):
+ """
+ L{StringTransport.resumeProducing} raises L{RuntimeError} if the
+ transport has been stopped.
+ """
+ self.transport.stopProducing()
+ self.assertRaises(RuntimeError, self.transport.resumeProducing)
+
+
+ def test_disconnectingTransportCannotPause(self):
+ """
+ L{StringTransport.pauseProducing} raises L{RuntimeError} if the
+ transport is being disconnected.
+ """
+ self.transport.loseConnection()
+ self.assertRaises(RuntimeError, self.transport.pauseProducing)
+
+
+ def test_disconnectingTransportCannotResume(self):
+ """
+ L{StringTransport.resumeProducing} raises L{RuntimeError} if the
+ transport is being disconnected.
+ """
+ self.transport.loseConnection()
+ self.assertRaises(RuntimeError, self.transport.resumeProducing)
+
+
+ def test_loseConnectionSetsDisconnecting(self):
+ """
+ L{StringTransport.loseConnection} toggles the C{disconnecting} instance
+ variable to C{True}.
+ """
+ self.assertFalse(self.transport.disconnecting)
+ self.transport.loseConnection()
+ self.assertTrue(self.transport.disconnecting)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_strports.py b/vendor/Twisted-10.0.0/twisted/test/test_strports.py
new file mode 100644
index 0000000000..1e58f2f6c6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_strports.py
@@ -0,0 +1,84 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.application import strports
+from twisted.trial import unittest
+
+class ParserTestCase(unittest.TestCase):
+
+ f = "Factory"
+
+ def testSimpleNumeric(self):
+ self.assertEqual(strports.parse('80', self.f),
+ ('TCP', (80, self.f), {'interface':'', 'backlog':50}))
+
+ def testSimpleTCP(self):
+ self.assertEqual(strports.parse('tcp:80', self.f),
+ ('TCP', (80, self.f), {'interface':'', 'backlog':50}))
+
+ def testInterfaceTCP(self):
+ self.assertEqual(strports.parse('tcp:80:interface=127.0.0.1', self.f),
+ ('TCP', (80, self.f),
+ {'interface':'127.0.0.1', 'backlog':50}))
+
+ def testBacklogTCP(self):
+ self.assertEqual(strports.parse('tcp:80:backlog=6', self.f),
+ ('TCP', (80, self.f),
+ {'interface':'', 'backlog':6}))
+
+
+ def test_simpleUNIX(self):
+ """
+ L{strports.parse} returns a C{'UNIX'} port description with defaults
+ for C{'mode'}, C{'backlog'}, and C{'wantPID'} when passed a string with
+ the C{'unix:'} prefix and no other parameter values.
+ """
+ self.assertEqual(
+ strports.parse('unix:/var/run/finger', self.f),
+ ('UNIX', ('/var/run/finger', self.f),
+ {'mode': 0666, 'backlog': 50, 'wantPID': True}))
+
+
+ def test_modeUNIX(self):
+ """
+ C{mode} can be set by including C{"mode=<some integer>"}.
+ """
+ self.assertEqual(
+ strports.parse('unix:/var/run/finger:mode=0660', self.f),
+ ('UNIX', ('/var/run/finger', self.f),
+ {'mode': 0660, 'backlog': 50, 'wantPID': True}))
+
+
+ def test_wantPIDUNIX(self):
+ """
+ C{wantPID} can be set to false by included C{"lockfile=0"}.
+ """
+ self.assertEqual(
+ strports.parse('unix:/var/run/finger:lockfile=0', self.f),
+ ('UNIX', ('/var/run/finger', self.f),
+ {'mode': 0666, 'backlog': 50, 'wantPID': False}))
+
+
+ def testAllKeywords(self):
+ self.assertEqual(strports.parse('port=80', self.f),
+ ('TCP', (80, self.f), {'interface':'', 'backlog':50}))
+
+ def testEscape(self):
+ self.assertEqual(
+ strports.parse(r'unix:foo\:bar\=baz\:qux\\', self.f),
+ ('UNIX', ('foo:bar=baz:qux\\', self.f),
+ {'mode': 0666, 'backlog': 50, 'wantPID': True}))
+
+
+ def testImpliedEscape(self):
+ self.assertEqual(
+ strports.parse(r'unix:address=foo=bar', self.f),
+ ('UNIX', ('foo=bar', self.f),
+ {'mode': 0666, 'backlog': 50, 'wantPID': True}))
+
+ def testNonstandardDefault(self):
+ self.assertEqual(
+ strports.parse('filename', self.f, 'unix'),
+ ('UNIX', ('filename', self.f),
+ {'mode': 0666, 'backlog': 50, 'wantPID': True}))
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_task.py b/vendor/Twisted-10.0.0/twisted/test/test_task.py
new file mode 100644
index 0000000000..3df212f0b9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_task.py
@@ -0,0 +1,627 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.python.compat import set
+
+from twisted.trial import unittest
+
+from twisted.internet import interfaces, task, reactor, defer, error
+
+# Be compatible with any jerks who used our private stuff
+Clock = task.Clock
+
+from twisted.python import failure
+
+
+class TestableLoopingCall(task.LoopingCall):
+ def __init__(self, clock, *a, **kw):
+ super(TestableLoopingCall, self).__init__(*a, **kw)
+ self.clock = clock
+
+
+
+class TestException(Exception):
+ pass
+
+
+
+class ClockTestCase(unittest.TestCase):
+ """
+ Test the non-wallclock based clock implementation.
+ """
+ def testSeconds(self):
+ """
+ Test that the L{seconds} method of the fake clock returns fake time.
+ """
+ c = task.Clock()
+ self.assertEquals(c.seconds(), 0)
+
+
+ def testCallLater(self):
+ """
+ Test that calls can be scheduled for later with the fake clock and
+ hands back an L{IDelayedCall}.
+ """
+ c = task.Clock()
+ call = c.callLater(1, lambda a, b: None, 1, b=2)
+ self.failUnless(interfaces.IDelayedCall.providedBy(call))
+ self.assertEquals(call.getTime(), 1)
+ self.failUnless(call.active())
+
+
+ def testCallLaterCancelled(self):
+ """
+ Test that calls can be cancelled.
+ """
+ c = task.Clock()
+ call = c.callLater(1, lambda a, b: None, 1, b=2)
+ call.cancel()
+ self.failIf(call.active())
+
+
+ def test_callLaterOrdering(self):
+ """
+ Test that the DelayedCall returned is not one previously
+ created.
+ """
+ c = task.Clock()
+ call1 = c.callLater(10, lambda a, b: None, 1, b=2)
+ call2 = c.callLater(1, lambda a, b: None, 3, b=4)
+ self.failIf(call1 is call2)
+
+
+ def testAdvance(self):
+ """
+ Test that advancing the clock will fire some calls.
+ """
+ events = []
+ c = task.Clock()
+ call = c.callLater(2, lambda: events.append(None))
+ c.advance(1)
+ self.assertEquals(events, [])
+ c.advance(1)
+ self.assertEquals(events, [None])
+ self.failIf(call.active())
+
+
+ def testAdvanceCancel(self):
+ """
+ Test attemping to cancel the call in a callback.
+
+ AlreadyCalled should be raised, not for example a ValueError from
+ removing the call from Clock.calls. This requires call.called to be
+ set before the callback is called.
+ """
+ c = task.Clock()
+ def cb():
+ self.assertRaises(error.AlreadyCalled, call.cancel)
+ call = c.callLater(1, cb)
+ c.advance(1)
+
+
+ def testCallLaterDelayed(self):
+ """
+ Test that calls can be delayed.
+ """
+ events = []
+ c = task.Clock()
+ call = c.callLater(1, lambda a, b: events.append((a, b)), 1, b=2)
+ call.delay(1)
+ self.assertEquals(call.getTime(), 2)
+ c.advance(1.5)
+ self.assertEquals(events, [])
+ c.advance(1.0)
+ self.assertEquals(events, [(1, 2)])
+
+
+ def testCallLaterResetLater(self):
+ """
+ Test that calls can have their time reset to a later time.
+ """
+ events = []
+ c = task.Clock()
+ call = c.callLater(2, lambda a, b: events.append((a, b)), 1, b=2)
+ c.advance(1)
+ call.reset(3)
+ self.assertEquals(call.getTime(), 4)
+ c.advance(2)
+ self.assertEquals(events, [])
+ c.advance(1)
+ self.assertEquals(events, [(1, 2)])
+
+
+ def testCallLaterResetSooner(self):
+ """
+ Test that calls can have their time reset to an earlier time.
+ """
+ events = []
+ c = task.Clock()
+ call = c.callLater(4, lambda a, b: events.append((a, b)), 1, b=2)
+ call.reset(3)
+ self.assertEquals(call.getTime(), 3)
+ c.advance(3)
+ self.assertEquals(events, [(1, 2)])
+
+
+ def test_getDelayedCalls(self):
+ """
+ Test that we can get a list of all delayed calls
+ """
+ c = task.Clock()
+ call = c.callLater(1, lambda x: None)
+ call2 = c.callLater(2, lambda x: None)
+
+ calls = c.getDelayedCalls()
+
+ self.assertEquals(set([call, call2]), set(calls))
+
+
+ def test_getDelayedCallsEmpty(self):
+ """
+ Test that we get an empty list from getDelayedCalls on a newly
+ constructed Clock.
+ """
+ c = task.Clock()
+ self.assertEquals(c.getDelayedCalls(), [])
+
+
+ def test_providesIReactorTime(self):
+ c = task.Clock()
+ self.failUnless(interfaces.IReactorTime.providedBy(c),
+ "Clock does not provide IReactorTime")
+
+
+class LoopTestCase(unittest.TestCase):
+ """
+ Tests for L{task.LoopingCall} based on a fake L{IReactorTime}
+ implementation.
+ """
+ def test_defaultClock(self):
+ """
+ L{LoopingCall}'s default clock should be the reactor.
+ """
+ call = task.LoopingCall(lambda: None)
+ self.assertEqual(call.clock, reactor)
+
+
+ def test_callbackTimeSkips(self):
+ """
+ When more time than the defined interval passes during the execution
+ of a callback, L{LoopingCall} should schedule the next call for the
+ next interval which is still in the future.
+ """
+ times = []
+ callDuration = None
+ clock = task.Clock()
+ def aCallback():
+ times.append(clock.seconds())
+ clock.advance(callDuration)
+ call = task.LoopingCall(aCallback)
+ call.clock = clock
+
+ # Start a LoopingCall with a 0.5 second increment, and immediately call
+ # the callable.
+ callDuration = 2
+ call.start(0.5)
+
+ # Verify that the callable was called, and since it was immediate, with
+ # no skips.
+ self.assertEqual(times, [0])
+
+ # The callback should have advanced the clock by the callDuration.
+ self.assertEqual(clock.seconds(), callDuration)
+
+ # An iteration should have occurred at 2, but since 2 is the present
+ # and not the future, it is skipped.
+
+ clock.advance(0)
+ self.assertEqual(times, [0])
+
+ # 2.5 is in the future, and is not skipped.
+ callDuration = 1
+ clock.advance(0.5)
+ self.assertEqual(times, [0, 2.5])
+ self.assertEqual(clock.seconds(), 3.5)
+
+ # Another iteration should have occurred, but it is again the
+ # present and not the future, so it is skipped as well.
+ clock.advance(0)
+ self.assertEqual(times, [0, 2.5])
+
+ # 4 is in the future, and is not skipped.
+ callDuration = 0
+ clock.advance(0.5)
+ self.assertEqual(times, [0, 2.5, 4])
+ self.assertEqual(clock.seconds(), 4)
+
+
+ def test_reactorTimeSkips(self):
+ """
+ When more time than the defined interval passes between when
+ L{LoopingCall} schedules itself to run again and when it actually
+ runs again, it should schedule the next call for the next interval
+ which is still in the future.
+ """
+ times = []
+ clock = task.Clock()
+ def aCallback():
+ times.append(clock.seconds())
+
+ # Start a LoopingCall that tracks the time passed, with a 0.5 second
+ # increment.
+ call = task.LoopingCall(aCallback)
+ call.clock = clock
+ call.start(0.5)
+
+ # Initially, no time should have passed!
+ self.assertEqual(times, [0])
+
+ # Advance the clock by 2 seconds (2 seconds should have passed)
+ clock.advance(2)
+ self.assertEqual(times, [0, 2])
+
+ # Advance the clock by 1 second (3 total should have passed)
+ clock.advance(1)
+ self.assertEqual(times, [0, 2, 3])
+
+ # Advance the clock by 0 seconds (this should have no effect!)
+ clock.advance(0)
+ self.assertEqual(times, [0, 2, 3])
+
+
+ def test_reactorTimeCountSkips(self):
+ """
+ When L{LoopingCall} schedules itself to run again, if more than the
+ specified interval has passed, it should schedule the next call for the
+ next interval which is still in the future. If it was created
+ using L{LoopingCall.withCount}, a positional argument will be
+ inserted at the beginning of the argument list, indicating the number
+ of calls that should have been made.
+ """
+ times = []
+ clock = task.Clock()
+ def aCallback(numCalls):
+ times.append((clock.seconds(), numCalls))
+
+ # Start a LoopingCall that tracks the time passed, and the number of
+ # skips, with a 0.5 second increment.
+ call = task.LoopingCall.withCount(aCallback)
+ call.clock = clock
+ INTERVAL = 0.5
+ REALISTIC_DELAY = 0.01
+ call.start(INTERVAL)
+
+ # Initially, no seconds should have passed, and one calls should have
+ # been made.
+ self.assertEqual(times, [(0, 1)])
+
+ # After the interval (plus a small delay, to account for the time that
+ # the reactor takes to wake up and process the LoopingCall), we should
+ # still have only made one call.
+ clock.advance(INTERVAL + REALISTIC_DELAY)
+ self.assertEqual(times, [(0, 1), (INTERVAL + REALISTIC_DELAY, 1)])
+
+ # After advancing the clock by three intervals (plus a small delay to
+ # account for the reactor), we should have skipped two calls; one less
+ # than the number of intervals which have completely elapsed. Along
+ # with the call we did actually make, the final number of calls is 3.
+ clock.advance((3 * INTERVAL) + REALISTIC_DELAY)
+ self.assertEqual(times,
+ [(0, 1), (INTERVAL + REALISTIC_DELAY, 1),
+ ((4 * INTERVAL) + (2 * REALISTIC_DELAY), 3)])
+
+ # Advancing the clock by 0 seconds should not cause any changes!
+ clock.advance(0)
+ self.assertEqual(times,
+ [(0, 1), (INTERVAL + REALISTIC_DELAY, 1),
+ ((4 * INTERVAL) + (2 * REALISTIC_DELAY), 3)])
+
+
+ def test_countLengthyIntervalCounts(self):
+ """
+ L{LoopingCall.withCount} counts only calls that were expected to be
+ made. So, if more than one, but less than two intervals pass between
+ invocations, it won't increase the count above 1. For example, a
+ L{LoopingCall} with interval T expects to be invoked at T, 2T, 3T, etc.
+ However, the reactor takes some time to get around to calling it, so in
+ practice it will be called at T+something, 2T+something, 3T+something;
+ and due to other things going on in the reactor, "something" is
+ variable. It won't increase the count unless "something" is greater
+ than T. So if the L{LoopingCall} is invoked at T, 2.75T, and 3T,
+ the count has not increased, even though the distance between
+ invocation 1 and invocation 2 is 1.75T.
+ """
+ times = []
+ clock = task.Clock()
+ def aCallback(count):
+ times.append((clock.seconds(), count))
+
+ # Start a LoopingCall that tracks the time passed, and the number of
+ # calls, with a 0.5 second increment.
+ call = task.LoopingCall.withCount(aCallback)
+ call.clock = clock
+ INTERVAL = 0.5
+ REALISTIC_DELAY = 0.01
+ call.start(INTERVAL)
+ self.assertEqual(times.pop(), (0, 1))
+
+ # About one interval... So far, so good
+ clock.advance(INTERVAL + REALISTIC_DELAY)
+ self.assertEqual(times.pop(), (INTERVAL + REALISTIC_DELAY, 1))
+
+ # Oh no, something delayed us for a while.
+ clock.advance(INTERVAL * 1.75)
+ self.assertEqual(times.pop(), ((2.75 * INTERVAL) + REALISTIC_DELAY, 1))
+
+ # Back on track! We got invoked when we expected this time.
+ clock.advance(INTERVAL * 0.25)
+ self.assertEqual(times.pop(), ((3.0 * INTERVAL) + REALISTIC_DELAY, 1))
+
+
+ def testBasicFunction(self):
+ # Arrange to have time advanced enough so that our function is
+ # called a few times.
+ # Only need to go to 2.5 to get 3 calls, since the first call
+ # happens before any time has elapsed.
+ timings = [0.05, 0.1, 0.1]
+
+ clock = task.Clock()
+
+ L = []
+ def foo(a, b, c=None, d=None):
+ L.append((a, b, c, d))
+
+ lc = TestableLoopingCall(clock, foo, "a", "b", d="d")
+ D = lc.start(0.1)
+
+ theResult = []
+ def saveResult(result):
+ theResult.append(result)
+ D.addCallback(saveResult)
+
+ clock.pump(timings)
+
+ self.assertEquals(len(L), 3,
+ "got %d iterations, not 3" % (len(L),))
+
+ for (a, b, c, d) in L:
+ self.assertEquals(a, "a")
+ self.assertEquals(b, "b")
+ self.assertEquals(c, None)
+ self.assertEquals(d, "d")
+
+ lc.stop()
+ self.assertIdentical(theResult[0], lc)
+
+ # Make sure it isn't planning to do anything further.
+ self.failIf(clock.calls)
+
+
+ def testDelayedStart(self):
+ timings = [0.05, 0.1, 0.1]
+
+ clock = task.Clock()
+
+ L = []
+ lc = TestableLoopingCall(clock, L.append, None)
+ d = lc.start(0.1, now=False)
+
+ theResult = []
+ def saveResult(result):
+ theResult.append(result)
+ d.addCallback(saveResult)
+
+ clock.pump(timings)
+
+ self.assertEquals(len(L), 2,
+ "got %d iterations, not 2" % (len(L),))
+ lc.stop()
+ self.assertIdentical(theResult[0], lc)
+
+ self.failIf(clock.calls)
+
+
+ def testBadDelay(self):
+ lc = task.LoopingCall(lambda: None)
+ self.assertRaises(ValueError, lc.start, -1)
+
+
+ # Make sure that LoopingCall.stop() prevents any subsequent calls.
+ def _stoppingTest(self, delay):
+ ran = []
+ def foo():
+ ran.append(None)
+
+ clock = task.Clock()
+ lc = TestableLoopingCall(clock, foo)
+ d = lc.start(delay, now=False)
+ lc.stop()
+ self.failIf(ran)
+ self.failIf(clock.calls)
+
+
+ def testStopAtOnce(self):
+ return self._stoppingTest(0)
+
+
+ def testStoppingBeforeDelayedStart(self):
+ return self._stoppingTest(10)
+
+
+
+class ReactorLoopTestCase(unittest.TestCase):
+ # Slightly inferior tests which exercise interactions with an actual
+ # reactor.
+ def testFailure(self):
+ def foo(x):
+ raise TestException(x)
+
+ lc = task.LoopingCall(foo, "bar")
+ return self.assertFailure(lc.start(0.1), TestException)
+
+
+ def testFailAndStop(self):
+ def foo(x):
+ lc.stop()
+ raise TestException(x)
+
+ lc = task.LoopingCall(foo, "bar")
+ return self.assertFailure(lc.start(0.1), TestException)
+
+
+ def testEveryIteration(self):
+ ran = []
+
+ def foo():
+ ran.append(None)
+ if len(ran) > 5:
+ lc.stop()
+
+ lc = task.LoopingCall(foo)
+ d = lc.start(0)
+ def stopped(ign):
+ self.assertEquals(len(ran), 6)
+ return d.addCallback(stopped)
+
+
+ def testStopAtOnceLater(self):
+ # Ensure that even when LoopingCall.stop() is called from a
+ # reactor callback, it still prevents any subsequent calls.
+ d = defer.Deferred()
+ def foo():
+ d.errback(failure.DefaultException(
+ "This task also should never get called."))
+ self._lc = task.LoopingCall(foo)
+ self._lc.start(1, now=False)
+ reactor.callLater(0, self._callback_for_testStopAtOnceLater, d)
+ return d
+
+
+ def _callback_for_testStopAtOnceLater(self, d):
+ self._lc.stop()
+ reactor.callLater(0, d.callback, "success")
+
+ def testWaitDeferred(self):
+ # Tests if the callable isn't scheduled again before the returned
+ # deferred has fired.
+ timings = [0.2, 0.8]
+ clock = task.Clock()
+
+ def foo():
+ d = defer.Deferred()
+ d.addCallback(lambda _: lc.stop())
+ clock.callLater(1, d.callback, None)
+ return d
+
+ lc = TestableLoopingCall(clock, foo)
+ d = lc.start(0.2)
+ clock.pump(timings)
+ self.failIf(clock.calls)
+
+ def testFailurePropagation(self):
+ # Tests if the failure of the errback of the deferred returned by the
+ # callable is propagated to the lc errback.
+ #
+ # To make sure this test does not hang trial when LoopingCall does not
+ # wait for the callable's deferred, it also checks there are no
+ # calls in the clock's callLater queue.
+ timings = [0.3]
+ clock = task.Clock()
+
+ def foo():
+ d = defer.Deferred()
+ clock.callLater(0.3, d.errback, TestException())
+ return d
+
+ lc = TestableLoopingCall(clock, foo)
+ d = lc.start(1)
+ self.assertFailure(d, TestException)
+
+ clock.pump(timings)
+ self.failIf(clock.calls)
+ return d
+
+
+ def test_deferredWithCount(self):
+ """
+ In the case that the function passed to L{LoopingCall.withCount}
+ returns a deferred, which does not fire before the next interval
+ elapses, the function should not be run again. And if a function call
+ is skipped in this fashion, the appropriate count should be
+ provided.
+ """
+ testClock = task.Clock()
+ d = defer.Deferred()
+ deferredCounts = []
+
+ def countTracker(possibleCount):
+ # Keep a list of call counts
+ deferredCounts.append(possibleCount)
+ # Return a deferred, but only on the first request
+ if len(deferredCounts) == 1:
+ return d
+ else:
+ return None
+
+ # Start a looping call for our countTracker function
+ # Set the increment to 0.2, and do not call the function on startup.
+ lc = task.LoopingCall.withCount(countTracker)
+ lc.clock = testClock
+ d = lc.start(0.2, now=False)
+
+ # Confirm that nothing has happened yet.
+ self.assertEquals(deferredCounts, [])
+
+ # Advance the clock by 0.2 and then 0.4;
+ testClock.pump([0.2, 0.4])
+ # We should now have exactly one count (of 1 call)
+ self.assertEquals(len(deferredCounts), 1)
+
+ # Fire the deferred, and advance the clock by another 0.2
+ d.callback(None)
+ testClock.pump([0.2])
+ # We should now have exactly 2 counts...
+ self.assertEquals(len(deferredCounts), 2)
+ # The first count should be 1 (one call)
+ # The second count should be 3 (calls were missed at about 0.6 and 0.8)
+ self.assertEquals(deferredCounts, [1, 3])
+
+
+
+class DeferLaterTests(unittest.TestCase):
+ """
+ Tests for L{task.deferLater}.
+ """
+ def test_callback(self):
+ """
+ The L{Deferred} returned by L{task.deferLater} is called back after
+ the specified delay with the result of the function passed in.
+ """
+ results = []
+ flag = object()
+ def callable(foo, bar):
+ results.append((foo, bar))
+ return flag
+
+ clock = task.Clock()
+ d = task.deferLater(clock, 3, callable, 'foo', bar='bar')
+ d.addCallback(self.assertIdentical, flag)
+ clock.advance(2)
+ self.assertEqual(results, [])
+ clock.advance(1)
+ self.assertEqual(results, [('foo', 'bar')])
+ return d
+
+
+ def test_errback(self):
+ """
+ The L{Deferred} returned by L{task.deferLater} is errbacked if the
+ supplied function raises an exception.
+ """
+ def callable():
+ raise TestException()
+
+ clock = task.Clock()
+ d = task.deferLater(clock, 1, callable)
+ clock.advance(1)
+ return self.assertFailure(d, TestException)
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_tcp.py b/vendor/Twisted-10.0.0/twisted/test/test_tcp.py
new file mode 100644
index 0000000000..1b7b4639ae
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_tcp.py
@@ -0,0 +1,1908 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorTCP}.
+"""
+
+import socket, random, errno
+
+from zope.interface import implements
+
+from twisted.trial import unittest
+
+from twisted.python.log import msg
+from twisted.internet import protocol, reactor, defer, interfaces
+from twisted.internet import error
+from twisted.internet.address import IPv4Address
+from twisted.internet.interfaces import IHalfCloseableProtocol, IPullProducer
+from twisted.protocols import policies
+from twisted.test.proto_helpers import AccumulatingProtocol
+
+
+def loopUntil(predicate, interval=0):
+ """
+ Poor excuse for an event notification helper. This polls a condition and
+ calls back a Deferred when it is seen to be true.
+
+ Do not use this function.
+ """
+ from twisted.internet import task
+ d = defer.Deferred()
+ def check():
+ res = predicate()
+ if res:
+ d.callback(res)
+ call = task.LoopingCall(check)
+ def stop(result):
+ call.stop()
+ return result
+ d.addCallback(stop)
+ d2 = call.start(interval)
+ d2.addErrback(d.errback)
+ return d
+
+
+class ClosingProtocol(protocol.Protocol):
+
+ def connectionMade(self):
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ reason.trap(error.ConnectionDone)
+
+class ClosingFactory(protocol.ServerFactory):
+ """Factory that closes port immediatley."""
+
+ def buildProtocol(self, conn):
+ self.port.stopListening()
+ return ClosingProtocol()
+
+
+class MyProtocolFactoryMixin(object):
+ """
+ Mixin for factories which create L{AccumulatingProtocol} instances.
+
+ @type protocolFactory: no-argument callable
+ @ivar protocolFactory: Factory for protocols - takes the place of the
+ typical C{protocol} attribute of factories (but that name is used by
+ this class for something else).
+
+ @type protocolConnectionMade: L{NoneType} or L{defer.Deferred}
+ @ivar protocolConnectionMade: When an instance of L{AccumulatingProtocol}
+ is connected, if this is not C{None}, the L{Deferred} will be called
+ back with the protocol instance and the attribute set to C{None}.
+
+ @type protocolConnectionLost: L{NoneType} or L{defer.Deferred}
+ @ivar protocolConnectionLost: When an instance of L{AccumulatingProtocol}
+ is created, this will be set as its C{closedDeferred} attribute and
+ then this attribute will be set to C{None} so the L{defer.Deferred} is
+ not used by more than one protocol.
+
+ @ivar protocol: The most recently created L{AccumulatingProtocol} instance
+ which was returned from C{buildProtocol}.
+
+ @type called: C{int}
+ @ivar called: A counter which is incremented each time C{buildProtocol}
+ is called.
+
+ @ivar peerAddresses: A C{list} of the addresses passed to C{buildProtocol}.
+ """
+ protocolFactory = AccumulatingProtocol
+
+ protocolConnectionMade = None
+ protocolConnectionLost = None
+ protocol = None
+ called = 0
+
+ def __init__(self):
+ self.peerAddresses = []
+
+
+ def buildProtocol(self, addr):
+ """
+ Create a L{AccumulatingProtocol} and set it up to be able to perform
+ callbacks.
+ """
+ self.peerAddresses.append(addr)
+ self.called += 1
+ p = self.protocolFactory()
+ p.factory = self
+ p.closedDeferred = self.protocolConnectionLost
+ self.protocolConnectionLost = None
+ self.protocol = p
+ return p
+
+
+
+class MyServerFactory(MyProtocolFactoryMixin, protocol.ServerFactory):
+ """
+ Server factory which creates L{AccumulatingProtocol} instances.
+ """
+
+
+
+class MyClientFactory(MyProtocolFactoryMixin, protocol.ClientFactory):
+ """
+ Client factory which creates L{AccumulatingProtocol} instances.
+ """
+ failed = 0
+ stopped = 0
+
+ def __init__(self):
+ MyProtocolFactoryMixin.__init__(self)
+ self.deferred = defer.Deferred()
+ self.failDeferred = defer.Deferred()
+
+ def clientConnectionFailed(self, connector, reason):
+ self.failed = 1
+ self.reason = reason
+ self.failDeferred.callback(None)
+
+ def clientConnectionLost(self, connector, reason):
+ self.lostReason = reason
+ self.deferred.callback(None)
+
+ def stopFactory(self):
+ self.stopped = 1
+
+
+
+class ListeningTestCase(unittest.TestCase):
+
+ def test_listen(self):
+ """
+ L{IReactorTCP.listenTCP} returns an object which provides
+ L{IListeningPort}.
+ """
+ f = MyServerFactory()
+ p1 = reactor.listenTCP(0, f, interface="127.0.0.1")
+ self.addCleanup(p1.stopListening)
+ self.failUnless(interfaces.IListeningPort.providedBy(p1))
+
+
+ def testStopListening(self):
+ """
+ The L{IListeningPort} returned by L{IReactorTCP.listenTCP} can be
+ stopped with its C{stopListening} method. After the L{Deferred} it
+ (optionally) returns has been called back, the port number can be bound
+ to a new server.
+ """
+ f = MyServerFactory()
+ port = reactor.listenTCP(0, f, interface="127.0.0.1")
+ n = port.getHost().port
+
+ def cbStopListening(ignored):
+ # Make sure we can rebind the port right away
+ port = reactor.listenTCP(n, f, interface="127.0.0.1")
+ return port.stopListening()
+
+ d = defer.maybeDeferred(port.stopListening)
+ d.addCallback(cbStopListening)
+ return d
+
+
+ def testNumberedInterface(self):
+ f = MyServerFactory()
+ # listen only on the loopback interface
+ p1 = reactor.listenTCP(0, f, interface='127.0.0.1')
+ return p1.stopListening()
+
+ def testPortRepr(self):
+ f = MyServerFactory()
+ p = reactor.listenTCP(0, f)
+ portNo = str(p.getHost().port)
+ self.failIf(repr(p).find(portNo) == -1)
+ def stoppedListening(ign):
+ self.failIf(repr(p).find(portNo) != -1)
+ d = defer.maybeDeferred(p.stopListening)
+ return d.addCallback(stoppedListening)
+
+
+ def test_serverRepr(self):
+ """
+ Check that the repr string of the server transport get the good port
+ number if the server listens on 0.
+ """
+ server = MyServerFactory()
+ serverConnMade = server.protocolConnectionMade = defer.Deferred()
+ port = reactor.listenTCP(0, server)
+ self.addCleanup(port.stopListening)
+
+ client = MyClientFactory()
+ clientConnMade = client.protocolConnectionMade = defer.Deferred()
+ connector = reactor.connectTCP("127.0.0.1",
+ port.getHost().port, client)
+ self.addCleanup(connector.disconnect)
+ def check((serverProto, clientProto)):
+ portNumber = port.getHost().port
+ self.assertEquals(
+ repr(serverProto.transport),
+ "<AccumulatingProtocol #0 on %s>" % (portNumber,))
+ serverProto.transport.loseConnection()
+ clientProto.transport.loseConnection()
+ return defer.gatherResults([serverConnMade, clientConnMade]
+ ).addCallback(check)
+
+
+ def test_restartListening(self):
+ """
+ Stop and then try to restart a L{tcp.Port}: after a restart, the
+ server should be able to handle client connections.
+ """
+ serverFactory = MyServerFactory()
+ port = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
+ self.addCleanup(port.stopListening)
+
+ def cbStopListening(ignored):
+ port.startListening()
+
+ client = MyClientFactory()
+ serverFactory.protocolConnectionMade = defer.Deferred()
+ client.protocolConnectionMade = defer.Deferred()
+ connector = reactor.connectTCP("127.0.0.1",
+ port.getHost().port, client)
+ self.addCleanup(connector.disconnect)
+ return defer.gatherResults([serverFactory.protocolConnectionMade,
+ client.protocolConnectionMade]
+ ).addCallback(close)
+
+ def close((serverProto, clientProto)):
+ clientProto.transport.loseConnection()
+ serverProto.transport.loseConnection()
+
+ d = defer.maybeDeferred(port.stopListening)
+ d.addCallback(cbStopListening)
+ return d
+
+
+ def test_exceptInStop(self):
+ """
+ If the server factory raises an exception in C{stopFactory}, the
+ deferred returned by L{tcp.Port.stopListening} should fail with the
+ corresponding error.
+ """
+ serverFactory = MyServerFactory()
+ def raiseException():
+ raise RuntimeError("An error")
+ serverFactory.stopFactory = raiseException
+ port = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
+
+ return self.assertFailure(port.stopListening(), RuntimeError)
+
+
+ def test_restartAfterExcept(self):
+ """
+ Even if the server factory raise an exception in C{stopFactory}, the
+ corresponding C{tcp.Port} instance should be in a sane state and can
+ be restarted.
+ """
+ serverFactory = MyServerFactory()
+ def raiseException():
+ raise RuntimeError("An error")
+ serverFactory.stopFactory = raiseException
+ port = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
+ self.addCleanup(port.stopListening)
+
+ def cbStopListening(ignored):
+ del serverFactory.stopFactory
+ port.startListening()
+
+ client = MyClientFactory()
+ serverFactory.protocolConnectionMade = defer.Deferred()
+ client.protocolConnectionMade = defer.Deferred()
+ connector = reactor.connectTCP("127.0.0.1",
+ port.getHost().port, client)
+ self.addCleanup(connector.disconnect)
+ return defer.gatherResults([serverFactory.protocolConnectionMade,
+ client.protocolConnectionMade]
+ ).addCallback(close)
+
+ def close((serverProto, clientProto)):
+ clientProto.transport.loseConnection()
+ serverProto.transport.loseConnection()
+
+ return self.assertFailure(port.stopListening(), RuntimeError
+ ).addCallback(cbStopListening)
+
+
+ def test_directConnectionLostCall(self):
+ """
+ If C{connectionLost} is called directly on a port object, it succeeds
+ (and doesn't expect the presence of a C{deferred} attribute).
+
+ C{connectionLost} is called by L{reactor.disconnectAll} at shutdown.
+ """
+ serverFactory = MyServerFactory()
+ port = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
+ portNumber = port.getHost().port
+ port.connectionLost(None)
+
+ client = MyClientFactory()
+ serverFactory.protocolConnectionMade = defer.Deferred()
+ client.protocolConnectionMade = defer.Deferred()
+ connector = reactor.connectTCP("127.0.0.1", portNumber, client)
+ def check(ign):
+ client.reason.trap(error.ConnectionRefusedError)
+ return client.failDeferred.addCallback(check)
+
+
+ def test_exceptInConnectionLostCall(self):
+ """
+ If C{connectionLost} is called directory on a port object and that the
+ server factory raises an exception in C{stopFactory}, the exception is
+ passed through to the caller.
+
+ C{connectionLost} is called by L{reactor.disconnectAll} at shutdown.
+ """
+ serverFactory = MyServerFactory()
+ def raiseException():
+ raise RuntimeError("An error")
+ serverFactory.stopFactory = raiseException
+ port = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
+ self.assertRaises(RuntimeError, port.connectionLost, None)
+
+
+
+def callWithSpew(f):
+ from twisted.python.util import spewerWithLinenums as spewer
+ import sys
+ sys.settrace(spewer)
+ try:
+ f()
+ finally:
+ sys.settrace(None)
+
+class LoopbackTestCase(unittest.TestCase):
+ """
+ Test loopback connections.
+ """
+ def test_closePortInProtocolFactory(self):
+ """
+ A port created with L{IReactorTCP.listenTCP} can be connected to with
+ L{IReactorTCP.connectTCP}.
+ """
+ f = ClosingFactory()
+ port = reactor.listenTCP(0, f, interface="127.0.0.1")
+ self.addCleanup(port.stopListening)
+ portNumber = port.getHost().port
+ f.port = port
+ clientF = MyClientFactory()
+ reactor.connectTCP("127.0.0.1", portNumber, clientF)
+ def check(x):
+ self.assertTrue(clientF.protocol.made)
+ self.assertTrue(port.disconnected)
+ clientF.lostReason.trap(error.ConnectionDone)
+ return clientF.deferred.addCallback(check)
+
+ def _trapCnxDone(self, obj):
+ getattr(obj, 'trap', lambda x: None)(error.ConnectionDone)
+
+
+ def _connectedClientAndServerTest(self, callback):
+ """
+ Invoke the given callback with a client protocol and a server protocol
+ which have been connected to each other.
+ """
+ serverFactory = MyServerFactory()
+ serverConnMade = defer.Deferred()
+ serverFactory.protocolConnectionMade = serverConnMade
+ port = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
+ self.addCleanup(port.stopListening)
+
+ portNumber = port.getHost().port
+ clientF = MyClientFactory()
+ clientConnMade = defer.Deferred()
+ clientF.protocolConnectionMade = clientConnMade
+ reactor.connectTCP("127.0.0.1", portNumber, clientF)
+
+ connsMade = defer.gatherResults([serverConnMade, clientConnMade])
+ def connected((serverProtocol, clientProtocol)):
+ callback(serverProtocol, clientProtocol)
+ serverProtocol.transport.loseConnection()
+ clientProtocol.transport.loseConnection()
+ connsMade.addCallback(connected)
+ return connsMade
+
+
+ def test_tcpNoDelay(self):
+ """
+ The transport of a protocol connected with L{IReactorTCP.connectTCP} or
+ L{IReactor.TCP.listenTCP} can have its I{TCP_NODELAY} state inspected
+ and manipulated with L{ITCPTransport.getTcpNoDelay} and
+ L{ITCPTransport.setTcpNoDelay}.
+ """
+ def check(serverProtocol, clientProtocol):
+ for p in [serverProtocol, clientProtocol]:
+ transport = p.transport
+ self.assertEquals(transport.getTcpNoDelay(), 0)
+ transport.setTcpNoDelay(1)
+ self.assertEquals(transport.getTcpNoDelay(), 1)
+ transport.setTcpNoDelay(0)
+ self.assertEquals(transport.getTcpNoDelay(), 0)
+ return self._connectedClientAndServerTest(check)
+
+
+ def test_tcpKeepAlive(self):
+ """
+ The transport of a protocol connected with L{IReactorTCP.connectTCP} or
+ L{IReactor.TCP.listenTCP} can have its I{SO_KEEPALIVE} state inspected
+ and manipulated with L{ITCPTransport.getTcpKeepAlive} and
+ L{ITCPTransport.setTcpKeepAlive}.
+ """
+ def check(serverProtocol, clientProtocol):
+ for p in [serverProtocol, clientProtocol]:
+ transport = p.transport
+ self.assertEquals(transport.getTcpKeepAlive(), 0)
+ transport.setTcpKeepAlive(1)
+ self.assertEquals(transport.getTcpKeepAlive(), 1)
+ transport.setTcpKeepAlive(0)
+ self.assertEquals(transport.getTcpKeepAlive(), 0)
+ return self._connectedClientAndServerTest(check)
+
+
+ def testFailing(self):
+ clientF = MyClientFactory()
+ # XXX we assume no one is listening on TCP port 69
+ reactor.connectTCP("127.0.0.1", 69, clientF, timeout=5)
+ def check(ignored):
+ clientF.reason.trap(error.ConnectionRefusedError)
+ return clientF.failDeferred.addCallback(check)
+
+
+ def test_connectionRefusedErrorNumber(self):
+ """
+ Assert that the error number of the ConnectionRefusedError is
+ ECONNREFUSED, and not some other socket related error.
+ """
+
+ # Bind a number of ports in the operating system. We will attempt
+ # to connect to these in turn immediately after closing them, in the
+ # hopes that no one else has bound them in the mean time. Any
+ # connection which succeeds is ignored and causes us to move on to
+ # the next port. As soon as a connection attempt fails, we move on
+ # to making an assertion about how it failed. If they all succeed,
+ # the test will fail.
+
+ # It would be nice to have a simpler, reliable way to cause a
+ # connection failure from the platform.
+ #
+ # On Linux (2.6.15), connecting to port 0 always fails. FreeBSD
+ # (5.4) rejects the connection attempt with EADDRNOTAVAIL.
+ #
+ # On FreeBSD (5.4), listening on a port and then repeatedly
+ # connecting to it without ever accepting any connections eventually
+ # leads to an ECONNREFUSED. On Linux (2.6.15), a seemingly
+ # unbounded number of connections succeed.
+
+ serverSockets = []
+ for i in xrange(10):
+ serverSocket = socket.socket()
+ serverSocket.bind(('127.0.0.1', 0))
+ serverSocket.listen(1)
+ serverSockets.append(serverSocket)
+ random.shuffle(serverSockets)
+
+ clientCreator = protocol.ClientCreator(reactor, protocol.Protocol)
+
+ def tryConnectFailure():
+ def connected(proto):
+ """
+ Darn. Kill it and try again, if there are any tries left.
+ """
+ proto.transport.loseConnection()
+ if serverSockets:
+ return tryConnectFailure()
+ self.fail("Could not fail to connect - could not test errno for that case.")
+
+ serverSocket = serverSockets.pop()
+ serverHost, serverPort = serverSocket.getsockname()
+ serverSocket.close()
+
+ connectDeferred = clientCreator.connectTCP(serverHost, serverPort)
+ connectDeferred.addCallback(connected)
+ return connectDeferred
+
+ refusedDeferred = tryConnectFailure()
+ self.assertFailure(refusedDeferred, error.ConnectionRefusedError)
+ def connRefused(exc):
+ self.assertEqual(exc.osError, errno.ECONNREFUSED)
+ refusedDeferred.addCallback(connRefused)
+ def cleanup(passthrough):
+ while serverSockets:
+ serverSockets.pop().close()
+ return passthrough
+ refusedDeferred.addBoth(cleanup)
+ return refusedDeferred
+
+
+ def test_connectByServiceFail(self):
+ """
+ Connecting to a named service which does not exist raises
+ L{error.ServiceNameUnknownError}.
+ """
+ self.assertRaises(
+ error.ServiceNameUnknownError,
+ reactor.connectTCP,
+ "127.0.0.1", "thisbetternotexist", MyClientFactory())
+
+
+ def test_connectByService(self):
+ """
+ L{IReactorTCP.connectTCP} accepts the name of a service instead of a
+ port number and connects to the port number associated with that
+ service, as defined by L{socket.getservbyname}.
+ """
+ serverFactory = MyServerFactory()
+ serverConnMade = defer.Deferred()
+ serverFactory.protocolConnectionMade = serverConnMade
+ port = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
+ self.addCleanup(port.stopListening)
+ portNumber = port.getHost().port
+ clientFactory = MyClientFactory()
+ clientConnMade = defer.Deferred()
+ clientFactory.protocolConnectionMade = clientConnMade
+
+ def fakeGetServicePortByName(serviceName, protocolName):
+ if serviceName == 'http' and protocolName == 'tcp':
+ return portNumber
+ return 10
+ self.patch(socket, 'getservbyname', fakeGetServicePortByName)
+
+ c = reactor.connectTCP('127.0.0.1', 'http', clientFactory)
+
+ connMade = defer.gatherResults([serverConnMade, clientConnMade])
+ def connected((serverProtocol, clientProtocol)):
+ self.assertTrue(
+ serverFactory.called,
+ "Server factory was not called upon to build a protocol.")
+ serverProtocol.transport.loseConnection()
+ clientProtocol.transport.loseConnection()
+ connMade.addCallback(connected)
+ return connMade
+
+
+class StartStopFactory(protocol.Factory):
+
+ started = 0
+ stopped = 0
+
+ def startFactory(self):
+ if self.started or self.stopped:
+ raise RuntimeError
+ self.started = 1
+
+ def stopFactory(self):
+ if not self.started or self.stopped:
+ raise RuntimeError
+ self.stopped = 1
+
+
+class ClientStartStopFactory(MyClientFactory):
+
+ started = 0
+ stopped = 0
+
+ def startFactory(self):
+ if self.started or self.stopped:
+ raise RuntimeError
+ self.started = 1
+
+ def stopFactory(self):
+ if not self.started or self.stopped:
+ raise RuntimeError
+ self.stopped = 1
+
+
+class FactoryTestCase(unittest.TestCase):
+ """Tests for factories."""
+
+ def test_serverStartStop(self):
+ """
+ The factory passed to L{IReactorTCP.listenTCP} should be started only
+ when it transitions from being used on no ports to being used on one
+ port and should be stopped only when it transitions from being used on
+ one port to being used on no ports.
+ """
+ # Note - this test doesn't need to use listenTCP. It is exercising
+ # logic implemented in Factory.doStart and Factory.doStop, so it could
+ # just call that directly. Some other test can make sure that
+ # listenTCP and stopListening correctly call doStart and
+ # doStop. -exarkun
+
+ f = StartStopFactory()
+
+ # listen on port
+ p1 = reactor.listenTCP(0, f, interface='127.0.0.1')
+ self.addCleanup(p1.stopListening)
+
+ self.assertEqual((f.started, f.stopped), (1, 0))
+
+ # listen on two more ports
+ p2 = reactor.listenTCP(0, f, interface='127.0.0.1')
+ p3 = reactor.listenTCP(0, f, interface='127.0.0.1')
+
+ self.assertEqual((f.started, f.stopped), (1, 0))
+
+ # close two ports
+ d1 = defer.maybeDeferred(p1.stopListening)
+ d2 = defer.maybeDeferred(p2.stopListening)
+ closedDeferred = defer.gatherResults([d1, d2])
+ def cbClosed(ignored):
+ self.assertEqual((f.started, f.stopped), (1, 0))
+ # Close the last port
+ return p3.stopListening()
+ closedDeferred.addCallback(cbClosed)
+
+ def cbClosedAll(ignored):
+ self.assertEquals((f.started, f.stopped), (1, 1))
+ closedDeferred.addCallback(cbClosedAll)
+ return closedDeferred
+
+
+ def test_clientStartStop(self):
+ """
+ The factory passed to L{IReactorTCP.connectTCP} should be started when
+ the connection attempt starts and stopped when it is over.
+ """
+ f = ClosingFactory()
+ p = reactor.listenTCP(0, f, interface="127.0.0.1")
+ self.addCleanup(p.stopListening)
+ portNumber = p.getHost().port
+ f.port = p
+
+ factory = ClientStartStopFactory()
+ reactor.connectTCP("127.0.0.1", portNumber, factory)
+ self.assertTrue(factory.started)
+ return loopUntil(lambda: factory.stopped)
+
+
+
+class ConnectorTestCase(unittest.TestCase):
+
+ def test_connectorIdentity(self):
+ """
+ L{IReactorTCP.connectTCP} returns an object which provides
+ L{IConnector}. The destination of the connector is the address which
+ was passed to C{connectTCP}. The same connector object is passed to
+ the factory's C{startedConnecting} method as to the factory's
+ C{clientConnectionLost} method.
+ """
+ serverFactory = ClosingFactory()
+ tcpPort = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
+ self.addCleanup(tcpPort.stopListening)
+ portNumber = tcpPort.getHost().port
+ serverFactory.port = tcpPort
+
+ seenConnectors = []
+ seenFailures = []
+
+ clientFactory = ClientStartStopFactory()
+ clientFactory.clientConnectionLost = (
+ lambda connector, reason: (seenConnectors.append(connector),
+ seenFailures.append(reason)))
+ clientFactory.startedConnecting = seenConnectors.append
+
+ connector = reactor.connectTCP("127.0.0.1", portNumber, clientFactory)
+ self.assertTrue(interfaces.IConnector.providedBy(connector))
+ dest = connector.getDestination()
+ self.assertEquals(dest.type, "TCP")
+ self.assertEquals(dest.host, "127.0.0.1")
+ self.assertEquals(dest.port, portNumber)
+
+ d = loopUntil(lambda: clientFactory.stopped)
+ def clientFactoryStopped(ignored):
+ seenFailures[0].trap(error.ConnectionDone)
+ self.assertEqual(seenConnectors, [connector, connector])
+ d.addCallback(clientFactoryStopped)
+ return d
+
+
+ def test_userFail(self):
+ """
+ Calling L{IConnector.stopConnecting} in C{Factory.startedConnecting}
+ results in C{Factory.clientConnectionFailed} being called with
+ L{error.UserError} as the reason.
+ """
+ serverFactory = MyServerFactory()
+ tcpPort = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
+ self.addCleanup(tcpPort.stopListening)
+ portNumber = tcpPort.getHost().port
+
+ def startedConnecting(connector):
+ connector.stopConnecting()
+
+ clientFactory = ClientStartStopFactory()
+ clientFactory.startedConnecting = startedConnecting
+ reactor.connectTCP("127.0.0.1", portNumber, clientFactory)
+
+ d = loopUntil(lambda: clientFactory.stopped)
+ def check(ignored):
+ self.assertEquals(clientFactory.failed, 1)
+ clientFactory.reason.trap(error.UserError)
+ return d.addCallback(check)
+
+
+ def test_reconnect(self):
+ """
+ Calling L{IConnector.connect} in C{Factory.clientConnectionLost} causes
+ a new connection attempt to be made.
+ """
+ serverFactory = ClosingFactory()
+ tcpPort = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
+ self.addCleanup(tcpPort.stopListening)
+ portNumber = tcpPort.getHost().port
+ serverFactory.port = tcpPort
+
+ clientFactory = MyClientFactory()
+
+ def clientConnectionLost(connector, reason):
+ connector.connect()
+ clientFactory.clientConnectionLost = clientConnectionLost
+ reactor.connectTCP("127.0.0.1", portNumber, clientFactory)
+
+ d = loopUntil(lambda: clientFactory.failed)
+ def reconnectFailed(ignored):
+ p = clientFactory.protocol
+ self.assertEqual((p.made, p.closed), (1, 1))
+ clientFactory.reason.trap(error.ConnectionRefusedError)
+ self.assertEqual(clientFactory.stopped, 1)
+ return d.addCallback(reconnectFailed)
+
+
+
+class CannotBindTestCase(unittest.TestCase):
+ """
+ Tests for correct behavior when a reactor cannot bind to the required TCP
+ port.
+ """
+
+ def test_cannotBind(self):
+ """
+ L{IReactorTCP.listenTCP} raises L{error.CannotListenError} if the
+ address to listen on is already in use.
+ """
+ f = MyServerFactory()
+
+ p1 = reactor.listenTCP(0, f, interface='127.0.0.1')
+ self.addCleanup(p1.stopListening)
+ n = p1.getHost().port
+ dest = p1.getHost()
+ self.assertEquals(dest.type, "TCP")
+ self.assertEquals(dest.host, "127.0.0.1")
+ self.assertEquals(dest.port, n)
+
+ # make sure new listen raises error
+ self.assertRaises(error.CannotListenError,
+ reactor.listenTCP, n, f, interface='127.0.0.1')
+
+
+
+ def _fireWhenDoneFunc(self, d, f):
+ """Returns closure that when called calls f and then callbacks d.
+ """
+ from twisted.python import util as tputil
+ def newf(*args, **kw):
+ rtn = f(*args, **kw)
+ d.callback('')
+ return rtn
+ return tputil.mergeFunctionMetadata(f, newf)
+
+
+ def test_clientBind(self):
+ """
+ L{IReactorTCP.connectTCP} calls C{Factory.clientConnectionFailed} with
+ L{error.ConnectBindError} if the bind address specified is already in
+ use.
+ """
+ theDeferred = defer.Deferred()
+ sf = MyServerFactory()
+ sf.startFactory = self._fireWhenDoneFunc(theDeferred, sf.startFactory)
+ p = reactor.listenTCP(0, sf, interface="127.0.0.1")
+ self.addCleanup(p.stopListening)
+
+ def _connect1(results):
+ d = defer.Deferred()
+ cf1 = MyClientFactory()
+ cf1.buildProtocol = self._fireWhenDoneFunc(d, cf1.buildProtocol)
+ reactor.connectTCP("127.0.0.1", p.getHost().port, cf1,
+ bindAddress=("127.0.0.1", 0))
+ d.addCallback(_conmade, cf1)
+ return d
+
+ def _conmade(results, cf1):
+ d = defer.Deferred()
+ cf1.protocol.connectionMade = self._fireWhenDoneFunc(
+ d, cf1.protocol.connectionMade)
+ d.addCallback(_check1connect2, cf1)
+ return d
+
+ def _check1connect2(results, cf1):
+ self.assertEquals(cf1.protocol.made, 1)
+
+ d1 = defer.Deferred()
+ d2 = defer.Deferred()
+ port = cf1.protocol.transport.getHost().port
+ cf2 = MyClientFactory()
+ cf2.clientConnectionFailed = self._fireWhenDoneFunc(
+ d1, cf2.clientConnectionFailed)
+ cf2.stopFactory = self._fireWhenDoneFunc(d2, cf2.stopFactory)
+ reactor.connectTCP("127.0.0.1", p.getHost().port, cf2,
+ bindAddress=("127.0.0.1", port))
+ d1.addCallback(_check2failed, cf1, cf2)
+ d2.addCallback(_check2stopped, cf1, cf2)
+ dl = defer.DeferredList([d1, d2])
+ dl.addCallback(_stop, cf1, cf2)
+ return dl
+
+ def _check2failed(results, cf1, cf2):
+ self.assertEquals(cf2.failed, 1)
+ cf2.reason.trap(error.ConnectBindError)
+ self.assertTrue(cf2.reason.check(error.ConnectBindError))
+ return results
+
+ def _check2stopped(results, cf1, cf2):
+ self.assertEquals(cf2.stopped, 1)
+ return results
+
+ def _stop(results, cf1, cf2):
+ d = defer.Deferred()
+ d.addCallback(_check1cleanup, cf1)
+ cf1.stopFactory = self._fireWhenDoneFunc(d, cf1.stopFactory)
+ cf1.protocol.transport.loseConnection()
+ return d
+
+ def _check1cleanup(results, cf1):
+ self.assertEquals(cf1.stopped, 1)
+
+ theDeferred.addCallback(_connect1)
+ return theDeferred
+
+
+
+class MyOtherClientFactory(protocol.ClientFactory):
+ def buildProtocol(self, address):
+ self.address = address
+ self.protocol = AccumulatingProtocol()
+ return self.protocol
+
+
+
+class LocalRemoteAddressTestCase(unittest.TestCase):
+ """
+ Tests for correct getHost/getPeer values and that the correct address is
+ passed to buildProtocol.
+ """
+ def test_hostAddress(self):
+ """
+ L{IListeningPort.getHost} returns the same address as a client
+ connection's L{ITCPTransport.getPeer}.
+ """
+ serverFactory = MyServerFactory()
+ serverFactory.protocolConnectionLost = defer.Deferred()
+ serverConnectionLost = serverFactory.protocolConnectionLost
+ port = reactor.listenTCP(0, serverFactory, interface='127.0.0.1')
+ self.addCleanup(port.stopListening)
+ n = port.getHost().port
+
+ clientFactory = MyClientFactory()
+ onConnection = clientFactory.protocolConnectionMade = defer.Deferred()
+ connector = reactor.connectTCP('127.0.0.1', n, clientFactory)
+
+ def check(ignored):
+ self.assertEquals([port.getHost()], clientFactory.peerAddresses)
+ self.assertEquals(
+ port.getHost(), clientFactory.protocol.transport.getPeer())
+ onConnection.addCallback(check)
+
+ def cleanup(ignored):
+ # Clean up the client explicitly here so that tear down of
+ # the server side of the connection begins, then wait for
+ # the server side to actually disconnect.
+ connector.disconnect()
+ return serverConnectionLost
+ onConnection.addCallback(cleanup)
+
+ return onConnection
+
+
+
+class WriterProtocol(protocol.Protocol):
+ def connectionMade(self):
+ # use everything ITransport claims to provide. If something here
+ # fails, the exception will be written to the log, but it will not
+ # directly flunk the test. The test will fail when maximum number of
+ # iterations have passed and the writer's factory.done has not yet
+ # been set.
+ self.transport.write("Hello Cleveland!\n")
+ seq = ["Goodbye", " cruel", " world", "\n"]
+ self.transport.writeSequence(seq)
+ peer = self.transport.getPeer()
+ if peer.type != "TCP":
+ print "getPeer returned non-TCP socket:", peer
+ self.factory.problem = 1
+ us = self.transport.getHost()
+ if us.type != "TCP":
+ print "getHost returned non-TCP socket:", us
+ self.factory.problem = 1
+ self.factory.done = 1
+
+ self.transport.loseConnection()
+
+class ReaderProtocol(protocol.Protocol):
+ def dataReceived(self, data):
+ self.factory.data += data
+ def connectionLost(self, reason):
+ self.factory.done = 1
+
+class WriterClientFactory(protocol.ClientFactory):
+ def __init__(self):
+ self.done = 0
+ self.data = ""
+ def buildProtocol(self, addr):
+ p = ReaderProtocol()
+ p.factory = self
+ self.protocol = p
+ return p
+
+class WriteDataTestCase(unittest.TestCase):
+ """
+ Test that connected TCP sockets can actually write data. Try to exercise
+ the entire ITransport interface.
+ """
+
+ def test_writer(self):
+ """
+ L{ITCPTransport.write} and L{ITCPTransport.writeSequence} send bytes to
+ the other end of the connection.
+ """
+ f = protocol.Factory()
+ f.protocol = WriterProtocol
+ f.done = 0
+ f.problem = 0
+ wrappedF = WiredFactory(f)
+ p = reactor.listenTCP(0, wrappedF, interface="127.0.0.1")
+ self.addCleanup(p.stopListening)
+ n = p.getHost().port
+ clientF = WriterClientFactory()
+ wrappedClientF = WiredFactory(clientF)
+ reactor.connectTCP("127.0.0.1", n, wrappedClientF)
+
+ def check(ignored):
+ self.failUnless(f.done, "writer didn't finish, it probably died")
+ self.failUnless(f.problem == 0, "writer indicated an error")
+ self.failUnless(clientF.done,
+ "client didn't see connection dropped")
+ expected = "".join(["Hello Cleveland!\n",
+ "Goodbye", " cruel", " world", "\n"])
+ self.failUnless(clientF.data == expected,
+ "client didn't receive all the data it expected")
+ d = defer.gatherResults([wrappedF.onDisconnect,
+ wrappedClientF.onDisconnect])
+ return d.addCallback(check)
+
+
+ def test_writeAfterShutdownWithoutReading(self):
+ """
+ A TCP transport which is written to after the connection has been shut
+ down should notify its protocol that the connection has been lost, even
+ if the TCP transport is not actively being monitored for read events
+ (ie, pauseProducing was called on it).
+ """
+ # This is an unpleasant thing. Generally tests shouldn't skip or
+ # run based on the name of the reactor being used (most tests
+ # shouldn't care _at all_ what reactor is being used, in fact). The
+ # Gtk reactor cannot pass this test, though, because it fails to
+ # implement IReactorTCP entirely correctly. Gtk is quite old at
+ # this point, so it's more likely that gtkreactor will be deprecated
+ # and removed rather than fixed to handle this case correctly.
+ # Since this is a pre-existing (and very long-standing) issue with
+ # the Gtk reactor, there's no reason for it to prevent this test
+ # being added to exercise the other reactors, for which the behavior
+ # was also untested but at least works correctly (now). See #2833
+ # for information on the status of gtkreactor.
+ if reactor.__class__.__name__ == 'IOCPReactor':
+ raise unittest.SkipTest(
+ "iocpreactor does not, in fact, stop reading immediately after "
+ "pauseProducing is called. This results in a bonus disconnection "
+ "notification. Under some circumstances, it might be possible to "
+ "not receive this notifications (specifically, pauseProducing, "
+ "deliver some data, proceed with this test).")
+ if reactor.__class__.__name__ == 'GtkReactor':
+ raise unittest.SkipTest(
+ "gtkreactor does not implement unclean disconnection "
+ "notification correctly. This might more properly be "
+ "a todo, but due to technical limitations it cannot be.")
+
+ # Called back after the protocol for the client side of the connection
+ # has paused its transport, preventing it from reading, therefore
+ # preventing it from noticing the disconnection before the rest of the
+ # actions which are necessary to trigger the case this test is for have
+ # been taken.
+ clientPaused = defer.Deferred()
+
+ # Called back when the protocol for the server side of the connection
+ # has received connection lost notification.
+ serverLost = defer.Deferred()
+
+ class Disconnecter(protocol.Protocol):
+ """
+ Protocol for the server side of the connection which disconnects
+ itself in a callback on clientPaused and publishes notification
+ when its connection is actually lost.
+ """
+ def connectionMade(self):
+ """
+ Set up a callback on clientPaused to lose the connection.
+ """
+ msg('Disconnector.connectionMade')
+ def disconnect(ignored):
+ msg('Disconnector.connectionMade disconnect')
+ self.transport.loseConnection()
+ msg('loseConnection called')
+ clientPaused.addCallback(disconnect)
+
+ def connectionLost(self, reason):
+ """
+ Notify observers that the server side of the connection has
+ ended.
+ """
+ msg('Disconnecter.connectionLost')
+ serverLost.callback(None)
+ msg('serverLost called back')
+
+ # Create the server port to which a connection will be made.
+ server = protocol.ServerFactory()
+ server.protocol = Disconnecter
+ port = reactor.listenTCP(0, server, interface='127.0.0.1')
+ self.addCleanup(port.stopListening)
+ addr = port.getHost()
+
+ class Infinite(object):
+ """
+ A producer which will write to its consumer as long as
+ resumeProducing is called.
+
+ @ivar consumer: The L{IConsumer} which will be written to.
+ """
+ implements(IPullProducer)
+
+ def __init__(self, consumer):
+ self.consumer = consumer
+
+ def resumeProducing(self):
+ msg('Infinite.resumeProducing')
+ self.consumer.write('x')
+ msg('Infinite.resumeProducing wrote to consumer')
+
+ def stopProducing(self):
+ msg('Infinite.stopProducing')
+
+
+ class UnreadingWriter(protocol.Protocol):
+ """
+ Trivial protocol which pauses its transport immediately and then
+ writes some bytes to it.
+ """
+ def connectionMade(self):
+ msg('UnreadingWriter.connectionMade')
+ self.transport.pauseProducing()
+ clientPaused.callback(None)
+ msg('clientPaused called back')
+ def write(ignored):
+ msg('UnreadingWriter.connectionMade write')
+ # This needs to be enough bytes to spill over into the
+ # userspace Twisted send buffer - if it all fits into
+ # the kernel, Twisted won't even poll for OUT events,
+ # which means it won't poll for any events at all, so
+ # the disconnection is never noticed. This is due to
+ # #1662. When #1662 is fixed, this test will likely
+ # need to be adjusted, otherwise connection lost
+ # notification will happen too soon and the test will
+ # probably begin to fail with ConnectionDone instead of
+ # ConnectionLost (in any case, it will no longer be
+ # entirely correct).
+ producer = Infinite(self.transport)
+ msg('UnreadingWriter.connectionMade write created producer')
+ self.transport.registerProducer(producer, False)
+ msg('UnreadingWriter.connectionMade write registered producer')
+ serverLost.addCallback(write)
+
+ # Create the client and initiate the connection
+ client = MyClientFactory()
+ client.protocolFactory = UnreadingWriter
+ clientConnectionLost = client.deferred
+ def cbClientLost(ignored):
+ msg('cbClientLost')
+ return client.lostReason
+ clientConnectionLost.addCallback(cbClientLost)
+ msg('Connecting to %s:%s' % (addr.host, addr.port))
+ connector = reactor.connectTCP(addr.host, addr.port, client)
+
+ # By the end of the test, the client should have received notification
+ # of unclean disconnection.
+ msg('Returning Deferred')
+ return self.assertFailure(clientConnectionLost, error.ConnectionLost)
+
+
+
+class ConnectionLosingProtocol(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.write("1")
+ self.transport.loseConnection()
+ self.master._connectionMade()
+ self.master.ports.append(self.transport)
+
+
+
+class NoopProtocol(protocol.Protocol):
+ def connectionMade(self):
+ self.d = defer.Deferred()
+ self.master.serverConns.append(self.d)
+
+ def connectionLost(self, reason):
+ self.d.callback(True)
+
+
+
+class ConnectionLostNotifyingProtocol(protocol.Protocol):
+ """
+ Protocol which fires a Deferred which was previously passed to
+ its initializer when the connection is lost.
+
+ @ivar onConnectionLost: The L{Deferred} which will be fired in
+ C{connectionLost}.
+
+ @ivar lostConnectionReason: C{None} until the connection is lost, then a
+ reference to the reason passed to C{connectionLost}.
+ """
+ def __init__(self, onConnectionLost):
+ self.lostConnectionReason = None
+ self.onConnectionLost = onConnectionLost
+
+
+ def connectionLost(self, reason):
+ self.lostConnectionReason = reason
+ self.onConnectionLost.callback(self)
+
+
+
+class HandleSavingProtocol(ConnectionLostNotifyingProtocol):
+ """
+ Protocol which grabs the platform-specific socket handle and
+ saves it as an attribute on itself when the connection is
+ established.
+ """
+ def makeConnection(self, transport):
+ """
+ Save the platform-specific socket handle for future
+ introspection.
+ """
+ self.handle = transport.getHandle()
+ return protocol.Protocol.makeConnection(self, transport)
+
+
+
+class ProperlyCloseFilesMixin:
+ """
+ Tests for platform resources properly being cleaned up.
+ """
+ def createServer(self, address, portNumber, factory):
+ """
+ Bind a server port to which connections will be made. The server
+ should use the given protocol factory.
+
+ @return: The L{IListeningPort} for the server created.
+ """
+ raise NotImplementedError()
+
+
+ def connectClient(self, address, portNumber, clientCreator):
+ """
+ Establish a connection to the given address using the given
+ L{ClientCreator} instance.
+
+ @return: A Deferred which will fire with the connected protocol instance.
+ """
+ raise NotImplementedError()
+
+
+ def getHandleExceptionType(self):
+ """
+ Return the exception class which will be raised when an operation is
+ attempted on a closed platform handle.
+ """
+ raise NotImplementedError()
+
+
+ def getHandleErrorCode(self):
+ """
+ Return the errno expected to result from writing to a closed
+ platform socket handle.
+ """
+ # These platforms have been seen to give EBADF:
+ #
+ # Linux 2.4.26, Linux 2.6.15, OS X 10.4, FreeBSD 5.4
+ # Windows 2000 SP 4, Windows XP SP 2
+ return errno.EBADF
+
+
+ def test_properlyCloseFiles(self):
+ """
+ Test that lost connections properly have their underlying socket
+ resources cleaned up.
+ """
+ onServerConnectionLost = defer.Deferred()
+ serverFactory = protocol.ServerFactory()
+ serverFactory.protocol = lambda: ConnectionLostNotifyingProtocol(
+ onServerConnectionLost)
+ serverPort = self.createServer('127.0.0.1', 0, serverFactory)
+
+ onClientConnectionLost = defer.Deferred()
+ serverAddr = serverPort.getHost()
+ clientCreator = protocol.ClientCreator(
+ reactor, lambda: HandleSavingProtocol(onClientConnectionLost))
+ clientDeferred = self.connectClient(
+ serverAddr.host, serverAddr.port, clientCreator)
+
+ def clientConnected(client):
+ """
+ Disconnect the client. Return a Deferred which fires when both
+ the client and the server have received disconnect notification.
+ """
+ client.transport.write(
+ 'some bytes to make sure the connection is set up')
+ client.transport.loseConnection()
+ return defer.gatherResults([
+ onClientConnectionLost, onServerConnectionLost])
+ clientDeferred.addCallback(clientConnected)
+
+ def clientDisconnected((client, server)):
+ """
+ Verify that the underlying platform socket handle has been
+ cleaned up.
+ """
+ client.lostConnectionReason.trap(error.ConnectionClosed)
+ server.lostConnectionReason.trap(error.ConnectionClosed)
+ expectedErrorCode = self.getHandleErrorCode()
+ err = self.assertRaises(
+ self.getHandleExceptionType(), client.handle.send, 'bytes')
+ self.assertEqual(err.args[0], expectedErrorCode)
+ clientDeferred.addCallback(clientDisconnected)
+
+ def cleanup(passthrough):
+ """
+ Shut down the server port. Return a Deferred which fires when
+ this has completed.
+ """
+ result = defer.maybeDeferred(serverPort.stopListening)
+ result.addCallback(lambda ign: passthrough)
+ return result
+ clientDeferred.addBoth(cleanup)
+
+ return clientDeferred
+
+
+
+class ProperlyCloseFilesTestCase(unittest.TestCase, ProperlyCloseFilesMixin):
+ """
+ Test that the sockets created by L{IReactorTCP.connectTCP} are cleaned up
+ when the connection they are associated with is closed.
+ """
+ def createServer(self, address, portNumber, factory):
+ """
+ Create a TCP server using L{IReactorTCP.listenTCP}.
+ """
+ return reactor.listenTCP(portNumber, factory, interface=address)
+
+
+ def connectClient(self, address, portNumber, clientCreator):
+ """
+ Create a TCP client using L{IReactorTCP.connectTCP}.
+ """
+ return clientCreator.connectTCP(address, portNumber)
+
+
+ def getHandleExceptionType(self):
+ """
+ Return L{socket.error} as the expected error type which will be
+ raised by a write to the low-level socket object after it has been
+ closed.
+ """
+ return socket.error
+
+
+
+class WiredForDeferreds(policies.ProtocolWrapper):
+ def __init__(self, factory, wrappedProtocol):
+ policies.ProtocolWrapper.__init__(self, factory, wrappedProtocol)
+
+ def connectionMade(self):
+ policies.ProtocolWrapper.connectionMade(self)
+ self.factory.onConnect.callback(None)
+
+ def connectionLost(self, reason):
+ policies.ProtocolWrapper.connectionLost(self, reason)
+ self.factory.onDisconnect.callback(None)
+
+
+
+class WiredFactory(policies.WrappingFactory):
+ protocol = WiredForDeferreds
+
+ def __init__(self, wrappedFactory):
+ policies.WrappingFactory.__init__(self, wrappedFactory)
+ self.onConnect = defer.Deferred()
+ self.onDisconnect = defer.Deferred()
+
+
+
+class AddressTestCase(unittest.TestCase):
+ """
+ Tests for address-related interactions with client and server protocols.
+ """
+ def setUp(self):
+ """
+ Create a port and connected client/server pair which can be used
+ to test factory behavior related to addresses.
+
+ @return: A L{defer.Deferred} which will be called back when both the
+ client and server protocols have received their connection made
+ callback.
+ """
+ class RememberingWrapper(protocol.ClientFactory):
+ """
+ Simple wrapper factory which records the addresses which are
+ passed to its L{buildProtocol} method and delegates actual
+ protocol creation to another factory.
+
+ @ivar addresses: A list of the objects passed to buildProtocol.
+ @ivar factory: The wrapped factory to which protocol creation is
+ delegated.
+ """
+ def __init__(self, factory):
+ self.addresses = []
+ self.factory = factory
+
+ # Only bother to pass on buildProtocol calls to the wrapped
+ # factory - doStart, doStop, etc aren't necessary for this test
+ # to pass.
+ def buildProtocol(self, addr):
+ """
+ Append the given address to C{self.addresses} and forward
+ the call to C{self.factory}.
+ """
+ self.addresses.append(addr)
+ return self.factory.buildProtocol(addr)
+
+ # Make a server which we can receive connection and disconnection
+ # notification for, and which will record the address passed to its
+ # buildProtocol.
+ self.server = MyServerFactory()
+ self.serverConnMade = self.server.protocolConnectionMade = defer.Deferred()
+ self.serverConnLost = self.server.protocolConnectionLost = defer.Deferred()
+ # RememberingWrapper is a ClientFactory, but ClientFactory is-a
+ # ServerFactory, so this is okay.
+ self.serverWrapper = RememberingWrapper(self.server)
+
+ # Do something similar for a client.
+ self.client = MyClientFactory()
+ self.clientConnMade = self.client.protocolConnectionMade = defer.Deferred()
+ self.clientConnLost = self.client.protocolConnectionLost = defer.Deferred()
+ self.clientWrapper = RememberingWrapper(self.client)
+
+ self.port = reactor.listenTCP(0, self.serverWrapper, interface='127.0.0.1')
+ self.connector = reactor.connectTCP(
+ self.port.getHost().host, self.port.getHost().port, self.clientWrapper)
+
+ return defer.gatherResults([self.serverConnMade, self.clientConnMade])
+
+
+ def tearDown(self):
+ """
+ Disconnect the client/server pair and shutdown the port created in
+ L{setUp}.
+ """
+ self.connector.disconnect()
+ return defer.gatherResults([
+ self.serverConnLost, self.clientConnLost,
+ defer.maybeDeferred(self.port.stopListening)])
+
+
+ def test_buildProtocolClient(self):
+ """
+ L{ClientFactory.buildProtocol} should be invoked with the address of
+ the server to which a connection has been established, which should
+ be the same as the address reported by the C{getHost} method of the
+ transport of the server protocol and as the C{getPeer} method of the
+ transport of the client protocol.
+ """
+ serverHost = self.server.protocol.transport.getHost()
+ clientPeer = self.client.protocol.transport.getPeer()
+
+ self.assertEqual(
+ self.clientWrapper.addresses,
+ [IPv4Address('TCP', serverHost.host, serverHost.port)])
+ self.assertEqual(
+ self.clientWrapper.addresses,
+ [IPv4Address('TCP', clientPeer.host, clientPeer.port)])
+
+
+ def test_buildProtocolServer(self):
+ """
+ L{ServerFactory.buildProtocol} should be invoked with the address of
+ the client which has connected to the port the factory is listening on,
+ which should be the same as the address reported by the C{getPeer}
+ method of the transport of the server protocol and as the C{getHost}
+ method of the transport of the client protocol.
+ """
+ clientHost = self.client.protocol.transport.getHost()
+ serverPeer = self.server.protocol.transport.getPeer()
+
+ self.assertEqual(
+ self.serverWrapper.addresses,
+ [IPv4Address('TCP', serverPeer.host, serverPeer.port)])
+ self.assertEqual(
+ self.serverWrapper.addresses,
+ [IPv4Address('TCP', clientHost.host, clientHost.port)])
+
+
+
+class LargeBufferWriterProtocol(protocol.Protocol):
+
+ # Win32 sockets cannot handle single huge chunks of bytes. Write one
+ # massive string to make sure Twisted deals with this fact.
+
+ def connectionMade(self):
+ # write 60MB
+ self.transport.write('X'*self.factory.len)
+ self.factory.done = 1
+ self.transport.loseConnection()
+
+class LargeBufferReaderProtocol(protocol.Protocol):
+ def dataReceived(self, data):
+ self.factory.len += len(data)
+ def connectionLost(self, reason):
+ self.factory.done = 1
+
+class LargeBufferReaderClientFactory(protocol.ClientFactory):
+ def __init__(self):
+ self.done = 0
+ self.len = 0
+ def buildProtocol(self, addr):
+ p = LargeBufferReaderProtocol()
+ p.factory = self
+ self.protocol = p
+ return p
+
+
+class FireOnClose(policies.ProtocolWrapper):
+ """A wrapper around a protocol that makes it fire a deferred when
+ connectionLost is called.
+ """
+ def connectionLost(self, reason):
+ policies.ProtocolWrapper.connectionLost(self, reason)
+ self.factory.deferred.callback(None)
+
+
+class FireOnCloseFactory(policies.WrappingFactory):
+ protocol = FireOnClose
+
+ def __init__(self, wrappedFactory):
+ policies.WrappingFactory.__init__(self, wrappedFactory)
+ self.deferred = defer.Deferred()
+
+
+class LargeBufferTestCase(unittest.TestCase):
+ """Test that buffering large amounts of data works.
+ """
+
+ datalen = 60*1024*1024
+ def testWriter(self):
+ f = protocol.Factory()
+ f.protocol = LargeBufferWriterProtocol
+ f.done = 0
+ f.problem = 0
+ f.len = self.datalen
+ wrappedF = FireOnCloseFactory(f)
+ p = reactor.listenTCP(0, wrappedF, interface="127.0.0.1")
+ self.addCleanup(p.stopListening)
+ n = p.getHost().port
+ clientF = LargeBufferReaderClientFactory()
+ wrappedClientF = FireOnCloseFactory(clientF)
+ reactor.connectTCP("127.0.0.1", n, wrappedClientF)
+
+ d = defer.gatherResults([wrappedF.deferred, wrappedClientF.deferred])
+ def check(ignored):
+ self.failUnless(f.done, "writer didn't finish, it probably died")
+ self.failUnless(clientF.len == self.datalen,
+ "client didn't receive all the data it expected "
+ "(%d != %d)" % (clientF.len, self.datalen))
+ self.failUnless(clientF.done,
+ "client didn't see connection dropped")
+ return d.addCallback(check)
+
+
+class MyHCProtocol(AccumulatingProtocol):
+
+ implements(IHalfCloseableProtocol)
+
+ readHalfClosed = False
+ writeHalfClosed = False
+
+ def readConnectionLost(self):
+ self.readHalfClosed = True
+ # Invoke notification logic from the base class to simplify testing.
+ if self.writeHalfClosed:
+ self.connectionLost(None)
+
+ def writeConnectionLost(self):
+ self.writeHalfClosed = True
+ # Invoke notification logic from the base class to simplify testing.
+ if self.readHalfClosed:
+ self.connectionLost(None)
+
+
+class MyHCFactory(protocol.ServerFactory):
+
+ called = 0
+ protocolConnectionMade = None
+
+ def buildProtocol(self, addr):
+ self.called += 1
+ p = MyHCProtocol()
+ p.factory = self
+ self.protocol = p
+ return p
+
+
+class HalfCloseTestCase(unittest.TestCase):
+ """Test half-closing connections."""
+
+ def setUp(self):
+ self.f = f = MyHCFactory()
+ self.p = p = reactor.listenTCP(0, f, interface="127.0.0.1")
+ self.addCleanup(p.stopListening)
+ d = loopUntil(lambda :p.connected)
+
+ self.cf = protocol.ClientCreator(reactor, MyHCProtocol)
+
+ d.addCallback(lambda _: self.cf.connectTCP(p.getHost().host,
+ p.getHost().port))
+ d.addCallback(self._setUp)
+ return d
+
+ def _setUp(self, client):
+ self.client = client
+ self.clientProtoConnectionLost = self.client.closedDeferred = defer.Deferred()
+ self.assertEquals(self.client.transport.connected, 1)
+ # Wait for the server to notice there is a connection, too.
+ return loopUntil(lambda: getattr(self.f, 'protocol', None) is not None)
+
+ def tearDown(self):
+ self.assertEquals(self.client.closed, 0)
+ self.client.transport.loseConnection()
+ d = defer.maybeDeferred(self.p.stopListening)
+ d.addCallback(lambda ign: self.clientProtoConnectionLost)
+ d.addCallback(self._tearDown)
+ return d
+
+ def _tearDown(self, ignored):
+ self.assertEquals(self.client.closed, 1)
+ # because we did half-close, the server also needs to
+ # closed explicitly.
+ self.assertEquals(self.f.protocol.closed, 0)
+ d = defer.Deferred()
+ def _connectionLost(reason):
+ self.f.protocol.closed = 1
+ d.callback(None)
+ self.f.protocol.connectionLost = _connectionLost
+ self.f.protocol.transport.loseConnection()
+ d.addCallback(lambda x:self.assertEquals(self.f.protocol.closed, 1))
+ return d
+
+ def testCloseWriteCloser(self):
+ client = self.client
+ f = self.f
+ t = client.transport
+
+ t.write("hello")
+ d = loopUntil(lambda :len(t._tempDataBuffer) == 0)
+ def loseWrite(ignored):
+ t.loseWriteConnection()
+ return loopUntil(lambda :t._writeDisconnected)
+ def check(ignored):
+ self.assertEquals(client.closed, False)
+ self.assertEquals(client.writeHalfClosed, True)
+ self.assertEquals(client.readHalfClosed, False)
+ return loopUntil(lambda :f.protocol.readHalfClosed)
+ def write(ignored):
+ w = client.transport.write
+ w(" world")
+ w("lalala fooled you")
+ self.assertEquals(0, len(client.transport._tempDataBuffer))
+ self.assertEquals(f.protocol.data, "hello")
+ self.assertEquals(f.protocol.closed, False)
+ self.assertEquals(f.protocol.readHalfClosed, True)
+ return d.addCallback(loseWrite).addCallback(check).addCallback(write)
+
+ def testWriteCloseNotification(self):
+ f = self.f
+ f.protocol.transport.loseWriteConnection()
+
+ d = defer.gatherResults([
+ loopUntil(lambda :f.protocol.writeHalfClosed),
+ loopUntil(lambda :self.client.readHalfClosed)])
+ d.addCallback(lambda _: self.assertEquals(
+ f.protocol.readHalfClosed, False))
+ return d
+
+
+class HalfClose2TestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.f = f = MyServerFactory()
+ self.f.protocolConnectionMade = defer.Deferred()
+ self.p = p = reactor.listenTCP(0, f, interface="127.0.0.1")
+
+ # XXX we don't test server side yet since we don't do it yet
+ d = protocol.ClientCreator(reactor, AccumulatingProtocol).connectTCP(
+ p.getHost().host, p.getHost().port)
+ d.addCallback(self._gotClient)
+ return d
+
+ def _gotClient(self, client):
+ self.client = client
+ # Now wait for the server to catch up - it doesn't matter if this
+ # Deferred has already fired and gone away, in that case we'll
+ # return None and not wait at all, which is precisely correct.
+ return self.f.protocolConnectionMade
+
+ def tearDown(self):
+ self.client.transport.loseConnection()
+ return self.p.stopListening()
+
+ def testNoNotification(self):
+ """
+ TCP protocols support half-close connections, but not all of them
+ support being notified of write closes. In this case, test that
+ half-closing the connection causes the peer's connection to be
+ closed.
+ """
+ self.client.transport.write("hello")
+ self.client.transport.loseWriteConnection()
+ self.f.protocol.closedDeferred = d = defer.Deferred()
+ self.client.closedDeferred = d2 = defer.Deferred()
+ d.addCallback(lambda x:
+ self.assertEqual(self.f.protocol.data, 'hello'))
+ d.addCallback(lambda x: self.assertEqual(self.f.protocol.closed, True))
+ return defer.gatherResults([d, d2])
+
+ def testShutdownException(self):
+ """
+ If the other side has already closed its connection,
+ loseWriteConnection should pass silently.
+ """
+ self.f.protocol.transport.loseConnection()
+ self.client.transport.write("X")
+ self.client.transport.loseWriteConnection()
+ self.f.protocol.closedDeferred = d = defer.Deferred()
+ self.client.closedDeferred = d2 = defer.Deferred()
+ d.addCallback(lambda x:
+ self.failUnlessEqual(self.f.protocol.closed, True))
+ return defer.gatherResults([d, d2])
+
+
+class HalfCloseBuggyApplicationTests(unittest.TestCase):
+ """
+ Test half-closing connections where notification code has bugs.
+ """
+
+ def setUp(self):
+ """
+ Set up a server and connect a client to it. Return a Deferred which
+ only fires once this is done.
+ """
+ self.serverFactory = MyHCFactory()
+ self.serverFactory.protocolConnectionMade = defer.Deferred()
+ self.port = reactor.listenTCP(
+ 0, self.serverFactory, interface="127.0.0.1")
+ self.addCleanup(self.port.stopListening)
+ addr = self.port.getHost()
+ creator = protocol.ClientCreator(reactor, MyHCProtocol)
+ clientDeferred = creator.connectTCP(addr.host, addr.port)
+ def setClient(clientProtocol):
+ self.clientProtocol = clientProtocol
+ clientDeferred.addCallback(setClient)
+ return defer.gatherResults([
+ self.serverFactory.protocolConnectionMade,
+ clientDeferred])
+
+
+ def aBug(self, *args):
+ """
+ Fake implementation of a callback which illegally raises an
+ exception.
+ """
+ raise RuntimeError("ONO I AM BUGGY CODE")
+
+
+ def _notificationRaisesTest(self):
+ """
+ Helper for testing that an exception is logged by the time the
+ client protocol loses its connection.
+ """
+ closed = self.clientProtocol.closedDeferred = defer.Deferred()
+ self.clientProtocol.transport.loseWriteConnection()
+ def check(ignored):
+ errors = self.flushLoggedErrors(RuntimeError)
+ self.assertEqual(len(errors), 1)
+ closed.addCallback(check)
+ return closed
+
+
+ def test_readNotificationRaises(self):
+ """
+ If C{readConnectionLost} raises an exception when the transport
+ calls it to notify the protocol of that event, the exception should
+ be logged and the protocol should be disconnected completely.
+ """
+ self.serverFactory.protocol.readConnectionLost = self.aBug
+ return self._notificationRaisesTest()
+
+
+ def test_writeNotificationRaises(self):
+ """
+ If C{writeConnectionLost} raises an exception when the transport
+ calls it to notify the protocol of that event, the exception should
+ be logged and the protocol should be disconnected completely.
+ """
+ self.clientProtocol.writeConnectionLost = self.aBug
+ return self._notificationRaisesTest()
+
+
+
+class LogTestCase(unittest.TestCase):
+ """
+ Test logging facility of TCP base classes.
+ """
+
+ def test_logstrClientSetup(self):
+ """
+ Check that the log customization of the client transport happens
+ once the client is connected.
+ """
+ server = MyServerFactory()
+
+ client = MyClientFactory()
+ client.protocolConnectionMade = defer.Deferred()
+
+ port = reactor.listenTCP(0, server, interface='127.0.0.1')
+ self.addCleanup(port.stopListening)
+
+ connector = reactor.connectTCP(
+ port.getHost().host, port.getHost().port, client)
+ self.addCleanup(connector.disconnect)
+
+ # It should still have the default value
+ self.assertEquals(connector.transport.logstr,
+ "Uninitialized")
+
+ def cb(ign):
+ self.assertEquals(connector.transport.logstr,
+ "AccumulatingProtocol,client")
+ client.protocolConnectionMade.addCallback(cb)
+ return client.protocolConnectionMade
+
+
+
+class PauseProducingTestCase(unittest.TestCase):
+ """
+ Test some behaviors of pausing the production of a transport.
+ """
+
+ def test_pauseProducingInConnectionMade(self):
+ """
+ In C{connectionMade} of a client protocol, C{pauseProducing} used to be
+ ignored: this test is here to ensure it's not ignored.
+ """
+ server = MyServerFactory()
+
+ client = MyClientFactory()
+ client.protocolConnectionMade = defer.Deferred()
+
+ port = reactor.listenTCP(0, server, interface='127.0.0.1')
+ self.addCleanup(port.stopListening)
+
+ connector = reactor.connectTCP(
+ port.getHost().host, port.getHost().port, client)
+ self.addCleanup(connector.disconnect)
+
+ def checkInConnectionMade(proto):
+ tr = proto.transport
+ # The transport should already be monitored
+ self.assertIn(tr, reactor.getReaders() +
+ reactor.getWriters())
+ proto.transport.pauseProducing()
+ self.assertNotIn(tr, reactor.getReaders() +
+ reactor.getWriters())
+ d = defer.Deferred()
+ d.addCallback(checkAfterConnectionMade)
+ reactor.callLater(0, d.callback, proto)
+ return d
+ def checkAfterConnectionMade(proto):
+ tr = proto.transport
+ # The transport should still not be monitored
+ self.assertNotIn(tr, reactor.getReaders() +
+ reactor.getWriters())
+ client.protocolConnectionMade.addCallback(checkInConnectionMade)
+ return client.protocolConnectionMade
+
+ if not interfaces.IReactorFDSet.providedBy(reactor):
+ test_pauseProducingInConnectionMade.skip = "Reactor not providing IReactorFDSet"
+
+
+
+class CallBackOrderTestCase(unittest.TestCase):
+ """
+ Test the order of reactor callbacks
+ """
+
+ def test_loseOrder(self):
+ """
+ Check that Protocol.connectionLost is called before factory's
+ clientConnectionLost
+ """
+ server = MyServerFactory()
+ server.protocolConnectionMade = (defer.Deferred()
+ .addCallback(lambda proto: self.addCleanup(
+ proto.transport.loseConnection)))
+
+ client = MyClientFactory()
+ client.protocolConnectionLost = defer.Deferred()
+ client.protocolConnectionMade = defer.Deferred()
+
+ def _cbCM(res):
+ """
+ protocol.connectionMade callback
+ """
+ reactor.callLater(0, client.protocol.transport.loseConnection)
+
+ client.protocolConnectionMade.addCallback(_cbCM)
+
+ port = reactor.listenTCP(0, server, interface='127.0.0.1')
+ self.addCleanup(port.stopListening)
+
+ connector = reactor.connectTCP(
+ port.getHost().host, port.getHost().port, client)
+ self.addCleanup(connector.disconnect)
+
+ def _cbCCL(res):
+ """
+ factory.clientConnectionLost callback
+ """
+ return 'CCL'
+
+ def _cbCL(res):
+ """
+ protocol.connectionLost callback
+ """
+ return 'CL'
+
+ def _cbGather(res):
+ self.assertEquals(res, ['CL', 'CCL'])
+
+ d = defer.gatherResults([
+ client.protocolConnectionLost.addCallback(_cbCL),
+ client.deferred.addCallback(_cbCCL)])
+ return d.addCallback(_cbGather)
+
+
+
+try:
+ import resource
+except ImportError:
+ pass
+else:
+ numRounds = resource.getrlimit(resource.RLIMIT_NOFILE)[0] + 10
+ ProperlyCloseFilesTestCase.numberRounds = numRounds
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_tcp_internals.py b/vendor/Twisted-10.0.0/twisted/test/test_tcp_internals.py
new file mode 100644
index 0000000000..e4bf5bd3dd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_tcp_internals.py
@@ -0,0 +1,240 @@
+# Copyright (c) 2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Whitebox tests for TCP APIs.
+"""
+
+import errno, socket, os
+
+try:
+ import resource
+except ImportError:
+ resource = None
+
+from twisted.trial.unittest import TestCase
+
+from twisted.python import log
+from twisted.internet.tcp import ECONNABORTED, ENOMEM, ENFILE, EMFILE, ENOBUFS, EINPROGRESS, Port
+from twisted.internet.protocol import ServerFactory
+from twisted.python.runtime import platform
+from twisted.internet.defer import maybeDeferred, gatherResults
+from twisted.internet import reactor, interfaces
+
+
+class PlatformAssumptionsTestCase(TestCase):
+ """
+ Test assumptions about platform behaviors.
+ """
+ socketLimit = 8192
+
+ def setUp(self):
+ self.openSockets = []
+ if resource is not None:
+ self.originalFileLimit = resource.getrlimit(resource.RLIMIT_NOFILE)
+ resource.setrlimit(resource.RLIMIT_NOFILE, (128, self.originalFileLimit[1]))
+ self.socketLimit = 256
+
+
+ def tearDown(self):
+ while self.openSockets:
+ self.openSockets.pop().close()
+ if resource is not None:
+ # OS X implicitly lowers the hard limit in the setrlimit call
+ # above. Retrieve the new hard limit to pass in to this
+ # setrlimit call, so that it doesn't give us a permission denied
+ # error.
+ currentHardLimit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+ newSoftLimit = min(self.originalFileLimit[0], currentHardLimit)
+ resource.setrlimit(resource.RLIMIT_NOFILE, (newSoftLimit, currentHardLimit))
+
+
+ def socket(self):
+ """
+ Create and return a new socket object, also tracking it so it can be
+ closed in the test tear down.
+ """
+ s = socket.socket()
+ self.openSockets.append(s)
+ return s
+
+
+ def test_acceptOutOfFiles(self):
+ """
+ Test that the platform accept(2) call fails with either L{EMFILE} or
+ L{ENOBUFS} when there are too many file descriptors open.
+ """
+ # Make a server to which to connect
+ port = self.socket()
+ port.bind(('127.0.0.1', 0))
+ serverPortNumber = port.getsockname()[1]
+ port.listen(5)
+
+ # Use up all the file descriptors
+ for i in xrange(self.socketLimit):
+ try:
+ self.socket()
+ except socket.error, e:
+ if e.args[0] in (EMFILE, ENOBUFS):
+ self.openSockets.pop().close()
+ break
+ else:
+ raise
+ else:
+ self.fail("Could provoke neither EMFILE nor ENOBUFS from platform.")
+
+ # Make a client to use to connect to the server
+ client = self.socket()
+ client.setblocking(False)
+
+ # Non-blocking connect is supposed to fail, but this is not true
+ # everywhere (e.g. freeBSD)
+ self.assertIn(client.connect_ex(('127.0.0.1', serverPortNumber)),
+ (0, EINPROGRESS))
+
+ # Make sure that the accept call fails in the way we expect.
+ exc = self.assertRaises(socket.error, port.accept)
+ self.assertIn(exc.args[0], (EMFILE, ENOBUFS))
+ if platform.getType() == "win32":
+ test_acceptOutOfFiles.skip = (
+ "Windows requires an unacceptably large amount of resources to "
+ "provoke this behavior in the naive manner.")
+
+
+
+class SelectReactorTestCase(TestCase):
+ """
+ Tests for select-specific failure conditions.
+ """
+
+ def setUp(self):
+ self.ports = []
+ self.messages = []
+ log.addObserver(self.messages.append)
+
+
+ def tearDown(self):
+ log.removeObserver(self.messages.append)
+ return gatherResults([
+ maybeDeferred(p.stopListening)
+ for p in self.ports])
+
+
+ def port(self, portNumber, factory, interface):
+ """
+ Create, start, and return a new L{Port}, also tracking it so it can
+ be stopped in the test tear down.
+ """
+ p = Port(portNumber, factory, interface=interface)
+ p.startListening()
+ self.ports.append(p)
+ return p
+
+
+ def _acceptFailureTest(self, socketErrorNumber):
+ """
+ Test behavior in the face of an exception from C{accept(2)}.
+
+ On any exception which indicates the platform is unable or unwilling
+ to allocate further resources to us, the existing port should remain
+ listening, a message should be logged, and the exception should not
+ propagate outward from doRead.
+
+ @param socketErrorNumber: The errno to simulate from accept.
+ """
+ class FakeSocket(object):
+ """
+ Pretend to be a socket in an overloaded system.
+ """
+ def accept(self):
+ raise socket.error(
+ socketErrorNumber, os.strerror(socketErrorNumber))
+
+ factory = ServerFactory()
+ port = self.port(0, factory, interface='127.0.0.1')
+ originalSocket = port.socket
+ try:
+ port.socket = FakeSocket()
+
+ port.doRead()
+
+ expectedFormat = "Could not accept new connection (%s)"
+ expectedErrorCode = errno.errorcode[socketErrorNumber]
+ expectedMessage = expectedFormat % (expectedErrorCode,)
+ for msg in self.messages:
+ if msg.get('message') == (expectedMessage,):
+ break
+ else:
+ self.fail("Log event for failed accept not found in "
+ "%r" % (self.messages,))
+ finally:
+ port.socket = originalSocket
+
+
+ def test_tooManyFilesFromAccept(self):
+ """
+ C{accept(2)} can fail with C{EMFILE} when there are too many open file
+ descriptors in the process. Test that this doesn't negatively impact
+ any other existing connections.
+
+ C{EMFILE} mainly occurs on Linux when the open file rlimit is
+ encountered.
+ """
+ return self._acceptFailureTest(EMFILE)
+
+
+ def test_noBufferSpaceFromAccept(self):
+ """
+ Similar to L{test_tooManyFilesFromAccept}, but test the case where
+ C{accept(2)} fails with C{ENOBUFS}.
+
+ This mainly occurs on Windows and FreeBSD, but may be possible on
+ Linux and other platforms as well.
+ """
+ return self._acceptFailureTest(ENOBUFS)
+
+
+ def test_connectionAbortedFromAccept(self):
+ """
+ Similar to L{test_tooManyFilesFromAccept}, but test the case where
+ C{accept(2)} fails with C{ECONNABORTED}.
+
+ It is not clear whether this is actually possible for TCP
+ connections on modern versions of Linux.
+ """
+ return self._acceptFailureTest(ECONNABORTED)
+
+
+ def test_noFilesFromAccept(self):
+ """
+ Similar to L{test_tooManyFilesFromAccept}, but test the case where
+ C{accept(2)} fails with C{ENFILE}.
+
+ This can occur on Linux when the system has exhausted (!) its supply
+ of inodes.
+ """
+ return self._acceptFailureTest(ENFILE)
+ if platform.getType() == 'win32':
+ test_noFilesFromAccept.skip = "Windows accept(2) cannot generate ENFILE"
+
+
+ def test_noMemoryFromAccept(self):
+ """
+ Similar to L{test_tooManyFilesFromAccept}, but test the case where
+ C{accept(2)} fails with C{ENOMEM}.
+
+ On Linux at least, this can sensibly occur, even in a Python program
+ (which eats memory like no ones business), when memory has become
+ fragmented or low memory has been filled (d_alloc calls
+ kmem_cache_alloc calls kmalloc - kmalloc only allocates out of low
+ memory).
+ """
+ return self._acceptFailureTest(ENOMEM)
+ if platform.getType() == 'win32':
+ test_noMemoryFromAccept.skip = "Windows accept(2) cannot generate ENOMEM"
+
+if not interfaces.IReactorFDSet.providedBy(reactor):
+ skipMsg = 'This test only applies to reactors that implement IReactorFDset'
+ PlatformAssumptionsTestCase.skip = skipMsg
+ SelectReactorTestCase.skip = skipMsg
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_text.py b/vendor/Twisted-10.0.0/twisted/test/test_text.py
new file mode 100644
index 0000000000..cd0109d0fa
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_text.py
@@ -0,0 +1,156 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.trial import unittest
+from twisted.python import text
+import string
+from cStringIO import StringIO
+
+sampleText = \
+"""Every attempt to employ mathematical methods in the study of chemical
+questions must be considered profoundly irrational and contrary to the
+spirit of chemistry ... If mathematical analysis should ever hold a
+prominent place in chemistry - an aberration which is happily almost
+impossible - it would occasion a rapid and widespread degeneration of that
+science.
+ -- Auguste Comte, Philosophie Positive, Paris, 1838
+"""
+
+lineWidth = 72
+
+def set_lineWidth(n):
+ global lineWidth
+ lineWidth = n
+
+class WrapTest(unittest.TestCase):
+ def setUp(self):
+ self.sampleSplitText = string.split(sampleText)
+
+ self.output = text.wordWrap(sampleText, lineWidth)
+
+ def test_wordCount(self):
+ """Compare the number of words."""
+ words = []
+ for line in self.output:
+ words.extend(string.split(line))
+ wordCount = len(words)
+ sampleTextWordCount = len(self.sampleSplitText)
+
+ self.failUnlessEqual(wordCount, sampleTextWordCount)
+
+ def test_wordMatch(self):
+ """Compare the lists of words."""
+
+ words = []
+ for line in self.output:
+ words.extend(string.split(line))
+
+ # Using failUnlessEqual here prints out some
+ # rather too long lists.
+ self.failUnless(self.sampleSplitText == words)
+
+ def test_lineLength(self):
+ """Check the length of the lines."""
+ failures = []
+ for line in self.output:
+ if not len(line) <= lineWidth:
+ failures.append(len(line))
+
+ if failures:
+ self.fail("%d of %d lines were too long.\n"
+ "%d < %s" % (len(failures), len(self.output),
+ lineWidth, failures))
+
+
+class SplitTest(unittest.TestCase):
+ """Tests for text.splitQuoted()"""
+
+ def test_oneWord(self):
+ """Splitting strings with one-word phrases."""
+ s = 'This code "works."'
+ r = text.splitQuoted(s)
+ self.failUnlessEqual(['This', 'code', 'works.'], r)
+
+ def test_multiWord(self):
+ s = 'The "hairy monkey" likes pie.'
+ r = text.splitQuoted(s)
+ self.failUnlessEqual(['The', 'hairy monkey', 'likes', 'pie.'], r)
+
+ # Some of the many tests that would fail:
+
+ #def test_preserveWhitespace(self):
+ # phrase = '"MANY SPACES"'
+ # s = 'With %s between.' % (phrase,)
+ # r = text.splitQuoted(s)
+ # self.failUnlessEqual(['With', phrase, 'between.'], r)
+
+ #def test_escapedSpace(self):
+ # s = r"One\ Phrase"
+ # r = text.splitQuoted(s)
+ # self.failUnlessEqual(["One Phrase"], r)
+
+class StrFileTest(unittest.TestCase):
+ def setUp(self):
+ self.io = StringIO("this is a test string")
+
+ def tearDown(self):
+ pass
+
+ def test_1_f(self):
+ self.assertEquals(False, text.strFile("x", self.io))
+
+ def test_1_1(self):
+ self.assertEquals(True, text.strFile("t", self.io))
+
+ def test_1_2(self):
+ self.assertEquals(True, text.strFile("h", self.io))
+
+ def test_1_3(self):
+ self.assertEquals(True, text.strFile("i", self.io))
+
+ def test_1_4(self):
+ self.assertEquals(True, text.strFile("s", self.io))
+
+ def test_1_5(self):
+ self.assertEquals(True, text.strFile("n", self.io))
+
+ def test_1_6(self):
+ self.assertEquals(True, text.strFile("g", self.io))
+
+ def test_3_1(self):
+ self.assertEquals(True, text.strFile("thi", self.io))
+
+ def test_3_2(self):
+ self.assertEquals(True, text.strFile("his", self.io))
+
+ def test_3_3(self):
+ self.assertEquals(True, text.strFile("is ", self.io))
+
+ def test_3_4(self):
+ self.assertEquals(True, text.strFile("ing", self.io))
+
+ def test_3_f(self):
+ self.assertEquals(False, text.strFile("bla", self.io))
+
+ def test_large_1(self):
+ self.assertEquals(True, text.strFile("this is a test", self.io))
+
+ def test_large_2(self):
+ self.assertEquals(True, text.strFile("is a test string", self.io))
+
+ def test_large_f(self):
+ self.assertEquals(False, text.strFile("ds jhfsa k fdas", self.io))
+
+ def test_overlarge_f(self):
+ self.assertEquals(False, text.strFile("djhsakj dhsa fkhsa s,mdbnfsauiw bndasdf hreew", self.io))
+
+ def test_self(self):
+ self.assertEquals(True, text.strFile("this is a test string", self.io))
+
+ def test_insensitive(self):
+ self.assertEquals(True, text.strFile("ThIs is A test STRING", self.io, False))
+
+testCases = [WrapTest, SplitTest, StrFileTest]
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_threadable.py b/vendor/Twisted-10.0.0/twisted/test/test_threadable.py
new file mode 100644
index 0000000000..c496b98158
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_threadable.py
@@ -0,0 +1,103 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys, pickle
+
+try:
+ import threading
+except ImportError:
+ threading = None
+
+from twisted.trial import unittest
+from twisted.python import threadable
+from twisted.internet import defer, reactor
+
+class TestObject:
+ synchronized = ['aMethod']
+
+ x = -1
+ y = 1
+
+ def aMethod(self):
+ for i in xrange(10):
+ self.x, self.y = self.y, self.x
+ self.z = self.x + self.y
+ assert self.z == 0, "z == %d, not 0 as expected" % (self.z,)
+
+threadable.synchronize(TestObject)
+
+class SynchronizationTestCase(unittest.TestCase):
+ def setUp(self):
+ """
+ Reduce the CPython check interval so that thread switches happen much
+ more often, hopefully exercising more possible race conditions. Also,
+ delay actual test startup until the reactor has been started.
+ """
+ if hasattr(sys, 'getcheckinterval'):
+ self.addCleanup(sys.setcheckinterval, sys.getcheckinterval())
+ sys.setcheckinterval(7)
+ # XXX This is a trial hack. We need to make sure the reactor
+ # actually *starts* for isInIOThread() to have a meaningful result.
+ # Returning a Deferred here should force that to happen, if it has
+ # not happened already. In the future, this should not be
+ # necessary.
+ d = defer.Deferred()
+ reactor.callLater(0, d.callback, None)
+ return d
+
+
+ def testIsInIOThread(self):
+ foreignResult = []
+ t = threading.Thread(target=lambda: foreignResult.append(threadable.isInIOThread()))
+ t.start()
+ t.join()
+ self.failIf(foreignResult[0], "Non-IO thread reported as IO thread")
+ self.failUnless(threadable.isInIOThread(), "IO thread reported as not IO thread")
+
+
+ def testThreadedSynchronization(self):
+ o = TestObject()
+
+ errors = []
+
+ def callMethodLots():
+ try:
+ for i in xrange(1000):
+ o.aMethod()
+ except AssertionError, e:
+ errors.append(str(e))
+
+ threads = []
+ for x in range(5):
+ t = threading.Thread(target=callMethodLots)
+ threads.append(t)
+ t.start()
+
+ for t in threads:
+ t.join()
+
+ if errors:
+ raise unittest.FailTest(errors)
+
+ def testUnthreadedSynchronization(self):
+ o = TestObject()
+ for i in xrange(1000):
+ o.aMethod()
+
+class SerializationTestCase(unittest.TestCase):
+ def testPickling(self):
+ lock = threadable.XLock()
+ lockType = type(lock)
+ lockPickle = pickle.dumps(lock)
+ newLock = pickle.loads(lockPickle)
+ self.failUnless(isinstance(newLock, lockType))
+
+ def testUnpickling(self):
+ lockPickle = 'ctwisted.python.threadable\nunpickle_lock\np0\n(tp1\nRp2\n.'
+ lock = pickle.loads(lockPickle)
+ newPickle = pickle.dumps(lock, 2)
+ newLock = pickle.loads(newPickle)
+
+if threading is None:
+ SynchronizationTestCase.testThreadedSynchronization.skip = "Platform lacks thread support"
+ SerializationTestCase.testPickling.skip = "Platform lacks thread support"
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_threadpool.py b/vendor/Twisted-10.0.0/twisted/test/test_threadpool.py
new file mode 100644
index 0000000000..30862d9265
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_threadpool.py
@@ -0,0 +1,583 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import pickle, time, weakref, gc
+
+from twisted.trial import unittest, util
+from twisted.python import threadable, failure, context
+from twisted.internet import reactor, interfaces
+from twisted.internet.defer import Deferred
+
+#
+# See the end of this module for the remainder of the imports.
+#
+
+class Synchronization(object):
+ failures = 0
+
+ def __init__(self, N, waiting):
+ self.N = N
+ self.waiting = waiting
+ self.lock = threading.Lock()
+ self.runs = []
+
+ def run(self):
+ # This is the testy part: this is supposed to be invoked
+ # serially from multiple threads. If that is actually the
+ # case, we will never fail to acquire this lock. If it is
+ # *not* the case, we might get here while someone else is
+ # holding the lock.
+ if self.lock.acquire(False):
+ if not len(self.runs) % 5:
+ time.sleep(0.0002) # Constant selected based on
+ # empirical data to maximize the
+ # chance of a quick failure if this
+ # code is broken.
+ self.lock.release()
+ else:
+ self.failures += 1
+
+ # This is just the only way I can think of to wake up the test
+ # method. It doesn't actually have anything to do with the
+ # test.
+ self.lock.acquire()
+ self.runs.append(None)
+ if len(self.runs) == self.N:
+ self.waiting.release()
+ self.lock.release()
+
+ synchronized = ["run"]
+threadable.synchronize(Synchronization)
+
+
+
+class ThreadPoolTestCase(unittest.TestCase):
+ """
+ Test threadpools.
+ """
+ def _waitForLock(self, lock):
+ for i in xrange(1000000):
+ if lock.acquire(False):
+ break
+ time.sleep(1e-5)
+ else:
+ self.fail("A long time passed without succeeding")
+
+
+ def test_attributes(self):
+ """
+ L{ThreadPool.min} and L{ThreadPool.max} are set to the values passed to
+ L{ThreadPool.__init__}.
+ """
+ pool = threadpool.ThreadPool(12, 22)
+ self.assertEqual(pool.min, 12)
+ self.assertEqual(pool.max, 22)
+
+
+ def test_start(self):
+ """
+ L{ThreadPool.start} creates the minimum number of threads specified.
+ """
+ pool = threadpool.ThreadPool(0, 5)
+ pool.start()
+ self.addCleanup(pool.stop)
+ self.assertEqual(len(pool.threads), 0)
+
+ pool = threadpool.ThreadPool(3, 10)
+ self.assertEqual(len(pool.threads), 0)
+ pool.start()
+ self.addCleanup(pool.stop)
+ self.assertEqual(len(pool.threads), 3)
+
+
+ def test_threadCreationArguments(self):
+ """
+ Test that creating threads in the threadpool with application-level
+ objects as arguments doesn't results in those objects never being
+ freed, with the thread maintaining a reference to them as long as it
+ exists.
+ """
+ tp = threadpool.ThreadPool(0, 1)
+ tp.start()
+ self.addCleanup(tp.stop)
+
+ # Sanity check - no threads should have been started yet.
+ self.assertEqual(tp.threads, [])
+
+ # Here's our function
+ def worker(arg):
+ pass
+ # weakref needs an object subclass
+ class Dumb(object):
+ pass
+ # And here's the unique object
+ unique = Dumb()
+
+ workerRef = weakref.ref(worker)
+ uniqueRef = weakref.ref(unique)
+
+ # Put some work in
+ tp.callInThread(worker, unique)
+
+ # Add an event to wait completion
+ event = threading.Event()
+ tp.callInThread(event.set)
+ event.wait(self.getTimeout())
+
+ del worker
+ del unique
+ gc.collect()
+ self.assertEquals(uniqueRef(), None)
+ self.assertEquals(workerRef(), None)
+
+
+ def test_threadCreationArgumentsCallInThreadWithCallback(self):
+ """
+ As C{test_threadCreationArguments} above, but for
+ callInThreadWithCallback.
+ """
+
+ tp = threadpool.ThreadPool(0, 1)
+ tp.start()
+ self.addCleanup(tp.stop)
+
+ # Sanity check - no threads should have been started yet.
+ self.assertEqual(tp.threads, [])
+
+ # this holds references obtained in onResult
+ refdict = {} # name -> ref value
+
+ onResultWait = threading.Event()
+ onResultDone = threading.Event()
+
+ resultRef = []
+
+ # result callback
+ def onResult(success, result):
+ onResultWait.wait(self.getTimeout())
+ refdict['workerRef'] = workerRef()
+ refdict['uniqueRef'] = uniqueRef()
+ onResultDone.set()
+ resultRef.append(weakref.ref(result))
+
+ # Here's our function
+ def worker(arg, test):
+ return Dumb()
+
+ # weakref needs an object subclass
+ class Dumb(object):
+ pass
+
+ # And here's the unique object
+ unique = Dumb()
+
+ onResultRef = weakref.ref(onResult)
+ workerRef = weakref.ref(worker)
+ uniqueRef = weakref.ref(unique)
+
+ # Put some work in
+ tp.callInThreadWithCallback(onResult, worker, unique, test=unique)
+
+ del worker
+ del unique
+ gc.collect()
+
+ # let onResult collect the refs
+ onResultWait.set()
+ # wait for onResult
+ onResultDone.wait(self.getTimeout())
+
+ self.assertEquals(uniqueRef(), None)
+ self.assertEquals(workerRef(), None)
+
+ # XXX There's a race right here - has onResult in the worker thread
+ # returned and the locals in _worker holding it and the result been
+ # deleted yet?
+
+ del onResult
+ gc.collect()
+ self.assertEqual(onResultRef(), None)
+ self.assertEqual(resultRef[0](), None)
+
+
+ def test_persistence(self):
+ """
+ Threadpools can be pickled and unpickled, which should preserve the
+ number of threads and other parameters.
+ """
+ pool = threadpool.ThreadPool(7, 20)
+
+ self.assertEquals(pool.min, 7)
+ self.assertEquals(pool.max, 20)
+
+ # check that unpickled threadpool has same number of threads
+ copy = pickle.loads(pickle.dumps(pool))
+
+ self.assertEquals(copy.min, 7)
+ self.assertEquals(copy.max, 20)
+
+
+ def _threadpoolTest(self, method):
+ """
+ Test synchronization of calls made with C{method}, which should be
+ one of the mechanisms of the threadpool to execute work in threads.
+ """
+ # This is a schizophrenic test: it seems to be trying to test
+ # both the callInThread()/dispatch() behavior of the ThreadPool as well
+ # as the serialization behavior of threadable.synchronize(). It
+ # would probably make more sense as two much simpler tests.
+ N = 10
+
+ tp = threadpool.ThreadPool()
+ tp.start()
+ self.addCleanup(tp.stop)
+
+ waiting = threading.Lock()
+ waiting.acquire()
+ actor = Synchronization(N, waiting)
+
+ for i in xrange(N):
+ method(tp, actor)
+
+ self._waitForLock(waiting)
+
+ self.failIf(actor.failures, "run() re-entered %d times" %
+ (actor.failures,))
+
+
+ def test_dispatch(self):
+ """
+ Call C{_threadpoolTest} with C{dispatch}.
+ """
+ return self._threadpoolTest(
+ lambda tp, actor: tp.dispatch(actor, actor.run))
+
+ test_dispatch.suppress = [util.suppress(
+ message="dispatch\(\) is deprecated since Twisted 8.0, "
+ "use callInThread\(\) instead",
+ category=DeprecationWarning)]
+
+
+ def test_callInThread(self):
+ """
+ Call C{_threadpoolTest} with C{callInThread}.
+ """
+ return self._threadpoolTest(
+ lambda tp, actor: tp.callInThread(actor.run))
+
+
+ def test_callInThreadException(self):
+ """
+ L{ThreadPool.callInThread} logs exceptions raised by the callable it
+ is passed.
+ """
+ class NewError(Exception):
+ pass
+
+ def raiseError():
+ raise NewError()
+
+ tp = threadpool.ThreadPool(0, 1)
+ tp.callInThread(raiseError)
+ tp.start()
+ tp.stop()
+
+ errors = self.flushLoggedErrors(NewError)
+ self.assertEqual(len(errors), 1)
+
+
+ def test_callInThreadWithCallback(self):
+ """
+ L{ThreadPool.callInThreadWithCallback} calls C{onResult} with a
+ two-tuple of C{(True, result)} where C{result} is the value returned
+ by the callable supplied.
+ """
+ waiter = threading.Lock()
+ waiter.acquire()
+
+ results = []
+
+ def onResult(success, result):
+ waiter.release()
+ results.append(success)
+ results.append(result)
+
+ tp = threadpool.ThreadPool(0, 1)
+ tp.callInThreadWithCallback(onResult, lambda : "test")
+ tp.start()
+
+ try:
+ self._waitForLock(waiter)
+ finally:
+ tp.stop()
+
+ self.assertTrue(results[0])
+ self.assertEqual(results[1], "test")
+
+
+ def test_callInThreadWithCallbackExceptionInCallback(self):
+ """
+ L{ThreadPool.callInThreadWithCallback} calls C{onResult} with a
+ two-tuple of C{(False, failure)} where C{failure} represents the
+ exception raised by the callable supplied.
+ """
+ class NewError(Exception):
+ pass
+
+ def raiseError():
+ raise NewError()
+
+ waiter = threading.Lock()
+ waiter.acquire()
+
+ results = []
+
+ def onResult(success, result):
+ waiter.release()
+ results.append(success)
+ results.append(result)
+
+ tp = threadpool.ThreadPool(0, 1)
+ tp.callInThreadWithCallback(onResult, raiseError)
+ tp.start()
+
+ try:
+ self._waitForLock(waiter)
+ finally:
+ tp.stop()
+
+ self.assertFalse(results[0])
+ self.assertTrue(isinstance(results[1], failure.Failure))
+ self.assertTrue(issubclass(results[1].type, NewError))
+
+
+ def test_callInThreadWithCallbackExceptionInOnResult(self):
+ """
+ L{ThreadPool.callInThreadWithCallback} logs the exception raised by
+ C{onResult}.
+ """
+ class NewError(Exception):
+ pass
+
+ waiter = threading.Lock()
+ waiter.acquire()
+
+ results = []
+
+ def onResult(success, result):
+ results.append(success)
+ results.append(result)
+ raise NewError()
+
+ tp = threadpool.ThreadPool(0, 1)
+ tp.callInThreadWithCallback(onResult, lambda : None)
+ tp.callInThread(waiter.release)
+ tp.start()
+
+ try:
+ self._waitForLock(waiter)
+ finally:
+ tp.stop()
+
+ errors = self.flushLoggedErrors(NewError)
+ self.assertEqual(len(errors), 1)
+
+ self.assertTrue(results[0])
+ self.assertEqual(results[1], None)
+
+
+ def test_callbackThread(self):
+ """
+ L{ThreadPool.callInThreadWithCallback} calls the function it is
+ given and the C{onResult} callback in the same thread.
+ """
+ threadIds = []
+
+ import thread
+
+ event = threading.Event()
+
+ def onResult(success, result):
+ threadIds.append(thread.get_ident())
+ event.set()
+
+ def func():
+ threadIds.append(thread.get_ident())
+
+ tp = threadpool.ThreadPool(0, 1)
+ tp.callInThreadWithCallback(onResult, func)
+ tp.start()
+ self.addCleanup(tp.stop)
+
+ event.wait(self.getTimeout())
+ self.assertEqual(len(threadIds), 2)
+ self.assertEqual(threadIds[0], threadIds[1])
+
+
+ def test_callbackContext(self):
+ """
+ The context L{ThreadPool.callInThreadWithCallback} is invoked in is
+ shared by the context the callable and C{onResult} callback are
+ invoked in.
+ """
+ myctx = context.theContextTracker.currentContext().contexts[-1]
+ myctx['testing'] = 'this must be present'
+
+ contexts = []
+
+ event = threading.Event()
+
+ def onResult(success, result):
+ ctx = context.theContextTracker.currentContext().contexts[-1]
+ contexts.append(ctx)
+ event.set()
+
+ def func():
+ ctx = context.theContextTracker.currentContext().contexts[-1]
+ contexts.append(ctx)
+
+ tp = threadpool.ThreadPool(0, 1)
+ tp.callInThreadWithCallback(onResult, func)
+ tp.start()
+ self.addCleanup(tp.stop)
+
+ event.wait(self.getTimeout())
+
+ self.assertEqual(len(contexts), 2)
+ self.assertEqual(myctx, contexts[0])
+ self.assertEqual(myctx, contexts[1])
+
+
+ def test_existingWork(self):
+ """
+ Work added to the threadpool before its start should be executed once
+ the threadpool is started: this is ensured by trying to release a lock
+ previously acquired.
+ """
+ waiter = threading.Lock()
+ waiter.acquire()
+
+ tp = threadpool.ThreadPool(0, 1)
+ tp.callInThread(waiter.release) # before start()
+ tp.start()
+
+ try:
+ self._waitForLock(waiter)
+ finally:
+ tp.stop()
+
+
+ def test_dispatchDeprecation(self):
+ """
+ Test for the deprecation of the dispatch method.
+ """
+ tp = threadpool.ThreadPool()
+ tp.start()
+ self.addCleanup(tp.stop)
+
+ def cb():
+ return tp.dispatch(None, lambda: None)
+
+ self.assertWarns(DeprecationWarning,
+ "dispatch() is deprecated since Twisted 8.0, "
+ "use callInThread() instead",
+ __file__, cb)
+
+
+ def test_dispatchWithCallbackDeprecation(self):
+ """
+ Test for the deprecation of the dispatchWithCallback method.
+ """
+ tp = threadpool.ThreadPool()
+ tp.start()
+ self.addCleanup(tp.stop)
+
+ def cb():
+ return tp.dispatchWithCallback(
+ None,
+ lambda x: None,
+ lambda x: None,
+ lambda: None)
+
+ self.assertWarns(DeprecationWarning,
+ "dispatchWithCallback() is deprecated since Twisted 8.0, "
+ "use twisted.internet.threads.deferToThread() instead.",
+ __file__, cb)
+
+
+
+class RaceConditionTestCase(unittest.TestCase):
+ def setUp(self):
+ self.event = threading.Event()
+ self.threadpool = threadpool.ThreadPool(0, 10)
+ self.threadpool.start()
+
+
+ def tearDown(self):
+ del self.event
+ self.threadpool.stop()
+ del self.threadpool
+
+
+ def test_synchronization(self):
+ """
+ Test a race condition: ensure that actions run in the pool synchronize
+ with actions run in the main thread.
+ """
+ timeout = self.getTimeout()
+ self.threadpool.callInThread(self.event.set)
+ self.event.wait(timeout)
+ self.event.clear()
+ for i in range(3):
+ self.threadpool.callInThread(self.event.wait)
+ self.threadpool.callInThread(self.event.set)
+ self.event.wait(timeout)
+ if not self.event.isSet():
+ self.event.set()
+ self.fail("Actions not synchronized")
+
+
+ def test_singleThread(self):
+ """
+ The submission of a new job to a thread pool in response to the
+ C{onResult} callback does not cause a new thread to be added to the
+ thread pool.
+
+ This requires that the thread which calls C{onResult} to have first
+ marked itself as available so that when the new job is queued, that
+ thread may be considered to run it. This is desirable so that when
+ only N jobs are ever being executed in the thread pool at once only
+ N threads will ever be created.
+ """
+ # Ensure no threads running
+ self.assertEquals(self.threadpool.workers, 0)
+
+ loopDeferred = Deferred()
+
+ def onResult(success, counter):
+ reactor.callFromThread(submit, counter)
+
+ def submit(counter):
+ if counter:
+ self.threadpool.callInThreadWithCallback(
+ onResult, lambda: counter - 1)
+ else:
+ loopDeferred.callback(None)
+
+ def cbLoop(ignored):
+ # Ensure there is only one thread running.
+ self.assertEqual(self.threadpool.workers, 1)
+
+ loopDeferred.addCallback(cbLoop)
+ submit(10)
+ return loopDeferred
+
+
+
+if interfaces.IReactorThreads(reactor, None) is None:
+ for cls in ThreadPoolTestCase, RaceConditionTestCase:
+ setattr(cls, 'skip', "No thread support, nothing to test here")
+else:
+ import threading
+ from twisted.python import threadpool
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_threads.py b/vendor/Twisted-10.0.0/twisted/test/test_threads.py
new file mode 100644
index 0000000000..eb692866ca
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_threads.py
@@ -0,0 +1,412 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Test methods in twisted.internet.threads and reactor thread APIs.
+"""
+
+import sys, os, time
+
+from twisted.trial import unittest
+
+from twisted.internet import reactor, defer, interfaces, threads, protocol, error
+from twisted.python import failure, threadable, log, threadpool
+
+
+
+class ReactorThreadsTestCase(unittest.TestCase):
+ """
+ Tests for the reactor threading API.
+ """
+
+ def test_suggestThreadPoolSize(self):
+ """
+ Try to change maximum number of threads.
+ """
+ reactor.suggestThreadPoolSize(34)
+ self.assertEquals(reactor.threadpool.max, 34)
+ reactor.suggestThreadPoolSize(4)
+ self.assertEquals(reactor.threadpool.max, 4)
+
+
+ def _waitForThread(self):
+ """
+ The reactor's threadpool is only available when the reactor is running,
+ so to have a sane behavior during the tests we make a dummy
+ L{threads.deferToThread} call.
+ """
+ return threads.deferToThread(time.sleep, 0)
+
+
+ def test_callInThread(self):
+ """
+ Test callInThread functionality: set a C{threading.Event}, and check
+ that it's not in the main thread.
+ """
+ def cb(ign):
+ waiter = threading.Event()
+ result = []
+ def threadedFunc():
+ result.append(threadable.isInIOThread())
+ waiter.set()
+
+ reactor.callInThread(threadedFunc)
+ waiter.wait(120)
+ if not waiter.isSet():
+ self.fail("Timed out waiting for event.")
+ else:
+ self.assertEquals(result, [False])
+ return self._waitForThread().addCallback(cb)
+
+
+ def test_callFromThread(self):
+ """
+ Test callFromThread functionality: from the main thread, and from
+ another thread.
+ """
+ def cb(ign):
+ firedByReactorThread = defer.Deferred()
+ firedByOtherThread = defer.Deferred()
+
+ def threadedFunc():
+ reactor.callFromThread(firedByOtherThread.callback, None)
+
+ reactor.callInThread(threadedFunc)
+ reactor.callFromThread(firedByReactorThread.callback, None)
+
+ return defer.DeferredList(
+ [firedByReactorThread, firedByOtherThread],
+ fireOnOneErrback=True)
+ return self._waitForThread().addCallback(cb)
+
+
+ def test_wakerOverflow(self):
+ """
+ Try to make an overflow on the reactor waker using callFromThread.
+ """
+ def cb(ign):
+ self.failure = None
+ waiter = threading.Event()
+ def threadedFunction():
+ # Hopefully a hundred thousand queued calls is enough to
+ # trigger the error condition
+ for i in xrange(100000):
+ try:
+ reactor.callFromThread(lambda: None)
+ except:
+ self.failure = failure.Failure()
+ break
+ waiter.set()
+ reactor.callInThread(threadedFunction)
+ waiter.wait(120)
+ if not waiter.isSet():
+ self.fail("Timed out waiting for event")
+ if self.failure is not None:
+ return defer.fail(self.failure)
+ return self._waitForThread().addCallback(cb)
+
+ def _testBlockingCallFromThread(self, reactorFunc):
+ """
+ Utility method to test L{threads.blockingCallFromThread}.
+ """
+ waiter = threading.Event()
+ results = []
+ errors = []
+ def cb1(ign):
+ def threadedFunc():
+ try:
+ r = threads.blockingCallFromThread(reactor, reactorFunc)
+ except Exception, e:
+ errors.append(e)
+ else:
+ results.append(r)
+ waiter.set()
+
+ reactor.callInThread(threadedFunc)
+ return threads.deferToThread(waiter.wait, self.getTimeout())
+
+ def cb2(ign):
+ if not waiter.isSet():
+ self.fail("Timed out waiting for event")
+ return results, errors
+
+ return self._waitForThread().addCallback(cb1).addBoth(cb2)
+
+ def test_blockingCallFromThread(self):
+ """
+ Test blockingCallFromThread facility: create a thread, call a function
+ in the reactor using L{threads.blockingCallFromThread}, and verify the
+ result returned.
+ """
+ def reactorFunc():
+ return defer.succeed("foo")
+ def cb(res):
+ self.assertEquals(res[0][0], "foo")
+
+ return self._testBlockingCallFromThread(reactorFunc).addCallback(cb)
+
+ def test_asyncBlockingCallFromThread(self):
+ """
+ Test blockingCallFromThread as above, but be sure the resulting
+ Deferred is not already fired.
+ """
+ def reactorFunc():
+ d = defer.Deferred()
+ reactor.callLater(0.1, d.callback, "egg")
+ return d
+ def cb(res):
+ self.assertEquals(res[0][0], "egg")
+
+ return self._testBlockingCallFromThread(reactorFunc).addCallback(cb)
+
+ def test_errorBlockingCallFromThread(self):
+ """
+ Test error report for blockingCallFromThread.
+ """
+ def reactorFunc():
+ return defer.fail(RuntimeError("bar"))
+ def cb(res):
+ self.assert_(isinstance(res[1][0], RuntimeError))
+ self.assertEquals(res[1][0].args[0], "bar")
+
+ return self._testBlockingCallFromThread(reactorFunc).addCallback(cb)
+
+ def test_asyncErrorBlockingCallFromThread(self):
+ """
+ Test error report for blockingCallFromThread as above, but be sure the
+ resulting Deferred is not already fired.
+ """
+ def reactorFunc():
+ d = defer.Deferred()
+ reactor.callLater(0.1, d.errback, RuntimeError("spam"))
+ return d
+ def cb(res):
+ self.assert_(isinstance(res[1][0], RuntimeError))
+ self.assertEquals(res[1][0].args[0], "spam")
+
+ return self._testBlockingCallFromThread(reactorFunc).addCallback(cb)
+
+
+class Counter:
+ index = 0
+ problem = 0
+
+ def add(self):
+ """A non thread-safe method."""
+ next = self.index + 1
+ # another thread could jump in here and increment self.index on us
+ if next != self.index + 1:
+ self.problem = 1
+ raise ValueError
+ # or here, same issue but we wouldn't catch it. We'd overwrite
+ # their results, and the index will have lost a count. If
+ # several threads get in here, we will actually make the count
+ # go backwards when we overwrite it.
+ self.index = next
+
+
+
+class DeferredResultTestCase(unittest.TestCase):
+ """
+ Test twisted.internet.threads.
+ """
+
+ def setUp(self):
+ reactor.suggestThreadPoolSize(8)
+
+
+ def tearDown(self):
+ reactor.suggestThreadPoolSize(0)
+
+
+ def testCallMultiple(self):
+ L = []
+ N = 10
+ d = defer.Deferred()
+
+ def finished():
+ self.assertEquals(L, range(N))
+ d.callback(None)
+
+ threads.callMultipleInThread([
+ (L.append, (i,), {}) for i in xrange(N)
+ ] + [(reactor.callFromThread, (finished,), {})])
+ return d
+
+
+ def test_deferredResult(self):
+ """
+ L{threads.deferToThread} executes the function passed, and correctly
+ handles the positional and keyword arguments given.
+ """
+ d = threads.deferToThread(lambda x, y=5: x + y, 3, y=4)
+ d.addCallback(self.assertEquals, 7)
+ return d
+
+
+ def test_deferredFailure(self):
+ """
+ Check that L{threads.deferToThread} return a failure object
+ with an appropriate exception instance when the called
+ function raises an exception.
+ """
+ class NewError(Exception):
+ pass
+ def raiseError():
+ raise NewError()
+ d = threads.deferToThread(raiseError)
+ return self.assertFailure(d, NewError)
+
+
+ def test_deferredFailureAfterSuccess(self):
+ """
+ Check that a successfull L{threads.deferToThread} followed by a one
+ that raises an exception correctly result as a failure.
+ """
+ # set up a condition that causes cReactor to hang. These conditions
+ # can also be set by other tests when the full test suite is run in
+ # alphabetical order (test_flow.FlowTest.testThreaded followed by
+ # test_internet.ReactorCoreTestCase.testStop, to be precise). By
+ # setting them up explicitly here, we can reproduce the hang in a
+ # single precise test case instead of depending upon side effects of
+ # other tests.
+ #
+ # alas, this test appears to flunk the default reactor too
+
+ d = threads.deferToThread(lambda: None)
+ d.addCallback(lambda ign: threads.deferToThread(lambda: 1/0))
+ return self.assertFailure(d, ZeroDivisionError)
+
+
+
+class DeferToThreadPoolTestCase(unittest.TestCase):
+ """
+ Test L{twisted.internet.threads.deferToThreadPool}.
+ """
+
+ def setUp(self):
+ self.tp = threadpool.ThreadPool(0, 8)
+ self.tp.start()
+
+
+ def tearDown(self):
+ self.tp.stop()
+
+
+ def test_deferredResult(self):
+ """
+ L{threads.deferToThreadPool} executes the function passed, and
+ correctly handles the positional and keyword arguments given.
+ """
+ d = threads.deferToThreadPool(reactor, self.tp,
+ lambda x, y=5: x + y, 3, y=4)
+ d.addCallback(self.assertEqual, 7)
+ return d
+
+
+ def test_deferredFailure(self):
+ """
+ Check that L{threads.deferToThreadPool} return a failure object with an
+ appropriate exception instance when the called function raises an
+ exception.
+ """
+ class NewError(Exception):
+ pass
+ def raiseError():
+ raise NewError()
+ d = threads.deferToThreadPool(reactor, self.tp, raiseError)
+ return self.assertFailure(d, NewError)
+
+
+
+_callBeforeStartupProgram = """
+import time
+import %(reactor)s
+%(reactor)s.install()
+
+from twisted.internet import reactor
+
+def threadedCall():
+ print 'threaded call'
+
+reactor.callInThread(threadedCall)
+
+# Spin very briefly to try to give the thread a chance to run, if it
+# is going to. Is there a better way to achieve this behavior?
+for i in xrange(100):
+ time.sleep(0.0)
+"""
+
+
+class ThreadStartupProcessProtocol(protocol.ProcessProtocol):
+ def __init__(self, finished):
+ self.finished = finished
+ self.out = []
+ self.err = []
+
+ def outReceived(self, out):
+ self.out.append(out)
+
+ def errReceived(self, err):
+ self.err.append(err)
+
+ def processEnded(self, reason):
+ self.finished.callback((self.out, self.err, reason))
+
+
+
+class StartupBehaviorTestCase(unittest.TestCase):
+ """
+ Test cases for the behavior of the reactor threadpool near startup
+ boundary conditions.
+
+ In particular, this asserts that no threaded calls are attempted
+ until the reactor starts up, that calls attempted before it starts
+ are in fact executed once it has started, and that in both cases,
+ the reactor properly cleans itself up (which is tested for
+ somewhat implicitly, by requiring a child process be able to exit,
+ something it cannot do unless the threadpool has been properly
+ torn down).
+ """
+
+
+ def testCallBeforeStartupUnexecuted(self):
+ progname = self.mktemp()
+ progfile = file(progname, 'w')
+ progfile.write(_callBeforeStartupProgram % {'reactor': reactor.__module__})
+ progfile.close()
+
+ def programFinished((out, err, reason)):
+ if reason.check(error.ProcessTerminated):
+ self.fail("Process did not exit cleanly (out: %s err: %s)" % (out, err))
+
+ if err:
+ log.msg("Unexpected output on standard error: %s" % (err,))
+ self.failIf(out, "Expected no output, instead received:\n%s" % (out,))
+
+ def programTimeout(err):
+ err.trap(error.TimeoutError)
+ proto.signalProcess('KILL')
+ return err
+
+ env = os.environ.copy()
+ env['PYTHONPATH'] = os.pathsep.join(sys.path)
+ d = defer.Deferred().addCallbacks(programFinished, programTimeout)
+ proto = ThreadStartupProcessProtocol(d)
+ reactor.spawnProcess(proto, sys.executable, ('python', progname), env)
+ return d
+
+
+
+if interfaces.IReactorThreads(reactor, None) is None:
+ for cls in (ReactorThreadsTestCase,
+ DeferredResultTestCase,
+ StartupBehaviorTestCase):
+ cls.skip = "No thread support, nothing to test here."
+else:
+ import threading
+
+if interfaces.IReactorProcess(reactor, None) is None:
+ for cls in (StartupBehaviorTestCase,):
+ cls.skip = "No process support, cannot run subprocess thread tests."
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_timehelpers.py b/vendor/Twisted-10.0.0/twisted/test/test_timehelpers.py
new file mode 100644
index 0000000000..5d506ab522
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_timehelpers.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for the deprecated L{twisted.test.time_helpers} module.
+"""
+
+import sys
+
+from twisted.trial.unittest import TestCase
+
+
+class TimeHelpersTests(TestCase):
+ """
+ A test for the deprecation of the module.
+ """
+ def test_deprecated(self):
+ """
+ Importing L{twisted.test.time_helpers} causes a deprecation warning
+ to be emitted.
+ """
+ # Make sure we're really importing it
+ sys.modules.pop('twisted.test.time_helpers', None)
+ import twisted.test.time_helpers
+ warnings = self.flushWarnings(
+ offendingFunctions=[self.test_deprecated])
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(
+ warnings[0]['message'],
+ "twisted.test.time_helpers is deprecated since Twisted 10.0. "
+ "See twisted.internet.task.Clock instead.")
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_timeoutqueue.py b/vendor/Twisted-10.0.0/twisted/test/test_timeoutqueue.py
new file mode 100644
index 0000000000..146b8ecc48
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_timeoutqueue.py
@@ -0,0 +1,73 @@
+
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for timeoutqueue module.
+"""
+
+import time
+
+from twisted.python import timeoutqueue
+from twisted.trial import unittest, util
+from twisted.internet import reactor, interfaces
+
+timeoutqueueSuppression = util.suppress(
+ message="timeoutqueue is deprecated since Twisted 8.0",
+ category=DeprecationWarning)
+
+
+class TimeoutQueueTest(unittest.TestCase):
+ """
+ Test L{timeoutqueue.TimeoutQueue} class.
+ """
+
+ def tearDown(self):
+ del self.q
+
+ def put(self):
+ time.sleep(1)
+ self.q.put(1)
+
+ def test_timeout(self):
+ q = self.q = timeoutqueue.TimeoutQueue()
+
+ try:
+ q.wait(1)
+ except timeoutqueue.TimedOut:
+ pass
+ else:
+ self.fail("Didn't time out")
+ test_timeout.suppress = [timeoutqueueSuppression]
+
+ def test_get(self):
+ q = self.q = timeoutqueue.TimeoutQueue()
+
+ start = time.time()
+ threading.Thread(target=self.put).start()
+ q.wait(1.5)
+ assert time.time() - start < 2
+
+ result = q.get(0)
+ if result != 1:
+ self.fail("Didn't get item we put in")
+ test_get.suppress = [timeoutqueueSuppression]
+
+ def test_deprecation(self):
+ """
+ Test that L{timeoutqueue.TimeoutQueue} prints a warning message.
+ """
+ def createQueue():
+ return timeoutqueue.TimeoutQueue()
+ self.q = self.assertWarns(
+ DeprecationWarning,
+ "timeoutqueue is deprecated since Twisted 8.0",
+ __file__,
+ createQueue)
+
+ if interfaces.IReactorThreads(reactor, None) is None:
+ test_get.skip = "No thread support, no way to test putting during a blocked get"
+ else:
+ global threading
+ import threading
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_tpfile.py b/vendor/Twisted-10.0.0/twisted/test/test_tpfile.py
new file mode 100644
index 0000000000..eaa1789ec2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_tpfile.py
@@ -0,0 +1,52 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.trial import unittest
+from twisted.protocols import loopback
+from twisted.protocols import basic
+from twisted.internet import protocol, abstract
+
+import StringIO
+
+class BufferingServer(protocol.Protocol):
+ buffer = ''
+ def dataReceived(self, data):
+ self.buffer += data
+
+class FileSendingClient(protocol.Protocol):
+ def __init__(self, f):
+ self.f = f
+
+ def connectionMade(self):
+ s = basic.FileSender()
+ d = s.beginFileTransfer(self.f, self.transport, lambda x: x)
+ d.addCallback(lambda r: self.transport.loseConnection())
+
+class FileSenderTestCase(unittest.TestCase):
+ def testSendingFile(self):
+ testStr = 'xyz' * 100 + 'abc' * 100 + '123' * 100
+ s = BufferingServer()
+ c = FileSendingClient(StringIO.StringIO(testStr))
+
+ d = loopback.loopbackTCP(s, c)
+ d.addCallback(lambda x : self.assertEquals(s.buffer, testStr))
+ return d
+
+ def testSendingEmptyFile(self):
+ fileSender = basic.FileSender()
+ consumer = abstract.FileDescriptor()
+ consumer.connected = 1
+ emptyFile = StringIO.StringIO('')
+
+ d = fileSender.beginFileTransfer(emptyFile, consumer, lambda x: x)
+
+ # The producer will be immediately exhausted, and so immediately
+ # unregistered
+ self.assertEqual(consumer.producer, None)
+
+ # Which means the Deferred from FileSender should have been called
+ self.failUnless(d.called,
+ 'producer unregistered with deferred being called')
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_twistd.py b/vendor/Twisted-10.0.0/twisted/test/test_twistd.py
new file mode 100644
index 0000000000..84b0cc4901
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_twistd.py
@@ -0,0 +1,1378 @@
+# Copyright (c) 2007-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.application.app} and L{twisted.scripts.twistd}.
+"""
+
+import signal, inspect, errno
+
+import os, sys, cPickle, StringIO
+try:
+ import pwd, grp
+except ImportError:
+ pwd = grp = None
+
+from zope.interface import implements
+
+from twisted.trial import unittest
+
+from twisted.application import service, app
+from twisted.scripts import twistd
+from twisted.python import log
+from twisted.python.usage import UsageError
+from twisted.python.log import ILogObserver
+from twisted.python.versions import Version
+from twisted.python.components import Componentized
+from twisted.internet.defer import Deferred
+from twisted.python.fakepwd import UserDatabase
+
+try:
+ from twisted.python import syslog
+except ImportError:
+ syslog = None
+
+try:
+ from twisted.scripts import _twistd_unix
+except ImportError:
+ _twistd_unix = None
+else:
+ from twisted.scripts._twistd_unix import UnixApplicationRunner
+ from twisted.scripts._twistd_unix import UnixAppLogger
+
+try:
+ import profile
+except ImportError:
+ profile = None
+
+try:
+ import hotshot
+ import hotshot.stats
+except (ImportError, SystemExit):
+ # For some reasons, hotshot.stats seems to raise SystemExit on some
+ # distributions, probably when considered non-free. See the import of
+ # this module in twisted.application.app for more details.
+ hotshot = None
+
+try:
+ import pstats
+ import cProfile
+except ImportError:
+ cProfile = None
+
+
+
+def patchUserDatabase(patch, user, uid, group, gid):
+ """
+ Patch L{pwd.getpwnam} so that it behaves as though only one user exists
+ and patch L{grp.getgrnam} so that it behaves as though only one group
+ exists.
+
+ @param patch: A function like L{TestCase.patch} which will be used to
+ install the fake implementations.
+
+ @type user: C{str}
+ @param user: The name of the single user which will exist.
+
+ @type uid: C{int}
+ @param uid: The UID of the single user which will exist.
+
+ @type group: C{str}
+ @param group: The name of the single user which will exist.
+
+ @type gid: C{int}
+ @param gid: The GID of the single group which will exist.
+ """
+ # Try not to be an unverified fake, but try not to depend on quirks of
+ # the system either (eg, run as a process with a uid and gid which
+ # equal each other, and so doesn't reliably test that uid is used where
+ # uid should be used and gid is used where gid should be used). -exarkun
+ pwent = pwd.getpwuid(os.getuid())
+ grent = grp.getgrgid(os.getgid())
+
+ database = UserDatabase()
+ database.addUser(
+ user, pwent.pw_passwd, uid, pwent.pw_gid,
+ pwent.pw_gecos, pwent.pw_dir, pwent.pw_shell)
+
+ def getgrnam(name):
+ result = list(grent)
+ result[result.index(grent.gr_name)] = group
+ result[result.index(grent.gr_gid)] = gid
+ result = tuple(result)
+ return {group: result}[name]
+
+ patch(pwd, "getpwnam", database.getpwnam)
+ patch(grp, "getgrnam", getgrnam)
+
+
+
+class MockServiceMaker(object):
+ """
+ A non-implementation of L{twisted.application.service.IServiceMaker}.
+ """
+ tapname = 'ueoa'
+
+ def makeService(self, options):
+ """
+ Take a L{usage.Options} instance and return a
+ L{service.IService} provider.
+ """
+ self.options = options
+ self.service = service.Service()
+ return self.service
+
+
+
+class CrippledAppLogger(app.AppLogger):
+ """
+ @see: CrippledApplicationRunner.
+ """
+
+ def start(self, application):
+ pass
+
+
+
+class CrippledApplicationRunner(twistd._SomeApplicationRunner):
+ """
+ An application runner that cripples the platform-specific runner and
+ nasty side-effect-having code so that we can use it without actually
+ running any environment-affecting code.
+ """
+ loggerFactory = CrippledAppLogger
+
+ def preApplication(self):
+ pass
+
+
+ def postApplication(self):
+ pass
+
+
+
+class ServerOptionsTest(unittest.TestCase):
+ """
+ Non-platform-specific tests for the pltaform-specific ServerOptions class.
+ """
+
+ def test_postOptionsSubCommandCausesNoSave(self):
+ """
+ postOptions should set no_save to True when a subcommand is used.
+ """
+ config = twistd.ServerOptions()
+ config.subCommand = 'ueoa'
+ config.postOptions()
+ self.assertEquals(config['no_save'], True)
+
+
+ def test_postOptionsNoSubCommandSavesAsUsual(self):
+ """
+ If no sub command is used, postOptions should not touch no_save.
+ """
+ config = twistd.ServerOptions()
+ config.postOptions()
+ self.assertEquals(config['no_save'], False)
+
+
+ def test_reportProfileDeprecation(self):
+ """
+ Check that the --report-profile option prints a C{DeprecationWarning}.
+ """
+ config = twistd.ServerOptions()
+ self.assertWarns(
+ DeprecationWarning, "--report-profile option is deprecated and "
+ "a no-op since Twisted 8.0.", app.__file__,
+ config.parseOptions, ["--report-profile", "foo"])
+
+
+ def test_listAllProfilers(self):
+ """
+ All the profilers that can be used in L{app.AppProfiler} are listed in
+ the help output.
+ """
+ config = twistd.ServerOptions()
+ helpOutput = str(config)
+ for profiler in app.AppProfiler.profilers:
+ self.assertIn(profiler, helpOutput)
+
+
+ def test_defaultUmask(self):
+ """
+ The default value for the C{umask} option is C{None}.
+ """
+ config = twistd.ServerOptions()
+ self.assertEqual(config['umask'], None)
+
+
+ def test_umask(self):
+ """
+ The value given for the C{umask} option is parsed as an octal integer
+ literal.
+ """
+ config = twistd.ServerOptions()
+ config.parseOptions(['--umask', '123'])
+ self.assertEqual(config['umask'], 83)
+ config.parseOptions(['--umask', '0123'])
+ self.assertEqual(config['umask'], 83)
+
+
+ def test_invalidUmask(self):
+ """
+ If a value is given for the C{umask} option which cannot be parsed as
+ an integer, L{UsageError} is raised by L{ServerOptions.parseOptions}.
+ """
+ config = twistd.ServerOptions()
+ self.assertRaises(UsageError, config.parseOptions, ['--umask', 'abcdef'])
+
+ if _twistd_unix is None:
+ msg = "twistd unix not available"
+ test_defaultUmask.skip = test_umask.skip = test_invalidUmask.skip = msg
+
+
+
+class TapFileTest(unittest.TestCase):
+ """
+ Test twistd-related functionality that requires a tap file on disk.
+ """
+
+ def setUp(self):
+ """
+ Create a trivial Application and put it in a tap file on disk.
+ """
+ self.tapfile = self.mktemp()
+ f = file(self.tapfile, 'wb')
+ cPickle.dump(service.Application("Hi!"), f)
+ f.close()
+
+
+ def test_createOrGetApplicationWithTapFile(self):
+ """
+ Ensure that the createOrGetApplication call that 'twistd -f foo.tap'
+ makes will load the Application out of foo.tap.
+ """
+ config = twistd.ServerOptions()
+ config.parseOptions(['-f', self.tapfile])
+ application = CrippledApplicationRunner(config).createOrGetApplication()
+ self.assertEquals(service.IService(application).name, 'Hi!')
+
+
+
+class TestLoggerFactory(object):
+ """
+ A logger factory for L{TestApplicationRunner}.
+ """
+
+ def __init__(self, runner):
+ self.runner = runner
+
+
+ def start(self, application):
+ """
+ Save the logging start on the C{runner} instance.
+ """
+ self.runner.order.append("log")
+ self.runner.hadApplicationLogObserver = hasattr(self.runner,
+ 'application')
+
+
+ def stop(self):
+ """
+ Don't log anything.
+ """
+
+
+
+class TestApplicationRunner(app.ApplicationRunner):
+ """
+ An ApplicationRunner which tracks the environment in which its methods are
+ called.
+ """
+
+ def __init__(self, options):
+ app.ApplicationRunner.__init__(self, options)
+ self.order = []
+ self.logger = TestLoggerFactory(self)
+
+
+ def preApplication(self):
+ self.order.append("pre")
+ self.hadApplicationPreApplication = hasattr(self, 'application')
+
+
+ def postApplication(self):
+ self.order.append("post")
+ self.hadApplicationPostApplication = hasattr(self, 'application')
+
+
+
+class ApplicationRunnerTest(unittest.TestCase):
+ """
+ Non-platform-specific tests for the platform-specific ApplicationRunner.
+ """
+ def setUp(self):
+ config = twistd.ServerOptions()
+ self.serviceMaker = MockServiceMaker()
+ # Set up a config object like it's been parsed with a subcommand
+ config.loadedPlugins = {'test_command': self.serviceMaker}
+ config.subOptions = object()
+ config.subCommand = 'test_command'
+ self.config = config
+
+
+ def test_applicationRunnerGetsCorrectApplication(self):
+ """
+ Ensure that a twistd plugin gets used in appropriate ways: it
+ is passed its Options instance, and the service it returns is
+ added to the application.
+ """
+ arunner = CrippledApplicationRunner(self.config)
+ arunner.run()
+
+ self.assertIdentical(
+ self.serviceMaker.options, self.config.subOptions,
+ "ServiceMaker.makeService needs to be passed the correct "
+ "sub Command object.")
+ self.assertIdentical(
+ self.serviceMaker.service,
+ service.IService(arunner.application).services[0],
+ "ServiceMaker.makeService's result needs to be set as a child "
+ "of the Application.")
+
+
+ def test_preAndPostApplication(self):
+ """
+ Test thet preApplication and postApplication methods are
+ called by ApplicationRunner.run() when appropriate.
+ """
+ s = TestApplicationRunner(self.config)
+ s.run()
+ self.assertFalse(s.hadApplicationPreApplication)
+ self.assertTrue(s.hadApplicationPostApplication)
+ self.assertTrue(s.hadApplicationLogObserver)
+ self.assertEquals(s.order, ["pre", "log", "post"])
+
+
+ def _applicationStartsWithConfiguredID(self, argv, uid, gid):
+ """
+ Assert that given a particular command line, an application is started
+ as a particular UID/GID.
+
+ @param argv: A list of strings giving the options to parse.
+ @param uid: An integer giving the expected UID.
+ @param gid: An integer giving the expected GID.
+ """
+ self.config.parseOptions(argv)
+
+ events = []
+ class FakeUnixApplicationRunner(twistd._SomeApplicationRunner):
+ def setupEnvironment(self, chroot, rundir, nodaemon, umask,
+ pidfile):
+ events.append('environment')
+
+ def shedPrivileges(self, euid, uid, gid):
+ events.append(('privileges', euid, uid, gid))
+
+ def startReactor(self, reactor, oldstdout, oldstderr):
+ events.append('reactor')
+
+ def removePID(self, pidfile):
+ pass
+
+
+ class FakeService(object):
+ implements(service.IService, service.IProcess)
+
+ processName = None
+
+ def privilegedStartService(self):
+ events.append('privilegedStartService')
+
+ def startService(self):
+ events.append('startService')
+
+ def stopService(self):
+ pass
+
+ runner = FakeUnixApplicationRunner(self.config)
+ runner.preApplication()
+ runner.application = FakeService()
+ runner.postApplication()
+
+ self.assertEqual(
+ events,
+ ['environment', 'privilegedStartService',
+ ('privileges', False, uid, gid), 'startService', 'reactor'])
+
+
+ def test_applicationStartsWithConfiguredNumericIDs(self):
+ """
+ L{postApplication} should change the UID and GID to the values
+ specified as numeric strings by the configuration after running
+ L{service.IService.privilegedStartService} and before running
+ L{service.IService.startService}.
+ """
+ uid = 1234
+ gid = 4321
+ self._applicationStartsWithConfiguredID(
+ ["--uid", str(uid), "--gid", str(gid)], uid, gid)
+
+
+ def test_applicationStartsWithConfiguredNameIDs(self):
+ """
+ L{postApplication} should change the UID and GID to the values
+ specified as user and group names by the configuration after running
+ L{service.IService.privilegedStartService} and before running
+ L{service.IService.startService}.
+ """
+ user = "foo"
+ uid = 1234
+ group = "bar"
+ gid = 4321
+ patchUserDatabase(self.patch, user, uid, group, gid)
+ self._applicationStartsWithConfiguredID(
+ ["--uid", user, "--gid", group], uid, gid)
+
+ if getattr(os, 'setuid', None) is None:
+ msg = "Platform does not support --uid/--gid twistd options."
+ test_applicationStartsWithConfiguredNameIDs.skip = msg
+ test_applicationStartsWithConfiguredNumericIDs.skip = msg
+ del msg
+
+
+ def test_startReactorRunsTheReactor(self):
+ """
+ L{startReactor} calls L{reactor.run}.
+ """
+ reactor = DummyReactor()
+ runner = app.ApplicationRunner({
+ "profile": False,
+ "profiler": "profile",
+ "debug": False})
+ runner.startReactor(reactor, None, None)
+ self.assertTrue(
+ reactor.called, "startReactor did not call reactor.run()")
+
+
+ def test_legacyApplicationRunnerGetLogObserver(self):
+ """
+ L{app.ApplicationRunner} subclasses can have a getLogObserver that used
+ to return a log observer. This test is there to ensure that it's
+ supported but it raises a warning when used.
+ """
+ observer = []
+ self.addCleanup(log.removeObserver, observer.append)
+ class GetLogObserverRunner(app.ApplicationRunner):
+ def getLogObserver(self):
+ return observer.append
+
+ def startLogging(self, observer):
+ """
+ Override C{startLogging} to call L{log.addObserver} instead of
+ L{log.startLoggingWithObserver}.
+ """
+ log.addObserver(observer)
+ self.logger._initialLog()
+
+ def preApplication(self):
+ pass
+
+ def postApplication(self):
+ pass
+
+ def createOrGetApplication(self):
+ pass
+
+ conf = twistd.ServerOptions()
+ runner = GetLogObserverRunner(conf)
+ self.assertWarns(DeprecationWarning,
+ "Specifying a log observer with getLogObserver is "
+ "deprecated. Please use a loggerFactory instead.",
+ app.__file__, runner.run)
+ self.assertEquals(len(observer), 3)
+
+
+
+class UnixApplicationRunnerSetupEnvironmentTests(unittest.TestCase):
+ """
+ Tests for L{UnixApplicationRunner.setupEnvironment}.
+
+ @ivar root: The root of the filesystem, or C{unset} if none has been
+ specified with a call to L{os.chroot} (patched for this TestCase with
+ L{UnixApplicationRunnerSetupEnvironmentTests.chroot ).
+
+ @ivar cwd: The current working directory of the process, or C{unset} if
+ none has been specified with a call to L{os.chdir} (patched for this
+ TestCase with L{UnixApplicationRunnerSetupEnvironmentTests.chdir).
+
+ @ivar mask: The current file creation mask of the process, or C{unset} if
+ none has been specified with a call to L{os.umask} (patched for this
+ TestCase with L{UnixApplicationRunnerSetupEnvironmentTests.umask).
+
+ @ivar daemon: A boolean indicating whether daemonization has been performed
+ by a call to L{_twistd_unix.daemonize} (patched for this TestCase with
+ L{UnixApplicationRunnerSetupEnvironmentTests.
+ """
+ if _twistd_unix is None:
+ skip = "twistd unix not available"
+
+ unset = object()
+
+ def setUp(self):
+ self.root = self.unset
+ self.cwd = self.unset
+ self.mask = self.unset
+ self.daemon = False
+ self.pid = os.getpid()
+ self.patch(os, 'chroot', lambda path: setattr(self, 'root', path))
+ self.patch(os, 'chdir', lambda path: setattr(self, 'cwd', path))
+ self.patch(os, 'umask', lambda mask: setattr(self, 'mask', mask))
+ self.patch(_twistd_unix, "daemonize", self.daemonize)
+ self.runner = UnixApplicationRunner({})
+
+
+ def daemonize(self):
+ """
+ Indicate that daemonization has happened and change the PID so that the
+ value written to the pidfile can be tested in the daemonization case.
+ """
+ self.daemon = True
+ self.patch(os, 'getpid', lambda: self.pid + 1)
+
+
+ def test_chroot(self):
+ """
+ L{UnixApplicationRunner.setupEnvironment} changes the root of the
+ filesystem if passed a non-C{None} value for the C{chroot} parameter.
+ """
+ self.runner.setupEnvironment("/foo/bar", ".", True, None, None)
+ self.assertEqual(self.root, "/foo/bar")
+
+
+ def test_noChroot(self):
+ """
+ L{UnixApplicationRunner.setupEnvironment} does not change the root of
+ the filesystem if passed C{None} for the C{chroot} parameter.
+ """
+ self.runner.setupEnvironment(None, ".", True, None, None)
+ self.assertIdentical(self.root, self.unset)
+
+
+ def test_changeWorkingDirectory(self):
+ """
+ L{UnixApplicationRunner.setupEnvironment} changes the working directory
+ of the process to the path given for the C{rundir} parameter.
+ """
+ self.runner.setupEnvironment(None, "/foo/bar", True, None, None)
+ self.assertEqual(self.cwd, "/foo/bar")
+
+
+ def test_daemonize(self):
+ """
+ L{UnixApplicationRunner.setupEnvironment} daemonizes the process if
+ C{False} is passed for the C{nodaemon} parameter.
+ """
+ self.runner.setupEnvironment(None, ".", False, None, None)
+ self.assertTrue(self.daemon)
+
+
+ def test_noDaemonize(self):
+ """
+ L{UnixApplicationRunner.setupEnvironment} does not daemonize the
+ process if C{True} is passed for the C{nodaemon} parameter.
+ """
+ self.runner.setupEnvironment(None, ".", True, None, None)
+ self.assertFalse(self.daemon)
+
+
+ def test_nonDaemonPIDFile(self):
+ """
+ L{UnixApplicationRunner.setupEnvironment} writes the process's PID to
+ the file specified by the C{pidfile} parameter.
+ """
+ pidfile = self.mktemp()
+ self.runner.setupEnvironment(None, ".", True, None, pidfile)
+ fObj = file(pidfile)
+ pid = int(fObj.read())
+ fObj.close()
+ self.assertEqual(pid, self.pid)
+
+
+ def test_daemonPIDFile(self):
+ """
+ L{UnixApplicationRunner.setupEnvironment} writes the daemonized
+ process's PID to the file specified by the C{pidfile} parameter if
+ C{nodaemon} is C{False}.
+ """
+ pidfile = self.mktemp()
+ self.runner.setupEnvironment(None, ".", False, None, pidfile)
+ fObj = file(pidfile)
+ pid = int(fObj.read())
+ fObj.close()
+ self.assertEqual(pid, self.pid + 1)
+
+
+ def test_umask(self):
+ """
+ L{UnixApplicationRunner.setupEnvironment} changes the process umask to
+ the value specified by the C{umask} parameter.
+ """
+ self.runner.setupEnvironment(None, ".", False, 123, None)
+ self.assertEqual(self.mask, 123)
+
+
+ def test_noDaemonizeNoUmask(self):
+ """
+ L{UnixApplicationRunner.setupEnvironment} doesn't change the process
+ umask if C{None} is passed for the C{umask} parameter and C{True} is
+ passed for the C{nodaemon} parameter.
+ """
+ self.runner.setupEnvironment(None, ".", True, None, None)
+ self.assertIdentical(self.mask, self.unset)
+
+
+ def test_daemonizedNoUmask(self):
+ """
+ L{UnixApplicationRunner.setupEnvironment} changes the process umask to
+ C{0077} if C{None} is passed for the C{umask} parameter and C{False} is
+ passed for the C{nodaemon} parameter.
+ """
+ self.runner.setupEnvironment(None, ".", False, None, None)
+ self.assertEqual(self.mask, 0077)
+
+
+
+class UnixApplicationRunnerStartApplicationTests(unittest.TestCase):
+ """
+ Tests for L{UnixApplicationRunner.startApplication}.
+ """
+ if _twistd_unix is None:
+ skip = "twistd unix not available"
+
+ def test_setupEnvironment(self):
+ """
+ L{UnixApplicationRunner.startApplication} calls
+ L{UnixApplicationRunner.setupEnvironment} with the chroot, rundir,
+ nodaemon, umask, and pidfile parameters from the configuration it is
+ constructed with.
+ """
+ options = twistd.ServerOptions()
+ options.parseOptions([
+ '--nodaemon',
+ '--umask', '0070',
+ '--chroot', '/foo/chroot',
+ '--rundir', '/foo/rundir',
+ '--pidfile', '/foo/pidfile'])
+ application = service.Application("test_setupEnvironment")
+ self.runner = UnixApplicationRunner(options)
+
+ args = []
+ def fakeSetupEnvironment(self, chroot, rundir, nodaemon, umask, pidfile):
+ args.extend((chroot, rundir, nodaemon, umask, pidfile))
+
+ # Sanity check
+ self.assertEqual(
+ inspect.getargspec(self.runner.setupEnvironment),
+ inspect.getargspec(fakeSetupEnvironment))
+
+ self.patch(UnixApplicationRunner, 'setupEnvironment', fakeSetupEnvironment)
+ self.patch(UnixApplicationRunner, 'shedPrivileges', lambda *a, **kw: None)
+ self.patch(app, 'startApplication', lambda *a, **kw: None)
+ self.runner.startApplication(application)
+
+ self.assertEqual(
+ args,
+ ['/foo/chroot', '/foo/rundir', True, 56, '/foo/pidfile'])
+
+
+
+class UnixApplicationRunnerRemovePID(unittest.TestCase):
+ """
+ Tests for L{UnixApplicationRunner.removePID}.
+ """
+ if _twistd_unix is None:
+ skip = "twistd unix not available"
+
+
+ def test_removePID(self):
+ """
+ L{UnixApplicationRunner.removePID} deletes the file the name of
+ which is passed to it.
+ """
+ runner = UnixApplicationRunner({})
+ path = self.mktemp()
+ os.makedirs(path)
+ pidfile = os.path.join(path, "foo.pid")
+ file(pidfile, "w").close()
+ runner.removePID(pidfile)
+ self.assertFalse(os.path.exists(pidfile))
+
+
+ def test_removePIDErrors(self):
+ """
+ Calling L{UnixApplicationRunner.removePID} with a non-existent filename logs
+ an OSError.
+ """
+ runner = UnixApplicationRunner({})
+ runner.removePID("fakepid")
+ errors = self.flushLoggedErrors(OSError)
+ self.assertEquals(len(errors), 1)
+ self.assertEquals(errors[0].value.errno, errno.ENOENT)
+
+
+
+class DummyReactor(object):
+ """
+ A dummy reactor, only providing a C{run} method and checking that it
+ has been called.
+
+ @ivar called: if C{run} has been called or not.
+ @type called: C{bool}
+ """
+ called = False
+
+ def run(self):
+ """
+ A fake run method, checking that it's been called one and only time.
+ """
+ if self.called:
+ raise RuntimeError("Already called")
+ self.called = True
+
+
+
+class AppProfilingTestCase(unittest.TestCase):
+ """
+ Tests for L{app.AppProfiler}.
+ """
+
+ def test_profile(self):
+ """
+ L{app.ProfileRunner.run} should call the C{run} method of the reactor
+ and save profile data in the specified file.
+ """
+ config = twistd.ServerOptions()
+ config["profile"] = self.mktemp()
+ config["profiler"] = "profile"
+ profiler = app.AppProfiler(config)
+ reactor = DummyReactor()
+
+ profiler.run(reactor)
+
+ self.assertTrue(reactor.called)
+ data = file(config["profile"]).read()
+ self.assertIn("DummyReactor.run", data)
+ self.assertIn("function calls", data)
+
+ if profile is None:
+ test_profile.skip = "profile module not available"
+
+
+ def _testStats(self, statsClass, profile):
+ out = StringIO.StringIO()
+
+ # Patch before creating the pstats, because pstats binds self.stream to
+ # sys.stdout early in 2.5 and newer.
+ stdout = self.patch(sys, 'stdout', out)
+
+ # If pstats.Stats can load the data and then reformat it, then the
+ # right thing probably happened.
+ stats = statsClass(profile)
+ stats.print_stats()
+ stdout.restore()
+
+ data = out.getvalue()
+ self.assertIn("function calls", data)
+ self.assertIn("(run)", data)
+
+
+ def test_profileSaveStats(self):
+ """
+ With the C{savestats} option specified, L{app.ProfileRunner.run}
+ should save the raw stats object instead of a summary output.
+ """
+ config = twistd.ServerOptions()
+ config["profile"] = self.mktemp()
+ config["profiler"] = "profile"
+ config["savestats"] = True
+ profiler = app.AppProfiler(config)
+ reactor = DummyReactor()
+
+ profiler.run(reactor)
+
+ self.assertTrue(reactor.called)
+ self._testStats(pstats.Stats, config['profile'])
+
+ if profile is None:
+ test_profileSaveStats.skip = "profile module not available"
+
+
+ def test_withoutProfile(self):
+ """
+ When the C{profile} module is not present, L{app.ProfilerRunner.run}
+ should raise a C{SystemExit} exception.
+ """
+ savedModules = sys.modules.copy()
+
+ config = twistd.ServerOptions()
+ config["profiler"] = "profile"
+ profiler = app.AppProfiler(config)
+
+ sys.modules["profile"] = None
+ try:
+ self.assertRaises(SystemExit, profiler.run, None)
+ finally:
+ sys.modules.clear()
+ sys.modules.update(savedModules)
+
+
+ def test_profilePrintStatsError(self):
+ """
+ When an error happens during the print of the stats, C{sys.stdout}
+ should be restored to its initial value.
+ """
+ class ErroneousProfile(profile.Profile):
+ def print_stats(self):
+ raise RuntimeError("Boom")
+ self.patch(profile, "Profile", ErroneousProfile)
+
+ config = twistd.ServerOptions()
+ config["profile"] = self.mktemp()
+ config["profiler"] = "profile"
+ profiler = app.AppProfiler(config)
+ reactor = DummyReactor()
+
+ oldStdout = sys.stdout
+ self.assertRaises(RuntimeError, profiler.run, reactor)
+ self.assertIdentical(sys.stdout, oldStdout)
+
+ if profile is None:
+ test_profilePrintStatsError.skip = "profile module not available"
+
+
+ def test_hotshot(self):
+ """
+ L{app.HotshotRunner.run} should call the C{run} method of the reactor
+ and save profile data in the specified file.
+ """
+ config = twistd.ServerOptions()
+ config["profile"] = self.mktemp()
+ config["profiler"] = "hotshot"
+ profiler = app.AppProfiler(config)
+ reactor = DummyReactor()
+
+ profiler.run(reactor)
+
+ self.assertTrue(reactor.called)
+ data = file(config["profile"]).read()
+ self.assertIn("run", data)
+ self.assertIn("function calls", data)
+
+ if hotshot is None:
+ test_hotshot.skip = "hotshot module not available"
+
+
+ def test_hotshotSaveStats(self):
+ """
+ With the C{savestats} option specified, L{app.HotshotRunner.run} should
+ save the raw stats object instead of a summary output.
+ """
+ config = twistd.ServerOptions()
+ config["profile"] = self.mktemp()
+ config["profiler"] = "hotshot"
+ config["savestats"] = True
+ profiler = app.AppProfiler(config)
+ reactor = DummyReactor()
+
+ profiler.run(reactor)
+
+ self.assertTrue(reactor.called)
+ self._testStats(hotshot.stats.load, config['profile'])
+
+ if hotshot is None:
+ test_hotshotSaveStats.skip = "hotshot module not available"
+
+
+ def test_withoutHotshot(self):
+ """
+ When the C{hotshot} module is not present, L{app.HotshotRunner.run}
+ should raise a C{SystemExit} exception and log the C{ImportError}.
+ """
+ savedModules = sys.modules.copy()
+ sys.modules["hotshot"] = None
+
+ config = twistd.ServerOptions()
+ config["profiler"] = "hotshot"
+ profiler = app.AppProfiler(config)
+ try:
+ self.assertRaises(SystemExit, profiler.run, None)
+ finally:
+ sys.modules.clear()
+ sys.modules.update(savedModules)
+
+
+ def test_nothotshotDeprecation(self):
+ """
+ Check that switching on the C{nothotshot} option produces a warning and
+ sets the profiler to B{profile}.
+ """
+ config = twistd.ServerOptions()
+ config['nothotshot'] = True
+ profiler = self.assertWarns(DeprecationWarning,
+ "The --nothotshot option is deprecated. Please specify the "
+ "profiler name using the --profiler option",
+ app.__file__, app.AppProfiler, config)
+ self.assertEquals(profiler.profiler, "profile")
+
+
+ def test_hotshotPrintStatsError(self):
+ """
+ When an error happens while printing the stats, C{sys.stdout}
+ should be restored to its initial value.
+ """
+ class ErroneousStats(pstats.Stats):
+ def print_stats(self):
+ raise RuntimeError("Boom")
+ self.patch(pstats, "Stats", ErroneousStats)
+
+ config = twistd.ServerOptions()
+ config["profile"] = self.mktemp()
+ config["profiler"] = "hotshot"
+ profiler = app.AppProfiler(config)
+ reactor = DummyReactor()
+
+ oldStdout = sys.stdout
+ self.assertRaises(RuntimeError, profiler.run, reactor)
+ self.assertIdentical(sys.stdout, oldStdout)
+
+ if hotshot is None:
+ test_hotshotPrintStatsError.skip = "hotshot module not available"
+
+
+ def test_cProfile(self):
+ """
+ L{app.CProfileRunner.run} should call the C{run} method of the
+ reactor and save profile data in the specified file.
+ """
+ config = twistd.ServerOptions()
+ config["profile"] = self.mktemp()
+ config["profiler"] = "cProfile"
+ profiler = app.AppProfiler(config)
+ reactor = DummyReactor()
+
+ profiler.run(reactor)
+
+ self.assertTrue(reactor.called)
+ data = file(config["profile"]).read()
+ self.assertIn("run", data)
+ self.assertIn("function calls", data)
+
+ if cProfile is None:
+ test_cProfile.skip = "cProfile module not available"
+
+
+ def test_cProfileSaveStats(self):
+ """
+ With the C{savestats} option specified,
+ L{app.CProfileRunner.run} should save the raw stats object
+ instead of a summary output.
+ """
+ config = twistd.ServerOptions()
+ config["profile"] = self.mktemp()
+ config["profiler"] = "cProfile"
+ config["savestats"] = True
+ profiler = app.AppProfiler(config)
+ reactor = DummyReactor()
+
+ profiler.run(reactor)
+
+ self.assertTrue(reactor.called)
+ self._testStats(pstats.Stats, config['profile'])
+
+ if cProfile is None:
+ test_cProfileSaveStats.skip = "cProfile module not available"
+
+
+ def test_withoutCProfile(self):
+ """
+ When the C{cProfile} module is not present,
+ L{app.CProfileRunner.run} should raise a C{SystemExit}
+ exception and log the C{ImportError}.
+ """
+ savedModules = sys.modules.copy()
+ sys.modules["cProfile"] = None
+
+ config = twistd.ServerOptions()
+ config["profiler"] = "cProfile"
+ profiler = app.AppProfiler(config)
+ try:
+ self.assertRaises(SystemExit, profiler.run, None)
+ finally:
+ sys.modules.clear()
+ sys.modules.update(savedModules)
+
+
+ def test_unknownProfiler(self):
+ """
+ Check that L{app.AppProfiler} raises L{SystemExit} when given an
+ unknown profiler name.
+ """
+ config = twistd.ServerOptions()
+ config["profile"] = self.mktemp()
+ config["profiler"] = "foobar"
+
+ error = self.assertRaises(SystemExit, app.AppProfiler, config)
+ self.assertEquals(str(error), "Unsupported profiler name: foobar")
+
+
+ def test_defaultProfiler(self):
+ """
+ L{app.Profiler} defaults to the hotshot profiler if not specified.
+ """
+ profiler = app.AppProfiler({})
+ self.assertEquals(profiler.profiler, "hotshot")
+
+
+ def test_profilerNameCaseInsentive(self):
+ """
+ The case of the profiler name passed to L{app.AppProfiler} is not
+ relevant.
+ """
+ profiler = app.AppProfiler({"profiler": "HotShot"})
+ self.assertEquals(profiler.profiler, "hotshot")
+
+
+ def test_oldRunWithProfiler(self):
+ """
+ L{app.runWithProfiler} should print a C{DeprecationWarning} pointing
+ at L{AppProfiler}.
+ """
+ class DummyProfiler(object):
+ called = False
+ def run(self, reactor):
+ self.called = True
+ profiler = DummyProfiler()
+ self.patch(app, "AppProfiler", lambda conf: profiler)
+
+ def runWithProfiler():
+ return app.runWithProfiler(DummyReactor(), {})
+
+ self.assertWarns(DeprecationWarning,
+ "runWithProfiler is deprecated since Twisted 8.0. "
+ "Use ProfileRunner instead.", __file__,
+ runWithProfiler)
+ self.assertTrue(profiler.called)
+
+
+ def test_oldRunWithHotshot(self):
+ """
+ L{app.runWithHotshot} should print a C{DeprecationWarning} pointing
+ at L{AppProfiler}.
+ """
+ class DummyProfiler(object):
+ called = False
+ def run(self, reactor):
+ self.called = True
+ profiler = DummyProfiler()
+ self.patch(app, "AppProfiler", lambda conf: profiler)
+
+ def runWithHotshot():
+ return app.runWithHotshot(DummyReactor(), {})
+
+ self.assertWarns(DeprecationWarning,
+ "runWithHotshot is deprecated since Twisted 8.0. "
+ "Use HotshotRunner instead.", __file__,
+ runWithHotshot)
+ self.assertTrue(profiler.called)
+
+
+
+def _patchFileLogObserver(patch):
+ """
+ Patch L{log.FileLogObserver} to record every call and keep a reference to
+ the passed log file for tests.
+
+ @param patch: a callback for patching (usually L{unittest.TestCase.patch}).
+
+ @return: the list that keeps track of the log files.
+ @rtype: C{list}
+ """
+ logFiles = []
+ oldFileLobObserver = log.FileLogObserver
+ def FileLogObserver(logFile):
+ logFiles.append(logFile)
+ return oldFileLobObserver(logFile)
+ patch(log, 'FileLogObserver', FileLogObserver)
+ return logFiles
+
+
+
+class AppLoggerTestCase(unittest.TestCase):
+ """
+ Tests for L{app.AppLogger}.
+
+ @ivar observers: list of observers installed during the tests.
+ @type observers: C{list}
+ """
+
+ def setUp(self):
+ """
+ Override L{log.addObserver} so that we can trace the observers
+ installed in C{self.observers}.
+ """
+ self.observers = []
+ def startLoggingWithObserver(observer):
+ self.observers.append(observer)
+ log.addObserver(observer)
+ self.patch(log, 'startLoggingWithObserver', startLoggingWithObserver)
+
+
+ def tearDown(self):
+ """
+ Remove all installed observers.
+ """
+ for observer in self.observers:
+ log.removeObserver(observer)
+
+
+ def _checkObserver(self, logs):
+ """
+ Ensure that initial C{twistd} logs are written to the given list.
+
+ @type logs: C{list}
+ @param logs: The list whose C{append} method was specified as the
+ initial log observer.
+ """
+ self.assertEquals(self.observers, [logs.append])
+ self.assertIn("starting up", logs[0]["message"][0])
+ self.assertIn("reactor class", logs[1]["message"][0])
+
+
+ def test_start(self):
+ """
+ L{app.AppLogger.start} calls L{log.addObserver}, and then writes some
+ messages about twistd and the reactor.
+ """
+ logger = app.AppLogger({})
+ observer = []
+ logger._getLogObserver = lambda: observer.append
+ logger.start(Componentized())
+ self._checkObserver(observer)
+
+
+ def test_startUsesApplicationLogObserver(self):
+ """
+ When the L{ILogObserver} component is available on the application,
+ that object will be used as the log observer instead of constructing a
+ new one.
+ """
+ application = Componentized()
+ logs = []
+ application.setComponent(ILogObserver, logs.append)
+ logger = app.AppLogger({})
+ logger.start(application)
+ self._checkObserver(logs)
+
+
+ def test_getLogObserverStdout(self):
+ """
+ When logfile is empty or set to C{-}, L{app.AppLogger._getLogObserver}
+ returns a log observer pointing at C{sys.stdout}.
+ """
+ logger = app.AppLogger({"logfile": "-"})
+ logFiles = _patchFileLogObserver(self.patch)
+
+ observer = logger._getLogObserver()
+
+ self.assertEquals(len(logFiles), 1)
+ self.assertIdentical(logFiles[0], sys.stdout)
+
+ logger = app.AppLogger({"logfile": ""})
+ observer = logger._getLogObserver()
+
+ self.assertEquals(len(logFiles), 2)
+ self.assertIdentical(logFiles[1], sys.stdout)
+
+
+ def test_getLogObserverFile(self):
+ """
+ When passing the C{logfile} option, L{app.AppLogger._getLogObserver}
+ returns a log observer pointing at the specified path.
+ """
+ logFiles = _patchFileLogObserver(self.patch)
+ filename = self.mktemp()
+ logger = app.AppLogger({"logfile": filename})
+
+ observer = logger._getLogObserver()
+
+ self.assertEquals(len(logFiles), 1)
+ self.assertEquals(logFiles[0].path,
+ os.path.abspath(filename))
+
+
+ def test_stop(self):
+ """
+ L{app.AppLogger.stop} removes the observer created in C{start}, and
+ reinitialize its C{_observer} so that if C{stop} is called several
+ times it doesn't break.
+ """
+ removed = []
+ observer = object()
+ def remove(observer):
+ removed.append(observer)
+ self.patch(log, 'removeObserver', remove)
+ logger = app.AppLogger({})
+ logger._observer = observer
+ logger.stop()
+ self.assertEquals(removed, [observer])
+ logger.stop()
+ self.assertEquals(removed, [observer])
+ self.assertIdentical(logger._observer, None)
+
+
+
+class UnixAppLoggerTestCase(unittest.TestCase):
+ """
+ Tests for L{UnixAppLogger}.
+
+ @ivar signals: list of signal handlers installed.
+ @type signals: C{list}
+ """
+ if _twistd_unix is None:
+ skip = "twistd unix not available"
+
+ def setUp(self):
+ """
+ Fake C{signal.signal} for not installing the handlers but saving them
+ in C{self.signals}.
+ """
+ self.signals = []
+ def fakeSignal(sig, f):
+ self.signals.append((sig, f))
+ self.patch(signal, "signal", fakeSignal)
+
+
+ def test_getLogObserverStdout(self):
+ """
+ When non-daemonized and C{logfile} is empty or set to C{-},
+ L{UnixAppLogger._getLogObserver} returns a log observer pointing at
+ C{sys.stdout}.
+ """
+ logFiles = _patchFileLogObserver(self.patch)
+
+ logger = UnixAppLogger({"logfile": "-", "nodaemon": True})
+ observer = logger._getLogObserver()
+ self.assertEquals(len(logFiles), 1)
+ self.assertIdentical(logFiles[0], sys.stdout)
+
+ logger = UnixAppLogger({"logfile": "", "nodaemon": True})
+ observer = logger._getLogObserver()
+ self.assertEquals(len(logFiles), 2)
+ self.assertIdentical(logFiles[1], sys.stdout)
+
+
+ def test_getLogObserverStdoutDaemon(self):
+ """
+ When daemonized and C{logfile} is set to C{-},
+ L{UnixAppLogger._getLogObserver} raises C{SystemExit}.
+ """
+ logger = UnixAppLogger({"logfile": "-", "nodaemon": False})
+ error = self.assertRaises(SystemExit, logger._getLogObserver)
+ self.assertEquals(str(error), "Daemons cannot log to stdout, exiting!")
+
+
+ def test_getLogObserverFile(self):
+ """
+ When C{logfile} contains a file name, L{app.AppLogger._getLogObserver}
+ returns a log observer pointing at the specified path, and a signal
+ handler rotating the log is installed.
+ """
+ logFiles = _patchFileLogObserver(self.patch)
+ filename = self.mktemp()
+ logger = UnixAppLogger({"logfile": filename})
+ observer = logger._getLogObserver()
+
+ self.assertEquals(len(logFiles), 1)
+ self.assertEquals(logFiles[0].path,
+ os.path.abspath(filename))
+
+ self.assertEquals(len(self.signals), 1)
+ self.assertEquals(self.signals[0][0], signal.SIGUSR1)
+
+ d = Deferred()
+ def rotate():
+ d.callback(None)
+ logFiles[0].rotate = rotate
+
+ rotateLog = self.signals[0][1]
+ rotateLog(None, None)
+ return d
+
+
+ def test_getLogObserverDontOverrideSignalHandler(self):
+ """
+ If a signal handler is already installed,
+ L{UnixAppLogger._getLogObserver} doesn't override it.
+ """
+ def fakeGetSignal(sig):
+ self.assertEquals(sig, signal.SIGUSR1)
+ return object()
+ self.patch(signal, "getsignal", fakeGetSignal)
+ filename = self.mktemp()
+ logger = UnixAppLogger({"logfile": filename})
+ observer = logger._getLogObserver()
+
+ self.assertEquals(self.signals, [])
+
+
+ def test_getLogObserverDefaultFile(self):
+ """
+ When daemonized and C{logfile} is empty, the observer returned by
+ L{UnixAppLogger._getLogObserver} points at C{twistd.log} in the current
+ directory.
+ """
+ logFiles = _patchFileLogObserver(self.patch)
+ logger = UnixAppLogger({"logfile": "", "nodaemon": False})
+ observer = logger._getLogObserver()
+
+ self.assertEquals(len(logFiles), 1)
+ self.assertEquals(logFiles[0].path,
+ os.path.abspath("twistd.log"))
+
+
+ def test_getLogObserverSyslog(self):
+ """
+ If C{syslog} is set to C{True}, L{UnixAppLogger._getLogObserver} starts
+ a L{syslog.SyslogObserver} with given C{prefix}.
+ """
+ class fakesyslogobserver(object):
+ def __init__(self, prefix):
+ fakesyslogobserver.prefix = prefix
+ def emit(self, eventDict):
+ pass
+ self.patch(syslog, "SyslogObserver", fakesyslogobserver)
+ logger = UnixAppLogger({"syslog": True, "prefix": "test-prefix"})
+ observer = logger._getLogObserver()
+ self.assertEquals(fakesyslogobserver.prefix, "test-prefix")
+
+ if syslog is None:
+ test_getLogObserverSyslog.skip = "Syslog not available"
+
+
+
+class DeprecationTests(unittest.TestCase):
+ """
+ Tests for deprecated features.
+ """
+
+ def test_initialLog(self):
+ """
+ L{app.initialLog} is deprecated.
+ """
+ logs = []
+ log.addObserver(logs.append)
+ self.addCleanup(log.removeObserver, logs.append)
+ self.callDeprecated(Version("Twisted", 8, 2, 0), app.initialLog)
+ self.assertEquals(len(logs), 2)
+ self.assertIn("starting up", logs[0]["message"][0])
+ self.assertIn("reactor class", logs[1]["message"][0])
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_udp.py b/vendor/Twisted-10.0.0/twisted/test/test_udp.py
new file mode 100644
index 0000000000..119c3ccf33
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_udp.py
@@ -0,0 +1,661 @@
+# -*- test-case-name: twisted.test.test_udp -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorUDP} and L{IReactorMulticast}.
+"""
+
+from twisted.trial import unittest, util
+
+from twisted.internet.defer import Deferred, gatherResults, maybeDeferred
+from twisted.internet import protocol, reactor, error, defer, interfaces
+from twisted.python import runtime
+
+
+class Mixin:
+
+ started = 0
+ stopped = 0
+
+ startedDeferred = None
+
+ def __init__(self):
+ self.packets = []
+
+ def startProtocol(self):
+ self.started = 1
+ if self.startedDeferred is not None:
+ d, self.startedDeferred = self.startedDeferred, None
+ d.callback(None)
+
+ def stopProtocol(self):
+ self.stopped = 1
+
+
+class Server(Mixin, protocol.DatagramProtocol):
+ packetReceived = None
+ refused = 0
+
+
+ def datagramReceived(self, data, addr):
+ self.packets.append((data, addr))
+ if self.packetReceived is not None:
+ d, self.packetReceived = self.packetReceived, None
+ d.callback(None)
+
+
+
+class Client(Mixin, protocol.ConnectedDatagramProtocol):
+
+ packetReceived = None
+ refused = 0
+
+ def datagramReceived(self, data):
+ self.packets.append(data)
+ if self.packetReceived is not None:
+ d, self.packetReceived = self.packetReceived, None
+ d.callback(None)
+
+ def connectionFailed(self, failure):
+ if self.startedDeferred is not None:
+ d, self.startedDeferred = self.startedDeferred, None
+ d.errback(failure)
+ self.failure = failure
+
+ def connectionRefused(self):
+ if self.startedDeferred is not None:
+ d, self.startedDeferred = self.startedDeferred, None
+ d.errback(error.ConnectionRefusedError("yup"))
+ self.refused = 1
+
+
+class GoodClient(Server):
+
+ def connectionRefused(self):
+ if self.startedDeferred is not None:
+ d, self.startedDeferred = self.startedDeferred, None
+ d.errback(error.ConnectionRefusedError("yup"))
+ self.refused = 1
+
+
+
+class BadClientError(Exception):
+ """
+ Raised by BadClient at the end of every datagramReceived call to try and
+ screw stuff up.
+ """
+
+
+
+class BadClient(protocol.DatagramProtocol):
+ """
+ A DatagramProtocol which always raises an exception from datagramReceived.
+ Used to test error handling behavior in the reactor for that method.
+ """
+ d = None
+
+ def setDeferred(self, d):
+ """
+ Set the Deferred which will be called back when datagramReceived is
+ called.
+ """
+ self.d = d
+
+
+ def datagramReceived(self, bytes, addr):
+ if self.d is not None:
+ d, self.d = self.d, None
+ d.callback(bytes)
+ raise BadClientError("Application code is very buggy!")
+
+
+
+class UDPTestCase(unittest.TestCase):
+
+ def testOldAddress(self):
+ server = Server()
+ d = server.startedDeferred = defer.Deferred()
+ p = reactor.listenUDP(0, server, interface="127.0.0.1")
+ def cbStarted(ignored):
+ addr = p.getHost()
+ self.assertEquals(addr, ('INET_UDP', addr.host, addr.port))
+ return p.stopListening()
+ return d.addCallback(cbStarted)
+ testOldAddress.suppress = [
+ util.suppress(message='IPv4Address.__getitem__',
+ category=DeprecationWarning)]
+
+
+ def testStartStop(self):
+ server = Server()
+ d = server.startedDeferred = defer.Deferred()
+ port1 = reactor.listenUDP(0, server, interface="127.0.0.1")
+ def cbStarted(ignored):
+ self.assertEquals(server.started, 1)
+ self.assertEquals(server.stopped, 0)
+ return port1.stopListening()
+ def cbStopped(ignored):
+ self.assertEquals(server.stopped, 1)
+ return d.addCallback(cbStarted).addCallback(cbStopped)
+
+ def testRebind(self):
+ # Ensure binding the same DatagramProtocol repeatedly invokes all
+ # the right callbacks.
+ server = Server()
+ d = server.startedDeferred = defer.Deferred()
+ p = reactor.listenUDP(0, server, interface="127.0.0.1")
+
+ def cbStarted(ignored, port):
+ return port.stopListening()
+
+ def cbStopped(ignored):
+ d = server.startedDeferred = defer.Deferred()
+ p = reactor.listenUDP(0, server, interface="127.0.0.1")
+ return d.addCallback(cbStarted, p)
+
+ return d.addCallback(cbStarted, p)
+
+
+ def testBindError(self):
+ server = Server()
+ d = server.startedDeferred = defer.Deferred()
+ port = reactor.listenUDP(0, server, interface='127.0.0.1')
+
+ def cbStarted(ignored):
+ self.assertEquals(port.getHost(), server.transport.getHost())
+
+ server2 = Server()
+ self.assertRaises(
+ error.CannotListenError,
+ reactor.listenUDP, port.getHost().port, server2,
+ interface='127.0.0.1')
+ d.addCallback(cbStarted)
+
+ def cbFinished(ignored):
+ return port.stopListening()
+ d.addCallback(cbFinished)
+ return d
+
+ def testSendPackets(self):
+ server = Server()
+ serverStarted = server.startedDeferred = defer.Deferred()
+ port1 = reactor.listenUDP(0, server, interface="127.0.0.1")
+
+ client = GoodClient()
+ clientStarted = client.startedDeferred = defer.Deferred()
+
+ def cbServerStarted(ignored):
+ self.port2 = reactor.listenUDP(0, client, interface="127.0.0.1")
+ return clientStarted
+
+ d = serverStarted.addCallback(cbServerStarted)
+
+ def cbClientStarted(ignored):
+ client.transport.connect("127.0.0.1",
+ server.transport.getHost().port)
+ cAddr = client.transport.getHost()
+ sAddr = server.transport.getHost()
+
+ serverSend = client.packetReceived = defer.Deferred()
+ server.transport.write("hello", (cAddr.host, cAddr.port))
+
+ clientWrites = [
+ ("a",),
+ ("b", None),
+ ("c", (sAddr.host, sAddr.port))]
+
+ def cbClientSend(ignored):
+ if clientWrites:
+ nextClientWrite = server.packetReceived = defer.Deferred()
+ nextClientWrite.addCallback(cbClientSend)
+ client.transport.write(*clientWrites.pop(0))
+ return nextClientWrite
+
+ # No one will ever call .errback on either of these Deferreds,
+ # but there is a non-trivial amount of test code which might
+ # cause them to fail somehow. So fireOnOneErrback=True.
+ return defer.DeferredList([
+ cbClientSend(None),
+ serverSend],
+ fireOnOneErrback=True)
+
+ d.addCallback(cbClientStarted)
+
+ def cbSendsFinished(ignored):
+ cAddr = client.transport.getHost()
+ sAddr = server.transport.getHost()
+ self.assertEquals(
+ client.packets,
+ [("hello", (sAddr.host, sAddr.port))])
+ clientAddr = (cAddr.host, cAddr.port)
+ self.assertEquals(
+ server.packets,
+ [("a", clientAddr),
+ ("b", clientAddr),
+ ("c", clientAddr)])
+
+ d.addCallback(cbSendsFinished)
+
+ def cbFinished(ignored):
+ return defer.DeferredList([
+ defer.maybeDeferred(port1.stopListening),
+ defer.maybeDeferred(self.port2.stopListening)],
+ fireOnOneErrback=True)
+
+ d.addCallback(cbFinished)
+ return d
+
+
+ def testConnectionRefused(self):
+ # assume no one listening on port 80 UDP
+ client = GoodClient()
+ clientStarted = client.startedDeferred = defer.Deferred()
+ port = reactor.listenUDP(0, client, interface="127.0.0.1")
+
+ server = Server()
+ serverStarted = server.startedDeferred = defer.Deferred()
+ port2 = reactor.listenUDP(0, server, interface="127.0.0.1")
+
+ d = defer.DeferredList(
+ [clientStarted, serverStarted],
+ fireOnOneErrback=True)
+
+ def cbStarted(ignored):
+ connectionRefused = client.startedDeferred = defer.Deferred()
+ client.transport.connect("127.0.0.1", 80)
+
+ for i in range(10):
+ client.transport.write(str(i))
+ server.transport.write(str(i), ("127.0.0.1", 80))
+
+ return self.assertFailure(
+ connectionRefused,
+ error.ConnectionRefusedError)
+
+ d.addCallback(cbStarted)
+
+ def cbFinished(ignored):
+ return defer.DeferredList([
+ defer.maybeDeferred(port.stopListening),
+ defer.maybeDeferred(port2.stopListening)],
+ fireOnOneErrback=True)
+
+ d.addCallback(cbFinished)
+ return d
+
+ def testBadConnect(self):
+ client = GoodClient()
+ port = reactor.listenUDP(0, client, interface="127.0.0.1")
+ self.assertRaises(ValueError, client.transport.connect,
+ "localhost", 80)
+ client.transport.connect("127.0.0.1", 80)
+ self.assertRaises(RuntimeError, client.transport.connect,
+ "127.0.0.1", 80)
+ return port.stopListening()
+
+
+
+ def testDatagramReceivedError(self):
+ """
+ Test that when datagramReceived raises an exception it is logged but
+ the port is not disconnected.
+ """
+ finalDeferred = defer.Deferred()
+
+ def cbCompleted(ign):
+ """
+ Flush the exceptions which the reactor should have logged and make
+ sure they're actually there.
+ """
+ errs = self.flushLoggedErrors(BadClientError)
+ self.assertEquals(len(errs), 2, "Incorrectly found %d errors, expected 2" % (len(errs),))
+ finalDeferred.addCallback(cbCompleted)
+
+ client = BadClient()
+ port = reactor.listenUDP(0, client, interface='127.0.0.1')
+
+ def cbCleanup(result):
+ """
+ Disconnect the port we started and pass on whatever was given to us
+ in case it was a Failure.
+ """
+ return defer.maybeDeferred(port.stopListening).addBoth(lambda ign: result)
+ finalDeferred.addBoth(cbCleanup)
+
+ addr = port.getHost()
+
+ # UDP is not reliable. Try to send as many as 60 packets before giving
+ # up. Conceivably, all sixty could be lost, but they probably won't be
+ # unless all UDP traffic is being dropped, and then the rest of these
+ # UDP tests will likely fail as well. Ideally, this test (and probably
+ # others) wouldn't even use actual UDP traffic: instead, they would
+ # stub out the socket with a fake one which could be made to behave in
+ # whatever way the test desires. Unfortunately, this is hard because
+ # of differences in various reactor implementations.
+ attempts = range(60)
+ succeededAttempts = []
+
+ def makeAttempt():
+ """
+ Send one packet to the listening BadClient. Set up a 0.1 second
+ timeout to do re-transmits in case the packet is dropped. When two
+ packets have been received by the BadClient, stop sending and let
+ the finalDeferred's callbacks do some assertions.
+ """
+ if not attempts:
+ try:
+ self.fail("Not enough packets received")
+ except:
+ finalDeferred.errback()
+
+ self.failIfIdentical(client.transport, None, "UDP Protocol lost its transport")
+
+ packet = str(attempts.pop(0))
+ packetDeferred = defer.Deferred()
+ client.setDeferred(packetDeferred)
+ client.transport.write(packet, (addr.host, addr.port))
+
+ def cbPacketReceived(packet):
+ """
+ A packet arrived. Cancel the timeout for it, record it, and
+ maybe finish the test.
+ """
+ timeoutCall.cancel()
+ succeededAttempts.append(packet)
+ if len(succeededAttempts) == 2:
+ # The second error has not yet been logged, since the
+ # exception which causes it hasn't even been raised yet.
+ # Give the datagramReceived call a chance to finish, then
+ # let the test finish asserting things.
+ reactor.callLater(0, finalDeferred.callback, None)
+ else:
+ makeAttempt()
+
+ def ebPacketTimeout(err):
+ """
+ The packet wasn't received quickly enough. Try sending another
+ one. It doesn't matter if the packet for which this was the
+ timeout eventually arrives: makeAttempt throws away the
+ Deferred on which this function is the errback, so when
+ datagramReceived callbacks, so it won't be on this Deferred, so
+ it won't raise an AlreadyCalledError.
+ """
+ makeAttempt()
+
+ packetDeferred.addCallbacks(cbPacketReceived, ebPacketTimeout)
+ packetDeferred.addErrback(finalDeferred.errback)
+
+ timeoutCall = reactor.callLater(
+ 0.1, packetDeferred.errback,
+ error.TimeoutError(
+ "Timed out in testDatagramReceivedError"))
+
+ makeAttempt()
+ return finalDeferred
+
+
+ def testPortRepr(self):
+ client = GoodClient()
+ p = reactor.listenUDP(0, client)
+ portNo = str(p.getHost().port)
+ self.failIf(repr(p).find(portNo) == -1)
+ def stoppedListening(ign):
+ self.failIf(repr(p).find(portNo) != -1)
+ d = defer.maybeDeferred(p.stopListening)
+ d.addCallback(stoppedListening)
+ return d
+
+
+class ReactorShutdownInteraction(unittest.TestCase):
+ """Test reactor shutdown interaction"""
+
+ def setUp(self):
+ """Start a UDP port"""
+ self.server = Server()
+ self.port = reactor.listenUDP(0, self.server, interface='127.0.0.1')
+
+ def tearDown(self):
+ """Stop the UDP port"""
+ return self.port.stopListening()
+
+ def testShutdownFromDatagramReceived(self):
+ """Test reactor shutdown while in a recvfrom() loop"""
+
+ # udp.Port's doRead calls recvfrom() in a loop, as an optimization.
+ # It is important this loop terminate under various conditions.
+ # Previously, if datagramReceived synchronously invoked
+ # reactor.stop(), under certain reactors, the Port's socket would
+ # synchronously disappear, causing an AttributeError inside that
+ # loop. This was mishandled, causing the loop to spin forever.
+ # This test is primarily to ensure that the loop never spins
+ # forever.
+
+ finished = defer.Deferred()
+ pr = self.server.packetReceived = defer.Deferred()
+
+ def pktRece(ignored):
+ # Simulate reactor.stop() behavior :(
+ self.server.transport.connectionLost()
+ # Then delay this Deferred chain until the protocol has been
+ # disconnected, as the reactor should do in an error condition
+ # such as we are inducing. This is very much a whitebox test.
+ reactor.callLater(0, finished.callback, None)
+ pr.addCallback(pktRece)
+
+ def flushErrors(ignored):
+ # We are breaking abstraction and calling private APIs, any
+ # number of horrible errors might occur. As long as the reactor
+ # doesn't hang, this test is satisfied. (There may be room for
+ # another, stricter test.)
+ self.flushLoggedErrors()
+ finished.addCallback(flushErrors)
+ self.server.transport.write('\0' * 64, ('127.0.0.1',
+ self.server.transport.getHost().port))
+ return finished
+
+
+
+class MulticastTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.server = Server()
+ self.client = Client()
+ # multicast won't work if we listen over loopback, apparently
+ self.port1 = reactor.listenMulticast(0, self.server)
+ self.port2 = reactor.listenMulticast(0, self.client)
+ self.client.transport.connect(
+ "127.0.0.1", self.server.transport.getHost().port)
+
+
+ def tearDown(self):
+ return gatherResults([
+ maybeDeferred(self.port1.stopListening),
+ maybeDeferred(self.port2.stopListening)])
+
+
+ def testTTL(self):
+ for o in self.client, self.server:
+ self.assertEquals(o.transport.getTTL(), 1)
+ o.transport.setTTL(2)
+ self.assertEquals(o.transport.getTTL(), 2)
+
+
+ def test_loopback(self):
+ """
+ Test that after loopback mode has been set, multicast packets are
+ delivered to their sender.
+ """
+ self.assertEquals(self.server.transport.getLoopbackMode(), 1)
+ addr = self.server.transport.getHost()
+ joined = self.server.transport.joinGroup("225.0.0.250")
+
+ def cbJoined(ignored):
+ d = self.server.packetReceived = Deferred()
+ self.server.transport.write("hello", ("225.0.0.250", addr.port))
+ return d
+ joined.addCallback(cbJoined)
+
+ def cbPacket(ignored):
+ self.assertEqual(len(self.server.packets), 1)
+ self.server.transport.setLoopbackMode(0)
+ self.assertEquals(self.server.transport.getLoopbackMode(), 0)
+ self.server.transport.write("hello", ("225.0.0.250", addr.port))
+
+ # This is fairly lame.
+ d = Deferred()
+ reactor.callLater(0, d.callback, None)
+ return d
+ joined.addCallback(cbPacket)
+
+ def cbNoPacket(ignored):
+ self.assertEqual(len(self.server.packets), 1)
+ joined.addCallback(cbNoPacket)
+
+ return joined
+
+
+ def test_interface(self):
+ """
+ Test C{getOutgoingInterface} and C{setOutgoingInterface}.
+ """
+ self.assertEqual(
+ self.client.transport.getOutgoingInterface(), "0.0.0.0")
+ self.assertEqual(
+ self.server.transport.getOutgoingInterface(), "0.0.0.0")
+
+ d1 = self.client.transport.setOutgoingInterface("127.0.0.1")
+ d2 = self.server.transport.setOutgoingInterface("127.0.0.1")
+ result = gatherResults([d1, d2])
+
+ def cbInterfaces(ignored):
+ self.assertEqual(
+ self.client.transport.getOutgoingInterface(), "127.0.0.1")
+ self.assertEqual(
+ self.server.transport.getOutgoingInterface(), "127.0.0.1")
+ result.addCallback(cbInterfaces)
+ return result
+
+
+ def test_joinLeave(self):
+ """
+ Test that multicast a group can be joined and left.
+ """
+ d = self.client.transport.joinGroup("225.0.0.250")
+
+ def clientJoined(ignored):
+ return self.client.transport.leaveGroup("225.0.0.250")
+ d.addCallback(clientJoined)
+
+ def clientLeft(ignored):
+ return self.server.transport.joinGroup("225.0.0.250")
+ d.addCallback(clientLeft)
+
+ def serverJoined(ignored):
+ return self.server.transport.leaveGroup("225.0.0.250")
+ d.addCallback(serverJoined)
+
+ return d
+
+
+ def test_joinFailure(self):
+ """
+ Test that an attempt to join an address which is not a multicast
+ address fails with L{error.MulticastJoinError}.
+ """
+ # 127.0.0.1 is not a multicast address, so joining it should fail.
+ return self.assertFailure(
+ self.client.transport.joinGroup("127.0.0.1"),
+ error.MulticastJoinError)
+ if runtime.platform.isWindows() and not runtime.platform.isVista():
+ test_joinFailure.todo = "Windows' multicast is wonky"
+
+
+ def test_multicast(self):
+ """
+ Test that a multicast group can be joined and messages sent to and
+ received from it.
+ """
+ c = Server()
+ p = reactor.listenMulticast(0, c)
+ addr = self.server.transport.getHost()
+
+ joined = self.server.transport.joinGroup("225.0.0.250")
+
+ def cbJoined(ignored):
+ d = self.server.packetReceived = Deferred()
+ c.transport.write("hello world", ("225.0.0.250", addr.port))
+ return d
+ joined.addCallback(cbJoined)
+
+ def cbPacket(ignored):
+ self.assertEquals(self.server.packets[0][0], "hello world")
+ joined.addCallback(cbPacket)
+
+ def cleanup(passthrough):
+ result = maybeDeferred(p.stopListening)
+ result.addCallback(lambda ign: passthrough)
+ return result
+ joined.addCallback(cleanup)
+
+ return joined
+
+
+ def test_multiListen(self):
+ """
+ Test that multiple sockets can listen on the same multicast port and
+ that they both receive multicast messages directed to that address.
+ """
+ firstClient = Server()
+ firstPort = reactor.listenMulticast(
+ 0, firstClient, listenMultiple=True)
+
+ portno = firstPort.getHost().port
+
+ secondClient = Server()
+ secondPort = reactor.listenMulticast(
+ portno, secondClient, listenMultiple=True)
+
+ joined = self.server.transport.joinGroup("225.0.0.250")
+
+ def serverJoined(ignored):
+ d1 = firstClient.packetReceived = Deferred()
+ d2 = secondClient.packetReceived = Deferred()
+ firstClient.transport.write("hello world", ("225.0.0.250", portno))
+ return gatherResults([d1, d2])
+ joined.addCallback(serverJoined)
+
+ def gotPackets(ignored):
+ self.assertEquals(firstClient.packets[0][0], "hello world")
+ self.assertEquals(secondClient.packets[0][0], "hello world")
+ joined.addCallback(gotPackets)
+
+ def cleanup(passthrough):
+ result = gatherResults([
+ maybeDeferred(firstPort.stopListening),
+ maybeDeferred(secondPort.stopListening)])
+ result.addCallback(lambda ign: passthrough)
+ return result
+ joined.addBoth(cleanup)
+ return joined
+ if runtime.platform.isWindows():
+ test_multiListen.skip = ("on non-linux platforms it appears multiple "
+ "processes can listen, but not multiple sockets "
+ "in same process?")
+
+if not interfaces.IReactorUDP(reactor, None):
+ UDPTestCase.skip = "This reactor does not support UDP"
+ ReactorShutdownInteraction.skip = "This reactor does not support UDP"
+if not interfaces.IReactorMulticast(reactor, None):
+ MulticastTestCase.skip = "This reactor does not support multicast"
+
+def checkForLinux22():
+ import os
+ if os.path.exists("/proc/version"):
+ s = open("/proc/version").read()
+ if s.startswith("Linux version"):
+ s = s.split()[2]
+ if s.split(".")[:2] == ["2", "2"]:
+ f = MulticastTestCase.testInterface.im_func
+ f.todo = "figure out why this fails in linux 2.2"
+checkForLinux22()
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_unix.py b/vendor/Twisted-10.0.0/twisted/test/test_unix.py
new file mode 100644
index 0000000000..de4a277c94
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_unix.py
@@ -0,0 +1,405 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for implementations of L{IReactorUNIX} and L{IReactorUNIXDatagram}.
+"""
+
+import stat, os, sys, types
+import socket
+
+from twisted.internet import interfaces, reactor, protocol, error, address, defer, utils
+from twisted.python import lockfile
+from twisted.trial import unittest
+
+from twisted.test.test_tcp import MyServerFactory, MyClientFactory
+
+
+class FailedConnectionClientFactory(protocol.ClientFactory):
+ def __init__(self, onFail):
+ self.onFail = onFail
+
+ def clientConnectionFailed(self, connector, reason):
+ self.onFail.errback(reason)
+
+
+
+class UnixSocketTestCase(unittest.TestCase):
+ """
+ Test unix sockets.
+ """
+ def test_peerBind(self):
+ """
+ The address passed to the server factory's C{buildProtocol} method and
+ the address returned by the connected protocol's transport's C{getPeer}
+ method match the address the client socket is bound to.
+ """
+ filename = self.mktemp()
+ peername = self.mktemp()
+ serverFactory = MyServerFactory()
+ connMade = serverFactory.protocolConnectionMade = defer.Deferred()
+ unixPort = reactor.listenUNIX(filename, serverFactory)
+ self.addCleanup(unixPort.stopListening)
+ unixSocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.addCleanup(unixSocket.close)
+ unixSocket.bind(peername)
+ unixSocket.connect(filename)
+ def cbConnMade(proto):
+ expected = address.UNIXAddress(peername)
+ self.assertEqual(serverFactory.peerAddresses, [expected])
+ self.assertEqual(proto.transport.getPeer(), expected)
+ connMade.addCallback(cbConnMade)
+ return connMade
+
+
+ def test_dumber(self):
+ """
+ L{IReactorUNIX.connectUNIX} can be used to connect a client to a server
+ started with L{IReactorUNIX.listenUNIX}.
+ """
+ filename = self.mktemp()
+ serverFactory = MyServerFactory()
+ serverConnMade = defer.Deferred()
+ serverFactory.protocolConnectionMade = serverConnMade
+ unixPort = reactor.listenUNIX(filename, serverFactory)
+ self.addCleanup(unixPort.stopListening)
+ clientFactory = MyClientFactory()
+ clientConnMade = defer.Deferred()
+ clientFactory.protocolConnectionMade = clientConnMade
+ c = reactor.connectUNIX(filename, clientFactory)
+ d = defer.gatherResults([serverConnMade, clientConnMade])
+ def allConnected((serverProtocol, clientProtocol)):
+
+ # Incidental assertion which may or may not be redundant with some
+ # other test. This probably deserves its own test method.
+ self.assertEqual(clientFactory.peerAddresses,
+ [address.UNIXAddress(filename)])
+
+ clientProtocol.transport.loseConnection()
+ serverProtocol.transport.loseConnection()
+ d.addCallback(allConnected)
+ return d
+
+
+ def test_pidFile(self):
+ """
+ A lockfile is created and locked when L{IReactorUNIX.listenUNIX} is
+ called and released when the Deferred returned by the L{IListeningPort}
+ provider's C{stopListening} method is called back.
+ """
+ filename = self.mktemp()
+ serverFactory = MyServerFactory()
+ serverConnMade = defer.Deferred()
+ serverFactory.protocolConnectionMade = serverConnMade
+ unixPort = reactor.listenUNIX(filename, serverFactory, wantPID=True)
+ self.assertTrue(lockfile.isLocked(filename + ".lock"))
+
+ # XXX This part would test something about the checkPID parameter, but
+ # it doesn't actually. It should be rewritten to test the several
+ # different possible behaviors. -exarkun
+ clientFactory = MyClientFactory()
+ clientConnMade = defer.Deferred()
+ clientFactory.protocolConnectionMade = clientConnMade
+ c = reactor.connectUNIX(filename, clientFactory, checkPID=1)
+
+ d = defer.gatherResults([serverConnMade, clientConnMade])
+ def _portStuff((serverProtocol, clientProto)):
+
+ # Incidental assertion which may or may not be redundant with some
+ # other test. This probably deserves its own test method.
+ self.assertEqual(clientFactory.peerAddresses,
+ [address.UNIXAddress(filename)])
+
+ clientProto.transport.loseConnection()
+ serverProtocol.transport.loseConnection()
+ return unixPort.stopListening()
+ d.addCallback(_portStuff)
+
+ def _check(ignored):
+ self.failIf(lockfile.isLocked(filename + ".lock"), 'locked')
+ d.addCallback(_check)
+ return d
+
+
+ def test_socketLocking(self):
+ """
+ L{IReactorUNIX.listenUNIX} raises L{error.CannotListenError} if passed
+ the name of a file on which a server is already listening.
+ """
+ filename = self.mktemp()
+ serverFactory = MyServerFactory()
+ unixPort = reactor.listenUNIX(filename, serverFactory, wantPID=True)
+
+ self.assertRaises(
+ error.CannotListenError,
+ reactor.listenUNIX, filename, serverFactory, wantPID=True)
+
+ def stoppedListening(ign):
+ unixPort = reactor.listenUNIX(filename, serverFactory, wantPID=True)
+ return unixPort.stopListening()
+
+ return unixPort.stopListening().addCallback(stoppedListening)
+
+
+ def _uncleanSocketTest(self, callback):
+ self.filename = self.mktemp()
+ source = ("from twisted.internet import protocol, reactor\n"
+ "reactor.listenUNIX(%r, protocol.ServerFactory(), wantPID=True)\n") % (self.filename,)
+ env = {'PYTHONPATH': os.pathsep.join(sys.path)}
+
+ d = utils.getProcessValue(sys.executable, ("-u", "-c", source), env=env)
+ d.addCallback(callback)
+ return d
+
+
+ def test_uncleanServerSocketLocking(self):
+ """
+ If passed C{True} for the C{wantPID} parameter, a server can be started
+ listening with L{IReactorUNIX.listenUNIX} when passed the name of a
+ file on which a previous server which has not exited cleanly has been
+ listening using the C{wantPID} option.
+ """
+ def ranStupidChild(ign):
+ # If this next call succeeds, our lock handling is correct.
+ p = reactor.listenUNIX(self.filename, MyServerFactory(), wantPID=True)
+ return p.stopListening()
+ return self._uncleanSocketTest(ranStupidChild)
+
+
+ def test_connectToUncleanServer(self):
+ """
+ If passed C{True} for the C{checkPID} parameter, a client connection
+ attempt made with L{IReactorUNIX.connectUNIX} fails with
+ L{error.BadFileError}.
+ """
+ def ranStupidChild(ign):
+ d = defer.Deferred()
+ f = FailedConnectionClientFactory(d)
+ c = reactor.connectUNIX(self.filename, f, checkPID=True)
+ return self.assertFailure(d, error.BadFileError)
+ return self._uncleanSocketTest(ranStupidChild)
+
+
+ def _reprTest(self, serverFactory, factoryName):
+ """
+ Test the C{__str__} and C{__repr__} implementations of a UNIX port when
+ used with the given factory.
+ """
+ filename = self.mktemp()
+ unixPort = reactor.listenUNIX(filename, serverFactory)
+
+ connectedString = "<%s on %r>" % (factoryName, filename)
+ self.assertEqual(repr(unixPort), connectedString)
+ self.assertEqual(str(unixPort), connectedString)
+
+ d = defer.maybeDeferred(unixPort.stopListening)
+ def stoppedListening(ign):
+ unconnectedString = "<%s (not listening)>" % (factoryName,)
+ self.assertEqual(repr(unixPort), unconnectedString)
+ self.assertEqual(str(unixPort), unconnectedString)
+ d.addCallback(stoppedListening)
+ return d
+
+
+ def test_reprWithClassicFactory(self):
+ """
+ The two string representations of the L{IListeningPort} returned by
+ L{IReactorUNIX.listenUNIX} contains the name of the classic factory
+ class being used and the filename on which the port is listening or
+ indicates that the port is not listening.
+ """
+ class ClassicFactory:
+ def doStart(self):
+ pass
+
+ def doStop(self):
+ pass
+
+ # Sanity check
+ self.assertIsInstance(ClassicFactory, types.ClassType)
+
+ return self._reprTest(
+ ClassicFactory(), "twisted.test.test_unix.ClassicFactory")
+
+
+ def test_reprWithNewStyleFactory(self):
+ """
+ The two string representations of the L{IListeningPort} returned by
+ L{IReactorUNIX.listenUNIX} contains the name of the new-style factory
+ class being used and the filename on which the port is listening or
+ indicates that the port is not listening.
+ """
+ class NewStyleFactory(object):
+ def doStart(self):
+ pass
+
+ def doStop(self):
+ pass
+
+ # Sanity check
+ self.assertIsInstance(NewStyleFactory, type)
+
+ return self._reprTest(
+ NewStyleFactory(), "twisted.test.test_unix.NewStyleFactory")
+
+
+
+class ClientProto(protocol.ConnectedDatagramProtocol):
+ started = stopped = False
+ gotback = None
+
+ def __init__(self):
+ self.deferredStarted = defer.Deferred()
+ self.deferredGotBack = defer.Deferred()
+
+ def stopProtocol(self):
+ self.stopped = True
+
+ def startProtocol(self):
+ self.started = True
+ self.deferredStarted.callback(None)
+
+ def datagramReceived(self, data):
+ self.gotback = data
+ self.deferredGotBack.callback(None)
+
+class ServerProto(protocol.DatagramProtocol):
+ started = stopped = False
+ gotwhat = gotfrom = None
+
+ def __init__(self):
+ self.deferredStarted = defer.Deferred()
+ self.deferredGotWhat = defer.Deferred()
+
+ def stopProtocol(self):
+ self.stopped = True
+
+ def startProtocol(self):
+ self.started = True
+ self.deferredStarted.callback(None)
+
+ def datagramReceived(self, data, addr):
+ self.gotfrom = addr
+ self.transport.write("hi back", addr)
+ self.gotwhat = data
+ self.deferredGotWhat.callback(None)
+
+
+
+class DatagramUnixSocketTestCase(unittest.TestCase):
+ """
+ Test datagram UNIX sockets.
+ """
+ def test_exchange(self):
+ """
+ Test that a datagram can be sent to and received by a server and vice
+ versa.
+ """
+ clientaddr = self.mktemp()
+ serveraddr = self.mktemp()
+ sp = ServerProto()
+ cp = ClientProto()
+ s = reactor.listenUNIXDatagram(serveraddr, sp)
+ self.addCleanup(s.stopListening)
+ c = reactor.connectUNIXDatagram(serveraddr, cp, bindAddress=clientaddr)
+ self.addCleanup(c.stopListening)
+
+ d = defer.gatherResults([sp.deferredStarted, cp.deferredStarted])
+ def write(ignored):
+ cp.transport.write("hi")
+ return defer.gatherResults([sp.deferredGotWhat,
+ cp.deferredGotBack])
+
+ def _cbTestExchange(ignored):
+ self.failUnlessEqual("hi", sp.gotwhat)
+ self.failUnlessEqual(clientaddr, sp.gotfrom)
+ self.failUnlessEqual("hi back", cp.gotback)
+
+ d.addCallback(write)
+ d.addCallback(_cbTestExchange)
+ return d
+
+
+ def test_cannotListen(self):
+ """
+ L{IReactorUNIXDatagram.listenUNIXDatagram} raises
+ L{error.CannotListenError} if the unix socket specified is already in
+ use.
+ """
+ addr = self.mktemp()
+ p = ServerProto()
+ s = reactor.listenUNIXDatagram(addr, p)
+ self.failUnlessRaises(error.CannotListenError, reactor.listenUNIXDatagram, addr, p)
+ s.stopListening()
+ os.unlink(addr)
+
+ # test connecting to bound and connected (somewhere else) address
+
+ def _reprTest(self, serverProto, protocolName):
+ """
+ Test the C{__str__} and C{__repr__} implementations of a UNIX datagram
+ port when used with the given protocol.
+ """
+ filename = self.mktemp()
+ unixPort = reactor.listenUNIXDatagram(filename, serverProto)
+
+ connectedString = "<%s on %r>" % (protocolName, filename)
+ self.assertEqual(repr(unixPort), connectedString)
+ self.assertEqual(str(unixPort), connectedString)
+
+ stopDeferred = defer.maybeDeferred(unixPort.stopListening)
+ def stoppedListening(ign):
+ unconnectedString = "<%s (not listening)>" % (protocolName,)
+ self.assertEqual(repr(unixPort), unconnectedString)
+ self.assertEqual(str(unixPort), unconnectedString)
+ stopDeferred.addCallback(stoppedListening)
+ return stopDeferred
+
+
+ def test_reprWithClassicProtocol(self):
+ """
+ The two string representations of the L{IListeningPort} returned by
+ L{IReactorUNIXDatagram.listenUNIXDatagram} contains the name of the
+ classic protocol class being used and the filename on which the port is
+ listening or indicates that the port is not listening.
+ """
+ class ClassicProtocol:
+ def makeConnection(self, transport):
+ pass
+
+ def doStop(self):
+ pass
+
+ # Sanity check
+ self.assertIsInstance(ClassicProtocol, types.ClassType)
+
+ return self._reprTest(
+ ClassicProtocol(), "twisted.test.test_unix.ClassicProtocol")
+
+
+ def test_reprWithNewStyleProtocol(self):
+ """
+ The two string representations of the L{IListeningPort} returned by
+ L{IReactorUNIXDatagram.listenUNIXDatagram} contains the name of the
+ new-style protocol class being used and the filename on which the port
+ is listening or indicates that the port is not listening.
+ """
+ class NewStyleProtocol(object):
+ def makeConnection(self, transport):
+ pass
+
+ def doStop(self):
+ pass
+
+ # Sanity check
+ self.assertIsInstance(NewStyleProtocol, type)
+
+ return self._reprTest(
+ NewStyleProtocol(), "twisted.test.test_unix.NewStyleProtocol")
+
+
+
+if not interfaces.IReactorUNIX(reactor, None):
+ UnixSocketTestCase.skip = "This reactor does not support UNIX domain sockets"
+if not interfaces.IReactorUNIXDatagram(reactor, None):
+ DatagramUnixSocketTestCase.skip = "This reactor does not support UNIX datagram sockets"
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_usage.py b/vendor/Twisted-10.0.0/twisted/test/test_usage.py
new file mode 100644
index 0000000000..e77c179206
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_usage.py
@@ -0,0 +1,372 @@
+
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.trial import unittest
+from twisted.python import usage
+
+
+class WellBehaved(usage.Options):
+ optParameters = [['long', 'w', 'default', 'and a docstring'],
+ ['another', 'n', 'no docstring'],
+ ['longonly', None, 'noshort'],
+ ['shortless', None, 'except',
+ 'this one got docstring'],
+ ]
+ optFlags = [['aflag', 'f',
+ """
+
+ flagallicious docstringness for this here
+
+ """],
+ ['flout', 'o'],
+ ]
+
+ def opt_myflag(self):
+ self.opts['myflag'] = "PONY!"
+
+ def opt_myparam(self, value):
+ self.opts['myparam'] = "%s WITH A PONY!" % (value,)
+
+
+class ParseCorrectnessTest(unittest.TestCase):
+ """
+ Test Options.parseArgs for correct values under good conditions.
+ """
+ def setUp(self):
+ """
+ Instantiate and parseOptions a well-behaved Options class.
+ """
+
+ self.niceArgV = ("--long Alpha -n Beta "
+ "--shortless Gamma -f --myflag "
+ "--myparam Tofu").split()
+
+ self.nice = WellBehaved()
+
+ self.nice.parseOptions(self.niceArgV)
+
+ def test_checkParameters(self):
+ """
+ Checking that parameters have correct values.
+ """
+ self.failUnlessEqual(self.nice.opts['long'], "Alpha")
+ self.failUnlessEqual(self.nice.opts['another'], "Beta")
+ self.failUnlessEqual(self.nice.opts['longonly'], "noshort")
+ self.failUnlessEqual(self.nice.opts['shortless'], "Gamma")
+
+ def test_checkFlags(self):
+ """
+ Checking that flags have correct values.
+ """
+ self.failUnlessEqual(self.nice.opts['aflag'], 1)
+ self.failUnlessEqual(self.nice.opts['flout'], 0)
+
+ def test_checkCustoms(self):
+ """
+ Checking that custom flags and parameters have correct values.
+ """
+ self.failUnlessEqual(self.nice.opts['myflag'], "PONY!")
+ self.failUnlessEqual(self.nice.opts['myparam'], "Tofu WITH A PONY!")
+
+
+class TypedOptions(usage.Options):
+ optParameters = [
+ ['fooint', None, 392, 'Foo int', int],
+ ['foofloat', None, 4.23, 'Foo float', float],
+ ['eggint', None, None, 'Egg int without default', int],
+ ['eggfloat', None, None, 'Egg float without default', float],
+ ]
+
+
+class TypedTestCase(unittest.TestCase):
+ """
+ Test Options.parseArgs for options with forced types.
+ """
+ def setUp(self):
+ self.usage = TypedOptions()
+
+ def test_defaultValues(self):
+ """
+ Test parsing of default values.
+ """
+ argV = []
+ self.usage.parseOptions(argV)
+ self.failUnlessEqual(self.usage.opts['fooint'], 392)
+ self.assert_(isinstance(self.usage.opts['fooint'], int))
+ self.failUnlessEqual(self.usage.opts['foofloat'], 4.23)
+ self.assert_(isinstance(self.usage.opts['foofloat'], float))
+ self.failUnlessEqual(self.usage.opts['eggint'], None)
+ self.failUnlessEqual(self.usage.opts['eggfloat'], None)
+
+ def test_parsingValues(self):
+ """
+ Test basic parsing of int and float values.
+ """
+ argV = ("--fooint 912 --foofloat -823.1 "
+ "--eggint 32 --eggfloat 21").split()
+ self.usage.parseOptions(argV)
+ self.failUnlessEqual(self.usage.opts['fooint'], 912)
+ self.assert_(isinstance(self.usage.opts['fooint'], int))
+ self.failUnlessEqual(self.usage.opts['foofloat'], -823.1)
+ self.assert_(isinstance(self.usage.opts['foofloat'], float))
+ self.failUnlessEqual(self.usage.opts['eggint'], 32)
+ self.assert_(isinstance(self.usage.opts['eggint'], int))
+ self.failUnlessEqual(self.usage.opts['eggfloat'], 21.)
+ self.assert_(isinstance(self.usage.opts['eggfloat'], float))
+
+ def test_invalidValues(self):
+ """
+ Check that passing wrong values raises an error.
+ """
+ argV = "--fooint egg".split()
+ self.assertRaises(usage.UsageError, self.usage.parseOptions, argV)
+
+
+class WrongTypedOptions(usage.Options):
+ optParameters = [
+ ['barwrong', None, None, 'Bar with wrong coerce', 'he']
+ ]
+
+
+class WeirdCallableOptions(usage.Options):
+ def _bar(value):
+ raise RuntimeError("Ouch")
+ def _foo(value):
+ raise ValueError("Yay")
+ optParameters = [
+ ['barwrong', None, None, 'Bar with strange callable', _bar],
+ ['foowrong', None, None, 'Foo with strange callable', _foo]
+ ]
+
+
+class WrongTypedTestCase(unittest.TestCase):
+ """
+ Test Options.parseArgs for wrong coerce options.
+ """
+ def test_nonCallable(self):
+ """
+ Check that using a non callable type fails.
+ """
+ us = WrongTypedOptions()
+ argV = "--barwrong egg".split()
+ self.assertRaises(TypeError, us.parseOptions, argV)
+
+ def test_notCalledInDefault(self):
+ """
+ Test that the coerce functions are not called if no values are
+ provided.
+ """
+ us = WeirdCallableOptions()
+ argV = []
+ us.parseOptions(argV)
+
+ def test_weirdCallable(self):
+ """
+ Test what happens when coerce functions raise errors.
+ """
+ us = WeirdCallableOptions()
+ argV = "--foowrong blah".split()
+ # ValueError is swallowed as UsageError
+ e = self.assertRaises(usage.UsageError, us.parseOptions, argV)
+ self.assertEquals(str(e), "Parameter type enforcement failed: Yay")
+
+ us = WeirdCallableOptions()
+ argV = "--barwrong blah".split()
+ # RuntimeError is not swallowed
+ self.assertRaises(RuntimeError, us.parseOptions, argV)
+
+
+class OutputTest(unittest.TestCase):
+ def test_uppercasing(self):
+ """
+ Error output case adjustment does not mangle options
+ """
+ opt = WellBehaved()
+ e = self.assertRaises(usage.UsageError,
+ opt.parseOptions, ['-Z'])
+ self.assertEquals(str(e), 'option -Z not recognized')
+
+
+class InquisitionOptions(usage.Options):
+ optFlags = [
+ ('expect', 'e'),
+ ]
+ optParameters = [
+ ('torture-device', 't',
+ 'comfy-chair',
+ 'set preferred torture device'),
+ ]
+
+
+class HolyQuestOptions(usage.Options):
+ optFlags = [('horseback', 'h',
+ 'use a horse'),
+ ('for-grail', 'g'),
+ ]
+
+
+class SubCommandOptions(usage.Options):
+ optFlags = [('europian-swallow', None,
+ 'set default swallow type to Europian'),
+ ]
+ subCommands = [
+ ('inquisition', 'inquest', InquisitionOptions,
+ 'Perform an inquisition'),
+ ('holyquest', 'quest', HolyQuestOptions,
+ 'Embark upon a holy quest'),
+ ]
+
+
+class SubCommandTest(unittest.TestCase):
+
+ def test_simpleSubcommand(self):
+ o = SubCommandOptions()
+ o.parseOptions(['--europian-swallow', 'inquisition'])
+ self.failUnlessEqual(o['europian-swallow'], True)
+ self.failUnlessEqual(o.subCommand, 'inquisition')
+ self.failUnless(isinstance(o.subOptions, InquisitionOptions))
+ self.failUnlessEqual(o.subOptions['expect'], False)
+ self.failUnlessEqual(o.subOptions['torture-device'], 'comfy-chair')
+
+ def test_subcommandWithFlagsAndOptions(self):
+ o = SubCommandOptions()
+ o.parseOptions(['inquisition', '--expect', '--torture-device=feather'])
+ self.failUnlessEqual(o['europian-swallow'], False)
+ self.failUnlessEqual(o.subCommand, 'inquisition')
+ self.failUnless(isinstance(o.subOptions, InquisitionOptions))
+ self.failUnlessEqual(o.subOptions['expect'], True)
+ self.failUnlessEqual(o.subOptions['torture-device'], 'feather')
+
+ def test_subcommandAliasWithFlagsAndOptions(self):
+ o = SubCommandOptions()
+ o.parseOptions(['inquest', '--expect', '--torture-device=feather'])
+ self.failUnlessEqual(o['europian-swallow'], False)
+ self.failUnlessEqual(o.subCommand, 'inquisition')
+ self.failUnless(isinstance(o.subOptions, InquisitionOptions))
+ self.failUnlessEqual(o.subOptions['expect'], True)
+ self.failUnlessEqual(o.subOptions['torture-device'], 'feather')
+
+ def test_anotherSubcommandWithFlagsAndOptions(self):
+ o = SubCommandOptions()
+ o.parseOptions(['holyquest', '--for-grail'])
+ self.failUnlessEqual(o['europian-swallow'], False)
+ self.failUnlessEqual(o.subCommand, 'holyquest')
+ self.failUnless(isinstance(o.subOptions, HolyQuestOptions))
+ self.failUnlessEqual(o.subOptions['horseback'], False)
+ self.failUnlessEqual(o.subOptions['for-grail'], True)
+
+ def test_noSubcommand(self):
+ o = SubCommandOptions()
+ o.parseOptions(['--europian-swallow'])
+ self.failUnlessEqual(o['europian-swallow'], True)
+ self.failUnlessEqual(o.subCommand, None)
+ self.failIf(hasattr(o, 'subOptions'))
+
+ def test_defaultSubcommand(self):
+ o = SubCommandOptions()
+ o.defaultSubCommand = 'inquest'
+ o.parseOptions(['--europian-swallow'])
+ self.failUnlessEqual(o['europian-swallow'], True)
+ self.failUnlessEqual(o.subCommand, 'inquisition')
+ self.failUnless(isinstance(o.subOptions, InquisitionOptions))
+ self.failUnlessEqual(o.subOptions['expect'], False)
+ self.failUnlessEqual(o.subOptions['torture-device'], 'comfy-chair')
+
+ def test_subCommandParseOptionsHasParent(self):
+ class SubOpt(usage.Options):
+ def parseOptions(self, *a, **kw):
+ self.sawParent = self.parent
+ usage.Options.parseOptions(self, *a, **kw)
+ class Opt(usage.Options):
+ subCommands = [
+ ('foo', 'f', SubOpt, 'bar'),
+ ]
+ o = Opt()
+ o.parseOptions(['foo'])
+ self.failUnless(hasattr(o.subOptions, 'sawParent'))
+ self.failUnlessEqual(o.subOptions.sawParent , o)
+
+ def test_subCommandInTwoPlaces(self):
+ """
+ The .parent pointer is correct even when the same Options class is
+ used twice.
+ """
+ class SubOpt(usage.Options):
+ pass
+ class OptFoo(usage.Options):
+ subCommands = [
+ ('foo', 'f', SubOpt, 'quux'),
+ ]
+ class OptBar(usage.Options):
+ subCommands = [
+ ('bar', 'b', SubOpt, 'quux'),
+ ]
+ oFoo = OptFoo()
+ oFoo.parseOptions(['foo'])
+ oBar=OptBar()
+ oBar.parseOptions(['bar'])
+ self.failUnless(hasattr(oFoo.subOptions, 'parent'))
+ self.failUnless(hasattr(oBar.subOptions, 'parent'))
+ self.failUnlessIdentical(oFoo.subOptions.parent, oFoo)
+ self.failUnlessIdentical(oBar.subOptions.parent, oBar)
+
+
+class HelpStringTest(unittest.TestCase):
+ def setUp(self):
+ """
+ Instantiate a well-behaved Options class.
+ """
+
+ self.niceArgV = ("--long Alpha -n Beta "
+ "--shortless Gamma -f --myflag "
+ "--myparam Tofu").split()
+
+ self.nice = WellBehaved()
+
+ def test_noGoBoom(self):
+ """
+ __str__ shouldn't go boom.
+ """
+ try:
+ self.nice.__str__()
+ except Exception, e:
+ self.fail(e)
+
+ def test_whitespaceStripFlagsAndParameters(self):
+ """
+ Extra whitespace in flag and parameters docs is stripped.
+ """
+ # We test this by making sure aflag and it's help string are on the
+ # same line.
+ lines = [s for s in str(self.nice).splitlines() if s.find("aflag")>=0]
+ self.failUnless(len(lines) > 0)
+ self.failUnless(lines[0].find("flagallicious") >= 0)
+
+
+class PortCoerceTestCase(unittest.TestCase):
+ """
+ Test the behavior of L{usage.portCoerce}.
+ """
+ def test_validCoerce(self):
+ """
+ Test the answers with valid input.
+ """
+ self.assertEquals(0, usage.portCoerce("0"))
+ self.assertEquals(3210, usage.portCoerce("3210"))
+ self.assertEquals(65535, usage.portCoerce("65535"))
+
+ def test_errorCoerce(self):
+ """
+ Test error path.
+ """
+ self.assertRaises(ValueError, usage.portCoerce, "")
+ self.assertRaises(ValueError, usage.portCoerce, "-21")
+ self.assertRaises(ValueError, usage.portCoerce, "212189")
+ self.assertRaises(ValueError, usage.portCoerce, "foo")
+
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/test_zshcomp.py b/vendor/Twisted-10.0.0/twisted/test/test_zshcomp.py
new file mode 100644
index 0000000000..096f9d6b1c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/test_zshcomp.py
@@ -0,0 +1,210 @@
+# Copyright (c) 2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for twisted.python.zshcomp
+"""
+
+import os, os.path
+from cStringIO import StringIO
+
+from twisted.trial import unittest
+from twisted.python import zshcomp, usage
+
+class ZshcompTestCase(unittest.TestCase):
+ """
+ Tests for the zsh completion function builder in twisted/python/zshcomp.py
+ """
+ def test_buildAll(self):
+ """
+ Build all the completion functions for twisted commands - no errors
+ should be raised
+ """
+ dirname = self.mktemp()
+ os.mkdir(dirname)
+ skippedCmds = [x[0] for x in zshcomp.makeCompFunctionFiles(dirname)]
+
+ # verify a zsh function was created for each twisted command
+ for info in zshcomp.generateFor:
+ if info[0] in skippedCmds:
+ continue
+ funcPath = os.path.join(dirname, '_' + info[0])
+ self.failUnless(os.path.exists(funcPath))
+
+ def test_accumulateMetadata(self):
+ """
+ Test that the zsh_* variables you can place on Option classes gets
+ picked up correctly
+ """
+ opts = TestOptions2()
+ ag = zshcomp.ArgumentsGenerator('dummy_cmd', opts, 'dummy_value')
+
+ altArgDescr = TestOptions.zsh_altArgDescr.copy()
+ altArgDescr.update(TestOptions2.zsh_altArgDescr)
+
+ actionDescr = TestOptions.zsh_actionDescr.copy()
+ actionDescr.update(TestOptions2.zsh_actionDescr)
+
+ self.failUnlessEquals(ag.altArgDescr, altArgDescr)
+ self.failUnlessEquals(ag.actionDescr, actionDescr)
+ self.failUnlessEquals(ag.multiUse, TestOptions.zsh_multiUse)
+ self.failUnlessEquals(ag.mutuallyExclusive,
+ TestOptions.zsh_mutuallyExclusive)
+ self.failUnlessEquals(ag.actions, TestOptions.zsh_actions)
+ self.failUnlessEquals(ag.extras, TestOptions.zsh_extras)
+
+ def test_accumulateAdditionalOptions(self):
+ """
+ Test that we pick up options that are only defined by having an
+ appropriately named method on your Options class,
+ e.g. def opt_foo(self, foo)
+ """
+ opts = TestOptions2()
+ ag = zshcomp.ArgumentsGenerator('dummy_cmd', opts, 'dummy_value')
+
+ self.failUnless('nocrash' in ag.optFlags_d and \
+ 'nocrash' in ag.optAll_d)
+ self.failUnless('difficulty' in ag.optParams_d and \
+ 'difficulty' in ag.optAll_d)
+
+ def test_verifyZshNames(self):
+ """
+ Test that using a parameter/flag name that doesn't exist
+ will raise an error
+ """
+ class TmpOptions(TestOptions2):
+ zsh_actions = {'detaill' : 'foo'} # Note typo of detail
+
+ opts = TmpOptions()
+ self.failUnlessRaises(ValueError, zshcomp.ArgumentsGenerator,
+ 'dummy_cmd', opts, 'dummy_value')
+
+ def test_zshCode(self):
+ """
+ Generate a completion function, and test the textual output
+ against a known correct output
+ """
+ cmd_name = 'testprog'
+ opts = CodeTestOptions()
+ f = StringIO()
+ b = zshcomp.Builder(cmd_name, opts, f)
+ b.write()
+ f.reset()
+ self.failUnlessEquals(f.read(), testOutput1)
+
+ def test_skipBuild(self):
+ """
+ Test that makeCompFunctionFiles skips building for commands whos
+ script module cannot be imported
+ """
+ generateFor = [('test_cmd', 'no.way.your.gonna.import.this', 'Foo')]
+ skips = zshcomp.makeCompFunctionFiles('out_dir', generateFor, {})
+ # no exceptions should be raised. hooray.
+ self.failUnlessEqual(len(skips), 1)
+ self.failUnlessEqual(len(skips[0]), 2)
+ self.failUnlessEqual(skips[0][0], 'test_cmd')
+ self.failUnless(isinstance(skips[0][1], ImportError))
+ self.flushLoggedErrors(self, ImportError)
+
+class TestOptions(usage.Options):
+ """
+ Command-line options for an imaginary game
+ """
+ optFlags = [['fokker', 'f',
+ 'Select the Fokker Dr.I as your dogfighter aircraft'],
+ ['albatros', 'a',
+ 'Select the Albatros D-III as your dogfighter aircraft'],
+ ['spad', 's',
+ 'Select the SPAD S.VII as your dogfighter aircraft'],
+ ['bristol', 'b',
+ 'Select the Bristol Scout as your dogfighter aircraft'],
+ ['physics', 'p',
+ 'Enable secret Twisted physics engine'],
+ ['jam', 'j',
+ 'Enable a small chance that your machine guns will jam!'],
+ ['verbose', 'v',
+ 'Verbose logging (may be specified more than once)'],
+ ]
+
+ optParameters = [['pilot-name', None, "What's your name, Ace?",
+ 'Manfred von Richthofen'],
+ ['detail', 'd',
+ 'Select the level of rendering detail (1-5)', '3'],
+ ]
+
+
+ zsh_altArgDescr = {'physics' : 'Twisted-Physics',
+ 'detail' : 'Rendering detail level'}
+ zsh_actionDescr = {'detail' : 'Pick your detail'}
+ zsh_multiUse = ['verbose']
+ zsh_mutuallyExclusive = [['fokker', 'albatros', 'spad', 'bristol']]
+ zsh_actions = {'detail' : '(1 2 3 4 5)'}
+ zsh_extras = [':saved game file to load:_files']
+
+class TestOptions2(TestOptions):
+ """
+ Extend the options and zsh metadata provided by TestOptions. zshcomp must
+ accumulate options and metadata from all classes in the hiearchy so this
+ is important for testing
+ """
+ optFlags = [['no-stalls', None,
+ 'Turn off the ability to stall your aircraft']]
+ optParameters = [['reality-level', None,
+ 'Select the level of physics reality (1-5)', '5']]
+
+ zsh_altArgDescr = {'no-stalls' : 'Can\'t stall your plane'}
+ zsh_actionDescr = {'reality-level' : 'Physics reality level'}
+
+ def opt_nocrash(self):
+ """Select that you can't crash your plane"""
+
+ def opt_difficulty(self, difficulty):
+ """How tough are you? (1-10)"""
+
+def _accuracyAction():
+ return '(1 2 3)'
+
+class CodeTestOptions(usage.Options):
+ """
+ Command-line options for an imaginary program
+ """
+ optFlags = [['color', 'c', 'Turn on color output'],
+ ['gray', 'g', 'Turn on gray-scale output'],
+ ['verbose', 'v',
+ 'Verbose logging (may be specified more than once)'],
+ ]
+
+ optParameters = [['optimization', None,
+ 'Select the level of optimization (1-5)', '5'],
+ ['accuracy', 'a',
+ 'Select the level of accuracy (1-3)', '3'],
+ ]
+
+
+ zsh_altArgDescr = {'color' : 'Color on',
+ 'optimization' : 'Optimization level'}
+ zsh_actionDescr = {'optimization' : 'Optimization?',
+ 'accuracy' : 'Accuracy?'}
+ zsh_multiUse = ['verbose']
+ zsh_mutuallyExclusive = [['color', 'gray']]
+ zsh_actions = {'optimization' : '(1 2 3 4 5)',
+ 'accuracy' : _accuracyAction}
+ zsh_extras = [':output file:_files']
+
+testOutput1 = """#compdef testprog
+_arguments -s -A "-*" \\
+':output file:_files' \\
+'(--accuracy)-a[3]:Accuracy?:(1 2 3)' \\
+'(-a)--accuracy=[3]:Accuracy?:(1 2 3)' \\
+'(--gray -g --color)-c[Color on]' \\
+'(--gray -g -c)--color[Color on]' \\
+'(--color -c --gray)-g[Turn on gray-scale output]' \\
+'(--color -c -g)--gray[Turn on gray-scale output]' \\
+'--help[Display this help and exit.]' \\
+'--optimization=[Optimization level]:Optimization?:(1 2 3 4 5)' \\
+'*-v[Verbose logging (may be specified more than once)]' \\
+'*--verbose[Verbose logging (may be specified more than once)]' \\
+'--version[version]' \\
+&& return 0
+"""
+
diff --git a/vendor/Twisted-10.0.0/twisted/test/testutils.py b/vendor/Twisted-10.0.0/twisted/test/testutils.py
new file mode 100644
index 0000000000..a310ea2128
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/testutils.py
@@ -0,0 +1,55 @@
+from cStringIO import StringIO
+from twisted.internet.protocol import FileWrapper
+
+class IOPump:
+ """Utility to pump data between clients and servers for protocol testing.
+
+ Perhaps this is a utility worthy of being in protocol.py?
+ """
+ def __init__(self, client, server, clientIO, serverIO):
+ self.client = client
+ self.server = server
+ self.clientIO = clientIO
+ self.serverIO = serverIO
+
+ def flush(self):
+ "Pump until there is no more input or output."
+ while self.pump():
+ pass
+
+ def pump(self):
+ """Move data back and forth.
+
+ Returns whether any data was moved.
+ """
+ self.clientIO.seek(0)
+ self.serverIO.seek(0)
+ cData = self.clientIO.read()
+ sData = self.serverIO.read()
+ self.clientIO.seek(0)
+ self.serverIO.seek(0)
+ self.clientIO.truncate()
+ self.serverIO.truncate()
+ for byte in cData:
+ self.server.dataReceived(byte)
+ for byte in sData:
+ self.client.dataReceived(byte)
+ if cData or sData:
+ return 1
+ else:
+ return 0
+
+
+def returnConnected(server, client):
+ """Take two Protocol instances and connect them.
+ """
+ cio = StringIO()
+ sio = StringIO()
+ client.makeConnection(FileWrapper(cio))
+ server.makeConnection(FileWrapper(sio))
+ pump = IOPump(client, server, cio, sio)
+ # Challenge-response authentication:
+ pump.flush()
+ # Uh...
+ pump.flush()
+ return pump
diff --git a/vendor/Twisted-10.0.0/twisted/test/time_helpers.py b/vendor/Twisted-10.0.0/twisted/test/time_helpers.py
new file mode 100644
index 0000000000..f5f2eed987
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/test/time_helpers.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Helper class to writing deterministic time-based unit tests.
+
+Do not use this module. It is a lie. See L{twisted.internet.task.Clock}
+instead.
+"""
+
+import warnings
+warnings.warn(
+ "twisted.test.time_helpers is deprecated since Twisted 10.0. "
+ "See twisted.internet.task.Clock instead.",
+ category=DeprecationWarning, stacklevel=2)
+
+
+class Clock(object):
+ """
+ A utility for monkey-patches various parts of Twisted to use a
+ simulated timing mechanism. DO NOT use this class. Use
+ L{twisted.internet.task.Clock}.
+ """
+ rightNow = 0.0
+
+ def __call__(self):
+ """
+ Return the current simulated time.
+ """
+ return self.rightNow
+
+ def install(self):
+ """
+ Monkeypatch L{twisted.internet.reactor.seconds} to use
+ L{__call__} as a time source
+ """
+ # Violation is fun.
+ from twisted.internet import reactor
+ self.reactor_original = reactor.seconds
+ reactor.seconds = self
+
+ def uninstall(self):
+ """
+ Remove the monkeypatching of L{twisted.internet.reactor.seconds}.
+ """
+ from twisted.internet import reactor
+ reactor.seconds = self.reactor_original
+
+ def adjust(self, amount):
+ """
+ Adjust the current simulated time upward by the given C{amount}.
+
+ Note that this does not cause any scheduled calls to be run.
+ """
+ self.rightNow += amount
+
+ def pump(self, reactor, timings):
+ """
+ Iterate the given C{reactor} with increments of time specified
+ by C{timings}.
+
+ For each timing, the simulated time will be L{adjust}ed and
+ the reactor will be iterated twice.
+ """
+ timings = list(timings)
+ timings.reverse()
+ self.adjust(timings.pop())
+ while timings:
+ self.adjust(timings.pop())
+ reactor.iterate()
+ reactor.iterate()
+
diff --git a/vendor/Twisted-10.0.0/twisted/topfiles/4335.misc b/vendor/Twisted-10.0.0/twisted/topfiles/4335.misc
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/topfiles/4335.misc
diff --git a/vendor/Twisted-10.0.0/twisted/topfiles/CREDITS b/vendor/Twisted-10.0.0/twisted/topfiles/CREDITS
new file mode 100644
index 0000000000..a4eeecefb3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/topfiles/CREDITS
@@ -0,0 +1,60 @@
+The Matrix
+
+- Glyph "Glyph" Lefkowitz <glyph@twistedmatrix.com>
+ electric violin
+- Sean "Riley" Riley <sean@twistedmatrix.com>
+ grand piano
+- Allen "Dash" Short <washort@twistedmatrix.com>
+ vocals and guitar
+- Christopher "Radix" Armstrong <radix@twistedmatrix.com>
+ percussion
+- Paul "z3p" Swartz <z3p@twistedmatrix.com>
+ oboe
+- Jürgen "snibril" Hermann <jh@twistedmatrix.com>
+ synthesizer
+- Moshe "vertical" Zadka <moshez@twistedmatrix.com>
+ accordion
+- Benjamin Bruheim <grolgh@online.no>
+ kazoo
+- Travis B. "Nafai" Hartwell <nafai@twistedmatrix.com>
+ keyboards
+- Itamar "itamar" Shtull-Trauring <twisted@itamarst.org>
+ alto recorder
+- Andrew "spiv" Bennetts <andrew@puzzling.org>
+ glockenspiel
+- Kevin "Acapnotic" Turner <acapnotic@twistedmatrix.com>
+ trombone
+- Donovan "fzZzy" Preston <dp@twistedmatrix.com>
+ bass and harmonium
+- Jp "exarkun" Calderone <exarkun@twistedmatrix.com>
+ geopolitical sociographic dissonance engine
+- Gavin "skreech" Cooper <coop@coopweb.org>
+ torque wrench
+- Jonathan "jml" Lange <jml@twistedmatrix.com>
+ pipe organ
+- Bob "etrepum" Ippolito <bob@redivi.com>
+ low frequency oscillator
+- Pavel "PenguinOfDoom" Pergamenshchik <ppergame@gmail.com>
+ electronic balalaika
+- Jonathan D. "slyphon" Simms <slyphon@twistedmatrix.com>
+ theramin and drums
+- Brian "warner" Warner <warner@twistedmatrix.com>
+ hertzian field renderer
+- Mary Gardiner <mary-twisted@puzzling.org>
+ krummhorn
+- Eric "teratorn" Mangold <teratorn@twistedmatrix.com>
+ serpentine bassoon
+- Tommi "Tv" Virtanen <tv@twistedmatrix.com>
+ didgeridoo
+- Justin "justinj" Johnson <justinj@twistedmatrix.com>
+ bass mandolin
+- Ralph "ralphm" Meijer <twisted@ralphm.ik.nu>
+ vocals and timbales
+- David "dreid" Reid <dreid@dreid.org>
+ banjo
+
+Extras
+
+- Jerry Hebert <jerry@cynics.org>
+- Nick Moffit <nick@zork.org>
+- Jeremy Fincher
diff --git a/vendor/Twisted-10.0.0/twisted/topfiles/ChangeLog.Old b/vendor/Twisted-10.0.0/twisted/topfiles/ChangeLog.Old
new file mode 100644
index 0000000000..30594b20e4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/topfiles/ChangeLog.Old
@@ -0,0 +1,3888 @@
+2005-03-12 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/scripts/mktap.py, twisted/scripts/twistd.py,
+ twisted/application/app.py: Changed UID and GID defaults for Process
+ to None. Changed mktap behavior to not specify UID and GID if they
+ are not given on the command line. Changed application startup to
+ not change UID or GID if they are not given. Changed twistd to add
+ UID and GID setting command line arguments.
+
+2005-02-10 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/defer.py: DeferredLock, DeferredSemaphore, and
+ DeferredQueue added.
+
+ * twisted/test/test_defer.py: Tests for above mentioned three new
+ classes.
+
+2004-11-27 Brian Warner <warner@lothar.com>
+
+ * util.py (SignalStateManager.save): don't save signal handlers
+ for SIGKILL and SIGSTOP, since we can't set them anyway.
+ Python2.4c1 raises an error when you try.
+
+2004-11-07 Brian Warner <warner@lothar.com>
+
+ * twisted/test/test_internet.py: correctly check for SSL support.
+ Improve timeout for testCallLater and testGetDelayedCalls to avoid
+ spurious failures on slow test systems. Close sockets in
+ PortStringification to fix trial warnings.
+
+ * twisted/internet/ssl.py: add a comment describing the correct
+ way to import twisted.internet.ssl (since it might partially fail
+ if OpenSSL is not available)
+
+2004-11-06 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/trial/assertions.py: assertRaises/failUnlessRaises now
+ returns the caught exception to allow tests to inspect the contents.
+
+2004-11-02 Brian Warner <warner@lothar.com>
+
+ * loopback.py (loopbackTCP): use trial's spinWhile and spinUntil
+ primitives instead of doing reactor.iterate() ourselves. Make sure
+ to wait for everything before finishing.
+
+2004-10-26 Cory Dodt <corydodt@twistedmatrix.com>
+
+ * twisted/python/{which,process}.py,
+ twisted/test/{test_wprocess,wprocess_for_testing}.py,
+ twisted/internet/{default,error,wprocess,process}.py: back out
+ wprocess due to test failures in wprocess and new trial. Resolves
+ issue 760.
+
+2004-10-24 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * TCP: Half-close of write and read for TCP connections, including
+ protocol notification for protocols that implement
+ IHalfCloseableProtocol.
+
+2004-10-07 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * Transports: Add a maximum to the number of bytes that will be
+ held in the write buffer even after they have been sent. This
+ puts a maximum on the cost of writing faster than the network
+ can accommodate.
+
+2004-10-06 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * Transports: New TCP/SSL/etc. buffering algorithm. All writes are
+ now stored until next iteration before being written, and many
+ small writes are not expensive.
+
+2004-09-30 Brian Warner <warner@lothar.com>
+
+ * glib2reactor.py: new reactor that uses just glib2, not gtk2.
+ This one doesn't require a DISPLAY, and cannot be used for GUI
+ apps.
+
+ * gtk2reactor.py: import gobject *after* pygtk.require, to make
+ sure we get the same versions of both
+
+2004-09-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/internet/defer.py: Add deferredGenerator and
+ waitForDeferred. This lets you write kinda-sorta
+ synchronous-looking code that uses Deferreds. See the
+ waitForDeferred docstring.
+
+2004-09-11 Cory Dodt <corydodt@twistedmatrix.com>
+
+ * twisted/python/{which,process}.py,
+ twisted/test/{test_wprocess,wprocess_for_testing}.py,
+ twisted/internet/{default,error,wprocess,process}.py: merge the
+ "wprocess" branch which uses Trent Mick's process.py to enable
+ spawnProcess in the default reactor on Windows
+
+2004-08-24 Brian Warner <warner@lothar.com>
+
+ * twisted/application/internet.py (TimerService): make it possible
+ to restart a stopped TimerService. Threw out a lot of (apparently)
+ unnecessary code in the process. Make sure it gets pickled in a
+ not-running state too.
+ * twisted/test/test_application.py (TestInternet2.testTimer): test
+ the changes, and update the way the test peeks inside TimerService
+
+2004-07-18 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/internet/utils.py: By passing errortoo=1, you can get
+ stderr from getProcessOutput
+
+2004-07-18 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/unix.py: if the utmp module is available, record
+ user logins/logouts into utmp/wtmp.
+
+2004-06-25 Paul Swartz <z3p@twistedmatrix.com>
+ * twisted/conch/checkers.py: Use functionality of crypt module instead
+ of an external module.
+
+2004-06-25 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/spread/banana.py: Disabled automatic import and use of
+ cBanana. PB will now use the pure-Python version of banana unless
+ cBanana is manually installed by the application.
+
+2004-06-12 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/client: added -r flag to reconnect to the server if
+ the connection is lost (closes 623).
+
+2004-06-06 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_enterprise.py: test open callback and
+ connect/disconnect.
+
+ * twisted/enterprise/adbapi.py: add open callback support
+ and disconnect() method. Issue 480.
+
+2004-06-05 Dave Peticolas <dave@krondo.com>
+
+ * twisted/enterprise/adbapi.py: Don't log sql exceptions (issue 631).
+ Remove deprecated api.
+
+ * twisted/news/database.py: do not use adbapi.Augmentation
+
+2004-06-03 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/internet/gtk2reactor.py: The choice between glib event
+ loop and gtk+ event loop is determined by argument at reactor
+ install time.
+
+2004-05-31 Dave Peticolas <dave@krondo.com>
+
+ * twisted/enterprise/sqlreflector.py: don't use Augmentation
+
+ * twisted/enterprise/populate.sql: remove
+
+ * twisted/enterprise/schema.sql: remove
+
+ * twisted/enterprise/row.py: remove deprecated classes
+
+ * twisted/enterprise/dbgadgets.py: remove
+
+ * twisted/enterprise/dbcred.py: remove
+
+ * twisted/test/test_enterprise.py: Fix Firebird test case.
+
+2004-05-21 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/internet/gtk2reactor.py: use glib event loop directly
+ instead of gtk2's event loop if possible.
+
+2004-05-04 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted.news, twisted.protocols.nntp: Moved back into trunk
+ pending an alternate split-up strategy.
+
+2004-05-04 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted.internet.reactor.listenUDP: transport.write() on UDP
+ ports no longer supports unresolved hostnames (though deprecated
+ support still exists).
+
+2004-4-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/lore/nevowlore.py, twisted/plugins.tml: Added Nevow
+ support for lore. See docstring of twisted.lore.nevowlore.
+
+2004-4-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted.news, twisted.protocols.nntp: Moved into a third party
+ package. Deprecated backwards-compatibility exists by importing
+ from the third-party package if available.
+
+2004-4-11 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted.conch: refactored the Conch client to separate connecting
+ to a server from user authentication from client-specific actions.
+
+2004-03-23 Andrew Bennetts <spiv@twistedmatrix.com>
+
+ * twisted.protocols.http: Small optimisation to HTTP implementation.
+ This changes return value of toChunk to a tuple of strings, rather
+ than one string.
+
+2004-4-3 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted.python.lockfile: added lockfile support, based on
+ liblockfile.
+ * twisted.internet.unix.Port: added a wantPID kwarg. If True, it
+ checks for and gets a lockfile for the UNIX socket.
+ * twisted.internet.unix.Connector: added a checkPID kwarg. If True,
+ it checks that the lockfile for the socket is current.
+
+2004-03-23 Pavel Pergamenshchik <pp64@cornell.edu>
+
+ * twisted.internet.iocp: Support for Windows IO Completion Ports.
+ Use with "--reactor=iocp" parameter to twistd or trial.
+
+2004-03-20 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted.internet: getHost(), getPeer(), buildProtocol() etc.
+ all use address objects from twisted.internet.address.
+
+ * twisted/internet/udp.py: Connected UDP support is now part of
+ the standard listenUDP-resulting UDP transport using a connect()
+ method.
+
+2004-03-18 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/application/internet.py: Changed TimerService to
+ log errors from the function it calls.
+
+ * twisted/application/test_application.py: Added test case
+ for logging of exceptions from functions TimerService calls.
+
+2004-03-07 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.2.1alpha1.
+
+2004-03-03 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/web/server.py: Fix UnsupportedMethod so that users'
+ allowedMethods are actually honored.
+
+ * twisted/web/resource.py: (Resource.render) If the resource has
+ an 'allowedMethods' attribute, pass it to UnsupportedMethod.
+
+2004-02-27 Andrew Bennetts <spiv@twistedmatrix.com>
+
+ * twisted/internet/defer.py: Add consumeErrors flag to DeferredList.
+ This takes care of the most common use-case for the recently
+ deprecated addDeferred method.
+
+2004-02-28 Dave Peticolas <dave@krondo.com>
+
+ * setup.py: install tap2rpm as a bin script
+
+ * twisted/test/test_enterprise.py: Test Firebird db. Fix typos.
+
+2004-02-27 Andrew Bennetts <spiv@twistedmatrix.com>
+
+ * twisted/internet/defer.py: Deprecated DeferredList.addDeferred. It
+ isn't as useful as it looks, and can have surprising behaviour.
+
+2004-02-25 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/protocols/dns.py: Fixed a bug in TCP support: It
+ wouldn't process any messages after the first, causing AXFR
+ queries to be totally broken (in addition to other problems in the
+ implementation of AXFR).
+
+ * twisted/names/client.py: Fixed the AXFR client (lookupZone),
+ thanks to DJB's wonderful documentation of the horribleness of
+ DNS.
+
+2004-02-25 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.2.0 final! Same as rc3.
+
+2004-02-24 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.2.0rc3 (same as rc2, with cBanana bug
+ fixed).
+
+2004-02-19 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/application/service.py (IService.disownServiceParent)
+ (IServiceCollection.removeService): These may return Deferred if they
+ have asynchronous side effects.
+
+2004-02-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.2.0rc2. Brown-paper bag release bug.
+
+2004-02-17 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.2.0rc1.
+
+2004-02-13 Brian Warner <warner@lothar.com>
+
+ * doc/howto/faq.xhtml: add entry on transport.getPeer()
+
+2004-01-31 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.2alpha2 (problem with Debian packaging).
+
+2004-01-30 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.2alpha1.
+
+2004-01-23 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/scripts/trial.py: trial now supports a --coverage
+ option, requiring Python 2.3.3. Give it a directory name (relative
+ to _trial_temp) to put code-coverage info in. It uses the stdlib
+ 'trace' module.
+
+2004-01-21 Pavel Pergamenshchik <pp64@cornell.edu>
+
+ * twisted/protocols/stateful.py: A new way to write protocols!
+ Current state is encoded as a pair (func, len). As soon as len
+ of data arrives, func is called with that amount of data. New
+ state is returned from func.
+ * twisted/test/test_stateful.py: Tests and an example, an
+ Int32StringReceiver implementation.
+
+2004-01-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/web/resource.py: The default render method of Resource
+ now supports delegating to methods of the form "render_*" where
+ "*" is the HTTP method that was used to make the
+ request. Examples: request_GET, request_HEAD, request_CONNECT, and
+ so on. This won't break any existing code - when people want to
+ use the better API, they can stop overriding 'render' and instead
+ override individual render_* methods.
+
+2004-01-13 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/web/soap.py: Beginning of client SOAP support.
+
+2004-01-10 Andrew Bennetts <spiv@twistedmatrix.com>
+
+ * twisted/protocols/ftp.py: Added support for partial downloads
+ and uploads to FTPClient (see the offset parameter of retrieveFile).
+
+2004-01-09 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/imap4.py: Add IMessageCopier interface to allow
+ for optimized implementations of message copying.
+
+2004-01-06 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/default.py (PosixReactorBase.spawnProcess): add
+ a 'childFDs' argument which allows the child's file descriptors to
+ be arbitrarily mapped to parent FDs or pipes. This allows you to
+ set up additional pipes into the child (say for a GPG passphrase
+ or separate status information).
+
+ * twisted/internet/process.py (Process): add childFDs, split out
+ ProcessReader and ProcessWriter (so that Process itself is no
+ longer also reading stdout).
+
+ * twisted/internet/protocol.py (ProcessProtocol): add new
+ childDataReceived and childConnectionLost methods, which default
+ to invoking the old methods for backwards compatibility
+
+ * twisted/test/test_process.py (FDTest): add test for childFDs
+ mapping. Also add timeouts to most tests, and make all
+ reactor.iterate() loops wait 10ms between iterations to avoid
+ spamming the CPU quite so badly. Closes issue435.
+ * twisted/test/process_fds.py: new child process for FDTest
+
+ * doc/howto/process.xhtml: document childFDs argument, add example
+
+2004-01-04 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/internet/gladereactor.py: logs all network traffic for
+ TCP/SSL/Unix sockets, allowing traffic to be displayed.
+
+2004-01-04 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_enterprise.py: test deleting rows not in cache
+
+ * twisted/enterprise/reflector.py: deleted rows don't have to be
+ in cache
+
+ * doc/examples/row_example.py: use KeyFactory from row_util
+
+ * doc/examples/row_util.py: add KeyFactory
+
+2003-12-31 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/defer.py (Deferred.setTimeout): if the Deferred
+ has already been called, don't bother with the timeout. This
+ happens when trial.util.deferredResult is used with a timeout
+ argument and the Deferred was created by defer.succeed().
+ * twisted/test/test_defer.py
+ (DeferredTestCase.testImmediateSuccess2): test for same
+
+2003-12-31 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/ident.py: Client and server ident implementation
+ * twisted/test/test_ident.py: Test cases for ident protocol
+
+2003-12-29 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/spread/pb.py: Changed PBServerFactory to use "protocol"
+ instance attribute for Broker creation.
+
+2003-12-26 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/web/server.py: display of tracebacks on web pages can
+ now be disabled by setting displayTracebacks to False on the Site
+ or by using applicable tap option. Woven does not yet use
+ this attribute.
+
+2003-12-23 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/web/client.py: if Host header is passed, use that
+ instead of extracting from request URL.
+
+2003-12-14 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_enterprise.py: Frederico Di Gregorio's patch
+ adding a psycopg test case.
+
+2003-12-09 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.1, based on rc4.
+
+2003-12-06 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/internet/wxreactor.py: Added experimental wxPython reactor,
+ which seems to work better than the twisted.internet.wxsupport.
+
+2003-12-05 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/ssh/filetransfer.py, session.py: added SFTPv3 support
+ to the Conch server.
+
+2003-12-04 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.1rc4, based on rc2. rc3 never happened!
+
+2003-12-04 Brian Warner <warner@lothar.com>
+
+ * twisted/persisted/sob.py (Persistent): fix misspelled class name,
+ add compatibility binding to "Persistant" (sic).
+
+ * twisted/test/test_sob.py: use Persistent
+ * twisted/application/service.py (Application): use Persistent
+
+2003-12-03 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/imap4.py: Added support for the
+ IDLE command (RFC 2177).
+
+2003-12-03 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/python/log.py: Added exception handling to
+ log publishing code. Observers which raise exceptions
+ will now be removed from the observer list.
+
+2003-12-02 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.1rc3.
+
+2003-12-01 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.1rc2 (from CVS HEAD).
+
+2003-12-01 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/python/runtime.py: Added seconds method to Platform
+ class.
+
+ * twisted/internet/base.py, twisted/internet/task.py: Changed
+ use of time.time() to use Platform.seconds() instead.
+
+2003-11-24 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/abstract.py: Changed FileDescriptor's
+ registerProducer method to immediately call the given producer's
+ stopProducing method if the FileDescriptor is in the process of
+ or has finished disconnecting.
+
+2003-11-24 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/imap4.py: Fix incorrect behavior of closing the
+ mailbox in response to an EXPUNGE command.
+
+2003-11-21 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/trial/runner.py: Added missing calls to setUpClass and
+ tearDownClass in SingletonRunner.
+
+2003-11-21 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.1rc1.
+
+2003-11-20 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/imap4.py: Fixed incorrect generation of
+ INTERNALDATE information.
+
+2003-11-20 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/abstract.py: Added an assert to
+ FileDescriptor.resumeProducing to prevent it from being
+ called when the transport is no longer connected.
+
+2003-11-20 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/tasks.py: LoopingCall added.
+
+2003-10-14 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/internet/tasks.py: Deprecated scheduling API removed.
+
+2003-11-18 Jonathan Simms <jonathan@embassynetworks.com>
+
+ * twisted/protocols/ftp.py: refactored to add cred support,
+ pipelining, security.
+ * twisted/test/test_ftp.py: tests for the new ftp
+
+2003-11-18 Sam Jordan <sam@twistedmatrix.com>
+
+ * twisted/protocols/msn.py: support for MSNP8
+ * doc/examples/msn_example.py: small msn example
+
+2003-11-13 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/ssh/agent.py: support for the OpenSSH agent protocol
+ * twisted/conch/ssh/connection.py: fix broken channel retrieval code
+ * twisted/conch/ssh/userauth.py: refactoring to allow use of the agent
+ * twisted/conch/ssj/transport.py: fix intermittent test failure
+ * twisted/internet/protocol.py: add UNIX socket support to
+ ClientCreator
+ * twisted/scripts/conch.py: use the key agent if available, also
+ agent forwarding
+
+2003-11-07 Brian Warner <warner@lothar.com>
+
+ * twisted/application/app.py (getApplication): provide a more
+ constructive error message when a .tac file doesn't define
+ 'application'. Closes issue387.
+
+2003-11-01 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/ssh/common.py: use GMPy for faster math if it's
+ available
+
+2003-10-24 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.0 final. Same codebase as rc2.
+
+2003-10-24 Brian Warner <warner@lothar.com>
+
+ * doc/howto/test-standard.xhtml: Add section on how to clean up.
+
+ * twisted/test/test_conch.py: improve post-test cleanup. Addresses
+ problems seen in issue343.
+
+ * twisted/internet/base.py (ReactorBase.callLater): prefix
+ "internal" parameter names with an underscore, to avoid colliding
+ with named parameters in the user's callback invocation. Closes
+ issue347.
+ (ReactorBase.addSystemEventTrigger)
+ (ReactorBase.callWhenRunning)
+ (ReactorBase.callInThread): same
+ * doc/howto/coding-standard.xhtml (Callback Arguments): explain why
+
+2003-10-22 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.0rc2.
+
+2003-10-21 Andrew Bennetts <spiv@twistedmatrix.com>
+
+ * twisted/lore/tree.py, twisted/lore/lint.py,
+ doc/howto/stylesheet.css: add a plain 'listing' class, for file
+ listings that aren't python source or HTML. This has slightly changed
+ the classes in the generated HTML, so custom stylesheets may need
+ updating.
+
+2003-10-16 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.0alpha3.
+
+2003-10-16 Brian Warner <warner@lothar.com>
+
+ * doc/howto/pb-cred.xhtml: update for newcred. Closes issue172.
+
+2003-10-15 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/base.py: add optional debug code, enabled with
+ base.DelayedCall.debug=True . If active, the call stack which
+ invoked reactor.callLater will be recorded in each DelayedCall. If
+ an exception happens when the timer function is run, the creator
+ stack will be logged in addition to the usual log.deferr().
+
+ * twisted/internet/defer.py: add some optional debug code, enabled
+ with defer.Deferred.debug=True . If active, it will record a stack
+ trace when the Deferred is created, and another when it is first
+ invoked. AlreadyCalledErrors will be given these two stack traces,
+ making it slightly easier to find the source of the problem.
+
+2003-10-15 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.0alpha2 (alpha1 was dead in the water).
+
+2003-10-15 Brian Warner <warner@lothar.com>
+
+ * setup.py: remove cReactor/ to the sandbox. Closes issue318.
+
+2003-10-14 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/web/static.py: registry no longer has support for
+ getting services based on their interfaces.
+
+2003-10-14 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.1.0alpha1.
+
+2003-10-13 Bob Ippolito <bob@redivi.com>
+
+ * doc/howto/choosing-reactor.xhtml:
+ Added cfreactor/Cocoa information.
+
+ * doc/examples/cocoaDemo:
+ Removed, replaced by doc/examples/Cocoa cfreactor demos.
+
+ * doc/examples/Cocoa:
+ Moved from sandbox/etrepum/examples/PyObjC, cleaned up.
+
+ * twisted/internet/cfsupport, twisted/internet/cfreactor.py:
+ Moved from sandbox/etrepum, cleaned up.
+
+ * twisted/application/app.py:
+ Added 'cf' -> twisted.internet.cfreactor to reactorTypes
+
+ * setup.py:
+ sys.platform=='darwin' - build cfsupport, do not build cReactor.
+
+ * INSTALL:
+ Changed URL of pimp repository to shorter version.
+
+2003-10-12 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * bin/tktwistd, twisted/scripts/tktwistd.py, doc/man/tktwistd.1:
+ Removed.
+
+2003-10-12 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/spread/pb.py: Perspective Broker no longer sends
+ detailed tracebacks over the wire unless the "unsafeTracebacks"
+ attribute is set of the factory.
+
+2003-10-02 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * setup.py, twisted/test/test_dir.py, twisted/python/_c_dir.c:
+ Removed _c_dir extension module for portability and maintenance
+ reasons.
+
+2003-10-03 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/spread/util.py twisted/test/test_spread.py: Fix issue
+ 286
+
+2003-10-01 Brian Warner <warner@lothar.com>
+
+ * twisted/web/client.py (HTTPDownloader): accept either a filename
+ or a file-like object (it must respond to .write and .close, and
+ partial requests will not be used with file-like objects). errback
+ the deferred if an IOError occurs in .open, .write. or .close,
+ usually something like "permission denied" or "file system full".
+ Closes issue234.
+ * twisted/test/test_webclient.py (WebClientTestCase.write): verify
+ that the errback gets called
+
+ * twisted/scripts/trial.py (run): add --until-failure option to
+ re-run the test until something fails. Closes issue87.
+
+2003-09-30 Brian Warner <warner@lothar.com>
+
+ * twisted/test/test_conch.py (testOurServerOpenSSHClient): replace
+ reactor.run() with .iterate calls: when using .run, exceptions in
+ the server cause a hang.
+
+2003-9-29 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/tap/procmon.py twisted/plugins.tml: remove procmon
+ tap. It was crufty and hard to port properly to new application.
+
+2003-09-29 Brian Warner <warner@lothar.com>
+
+ * twisted/scripts/trial.py (Options.opt_reactor): make trial
+ accept the same reactor-name abbreviations as twistd does. Closes
+ issue69.
+ (top): add test-case-name tag
+
+ * doc/man/trial.1: document the change
+
+2003-09-28 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.8alpha3.
+
+2003-09-27 Cory Dodt <corydodt@yahoo.com>
+
+ * win32/main.aap win32/pyx.x-foo.iss.template win32/README.win32:
+ Be nice to people who don't install Python for "All Users" on win32.
+
+2003-9-18 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/application/strports.py twisted/test/test_strports.py:
+ New API/mini-language for defining ports
+
+2003-9-18 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/spider.py: removed, it was unmaintained.
+
+2003-09-19 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/names/authority.py twisted/test/test_names.py
+ twisted/protocols/dns.py: Client and server support for TTLs on
+ all records. All Record_* types now take a ttl= keyword
+ argument. You can pass the ttl= argument to all the record classes
+ in your pyzones, too.
+
+2003-09-19 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/application/__init__.py twisted/application/app.py
+ twisted/application/compat.py twisted/application/internet.py
+ twisted/application/service.py twisted/scripts/twistd.py
+ twisted/scripts/twistw.py twisted/scripts/mktap.py
+ twisted/scripts/tapconvert.py bin/twistw: Update to new-style
+ applications.
+
+2003-09-19 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/names/client.py: Instantiation of theResolver global made
+ lazy. As a result importing it directly will now fail if it has not
+ yet been created. It should not be used directly anymore; instead,
+ use the module-scope lookup methods, or instantiate your own
+ resolver.
+
+ * twisted/mail/relaymanager.py: Instantiation of MXCalculator made
+ lazy.
+
+2003-09-18 Stephen Thorne <stephen@thorne.id.au>
+
+ * twisted/web/distrib.py: Removed dependancy on twisted.web.widgets, and
+ instead using woven.
+
+2003-09-18 Stephen Thorne <stephen@thorne.id.au>
+
+ * doc/howto/woven-reference.html: Added this new documentation file.
+ * doc/howto/index.html: Added woven-reference to index
+ * admin/: Added woven-reference.tex to book.tex
+
+2003-09-18 Stephen Thorne <stephen@thorne.id.au>
+
+ * twisted/web/woven/widgets.py: Stop the 'Option' widget from having a
+ name="" attribute. Closes issue255.
+
+2003-09-16 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.8alpha1.
+
+ * .: Releasing Twisted 1.0.8alpha2 (Fixed Debian packages).
+
+2003-09-13 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.7 (no code changes since 1.0.7rc1).
+
+ * twisted/web/vhost.py: Un-gobble the path segment that a vhost eats
+ when the resource we're wrapping isLeaf. Potentially closes issue125.
+
+2003-09-12 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/web/microdom.py: lenient mode correctly handles <script>
+ tags with CDATA or comments protecting the code (closes issue #231).
+
+2003-09-10 Tommi Virtanen <tv@twistedmatrix.com>
+
+ * HTTPS support for XML-RPC and web clients (closes issue #236).
+
+2003-08-29 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.7rc1.
+
+2003-09-12 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/spread/pb.py: new cred support for Perspective Broker.
+
+2003-08-26 Dave Peticolas <dave@krondo.com>
+
+ * doc/howto/xmlrpc.html: document sub-handler and introspection
+
+ * twisted/test/test_xmlrpc.py: test introspection support
+
+ * twisted/web/xmlrpc.py: implement sub-handlers and introspection
+ support
+
+2003-08-23 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/gtk2reactor.py: force timeout values to be
+ integers, because recent pygtk's complain when they get floats
+
+2003-08-19 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.7alpha5.
+
+2003-08-18 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/imap4.py: Remove support code for old versions
+ of IMailbox.fetch(); also change the interface once again (no
+ backwards compat this time) to require sequence numbers to be
+ returned, not just whatever the MessageSet spit out.
+
+2003-08-16 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_import.py: update for enterprise
+
+ * twisted/enterprise/sqlreflector.py: use dbpool directly
+
+ * twisted/enterprise/row.py: deprecate KeyFactory and StatementBatch
+
+ * twisted/enterprise/dbpassport.py: remove
+
+ * twisted/enterprise/dbgadgets.py: deprecate all
+
+ * twisted/enterprise/dbcred.py: deprecate all
+
+ * twisted/enterprise/adbapi.py: deprecate Augmentation. deprecate
+ crufty bits of ConnectionPool API.
+
+2003-08-11 Dave Peticolas <dave@krondo.com>
+
+ * twisted/enterprise/sqlreflector.py: fix docs
+
+2003-08-08 Donovan Preston <dp@twistedmatrix.com>
+
+ * Added getAllPatterns API to Widget, which returns all nodes
+ which have the given pattern name.
+
+ * Refactored List widget to use getAllPatterns, so you can have
+ more than one listHeader, listFooter, and emptyList node.
+
+2003-08-08 Dave Peticolas <dave@krondo.com>
+
+ * twisted/internet/base.py: remove unused internal function.
+
+ * twisted/internet/gladereactor.py: remove unused internal function.
+ clean up imports.
+
+2003-08-07 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.7alpha4.
+
+2003-08-06 Donovan Preston <dp@twistedmatrix.com>
+
+ * Major woven optimizations.
+
+ * Removal of inspect-based hacks allowing backwards compatibility
+ with the old IModel interface. All your IModel methods should take
+ the request as the first argument now.
+
+ * Default to non-case-preserving when importing Woven templates,
+ and case-insensitive microdom. If you are using getPattern or
+ getAttribute in any of your woven code, you will have to make sure
+ to pass all lowercase strings.
+
+ * Removal of __eq__ magic methods in microdom. This was just
+ slowing woven down far too much, since without it python can
+ use identity when looking for a node in replaceChild. This means
+ you will have to explicitly use the isEqualToDocument or
+ isEqualToNode call if you are testing for the equality of microdom
+ nodes.
+
+ * Removal of usage of hasAttribute, getAttribute, removeAttribute
+ from woven for a speed gain at the expense of tying woven slightly
+ closer to microdom. Nobody will notice.
+
+ * Improved getPattern semantics thanks to a patch by Rich
+ Cavenaugh. getPattern will now not look for a pattern below any
+ nodes which have model= or view= directives on them.
+
+2003-08-04 Dave Peticolas <dave@krondo.com>
+
+ * twisted/python/usage.py: use parameter docs if handler
+ method has none. fixes bug displaying trial help.
+
+2003-07-31 Brian Warner <warner@lothar.com>
+
+ * twisted/python/filepath.py (FilePath.__getstate__): allow
+ FilePath objects to survive unpersisting.
+
+2003-07-30 Brian Warner <warner@lothar.com>
+
+ * doc/howto/faq.html: mention spawnProcess vs. os.environ
+
+ * doc/howto/test-standard.html: document usage of .todo and .skip
+
+2003-07-28 Brian Warner <warner@lothar.com>
+
+ * twisted/python/_c_dir.c: hush compiler warning
+
+ * setup.py: add twisted.xish
+
+2003-07-28 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/spread/pb.py (PBClientFactory): a new, superior API for
+ starting PB connections. Create a factory, do a
+ reactor.connectTCP/SSL() etc., then factory.getPerspective().
+
+2003-07-27 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_enterprise.py: enable tests that depend on
+ cp_min and cp_max
+
+ * twisted/enterprise/adbapi.py: use threadpool to handle cp_min and
+ cp_max arguments
+
+ * twisted/test/test_threadpool.py: test existing work
+
+ * twisted/python/threadpool.py: check for existing work in start()
+
+2003-07-25 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/imap4.py: The fetch method of the IMailbox
+ interface has been changed to accept only a MessageSet and a uid
+ argument and to return an IMessage implementor.
+
+2003-07-24 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/cReactor/cDelayedCall.c: implement .active and
+ .getTime methods
+
+ * twisted/test/test_internet.py (InterfaceTestCase.wake): remove
+ reactor.initThreads() call. This is a private method which is
+ triggered internally by the current reactor when threadable.init
+ is called. It does not need to be called independently, and not
+ all reactors implement this particular method.
+
+ * twisted/test/test_threads.py: shuffle test cases, add timeouts
+ to avoid hanging tests. Added (disabled) test to trigger cReactor
+ hang (but unfortunately it fails under the default reactor)
+
+2003-07-23 Dave Peticolas <dave@krondo.com>
+
+ * twisted/internet/threads.py: avoid top-level reactor import
+
+2003-07-23 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/imap4.py: The fetch method of the IMailbox
+ interface has been changed to accept a list of (non-string)
+ objects representing the requested message parts. Less knowledge
+ of the IMAP4 protocol should be required to properly implement
+ the interface.
+
+2003-07-23 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_enterprise.py: more tests
+
+2003-07-21 Dave Peticolas <dave@krondo.com>
+
+ * twisted/internet/base.py: implement callWhenRunning
+
+ * twisted/internet/interfaces.py: add callWhenRunning API
+
+ * twisted/test/test_pop3.py: string in string only works in 2.3
+
+2003-07-19 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.7alpha3 (for form and twisted.names
+ updates mentioned below).
+
+2003-07-19 Ying Li <cyli@ai.mit.edu>
+
+ * twisted/web/woven/form.py: Changed form widgets so that if the
+ template already has the widget coded, merges the template widget
+ with the model widget (sets default values, etc.).
+
+ * twisted/web/woven/form.py, twisted/python/formmethod.py: Can
+ format layout of checkgroups and radiogroups into tables, rows, or
+ columns.
+
+ * twisted/web/woven/form.py, twisted/python/formmethod.py: Added
+ file input widget (unable to retrieve filename or file type - have
+ to ask for that separately).
+
+2003-07-19 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/protocols/dns.py, twisted/names: Twisted Names can now
+ return the `authoritative' bit. All of the resolvers in
+ twisted/names/authority.py now set it.
+
+2003-07-17 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.7alpha2 (Debian packages should be
+ correct now)
+
+2003-07-17 Dave Peticolas <dave@krondo.com>
+
+ * doc/howto/components.html: methods in interfaces do have self
+ parameters
+
+2003-07-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/web/client.py: Added a `timeout' keyword argument to
+ getPage; If the web page takes longer than `timeout' to fetch,
+ defer.TimeoutError is errbacked.
+
+ * twisted/web/server.py, twisted/protocols/http.py: add `timeout'
+ argument to HTTPFactory and Site to specify how long to allow
+ connections to sit without communication before disconnecting
+ them.
+
+2003-07-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.7alpha1.
+
+2003-07-17 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/smtp.py: Address class changed to provide a
+ default domain for addresses missing a domain part.
+
+2003-07-16 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/protocols/sux.py: In beExtremelyLenient mode, all data
+ in script elements is considered plain text and will not be parsed
+ for tags or entity references.
+
+2003-07-15 Dave Peticolas <dave@krondo.com>
+
+ * twisted/persisted/styles.py: better debugging output
+ for Ephemeral
+
+2003-07-14 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/cred/checkers.py, twisted/cred/credentials.py:
+ CramMD5Credentials and OnDiskUsernamePasswordDatabase added;
+ IUsernameHashedPassword also created for use by protocols that
+ do not receive plaintext passwords over the network.
+
+ * twisted/mail/, twisted/protocols/smtp.py: Addition of alias
+ support and authenticated ESMTP connections. Several interfaces
+ changed, but deprecation warnings and backwards compatibility code
+ has been put in place to ease the change.
+
+2003-07-12 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/web/util.py: Add a new ChildRedirector that, when placed
+ at /foo to redirect to /bar, will also redirect /foo/abc to
+ /bar/abc.
+
+ * twisted/web/scripts.py: Fixed ResourceScriptWrapper so that you
+ can now .putChild on the resource you create in an .rpy file that
+ is wrapped with this class.
+
+2003-07-06 Paul Swartz <z3p@twistedmatrix.com>
+ * twisted/conch/[checkers,credentials,pamauth].py,
+ twisted/conch/ssh/userauth.py, twisted/tap/conch.py: made PAM
+ work again as an authentication.
+
+2003-07-05 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_enterprise.py: more tests. Add mysql test.
+
+2003-07-05 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/web/soap.py: Now requires SOAPpy v0.10.1, allow subclasses
+ to determine method publishing strategy.
+
+2004-07-05 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * bin/mailmail, doc/man/mailmail.1, twisted/scripts/mailmail.py:
+ sendmail replacement
+
+2003-07-04 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_enterprise.py: add sqlite. more tests.
+ Add Postgres test.
+
+ * twisted/enterprise/util.py: fix bug in getKeyColumn
+
+ * twisted/enterprise/sqlreflector.py: clean up imports
+
+ * twisted/enterprise/row.py: clean up imports
+
+ * twisted/enterprise/reflector.py: clean up imports
+
+2004-07-04 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/python/dir.c: Wrapper around opendir(3), readdir(3),
+ and scandir(3) for use by twisted.python.plugins.
+
+2003-07-03 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/news/database.py: NewsShelf.articleRequest() and
+ NewsShelf.bodyRequest() now expected to return a file-like object
+ in the last position of its returned three-tuple. The old API
+ is still supported, but deprecated.
+
+2003-07-03 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_enterprise.py: add gadfly test
+
+ * twisted/web/woven/input.py: remove excess newline.
+
+ * twisted/trial/unittest.py: take out unused methodPrefix var
+
+ * twisted/enterprise/adbapi.py: accept 'noisy' kw arg. persist
+ noisy, min, and max args. just warn about non-dbapi db libs.
+
+ * twisted/enterprise/reflector.py: fix spelling
+
+ * twisted/enterprise/sqlreflector.py 80 columns, don't addToCache
+ in insertRow
+
+ * twisted/enterprise/xmlreflector.py: 80 columns
+
+2003-07-01 Brian Warner <warner@lothar.com>
+
+ * sandbox/warner/fusd_twisted.py: experimental glue code for FUSD,
+ a system for implementing Linux device drivers in userspace
+
+2003-06-27 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.6rc3. Fixed a security bug in
+ twisted.web.
+
+ * .: Releasing Twisted 1.0.6rc4. One more twisted.web bug.
+
+ * .: Releasing Twisted 1.0.6.
+
+2003-06-26 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.6rc1.
+
+ * .: Releasing Twisted 1.0.6rc2. Pop3 had failing tests.
+
+2003-06-26 Clark C. Evans <cce@twistedmatrix.com>
+
+ * twisted/flow/*.py: Moved Flow from the sandbox to
+ twisted.flow. The callback is dead. Long live the callback!
+
+2003-06-26 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/pop3.py: POP3.authenticateUserXYZ no longer
+ returns a Mailbox object. It now returns a 3-tuple. See
+ twisted.cred.portal.Portal.login for more details about the return
+ value.
+
+2003-06-24 Brian Warner <warner@lothar.com>
+
+ * doc/howto/upgrading.html: Explain Versioned and rebuild()
+
+2003-06-23 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/scripts/trial.py twisted/trial/reporter.py
+ doc/man/trial.1:
+
+ Added a --tbformat={plain,emacs} option to trial. Now the default
+ is to show the regular python traceback; if you want tracebacks
+ that look like compiler output for emacs, use --tbformat=emacs.
+
+2003-06-23 Cory Dodt <corydodt@yahoo.com>
+
+ * twisted/python/util.py twisted/web/microdom.py
+ twisted/test/test_{util,xml}.py: preserveCase and caseInsensitive
+ work on attribute names as well as element names.
+
+2003-06-22 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/defer.py: Changed maybeDeferred API from
+ maybeDeferred(deferred, f, *args, **kw) to maybeDeferred(f, *args,
+ **kw).
+
+2003-06-19 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/{checkers,credentials,realm}.py,
+ twisted/conch/ssh/userauth.py: Moved the Conch user authentication
+ code to use the new version of Cred.
+
+2003-06-19 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.6alpha3. There was a problem in
+ twisted.python.compat that was breaking the documentation
+ building. It is now fixed.
+
+2003-06-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.6alpha2.
+
+2003-06-16 Donovan Preston <dp@twistedmatrix.com>
+
+ * twisted/web/woven/{controller,view,widgets}.py: Cleaned up the
+ output of Woven so it never leaves any woven-specific attributes
+ on the output HTML. Also, id attributes are not set on every
+ node with a View unless you are using LivePage.
+
+2003-06-11 Brian Warner <warner@lothar.com>
+
+ * doc/howto/cvs-dev.html: add "Working from CVS" hints
+
+2003-06-10 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/internet/protocol.py: connection refused errors for
+ connected datagram protocols (connectUDP) are indicated using
+ callback, ConnectedDatagramProtocol.connectionRefused, rather
+ than an exception as before.
+
+2003-06-09 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/trial/{unittest,runner}.py: Added setUpClass and
+ tearDownClass methods and invocations to twisted.trial. Implement
+ those methods in your TestCases if you want to manage resources on
+ a per-class level.
+
+2003-06-09 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/mail/relay.py: Default relaying rule change from all
+ local and all non-INET connections to all local and all UNIX
+ connections.
+
+2003-06-08 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/interfaces.py: Added ITLSTransport interface,
+ subclassing ITCPTransport and adding one method - startTLS()
+
+ * twisted/internet/tcp.py: Connector class made to implement
+ ITLSTransport if TLS is available.
+
+2003-06-05 Brian Warner <warner@lothar.com>
+
+ * twisted/conch/ssh/transport.py (ssh_KEX_DH_GEX_INIT): don't use
+ small values for DH parameter 'y'. openssh rejects these because they
+ make it trivial to reconstruct the shared secret. This caused a test
+ failure about 1024 times out of every 65536.
+
+ * twisted/test/test_dirdbm.py (DirDbmTestCase.testModificationTime):
+ dodge a kernel bug that lets mtime get skewed from time(), causing
+ an occasional test failure
+
+2003-06-03 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/__init__.py twisted/internet/app.py
+ * twisted/internet/unix.py twisted/internet/tcp.py
+ * twisted/manhole/ui/gtk2manhole.py twisted/protocols/dns.py
+ * twisted/protocols/smtp.py twisted/protocols/sux.py
+ * twisted/protocols/imap4.py twisted/protocols/sip.py
+ * twisted/protocols/htb.py twisted/protocols/pcp.py
+ * twisted/python/formmethod.py twisted/python/reflect.py
+ * twisted/python/util.py twisted/python/components.py
+ * twisted/spread/jelly.py twisted/spread/newjelly.py
+ * twisted/test/test_components.py twisted/test/test_rebuild.py
+ * twisted/test/test_trial.py twisted/test/test_world.py
+ * twisted/test/test_setup.py twisted/test/test_newjelly.py
+ * twisted/test/test_compat.py twisted/test/test_pcp.py
+ * twisted/test/test_log.py twisted/web/microdom.py
+ * twisted/web/woven/page.py twisted/popsicle/mailsicle.py
+ * twisted/trial/remote.py twisted/trial/unittest.py
+ * twisted/world/allocator.py twisted/world/compound.py
+ * twisted/world/database.py twisted/world/storable.py
+ * twisted/world/structfile.py twisted/world/typemap.py:
+
+ Remove direct usage of twisted.python.compat; Modify __builtin__
+ module to include forward-compatibility hacks.
+
+2003-05-30 Brian Warner <warner@lothar.com>
+
+ * twisted/conch/ssh/keys.py (signData_dsa): Force DSS signature
+ blobs to be 20 bytes long. About 1% of the time, the sig numbers
+ would come out small and fit into 19 bytes, which would result in
+ an invalid signature.
+ * twisted/test/test_conch.py: remove special hacked test case used
+ to find that invalid-signature problem.
+
+2003-05-29 Brian Warner <warner@lothar.com>
+
+ * twisted/python/formmethod.py: this module needs False from compat
+
+ * twisted/internet/process.py (ProcessWriter.writeSomeData):
+ Accomodate Mac OS-X, which sometimes raises OSError(EAGAIN)
+ instead of IOError(EAGAIN) when the pipe is full.
+
+2003-05-27 Brian Warner <warner@lothar.com>
+
+ * twisted/test/test_process.py (EchoProtocol): try to close
+ occasional test failure. Do transport.closeStdin() instead of
+ loseConnection() because the child still has data to write (to
+ stderr). Closing all three streams takes away its voice, forces it
+ to exit with an error, and is probably causing problems.
+
+ * twisted/test/test_factories.py (testStopTrying): stop test after
+ 5 seconds rather than 2000 iterations. Some reactors iterate at
+ different rates.
+
+2003-05-24 Brian Warner <warner@lothar.com>
+
+ * twisted/scripts/trial.py (Options.opt_testmodule): ignore
+ deleted files, recognize twisted/test/* files as test cases
+
+2003-05-22 Brian Warner <warner@lothar.com>
+
+ * twisted/test/test_newjelly.py (JellyTestCase.testUnicode): make
+ sure unicode strings don't mutate into plain ones
+
+2003-05-21 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/tcp.py (Connection.getTcpKeepAlive): Add
+ functions to control SO_KEEPALIVE bit on TCP sockets.
+ * twisted/internet/interfaces.py (ITCPTransport): ditto
+ * twisted/test/test_tcp.py (LoopbackTestCase.testTcpKeepAlive):
+ test it
+
+ * doc/howto/test-standard.html: document test-case-name format
+
+ * doc/howto/coding-standard.html: encourage test-case-name tags
+
+ * twisted/protocols/htb.py, twisted/protocols/irc.py,
+ twisted/protocols/pcp.py, twisted/python/text.py,
+ twisted/spread/pb.py, twisted/trial/remote.py: clean up
+ test-case-name tags
+
+ * twisted/scripts/trial.py (Options.opt_testmodule): try to handle
+ test-case-name tags the same way emacs does
+
+2003-05-21 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * bin/coil, doc/man/coil.1, doc/man/index.html: removed. Coil
+ isn't being maintained, pending a total rewrite.
+
+2003-05-20 Brian Warner <warner@lothar.com>
+
+ * twisted/python/reflect.py (namedAny): re-raise ImportErrors that
+ happen inside the module being imported, instead of assuming that
+ it means the module doesn't exist.
+
+2003-05-19 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/web/server.py: Added two new methods to Request objects:
+ rememberRootURL and getRootURL. Calling rememberRootURL will store
+ the already-processed part of the URL on the request, and calling
+ getRootURL will return it. This is so you can more easily link to
+ disparate parts of your web application.
+
+ * twisted/web/woven/{page,widgets}.py: Updated Woven to take
+ advantage of previously-mentioned Request changes. You can now say
+ `appRoot = True' in the Page subclass that is instantiated by your
+ .rpy (for example), and then use a RootRelativeLink widget
+ (exactly the same way you use a Link widget) to get a link
+ relative to your root .rpy.
+
+2003-05-16 Brian Warner <warner@lothar.com>
+
+ * twisted/scripts/trial.py: catch failures during import of test
+ modules named on the command line too.
+
+ * twisted/trial/unittest.py (TestSuite.addModule): catch all failures
+ during import so that syntax errors in test files don't prevent
+ other tests from being run.
+
+ * twisted/trial/reporter.py (TextReporter): handle both Failures
+ and exception tuples in import errors. Emit the messages before the
+ last summary line so that test-result parsers can still find the
+ pass/fail counts.
+
+ * doc/howto/faq.html: Add note about Ephemeral in the
+ import-from-self twistd entry.
+
+2003-05-13 Brian Warner <warner@lothar.com>
+
+ * twisted/trial/runner.py: sort tests by name within a TestCase
+
+2003-05-13 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/internet/{default,internet}.py: Add an `active' method to
+ DelayedCall, which returns True if it hasn't been called or
+ cancelled.
+
+2003-05-13 Jonathan Lange <jml@twistedmatrix.com>
+
+ * twisted/trial/unittest.py twisted/scripts/trial.py
+ doc/man/trial.1: Add --recurse option to make trial search within
+ sub-packages for test modules.
+
+2003-5-12 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/lore/default.py twisted/lore/latex.py
+ twisted/lore/lint.py twisted/lore/math.py twisted/lore/tree.py
+ twisted/lore/lmath.py twisted/lore/slides.py:
+ Added indexing support to LaTeX and lint, and made sure the
+ config dictionary is passed to the tree processors [this is an
+ API change which might have effect on Lore extensions!]. Rename
+ math to lmath, to avoid some corner-case bugs where it gets mixed
+ with the Python standard module "math".
+
+2003-05-11 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.6alpha1. There was a problem
+ with file descriptors in 1.0.5; some debugging information
+ has been added to this release. The problem should be fixed
+ by alpha2.
+
+2003-05-08 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.5 (same code-base as rc2).
+
+2003-05-08 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/world: Added an object database to Twisted. This is
+ still highly experimental!
+
+2003-5-6 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/trial/reporter.py twisted/scripts/trial.py: Add --timing
+ option to make the reporter output wall-clock time.
+
+2003-05-05 Brian Warner <warner@lothar.com>
+
+ * setup.py (setup_args): s/licence/license/, preferred in python-2.3
+
+2003-05-05 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 1.0.5rc1.
+
+ * .: Releasing Twisted 1.0.5rc2 (only a Debian build problem fixed).
+
+2003-05-05 Brian Warner <warner@lothar.com>
+
+ * twisted/trial/reporter.py: remove ResultTypes, it doesn't really
+ accomplish its goal
+
+ * twisted/trial/unittest.py: move log.startKeepingErrors() from
+ top-level to TestSuite.run(). This fixes the problem of errors
+ being eaten by code which imports unittest for other reasons (like
+ to use trial.remote reporting)
+
+2003-05-04 Brian Warner <warner@lothar.com>
+
+ * twisted/trial/reporter.py (ResultTypes): export legal values for
+ Reporter.reportResults() so remote reporters know what to expect
+
+2003-05-03 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/tcp.py, twisted/internet/ssl.py: TLS support
+ added to TCP connections; startTLS() method added to transport
+ objects to switch from unencrypted to encrypted mode.
+
+2003-05-02 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/protocol.py: Added continueTrying attribute to
+ ReconnectingClientFactory, and increased the number of states where
+ stopTrying() will actually stop further connection attempts.
+
+2003-05-01 Brian Warner <warner@lothar.com>
+
+ * twisted/test/test_trial.py: handle new trial layout
+ * twisted/trial/runner.py (runTest): utility function to help
+ test_trial
+ * twisted/trial/util.py (extract_tb): handle new trial layout,
+ ignore the right framework functions.
+
+2003-05-01 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/python/context.py: call-stack context tree.
+
+ * twisted/python/components.py: support interface-to-interface
+ adapatation, IFoo(o) syntax for adaptation, context-based
+ registries and more.
+
+ * twisted/python/log.py: Totally rewritten logging system.
+
+2003-05-01 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/gtk2reactor.py (Gtk2Reactor._doReadOrWrite):
+ add Anthony's cached-Failure speedup to gtk2 too.
+
+2003-05-01 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/internet/tcp.py, twisted/internet/default.py: cache
+ Failures whose contents are always identical. Speeds up lost
+ connections considerably.
+
+ * twisted/python/failure.py: If you pass only an exception object
+ to Failure(), a stack will not be constructed. Speeds up Failure
+ creation in certain common cases where traceback printing isn't
+ required.
+
+2003-04-29 Brian Warner <warner@lothar.com>
+
+ * twisted/test/test_process.py: make all child processes inherit
+ their parent's environment
+
+ * twisted/web/resource.py, twisted/python/roots.py: add
+ test-case-name tag
+
+ * twisted/web/resource.py (IResource)
+ twisted/spread/refpath.py (PathReferenceAcquisitionContext.getIndex)
+ twisted/python/roots.py (Collection.getEntity): appease pychecker
+
+2003-04-27 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * doc/examples/bananabench.py, twisted/internet/utils.py,
+ twisted/mail/bounce.py, twisted/persisted/styles.py,
+ twisted/python/log.py, twisted/python/reflect.py,
+ twisted/spread/pb.py, twisted/test/test_banana.py,
+ twisted/test/test_iutils.py, twisted/test/test_persisted.py,
+ twisted/test/test_process.py, twisted/web/domhelpers.py,
+ twisted/web/script.py, twisted/web/server.py, twisted/web/test.py:
+ Change the usage of cStringIO to fallback to StringIO if the former
+ is not available.
+
+ * twisted/im/gtkaccount.py, twisted/internet/app.py,
+ twisted/mail/relay.py, twisted/mail/relaymanager.py,
+ twisted/persisted/journal/base.py, twisted/persisted/dirdbm.py,
+ twisted/scripts/conch.py, twisted/scripts/tapconvert.py,
+ twisted/scripts/twistd.py, twisted/scripts/websetroot.py,
+ twisted/test/test_mvc.py, twisted/test/test_persisted.py,
+ twisted/web/woven/template.py, twisted/web/woven/view.py,
+ twisted/popsicle/picklesicle.py: Change the usage of cPickle to
+ fallback to pickle if the former is not available.
+
+ * doc/howto/coding-standard.html: Document the way to use extension
+ versions of modules for which there is a pure-python equivalent.
+
+2003-04-26 Dave Peticolas <dave@krondo.com>
+
+ * twisted/enterprise/adbapi.py: commit successful _runQuery calls
+ instead of rolling back
+
+2003-04-23 Brian Warner <warner@lothar.com>
+
+ * doc/howto/telnet.html: Update example from twisted-0.15.5(!) to
+ 1.0.4
+
+ * twisted/protocols/loopback.py: use reactor.iterate(0.01) so the
+ tests hammer the CPU slightly less
+
+ * twisted/test/test_trial.py (LoopbackTests.testError): .type is a
+ string
+ * twisted/trial/remote.py (JellyReporter.reportResults): stringify
+ .type and .value from Failures before jellying them.
+
+ * twisted/internet/base.py (ReactorBase.suggestThreadPoolSize):
+ don't let suggestThreadPoolSize(0) be the only reason threads are
+ initialized.
+
+ * twisted/python/log.py (err): always log Failures to the logfile. If
+ we're doing _keepErrors, then also add them to _keptErrors.
+
+ * twisted/trial/unittest.py (TestSuite.runOneTest): only do
+ reportResults once per test. Handle reactor.threadpool being None.
+
+2003-04-22 Bob Ippolito <bob@redivi.com>
+
+ * twisted/python/compat.py: Complete iter implementation with
+ __getitem__ hack for 2.1. dict now supports the full 2.3 featureset.
+
+ * twisted/test/test_compat.py: Tests for compat module, so we know if
+ it works or not now ;)
+
+2003-04-22 Andrew Bennetts <spiv@twistedmatrix.com>
+
+ * twisted/lore/latex.py: Handle cross-references and labels slightly
+ better, so that e.g. man/lore.html and howto/lore.html don't generate
+ conflicting labels. Also, emit \loreref{...} instead of \pageref{...}
+ -- this isn't a standard LaTeX command, see admin/book.tex for an
+ example definition. In HTML generation, all relative hrefs in <a>
+ tags are now munged from .html to .xhtml, unless class="absolute".
+
+2003-04-21 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/interfaces.py: Added getServiceNamed, addService,
+ and removeService to IServiceCollection.
+
+2003-04-21 Brian Warner <warner@lothar.com>
+
+ * twisted/web/woven/*.py: add test-case-name tags
+
+2003-04-21 Bob Ippolito <bob@redivi.com>
+
+ * twisted/web/static.py (File, DirectoryListing): DirectoryListing
+ now gets the directory listing from File.listNames, and no longer
+ calls os.listdir directly (unless a directory listing is not
+ specified in the DirectoryListing constructor).
+
+2003-04-19 Brian Warner <warner@lothar.com>
+
+ * twisted/trial/remote.py (JellyReporter.cleanResults): handle
+ strings as testClass/method to unbreak tests
+
+ * twisted/trial/remote.py (JellyReporter.reportResults): send only
+ name of testClass/method to remote reporter, not whole class and
+ method. Also add .taster hook to DecodeReport to let users specify
+ their own security options.
+
+2003-04-17 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * .: Release 1.0.4 Final.
+
+2003-04-16 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * .: Release 1.0.4rc1.
+
+2003-04-15 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * admin/accepttests, admin/accepttests.py: Acceptance tests
+ turned into a Python module with no unguarded top-level code,
+ to make running acceptance tests selectively possible.
+
+2003-04-14 Brian Warner <warner@lothar.com>
+
+ * twisted/python/threadable.py (init):
+ * twisted/spread/newjelly.py (SecurityOptions.allowBasicTypes):
+ * twisted/spread/jelly.py (SecurityOptions.allowBasicTypes):
+ Remove old apply() calls.
+
+ * twisted/spread/flavors.py (Copyable.jellyFor): Use proper
+ jellier .prepare/.preserve dance when .invoker is non-None. This
+ fixes jellying of circular references when passed through PB
+ connections.
+
+ * twisted/test/test_newjelly.py: add test case that sets .invoker
+ to verify that code path too
+
+2003-04-14 Jonathan Lange <jml@ids.org.au>
+
+ * twisted/web/woven/controller.py (Controller): now, if getChild
+ cannot find the requested child, it will ask getDynamicChild -- a
+ method like getChild, but designed to be overriden by users.
+
+2003-04-13 Bob Ippolito <bob@redivi.com>
+
+ * twisted/internet/app.py (DependentMultiService): a MultiService
+ to start services in insert order and stop them in reverse. Uses
+ chained deferreds to ensure that if a startService or stopService
+ returns a deferred, then the next service in the queue will wait
+ until its dependency has finished.
+
+2003-04-12 Brian Warner <warner@lothar.com>
+
+ * twisted/test/test_process.py (PosixProcessTestCasePTY): skip
+ testStdio, testStderr, and testProcess. PTYs do not have separate
+ stdout/stderr, so the tests just aren't relevant. testProcess
+ might be, but it requires support for closing the write side
+ separately from the read side, and I don't think our processPTY
+ can do that quite yet.
+
+ * twisted/test/test_tcp.py (LocalRemoteAddressTestCase): iterate
+ harder. some systems might not connect to localhost before
+ iterate() is called, flunking the test
+
+ * twisted/test/test_process.py: only install SIGCHLD handler if the
+ reactor offers a hook for it.
+
+ * twisted/test/test_policies.py (ThrottlingTestCase.doIterations):
+ add more iterations to accomodate reactors that do less IO per pass
+
+ * twisted/test/process_signal.py: reset SIGHUP to default handler,
+ fixes test failures in a 'nohup' environment
+
+ * twisted/test/test_process.py (PosixProcessTestCasePTY): remove
+ testClosePty.todo now that it works
+ (SignalProtocol.processEnded): Improve testSignal error messages
+
+ * twisted/internet/process.py (PTYProcess.connectionLost): Treat
+ PTYs more like sockets: loseConnection sets .disconnecting and
+ lets the write pipe drain, then the PTY is closed in
+ connectionLost.
+
+2003-04-12 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/plugins.tml, twisted/tap/ssh.py, twisted/tap/conch.py: moved
+ the conch server from 'mktap ssh' to 'mktap conch'.
+
+2003-04-12 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/gtk2reactor.py (Gtk2Reactor.doIteration): don't
+ process *all* events before exiting: lots of IO (like test cases which
+ do connect()s from inside connectionMade) will keep us from surfacing
+ from reactor.iterate(), causing a lockup.
+ * twisted/internet/gtkreactor.py (GtkReactor.doIteration): same. Use
+ the same code as gtk2reactor with minor gtk1-vs-gtk2 variations.
+
+2003-04-11 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/gtk2reactor.py (Gtk2Reactor.doIteration): use
+ timers to match the behavior of select()-based reactors.
+ reactor.iterate(delay) is thus defined to return after 'delay'
+ seconds, or earlier if something woke it up (like IO, or timers
+ expiring).
+
+2003-04-11 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/defer.py: Added new, experimental function,
+ "maybeDeferred". API is subject to change.
+
+2003-04-11 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/scripts/mktap.py: Sped up --debug and --progress by
+ introducing a two-pass option parser.
+
+2003-04-11 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/gtk2reactor.py: major fixes. Use different
+ POLLIN/OUT flags to robustly work around pygtk bug, change
+ callback() to behave more like pollreactor (since gtk uses poll
+ internally). doIteration now calls gtk.main_iteration in a
+ non-blocking way. Attempt to emulate doIteration(delay!=0) by
+ using time.sleep().
+
+ * twisted/internet/gtkreactor.py: same fixes as for gtk2reactor.
+ Instead of a pygtk bug we've got the limited gtk_input_add API,
+ which hides POLLHUP/POLLERR, so detecting closed fds might not be
+ as reliable.
+
+2003-04-11 Andrew Bennetts <spiv@twistedmatrix.com>
+
+ * twisted/lore:
+ Added a "lore-slides" plugin, with HTML, Magicpoint and Prosper output
+ targets. It's still a bit rough, but functional.
+
+2003-04-10 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * .: Release 1.0.4alpha2.
+
+2003-04-09 Brian Warner <warner@lothar.com>
+
+ * twisted/scripts/trial.py (Options.opt_reactor): install reactor
+ before parseArgs() does an import and installs the default one
+
+ * twisted/internet/process.py: fix typo,
+ s/registerReapProccessHandler/registerReapProcessHandler)/
+
+2003-04-09 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/base.py: Change the sort order of DelayedCalls
+ and remove them from the end of the list instead of the beginning.
+ This changes O(n) complexity to O(1) complexity.
+
+2003-04-09 Brian Warner <warner@lothar.com>
+
+ * twisted/test/test_jelly.py, test_newjelly: Test cleanup.
+ Parameterize the jelly module used by the tests, make test_jelly a
+ subclass of test_newjelly using a different jelly module: tests
+ should now be unified. Also change tests to use proper trial
+ self.failUnless() methods instead of bare assert().
+
+2003-04-09 Bob Ippolito <bob@redivi.com>
+
+ * twisted/python/util.py (OrderedDict): added a UserDict subclass
+ that preserves insert order (for __repr__, items, values, keys).
+
+ * twisted/internet/app.py (Application, _AbstractServiceCollection):
+ Preserve service order, start services in order, stop them in reverse.
+
+2003-04-09 Andrew Bennetts <spiv@twistedmatrix.com>
+
+ * twisted/protocols/ftp.py (FTPClient):
+ Added STOR support to FTPClient, as well as support for using
+ Producers or Consumers instead of Protocols for uploading/downloading.
+ * twisted/protocols/policies.py (TimeoutWrapper):
+ Added a timeout policy that can be used to automatically disconnect
+ inactive connections.
+
+2003-04-07 Brian Warner <warner@lothar.com>
+
+ * twisted/test/test_banana.py (BananaTestCase): add Acapnotic's
+ crash-cBanana test case, and some others.
+
+ * twisted/spread/banana.py (Pynana.dataReceived): add 640k limit on
+ lists/tuples, parameterize the limit into banana.SIZE_LIMIT, define
+ and use BananaError on all problems. Impose 640k limit on outbound
+ lists/tuples/strings to catch problems on transmit side too.
+
+ * twisted/spread/cBanana.c (cBanana_dataReceived): check malloc()
+ return values to avoid segfault from oversized lists. Impose 640k
+ limit on length of incoming lists. Raise BananaError on these
+ checks instead of the previously-unreachable
+ cBanana.'cBanana.error' exception.
+
+ * twisted/test/test_process.py (TwoProcessProtocol): add test to make
+ sure killing one process doesn't take out a second one
+ (PosixProcessTestCasePTY): add variant that sets usePTY=1
+
+2003-04-06 Brian Warner <warner@lothar.com>
+
+ * twisted/trial/{unittest.py,remote.py}, twisted/test/test_trial.py:
+ Collapse most reportFoo methods into a single reportResults() that
+ takes a resultType parameter. This anticipates the addition of .todo
+ test-case flags that will add two more resultTypes.
+ * twisted/trial/unittest.py: Add .todo flags: creates EXPECTED_FAILURE
+ and UNEXPECTED_SUCCESS resultTypes. Like .skip, the .todo can be
+ added either to the TestCase object or as a method attribute.
+
+2003-04-04 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/scripts/trial.py: Now takes whatever you throw at it on
+ the command line, be it a filename, or a dotted python name for a
+ package, module, TestCase, or test method; you no longer need to
+ use the -pmcfM switches (unless you really want to).
+
+ * twisted/protocols/htb.py: Egress traffic shaping for Consumers
+ and Transports, using Heirarchial Token Buckets, patterened after
+ Martin Devera's Hierarchical Token Bucket traffic shaper for the
+ Linux kernel.
+
+ * doc/examples/shaper.py: Demonstration of shaping traffic on a
+ web server.
+
+ * twisted/protocols/pcp.py: Producer/Consumer proxy, for when you
+ wish to install yourself between a Producer and a Consumer and
+ subvert the flow of data.
+
+2003-04-04 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/web/microdom.py: parseXML and parseXMLString functions
+ that are setup to use the correct settings for strict XML parsing
+ and manipulation.
+
+2003-03-31 Brian Warner <warner@lothar.com>
+
+ * twisted/trial/unittest.py: use SkipTest's argument as a reason
+ and display it in the test results instead of the traceback. Allow
+ test methods and TestCase classes to define a .skip attribute
+ instead of raising SkipTest.
+
+2003-03-31 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/trial/remote.py: machine-readable trial output to allow
+ for the test runner and the results Reporter to be in seperate
+ processes.
+
+2003-03-15 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/app.py: Renamed "factory" argument to
+ Application.listenUDP() to "proto"
+
+2003-03-13 Tommi Virtanen <tv@twistedmatrix.com>
+
+ * twisted/tap/procmon.py, twisted/plugins.tml: support for mktapping
+ ProcessMonitors.
+
+2003-03-11 Bob Ippolito <bob@redivi.com>
+
+ * twisted/internet/: Replaced apply() in non-deprecated
+ twisted.internet modules with Direct Function Calls per
+ recommendation from PEP 290.
+
+ * twisted/web/client.py: HTTPPageGetter will now write
+ self.factory.postdata to the transport after the headers if the
+ attribute is present and is not None. The factories, getPage and
+ downloadPage now accept keyword arguments for method, postdata,
+ and headers. A Content-Length header will be automatically provided
+ for the given postdata if one isn't already present. Note that
+ postdata is passed through raw; it is the user's responsibility to
+ provide a Content-Type header and preformatted postdata. This change
+ should be backwards compatible.
+
+2003-03-05 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/internet/: reactor.run() now accepts a keyword
+ argument, installSignalHandlers, indicating if signal handlers
+ should be installed.
+
+2003-03-04 Tommi Virtanen <tv@twistedmatrix.com>
+
+ * twisted/scripts/mktap.py, twisted/internet/app.py: mktap now
+ accepts --uid=0 and --gid=0 to really mean root, has command line
+ help for --uid=/--gid=, and understands user and group names in
+ addition to numbers.
+
+2003-03-04 Tommi Virtanen <tv@twistedmatrix.com>
+
+ * twisted/scripts/tap2deb.py, doc/man/tap2deb.1: Option --version=
+ collided with global options, renamed to --set-version=.
+
+2003-03-01 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/scripts/twistd.py: Added --report-profile flag to twistd
+ daemon.
+
+2003-02-24 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/tcp.py, base.py: set FD_CLOEXEC on all new
+ sockets (if available), so they will be closed when spawnProcess
+ does its fork-and-exec.
+
+2003-02-23 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/scripts/manhole.py: 1.4 manhole now defaults to using a
+ GTK2 client where available. Start manhole with the "--toolkit gtk1"
+ parameter if you want the old one back.
+
+2003-2-19 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/monitor.py: Monitor web sites.
+
+2003-2-20 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/internet/{app,default,interface,unix}.py: Add 'mode' argument
+ to the listenUNIX interface, which sets the filesystem mode for the
+ socket.
+
+2003-2-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Release 1.0.4alpha1.
+
+2003-2-18 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/server.py twisted/protocols/http.py: Add a way for
+ resources (and other interested parties) to know when a request has
+ finished, for normal or abnormal reasons.
+
+2003-02-17 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/scripts/conch.py: Added experimental support for connection
+ caching, where if a connection is already available to a server, the
+ client will multiplex another session over the existing connection,
+ rather that creating a new one.
+
+2003-02-16 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * doc/examples/echoserv.py: Rewrote main code to not create a .tap
+ file (examples should be simple, and demonstrate as few things as
+ possible each).
+
+ * doc/examples/echoclient.py: Added UDP echo protocol
+ implementation; it is unused by default, but easily enabled.
+
+2003-02-16 Cory Dodt <corydodt@yahoo.com>
+
+ * twisted/lore/{latex,default}.py: provide a --config book option
+ to Lore, for producing book-level documents from an index page.
+
+2003-02-15 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/scripts/mktap.py, twisted/scripts/twistd.py: Added the
+ --appname and --originalname parameters, respectively.
+
+ * twisted/doc/man/mktap.py, twisted/doc/man/twistd.py: Documented
+ the above two new parameters.
+
+2003-02-12 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/python/text.py (docstringLStrip): 1.6 This will be going
+ away in favor of inspect.getdoc.
+
+2003-02-11 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/im/interfaces.py (IAccount): 1.4 New instance attribute:
+ "client". Also, added methods getGroup and getPerson.
+
+ * twisted/im/basechat.py (ChatUI.getPerson, .getGroup): 1.7 No
+ longer accept a Class parameter. The class of the person/group is
+ determined by the account they are obtained through.
+
+ * twisted/im/basesupport.py (AbstractPerson, AbstractGroup): 1.15
+ Hold a reference to account, not client. Also, lose the "chatui"
+ parameter -- this may require follow-up.
+ (AbstractAccount.__setstate__): 1.15 remove this method. (Why
+ was self.port = int(self.port) in __setstate__?)
+ (AbstractAccount): 1.15 implement getGroup and getPerson here,
+ using _groupFactory and _personFactory factory attributes.
+
+ * twisted/im/gtkchat.py (GtkChatClientUI.getPerson, .getGroup): 1.15
+ follow ChatUI interface changes.
+
+2003-02-09 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/error.py (ProcessDone,ProcessTerminated):
+ * twisted/internet/process.py (Process.maybeCallProcessEnded,
+ * twisted/internet/process.py (PTYProcess.maybeCallProcessEnded,
+ record the signal that killed the process in .signal, set .signal
+ to None if the process died of natural causes, set .exitCode to None
+ if the process died of a signal.
+ * twisted/test/test_process.py: verify .signal, .exitCode are set
+ to None when they ought to be, verify signal-death is reported with
+ ProcessTerminated and not ProcessDone
+
+ * ChangeLog: Set add-log-time-format to iso8601.
+
+2003-02-09 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing 1.0.3rc1.
+
+2003-02-08 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/tap/mail.py twisted/mail/tap.py twisted/plugins.tml:
+ Moved from tap to mail, trying to thin down twisted.tap a little.
+
+2003-02-07 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/lore/default.py twisted/lore/tree.py twisted/lore/latex.py
+ twisted/lore/man2lore.py twisted/lore/math.py
+ twisted/scripts/html2latex.py twisted/scripts/generatelore.py
+ twisted/scripts/hlint.py twisted/scripts/lore.py bin/lore
+ bin/generatelore bin/hlint bin/html2latex twisted/plugins.tml:
+ refactor lore to be cleaner, more usable and more extendible.
+ Removed old scripts, and combined them into one plugin-based script
+ which supports Lore, Math-Lore and Man pages and converts to
+ LaTeX, HTML and (man pages) to Lore.
+
+2003-02-06 Bob Ippolito <bob@redivi.com>
+
+ * twisted/protocols/smtp.py: sendEmail supports multipartboundary
+ keyword argument, which is useful for doing HTML emails if passed
+ "alternative" as opposed to the default "mixed". Uses 7bit
+ encoding for mime types that start with 'text', base64 otherwise.
+
+2003-02-04 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/app.py: listenUNIX and unlistenUNIX methods added
+ to Application class. These should be used in place of listenTCP
+ and unlistenTCP when UNIX sockets are desired. The old,
+ undocumented behavior no longer works! Also added connectUDP and
+ unlistenUDP to Application.
+
+2003-01-31 Cory Dodt <corydodt@yahoo.com>
+
+ * twisted/lore/latex.py: Don't treat comments like text nodes, just
+ drop them.
+
+2003-01-30 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/default.py
+ twisted/internet/base.py
+ twisted/internet/tcp.py
+ twisted/internet/ssl.py
+ twisted/internet/udp.py
+ twisted/internet/unix.py
+
+ Refactor of many internal classes, including Clients and
+ Connectors. UNIX socket functionality moved out of the TCP classes
+ and into a new module, unix.py, and implementation of IReactorUNIX
+ by PosixReactorBase made conditional on platform UNIX socket
+ support. Redundant inheritance cruft removed from various classes.
+
+ * twisted/internet/app.py: listenWith, unlistenWith, and connectWith
+ methods added to Application.
+
+ * twisted/internet/interfaces.py: IReactorArbitrary added.
+
+2003-01-30 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/manhole/service.py (IManholeClient.console): 1.35
+ exception messages now use a Failure.
+ (IManholeClient.listCapabilities): 1.35 Method to describe what
+ capabilities a client has, i.e. "I can receive Failures for
+ exceptions."
+
+2003-01-29 Donovan Preston <dp@twistedmatrix.com>
+
+ * twisted/web/woven/controller.py
+ twisted/web/woven/template.py
+ twisted/web/woven/view.py
+ twisted/web/woven/widgets.py Major woven codepath cleanup
+
+ * Uses a flat list of outstanding DOM nodes instead of
+ recursion to keep track of where Woven is in the page
+ rendering process
+
+ * Removes View's dependency on DOMTemplate as a base
+ class, in preparation for deprecation of DOMTemplate
+ (all of the same semantics are now directly implemented
+ in View). As a result, View has no base classes, making
+ the inheritance chain cleaner.
+
+ * Stores the namespace stacks (model, view, and controller
+ name lookup chain) in the View directly, and each widget
+ gets an immutable reference to it's position in the lookup
+ chain when it is created, making re-rendering Widgets more
+ reliable
+
+ * Represents the namespace stacks as a cons-like tuple
+ structure instead of mutable python lists, reducing
+ confusion and list-copying; instead of copying the current
+ stack lists each time a Widget is created, it just gets a
+ reference to the current tuples for each of the stacks
+
+2003-01-29 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing 1.0.2 Final.
+
+ * .: Releasing 1.0.3alpha1. Release Often :-D
+
+2003-01-29 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/internet/abstract.py (FileDescriptor.__init__): 1.36
+ Ephemeral.
+
+ * twisted/internet/tcp.py (Port.__getstate__): 1.100 As an
+ Ephemeral, this needs no __getstate__.
+
+2003-01-27 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/spread/ui/gtk2util.py (login): Perspective Broker login
+ dialog for GTK+ version 2.
+
+2003-01-26 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing 1.0.2rc1.
+
+ * .: Releasing 1.0.2rc2 (rc1 was dead in the water; hlint bug now
+ fixed).
+
+ * .: Releasing 1.0.2rc3 (rc2 was dead in the water;
+ twisted.lore.latex bug now fixed)
+
+2003-01-26 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/im/interfaces.py (IClient.__init__): 1.3 Accept a
+ logonDeferred parameter. The client should call this back when
+ it is successfully logged in.
+
+ * twisted/im/basesupport.py
+ (AbstractClientMixin.registerAsAccountClient): 1.13 Gone.
+ chatui.registerAccountClient is called in AbstractAccount.logOn
+ instead.
+
+2003-01-22 Dave Peticolas <dave@krondo.com>
+
+ * twisted/web/xmlrpc.py: add docstring for Proxy. handle
+ serialization errors. check for empty deferred on connectionLost.
+
+ * twisted/test/test_internet.py: make sure wakeUp actually works
+
+2003-01-21 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/internet/defer.py: added utility method for
+ getting result of list of Deferreds as simple list.
+
+2003-1-20 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/interfaces.py: type argument removed from
+ IReactorCore.resolve method. IReactorPluggableResolver interface
+ added.
+
+ * twisted/internet/base.py: IReactorPluggable added to
+ ReactorBase.__implements__ and ReactorBase.installResolver added.
+
+2003-1-18 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/trial/unittest.py twisted/scripts/trial.py: adding --summary
+
+2003-01-15 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing 1.0.2alpha3.
+
+2003-01-13 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing 1.0.2alpha2.
+
+2003-01-11 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/protocols/shoutcast.py: add client support for
+ Shoutcast MP3 streaming protocol.
+
+2003-01-10 Itamar Shtull-Trauring <itamar@itamarst.org>
+
+ * twisted/scripts/twistd.py: in debug mode, jump into debugger for any
+ logged exception.
+
+2003-01-10 Dave Peticolas <dave@krondo.com>
+
+ * twisted/trial/unittest.py: enable test cruft checking
+
+ * twisted/test/test_policies.py: cleanup timers
+
+ * twisted/protocols/policies.py: start/stop bandwidth timers as needed
+
+ * twisted/test/test_internet.py: cleanup timers
+
+ * twisted/test/test_woven.py: expire sessions to clean up timers
+
+ * twisted/web/woven/guard.py: stop timer when session expires
+
+2003-1-9 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/google.py: Search google for best matches
+
+2003-01-09 Dave Peticolas <dave@krondo.com>
+
+ * twisted/protocols/http.py: start/stop log timer as needed
+
+2003-01-08 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_smtp.py: cleanup timers after test
+
+ * twisted/trial/unittest.py: keep errors that are logged and
+ submit them as test failures when tests are finished.
+
+ * twisted/python/log.py: if errors are being kept, don't print
+ them
+
+2003-1-8 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * doc/man/trial.1 twisted/scripts/trial.py: Add -l/--logfile argument
+ to allow giving a log file.
+
+ * twisted/trial/unittest.py: add SkipTest exception, which tests can
+ raise in their various test* method to skip a test which is not
+ excpected to pass.
+
+2003-01-08 Jonathan M. Lange <jml@mumak.net>
+
+ * twisted/trial/*, bin/trial, twisted/scripts/trial.py,
+ doc/man/trial.1: Added 'trial', a new unit testing framework for
+ Twisted.
+
+ * twisted/test/test_*, admin/runtests: Moved existing tests over to
+ trial.
+
+2003-01-06 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/python/microdom.py: Added beExtremelyLenient mode (for
+ parsing "tag soup"). While this isn't quite as lenient as Mozilla
+ or IE's code (it will, for example, translate
+ <div><i><b>foo</i>bar</b></div> to <div><i><b>foo</b></i>bar</div>
+ ) I am still rather proud of the wide range of complete garbage
+ that it will mangle into at least reasonably similar XHTML-esque
+ documents.
+
+2003-01-05 Brian Warner <warner@lothar.com>
+
+ * twisted/internet/cReactor/*, setup.py: Implement getDelayedCalls for
+ cReactor. Create cDelayedCall class, implement .cancel(), .reset(),
+ and .delay() for them.
+
+2003-01-03 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/python/components.py: Fix bug due to interaction between
+ Componentized subclasses and twisted.python.rebuild.rebuild()
+
+ * twisted/python/reflect.py: Removed backwards compatability hack
+ for deprecated name twisted.protocols.telnet.ShellFactory and empty
+ oldModules dictionary.
+
+2003-01-02 Brian Warner <warner@lothar.com>
+
+ * twisted/test/test_internet.py (DelayedTestCase): add test
+ coverage for IReactorTime.getDelayedCalls
+
+2002-12-30 Brian Warner <warner@lothar.com>
+
+ * pyunit/unittest.py (TestCase.__call__): clean the reactor between
+ tests: cancel any leftover reactor.callLater() timers. This helps
+ to keep deferred failures isolated to the test that caused them.
+
+2002-12-30 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/*: added docstrings to most conch classes and functions
+
+2002-12-30 Brian Warner <warner@lothar.com>
+
+ * twisted/spread/pb.py (Broker.connectionLost): clear localObjects
+ too, to break a circular reference involving AuthServs that could
+ keep the Broker (and any outstanding pb.Referenceables) alive
+ forever.
+
+2002-12-29 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/python/compat.py: Single module where all compatability
+ code for supporting old Python versions should be placed.
+
+2002-12-28 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/web/woven/guard.py: Newer, better wrappers for
+ authentication and session management. In particular a nice
+ feature of this new code is automatic negotiation with browsers on
+ whether cookies are enabled or not.
+
+2002-12-27 Paul Swartz <z3p@twistedmatrix.com>
+
+ * bin/tkconch: initial commit of tkconch, a SSH client using Tkinter
+ as a terminal emulator. puts up a menu to configure when run without
+ arguments.
+
+ * twisted/conch/ui: moved ansi.py and tkvt100.py to t.c.ui so they are
+ away from the purely conch stuff.
+
+2002-12-25 Christmas Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing 1.0.2alpha1 - Merry Christmas!
+
+2002-12-25 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/dict.py: dict client protocol implementation
+ from Pavel "Pahan" Pergamenshchik (<pp64@cornell.edu>)
+
+2002-12-23 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * doc/examples/testdns.py and doc/examples/dns-service.py added as
+ simple example of how to use new DNS client API.
+
+2002-12-23 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/xmlrpc.py: added XML RPC client support
+
+2002-12-22 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/ssh/keys.py, twisted/conch/ssh/asn1.py: support for
+ writing public and private keys.
+
+ * bin/ckeygen: new script to create public/private key pairs
+
+2002-12-22 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/protocols/dns.py: Support for AFSDB, RP, and SRV RRs
+ added.
+
+2002-12-18 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/persisted/dirdbm.py: copyTo and clear methods added
+ to DirDBM class
+
+2002-12-18 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/ssh/connection.py, twisted/test/test_conch: fixes to
+ work on Python 2.1.
+
+ * twisted/internet/process.py: usePTY now can be an optional tuple of
+ (masterfd, slavefd, ttyname).
+
+2002-12-18 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/rewrite.py: it works now, even when used as a rootish
+ resource. Also, the request.path is massaged.
+
+2002-12-13 Dave Peticolas <dave@krondo.com>
+
+ * twisted/enterprise/util.py: support numeric type
+
+2002-12-13 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/client.py: add 301/302 support
+
+2002-12-13 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_ftp.py: give client time to start up (fixes
+ one test for gtk/gtk2 reactors)
+
+ * twisted/protocols/ftp.py: ftp client in passive mode should not
+ close data until both command and protocol are finished. (fixes
+ one test in gtk/gtk2 reactors)
+
+ * twisted/internet/gtkreactor.py: remove redundant code
+
+ * twisted/internet/gtk2reactor.py: remove redundant code
+
+ * twisted/internet/abstract.py: fix spelling in documentation
+
+2002-12-12 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_jelly.py: test class serialization
+
+ * twisted/spread/jelly.py: join module names with '.' in
+ _unjelly_class
+
+2002-12-12 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/pamauth.py: added, gives support for authentication
+ using PAM.
+
+ * twisted/conch/*: support for the keyboard-interactive authentication
+ method which uses PAM.
+
+2002-12-12 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/python/log.py: add setStdout, set logfile to NullFile by
+ default.
+
+2002-12-11 Donovan Preston <dp@twistedmatrix.com>
+
+ * Added new woven example, Hello World.
+
+ * Updated woven howto to talk about Hello World. TODO: Finish refactoring
+ woven quotes example, then write more advanced woven howtos on writing
+ Widgets and InputHandlers.
+
+2002-12-11 Paul Swartz <z3p@twistedmatix.com>
+
+ * twisted/conch/*: enabled 'exec' on the server, disabled core dumps,
+ and some fixes
+
+2002-12-10 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/*: many fixes to conch server, now works and can run
+ as root.
+
+ * twisted/conh/ssh/session.py: fix root exploit where a python shell was
+ left acessable to anyone.
+
+2002-12-10 Cory Dodt <corydodt@yahoo.com>
+
+ * t/scripts/postinstall.py: new. Create shortcut icons on win32.
+
+ * twisted-post-install.py: new. Runs t/scripts/postinstall.py
+
+ * setup.py: copy twisted-post-install.py during install_scripts
+
+2002-12-09 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/internet/app.py: actually set the euid/egid if users ask
+
+2002-12-09 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_conch.py: wait for ssh process to finish
+
+ * twisted/scripts/postinstall.py: fix indentation
+
+ * twisted/conch/identity.py: fix indentation
+
+2002-12-09 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/conch/ssh/transport.py: don't accept host keys by default
+ because it's a huge security hole.
+
+2002-12-09 Dave Peticolas <dave@krondo.com>
+
+ * twisted/enterprise/util.py: handle None as null
+
+ * twisted/internet/interfaces.py: add missing 'self' argument
+
+2002-12-08 Dave Peticolas <dave@krondo.com>
+
+ * pyunit/unittest.py: add missing 'self.' prefix to data member
+ reference
+
+ * twisted/enterprise/util.py: make sure quoted values are strings
+ (fixes bug storing boolean types)
+
+2002-12-06 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_internet.py: flush error to prevent failure
+ with non-destructive DeferredLists.
+
+ * twisted/test/test_ftp.py: flush FTPErrors to prevent failures
+ with non-destructive DeferredLists.
+
+ * twisted/test/test_defer.py: catch the errors to prevent failure
+ with non-destructive DeferredLists
+
+ * twisted/enterprise/util.py: add some postgres types. boolean
+ types need to be quoted. remove unused selectSQL variable.
+
+2002-12-05 Dave Peticolas <dave@krondo.com>
+
+ * twisted/enterprise/sqlreflector.py: fix some sql escaping
+ bugs. allow subclasses to override escaping semantics.
+
+ * twisted/enterprise/util.py: allow quote function's string escape
+ routine to be overridden with a keyword argument.
+
+2002-12-5 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/python/plugin.py: fixed a bug that got the wrong plugins.tml
+ if the package was installed in two different places
+
+ * twisted/inetd/*, twisted/runner/*: moved inetd to runner, to live in
+ harmony with procmon
+
+2002-12-04 Dave Peticolas <dave@krondo.com>
+
+ * twisted/test/test_policies.py: Take the start time timestamp
+ immediately before creating the ThrottlingFactory, since the
+ factory starts timing when it is created.
+
+ * admin/runtests: Add a 'gtk2' test type to use the gtk2reactor
+ for the test suite.
+
+2002-12-2 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/client.py: web client
+
+2002-11-30 Paul Swartz <z3p@twistedmatrix.com>
+
+ * Summary of Conch changes: An actual client (bin/conch) which is
+ mostly compatible with the OpenSSH client. An optional C module to
+ speed up some of the math operations. A bunch of other stuff has
+ changed too, but it's hard to summarize a month of work.
+
+2002-11-24 Donovan Preston <dp@twistedmatrix.com>
+
+ * twisted/web/woven/*: Added the beginnings of a general framework for
+ asynchronously updating portions of woven pages that have already been
+ sent to the browser. Added controller.LiveController, page.LivePage,
+ and utils.ILivePage to contain code for dealing with keeping Views alive
+ for as long as the user is still looking at a page and has a live
+ Session object on the server; code for responding to model changed
+ notifications, rerendering Views that depend on those models that have
+ changed; code for sending these rerendered views as html fragments to
+ the browser; and javascript code to mutate the DOM of the live page
+ with the updated HTML. Mozilla only for the moment; ie to come soon.
+
+ * twisted/web/woven/widgets.py: Added API for attaching Python functions
+ to widgets that fire when a given javascript event occurs in the
+ browser.
+ Widget.addEventHandler(self, eventName, handler, *args) and
+ Widget.onEvent(self, request, eventName, *args). The default onEvent
+ will dispatch to event handlers registered with addEventHandler.
+
+2002-11-24 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing 1.0.1.
+
+2002-11-23 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/names/client.py, twisted/names/server.py: Client and
+ server domain name APIs
+
+ * twisted/tap/dns.py: 'mktap dns'
+
+2002-11-23 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/scripts/twistd.py twisted/python/syslog.py: Add syslog support
+
+2002-11-23 Kevin Turner <acapnotic@twistedmatrix.com>, Sam Jordan <sam@twistedmatrix.com>
+
+ * twisted/protocols/irc.py (IRCClient.dccResume, dccAcceptResume):
+ Methods for mIRC-style resumed file transfers.
+ (IRCClient.dccDoSend, IRCClient.dccDoResume)
+ (IRCClient.dccDoAcceptResume, IRCClient.dccDoChat): These are for
+ clients to override to make DCC things happen.
+ (IRCClient.dcc_SEND, dcc_ACCEPT, dcc_RESUME, dcc_CHAT)
+ (IRCClient.ctcpQuery_DCC): Refactored to dispatch to dcc_* methods.
+ (DccFileReceiveBasic.__init__): takes a resumeOffset
+
+2002-11-20 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing 1.0.1rc1
+
+2002-11-16 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * Multicast UDP socket support in most reactors.
+
+2002-11-11 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * .: Releasing 1.0.1alpha4
+
+ * .: Releasing 1.0.1alpha3
+
+2002-11-10 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * .: Releasing 1.0.1alpha2
+
+ * twisted/web/static.py, twisted/tap/web.py: Changed 'mktap web'
+ to use --ignore-ext .ext so that you can assign order to the
+ extensions you want to ignore, and not accidentally catch bad
+ extensions.
+
+2002-11-04 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * twisted/internet/tksupport.py: new, better Tkinter integration.
+ Unlike before, run the reactor as usual, do *not* call Tkinter's
+ mainloop() yourself.
+
+2002-10-25 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/domhelpers.py twisted/python/domhelpers.py
+ twisted/lore/tree.py twisted/web/woven/widgets.py: Moved domhelpers
+ to twisted.web, and add to it all the generic dom-query functions
+ from twisted.lore.tree
+
+ * twisted/scripts/generatelore.py twisted/scripts/html2latex.py
+ bin/html2latex bin/generatelore twisted/lore/__init__.py
+ twisted/lore/latex.py twisted/lore/tree.py: Add the document generation
+ Twisted uses internally to the public interface.
+
+ * twisted/python/htmlizer.py: a Python->HTML colouriser
+
+2002-10-23 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * twisted/web/soap.py: experimental SOAP support, using SOAPpy.
+ See doc/examples/soap.py for sample usage.
+
+2002-10-22 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/python/log.py: Two new features.
+ 1) a stupid `debug' method that simply prefixes a message with "debug"
+ and indents it so it's easier to distinguish from normal messages.
+ This can eventually log to some magic "debug channel", once we have
+ that implemented.
+
+ 2) implemented a custom warning handler; now warnings look sexy.
+ (the hackish overriding of warnings.showwarning is the recommended way
+ to do so, according to the library reference.)
+
+2002-10-22 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * setup.py: conditionalize cReactor on threads support too. This
+ is somewhat of a hack as it it done currently, but it's only necessary
+ on weird OSes like NetBSD. I assume any UNIX with thread support has
+ pthreads.
+
+ * twisted/internet/tksupport.py: tunable reactor iterate delay
+ parameter [by Jp Calderone]
+
+2002-10-17 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * bin/websetroot twisted/scripts/websetroot.py: Added a program to set
+ the root of a web server after the tap exists
+
+2002-10-14 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/vhost.py: add a virtual host monster to support twisted
+ sites behind a reverse proxy
+
+ * twisted/tap/web.py twisted/web/script.py
+ doc/man/mktap.1: adding an option to have a resource script as the root
+
+2002-10-13 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/internet/utils.py twisted/internet/process.py
+ twisted/internet/interfaces.py twisted/internet/default.py: Moved
+ utility functions into twisted.internet.utils
+
+2002-10-12 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/internet/process.py twisted/internet/interfaces.py
+ twisted/internet/default.py: Add utility method to get output of
+ programs.
+
+2002-10-11 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted/internet/wxsupport.py: improved responsiveness of wxPython
+ GUI (50 FPS instead of 10 FPS).
+
+2002-10-08 Brian Warner <warner@twistedmatrix.com>
+
+ * doc/howto: Added PB/cred and Application docs, updated Manhole
+ and Process docs. Moved Manhole from "Administrators" section to
+ "Developers" section.
+
+2002-10-10 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * .: Releasing 0.99.4
+
+2002-10-07 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * .: Release 0.99.4rc1
+
+ * twisted/protocols/http.py: backed out changes to HTTP that
+ broke 0.99.3 twisted.web.distrib.
+
+2002-10-7 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/script.py: Add ResourceTemplate which uses PTL for
+ creation of resources.
+
+2002-10-7 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/tap/web.py: It is now possibly to add processors via
+ the command line
+
+
+2002-10-04 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twistd: when running in debug mode (-b), sending a SIGINT signal
+ to the process will drop into the debugger prompt.
+
+2002-10-5 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * .: Releasing 0.99.3
+
+2002-10-01 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted/protocols/http.py: Fixed many bugs in protocol parsing,
+ found by new unit tests.
+
+2002-9-30 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/protocols/sux.py twisted/web/microdom.py: Made is possible
+ to sanely handle parse errors
+
+2002-09-26 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/internet/app.py (_AbstractServiceCollection.removeService):
+ (MultiService.removeService): inverse of addService
+ (ApplicationService.disownServiceParent): inverse of setServiceParent
+
+2002-9-27 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * .: Releasing 0.99.2
+
+2002-09-26 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/web/microdom.py: Better string formatting of XML
+ elements is now available, to aid with debugging of web.woven
+ (among other applications).
+
+2002-09-25 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/tap/manhole.py: mktap manhole will now prompt for a
+ password or accept one from stdin if one is not provided on the
+ command line.
+
+2002-09-25 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * bin/tapconvert: made sure tapconvert program gets installed.
+
+2002-09-24 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/web/resource.py (Resource.wasModifiedSince): revoked,
+ not adding this after all. Instead,
+
+ * twisted/protocols/http.py (Request.setLastModified)
+ (Request.setETag): these methods to set cache validation headers
+ for the request will return http.CACHED if the request is
+ conditional and this setting causes the condition to fail.
+
+2002-9-24 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * .: Releasing 0.99.2rc2
+
+2002-9-23 Donovan Preston <dp@twistedmatrix.com>
+
+ * Renaming domtemplate/domwidgets/dominput/wmvc to Woven
+ Woven - The Web Object Visualization Environment
+
+ * Created package twisted/web/woven
+
+ * Renamed domtemplate to template, domwidgets to widgets,
+ and dominput to input
+
+ * Refactored wmvc into three modules, model, view, and controller
+
+2002-9-23 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/spread/pb.py: add getObjectAtSSL, refactored into
+ getObjectRetreiver so more transports can be easily supported
+
+2002-09-21 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/protocols/http.py (Request.setLastModified): Use
+ setLastModified to set a timestamp on a http.Request object, and
+ it will add a Last-Modified header to the outgoing reply.
+
+ * twisted/web/resource.py (Resource.wasModifiedSince): companion
+ method, override this to get sensible handling of
+ If-Modified-Since conditional requests.
+
+2002-09-21 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/web/static.py, twisted/web/script.py: Previously, it was
+ not possible to use the same xmlmvc application (directory full
+ of files and all) to interface to separate instances in the same
+ server, without a considerable amount of hassle. We have
+ introduced a new "Registry" object which is passed to all .rpy
+ and .epy scripts as "registry" in the namespace. This is a
+ componentized, so it can be used to associate different
+ components for the same interface for different File instances
+ which represent the same underlying directory.
+
+2002-09-20 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/web/microdom.py: You can now specify tags that the
+ parser will automatically close if they are not closed
+ immediately. This is to support output from HTML editors which
+ will not output XML, but still have a predictable
+ almost-but-not-quite XML structure. Specifically it has been
+ tested with Mozilla Composer.
+
+2002-9-20 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * Documenting for others
+
+ * setup.py: now setup.py can function as a module
+
+ * twisted/enterprise/xmlreflector.py: deprintified
+
+ * twisted/internet/abstract.py, twisted/internet/fdesc.py,
+ twisted/internet/app.py, twisted/internet/gtkreactor.py,
+ twisted/internet/main.py, twisted/internet/protocol.py,
+ twisted/internet/ssl.py, twisted/internet/tksupport.py,
+ twisted/internet/pollreactor.py, twisted/internet/defer.py:
+ added and modified __all__
+
+ * twisted/internet/base.py: changed ReactorBase's __name__, added
+ __all__
+
+ * twisted/internet/default.py, twisted/internet/error.py,
+ twisted/internet/process.py,
+ twisted/internet/win32eventreactor.py: reaping all processes on
+ SIGCHLD, changes in process's API
+
+ * twisted/python/components.py: added Adapter and setComponent
+
+ * twisted/python/log.py: logging several strings works
+
+ * twisted/python/reflect.py: fixed namedModule() to handle packages
+
+ * twisted/web/dom*.py: added submodels, moved to microdom, removed
+ unsafe code
+
+ * twisted/python/mvc.py: changed submodel support, added ListModel,
+ Wrapper
+
+ * twisted/web/microdom.py: minidom compat fixes
+
+2002-9-20 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted/internet/error.py twisted/internet/process.py:
+ ProcessEnded -> ProcessTerminated/ProcessDone. Now it is possible
+ to read off the error code.
+
+2002-9-19 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/scripts/twistd.py: Added ability to chroot. Moved directory
+ change to after loading of application.
+
+2002-9-19 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/*: changed print to log.msg
+
+ * bin/* twisted/scripts/*.py: move code from bin/ to modules
+
+ * twisted/inetd/*.py: inetd server in twisted
+
+ * twisted/protocols/sux.py twisted/web/microdom.py: XML parsing
+
+ * twisted/conch/*.py: better logging and protocol support
+
+ * twisted/cred/*.py: deprecation fixes
+
+ * twisted/internet/app.py: add encryption
+
+ * twisted/internet/base.py: fix deprecation, add DelayedCall,
+ move to connect* from client*
+
+ * twisted/internet/error.py: errno mapping works on more platforms,
+ AlreadyCalled, AlreadyCancelled errors
+
+ * twisted/internet/gtkreactor.py: try requiring gtk1.2, timeout->idle
+
+ * twisted/internet/interfaces.py: added IDelayedCall IProcessTransports
+
+ * twisted/internet/javareactor.py: using failure, better dealing with
+ connection losing, new connect* API
+
+ * twisted/internet/process.py: dealing better with ending
+
+ * twisted/internet/protocol.py: factories have a "noisy" attribute,
+ added ReconnectingClientFactory BaseProtocol
+
+ * twisted/internet/ptypro.py: fixed traceback
+
+ * twisted/internet/reactor.py: better guessing of default
+
+ * twisted/internet/tcp.py: failure
+
+ * twisted/internet/win32eventreactor.py: update to new API, support GUI
+
+ * twisted/manhole/service.py: fix deprecation
+
+ * twisted/news/database.py: fix to be 2.1 compat., generating
+ message-id, bytes, lines, date headers, improved storage
+
+ * twisted/news/news.py: UsenetClientFactory, UsenetServerFactory
+
+ * twisted/persisted/marmalade.py: use twisted.web.microdom
+
+ * twisted/protocols/ftp.py: dito, data port uses new client API
+
+ * twisted/protocols/http.py: StringTransport instead of StringIO
+
+ * twisted/protocols/irc.py: stricter parsing, avoid flooding
+
+ * twisted/protocols/loopback.py: new reactor API, loopback over UNIX
+ sockets
+
+ * twisted/protocols/nntp.py: more lenient parsing, more protocol support
+
+ * twisted/protocols/oscar.py: new reactor API
+
+ * twisted/python/components.py: fix setAdapter add removeComponent
+
+ * twisted/python/failure.py: cleanFailure
+
+ * twisted/python/log.py: can now log multiple strings in one go
+
+ * twisted/python/logfile.py: fixed rotation
+
+ * twisted/python/rebuild.py: better 2.2 support
+
+ * twisted/python/util.py: getPassword
+
+ * twisted/scripts/mktap.py: better --help, --type, encryption
+
+ * twisted/spread/*.py: removed deprecation warnings
+
+ * twisted/spread/util.py: improved Pager
+
+ * twisted/tap/news.py: works saner now
+
+ * twisted/tap/ssh.py: can specify authorizer
+
+ * twisted/tap/words.py: can bind services to specific interfaces
+
+ * twisted/web/distrib.py: now works on java too
+
+ * twisted/web/domtemplate.py: improved cache
+
+ * twisted/web/error.py: ForbiddenResource
+
+ * twisted/web/html.py: lower-case tags
+
+ * twisted/web/server.py: use components
+
+ * twisted/web/static.py: added .flac, .ogg, properly 404/403,
+ lower-case tags
+
+ * twisted/web/twcgi.py: fixed for new process API
+
+ * twisted/web/widgets.py: lower-case tags
+
+ * twisted/web/xmlrpc.py: new abstraction for long running xml-rpc
+ commands, add __all__
+
+ * twisted/words/ircservice.py: new connectionLost API
+
+ * twisted/words/service.py: refactoring and error handling
+
+ * twisted/words/tendril.py: lots of fixes, it works now
+
+2002-09-17 Donovan Preston <dp@twistedmatrix.com>
+
+ * Added better error reporting to WebMVC. To do this, I had to
+ remove the use of "class" and "id" attributes on nodes as
+ synonyms for "model", "view", and "controller". Overloading
+ these attributes for three purposes, not to mention their
+ usage by JavaScript and CSS, was just far too error-prone.
+
+2002-09-09 Andrew Bennetts <spiv@twistedmatrix.com>
+
+ * twisted.inetd: An inetd(8) replacement. TCP support should be
+ complete, but UDP and Sun-RPC support is still buggy. This was
+ mainly written as a proof-of-concept for how to do a forking
+ super-server with Twisted, but is already usable.
+
+2002-08-30 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.99.1rc4. There was a bug in the acquisition
+ code, as well as a typo in TwistedQuotes.
+
+2002-08-29 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.99.1rc3. A bug in the release script
+ left .pyc files in the tarball.
+
+2002-08-29 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.99.1rc2. There was a bug with circular
+ imports between modules in twisted.python.
+
+2002-08-28 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.99.1rc1.
+
+2002-08-27 Donovan Preston <dp@twistedmatrix.com>
+
+ * twisted.web.domtemplate: Look up templates in the directory of
+ the module containing the DOMTemplate doing the lookup before
+ going along with regular acquisition.
+
+2002-08-27 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted.*: Lots of minor fixes to make JavaReactor work again.
+
+2002-08-26 Andrew Bennetts <andrew-twisted@puzzling.org>
+
+ * twisted.python.logfile: Added the ability to disable log
+ rotation if logRotation is None.
+
+2002-08-22 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted.news: Added a decent RDBM storage backend.
+
+2002-08-21 Paul Swartz <z3p@twistedmatrix.com>
+
+ * doc/howto/process.html: Process documentation, too!
+
+2002-08-20 Paul Swartz <z3p@twistedmatrix.com>
+
+ * doc/howto/clients.html: Client-writing documentation.
+
+2002-08-20 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted.protocols.nntp: More protocol implemented: SLAVE, XPATH,
+ XINDEX, XROVER, TAKETHIS, and CHECK.
+
+2002-08-19 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * bin, twisted.scripts.*: Migrated all bin/* scripts'
+ implementations to twisted/scripts. This means win32 users will
+ finally have access to all of the twisted scripts through .bat
+ files!
+
+2002-08-19 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted.news, twisted.protocols.nntp: Additional RFC977 support:
+ HELP and IHAVE implemented.
+
+2002-08-19 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted.internet.{process,win32eventreactor,etc}: New and
+ hopefully final Process API, and improved Win32 GUI support.
+
+2002-08-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Everything: Got rid of almost all usage of the `print' statement
+ as well as any usage of stdout. This will make it easier to
+ redirect boring log output and still write to stdout in your
+ scripts.
+
+2002-08-18 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.99.0 final. No changes since rc9.
+
+2002-08-17 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.99.0rc8, with a fix to tap2deb and
+ slightly updated options documentation.
+
+ * Releasing Twisted 0.99.0rc9 with fixes to release-twisted
+ and doc/howto/options.html.
+
+2002-08-16 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.99.0rc6, with some fixes to setup.py
+ * Releasing Twisted 0.99.0rc7, __init__.py fixes.
+
+2002-08-15 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.99.0rc5, with some one severe bug-fix and
+ a few smaller ones.
+
+2002-08-14 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.99.0rc1! ON THE WAY TO 1.0, BABY!
+ * Releasing Twisted 0.99.0rc2! Sorry, typoed the version number in
+ copyright.py
+ * Releasing Twisted 0.99.0rc3! I HATE TAGGING!
+ * Releasing Twisted 0.99.0rc4, some very minor errors fixed.
+
+2002-08-14 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted.internet, twisted.cred: Applications and Authorizers are
+ now completely decoupled, save for a tiny backwards-compatibility.
+
+2002-08-10 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted.internet.defer, twisted.python.failure: Changes to
+ Deferred and Failure to make errbacks more consistent. error
+ callbacks are now *guaranteed* to be passed a Failure instance,
+ no matter what was passed to Deferred.errback().
+
+2002-08-07 Jp Calderone <exarkun@twistedmatrix.com>
+
+ * twisted.python.usage: New "subcommands" feature for
+ usage.Options: Now, you can have nested commands
+ (`cvs commit'-style) for your usage.Options programs.
+
+2002-08-04 Bruce Mitchener <bruce@twistedmatrix.com>
+
+ * twisted.internet: New `writeSequence' method on transport
+ objects: This can increase efficiency as compared to `write`ing
+ concatenated strings, by copying less data in memory.
+
+2002-08-02 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted.cred.service, twisted.internet.app: Application/Service
+ refactor: These two things should be less dependant on each other,
+ now.
+
+2002-07-31 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted.issues: After weeks of hacking in the secret (Austin,
+ TX) hideout with Allen Short, twisted.issues, the successor to
+ Twisted Bugs, is born. Featuring a paranoia-inducing chat-bot
+ interface!
+
+2002-07-30 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted.internet.kqueue: Thanks to Matt Campbell, we now have a
+ new FreeBSD KQueue Reactor.
+
+2002-07-27 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * doc/fun/Twisted.Quotes: Added our seekrut Twisted.Quotes file to
+ Twisted proper.
+
+2002-07-26 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted.spread: "Paging" for PB: this is an abstraction for
+ sending big streams of data across a PB connection.
+
+
+2002-07-23 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted.internet: Rewrite of client APIs. `reactor.clientXXX'
+ methods are now deprecated. See new reactor.connect*
+ documentation. Also Application-level client methods have been
+ reworked, see the Application documentation.
+
+2002-07-23 Bryce Wilcox-O'Hearn <zooko@twistedmatrix.com>
+
+ * twisted.zoot: Application-level implementation of Gnutella.
+
+2002-07-21 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted.im, bin/im: GUI improvements to t-im, and renamed
+ bin/t-im to bin/im (and get rid of old twisted.words client).
+
+2002-07-15 Bryce Wilcox-O'Hearn <zooko@twistedmatrix.com>
+
+ * twisted.protocols.gnutella: Twisted now has an implementation of
+ the Gnutella protocol.
+
+2002-07-15 Sean Riley <sean@twistedmatrix.com>
+
+ * twisted.sister: Now featuring distributed login.
+
+2002-07-15 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted.conch: A new implementation of ssh2, bringing Twisted
+ one step closer to being a complete replacement of all unix
+ services ;-)
+
+2002-07-14 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.19.0! It's exactly the same as rc4.
+
+2002-07-13 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.19.0rc4. All Known Issues in the README have
+ been fixed. This will hopefully be the last release candidate for
+ 0.19.0.
+
+2002-07-07 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.19.0rc3.
+
+2002-07-07 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.19.0rc2.
+
+2002-07-07 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * Releasing Twisted 0.19.0rc1.
+
+2002-07-07 Keith Zaback <krz@twistedmatrix.com>
+
+ * twisted.internet.cReactor: A new poll-based reactor written in
+ C. This is still very experimental and incomplete.
+
+2002-07-07 Donovan Preston <dp@twistedmatrix.com>
+
+ * twisted.web.dom*: Better support in domtemplate/domwidgets etc
+ for Deferreds and Widgets. Also deprecated getTemplateMethods
+ method in favor of automatically looking up methods on the class
+ based on the attributes found in the template. There are some
+ minimal docs already, and better ones coming soon.
+
+2002-06-26 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted.internet.process,interfaces,default: Process now
+ supports SetUID: there are new UID/GID arguments to the process
+ spawning methods/constructors.
+
+2002-06-22 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted.protocols.oscar: totally rewrote OSCAR protocol
+ implementation.
+
+2002-06-18 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted.internet.defer: Deprecated the arm method of Deferred
+ objects: the replacement is a pair of methods, pause and
+ unpause. After the pause method is called, it is guaranteed that
+ no call/errbacks will be called (at least) until unpause is
+ called.
+
+2002-06-10 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/persisted/aot.py, bin/twistd,mktap, twisted/internet/app.py:
+
+ AOT (Abstract Object Tree) experimental source-persistence
+ mechanism. This is a more-concise, easier-to-edit alternative to
+ Twisted's XML persistence, for people who know how to edit Python
+ code. Also added appropriate options to mktap and twistd to
+ load/save .tas (Twisted Application Source) files.
+
+ I will be working on making the formatting better, soon, but it's
+ workable for now.
+
+2002-06-08 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted.internet, twisted.tap.web: Add a --https and related
+ options to 'mktap web'; web is now much more SSL-friendly.
+
+
+2002-06-02 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * twisted.internet: changed protocol factory interface - it now has
+ doStop and doStart which are called in reactors, not app.Application.
+ This turns start/stopFactory into an implementation-specific feature,
+ and also ensures they are only called once.
+
+2002-06-01 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 0.18.0
+
+2002-05-31 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/coil/plugins/portforward.py, twisted/tap/portforward.py:
+ Forgot to add these before rc1 :-) You can use the portforwarder
+ with Coil and mktap again (previously "stupidproxy")
+
+ * twisted/web/static.py: Fixed a bunch of bugs related to redirection
+ for directories.
+
+ * .: Releasing Twisted 0.18.0rc2
+
+2002-05-30 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * Twisted no longer barfs when the Python XML packages aren't available.
+
+2002-05-29 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * .: Releasing Twisted 0.18.0rc1
+
+2002-05-25 Christopher Armstrong <radix@twistedmatrix.com>
+
+ * twisted/spread/pb.py, twisted/internet/defer.py,
+ twisted/python/failure.py, etc:
+
+ Perspective broker now supports Failures! This should make writing
+ robust PB clients *much* easier. What this means is that errbacks will
+ recieve instances of t.python.failure.Failure instead of just strings
+ containing the traceback -- so you can easily .trap() particular
+ errors and handle them appropriately.
+
+2002-05-24 Itamar Shtull-Trauring, Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted.mail cleanups:
+
+ * basic bounce support.
+
+ * removed telnet from mail tap
+
+ * mail domains now receive service in __init__
+
+ * split file system stuff into Queue (renamed from
+ MessageCollection)
+
+ * Put a Queue in service
+
+ * twisted/protocol/smtp.py: changed SMTPClient API so that it returns
+ a file for the message content, instead of a string.
+
+2002-05-23 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * Twisted applications can now be persisted to XML files (.tax) with
+ the --xml option -- this is pretty verbose and needs some optimizations.
+
+2002-05-22 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/persisted/marmalade.py: Marmalade: Jelly, with just a hint
+ of bitterness. An XML object serialization module designed so
+ people can hand-edit persisted objects (like Twisted Applications).
+
+2002-05-21 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * twisted/internet/gtkreactor.py: GTK+ support for win32; input_add
+ is not supported in win32 and had to be worked around.
+
+2002-05-20 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * twisted/pythor/defer.py, twisted/protocols/protocol.py,
+ twisted/internet/defer.py, twisted/internet/protocol.py:
+
+ Moved defer and protocol to twisted.internet to straighten
+ out dependancies.
+
+2002-05-18 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/metrics, twisted/forum: Metrics and Forum are no longer
+ a part of Twisted proper; They are now in different CVS modules, and
+ will be released separately.
+
+2002-05-15 Andrew Bennetts <andrew-twisted@puzzling.org>
+
+ * twisted/protocols/ftp.py: Small fixes to FTPClient that have
+ changed the interface slightly -- return values from callbacks
+ are now consistent for active and passive FTP. Have a look at
+ doc/examples/ftpclient.py for details.
+
+2002-05-12 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * doc/specifications/banana.html: Documentation of the Banana protocol.
+
+2002-05-06 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/im/gtkchat.py: Some more UI improvements to InstanceMessenger:
+ Nicks are now colorful (each nick is hashed to get a color) and
+ messages now have timestamps.
+
+2002-05-04 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * Reactor Refactor! Pretty much all of the twisted.internet.* classes
+ are being depracated in favor of a single, central class called the
+ "reactor". Interfaces are defined in twisted.internet.interfaces.
+ For a much more descriptive comment about this change, see
+ http://twistedmatrix.com/pipermail/twisted-commits/2002-May/002104.html.
+
+2002-05-04 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/spread/pb.py: There is now some resource limiting in PB.
+ Clients can now have the number of references to an object limited.
+
+2002-04-29 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/im/*: Refactored Twisted InstanceMessenger to seperate GUI
+ and logic. Also improved the UI a bit.
+
+2002-04-28 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * twisted/protocols/http.py: log hits using extended log format
+ and make web taps logfile configurable.
+
+2002-04-26 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * twisted/lumberjack/logfile.py: reversed order of rotated
+ logs - higer numbers are now older.
+
+2002-04-24 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * doc/examples/ircLogBot.py: We now have a sample IRC bot that logs
+ all messages to a file.
+
+2002-04-24 Itamar Shtull-Trauring <twisted@itamarst.org>
+
+ * twisted/python/components.py: Twisted's interfaces are now
+ more like Zope's - __implements__ is an Interface subclass
+ or a tuple (or tuple of tuples). Additonally, an instance can
+ implement an interface even if its class doesn't have an
+ __implements__.
+
+2002-04-22 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/python/usage.py: Minor niceties for usage.Options:
+ You can now look up the options of an Options object with
+ optObj['optName'], and you if you define opt_* methods with
+ underscores in them, using dashes on the command line will work.
+
+2002-04-21 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/scripts/mktap.py: No more --manhole* options, use
+ '--append=my.tap manhole' now.
+
+2002-04-20 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * .: Releasing Twisted 0.17.4.
+
+ * twisted/internet/tcp.py: Make unix domain sockets *really*
+ world-accessible, rather than just accessible by "other".
+
+2002-04-19 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted/web/{server,twcgi}.py: Fixed POST bug in distributed
+ web servers.
+
+2002-04-19 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * .: Releasing Twisted 0.17.3.
+
+2002-04-19 Glyph Lefkowitz <carmstro@twistedmatrix.com>
+
+ * twisted/web/distrib.py: Fix a bug where static.File transfers
+ over a distributed-web connection would not finish up properly.
+
+2002-04-18 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * .: Releasing Twisted 0.17.2.
+
+2002-04-18 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/news: A news server and NNTP protocol support courtesy of
+ exarkun. Another step towards Twisted implementations of EVERYTHING
+ IN THE WORLD!
+
+2002-04-17 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/spread/pb.py: Errors during jelly serialization used to
+ just blow up; now they more properly return a Deferred Failure. This
+ will make hangs in PB apps (most notably distributed web) less common.
+
+2002-04-17 Donovan Preston <dp@twistedmatrix.com>
+
+ * Major changes to the capabilities of the static web server, in an
+ attempt to be able to use Twisted instead of Zope at work; my plan is to
+ capture many of the conveniences of Zope without the implicitness and
+ complexity that comes with working around implicit behavior when it fails.
+
+ 1) .trp and .rpy support in the static web server:
+ Very simple handlers to allow you to easily add Resource objects
+ dynamically to a running server, by merely changing files on the
+ filesystem.
+ An .rpy file will be executed, and if a "resource" variable exists upon the
+ execution's completion, it will be returned.
+ A .trp file (twisted resource pickle) will be unpickled and returned. An
+ object unpickled from a .trp should either implement IResource itself,
+ or have a registered adapter in twisted.python.components.
+
+ 2) Acquisition:
+ As resources are being looked up by repeated calls to getChild, this
+ change creates instances of
+ twisted.spread.refpath.PathReferenceAcquisitionContext and puts
+ them in the request as "request.pathRef"
+ Any method that has an instance of the request can then climb up
+ the parent tree using "request.pathRef['parentRef']['parentRef']
+ PathReferenceAcquisitionContext instances can be dereferenced to the
+ actual object using getObject
+ Convenience method: "locate" returns a PathReference to first place
+ in the parent heirarchy a name is seen
+ Convenience method: "acquire" somewhat like Zope acquisition;
+ mostly untested, may need fixes
+
+ 3) DOM-based templating system:
+ A new templating system that allows python scripts to use the DOM
+ to manipulate the HTML node tree. Loosely based on Enhydra.
+ Subclasses of twisted.web.domtemplate.DOMTemplate can override
+ the templateFile attribute and the getTemplateMethods method;
+ ultimately, while templateFile is being parsed, the methods
+ specified will be called with instances of xml.dom.mindom.Node
+ as the first parameter, allowing the python code to manipulate
+ (see twisted.web.blog for an example)
+
+2002-04-17 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/web/static.py, twisted/tap/web.py: Added a new feature
+ that allows requests for /foo to return /foo.extension, which is
+ disabled by default. If you want a --static webserver that
+ uses this feature, use 'mktap web --static <dir> --allow_ignore_ext'.
+
+ * twisted/tap/web.py: Also switched --static to --path; it doesn't
+ make sense to call something that automatically executes cgis, epys,
+ rpys, php, etc., "static". :-)
+
+2002-04-14 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * HTTP 1.1 now supports persistent and pipelined connections.
+
+ User-visible API changes:
+ - Request.content is now a file-like object, instead of a string.
+ - Functions that incorrectly used Request.received instead of
+ Request.getAllHeaders() will break.
+ - sendHeader, finishHeaders, sendStatus are all hidden now.
+
+2002-04-12 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/coil/plugins/tendril.py (TendrilConfigurator): New coil
+ configurator for words.tendril.
+
+2002-04-10 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * .: Releasing Twisted 0.17.0
+
+2002-04-10 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/bugs: Gone. Separate plugin package.
+ * twisted/eco: Gone. The king is dead. Long live the king!
+ (eco is no longer going to be developed, Pyrex has obviated it.)
+
+2002-04-10 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/protocols/irc.py: Some fix-ups to IRCClient and
+ DccFileReceive, from Joe Jordan (psy).
+
+2002-04-10 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/reality: Gone. This is now in a completely separate plugin
+ package.
+
+2002-04-09 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * win32 process support seems to *finally* be working correctly. Many
+ thanks to Drew Whitehouse for help with testing and debugging.
+
+2002-04-08 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * coil refactored yet again, this time to use components and adapters.
+ The design is now much cleaner.
+
+2002-04-08 Glyph Lefkowitz <glyph@zelda.twistedmatrix.com>
+
+ * twisted/spread/jelly.py: Refactored jelly to provide (a) more
+ sane, language-portable API for efficient extensibility and (b)
+ final version of "wire" protocol. This should be very close to
+ the last wire-protocol-breaking change to PB before
+ standardization happens.
+
+2002-04-04 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * Removed __getattr__ backwards compatibility in PB
+
+2002-04-03 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/python/usage.py, twisted/test/test_usage.py, bin/mktap, twisted/tap/*.py:
+ Made the usage.Options interface better -- options are now stored in the
+ 'opts' dict. This is backwards compatible, and I added a deprecation warning.
+
+2002-04-01 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * .: Releasing Twisted 0.16.0.
+
+2002-03-29 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * Added Qt event loop support, written by Sirtaj Singh Kang and
+ Aleksandar Erkalovic.
+
+2002-03-29 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * Added a 'coil' command for configuring TAP files
+
+2002-03-15 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * XML-RPC published methods can now return Deferreds, and Twisted
+ will Do The Right Thing.
+
+2002-03-13 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * Refactored coil, the configuration mechanism for Twisted.
+ See twisted.coil and twisted.coil.plugins for examples of how
+ to use the new interface. Chris Armstrong did some UI improvements
+ for coil as well.
+
+ * Checked in win32 Process support, and fixed win32 event loop.
+
+2002-03-11 Glyph Lefkowitz <glyph@janus.twistedmatrix.com>
+
+ * More robust shutdown sequence for default mainloop (other
+ mainloops should follow suit, but they didn't implement shutdown
+ callbacks properly before anyway...). This allows for shutdown
+ callbacks to continue using the main loop.
+
+2002-03-09 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * Automatic log rotation for twistd. In addition, sending SIGUSR1
+ to twistd will rotate the log.
+
+2002-03-07 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * .: Releasing Twisted 0.15.5.
+
+2002-03-06 Glyph Lefkowitz <glyph@zelda.twistedmatrix.com>
+
+ * twisted/web/html.py: Got rid of html.Interface. This was a really
+ old, really deprecated API.
+
+2002-03-06 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/web/widgets.py: Deprecated usage of Gadget.addFile(path)
+ and replaced it with Gadget.putPath(path, pathname). This is
+ a lot more flexible.
+
+2002-03-05 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted/internet/win32.py: New win32 event loop, written by
+ Andrew Bennetts.
+
+ * twisted/tap/*: Changed the interface for creating tap modules - use
+ a method called updateApplication instead of getPorts. this
+ is a much more generic and useful mechanism.
+
+ * twisted/internet/task.py: Fixed a bug where the schedular wasn't
+ installed in some cases.
+
+2002-03-04 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/web/server.py: authorizer.Unauthorized->util.Unauthorized
+ (leftovers from removing .passport references.)
+
+ * twisted/names/dns.py: Added support for TTL.
+
+2002-03-02 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * .: Releasing Twisted 0.15.4.
+
+2002-03-02 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/words/ircservice.py: Send End-Of-MOTD message --
+ some clients rely on this for automatic joining of channels
+ and whatnot.
+
+2002-03-02 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/names/dns.py: Fixed bugs in DNS client
+
+2002-03-01 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * twisted/protocols/dns.py: Can now correctly serialize answers
+
+ * twisted/names/dns.py: Can now do simple serving of domains
+
+ * twisted/internet/stupid.py: Removed spurious debugging print
+
+2002-02-28 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * .: Releasing 0.15.3.
+
+2002-02-27 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted/mail/*, twisted/plugins.tml: The Mail server is now
+ COILable.
+
+ * bin/twistd: security fix: use a secure umask (077, rather than 0)
+ for twistd.pid.
+
+2002-02-26 Allen Short <washort@twistedmatrix.com>
+
+ * twisted/eco/eco.py, twisted/eco/sexpy.py: ECO now supports
+ backquoting and macros.
+
+2002-02-26 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted/protocols/ftp.py, twisted/plugins.tml: Made the FTP
+ server COILable!
+
+2002-02-26 Benjamin Bruheim <phed@twistedmatrix.com>
+
+ * twisted/web/distrib.py: Fixed a win32-compatibility bug.
+
+2002-02-24 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted/protocols/socks.py: Made SOCKSv4 coilable, and fixed a
+ bug so it'd work with Mozilla.
+
+2002-02-24 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * .: Releasing Twisted 0.15.2.
+
+2002-02-24 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * setup.py: Added plugins.tml and instancemessenger.glade installs
+ so mktap and t-im work in a 'setup.py install' install.
+
+ * debian/rules: Install plugins.tml so mktap works in debian installs.
+
+ * doc/man/mktap.1, twistd.1: Updated the man pages to be more accurate.
+
+2002-02-24 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * bin/mktap: Better error reporting when we don't find
+ the plugins files.
+
+ * bin/twistd: Print out the *real* usage description rather than
+ barfing when we get bad command line arguments.
+
+2002-02-24 Moshe Zadka <moshez@twistedmatrix.com>
+
+ * debian/rules: Install the instancemessenger.glade file, so IM
+ will work in debian installs.
+
+2002-02-24 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/protocols/oscar.py, socks.py, toc.py: Fixed a security
+ hole in TOC where clients could call any method on the server.
+
+2002-02-23 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted/tap/coil.py: There is now a tap-creator for COIL.
+
+ * twisted/internet/stupidproxy.py: Now with COILability!
+
+2002-02-23 Glyph Lefkowitz <glyph@zelda.twistedmatrix.com>
+
+ * bin/mktap: mktap now uses Plugins instead of searching through
+ twisted.tap. Yay for unified configuration systems!
+
+
+2002-02-22 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/im, twisted/words: t-im can now do topic setting (words
+ only), fixed the Group Metadata-setting interface in the service.
+
+2002-02-22 Glyph Lefkowitz <glyph@zelda.twistedmatrix.com>
+
+ * twisted/manhole: COIL can now load Plugins.
+
+2002-02-21 Glyph Lefkowitz <glyph@zelda.twistedmatrix.com>
+
+ * twisted.spread.pb: Changed remote method invocations to be
+ called through .callRemote rather than implicitly by getattr, and
+ added LocalAsRemote utility class for emulating remote behavior.
+
+2002-02-21 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted.protocols.ftp: Fixed a lot of serious bugs.
+
+2002-02-20 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted.protocols.telnet: the python shell now supports
+ multi-line commands and can be configured using coil.
+
+2002-02-13 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted.lumberjack: a log rotation and viewing service.
+ Currently only log rotation is supported.
+
+2002-02-12 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/words/ircservice.py (IRCChatter.irc_AWAY): Fix bug
+ where you can never come back from being away (at least using
+ epic4). Closes: #%d
+
+2002-02-11 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/web/widgets.py: Changed Gadget.page to Gadget.pageFactory
+ for clarity (this is backwards-compatible).
+
+2002-02-10 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted/spread/jelly.py:
+ * twisted/spread/banana.py:
+ * twisted/spread/pb.py: fixed bugs found by pychecker, got rid
+ of __ping__ method support, and added 'local_' methods to
+ RemoteReference
+
+ * twisted/persisted/styles.py: pychecker bug fixes
+
+2002-02-09 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * bin/eco: Created a command-line interpreter for ECO.
+
+ * doc/man/eco.1: man page for bin/eco
+
+2002-02-09 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/eco/eco.py: Reverted evaluator state back to functional-ness
+ :) And added functions (anonymous and global), and broke various
+ interfaces
+
+2002-02-09 Allen Short <washort@twistedmatrix.com>
+
+ * twisted/eco/eco.py: Refactored evaluator into a class, improved
+ python-function argument signatures, and added and/or/not functions.
+
+2002-02-08 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/words/service.py, ircservice.py: Fixed annoying PING
+ bug, and added /topic support.
+
+2002-02-08 Glyph Lefkowitz <glyph@twistedmatrix.com>
+
+ * twisted/eco: Initial prototype of ECO, the Elegant C Overlay
+ macro engine.
+
+2002-02-02 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/im/ircsupport.py: Added support for the IRC protocol
+ to IM.
+
+2002-02-02 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/python/deferred.py: added Deferred.addErrback, so now
+ it's easy to attach errbacks to deferreds when you don't care
+ about plain results.
+
+ * twisted/im/chat.py, twisted/im/pbsupport.py: added support for
+ displaying topics.
+
+2002-02-02 Paul Swartz <z3p@twistedmatrix.com>
+
+ * SOCKSv4 support: there is now a protocols.socks, which contains
+ support for SOCKSv4, a TCP proxying protocol. mktap also has
+ support for the new protocol.
+
+2002-02-02 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/words/ircservice.py (IRCChatter.receiveDirectMessage),
+ (IRCChatter.receiveGroupMessage),
+ (IRCChatter.irc_PRIVMSG): Added CTCP ACTION <-> emote translation
+
+2002-02-01 Paul Swartz <z3p@twistedmatrix.com>
+
+ * twisted/im/tocsupport.py: Added support for most of the TOC
+ protocol to IM.
+
+
+2002-02-01 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/im/*.py: added metadata/emote support to IM. "/me foo"
+ now triggers a backwards-compatible emote.
+
+
+2002-01-30 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * twisted/internet/tcp.py: Fixed the bug where startFactory() would
+ get called twice.
+
+2002-01-30 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/im: a new client for twisted.words (and eventually
+ much more) based on GTK+ and Glade. This is mainly glyph's
+ code, but I organized it for him to check in.
+
+ * twisted/words/service.py: metadata support for words messages
+ (only {'style': 'emote'} is standardized as of yet)
+
+2002-01-29 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * Added hook to tcp.Port and ssl.Port for limiting acceptable
+ connections - approveConnection(socket, addr).
+
+2002-01-27 Chris Armstrong <carmstro@twistedmatrix.com>
+
+ * twisted/words/ircservice.py: You can now change the topic
+ of a channel with '/msg channelName topic <topic>' - note that
+ 'channelName' does *not* include the '#'.
+
+2002-01-23 Glyph Lefkowitz <glyph@zelda.twistedmatrix.com>
+
+ * Incompatible change to PB: all remote methods now return
+ Deferreds. This doesn't break code in as many places as possible,
+ but the connection methods now work differently and have different
+ signatures.
+
+ * Incompatible change to Banana: Banana now really supports floats
+ and long integers. This involved removing some nasty hackery that
+ was previously part of the protocol spec, so you'll need to
+ upgrade.
+
+ * Added a feature to Jelly: Jelly now supports unicode strings.
+
+ * Improved Twisted.Forums considerably: still needs work, but it's
+ growing into an example of what you can do with a Twisted.Web
+ application.
+
+ * Added Twisted.Web.Webpassport -- generic mechanism for web-based
+ login to arbitrary services. This in conjunction with some code
+ in Forum that uses it.
+
+ * Incompatible change in Enterprise: all query methods now return
+ Deferreds, as well as take arguments in an order which makes it
+ possible to pass arbitrary argument lists for using the database's
+ formatting characters rather than python's.
+
+2002-01-15 Glyph Lefkowitz <glyph@zelda.twistedmatrix.com>
+
+ * twisted/internet/passport.py: (and friends) Retrieval of
+ perspectives is now asynchronous, hooray (this took way too long)!
+ Perspectives may now be stored in external data sources. Lurching
+ slowly towards a stable API for the Passport system, along with
+ Sean's recent commits of tools to manipulate it.
+
+2002-01-14 Kevin Turner <acapnotic@twistedmatrix.com>
+
+ * twisted/python/explorer.py: reimplementated. So it's better.
+ And yes, I broke the API.
+
+ * twisted/manhole/ui/spelunk_gnome.py: Less duplication of visages,
+ and they're draggable now too.
+
+2002-01-13 Itamar Shtull-Trauring <itamarst@twistedmatrix.com>
+
+ * Changed twisted.enterprise.adabi so operations can accept lists
+ of arguments. This allows us to use the database adaptor's native
+ SQL quoting ability instead of either doing it ourselves, or the
+ *current* way twisted does it (not doing it at all, AFAICT!).
+
+ cursor.execute("INSERT INTO foo VALUES (%s, %d), "it's magic", 12)
+
+ Problem is that different adaptors may have different codes for
+ quoting.
+
+ * First go at database for twisted.bugs. I hate RDBMS. I hate web.
+
+--- 0.13.0 Release ---
+
+# Local Variables:
+# add-log-time-format: add-log-iso8601-time-string
+# End:
diff --git a/vendor/Twisted-10.0.0/twisted/topfiles/NEWS b/vendor/Twisted-10.0.0/twisted/topfiles/NEWS
new file mode 100644
index 0000000000..76b9e792c9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/topfiles/NEWS
@@ -0,0 +1,942 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/<number>
+
+Twisted Core 10.0.0 (2010-03-01)
+================================
+
+Features
+--------
+ - The twistd man page now has a SIGNALS section. (#689)
+
+ - reactor.spawnProcess now will not emit a PotentialZombieWarning
+ when called before reactor.run, and there will be no potential for
+ zombie processes in this case. (#2078)
+
+ - High-throughput applications based on Perspective Broker should now
+ run noticably faster thanks to the use of a more efficient decoding
+ function in Twisted Spread. (#2310)
+
+ - Documentation for trac-post-commit-hook functionality in svn-dev
+ policy. (#3867)
+
+ - twisted.protocols.socks.SOCKSv4 now supports the SOCKSv4a protocol.
+ (#3886)
+
+ - Trial can now output test results according to the subunit
+ protocol, as long as Subunit is installed (see
+ https://launchpad.net/subunit). (#4004)
+
+ - twisted.protocols.amp now provides a ListOf argument type which can
+ be composed with some other argument types to create a zero or more
+ element sequence of that type. (#4116)
+
+ - If returnValue is invoked outside of a function decorated with
+ @inlineCallbacks, but causes a function thusly decorated to exit, a
+ DeprecationWarning will be emitted explaining this potentially
+ confusing behavior. In a future release, this will cause an
+ exception. (#4157)
+
+ - twisted.python.logfile.BaseLogFile now has a reopen method allowing
+ you to use an external logrotate mechanism. (#4255)
+
+Bugfixes
+--------
+ - FTP.ftp_NLST now handles requests on invalid paths in a way
+ consistent with RFC 959. (#1342)
+
+ - twisted.python.util.initgroups now calls the low-level C initgroups
+ by default if available: the python version can create lots of I/O
+ with certain authentication setup to retrieve all the necessary
+ information. (#3226)
+
+ - startLogging now does nothing on subsequent invocations, thus
+ fixing a terrible infinite recursion bug that's only on edge case.
+ (#3289)
+
+ - Stringify non-string data to NetstringReceiver.sendString before
+ calculating the length so that the calculated length is equal to
+ the actual length of the transported data. (#3299)
+
+ - twisted.python.win32.cmdLineQuote now correctly quotes empty
+ strings arguments (#3876)
+
+ - Change the behavior of the Gtk2Reactor to register only one source
+ watch for each file descriptor, instead of one for reading and one
+ for writing. In particular, it fixes a bug with Glib under Windows
+ where we failed to notify when a client is connected. (#3925)
+
+ - Twisted Trial no longer crashes if it can't remove an old
+ _trial_temp directory. (#4020)
+
+ - The optional _c_urlarg extension now handles unquote("") correctly
+ on platforms where malloc(0) returns NULL, such as AIX. It also
+ compiles with less warnings. (#4142)
+
+ - On POSIX, child processes created with reactor.spawnProcess will no
+ longer automatically ignore the signals which the parent process
+ has set to be ignored. (#4199)
+
+ - All SOCKSv4a tests now use a dummy reactor with a deterministic
+ resolve method. (#4275)
+
+ - Prevent extraneous server, date and content-type headers in proxy
+ responses. (#4277)
+
+Deprecations and Removals
+-------------------------
+ - twisted.internet.error.PotentialZombieWarning is now deprecated.
+ (#2078)
+
+ - twisted.test.time_helpers is now deprecated. (#3719)
+
+ - The deprecated connectUDP method of IReactorUDP has now been
+ removed. (#4075)
+
+ - twisted.trial.unittest.TestCase now ignores the previously
+ deprecated setUpClass and tearDownClass methods. (#4175)
+
+Other
+-----
+ - #917, #2406, #2481, #2608, #2689, #2884, #3056, #3082, #3199,
+ #3480, #3592, #3718, #3935, #4066, #4083, #4154, #4166, #4169,
+ #4176, #4183, #4186, #4188, #4189, #4194, #4201, #4204, #4209,
+ #4222, #4234, #4235, #4238, #4240, #4245, #4251, #4264, #4268,
+ #4269, #4282
+
+
+Twisted Core 9.0.0 (2009-11-24)
+===============================
+
+Features
+--------
+ - LineReceiver.clearLineBuffer now returns the bytes that it cleared (#3573)
+ - twisted.protocols.amp now raises InvalidSignature when bad arguments are
+ passed to Command.makeArguments (#2808)
+ - IArgumentType was added to represent an existing but previously unspecified
+ interface in amp (#3468)
+ - Obscure python tricks have been removed from the finger tutorials (#2110)
+ - The digest auth implementations in twisted.web and twisted.protocolos.sip
+ have been merged together in twisted.cred (#3575)
+ - FilePath and ZipPath now has a parents() method which iterates up all of its
+ parents (#3588)
+ - reactors which support threads now have a getThreadPool method (#3591)
+ - The MemCache client implementation now allows arguments to the "stats"
+ command (#3661)
+ - The MemCache client now has a getMultiple method which allows fetching of
+ multiple values (#3171)
+ - twisted.spread.jelly can now unserialize some new-style classes (#2950)
+ - twisted.protocols.loopback.loopbackAsync now accepts a parameter to control
+ the data passed between client and server (#3820)
+ - The IOCP reactor now supports SSL (#593)
+ - Tasks in a twisted.internet.task.Cooperator can now be paused, resumed, and
+ cancelled (#2712)
+ - AmpList arguments can now be made optional (#3891)
+ - The syslog output observer now supports log levels (#3300)
+ - LoopingCall now supports reporting the number of intervals missed if it
+ isn't able to schedule calls fast enough (#3671)
+
+Fixes
+-----
+ - The deprecated md5 and sha modules are no longer used if the stdlib hashlib
+ module is available (#2763)
+ - An obscure deadlock involving waking up the reactor within signal handlers
+ in particular threads was fixed (#1997)
+ - The passivePortRange attribute of FTPFactory is now honored (#3593)
+ - TestCase.flushWarnings now flushes warnings even if they were produced by a
+ file that was renamed since it was byte compiled (#3598)
+ - Some internal file descriptors are now marked as close-on-exec, so these will
+ no longer be leaked to child processes (#3576)
+ - twisted.python.zipstream now correctly extracts the first file in a directory
+ as a file, and not an empty directory (#3625)
+ - proxyForInterface now returns classes which correctly *implement* interfaces
+ rather than *providing* them (#3646)
+ - SIP Via header parameters should now be correctly generated (#2194)
+ - The Deferred returned by stopListening would sometimes previously never fire
+ if an exception was raised by the underlying file descriptor's connectionLost
+ method. Now the Deferred will fire with a failure (#3654)
+ - The command-line tool "manhole" should now work with newer versions of pygtk
+ (#2464)
+ - When a DefaultOpenSSLContextFactory is instantiated with invalid parameters,
+ it will now raise an exception immediately instead of waiting for the first
+ connection (#3700)
+ - Twisted command line scripts should now work when installed in a virtualenv
+ (#3750)
+ - Trial will no longer delete temp directories which it did not create (#3481)
+ - Processes started on Windows should now be cleaned up properly in more cases
+ (#3893)
+ - Certain misbehaving importers will no longer cause twisted.python.modules
+ (and thus trial) to raise an exception, but rather issue a warning (#3913)
+ - MemCache client protocol methods will now fail when the transport has been
+ disconnected (#3643)
+ - In the AMP method callRemoteString, the requiresAnswer parameter is now
+ honored (#3999)
+ - Spawning a "script" (a file which starts with a #! line) on Windows running
+ Python 2.6 will now work instead of raising an exception about file mode
+ "ru" (#3567)
+ - FilePath's walk method now calls its "descend" parameter even on the first
+ level of children, instead of only on grandchildren. This allows for better
+ symlink cycle detection (#3911)
+ - Attempting to write unicode data to process pipes on Windows will no longer
+ result in arbitrarily encoded messages being written to the pipe, but instead
+ will immediately raise an error (#3930)
+ - The various twisted command line utilities will no longer print
+ ModuleType.__doc__ when Twisted was installed with setuptools (#4030)
+ - A Failure object will now be passed to connectionLost on stdio connections
+ on Windows, instead of an Exception object (#3922)
+
+Deprecations and Removals
+-------------------------
+ - twisted.persisted.marmalade was deleted after a long period of deprecation
+ (#876)
+ - Some remaining references to the long-gone plugins.tml system were removed
+ (#3246)
+ - SSLv2 is now disabled by default, but it can be re-enabled explicitly
+ (#3330)
+ - twisted.python.plugin has been removed (#1911)
+ - reactor.run will now raise a ReactorAlreadyRunning exception when it is
+ called reentrantly instead of warning a DeprecationWarning (#1785)
+ - twisted.spread.refpath is now deprecated because it is unmaintained,
+ untested, and has dubious value (#3723)
+ - The unused --quiet flag has been removed from the twistd command (#3003)
+
+Other
+-----
+ - #3545, #3490, #3544, #3537, #3455, #3315, #2281, #3564, #3570, #3571, #3486,
+ #3241, #3599, #3220, #1522, #3611, #3596, #3606, #3609, #3602, #3637, #3647,
+ #3632, #3675, #3673, #3686, #2217, #3685, #3688, #2456, #506, #3635, #2153,
+ #3581, #3708, #3714, #3717, #3698, #3747, #3704, #3707, #3713, #3720, #3692,
+ #3376, #3652, #3695, #3735, #3786, #3783, #3699, #3340, #3810, #3822, #3817,
+ #3791, #3859, #2459, #3677, #3883, #3894, #3861, #3822, #3852, #3875, #2722,
+ #3768, #3914, #3885, #2719, #3905, #3942, #2820, #3990, #3954, #1627, #2326,
+ #2972, #3253, #3937, #4058, #1200, #3639, #4079, #4063, #4050
+
+
+Core 8.2.0 (2008-12-16)
+=======================
+
+Features
+--------
+ - Reactors are slowly but surely becoming more isolated, thus improving
+ testability (#3198)
+ - FilePath has gained a realpath method, and FilePath.walk no longer infinitely
+ recurses in the case of a symlink causing a self-recursing filesystem tree
+ (#3098)
+ - FilePath's moveTo and copyTo methods now have an option to disable following
+ of symlinks (#3105)
+ - Private APIs are now included in the API documentation (#3268)
+ - hotshot is now the default profiler for the twistd --profile parameter and
+ using cProfile is now documented (#3355, #3356)
+ - Process protocols can now implement a processExited method, which is
+ distinct from processEnded in that it is called immediately when the child
+ has died, instead of waiting for all the file descriptors to be closed
+ (#1291)
+ - twistd now has a --umask option (#966, #3024)
+ - A new deferToThreadPool function exists in twisted.internet.threads (#2845)
+ - There is now an example of writing an FTP server in examples/ftpserver.py
+ (#1579)
+ - A new runAsEffectiveUser function has been added to twisted.python.util
+ (#2607)
+ - twisted.internet.utils.getProcessOutput now offers a mechanism for
+ waiting for the process to actually end, in the event of data received on
+ stderr (#3239)
+ - A fullyQualifiedName function has been added to twisted.python.reflect
+ (#3254)
+ - strports now defaults to managing access to a UNIX socket with a lock;
+ lockfile=0 can be included in the strports specifier to disable this
+ behavior (#2295)
+ - FTPClient now has a 'rename' method (#3335)
+ - FTPClient now has a 'makeDirectory' method (#3500)
+ - FTPClient now has a 'removeFile' method (#3491)
+ - flushWarnings, A new Trial method for testing warnings, has been added
+ (#3487, #3427, #3506)
+ - The log observer can now be configured in .tac files (#3534)
+
+Fixes
+-----
+ - TLS Session Tickets are now disabled by default, allowing connections to
+ certain servers which hang when an empty session ticket is received (like
+ GTalk) (#3463)
+ - twisted.enterprise.adbapi.ConnectionPool's noisy attribute now defaults to
+ False, as documented (#1806)
+ - Error handling and logging in adbapi is now much improved (#3244)
+ - TCP listeners can now be restarted (#2913)
+ - Doctests can now be rerun with trial's --until-failure option (#2713)
+ - Some memory leaks have been fixed in trial's --until-failure
+ implementation (#3119, #3269)
+ - Trial's summary reporter now prints correct runtime information and handles
+ the case of 0 tests (#3184)
+ - Trial and any other user of the 'namedAny' function now has better error
+ reporting in the case of invalid module names (#3259)
+ - Multiple instances of trial can now run in parallel in the same directory
+ by creating _trial_temp directories with an incremental suffix (#2338)
+ - Trial's failUnlessWarns method now works on Python 2.6 (#3223)
+ - twisted.python.log now hooks into the warnings system in a way compatible
+ with Python 2.6 (#3211)
+ - The GTK2 reactor is now better supported on Windows, but still not passing
+ the entire test suite (#3203)
+ - low-level failure handling in spawnProcess has been improved and no longer
+ leaks file descriptors (#2305, #1410)
+ - Perspective Broker avatars now have their logout functions called in more
+ cases (#392)
+ - Log observers which raise exceptions are no longer removed (#1069)
+ - transport.getPeer now always includes an IP address in the Address returned
+ instead of a hostname (#3059)
+ - Functions in twisted.internet.utils which spawn processes now avoid calling
+ chdir in the case where no working directory is passed, to avoid some
+ obscure permission errors (#3159)
+ - twisted.spread.publish.Publishable no longer corrupts line endings on
+ Windows (#2327)
+ - SelectReactor now properly detects when a TLS/TCP connection has been
+ disconnected (#3218)
+ - twisted.python.lockfile no longer raises an EEXIST OSError and is much
+ better supported on Windows (#3367)
+ - When ITLSTransport.startTLS is called while there is data in the write
+ buffer, TLS negotiation will now be delayed instead of the method raising
+ an exception (#686)
+ - The userAnonymous argument to FTPFactory is now honored (#3390)
+ - twisted.python.modules no longer tries to "fix" sys.modules after an import
+ error, which was just causing problems (#3388)
+ - setup.py no longer attempts to build extension modules when run with Jython
+ (#3410)
+ - AMP boxes can now be sent in IBoxReceiver.startReceivingBoxes (#3477)
+ - AMP connections are closed as soon as a key length larger than 255 is
+ received (#3478)
+ - Log events with timezone offsets between -1 and -59 minutes are now
+ correctly reported as negative (#3515)
+
+Deprecations and Removals
+-------------------------
+ - Trial's setUpClass and tearDownClass methods are now deprecated (#2903)
+ - problemsFromTransport has been removed in favor of the argument passed to
+ connectionLost (#2874)
+ - The mode parameter to methods of IReactorUNIX and IReactorUNIXDatagram are
+ deprecated in favor of applications taking other security precautions, since
+ the mode of a Unix socket is often not respected (#1068)
+ - Index access on instances of twisted.internet.defer.FirstError has been
+ removed in favor of the subFailure attribute (#3298)
+ - The 'changeDirectory' method of FTPClient has been deprecated in favor of
+ the 'cwd' method (#3491)
+
+Other
+-----
+
+ - #3202, #2869, #3225, #2955, #3237, #3196, #2355, #2881, #3054, #2374, #2918,
+ #3210, #3052, #3267, #3288, #2985, #3295, #3297, #2512, #3302, #1222, #2631,
+ #3306, #3116, #3215, #1489, #3319, #3320, #3321, #1255, #2169, #3182, #3323,
+ #3301, #3318, #3029, #3338, #3346, #1144, #3173, #3165, #685, #3357, #2582,
+ #3370, #2438, #1253, #637, #1971, #2208, #979, #1790, #1888, #1882, #1793,
+ #754, #1890, #1931, #1246, #1025, #3177, #2496, #2567, #3400, #2213, #2027,
+ #3415, #1262, #3422, #2500, #3414, #3045, #3111, #2974, #2947, #3222, #2878,
+ #3402, #2909, #3423, #1328, #1852, #3382, #3393, #2029, #3489, #1853, #2026,
+ #2375, #3502, #3482, #3504, #3505, #3507, #2605, #3519, #3520, #3121, #3484,
+ #3439, #3216, #3511, #3524, #3521, #3197, #2486, #2449, #2748, #3381, #3236,
+ #671
+
+
+8.1.0 (2008-05-18)
+==================
+
+Features
+--------
+
+ - twisted.internet.error.ConnectionClosed is a new exception which is the
+ superclass of ConnectionLost and ConnectionDone (#3137)
+ - Trial's CPU and memory performance should be better now (#3034)
+ - twisted.python.filepath.FilePath now has a chmod method (#3124)
+
+Fixes
+-----
+
+ - Some reactor re-entrancy regressions were fixed (#3146, #3168)
+ - A regression was fixed whereby constructing a Failure for an exception and
+ traceback raised out of a Pyrex extension would fail (#3132)
+ - CopyableFailures in PB can again be created from CopiedFailures (#3174)
+ - FilePath.remove, when called on a FilePath representing a symlink to a
+ directory, no longer removes the contents of the targeted directory, and
+ instead removes the symlink (#3097)
+ - FilePath now has a linkTo method for creating new symlinks (#3122)
+ - The docstring for Trial's addCleanup method now correctly specifies when
+ cleanup functions are run (#3131)
+ - assertWarns now deals better with multiple identical warnings (#2904)
+ - Various windows installer bugs were fixed (#3115, #3144, #3150, #3151, #3164)
+ - API links in the howto documentation have been corrected (#3130)
+ - The Win32 Process transport object now has a pid attribute (#1836)
+ - A doc bug in the twistd plugin howto which would inevitably lead to
+ confusion was fixed (#3183)
+ - A regression breaking IOCP introduced after the last release was fixed
+ (#3200)
+
+
+Deprecations and Removals
+-------------------------
+
+ - mktap is now fully deprecated, and will emit DeprecationWarnings when used
+ (#3127)
+
+Other
+-----
+ - #3079, #3118, #3120, #3145, #3069, #3149, #3186, #3208, #2762
+
+
+8.0.1 (2008-03-26)
+==================
+
+Fixes
+-----
+ - README no longer refers to obsolete trial command line option
+ - twistd no longer causes a bizarre DeprecationWarning about mktap
+
+
+8.0.0 (2008-03-17)
+==================
+
+Features
+--------
+
+ - The IOCP reactor has had many changes and is now greatly improved
+ (#1760, #3055)
+ - The main Twisted distribution is now easy_installable (#1286, #3110)
+ - twistd can now profile with cProfile (#2469)
+ - twisted.internet.defer contains a DeferredFilesystemLock which gives a
+ Deferred interface to lock file acquisition (#2180)
+ - twisted.python.modules is a new system for representing and manipulating
+ module paths (i.e. sys.path) (#1951)
+ - twisted.internet.fdesc now contains a writeToFD function, along with other
+ minor fixes (#2419)
+ - twisted.python.usage now allows optional type enforcement (#739)
+ - The reactor now has a blockingCallFromThread method for non-reactor threads
+ to use to wait for a reactor-scheduled call to return a result (#1042, #3030)
+ - Exceptions raised inside of inlineCallbacks-using functions now have a
+ better chance of coming with a meaningful traceback (#2639, #2803)
+ - twisted.python.randbytes now contains code for generating secure random
+ bytes (#2685)
+ - The classes in twisted.application.internet now accept a reactor parameter
+ for specifying the reactor to use for underlying calls to allow for better
+ testability (#2937)
+ - LoopingCall now allows you to specify the reactor to use to schedule new
+ calls, allowing much better testing techniques (#2633, #2634)
+ - twisted.internet.task.deferLater is a new API for scheduling calls and
+ getting deferreds which are fired with their results (#1875)
+ - objgrep now knows how to search through deque objects (#2323)
+ - twisted.python.log now contains a Twisted log observer which can forward
+ messages to the Python logging system (#1351)
+ - Log files now include seconds in the timestamps (#867)
+ - It is now possible to limit the number of log files to create during log
+ rotation (#1095)
+ - The interface required by the log context system is now documented as
+ ILoggingContext, and abstract.FileDescriptor now declares that it implements
+ it (#1272)
+ - There is now an example cred checker that uses a database via adbapi (#460)
+ - The epoll reactor is now documented in the choosing-reactors howto (#2539)
+ - There were improvements to the client howto (#222)
+ - Int8Receiver was added (#2315)
+ - Various refactorings to AMP introduced better testability and public
+ interfaces (#2657, #2667, #2656, #2664, #2810)
+ - twisted.protocol.policies.TrafficLoggingFactory now has a resetCounter
+ method (#2757)
+ - The FTP client can be told which port range within which to bind passive
+ transfer ports (#1904)
+ - twisted.protocols.memcache contains a new asynchronous memcache client
+ (#2506, #2957)
+ - PB now supports anonymous login (#439, #2312)
+ - twisted.spread.jelly now supports decimal objects (#2920)
+ - twisted.spread.jelly now supports all forms of sets (#2958)
+ - There is now an interface describing the API that process protocols must
+ provide (#3020)
+ - Trial reporting to core unittest TestResult objects has been improved (#2495)
+ - Trial's TestCase now has an addCleanup method which allows easy setup of
+ tear-down code (#2610, #2899)
+ - Trial's TestCase now has an assertIsInstance method (#2749)
+ - Trial's memory footprint and speed are greatly improved (#2275)
+ - At the end of trial runs, "PASSED" and "FAILED" messages are now colorized
+ (#2856)
+ - Tests which leave global state around in the reactor will now fail in
+ trial. A new option, --unclean-warnings, will convert these errors back into
+ warnings (#2091)
+ - Trial now has a --without-module command line for testing code in an
+ environment that lacks a particular Python module (#1795)
+ - Error reporting of failed assertEquals assertions now has much nicer
+ formatting (#2893)
+ - Trial now has methods for monkey-patching (#2598)
+ - Trial now has an ITestCase (#2898, #1950)
+ - The trial reporter API now has a 'done' method which is called at the end of
+ a test run (#2883)
+ - TestCase now has an assertWarns method which allows testing that functions
+ emit warnings (#2626, #2703)
+ - There are now no string exceptions in the entire Twisted code base (#2063)
+ - There is now a system for specifying credentials checkers with a string
+ (#2570)
+
+Fixes
+-----
+
+ - Some tests which were asserting the value of stderr have been changed
+ because Python uncontrollably writes bytes to stderr (#2405)
+ - Log files handle time zones with DST better (#2404)
+ - Subprocesses using PTYs on OS X that are handled by Twisted will now be able
+ to more reliably write the final bytes before they exit, allowing Twisted
+ code to more reliably receive them (#2371, #2858)
+ - Trial unit test reporting has been improved (#1901)
+ - The kqueue reactor handles connection failures better (#2172)
+ - It's now possible to run "trial foo/bar/" without an exception: trailing
+ slashes no longer cause problems (#2005)
+ - cred portals now better deal with implementations of inherited interfaces
+ (#2523)
+ - FTP error handling has been improved (#1160, 1107)
+ - Trial behaves better with respect to file locking on Windows (#2482)
+ - The FTP server now gives a better error when STOR is attempted during an
+ anonymous session (#1575)
+ - Trial now behaves better with tests that use the reactor's threadpool (#1832)
+ - twisted.python.reload now behaves better with new-style objects (#2297)
+ - LogFile's defaultMode parameter is now better implemented, preventing
+ potential security exploits (#2586)
+ - A minor obscure leak in thread pools was corrected (#1134)
+ - twisted.internet.task.Clock now returns the correct DelayedCall from
+ callLater, instead of returning the one scheduled for the furthest in the
+ future (#2691)
+ - twisted.spread.util.FilePager no longer unnecessarily buffers data in
+ memory (#1843, 2321)
+ - Asking for twistd or trial to use an unavailable reactor no longer prints a
+ traceback (#2457)
+ - System event triggers have fewer obscure bugs (#2509)
+ - Plugin discovery code is much better behaved, allowing multiple
+ installations of a package with plugins (#2339, #2769)
+ - Process and PTYProcess have been merged and some minor bugs have been fixed
+ (#2341)
+ - The reactor has less global state (#2545)
+ - Failure can now correctly represent and format errors caused by string
+ exceptions (#2830)
+ - The epoll reactor now has better error handling which now avoids the bug
+ causing 100% CPU usage in some cases (#2809)
+ - Errors raised during trial setUp or tearDown methods are now handled better
+ (#2837)
+ - A problem when deferred callbacks add new callbacks to the deferred that
+ they are a callback of was fixed (#2849)
+ - Log messages that are emitted during connectionMade now have the protocol
+ prefix correctly set (#2813)
+ - The string representation of a TCP Server connection now contains the actual
+ port that it's bound to when it was configured to listen on port 0 (#2826)
+ - There is better reporting of error codes for TCP failures on Windows (#2425)
+ - Process spawning has been made slightly more robust by disabling garbage
+ collection temporarily immediately after forking so that finalizers cannot
+ be executed in an unexpected environment (#2483)
+ - namedAny now detects import errors better (#698)
+ - Many fixes and improvements to the twisted.python.zipstream module have
+ been made (#2996)
+ - FilePager no longer blows up on empty files (#3023)
+ - twisted.python.util.FancyEqMixin has been improved to cooperate with objects
+ of other types (#2944)
+ - twisted.python.FilePath.exists now restats to prevent incorrect result
+ (#2896)
+ - twisted.python.util.mergeFunctionMetadata now also merges the __module__
+ attribute (#3049)
+ - It is now possible to call transport.pauseProducing within connectionMade on
+ TCP transports without it being ignored (#1780)
+ - twisted.python.versions now understands new SVN metadata format for fetching
+ the SVN revision number (#3058)
+ - It's now possible to use reactor.callWhenRunning(reactor.stop) on gtk2 and
+ glib2 reactors (#3011)
+
+Deprecations and removals
+-------------------------
+ - twisted.python.timeoutqueue is now deprecated (#2536)
+ - twisted.enterprise.row and twisted.enterprise.reflector are now deprecated
+ (#2387)
+ - twisted.enterprise.util is now deprecated (#3022)
+ - The dispatch and dispatchWithCallback methods of ThreadPool are now
+ deprecated (#2684)
+ - Starting the same reactor multiple times is now deprecated (#1785)
+ - The visit method of various test classes in trial has been deprecated (#2897)
+ - The --report-profile option to twistd and twisted.python.dxprofile are
+ deprecated (#2908)
+ - The upDownError method of Trial reporters is deprecated (#2883)
+
+Other
+-----
+
+ - #2396, #2211, #1921, #2378, #2247, #1603, #2463, #2530, #2426, #2356, #2574,
+ - #1844, #2575, #2655, #2640, #2670, #2688, #2543, #2743, #2744, #2745, #2746,
+ - #2742, #2741, #1730, #2831, #2216, #1192, #2848, #2767, #1220, #2727, #2643,
+ - #2669, #2866, #2867, #1879, #2766, #2855, #2547, #2857, #2862, #1264, #2735,
+ - #942, #2885, #2739, #2901, #2928, #2954, #2906, #2925, #2942, #2894, #2793,
+ - #2761, #2977, #2968, #2895, #3000, #2990, #2919, #2969, #2921, #3005, #421,
+ - #3031, #2940, #1181, #2783, #1049, #3053, #2847, #2941, #2876, #2886, #3086,
+ - #3095, #3109
+
+
+2.5.0 (2006-12-29)
+==================
+
+Twisted 2.5.0 is a major feature release, with several interesting new
+developments and a great number of bug fixes. Some of the highlights
+follow.
+
+ * AMP, the Asynchronous Messaging Protocol, was introduced. AMP is
+ a protocol which provides request/response semantics over a
+ persistent connection in a very simple and extensible manner.
+
+ * An Epoll-based reactor was added, which can be used with twistd or
+ trial by passing "-r epoll" on the command line. This may improve
+ performance of certain high-traffic network applications.
+
+ * The 'twistd' command can now accept sub-commands which name an
+ application to run. For example, 'twistd web --path .' will start a
+ web server serving files out of the current directory. This
+ functionality is meant to replace the old way of doing things with
+ 'mktap' and 'twistd -f'.
+
+ * Python 2.5 is now supported. Previous releases of Twisted were
+ broken by changes in the release of Python 2.5.
+
+ * 'inlineCallbacks' was added, which allows taking advantage of the
+ new 'yield' expression syntax in Python 2.5 to avoid writing
+ callbacks for Deferreds.
+
+In addition to these changes, there are many other minor features and
+a large number of bug fixes.
+
+Features
+--------
+ - log.err can now take a second argument for specifying information
+ about an error (#1399)
+ - A time-simulating test helper class, twisted.internet.task.Clock,
+ was added (#1757)
+ - Trial docstring improvements were made (#1604, #2133)
+ - New SSL features were added to twisted.internet.ssl, such as client
+ validation (#302)
+ - Python 2.5 is now supported (#1867)
+ - Trial's assertFailure now provides more information on failure (#1869)
+ - Trial can now be run on tests within a zipfile (#1940)
+ - AMP, a new simple protocol for asynchronous messaging, was added (#1715)
+ - Trial's colorful reporter now works on win32 (#1646)
+ - Trial test modules may now dynamically construct TestSuites (#1638, #2165)
+ - twistd can now make use of plugins to run applications (#1922, #2013)
+ - Twisted now works with the latest (unreleased) zope.interface (#2160)
+ - An epoll-based reactor, epollreactor, was added. It is selectable
+ with the -r options to twistd and trial (#1953)
+ - twistd and trial now use the plugin system to find reactors which
+ can be selected (#719)
+ - twisted.internet.defer.inlineCallbacks was added. It takes
+ advantage of Python 2.5's generators to offer a way to deal with
+ Deferreds without callbacks (#2100)
+
+Fixes
+-----
+ - Traceback formatting in Trial was improved (#1454, #1610)
+ - twisted.python.filepath.FilePath.islink now actually returns True when
+ appropriate (#1773)
+ - twisted.plugin now no longer raises spurious errors (#926)
+ - twisted.pb Cacheables may now be new-style classes (#1324)
+ - FileDescriptor now deals with producers in a more
+ interface-compliant and robust manner (#2286, #811)
+ - "setup.py build" and other setup.py commands which don't actually
+ install the software now work (#1835)
+ - wxreactor has had various fixes (#1235, #1574, #1688)
+
+Deprecations and Removals
+-------------------------
+ - The old twisted.cred API (Perspectives, Identities and such) was
+ removed (#1440)
+ - twisted.spread.newjelly was removed (#1831)
+ - Various deprecated things in twisted.python.components were
+ removed: Interface, MetaInterface, getAdapterClass, and
+ getAdapterClassWithInheritance (#1636)
+ - twisted.enterprise.xmlreflector was removed (#661)
+ - mktap is slowly on its way out, now that twistd supports plugins. It
+ is not yet officially deprecated (#2013)
+ - tkmktap was removed, because it wasn't working anyway (#2020)
+ - reactor.iterate calls made inside of a Trial test case are
+ deprecated (#2090)
+ - twisted.internet.qtreactor was removed: It has been moved to a
+ separate project. See http://twistedmatrix.com/trac/wiki/QTReactor
+ (#2130, #2137)
+ - threadedselectreactor is now not a directly usable reactor; it is
+ only meant to help in writing other reactors (#2126)
+ - twisted.python.reflect.funcinfo is deprecated (#2079)
+ - twisted.spread.sturdy, which was already completely broken, was
+ removed (#2299)
+
+
+Other
+-----
+The following changes are minor or closely related to other changes.
+
+ - #1783, #1786, #1788, #1648, #1734, #1609, #1800, #1818,
+ #1629, #1829, #491, #1816, #1824, #1855, #1797, #1637, #1371,
+ #1892, #1887, #1897, #1563, #1741, #1943, #1952, #1276,
+ #1837, #1726, #1963, #1965, #1973, #1976, #1991, #1936, #1113,
+ #630, #2002, #2040, #2044, #1617, #2045, #2055, #2056, #2022,
+ #2052, #1552, #1999, #1507, #2054, #1970, #1968, #662, #1910,
+ #1694, #1999, #1409, #2150, #2127, #2155, #1983, #2014, #2222,
+ #1067, #2136, #2065, #1430, #2173, #2212, #1871, #2147, #1199,
+ #2273, #428, #992, #815, #2024, #2292, #2125, #2139, #2291, #2174,
+ #2306, #2228, #2309, #2319, #2317, #2313, #2154, #1985, #1201
+
+
+2.4.0 (2006-05-21)
+==================
+
+Features
+--------
+ - twisted.internet.task.Cooperator (Added along with #1701).
+
+Fixes
+-----
+ - Errors in UDP protocols no longer unbind the UDP port (#1695).
+ - Misc: #1717, #1705, #1563, #1719, #1721, #1722, #1728.
+
+
+2.3.0 (2006-05-14)
+==================
+
+Features
+--------
+ - twisted-dev-mode's F9 now uses trial's --testmodule feature, rather than
+ trying to guess what tests to run. This will break files using the "-x"
+ test-case-name hack (just use a comma separated list instead).
+ - API Documentation improvements.
+ - A new Producer/Consumer guide (#53)
+ - Better-defined error behavior in IReactorMulticast (#1578)
+ - IOCP Multicast support (#1500)
+ - Improved STDIO support on Windows. (#1553)
+ - LoopingCall supports Deferreds such that it will wait until a
+ Deferred has fired before rescheduling the next call (#1487)
+ - Added twisted.python.versions.Version, a structured representation
+ of Version information, including support for SVN revision numbers
+ (#1663)
+
+Fixes
+-----
+
+ - Many trial fixes, as usual
+ - All API documentation is now correctly formatted as epytext (#1545)
+ - twisted.python.filepath.FilePath.__repr__ is safer.
+ - Fix trial's "until-failure" mode. (#1453)
+ - deferredGenerator now no longer causes handled exceptions (or
+ results) to propagate to the resulting Deferred (#1709).
+ - Misc: #1483, #1495, #1503, #1532, #1539, #1559, #1509, #1538,
+ #1571, #1331, #1561, #737, #1562, #1573, #1594, #1607, #1407, #1615,
+ #1645, #1634, #1620, #1664, #1666, #1650, #1670, #1675, #1692, #1710,
+ #1668.
+
+Deprecations
+------------
+
+ - Removal of already-deprecated trial APIs: the assertions module,
+ util.deferredResult, util.deferredError, util.fireWhenDoneFunc,
+ util.spinUntil, util.spinWhile, util.extract_tb,
+ util.format_exception, util.suppress_warnings, unittest.wait,
+ util.wait
+ - The backwards compatibility layer of twisted.python.components
+ (e.g., backwardsCompatImplements, fixClassImplements, etc) has been
+ disabled. The functions still exist, but do nothing as to not break
+ user code outright (#1511)
+ - Deprecate the usage of the 'default' argument as a keyword argument
+ in Interface.__call__. Passing a second positional argument to
+ specify the default return value of the adaptation is still
+ supported.
+
+
+2.2.0 (2006-02-12)
+==================
+
+Features
+--------
+ - Twisted no longer works with Python 2.2
+ - FTP server supports more clients
+ - Process support on Windows
+ - twisted.internet.stdio improved (including Windows support!)
+ - Trial:
+ - Continued Trial refactoring
+ - Default trial reporter is verbose black&white when color isn't supported
+ - Deferreds returned in trial tests that don't fire before the
+ unittest timeout now have their errback fired with a TimeoutError
+ - raising SkipTest in setUp and setUpClass skips tests
+ - Test suites are failed if there are import errors
+
+Fixes
+-----
+ - iocpreactor fixes
+ - Threadpool fixes
+ - Fixed infinite loops in datagramReceived edge cases
+ - Issues resolved: 654, 773, 998, 1005, 1008, 1116, 1123, 1198, 1221,
+ 1232, 1233, 1236, 1240, 1244, 1258, 1263, 1265, 1266, 1271, 1275,
+ 1293, 1294, 1298, 1308, 1316, 1317, 1321, 1341, 1344, 1353, 1359,
+ 1372, 1374, 1377, 1379, 1380, 1385, 1388, 1389, 1413, 1422, 1426,
+ 1434, 1435, 1448, 1449, 1456
+
+Deprecations
+------------
+ - Trial:
+ - spinWhile and spinUntil
+ - util.wait
+ - extract_tb and format_exception
+ - util.suppressWarnings
+ - runReactor is gone
+
+
+2.1.0 (2005-11-06)
+==================
+
+Features
+--------
+ - threadedselectreactor, a reactor which potentially makes
+ integration with foreign event loops much simpler.
+ - major improvements to twisted.conch.insults, including many new widgets.
+ - adbapi ConnectionPools now have 'runWithConnection' which is
+ similar to runInteraction but gives you a connection object instead of
+ a transaction. [975]
+ - __file__ is now usable in tac files
+ - twisted.cred.pamauth now contains a PAM checker (moved from twisted.conch)
+ - twisted.protocols.policies.LimitTotalConnectionsFactory now exists,
+ which does as the name suggests
+ - twisted.protocols.ident now uses /proc/net/tcp on Linux [233]
+ - trial now recurses packages by default (a la the old -R parameter)
+ - (PB) Calling a remote method that doesn't exist now raises
+ NoSuchMethod instead of AttributeError.
+
+Fixes
+-----
+ - FTP client and server improvements
+ - Trial improvements: The code is now much simpler, and more stable.
+ - twisted.protocols.basic.FileSender now works with empty files
+ - Twisted should now be much more usable on Pythons without thread support.
+ - minor improvements to process code in win32eventreactor
+ - twistd -y (--python) now implies -o (--nosave). [539]
+ - improved lockfile handling especially with respect to unix sockets.
+ - deferredGenerator now no longer overuses the stack, which sometimes
+ caused stack overflows.
+ - Failure.raiseException now at least always raises the correct Exception.
+ - minor improvements to serialport code
+
+Deprecations
+------------
+ - twisted.python.componts.getAdapter. Use IFoo(o) instead.
+ - Adapter persistence (IFoo(x, persist=True)). Just don't use it.
+ - log.debug. It was equivalent to log.msg(), just use that.
+ - twisted.protocols.telnet. twisted.conch.telnet replaces it.
+ - Setting a trial reporter using a flag to 'trial'. Instead of 'trial
+ --bwverbose', for example, use 'trial --reporter=bwverbose'.
+ - trial --coverage will become a flag in Twisted 2.2.
+ - passing a fully-qualified python name to --reporter is
+ deprecated. Pass only names of Reporter plugins.
+ - trial --psyco.
+ - trial -R (--recurse) is now the default, so passing it is deprecated.
+ - trial --reporter-args. Use the plugin system to do this sort of thing.
+ - trial.assertions.assertionMethod and trial.unittest.assertionMethod
+ are both deprecated. Use instance methods on TestCases instead.
+ - trial's deferredResult, deferredError, and wait functions. Return
+ Deferreds from your test methods instead of using them.
+ - Raising unittest.SkipTest with no arguments. Give a reason for your skip.
+ - The Failure returned from a gatherResults and DeferredList is now
+ of type FirstError instead of a tuple of (Exception, index). It
+ supports a firstError[idx] syntax but that is deprecated. Use
+ firstError.subFailure and firstError.index instead.
+ - whenThreaded now simply calls the passed function synchronously.
+
+2.0.1 (2005-05-09)
+===================
+Minor bug fix release.
+
+SVN rev (file) - [bug number] description
+-----------------------------------------
+13307 (twisted/topfiles/README) - Mention support for python 2.4, too
+13324 (twisted/internet/defer.py) - [947] Fix DeferredQueue backlog/size limit.
+13354 (twisted/plugins/__init__.py) - Correct maintainer address.
+13355 (twisted/test/test_defer.py) - improvements to DeferredQueue test case
+13387 (setup.py) - add news to list of subprojects to install
+13332 (twisted/internet/posixbase.py) - Fix spelling error
+13366 (twisted/internet/qtreactor.py) - [957] [954] reactor.iterate fixes
+13368 (twisted/test/test_internet.py) - Fix DelayedCall test case
+13422 (twisted/internet/posixbase.py) - Remove log from _Win32Waker creation.
+13437 (twisted/plugin.py) - [958] Only write cache if there were changes.
+13666 (twisted/internet/gtkreactor.py,gtk2reactor.py) - Don't run callbacks
+ until the reactor is actually up and running
+13748 (twisted/internet/gtk2reactor.py) - [552] [994] Initialize threading properly.
+
+
+2.0.0 (2005-03-25)
+==================
+
+Major new features
+------------------
+ - Replaced home-grown components system with zope.interface.
+ - Split Twisted into multiple pieces.
+ - Relicensed: Now under the MIT license, rather than LGPL.
+ - Python 2.4 compatibility fixes
+ - Major efficiency improvements in TCP buffering algorithm.
+ - Major efficiency improvements in reactor.callLater/DelayedCall.
+ - Half-close support for TCP/SSL. (loseWriteConnection).
+
+Miscellaneous features/fixes
+----------------------------
+ - New plugin system: twisted.plugin
+ - Better debugging support. Control-C will break you into PDB.
+ - The twistd command has --uid --gid command line arguments.
+ - *Incompatibility: mktap defaults to not change UID/GID, instead of saving
+ the invoking user's UID/GID.
+ - Removed some functions that were deprecated since Twisted 1.0.
+ - ZSH tab-completion for twisted commands.
+
+ - More correct daemonization in twistd.
+ - twisted.python.log: do not close the log because of invalid format string.
+ - Disabled automatic import of cBanana.
+ - Boolean support for twisted.persisted.marmalade.
+ - Refactor of plugin and application HOWTO documentation
+ - Async HOWTO expanded greatly.
+ - twisted.python.usage outputs the actual defaults, not passed in values.
+
+twisted.trial
+-------------
+ - Rewritten, a bunch of bugs fixed, a few more added.
+
+twisted.internet
+----------------
+ - Multi-listen UDP multicast support
+ - protocol.ClientCreator has a connectSSL.
+ - defer.deferredGenerator: allows you to write Deferred code w/o callbacks.
+ - Deferred.setTimeout is now deprecated.
+ - New defer.DeferredLock/DeferredSemaphore/DeferredQueue.
+ - Add utils.getProcessOutputAndValue to get stdout/err/value.
+
+ - Default DNS resolver is now non-blocking.
+ - Increased default TCP accept backlog from 5 to 50.
+ - Make buffering large amounts of TCP data work on Windows.
+ - Fixed SSL disconnect to not wait for remote host. Fixes issue with firefox.
+ - Separate state for Deferred finalization so that GC-loops preventing
+ finalization don't occur.
+ - Many Process bugfixes
+ - Processes spawned on windows can successfully use sockets
+ - gtk2reactor can optionally use glib event loop instead of gtk
+ - gtk2reactor notifies gobject to initialize thread support
+ - Fix registering a streaming producer on a transport.
+ - Close client sockets explicitly after failed connections.
+ - ReconnectingClientFactory now continues attempting to reconnect after all
+ errors, not just those which are not UserErrors.
+
+twisted.protocols
+-----------------
+ - Portforward doesn't start reading from a client until a connection is made.
+ - Bugfixes in twisted.protocols.loopback
+ - Improve speed of twisted.protocols.LineReceiver.
+ - LineReceiver implements IProducer. (stop/pause/resumeProducing)
+ - SOCKSv4 properly closes connections
+
+twisted.enterprise
+------------------
+ - Add "new connection" callback to adbapi.ConnectionPool to allow for
+ custom db connection setup (cp_openfun)
+ - adbapi.ConnectionPool automatic reconnection support
+ - Don't log exceptions extraneously
+
+
+1.3.0 (2004-05-14)
+==================
+
+- Address objects for IPv4 and Unix addresses throughout twisted.internet.
+- Improved connected UDP APIs.
+- Refactored SSH client support.
+- Initial implementation of Windows I/O Completion Ports event loop.
+- Bug fixes and feature enhancements.
+- Nevow support for Lore (so your Lore documents can use Nevow directives).
+- This is the last release before Twisted begins splitting up.
diff --git a/vendor/Twisted-10.0.0/twisted/topfiles/README b/vendor/Twisted-10.0.0/twisted/topfiles/README
new file mode 100644
index 0000000000..7d67a82f99
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/topfiles/README
@@ -0,0 +1,14 @@
+Twisted Core 10.0.0
+===================
+
+Twisted Core makes up the core parts of Twisted, including:
+
+ * Networking support (twisted.internet)
+ * Trial, the unit testing framework (twisted.trial)
+ * AMP, the Asynchronous Messaging Protocol (twisted.protocols.amp)
+ * Twisted Spread, a remote object system (twisted.spread)
+ * Utility code (twisted.python)
+ * Basic abstractions that multiple subprojects use
+ (twisted.cred, twisted.application, twisted.plugin)
+ * Database connectivity support (twisted.enterprise)
+ * A few basic protocols and protocol abstractions (twisted.protocols)
diff --git a/vendor/Twisted-10.0.0/twisted/topfiles/setup.py b/vendor/Twisted-10.0.0/twisted/topfiles/setup.py
new file mode 100644
index 0000000000..a135c77fbe
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/topfiles/setup.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Distutils installer for Twisted.
+"""
+
+import os, sys
+
+if sys.version_info < (2,3):
+ print >>sys.stderr, "You must use at least Python 2.3 for Twisted"
+ sys.exit(3)
+
+if os.path.exists('twisted'):
+ sys.path.insert(0, '.') # eek! need this to import twisted. sorry.
+from twisted import copyright
+from twisted.python.dist import setup, ConditionalExtension as Extension
+from twisted.python.dist import getPackages, getDataFiles, getScripts
+from twisted.python.dist import twisted_subprojects
+
+
+
+extensions = [
+ Extension("twisted.protocols._c_urlarg",
+ ["twisted/protocols/_c_urlarg.c"]),
+
+ Extension("twisted.test.raiser",
+ ["twisted/test/raiser.c"]),
+
+ Extension("twisted.python._epoll",
+ ["twisted/python/_epoll.c"],
+ condition=lambda builder: builder._check_header("sys/epoll.h")),
+
+ Extension("twisted.internet.iocpreactor.iocpsupport",
+ ["twisted/internet/iocpreactor/iocpsupport/iocpsupport.c",
+ "twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c"],
+ libraries=["ws2_32"],
+ condition=lambda builder: sys.platform == "win32"),
+
+ Extension("twisted.internet.cfsupport",
+ ["twisted/internet/cfsupport/cfsupport.c"],
+ extra_compile_args=['-w'],
+ extra_link_args=['-framework','CoreFoundation',
+ '-framework','CoreServices',
+ '-framework','Carbon'],
+ condition=lambda builder: sys.platform == "darwin"),
+
+ Extension("twisted.python._initgroups",
+ ["twisted/python/_initgroups.c"]),
+]
+
+# Figure out which plugins to include: all plugins except subproject ones
+subProjectsPlugins = ['twisted_%s.py' % subProject
+ for subProject in twisted_subprojects]
+plugins = os.listdir(os.path.join(
+ os.path.dirname(os.path.abspath(copyright.__file__)), 'plugins'))
+plugins = [plugin[:-3] for plugin in plugins if plugin.endswith('.py') and
+ plugin not in subProjectsPlugins]
+
+
+
+setup_args = dict(
+ # metadata
+ name="Twisted Core",
+ version=copyright.version,
+ description="The core parts of the Twisted networking framework",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="Glyph Lefkowitz",
+ url="http://twistedmatrix.com/",
+ license="MIT",
+ long_description="""\
+This is the core of Twisted, including:
+ * Networking support (twisted.internet)
+ * Trial, the unit testing framework (twisted.trial)
+ * AMP, the Asynchronous Messaging Protocol (twisted.protocols.amp)
+ * Twisted Spread, a remote object system (twisted.spread)
+ * Utility code (twisted.python)
+ * Basic abstractions that multiple subprojects use
+ (twisted.cred, twisted.application, twisted.plugin)
+ * Database connectivity support (twisted.enterprise)
+ * A few basic protocols and protocol abstractions (twisted.protocols)
+""",
+
+ # build stuff
+ packages=getPackages('twisted',
+ ignore=twisted_subprojects + ['plugins']),
+ plugins=plugins,
+ data_files=getDataFiles('twisted', ignore=twisted_subprojects),
+ conditionalExtensions=extensions,
+ scripts = getScripts(""),
+)
+
+
+if __name__ == '__main__':
+ setup(**setup_args)
diff --git a/vendor/Twisted-10.0.0/twisted/trial/__init__.py b/vendor/Twisted-10.0.0/twisted/trial/__init__.py
new file mode 100644
index 0000000000..f2d5aadf9d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/__init__.py
@@ -0,0 +1,52 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+#
+# Maintainer: Jonathan Lange
+
+"""
+Asynchronous unit testing framework.
+
+Trial extends Python's builtin C{unittest} to provide support for asynchronous
+tests.
+
+Maintainer: Jonathan Lange
+
+Trial strives to be compatible with other Python xUnit testing frameworks.
+"Compatibility" is a difficult things to define. In practice, it means that:
+
+ - L{twisted.trial.unittest.TestCase} objects should be able to be used by
+ other test runners without those runners requiring special support for
+ Trial tests.
+
+ - Tests that subclass the standard library C{TestCase} and don't do anything
+ "too weird" should be able to be discoverable and runnable by the Trial
+ test runner without the authors of those tests having to jump through
+ hoops.
+
+ - Tests that implement the interface provided by the standard library
+ C{TestCase} should be runnable by the Trial runner.
+
+ - The Trial test runner and Trial L{unittest.TestCase} objects ought to be
+ able to use standard library C{TestResult} objects, and third party
+ C{TestResult} objects based on the standard library.
+
+This list is not necessarily exhaustive -- compatibility is hard to define.
+Contributors who discover more helpful ways of defining compatibility are
+encouraged to update this document.
+
+
+Examples:
+
+B{Timeouts} for tests should be implemented in the runner. If this is done,
+then timeouts could work for third-party TestCase objects as well as for
+L{twisted.trial.unittest.TestCase} objects. Further, Twisted C{TestCase}
+objects will run in other runners without timing out.
+See U{http://twistedmatrix.com/trac/ticket/2675}.
+
+Running tests in a temporary directory should be a feature of the test case,
+because often tests themselves rely on this behaviour. If the feature is
+implemented in the runner, then tests will change behaviour (possibly
+breaking) when run in a different test runner. Further, many tests don't even
+care about the filesystem.
+See U{http://twistedmatrix.com/trac/ticket/2916}.
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/trial/itrial.py b/vendor/Twisted-10.0.0/twisted/trial/itrial.py
new file mode 100644
index 0000000000..5b3afbe5d4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/itrial.py
@@ -0,0 +1,251 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Interfaces for Trial.
+
+Maintainer: Jonathan Lange
+"""
+
+import zope.interface as zi
+from zope.interface import Attribute
+
+
+class ITestCase(zi.Interface):
+ """
+ The interface that a test case must implement in order to be used in Trial.
+ """
+
+ failureException = zi.Attribute(
+ "The exception class that is raised by failed assertions")
+
+
+ def __call__(result):
+ """
+ Run the test. Should always do exactly the same thing as run().
+ """
+
+
+ def countTestCases():
+ """
+ Return the number of tests in this test case. Usually 1.
+ """
+
+
+ def id():
+ """
+ Return a unique identifier for the test, usually the fully-qualified
+ Python name.
+ """
+
+
+ def run(result):
+ """
+ Run the test, storing the results in C{result}.
+
+ @param result: A L{TestResult}.
+ """
+
+
+ def shortDescription():
+ """
+ Return a short description of the test.
+ """
+
+
+
+class IReporter(zi.Interface):
+ """
+ I report results from a run of a test suite.
+ """
+
+ stream = zi.Attribute(
+ "Deprecated in Twisted 8.0. "
+ "The io-stream that this reporter will write to")
+ tbformat = zi.Attribute("Either 'default', 'brief', or 'verbose'")
+ args = zi.Attribute(
+ "Additional string argument passed from the command line")
+ shouldStop = zi.Attribute(
+ """
+ A boolean indicating that this reporter would like the test run to stop.
+ """)
+ separator = Attribute(
+ "Deprecated in Twisted 8.0. "
+ "A value which will occasionally be passed to the L{write} method.")
+ testsRun = Attribute(
+ """
+ The number of tests that seem to have been run according to this
+ reporter.
+ """)
+
+
+ def startTest(method):
+ """
+ Report the beginning of a run of a single test method.
+
+ @param method: an object that is adaptable to ITestMethod
+ """
+
+
+ def stopTest(method):
+ """
+ Report the status of a single test method
+
+ @param method: an object that is adaptable to ITestMethod
+ """
+
+
+ def startSuite(name):
+ """
+ Deprecated in Twisted 8.0.
+
+ Suites which wish to appear in reporter output should call this
+ before running their tests.
+ """
+
+
+ def endSuite(name):
+ """
+ Deprecated in Twisted 8.0.
+
+ Called at the end of a suite, if and only if that suite has called
+ C{startSuite}.
+ """
+
+
+ def cleanupErrors(errs):
+ """
+ Deprecated in Twisted 8.0.
+
+ Called when the reactor has been left in a 'dirty' state
+
+ @param errs: a list of L{twisted.python.failure.Failure}s
+ """
+
+
+ def upDownError(userMeth, warn=True, printStatus=True):
+ """
+ Deprecated in Twisted 8.0.
+
+ Called when an error occurs in a setUp* or tearDown* method
+
+ @param warn: indicates whether or not the reporter should emit a
+ warning about the error
+ @type warn: Boolean
+ @param printStatus: indicates whether or not the reporter should
+ print the name of the method and the status
+ message appropriate for the type of error
+ @type printStatus: Boolean
+ """
+
+
+ def addSuccess(test):
+ """
+ Record that test passed.
+ """
+
+
+ def addError(test, error):
+ """
+ Record that a test has raised an unexpected exception.
+
+ @param test: The test that has raised an error.
+ @param error: The error that the test raised. It will either be a
+ three-tuple in the style of C{sys.exc_info()} or a
+ L{Failure<twisted.python.failure.Failure>} object.
+ """
+
+
+ def addFailure(test, failure):
+ """
+ Record that a test has failed with the given failure.
+
+ @param test: The test that has failed.
+ @param failure: The failure that the test failed with. It will
+ either be a three-tuple in the style of C{sys.exc_info()}
+ or a L{Failure<twisted.python.failure.Failure>} object.
+ """
+
+
+ def addExpectedFailure(test, failure, todo):
+ """
+ Record that the given test failed, and was expected to do so.
+
+ @type test: L{pyunit.TestCase}
+ @param test: The test which this is about.
+ @type error: L{failure.Failure}
+ @param error: The error which this test failed with.
+ @type todo: L{unittest.Todo}
+ @param todo: The reason for the test's TODO status.
+ """
+
+
+ def addUnexpectedSuccess(test, todo):
+ """
+ Record that the given test failed, and was expected to do so.
+
+ @type test: L{pyunit.TestCase}
+ @param test: The test which this is about.
+ @type todo: L{unittest.Todo}
+ @param todo: The reason for the test's TODO status.
+ """
+
+
+ def addSkip(test, reason):
+ """
+ Record that a test has been skipped for the given reason.
+
+ @param test: The test that has been skipped.
+ @param reason: An object that the test case has specified as the reason
+ for skipping the test.
+ """
+
+
+ def printSummary():
+ """
+ Deprecated in Twisted 8.0, use L{done} instead.
+
+ Present a summary of the test results.
+ """
+
+
+ def printErrors():
+ """
+ Deprecated in Twisted 8.0, use L{done} instead.
+
+ Present the errors that have occured during the test run. This method
+ will be called after all tests have been run.
+ """
+
+
+ def write(string):
+ """
+ Deprecated in Twisted 8.0, use L{done} instead.
+
+ Display a string to the user, without appending a new line.
+ """
+
+
+ def writeln(string):
+ """
+ Deprecated in Twisted 8.0, use L{done} instead.
+
+ Display a string to the user, appending a new line.
+ """
+
+ def wasSuccessful():
+ """
+ Return a boolean indicating whether all test results that were reported
+ to this reporter were successful or not.
+ """
+
+
+ def done():
+ """
+ Called when the test run is complete.
+
+ This gives the result object an opportunity to display a summary of
+ information to the user. Once you have called C{done} on an
+ L{IReporter} object, you should assume that the L{IReporter} object is
+ no longer usable.
+ """
diff --git a/vendor/Twisted-10.0.0/twisted/trial/reporter.py b/vendor/Twisted-10.0.0/twisted/trial/reporter.py
new file mode 100644
index 0000000000..f7d7e0c1c5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/reporter.py
@@ -0,0 +1,1204 @@
+# -*- test-case-name: twisted.trial.test.test_reporter -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+#
+# Maintainer: Jonathan Lange
+
+"""
+Defines classes that handle the results of tests.
+"""
+
+import sys, os
+import time
+import warnings
+
+from twisted.python.compat import set
+from twisted.python import reflect, log
+from twisted.python.components import proxyForInterface
+from twisted.python.failure import Failure
+from twisted.python.util import untilConcludes
+from twisted.trial import itrial, util
+
+try:
+ from subunit import TestProtocolClient
+except ImportError:
+ TestProtocolClient = None
+from zope.interface import implements
+
+pyunit = __import__('unittest')
+
+
+class BrokenTestCaseWarning(Warning):
+ """
+ Emitted as a warning when an exception occurs in one of setUp or tearDown.
+ """
+
+
+class SafeStream(object):
+ """
+ Wraps a stream object so that all C{write} calls are wrapped in
+ L{untilConcludes}.
+ """
+
+ def __init__(self, original):
+ self.original = original
+
+ def __getattr__(self, name):
+ return getattr(self.original, name)
+
+ def write(self, *a, **kw):
+ return untilConcludes(self.original.write, *a, **kw)
+
+
+class TestResult(pyunit.TestResult, object):
+ """
+ Accumulates the results of several L{twisted.trial.unittest.TestCase}s.
+
+ @ivar successes: count the number of successes achieved by the test run.
+ @type successes: C{int}
+ """
+ implements(itrial.IReporter)
+
+ def __init__(self):
+ super(TestResult, self).__init__()
+ self.skips = []
+ self.expectedFailures = []
+ self.unexpectedSuccesses = []
+ self.successes = 0
+ self._timings = []
+
+ def __repr__(self):
+ return ('<%s run=%d errors=%d failures=%d todos=%d dones=%d skips=%d>'
+ % (reflect.qual(self.__class__), self.testsRun,
+ len(self.errors), len(self.failures),
+ len(self.expectedFailures), len(self.skips),
+ len(self.unexpectedSuccesses)))
+
+ def _getTime(self):
+ return time.time()
+
+ def _getFailure(self, error):
+ """
+ Convert a C{sys.exc_info()}-style tuple to a L{Failure}, if necessary.
+ """
+ if isinstance(error, tuple):
+ return Failure(error[1], error[0], error[2])
+ return error
+
+ def startTest(self, test):
+ """
+ This must be called before the given test is commenced.
+
+ @type test: L{pyunit.TestCase}
+ """
+ super(TestResult, self).startTest(test)
+ self._testStarted = self._getTime()
+
+ def stopTest(self, test):
+ """
+ This must be called after the given test is completed.
+
+ @type test: L{pyunit.TestCase}
+ """
+ super(TestResult, self).stopTest(test)
+ self._lastTime = self._getTime() - self._testStarted
+
+ def addFailure(self, test, fail):
+ """
+ Report a failed assertion for the given test.
+
+ @type test: L{pyunit.TestCase}
+ @type fail: L{Failure} or L{tuple}
+ """
+ self.failures.append((test, self._getFailure(fail)))
+
+ def addError(self, test, error):
+ """
+ Report an error that occurred while running the given test.
+
+ @type test: L{pyunit.TestCase}
+ @type error: L{Failure} or L{tuple}
+ """
+ self.errors.append((test, self._getFailure(error)))
+
+ def addSkip(self, test, reason):
+ """
+ Report that the given test was skipped.
+
+ In Trial, tests can be 'skipped'. Tests are skipped mostly because there
+ is some platform or configuration issue that prevents them from being
+ run correctly.
+
+ @type test: L{pyunit.TestCase}
+ @type reason: L{str}
+ """
+ self.skips.append((test, reason))
+
+ def addUnexpectedSuccess(self, test, todo):
+ """Report that the given test succeeded against expectations.
+
+ In Trial, tests can be marked 'todo'. That is, they are expected to fail.
+ When a test that is expected to fail instead succeeds, it should call
+ this method to report the unexpected success.
+
+ @type test: L{pyunit.TestCase}
+ @type todo: L{unittest.Todo}
+ """
+ # XXX - 'todo' should just be a string
+ self.unexpectedSuccesses.append((test, todo))
+
+ def addExpectedFailure(self, test, error, todo):
+ """Report that the given test failed, and was expected to do so.
+
+ In Trial, tests can be marked 'todo'. That is, they are expected to fail.
+
+ @type test: L{pyunit.TestCase}
+ @type error: L{Failure}
+ @type todo: L{unittest.Todo}
+ """
+ # XXX - 'todo' should just be a string
+ self.expectedFailures.append((test, error, todo))
+
+ def addSuccess(self, test):
+ """Report that the given test succeeded.
+
+ @type test: L{pyunit.TestCase}
+ """
+ self.successes += 1
+
+ def upDownError(self, method, error, warn, printStatus):
+ warnings.warn("upDownError is deprecated in Twisted 8.0.",
+ category=DeprecationWarning, stacklevel=3)
+
+ def cleanupErrors(self, errs):
+ """Report an error that occurred during the cleanup between tests.
+ """
+ warnings.warn("Cleanup errors are actual errors. Use addError. "
+ "Deprecated in Twisted 8.0",
+ category=DeprecationWarning, stacklevel=2)
+
+ def startSuite(self, name):
+ warnings.warn("startSuite deprecated in Twisted 8.0",
+ category=DeprecationWarning, stacklevel=2)
+
+ def endSuite(self, name):
+ warnings.warn("endSuite deprecated in Twisted 8.0",
+ category=DeprecationWarning, stacklevel=2)
+
+
+ def done(self):
+ """
+ The test suite has finished running.
+ """
+
+
+
+class TestResultDecorator(proxyForInterface(itrial.IReporter,
+ "_originalReporter")):
+ """
+ Base class for TestResult decorators.
+
+ @ivar _originalReporter: The wrapped instance of reporter.
+ @type _originalReporter: A provider of L{itrial.IReporter}
+ """
+
+ implements(itrial.IReporter)
+
+
+
+class UncleanWarningsReporterWrapper(TestResultDecorator):
+ """
+ A wrapper for a reporter that converts L{util.DirtyReactorError}s
+ to warnings.
+ """
+ implements(itrial.IReporter)
+
+ def addError(self, test, error):
+ """
+ If the error is a L{util.DirtyReactorError}, instead of
+ reporting it as a normal error, throw a warning.
+ """
+
+ if (isinstance(error, Failure)
+ and error.check(util.DirtyReactorAggregateError)):
+ warnings.warn(error.getErrorMessage())
+ else:
+ self._originalReporter.addError(test, error)
+
+
+
+class _AdaptedReporter(TestResultDecorator):
+ """
+ TestResult decorator that makes sure that addError only gets tests that
+ have been adapted with a particular test adapter.
+ """
+
+ def __init__(self, original, testAdapter):
+ """
+ Construct an L{_AdaptedReporter}.
+
+ @param original: An {itrial.IReporter}.
+ @param testAdapter: A callable that returns an L{itrial.ITestCase}.
+ """
+ TestResultDecorator.__init__(self, original)
+ self.testAdapter = testAdapter
+
+
+ def addError(self, test, error):
+ """
+ See L{itrial.IReporter}.
+ """
+ test = self.testAdapter(test)
+ return self._originalReporter.addError(test, error)
+
+
+ def addExpectedFailure(self, test, failure, todo):
+ """
+ See L{itrial.IReporter}.
+ """
+ return self._originalReporter.addExpectedFailure(
+ self.testAdapter(test), failure, todo)
+
+
+ def addFailure(self, test, failure):
+ """
+ See L{itrial.IReporter}.
+ """
+ test = self.testAdapter(test)
+ return self._originalReporter.addFailure(test, failure)
+
+
+ def addSkip(self, test, skip):
+ """
+ See L{itrial.IReporter}.
+ """
+ test = self.testAdapter(test)
+ return self._originalReporter.addSkip(test, skip)
+
+
+ def addUnexpectedSuccess(self, test, todo):
+ """
+ See L{itrial.IReporter}.
+ """
+ test = self.testAdapter(test)
+ return self._originalReporter.addUnexpectedSuccess(test, todo)
+
+
+ def startTest(self, test):
+ """
+ See L{itrial.IReporter}.
+ """
+ return self._originalReporter.startTest(self.testAdapter(test))
+
+
+ def stopTest(self, test):
+ """
+ See L{itrial.IReporter}.
+ """
+ return self._originalReporter.stopTest(self.testAdapter(test))
+
+
+
+class Reporter(TestResult):
+ """
+ A basic L{TestResult} with support for writing to a stream.
+
+ @ivar _startTime: The time when the first test was started. It defaults to
+ C{None}, which means that no test was actually launched.
+ @type _startTime: C{float} or C{NoneType}
+
+ @ivar _warningCache: A C{set} of tuples of warning message (file, line,
+ text, category) which have already been written to the output stream
+ during the currently executing test. This is used to avoid writing
+ duplicates of the same warning to the output stream.
+ @type _warningCache: C{set}
+
+ @ivar _publisher: The log publisher which will be observed for warning
+ events.
+ @type _publisher: L{LogPublisher} (or another type sufficiently similar)
+ """
+
+ implements(itrial.IReporter)
+
+ _separator = '-' * 79
+ _doubleSeparator = '=' * 79
+
+ def __init__(self, stream=sys.stdout, tbformat='default', realtime=False,
+ publisher=None):
+ super(Reporter, self).__init__()
+ self._stream = SafeStream(stream)
+ self.tbformat = tbformat
+ self.realtime = realtime
+ self._startTime = None
+ self._warningCache = set()
+
+ # Start observing log events so as to be able to report warnings.
+ self._publisher = publisher
+ if publisher is not None:
+ publisher.addObserver(self._observeWarnings)
+
+
+ def _observeWarnings(self, event):
+ """
+ Observe warning events and write them to C{self._stream}.
+
+ This method is a log observer which will be registered with
+ C{self._publisher.addObserver}.
+
+ @param event: A C{dict} from the logging system. If it has a
+ C{'warning'} key, a logged warning will be extracted from it and
+ possibly written to C{self.stream}.
+ """
+ if 'warning' in event:
+ key = (event['filename'], event['lineno'],
+ event['category'].split('.')[-1],
+ str(event['warning']))
+ if key not in self._warningCache:
+ self._warningCache.add(key)
+ self._stream.write('%s:%s: %s: %s\n' % key)
+
+
+ def stream(self):
+ warnings.warn("stream is deprecated in Twisted 8.0.",
+ category=DeprecationWarning, stacklevel=2)
+ return self._stream
+ stream = property(stream)
+
+
+ def separator(self):
+ warnings.warn("separator is deprecated in Twisted 8.0.",
+ category=DeprecationWarning, stacklevel=2)
+ return self._separator
+ separator = property(separator)
+
+
+ def startTest(self, test):
+ """
+ Called when a test begins to run. Records the time when it was first
+ called and resets the warning cache.
+
+ @param test: L{ITestCase}
+ """
+ super(Reporter, self).startTest(test)
+ if self._startTime is None:
+ self._startTime = self._getTime()
+ self._warningCache = set()
+
+
+ def addFailure(self, test, fail):
+ """
+ Called when a test fails. If L{realtime} is set, then it prints the
+ error to the stream.
+
+ @param test: L{ITestCase} that failed.
+ @param fail: L{failure.Failure} containing the error.
+ """
+ super(Reporter, self).addFailure(test, fail)
+ if self.realtime:
+ fail = self.failures[-1][1] # guarantee it's a Failure
+ self._write(self._formatFailureTraceback(fail))
+
+
+ def addError(self, test, error):
+ """
+ Called when a test raises an error. If L{realtime} is set, then it
+ prints the error to the stream.
+
+ @param test: L{ITestCase} that raised the error.
+ @param error: L{failure.Failure} containing the error.
+ """
+ error = self._getFailure(error)
+ super(Reporter, self).addError(test, error)
+ if self.realtime:
+ error = self.errors[-1][1] # guarantee it's a Failure
+ self._write(self._formatFailureTraceback(error))
+
+
+ def write(self, format, *args):
+ warnings.warn("write is deprecated in Twisted 8.0.",
+ category=DeprecationWarning, stacklevel=2)
+ self._write(format, *args)
+
+
+ def _write(self, format, *args):
+ """
+ Safely write to the reporter's stream.
+
+ @param format: A format string to write.
+ @param *args: The arguments for the format string.
+ """
+ s = str(format)
+ assert isinstance(s, type(''))
+ if args:
+ self._stream.write(s % args)
+ else:
+ self._stream.write(s)
+ untilConcludes(self._stream.flush)
+
+
+ def writeln(self, format, *args):
+ warnings.warn("writeln is deprecated in Twisted 8.0.",
+ category=DeprecationWarning, stacklevel=2)
+ self._writeln(format, *args)
+
+
+ def _writeln(self, format, *args):
+ """
+ Safely write a line to the reporter's stream. Newline is appended to
+ the format string.
+
+ @param format: A format string to write.
+ @param *args: The arguments for the format string.
+ """
+ self._write(format, *args)
+ self._write('\n')
+
+
+ def upDownError(self, method, error, warn, printStatus):
+ super(Reporter, self).upDownError(method, error, warn, printStatus)
+ if warn:
+ tbStr = self._formatFailureTraceback(error)
+ log.msg(tbStr)
+ msg = ("caught exception in %s, your TestCase is broken\n\n%s"
+ % (method, tbStr))
+ warnings.warn(msg, BrokenTestCaseWarning, stacklevel=2)
+
+
+ def cleanupErrors(self, errs):
+ super(Reporter, self).cleanupErrors(errs)
+ warnings.warn("%s\n%s" % ("REACTOR UNCLEAN! traceback(s) follow: ",
+ self._formatFailureTraceback(errs)),
+ BrokenTestCaseWarning)
+
+
+ def _trimFrames(self, frames):
+ # when a method fails synchronously, the stack looks like this:
+ # [0]: defer.maybeDeferred()
+ # [1]: utils.runWithWarningsSuppressed()
+ # [2:-2]: code in the test method which failed
+ # [-1]: unittest.fail
+
+ # when a method fails inside a Deferred (i.e., when the test method
+ # returns a Deferred, and that Deferred's errback fires), the stack
+ # captured inside the resulting Failure looks like this:
+ # [0]: defer.Deferred._runCallbacks
+ # [1:-2]: code in the testmethod which failed
+ # [-1]: unittest.fail
+
+ # as a result, we want to trim either [maybeDeferred,runWWS] or
+ # [Deferred._runCallbacks] from the front, and trim the
+ # [unittest.fail] from the end.
+
+ # There is also another case, when the test method is badly defined and
+ # contains extra arguments.
+
+ newFrames = list(frames)
+
+ if len(frames) < 2:
+ return newFrames
+
+ first = newFrames[0]
+ second = newFrames[1]
+ if (first[0] == "maybeDeferred"
+ and os.path.splitext(os.path.basename(first[1]))[0] == 'defer'
+ and second[0] == "runWithWarningsSuppressed"
+ and os.path.splitext(os.path.basename(second[1]))[0] == 'utils'):
+ newFrames = newFrames[2:]
+ elif (first[0] == "_runCallbacks"
+ and os.path.splitext(os.path.basename(first[1]))[0] == 'defer'):
+ newFrames = newFrames[1:]
+
+ if not newFrames:
+ # The method fails before getting called, probably an argument problem
+ return newFrames
+
+ last = newFrames[-1]
+ if (last[0].startswith('fail')
+ and os.path.splitext(os.path.basename(last[1]))[0] == 'unittest'):
+ newFrames = newFrames[:-1]
+
+ return newFrames
+
+
+ def _formatFailureTraceback(self, fail):
+ if isinstance(fail, str):
+ return fail.rstrip() + '\n'
+ fail.frames, frames = self._trimFrames(fail.frames), fail.frames
+ result = fail.getTraceback(detail=self.tbformat, elideFrameworkCode=True)
+ fail.frames = frames
+ return result
+
+
+ def _printResults(self, flavour, errors, formatter):
+ """
+ Print a group of errors to the stream.
+
+ @param flavour: A string indicating the kind of error (e.g. 'TODO').
+ @param errors: A list of errors, often L{failure.Failure}s, but
+ sometimes 'todo' errors.
+ @param formatter: A callable that knows how to format the errors.
+ """
+ for content in errors:
+ self._writeln(self._doubleSeparator)
+ self._writeln('%s: %s' % (flavour, content[0].id()))
+ self._writeln('')
+ self._write(formatter(*(content[1:])))
+
+
+ def _printExpectedFailure(self, error, todo):
+ return 'Reason: %r\n%s' % (todo.reason,
+ self._formatFailureTraceback(error))
+
+
+ def _printUnexpectedSuccess(self, todo):
+ ret = 'Reason: %r\n' % (todo.reason,)
+ if todo.errors:
+ ret += 'Expected errors: %s\n' % (', '.join(todo.errors),)
+ return ret
+
+
+ def printErrors(self):
+ """
+ Print all of the non-success results in full to the stream.
+ """
+ warnings.warn("printErrors is deprecated in Twisted 8.0.",
+ category=DeprecationWarning, stacklevel=2)
+ self._printErrors()
+
+
+ def _printErrors(self):
+ """
+ Print all of the non-success results to the stream in full.
+ """
+ self._write('\n')
+ self._printResults('[SKIPPED]', self.skips, lambda x : '%s\n' % x)
+ self._printResults('[TODO]', self.expectedFailures,
+ self._printExpectedFailure)
+ self._printResults('[FAIL]', self.failures,
+ self._formatFailureTraceback)
+ self._printResults('[ERROR]', self.errors,
+ self._formatFailureTraceback)
+ self._printResults('[SUCCESS!?!]', self.unexpectedSuccesses,
+ self._printUnexpectedSuccess)
+
+
+ def _getSummary(self):
+ """
+ Return a formatted count of tests status results.
+ """
+ summaries = []
+ for stat in ("skips", "expectedFailures", "failures", "errors",
+ "unexpectedSuccesses"):
+ num = len(getattr(self, stat))
+ if num:
+ summaries.append('%s=%d' % (stat, num))
+ if self.successes:
+ summaries.append('successes=%d' % (self.successes,))
+ summary = (summaries and ' ('+', '.join(summaries)+')') or ''
+ return summary
+
+
+ def printSummary(self):
+ """
+ Print a line summarising the test results to the stream.
+ """
+ warnings.warn("printSummary is deprecated in Twisted 8.0.",
+ category=DeprecationWarning, stacklevel=2)
+ self._printSummary()
+
+
+ def _printSummary(self):
+ """
+ Print a line summarising the test results to the stream.
+ """
+ summary = self._getSummary()
+ if self.wasSuccessful():
+ status = "PASSED"
+ else:
+ status = "FAILED"
+ self._write("%s%s\n", status, summary)
+
+
+ def done(self):
+ """
+ Summarize the result of the test run.
+
+ The summary includes a report of all of the errors, todos, skips and
+ so forth that occurred during the run. It also includes the number of
+ tests that were run and how long it took to run them (not including
+ load time).
+
+ Expects that L{_printErrors}, L{_writeln}, L{_write}, L{_printSummary}
+ and L{_separator} are all implemented.
+ """
+ if self._publisher is not None:
+ self._publisher.removeObserver(self._observeWarnings)
+ self._printErrors()
+ self._writeln(self._separator)
+ if self._startTime is not None:
+ self._writeln('Ran %d tests in %.3fs', self.testsRun,
+ time.time() - self._startTime)
+ self._write('\n')
+ self._printSummary()
+
+
+
+class MinimalReporter(Reporter):
+ """
+ A minimalist reporter that prints only a summary of the test result, in
+ the form of (timeTaken, #tests, #tests, #errors, #failures, #skips).
+ """
+
+ def _printErrors(self):
+ """
+ Don't print a detailed summary of errors. We only care about the
+ counts.
+ """
+
+
+ def _printSummary(self):
+ """
+ Print out a one-line summary of the form:
+ '%(runtime) %(number_of_tests) %(number_of_tests) %(num_errors)
+ %(num_failures) %(num_skips)'
+ """
+ numTests = self.testsRun
+ if self._startTime is not None:
+ timing = self._getTime() - self._startTime
+ else:
+ timing = 0
+ t = (timing, numTests, numTests,
+ len(self.errors), len(self.failures), len(self.skips))
+ self._writeln(' '.join(map(str, t)))
+
+
+
+class TextReporter(Reporter):
+ """
+ Simple reporter that prints a single character for each test as it runs,
+ along with the standard Trial summary text.
+ """
+
+ def addSuccess(self, test):
+ super(TextReporter, self).addSuccess(test)
+ self._write('.')
+
+
+ def addError(self, *args):
+ super(TextReporter, self).addError(*args)
+ self._write('E')
+
+
+ def addFailure(self, *args):
+ super(TextReporter, self).addFailure(*args)
+ self._write('F')
+
+
+ def addSkip(self, *args):
+ super(TextReporter, self).addSkip(*args)
+ self._write('S')
+
+
+ def addExpectedFailure(self, *args):
+ super(TextReporter, self).addExpectedFailure(*args)
+ self._write('T')
+
+
+ def addUnexpectedSuccess(self, *args):
+ super(TextReporter, self).addUnexpectedSuccess(*args)
+ self._write('!')
+
+
+
+class VerboseTextReporter(Reporter):
+ """
+ A verbose reporter that prints the name of each test as it is running.
+
+ Each line is printed with the name of the test, followed by the result of
+ that test.
+ """
+
+ # This is actually the bwverbose option
+
+ def startTest(self, tm):
+ self._write('%s ... ', tm.id())
+ super(VerboseTextReporter, self).startTest(tm)
+
+
+ def addSuccess(self, test):
+ super(VerboseTextReporter, self).addSuccess(test)
+ self._write('[OK]')
+
+
+ def addError(self, *args):
+ super(VerboseTextReporter, self).addError(*args)
+ self._write('[ERROR]')
+
+
+ def addFailure(self, *args):
+ super(VerboseTextReporter, self).addFailure(*args)
+ self._write('[FAILURE]')
+
+
+ def addSkip(self, *args):
+ super(VerboseTextReporter, self).addSkip(*args)
+ self._write('[SKIPPED]')
+
+
+ def addExpectedFailure(self, *args):
+ super(VerboseTextReporter, self).addExpectedFailure(*args)
+ self._write('[TODO]')
+
+
+ def addUnexpectedSuccess(self, *args):
+ super(VerboseTextReporter, self).addUnexpectedSuccess(*args)
+ self._write('[SUCCESS!?!]')
+
+
+ def stopTest(self, test):
+ super(VerboseTextReporter, self).stopTest(test)
+ self._write('\n')
+
+
+
+class TimingTextReporter(VerboseTextReporter):
+ """
+ Prints out each test as it is running, followed by the time taken for each
+ test to run.
+ """
+
+ def stopTest(self, method):
+ """
+ Mark the test as stopped, and write the time it took to run the test
+ to the stream.
+ """
+ super(TimingTextReporter, self).stopTest(method)
+ self._write("(%.03f secs)\n" % self._lastTime)
+
+
+
+class _AnsiColorizer(object):
+ """
+ A colorizer is an object that loosely wraps around a stream, allowing
+ callers to write text to the stream in a particular color.
+
+ Colorizer classes must implement C{supported()} and C{write(text, color)}.
+ """
+ _colors = dict(black=30, red=31, green=32, yellow=33,
+ blue=34, magenta=35, cyan=36, white=37)
+
+ def __init__(self, stream):
+ self.stream = stream
+
+ def supported(cls, stream=sys.stdout):
+ """
+ A class method that returns True if the current platform supports
+ coloring terminal output using this method. Returns False otherwise.
+ """
+ if not stream.isatty():
+ return False # auto color only on TTYs
+ try:
+ import curses
+ except ImportError:
+ return False
+ else:
+ try:
+ try:
+ return curses.tigetnum("colors") > 2
+ except curses.error:
+ curses.setupterm()
+ return curses.tigetnum("colors") > 2
+ except:
+ # guess false in case of error
+ return False
+ supported = classmethod(supported)
+
+ def write(self, text, color):
+ """
+ Write the given text to the stream in the given color.
+
+ @param text: Text to be written to the stream.
+
+ @param color: A string label for a color. e.g. 'red', 'white'.
+ """
+ color = self._colors[color]
+ self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
+
+
+class _Win32Colorizer(object):
+ """
+ See _AnsiColorizer docstring.
+ """
+ def __init__(self, stream):
+ from win32console import GetStdHandle, STD_OUTPUT_HANDLE, \
+ FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \
+ FOREGROUND_INTENSITY
+ red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN,
+ FOREGROUND_BLUE, FOREGROUND_INTENSITY)
+ self.stream = stream
+ self.screenBuffer = GetStdHandle(STD_OUTPUT_HANDLE)
+ self._colors = {
+ 'normal': red | green | blue,
+ 'red': red | bold,
+ 'green': green | bold,
+ 'blue': blue | bold,
+ 'yellow': red | green | bold,
+ 'magenta': red | blue | bold,
+ 'cyan': green | blue | bold,
+ 'white': red | green | blue | bold
+ }
+
+ def supported(cls, stream=sys.stdout):
+ try:
+ import win32console
+ screenBuffer = win32console.GetStdHandle(
+ win32console.STD_OUTPUT_HANDLE)
+ except ImportError:
+ return False
+ import pywintypes
+ try:
+ screenBuffer.SetConsoleTextAttribute(
+ win32console.FOREGROUND_RED |
+ win32console.FOREGROUND_GREEN |
+ win32console.FOREGROUND_BLUE)
+ except pywintypes.error:
+ return False
+ else:
+ return True
+ supported = classmethod(supported)
+
+ def write(self, text, color):
+ color = self._colors[color]
+ self.screenBuffer.SetConsoleTextAttribute(color)
+ self.stream.write(text)
+ self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
+
+
+class _NullColorizer(object):
+ """
+ See _AnsiColorizer docstring.
+ """
+ def __init__(self, stream):
+ self.stream = stream
+
+ def supported(cls, stream=sys.stdout):
+ return True
+ supported = classmethod(supported)
+
+ def write(self, text, color):
+ self.stream.write(text)
+
+
+
+class SubunitReporter(object):
+ """
+ Reports test output via Subunit.
+
+ @ivar _subunit: The subunit protocol client that we are wrapping.
+
+ @ivar _successful: An internal variable, used to track whether we have
+ received only successful results.
+
+ @since: 10.0
+ """
+ implements(itrial.IReporter)
+
+
+ def __init__(self, stream=sys.stdout, tbformat='default',
+ realtime=False, publisher=None):
+ """
+ Construct a L{SubunitReporter}.
+
+ @param stream: A file-like object representing the stream to print
+ output to. Defaults to stdout.
+ @param tbformat: The format for tracebacks. Ignored, since subunit
+ always uses Python's standard format.
+ @param realtime: Whether or not to print exceptions in the middle
+ of the test results. Ignored, since subunit always does this.
+ @param publisher: The log publisher which will be preserved for
+ reporting events. Ignored, as it's not relevant to subunit.
+ """
+ if TestProtocolClient is None:
+ raise Exception("Subunit not available")
+ self._subunit = TestProtocolClient(stream)
+ self._successful = True
+
+
+ def done(self):
+ """
+ Record that the entire test suite run is finished.
+
+ We do nothing, since a summary clause is irrelevant to the subunit
+ protocol.
+ """
+ pass
+
+
+ def shouldStop(self):
+ """
+ Whether or not the test runner should stop running tests.
+ """
+ return self._subunit.shouldStop
+ shouldStop = property(shouldStop)
+
+
+ def stop(self):
+ """
+ Signal that the test runner should stop running tests.
+ """
+ return self._subunit.stop()
+
+
+ def wasSuccessful(self):
+ """
+ Has the test run been successful so far?
+
+ @return: C{True} if we have received no reports of errors or failures,
+ C{False} otherwise.
+ """
+ # Subunit has a bug in its implementation of wasSuccessful, see
+ # https://bugs.edge.launchpad.net/subunit/+bug/491090, so we can't
+ # simply forward it on.
+ return self._successful
+
+
+ def startTest(self, test):
+ """
+ Record that C{test} has started.
+ """
+ return self._subunit.startTest(test)
+
+
+ def stopTest(self, test):
+ """
+ Record that C{test} has completed.
+ """
+ return self._subunit.stopTest(test)
+
+
+ def addSuccess(self, test):
+ """
+ Record that C{test} was successful.
+ """
+ return self._subunit.addSuccess(test)
+
+
+ def addSkip(self, test, reason):
+ """
+ Record that C{test} was skipped for C{reason}.
+
+ Some versions of subunit don't have support for addSkip. In those
+ cases, the skip is reported as a success.
+
+ @param test: A unittest-compatible C{TestCase}.
+ @param reason: The reason for it being skipped. The C{str()} of this
+ object will be included in the subunit output stream.
+ """
+ addSkip = getattr(self._subunit, 'addSkip', None)
+ if addSkip is None:
+ self.addSuccess(test)
+ else:
+ self._subunit.addSkip(test, reason)
+
+
+ def addError(self, test, err):
+ """
+ Record that C{test} failed with an unexpected error C{err}.
+
+ Also marks the run as being unsuccessful, causing
+ L{SubunitReporter.wasSuccessful} to return C{False}.
+ """
+ self._successful = False
+ return self._subunit.addError(
+ test, util.excInfoOrFailureToExcInfo(err))
+
+
+ def addFailure(self, test, err):
+ """
+ Record that C{test} failed an assertion with the error C{err}.
+
+ Also marks the run as being unsuccessful, causing
+ L{SubunitReporter.wasSuccessful} to return C{False}.
+ """
+ self._successful = False
+ return self._subunit.addFailure(
+ test, util.excInfoOrFailureToExcInfo(err))
+
+
+ def addExpectedFailure(self, test, failure, todo):
+ """
+ Record an expected failure from a test.
+
+ Some versions of subunit do not implement this. For those versions, we
+ record a success.
+ """
+ failure = util.excInfoOrFailureToExcInfo(failure)
+ addExpectedFailure = getattr(self._subunit, 'addExpectedFailure', None)
+ if addExpectedFailure is None:
+ self.addSuccess(test)
+ else:
+ addExpectedFailure(test, failure)
+
+
+ def addUnexpectedSuccess(self, test, todo):
+ """
+ Record an unexpected success.
+
+ Since subunit has no way of expressing this concept, we record a
+ success on the subunit stream.
+ """
+ # Not represented in pyunit/subunit.
+ self.addSuccess(test)
+
+
+
+class TreeReporter(Reporter):
+ """
+ Print out the tests in the form a tree.
+
+ Tests are indented according to which class and module they belong.
+ Results are printed in ANSI color.
+ """
+
+ currentLine = ''
+ indent = ' '
+ columns = 79
+
+ FAILURE = 'red'
+ ERROR = 'red'
+ TODO = 'blue'
+ SKIP = 'blue'
+ TODONE = 'red'
+ SUCCESS = 'green'
+
+ def __init__(self, stream=sys.stdout, *args, **kwargs):
+ super(TreeReporter, self).__init__(stream, *args, **kwargs)
+ self._lastTest = []
+ for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
+ if colorizer.supported(stream):
+ self._colorizer = colorizer(stream)
+ break
+
+ def getDescription(self, test):
+ """
+ Return the name of the method which 'test' represents. This is
+ what gets displayed in the leaves of the tree.
+
+ e.g. getDescription(TestCase('test_foo')) ==> test_foo
+ """
+ return test.id().split('.')[-1]
+
+ def addSuccess(self, test):
+ super(TreeReporter, self).addSuccess(test)
+ self.endLine('[OK]', self.SUCCESS)
+
+ def addError(self, *args):
+ super(TreeReporter, self).addError(*args)
+ self.endLine('[ERROR]', self.ERROR)
+
+ def addFailure(self, *args):
+ super(TreeReporter, self).addFailure(*args)
+ self.endLine('[FAIL]', self.FAILURE)
+
+ def addSkip(self, *args):
+ super(TreeReporter, self).addSkip(*args)
+ self.endLine('[SKIPPED]', self.SKIP)
+
+ def addExpectedFailure(self, *args):
+ super(TreeReporter, self).addExpectedFailure(*args)
+ self.endLine('[TODO]', self.TODO)
+
+ def addUnexpectedSuccess(self, *args):
+ super(TreeReporter, self).addUnexpectedSuccess(*args)
+ self.endLine('[SUCCESS!?!]', self.TODONE)
+
+ def _write(self, format, *args):
+ if args:
+ format = format % args
+ self.currentLine = format
+ super(TreeReporter, self)._write(self.currentLine)
+
+
+ def _getPreludeSegments(self, testID):
+ """
+ Return a list of all non-leaf segments to display in the tree.
+
+ Normally this is the module and class name.
+ """
+ segments = testID.split('.')[:-1]
+ if len(segments) == 0:
+ return segments
+ segments = [
+ seg for seg in '.'.join(segments[:-1]), segments[-1]
+ if len(seg) > 0]
+ return segments
+
+
+ def _testPrelude(self, testID):
+ """
+ Write the name of the test to the stream, indenting it appropriately.
+
+ If the test is the first test in a new 'branch' of the tree, also
+ write all of the parents in that branch.
+ """
+ segments = self._getPreludeSegments(testID)
+ indentLevel = 0
+ for seg in segments:
+ if indentLevel < len(self._lastTest):
+ if seg != self._lastTest[indentLevel]:
+ self._write('%s%s\n' % (self.indent * indentLevel, seg))
+ else:
+ self._write('%s%s\n' % (self.indent * indentLevel, seg))
+ indentLevel += 1
+ self._lastTest = segments
+
+
+ def cleanupErrors(self, errs):
+ self._colorizer.write(' cleanup errors', self.ERROR)
+ self.endLine('[ERROR]', self.ERROR)
+ super(TreeReporter, self).cleanupErrors(errs)
+
+ def upDownError(self, method, error, warn, printStatus):
+ self._colorizer.write(" %s" % method, self.ERROR)
+ if printStatus:
+ self.endLine('[ERROR]', self.ERROR)
+ super(TreeReporter, self).upDownError(method, error, warn, printStatus)
+
+ def startTest(self, test):
+ """
+ Called when C{test} starts. Writes the tests name to the stream using
+ a tree format.
+ """
+ self._testPrelude(test.id())
+ self._write('%s%s ... ' % (self.indent * (len(self._lastTest)),
+ self.getDescription(test)))
+ super(TreeReporter, self).startTest(test)
+
+
+ def endLine(self, message, color):
+ """
+ Print 'message' in the given color.
+
+ @param message: A string message, usually '[OK]' or something similar.
+ @param color: A string color, 'red', 'green' and so forth.
+ """
+ spaces = ' ' * (self.columns - len(self.currentLine) - len(message))
+ super(TreeReporter, self)._write(spaces)
+ self._colorizer.write(message, color)
+ super(TreeReporter, self)._write("\n")
+
+
+ def _printSummary(self):
+ """
+ Print a line summarising the test results to the stream, and color the
+ status result.
+ """
+ summary = self._getSummary()
+ if self.wasSuccessful():
+ status = "PASSED"
+ color = self.SUCCESS
+ else:
+ status = "FAILED"
+ color = self.FAILURE
+ self._colorizer.write(status, color)
+ self._write("%s\n", summary)
diff --git a/vendor/Twisted-10.0.0/twisted/trial/runner.py b/vendor/Twisted-10.0.0/twisted/trial/runner.py
new file mode 100644
index 0000000000..7210f8e89e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/runner.py
@@ -0,0 +1,905 @@
+# -*- test-case-name: twisted.trial.test.test_runner -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A miscellany of code used to run Trial tests.
+
+Maintainer: Jonathan Lange
+"""
+
+
+import pdb
+import os, types, warnings, sys, inspect, imp
+import random, doctest, time
+
+from twisted.python import reflect, log, failure, modules, filepath
+from twisted.python.util import dsu
+from twisted.python.compat import set
+from twisted.python.lockfile import FilesystemLock
+
+from twisted.internet import defer
+from twisted.trial import util, unittest
+from twisted.trial.itrial import ITestCase
+from twisted.trial.reporter import UncleanWarningsReporterWrapper
+
+# These are imported so that they remain in the public API for t.trial.runner
+from twisted.trial.unittest import suiteVisit, TestSuite
+
+from zope.interface import implements
+
+pyunit = __import__('unittest')
+
+
+
+class _WorkingDirectoryBusy(Exception):
+ """
+ A working directory was specified to the runner, but another test run is
+ currently using that directory.
+ """
+
+
+
+class _NoTrialMarker(Exception):
+ """
+ No trial marker file could be found.
+
+ Raised when trial attempts to remove a trial temporary working directory
+ that does not contain a marker file.
+ """
+
+
+
+def isPackage(module):
+ """Given an object return True if the object looks like a package"""
+ if not isinstance(module, types.ModuleType):
+ return False
+ basename = os.path.splitext(os.path.basename(module.__file__))[0]
+ return basename == '__init__'
+
+
+def isPackageDirectory(dirname):
+ """Is the directory at path 'dirname' a Python package directory?
+ Returns the name of the __init__ file (it may have a weird extension)
+ if dirname is a package directory. Otherwise, returns False"""
+ for ext in zip(*imp.get_suffixes())[0]:
+ initFile = '__init__' + ext
+ if os.path.exists(os.path.join(dirname, initFile)):
+ return initFile
+ return False
+
+
+def samefile(filename1, filename2):
+ """
+ A hacky implementation of C{os.path.samefile}. Used by L{filenameToModule}
+ when the platform doesn't provide C{os.path.samefile}. Do not use this.
+ """
+ return os.path.abspath(filename1) == os.path.abspath(filename2)
+
+
+def filenameToModule(fn):
+ """
+ Given a filename, do whatever possible to return a module object matching
+ that file.
+
+ If the file in question is a module in Python path, properly import and
+ return that module. Otherwise, load the source manually.
+
+ @param fn: A filename.
+ @return: A module object.
+ @raise ValueError: If C{fn} does not exist.
+ """
+ if not os.path.exists(fn):
+ raise ValueError("%r doesn't exist" % (fn,))
+ try:
+ ret = reflect.namedAny(reflect.filenameToModuleName(fn))
+ except (ValueError, AttributeError):
+ # Couldn't find module. The file 'fn' is not in PYTHONPATH
+ return _importFromFile(fn)
+ # ensure that the loaded module matches the file
+ retFile = os.path.splitext(ret.__file__)[0] + '.py'
+ # not all platforms (e.g. win32) have os.path.samefile
+ same = getattr(os.path, 'samefile', samefile)
+ if os.path.isfile(fn) and not same(fn, retFile):
+ del sys.modules[ret.__name__]
+ ret = _importFromFile(fn)
+ return ret
+
+
+def _importFromFile(fn, moduleName=None):
+ fn = _resolveDirectory(fn)
+ if not moduleName:
+ moduleName = os.path.splitext(os.path.split(fn)[-1])[0]
+ if moduleName in sys.modules:
+ return sys.modules[moduleName]
+ fd = open(fn, 'r')
+ try:
+ module = imp.load_source(moduleName, fn, fd)
+ finally:
+ fd.close()
+ return module
+
+
+def _resolveDirectory(fn):
+ if os.path.isdir(fn):
+ initFile = isPackageDirectory(fn)
+ if initFile:
+ fn = os.path.join(fn, initFile)
+ else:
+ raise ValueError('%r is not a package directory' % (fn,))
+ return fn
+
+
+
+class DestructiveTestSuite(TestSuite):
+ """
+ A test suite which remove the tests once run, to minimize memory usage.
+ """
+
+ def run(self, result):
+ """
+ Almost the same as L{TestSuite.run}, but with C{self._tests} being
+ empty at the end.
+ """
+ while self._tests:
+ if result.shouldStop:
+ break
+ test = self._tests.pop(0)
+ test(result)
+ return result
+
+
+
+# When an error occurs outside of any test, the user will see this string
+# in place of a test's name.
+NOT_IN_TEST = "<not in test>"
+
+
+
+class LoggedSuite(TestSuite):
+ """
+ Any errors logged in this suite will be reported to the L{TestResult}
+ object.
+ """
+
+ def run(self, result):
+ """
+ Run the suite, storing all errors in C{result}. If an error is logged
+ while no tests are running, then it will be added as an error to
+ C{result}.
+
+ @param result: A L{TestResult} object.
+ """
+ observer = unittest._logObserver
+ observer._add()
+ super(LoggedSuite, self).run(result)
+ observer._remove()
+ for error in observer.getErrors():
+ result.addError(TestHolder(NOT_IN_TEST), error)
+ observer.flushErrors()
+
+
+
+class DocTestSuite(TestSuite):
+ """
+ DEPRECATED in Twisted 8.0.
+
+ Behaves like doctest.DocTestSuite, but decorates individual TestCases so
+ they support visit and so that id() behaviour is meaningful and consistent
+ between Python versions.
+ """
+
+ def __init__(self, testModule):
+ warnings.warn("DocTestSuite is deprecated in Twisted 8.0.",
+ category=DeprecationWarning, stacklevel=2)
+ TestSuite.__init__(self)
+ suite = doctest.DocTestSuite(testModule)
+ for test in suite._tests: #yay encapsulation
+ self.addTest(ITestCase(test))
+
+
+
+class PyUnitTestCase(object):
+ """
+ DEPRECATED in Twisted 8.0.
+
+ This class decorates the pyunit.TestCase class, mainly to work around the
+ differences between unittest in Python 2.3, 2.4, and 2.5. These
+ differences are::
+
+ - The way doctest unittests describe themselves
+ - Where the implementation of TestCase.run is (used to be in __call__)
+ - Where the test method name is kept (mangled-private or non-mangled
+ private variable)
+
+ It also implements visit, which we like.
+ """
+
+ def __init__(self, test):
+ warnings.warn("Deprecated in Twisted 8.0.",
+ category=DeprecationWarning)
+ self._test = test
+ test.id = self.id
+
+ def id(self):
+ cls = self._test.__class__
+ tmn = getattr(self._test, '_TestCase__testMethodName', None)
+ if tmn is None:
+ # python2.5's 'unittest' module is more sensible; but different.
+ tmn = self._test._testMethodName
+ return (cls.__module__ + '.' + cls.__name__ + '.' +
+ tmn)
+
+ def __repr__(self):
+ return 'PyUnitTestCase<%r>'%(self.id(),)
+
+ def __call__(self, results):
+ return self._test(results)
+
+
+ def visit(self, visitor):
+ """
+ Call the given visitor with the original, standard library, test case
+ that C{self} wraps. See L{unittest.TestCase.visit}.
+
+ Deprecated in Twisted 8.0.
+ """
+ warnings.warn("Test visitors deprecated in Twisted 8.0",
+ category=DeprecationWarning)
+ visitor(self._test)
+
+
+ def __getattr__(self, name):
+ return getattr(self._test, name)
+
+
+
+class DocTestCase(PyUnitTestCase):
+ """
+ DEPRECATED in Twisted 8.0.
+ """
+
+ def id(self):
+ """
+ In Python 2.4, doctests have correct id() behaviour. In Python 2.3,
+ id() returns 'runit'.
+
+ Here we override id() so that at least it will always contain the
+ fully qualified Python name of the doctest.
+ """
+ return self._test.shortDescription()
+
+
+class TrialSuite(TestSuite):
+ """
+ Suite to wrap around every single test in a C{trial} run. Used internally
+ by Trial to set up things necessary for Trial tests to work, regardless of
+ what context they are run in.
+ """
+
+ def __init__(self, tests=()):
+ suite = LoggedSuite(tests)
+ super(TrialSuite, self).__init__([suite])
+
+
+ def _bail(self):
+ from twisted.internet import reactor
+ d = defer.Deferred()
+ reactor.addSystemEventTrigger('after', 'shutdown',
+ lambda: d.callback(None))
+ reactor.fireSystemEvent('shutdown') # radix's suggestion
+ # As long as TestCase does crap stuff with the reactor we need to
+ # manually shutdown the reactor here, and that requires util.wait
+ # :(
+ # so that the shutdown event completes
+ unittest.TestCase('mktemp')._wait(d)
+
+ def run(self, result):
+ try:
+ TestSuite.run(self, result)
+ finally:
+ self._bail()
+
+
+def name(thing):
+ """
+ @param thing: an object from modules (instance of PythonModule,
+ PythonAttribute), a TestCase subclass, or an instance of a TestCase.
+ """
+ if isTestCase(thing):
+ # TestCase subclass
+ theName = reflect.qual(thing)
+ else:
+ # thing from trial, or thing from modules.
+ # this monstrosity exists so that modules' objects do not have to
+ # implement id(). -jml
+ try:
+ theName = thing.id()
+ except AttributeError:
+ theName = thing.name
+ return theName
+
+
+def isTestCase(obj):
+ """
+ Returns C{True} if C{obj} is a class that contains test cases, C{False}
+ otherwise. Used to find all the tests in a module.
+ """
+ try:
+ return issubclass(obj, pyunit.TestCase)
+ except TypeError:
+ return False
+
+
+
+class TestHolder(object):
+ """
+ Placeholder for a L{TestCase} inside a reporter. As far as a L{TestResult}
+ is concerned, this looks exactly like a unit test.
+ """
+
+ implements(ITestCase)
+
+ def __init__(self, description):
+ """
+ @param description: A string to be displayed L{TestResult}.
+ """
+ self.description = description
+
+
+ def id(self):
+ return self.description
+
+
+ def shortDescription(self):
+ return self.description
+
+
+
+class ErrorHolder(TestHolder):
+ """
+ Used to insert arbitrary errors into a test suite run. Provides enough
+ methods to look like a C{TestCase}, however, when it is run, it simply adds
+ an error to the C{TestResult}. The most common use-case is for when a
+ module fails to import.
+ """
+
+ def __init__(self, description, error):
+ """
+ @param description: A string used by C{TestResult}s to identify this
+ error. Generally, this is the name of a module that failed to import.
+
+ @param error: The error to be added to the result. Can be an exc_info
+ tuple or a L{twisted.python.failure.Failure}.
+ """
+ super(ErrorHolder, self).__init__(description)
+ self.error = error
+
+
+ def __repr__(self):
+ return "<ErrorHolder description=%r error=%r>" % (self.description,
+ self.error)
+
+
+ def run(self, result):
+ result.addError(self, self.error)
+
+
+ def __call__(self, result):
+ return self.run(result)
+
+
+ def countTestCases(self):
+ return 0
+
+
+ def visit(self, visitor):
+ """
+ See L{unittest.TestCase.visit}.
+ """
+ visitor(self)
+
+
+
+class TestLoader(object):
+ """
+ I find tests inside function, modules, files -- whatever -- then return
+ them wrapped inside a Test (either a L{TestSuite} or a L{TestCase}).
+
+ @ivar methodPrefix: A string prefix. C{TestLoader} will assume that all the
+ methods in a class that begin with C{methodPrefix} are test cases.
+
+ @ivar modulePrefix: A string prefix. Every module in a package that begins
+ with C{modulePrefix} is considered a module full of tests.
+
+ @ivar forceGarbageCollection: A flag applied to each C{TestCase} loaded.
+ See L{unittest.TestCase} for more information.
+
+ @ivar sorter: A key function used to sort C{TestCase}s, test classes,
+ modules and packages.
+
+ @ivar suiteFactory: A callable which is passed a list of tests (which
+ themselves may be suites of tests). Must return a test suite.
+ """
+
+ methodPrefix = 'test'
+ modulePrefix = 'test_'
+
+ def __init__(self):
+ self.suiteFactory = TestSuite
+ self.sorter = name
+ self._importErrors = []
+
+ def sort(self, xs):
+ """
+ Sort the given things using L{sorter}.
+
+ @param xs: A list of test cases, class or modules.
+ """
+ return dsu(xs, self.sorter)
+
+ def findTestClasses(self, module):
+ """Given a module, return all Trial test classes"""
+ classes = []
+ for name, val in inspect.getmembers(module):
+ if isTestCase(val):
+ classes.append(val)
+ return self.sort(classes)
+
+ def findByName(self, name):
+ """
+ Return a Python object given a string describing it.
+
+ @param name: a string which may be either a filename or a
+ fully-qualified Python name.
+
+ @return: If C{name} is a filename, return the module. If C{name} is a
+ fully-qualified Python name, return the object it refers to.
+ """
+ if os.path.exists(name):
+ return filenameToModule(name)
+ return reflect.namedAny(name)
+
+ def loadModule(self, module):
+ """
+ Return a test suite with all the tests from a module.
+
+ Included are TestCase subclasses and doctests listed in the module's
+ __doctests__ module. If that's not good for you, put a function named
+ either C{testSuite} or C{test_suite} in your module that returns a
+ TestSuite, and I'll use the results of that instead.
+
+ If C{testSuite} and C{test_suite} are both present, then I'll use
+ C{testSuite}.
+ """
+ ## XXX - should I add an optional parameter to disable the check for
+ ## a custom suite.
+ ## OR, should I add another method
+ if not isinstance(module, types.ModuleType):
+ raise TypeError("%r is not a module" % (module,))
+ if hasattr(module, 'testSuite'):
+ return module.testSuite()
+ elif hasattr(module, 'test_suite'):
+ return module.test_suite()
+ suite = self.suiteFactory()
+ for testClass in self.findTestClasses(module):
+ suite.addTest(self.loadClass(testClass))
+ if not hasattr(module, '__doctests__'):
+ return suite
+ docSuite = self.suiteFactory()
+ for doctest in module.__doctests__:
+ docSuite.addTest(self.loadDoctests(doctest))
+ return self.suiteFactory([suite, docSuite])
+ loadTestsFromModule = loadModule
+
+ def loadClass(self, klass):
+ """
+ Given a class which contains test cases, return a sorted list of
+ C{TestCase} instances.
+ """
+ if not (isinstance(klass, type) or isinstance(klass, types.ClassType)):
+ raise TypeError("%r is not a class" % (klass,))
+ if not isTestCase(klass):
+ raise ValueError("%r is not a test case" % (klass,))
+ names = self.getTestCaseNames(klass)
+ tests = self.sort([self._makeCase(klass, self.methodPrefix+name)
+ for name in names])
+ return self.suiteFactory(tests)
+ loadTestsFromTestCase = loadClass
+
+ def getTestCaseNames(self, klass):
+ """
+ Given a class that contains C{TestCase}s, return a list of names of
+ methods that probably contain tests.
+ """
+ return reflect.prefixedMethodNames(klass, self.methodPrefix)
+
+ def loadMethod(self, method):
+ """
+ Given a method of a C{TestCase} that represents a test, return a
+ C{TestCase} instance for that test.
+ """
+ if not isinstance(method, types.MethodType):
+ raise TypeError("%r not a method" % (method,))
+ return self._makeCase(method.im_class, method.__name__)
+
+ def _makeCase(self, klass, methodName):
+ return klass(methodName)
+
+ def loadPackage(self, package, recurse=False):
+ """
+ Load tests from a module object representing a package, and return a
+ TestSuite containing those tests.
+
+ Tests are only loaded from modules whose name begins with 'test_'
+ (or whatever C{modulePrefix} is set to).
+
+ @param package: a types.ModuleType object (or reasonable facsimilie
+ obtained by importing) which may contain tests.
+
+ @param recurse: A boolean. If True, inspect modules within packages
+ within the given package (and so on), otherwise, only inspect modules
+ in the package itself.
+
+ @raise: TypeError if 'package' is not a package.
+
+ @return: a TestSuite created with my suiteFactory, containing all the
+ tests.
+ """
+ if not isPackage(package):
+ raise TypeError("%r is not a package" % (package,))
+ pkgobj = modules.getModule(package.__name__)
+ if recurse:
+ discovery = pkgobj.walkModules()
+ else:
+ discovery = pkgobj.iterModules()
+ discovered = []
+ for disco in discovery:
+ if disco.name.split(".")[-1].startswith(self.modulePrefix):
+ discovered.append(disco)
+ suite = self.suiteFactory()
+ for modinfo in self.sort(discovered):
+ try:
+ module = modinfo.load()
+ except:
+ thingToAdd = ErrorHolder(modinfo.name, failure.Failure())
+ else:
+ thingToAdd = self.loadModule(module)
+ suite.addTest(thingToAdd)
+ return suite
+
+ def loadDoctests(self, module):
+ """
+ Return a suite of tests for all the doctests defined in C{module}.
+
+ @param module: A module object or a module name.
+ """
+ if isinstance(module, str):
+ try:
+ module = reflect.namedAny(module)
+ except:
+ return ErrorHolder(module, failure.Failure())
+ if not inspect.ismodule(module):
+ warnings.warn("trial only supports doctesting modules")
+ return
+ extraArgs = {}
+ if sys.version_info > (2, 4):
+ # Work around Python issue2604: DocTestCase.tearDown clobbers globs
+ def saveGlobals(test):
+ """
+ Save C{test.globs} and replace it with a copy so that if
+ necessary, the original will be available for the next test
+ run.
+ """
+ test._savedGlobals = getattr(test, '_savedGlobals', test.globs)
+ test.globs = test._savedGlobals.copy()
+ extraArgs['setUp'] = saveGlobals
+ return doctest.DocTestSuite(module, **extraArgs)
+
+ def loadAnything(self, thing, recurse=False):
+ """
+ Given a Python object, return whatever tests that are in it. Whatever
+ 'in' might mean.
+
+ @param thing: A Python object. A module, method, class or package.
+ @param recurse: Whether or not to look in subpackages of packages.
+ Defaults to False.
+
+ @return: A C{TestCase} or C{TestSuite}.
+ """
+ if isinstance(thing, types.ModuleType):
+ if isPackage(thing):
+ return self.loadPackage(thing, recurse)
+ return self.loadModule(thing)
+ elif isinstance(thing, types.ClassType):
+ return self.loadClass(thing)
+ elif isinstance(thing, type):
+ return self.loadClass(thing)
+ elif isinstance(thing, types.MethodType):
+ return self.loadMethod(thing)
+ raise TypeError("No loader for %r. Unrecognized type" % (thing,))
+
+ def loadByName(self, name, recurse=False):
+ """
+ Given a string representing a Python object, return whatever tests
+ are in that object.
+
+ If C{name} is somehow inaccessible (e.g. the module can't be imported,
+ there is no Python object with that name etc) then return an
+ L{ErrorHolder}.
+
+ @param name: The fully-qualified name of a Python object.
+ """
+ try:
+ thing = self.findByName(name)
+ except:
+ return ErrorHolder(name, failure.Failure())
+ return self.loadAnything(thing, recurse)
+ loadTestsFromName = loadByName
+
+ def loadByNames(self, names, recurse=False):
+ """
+ Construct a TestSuite containing all the tests found in 'names', where
+ names is a list of fully qualified python names and/or filenames. The
+ suite returned will have no duplicate tests, even if the same object
+ is named twice.
+ """
+ things = []
+ errors = []
+ for name in names:
+ try:
+ things.append(self.findByName(name))
+ except:
+ errors.append(ErrorHolder(name, failure.Failure()))
+ suites = [self.loadAnything(thing, recurse)
+ for thing in set(things)]
+ suites.extend(errors)
+ return self.suiteFactory(suites)
+
+
+
+class DryRunVisitor(object):
+ """
+ A visitor that makes a reporter think that every test visited has run
+ successfully.
+ """
+
+ def __init__(self, reporter):
+ """
+ @param reporter: A C{TestResult} object.
+ """
+ self.reporter = reporter
+
+
+ def markSuccessful(self, testCase):
+ """
+ Convince the reporter that this test has been run successfully.
+ """
+ self.reporter.startTest(testCase)
+ self.reporter.addSuccess(testCase)
+ self.reporter.stopTest(testCase)
+
+
+
+class TrialRunner(object):
+ """
+ A specialised runner that the trial front end uses.
+ """
+
+ DEBUG = 'debug'
+ DRY_RUN = 'dry-run'
+
+ def _getDebugger(self):
+ dbg = pdb.Pdb()
+ try:
+ import readline
+ except ImportError:
+ print "readline module not available"
+ hasattr(sys, 'exc_clear') and sys.exc_clear()
+ for path in ('.pdbrc', 'pdbrc'):
+ if os.path.exists(path):
+ try:
+ rcFile = file(path, 'r')
+ except IOError:
+ hasattr(sys, 'exc_clear') and sys.exc_clear()
+ else:
+ dbg.rcLines.extend(rcFile.readlines())
+ return dbg
+
+
+ def _removeSafely(self, path):
+ """
+ Safely remove a path, recursively.
+
+ If C{path} does not contain a node named C{"_trial_marker"}, a
+ L{_NoTrialMarker} exception is raised and the path is not removed.
+
+ @type path: L{twisted.python.filepath.FilePath}
+ @param path: The absolute path to a test directory
+ """
+ if not path.child('_trial_marker').exists():
+ raise _NoTrialMarker(
+ '%r is not a trial temporary path, refusing to remove it'
+ % (path,))
+
+ try:
+ path.remove()
+ except OSError, e:
+ print ("could not remove %r, caught OSError [Errno %s]: %s"
+ % (path, e.errno, e.strerror))
+ try:
+ newPath = filepath.FilePath('_trial_temp_old%s'
+ % random.randint(0, 99999999))
+ path.moveTo(newPath)
+ except OSError, e:
+ print ("could not rename path, caught OSError [Errno %s]: %s"
+ % (e.errno, e.strerror))
+ raise
+
+
+ def _setUpTestdir(self):
+ self._tearDownLogFile()
+ currentDir = os.getcwd()
+ base = filepath.FilePath(self.workingDirectory)
+ counter = 0
+ while True:
+ if counter:
+ testdir = base.sibling('%s-%d' % (base.basename(), counter))
+ else:
+ testdir = base
+
+ self._testDirLock = FilesystemLock(testdir.path + '.lock')
+ if self._testDirLock.lock():
+ # It is not in use
+ if testdir.exists():
+ # It exists though - delete it
+ self._removeSafely(testdir)
+ break
+ else:
+ # It is in use
+ if self.workingDirectory == '_trial_temp':
+ counter += 1
+ else:
+ raise _WorkingDirectoryBusy()
+
+ testdir.makedirs()
+ os.chdir(testdir.path)
+ file('_trial_marker', 'w').close()
+ return currentDir
+
+
+ def _tearDownTestdir(self, oldDir):
+ os.chdir(oldDir)
+ self._testDirLock.unlock()
+
+
+ _log = log
+ def _makeResult(self):
+ reporter = self.reporterFactory(self.stream, self.tbformat,
+ self.rterrors, self._log)
+ if self.uncleanWarnings:
+ reporter = UncleanWarningsReporterWrapper(reporter)
+ return reporter
+
+ def __init__(self, reporterFactory,
+ mode=None,
+ logfile='test.log',
+ stream=sys.stdout,
+ profile=False,
+ tracebackFormat='default',
+ realTimeErrors=False,
+ uncleanWarnings=False,
+ workingDirectory=None,
+ forceGarbageCollection=False):
+ self.reporterFactory = reporterFactory
+ self.logfile = logfile
+ self.mode = mode
+ self.stream = stream
+ self.tbformat = tracebackFormat
+ self.rterrors = realTimeErrors
+ self.uncleanWarnings = uncleanWarnings
+ self._result = None
+ self.workingDirectory = workingDirectory or '_trial_temp'
+ self._logFileObserver = None
+ self._logFileObject = None
+ self._forceGarbageCollection = forceGarbageCollection
+ if profile:
+ self.run = util.profiled(self.run, 'profile.data')
+
+ def _tearDownLogFile(self):
+ if self._logFileObserver is not None:
+ log.removeObserver(self._logFileObserver.emit)
+ self._logFileObserver = None
+ if self._logFileObject is not None:
+ self._logFileObject.close()
+ self._logFileObject = None
+
+ def _setUpLogFile(self):
+ self._tearDownLogFile()
+ if self.logfile == '-':
+ logFile = sys.stdout
+ else:
+ logFile = file(self.logfile, 'a')
+ self._logFileObject = logFile
+ self._logFileObserver = log.FileLogObserver(logFile)
+ log.startLoggingWithObserver(self._logFileObserver.emit, 0)
+
+
+ def run(self, test):
+ """
+ Run the test or suite and return a result object.
+ """
+ test = unittest.decorate(test, ITestCase)
+ if self._forceGarbageCollection:
+ test = unittest.decorate(
+ test, unittest._ForceGarbageCollectionDecorator)
+ return self._runWithoutDecoration(test)
+
+
+ def _runWithoutDecoration(self, test):
+ """
+ Private helper that runs the given test but doesn't decorate it.
+ """
+ result = self._makeResult()
+ # decorate the suite with reactor cleanup and log starting
+ # This should move out of the runner and be presumed to be
+ # present
+ suite = TrialSuite([test])
+ startTime = time.time()
+ if self.mode == self.DRY_RUN:
+ for single in unittest._iterateTests(suite):
+ result.startTest(single)
+ result.addSuccess(single)
+ result.stopTest(single)
+ else:
+ if self.mode == self.DEBUG:
+ # open question - should this be self.debug() instead.
+ debugger = self._getDebugger()
+ run = lambda: debugger.runcall(suite.run, result)
+ else:
+ run = lambda: suite.run(result)
+
+ oldDir = self._setUpTestdir()
+ try:
+ self._setUpLogFile()
+ run()
+ finally:
+ self._tearDownLogFile()
+ self._tearDownTestdir(oldDir)
+
+ endTime = time.time()
+ done = getattr(result, 'done', None)
+ if done is None:
+ warnings.warn(
+ "%s should implement done() but doesn't. Falling back to "
+ "printErrors() and friends." % reflect.qual(result.__class__),
+ category=DeprecationWarning, stacklevel=3)
+ result.printErrors()
+ result.writeln(result.separator)
+ result.writeln('Ran %d tests in %.3fs', result.testsRun,
+ endTime - startTime)
+ result.write('\n')
+ result.printSummary()
+ else:
+ result.done()
+ return result
+
+
+ def runUntilFailure(self, test):
+ """
+ Repeatedly run C{test} until it fails.
+ """
+ count = 0
+ while True:
+ count += 1
+ self.stream.write("Test Pass %d\n" % (count,))
+ if count == 1:
+ result = self.run(test)
+ else:
+ result = self._runWithoutDecoration(test)
+ if result.testsRun == 0:
+ break
+ if not result.wasSuccessful():
+ break
+ return result
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/__init__.py b/vendor/Twisted-10.0.0/twisted/trial/test/__init__.py
new file mode 100644
index 0000000000..e239537288
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/__init__.py
@@ -0,0 +1 @@
+"""unittesting framework tests"""
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/detests.py b/vendor/Twisted-10.0.0/twisted/trial/test/detests.py
new file mode 100644
index 0000000000..b131bda2f9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/detests.py
@@ -0,0 +1,195 @@
+from __future__ import generators
+from twisted.trial import unittest
+from twisted.internet import defer, threads, reactor
+
+
+class DeferredSetUpOK(unittest.TestCase):
+ def setUp(self):
+ d = defer.succeed('value')
+ d.addCallback(self._cb_setUpCalled)
+ return d
+
+ def _cb_setUpCalled(self, ignored):
+ self._setUpCalled = True
+
+ def test_ok(self):
+ self.failUnless(self._setUpCalled)
+
+
+class DeferredSetUpFail(unittest.TestCase):
+ testCalled = False
+
+ def setUp(self):
+ return defer.fail(unittest.FailTest('i fail'))
+
+ def test_ok(self):
+ DeferredSetUpFail.testCalled = True
+ self.fail("I should not get called")
+
+
+class DeferredSetUpCallbackFail(unittest.TestCase):
+ testCalled = False
+
+ def setUp(self):
+ d = defer.succeed('value')
+ d.addCallback(self._cb_setUpCalled)
+ return d
+
+ def _cb_setUpCalled(self, ignored):
+ self.fail('deliberate failure')
+
+ def test_ok(self):
+ DeferredSetUpCallbackFail.testCalled = True
+
+
+class DeferredSetUpError(unittest.TestCase):
+ testCalled = False
+
+ def setUp(self):
+ return defer.fail(RuntimeError('deliberate error'))
+
+ def test_ok(self):
+ DeferredSetUpError.testCalled = True
+
+
+class DeferredSetUpNeverFire(unittest.TestCase):
+ testCalled = False
+
+ def setUp(self):
+ return defer.Deferred()
+
+ def test_ok(self):
+ DeferredSetUpNeverFire.testCalled = True
+
+
+class DeferredSetUpSkip(unittest.TestCase):
+ testCalled = False
+
+ def setUp(self):
+ d = defer.succeed('value')
+ d.addCallback(self._cb1)
+ return d
+
+ def _cb1(self, ignored):
+ raise unittest.SkipTest("skip me")
+
+ def test_ok(self):
+ DeferredSetUpSkip.testCalled = True
+
+
+class DeferredTests(unittest.TestCase):
+ touched = False
+
+ def _cb_fail(self, reason):
+ self.fail(reason)
+
+ def _cb_error(self, reason):
+ raise RuntimeError(reason)
+
+ def _cb_skip(self, reason):
+ raise unittest.SkipTest(reason)
+
+ def _touchClass(self, ignored):
+ self.__class__.touched = True
+
+ def setUp(self):
+ self.__class__.touched = False
+
+ def test_pass(self):
+ return defer.succeed('success')
+
+ def test_passGenerated(self):
+ self._touchClass(None)
+ yield None
+ test_passGenerated = defer.deferredGenerator(test_passGenerated)
+
+ def test_fail(self):
+ return defer.fail(self.failureException('I fail'))
+
+ def test_failureInCallback(self):
+ d = defer.succeed('fail')
+ d.addCallback(self._cb_fail)
+ return d
+
+ def test_errorInCallback(self):
+ d = defer.succeed('error')
+ d.addCallback(self._cb_error)
+ return d
+
+ def test_skip(self):
+ d = defer.succeed('skip')
+ d.addCallback(self._cb_skip)
+ d.addCallback(self._touchClass)
+ return d
+
+ def test_thread(self):
+ return threads.deferToThread(lambda : None)
+
+ def test_expectedFailure(self):
+ d = defer.succeed('todo')
+ d.addCallback(self._cb_error)
+ return d
+ test_expectedFailure.todo = "Expected failure"
+
+
+class TimeoutTests(unittest.TestCase):
+ timedOut = None
+
+ def test_pass(self):
+ d = defer.Deferred()
+ reactor.callLater(0, d.callback, 'hoorj!')
+ return d
+ test_pass.timeout = 2
+
+ def test_passDefault(self):
+ # test default timeout
+ d = defer.Deferred()
+ reactor.callLater(0, d.callback, 'hoorj!')
+ return d
+
+ def test_timeout(self):
+ return defer.Deferred()
+ test_timeout.timeout = 0.1
+
+ def test_timeoutZero(self):
+ return defer.Deferred()
+ test_timeoutZero.timeout = 0
+
+ def test_expectedFailure(self):
+ return defer.Deferred()
+ test_expectedFailure.timeout = 0.1
+ test_expectedFailure.todo = "i will get it right, eventually"
+
+ def test_skip(self):
+ return defer.Deferred()
+ test_skip.timeout = 0.1
+ test_skip.skip = "i will get it right, eventually"
+
+ def test_errorPropagation(self):
+ def timedOut(err):
+ self.__class__.timedOut = err
+ return err
+ d = defer.Deferred()
+ d.addErrback(timedOut)
+ return d
+ test_errorPropagation.timeout = 0.1
+
+ def test_calledButNeverCallback(self):
+ d = defer.Deferred()
+ def neverFire(r):
+ return defer.Deferred()
+ d.addCallback(neverFire)
+ d.callback(1)
+ return d
+ test_calledButNeverCallback.timeout = 0.1
+
+
+class TestClassTimeoutAttribute(unittest.TestCase):
+ timeout = 0.2
+
+ def setUp(self):
+ self.d = defer.Deferred()
+
+ def testMethod(self):
+ self.methodCalled = True
+ return self.d
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/erroneous.py b/vendor/Twisted-10.0.0/twisted/trial/test/erroneous.py
new file mode 100644
index 0000000000..26d0bbbf3f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/erroneous.py
@@ -0,0 +1,130 @@
+# -*- test-case-name: twisted.trial.test.test_tests -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.trial import unittest, util
+from twisted.internet import reactor, protocol, defer
+
+
+class FoolishError(Exception):
+ pass
+
+
+class TestFailureInSetUp(unittest.TestCase):
+ def setUp(self):
+ raise FoolishError, "I am a broken setUp method"
+
+ def test_noop(self):
+ pass
+
+
+class TestFailureInTearDown(unittest.TestCase):
+ def tearDown(self):
+ raise FoolishError, "I am a broken tearDown method"
+
+ def test_noop(self):
+ pass
+
+
+class TestRegularFail(unittest.TestCase):
+ def test_fail(self):
+ self.fail("I fail")
+
+ def test_subfail(self):
+ self.subroutine()
+
+ def subroutine(self):
+ self.fail("I fail inside")
+
+class TestFailureInDeferredChain(unittest.TestCase):
+ def test_fail(self):
+ d = defer.Deferred()
+ d.addCallback(self._later)
+ reactor.callLater(0, d.callback, None)
+ return d
+ def _later(self, res):
+ self.fail("I fail later")
+
+
+
+class ErrorTest(unittest.TestCase):
+ """
+ A test case which has a L{test_foo} which will raise an error.
+
+ @ivar ran: boolean indicating whether L{test_foo} has been run.
+ """
+ ran = False
+
+ def test_foo(self):
+ """
+ Set C{self.ran} to True and raise a C{ZeroDivisionError}
+ """
+ self.ran = True
+ 1/0
+
+
+
+class TestSkipTestCase(unittest.TestCase):
+ pass
+
+TestSkipTestCase.skip = "skipping this test"
+
+
+class DelayedCall(unittest.TestCase):
+ hiddenExceptionMsg = "something blew up"
+
+ def go(self):
+ raise RuntimeError(self.hiddenExceptionMsg)
+
+ def testHiddenException(self):
+ """
+ What happens if an error is raised in a DelayedCall and an error is
+ also raised in the test?
+
+ L{test_reporter.TestErrorReporting.testHiddenException} checks that
+ both errors get reported.
+
+ Note that this behaviour is deprecated. A B{real} test would return a
+ Deferred that got triggered by the callLater. This would guarantee the
+ delayed call error gets reported.
+ """
+ reactor.callLater(0, self.go)
+ reactor.iterate(0.01)
+ self.fail("Deliberate failure to mask the hidden exception")
+ testHiddenException.suppress = [util.suppress(
+ message=r'reactor\.iterate cannot be used.*',
+ category=DeprecationWarning)]
+
+
+class ReactorCleanupTests(unittest.TestCase):
+ def test_leftoverPendingCalls(self):
+ def _():
+ print 'foo!'
+ reactor.callLater(10000.0, _)
+
+class SocketOpenTest(unittest.TestCase):
+ def test_socketsLeftOpen(self):
+ f = protocol.Factory()
+ f.protocol = protocol.Protocol
+ reactor.listenTCP(0, f)
+
+class TimingOutDeferred(unittest.TestCase):
+ def test_alpha(self):
+ pass
+
+ def test_deferredThatNeverFires(self):
+ self.methodCalled = True
+ d = defer.Deferred()
+ return d
+
+ def test_omega(self):
+ pass
+
+
+def unexpectedException(self):
+ """i will raise an unexpected exception...
+ ... *CAUSE THAT'S THE KINDA GUY I AM*
+
+ >>> 1/0
+ """
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite.py b/vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite.py
new file mode 100644
index 0000000000..89ad162999
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2006 Twisted Matrix Laboratories. See LICENSE for details
+
+"""
+Mock test module that contains a C{test_suite} method. L{runner.TestLoader}
+should load the tests from the C{test_suite}, not from the C{Foo} C{TestCase}.
+
+See {twisted.trial.test.test_loader.LoaderTest.test_loadModuleWith_test_suite}.
+"""
+
+
+from twisted.trial import unittest, runner
+
+class Foo(unittest.TestCase):
+ def test_foo(self):
+ pass
+
+
+def test_suite():
+ ts = runner.TestSuite()
+ ts.name = "MyCustomSuite"
+ return ts
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite2.py b/vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite2.py
new file mode 100644
index 0000000000..6d05457e0b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite2.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2006 Twisted Matrix Laboratories. See LICENSE for details
+
+"""
+Mock test module that contains a C{testSuite} method. L{runner.TestLoader}
+should load the tests from the C{testSuite}, not from the C{Foo} C{TestCase}.
+
+See L{twisted.trial.test.test_loader.LoaderTest.test_loadModuleWith_testSuite}.
+"""
+
+
+from twisted.trial import unittest, runner
+
+class Foo(unittest.TestCase):
+ def test_foo(self):
+ pass
+
+
+def testSuite():
+ ts = runner.TestSuite()
+ ts.name = "MyCustomSuite"
+ return ts
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite3.py b/vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite3.py
new file mode 100644
index 0000000000..c5b89d47dd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/mockcustomsuite3.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2006 Twisted Matrix Laboratories. See LICENSE for details
+
+"""
+Mock test module that contains both a C{test_suite} and a C{testSuite} method.
+L{runner.TestLoader} should load the tests from the C{testSuite}, not from the
+C{Foo} C{TestCase} nor from the C{test_suite} method.
+
+See {twisted.trial.test.test_loader.LoaderTest.test_loadModuleWithBothCustom}.
+"""
+
+
+from twisted.trial import unittest, runner
+
+class Foo(unittest.TestCase):
+ def test_foo(self):
+ pass
+
+
+def test_suite():
+ ts = runner.TestSuite()
+ ts.name = "test_suite"
+ return ts
+
+
+def testSuite():
+ ts = runner.TestSuite()
+ ts.name = "testSuite"
+ return ts
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/mockdoctest.py b/vendor/Twisted-10.0.0/twisted/trial/test/mockdoctest.py
new file mode 100644
index 0000000000..10ad7775a2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/mockdoctest.py
@@ -0,0 +1,104 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# this module is a trivial class with doctests and a __test__ attribute
+# to test trial's doctest support with python2.4
+
+
+class Counter(object):
+ """a simple counter object for testing trial's doctest support
+
+ >>> c = Counter()
+ >>> c.value()
+ 0
+ >>> c += 3
+ >>> c.value()
+ 3
+ >>> c.incr()
+ >>> c.value() == 4
+ True
+ >>> c == 4
+ True
+ >>> c != 9
+ True
+
+ """
+ _count = 0
+
+ def __init__(self, initialValue=0, maxval=None):
+ self._count = initialValue
+ self.maxval = maxval
+
+ def __iadd__(self, other):
+ """add other to my value and return self
+
+ >>> c = Counter(100)
+ >>> c += 333
+ >>> c == 433
+ True
+ """
+ if self.maxval is not None and ((self._count + other) > self.maxval):
+ raise ValueError, "sorry, counter got too big"
+ else:
+ self._count += other
+ return self
+
+ def __eq__(self, other):
+ """equality operator, compare other to my value()
+
+ >>> c = Counter()
+ >>> c == 0
+ True
+ >>> c += 10
+ >>> c.incr()
+ >>> c == 10 # fail this test on purpose
+ True
+
+ """
+ return self._count == other
+
+ def __ne__(self, other):
+ """inequality operator
+
+ >>> c = Counter()
+ >>> c != 10
+ True
+ """
+ return not self.__eq__(other)
+
+ def incr(self):
+ """increment my value by 1
+
+ >>> from twisted.trial.test.mockdoctest import Counter
+ >>> c = Counter(10, 11)
+ >>> c.incr()
+ >>> c.value() == 11
+ True
+ >>> c.incr()
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in ?
+ File "twisted/trial/test/mockdoctest.py", line 51, in incr
+ self.__iadd__(1)
+ File "twisted/trial/test/mockdoctest.py", line 39, in __iadd__
+ raise ValueError, "sorry, counter got too big"
+ ValueError: sorry, counter got too big
+ """
+ self.__iadd__(1)
+
+ def value(self):
+ """return this counter's value
+
+ >>> c = Counter(555)
+ >>> c.value() == 555
+ True
+ """
+ return self._count
+
+ def unexpectedException(self):
+ """i will raise an unexpected exception...
+ ... *CAUSE THAT'S THE KINDA GUY I AM*
+
+ >>> 1/0
+ """
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/moduleself.py b/vendor/Twisted-10.0.0/twisted/trial/test/moduleself.py
new file mode 100644
index 0000000000..1f87c8239a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/moduleself.py
@@ -0,0 +1,7 @@
+# -*- test-case-name: twisted.trial.test.moduleself -*-
+from twisted.trial import unittest
+
+class Foo(unittest.TestCase):
+
+ def testFoo(self):
+ pass
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/moduletest.py b/vendor/Twisted-10.0.0/twisted/trial/test/moduletest.py
new file mode 100644
index 0000000000..c5e1d701d5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/moduletest.py
@@ -0,0 +1,11 @@
+# -*- test-case-name: twisted.trial.test.test_test_visitor -*-
+
+# fodder for test_script, which parses files for emacs local variable
+# declarations. This one is supposed to have:
+# test-case-name: twisted.trial.test.test_test_visitor.
+# in the first line
+# The class declaration is irrelevant
+
+class Foo(object):
+ pass
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/notpython b/vendor/Twisted-10.0.0/twisted/trial/test/notpython
new file mode 100644
index 0000000000..311485ce54
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/notpython
@@ -0,0 +1,2 @@
+
+this isn't python
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/novars.py b/vendor/Twisted-10.0.0/twisted/trial/test/novars.py
new file mode 100644
index 0000000000..93bc03dfd9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/novars.py
@@ -0,0 +1,6 @@
+# fodder for test_script, which parses files for emacs local variable
+# declarations. This one is supposed to have none.
+# The class declaration is irrelevant
+
+class Bar(object):
+ pass
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/packages.py b/vendor/Twisted-10.0.0/twisted/trial/test/packages.py
new file mode 100644
index 0000000000..d5373cf28a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/packages.py
@@ -0,0 +1,134 @@
+import sys, os
+from twisted.trial import unittest
+
+testModule = """
+from twisted.trial import unittest
+
+class FooTest(unittest.TestCase):
+ def testFoo(self):
+ pass
+"""
+
+dosModule = testModule.replace('\n', '\r\n')
+
+
+testSample = """
+'''This module is used by test_loader to test the Trial test loading
+functionality. Do NOT change the number of tests in this module.
+Do NOT change the names the tests in this module.
+'''
+
+import unittest as pyunit
+from twisted.trial import unittest
+
+class FooTest(unittest.TestCase):
+ def test_foo(self):
+ pass
+
+ def test_bar(self):
+ pass
+
+
+class PyunitTest(pyunit.TestCase):
+ def test_foo(self):
+ pass
+
+ def test_bar(self):
+ pass
+
+
+class NotATest(object):
+ def test_foo(self):
+ pass
+
+
+class AlphabetTest(unittest.TestCase):
+ def test_a(self):
+ pass
+
+ def test_b(self):
+ pass
+
+ def test_c(self):
+ pass
+"""
+
+
+class PackageTest(unittest.TestCase):
+ files = [
+ ('badpackage/__init__.py', 'frotz\n'),
+ ('badpackage/test_module.py', ''),
+ ('package2/__init__.py', ''),
+ ('package2/test_module.py', 'import frotz\n'),
+ ('package/__init__.py', ''),
+ ('package/frotz.py', 'frotz\n'),
+ ('package/test_bad_module.py',
+ 'raise ZeroDivisionError("fake error")'),
+ ('package/test_dos_module.py', dosModule),
+ ('package/test_import_module.py', 'import frotz'),
+ ('package/test_module.py', testModule),
+ ('goodpackage/__init__.py', ''),
+ ('goodpackage/test_sample.py', testSample),
+ ('goodpackage/sub/__init__.py', ''),
+ ('goodpackage/sub/test_sample.py', testSample)
+ ]
+
+ def _toModuleName(self, filename):
+ name = os.path.splitext(filename)[0]
+ segs = name.split('/')
+ if segs[-1] == '__init__':
+ segs = segs[:-1]
+ return '.'.join(segs)
+
+ def getModules(self):
+ return map(self._toModuleName, zip(*self.files)[0])
+
+ def cleanUpModules(self):
+ modules = self.getModules()
+ modules.sort()
+ modules.reverse()
+ for module in modules:
+ try:
+ del sys.modules[module]
+ except KeyError:
+ pass
+
+ def createFiles(self, files, parentDir='.'):
+ for filename, contents in self.files:
+ filename = os.path.join(parentDir, filename)
+ self._createDirectory(filename)
+ fd = open(filename, 'w')
+ fd.write(contents)
+ fd.close()
+
+ def _createDirectory(self, filename):
+ directory = os.path.dirname(filename)
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+ def setUp(self, parentDir=None):
+ if parentDir is None:
+ parentDir = self.mktemp()
+ self.parent = parentDir
+ self.createFiles(self.files, parentDir)
+
+ def tearDown(self):
+ self.cleanUpModules()
+
+class SysPathManglingTest(PackageTest):
+ def setUp(self, parent=None):
+ self.oldPath = sys.path[:]
+ self.newPath = sys.path[:]
+ if parent is None:
+ parent = self.mktemp()
+ PackageTest.setUp(self, parent)
+ self.newPath.append(self.parent)
+ self.mangleSysPath(self.newPath)
+
+ def tearDown(self):
+ PackageTest.tearDown(self)
+ self.mangleSysPath(self.oldPath)
+
+ def mangleSysPath(self, pathVar):
+ sys.path[:] = pathVar
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/sample.py b/vendor/Twisted-10.0.0/twisted/trial/test/sample.py
new file mode 100644
index 0000000000..a3f9568d1f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/sample.py
@@ -0,0 +1,40 @@
+"""This module is used by test_loader to test the Trial test loading
+functionality. Do NOT change the number of tests in this module. Do NOT change
+the names the tests in this module.
+"""
+
+import unittest as pyunit
+from twisted.trial import unittest
+
+class FooTest(unittest.TestCase):
+ def test_foo(self):
+ pass
+
+ def test_bar(self):
+ pass
+
+
+class PyunitTest(pyunit.TestCase):
+ def test_foo(self):
+ pass
+
+ def test_bar(self):
+ pass
+
+
+class NotATest(object):
+ def test_foo(self):
+ pass
+
+
+class AlphabetTest(unittest.TestCase):
+ def test_a(self):
+ pass
+
+ def test_b(self):
+ pass
+
+ def test_c(self):
+ pass
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/scripttest.py b/vendor/Twisted-10.0.0/twisted/trial/test/scripttest.py
new file mode 100644
index 0000000000..ad4f580da6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/scripttest.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+# -*- test-case-name: twisted.trial.test.test_test_visitor,twisted.trial.test.test_class -*-
+
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# fodder for test_script, which parses files for emacs local variable
+# declarations. This one is supposed to have:
+# test-case-name: twisted.trial.test.test_test_visitor
+# in the second line
+# The class declaration is irrelevant
+
+class Foo(object):
+ pass
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/suppression.py b/vendor/Twisted-10.0.0/twisted/trial/test/suppression.py
new file mode 100644
index 0000000000..b0d072c1ff
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/suppression.py
@@ -0,0 +1,57 @@
+# -*- test-case-name: twisted.trial.test.test_tests -*-
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases used to make sure that warning supression works at the module,
+method, and class levels.
+"""
+
+import warnings
+
+from twisted.trial import unittest, util
+
+
+
+METHOD_WARNING_MSG = "method warning message"
+CLASS_WARNING_MSG = "class warning message"
+MODULE_WARNING_MSG = "module warning message"
+
+class MethodWarning(Warning):
+ pass
+
+class ClassWarning(Warning):
+ pass
+
+class ModuleWarning(Warning):
+ pass
+
+class EmitMixin:
+ def _emit(self):
+ warnings.warn(METHOD_WARNING_MSG, MethodWarning)
+ warnings.warn(CLASS_WARNING_MSG, ClassWarning)
+ warnings.warn(MODULE_WARNING_MSG, ModuleWarning)
+
+
+class TestSuppression(unittest.TestCase, EmitMixin):
+ def testSuppressMethod(self):
+ self._emit()
+ testSuppressMethod.suppress = [util.suppress(message=METHOD_WARNING_MSG)]
+
+ def testSuppressClass(self):
+ self._emit()
+
+ def testOverrideSuppressClass(self):
+ self._emit()
+ testOverrideSuppressClass.suppress = []
+
+TestSuppression.suppress = [util.suppress(message=CLASS_WARNING_MSG)]
+
+
+class TestSuppression2(unittest.TestCase, EmitMixin):
+ def testSuppressModule(self):
+ self._emit()
+
+suppress = [util.suppress(message=MODULE_WARNING_MSG)]
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_assertions.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_assertions.py
new file mode 100644
index 0000000000..5ca0f105be
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_assertions.py
@@ -0,0 +1,742 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details
+
+"""
+Tests for assertions provided by L{twisted.trial.unittest.TestCase}.
+"""
+
+import warnings, StringIO
+from pprint import pformat
+
+from twisted.python import reflect, failure
+from twisted.python.deprecate import deprecated, getVersionString
+from twisted.python.versions import Version
+from twisted.python.util import dsu
+from twisted.internet import defer
+from twisted.trial import unittest, runner, reporter
+
+class MockEquality(object):
+ def __init__(self, name):
+ self.name = name
+
+ def __repr__(self):
+ return "MockEquality(%s)" % (self.name,)
+
+ def __eq__(self, other):
+ if not hasattr(other, 'name'):
+ raise ValueError("%r not comparable to %r" % (other, self))
+ return self.name[0] == other.name[0]
+
+
+class TestAssertions(unittest.TestCase):
+ """
+ Tests for TestCase's assertion methods. That is, failUnless*,
+ failIf*, assert*.
+
+ This is pretty paranoid. Still, a certain paranoia is healthy if you
+ are testing a unit testing framework.
+ """
+
+ class FailingTest(unittest.TestCase):
+ def test_fails(self):
+ raise self.failureException()
+
+ def testFail(self):
+ try:
+ self.fail("failed")
+ except self.failureException, e:
+ if not str(e) == 'failed':
+ raise self.failureException("Exception had msg %s instead of %s"
+ % str(e), 'failed')
+ else:
+ raise self.failureException("Call to self.fail() didn't fail test")
+
+ def test_failingException_fails(self):
+ test = runner.TestLoader().loadClass(TestAssertions.FailingTest)
+ io = StringIO.StringIO()
+ result = reporter.TestResult()
+ test.run(result)
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.errors, [])
+ self.failUnlessEqual(len(result.failures), 1)
+
+ def test_failIf(self):
+ for notTrue in [0, 0.0, False, None, (), []]:
+ self.failIf(notTrue, "failed on %r" % (notTrue,))
+ for true in [1, True, 'cat', [1,2], (3,4)]:
+ try:
+ self.failIf(true, "failed on %r" % (true,))
+ except self.failureException, e:
+ self.failUnlessEqual(str(e), "failed on %r" % (true,))
+ else:
+ self.fail("Call to failIf(%r) didn't fail" % (true,))
+
+ def test_failUnless(self):
+ for notTrue in [0, 0.0, False, None, (), []]:
+ try:
+ self.failUnless(notTrue, "failed on %r" % (notTrue,))
+ except self.failureException, e:
+ self.failUnlessEqual(str(e), "failed on %r" % (notTrue,))
+ else:
+ self.fail("Call to failUnless(%r) didn't fail" % (notTrue,))
+ for true in [1, True, 'cat', [1,2], (3,4)]:
+ self.failUnless(true, "failed on %r" % (true,))
+
+ def _testEqualPair(self, first, second):
+ x = self.failUnlessEqual(first, second)
+ if x != first:
+ self.fail("failUnlessEqual should return first parameter")
+
+ def _testUnequalPair(self, first, second):
+ try:
+ self.failUnlessEqual(first, second)
+ except self.failureException, e:
+ expected = 'not equal:\na = %s\nb = %s\n' % (
+ pformat(first), pformat(second))
+ if str(e) != expected:
+ self.fail("Expected: %r; Got: %s" % (expected, str(e)))
+ else:
+ self.fail("Call to failUnlessEqual(%r, %r) didn't fail"
+ % (first, second))
+
+ def test_failUnlessEqual_basic(self):
+ self._testEqualPair('cat', 'cat')
+ self._testUnequalPair('cat', 'dog')
+ self._testEqualPair([1], [1])
+ self._testUnequalPair([1], 'orange')
+
+ def test_failUnlessEqual_custom(self):
+ x = MockEquality('first')
+ y = MockEquality('second')
+ z = MockEquality('fecund')
+ self._testEqualPair(x, x)
+ self._testEqualPair(x, z)
+ self._testUnequalPair(x, y)
+ self._testUnequalPair(y, z)
+
+ def test_failUnlessEqualMessage(self):
+ """
+ When a message is passed to L{assertEqual}, it is included in the
+ error message.
+ """
+ exception = self.assertRaises(
+ self.failureException, self.assertEqual,
+ 'foo', 'bar', 'message')
+ self.assertEqual(
+ str(exception),
+ "message\nnot equal:\na = 'foo'\nb = 'bar'\n")
+
+
+ def test_failUnlessEqualNoneMessage(self):
+ """
+ If a message is specified as C{None}, it is not included in the error
+ message of L{assertEqual}.
+ """
+ exception = self.assertRaises(
+ self.failureException, self.assertEqual, 'foo', 'bar', None)
+ self.assertEqual(str(exception), "not equal:\na = 'foo'\nb = 'bar'\n")
+
+
+ def test_failUnlessEqual_incomparable(self):
+ apple = MockEquality('apple')
+ orange = ['orange']
+ try:
+ self.failUnlessEqual(apple, orange)
+ except self.failureException:
+ self.fail("Fail raised when ValueError ought to have been raised.")
+ except ValueError:
+ # good. error not swallowed
+ pass
+ else:
+ self.fail("Comparing %r and %r should have raised an exception"
+ % (apple, orange))
+
+ def _raiseError(self, error):
+ raise error
+
+ def test_failUnlessRaises_expected(self):
+ x = self.failUnlessRaises(ValueError, self._raiseError, ValueError)
+ self.failUnless(isinstance(x, ValueError),
+ "Expect failUnlessRaises to return instance of raised "
+ "exception.")
+
+ def test_failUnlessRaises_unexpected(self):
+ try:
+ self.failUnlessRaises(ValueError, self._raiseError, TypeError)
+ except TypeError:
+ self.fail("failUnlessRaises shouldn't re-raise unexpected "
+ "exceptions")
+ except self.failureException, e:
+ # what we expect
+ pass
+ else:
+ self.fail("Expected exception wasn't raised. Should have failed")
+
+ def test_failUnlessRaises_noException(self):
+ try:
+ self.failUnlessRaises(ValueError, lambda : None)
+ except self.failureException, e:
+ self.failUnlessEqual(str(e),
+ 'ValueError not raised (None returned)')
+ else:
+ self.fail("Exception not raised. Should have failed")
+
+ def test_failUnlessRaises_failureException(self):
+ x = self.failUnlessRaises(self.failureException, self._raiseError,
+ self.failureException)
+ self.failUnless(isinstance(x, self.failureException),
+ "Expected %r instance to be returned"
+ % (self.failureException,))
+ try:
+ x = self.failUnlessRaises(self.failureException, self._raiseError,
+ ValueError)
+ except self.failureException, e:
+ # what we expect
+ pass
+ else:
+ self.fail("Should have raised exception")
+
+ def test_failIfEqual_basic(self):
+ x, y, z = [1], [2], [1]
+ ret = self.failIfEqual(x, y)
+ self.failUnlessEqual(ret, x,
+ "failIfEqual should return first parameter")
+ self.failUnlessRaises(self.failureException,
+ self.failIfEqual, x, x)
+ self.failUnlessRaises(self.failureException,
+ self.failIfEqual, x, z)
+
+ def test_failIfEqual_customEq(self):
+ x = MockEquality('first')
+ y = MockEquality('second')
+ z = MockEquality('fecund')
+ ret = self.failIfEqual(x, y)
+ self.failUnlessEqual(ret, x,
+ "failIfEqual should return first parameter")
+ self.failUnlessRaises(self.failureException,
+ self.failIfEqual, x, x)
+ # test when __ne__ is not defined
+ self.failIfEqual(x, z, "__ne__ not defined, so not equal")
+
+ def test_failUnlessIdentical(self):
+ x, y, z = [1], [1], [2]
+ ret = self.failUnlessIdentical(x, x)
+ self.failUnlessEqual(ret, x,
+ 'failUnlessIdentical should return first '
+ 'parameter')
+ self.failUnlessRaises(self.failureException,
+ self.failUnlessIdentical, x, y)
+ self.failUnlessRaises(self.failureException,
+ self.failUnlessIdentical, x, z)
+
+ def test_failUnlessApproximates(self):
+ x, y, z = 1.0, 1.1, 1.2
+ self.failUnlessApproximates(x, x, 0.2)
+ ret = self.failUnlessApproximates(x, y, 0.2)
+ self.failUnlessEqual(ret, x, "failUnlessApproximates should return "
+ "first parameter")
+ self.failUnlessRaises(self.failureException,
+ self.failUnlessApproximates, x, z, 0.1)
+ self.failUnlessRaises(self.failureException,
+ self.failUnlessApproximates, x, y, 0.1)
+
+ def test_failUnlessAlmostEqual(self):
+ precision = 5
+ x = 8.000001
+ y = 8.00001
+ z = 8.000002
+ self.failUnlessAlmostEqual(x, x, precision)
+ ret = self.failUnlessAlmostEqual(x, z, precision)
+ self.failUnlessEqual(ret, x, "failUnlessAlmostEqual should return "
+ "first parameter (%r, %r)" % (ret, x))
+ self.failUnlessRaises(self.failureException,
+ self.failUnlessAlmostEqual, x, y, precision)
+
+ def test_failIfAlmostEqual(self):
+ precision = 5
+ x = 8.000001
+ y = 8.00001
+ z = 8.000002
+ ret = self.failIfAlmostEqual(x, y, precision)
+ self.failUnlessEqual(ret, x, "failIfAlmostEqual should return "
+ "first parameter (%r, %r)" % (ret, x))
+ self.failUnlessRaises(self.failureException,
+ self.failIfAlmostEqual, x, x, precision)
+ self.failUnlessRaises(self.failureException,
+ self.failIfAlmostEqual, x, z, precision)
+
+ def test_failUnlessSubstring(self):
+ x = "cat"
+ y = "the dog sat"
+ z = "the cat sat"
+ self.failUnlessSubstring(x, x)
+ ret = self.failUnlessSubstring(x, z)
+ self.failUnlessEqual(ret, x, 'should return first parameter')
+ self.failUnlessRaises(self.failureException,
+ self.failUnlessSubstring, x, y)
+ self.failUnlessRaises(self.failureException,
+ self.failUnlessSubstring, z, x)
+
+ def test_failIfSubstring(self):
+ x = "cat"
+ y = "the dog sat"
+ z = "the cat sat"
+ self.failIfSubstring(z, x)
+ ret = self.failIfSubstring(x, y)
+ self.failUnlessEqual(ret, x, 'should return first parameter')
+ self.failUnlessRaises(self.failureException,
+ self.failIfSubstring, x, x)
+ self.failUnlessRaises(self.failureException,
+ self.failIfSubstring, x, z)
+
+ def test_assertFailure(self):
+ d = defer.maybeDeferred(lambda: 1/0)
+ return self.assertFailure(d, ZeroDivisionError)
+
+ def test_assertFailure_wrongException(self):
+ d = defer.maybeDeferred(lambda: 1/0)
+ self.assertFailure(d, OverflowError)
+ d.addCallbacks(lambda x: self.fail('Should have failed'),
+ lambda x: x.trap(self.failureException))
+ return d
+
+ def test_assertFailure_noException(self):
+ d = defer.succeed(None)
+ self.assertFailure(d, ZeroDivisionError)
+ d.addCallbacks(lambda x: self.fail('Should have failed'),
+ lambda x: x.trap(self.failureException))
+ return d
+
+ def test_assertFailure_moreInfo(self):
+ """
+ In the case of assertFailure failing, check that we get lots of
+ information about the exception that was raised.
+ """
+ try:
+ 1/0
+ except ZeroDivisionError:
+ f = failure.Failure()
+ d = defer.fail(f)
+ d = self.assertFailure(d, RuntimeError)
+ d.addErrback(self._checkInfo, f)
+ return d
+
+ def _checkInfo(self, assertionFailure, f):
+ assert assertionFailure.check(self.failureException)
+ output = assertionFailure.getErrorMessage()
+ self.assertIn(f.getErrorMessage(), output)
+ self.assertIn(f.getBriefTraceback(), output)
+
+ def test_assertFailure_masked(self):
+ """
+ A single wrong assertFailure should fail the whole test.
+ """
+ class ExampleFailure(Exception):
+ pass
+
+ class TC(unittest.TestCase):
+ failureException = ExampleFailure
+ def test_assertFailure(self):
+ d = defer.maybeDeferred(lambda: 1/0)
+ self.assertFailure(d, OverflowError)
+ self.assertFailure(d, ZeroDivisionError)
+ return d
+
+ test = TC('test_assertFailure')
+ result = reporter.TestResult()
+ test.run(result)
+ self.assertEqual(1, len(result.failures))
+
+
+ def test_assertWarns(self):
+ """
+ Test basic assertWarns report.
+ """
+ def deprecated(a):
+ warnings.warn("Woo deprecated", category=DeprecationWarning)
+ return a
+ r = self.assertWarns(DeprecationWarning, "Woo deprecated", __file__,
+ deprecated, 123)
+ self.assertEquals(r, 123)
+
+
+ def test_assertWarnsRegistryClean(self):
+ """
+ Test that assertWarns cleans the warning registry, so the warning is
+ not swallowed the second time.
+ """
+ def deprecated(a):
+ warnings.warn("Woo deprecated", category=DeprecationWarning)
+ return a
+ r1 = self.assertWarns(DeprecationWarning, "Woo deprecated", __file__,
+ deprecated, 123)
+ self.assertEquals(r1, 123)
+ # The warning should be raised again
+ r2 = self.assertWarns(DeprecationWarning, "Woo deprecated", __file__,
+ deprecated, 321)
+ self.assertEquals(r2, 321)
+
+
+ def test_assertWarnsError(self):
+ """
+ Test assertWarns failure when no warning is generated.
+ """
+ def normal(a):
+ return a
+ self.assertRaises(self.failureException,
+ self.assertWarns, DeprecationWarning, "Woo deprecated", __file__,
+ normal, 123)
+
+
+ def test_assertWarnsWrongCategory(self):
+ """
+ Test assertWarns failure when the category is wrong.
+ """
+ def deprecated(a):
+ warnings.warn("Foo deprecated", category=DeprecationWarning)
+ return a
+ self.assertRaises(self.failureException,
+ self.assertWarns, UserWarning, "Foo deprecated", __file__,
+ deprecated, 123)
+
+
+ def test_assertWarnsWrongMessage(self):
+ """
+ Test assertWarns failure when the message is wrong.
+ """
+ def deprecated(a):
+ warnings.warn("Foo deprecated", category=DeprecationWarning)
+ return a
+ self.assertRaises(self.failureException,
+ self.assertWarns, DeprecationWarning, "Bar deprecated", __file__,
+ deprecated, 123)
+
+
+ def test_assertWarnsWrongFile(self):
+ """
+ If the warning emitted by a function refers to a different file than is
+ passed to C{assertWarns}, C{failureException} is raised.
+ """
+ def deprecated(a):
+ # stacklevel=2 points at the direct caller of the function. The
+ # way assertRaises is invoked below, the direct caller will be
+ # something somewhere in trial, not something in this file. In
+ # Python 2.5 and earlier, stacklevel of 0 resulted in a warning
+ # pointing to the warnings module itself. Starting in Python 2.6,
+ # stacklevel of 0 and 1 both result in a warning pointing to *this*
+ # file, presumably due to the fact that the warn function is
+ # implemented in C and has no convenient Python
+ # filename/linenumber.
+ warnings.warn(
+ "Foo deprecated", category=DeprecationWarning, stacklevel=2)
+ self.assertRaises(
+ self.failureException,
+ # Since the direct caller isn't in this file, try to assert that
+ # the warning *does* point to this file, so that assertWarns raises
+ # an exception.
+ self.assertWarns, DeprecationWarning, "Foo deprecated", __file__,
+ deprecated, 123)
+
+ def test_assertWarnsOnClass(self):
+ """
+ Test assertWarns works when creating a class instance.
+ """
+ class Warn:
+ def __init__(self):
+ warnings.warn("Do not call me", category=RuntimeWarning)
+ r = self.assertWarns(RuntimeWarning, "Do not call me", __file__,
+ Warn)
+ self.assertTrue(isinstance(r, Warn))
+ r = self.assertWarns(RuntimeWarning, "Do not call me", __file__,
+ Warn)
+ self.assertTrue(isinstance(r, Warn))
+
+
+ def test_assertWarnsOnMethod(self):
+ """
+ Test assertWarns works when used on an instance method.
+ """
+ class Warn:
+ def deprecated(self, a):
+ warnings.warn("Bar deprecated", category=DeprecationWarning)
+ return a
+ w = Warn()
+ r = self.assertWarns(DeprecationWarning, "Bar deprecated", __file__,
+ w.deprecated, 321)
+ self.assertEquals(r, 321)
+ r = self.assertWarns(DeprecationWarning, "Bar deprecated", __file__,
+ w.deprecated, 321)
+ self.assertEquals(r, 321)
+
+
+ def test_assertWarnsOnCall(self):
+ """
+ Test assertWarns works on instance with C{__call__} method.
+ """
+ class Warn:
+ def __call__(self, a):
+ warnings.warn("Egg deprecated", category=DeprecationWarning)
+ return a
+ w = Warn()
+ r = self.assertWarns(DeprecationWarning, "Egg deprecated", __file__,
+ w, 321)
+ self.assertEquals(r, 321)
+ r = self.assertWarns(DeprecationWarning, "Egg deprecated", __file__,
+ w, 321)
+ self.assertEquals(r, 321)
+
+
+ def test_assertWarnsFilter(self):
+ """
+ Test assertWarns on a warning filterd by default.
+ """
+ def deprecated(a):
+ warnings.warn("Woo deprecated", category=PendingDeprecationWarning)
+ return a
+ r = self.assertWarns(PendingDeprecationWarning, "Woo deprecated",
+ __file__, deprecated, 123)
+ self.assertEquals(r, 123)
+
+
+ def test_assertWarnsMultipleWarnings(self):
+ """
+ C{assertWarns} does not raise an exception if the function it is passed
+ triggers the same warning more than once.
+ """
+ def deprecated():
+ warnings.warn("Woo deprecated", category=PendingDeprecationWarning)
+ def f():
+ deprecated()
+ deprecated()
+ self.assertWarns(
+ PendingDeprecationWarning, "Woo deprecated", __file__, f)
+
+
+ def test_assertWarnsDifferentWarnings(self):
+ """
+ For now, assertWarns is unable to handle multiple different warnings,
+ so it should raise an exception if it's the case.
+ """
+ def deprecated(a):
+ warnings.warn("Woo deprecated", category=DeprecationWarning)
+ warnings.warn("Another one", category=PendingDeprecationWarning)
+ e = self.assertRaises(self.failureException,
+ self.assertWarns, DeprecationWarning, "Woo deprecated",
+ __file__, deprecated, 123)
+ self.assertEquals(str(e), "Can't handle different warnings")
+
+
+ def test_assertWarnsAfterUnassertedWarning(self):
+ """
+ Warnings emitted before L{TestCase.assertWarns} is called do not get
+ flushed and do not alter the behavior of L{TestCase.assertWarns}.
+ """
+ class TheWarning(Warning):
+ pass
+
+ def f(message):
+ warnings.warn(message, category=TheWarning)
+ f("foo")
+ self.assertWarns(TheWarning, "bar", __file__, f, "bar")
+ [warning] = self.flushWarnings([f])
+ self.assertEqual(warning['message'], "foo")
+
+
+ def test_assertIsInstance(self):
+ """
+ Test a true condition of assertIsInstance.
+ """
+ A = type('A', (object,), {})
+ a = A()
+ self.assertIsInstance(a, A)
+
+ def test_assertIsInstanceMultipleClasses(self):
+ """
+ Test a true condition of assertIsInstance with multiple classes.
+ """
+ A = type('A', (object,), {})
+ B = type('B', (object,), {})
+ a = A()
+ self.assertIsInstance(a, (A, B))
+
+ def test_assertIsInstanceError(self):
+ """
+ Test an error with assertIsInstance.
+ """
+ A = type('A', (object,), {})
+ B = type('B', (object,), {})
+ a = A()
+ self.assertRaises(self.failureException, self.assertIsInstance, a, B)
+
+ def test_assertIsInstanceErrorMultipleClasses(self):
+ """
+ Test an error with assertIsInstance and multiple classes.
+ """
+ A = type('A', (object,), {})
+ B = type('B', (object,), {})
+ C = type('C', (object,), {})
+ a = A()
+ self.assertRaises(self.failureException, self.assertIsInstance, a, (B, C))
+
+ def test_assertNotIsInstance(self):
+ """
+ Test a true condition of assertNotIsInstance.
+ """
+ A = type('A', (object,), {})
+ B = type('B', (object,), {})
+ a = A()
+ self.assertNotIsInstance(a, B)
+
+ def test_assertNotIsInstanceMultipleClasses(self):
+ """
+ Test a true condition of assertNotIsInstance and multiple classes.
+ """
+ A = type('A', (object,), {})
+ B = type('B', (object,), {})
+ C = type('C', (object,), {})
+ a = A()
+ self.assertNotIsInstance(a, (B, C))
+
+ def test_assertNotIsInstanceError(self):
+ """
+ Test an error with assertNotIsInstance.
+ """
+ A = type('A', (object,), {})
+ a = A()
+ error = self.assertRaises(self.failureException,
+ self.assertNotIsInstance, a, A)
+ self.assertEquals(str(error), "%r is an instance of %s" % (a, A))
+
+ def test_assertNotIsInstanceErrorMultipleClasses(self):
+ """
+ Test an error with assertNotIsInstance and multiple classes.
+ """
+ A = type('A', (object,), {})
+ B = type('B', (object,), {})
+ a = A()
+ self.assertRaises(self.failureException, self.assertNotIsInstance, a, (A, B))
+
+
+
+class TestAssertionNames(unittest.TestCase):
+ """
+ Tests for consistency of naming within TestCase assertion methods
+ """
+ def _getAsserts(self):
+ dct = {}
+ reflect.accumulateMethods(self, dct, 'assert')
+ return [ dct[k] for k in dct if not k.startswith('Not') and k != '_' ]
+
+ def _name(self, x):
+ return x.__name__
+
+ def test_failUnless_matches_assert(self):
+ asserts = self._getAsserts()
+ failUnlesses = reflect.prefixedMethods(self, 'failUnless')
+ self.failUnlessEqual(dsu(asserts, self._name),
+ dsu(failUnlesses, self._name))
+
+ def test_failIf_matches_assertNot(self):
+ asserts = reflect.prefixedMethods(unittest.TestCase, 'assertNot')
+ failIfs = reflect.prefixedMethods(unittest.TestCase, 'failIf')
+ self.failUnlessEqual(dsu(asserts, self._name),
+ dsu(failIfs, self._name))
+
+ def test_equalSpelling(self):
+ for name, value in vars(self).items():
+ if not callable(value):
+ continue
+ if name.endswith('Equal'):
+ self.failUnless(hasattr(self, name+'s'),
+ "%s but no %ss" % (name, name))
+ self.failUnlessEqual(value, getattr(self, name+'s'))
+ if name.endswith('Equals'):
+ self.failUnless(hasattr(self, name[:-1]),
+ "%s but no %s" % (name, name[:-1]))
+ self.failUnlessEqual(value, getattr(self, name[:-1]))
+
+
+class TestCallDeprecated(unittest.TestCase):
+ """
+ Test use of the L{TestCase.callDeprecated} method with version objects.
+ """
+
+ version = Version('Twisted', 8, 0, 0)
+
+ def oldMethod(self, x):
+ """
+ Deprecated method for testing.
+ """
+ return x
+
+
+ def test_callDeprecatedSuppressesWarning(self):
+ """
+ callDeprecated calls a deprecated callable, suppressing the
+ deprecation warning.
+ """
+ self.callDeprecated(self.version, self.oldMethod, 'foo')
+ self.assertEqual(
+ self.flushWarnings(), [], "No warnings should be shown")
+
+
+ def test_callDeprecatedCallsFunction(self):
+ """
+ L{callDeprecated} actually calls the callable passed to it.
+ """
+ result = self.callDeprecated(self.version, self.oldMethod, 'foo')
+ self.assertEqual('foo', result)
+
+
+ def test_failsWithoutDeprecation(self):
+ """
+ callDeprecated raises a test failure if the callable is not
+ deprecated.
+ """
+ def notDeprecated():
+ pass
+ exception = self.assertRaises(
+ self.failureException,
+ self.callDeprecated, self.version, notDeprecated)
+ self.assertEqual(
+ "%r is not deprecated." % notDeprecated, str(exception))
+
+
+ def test_failsWithIncorrectDeprecation(self):
+ """
+ callDeprecated raises a test failure if the callable was deprecated
+ at a different version to the one expected.
+ """
+ differentVersion = Version('Foo', 1, 2, 3)
+ exception = self.assertRaises(
+ self.failureException,
+ self.callDeprecated,
+ differentVersion, self.oldMethod, 'foo')
+ self.assertIn(getVersionString(self.version), str(exception))
+ self.assertIn(getVersionString(differentVersion), str(exception))
+
+
+ def test_nestedDeprecation(self):
+ """
+ L{callDeprecated} ignores all deprecations apart from the first.
+
+ Multiple warnings are generated when a deprecated function calls
+ another deprecated function. The first warning is the one generated by
+ the explicitly called function. That's the warning that we care about.
+ """
+ differentVersion = Version('Foo', 1, 2, 3)
+
+ def nestedDeprecation(*args):
+ return self.oldMethod(*args)
+ nestedDeprecation = deprecated(differentVersion)(nestedDeprecation)
+
+ self.callDeprecated(differentVersion, nestedDeprecation, 24)
+
+ # The oldMethod deprecation should have been emitted too, not captured
+ # by callDeprecated. Flush it now to make sure it did happen and to
+ # prevent it from showing up on stdout.
+ warningsShown = self.flushWarnings()
+ self.assertEqual(len(warningsShown), 1)
+
+TestCallDeprecated.oldMethod = deprecated(TestCallDeprecated.version)(
+ TestCallDeprecated.oldMethod)
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_deferred.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_deferred.py
new file mode 100644
index 0000000000..1df2a2210c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_deferred.py
@@ -0,0 +1,220 @@
+from twisted.internet import defer
+from twisted.trial import unittest
+from twisted.trial import runner, reporter, util
+from twisted.trial.test import detests
+
+
+class TestSetUp(unittest.TestCase):
+ def _loadSuite(self, klass):
+ loader = runner.TestLoader()
+ r = reporter.TestResult()
+ s = loader.loadClass(klass)
+ return r, s
+
+ def test_success(self):
+ result, suite = self._loadSuite(detests.DeferredSetUpOK)
+ suite(result)
+ self.failUnless(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+
+ def test_fail(self):
+ self.failIf(detests.DeferredSetUpFail.testCalled)
+ result, suite = self._loadSuite(detests.DeferredSetUpFail)
+ suite(result)
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.failures), 0)
+ self.failUnlessEqual(len(result.errors), 1)
+ self.failIf(detests.DeferredSetUpFail.testCalled)
+
+ def test_callbackFail(self):
+ self.failIf(detests.DeferredSetUpCallbackFail.testCalled)
+ result, suite = self._loadSuite(detests.DeferredSetUpCallbackFail)
+ suite(result)
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.failures), 0)
+ self.failUnlessEqual(len(result.errors), 1)
+ self.failIf(detests.DeferredSetUpCallbackFail.testCalled)
+
+ def test_error(self):
+ self.failIf(detests.DeferredSetUpError.testCalled)
+ result, suite = self._loadSuite(detests.DeferredSetUpError)
+ suite(result)
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.failures), 0)
+ self.failUnlessEqual(len(result.errors), 1)
+ self.failIf(detests.DeferredSetUpError.testCalled)
+
+ def test_skip(self):
+ self.failIf(detests.DeferredSetUpSkip.testCalled)
+ result, suite = self._loadSuite(detests.DeferredSetUpSkip)
+ suite(result)
+ self.failUnless(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.failures), 0)
+ self.failUnlessEqual(len(result.errors), 0)
+ self.failUnlessEqual(len(result.skips), 1)
+ self.failIf(detests.DeferredSetUpSkip.testCalled)
+
+
+class TestNeverFire(unittest.TestCase):
+ def setUp(self):
+ self._oldTimeout = util.DEFAULT_TIMEOUT_DURATION
+ util.DEFAULT_TIMEOUT_DURATION = 0.1
+
+ def tearDown(self):
+ util.DEFAULT_TIMEOUT_DURATION = self._oldTimeout
+
+ def _loadSuite(self, klass):
+ loader = runner.TestLoader()
+ r = reporter.TestResult()
+ s = loader.loadClass(klass)
+ return r, s
+
+ def test_setUp(self):
+ self.failIf(detests.DeferredSetUpNeverFire.testCalled)
+ result, suite = self._loadSuite(detests.DeferredSetUpNeverFire)
+ suite(result)
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.failures), 0)
+ self.failUnlessEqual(len(result.errors), 1)
+ self.failIf(detests.DeferredSetUpNeverFire.testCalled)
+ self.failUnless(result.errors[0][1].check(defer.TimeoutError))
+
+
+class TestTester(unittest.TestCase):
+ def getTest(self, name):
+ raise NotImplementedError("must override me")
+
+ def runTest(self, name):
+ result = reporter.TestResult()
+ self.getTest(name).run(result)
+ return result
+
+
+class TestDeferred(TestTester):
+ def getTest(self, name):
+ return detests.DeferredTests(name)
+
+ def test_pass(self):
+ result = self.runTest('test_pass')
+ self.failUnless(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+
+ def test_passGenerated(self):
+ result = self.runTest('test_passGenerated')
+ self.failUnless(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnless(detests.DeferredTests.touched)
+
+ def test_fail(self):
+ result = self.runTest('test_fail')
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.failures), 1)
+
+ def test_failureInCallback(self):
+ result = self.runTest('test_failureInCallback')
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.failures), 1)
+
+ def test_errorInCallback(self):
+ result = self.runTest('test_errorInCallback')
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.errors), 1)
+
+ def test_skip(self):
+ result = self.runTest('test_skip')
+ self.failUnless(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.skips), 1)
+ self.failIf(detests.DeferredTests.touched)
+
+ def test_todo(self):
+ result = self.runTest('test_expectedFailure')
+ self.failUnless(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.errors), 0)
+ self.failUnlessEqual(len(result.failures), 0)
+ self.failUnlessEqual(len(result.expectedFailures), 1)
+
+ def test_thread(self):
+ result = self.runTest('test_thread')
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnless(result.wasSuccessful(), result.errors)
+
+
+class TestTimeout(TestTester):
+ def getTest(self, name):
+ return detests.TimeoutTests(name)
+
+ def _wasTimeout(self, error):
+ self.failUnlessEqual(error.check(defer.TimeoutError),
+ defer.TimeoutError)
+
+ def test_pass(self):
+ result = self.runTest('test_pass')
+ self.failUnless(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+
+ def test_passDefault(self):
+ result = self.runTest('test_passDefault')
+ self.failUnless(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+
+ def test_timeout(self):
+ result = self.runTest('test_timeout')
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.errors), 1)
+ self._wasTimeout(result.errors[0][1])
+
+ def test_timeoutZero(self):
+ result = self.runTest('test_timeoutZero')
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.errors), 1)
+ self._wasTimeout(result.errors[0][1])
+
+ def test_skip(self):
+ result = self.runTest('test_skip')
+ self.failUnless(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.skips), 1)
+
+ def test_todo(self):
+ result = self.runTest('test_expectedFailure')
+ self.failUnless(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.expectedFailures), 1)
+ self._wasTimeout(result.expectedFailures[0][1])
+
+ def test_errorPropagation(self):
+ result = self.runTest('test_errorPropagation')
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(result.testsRun, 1)
+ self._wasTimeout(detests.TimeoutTests.timedOut)
+
+ def test_classTimeout(self):
+ loader = runner.TestLoader()
+ suite = loader.loadClass(detests.TestClassTimeoutAttribute)
+ result = reporter.TestResult()
+ suite.run(result)
+ self.failUnlessEqual(len(result.errors), 1)
+ self._wasTimeout(result.errors[0][1])
+
+ def test_callbackReturnsNonCallingDeferred(self):
+ #hacky timeout
+ # raises KeyboardInterrupt because Trial sucks
+ from twisted.internet import reactor
+ call = reactor.callLater(2, reactor.crash)
+ result = self.runTest('test_calledButNeverCallback')
+ if call.active():
+ call.cancel()
+ self.failIf(result.wasSuccessful())
+ self._wasTimeout(result.errors[0][1])
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_doctest.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_doctest.py
new file mode 100644
index 0000000000..b186e572dd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_doctest.py
@@ -0,0 +1,81 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test twisted's doctest support.
+"""
+
+from twisted.trial import itrial, runner, unittest, reporter
+from twisted.trial.test import mockdoctest
+
+
+class TestRunners(unittest.TestCase):
+ """
+ Tests for Twisted's doctest support.
+ """
+
+ def test_id(self):
+ """
+ Check that the id() of the doctests' case object contains the FQPN of
+ the actual tests. We need this because id() has weird behaviour w/
+ doctest in Python 2.3.
+ """
+ loader = runner.TestLoader()
+ suite = loader.loadDoctests(mockdoctest)
+ idPrefix = 'twisted.trial.test.mockdoctest.Counter'
+ for test in suite._tests:
+ self.assertIn(idPrefix, itrial.ITestCase(test).id())
+
+
+ def makeDocSuite(self, module):
+ """
+ Return a L{runner.DocTestSuite} for the doctests in C{module}.
+ """
+ return self.assertWarns(
+ DeprecationWarning, "DocTestSuite is deprecated in Twisted 8.0.",
+ __file__, lambda: runner.DocTestSuite(mockdoctest))
+
+
+ def test_correctCount(self):
+ """
+ L{countTestCases} returns the number of doctests in the module.
+ """
+ suite = self.makeDocSuite(mockdoctest)
+ self.assertEqual(7, suite.countTestCases())
+
+
+ def test_basicTrialIntegration(self):
+ """
+ L{loadDoctests} loads all of the doctests in the given module.
+ """
+ loader = runner.TestLoader()
+ suite = loader.loadDoctests(mockdoctest)
+ self.assertEqual(7, suite.countTestCases())
+
+
+ def _testRun(self, suite):
+ """
+ Run C{suite} and check the result.
+ """
+ result = reporter.TestResult()
+ suite.run(result)
+ self.assertEqual(5, result.successes)
+ # doctest reports failures as errors in 2.3
+ self.assertEqual(2, len(result.errors) + len(result.failures))
+
+
+ def test_expectedResults(self, count=1):
+ """
+ Trial can correctly run doctests with its xUnit test APIs.
+ """
+ suite = runner.TestLoader().loadDoctests(mockdoctest)
+ self._testRun(suite)
+
+
+ def test_repeatable(self):
+ """
+ Doctests should be runnable repeatably.
+ """
+ suite = runner.TestLoader().loadDoctests(mockdoctest)
+ self._testRun(suite)
+ self._testRun(suite)
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_keyboard.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_keyboard.py
new file mode 100644
index 0000000000..78f3f6539d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_keyboard.py
@@ -0,0 +1,113 @@
+import StringIO
+from twisted.trial import unittest
+from twisted.trial import reporter, runner
+
+
+class TrialTest(unittest.TestCase):
+ def setUp(self):
+ self.output = StringIO.StringIO()
+ self.reporter = reporter.TestResult()
+ self.loader = runner.TestLoader()
+
+
+class TestInterruptInTest(TrialTest):
+ class InterruptedTest(unittest.TestCase):
+ def test_02_raiseInterrupt(self):
+ raise KeyboardInterrupt
+
+ def test_01_doNothing(self):
+ pass
+
+ def test_03_doNothing(self):
+ TestInterruptInTest.test_03_doNothing_run = True
+
+ def setUp(self):
+ super(TestInterruptInTest, self).setUp()
+ self.suite = self.loader.loadClass(TestInterruptInTest.InterruptedTest)
+ TestInterruptInTest.test_03_doNothing_run = None
+
+ def test_setUpOK(self):
+ self.failUnlessEqual(3, self.suite.countTestCases())
+ self.failUnlessEqual(0, self.reporter.testsRun)
+ self.failIf(self.reporter.shouldStop)
+
+ def test_interruptInTest(self):
+ runner.TrialSuite([self.suite]).run(self.reporter)
+ self.failUnless(self.reporter.shouldStop)
+ self.failUnlessEqual(2, self.reporter.testsRun)
+ self.failIf(TestInterruptInTest.test_03_doNothing_run,
+ "test_03_doNothing ran.")
+
+
+class TestInterruptInSetUp(TrialTest):
+ testsRun = 0
+
+ class InterruptedTest(unittest.TestCase):
+ def setUp(self):
+ if TestInterruptInSetUp.testsRun > 0:
+ raise KeyboardInterrupt
+
+ def test_01(self):
+ TestInterruptInSetUp.testsRun += 1
+
+ def test_02(self):
+ TestInterruptInSetUp.testsRun += 1
+ TestInterruptInSetUp.test_02_run = True
+
+ def setUp(self):
+ super(TestInterruptInSetUp, self).setUp()
+ self.suite = self.loader.loadClass(
+ TestInterruptInSetUp.InterruptedTest)
+ TestInterruptInSetUp.test_02_run = False
+ TestInterruptInSetUp.testsRun = 0
+
+ def test_setUpOK(self):
+ self.failUnlessEqual(0, TestInterruptInSetUp.testsRun)
+ self.failUnlessEqual(2, self.suite.countTestCases())
+ self.failUnlessEqual(0, self.reporter.testsRun)
+ self.failIf(self.reporter.shouldStop)
+
+ def test_interruptInSetUp(self):
+ runner.TrialSuite([self.suite]).run(self.reporter)
+ self.failUnless(self.reporter.shouldStop)
+ self.failUnlessEqual(2, self.reporter.testsRun)
+ self.failIf(TestInterruptInSetUp.test_02_run,
+ "test_02 ran")
+
+
+class TestInterruptInTearDown(TrialTest):
+ testsRun = 0
+
+ class InterruptedTest(unittest.TestCase):
+ def tearDown(self):
+ if TestInterruptInTearDown.testsRun > 0:
+ raise KeyboardInterrupt
+
+ def test_01(self):
+ TestInterruptInTearDown.testsRun += 1
+
+ def test_02(self):
+ TestInterruptInTearDown.testsRun += 1
+ TestInterruptInTearDown.test_02_run = True
+
+ def setUp(self):
+ super(TestInterruptInTearDown, self).setUp()
+ self.suite = self.loader.loadClass(
+ TestInterruptInTearDown.InterruptedTest)
+ TestInterruptInTearDown.testsRun = 0
+ TestInterruptInTearDown.test_02_run = False
+
+ def test_setUpOK(self):
+ self.failUnlessEqual(0, TestInterruptInTearDown.testsRun)
+ self.failUnlessEqual(2, self.suite.countTestCases())
+ self.failUnlessEqual(0, self.reporter.testsRun)
+ self.failIf(self.reporter.shouldStop)
+
+ def test_interruptInTearDown(self):
+ runner.TrialSuite([self.suite]).run(self.reporter)
+ self.failUnlessEqual(1, self.reporter.testsRun)
+ self.failUnless(self.reporter.shouldStop)
+ self.failIf(TestInterruptInTearDown.test_02_run,
+ "test_02 ran")
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_loader.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_loader.py
new file mode 100644
index 0000000000..703d06b32c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_loader.py
@@ -0,0 +1,541 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for loading tests by name.
+"""
+
+import os
+import shutil
+import sys
+
+from twisted.python import util
+from twisted.python.hashlib import md5
+from twisted.trial.test import packages
+from twisted.trial import runner, reporter, unittest
+from twisted.trial.itrial import ITestCase
+
+from twisted.python.modules import getModule
+
+
+
+def testNames(tests):
+ """
+ Return the id of each test within the given test suite or case.
+ """
+ names = []
+ for test in unittest._iterateTests(tests):
+ names.append(test.id())
+ return names
+
+
+
+class FinderTest(packages.PackageTest):
+ def setUp(self):
+ packages.PackageTest.setUp(self)
+ self.loader = runner.TestLoader()
+
+ def tearDown(self):
+ packages.PackageTest.tearDown(self)
+
+ def test_findPackage(self):
+ sample1 = self.loader.findByName('twisted')
+ import twisted as sample2
+ self.failUnlessEqual(sample1, sample2)
+
+ def test_findModule(self):
+ sample1 = self.loader.findByName('twisted.trial.test.sample')
+ import sample as sample2
+ self.failUnlessEqual(sample1, sample2)
+
+ def test_findFile(self):
+ path = util.sibpath(__file__, 'sample.py')
+ sample1 = self.loader.findByName(path)
+ import sample as sample2
+ self.failUnlessEqual(sample1, sample2)
+
+ def test_findObject(self):
+ sample1 = self.loader.findByName('twisted.trial.test.sample.FooTest')
+ import sample
+ self.failUnlessEqual(sample.FooTest, sample1)
+
+ def test_findNonModule(self):
+ self.failUnlessRaises(AttributeError,
+ self.loader.findByName,
+ 'twisted.trial.test.nonexistent')
+
+ def test_findNonPackage(self):
+ self.failUnlessRaises(ValueError,
+ self.loader.findByName,
+ 'nonextant')
+
+ def test_findNonFile(self):
+ path = util.sibpath(__file__, 'nonexistent.py')
+ self.failUnlessRaises(ValueError, self.loader.findByName, path)
+
+
+
+class FileTest(packages.SysPathManglingTest):
+ """
+ Tests for L{runner.filenameToModule}.
+ """
+ def test_notFile(self):
+ self.failUnlessRaises(ValueError,
+ runner.filenameToModule, 'doesntexist')
+
+ def test_moduleInPath(self):
+ sample1 = runner.filenameToModule(util.sibpath(__file__, 'sample.py'))
+ import sample as sample2
+ self.failUnlessEqual(sample2, sample1)
+
+
+ def test_moduleNotInPath(self):
+ """
+ If passed the path to a file containing the implementation of a
+ module within a package which is not on the import path,
+ L{runner.filenameToModule} returns a module object loosely
+ resembling the module defined by that file anyway.
+ """
+ # "test_sample" isn't actually the name of this module. However,
+ # filenameToModule can't seem to figure that out. So clean up this
+ # mis-named module. It would be better if this weren't necessary
+ # and filenameToModule either didn't exist or added a correctly
+ # named module to sys.modules.
+ self.addCleanup(sys.modules.pop, 'test_sample', None)
+
+ self.mangleSysPath(self.oldPath)
+ sample1 = runner.filenameToModule(
+ os.path.join(self.parent, 'goodpackage', 'test_sample.py'))
+ self.mangleSysPath(self.newPath)
+ from goodpackage import test_sample as sample2
+ self.failUnlessEqual(os.path.splitext(sample2.__file__)[0],
+ os.path.splitext(sample1.__file__)[0])
+
+
+ def test_packageInPath(self):
+ package1 = runner.filenameToModule(os.path.join(self.parent,
+ 'goodpackage'))
+ import goodpackage
+ self.failUnlessEqual(goodpackage, package1)
+
+
+ def test_packageNotInPath(self):
+ """
+ If passed the path to a directory which represents a package which
+ is not on the import path, L{runner.filenameToModule} returns a
+ module object loosely resembling the package defined by that
+ directory anyway.
+ """
+ # "__init__" isn't actually the name of the package! However,
+ # filenameToModule is pretty stupid and decides that is its name
+ # after all. Make sure it gets cleaned up. See the comment in
+ # test_moduleNotInPath for possible courses of action related to
+ # this.
+ self.addCleanup(sys.modules.pop, "__init__")
+
+ self.mangleSysPath(self.oldPath)
+ package1 = runner.filenameToModule(
+ os.path.join(self.parent, 'goodpackage'))
+ self.mangleSysPath(self.newPath)
+ import goodpackage
+ self.failUnlessEqual(os.path.splitext(goodpackage.__file__)[0],
+ os.path.splitext(package1.__file__)[0])
+
+
+ def test_directoryNotPackage(self):
+ self.failUnlessRaises(ValueError, runner.filenameToModule,
+ util.sibpath(__file__, 'directory'))
+
+ def test_filenameNotPython(self):
+ self.failUnlessRaises(ValueError, runner.filenameToModule,
+ util.sibpath(__file__, 'notpython.py'))
+
+ def test_filenameMatchesPackage(self):
+ filename = os.path.join(self.parent, 'goodpackage.py')
+ fd = open(filename, 'w')
+ fd.write(packages.testModule)
+ fd.close()
+ try:
+ module = runner.filenameToModule(filename)
+ self.failUnlessEqual(filename, module.__file__)
+ finally:
+ os.remove(filename)
+
+ def test_directory(self):
+ """
+ Test loader against a filesystem directory. It should handle
+ 'path' and 'path/' the same way.
+ """
+ path = util.sibpath(__file__, 'goodDirectory')
+ os.mkdir(path)
+ f = file(os.path.join(path, '__init__.py'), "w")
+ f.close()
+ try:
+ module = runner.filenameToModule(path)
+ self.assert_(module.__name__.endswith('goodDirectory'))
+ module = runner.filenameToModule(path + os.path.sep)
+ self.assert_(module.__name__.endswith('goodDirectory'))
+ finally:
+ shutil.rmtree(path)
+
+
+
+class LoaderTest(packages.SysPathManglingTest):
+
+ def setUp(self):
+ self.loader = runner.TestLoader()
+ packages.SysPathManglingTest.setUp(self)
+
+ def test_sortCases(self):
+ import sample
+ suite = self.loader.loadClass(sample.AlphabetTest)
+ self.failUnlessEqual(['test_a', 'test_b', 'test_c'],
+ [test._testMethodName for test in suite._tests])
+ newOrder = ['test_b', 'test_c', 'test_a']
+ sortDict = dict(zip(newOrder, range(3)))
+ self.loader.sorter = lambda x : sortDict.get(x.shortDescription(), -1)
+ suite = self.loader.loadClass(sample.AlphabetTest)
+ self.failUnlessEqual(newOrder,
+ [test._testMethodName for test in suite._tests])
+
+ def test_loadMethod(self):
+ import sample
+ suite = self.loader.loadMethod(sample.FooTest.test_foo)
+ self.failUnlessEqual(1, suite.countTestCases())
+ self.failUnlessEqual('test_foo', suite._testMethodName)
+
+ def test_loadFailingMethod(self):
+ # test added for issue1353
+ import erroneous
+ suite = self.loader.loadMethod(erroneous.TestRegularFail.test_fail)
+ result = reporter.TestResult()
+ suite.run(result)
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failUnlessEqual(len(result.failures), 1)
+
+ def test_loadNonMethod(self):
+ import sample
+ self.failUnlessRaises(TypeError, self.loader.loadMethod, sample)
+ self.failUnlessRaises(TypeError,
+ self.loader.loadMethod, sample.FooTest)
+ self.failUnlessRaises(TypeError, self.loader.loadMethod, "string")
+ self.failUnlessRaises(TypeError,
+ self.loader.loadMethod, ('foo', 'bar'))
+
+ def test_loadClass(self):
+ import sample
+ suite = self.loader.loadClass(sample.FooTest)
+ self.failUnlessEqual(2, suite.countTestCases())
+ self.failUnlessEqual(['test_bar', 'test_foo'],
+ [test._testMethodName for test in suite._tests])
+
+
+ def test_loadNonClass(self):
+ import sample
+ self.failUnlessRaises(TypeError, self.loader.loadClass, sample)
+ self.failUnlessRaises(TypeError,
+ self.loader.loadClass, sample.FooTest.test_foo)
+ self.failUnlessRaises(TypeError, self.loader.loadClass, "string")
+ self.failUnlessRaises(TypeError,
+ self.loader.loadClass, ('foo', 'bar'))
+
+ def test_loadNonTestCase(self):
+ import sample
+ self.failUnlessRaises(ValueError, self.loader.loadClass,
+ sample.NotATest)
+
+ def test_loadModule(self):
+ import sample
+ suite = self.loader.loadModule(sample)
+ self.failUnlessEqual(7, suite.countTestCases())
+
+ def test_loadNonModule(self):
+ import sample
+ self.failUnlessRaises(TypeError,
+ self.loader.loadModule, sample.FooTest)
+ self.failUnlessRaises(TypeError,
+ self.loader.loadModule, sample.FooTest.test_foo)
+ self.failUnlessRaises(TypeError, self.loader.loadModule, "string")
+ self.failUnlessRaises(TypeError,
+ self.loader.loadModule, ('foo', 'bar'))
+
+ def test_loadPackage(self):
+ import goodpackage
+ suite = self.loader.loadPackage(goodpackage)
+ self.failUnlessEqual(7, suite.countTestCases())
+
+ def test_loadNonPackage(self):
+ import sample
+ self.failUnlessRaises(TypeError,
+ self.loader.loadPackage, sample.FooTest)
+ self.failUnlessRaises(TypeError,
+ self.loader.loadPackage, sample.FooTest.test_foo)
+ self.failUnlessRaises(TypeError, self.loader.loadPackage, "string")
+ self.failUnlessRaises(TypeError,
+ self.loader.loadPackage, ('foo', 'bar'))
+
+ def test_loadModuleAsPackage(self):
+ import sample
+ ## XXX -- should this instead raise a ValueError? -- jml
+ self.failUnlessRaises(TypeError, self.loader.loadPackage, sample)
+
+ def test_loadPackageRecursive(self):
+ import goodpackage
+ suite = self.loader.loadPackage(goodpackage, recurse=True)
+ self.failUnlessEqual(14, suite.countTestCases())
+
+ def test_loadAnythingOnModule(self):
+ import sample
+ suite = self.loader.loadAnything(sample)
+ self.failUnlessEqual(sample.__name__,
+ suite._tests[0]._tests[0].__class__.__module__)
+
+ def test_loadAnythingOnClass(self):
+ import sample
+ suite = self.loader.loadAnything(sample.FooTest)
+ self.failUnlessEqual(2, suite.countTestCases())
+
+ def test_loadAnythingOnMethod(self):
+ import sample
+ suite = self.loader.loadAnything(sample.FooTest.test_foo)
+ self.failUnlessEqual(1, suite.countTestCases())
+
+ def test_loadAnythingOnPackage(self):
+ import goodpackage
+ suite = self.loader.loadAnything(goodpackage)
+ self.failUnless(isinstance(suite, self.loader.suiteFactory))
+ self.failUnlessEqual(7, suite.countTestCases())
+
+ def test_loadAnythingOnPackageRecursive(self):
+ import goodpackage
+ suite = self.loader.loadAnything(goodpackage, recurse=True)
+ self.failUnless(isinstance(suite, self.loader.suiteFactory))
+ self.failUnlessEqual(14, suite.countTestCases())
+
+ def test_loadAnythingOnString(self):
+ # the important thing about this test is not the string-iness
+ # but the non-handledness.
+ self.failUnlessRaises(TypeError,
+ self.loader.loadAnything, "goodpackage")
+
+ def test_importErrors(self):
+ import package
+ suite = self.loader.loadPackage(package, recurse=True)
+ result = reporter.Reporter()
+ suite.run(result)
+ self.failUnlessEqual(False, result.wasSuccessful())
+ self.failUnlessEqual(2, len(result.errors))
+ errors = [test.id() for test, error in result.errors]
+ errors.sort()
+ self.failUnlessEqual(errors, ['package.test_bad_module',
+ 'package.test_import_module'])
+
+
+ def test_differentInstances(self):
+ """
+ L{TestLoader.loadClass} returns a suite with each test method
+ represented by a different instances of the L{TestCase} they are
+ defined on.
+ """
+ class DistinctInstances(unittest.TestCase):
+ def test_1(self):
+ self.first = 'test1Run'
+
+ def test_2(self):
+ self.assertFalse(hasattr(self, 'first'))
+
+ suite = self.loader.loadClass(DistinctInstances)
+ result = reporter.Reporter()
+ suite.run(result)
+ self.assertTrue(result.wasSuccessful())
+
+
+ def test_loadModuleWith_test_suite(self):
+ """
+ Check that C{test_suite} is used when present and other L{TestCase}s are
+ not included.
+ """
+ from twisted.trial.test import mockcustomsuite
+ suite = self.loader.loadModule(mockcustomsuite)
+ self.failUnlessEqual(0, suite.countTestCases())
+ self.failUnlessEqual("MyCustomSuite", getattr(suite, 'name', None))
+
+
+ def test_loadModuleWith_testSuite(self):
+ """
+ Check that C{testSuite} is used when present and other L{TestCase}s are
+ not included.
+ """
+ from twisted.trial.test import mockcustomsuite2
+ suite = self.loader.loadModule(mockcustomsuite2)
+ self.assertEqual(0, suite.countTestCases())
+ self.assertEqual("MyCustomSuite", getattr(suite, 'name', None))
+
+
+ def test_loadModuleWithBothCustom(self):
+ """
+ Check that if C{testSuite} and C{test_suite} are both present in a
+ module then C{testSuite} gets priority.
+ """
+ from twisted.trial.test import mockcustomsuite3
+ suite = self.loader.loadModule(mockcustomsuite3)
+ self.assertEqual('testSuite', getattr(suite, 'name', None))
+
+
+ def test_customLoadRaisesAttributeError(self):
+ """
+ Make sure that any C{AttributeError}s raised by C{testSuite} are not
+ swallowed by L{TestLoader}.
+ """
+ def testSuite():
+ raise AttributeError('should be reraised')
+ from twisted.trial.test import mockcustomsuite2
+ mockcustomsuite2.testSuite, original = (testSuite,
+ mockcustomsuite2.testSuite)
+ try:
+ self.assertRaises(AttributeError, self.loader.loadModule,
+ mockcustomsuite2)
+ finally:
+ mockcustomsuite2.testSuite = original
+
+
+ # XXX - duplicated and modified from test_script
+ def assertSuitesEqual(self, test1, test2):
+ names1 = testNames(test1)
+ names2 = testNames(test2)
+ names1.sort()
+ names2.sort()
+ self.assertEqual(names1, names2)
+
+ def test_loadByNamesDuplicate(self):
+ """
+ Check that loadByNames ignores duplicate names
+ """
+ module = 'twisted.trial.test.test_test_visitor'
+ suite1 = self.loader.loadByNames([module, module], True)
+ suite2 = self.loader.loadByName(module, True)
+ self.assertSuitesEqual(suite1, suite2)
+
+ def test_loadDifferentNames(self):
+ """
+ Check that loadByNames loads all the names that it is given
+ """
+ modules = ['goodpackage', 'package.test_module']
+ suite1 = self.loader.loadByNames(modules)
+ suite2 = runner.TestSuite(map(self.loader.loadByName, modules))
+ self.assertSuitesEqual(suite1, suite2)
+
+
+
+class ZipLoadingTest(LoaderTest):
+ def setUp(self):
+ from twisted.test.test_paths import zipit
+ LoaderTest.setUp(self)
+ zipit(self.parent, self.parent+'.zip')
+ self.parent += '.zip'
+ self.mangleSysPath(self.oldPath+[self.parent])
+
+
+
+class PackageOrderingTest(packages.SysPathManglingTest):
+ if sys.version_info < (2, 4):
+ skip = (
+ "Python 2.3 import semantics make this behavior incorrect on that "
+ "version of Python as well as difficult to test. The second "
+ "import of a package which raised an exception the first time it "
+ "was imported will succeed on Python 2.3, whereas it will fail on "
+ "later versions of Python. Trial does not account for this, so "
+ "this test fails with inconsistencies between the expected and "
+ "the received loader errors.")
+
+ def setUp(self):
+ self.loader = runner.TestLoader()
+ self.topDir = self.mktemp()
+ parent = os.path.join(self.topDir, "uberpackage")
+ os.makedirs(parent)
+ file(os.path.join(parent, "__init__.py"), "wb").close()
+ packages.SysPathManglingTest.setUp(self, parent)
+ self.mangleSysPath(self.oldPath + [self.topDir])
+
+ def _trialSortAlgorithm(self, sorter):
+ """
+ Right now, halfway by accident, trial sorts like this:
+
+ 1. all modules are grouped together in one list and sorted.
+
+ 2. within each module, the classes are grouped together in one list
+ and sorted.
+
+ 3. finally within each class, each test method is grouped together
+ in a list and sorted.
+
+ This attempts to return a sorted list of testable thingies following
+ those rules, so that we can compare the behavior of loadPackage.
+
+ The things that show as 'cases' are errors from modules which failed to
+ import, and test methods. Let's gather all those together.
+ """
+ pkg = getModule('uberpackage')
+ testModules = []
+ for testModule in pkg.walkModules():
+ if testModule.name.split(".")[-1].startswith("test_"):
+ testModules.append(testModule)
+ sortedModules = util.dsu(testModules, sorter) # ONE
+ for modinfo in sortedModules:
+ # Now let's find all the classes.
+ module = modinfo.load(None)
+ if module is None:
+ yield modinfo
+ else:
+ testClasses = []
+ for attrib in modinfo.iterAttributes():
+ if runner.isTestCase(attrib.load()):
+ testClasses.append(attrib)
+ sortedClasses = util.dsu(testClasses, sorter) # TWO
+ for clsinfo in sortedClasses:
+ testMethods = []
+ for attr in clsinfo.iterAttributes():
+ if attr.name.split(".")[-1].startswith('test'):
+ testMethods.append(attr)
+ sortedMethods = util.dsu(testMethods, sorter) # THREE
+ for methinfo in sortedMethods:
+ yield methinfo
+
+
+ def loadSortedPackages(self, sorter=runner.name):
+ """
+ Verify that packages are loaded in the correct order.
+ """
+ import uberpackage
+ self.loader.sorter = sorter
+ suite = self.loader.loadPackage(uberpackage, recurse=True)
+ # XXX: Work around strange, unexplained Zope crap.
+ # jml, 2007-11-15.
+ suite = unittest.decorate(suite, ITestCase)
+ resultingTests = list(unittest._iterateTests(suite))
+ manifest = list(self._trialSortAlgorithm(sorter))
+ for number, (manifestTest, actualTest) in enumerate(
+ zip(manifest, resultingTests)):
+ self.assertEqual(
+ manifestTest.name, actualTest.id(),
+ "#%d: %s != %s" %
+ (number, manifestTest.name, actualTest.id()))
+ self.assertEqual(len(manifest), len(resultingTests))
+
+
+ def test_sortPackagesDefaultOrder(self):
+ self.loadSortedPackages()
+
+
+ def test_sortPackagesSillyOrder(self):
+ def sillySorter(s):
+ # This has to work on fully-qualified class names and class
+ # objects, which is silly, but it's the "spec", such as it is.
+# if isinstance(s, type) or isinstance(s, types.ClassType):
+# return s.__module__+'.'+s.__name__
+ n = runner.name(s)
+ d = md5(n).hexdigest()
+ return d
+ self.loadSortedPackages(sillySorter)
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_log.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_log.py
new file mode 100644
index 0000000000..bce2eef5a6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_log.py
@@ -0,0 +1,197 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test the interaction between trial and errors logged during test run.
+"""
+
+import time
+
+from twisted.internet import reactor, task
+from twisted.python import failure, log
+from twisted.trial import unittest, reporter
+
+
+def makeFailure():
+ """
+ Return a new, realistic failure.
+ """
+ try:
+ 1/0
+ except ZeroDivisionError:
+ f = failure.Failure()
+ return f
+
+
+class Mask(object):
+ """
+ Hide C{MockTest}s from Trial's automatic test finder.
+ """
+
+ class MockTest(unittest.TestCase):
+ def test_silent(self):
+ """
+ Don't log any errors.
+ """
+
+ def test_single(self):
+ """
+ Log a single error.
+ """
+ log.err(makeFailure())
+
+ def test_double(self):
+ """
+ Log two errors.
+ """
+ log.err(makeFailure())
+ log.err(makeFailure())
+
+ def test_inCallback(self):
+ """
+ Log an error in an asynchronous callback.
+ """
+ return task.deferLater(reactor, 0, lambda: log.err(makeFailure()))
+
+
+class TestObserver(unittest.TestCase):
+ """
+ Tests for L{unittest._LogObserver}, a helper for the implementation of
+ L{TestCase.flushLoggedErrors}.
+ """
+ def setUp(self):
+ self.result = reporter.TestResult()
+ self.observer = unittest._LogObserver()
+
+
+ def test_msg(self):
+ """
+ Test that a standard log message doesn't go anywhere near the result.
+ """
+ self.observer.gotEvent({'message': ('some message',),
+ 'time': time.time(), 'isError': 0,
+ 'system': '-'})
+ self.assertEqual(self.observer.getErrors(), [])
+
+
+ def test_error(self):
+ """
+ Test that an observed error gets added to the result
+ """
+ f = makeFailure()
+ self.observer.gotEvent({'message': (),
+ 'time': time.time(), 'isError': 1,
+ 'system': '-', 'failure': f,
+ 'why': None})
+ self.assertEqual(self.observer.getErrors(), [f])
+
+
+ def test_flush(self):
+ """
+ Check that flushing the observer with no args removes all errors.
+ """
+ self.test_error()
+ flushed = self.observer.flushErrors()
+ self.assertEqual(self.observer.getErrors(), [])
+ self.assertEqual(len(flushed), 1)
+ self.assertTrue(flushed[0].check(ZeroDivisionError))
+
+
+ def _makeRuntimeFailure(self):
+ return failure.Failure(RuntimeError('test error'))
+
+
+ def test_flushByType(self):
+ """
+ Check that flushing the observer remove all failures of the given type.
+ """
+ self.test_error() # log a ZeroDivisionError to the observer
+ f = self._makeRuntimeFailure()
+ self.observer.gotEvent(dict(message=(), time=time.time(), isError=1,
+ system='-', failure=f, why=None))
+ flushed = self.observer.flushErrors(ZeroDivisionError)
+ self.assertEqual(self.observer.getErrors(), [f])
+ self.assertEqual(len(flushed), 1)
+ self.assertTrue(flushed[0].check(ZeroDivisionError))
+
+
+ def test_ignoreErrors(self):
+ """
+ Check that C{_ignoreErrors} actually causes errors to be ignored.
+ """
+ self.observer._ignoreErrors(ZeroDivisionError)
+ f = makeFailure()
+ self.observer.gotEvent({'message': (),
+ 'time': time.time(), 'isError': 1,
+ 'system': '-', 'failure': f,
+ 'why': None})
+ self.assertEqual(self.observer.getErrors(), [])
+
+
+ def test_clearIgnores(self):
+ """
+ Check that C{_clearIgnores} ensures that previously ignored errors
+ get captured.
+ """
+ self.observer._ignoreErrors(ZeroDivisionError)
+ self.observer._clearIgnores()
+ f = makeFailure()
+ self.observer.gotEvent({'message': (),
+ 'time': time.time(), 'isError': 1,
+ 'system': '-', 'failure': f,
+ 'why': None})
+ self.assertEqual(self.observer.getErrors(), [f])
+
+
+
+class LogErrors(unittest.TestCase):
+ """
+ High-level tests demonstrating the expected behaviour of logged errors
+ during tests.
+ """
+
+ def setUp(self):
+ self.result = reporter.TestResult()
+
+ def tearDown(self):
+ self.flushLoggedErrors(ZeroDivisionError)
+
+ def test_singleError(self):
+ """
+ Test that a logged error gets reported as a test error.
+ """
+ test = Mask.MockTest('test_single')
+ test(self.result)
+ self.assertEqual(len(self.result.errors), 1)
+ self.assertTrue(self.result.errors[0][1].check(ZeroDivisionError),
+ self.result.errors[0][1])
+
+ def test_twoErrors(self):
+ """
+ Test that when two errors get logged, they both get reported as test
+ errors.
+ """
+ test = Mask.MockTest('test_double')
+ test(self.result)
+ self.assertEqual(len(self.result.errors), 2)
+
+ def test_inCallback(self):
+ """
+ Test that errors logged in callbacks get reported as test errors.
+ """
+ test = Mask.MockTest('test_inCallback')
+ test(self.result)
+ self.assertEqual(len(self.result.errors), 1)
+ self.assertTrue(self.result.errors[0][1].check(ZeroDivisionError),
+ self.result.errors[0][1])
+
+ def test_errorsIsolated(self):
+ """
+ Check that an error logged in one test doesn't fail the next test.
+ """
+ t1 = Mask.MockTest('test_single')
+ t2 = Mask.MockTest('test_silent')
+ t1(self.result)
+ t2(self.result)
+ self.assertEqual(len(self.result.errors), 1)
+ self.assertEqual(self.result.errors[0][0], t1)
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_output.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_output.py
new file mode 100644
index 0000000000..2e817aa5d8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_output.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for the output generated by trial.
+"""
+
+import os, StringIO
+
+from twisted.scripts import trial
+from twisted.trial import runner
+from twisted.trial.test import packages
+
+
+def runTrial(*args):
+ from twisted.trial import reporter
+ config = trial.Options()
+ config.parseOptions(args)
+ output = StringIO.StringIO()
+ myRunner = runner.TrialRunner(
+ reporter.VerboseTextReporter,
+ stream=output,
+ workingDirectory=config['temp-directory'])
+ suite = trial._getSuite(config)
+ result = myRunner.run(suite)
+ return output.getvalue()
+
+
+class TestImportErrors(packages.SysPathManglingTest):
+ """Actually run trial as if on the command line and check that the output
+ is what we expect.
+ """
+
+ debug = False
+ parent = "_testImportErrors"
+ def runTrial(self, *args):
+ return runTrial('--temp-directory', self.mktemp(), *args)
+
+ def _print(self, stuff):
+ print stuff
+ return stuff
+
+ def failUnlessIn(self, container, containee, *args, **kwargs):
+ # redefined to be useful in callbacks
+ super(TestImportErrors, self).failUnlessIn(
+ containee, container, *args, **kwargs)
+ return container
+
+ def failIfIn(self, container, containee, *args, **kwargs):
+ # redefined to be useful in callbacks
+ super(TestImportErrors, self).failIfIn(
+ containee, container, *args, **kwargs)
+ return container
+
+ def test_trialRun(self):
+ self.runTrial()
+
+ def test_nonexistentModule(self):
+ d = self.runTrial('twisted.doesntexist')
+ self.failUnlessIn(d, '[ERROR]')
+ self.failUnlessIn(d, 'twisted.doesntexist')
+ return d
+
+ def test_nonexistentPackage(self):
+ d = self.runTrial('doesntexist')
+ self.failUnlessIn(d, 'doesntexist')
+ self.failUnlessIn(d, 'ModuleNotFound')
+ self.failUnlessIn(d, '[ERROR]')
+ return d
+
+ def test_nonexistentPackageWithModule(self):
+ d = self.runTrial('doesntexist.barney')
+ self.failUnlessIn(d, 'doesntexist.barney')
+ self.failUnlessIn(d, 'ObjectNotFound')
+ self.failUnlessIn(d, '[ERROR]')
+ return d
+
+ def test_badpackage(self):
+ d = self.runTrial('badpackage')
+ self.failUnlessIn(d, '[ERROR]')
+ self.failUnlessIn(d, 'badpackage')
+ self.failIfIn(d, 'IOError')
+ return d
+
+ def test_moduleInBadpackage(self):
+ d = self.runTrial('badpackage.test_module')
+ self.failUnlessIn(d, "[ERROR]")
+ self.failUnlessIn(d, "badpackage.test_module")
+ self.failIfIn(d, 'IOError')
+ return d
+
+ def test_badmodule(self):
+ d = self.runTrial('package.test_bad_module')
+ self.failUnlessIn(d, '[ERROR]')
+ self.failUnlessIn(d, 'package.test_bad_module')
+ self.failIfIn(d, 'IOError')
+ self.failIfIn(d, '<module ')
+ return d
+
+ def test_badimport(self):
+ d = self.runTrial('package.test_import_module')
+ self.failUnlessIn(d, '[ERROR]')
+ self.failUnlessIn(d, 'package.test_import_module')
+ self.failIfIn(d, 'IOError')
+ self.failIfIn(d, '<module ')
+ return d
+
+ def test_recurseImport(self):
+ d = self.runTrial('package')
+ self.failUnlessIn(d, '[ERROR]')
+ self.failUnlessIn(d, 'test_bad_module')
+ self.failUnlessIn(d, 'test_import_module')
+ self.failIfIn(d, '<module ')
+ self.failIfIn(d, 'IOError')
+ return d
+
+ def test_recurseImportErrors(self):
+ d = self.runTrial('package2')
+ self.failUnlessIn(d, '[ERROR]')
+ self.failUnlessIn(d, 'package2')
+ self.failUnlessIn(d, 'test_module')
+ self.failUnlessIn(d, "No module named frotz")
+ self.failIfIn(d, '<module ')
+ self.failIfIn(d, 'IOError')
+ return d
+
+ def test_nonRecurseImportErrors(self):
+ d = self.runTrial('-N', 'package2')
+ self.failUnlessIn(d, '[ERROR]')
+ self.failUnlessIn(d, "No module named frotz")
+ self.failIfIn(d, '<module ')
+ return d
+
+ def test_regularRun(self):
+ d = self.runTrial('package.test_module')
+ self.failIfIn(d, '[ERROR]')
+ self.failIfIn(d, 'IOError')
+ self.failUnlessIn(d, 'OK')
+ self.failUnlessIn(d, 'PASSED (successes=1)')
+ return d
+
+ def test_filename(self):
+ self.mangleSysPath(self.oldPath)
+ d = self.runTrial(
+ os.path.join(self.parent, 'package', 'test_module.py'))
+ self.failIfIn(d, '[ERROR]')
+ self.failIfIn(d, 'IOError')
+ self.failUnlessIn(d, 'OK')
+ self.failUnlessIn(d, 'PASSED (successes=1)')
+ return d
+
+ def test_dosFile(self):
+ ## XXX -- not really an output test, more of a script test
+ self.mangleSysPath(self.oldPath)
+ d = self.runTrial(
+ os.path.join(self.parent,
+ 'package', 'test_dos_module.py'))
+ self.failIfIn(d, '[ERROR]')
+ self.failIfIn(d, 'IOError')
+ self.failUnlessIn(d, 'OK')
+ self.failUnlessIn(d, 'PASSED (successes=1)')
+ return d
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_plugins.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_plugins.py
new file mode 100644
index 0000000000..fff3cdd5e8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_plugins.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+#
+# Maintainer: Jonathan Lange
+
+"""
+Tests for L{twisted.plugins.twisted_trial}.
+"""
+
+from twisted.plugin import getPlugins
+from twisted.trial import unittest
+from twisted.trial.itrial import IReporter
+
+
+class TestPlugins(unittest.TestCase):
+ """
+ Tests for Trial's reporter plugins.
+ """
+
+ def getPluginsByLongOption(self, longOption):
+ """
+ Return the Trial reporter plugin with the given long option.
+
+ If more than one is found, raise ValueError. If none are found, raise
+ IndexError.
+ """
+ plugins = [
+ plugin for plugin in getPlugins(IReporter)
+ if plugin.longOpt == longOption]
+ if len(plugins) > 1:
+ raise ValueError(
+ "More than one plugin found with long option %r: %r"
+ % (longOption, plugins))
+ return plugins[0]
+
+
+ def test_subunitPlugin(self):
+ """
+ One of the reporter plugins is the subunit reporter plugin.
+ """
+ subunitPlugin = self.getPluginsByLongOption('subunit')
+ self.assertEquals('Subunit Reporter', subunitPlugin.name)
+ self.assertEquals('twisted.trial.reporter', subunitPlugin.module)
+ self.assertEquals('subunit', subunitPlugin.longOpt)
+ self.assertIdentical(None, subunitPlugin.shortOpt)
+ self.assertEquals('SubunitReporter', subunitPlugin.klass)
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_pyunitcompat.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_pyunitcompat.py
new file mode 100644
index 0000000000..ff4820d683
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_pyunitcompat.py
@@ -0,0 +1,222 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+#
+# Maintainer: Jonathan Lange
+
+
+import sys
+import traceback
+
+from zope.interface import implements
+
+from twisted.python import reflect
+from twisted.python.failure import Failure
+from twisted.trial import util
+from twisted.trial.unittest import TestCase, PyUnitResultAdapter
+from twisted.trial.itrial import IReporter, ITestCase
+from twisted.trial.test import erroneous
+
+pyunit = __import__('unittest')
+
+
+class TestPyUnitTestCase(TestCase):
+
+ class PyUnitTest(pyunit.TestCase):
+
+ def test_pass(self):
+ pass
+
+
+ def setUp(self):
+ self.original = self.PyUnitTest('test_pass')
+ self.test = ITestCase(self.original)
+
+
+ def test_visit(self):
+ """
+ Trial assumes that test cases implement visit().
+ """
+ log = []
+ def visitor(test):
+ log.append(test)
+ self.test.visit(visitor)
+ self.assertEqual(log, [self.test])
+ test_visit.suppress = [
+ util.suppress(category=DeprecationWarning,
+ message="Test visitors deprecated in Twisted 8.0")]
+
+
+ def test_callable(self):
+ """
+ Tests must be callable in order to be used with Python's unittest.py.
+ """
+ self.assertTrue(callable(self.test),
+ "%r is not callable." % (self.test,))
+
+
+class TestPyUnitResult(TestCase):
+ """
+ Tests to show that PyUnitResultAdapter wraps TestResult objects from the
+ standard library 'unittest' module in such a way as to make them usable and
+ useful from Trial.
+ """
+
+ def test_dontUseAdapterWhenReporterProvidesIReporter(self):
+ """
+ The L{PyUnitResultAdapter} is only used when the result passed to
+ C{run} does *not* provide L{IReporter}.
+ """
+ class StubReporter(object):
+ """
+ A reporter which records data about calls made to it.
+
+ @ivar errors: Errors passed to L{addError}.
+ @ivar failures: Failures passed to L{addFailure}.
+ """
+
+ implements(IReporter)
+
+ def __init__(self):
+ self.errors = []
+ self.failures = []
+
+ def startTest(self, test):
+ """
+ Do nothing.
+ """
+
+ def stopTest(self, test):
+ """
+ Do nothing.
+ """
+
+ def addError(self, test, error):
+ """
+ Record the error.
+ """
+ self.errors.append(error)
+
+ test = erroneous.ErrorTest("test_foo")
+ result = StubReporter()
+ test.run(result)
+ self.assertIsInstance(result.errors[0], Failure)
+
+
+ def test_success(self):
+ class SuccessTest(TestCase):
+ ran = False
+ def test_foo(s):
+ s.ran = True
+ test = SuccessTest('test_foo')
+ result = pyunit.TestResult()
+ test.run(result)
+
+ self.failUnless(test.ran)
+ self.assertEqual(1, result.testsRun)
+ self.failUnless(result.wasSuccessful())
+
+ def test_failure(self):
+ class FailureTest(TestCase):
+ ran = False
+ def test_foo(s):
+ s.ran = True
+ s.fail('boom!')
+ test = FailureTest('test_foo')
+ result = pyunit.TestResult()
+ test.run(result)
+
+ self.failUnless(test.ran)
+ self.assertEqual(1, result.testsRun)
+ self.assertEqual(1, len(result.failures))
+ self.failIf(result.wasSuccessful())
+
+ def test_error(self):
+ test = erroneous.ErrorTest('test_foo')
+ result = pyunit.TestResult()
+ test.run(result)
+
+ self.failUnless(test.ran)
+ self.assertEqual(1, result.testsRun)
+ self.assertEqual(1, len(result.errors))
+ self.failIf(result.wasSuccessful())
+
+ def test_setUpError(self):
+ class ErrorTest(TestCase):
+ ran = False
+ def setUp(self):
+ 1/0
+ def test_foo(s):
+ s.ran = True
+ test = ErrorTest('test_foo')
+ result = pyunit.TestResult()
+ test.run(result)
+
+ self.failIf(test.ran)
+ self.assertEqual(1, result.testsRun)
+ self.assertEqual(1, len(result.errors))
+ self.failIf(result.wasSuccessful())
+
+ def test_tracebackFromFailure(self):
+ """
+ Errors added through the L{PyUnitResultAdapter} have the same traceback
+ information as if there were no adapter at all.
+ """
+ try:
+ 1/0
+ except ZeroDivisionError:
+ exc_info = sys.exc_info()
+ f = Failure()
+ pyresult = pyunit.TestResult()
+ result = PyUnitResultAdapter(pyresult)
+ result.addError(self, f)
+ self.assertEqual(pyresult.errors[0][1],
+ ''.join(traceback.format_exception(*exc_info)))
+
+
+ def test_traceback(self):
+ """
+ As test_tracebackFromFailure, but covering more code.
+ """
+ class ErrorTest(TestCase):
+ exc_info = None
+ def test_foo(self):
+ try:
+ 1/0
+ except ZeroDivisionError:
+ self.exc_info = sys.exc_info()
+ raise
+ test = ErrorTest('test_foo')
+ result = pyunit.TestResult()
+ test.run(result)
+
+ # We can't test that the tracebacks are equal, because Trial's
+ # machinery inserts a few extra frames on the top and we don't really
+ # want to trim them off without an extremely good reason.
+ #
+ # So, we just test that the result's stack ends with the the
+ # exception's stack.
+
+ expected_stack = ''.join(traceback.format_tb(test.exc_info[2]))
+ observed_stack = '\n'.join(result.errors[0][1].splitlines()[:-1])
+
+ self.assertEqual(expected_stack.strip(),
+ observed_stack[-len(expected_stack):].strip())
+
+
+ def test_tracebackFromCleanFailure(self):
+ """
+ Errors added through the L{PyUnitResultAdapter} have the same
+ traceback information as if there were no adapter at all, even
+ if the Failure that held the information has been cleaned.
+ """
+ try:
+ 1/0
+ except ZeroDivisionError:
+ exc_info = sys.exc_info()
+ f = Failure()
+ f.cleanFailure()
+ pyresult = pyunit.TestResult()
+ result = PyUnitResultAdapter(pyresult)
+ result.addError(self, f)
+ self.assertEqual(pyresult.errors[0][1],
+ ''.join(traceback.format_exception(*exc_info)))
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_reporter.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_reporter.py
new file mode 100644
index 0000000000..52fcff2956
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_reporter.py
@@ -0,0 +1,1561 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+#
+# Maintainer: Jonathan Lange
+
+"""
+Tests for L{twisted.trial.reporter}.
+"""
+
+
+import errno, sys, os, re, StringIO
+
+from twisted.internet.utils import suppressWarnings
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.trial import itrial, unittest, runner, reporter, util
+from twisted.trial.reporter import UncleanWarningsReporterWrapper
+from twisted.trial.test import erroneous
+from twisted.trial.unittest import makeTodo, SkipTest, Todo
+
+
+class BrokenStream(object):
+ """
+ Stream-ish object that raises a signal interrupt error. We use this to make
+ sure that Trial still manages to write what it needs to write.
+ """
+ written = False
+ flushed = False
+
+ def __init__(self, fObj):
+ self.fObj = fObj
+
+ def write(self, s):
+ if self.written:
+ return self.fObj.write(s)
+ self.written = True
+ raise IOError(errno.EINTR, "Interrupted write")
+
+ def flush(self):
+ if self.flushed:
+ return self.fObj.flush()
+ self.flushed = True
+ raise IOError(errno.EINTR, "Interrupted flush")
+
+
+class StringTest(unittest.TestCase):
+ def stringComparison(self, expect, output):
+ output = filter(None, output)
+ self.failUnless(len(expect) <= len(output),
+ "Must have more observed than expected"
+ "lines %d < %d" % (len(output), len(expect)))
+ REGEX_PATTERN_TYPE = type(re.compile(''))
+ for line_number, (exp, out) in enumerate(zip(expect, output)):
+ if exp is None:
+ continue
+ elif isinstance(exp, str):
+ self.assertSubstring(exp, out, "Line %d: %r not in %r"
+ % (line_number, exp, out))
+ elif isinstance(exp, REGEX_PATTERN_TYPE):
+ self.failUnless(exp.match(out),
+ "Line %d: %r did not match string %r"
+ % (line_number, exp.pattern, out))
+ else:
+ raise TypeError("don't know what to do with object %r"
+ % (exp,))
+
+
+class TestTestResult(unittest.TestCase):
+ def setUp(self):
+ self.result = reporter.TestResult()
+
+ def test_pyunitAddError(self):
+ # pyunit passes an exc_info tuple directly to addError
+ try:
+ raise RuntimeError('foo')
+ except RuntimeError, excValue:
+ self.result.addError(self, sys.exc_info())
+ failure = self.result.errors[0][1]
+ self.assertEqual(excValue, failure.value)
+ self.assertEqual(RuntimeError, failure.type)
+
+ def test_pyunitAddFailure(self):
+ # pyunit passes an exc_info tuple directly to addFailure
+ try:
+ raise self.failureException('foo')
+ except self.failureException, excValue:
+ self.result.addFailure(self, sys.exc_info())
+ failure = self.result.failures[0][1]
+ self.assertEqual(excValue, failure.value)
+ self.assertEqual(self.failureException, failure.type)
+
+
+class TestReporterRealtime(TestTestResult):
+ def setUp(self):
+ output = StringIO.StringIO()
+ self.result = reporter.Reporter(output, realtime=True)
+
+
+class TestErrorReporting(StringTest):
+ doubleSeparator = re.compile(r'^=+$')
+
+ def setUp(self):
+ self.loader = runner.TestLoader()
+ self.output = StringIO.StringIO()
+ self.result = reporter.Reporter(self.output)
+
+ def getOutput(self, suite):
+ result = self.getResult(suite)
+ result.done()
+ return self.output.getvalue()
+
+ def getResult(self, suite):
+ suite.run(self.result)
+ return self.result
+
+ def testFormatErroredMethod(self):
+ suite = self.loader.loadClass(erroneous.TestFailureInSetUp)
+ output = self.getOutput(suite).splitlines()
+ match = [self.doubleSeparator,
+ ('[ERROR]: twisted.trial.test.erroneous.'
+ 'TestFailureInSetUp.test_noop'),
+ 'Traceback (most recent call last):',
+ re.compile(r'^\s+File .*erroneous\.py., line \d+, in setUp$'),
+ re.compile(r'^\s+raise FoolishError, '
+ r'.I am a broken setUp method.$'),
+ ('twisted.trial.test.erroneous.FoolishError: '
+ 'I am a broken setUp method')]
+ self.stringComparison(match, output)
+
+ def testFormatFailedMethod(self):
+ suite = self.loader.loadMethod(erroneous.TestRegularFail.test_fail)
+ output = self.getOutput(suite).splitlines()
+ match = [
+ self.doubleSeparator,
+ '[FAIL]: '
+ 'twisted.trial.test.erroneous.TestRegularFail.test_fail',
+ 'Traceback (most recent call last):',
+ re.compile(r'^\s+File .*erroneous\.py., line \d+, in test_fail$'),
+ re.compile(r'^\s+self\.fail\("I fail"\)$'),
+ 'twisted.trial.unittest.FailTest: I fail'
+ ]
+ self.stringComparison(match, output)
+
+ def testDoctestError(self):
+ from twisted.trial.test import erroneous
+ suite = unittest.decorate(
+ self.loader.loadDoctests(erroneous), itrial.ITestCase)
+ output = self.getOutput(suite)
+ path = 'twisted.trial.test.erroneous.unexpectedException'
+ for substring in ['1/0', 'ZeroDivisionError',
+ 'Exception raised:', path]:
+ self.assertSubstring(substring, output)
+ self.failUnless(re.search('Fail(ed|ure in) example:', output),
+ "Couldn't match 'Failure in example: ' "
+ "or 'Failed example: '")
+ expect = [self.doubleSeparator,
+ re.compile(r'\[(ERROR|FAIL)\]: .*' + re.escape(path))]
+ self.stringComparison(expect, output.splitlines())
+
+ def testHiddenException(self):
+ """
+ Check that errors in C{DelayedCall}s get reported, even if the
+ test already has a failure.
+
+ Only really necessary for testing the deprecated style of tests that
+ use iterate() directly. See
+ L{erroneous.DelayedCall.testHiddenException} for more details.
+ """
+ test = erroneous.DelayedCall('testHiddenException')
+ output = self.getOutput(test).splitlines()
+ match = [
+ self.doubleSeparator,
+ '[FAIL]: '
+ 'twisted.trial.test.erroneous.DelayedCall.testHiddenException',
+ 'Traceback (most recent call last):',
+ re.compile(r'^\s+File .*erroneous\.py., line \d+, in '
+ 'testHiddenException$'),
+ re.compile(r'^\s+self\.fail\("Deliberate failure to mask the '
+ 'hidden exception"\)$'),
+ 'twisted.trial.unittest.FailTest: '
+ 'Deliberate failure to mask the hidden exception',
+ self.doubleSeparator,
+ '[ERROR]: '
+ 'twisted.trial.test.erroneous.DelayedCall.testHiddenException',
+ 'Traceback (most recent call last):',
+ re.compile(r'^\s+File .* in runUntilCurrent'),
+ re.compile(r'^\s+.*'),
+ re.compile('^\s+File .*erroneous\.py", line \d+, in go'),
+ re.compile('^\s+raise RuntimeError\(self.hiddenExceptionMsg\)'),
+ 'exceptions.RuntimeError: something blew up',
+ ]
+
+ self.stringComparison(match, output)
+
+
+
+class TestUncleanWarningWrapperErrorReporting(TestErrorReporting):
+ """
+ Tests that the L{UncleanWarningsReporterWrapper} can sufficiently proxy
+ IReporter failure and error reporting methods to a L{reporter.Reporter}.
+ """
+ def setUp(self):
+ self.loader = runner.TestLoader()
+ self.output = StringIO.StringIO()
+ self.result = UncleanWarningsReporterWrapper(
+ reporter.Reporter(self.output))
+
+
+
+class TracebackHandling(unittest.TestCase):
+ def getErrorFrames(self, test):
+ stream = StringIO.StringIO()
+ result = reporter.Reporter(stream)
+ test.run(result)
+ bads = result.failures + result.errors
+ assert len(bads) == 1
+ assert bads[0][0] == test
+ return result._trimFrames(bads[0][1].frames)
+
+ def checkFrames(self, observedFrames, expectedFrames):
+ for observed, expected in zip(observedFrames, expectedFrames):
+ self.assertEqual(observed[0], expected[0])
+ observedSegs = os.path.splitext(observed[1])[0].split(os.sep)
+ expectedSegs = expected[1].split('/')
+ self.assertEqual(observedSegs[-len(expectedSegs):],
+ expectedSegs)
+ self.assertEqual(len(observedFrames), len(expectedFrames))
+
+ def test_basic(self):
+ test = erroneous.TestRegularFail('test_fail')
+ frames = self.getErrorFrames(test)
+ self.checkFrames(frames,
+ [('test_fail', 'twisted/trial/test/erroneous')])
+
+ def test_subroutine(self):
+ test = erroneous.TestRegularFail('test_subfail')
+ frames = self.getErrorFrames(test)
+ self.checkFrames(frames,
+ [('test_subfail', 'twisted/trial/test/erroneous'),
+ ('subroutine', 'twisted/trial/test/erroneous')])
+
+ def test_deferred(self):
+ test = erroneous.TestFailureInDeferredChain('test_fail')
+ frames = self.getErrorFrames(test)
+ self.checkFrames(frames,
+ [('_later', 'twisted/trial/test/erroneous')])
+
+ def test_noFrames(self):
+ result = reporter.Reporter(None)
+ self.assertEqual([], result._trimFrames([]))
+
+ def test_oneFrame(self):
+ result = reporter.Reporter(None)
+ self.assertEqual(['fake frame'], result._trimFrames(['fake frame']))
+
+
+class FormatFailures(StringTest):
+ def setUp(self):
+ try:
+ raise RuntimeError('foo')
+ except RuntimeError:
+ self.f = Failure()
+ self.f.frames = [
+ ['foo', 'foo/bar.py', 5, [('x', 5)], [('y', 'orange')]],
+ ['qux', 'foo/bar.py', 10, [('a', 'two')], [('b', 'MCMXCIX')]]
+ ]
+ self.stream = StringIO.StringIO()
+ self.result = reporter.Reporter(self.stream)
+
+ def test_formatDefault(self):
+ tb = self.result._formatFailureTraceback(self.f)
+ self.stringComparison([
+ 'Traceback (most recent call last):',
+ ' File "foo/bar.py", line 5, in foo',
+ re.compile(r'^\s*$'),
+ ' File "foo/bar.py", line 10, in qux',
+ re.compile(r'^\s*$'),
+ 'RuntimeError: foo'], tb.splitlines())
+
+ def test_formatString(self):
+ tb = '''
+ File "twisted/trial/unittest.py", line 256, in failUnlessSubstring
+ return self.failUnlessIn(substring, astring, msg)
+exceptions.TypeError: iterable argument required
+
+'''
+ expected = '''
+ File "twisted/trial/unittest.py", line 256, in failUnlessSubstring
+ return self.failUnlessIn(substring, astring, msg)
+exceptions.TypeError: iterable argument required
+'''
+ formatted = self.result._formatFailureTraceback(tb)
+ self.assertEqual(expected, formatted)
+
+ def test_mutation(self):
+ frames = self.f.frames[:]
+ tb = self.result._formatFailureTraceback(self.f)
+ self.assertEqual(self.f.frames, frames)
+
+
+class PyunitTestNames(unittest.TestCase):
+ def setUp(self):
+ from twisted.trial.test import sample
+ self.stream = StringIO.StringIO()
+ self.test = sample.PyunitTest('test_foo')
+
+ def test_verboseReporter(self):
+ result = reporter.VerboseTextReporter(self.stream)
+ result.startTest(self.test)
+ output = self.stream.getvalue()
+ self.failUnlessEqual(
+ output, 'twisted.trial.test.sample.PyunitTest.test_foo ... ')
+
+ def test_treeReporter(self):
+ result = reporter.TreeReporter(self.stream)
+ result.startTest(self.test)
+ output = self.stream.getvalue()
+ output = output.splitlines()[-1].strip()
+ self.failUnlessEqual(output, result.getDescription(self.test) + ' ...')
+
+ def test_getDescription(self):
+ result = reporter.TreeReporter(self.stream)
+ output = result.getDescription(self.test)
+ self.failUnlessEqual(output, 'test_foo')
+
+
+ def test_minimalReporter(self):
+ """
+ The summary of L{reporter.MinimalReporter} is a simple list of numbers,
+ indicating how many tests ran, how many failed etc.
+
+ The numbers represents:
+ * the run time of the tests
+ * the number of tests run, printed 2 times for legacy reasons
+ * the number of errors
+ * the number of failures
+ * the number of skips
+ """
+ result = reporter.MinimalReporter(self.stream)
+ self.test.run(result)
+ result._printSummary()
+ output = self.stream.getvalue().strip().split(' ')
+ self.failUnlessEqual(output[1:], ['1', '1', '0', '0', '0'])
+
+
+ def test_minimalReporterTime(self):
+ """
+ L{reporter.MinimalReporter} reports the time to run the tests as first
+ data in its output.
+ """
+ times = [1.0, 1.2, 1.5, 1.9]
+ result = reporter.MinimalReporter(self.stream)
+ result._getTime = lambda: times.pop(0)
+ self.test.run(result)
+ result._printSummary()
+ output = self.stream.getvalue().strip().split(' ')
+ timer = output[0]
+ self.assertEquals(timer, "0.7")
+
+
+ def test_emptyMinimalReporter(self):
+ """
+ The summary of L{reporter.MinimalReporter} is a list of zeroes when no
+ test is actually run.
+ """
+ result = reporter.MinimalReporter(self.stream)
+ result._printSummary()
+ output = self.stream.getvalue().strip().split(' ')
+ self.failUnlessEqual(output, ['0', '0', '0', '0', '0', '0'])
+
+
+
+class TestDirtyReactor(unittest.TestCase):
+ """
+ The trial script has an option to treat L{DirtyReactorAggregateError}s as
+ warnings, as a migration tool for test authors. It causes a wrapper to be
+ placed around reporters that replaces L{DirtyReactorAggregatErrors} with
+ warnings.
+ """
+
+ def setUp(self):
+ self.dirtyError = Failure(
+ util.DirtyReactorAggregateError(['foo'], ['bar']))
+ self.output = StringIO.StringIO()
+ self.test = TestDirtyReactor('test_errorByDefault')
+
+
+ def test_errorByDefault(self):
+ """
+ C{DirtyReactorAggregateError}s are reported as errors with the default
+ Reporter.
+ """
+ result = reporter.Reporter(stream=self.output)
+ result.addError(self.test, self.dirtyError)
+ self.assertEqual(len(result.errors), 1)
+ self.assertEqual(result.errors[0][1], self.dirtyError)
+
+
+ def test_warningsEnabled(self):
+ """
+ C{DirtyReactorErrors}s are reported as warnings when using the
+ L{UncleanWarningsReporterWrapper}.
+ """
+ result = UncleanWarningsReporterWrapper(
+ reporter.Reporter(stream=self.output))
+ self.assertWarns(UserWarning, self.dirtyError.getErrorMessage(),
+ reporter.__file__,
+ result.addError, self.test, self.dirtyError)
+
+
+ def test_warningsMaskErrors(self):
+ """
+ C{DirtyReactorErrors}s are I{not} reported as errors if the
+ L{UncleanWarningsReporterWrapper} is used.
+ """
+ result = UncleanWarningsReporterWrapper(
+ reporter.Reporter(stream=self.output))
+ self.assertWarns(UserWarning, self.dirtyError.getErrorMessage(),
+ reporter.__file__,
+ result.addError, self.test, self.dirtyError)
+ self.assertEquals(result._originalReporter.errors, [])
+
+
+ def test_dealsWithThreeTuples(self):
+ """
+ Some annoying stuff can pass three-tuples to addError instead of
+ Failures (like PyUnit). The wrapper, of course, handles this case,
+ since it is a part of L{twisted.trial.itrial.IReporter}! But it does
+ not convert L{DirtyReactorError} to warnings in this case, because
+ nobody should be passing those in the form of three-tuples.
+ """
+ result = UncleanWarningsReporterWrapper(
+ reporter.Reporter(stream=self.output))
+ result.addError(self.test,
+ (self.dirtyError.type, self.dirtyError.value, None))
+ self.assertEqual(len(result._originalReporter.errors), 1)
+ self.assertEquals(result._originalReporter.errors[0][1].type,
+ self.dirtyError.type)
+ self.assertEquals(result._originalReporter.errors[0][1].value,
+ self.dirtyError.value)
+
+
+
+class TrialTestNames(unittest.TestCase):
+
+ def setUp(self):
+ from twisted.trial.test import sample
+ self.stream = StringIO.StringIO()
+ self.test = sample.FooTest('test_foo')
+
+ def test_verboseReporter(self):
+ result = reporter.VerboseTextReporter(self.stream)
+ result.startTest(self.test)
+ output = self.stream.getvalue()
+ self.failUnlessEqual(output, self.test.id() + ' ... ')
+
+ def test_treeReporter(self):
+ result = reporter.TreeReporter(self.stream)
+ result.startTest(self.test)
+ output = self.stream.getvalue()
+ output = output.splitlines()[-1].strip()
+ self.failUnlessEqual(output, result.getDescription(self.test) + ' ...')
+
+ def test_treeReporterWithDocstrings(self):
+ """A docstring"""
+ result = reporter.TreeReporter(self.stream)
+ self.assertEqual(result.getDescription(self),
+ 'test_treeReporterWithDocstrings')
+
+ def test_getDescription(self):
+ result = reporter.TreeReporter(self.stream)
+ output = result.getDescription(self.test)
+ self.failUnlessEqual(output, "test_foo")
+
+
+class TestSkip(unittest.TestCase):
+ """
+ Tests for L{reporter.Reporter}'s handling of skips.
+ """
+ def setUp(self):
+ from twisted.trial.test import sample
+ self.stream = StringIO.StringIO()
+ self.result = reporter.Reporter(self.stream)
+ self.test = sample.FooTest('test_foo')
+
+ def _getSkips(self, result):
+ """
+ Get the number of skips that happened to a reporter.
+ """
+ return len(result.skips)
+
+ def test_accumulation(self):
+ self.result.addSkip(self.test, 'some reason')
+ self.assertEqual(self._getSkips(self.result), 1)
+
+ def test_success(self):
+ self.result.addSkip(self.test, 'some reason')
+ self.failUnlessEqual(True, self.result.wasSuccessful())
+
+
+ def test_summary(self):
+ """
+ The summary of a successful run with skips indicates that the test
+ suite passed and includes the number of skips.
+ """
+ self.result.addSkip(self.test, 'some reason')
+ self.result.done()
+ output = self.stream.getvalue().splitlines()[-1]
+ prefix = 'PASSED '
+ self.failUnless(output.startswith(prefix))
+ self.failUnlessEqual(output[len(prefix):].strip(), '(skips=1)')
+
+
+ def test_basicErrors(self):
+ """
+ The output at the end of a test run with skips includes the reasons
+ for skipping those tests.
+ """
+ self.result.addSkip(self.test, 'some reason')
+ self.result.done()
+ output = self.stream.getvalue().splitlines()[4]
+ self.failUnlessEqual(output.strip(), 'some reason')
+
+
+ def test_booleanSkip(self):
+ """
+ Tests can be skipped without specifying a reason by setting the 'skip'
+ attribute to True. When this happens, the test output includes 'True'
+ as the reason.
+ """
+ self.result.addSkip(self.test, True)
+ self.result.done()
+ output = self.stream.getvalue().splitlines()[4]
+ self.failUnlessEqual(output, 'True')
+
+
+ def test_exceptionSkip(self):
+ """
+ Skips can be raised as errors. When this happens, the error is
+ included in the summary at the end of the test suite.
+ """
+ try:
+ 1/0
+ except Exception, e:
+ error = e
+ self.result.addSkip(self.test, error)
+ self.result.done()
+ output = '\n'.join(self.stream.getvalue().splitlines()[3:5]).strip()
+ self.failUnlessEqual(output, str(e))
+
+
+class UncleanWarningSkipTest(TestSkip):
+ """
+ Tests for skips on a L{reporter.Reporter} wrapped by an
+ L{UncleanWarningsReporterWrapper}.
+ """
+ def setUp(self):
+ TestSkip.setUp(self)
+ self.result = UncleanWarningsReporterWrapper(self.result)
+
+ def _getSkips(self, result):
+ """
+ Get the number of skips that happened to a reporter inside of an
+ unclean warnings reporter wrapper.
+ """
+ return len(result._originalReporter.skips)
+
+
+
+class TodoTest(unittest.TestCase):
+ """
+ Tests for L{reporter.Reporter}'s handling of todos.
+ """
+
+ def setUp(self):
+ from twisted.trial.test import sample
+ self.stream = StringIO.StringIO()
+ self.result = reporter.Reporter(self.stream)
+ self.test = sample.FooTest('test_foo')
+
+
+ def _getTodos(self, result):
+ """
+ Get the number of todos that happened to a reporter.
+ """
+ return len(result.expectedFailures)
+
+
+ def _getUnexpectedSuccesses(self, result):
+ """
+ Get the number of unexpected successes that happened to a reporter.
+ """
+ return len(result.unexpectedSuccesses)
+
+
+ def test_accumulation(self):
+ """
+ L{reporter.Reporter} accumulates the expected failures that it
+ is notified of.
+ """
+ self.result.addExpectedFailure(self.test, Failure(Exception()),
+ makeTodo('todo!'))
+ self.assertEqual(self._getTodos(self.result), 1)
+
+
+ def test_success(self):
+ """
+ A test run is still successful even if there are expected failures.
+ """
+ self.result.addExpectedFailure(self.test, Failure(Exception()),
+ makeTodo('todo!'))
+ self.assertEqual(True, self.result.wasSuccessful())
+
+
+ def test_unexpectedSuccess(self):
+ """
+ A test which is marked as todo but succeeds will have an unexpected
+ success reported to its result. A test run is still successful even
+ when this happens.
+ """
+ self.result.addUnexpectedSuccess(self.test, makeTodo("Heya!"))
+ self.assertEqual(True, self.result.wasSuccessful())
+ self.assertEqual(self._getUnexpectedSuccesses(self.result), 1)
+
+
+ def test_summary(self):
+ """
+ The reporter's C{printSummary} method should print the number of
+ expected failures that occured.
+ """
+ self.result.addExpectedFailure(self.test, Failure(Exception()),
+ makeTodo('some reason'))
+ self.result.done()
+ output = self.stream.getvalue().splitlines()[-1]
+ prefix = 'PASSED '
+ self.failUnless(output.startswith(prefix))
+ self.assertEqual(output[len(prefix):].strip(),
+ '(expectedFailures=1)')
+
+
+ def test_basicErrors(self):
+ """
+ The reporter's L{printErrors} method should include the value of the
+ Todo.
+ """
+ self.result.addExpectedFailure(self.test, Failure(Exception()),
+ makeTodo('some reason'))
+ self.result.done()
+ output = self.stream.getvalue().splitlines()[4].strip()
+ self.assertEqual(output, "Reason: 'some reason'")
+
+
+ def test_booleanTodo(self):
+ """
+ Booleans CAN'T be used as the value of a todo. Maybe this sucks. This
+ is a test for current behavior, not a requirement.
+ """
+ self.result.addExpectedFailure(self.test, Failure(Exception()),
+ makeTodo(True))
+ self.assertRaises(Exception, self.result.done)
+
+
+ def test_exceptionTodo(self):
+ """
+ The exception for expected failures should be shown in the
+ C{printErrors} output.
+ """
+ try:
+ 1/0
+ except Exception, e:
+ error = e
+ self.result.addExpectedFailure(self.test, Failure(error),
+ makeTodo("todo!"))
+ self.result.done()
+ output = '\n'.join(self.stream.getvalue().splitlines()[3:]).strip()
+ self.assertTrue(str(e) in output)
+
+
+
+class UncleanWarningTodoTest(TodoTest):
+ """
+ Tests for L{UncleanWarningsReporterWrapper}'s handling of todos.
+ """
+
+ def setUp(self):
+ TodoTest.setUp(self)
+ self.result = UncleanWarningsReporterWrapper(self.result)
+
+
+ def _getTodos(self, result):
+ """
+ Get the number of todos that happened to a reporter inside of an
+ unclean warnings reporter wrapper.
+ """
+ return len(result._originalReporter.expectedFailures)
+
+
+ def _getUnexpectedSuccesses(self, result):
+ """
+ Get the number of unexpected successes that happened to a reporter
+ inside of an unclean warnings reporter wrapper.
+ """
+ return len(result._originalReporter.unexpectedSuccesses)
+
+
+
+class MockColorizer:
+ """
+ Used by TestTreeReporter to make sure that output is colored correctly.
+ """
+
+ def __init__(self, stream):
+ self.log = []
+
+
+ def write(self, text, color):
+ self.log.append((color, text))
+
+
+
+class TestTreeReporter(unittest.TestCase):
+ def setUp(self):
+ from twisted.trial.test import sample
+ self.test = sample.FooTest('test_foo')
+ self.stream = StringIO.StringIO()
+ self.result = reporter.TreeReporter(self.stream)
+ self.result._colorizer = MockColorizer(self.stream)
+ self.log = self.result._colorizer.log
+
+ def makeError(self):
+ try:
+ 1/0
+ except ZeroDivisionError:
+ f = Failure()
+ return f
+
+ def test_cleanupError(self):
+ """
+ Run cleanupErrors and check that the output is correct, and colored
+ correctly.
+ """
+ f = self.makeError()
+ self.result.cleanupErrors(f)
+ color, text = self.log[0]
+ self.assertEqual(color.strip(), self.result.ERROR)
+ self.assertEqual(text.strip(), 'cleanup errors')
+ color, text = self.log[1]
+ self.assertEqual(color.strip(), self.result.ERROR)
+ self.assertEqual(text.strip(), '[ERROR]')
+ test_cleanupError = suppressWarnings(
+ test_cleanupError,
+ util.suppress(category=reporter.BrokenTestCaseWarning),
+ util.suppress(category=DeprecationWarning))
+
+
+ def test_upDownError(self):
+ """
+ Run upDownError and check that the output is correct and colored
+ correctly.
+ """
+ self.result.upDownError("method", None, None, False)
+ color, text = self.log[0]
+ self.assertEqual(color.strip(), self.result.ERROR)
+ self.assertEqual(text.strip(), 'method')
+ test_upDownError = suppressWarnings(
+ test_upDownError,
+ util.suppress(category=DeprecationWarning,
+ message="upDownError is deprecated in Twisted 8.0."))
+
+
+ def test_summaryColoredSuccess(self):
+ """
+ The summary in case of success should have a good count of successes
+ and be colored properly.
+ """
+ self.result.addSuccess(self.test)
+ self.result.done()
+ self.assertEquals(self.log[1], (self.result.SUCCESS, 'PASSED'))
+ self.assertEquals(
+ self.stream.getvalue().splitlines()[-1].strip(), "(successes=1)")
+
+
+ def test_summaryColoredFailure(self):
+ """
+ The summary in case of failure should have a good count of errors
+ and be colored properly.
+ """
+ try:
+ raise RuntimeError('foo')
+ except RuntimeError, excValue:
+ self.result.addError(self, sys.exc_info())
+ self.result.done()
+ self.assertEquals(self.log[1], (self.result.FAILURE, 'FAILED'))
+ self.assertEquals(
+ self.stream.getvalue().splitlines()[-1].strip(), "(errors=1)")
+
+
+ def test_getPrelude(self):
+ """
+ The tree needs to get the segments of the test ID that correspond
+ to the module and class that it belongs to.
+ """
+ self.assertEqual(
+ ['foo.bar', 'baz'],
+ self.result._getPreludeSegments('foo.bar.baz.qux'))
+ self.assertEqual(
+ ['foo', 'bar'],
+ self.result._getPreludeSegments('foo.bar.baz'))
+ self.assertEqual(
+ ['foo'],
+ self.result._getPreludeSegments('foo.bar'))
+ self.assertEqual([], self.result._getPreludeSegments('foo'))
+
+
+
+class TestReporterInterface(unittest.TestCase):
+ """
+ Tests for the bare interface of a trial reporter.
+
+ Subclass this test case and provide a different 'resultFactory' to test
+ that a particular reporter implementation will work with the rest of
+ Trial.
+
+ @cvar resultFactory: A callable that returns a reporter to be tested. The
+ callable must take the same parameters as L{reporter.Reporter}.
+ """
+
+ resultFactory = reporter.Reporter
+
+ def setUp(self):
+ from twisted.trial.test import sample
+ self.test = sample.FooTest('test_foo')
+ self.stream = StringIO.StringIO()
+ self.publisher = log.LogPublisher()
+ self.result = self.resultFactory(self.stream, publisher=self.publisher)
+
+
+ def test_shouldStopInitiallyFalse(self):
+ """
+ shouldStop is False to begin with.
+ """
+ self.assertEquals(False, self.result.shouldStop)
+
+
+ def test_shouldStopTrueAfterStop(self):
+ """
+ shouldStop becomes True soon as someone calls stop().
+ """
+ self.result.stop()
+ self.assertEquals(True, self.result.shouldStop)
+
+
+ def test_wasSuccessfulInitiallyTrue(self):
+ """
+ wasSuccessful() is True when there have been no results reported.
+ """
+ self.assertEquals(True, self.result.wasSuccessful())
+
+
+ def test_wasSuccessfulTrueAfterSuccesses(self):
+ """
+ wasSuccessful() is True when there have been only successes, False
+ otherwise.
+ """
+ self.result.addSuccess(self.test)
+ self.assertEquals(True, self.result.wasSuccessful())
+
+
+ def test_wasSuccessfulFalseAfterErrors(self):
+ """
+ wasSuccessful() becomes False after errors have been reported.
+ """
+ try:
+ 1 / 0
+ except ZeroDivisionError:
+ self.result.addError(self.test, sys.exc_info())
+ self.assertEquals(False, self.result.wasSuccessful())
+
+
+ def test_wasSuccessfulFalseAfterFailures(self):
+ """
+ wasSuccessful() becomes False after failures have been reported.
+ """
+ try:
+ self.fail("foo")
+ except self.failureException:
+ self.result.addFailure(self.test, sys.exc_info())
+ self.assertEquals(False, self.result.wasSuccessful())
+
+
+
+class TestReporter(TestReporterInterface):
+ """
+ Tests for the base L{reporter.Reporter} class.
+ """
+
+ def setUp(self):
+ TestReporterInterface.setUp(self)
+ self._timer = 0
+ self.result._getTime = self._getTime
+
+
+ def _getTime(self):
+ self._timer += 1
+ return self._timer
+
+
+ def test_startStop(self):
+ self.result.startTest(self.test)
+ self.result.stopTest(self.test)
+ self.assertTrue(self.result._lastTime > 0)
+ self.assertEqual(self.result.testsRun, 1)
+ self.assertEqual(self.result.wasSuccessful(), True)
+
+
+ def test_brokenStream(self):
+ """
+ Test that the reporter safely writes to its stream.
+ """
+ result = self.resultFactory(stream=BrokenStream(self.stream))
+ result._writeln("Hello")
+ self.assertEqual(self.stream.getvalue(), 'Hello\n')
+ self.stream.truncate(0)
+ result._writeln("Hello %s!", 'World')
+ self.assertEqual(self.stream.getvalue(), 'Hello World!\n')
+
+
+ def test_printErrorsDeprecated(self):
+ """
+ L{IReporter.printErrors} was deprecated in Twisted 8.0.
+ """
+ def f():
+ self.result.printErrors()
+ self.assertWarns(
+ DeprecationWarning, "printErrors is deprecated in Twisted 8.0.",
+ __file__, f)
+
+
+ def test_printSummaryDeprecated(self):
+ """
+ L{IReporter.printSummary} was deprecated in Twisted 8.0.
+ """
+ def f():
+ self.result.printSummary()
+ self.assertWarns(
+ DeprecationWarning, "printSummary is deprecated in Twisted 8.0.",
+ __file__, f)
+
+
+ def test_writeDeprecated(self):
+ """
+ L{IReporter.write} was deprecated in Twisted 8.0.
+ """
+ def f():
+ self.result.write("")
+ self.assertWarns(
+ DeprecationWarning, "write is deprecated in Twisted 8.0.",
+ __file__, f)
+
+
+ def test_writelnDeprecated(self):
+ """
+ L{IReporter.writeln} was deprecated in Twisted 8.0.
+ """
+ def f():
+ self.result.writeln("")
+ self.assertWarns(
+ DeprecationWarning, "writeln is deprecated in Twisted 8.0.",
+ __file__, f)
+
+
+ def test_separatorDeprecated(self):
+ """
+ L{IReporter.separator} was deprecated in Twisted 8.0.
+ """
+ def f():
+ return self.result.separator
+ self.assertWarns(
+ DeprecationWarning, "separator is deprecated in Twisted 8.0.",
+ __file__, f)
+
+
+ def test_streamDeprecated(self):
+ """
+ L{IReporter.stream} was deprecated in Twisted 8.0.
+ """
+ def f():
+ return self.result.stream
+ self.assertWarns(
+ DeprecationWarning, "stream is deprecated in Twisted 8.0.",
+ __file__, f)
+
+
+ def test_upDownErrorDeprecated(self):
+ """
+ L{IReporter.upDownError} was deprecated in Twisted 8.0.
+ """
+ def f():
+ self.result.upDownError(None, None, None, None)
+ self.assertWarns(
+ DeprecationWarning, "upDownError is deprecated in Twisted 8.0.",
+ __file__, f)
+
+
+ def test_warning(self):
+ """
+ L{reporter.Reporter} observes warnings emitted by the Twisted log
+ system and writes them to its output stream.
+ """
+ message = RuntimeWarning("some warning text")
+ category = 'exceptions.RuntimeWarning'
+ filename = "path/to/some/file.py"
+ lineno = 71
+ self.publisher.msg(
+ warning=message, category=category,
+ filename=filename, lineno=lineno)
+ self.assertEqual(
+ self.stream.getvalue(),
+ "%s:%d: %s: %s\n" % (
+ filename, lineno, category.split('.')[-1], message))
+
+
+ def test_duplicateWarningSuppressed(self):
+ """
+ A warning emitted twice within a single test is only written to the
+ stream once.
+ """
+ # Emit the warning and assert that it shows up
+ self.test_warning()
+ # Emit the warning again and assert that the stream still only has one
+ # warning on it.
+ self.test_warning()
+
+
+ def test_warningEmittedForNewTest(self):
+ """
+ A warning emitted again after a new test has started is written to the
+ stream again.
+ """
+ test = self.__class__('test_warningEmittedForNewTest')
+ self.result.startTest(test)
+
+ # Clear whatever startTest wrote to the stream
+ self.stream.seek(0)
+ self.stream.truncate()
+
+ # Emit a warning (and incidentally, assert that it was emitted)
+ self.test_warning()
+
+ # Clean up from the first warning to simplify the rest of the
+ # assertions.
+ self.stream.seek(0)
+ self.stream.truncate()
+
+ # Stop the first test and start another one (it just happens to be the
+ # same one, but that doesn't matter)
+ self.result.stopTest(test)
+ self.result.startTest(test)
+
+ # Clean up the stopTest/startTest output
+ self.stream.seek(0)
+ self.stream.truncate()
+
+ # Emit the warning again and make sure it shows up
+ self.test_warning()
+
+
+ def test_stopObserving(self):
+ """
+ L{reporter.Reporter} stops observing log events when its C{done} method
+ is called.
+ """
+ self.result.done()
+ self.stream.seek(0)
+ self.stream.truncate()
+ self.publisher.msg(
+ warning=RuntimeWarning("some message"),
+ category='exceptions.RuntimeWarning',
+ filename="file/name.py", lineno=17)
+ self.assertEqual(self.stream.getvalue(), "")
+
+
+
+class TestSafeStream(unittest.TestCase):
+ def test_safe(self):
+ """
+ Test that L{reporter.SafeStream} successfully write to its original
+ stream even if an interrupt happens during the write.
+ """
+ stream = StringIO.StringIO()
+ broken = BrokenStream(stream)
+ safe = reporter.SafeStream(broken)
+ safe.write("Hello")
+ self.assertEqual(stream.getvalue(), "Hello")
+
+
+
+class TestSubunitReporter(TestReporterInterface):
+ """
+ Tests for the subunit reporter.
+
+ This just tests that the subunit reporter implements the basic interface.
+ """
+
+ resultFactory = reporter.SubunitReporter
+
+
+ def setUp(self):
+ if reporter.TestProtocolClient is None:
+ raise SkipTest(
+ "Subunit not installed, cannot test SubunitReporter")
+ TestReporterInterface.setUp(self)
+
+
+ def assertForwardsToSubunit(self, methodName, *args, **kwargs):
+ """
+ Assert that 'methodName' on L{SubunitReporter} forwards to the
+ equivalent method on subunit.
+
+ Checks that the return value from subunit is returned from the
+ L{SubunitReporter} and that the reporter writes the same data to its
+ stream as subunit does to its own.
+
+ Assumes that the method on subunit has the same name as the method on
+ L{SubunitReporter}.
+ """
+ stream = StringIO.StringIO()
+ subunitClient = reporter.TestProtocolClient(stream)
+ subunitReturn = getattr(subunitClient, methodName)(*args, **kwargs)
+ subunitOutput = stream.getvalue()
+ reporterReturn = getattr(self.result, methodName)(*args, **kwargs)
+ self.assertEquals(subunitReturn, reporterReturn)
+ self.assertEquals(subunitOutput, self.stream.getvalue())
+
+
+ def test_subunitWithoutAddExpectedFailureInstalled(self):
+ """
+ Some versions of subunit don't have "addExpectedFailure". For these
+ versions, we report expected failures as successes.
+ """
+ try:
+ addExpectedFailure = reporter.TestProtocolClient.addExpectedFailure
+ except AttributeError:
+ # Then we've actually got one of those old versions installed, and
+ # the test is immediately applicable.
+ pass
+ else:
+ del reporter.TestProtocolClient.addExpectedFailure
+ self.addCleanup(
+ setattr, reporter.TestProtocolClient, 'addExpectedFailure',
+ addExpectedFailure)
+ try:
+ 1 / 0
+ except ZeroDivisionError:
+ self.result.addExpectedFailure(self.test, sys.exc_info(), "todo")
+ expectedFailureOutput = self.stream.getvalue()
+ self.stream.truncate(0)
+ self.result.addSuccess(self.test)
+ successOutput = self.stream.getvalue()
+ self.assertEquals(successOutput, expectedFailureOutput)
+
+
+ def test_subunitWithoutAddSkipInstalled(self):
+ """
+ Some versions of subunit don't have "addSkip". For these versions, we
+ report skips as successes.
+ """
+ try:
+ addSkip = reporter.TestProtocolClient.addSkip
+ except AttributeError:
+ # Then we've actually got one of those old versions installed, and
+ # the test is immediately applicable.
+ pass
+ else:
+ del reporter.TestProtocolClient.addSkip
+ self.addCleanup(
+ setattr, reporter.TestProtocolClient, 'addSkip', addSkip)
+ self.result.addSkip(self.test, "reason")
+ skipOutput = self.stream.getvalue()
+ self.stream.truncate(0)
+ self.result.addSuccess(self.test)
+ successOutput = self.stream.getvalue()
+ self.assertEquals(successOutput, skipOutput)
+
+
+ def test_addExpectedFailurePassedThrough(self):
+ """
+ Some versions of subunit have "addExpectedFailure". For these
+ versions, when we call 'addExpectedFailure' on the test result, we
+ pass the error and test through to the subunit client.
+ """
+ addExpectedFailureCalls = []
+ def addExpectedFailure(test, error):
+ addExpectedFailureCalls.append((test, error))
+
+ # Provide our own addExpectedFailure, whether or not the locally
+ # installed subunit has addExpectedFailure.
+ self.result._subunit.addExpectedFailure = addExpectedFailure
+ try:
+ 1 / 0
+ except ZeroDivisionError:
+ exc_info = sys.exc_info()
+ self.result.addExpectedFailure(self.test, exc_info, 'todo')
+ self.assertEquals(addExpectedFailureCalls, [(self.test, exc_info)])
+
+
+ def test_addSkipSendsSubunitAddSkip(self):
+ """
+ Some versions of subunit have "addSkip". For these versions, when we
+ call 'addSkip' on the test result, we pass the test and reason through
+ to the subunit client.
+ """
+ addSkipCalls = []
+ def addSkip(test, reason):
+ addSkipCalls.append((test, reason))
+
+ # Provide our own addSkip, whether or not the locally-installed
+ # subunit has addSkip.
+ self.result._subunit.addSkip = addSkip
+ self.result.addSkip(self.test, 'reason')
+ self.assertEquals(addSkipCalls, [(self.test, 'reason')])
+
+
+ def test_doneDoesNothing(self):
+ """
+ The subunit reporter doesn't need to print out a summary -- the stream
+ of results is everything. Thus, done() does nothing.
+ """
+ self.result.done()
+ self.assertEquals('', self.stream.getvalue())
+
+
+ def test_startTestSendsSubunitStartTest(self):
+ """
+ SubunitReporter.startTest() sends the subunit 'startTest' message.
+ """
+ self.assertForwardsToSubunit('startTest', self.test)
+
+
+ def test_stopTestSendsSubunitStopTest(self):
+ """
+ SubunitReporter.stopTest() sends the subunit 'stopTest' message.
+ """
+ self.assertForwardsToSubunit('stopTest', self.test)
+
+
+ def test_addSuccessSendsSubunitAddSuccess(self):
+ """
+ SubunitReporter.addSuccess() sends the subunit 'addSuccess' message.
+ """
+ self.assertForwardsToSubunit('addSuccess', self.test)
+
+
+ def test_addErrorSendsSubunitAddError(self):
+ """
+ SubunitReporter.addError() sends the subunit 'addError' message.
+ """
+ try:
+ 1 / 0
+ except ZeroDivisionError:
+ error = sys.exc_info()
+ self.assertForwardsToSubunit('addError', self.test, error)
+
+
+ def test_addFailureSendsSubunitAddFailure(self):
+ """
+ SubunitReporter.addFailure() sends the subunit 'addFailure' message.
+ """
+ try:
+ self.fail('hello')
+ except self.failureException:
+ failure = sys.exc_info()
+ self.assertForwardsToSubunit('addFailure', self.test, failure)
+
+
+ def test_addUnexpectedSuccessSendsSubunitAddSuccess(self):
+ """
+ SubunitReporter.addFailure() sends the subunit 'addSuccess' message,
+ since subunit doesn't model unexpected success.
+ """
+ stream = StringIO.StringIO()
+ subunitClient = reporter.TestProtocolClient(stream)
+ subunitClient.addSuccess(self.test)
+ subunitOutput = stream.getvalue()
+ self.result.addUnexpectedSuccess(self.test, 'todo')
+ self.assertEquals(subunitOutput, self.stream.getvalue())
+
+
+
+class TestSubunitReporterNotInstalled(unittest.TestCase):
+ """
+ Test behaviour when the subunit reporter is not installed.
+ """
+
+ def test_subunitNotInstalled(self):
+ """
+ If subunit is not installed, TestProtocolClient will be None, and
+ SubunitReporter will raise an error when you try to construct it.
+ """
+ stream = StringIO.StringIO()
+ self.patch(reporter, 'TestProtocolClient', None)
+ e = self.assertRaises(Exception, reporter.SubunitReporter, stream)
+ self.assertEquals("Subunit not available", str(e))
+
+
+
+class TestTimingReporter(TestReporter):
+ resultFactory = reporter.TimingTextReporter
+
+
+
+class LoggingReporter(reporter.Reporter):
+ """
+ Simple reporter that stores the last test that was passed to it.
+ """
+
+ def __init__(self, *args, **kwargs):
+ reporter.Reporter.__init__(self, *args, **kwargs)
+ self.test = None
+
+ def addError(self, test, error):
+ self.test = test
+
+ def addExpectedFailure(self, test, failure, todo):
+ self.test = test
+
+ def addFailure(self, test, failure):
+ self.test = test
+
+ def addSkip(self, test, skip):
+ self.test = test
+
+ def addUnexpectedSuccess(self, test, todo):
+ self.test = test
+
+ def startTest(self, test):
+ self.test = test
+
+ def stopTest(self, test):
+ self.test = test
+
+
+
+class TestAdaptedReporter(unittest.TestCase):
+ """
+ L{reporter._AdaptedReporter} is a reporter wrapper that wraps all of the
+ tests it receives before passing them on to the original reporter.
+ """
+
+ def setUp(self):
+ self.wrappedResult = self.getWrappedResult()
+
+
+ def _testAdapter(self, test):
+ return test.id()
+
+
+ def assertWrapped(self, wrappedResult, test):
+ self.assertEqual(wrappedResult._originalReporter.test, self._testAdapter(test))
+
+
+ def getFailure(self, exceptionInstance):
+ """
+ Return a L{Failure} from raising the given exception.
+
+ @param exceptionInstance: The exception to raise.
+ @return: L{Failure}
+ """
+ try:
+ raise exceptionInstance
+ except:
+ return Failure()
+
+
+ def getWrappedResult(self):
+ result = LoggingReporter()
+ return reporter._AdaptedReporter(result, self._testAdapter)
+
+
+ def test_addError(self):
+ """
+ C{addError} wraps its test with the provided adapter.
+ """
+ self.wrappedResult.addError(self, self.getFailure(RuntimeError()))
+ self.assertWrapped(self.wrappedResult, self)
+
+
+ def test_addFailure(self):
+ """
+ C{addFailure} wraps its test with the provided adapter.
+ """
+ self.wrappedResult.addFailure(self, self.getFailure(AssertionError()))
+ self.assertWrapped(self.wrappedResult, self)
+
+
+ def test_addSkip(self):
+ """
+ C{addSkip} wraps its test with the provided adapter.
+ """
+ self.wrappedResult.addSkip(self, self.getFailure(SkipTest('no reason')))
+ self.assertWrapped(self.wrappedResult, self)
+
+
+ def test_startTest(self):
+ """
+ C{startTest} wraps its test with the provided adapter.
+ """
+ self.wrappedResult.startTest(self)
+ self.assertWrapped(self.wrappedResult, self)
+
+
+ def test_stopTest(self):
+ """
+ C{stopTest} wraps its test with the provided adapter.
+ """
+ self.wrappedResult.stopTest(self)
+ self.assertWrapped(self.wrappedResult, self)
+
+
+ def test_addExpectedFailure(self):
+ """
+ C{addExpectedFailure} wraps its test with the provided adapter.
+ """
+ self.wrappedResult.addExpectedFailure(
+ self, self.getFailure(RuntimeError()), Todo("no reason"))
+ self.assertWrapped(self.wrappedResult, self)
+
+
+ def test_addUnexpectedSuccess(self):
+ """
+ C{addUnexpectedSuccess} wraps its test with the provided adapter.
+ """
+ self.wrappedResult.addUnexpectedSuccess(self, Todo("no reason"))
+ self.assertWrapped(self.wrappedResult, self)
+
+
+
+class FakeStream(object):
+ """
+ A fake stream which C{isatty} method returns some predictable.
+
+ @ivar tty: returned value of C{isatty}.
+ @type tty: C{bool}
+ """
+
+ def __init__(self, tty=True):
+ self.tty = tty
+
+
+ def isatty(self):
+ return self.tty
+
+
+
+class AnsiColorizerTests(unittest.TestCase):
+ """
+ Tests for L{reporter._AnsiColorizer}.
+ """
+
+ def setUp(self):
+ self.savedModules = sys.modules.copy()
+
+
+ def tearDown(self):
+ sys.modules.clear()
+ sys.modules.update(self.savedModules)
+
+
+ def test_supportedStdOutTTY(self):
+ """
+ L{reporter._AnsiColorizer.supported} returns C{False} if the given
+ stream is not a TTY.
+ """
+ self.assertFalse(reporter._AnsiColorizer.supported(FakeStream(False)))
+
+
+ def test_supportedNoCurses(self):
+ """
+ L{reporter._AnsiColorizer.supported} returns C{False} if the curses
+ module can't be imported.
+ """
+ sys.modules['curses'] = None
+ self.assertFalse(reporter._AnsiColorizer.supported(FakeStream()))
+
+
+ def test_supportedSetupTerm(self):
+ """
+ L{reporter._AnsiColorizer.supported} returns C{True} if
+ C{curses.tigetnum} returns more than 2 supported colors. It only tries
+ to call C{curses.setupterm} if C{curses.tigetnum} previously failed
+ with a C{curses.error}.
+ """
+ class fakecurses(object):
+ error = RuntimeError
+ setUp = 0
+
+ def setupterm(self):
+ self.setUp += 1
+
+ def tigetnum(self, value):
+ if self.setUp:
+ return 3
+ else:
+ raise self.error()
+
+ sys.modules['curses'] = fakecurses()
+ self.assertTrue(reporter._AnsiColorizer.supported(FakeStream()))
+ self.assertTrue(reporter._AnsiColorizer.supported(FakeStream()))
+
+ self.assertEquals(sys.modules['curses'].setUp, 1)
+
+
+ def test_supportedTigetNumWrongError(self):
+ """
+ L{reporter._AnsiColorizer.supported} returns C{False} and doesn't try
+ to call C{curses.setupterm} if C{curses.tigetnum} returns something
+ different than C{curses.error}.
+ """
+ class fakecurses(object):
+ error = RuntimeError
+
+ def tigetnum(self, value):
+ raise ValueError()
+
+ sys.modules['curses'] = fakecurses()
+ self.assertFalse(reporter._AnsiColorizer.supported(FakeStream()))
+
+
+ def test_supportedTigetNumNotEnoughColor(self):
+ """
+ L{reporter._AnsiColorizer.supported} returns C{False} if
+ C{curses.tigetnum} returns less than 2 supported colors.
+ """
+ class fakecurses(object):
+ error = RuntimeError
+
+ def tigetnum(self, value):
+ return 1
+
+ sys.modules['curses'] = fakecurses()
+ self.assertFalse(reporter._AnsiColorizer.supported(FakeStream()))
+
+
+ def test_supportedTigetNumErrors(self):
+ """
+ L{reporter._AnsiColorizer.supported} returns C{False} if
+ C{curses.tigetnum} raises an error, and calls C{curses.setupterm} once.
+ """
+ class fakecurses(object):
+ error = RuntimeError
+ setUp = 0
+
+ def setupterm(self):
+ self.setUp += 1
+
+ def tigetnum(self, value):
+ raise self.error()
+
+ sys.modules['curses'] = fakecurses()
+ self.assertFalse(reporter._AnsiColorizer.supported(FakeStream()))
+ self.assertEquals(sys.modules['curses'].setUp, 1)
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_runner.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_runner.py
new file mode 100644
index 0000000000..eba1411cce
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_runner.py
@@ -0,0 +1,914 @@
+# Copyright (c) 2005-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+#
+# Maintainer: Jonathan Lange
+# Author: Robert Collins
+
+
+import StringIO, os, sys
+from zope.interface import implements
+
+from twisted.trial.itrial import IReporter, ITestCase
+from twisted.trial import unittest, runner, reporter, util
+from twisted.python import failure, log, reflect, filepath
+from twisted.scripts import trial
+from twisted.plugins import twisted_trial
+from twisted import plugin
+from twisted.internet import defer
+
+
+pyunit = __import__('unittest')
+
+
+class CapturingDebugger(object):
+
+ def __init__(self):
+ self._calls = []
+
+ def runcall(self, *args, **kwargs):
+ self._calls.append('runcall')
+ args[0](*args[1:], **kwargs)
+
+
+
+class CapturingReporter(object):
+ """
+ Reporter that keeps a log of all actions performed on it.
+ """
+
+ implements(IReporter)
+
+ stream = None
+ tbformat = None
+ args = None
+ separator = None
+ testsRun = None
+
+ def __init__(self, stream=None, tbformat=None, rterrors=None,
+ publisher=None):
+ """
+ Create a capturing reporter.
+ """
+ self._calls = []
+ self.shouldStop = False
+ self._stream = stream
+ self._tbformat = tbformat
+ self._rterrors = rterrors
+ self._publisher = publisher
+
+
+ def startTest(self, method):
+ """
+ Report the beginning of a run of a single test method
+ @param method: an object that is adaptable to ITestMethod
+ """
+ self._calls.append('startTest')
+
+
+ def stopTest(self, method):
+ """
+ Report the status of a single test method
+ @param method: an object that is adaptable to ITestMethod
+ """
+ self._calls.append('stopTest')
+
+
+ def cleanupErrors(self, errs):
+ """called when the reactor has been left in a 'dirty' state
+ @param errs: a list of L{twisted.python.failure.Failure}s
+ """
+ self._calls.append('cleanupError')
+
+
+ def addSuccess(self, test):
+ self._calls.append('addSuccess')
+
+
+ def done(self):
+ """
+ Do nothing. These tests don't care about done.
+ """
+
+
+
+class TrialRunnerTestsMixin:
+ """
+ Mixin defining tests for L{runner.TrialRunner}.
+ """
+ def tearDown(self):
+ self.runner._tearDownLogFile()
+
+
+ def test_empty(self):
+ """
+ Empty test method, used by the other tests.
+ """
+
+
+ def _getObservers(self):
+ return log.theLogPublisher.observers
+
+
+ def test_addObservers(self):
+ """
+ Any log system observers L{TrialRunner.run} adds are removed by the
+ time it returns.
+ """
+ originalCount = len(self._getObservers())
+ self.runner.run(self.test)
+ newCount = len(self._getObservers())
+ self.assertEqual(newCount, originalCount)
+
+
+ def test_logFileAlwaysActive(self):
+ """
+ Test that a new file is opened on each run.
+ """
+ oldSetUpLogFile = self.runner._setUpLogFile
+ l = []
+ def setUpLogFile():
+ oldSetUpLogFile()
+ l.append(self.runner._logFileObserver)
+ self.runner._setUpLogFile = setUpLogFile
+ self.runner.run(self.test)
+ self.runner.run(self.test)
+ self.failUnlessEqual(len(l), 2)
+ self.failIf(l[0] is l[1], "Should have created a new file observer")
+
+
+ def test_logFileGetsClosed(self):
+ """
+ Test that file created is closed during the run.
+ """
+ oldSetUpLogFile = self.runner._setUpLogFile
+ l = []
+ def setUpLogFile():
+ oldSetUpLogFile()
+ l.append(self.runner._logFileObject)
+ self.runner._setUpLogFile = setUpLogFile
+ self.runner.run(self.test)
+ self.failUnlessEqual(len(l), 1)
+ self.failUnless(l[0].closed)
+
+
+
+class TestTrialRunner(TrialRunnerTestsMixin, unittest.TestCase):
+ """
+ Tests for L{runner.TrialRunner} with the feature to turn unclean errors
+ into warnings disabled.
+ """
+ def setUp(self):
+ self.stream = StringIO.StringIO()
+ self.runner = runner.TrialRunner(CapturingReporter, stream=self.stream)
+ self.test = TestTrialRunner('test_empty')
+
+
+ def test_publisher(self):
+ """
+ The reporter constructed by L{runner.TrialRunner} is passed
+ L{twisted.python.log} as the value for the C{publisher} parameter.
+ """
+ result = self.runner._makeResult()
+ self.assertIdentical(result._publisher, log)
+
+
+
+class TrialRunnerWithUncleanWarningsReporter(TrialRunnerTestsMixin,
+ unittest.TestCase):
+ """
+ Tests for the TrialRunner's interaction with an unclean-error suppressing
+ reporter.
+ """
+
+ def setUp(self):
+ self.stream = StringIO.StringIO()
+ self.runner = runner.TrialRunner(CapturingReporter, stream=self.stream,
+ uncleanWarnings=True)
+ self.test = TestTrialRunner('test_empty')
+
+
+
+class DryRunMixin(object):
+
+ suppress = [util.suppress(
+ category=DeprecationWarning,
+ message="Test visitors deprecated in Twisted 8.0")]
+
+
+ def setUp(self):
+ self.log = []
+ self.stream = StringIO.StringIO()
+ self.runner = runner.TrialRunner(CapturingReporter,
+ runner.TrialRunner.DRY_RUN,
+ stream=self.stream)
+ self.makeTestFixtures()
+
+
+ def makeTestFixtures(self):
+ """
+ Set C{self.test} and C{self.suite}, where C{self.suite} is an empty
+ TestSuite.
+ """
+
+
+ def test_empty(self):
+ """
+ If there are no tests, the reporter should not receive any events to
+ report.
+ """
+ result = self.runner.run(runner.TestSuite())
+ self.assertEqual(result._calls, [])
+
+
+ def test_singleCaseReporting(self):
+ """
+ If we are running a single test, check the reporter starts, passes and
+ then stops the test during a dry run.
+ """
+ result = self.runner.run(self.test)
+ self.assertEqual(result._calls, ['startTest', 'addSuccess', 'stopTest'])
+
+
+ def test_testsNotRun(self):
+ """
+ When we are doing a dry run, the tests should not actually be run.
+ """
+ self.runner.run(self.test)
+ self.assertEqual(self.log, [])
+
+
+
+class DryRunTest(DryRunMixin, unittest.TestCase):
+ """
+ Check that 'dry run' mode works well with Trial tests.
+ """
+ def makeTestFixtures(self):
+ class MockTest(unittest.TestCase):
+ def test_foo(test):
+ self.log.append('test_foo')
+ self.test = MockTest('test_foo')
+ self.suite = runner.TestSuite()
+
+
+
+class PyUnitDryRunTest(DryRunMixin, unittest.TestCase):
+ """
+ Check that 'dry run' mode works well with stdlib unittest tests.
+ """
+ def makeTestFixtures(self):
+ class PyunitCase(pyunit.TestCase):
+ def test_foo(self):
+ pass
+ self.test = PyunitCase('test_foo')
+ self.suite = pyunit.TestSuite()
+
+
+
+class TestRunner(unittest.TestCase):
+ def setUp(self):
+ self.config = trial.Options()
+ # whitebox hack a reporter in, because plugins are CACHED and will
+ # only reload if the FILE gets changed.
+
+ parts = reflect.qual(CapturingReporter).split('.')
+ package = '.'.join(parts[:-1])
+ klass = parts[-1]
+ plugins = [twisted_trial._Reporter(
+ "Test Helper Reporter",
+ package,
+ description="Utility for unit testing.",
+ longOpt="capturing",
+ shortOpt=None,
+ klass=klass)]
+
+
+ # XXX There should really be a general way to hook the plugin system
+ # for tests.
+ def getPlugins(iface, *a, **kw):
+ self.assertEqual(iface, IReporter)
+ return plugins + list(self.original(iface, *a, **kw))
+
+ self.original = plugin.getPlugins
+ plugin.getPlugins = getPlugins
+
+ self.standardReport = ['startTest', 'addSuccess', 'stopTest',
+ 'startTest', 'addSuccess', 'stopTest',
+ 'startTest', 'addSuccess', 'stopTest',
+ 'startTest', 'addSuccess', 'stopTest',
+ 'startTest', 'addSuccess', 'stopTest',
+ 'startTest', 'addSuccess', 'stopTest',
+ 'startTest', 'addSuccess', 'stopTest']
+
+
+ def tearDown(self):
+ plugin.getPlugins = self.original
+
+
+ def parseOptions(self, args):
+ self.config.parseOptions(args)
+
+
+ def getRunner(self):
+ r = trial._makeRunner(self.config)
+ r.stream = StringIO.StringIO()
+ # XXX The runner should always take care of cleaning this up itself.
+ # It's not clear why this is necessary. The runner always tears down
+ # its log file.
+ self.addCleanup(r._tearDownLogFile)
+ # XXX The runner should always take care of cleaning this up itself as
+ # well. It's necessary because TrialRunner._setUpTestdir might raise
+ # an exception preventing Reporter.done from being run, leaving the
+ # observer added by Reporter.__init__ still present in the system.
+ # Something better needs to happen inside
+ # TrialRunner._runWithoutDecoration to remove the need for this cludge.
+ r._log = log.LogPublisher()
+ return r
+
+
+ def test_runner_can_get_reporter(self):
+ self.parseOptions([])
+ result = self.config['reporter']
+ runner = self.getRunner()
+ self.assertEqual(result, runner._makeResult().__class__)
+
+
+ def test_runner_get_result(self):
+ self.parseOptions([])
+ runner = self.getRunner()
+ result = runner._makeResult()
+ self.assertEqual(result.__class__, self.config['reporter'])
+
+
+ def test_uncleanWarningsOffByDefault(self):
+ """
+ By default Trial sets the 'uncleanWarnings' option on the runner to
+ False. This means that dirty reactor errors will be reported as
+ errors. See L{test_reporter.TestDirtyReactor}.
+ """
+ self.parseOptions([])
+ runner = self.getRunner()
+ self.assertNotIsInstance(runner._makeResult(),
+ reporter.UncleanWarningsReporterWrapper)
+
+
+ def test_getsUncleanWarnings(self):
+ """
+ Specifying '--unclean-warnings' on the trial command line will cause
+ reporters to be wrapped in a device which converts unclean errors to
+ warnings. See L{test_reporter.TestDirtyReactor} for implications.
+ """
+ self.parseOptions(['--unclean-warnings'])
+ runner = self.getRunner()
+ self.assertIsInstance(runner._makeResult(),
+ reporter.UncleanWarningsReporterWrapper)
+
+
+ def test_runner_working_directory(self):
+ self.parseOptions(['--temp-directory', 'some_path'])
+ runner = self.getRunner()
+ self.assertEquals(runner.workingDirectory, 'some_path')
+
+
+ def test_concurrentImplicitWorkingDirectory(self):
+ """
+ If no working directory is explicitly specified and the default
+ working directory is in use by another runner, L{TrialRunner.run}
+ selects a different default working directory to use.
+ """
+ self.parseOptions([])
+
+ initialDirectory = os.getcwd()
+ self.addCleanup(os.chdir, initialDirectory)
+
+ firstRunner = self.getRunner()
+ secondRunner = self.getRunner()
+
+ where = {}
+
+ class ConcurrentCase(unittest.TestCase):
+ def test_first(self):
+ """
+ Start a second test run which will have a default working
+ directory which is the same as the working directory of the
+ test run already in progress.
+ """
+ # Change the working directory to the value it had before this
+ # test suite was started.
+ where['concurrent'] = subsequentDirectory = os.getcwd()
+ os.chdir(initialDirectory)
+ self.addCleanup(os.chdir, subsequentDirectory)
+
+ secondRunner.run(ConcurrentCase('test_second'))
+
+ def test_second(self):
+ """
+ Record the working directory for later analysis.
+ """
+ where['record'] = os.getcwd()
+
+ result = firstRunner.run(ConcurrentCase('test_first'))
+ bad = result.errors + result.failures
+ if bad:
+ self.fail(bad[0][1])
+ self.assertEqual(
+ where, {
+ 'concurrent': os.path.join(initialDirectory, '_trial_temp'),
+ 'record': os.path.join(initialDirectory, '_trial_temp-1')})
+
+
+ def test_concurrentExplicitWorkingDirectory(self):
+ """
+ If a working directory which is already in use is explicitly specified,
+ L{TrialRunner.run} raises L{_WorkingDirectoryBusy}.
+ """
+ self.parseOptions(['--temp-directory', os.path.abspath(self.mktemp())])
+
+ initialDirectory = os.getcwd()
+ self.addCleanup(os.chdir, initialDirectory)
+
+ firstRunner = self.getRunner()
+ secondRunner = self.getRunner()
+
+ class ConcurrentCase(unittest.TestCase):
+ def test_concurrent(self):
+ """
+ Try to start another runner in the same working directory and
+ assert that it raises L{_WorkingDirectoryBusy}.
+ """
+ self.assertRaises(
+ runner._WorkingDirectoryBusy,
+ secondRunner.run, ConcurrentCase('test_failure'))
+
+ def test_failure(self):
+ """
+ Should not be called, always fails.
+ """
+ self.fail("test_failure should never be called.")
+
+ result = firstRunner.run(ConcurrentCase('test_concurrent'))
+ bad = result.errors + result.failures
+ if bad:
+ self.fail(bad[0][1])
+
+
+ def test_runner_normal(self):
+ self.parseOptions(['--temp-directory', self.mktemp(),
+ '--reporter', 'capturing',
+ 'twisted.trial.test.sample'])
+ my_runner = self.getRunner()
+ loader = runner.TestLoader()
+ suite = loader.loadByName('twisted.trial.test.sample', True)
+ result = my_runner.run(suite)
+ self.assertEqual(self.standardReport, result._calls)
+
+
+ def test_runner_debug(self):
+ self.parseOptions(['--reporter', 'capturing',
+ '--debug', 'twisted.trial.test.sample'])
+ my_runner = self.getRunner()
+ debugger = CapturingDebugger()
+ def get_debugger():
+ return debugger
+ my_runner._getDebugger = get_debugger
+ loader = runner.TestLoader()
+ suite = loader.loadByName('twisted.trial.test.sample', True)
+ result = my_runner.run(suite)
+ self.assertEqual(self.standardReport, result._calls)
+ self.assertEqual(['runcall'], debugger._calls)
+
+
+ def test_removeSafelyNoTrialMarker(self):
+ """
+ If a path doesn't contain a node named C{"_trial_marker"}, that path is
+ not removed by L{runner._removeSafely} and a L{runner._NoTrialMarker}
+ exception is raised instead.
+ """
+ directory = self.mktemp()
+ os.mkdir(directory)
+ dirPath = filepath.FilePath(directory)
+
+ self.parseOptions([])
+ myRunner = self.getRunner()
+ self.assertRaises(runner._NoTrialMarker,
+ myRunner._removeSafely, dirPath)
+
+
+ def test_removeSafelyRemoveFailsMoveSucceeds(self):
+ """
+ If an L{OSError} is raised while removing a path in
+ L{runner._removeSafely}, an attempt is made to move the path to a new
+ name.
+ """
+ def dummyRemove():
+ """
+ Raise an C{OSError} to emulate the branch of L{runner._removeSafely}
+ in which path removal fails.
+ """
+ raise OSError()
+
+ # Patch stdout so we can check the print statements in _removeSafely
+ out = StringIO.StringIO()
+ stdout = self.patch(sys, 'stdout', out)
+
+ # Set up a trial directory with a _trial_marker
+ directory = self.mktemp()
+ os.mkdir(directory)
+ dirPath = filepath.FilePath(directory)
+ dirPath.child('_trial_marker').touch()
+ # Ensure that path.remove() raises an OSError
+ dirPath.remove = dummyRemove
+
+ self.parseOptions([])
+ myRunner = self.getRunner()
+ myRunner._removeSafely(dirPath)
+ self.assertIn("could not remove FilePath", out.getvalue())
+
+
+ def test_removeSafelyRemoveFailsMoveFails(self):
+ """
+ If an L{OSError} is raised while removing a path in
+ L{runner._removeSafely}, an attempt is made to move the path to a new
+ name. If that attempt fails, the L{OSError} is re-raised.
+ """
+ def dummyRemove():
+ """
+ Raise an C{OSError} to emulate the branch of L{runner._removeSafely}
+ in which path removal fails.
+ """
+ raise OSError("path removal failed")
+
+ def dummyMoveTo(path):
+ """
+ Raise an C{OSError} to emulate the branch of L{runner._removeSafely}
+ in which path movement fails.
+ """
+ raise OSError("path movement failed")
+
+ # Patch stdout so we can check the print statements in _removeSafely
+ out = StringIO.StringIO()
+ stdout = self.patch(sys, 'stdout', out)
+
+ # Set up a trial directory with a _trial_marker
+ directory = self.mktemp()
+ os.mkdir(directory)
+ dirPath = filepath.FilePath(directory)
+ dirPath.child('_trial_marker').touch()
+
+ # Ensure that path.remove() and path.moveTo() both raise OSErrors
+ dirPath.remove = dummyRemove
+ dirPath.moveTo = dummyMoveTo
+
+ self.parseOptions([])
+ myRunner = self.getRunner()
+ error = self.assertRaises(OSError, myRunner._removeSafely, dirPath)
+ self.assertEquals(str(error), "path movement failed")
+ self.assertIn("could not remove FilePath", out.getvalue())
+
+
+
+class TestTrialSuite(unittest.TestCase):
+
+ def test_imports(self):
+ # FIXME, HTF do you test the reactor can be cleaned up ?!!!
+ from twisted.trial.runner import TrialSuite
+ # silence pyflakes warning
+ silencePyflakes = TrialSuite
+
+
+
+
+class TestUntilFailure(unittest.TestCase):
+ class FailAfter(unittest.TestCase):
+ """
+ A test case that fails when run 3 times in a row.
+ """
+ count = []
+ def test_foo(self):
+ self.count.append(None)
+ if len(self.count) == 3:
+ self.fail('Count reached 3')
+
+
+ def setUp(self):
+ TestUntilFailure.FailAfter.count = []
+ self.test = TestUntilFailure.FailAfter('test_foo')
+ self.stream = StringIO.StringIO()
+ self.runner = runner.TrialRunner(reporter.Reporter, stream=self.stream)
+
+
+ def test_runUntilFailure(self):
+ """
+ Test that the runUntilFailure method of the runner actually fail after
+ a few runs.
+ """
+ result = self.runner.runUntilFailure(self.test)
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failIf(result.wasSuccessful())
+ self.assertEquals(self._getFailures(result), 1)
+
+
+ def _getFailures(self, result):
+ """
+ Get the number of failures that were reported to a result.
+ """
+ return len(result.failures)
+
+
+ def test_runUntilFailureDecorate(self):
+ """
+ C{runUntilFailure} doesn't decorate the tests uselessly: it does it one
+ time when run starts, but not at each turn.
+ """
+ decorated = []
+ def decorate(test, interface):
+ decorated.append((test, interface))
+ return test
+ self.patch(unittest, "decorate", decorate)
+ result = self.runner.runUntilFailure(self.test)
+ self.failUnlessEqual(result.testsRun, 1)
+
+ self.assertEquals(len(decorated), 1)
+ self.assertEquals(decorated, [(self.test, ITestCase)])
+
+
+ def test_runUntilFailureForceGCDecorate(self):
+ """
+ C{runUntilFailure} applies the force-gc decoration after the standard
+ L{ITestCase} decoration, but only one time.
+ """
+ decorated = []
+ def decorate(test, interface):
+ decorated.append((test, interface))
+ return test
+ self.patch(unittest, "decorate", decorate)
+ self.runner._forceGarbageCollection = True
+ result = self.runner.runUntilFailure(self.test)
+ self.failUnlessEqual(result.testsRun, 1)
+
+ self.assertEquals(len(decorated), 2)
+ self.assertEquals(decorated,
+ [(self.test, ITestCase),
+ (self.test, unittest._ForceGarbageCollectionDecorator)])
+
+
+
+class UncleanUntilFailureTests(TestUntilFailure):
+ """
+ Test that the run-until-failure feature works correctly with the unclean
+ error suppressor.
+ """
+
+ def setUp(self):
+ TestUntilFailure.setUp(self)
+ self.runner = runner.TrialRunner(reporter.Reporter, stream=self.stream,
+ uncleanWarnings=True)
+
+ def _getFailures(self, result):
+ """
+ Get the number of failures that were reported to a result that
+ is wrapped in an UncleanFailureWrapper.
+ """
+ return len(result._originalReporter.failures)
+
+
+
+class BreakingSuite(runner.TestSuite):
+ """
+ A L{TestSuite} that logs an error when it is run.
+ """
+
+ def run(self, result):
+ try:
+ raise RuntimeError("error that occurs outside of a test")
+ except RuntimeError, e:
+ log.err(failure.Failure())
+
+
+
+class TestLoggedErrors(unittest.TestCase):
+ """
+ It is possible for an error generated by a test to be logged I{outside} of
+ any test. The log observers constructed by L{TestCase} won't catch these
+ errors. Here we try to generate such errors and ensure they are reported to
+ a L{TestResult} object.
+ """
+
+ def tearDown(self):
+ self.flushLoggedErrors(RuntimeError)
+
+
+ def test_construct(self):
+ """
+ Check that we can construct a L{runner.LoggedSuite} and that it
+ starts empty.
+ """
+ suite = runner.LoggedSuite()
+ self.assertEqual(suite.countTestCases(), 0)
+
+
+ def test_capturesError(self):
+ """
+ Chek that a L{LoggedSuite} reports any logged errors to its result.
+ """
+ result = reporter.TestResult()
+ suite = runner.LoggedSuite([BreakingSuite()])
+ suite.run(result)
+ self.assertEqual(len(result.errors), 1)
+ self.assertEqual(result.errors[0][0].id(), runner.NOT_IN_TEST)
+ self.failUnless(result.errors[0][1].check(RuntimeError))
+
+
+
+class TestTestHolder(unittest.TestCase):
+
+ def setUp(self):
+ self.description = "description"
+ self.holder = runner.TestHolder(self.description)
+
+
+ def test_holder(self):
+ """
+ Check that L{runner.TestHolder} takes a description as a parameter
+ and that this description is returned by the C{id} and
+ C{shortDescription} methods.
+ """
+ self.assertEqual(self.holder.id(), self.description)
+ self.assertEqual(self.holder.shortDescription(), self.description)
+
+
+ def test_holderImplementsITestCase(self):
+ """
+ L{runner.TestHolder} implements L{ITestCase}.
+ """
+ self.assertIdentical(self.holder, ITestCase(self.holder))
+
+
+
+class TestErrorHolder(TestTestHolder):
+ """
+ Test L{runner.ErrorHolder} shares behaviour with L{runner.TestHolder}.
+ """
+
+ def setUp(self):
+ self.description = "description"
+ # make a real Failure so we can construct ErrorHolder()
+ try:
+ 1/0
+ except ZeroDivisionError:
+ error = failure.Failure()
+ self.holder = runner.ErrorHolder(self.description, error)
+
+
+
+class TestMalformedMethod(unittest.TestCase):
+ """
+ Test that trial manages when test methods don't have correct signatures.
+ """
+ class ContainMalformed(unittest.TestCase):
+ """
+ This TestCase holds malformed test methods that trial should handle.
+ """
+ def test_foo(self, blah):
+ pass
+ def test_bar():
+ pass
+ test_spam = defer.deferredGenerator(test_bar)
+
+ def _test(self, method):
+ """
+ Wrapper for one of the test method of L{ContainMalformed}.
+ """
+ stream = StringIO.StringIO()
+ trialRunner = runner.TrialRunner(reporter.Reporter, stream=stream)
+ test = TestMalformedMethod.ContainMalformed(method)
+ result = trialRunner.run(test)
+ self.failUnlessEqual(result.testsRun, 1)
+ self.failIf(result.wasSuccessful())
+ self.failUnlessEqual(len(result.errors), 1)
+
+ def test_extraArg(self):
+ """
+ Test when the method has extra (useless) arguments.
+ """
+ self._test('test_foo')
+
+ def test_noArg(self):
+ """
+ Test when the method doesn't have even self as argument.
+ """
+ self._test('test_bar')
+
+ def test_decorated(self):
+ """
+ Test a decorated method also fails.
+ """
+ self._test('test_spam')
+
+
+
+class DestructiveTestSuiteTestCase(unittest.TestCase):
+ """
+ Test for L{runner.DestructiveTestSuite}.
+ """
+
+ def test_basic(self):
+ """
+ Thes destructive test suite should run the tests normally.
+ """
+ called = []
+ class MockTest(unittest.TestCase):
+ def test_foo(test):
+ called.append(True)
+ test = MockTest('test_foo')
+ result = reporter.TestResult()
+ suite = runner.DestructiveTestSuite([test])
+ self.assertEquals(called, [])
+ suite.run(result)
+ self.assertEquals(called, [True])
+ self.assertEquals(suite.countTestCases(), 0)
+
+
+ def test_shouldStop(self):
+ """
+ Test the C{shouldStop} management: raising a C{KeyboardInterrupt} must
+ interrupt the suite.
+ """
+ called = []
+ class MockTest(unittest.TestCase):
+ def test_foo1(test):
+ called.append(1)
+ def test_foo2(test):
+ raise KeyboardInterrupt()
+ def test_foo3(test):
+ called.append(2)
+ result = reporter.TestResult()
+ loader = runner.TestLoader()
+ loader.suiteFactory = runner.DestructiveTestSuite
+ suite = loader.loadClass(MockTest)
+ self.assertEquals(called, [])
+ suite.run(result)
+ self.assertEquals(called, [1])
+ # The last test shouldn't have been run
+ self.assertEquals(suite.countTestCases(), 1)
+
+
+ def test_cleanup(self):
+ """
+ Checks that the test suite cleanups its tests during the run, so that
+ it ends empty.
+ """
+ class MockTest(unittest.TestCase):
+ def test_foo(test):
+ pass
+ test = MockTest('test_foo')
+ result = reporter.TestResult()
+ suite = runner.DestructiveTestSuite([test])
+ self.assertEquals(suite.countTestCases(), 1)
+ suite.run(result)
+ self.assertEquals(suite.countTestCases(), 0)
+
+
+
+class TestRunnerDeprecation(unittest.TestCase):
+
+ class FakeReporter(reporter.Reporter):
+ """
+ Fake reporter that does *not* implement done() but *does* implement
+ printErrors, separator, printSummary, stream, write and writeln
+ without deprecations.
+ """
+
+ done = None
+ separator = None
+ stream = None
+
+ def printErrors(self, *args):
+ pass
+
+ def printSummary(self, *args):
+ pass
+
+ def write(self, *args):
+ pass
+
+ def writeln(self, *args):
+ pass
+
+
+ def test_reporterDeprecations(self):
+ """
+ The runner emits a warning if it is using a result that doesn't
+ implement 'done'.
+ """
+ trialRunner = runner.TrialRunner(None)
+ result = self.FakeReporter()
+ trialRunner._makeResult = lambda: result
+ def f():
+ # We have to use a pyunit test, otherwise we'll get deprecation
+ # warnings about using iterate() in a test.
+ trialRunner.run(pyunit.TestCase('id'))
+ self.assertWarns(
+ DeprecationWarning,
+ "%s should implement done() but doesn't. Falling back to "
+ "printErrors() and friends." % reflect.qual(result.__class__),
+ __file__, f)
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_script.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_script.py
new file mode 100644
index 0000000000..d79e810e4a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_script.py
@@ -0,0 +1,390 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import gc
+import StringIO, sys, types
+
+from twisted.trial import unittest, runner
+from twisted.scripts import trial
+from twisted.python import util
+from twisted.python.compat import set
+
+from twisted.trial.test.test_loader import testNames
+
+pyunit = __import__('unittest')
+
+
+def sibpath(filename):
+ """For finding files in twisted/trial/test"""
+ return util.sibpath(__file__, filename)
+
+
+
+class ForceGarbageCollection(unittest.TestCase):
+ """
+ Tests for the --force-gc option.
+ """
+
+ def setUp(self):
+ self.config = trial.Options()
+ self.log = []
+ self.patch(gc, 'collect', self.collect)
+ test = pyunit.FunctionTestCase(self.simpleTest)
+ self.test = runner.TestSuite([test, test])
+
+
+ def simpleTest(self):
+ """
+ A simple test method that records that it was run.
+ """
+ self.log.append('test')
+
+
+ def collect(self):
+ """
+ A replacement for gc.collect that logs calls to itself.
+ """
+ self.log.append('collect')
+
+
+ def makeRunner(self):
+ """
+ Return a L{runner.TrialRunner} object that is safe to use in tests.
+ """
+ runner = trial._makeRunner(self.config)
+ runner.stream = StringIO.StringIO()
+ return runner
+
+
+ def test_forceGc(self):
+ """
+ Passing the --force-gc option to the trial script forces the garbage
+ collector to run before and after each test.
+ """
+ self.config['force-gc'] = True
+ self.config.postOptions()
+ runner = self.makeRunner()
+ runner.run(self.test)
+ self.assertEqual(self.log, ['collect', 'test', 'collect',
+ 'collect', 'test', 'collect'])
+
+
+ def test_unforceGc(self):
+ """
+ By default, no garbage collection is forced.
+ """
+ self.config.postOptions()
+ runner = self.makeRunner()
+ runner.run(self.test)
+ self.assertEqual(self.log, ['test', 'test'])
+
+
+
+class TestSuiteUsed(unittest.TestCase):
+ """
+ Check the category of tests suite used by the loader.
+ """
+
+ def setUp(self):
+ """
+ Create a trial configuration object.
+ """
+ self.config = trial.Options()
+
+
+ def test_defaultSuite(self):
+ """
+ By default, the loader should use L{runner.DestructiveTestSuite}
+ """
+ loader = trial._getLoader(self.config)
+ self.assertEquals(loader.suiteFactory, runner.DestructiveTestSuite)
+
+
+ def test_untilFailureSuite(self):
+ """
+ The C{until-failure} configuration uses the L{runner.TestSuite} to keep
+ instances alive across runs.
+ """
+ self.config['until-failure'] = True
+ loader = trial._getLoader(self.config)
+ self.assertEquals(loader.suiteFactory, runner.TestSuite)
+
+
+
+class TestModuleTest(unittest.TestCase):
+ def setUp(self):
+ self.config = trial.Options()
+
+ def tearDown(self):
+ self.config = None
+
+ def test_testNames(self):
+ """
+ Check that the testNames helper method accurately collects the
+ names of tests in suite.
+ """
+ self.assertEqual(testNames(self), [self.id()])
+
+ def assertSuitesEqual(self, test1, names):
+ loader = runner.TestLoader()
+ names1 = testNames(test1)
+ names2 = testNames(runner.TestSuite(map(loader.loadByName, names)))
+ names1.sort()
+ names2.sort()
+ self.assertEqual(names1, names2)
+
+ def test_baseState(self):
+ self.failUnlessEqual(0, len(self.config['tests']))
+
+ def test_testmoduleOnModule(self):
+ """
+ Check that --testmodule loads a suite which contains the tests
+ referred to in test-case-name inside its parameter.
+ """
+ self.config.opt_testmodule(sibpath('moduletest.py'))
+ self.assertSuitesEqual(trial._getSuite(self.config),
+ ['twisted.trial.test.test_test_visitor'])
+
+ def test_testmoduleTwice(self):
+ """
+ When the same module is specified with two --testmodule flags, it
+ should only appear once in the suite.
+ """
+ self.config.opt_testmodule(sibpath('moduletest.py'))
+ self.config.opt_testmodule(sibpath('moduletest.py'))
+ self.assertSuitesEqual(trial._getSuite(self.config),
+ ['twisted.trial.test.test_test_visitor'])
+
+ def test_testmoduleOnSourceAndTarget(self):
+ """
+ If --testmodule is specified twice, once for module A and once for
+ a module which refers to module A, then make sure module A is only
+ added once.
+ """
+ self.config.opt_testmodule(sibpath('moduletest.py'))
+ self.config.opt_testmodule(sibpath('test_test_visitor.py'))
+ self.assertSuitesEqual(trial._getSuite(self.config),
+ ['twisted.trial.test.test_test_visitor'])
+
+ def test_testmoduleOnSelfModule(self):
+ """
+ When given a module that refers to *itself* in the test-case-name
+ variable, check that --testmodule only adds the tests once.
+ """
+ self.config.opt_testmodule(sibpath('moduleself.py'))
+ self.assertSuitesEqual(trial._getSuite(self.config),
+ ['twisted.trial.test.moduleself'])
+
+ def test_testmoduleOnScript(self):
+ """
+ Check that --testmodule loads tests referred to in test-case-name
+ buffer variables.
+ """
+ self.config.opt_testmodule(sibpath('scripttest.py'))
+ self.assertSuitesEqual(trial._getSuite(self.config),
+ ['twisted.trial.test.test_test_visitor',
+ 'twisted.trial.test.test_class'])
+
+ def test_testmoduleOnNonexistentFile(self):
+ """
+ Check that --testmodule displays a meaningful error message when
+ passed a non-existent filename.
+ """
+ buffy = StringIO.StringIO()
+ stderr, sys.stderr = sys.stderr, buffy
+ filename = 'test_thisbetternoteverexist.py'
+ try:
+ self.config.opt_testmodule(filename)
+ self.failUnlessEqual(0, len(self.config['tests']))
+ self.failUnlessEqual("File %r doesn't exist\n" % (filename,),
+ buffy.getvalue())
+ finally:
+ sys.stderr = stderr
+
+ def test_testmoduleOnEmptyVars(self):
+ """
+ Check that --testmodule adds no tests to the suite for modules
+ which lack test-case-name buffer variables.
+ """
+ self.config.opt_testmodule(sibpath('novars.py'))
+ self.failUnlessEqual(0, len(self.config['tests']))
+
+ def test_testmoduleOnModuleName(self):
+ """
+ Check that --testmodule does *not* support module names as arguments
+ and that it displays a meaningful error message.
+ """
+ buffy = StringIO.StringIO()
+ stderr, sys.stderr = sys.stderr, buffy
+ moduleName = 'twisted.trial.test.test_script'
+ try:
+ self.config.opt_testmodule(moduleName)
+ self.failUnlessEqual(0, len(self.config['tests']))
+ self.failUnlessEqual("File %r doesn't exist\n" % (moduleName,),
+ buffy.getvalue())
+ finally:
+ sys.stderr = stderr
+
+ def test_parseLocalVariable(self):
+ declaration = '-*- test-case-name: twisted.trial.test.test_tests -*-'
+ localVars = trial._parseLocalVariables(declaration)
+ self.failUnlessEqual({'test-case-name':
+ 'twisted.trial.test.test_tests'},
+ localVars)
+
+ def test_trailingSemicolon(self):
+ declaration = '-*- test-case-name: twisted.trial.test.test_tests; -*-'
+ localVars = trial._parseLocalVariables(declaration)
+ self.failUnlessEqual({'test-case-name':
+ 'twisted.trial.test.test_tests'},
+ localVars)
+
+ def test_parseLocalVariables(self):
+ declaration = ('-*- test-case-name: twisted.trial.test.test_tests; '
+ 'foo: bar -*-')
+ localVars = trial._parseLocalVariables(declaration)
+ self.failUnlessEqual({'test-case-name':
+ 'twisted.trial.test.test_tests',
+ 'foo': 'bar'},
+ localVars)
+
+ def test_surroundingGuff(self):
+ declaration = ('## -*- test-case-name: '
+ 'twisted.trial.test.test_tests -*- #')
+ localVars = trial._parseLocalVariables(declaration)
+ self.failUnlessEqual({'test-case-name':
+ 'twisted.trial.test.test_tests'},
+ localVars)
+
+ def test_invalidLine(self):
+ self.failUnlessRaises(ValueError, trial._parseLocalVariables,
+ 'foo')
+
+ def test_invalidDeclaration(self):
+ self.failUnlessRaises(ValueError, trial._parseLocalVariables,
+ '-*- foo -*-')
+ self.failUnlessRaises(ValueError, trial._parseLocalVariables,
+ '-*- foo: bar; qux -*-')
+ self.failUnlessRaises(ValueError, trial._parseLocalVariables,
+ '-*- foo: bar: baz; qux: qax -*-')
+
+ def test_variablesFromFile(self):
+ localVars = trial.loadLocalVariables(sibpath('moduletest.py'))
+ self.failUnlessEqual({'test-case-name':
+ 'twisted.trial.test.test_test_visitor'},
+ localVars)
+
+ def test_noVariablesInFile(self):
+ localVars = trial.loadLocalVariables(sibpath('novars.py'))
+ self.failUnlessEqual({}, localVars)
+
+ def test_variablesFromScript(self):
+ localVars = trial.loadLocalVariables(sibpath('scripttest.py'))
+ self.failUnlessEqual(
+ {'test-case-name': ('twisted.trial.test.test_test_visitor,'
+ 'twisted.trial.test.test_class')},
+ localVars)
+
+ def test_getTestModules(self):
+ modules = trial.getTestModules(sibpath('moduletest.py'))
+ self.failUnlessEqual(modules, ['twisted.trial.test.test_test_visitor'])
+
+ def test_getTestModules_noVars(self):
+ modules = trial.getTestModules(sibpath('novars.py'))
+ self.failUnlessEqual(len(modules), 0)
+
+ def test_getTestModules_multiple(self):
+ modules = trial.getTestModules(sibpath('scripttest.py'))
+ self.failUnlessEqual(set(modules),
+ set(['twisted.trial.test.test_test_visitor',
+ 'twisted.trial.test.test_class']))
+
+ def test_looksLikeTestModule(self):
+ for filename in ['test_script.py', 'twisted/trial/test/test_script.py']:
+ self.failUnless(trial.isTestFile(filename),
+ "%r should be a test file" % (filename,))
+ for filename in ['twisted/trial/test/moduletest.py',
+ sibpath('scripttest.py'), sibpath('test_foo.bat')]:
+ self.failIf(trial.isTestFile(filename),
+ "%r should *not* be a test file" % (filename,))
+
+
+class WithoutModuleTests(unittest.TestCase):
+ """
+ Test the C{without-module} flag.
+ """
+
+ def setUp(self):
+ """
+ Create a L{trial.Options} object to be used in the tests, and save
+ C{sys.modules}.
+ """
+ self.config = trial.Options()
+ self.savedModules = dict(sys.modules)
+
+
+ def tearDown(self):
+ """
+ Restore C{sys.modules}.
+ """
+ for module in ('imaplib', 'smtplib'):
+ if module in self.savedModules:
+ sys.modules[module] = self.savedModules[module]
+ else:
+ sys.modules.pop(module, None)
+
+
+ def _checkSMTP(self):
+ """
+ Try to import the C{smtplib} module, and return it.
+ """
+ import smtplib
+ return smtplib
+
+
+ def _checkIMAP(self):
+ """
+ Try to import the C{imaplib} module, and return it.
+ """
+ import imaplib
+ return imaplib
+
+
+ def test_disableOneModule(self):
+ """
+ Check that after disabling a module, it can't be imported anymore.
+ """
+ self.config.parseOptions(["--without-module", "smtplib"])
+ self.assertRaises(ImportError, self._checkSMTP)
+ # Restore sys.modules
+ del sys.modules["smtplib"]
+ # Then the function should succeed
+ self.assertIsInstance(self._checkSMTP(), types.ModuleType)
+
+
+ def test_disableMultipleModules(self):
+ """
+ Check that several modules can be disabled at once.
+ """
+ self.config.parseOptions(["--without-module", "smtplib,imaplib"])
+ self.assertRaises(ImportError, self._checkSMTP)
+ self.assertRaises(ImportError, self._checkIMAP)
+ # Restore sys.modules
+ del sys.modules["smtplib"]
+ del sys.modules["imaplib"]
+ # Then the functions should succeed
+ self.assertIsInstance(self._checkSMTP(), types.ModuleType)
+ self.assertIsInstance(self._checkIMAP(), types.ModuleType)
+
+
+ def test_disableAlreadyImportedModule(self):
+ """
+ Disabling an already imported module should produce a warning.
+ """
+ self.assertIsInstance(self._checkSMTP(), types.ModuleType)
+ self.assertWarns(RuntimeWarning,
+ "Module 'smtplib' already imported, disabling anyway.",
+ trial.__file__,
+ self.config.parseOptions, ["--without-module", "smtplib"])
+ self.assertRaises(ImportError, self._checkSMTP)
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_test_visitor.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_test_visitor.py
new file mode 100644
index 0000000000..b5c3484e49
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_test_visitor.py
@@ -0,0 +1,82 @@
+from twisted.trial import unittest
+from twisted.trial.runner import TestSuite, suiteVisit
+
+pyunit = __import__('unittest')
+
+
+
+class MockVisitor(object):
+ def __init__(self):
+ self.calls = []
+
+
+ def __call__(self, testCase):
+ self.calls.append(testCase)
+
+
+
+class TestTestVisitor(unittest.TestCase):
+ def setUp(self):
+ self.visitor = MockVisitor()
+
+
+ def test_visitCase(self):
+ """
+ Test that C{visit} works for a single test case.
+ """
+ testCase = TestTestVisitor('test_visitCase')
+ testCase.visit(self.visitor)
+ self.assertEqual(self.visitor.calls, [testCase])
+
+
+ def test_visitSuite(self):
+ """
+ Test that C{visit} hits all tests in a suite.
+ """
+ tests = [TestTestVisitor('test_visitCase'),
+ TestTestVisitor('test_visitSuite')]
+ testSuite = TestSuite(tests)
+ testSuite.visit(self.visitor)
+ self.assertEqual(self.visitor.calls, tests)
+
+
+ def test_visitEmptySuite(self):
+ """
+ Test that C{visit} on an empty suite hits nothing.
+ """
+ TestSuite().visit(self.visitor)
+ self.assertEqual(self.visitor.calls, [])
+
+
+ def test_visitNestedSuite(self):
+ """
+ Test that C{visit} recurses through suites.
+ """
+ tests = [TestTestVisitor('test_visitCase'),
+ TestTestVisitor('test_visitSuite')]
+ testSuite = TestSuite([TestSuite([test]) for test in tests])
+ testSuite.visit(self.visitor)
+ self.assertEqual(self.visitor.calls, tests)
+
+
+ def test_visitPyunitSuite(self):
+ """
+ Test that C{suiteVisit} visits stdlib unittest suites
+ """
+ test = TestTestVisitor('test_visitPyunitSuite')
+ suite = pyunit.TestSuite([test])
+ suiteVisit(suite, self.visitor)
+ self.assertEqual(self.visitor.calls, [test])
+
+
+ def test_visitPyunitCase(self):
+ """
+ Test that a stdlib test case in a suite gets visited.
+ """
+ class PyunitCase(pyunit.TestCase):
+ def test_foo(self):
+ pass
+ test = PyunitCase('test_foo')
+ TestSuite([test]).visit(self.visitor)
+ self.assertEqual(
+ [call.id() for call in self.visitor.calls], [test.id()])
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_testcase.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_testcase.py
new file mode 100644
index 0000000000..8ff8d485e8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_testcase.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Direct unit tests for L{twisted.trial.unittest.TestCase}.
+"""
+
+from twisted.trial.unittest import TestCase
+
+
+class TestCaseTests(TestCase):
+ """
+ L{TestCase} tests.
+ """
+ class MyTestCase(TestCase):
+ """
+ Some test methods which can be used to test behaviors of
+ L{TestCase}.
+ """
+ def test_1(self):
+ pass
+
+ def setUp(self):
+ """
+ Create a couple instances of C{MyTestCase}, each for the same test
+ method, to be used in the test methods of this class.
+ """
+ self.first = self.MyTestCase('test_1')
+ self.second = self.MyTestCase('test_1')
+
+
+ def test_equality(self):
+ """
+ In order for one test method to be runnable twice, two TestCase
+ instances with the same test method name must not compare as equal.
+ """
+ self.assertTrue(self.first == self.first)
+ self.assertTrue(self.first != self.second)
+ self.assertFalse(self.first == self.second)
+
+
+ def test_hashability(self):
+ """
+ In order for one test method to be runnable twice, two TestCase
+ instances with the same test method name should not have the same
+ hash value.
+ """
+ container = {}
+ container[self.first] = None
+ container[self.second] = None
+ self.assertEquals(len(container), 2)
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_tests.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_tests.py
new file mode 100644
index 0000000000..4834da730f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_tests.py
@@ -0,0 +1,1056 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for the behaviour of unit tests.
+"""
+
+import gc, StringIO, sys, weakref
+
+from twisted.internet import defer, reactor
+from twisted.trial import unittest, runner, reporter, util
+from twisted.trial.test import erroneous, suppression
+from twisted.trial.test.test_reporter import LoggingReporter
+
+
+class ResultsTestMixin:
+ def loadSuite(self, suite):
+ self.loader = runner.TestLoader()
+ self.suite = self.loader.loadClass(suite)
+ self.reporter = reporter.TestResult()
+
+ def test_setUp(self):
+ self.failUnless(self.reporter.wasSuccessful())
+ self.failUnlessEqual(self.reporter.errors, [])
+ self.failUnlessEqual(self.reporter.failures, [])
+ self.failUnlessEqual(self.reporter.skips, [])
+
+ def assertCount(self, numTests):
+ self.failUnlessEqual(self.suite.countTestCases(), numTests)
+ self.suite(self.reporter)
+ self.failUnlessEqual(self.reporter.testsRun, numTests)
+
+
+
+class TestSuccess(unittest.TestCase):
+ """
+ Test that successful tests are reported as such.
+ """
+
+ def setUp(self):
+ self.result = reporter.TestResult()
+
+
+ def test_successful(self):
+ """
+ A successful test, used by other tests.
+ """
+
+
+ def assertSuccessful(self, test, result):
+ self.assertEqual(result.successes, 1)
+ self.assertEqual(result.failures, [])
+ self.assertEqual(result.errors, [])
+ self.assertEqual(result.expectedFailures, [])
+ self.assertEqual(result.unexpectedSuccesses, [])
+ self.assertEqual(result.skips, [])
+
+
+ def test_successfulIsReported(self):
+ """
+ Test that when a successful test is run, it is reported as a success,
+ and not as any other kind of result.
+ """
+ test = TestSuccess('test_successful')
+ test.run(self.result)
+ self.assertSuccessful(test, self.result)
+
+
+ def test_defaultIsSuccessful(self):
+ """
+ Test that L{unittest.TestCase} itself can be instantiated, run, and
+ reported as being successful.
+ """
+ test = unittest.TestCase()
+ test.run(self.result)
+ self.assertSuccessful(test, self.result)
+
+
+ def test_noReference(self):
+ """
+ Test that no reference is kept on a successful test.
+ """
+ test = TestSuccess('test_successful')
+ ref = weakref.ref(test)
+ test.run(self.result)
+ self.assertSuccessful(test, self.result)
+ del test
+ gc.collect()
+ self.assertIdentical(ref(), None)
+
+
+
+class TestSkipMethods(unittest.TestCase, ResultsTestMixin):
+ class SkippingTests(unittest.TestCase):
+ def test_skip1(self):
+ raise unittest.SkipTest('skip1')
+
+ def test_skip2(self):
+ raise RuntimeError("I should not get raised")
+ test_skip2.skip = 'skip2'
+
+ def test_skip3(self):
+ self.fail('I should not fail')
+ test_skip3.skip = 'skip3'
+
+ class SkippingSetUp(unittest.TestCase):
+ def setUp(self):
+ raise unittest.SkipTest('skipSetUp')
+
+ def test_1(self):
+ pass
+
+ def test_2(self):
+ pass
+
+ def setUp(self):
+ self.loadSuite(TestSkipMethods.SkippingTests)
+
+ def test_counting(self):
+ self.assertCount(3)
+
+ def test_results(self):
+ self.suite(self.reporter)
+ self.failUnless(self.reporter.wasSuccessful())
+ self.failUnlessEqual(self.reporter.errors, [])
+ self.failUnlessEqual(self.reporter.failures, [])
+ self.failUnlessEqual(len(self.reporter.skips), 3)
+
+ def test_setUp(self):
+ self.loadSuite(TestSkipMethods.SkippingSetUp)
+ self.suite(self.reporter)
+ self.failUnless(self.reporter.wasSuccessful())
+ self.failUnlessEqual(self.reporter.errors, [])
+ self.failUnlessEqual(self.reporter.failures, [])
+ self.failUnlessEqual(len(self.reporter.skips), 2)
+
+ def test_reasons(self):
+ self.suite(self.reporter)
+ prefix = 'test_'
+ # whiteboxing reporter
+ for test, reason in self.reporter.skips:
+ self.failUnlessEqual(test.shortDescription()[len(prefix):],
+ str(reason))
+
+
+class TestSkipClasses(unittest.TestCase, ResultsTestMixin):
+ class SkippedClass(unittest.TestCase):
+ skip = 'class'
+ def setUp(self):
+ self.__class__._setUpRan = True
+ def test_skip1(self):
+ raise unittest.SkipTest('skip1')
+ def test_skip2(self):
+ raise RuntimeError("Ought to skip me")
+ test_skip2.skip = 'skip2'
+ def test_skip3(self):
+ pass
+ def test_skip4(self):
+ raise RuntimeError("Skip me too")
+
+
+ def setUp(self):
+ self.loadSuite(TestSkipClasses.SkippedClass)
+ TestSkipClasses.SkippedClass._setUpRan = False
+
+
+ def test_counting(self):
+ """
+ Skipped test methods still contribute to the total test count.
+ """
+ self.assertCount(4)
+
+
+ def test_setUpRan(self):
+ """
+ The C{setUp} method is not called if the class is set to skip.
+ """
+ self.suite(self.reporter)
+ self.assertFalse(TestSkipClasses.SkippedClass._setUpRan)
+
+
+ def test_results(self):
+ """
+ Skipped test methods don't cause C{wasSuccessful} to return C{False},
+ nor do they contribute to the C{errors} or C{failures} of the reporter.
+ They do, however, add elements to the reporter's C{skips} list.
+ """
+ self.suite(self.reporter)
+ self.failUnless(self.reporter.wasSuccessful())
+ self.failUnlessEqual(self.reporter.errors, [])
+ self.failUnlessEqual(self.reporter.failures, [])
+ self.failUnlessEqual(len(self.reporter.skips), 4)
+
+
+ def test_reasons(self):
+ """
+ Test methods which raise L{unittest.SkipTest} or have their C{skip}
+ attribute set to something are skipped.
+ """
+ self.suite(self.reporter)
+ expectedReasons = ['class', 'skip2', 'class', 'class']
+ # whitebox reporter
+ reasonsGiven = [reason for test, reason in self.reporter.skips]
+ self.assertEquals(expectedReasons, reasonsGiven)
+
+
+
+class TestTodo(unittest.TestCase, ResultsTestMixin):
+ class TodoTests(unittest.TestCase):
+ def test_todo1(self):
+ self.fail("deliberate failure")
+ test_todo1.todo = "todo1"
+
+ def test_todo2(self):
+ raise RuntimeError("deliberate error")
+ test_todo2.todo = "todo2"
+
+ def test_todo3(self):
+ """unexpected success"""
+ test_todo3.todo = 'todo3'
+
+ def setUp(self):
+ self.loadSuite(TestTodo.TodoTests)
+
+ def test_counting(self):
+ self.assertCount(3)
+
+ def test_results(self):
+ self.suite(self.reporter)
+ self.failUnless(self.reporter.wasSuccessful())
+ self.failUnlessEqual(self.reporter.errors, [])
+ self.failUnlessEqual(self.reporter.failures, [])
+ self.failUnlessEqual(self.reporter.skips, [])
+ self.failUnlessEqual(len(self.reporter.expectedFailures), 2)
+ self.failUnlessEqual(len(self.reporter.unexpectedSuccesses), 1)
+
+ def test_expectedFailures(self):
+ self.suite(self.reporter)
+ expectedReasons = ['todo1', 'todo2']
+ reasonsGiven = [ r.reason
+ for t, e, r in self.reporter.expectedFailures ]
+ self.failUnlessEqual(expectedReasons, reasonsGiven)
+
+ def test_unexpectedSuccesses(self):
+ self.suite(self.reporter)
+ expectedReasons = ['todo3']
+ reasonsGiven = [ r.reason
+ for t, r in self.reporter.unexpectedSuccesses ]
+ self.failUnlessEqual(expectedReasons, reasonsGiven)
+
+
+class TestTodoClass(unittest.TestCase, ResultsTestMixin):
+ class TodoClass(unittest.TestCase):
+ def test_todo1(self):
+ pass
+ test_todo1.todo = "method"
+ def test_todo2(self):
+ pass
+ def test_todo3(self):
+ self.fail("Deliberate Failure")
+ test_todo3.todo = "method"
+ def test_todo4(self):
+ self.fail("Deliberate Failure")
+ TodoClass.todo = "class"
+
+ def setUp(self):
+ self.loadSuite(TestTodoClass.TodoClass)
+
+ def test_counting(self):
+ self.assertCount(4)
+
+ def test_results(self):
+ self.suite(self.reporter)
+ self.failUnless(self.reporter.wasSuccessful())
+ self.failUnlessEqual(self.reporter.errors, [])
+ self.failUnlessEqual(self.reporter.failures, [])
+ self.failUnlessEqual(self.reporter.skips, [])
+ self.failUnlessEqual(len(self.reporter.expectedFailures), 2)
+ self.failUnlessEqual(len(self.reporter.unexpectedSuccesses), 2)
+
+ def test_expectedFailures(self):
+ self.suite(self.reporter)
+ expectedReasons = ['method', 'class']
+ reasonsGiven = [ r.reason
+ for t, e, r in self.reporter.expectedFailures ]
+ self.failUnlessEqual(expectedReasons, reasonsGiven)
+
+ def test_unexpectedSuccesses(self):
+ self.suite(self.reporter)
+ expectedReasons = ['method', 'class']
+ reasonsGiven = [ r.reason
+ for t, r in self.reporter.unexpectedSuccesses ]
+ self.failUnlessEqual(expectedReasons, reasonsGiven)
+
+
+class TestStrictTodo(unittest.TestCase, ResultsTestMixin):
+ class Todos(unittest.TestCase):
+ def test_todo1(self):
+ raise RuntimeError, "expected failure"
+ test_todo1.todo = (RuntimeError, "todo1")
+
+ def test_todo2(self):
+ raise RuntimeError, "expected failure"
+ test_todo2.todo = ((RuntimeError, OSError), "todo2")
+
+ def test_todo3(self):
+ raise RuntimeError, "we had no idea!"
+ test_todo3.todo = (OSError, "todo3")
+
+ def test_todo4(self):
+ raise RuntimeError, "we had no idea!"
+ test_todo4.todo = ((OSError, SyntaxError), "todo4")
+
+ def test_todo5(self):
+ self.fail("deliberate failure")
+ test_todo5.todo = (unittest.FailTest, "todo5")
+
+ def test_todo6(self):
+ self.fail("deliberate failure")
+ test_todo6.todo = (RuntimeError, "todo6")
+
+ def test_todo7(self):
+ pass
+ test_todo7.todo = (RuntimeError, "todo7")
+
+ def setUp(self):
+ self.loadSuite(TestStrictTodo.Todos)
+
+ def test_counting(self):
+ self.assertCount(7)
+
+ def test_results(self):
+ self.suite(self.reporter)
+ self.failIf(self.reporter.wasSuccessful())
+ self.failUnlessEqual(len(self.reporter.errors), 2)
+ self.failUnlessEqual(len(self.reporter.failures), 1)
+ self.failUnlessEqual(len(self.reporter.expectedFailures), 3)
+ self.failUnlessEqual(len(self.reporter.unexpectedSuccesses), 1)
+ self.failUnlessEqual(self.reporter.skips, [])
+
+ def test_expectedFailures(self):
+ self.suite(self.reporter)
+ expectedReasons = ['todo1', 'todo2', 'todo5']
+ reasonsGotten = [ r.reason
+ for t, e, r in self.reporter.expectedFailures ]
+ self.failUnlessEqual(expectedReasons, reasonsGotten)
+
+ def test_unexpectedSuccesses(self):
+ self.suite(self.reporter)
+ expectedReasons = [([RuntimeError], 'todo7')]
+ reasonsGotten = [ (r.errors, r.reason)
+ for t, r in self.reporter.unexpectedSuccesses ]
+ self.failUnlessEqual(expectedReasons, reasonsGotten)
+
+
+
+class TestCleanup(unittest.TestCase):
+
+ def setUp(self):
+ self.result = reporter.Reporter(StringIO.StringIO())
+ self.loader = runner.TestLoader()
+
+
+ def testLeftoverSockets(self):
+ """
+ Trial reports a L{util.DirtyReactorAggregateError} if a test leaves
+ sockets behind.
+ """
+ suite = self.loader.loadMethod(
+ erroneous.SocketOpenTest.test_socketsLeftOpen)
+ suite.run(self.result)
+ self.failIf(self.result.wasSuccessful())
+ # socket cleanup happens at end of class's tests.
+ # all the tests in the class are successful, even if the suite
+ # fails
+ self.assertEqual(self.result.successes, 1)
+ failure = self.result.errors[0][1]
+ self.failUnless(failure.check(util.DirtyReactorAggregateError))
+
+
+ def testLeftoverPendingCalls(self):
+ """
+ Trial reports a L{util.DirtyReactorAggregateError} and fails the test
+ if a test leaves a L{DelayedCall} hanging.
+ """
+ suite = erroneous.ReactorCleanupTests('test_leftoverPendingCalls')
+ suite.run(self.result)
+ self.failIf(self.result.wasSuccessful())
+ failure = self.result.errors[0][1]
+ self.assertEqual(self.result.successes, 0)
+ self.failUnless(failure.check(util.DirtyReactorAggregateError))
+
+
+
+class FixtureTest(unittest.TestCase):
+ """
+ Tests for broken fixture helper methods (e.g. setUp, tearDown).
+ """
+
+ def setUp(self):
+ self.reporter = reporter.Reporter()
+ self.loader = runner.TestLoader()
+
+
+ def testBrokenSetUp(self):
+ """
+ When setUp fails, the error is recorded in the result object.
+ """
+ self.loader.loadClass(erroneous.TestFailureInSetUp).run(self.reporter)
+ self.assert_(len(self.reporter.errors) > 0)
+ self.assert_(isinstance(self.reporter.errors[0][1].value,
+ erroneous.FoolishError))
+
+
+ def testBrokenTearDown(self):
+ """
+ When tearDown fails, the error is recorded in the result object.
+ """
+ suite = self.loader.loadClass(erroneous.TestFailureInTearDown)
+ suite.run(self.reporter)
+ errors = self.reporter.errors
+ self.assert_(len(errors) > 0)
+ self.assert_(isinstance(errors[0][1].value, erroneous.FoolishError))
+
+
+
+class SuppressionTest(unittest.TestCase):
+
+ def runTests(self, suite):
+ suite.run(reporter.TestResult())
+
+
+ def setUp(self):
+ self.loader = runner.TestLoader()
+
+
+ def test_suppressMethod(self):
+ """
+ A suppression set on a test method prevents warnings emitted by that
+ test method which the suppression matches from being emitted.
+ """
+ self.runTests(self.loader.loadMethod(
+ suppression.TestSuppression.testSuppressMethod))
+ warningsShown = self.flushWarnings([
+ suppression.TestSuppression._emit])
+ self.assertEqual(
+ warningsShown[0]['message'], suppression.CLASS_WARNING_MSG)
+ self.assertEqual(
+ warningsShown[1]['message'], suppression.MODULE_WARNING_MSG)
+ self.assertEqual(len(warningsShown), 2)
+
+
+ def test_suppressClass(self):
+ """
+ A suppression set on a L{TestCase} subclass prevents warnings emitted
+ by any test methods defined on that class which match the suppression
+ from being emitted.
+ """
+ self.runTests(self.loader.loadMethod(
+ suppression.TestSuppression.testSuppressClass))
+ warningsShown = self.flushWarnings([
+ suppression.TestSuppression._emit])
+ self.assertEqual(
+ warningsShown[0]['message'], suppression.METHOD_WARNING_MSG)
+ self.assertEqual(
+ warningsShown[1]['message'], suppression.MODULE_WARNING_MSG)
+ self.assertEqual(len(warningsShown), 2)
+
+
+ def test_suppressModule(self):
+ """
+ A suppression set on a module prevents warnings emitted by any test
+ mewthods defined in that module which match the suppression from being
+ emitted.
+ """
+ self.runTests(self.loader.loadMethod(
+ suppression.TestSuppression2.testSuppressModule))
+ warningsShown = self.flushWarnings([
+ suppression.TestSuppression._emit])
+ self.assertEqual(
+ warningsShown[0]['message'], suppression.METHOD_WARNING_MSG)
+ self.assertEqual(
+ warningsShown[1]['message'], suppression.CLASS_WARNING_MSG)
+ self.assertEqual(len(warningsShown), 2)
+
+
+ def test_overrideSuppressClass(self):
+ """
+ The suppression set on a test method completely overrides a suppression
+ with wider scope; if it does not match a warning emitted by that test
+ method, the warning is emitted, even if a wider suppression matches.
+ """
+ case = self.loader.loadMethod(
+ suppression.TestSuppression.testOverrideSuppressClass)
+ self.runTests(case)
+ warningsShown = self.flushWarnings([
+ suppression.TestSuppression._emit])
+ self.assertEqual(
+ warningsShown[0]['message'], suppression.METHOD_WARNING_MSG)
+ self.assertEqual(
+ warningsShown[1]['message'], suppression.CLASS_WARNING_MSG)
+ self.assertEqual(
+ warningsShown[2]['message'], suppression.MODULE_WARNING_MSG)
+ self.assertEqual(len(warningsShown), 3)
+
+
+
+class GCMixin:
+ """
+ I provide a few mock tests that log setUp, tearDown, test execution and
+ garbage collection. I'm used to test whether gc.collect gets called.
+ """
+
+ class BasicTest(unittest.TestCase):
+ def setUp(self):
+ self._log('setUp')
+ def test_foo(self):
+ self._log('test')
+ def tearDown(self):
+ self._log('tearDown')
+
+ class ClassTest(unittest.TestCase):
+ def test_1(self):
+ self._log('test1')
+ def test_2(self):
+ self._log('test2')
+
+ def _log(self, msg):
+ self._collectCalled.append(msg)
+
+ def collect(self):
+ """Fake gc.collect"""
+ self._log('collect')
+
+ def setUp(self):
+ self._collectCalled = []
+ self.BasicTest._log = self.ClassTest._log = self._log
+ self._oldCollect = gc.collect
+ gc.collect = self.collect
+
+ def tearDown(self):
+ gc.collect = self._oldCollect
+
+
+
+class TestGarbageCollectionDefault(GCMixin, unittest.TestCase):
+
+ def test_collectNotDefault(self):
+ """
+ By default, tests should not force garbage collection.
+ """
+ test = self.BasicTest('test_foo')
+ result = reporter.TestResult()
+ test.run(result)
+ self.failUnlessEqual(self._collectCalled, ['setUp', 'test', 'tearDown'])
+
+
+
+class TestGarbageCollection(GCMixin, unittest.TestCase):
+
+ def test_collectCalled(self):
+ """
+ test gc.collect is called before and after each test.
+ """
+ test = TestGarbageCollection.BasicTest('test_foo')
+ test = unittest._ForceGarbageCollectionDecorator(test)
+ result = reporter.TestResult()
+ test.run(result)
+ self.failUnlessEqual(
+ self._collectCalled,
+ ['collect', 'setUp', 'test', 'tearDown', 'collect'])
+
+
+
+class TestUnhandledDeferred(unittest.TestCase):
+
+ def setUp(self):
+ from twisted.trial.test import weird
+ # test_unhandledDeferred creates a cycle. we need explicit control of gc
+ gc.disable()
+ self.test1 = unittest._ForceGarbageCollectionDecorator(
+ weird.TestBleeding('test_unhandledDeferred'))
+
+ def test_isReported(self):
+ """
+ Forcing garbage collection should cause unhandled Deferreds to be
+ reported as errors.
+ """
+ result = reporter.TestResult()
+ self.test1(result)
+ self.assertEqual(len(result.errors), 1,
+ 'Unhandled deferred passed without notice')
+
+ def test_doesntBleed(self):
+ """
+ Forcing garbage collection in the test should mean that there are
+ no unreachable cycles immediately after the test completes.
+ """
+ result = reporter.TestResult()
+ self.test1(result)
+ self.flushLoggedErrors() # test1 logs errors that get caught be us.
+ # test1 created unreachable cycle.
+ # it & all others should have been collected by now.
+ n = gc.collect()
+ self.assertEqual(n, 0, 'unreachable cycle still existed')
+ # check that last gc.collect didn't log more errors
+ x = self.flushLoggedErrors()
+ self.assertEqual(len(x), 0, 'Errors logged after gc.collect')
+
+ def tearDown(self):
+ gc.collect()
+ gc.enable()
+ self.flushLoggedErrors()
+
+
+
+class TestAddCleanup(unittest.TestCase):
+ """
+ Test the addCleanup method of TestCase.
+ """
+
+ class MockTest(unittest.TestCase):
+
+ def setUp(self):
+ self.log = ['setUp']
+
+ def brokenSetUp(self):
+ self.log = ['setUp']
+ raise RuntimeError("Deliberate failure")
+
+ def skippingSetUp(self):
+ self.log = ['setUp']
+ raise unittest.SkipTest("Don't do this")
+
+ def append(self, thing):
+ self.log.append(thing)
+
+ def tearDown(self):
+ self.log.append('tearDown')
+
+ def runTest(self):
+ self.log.append('runTest')
+
+
+ def setUp(self):
+ unittest.TestCase.setUp(self)
+ self.result = reporter.TestResult()
+ self.test = TestAddCleanup.MockTest()
+
+
+ def test_addCleanupCalledIfSetUpFails(self):
+ """
+ Callables added with C{addCleanup} are run even if setUp fails.
+ """
+ self.test.setUp = self.test.brokenSetUp
+ self.test.addCleanup(self.test.append, 'foo')
+ self.test.run(self.result)
+ self.assertEqual(['setUp', 'foo'], self.test.log)
+
+
+ def test_addCleanupCalledIfSetUpSkips(self):
+ """
+ Callables added with C{addCleanup} are run even if setUp raises
+ L{SkipTest}. This allows test authors to reliably provide clean up
+ code using C{addCleanup}.
+ """
+ self.test.setUp = self.test.skippingSetUp
+ self.test.addCleanup(self.test.append, 'foo')
+ self.test.run(self.result)
+ self.assertEqual(['setUp', 'foo'], self.test.log)
+
+
+ def test_addCleanupCalledInReverseOrder(self):
+ """
+ Callables added with C{addCleanup} should be called before C{tearDown}
+ in reverse order of addition.
+ """
+ self.test.addCleanup(self.test.append, "foo")
+ self.test.addCleanup(self.test.append, 'bar')
+ self.test.run(self.result)
+ self.assertEqual(['setUp', 'runTest', 'bar', 'foo', 'tearDown'],
+ self.test.log)
+
+
+ def test_addCleanupWaitsForDeferreds(self):
+ """
+ If an added callable returns a L{Deferred}, then the test should wait
+ until that L{Deferred} has fired before running the next cleanup
+ method.
+ """
+ def cleanup(message):
+ d = defer.Deferred()
+ reactor.callLater(0, d.callback, message)
+ return d.addCallback(self.test.append)
+ self.test.addCleanup(self.test.append, 'foo')
+ self.test.addCleanup(cleanup, 'bar')
+ self.test.run(self.result)
+ self.assertEqual(['setUp', 'runTest', 'bar', 'foo', 'tearDown'],
+ self.test.log)
+
+
+ def test_errorInCleanupIsCaptured(self):
+ """
+ Errors raised in cleanup functions should be treated like errors in
+ C{tearDown}. They should be added as errors and fail the test. Skips,
+ todos and failures are all treated as errors.
+ """
+ self.test.addCleanup(self.test.fail, 'foo')
+ self.test.run(self.result)
+ self.failIf(self.result.wasSuccessful())
+ self.assertEqual(1, len(self.result.errors))
+ [(test, error)] = self.result.errors
+ self.assertEqual(test, self.test)
+ self.assertEqual(error.getErrorMessage(), 'foo')
+
+
+ def test_cleanupsContinueRunningAfterError(self):
+ """
+ If a cleanup raises an error then that does not stop the other
+ cleanups from being run.
+ """
+ self.test.addCleanup(self.test.append, 'foo')
+ self.test.addCleanup(self.test.fail, 'bar')
+ self.test.run(self.result)
+ self.assertEqual(['setUp', 'runTest', 'foo', 'tearDown'],
+ self.test.log)
+ self.assertEqual(1, len(self.result.errors))
+ [(test, error)] = self.result.errors
+ self.assertEqual(test, self.test)
+ self.assertEqual(error.getErrorMessage(), 'bar')
+
+
+ def test_multipleErrorsReported(self):
+ """
+ If more than one cleanup fails, then the test should fail with more
+ than one error.
+ """
+ self.test.addCleanup(self.test.fail, 'foo')
+ self.test.addCleanup(self.test.fail, 'bar')
+ self.test.run(self.result)
+ self.assertEqual(['setUp', 'runTest', 'tearDown'],
+ self.test.log)
+ self.assertEqual(2, len(self.result.errors))
+ [(test1, error1), (test2, error2)] = self.result.errors
+ self.assertEqual(test1, self.test)
+ self.assertEqual(test2, self.test)
+ self.assertEqual(error1.getErrorMessage(), 'bar')
+ self.assertEqual(error2.getErrorMessage(), 'foo')
+
+
+
+class TestSuiteClearing(unittest.TestCase):
+ """
+ Tests for our extension that allows us to clear out a L{TestSuite}.
+ """
+
+
+ def test_clearSuite(self):
+ """
+ Calling L{unittest._clearSuite} on a populated L{TestSuite} removes
+ all tests.
+ """
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.TestCase())
+ # Double check that the test suite actually has something in it.
+ self.assertEqual(1, suite.countTestCases())
+ unittest._clearSuite(suite)
+ self.assertEqual(0, suite.countTestCases())
+
+
+ def test_clearPyunitSuite(self):
+ """
+ Calling L{unittest._clearSuite} on a populated standard library
+ L{TestSuite} removes all tests.
+
+ This test is important since C{_clearSuite} operates by mutating
+ internal variables.
+ """
+ pyunit = __import__('unittest')
+ suite = pyunit.TestSuite()
+ suite.addTest(unittest.TestCase())
+ # Double check that the test suite actually has something in it.
+ self.assertEqual(1, suite.countTestCases())
+ unittest._clearSuite(suite)
+ self.assertEqual(0, suite.countTestCases())
+
+
+
+class TestTestDecorator(unittest.TestCase):
+ """
+ Tests for our test decoration features.
+ """
+
+
+ def assertTestsEqual(self, observed, expected):
+ """
+ Assert that the given decorated tests are equal.
+ """
+ self.assertEqual(observed.__class__, expected.__class__,
+ "Different class")
+ observedOriginal = getattr(observed, '_originalTest', None)
+ expectedOriginal = getattr(expected, '_originalTest', None)
+ self.assertIdentical(observedOriginal, expectedOriginal)
+ if observedOriginal is expectedOriginal is None:
+ self.assertIdentical(observed, expected)
+
+
+ def assertSuitesEqual(self, observed, expected):
+ """
+ Assert that the given test suites with decorated tests are equal.
+ """
+ self.assertEqual(observed.__class__, expected.__class__,
+ "Different class")
+ self.assertEqual(len(observed._tests), len(expected._tests),
+ "Different number of tests.")
+ for observedTest, expectedTest in zip(observed._tests,
+ expected._tests):
+ if getattr(observedTest, '_tests', None) is not None:
+ self.assertSuitesEqual(observedTest, expectedTest)
+ else:
+ self.assertTestsEqual(observedTest, expectedTest)
+
+
+ def test_usesAdaptedReporterWithRun(self):
+ """
+ For decorated tests, C{run} uses a result adapter that preserves the
+ test decoration for calls to C{addError}, C{startTest} and the like.
+
+ See L{reporter._AdaptedReporter}.
+ """
+ test = unittest.TestCase()
+ decoratedTest = unittest.TestDecorator(test)
+ result = LoggingReporter()
+ decoratedTest.run(result)
+ self.assertTestsEqual(result.test, decoratedTest)
+
+
+ def test_usesAdaptedReporterWithCall(self):
+ """
+ For decorated tests, C{__call__} uses a result adapter that preserves
+ the test decoration for calls to C{addError}, C{startTest} and the
+ like.
+
+ See L{reporter._AdaptedReporter}.
+ """
+ test = unittest.TestCase()
+ decoratedTest = unittest.TestDecorator(test)
+ result = LoggingReporter()
+ decoratedTest(result)
+ self.assertTestsEqual(result.test, decoratedTest)
+
+
+ def test_decorateSingleTest(self):
+ """
+ Calling L{decorate} on a single test case returns the test case
+ decorated with the provided decorator.
+ """
+ test = unittest.TestCase()
+ decoratedTest = unittest.decorate(test, unittest.TestDecorator)
+ self.assertTestsEqual(unittest.TestDecorator(test), decoratedTest)
+
+
+ def test_decorateTestSuite(self):
+ """
+ Calling L{decorate} on a test suite will return a test suite with
+ each test decorated with the provided decorator.
+ """
+ test = unittest.TestCase()
+ suite = unittest.TestSuite([test])
+ decoratedTest = unittest.decorate(suite, unittest.TestDecorator)
+ self.assertSuitesEqual(
+ decoratedTest, unittest.TestSuite([unittest.TestDecorator(test)]))
+
+
+ def test_decorateInPlaceMutatesOriginal(self):
+ """
+ Calling L{decorate} on a test suite will mutate the original suite.
+ """
+ test = unittest.TestCase()
+ suite = unittest.TestSuite([test])
+ decoratedTest = unittest.decorate(
+ suite, unittest.TestDecorator)
+ self.assertSuitesEqual(
+ decoratedTest, unittest.TestSuite([unittest.TestDecorator(test)]))
+ self.assertSuitesEqual(
+ suite, unittest.TestSuite([unittest.TestDecorator(test)]))
+
+
+ def test_decorateTestSuiteReferences(self):
+ """
+ When decorating a test suite in-place, the number of references to the
+ test objects in that test suite should stay the same.
+
+ Previously, L{unittest.decorate} recreated a test suite, so the
+ original suite kept references to the test objects. This test is here
+ to ensure the problem doesn't reappear again.
+ """
+ getrefcount = getattr(sys, 'getrefcount', None)
+ if getrefcount is None:
+ raise unittest.SkipTest(
+ "getrefcount not supported on this platform")
+ test = unittest.TestCase()
+ suite = unittest.TestSuite([test])
+ count1 = getrefcount(test)
+ decoratedTest = unittest.decorate(suite, unittest.TestDecorator)
+ count2 = getrefcount(test)
+ self.assertEquals(count1, count2)
+
+
+ def test_decorateNestedTestSuite(self):
+ """
+ Calling L{decorate} on a test suite with nested suites will return a
+ test suite that maintains the same structure, but with all tests
+ decorated.
+ """
+ test = unittest.TestCase()
+ suite = unittest.TestSuite([unittest.TestSuite([test])])
+ decoratedTest = unittest.decorate(suite, unittest.TestDecorator)
+ expected = unittest.TestSuite(
+ [unittest.TestSuite([unittest.TestDecorator(test)])])
+ self.assertSuitesEqual(decoratedTest, expected)
+
+
+ def test_decorateDecoratedSuite(self):
+ """
+ Calling L{decorate} on a test suite with already-decorated tests
+ decorates all of the tests in the suite again.
+ """
+ test = unittest.TestCase()
+ decoratedTest = unittest.decorate(test, unittest.TestDecorator)
+ redecoratedTest = unittest.decorate(decoratedTest,
+ unittest.TestDecorator)
+ self.assertTestsEqual(redecoratedTest,
+ unittest.TestDecorator(decoratedTest))
+
+
+ def test_decoratePreservesSuite(self):
+ """
+ Tests can be in non-standard suites. L{decorate} preserves the
+ non-standard suites when it decorates the tests.
+ """
+ test = unittest.TestCase()
+ suite = runner.DestructiveTestSuite([test])
+ decorated = unittest.decorate(suite, unittest.TestDecorator)
+ self.assertSuitesEqual(
+ decorated,
+ runner.DestructiveTestSuite([unittest.TestDecorator(test)]))
+
+
+class TestMonkeyPatchSupport(unittest.TestCase):
+ """
+ Tests for the patch() helper method in L{unittest.TestCase}.
+ """
+
+
+ def setUp(self):
+ self.originalValue = 'original'
+ self.patchedValue = 'patched'
+ self.objectToPatch = self.originalValue
+ self.test = unittest.TestCase()
+
+
+ def test_patch(self):
+ """
+ Calling C{patch()} on a test monkey patches the specified object and
+ attribute.
+ """
+ self.test.patch(self, 'objectToPatch', self.patchedValue)
+ self.assertEqual(self.objectToPatch, self.patchedValue)
+
+
+ def test_patchRestoredAfterRun(self):
+ """
+ Any monkey patches introduced by a test using C{patch()} are reverted
+ after the test has run.
+ """
+ self.test.patch(self, 'objectToPatch', self.patchedValue)
+ self.test.run(reporter.Reporter())
+ self.assertEqual(self.objectToPatch, self.originalValue)
+
+
+ def test_revertDuringTest(self):
+ """
+ C{patch()} return a L{monkey.MonkeyPatcher} object that can be used to
+ restore the original values before the end of the test.
+ """
+ patch = self.test.patch(self, 'objectToPatch', self.patchedValue)
+ patch.restore()
+ self.assertEqual(self.objectToPatch, self.originalValue)
+
+
+ def test_revertAndRepatch(self):
+ """
+ The returned L{monkey.MonkeyPatcher} object can re-apply the patch
+ during the test run.
+ """
+ patch = self.test.patch(self, 'objectToPatch', self.patchedValue)
+ patch.restore()
+ patch.patch()
+ self.assertEqual(self.objectToPatch, self.patchedValue)
+
+
+ def test_successivePatches(self):
+ """
+ Successive patches are applied and reverted just like a single patch.
+ """
+ self.test.patch(self, 'objectToPatch', self.patchedValue)
+ self.assertEqual(self.objectToPatch, self.patchedValue)
+ self.test.patch(self, 'objectToPatch', 'second value')
+ self.assertEqual(self.objectToPatch, 'second value')
+ self.test.run(reporter.Reporter())
+ self.assertEqual(self.objectToPatch, self.originalValue)
+
+
+
+class TestIterateTests(unittest.TestCase):
+ """
+ L{_iterateTests} returns a list of all test cases in a test suite or test
+ case.
+ """
+
+ def test_iterateTestCase(self):
+ """
+ L{_iterateTests} on a single test case returns a list containing that
+ test case.
+ """
+ test = unittest.TestCase()
+ self.assertEqual([test], list(unittest._iterateTests(test)))
+
+
+ def test_iterateSingletonTestSuite(self):
+ """
+ L{_iterateTests} on a test suite that contains a single test case
+ returns a list containing that test case.
+ """
+ test = unittest.TestCase()
+ suite = runner.TestSuite([test])
+ self.assertEqual([test], list(unittest._iterateTests(suite)))
+
+
+ def test_iterateNestedTestSuite(self):
+ """
+ L{_iterateTests} returns tests that are in nested test suites.
+ """
+ test = unittest.TestCase()
+ suite = runner.TestSuite([runner.TestSuite([test])])
+ self.assertEqual([test], list(unittest._iterateTests(suite)))
+
+
+ def test_iterateIsLeftToRightDepthFirst(self):
+ """
+ L{_iterateTests} returns tests in left-to-right, depth-first order.
+ """
+ test = unittest.TestCase()
+ suite = runner.TestSuite([runner.TestSuite([test]), self])
+ self.assertEqual([test, self], list(unittest._iterateTests(suite)))
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_util.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_util.py
new file mode 100644
index 0000000000..6357d4c907
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_util.py
@@ -0,0 +1,533 @@
+import os
+
+from zope.interface import implements
+
+from twisted.internet.interfaces import IProcessTransport
+from twisted.internet import defer
+from twisted.internet.base import DelayedCall
+
+from twisted.trial.unittest import TestCase
+from twisted.trial import util
+from twisted.trial.util import DirtyReactorAggregateError, _Janitor
+from twisted.trial.test import packages
+
+
+
+class TestMktemp(TestCase):
+ def test_name(self):
+ name = self.mktemp()
+ dirs = os.path.dirname(name).split(os.sep)[:-1]
+ self.failUnlessEqual(
+ dirs, ['twisted.trial.test.test_util', 'TestMktemp', 'test_name'])
+
+ def test_unique(self):
+ name = self.mktemp()
+ self.failIfEqual(name, self.mktemp())
+
+ def test_created(self):
+ name = self.mktemp()
+ dirname = os.path.dirname(name)
+ self.failUnless(os.path.exists(dirname))
+ self.failIf(os.path.exists(name))
+
+ def test_location(self):
+ path = os.path.abspath(self.mktemp())
+ self.failUnless(path.startswith(os.getcwd()))
+
+
+class TestIntrospection(TestCase):
+ def test_containers(self):
+ import suppression
+ parents = util.getPythonContainers(
+ suppression.TestSuppression2.testSuppressModule)
+ expected = [suppression.TestSuppression2, suppression]
+ for a, b in zip(parents, expected):
+ self.failUnlessEqual(a, b)
+
+
+class TestFindObject(packages.SysPathManglingTest):
+ def test_importPackage(self):
+ package1 = util.findObject('package')
+ import package as package2
+ self.failUnlessEqual(package1, (True, package2))
+
+ def test_importModule(self):
+ test_sample2 = util.findObject('goodpackage.test_sample')
+ from goodpackage import test_sample
+ self.failUnlessEqual((True, test_sample), test_sample2)
+
+ def test_importError(self):
+ self.failUnlessRaises(ZeroDivisionError,
+ util.findObject, 'package.test_bad_module')
+
+ def test_sophisticatedImportError(self):
+ self.failUnlessRaises(ImportError,
+ util.findObject, 'package2.test_module')
+
+ def test_importNonexistentPackage(self):
+ self.failUnlessEqual(util.findObject('doesntexist')[0], False)
+
+ def test_findNonexistentModule(self):
+ self.failUnlessEqual(util.findObject('package.doesntexist')[0], False)
+
+ def test_findNonexistentObject(self):
+ self.failUnlessEqual(util.findObject(
+ 'goodpackage.test_sample.doesnt')[0], False)
+ self.failUnlessEqual(util.findObject(
+ 'goodpackage.test_sample.AlphabetTest.doesntexist')[0], False)
+
+ def test_findObjectExist(self):
+ alpha1 = util.findObject('goodpackage.test_sample.AlphabetTest')
+ from goodpackage import test_sample
+ self.failUnlessEqual(alpha1, (True, test_sample.AlphabetTest))
+
+
+
+class TestRunSequentially(TestCase):
+ """
+ Sometimes it is useful to be able to run an arbitrary list of callables,
+ one after the other.
+
+ When some of those callables can return Deferreds, things become complex.
+ """
+
+ def test_emptyList(self):
+ """
+ When asked to run an empty list of callables, runSequentially returns a
+ successful Deferred that fires an empty list.
+ """
+ d = util._runSequentially([])
+ d.addCallback(self.assertEqual, [])
+ return d
+
+
+ def test_singleSynchronousSuccess(self):
+ """
+ When given a callable that succeeds without returning a Deferred,
+ include the return value in the results list, tagged with a SUCCESS
+ flag.
+ """
+ d = util._runSequentially([lambda: None])
+ d.addCallback(self.assertEqual, [(defer.SUCCESS, None)])
+ return d
+
+
+ def test_singleSynchronousFailure(self):
+ """
+ When given a callable that raises an exception, include a Failure for
+ that exception in the results list, tagged with a FAILURE flag.
+ """
+ d = util._runSequentially([lambda: self.fail('foo')])
+ def check(results):
+ [(flag, fail)] = results
+ fail.trap(self.failureException)
+ self.assertEqual(fail.getErrorMessage(), 'foo')
+ self.assertEqual(flag, defer.FAILURE)
+ return d.addCallback(check)
+
+
+ def test_singleAsynchronousSuccess(self):
+ """
+ When given a callable that returns a successful Deferred, include the
+ result of the Deferred in the results list, tagged with a SUCCESS flag.
+ """
+ d = util._runSequentially([lambda: defer.succeed(None)])
+ d.addCallback(self.assertEqual, [(defer.SUCCESS, None)])
+ return d
+
+
+ def test_singleAsynchronousFailure(self):
+ """
+ When given a callable that returns a failing Deferred, include the
+ failure the results list, tagged with a FAILURE flag.
+ """
+ d = util._runSequentially([lambda: defer.fail(ValueError('foo'))])
+ def check(results):
+ [(flag, fail)] = results
+ fail.trap(ValueError)
+ self.assertEqual(fail.getErrorMessage(), 'foo')
+ self.assertEqual(flag, defer.FAILURE)
+ return d.addCallback(check)
+
+
+ def test_callablesCalledInOrder(self):
+ """
+ Check that the callables are called in the given order, one after the
+ other.
+ """
+ log = []
+ deferreds = []
+
+ def append(value):
+ d = defer.Deferred()
+ log.append(value)
+ deferreds.append(d)
+ return d
+
+ d = util._runSequentially([lambda: append('foo'),
+ lambda: append('bar')])
+
+ # runSequentially should wait until the Deferred has fired before
+ # running the second callable.
+ self.assertEqual(log, ['foo'])
+ deferreds[-1].callback(None)
+ self.assertEqual(log, ['foo', 'bar'])
+
+ # Because returning created Deferreds makes jml happy.
+ deferreds[-1].callback(None)
+ return d
+
+
+ def test_continuesAfterError(self):
+ """
+ If one of the callables raises an error, then runSequentially continues
+ to run the remaining callables.
+ """
+ d = util._runSequentially([lambda: self.fail('foo'), lambda: 'bar'])
+ def check(results):
+ [(flag1, fail), (flag2, result)] = results
+ fail.trap(self.failureException)
+ self.assertEqual(flag1, defer.FAILURE)
+ self.assertEqual(fail.getErrorMessage(), 'foo')
+ self.assertEqual(flag2, defer.SUCCESS)
+ self.assertEqual(result, 'bar')
+ return d.addCallback(check)
+
+
+ def test_stopOnFirstError(self):
+ """
+ If the C{stopOnFirstError} option is passed to C{runSequentially}, then
+ no further callables are called after the first exception is raised.
+ """
+ d = util._runSequentially([lambda: self.fail('foo'), lambda: 'bar'],
+ stopOnFirstError=True)
+ def check(results):
+ [(flag1, fail)] = results
+ fail.trap(self.failureException)
+ self.assertEqual(flag1, defer.FAILURE)
+ self.assertEqual(fail.getErrorMessage(), 'foo')
+ return d.addCallback(check)
+
+
+ def test_stripFlags(self):
+ """
+ If the C{stripFlags} option is passed to C{runSequentially} then the
+ SUCCESS / FAILURE flags are stripped from the output. Instead, the
+ Deferred fires a flat list of results containing only the results and
+ failures.
+ """
+ d = util._runSequentially([lambda: self.fail('foo'), lambda: 'bar'],
+ stripFlags=True)
+ def check(results):
+ [fail, result] = results
+ fail.trap(self.failureException)
+ self.assertEqual(fail.getErrorMessage(), 'foo')
+ self.assertEqual(result, 'bar')
+ return d.addCallback(check)
+ test_stripFlags.todo = "YAGNI"
+
+
+
+class DirtyReactorAggregateErrorTest(TestCase):
+ """
+ Tests for the L{DirtyReactorAggregateError}.
+ """
+
+ def test_formatDelayedCall(self):
+ """
+ Delayed calls are formatted nicely.
+ """
+ error = DirtyReactorAggregateError(["Foo", "bar"])
+ self.assertEquals(str(error),
+ """\
+Reactor was unclean.
+DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
+Foo
+bar""")
+
+
+ def test_formatSelectables(self):
+ """
+ Selectables are formatted nicely.
+ """
+ error = DirtyReactorAggregateError([], ["selectable 1", "selectable 2"])
+ self.assertEquals(str(error),
+ """\
+Reactor was unclean.
+Selectables:
+selectable 1
+selectable 2""")
+
+
+ def test_formatDelayedCallsAndSelectables(self):
+ """
+ Both delayed calls and selectables can appear in the same error.
+ """
+ error = DirtyReactorAggregateError(["bleck", "Boozo"],
+ ["Sel1", "Sel2"])
+ self.assertEquals(str(error),
+ """\
+Reactor was unclean.
+DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
+bleck
+Boozo
+Selectables:
+Sel1
+Sel2""")
+
+
+
+class StubReactor(object):
+ """
+ A reactor stub which contains enough functionality to be used with the
+ L{_Janitor}.
+
+ @ivar iterations: A list of the arguments passed to L{iterate}.
+ @ivar removeAllCalled: Number of times that L{removeAll} was called.
+ @ivar selectables: The value that will be returned from L{removeAll}.
+ @ivar delayedCalls: The value to return from L{getDelayedCalls}.
+ """
+
+ def __init__(self, delayedCalls, selectables=None):
+ """
+ @param delayedCalls: See L{StubReactor.delayedCalls}.
+ @param selectables: See L{StubReactor.selectables}.
+ """
+ self.delayedCalls = delayedCalls
+ self.iterations = []
+ self.removeAllCalled = 0
+ if not selectables:
+ selectables = []
+ self.selectables = selectables
+
+
+ def iterate(self, timeout=None):
+ """
+ Increment C{self.iterations}.
+ """
+ self.iterations.append(timeout)
+
+
+ def getDelayedCalls(self):
+ """
+ Return C{self.delayedCalls}.
+ """
+ return self.delayedCalls
+
+
+ def removeAll(self):
+ """
+ Increment C{self.removeAllCalled} and return C{self.selectables}.
+ """
+ self.removeAllCalled += 1
+ return self.selectables
+
+
+
+class StubErrorReporter(object):
+ """
+ A subset of L{twisted.trial.itrial.IReporter} which records L{addError}
+ calls.
+
+ @ivar errors: List of two-tuples of (test, error) which were passed to
+ L{addError}.
+ """
+
+ def __init__(self):
+ self.errors = []
+
+
+ def addError(self, test, error):
+ """
+ Record parameters in C{self.errors}.
+ """
+ self.errors.append((test, error))
+
+
+
+class JanitorTests(TestCase):
+ """
+ Tests for L{_Janitor}!
+ """
+
+ def test_cleanPendingSpinsReactor(self):
+ """
+ During pending-call cleanup, the reactor will be spun twice with an
+ instant timeout. This is not a requirement, it is only a test for
+ current behavior. Hopefully Trial will eventually not do this kind of
+ reactor stuff.
+ """
+ reactor = StubReactor([])
+ jan = _Janitor(None, None, reactor=reactor)
+ jan._cleanPending()
+ self.assertEquals(reactor.iterations, [0, 0])
+
+
+ def test_cleanPendingCancelsCalls(self):
+ """
+ During pending-call cleanup, the janitor cancels pending timed calls.
+ """
+ def func():
+ return "Lulz"
+ cancelled = []
+ delayedCall = DelayedCall(300, func, (), {},
+ cancelled.append, lambda x: None)
+ reactor = StubReactor([delayedCall])
+ jan = _Janitor(None, None, reactor=reactor)
+ jan._cleanPending()
+ self.assertEquals(cancelled, [delayedCall])
+
+
+ def test_cleanPendingReturnsDelayedCallStrings(self):
+ """
+ The Janitor produces string representations of delayed calls from the
+ delayed call cleanup method. It gets the string representations
+ *before* cancelling the calls; this is important because cancelling the
+ call removes critical debugging information from the string
+ representation.
+ """
+ delayedCall = DelayedCall(300, lambda: None, (), {},
+ lambda x: None, lambda x: None,
+ seconds=lambda: 0)
+ delayedCallString = str(delayedCall)
+ reactor = StubReactor([delayedCall])
+ jan = _Janitor(None, None, reactor=reactor)
+ strings = jan._cleanPending()
+ self.assertEquals(strings, [delayedCallString])
+
+
+ def test_cleanReactorRemovesSelectables(self):
+ """
+ The Janitor will remove selectables during reactor cleanup.
+ """
+ reactor = StubReactor([])
+ jan = _Janitor(None, None, reactor=reactor)
+ jan._cleanReactor()
+ self.assertEquals(reactor.removeAllCalled, 1)
+
+
+ def test_cleanReactorKillsProcesses(self):
+ """
+ The Janitor will kill processes during reactor cleanup.
+ """
+ class StubProcessTransport(object):
+ """
+ A stub L{IProcessTransport} provider which records signals.
+ @ivar signals: The signals passed to L{signalProcess}.
+ """
+ implements(IProcessTransport)
+
+ def __init__(self):
+ self.signals = []
+
+ def signalProcess(self, signal):
+ """
+ Append C{signal} to C{self.signals}.
+ """
+ self.signals.append(signal)
+
+ pt = StubProcessTransport()
+ reactor = StubReactor([], [pt])
+ jan = _Janitor(None, None, reactor=reactor)
+ jan._cleanReactor()
+ self.assertEquals(pt.signals, ["KILL"])
+
+
+ def test_cleanReactorReturnsSelectableStrings(self):
+ """
+ The Janitor returns string representations of the selectables that it
+ cleaned up from the reactor cleanup method.
+ """
+ class Selectable(object):
+ """
+ A stub Selectable which only has an interesting string
+ representation.
+ """
+ def __repr__(self):
+ return "(SELECTABLE!)"
+
+ reactor = StubReactor([], [Selectable()])
+ jan = _Janitor(None, None, reactor=reactor)
+ self.assertEquals(jan._cleanReactor(), ["(SELECTABLE!)"])
+
+
+ def test_postCaseCleanupNoErrors(self):
+ """
+ The post-case cleanup method will return True and not call C{addError}
+ on the result if there are no pending calls.
+ """
+ reactor = StubReactor([])
+ test = object()
+ reporter = StubErrorReporter()
+ jan = _Janitor(test, reporter, reactor=reactor)
+ self.assertTrue(jan.postCaseCleanup())
+ self.assertEquals(reporter.errors, [])
+
+
+ def test_postCaseCleanupWithErrors(self):
+ """
+ The post-case cleanup method will return False and call C{addError} on
+ the result with a L{DirtyReactorAggregateError} Failure if there are
+ pending calls.
+ """
+ delayedCall = DelayedCall(300, lambda: None, (), {},
+ lambda x: None, lambda x: None,
+ seconds=lambda: 0)
+ delayedCallString = str(delayedCall)
+ reactor = StubReactor([delayedCall], [])
+ test = object()
+ reporter = StubErrorReporter()
+ jan = _Janitor(test, reporter, reactor=reactor)
+ self.assertFalse(jan.postCaseCleanup())
+ self.assertEquals(len(reporter.errors), 1)
+ self.assertEquals(reporter.errors[0][1].value.delayedCalls,
+ [delayedCallString])
+
+
+ def test_postClassCleanupNoErrors(self):
+ """
+ The post-class cleanup method will not call C{addError} on the result
+ if there are no pending calls or selectables.
+ """
+ reactor = StubReactor([])
+ test = object()
+ reporter = StubErrorReporter()
+ jan = _Janitor(test, reporter, reactor=reactor)
+ jan.postClassCleanup()
+ self.assertEquals(reporter.errors, [])
+
+
+ def test_postClassCleanupWithPendingCallErrors(self):
+ """
+ The post-class cleanup method call C{addError} on the result with a
+ L{DirtyReactorAggregateError} Failure if there are pending calls.
+ """
+ delayedCall = DelayedCall(300, lambda: None, (), {},
+ lambda x: None, lambda x: None,
+ seconds=lambda: 0)
+ delayedCallString = str(delayedCall)
+ reactor = StubReactor([delayedCall], [])
+ test = object()
+ reporter = StubErrorReporter()
+ jan = _Janitor(test, reporter, reactor=reactor)
+ jan.postClassCleanup()
+ self.assertEquals(len(reporter.errors), 1)
+ self.assertEquals(reporter.errors[0][1].value.delayedCalls,
+ [delayedCallString])
+
+
+ def test_postClassCleanupWithSelectableErrors(self):
+ """
+ The post-class cleanup method call C{addError} on the result with a
+ L{DirtyReactorAggregateError} Failure if there are selectables.
+ """
+ selectable = "SELECTABLE HERE"
+ reactor = StubReactor([], [selectable])
+ test = object()
+ reporter = StubErrorReporter()
+ jan = _Janitor(test, reporter, reactor=reactor)
+ jan.postClassCleanup()
+ self.assertEquals(len(reporter.errors), 1)
+ self.assertEquals(reporter.errors[0][1].value.selectables,
+ [repr(selectable)])
+
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/test_warning.py b/vendor/Twisted-10.0.0/twisted/trial/test/test_warning.py
new file mode 100644
index 0000000000..27bb2e8cb0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/test_warning.py
@@ -0,0 +1,436 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for Trial's interaction with the Python warning system.
+"""
+
+import sys, warnings
+from StringIO import StringIO
+
+from twisted.python.filepath import FilePath
+from twisted.trial.unittest import TestCase, _collectWarnings
+from twisted.trial.reporter import TestResult
+
+class Mask(object):
+ """
+ Hide a L{TestCase} definition from trial's automatic discovery mechanism.
+ """
+ class MockTests(TestCase):
+ """
+ A test case which is used by L{FlushWarningsTests} to verify behavior
+ which cannot be verified by code inside a single test method.
+ """
+ message = "some warning text"
+ category = UserWarning
+
+ def test_unflushed(self):
+ """
+ Generate a warning and don't flush it.
+ """
+ warnings.warn(self.message, self.category)
+
+
+ def test_flushed(self):
+ """
+ Generate a warning and flush it.
+ """
+ warnings.warn(self.message, self.category)
+ self.assertEqual(len(self.flushWarnings()), 1)
+
+
+
+class FlushWarningsTests(TestCase):
+ """
+ Tests for L{TestCase.flushWarnings}, an API for examining the warnings
+ emitted so far in a test.
+ """
+
+ def assertDictSubset(self, set, subset):
+ """
+ Assert that all the keys present in C{subset} are also present in
+ C{set} and that the corresponding values are equal.
+ """
+ for k, v in subset.iteritems():
+ self.assertEqual(set[k], v)
+
+
+ def assertDictSubsets(self, sets, subsets):
+ """
+ For each pair of corresponding elements in C{sets} and C{subsets},
+ assert that the element from C{subsets} is a subset of the element from
+ C{sets}.
+ """
+ self.assertEqual(len(sets), len(subsets))
+ for a, b in zip(sets, subsets):
+ self.assertDictSubset(a, b)
+
+
+ def test_none(self):
+ """
+ If no warnings are emitted by a test, L{TestCase.flushWarnings} returns
+ an empty list.
+ """
+ self.assertEqual(self.flushWarnings(), [])
+
+
+ def test_several(self):
+ """
+ If several warnings are emitted by a test, L{TestCase.flushWarnings}
+ returns a list containing all of them.
+ """
+ firstMessage = "first warning message"
+ firstCategory = UserWarning
+ warnings.warn(message=firstMessage, category=firstCategory)
+
+ secondMessage = "second warning message"
+ secondCategory = RuntimeWarning
+ warnings.warn(message=secondMessage, category=secondCategory)
+
+ self.assertDictSubsets(
+ self.flushWarnings(),
+ [{'category': firstCategory, 'message': firstMessage},
+ {'category': secondCategory, 'message': secondMessage}])
+
+
+ def test_repeated(self):
+ """
+ The same warning triggered twice from the same place is included twice
+ in the list returned by L{TestCase.flushWarnings}.
+ """
+ message = "the message"
+ category = RuntimeWarning
+ for i in range(2):
+ warnings.warn(message=message, category=category)
+
+ self.assertDictSubsets(
+ self.flushWarnings(),
+ [{'category': category, 'message': message}] * 2)
+
+
+ def test_cleared(self):
+ """
+ After a particular warning event has been returned by
+ L{TestCase.flushWarnings}, it is not returned by subsequent calls.
+ """
+ message = "the message"
+ category = RuntimeWarning
+ warnings.warn(message=message, category=category)
+ self.assertDictSubsets(
+ self.flushWarnings(),
+ [{'category': category, 'message': message}])
+ self.assertEqual(self.flushWarnings(), [])
+
+
+ def test_unflushed(self):
+ """
+ Any warnings emitted by a test which are not flushed are emitted to the
+ Python warning system.
+ """
+ result = TestResult()
+ case = Mask.MockTests('test_unflushed')
+ case.run(result)
+ warningsShown = self.flushWarnings([Mask.MockTests.test_unflushed])
+ self.assertEqual(warningsShown[0]['message'], 'some warning text')
+ self.assertIdentical(warningsShown[0]['category'], UserWarning)
+
+ where = case.test_unflushed.im_func.func_code
+ filename = where.co_filename
+ # If someone edits MockTests.test_unflushed, the value added to
+ # firstlineno might need to change.
+ lineno = where.co_firstlineno + 4
+
+ self.assertEqual(warningsShown[0]['filename'], filename)
+ self.assertEqual(warningsShown[0]['lineno'], lineno)
+
+ self.assertEqual(len(warningsShown), 1)
+
+
+ def test_flushed(self):
+ """
+ Any warnings emitted by a test which are flushed are not emitted to the
+ Python warning system.
+ """
+ result = TestResult()
+ case = Mask.MockTests('test_flushed')
+ output = StringIO()
+ monkey = self.patch(sys, 'stdout', output)
+ case.run(result)
+ monkey.restore()
+ self.assertEqual(output.getvalue(), "")
+
+
+ def test_warningsConfiguredAsErrors(self):
+ """
+ If a warnings filter has been installed which turns warnings into
+ exceptions, tests have an error added to the reporter for them for each
+ unflushed warning.
+ """
+ class CustomWarning(Warning):
+ pass
+
+ result = TestResult()
+ case = Mask.MockTests('test_unflushed')
+ case.category = CustomWarning
+
+ originalWarnings = warnings.filters[:]
+ try:
+ warnings.simplefilter('error')
+ case.run(result)
+ self.assertEqual(len(result.errors), 1)
+ self.assertIdentical(result.errors[0][0], case)
+ result.errors[0][1].trap(CustomWarning)
+ finally:
+ warnings.filters[:] = originalWarnings
+
+
+ def test_flushedWarningsConfiguredAsErrors(self):
+ """
+ If a warnings filter has been installed which turns warnings into
+ exceptions, tests which emit those warnings but flush them do not have
+ an error added to the reporter.
+ """
+ class CustomWarning(Warning):
+ pass
+
+ result = TestResult()
+ case = Mask.MockTests('test_flushed')
+ case.category = CustomWarning
+
+ originalWarnings = warnings.filters[:]
+ try:
+ warnings.simplefilter('error')
+ case.run(result)
+ self.assertEqual(result.errors, [])
+ finally:
+ warnings.filters[:] = originalWarnings
+
+
+ def test_multipleFlushes(self):
+ """
+ Any warnings emitted after a call to L{TestCase.flushWarnings} can be
+ flushed by another call to L{TestCase.flushWarnings}.
+ """
+ warnings.warn("first message")
+ self.assertEqual(len(self.flushWarnings()), 1)
+ warnings.warn("second message")
+ self.assertEqual(len(self.flushWarnings()), 1)
+
+
+ def test_filterOnOffendingFunction(self):
+ """
+ The list returned by L{TestCase.flushWarnings} includes only those
+ warnings which refer to the source of the function passed as the value
+ for C{offendingFunction}, if a value is passed for that parameter.
+ """
+ firstMessage = "first warning text"
+ firstCategory = UserWarning
+ def one():
+ warnings.warn(firstMessage, firstCategory, stacklevel=1)
+
+ secondMessage = "some text"
+ secondCategory = RuntimeWarning
+ def two():
+ warnings.warn(secondMessage, secondCategory, stacklevel=1)
+
+ one()
+ two()
+
+ self.assertDictSubsets(
+ self.flushWarnings(offendingFunctions=[one]),
+ [{'category': firstCategory, 'message': firstMessage}])
+ self.assertDictSubsets(
+ self.flushWarnings(offendingFunctions=[two]),
+ [{'category': secondCategory, 'message': secondMessage}])
+
+
+ def test_functionBoundaries(self):
+ """
+ Verify that warnings emitted at the very edges of a function are still
+ determined to be emitted from that function.
+ """
+ def warner():
+ warnings.warn("first line warning")
+ warnings.warn("internal line warning")
+ warnings.warn("last line warning")
+
+ warner()
+ self.assertEqual(
+ len(self.flushWarnings(offendingFunctions=[warner])), 3)
+
+
+ def test_invalidFilter(self):
+ """
+ If an object which is neither a function nor a method is included in
+ the C{offendingFunctions} list, L{TestCase.flushWarnings} raises
+ L{ValueError}. Such a call flushes no warnings.
+ """
+ warnings.warn("oh no")
+ self.assertRaises(ValueError, self.flushWarnings, [None])
+ self.assertEqual(len(self.flushWarnings()), 1)
+
+
+ def test_missingSource(self):
+ """
+ Warnings emitted by a function the source code of which is not
+ available can still be flushed.
+ """
+ package = FilePath(self.mktemp()).child('twisted_private_helper')
+ package.makedirs()
+ package.child('__init__.py').setContent('')
+ package.child('missingsourcefile.py').setContent('''
+import warnings
+def foo():
+ warnings.warn("oh no")
+''')
+ sys.path.insert(0, package.parent().path)
+ self.addCleanup(sys.path.remove, package.parent().path)
+ from twisted_private_helper import missingsourcefile
+ self.addCleanup(sys.modules.pop, 'twisted_private_helper')
+ self.addCleanup(sys.modules.pop, missingsourcefile.__name__)
+ package.child('missingsourcefile.py').remove()
+
+ missingsourcefile.foo()
+ self.assertEqual(len(self.flushWarnings([missingsourcefile.foo])), 1)
+
+
+ def test_renamedSource(self):
+ """
+ Warnings emitted by a function defined in a file which has been renamed
+ since it was initially compiled can still be flushed.
+
+ This is testing the code which specifically supports working around the
+ unfortunate behavior of CPython to write a .py source file name into
+ the .pyc files it generates and then trust that it is correct in
+ various places. If source files are renamed, .pyc files may not be
+ regenerated, but they will contain incorrect filenames.
+ """
+ package = FilePath(self.mktemp()).child('twisted_private_helper')
+ package.makedirs()
+ package.child('__init__.py').setContent('')
+ package.child('module.py').setContent('''
+import warnings
+def foo():
+ warnings.warn("oh no")
+''')
+ sys.path.insert(0, package.parent().path)
+ self.addCleanup(sys.path.remove, package.parent().path)
+
+ # Import it to cause pycs to be generated
+ from twisted_private_helper import module
+
+ # Clean up the state resulting from that import; we're not going to use
+ # this module, so it should go away.
+ del sys.modules['twisted_private_helper']
+ del sys.modules[module.__name__]
+
+ # Rename the source directory
+ package.moveTo(package.sibling('twisted_renamed_helper'))
+
+ # Import the newly renamed version
+ from twisted_renamed_helper import module
+ self.addCleanup(sys.modules.pop, 'twisted_renamed_helper')
+ self.addCleanup(sys.modules.pop, module.__name__)
+
+ # Generate the warning
+ module.foo()
+
+ # Flush it
+ self.assertEqual(len(self.flushWarnings([module.foo])), 1)
+
+
+
+class FakeWarning(Warning):
+ pass
+
+
+
+class CollectWarningsTests(TestCase):
+ """
+ Tests for L{_collectWarnings}.
+ """
+ def test_callsObserver(self):
+ """
+ L{_collectWarnings} calls the observer with each emitted warning.
+ """
+ firstMessage = "dummy calls observer warning"
+ secondMessage = firstMessage[::-1]
+ events = []
+ def f():
+ events.append('call')
+ warnings.warn(firstMessage)
+ warnings.warn(secondMessage)
+ events.append('returning')
+
+ _collectWarnings(events.append, f)
+
+ self.assertEqual(events[0], 'call')
+ self.assertEqual(events[1].message, firstMessage)
+ self.assertEqual(events[2].message, secondMessage)
+ self.assertEqual(events[3], 'returning')
+ self.assertEqual(len(events), 4)
+
+
+ def test_suppresses(self):
+ """
+ Any warnings emitted by a call to a function passed to
+ L{_collectWarnings} are not actually emitted to the warning system.
+ """
+ output = StringIO()
+ self.patch(sys, 'stdout', output)
+ _collectWarnings(lambda x: None, warnings.warn, "text")
+ self.assertEqual(output.getvalue(), "")
+
+
+ def test_callsFunction(self):
+ """
+ L{_collectWarnings} returns the result of calling the callable passed to
+ it with the parameters given.
+ """
+ arguments = []
+ value = object()
+
+ def f(*args, **kwargs):
+ arguments.append((args, kwargs))
+ return value
+
+ result = _collectWarnings(lambda x: None, f, 1, 'a', b=2, c='d')
+ self.assertEqual(arguments, [((1, 'a'), {'b': 2, 'c': 'd'})])
+ self.assertIdentical(result, value)
+
+
+ def test_duplicateWarningCollected(self):
+ """
+ Subsequent emissions of a warning from a particular source site can be
+ collected by L{_collectWarnings}. In particular, the per-module
+ emitted-warning cache should be bypassed (I{__warningregistry__}).
+ """
+ # Make sure the worst case is tested: if __warningregistry__ isn't in a
+ # module's globals, then the warning system will add it and start using
+ # it to avoid emitting duplicate warnings. Delete __warningregistry__
+ # to ensure that even modules which are first imported as a test is
+ # running still interact properly with the warning system.
+ global __warningregistry__
+ del __warningregistry__
+
+ def f():
+ warnings.warn("foo")
+ warnings.simplefilter('default')
+ f()
+ events = []
+ _collectWarnings(events.append, f)
+ self.assertEqual(len(events), 1)
+ self.assertEqual(events[0].message, "foo")
+ self.assertEqual(len(self.flushWarnings()), 1)
+
+
+ def test_immutableObject(self):
+ """
+ L{_collectWarnings}'s behavior is not altered by the presence of an
+ object which cannot have attributes set on it as a value in
+ C{sys.modules}.
+ """
+ key = object()
+ sys.modules[key] = key
+ self.addCleanup(sys.modules.pop, key)
+ self.test_duplicateWarningCollected()
diff --git a/vendor/Twisted-10.0.0/twisted/trial/test/weird.py b/vendor/Twisted-10.0.0/twisted/trial/test/weird.py
new file mode 100644
index 0000000000..e35526dcb4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/test/weird.py
@@ -0,0 +1,20 @@
+from twisted.trial import unittest
+from twisted.internet import defer
+
+# Used in test_tests.TestUnhandledDeferred
+
+class TestBleeding(unittest.TestCase):
+ """This test creates an unhandled Deferred and leaves it in a cycle.
+
+ The Deferred is left in a cycle so that the garbage collector won't pick it
+ up immediately. We were having some problems where unhandled Deferreds in
+ one test were failing random other tests. (See #1507, #1213)
+ """
+ def test_unhandledDeferred(self):
+ try:
+ 1/0
+ except ZeroDivisionError:
+ f = defer.fail()
+ # these two lines create the cycle. don't remove them
+ l = [f]
+ l.append(l)
diff --git a/vendor/Twisted-10.0.0/twisted/trial/unittest.py b/vendor/Twisted-10.0.0/twisted/trial/unittest.py
new file mode 100644
index 0000000000..3a30562f0b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/unittest.py
@@ -0,0 +1,1597 @@
+# -*- test-case-name: twisted.trial.test.test_tests -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Things likely to be used by writers of unit tests.
+
+Maintainer: Jonathan Lange
+"""
+
+
+import doctest, inspect
+import os, warnings, sys, tempfile, gc, types
+from pprint import pformat
+try:
+ from dis import findlinestarts as _findlinestarts
+except ImportError:
+ # Definition copied from Python's Lib/dis.py - findlinestarts was not
+ # available in Python 2.3. This function is copyright Python Software
+ # Foundation, released under the Python license:
+ # http://www.python.org/psf/license/
+ def _findlinestarts(code):
+ """Find the offsets in a byte code which are start of lines in the source.
+
+ Generate pairs (offset, lineno) as described in Python/compile.c.
+
+ """
+ byte_increments = [ord(c) for c in code.co_lnotab[0::2]]
+ line_increments = [ord(c) for c in code.co_lnotab[1::2]]
+
+ lastlineno = None
+ lineno = code.co_firstlineno
+ addr = 0
+ for byte_incr, line_incr in zip(byte_increments, line_increments):
+ if byte_incr:
+ if lineno != lastlineno:
+ yield (addr, lineno)
+ lastlineno = lineno
+ addr += byte_incr
+ lineno += line_incr
+ if lineno != lastlineno:
+ yield (addr, lineno)
+
+from twisted.internet import defer, utils
+from twisted.python import components, failure, log, monkey
+from twisted.python.deprecate import getDeprecationWarningString
+
+from twisted.trial import itrial, reporter, util
+
+pyunit = __import__('unittest')
+
+from zope.interface import implements
+
+
+
+class SkipTest(Exception):
+ """
+ Raise this (with a reason) to skip the current test. You may also set
+ method.skip to a reason string to skip it, or set class.skip to skip the
+ entire TestCase.
+ """
+
+
+class FailTest(AssertionError):
+ """Raised to indicate the current test has failed to pass."""
+
+
+class Todo(object):
+ """
+ Internal object used to mark a L{TestCase} as 'todo'. Tests marked 'todo'
+ are reported differently in Trial L{TestResult}s. If todo'd tests fail,
+ they do not fail the suite and the errors are reported in a separate
+ category. If todo'd tests succeed, Trial L{TestResult}s will report an
+ unexpected success.
+ """
+
+ def __init__(self, reason, errors=None):
+ """
+ @param reason: A string explaining why the test is marked 'todo'
+
+ @param errors: An iterable of exception types that the test is
+ expected to raise. If one of these errors is raised by the test, it
+ will be trapped. Raising any other kind of error will fail the test.
+ If C{None} is passed, then all errors will be trapped.
+ """
+ self.reason = reason
+ self.errors = errors
+
+ def __repr__(self):
+ return "<Todo reason=%r errors=%r>" % (self.reason, self.errors)
+
+ def expected(self, failure):
+ """
+ @param failure: A L{twisted.python.failure.Failure}.
+
+ @return: C{True} if C{failure} is expected, C{False} otherwise.
+ """
+ if self.errors is None:
+ return True
+ for error in self.errors:
+ if failure.check(error):
+ return True
+ return False
+
+
+def makeTodo(value):
+ """
+ Return a L{Todo} object built from C{value}.
+
+ If C{value} is a string, return a Todo that expects any exception with
+ C{value} as a reason. If C{value} is a tuple, the second element is used
+ as the reason and the first element as the excepted error(s).
+
+ @param value: A string or a tuple of C{(errors, reason)}, where C{errors}
+ is either a single exception class or an iterable of exception classes.
+
+ @return: A L{Todo} object.
+ """
+ if isinstance(value, str):
+ return Todo(reason=value)
+ if isinstance(value, tuple):
+ errors, reason = value
+ try:
+ errors = list(errors)
+ except TypeError:
+ errors = [errors]
+ return Todo(reason=reason, errors=errors)
+
+
+
+class _Warning(object):
+ """
+ A L{_Warning} instance represents one warning emitted through the Python
+ warning system (L{warnings}). This is used to insulate callers of
+ L{_collectWarnings} from changes to the Python warnings system which might
+ otherwise require changes to the warning objects that function passes to
+ the observer object it accepts.
+
+ @ivar message: The string which was passed as the message parameter to
+ L{warnings.warn}.
+
+ @ivar category: The L{Warning} subclass which was passed as the category
+ parameter to L{warnings.warn}.
+
+ @ivar filename: The name of the file containing the definition of the code
+ object which was C{stacklevel} frames above the call to
+ L{warnings.warn}, where C{stacklevel} is the value of the C{stacklevel}
+ parameter passed to L{warnings.warn}.
+
+ @ivar lineno: The source line associated with the active instruction of the
+ code object object which was C{stacklevel} frames above the call to
+ L{warnings.warn}, where C{stacklevel} is the value of the C{stacklevel}
+ parameter passed to L{warnings.warn}.
+ """
+ def __init__(self, message, category, filename, lineno):
+ self.message = message
+ self.category = category
+ self.filename = filename
+ self.lineno = lineno
+
+
+
+def _collectWarnings(observeWarning, f, *args, **kwargs):
+ """
+ Call C{f} with C{args} positional arguments and C{kwargs} keyword arguments
+ and collect all warnings which are emitted as a result in a list.
+
+ @param observeWarning: A callable which will be invoked with a L{_Warning}
+ instance each time a warning is emitted.
+
+ @return: The return value of C{f(*args, **kwargs)}.
+ """
+ def showWarning(message, category, filename, lineno, file=None, line=None):
+ assert isinstance(message, Warning)
+ observeWarning(_Warning(
+ message.args[0], category, filename, lineno))
+
+ # Disable the per-module cache for every module otherwise if the warning
+ # which the caller is expecting us to collect was already emitted it won't
+ # be re-emitted by the call to f which happens below.
+ for v in sys.modules.itervalues():
+ if v is not None:
+ try:
+ v.__warningregistry__ = None
+ except:
+ # Don't specify a particular exception type to handle in case
+ # some wacky object raises some wacky exception in response to
+ # the setattr attempt.
+ pass
+
+ origFilters = warnings.filters[:]
+ origShow = warnings.showwarning
+ warnings.simplefilter('always')
+ try:
+ warnings.showwarning = showWarning
+ result = f(*args, **kwargs)
+ finally:
+ warnings.filters[:] = origFilters
+ warnings.showwarning = origShow
+ return result
+
+
+
+class _Assertions(pyunit.TestCase, object):
+ """
+ Replaces many of the built-in TestCase assertions. In general, these
+ assertions provide better error messages and are easier to use in
+ callbacks. Also provides new assertions such as L{failUnlessFailure}.
+
+ Although the tests are defined as 'failIf*' and 'failUnless*', they can
+ also be called as 'assertNot*' and 'assert*'.
+ """
+
+ def fail(self, msg=None):
+ """
+ Absolutely fail the test. Do not pass go, do not collect $200.
+
+ @param msg: the message that will be displayed as the reason for the
+ failure
+ """
+ raise self.failureException(msg)
+
+ def failIf(self, condition, msg=None):
+ """
+ Fail the test if C{condition} evaluates to True.
+
+ @param condition: any object that defines __nonzero__
+ """
+ if condition:
+ raise self.failureException(msg)
+ return condition
+ assertNot = assertFalse = failUnlessFalse = failIf
+
+ def failUnless(self, condition, msg=None):
+ """
+ Fail the test if C{condition} evaluates to False.
+
+ @param condition: any object that defines __nonzero__
+ """
+ if not condition:
+ raise self.failureException(msg)
+ return condition
+ assert_ = assertTrue = failUnlessTrue = failUnless
+
+ def failUnlessRaises(self, exception, f, *args, **kwargs):
+ """
+ Fail the test unless calling the function C{f} with the given
+ C{args} and C{kwargs} raises C{exception}. The failure will report
+ the traceback and call stack of the unexpected exception.
+
+ @param exception: exception type that is to be expected
+ @param f: the function to call
+
+ @return: The raised exception instance, if it is of the given type.
+ @raise self.failureException: Raised if the function call does
+ not raise an exception or if it raises an exception of a
+ different type.
+ """
+ try:
+ result = f(*args, **kwargs)
+ except exception, inst:
+ return inst
+ except:
+ raise self.failureException('%s raised instead of %s:\n %s'
+ % (sys.exc_info()[0],
+ exception.__name__,
+ failure.Failure().getTraceback()))
+ else:
+ raise self.failureException('%s not raised (%r returned)'
+ % (exception.__name__, result))
+ assertRaises = failUnlessRaises
+
+ def failUnlessEqual(self, first, second, msg=''):
+ """
+ Fail the test if C{first} and C{second} are not equal.
+
+ @param msg: A string describing the failure that's included in the
+ exception.
+ """
+ if not first == second:
+ if msg is None:
+ msg = ''
+ if len(msg) > 0:
+ msg += '\n'
+ raise self.failureException(
+ '%snot equal:\na = %s\nb = %s\n'
+ % (msg, pformat(first), pformat(second)))
+ return first
+ assertEqual = assertEquals = failUnlessEquals = failUnlessEqual
+
+ def failUnlessIdentical(self, first, second, msg=None):
+ """
+ Fail the test if C{first} is not C{second}. This is an
+ obect-identity-equality test, not an object equality
+ (i.e. C{__eq__}) test.
+
+ @param msg: if msg is None, then the failure message will be
+ '%r is not %r' % (first, second)
+ """
+ if first is not second:
+ raise self.failureException(msg or '%r is not %r' % (first, second))
+ return first
+ assertIdentical = failUnlessIdentical
+
+ def failIfIdentical(self, first, second, msg=None):
+ """
+ Fail the test if C{first} is C{second}. This is an
+ obect-identity-equality test, not an object equality
+ (i.e. C{__eq__}) test.
+
+ @param msg: if msg is None, then the failure message will be
+ '%r is %r' % (first, second)
+ """
+ if first is second:
+ raise self.failureException(msg or '%r is %r' % (first, second))
+ return first
+ assertNotIdentical = failIfIdentical
+
+ def failIfEqual(self, first, second, msg=None):
+ """
+ Fail the test if C{first} == C{second}.
+
+ @param msg: if msg is None, then the failure message will be
+ '%r == %r' % (first, second)
+ """
+ if not first != second:
+ raise self.failureException(msg or '%r == %r' % (first, second))
+ return first
+ assertNotEqual = assertNotEquals = failIfEquals = failIfEqual
+
+ def failUnlessIn(self, containee, container, msg=None):
+ """
+ Fail the test if C{containee} is not found in C{container}.
+
+ @param containee: the value that should be in C{container}
+ @param container: a sequence type, or in the case of a mapping type,
+ will follow semantics of 'if key in dict.keys()'
+ @param msg: if msg is None, then the failure message will be
+ '%r not in %r' % (first, second)
+ """
+ if containee not in container:
+ raise self.failureException(msg or "%r not in %r"
+ % (containee, container))
+ return containee
+ assertIn = failUnlessIn
+
+ def failIfIn(self, containee, container, msg=None):
+ """
+ Fail the test if C{containee} is found in C{container}.
+
+ @param containee: the value that should not be in C{container}
+ @param container: a sequence type, or in the case of a mapping type,
+ will follow semantics of 'if key in dict.keys()'
+ @param msg: if msg is None, then the failure message will be
+ '%r in %r' % (first, second)
+ """
+ if containee in container:
+ raise self.failureException(msg or "%r in %r"
+ % (containee, container))
+ return containee
+ assertNotIn = failIfIn
+
+ def failIfAlmostEqual(self, first, second, places=7, msg=None):
+ """
+ Fail if the two objects are equal as determined by their
+ difference rounded to the given number of decimal places
+ (default 7) and comparing to zero.
+
+ @note: decimal places (from zero) is usually not the same
+ as significant digits (measured from the most
+ signficant digit).
+
+ @note: included for compatiblity with PyUnit test cases
+ """
+ if round(second-first, places) == 0:
+ raise self.failureException(msg or '%r == %r within %r places'
+ % (first, second, places))
+ return first
+ assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual
+ failIfAlmostEquals = failIfAlmostEqual
+
+ def failUnlessAlmostEqual(self, first, second, places=7, msg=None):
+ """
+ Fail if the two objects are unequal as determined by their
+ difference rounded to the given number of decimal places
+ (default 7) and comparing to zero.
+
+ @note: decimal places (from zero) is usually not the same
+ as significant digits (measured from the most
+ signficant digit).
+
+ @note: included for compatiblity with PyUnit test cases
+ """
+ if round(second-first, places) != 0:
+ raise self.failureException(msg or '%r != %r within %r places'
+ % (first, second, places))
+ return first
+ assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual
+ failUnlessAlmostEquals = failUnlessAlmostEqual
+
+ def failUnlessApproximates(self, first, second, tolerance, msg=None):
+ """
+ Fail if C{first} - C{second} > C{tolerance}
+
+ @param msg: if msg is None, then the failure message will be
+ '%r ~== %r' % (first, second)
+ """
+ if abs(first - second) > tolerance:
+ raise self.failureException(msg or "%s ~== %s" % (first, second))
+ return first
+ assertApproximates = failUnlessApproximates
+
+ def failUnlessFailure(self, deferred, *expectedFailures):
+ """
+ Fail if C{deferred} does not errback with one of C{expectedFailures}.
+ Returns the original Deferred with callbacks added. You will need
+ to return this Deferred from your test case.
+ """
+ def _cb(ignore):
+ raise self.failureException(
+ "did not catch an error, instead got %r" % (ignore,))
+
+ def _eb(failure):
+ if failure.check(*expectedFailures):
+ return failure.value
+ else:
+ output = ('\nExpected: %r\nGot:\n%s'
+ % (expectedFailures, str(failure)))
+ raise self.failureException(output)
+ return deferred.addCallbacks(_cb, _eb)
+ assertFailure = failUnlessFailure
+
+ def failUnlessSubstring(self, substring, astring, msg=None):
+ """
+ Fail if C{substring} does not exist within C{astring}.
+ """
+ return self.failUnlessIn(substring, astring, msg)
+ assertSubstring = failUnlessSubstring
+
+ def failIfSubstring(self, substring, astring, msg=None):
+ """
+ Fail if C{astring} contains C{substring}.
+ """
+ return self.failIfIn(substring, astring, msg)
+ assertNotSubstring = failIfSubstring
+
+ def failUnlessWarns(self, category, message, filename, f,
+ *args, **kwargs):
+ """
+ Fail if the given function doesn't generate the specified warning when
+ called. It calls the function, checks the warning, and forwards the
+ result of the function if everything is fine.
+
+ @param category: the category of the warning to check.
+ @param message: the output message of the warning to check.
+ @param filename: the filename where the warning should come from.
+ @param f: the function which is supposed to generate the warning.
+ @type f: any callable.
+ @param args: the arguments to C{f}.
+ @param kwargs: the keywords arguments to C{f}.
+
+ @return: the result of the original function C{f}.
+ """
+ warningsShown = []
+ result = _collectWarnings(warningsShown.append, f, *args, **kwargs)
+
+ if not warningsShown:
+ self.fail("No warnings emitted")
+ first = warningsShown[0]
+ for other in warningsShown[1:]:
+ if ((other.message, other.category)
+ != (first.message, first.category)):
+ self.fail("Can't handle different warnings")
+ self.assertEqual(first.message, message)
+ self.assertIdentical(first.category, category)
+
+ # Use starts with because of .pyc/.pyo issues.
+ self.failUnless(
+ filename.startswith(first.filename),
+ 'Warning in %r, expected %r' % (first.filename, filename))
+
+ # It would be nice to be able to check the line number as well, but
+ # different configurations actually end up reporting different line
+ # numbers (generally the variation is only 1 line, but that's enough
+ # to fail the test erroneously...).
+ # self.assertEqual(lineno, xxx)
+
+ return result
+ assertWarns = failUnlessWarns
+
+ def failUnlessIsInstance(self, instance, classOrTuple):
+ """
+ Fail if C{instance} is not an instance of the given class or of
+ one of the given classes.
+
+ @param instance: the object to test the type (first argument of the
+ C{isinstance} call).
+ @type instance: any.
+ @param classOrTuple: the class or classes to test against (second
+ argument of the C{isinstance} call).
+ @type classOrTuple: class, type, or tuple.
+ """
+ if not isinstance(instance, classOrTuple):
+ self.fail("%r is not an instance of %s" % (instance, classOrTuple))
+ assertIsInstance = failUnlessIsInstance
+
+ def failIfIsInstance(self, instance, classOrTuple):
+ """
+ Fail if C{instance} is not an instance of the given class or of
+ one of the given classes.
+
+ @param instance: the object to test the type (first argument of the
+ C{isinstance} call).
+ @type instance: any.
+ @param classOrTuple: the class or classes to test against (second
+ argument of the C{isinstance} call).
+ @type classOrTuple: class, type, or tuple.
+ """
+ if isinstance(instance, classOrTuple):
+ self.fail("%r is an instance of %s" % (instance, classOrTuple))
+ assertNotIsInstance = failIfIsInstance
+
+
+class _LogObserver(object):
+ """
+ Observes the Twisted logs and catches any errors.
+
+ @ivar _errors: A C{list} of L{Failure} instances which were received as
+ error events from the Twisted logging system.
+
+ @ivar _added: A C{int} giving the number of times C{_add} has been called
+ less the number of times C{_remove} has been called; used to only add
+ this observer to the Twisted logging since once, regardless of the
+ number of calls to the add method.
+
+ @ivar _ignored: A C{list} of exception types which will not be recorded.
+ """
+
+ def __init__(self):
+ self._errors = []
+ self._added = 0
+ self._ignored = []
+
+
+ def _add(self):
+ if self._added == 0:
+ log.addObserver(self.gotEvent)
+ self._oldFE, log._flushErrors = (log._flushErrors, self.flushErrors)
+ self._oldIE, log._ignore = (log._ignore, self._ignoreErrors)
+ self._oldCI, log._clearIgnores = (log._clearIgnores,
+ self._clearIgnores)
+ self._added += 1
+
+ def _remove(self):
+ self._added -= 1
+ if self._added == 0:
+ log.removeObserver(self.gotEvent)
+ log._flushErrors = self._oldFE
+ log._ignore = self._oldIE
+ log._clearIgnores = self._oldCI
+
+
+ def _ignoreErrors(self, *errorTypes):
+ """
+ Do not store any errors with any of the given types.
+ """
+ self._ignored.extend(errorTypes)
+
+
+ def _clearIgnores(self):
+ """
+ Stop ignoring any errors we might currently be ignoring.
+ """
+ self._ignored = []
+
+
+ def flushErrors(self, *errorTypes):
+ """
+ Flush errors from the list of caught errors. If no arguments are
+ specified, remove all errors. If arguments are specified, only remove
+ errors of those types from the stored list.
+ """
+ if errorTypes:
+ flushed = []
+ remainder = []
+ for f in self._errors:
+ if f.check(*errorTypes):
+ flushed.append(f)
+ else:
+ remainder.append(f)
+ self._errors = remainder
+ else:
+ flushed = self._errors
+ self._errors = []
+ return flushed
+
+
+ def getErrors(self):
+ """
+ Return a list of errors caught by this observer.
+ """
+ return self._errors
+
+
+ def gotEvent(self, event):
+ """
+ The actual observer method. Called whenever a message is logged.
+
+ @param event: A dictionary containing the log message. Actual
+ structure undocumented (see source for L{twisted.python.log}).
+ """
+ if event.get('isError', False) and 'failure' in event:
+ f = event['failure']
+ if len(self._ignored) == 0 or not f.check(*self._ignored):
+ self._errors.append(f)
+
+
+
+_logObserver = _LogObserver()
+
+_wait_is_running = []
+
+class TestCase(_Assertions):
+ """
+ A unit test. The atom of the unit testing universe.
+
+ This class extends C{unittest.TestCase} from the standard library. The
+ main feature is the ability to return C{Deferred}s from tests and fixture
+ methods and to have the suite wait for those C{Deferred}s to fire.
+
+ To write a unit test, subclass C{TestCase} and define a method (say,
+ 'test_foo') on the subclass. To run the test, instantiate your subclass
+ with the name of the method, and call L{run} on the instance, passing a
+ L{TestResult} object.
+
+ The C{trial} script will automatically find any C{TestCase} subclasses
+ defined in modules beginning with 'test_' and construct test cases for all
+ methods beginning with 'test'.
+
+ If an error is logged during the test run, the test will fail with an
+ error. See L{log.err}.
+
+ @ivar failureException: An exception class, defaulting to C{FailTest}. If
+ the test method raises this exception, it will be reported as a failure,
+ rather than an exception. All of the assertion methods raise this if the
+ assertion fails.
+
+ @ivar skip: C{None} or a string explaining why this test is to be
+ skipped. If defined, the test will not be run. Instead, it will be
+ reported to the result object as 'skipped' (if the C{TestResult} supports
+ skipping).
+
+ @ivar suppress: C{None} or a list of tuples of C{(args, kwargs)} to be
+ passed to C{warnings.filterwarnings}. Use these to suppress warnings
+ raised in a test. Useful for testing deprecated code. See also
+ L{util.suppress}.
+
+ @ivar timeout: A real number of seconds. If set, the test will
+ raise an error if it takes longer than C{timeout} seconds.
+ If not set, util.DEFAULT_TIMEOUT_DURATION is used.
+
+ @ivar todo: C{None}, a string or a tuple of C{(errors, reason)} where
+ C{errors} is either an exception class or an iterable of exception
+ classes, and C{reason} is a string. See L{Todo} or L{makeTodo} for more
+ information.
+ """
+
+ implements(itrial.ITestCase)
+ failureException = FailTest
+
+ def __init__(self, methodName='runTest'):
+ """
+ Construct an asynchronous test case for C{methodName}.
+
+ @param methodName: The name of a method on C{self}. This method should
+ be a unit test. That is, it should be a short method that calls some of
+ the assert* methods. If C{methodName} is unspecified, L{runTest} will
+ be used as the test method. This is mostly useful for testing Trial.
+ """
+ super(TestCase, self).__init__(methodName)
+ self._testMethodName = methodName
+ testMethod = getattr(self, methodName)
+ self._parents = [testMethod, self]
+ self._parents.extend(util.getPythonContainers(testMethod))
+ self._passed = False
+ self._cleanups = []
+
+ if sys.version_info >= (2, 6):
+ # Override the comparison defined by the base TestCase which considers
+ # instances of the same class with the same _testMethodName to be
+ # equal. Since trial puts TestCase instances into a set, that
+ # definition of comparison makes it impossible to run the same test
+ # method twice. Most likely, trial should stop using a set to hold
+ # tests, but until it does, this is necessary on Python 2.6. Only
+ # __eq__ and __ne__ are required here, not __hash__, since the
+ # inherited __hash__ is compatible with these equality semantics. A
+ # different __hash__ might be slightly more efficient (by reducing
+ # collisions), but who cares? -exarkun
+ def __eq__(self, other):
+ return self is other
+
+ def __ne__(self, other):
+ return self is not other
+
+
+ def _run(self, methodName, result):
+ from twisted.internet import reactor
+ timeout = self.getTimeout()
+ def onTimeout(d):
+ e = defer.TimeoutError("%r (%s) still running at %s secs"
+ % (self, methodName, timeout))
+ f = failure.Failure(e)
+ # try to errback the deferred that the test returns (for no gorram
+ # reason) (see issue1005 and test_errorPropagation in
+ # test_deferred)
+ try:
+ d.errback(f)
+ except defer.AlreadyCalledError:
+ # if the deferred has been called already but the *back chain
+ # is still unfinished, crash the reactor and report timeout
+ # error ourself.
+ reactor.crash()
+ self._timedOut = True # see self._wait
+ todo = self.getTodo()
+ if todo is not None and todo.expected(f):
+ result.addExpectedFailure(self, f, todo)
+ else:
+ result.addError(self, f)
+ onTimeout = utils.suppressWarnings(
+ onTimeout, util.suppress(category=DeprecationWarning))
+ method = getattr(self, methodName)
+ d = defer.maybeDeferred(utils.runWithWarningsSuppressed,
+ self.getSuppress(), method)
+ call = reactor.callLater(timeout, onTimeout, d)
+ d.addBoth(lambda x : call.active() and call.cancel() or x)
+ return d
+
+ def shortDescription(self):
+ desc = super(TestCase, self).shortDescription()
+ if desc is None:
+ return self._testMethodName
+ return desc
+
+ def __call__(self, *args, **kwargs):
+ return self.run(*args, **kwargs)
+
+ def deferSetUp(self, ignored, result):
+ d = self._run('setUp', result)
+ d.addCallbacks(self.deferTestMethod, self._ebDeferSetUp,
+ callbackArgs=(result,),
+ errbackArgs=(result,))
+ return d
+
+ def _ebDeferSetUp(self, failure, result):
+ if failure.check(SkipTest):
+ result.addSkip(self, self._getReason(failure))
+ else:
+ result.addError(self, failure)
+ if failure.check(KeyboardInterrupt):
+ result.stop()
+ return self.deferRunCleanups(None, result)
+
+ def deferTestMethod(self, ignored, result):
+ d = self._run(self._testMethodName, result)
+ d.addCallbacks(self._cbDeferTestMethod, self._ebDeferTestMethod,
+ callbackArgs=(result,),
+ errbackArgs=(result,))
+ d.addBoth(self.deferRunCleanups, result)
+ d.addBoth(self.deferTearDown, result)
+ return d
+
+ def _cbDeferTestMethod(self, ignored, result):
+ if self.getTodo() is not None:
+ result.addUnexpectedSuccess(self, self.getTodo())
+ else:
+ self._passed = True
+ return ignored
+
+ def _ebDeferTestMethod(self, f, result):
+ todo = self.getTodo()
+ if todo is not None and todo.expected(f):
+ result.addExpectedFailure(self, f, todo)
+ elif f.check(self.failureException, FailTest):
+ result.addFailure(self, f)
+ elif f.check(KeyboardInterrupt):
+ result.addError(self, f)
+ result.stop()
+ elif f.check(SkipTest):
+ result.addSkip(self, self._getReason(f))
+ else:
+ result.addError(self, f)
+
+ def deferTearDown(self, ignored, result):
+ d = self._run('tearDown', result)
+ d.addErrback(self._ebDeferTearDown, result)
+ return d
+
+ def _ebDeferTearDown(self, failure, result):
+ result.addError(self, failure)
+ if failure.check(KeyboardInterrupt):
+ result.stop()
+ self._passed = False
+
+ def deferRunCleanups(self, ignored, result):
+ """
+ Run any scheduled cleanups and report errors (if any to the result
+ object.
+ """
+ d = self._runCleanups()
+ d.addCallback(self._cbDeferRunCleanups, result)
+ return d
+
+ def _cbDeferRunCleanups(self, cleanupResults, result):
+ for flag, failure in cleanupResults:
+ if flag == defer.FAILURE:
+ result.addError(self, failure)
+ if failure.check(KeyboardInterrupt):
+ result.stop()
+ self._passed = False
+
+ def _cleanUp(self, result):
+ try:
+ clean = util._Janitor(self, result).postCaseCleanup()
+ if not clean:
+ self._passed = False
+ except:
+ result.addError(self, failure.Failure())
+ self._passed = False
+ for error in self._observer.getErrors():
+ result.addError(self, error)
+ self._passed = False
+ self.flushLoggedErrors()
+ self._removeObserver()
+ if self._passed:
+ result.addSuccess(self)
+
+ def _classCleanUp(self, result):
+ try:
+ util._Janitor(self, result).postClassCleanup()
+ except:
+ result.addError(self, failure.Failure())
+
+ def _makeReactorMethod(self, name):
+ """
+ Create a method which wraps the reactor method C{name}. The new
+ method issues a deprecation warning and calls the original.
+ """
+ def _(*a, **kw):
+ warnings.warn("reactor.%s cannot be used inside unit tests. "
+ "In the future, using %s will fail the test and may "
+ "crash or hang the test run."
+ % (name, name),
+ stacklevel=2, category=DeprecationWarning)
+ return self._reactorMethods[name](*a, **kw)
+ return _
+
+ def _deprecateReactor(self, reactor):
+ """
+ Deprecate C{iterate}, C{crash} and C{stop} on C{reactor}. That is,
+ each method is wrapped in a function that issues a deprecation
+ warning, then calls the original.
+
+ @param reactor: The Twisted reactor.
+ """
+ self._reactorMethods = {}
+ for name in ['crash', 'iterate', 'stop']:
+ self._reactorMethods[name] = getattr(reactor, name)
+ setattr(reactor, name, self._makeReactorMethod(name))
+
+ def _undeprecateReactor(self, reactor):
+ """
+ Restore the deprecated reactor methods. Undoes what
+ L{_deprecateReactor} did.
+
+ @param reactor: The Twisted reactor.
+ """
+ for name, method in self._reactorMethods.iteritems():
+ setattr(reactor, name, method)
+ self._reactorMethods = {}
+
+ def _installObserver(self):
+ self._observer = _logObserver
+ self._observer._add()
+
+ def _removeObserver(self):
+ self._observer._remove()
+
+ def flushLoggedErrors(self, *errorTypes):
+ """
+ Remove stored errors received from the log.
+
+ C{TestCase} stores each error logged during the run of the test and
+ reports them as errors during the cleanup phase (after C{tearDown}).
+
+ @param *errorTypes: If unspecifed, flush all errors. Otherwise, only
+ flush errors that match the given types.
+
+ @return: A list of failures that have been removed.
+ """
+ return self._observer.flushErrors(*errorTypes)
+
+
+ def flushWarnings(self, offendingFunctions=None):
+ """
+ Remove stored warnings from the list of captured warnings and return
+ them.
+
+ @param offendingFunctions: If C{None}, all warnings issued during the
+ currently running test will be flushed. Otherwise, only warnings
+ which I{point} to a function included in this list will be flushed.
+ All warnings include a filename and source line number; if these
+ parts of a warning point to a source line which is part of a
+ function, then the warning I{points} to that function.
+ @type offendingFunctions: L{NoneType} or L{list} of functions or methods.
+
+ @raise ValueError: If C{offendingFunctions} is not C{None} and includes
+ an object which is not a L{FunctionType} or L{MethodType} instance.
+
+ @return: A C{list}, each element of which is a C{dict} giving
+ information about one warning which was flushed by this call. The
+ keys of each C{dict} are:
+
+ - C{'message'}: The string which was passed as the I{message}
+ parameter to L{warnings.warn}.
+
+ - C{'category'}: The warning subclass which was passed as the
+ I{category} parameter to L{warnings.warn}.
+
+ - C{'filename'}: The name of the file containing the definition
+ of the code object which was C{stacklevel} frames above the
+ call to L{warnings.warn}, where C{stacklevel} is the value of
+ the C{stacklevel} parameter passed to L{warnings.warn}.
+
+ - C{'lineno'}: The source line associated with the active
+ instruction of the code object object which was C{stacklevel}
+ frames above the call to L{warnings.warn}, where
+ C{stacklevel} is the value of the C{stacklevel} parameter
+ passed to L{warnings.warn}.
+ """
+ if offendingFunctions is None:
+ toFlush = self._warnings[:]
+ self._warnings[:] = []
+ else:
+ toFlush = []
+ for aWarning in self._warnings:
+ for aFunction in offendingFunctions:
+ if not isinstance(aFunction, (
+ types.FunctionType, types.MethodType)):
+ raise ValueError("%r is not a function or method" % (
+ aFunction,))
+
+ # inspect.getabsfile(aFunction) sometimes returns a
+ # filename which disagrees with the filename the warning
+ # system generates. This seems to be because a
+ # function's code object doesn't deal with source files
+ # being renamed. inspect.getabsfile(module) seems
+ # better (or at least agrees with the warning system
+ # more often), and does some normalization for us which
+ # is desirable. inspect.getmodule() is attractive, but
+ # somewhat broken in Python 2.3. See Python bug 4845.
+ aModule = sys.modules[aFunction.__module__]
+ filename = inspect.getabsfile(aModule)
+
+ if filename != os.path.normcase(aWarning.filename):
+ continue
+ lineStarts = list(_findlinestarts(aFunction.func_code))
+ first = lineStarts[0][1]
+ last = lineStarts[-1][1]
+ if not (first <= aWarning.lineno <= last):
+ continue
+ # The warning points to this function, flush it and move on
+ # to the next warning.
+ toFlush.append(aWarning)
+ break
+ # Remove everything which is being flushed.
+ map(self._warnings.remove, toFlush)
+
+ return [
+ {'message': w.message, 'category': w.category,
+ 'filename': w.filename, 'lineno': w.lineno}
+ for w in toFlush]
+
+
+ def addCleanup(self, f, *args, **kwargs):
+ """
+ Add the given function to a list of functions to be called after the
+ test has run, but before C{tearDown}.
+
+ Functions will be run in reverse order of being added. This helps
+ ensure that tear down complements set up.
+
+ The function C{f} may return a Deferred. If so, C{TestCase} will wait
+ until the Deferred has fired before proceeding to the next function.
+ """
+ self._cleanups.append((f, args, kwargs))
+
+
+ def callDeprecated(self, version, f, *args, **kwargs):
+ """
+ Call a function that was deprecated at a specific version.
+
+ @param version: The version that the function was deprecated in.
+ @param f: The deprecated function to call.
+ @return: Whatever the function returns.
+ """
+ result = f(*args, **kwargs)
+ warningsShown = self.flushWarnings([self.callDeprecated])
+
+ if len(warningsShown) == 0:
+ self.fail('%r is not deprecated.' % (f,))
+
+ observedWarning = warningsShown[0]['message']
+ expectedWarning = getDeprecationWarningString(f, version)
+ self.assertEqual(expectedWarning, observedWarning)
+
+ return result
+
+
+ def _runCleanups(self):
+ """
+ Run the cleanups added with L{addCleanup} in order.
+
+ @return: A C{Deferred} that fires when all cleanups are run.
+ """
+ def _makeFunction(f, args, kwargs):
+ return lambda: f(*args, **kwargs)
+ callables = []
+ while len(self._cleanups) > 0:
+ f, args, kwargs = self._cleanups.pop()
+ callables.append(_makeFunction(f, args, kwargs))
+ return util._runSequentially(callables)
+
+
+ def patch(self, obj, attribute, value):
+ """
+ Monkey patch an object for the duration of the test.
+
+ The monkey patch will be reverted at the end of the test using the
+ L{addCleanup} mechanism.
+
+ The L{MonkeyPatcher} is returned so that users can restore and
+ re-apply the monkey patch within their tests.
+
+ @param obj: The object to monkey patch.
+ @param attribute: The name of the attribute to change.
+ @param value: The value to set the attribute to.
+ @return: A L{monkey.MonkeyPatcher} object.
+ """
+ monkeyPatch = monkey.MonkeyPatcher((obj, attribute, value))
+ monkeyPatch.patch()
+ self.addCleanup(monkeyPatch.restore)
+ return monkeyPatch
+
+
+ def runTest(self):
+ """
+ If no C{methodName} argument is passed to the constructor, L{run} will
+ treat this method as the thing with the actual test inside.
+ """
+
+
+ def run(self, result):
+ """
+ Run the test case, storing the results in C{result}.
+
+ First runs C{setUp} on self, then runs the test method (defined in the
+ constructor), then runs C{tearDown}. Any of these may return
+ L{Deferred}s. After they complete, does some reactor cleanup.
+
+ @param result: A L{TestResult} object.
+ """
+ log.msg("--> %s <--" % (self.id()))
+ from twisted.internet import reactor
+ new_result = itrial.IReporter(result, None)
+ if new_result is None:
+ result = PyUnitResultAdapter(result)
+ else:
+ result = new_result
+ self._timedOut = False
+ result.startTest(self)
+ if self.getSkip(): # don't run test methods that are marked as .skip
+ result.addSkip(self, self.getSkip())
+ result.stopTest(self)
+ return
+ self._installObserver()
+
+ # All the code inside runThunk will be run such that warnings emitted
+ # by it will be collected and retrievable by flushWarnings.
+ def runThunk():
+ self._passed = False
+ self._deprecateReactor(reactor)
+ try:
+ d = self.deferSetUp(None, result)
+ try:
+ self._wait(d)
+ finally:
+ self._cleanUp(result)
+ self._classCleanUp(result)
+ finally:
+ self._undeprecateReactor(reactor)
+
+ self._warnings = []
+ _collectWarnings(self._warnings.append, runThunk)
+
+ # Any collected warnings which the test method didn't flush get
+ # re-emitted so they'll be logged or show up on stdout or whatever.
+ for w in self.flushWarnings():
+ try:
+ warnings.warn_explicit(**w)
+ except:
+ result.addError(self, failure.Failure())
+
+ result.stopTest(self)
+
+
+ def _getReason(self, f):
+ if len(f.value.args) > 0:
+ reason = f.value.args[0]
+ else:
+ warnings.warn(("Do not raise unittest.SkipTest with no "
+ "arguments! Give a reason for skipping tests!"),
+ stacklevel=2)
+ reason = f
+ return reason
+
+ def getSkip(self):
+ """
+ Return the skip reason set on this test, if any is set. Checks on the
+ instance first, then the class, then the module, then packages. As
+ soon as it finds something with a C{skip} attribute, returns that.
+ Returns C{None} if it cannot find anything. See L{TestCase} docstring
+ for more details.
+ """
+ return util.acquireAttribute(self._parents, 'skip', None)
+
+ def getTodo(self):
+ """
+ Return a L{Todo} object if the test is marked todo. Checks on the
+ instance first, then the class, then the module, then packages. As
+ soon as it finds something with a C{todo} attribute, returns that.
+ Returns C{None} if it cannot find anything. See L{TestCase} docstring
+ for more details.
+ """
+ todo = util.acquireAttribute(self._parents, 'todo', None)
+ if todo is None:
+ return None
+ return makeTodo(todo)
+
+ def getTimeout(self):
+ """
+ Returns the timeout value set on this test. Checks on the instance
+ first, then the class, then the module, then packages. As soon as it
+ finds something with a C{timeout} attribute, returns that. Returns
+ L{util.DEFAULT_TIMEOUT_DURATION} if it cannot find anything. See
+ L{TestCase} docstring for more details.
+ """
+ timeout = util.acquireAttribute(self._parents, 'timeout',
+ util.DEFAULT_TIMEOUT_DURATION)
+ try:
+ return float(timeout)
+ except (ValueError, TypeError):
+ # XXX -- this is here because sometimes people will have methods
+ # called 'timeout', or set timeout to 'orange', or something
+ # Particularly, test_news.NewsTestCase and ReactorCoreTestCase
+ # both do this.
+ warnings.warn("'timeout' attribute needs to be a number.",
+ category=DeprecationWarning)
+ return util.DEFAULT_TIMEOUT_DURATION
+
+ def getSuppress(self):
+ """
+ Returns any warning suppressions set for this test. Checks on the
+ instance first, then the class, then the module, then packages. As
+ soon as it finds something with a C{suppress} attribute, returns that.
+ Returns any empty list (i.e. suppress no warnings) if it cannot find
+ anything. See L{TestCase} docstring for more details.
+ """
+ return util.acquireAttribute(self._parents, 'suppress', [])
+
+
+ def visit(self, visitor):
+ """
+ Visit this test case. Call C{visitor} with C{self} as a parameter.
+
+ Deprecated in Twisted 8.0.
+
+ @param visitor: A callable which expects a single parameter: a test
+ case.
+
+ @return: None
+ """
+ warnings.warn("Test visitors deprecated in Twisted 8.0",
+ category=DeprecationWarning)
+ visitor(self)
+
+
+ def mktemp(self):
+ """Returns a unique name that may be used as either a temporary
+ directory or filename.
+
+ @note: you must call os.mkdir on the value returned from this
+ method if you wish to use it as a directory!
+ """
+ MAX_FILENAME = 32 # some platforms limit lengths of filenames
+ base = os.path.join(self.__class__.__module__[:MAX_FILENAME],
+ self.__class__.__name__[:MAX_FILENAME],
+ self._testMethodName[:MAX_FILENAME])
+ if not os.path.exists(base):
+ os.makedirs(base)
+ dirname = tempfile.mkdtemp('', '', base)
+ return os.path.join(dirname, 'temp')
+
+ def _wait(self, d, running=_wait_is_running):
+ """Take a Deferred that only ever callbacks. Block until it happens.
+ """
+ from twisted.internet import reactor
+ if running:
+ raise RuntimeError("_wait is not reentrant")
+
+ results = []
+ def append(any):
+ if results is not None:
+ results.append(any)
+ def crash(ign):
+ if results is not None:
+ reactor.crash()
+ crash = utils.suppressWarnings(
+ crash, util.suppress(message=r'reactor\.crash cannot be used.*',
+ category=DeprecationWarning))
+ def stop():
+ reactor.crash()
+ stop = utils.suppressWarnings(
+ stop, util.suppress(message=r'reactor\.crash cannot be used.*',
+ category=DeprecationWarning))
+
+ running.append(None)
+ try:
+ d.addBoth(append)
+ if results:
+ # d might have already been fired, in which case append is
+ # called synchronously. Avoid any reactor stuff.
+ return
+ d.addBoth(crash)
+ reactor.stop = stop
+ try:
+ reactor.run()
+ finally:
+ del reactor.stop
+
+ # If the reactor was crashed elsewhere due to a timeout, hopefully
+ # that crasher also reported an error. Just return.
+ # _timedOut is most likely to be set when d has fired but hasn't
+ # completed its callback chain (see self._run)
+ if results or self._timedOut: #defined in run() and _run()
+ return
+
+ # If the timeout didn't happen, and we didn't get a result or
+ # a failure, then the user probably aborted the test, so let's
+ # just raise KeyboardInterrupt.
+
+ # FIXME: imagine this:
+ # web/test/test_webclient.py:
+ # exc = self.assertRaises(error.Error, wait, method(url))
+ #
+ # wait() will raise KeyboardInterrupt, and assertRaises will
+ # swallow it. Therefore, wait() raising KeyboardInterrupt is
+ # insufficient to stop trial. A suggested solution is to have
+ # this code set a "stop trial" flag, or otherwise notify trial
+ # that it should really try to stop as soon as possible.
+ raise KeyboardInterrupt()
+ finally:
+ results = None
+ running.pop()
+
+
+class UnsupportedTrialFeature(Exception):
+ """A feature of twisted.trial was used that pyunit cannot support."""
+
+
+
+class PyUnitResultAdapter(object):
+ """
+ Wrap a C{TestResult} from the standard library's C{unittest} so that it
+ supports the extended result types from Trial, and also supports
+ L{twisted.python.failure.Failure}s being passed to L{addError} and
+ L{addFailure}.
+ """
+
+ def __init__(self, original):
+ """
+ @param original: A C{TestResult} instance from C{unittest}.
+ """
+ self.original = original
+
+ def _exc_info(self, err):
+ return util.excInfoOrFailureToExcInfo(err)
+
+ def startTest(self, method):
+ self.original.startTest(method)
+
+ def stopTest(self, method):
+ self.original.stopTest(method)
+
+ def addFailure(self, test, fail):
+ self.original.addFailure(test, self._exc_info(fail))
+
+ def addError(self, test, error):
+ self.original.addError(test, self._exc_info(error))
+
+ def _unsupported(self, test, feature, info):
+ self.original.addFailure(
+ test,
+ (UnsupportedTrialFeature,
+ UnsupportedTrialFeature(feature, info),
+ None))
+
+ def addSkip(self, test, reason):
+ """
+ Report the skip as a failure.
+ """
+ self._unsupported(test, 'skip', reason)
+
+ def addUnexpectedSuccess(self, test, todo):
+ """
+ Report the unexpected success as a failure.
+ """
+ self._unsupported(test, 'unexpected success', todo)
+
+ def addExpectedFailure(self, test, error):
+ """
+ Report the expected failure (i.e. todo) as a failure.
+ """
+ self._unsupported(test, 'expected failure', error)
+
+ def addSuccess(self, test):
+ self.original.addSuccess(test)
+
+ def upDownError(self, method, error, warn, printStatus):
+ pass
+
+
+
+def suiteVisit(suite, visitor):
+ """
+ Visit each test in C{suite} with C{visitor}.
+
+ Deprecated in Twisted 8.0.
+
+ @param visitor: A callable which takes a single argument, the L{TestCase}
+ instance to visit.
+ @return: None
+ """
+ warnings.warn("Test visitors deprecated in Twisted 8.0",
+ category=DeprecationWarning)
+ for case in suite._tests:
+ visit = getattr(case, 'visit', None)
+ if visit is not None:
+ visit(visitor)
+ elif isinstance(case, pyunit.TestCase):
+ case = itrial.ITestCase(case)
+ case.visit(visitor)
+ elif isinstance(case, pyunit.TestSuite):
+ suiteVisit(case, visitor)
+ else:
+ case.visit(visitor)
+
+
+
+class TestSuite(pyunit.TestSuite):
+ """
+ Extend the standard library's C{TestSuite} with support for the visitor
+ pattern and a consistently overrideable C{run} method.
+ """
+
+ visit = suiteVisit
+
+ def __call__(self, result):
+ return self.run(result)
+
+
+ def run(self, result):
+ """
+ Call C{run} on every member of the suite.
+ """
+ # we implement this because Python 2.3 unittest defines this code
+ # in __call__, whereas 2.4 defines the code in run.
+ for test in self._tests:
+ if result.shouldStop:
+ break
+ test(result)
+ return result
+
+
+
+class TestDecorator(components.proxyForInterface(itrial.ITestCase,
+ "_originalTest")):
+ """
+ Decorator for test cases.
+
+ @param _originalTest: The wrapped instance of test.
+ @type _originalTest: A provider of L{itrial.ITestCase}
+ """
+
+ implements(itrial.ITestCase)
+
+
+ def __call__(self, result):
+ """
+ Run the unit test.
+
+ @param result: A TestResult object.
+ """
+ return self.run(result)
+
+
+ def run(self, result):
+ """
+ Run the unit test.
+
+ @param result: A TestResult object.
+ """
+ return self._originalTest.run(
+ reporter._AdaptedReporter(result, self.__class__))
+
+
+
+def _clearSuite(suite):
+ """
+ Clear all tests from C{suite}.
+
+ This messes with the internals of C{suite}. In particular, it assumes that
+ the suite keeps all of its tests in a list in an instance variable called
+ C{_tests}.
+ """
+ suite._tests = []
+
+
+def decorate(test, decorator):
+ """
+ Decorate all test cases in C{test} with C{decorator}.
+
+ C{test} can be a test case or a test suite. If it is a test suite, then the
+ structure of the suite is preserved.
+
+ L{decorate} tries to preserve the class of the test suites it finds, but
+ assumes the presence of the C{_tests} attribute on the suite.
+
+ @param test: The C{TestCase} or C{TestSuite} to decorate.
+
+ @param decorator: A unary callable used to decorate C{TestCase}s.
+
+ @return: A decorated C{TestCase} or a C{TestSuite} containing decorated
+ C{TestCase}s.
+ """
+
+ try:
+ tests = iter(test)
+ except TypeError:
+ return decorator(test)
+
+ # At this point, we know that 'test' is a test suite.
+ _clearSuite(test)
+
+ for case in tests:
+ test.addTest(decorate(case, decorator))
+ return test
+
+
+
+class _PyUnitTestCaseAdapter(TestDecorator):
+ """
+ Adapt from pyunit.TestCase to ITestCase.
+ """
+
+
+ def visit(self, visitor):
+ """
+ Deprecated in Twisted 8.0.
+ """
+ warnings.warn("Test visitors deprecated in Twisted 8.0",
+ category=DeprecationWarning)
+ visitor(self)
+
+
+
+class _BrokenIDTestCaseAdapter(_PyUnitTestCaseAdapter):
+ """
+ Adapter for pyunit-style C{TestCase} subclasses that have undesirable id()
+ methods. That is L{pyunit.FunctionTestCase} and L{pyunit.DocTestCase}.
+ """
+
+ def id(self):
+ """
+ Return the fully-qualified Python name of the doctest.
+ """
+ testID = self._originalTest.shortDescription()
+ if testID is not None:
+ return testID
+ return self._originalTest.id()
+
+
+
+class _ForceGarbageCollectionDecorator(TestDecorator):
+ """
+ Forces garbage collection to be run before and after the test. Any errors
+ logged during the post-test collection are added to the test result as
+ errors.
+ """
+
+ def run(self, result):
+ gc.collect()
+ TestDecorator.run(self, result)
+ _logObserver._add()
+ gc.collect()
+ for error in _logObserver.getErrors():
+ result.addError(self, error)
+ _logObserver.flushErrors()
+ _logObserver._remove()
+
+
+components.registerAdapter(
+ _PyUnitTestCaseAdapter, pyunit.TestCase, itrial.ITestCase)
+
+
+components.registerAdapter(
+ _BrokenIDTestCaseAdapter, pyunit.FunctionTestCase, itrial.ITestCase)
+
+
+_docTestCase = getattr(doctest, 'DocTestCase', None)
+if _docTestCase:
+ components.registerAdapter(
+ _BrokenIDTestCaseAdapter, _docTestCase, itrial.ITestCase)
+
+
+def _iterateTests(testSuiteOrCase):
+ """
+ Iterate through all of the test cases in C{testSuiteOrCase}.
+ """
+ try:
+ suite = iter(testSuiteOrCase)
+ except TypeError:
+ yield testSuiteOrCase
+ else:
+ for test in suite:
+ for subtest in _iterateTests(test):
+ yield subtest
+
+
+
+# Support for Python 2.3
+try:
+ iter(pyunit.TestSuite())
+except TypeError:
+ # Python 2.3's TestSuite doesn't support iteration. Let's monkey patch it!
+ def __iter__(self):
+ return iter(self._tests)
+ pyunit.TestSuite.__iter__ = __iter__
+
+
+
+class _SubTestCase(TestCase):
+ def __init__(self):
+ TestCase.__init__(self, 'run')
+
+_inst = _SubTestCase()
+
+def _deprecate(name):
+ """
+ Internal method used to deprecate top-level assertions. Do not use this.
+ """
+ def _(*args, **kwargs):
+ warnings.warn("unittest.%s is deprecated. Instead use the %r "
+ "method on unittest.TestCase" % (name, name),
+ stacklevel=2, category=DeprecationWarning)
+ return getattr(_inst, name)(*args, **kwargs)
+ return _
+
+
+_assertions = ['fail', 'failUnlessEqual', 'failIfEqual', 'failIfEquals',
+ 'failUnless', 'failUnlessIdentical', 'failUnlessIn',
+ 'failIfIdentical', 'failIfIn', 'failIf',
+ 'failUnlessAlmostEqual', 'failIfAlmostEqual',
+ 'failUnlessRaises', 'assertApproximates',
+ 'assertFailure', 'failUnlessSubstring', 'failIfSubstring',
+ 'assertAlmostEqual', 'assertAlmostEquals',
+ 'assertNotAlmostEqual', 'assertNotAlmostEquals', 'assertEqual',
+ 'assertEquals', 'assertNotEqual', 'assertNotEquals',
+ 'assertRaises', 'assert_', 'assertIdentical',
+ 'assertNotIdentical', 'assertIn', 'assertNotIn',
+ 'failUnlessFailure', 'assertSubstring', 'assertNotSubstring']
+
+
+for methodName in _assertions:
+ globals()[methodName] = _deprecate(methodName)
+
+
+__all__ = ['TestCase', 'FailTest', 'SkipTest']
diff --git a/vendor/Twisted-10.0.0/twisted/trial/util.py b/vendor/Twisted-10.0.0/twisted/trial/util.py
new file mode 100644
index 0000000000..2f6324396f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/trial/util.py
@@ -0,0 +1,378 @@
+# -*- test-case-name: twisted.trial.test.test_util -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+#
+
+"""
+A collection of utility functions and classes, used internally by Trial.
+
+This code is for Trial's internal use. Do NOT use this code if you are writing
+tests. It is subject to change at the Trial maintainer's whim. There is
+nothing here in this module for you to use unless you are maintaining Trial.
+
+Any non-Trial Twisted code that uses this module will be shot.
+
+Maintainer: Jonathan Lange
+"""
+
+import traceback, sys
+
+from twisted.internet import defer, utils, interfaces
+from twisted.python.failure import Failure
+
+
+DEFAULT_TIMEOUT = object()
+DEFAULT_TIMEOUT_DURATION = 120.0
+
+
+class FailureError(Exception):
+ """
+ DEPRECATED in Twisted 8.0. This exception is never raised by Trial.
+
+ Wraps around a Failure so it can get re-raised as an Exception.
+ """
+
+ def __init__(self, failure):
+ Exception.__init__(self)
+ self.original = failure
+
+
+
+class DirtyReactorWarning(Warning):
+ """
+ DEPRECATED in Twisted 8.0.
+
+ This warning is not used by Trial any more.
+ """
+
+
+
+class DirtyReactorError(Exception):
+ """
+ DEPRECATED in Twisted 8.0. This is not used by Trial any more.
+ """
+
+ def __init__(self, msg):
+ Exception.__init__(self, self._getMessage(msg))
+
+ def _getMessage(self, msg):
+ return ("reactor left in unclean state, the following Selectables "
+ "were left over: %s" % (msg,))
+
+
+
+
+class PendingTimedCallsError(DirtyReactorError):
+ """
+ DEPRECATED in Twisted 8.0. This is not used by Trial any more.
+ """
+
+ def _getMessage(self, msg):
+ return ("pendingTimedCalls still pending (consider setting "
+ "twisted.internet.base.DelayedCall.debug = True): %s" % (msg,))
+
+
+
+class DirtyReactorAggregateError(Exception):
+ """
+ Passed to L{twisted.trial.itrial.IReporter.addError} when the reactor is
+ left in an unclean state after a test.
+
+ @ivar delayedCalls: The L{DelayedCall} objects which weren't cleaned up.
+ @ivar selectables: The selectables which weren't cleaned up.
+ """
+
+ def __init__(self, delayedCalls, selectables=None):
+ self.delayedCalls = delayedCalls
+ self.selectables = selectables
+
+ def __str__(self):
+ """
+ Return a multi-line message describing all of the unclean state.
+ """
+ msg = "Reactor was unclean."
+ if self.delayedCalls:
+ msg += ("\nDelayedCalls: (set "
+ "twisted.internet.base.DelayedCall.debug = True to "
+ "debug)\n")
+ msg += "\n".join(map(str, self.delayedCalls))
+ if self.selectables:
+ msg += "\nSelectables:\n"
+ msg += "\n".join(map(str, self.selectables))
+ return msg
+
+
+
+class _Janitor(object):
+ """
+ The guy that cleans up after you.
+
+ @ivar test: The L{TestCase} to report errors about.
+ @ivar result: The L{IReporter} to report errors to.
+ @ivar reactor: The reactor to use. If None, the global reactor
+ will be used.
+ """
+ def __init__(self, test, result, reactor=None):
+ """
+ @param test: See L{_Janitor.test}.
+ @param result: See L{_Janitor.result}.
+ @param reactor: See L{_Janitor.reactor}.
+ """
+ self.test = test
+ self.result = result
+ self.reactor = reactor
+
+
+ def postCaseCleanup(self):
+ """
+ Called by L{unittest.TestCase} after a test to catch any logged errors
+ or pending L{DelayedCall}s.
+ """
+ calls = self._cleanPending()
+ if calls:
+ aggregate = DirtyReactorAggregateError(calls)
+ self.result.addError(self.test, Failure(aggregate))
+ return False
+ return True
+
+
+ def postClassCleanup(self):
+ """
+ Called by L{unittest.TestCase} after the last test in a C{TestCase}
+ subclass. Ensures the reactor is clean by murdering the threadpool,
+ catching any pending L{DelayedCall}s, open sockets etc.
+ """
+ selectables = self._cleanReactor()
+ calls = self._cleanPending()
+ if selectables or calls:
+ aggregate = DirtyReactorAggregateError(calls, selectables)
+ self.result.addError(self.test, Failure(aggregate))
+ self._cleanThreads()
+
+
+ def _getReactor(self):
+ """
+ Get either the passed-in reactor or the global reactor.
+ """
+ if self.reactor is not None:
+ reactor = self.reactor
+ else:
+ from twisted.internet import reactor
+ return reactor
+
+
+ def _cleanPending(self):
+ """
+ Cancel all pending calls and return their string representations.
+ """
+ reactor = self._getReactor()
+
+ # flush short-range timers
+ reactor.iterate(0)
+ reactor.iterate(0)
+
+ delayedCallStrings = []
+ for p in reactor.getDelayedCalls():
+ if p.active():
+ delayedString = str(p)
+ p.cancel()
+ else:
+ print "WEIRDNESS! pending timed call not active!"
+ delayedCallStrings.append(delayedString)
+ return delayedCallStrings
+ _cleanPending = utils.suppressWarnings(
+ _cleanPending, (('ignore',), {'category': DeprecationWarning,
+ 'message':
+ r'reactor\.iterate cannot be used.*'}))
+
+ def _cleanThreads(self):
+ reactor = self._getReactor()
+ if interfaces.IReactorThreads.providedBy(reactor):
+ if reactor.threadpool is not None:
+ # Stop the threadpool now so that a new one is created.
+ # This improves test isolation somewhat (although this is a
+ # post class cleanup hook, so it's only isolating classes
+ # from each other, not methods from each other).
+ reactor._stopThreadPool()
+
+ def _cleanReactor(self):
+ """
+ Remove all selectables from the reactor, kill any of them that were
+ processes, and return their string representation.
+ """
+ reactor = self._getReactor()
+ selectableStrings = []
+ for sel in reactor.removeAll():
+ if interfaces.IProcessTransport.providedBy(sel):
+ sel.signalProcess('KILL')
+ selectableStrings.append(repr(sel))
+ return selectableStrings
+
+
+def excInfoOrFailureToExcInfo(err):
+ """
+ Coerce a Failure to an _exc_info, if err is a Failure.
+
+ @param err: Either a tuple such as returned by L{sys.exc_info} or a
+ L{Failure} object.
+ @return: A tuple like the one returned by L{sys.exc_info}. e.g.
+ C{exception_type, exception_object, traceback_object}.
+ """
+ if isinstance(err, Failure):
+ # Unwrap the Failure into a exc_info tuple.
+ err = (err.type, err.value, err.getTracebackObject())
+ return err
+
+
+def suppress(action='ignore', **kwarg):
+ """
+ Sets up the .suppress tuple properly, pass options to this method as you
+ would the stdlib warnings.filterwarnings()
+
+ So, to use this with a .suppress magic attribute you would do the
+ following:
+
+ >>> from twisted.trial import unittest, util
+ >>> import warnings
+ >>>
+ >>> class TestFoo(unittest.TestCase):
+ ... def testFooBar(self):
+ ... warnings.warn("i am deprecated", DeprecationWarning)
+ ... testFooBar.suppress = [util.suppress(message='i am deprecated')]
+ ...
+ >>>
+
+ Note that as with the todo and timeout attributes: the module level
+ attribute acts as a default for the class attribute which acts as a default
+ for the method attribute. The suppress attribute can be overridden at any
+ level by specifying C{.suppress = []}
+ """
+ return ((action,), kwarg)
+
+
+def profiled(f, outputFile):
+ def _(*args, **kwargs):
+ if sys.version_info[0:2] != (2, 4):
+ import profile
+ prof = profile.Profile()
+ try:
+ result = prof.runcall(f, *args, **kwargs)
+ prof.dump_stats(outputFile)
+ except SystemExit:
+ pass
+ prof.print_stats()
+ return result
+ else: # use hotshot, profile is broken in 2.4
+ import hotshot.stats
+ prof = hotshot.Profile(outputFile)
+ try:
+ return prof.runcall(f, *args, **kwargs)
+ finally:
+ stats = hotshot.stats.load(outputFile)
+ stats.strip_dirs()
+ stats.sort_stats('cum') # 'time'
+ stats.print_stats(100)
+ return _
+
+
+def getPythonContainers(meth):
+ """Walk up the Python tree from method 'meth', finding its class, its module
+ and all containing packages."""
+ containers = []
+ containers.append(meth.im_class)
+ moduleName = meth.im_class.__module__
+ while moduleName is not None:
+ module = sys.modules.get(moduleName, None)
+ if module is None:
+ module = __import__(moduleName)
+ containers.append(module)
+ moduleName = getattr(module, '__module__', None)
+ return containers
+
+
+_DEFAULT = object()
+def acquireAttribute(objects, attr, default=_DEFAULT):
+ """Go through the list 'objects' sequentially until we find one which has
+ attribute 'attr', then return the value of that attribute. If not found,
+ return 'default' if set, otherwise, raise AttributeError. """
+ for obj in objects:
+ if hasattr(obj, attr):
+ return getattr(obj, attr)
+ if default is not _DEFAULT:
+ return default
+ raise AttributeError('attribute %r not found in %r' % (attr, objects))
+
+
+def findObject(name):
+ """Get a fully-named package, module, module-global object or attribute.
+ Forked from twisted.python.reflect.namedAny.
+
+ Returns a tuple of (bool, obj). If bool is True, the named object exists
+ and is returned as obj. If bool is False, the named object does not exist
+ and the value of obj is unspecified.
+ """
+ names = name.split('.')
+ topLevelPackage = None
+ moduleNames = names[:]
+ while not topLevelPackage:
+ trialname = '.'.join(moduleNames)
+ if len(trialname) == 0:
+ return (False, None)
+ try:
+ topLevelPackage = __import__(trialname)
+ except ImportError:
+ # if the ImportError happened in the module being imported,
+ # this is a failure that should be handed to our caller.
+ # count stack frames to tell the difference.
+ exc_info = sys.exc_info()
+ if len(traceback.extract_tb(exc_info[2])) > 1:
+ try:
+ # Clean up garbage left in sys.modules.
+ del sys.modules[trialname]
+ except KeyError:
+ # Python 2.4 has fixed this. Yay!
+ pass
+ raise exc_info[0], exc_info[1], exc_info[2]
+ moduleNames.pop()
+ obj = topLevelPackage
+ for n in names[1:]:
+ try:
+ obj = getattr(obj, n)
+ except AttributeError:
+ return (False, obj)
+ return (True, obj)
+
+
+
+def _runSequentially(callables, stopOnFirstError=False):
+ """
+ Run the given callables one after the other. If a callable returns a
+ Deferred, wait until it has finished before running the next callable.
+
+ @param callables: An iterable of callables that take no parameters.
+
+ @param stopOnFirstError: If True, then stop running callables as soon as
+ one raises an exception or fires an errback. False by default.
+
+ @return: A L{Deferred} that fires a list of C{(flag, value)} tuples. Each
+ tuple will be either C{(SUCCESS, <return value>)} or C{(FAILURE,
+ <Failure>)}.
+ """
+ results = []
+ for f in callables:
+ d = defer.maybeDeferred(f)
+ thing = defer.waitForDeferred(d)
+ yield thing
+ try:
+ results.append((defer.SUCCESS, thing.getResult()))
+ except:
+ results.append((defer.FAILURE, Failure()))
+ if stopOnFirstError:
+ break
+ yield results
+_runSequentially = defer.deferredGenerator(_runSequentially)
+
+
+
+__all__ = ['FailureError', 'DirtyReactorWarning', 'DirtyReactorError',
+ 'PendingTimedCallsError', 'excInfoOrFailureToExcInfo']
diff --git a/vendor/Twisted-10.0.0/twisted/web/__init__.py b/vendor/Twisted-10.0.0/twisted/web/__init__.py
new file mode 100644
index 0000000000..d583321b10
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/__init__.py
@@ -0,0 +1,13 @@
+# -*- test-case-name: twisted.web.test -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Twisted Web: a L{web server<twisted.web.server>} (including an
+L{HTTP implementation<twisted.web.http>} and a
+L{resource model<twisted.web.resource>}) and
+a L{web client<twisted.web.client>}.
+"""
+
+from twisted.web._version import version
+__version__ = version.short()
diff --git a/vendor/Twisted-10.0.0/twisted/web/_auth/__init__.py b/vendor/Twisted-10.0.0/twisted/web/_auth/__init__.py
new file mode 100644
index 0000000000..17d5ca9861
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/_auth/__init__.py
@@ -0,0 +1,7 @@
+# -*- test-case-name: twisted.web.test.test_httpauth -*-
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+HTTP header-based authentication migrated from web2
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/web/_auth/basic.py b/vendor/Twisted-10.0.0/twisted/web/_auth/basic.py
new file mode 100644
index 0000000000..eaa2e6a87b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/_auth/basic.py
@@ -0,0 +1,59 @@
+# -*- test-case-name: twisted.web.test.test_httpauth -*-
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+HTTP BASIC authentication.
+
+@see: U{http://tools.ietf.org/html/rfc1945}
+@see: U{http://tools.ietf.org/html/rfc2616}
+@see: U{http://tools.ietf.org/html/rfc2617}
+"""
+
+import binascii
+
+from zope.interface import implements
+
+from twisted.cred import credentials, error
+from twisted.web.iweb import ICredentialFactory
+
+
+class BasicCredentialFactory(object):
+ """
+ Credential Factory for HTTP Basic Authentication
+
+ @type authenticationRealm: C{str}
+ @ivar authenticationRealm: The HTTP authentication realm which will be issued in
+ challenges.
+ """
+ implements(ICredentialFactory)
+
+ scheme = 'basic'
+
+ def __init__(self, authenticationRealm):
+ self.authenticationRealm = authenticationRealm
+
+
+ def getChallenge(self, request):
+ """
+ Return a challenge including the HTTP authentication realm with which
+ this factory was created.
+ """
+ return {'realm': self.authenticationRealm}
+
+
+ def decode(self, response, request):
+ """
+ Parse the base64-encoded, colon-separated username and password into a
+ L{credentials.UsernamePassword} instance.
+ """
+ try:
+ creds = binascii.a2b_base64(response + '===')
+ except binascii.Error:
+ raise error.LoginFailed('Invalid credentials')
+
+ creds = creds.split(':', 1)
+ if len(creds) == 2:
+ return credentials.UsernamePassword(*creds)
+ else:
+ raise error.LoginFailed('Invalid credentials')
diff --git a/vendor/Twisted-10.0.0/twisted/web/_auth/digest.py b/vendor/Twisted-10.0.0/twisted/web/_auth/digest.py
new file mode 100644
index 0000000000..24f29137d3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/_auth/digest.py
@@ -0,0 +1,54 @@
+# -*- test-case-name: twisted.web.test.test_httpauth -*-
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implementation of RFC2617: HTTP Digest Authentication
+
+@see: U{http://www.faqs.org/rfcs/rfc2617.html}
+"""
+
+from zope.interface import implements
+from twisted.cred import credentials
+from twisted.web.iweb import ICredentialFactory
+
+class DigestCredentialFactory(object):
+ """
+ Wrapper for L{digest.DigestCredentialFactory} that implements the
+ L{ICredentialFactory} interface.
+ """
+ implements(ICredentialFactory)
+
+ scheme = 'digest'
+
+ def __init__(self, algorithm, authenticationRealm):
+ """
+ Create the digest credential factory that this object wraps.
+ """
+ self.digest = credentials.DigestCredentialFactory(algorithm,
+ authenticationRealm)
+
+
+ def getChallenge(self, request):
+ """
+ Generate the challenge for use in the WWW-Authenticate header
+
+ @param request: The L{IRequest} to with access was denied and for the
+ response to which this challenge is being generated.
+
+ @return: The C{dict} that can be used to generate a WWW-Authenticate
+ header.
+ """
+ return self.digest.getChallenge(request.getClientIP())
+
+
+ def decode(self, response, request):
+ """
+ Create a L{twisted.cred.digest.DigestedCredentials} object from the
+ given response and request.
+
+ @see: L{ICredentialFactory.decode}
+ """
+ return self.digest.decode(response,
+ request.method,
+ request.getClientIP())
diff --git a/vendor/Twisted-10.0.0/twisted/web/_auth/wrapper.py b/vendor/Twisted-10.0.0/twisted/web/_auth/wrapper.py
new file mode 100644
index 0000000000..9af2ea5753
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/_auth/wrapper.py
@@ -0,0 +1,222 @@
+# -*- test-case-name: twisted.web.test.test_httpauth -*-
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A guard implementation which supports HTTP header-based authentication
+schemes.
+
+If no I{Authorization} header is supplied, an anonymous login will be
+attempted by using a L{Anonymous} credentials object. If such a header is
+supplied and does not contain allowed credentials, or if anonymous login is
+denied, a 401 will be sent in the response along with I{WWW-Authenticate}
+headers for each of the allowed authentication schemes.
+"""
+
+from zope.interface import implements
+
+from twisted.python import log
+from twisted.python.components import proxyForInterface
+from twisted.web.resource import IResource, ErrorPage
+from twisted.web import util
+from twisted.cred import error
+from twisted.cred.credentials import Anonymous
+
+
+class UnauthorizedResource(object):
+ """
+ Simple IResource to escape Resource dispatch
+ """
+ implements(IResource)
+ isLeaf = True
+
+
+ def __init__(self, factories):
+ self._credentialFactories = factories
+
+
+ def render(self, request):
+ """
+ Send www-authenticate headers to the client
+ """
+ def generateWWWAuthenticate(scheme, challenge):
+ l = []
+ for k,v in challenge.iteritems():
+ l.append("%s=%s" % (k, quoteString(v)))
+ return "%s %s" % (scheme, ", ".join(l))
+
+ def quoteString(s):
+ return '"%s"' % (s.replace('\\', '\\\\').replace('"', '\\"'),)
+
+ request.setResponseCode(401)
+ for fact in self._credentialFactories:
+ challenge = fact.getChallenge(request)
+ request.responseHeaders.addRawHeader(
+ 'www-authenticate',
+ generateWWWAuthenticate(fact.scheme, challenge))
+ return 'Unauthorized'
+
+
+ def getChildWithDefault(self, path, request):
+ """
+ Disable resource dispatch
+ """
+ return self
+
+
+
+class HTTPAuthSessionWrapper(object):
+ """
+ Wrap a portal, enforcing supported header-based authentication schemes.
+
+ @ivar _portal: The L{Portal} which will be used to retrieve L{IResource}
+ avatars.
+
+ @ivar _credentialFactories: A list of L{ICredentialFactory} providers which
+ will be used to decode I{Authorization} headers into L{ICredentials}
+ providers.
+ """
+ implements(IResource)
+ isLeaf = False
+
+ def __init__(self, portal, credentialFactories):
+ """
+ Initialize a session wrapper
+
+ @type portal: C{Portal}
+ @param portal: The portal that will authenticate the remote client
+
+ @type credentialFactories: C{Iterable}
+ @param credentialFactories: The portal that will authenticate the
+ remote client based on one submitted C{ICredentialFactory}
+ """
+ self._portal = portal
+ self._credentialFactories = credentialFactories
+
+
+ def _authorizedResource(self, request):
+ """
+ Get the L{IResource} which the given request is authorized to receive.
+ If the proper authorization headers are present, the resource will be
+ requested from the portal. If not, an anonymous login attempt will be
+ made.
+ """
+ authheader = request.getHeader('authorization')
+ if not authheader:
+ return util.DeferredResource(self._login(Anonymous()))
+
+ factory, respString = self._selectParseHeader(authheader)
+ if factory is None:
+ return UnauthorizedResource(self._credentialFactories)
+ try:
+ credentials = factory.decode(respString, request)
+ except error.LoginFailed:
+ return UnauthorizedResource(self._credentialFactories)
+ except:
+ log.err(None, "Unexpected failure from credentials factory")
+ return ErrorPage(500, None, None)
+ else:
+ return util.DeferredResource(self._login(credentials))
+
+
+ def render(self, request):
+ """
+ Find the L{IResource} avatar suitable for the given request, if
+ possible, and render it. Otherwise, perhaps render an error page
+ requiring authorization or describing an internal server failure.
+ """
+ return self._authorizedResource(request).render(request)
+
+
+ def getChildWithDefault(self, path, request):
+ """
+ Inspect the Authorization HTTP header, and return a deferred which,
+ when fired after successful authentication, will return an authorized
+ C{Avatar}. On authentication failure, an C{UnauthorizedResource} will
+ be returned, essentially halting further dispatch on the wrapped
+ resource and all children
+ """
+ # Don't consume any segments of the request - this class should be
+ # transparent!
+ request.postpath.insert(0, request.prepath.pop())
+ return self._authorizedResource(request)
+
+
+ def _login(self, credentials):
+ """
+ Get the L{IResource} avatar for the given credentials.
+
+ @return: A L{Deferred} which will be called back with an L{IResource}
+ avatar or which will errback if authentication fails.
+ """
+ d = self._portal.login(credentials, None, IResource)
+ d.addCallbacks(self._loginSucceeded, self._loginFailed)
+ return d
+
+
+ def _loginSucceeded(self, (interface, avatar, logout)):
+ """
+ Handle login success by wrapping the resulting L{IResource} avatar
+ so that the C{logout} callback will be invoked when rendering is
+ complete.
+ """
+ class ResourceWrapper(proxyForInterface(IResource, 'resource')):
+ """
+ Wrap an L{IResource} so that whenever it or a child of it
+ completes rendering, the cred logout hook will be invoked.
+
+ An assumption is made here that exactly one L{IResource} from
+ among C{avatar} and all of its children will be rendered. If
+ more than one is rendered, C{logout} will be invoked multiple
+ times and probably earlier than desired.
+ """
+ def getChildWithDefault(self, name, request):
+ """
+ Pass through the lookup to the wrapped resource, wrapping
+ the result in L{ResourceWrapper} to ensure C{logout} is
+ called when rendering of the child is complete.
+ """
+ return ResourceWrapper(self.resource.getChildWithDefault(name, request))
+
+ def render(self, request):
+ """
+ Hook into response generation so that when rendering has
+ finished completely, C{logout} is called.
+ """
+ request.notifyFinish().addCallback(lambda ign: logout())
+ return super(ResourceWrapper, self).render(request)
+
+ return ResourceWrapper(avatar)
+
+
+ def _loginFailed(self, result):
+ """
+ Handle login failure by presenting either another challenge (for
+ expected authentication/authorization-related failures) or a server
+ error page (for anything else).
+ """
+ if result.check(error.Unauthorized, error.LoginFailed):
+ return UnauthorizedResource(self._credentialFactories)
+ else:
+ log.err(
+ result,
+ "HTTPAuthSessionWrapper.getChildWithDefault encountered "
+ "unexpected error")
+ return ErrorPage(500, None, None)
+
+
+ def _selectParseHeader(self, header):
+ """
+ Choose an C{ICredentialFactory} from C{_credentialFactories}
+ suitable to use to decode the given I{Authenticate} header.
+
+ @return: A two-tuple of a factory and the remaining portion of the
+ header value to be decoded or a two-tuple of C{None} if no
+ factory can decode the header value.
+ """
+ elements = header.split(' ')
+ scheme = elements[0].lower()
+ for fact in self._credentialFactories:
+ if fact.scheme == scheme:
+ return (fact, ' '.join(elements[1:]))
+ return (None, None)
diff --git a/vendor/Twisted-10.0.0/twisted/web/_newclient.py b/vendor/Twisted-10.0.0/twisted/web/_newclient.py
new file mode 100644
index 0000000000..db8ad4f482
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/_newclient.py
@@ -0,0 +1,1413 @@
+# -*- test-case-name: twisted.web.test.test_newclient -*-
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An U{HTTP 1.1<http://www.w3.org/Protocols/rfc2616/rfc2616.html>} client.
+
+The way to use the functionality provided by this module is to:
+
+ - Connect a L{HTTP11ClientProtocol} to an HTTP server
+ - Create a L{Request} with the appropriate data
+ - Pass the request to L{HTTP11ClientProtocol.request}
+ - The returned Deferred will fire with a L{Response} object
+ - Create a L{IProtocol} provider which can handle the response body
+ - Connect it to the response with L{Response.deliverBody}
+ - When the protocol's C{connectionLost} method is called, the response is
+ complete. See L{Response.deliverBody} for details.
+
+Various other classes in this module support this usage:
+
+ - HTTPParser is the basic HTTP parser. It can handle the parts of HTTP which
+ are symmetric between requests and responses.
+
+ - HTTPClientParser extends HTTPParser to handle response-specific parts of
+ HTTP. One instance is created for each request to parse the corresponding
+ response.
+"""
+
+__metaclass__ = type
+
+from zope.interface import implements
+
+from twisted.python import log
+from twisted.python.reflect import fullyQualifiedName
+from twisted.python.failure import Failure
+from twisted.python.compat import set
+from twisted.internet.interfaces import IConsumer, IPushProducer
+from twisted.internet.error import ConnectionDone
+from twisted.internet.defer import Deferred, succeed, fail, maybeDeferred
+from twisted.internet.protocol import Protocol
+from twisted.protocols.basic import LineReceiver
+from twisted.web.iweb import UNKNOWN_LENGTH
+from twisted.web.http_headers import Headers
+from twisted.web.http import NO_CONTENT, NOT_MODIFIED
+from twisted.web.http import _DataLoss, PotentialDataLoss
+from twisted.web.http import _IdentityTransferDecoder, _ChunkedTransferDecoder
+
+# States HTTPParser can be in
+STATUS = 'STATUS'
+HEADER = 'HEADER'
+BODY = 'BODY'
+DONE = 'DONE'
+
+
+class BadHeaders(Exception):
+ """
+ Headers passed to L{Request} were in some way invalid.
+ """
+
+
+
+class ExcessWrite(Exception):
+ """
+ The body L{IBodyProducer} for a request tried to write data after
+ indicating it had finished writing data.
+ """
+
+
+class ParseError(Exception):
+ """
+ Some received data could not be parsed.
+
+ @ivar data: The string which could not be parsed.
+ """
+ def __init__(self, reason, data):
+ Exception.__init__(self, reason, data)
+ self.data = data
+
+
+
+class BadResponseVersion(ParseError):
+ """
+ The version string in a status line was unparsable.
+ """
+
+
+
+class _WrapperException(Exception):
+ """
+ L{_WrapperException} is the base exception type for exceptions which
+ include one or more other exceptions as the low-level causes.
+
+ @ivar reasons: A list of exceptions. See subclass documentation for more
+ details.
+ """
+ def __init__(self, reasons):
+ Exception.__init__(self, reasons)
+ self.reasons = reasons
+
+
+
+class RequestGenerationFailed(_WrapperException):
+ """
+ There was an error while creating the bytes which make up a request.
+
+ @ivar reasons: A C{list} of one or more L{Failure} instances giving the
+ reasons the request generation was considered to have failed.
+ """
+
+
+
+class RequestTransmissionFailed(_WrapperException):
+ """
+ There was an error while sending the bytes which make up a request.
+
+ @ivar reasons: A C{list} of one or more L{Failure} instances giving the
+ reasons the request transmission was considered to have failed.
+ """
+
+
+
+class WrongBodyLength(Exception):
+ """
+ An L{IBodyProducer} declared the number of bytes it was going to
+ produce (via its C{length} attribute) and then produced a different number
+ of bytes.
+ """
+
+
+
+class ResponseDone(Exception):
+ """
+ L{ResponseDone} may be passed to L{IProtocol.connectionLost} on the
+ protocol passed to L{Response.deliverBody} and indicates that the entire
+ response has been delivered.
+ """
+
+
+
+class ResponseFailed(_WrapperException):
+ """
+ L{ResponseFailed} indicates that all of the response to a request was not
+ received for some reason.
+
+ @ivar reasons: A C{list} of one or more L{Failure} instances giving the
+ reasons the response was considered to have failed.
+ """
+
+
+
+class RequestNotSent(Exception):
+ """
+ L{RequestNotSent} indicates that an attempt was made to issue a request but
+ for reasons unrelated to the details of the request itself, the request
+ could not be sent. For example, this may indicate that an attempt was made
+ to send a request using a protocol which is no longer connected to a
+ server.
+ """
+
+
+
+def _callAppFunction(function):
+ """
+ Call C{function}. If it raises an exception, log it with a minimal
+ description of the source.
+
+ @return: C{None}
+ """
+ try:
+ function()
+ except:
+ log.err(None, "Unexpected exception from %s" % (
+ fullyQualifiedName(function),))
+
+
+
+class HTTPParser(LineReceiver):
+ """
+ L{HTTPParser} handles the parsing side of HTTP processing. With a suitable
+ subclass, it can parse either the client side or the server side of the
+ connection.
+
+ @ivar headers: All of the non-connection control message headers yet
+ received.
+
+ @ivar state: State indicator for the response parsing state machine. One
+ of C{STATUS}, C{HEADER}, C{BODY}, C{DONE}.
+
+ @ivar _partialHeader: C{None} or a C{list} of the lines of a multiline
+ header while that header is being received.
+ """
+
+ # NOTE: According to HTTP spec, we're supposed to eat the
+ # 'Proxy-Authenticate' and 'Proxy-Authorization' headers also, but that
+ # doesn't sound like a good idea to me, because it makes it impossible to
+ # have a non-authenticating transparent proxy in front of an authenticating
+ # proxy. An authenticating proxy can eat them itself. -jknight
+ #
+ # Further, quoting
+ # http://homepages.tesco.net/J.deBoynePollard/FGA/web-proxy-connection-header.html
+ # regarding the 'Proxy-Connection' header:
+ #
+ # The Proxy-Connection: header is a mistake in how some web browsers
+ # use HTTP. Its name is the result of a false analogy. It is not a
+ # standard part of the protocol. There is a different standard
+ # protocol mechanism for doing what it does. And its existence
+ # imposes a requirement upon HTTP servers such that no proxy HTTP
+ # server can be standards-conforming in practice.
+ #
+ # -exarkun
+
+ CONNECTION_CONTROL_HEADERS = set([
+ 'content-length', 'connection', 'keep-alive', 'te', 'trailers',
+ 'transfer-encoding', 'upgrade', 'proxy-connection'])
+
+ def connectionMade(self):
+ self.headers = Headers()
+ self.connHeaders = Headers()
+ self.state = STATUS
+ self._partialHeader = None
+
+
+ def switchToBodyMode(self, decoder):
+ """
+ Switch to body parsing mode - interpret any more bytes delivered as
+ part of the message body and deliver them to the given decoder.
+ """
+ if self.state == BODY:
+ raise RuntimeError("already in body mode")
+
+ self.bodyDecoder = decoder
+ self.state = BODY
+ self.setRawMode()
+
+
+ def lineReceived(self, line):
+ """
+ Handle one line from a response.
+ """
+ if self.state == STATUS:
+ self.statusReceived(line)
+ self.state = HEADER
+ elif self.state == HEADER:
+ if not line or line[0] not in ' \t':
+ if self._partialHeader is not None:
+ header = ''.join(self._partialHeader)
+ name, value = header.split(':', 1)
+ value = value.strip()
+ self.headerReceived(name, value)
+ if not line:
+ # Empty line means the header section is over.
+ self.allHeadersReceived()
+ else:
+ # Line not beginning with LWS is another header.
+ self._partialHeader = [line]
+ else:
+ # A line beginning with LWS is a continuation of a header
+ # begun on a previous line.
+ self._partialHeader.append(line)
+
+
+ def rawDataReceived(self, data):
+ """
+ Pass data from the message body to the body decoder object.
+ """
+ self.bodyDecoder.dataReceived(data)
+
+
+ def isConnectionControlHeader(self, name):
+ """
+ Return C{True} if the given lower-cased name is the name of a
+ connection control header (rather than an entity header).
+
+ According to RFC 2616, section 14.10, the tokens in the Connection
+ header are probably relevant here. However, I am not sure what the
+ practical consequences of either implementing or ignoring that are.
+ So I leave it unimplemented for the time being.
+ """
+ return name in self.CONNECTION_CONTROL_HEADERS
+
+
+ def statusReceived(self, status):
+ """
+ Callback invoked whenever the first line of a new message is received.
+ Override this.
+
+ @param status: The first line of an HTTP request or response message
+ without trailing I{CR LF}.
+ @type status: C{str}
+ """
+
+
+ def headerReceived(self, name, value):
+ """
+ Store the given header in C{self.headers}.
+ """
+ name = name.lower()
+ if self.isConnectionControlHeader(name):
+ headers = self.connHeaders
+ else:
+ headers = self.headers
+ headers.addRawHeader(name, value)
+
+
+ def allHeadersReceived(self):
+ """
+ Callback invoked after the last header is passed to C{headerReceived}.
+ Override this to change to the C{BODY} or C{DONE} state.
+ """
+ self.switchToBodyMode(None)
+
+
+
+class HTTPClientParser(HTTPParser):
+ """
+ An HTTP parser which only handles HTTP responses.
+
+ @ivar request: The request with which the expected response is associated.
+ @type request: L{Request}
+
+ @ivar NO_BODY_CODES: A C{set} of response codes which B{MUST NOT} have a
+ body.
+
+ @ivar finisher: A callable to invoke when this response is fully parsed.
+
+ @ivar _responseDeferred: A L{Deferred} which will be called back with the
+ response when all headers in the response have been received.
+ Thereafter, C{None}.
+ """
+ NO_BODY_CODES = set([NO_CONTENT, NOT_MODIFIED])
+
+ _transferDecoders = {
+ 'chunked': _ChunkedTransferDecoder,
+ }
+
+ bodyDecoder = None
+
+ def __init__(self, request, finisher):
+ self.request = request
+ self.finisher = finisher
+ self._responseDeferred = Deferred()
+
+
+ def parseVersion(self, strversion):
+ """
+ Parse version strings of the form Protocol '/' Major '.' Minor. E.g.
+ 'HTTP/1.1'. Returns (protocol, major, minor). Will raise ValueError
+ on bad syntax.
+ """
+ try:
+ proto, strnumber = strversion.split('/')
+ major, minor = strnumber.split('.')
+ major, minor = int(major), int(minor)
+ except ValueError, e:
+ raise BadResponseVersion(str(e), strversion)
+ if major < 0 or minor < 0:
+ raise BadResponseVersion("version may not be negative", strversion)
+ return (proto, major, minor)
+
+
+ def statusReceived(self, status):
+ """
+ Parse the status line into its components and create a response object
+ to keep track of this response's state.
+ """
+ parts = status.split(' ', 2)
+ if len(parts) != 3:
+ raise ParseError("wrong number of parts", status)
+
+ try:
+ statusCode = int(parts[1])
+ except ValueError:
+ raise ParseError("non-integer status code", status)
+
+ self.response = Response(
+ self.parseVersion(parts[0]),
+ statusCode,
+ parts[2],
+ self.headers,
+ self.transport)
+
+
+ def _finished(self, rest):
+ """
+ Called to indicate that an entire response has been received. No more
+ bytes will be interpreted by this L{HTTPClientParser}. Extra bytes are
+ passed up and the state of this L{HTTPClientParser} is set to I{DONE}.
+
+ @param rest: A C{str} giving any extra bytes delivered to this
+ L{HTTPClientParser} which are not part of the response being
+ parsed.
+ """
+ self.state = DONE
+ self.finisher(rest)
+
+
+ def isConnectionControlHeader(self, name):
+ """
+ Content-Length in the response to a HEAD request is an entity header,
+ not a connection control header.
+ """
+ if self.request.method == 'HEAD' and name == 'content-length':
+ return False
+ return HTTPParser.isConnectionControlHeader(self, name)
+
+
+ def allHeadersReceived(self):
+ """
+ Figure out how long the response body is going to be by examining
+ headers and stuff.
+ """
+ if (self.response.code in self.NO_BODY_CODES
+ or self.request.method == 'HEAD'):
+ self.response.length = 0
+ self._finished(self.clearLineBuffer())
+ else:
+ transferEncodingHeaders = self.connHeaders.getRawHeaders(
+ 'transfer-encoding')
+ if transferEncodingHeaders:
+
+ # This could be a KeyError. However, that would mean we do not
+ # know how to decode the response body, so failing the request
+ # is as good a behavior as any. Perhaps someday we will want
+ # to normalize/document/test this specifically, but failing
+ # seems fine to me for now.
+ transferDecoder = self._transferDecoders[transferEncodingHeaders[0].lower()]
+
+ # If anyone ever invents a transfer encoding other than
+ # chunked (yea right), and that transfer encoding can predict
+ # the length of the response body, it might be sensible to
+ # allow the transfer decoder to set the response object's
+ # length attribute.
+ else:
+ contentLengthHeaders = self.connHeaders.getRawHeaders('content-length')
+ if contentLengthHeaders is None:
+ contentLength = None
+ elif len(contentLengthHeaders) == 1:
+ contentLength = int(contentLengthHeaders[0])
+ self.response.length = contentLength
+ else:
+ # "HTTP Message Splitting" or "HTTP Response Smuggling"
+ # potentially happening. Or it's just a buggy server.
+ raise ValueError(
+ "Too many Content-Length headers; response is invalid")
+
+ if contentLength == 0:
+ self._finished(self.clearLineBuffer())
+ transferDecoder = None
+ else:
+ transferDecoder = lambda x, y: _IdentityTransferDecoder(
+ contentLength, x, y)
+
+ if transferDecoder is None:
+ self.response._bodyDataFinished()
+ else:
+ # Make sure as little data as possible from the response body
+ # gets delivered to the response object until the response
+ # object actually indicates it is ready to handle bytes
+ # (probably because an application gave it a way to interpret
+ # them).
+ self.transport.pauseProducing()
+ self.switchToBodyMode(transferDecoder(
+ self.response._bodyDataReceived,
+ self._finished))
+
+ # This must be last. If it were first, then application code might
+ # change some state (for example, registering a protocol to receive the
+ # response body). Then the pauseProducing above would be wrong since
+ # the response is ready for bytes and nothing else would ever resume
+ # the transport.
+ self._responseDeferred.callback(self.response)
+ del self._responseDeferred
+
+
+ def connectionLost(self, reason):
+ if self.bodyDecoder is not None:
+ try:
+ try:
+ self.bodyDecoder.noMoreData()
+ except PotentialDataLoss:
+ self.response._bodyDataFinished(Failure())
+ except _DataLoss:
+ self.response._bodyDataFinished(
+ Failure(ResponseFailed([reason, Failure()])))
+ else:
+ self.response._bodyDataFinished()
+ except:
+ # Handle exceptions from both the except suites and the else
+ # suite. Those functions really shouldn't raise exceptions,
+ # but maybe there's some buggy application code somewhere
+ # making things difficult.
+ log.err()
+ elif self.state != DONE:
+ self._responseDeferred.errback(Failure(ResponseFailed([reason])))
+ del self._responseDeferred
+
+
+
+class Request:
+ """
+ A L{Request} instance describes an HTTP request to be sent to an HTTP
+ server.
+
+ @ivar method: The HTTP method to for this request, ex: 'GET', 'HEAD',
+ 'POST', etc.
+ @type method: C{str}
+
+ @ivar uri: The relative URI of the resource to request. For example,
+ C{'/foo/bar?baz=quux'}.
+ @type uri: C{str}
+
+ @ivar headers: Headers to be sent to the server. It is important to
+ note that this object does not create any implicit headers. So it
+ is up to the HTTP Client to add required headers such as 'Host'.
+ @type headers: L{twisted.web.http_headers.Headers}
+
+ @ivar bodyProducer: C{None} or an L{IBodyProducer} provider which
+ produces the content body to send to the remote HTTP server.
+ """
+ def __init__(self, method, uri, headers, bodyProducer):
+ self.method = method
+ self.uri = uri
+ self.headers = headers
+ self.bodyProducer = bodyProducer
+
+
+ def _writeHeaders(self, transport, TEorCL):
+ hosts = self.headers.getRawHeaders('host', ())
+ if len(hosts) != 1:
+ raise BadHeaders("Exactly one Host header required")
+
+ # In the future, having the protocol version be a parameter to this
+ # method would probably be good. It would be nice if this method
+ # weren't limited to issueing HTTP/1.1 requests.
+ requestLines = []
+ requestLines.append(
+ '%s %s HTTP/1.1\r\n' % (self.method, self.uri))
+ requestLines.append('Connection: close\r\n')
+ if TEorCL is not None:
+ requestLines.append(TEorCL)
+ for name, values in self.headers.getAllRawHeaders():
+ requestLines.extend(['%s: %s\r\n' % (name, v) for v in values])
+ requestLines.append('\r\n')
+ transport.writeSequence(requestLines)
+
+
+ def _writeToChunked(self, transport):
+ """
+ Write this request to the given transport using chunked
+ transfer-encoding to frame the body.
+ """
+ self._writeHeaders(transport, 'Transfer-Encoding: chunked\r\n')
+ encoder = ChunkedEncoder(transport)
+ encoder.registerProducer(self.bodyProducer, True)
+ d = self.bodyProducer.startProducing(encoder)
+
+ def cbProduced(ignored):
+ encoder.unregisterProducer()
+ def ebProduced(err):
+ encoder._allowNoMoreWrites()
+ # Don't call the encoder's unregisterProducer because it will write
+ # a zero-length chunk. This would indicate to the server that the
+ # request body is complete. There was an error, though, so we
+ # don't want to do that.
+ transport.unregisterProducer()
+ return err
+ d.addCallbacks(cbProduced, ebProduced)
+ return d
+
+
+ def _writeToContentLength(self, transport):
+ """
+ Write this request to the given transport using content-length to frame
+ the body.
+ """
+ self._writeHeaders(
+ transport,
+ 'Content-Length: %d\r\n' % (self.bodyProducer.length,))
+
+ # This Deferred is used to signal an error in the data written to the
+ # encoder below. It can only errback and it will only do so before too
+ # many bytes have been written to the encoder and before the producer
+ # Deferred fires.
+ finishedConsuming = Deferred()
+
+ # This makes sure the producer writes the correct number of bytes for
+ # the request body.
+ encoder = LengthEnforcingConsumer(
+ self.bodyProducer, transport, finishedConsuming)
+
+ transport.registerProducer(self.bodyProducer, True)
+
+ finishedProducing = self.bodyProducer.startProducing(encoder)
+
+ def combine(consuming, producing):
+ # This Deferred is returned and will be fired when the first of
+ # consuming or producing fires.
+ ultimate = Deferred()
+
+ # Keep track of what has happened so far. This initially
+ # contains None, then an integer uniquely identifying what
+ # sequence of events happened. See the callbacks and errbacks
+ # defined below for the meaning of each value.
+ state = [None]
+
+ def ebConsuming(err):
+ if state == [None]:
+ # The consuming Deferred failed first. This means the
+ # overall writeTo Deferred is going to errback now. The
+ # producing Deferred should not fire later (because the
+ # consumer should have called stopProducing on the
+ # producer), but if it does, a callback will be ignored
+ # and an errback will be logged.
+ state[0] = 1
+ ultimate.errback(err)
+ else:
+ # The consuming Deferred errbacked after the producing
+ # Deferred fired. This really shouldn't ever happen.
+ # If it does, I goofed. Log the error anyway, just so
+ # there's a chance someone might notice and complain.
+ log.err(
+ err,
+ "Buggy state machine in %r/[%d]: "
+ "ebConsuming called" % (self, state[0]))
+
+ def cbProducing(result):
+ if state == [None]:
+ # The producing Deferred succeeded first. Nothing will
+ # ever happen to the consuming Deferred. Tell the
+ # encoder we're done so it can check what the producer
+ # wrote and make sure it was right.
+ state[0] = 2
+ try:
+ encoder._noMoreWritesExpected()
+ except:
+ # Fail the overall writeTo Deferred - something the
+ # producer did was wrong.
+ ultimate.errback()
+ else:
+ # Success - succeed the overall writeTo Deferred.
+ ultimate.callback(None)
+ # Otherwise, the consuming Deferred already errbacked. The
+ # producing Deferred wasn't supposed to fire, but it did
+ # anyway. It's buggy, but there's not really anything to be
+ # done about it. Just ignore this result.
+
+ def ebProducing(err):
+ if state == [None]:
+ # The producing Deferred failed first. This means the
+ # overall writeTo Deferred is going to errback now.
+ # Tell the encoder that we're done so it knows to reject
+ # further writes from the producer (which should not
+ # happen, but the producer may be buggy).
+ state[0] = 3
+ encoder._allowNoMoreWrites()
+ ultimate.errback(err)
+ else:
+ # The producing Deferred failed after the consuming
+ # Deferred failed. It shouldn't have, so it's buggy.
+ # Log the exception in case anyone who can fix the code
+ # is watching.
+ log.err(err, "Producer is buggy")
+
+ consuming.addErrback(ebConsuming)
+ producing.addCallbacks(cbProducing, ebProducing)
+
+ return ultimate
+
+ d = combine(finishedConsuming, finishedProducing)
+ def f(passthrough):
+ # Regardless of what happens with the overall Deferred, once it
+ # fires, the producer registered way up above the definition of
+ # combine should be unregistered.
+ transport.unregisterProducer()
+ return passthrough
+ d.addBoth(f)
+ return d
+
+
+ def writeTo(self, transport):
+ """
+ Format this L{Request} as an HTTP/1.1 request and write it to the given
+ transport. If bodyProducer is not None, it will be associated with an
+ L{IConsumer}.
+
+ @return: A L{Deferred} which fires with C{None} when the request has
+ been completely written to the transport or with a L{Failure} if
+ there is any problem generating the request bytes.
+ """
+ if self.bodyProducer is not None:
+ if self.bodyProducer.length is UNKNOWN_LENGTH:
+ return self._writeToChunked(transport)
+ else:
+ return self._writeToContentLength(transport)
+ else:
+ self._writeHeaders(transport, None)
+ return succeed(None)
+
+
+ def stopWriting(self):
+ """
+ Stop writing this request to the transport. This can only be called
+ after C{writeTo} and before the L{Deferred} returned by C{writeTo}
+ fires. It should cancel any asynchronous task started by C{writeTo}.
+ The L{Deferred} returned by C{writeTo} need not be fired if this method
+ is called.
+ """
+ # If bodyProducer is None, then the Deferred returned by writeTo has
+ # fired already and this method cannot be called.
+ _callAppFunction(self.bodyProducer.stopProducing)
+
+
+
+class LengthEnforcingConsumer:
+ """
+ An L{IConsumer} proxy which enforces an exact length requirement on the
+ total data written to it.
+
+ @ivar _length: The number of bytes remaining to be written.
+
+ @ivar _producer: The L{IBodyProducer} which is writing to this
+ consumer.
+
+ @ivar _consumer: The consumer to which at most C{_length} bytes will be
+ forwarded.
+
+ @ivar _finished: A L{Deferred} which will be fired with a L{Failure} if too
+ many bytes are written to this consumer.
+ """
+ def __init__(self, producer, consumer, finished):
+ self._length = producer.length
+ self._producer = producer
+ self._consumer = consumer
+ self._finished = finished
+
+
+ def _allowNoMoreWrites(self):
+ """
+ Indicate that no additional writes are allowed. Attempts to write
+ after calling this method will be met with an exception.
+ """
+ self._finished = None
+
+
+ def write(self, bytes):
+ """
+ Write C{bytes} to the underlying consumer unless
+ C{_noMoreWritesExpected} has been called or there are/have been too
+ many bytes.
+ """
+ if self._finished is None:
+ # No writes are supposed to happen any more. Try to convince the
+ # calling code to stop calling this method by calling its
+ # stopProducing method and then throwing an exception at it. This
+ # exception isn't documented as part of the API because you're
+ # never supposed to expect it: only buggy code will ever receive
+ # it.
+ self._producer.stopProducing()
+ raise ExcessWrite()
+
+ if len(bytes) <= self._length:
+ self._length -= len(bytes)
+ self._consumer.write(bytes)
+ else:
+ # No synchronous exception is raised in *this* error path because
+ # we still have _finished which we can use to report the error to a
+ # better place than the direct caller of this method (some
+ # arbitrary application code).
+ _callAppFunction(self._producer.stopProducing)
+ self._finished.errback(WrongBodyLength("too many bytes written"))
+ self._allowNoMoreWrites()
+
+
+ def _noMoreWritesExpected(self):
+ """
+ Called to indicate no more bytes will be written to this consumer.
+ Check to see that the correct number have been written.
+
+ @raise WrongBodyLength: If not enough bytes have been written.
+ """
+ if self._finished is not None:
+ self._allowNoMoreWrites()
+ if self._length:
+ raise WrongBodyLength("too few bytes written")
+
+
+
+def makeStatefulDispatcher(name, template):
+ """
+ Given a I{dispatch} name and a function, return a function which can be
+ used as a method and which, when called, will call another method defined
+ on the instance and return the result. The other method which is called is
+ determined by the value of the C{_state} attribute of the instance.
+
+ @param name: A string which is used to construct the name of the subsidiary
+ method to invoke. The subsidiary method is named like C{'_%s_%s' %
+ (name, _state)}.
+
+ @param template: A function object which is used to give the returned
+ function a docstring.
+
+ @return: The dispatcher function.
+ """
+ def dispatcher(self, *args, **kwargs):
+ func = getattr(self, '_' + name + '_' + self._state, None)
+ if func is None:
+ raise RuntimeError(
+ "%r has no %s method in state %s" % (self, name, self._state))
+ return func(*args, **kwargs)
+ dispatcher.__doc__ = template.__doc__
+ return dispatcher
+
+
+
+class Response:
+ """
+ A L{Response} instance describes an HTTP response received from an HTTP
+ server.
+
+ L{Response} should not be subclassed or instantiated.
+
+ @ivar version: A three-tuple describing the protocol and protocol version
+ of the response. The first element is of type C{str}, the second and
+ third are of type C{int}. For example, C{('HTTP', 1, 1)}.
+ @type version: C{tuple}
+
+ @ivar code: The HTTP status code of this response.
+ @type code: C{int}
+
+ @ivar phrase: The HTTP reason phrase of this response.
+ @type phrase: C{str}
+
+ @ivar headers: The HTTP response headers of this response.
+ @type headers: L{Headers}
+
+ @ivar length: The number of bytes expected to be in the body of this
+ response or L{UNKNOWN_LENGTH} if the server did not indicate how many
+ bytes to expect. For I{HEAD} responses, this will be 0; if the
+ response includes a I{Content-Length} header, it will be available in
+ C{headers}.
+ @type length: C{int} or something else
+
+ @ivar _transport: The transport which is delivering this response.
+
+ @ivar _bodyProtocol: The L{IProtocol} provider to which the body is
+ delivered. C{None} before one has been registered with
+ C{deliverBody}.
+
+ @ivar _bodyBuffer: A C{list} of the strings passed to C{bodyDataReceived}
+ before C{deliverBody} is called. C{None} afterwards.
+
+ @ivar _state: Indicates what state this L{Response} instance is in,
+ particularly with respect to delivering bytes from the response body
+ to an application-suppled protocol object. This may be one of
+ C{'INITIAL'}, C{'CONNECTED'}, C{'DEFERRED_CLOSE'}, or C{'FINISHED'},
+ with the following meanings:
+
+ - INITIAL: This is the state L{Response} objects start in. No
+ protocol has yet been provided and the underlying transport may
+ still have bytes to deliver to it.
+
+ - DEFERRED_CLOSE: If the underlying transport indicates all bytes
+ have been delivered but no application-provided protocol is yet
+ available, the L{Response} moves to this state. Data is
+ buffered and waiting for a protocol to be delivered to.
+
+ - CONNECTED: If a protocol is provided when the state is INITIAL,
+ the L{Response} moves to this state. Any buffered data is
+ delivered and any data which arrives from the transport
+ subsequently is given directly to the protocol.
+
+ - FINISHED: If a protocol is provided in the DEFERRED_CLOSE state,
+ the L{Response} moves to this state after delivering all
+ buffered data to the protocol. Otherwise, if the L{Response} is
+ in the CONNECTED state, if the transport indicates there is no
+ more data, the L{Response} moves to this state. Nothing else
+ can happen once the L{Response} is in this state.
+ """
+
+ length = UNKNOWN_LENGTH
+
+ _bodyProtocol = None
+ _bodyFinished = False
+
+ def __init__(self, version, code, phrase, headers, _transport):
+ self.version = version
+ self.code = code
+ self.phrase = phrase
+ self.headers = headers
+ self._transport = _transport
+ self._bodyBuffer = []
+ self._state = 'INITIAL'
+
+
+ def deliverBody(self, protocol):
+ """
+ Register an L{IProtocol} provider to receive the response body.
+
+ The protocol will be connected to a transport which provides
+ L{IPushProducer}. The protocol's C{connectionLost} method will be
+ called with:
+
+ - ResponseDone, which indicates that all bytes from the response
+ have been successfully delivered.
+
+ - PotentialDataLoss, which indicates that it cannot be determined
+ if the entire response body has been delivered. This only occurs
+ when making requests to HTTP servers which do not set
+ I{Content-Length} or a I{Transfer-Encoding} in the response.
+
+ - ResponseFailed, which indicates that some bytes from the response
+ were lost. The C{reasons} attribute of the exception may provide
+ more specific indications as to why.
+ """
+ deliverBody = makeStatefulDispatcher('deliverBody', deliverBody)
+
+
+ def _deliverBody_INITIAL(self, protocol):
+ """
+ Deliver any buffered data to C{protocol} and prepare to deliver any
+ future data to it. Move to the C{'CONNECTED'} state.
+ """
+ # Now that there's a protocol to consume the body, resume the
+ # transport. It was previously paused by HTTPClientParser to avoid
+ # reading too much data before it could be handled.
+ self._transport.resumeProducing()
+
+ protocol.makeConnection(self._transport)
+ self._bodyProtocol = protocol
+ for data in self._bodyBuffer:
+ self._bodyProtocol.dataReceived(data)
+ self._bodyBuffer = None
+ self._state = 'CONNECTED'
+
+
+ def _deliverBody_CONNECTED(self, protocol):
+ """
+ It is invalid to attempt to deliver data to a protocol when it is
+ already being delivered to another protocol.
+ """
+ raise RuntimeError(
+ "Response already has protocol %r, cannot deliverBody "
+ "again" % (self._bodyProtocol,))
+
+
+ def _deliverBody_DEFERRED_CLOSE(self, protocol):
+ """
+ Deliver any buffered data to C{protocol} and then disconnect the
+ protocol. Move to the C{'FINISHED'} state.
+ """
+ # Unlike _deliverBody_INITIAL, there is no need to resume the
+ # transport here because all of the response data has been received
+ # already. Some higher level code may want to resume the transport if
+ # that code expects further data to be received over it.
+
+ protocol.makeConnection(self._transport)
+
+ for data in self._bodyBuffer:
+ protocol.dataReceived(data)
+ self._bodyBuffer = None
+ protocol.connectionLost(self._reason)
+ self._state = 'FINISHED'
+
+
+ def _deliverBody_FINISHED(self, protocol):
+ """
+ It is invalid to attempt to deliver data to a protocol after the
+ response body has been delivered to another protocol.
+ """
+ raise RuntimeError(
+ "Response already finished, cannot deliverBody now.")
+
+
+ def _bodyDataReceived(self, data):
+ """
+ Called by HTTPClientParser with chunks of data from the response body.
+ They will be buffered or delivered to the protocol passed to
+ deliverBody.
+ """
+ _bodyDataReceived = makeStatefulDispatcher('bodyDataReceived',
+ _bodyDataReceived)
+
+
+ def _bodyDataReceived_INITIAL(self, data):
+ """
+ Buffer any data received for later delivery to a protocol passed to
+ C{deliverBody}.
+
+ Little or no data should be buffered by this method, since the
+ transport has been paused and will not be resumed until a protocol
+ is supplied.
+ """
+ self._bodyBuffer.append(data)
+
+
+ def _bodyDataReceived_CONNECTED(self, data):
+ """
+ Deliver any data received to the protocol to which this L{Response}
+ is connected.
+ """
+ self._bodyProtocol.dataReceived(data)
+
+
+ def _bodyDataReceived_DEFERRED_CLOSE(self, data):
+ """
+ It is invalid for data to be delivered after it has been indicated
+ that the response body has been completely delivered.
+ """
+ raise RuntimeError("Cannot receive body data after _bodyDataFinished")
+
+
+ def _bodyDataReceived_FINISHED(self, data):
+ """
+ It is invalid for data to be delivered after the response bofdy has
+ been delivered to a protocol.
+ """
+ raise RuntimeError("Cannot receive body data after protocol disconnected")
+
+
+ def _bodyDataFinished(self, reason=None):
+ """
+ Called by HTTPClientParser when no more body data is available. If the
+ optional reason is supplied, this indicates a problem or potential
+ problem receiving all of the response body.
+ """
+ _bodyDataFinished = makeStatefulDispatcher('bodyDataFinished',
+ _bodyDataFinished)
+
+
+ def _bodyDataFinished_INITIAL(self, reason=None):
+ """
+ Move to the C{'DEFERRED_CLOSE'} state to wait for a protocol to
+ which to deliver the response body.
+ """
+ self._state = 'DEFERRED_CLOSE'
+ if reason is None:
+ reason = Failure(ResponseDone("Response body fully received"))
+ self._reason = reason
+
+
+ def _bodyDataFinished_CONNECTED(self, reason=None):
+ """
+ Disconnect the protocol and move to the C{'FINISHED'} state.
+ """
+ if reason is None:
+ reason = Failure(ResponseDone("Response body fully received"))
+ self._bodyProtocol.connectionLost(reason)
+ self._bodyProtocol = None
+ self._state = 'FINISHED'
+
+
+ def _bodyDataFinished_DEFERRED_CLOSE(self):
+ """
+ It is invalid to attempt to notify the L{Response} of the end of the
+ response body data more than once.
+ """
+ raise RuntimeError("Cannot finish body data more than once")
+
+
+ def _bodyDataFinished_FINISHED(self):
+ """
+ It is invalid to attempt to notify the L{Response} of the end of the
+ response body data more than once.
+ """
+ raise RuntimeError("Cannot finish body data after protocol disconnected")
+
+
+
+class ChunkedEncoder:
+ """
+ Helper object which exposes L{IConsumer} on top of L{HTTP11ClientProtocol}
+ for streaming request bodies to the server.
+ """
+ implements(IConsumer)
+
+ def __init__(self, transport):
+ self.transport = transport
+
+
+ def _allowNoMoreWrites(self):
+ """
+ Indicate that no additional writes are allowed. Attempts to write
+ after calling this method will be met with an exception.
+ """
+ self.transport = None
+
+
+ def registerProducer(self, producer, streaming):
+ """
+ Register the given producer with C{self.transport}.
+ """
+ self.transport.registerProducer(producer, streaming)
+
+
+ def write(self, data):
+ """
+ Write the given request body bytes to the transport using chunked
+ encoding.
+
+ @type data: C{str}
+ """
+ if self.transport is None:
+ raise ExcessWrite()
+ self.transport.writeSequence(("%x\r\n" % len(data), data, "\r\n"))
+
+
+ def unregisterProducer(self):
+ """
+ Indicate that the request body is complete and finish the request.
+ """
+ self.write('')
+ self.transport.unregisterProducer()
+ self._allowNoMoreWrites()
+
+
+
+class TransportProxyProducer:
+ """
+ An L{IPushProducer} implementation which wraps another such thing and
+ proxies calls to it until it is told to stop.
+
+ @ivar _producer: The wrapped L{IPushProducer} provider or C{None} after
+ this proxy has been stopped.
+ """
+ implements(IPushProducer)
+
+ # LineReceiver uses this undocumented attribute of transports to decide
+ # when to stop calling lineReceived or rawDataReceived (if it finds it to
+ # be true, it doesn't bother to deliver any more data). Set disconnecting
+ # to False here and never change it to true so that all data is always
+ # delivered to us and so that LineReceiver doesn't fail with an
+ # AttributeError.
+ disconnecting = False
+
+ def __init__(self, producer):
+ self._producer = producer
+
+
+ def _stopProxying(self):
+ """
+ Stop forwarding calls of L{IPushProducer} methods to the underlying
+ L{IPushProvider} provider.
+ """
+ self._producer = None
+
+
+ def stopProducing(self):
+ """
+ Proxy the stoppage to the underlying producer, unless this proxy has
+ been stopped.
+ """
+ if self._producer is not None:
+ self._producer.stopProducing()
+
+
+ def resumeProducing(self):
+ """
+ Proxy the resumption to the underlying producer, unless this proxy has
+ been stopped.
+ """
+ if self._producer is not None:
+ self._producer.resumeProducing()
+
+
+ def pauseProducing(self):
+ """
+ Proxy the pause to the underlying producer, unless this proxy has been
+ stopped.
+ """
+ if self._producer is not None:
+ self._producer.pauseProducing()
+
+
+
+class HTTP11ClientProtocol(Protocol):
+ """
+ L{HTTP11ClientProtocol} is an implementation of the HTTP 1.1 client
+ protocol. It supports as few features as possible.
+
+ @ivar _parser: After a request is issued, the L{HTTPClientParser} to
+ which received data making up the response to that request is
+ delivered.
+
+ @ivar _finishedRequest: After a request is issued, the L{Deferred} which
+ will fire when a L{Response} object corresponding to that request is
+ available. This allows L{HTTP11ClientProtocol} to fail the request
+ if there is a connection or parsing problem.
+
+ @ivar _currentRequest: After a request is issued, the L{Request}
+ instance used to make that request. This allows
+ L{HTTP11ClientProtocol} to stop request generation if necessary (for
+ example, if the connection is lost).
+
+ @ivar _transportProxy: After a request is issued, the
+ L{TransportProxyProducer} to which C{_parser} is connected. This
+ allows C{_parser} to pause and resume the transport in a way which
+ L{HTTP11ClientProtocol} can exert some control over.
+
+ @ivar _responseDeferred: After a request is issued, the L{Deferred} from
+ C{_parser} which will fire with a L{Response} when one has been
+ received. This is eventually chained with C{_finishedRequest}, but
+ only in certain cases to avoid double firing that Deferred.
+
+ @ivar _state: Indicates what state this L{HTTP11ClientProtocol} instance
+ is in with respect to transmission of a request and reception of a
+ response. This may be one of C{'QUIESCENT'}, C{'TRANSMITTING'},
+ C{'TRANSMITTING_AFTER_RECEIVING_RESPONSE'}, C{'GENERATION_FAILED'},
+ C{'WAITING'}, or C{'CONNECTION_LOST'}, with the following meanings:
+
+ - QUIESCENT: This is the state L{HTTP11ClientProtocol} instances
+ start in. Nothing is happening: no request is being sent and no
+ response is being received or expected.
+
+ - TRANSMITTING: When a request is made (via L{request}), the
+ instance moves to this state. L{Request.writeTo} has been used
+ to start to send a request but it has not yet finished.
+
+ - TRANSMITTING_AFTER_RECEIVING_RESPONSE: The server has returned a
+ complete response but the request has not yet been fully sent
+ yet. The instance will remain in this state until the request
+ is fully sent.
+
+ - GENERATION_FAILED: There was an error while the request. The
+ request was not fully sent to the network.
+
+ - WAITING: The request was fully sent to the network. The
+ instance is now waiting for the response to be fully received.
+
+ - CONNECTION_LOST: The connection has been lost.
+ """
+ _state = 'QUIESCENT'
+ _parser = None
+
+ def request(self, request):
+ """
+ Issue C{request} over C{self.transport} and return a L{Deferred} which
+ will fire with a L{Response} instance or an error.
+
+ @param request: The object defining the parameters of the request to
+ issue.
+ @type request: L{Request}
+
+ @rtype: L{Deferred}
+ @return: The deferred may errback with L{RequestGenerationFailed} if
+ the request was not fully written to the transport due to a local
+ error. It may errback with L{RequestTransmissionFailed} if it was
+ not fully written to the transport due to a network error. It may
+ errback with L{ResponseFailed} if the request was sent (not
+ necessarily received) but some or all of the response was lost. It
+ may errback with L{RequestNotSent} if it is not possible to send
+ any more requests using this L{HTTP11ClientProtocol}.
+ """
+ if self._state != 'QUIESCENT':
+ return fail(RequestNotSent())
+
+ self._state = 'TRANSMITTING'
+ _requestDeferred = maybeDeferred(request.writeTo, self.transport)
+ self._finishedRequest = Deferred()
+
+ # Keep track of the Request object in case we need to call stopWriting
+ # on it.
+ self._currentRequest = request
+
+ self._transportProxy = TransportProxyProducer(self.transport)
+ self._parser = HTTPClientParser(request, self._finishResponse)
+ self._parser.makeConnection(self._transportProxy)
+ self._responseDeferred = self._parser._responseDeferred
+
+ def cbRequestWrotten(ignored):
+ if self._state == 'TRANSMITTING':
+ self._state = 'WAITING'
+ # XXX We're stuck in WAITING until we lose the connection now.
+ # This will be wrong when persistent connections are supported.
+ # See #3420 for persistent connections.
+
+ self._responseDeferred.chainDeferred(self._finishedRequest)
+
+ def ebRequestWriting(err):
+ if self._state == 'TRANSMITTING':
+ self._state = 'GENERATION_FAILED'
+ self.transport.loseConnection()
+ self._finishedRequest.errback(
+ Failure(RequestGenerationFailed([err])))
+ else:
+ log.err(err, "foo")
+
+ _requestDeferred.addCallbacks(cbRequestWrotten, ebRequestWriting)
+
+ return self._finishedRequest
+
+
+ def _finishResponse(self, rest):
+ """
+ Called by an L{HTTPClientParser} to indicate that it has parsed a
+ complete response.
+
+ @param rest: A C{str} giving any trailing bytes which were given to
+ the L{HTTPClientParser} which were not part of the response it
+ was parsing.
+ """
+ # XXX this is because Connection: close is hard-coded above, probably
+ # will want to change that at some point. Either the client or the
+ # server can control this.
+
+ # XXX If the connection isn't being closed at this point, it's
+ # important to make sure the transport isn't paused (after _giveUp,
+ # or inside it, or something - after the parser can no longer touch
+ # the transport)
+
+ # For both of the above, see #3420 for persistent connections.
+
+ if self._state == 'TRANSMITTING':
+ # The server sent the entire response before we could send the
+ # whole request. That sucks. Oh well. Fire the request()
+ # Deferred with the response. But first, make sure that if the
+ # request does ever finish being written that it won't try to fire
+ # that Deferred.
+ self._state = 'TRANSMITTING_AFTER_RECEIVING_RESPONSE'
+ self._responseDeferred.chainDeferred(self._finishedRequest)
+
+ self._giveUp(Failure(ConnectionDone("synthetic!")))
+
+
+ def _disconnectParser(self, reason):
+ """
+ If there is still a parser, call its C{connectionLost} method with the
+ given reason. If there is not, do nothing.
+
+ @type reason: L{Failure}
+ """
+ if self._parser is not None:
+ parser = self._parser
+ self._parser = None
+
+ # The parser is no longer allowed to do anything to the real
+ # transport. Stop proxying from the parser's transport to the real
+ # transport before telling the parser it's done so that it can't do
+ # anything.
+ self._transportProxy._stopProxying()
+
+ parser.connectionLost(reason)
+
+
+ def _giveUp(self, reason):
+ """
+ Lose the underlying connection and disconnect the parser with the given
+ L{Failure}.
+
+ Use this method instead of calling the transport's loseConnection
+ method directly otherwise random things will break.
+ """
+ self.transport.loseConnection()
+ self._disconnectParser(reason)
+
+
+ def dataReceived(self, bytes):
+ """
+ Handle some stuff from some place.
+ """
+ try:
+ self._parser.dataReceived(bytes)
+ except:
+ self._giveUp(Failure())
+
+
+ def connectionLost(self, reason):
+ """
+ The underlying transport went away. If appropriate, notify the parser
+ object.
+ """
+ connectionLost = makeStatefulDispatcher('connectionLost', connectionLost)
+
+
+ def _connectionLost_QUIESCENT(self, reason):
+ """
+ Nothing is currently happening. Move to the C{'CONNECTION_LOST'}
+ state but otherwise do nothing.
+ """
+ self._state = 'CONNECTION_LOST'
+
+
+ def _connectionLost_GENERATION_FAILED(self, reason):
+ """
+ The connection was in an inconsistent state. Move to the
+ C{'CONNECTION_LOST'} state but otherwise do nothing.
+ """
+ self._state = 'CONNECTION_LOST'
+
+
+ def _connectionLost_TRANSMITTING(self, reason):
+ """
+ Fail the L{Deferred} for the current request, notify the request
+ object that it does not need to continue transmitting itself, and
+ move to the C{'CONNECTION_LOST'} state.
+ """
+ self._state = 'CONNECTION_LOST'
+ self._finishedRequest.errback(
+ Failure(RequestTransmissionFailed([reason])))
+ del self._finishedRequest
+
+ # Tell the request that it should stop bothering now.
+ self._currentRequest.stopWriting()
+
+
+ def _connectionLost_WAITING(self, reason):
+ """
+ Disconnect the response parser so that it can propagate the event as
+ necessary (for example, to call an application protocol's
+ C{connectionLost} method, or to fail a request L{Deferred}) and move
+ to the C{'CONNECTION_LOST'} state.
+ """
+ self._disconnectParser(reason)
+ self._state = 'CONNECTION_LOST'
diff --git a/vendor/Twisted-10.0.0/twisted/web/_version.py b/vendor/Twisted-10.0.0/twisted/web/_version.py
new file mode 100644
index 0000000000..3f9d46ed0a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/_version.py
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twisted.web', 10, 0, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/web/client.py b/vendor/Twisted-10.0.0/twisted/web/client.py
new file mode 100644
index 0000000000..ae638224a9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/client.py
@@ -0,0 +1,644 @@
+# -*- test-case-name: twisted.web.test.test_webclient -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+HTTP client.
+"""
+
+import os, types
+from urlparse import urlunparse
+
+from twisted.python import log
+from twisted.web import http
+from twisted.internet import defer, protocol, reactor
+from twisted.python import failure
+from twisted.python.util import InsensitiveDict
+from twisted.web import error
+from twisted.web.http_headers import Headers
+from twisted.python.compat import set
+
+
+class PartialDownloadError(error.Error):
+ """
+ Page was only partially downloaded, we got disconnected in middle.
+
+ @ivar response: All of the response body which was downloaded.
+ """
+
+
+class HTTPPageGetter(http.HTTPClient):
+ """
+ Gets a resource via HTTP, then quits.
+
+ Typically used with L{HTTPClientFactory}. Note that this class does not, by
+ itself, do anything with the response. If you want to download a resource
+ into a file, use L{HTTPPageDownloader} instead.
+ """
+
+ quietLoss = 0
+ followRedirect = True
+ failed = 0
+
+ _specialHeaders = set(('host', 'user-agent', 'cookie', 'content-length'))
+
+ def connectionMade(self):
+ method = getattr(self.factory, 'method', 'GET')
+ self.sendCommand(method, self.factory.path)
+ self.sendHeader('Host', self.factory.headers.get("host", self.factory.host))
+ self.sendHeader('User-Agent', self.factory.agent)
+ data = getattr(self.factory, 'postdata', None)
+ if data is not None:
+ self.sendHeader("Content-Length", str(len(data)))
+
+ cookieData = []
+ for (key, value) in self.factory.headers.items():
+ if key.lower() not in self._specialHeaders:
+ # we calculated it on our own
+ self.sendHeader(key, value)
+ if key.lower() == 'cookie':
+ cookieData.append(value)
+ for cookie, cookval in self.factory.cookies.items():
+ cookieData.append('%s=%s' % (cookie, cookval))
+ if cookieData:
+ self.sendHeader('Cookie', '; '.join(cookieData))
+ self.endHeaders()
+ self.headers = {}
+
+ if data is not None:
+ self.transport.write(data)
+
+ def handleHeader(self, key, value):
+ """
+ Called every time a header is received. Stores the header information
+ as key-value pairs in the C{headers} attribute.
+
+ @type key: C{str}
+ @param key: An HTTP header field name.
+
+ @type value: C{str}
+ @param value: An HTTP header field value.
+ """
+ key = key.lower()
+ l = self.headers.setdefault(key, [])
+ l.append(value)
+
+ def handleStatus(self, version, status, message):
+ self.version, self.status, self.message = version, status, message
+ self.factory.gotStatus(version, status, message)
+
+ def handleEndHeaders(self):
+ self.factory.gotHeaders(self.headers)
+ m = getattr(self, 'handleStatus_'+self.status, self.handleStatusDefault)
+ m()
+
+ def handleStatus_200(self):
+ pass
+
+ handleStatus_201 = lambda self: self.handleStatus_200()
+ handleStatus_202 = lambda self: self.handleStatus_200()
+
+ def handleStatusDefault(self):
+ self.failed = 1
+
+ def handleStatus_301(self):
+ l = self.headers.get('location')
+ if not l:
+ self.handleStatusDefault()
+ return
+ url = l[0]
+ if self.followRedirect:
+ scheme, host, port, path = \
+ _parse(url, defaultPort=self.transport.getPeer().port)
+
+ self.factory._redirectCount += 1
+ if self.factory._redirectCount >= self.factory.redirectLimit:
+ err = error.InfiniteRedirection(
+ self.status,
+ 'Infinite redirection detected',
+ location=url)
+ self.factory.noPage(failure.Failure(err))
+ self.quietLoss = True
+ self.transport.loseConnection()
+ return
+
+ self.factory.setURL(url)
+
+ if self.factory.scheme == 'https':
+ from twisted.internet import ssl
+ contextFactory = ssl.ClientContextFactory()
+ reactor.connectSSL(self.factory.host, self.factory.port,
+ self.factory, contextFactory)
+ else:
+ reactor.connectTCP(self.factory.host, self.factory.port,
+ self.factory)
+ else:
+ self.handleStatusDefault()
+ self.factory.noPage(
+ failure.Failure(
+ error.PageRedirect(
+ self.status, self.message, location = url)))
+ self.quietLoss = True
+ self.transport.loseConnection()
+
+ def handleStatus_302(self):
+ if self.afterFoundGet:
+ self.handleStatus_303()
+ self.handleStatus_301()
+
+
+ def handleStatus_303(self):
+ self.factory.method = 'GET'
+ self.handleStatus_301()
+
+ def connectionLost(self, reason):
+ if not self.quietLoss:
+ http.HTTPClient.connectionLost(self, reason)
+ self.factory.noPage(reason)
+
+ def handleResponse(self, response):
+ if self.quietLoss:
+ return
+ if self.failed:
+ self.factory.noPage(
+ failure.Failure(
+ error.Error(
+ self.status, self.message, response)))
+ if self.factory.method == 'HEAD':
+ # Callback with empty string, since there is never a response
+ # body for HEAD requests.
+ self.factory.page('')
+ elif self.length != None and self.length != 0:
+ self.factory.noPage(failure.Failure(
+ PartialDownloadError(self.status, self.message, response)))
+ else:
+ self.factory.page(response)
+ # server might be stupid and not close connection. admittedly
+ # the fact we do only one request per connection is also
+ # stupid...
+ self.transport.loseConnection()
+
+ def timeout(self):
+ self.quietLoss = True
+ self.transport.loseConnection()
+ self.factory.noPage(defer.TimeoutError("Getting %s took longer than %s seconds." % (self.factory.url, self.factory.timeout)))
+
+
+class HTTPPageDownloader(HTTPPageGetter):
+
+ transmittingPage = 0
+
+ def handleStatus_200(self, partialContent=0):
+ HTTPPageGetter.handleStatus_200(self)
+ self.transmittingPage = 1
+ self.factory.pageStart(partialContent)
+
+ def handleStatus_206(self):
+ self.handleStatus_200(partialContent=1)
+
+ def handleResponsePart(self, data):
+ if self.transmittingPage:
+ self.factory.pagePart(data)
+
+ def handleResponseEnd(self):
+ if self.length:
+ self.transmittingPage = 0
+ self.factory.noPage(
+ failure.Failure(
+ PartialDownloadError(self.status)))
+ if self.transmittingPage:
+ self.factory.pageEnd()
+ self.transmittingPage = 0
+ if self.failed:
+ self.factory.noPage(
+ failure.Failure(
+ error.Error(
+ self.status, self.message, None)))
+ self.transport.loseConnection()
+
+
+class HTTPClientFactory(protocol.ClientFactory):
+ """Download a given URL.
+
+ @type deferred: Deferred
+ @ivar deferred: A Deferred that will fire when the content has
+ been retrieved. Once this is fired, the ivars `status', `version',
+ and `message' will be set.
+
+ @type status: str
+ @ivar status: The status of the response.
+
+ @type version: str
+ @ivar version: The version of the response.
+
+ @type message: str
+ @ivar message: The text message returned with the status.
+
+ @type response_headers: dict
+ @ivar response_headers: The headers that were specified in the
+ response from the server.
+
+ @type method: str
+ @ivar method: The HTTP method to use in the request. This should be one of
+ OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, or CONNECT (case
+ matters). Other values may be specified if the server being contacted
+ supports them.
+
+ @type redirectLimit: int
+ @ivar redirectLimit: The maximum number of HTTP redirects that can occur
+ before it is assumed that the redirection is endless.
+
+ @type afterFoundGet: C{bool}
+ @ivar afterFoundGet: Deviate from the HTTP 1.1 RFC by handling redirects
+ the same way as most web browsers; if the request method is POST and a
+ 302 status is encountered, the redirect is followed with a GET method
+
+ @type _redirectCount: int
+ @ivar _redirectCount: The current number of HTTP redirects encountered.
+ """
+
+ protocol = HTTPPageGetter
+
+ url = None
+ scheme = None
+ host = ''
+ port = None
+ path = None
+
+ def __init__(self, url, method='GET', postdata=None, headers=None,
+ agent="Twisted PageGetter", timeout=0, cookies=None,
+ followRedirect=True, redirectLimit=20,
+ afterFoundGet=False):
+ self.followRedirect = followRedirect
+ self.redirectLimit = redirectLimit
+ self._redirectCount = 0
+ self.timeout = timeout
+ self.agent = agent
+ self.afterFoundGet = afterFoundGet
+ if cookies is None:
+ cookies = {}
+ self.cookies = cookies
+ if headers is not None:
+ self.headers = InsensitiveDict(headers)
+ else:
+ self.headers = InsensitiveDict()
+ if postdata is not None:
+ self.headers.setdefault('Content-Length', len(postdata))
+ # just in case a broken http/1.1 decides to keep connection alive
+ self.headers.setdefault("connection", "close")
+ self.postdata = postdata
+ self.method = method
+
+ self.setURL(url)
+
+ self.waiting = 1
+ self.deferred = defer.Deferred()
+ self.response_headers = None
+
+ def __repr__(self):
+ return "<%s: %s>" % (self.__class__.__name__, self.url)
+
+ def setURL(self, url):
+ self.url = url
+ scheme, host, port, path = _parse(url)
+ if scheme and host:
+ self.scheme = scheme
+ self.host = host
+ self.port = port
+ self.path = path
+
+ def buildProtocol(self, addr):
+ p = protocol.ClientFactory.buildProtocol(self, addr)
+ p.followRedirect = self.followRedirect
+ p.afterFoundGet = self.afterFoundGet
+ if self.timeout:
+ timeoutCall = reactor.callLater(self.timeout, p.timeout)
+ self.deferred.addBoth(self._cancelTimeout, timeoutCall)
+ return p
+
+ def _cancelTimeout(self, result, timeoutCall):
+ if timeoutCall.active():
+ timeoutCall.cancel()
+ return result
+
+ def gotHeaders(self, headers):
+ self.response_headers = headers
+ if headers.has_key('set-cookie'):
+ for cookie in headers['set-cookie']:
+ cookparts = cookie.split(';')
+ cook = cookparts[0]
+ cook.lstrip()
+ k, v = cook.split('=', 1)
+ self.cookies[k.lstrip()] = v.lstrip()
+
+ def gotStatus(self, version, status, message):
+ self.version, self.status, self.message = version, status, message
+
+ def page(self, page):
+ if self.waiting:
+ self.waiting = 0
+ self.deferred.callback(page)
+
+ def noPage(self, reason):
+ if self.waiting:
+ self.waiting = 0
+ self.deferred.errback(reason)
+
+ def clientConnectionFailed(self, _, reason):
+ if self.waiting:
+ self.waiting = 0
+ self.deferred.errback(reason)
+
+
+class HTTPDownloader(HTTPClientFactory):
+ """Download to a file."""
+
+ protocol = HTTPPageDownloader
+ value = None
+
+ def __init__(self, url, fileOrName,
+ method='GET', postdata=None, headers=None,
+ agent="Twisted client", supportPartial=0,
+ timeout=0, cookies=None, followRedirect=1,
+ redirectLimit=20):
+ self.requestedPartial = 0
+ if isinstance(fileOrName, types.StringTypes):
+ self.fileName = fileOrName
+ self.file = None
+ if supportPartial and os.path.exists(self.fileName):
+ fileLength = os.path.getsize(self.fileName)
+ if fileLength:
+ self.requestedPartial = fileLength
+ if headers == None:
+ headers = {}
+ headers["range"] = "bytes=%d-" % fileLength
+ else:
+ self.file = fileOrName
+ HTTPClientFactory.__init__(
+ self, url, method=method, postdata=postdata, headers=headers,
+ agent=agent, timeout=timeout, cookies=cookies,
+ followRedirect=followRedirect, redirectLimit=redirectLimit)
+
+
+ def gotHeaders(self, headers):
+ HTTPClientFactory.gotHeaders(self, headers)
+ if self.requestedPartial:
+ contentRange = headers.get("content-range", None)
+ if not contentRange:
+ # server doesn't support partial requests, oh well
+ self.requestedPartial = 0
+ return
+ start, end, realLength = http.parseContentRange(contentRange[0])
+ if start != self.requestedPartial:
+ # server is acting wierdly
+ self.requestedPartial = 0
+
+
+ def openFile(self, partialContent):
+ if partialContent:
+ file = open(self.fileName, 'rb+')
+ file.seek(0, 2)
+ else:
+ file = open(self.fileName, 'wb')
+ return file
+
+ def pageStart(self, partialContent):
+ """Called on page download start.
+
+ @param partialContent: tells us if the download is partial download we requested.
+ """
+ if partialContent and not self.requestedPartial:
+ raise ValueError, "we shouldn't get partial content response if we didn't want it!"
+ if self.waiting:
+ try:
+ if not self.file:
+ self.file = self.openFile(partialContent)
+ except IOError:
+ #raise
+ self.deferred.errback(failure.Failure())
+
+ def pagePart(self, data):
+ if not self.file:
+ return
+ try:
+ self.file.write(data)
+ except IOError:
+ #raise
+ self.file = None
+ self.deferred.errback(failure.Failure())
+
+
+ def noPage(self, reason):
+ """
+ Close the storage file and errback the waiting L{Deferred} with the
+ given reason.
+ """
+ if self.waiting:
+ self.waiting = 0
+ if self.file:
+ try:
+ self.file.close()
+ except:
+ log.err(None, "Error closing HTTPDownloader file")
+ self.deferred.errback(reason)
+
+
+ def pageEnd(self):
+ self.waiting = 0
+ if not self.file:
+ return
+ try:
+ self.file.close()
+ except IOError:
+ self.deferred.errback(failure.Failure())
+ return
+ self.deferred.callback(self.value)
+
+
+
+def _parse(url, defaultPort=None):
+ """
+ Split the given URL into the scheme, host, port, and path.
+
+ @type url: C{str}
+ @param url: An URL to parse.
+
+ @type defaultPort: C{int} or C{None}
+ @param defaultPort: An alternate value to use as the port if the URL does
+ not include one.
+
+ @return: A four-tuple of the scheme, host, port, and path of the URL. All
+ of these are C{str} instances except for port, which is an C{int}.
+ """
+ url = url.strip()
+ parsed = http.urlparse(url)
+ scheme = parsed[0]
+ path = urlunparse(('', '') + parsed[2:])
+
+ if defaultPort is None:
+ if scheme == 'https':
+ defaultPort = 443
+ else:
+ defaultPort = 80
+
+ host, port = parsed[1], defaultPort
+ if ':' in host:
+ host, port = host.split(':')
+ try:
+ port = int(port)
+ except ValueError:
+ port = defaultPort
+
+ if path == '':
+ path = '/'
+
+ return scheme, host, port, path
+
+
+def _makeGetterFactory(url, factoryFactory, contextFactory=None,
+ *args, **kwargs):
+ """
+ Create and connect an HTTP page getting factory.
+
+ Any additional positional or keyword arguments are used when calling
+ C{factoryFactory}.
+
+ @param factoryFactory: Factory factory that is called with C{url}, C{args}
+ and C{kwargs} to produce the getter
+
+ @param contextFactory: Context factory to use when creating a secure
+ connection, defaulting to C{None}
+
+ @return: The factory created by C{factoryFactory}
+ """
+ scheme, host, port, path = _parse(url)
+ factory = factoryFactory(url, *args, **kwargs)
+ if scheme == 'https':
+ from twisted.internet import ssl
+ if contextFactory is None:
+ contextFactory = ssl.ClientContextFactory()
+ reactor.connectSSL(host, port, factory, contextFactory)
+ else:
+ reactor.connectTCP(host, port, factory)
+ return factory
+
+
+def getPage(url, contextFactory=None, *args, **kwargs):
+ """
+ Download a web page as a string.
+
+ Download a page. Return a deferred, which will callback with a
+ page (as a string) or errback with a description of the error.
+
+ See HTTPClientFactory to see what extra args can be passed.
+ """
+ return _makeGetterFactory(
+ url,
+ HTTPClientFactory,
+ contextFactory=contextFactory,
+ *args, **kwargs).deferred
+
+
+def downloadPage(url, file, contextFactory=None, *args, **kwargs):
+ """
+ Download a web page to a file.
+
+ @param file: path to file on filesystem, or file-like object.
+
+ See HTTPDownloader to see what extra args can be passed.
+ """
+ factoryFactory = lambda url, *a, **kw: HTTPDownloader(url, file, *a, **kw)
+ return _makeGetterFactory(
+ url,
+ factoryFactory,
+ contextFactory=contextFactory,
+ *args, **kwargs).deferred
+
+
+# The code which follows is based on the new HTTP client implementation. It
+# should be significantly better than anything above, though it is not yet
+# feature equivalent.
+
+from twisted.internet.protocol import ClientCreator
+from twisted.web.error import SchemeNotSupported
+from twisted.web._newclient import ResponseDone, Request, HTTP11ClientProtocol
+from twisted.web._newclient import Response
+
+class Agent(object):
+ """
+ L{Agent} is a very basic HTTP client. It supports I{HTTP} scheme URIs. It
+ does not support persistent connections.
+
+ @ivar _reactor: The L{IReactorTCP} implementation which will be used to set
+ up connections over which to issue requests.
+
+ @since: 9.0
+ """
+ _protocol = HTTP11ClientProtocol
+
+ def __init__(self, reactor):
+ self._reactor = reactor
+
+
+ def request(self, method, uri, headers=None, bodyProducer=None):
+ """
+ Issue a new request.
+
+ @param method: The request method to send.
+ @type method: C{str}
+
+ @param uri: The request URI send.
+ @type uri: C{str}
+
+ @param headers: The request headers to send. If no I{Host} header is
+ included, one will be added based on the request URI.
+ @type headers: L{Headers}
+
+ @param bodyProducer: An object which will produce the request body or,
+ if the request body is to be empty, L{None}.
+ @type bodyProducer: L{IBodyProducer} provider
+
+ @return: A L{Deferred} which fires with the result of the request (a
+ L{Response} instance), or fails if there is a problem setting up a
+ connection over which to issue the request. It may also fail with
+ L{SchemeNotSupported} if the scheme of the given URI is not
+ supported.
+ @rtype: L{Deferred}
+ """
+ scheme, host, port, path = _parse(uri)
+ if scheme != 'http':
+ return defer.fail(SchemeNotSupported(
+ "Unsupported scheme: %r" % (scheme,)))
+ cc = ClientCreator(self._reactor, self._protocol)
+ d = cc.connectTCP(host, port)
+ if headers is None:
+ headers = Headers()
+ if not headers.hasHeader('host'):
+ # This is a lot of copying. It might be nice if there were a bit
+ # less.
+ headers = Headers(dict(headers.getAllRawHeaders()))
+ headers.addRawHeader(
+ 'host', self._computeHostValue(scheme, host, port))
+ def cbConnected(proto):
+ return proto.request(Request(method, path, headers, bodyProducer))
+ d.addCallback(cbConnected)
+ return d
+
+
+ def _computeHostValue(self, scheme, host, port):
+ """
+ Compute the string to use for the value of the I{Host} header, based on
+ the given scheme, host name, and port number.
+ """
+ if port == 80:
+ return host
+ return '%s:%d' % (host, port)
+
+
+
+__all__ = [
+ 'PartialDownloadError',
+ 'HTTPPageGetter', 'HTTPPageDownloader', 'HTTPClientFactory', 'HTTPDownloader',
+ 'getPage', 'downloadPage',
+
+ 'ResponseDone', 'Response', 'Agent']
diff --git a/vendor/Twisted-10.0.0/twisted/web/demo.py b/vendor/Twisted-10.0.0/twisted/web/demo.py
new file mode 100644
index 0000000000..57df24046f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/demo.py
@@ -0,0 +1,29 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""I am a simple test resource.
+"""
+
+from twisted.python import log
+from twisted.web import static
+
+class Test(static.Data):
+ isLeaf = True
+ def __init__(self):
+ static.Data.__init__(
+ self,
+ """
+ <html>
+ <head><title>Temporary Test</title><head>
+ <body>
+
+ Hello! This is a temporary test until a more sophisticated form
+ demo can be put back in using more up-to-date Twisted APIs.
+
+ </body>
+ </html>
+ """,
+ "text/html")
+
diff --git a/vendor/Twisted-10.0.0/twisted/web/distrib.py b/vendor/Twisted-10.0.0/twisted/web/distrib.py
new file mode 100644
index 0000000000..a94fd2f7af
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/distrib.py
@@ -0,0 +1,374 @@
+# -*- test-case-name: twisted.web.test.test_distrib -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Distributed web servers.
+
+This is going to have to be refactored so that argument parsing is done
+by each subprocess and not by the main web server (i.e. GET, POST etc.).
+"""
+
+# System Imports
+import types, os, copy, cStringIO
+try:
+ import pwd
+except ImportError:
+ pwd = None
+
+from xml.dom.minidom import Element, Text
+
+# Twisted Imports
+from twisted.spread import pb
+from twisted.spread.banana import SIZE_LIMIT
+from twisted.web import http, resource, server, html, static
+from twisted.web.http_headers import Headers
+from twisted.python import log
+from twisted.persisted import styles
+from twisted.internet import address, reactor
+
+
+class _ReferenceableProducerWrapper(pb.Referenceable):
+ def __init__(self, producer):
+ self.producer = producer
+
+ def remote_resumeProducing(self):
+ self.producer.resumeProducing()
+
+ def remote_pauseProducing(self):
+ self.producer.pauseProducing()
+
+ def remote_stopProducing(self):
+ self.producer.stopProducing()
+
+
+class Request(pb.RemoteCopy, server.Request):
+ """
+ A request which was received by a L{ResourceSubscription} and sent via
+ PB to a distributed node.
+ """
+ def setCopyableState(self, state):
+ """
+ Initialize this L{twisted.web.distrib.Request} based on the copied
+ state so that it closely resembles a L{twisted.web.server.Request}.
+ """
+ for k in 'host', 'client':
+ tup = state[k]
+ addrdesc = {'INET': 'TCP', 'UNIX': 'UNIX'}[tup[0]]
+ addr = {'TCP': lambda: address.IPv4Address(addrdesc,
+ tup[1], tup[2],
+ _bwHack='INET'),
+ 'UNIX': lambda: address.UNIXAddress(tup[1])}[addrdesc]()
+ state[k] = addr
+ state['requestHeaders'] = Headers(dict(state['requestHeaders']))
+ pb.RemoteCopy.setCopyableState(self, state)
+ # Emulate the local request interface --
+ self.content = cStringIO.StringIO(self.content_data)
+ self.finish = self.remote.remoteMethod('finish')
+ self.setHeader = self.remote.remoteMethod('setHeader')
+ self.addCookie = self.remote.remoteMethod('addCookie')
+ self.setETag = self.remote.remoteMethod('setETag')
+ self.setResponseCode = self.remote.remoteMethod('setResponseCode')
+ self.setLastModified = self.remote.remoteMethod('setLastModified')
+
+ # To avoid failing if a resource tries to write a very long string
+ # all at once, this one will be handled slightly differently.
+ self._write = self.remote.remoteMethod('write')
+
+
+ def write(self, bytes):
+ """
+ Write the given bytes to the response body.
+
+ @param bytes: The bytes to write. If this is longer than 640k, it
+ will be split up into smaller pieces.
+ """
+ start = 0
+ end = SIZE_LIMIT
+ while True:
+ self._write(bytes[start:end])
+ start += SIZE_LIMIT
+ end += SIZE_LIMIT
+ if start >= len(bytes):
+ break
+
+
+ def registerProducer(self, producer, streaming):
+ self.remote.callRemote("registerProducer",
+ _ReferenceableProducerWrapper(producer),
+ streaming).addErrback(self.fail)
+
+ def unregisterProducer(self):
+ self.remote.callRemote("unregisterProducer").addErrback(self.fail)
+
+ def fail(self, failure):
+ log.err(failure)
+
+
+pb.setUnjellyableForClass(server.Request, Request)
+
+class Issue:
+ def __init__(self, request):
+ self.request = request
+
+ def finished(self, result):
+ if result != server.NOT_DONE_YET:
+ assert isinstance(result, types.StringType),\
+ "return value not a string"
+ self.request.write(result)
+ self.request.finish()
+
+ def failed(self, failure):
+ #XXX: Argh. FIXME.
+ failure = str(failure)
+ self.request.write(
+ resource.ErrorPage(http.INTERNAL_SERVER_ERROR,
+ "Server Connection Lost",
+ "Connection to distributed server lost:" +
+ html.PRE(failure)).
+ render(self.request))
+ self.request.finish()
+ log.msg(failure)
+
+
+class ResourceSubscription(resource.Resource):
+ isLeaf = 1
+ waiting = 0
+ def __init__(self, host, port):
+ resource.Resource.__init__(self)
+ self.host = host
+ self.port = port
+ self.pending = []
+ self.publisher = None
+
+ def __getstate__(self):
+ """Get persistent state for this ResourceSubscription.
+ """
+ # When I unserialize,
+ state = copy.copy(self.__dict__)
+ # Publisher won't be connected...
+ state['publisher'] = None
+ # I won't be making a connection
+ state['waiting'] = 0
+ # There will be no pending requests.
+ state['pending'] = []
+ return state
+
+ def connected(self, publisher):
+ """I've connected to a publisher; I'll now send all my requests.
+ """
+ log.msg('connected to publisher')
+ publisher.broker.notifyOnDisconnect(self.booted)
+ self.publisher = publisher
+ self.waiting = 0
+ for request in self.pending:
+ self.render(request)
+ self.pending = []
+
+ def notConnected(self, msg):
+ """I can't connect to a publisher; I'll now reply to all pending
+ requests.
+ """
+ log.msg("could not connect to distributed web service: %s" % msg)
+ self.waiting = 0
+ self.publisher = None
+ for request in self.pending:
+ request.write("Unable to connect to distributed server.")
+ request.finish()
+ self.pending = []
+
+ def booted(self):
+ self.notConnected("connection dropped")
+
+ def render(self, request):
+ """Render this request, from my server.
+
+ This will always be asynchronous, and therefore return NOT_DONE_YET.
+ It spins off a request to the pb client, and either adds it to the list
+ of pending issues or requests it immediately, depending on if the
+ client is already connected.
+ """
+ if not self.publisher:
+ self.pending.append(request)
+ if not self.waiting:
+ self.waiting = 1
+ bf = pb.PBClientFactory()
+ timeout = 10
+ if self.host == "unix":
+ reactor.connectUNIX(self.port, bf, timeout)
+ else:
+ reactor.connectTCP(self.host, self.port, bf, timeout)
+ d = bf.getRootObject()
+ d.addCallbacks(self.connected, self.notConnected)
+
+ else:
+ i = Issue(request)
+ self.publisher.callRemote('request', request).addCallbacks(i.finished, i.failed)
+ return server.NOT_DONE_YET
+
+
+
+class ResourcePublisher(pb.Root, styles.Versioned):
+ """
+ L{ResourcePublisher} exposes a remote API which can be used to respond
+ to request.
+
+ @ivar site: The site which will be used for resource lookup.
+ @type site: L{twisted.web.server.Site}
+ """
+ def __init__(self, site):
+ self.site = site
+
+ persistenceVersion = 2
+
+ def upgradeToVersion2(self):
+ self.application.authorizer.removeIdentity("web")
+ del self.application.services[self.serviceName]
+ del self.serviceName
+ del self.application
+ del self.perspectiveName
+
+ def getPerspectiveNamed(self, name):
+ return self
+
+
+ def remote_request(self, request):
+ """
+ Look up the resource for the given request and render it.
+ """
+ res = self.site.getResourceFor(request)
+ log.msg( request )
+ result = res.render(request)
+ if result is not server.NOT_DONE_YET:
+ request.write(result)
+ request.finish()
+ return server.NOT_DONE_YET
+
+
+
+class UserDirectory(resource.Resource):
+ """
+ A resource which lists available user resources and serves them as
+ children.
+
+ @ivar _pwd: An object like L{pwd} which is used to enumerate users and
+ their home directories.
+ """
+
+ userDirName = 'public_html'
+ userSocketName = '.twistd-web-pb'
+
+ template = """
+<html>
+ <head>
+ <title>twisted.web.distrib.UserDirectory</title>
+ <style>
+
+ a
+ {
+ font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
+ color: #369;
+ text-decoration: none;
+ }
+
+ th
+ {
+ font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
+ font-weight: bold;
+ text-decoration: none;
+ text-align: left;
+ }
+
+ pre, code
+ {
+ font-family: "Courier New", Courier, monospace;
+ }
+
+ p, body, td, ol, ul, menu, blockquote, div
+ {
+ font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
+ color: #000;
+ }
+ </style>
+ </head>
+
+ <body>
+ <h1>twisted.web.distrib.UserDirectory</h1>
+
+ %(users)s
+</body>
+</html>
+"""
+
+ def __init__(self, userDatabase=None):
+ resource.Resource.__init__(self)
+ if userDatabase is None:
+ userDatabase = pwd
+ self._pwd = userDatabase
+
+
+ def _users(self):
+ """
+ Return a list of two-tuples giving links to user resources and text to
+ associate with those links.
+ """
+ users = []
+ for user in self._pwd.getpwall():
+ name, passwd, uid, gid, gecos, dir, shell = user
+ realname = gecos.split(',')[0]
+ if not realname:
+ realname = name
+ if os.path.exists(os.path.join(dir, self.userDirName)):
+ users.append((name, realname + ' (file)'))
+ twistdsock = os.path.join(dir, self.userSocketName)
+ if os.path.exists(twistdsock):
+ linkName = name + '.twistd'
+ users.append((linkName, realname + ' (twistd)'))
+ return users
+
+
+ def render_GET(self, request):
+ """
+ Render as HTML a listing of all known users with links to their
+ personal resources.
+ """
+ listing = Element('ul')
+ for link, text in self._users():
+ linkElement = Element('a')
+ linkElement.setAttribute('href', link + '/')
+ textNode = Text()
+ textNode.data = text
+ linkElement.appendChild(textNode)
+ item = Element('li')
+ item.appendChild(linkElement)
+ listing.appendChild(item)
+ return self.template % {'users': listing.toxml()}
+
+
+ def getChild(self, name, request):
+ if name == '':
+ return self
+
+ td = '.twistd'
+
+ if name[-len(td):] == td:
+ username = name[:-len(td)]
+ sub = 1
+ else:
+ username = name
+ sub = 0
+ try:
+ pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, pw_shell \
+ = self._pwd.getpwnam(username)
+ except KeyError:
+ return resource.NoResource()
+ if sub:
+ twistdsock = os.path.join(pw_dir, self.userSocketName)
+ rs = ResourceSubscription('unix',twistdsock)
+ self.putChild(name, rs)
+ return rs
+ else:
+ path = os.path.join(pw_dir, self.userDirName)
+ if not os.path.exists(path):
+ return resource.NoResource()
+ return static.File(path)
diff --git a/vendor/Twisted-10.0.0/twisted/web/domhelpers.py b/vendor/Twisted-10.0.0/twisted/web/domhelpers.py
new file mode 100644
index 0000000000..0ca9ce2c6b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/domhelpers.py
@@ -0,0 +1,268 @@
+# -*- test-case-name: twisted.web.test.test_domhelpers -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A library for performing interesting tasks with DOM objects.
+"""
+
+import StringIO
+
+from twisted.web import microdom
+from twisted.web.microdom import getElementsByTagName, escape, unescape
+
+
+class NodeLookupError(Exception):
+ pass
+
+
+def substitute(request, node, subs):
+ """
+ Look through the given node's children for strings, and
+ attempt to do string substitution with the given parameter.
+ """
+ for child in node.childNodes:
+ if hasattr(child, 'nodeValue') and child.nodeValue:
+ child.replaceData(0, len(child.nodeValue), child.nodeValue % subs)
+ substitute(request, child, subs)
+
+def _get(node, nodeId, nodeAttrs=('id','class','model','pattern')):
+ """
+ (internal) Get a node with the specified C{nodeId} as any of the C{class},
+ C{id} or C{pattern} attributes.
+ """
+
+ if hasattr(node, 'hasAttributes') and node.hasAttributes():
+ for nodeAttr in nodeAttrs:
+ if (str (node.getAttribute(nodeAttr)) == nodeId):
+ return node
+ if node.hasChildNodes():
+ if hasattr(node.childNodes, 'length'):
+ length = node.childNodes.length
+ else:
+ length = len(node.childNodes)
+ for childNum in range(length):
+ result = _get(node.childNodes[childNum], nodeId)
+ if result: return result
+
+def get(node, nodeId):
+ """
+ Get a node with the specified C{nodeId} as any of the C{class},
+ C{id} or C{pattern} attributes. If there is no such node, raise
+ L{NodeLookupError}.
+ """
+ result = _get(node, nodeId)
+ if result: return result
+ raise NodeLookupError, nodeId
+
+def getIfExists(node, nodeId):
+ """
+ Get a node with the specified C{nodeId} as any of the C{class},
+ C{id} or C{pattern} attributes. If there is no such node, return
+ C{None}.
+ """
+ return _get(node, nodeId)
+
+def getAndClear(node, nodeId):
+ """Get a node with the specified C{nodeId} as any of the C{class},
+ C{id} or C{pattern} attributes. If there is no such node, raise
+ L{NodeLookupError}. Remove all child nodes before returning.
+ """
+ result = get(node, nodeId)
+ if result:
+ clearNode(result)
+ return result
+
+def clearNode(node):
+ """
+ Remove all children from the given node.
+ """
+ node.childNodes[:] = []
+
+def locateNodes(nodeList, key, value, noNesting=1):
+ """
+ Find subnodes in the given node where the given attribute
+ has the given value.
+ """
+ returnList = []
+ if not isinstance(nodeList, type([])):
+ return locateNodes(nodeList.childNodes, key, value, noNesting)
+ for childNode in nodeList:
+ if not hasattr(childNode, 'getAttribute'):
+ continue
+ if str(childNode.getAttribute(key)) == value:
+ returnList.append(childNode)
+ if noNesting:
+ continue
+ returnList.extend(locateNodes(childNode, key, value, noNesting))
+ return returnList
+
+def superSetAttribute(node, key, value):
+ if not hasattr(node, 'setAttribute'): return
+ node.setAttribute(key, value)
+ if node.hasChildNodes():
+ for child in node.childNodes:
+ superSetAttribute(child, key, value)
+
+def superPrependAttribute(node, key, value):
+ if not hasattr(node, 'setAttribute'): return
+ old = node.getAttribute(key)
+ if old:
+ node.setAttribute(key, value+'/'+old)
+ else:
+ node.setAttribute(key, value)
+ if node.hasChildNodes():
+ for child in node.childNodes:
+ superPrependAttribute(child, key, value)
+
+def superAppendAttribute(node, key, value):
+ if not hasattr(node, 'setAttribute'): return
+ old = node.getAttribute(key)
+ if old:
+ node.setAttribute(key, old + '/' + value)
+ else:
+ node.setAttribute(key, value)
+ if node.hasChildNodes():
+ for child in node.childNodes:
+ superAppendAttribute(child, key, value)
+
+def gatherTextNodes(iNode, dounescape=0, joinWith=""):
+ """Visit each child node and collect its text data, if any, into a string.
+For example::
+ >>> doc=microdom.parseString('<a>1<b>2<c>3</c>4</b></a>')
+ >>> gatherTextNodes(doc.documentElement)
+ '1234'
+With dounescape=1, also convert entities back into normal characters.
+@return: the gathered nodes as a single string
+@rtype: str
+"""
+ gathered=[]
+ gathered_append=gathered.append
+ slice=[iNode]
+ while len(slice)>0:
+ c=slice.pop(0)
+ if hasattr(c, 'nodeValue') and c.nodeValue is not None:
+ if dounescape:
+ val=unescape(c.nodeValue)
+ else:
+ val=c.nodeValue
+ gathered_append(val)
+ slice[:0]=c.childNodes
+ return joinWith.join(gathered)
+
+class RawText(microdom.Text):
+ """This is an evil and horrible speed hack. Basically, if you have a big
+ chunk of XML that you want to insert into the DOM, but you don't want to
+ incur the cost of parsing it, you can construct one of these and insert it
+ into the DOM. This will most certainly only work with microdom as the API
+ for converting nodes to xml is different in every DOM implementation.
+
+ This could be improved by making this class a Lazy parser, so if you
+ inserted this into the DOM and then later actually tried to mutate this
+ node, it would be parsed then.
+ """
+
+ def writexml(self, writer, indent="", addindent="", newl="", strip=0, nsprefixes=None, namespace=None):
+ writer.write("%s%s%s" % (indent, self.data, newl))
+
+def findNodes(parent, matcher, accum=None):
+ if accum is None:
+ accum = []
+ if not parent.hasChildNodes():
+ return accum
+ for child in parent.childNodes:
+ # print child, child.nodeType, child.nodeName
+ if matcher(child):
+ accum.append(child)
+ findNodes(child, matcher, accum)
+ return accum
+
+
+def findNodesShallowOnMatch(parent, matcher, recurseMatcher, accum=None):
+ if accum is None:
+ accum = []
+ if not parent.hasChildNodes():
+ return accum
+ for child in parent.childNodes:
+ # print child, child.nodeType, child.nodeName
+ if matcher(child):
+ accum.append(child)
+ if recurseMatcher(child):
+ findNodesShallowOnMatch(child, matcher, recurseMatcher, accum)
+ return accum
+
+def findNodesShallow(parent, matcher, accum=None):
+ if accum is None:
+ accum = []
+ if not parent.hasChildNodes():
+ return accum
+ for child in parent.childNodes:
+ if matcher(child):
+ accum.append(child)
+ else:
+ findNodes(child, matcher, accum)
+ return accum
+
+
+def findElementsWithAttributeShallow(parent, attribute):
+ """
+ Return an iterable of the elements which are direct children of C{parent}
+ and which have the C{attribute} attribute.
+ """
+ return findNodesShallow(parent,
+ lambda n: getattr(n, 'tagName', None) is not None and
+ n.hasAttribute(attribute))
+
+
+def findElements(parent, matcher):
+ """
+ Return an iterable of the elements which are children of C{parent} for
+ which the predicate C{matcher} returns true.
+ """
+ return findNodes(
+ parent,
+ lambda n, matcher=matcher: getattr(n, 'tagName', None) is not None and
+ matcher(n))
+
+def findElementsWithAttribute(parent, attribute, value=None):
+ if value:
+ return findElements(
+ parent,
+ lambda n, attribute=attribute, value=value:
+ n.hasAttribute(attribute) and n.getAttribute(attribute) == value)
+ else:
+ return findElements(
+ parent,
+ lambda n, attribute=attribute: n.hasAttribute(attribute))
+
+
+def findNodesNamed(parent, name):
+ return findNodes(parent, lambda n, name=name: n.nodeName == name)
+
+
+def writeNodeData(node, oldio):
+ for subnode in node.childNodes:
+ if hasattr(subnode, 'data'):
+ oldio.write(subnode.data)
+ else:
+ writeNodeData(subnode, oldio)
+
+
+def getNodeText(node):
+ oldio = StringIO.StringIO()
+ writeNodeData(node, oldio)
+ return oldio.getvalue()
+
+
+def getParents(node):
+ l = []
+ while node:
+ l.append(node)
+ node = node.parentNode
+ return l
+
+def namedChildren(parent, nodeName):
+ """namedChildren(parent, nodeName) -> children (not descendants) of parent
+ that have tagName == nodeName
+ """
+ return [n for n in parent.childNodes if getattr(n, 'tagName', '')==nodeName]
diff --git a/vendor/Twisted-10.0.0/twisted/web/error.py b/vendor/Twisted-10.0.0/twisted/web/error.py
new file mode 100644
index 0000000000..2d01da2f9a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/error.py
@@ -0,0 +1,230 @@
+# -*- test-case-name: twisted.web.test.test_error -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Exception definitions for L{twisted.web}.
+"""
+
+import operator, warnings
+
+from twisted.web import http
+
+
+class Error(Exception):
+ """
+ A basic HTTP error.
+
+ @type status: C{str}
+ @ivar status: Refers to an HTTP status code, for example L{http.NOT_FOUND}.
+
+ @type message: C{str}
+ @param message: A short error message, for example "NOT FOUND".
+
+ @type response: C{str}
+ @ivar response: A complete HTML document for an error page.
+ """
+ def __init__(self, code, message=None, response=None):
+ """
+ Initializes a basic exception.
+
+ @type code: C{str}
+ @param code: Refers to an HTTP status code, for example
+ L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
+ descriptive string that is used instead.
+
+ @type message: C{str}
+ @param message: A short error message, for example "NOT FOUND".
+
+ @type response: C{str}
+ @param response: A complete HTML document for an error page.
+ """
+ if not message:
+ try:
+ message = http.responses.get(int(code))
+ except ValueError:
+ # If code wasn't a stringified int, can't map the
+ # status code to a descriptive string so keep message
+ # unchanged.
+ pass
+
+ Exception.__init__(self, code, message, response)
+ self.status = code
+ self.message = message
+ self.response = response
+
+
+ def __str__(self):
+ return '%s %s' % (self[0], self[1])
+
+
+
+class PageRedirect(Error):
+ """
+ A request resulted in an HTTP redirect.
+
+ @type location: C{str}
+ @ivar location: The location of the redirect which was not followed.
+ """
+ def __init__(self, code, message=None, response=None, location=None):
+ """
+ Initializes a page redirect exception.
+
+ @type code: C{str}
+ @param code: Refers to an HTTP status code, for example
+ L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
+ descriptive string that is used instead.
+
+ @type message: C{str}
+ @param message: A short error message, for example "NOT FOUND".
+
+ @type response: C{str}
+ @param response: A complete HTML document for an error page.
+
+ @type location: C{str}
+ @param location: The location response-header field value. It is an
+ absolute URI used to redirect the receiver to a location other than
+ the Request-URI so the request can be completed.
+ """
+ if not message:
+ try:
+ message = http.responses.get(int(code))
+ except ValueError:
+ # If code wasn't a stringified int, can't map the
+ # status code to a descriptive string so keep message
+ # unchanged.
+ pass
+
+ if location and message:
+ message = "%s to %s" % (message, location)
+
+ Error.__init__(self, code, message, response)
+ self.location = location
+
+
+
+class InfiniteRedirection(Error):
+ """
+ HTTP redirection is occurring endlessly.
+
+ @type location: C{str}
+ @ivar location: The first URL in the series of redirections which was
+ not followed.
+ """
+ def __init__(self, code, message=None, response=None, location=None):
+ """
+ Initializes an infinite redirection exception.
+
+ @type code: C{str}
+ @param code: Refers to an HTTP status code, for example
+ L{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
+ descriptive string that is used instead.
+
+ @type message: C{str}
+ @param message: A short error message, for example "NOT FOUND".
+
+ @type response: C{str}
+ @param response: A complete HTML document for an error page.
+
+ @type location: C{str}
+ @param location: The location response-header field value. It is an
+ absolute URI used to redirect the receiver to a location other than
+ the Request-URI so the request can be completed.
+ """
+ if not message:
+ try:
+ message = http.responses.get(int(code))
+ except ValueError:
+ # If code wasn't a stringified int, can't map the
+ # status code to a descriptive string so keep message
+ # unchanged.
+ pass
+
+ if location and message:
+ message = "%s to %s" % (message, location)
+
+ Error.__init__(self, code, message, response)
+ self.location = location
+
+
+
+class UnsupportedMethod(Exception):
+ """
+ Raised by a resource when faced with a strange request method.
+
+ RFC 2616 (HTTP 1.1) gives us two choices when faced with this situtation:
+ If the type of request is known to us, but not allowed for the requested
+ resource, respond with NOT_ALLOWED. Otherwise, if the request is something
+ we don't know how to deal with in any case, respond with NOT_IMPLEMENTED.
+
+ When this exception is raised by a Resource's render method, the server
+ will make the appropriate response.
+
+ This exception's first argument MUST be a sequence of the methods the
+ resource *does* support.
+ """
+
+ allowedMethods = ()
+
+ def __init__(self, allowedMethods, *args):
+ Exception.__init__(self, allowedMethods, *args)
+ self.allowedMethods = allowedMethods
+
+ if not operator.isSequenceType(allowedMethods):
+ why = "but my first argument is not a sequence."
+ s = ("First argument must be a sequence of"
+ " supported methods, %s" % (why,))
+ raise TypeError, s
+
+
+
+class SchemeNotSupported(Exception):
+ """
+ The scheme of a URI was not one of the supported values.
+ """
+
+
+
+from twisted.web import resource as _resource
+
+class ErrorPage(_resource.ErrorPage):
+ """
+ Deprecated alias for L{twisted.web.resource.ErrorPage}.
+ """
+ def __init__(self, *args, **kwargs):
+ warnings.warn(
+ "twisted.web.error.ErrorPage is deprecated since Twisted 9.0. "
+ "See twisted.web.resource.ErrorPage.", DeprecationWarning,
+ stacklevel=2)
+ _resource.ErrorPage.__init__(self, *args, **kwargs)
+
+
+
+class NoResource(_resource.NoResource):
+ """
+ Deprecated alias for L{twisted.web.resource.NoResource}.
+ """
+ def __init__(self, *args, **kwargs):
+ warnings.warn(
+ "twisted.web.error.NoResource is deprecated since Twisted 9.0. "
+ "See twisted.web.resource.NoResource.", DeprecationWarning,
+ stacklevel=2)
+ _resource.NoResource.__init__(self, *args, **kwargs)
+
+
+
+class ForbiddenResource(_resource.ForbiddenResource):
+ """
+ Deprecated alias for L{twisted.web.resource.ForbiddenResource}.
+ """
+ def __init__(self, *args, **kwargs):
+ warnings.warn(
+ "twisted.web.error.ForbiddenResource is deprecated since Twisted "
+ "9.0. See twisted.web.resource.ForbiddenResource.",
+ DeprecationWarning, stacklevel=2)
+ _resource.ForbiddenResource.__init__(self, *args, **kwargs)
+
+
+__all__ = [
+ 'Error', 'PageRedirect', 'InfiniteRedirection',
+ 'ErrorPage', 'NoResource', 'ForbiddenResource']
diff --git a/vendor/Twisted-10.0.0/twisted/web/google.py b/vendor/Twisted-10.0.0/twisted/web/google.py
new file mode 100644
index 0000000000..a0b8e8f073
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/google.py
@@ -0,0 +1,75 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+"""\"I'm Feeling Lucky\" with U{Google<http://google.com>}.
+"""
+import urllib
+from twisted.internet import protocol, reactor, defer
+from twisted.web import http
+
+class GoogleChecker(http.HTTPClient):
+
+ def connectionMade(self):
+ self.sendCommand('GET', self.factory.url)
+ self.sendHeader('Host', self.factory.host)
+ self.sendHeader('User-Agent', self.factory.agent)
+ self.endHeaders()
+
+ def handleHeader(self, key, value):
+ key = key.lower()
+ if key == 'location':
+ self.factory.gotLocation(value)
+
+ def handleStatus(self, version, status, message):
+ if status != '302':
+ self.factory.noLocation(ValueError("bad status"))
+
+ def handleEndHeaders(self):
+ self.factory.noLocation(ValueError("no location"))
+
+ def handleResponsePart(self, part):
+ pass
+
+ def handleResponseEnd(self):
+ pass
+
+ def connectionLost(self, reason):
+ self.factory.noLocation(reason)
+
+
+class GoogleCheckerFactory(protocol.ClientFactory):
+
+ protocol = GoogleChecker
+
+ def __init__(self, words):
+ self.url = ('/search?q=%s&btnI=%s' %
+ (urllib.quote_plus(' '.join(words)),
+ urllib.quote_plus("I'm Feeling Lucky")))
+ self.agent="Twisted/GoogleChecker"
+ self.host = "www.google.com"
+ self.deferred = defer.Deferred()
+
+ def clientConnectionFailed(self, _, reason):
+ self.noLocation(reason)
+
+ def gotLocation(self, location):
+ if self.deferred:
+ self.deferred.callback(location)
+ self.deferred = None
+
+ def noLocation(self, error):
+ if self.deferred:
+ self.deferred.errback(error)
+ self.deferred = None
+
+
+def checkGoogle(words):
+ """Check google for a match.
+
+ @returns: a Deferred which will callback with a URL or errback with a
+ Failure.
+ """
+ factory = GoogleCheckerFactory(words)
+ reactor.connectTCP('www.google.com', 80, factory)
+ return factory.deferred
diff --git a/vendor/Twisted-10.0.0/twisted/web/guard.py b/vendor/Twisted-10.0.0/twisted/web/guard.py
new file mode 100644
index 0000000000..bb3170bd79
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/guard.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Resource traversal integration with L{twisted.cred} to allow for
+authentication and authorization of HTTP requests.
+"""
+
+# Expose HTTP authentication classes here.
+from twisted.web._auth.wrapper import HTTPAuthSessionWrapper
+from twisted.web._auth.basic import BasicCredentialFactory
+from twisted.web._auth.digest import DigestCredentialFactory
+
+__all__ = [
+ "HTTPAuthSessionWrapper",
+
+ "BasicCredentialFactory", "DigestCredentialFactory"]
diff --git a/vendor/Twisted-10.0.0/twisted/web/html.py b/vendor/Twisted-10.0.0/twisted/web/html.py
new file mode 100644
index 0000000000..fd3ff5fb1b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/html.py
@@ -0,0 +1,49 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""I hold HTML generation helpers.
+"""
+
+from twisted.python import log
+#t.w imports
+from twisted.web import resource
+
+import traceback, string
+
+from cStringIO import StringIO
+from microdom import escape
+
+def PRE(text):
+ "Wrap <pre> tags around some text and HTML-escape it."
+ return "<pre>"+escape(text)+"</pre>"
+
+def UL(lst):
+ io = StringIO()
+ io.write("<ul>\n")
+ for el in lst:
+ io.write("<li> %s</li>\n" % el)
+ io.write("</ul>")
+ return io.getvalue()
+
+def linkList(lst):
+ io = StringIO()
+ io.write("<ul>\n")
+ for hr, el in lst:
+ io.write('<li> <a href="%s">%s</a></li>\n' % (hr, el))
+ io.write("</ul>")
+ return io.getvalue()
+
+def output(func, *args, **kw):
+ """output(func, *args, **kw) -> html string
+ Either return the result of a function (which presumably returns an
+ HTML-legal string) or a sparse HTMLized error message and a message
+ in the server log.
+ """
+ try:
+ return func(*args, **kw)
+ except:
+ log.msg("Error calling %r:" % (func,))
+ log.err()
+ return PRE("An error occurred.")
diff --git a/vendor/Twisted-10.0.0/twisted/web/http.py b/vendor/Twisted-10.0.0/twisted/web/http.py
new file mode 100644
index 0000000000..cc372b6a1c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/http.py
@@ -0,0 +1,1797 @@
+# -*- test-case-name: twisted.web.test.test_http -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+HyperText Transfer Protocol implementation.
+
+This is used by twisted.web.
+
+Future Plans:
+ - HTTP client support will at some point be refactored to support HTTP/1.1.
+ - Accept chunked data from clients in server.
+ - Other missing HTTP features from the RFC.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+# system imports
+from cStringIO import StringIO
+import tempfile
+import base64, binascii
+import cgi
+import socket
+import math
+import time
+import calendar
+import warnings
+import os
+from urlparse import urlparse as _urlparse
+
+from zope.interface import implements
+
+# twisted imports
+from twisted.internet import interfaces, reactor, protocol, address
+from twisted.internet.defer import Deferred
+from twisted.protocols import policies, basic
+from twisted.python import log
+try: # try importing the fast, C version
+ from twisted.protocols._c_urlarg import unquote
+except ImportError:
+ from urllib import unquote
+
+from twisted.web.http_headers import _DictHeaders, Headers
+
+protocol_version = "HTTP/1.1"
+
+_CONTINUE = 100
+SWITCHING = 101
+
+OK = 200
+CREATED = 201
+ACCEPTED = 202
+NON_AUTHORITATIVE_INFORMATION = 203
+NO_CONTENT = 204
+RESET_CONTENT = 205
+PARTIAL_CONTENT = 206
+MULTI_STATUS = 207
+
+MULTIPLE_CHOICE = 300
+MOVED_PERMANENTLY = 301
+FOUND = 302
+SEE_OTHER = 303
+NOT_MODIFIED = 304
+USE_PROXY = 305
+TEMPORARY_REDIRECT = 307
+
+BAD_REQUEST = 400
+UNAUTHORIZED = 401
+PAYMENT_REQUIRED = 402
+FORBIDDEN = 403
+NOT_FOUND = 404
+NOT_ALLOWED = 405
+NOT_ACCEPTABLE = 406
+PROXY_AUTH_REQUIRED = 407
+REQUEST_TIMEOUT = 408
+CONFLICT = 409
+GONE = 410
+LENGTH_REQUIRED = 411
+PRECONDITION_FAILED = 412
+REQUEST_ENTITY_TOO_LARGE = 413
+REQUEST_URI_TOO_LONG = 414
+UNSUPPORTED_MEDIA_TYPE = 415
+REQUESTED_RANGE_NOT_SATISFIABLE = 416
+EXPECTATION_FAILED = 417
+
+INTERNAL_SERVER_ERROR = 500
+NOT_IMPLEMENTED = 501
+BAD_GATEWAY = 502
+SERVICE_UNAVAILABLE = 503
+GATEWAY_TIMEOUT = 504
+HTTP_VERSION_NOT_SUPPORTED = 505
+INSUFFICIENT_STORAGE_SPACE = 507
+NOT_EXTENDED = 510
+
+RESPONSES = {
+ # 100
+ _CONTINUE: "Continue",
+ SWITCHING: "Switching Protocols",
+
+ # 200
+ OK: "OK",
+ CREATED: "Created",
+ ACCEPTED: "Accepted",
+ NON_AUTHORITATIVE_INFORMATION: "Non-Authoritative Information",
+ NO_CONTENT: "No Content",
+ RESET_CONTENT: "Reset Content.",
+ PARTIAL_CONTENT: "Partial Content",
+ MULTI_STATUS: "Multi-Status",
+
+ # 300
+ MULTIPLE_CHOICE: "Multiple Choices",
+ MOVED_PERMANENTLY: "Moved Permanently",
+ FOUND: "Found",
+ SEE_OTHER: "See Other",
+ NOT_MODIFIED: "Not Modified",
+ USE_PROXY: "Use Proxy",
+ # 306 not defined??
+ TEMPORARY_REDIRECT: "Temporary Redirect",
+
+ # 400
+ BAD_REQUEST: "Bad Request",
+ UNAUTHORIZED: "Unauthorized",
+ PAYMENT_REQUIRED: "Payment Required",
+ FORBIDDEN: "Forbidden",
+ NOT_FOUND: "Not Found",
+ NOT_ALLOWED: "Method Not Allowed",
+ NOT_ACCEPTABLE: "Not Acceptable",
+ PROXY_AUTH_REQUIRED: "Proxy Authentication Required",
+ REQUEST_TIMEOUT: "Request Time-out",
+ CONFLICT: "Conflict",
+ GONE: "Gone",
+ LENGTH_REQUIRED: "Length Required",
+ PRECONDITION_FAILED: "Precondition Failed",
+ REQUEST_ENTITY_TOO_LARGE: "Request Entity Too Large",
+ REQUEST_URI_TOO_LONG: "Request-URI Too Long",
+ UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type",
+ REQUESTED_RANGE_NOT_SATISFIABLE: "Requested Range not satisfiable",
+ EXPECTATION_FAILED: "Expectation Failed",
+
+ # 500
+ INTERNAL_SERVER_ERROR: "Internal Server Error",
+ NOT_IMPLEMENTED: "Not Implemented",
+ BAD_GATEWAY: "Bad Gateway",
+ SERVICE_UNAVAILABLE: "Service Unavailable",
+ GATEWAY_TIMEOUT: "Gateway Time-out",
+ HTTP_VERSION_NOT_SUPPORTED: "HTTP Version not supported",
+ INSUFFICIENT_STORAGE_SPACE: "Insufficient Storage Space",
+ NOT_EXTENDED: "Not Extended"
+ }
+
+CACHED = """Magic constant returned by http.Request methods to set cache
+validation headers when the request is conditional and the value fails
+the condition."""
+
+# backwards compatability
+responses = RESPONSES
+
+
+# datetime parsing and formatting
+weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+monthname = [None,
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+weekdayname_lower = [name.lower() for name in weekdayname]
+monthname_lower = [name and name.lower() for name in monthname]
+
+def urlparse(url):
+ """
+ Parse an URL into six components.
+
+ This is similar to L{urlparse.urlparse}, but rejects C{unicode} input
+ and always produces C{str} output.
+
+ @type url: C{str}
+
+ @raise TypeError: The given url was a C{unicode} string instead of a
+ C{str}.
+
+ @rtype: six-tuple of str
+ @return: The scheme, net location, path, params, query string, and fragment
+ of the URL.
+ """
+ if isinstance(url, unicode):
+ raise TypeError("url must be str, not unicode")
+ scheme, netloc, path, params, query, fragment = _urlparse(url)
+ if isinstance(scheme, unicode):
+ scheme = scheme.encode('ascii')
+ netloc = netloc.encode('ascii')
+ path = path.encode('ascii')
+ query = query.encode('ascii')
+ fragment = fragment.encode('ascii')
+ return scheme, netloc, path, params, query, fragment
+
+
+def parse_qs(qs, keep_blank_values=0, strict_parsing=0, unquote=unquote):
+ """
+ like cgi.parse_qs, only with custom unquote function
+ """
+ d = {}
+ items = [s2 for s1 in qs.split("&") for s2 in s1.split(";")]
+ for item in items:
+ try:
+ k, v = item.split("=", 1)
+ except ValueError:
+ if strict_parsing:
+ raise
+ continue
+ if v or keep_blank_values:
+ k = unquote(k.replace("+", " "))
+ v = unquote(v.replace("+", " "))
+ if k in d:
+ d[k].append(v)
+ else:
+ d[k] = [v]
+ return d
+
+def datetimeToString(msSinceEpoch=None):
+ """
+ Convert seconds since epoch to HTTP datetime string.
+ """
+ if msSinceEpoch == None:
+ msSinceEpoch = time.time()
+ year, month, day, hh, mm, ss, wd, y, z = time.gmtime(msSinceEpoch)
+ s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
+ weekdayname[wd],
+ day, monthname[month], year,
+ hh, mm, ss)
+ return s
+
+def datetimeToLogString(msSinceEpoch=None):
+ """
+ Convert seconds since epoch to log datetime string.
+ """
+ if msSinceEpoch == None:
+ msSinceEpoch = time.time()
+ year, month, day, hh, mm, ss, wd, y, z = time.gmtime(msSinceEpoch)
+ s = "[%02d/%3s/%4d:%02d:%02d:%02d +0000]" % (
+ day, monthname[month], year,
+ hh, mm, ss)
+ return s
+
+
+# a hack so we don't need to recalculate log datetime every hit,
+# at the price of a small, unimportant, inaccuracy.
+_logDateTime = None
+_logDateTimeUsers = 0
+_resetLogDateTimeID = None
+
+def _resetLogDateTime():
+ global _logDateTime
+ global _resetLogDateTime
+ global _resetLogDateTimeID
+ _logDateTime = datetimeToLogString()
+ _resetLogDateTimeID = reactor.callLater(1, _resetLogDateTime)
+
+def _logDateTimeStart():
+ global _logDateTimeUsers
+ if not _logDateTimeUsers:
+ _resetLogDateTime()
+ _logDateTimeUsers += 1
+
+def _logDateTimeStop():
+ global _logDateTimeUsers
+ _logDateTimeUsers -= 1;
+ if (not _logDateTimeUsers and _resetLogDateTimeID
+ and _resetLogDateTimeID.active()):
+ _resetLogDateTimeID.cancel()
+
+def timegm(year, month, day, hour, minute, second):
+ """
+ Convert time tuple in GMT to seconds since epoch, GMT
+ """
+ EPOCH = 1970
+ if year < EPOCH:
+ raise ValueError("Years prior to %d not supported" % (EPOCH,))
+ assert 1 <= month <= 12
+ days = 365*(year-EPOCH) + calendar.leapdays(EPOCH, year)
+ for i in range(1, month):
+ days = days + calendar.mdays[i]
+ if month > 2 and calendar.isleap(year):
+ days = days + 1
+ days = days + day - 1
+ hours = days*24 + hour
+ minutes = hours*60 + minute
+ seconds = minutes*60 + second
+ return seconds
+
+def stringToDatetime(dateString):
+ """
+ Convert an HTTP date string (one of three formats) to seconds since epoch.
+ """
+ parts = dateString.split()
+
+ if not parts[0][0:3].lower() in weekdayname_lower:
+ # Weekday is stupid. Might have been omitted.
+ try:
+ return stringToDatetime("Sun, "+dateString)
+ except ValueError:
+ # Guess not.
+ pass
+
+ partlen = len(parts)
+ if (partlen == 5 or partlen == 6) and parts[1].isdigit():
+ # 1st date format: Sun, 06 Nov 1994 08:49:37 GMT
+ # (Note: "GMT" is literal, not a variable timezone)
+ # (also handles without "GMT")
+ # This is the normal format
+ day = parts[1]
+ month = parts[2]
+ year = parts[3]
+ time = parts[4]
+ elif (partlen == 3 or partlen == 4) and parts[1].find('-') != -1:
+ # 2nd date format: Sunday, 06-Nov-94 08:49:37 GMT
+ # (Note: "GMT" is literal, not a variable timezone)
+ # (also handles without without "GMT")
+ # Two digit year, yucko.
+ day, month, year = parts[1].split('-')
+ time = parts[2]
+ year=int(year)
+ if year < 69:
+ year = year + 2000
+ elif year < 100:
+ year = year + 1900
+ elif len(parts) == 5:
+ # 3rd date format: Sun Nov 6 08:49:37 1994
+ # ANSI C asctime() format.
+ day = parts[2]
+ month = parts[1]
+ year = parts[4]
+ time = parts[3]
+ else:
+ raise ValueError("Unknown datetime format %r" % dateString)
+
+ day = int(day)
+ month = int(monthname_lower.index(month.lower()))
+ year = int(year)
+ hour, min, sec = map(int, time.split(':'))
+ return int(timegm(year, month, day, hour, min, sec))
+
+def toChunk(data):
+ """
+ Convert string to a chunk.
+
+ @returns: a tuple of strings representing the chunked encoding of data
+ """
+ return ("%x\r\n" % len(data), data, "\r\n")
+
+def fromChunk(data):
+ """
+ Convert chunk to string.
+
+ @returns: tuple (result, remaining), may raise ValueError.
+ """
+ prefix, rest = data.split('\r\n', 1)
+ length = int(prefix, 16)
+ if length < 0:
+ raise ValueError("Chunk length must be >= 0, not %d" % (length,))
+ if not rest[length:length + 2] == '\r\n':
+ raise ValueError, "chunk must end with CRLF"
+ return rest[:length], rest[length + 2:]
+
+
+def parseContentRange(header):
+ """
+ Parse a content-range header into (start, end, realLength).
+
+ realLength might be None if real length is not known ('*').
+ """
+ kind, other = header.strip().split()
+ if kind.lower() != "bytes":
+ raise ValueError, "a range of type %r is not supported"
+ startend, realLength = other.split("/")
+ start, end = map(int, startend.split("-"))
+ if realLength == "*":
+ realLength = None
+ else:
+ realLength = int(realLength)
+ return (start, end, realLength)
+
+
+
+class StringTransport:
+ """
+ I am a StringIO wrapper that conforms for the transport API. I support
+ the `writeSequence' method.
+ """
+ def __init__(self):
+ self.s = StringIO()
+ def writeSequence(self, seq):
+ self.s.write(''.join(seq))
+ def __getattr__(self, attr):
+ return getattr(self.__dict__['s'], attr)
+
+
+class HTTPClient(basic.LineReceiver):
+ """
+ A client for HTTP 1.0.
+
+ Notes:
+ You probably want to send a 'Host' header with the name of the site you're
+ connecting to, in order to not break name based virtual hosting.
+
+ @ivar length: The length of the request body in bytes.
+ @type length: C{int}
+
+ @ivar firstLine: Are we waiting for the first header line?
+ @type firstLine: C{bool}
+
+ @ivar __buffer: The buffer that stores the response to the HTTP request.
+ @type __buffer: A C{StringIO} object.
+
+ @ivar _header: Part or all of an HTTP request header.
+ @type _header: C{str}
+ """
+ length = None
+ firstLine = True
+ __buffer = None
+ _header = ""
+
+ def sendCommand(self, command, path):
+ self.transport.write('%s %s HTTP/1.0\r\n' % (command, path))
+
+ def sendHeader(self, name, value):
+ self.transport.write('%s: %s\r\n' % (name, value))
+
+ def endHeaders(self):
+ self.transport.write('\r\n')
+
+
+ def extractHeader(self, header):
+ """
+ Given a complete HTTP header, extract the field name and value and
+ process the header.
+
+ @param header: a complete HTTP request header of the form
+ 'field-name: value'.
+ @type header: C{str}
+ """
+ key, val = header.split(':', 1)
+ val = val.lstrip()
+ self.handleHeader(key, val)
+ if key.lower() == 'content-length':
+ self.length = int(val)
+
+
+ def lineReceived(self, line):
+ """
+ Parse the status line and headers for an HTTP request.
+
+ @param line: Part of an HTTP request header. Request bodies are parsed
+ in L{rawDataReceived}.
+ @type line: C{str}
+ """
+ if self.firstLine:
+ self.firstLine = False
+ l = line.split(None, 2)
+ version = l[0]
+ status = l[1]
+ try:
+ message = l[2]
+ except IndexError:
+ # sometimes there is no message
+ message = ""
+ self.handleStatus(version, status, message)
+ return
+ if not line:
+ if self._header != "":
+ # Only extract headers if there are any
+ self.extractHeader(self._header)
+ self.__buffer = StringIO()
+ self.handleEndHeaders()
+ self.setRawMode()
+ return
+
+ if line.startswith('\t') or line.startswith(' '):
+ # This line is part of a multiline header. According to RFC 822, in
+ # "unfolding" multiline headers you do not strip the leading
+ # whitespace on the continuing line.
+ self._header = self._header + line
+ elif self._header:
+ # This line starts a new header, so process the previous one.
+ self.extractHeader(self._header)
+ self._header = line
+ else: # First header
+ self._header = line
+
+
+ def connectionLost(self, reason):
+ self.handleResponseEnd()
+
+ def handleResponseEnd(self):
+ """
+ The response has been completely received.
+
+ This callback may be invoked more than once per request.
+ """
+ if self.__buffer is not None:
+ b = self.__buffer.getvalue()
+ self.__buffer = None
+ self.handleResponse(b)
+
+ def handleResponsePart(self, data):
+ self.__buffer.write(data)
+
+ def connectionMade(self):
+ pass
+
+ def handleStatus(self, version, status, message):
+ """
+ Called when the status-line is received.
+
+ @param version: e.g. 'HTTP/1.0'
+ @param status: e.g. '200'
+ @type status: C{str}
+ @param message: e.g. 'OK'
+ """
+
+ def handleHeader(self, key, val):
+ """
+ Called every time a header is received.
+ """
+
+ def handleEndHeaders(self):
+ """
+ Called when all headers have been received.
+ """
+
+
+ def rawDataReceived(self, data):
+ if self.length is not None:
+ data, rest = data[:self.length], data[self.length:]
+ self.length -= len(data)
+ else:
+ rest = ''
+ self.handleResponsePart(data)
+ if self.length == 0:
+ self.handleResponseEnd()
+ self.setLineMode(rest)
+
+
+
+# response codes that must have empty bodies
+NO_BODY_CODES = (204, 304)
+
+class Request:
+ """
+ A HTTP request.
+
+ Subclasses should override the process() method to determine how
+ the request will be processed.
+
+ @ivar method: The HTTP method that was used.
+ @ivar uri: The full URI that was requested (includes arguments).
+ @ivar path: The path only (arguments not included).
+ @ivar args: All of the arguments, including URL and POST arguments.
+ @type args: A mapping of strings (the argument names) to lists of values.
+ i.e., ?foo=bar&foo=baz&quux=spam results in
+ {'foo': ['bar', 'baz'], 'quux': ['spam']}.
+
+ @type requestHeaders: L{http_headers.Headers}
+ @ivar requestHeaders: All received HTTP request headers.
+
+ @ivar received_headers: Backwards-compatibility access to
+ C{requestHeaders}. Use C{requestHeaders} instead. C{received_headers}
+ behaves mostly like a C{dict} and does not provide access to all header
+ values.
+
+ @type responseHeaders: L{http_headers.Headers}
+ @ivar responseHeaders: All HTTP response headers to be sent.
+
+ @ivar headers: Backwards-compatibility access to C{responseHeaders}. Use
+ C{responseHeaders} instead. C{headers} behaves mostly like a C{dict}
+ and does not provide access to all header values nor does it allow
+ multiple values for one header to be set.
+
+ @ivar notifications: A C{list} of L{Deferred}s which are waiting for
+ notification that the response to this request has been finished
+ (successfully or with an error). Don't use this attribute directly,
+ instead use the L{Request.notifyFinish} method.
+
+ @ivar _disconnected: A flag which is C{False} until the connection over
+ which this request was received is closed and which is C{True} after
+ that.
+ @type _disconnected: C{bool}
+ """
+ implements(interfaces.IConsumer)
+
+ producer = None
+ finished = 0
+ code = OK
+ code_message = RESPONSES[OK]
+ method = "(no method yet)"
+ clientproto = "(no clientproto yet)"
+ uri = "(no uri yet)"
+ startedWriting = 0
+ chunked = 0
+ sentLength = 0 # content-length of response, or total bytes sent via chunking
+ etag = None
+ lastModified = None
+ args = None
+ path = None
+ content = None
+ _forceSSL = 0
+ _disconnected = False
+
+ def __init__(self, channel, queued):
+ """
+ @param channel: the channel we're connected to.
+ @param queued: are we in the request queue, or can we start writing to
+ the transport?
+ """
+ self.notifications = []
+ self.channel = channel
+ self.queued = queued
+ self.requestHeaders = Headers()
+ self.received_cookies = {}
+ self.responseHeaders = Headers()
+ self.cookies = [] # outgoing cookies
+
+ if queued:
+ self.transport = StringTransport()
+ else:
+ self.transport = self.channel.transport
+
+
+ def __setattr__(self, name, value):
+ """
+ Support assignment of C{dict} instances to C{received_headers} for
+ backwards-compatibility.
+ """
+ if name == 'received_headers':
+ # A property would be nice, but Request is classic.
+ self.requestHeaders = headers = Headers()
+ for k, v in value.iteritems():
+ headers.setRawHeaders(k, [v])
+ elif name == 'requestHeaders':
+ self.__dict__[name] = value
+ self.__dict__['received_headers'] = _DictHeaders(value)
+ elif name == 'headers':
+ self.responseHeaders = headers = Headers()
+ for k, v in value.iteritems():
+ headers.setRawHeaders(k, [v])
+ elif name == 'responseHeaders':
+ self.__dict__[name] = value
+ self.__dict__['headers'] = _DictHeaders(value)
+ else:
+ self.__dict__[name] = value
+
+
+ def _cleanup(self):
+ """
+ Called when have finished responding and are no longer queued.
+ """
+ if self.producer:
+ log.err(RuntimeError("Producer was not unregistered for %s" % self.uri))
+ self.unregisterProducer()
+ self.channel.requestDone(self)
+ del self.channel
+ try:
+ self.content.close()
+ except OSError:
+ # win32 suckiness, no idea why it does this
+ pass
+ del self.content
+ for d in self.notifications:
+ d.callback(None)
+ self.notifications = []
+
+ # methods for channel - end users should not use these
+
+ def noLongerQueued(self):
+ """
+ Notify the object that it is no longer queued.
+
+ We start writing whatever data we have to the transport, etc.
+
+ This method is not intended for users.
+ """
+ if not self.queued:
+ raise RuntimeError, "noLongerQueued() got called unnecessarily."
+
+ self.queued = 0
+
+ # set transport to real one and send any buffer data
+ data = self.transport.getvalue()
+ self.transport = self.channel.transport
+ if data:
+ self.transport.write(data)
+
+ # if we have producer, register it with transport
+ if (self.producer is not None) and not self.finished:
+ self.transport.registerProducer(self.producer, self.streamingProducer)
+
+ # if we're finished, clean up
+ if self.finished:
+ self._cleanup()
+
+ def gotLength(self, length):
+ """
+ Called when HTTP channel got length of content in this request.
+
+ This method is not intended for users.
+
+ @param length: The length of the request body, as indicated by the
+ request headers. C{None} if the request headers do not indicate a
+ length.
+ """
+ if length is not None and length < 100000:
+ self.content = StringIO()
+ else:
+ self.content = tempfile.TemporaryFile()
+
+
+ def parseCookies(self):
+ """
+ Parse cookie headers.
+
+ This method is not intended for users.
+ """
+ cookieheaders = self.requestHeaders.getRawHeaders("cookie")
+
+ if cookieheaders is None:
+ return
+
+ for cookietxt in cookieheaders:
+ if cookietxt:
+ for cook in cookietxt.split(';'):
+ cook = cook.lstrip()
+ try:
+ k, v = cook.split('=', 1)
+ self.received_cookies[k] = v
+ except ValueError:
+ pass
+
+
+ def handleContentChunk(self, data):
+ """
+ Write a chunk of data.
+
+ This method is not intended for users.
+ """
+ self.content.write(data)
+
+
+ def requestReceived(self, command, path, version):
+ """
+ Called by channel when all data has been received.
+
+ This method is not intended for users.
+
+ @type command: C{str}
+ @param command: The HTTP verb of this request. This has the case
+ supplied by the client (eg, it maybe "get" rather than "GET").
+
+ @type path: C{str}
+ @param path: The URI of this request.
+
+ @type version: C{str}
+ @param version: The HTTP version of this request.
+ """
+ self.content.seek(0,0)
+ self.args = {}
+ self.stack = []
+
+ self.method, self.uri = command, path
+ self.clientproto = version
+ x = self.uri.split('?', 1)
+
+ if len(x) == 1:
+ self.path = self.uri
+ else:
+ self.path, argstring = x
+ self.args = parse_qs(argstring, 1)
+
+ # cache the client and server information, we'll need this later to be
+ # serialized and sent with the request so CGIs will work remotely
+ self.client = self.channel.transport.getPeer()
+ self.host = self.channel.transport.getHost()
+
+ # Argument processing
+ args = self.args
+ ctype = self.requestHeaders.getRawHeaders('content-type')
+ if ctype is not None:
+ ctype = ctype[0]
+
+ if self.method == "POST" and ctype:
+ mfd = 'multipart/form-data'
+ key, pdict = cgi.parse_header(ctype)
+ if key == 'application/x-www-form-urlencoded':
+ args.update(parse_qs(self.content.read(), 1))
+ elif key == mfd:
+ try:
+ args.update(cgi.parse_multipart(self.content, pdict))
+ except KeyError, e:
+ if e.args[0] == 'content-disposition':
+ # Parse_multipart can't cope with missing
+ # content-dispostion headers in multipart/form-data
+ # parts, so we catch the exception and tell the client
+ # it was a bad request.
+ self.channel.transport.write(
+ "HTTP/1.1 400 Bad Request\r\n\r\n")
+ self.channel.transport.loseConnection()
+ return
+ raise
+ self.content.seek(0, 0)
+
+ self.process()
+
+
+ def __repr__(self):
+ return '<%s %s %s>'% (self.method, self.uri, self.clientproto)
+
+ def process(self):
+ """
+ Override in subclasses.
+
+ This method is not intended for users.
+ """
+ pass
+
+
+ # consumer interface
+
+ def registerProducer(self, producer, streaming):
+ """
+ Register a producer.
+ """
+ if self.producer:
+ raise ValueError, "registering producer %s before previous one (%s) was unregistered" % (producer, self.producer)
+
+ self.streamingProducer = streaming
+ self.producer = producer
+
+ if self.queued:
+ if streaming:
+ producer.pauseProducing()
+ else:
+ self.transport.registerProducer(producer, streaming)
+
+ def unregisterProducer(self):
+ """
+ Unregister the producer.
+ """
+ if not self.queued:
+ self.transport.unregisterProducer()
+ self.producer = None
+
+ # private http response methods
+
+ def _sendError(self, code, resp=''):
+ self.transport.write('%s %s %s\r\n\r\n' % (self.clientproto, code, resp))
+
+
+ # The following is the public interface that people should be
+ # writing to.
+ def getHeader(self, key):
+ """
+ Get an HTTP request header.
+
+ @type key: C{str}
+ @param key: The name of the header to get the value of.
+
+ @rtype: C{str} or C{NoneType}
+ @return: The value of the specified header, or C{None} if that header
+ was not present in the request.
+ """
+ value = self.requestHeaders.getRawHeaders(key)
+ if value is not None:
+ return value[-1]
+
+
+ def getCookie(self, key):
+ """
+ Get a cookie that was sent from the network.
+ """
+ return self.received_cookies.get(key)
+
+
+ def notifyFinish(self):
+ """
+ Notify when the response to this request has finished.
+
+ @rtype: L{Deferred}
+
+ @return: A L{Deferred} which will be triggered when the request is
+ finished -- with a C{None} value if the request finishes
+ successfully or with an error if the request is interrupted by an
+ error (for example, the client closing the connection prematurely).
+ """
+ self.notifications.append(Deferred())
+ return self.notifications[-1]
+
+
+ def finish(self):
+ """
+ Indicate that all response data has been written to this L{Request}.
+ """
+ if self._disconnected:
+ raise RuntimeError(
+ "Request.finish called on a request after its connection was lost; "
+ "use Request.notifyFinish to keep track of this.")
+ if self.finished:
+ warnings.warn("Warning! request.finish called twice.", stacklevel=2)
+ return
+
+ if not self.startedWriting:
+ # write headers
+ self.write('')
+
+ if self.chunked:
+ # write last chunk and closing CRLF
+ self.transport.write("0\r\n\r\n")
+
+ # log request
+ if hasattr(self.channel, "factory"):
+ self.channel.factory.log(self)
+
+ self.finished = 1
+ if not self.queued:
+ self._cleanup()
+
+ def write(self, data):
+ """
+ Write some data as a result of an HTTP request. The first
+ time this is called, it writes out response data.
+
+ @type data: C{str}
+ @param data: Some bytes to be sent as part of the response body.
+ """
+ if not self.startedWriting:
+ self.startedWriting = 1
+ version = self.clientproto
+ l = []
+ l.append('%s %s %s\r\n' % (version, self.code,
+ self.code_message))
+ # if we don't have a content length, we send data in
+ # chunked mode, so that we can support pipelining in
+ # persistent connections.
+ if ((version == "HTTP/1.1") and
+ (self.responseHeaders.getRawHeaders('content-length') is None) and
+ self.method != "HEAD" and self.code not in NO_BODY_CODES):
+ l.append("%s: %s\r\n" % ('Transfer-Encoding', 'chunked'))
+ self.chunked = 1
+
+ if self.lastModified is not None:
+ if self.responseHeaders.hasHeader('last-modified'):
+ log.msg("Warning: last-modified specified both in"
+ " header list and lastModified attribute.")
+ else:
+ self.responseHeaders.setRawHeaders(
+ 'last-modified',
+ [datetimeToString(self.lastModified)])
+
+ if self.etag is not None:
+ self.responseHeaders.setRawHeaders('ETag', [self.etag])
+
+ for name, values in self.responseHeaders.getAllRawHeaders():
+ for value in values:
+ l.append("%s: %s\r\n" % (name, value))
+
+ for cookie in self.cookies:
+ l.append('%s: %s\r\n' % ("Set-Cookie", cookie))
+
+ l.append("\r\n")
+
+ self.transport.writeSequence(l)
+
+ # if this is a "HEAD" request, we shouldn't return any data
+ if self.method == "HEAD":
+ self.write = lambda data: None
+ return
+
+ # for certain result codes, we should never return any data
+ if self.code in NO_BODY_CODES:
+ self.write = lambda data: None
+ return
+
+ self.sentLength = self.sentLength + len(data)
+ if data:
+ if self.chunked:
+ self.transport.writeSequence(toChunk(data))
+ else:
+ self.transport.write(data)
+
+ def addCookie(self, k, v, expires=None, domain=None, path=None, max_age=None, comment=None, secure=None):
+ """
+ Set an outgoing HTTP cookie.
+
+ In general, you should consider using sessions instead of cookies, see
+ L{twisted.web.server.Request.getSession} and the
+ L{twisted.web.server.Session} class for details.
+ """
+ cookie = '%s=%s' % (k, v)
+ if expires is not None:
+ cookie = cookie +"; Expires=%s" % expires
+ if domain is not None:
+ cookie = cookie +"; Domain=%s" % domain
+ if path is not None:
+ cookie = cookie +"; Path=%s" % path
+ if max_age is not None:
+ cookie = cookie +"; Max-Age=%s" % max_age
+ if comment is not None:
+ cookie = cookie +"; Comment=%s" % comment
+ if secure:
+ cookie = cookie +"; Secure"
+ self.cookies.append(cookie)
+
+ def setResponseCode(self, code, message=None):
+ """
+ Set the HTTP response code.
+ """
+ if not isinstance(code, (int, long)):
+ raise TypeError("HTTP response code must be int or long")
+ self.code = code
+ if message:
+ self.code_message = message
+ else:
+ self.code_message = RESPONSES.get(code, "Unknown Status")
+
+
+ def setHeader(self, name, value):
+ """
+ Set an HTTP response header. Overrides any previously set values for
+ this header.
+
+ @type name: C{str}
+ @param name: The name of the header for which to set the value.
+
+ @type value: C{str}
+ @param value: The value to set for the named header.
+ """
+ self.responseHeaders.setRawHeaders(name, [value])
+
+
+ def redirect(self, url):
+ """
+ Utility function that does a redirect.
+
+ The request should have finish() called after this.
+ """
+ self.setResponseCode(FOUND)
+ self.setHeader("location", url)
+
+
+ def setLastModified(self, when):
+ """
+ Set the C{Last-Modified} time for the response to this request.
+
+ If I am called more than once, I ignore attempts to set
+ Last-Modified earlier, only replacing the Last-Modified time
+ if it is to a later value.
+
+ If I am a conditional request, I may modify my response code
+ to L{NOT_MODIFIED} if appropriate for the time given.
+
+ @param when: The last time the resource being returned was
+ modified, in seconds since the epoch.
+ @type when: number
+ @return: If I am a C{If-Modified-Since} conditional request and
+ the time given is not newer than the condition, I return
+ L{http.CACHED<CACHED>} to indicate that you should write no
+ body. Otherwise, I return a false value.
+ """
+ # time.time() may be a float, but the HTTP-date strings are
+ # only good for whole seconds.
+ when = long(math.ceil(when))
+ if (not self.lastModified) or (self.lastModified < when):
+ self.lastModified = when
+
+ modifiedSince = self.getHeader('if-modified-since')
+ if modifiedSince:
+ firstPart = modifiedSince.split(';', 1)[0]
+ try:
+ modifiedSince = stringToDatetime(firstPart)
+ except ValueError:
+ return None
+ if modifiedSince >= when:
+ self.setResponseCode(NOT_MODIFIED)
+ return CACHED
+ return None
+
+ def setETag(self, etag):
+ """
+ Set an C{entity tag} for the outgoing response.
+
+ That's \"entity tag\" as in the HTTP/1.1 C{ETag} header, \"used
+ for comparing two or more entities from the same requested
+ resource.\"
+
+ If I am a conditional request, I may modify my response code
+ to L{NOT_MODIFIED} or L{PRECONDITION_FAILED}, if appropriate
+ for the tag given.
+
+ @param etag: The entity tag for the resource being returned.
+ @type etag: string
+ @return: If I am a C{If-None-Match} conditional request and
+ the tag matches one in the request, I return
+ L{http.CACHED<CACHED>} to indicate that you should write
+ no body. Otherwise, I return a false value.
+ """
+ if etag:
+ self.etag = etag
+
+ tags = self.getHeader("if-none-match")
+ if tags:
+ tags = tags.split()
+ if (etag in tags) or ('*' in tags):
+ self.setResponseCode(((self.method in ("HEAD", "GET"))
+ and NOT_MODIFIED)
+ or PRECONDITION_FAILED)
+ return CACHED
+ return None
+
+
+ def getAllHeaders(self):
+ """
+ Return dictionary mapping the names of all received headers to the last
+ value received for each.
+
+ Since this method does not return all header information,
+ C{self.requestHeaders.getAllRawHeaders()} may be preferred.
+ """
+ headers = {}
+ for k, v in self.requestHeaders.getAllRawHeaders():
+ headers[k.lower()] = v[-1]
+ return headers
+
+
+ def getRequestHostname(self):
+ """
+ Get the hostname that the user passed in to the request.
+
+ This will either use the Host: header (if it is available) or the
+ host we are listening on if the header is unavailable.
+
+ @returns: the requested hostname
+ @rtype: C{str}
+ """
+ # XXX This method probably has no unit tests. I changed it a ton and
+ # nothing failed.
+ host = self.getHeader('host')
+ if host:
+ return host.split(':', 1)[0]
+ return self.getHost().host
+
+
+ def getHost(self):
+ """
+ Get my originally requesting transport's host.
+
+ Don't rely on the 'transport' attribute, since Request objects may be
+ copied remotely. For information on this method's return value, see
+ twisted.internet.tcp.Port.
+ """
+ return self.host
+
+ def setHost(self, host, port, ssl=0):
+ """
+ Change the host and port the request thinks it's using.
+
+ This method is useful for working with reverse HTTP proxies (e.g.
+ both Squid and Apache's mod_proxy can do this), when the address
+ the HTTP client is using is different than the one we're listening on.
+
+ For example, Apache may be listening on https://www.example.com, and then
+ forwarding requests to http://localhost:8080, but we don't want HTML produced
+ by Twisted to say 'http://localhost:8080', they should say 'https://www.example.com',
+ so we do::
+
+ request.setHost('www.example.com', 443, ssl=1)
+
+ @type host: C{str}
+ @param host: The value to which to change the host header.
+
+ @type ssl: C{bool}
+ @param ssl: A flag which, if C{True}, indicates that the request is
+ considered secure (if C{True}, L{isSecure} will return C{True}).
+ """
+ self._forceSSL = ssl
+ self.requestHeaders.setRawHeaders("host", [host])
+ self.host = address.IPv4Address("TCP", host, port)
+
+
+ def getClientIP(self):
+ """
+ Return the IP address of the client who submitted this request.
+
+ @returns: the client IP address
+ @rtype: C{str}
+ """
+ if isinstance(self.client, address.IPv4Address):
+ return self.client.host
+ else:
+ return None
+
+ def isSecure(self):
+ """
+ Return True if this request is using a secure transport.
+
+ Normally this method returns True if this request's HTTPChannel
+ instance is using a transport that implements ISSLTransport.
+
+ This will also return True if setHost() has been called
+ with ssl=True.
+
+ @returns: True if this request is secure
+ @rtype: C{bool}
+ """
+ if self._forceSSL:
+ return True
+ transport = getattr(getattr(self, 'channel', None), 'transport', None)
+ if interfaces.ISSLTransport(transport, None) is not None:
+ return True
+ return False
+
+ def _authorize(self):
+ # Authorization, (mostly) per the RFC
+ try:
+ authh = self.getHeader("Authorization")
+ if not authh:
+ self.user = self.password = ''
+ return
+ bas, upw = authh.split()
+ if bas.lower() != "basic":
+ raise ValueError
+ upw = base64.decodestring(upw)
+ self.user, self.password = upw.split(':', 1)
+ except (binascii.Error, ValueError):
+ self.user = self.password = ""
+ except:
+ log.err()
+ self.user = self.password = ""
+
+ def getUser(self):
+ """
+ Return the HTTP user sent with this request, if any.
+
+ If no user was supplied, return the empty string.
+
+ @returns: the HTTP user, if any
+ @rtype: C{str}
+ """
+ try:
+ return self.user
+ except:
+ pass
+ self._authorize()
+ return self.user
+
+ def getPassword(self):
+ """
+ Return the HTTP password sent with this request, if any.
+
+ If no password was supplied, return the empty string.
+
+ @returns: the HTTP password, if any
+ @rtype: C{str}
+ """
+ try:
+ return self.password
+ except:
+ pass
+ self._authorize()
+ return self.password
+
+ def getClient(self):
+ if self.client.type != 'TCP':
+ return None
+ host = self.client.host
+ try:
+ name, names, addresses = socket.gethostbyaddr(host)
+ except socket.error:
+ return host
+ names.insert(0, name)
+ for name in names:
+ if '.' in name:
+ return name
+ return names[0]
+
+
+ def connectionLost(self, reason):
+ """
+ There is no longer a connection for this request to respond over.
+ Clean up anything which can't be useful anymore.
+ """
+ self._disconnected = True
+ self.channel = None
+ if self.content is not None:
+ self.content.close()
+ for d in self.notifications:
+ d.errback(reason)
+ self.notifications = []
+
+
+
+class _DataLoss(Exception):
+ """
+ L{_DataLoss} indicates that not all of a message body was received. This
+ is only one of several possible exceptions which may indicate that data
+ was lost. Because of this, it should not be checked for by
+ specifically; any unexpected exception should be treated as having
+ caused data loss.
+ """
+
+
+
+class PotentialDataLoss(Exception):
+ """
+ L{PotentialDataLoss} may be raised by a transfer encoding decoder's
+ C{noMoreData} method to indicate that it cannot be determined if the
+ entire response body has been delivered. This only occurs when making
+ requests to HTTP servers which do not set I{Content-Length} or a
+ I{Transfer-Encoding} in the response because in this case the end of the
+ response is indicated by the connection being closed, an event which may
+ also be due to a transient network problem or other error.
+ """
+
+
+
+class _IdentityTransferDecoder(object):
+ """
+ Protocol for accumulating bytes up to a specified length. This handles the
+ case where no I{Transfer-Encoding} is specified.
+
+ @ivar contentLength: Counter keeping track of how many more bytes there are
+ to receive.
+
+ @ivar dataCallback: A one-argument callable which will be invoked each
+ time application data is received.
+
+ @ivar finishCallback: A one-argument callable which will be invoked when
+ the terminal chunk is received. It will be invoked with all bytes
+ which were delivered to this protocol which came after the terminal
+ chunk.
+ """
+ def __init__(self, contentLength, dataCallback, finishCallback):
+ self.contentLength = contentLength
+ self.dataCallback = dataCallback
+ self.finishCallback = finishCallback
+
+
+ def dataReceived(self, data):
+ """
+ Interpret the next chunk of bytes received. Either deliver them to the
+ data callback or invoke the finish callback if enough bytes have been
+ received.
+
+ @raise RuntimeError: If the finish callback has already been invoked
+ during a previous call to this methood.
+ """
+ if self.dataCallback is None:
+ raise RuntimeError(
+ "_IdentityTransferDecoder cannot decode data after finishing")
+
+ if self.contentLength is None:
+ self.dataCallback(data)
+ elif len(data) < self.contentLength:
+ self.contentLength -= len(data)
+ self.dataCallback(data)
+ else:
+ # Make the state consistent before invoking any code belonging to
+ # anyone else in case noMoreData ends up being called beneath this
+ # stack frame.
+ contentLength = self.contentLength
+ dataCallback = self.dataCallback
+ finishCallback = self.finishCallback
+ self.dataCallback = self.finishCallback = None
+ self.contentLength = 0
+
+ dataCallback(data[:contentLength])
+ finishCallback(data[contentLength:])
+
+
+ def noMoreData(self):
+ """
+ All data which will be delivered to this decoder has been. Check to
+ make sure as much data as was expected has been received.
+
+ @raise PotentialDataLoss: If the content length is unknown.
+ @raise _DataLoss: If the content length is known and fewer than that
+ many bytes have been delivered.
+
+ @return: C{None}
+ """
+ finishCallback = self.finishCallback
+ self.dataCallback = self.finishCallback = None
+ if self.contentLength is None:
+ finishCallback('')
+ raise PotentialDataLoss()
+ elif self.contentLength != 0:
+ raise _DataLoss()
+
+
+
+class _ChunkedTransferDecoder(object):
+ """
+ Protocol for decoding I{chunked} Transfer-Encoding, as defined by RFC 2616,
+ section 3.6.1. This protocol can interpret the contents of a request or
+ response body which uses the I{chunked} Transfer-Encoding. It cannot
+ interpret any of the rest of the HTTP protocol.
+
+ It may make sense for _ChunkedTransferDecoder to be an actual IProtocol
+ implementation. Currently, the only user of this class will only ever
+ call dataReceived on it. However, it might be an improvement if the
+ user could connect this to a transport and deliver connection lost
+ notification. This way, `dataCallback` becomes `self.transport.write`
+ and perhaps `finishCallback` becomes `self.transport.loseConnection()`
+ (although I'm not sure where the extra data goes in that case). This
+ could also allow this object to indicate to the receiver of data that
+ the stream was not completely received, an error case which should be
+ noticed. -exarkun
+
+ @ivar dataCallback: A one-argument callable which will be invoked each
+ time application data is received.
+
+ @ivar finishCallback: A one-argument callable which will be invoked when
+ the terminal chunk is received. It will be invoked with all bytes
+ which were delivered to this protocol which came after the terminal
+ chunk.
+
+ @ivar length: Counter keeping track of how many more bytes in a chunk there
+ are to receive.
+
+ @ivar state: One of C{'chunk-length'}, C{'trailer'}, C{'body'}, or
+ C{'finished'}. For C{'chunk-length'}, data for the chunk length line
+ is currently being read. For C{'trailer'}, the CR LF pair which
+ follows each chunk is being read. For C{'body'}, the contents of a
+ chunk are being read. For C{'finished'}, the last chunk has been
+ completely read and no more input is valid.
+
+ @ivar finish: A flag indicating that the last chunk has been started. When
+ it finishes, the state will change to C{'finished'} and no more data
+ will be accepted.
+ """
+ state = 'chunk-length'
+ finish = False
+
+ def __init__(self, dataCallback, finishCallback):
+ self.dataCallback = dataCallback
+ self.finishCallback = finishCallback
+ self._buffer = ''
+
+
+ def dataReceived(self, data):
+ """
+ Interpret data from a request or response body which uses the
+ I{chunked} Transfer-Encoding.
+ """
+ data = self._buffer + data
+ self._buffer = ''
+ while data:
+ if self.state == 'chunk-length':
+ if '\r\n' in data:
+ line, rest = data.split('\r\n', 1)
+ parts = line.split(';')
+ self.length = int(parts[0], 16)
+ if self.length == 0:
+ self.state = 'trailer'
+ self.finish = True
+ else:
+ self.state = 'body'
+ data = rest
+ else:
+ self._buffer = data
+ data = ''
+ elif self.state == 'trailer':
+ if data.startswith('\r\n'):
+ data = data[2:]
+ if self.finish:
+ self.state = 'finished'
+ self.finishCallback(data)
+ data = ''
+ else:
+ self.state = 'chunk-length'
+ else:
+ self._buffer = data
+ data = ''
+ elif self.state == 'body':
+ if len(data) >= self.length:
+ chunk, data = data[:self.length], data[self.length:]
+ self.dataCallback(chunk)
+ self.state = 'trailer'
+ elif len(data) < self.length:
+ self.length -= len(data)
+ self.dataCallback(data)
+ data = ''
+ elif self.state == 'finished':
+ raise RuntimeError(
+ "_ChunkedTransferDecoder.dataReceived called after last "
+ "chunk was processed")
+
+
+ def noMoreData(self):
+ """
+ Verify that all data has been received. If it has not been, raise
+ L{_DataLoss}.
+ """
+ if self.state != 'finished':
+ raise _DataLoss(
+ "Chunked decoder in %r state, still expecting more data to "
+ "get to finished state." % (self.state,))
+
+
+
+class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
+ """
+ A receiver for HTTP requests.
+
+ @ivar _transferDecoder: C{None} or an instance of
+ L{_ChunkedTransferDecoder} if the request body uses the I{chunked}
+ Transfer-Encoding.
+ """
+
+ maxHeaders = 500 # max number of headers allowed per request
+
+ length = 0
+ persistent = 1
+ __header = ''
+ __first_line = 1
+ __content = None
+
+ # set in instances or subclasses
+ requestFactory = Request
+
+ _savedTimeOut = None
+ _receivedHeaderCount = 0
+
+ def __init__(self):
+ # the request queue
+ self.requests = []
+ self._transferDecoder = None
+
+
+ def connectionMade(self):
+ self.setTimeout(self.timeOut)
+
+ def lineReceived(self, line):
+ self.resetTimeout()
+
+ if self.__first_line:
+ # if this connection is not persistent, drop any data which
+ # the client (illegally) sent after the last request.
+ if not self.persistent:
+ self.dataReceived = self.lineReceived = lambda *args: None
+ return
+
+ # IE sends an extraneous empty line (\r\n) after a POST request;
+ # eat up such a line, but only ONCE
+ if not line and self.__first_line == 1:
+ self.__first_line = 2
+ return
+
+ # create a new Request object
+ request = self.requestFactory(self, len(self.requests))
+ self.requests.append(request)
+
+ self.__first_line = 0
+ parts = line.split()
+ if len(parts) != 3:
+ self.transport.write("HTTP/1.1 400 Bad Request\r\n\r\n")
+ self.transport.loseConnection()
+ return
+ command, request, version = parts
+ self._command = command
+ self._path = request
+ self._version = version
+ elif line == '':
+ if self.__header:
+ self.headerReceived(self.__header)
+ self.__header = ''
+ self.allHeadersReceived()
+ if self.length == 0:
+ self.allContentReceived()
+ else:
+ self.setRawMode()
+ elif line[0] in ' \t':
+ self.__header = self.__header+'\n'+line
+ else:
+ if self.__header:
+ self.headerReceived(self.__header)
+ self.__header = line
+
+
+ def _finishRequestBody(self, data):
+ self.allContentReceived()
+ self.setLineMode(data)
+
+
+ def headerReceived(self, line):
+ """
+ Do pre-processing (for content-length) and store this header away.
+ Enforce the per-request header limit.
+
+ @type line: C{str}
+ @param line: A line from the header section of a request, excluding the
+ line delimiter.
+ """
+ header, data = line.split(':', 1)
+ header = header.lower()
+ data = data.strip()
+ if header == 'content-length':
+ self.length = int(data)
+ self._transferDecoder = _IdentityTransferDecoder(
+ self.length, self.requests[-1].handleContentChunk, self._finishRequestBody)
+ elif header == 'transfer-encoding' and data.lower() == 'chunked':
+ self.length = None
+ self._transferDecoder = _ChunkedTransferDecoder(
+ self.requests[-1].handleContentChunk, self._finishRequestBody)
+
+ reqHeaders = self.requests[-1].requestHeaders
+ values = reqHeaders.getRawHeaders(header)
+ if values is not None:
+ values.append(data)
+ else:
+ reqHeaders.setRawHeaders(header, [data])
+
+ self._receivedHeaderCount += 1
+ if self._receivedHeaderCount > self.maxHeaders:
+ self.transport.write("HTTP/1.1 400 Bad Request\r\n\r\n")
+ self.transport.loseConnection()
+
+
+ def allContentReceived(self):
+ command = self._command
+ path = self._path
+ version = self._version
+
+ # reset ALL state variables, so we don't interfere with next request
+ self.length = 0
+ self._receivedHeaderCount = 0
+ self.__first_line = 1
+ self._transferDecoder = None
+ del self._command, self._path, self._version
+
+ # Disable the idle timeout, in case this request takes a long
+ # time to finish generating output.
+ if self.timeOut:
+ self._savedTimeOut = self.setTimeout(None)
+
+ req = self.requests[-1]
+ req.requestReceived(command, path, version)
+
+ def rawDataReceived(self, data):
+ self.resetTimeout()
+ self._transferDecoder.dataReceived(data)
+
+
+ def allHeadersReceived(self):
+ req = self.requests[-1]
+ req.parseCookies()
+ self.persistent = self.checkPersistence(req, self._version)
+ req.gotLength(self.length)
+
+
+ def checkPersistence(self, request, version):
+ """
+ Check if the channel should close or not.
+
+ @param request: The request most recently received over this channel
+ against which checks will be made to determine if this connection
+ can remain open after a matching response is returned.
+
+ @type version: C{str}
+ @param version: The version of the request.
+
+ @rtype: C{bool}
+ @return: A flag which, if C{True}, indicates that this connection may
+ remain open to receive another request; if C{False}, the connection
+ must be closed in order to indicate the completion of the response
+ to C{request}.
+ """
+ connection = request.requestHeaders.getRawHeaders('connection')
+ if connection:
+ tokens = map(str.lower, connection[0].split(' '))
+ else:
+ tokens = []
+
+ # HTTP 1.0 persistent connection support is currently disabled,
+ # since we need a way to disable pipelining. HTTP 1.0 can't do
+ # pipelining since we can't know in advance if we'll have a
+ # content-length header, if we don't have the header we need to close the
+ # connection. In HTTP 1.1 this is not an issue since we use chunked
+ # encoding if content-length is not available.
+
+ #if version == "HTTP/1.0":
+ # if 'keep-alive' in tokens:
+ # request.setHeader('connection', 'Keep-Alive')
+ # return 1
+ # else:
+ # return 0
+ if version == "HTTP/1.1":
+ if 'close' in tokens:
+ request.responseHeaders.setRawHeaders('connection', ['close'])
+ return False
+ else:
+ return True
+ else:
+ return False
+
+
+ def requestDone(self, request):
+ """
+ Called by first request in queue when it is done.
+ """
+ if request != self.requests[0]: raise TypeError
+ del self.requests[0]
+
+ if self.persistent:
+ # notify next request it can start writing
+ if self.requests:
+ self.requests[0].noLongerQueued()
+ else:
+ if self._savedTimeOut:
+ self.setTimeout(self._savedTimeOut)
+ else:
+ self.transport.loseConnection()
+
+ def timeoutConnection(self):
+ log.msg("Timing out client: %s" % str(self.transport.getPeer()))
+ policies.TimeoutMixin.timeoutConnection(self)
+
+ def connectionLost(self, reason):
+ self.setTimeout(None)
+ for request in self.requests:
+ request.connectionLost(reason)
+
+
+class HTTPFactory(protocol.ServerFactory):
+ """
+ Factory for HTTP server.
+ """
+
+ protocol = HTTPChannel
+
+ logPath = None
+
+ timeOut = 60 * 60 * 12
+
+ def __init__(self, logPath=None, timeout=60*60*12):
+ if logPath is not None:
+ logPath = os.path.abspath(logPath)
+ self.logPath = logPath
+ self.timeOut = timeout
+
+ def buildProtocol(self, addr):
+ p = protocol.ServerFactory.buildProtocol(self, addr)
+ # timeOut needs to be on the Protocol instance cause
+ # TimeoutMixin expects it there
+ p.timeOut = self.timeOut
+ return p
+
+ def startFactory(self):
+ _logDateTimeStart()
+ if self.logPath:
+ self.logFile = self._openLogFile(self.logPath)
+ else:
+ self.logFile = log.logfile
+
+ def stopFactory(self):
+ if hasattr(self, "logFile"):
+ if self.logFile != log.logfile:
+ self.logFile.close()
+ del self.logFile
+ _logDateTimeStop()
+
+ def _openLogFile(self, path):
+ """
+ Override in subclasses, e.g. to use twisted.python.logfile.
+ """
+ f = open(path, "a", 1)
+ return f
+
+ def _escape(self, s):
+ # pain in the ass. Return a string like python repr, but always
+ # escaped as if surrounding quotes were "".
+ r = repr(s)
+ if r[0] == "'":
+ return r[1:-1].replace('"', '\\"').replace("\\'", "'")
+ return r[1:-1]
+
+ def log(self, request):
+ """
+ Log a request's result to the logfile, by default in combined log format.
+ """
+ if hasattr(self, "logFile"):
+ line = '%s - - %s "%s" %d %s "%s" "%s"\n' % (
+ request.getClientIP(),
+ # request.getUser() or "-", # the remote user is almost never important
+ _logDateTime,
+ '%s %s %s' % (self._escape(request.method),
+ self._escape(request.uri),
+ self._escape(request.clientproto)),
+ request.code,
+ request.sentLength or "-",
+ self._escape(request.getHeader("referer") or "-"),
+ self._escape(request.getHeader("user-agent") or "-"))
+ self.logFile.write(line)
diff --git a/vendor/Twisted-10.0.0/twisted/web/http_headers.py b/vendor/Twisted-10.0.0/twisted/web/http_headers.py
new file mode 100644
index 0000000000..29ed567b3e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/http_headers.py
@@ -0,0 +1,260 @@
+# -*- test-case-name: twisted.web.test.test_http_headers
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An API for storing HTTP header names and values.
+"""
+
+
+from UserDict import DictMixin
+
+
+def _dashCapitalize(name):
+ """
+ Return a string which is capitalized using '-' as a word separator.
+
+ @param name: The name of the header to capitalize.
+ @type name: str
+
+ @return: The given header capitalized using '-' as a word separator.
+ @rtype: str
+ """
+ return '-'.join([word.capitalize() for word in name.split('-')])
+
+
+
+class _DictHeaders(DictMixin):
+ """
+ A C{dict}-like wrapper around L{Headers} to provide backwards compatibility
+ for L{Request.received_headers} and L{Request.headers} which used to be
+ plain C{dict} instances.
+
+ @type _headers: L{Headers}
+ @ivar _headers: The real header storage object.
+ """
+ def __init__(self, headers):
+ self._headers = headers
+
+
+ def __getitem__(self, key):
+ """
+ Return the last value for header of C{key}.
+ """
+ if self._headers.hasHeader(key):
+ return self._headers.getRawHeaders(key)[-1]
+ raise KeyError(key)
+
+
+ def __setitem__(self, key, value):
+ """
+ Set the given header.
+ """
+ self._headers.setRawHeaders(key, [value])
+
+
+ def __delitem__(self, key):
+ """
+ Delete the given header.
+ """
+ if self._headers.hasHeader(key):
+ self._headers.removeHeader(key)
+ else:
+ raise KeyError(key)
+
+
+ def keys(self):
+ """
+ Return a list of all header names.
+ """
+ return [k.lower() for k, v in self._headers.getAllRawHeaders()]
+
+
+ def copy(self):
+ """
+ Return a C{dict} mapping each header name to the last corresponding
+ header value.
+ """
+ return dict(self.items())
+
+
+ # Python 2.3 DictMixin.setdefault is defined so as not to have a default
+ # for the value parameter. This is necessary to make this setdefault look
+ # like dict.setdefault on Python 2.3. -exarkun
+ def setdefault(self, name, value=None):
+ """
+ Retrieve the last value for the given header name. If there are no
+ values present for that header, set the value to C{value} and return
+ that instead. Note that C{None} is the default for C{value} for
+ backwards compatibility, but header values may only be of type C{str}.
+ """
+ return DictMixin.setdefault(self, name, value)
+
+
+ # The remaining methods are only for efficiency. The same behavior
+ # should remain even if they are removed. For details, see
+ # <http://docs.python.org/lib/module-UserDict.html>.
+ # -exarkun
+ def __contains__(self, name):
+ """
+ Return C{True} if the named header is present, C{False} otherwise.
+ """
+ return self._headers.getRawHeaders(name) is not None
+
+
+ def __iter__(self):
+ """
+ Return an iterator of the lowercase name of each header present.
+ """
+ for k, v in self._headers.getAllRawHeaders():
+ yield k.lower()
+
+
+ def iteritems(self):
+ """
+ Return an iterable of two-tuples of each lower-case header name and the
+ last value for that header.
+ """
+ for k, v in self._headers.getAllRawHeaders():
+ yield k.lower(), v[-1]
+
+
+
+class Headers(object):
+ """
+ This class stores the HTTP headers as both a parsed representation
+ and the raw string representation. It converts between the two on
+ demand.
+
+ @cvar _caseMappings: A C{dict} that maps lowercase header names
+ to their canonicalized representation.
+
+ @ivar _rawHeaders: A C{dict} mapping header names as C{str} to C{lists} of
+ header values as C{str}.
+ """
+ _caseMappings = {'www-authenticate': 'WWW-Authenticate'}
+
+ def __init__(self, rawHeaders=None):
+ self._rawHeaders = {}
+ if rawHeaders is not None:
+ for name, values in rawHeaders.iteritems():
+ self.setRawHeaders(name, values)
+
+
+ def __repr__(self):
+ """
+ Return a string fully describing the headers set on this object.
+ """
+ return '%s(%r)' % (self.__class__.__name__, self._rawHeaders,)
+
+
+ def __cmp__(self, other):
+ """
+ Define L{Headers} instances as being equal to each other if they have
+ the same raw headers.
+ """
+ if isinstance(other, Headers):
+ return cmp(self._rawHeaders, other._rawHeaders)
+ return NotImplemented
+
+
+ def hasHeader(self, name):
+ """
+ Check for the existence of a given header.
+
+ @type name: C{str}
+ @param name: The name of the HTTP header to check for.
+
+ @rtype: C{bool}
+ @return: C{True} if the header exists, otherwise C{False}.
+ """
+ return name.lower() in self._rawHeaders
+
+
+ def removeHeader(self, name):
+ """
+ Remove the named header from this header object.
+
+ @type name: C{str}
+ @param name: The name of the HTTP header to remove.
+
+ @return: C{None}
+ """
+ self._rawHeaders.pop(name.lower(), None)
+
+
+ def setRawHeaders(self, name, values):
+ """
+ Sets the raw representation of the given header.
+
+ @type name: C{str}
+ @param name: The name of the HTTP header to set the values for.
+
+ @type values: C{list}
+ @param values: A list of strings each one being a header value of
+ the given name.
+
+ @return: C{None}
+ """
+ self._rawHeaders[name.lower()] = values
+
+
+ def addRawHeader(self, name, value):
+ """
+ Add a new raw value for the given header.
+
+ @type name: C{str}
+ @param name: The name of the header for which to set the value.
+
+ @type value: C{str}
+ @param value: The value to set for the named header.
+ """
+ values = self.getRawHeaders(name)
+ if values is None:
+ self.setRawHeaders(name, [value])
+ else:
+ values.append(value)
+
+
+ def getRawHeaders(self, name, default=None):
+ """
+ Returns a list of headers matching the given name as the raw string
+ given.
+
+ @type name: C{str}
+ @param name: The name of the HTTP header to get the values of.
+
+ @param default: The value to return if no header with the given C{name}
+ exists.
+
+ @rtype: C{list}
+ @return: A C{list} of values for the given header.
+ """
+ return self._rawHeaders.get(name.lower(), default)
+
+
+ def getAllRawHeaders(self):
+ """
+ Return an iterator of key, value pairs of all headers contained in this
+ object, as strings. The keys are capitalized in canonical
+ capitalization.
+ """
+ for k, v in self._rawHeaders.iteritems():
+ yield self._canonicalNameCaps(k), v
+
+
+ def _canonicalNameCaps(self, name):
+ """
+ Return the canonical name for the given header.
+
+ @type name: C{str}
+ @param name: The all-lowercase header name to capitalize in its
+ canonical form.
+
+ @rtype: C{str}
+ @return: The canonical name of the header.
+ """
+ return self._caseMappings.get(name, _dashCapitalize(name))
+
+
+__all__ = ['Headers']
diff --git a/vendor/Twisted-10.0.0/twisted/web/iweb.py b/vendor/Twisted-10.0.0/twisted/web/iweb.py
new file mode 100644
index 0000000000..06655dc49a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/iweb.py
@@ -0,0 +1,421 @@
+# -*- test-case-name: twisted.web.test -*-
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Interface definitions for L{twisted.web}.
+
+@var UNKNOWN_LENGTH: An opaque object which may be used as the value of
+ L{IBodyProducer.length} to indicate that the length of the entity
+ body is not known in advance.
+"""
+
+from zope.interface import Interface, Attribute
+
+from twisted.internet.interfaces import IPushProducer
+from twisted.cred.credentials import IUsernameDigestHash
+
+
+class IRequest(Interface):
+ """
+ An HTTP request.
+
+ @since: 9.0
+ """
+
+ method = Attribute("A C{str} giving the HTTP method that was used.")
+ uri = Attribute(
+ "A C{str} giving the full encoded URI which was requested (including "
+ "query arguments).")
+ path = Attribute(
+ "A C{str} giving the encoded query path of the request URI.")
+ args = Attribute(
+ "A mapping of decoded query argument names as C{str} to "
+ "corresponding query argument values as C{list}s of C{str}. "
+ "For example, for a URI with C{'foo=bar&foo=baz&quux=spam'} "
+ "for its query part, C{args} will be C{{'foo': ['bar', 'baz'], "
+ "'quux': ['spam']}}.")
+
+ received_headers = Attribute(
+ "Backwards-compatibility access to C{requestHeaders}. Use "
+ "C{requestHeaders} instead. C{received_headers} behaves mostly "
+ "like a C{dict} and does not provide access to all header values.")
+
+ requestHeaders = Attribute(
+ "A L{http_headers.Headers} instance giving all received HTTP request "
+ "headers.")
+
+ headers = Attribute(
+ "Backwards-compatibility access to C{responseHeaders}. Use"
+ "C{responseHeaders} instead. C{headers} behaves mostly like a "
+ "C{dict} and does not provide access to all header values nor "
+ "does it allow multiple values for one header to be set.")
+
+ responseHeaders = Attribute(
+ "A L{http_headers.Headers} instance holding all HTTP response "
+ "headers to be sent.")
+
+ def getHeader(key):
+ """
+ Get an HTTP request header.
+
+ @type key: C{str}
+ @param key: The name of the header to get the value of.
+
+ @rtype: C{str} or C{NoneType}
+ @return: The value of the specified header, or C{None} if that header
+ was not present in the request.
+ """
+
+
+ def getCookie(key):
+ """
+ Get a cookie that was sent from the network.
+ """
+
+
+ def getAllHeaders():
+ """
+ Return dictionary mapping the names of all received headers to the last
+ value received for each.
+
+ Since this method does not return all header information,
+ C{requestHeaders.getAllRawHeaders()} may be preferred.
+ """
+
+
+ def getRequestHostname():
+ """
+ Get the hostname that the user passed in to the request.
+
+ This will either use the Host: header (if it is available) or the
+ host we are listening on if the header is unavailable.
+
+ @returns: the requested hostname
+ @rtype: C{str}
+ """
+
+
+ def getHost():
+ """
+ Get my originally requesting transport's host.
+
+ @return: An L{IAddress}.
+ """
+
+
+ def getClientIP():
+ """
+ Return the IP address of the client who submitted this request.
+
+ @returns: the client IP address or C{None} if the request was submitted
+ over a transport where IP addresses do not make sense.
+ @rtype: C{str} or L{NoneType}
+ """
+
+
+ def getClient():
+ """
+ Return the hostname of the IP address of the client who submitted this
+ request, if possible.
+
+ This method is B{deprecated}. See L{getClientIP} instead.
+
+ @rtype: L{NoneType} or L{str}
+ @return: The canonical hostname of the client, as determined by
+ performing a name lookup on the IP address of the client.
+ """
+
+
+ def getUser():
+ """
+ Return the HTTP user sent with this request, if any.
+
+ If no user was supplied, return the empty string.
+
+ @returns: the HTTP user, if any
+ @rtype: C{str}
+ """
+
+
+ def getPassword():
+ """
+ Return the HTTP password sent with this request, if any.
+
+ If no password was supplied, return the empty string.
+
+ @returns: the HTTP password, if any
+ @rtype: C{str}
+ """
+
+
+ def isSecure():
+ """
+ Return True if this request is using a secure transport.
+
+ Normally this method returns True if this request's HTTPChannel
+ instance is using a transport that implements ISSLTransport.
+
+ This will also return True if setHost() has been called
+ with ssl=True.
+
+ @returns: True if this request is secure
+ @rtype: C{bool}
+ """
+
+
+ def getSession(sessionInterface=None):
+ """
+ Look up the session associated with this request or create a new one if
+ there is not one.
+
+ @return: The L{Session} instance identified by the session cookie in
+ the request, or the C{sessionInterface} component of that session
+ if C{sessionInterface} is specified.
+ """
+
+
+ def URLPath():
+ """
+ @return: A L{URLPath} instance which identifies the URL for which this
+ request is.
+ """
+
+
+ def prePathURL():
+ """
+ @return: At any time during resource traversal, a L{str} giving an
+ absolute URL to the most nested resource which has yet been
+ reached.
+ """
+
+
+ def rememberRootURL():
+ """
+ Remember the currently-processed part of the URL for later
+ recalling.
+ """
+
+
+ def getRootURL():
+ """
+ Get a previously-remembered URL.
+ """
+
+
+ # Methods for outgoing response
+ def finish():
+ """
+ Indicate that the response to this request is complete.
+ """
+
+
+ def write(data):
+ """
+ Write some data to the body of the response to this request. Response
+ headers are written the first time this method is called, after which
+ new response headers may not be added.
+ """
+
+
+ def addCookie(k, v, expires=None, domain=None, path=None, max_age=None, comment=None, secure=None):
+ """
+ Set an outgoing HTTP cookie.
+
+ In general, you should consider using sessions instead of cookies, see
+ L{twisted.web.server.Request.getSession} and the
+ L{twisted.web.server.Session} class for details.
+ """
+
+
+ def setResponseCode(code, message=None):
+ """
+ Set the HTTP response code.
+ """
+
+
+ def setHeader(k, v):
+ """
+ Set an HTTP response header. Overrides any previously set values for
+ this header.
+
+ @type name: C{str}
+ @param name: The name of the header for which to set the value.
+
+ @type value: C{str}
+ @param value: The value to set for the named header.
+ """
+
+
+ def redirect(url):
+ """
+ Utility function that does a redirect.
+
+ The request should have finish() called after this.
+ """
+
+
+ def setLastModified(when):
+ """
+ Set the C{Last-Modified} time for the response to this request.
+
+ If I am called more than once, I ignore attempts to set Last-Modified
+ earlier, only replacing the Last-Modified time if it is to a later
+ value.
+
+ If I am a conditional request, I may modify my response code to
+ L{NOT_MODIFIED} if appropriate for the time given.
+
+ @param when: The last time the resource being returned was modified, in
+ seconds since the epoch.
+ @type when: C{int}, C{long} or C{float}
+
+ @return: If I am a C{If-Modified-Since} conditional request and the
+ time given is not newer than the condition, I return
+ L{http.CACHED<CACHED>} to indicate that you should write no body.
+ Otherwise, I return a false value.
+ """
+
+
+ def setETag(etag):
+ """
+ Set an C{entity tag} for the outgoing response.
+
+ That's "entity tag" as in the HTTP/1.1 C{ETag} header, "used for
+ comparing two or more entities from the same requested resource."
+
+ If I am a conditional request, I may modify my response code to
+ L{NOT_MODIFIED} or L{PRECONDITION_FAILED}, if appropriate for the tag
+ given.
+
+ @param etag: The entity tag for the resource being returned.
+ @type etag: C{str}
+ @return: If I am a C{If-None-Match} conditional request and the tag
+ matches one in the request, I return L{http.CACHED<CACHED>} to
+ indicate that you should write no body. Otherwise, I return a
+ false value.
+ """
+
+
+ def setHost(host, port, ssl=0):
+ """
+ Change the host and port the request thinks it's using.
+
+ This method is useful for working with reverse HTTP proxies (e.g. both
+ Squid and Apache's mod_proxy can do this), when the address the HTTP
+ client is using is different than the one we're listening on.
+
+ For example, Apache may be listening on https://www.example.com, and
+ then forwarding requests to http://localhost:8080, but we don't want
+ HTML produced by Twisted to say 'http://localhost:8080', they should
+ say 'https://www.example.com', so we do::
+
+ request.setHost('www.example.com', 443, ssl=1)
+ """
+
+
+
+class ICredentialFactory(Interface):
+ """
+ A credential factory defines a way to generate a particular kind of
+ authentication challenge and a way to interpret the responses to these
+ challenges. It creates L{ICredentials} providers from responses. These
+ objects will be used with L{twisted.cred} to authenticate an authorize
+ requests.
+ """
+ scheme = Attribute(
+ "A C{str} giving the name of the authentication scheme with which "
+ "this factory is associated. For example, C{'basic'} or C{'digest'}.")
+
+
+ def getChallenge(request):
+ """
+ Generate a new challenge to be sent to a client.
+
+ @type peer: L{twisted.web.http.Request}
+ @param peer: The request the response to which this challenge will be
+ included.
+
+ @rtype: C{dict}
+ @return: A mapping from C{str} challenge fields to associated C{str}
+ values.
+ """
+
+
+ def decode(response, request):
+ """
+ Create a credentials object from the given response.
+
+ @type response: C{str}
+ @param response: scheme specific response string
+
+ @type request: L{twisted.web.http.Request}
+ @param request: The request being processed (from which the response
+ was taken).
+
+ @raise twisted.cred.error.LoginFailed: If the response is invalid.
+
+ @rtype: L{twisted.cred.credentials.ICredentials} provider
+ @return: The credentials represented by the given response.
+ """
+
+
+
+class IBodyProducer(IPushProducer):
+ """
+ Objects which provide L{IBodyProducer} write bytes to an object which
+ provides L{IConsumer} by calling its C{write} method repeatedly.
+
+ L{IBodyProducer} providers may start producing as soon as they have
+ an L{IConsumer} provider. That is, they should not wait for a
+ C{resumeProducing} call to begin writing data.
+
+ L{IConsumer.unregisterProducer} must not be called. Instead, the
+ L{Deferred} returned from C{startProducing} must be fired when all bytes
+ have been written.
+
+ L{IConsumer.write} may synchronously invoke any of C{pauseProducing},
+ C{resumeProducing}, or C{stopProducing}. These methods must be implemented
+ with this in mind.
+
+ @since: 9.0
+ """
+
+ # Despite the restrictions above and the additional requirements of
+ # stopProducing documented below, this interface still needs to be an
+ # IPushProducer subclass. Providers of it will be passed to IConsumer
+ # providers which only know about IPushProducer and IPullProducer, not
+ # about this interface. This interface needs to remain close enough to one
+ # of those interfaces for consumers to work with it.
+
+ length = Attribute(
+ """
+ C{length} is a C{int} indicating how many bytes in total this
+ L{IBodyProducer} will write to the consumer or L{UNKNOWN_LENGTH}
+ if this is not known in advance.
+ """)
+
+ def startProducing(consumer):
+ """
+ Start producing to the given L{IConsumer} provider.
+
+ @return: A L{Deferred} which fires with C{None} when all bytes have
+ been produced or with a L{Failure} if there is any problem before
+ all bytes have been produced.
+ """
+
+
+ def stopProducing():
+ """
+ In addition to the standard behavior of L{IProducer.stopProducing}
+ (stop producing data), make sure the L{Deferred} returned by
+ C{startProducing} is never fired.
+ """
+
+UNKNOWN_LENGTH = u"twisted.web.iweb.UNKNOWN_LENGTH"
+
+__all__ = [
+ "IUsernameDigestHash", "ICredentialFactory", "IRequest",
+ "IBodyProducer",
+
+ "UNKNOWN_LENGTH"]
diff --git a/vendor/Twisted-10.0.0/twisted/web/microdom.py b/vendor/Twisted-10.0.0/twisted/web/microdom.py
new file mode 100644
index 0000000000..f899e39e25
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/microdom.py
@@ -0,0 +1,1028 @@
+# -*- test-case-name: twisted.web.test.test_xml -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Micro Document Object Model: a partial DOM implementation with SUX.
+
+This is an implementation of what we consider to be the useful subset of the
+DOM. The chief advantage of this library is that, not being burdened with
+standards compliance, it can remain very stable between versions. We can also
+implement utility 'pythonic' ways to access and mutate the XML tree.
+
+Since this has not subjected to a serious trial by fire, it is not recommended
+to use this outside of Twisted applications. However, it seems to work just
+fine for the documentation generator, which parses a fairly representative
+sample of XML.
+
+Microdom mainly focuses on working with HTML and XHTML.
+"""
+
+# System Imports
+import re
+from cStringIO import StringIO
+
+# create NodeList class
+from types import ListType as NodeList
+from types import StringTypes, UnicodeType
+
+# Twisted Imports
+from twisted.web.sux import XMLParser, ParseError
+from twisted.python.util import InsensitiveDict
+
+
+def getElementsByTagName(iNode, name):
+ """
+ Return a list of all child elements of C{iNode} with a name matching
+ C{name}.
+
+ Note that this implementation does not conform to the DOM Level 1 Core
+ specification because it may return C{iNode}.
+
+ @param iNode: An element at which to begin searching. If C{iNode} has a
+ name matching C{name}, it will be included in the result.
+
+ @param name: A C{str} giving the name of the elements to return.
+
+ @return: A C{list} of direct or indirect child elements of C{iNode} with
+ the name C{name}. This may include C{iNode}.
+ """
+ matches = []
+ matches_append = matches.append # faster lookup. don't do this at home
+ slice = [iNode]
+ while len(slice)>0:
+ c = slice.pop(0)
+ if c.nodeName == name:
+ matches_append(c)
+ slice[:0] = c.childNodes
+ return matches
+
+
+
+def getElementsByTagNameNoCase(iNode, name):
+ name = name.lower()
+ matches = []
+ matches_append = matches.append
+ slice=[iNode]
+ while len(slice)>0:
+ c = slice.pop(0)
+ if c.nodeName.lower() == name:
+ matches_append(c)
+ slice[:0] = c.childNodes
+ return matches
+
+# order is important
+HTML_ESCAPE_CHARS = (('&', '&amp;'), # don't add any entities before this one
+ ('<', '&lt;'),
+ ('>', '&gt;'),
+ ('"', '&quot;'))
+REV_HTML_ESCAPE_CHARS = list(HTML_ESCAPE_CHARS)
+REV_HTML_ESCAPE_CHARS.reverse()
+
+XML_ESCAPE_CHARS = HTML_ESCAPE_CHARS + (("'", '&apos;'),)
+REV_XML_ESCAPE_CHARS = list(XML_ESCAPE_CHARS)
+REV_XML_ESCAPE_CHARS.reverse()
+
+def unescape(text, chars=REV_HTML_ESCAPE_CHARS):
+ "Perform the exact opposite of 'escape'."
+ for s, h in chars:
+ text = text.replace(h, s)
+ return text
+
+def escape(text, chars=HTML_ESCAPE_CHARS):
+ "Escape a few XML special chars with XML entities."
+ for s, h in chars:
+ text = text.replace(s, h)
+ return text
+
+
+class MismatchedTags(Exception):
+
+ def __init__(self, filename, expect, got, endLine, endCol, begLine, begCol):
+ (self.filename, self.expect, self.got, self.begLine, self.begCol, self.endLine,
+ self.endCol) = filename, expect, got, begLine, begCol, endLine, endCol
+
+ def __str__(self):
+ return ("expected </%s>, got </%s> line: %s col: %s, began line: %s col: %s"
+ % (self.expect, self.got, self.endLine, self.endCol, self.begLine,
+ self.begCol))
+
+
+class Node(object):
+ nodeName = "Node"
+
+ def __init__(self, parentNode=None):
+ self.parentNode = parentNode
+ self.childNodes = []
+
+ def isEqualToNode(self, other):
+ """
+ Compare this node to C{other}. If the nodes have the same number of
+ children and corresponding children are equal to each other, return
+ C{True}, otherwise return C{False}.
+
+ @type other: L{Node}
+ @rtype: C{bool}
+ """
+ if len(self.childNodes) != len(other.childNodes):
+ return False
+ for a, b in zip(self.childNodes, other.childNodes):
+ if not a.isEqualToNode(b):
+ return False
+ return True
+
+ def writexml(self, stream, indent='', addindent='', newl='', strip=0,
+ nsprefixes={}, namespace=''):
+ raise NotImplementedError()
+
+ def toxml(self, indent='', addindent='', newl='', strip=0, nsprefixes={},
+ namespace=''):
+ s = StringIO()
+ self.writexml(s, indent, addindent, newl, strip, nsprefixes, namespace)
+ rv = s.getvalue()
+ return rv
+
+ def writeprettyxml(self, stream, indent='', addindent=' ', newl='\n', strip=0):
+ return self.writexml(stream, indent, addindent, newl, strip)
+
+ def toprettyxml(self, indent='', addindent=' ', newl='\n', strip=0):
+ return self.toxml(indent, addindent, newl, strip)
+
+ def cloneNode(self, deep=0, parent=None):
+ raise NotImplementedError()
+
+ def hasChildNodes(self):
+ if self.childNodes:
+ return 1
+ else:
+ return 0
+
+
+ def appendChild(self, child):
+ """
+ Make the given L{Node} the last child of this node.
+
+ @param child: The L{Node} which will become a child of this node.
+
+ @raise TypeError: If C{child} is not a C{Node} instance.
+ """
+ if not isinstance(child, Node):
+ raise TypeError("expected Node instance")
+ self.childNodes.append(child)
+ child.parentNode = self
+
+
+ def insertBefore(self, new, ref):
+ """
+ Make the given L{Node} C{new} a child of this node which comes before
+ the L{Node} C{ref}.
+
+ @param new: A L{Node} which will become a child of this node.
+
+ @param ref: A L{Node} which is already a child of this node which
+ C{new} will be inserted before.
+
+ @raise TypeError: If C{new} or C{ref} is not a C{Node} instance.
+
+ @return: C{new}
+ """
+ if not isinstance(new, Node) or not isinstance(ref, Node):
+ raise TypeError("expected Node instance")
+ i = self.childNodes.index(ref)
+ new.parentNode = self
+ self.childNodes.insert(i, new)
+ return new
+
+
+ def removeChild(self, child):
+ """
+ Remove the given L{Node} from this node's children.
+
+ @param child: A L{Node} which is a child of this node which will no
+ longer be a child of this node after this method is called.
+
+ @raise TypeError: If C{child} is not a C{Node} instance.
+
+ @return: C{child}
+ """
+ if not isinstance(child, Node):
+ raise TypeError("expected Node instance")
+ if child in self.childNodes:
+ self.childNodes.remove(child)
+ child.parentNode = None
+ return child
+
+ def replaceChild(self, newChild, oldChild):
+ """
+ Replace a L{Node} which is already a child of this node with a
+ different node.
+
+ @param newChild: A L{Node} which will be made a child of this node.
+
+ @param oldChild: A L{Node} which is a child of this node which will
+ give up its position to C{newChild}.
+
+ @raise TypeError: If C{newChild} or C{oldChild} is not a C{Node}
+ instance.
+
+ @raise ValueError: If C{oldChild} is not a child of this C{Node}.
+ """
+ if not isinstance(newChild, Node) or not isinstance(oldChild, Node):
+ raise TypeError("expected Node instance")
+ if oldChild.parentNode is not self:
+ raise ValueError("oldChild is not a child of this node")
+ self.childNodes[self.childNodes.index(oldChild)] = newChild
+ oldChild.parentNode = None
+ newChild.parentNode = self
+
+
+ def lastChild(self):
+ return self.childNodes[-1]
+
+
+ def firstChild(self):
+ if len(self.childNodes):
+ return self.childNodes[0]
+ return None
+
+ #def get_ownerDocument(self):
+ # """This doesn't really get the owner document; microdom nodes
+ # don't even have one necessarily. This gets the root node,
+ # which is usually what you really meant.
+ # *NOT DOM COMPLIANT.*
+ # """
+ # node=self
+ # while (node.parentNode): node=node.parentNode
+ # return node
+ #ownerDocument=node.get_ownerDocument()
+ # leaving commented for discussion; see also domhelpers.getParents(node)
+
+class Document(Node):
+
+ def __init__(self, documentElement=None):
+ Node.__init__(self)
+ if documentElement:
+ self.appendChild(documentElement)
+
+ def cloneNode(self, deep=0, parent=None):
+ d = Document()
+ d.doctype = self.doctype
+ if deep:
+ newEl = self.documentElement.cloneNode(1, self)
+ else:
+ newEl = self.documentElement
+ d.appendChild(newEl)
+ return d
+
+ doctype = None
+
+ def isEqualToDocument(self, n):
+ return (self.doctype == n.doctype) and Node.isEqualToNode(self, n)
+ isEqualToNode = isEqualToDocument
+
+ def get_documentElement(self):
+ return self.childNodes[0]
+ documentElement=property(get_documentElement)
+
+ def appendChild(self, child):
+ """
+ Make the given L{Node} the I{document element} of this L{Document}.
+
+ @param child: The L{Node} to make into this L{Document}'s document
+ element.
+
+ @raise ValueError: If this document already has a document element.
+ """
+ if self.childNodes:
+ raise ValueError("Only one element per document.")
+ Node.appendChild(self, child)
+
+ def writexml(self, stream, indent='', addindent='', newl='', strip=0,
+ nsprefixes={}, namespace=''):
+ stream.write('<?xml version="1.0"?>' + newl)
+ if self.doctype:
+ stream.write("<!DOCTYPE "+self.doctype+">" + newl)
+ self.documentElement.writexml(stream, indent, addindent, newl, strip,
+ nsprefixes, namespace)
+
+ # of dubious utility (?)
+ def createElement(self, name, **kw):
+ return Element(name, **kw)
+
+ def createTextNode(self, text):
+ return Text(text)
+
+ def createComment(self, text):
+ return Comment(text)
+
+ def getElementsByTagName(self, name):
+ if self.documentElement.caseInsensitive:
+ return getElementsByTagNameNoCase(self, name)
+ return getElementsByTagName(self, name)
+
+ def getElementById(self, id):
+ childNodes = self.childNodes[:]
+ while childNodes:
+ node = childNodes.pop(0)
+ if node.childNodes:
+ childNodes.extend(node.childNodes)
+ if hasattr(node, 'getAttribute') and node.getAttribute("id") == id:
+ return node
+
+
+class EntityReference(Node):
+
+ def __init__(self, eref, parentNode=None):
+ Node.__init__(self, parentNode)
+ self.eref = eref
+ self.nodeValue = self.data = "&" + eref + ";"
+
+ def isEqualToEntityReference(self, n):
+ if not isinstance(n, EntityReference):
+ return 0
+ return (self.eref == n.eref) and (self.nodeValue == n.nodeValue)
+ isEqualToNode = isEqualToEntityReference
+
+ def writexml(self, stream, indent='', addindent='', newl='', strip=0,
+ nsprefixes={}, namespace=''):
+ stream.write(self.nodeValue)
+
+ def cloneNode(self, deep=0, parent=None):
+ return EntityReference(self.eref, parent)
+
+
+class CharacterData(Node):
+
+ def __init__(self, data, parentNode=None):
+ Node.__init__(self, parentNode)
+ self.value = self.data = self.nodeValue = data
+
+ def isEqualToCharacterData(self, n):
+ return self.value == n.value
+ isEqualToNode = isEqualToCharacterData
+
+
+class Comment(CharacterData):
+ """A comment node."""
+
+ def writexml(self, stream, indent='', addindent='', newl='', strip=0,
+ nsprefixes={}, namespace=''):
+ val=self.data
+ if isinstance(val, UnicodeType):
+ val=val.encode('utf8')
+ stream.write("<!--%s-->" % val)
+
+ def cloneNode(self, deep=0, parent=None):
+ return Comment(self.nodeValue, parent)
+
+
+class Text(CharacterData):
+
+ def __init__(self, data, parentNode=None, raw=0):
+ CharacterData.__init__(self, data, parentNode)
+ self.raw = raw
+
+
+ def isEqualToNode(self, other):
+ """
+ Compare this text to C{text}. If the underlying values and the C{raw}
+ flag are the same, return C{True}, otherwise return C{False}.
+ """
+ return (
+ CharacterData.isEqualToNode(self, other) and
+ self.raw == other.raw)
+
+
+ def cloneNode(self, deep=0, parent=None):
+ return Text(self.nodeValue, parent, self.raw)
+
+ def writexml(self, stream, indent='', addindent='', newl='', strip=0,
+ nsprefixes={}, namespace=''):
+ if self.raw:
+ val = self.nodeValue
+ if not isinstance(val, StringTypes):
+ val = str(self.nodeValue)
+ else:
+ v = self.nodeValue
+ if not isinstance(v, StringTypes):
+ v = str(v)
+ if strip:
+ v = ' '.join(v.split())
+ val = escape(v)
+ if isinstance(val, UnicodeType):
+ val = val.encode('utf8')
+ stream.write(val)
+
+ def __repr__(self):
+ return "Text(%s" % repr(self.nodeValue) + ')'
+
+
+class CDATASection(CharacterData):
+ def cloneNode(self, deep=0, parent=None):
+ return CDATASection(self.nodeValue, parent)
+
+ def writexml(self, stream, indent='', addindent='', newl='', strip=0,
+ nsprefixes={}, namespace=''):
+ stream.write("<![CDATA[")
+ stream.write(self.nodeValue)
+ stream.write("]]>")
+
+def _genprefix():
+ i = 0
+ while True:
+ yield 'p' + str(i)
+ i = i + 1
+genprefix = _genprefix().next
+
+class _Attr(CharacterData):
+ "Support class for getAttributeNode."
+
+class Element(Node):
+
+ preserveCase = 0
+ caseInsensitive = 1
+ nsprefixes = None
+
+ def __init__(self, tagName, attributes=None, parentNode=None,
+ filename=None, markpos=None,
+ caseInsensitive=1, preserveCase=0,
+ namespace=None):
+ Node.__init__(self, parentNode)
+ self.preserveCase = preserveCase or not caseInsensitive
+ self.caseInsensitive = caseInsensitive
+ if not preserveCase:
+ tagName = tagName.lower()
+ if attributes is None:
+ self.attributes = {}
+ else:
+ self.attributes = attributes
+ for k, v in self.attributes.items():
+ self.attributes[k] = unescape(v)
+
+ if caseInsensitive:
+ self.attributes = InsensitiveDict(self.attributes,
+ preserve=preserveCase)
+
+ self.endTagName = self.nodeName = self.tagName = tagName
+ self._filename = filename
+ self._markpos = markpos
+ self.namespace = namespace
+
+ def addPrefixes(self, pfxs):
+ if self.nsprefixes is None:
+ self.nsprefixes = pfxs
+ else:
+ self.nsprefixes.update(pfxs)
+
+ def endTag(self, endTagName):
+ if not self.preserveCase:
+ endTagName = endTagName.lower()
+ self.endTagName = endTagName
+
+ def isEqualToElement(self, n):
+ if self.caseInsensitive:
+ return ((self.attributes == n.attributes)
+ and (self.nodeName.lower() == n.nodeName.lower()))
+ return (self.attributes == n.attributes) and (self.nodeName == n.nodeName)
+
+
+ def isEqualToNode(self, other):
+ """
+ Compare this element to C{other}. If the C{nodeName}, C{namespace},
+ C{attributes}, and C{childNodes} are all the same, return C{True},
+ otherwise return C{False}.
+ """
+ return (
+ self.nodeName.lower() == other.nodeName.lower() and
+ self.namespace == other.namespace and
+ self.attributes == other.attributes and
+ Node.isEqualToNode(self, other))
+
+
+ def cloneNode(self, deep=0, parent=None):
+ clone = Element(
+ self.tagName, parentNode=parent, namespace=self.namespace,
+ preserveCase=self.preserveCase, caseInsensitive=self.caseInsensitive)
+ clone.attributes.update(self.attributes)
+ if deep:
+ clone.childNodes = [child.cloneNode(1, clone) for child in self.childNodes]
+ else:
+ clone.childNodes = []
+ return clone
+
+ def getElementsByTagName(self, name):
+ if self.caseInsensitive:
+ return getElementsByTagNameNoCase(self, name)
+ return getElementsByTagName(self, name)
+
+ def hasAttributes(self):
+ return 1
+
+ def getAttribute(self, name, default=None):
+ return self.attributes.get(name, default)
+
+ def getAttributeNS(self, ns, name, default=None):
+ nsk = (ns, name)
+ if self.attributes.has_key(nsk):
+ return self.attributes[nsk]
+ if ns == self.namespace:
+ return self.attributes.get(name, default)
+ return default
+
+ def getAttributeNode(self, name):
+ return _Attr(self.getAttribute(name), self)
+
+ def setAttribute(self, name, attr):
+ self.attributes[name] = attr
+
+ def removeAttribute(self, name):
+ if name in self.attributes:
+ del self.attributes[name]
+
+ def hasAttribute(self, name):
+ return name in self.attributes
+
+
+ def writexml(self, stream, indent='', addindent='', newl='', strip=0,
+ nsprefixes={}, namespace=''):
+ """
+ Serialize this L{Element} to the given stream.
+
+ @param stream: A file-like object to which this L{Element} will be
+ written.
+
+ @param nsprefixes: A C{dict} mapping namespace URIs as C{str} to
+ prefixes as C{str}. This defines the prefixes which are already in
+ scope in the document at the point at which this L{Element} exists.
+ This is essentially an implementation detail for namespace support.
+ Applications should not try to use it.
+
+ @param namespace: The namespace URI as a C{str} which is the default at
+ the point in the document at which this L{Element} exists. This is
+ essentially an implementation detail for namespace support.
+ Applications should not try to use it.
+ """
+ # write beginning
+ ALLOWSINGLETON = ('img', 'br', 'hr', 'base', 'meta', 'link', 'param',
+ 'area', 'input', 'col', 'basefont', 'isindex',
+ 'frame')
+ BLOCKELEMENTS = ('html', 'head', 'body', 'noscript', 'ins', 'del',
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'script',
+ 'ul', 'ol', 'dl', 'pre', 'hr', 'blockquote',
+ 'address', 'p', 'div', 'fieldset', 'table', 'tr',
+ 'form', 'object', 'fieldset', 'applet', 'map')
+ FORMATNICELY = ('tr', 'ul', 'ol', 'head')
+
+ # this should never be necessary unless people start
+ # changing .tagName on the fly(?)
+ if not self.preserveCase:
+ self.endTagName = self.tagName
+ w = stream.write
+ if self.nsprefixes:
+ newprefixes = self.nsprefixes.copy()
+ for ns in nsprefixes.keys():
+ if ns in newprefixes:
+ del newprefixes[ns]
+ else:
+ newprefixes = {}
+
+ begin = ['<']
+ if self.tagName in BLOCKELEMENTS:
+ begin = [newl, indent] + begin
+ bext = begin.extend
+ writeattr = lambda _atr, _val: bext((' ', _atr, '="', escape(_val), '"'))
+
+ # Make a local for tracking what end tag will be used. If namespace
+ # prefixes are involved, this will be changed to account for that
+ # before it's actually used.
+ endTagName = self.endTagName
+
+ if namespace != self.namespace and self.namespace is not None:
+ # If the current default namespace is not the namespace of this tag
+ # (and this tag has a namespace at all) then we'll write out
+ # something related to namespaces.
+ if self.namespace in nsprefixes:
+ # This tag's namespace already has a prefix bound to it. Use
+ # that prefix.
+ prefix = nsprefixes[self.namespace]
+ bext(prefix + ':' + self.tagName)
+ # Also make sure we use it for the end tag.
+ endTagName = prefix + ':' + self.endTagName
+ else:
+ # This tag's namespace has no prefix bound to it. Change the
+ # default namespace to this tag's namespace so we don't need
+ # prefixes. Alternatively, we could add a new prefix binding.
+ # I'm not sure why the code was written one way rather than the
+ # other. -exarkun
+ bext(self.tagName)
+ writeattr("xmlns", self.namespace)
+ # The default namespace just changed. Make sure any children
+ # know about this.
+ namespace = self.namespace
+ else:
+ # This tag has no namespace or its namespace is already the default
+ # namespace. Nothing extra to do here.
+ bext(self.tagName)
+
+ j = ''.join
+ for attr, val in self.attributes.iteritems():
+ if isinstance(attr, tuple):
+ ns, key = attr
+ if nsprefixes.has_key(ns):
+ prefix = nsprefixes[ns]
+ else:
+ prefix = genprefix()
+ newprefixes[ns] = prefix
+ assert val is not None
+ writeattr(prefix+':'+key,val)
+ else:
+ assert val is not None
+ writeattr(attr, val)
+ if newprefixes:
+ for ns, prefix in newprefixes.iteritems():
+ if prefix:
+ writeattr('xmlns:'+prefix, ns)
+ newprefixes.update(nsprefixes)
+ downprefixes = newprefixes
+ else:
+ downprefixes = nsprefixes
+ w(j(begin))
+ if self.childNodes:
+ w(">")
+ newindent = indent + addindent
+ for child in self.childNodes:
+ if self.tagName in BLOCKELEMENTS and \
+ self.tagName in FORMATNICELY:
+ w(j((newl, newindent)))
+ child.writexml(stream, newindent, addindent, newl, strip,
+ downprefixes, namespace)
+ if self.tagName in BLOCKELEMENTS:
+ w(j((newl, indent)))
+ w(j(('</', endTagName, '>')))
+ elif self.tagName.lower() not in ALLOWSINGLETON:
+ w(j(('></', endTagName, '>')))
+ else:
+ w(" />")
+
+
+ def __repr__(self):
+ rep = "Element(%s" % repr(self.nodeName)
+ if self.attributes:
+ rep += ", attributes=%r" % (self.attributes,)
+ if self._filename:
+ rep += ", filename=%r" % (self._filename,)
+ if self._markpos:
+ rep += ", markpos=%r" % (self._markpos,)
+ return rep + ')'
+
+ def __str__(self):
+ rep = "<" + self.nodeName
+ if self._filename or self._markpos:
+ rep += " ("
+ if self._filename:
+ rep += repr(self._filename)
+ if self._markpos:
+ rep += " line %s column %s" % self._markpos
+ if self._filename or self._markpos:
+ rep += ")"
+ for item in self.attributes.items():
+ rep += " %s=%r" % item
+ if self.hasChildNodes():
+ rep += " >...</%s>" % self.nodeName
+ else:
+ rep += " />"
+ return rep
+
+def _unescapeDict(d):
+ dd = {}
+ for k, v in d.items():
+ dd[k] = unescape(v)
+ return dd
+
+def _reverseDict(d):
+ dd = {}
+ for k, v in d.items():
+ dd[v]=k
+ return dd
+
+class MicroDOMParser(XMLParser):
+
+ # <dash> glyph: a quick scan thru the DTD says BODY, AREA, LINK, IMG, HR,
+ # P, DT, DD, LI, INPUT, OPTION, THEAD, TFOOT, TBODY, COLGROUP, COL, TR, TH,
+ # TD, HEAD, BASE, META, HTML all have optional closing tags
+
+ soonClosers = 'area link br img hr input base meta'.split()
+ laterClosers = {'p': ['p', 'dt'],
+ 'dt': ['dt','dd'],
+ 'dd': ['dt', 'dd'],
+ 'li': ['li'],
+ 'tbody': ['thead', 'tfoot', 'tbody'],
+ 'thead': ['thead', 'tfoot', 'tbody'],
+ 'tfoot': ['thead', 'tfoot', 'tbody'],
+ 'colgroup': ['colgroup'],
+ 'col': ['col'],
+ 'tr': ['tr'],
+ 'td': ['td'],
+ 'th': ['th'],
+ 'head': ['body'],
+ 'title': ['head', 'body'], # this looks wrong...
+ 'option': ['option'],
+ }
+
+
+ def __init__(self, beExtremelyLenient=0, caseInsensitive=1, preserveCase=0,
+ soonClosers=soonClosers, laterClosers=laterClosers):
+ self.elementstack = []
+ d = {'xmlns': 'xmlns', '': None}
+ dr = _reverseDict(d)
+ self.nsstack = [(d,None,dr)]
+ self.documents = []
+ self._mddoctype = None
+ self.beExtremelyLenient = beExtremelyLenient
+ self.caseInsensitive = caseInsensitive
+ self.preserveCase = preserveCase or not caseInsensitive
+ self.soonClosers = soonClosers
+ self.laterClosers = laterClosers
+ # self.indentlevel = 0
+
+ def shouldPreserveSpace(self):
+ for edx in xrange(len(self.elementstack)):
+ el = self.elementstack[-edx]
+ if el.tagName == 'pre' or el.getAttribute("xml:space", '') == 'preserve':
+ return 1
+ return 0
+
+ def _getparent(self):
+ if self.elementstack:
+ return self.elementstack[-1]
+ else:
+ return None
+
+ COMMENT = re.compile(r"\s*/[/*]\s*")
+
+ def _fixScriptElement(self, el):
+ # this deals with case where there is comment or CDATA inside
+ # <script> tag and we want to do the right thing with it
+ if not self.beExtremelyLenient or not len(el.childNodes) == 1:
+ return
+ c = el.firstChild()
+ if isinstance(c, Text):
+ # deal with nasty people who do stuff like:
+ # <script> // <!--
+ # x = 1;
+ # // --></script>
+ # tidy does this, for example.
+ prefix = ""
+ oldvalue = c.value
+ match = self.COMMENT.match(oldvalue)
+ if match:
+ prefix = match.group()
+ oldvalue = oldvalue[len(prefix):]
+
+ # now see if contents are actual node and comment or CDATA
+ try:
+ e = parseString("<a>%s</a>" % oldvalue).childNodes[0]
+ except (ParseError, MismatchedTags):
+ return
+ if len(e.childNodes) != 1:
+ return
+ e = e.firstChild()
+ if isinstance(e, (CDATASection, Comment)):
+ el.childNodes = []
+ if prefix:
+ el.childNodes.append(Text(prefix))
+ el.childNodes.append(e)
+
+ def gotDoctype(self, doctype):
+ self._mddoctype = doctype
+
+ def gotTagStart(self, name, attributes):
+ # print ' '*self.indentlevel, 'start tag',name
+ # self.indentlevel += 1
+ parent = self._getparent()
+ if (self.beExtremelyLenient and isinstance(parent, Element)):
+ parentName = parent.tagName
+ myName = name
+ if self.caseInsensitive:
+ parentName = parentName.lower()
+ myName = myName.lower()
+ if myName in self.laterClosers.get(parentName, []):
+ self.gotTagEnd(parent.tagName)
+ parent = self._getparent()
+ attributes = _unescapeDict(attributes)
+ namespaces = self.nsstack[-1][0]
+ newspaces = {}
+ for k, v in attributes.items():
+ if k.startswith('xmlns'):
+ spacenames = k.split(':',1)
+ if len(spacenames) == 2:
+ newspaces[spacenames[1]] = v
+ else:
+ newspaces[''] = v
+ del attributes[k]
+ if newspaces:
+ namespaces = namespaces.copy()
+ namespaces.update(newspaces)
+ for k, v in attributes.items():
+ ksplit = k.split(':', 1)
+ if len(ksplit) == 2:
+ pfx, tv = ksplit
+ if pfx != 'xml' and namespaces.has_key(pfx):
+ attributes[namespaces[pfx], tv] = v
+ del attributes[k]
+ el = Element(name, attributes, parent,
+ self.filename, self.saveMark(),
+ caseInsensitive=self.caseInsensitive,
+ preserveCase=self.preserveCase,
+ namespace=namespaces.get(''))
+ revspaces = _reverseDict(newspaces)
+ el.addPrefixes(revspaces)
+
+ if newspaces:
+ rscopy = self.nsstack[-1][2].copy()
+ rscopy.update(revspaces)
+ self.nsstack.append((namespaces, el, rscopy))
+ self.elementstack.append(el)
+ if parent:
+ parent.appendChild(el)
+ if (self.beExtremelyLenient and el.tagName in self.soonClosers):
+ self.gotTagEnd(name)
+
+ def _gotStandalone(self, factory, data):
+ parent = self._getparent()
+ te = factory(data, parent)
+ if parent:
+ parent.appendChild(te)
+ elif self.beExtremelyLenient:
+ self.documents.append(te)
+
+ def gotText(self, data):
+ if data.strip() or self.shouldPreserveSpace():
+ self._gotStandalone(Text, data)
+
+ def gotComment(self, data):
+ self._gotStandalone(Comment, data)
+
+ def gotEntityReference(self, entityRef):
+ self._gotStandalone(EntityReference, entityRef)
+
+ def gotCData(self, cdata):
+ self._gotStandalone(CDATASection, cdata)
+
+ def gotTagEnd(self, name):
+ # print ' '*self.indentlevel, 'end tag',name
+ # self.indentlevel -= 1
+ if not self.elementstack:
+ if self.beExtremelyLenient:
+ return
+ raise MismatchedTags(*((self.filename, "NOTHING", name)
+ +self.saveMark()+(0,0)))
+ el = self.elementstack.pop()
+ pfxdix = self.nsstack[-1][2]
+ if self.nsstack[-1][1] is el:
+ nstuple = self.nsstack.pop()
+ else:
+ nstuple = None
+ if self.caseInsensitive:
+ tn = el.tagName.lower()
+ cname = name.lower()
+ else:
+ tn = el.tagName
+ cname = name
+
+ nsplit = name.split(':',1)
+ if len(nsplit) == 2:
+ pfx, newname = nsplit
+ ns = pfxdix.get(pfx,None)
+ if ns is not None:
+ if el.namespace != ns:
+ if not self.beExtremelyLenient:
+ raise MismatchedTags(*((self.filename, el.tagName, name)
+ +self.saveMark()+el._markpos))
+ if not (tn == cname):
+ if self.beExtremelyLenient:
+ if self.elementstack:
+ lastEl = self.elementstack[0]
+ for idx in xrange(len(self.elementstack)):
+ if self.elementstack[-(idx+1)].tagName == cname:
+ self.elementstack[-(idx+1)].endTag(name)
+ break
+ else:
+ # this was a garbage close tag; wait for a real one
+ self.elementstack.append(el)
+ if nstuple is not None:
+ self.nsstack.append(nstuple)
+ return
+ del self.elementstack[-(idx+1):]
+ if not self.elementstack:
+ self.documents.append(lastEl)
+ return
+ else:
+ raise MismatchedTags(*((self.filename, el.tagName, name)
+ +self.saveMark()+el._markpos))
+ el.endTag(name)
+ if not self.elementstack:
+ self.documents.append(el)
+ if self.beExtremelyLenient and el.tagName == "script":
+ self._fixScriptElement(el)
+
+ def connectionLost(self, reason):
+ XMLParser.connectionLost(self, reason) # This can cause more events!
+ if self.elementstack:
+ if self.beExtremelyLenient:
+ self.documents.append(self.elementstack[0])
+ else:
+ raise MismatchedTags(*((self.filename, self.elementstack[-1],
+ "END_OF_FILE")
+ +self.saveMark()
+ +self.elementstack[-1]._markpos))
+
+
+def parse(readable, *args, **kwargs):
+ """Parse HTML or XML readable."""
+ if not hasattr(readable, "read"):
+ readable = open(readable, "rb")
+ mdp = MicroDOMParser(*args, **kwargs)
+ mdp.filename = getattr(readable, "name", "<xmlfile />")
+ mdp.makeConnection(None)
+ if hasattr(readable,"getvalue"):
+ mdp.dataReceived(readable.getvalue())
+ else:
+ r = readable.read(1024)
+ while r:
+ mdp.dataReceived(r)
+ r = readable.read(1024)
+ mdp.connectionLost(None)
+
+ if not mdp.documents:
+ raise ParseError(mdp.filename, 0, 0, "No top-level Nodes in document")
+
+ if mdp.beExtremelyLenient:
+ if len(mdp.documents) == 1:
+ d = mdp.documents[0]
+ if not isinstance(d, Element):
+ el = Element("html")
+ el.appendChild(d)
+ d = el
+ else:
+ d = Element("html")
+ for child in mdp.documents:
+ d.appendChild(child)
+ else:
+ d = mdp.documents[0]
+ doc = Document(d)
+ doc.doctype = mdp._mddoctype
+ return doc
+
+def parseString(st, *args, **kw):
+ if isinstance(st, UnicodeType):
+ # this isn't particularly ideal, but it does work.
+ return parse(StringIO(st.encode('UTF-16')), *args, **kw)
+ return parse(StringIO(st), *args, **kw)
+
+
+def parseXML(readable):
+ """Parse an XML readable object."""
+ return parse(readable, caseInsensitive=0, preserveCase=1)
+
+
+def parseXMLString(st):
+ """Parse an XML readable object."""
+ return parseString(st, caseInsensitive=0, preserveCase=1)
+
+
+# Utility
+
+class lmx:
+ """Easy creation of XML."""
+
+ def __init__(self, node='div'):
+ if isinstance(node, StringTypes):
+ node = Element(node)
+ self.node = node
+
+ def __getattr__(self, name):
+ if name[0] == '_':
+ raise AttributeError("no private attrs")
+ return lambda **kw: self.add(name,**kw)
+
+ def __setitem__(self, key, val):
+ self.node.setAttribute(key, val)
+
+ def __getitem__(self, key):
+ return self.node.getAttribute(key)
+
+ def text(self, txt, raw=0):
+ nn = Text(txt, raw=raw)
+ self.node.appendChild(nn)
+ return self
+
+ def add(self, tagName, **kw):
+ newNode = Element(tagName, caseInsensitive=0, preserveCase=0)
+ self.node.appendChild(newNode)
+ xf = lmx(newNode)
+ for k, v in kw.items():
+ if k[0] == '_':
+ k = k[1:]
+ xf[k]=v
+ return xf
diff --git a/vendor/Twisted-10.0.0/twisted/web/proxy.py b/vendor/Twisted-10.0.0/twisted/web/proxy.py
new file mode 100644
index 0000000000..61446782b0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/proxy.py
@@ -0,0 +1,302 @@
+# -*- test-case-name: twisted.web.test.test_proxy -*-
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Simplistic HTTP proxy support.
+
+This comes in two main variants - the Proxy and the ReverseProxy.
+
+When a Proxy is in use, a browser trying to connect to a server (say,
+www.yahoo.com) will be intercepted by the Proxy, and the proxy will covertly
+connect to the server, and return the result.
+
+When a ReverseProxy is in use, the client connects directly to the ReverseProxy
+(say, www.yahoo.com) which farms off the request to one of a pool of servers,
+and returns the result.
+
+Normally, a Proxy is used on the client end of an Internet connection, while a
+ReverseProxy is used on the server end.
+"""
+
+import urlparse
+from urllib import quote as urlquote
+
+from twisted.internet import reactor
+from twisted.internet.protocol import ClientFactory
+from twisted.web.resource import Resource
+from twisted.web.server import NOT_DONE_YET
+from twisted.web.http import HTTPClient, Request, HTTPChannel
+
+
+
+class ProxyClient(HTTPClient):
+ """
+ Used by ProxyClientFactory to implement a simple web proxy.
+
+ @ivar _finished: A flag which indicates whether or not the original request
+ has been finished yet.
+ """
+ _finished = False
+
+ def __init__(self, command, rest, version, headers, data, father):
+ self.father = father
+ self.command = command
+ self.rest = rest
+ if "proxy-connection" in headers:
+ del headers["proxy-connection"]
+ headers["connection"] = "close"
+ headers.pop('keep-alive', None)
+ self.headers = headers
+ self.data = data
+
+
+ def connectionMade(self):
+ self.sendCommand(self.command, self.rest)
+ for header, value in self.headers.items():
+ self.sendHeader(header, value)
+ self.endHeaders()
+ self.transport.write(self.data)
+
+
+ def handleStatus(self, version, code, message):
+ self.father.setResponseCode(int(code), message)
+
+
+ def handleHeader(self, key, value):
+ # t.web.server.Request sets default values for these headers in its
+ # 'process' method. When these headers are received from the remote
+ # server, they ought to override the defaults, rather than append to
+ # them.
+ if key.lower() in ['server', 'date', 'content-type']:
+ self.father.responseHeaders.setRawHeaders(key, [value])
+ else:
+ self.father.responseHeaders.addRawHeader(key, value)
+
+
+ def handleResponsePart(self, buffer):
+ self.father.write(buffer)
+
+
+ def handleResponseEnd(self):
+ """
+ Finish the original request, indicating that the response has been
+ completely written to it, and disconnect the outgoing transport.
+ """
+ if not self._finished:
+ self._finished = True
+ self.father.finish()
+ self.transport.loseConnection()
+
+
+
+class ProxyClientFactory(ClientFactory):
+ """
+ Used by ProxyRequest to implement a simple web proxy.
+ """
+
+ protocol = ProxyClient
+
+
+ def __init__(self, command, rest, version, headers, data, father):
+ self.father = father
+ self.command = command
+ self.rest = rest
+ self.headers = headers
+ self.data = data
+ self.version = version
+
+
+ def buildProtocol(self, addr):
+ return self.protocol(self.command, self.rest, self.version,
+ self.headers, self.data, self.father)
+
+
+ def clientConnectionFailed(self, connector, reason):
+ """
+ Report a connection failure in a response to the incoming request as
+ an error.
+ """
+ self.father.setResponseCode(501, "Gateway error")
+ self.father.responseHeaders.addRawHeader("Content-Type", "text/html")
+ self.father.write("<H1>Could not connect</H1>")
+ self.father.finish()
+
+
+
+class ProxyRequest(Request):
+ """
+ Used by Proxy to implement a simple web proxy.
+
+ @ivar reactor: the reactor used to create connections.
+ @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
+ """
+
+ protocols = {'http': ProxyClientFactory}
+ ports = {'http': 80}
+
+ def __init__(self, channel, queued, reactor=reactor):
+ Request.__init__(self, channel, queued)
+ self.reactor = reactor
+
+
+ def process(self):
+ parsed = urlparse.urlparse(self.uri)
+ protocol = parsed[0]
+ host = parsed[1]
+ port = self.ports[protocol]
+ if ':' in host:
+ host, port = host.split(':')
+ port = int(port)
+ rest = urlparse.urlunparse(('', '') + parsed[2:])
+ if not rest:
+ rest = rest + '/'
+ class_ = self.protocols[protocol]
+ headers = self.getAllHeaders().copy()
+ if 'host' not in headers:
+ headers['host'] = host
+ self.content.seek(0, 0)
+ s = self.content.read()
+ clientFactory = class_(self.method, rest, self.clientproto, headers,
+ s, self)
+ self.reactor.connectTCP(host, port, clientFactory)
+
+
+
+class Proxy(HTTPChannel):
+ """
+ This class implements a simple web proxy.
+
+ Since it inherits from L{twisted.protocols.http.HTTPChannel}, to use it you
+ should do something like this::
+
+ from twisted.web import http
+ f = http.HTTPFactory()
+ f.protocol = Proxy
+
+ Make the HTTPFactory a listener on a port as per usual, and you have
+ a fully-functioning web proxy!
+ """
+
+ requestFactory = ProxyRequest
+
+
+
+class ReverseProxyRequest(Request):
+ """
+ Used by ReverseProxy to implement a simple reverse proxy.
+
+ @ivar proxyClientFactoryClass: a proxy client factory class, used to create
+ new connections.
+ @type proxyClientFactoryClass: L{ClientFactory}
+
+ @ivar reactor: the reactor used to create connections.
+ @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
+ """
+
+ proxyClientFactoryClass = ProxyClientFactory
+
+ def __init__(self, channel, queued, reactor=reactor):
+ Request.__init__(self, channel, queued)
+ self.reactor = reactor
+
+
+ def process(self):
+ """
+ Handle this request by connecting to the proxied server and forwarding
+ it there, then forwarding the response back as the response to this
+ request.
+ """
+ self.received_headers['host'] = self.factory.host
+ clientFactory = self.proxyClientFactoryClass(
+ self.method, self.uri, self.clientproto, self.getAllHeaders(),
+ self.content.read(), self)
+ self.reactor.connectTCP(self.factory.host, self.factory.port,
+ clientFactory)
+
+
+
+class ReverseProxy(HTTPChannel):
+ """
+ Implements a simple reverse proxy.
+
+ For details of usage, see the file examples/proxy.py.
+ """
+
+ requestFactory = ReverseProxyRequest
+
+
+
+class ReverseProxyResource(Resource):
+ """
+ Resource that renders the results gotten from another server
+
+ Put this resource in the tree to cause everything below it to be relayed
+ to a different server.
+
+ @ivar proxyClientFactoryClass: a proxy client factory class, used to create
+ new connections.
+ @type proxyClientFactoryClass: L{ClientFactory}
+
+ @ivar reactor: the reactor used to create connections.
+ @type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
+ """
+
+ proxyClientFactoryClass = ProxyClientFactory
+
+
+ def __init__(self, host, port, path, reactor=reactor):
+ """
+ @param host: the host of the web server to proxy.
+ @type host: C{str}
+
+ @param port: the port of the web server to proxy.
+ @type port: C{port}
+
+ @param path: the base path to fetch data from. Note that you shouldn't
+ put any trailing slashes in it, it will be added automatically in
+ request. For example, if you put B{/foo}, a request on B{/bar} will
+ be proxied to B{/foo/bar}. Any required encoding of special
+ characters (such as " " or "/") should have been done already.
+
+ @type path: C{str}
+ """
+ Resource.__init__(self)
+ self.host = host
+ self.port = port
+ self.path = path
+ self.reactor = reactor
+
+
+ def getChild(self, path, request):
+ """
+ Create and return a proxy resource with the same proxy configuration
+ as this one, except that its path also contains the segment given by
+ C{path} at the end.
+ """
+ return ReverseProxyResource(
+ self.host, self.port, self.path + '/' + urlquote(path, safe=""))
+
+
+ def render(self, request):
+ """
+ Render a request by forwarding it to the proxied server.
+ """
+ # RFC 2616 tells us that we can omit the port if it's the default port,
+ # but we have to provide it otherwise
+ if self.port == 80:
+ host = self.host
+ else:
+ host = "%s:%d" % (self.host, self.port)
+ request.received_headers['host'] = host
+ request.content.seek(0, 0)
+ qs = urlparse.urlparse(request.uri)[4]
+ if qs:
+ rest = self.path + '?' + qs
+ else:
+ rest = self.path
+ clientFactory = self.proxyClientFactoryClass(
+ request.method, rest, request.clientproto,
+ request.getAllHeaders(), request.content.read(), request)
+ self.reactor.connectTCP(self.host, self.port, clientFactory)
+ return NOT_DONE_YET
diff --git a/vendor/Twisted-10.0.0/twisted/web/resource.py b/vendor/Twisted-10.0.0/twisted/web/resource.py
new file mode 100644
index 0000000000..afd1ea5c71
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/resource.py
@@ -0,0 +1,300 @@
+# -*- test-case-name: twisted.web.test.test_web -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implementation of the lowest-level Resource class.
+"""
+
+import warnings
+
+from zope.interface import Attribute, implements, Interface
+
+from twisted.web import http
+
+
+class IResource(Interface):
+ """
+ A web resource.
+ """
+
+ isLeaf = Attribute(
+ """
+ Signal if this IResource implementor is a "leaf node" or not. If True,
+ getChildWithDefault will not be called on this Resource.
+ """)
+
+ def getChildWithDefault(name, request):
+ """
+ Return a child with the given name for the given request.
+ This is the external interface used by the Resource publishing
+ machinery. If implementing IResource without subclassing
+ Resource, it must be provided. However, if subclassing Resource,
+ getChild overridden instead.
+ """
+
+ def putChild(path, child):
+ """
+ Put a child IResource implementor at the given path.
+ """
+
+ def render(request):
+ """
+ Render a request. This is called on the leaf resource for
+ a request. Render must return either a string, which will
+ be sent to the browser as the HTML for the request, or
+ server.NOT_DONE_YET. If NOT_DONE_YET is returned,
+ at some point later (in a Deferred callback, usually)
+ call request.write("<html>") to write data to the request,
+ and request.finish() to send the data to the browser.
+ """
+
+
+
+def getChildForRequest(resource, request):
+ """
+ Traverse resource tree to find who will handle the request.
+ """
+ while request.postpath and not resource.isLeaf:
+ pathElement = request.postpath.pop(0)
+ request.prepath.append(pathElement)
+ resource = resource.getChildWithDefault(pathElement, request)
+ return resource
+
+
+
+class Resource:
+ """
+ I define a web-accessible resource.
+
+ I serve 2 main purposes; one is to provide a standard representation for
+ what HTTP specification calls an 'entity', and the other is to provide an
+ abstract directory structure for URL retrieval.
+ """
+
+ implements(IResource)
+
+ entityType = IResource
+
+ server = None
+
+ def __init__(self):
+ """Initialize.
+ """
+ self.children = {}
+
+ isLeaf = 0
+
+ ### Abstract Collection Interface
+
+ def listStaticNames(self):
+ return self.children.keys()
+
+ def listStaticEntities(self):
+ return self.children.items()
+
+ def listNames(self):
+ return self.listStaticNames() + self.listDynamicNames()
+
+ def listEntities(self):
+ return self.listStaticEntities() + self.listDynamicEntities()
+
+ def listDynamicNames(self):
+ return []
+
+ def listDynamicEntities(self, request=None):
+ return []
+
+ def getStaticEntity(self, name):
+ return self.children.get(name)
+
+ def getDynamicEntity(self, name, request):
+ if not self.children.has_key(name):
+ return self.getChild(name, request)
+ else:
+ return None
+
+ def delEntity(self, name):
+ del self.children[name]
+
+ def reallyPutEntity(self, name, entity):
+ self.children[name] = entity
+
+ # Concrete HTTP interface
+
+ def getChild(self, path, request):
+ """
+ Retrieve a 'child' resource from me.
+
+ Implement this to create dynamic resource generation -- resources which
+ are always available may be registered with self.putChild().
+
+ This will not be called if the class-level variable 'isLeaf' is set in
+ your subclass; instead, the 'postpath' attribute of the request will be
+ left as a list of the remaining path elements.
+
+ For example, the URL /foo/bar/baz will normally be::
+
+ | site.resource.getChild('foo').getChild('bar').getChild('baz').
+
+ However, if the resource returned by 'bar' has isLeaf set to true, then
+ the getChild call will never be made on it.
+
+ @param path: a string, describing the child
+
+ @param request: a twisted.web.server.Request specifying meta-information
+ about the request that is being made for this child.
+ """
+ return NoResource("No such child resource.")
+
+
+ def getChildWithDefault(self, path, request):
+ """
+ Retrieve a static or dynamically generated child resource from me.
+
+ First checks if a resource was added manually by putChild, and then
+ call getChild to check for dynamic resources. Only override if you want
+ to affect behaviour of all child lookups, rather than just dynamic
+ ones.
+
+ This will check to see if I have a pre-registered child resource of the
+ given name, and call getChild if I do not.
+ """
+ if path in self.children:
+ return self.children[path]
+ return self.getChild(path, request)
+
+
+ def getChildForRequest(self, request):
+ warnings.warn("Please use module level getChildForRequest.", DeprecationWarning, 2)
+ return getChildForRequest(self, request)
+
+
+ def putChild(self, path, child):
+ """
+ Register a static child.
+
+ You almost certainly don't want '/' in your path. If you
+ intended to have the root of a folder, e.g. /foo/, you want
+ path to be ''.
+ """
+ self.children[path] = child
+ child.server = self.server
+
+
+ def render(self, request):
+ """
+ Render a given resource. See L{IResource}'s render method.
+
+ I delegate to methods of self with the form 'render_METHOD'
+ where METHOD is the HTTP that was used to make the
+ request. Examples: render_GET, render_HEAD, render_POST, and
+ so on. Generally you should implement those methods instead of
+ overriding this one.
+
+ render_METHOD methods are expected to return a string which
+ will be the rendered page, unless the return value is
+ twisted.web.server.NOT_DONE_YET, in which case it is this
+ class's responsibility to write the results to
+ request.write(data), then call request.finish().
+
+ Old code that overrides render() directly is likewise expected
+ to return a string or NOT_DONE_YET.
+ """
+ m = getattr(self, 'render_' + request.method, None)
+ if not m:
+ # This needs to be here until the deprecated subclasses of the
+ # below three error resources in twisted.web.error are removed.
+ from twisted.web.error import UnsupportedMethod
+ raise UnsupportedMethod(getattr(self, 'allowedMethods', ()))
+ return m(request)
+
+
+ def render_HEAD(self, request):
+ """
+ Default handling of HEAD method.
+
+ I just return self.render_GET(request). When method is HEAD,
+ the framework will handle this correctly.
+ """
+ return self.render_GET(request)
+
+
+
+class ErrorPage(Resource):
+ """
+ L{ErrorPage} is a resource which responds with a particular
+ (parameterized) status and a body consisting of HTML containing some
+ descriptive text. This is useful for rendering simple error pages.
+
+ @ivar template: A C{str} which will have a dictionary interpolated into
+ it to generate the response body. The dictionary has the following
+ keys:
+
+ - C{"code"}: The status code passed to L{ErrorPage.__init__}.
+ - C{"brief"}: The brief description passed to L{ErrorPage.__init__}.
+ - C{"detail"}: The detailed description passed to
+ L{ErrorPage.__init__}.
+
+ @ivar code: An integer status code which will be used for the response.
+ @ivar brief: A short string which will be included in the response body.
+ @ivar detail: A longer string which will be included in the response body.
+ """
+
+ template = """
+<html>
+ <head><title>%(code)s - %(brief)s</title></head>
+ <body>
+ <h1>%(brief)s</h1>
+ <p>%(detail)s</p>
+ </body>
+</html>
+"""
+
+ def __init__(self, status, brief, detail):
+ Resource.__init__(self)
+ self.code = status
+ self.brief = brief
+ self.detail = detail
+
+
+ def render(self, request):
+ request.setResponseCode(self.code)
+ request.setHeader("content-type", "text/html")
+ return self.template % dict(
+ code=self.code,
+ brief=self.brief,
+ detail=self.detail)
+
+
+ def getChild(self, chnam, request):
+ return self
+
+
+
+class NoResource(ErrorPage):
+ """
+ L{NoResource} is a specialization of L{ErrorPage} which returns the HTTP
+ response code I{NOT FOUND}.
+ """
+ def __init__(self, message="Sorry. No luck finding that resource."):
+ ErrorPage.__init__(self, http.NOT_FOUND,
+ "No Such Resource",
+ message)
+
+
+
+class ForbiddenResource(ErrorPage):
+ """
+ L{ForbiddenResource} is a specialization of L{ErrorPage} which returns the
+ I{FORBIDDEN} HTTP response code.
+ """
+ def __init__(self, message="Sorry, resource is forbidden."):
+ ErrorPage.__init__(self, http.FORBIDDEN,
+ "Forbidden Resource",
+ message)
+
+
+__all__ = [
+ 'IResource', 'getChildForRequest',
+ 'Resource', 'ErrorPage', 'NoResource', 'ForbiddenResource']
diff --git a/vendor/Twisted-10.0.0/twisted/web/rewrite.py b/vendor/Twisted-10.0.0/twisted/web/rewrite.py
new file mode 100644
index 0000000000..b41ca00347
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/rewrite.py
@@ -0,0 +1,52 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.web import resource
+
+class RewriterResource(resource.Resource):
+
+ def __init__(self, orig, *rewriteRules):
+ resource.Resource.__init__(self)
+ self.resource = orig
+ self.rewriteRules = list(rewriteRules)
+
+ def _rewrite(self, request):
+ for rewriteRule in self.rewriteRules:
+ rewriteRule(request)
+
+ def getChild(self, path, request):
+ request.postpath.insert(0, path)
+ request.prepath.pop()
+ self._rewrite(request)
+ path = request.postpath.pop(0)
+ request.prepath.append(path)
+ return self.resource.getChildWithDefault(path, request)
+
+ def render(self, request):
+ self._rewrite(request)
+ return self.resource.render(request)
+
+
+def tildeToUsers(request):
+ if request.postpath and request.postpath[0][:1]=='~':
+ request.postpath[:1] = ['users', request.postpath[0][1:]]
+ request.path = '/'+'/'.join(request.prepath+request.postpath)
+
+def alias(aliasPath, sourcePath):
+ """
+ I am not a very good aliaser. But I'm the best I can be. If I'm
+ aliasing to a Resource that generates links, and it uses any parts
+ of request.prepath to do so, the links will not be relative to the
+ aliased path, but rather to the aliased-to path. That I can't
+ alias static.File directory listings that nicely. However, I can
+ still be useful, as many resources will play nice.
+ """
+ sourcePath = sourcePath.split('/')
+ aliasPath = aliasPath.split('/')
+ def rewriter(request):
+ if request.postpath[:len(aliasPath)] == aliasPath:
+ after = request.postpath[len(aliasPath):]
+ request.postpath = sourcePath + after
+ request.path = '/'+'/'.join(request.prepath+request.postpath)
+ return rewriter
diff --git a/vendor/Twisted-10.0.0/twisted/web/script.py b/vendor/Twisted-10.0.0/twisted/web/script.py
new file mode 100644
index 0000000000..c1e41da0f6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/script.py
@@ -0,0 +1,169 @@
+# -*- test-case-name: twisted.web.test.test_script -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+I contain PythonScript, which is a very simple python script resource.
+"""
+
+import os, traceback
+
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+from twisted import copyright
+from twisted.web import http, server, static, resource, html
+
+
+rpyNoResource = """<p>You forgot to assign to the variable "resource" in your script. For example:</p>
+<pre>
+# MyCoolWebApp.rpy
+
+import mygreatresource
+
+resource = mygreatresource.MyGreatResource()
+</pre>
+"""
+
+class AlreadyCached(Exception):
+ """This exception is raised when a path has already been cached.
+ """
+
+class CacheScanner:
+ def __init__(self, path, registry):
+ self.path = path
+ self.registry = registry
+ self.doCache = 0
+
+ def cache(self):
+ c = self.registry.getCachedPath(self.path)
+ if c is not None:
+ raise AlreadyCached(c)
+ self.recache()
+
+ def recache(self):
+ self.doCache = 1
+
+noRsrc = resource.ErrorPage(500, "Whoops! Internal Error", rpyNoResource)
+
+def ResourceScript(path, registry):
+ """
+ I am a normal py file which must define a 'resource' global, which should
+ be an instance of (a subclass of) web.resource.Resource; it will be
+ renderred.
+ """
+ cs = CacheScanner(path, registry)
+ glob = {'__file__': path,
+ 'resource': noRsrc,
+ 'registry': registry,
+ 'cache': cs.cache,
+ 'recache': cs.recache}
+ try:
+ execfile(path, glob, glob)
+ except AlreadyCached, ac:
+ return ac.args[0]
+ rsrc = glob['resource']
+ if cs.doCache and rsrc is not noRsrc:
+ registry.cachePath(path, rsrc)
+ return rsrc
+
+def ResourceTemplate(path, registry):
+ from quixote import ptl_compile
+
+ glob = {'__file__': path,
+ 'resource': resource.ErrorPage(500, "Whoops! Internal Error",
+ rpyNoResource),
+ 'registry': registry}
+
+ e = ptl_compile.compile_template(open(path), path)
+ exec e in glob
+ return glob['resource']
+
+
+class ResourceScriptWrapper(resource.Resource):
+
+ def __init__(self, path, registry=None):
+ resource.Resource.__init__(self)
+ self.path = path
+ self.registry = registry or static.Registry()
+
+ def render(self, request):
+ res = ResourceScript(self.path, self.registry)
+ return res.render(request)
+
+ def getChildWithDefault(self, path, request):
+ res = ResourceScript(self.path, self.registry)
+ return res.getChildWithDefault(path, request)
+
+
+
+class ResourceScriptDirectory(resource.Resource):
+ """
+ L{ResourceScriptDirectory} is a resource which serves scripts from a
+ filesystem directory. File children of a L{ResourceScriptDirectory} will
+ be served using L{ResourceScript}. Directory children will be served using
+ another L{ResourceScriptDirectory}.
+
+ @ivar path: A C{str} giving the filesystem path in which children will be
+ looked up.
+
+ @ivar registry: A L{static.Registry} instance which will be used to decide
+ how to interpret scripts found as children of this resource.
+ """
+ def __init__(self, pathname, registry=None):
+ resource.Resource.__init__(self)
+ self.path = pathname
+ self.registry = registry or static.Registry()
+
+ def getChild(self, path, request):
+ fn = os.path.join(self.path, path)
+
+ if os.path.isdir(fn):
+ return ResourceScriptDirectory(fn, self.registry)
+ if os.path.exists(fn):
+ return ResourceScript(fn, self.registry)
+ return resource.NoResource()
+
+ def render(self, request):
+ return resource.NoResource().render(request)
+
+
+class PythonScript(resource.Resource):
+ """I am an extremely simple dynamic resource; an embedded python script.
+
+ This will execute a file (usually of the extension '.epy') as Python code,
+ internal to the webserver.
+ """
+ isLeaf = 1
+ def __init__(self, filename, registry):
+ """Initialize me with a script name.
+ """
+ self.filename = filename
+ self.registry = registry
+
+ def render(self, request):
+ """Render me to a web client.
+
+ Load my file, execute it in a special namespace (with 'request' and
+ '__file__' global vars) and finish the request. Output to the web-page
+ will NOT be handled with print - standard output goes to the log - but
+ with request.write.
+ """
+ request.setHeader("x-powered-by","Twisted/%s" % copyright.version)
+ namespace = {'request': request,
+ '__file__': self.filename,
+ 'registry': self.registry}
+ try:
+ execfile(self.filename, namespace, namespace)
+ except IOError, e:
+ if e.errno == 2: #file not found
+ request.setResponseCode(http.NOT_FOUND)
+ request.write(resource.NoResource("File not found.").render(request))
+ except:
+ io = StringIO.StringIO()
+ traceback.print_exc(file=io)
+ request.write(html.PRE(io.getvalue()))
+ request.finish()
+ return server.NOT_DONE_YET
diff --git a/vendor/Twisted-10.0.0/twisted/web/server.py b/vendor/Twisted-10.0.0/twisted/web/server.py
new file mode 100644
index 0000000000..2e9eabfb28
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/server.py
@@ -0,0 +1,527 @@
+# -*- test-case-name: twisted.web.test.test_web -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+This is a web-server which integrates with the twisted.internet
+infrastructure.
+"""
+
+# System Imports
+
+import warnings
+import string
+import types
+import copy
+import os
+from urllib import quote
+
+from zope.interface import implements
+
+try:
+ from twisted.protocols._c_urlarg import unquote
+except ImportError:
+ from urllib import unquote
+
+#some useful constants
+NOT_DONE_YET = 1
+
+# Twisted Imports
+from twisted.spread import pb
+from twisted.internet import defer, address, task
+from twisted.web import iweb, http
+from twisted.python import log, reflect, failure, components
+from twisted import copyright
+from twisted.web import util as webutil, resource
+from twisted.web.error import UnsupportedMethod
+
+# backwards compatability
+date_time_string = http.datetimeToString
+string_date_time = http.stringToDatetime
+
+# Support for other methods may be implemented on a per-resource basis.
+supportedMethods = ('GET', 'HEAD', 'POST')
+
+
+def _addressToTuple(addr):
+ if isinstance(addr, address.IPv4Address):
+ return ('INET', addr.host, addr.port)
+ elif isinstance(addr, address.UNIXAddress):
+ return ('UNIX', addr.name)
+ else:
+ return tuple(addr)
+
+class Request(pb.Copyable, http.Request, components.Componentized):
+ implements(iweb.IRequest)
+
+ site = None
+ appRootURL = None
+ __pychecker__ = 'unusednames=issuer'
+
+ def __init__(self, *args, **kw):
+ http.Request.__init__(self, *args, **kw)
+ components.Componentized.__init__(self)
+
+ def getStateToCopyFor(self, issuer):
+ x = self.__dict__.copy()
+ del x['transport']
+ # XXX refactor this attribute out; it's from protocol
+ # del x['server']
+ del x['channel']
+ del x['content']
+ del x['site']
+ self.content.seek(0, 0)
+ x['content_data'] = self.content.read()
+ x['remote'] = pb.ViewPoint(issuer, self)
+
+ # Address objects aren't jellyable
+ x['host'] = _addressToTuple(x['host'])
+ x['client'] = _addressToTuple(x['client'])
+
+ # Header objects also aren't jellyable.
+ x['requestHeaders'] = list(x['requestHeaders'].getAllRawHeaders())
+
+ return x
+
+ # HTML generation helpers
+
+ def sibLink(self, name):
+ "Return the text that links to a sibling of the requested resource."
+ if self.postpath:
+ return (len(self.postpath)*"../") + name
+ else:
+ return name
+
+ def childLink(self, name):
+ "Return the text that links to a child of the requested resource."
+ lpp = len(self.postpath)
+ if lpp > 1:
+ return ((lpp-1)*"../") + name
+ elif lpp == 1:
+ return name
+ else: # lpp == 0
+ if len(self.prepath) and self.prepath[-1]:
+ return self.prepath[-1] + '/' + name
+ else:
+ return name
+
+ def process(self):
+ "Process a request."
+
+ # get site from channel
+ self.site = self.channel.site
+
+ # set various default headers
+ self.setHeader('server', version)
+ self.setHeader('date', http.datetimeToString())
+ self.setHeader('content-type', "text/html")
+
+ # Resource Identification
+ self.prepath = []
+ self.postpath = map(unquote, string.split(self.path[1:], '/'))
+ try:
+ resrc = self.site.getResourceFor(self)
+ self.render(resrc)
+ except:
+ self.processingFailed(failure.Failure())
+
+
+ def render(self, resrc):
+ try:
+ body = resrc.render(self)
+ except UnsupportedMethod, e:
+ allowedMethods = e.allowedMethods
+ if (self.method == "HEAD") and ("GET" in allowedMethods):
+ # We must support HEAD (RFC 2616, 5.1.1). If the
+ # resource doesn't, fake it by giving the resource
+ # a 'GET' request and then return only the headers,
+ # not the body.
+ log.msg("Using GET to fake a HEAD request for %s" %
+ (resrc,))
+ self.method = "GET"
+ body = resrc.render(self)
+
+ if body is NOT_DONE_YET:
+ log.msg("Tried to fake a HEAD request for %s, but "
+ "it got away from me." % resrc)
+ # Oh well, I guess we won't include the content length.
+ else:
+ self.setHeader('content-length', str(len(body)))
+
+ self.write('')
+ self.finish()
+ return
+
+ if self.method in (supportedMethods):
+ # We MUST include an Allow header
+ # (RFC 2616, 10.4.6 and 14.7)
+ self.setHeader('Allow', allowedMethods)
+ s = ('''Your browser approached me (at %(URI)s) with'''
+ ''' the method "%(method)s". I only allow'''
+ ''' the method%(plural)s %(allowed)s here.''' % {
+ 'URI': self.uri,
+ 'method': self.method,
+ 'plural': ((len(allowedMethods) > 1) and 's') or '',
+ 'allowed': string.join(allowedMethods, ', ')
+ })
+ epage = resource.ErrorPage(http.NOT_ALLOWED,
+ "Method Not Allowed", s)
+ body = epage.render(self)
+ else:
+ epage = resource.ErrorPage(http.NOT_IMPLEMENTED, "Huh?",
+ "I don't know how to treat a"
+ " %s request." % (self.method,))
+ body = epage.render(self)
+ # end except UnsupportedMethod
+
+ if body == NOT_DONE_YET:
+ return
+ if type(body) is not types.StringType:
+ body = resource.ErrorPage(
+ http.INTERNAL_SERVER_ERROR,
+ "Request did not return a string",
+ "Request: " + html.PRE(reflect.safe_repr(self)) + "<br />" +
+ "Resource: " + html.PRE(reflect.safe_repr(resrc)) + "<br />" +
+ "Value: " + html.PRE(reflect.safe_repr(body))).render(self)
+
+ if self.method == "HEAD":
+ if len(body) > 0:
+ # This is a Bad Thing (RFC 2616, 9.4)
+ log.msg("Warning: HEAD request %s for resource %s is"
+ " returning a message body."
+ " I think I'll eat it."
+ % (self, resrc))
+ self.setHeader('content-length', str(len(body)))
+ self.write('')
+ else:
+ self.setHeader('content-length', str(len(body)))
+ self.write(body)
+ self.finish()
+
+ def processingFailed(self, reason):
+ log.err(reason)
+ if self.site.displayTracebacks:
+ body = ("<html><head><title>web.Server Traceback (most recent call last)</title></head>"
+ "<body><b>web.Server Traceback (most recent call last):</b>\n\n"
+ "%s\n\n</body></html>\n"
+ % webutil.formatFailure(reason))
+ else:
+ body = ("<html><head><title>Processing Failed</title></head><body>"
+ "<b>Processing Failed</b></body></html>")
+
+ self.setResponseCode(http.INTERNAL_SERVER_ERROR)
+ self.setHeader('content-type',"text/html")
+ self.setHeader('content-length', str(len(body)))
+ self.write(body)
+ self.finish()
+ return reason
+
+ def view_write(self, issuer, data):
+ """Remote version of write; same interface.
+ """
+ self.write(data)
+
+ def view_finish(self, issuer):
+ """Remote version of finish; same interface.
+ """
+ self.finish()
+
+ def view_addCookie(self, issuer, k, v, **kwargs):
+ """Remote version of addCookie; same interface.
+ """
+ self.addCookie(k, v, **kwargs)
+
+ def view_setHeader(self, issuer, k, v):
+ """Remote version of setHeader; same interface.
+ """
+ self.setHeader(k, v)
+
+ def view_setLastModified(self, issuer, when):
+ """Remote version of setLastModified; same interface.
+ """
+ self.setLastModified(when)
+
+ def view_setETag(self, issuer, tag):
+ """Remote version of setETag; same interface.
+ """
+ self.setETag(tag)
+
+ def view_setResponseCode(self, issuer, code):
+ """Remote version of setResponseCode; same interface.
+ """
+ self.setResponseCode(code)
+
+ def view_registerProducer(self, issuer, producer, streaming):
+ """Remote version of registerProducer; same interface.
+ (requires a remote producer.)
+ """
+ self.registerProducer(_RemoteProducerWrapper(producer), streaming)
+
+ def view_unregisterProducer(self, issuer):
+ self.unregisterProducer()
+
+ ### these calls remain local
+
+ session = None
+
+ def getSession(self, sessionInterface = None):
+ # Session management
+ if not self.session:
+ cookiename = string.join(['TWISTED_SESSION'] + self.sitepath, "_")
+ sessionCookie = self.getCookie(cookiename)
+ if sessionCookie:
+ try:
+ self.session = self.site.getSession(sessionCookie)
+ except KeyError:
+ pass
+ # if it still hasn't been set, fix it up.
+ if not self.session:
+ self.session = self.site.makeSession()
+ self.addCookie(cookiename, self.session.uid, path='/')
+ self.session.touch()
+ if sessionInterface:
+ return self.session.getComponent(sessionInterface)
+ return self.session
+
+ def _prePathURL(self, prepath):
+ port = self.getHost().port
+ if self.isSecure():
+ default = 443
+ else:
+ default = 80
+ if port == default:
+ hostport = ''
+ else:
+ hostport = ':%d' % port
+ return 'http%s://%s%s/%s' % (
+ self.isSecure() and 's' or '',
+ self.getRequestHostname(),
+ hostport,
+ '/'.join([quote(segment, safe='') for segment in prepath]))
+
+ def prePathURL(self):
+ return self._prePathURL(self.prepath)
+
+ def URLPath(self):
+ from twisted.python import urlpath
+ return urlpath.URLPath.fromRequest(self)
+
+ def rememberRootURL(self):
+ """
+ Remember the currently-processed part of the URL for later
+ recalling.
+ """
+ url = self._prePathURL(self.prepath[:-1])
+ self.appRootURL = url
+
+ def getRootURL(self):
+ """
+ Get a previously-remembered URL.
+ """
+ return self.appRootURL
+
+
+class _RemoteProducerWrapper:
+ def __init__(self, remote):
+ self.resumeProducing = remote.remoteMethod("resumeProducing")
+ self.pauseProducing = remote.remoteMethod("pauseProducing")
+ self.stopProducing = remote.remoteMethod("stopProducing")
+
+
+class Session(components.Componentized):
+ """
+ A user's session with a system.
+
+ This utility class contains no functionality, but is used to
+ represent a session.
+
+ @ivar _reactor: An object providing L{IReactorTime} to use for scheduling
+ expiration.
+ @ivar sessionTimeout: timeout of a session, in seconds.
+ @ivar loopFactory: Deprecated in Twisted 9.0. Does nothing. Do not use.
+ """
+ sessionTimeout = 900
+ loopFactory = task.LoopingCall
+
+ _expireCall = None
+
+ def __init__(self, site, uid, reactor=None):
+ """
+ Initialize a session with a unique ID for that session.
+ """
+ components.Componentized.__init__(self)
+
+ if reactor is None:
+ from twisted.internet import reactor
+ self._reactor = reactor
+
+ self.site = site
+ self.uid = uid
+ self.expireCallbacks = []
+ self.touch()
+ self.sessionNamespaces = {}
+
+
+ def startCheckingExpiration(self, lifetime=None):
+ """
+ Start expiration tracking.
+
+ @param lifetime: Ignored; deprecated.
+
+ @return: C{None}
+ """
+ if lifetime is not None:
+ warnings.warn(
+ "The lifetime parameter to startCheckingExpiration is "
+ "deprecated since Twisted 9.0. See Session.sessionTimeout "
+ "instead.", DeprecationWarning, stacklevel=2)
+ self._expireCall = self._reactor.callLater(
+ self.sessionTimeout, self.expire)
+
+
+ def notifyOnExpire(self, callback):
+ """
+ Call this callback when the session expires or logs out.
+ """
+ self.expireCallbacks.append(callback)
+
+
+ def expire(self):
+ """
+ Expire/logout of the session.
+ """
+ del self.site.sessions[self.uid]
+ for c in self.expireCallbacks:
+ c()
+ self.expireCallbacks = []
+ if self._expireCall and self._expireCall.active():
+ self._expireCall.cancel()
+ # Break reference cycle.
+ self._expireCall = None
+
+
+ def touch(self):
+ """
+ Notify session modification.
+ """
+ self.lastModified = self._reactor.seconds()
+ if self._expireCall is not None:
+ self._expireCall.reset(self.sessionTimeout)
+
+
+ def checkExpired(self):
+ """
+ Deprecated; does nothing.
+ """
+ warnings.warn(
+ "Session.checkExpired is deprecated since Twisted 9.0; sessions "
+ "check themselves now, you don't need to.",
+ stacklevel=2, category=DeprecationWarning)
+
+
+version = "TwistedWeb/%s" % copyright.version
+
+
+class Site(http.HTTPFactory):
+ """
+ A web site: manage log, sessions, and resources.
+
+ @ivar counter: increment value used for generating unique sessions ID.
+ @ivar requestFactory: factory creating requests objects. Default to
+ L{Request}.
+ @ivar displayTracebacks: if set, Twisted internal errors are displayed on
+ rendered pages. Default to C{True}.
+ @ivar sessionFactory: factory for sessions objects. Default to L{Session}.
+ @ivar sessionCheckTime: Deprecated. See L{Session.sessionTimeout} instead.
+ """
+ counter = 0
+ requestFactory = Request
+ displayTracebacks = True
+ sessionFactory = Session
+ sessionCheckTime = 1800
+
+ def __init__(self, resource, logPath=None, timeout=60*60*12):
+ """
+ Initialize.
+ """
+ http.HTTPFactory.__init__(self, logPath=logPath, timeout=timeout)
+ self.sessions = {}
+ self.resource = resource
+
+ def _openLogFile(self, path):
+ from twisted.python import logfile
+ return logfile.LogFile(os.path.basename(path), os.path.dirname(path))
+
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ d['sessions'] = {}
+ return d
+
+ def _mkuid(self):
+ """
+ (internal) Generate an opaque, unique ID for a user's session.
+ """
+ from twisted.python.hashlib import md5
+ import random
+ self.counter = self.counter + 1
+ return md5("%s_%s" % (str(random.random()) , str(self.counter))).hexdigest()
+
+ def makeSession(self):
+ """
+ Generate a new Session instance, and store it for future reference.
+ """
+ uid = self._mkuid()
+ session = self.sessions[uid] = self.sessionFactory(self, uid)
+ session.startCheckingExpiration()
+ return session
+
+ def getSession(self, uid):
+ """
+ Get a previously generated session, by its unique ID.
+ This raises a KeyError if the session is not found.
+ """
+ return self.sessions[uid]
+
+ def buildProtocol(self, addr):
+ """
+ Generate a channel attached to this site.
+ """
+ channel = http.HTTPFactory.buildProtocol(self, addr)
+ channel.requestFactory = self.requestFactory
+ channel.site = self
+ return channel
+
+ isLeaf = 0
+
+ def render(self, request):
+ """
+ Redirect because a Site is always a directory.
+ """
+ request.redirect(request.prePathURL() + '/')
+ request.finish()
+
+ def getChildWithDefault(self, pathEl, request):
+ """
+ Emulate a resource's getChild method.
+ """
+ request.site = self
+ return self.resource.getChildWithDefault(pathEl, request)
+
+ def getResourceFor(self, request):
+ """
+ Get a resource for a request.
+
+ This iterates through the resource heirarchy, calling
+ getChildWithDefault on each resource it finds for a path element,
+ stopping when it hits an element where isLeaf is true.
+ """
+ request.site = self
+ # Sitepath is used to determine cookie names between distributed
+ # servers and disconnected sites.
+ request.sitepath = copy.copy(request.prepath)
+ return resource.getChildForRequest(self.resource, request)
+
+
+import html
+
diff --git a/vendor/Twisted-10.0.0/twisted/web/soap.py b/vendor/Twisted-10.0.0/twisted/web/soap.py
new file mode 100644
index 0000000000..70a605581b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/soap.py
@@ -0,0 +1,154 @@
+# -*- test-case-name: twisted.web.test.test_soap -*-
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+SOAP support for twisted.web.
+
+Requires SOAPpy 0.10.1 or later.
+
+Maintainer: Itamar Shtull-Trauring
+
+Future plans:
+SOAPContext support of some kind.
+Pluggable method lookup policies.
+"""
+
+# SOAPpy
+import SOAPpy
+
+# twisted imports
+from twisted.web import server, resource, client
+from twisted.internet import defer
+
+
+class SOAPPublisher(resource.Resource):
+ """Publish SOAP methods.
+
+ By default, publish methods beginning with 'soap_'. If the method
+ has an attribute 'useKeywords', it well get the arguments passed
+ as keyword args.
+ """
+
+ isLeaf = 1
+
+ # override to change the encoding used for responses
+ encoding = "UTF-8"
+
+ def lookupFunction(self, functionName):
+ """Lookup published SOAP function.
+
+ Override in subclasses. Default behaviour - publish methods
+ starting with soap_.
+
+ @return: callable or None if not found.
+ """
+ return getattr(self, "soap_%s" % functionName, None)
+
+ def render(self, request):
+ """Handle a SOAP command."""
+ data = request.content.read()
+
+ p, header, body, attrs = SOAPpy.parseSOAPRPC(data, 1, 1, 1)
+
+ methodName, args, kwargs, ns = p._name, p._aslist, p._asdict, p._ns
+
+ # deal with changes in SOAPpy 0.11
+ if callable(args):
+ args = args()
+ if callable(kwargs):
+ kwargs = kwargs()
+
+ function = self.lookupFunction(methodName)
+
+ if not function:
+ self._methodNotFound(request, methodName)
+ return server.NOT_DONE_YET
+ else:
+ if hasattr(function, "useKeywords"):
+ keywords = {}
+ for k, v in kwargs.items():
+ keywords[str(k)] = v
+ d = defer.maybeDeferred(function, **keywords)
+ else:
+ d = defer.maybeDeferred(function, *args)
+
+ d.addCallback(self._gotResult, request, methodName)
+ d.addErrback(self._gotError, request, methodName)
+ return server.NOT_DONE_YET
+
+ def _methodNotFound(self, request, methodName):
+ response = SOAPpy.buildSOAP(SOAPpy.faultType("%s:Client" %
+ SOAPpy.NS.ENV_T, "Method %s not found" % methodName),
+ encoding=self.encoding)
+ self._sendResponse(request, response, status=500)
+
+ def _gotResult(self, result, request, methodName):
+ if not isinstance(result, SOAPpy.voidType):
+ result = {"Result": result}
+ response = SOAPpy.buildSOAP(kw={'%sResponse' % methodName: result},
+ encoding=self.encoding)
+ self._sendResponse(request, response)
+
+ def _gotError(self, failure, request, methodName):
+ e = failure.value
+ if isinstance(e, SOAPpy.faultType):
+ fault = e
+ else:
+ fault = SOAPpy.faultType("%s:Server" % SOAPpy.NS.ENV_T,
+ "Method %s failed." % methodName)
+ response = SOAPpy.buildSOAP(fault, encoding=self.encoding)
+ self._sendResponse(request, response, status=500)
+
+ def _sendResponse(self, request, response, status=200):
+ request.setResponseCode(status)
+
+ if self.encoding is not None:
+ mimeType = 'text/xml; charset="%s"' % self.encoding
+ else:
+ mimeType = "text/xml"
+ request.setHeader("Content-type", mimeType)
+ request.setHeader("Content-length", str(len(response)))
+ request.write(response)
+ request.finish()
+
+
+class Proxy:
+ """A Proxy for making remote SOAP calls.
+
+ Pass the URL of the remote SOAP server to the constructor.
+
+ Use proxy.callRemote('foobar', 1, 2) to call remote method
+ 'foobar' with args 1 and 2, proxy.callRemote('foobar', x=1)
+ will call foobar with named argument 'x'.
+ """
+
+ # at some point this should have encoding etc. kwargs
+ def __init__(self, url, namespace=None, header=None):
+ self.url = url
+ self.namespace = namespace
+ self.header = header
+
+ def _cbGotResult(self, result):
+ result = SOAPpy.parseSOAPRPC(result)
+ if hasattr(result, 'Result'):
+ return result.Result
+ elif len(result) == 1:
+ ## SOAPpy 0.11.6 wraps the return results in a containing structure.
+ ## This check added to make Proxy behaviour emulate SOAPProxy, which
+ ## flattens the structure by default.
+ ## This behaviour is OK because even singleton lists are wrapped in
+ ## another singleton structType, which is almost always useless.
+ return result[0]
+ else:
+ return result
+
+ def callRemote(self, method, *args, **kwargs):
+ payload = SOAPpy.buildSOAP(args=args, kw=kwargs, method=method,
+ header=self.header, namespace=self.namespace)
+ return client.getPage(self.url, postdata=payload, method="POST",
+ headers={'content-type': 'text/xml',
+ 'SOAPAction': method}
+ ).addCallback(self._cbGotResult)
+
diff --git a/vendor/Twisted-10.0.0/twisted/web/static.py b/vendor/Twisted-10.0.0/twisted/web/static.py
new file mode 100644
index 0000000000..e31795cf25
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/static.py
@@ -0,0 +1,1104 @@
+# -*- test-case-name: twisted.web.test.test_static -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Static resources for L{twisted.web}.
+"""
+
+import os
+import warnings
+import urllib
+import itertools
+import cgi
+import time
+
+from zope.interface import implements
+
+from twisted.web import server
+from twisted.web import resource
+from twisted.web import http
+from twisted.web.util import redirectTo
+
+from twisted.python import components, filepath, log
+from twisted.internet import abstract, interfaces
+from twisted.spread import pb
+from twisted.persisted import styles
+from twisted.python.util import InsensitiveDict
+from twisted.python.runtime import platformType
+
+
+dangerousPathError = resource.NoResource("Invalid request URL.")
+
+def isDangerous(path):
+ return path == '..' or '/' in path or os.sep in path
+
+
+class Data(resource.Resource):
+ """
+ This is a static, in-memory resource.
+ """
+
+ def __init__(self, data, type):
+ resource.Resource.__init__(self)
+ self.data = data
+ self.type = type
+
+
+ def render_GET(self, request):
+ request.setHeader("content-type", self.type)
+ request.setHeader("content-length", str(len(self.data)))
+ if request.method == "HEAD":
+ return ''
+ return self.data
+ render_HEAD = render_GET
+
+
+def addSlash(request):
+ qs = ''
+ qindex = request.uri.find('?')
+ if qindex != -1:
+ qs = request.uri[qindex:]
+
+ return "http%s://%s%s/%s" % (
+ request.isSecure() and 's' or '',
+ request.getHeader("host"),
+ (request.uri.split('?')[0]),
+ qs)
+
+class Redirect(resource.Resource):
+ def __init__(self, request):
+ resource.Resource.__init__(self)
+ self.url = addSlash(request)
+
+ def render(self, request):
+ return redirectTo(self.url, request)
+
+
+class Registry(components.Componentized, styles.Versioned):
+ """
+ I am a Componentized object that will be made available to internal Twisted
+ file-based dynamic web content such as .rpy and .epy scripts.
+ """
+
+ def __init__(self):
+ components.Componentized.__init__(self)
+ self._pathCache = {}
+
+ persistenceVersion = 1
+
+ def upgradeToVersion1(self):
+ self._pathCache = {}
+
+ def cachePath(self, path, rsrc):
+ self._pathCache[path] = rsrc
+
+ def getCachedPath(self, path):
+ return self._pathCache.get(path)
+
+
+def loadMimeTypes(mimetype_locations=['/etc/mime.types']):
+ """
+ Multiple file locations containing mime-types can be passed as a list.
+ The files will be sourced in that order, overriding mime-types from the
+ files sourced beforehand, but only if a new entry explicitly overrides
+ the current entry.
+ """
+ import mimetypes
+ # Grab Python's built-in mimetypes dictionary.
+ contentTypes = mimetypes.types_map
+ # Update Python's semi-erroneous dictionary with a few of the
+ # usual suspects.
+ contentTypes.update(
+ {
+ '.conf': 'text/plain',
+ '.diff': 'text/plain',
+ '.exe': 'application/x-executable',
+ '.flac': 'audio/x-flac',
+ '.java': 'text/plain',
+ '.ogg': 'application/ogg',
+ '.oz': 'text/x-oz',
+ '.swf': 'application/x-shockwave-flash',
+ '.tgz': 'application/x-gtar',
+ '.wml': 'text/vnd.wap.wml',
+ '.xul': 'application/vnd.mozilla.xul+xml',
+ '.py': 'text/plain',
+ '.patch': 'text/plain',
+ }
+ )
+ # Users can override these mime-types by loading them out configuration
+ # files (this defaults to ['/etc/mime.types']).
+ for location in mimetype_locations:
+ if os.path.exists(location):
+ more = mimetypes.read_mime_types(location)
+ if more is not None:
+ contentTypes.update(more)
+
+ return contentTypes
+
+def getTypeAndEncoding(filename, types, encodings, defaultType):
+ p, ext = os.path.splitext(filename)
+ ext = ext.lower()
+ if encodings.has_key(ext):
+ enc = encodings[ext]
+ ext = os.path.splitext(p)[1].lower()
+ else:
+ enc = None
+ type = types.get(ext, defaultType)
+ return type, enc
+
+
+
+class File(resource.Resource, styles.Versioned, filepath.FilePath):
+ """
+ File is a resource that represents a plain non-interpreted file
+ (although it can look for an extension like .rpy or .cgi and hand the
+ file to a processor for interpretation if you wish). Its constructor
+ takes a file path.
+
+ Alternatively, you can give a directory path to the constructor. In this
+ case the resource will represent that directory, and its children will
+ be files underneath that directory. This provides access to an entire
+ filesystem tree with a single Resource.
+
+ If you map the URL 'http://server/FILE' to a resource created as
+ File('/tmp'), then http://server/FILE/ will return an HTML-formatted
+ listing of the /tmp/ directory, and http://server/FILE/foo/bar.html will
+ return the contents of /tmp/foo/bar.html .
+
+ @cvar childNotFound: L{Resource} used to render 404 Not Found error pages.
+ """
+
+ contentTypes = loadMimeTypes()
+
+ contentEncodings = {
+ ".gz" : "gzip",
+ ".bz2": "bzip2"
+ }
+
+ processors = {}
+
+ indexNames = ["index", "index.html", "index.htm", "index.trp", "index.rpy"]
+
+ type = None
+
+ ### Versioning
+
+ persistenceVersion = 6
+
+ def upgradeToVersion6(self):
+ self.ignoredExts = []
+ if self.allowExt:
+ self.ignoreExt("*")
+ del self.allowExt
+
+
+ def upgradeToVersion5(self):
+ if not isinstance(self.registry, Registry):
+ self.registry = Registry()
+
+
+ def upgradeToVersion4(self):
+ if not hasattr(self, 'registry'):
+ self.registry = {}
+
+
+ def upgradeToVersion3(self):
+ if not hasattr(self, 'allowExt'):
+ self.allowExt = 0
+
+
+ def upgradeToVersion2(self):
+ self.defaultType = "text/html"
+
+
+ def upgradeToVersion1(self):
+ if hasattr(self, 'indexName'):
+ self.indexNames = [self.indexName]
+ del self.indexName
+
+
+ def __init__(self, path, defaultType="text/html", ignoredExts=(), registry=None, allowExt=0):
+ """
+ Create a file with the given path.
+
+ @param path: The filename of the file from which this L{File} will
+ serve data.
+ @type path: C{str}
+
+ @param defaultType: A I{major/minor}-style MIME type specifier
+ indicating the I{Content-Type} with which this L{File}'s data
+ will be served if a MIME type cannot be determined based on
+ C{path}'s extension.
+ @type defaultType: C{str}
+
+ @param ignoredExts: A sequence giving the extensions of paths in the
+ filesystem which will be ignored for the purposes of child
+ lookup. For example, if C{ignoredExts} is C{(".bar",)} and
+ C{path} is a directory containing a file named C{"foo.bar"}, a
+ request for the C{"foo"} child of this resource will succeed
+ with a L{File} pointing to C{"foo.bar"}.
+
+ @param registry: The registry object being used to handle this
+ request. If C{None}, one will be created.
+ @type registry: L{Registry}
+
+ @param allowExt: Ignored parameter, only present for backwards
+ compatibility. Do not pass a value for this parameter.
+ """
+ resource.Resource.__init__(self)
+ filepath.FilePath.__init__(self, path)
+ self.defaultType = defaultType
+ if ignoredExts in (0, 1) or allowExt:
+ warnings.warn("ignoredExts should receive a list, not a boolean")
+ if ignoredExts or allowExt:
+ self.ignoredExts = ['*']
+ else:
+ self.ignoredExts = []
+ else:
+ self.ignoredExts = list(ignoredExts)
+ self.registry = registry or Registry()
+
+
+ def ignoreExt(self, ext):
+ """Ignore the given extension.
+
+ Serve file.ext if file is requested
+ """
+ self.ignoredExts.append(ext)
+
+ childNotFound = resource.NoResource("File not found.")
+
+ def directoryListing(self):
+ return DirectoryLister(self.path,
+ self.listNames(),
+ self.contentTypes,
+ self.contentEncodings,
+ self.defaultType)
+
+
+ def getChild(self, path, request):
+ """
+ If this L{File}'s path refers to a directory, return a L{File}
+ referring to the file named C{path} in that directory.
+
+ If C{path} is the empty string, return a L{DirectoryLister} instead.
+ """
+ self.restat(reraise=False)
+
+ if not self.isdir():
+ return self.childNotFound
+
+ if path:
+ try:
+ fpath = self.child(path)
+ except filepath.InsecurePath:
+ return self.childNotFound
+ else:
+ fpath = self.childSearchPreauth(*self.indexNames)
+ if fpath is None:
+ return self.directoryListing()
+
+ if not fpath.exists():
+ fpath = fpath.siblingExtensionSearch(*self.ignoredExts)
+ if fpath is None:
+ return self.childNotFound
+
+ if platformType == "win32":
+ # don't want .RPY to be different than .rpy, since that would allow
+ # source disclosure.
+ processor = InsensitiveDict(self.processors).get(fpath.splitext()[1])
+ else:
+ processor = self.processors.get(fpath.splitext()[1])
+ if processor:
+ return resource.IResource(processor(fpath.path, self.registry))
+ return self.createSimilarFile(fpath.path)
+
+
+ # methods to allow subclasses to e.g. decrypt files on the fly:
+ def openForReading(self):
+ """Open a file and return it."""
+ return self.open()
+
+
+ def getFileSize(self):
+ """Return file size."""
+ return self.getsize()
+
+
+ def _parseRangeHeader(self, range):
+ """
+ Parse the value of a Range header into (start, stop) pairs.
+
+ In a given pair, either of start or stop can be None, signifying that
+ no value was provided, but not both.
+
+ @return: A list C{[(start, stop)]} of pairs of length at least one.
+
+ @raise ValueError: if the header is syntactically invalid or if the
+ Bytes-Unit is anything other than 'bytes'.
+ """
+ try:
+ kind, value = range.split('=', 1)
+ except ValueError:
+ raise ValueError("Missing '=' separator")
+ kind = kind.strip()
+ if kind != 'bytes':
+ raise ValueError("Unsupported Bytes-Unit: %r" % (kind,))
+ unparsedRanges = filter(None, map(str.strip, value.split(',')))
+ parsedRanges = []
+ for byteRange in unparsedRanges:
+ try:
+ start, end = byteRange.split('-', 1)
+ except ValueError:
+ raise ValueError("Invalid Byte-Range: %r" % (byteRange,))
+ if start:
+ try:
+ start = int(start)
+ except ValueError:
+ raise ValueError("Invalid Byte-Range: %r" % (byteRange,))
+ else:
+ start = None
+ if end:
+ try:
+ end = int(end)
+ except ValueError:
+ raise ValueError("Invalid Byte-Range: %r" % (byteRange,))
+ else:
+ end = None
+ if start is not None:
+ if end is not None and start > end:
+ # Start must be less than or equal to end or it is invalid.
+ raise ValueError("Invalid Byte-Range: %r" % (byteRange,))
+ elif end is None:
+ # One or both of start and end must be specified. Omitting
+ # both is invalid.
+ raise ValueError("Invalid Byte-Range: %r" % (byteRange,))
+ parsedRanges.append((start, end))
+ return parsedRanges
+
+
+ def _rangeToOffsetAndSize(self, start, end):
+ """
+ Convert a start and end from a Range header to an offset and size.
+
+ This method checks that the resulting range overlaps with the resource
+ being served (and so has the value of C{getFileSize()} as an indirect
+ input).
+
+ Either but not both of start or end can be C{None}:
+
+ - Omitted start means that the end value is actually a start value
+ relative to the end of the resource.
+
+ - Omitted end means the end of the resource should be the end of
+ the range.
+
+ End is interpreted as inclusive, as per RFC 2616.
+
+ If this range doesn't overlap with any of this resource, C{(0, 0)} is
+ returned, which is not otherwise a value return value.
+
+ @param start: The start value from the header, or C{None} if one was
+ not present.
+ @param end: The end value from the header, or C{None} if one was not
+ present.
+ @return: C{(offset, size)} where offset is how far into this resource
+ this resource the range begins and size is how long the range is,
+ or C{(0, 0)} if the range does not overlap this resource.
+ """
+ size = self.getFileSize()
+ if start is None:
+ start = size - end
+ end = size
+ elif end is None:
+ end = size
+ elif end < size:
+ end += 1
+ elif end > size:
+ end = size
+ if start >= size:
+ start = end = 0
+ return start, (end - start)
+
+
+ def _contentRange(self, offset, size):
+ """
+ Return a string suitable for the value of a Content-Range header for a
+ range with the given offset and size.
+
+ The offset and size are not sanity checked in any way.
+
+ @param offset: How far into this resource the range begins.
+ @param size: How long the range is.
+ @return: The value as appropriate for the value of a Content-Range
+ header.
+ """
+ return 'bytes %d-%d/%d' % (
+ offset, offset + size - 1, self.getFileSize())
+
+
+ def _doSingleRangeRequest(self, request, (start, end)):
+ """
+ Set up the response for Range headers that specify a single range.
+
+ This method checks if the request is satisfiable and sets the response
+ code and Content-Range header appropriately. The return value
+ indicates which part of the resource to return.
+
+ @param request: The Request object.
+ @param start: The start of the byte range as specified by the header.
+ @param end: The end of the byte range as specified by the header. At
+ most one of C{start} and C{end} may be C{None}.
+ @return: A 2-tuple of the offset and size of the range to return.
+ offset == size == 0 indicates that the request is not satisfiable.
+ """
+ offset, size = self._rangeToOffsetAndSize(start, end)
+ if offset == size == 0:
+ # This range doesn't overlap with any of this resource, so the
+ # request is unsatisfiable.
+ request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
+ request.setHeader(
+ 'content-range', 'bytes */%d' % (self.getFileSize(),))
+ else:
+ request.setResponseCode(http.PARTIAL_CONTENT)
+ request.setHeader(
+ 'content-range', self._contentRange(offset, size))
+ return offset, size
+
+
+ def _doMultipleRangeRequest(self, request, byteRanges):
+ """
+ Set up the response for Range headers that specify a single range.
+
+ This method checks if the request is satisfiable and sets the response
+ code and Content-Type and Content-Length headers appropriately. The
+ return value, which is a little complicated, indicates which parts of
+ the resource to return and the boundaries that should separate the
+ parts.
+
+ In detail, the return value is a tuple rangeInfo C{rangeInfo} is a
+ list of 3-tuples C{(partSeparator, partOffset, partSize)}. The
+ response to this request should be, for each element of C{rangeInfo},
+ C{partSeparator} followed by C{partSize} bytes of the resource
+ starting at C{partOffset}. Each C{partSeparator} includes the
+ MIME-style boundary and the part-specific Content-type and
+ Content-range headers. It is convenient to return the separator as a
+ concrete string from this method, becasue this method needs to compute
+ the number of bytes that will make up the response to be able to set
+ the Content-Length header of the response accurately.
+
+ @param request: The Request object.
+ @param byteRanges: A list of C{(start, end)} values as specified by
+ the header. For each range, at most one of C{start} and C{end}
+ may be C{None}.
+ @return: See above.
+ """
+ matchingRangeFound = False
+ rangeInfo = []
+ contentLength = 0
+ boundary = "%x%x" % (int(time.time()*1000000), os.getpid())
+ if self.type:
+ contentType = self.type
+ else:
+ contentType = 'bytes' # It's what Apache does...
+ for start, end in byteRanges:
+ partOffset, partSize = self._rangeToOffsetAndSize(start, end)
+ if partOffset == partSize == 0:
+ continue
+ contentLength += partSize
+ matchingRangeFound = True
+ partContentRange = self._contentRange(partOffset, partSize)
+ partSeparator = (
+ "\r\n"
+ "--%s\r\n"
+ "Content-type: %s\r\n"
+ "Content-range: %s\r\n"
+ "\r\n") % (boundary, contentType, partContentRange)
+ contentLength += len(partSeparator)
+ rangeInfo.append((partSeparator, partOffset, partSize))
+ if not matchingRangeFound:
+ request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
+ request.setHeader(
+ 'content-length', '0')
+ request.setHeader(
+ 'content-range', 'bytes */%d' % (self.getFileSize(),))
+ return [], ''
+ finalBoundary = "\r\n--" + boundary + "--\r\n"
+ rangeInfo.append((finalBoundary, 0, 0))
+ request.setResponseCode(http.PARTIAL_CONTENT)
+ request.setHeader(
+ 'content-type', 'multipart/byteranges; boundary="%s"' % (boundary,))
+ request.setHeader(
+ 'content-length', contentLength + len(finalBoundary))
+ return rangeInfo
+
+
+ def _setContentHeaders(self, request, size=None):
+ """
+ Set the Content-length and Content-type headers for this request.
+
+ This method is not appropriate for requests for multiple byte ranges;
+ L{_doMultipleRangeRequest} will set these headers in that case.
+
+ @param request: The L{Request} object.
+ @param size: The size of the response. If not specified, default to
+ C{self.getFileSize()}.
+ """
+ if size is None:
+ size = self.getFileSize()
+ request.setHeader('content-length', str(size))
+ if self.type:
+ request.setHeader('content-type', self.type)
+ if self.encoding:
+ request.setHeader('content-encoding', self.encoding)
+
+
+ def makeProducer(self, request, fileForReading):
+ """
+ Make a L{StaticProducer} that will produce the body of this response.
+
+ This method will also set the response code and Content-* headers.
+
+ @param request: The L{Request} object.
+ @param fileForReading: The file object containing the resource.
+ @return: A L{StaticProducer}. Calling C{.start()} on this will begin
+ producing the response.
+ """
+ byteRange = request.getHeader('range')
+ if byteRange is None:
+ self._setContentHeaders(request)
+ request.setResponseCode(http.OK)
+ return NoRangeStaticProducer(request, fileForReading)
+ try:
+ parsedRanges = self._parseRangeHeader(byteRange)
+ except ValueError:
+ log.msg("Ignoring malformed Range header %r" % (byteRange,))
+ self._setContentHeaders(request)
+ request.setResponseCode(http.OK)
+ return NoRangeStaticProducer(request, fileForReading)
+
+ if len(parsedRanges) == 1:
+ offset, size = self._doSingleRangeRequest(
+ request, parsedRanges[0])
+ self._setContentHeaders(request, size)
+ return SingleRangeStaticProducer(
+ request, fileForReading, offset, size)
+ else:
+ rangeInfo = self._doMultipleRangeRequest(request, parsedRanges)
+ return MultipleRangeStaticProducer(
+ request, fileForReading, rangeInfo)
+
+
+ def render_GET(self, request):
+ """
+ Begin sending the contents of this L{File} (or a subset of the
+ contents, based on the 'range' header) to the given request.
+ """
+ self.restat(False)
+
+ if self.type is None:
+ self.type, self.encoding = getTypeAndEncoding(self.basename(),
+ self.contentTypes,
+ self.contentEncodings,
+ self.defaultType)
+
+ if not self.exists():
+ return self.childNotFound.render(request)
+
+ if self.isdir():
+ return self.redirect(request)
+
+ request.setHeader('accept-ranges', 'bytes')
+
+ try:
+ fileForReading = self.openForReading()
+ except IOError, e:
+ import errno
+ if e[0] == errno.EACCES:
+ return resource.ForbiddenResource().render(request)
+ else:
+ raise
+
+ if request.setLastModified(self.getmtime()) is http.CACHED:
+ return ''
+
+
+ producer = self.makeProducer(request, fileForReading)
+
+ if request.method == 'HEAD':
+ return ''
+
+ producer.start()
+ # and make sure the connection doesn't get closed
+ return server.NOT_DONE_YET
+ render_HEAD = render_GET
+
+
+ def redirect(self, request):
+ return redirectTo(addSlash(request), request)
+
+
+ def listNames(self):
+ if not self.isdir():
+ return []
+ directory = self.listdir()
+ directory.sort()
+ return directory
+
+ def listEntities(self):
+ return map(lambda fileName, self=self: self.createSimilarFile(os.path.join(self.path, fileName)), self.listNames())
+
+
+ def createPickleChild(self, name, child):
+ warnings.warn(
+ "File.createPickleChild is deprecated since Twisted 9.0. "
+ "Resource persistence is beyond the scope of Twisted Web.",
+ DeprecationWarning, stacklevel=2)
+
+ if not os.path.isdir(self.path):
+ resource.Resource.putChild(self, name, child)
+ # xxx use a file-extension-to-save-function dictionary instead
+ if type(child) == type(""):
+ fl = open(os.path.join(self.path, name), 'wb')
+ fl.write(child)
+ else:
+ if '.' not in name:
+ name = name + '.trp'
+ fl = open(os.path.join(self.path, name), 'wb')
+ from pickle import Pickler
+ pk = Pickler(fl)
+ pk.dump(child)
+ fl.close()
+
+
+ def createSimilarFile(self, path):
+ f = self.__class__(path, self.defaultType, self.ignoredExts, self.registry)
+ # refactoring by steps, here - constructor should almost certainly take these
+ f.processors = self.processors
+ f.indexNames = self.indexNames[:]
+ f.childNotFound = self.childNotFound
+ return f
+
+
+
+class StaticProducer(object):
+ """
+ Superclass for classes that implement the business of producing.
+
+ @ivar request: The L{IRequest} to write the contents of the file to.
+ @ivar fileObject: The file the contents of which to write to the request.
+ """
+
+ implements(interfaces.IPullProducer)
+
+ bufferSize = abstract.FileDescriptor.bufferSize
+
+
+ def __init__(self, request, fileObject):
+ """
+ Initialize the instance.
+ """
+ self.request = request
+ self.fileObject = fileObject
+
+
+ def start(self):
+ raise NotImplementedError(self.start)
+
+
+ def resumeProducing(self):
+ raise NotImplementedError(self.resumeProducing)
+
+
+ def stopProducing(self):
+ """
+ Stop producing data.
+
+ L{IPullProducer.stopProducing} is called when our consumer has died,
+ and subclasses also call this method when they are done producing
+ data.
+ """
+ self.fileObject.close()
+ self.request = None
+
+
+
+class NoRangeStaticProducer(StaticProducer):
+ """
+ A L{StaticProducer} that writes the entire file to the request.
+ """
+
+ def start(self):
+ self.request.registerProducer(self, False)
+
+
+ def resumeProducing(self):
+ if not self.request:
+ return
+ data = self.fileObject.read(self.bufferSize)
+ if data:
+ # this .write will spin the reactor, calling .doWrite and then
+ # .resumeProducing again, so be prepared for a re-entrant call
+ self.request.write(data)
+ else:
+ self.request.unregisterProducer()
+ self.request.finish()
+ self.stopProducing()
+
+
+
+class SingleRangeStaticProducer(StaticProducer):
+ """
+ A L{StaticProducer} that writes a single chunk of a file to the request.
+ """
+
+ def __init__(self, request, fileObject, offset, size):
+ """
+ Initialize the instance.
+
+ @param request: See L{StaticProducer}.
+ @param fileObject: See L{StaticProducer}.
+ @param offset: The offset into the file of the chunk to be written.
+ @param size: The size of the chunk to write.
+ """
+ StaticProducer.__init__(self, request, fileObject)
+ self.offset = offset
+ self.size = size
+
+
+ def start(self):
+ self.fileObject.seek(self.offset)
+ self.bytesWritten = 0
+ self.request.registerProducer(self, 0)
+
+
+ def resumeProducing(self):
+ if not self.request:
+ return
+ data = self.fileObject.read(
+ min(self.bufferSize, self.size - self.bytesWritten))
+ if data:
+ self.bytesWritten += len(data)
+ # this .write will spin the reactor, calling .doWrite and then
+ # .resumeProducing again, so be prepared for a re-entrant call
+ self.request.write(data)
+ if self.request and self.bytesWritten == self.size:
+ self.request.unregisterProducer()
+ self.request.finish()
+ self.stopProducing()
+
+
+
+class MultipleRangeStaticProducer(StaticProducer):
+ """
+ A L{StaticProducer} that writes several chunks of a file to the request.
+ """
+
+ def __init__(self, request, fileObject, rangeInfo):
+ """
+ Initialize the instance.
+
+ @param request: See L{StaticProducer}.
+ @param fileObject: See L{StaticProducer}.
+ @param rangeInfo: A list of tuples C{[(boundary, offset, size)]}
+ where:
+ - C{boundary} will be written to the request first.
+ - C{offset} the offset into the file of chunk to write.
+ - C{size} the size of the chunk to write.
+ """
+ StaticProducer.__init__(self, request, fileObject)
+ self.rangeInfo = rangeInfo
+
+
+ def start(self):
+ self.rangeIter = iter(self.rangeInfo)
+ self._nextRange()
+ self.request.registerProducer(self, 0)
+
+
+ def _nextRange(self):
+ self.partBoundary, partOffset, self._partSize = self.rangeIter.next()
+ self._partBytesWritten = 0
+ self.fileObject.seek(partOffset)
+
+
+ def resumeProducing(self):
+ if not self.request:
+ return
+ data = []
+ dataLength = 0
+ done = False
+ while dataLength < self.bufferSize:
+ if self.partBoundary:
+ dataLength += len(self.partBoundary)
+ data.append(self.partBoundary)
+ self.partBoundary = None
+ p = self.fileObject.read(
+ min(self.bufferSize - dataLength,
+ self._partSize - self._partBytesWritten))
+ self._partBytesWritten += len(p)
+ dataLength += len(p)
+ data.append(p)
+ if self.request and self._partBytesWritten == self._partSize:
+ try:
+ self._nextRange()
+ except StopIteration:
+ done = True
+ break
+ self.request.write(''.join(data))
+ if done:
+ self.request.unregisterProducer()
+ self.request.finish()
+ self.request = None
+
+
+class FileTransfer(pb.Viewable):
+ """
+ A class to represent the transfer of a file over the network.
+ """
+ request = None
+
+ def __init__(self, file, size, request):
+ warnings.warn(
+ "FileTransfer is deprecated since Twisted 9.0. "
+ "Use a subclass of StaticProducer instead.",
+ DeprecationWarning, stacklevel=2)
+ self.file = file
+ self.size = size
+ self.request = request
+ self.written = self.file.tell()
+ request.registerProducer(self, 0)
+
+ def resumeProducing(self):
+ if not self.request:
+ return
+ data = self.file.read(min(abstract.FileDescriptor.bufferSize, self.size - self.written))
+ if data:
+ self.written += len(data)
+ # this .write will spin the reactor, calling .doWrite and then
+ # .resumeProducing again, so be prepared for a re-entrant call
+ self.request.write(data)
+ if self.request and self.file.tell() == self.size:
+ self.request.unregisterProducer()
+ self.request.finish()
+ self.request = None
+
+ def pauseProducing(self):
+ pass
+
+ def stopProducing(self):
+ self.file.close()
+ self.request = None
+
+ # Remotely relay producer interface.
+
+ def view_resumeProducing(self, issuer):
+ self.resumeProducing()
+
+ def view_pauseProducing(self, issuer):
+ self.pauseProducing()
+
+ def view_stopProducing(self, issuer):
+ self.stopProducing()
+
+
+
+class ASISProcessor(resource.Resource):
+ """
+ Serve files exactly as responses without generating a status-line or any
+ headers. Inspired by Apache's mod_asis.
+ """
+
+ def __init__(self, path, registry=None):
+ resource.Resource.__init__(self)
+ self.path = path
+ self.registry = registry or Registry()
+
+
+ def render(self, request):
+ request.startedWriting = 1
+ res = File(self.path, registry=self.registry)
+ return res.render(request)
+
+
+
+def formatFileSize(size):
+ """
+ Format the given file size in bytes to human readable format.
+ """
+ if size < 1024:
+ return '%iB' % size
+ elif size < (1024 ** 2):
+ return '%iK' % (size / 1024)
+ elif size < (1024 ** 3):
+ return '%iM' % (size / (1024 ** 2))
+ else:
+ return '%iG' % (size / (1024 ** 3))
+
+
+
+class DirectoryLister(resource.Resource):
+ """
+ Print the content of a directory.
+
+ @ivar template: page template used to render the content of the directory.
+ It must contain the format keys B{header} and B{tableContent}.
+ @type template: C{str}
+
+ @ivar linePattern: template used to render one line in the listing table.
+ It must contain the format keys B{class}, B{href}, B{text}, B{size},
+ B{type} and B{encoding}.
+ @type linePattern: C{str}
+
+ @ivar contentEncodings: a mapping of extensions to encoding types.
+ @type contentEncodings: C{dict}
+
+ @ivar defaultType: default type used when no mimetype is detected.
+ @type defaultType: C{str}
+
+ @ivar dirs: filtered content of C{path}, if the whole content should not be
+ displayed (default to C{None}, which means the actual content of
+ C{path} is printed).
+ @type dirs: C{NoneType} or C{list}
+
+ @ivar path: directory which content should be listed.
+ @type path: C{str}
+ """
+
+ template = """<html>
+<head>
+<title>%(header)s</title>
+<style>
+.even-dir { background-color: #efe0ef }
+.even { background-color: #eee }
+.odd-dir {background-color: #f0d0ef }
+.odd { background-color: #dedede }
+.icon { text-align: center }
+.listing {
+ margin-left: auto;
+ margin-right: auto;
+ width: 50%%;
+ padding: 0.1em;
+ }
+
+body { border: 0; padding: 0; margin: 0; background-color: #efefef; }
+h1 {padding: 0.1em; background-color: #777; color: white; border-bottom: thin white dashed;}
+
+</style>
+</head>
+
+<body>
+<h1>%(header)s</h1>
+
+<table>
+ <thead>
+ <tr>
+ <th>Filename</th>
+ <th>Size</th>
+ <th>Content type</th>
+ <th>Content encoding</th>
+ </tr>
+ </thead>
+ <tbody>
+%(tableContent)s
+ </tbody>
+</table>
+
+</body>
+</html>
+"""
+
+ linePattern = """<tr class="%(class)s">
+ <td><a href="%(href)s">%(text)s</a></td>
+ <td>%(size)s</td>
+ <td>%(type)s</td>
+ <td>%(encoding)s</td>
+</tr>
+"""
+
+ def __init__(self, pathname, dirs=None,
+ contentTypes=File.contentTypes,
+ contentEncodings=File.contentEncodings,
+ defaultType='text/html'):
+ resource.Resource.__init__(self)
+ self.contentTypes = contentTypes
+ self.contentEncodings = contentEncodings
+ self.defaultType = defaultType
+ # dirs allows usage of the File to specify what gets listed
+ self.dirs = dirs
+ self.path = pathname
+
+
+ def _getFilesAndDirectories(self, directory):
+ """
+ Helper returning files and directories in given directory listing, with
+ attributes to be used to build a table content with
+ C{self.linePattern}.
+
+ @return: tuple of (directories, files)
+ @rtype: C{tuple} of C{list}
+ """
+ files = []
+ dirs = []
+ for path in directory:
+ url = urllib.quote(path, "/")
+ escapedPath = cgi.escape(path)
+ if os.path.isdir(os.path.join(self.path, path)):
+ url = url + '/'
+ dirs.append({'text': escapedPath + "/", 'href': url,
+ 'size': '', 'type': '[Directory]',
+ 'encoding': ''})
+ else:
+ mimetype, encoding = getTypeAndEncoding(path, self.contentTypes,
+ self.contentEncodings,
+ self.defaultType)
+ try:
+ size = os.stat(os.path.join(self.path, path)).st_size
+ except OSError:
+ continue
+ files.append({
+ 'text': escapedPath, "href": url,
+ 'type': '[%s]' % mimetype,
+ 'encoding': (encoding and '[%s]' % encoding or ''),
+ 'size': formatFileSize(size)})
+ return dirs, files
+
+
+ def _buildTableContent(self, elements):
+ """
+ Build a table content using C{self.linePattern} and giving elements odd
+ and even classes.
+ """
+ tableContent = []
+ rowClasses = itertools.cycle(['odd', 'even'])
+ for element, rowClass in zip(elements, rowClasses):
+ element["class"] = rowClass
+ tableContent.append(self.linePattern % element)
+ return tableContent
+
+
+ def render(self, request):
+ """
+ Render a listing of the content of C{self.path}.
+ """
+ if self.dirs is None:
+ directory = os.listdir(self.path)
+ directory.sort()
+ else:
+ directory = self.dirs
+
+ dirs, files = self._getFilesAndDirectories(directory)
+
+ tableContent = "".join(self._buildTableContent(dirs + files))
+
+ header = "Directory listing for %s" % (
+ cgi.escape(urllib.unquote(request.uri)),)
+
+ return self.template % {"header": header, "tableContent": tableContent}
+
+
+ def __repr__(self):
+ return '<DirectoryLister of %r>' % self.path
+
+ __str__ = __repr__
diff --git a/vendor/Twisted-10.0.0/twisted/web/sux.py b/vendor/Twisted-10.0.0/twisted/web/sux.py
new file mode 100644
index 0000000000..6f8fea1dc3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/sux.py
@@ -0,0 +1,657 @@
+# -*- test-case-name: twisted.web.test.test_xml -*-
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+*S*mall, *U*ncomplicated *X*ML.
+
+This is a very simple implementation of XML/HTML as a network
+protocol. It is not at all clever. Its main features are that it
+does not:
+
+ - support namespaces
+ - mung mnemonic entity references
+ - validate
+ - perform *any* external actions (such as fetching URLs or writing files)
+ under *any* circumstances
+ - has lots and lots of horrible hacks for supporting broken HTML (as an
+ option, they're not on by default).
+"""
+
+from twisted.internet.protocol import Protocol, FileWrapper
+from twisted.python.reflect import prefixedMethodNames
+
+
+
+# Elements of the three-tuples in the state table.
+BEGIN_HANDLER = 0
+DO_HANDLER = 1
+END_HANDLER = 2
+
+identChars = '.-_:'
+lenientIdentChars = identChars + ';+#/%~'
+
+def nop(*args, **kw):
+ "Do nothing."
+
+
+def unionlist(*args):
+ l = []
+ for x in args:
+ l.extend(x)
+ d = dict([(x, 1) for x in l])
+ return d.keys()
+
+
+def zipfndict(*args, **kw):
+ default = kw.get('default', nop)
+ d = {}
+ for key in unionlist(*[fndict.keys() for fndict in args]):
+ d[key] = tuple([x.get(key, default) for x in args])
+ return d
+
+
+def prefixedMethodClassDict(clazz, prefix):
+ return dict([(name, getattr(clazz, prefix + name)) for name in prefixedMethodNames(clazz, prefix)])
+
+
+def prefixedMethodObjDict(obj, prefix):
+ return dict([(name, getattr(obj, prefix + name)) for name in prefixedMethodNames(obj.__class__, prefix)])
+
+
+class ParseError(Exception):
+
+ def __init__(self, filename, line, col, message):
+ self.filename = filename
+ self.line = line
+ self.col = col
+ self.message = message
+
+ def __str__(self):
+ return "%s:%s:%s: %s" % (self.filename, self.line, self.col,
+ self.message)
+
+class XMLParser(Protocol):
+
+ state = None
+ encodings = None
+ filename = "<xml />"
+ beExtremelyLenient = 0
+ _prepend = None
+
+ # _leadingBodyData will sometimes be set before switching to the
+ # 'bodydata' state, when we "accidentally" read a byte of bodydata
+ # in a different state.
+ _leadingBodyData = None
+
+ def connectionMade(self):
+ self.lineno = 1
+ self.colno = 0
+ self.encodings = []
+
+ def saveMark(self):
+ '''Get the line number and column of the last character parsed'''
+ # This gets replaced during dataReceived, restored afterwards
+ return (self.lineno, self.colno)
+
+ def _parseError(self, message):
+ raise ParseError(*((self.filename,)+self.saveMark()+(message,)))
+
+ def _buildStateTable(self):
+ '''Return a dictionary of begin, do, end state function tuples'''
+ # _buildStateTable leaves something to be desired but it does what it
+ # does.. probably slowly, so I'm doing some evil caching so it doesn't
+ # get called more than once per class.
+ stateTable = getattr(self.__class__, '__stateTable', None)
+ if stateTable is None:
+ stateTable = self.__class__.__stateTable = zipfndict(
+ *[prefixedMethodObjDict(self, prefix)
+ for prefix in ('begin_', 'do_', 'end_')])
+ return stateTable
+
+ def _decode(self, data):
+ if 'UTF-16' in self.encodings or 'UCS-2' in self.encodings:
+ assert not len(data) & 1, 'UTF-16 must come in pairs for now'
+ if self._prepend:
+ data = self._prepend + data
+ for encoding in self.encodings:
+ data = unicode(data, encoding)
+ return data
+
+ def maybeBodyData(self):
+ if self.endtag:
+ return 'bodydata'
+
+ # Get ready for fun! We're going to allow
+ # <script>if (foo < bar)</script> to work!
+ # We do this by making everything between <script> and
+ # </script> a Text
+ # BUT <script src="foo"> will be special-cased to do regular,
+ # lenient behavior, because those may not have </script>
+ # -radix
+
+ if (self.tagName == 'script'
+ and not self.tagAttributes.has_key('src')):
+ # we do this ourselves rather than having begin_waitforendscript
+ # becuase that can get called multiple times and we don't want
+ # bodydata to get reset other than the first time.
+ self.begin_bodydata(None)
+ return 'waitforendscript'
+ return 'bodydata'
+
+
+
+ def dataReceived(self, data):
+ stateTable = self._buildStateTable()
+ if not self.state:
+ # all UTF-16 starts with this string
+ if data.startswith('\xff\xfe'):
+ self._prepend = '\xff\xfe'
+ self.encodings.append('UTF-16')
+ data = data[2:]
+ elif data.startswith('\xfe\xff'):
+ self._prepend = '\xfe\xff'
+ self.encodings.append('UTF-16')
+ data = data[2:]
+ self.state = 'begin'
+ if self.encodings:
+ data = self._decode(data)
+ # bring state, lineno, colno into local scope
+ lineno, colno = self.lineno, self.colno
+ curState = self.state
+ # replace saveMark with a nested scope function
+ _saveMark = self.saveMark
+ def saveMark():
+ return (lineno, colno)
+ self.saveMark = saveMark
+ # fetch functions from the stateTable
+ beginFn, doFn, endFn = stateTable[curState]
+ try:
+ for byte in data:
+ # do newline stuff
+ if byte == '\n':
+ lineno += 1
+ colno = 0
+ else:
+ colno += 1
+ newState = doFn(byte)
+ if newState is not None and newState != curState:
+ # this is the endFn from the previous state
+ endFn()
+ curState = newState
+ beginFn, doFn, endFn = stateTable[curState]
+ beginFn(byte)
+ finally:
+ self.saveMark = _saveMark
+ self.lineno, self.colno = lineno, colno
+ # state doesn't make sense if there's an exception..
+ self.state = curState
+
+
+ def connectionLost(self, reason):
+ """
+ End the last state we were in.
+ """
+ stateTable = self._buildStateTable()
+ stateTable[self.state][END_HANDLER]()
+
+
+ # state methods
+
+ def do_begin(self, byte):
+ if byte.isspace():
+ return
+ if byte != '<':
+ if self.beExtremelyLenient:
+ self._leadingBodyData = byte
+ return 'bodydata'
+ self._parseError("First char of document [%r] wasn't <" % (byte,))
+ return 'tagstart'
+
+ def begin_comment(self, byte):
+ self.commentbuf = ''
+
+ def do_comment(self, byte):
+ self.commentbuf += byte
+ if self.commentbuf.endswith('-->'):
+ self.gotComment(self.commentbuf[:-3])
+ return 'bodydata'
+
+ def begin_tagstart(self, byte):
+ self.tagName = '' # name of the tag
+ self.tagAttributes = {} # attributes of the tag
+ self.termtag = 0 # is the tag self-terminating
+ self.endtag = 0
+
+ def do_tagstart(self, byte):
+ if byte.isalnum() or byte in identChars:
+ self.tagName += byte
+ if self.tagName == '!--':
+ return 'comment'
+ elif byte.isspace():
+ if self.tagName:
+ if self.endtag:
+ # properly strict thing to do here is probably to only
+ # accept whitespace
+ return 'waitforgt'
+ return 'attrs'
+ else:
+ self._parseError("Whitespace before tag-name")
+ elif byte == '>':
+ if self.endtag:
+ self.gotTagEnd(self.tagName)
+ return 'bodydata'
+ else:
+ self.gotTagStart(self.tagName, {})
+ return (not self.beExtremelyLenient) and 'bodydata' or self.maybeBodyData()
+ elif byte == '/':
+ if self.tagName:
+ return 'afterslash'
+ else:
+ self.endtag = 1
+ elif byte in '!?':
+ if self.tagName:
+ if not self.beExtremelyLenient:
+ self._parseError("Invalid character in tag-name")
+ else:
+ self.tagName += byte
+ self.termtag = 1
+ elif byte == '[':
+ if self.tagName == '!':
+ return 'expectcdata'
+ else:
+ self._parseError("Invalid '[' in tag-name")
+ else:
+ if self.beExtremelyLenient:
+ self.bodydata = '<'
+ return 'unentity'
+ self._parseError('Invalid tag character: %r'% byte)
+
+ def begin_unentity(self, byte):
+ self.bodydata += byte
+
+ def do_unentity(self, byte):
+ self.bodydata += byte
+ return 'bodydata'
+
+ def end_unentity(self):
+ self.gotText(self.bodydata)
+
+ def begin_expectcdata(self, byte):
+ self.cdatabuf = byte
+
+ def do_expectcdata(self, byte):
+ self.cdatabuf += byte
+ cdb = self.cdatabuf
+ cd = '[CDATA['
+ if len(cd) > len(cdb):
+ if cd.startswith(cdb):
+ return
+ elif self.beExtremelyLenient:
+ ## WHAT THE CRAP!? MSWord9 generates HTML that includes these
+ ## bizarre <![if !foo]> <![endif]> chunks, so I've gotta ignore
+ ## 'em as best I can. this should really be a separate parse
+ ## state but I don't even have any idea what these _are_.
+ return 'waitforgt'
+ else:
+ self._parseError("Mal-formed CDATA header")
+ if cd == cdb:
+ self.cdatabuf = ''
+ return 'cdata'
+ self._parseError("Mal-formed CDATA header")
+
+ def do_cdata(self, byte):
+ self.cdatabuf += byte
+ if self.cdatabuf.endswith("]]>"):
+ self.cdatabuf = self.cdatabuf[:-3]
+ return 'bodydata'
+
+ def end_cdata(self):
+ self.gotCData(self.cdatabuf)
+ self.cdatabuf = ''
+
+ def do_attrs(self, byte):
+ if byte.isalnum() or byte in identChars:
+ # XXX FIXME really handle !DOCTYPE at some point
+ if self.tagName == '!DOCTYPE':
+ return 'doctype'
+ if self.tagName[0] in '!?':
+ return 'waitforgt'
+ return 'attrname'
+ elif byte.isspace():
+ return
+ elif byte == '>':
+ self.gotTagStart(self.tagName, self.tagAttributes)
+ return (not self.beExtremelyLenient) and 'bodydata' or self.maybeBodyData()
+ elif byte == '/':
+ return 'afterslash'
+ elif self.beExtremelyLenient:
+ # discard and move on? Only case I've seen of this so far was:
+ # <foo bar="baz"">
+ return
+ self._parseError("Unexpected character: %r" % byte)
+
+ def begin_doctype(self, byte):
+ self.doctype = byte
+
+ def do_doctype(self, byte):
+ if byte == '>':
+ return 'bodydata'
+ self.doctype += byte
+
+ def end_doctype(self):
+ self.gotDoctype(self.doctype)
+ self.doctype = None
+
+ def do_waitforgt(self, byte):
+ if byte == '>':
+ if self.endtag or not self.beExtremelyLenient:
+ return 'bodydata'
+ return self.maybeBodyData()
+
+ def begin_attrname(self, byte):
+ self.attrname = byte
+ self._attrname_termtag = 0
+
+ def do_attrname(self, byte):
+ if byte.isalnum() or byte in identChars:
+ self.attrname += byte
+ return
+ elif byte == '=':
+ return 'beforeattrval'
+ elif byte.isspace():
+ return 'beforeeq'
+ elif self.beExtremelyLenient:
+ if byte in '"\'':
+ return 'attrval'
+ if byte in lenientIdentChars or byte.isalnum():
+ self.attrname += byte
+ return
+ if byte == '/':
+ self._attrname_termtag = 1
+ return
+ if byte == '>':
+ self.attrval = 'True'
+ self.tagAttributes[self.attrname] = self.attrval
+ self.gotTagStart(self.tagName, self.tagAttributes)
+ if self._attrname_termtag:
+ self.gotTagEnd(self.tagName)
+ return 'bodydata'
+ return self.maybeBodyData()
+ # something is really broken. let's leave this attribute where it
+ # is and move on to the next thing
+ return
+ self._parseError("Invalid attribute name: %r %r" % (self.attrname, byte))
+
+ def do_beforeattrval(self, byte):
+ if byte in '"\'':
+ return 'attrval'
+ elif byte.isspace():
+ return
+ elif self.beExtremelyLenient:
+ if byte in lenientIdentChars or byte.isalnum():
+ return 'messyattr'
+ if byte == '>':
+ self.attrval = 'True'
+ self.tagAttributes[self.attrname] = self.attrval
+ self.gotTagStart(self.tagName, self.tagAttributes)
+ return self.maybeBodyData()
+ if byte == '\\':
+ # I saw this in actual HTML once:
+ # <font size=\"3\"><sup>SM</sup></font>
+ return
+ self._parseError("Invalid initial attribute value: %r; Attribute values must be quoted." % byte)
+
+ attrname = ''
+ attrval = ''
+
+ def begin_beforeeq(self,byte):
+ self._beforeeq_termtag = 0
+
+ def do_beforeeq(self, byte):
+ if byte == '=':
+ return 'beforeattrval'
+ elif byte.isspace():
+ return
+ elif self.beExtremelyLenient:
+ if byte.isalnum() or byte in identChars:
+ self.attrval = 'True'
+ self.tagAttributes[self.attrname] = self.attrval
+ return 'attrname'
+ elif byte == '>':
+ self.attrval = 'True'
+ self.tagAttributes[self.attrname] = self.attrval
+ self.gotTagStart(self.tagName, self.tagAttributes)
+ if self._beforeeq_termtag:
+ self.gotTagEnd(self.tagName)
+ return 'bodydata'
+ return self.maybeBodyData()
+ elif byte == '/':
+ self._beforeeq_termtag = 1
+ return
+ self._parseError("Invalid attribute")
+
+ def begin_attrval(self, byte):
+ self.quotetype = byte
+ self.attrval = ''
+
+ def do_attrval(self, byte):
+ if byte == self.quotetype:
+ return 'attrs'
+ self.attrval += byte
+
+ def end_attrval(self):
+ self.tagAttributes[self.attrname] = self.attrval
+ self.attrname = self.attrval = ''
+
+ def begin_messyattr(self, byte):
+ self.attrval = byte
+
+ def do_messyattr(self, byte):
+ if byte.isspace():
+ return 'attrs'
+ elif byte == '>':
+ endTag = 0
+ if self.attrval.endswith('/'):
+ endTag = 1
+ self.attrval = self.attrval[:-1]
+ self.tagAttributes[self.attrname] = self.attrval
+ self.gotTagStart(self.tagName, self.tagAttributes)
+ if endTag:
+ self.gotTagEnd(self.tagName)
+ return 'bodydata'
+ return self.maybeBodyData()
+ else:
+ self.attrval += byte
+
+ def end_messyattr(self):
+ if self.attrval:
+ self.tagAttributes[self.attrname] = self.attrval
+
+ def begin_afterslash(self, byte):
+ self._after_slash_closed = 0
+
+ def do_afterslash(self, byte):
+ # this state is only after a self-terminating slash, e.g. <foo/>
+ if self._after_slash_closed:
+ self._parseError("Mal-formed")#XXX When does this happen??
+ if byte != '>':
+ if self.beExtremelyLenient:
+ return
+ else:
+ self._parseError("No data allowed after '/'")
+ self._after_slash_closed = 1
+ self.gotTagStart(self.tagName, self.tagAttributes)
+ self.gotTagEnd(self.tagName)
+ # don't need maybeBodyData here because there better not be
+ # any javascript code after a <script/>... we'll see :(
+ return 'bodydata'
+
+ def begin_bodydata(self, byte):
+ if self._leadingBodyData:
+ self.bodydata = self._leadingBodyData
+ del self._leadingBodyData
+ else:
+ self.bodydata = ''
+
+ def do_bodydata(self, byte):
+ if byte == '<':
+ return 'tagstart'
+ if byte == '&':
+ return 'entityref'
+ self.bodydata += byte
+
+ def end_bodydata(self):
+ self.gotText(self.bodydata)
+ self.bodydata = ''
+
+ def do_waitforendscript(self, byte):
+ if byte == '<':
+ return 'waitscriptendtag'
+ self.bodydata += byte
+
+ def begin_waitscriptendtag(self, byte):
+ self.temptagdata = ''
+ self.tagName = ''
+ self.endtag = 0
+
+ def do_waitscriptendtag(self, byte):
+ # 1 enforce / as first byte read
+ # 2 enforce following bytes to be subset of "script" until
+ # tagName == "script"
+ # 2a when that happens, gotText(self.bodydata) and gotTagEnd(self.tagName)
+ # 3 spaces can happen anywhere, they're ignored
+ # e.g. < / script >
+ # 4 anything else causes all data I've read to be moved to the
+ # bodydata, and switch back to waitforendscript state
+
+ # If it turns out this _isn't_ a </script>, we need to
+ # remember all the data we've been through so we can append it
+ # to bodydata
+ self.temptagdata += byte
+
+ # 1
+ if byte == '/':
+ self.endtag = True
+ elif not self.endtag:
+ self.bodydata += "<" + self.temptagdata
+ return 'waitforendscript'
+ # 2
+ elif byte.isalnum() or byte in identChars:
+ self.tagName += byte
+ if not 'script'.startswith(self.tagName):
+ self.bodydata += "<" + self.temptagdata
+ return 'waitforendscript'
+ elif self.tagName == 'script':
+ self.gotText(self.bodydata)
+ self.gotTagEnd(self.tagName)
+ return 'waitforgt'
+ # 3
+ elif byte.isspace():
+ return 'waitscriptendtag'
+ # 4
+ else:
+ self.bodydata += "<" + self.temptagdata
+ return 'waitforendscript'
+
+
+ def begin_entityref(self, byte):
+ self.erefbuf = ''
+ self.erefextra = '' # extra bit for lenient mode
+
+ def do_entityref(self, byte):
+ if byte.isspace() or byte == "<":
+ if self.beExtremelyLenient:
+ # '&foo' probably was '&amp;foo'
+ if self.erefbuf and self.erefbuf != "amp":
+ self.erefextra = self.erefbuf
+ self.erefbuf = "amp"
+ if byte == "<":
+ return "tagstart"
+ else:
+ self.erefextra += byte
+ return 'spacebodydata'
+ self._parseError("Bad entity reference")
+ elif byte != ';':
+ self.erefbuf += byte
+ else:
+ return 'bodydata'
+
+ def end_entityref(self):
+ self.gotEntityReference(self.erefbuf)
+
+ # hacky support for space after & in entityref in beExtremelyLenient
+ # state should only happen in that case
+ def begin_spacebodydata(self, byte):
+ self.bodydata = self.erefextra
+ self.erefextra = None
+ do_spacebodydata = do_bodydata
+ end_spacebodydata = end_bodydata
+
+ # Sorta SAX-ish API
+
+ def gotTagStart(self, name, attributes):
+ '''Encountered an opening tag.
+
+ Default behaviour is to print.'''
+ print 'begin', name, attributes
+
+ def gotText(self, data):
+ '''Encountered text
+
+ Default behaviour is to print.'''
+ print 'text:', repr(data)
+
+ def gotEntityReference(self, entityRef):
+ '''Encountered mnemonic entity reference
+
+ Default behaviour is to print.'''
+ print 'entityRef: &%s;' % entityRef
+
+ def gotComment(self, comment):
+ '''Encountered comment.
+
+ Default behaviour is to ignore.'''
+ pass
+
+ def gotCData(self, cdata):
+ '''Encountered CDATA
+
+ Default behaviour is to call the gotText method'''
+ self.gotText(cdata)
+
+ def gotDoctype(self, doctype):
+ """Encountered DOCTYPE
+
+ This is really grotty: it basically just gives you everything between
+ '<!DOCTYPE' and '>' as an argument.
+ """
+ print '!DOCTYPE', repr(doctype)
+
+ def gotTagEnd(self, name):
+ '''Encountered closing tag
+
+ Default behaviour is to print.'''
+ print 'end', name
+
+if __name__ == '__main__':
+ from cStringIO import StringIO
+ testDocument = '''
+
+ <!DOCTYPE ignore all this shit, hah its malformed!!!!@$>
+ <?xml version="suck it"?>
+ <foo>
+ &#65;
+ <bar />
+ <baz boz="buz">boz &zop;</baz>
+ <![CDATA[ foo bar baz ]]>
+ </foo>
+ '''
+ x = XMLParser()
+ x.makeConnection(FileWrapper(StringIO()))
+ # fn = "/home/glyph/Projects/Twisted/doc/howto/ipc10paper.html"
+ fn = "/home/glyph/gruesome.xml"
+ # testDocument = open(fn).read()
+ x.dataReceived(testDocument)
diff --git a/vendor/Twisted-10.0.0/twisted/web/tap.py b/vendor/Twisted-10.0.0/twisted/web/tap.py
new file mode 100644
index 0000000000..bd3b4079b2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/tap.py
@@ -0,0 +1,234 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Support for creating a service which runs a web server.
+"""
+
+import os
+
+# Twisted Imports
+from twisted.web import server, static, twcgi, script, demo, distrib, wsgi
+from twisted.internet import interfaces, reactor
+from twisted.python import usage, reflect, threadpool
+from twisted.spread import pb
+from twisted.application import internet, service, strports
+
+
+class Options(usage.Options):
+ """
+ Define the options accepted by the I{twistd web} plugin.
+ """
+ synopsis = "[web options]"
+
+ optParameters = [["port", "p", None, "strports description of the port to "
+ "start the server on."],
+ ["logfile", "l", None, "Path to web CLF (Combined Log Format) log file."],
+ ["https", None, None, "Port to listen on for Secure HTTP."],
+ ["certificate", "c", "server.pem", "SSL certificate to use for HTTPS. "],
+ ["privkey", "k", "server.pem", "SSL certificate to use for HTTPS."],
+ ]
+
+ optFlags = [["personal", "",
+ "Instead of generating a webserver, generate a "
+ "ResourcePublisher which listens on the port given by "
+ "--port, or ~/%s " % (distrib.UserDirectory.userSocketName,) +
+ "if --port is not specified."],
+ ["notracebacks", "n", "Do not display tracebacks in broken web pages. " +
+ "Displaying tracebacks to users may be security risk!"],
+ ]
+
+ zsh_actions = {"logfile" : "_files -g '*.log'", "certificate" : "_files -g '*.pem'",
+ "privkey" : "_files -g '*.pem'"}
+
+
+ longdesc = """\
+This starts a webserver. If you specify no arguments, it will be a
+demo webserver that has the Test class from twisted.web.demo in it."""
+
+ def __init__(self):
+ usage.Options.__init__(self)
+ self['indexes'] = []
+ self['root'] = None
+
+ def opt_index(self, indexName):
+ """
+ Add the name of a file used to check for directory indexes.
+ [default: index, index.html]
+ """
+ self['indexes'].append(indexName)
+
+ opt_i = opt_index
+
+ def opt_user(self):
+ """
+ Makes a server with ~/public_html and ~/.twistd-web-pb support for
+ users.
+ """
+ self['root'] = distrib.UserDirectory()
+
+ opt_u = opt_user
+
+ def opt_path(self, path):
+ """
+ <path> is either a specific file or a directory to be set as the root
+ of the web server. Use this if you have a directory full of HTML, cgi,
+ php3, epy, or rpy files or any other files that you want to be served
+ up raw.
+ """
+ def trp(*args, **kwargs):
+ # Help avoid actually importing twisted.web.trp until it is really
+ # needed. This avoids getting a deprecation warning if you're not
+ # using deprecated functionality.
+ from twisted.web import trp
+ return trp.ResourceUnpickler(*args, **kwargs)
+
+ self['root'] = static.File(os.path.abspath(path))
+ self['root'].processors = {
+ '.cgi': twcgi.CGIScript,
+ '.php3': twcgi.PHP3Script,
+ '.php': twcgi.PHPScript,
+ '.epy': script.PythonScript,
+ '.rpy': script.ResourceScript,
+ '.trp': trp,
+ }
+
+ def opt_processor(self, proc):
+ """
+ `ext=class' where `class' is added as a Processor for files ending
+ with `ext'.
+ """
+ if not isinstance(self['root'], static.File):
+ raise usage.UsageError("You can only use --processor after --path.")
+ ext, klass = proc.split('=', 1)
+ self['root'].processors[ext] = reflect.namedClass(klass)
+
+ def opt_class(self, className):
+ """
+ Create a Resource subclass with a zero-argument constructor.
+ """
+ classObj = reflect.namedClass(className)
+ self['root'] = classObj()
+
+
+ def opt_resource_script(self, name):
+ """
+ An .rpy file to be used as the root resource of the webserver.
+ """
+ self['root'] = script.ResourceScriptWrapper(name)
+
+
+ def opt_wsgi(self, name):
+ """
+ The FQPN of a WSGI application object to serve as the root resource of
+ the webserver.
+ """
+ pool = threadpool.ThreadPool()
+ reactor.callWhenRunning(pool.start)
+ reactor.addSystemEventTrigger('after', 'shutdown', pool.stop)
+ try:
+ application = reflect.namedAny(name)
+ except (AttributeError, ValueError):
+ raise usage.UsageError("No such WSGI application: %r" % (name,))
+ self['root'] = wsgi.WSGIResource(reactor, pool, application)
+
+
+ def opt_mime_type(self, defaultType):
+ """
+ Specify the default mime-type for static files.
+ """
+ if not isinstance(self['root'], static.File):
+ raise usage.UsageError("You can only use --mime_type after --path.")
+ self['root'].defaultType = defaultType
+ opt_m = opt_mime_type
+
+
+ def opt_allow_ignore_ext(self):
+ """
+ Specify whether or not a request for 'foo' should return 'foo.ext'
+ """
+ if not isinstance(self['root'], static.File):
+ raise usage.UsageError("You can only use --allow_ignore_ext "
+ "after --path.")
+ self['root'].ignoreExt('*')
+
+ def opt_ignore_ext(self, ext):
+ """
+ Specify an extension to ignore. These will be processed in order.
+ """
+ if not isinstance(self['root'], static.File):
+ raise usage.UsageError("You can only use --ignore_ext "
+ "after --path.")
+ self['root'].ignoreExt(ext)
+
+ def postOptions(self):
+ """
+ Set up conditional defaults and check for dependencies.
+
+ If SSL is not available but an HTTPS server was configured, raise a
+ L{UsageError} indicating that this is not possible.
+
+ If no server port was supplied, select a default appropriate for the
+ other options supplied.
+ """
+ if self['https']:
+ try:
+ from twisted.internet.ssl import DefaultOpenSSLContextFactory
+ except ImportError:
+ raise usage.UsageError("SSL support not installed")
+ if self['port'] is None:
+ if self['personal']:
+ path = os.path.expanduser(
+ os.path.join('~', distrib.UserDirectory.userSocketName))
+ self['port'] = 'unix:' + path
+ else:
+ self['port'] = 'tcp:8080'
+
+
+
+def makePersonalServerFactory(site):
+ """
+ Create and return a factory which will respond to I{distrib} requests
+ against the given site.
+
+ @type site: L{twisted.web.server.Site}
+ @rtype: L{twisted.internet.protocol.Factory}
+ """
+ return pb.PBServerFactory(distrib.ResourcePublisher(site))
+
+
+
+def makeService(config):
+ s = service.MultiService()
+ if config['root']:
+ root = config['root']
+ if config['indexes']:
+ config['root'].indexNames = config['indexes']
+ else:
+ # This really ought to be web.Admin or something
+ root = demo.Test()
+
+ if isinstance(root, static.File):
+ root.registry.setComponent(interfaces.IServiceCollection, s)
+
+ if config['logfile']:
+ site = server.Site(root, logPath=config['logfile'])
+ else:
+ site = server.Site(root)
+
+ site.displayTracebacks = not config["notracebacks"]
+
+ if config['personal']:
+ personal = strports.service(
+ config['port'], makePersonalServerFactory(site))
+ personal.setServiceParent(s)
+ else:
+ if config['https']:
+ from twisted.internet.ssl import DefaultOpenSSLContextFactory
+ i = internet.SSLServer(int(config['https']), site,
+ DefaultOpenSSLContextFactory(config['privkey'],
+ config['certificate']))
+ i.setServiceParent(s)
+ strports.service(config['port'], site).setServiceParent(s)
+
+ return s
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/__init__.py b/vendor/Twisted-10.0.0/twisted/web/test/__init__.py
new file mode 100644
index 0000000000..23b9105f69
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/__init__.py
@@ -0,0 +1,7 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web}.
+"""
+
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/_util.py b/vendor/Twisted-10.0.0/twisted/web/test/_util.py
new file mode 100644
index 0000000000..c0daa641f7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/_util.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+General helpers for L{twisted.web} unit tests.
+"""
+
+from twisted.internet.defer import succeed
+from twisted.web import server
+
+
+def _render(resource, request):
+ result = resource.render(request)
+ if isinstance(result, str):
+ request.write(result)
+ request.finish()
+ return succeed(None)
+ elif result is server.NOT_DONE_YET:
+ if request.finished:
+ return succeed(None)
+ else:
+ return request.notifyFinish()
+ else:
+ raise ValueError("Unexpected return value: %r" % (result,))
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_cgi.py b/vendor/Twisted-10.0.0/twisted/web/test/test_cgi.py
new file mode 100755
index 0000000000..2da9c18e65
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_cgi.py
@@ -0,0 +1,190 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web.twcgi}.
+"""
+
+import sys, os
+
+from twisted.trial import unittest
+from twisted.internet import reactor, interfaces, error
+from twisted.python import util, failure
+from twisted.web.http import NOT_FOUND, INTERNAL_SERVER_ERROR
+from twisted.web import client, twcgi, server, resource
+from twisted.web.test._util import _render
+from twisted.web.test.test_web import DummyRequest
+
+DUMMY_CGI = '''\
+print "Header: OK"
+print
+print "cgi output"
+'''
+
+READINPUT_CGI = '''\
+# this is an example of a correctly-written CGI script which reads a body
+# from stdin, which only reads env['CONTENT_LENGTH'] bytes.
+
+import os, sys
+
+body_length = int(os.environ.get('CONTENT_LENGTH',0))
+indata = sys.stdin.read(body_length)
+print "Header: OK"
+print
+print "readinput ok"
+'''
+
+READALLINPUT_CGI = '''\
+# this is an example of the typical (incorrect) CGI script which expects
+# the server to close stdin when the body of the request is complete.
+# A correct CGI should only read env['CONTENT_LENGTH'] bytes.
+
+import sys
+
+indata = sys.stdin.read()
+print "Header: OK"
+print
+print "readallinput ok"
+'''
+
+class PythonScript(twcgi.FilteredScript):
+ filter = sys.executable
+ filters = sys.executable, # web2's version
+
+class CGI(unittest.TestCase):
+ """
+ Tests for L{twcgi.FilteredScript}.
+ """
+
+ if not interfaces.IReactorProcess.providedBy(reactor):
+ skip = "CGI tests require a functional reactor.spawnProcess()"
+
+ def startServer(self, cgi):
+ root = resource.Resource()
+ cgipath = util.sibpath(__file__, cgi)
+ root.putChild("cgi", PythonScript(cgipath))
+ site = server.Site(root)
+ self.p = reactor.listenTCP(0, site)
+ return self.p.getHost().port
+
+ def tearDown(self):
+ if self.p:
+ return self.p.stopListening()
+
+
+ def testCGI(self):
+ cgiFilename = os.path.abspath(self.mktemp())
+ cgiFile = file(cgiFilename, 'wt')
+ cgiFile.write(DUMMY_CGI)
+ cgiFile.close()
+
+ portnum = self.startServer(cgiFilename)
+ d = client.getPage("http://localhost:%d/cgi" % portnum)
+ d.addCallback(self._testCGI_1)
+ return d
+ def _testCGI_1(self, res):
+ self.failUnlessEqual(res, "cgi output" + os.linesep)
+
+
+ def testReadEmptyInput(self):
+ cgiFilename = os.path.abspath(self.mktemp())
+ cgiFile = file(cgiFilename, 'wt')
+ cgiFile.write(READINPUT_CGI)
+ cgiFile.close()
+
+ portnum = self.startServer(cgiFilename)
+ d = client.getPage("http://localhost:%d/cgi" % portnum)
+ d.addCallback(self._testReadEmptyInput_1)
+ return d
+ testReadEmptyInput.timeout = 5
+ def _testReadEmptyInput_1(self, res):
+ self.failUnlessEqual(res, "readinput ok%s" % os.linesep)
+
+ def testReadInput(self):
+ cgiFilename = os.path.abspath(self.mktemp())
+ cgiFile = file(cgiFilename, 'wt')
+ cgiFile.write(READINPUT_CGI)
+ cgiFile.close()
+
+ portnum = self.startServer(cgiFilename)
+ d = client.getPage("http://localhost:%d/cgi" % portnum,
+ method="POST",
+ postdata="Here is your stdin")
+ d.addCallback(self._testReadInput_1)
+ return d
+ testReadInput.timeout = 5
+ def _testReadInput_1(self, res):
+ self.failUnlessEqual(res, "readinput ok%s" % os.linesep)
+
+
+ def testReadAllInput(self):
+ cgiFilename = os.path.abspath(self.mktemp())
+ cgiFile = file(cgiFilename, 'wt')
+ cgiFile.write(READALLINPUT_CGI)
+ cgiFile.close()
+
+ portnum = self.startServer(cgiFilename)
+ d = client.getPage("http://localhost:%d/cgi" % portnum,
+ method="POST",
+ postdata="Here is your stdin")
+ d.addCallback(self._testReadAllInput_1)
+ return d
+ testReadAllInput.timeout = 5
+ def _testReadAllInput_1(self, res):
+ self.failUnlessEqual(res, "readallinput ok%s" % os.linesep)
+
+
+
+class CGIDirectoryTests(unittest.TestCase):
+ """
+ Tests for L{twcgi.CGIDirectory}.
+ """
+ def test_render(self):
+ """
+ L{twcgi.CGIDirectory.render} sets the HTTP response code to I{NOT
+ FOUND}.
+ """
+ resource = twcgi.CGIDirectory(self.mktemp())
+ request = DummyRequest([''])
+ d = _render(resource, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, NOT_FOUND)
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_notFoundChild(self):
+ """
+ L{twcgi.CGIDirectory.getChild} returns a resource which renders an
+ response with the HTTP I{NOT FOUND} status code if the indicated child
+ does not exist as an entry in the directory used to initialized the
+ L{twcgi.CGIDirectory}.
+ """
+ path = self.mktemp()
+ os.makedirs(path)
+ resource = twcgi.CGIDirectory(path)
+ request = DummyRequest(['foo'])
+ child = resource.getChild("foo", request)
+ d = _render(child, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, NOT_FOUND)
+ d.addCallback(cbRendered)
+ return d
+
+
+
+class CGIProcessProtocolTests(unittest.TestCase):
+ """
+ Tests for L{twcgi.CGIProcessProtocol}.
+ """
+ def test_prematureEndOfHeaders(self):
+ """
+ If the process communicating with L{CGIProcessProtocol} ends before
+ finishing writing out headers, the response has I{INTERNAL SERVER
+ ERROR} as its status code.
+ """
+ request = DummyRequest([''])
+ protocol = twcgi.CGIProcessProtocol(request)
+ protocol.processEnded(failure.Failure(error.ProcessTerminated()))
+ self.assertEqual(request.responseCode, INTERNAL_SERVER_ERROR)
+
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_distrib.py b/vendor/Twisted-10.0.0/twisted/web/test/test_distrib.py
new file mode 100755
index 0000000000..d7960d291f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_distrib.py
@@ -0,0 +1,361 @@
+# Copyright (c) 2008-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web.distrib}.
+"""
+
+from os.path import abspath
+from xml.dom.minidom import parseString
+try:
+ import pwd
+except ImportError:
+ pwd = None
+
+from zope.interface.verify import verifyObject
+
+from twisted.python import log, filepath
+from twisted.internet import reactor, defer
+from twisted.trial import unittest
+from twisted.spread import pb
+from twisted.spread.banana import SIZE_LIMIT
+from twisted.web import http, distrib, client, resource, static, server
+from twisted.web.test.test_web import DummyRequest
+from twisted.web.test._util import _render
+
+
+class MySite(server.Site):
+ def stopFactory(self):
+ if hasattr(self, "logFile"):
+ if self.logFile != log.logfile:
+ self.logFile.close()
+ del self.logFile
+
+
+
+class PBServerFactory(pb.PBServerFactory):
+ """
+ A PB server factory which keeps track of the most recent protocol it
+ created.
+
+ @ivar proto: L{None} or the L{Broker} instance most recently returned
+ from C{buildProtocol}.
+ """
+ proto = None
+
+ def buildProtocol(self, addr):
+ self.proto = pb.PBServerFactory.buildProtocol(self, addr)
+ return self.proto
+
+
+
+class DistribTest(unittest.TestCase):
+ port1 = None
+ port2 = None
+ sub = None
+ f1 = None
+
+ def tearDown(self):
+ """
+ Clean up all the event sources left behind by either directly by
+ test methods or indirectly via some distrib API.
+ """
+ dl = [defer.Deferred(), defer.Deferred()]
+ if self.f1 is not None and self.f1.proto is not None:
+ self.f1.proto.notifyOnDisconnect(lambda: dl[0].callback(None))
+ else:
+ dl[0].callback(None)
+ if self.sub is not None and self.sub.publisher is not None:
+ self.sub.publisher.broker.notifyOnDisconnect(
+ lambda: dl[1].callback(None))
+ self.sub.publisher.broker.transport.loseConnection()
+ else:
+ dl[1].callback(None)
+ http._logDateTimeStop()
+ if self.port1 is not None:
+ dl.append(self.port1.stopListening())
+ if self.port2 is not None:
+ dl.append(self.port2.stopListening())
+ return defer.gatherResults(dl)
+
+
+ def testDistrib(self):
+ # site1 is the publisher
+ r1 = resource.Resource()
+ r1.putChild("there", static.Data("root", "text/plain"))
+ site1 = server.Site(r1)
+ self.f1 = PBServerFactory(distrib.ResourcePublisher(site1))
+ self.port1 = reactor.listenTCP(0, self.f1)
+ self.sub = distrib.ResourceSubscription("127.0.0.1",
+ self.port1.getHost().port)
+ r2 = resource.Resource()
+ r2.putChild("here", self.sub)
+ f2 = MySite(r2)
+ self.port2 = reactor.listenTCP(0, f2)
+ d = client.getPage("http://127.0.0.1:%d/here/there" % \
+ self.port2.getHost().port)
+ d.addCallback(self.failUnlessEqual, 'root')
+ return d
+
+
+ def _requestTest(self, child, **kwargs):
+ """
+ Set up a resource on a distrib site using L{ResourcePublisher} and
+ then retrieve it from a L{ResourceSubscription} via an HTTP client.
+
+ @param child: The resource to publish using distrib.
+ @param **kwargs: Extra keyword arguments to pass to L{getPage} when
+ requesting the resource.
+
+ @return: A L{Deferred} which fires with the result of the request.
+ """
+ distribRoot = resource.Resource()
+ distribRoot.putChild("child", child)
+ distribSite = server.Site(distribRoot)
+ self.f1 = distribFactory = PBServerFactory(
+ distrib.ResourcePublisher(distribSite))
+ distribPort = reactor.listenTCP(
+ 0, distribFactory, interface="127.0.0.1")
+ self.addCleanup(distribPort.stopListening)
+ addr = distribPort.getHost()
+
+ self.sub = mainRoot = distrib.ResourceSubscription(
+ addr.host, addr.port)
+ mainSite = server.Site(mainRoot)
+ mainPort = reactor.listenTCP(0, mainSite, interface="127.0.0.1")
+ self.addCleanup(mainPort.stopListening)
+ mainAddr = mainPort.getHost()
+
+ return client.getPage("http://%s:%s/child" % (
+ mainAddr.host, mainAddr.port), **kwargs)
+
+
+
+ def test_requestHeaders(self):
+ """
+ The request headers are available on the request object passed to a
+ distributed resource's C{render} method.
+ """
+ requestHeaders = {}
+
+ class ReportRequestHeaders(resource.Resource):
+ def render(self, request):
+ requestHeaders.update(dict(
+ request.requestHeaders.getAllRawHeaders()))
+ return ""
+
+ request = self._requestTest(
+ ReportRequestHeaders(), headers={'foo': 'bar'})
+ def cbRequested(result):
+ self.assertEquals(requestHeaders['Foo'], ['bar'])
+ request.addCallback(cbRequested)
+ return request
+
+
+ def test_largeWrite(self):
+ """
+ If a string longer than the Banana size limit is passed to the
+ L{distrib.Request} passed to the remote resource, it is broken into
+ smaller strings to be transported over the PB connection.
+ """
+ class LargeWrite(resource.Resource):
+ def render(self, request):
+ request.write('x' * SIZE_LIMIT + 'y')
+ request.finish()
+ return server.NOT_DONE_YET
+
+ request = self._requestTest(LargeWrite())
+ request.addCallback(self.assertEquals, 'x' * SIZE_LIMIT + 'y')
+ return request
+
+
+ def test_largeReturn(self):
+ """
+ Like L{test_largeWrite}, but for the case where C{render} returns a
+ long string rather than explicitly passing it to L{Request.write}.
+ """
+ class LargeReturn(resource.Resource):
+ def render(self, request):
+ return 'x' * SIZE_LIMIT + 'y'
+
+ request = self._requestTest(LargeReturn())
+ request.addCallback(self.assertEquals, 'x' * SIZE_LIMIT + 'y')
+ return request
+
+
+ def test_connectionLost(self):
+ """
+ If there is an error issuing the request to the remote publisher, an
+ error response is returned.
+ """
+ # Using pb.Root as a publisher will cause request calls to fail with an
+ # error every time. Just what we want to test.
+ self.f1 = serverFactory = PBServerFactory(pb.Root())
+ self.port1 = serverPort = reactor.listenTCP(0, serverFactory)
+
+ self.sub = subscription = distrib.ResourceSubscription(
+ "127.0.0.1", serverPort.getHost().port)
+ request = DummyRequest([''])
+ d = _render(subscription, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, 500)
+ # This is the error we caused the request to fail with. It should
+ # have been logged.
+ self.assertEqual(len(self.flushLoggedErrors(pb.NoSuchMethod)), 1)
+ d.addCallback(cbRendered)
+ return d
+
+
+
+class _PasswordDatabase:
+ def __init__(self, users):
+ self._users = users
+
+
+ def getpwall(self):
+ return iter(self._users)
+
+
+ def getpwnam(self, username):
+ for user in self._users:
+ if user[0] == username:
+ return user
+ raise KeyError()
+
+
+
+class UserDirectoryTests(unittest.TestCase):
+ """
+ Tests for L{UserDirectory}, a resource for listing all user resources
+ available on a system.
+ """
+ def setUp(self):
+ self.alice = ('alice', 'x', 123, 456, 'Alice,,,', self.mktemp(), '/bin/sh')
+ self.bob = ('bob', 'x', 234, 567, 'Bob,,,', self.mktemp(), '/bin/sh')
+ self.database = _PasswordDatabase([self.alice, self.bob])
+ self.directory = distrib.UserDirectory(self.database)
+
+
+ def test_interface(self):
+ """
+ L{UserDirectory} instances provide L{resource.IResource}.
+ """
+ self.assertTrue(verifyObject(resource.IResource, self.directory))
+
+
+ def _404Test(self, name):
+ """
+ Verify that requesting the C{name} child of C{self.directory} results
+ in a 404 response.
+ """
+ request = DummyRequest([name])
+ result = self.directory.getChild(name, request)
+ d = _render(result, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, 404)
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_getInvalidUser(self):
+ """
+ L{UserDirectory.getChild} returns a resource which renders a 404
+ response when passed a string which does not correspond to any known
+ user.
+ """
+ return self._404Test('carol')
+
+
+ def test_getUserWithoutResource(self):
+ """
+ L{UserDirectory.getChild} returns a resource which renders a 404
+ response when passed a string which corresponds to a known user who has
+ neither a user directory nor a user distrib socket.
+ """
+ return self._404Test('alice')
+
+
+ def test_getPublicHTMLChild(self):
+ """
+ L{UserDirectory.getChild} returns a L{static.File} instance when passed
+ the name of a user with a home directory containing a I{public_html}
+ directory.
+ """
+ home = filepath.FilePath(self.bob[-2])
+ public_html = home.child('public_html')
+ public_html.makedirs()
+ request = DummyRequest(['bob'])
+ result = self.directory.getChild('bob', request)
+ self.assertIsInstance(result, static.File)
+ self.assertEqual(result.path, public_html.path)
+
+
+ def test_getDistribChild(self):
+ """
+ L{UserDirectory.getChild} returns a L{ResourceSubscription} instance
+ when passed the name of a user suffixed with C{".twistd"} who has a
+ home directory containing a I{.twistd-web-pb} socket.
+ """
+ home = filepath.FilePath(self.bob[-2])
+ home.makedirs()
+ web = home.child('.twistd-web-pb')
+ request = DummyRequest(['bob'])
+ result = self.directory.getChild('bob.twistd', request)
+ self.assertIsInstance(result, distrib.ResourceSubscription)
+ self.assertEqual(result.host, 'unix')
+ self.assertEqual(abspath(result.port), web.path)
+
+
+ def test_invalidMethod(self):
+ """
+ L{UserDirectory.render} raises L{UnsupportedMethod} in response to a
+ non-I{GET} request.
+ """
+ request = DummyRequest([''])
+ request.method = 'POST'
+ self.assertRaises(
+ server.UnsupportedMethod, self.directory.render, request)
+
+
+ def test_render(self):
+ """
+ L{UserDirectory} renders a list of links to available user content
+ in response to a I{GET} request.
+ """
+ public_html = filepath.FilePath(self.alice[-2]).child('public_html')
+ public_html.makedirs()
+ web = filepath.FilePath(self.bob[-2])
+ web.makedirs()
+ # This really only works if it's a unix socket, but the implementation
+ # doesn't currently check for that. It probably should someday, and
+ # then skip users with non-sockets.
+ web.child('.twistd-web-pb').setContent("")
+
+ request = DummyRequest([''])
+ result = _render(self.directory, request)
+ def cbRendered(ignored):
+ document = parseString(''.join(request.written))
+
+ # Each user should have an li with a link to their page.
+ [alice, bob] = document.getElementsByTagName('li')
+ self.assertEqual(alice.firstChild.tagName, 'a')
+ self.assertEqual(alice.firstChild.getAttribute('href'), 'alice/')
+ self.assertEqual(alice.firstChild.firstChild.data, 'Alice (file)')
+ self.assertEqual(bob.firstChild.tagName, 'a')
+ self.assertEqual(bob.firstChild.getAttribute('href'), 'bob.twistd/')
+ self.assertEqual(bob.firstChild.firstChild.data, 'Bob (twistd)')
+
+ result.addCallback(cbRendered)
+ return result
+
+
+ def test_passwordDatabase(self):
+ """
+ If L{UserDirectory} is instantiated with no arguments, it uses the
+ L{pwd} module as its password database.
+ """
+ directory = distrib.UserDirectory()
+ self.assertIdentical(directory._pwd, pwd)
+ if pwd is None:
+ test_passwordDatabase.skip = "pwd module required"
+
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_domhelpers.py b/vendor/Twisted-10.0.0/twisted/web/test/test_domhelpers.py
new file mode 100644
index 0000000000..17113f6752
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_domhelpers.py
@@ -0,0 +1,306 @@
+# -*- test-case-name: twisted.web.test.test_domhelpers -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Specific tests for (some of) the methods in L{twisted.web.domhelpers}.
+"""
+
+from xml.dom import minidom
+
+from twisted.trial.unittest import TestCase
+
+from twisted.web import microdom
+
+from twisted.web import domhelpers
+
+
+class DOMHelpersTestsMixin:
+ """
+ A mixin for L{TestCase} subclasses which defines test methods for
+ domhelpers functionality based on a DOM creation function provided by a
+ subclass.
+ """
+ dom = None
+
+ def test_getElementsByTagName(self):
+ doc1 = self.dom.parseString('<foo/>')
+ actual=domhelpers.getElementsByTagName(doc1, 'foo')[0].nodeName
+ expected='foo'
+ self.assertEquals(actual, expected)
+ el1=doc1.documentElement
+ actual=domhelpers.getElementsByTagName(el1, 'foo')[0].nodeName
+ self.assertEqual(actual, expected)
+
+ doc2_xml='<a><foo in="a"/><b><foo in="b"/></b><c><foo in="c"/></c><foo in="d"/><foo in="ef"/><g><foo in="g"/><h><foo in="h"/></h></g></a>'
+ doc2 = self.dom.parseString(doc2_xml)
+ tag_list=domhelpers.getElementsByTagName(doc2, 'foo')
+ actual=''.join([node.getAttribute('in') for node in tag_list])
+ expected='abcdefgh'
+ self.assertEquals(actual, expected)
+ el2=doc2.documentElement
+ tag_list=domhelpers.getElementsByTagName(el2, 'foo')
+ actual=''.join([node.getAttribute('in') for node in tag_list])
+ self.assertEqual(actual, expected)
+
+ doc3_xml='''
+<a><foo in="a"/>
+ <b><foo in="b"/>
+ <d><foo in="d"/>
+ <g><foo in="g"/></g>
+ <h><foo in="h"/></h>
+ </d>
+ <e><foo in="e"/>
+ <i><foo in="i"/></i>
+ </e>
+ </b>
+ <c><foo in="c"/>
+ <f><foo in="f"/>
+ <j><foo in="j"/></j>
+ </f>
+ </c>
+</a>'''
+ doc3 = self.dom.parseString(doc3_xml)
+ tag_list=domhelpers.getElementsByTagName(doc3, 'foo')
+ actual=''.join([node.getAttribute('in') for node in tag_list])
+ expected='abdgheicfj'
+ self.assertEquals(actual, expected)
+ el3=doc3.documentElement
+ tag_list=domhelpers.getElementsByTagName(el3, 'foo')
+ actual=''.join([node.getAttribute('in') for node in tag_list])
+ self.assertEqual(actual, expected)
+
+ doc4_xml='<foo><bar></bar><baz><foo/></baz></foo>'
+ doc4 = self.dom.parseString(doc4_xml)
+ actual=domhelpers.getElementsByTagName(doc4, 'foo')
+ root=doc4.documentElement
+ expected=[root, root.childNodes[-1].childNodes[0]]
+ self.assertEquals(actual, expected)
+ actual=domhelpers.getElementsByTagName(root, 'foo')
+ self.assertEqual(actual, expected)
+
+
+ def test_gatherTextNodes(self):
+ doc1 = self.dom.parseString('<a>foo</a>')
+ actual=domhelpers.gatherTextNodes(doc1)
+ expected='foo'
+ self.assertEqual(actual, expected)
+ actual=domhelpers.gatherTextNodes(doc1.documentElement)
+ self.assertEqual(actual, expected)
+
+ doc2_xml='<a>a<b>b</b><c>c</c>def<g>g<h>h</h></g></a>'
+ doc2 = self.dom.parseString(doc2_xml)
+ actual=domhelpers.gatherTextNodes(doc2)
+ expected='abcdefgh'
+ self.assertEqual(actual, expected)
+ actual=domhelpers.gatherTextNodes(doc2.documentElement)
+ self.assertEqual(actual, expected)
+
+ doc3_xml=('<a>a<b>b<d>d<g>g</g><h>h</h></d><e>e<i>i</i></e></b>' +
+ '<c>c<f>f<j>j</j></f></c></a>')
+ doc3 = self.dom.parseString(doc3_xml)
+ actual=domhelpers.gatherTextNodes(doc3)
+ expected='abdgheicfj'
+ self.assertEqual(actual, expected)
+ actual=domhelpers.gatherTextNodes(doc3.documentElement)
+ self.assertEqual(actual, expected)
+
+ def test_clearNode(self):
+ doc1 = self.dom.parseString('<a><b><c><d/></c></b></a>')
+ a_node=doc1.documentElement
+ domhelpers.clearNode(a_node)
+ self.assertEqual(
+ a_node.toxml(),
+ self.dom.Element('a').toxml())
+
+ doc2 = self.dom.parseString('<a><b><c><d/></c></b></a>')
+ b_node=doc2.documentElement.childNodes[0]
+ domhelpers.clearNode(b_node)
+ actual=doc2.documentElement.toxml()
+ expected = self.dom.Element('a')
+ expected.appendChild(self.dom.Element('b'))
+ self.assertEqual(actual, expected.toxml())
+
+
+ def test_get(self):
+ doc1 = self.dom.parseString('<a><b id="bar"/><c class="foo"/></a>')
+ node=domhelpers.get(doc1, "foo")
+ actual=node.toxml()
+ expected = self.dom.Element('c')
+ expected.setAttribute('class', 'foo')
+ self.assertEqual(actual, expected.toxml())
+
+ node=domhelpers.get(doc1, "bar")
+ actual=node.toxml()
+ expected = self.dom.Element('b')
+ expected.setAttribute('id', 'bar')
+ self.assertEqual(actual, expected.toxml())
+
+ self.assertRaises(domhelpers.NodeLookupError,
+ domhelpers.get,
+ doc1,
+ "pzork")
+
+ def test_getIfExists(self):
+ doc1 = self.dom.parseString('<a><b id="bar"/><c class="foo"/></a>')
+ node=domhelpers.getIfExists(doc1, "foo")
+ actual=node.toxml()
+ expected = self.dom.Element('c')
+ expected.setAttribute('class', 'foo')
+ self.assertEqual(actual, expected.toxml())
+
+ node=domhelpers.getIfExists(doc1, "pzork")
+ self.assertIdentical(node, None)
+
+
+ def test_getAndClear(self):
+ doc1 = self.dom.parseString('<a><b id="foo"><c></c></b></a>')
+ node=domhelpers.getAndClear(doc1, "foo")
+ actual=node.toxml()
+ expected = self.dom.Element('b')
+ expected.setAttribute('id', 'foo')
+ self.assertEqual(actual, expected.toxml())
+
+
+ def test_locateNodes(self):
+ doc1 = self.dom.parseString('<a><b foo="olive"><c foo="olive"/></b><d foo="poopy"/></a>')
+ node_list=domhelpers.locateNodes(
+ doc1.childNodes, 'foo', 'olive', noNesting=1)
+ actual=''.join([node.toxml() for node in node_list])
+ expected = self.dom.Element('b')
+ expected.setAttribute('foo', 'olive')
+ c = self.dom.Element('c')
+ c.setAttribute('foo', 'olive')
+ expected.appendChild(c)
+
+ self.assertEqual(actual, expected.toxml())
+
+ node_list=domhelpers.locateNodes(
+ doc1.childNodes, 'foo', 'olive', noNesting=0)
+ actual=''.join([node.toxml() for node in node_list])
+ self.assertEqual(actual, expected.toxml() + c.toxml())
+
+
+ def test_getParents(self):
+ doc1 = self.dom.parseString('<a><b><c><d/></c><e/></b><f/></a>')
+ node_list = domhelpers.getParents(
+ doc1.childNodes[0].childNodes[0].childNodes[0])
+ actual = ''.join([node.tagName for node in node_list
+ if hasattr(node, 'tagName')])
+ self.assertEqual(actual, 'cba')
+
+
+ def test_findElementsWithAttribute(self):
+ doc1 = self.dom.parseString('<a foo="1"><b foo="2"/><c foo="1"/><d/></a>')
+ node_list = domhelpers.findElementsWithAttribute(doc1, 'foo')
+ actual = ''.join([node.tagName for node in node_list])
+ self.assertEqual(actual, 'abc')
+
+ node_list = domhelpers.findElementsWithAttribute(doc1, 'foo', '1')
+ actual = ''.join([node.tagName for node in node_list])
+ self.assertEqual(actual, 'ac')
+
+
+ def test_findNodesNamed(self):
+ doc1 = self.dom.parseString('<doc><foo/><bar/><foo>a</foo></doc>')
+ node_list = domhelpers.findNodesNamed(doc1, 'foo')
+ actual = len(node_list)
+ self.assertEqual(actual, 2)
+
+ # NOT SURE WHAT THESE ARE SUPPOSED TO DO..
+ # def test_RawText FIXME
+ # def test_superSetAttribute FIXME
+ # def test_superPrependAttribute FIXME
+ # def test_superAppendAttribute FIXME
+ # def test_substitute FIXME
+
+ def test_escape(self):
+ j='this string " contains many & characters> xml< won\'t like'
+ expected='this string &quot; contains many &amp; characters&gt; xml&lt; won\'t like'
+ self.assertEqual(domhelpers.escape(j), expected)
+
+ def test_unescape(self):
+ j='this string &quot; has &&amp; entities &gt; &lt; and some characters xml won\'t like<'
+ expected='this string " has && entities > < and some characters xml won\'t like<'
+ self.assertEqual(domhelpers.unescape(j), expected)
+
+
+ def test_getNodeText(self):
+ """
+ L{getNodeText} returns the concatenation of all the text data at or
+ beneath the node passed to it.
+ """
+ node = self.dom.parseString('<foo><bar>baz</bar><bar>quux</bar></foo>')
+ self.assertEqual(domhelpers.getNodeText(node), "bazquux")
+
+
+
+class MicroDOMHelpersTests(DOMHelpersTestsMixin, TestCase):
+ dom = microdom
+
+ def test_gatherTextNodesDropsWhitespace(self):
+ """
+ Microdom discards whitespace-only text nodes, so L{gatherTextNodes}
+ returns only the text from nodes which had non-whitespace characters.
+ """
+ doc4_xml='''<html>
+ <head>
+ </head>
+ <body>
+ stuff
+ </body>
+</html>
+'''
+ doc4 = self.dom.parseString(doc4_xml)
+ actual = domhelpers.gatherTextNodes(doc4)
+ expected = '\n stuff\n '
+ self.assertEqual(actual, expected)
+ actual = domhelpers.gatherTextNodes(doc4.documentElement)
+ self.assertEqual(actual, expected)
+
+
+ def test_textEntitiesNotDecoded(self):
+ """
+ Microdom does not decode entities in text nodes.
+ """
+ doc5_xml='<x>Souffl&amp;</x>'
+ doc5 = self.dom.parseString(doc5_xml)
+ actual=domhelpers.gatherTextNodes(doc5)
+ expected='Souffl&amp;'
+ self.assertEqual(actual, expected)
+ actual=domhelpers.gatherTextNodes(doc5.documentElement)
+ self.assertEqual(actual, expected)
+
+
+
+class MiniDOMHelpersTests(DOMHelpersTestsMixin, TestCase):
+ dom = minidom
+
+ def test_textEntitiesDecoded(self):
+ """
+ Minidom does decode entities in text nodes.
+ """
+ doc5_xml='<x>Souffl&amp;</x>'
+ doc5 = self.dom.parseString(doc5_xml)
+ actual=domhelpers.gatherTextNodes(doc5)
+ expected='Souffl&'
+ self.assertEqual(actual, expected)
+ actual=domhelpers.gatherTextNodes(doc5.documentElement)
+ self.assertEqual(actual, expected)
+
+
+ def test_getNodeUnicodeText(self):
+ """
+ L{domhelpers.getNodeText} returns a C{unicode} string when text
+ nodes are represented in the DOM with unicode, whether or not there
+ are non-ASCII characters present.
+ """
+ node = self.dom.parseString("<foo>bar</foo>")
+ text = domhelpers.getNodeText(node)
+ self.assertEqual(text, u"bar")
+ self.assertIsInstance(text, unicode)
+
+ node = self.dom.parseString(u"<foo>\N{SNOWMAN}</foo>".encode('utf-8'))
+ text = domhelpers.getNodeText(node)
+ self.assertEqual(text, u"\N{SNOWMAN}")
+ self.assertIsInstance(text, unicode)
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_error.py b/vendor/Twisted-10.0.0/twisted/web/test/test_error.py
new file mode 100644
index 0000000000..4de7738dd5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_error.py
@@ -0,0 +1,151 @@
+# Copyright (c) 2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+HTTP errors.
+"""
+
+from twisted.trial import unittest
+from twisted.web import error
+
+class ErrorTestCase(unittest.TestCase):
+ """
+ Tests for how L{Error} attributes are initialized.
+ """
+ def test_noMessageValidStatus(self):
+ """
+ If no C{message} argument is passed to the L{Error} constructor and the
+ C{code} argument is a valid HTTP status code, C{code} is mapped to a
+ descriptive string to which C{message} is assigned.
+ """
+ e = error.Error("200")
+ self.assertEquals(e.message, "OK")
+
+
+ def test_noMessageInvalidStatus(self):
+ """
+ If no C{message} argument is passed to the L{Error} constructor and
+ C{code} isn't a valid HTTP status code, C{message} stays C{None}.
+ """
+ e = error.Error("InvalidCode")
+ self.assertEquals(e.message, None)
+
+
+ def test_messageExists(self):
+ """
+ If a C{message} argument is passed to the L{Error} constructor, the
+ C{message} isn't affected by the value of C{status}.
+ """
+ e = error.Error("200", "My own message")
+ self.assertEquals(e.message, "My own message")
+
+
+
+class PageRedirectTestCase(unittest.TestCase):
+ """
+ Tests for how L{PageRedirect} attributes are initialized.
+ """
+ def test_noMessageValidStatus(self):
+ """
+ If no C{message} argument is passed to the L{PageRedirect} constructor
+ and the C{code} argument is a valid HTTP status code, C{code} is mapped
+ to a descriptive string to which C{message} is assigned.
+ """
+ e = error.PageRedirect("200", location="/foo")
+ self.assertEquals(e.message, "OK to /foo")
+
+
+ def test_noMessageValidStatusNoLocation(self):
+ """
+ If no C{message} argument is passed to the L{PageRedirect} constructor
+ and C{location} is also empty and the C{code} argument is a valid HTTP
+ status code, C{code} is mapped to a descriptive string to which
+ C{message} is assigned without trying to include an empty location.
+ """
+ e = error.PageRedirect("200")
+ self.assertEquals(e.message, "OK")
+
+
+ def test_noMessageInvalidStatusLocationExists(self):
+ """
+ If no C{message} argument is passed to the L{PageRedirect} constructor
+ and C{code} isn't a valid HTTP status code, C{message} stays C{None}.
+ """
+ e = error.PageRedirect("InvalidCode", location="/foo")
+ self.assertEquals(e.message, None)
+
+
+ def test_messageExistsLocationExists(self):
+ """
+ If a C{message} argument is passed to the L{PageRedirect} constructor,
+ the C{message} isn't affected by the value of C{status}.
+ """
+ e = error.PageRedirect("200", "My own message", location="/foo")
+ self.assertEquals(e.message, "My own message to /foo")
+
+
+ def test_messageExistsNoLocation(self):
+ """
+ If a C{message} argument is passed to the L{PageRedirect} constructor
+ and no location is provided, C{message} doesn't try to include the empty
+ location.
+ """
+ e = error.PageRedirect("200", "My own message")
+ self.assertEquals(e.message, "My own message")
+
+
+
+class InfiniteRedirectionTestCase(unittest.TestCase):
+ """
+ Tests for how L{InfiniteRedirection} attributes are initialized.
+ """
+ def test_noMessageValidStatus(self):
+ """
+ If no C{message} argument is passed to the L{InfiniteRedirection}
+ constructor and the C{code} argument is a valid HTTP status code,
+ C{code} is mapped to a descriptive string to which C{message} is
+ assigned.
+ """
+ e = error.InfiniteRedirection("200", location="/foo")
+ self.assertEquals(e.message, "OK to /foo")
+
+
+ def test_noMessageValidStatusNoLocation(self):
+ """
+ If no C{message} argument is passed to the L{InfiniteRedirection}
+ constructor and C{location} is also empty and the C{code} argument is a
+ valid HTTP status code, C{code} is mapped to a descriptive string to
+ which C{message} is assigned without trying to include an empty
+ location.
+ """
+ e = error.InfiniteRedirection("200")
+ self.assertEquals(e.message, "OK")
+
+
+ def test_noMessageInvalidStatusLocationExists(self):
+ """
+ If no C{message} argument is passed to the L{InfiniteRedirection}
+ constructor and C{code} isn't a valid HTTP status code, C{message} stays
+ C{None}.
+ """
+ e = error.InfiniteRedirection("InvalidCode", location="/foo")
+ self.assertEquals(e.message, None)
+
+
+ def test_messageExistsLocationExists(self):
+ """
+ If a C{message} argument is passed to the L{InfiniteRedirection}
+ constructor, the C{message} isn't affected by the value of C{status}.
+ """
+ e = error.InfiniteRedirection("200", "My own message", location="/foo")
+ self.assertEquals(e.message, "My own message to /foo")
+
+
+ def test_messageExistsNoLocation(self):
+ """
+ If a C{message} argument is passed to the L{InfiniteRedirection}
+ constructor and no location is provided, C{message} doesn't try to
+ include the empty location.
+ """
+ e = error.InfiniteRedirection("200", "My own message")
+ self.assertEquals(e.message, "My own message")
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_http.py b/vendor/Twisted-10.0.0/twisted/web/test/test_http.py
new file mode 100644
index 0000000000..d2b2f6c65d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_http.py
@@ -0,0 +1,1531 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test HTTP support.
+"""
+
+from urlparse import urlparse, urlunsplit, clear_cache
+import random, urllib, cgi
+
+from twisted.python.compat import set
+from twisted.python.failure import Failure
+from twisted.trial import unittest
+from twisted.trial.unittest import TestCase
+from twisted.web import http, http_headers
+from twisted.web.http import PotentialDataLoss, _DataLoss
+from twisted.web.http import _IdentityTransferDecoder
+from twisted.protocols import loopback
+from twisted.internet.task import Clock
+from twisted.internet.error import ConnectionLost
+from twisted.test.proto_helpers import StringTransport
+from twisted.test.test_internet import DummyProducer
+from twisted.web.test.test_web import DummyChannel
+
+
+class DateTimeTest(unittest.TestCase):
+ """Test date parsing functions."""
+
+ def testRoundtrip(self):
+ for i in range(10000):
+ time = random.randint(0, 2000000000)
+ timestr = http.datetimeToString(time)
+ time2 = http.stringToDatetime(timestr)
+ self.assertEquals(time, time2)
+
+
+class DummyHTTPHandler(http.Request):
+
+ def process(self):
+ self.content.seek(0, 0)
+ data = self.content.read()
+ length = self.getHeader('content-length')
+ request = "'''\n"+str(length)+"\n"+data+"'''\n"
+ self.setResponseCode(200)
+ self.setHeader("Request", self.uri)
+ self.setHeader("Command", self.method)
+ self.setHeader("Version", self.clientproto)
+ self.setHeader("Content-Length", len(request))
+ self.write(request)
+ self.finish()
+
+
+class LoopbackHTTPClient(http.HTTPClient):
+
+ def connectionMade(self):
+ self.sendCommand("GET", "/foo/bar")
+ self.sendHeader("Content-Length", 10)
+ self.endHeaders()
+ self.transport.write("0123456789")
+
+
+class ResponseTestMixin(object):
+ """
+ A mixin that provides a simple means of comparing an actual response string
+ to an expected response string by performing the minimal parsing.
+ """
+
+ def assertResponseEquals(self, responses, expected):
+ """
+ Assert that the C{responses} matches the C{expected} responses.
+
+ @type responses: C{str}
+ @param responses: The bytes sent in response to one or more requests.
+
+ @type expected: C{list} of C{tuple} of C{str}
+ @param expected: The expected values for the responses. Each tuple
+ element of the list represents one response. Each string element
+ of the tuple is a full header line without delimiter, except for
+ the last element which gives the full response body.
+ """
+ for response in expected:
+ expectedHeaders, expectedContent = response[:-1], response[-1]
+ headers, rest = responses.split('\r\n\r\n', 1)
+ headers = headers.splitlines()
+ self.assertEqual(set(headers), set(expectedHeaders))
+ content = rest[:len(expectedContent)]
+ responses = rest[len(expectedContent):]
+ self.assertEqual(content, expectedContent)
+
+
+
+class HTTP1_0TestCase(unittest.TestCase, ResponseTestMixin):
+ requests = (
+ "GET / HTTP/1.0\r\n"
+ "\r\n"
+ "GET / HTTP/1.1\r\n"
+ "Accept: text/html\r\n"
+ "\r\n")
+
+ expected_response = [
+ ("HTTP/1.0 200 OK",
+ "Request: /",
+ "Command: GET",
+ "Version: HTTP/1.0",
+ "Content-Length: 13",
+ "'''\nNone\n'''\n")]
+
+ def test_buffer(self):
+ """
+ Send requests over a channel and check responses match what is expected.
+ """
+ b = StringTransport()
+ a = http.HTTPChannel()
+ a.requestFactory = DummyHTTPHandler
+ a.makeConnection(b)
+ # one byte at a time, to stress it.
+ for byte in self.requests:
+ a.dataReceived(byte)
+ a.connectionLost(IOError("all one"))
+ value = b.value()
+ self.assertResponseEquals(value, self.expected_response)
+
+
+ def test_requestBodyTimeout(self):
+ """
+ L{HTTPChannel} resets its timeout whenever data from a request body is
+ delivered to it.
+ """
+ clock = Clock()
+ transport = StringTransport()
+ protocol = http.HTTPChannel()
+ protocol.timeOut = 100
+ protocol.callLater = clock.callLater
+ protocol.makeConnection(transport)
+ protocol.dataReceived('POST / HTTP/1.0\r\nContent-Length: 2\r\n\r\n')
+ clock.advance(99)
+ self.assertFalse(transport.disconnecting)
+ protocol.dataReceived('x')
+ clock.advance(99)
+ self.assertFalse(transport.disconnecting)
+ protocol.dataReceived('x')
+ self.assertEqual(len(protocol.requests), 1)
+
+
+
+class HTTP1_1TestCase(HTTP1_0TestCase):
+
+ requests = (
+ "GET / HTTP/1.1\r\n"
+ "Accept: text/html\r\n"
+ "\r\n"
+ "POST / HTTP/1.1\r\n"
+ "Content-Length: 10\r\n"
+ "\r\n"
+ "0123456789POST / HTTP/1.1\r\n"
+ "Content-Length: 10\r\n"
+ "\r\n"
+ "0123456789HEAD / HTTP/1.1\r\n"
+ "\r\n")
+
+ expected_response = [
+ ("HTTP/1.1 200 OK",
+ "Request: /",
+ "Command: GET",
+ "Version: HTTP/1.1",
+ "Content-Length: 13",
+ "'''\nNone\n'''\n"),
+ ("HTTP/1.1 200 OK",
+ "Request: /",
+ "Command: POST",
+ "Version: HTTP/1.1",
+ "Content-Length: 21",
+ "'''\n10\n0123456789'''\n"),
+ ("HTTP/1.1 200 OK",
+ "Request: /",
+ "Command: POST",
+ "Version: HTTP/1.1",
+ "Content-Length: 21",
+ "'''\n10\n0123456789'''\n"),
+ ("HTTP/1.1 200 OK",
+ "Request: /",
+ "Command: HEAD",
+ "Version: HTTP/1.1",
+ "Content-Length: 13",
+ "")]
+
+
+
+class HTTP1_1_close_TestCase(HTTP1_0TestCase):
+
+ requests = (
+ "GET / HTTP/1.1\r\n"
+ "Accept: text/html\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ "GET / HTTP/1.0\r\n"
+ "\r\n")
+
+ expected_response = [
+ ("HTTP/1.1 200 OK",
+ "Connection: close",
+ "Request: /",
+ "Command: GET",
+ "Version: HTTP/1.1",
+ "Content-Length: 13",
+ "'''\nNone\n'''\n")]
+
+
+
+class HTTP0_9TestCase(HTTP1_0TestCase):
+
+ requests = (
+ "GET /\r\n")
+
+ expected_response = "HTTP/1.1 400 Bad Request\r\n\r\n"
+
+
+ def assertResponseEquals(self, response, expectedResponse):
+ self.assertEquals(response, expectedResponse)
+
+
+class HTTPLoopbackTestCase(unittest.TestCase):
+
+ expectedHeaders = {'request' : '/foo/bar',
+ 'command' : 'GET',
+ 'version' : 'HTTP/1.0',
+ 'content-length' : '21'}
+ numHeaders = 0
+ gotStatus = 0
+ gotResponse = 0
+ gotEndHeaders = 0
+
+ def _handleStatus(self, version, status, message):
+ self.gotStatus = 1
+ self.assertEquals(version, "HTTP/1.0")
+ self.assertEquals(status, "200")
+
+ def _handleResponse(self, data):
+ self.gotResponse = 1
+ self.assertEquals(data, "'''\n10\n0123456789'''\n")
+
+ def _handleHeader(self, key, value):
+ self.numHeaders = self.numHeaders + 1
+ self.assertEquals(self.expectedHeaders[key.lower()], value)
+
+ def _handleEndHeaders(self):
+ self.gotEndHeaders = 1
+ self.assertEquals(self.numHeaders, 4)
+
+ def testLoopback(self):
+ server = http.HTTPChannel()
+ server.requestFactory = DummyHTTPHandler
+ client = LoopbackHTTPClient()
+ client.handleResponse = self._handleResponse
+ client.handleHeader = self._handleHeader
+ client.handleEndHeaders = self._handleEndHeaders
+ client.handleStatus = self._handleStatus
+ d = loopback.loopbackAsync(server, client)
+ d.addCallback(self._cbTestLoopback)
+ return d
+
+ def _cbTestLoopback(self, ignored):
+ if not (self.gotStatus and self.gotResponse and self.gotEndHeaders):
+ raise RuntimeError(
+ "didn't got all callbacks %s"
+ % [self.gotStatus, self.gotResponse, self.gotEndHeaders])
+ del self.gotEndHeaders
+ del self.gotResponse
+ del self.gotStatus
+ del self.numHeaders
+
+
+
+def _prequest(**headers):
+ """
+ Make a request with the given request headers for the persistence tests.
+ """
+ request = http.Request(DummyChannel(), None)
+ for k, v in headers.iteritems():
+ request.requestHeaders.setRawHeaders(k, v)
+ return request
+
+
+class PersistenceTestCase(unittest.TestCase):
+ """
+ Tests for persistent HTTP connections.
+ """
+
+ ptests = [#(PRequest(connection="Keep-Alive"), "HTTP/1.0", 1, {'connection' : 'Keep-Alive'}),
+ (_prequest(), "HTTP/1.0", 0, {'connection': None}),
+ (_prequest(connection=["close"]), "HTTP/1.1", 0, {'connection' : ['close']}),
+ (_prequest(), "HTTP/1.1", 1, {'connection': None}),
+ (_prequest(), "HTTP/0.9", 0, {'connection': None}),
+ ]
+
+
+ def testAlgorithm(self):
+ c = http.HTTPChannel()
+ for req, version, correctResult, resultHeaders in self.ptests:
+ result = c.checkPersistence(req, version)
+ self.assertEquals(result, correctResult)
+ for header in resultHeaders.keys():
+ self.assertEquals(req.responseHeaders.getRawHeaders(header, None), resultHeaders[header])
+
+
+
+class IdentityTransferEncodingTests(TestCase):
+ """
+ Tests for L{_IdentityTransferDecoder}.
+ """
+ def setUp(self):
+ """
+ Create an L{_IdentityTransferDecoder} with callbacks hooked up so that
+ calls to them can be inspected.
+ """
+ self.data = []
+ self.finish = []
+ self.contentLength = 10
+ self.decoder = _IdentityTransferDecoder(
+ self.contentLength, self.data.append, self.finish.append)
+
+
+ def test_exactAmountReceived(self):
+ """
+ If L{_IdentityTransferDecoder.dataReceived} is called with a string
+ with length equal to the content length passed to
+ L{_IdentityTransferDecoder}'s initializer, the data callback is invoked
+ with that string and the finish callback is invoked with a zero-length
+ string.
+ """
+ self.decoder.dataReceived('x' * self.contentLength)
+ self.assertEqual(self.data, ['x' * self.contentLength])
+ self.assertEqual(self.finish, [''])
+
+
+ def test_shortStrings(self):
+ """
+ If L{_IdentityTransferDecoder.dataReceived} is called multiple times
+ with strings which, when concatenated, are as long as the content
+ length provided, the data callback is invoked with each string and the
+ finish callback is invoked only after the second call.
+ """
+ self.decoder.dataReceived('x')
+ self.assertEqual(self.data, ['x'])
+ self.assertEqual(self.finish, [])
+ self.decoder.dataReceived('y' * (self.contentLength - 1))
+ self.assertEqual(self.data, ['x', 'y' * (self.contentLength - 1)])
+ self.assertEqual(self.finish, [''])
+
+
+ def test_longString(self):
+ """
+ If L{_IdentityTransferDecoder.dataReceived} is called with a string
+ with length greater than the provided content length, only the prefix
+ of that string up to the content length is passed to the data callback
+ and the remainder is passed to the finish callback.
+ """
+ self.decoder.dataReceived('x' * self.contentLength + 'y')
+ self.assertEqual(self.data, ['x' * self.contentLength])
+ self.assertEqual(self.finish, ['y'])
+
+
+ def test_rejectDataAfterFinished(self):
+ """
+ If data is passed to L{_IdentityTransferDecoder.dataReceived} after the
+ finish callback has been invoked, L{RuntimeError} is raised.
+ """
+ failures = []
+ def finish(bytes):
+ try:
+ decoder.dataReceived('foo')
+ except:
+ failures.append(Failure())
+ decoder = _IdentityTransferDecoder(5, self.data.append, finish)
+ decoder.dataReceived('x' * 4)
+ self.assertEqual(failures, [])
+ decoder.dataReceived('y')
+ failures[0].trap(RuntimeError)
+ self.assertEqual(
+ str(failures[0].value),
+ "_IdentityTransferDecoder cannot decode data after finishing")
+
+
+ def test_unknownContentLength(self):
+ """
+ If L{_IdentityTransferDecoder} is constructed with C{None} for the
+ content length, it passes all data delivered to it through to the data
+ callback.
+ """
+ data = []
+ finish = []
+ decoder = _IdentityTransferDecoder(None, data.append, finish.append)
+ decoder.dataReceived('x')
+ self.assertEqual(data, ['x'])
+ decoder.dataReceived('y')
+ self.assertEqual(data, ['x', 'y'])
+ self.assertEqual(finish, [])
+
+
+ def _verifyCallbacksUnreferenced(self, decoder):
+ """
+ Check the decoder's data and finish callbacks and make sure they are
+ None in order to help avoid references cycles.
+ """
+ self.assertIdentical(decoder.dataCallback, None)
+ self.assertIdentical(decoder.finishCallback, None)
+
+
+ def test_earlyConnectionLose(self):
+ """
+ L{_IdentityTransferDecoder.noMoreData} raises L{_DataLoss} if it is
+ called and the content length is known but not enough bytes have been
+ delivered.
+ """
+ self.decoder.dataReceived('x' * (self.contentLength - 1))
+ self.assertRaises(_DataLoss, self.decoder.noMoreData)
+ self._verifyCallbacksUnreferenced(self.decoder)
+
+
+ def test_unknownContentLengthConnectionLose(self):
+ """
+ L{_IdentityTransferDecoder.noMoreData} calls the finish callback and
+ raises L{PotentialDataLoss} if it is called and the content length is
+ unknown.
+ """
+ body = []
+ finished = []
+ decoder = _IdentityTransferDecoder(None, body.append, finished.append)
+ self.assertRaises(PotentialDataLoss, decoder.noMoreData)
+ self.assertEqual(body, [])
+ self.assertEqual(finished, [''])
+ self._verifyCallbacksUnreferenced(decoder)
+
+
+ def test_finishedConnectionLose(self):
+ """
+ L{_IdentityTransferDecoder.noMoreData} does not raise any exception if
+ it is called when the content length is known and that many bytes have
+ been delivered.
+ """
+ self.decoder.dataReceived('x' * self.contentLength)
+ self.decoder.noMoreData()
+ self._verifyCallbacksUnreferenced(self.decoder)
+
+
+
+class ChunkedTransferEncodingTests(unittest.TestCase):
+ """
+ Tests for L{_ChunkedTransferDecoder}, which turns a byte stream encoded
+ using HTTP I{chunked} C{Transfer-Encoding} back into the original byte
+ stream.
+ """
+ def test_decoding(self):
+ """
+ L{_ChunkedTransferDecoder.dataReceived} decodes chunked-encoded data
+ and passes the result to the specified callback.
+ """
+ L = []
+ p = http._ChunkedTransferDecoder(L.append, None)
+ p.dataReceived('3\r\nabc\r\n5\r\n12345\r\n')
+ p.dataReceived('a\r\n0123456789\r\n')
+ self.assertEqual(L, ['abc', '12345', '0123456789'])
+
+
+ def test_short(self):
+ """
+ L{_ChunkedTransferDecoder.dataReceived} decodes chunks broken up and
+ delivered in multiple calls.
+ """
+ L = []
+ finished = []
+ p = http._ChunkedTransferDecoder(L.append, finished.append)
+ for s in '3\r\nabc\r\n5\r\n12345\r\n0\r\n\r\n':
+ p.dataReceived(s)
+ self.assertEqual(L, ['a', 'b', 'c', '1', '2', '3', '4', '5'])
+ self.assertEqual(finished, [''])
+
+
+ def test_newlines(self):
+ """
+ L{_ChunkedTransferDecoder.dataReceived} doesn't treat CR LF pairs
+ embedded in chunk bodies specially.
+ """
+ L = []
+ p = http._ChunkedTransferDecoder(L.append, None)
+ p.dataReceived('2\r\n\r\n\r\n')
+ self.assertEqual(L, ['\r\n'])
+
+
+ def test_extensions(self):
+ """
+ L{_ChunkedTransferDecoder.dataReceived} disregards chunk-extension
+ fields.
+ """
+ L = []
+ p = http._ChunkedTransferDecoder(L.append, None)
+ p.dataReceived('3; x-foo=bar\r\nabc\r\n')
+ self.assertEqual(L, ['abc'])
+
+
+ def test_finish(self):
+ """
+ L{_ChunkedTransferDecoder.dataReceived} interprets a zero-length
+ chunk as the end of the chunked data stream and calls the completion
+ callback.
+ """
+ finished = []
+ p = http._ChunkedTransferDecoder(None, finished.append)
+ p.dataReceived('0\r\n\r\n')
+ self.assertEqual(finished, [''])
+
+
+ def test_extra(self):
+ """
+ L{_ChunkedTransferDecoder.dataReceived} passes any bytes which come
+ after the terminating zero-length chunk to the completion callback.
+ """
+ finished = []
+ p = http._ChunkedTransferDecoder(None, finished.append)
+ p.dataReceived('0\r\n\r\nhello')
+ self.assertEqual(finished, ['hello'])
+
+
+ def test_afterFinished(self):
+ """
+ L{_ChunkedTransferDecoder.dataReceived} raises L{RuntimeError} if it
+ is called after it has seen the last chunk.
+ """
+ p = http._ChunkedTransferDecoder(None, lambda bytes: None)
+ p.dataReceived('0\r\n\r\n')
+ self.assertRaises(RuntimeError, p.dataReceived, 'hello')
+
+
+ def test_earlyConnectionLose(self):
+ """
+ L{_ChunkedTransferDecoder.noMoreData} raises L{_DataLoss} if it is
+ called and the end of the last trailer has not yet been received.
+ """
+ parser = http._ChunkedTransferDecoder(None, lambda bytes: None)
+ parser.dataReceived('0\r\n\r')
+ exc = self.assertRaises(_DataLoss, parser.noMoreData)
+ self.assertEqual(
+ str(exc),
+ "Chunked decoder in 'trailer' state, still expecting more data "
+ "to get to finished state.")
+
+
+ def test_finishedConnectionLose(self):
+ """
+ L{_ChunkedTransferDecoder.noMoreData} does not raise any exception if
+ it is called after the terminal zero length chunk is received.
+ """
+ parser = http._ChunkedTransferDecoder(None, lambda bytes: None)
+ parser.dataReceived('0\r\n\r\n')
+ parser.noMoreData()
+
+
+ def test_reentrantFinishedNoMoreData(self):
+ """
+ L{_ChunkedTransferDecoder.noMoreData} can be called from the finished
+ callback without raising an exception.
+ """
+ errors = []
+ successes = []
+ def finished(extra):
+ try:
+ parser.noMoreData()
+ except:
+ errors.append(Failure())
+ else:
+ successes.append(True)
+ parser = http._ChunkedTransferDecoder(None, finished)
+ parser.dataReceived('0\r\n\r\n')
+ self.assertEqual(errors, [])
+ self.assertEqual(successes, [True])
+
+
+
+class ChunkingTestCase(unittest.TestCase):
+
+ strings = ["abcv", "", "fdfsd423", "Ffasfas\r\n",
+ "523523\n\rfsdf", "4234"]
+
+ def testChunks(self):
+ for s in self.strings:
+ self.assertEquals((s, ''), http.fromChunk(''.join(http.toChunk(s))))
+ self.assertRaises(ValueError, http.fromChunk, '-5\r\nmalformed!\r\n')
+
+ def testConcatenatedChunks(self):
+ chunked = ''.join([''.join(http.toChunk(t)) for t in self.strings])
+ result = []
+ buffer = ""
+ for c in chunked:
+ buffer = buffer + c
+ try:
+ data, buffer = http.fromChunk(buffer)
+ result.append(data)
+ except ValueError:
+ pass
+ self.assertEquals(result, self.strings)
+
+
+
+class ParsingTestCase(unittest.TestCase):
+ """
+ Tests for protocol parsing in L{HTTPChannel}.
+ """
+ def runRequest(self, httpRequest, requestClass, success=1):
+ httpRequest = httpRequest.replace("\n", "\r\n")
+ b = StringTransport()
+ a = http.HTTPChannel()
+ a.requestFactory = requestClass
+ a.makeConnection(b)
+ # one byte at a time, to stress it.
+ for byte in httpRequest:
+ if a.transport.disconnecting:
+ break
+ a.dataReceived(byte)
+ a.connectionLost(IOError("all done"))
+ if success:
+ self.assertEquals(self.didRequest, 1)
+ del self.didRequest
+ else:
+ self.assert_(not hasattr(self, "didRequest"))
+ return a
+
+
+ def test_basicAuth(self):
+ """
+ L{HTTPChannel} provides username and password information supplied in
+ an I{Authorization} header to the L{Request} which makes it available
+ via its C{getUser} and C{getPassword} methods.
+ """
+ testcase = self
+ class Request(http.Request):
+ l = []
+ def process(self):
+ testcase.assertEquals(self.getUser(), self.l[0])
+ testcase.assertEquals(self.getPassword(), self.l[1])
+ for u, p in [("foo", "bar"), ("hello", "there:z")]:
+ Request.l[:] = [u, p]
+ s = "%s:%s" % (u, p)
+ f = "GET / HTTP/1.0\nAuthorization: Basic %s\n\n" % (s.encode("base64").strip(), )
+ self.runRequest(f, Request, 0)
+
+
+ def test_headers(self):
+ """
+ Headers received by L{HTTPChannel} in a request are made available to
+ the L{Request}.
+ """
+ processed = []
+ class MyRequest(http.Request):
+ def process(self):
+ processed.append(self)
+ self.finish()
+
+ requestLines = [
+ "GET / HTTP/1.0",
+ "Foo: bar",
+ "baz: Quux",
+ "baz: quux",
+ "",
+ ""]
+
+ self.runRequest('\n'.join(requestLines), MyRequest, 0)
+ [request] = processed
+ self.assertEquals(
+ request.requestHeaders.getRawHeaders('foo'), ['bar'])
+ self.assertEquals(
+ request.requestHeaders.getRawHeaders('bAz'), ['Quux', 'quux'])
+
+
+ def test_tooManyHeaders(self):
+ """
+ L{HTTPChannel} enforces a limit of C{HTTPChannel.maxHeaders} on the
+ number of headers received per request.
+ """
+ processed = []
+ class MyRequest(http.Request):
+ def process(self):
+ processed.append(self)
+
+ requestLines = ["GET / HTTP/1.0"]
+ for i in range(http.HTTPChannel.maxHeaders + 2):
+ requestLines.append("%s: foo" % (i,))
+ requestLines.extend(["", ""])
+
+ channel = self.runRequest("\n".join(requestLines), MyRequest, 0)
+ self.assertEqual(processed, [])
+ self.assertEqual(
+ channel.transport.value(),
+ "HTTP/1.1 400 Bad Request\r\n\r\n")
+
+
+ def test_headerLimitPerRequest(self):
+ """
+ L{HTTPChannel} enforces the limit of C{HTTPChannel.maxHeaders} per
+ request so that headers received in an earlier request do not count
+ towards the limit when processing a later request.
+ """
+ processed = []
+ class MyRequest(http.Request):
+ def process(self):
+ processed.append(self)
+ self.finish()
+
+ self.patch(http.HTTPChannel, 'maxHeaders', 1)
+ requestLines = [
+ "GET / HTTP/1.1",
+ "Foo: bar",
+ "",
+ "",
+ "GET / HTTP/1.1",
+ "Bar: baz",
+ "",
+ ""]
+
+ channel = self.runRequest("\n".join(requestLines), MyRequest, 0)
+ [first, second] = processed
+ self.assertEqual(first.getHeader('foo'), 'bar')
+ self.assertEqual(second.getHeader('bar'), 'baz')
+ self.assertEqual(
+ channel.transport.value(),
+ 'HTTP/1.1 200 OK\r\n'
+ 'Transfer-Encoding: chunked\r\n'
+ '\r\n'
+ '0\r\n'
+ '\r\n'
+ 'HTTP/1.1 200 OK\r\n'
+ 'Transfer-Encoding: chunked\r\n'
+ '\r\n'
+ '0\r\n'
+ '\r\n')
+
+
+ def testCookies(self):
+ """
+ Test cookies parsing and reading.
+ """
+ httpRequest = '''\
+GET / HTTP/1.0
+Cookie: rabbit="eat carrot"; ninja=secret; spam="hey 1=1!"
+
+'''
+ testcase = self
+
+ class MyRequest(http.Request):
+ def process(self):
+ testcase.assertEquals(self.getCookie('rabbit'), '"eat carrot"')
+ testcase.assertEquals(self.getCookie('ninja'), 'secret')
+ testcase.assertEquals(self.getCookie('spam'), '"hey 1=1!"')
+ testcase.didRequest = 1
+ self.finish()
+
+ self.runRequest(httpRequest, MyRequest)
+
+ def testGET(self):
+ httpRequest = '''\
+GET /?key=value&multiple=two+words&multiple=more%20words&empty= HTTP/1.0
+
+'''
+ testcase = self
+ class MyRequest(http.Request):
+ def process(self):
+ testcase.assertEquals(self.method, "GET")
+ testcase.assertEquals(self.args["key"], ["value"])
+ testcase.assertEquals(self.args["empty"], [""])
+ testcase.assertEquals(self.args["multiple"], ["two words", "more words"])
+ testcase.didRequest = 1
+ self.finish()
+
+ self.runRequest(httpRequest, MyRequest)
+
+
+ def test_extraQuestionMark(self):
+ """
+ While only a single '?' is allowed in an URL, several other servers
+ allow several and pass all after the first through as part of the
+ query arguments. Test that we emulate this behavior.
+ """
+ httpRequest = 'GET /foo?bar=?&baz=quux HTTP/1.0\n\n'
+
+ testcase = self
+ class MyRequest(http.Request):
+ def process(self):
+ testcase.assertEqual(self.method, 'GET')
+ testcase.assertEqual(self.path, '/foo')
+ testcase.assertEqual(self.args['bar'], ['?'])
+ testcase.assertEqual(self.args['baz'], ['quux'])
+ testcase.didRequest = 1
+ self.finish()
+
+ self.runRequest(httpRequest, MyRequest)
+
+
+ def test_formPOSTRequest(self):
+ """
+ The request body of a I{POST} request with a I{Content-Type} header
+ of I{application/x-www-form-urlencoded} is parsed according to that
+ content type and made available in the C{args} attribute of the
+ request object. The original bytes of the request may still be read
+ from the C{content} attribute.
+ """
+ query = 'key=value&multiple=two+words&multiple=more%20words&empty='
+ httpRequest = '''\
+POST / HTTP/1.0
+Content-Length: %d
+Content-Type: application/x-www-form-urlencoded
+
+%s''' % (len(query), query)
+
+ testcase = self
+ class MyRequest(http.Request):
+ def process(self):
+ testcase.assertEquals(self.method, "POST")
+ testcase.assertEquals(self.args["key"], ["value"])
+ testcase.assertEquals(self.args["empty"], [""])
+ testcase.assertEquals(self.args["multiple"], ["two words", "more words"])
+
+ # Reading from the content file-like must produce the entire
+ # request body.
+ testcase.assertEquals(self.content.read(), query)
+ testcase.didRequest = 1
+ self.finish()
+
+ self.runRequest(httpRequest, MyRequest)
+
+ def testMissingContentDisposition(self):
+ req = '''\
+POST / HTTP/1.0
+Content-Type: multipart/form-data; boundary=AaB03x
+Content-Length: 103
+
+--AaB03x
+Content-Type: text/plain
+Content-Transfer-Encoding: quoted-printable
+
+abasdfg
+--AaB03x--
+'''
+ self.runRequest(req, http.Request, success=False)
+
+ def test_chunkedEncoding(self):
+ """
+ If a request uses the I{chunked} transfer encoding, the request body is
+ decoded accordingly before it is made available on the request.
+ """
+ httpRequest = '''\
+GET / HTTP/1.0
+Content-Type: text/plain
+Transfer-Encoding: chunked
+
+6
+Hello,
+14
+ spam,eggs spam spam
+0
+
+'''
+ testcase = self
+ class MyRequest(http.Request):
+ def process(self):
+ # The tempfile API used to create content returns an
+ # instance of a different type depending on what platform
+ # we're running on. The point here is to verify that the
+ # request body is in a file that's on the filesystem.
+ # Having a fileno method that returns an int is a somewhat
+ # close approximation of this. -exarkun
+ testcase.assertIsInstance(self.content.fileno(), int)
+ testcase.assertEqual(self.method, 'GET')
+ testcase.assertEqual(self.path, '/')
+ content = self.content.read()
+ testcase.assertEqual(content, 'Hello, spam,eggs spam spam')
+ testcase.assertIdentical(self.channel._transferDecoder, None)
+ testcase.didRequest = 1
+ self.finish()
+
+ self.runRequest(httpRequest, MyRequest)
+
+
+
+class QueryArgumentsTestCase(unittest.TestCase):
+ def testUnquote(self):
+ try:
+ from twisted.protocols import _c_urlarg
+ except ImportError:
+ raise unittest.SkipTest("_c_urlarg module is not available")
+ # work exactly like urllib.unquote, including stupid things
+ # % followed by a non-hexdigit in the middle and in the end
+ self.failUnlessEqual(urllib.unquote("%notreally%n"),
+ _c_urlarg.unquote("%notreally%n"))
+ # % followed by hexdigit, followed by non-hexdigit
+ self.failUnlessEqual(urllib.unquote("%1quite%1"),
+ _c_urlarg.unquote("%1quite%1"))
+ # unquoted text, followed by some quoted chars, ends in a trailing %
+ self.failUnlessEqual(urllib.unquote("blah%21%40%23blah%"),
+ _c_urlarg.unquote("blah%21%40%23blah%"))
+ # Empty string
+ self.failUnlessEqual(urllib.unquote(""), _c_urlarg.unquote(""))
+
+ def testParseqs(self):
+ self.failUnlessEqual(cgi.parse_qs("a=b&d=c;+=f"),
+ http.parse_qs("a=b&d=c;+=f"))
+ self.failUnlessRaises(ValueError, http.parse_qs, "blah",
+ strict_parsing = 1)
+ self.failUnlessEqual(cgi.parse_qs("a=&b=c", keep_blank_values = 1),
+ http.parse_qs("a=&b=c", keep_blank_values = 1))
+ self.failUnlessEqual(cgi.parse_qs("a=&b=c"),
+ http.parse_qs("a=&b=c"))
+
+
+ def test_urlparse(self):
+ """
+ For a given URL, L{http.urlparse} should behave the same as
+ L{urlparse}, except it should always return C{str}, never C{unicode}.
+ """
+ def urls():
+ for scheme in ('http', 'https'):
+ for host in ('example.com',):
+ for port in (None, 100):
+ for path in ('', 'path'):
+ if port is not None:
+ host = host + ':' + str(port)
+ yield urlunsplit((scheme, host, path, '', ''))
+
+
+ def assertSameParsing(url, decode):
+ """
+ Verify that C{url} is parsed into the same objects by both
+ L{http.urlparse} and L{urlparse}.
+ """
+ urlToStandardImplementation = url
+ if decode:
+ urlToStandardImplementation = url.decode('ascii')
+ standardResult = urlparse(urlToStandardImplementation)
+ scheme, netloc, path, params, query, fragment = http.urlparse(url)
+ self.assertEqual(
+ (scheme, netloc, path, params, query, fragment),
+ standardResult)
+ self.assertTrue(isinstance(scheme, str))
+ self.assertTrue(isinstance(netloc, str))
+ self.assertTrue(isinstance(path, str))
+ self.assertTrue(isinstance(params, str))
+ self.assertTrue(isinstance(query, str))
+ self.assertTrue(isinstance(fragment, str))
+
+ # With caching, unicode then str
+ clear_cache()
+ for url in urls():
+ assertSameParsing(url, True)
+ assertSameParsing(url, False)
+
+ # With caching, str then unicode
+ clear_cache()
+ for url in urls():
+ assertSameParsing(url, False)
+ assertSameParsing(url, True)
+
+ # Without caching
+ for url in urls():
+ clear_cache()
+ assertSameParsing(url, True)
+ clear_cache()
+ assertSameParsing(url, False)
+
+
+ def test_urlparseRejectsUnicode(self):
+ """
+ L{http.urlparse} should reject unicode input early.
+ """
+ self.assertRaises(TypeError, http.urlparse, u'http://example.org/path')
+
+
+ def testEscchar(self):
+ try:
+ from twisted.protocols import _c_urlarg
+ except ImportError:
+ raise unittest.SkipTest("_c_urlarg module is not available")
+ self.failUnlessEqual("!@#+b",
+ _c_urlarg.unquote("+21+40+23+b", "+"))
+
+class ClientDriver(http.HTTPClient):
+ def handleStatus(self, version, status, message):
+ self.version = version
+ self.status = status
+ self.message = message
+
+class ClientStatusParsing(unittest.TestCase):
+ def testBaseline(self):
+ c = ClientDriver()
+ c.lineReceived('HTTP/1.0 201 foo')
+ self.failUnlessEqual(c.version, 'HTTP/1.0')
+ self.failUnlessEqual(c.status, '201')
+ self.failUnlessEqual(c.message, 'foo')
+
+ def testNoMessage(self):
+ c = ClientDriver()
+ c.lineReceived('HTTP/1.0 201')
+ self.failUnlessEqual(c.version, 'HTTP/1.0')
+ self.failUnlessEqual(c.status, '201')
+ self.failUnlessEqual(c.message, '')
+
+ def testNoMessage_trailingSpace(self):
+ c = ClientDriver()
+ c.lineReceived('HTTP/1.0 201 ')
+ self.failUnlessEqual(c.version, 'HTTP/1.0')
+ self.failUnlessEqual(c.status, '201')
+ self.failUnlessEqual(c.message, '')
+
+
+
+class RequestTests(unittest.TestCase, ResponseTestMixin):
+ """
+ Tests for L{http.Request}
+ """
+ def _compatHeadersTest(self, oldName, newName):
+ """
+ Verify that each of two different attributes which are associated with
+ the same state properly reflect changes made through the other.
+
+ This is used to test that the C{headers}/C{responseHeaders} and
+ C{received_headers}/C{requestHeaders} pairs interact properly.
+ """
+ req = http.Request(DummyChannel(), None)
+ getattr(req, newName).setRawHeaders("test", ["lemur"])
+ self.assertEqual(getattr(req, oldName)["test"], "lemur")
+ setattr(req, oldName, {"foo": "bar"})
+ self.assertEqual(
+ list(getattr(req, newName).getAllRawHeaders()),
+ [("Foo", ["bar"])])
+ setattr(req, newName, http_headers.Headers())
+ self.assertEqual(getattr(req, oldName), {})
+
+
+ def test_received_headers(self):
+ """
+ L{Request.received_headers} is a backwards compatible API which
+ accesses and allows mutation of the state at L{Request.requestHeaders}.
+ """
+ self._compatHeadersTest('received_headers', 'requestHeaders')
+
+
+ def test_headers(self):
+ """
+ L{Request.headers} is a backwards compatible API which accesses and
+ allows mutation of the state at L{Request.responseHeaders}.
+ """
+ self._compatHeadersTest('headers', 'responseHeaders')
+
+
+ def test_getHeader(self):
+ """
+ L{http.Request.getHeader} returns the value of the named request
+ header.
+ """
+ req = http.Request(DummyChannel(), None)
+ req.requestHeaders.setRawHeaders("test", ["lemur"])
+ self.assertEquals(req.getHeader("test"), "lemur")
+
+
+ def test_getHeaderReceivedMultiples(self):
+ """
+ When there are multiple values for a single request header,
+ L{http.Request.getHeader} returns the last value.
+ """
+ req = http.Request(DummyChannel(), None)
+ req.requestHeaders.setRawHeaders("test", ["lemur", "panda"])
+ self.assertEquals(req.getHeader("test"), "panda")
+
+
+ def test_getHeaderNotFound(self):
+ """
+ L{http.Request.getHeader} returns C{None} when asked for the value of a
+ request header which is not present.
+ """
+ req = http.Request(DummyChannel(), None)
+ self.assertEquals(req.getHeader("test"), None)
+
+
+ def test_getAllHeaders(self):
+ """
+ L{http.Request.getAllheaders} returns a C{dict} mapping all request
+ header names to their corresponding values.
+ """
+ req = http.Request(DummyChannel(), None)
+ req.requestHeaders.setRawHeaders("test", ["lemur"])
+ self.assertEquals(req.getAllHeaders(), {"test": "lemur"})
+
+
+ def test_getAllHeadersNoHeaders(self):
+ """
+ L{http.Request.getAllHeaders} returns an empty C{dict} if there are no
+ request headers.
+ """
+ req = http.Request(DummyChannel(), None)
+ self.assertEquals(req.getAllHeaders(), {})
+
+
+ def test_getAllHeadersMultipleHeaders(self):
+ """
+ When there are multiple values for a single request header,
+ L{http.Request.getAllHeaders} returns only the last value.
+ """
+ req = http.Request(DummyChannel(), None)
+ req.requestHeaders.setRawHeaders("test", ["lemur", "panda"])
+ self.assertEquals(req.getAllHeaders(), {"test": "panda"})
+
+
+ def test_setResponseCode(self):
+ """
+ L{http.Request.setResponseCode} takes a status code and causes it to be
+ used as the response status.
+ """
+ channel = DummyChannel()
+ req = http.Request(channel, None)
+ req.setResponseCode(201)
+ req.write('')
+ self.assertEqual(
+ channel.transport.written.getvalue().splitlines()[0],
+ '%s 201 Created' % (req.clientproto,))
+
+
+ def test_setResponseCodeAndMessage(self):
+ """
+ L{http.Request.setResponseCode} takes a status code and a message and
+ causes them to be used as the response status.
+ """
+ channel = DummyChannel()
+ req = http.Request(channel, None)
+ req.setResponseCode(202, "happily accepted")
+ req.write('')
+ self.assertEqual(
+ channel.transport.written.getvalue().splitlines()[0],
+ '%s 202 happily accepted' % (req.clientproto,))
+
+
+ def test_setResponseCodeAcceptsIntegers(self):
+ """
+ L{http.Request.setResponseCode} accepts C{int} or C{long} for the code
+ parameter and raises L{TypeError} if passed anything else.
+ """
+ req = http.Request(DummyChannel(), None)
+ req.setResponseCode(1)
+ req.setResponseCode(1L)
+ self.assertRaises(TypeError, req.setResponseCode, "1")
+
+
+ def test_setHost(self):
+ """
+ L{http.Request.setHost} sets the value of the host request header.
+ """
+ req = http.Request(DummyChannel(), None)
+ req.setHost("example.com", 443)
+ self.assertEqual(
+ req.requestHeaders.getRawHeaders("host"), ["example.com"])
+
+
+ def test_setHeader(self):
+ """
+ L{http.Request.setHeader} sets the value of the given response header.
+ """
+ req = http.Request(DummyChannel(), None)
+ req.setHeader("test", "lemur")
+ self.assertEquals(req.responseHeaders.getRawHeaders("test"), ["lemur"])
+
+
+ def test_firstWrite(self):
+ """
+ For an HTTP 1.0 request, L{http.Request.write} sends an HTTP 1.0
+ Response-Line and whatever response headers are set.
+ """
+ req = http.Request(DummyChannel(), None)
+ trans = StringTransport()
+
+ req.transport = trans
+
+ req.setResponseCode(200)
+ req.clientproto = "HTTP/1.0"
+ req.responseHeaders.setRawHeaders("test", ["lemur"])
+ req.write('Hello')
+
+ self.assertResponseEquals(
+ trans.value(),
+ [("HTTP/1.0 200 OK",
+ "Test: lemur",
+ "Hello")])
+
+
+ def test_firstWriteHTTP11Chunked(self):
+ """
+ For an HTTP 1.1 request, L{http.Request.write} sends an HTTP 1.1
+ Response-Line, whatever response headers are set, and uses chunked
+ encoding for the response body.
+ """
+ req = http.Request(DummyChannel(), None)
+ trans = StringTransport()
+
+ req.transport = trans
+
+ req.setResponseCode(200)
+ req.clientproto = "HTTP/1.1"
+ req.responseHeaders.setRawHeaders("test", ["lemur"])
+ req.write('Hello')
+ req.write('World!')
+
+ self.assertResponseEquals(
+ trans.value(),
+ [("HTTP/1.1 200 OK",
+ "Test: lemur",
+ "Transfer-Encoding: chunked",
+ "5\r\nHello\r\n6\r\nWorld!\r\n")])
+
+
+ def test_firstWriteLastModified(self):
+ """
+ For an HTTP 1.0 request for a resource with a known last modified time,
+ L{http.Request.write} sends an HTTP Response-Line, whatever response
+ headers are set, and a last-modified header with that time.
+ """
+ req = http.Request(DummyChannel(), None)
+ trans = StringTransport()
+
+ req.transport = trans
+
+ req.setResponseCode(200)
+ req.clientproto = "HTTP/1.0"
+ req.lastModified = 0
+ req.responseHeaders.setRawHeaders("test", ["lemur"])
+ req.write('Hello')
+
+ self.assertResponseEquals(
+ trans.value(),
+ [("HTTP/1.0 200 OK",
+ "Test: lemur",
+ "Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT",
+ "Hello")])
+
+
+ def test_parseCookies(self):
+ """
+ L{http.Request.parseCookies} extracts cookies from C{requestHeaders}
+ and adds them to C{received_cookies}.
+ """
+ req = http.Request(DummyChannel(), None)
+ req.requestHeaders.setRawHeaders(
+ "cookie", ['test="lemur"; test2="panda"'])
+ req.parseCookies()
+ self.assertEquals(req.received_cookies, {"test": '"lemur"',
+ "test2": '"panda"'})
+
+
+ def test_parseCookiesMultipleHeaders(self):
+ """
+ L{http.Request.parseCookies} can extract cookies from multiple Cookie
+ headers.
+ """
+ req = http.Request(DummyChannel(), None)
+ req.requestHeaders.setRawHeaders(
+ "cookie", ['test="lemur"', 'test2="panda"'])
+ req.parseCookies()
+ self.assertEquals(req.received_cookies, {"test": '"lemur"',
+ "test2": '"panda"'})
+
+
+ def test_connectionLost(self):
+ """
+ L{http.Request.connectionLost} closes L{Request.content} and drops the
+ reference to the L{HTTPChannel} to assist with garbage collection.
+ """
+ req = http.Request(DummyChannel(), None)
+
+ # Cause Request.content to be created at all.
+ req.gotLength(10)
+
+ # Grab a reference to content in case the Request drops it later on.
+ content = req.content
+
+ # Put some bytes into it
+ req.handleContentChunk("hello")
+
+ # Then something goes wrong and content should get closed.
+ req.connectionLost(Failure(ConnectionLost("Finished")))
+ self.assertTrue(content.closed)
+ self.assertIdentical(req.channel, None)
+
+
+ def test_registerProducerTwiceFails(self):
+ """
+ Calling L{Request.registerProducer} when a producer is already
+ registered raises ValueError.
+ """
+ req = http.Request(DummyChannel(), None)
+ req.registerProducer(DummyProducer(), True)
+ self.assertRaises(
+ ValueError, req.registerProducer, DummyProducer(), True)
+
+
+ def test_registerProducerWhenQueuedPausesPushProducer(self):
+ """
+ Calling L{Request.registerProducer} with an IPushProducer when the
+ request is queued pauses the producer.
+ """
+ req = http.Request(DummyChannel(), True)
+ producer = DummyProducer()
+ req.registerProducer(producer, True)
+ self.assertEquals(['pause'], producer.events)
+
+
+ def test_registerProducerWhenQueuedDoesntPausePullProducer(self):
+ """
+ Calling L{Request.registerProducer} with an IPullProducer when the
+ request is queued does not pause the producer, because it doesn't make
+ sense to pause a pull producer.
+ """
+ req = http.Request(DummyChannel(), True)
+ producer = DummyProducer()
+ req.registerProducer(producer, False)
+ self.assertEquals([], producer.events)
+
+
+ def test_registerProducerWhenQueuedDoesntRegisterPushProducer(self):
+ """
+ Calling L{Request.registerProducer} with an IPushProducer when the
+ request is queued does not register the producer on the request's
+ transport.
+ """
+ self.assertIdentical(
+ None, getattr(http.StringTransport, 'registerProducer', None),
+ "StringTransport cannot implement registerProducer for this test "
+ "to be valid.")
+ req = http.Request(DummyChannel(), True)
+ producer = DummyProducer()
+ req.registerProducer(producer, True)
+ # This is a roundabout assertion: http.StringTransport doesn't
+ # implement registerProducer, so Request.registerProducer can't have
+ # tried to call registerProducer on the transport.
+ self.assertIsInstance(req.transport, http.StringTransport)
+
+
+ def test_registerProducerWhenQueuedDoesntRegisterPullProducer(self):
+ """
+ Calling L{Request.registerProducer} with an IPullProducer when the
+ request is queued does not register the producer on the request's
+ transport.
+ """
+ self.assertIdentical(
+ None, getattr(http.StringTransport, 'registerProducer', None),
+ "StringTransport cannot implement registerProducer for this test "
+ "to be valid.")
+ req = http.Request(DummyChannel(), True)
+ producer = DummyProducer()
+ req.registerProducer(producer, False)
+ # This is a roundabout assertion: http.StringTransport doesn't
+ # implement registerProducer, so Request.registerProducer can't have
+ # tried to call registerProducer on the transport.
+ self.assertIsInstance(req.transport, http.StringTransport)
+
+
+ def test_registerProducerWhenNotQueuedRegistersPushProducer(self):
+ """
+ Calling L{Request.registerProducer} with an IPushProducer when the
+ request is not queued registers the producer as a push producer on the
+ request's transport.
+ """
+ req = http.Request(DummyChannel(), False)
+ producer = DummyProducer()
+ req.registerProducer(producer, True)
+ self.assertEquals([(producer, True)], req.transport.producers)
+
+
+ def test_registerProducerWhenNotQueuedRegistersPullProducer(self):
+ """
+ Calling L{Request.registerProducer} with an IPullProducer when the
+ request is not queued registers the producer as a pull producer on the
+ request's transport.
+ """
+ req = http.Request(DummyChannel(), False)
+ producer = DummyProducer()
+ req.registerProducer(producer, False)
+ self.assertEquals([(producer, False)], req.transport.producers)
+
+
+ def test_connectionLostNotification(self):
+ """
+ L{Request.connectionLost} triggers all finish notification Deferreds
+ and cleans up per-request state.
+ """
+ d = DummyChannel()
+ request = http.Request(d, True)
+ finished = request.notifyFinish()
+ request.connectionLost(Failure(ConnectionLost("Connection done")))
+ self.assertIdentical(request.channel, None)
+ return self.assertFailure(finished, ConnectionLost)
+
+
+ def test_finishNotification(self):
+ """
+ L{Request.finish} triggers all finish notification Deferreds.
+ """
+ request = http.Request(DummyChannel(), False)
+ finished = request.notifyFinish()
+ # Force the request to have a non-None content attribute. This is
+ # probably a bug in Request.
+ request.gotLength(1)
+ request.finish()
+ return finished
+
+
+ def test_finishAfterConnectionLost(self):
+ """
+ Calling L{Request.finish} after L{Request.connectionLost} has been
+ called results in a L{RuntimeError} being raised.
+ """
+ channel = DummyChannel()
+ transport = channel.transport
+ req = http.Request(channel, False)
+ req.connectionLost(Failure(ConnectionLost("The end.")))
+ self.assertRaises(RuntimeError, req.finish)
+
+
+
+class MultilineHeadersTestCase(unittest.TestCase):
+ """
+ Tests to exercise handling of multiline headers by L{HTTPClient}. RFCs 1945
+ (HTTP 1.0) and 2616 (HTTP 1.1) state that HTTP message header fields can
+ span multiple lines if each extra line is preceded by at least one space or
+ horizontal tab.
+ """
+ def setUp(self):
+ """
+ Initialize variables used to verify that the header-processing functions
+ are getting called.
+ """
+ self.handleHeaderCalled = False
+ self.handleEndHeadersCalled = False
+
+ # Dictionary of sample complete HTTP header key/value pairs, including
+ # multiline headers.
+ expectedHeaders = {'Content-Length': '10',
+ 'X-Multiline' : 'line-0\tline-1',
+ 'X-Multiline2' : 'line-2 line-3'}
+
+ def ourHandleHeader(self, key, val):
+ """
+ Dummy implementation of L{HTTPClient.handleHeader}.
+ """
+ self.handleHeaderCalled = True
+ self.assertEquals(val, self.expectedHeaders[key])
+
+
+ def ourHandleEndHeaders(self):
+ """
+ Dummy implementation of L{HTTPClient.handleEndHeaders}.
+ """
+ self.handleEndHeadersCalled = True
+
+
+ def test_extractHeader(self):
+ """
+ A header isn't processed by L{HTTPClient.extractHeader} until it is
+ confirmed in L{HTTPClient.lineReceived} that the header has been
+ received completely.
+ """
+ c = ClientDriver()
+ c.handleHeader = self.ourHandleHeader
+ c.handleEndHeaders = self.ourHandleEndHeaders
+
+ c.lineReceived('HTTP/1.0 201')
+ c.lineReceived('Content-Length: 10')
+ self.assertIdentical(c.length, None)
+ self.assertFalse(self.handleHeaderCalled)
+ self.assertFalse(self.handleEndHeadersCalled)
+
+ # Signal end of headers.
+ c.lineReceived('')
+ self.assertTrue(self.handleHeaderCalled)
+ self.assertTrue(self.handleEndHeadersCalled)
+
+ self.assertEquals(c.length, 10)
+
+
+ def test_noHeaders(self):
+ """
+ An HTTP request with no headers will not cause any calls to
+ L{handleHeader} but will cause L{handleEndHeaders} to be called on
+ L{HTTPClient} subclasses.
+ """
+ c = ClientDriver()
+ c.handleHeader = self.ourHandleHeader
+ c.handleEndHeaders = self.ourHandleEndHeaders
+ c.lineReceived('HTTP/1.0 201')
+
+ # Signal end of headers.
+ c.lineReceived('')
+ self.assertFalse(self.handleHeaderCalled)
+ self.assertTrue(self.handleEndHeadersCalled)
+
+ self.assertEquals(c.version, 'HTTP/1.0')
+ self.assertEquals(c.status, '201')
+
+
+ def test_multilineHeaders(self):
+ """
+ L{HTTPClient} parses multiline headers by buffering header lines until
+ an empty line or a line that does not start with whitespace hits
+ lineReceived, confirming that the header has been received completely.
+ """
+ c = ClientDriver()
+ c.handleHeader = self.ourHandleHeader
+ c.handleEndHeaders = self.ourHandleEndHeaders
+
+ c.lineReceived('HTTP/1.0 201')
+ c.lineReceived('X-Multiline: line-0')
+ self.assertFalse(self.handleHeaderCalled)
+ # Start continuing line with a tab.
+ c.lineReceived('\tline-1')
+ c.lineReceived('X-Multiline2: line-2')
+ # The previous header must be complete, so now it can be processed.
+ self.assertTrue(self.handleHeaderCalled)
+ # Start continuing line with a space.
+ c.lineReceived(' line-3')
+ c.lineReceived('Content-Length: 10')
+
+ # Signal end of headers.
+ c.lineReceived('')
+ self.assertTrue(self.handleEndHeadersCalled)
+
+ self.assertEquals(c.version, 'HTTP/1.0')
+ self.assertEquals(c.status, '201')
+ self.assertEquals(c.length, 10)
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_http_headers.py b/vendor/Twisted-10.0.0/twisted/web/test/test_http_headers.py
new file mode 100644
index 0000000000..3bdb26f19a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_http_headers.py
@@ -0,0 +1,585 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web.http_headers}.
+"""
+
+import sys
+
+from twisted.python.compat import set
+from twisted.trial.unittest import TestCase
+from twisted.web.http_headers import _DictHeaders, Headers
+
+
+class HeadersTests(TestCase):
+ """
+ Tests for L{Headers}.
+ """
+ def test_initializer(self):
+ """
+ The header values passed to L{Headers.__init__} can be retrieved via
+ L{Headers.getRawHeaders}.
+ """
+ h = Headers({'Foo': ['bar']})
+ self.assertEqual(h.getRawHeaders('foo'), ['bar'])
+
+
+ def test_setRawHeaders(self):
+ """
+ L{Headers.setRawHeaders} sets the header values for the given
+ header name to the sequence of string values.
+ """
+ rawValue = ["value1", "value2"]
+ h = Headers()
+ h.setRawHeaders("test", rawValue)
+ self.assertTrue(h.hasHeader("test"))
+ self.assertTrue(h.hasHeader("Test"))
+ self.assertEqual(h.getRawHeaders("test"), rawValue)
+
+
+ def test_addRawHeader(self):
+ """
+ L{Headers.addRawHeader} adds a new value for a given header.
+ """
+ h = Headers()
+ h.addRawHeader("test", "lemur")
+ self.assertEqual(h.getRawHeaders("test"), ["lemur"])
+ h.addRawHeader("test", "panda")
+ self.assertEqual(h.getRawHeaders("test"), ["lemur", "panda"])
+
+
+ def test_getRawHeadersNoDefault(self):
+ """
+ L{Headers.getRawHeaders} returns C{None} if the header is not found and
+ no default is specified.
+ """
+ self.assertIdentical(Headers().getRawHeaders("test"), None)
+
+
+ def test_getRawHeadersDefaultValue(self):
+ """
+ L{Headers.getRawHeaders} returns the specified default value when no
+ header is found.
+ """
+ h = Headers()
+ default = object()
+ self.assertIdentical(h.getRawHeaders("test", default), default)
+
+
+ def test_getRawHeaders(self):
+ """
+ L{Headers.getRawHeaders} returns the values which have been set for a
+ given header.
+ """
+ h = Headers()
+ h.setRawHeaders("test", ["lemur"])
+ self.assertEqual(h.getRawHeaders("test"), ["lemur"])
+ self.assertEqual(h.getRawHeaders("Test"), ["lemur"])
+
+
+ def test_hasHeaderTrue(self):
+ """
+ Check that L{Headers.hasHeader} returns C{True} when the given header
+ is found.
+ """
+ h = Headers()
+ h.setRawHeaders("test", ["lemur"])
+ self.assertTrue(h.hasHeader("test"))
+ self.assertTrue(h.hasHeader("Test"))
+
+
+ def test_hasHeaderFalse(self):
+ """
+ L{Headers.hasHeader} returns C{False} when the given header is not
+ found.
+ """
+ self.assertFalse(Headers().hasHeader("test"))
+
+
+ def test_removeHeader(self):
+ """
+ Check that L{Headers.removeHeader} removes the given header.
+ """
+ h = Headers()
+
+ h.setRawHeaders("foo", ["lemur"])
+ self.assertTrue(h.hasHeader("foo"))
+ h.removeHeader("foo")
+ self.assertFalse(h.hasHeader("foo"))
+
+ h.setRawHeaders("bar", ["panda"])
+ self.assertTrue(h.hasHeader("bar"))
+ h.removeHeader("Bar")
+ self.assertFalse(h.hasHeader("bar"))
+
+
+ def test_removeHeaderDoesntExist(self):
+ """
+ L{Headers.removeHeader} is a no-operation when the specified header is
+ not found.
+ """
+ h = Headers()
+ h.removeHeader("test")
+ self.assertEqual(list(h.getAllRawHeaders()), [])
+
+
+ def test_canonicalNameCaps(self):
+ """
+ L{Headers._canonicalNameCaps} returns the canonical capitalization for
+ the given header.
+ """
+ h = Headers()
+ self.assertEqual(h._canonicalNameCaps("test"), "Test")
+ self.assertEqual(h._canonicalNameCaps("test-stuff"), "Test-Stuff")
+ self.assertEqual(h._canonicalNameCaps("www-authenticate"),
+ "WWW-Authenticate")
+
+
+ def test_getAllRawHeaders(self):
+ """
+ L{Headers.getAllRawHeaders} returns an iterable of (k, v) pairs, where
+ C{k} is the canonicalized representation of the header name, and C{v}
+ is a sequence of values.
+ """
+ h = Headers()
+ h.setRawHeaders("test", ["lemurs"])
+ h.setRawHeaders("www-authenticate", ["basic aksljdlk="])
+
+ allHeaders = set([(k, tuple(v)) for k, v in h.getAllRawHeaders()])
+
+ self.assertEqual(allHeaders,
+ set([("WWW-Authenticate", ("basic aksljdlk=",)),
+ ("Test", ("lemurs",))]))
+
+
+ def test_headersComparison(self):
+ """
+ A L{Headers} instance compares equal to itself and to another
+ L{Headers} instance with the same values.
+ """
+ first = Headers()
+ first.setRawHeaders("foo", ["panda"])
+ second = Headers()
+ second.setRawHeaders("foo", ["panda"])
+ third = Headers()
+ third.setRawHeaders("foo", ["lemur", "panda"])
+ self.assertEqual(first, first)
+ self.assertEqual(first, second)
+ self.assertNotEqual(first, third)
+
+
+ def test_otherComparison(self):
+ """
+ An instance of L{Headers} does not compare equal to other unrelated
+ objects.
+ """
+ h = Headers()
+ self.assertNotEqual(h, ())
+ self.assertNotEqual(h, object())
+ self.assertNotEqual(h, "foo")
+
+
+ def test_repr(self):
+ """
+ The L{repr} of a L{Headers} instance shows the names and values of all
+ the headers it contains.
+ """
+ self.assertEqual(
+ repr(Headers({"foo": ["bar", "baz"]})),
+ "Headers({'foo': ['bar', 'baz']})")
+
+
+ def test_subclassRepr(self):
+ """
+ The L{repr} of an instance of a subclass of L{Headers} uses the name
+ of the subclass instead of the string C{"Headers"}.
+ """
+ class FunnyHeaders(Headers):
+ pass
+ self.assertEqual(
+ repr(FunnyHeaders({"foo": ["bar", "baz"]})),
+ "FunnyHeaders({'foo': ['bar', 'baz']})")
+
+
+
+class HeaderDictTests(TestCase):
+ """
+ Tests for the backwards compatible C{dict} interface for L{Headers}
+ provided by L{_DictHeaders}.
+ """
+ def headers(self, **kw):
+ """
+ Create a L{Headers} instance populated with the header name/values
+ specified by C{kw} and a L{_DictHeaders} wrapped around it and return
+ them both.
+ """
+ h = Headers()
+ for k, v in kw.iteritems():
+ h.setRawHeaders(k, v)
+ return h, _DictHeaders(h)
+
+
+ def test_getItem(self):
+ """
+ L{_DictHeaders.__getitem__} returns a single header for the given name.
+ """
+ headers, wrapper = self.headers(test=["lemur"])
+ self.assertEqual(wrapper["test"], "lemur")
+
+
+ def test_getItemMultiple(self):
+ """
+ L{_DictHeaders.__getitem__} returns only the last header value for a
+ given name.
+ """
+ headers, wrapper = self.headers(test=["lemur", "panda"])
+ self.assertEqual(wrapper["test"], "panda")
+
+
+ def test_getItemMissing(self):
+ """
+ L{_DictHeaders.__getitem__} raises L{KeyError} if called with a header
+ which is not present.
+ """
+ headers, wrapper = self.headers()
+ exc = self.assertRaises(KeyError, wrapper.__getitem__, "test")
+ self.assertEqual(exc.args, ("test",))
+
+
+ def test_iteration(self):
+ """
+ L{_DictHeaders.__iter__} returns an iterator the elements of which
+ are the lowercase name of each header present.
+ """
+ headers, wrapper = self.headers(foo=["lemur", "panda"], bar=["baz"])
+ self.assertEqual(set(list(wrapper)), set(["foo", "bar"]))
+
+
+ def test_length(self):
+ """
+ L{_DictHeaders.__len__} returns the number of headers present.
+ """
+ headers, wrapper = self.headers()
+ self.assertEqual(len(wrapper), 0)
+ headers.setRawHeaders("foo", ["bar"])
+ self.assertEqual(len(wrapper), 1)
+ headers.setRawHeaders("test", ["lemur", "panda"])
+ self.assertEqual(len(wrapper), 2)
+
+
+ def test_setItem(self):
+ """
+ L{_DictHeaders.__setitem__} sets a single header value for the given
+ name.
+ """
+ headers, wrapper = self.headers()
+ wrapper["test"] = "lemur"
+ self.assertEqual(headers.getRawHeaders("test"), ["lemur"])
+
+
+ def test_setItemOverwrites(self):
+ """
+ L{_DictHeaders.__setitem__} will replace any previous header values for
+ the given name.
+ """
+ headers, wrapper = self.headers(test=["lemur", "panda"])
+ wrapper["test"] = "lemur"
+ self.assertEqual(headers.getRawHeaders("test"), ["lemur"])
+
+
+ def test_delItem(self):
+ """
+ L{_DictHeaders.__delitem__} will remove the header values for the given
+ name.
+ """
+ headers, wrapper = self.headers(test=["lemur"])
+ del wrapper["test"]
+ self.assertFalse(headers.hasHeader("test"))
+
+
+ def test_delItemMissing(self):
+ """
+ L{_DictHeaders.__delitem__} will raise L{KeyError} if the given name is
+ not present.
+ """
+ headers, wrapper = self.headers()
+ exc = self.assertRaises(KeyError, wrapper.__delitem__, "test")
+ self.assertEqual(exc.args, ("test",))
+
+
+ def test_keys(self, _method='keys', _requireList=True):
+ """
+ L{_DictHeaders.keys} will return a list of all present header names.
+ """
+ headers, wrapper = self.headers(test=["lemur"], foo=["bar"])
+ keys = getattr(wrapper, _method)()
+ if _requireList:
+ self.assertIsInstance(keys, list)
+ self.assertEqual(set(keys), set(["foo", "test"]))
+
+
+ def test_iterkeys(self):
+ """
+ L{_DictHeaders.iterkeys} will return all present header names.
+ """
+ self.test_keys('iterkeys', False)
+
+
+ def test_values(self, _method='values', _requireList=True):
+ """
+ L{_DictHeaders.values} will return a list of all present header values,
+ returning only the last value for headers with more than one.
+ """
+ headers, wrapper = self.headers(foo=["lemur"], bar=["marmot", "panda"])
+ values = getattr(wrapper, _method)()
+ if _requireList:
+ self.assertIsInstance(values, list)
+ self.assertEqual(set(values), set(["lemur", "panda"]))
+
+
+ def test_itervalues(self):
+ """
+ L{_DictHeaders.itervalues} will return all present header values,
+ returning only the last value for headers with more than one.
+ """
+ self.test_values('itervalues', False)
+
+
+ def test_items(self, _method='items', _requireList=True):
+ """
+ L{_DictHeaders.items} will return a list of all present header names
+ and values as tuples, returning only the last value for headers with
+ more than one.
+ """
+ headers, wrapper = self.headers(foo=["lemur"], bar=["marmot", "panda"])
+ items = getattr(wrapper, _method)()
+ if _requireList:
+ self.assertIsInstance(items, list)
+ self.assertEqual(set(items), set([("foo", "lemur"), ("bar", "panda")]))
+
+
+ def test_iteritems(self):
+ """
+ L{_DictHeaders.iteritems} will return all present header names and
+ values as tuples, returning only the last value for headers with more
+ than one.
+ """
+ self.test_items('iteritems', False)
+
+
+ def test_clear(self):
+ """
+ L{_DictHeaders.clear} will remove all headers.
+ """
+ headers, wrapper = self.headers(foo=["lemur"], bar=["panda"])
+ wrapper.clear()
+ self.assertEqual(list(headers.getAllRawHeaders()), [])
+
+
+ def test_copy(self):
+ """
+ L{_DictHeaders.copy} will return a C{dict} with all the same headers
+ and the last value for each.
+ """
+ headers, wrapper = self.headers(foo=["lemur", "panda"], bar=["marmot"])
+ duplicate = wrapper.copy()
+ self.assertEqual(duplicate, {"foo": "panda", "bar": "marmot"})
+
+
+ def test_get(self):
+ """
+ L{_DictHeaders.get} returns the last value for the given header name.
+ """
+ headers, wrapper = self.headers(foo=["lemur", "panda"])
+ self.assertEqual(wrapper.get("foo"), "panda")
+
+
+ def test_getMissing(self):
+ """
+ L{_DictHeaders.get} returns C{None} for a header which is not present.
+ """
+ headers, wrapper = self.headers()
+ self.assertIdentical(wrapper.get("foo"), None)
+
+
+ def test_getDefault(self):
+ """
+ L{_DictHeaders.get} returns the last value for the given header name
+ even when it is invoked with a default value.
+ """
+ headers, wrapper = self.headers(foo=["lemur"])
+ self.assertEqual(wrapper.get("foo", "bar"), "lemur")
+
+
+ def test_getDefaultMissing(self):
+ """
+ L{_DictHeaders.get} returns the default value specified if asked for a
+ header which is not present.
+ """
+ headers, wrapper = self.headers()
+ self.assertEqual(wrapper.get("foo", "bar"), "bar")
+
+
+ def test_has_key(self):
+ """
+ L{_DictHeaders.has_key} returns C{True} if the given header is present,
+ C{False} otherwise.
+ """
+ headers, wrapper = self.headers(foo=["lemur"])
+ self.assertTrue(wrapper.has_key("foo"))
+ self.assertFalse(wrapper.has_key("bar"))
+
+
+ def test_contains(self):
+ """
+ L{_DictHeaders.__contains__} returns C{True} if the given header is
+ present, C{False} otherwise.
+ """
+ headers, wrapper = self.headers(foo=["lemur"])
+ self.assertIn("foo", wrapper)
+ self.assertNotIn("bar", wrapper)
+
+
+ def test_pop(self):
+ """
+ L{_DictHeaders.pop} returns the last header value associated with the
+ given header name and removes the header.
+ """
+ headers, wrapper = self.headers(foo=["lemur", "panda"])
+ self.assertEqual(wrapper.pop("foo"), "panda")
+ self.assertIdentical(headers.getRawHeaders("foo"), None)
+
+
+ def test_popMissing(self):
+ """
+ L{_DictHeaders.pop} raises L{KeyError} if passed a header name which is
+ not present.
+ """
+ headers, wrapper = self.headers()
+ self.assertRaises(KeyError, wrapper.pop, "foo")
+
+
+ def test_popDefault(self):
+ """
+ L{_DictHeaders.pop} returns the last header value associated with the
+ given header name and removes the header, even if it is supplied with a
+ default value.
+ """
+ headers, wrapper = self.headers(foo=["lemur"])
+ self.assertEqual(wrapper.pop("foo", "bar"), "lemur")
+ self.assertIdentical(headers.getRawHeaders("foo"), None)
+
+
+ def test_popDefaultMissing(self):
+ """
+ L{_DictHeaders.pop} returns the default value is asked for a header
+ name which is not present.
+ """
+ headers, wrapper = self.headers(foo=["lemur"])
+ self.assertEqual(wrapper.pop("bar", "baz"), "baz")
+ self.assertEqual(headers.getRawHeaders("foo"), ["lemur"])
+
+
+ def test_popitem(self):
+ """
+ L{_DictHeaders.popitem} returns some header name/value pair.
+ """
+ headers, wrapper = self.headers(foo=["lemur", "panda"])
+ self.assertEqual(wrapper.popitem(), ("foo", "panda"))
+ self.assertIdentical(headers.getRawHeaders("foo"), None)
+
+
+ def test_popitemEmpty(self):
+ """
+ L{_DictHeaders.popitem} raises L{KeyError} if there are no headers
+ present.
+ """
+ headers, wrapper = self.headers()
+ self.assertRaises(KeyError, wrapper.popitem)
+
+
+ def test_update(self):
+ """
+ L{_DictHeaders.update} adds the header/value pairs in the C{dict} it is
+ passed, overriding any existing values for those headers.
+ """
+ headers, wrapper = self.headers(foo=["lemur"])
+ wrapper.update({"foo": "panda", "bar": "marmot"})
+ self.assertEqual(headers.getRawHeaders("foo"), ["panda"])
+ self.assertEqual(headers.getRawHeaders("bar"), ["marmot"])
+
+
+ def test_updateWithKeywords(self):
+ """
+ L{_DictHeaders.update} adds header names given as keyword arguments
+ with the keyword values as the header value.
+ """
+ headers, wrapper = self.headers(foo=["lemur"])
+ wrapper.update(foo="panda", bar="marmot")
+ self.assertEqual(headers.getRawHeaders("foo"), ["panda"])
+ self.assertEqual(headers.getRawHeaders("bar"), ["marmot"])
+
+ if sys.version_info < (2, 4):
+ test_updateWithKeywords.skip = (
+ "Python 2.3 does not support keyword arguments to dict.update.")
+
+
+ def test_setdefaultMissing(self):
+ """
+ If passed the name of a header which is not present,
+ L{_DictHeaders.setdefault} sets the value of the given header to the
+ specified default value and returns it.
+ """
+ headers, wrapper = self.headers(foo=["bar"])
+ self.assertEqual(wrapper.setdefault("baz", "quux"), "quux")
+ self.assertEqual(headers.getRawHeaders("foo"), ["bar"])
+ self.assertEqual(headers.getRawHeaders("baz"), ["quux"])
+
+
+ def test_setdefaultPresent(self):
+ """
+ If passed the name of a header which is present,
+ L{_DictHeaders.setdefault} makes no changes to the headers and
+ returns the last value already associated with that header.
+ """
+ headers, wrapper = self.headers(foo=["bar", "baz"])
+ self.assertEqual(wrapper.setdefault("foo", "quux"), "baz")
+ self.assertEqual(headers.getRawHeaders("foo"), ["bar", "baz"])
+
+
+ def test_setdefaultDefault(self):
+ """
+ If a value is not passed to L{_DictHeaders.setdefault}, C{None} is
+ used.
+ """
+ # This results in an invalid state for the headers, but maybe some
+ # application is doing this an intermediate step towards some other
+ # state. Anyway, it was broken with the old implementation so it's
+ # broken with the new implementation. Compatibility, for the win.
+ # -exarkun
+ headers, wrapper = self.headers()
+ self.assertIdentical(wrapper.setdefault("foo"), None)
+ self.assertEqual(headers.getRawHeaders("foo"), [None])
+
+
+ def test_dictComparison(self):
+ """
+ An instance of L{_DictHeaders} compares equal to a C{dict} which
+ contains the same header/value pairs. For header names with multiple
+ values, the last value only is considered.
+ """
+ headers, wrapper = self.headers(foo=["lemur"], bar=["panda", "marmot"])
+ self.assertNotEqual(wrapper, {"foo": "lemur", "bar": "panda"})
+ self.assertEqual(wrapper, {"foo": "lemur", "bar": "marmot"})
+
+
+ def test_otherComparison(self):
+ """
+ An instance of L{_DictHeaders} does not compare equal to other
+ unrelated objects.
+ """
+ headers, wrapper = self.headers()
+ self.assertNotEqual(wrapper, ())
+ self.assertNotEqual(wrapper, object())
+ self.assertNotEqual(wrapper, "foo")
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_httpauth.py b/vendor/Twisted-10.0.0/twisted/web/test/test_httpauth.py
new file mode 100644
index 0000000000..03003fa8ee
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_httpauth.py
@@ -0,0 +1,586 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web._auth}.
+"""
+
+
+from zope.interface import implements
+from zope.interface.verify import verifyObject
+
+from twisted.trial import unittest
+
+from twisted.internet.address import IPv4Address
+
+from twisted.cred import error, portal
+from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+from twisted.cred.checkers import ANONYMOUS, AllowAnonymousAccess
+from twisted.cred.credentials import IUsernamePassword
+
+from twisted.web.iweb import ICredentialFactory
+from twisted.web.resource import IResource, Resource, getChildForRequest
+from twisted.web._auth import basic, digest
+from twisted.web._auth.wrapper import HTTPAuthSessionWrapper, UnauthorizedResource
+from twisted.web._auth.basic import BasicCredentialFactory
+
+from twisted.web.server import NOT_DONE_YET
+from twisted.web.static import Data
+
+from twisted.web.test.test_web import DummyRequest
+
+
+def b64encode(s):
+ return s.encode('base64').strip()
+
+
+class BasicAuthTestsMixin:
+ """
+ L{TestCase} mixin class which defines a number of tests for
+ L{basic.BasicCredentialFactory}. Because this mixin defines C{setUp}, it
+ must be inherited before L{TestCase}.
+ """
+ def setUp(self):
+ self.request = self.makeRequest()
+ self.realm = 'foo'
+ self.username = 'dreid'
+ self.password = 'S3CuR1Ty'
+ self.credentialFactory = basic.BasicCredentialFactory(self.realm)
+
+
+ def makeRequest(self, method='GET', clientAddress=None):
+ """
+ Create a request object to be passed to
+ L{basic.BasicCredentialFactory.decode} along with a response value.
+ Override this in a subclass.
+ """
+ raise NotImplementedError("%r did not implement makeRequest" % (
+ self.__class__,))
+
+
+ def test_interface(self):
+ """
+ L{BasicCredentialFactory} implements L{ICredentialFactory}.
+ """
+ self.assertTrue(
+ verifyObject(ICredentialFactory, self.credentialFactory))
+
+
+ def test_usernamePassword(self):
+ """
+ L{basic.BasicCredentialFactory.decode} turns a base64-encoded response
+ into a L{UsernamePassword} object with a password which reflects the
+ one which was encoded in the response.
+ """
+ response = b64encode('%s:%s' % (self.username, self.password))
+
+ creds = self.credentialFactory.decode(response, self.request)
+ self.assertTrue(IUsernamePassword.providedBy(creds))
+ self.assertTrue(creds.checkPassword(self.password))
+ self.assertFalse(creds.checkPassword(self.password + 'wrong'))
+
+
+ def test_incorrectPadding(self):
+ """
+ L{basic.BasicCredentialFactory.decode} decodes a base64-encoded
+ response with incorrect padding.
+ """
+ response = b64encode('%s:%s' % (self.username, self.password))
+ response = response.strip('=')
+
+ creds = self.credentialFactory.decode(response, self.request)
+ self.assertTrue(verifyObject(IUsernamePassword, creds))
+ self.assertTrue(creds.checkPassword(self.password))
+
+
+ def test_invalidEncoding(self):
+ """
+ L{basic.BasicCredentialFactory.decode} raises L{LoginFailed} if passed
+ a response which is not base64-encoded.
+ """
+ response = 'x' # one byte cannot be valid base64 text
+ self.assertRaises(
+ error.LoginFailed,
+ self.credentialFactory.decode, response, self.makeRequest())
+
+
+ def test_invalidCredentials(self):
+ """
+ L{basic.BasicCredentialFactory.decode} raises L{LoginFailed} when
+ passed a response which is not valid base64-encoded text.
+ """
+ response = b64encode('123abc+/')
+ self.assertRaises(
+ error.LoginFailed,
+ self.credentialFactory.decode,
+ response, self.makeRequest())
+
+
+class RequestMixin:
+ def makeRequest(self, method='GET', clientAddress=None):
+ """
+ Create a L{DummyRequest} (change me to create a
+ L{twisted.web.http.Request} instead).
+ """
+ request = DummyRequest('/')
+ request.method = method
+ request.client = clientAddress
+ return request
+
+
+
+class BasicAuthTestCase(RequestMixin, BasicAuthTestsMixin, unittest.TestCase):
+ """
+ Basic authentication tests which use L{twisted.web.http.Request}.
+ """
+
+
+
+class DigestAuthTestCase(RequestMixin, unittest.TestCase):
+ """
+ Digest authentication tests which use L{twisted.web.http.Request}.
+ """
+
+ def setUp(self):
+ """
+ Create a DigestCredentialFactory for testing
+ """
+ self.realm = "test realm"
+ self.algorithm = "md5"
+ self.credentialFactory = digest.DigestCredentialFactory(
+ self.algorithm, self.realm)
+ self.request = self.makeRequest()
+
+
+ def test_decode(self):
+ """
+ L{digest.DigestCredentialFactory.decode} calls the C{decode} method on
+ L{twisted.cred.digest.DigestCredentialFactory} with the HTTP method and
+ host of the request.
+ """
+ host = '169.254.0.1'
+ method = 'GET'
+ done = [False]
+ response = object()
+ def check(_response, _method, _host):
+ self.assertEqual(response, _response)
+ self.assertEqual(method, _method)
+ self.assertEqual(host, _host)
+ done[0] = True
+
+ self.patch(self.credentialFactory.digest, 'decode', check)
+ req = self.makeRequest(method, IPv4Address('TCP', host, 81))
+ self.credentialFactory.decode(response, req)
+ self.assertTrue(done[0])
+
+
+ def test_interface(self):
+ """
+ L{DigestCredentialFactory} implements L{ICredentialFactory}.
+ """
+ self.assertTrue(
+ verifyObject(ICredentialFactory, self.credentialFactory))
+
+
+ def test_getChallenge(self):
+ """
+ The challenge issued by L{DigestCredentialFactory.getChallenge} must
+ include C{'qop'}, C{'realm'}, C{'algorithm'}, C{'nonce'}, and
+ C{'opaque'} keys. The values for the C{'realm'} and C{'algorithm'}
+ keys must match the values supplied to the factory's initializer.
+ None of the values may have newlines in them.
+ """
+ challenge = self.credentialFactory.getChallenge(self.request)
+ self.assertEquals(challenge['qop'], 'auth')
+ self.assertEquals(challenge['realm'], 'test realm')
+ self.assertEquals(challenge['algorithm'], 'md5')
+ self.assertIn('nonce', challenge)
+ self.assertIn('opaque', challenge)
+ for v in challenge.values():
+ self.assertNotIn('\n', v)
+
+
+ def test_getChallengeWithoutClientIP(self):
+ """
+ L{DigestCredentialFactory.getChallenge} can issue a challenge even if
+ the L{Request} it is passed returns C{None} from C{getClientIP}.
+ """
+ request = self.makeRequest('GET', None)
+ challenge = self.credentialFactory.getChallenge(request)
+ self.assertEqual(challenge['qop'], 'auth')
+ self.assertEqual(challenge['realm'], 'test realm')
+ self.assertEqual(challenge['algorithm'], 'md5')
+ self.assertIn('nonce', challenge)
+ self.assertIn('opaque', challenge)
+
+
+
+class UnauthorizedResourceTests(unittest.TestCase):
+ """
+ Tests for L{UnauthorizedResource}.
+ """
+ def test_getChildWithDefault(self):
+ """
+ An L{UnauthorizedResource} is every child of itself.
+ """
+ resource = UnauthorizedResource([])
+ self.assertIdentical(
+ resource.getChildWithDefault("foo", None), resource)
+ self.assertIdentical(
+ resource.getChildWithDefault("bar", None), resource)
+
+
+ def test_render(self):
+ """
+ L{UnauthorizedResource} renders with a 401 response code and a
+ I{WWW-Authenticate} header and puts a simple unauthorized message
+ into the response body.
+ """
+ resource = UnauthorizedResource([
+ BasicCredentialFactory('example.com')])
+ request = DummyRequest([''])
+ request.render(resource)
+ self.assertEqual(request.responseCode, 401)
+ self.assertEqual(
+ request.responseHeaders.getRawHeaders('www-authenticate'),
+ ['basic realm="example.com"'])
+ self.assertEqual(request.written, ['Unauthorized'])
+
+
+ def test_renderQuotesRealm(self):
+ """
+ The realm value included in the I{WWW-Authenticate} header set in
+ the response when L{UnauthorizedResounrce} is rendered has quotes
+ and backslashes escaped.
+ """
+ resource = UnauthorizedResource([
+ BasicCredentialFactory('example\\"foo')])
+ request = DummyRequest([''])
+ request.render(resource)
+ self.assertEqual(
+ request.responseHeaders.getRawHeaders('www-authenticate'),
+ ['basic realm="example\\\\\\"foo"'])
+
+
+
+class Realm(object):
+ """
+ A simple L{IRealm} implementation which gives out L{WebAvatar} for any
+ avatarId.
+
+ @type loggedIn: C{int}
+ @ivar loggedIn: The number of times C{requestAvatar} has been invoked for
+ L{IResource}.
+
+ @type loggedOut: C{int}
+ @ivar loggedOut: The number of times the logout callback has been invoked.
+ """
+ implements(portal.IRealm)
+
+ def __init__(self, avatarFactory):
+ self.loggedOut = 0
+ self.loggedIn = 0
+ self.avatarFactory = avatarFactory
+
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if IResource in interfaces:
+ self.loggedIn += 1
+ return IResource, self.avatarFactory(avatarId), self.logout
+ raise NotImplementedError()
+
+
+ def logout(self):
+ self.loggedOut += 1
+
+
+
+class HTTPAuthHeaderTests(unittest.TestCase):
+ """
+ Tests for L{HTTPAuthSessionWrapper}.
+ """
+ makeRequest = DummyRequest
+
+ def setUp(self):
+ """
+ Create a realm, portal, and L{HTTPAuthSessionWrapper} to use in the tests.
+ """
+ self.username = 'foo bar'
+ self.password = 'bar baz'
+ self.avatarContent = "contents of the avatar resource itself"
+ self.childName = "foo-child"
+ self.childContent = "contents of the foo child of the avatar"
+ self.checker = InMemoryUsernamePasswordDatabaseDontUse()
+ self.checker.addUser(self.username, self.password)
+ self.avatar = Data(self.avatarContent, 'text/plain')
+ self.avatar.putChild(
+ self.childName, Data(self.childContent, 'text/plain'))
+ self.avatars = {self.username: self.avatar}
+ self.realm = Realm(self.avatars.get)
+ self.portal = portal.Portal(self.realm, [self.checker])
+ self.credentialFactories = []
+ self.wrapper = HTTPAuthSessionWrapper(
+ self.portal, self.credentialFactories)
+
+
+ def _authorizedBasicLogin(self, request):
+ """
+ Add an I{basic authorization} header to the given request and then
+ dispatch it, starting from C{self.wrapper} and returning the resulting
+ L{IResource}.
+ """
+ authorization = b64encode(self.username + ':' + self.password)
+ request.headers['authorization'] = 'Basic ' + authorization
+ return getChildForRequest(self.wrapper, request)
+
+
+ def test_getChildWithDefault(self):
+ """
+ Resource traversal which encounters an L{HTTPAuthSessionWrapper}
+ results in an L{UnauthorizedResource} instance when the request does
+ not have the required I{Authorization} headers.
+ """
+ request = self.makeRequest([self.childName])
+ child = getChildForRequest(self.wrapper, request)
+ d = request.notifyFinish()
+ def cbFinished(result):
+ self.assertEquals(request.responseCode, 401)
+ d.addCallback(cbFinished)
+ request.render(child)
+ return d
+
+
+ def _invalidAuthorizationTest(self, response):
+ """
+ Create a request with the given value as the value of an
+ I{Authorization} header and perform resource traversal with it,
+ starting at C{self.wrapper}. Assert that the result is a 401 response
+ code. Return a L{Deferred} which fires when this is all done.
+ """
+ self.credentialFactories.append(BasicCredentialFactory('example.com'))
+ request = self.makeRequest([self.childName])
+ request.headers['authorization'] = response
+ child = getChildForRequest(self.wrapper, request)
+ d = request.notifyFinish()
+ def cbFinished(result):
+ self.assertEqual(request.responseCode, 401)
+ d.addCallback(cbFinished)
+ request.render(child)
+ return d
+
+
+ def test_getChildWithDefaultUnauthorizedUser(self):
+ """
+ Resource traversal which enouncters an L{HTTPAuthSessionWrapper}
+ results in an L{UnauthorizedResource} when the request has an
+ I{Authorization} header with a user which does not exist.
+ """
+ return self._invalidAuthorizationTest('Basic ' + b64encode('foo:bar'))
+
+
+ def test_getChildWithDefaultUnauthorizedPassword(self):
+ """
+ Resource traversal which enouncters an L{HTTPAuthSessionWrapper}
+ results in an L{UnauthorizedResource} when the request has an
+ I{Authorization} header with a user which exists and the wrong
+ password.
+ """
+ return self._invalidAuthorizationTest(
+ 'Basic ' + b64encode(self.username + ':bar'))
+
+
+ def test_getChildWithDefaultUnrecognizedScheme(self):
+ """
+ Resource traversal which enouncters an L{HTTPAuthSessionWrapper}
+ results in an L{UnauthorizedResource} when the request has an
+ I{Authorization} header with an unrecognized scheme.
+ """
+ return self._invalidAuthorizationTest('Quux foo bar baz')
+
+
+ def test_getChildWithDefaultAuthorized(self):
+ """
+ Resource traversal which encounters an L{HTTPAuthSessionWrapper}
+ results in an L{IResource} which renders the L{IResource} avatar
+ retrieved from the portal when the request has a valid I{Authorization}
+ header.
+ """
+ self.credentialFactories.append(BasicCredentialFactory('example.com'))
+ request = self.makeRequest([self.childName])
+ child = self._authorizedBasicLogin(request)
+ d = request.notifyFinish()
+ def cbFinished(ignored):
+ self.assertEquals(request.written, [self.childContent])
+ d.addCallback(cbFinished)
+ request.render(child)
+ return d
+
+
+ def test_renderAuthorized(self):
+ """
+ Resource traversal which terminates at an L{HTTPAuthSessionWrapper}
+ and includes correct authentication headers results in the
+ L{IResource} avatar (not one of its children) retrieved from the
+ portal being rendered.
+ """
+ self.credentialFactories.append(BasicCredentialFactory('example.com'))
+ # Request it exactly, not any of its children.
+ request = self.makeRequest([])
+ child = self._authorizedBasicLogin(request)
+ d = request.notifyFinish()
+ def cbFinished(ignored):
+ self.assertEquals(request.written, [self.avatarContent])
+ d.addCallback(cbFinished)
+ request.render(child)
+ return d
+
+
+ def test_getChallengeCalledWithRequest(self):
+ """
+ When L{HTTPAuthSessionWrapper} finds an L{ICredentialFactory} to issue
+ a challenge, it calls the C{getChallenge} method with the request as an
+ argument.
+ """
+ class DumbCredentialFactory(object):
+ implements(ICredentialFactory)
+ scheme = 'dumb'
+
+ def __init__(self):
+ self.requests = []
+
+ def getChallenge(self, request):
+ self.requests.append(request)
+ return {}
+
+ factory = DumbCredentialFactory()
+ self.credentialFactories.append(factory)
+ request = self.makeRequest([self.childName])
+ child = getChildForRequest(self.wrapper, request)
+ d = request.notifyFinish()
+ def cbFinished(ignored):
+ self.assertEqual(factory.requests, [request])
+ d.addCallback(cbFinished)
+ request.render(child)
+ return d
+
+
+ def test_logout(self):
+ """
+ The realm's logout callback is invoked after the resource is rendered.
+ """
+ self.credentialFactories.append(BasicCredentialFactory('example.com'))
+
+ class SlowerResource(Resource):
+ def render(self, request):
+ return NOT_DONE_YET
+
+ self.avatar.putChild(self.childName, SlowerResource())
+ request = self.makeRequest([self.childName])
+ child = self._authorizedBasicLogin(request)
+ request.render(child)
+ self.assertEqual(self.realm.loggedOut, 0)
+ request.finish()
+ self.assertEqual(self.realm.loggedOut, 1)
+
+
+ def test_decodeRaises(self):
+ """
+ Resource traversal which enouncters an L{HTTPAuthSessionWrapper}
+ results in an L{UnauthorizedResource} when the request has a I{Basic
+ Authorization} header which cannot be decoded using base64.
+ """
+ self.credentialFactories.append(BasicCredentialFactory('example.com'))
+ request = self.makeRequest([self.childName])
+ request.headers['authorization'] = 'Basic decode should fail'
+ child = getChildForRequest(self.wrapper, request)
+ self.assertIsInstance(child, UnauthorizedResource)
+
+
+ def test_selectParseResponse(self):
+ """
+ L{HTTPAuthSessionWrapper._selectParseHeader} returns a two-tuple giving
+ the L{ICredentialFactory} to use to parse the header and a string
+ containing the portion of the header which remains to be parsed.
+ """
+ basicAuthorization = 'Basic abcdef123456'
+ self.assertEqual(
+ self.wrapper._selectParseHeader(basicAuthorization),
+ (None, None))
+ factory = BasicCredentialFactory('example.com')
+ self.credentialFactories.append(factory)
+ self.assertEqual(
+ self.wrapper._selectParseHeader(basicAuthorization),
+ (factory, 'abcdef123456'))
+
+
+ def test_unexpectedDecodeError(self):
+ """
+ Any unexpected exception raised by the credential factory's C{decode}
+ method results in a 500 response code and causes the exception to be
+ logged.
+ """
+ class UnexpectedException(Exception):
+ pass
+
+ class BadFactory(object):
+ scheme = 'bad'
+
+ def getChallenge(self, client):
+ return {}
+
+ def decode(self, response, request):
+ raise UnexpectedException()
+
+ self.credentialFactories.append(BadFactory())
+ request = self.makeRequest([self.childName])
+ request.headers['authorization'] = 'Bad abc'
+ child = getChildForRequest(self.wrapper, request)
+ request.render(child)
+ self.assertEqual(request.responseCode, 500)
+ self.assertEqual(len(self.flushLoggedErrors(UnexpectedException)), 1)
+
+
+ def test_unexpectedLoginError(self):
+ """
+ Any unexpected failure from L{Portal.login} results in a 500 response
+ code and causes the failure to be logged.
+ """
+ class UnexpectedException(Exception):
+ pass
+
+ class BrokenChecker(object):
+ credentialInterfaces = (IUsernamePassword,)
+
+ def requestAvatarId(self, credentials):
+ raise UnexpectedException()
+
+ self.portal.registerChecker(BrokenChecker())
+ self.credentialFactories.append(BasicCredentialFactory('example.com'))
+ request = self.makeRequest([self.childName])
+ child = self._authorizedBasicLogin(request)
+ request.render(child)
+ self.assertEqual(request.responseCode, 500)
+ self.assertEqual(len(self.flushLoggedErrors(UnexpectedException)), 1)
+
+
+ def test_anonymousAccess(self):
+ """
+ Anonymous requests are allowed if a L{Portal} has an anonymous checker
+ registered.
+ """
+ unprotectedContents = "contents of the unprotected child resource"
+
+ self.avatars[ANONYMOUS] = Resource()
+ self.avatars[ANONYMOUS].putChild(
+ self.childName, Data(unprotectedContents, 'text/plain'))
+ self.portal.registerChecker(AllowAnonymousAccess())
+
+ self.credentialFactories.append(BasicCredentialFactory('example.com'))
+ request = self.makeRequest([self.childName])
+ child = getChildForRequest(self.wrapper, request)
+ d = request.notifyFinish()
+ def cbFinished(ignored):
+ self.assertEquals(request.written, [unprotectedContents])
+ d.addCallback(cbFinished)
+ request.render(child)
+ return d
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_newclient.py b/vendor/Twisted-10.0.0/twisted/web/test/test_newclient.py
new file mode 100644
index 0000000000..3654da5720
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_newclient.py
@@ -0,0 +1,2082 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web._newclient}.
+"""
+
+__metaclass__ = type
+
+from zope.interface import implements
+from zope.interface.verify import verifyObject
+
+from twisted.python.failure import Failure
+from twisted.internet.interfaces import IConsumer, IPushProducer
+from twisted.internet.error import ConnectionDone
+from twisted.internet.defer import Deferred, succeed, fail
+from twisted.internet.protocol import Protocol
+from twisted.trial.unittest import TestCase
+from twisted.test.proto_helpers import StringTransport, AccumulatingProtocol
+from twisted.web._newclient import UNKNOWN_LENGTH, STATUS, HEADER, BODY, DONE
+from twisted.web._newclient import Request, Response, HTTPParser, HTTPClientParser
+from twisted.web._newclient import BadResponseVersion, ParseError, HTTP11ClientProtocol
+from twisted.web._newclient import ChunkedEncoder, RequestGenerationFailed, RequestTransmissionFailed, ResponseFailed, WrongBodyLength, RequestNotSent
+from twisted.web._newclient import BadHeaders, ResponseDone, PotentialDataLoss, ExcessWrite
+from twisted.web._newclient import TransportProxyProducer, LengthEnforcingConsumer, makeStatefulDispatcher
+from twisted.web.http_headers import Headers
+from twisted.web.http import _DataLoss
+from twisted.web.iweb import IBodyProducer
+
+
+
+class ArbitraryException(Exception):
+ """
+ A unique, arbitrary exception type which L{twisted.web._newclient} knows
+ nothing about.
+ """
+
+
+class AnotherArbitraryException(Exception):
+ """
+ Similar to L{ArbitraryException} but with a different identity.
+ """
+
+
+# A re-usable Headers instance for tests which don't really care what headers
+# they're sending.
+_boringHeaders = Headers({'host': ['example.com']})
+
+
+def assertWrapperExceptionTypes(self, deferred, mainType, reasonTypes):
+ """
+ Assert that the given L{Deferred} fails with the exception given by
+ C{mainType} and that the exceptions wrapped by the instance of C{mainType}
+ it fails with match the list of exception types given by C{reasonTypes}.
+
+ This is a helper for testing failures of exceptions which subclass
+ L{_newclient._WrapperException}.
+
+ @param self: A L{TestCase} instance which will be used to make the
+ assertions.
+
+ @param deferred: The L{Deferred} which is expected to fail with
+ C{mainType}.
+
+ @param mainType: A L{_newclient._WrapperException} subclass which will be
+ trapped on C{deferred}.
+
+ @param reasonTypes: A sequence of exception types which will be trapped on
+ the resulting L{mainType} exception instance's C{reasons} sequence.
+
+ @return: A L{Deferred} which fires with the C{mainType} instance
+ C{deferred} fails with, or which fails somehow.
+ """
+ def cbFailed(err):
+ for reason, type in zip(err.reasons, reasonTypes):
+ reason.trap(type)
+ self.assertEqual(len(err.reasons), len(reasonTypes))
+ return err
+ d = self.assertFailure(deferred, mainType)
+ d.addCallback(cbFailed)
+ return d
+
+
+
+def assertResponseFailed(self, deferred, reasonTypes):
+ """
+ A simple helper to invoke L{assertWrapperExceptionTypes} with a C{mainType}
+ of L{ResponseFailed}.
+ """
+ return assertWrapperExceptionTypes(self, deferred, ResponseFailed, reasonTypes)
+
+
+
+def assertRequestGenerationFailed(self, deferred, reasonTypes):
+ """
+ A simple helper to invoke L{assertWrapperExceptionTypes} with a C{mainType}
+ of L{RequestGenerationFailed}.
+ """
+ return assertWrapperExceptionTypes(self, deferred, RequestGenerationFailed, reasonTypes)
+
+
+
+def assertRequestTransmissionFailed(self, deferred, reasonTypes):
+ """
+ A simple helper to invoke L{assertWrapperExceptionTypes} with a C{mainType}
+ of L{RequestTransmissionFailed}.
+ """
+ return assertWrapperExceptionTypes(self, deferred, RequestTransmissionFailed, reasonTypes)
+
+
+
+def justTransportResponse(transport):
+ """
+ Helper function for creating a Response which uses the given transport.
+ All of the other parameters to L{Response.__init__} are filled with
+ arbitrary values. Only use this method if you don't care about any of
+ them.
+ """
+ return Response(('HTTP', 1, 1), 200, 'OK', _boringHeaders, transport)
+
+
+class MakeStatefulDispatcherTests(TestCase):
+ """
+ Tests for L{makeStatefulDispatcher}.
+ """
+ def test_functionCalledByState(self):
+ """
+ A method defined with L{makeStatefulDispatcher} invokes a second
+ method based on the current state of the object.
+ """
+ class Foo:
+ _state = 'A'
+
+ def bar(self):
+ pass
+ bar = makeStatefulDispatcher('quux', bar)
+
+ def _quux_A(self):
+ return 'a'
+
+ def _quux_B(self):
+ return 'b'
+
+ stateful = Foo()
+ self.assertEqual(stateful.bar(), 'a')
+ stateful._state = 'B'
+ self.assertEqual(stateful.bar(), 'b')
+ stateful._state = 'C'
+ self.assertRaises(RuntimeError, stateful.bar)
+
+
+
+class HTTPParserTests(TestCase):
+ """
+ Tests for L{HTTPParser} which is responsible for the bulk of the task of
+ parsing HTTP bytes.
+ """
+ def test_statusCallback(self):
+ """
+ L{HTTPParser} calls its C{statusReceived} method when it receives a
+ status line.
+ """
+ status = []
+ protocol = HTTPParser()
+ protocol.statusReceived = status.append
+ protocol.makeConnection(StringTransport())
+ self.assertEqual(protocol.state, STATUS)
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+ self.assertEqual(status, ['HTTP/1.1 200 OK'])
+ self.assertEqual(protocol.state, HEADER)
+
+
+ def _headerTestSetup(self):
+ header = {}
+ protocol = HTTPParser()
+ protocol.headerReceived = header.__setitem__
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+ return header, protocol
+
+
+ def test_headerCallback(self):
+ """
+ L{HTTPParser} calls its C{headerReceived} method when it receives a
+ header.
+ """
+ header, protocol = self._headerTestSetup()
+ protocol.dataReceived('X-Foo:bar\r\n')
+ # Cannot tell it's not a continue header until the next line arrives
+ # and is not a continuation
+ protocol.dataReceived('\r\n')
+ self.assertEqual(header, {'X-Foo': 'bar'})
+ self.assertEqual(protocol.state, BODY)
+
+
+ def test_continuedHeaderCallback(self):
+ """
+ If a header is split over multiple lines, L{HTTPParser} calls
+ C{headerReceived} with the entire value once it is received.
+ """
+ header, protocol = self._headerTestSetup()
+ protocol.dataReceived('X-Foo: bar\r\n')
+ protocol.dataReceived(' baz\r\n')
+ protocol.dataReceived('\tquux\r\n')
+ protocol.dataReceived('\r\n')
+ self.assertEqual(header, {'X-Foo': 'bar baz\tquux'})
+ self.assertEqual(protocol.state, BODY)
+
+
+ def test_fieldContentWhitespace(self):
+ """
+ Leading and trailing linear whitespace is stripped from the header
+ value passed to the C{headerReceived} callback.
+ """
+ header, protocol = self._headerTestSetup()
+ value = ' \t \r\n bar \t\r\n \t\r\n'
+ protocol.dataReceived('X-Bar:' + value)
+ protocol.dataReceived('X-Foo:' + value)
+ protocol.dataReceived('\r\n')
+ self.assertEqual(header, {'X-Foo': 'bar',
+ 'X-Bar': 'bar'})
+
+
+ def test_allHeadersCallback(self):
+ """
+ After the last header is received, L{HTTPParser} calls
+ C{allHeadersReceived}.
+ """
+ called = []
+ header, protocol = self._headerTestSetup()
+ def allHeadersReceived():
+ called.append(protocol.state)
+ protocol.state = STATUS
+ protocol.allHeadersReceived = allHeadersReceived
+ protocol.dataReceived('\r\n')
+ self.assertEqual(called, [HEADER])
+ self.assertEqual(protocol.state, STATUS)
+
+
+ def test_noHeaderCallback(self):
+ """
+ If there are no headers in the message, L{HTTPParser} does not call
+ C{headerReceived}.
+ """
+ header, protocol = self._headerTestSetup()
+ protocol.dataReceived('\r\n')
+ self.assertEqual(header, {})
+ self.assertEqual(protocol.state, BODY)
+
+
+ def test_headersSavedOnResponse(self):
+ """
+ All headers received by L{HTTPParser} are added to
+ L{HTTPParser.headers}.
+ """
+ protocol = HTTPParser()
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+ protocol.dataReceived('X-Foo: bar\r\n')
+ protocol.dataReceived('X-Foo: baz\r\n')
+ protocol.dataReceived('\r\n')
+ self.assertEqual(
+ list(protocol.headers.getAllRawHeaders()),
+ [('X-Foo', ['bar', 'baz'])])
+
+
+ def test_connectionControlHeaders(self):
+ """
+ L{HTTPParser.isConnectionControlHeader} returns C{True} for headers
+ which are always connection control headers (similar to "hop-by-hop"
+ headers from RFC 2616 section 13.5.1) and C{False} for other headers.
+ """
+ protocol = HTTPParser()
+ connHeaderNames = [
+ 'content-length', 'connection', 'keep-alive', 'te', 'trailers',
+ 'transfer-encoding', 'upgrade', 'proxy-connection']
+
+ for header in connHeaderNames:
+ self.assertTrue(
+ protocol.isConnectionControlHeader(header),
+ "Expecting %r to be a connection control header, but "
+ "wasn't" % (header,))
+ self.assertFalse(
+ protocol.isConnectionControlHeader("date"),
+ "Expecting the arbitrarily selected 'date' header to not be "
+ "a connection control header, but was.")
+
+
+ def test_switchToBodyMode(self):
+ """
+ L{HTTPParser.switchToBodyMode} raises L{RuntimeError} if called more
+ than once.
+ """
+ protocol = HTTPParser()
+ protocol.makeConnection(StringTransport())
+ protocol.switchToBodyMode(object())
+ self.assertRaises(RuntimeError, protocol.switchToBodyMode, object())
+
+
+
+class HTTPClientParserTests(TestCase):
+ """
+ Tests for L{HTTPClientParser} which is responsible for parsing HTTP
+ response messages.
+ """
+ def test_parseVersion(self):
+ """
+ L{HTTPClientParser.parseVersion} parses a status line into its three
+ components.
+ """
+ protocol = HTTPClientParser(None, None)
+ self.assertEqual(
+ protocol.parseVersion('CANDY/7.2'),
+ ('CANDY', 7, 2))
+
+
+ def test_parseBadVersion(self):
+ """
+ L{HTTPClientParser.parseVersion} raises L{ValueError} when passed an
+ unparsable version.
+ """
+ protocol = HTTPClientParser(None, None)
+ e = BadResponseVersion
+ f = protocol.parseVersion
+
+ def checkParsing(s):
+ exc = self.assertRaises(e, f, s)
+ self.assertEqual(exc.data, s)
+
+ checkParsing('foo')
+ checkParsing('foo/bar/baz')
+
+ checkParsing('foo/')
+ checkParsing('foo/..')
+
+ checkParsing('foo/a.b')
+ checkParsing('foo/-1.-1')
+
+
+ def test_responseStatusParsing(self):
+ """
+ L{HTTPClientParser.statusReceived} parses the version, code, and phrase
+ from the status line and stores them on the response object.
+ """
+ request = Request('GET', '/', _boringHeaders, None)
+ protocol = HTTPClientParser(request, None)
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+ self.assertEqual(protocol.response.version, ('HTTP', 1, 1))
+ self.assertEqual(protocol.response.code, 200)
+ self.assertEqual(protocol.response.phrase, 'OK')
+
+
+ def test_badResponseStatus(self):
+ """
+ L{HTTPClientParser.statusReceived} raises L{ParseError} if it is called
+ with a status line which cannot be parsed.
+ """
+ protocol = HTTPClientParser(None, None)
+
+ def checkParsing(s):
+ exc = self.assertRaises(ParseError, protocol.statusReceived, s)
+ self.assertEqual(exc.data, s)
+
+ # If there are fewer than three whitespace-delimited parts to the
+ # status line, it is not valid and cannot be parsed.
+ checkParsing('foo')
+ checkParsing('HTTP/1.1 200')
+
+ # If the response code is not an integer, the status line is not valid
+ # and cannot be parsed.
+ checkParsing('HTTP/1.1 bar OK')
+
+
+ def _noBodyTest(self, request, response):
+ """
+ Assert that L{HTTPClientParser} parses the given C{response} to
+ C{request}, resulting in a response with no body and no extra bytes and
+ leaving the transport in the producing state.
+
+ @param request: A L{Request} instance which might have caused a server
+ to return the given response.
+ @param response: A string giving the response to be parsed.
+
+ @return: A C{dict} of headers from the response.
+ """
+ header = {}
+ finished = []
+ protocol = HTTPClientParser(request, finished.append)
+ protocol.headerReceived = header.__setitem__
+ body = []
+ protocol._bodyDataReceived = body.append
+ transport = StringTransport()
+ protocol.makeConnection(transport)
+ protocol.dataReceived(response)
+ self.assertEqual(transport.producerState, 'producing')
+ self.assertEqual(protocol.state, DONE)
+ self.assertEqual(body, [])
+ self.assertEqual(finished, [''])
+ self.assertEqual(protocol.response.length, 0)
+ return header
+
+
+ def test_headResponse(self):
+ """
+ If the response is to a HEAD request, no body is expected, the body
+ callback is not invoked, and the I{Content-Length} header is passed to
+ the header callback.
+ """
+ request = Request('HEAD', '/', _boringHeaders, None)
+ status = (
+ 'HTTP/1.1 200 OK\r\n'
+ 'Content-Length: 10\r\n'
+ '\r\n')
+ header = self._noBodyTest(request, status)
+ self.assertEqual(header, {'Content-Length': '10'})
+
+
+ def test_noContentResponse(self):
+ """
+ If the response code is I{NO CONTENT} (204), no body is expected and
+ the body callback is not invoked.
+ """
+ request = Request('GET', '/', _boringHeaders, None)
+ status = (
+ 'HTTP/1.1 204 NO CONTENT\r\n'
+ '\r\n')
+ self._noBodyTest(request, status)
+
+
+ def test_notModifiedResponse(self):
+ """
+ If the response code is I{NOT MODIFIED} (304), no body is expected and
+ the body callback is not invoked.
+ """
+ request = Request('GET', '/', _boringHeaders, None)
+ status = (
+ 'HTTP/1.1 304 NOT MODIFIED\r\n'
+ '\r\n')
+ self._noBodyTest(request, status)
+
+
+ def test_responseHeaders(self):
+ """
+ The response headers are added to the response object's C{headers}
+ L{Headers} instance.
+ """
+ protocol = HTTPClientParser(
+ Request('GET', '/', _boringHeaders, None),
+ lambda rest: None)
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+ protocol.dataReceived('X-Foo: bar\r\n')
+ protocol.dataReceived('\r\n')
+ self.assertEqual(
+ protocol.connHeaders,
+ Headers({}))
+ self.assertEqual(
+ protocol.response.headers,
+ Headers({'x-foo': ['bar']}))
+ self.assertIdentical(protocol.response.length, UNKNOWN_LENGTH)
+
+
+ def test_connectionHeaders(self):
+ """
+ The connection control headers are added to the parser's C{connHeaders}
+ L{Headers} instance.
+ """
+ protocol = HTTPClientParser(
+ Request('GET', '/', _boringHeaders, None),
+ lambda rest: None)
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+ protocol.dataReceived('Content-Length: 123\r\n')
+ protocol.dataReceived('Connection: close\r\n')
+ protocol.dataReceived('\r\n')
+ self.assertEqual(
+ protocol.response.headers,
+ Headers({}))
+ self.assertEqual(
+ protocol.connHeaders,
+ Headers({'content-length': ['123'],
+ 'connection': ['close']}))
+ self.assertEqual(protocol.response.length, 123)
+
+
+ def test_headResponseContentLengthEntityHeader(self):
+ """
+ If a HEAD request is made, the I{Content-Length} header in the response
+ is added to the response headers, not the connection control headers.
+ """
+ protocol = HTTPClientParser(
+ Request('HEAD', '/', _boringHeaders, None),
+ lambda rest: None)
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+ protocol.dataReceived('Content-Length: 123\r\n')
+ protocol.dataReceived('\r\n')
+ self.assertEqual(
+ protocol.response.headers,
+ Headers({'content-length': ['123']}))
+ self.assertEqual(
+ protocol.connHeaders,
+ Headers({}))
+ self.assertEqual(protocol.response.length, 0)
+
+
+ def test_contentLength(self):
+ """
+ If a response includes a body with a length given by the
+ I{Content-Length} header, the bytes which make up the body are passed
+ to the C{_bodyDataReceived} callback on the L{HTTPParser}.
+ """
+ finished = []
+ protocol = HTTPClientParser(
+ Request('GET', '/', _boringHeaders, None),
+ finished.append)
+ transport = StringTransport()
+ protocol.makeConnection(transport)
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+ body = []
+ protocol.response._bodyDataReceived = body.append
+ protocol.dataReceived('Content-Length: 10\r\n')
+ protocol.dataReceived('\r\n')
+
+ # Incidentally, the transport should be paused now. It is the response
+ # object's responsibility to resume this when it is ready for bytes.
+ self.assertEqual(transport.producerState, 'paused')
+
+ self.assertEqual(protocol.state, BODY)
+ protocol.dataReceived('x' * 6)
+ self.assertEqual(body, ['x' * 6])
+ self.assertEqual(protocol.state, BODY)
+ protocol.dataReceived('y' * 4)
+ self.assertEqual(body, ['x' * 6, 'y' * 4])
+ self.assertEqual(protocol.state, DONE)
+ self.assertTrue(finished, [''])
+
+
+ def test_zeroContentLength(self):
+ """
+ If a response includes a I{Content-Length} header indicating zero bytes
+ in the response, L{Response.length} is set accordingly and no data is
+ delivered to L{Response._bodyDataReceived}.
+ """
+ finished = []
+ protocol = HTTPClientParser(
+ Request('GET', '/', _boringHeaders, None),
+ finished.append)
+
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+
+ body = []
+ protocol.response._bodyDataReceived = body.append
+
+ protocol.dataReceived('Content-Length: 0\r\n')
+ protocol.dataReceived('\r\n')
+
+ self.assertEqual(protocol.state, DONE)
+ self.assertEqual(body, [])
+ self.assertTrue(finished, [''])
+ self.assertEqual(protocol.response.length, 0)
+
+
+
+ def test_multipleContentLengthHeaders(self):
+ """
+ If a response includes multiple I{Content-Length} headers,
+ L{HTTPClientParser.dataReceived} raises L{ValueError} to indicate that
+ the response is invalid and the transport is now unusable.
+ """
+ protocol = HTTPClientParser(
+ Request('GET', '/', _boringHeaders, None),
+ None)
+
+ protocol.makeConnection(StringTransport())
+ self.assertRaises(
+ ValueError,
+ protocol.dataReceived,
+ 'HTTP/1.1 200 OK\r\n'
+ 'Content-Length: 1\r\n'
+ 'Content-Length: 2\r\n'
+ '\r\n')
+
+
+ def test_extraBytesPassedBack(self):
+ """
+ If extra bytes are received past the end of a response, they are passed
+ to the finish callback.
+ """
+ finished = []
+ protocol = HTTPClientParser(
+ Request('GET', '/', _boringHeaders, None),
+ finished.append)
+
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+ protocol.dataReceived('Content-Length: 0\r\n')
+ protocol.dataReceived('\r\nHere is another thing!')
+ self.assertEqual(protocol.state, DONE)
+ self.assertEqual(finished, ['Here is another thing!'])
+
+
+ def test_extraBytesPassedBackHEAD(self):
+ """
+ If extra bytes are received past the end of the headers of a response
+ to a HEAD request, they are passed to the finish callback.
+ """
+ finished = []
+ protocol = HTTPClientParser(
+ Request('HEAD', '/', _boringHeaders, None),
+ finished.append)
+
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+ protocol.dataReceived('Content-Length: 12\r\n')
+ protocol.dataReceived('\r\nHere is another thing!')
+ self.assertEqual(protocol.state, DONE)
+ self.assertEqual(finished, ['Here is another thing!'])
+
+
+ def test_chunkedResponseBody(self):
+ """
+ If the response headers indicate the response body is encoded with the
+ I{chunked} transfer encoding, the body is decoded according to that
+ transfer encoding before being passed to L{Response._bodyDataReceived}.
+ """
+ finished = []
+ protocol = HTTPClientParser(
+ Request('GET', '/', _boringHeaders, None),
+ finished.append)
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+
+ body = []
+ protocol.response._bodyDataReceived = body.append
+
+ protocol.dataReceived('Transfer-Encoding: chunked\r\n')
+ protocol.dataReceived('\r\n')
+
+ # No data delivered yet
+ self.assertEqual(body, [])
+
+ # Cannot predict the length of a chunked encoded response body.
+ self.assertIdentical(protocol.response.length, UNKNOWN_LENGTH)
+
+ # Deliver some chunks and make sure the data arrives
+ protocol.dataReceived('3\r\na')
+ self.assertEqual(body, ['a'])
+ protocol.dataReceived('bc\r\n')
+ self.assertEqual(body, ['a', 'bc'])
+
+ # The response's _bodyDataFinished method should be called when the last
+ # chunk is received. Extra data should be passed to the finished
+ # callback.
+ protocol.dataReceived('0\r\n\r\nextra')
+ self.assertEqual(finished, ['extra'])
+
+
+ def test_unknownContentLength(self):
+ """
+ If a response does not include a I{Transfer-Encoding} or a
+ I{Content-Length}, the end of response body is indicated by the
+ connection being closed.
+ """
+ finished = []
+ protocol = HTTPClientParser(
+ Request('GET', '/', _boringHeaders, None), finished.append)
+ transport = StringTransport()
+ protocol.makeConnection(transport)
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+
+ body = []
+ protocol.response._bodyDataReceived = body.append
+
+ protocol.dataReceived('\r\n')
+ protocol.dataReceived('foo')
+ protocol.dataReceived('bar')
+ self.assertEqual(body, ['foo', 'bar'])
+ protocol.connectionLost(ConnectionDone("simulated end of connection"))
+ self.assertEqual(finished, [''])
+
+
+ def test_contentLengthAndTransferEncoding(self):
+ """
+ According to RFC 2616, section 4.4, point 3, if I{Content-Length} and
+ I{Transfer-Encoding: chunked} are present, I{Content-Length} MUST be
+ ignored
+ """
+ finished = []
+ protocol = HTTPClientParser(
+ Request('GET', '/', _boringHeaders, None), finished.append)
+ transport = StringTransport()
+ protocol.makeConnection(transport)
+ protocol.dataReceived('HTTP/1.1 200 OK\r\n')
+
+ body = []
+ protocol.response._bodyDataReceived = body.append
+
+ protocol.dataReceived(
+ 'Content-Length: 102\r\n'
+ 'Transfer-Encoding: chunked\r\n'
+ '\r\n'
+ '3\r\n'
+ 'abc\r\n'
+ '0\r\n'
+ '\r\n')
+
+ self.assertEqual(body, ['abc'])
+ self.assertEqual(finished, [''])
+
+
+ def test_connectionLostBeforeBody(self):
+ """
+ If L{HTTPClientParser.connectionLost} is called before the headers are
+ finished, the C{_responseDeferred} is fired with the L{Failure} passed
+ to C{connectionLost}.
+ """
+ transport = StringTransport()
+ protocol = HTTPClientParser(Request('GET', '/', _boringHeaders, None), None)
+ protocol.makeConnection(transport)
+ # Grab this here because connectionLost gets rid of the attribute
+ responseDeferred = protocol._responseDeferred
+ protocol.connectionLost(Failure(ArbitraryException()))
+
+ return assertResponseFailed(
+ self, responseDeferred, [ArbitraryException])
+
+
+ def test_connectionLostWithError(self):
+ """
+ If one of the L{Response} methods called by
+ L{HTTPClientParser.connectionLost} raises an exception, the exception
+ is logged and not re-raised.
+ """
+ transport = StringTransport()
+ protocol = HTTPClientParser(Request('GET', '/', _boringHeaders, None), None)
+ protocol.makeConnection(transport)
+
+ response = []
+ protocol._responseDeferred.addCallback(response.append)
+ protocol.dataReceived(
+ 'HTTP/1.1 200 OK\r\n'
+ 'Content-Length: 1\r\n'
+ '\r\n')
+ response = response[0]
+
+ # Arrange for an exception
+ def fakeBodyDataFinished(err=None):
+ raise ArbitraryException()
+ response._bodyDataFinished = fakeBodyDataFinished
+
+ protocol.connectionLost(None)
+
+ self.assertEqual(len(self.flushLoggedErrors(ArbitraryException)), 1)
+
+
+
+class SlowRequest:
+ """
+ L{SlowRequest} is a fake implementation of L{Request} which is easily
+ controlled externally (for example, by code in a test method).
+
+ @ivar stopped: A flag indicating whether C{stopWriting} has been called.
+
+ @ivar finished: After C{writeTo} is called, a L{Deferred} which was
+ returned by that method. L{SlowRequest} will never fire this
+ L{Deferred}.
+ """
+ method = 'GET'
+ stopped = False
+
+ def writeTo(self, transport):
+ self.finished = Deferred()
+ return self.finished
+
+
+ def stopWriting(self):
+ self.stopped = True
+
+
+
+class SimpleRequest:
+ """
+ L{SimpleRequest} is a fake implementation of L{Request} which writes a
+ short, fixed string to the transport passed to its C{writeTo} method and
+ returns a succeeded L{Deferred}. This vaguely emulates the behavior of a
+ L{Request} with no body producer.
+ """
+ def writeTo(self, transport):
+ transport.write('SOME BYTES')
+ return succeed(None)
+
+
+
+class HTTP11ClientProtocolTests(TestCase):
+ """
+ Tests for the HTTP 1.1 client protocol implementation,
+ L{HTTP11ClientProtocol}.
+ """
+ def setUp(self):
+ """
+ Create an L{HTTP11ClientProtocol} connected to a fake transport.
+ """
+ self.transport = StringTransport()
+ self.protocol = HTTP11ClientProtocol()
+ self.protocol.makeConnection(self.transport)
+
+
+ def test_request(self):
+ """
+ L{HTTP11ClientProtocol.request} accepts a L{Request} and calls its
+ C{writeTo} method with its own transport.
+ """
+ self.protocol.request(SimpleRequest())
+ self.assertEqual(self.transport.value(), 'SOME BYTES')
+
+
+ def test_secondRequest(self):
+ """
+ The second time L{HTTP11ClientProtocol.request} is called, it returns a
+ L{Deferred} which immediately fires with a L{Failure} wrapping a
+ L{RequestNotSent} exception.
+ """
+ self.protocol.request(SlowRequest())
+ def cbNotSent(ignored):
+ self.assertEqual(self.transport.value(), '')
+ d = self.assertFailure(
+ self.protocol.request(SimpleRequest()), RequestNotSent)
+ d.addCallback(cbNotSent)
+ return d
+
+
+ def test_requestAfterConnectionLost(self):
+ """
+ L{HTTP11ClientProtocol.request} returns a L{Deferred} which immediately
+ fires with a L{Failure} wrapping a L{RequestNotSent} if called after
+ the protocol has been disconnected.
+ """
+ self.protocol.connectionLost(
+ Failure(ConnectionDone("sad transport")))
+ def cbNotSent(ignored):
+ self.assertEqual(self.transport.value(), '')
+ d = self.assertFailure(
+ self.protocol.request(SimpleRequest()), RequestNotSent)
+ d.addCallback(cbNotSent)
+ return d
+
+
+ def test_failedWriteTo(self):
+ """
+ If the L{Deferred} returned by L{Request.writeTo} fires with a
+ L{Failure}, L{HTTP11ClientProtocol.request} disconnects its transport
+ and returns a L{Deferred} which fires with a L{Failure} of
+ L{RequestGenerationFailed} wrapping the underlying failure.
+ """
+ class BrokenRequest:
+ def writeTo(self, transport):
+ return fail(ArbitraryException())
+
+ d = self.protocol.request(BrokenRequest())
+ def cbFailed(ignored):
+ self.assertTrue(self.transport.disconnecting)
+ # Simulate what would happen if the protocol had a real transport
+ # and make sure no exception is raised.
+ self.protocol.connectionLost(
+ Failure(ConnectionDone("you asked for it")))
+ d = assertRequestGenerationFailed(self, d, [ArbitraryException])
+ d.addCallback(cbFailed)
+ return d
+
+
+ def test_synchronousWriteToError(self):
+ """
+ If L{Request.writeTo} raises an exception,
+ L{HTTP11ClientProtocol.request} returns a L{Deferred} which fires with
+ a L{Failure} of L{RequestGenerationFailed} wrapping that exception.
+ """
+ class BrokenRequest:
+ def writeTo(self, transport):
+ raise ArbitraryException()
+
+ d = self.protocol.request(BrokenRequest())
+ return assertRequestGenerationFailed(self, d, [ArbitraryException])
+
+
+ def test_connectionLostDuringRequestGeneration(self, mode=None):
+ """
+ If L{HTTP11ClientProtocol}'s transport is disconnected before the
+ L{Deferred} returned by L{Request.writeTo} fires, the L{Deferred}
+ returned by L{HTTP11ClientProtocol.request} fires with a L{Failure} of
+ L{RequestTransmissionFailed} wrapping the underlying failure.
+ """
+ request = SlowRequest()
+ d = self.protocol.request(request)
+ d = assertRequestTransmissionFailed(self, d, [ArbitraryException])
+
+ # The connection hasn't been lost yet. The request should still be
+ # allowed to do its thing.
+ self.assertFalse(request.stopped)
+
+ self.protocol.connectionLost(Failure(ArbitraryException()))
+
+ # Now the connection has been lost. The request should have been told
+ # to stop writing itself.
+ self.assertTrue(request.stopped)
+
+ if mode == 'callback':
+ request.finished.callback(None)
+ elif mode == 'errback':
+ request.finished.errback(Failure(AnotherArbitraryException()))
+ errors = self.flushLoggedErrors(AnotherArbitraryException)
+ self.assertEqual(len(errors), 1)
+ else:
+ # Don't fire the writeTo Deferred at all.
+ pass
+ return d
+
+
+ def test_connectionLostBeforeGenerationFinished(self):
+ """
+ If the request passed to L{HTTP11ClientProtocol} finishes generation
+ successfully after the L{HTTP11ClientProtocol}'s connection has been
+ lost, nothing happens.
+ """
+ return self.test_connectionLostDuringRequestGeneration('callback')
+
+
+ def test_connectionLostBeforeGenerationFailed(self):
+ """
+ If the request passed to L{HTTP11ClientProtocol} finished generation
+ with an error after the L{HTTP11ClientProtocol}'s connection has been
+ lost, nothing happens.
+ """
+ return self.test_connectionLostDuringRequestGeneration('errback')
+
+
+ def test_receiveSimplestResponse(self):
+ """
+ When a response is delivered to L{HTTP11ClientProtocol}, the
+ L{Deferred} previously returned by the C{request} method is called back
+ with a L{Response} instance and the connection is closed.
+ """
+ d = self.protocol.request(Request('GET', '/', _boringHeaders, None))
+ def cbRequest(response):
+ self.assertEqual(response.code, 200)
+ self.assertEqual(response.headers, Headers())
+ self.assertTrue(self.transport.disconnecting)
+ d.addCallback(cbRequest)
+ self.protocol.dataReceived(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n")
+ return d
+
+
+ def test_receiveResponseHeaders(self):
+ """
+ The headers included in a response delivered to L{HTTP11ClientProtocol}
+ are included on the L{Response} instance passed to the callback
+ returned by the C{request} method.
+ """
+ d = self.protocol.request(Request('GET', '/', _boringHeaders, None))
+ def cbRequest(response):
+ expected = Headers({'x-foo': ['bar', 'baz']})
+ self.assertEqual(response.headers, expected)
+ d.addCallback(cbRequest)
+ self.protocol.dataReceived(
+ "HTTP/1.1 200 OK\r\n"
+ "X-Foo: bar\r\n"
+ "X-Foo: baz\r\n"
+ "\r\n")
+ return d
+
+
+ def test_receiveResponseBeforeRequestGenerationDone(self):
+ """
+ If response bytes are delivered to L{HTTP11ClientProtocol} before the
+ L{Deferred} returned by L{Request.writeTo} fires, those response bytes
+ are parsed as part of the response.
+ """
+ request = SlowRequest()
+ d = self.protocol.request(request)
+ self.protocol.dataReceived(
+ "HTTP/1.1 200 OK\r\n"
+ "X-Foo: bar\r\n"
+ "Content-Length: 6\r\n"
+ "\r\n"
+ "foobar")
+ def cbResponse(response):
+ p = AccumulatingProtocol()
+ whenFinished = p.closedDeferred = Deferred()
+ response.deliverBody(p)
+ return whenFinished.addCallback(
+ lambda ign: (response, p.data))
+ d.addCallback(cbResponse)
+ def cbAllResponse((response, body)):
+ self.assertEqual(response.version, ('HTTP', 1, 1))
+ self.assertEqual(response.code, 200)
+ self.assertEqual(response.phrase, 'OK')
+ self.assertEqual(response.headers, Headers({'x-foo': ['bar']}))
+ self.assertEqual(body, "foobar")
+
+ # Also nothing bad should happen if the request does finally
+ # finish, even though it is completely irrelevant.
+ request.finished.callback(None)
+
+ d.addCallback(cbAllResponse)
+ return d
+
+
+ def test_receiveResponseBody(self):
+ """
+ The C{deliverBody} method of the response object with which the
+ L{Deferred} returned by L{HTTP11ClientProtocol.request} fires can be
+ used to get the body of the response.
+ """
+ protocol = AccumulatingProtocol()
+ whenFinished = protocol.closedDeferred = Deferred()
+ requestDeferred = self.protocol.request(Request('GET', '/', _boringHeaders, None))
+
+ self.protocol.dataReceived(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 6\r\n"
+ "\r")
+
+ # Here's what's going on: all the response headers have been delivered
+ # by this point, so the request Deferred can fire with a Response
+ # object. The body is yet to come, but that's okay, because the
+ # Response object is how you *get* the body.
+ result = []
+ requestDeferred.addCallback(result.append)
+
+ self.assertEqual(result, [])
+ # Deliver the very last byte of the response. It is exactly at this
+ # point which the Deferred returned by request should fire.
+ self.protocol.dataReceived("\n")
+ response = result[0]
+
+ response.deliverBody(protocol)
+
+ self.protocol.dataReceived("foo")
+ self.protocol.dataReceived("bar")
+
+ def cbAllResponse(ignored):
+ self.assertEqual(protocol.data, "foobar")
+ protocol.closedReason.trap(ResponseDone)
+ whenFinished.addCallback(cbAllResponse)
+ return whenFinished
+
+
+ def test_responseBodyFinishedWhenConnectionLostWhenContentLengthIsUnknown(
+ self):
+ """
+ If the length of the response body is unknown, the protocol passed to
+ the response's C{deliverBody} method has its C{connectionLost}
+ method called with a L{Failure} wrapping a L{PotentialDataLoss}
+ exception.
+ """
+ requestDeferred = self.protocol.request(Request('GET', '/', _boringHeaders, None))
+ self.protocol.dataReceived(
+ "HTTP/1.1 200 OK\r\n"
+ "\r\n")
+
+ result = []
+ requestDeferred.addCallback(result.append)
+ response = result[0]
+
+ protocol = AccumulatingProtocol()
+ response.deliverBody(protocol)
+
+ self.protocol.dataReceived("foo")
+ self.protocol.dataReceived("bar")
+
+ self.assertEqual(protocol.data, "foobar")
+ self.protocol.connectionLost(
+ Failure(ConnectionDone("low-level transport disconnected")))
+
+ protocol.closedReason.trap(PotentialDataLoss)
+
+
+ def test_chunkedResponseBodyUnfinishedWhenConnectionLost(self):
+ """
+ If the final chunk has not been received when the connection is lost
+ (for any reason), the protocol passed to C{deliverBody} has its
+ C{connectionLost} method called with a L{Failure} wrapping the
+ exception for that reason.
+ """
+ requestDeferred = self.protocol.request(Request('GET', '/', _boringHeaders, None))
+ self.protocol.dataReceived(
+ "HTTP/1.1 200 OK\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n")
+
+ result = []
+ requestDeferred.addCallback(result.append)
+ response = result[0]
+
+ protocol = AccumulatingProtocol()
+ response.deliverBody(protocol)
+
+ self.protocol.dataReceived("3\r\nfoo\r\n")
+ self.protocol.dataReceived("3\r\nbar\r\n")
+
+ self.assertEqual(protocol.data, "foobar")
+
+ self.protocol.connectionLost(Failure(ArbitraryException()))
+
+ return assertResponseFailed(
+ self, fail(protocol.closedReason), [ArbitraryException, _DataLoss])
+
+
+ def test_parserDataReceivedException(self):
+ """
+ If the parser L{HTTP11ClientProtocol} delivers bytes to in
+ C{dataReceived} raises an exception, the exception is wrapped in a
+ L{Failure} and passed to the parser's C{connectionLost} and then the
+ L{HTTP11ClientProtocol}'s transport is disconnected.
+ """
+ requestDeferred = self.protocol.request(Request('GET', '/', _boringHeaders, None))
+ self.protocol.dataReceived('unparseable garbage goes here\r\n')
+ d = assertResponseFailed(self, requestDeferred, [ParseError])
+ def cbFailed(exc):
+ self.assertTrue(self.transport.disconnecting)
+ self.assertEqual(
+ exc.reasons[0].value.data, 'unparseable garbage goes here')
+
+ # Now do what StringTransport doesn't do but a real transport would
+ # have, call connectionLost on the HTTP11ClientProtocol. Nothing
+ # is asserted about this, but it's important for it to not raise an
+ # exception.
+ self.protocol.connectionLost(Failure(ConnectionDone("it is done")))
+
+ d.addCallback(cbFailed)
+ return d
+
+
+ def test_proxyStopped(self):
+ """
+ When the HTTP response parser is disconnected, the
+ L{TransportProxyProducer} which was connected to it as a transport is
+ stopped.
+ """
+ requestDeferred = self.protocol.request(Request('GET', '/', _boringHeaders, None))
+ transport = self.protocol._parser.transport
+ self.assertIdentical(transport._producer, self.transport)
+ self.protocol._disconnectParser(Failure(ConnectionDone("connection done")))
+ self.assertIdentical(transport._producer, None)
+ return assertResponseFailed(self, requestDeferred, [ConnectionDone])
+
+
+
+class StringProducer:
+ """
+ L{StringProducer} is a dummy body producer.
+
+ @ivar stopped: A flag which indicates whether or not C{stopProducing} has
+ been called.
+ @ivar consumer: After C{startProducing} is called, the value of the
+ C{consumer} argument to that method.
+ @ivar finished: After C{startProducing} is called, a L{Deferred} which was
+ returned by that method. L{StringProducer} will never fire this
+ L{Deferred}.
+ """
+ implements(IBodyProducer)
+
+ stopped = False
+
+ def __init__(self, length):
+ self.length = length
+
+
+ def startProducing(self, consumer):
+ self.consumer = consumer
+ self.finished = Deferred()
+ return self.finished
+
+
+ def stopProducing(self):
+ self.stopped = True
+
+
+
+class RequestTests(TestCase):
+ """
+ Tests for L{Request}.
+ """
+ def setUp(self):
+ self.transport = StringTransport()
+
+
+ def test_sendSimplestRequest(self):
+ """
+ L{Request.writeTo} formats the request data and writes it to the given
+ transport.
+ """
+ Request('GET', '/', _boringHeaders, None).writeTo(self.transport)
+ self.assertEqual(
+ self.transport.value(),
+ "GET / HTTP/1.1\r\n"
+ "Connection: close\r\n"
+ "Host: example.com\r\n"
+ "\r\n")
+
+
+ def test_sendRequestHeaders(self):
+ """
+ L{Request.writeTo} formats header data and writes it to the given
+ transport.
+ """
+ headers = Headers({'x-foo': ['bar', 'baz'], 'host': ['example.com']})
+ Request('GET', '/foo', headers, None).writeTo(self.transport)
+ lines = self.transport.value().split('\r\n')
+ self.assertEqual(lines[0], "GET /foo HTTP/1.1")
+ self.assertEqual(lines[-2:], ["", ""])
+ del lines[0], lines[-2:]
+ lines.sort()
+ self.assertEqual(
+ lines,
+ ["Connection: close",
+ "Host: example.com",
+ "X-Foo: bar",
+ "X-Foo: baz"])
+
+
+ def test_sendChunkedRequestBody(self):
+ """
+ L{Request.writeTo} uses chunked encoding to write data from the request
+ body producer to the given transport. It registers the request body
+ producer with the transport.
+ """
+ producer = StringProducer(UNKNOWN_LENGTH)
+ request = Request('POST', '/bar', _boringHeaders, producer)
+ request.writeTo(self.transport)
+
+ self.assertNotIdentical(producer.consumer, None)
+ self.assertIdentical(self.transport.producer, producer)
+ self.assertTrue(self.transport.streaming)
+
+ self.assertEqual(
+ self.transport.value(),
+ "POST /bar HTTP/1.1\r\n"
+ "Connection: close\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "Host: example.com\r\n"
+ "\r\n")
+ self.transport.clear()
+
+ producer.consumer.write('x' * 3)
+ producer.consumer.write('y' * 15)
+ producer.finished.callback(None)
+ self.assertIdentical(self.transport.producer, None)
+ self.assertEqual(
+ self.transport.value(),
+ "3\r\n"
+ "xxx\r\n"
+ "f\r\n"
+ "yyyyyyyyyyyyyyy\r\n"
+ "0\r\n"
+ "\r\n")
+
+
+ def test_sendChunkedRequestBodyWithError(self):
+ """
+ If L{Request} is created with a C{bodyProducer} without a known length
+ and the L{Deferred} returned from its C{startProducing} method fires
+ with a L{Failure}, the L{Deferred} returned by L{Request.writeTo} fires
+ with that L{Failure} and the body producer is unregistered from the
+ transport. The final zero-length chunk is not written to the
+ transport.
+ """
+ producer = StringProducer(UNKNOWN_LENGTH)
+ request = Request('POST', '/bar', _boringHeaders, producer)
+ writeDeferred = request.writeTo(self.transport)
+ self.transport.clear()
+ producer.finished.errback(ArbitraryException())
+ def cbFailed(ignored):
+ self.assertEqual(self.transport.value(), "")
+ self.assertIdentical(self.transport.producer, None)
+ d = self.assertFailure(writeDeferred, ArbitraryException)
+ d.addCallback(cbFailed)
+ return d
+
+
+ def test_sendRequestBodyWithLength(self):
+ """
+ If L{Request} is created with a C{bodyProducer} with a known length,
+ that length is sent as the value for the I{Content-Length} header and
+ chunked encoding is not used.
+ """
+ producer = StringProducer(3)
+ request = Request('POST', '/bar', _boringHeaders, producer)
+ request.writeTo(self.transport)
+
+ self.assertNotIdentical(producer.consumer, None)
+ self.assertIdentical(self.transport.producer, producer)
+ self.assertTrue(self.transport.streaming)
+
+ self.assertEqual(
+ self.transport.value(),
+ "POST /bar HTTP/1.1\r\n"
+ "Connection: close\r\n"
+ "Content-Length: 3\r\n"
+ "Host: example.com\r\n"
+ "\r\n")
+ self.transport.clear()
+
+ producer.consumer.write('abc')
+ producer.finished.callback(None)
+ self.assertIdentical(self.transport.producer, None)
+ self.assertEqual(self.transport.value(), "abc")
+
+
+ def test_sendRequestBodyWithTooFewBytes(self):
+ """
+ If L{Request} is created with a C{bodyProducer} with a known length and
+ the producer does not produce that many bytes, the L{Deferred} returned
+ by L{Request.writeTo} fires with a L{Failure} wrapping a
+ L{WrongBodyLength} exception.
+ """
+ producer = StringProducer(3)
+ request = Request('POST', '/bar', _boringHeaders, producer)
+ writeDeferred = request.writeTo(self.transport)
+ producer.consumer.write('ab')
+ producer.finished.callback(None)
+ self.assertIdentical(self.transport.producer, None)
+ return self.assertFailure(writeDeferred, WrongBodyLength)
+
+
+ def _sendRequestBodyWithTooManyBytesTest(self, finisher):
+ """
+ Verify that when too many bytes have been written by a body producer
+ and then the body producer's C{startProducing} L{Deferred} fires that
+ the producer is unregistered from the transport and that the
+ L{Deferred} returned from L{Request.writeTo} is fired with a L{Failure}
+ wrapping a L{WrongBodyLength}.
+
+ @param finisher: A callable which will be invoked with the body
+ producer after too many bytes have been written to the transport.
+ It should fire the startProducing Deferred somehow.
+ """
+ producer = StringProducer(3)
+ request = Request('POST', '/bar', _boringHeaders, producer)
+ writeDeferred = request.writeTo(self.transport)
+
+ producer.consumer.write('ab')
+
+ # The producer hasn't misbehaved yet, so it shouldn't have been
+ # stopped.
+ self.assertFalse(producer.stopped)
+
+ producer.consumer.write('cd')
+
+ # Now the producer *has* misbehaved, so we should have tried to
+ # make it stop.
+ self.assertTrue(producer.stopped)
+
+ # The transport should have had the producer unregistered from it as
+ # well.
+ self.assertIdentical(self.transport.producer, None)
+
+ def cbFailed(exc):
+ # The "cd" should not have been written to the transport because
+ # the request can now locally be recognized to be invalid. If we
+ # had written the extra bytes, the server could have decided to
+ # start processing the request, which would be bad since we're
+ # going to indicate failure locally.
+ self.assertEqual(
+ self.transport.value(),
+ "POST /bar HTTP/1.1\r\n"
+ "Connection: close\r\n"
+ "Content-Length: 3\r\n"
+ "Host: example.com\r\n"
+ "\r\n"
+ "ab")
+ self.transport.clear()
+
+ # Subsequent writes should be ignored, as should firing the
+ # Deferred returned from startProducing.
+ self.assertRaises(ExcessWrite, producer.consumer.write, 'ef')
+
+ # Likewise, if the Deferred returned from startProducing fires,
+ # this should more or less be ignored (aside from possibly logging
+ # an error).
+ finisher(producer)
+
+ # There should have been nothing further written to the transport.
+ self.assertEqual(self.transport.value(), "")
+
+ d = self.assertFailure(writeDeferred, WrongBodyLength)
+ d.addCallback(cbFailed)
+ return d
+
+
+ def test_sendRequestBodyWithTooManyBytes(self):
+ """
+ If L{Request} is created with a C{bodyProducer} with a known length and
+ the producer tries to produce more than than many bytes, the
+ L{Deferred} returned by L{Request.writeTo} fires with a L{Failure}
+ wrapping a L{WrongBodyLength} exception.
+ """
+ def finisher(producer):
+ producer.finished.callback(None)
+ return self._sendRequestBodyWithTooManyBytesTest(finisher)
+
+
+ def test_sendRequestBodyErrorWithTooManyBytes(self):
+ """
+ If L{Request} is created with a C{bodyProducer} with a known length and
+ the producer tries to produce more than than many bytes, the
+ L{Deferred} returned by L{Request.writeTo} fires with a L{Failure}
+ wrapping a L{WrongBodyLength} exception.
+ """
+ def finisher(producer):
+ producer.finished.errback(ArbitraryException())
+ errors = self.flushLoggedErrors(ArbitraryException)
+ self.assertEqual(len(errors), 1)
+ return self._sendRequestBodyWithTooManyBytesTest(finisher)
+
+
+ def test_sendRequestBodyErrorWithConsumerError(self):
+ """
+ Though there should be no way for the internal C{finishedConsuming}
+ L{Deferred} in L{Request._writeToContentLength} to fire a L{Failure}
+ after the C{finishedProducing} L{Deferred} has fired, in case this does
+ happen, the error should be logged with a message about how there's
+ probably a bug in L{Request}.
+
+ This is a whitebox test.
+ """
+ producer = StringProducer(3)
+ request = Request('POST', '/bar', _boringHeaders, producer)
+ writeDeferred = request.writeTo(self.transport)
+
+ finishedConsuming = producer.consumer._finished
+
+ producer.consumer.write('abc')
+ producer.finished.callback(None)
+
+ finishedConsuming.errback(ArbitraryException())
+ self.assertEqual(len(self.flushLoggedErrors(ArbitraryException)), 1)
+
+
+ def _sendRequestBodyFinishedEarlyThenTooManyBytes(self, finisher):
+ """
+ Verify that if the body producer fires its Deferred and then keeps
+ writing to the consumer that the extra writes are ignored and the
+ L{Deferred} returned by L{Request.writeTo} fires with a L{Failure}
+ wrapping the most appropriate exception type.
+ """
+ producer = StringProducer(3)
+ request = Request('POST', '/bar', _boringHeaders, producer)
+ writeDeferred = request.writeTo(self.transport)
+
+ producer.consumer.write('ab')
+ finisher(producer)
+ self.assertIdentical(self.transport.producer, None)
+ self.transport.clear()
+ self.assertRaises(ExcessWrite, producer.consumer.write, 'cd')
+ self.assertEqual(self.transport.value(), "")
+ return writeDeferred
+
+
+ def test_sendRequestBodyFinishedEarlyThenTooManyBytes(self):
+ """
+ If the request body producer indicates it is done by firing the
+ L{Deferred} returned from its C{startProducing} method but then goes on
+ to write too many bytes, the L{Deferred} returned by {Request.writeTo}
+ fires with a L{Failure} wrapping L{WrongBodyLength}.
+ """
+ def finisher(producer):
+ producer.finished.callback(None)
+ return self.assertFailure(
+ self._sendRequestBodyFinishedEarlyThenTooManyBytes(finisher),
+ WrongBodyLength)
+
+
+ def test_sendRequestBodyErroredEarlyThenTooManyBytes(self):
+ """
+ If the request body producer indicates an error by firing the
+ L{Deferred} returned from its C{startProducing} method but then goes on
+ to write too many bytes, the L{Deferred} returned by {Request.writeTo}
+ fires with that L{Failure} and L{WrongBodyLength} is logged.
+ """
+ def finisher(producer):
+ producer.finished.errback(ArbitraryException())
+ return self.assertFailure(
+ self._sendRequestBodyFinishedEarlyThenTooManyBytes(finisher),
+ ArbitraryException)
+
+
+ def test_sendChunkedRequestBodyFinishedThenWriteMore(self, _with=None):
+ """
+ If the request body producer with an unknown length tries to write
+ after firing the L{Deferred} returned by its C{startProducing} method,
+ the C{write} call raises an exception and does not write anything to
+ the underlying transport.
+ """
+ producer = StringProducer(UNKNOWN_LENGTH)
+ request = Request('POST', '/bar', _boringHeaders, producer)
+ writeDeferred = request.writeTo(self.transport)
+ producer.finished.callback(_with)
+ self.transport.clear()
+
+ self.assertRaises(ExcessWrite, producer.consumer.write, 'foo')
+ self.assertEqual(self.transport.value(), "")
+ return writeDeferred
+
+
+ def test_sendChunkedRequestBodyFinishedWithErrorThenWriteMore(self):
+ """
+ If the request body producer with an unknown length tries to write
+ after firing the L{Deferred} returned by its C{startProducing} method
+ with a L{Failure}, the C{write} call raises an exception and does not
+ write anything to the underlying transport.
+ """
+ d = self.test_sendChunkedRequestBodyFinishedThenWriteMore(
+ Failure(ArbitraryException()))
+ return self.assertFailure(d, ArbitraryException)
+
+
+ def test_sendRequestBodyWithError(self):
+ """
+ If the L{Deferred} returned from the C{startProducing} method of the
+ L{IBodyProducer} passed to L{Request} fires with a L{Failure}, the
+ L{Deferred} returned from L{Request.writeTo} fails with that
+ L{Failure}.
+ """
+ producer = StringProducer(5)
+ request = Request('POST', '/bar', _boringHeaders, producer)
+ writeDeferred = request.writeTo(self.transport)
+
+ # Sanity check - the producer should be registered with the underlying
+ # transport.
+ self.assertIdentical(self.transport.producer, producer)
+ self.assertTrue(self.transport.streaming)
+
+ producer.consumer.write('ab')
+ self.assertEqual(
+ self.transport.value(),
+ "POST /bar HTTP/1.1\r\n"
+ "Connection: close\r\n"
+ "Content-Length: 5\r\n"
+ "Host: example.com\r\n"
+ "\r\n"
+ "ab")
+
+ self.assertFalse(self.transport.disconnecting)
+ producer.finished.errback(Failure(ArbitraryException()))
+
+ # Disconnection is handled by a higher level. Request should leave the
+ # transport alone in this case.
+ self.assertFalse(self.transport.disconnecting)
+
+ # Oh. Except it should unregister the producer that it registered.
+ self.assertIdentical(self.transport.producer, None)
+
+ return self.assertFailure(writeDeferred, ArbitraryException)
+
+ def test_hostHeaderRequired(self):
+ """
+ L{Request.writeTo} raises L{BadHeaders} if there is not exactly one
+ I{Host} header and writes nothing to the given transport.
+ """
+ request = Request('GET', '/', Headers({}), None)
+ self.assertRaises(BadHeaders, request.writeTo, self.transport)
+ self.assertEqual(self.transport.value(), '')
+
+ request = Request('GET', '/', Headers({'Host': ['example.com', 'example.org']}), None)
+ self.assertRaises(BadHeaders, request.writeTo, self.transport)
+ self.assertEqual(self.transport.value(), '')
+
+
+ def test_stopWriting(self):
+ """
+ L{Request.stopWriting} calls its body producer's C{stopProducing}
+ method.
+ """
+ producer = StringProducer(3)
+ request = Request('GET', '/', _boringHeaders, producer)
+ d = request.writeTo(self.transport)
+ self.assertFalse(producer.stopped)
+ request.stopWriting()
+ self.assertTrue(producer.stopped)
+
+
+ def test_brokenStopProducing(self):
+ """
+ If the body producer's C{stopProducing} method raises an exception,
+ L{Request.stopWriting} logs it and does not re-raise it.
+ """
+ producer = StringProducer(3)
+ def brokenStopProducing():
+ raise ArbitraryException("stopProducing is busted")
+ producer.stopProducing = brokenStopProducing
+
+ request = Request('GET', '/', _boringHeaders, producer)
+ d = request.writeTo(self.transport)
+ request.stopWriting()
+ self.assertEqual(
+ len(self.flushLoggedErrors(ArbitraryException)), 1)
+
+
+
+class LengthEnforcingConsumerTests(TestCase):
+ """
+ Tests for L{LengthEnforcingConsumer}.
+ """
+ def setUp(self):
+ self.result = Deferred()
+ self.producer = StringProducer(10)
+ self.transport = StringTransport()
+ self.enforcer = LengthEnforcingConsumer(
+ self.producer, self.transport, self.result)
+
+
+ def test_write(self):
+ """
+ L{LengthEnforcingConsumer.write} calls the wrapped consumer's C{write}
+ method with the bytes it is passed as long as there are fewer of them
+ than the C{length} attribute indicates remain to be received.
+ """
+ self.enforcer.write('abc')
+ self.assertEqual(self.transport.value(), 'abc')
+ self.transport.clear()
+ self.enforcer.write('def')
+ self.assertEqual(self.transport.value(), 'def')
+
+
+ def test_finishedEarly(self):
+ """
+ L{LengthEnforcingConsumer._noMoreWritesExpected} raises
+ L{WrongBodyLength} if it is called before the indicated number of bytes
+ have been written.
+ """
+ self.enforcer.write('x' * 9)
+ self.assertRaises(WrongBodyLength, self.enforcer._noMoreWritesExpected)
+
+
+ def test_writeTooMany(self, _unregisterAfter=False):
+ """
+ If it is called with a total number of bytes exceeding the indicated
+ limit passed to L{LengthEnforcingConsumer.__init__},
+ L{LengthEnforcingConsumer.write} fires the L{Deferred} with a
+ L{Failure} wrapping a L{WrongBodyLength} and also calls the
+ C{stopProducing} method of the producer.
+ """
+ self.enforcer.write('x' * 10)
+ self.assertFalse(self.producer.stopped)
+ self.enforcer.write('x')
+ self.assertTrue(self.producer.stopped)
+ if _unregisterAfter:
+ self.enforcer._noMoreWritesExpected()
+ return self.assertFailure(self.result, WrongBodyLength)
+
+
+ def test_writeAfterNoMoreExpected(self):
+ """
+ If L{LengthEnforcingConsumer.write} is called after
+ L{LengthEnforcingConsumer._noMoreWritesExpected}, it calls the
+ producer's C{stopProducing} method and raises L{ExcessWrite}.
+ """
+ self.enforcer.write('x' * 10)
+ self.enforcer._noMoreWritesExpected()
+ self.assertFalse(self.producer.stopped)
+ self.assertRaises(ExcessWrite, self.enforcer.write, 'x')
+ self.assertTrue(self.producer.stopped)
+
+
+ def test_finishedLate(self):
+ """
+ L{LengthEnforcingConsumer._noMoreWritesExpected} does nothing (in
+ particular, it does not raise any exception) if called after too many
+ bytes have been passed to C{write}.
+ """
+ return self.test_writeTooMany(True)
+
+
+ def test_finished(self):
+ """
+ If L{LengthEnforcingConsumer._noMoreWritesExpected} is called after
+ the correct number of bytes have been written it returns C{None}.
+ """
+ self.enforcer.write('x' * 10)
+ self.assertIdentical(self.enforcer._noMoreWritesExpected(), None)
+
+
+ def test_stopProducingRaises(self):
+ """
+ If L{LengthEnforcingConsumer.write} calls the producer's
+ C{stopProducing} because too many bytes were written and the
+ C{stopProducing} method raises an exception, the exception is logged
+ and the L{LengthEnforcingConsumer} still errbacks the finished
+ L{Deferred}.
+ """
+ def brokenStopProducing():
+ StringProducer.stopProducing(self.producer)
+ raise ArbitraryException("stopProducing is busted")
+ self.producer.stopProducing = brokenStopProducing
+
+ def cbFinished(ignored):
+ self.assertEqual(
+ len(self.flushLoggedErrors(ArbitraryException)), 1)
+ d = self.test_writeTooMany()
+ d.addCallback(cbFinished)
+ return d
+
+
+
+class RequestBodyConsumerTests(TestCase):
+ """
+ Tests for L{ChunkedEncoder} which sits between an L{ITransport} and a
+ request/response body producer and chunked encodes everything written to
+ it.
+ """
+ def test_interface(self):
+ """
+ L{ChunkedEncoder} instances provide L{IConsumer}.
+ """
+ self.assertTrue(
+ verifyObject(IConsumer, ChunkedEncoder(StringTransport())))
+
+
+ def test_write(self):
+ """
+ L{ChunkedEncoder.write} writes to the transport the chunked encoded
+ form of the bytes passed to it.
+ """
+ transport = StringTransport()
+ encoder = ChunkedEncoder(transport)
+ encoder.write('foo')
+ self.assertEqual(transport.value(), '3\r\nfoo\r\n')
+ transport.clear()
+ encoder.write('x' * 16)
+ self.assertEqual(transport.value(), '10\r\n' + 'x' * 16 + '\r\n')
+
+
+ def test_producerRegistration(self):
+ """
+ L{ChunkedEncoder.registerProducer} registers the given streaming
+ producer with its transport and L{ChunkedEncoder.unregisterProducer}
+ writes a zero-length chunk to its transport and unregisters the
+ transport's producer.
+ """
+ transport = StringTransport()
+ producer = object()
+ encoder = ChunkedEncoder(transport)
+ encoder.registerProducer(producer, True)
+ self.assertIdentical(transport.producer, producer)
+ self.assertTrue(transport.streaming)
+ encoder.unregisterProducer()
+ self.assertIdentical(transport.producer, None)
+ self.assertEqual(transport.value(), '0\r\n\r\n')
+
+
+
+class TransportProxyProducerTests(TestCase):
+ """
+ Tests for L{TransportProxyProducer} which proxies the L{IPushProducer}
+ interface of a transport.
+ """
+ def test_interface(self):
+ """
+ L{TransportProxyProducer} instances provide L{IPushProducer}.
+ """
+ self.assertTrue(
+ verifyObject(IPushProducer, TransportProxyProducer(None)))
+
+
+ def test_stopProxyingUnreferencesProducer(self):
+ """
+ L{TransportProxyProducer._stopProxying} drops the reference to the
+ wrapped L{IPushProducer} provider.
+ """
+ transport = StringTransport()
+ proxy = TransportProxyProducer(transport)
+ self.assertIdentical(proxy._producer, transport)
+ proxy._stopProxying()
+ self.assertIdentical(proxy._producer, None)
+
+
+ def test_resumeProducing(self):
+ """
+ L{TransportProxyProducer.resumeProducing} calls the wrapped
+ transport's C{resumeProducing} method unless told to stop proxying.
+ """
+ transport = StringTransport()
+ transport.pauseProducing()
+
+ proxy = TransportProxyProducer(transport)
+ # The transport should still be paused.
+ self.assertEqual(transport.producerState, 'paused')
+ proxy.resumeProducing()
+ # The transport should now be resumed.
+ self.assertEqual(transport.producerState, 'producing')
+
+ transport.pauseProducing()
+ proxy._stopProxying()
+
+ # The proxy should no longer do anything to the transport.
+ proxy.resumeProducing()
+ self.assertEqual(transport.producerState, 'paused')
+
+
+ def test_pauseProducing(self):
+ """
+ L{TransportProxyProducer.pauseProducing} calls the wrapped transport's
+ C{pauseProducing} method unless told to stop proxying.
+ """
+ transport = StringTransport()
+
+ proxy = TransportProxyProducer(transport)
+ # The transport should still be producing.
+ self.assertEqual(transport.producerState, 'producing')
+ proxy.pauseProducing()
+ # The transport should now be paused.
+ self.assertEqual(transport.producerState, 'paused')
+
+ transport.resumeProducing()
+ proxy._stopProxying()
+
+ # The proxy should no longer do anything to the transport.
+ proxy.pauseProducing()
+ self.assertEqual(transport.producerState, 'producing')
+
+
+ def test_stopProducing(self):
+ """
+ L{TransportProxyProducer.stopProducing} calls the wrapped transport's
+ C{stopProducing} method unless told to stop proxying.
+ """
+ transport = StringTransport()
+ proxy = TransportProxyProducer(transport)
+ # The transport should still be producing.
+ self.assertEqual(transport.producerState, 'producing')
+ proxy.stopProducing()
+ # The transport should now be stopped.
+ self.assertEqual(transport.producerState, 'stopped')
+
+ transport = StringTransport()
+ proxy = TransportProxyProducer(transport)
+ proxy._stopProxying()
+ proxy.stopProducing()
+ # The transport should not have been stopped.
+ self.assertEqual(transport.producerState, 'producing')
+
+
+
+class ResponseTests(TestCase):
+ """
+ Tests for L{Response}.
+ """
+ def test_makeConnection(self):
+ """
+ The L{IProtocol} provider passed to L{Response.deliverBody} has its
+ C{makeConnection} method called with an L{IPushProducer} provider
+ hooked up to the response as an argument.
+ """
+ producers = []
+ transport = StringTransport()
+ class SomeProtocol(Protocol):
+ def makeConnection(self, producer):
+ producers.append(producer)
+
+ consumer = SomeProtocol()
+ response = justTransportResponse(transport)
+ response.deliverBody(consumer)
+ [theProducer] = producers
+ theProducer.pauseProducing()
+ self.assertEqual(transport.producerState, 'paused')
+ theProducer.resumeProducing()
+ self.assertEqual(transport.producerState, 'producing')
+
+
+ def test_dataReceived(self):
+ """
+ The L{IProtocol} provider passed to L{Response.deliverBody} has its
+ C{dataReceived} method called with bytes received as part of the
+ response body.
+ """
+ bytes = []
+ class ListConsumer(Protocol):
+ def dataReceived(self, data):
+ bytes.append(data)
+
+
+ consumer = ListConsumer()
+ response = justTransportResponse(StringTransport())
+ response.deliverBody(consumer)
+
+ response._bodyDataReceived('foo')
+ self.assertEqual(bytes, ['foo'])
+
+
+ def test_connectionLost(self):
+ """
+ The L{IProtocol} provider passed to L{Response.deliverBody} has its
+ C{connectionLost} method called with a L{Failure} wrapping
+ L{ResponseDone} when the response's C{_bodyDataFinished} method is
+ called.
+ """
+ lost = []
+ class ListConsumer(Protocol):
+ def connectionLost(self, reason):
+ lost.append(reason)
+
+ consumer = ListConsumer()
+ response = justTransportResponse(StringTransport())
+ response.deliverBody(consumer)
+
+ response._bodyDataFinished()
+ lost[0].trap(ResponseDone)
+ self.assertEqual(len(lost), 1)
+
+ # The protocol reference should be dropped, too, to facilitate GC or
+ # whatever.
+ self.assertIdentical(response._bodyProtocol, None)
+
+
+ def test_bufferEarlyData(self):
+ """
+ If data is delivered to the L{Response} before a protocol is registered
+ with C{deliverBody}, that data is buffered until the protocol is
+ registered and then is delivered.
+ """
+ bytes = []
+ class ListConsumer(Protocol):
+ def dataReceived(self, data):
+ bytes.append(data)
+
+ protocol = ListConsumer()
+ response = justTransportResponse(StringTransport())
+ response._bodyDataReceived('foo')
+ response._bodyDataReceived('bar')
+ response.deliverBody(protocol)
+ response._bodyDataReceived('baz')
+ self.assertEqual(bytes, ['foo', 'bar', 'baz'])
+ # Make sure the implementation-detail-byte-buffer is cleared because
+ # not clearing it wastes memory.
+ self.assertIdentical(response._bodyBuffer, None)
+
+
+ def test_multipleStartProducingFails(self):
+ """
+ L{Response.deliverBody} raises L{RuntimeError} if called more than
+ once.
+ """
+ response = justTransportResponse(StringTransport())
+ response.deliverBody(Protocol())
+ self.assertRaises(RuntimeError, response.deliverBody, Protocol())
+
+
+ def test_startProducingAfterFinishedFails(self):
+ """
+ L{Response.deliverBody} raises L{RuntimeError} if called after
+ L{Response._bodyDataFinished}.
+ """
+ response = justTransportResponse(StringTransport())
+ response.deliverBody(Protocol())
+ response._bodyDataFinished()
+ self.assertRaises(RuntimeError, response.deliverBody, Protocol())
+
+
+ def test_bodyDataReceivedAfterFinishedFails(self):
+ """
+ L{Response._bodyDataReceived} raises L{RuntimeError} if called after
+ L{Response._bodyDataFinished} but before L{Response.deliverBody}.
+ """
+ response = justTransportResponse(StringTransport())
+ response._bodyDataFinished()
+ self.assertRaises(RuntimeError, response._bodyDataReceived, 'foo')
+
+
+ def test_bodyDataReceivedAfterDeliveryFails(self):
+ """
+ L{Response._bodyDataReceived} raises L{RuntimeError} if called after
+ L{Response._bodyDataFinished} and after L{Response.deliverBody}.
+ """
+ response = justTransportResponse(StringTransport())
+ response._bodyDataFinished()
+ response.deliverBody(Protocol())
+ self.assertRaises(RuntimeError, response._bodyDataReceived, 'foo')
+
+
+ def test_bodyDataFinishedAfterFinishedFails(self):
+ """
+ L{Response._bodyDataFinished} raises L{RuntimeError} if called more
+ than once.
+ """
+ response = justTransportResponse(StringTransport())
+ response._bodyDataFinished()
+ self.assertRaises(RuntimeError, response._bodyDataFinished)
+
+
+ def test_bodyDataFinishedAfterDeliveryFails(self):
+ """
+ L{Response._bodyDataFinished} raises L{RuntimeError} if called after
+ the body has been delivered.
+ """
+ response = justTransportResponse(StringTransport())
+ response._bodyDataFinished()
+ response.deliverBody(Protocol())
+ self.assertRaises(RuntimeError, response._bodyDataFinished)
+
+
+ def test_transportResumed(self):
+ """
+ L{Response.deliverBody} resumes the HTTP connection's transport
+ before passing it to the transport's C{makeConnection} method.
+ """
+ transportState = []
+ class ListConsumer(Protocol):
+ def makeConnection(self, transport):
+ transportState.append(transport.producerState)
+
+ transport = StringTransport()
+ transport.pauseProducing()
+ protocol = ListConsumer()
+ response = justTransportResponse(transport)
+ self.assertEqual(transport.producerState, 'paused')
+ response.deliverBody(protocol)
+ self.assertEqual(transportState, ['producing'])
+
+
+ def test_bodyDataFinishedBeforeStartProducing(self):
+ """
+ If the entire body is delivered to the L{Response} before the
+ response's C{deliverBody} method is called, the protocol passed to
+ C{deliverBody} is immediately given the body data and then
+ disconnected.
+ """
+ transport = StringTransport()
+ response = justTransportResponse(transport)
+ response._bodyDataReceived('foo')
+ response._bodyDataReceived('bar')
+ response._bodyDataFinished()
+
+ protocol = AccumulatingProtocol()
+ response.deliverBody(protocol)
+ self.assertEqual(protocol.data, 'foobar')
+ protocol.closedReason.trap(ResponseDone)
+
+
+ def test_finishedWithErrorWhenConnected(self):
+ """
+ The L{Failure} passed to L{Response._bodyDataFinished} when the response
+ is in the I{connected} state is passed to the C{connectionLost} method
+ of the L{IProtocol} provider passed to the L{Response}'s
+ C{deliverBody} method.
+ """
+ transport = StringTransport()
+ response = justTransportResponse(transport)
+
+ protocol = AccumulatingProtocol()
+ response.deliverBody(protocol)
+
+ # Sanity check - this test is for the connected state
+ self.assertEqual(response._state, 'CONNECTED')
+ response._bodyDataFinished(Failure(ArbitraryException()))
+
+ protocol.closedReason.trap(ArbitraryException)
+
+
+ def test_finishedWithErrorWhenInitial(self):
+ """
+ The L{Failure} passed to L{Response._bodyDataFinished} when the response
+ is in the I{initial} state is passed to the C{connectionLost} method of
+ the L{IProtocol} provider passed to the L{Response}'s C{deliverBody}
+ method.
+ """
+ transport = StringTransport()
+ response = justTransportResponse(transport)
+
+ # Sanity check - this test is for the initial state
+ self.assertEqual(response._state, 'INITIAL')
+ response._bodyDataFinished(Failure(ArbitraryException()))
+
+ protocol = AccumulatingProtocol()
+ response.deliverBody(protocol)
+
+ protocol.closedReason.trap(ArbitraryException)
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_proxy.py b/vendor/Twisted-10.0.0/twisted/web/test/test_proxy.py
new file mode 100644
index 0000000000..7cad1a354e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_proxy.py
@@ -0,0 +1,541 @@
+# Copyright (c) 2007-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test for L{twisted.web.proxy}.
+"""
+
+from twisted.trial.unittest import TestCase
+from twisted.test.proto_helpers import StringTransportWithDisconnection
+from twisted.test.proto_helpers import MemoryReactor
+
+from twisted.web.resource import Resource
+from twisted.web.server import Site
+from twisted.web.proxy import ReverseProxyResource, ProxyClientFactory
+from twisted.web.proxy import ProxyClient, ProxyRequest, ReverseProxyRequest
+from twisted.web.test.test_web import DummyRequest
+
+
+class ReverseProxyResourceTestCase(TestCase):
+ """
+ Tests for L{ReverseProxyResource}.
+ """
+
+ def _testRender(self, uri, expectedURI):
+ """
+ Check that a request pointing at C{uri} produce a new proxy connection,
+ with the path of this request pointing at C{expectedURI}.
+ """
+ root = Resource()
+ reactor = MemoryReactor()
+ resource = ReverseProxyResource("127.0.0.1", 1234, "/path", reactor)
+ root.putChild('index', resource)
+ site = Site(root)
+
+ transport = StringTransportWithDisconnection()
+ channel = site.buildProtocol(None)
+ channel.makeConnection(transport)
+ # Clear the timeout if the tests failed
+ self.addCleanup(channel.connectionLost, None)
+
+ channel.dataReceived("GET %s HTTP/1.1\r\nAccept: text/html\r\n\r\n" %
+ (uri,))
+
+ # Check that one connection has been created, to the good host/port
+ self.assertEquals(len(reactor.tcpClients), 1)
+ self.assertEquals(reactor.tcpClients[0][0], "127.0.0.1")
+ self.assertEquals(reactor.tcpClients[0][1], 1234)
+
+ # Check the factory passed to the connect, and its given path
+ factory = reactor.tcpClients[0][2]
+ self.assertIsInstance(factory, ProxyClientFactory)
+ self.assertEquals(factory.rest, expectedURI)
+ self.assertEquals(factory.headers["host"], "127.0.0.1:1234")
+
+
+ def test_render(self):
+ """
+ Test that L{ReverseProxyResource.render} initiates a connection to the
+ given server with a L{ProxyClientFactory} as parameter.
+ """
+ return self._testRender("/index", "/path")
+
+
+ def test_renderWithQuery(self):
+ """
+ Test that L{ReverseProxyResource.render} passes query parameters to the
+ created factory.
+ """
+ return self._testRender("/index?foo=bar", "/path?foo=bar")
+
+
+ def test_getChild(self):
+ """
+ The L{ReverseProxyResource.getChild} method should return a resource
+ instance with the same class as the originating resource, forward port
+ and host values, and update the path value with the value passed.
+ """
+ resource = ReverseProxyResource("127.0.0.1", 1234, "/path")
+ child = resource.getChild('foo', None)
+ # The child should keep the same class
+ self.assertIsInstance(child, ReverseProxyResource)
+ self.assertEquals(child.path, "/path/foo")
+ self.assertEquals(child.port, 1234)
+ self.assertEquals(child.host, "127.0.0.1")
+
+
+ def test_getChildWithSpecial(self):
+ """
+ The L{ReverseProxyResource} return by C{getChild} has a path which has
+ already been quoted.
+ """
+ resource = ReverseProxyResource("127.0.0.1", 1234, "/path")
+ child = resource.getChild(' /%', None)
+ self.assertEqual(child.path, "/path/%20%2F%25")
+
+
+
+class DummyChannel(object):
+ """
+ A dummy HTTP channel, that does nothing but holds a transport and saves
+ connection lost.
+
+ @ivar transport: the transport used by the client.
+ @ivar lostReason: the reason saved at connection lost.
+ """
+
+ def __init__(self, transport):
+ """
+ Hold a reference to the transport.
+ """
+ self.transport = transport
+ self.lostReason = None
+
+
+ def connectionLost(self, reason):
+ """
+ Keep track of the connection lost reason.
+ """
+ self.lostReason = reason
+
+
+
+class ProxyClientTestCase(TestCase):
+ """
+ Tests for L{ProxyClient}.
+ """
+
+ def _parseOutHeaders(self, content):
+ """
+ Parse the headers out of some web content.
+
+ @param content: Bytes received from a web server.
+ @return: A tuple of (requestLine, headers, body). C{headers} is a dict
+ of headers, C{requestLine} is the first line (e.g. "POST /foo ...")
+ and C{body} is whatever is left.
+ """
+ headers, body = content.split('\r\n\r\n')
+ headers = headers.split('\r\n')
+ requestLine = headers.pop(0)
+ return (
+ requestLine, dict(header.split(': ') for header in headers), body)
+
+
+ def makeRequest(self, path):
+ """
+ Make a dummy request object for the URL path.
+
+ @param path: A URL path, beginning with a slash.
+ @return: A L{DummyRequest}.
+ """
+ return DummyRequest(path)
+
+
+ def makeProxyClient(self, request, method="GET", headers=None,
+ requestBody=""):
+ """
+ Make a L{ProxyClient} object used for testing.
+
+ @param request: The request to use.
+ @param method: The HTTP method to use, GET by default.
+ @param headers: The HTTP headers to use expressed as a dict. If not
+ provided, defaults to {'accept': 'text/html'}.
+ @param requestBody: The body of the request. Defaults to the empty
+ string.
+ @return: A L{ProxyClient}
+ """
+ if headers is None:
+ headers = {"accept": "text/html"}
+ path = '/' + request.postpath
+ return ProxyClient(
+ method, path, 'HTTP/1.0', headers, requestBody, request)
+
+
+ def connectProxy(self, proxyClient):
+ """
+ Connect a proxy client to a L{StringTransportWithDisconnection}.
+
+ @param proxyClient: A L{ProxyClient}.
+ @return: The L{StringTransportWithDisconnection}.
+ """
+ clientTransport = StringTransportWithDisconnection()
+ clientTransport.protocol = proxyClient
+ proxyClient.makeConnection(clientTransport)
+ return clientTransport
+
+
+ def assertForwardsHeaders(self, proxyClient, requestLine, headers):
+ """
+ Assert that C{proxyClient} sends C{headers} when it connects.
+
+ @param proxyClient: A L{ProxyClient}.
+ @param requestLine: The request line we expect to be sent.
+ @param headers: A dict of headers we expect to be sent.
+ @return: If the assertion is successful, return the request body as
+ bytes.
+ """
+ self.connectProxy(proxyClient)
+ requestContent = proxyClient.transport.value()
+ receivedLine, receivedHeaders, body = self._parseOutHeaders(
+ requestContent)
+ self.assertEquals(receivedLine, requestLine)
+ self.assertEquals(receivedHeaders, headers)
+ return body
+
+
+ def makeResponseBytes(self, code, message, headers, body):
+ lines = ["HTTP/1.0 %d %s" % (code, message)]
+ for header, values in headers:
+ for value in values:
+ lines.append("%s: %s" % (header, value))
+ lines.extend(['', body])
+ return '\r\n'.join(lines)
+
+
+ def assertForwardsResponse(self, request, code, message, headers, body):
+ """
+ Assert that C{request} has forwarded a response from the server.
+
+ @param request: A L{DummyRequest}.
+ @param code: The expected HTTP response code.
+ @param message: The expected HTTP message.
+ @param headers: The expected HTTP headers.
+ @param body: The expected response body.
+ """
+ self.assertEquals(request.responseCode, code)
+ self.assertEquals(request.responseMessage, message)
+ receivedHeaders = list(request.responseHeaders.getAllRawHeaders())
+ receivedHeaders.sort()
+ expectedHeaders = headers[:]
+ expectedHeaders.sort()
+ self.assertEquals(receivedHeaders, expectedHeaders)
+ self.assertEquals(''.join(request.written), body)
+
+
+ def _testDataForward(self, code, message, headers, body, method="GET",
+ requestBody="", loseConnection=True):
+ """
+ Build a fake proxy connection, and send C{data} over it, checking that
+ it's forwarded to the originating request.
+ """
+ request = self.makeRequest('foo')
+ client = self.makeProxyClient(
+ request, method, {'accept': 'text/html'}, requestBody)
+
+ receivedBody = self.assertForwardsHeaders(
+ client, '%s /foo HTTP/1.0' % (method,),
+ {'connection': 'close', 'accept': 'text/html'})
+
+ self.assertEquals(receivedBody, requestBody)
+
+ # Fake an answer
+ client.dataReceived(
+ self.makeResponseBytes(code, message, headers, body))
+
+ # Check that the response data has been forwarded back to the original
+ # requester.
+ self.assertForwardsResponse(request, code, message, headers, body)
+
+ # Check that when the response is done, the request is finished.
+ if loseConnection:
+ client.transport.loseConnection()
+
+ # Even if we didn't call loseConnection, the transport should be
+ # disconnected. This lets us not rely on the server to close our
+ # sockets for us.
+ self.assertFalse(client.transport.connected)
+ self.assertEquals(request.finished, 1)
+
+
+ def test_forward(self):
+ """
+ When connected to the server, L{ProxyClient} should send the saved
+ request, with modifications of the headers, and then forward the result
+ to the parent request.
+ """
+ return self._testDataForward(
+ 200, "OK", [("Foo", ["bar", "baz"])], "Some data\r\n")
+
+
+ def test_postData(self):
+ """
+ Try to post content in the request, and check that the proxy client
+ forward the body of the request.
+ """
+ return self._testDataForward(
+ 200, "OK", [("Foo", ["bar"])], "Some data\r\n", "POST", "Some content")
+
+
+ def test_statusWithMessage(self):
+ """
+ If the response contains a status with a message, it should be
+ forwarded to the parent request with all the information.
+ """
+ return self._testDataForward(
+ 404, "Not Found", [], "")
+
+
+ def test_contentLength(self):
+ """
+ If the response contains a I{Content-Length} header, the inbound
+ request object should still only have C{finish} called on it once.
+ """
+ data = "foo bar baz"
+ return self._testDataForward(
+ 200, "OK", [("Content-Length", [str(len(data))])], data)
+
+
+ def test_losesConnection(self):
+ """
+ If the response contains a I{Content-Length} header, the outgoing
+ connection is closed when all response body data has been received.
+ """
+ data = "foo bar baz"
+ return self._testDataForward(
+ 200, "OK", [("Content-Length", [str(len(data))])], data,
+ loseConnection=False)
+
+
+ def test_headersCleanups(self):
+ """
+ The headers given at initialization should be modified:
+ B{proxy-connection} should be removed if present, and B{connection}
+ should be added.
+ """
+ client = ProxyClient('GET', '/foo', 'HTTP/1.0',
+ {"accept": "text/html", "proxy-connection": "foo"}, '', None)
+ self.assertEquals(client.headers,
+ {"accept": "text/html", "connection": "close"})
+
+
+ def test_keepaliveNotForwarded(self):
+ """
+ The proxy doesn't really know what to do with keepalive things from
+ the remote server, so we stomp over any keepalive header we get from
+ the client.
+ """
+ headers = {
+ "accept": "text/html",
+ 'keep-alive': '300',
+ 'connection': 'keep-alive',
+ }
+ expectedHeaders = headers.copy()
+ expectedHeaders['connection'] = 'close'
+ del expectedHeaders['keep-alive']
+ client = ProxyClient('GET', '/foo', 'HTTP/1.0', headers, '', None)
+ self.assertForwardsHeaders(
+ client, 'GET /foo HTTP/1.0', expectedHeaders)
+
+
+ def test_defaultHeadersOverridden(self):
+ """
+ L{server.Request} within the proxy sets certain response headers by
+ default. When we get these headers back from the remote server, the
+ defaults are overridden rather than simply appended.
+ """
+ request = self.makeRequest('foo')
+ request.responseHeaders.setRawHeaders('server', ['old-bar'])
+ request.responseHeaders.setRawHeaders('date', ['old-baz'])
+ request.responseHeaders.setRawHeaders('content-type', ["old/qux"])
+ client = self.makeProxyClient(request, headers={'accept': 'text/html'})
+ self.connectProxy(client)
+ headers = {
+ 'Server': ['bar'],
+ 'Date': ['2010-01-01'],
+ 'Content-Type': ['application/x-baz'],
+ }
+ client.dataReceived(
+ self.makeResponseBytes(200, "OK", headers.items(), ''))
+ self.assertForwardsResponse(
+ request, 200, 'OK', headers.items(), '')
+
+
+
+class ProxyClientFactoryTestCase(TestCase):
+ """
+ Tests for L{ProxyClientFactory}.
+ """
+
+ def test_connectionFailed(self):
+ """
+ Check that L{ProxyClientFactory.clientConnectionFailed} produces
+ a B{501} response to the parent request.
+ """
+ request = DummyRequest(['foo'])
+ factory = ProxyClientFactory('GET', '/foo', 'HTTP/1.0',
+ {"accept": "text/html"}, '', request)
+
+ factory.clientConnectionFailed(None, None)
+ self.assertEquals(request.responseCode, 501)
+ self.assertEquals(request.responseMessage, "Gateway error")
+ self.assertEquals(
+ list(request.responseHeaders.getAllRawHeaders()),
+ [("Content-Type", ["text/html"])])
+ self.assertEquals(
+ ''.join(request.written),
+ "<H1>Could not connect</H1>")
+ self.assertEquals(request.finished, 1)
+
+
+ def test_buildProtocol(self):
+ """
+ L{ProxyClientFactory.buildProtocol} should produce a L{ProxyClient}
+ with the same values of attributes (with updates on the headers).
+ """
+ factory = ProxyClientFactory('GET', '/foo', 'HTTP/1.0',
+ {"accept": "text/html"}, 'Some data',
+ None)
+ proto = factory.buildProtocol(None)
+ self.assertIsInstance(proto, ProxyClient)
+ self.assertEquals(proto.command, 'GET')
+ self.assertEquals(proto.rest, '/foo')
+ self.assertEquals(proto.data, 'Some data')
+ self.assertEquals(proto.headers,
+ {"accept": "text/html", "connection": "close"})
+
+
+
+class ProxyRequestTestCase(TestCase):
+ """
+ Tests for L{ProxyRequest}.
+ """
+
+ def _testProcess(self, uri, expectedURI, method="GET", data=""):
+ """
+ Build a request pointing at C{uri}, and check that a proxied request
+ is created, pointing a C{expectedURI}.
+ """
+ transport = StringTransportWithDisconnection()
+ channel = DummyChannel(transport)
+ reactor = MemoryReactor()
+ request = ProxyRequest(channel, False, reactor)
+ request.gotLength(len(data))
+ request.handleContentChunk(data)
+ request.requestReceived(method, 'http://example.com%s' % (uri,),
+ 'HTTP/1.0')
+
+ self.assertEquals(len(reactor.tcpClients), 1)
+ self.assertEquals(reactor.tcpClients[0][0], "example.com")
+ self.assertEquals(reactor.tcpClients[0][1], 80)
+
+ factory = reactor.tcpClients[0][2]
+ self.assertIsInstance(factory, ProxyClientFactory)
+ self.assertEquals(factory.command, method)
+ self.assertEquals(factory.version, 'HTTP/1.0')
+ self.assertEquals(factory.headers, {'host': 'example.com'})
+ self.assertEquals(factory.data, data)
+ self.assertEquals(factory.rest, expectedURI)
+ self.assertEquals(factory.father, request)
+
+
+ def test_process(self):
+ """
+ L{ProxyRequest.process} should create a connection to the given server,
+ with a L{ProxyClientFactory} as connection factory, with the correct
+ parameters:
+ - forward comment, version and data values
+ - update headers with the B{host} value
+ - remove the host from the URL
+ - pass the request as parent request
+ """
+ return self._testProcess("/foo/bar", "/foo/bar")
+
+
+ def test_processWithoutTrailingSlash(self):
+ """
+ If the incoming request doesn't contain a slash,
+ L{ProxyRequest.process} should add one when instantiating
+ L{ProxyClientFactory}.
+ """
+ return self._testProcess("", "/")
+
+
+ def test_processWithData(self):
+ """
+ L{ProxyRequest.process} should be able to retrieve request body and
+ to forward it.
+ """
+ return self._testProcess(
+ "/foo/bar", "/foo/bar", "POST", "Some content")
+
+
+ def test_processWithPort(self):
+ """
+ Check that L{ProxyRequest.process} correctly parse port in the incoming
+ URL, and create a outgoing connection with this port.
+ """
+ transport = StringTransportWithDisconnection()
+ channel = DummyChannel(transport)
+ reactor = MemoryReactor()
+ request = ProxyRequest(channel, False, reactor)
+ request.gotLength(0)
+ request.requestReceived('GET', 'http://example.com:1234/foo/bar',
+ 'HTTP/1.0')
+
+ # That should create one connection, with the port parsed from the URL
+ self.assertEquals(len(reactor.tcpClients), 1)
+ self.assertEquals(reactor.tcpClients[0][0], "example.com")
+ self.assertEquals(reactor.tcpClients[0][1], 1234)
+
+
+
+class DummyFactory(object):
+ """
+ A simple holder for C{host} and C{port} information.
+ """
+
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+
+
+
+class ReverseProxyRequestTestCase(TestCase):
+ """
+ Tests for L{ReverseProxyRequest}.
+ """
+
+ def test_process(self):
+ """
+ L{ReverseProxyRequest.process} should create a connection to its
+ factory host/port, using a L{ProxyClientFactory} instantiated with the
+ correct parameters, and particulary set the B{host} header to the
+ factory host.
+ """
+ transport = StringTransportWithDisconnection()
+ channel = DummyChannel(transport)
+ reactor = MemoryReactor()
+ request = ReverseProxyRequest(channel, False, reactor)
+ request.factory = DummyFactory("example.com", 1234)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
+
+ # Check that one connection has been created, to the good host/port
+ self.assertEquals(len(reactor.tcpClients), 1)
+ self.assertEquals(reactor.tcpClients[0][0], "example.com")
+ self.assertEquals(reactor.tcpClients[0][1], 1234)
+
+ # Check the factory passed to the connect, and its headers
+ factory = reactor.tcpClients[0][2]
+ self.assertIsInstance(factory, ProxyClientFactory)
+ self.assertEquals(factory.headers, {'host': 'example.com'})
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_resource.py b/vendor/Twisted-10.0.0/twisted/web/test/test_resource.py
new file mode 100644
index 0000000000..d6631c2e41
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_resource.py
@@ -0,0 +1,144 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web.resource}.
+"""
+
+from twisted.trial.unittest import TestCase
+from twisted.web import error
+from twisted.web.http import NOT_FOUND, FORBIDDEN
+from twisted.web.resource import ErrorPage, NoResource, ForbiddenResource
+from twisted.web.test.test_web import DummyRequest
+
+
+class ErrorPageTests(TestCase):
+ """
+ Tests for L{ErrorPage}, L{NoResource}, and L{ForbiddenResource}.
+ """
+
+ errorPage = ErrorPage
+ noResource = NoResource
+ forbiddenResource = ForbiddenResource
+
+ def test_getChild(self):
+ """
+ The C{getChild} method of L{ErrorPage} returns the L{ErrorPage} it is
+ called on.
+ """
+ page = self.errorPage(321, "foo", "bar")
+ self.assertIdentical(page.getChild("name", object()), page)
+
+
+ def _pageRenderingTest(self, page, code, brief, detail):
+ request = DummyRequest([''])
+ self.assertEqual(
+ page.render(request),
+ "\n"
+ "<html>\n"
+ " <head><title>%s - %s</title></head>\n"
+ " <body>\n"
+ " <h1>%s</h1>\n"
+ " <p>%s</p>\n"
+ " </body>\n"
+ "</html>\n" % (code, brief, brief, detail))
+ self.assertEqual(request.responseCode, code)
+ self.assertEqual(
+ request.outgoingHeaders, {'content-type': 'text/html'})
+
+
+ def test_errorPageRendering(self):
+ """
+ L{ErrorPage.render} returns a C{str} describing the error defined by
+ the response code and message passed to L{ErrorPage.__init__}. It also
+ uses that response code to set the response code on the L{Request}
+ passed in.
+ """
+ code = 321
+ brief = "brief description text"
+ detail = "much longer text might go here"
+ page = self.errorPage(code, brief, detail)
+ self._pageRenderingTest(page, code, brief, detail)
+
+
+ def test_noResourceRendering(self):
+ """
+ L{NoResource} sets the HTTP I{NOT FOUND} code.
+ """
+ detail = "long message"
+ page = self.noResource(detail)
+ self._pageRenderingTest(page, NOT_FOUND, "No Such Resource", detail)
+
+
+ def test_forbiddenResourceRendering(self):
+ """
+ L{ForbiddenResource} sets the HTTP I{FORBIDDEN} code.
+ """
+ detail = "longer message"
+ page = self.forbiddenResource(detail)
+ self._pageRenderingTest(page, FORBIDDEN, "Forbidden Resource", detail)
+
+
+
+class DeprecatedErrorPageTests(ErrorPageTests):
+ """
+ Tests for L{error.ErrorPage}, L{error.NoResource}, and
+ L{error.ForbiddenResource}.
+ """
+ def errorPage(self, *args):
+ return error.ErrorPage(*args)
+
+
+ def noResource(self, *args):
+ return error.NoResource(*args)
+
+
+ def forbiddenResource(self, *args):
+ return error.ForbiddenResource(*args)
+
+
+ def _assertWarning(self, name, offendingFunction):
+ warnings = self.flushWarnings([offendingFunction])
+ self.assertEqual(len(warnings), 1)
+ self.assertEqual(warnings[0]['category'], DeprecationWarning)
+ self.assertEqual(
+ warnings[0]['message'],
+ 'twisted.web.error.%s is deprecated since Twisted 9.0. '
+ 'See twisted.web.resource.%s.' % (name, name))
+
+
+ def test_getChild(self):
+ """
+ Like L{ErrorPageTests.test_getChild}, but flush the deprecation warning
+ emitted by instantiating L{error.ErrorPage}.
+ """
+ ErrorPageTests.test_getChild(self)
+ self._assertWarning('ErrorPage', self.errorPage)
+
+
+ def test_errorPageRendering(self):
+ """
+ Like L{ErrorPageTests.test_errorPageRendering}, but flush the
+ deprecation warning emitted by instantiating L{error.ErrorPage}.
+ """
+ ErrorPageTests.test_errorPageRendering(self)
+ self._assertWarning('ErrorPage', self.errorPage)
+
+
+ def test_noResourceRendering(self):
+ """
+ Like L{ErrorPageTests.test_noResourceRendering}, but flush the
+ deprecation warning emitted by instantiating L{error.NoResource}.
+ """
+ ErrorPageTests.test_noResourceRendering(self)
+ self._assertWarning('NoResource', self.noResource)
+
+
+ def test_forbiddenResourceRendering(self):
+ """
+ Like L{ErrorPageTests.test_forbiddenResourceRendering}, but flush the
+ deprecation warning emitted by instantiating
+ L{error.ForbiddenResource}.
+ """
+ ErrorPageTests.test_forbiddenResourceRendering(self)
+ self._assertWarning('ForbiddenResource', self.forbiddenResource)
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_script.py b/vendor/Twisted-10.0.0/twisted/web/test/test_script.py
new file mode 100644
index 0000000000..f88d2216d9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_script.py
@@ -0,0 +1,70 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web.script}.
+"""
+
+import os
+
+from twisted.trial.unittest import TestCase
+from twisted.web.http import NOT_FOUND
+from twisted.web.script import ResourceScriptDirectory, PythonScript
+from twisted.web.test._util import _render
+from twisted.web.test.test_web import DummyRequest
+
+
+class ResourceScriptDirectoryTests(TestCase):
+ """
+ Tests for L{ResourceScriptDirectory}.
+ """
+ def test_render(self):
+ """
+ L{ResourceScriptDirectory.render} sets the HTTP response code to I{NOT
+ FOUND}.
+ """
+ resource = ResourceScriptDirectory(self.mktemp())
+ request = DummyRequest([''])
+ d = _render(resource, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, NOT_FOUND)
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_notFoundChild(self):
+ """
+ L{ResourceScriptDirectory.getChild} returns a resource which renders an
+ response with the HTTP I{NOT FOUND} status code if the indicated child
+ does not exist as an entry in the directory used to initialized the
+ L{ResourceScriptDirectory}.
+ """
+ path = self.mktemp()
+ os.makedirs(path)
+ resource = ResourceScriptDirectory(path)
+ request = DummyRequest(['foo'])
+ child = resource.getChild("foo", request)
+ d = _render(child, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, NOT_FOUND)
+ d.addCallback(cbRendered)
+ return d
+
+
+
+class PythonScriptTests(TestCase):
+ """
+ Tests for L{PythonScript}.
+ """
+ def test_notFoundRender(self):
+ """
+ If the source file a L{PythonScript} is initialized with doesn't exist,
+ L{PythonScript.render} sets the HTTP response code to I{NOT FOUND}.
+ """
+ resource = PythonScript(self.mktemp(), None)
+ request = DummyRequest([''])
+ d = _render(resource, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, NOT_FOUND)
+ d.addCallback(cbRendered)
+ return d
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_soap.py b/vendor/Twisted-10.0.0/twisted/web/test/test_soap.py
new file mode 100644
index 0000000000..54e990e84c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_soap.py
@@ -0,0 +1,114 @@
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+#
+
+"""Test SOAP support."""
+
+try:
+ import SOAPpy
+except ImportError:
+ SOAPpy = None
+ class SOAPPublisher: pass
+else:
+ from twisted.web import soap
+ SOAPPublisher = soap.SOAPPublisher
+
+from twisted.trial import unittest
+from twisted.web import server, error
+from twisted.internet import reactor, defer
+
+
+class Test(SOAPPublisher):
+
+ def soap_add(self, a, b):
+ return a + b
+
+ def soap_kwargs(self, a=1, b=2):
+ return a + b
+ soap_kwargs.useKeywords=True
+
+ def soap_triple(self, string, num):
+ return [string, num, None]
+
+ def soap_struct(self):
+ return SOAPpy.structType({"a": "c"})
+
+ def soap_defer(self, x):
+ return defer.succeed(x)
+
+ def soap_deferFail(self):
+ return defer.fail(ValueError())
+
+ def soap_fail(self):
+ raise RuntimeError
+
+ def soap_deferFault(self):
+ return defer.fail(ValueError())
+
+ def soap_complex(self):
+ return {"a": ["b", "c", 12, []], "D": "foo"}
+
+ def soap_dict(self, map, key):
+ return map[key]
+
+
+class SOAPTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.publisher = Test()
+ self.p = reactor.listenTCP(0, server.Site(self.publisher),
+ interface="127.0.0.1")
+ self.port = self.p.getHost().port
+
+ def tearDown(self):
+ return self.p.stopListening()
+
+ def proxy(self):
+ return soap.Proxy("http://127.0.0.1:%d/" % self.port)
+
+ def testResults(self):
+ inputOutput = [
+ ("add", (2, 3), 5),
+ ("defer", ("a",), "a"),
+ ("dict", ({"a": 1}, "a"), 1),
+ ("triple", ("a", 1), ["a", 1, None])]
+
+ dl = []
+ for meth, args, outp in inputOutput:
+ d = self.proxy().callRemote(meth, *args)
+ d.addCallback(self.assertEquals, outp)
+ dl.append(d)
+
+ # SOAPpy kinda blows.
+ d = self.proxy().callRemote('complex')
+ d.addCallback(lambda result: result._asdict())
+ d.addCallback(self.assertEquals, {"a": ["b", "c", 12, []], "D": "foo"})
+ dl.append(d)
+
+ # We now return to our regularly scheduled program, already in progress.
+ return defer.DeferredList(dl, fireOnOneErrback=True)
+
+ def testMethodNotFound(self):
+ """
+ Check that a non existing method return error 500.
+ """
+ d = self.proxy().callRemote('doesntexist')
+ self.assertFailure(d, error.Error)
+ def cb(err):
+ self.assertEquals(int(err.status), 500)
+ d.addCallback(cb)
+ return d
+
+ def testLookupFunction(self):
+ """
+ Test lookupFunction method on publisher, to see available remote
+ methods.
+ """
+ self.assertTrue(self.publisher.lookupFunction("add"))
+ self.assertTrue(self.publisher.lookupFunction("fail"))
+ self.assertFalse(self.publisher.lookupFunction("foobar"))
+
+if not SOAPpy:
+ SOAPTestCase.skip = "SOAPpy not installed"
+
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_static.py b/vendor/Twisted-10.0.0/twisted/web/test/test_static.py
new file mode 100644
index 0000000000..ee8fe267a0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_static.py
@@ -0,0 +1,1507 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web.static}.
+"""
+
+import os, re, StringIO
+
+from zope.interface.verify import verifyObject
+
+from twisted.internet import abstract, interfaces
+from twisted.python.compat import set
+from twisted.python.runtime import platform
+from twisted.python.filepath import FilePath
+from twisted.python import log
+from twisted.trial.unittest import TestCase
+from twisted.web import static, http, script, resource
+from twisted.web.server import UnsupportedMethod
+from twisted.web.test.test_web import DummyRequest
+from twisted.web.test._util import _render
+
+
+class StaticDataTests(TestCase):
+ """
+ Tests for L{Data}.
+ """
+ def test_headRequest(self):
+ """
+ L{Data.render} returns an empty response body for a I{HEAD} request.
+ """
+ data = static.Data("foo", "bar")
+ request = DummyRequest([''])
+ request.method = 'HEAD'
+ d = _render(data, request)
+ def cbRendered(ignored):
+ self.assertEqual(''.join(request.written), "")
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_invalidMethod(self):
+ """
+ L{Data.render} raises L{UnsupportedMethod} in response to a non-I{GET},
+ non-I{HEAD} request.
+ """
+ data = static.Data("foo", "bar")
+ request = DummyRequest([''])
+ request.method = 'POST'
+ self.assertRaises(UnsupportedMethod, data.render, request)
+
+
+
+class StaticFileTests(TestCase):
+ """
+ Tests for the basic behavior of L{File}.
+ """
+ def _render(self, resource, request):
+ return _render(resource, request)
+
+
+ def test_invalidMethod(self):
+ """
+ L{File.render} raises L{UnsupportedMethod} in response to a non-I{GET},
+ non-I{HEAD} request.
+ """
+ request = DummyRequest([''])
+ request.method = 'POST'
+ path = FilePath(self.mktemp())
+ path.setContent("foo")
+ file = static.File(path.path)
+ self.assertRaises(UnsupportedMethod, file.render, request)
+
+
+ def test_notFound(self):
+ """
+ If a request is made which encounters a L{File} before a final segment
+ which does not correspond to any file in the path the L{File} was
+ created with, a not found response is sent.
+ """
+ base = FilePath(self.mktemp())
+ base.makedirs()
+ file = static.File(base.path)
+
+ request = DummyRequest(['foobar'])
+ child = resource.getChildForRequest(file, request)
+
+ d = self._render(child, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, 404)
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_emptyChild(self):
+ """
+ The C{''} child of a L{File} which corresponds to a directory in the
+ filesystem is a L{DirectoryLister}.
+ """
+ base = FilePath(self.mktemp())
+ base.makedirs()
+ file = static.File(base.path)
+
+ request = DummyRequest([''])
+ child = resource.getChildForRequest(file, request)
+ self.assertIsInstance(child, static.DirectoryLister)
+ self.assertEqual(child.path, base.path)
+
+
+ def test_securityViolationNotFound(self):
+ """
+ If a request is made which encounters a L{File} before a final segment
+ which cannot be looked up in the filesystem due to security
+ considerations, a not found response is sent.
+ """
+ base = FilePath(self.mktemp())
+ base.makedirs()
+ file = static.File(base.path)
+
+ request = DummyRequest(['..'])
+ child = resource.getChildForRequest(file, request)
+
+ d = self._render(child, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, 404)
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_forbiddenResource(self):
+ """
+ If the file in the filesystem which would satisfy a request cannot be
+ read, L{File.render} sets the HTTP response code to I{FORBIDDEN}.
+ """
+ base = FilePath(self.mktemp())
+ base.setContent('')
+ # Make sure we can delete the file later.
+ self.addCleanup(base.chmod, 0700)
+
+ # Get rid of our own read permission.
+ base.chmod(0)
+
+ file = static.File(base.path)
+ request = DummyRequest([''])
+ d = self._render(file, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, 403)
+ d.addCallback(cbRendered)
+ return d
+ if platform.isWindows():
+ test_forbiddenResource.skip = "Cannot remove read permission on Windows"
+
+
+ def test_indexNames(self):
+ """
+ If a request is made which encounters a L{File} before a final empty
+ segment, a file in the L{File} instance's C{indexNames} list which
+ exists in the path the L{File} was created with is served as the
+ response to the request.
+ """
+ base = FilePath(self.mktemp())
+ base.makedirs()
+ base.child("foo.bar").setContent("baz")
+ file = static.File(base.path)
+ file.indexNames = ['foo.bar']
+
+ request = DummyRequest([''])
+ child = resource.getChildForRequest(file, request)
+
+ d = self._render(child, request)
+ def cbRendered(ignored):
+ self.assertEqual(''.join(request.written), 'baz')
+ self.assertEqual(request.outgoingHeaders['content-length'], '3')
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_staticFile(self):
+ """
+ If a request is made which encounters a L{File} before a final segment
+ which names a file in the path the L{File} was created with, that file
+ is served as the response to the request.
+ """
+ base = FilePath(self.mktemp())
+ base.makedirs()
+ base.child("foo.bar").setContent("baz")
+ file = static.File(base.path)
+
+ request = DummyRequest(['foo.bar'])
+ child = resource.getChildForRequest(file, request)
+
+ d = self._render(child, request)
+ def cbRendered(ignored):
+ self.assertEqual(''.join(request.written), 'baz')
+ self.assertEqual(request.outgoingHeaders['content-length'], '3')
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_staticFileDeletedGetChild(self):
+ """
+ A L{static.File} created for a directory which does not exist should
+ return childNotFound from L{static.File.getChild}.
+ """
+ staticFile = static.File(self.mktemp())
+ request = DummyRequest(['foo.bar'])
+ child = staticFile.getChild("foo.bar", request)
+ self.assertEquals(child, staticFile.childNotFound)
+
+
+ def test_staticFileDeletedRender(self):
+ """
+ A L{static.File} created for a file which does not exist should render
+ its C{childNotFound} page.
+ """
+ staticFile = static.File(self.mktemp())
+ request = DummyRequest(['foo.bar'])
+ request2 = DummyRequest(['foo.bar'])
+ d = self._render(staticFile, request)
+ d2 = self._render(staticFile.childNotFound, request2)
+ def cbRendered2(ignored):
+ def cbRendered(ignored):
+ self.assertEquals(''.join(request.written),
+ ''.join(request2.written))
+ d.addCallback(cbRendered)
+ return d
+ d2.addCallback(cbRendered2)
+ return d2
+
+
+ def test_headRequest(self):
+ """
+ L{static.File.render} returns an empty response body for I{HEAD}
+ requests.
+ """
+ path = FilePath(self.mktemp())
+ path.setContent("foo")
+ file = static.File(path.path)
+ request = DummyRequest([''])
+ request.method = 'HEAD'
+ d = _render(file, request)
+ def cbRendered(ignored):
+ self.assertEqual("".join(request.written), "")
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_processors(self):
+ """
+ If a request is made which encounters a L{File} before a final segment
+ which names a file with an extension which is in the L{File}'s
+ C{processors} mapping, the processor associated with that extension is
+ used to serve the response to the request.
+ """
+ base = FilePath(self.mktemp())
+ base.makedirs()
+ base.child("foo.bar").setContent(
+ "from twisted.web.static import Data\n"
+ "resource = Data('dynamic world','text/plain')\n")
+
+ file = static.File(base.path)
+ file.processors = {'.bar': script.ResourceScript}
+ request = DummyRequest(["foo.bar"])
+ child = resource.getChildForRequest(file, request)
+
+ d = self._render(child, request)
+ def cbRendered(ignored):
+ self.assertEqual(''.join(request.written), 'dynamic world')
+ self.assertEqual(request.outgoingHeaders['content-length'], '13')
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_ignoreExt(self):
+ """
+ The list of ignored extensions can be set by passing a value to
+ L{File.__init__} or by calling L{File.ignoreExt} later.
+ """
+ file = static.File(".")
+ self.assertEqual(file.ignoredExts, [])
+ file.ignoreExt(".foo")
+ file.ignoreExt(".bar")
+ self.assertEqual(file.ignoredExts, [".foo", ".bar"])
+
+ file = static.File(".", ignoredExts=(".bar", ".baz"))
+ self.assertEqual(file.ignoredExts, [".bar", ".baz"])
+
+
+ def test_ignoredExtensionsIgnored(self):
+ """
+ A request for the I{base} child of a L{File} succeeds with a resource
+ for the I{base<extension>} file in the path the L{File} was created
+ with if such a file exists and the L{File} has been configured to
+ ignore the I{<extension>} extension.
+ """
+ base = FilePath(self.mktemp())
+ base.makedirs()
+ base.child('foo.bar').setContent('baz')
+ base.child('foo.quux').setContent('foobar')
+ file = static.File(base.path, ignoredExts=(".bar",))
+
+ request = DummyRequest(["foo"])
+ child = resource.getChildForRequest(file, request)
+
+ d = self._render(child, request)
+ def cbRendered(ignored):
+ self.assertEqual(''.join(request.written), 'baz')
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_createPickleChild(self):
+ """
+ L{static.File.createPickleChild} is deprecated.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+ static.File(path.path).createPickleChild("foo", None)
+ warnings = self.flushWarnings([self.test_createPickleChild])
+ self.assertEqual(warnings[0]['category'], DeprecationWarning)
+ self.assertEqual(
+ warnings[0]['message'],
+ "File.createPickleChild is deprecated since Twisted 9.0. "
+ "Resource persistence is beyond the scope of Twisted Web.")
+ self.assertEqual(len(warnings), 1)
+
+
+
+class StaticMakeProducerTests(TestCase):
+ """
+ Tests for L{File.makeProducer}.
+ """
+
+
+ def makeResourceWithContent(self, content, type=None, encoding=None):
+ """
+ Make a L{static.File} resource that has C{content} for its content.
+
+ @param content: The bytes to use as the contents of the resource.
+ @param type: Optional value for the content type of the resource.
+ """
+ fileName = self.mktemp()
+ fileObject = open(fileName, 'w')
+ fileObject.write(content)
+ fileObject.close()
+ resource = static.File(fileName)
+ resource.encoding = encoding
+ resource.type = type
+ return resource
+
+
+ def contentHeaders(self, request):
+ """
+ Extract the content-* headers from the L{DummyRequest} C{request}.
+
+ This returns the subset of C{request.outgoingHeaders} of headers that
+ start with 'content-'.
+ """
+ contentHeaders = {}
+ for k, v in request.outgoingHeaders.iteritems():
+ if k.startswith('content-'):
+ contentHeaders[k] = v
+ return contentHeaders
+
+
+ def test_noRangeHeaderGivesNoRangeStaticProducer(self):
+ """
+ makeProducer when no Range header is set returns an instance of
+ NoRangeStaticProducer.
+ """
+ resource = self.makeResourceWithContent('')
+ request = DummyRequest([])
+ producer = resource.makeProducer(request, resource.openForReading())
+ self.assertIsInstance(producer, static.NoRangeStaticProducer)
+
+
+ def test_noRangeHeaderSets200OK(self):
+ """
+ makeProducer when no Range header is set sets the responseCode on the
+ request to 'OK'.
+ """
+ resource = self.makeResourceWithContent('')
+ request = DummyRequest([])
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(http.OK, request.responseCode)
+
+
+ def test_noRangeHeaderSetsContentHeaders(self):
+ """
+ makeProducer when no Range header is set sets the Content-* headers
+ for the response.
+ """
+ length = 123
+ contentType = "text/plain"
+ contentEncoding = 'gzip'
+ resource = self.makeResourceWithContent(
+ 'a'*length, type=contentType, encoding=contentEncoding)
+ request = DummyRequest([])
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(
+ {'content-type': contentType, 'content-length': str(length),
+ 'content-encoding': contentEncoding},
+ self.contentHeaders(request))
+
+
+ def test_singleRangeGivesSingleRangeStaticProducer(self):
+ """
+ makeProducer when the Range header requests a single byte range
+ returns an instance of SingleRangeStaticProducer.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=1-3'
+ resource = self.makeResourceWithContent('abcdef')
+ producer = resource.makeProducer(request, resource.openForReading())
+ self.assertIsInstance(producer, static.SingleRangeStaticProducer)
+
+
+ def test_singleRangeSets206PartialContent(self):
+ """
+ makeProducer when the Range header requests a single, satisfiable byte
+ range sets the response code on the request to 'Partial Content'.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=1-3'
+ resource = self.makeResourceWithContent('abcdef')
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(
+ http.PARTIAL_CONTENT, request.responseCode)
+
+
+ def test_singleRangeSetsContentHeaders(self):
+ """
+ makeProducer when the Range header requests a single, satisfiable byte
+ range sets the Content-* headers appropriately.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=1-3'
+ contentType = "text/plain"
+ contentEncoding = 'gzip'
+ resource = self.makeResourceWithContent('abcdef', type=contentType, encoding=contentEncoding)
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(
+ {'content-type': contentType, 'content-encoding': contentEncoding,
+ 'content-range': 'bytes 1-3/6', 'content-length': '3'},
+ self.contentHeaders(request))
+
+
+ def test_singleUnsatisfiableRangeReturnsSingleRangeStaticProducer(self):
+ """
+ makeProducer still returns an instance of L{SingleRangeStaticProducer}
+ when the Range header requests a single unsatisfiable byte range.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=4-10'
+ resource = self.makeResourceWithContent('abc')
+ producer = resource.makeProducer(request, resource.openForReading())
+ self.assertIsInstance(producer, static.SingleRangeStaticProducer)
+
+
+ def test_singleUnsatisfiableRangeSets416ReqestedRangeNotSatisfiable(self):
+ """
+ makeProducer sets the response code of the request to of 'Requested
+ Range Not Satisfiable' when the Range header requests a single
+ unsatisfiable byte range.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=4-10'
+ resource = self.makeResourceWithContent('abc')
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(
+ http.REQUESTED_RANGE_NOT_SATISFIABLE, request.responseCode)
+
+
+ def test_singleUnsatisfiableRangeSetsContentHeaders(self):
+ """
+ makeProducer when the Range header requests a single, unsatisfiable
+ byte range sets the Content-* headers appropriately.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=4-10'
+ contentType = "text/plain"
+ resource = self.makeResourceWithContent('abc', type=contentType)
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(
+ {'content-type': 'text/plain', 'content-length': '0',
+ 'content-range': 'bytes */3'},
+ self.contentHeaders(request))
+
+
+ def test_singlePartiallyOverlappingRangeSetsContentHeaders(self):
+ """
+ makeProducer when the Range header requests a single byte range that
+ partly overlaps the resource sets the Content-* headers appropriately.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=2-10'
+ contentType = "text/plain"
+ resource = self.makeResourceWithContent('abc', type=contentType)
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(
+ {'content-type': 'text/plain', 'content-length': '1',
+ 'content-range': 'bytes 2-2/3'},
+ self.contentHeaders(request))
+
+
+ def test_multipleRangeGivesMultipleRangeStaticProducer(self):
+ """
+ makeProducer when the Range header requests a single byte range
+ returns an instance of MultipleRangeStaticProducer.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=1-3,5-6'
+ resource = self.makeResourceWithContent('abcdef')
+ producer = resource.makeProducer(request, resource.openForReading())
+ self.assertIsInstance(producer, static.MultipleRangeStaticProducer)
+
+
+ def test_multipleRangeSets206PartialContent(self):
+ """
+ makeProducer when the Range header requests a multiple satisfiable
+ byte ranges sets the response code on the request to 'Partial
+ Content'.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=1-3,5-6'
+ resource = self.makeResourceWithContent('abcdef')
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(
+ http.PARTIAL_CONTENT, request.responseCode)
+
+
+ def test_mutipleRangeSetsContentHeaders(self):
+ """
+ makeProducer when the Range header requests a single, satisfiable byte
+ range sets the Content-* headers appropriately.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=1-3,5-6'
+ resource = self.makeResourceWithContent(
+ 'abcdefghijkl', encoding='gzip')
+ producer = resource.makeProducer(request, resource.openForReading())
+ contentHeaders = self.contentHeaders(request)
+ # The only content-* headers set are content-type and content-length.
+ self.assertEqual(
+ set(['content-length', 'content-type']),
+ set(contentHeaders.keys()))
+ # The content-length depends on the boundary used in the response.
+ expectedLength = 5
+ for boundary, offset, size in producer.rangeInfo:
+ expectedLength += len(boundary)
+ self.assertEqual(expectedLength, contentHeaders['content-length'])
+ # Content-type should be set to a value indicating a multipart
+ # response and the boundary used to separate the parts.
+ self.assertIn('content-type', contentHeaders)
+ contentType = contentHeaders['content-type']
+ self.assertNotIdentical(
+ None, re.match(
+ 'multipart/byteranges; boundary="[^"]*"\Z', contentType))
+ # Content-encoding is not set in the response to a multiple range
+ # response, which is a bit wussy but works well enough with the way
+ # static.File does content-encodings...
+ self.assertNotIn('content-encoding', contentHeaders)
+
+
+ def test_multipleUnsatisfiableRangesReturnsMultipleRangeStaticProducer(self):
+ """
+ makeProducer still returns an instance of L{SingleRangeStaticProducer}
+ when the Range header requests multiple ranges, none of which are
+ satisfiable.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=10-12,15-20'
+ resource = self.makeResourceWithContent('abc')
+ producer = resource.makeProducer(request, resource.openForReading())
+ self.assertIsInstance(producer, static.MultipleRangeStaticProducer)
+
+
+ def test_multipleUnsatisfiableRangesSets416ReqestedRangeNotSatisfiable(self):
+ """
+ makeProducer sets the response code of the request to of 'Requested
+ Range Not Satisfiable' when the Range header requests multiple ranges,
+ none of which are satisfiable.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=10-12,15-20'
+ resource = self.makeResourceWithContent('abc')
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(
+ http.REQUESTED_RANGE_NOT_SATISFIABLE, request.responseCode)
+
+
+ def test_multipleUnsatisfiableRangeSetsContentHeaders(self):
+ """
+ makeProducer when the Range header requests multiple ranges, none of
+ which are satisfiable, sets the Content-* headers appropriately.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=4-10'
+ contentType = "text/plain"
+ request.headers['range'] = 'bytes=10-12,15-20'
+ resource = self.makeResourceWithContent('abc', type=contentType)
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(
+ {'content-length': '0', 'content-range': 'bytes */3'},
+ self.contentHeaders(request))
+
+
+ def test_oneSatisfiableRangeIsEnough(self):
+ """
+ makeProducer when the Range header requests multiple ranges, at least
+ one of which matches, sets the response code to 'Partial Content'.
+ """
+ request = DummyRequest([])
+ request.headers['range'] = 'bytes=1-3,100-200'
+ resource = self.makeResourceWithContent('abcdef')
+ resource.makeProducer(request, resource.openForReading())
+ self.assertEqual(
+ http.PARTIAL_CONTENT, request.responseCode)
+
+
+
+class StaticProducerTests(TestCase):
+ """
+ Tests for the abstract L{StaticProducer}.
+ """
+
+ def test_stopProducingClosesFile(self):
+ """
+ L{StaticProducer.stopProducing} closes the file object the producer is
+ producing data from.
+ """
+ fileObject = StringIO.StringIO()
+ producer = static.StaticProducer(None, fileObject)
+ producer.stopProducing()
+ self.assertTrue(fileObject.closed)
+
+
+ def test_stopProducingSetsRequestToNone(self):
+ """
+ L{StaticProducer.stopProducing} sets the request instance variable to
+ None, which indicates to subclasses' resumeProducing methods that no
+ more data should be produced.
+ """
+ fileObject = StringIO.StringIO()
+ producer = static.StaticProducer(DummyRequest([]), fileObject)
+ producer.stopProducing()
+ self.assertIdentical(None, producer.request)
+
+
+
+class NoRangeStaticProducerTests(TestCase):
+ """
+ Tests for L{NoRangeStaticProducer}.
+ """
+
+ def test_implementsIPullProducer(self):
+ """
+ L{NoRangeStaticProducer} implements L{IPullProducer}.
+ """
+ verifyObject(
+ interfaces.IPullProducer,
+ static.NoRangeStaticProducer(None, None))
+
+
+ def test_resumeProducingProducesContent(self):
+ """
+ L{NoRangeStaticProducer.resumeProducing} writes content from the
+ resource to the request.
+ """
+ request = DummyRequest([])
+ content = 'abcdef'
+ producer = static.NoRangeStaticProducer(
+ request, StringIO.StringIO(content))
+ # start calls registerProducer on the DummyRequest, which pulls all
+ # output from the producer and so we just need this one call.
+ producer.start()
+ self.assertEqual(content, ''.join(request.written))
+
+
+ def test_resumeProducingBuffersOutput(self):
+ """
+ L{NoRangeStaticProducer.start} writes at most
+ C{abstract.FileDescriptor.bufferSize} bytes of content from the
+ resource to the request at once.
+ """
+ request = DummyRequest([])
+ bufferSize = abstract.FileDescriptor.bufferSize
+ content = 'a' * (2*bufferSize + 1)
+ producer = static.NoRangeStaticProducer(
+ request, StringIO.StringIO(content))
+ # start calls registerProducer on the DummyRequest, which pulls all
+ # output from the producer and so we just need this one call.
+ producer.start()
+ expected = [
+ content[0:bufferSize],
+ content[bufferSize:2*bufferSize],
+ content[2*bufferSize:]
+ ]
+ self.assertEqual(expected, request.written)
+
+
+ def test_finishCalledWhenDone(self):
+ """
+ L{NoRangeStaticProducer.resumeProducing} calls finish() on the request
+ after it is done producing content.
+ """
+ request = DummyRequest([])
+ finishDeferred = request.notifyFinish()
+ callbackList = []
+ finishDeferred.addCallback(callbackList.append)
+ producer = static.NoRangeStaticProducer(
+ request, StringIO.StringIO('abcdef'))
+ # start calls registerProducer on the DummyRequest, which pulls all
+ # output from the producer and so we just need this one call.
+ producer.start()
+ self.assertEqual([None], callbackList)
+
+
+
+class SingleRangeStaticProducerTests(TestCase):
+ """
+ Tests for L{SingleRangeStaticProducer}.
+ """
+
+ def test_implementsIPullProducer(self):
+ """
+ L{SingleRangeStaticProducer} implements L{IPullProducer}.
+ """
+ verifyObject(
+ interfaces.IPullProducer,
+ static.SingleRangeStaticProducer(None, None, None, None))
+
+
+ def test_resumeProducingProducesContent(self):
+ """
+ L{SingleRangeStaticProducer.resumeProducing} writes the given amount
+ of content, starting at the given offset, from the resource to the
+ request.
+ """
+ request = DummyRequest([])
+ content = 'abcdef'
+ producer = static.SingleRangeStaticProducer(
+ request, StringIO.StringIO(content), 1, 3)
+ # DummyRequest.registerProducer pulls all output from the producer, so
+ # we just need to call start.
+ producer.start()
+ self.assertEqual(content[1:4], ''.join(request.written))
+
+
+ def test_resumeProducingBuffersOutput(self):
+ """
+ L{SingleRangeStaticProducer.start} writes at most
+ C{abstract.FileDescriptor.bufferSize} bytes of content from the
+ resource to the request at once.
+ """
+ request = DummyRequest([])
+ bufferSize = abstract.FileDescriptor.bufferSize
+ content = 'abc' * bufferSize
+ producer = static.SingleRangeStaticProducer(
+ request, StringIO.StringIO(content), 1, bufferSize+10)
+ # DummyRequest.registerProducer pulls all output from the producer, so
+ # we just need to call start.
+ producer.start()
+ expected = [
+ content[1:bufferSize+1],
+ content[bufferSize+1:bufferSize+11],
+ ]
+ self.assertEqual(expected, request.written)
+
+
+ def test_finishCalledWhenDone(self):
+ """
+ L{SingleRangeStaticProducer.resumeProducing} calls finish() on the
+ request after it is done producing content.
+ """
+ request = DummyRequest([])
+ finishDeferred = request.notifyFinish()
+ callbackList = []
+ finishDeferred.addCallback(callbackList.append)
+ producer = static.SingleRangeStaticProducer(
+ request, StringIO.StringIO('abcdef'), 1, 1)
+ # start calls registerProducer on the DummyRequest, which pulls all
+ # output from the producer and so we just need this one call.
+ producer.start()
+ self.assertEqual([None], callbackList)
+
+
+
+class MultipleRangeStaticProducerTests(TestCase):
+ """
+ Tests for L{MultipleRangeStaticProducer}.
+ """
+
+ def test_implementsIPullProducer(self):
+ """
+ L{MultipleRangeStaticProducer} implements L{IPullProducer}.
+ """
+ verifyObject(
+ interfaces.IPullProducer,
+ static.MultipleRangeStaticProducer(None, None, None))
+
+
+ def test_resumeProducingProducesContent(self):
+ """
+ L{MultipleRangeStaticProducer.resumeProducing} writes the requested
+ chunks of content from the resource to the request, with the supplied
+ boundaries in between each chunk.
+ """
+ request = DummyRequest([])
+ content = 'abcdef'
+ producer = static.MultipleRangeStaticProducer(
+ request, StringIO.StringIO(content), [('1', 1, 3), ('2', 5, 1)])
+ # DummyRequest.registerProducer pulls all output from the producer, so
+ # we just need to call start.
+ producer.start()
+ self.assertEqual('1bcd2f', ''.join(request.written))
+
+
+ def test_resumeProducingBuffersOutput(self):
+ """
+ L{MultipleRangeStaticProducer.start} writes about
+ C{abstract.FileDescriptor.bufferSize} bytes of content from the
+ resource to the request at once.
+
+ To be specific about the 'about' above: it can write slightly more,
+ for example in the case where the first boundary plus the first chunk
+ is less than C{bufferSize} but first boundary plus the first chunk
+ plus the second boundary is more, but this is unimportant as in
+ practice the boundaries are fairly small. On the other side, it is
+ important for performance to bundle up several small chunks into one
+ call to request.write.
+ """
+ request = DummyRequest([])
+ content = '0123456789' * 2
+ producer = static.MultipleRangeStaticProducer(
+ request, StringIO.StringIO(content),
+ [('a', 0, 2), ('b', 5, 10), ('c', 0, 0)])
+ producer.bufferSize = 10
+ # DummyRequest.registerProducer pulls all output from the producer, so
+ # we just need to call start.
+ producer.start()
+ expected = [
+ 'a' + content[0:2] + 'b' + content[5:11],
+ content[11:15] + 'c',
+ ]
+ self.assertEqual(expected, request.written)
+
+
+ def test_finishCalledWhenDone(self):
+ """
+ L{MultipleRangeStaticProducer.resumeProducing} calls finish() on the
+ request after it is done producing content.
+ """
+ request = DummyRequest([])
+ finishDeferred = request.notifyFinish()
+ callbackList = []
+ finishDeferred.addCallback(callbackList.append)
+ producer = static.MultipleRangeStaticProducer(
+ request, StringIO.StringIO('abcdef'), [('', 1, 2)])
+ # start calls registerProducer on the DummyRequest, which pulls all
+ # output from the producer and so we just need this one call.
+ producer.start()
+ self.assertEqual([None], callbackList)
+
+
+
+class RangeTests(TestCase):
+ """
+ Tests for I{Range-Header} support in L{twisted.web.static.File}.
+
+ @type file: L{file}
+ @ivar file: Temporary (binary) file containing the content to be served.
+
+ @type resource: L{static.File}
+ @ivar resource: A leaf web resource using C{file} as content.
+
+ @type request: L{DummyRequest}
+ @ivar request: A fake request, requesting C{resource}.
+
+ @type catcher: L{list}
+ @ivar catcher: List which gathers all log information.
+ """
+ def setUp(self):
+ """
+ Create a temporary file with a fixed payload of 64 bytes. Create a
+ resource for that file and create a request which will be for that
+ resource. Each test can set a different range header to test different
+ aspects of the implementation.
+ """
+ path = FilePath(self.mktemp())
+ # This is just a jumble of random stuff. It's supposed to be a good
+ # set of data for this test, particularly in order to avoid
+ # accidentally seeing the right result by having a byte sequence
+ # repeated at different locations or by having byte values which are
+ # somehow correlated with their position in the string.
+ self.payload = ('\xf8u\xf3E\x8c7\xce\x00\x9e\xb6a0y0S\xf0\xef\xac\xb7'
+ '\xbe\xb5\x17M\x1e\x136k{\x1e\xbe\x0c\x07\x07\t\xd0'
+ '\xbckY\xf5I\x0b\xb8\x88oZ\x1d\x85b\x1a\xcdk\xf2\x1d'
+ '&\xfd%\xdd\x82q/A\x10Y\x8b')
+ path.setContent(self.payload)
+ self.file = path.open()
+ self.resource = static.File(self.file.name)
+ self.resource.isLeaf = 1
+ self.request = DummyRequest([''])
+ self.request.uri = self.file.name
+ self.catcher = []
+ log.addObserver(self.catcher.append)
+
+
+ def tearDown(self):
+ """
+ Clean up the resource file and the log observer.
+ """
+ self.file.close()
+ log.removeObserver(self.catcher.append)
+
+
+ def _assertLogged(self, expected):
+ """
+ Asserts that a given log message occurred with an expected message.
+ """
+ logItem = self.catcher.pop()
+ self.assertEquals(logItem["message"][0], expected)
+ self.assertEqual(
+ self.catcher, [], "An additional log occured: %r" % (logItem,))
+
+
+ def test_invalidRanges(self):
+ """
+ L{File._parseRangeHeader} raises L{ValueError} when passed
+ syntactically invalid byte ranges.
+ """
+ f = self.resource._parseRangeHeader
+
+ # there's no =
+ self.assertRaises(ValueError, f, 'bytes')
+
+ # unknown isn't a valid Bytes-Unit
+ self.assertRaises(ValueError, f, 'unknown=1-2')
+
+ # there's no - in =stuff
+ self.assertRaises(ValueError, f, 'bytes=3')
+
+ # both start and end are empty
+ self.assertRaises(ValueError, f, 'bytes=-')
+
+ # start isn't an integer
+ self.assertRaises(ValueError, f, 'bytes=foo-')
+
+ # end isn't an integer
+ self.assertRaises(ValueError, f, 'bytes=-foo')
+
+ # end isn't equal to or greater than start
+ self.assertRaises(ValueError, f, 'bytes=5-4')
+
+
+ def test_rangeMissingStop(self):
+ """
+ A single bytes range without an explicit stop position is parsed into a
+ two-tuple giving the start position and C{None}.
+ """
+ self.assertEqual(
+ self.resource._parseRangeHeader('bytes=0-'), [(0, None)])
+
+
+ def test_rangeMissingStart(self):
+ """
+ A single bytes range without an explicit start position is parsed into
+ a two-tuple of C{None} and the end position.
+ """
+ self.assertEqual(
+ self.resource._parseRangeHeader('bytes=-3'), [(None, 3)])
+
+
+ def test_range(self):
+ """
+ A single bytes range with explicit start and stop positions is parsed
+ into a two-tuple of those positions.
+ """
+ self.assertEqual(
+ self.resource._parseRangeHeader('bytes=2-5'), [(2, 5)])
+
+
+ def test_rangeWithSpace(self):
+ """
+ A single bytes range with whitespace in allowed places is parsed in
+ the same way as it would be without the whitespace.
+ """
+ self.assertEqual(
+ self.resource._parseRangeHeader(' bytes=1-2 '), [(1, 2)])
+ self.assertEqual(
+ self.resource._parseRangeHeader('bytes =1-2 '), [(1, 2)])
+ self.assertEqual(
+ self.resource._parseRangeHeader('bytes= 1-2'), [(1, 2)])
+ self.assertEqual(
+ self.resource._parseRangeHeader('bytes=1 -2'), [(1, 2)])
+ self.assertEqual(
+ self.resource._parseRangeHeader('bytes=1- 2'), [(1, 2)])
+ self.assertEqual(
+ self.resource._parseRangeHeader('bytes=1-2 '), [(1, 2)])
+
+
+ def test_nullRangeElements(self):
+ """
+ If there are multiple byte ranges but only one is non-null, the
+ non-null range is parsed and its start and stop returned.
+ """
+ self.assertEqual(
+ self.resource._parseRangeHeader('bytes=1-2,\r\n, ,\t'), [(1, 2)])
+
+
+ def test_multipleRanges(self):
+ """
+ If multiple byte ranges are specified their starts and stops are
+ returned.
+ """
+ self.assertEqual(
+ self.resource._parseRangeHeader('bytes=1-2,3-4'),
+ [(1, 2), (3, 4)])
+
+
+ def test_bodyLength(self):
+ """
+ A correct response to a range request is as long as the length of the
+ requested range.
+ """
+ self.request.headers['range'] = 'bytes=0-43'
+ self.resource.render(self.request)
+ self.assertEquals(len(''.join(self.request.written)), 44)
+
+
+ def test_invalidRangeRequest(self):
+ """
+ An incorrect range request (RFC 2616 defines a correct range request as
+ a Bytes-Unit followed by a '=' character followed by a specific range.
+ Only 'bytes' is defined) results in the range header value being logged
+ and a normal 200 response being sent.
+ """
+ self.request.headers['range'] = range = 'foobar=0-43'
+ self.resource.render(self.request)
+ expected = "Ignoring malformed Range header %r" % (range,)
+ self._assertLogged(expected)
+ self.assertEquals(''.join(self.request.written), self.payload)
+ self.assertEquals(self.request.responseCode, http.OK)
+ self.assertEquals(
+ self.request.outgoingHeaders['content-length'],
+ str(len(self.payload)))
+
+
+ def parseMultipartBody(self, body, boundary):
+ """
+ Parse C{body} as a multipart MIME response separated by C{boundary}.
+
+ Note that this with fail the calling test on certain syntactic
+ problems.
+ """
+ sep = "\r\n--" + boundary
+ parts = ''.join(body).split(sep)
+ self.assertEquals('', parts[0])
+ self.assertEquals('--\r\n', parts[-1])
+ parsed_parts = []
+ for part in parts[1:-1]:
+ before, header1, header2, blank, partBody = part.split('\r\n', 4)
+ headers = header1 + '\n' + header2
+ self.assertEqual('', before)
+ self.assertEqual('', blank)
+ partContentTypeValue = re.search(
+ '^content-type: (.*)$', headers, re.I|re.M).group(1)
+ start, end, size = re.search(
+ '^content-range: bytes ([0-9]+)-([0-9]+)/([0-9]+)$',
+ headers, re.I|re.M).groups()
+ parsed_parts.append(
+ {'contentType': partContentTypeValue,
+ 'contentRange': (start, end, size),
+ 'body': partBody})
+ return parsed_parts
+
+
+ def test_multipleRangeRequest(self):
+ """
+ The response to a request for multipe bytes ranges is a MIME-ish
+ multipart response.
+ """
+ startEnds = [(0, 2), (20, 30), (40, 50)]
+ rangeHeaderValue = ','.join(["%s-%s"%(s,e) for (s, e) in startEnds])
+ self.request.headers['range'] = 'bytes=' + rangeHeaderValue
+ self.resource.render(self.request)
+ self.assertEquals(self.request.responseCode, http.PARTIAL_CONTENT)
+ boundary = re.match(
+ '^multipart/byteranges; boundary="(.*)"$',
+ self.request.outgoingHeaders['content-type']).group(1)
+ parts = self.parseMultipartBody(''.join(self.request.written), boundary)
+ self.assertEquals(len(startEnds), len(parts))
+ for part, (s, e) in zip(parts, startEnds):
+ self.assertEqual(self.resource.type, part['contentType'])
+ start, end, size = part['contentRange']
+ self.assertEqual(int(start), s)
+ self.assertEqual(int(end), e)
+ self.assertEqual(int(size), self.resource.getFileSize())
+ self.assertEqual(self.payload[s:e+1], part['body'])
+
+
+ def test_multipleRangeRequestWithRangeOverlappingEnd(self):
+ """
+ The response to a request for multipe bytes ranges is a MIME-ish
+ multipart response, even when one of the ranged falls off the end of
+ the resource.
+ """
+ startEnds = [(0, 2), (40, len(self.payload) + 10)]
+ rangeHeaderValue = ','.join(["%s-%s"%(s,e) for (s, e) in startEnds])
+ self.request.headers['range'] = 'bytes=' + rangeHeaderValue
+ self.resource.render(self.request)
+ self.assertEquals(self.request.responseCode, http.PARTIAL_CONTENT)
+ boundary = re.match(
+ '^multipart/byteranges; boundary="(.*)"$',
+ self.request.outgoingHeaders['content-type']).group(1)
+ parts = self.parseMultipartBody(''.join(self.request.written), boundary)
+ self.assertEquals(len(startEnds), len(parts))
+ for part, (s, e) in zip(parts, startEnds):
+ self.assertEqual(self.resource.type, part['contentType'])
+ start, end, size = part['contentRange']
+ self.assertEqual(int(start), s)
+ self.assertEqual(int(end), min(e, self.resource.getFileSize()-1))
+ self.assertEqual(int(size), self.resource.getFileSize())
+ self.assertEqual(self.payload[s:e+1], part['body'])
+
+
+ def test_implicitEnd(self):
+ """
+ If the end byte position is omitted, then it is treated as if the
+ length of the resource was specified by the end byte position.
+ """
+ self.request.headers['range'] = 'bytes=23-'
+ self.resource.render(self.request)
+ self.assertEquals(''.join(self.request.written), self.payload[23:])
+ self.assertEquals(len(''.join(self.request.written)), 41)
+ self.assertEquals(self.request.responseCode, http.PARTIAL_CONTENT)
+ self.assertEquals(
+ self.request.outgoingHeaders['content-range'], 'bytes 23-63/64')
+ self.assertEquals(self.request.outgoingHeaders['content-length'], '41')
+
+
+ def test_implicitStart(self):
+ """
+ If the start byte position is omitted but the end byte position is
+ supplied, then the range is treated as requesting the last -N bytes of
+ the resource, where N is the end byte position.
+ """
+ self.request.headers['range'] = 'bytes=-17'
+ self.resource.render(self.request)
+ self.assertEquals(''.join(self.request.written), self.payload[-17:])
+ self.assertEquals(len(''.join(self.request.written)), 17)
+ self.assertEquals(self.request.responseCode, http.PARTIAL_CONTENT)
+ self.assertEquals(
+ self.request.outgoingHeaders['content-range'], 'bytes 47-63/64')
+ self.assertEquals(self.request.outgoingHeaders['content-length'], '17')
+
+
+ def test_explicitRange(self):
+ """
+ A correct response to a bytes range header request from A to B starts
+ with the A'th byte and ends with (including) the B'th byte. The first
+ byte of a page is numbered with 0.
+ """
+ self.request.headers['range'] = 'bytes=3-43'
+ self.resource.render(self.request)
+ written = ''.join(self.request.written)
+ self.assertEquals(written, self.payload[3:44])
+ self.assertEquals(self.request.responseCode, http.PARTIAL_CONTENT)
+ self.assertEquals(
+ self.request.outgoingHeaders['content-range'], 'bytes 3-43/64')
+ self.assertEquals(
+ str(len(written)), self.request.outgoingHeaders['content-length'])
+
+
+ def test_explicitRangeOverlappingEnd(self):
+ """
+ A correct response to a bytes range header request from A to B when B
+ is past the end of the resource starts with the A'th byte and ends
+ with the last byte of the resource. The first byte of a page is
+ numbered with 0.
+ """
+ self.request.headers['range'] = 'bytes=40-100'
+ self.resource.render(self.request)
+ written = ''.join(self.request.written)
+ self.assertEquals(written, self.payload[40:])
+ self.assertEquals(self.request.responseCode, http.PARTIAL_CONTENT)
+ self.assertEquals(
+ self.request.outgoingHeaders['content-range'], 'bytes 40-63/64')
+ self.assertEquals(
+ str(len(written)), self.request.outgoingHeaders['content-length'])
+
+
+ def test_statusCodeRequestedRangeNotSatisfiable(self):
+ """
+ If a range is syntactically invalid due to the start being greater than
+ the end, the range header is ignored (the request is responded to as if
+ it were not present).
+ """
+ self.request.headers['range'] = 'bytes=20-13'
+ self.resource.render(self.request)
+ self.assertEquals(self.request.responseCode, http.OK)
+ self.assertEquals(''.join(self.request.written), self.payload)
+ self.assertEquals(
+ self.request.outgoingHeaders['content-length'],
+ str(len(self.payload)))
+
+
+ def test_invalidStartBytePos(self):
+ """
+ If a range is unsatisfiable due to the start not being less than the
+ length of the resource, the response is 416 (Requested range not
+ satisfiable) and no data is written to the response body (RFC 2616,
+ section 14.35.1).
+ """
+ self.request.headers['range'] = 'bytes=67-108'
+ self.resource.render(self.request)
+ self.assertEquals(
+ self.request.responseCode, http.REQUESTED_RANGE_NOT_SATISFIABLE)
+ self.assertEquals(''.join(self.request.written), '')
+ self.assertEquals(self.request.outgoingHeaders['content-length'], '0')
+ # Sections 10.4.17 and 14.16
+ self.assertEquals(
+ self.request.outgoingHeaders['content-range'],
+ 'bytes */%d' % (len(self.payload),))
+
+
+
+class DirectoryListerTest(TestCase):
+ """
+ Tests for L{static.DirectoryLister}.
+ """
+ def _request(self, uri):
+ request = DummyRequest([''])
+ request.uri = uri
+ return request
+
+
+ def test_renderHeader(self):
+ """
+ L{static.DirectoryLister} prints the request uri as header of the
+ rendered content.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+
+ lister = static.DirectoryLister(path.path)
+ data = lister.render(self._request('foo'))
+ self.assertIn("<h1>Directory listing for foo</h1>", data)
+ self.assertIn("<title>Directory listing for foo</title>", data)
+
+
+ def test_renderUnquoteHeader(self):
+ """
+ L{static.DirectoryLister} unquote the request uri before printing it.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+
+ lister = static.DirectoryLister(path.path)
+ data = lister.render(self._request('foo%20bar'))
+ self.assertIn("<h1>Directory listing for foo bar</h1>", data)
+ self.assertIn("<title>Directory listing for foo bar</title>", data)
+
+
+ def test_escapeHeader(self):
+ """
+ L{static.DirectoryLister} escape "&", "<" and ">" after unquoting the
+ request uri.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+
+ lister = static.DirectoryLister(path.path)
+ data = lister.render(self._request('foo%26bar'))
+ self.assertIn("<h1>Directory listing for foo&amp;bar</h1>", data)
+ self.assertIn("<title>Directory listing for foo&amp;bar</title>", data)
+
+
+ def test_renderFiles(self):
+ """
+ L{static.DirectoryLister} is able to list all the files inside a
+ directory.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+ path.child('file1').setContent("content1")
+ path.child('file2').setContent("content2" * 1000)
+
+ lister = static.DirectoryLister(path.path)
+ data = lister.render(self._request('foo'))
+ body = """<tr class="odd">
+ <td><a href="file1">file1</a></td>
+ <td>8B</td>
+ <td>[text/html]</td>
+ <td></td>
+</tr>
+<tr class="even">
+ <td><a href="file2">file2</a></td>
+ <td>7K</td>
+ <td>[text/html]</td>
+ <td></td>
+</tr>"""
+ self.assertIn(body, data)
+
+
+ def test_renderDirectories(self):
+ """
+ L{static.DirectoryLister} is able to list all the directories inside
+ a directory.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+ path.child('dir1').makedirs()
+ path.child('dir2 & 3').makedirs()
+
+ lister = static.DirectoryLister(path.path)
+ data = lister.render(self._request('foo'))
+ body = """<tr class="odd">
+ <td><a href="dir1/">dir1/</a></td>
+ <td></td>
+ <td>[Directory]</td>
+ <td></td>
+</tr>
+<tr class="even">
+ <td><a href="dir2%20%26%203/">dir2 &amp; 3/</a></td>
+ <td></td>
+ <td>[Directory]</td>
+ <td></td>
+</tr>"""
+ self.assertIn(body, data)
+
+
+ def test_renderFiltered(self):
+ """
+ L{static.DirectoryLister} takes a optional C{dirs} argument that
+ filter out the list of of directories and files printed.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+ path.child('dir1').makedirs()
+ path.child('dir2').makedirs()
+ path.child('dir3').makedirs()
+ lister = static.DirectoryLister(path.path, dirs=["dir1", "dir3"])
+ data = lister.render(self._request('foo'))
+ body = """<tr class="odd">
+ <td><a href="dir1/">dir1/</a></td>
+ <td></td>
+ <td>[Directory]</td>
+ <td></td>
+</tr>
+<tr class="even">
+ <td><a href="dir3/">dir3/</a></td>
+ <td></td>
+ <td>[Directory]</td>
+ <td></td>
+</tr>"""
+ self.assertIn(body, data)
+
+
+ def test_oddAndEven(self):
+ """
+ L{static.DirectoryLister} gives an alternate class for each odd and
+ even rows in the table.
+ """
+ lister = static.DirectoryLister(None)
+ elements = [{"href": "", "text": "", "size": "", "type": "",
+ "encoding": ""} for i in xrange(5)]
+ content = lister._buildTableContent(elements)
+
+ self.assertEquals(len(content), 5)
+ self.assertTrue(content[0].startswith('<tr class="odd">'))
+ self.assertTrue(content[1].startswith('<tr class="even">'))
+ self.assertTrue(content[2].startswith('<tr class="odd">'))
+ self.assertTrue(content[3].startswith('<tr class="even">'))
+ self.assertTrue(content[4].startswith('<tr class="odd">'))
+
+
+ def test_mimeTypeAndEncodings(self):
+ """
+ L{static.DirectoryLister} is able to detect mimetype and encoding of
+ listed files.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+ path.child('file1.txt').setContent("file1")
+ path.child('file2.py').setContent("python")
+ path.child('file3.conf.gz').setContent("conf compressed")
+ path.child('file4.diff.bz2').setContent("diff compressed")
+ directory = os.listdir(path.path)
+ directory.sort()
+
+ contentTypes = {
+ ".txt": "text/plain",
+ ".py": "text/python",
+ ".conf": "text/configuration",
+ ".diff": "text/diff"
+ }
+
+ lister = static.DirectoryLister(path.path, contentTypes=contentTypes)
+ dirs, files = lister._getFilesAndDirectories(directory)
+ self.assertEquals(dirs, [])
+ self.assertEquals(files, [
+ {'encoding': '',
+ 'href': 'file1.txt',
+ 'size': '5B',
+ 'text': 'file1.txt',
+ 'type': '[text/plain]'},
+ {'encoding': '',
+ 'href': 'file2.py',
+ 'size': '6B',
+ 'text': 'file2.py',
+ 'type': '[text/python]'},
+ {'encoding': '[gzip]',
+ 'href': 'file3.conf.gz',
+ 'size': '15B',
+ 'text': 'file3.conf.gz',
+ 'type': '[text/configuration]'},
+ {'encoding': '[bzip2]',
+ 'href': 'file4.diff.bz2',
+ 'size': '15B',
+ 'text': 'file4.diff.bz2',
+ 'type': '[text/diff]'}])
+
+
+ def test_brokenSymlink(self):
+ """
+ If on the file in the listing points to a broken symlink, it should not
+ be returned by L{static.DirectoryLister._getFilesAndDirectories}.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+ file1 = path.child('file1')
+ file1.setContent("file1")
+ file1.linkTo(path.child("file2"))
+ file1.remove()
+
+ lister = static.DirectoryLister(path.path)
+ directory = os.listdir(path.path)
+ directory.sort()
+ dirs, files = lister._getFilesAndDirectories(directory)
+ self.assertEquals(dirs, [])
+ self.assertEquals(files, [])
+
+ if getattr(os, "symlink", None) is None:
+ test_brokenSymlink.skip = "No symlink support"
+
+
+ def test_childrenNotFound(self):
+ """
+ Any child resource of L{static.DirectoryLister} renders an HTTP
+ I{NOT FOUND} response code.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+ lister = static.DirectoryLister(path.path)
+ request = self._request('')
+ child = resource.getChildForRequest(lister, request)
+ result = _render(child, request)
+ def cbRendered(ignored):
+ self.assertEquals(request.responseCode, http.NOT_FOUND)
+ result.addCallback(cbRendered)
+ return result
+
+
+ def test_repr(self):
+ """
+ L{static.DirectoryLister.__repr__} gives the path of the lister.
+ """
+ path = FilePath(self.mktemp())
+ lister = static.DirectoryLister(path.path)
+ self.assertEquals(repr(lister),
+ "<DirectoryLister of %r>" % (path.path,))
+ self.assertEquals(str(lister),
+ "<DirectoryLister of %r>" % (path.path,))
+
+ def test_formatFileSize(self):
+ """
+ L{static.formatFileSize} format an amount of bytes into a more readable
+ format.
+ """
+ self.assertEquals(static.formatFileSize(0), "0B")
+ self.assertEquals(static.formatFileSize(123), "123B")
+ self.assertEquals(static.formatFileSize(4567), "4K")
+ self.assertEquals(static.formatFileSize(8900000), "8M")
+ self.assertEquals(static.formatFileSize(1234000000), "1G")
+ self.assertEquals(static.formatFileSize(1234567890000), "1149G")
+
+
+
+class TestFileTransferDeprecated(TestCase):
+ """
+ L{static.FileTransfer} is deprecated.
+ """
+
+ def test_deprecation(self):
+ """
+ Instantiation of L{FileTransfer} produces a deprecation warning.
+ """
+ static.FileTransfer(StringIO.StringIO(), 0, DummyRequest([]))
+ warnings = self.flushWarnings([self.test_deprecation])
+ self.assertEqual(len(warnings), 1)
+ self.assertEqual(warnings[0]['category'], DeprecationWarning)
+ self.assertEqual(
+ warnings[0]['message'],
+ 'FileTransfer is deprecated since Twisted 9.0. '
+ 'Use a subclass of StaticProducer instead.')
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_tap.py b/vendor/Twisted-10.0.0/twisted/web/test/test_tap.py
new file mode 100644
index 0000000000..358f2789d8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_tap.py
@@ -0,0 +1,251 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web.tap}.
+"""
+
+import os, stat, pickle
+
+from twisted.python.usage import UsageError
+from twisted.python.filepath import FilePath
+from twisted.internet.interfaces import IReactorUNIX
+from twisted.internet import reactor
+from twisted.python.threadpool import ThreadPool
+from twisted.trial.unittest import TestCase
+from twisted.application import strports
+
+from twisted.web.server import Site
+from twisted.web.static import Data, File
+from twisted.web.distrib import ResourcePublisher, UserDirectory
+from twisted.web.wsgi import WSGIResource
+from twisted.web.tap import Options, makePersonalServerFactory, makeService
+from twisted.web.twcgi import CGIScript, PHP3Script, PHPScript
+from twisted.web.script import PythonScript
+
+
+from twisted.spread.pb import PBServerFactory
+
+application = object()
+
+class ServiceTests(TestCase):
+ """
+ Tests for the service creation APIs in L{twisted.web.tap}.
+ """
+ def _pathOption(self):
+ """
+ Helper for the I{--path} tests which creates a directory and creates
+ an L{Options} object which uses that directory as its static
+ filesystem root.
+
+ @return: A two-tuple of a L{FilePath} referring to the directory and
+ the value associated with the C{'root'} key in the L{Options}
+ instance after parsing a I{--path} option.
+ """
+ path = FilePath(self.mktemp())
+ path.makedirs()
+ options = Options()
+ options.parseOptions(['--path', path.path])
+ root = options['root']
+ return path, root
+
+
+ def test_path(self):
+ """
+ The I{--path} option causes L{Options} to create a root resource
+ which serves responses from the specified path.
+ """
+ path, root = self._pathOption()
+ self.assertIsInstance(root, File)
+ self.assertEqual(root.path, path.path)
+
+
+ def test_cgiProcessor(self):
+ """
+ The I{--path} option creates a root resource which serves a
+ L{CGIScript} instance for any child with the C{".cgi"} extension.
+ """
+ path, root = self._pathOption()
+ path.child("foo.cgi").setContent("")
+ self.assertIsInstance(root.getChild("foo.cgi", None), CGIScript)
+
+
+ def test_php3Processor(self):
+ """
+ The I{--path} option creates a root resource which serves a
+ L{PHP3Script} instance for any child with the C{".php3"} extension.
+ """
+ path, root = self._pathOption()
+ path.child("foo.php3").setContent("")
+ self.assertIsInstance(root.getChild("foo.php3", None), PHP3Script)
+
+
+ def test_phpProcessor(self):
+ """
+ The I{--path} option creates a root resource which serves a
+ L{PHPScript} instance for any child with the C{".php"} extension.
+ """
+ path, root = self._pathOption()
+ path.child("foo.php").setContent("")
+ self.assertIsInstance(root.getChild("foo.php", None), PHPScript)
+
+
+ def test_epyProcessor(self):
+ """
+ The I{--path} option creates a root resource which serves a
+ L{PythonScript} instance for any child with the C{".epy"} extension.
+ """
+ path, root = self._pathOption()
+ path.child("foo.epy").setContent("")
+ self.assertIsInstance(root.getChild("foo.epy", None), PythonScript)
+
+
+ def test_rpyProcessor(self):
+ """
+ The I{--path} option creates a root resource which serves the
+ C{resource} global defined by the Python source in any child with
+ the C{".rpy"} extension.
+ """
+ path, root = self._pathOption()
+ path.child("foo.rpy").setContent(
+ "from twisted.web.static import Data\n"
+ "resource = Data('content', 'major/minor')\n")
+ child = root.getChild("foo.rpy", None)
+ self.assertIsInstance(child, Data)
+ self.assertEqual(child.data, 'content')
+ self.assertEqual(child.type, 'major/minor')
+
+
+ def test_trpProcessor(self):
+ """
+ The I{--path} option creates a root resource which serves the
+ pickled resource out of any child with the C{".rpy"} extension.
+ """
+ path, root = self._pathOption()
+ path.child("foo.trp").setContent(pickle.dumps(Data("foo", "bar")))
+ child = root.getChild("foo.trp", None)
+ self.assertIsInstance(child, Data)
+ self.assertEqual(child.data, 'foo')
+ self.assertEqual(child.type, 'bar')
+
+ warnings = self.flushWarnings()
+
+ # If the trp module hadn't been imported before this test ran, there
+ # will be two deprecation warnings; one for the module, one for the
+ # function. If the module has already been imported, the
+ # module-scope deprecation won't be emitted again.
+ if len(warnings) == 2:
+ self.assertEqual(warnings[0]['category'], DeprecationWarning)
+ self.assertEqual(
+ warnings[0]['message'],
+ "twisted.web.trp is deprecated as of Twisted 9.0. Resource "
+ "persistence is beyond the scope of Twisted Web.")
+ warning = warnings[1]
+ else:
+ warning = warnings[0]
+
+ self.assertEqual(warning['category'], DeprecationWarning)
+ self.assertEqual(
+ warning['message'],
+ "twisted.web.trp.ResourceUnpickler is deprecated as of Twisted "
+ "9.0. Resource persistence is beyond the scope of Twisted Web.")
+
+
+ def test_makePersonalServerFactory(self):
+ """
+ L{makePersonalServerFactory} returns a PB server factory which has
+ as its root object a L{ResourcePublisher}.
+ """
+ # The fact that this pile of objects can actually be used somehow is
+ # verified by twisted.web.test.test_distrib.
+ site = Site(Data("foo bar", "text/plain"))
+ serverFactory = makePersonalServerFactory(site)
+ self.assertIsInstance(serverFactory, PBServerFactory)
+ self.assertIsInstance(serverFactory.root, ResourcePublisher)
+ self.assertIdentical(serverFactory.root.site, site)
+
+
+ def test_personalServer(self):
+ """
+ The I{--personal} option to L{makeService} causes it to return a
+ service which will listen on the server address given by the I{--port}
+ option.
+ """
+ port = self.mktemp()
+ options = Options()
+ options.parseOptions(['--port', 'unix:' + port, '--personal'])
+ service = makeService(options)
+ service.startService()
+ self.addCleanup(service.stopService)
+ self.assertTrue(os.path.exists(port))
+ self.assertTrue(stat.S_ISSOCK(os.stat(port).st_mode))
+
+ if not IReactorUNIX.providedBy(reactor):
+ test_personalServer.skip = (
+ "The reactor does not support UNIX domain sockets")
+
+
+ def test_defaultPersonalPath(self):
+ """
+ If the I{--port} option not specified but the I{--personal} option is,
+ L{Options} defaults the port to C{UserDirectory.userSocketName} in the
+ user's home directory.
+ """
+ options = Options()
+ options.parseOptions(['--personal'])
+ path = os.path.expanduser(
+ os.path.join('~', UserDirectory.userSocketName))
+ self.assertEqual(
+ strports.parse(options['port'], None)[:2],
+ ('UNIX', (path, None)))
+
+ if not IReactorUNIX.providedBy(reactor):
+ test_defaultPersonalPath.skip = (
+ "The reactor does not support UNIX domain sockets")
+
+
+ def test_defaultPort(self):
+ """
+ If the I{--port} option is not specified, L{Options} defaults the port
+ to C{8080}.
+ """
+ options = Options()
+ options.parseOptions([])
+ self.assertEqual(
+ strports.parse(options['port'], None)[:2],
+ ('TCP', (8080, None)))
+
+
+ def test_wsgi(self):
+ """
+ The I{--wsgi} option takes the fully-qualifed Python name of a WSGI
+ application object and creates a L{WSGIResource} at the root which
+ serves that application.
+ """
+ options = Options()
+ options.parseOptions(['--wsgi', __name__ + '.application'])
+ root = options['root']
+ self.assertTrue(root, WSGIResource)
+ self.assertIdentical(root._reactor, reactor)
+ self.assertTrue(isinstance(root._threadpool, ThreadPool))
+ self.assertIdentical(root._application, application)
+
+ # The threadpool should start and stop with the reactor.
+ self.assertFalse(root._threadpool.started)
+ reactor.fireSystemEvent('startup')
+ self.assertTrue(root._threadpool.started)
+ self.assertFalse(root._threadpool.joined)
+ reactor.fireSystemEvent('shutdown')
+ self.assertTrue(root._threadpool.joined)
+
+
+ def test_invalidApplication(self):
+ """
+ If I{--wsgi} is given an invalid name, L{Options.parseOptions}
+ raises L{UsageError}.
+ """
+ options = Options()
+ for name in [__name__ + '.nosuchthing', 'foo.']:
+ exc = self.assertRaises(
+ UsageError, options.parseOptions, ['--wsgi', name])
+ self.assertEqual(str(exc), "No such WSGI application: %r" % (name,))
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_vhost.py b/vendor/Twisted-10.0.0/twisted/web/test/test_vhost.py
new file mode 100644
index 0000000000..076d09c670
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_vhost.py
@@ -0,0 +1,105 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web.vhost}.
+"""
+
+from twisted.internet.defer import gatherResults
+from twisted.trial.unittest import TestCase
+from twisted.web.http import NOT_FOUND
+from twisted.web.static import Data
+from twisted.web.vhost import NameVirtualHost
+from twisted.web.test.test_web import DummyRequest
+from twisted.web.test._util import _render
+
+class NameVirtualHostTests(TestCase):
+ """
+ Tests for L{NameVirtualHost}.
+ """
+ def test_renderWithoutHost(self):
+ """
+ L{NameVirtualHost.render} returns the result of rendering the
+ instance's C{default} if it is not C{None} and there is no I{Host}
+ header in the request.
+ """
+ virtualHostResource = NameVirtualHost()
+ virtualHostResource.default = Data("correct result", "")
+ request = DummyRequest([''])
+ self.assertEqual(
+ virtualHostResource.render(request), "correct result")
+
+
+ def test_renderWithoutHostNoDefault(self):
+ """
+ L{NameVirtualHost.render} returns a response with a status of I{NOT
+ FOUND} if the instance's C{default} is C{None} and there is no I{Host}
+ header in the request.
+ """
+ virtualHostResource = NameVirtualHost()
+ request = DummyRequest([''])
+ d = _render(virtualHostResource, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, NOT_FOUND)
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_renderWithHost(self):
+ """
+ L{NameVirtualHost.render} returns the result of rendering the resource
+ which is the value in the instance's C{host} dictionary corresponding
+ to the key indicated by the value of the I{Host} header in the request.
+ """
+ virtualHostResource = NameVirtualHost()
+ virtualHostResource.addHost('example.org', Data("winner", ""))
+
+ request = DummyRequest([''])
+ request.headers['host'] = 'example.org'
+ d = _render(virtualHostResource, request)
+ def cbRendered(ignored, request):
+ self.assertEqual(''.join(request.written), "winner")
+ d.addCallback(cbRendered, request)
+
+ # The port portion of the Host header should not be considered.
+ requestWithPort = DummyRequest([''])
+ requestWithPort.headers['host'] = 'example.org:8000'
+ dWithPort = _render(virtualHostResource, requestWithPort)
+ def cbRendered(ignored, requestWithPort):
+ self.assertEqual(''.join(requestWithPort.written), "winner")
+ dWithPort.addCallback(cbRendered, requestWithPort)
+
+ return gatherResults([d, dWithPort])
+
+
+ def test_renderWithUnknownHost(self):
+ """
+ L{NameVirtualHost.render} returns the result of rendering the
+ instance's C{default} if it is not C{None} and there is no host
+ matching the value of the I{Host} header in the request.
+ """
+ virtualHostResource = NameVirtualHost()
+ virtualHostResource.default = Data("correct data", "")
+ request = DummyRequest([''])
+ request.headers['host'] = 'example.com'
+ d = _render(virtualHostResource, request)
+ def cbRendered(ignored):
+ self.assertEqual(''.join(request.written), "correct data")
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_renderWithUnknownHostNoDefault(self):
+ """
+ L{NameVirtualHost.render} returns a response with a status of I{NOT
+ FOUND} if the instance's C{default} is C{None} and there is no host
+ matching the value of the I{Host} header in the request.
+ """
+ virtualHostResource = NameVirtualHost()
+ request = DummyRequest([''])
+ request.headers['host'] = 'example.com'
+ d = _render(virtualHostResource, request)
+ def cbRendered(ignored):
+ self.assertEqual(request.responseCode, NOT_FOUND)
+ d.addCallback(cbRendered)
+ return d
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_web.py b/vendor/Twisted-10.0.0/twisted/web/test/test_web.py
new file mode 100644
index 0000000000..0fde6ff2fb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_web.py
@@ -0,0 +1,863 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for various parts of L{twisted.web}.
+"""
+
+from cStringIO import StringIO
+
+from zope.interface import implements
+from zope.interface.verify import verifyObject
+
+from twisted.trial import unittest
+from twisted.internet import reactor
+from twisted.internet.address import IPv4Address
+from twisted.internet.defer import Deferred
+from twisted.web import server, resource, util
+from twisted.internet import defer, interfaces, task
+from twisted.web import iweb, http, http_headers
+from twisted.python import log
+
+
+class DummyRequest:
+ """
+ Represents a dummy or fake request.
+
+ @ivar _finishedDeferreds: C{None} or a C{list} of L{Deferreds} which will
+ be called back with C{None} when C{finish} is called or which will be
+ errbacked if C{processingFailed} is called.
+
+ @type headers: C{dict}
+ @ivar headers: A mapping of header name to header value for all request
+ headers.
+
+ @type outgoingHeaders: C{dict}
+ @ivar outgoingHeaders: A mapping of header name to header value for all
+ response headers.
+
+ @type responseCode: C{int}
+ @ivar responseCode: The response code which was passed to
+ C{setResponseCode}.
+
+ @type written: C{list} of C{str}
+ @ivar written: The bytes which have been written to the request.
+ """
+ uri = 'http://dummy/'
+ method = 'GET'
+ client = None
+
+ def registerProducer(self, prod,s):
+ self.go = 1
+ while self.go:
+ prod.resumeProducing()
+
+ def unregisterProducer(self):
+ self.go = 0
+
+
+ def __init__(self, postpath, session=None):
+ self.sitepath = []
+ self.written = []
+ self.finished = 0
+ self.postpath = postpath
+ self.prepath = []
+ self.session = None
+ self.protoSession = session or server.Session(0, self)
+ self.args = {}
+ self.outgoingHeaders = {}
+ self.responseHeaders = http_headers.Headers()
+ self.responseCode = None
+ self.headers = {}
+ self._finishedDeferreds = []
+
+
+ def getHeader(self, name):
+ """
+ Retrieve the value of a request header.
+
+ @type name: C{str}
+ @param name: The name of the request header for which to retrieve the
+ value. Header names are compared case-insensitively.
+
+ @rtype: C{str} or L{NoneType}
+ @return: The value of the specified request header.
+ """
+ return self.headers.get(name.lower(), None)
+
+
+ def setHeader(self, name, value):
+ """TODO: make this assert on write() if the header is content-length
+ """
+ self.outgoingHeaders[name.lower()] = value
+
+ def getSession(self):
+ if self.session:
+ return self.session
+ assert not self.written, "Session cannot be requested after data has been written."
+ self.session = self.protoSession
+ return self.session
+
+
+ def render(self, resource):
+ """
+ Render the given resource as a response to this request.
+
+ This implementation only handles a few of the most common behaviors of
+ resources. It can handle a render method that returns a string or
+ C{NOT_DONE_YET}. It doesn't know anything about the semantics of
+ request methods (eg HEAD) nor how to set any particular headers.
+ Basically, it's largely broken, but sufficient for some tests at least.
+ It should B{not} be expanded to do all the same stuff L{Request} does.
+ Instead, L{DummyRequest} should be phased out and L{Request} (or some
+ other real code factored in a different way) used.
+ """
+ result = resource.render(self)
+ if result is server.NOT_DONE_YET:
+ return
+ self.write(result)
+ self.finish()
+
+
+ def write(self, data):
+ self.written.append(data)
+
+ def notifyFinish(self):
+ """
+ Return a L{Deferred} which is called back with C{None} when the request
+ is finished. This will probably only work if you haven't called
+ C{finish} yet.
+ """
+ finished = Deferred()
+ self._finishedDeferreds.append(finished)
+ return finished
+
+
+ def finish(self):
+ """
+ Record that the request is finished and callback and L{Deferred}s
+ waiting for notification of this.
+ """
+ self.finished = self.finished + 1
+ if self._finishedDeferreds is not None:
+ observers = self._finishedDeferreds
+ self._finishedDeferreds = None
+ for obs in observers:
+ obs.callback(None)
+
+
+ def processingFailed(self, reason):
+ """
+ Errback and L{Deferreds} waiting for finish notification.
+ """
+ if self._finishedDeferreds is not None:
+ observers = self._finishedDeferreds
+ self._finishedDeferreds = None
+ for obs in observers:
+ obs.errback(reason)
+
+
+ def addArg(self, name, value):
+ self.args[name] = [value]
+
+
+ def setResponseCode(self, code, message=None):
+ """
+ Set the HTTP status response code, but takes care that this is called
+ before any data is written.
+ """
+ assert not self.written, "Response code cannot be set after data has been written: %s." % "@@@@".join(self.written)
+ self.responseCode = code
+ self.responseMessage = message
+
+
+ def setLastModified(self, when):
+ assert not self.written, "Last-Modified cannot be set after data has been written: %s." % "@@@@".join(self.written)
+
+
+ def setETag(self, tag):
+ assert not self.written, "ETag cannot be set after data has been written: %s." % "@@@@".join(self.written)
+
+
+ def getClientIP(self):
+ """
+ Return the IPv4 address of the client which made this request, if there
+ is one, otherwise C{None}.
+ """
+ if isinstance(self.client, IPv4Address):
+ return self.client.host
+ return None
+
+
+class ResourceTestCase(unittest.TestCase):
+ def testListEntities(self):
+ r = resource.Resource()
+ self.failUnlessEqual([], r.listEntities())
+
+
+class SimpleResource(resource.Resource):
+ def render(self, request):
+ if http.CACHED in (request.setLastModified(10),
+ request.setETag('MatchingTag')):
+ return ''
+ else:
+ return "correct"
+
+
+class DummyChannel:
+ class TCP:
+ port = 80
+ disconnected = False
+
+ def __init__(self):
+ self.written = StringIO()
+ self.producers = []
+
+ def getPeer(self):
+ return IPv4Address("TCP", '192.168.1.1', 12344)
+
+ def write(self, bytes):
+ assert isinstance(bytes, str)
+ self.written.write(bytes)
+
+ def writeSequence(self, iovec):
+ map(self.write, iovec)
+
+ def getHost(self):
+ return IPv4Address("TCP", '10.0.0.1', self.port)
+
+ def registerProducer(self, producer, streaming):
+ self.producers.append((producer, streaming))
+
+ def loseConnection(self):
+ self.disconnected = True
+
+
+ class SSL(TCP):
+ implements(interfaces.ISSLTransport)
+
+ site = server.Site(resource.Resource())
+
+ def __init__(self):
+ self.transport = self.TCP()
+
+
+ def requestDone(self, request):
+ pass
+
+
+
+class SiteTest(unittest.TestCase):
+ def test_simplestSite(self):
+ """
+ L{Site.getResourceFor} returns the C{""} child of the root resource it
+ is constructed with when processing a request for I{/}.
+ """
+ sres1 = SimpleResource()
+ sres2 = SimpleResource()
+ sres1.putChild("",sres2)
+ site = server.Site(sres1)
+ self.assertIdentical(
+ site.getResourceFor(DummyRequest([''])),
+ sres2, "Got the wrong resource.")
+
+
+
+class SessionTest(unittest.TestCase):
+ """
+ Tests for L{server.Session}.
+ """
+ def setUp(self):
+ """
+ Create a site with one active session using a deterministic, easily
+ controlled clock.
+ """
+ self.clock = task.Clock()
+ self.uid = 'unique'
+ self.site = server.Site(resource.Resource())
+ self.session = server.Session(self.site, self.uid, self.clock)
+ self.site.sessions[self.uid] = self.session
+
+
+ def test_defaultReactor(self):
+ """
+ If not value is passed to L{server.Session.__init__}, the global
+ reactor is used.
+ """
+ session = server.Session(server.Site(resource.Resource()), '123')
+ self.assertIdentical(session._reactor, reactor)
+
+
+ def test_startCheckingExpiration(self):
+ """
+ L{server.Session.startCheckingExpiration} causes the session to expire
+ after L{server.Session.sessionTimeout} seconds without activity.
+ """
+ self.session.startCheckingExpiration()
+
+ # Advance to almost the timeout - nothing should happen.
+ self.clock.advance(self.session.sessionTimeout - 1)
+ self.assertIn(self.uid, self.site.sessions)
+
+ # Advance to the timeout, the session should expire.
+ self.clock.advance(1)
+ self.assertNotIn(self.uid, self.site.sessions)
+
+ # There should be no calls left over, either.
+ self.assertFalse(self.clock.calls)
+
+
+ def test_expire(self):
+ """
+ L{server.Session.expire} expires the session.
+ """
+ self.session.expire()
+ # It should be gone from the session dictionary.
+ self.assertNotIn(self.uid, self.site.sessions)
+ # And there should be no pending delayed calls.
+ self.assertFalse(self.clock.calls)
+
+
+ def test_expireWhileChecking(self):
+ """
+ L{server.Session.expire} expires the session even if the timeout call
+ isn't due yet.
+ """
+ self.session.startCheckingExpiration()
+ self.test_expire()
+
+
+ def test_notifyOnExpire(self):
+ """
+ A function registered with L{server.Session.notifyOnExpire} is called
+ when the session expires.
+ """
+ callbackRan = [False]
+ def expired():
+ callbackRan[0] = True
+ self.session.notifyOnExpire(expired)
+ self.session.expire()
+ self.assertTrue(callbackRan[0])
+
+
+ def test_touch(self):
+ """
+ L{server.Session.touch} updates L{server.Session.lastModified} and
+ delays session timeout.
+ """
+ # Make sure it works before startCheckingExpiration
+ self.clock.advance(3)
+ self.session.touch()
+ self.assertEqual(self.session.lastModified, 3)
+
+ # And after startCheckingExpiration
+ self.session.startCheckingExpiration()
+ self.clock.advance(self.session.sessionTimeout - 1)
+ self.session.touch()
+ self.clock.advance(self.session.sessionTimeout - 1)
+ self.assertIn(self.uid, self.site.sessions)
+
+ # It should have advanced it by just sessionTimeout, no more.
+ self.clock.advance(1)
+ self.assertNotIn(self.uid, self.site.sessions)
+
+
+ def test_startCheckingExpirationParameterDeprecated(self):
+ """
+ L{server.Session.startCheckingExpiration} emits a deprecation warning
+ if it is invoked with a parameter.
+ """
+ self.session.startCheckingExpiration(123)
+ warnings = self.flushWarnings([
+ self.test_startCheckingExpirationParameterDeprecated])
+ self.assertEqual(len(warnings), 1)
+ self.assertEqual(warnings[0]['category'], DeprecationWarning)
+ self.assertEqual(
+ warnings[0]['message'],
+ "The lifetime parameter to startCheckingExpiration is deprecated "
+ "since Twisted 9.0. See Session.sessionTimeout instead.")
+
+
+ def test_checkExpiredDeprecated(self):
+ """
+ L{server.Session.checkExpired} is deprecated.
+ """
+ self.session.checkExpired()
+ warnings = self.flushWarnings([self.test_checkExpiredDeprecated])
+ self.assertEqual(warnings[0]['category'], DeprecationWarning)
+ self.assertEqual(
+ warnings[0]['message'],
+ "Session.checkExpired is deprecated since Twisted 9.0; sessions "
+ "check themselves now, you don't need to.")
+ self.assertEqual(len(warnings), 1)
+
+
+# Conditional requests:
+# If-None-Match, If-Modified-Since
+
+# make conditional request:
+# normal response if condition succeeds
+# if condition fails:
+# response code
+# no body
+
+def httpBody(whole):
+ return whole.split('\r\n\r\n', 1)[1]
+
+def httpHeader(whole, key):
+ key = key.lower()
+ headers = whole.split('\r\n\r\n', 1)[0]
+ for header in headers.split('\r\n'):
+ if header.lower().startswith(key):
+ return header.split(':', 1)[1].strip()
+ return None
+
+def httpCode(whole):
+ l1 = whole.split('\r\n', 1)[0]
+ return int(l1.split()[1])
+
+class ConditionalTest(unittest.TestCase):
+ """
+ web.server's handling of conditional requests for cache validation.
+ """
+
+ # XXX: test web.distrib.
+
+ def setUp(self):
+ self.resrc = SimpleResource()
+ self.resrc.putChild('', self.resrc)
+ self.site = server.Site(self.resrc)
+ self.site = server.Site(self.resrc)
+ self.site.logFile = log.logfile
+
+ # HELLLLLLLLLLP! This harness is Very Ugly.
+ self.channel = self.site.buildProtocol(None)
+ self.transport = http.StringTransport()
+ self.transport.close = lambda *a, **kw: None
+ self.transport.disconnecting = lambda *a, **kw: 0
+ self.transport.getPeer = lambda *a, **kw: "peer"
+ self.transport.getHost = lambda *a, **kw: "host"
+ self.channel.makeConnection(self.transport)
+ for l in ["GET / HTTP/1.1",
+ "Accept: text/html"]:
+ self.channel.lineReceived(l)
+
+ def tearDown(self):
+ self.channel.connectionLost(None)
+
+
+ def _modifiedTest(self, modifiedSince):
+ """
+ Given the value C{modifiedSince} for the I{If-Modified-Since}
+ header, verify that a response with a 200 code and the resource as
+ the body is returned.
+ """
+ self.channel.lineReceived("If-Modified-Since: " + modifiedSince)
+ self.channel.lineReceived('')
+ result = self.transport.getvalue()
+ self.failUnlessEqual(httpCode(result), http.OK)
+ self.failUnlessEqual(httpBody(result), "correct")
+
+
+ def test_modified(self):
+ """
+ If a request is made with an I{If-Modified-Since} header value with
+ a timestamp indicating a time before the last modification of the
+ requested resource, a 200 response is returned along with a response
+ body containing the resource.
+ """
+ self._modifiedTest(http.datetimeToString(1))
+
+
+ def test_unmodified(self):
+ """
+ If a request is made with an I{If-Modified-Since} header value with
+ a timestamp indicating a time after the last modification of the
+ request resource, a 304 response is returned along with an empty
+ response body.
+ """
+ self.channel.lineReceived("If-Modified-Since: %s"
+ % http.datetimeToString(100))
+ self.channel.lineReceived('')
+ result = self.transport.getvalue()
+ self.failUnlessEqual(httpCode(result), http.NOT_MODIFIED)
+ self.failUnlessEqual(httpBody(result), "")
+
+
+ def test_invalidTimestamp(self):
+ """
+ If a request is made with an I{If-Modified-Since} header value which
+ cannot be parsed, the header is treated as not having been present
+ and a normal 200 response is returned with a response body
+ containing the resource.
+ """
+ self._modifiedTest("like, maybe a week ago, I guess?")
+
+
+ def test_invalidTimestampYear(self):
+ """
+ If a request is made with an I{If-Modified-Since} header value which
+ contains a string in the year position which is not an integer, the
+ header is treated as not having been present and a normal 200
+ response is returned with a response body containing the resource.
+ """
+ self._modifiedTest("Thu, 01 Jan blah 00:00:10 GMT")
+
+
+ def test_invalidTimestampTooLongAgo(self):
+ """
+ If a request is made with an I{If-Modified-Since} header value which
+ contains a year before the epoch, the header is treated as not
+ having been present and a normal 200 response is returned with a
+ response body containing the resource.
+ """
+ self._modifiedTest("Thu, 01 Jan 1899 00:00:10 GMT")
+
+
+ def test_invalidTimestampMonth(self):
+ """
+ If a request is made with an I{If-Modified-Since} header value which
+ contains a string in the month position which is not a recognized
+ month abbreviation, the header is treated as not having been present
+ and a normal 200 response is returned with a response body
+ containing the resource.
+ """
+ self._modifiedTest("Thu, 01 Blah 1970 00:00:10 GMT")
+
+
+ def test_etagMatchedNot(self):
+ """If-None-Match ETag cache validator (positive)"""
+ self.channel.lineReceived("If-None-Match: unmatchedTag")
+ self.channel.lineReceived('')
+ result = self.transport.getvalue()
+ self.failUnlessEqual(httpCode(result), http.OK)
+ self.failUnlessEqual(httpBody(result), "correct")
+
+ def test_etagMatched(self):
+ """If-None-Match ETag cache validator (negative)"""
+ self.channel.lineReceived("If-None-Match: MatchingTag")
+ self.channel.lineReceived('')
+ result = self.transport.getvalue()
+ self.failUnlessEqual(httpHeader(result, "ETag"), "MatchingTag")
+ self.failUnlessEqual(httpCode(result), http.NOT_MODIFIED)
+ self.failUnlessEqual(httpBody(result), "")
+
+
+
+from twisted.web import google
+class GoogleTestCase(unittest.TestCase):
+ def testCheckGoogle(self):
+ raise unittest.SkipTest("no violation of google ToS")
+ d = google.checkGoogle('site:www.twistedmatrix.com twisted')
+ d.addCallback(self.assertEquals, 'http://twistedmatrix.com/')
+ return d
+
+
+
+
+
+class RequestTests(unittest.TestCase):
+ """
+ Tests for the HTTP request class, L{server.Request}.
+ """
+
+ def test_interface(self):
+ """
+ L{server.Request} instances provide L{iweb.IRequest}.
+ """
+ self.assertTrue(
+ verifyObject(iweb.IRequest, server.Request(DummyChannel(), True)))
+
+
+ def testChildLink(self):
+ request = server.Request(DummyChannel(), 1)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
+ self.assertEqual(request.childLink('baz'), 'bar/baz')
+ request = server.Request(DummyChannel(), 1)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo/bar/', 'HTTP/1.0')
+ self.assertEqual(request.childLink('baz'), 'baz')
+
+ def testPrePathURLSimple(self):
+ request = server.Request(DummyChannel(), 1)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
+ request.setHost('example.com', 80)
+ self.assertEqual(request.prePathURL(), 'http://example.com/foo/bar')
+
+ def testPrePathURLNonDefault(self):
+ d = DummyChannel()
+ d.transport.port = 81
+ request = server.Request(d, 1)
+ request.setHost('example.com', 81)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
+ self.assertEqual(request.prePathURL(), 'http://example.com:81/foo/bar')
+
+ def testPrePathURLSSLPort(self):
+ d = DummyChannel()
+ d.transport.port = 443
+ request = server.Request(d, 1)
+ request.setHost('example.com', 443)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
+ self.assertEqual(request.prePathURL(), 'http://example.com:443/foo/bar')
+
+ def testPrePathURLSSLPortAndSSL(self):
+ d = DummyChannel()
+ d.transport = DummyChannel.SSL()
+ d.transport.port = 443
+ request = server.Request(d, 1)
+ request.setHost('example.com', 443)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
+ self.assertEqual(request.prePathURL(), 'https://example.com/foo/bar')
+
+ def testPrePathURLHTTPPortAndSSL(self):
+ d = DummyChannel()
+ d.transport = DummyChannel.SSL()
+ d.transport.port = 80
+ request = server.Request(d, 1)
+ request.setHost('example.com', 80)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
+ self.assertEqual(request.prePathURL(), 'https://example.com:80/foo/bar')
+
+ def testPrePathURLSSLNonDefault(self):
+ d = DummyChannel()
+ d.transport = DummyChannel.SSL()
+ d.transport.port = 81
+ request = server.Request(d, 1)
+ request.setHost('example.com', 81)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
+ self.assertEqual(request.prePathURL(), 'https://example.com:81/foo/bar')
+
+ def testPrePathURLSetSSLHost(self):
+ d = DummyChannel()
+ d.transport.port = 81
+ request = server.Request(d, 1)
+ request.setHost('foo.com', 81, 1)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
+ self.assertEqual(request.prePathURL(), 'https://foo.com:81/foo/bar')
+
+
+ def test_prePathURLQuoting(self):
+ """
+ L{Request.prePathURL} quotes special characters in the URL segments to
+ preserve the original meaning.
+ """
+ d = DummyChannel()
+ request = server.Request(d, 1)
+ request.setHost('example.com', 80)
+ request.gotLength(0)
+ request.requestReceived('GET', '/foo%2Fbar', 'HTTP/1.0')
+ self.assertEqual(request.prePathURL(), 'http://example.com/foo%2Fbar')
+
+
+
+class RootResource(resource.Resource):
+ isLeaf=0
+ def getChildWithDefault(self, name, request):
+ request.rememberRootURL()
+ return resource.Resource.getChildWithDefault(self, name, request)
+ def render(self, request):
+ return ''
+
+class RememberURLTest(unittest.TestCase):
+ def createServer(self, r):
+ chan = DummyChannel()
+ chan.site = server.Site(r)
+ return chan
+
+ def testSimple(self):
+ r = resource.Resource()
+ r.isLeaf=0
+ rr = RootResource()
+ r.putChild('foo', rr)
+ rr.putChild('', rr)
+ rr.putChild('bar', resource.Resource())
+ chan = self.createServer(r)
+ for url in ['/foo/', '/foo/bar', '/foo/bar/baz', '/foo/bar/']:
+ request = server.Request(chan, 1)
+ request.setHost('example.com', 81)
+ request.gotLength(0)
+ request.requestReceived('GET', url, 'HTTP/1.0')
+ self.assertEqual(request.getRootURL(), "http://example.com/foo")
+
+ def testRoot(self):
+ rr = RootResource()
+ rr.putChild('', rr)
+ rr.putChild('bar', resource.Resource())
+ chan = self.createServer(rr)
+ for url in ['/', '/bar', '/bar/baz', '/bar/']:
+ request = server.Request(chan, 1)
+ request.setHost('example.com', 81)
+ request.gotLength(0)
+ request.requestReceived('GET', url, 'HTTP/1.0')
+ self.assertEqual(request.getRootURL(), "http://example.com/")
+
+
+class NewRenderResource(resource.Resource):
+ def render_GET(self, request):
+ return "hi hi"
+
+ def render_HEH(self, request):
+ return "ho ho"
+
+
+class NewRenderTestCase(unittest.TestCase):
+ def _getReq(self):
+ d = DummyChannel()
+ d.site.resource.putChild('newrender', NewRenderResource())
+ d.transport.port = 81
+ request = server.Request(d, 1)
+ request.setHost('example.com', 81)
+ request.gotLength(0)
+ return request
+
+ def testGoodMethods(self):
+ req = self._getReq()
+ req.requestReceived('GET', '/newrender', 'HTTP/1.0')
+ self.assertEquals(req.transport.getvalue().splitlines()[-1], 'hi hi')
+
+ req = self._getReq()
+ req.requestReceived('HEH', '/newrender', 'HTTP/1.0')
+ self.assertEquals(req.transport.getvalue().splitlines()[-1], 'ho ho')
+
+ def testBadMethods(self):
+ req = self._getReq()
+ req.requestReceived('CONNECT', '/newrender', 'HTTP/1.0')
+ self.assertEquals(req.code, 501)
+
+ req = self._getReq()
+ req.requestReceived('hlalauguG', '/newrender', 'HTTP/1.0')
+ self.assertEquals(req.code, 501)
+
+ def testImplicitHead(self):
+ req = self._getReq()
+ req.requestReceived('HEAD', '/newrender', 'HTTP/1.0')
+ self.assertEquals(req.code, 200)
+ self.assertEquals(-1, req.transport.getvalue().find('hi hi'))
+
+
+
+class SDResource(resource.Resource):
+ def __init__(self,default):
+ self.default = default
+
+
+ def getChildWithDefault(self, name, request):
+ d = defer.succeed(self.default)
+ resource = util.DeferredResource(d)
+ return resource.getChildWithDefault(name, request)
+
+
+
+class DeferredResourceTests(unittest.TestCase):
+ """
+ Tests for L{DeferredResource}.
+ """
+
+ def testDeferredResource(self):
+ r = resource.Resource()
+ r.isLeaf = 1
+ s = SDResource(r)
+ d = DummyRequest(['foo', 'bar', 'baz'])
+ resource.getChildForRequest(s, d)
+ self.assertEqual(d.postpath, ['bar', 'baz'])
+
+
+ def test_render(self):
+ """
+ L{DeferredResource} uses the request object's C{render} method to
+ render the resource which is the result of the L{Deferred} being
+ handled.
+ """
+ rendered = []
+ request = DummyRequest([])
+ request.render = rendered.append
+
+ result = resource.Resource()
+ deferredResource = util.DeferredResource(defer.succeed(result))
+ deferredResource.render(request)
+ self.assertEquals(rendered, [result])
+
+
+
+class DummyRequestForLogTest(DummyRequest):
+ uri = '/dummy' # parent class uri has "http://", which doesn't really happen
+ code = 123
+
+ clientproto = 'HTTP/1.0'
+ sentLength = None
+ client = IPv4Address('TCP', '1.2.3.4', 12345)
+
+
+
+class TestLogEscaping(unittest.TestCase):
+ def setUp(self):
+ self.site = http.HTTPFactory()
+ self.site.logFile = StringIO()
+ self.request = DummyRequestForLogTest(self.site, False)
+
+ def testSimple(self):
+ http._logDateTime = "[%02d/%3s/%4d:%02d:%02d:%02d +0000]" % (
+ 25, 'Oct', 2004, 12, 31, 59)
+ self.site.log(self.request)
+ self.site.logFile.seek(0)
+ self.assertEqual(
+ self.site.logFile.read(),
+ '1.2.3.4 - - [25/Oct/2004:12:31:59 +0000] "GET /dummy HTTP/1.0" 123 - "-" "-"\n')
+
+ def testMethodQuote(self):
+ http._logDateTime = "[%02d/%3s/%4d:%02d:%02d:%02d +0000]" % (
+ 25, 'Oct', 2004, 12, 31, 59)
+ self.request.method = 'G"T'
+ self.site.log(self.request)
+ self.site.logFile.seek(0)
+ self.assertEqual(
+ self.site.logFile.read(),
+ '1.2.3.4 - - [25/Oct/2004:12:31:59 +0000] "G\\"T /dummy HTTP/1.0" 123 - "-" "-"\n')
+
+ def testRequestQuote(self):
+ http._logDateTime = "[%02d/%3s/%4d:%02d:%02d:%02d +0000]" % (
+ 25, 'Oct', 2004, 12, 31, 59)
+ self.request.uri='/dummy"withquote'
+ self.site.log(self.request)
+ self.site.logFile.seek(0)
+ self.assertEqual(
+ self.site.logFile.read(),
+ '1.2.3.4 - - [25/Oct/2004:12:31:59 +0000] "GET /dummy\\"withquote HTTP/1.0" 123 - "-" "-"\n')
+
+ def testProtoQuote(self):
+ http._logDateTime = "[%02d/%3s/%4d:%02d:%02d:%02d +0000]" % (
+ 25, 'Oct', 2004, 12, 31, 59)
+ self.request.clientproto='HT"P/1.0'
+ self.site.log(self.request)
+ self.site.logFile.seek(0)
+ self.assertEqual(
+ self.site.logFile.read(),
+ '1.2.3.4 - - [25/Oct/2004:12:31:59 +0000] "GET /dummy HT\\"P/1.0" 123 - "-" "-"\n')
+
+ def testRefererQuote(self):
+ http._logDateTime = "[%02d/%3s/%4d:%02d:%02d:%02d +0000]" % (
+ 25, 'Oct', 2004, 12, 31, 59)
+ self.request.headers['referer'] = 'http://malicious" ".website.invalid'
+ self.site.log(self.request)
+ self.site.logFile.seek(0)
+ self.assertEqual(
+ self.site.logFile.read(),
+ '1.2.3.4 - - [25/Oct/2004:12:31:59 +0000] "GET /dummy HTTP/1.0" 123 - "http://malicious\\" \\".website.invalid" "-"\n')
+
+ def testUserAgentQuote(self):
+ http._logDateTime = "[%02d/%3s/%4d:%02d:%02d:%02d +0000]" % (
+ 25, 'Oct', 2004, 12, 31, 59)
+ self.request.headers['user-agent'] = 'Malicious Web" Evil'
+ self.site.log(self.request)
+ self.site.logFile.seek(0)
+ self.assertEqual(
+ self.site.logFile.read(),
+ '1.2.3.4 - - [25/Oct/2004:12:31:59 +0000] "GET /dummy HTTP/1.0" 123 - "-" "Malicious Web\\" Evil"\n')
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_webclient.py b/vendor/Twisted-10.0.0/twisted/web/test/test_webclient.py
new file mode 100644
index 0000000000..314f3e739c
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_webclient.py
@@ -0,0 +1,1060 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web.client}.
+"""
+
+import os
+from errno import ENOSPC
+
+from urlparse import urlparse
+
+from twisted.trial import unittest
+from twisted.web import server, static, client, error, util, resource, http_headers
+from twisted.internet import reactor, defer, interfaces
+from twisted.python.filepath import FilePath
+from twisted.python.log import msg
+from twisted.protocols.policies import WrappingFactory
+from twisted.test.proto_helpers import StringTransport
+from twisted.test.proto_helpers import MemoryReactor
+from twisted.internet.address import IPv4Address
+from twisted.internet.task import Clock
+from twisted.internet.error import ConnectionRefusedError
+from twisted.internet.protocol import Protocol
+from twisted.internet.defer import Deferred
+from twisted.web.client import Request
+from twisted.web.error import SchemeNotSupported
+
+try:
+ from twisted.internet import ssl
+except:
+ ssl = None
+
+
+
+class ExtendedRedirect(resource.Resource):
+ """
+ Redirection resource.
+
+ The HTTP status code is set according to the C{code} query parameter.
+
+ @type lastMethod: C{str}
+ @ivar lastMethod: Last handled HTTP request method
+ """
+ isLeaf = 1
+ lastMethod = None
+
+
+ def __init__(self, url):
+ resource.Resource.__init__(self)
+ self.url = url
+
+
+ def render(self, request):
+ if self.lastMethod:
+ self.lastMethod = request.method
+ return "OK Thnx!"
+ else:
+ self.lastMethod = request.method
+ code = int(request.args['code'][0])
+ return self.redirectTo(self.url, request, code)
+
+
+ def getChild(self, name, request):
+ return self
+
+
+ def redirectTo(self, url, request, code):
+ request.setResponseCode(code)
+ request.setHeader("location", url)
+ return "OK Bye!"
+
+
+
+class ForeverTakingResource(resource.Resource):
+ """
+ L{ForeverTakingResource} is a resource which never finishes responding
+ to requests.
+ """
+ def __init__(self, write=False):
+ resource.Resource.__init__(self)
+ self._write = write
+
+ def render(self, request):
+ if self._write:
+ request.write('some bytes')
+ return server.NOT_DONE_YET
+
+
+class CookieMirrorResource(resource.Resource):
+ def render(self, request):
+ l = []
+ for k,v in request.received_cookies.items():
+ l.append((k, v))
+ l.sort()
+ return repr(l)
+
+class RawCookieMirrorResource(resource.Resource):
+ def render(self, request):
+ return repr(request.getHeader('cookie'))
+
+class ErrorResource(resource.Resource):
+
+ def render(self, request):
+ request.setResponseCode(401)
+ if request.args.get("showlength"):
+ request.setHeader("content-length", "0")
+ return ""
+
+class NoLengthResource(resource.Resource):
+
+ def render(self, request):
+ return "nolength"
+
+
+
+class HostHeaderResource(resource.Resource):
+ """
+ A testing resource which renders itself as the value of the host header
+ from the request.
+ """
+ def render(self, request):
+ return request.received_headers['host']
+
+
+
+class PayloadResource(resource.Resource):
+ """
+ A testing resource which renders itself as the contents of the request body
+ as long as the request body is 100 bytes long, otherwise which renders
+ itself as C{"ERROR"}.
+ """
+ def render(self, request):
+ data = request.content.read()
+ contentLength = request.received_headers['content-length']
+ if len(data) != 100 or int(contentLength) != 100:
+ return "ERROR"
+ return data
+
+
+
+class BrokenDownloadResource(resource.Resource):
+
+ def render(self, request):
+ # only sends 3 bytes even though it claims to send 5
+ request.setHeader("content-length", "5")
+ request.write('abc')
+ return ''
+
+class CountingRedirect(util.Redirect):
+ """
+ A L{util.Redirect} resource that keeps track of the number of times the
+ resource has been accessed.
+ """
+ def __init__(self, *a, **kw):
+ util.Redirect.__init__(self, *a, **kw)
+ self.count = 0
+
+ def render(self, request):
+ self.count += 1
+ return util.Redirect.render(self, request)
+
+
+
+class ParseUrlTestCase(unittest.TestCase):
+ """
+ Test URL parsing facility and defaults values.
+ """
+
+ def test_parse(self):
+ """
+ L{client._parse} correctly parses a URL into its various components.
+ """
+ # The default port for HTTP is 80.
+ self.assertEqual(
+ client._parse('http://127.0.0.1/'),
+ ('http', '127.0.0.1', 80, '/'))
+
+ # The default port for HTTPS is 443.
+ self.assertEqual(
+ client._parse('https://127.0.0.1/'),
+ ('https', '127.0.0.1', 443, '/'))
+
+ # Specifying a port.
+ self.assertEqual(
+ client._parse('http://spam:12345/'),
+ ('http', 'spam', 12345, '/'))
+
+ # Weird (but commonly accepted) structure uses default port.
+ self.assertEqual(
+ client._parse('http://spam:/'),
+ ('http', 'spam', 80, '/'))
+
+ # Spaces in the hostname are trimmed, the default path is /.
+ self.assertEqual(
+ client._parse('http://foo '),
+ ('http', 'foo', 80, '/'))
+
+
+ def test_externalUnicodeInterference(self):
+ """
+ L{client._parse} should return C{str} for the scheme, host, and path
+ elements of its return tuple, even when passed an URL which has
+ previously been passed to L{urlparse} as a C{unicode} string.
+ """
+ badInput = u'http://example.com/path'
+ goodInput = badInput.encode('ascii')
+ urlparse(badInput)
+ scheme, host, port, path = client._parse(goodInput)
+ self.assertTrue(isinstance(scheme, str))
+ self.assertTrue(isinstance(host, str))
+ self.assertTrue(isinstance(path, str))
+
+
+
+class HTTPPageGetterTests(unittest.TestCase):
+ """
+ Tests for L{HTTPPagerGetter}, the HTTP client protocol implementation
+ used to implement L{getPage}.
+ """
+ def test_earlyHeaders(self):
+ """
+ When a connection is made, L{HTTPPagerGetter} sends the headers from
+ its factory's C{headers} dict. If I{Host} or I{Content-Length} is
+ present in this dict, the values are not sent, since they are sent with
+ special values before the C{headers} dict is processed. If
+ I{User-Agent} is present in the dict, it overrides the value of the
+ C{agent} attribute of the factory. If I{Cookie} is present in the
+ dict, its value is added to the values from the factory's C{cookies}
+ attribute.
+ """
+ factory = client.HTTPClientFactory(
+ 'http://foo/bar',
+ agent="foobar",
+ cookies={'baz': 'quux'},
+ postdata="some data",
+ headers={
+ 'Host': 'example.net',
+ 'User-Agent': 'fooble',
+ 'Cookie': 'blah blah',
+ 'Content-Length': '12981',
+ 'Useful': 'value'})
+ transport = StringTransport()
+ protocol = client.HTTPPageGetter()
+ protocol.factory = factory
+ protocol.makeConnection(transport)
+ self.assertEqual(
+ transport.value(),
+ "GET /bar HTTP/1.0\r\n"
+ "Host: example.net\r\n"
+ "User-Agent: foobar\r\n"
+ "Content-Length: 9\r\n"
+ "Useful: value\r\n"
+ "connection: close\r\n"
+ "Cookie: blah blah; baz=quux\r\n"
+ "\r\n"
+ "some data")
+
+
+
+class WebClientTestCase(unittest.TestCase):
+ def _listen(self, site):
+ return reactor.listenTCP(0, site, interface="127.0.0.1")
+
+ def setUp(self):
+ self.cleanupServerConnections = 0
+ name = self.mktemp()
+ os.mkdir(name)
+ FilePath(name).child("file").setContent("0123456789")
+ r = static.File(name)
+ r.putChild("redirect", util.Redirect("/file"))
+ self.infiniteRedirectResource = CountingRedirect("/infiniteRedirect")
+ r.putChild("infiniteRedirect", self.infiniteRedirectResource)
+ r.putChild("wait", ForeverTakingResource())
+ r.putChild("write-then-wait", ForeverTakingResource(write=True))
+ r.putChild("error", ErrorResource())
+ r.putChild("nolength", NoLengthResource())
+ r.putChild("host", HostHeaderResource())
+ r.putChild("payload", PayloadResource())
+ r.putChild("broken", BrokenDownloadResource())
+ r.putChild("cookiemirror", CookieMirrorResource())
+
+ miscasedHead = static.Data("miscased-head GET response content", "major/minor")
+ miscasedHead.render_Head = lambda request: "miscased-head content"
+ r.putChild("miscased-head", miscasedHead)
+
+ self.extendedRedirect = ExtendedRedirect('/extendedRedirect')
+ r.putChild("extendedRedirect", self.extendedRedirect)
+ self.site = server.Site(r, timeout=None)
+ self.wrapper = WrappingFactory(self.site)
+ self.port = self._listen(self.wrapper)
+ self.portno = self.port.getHost().port
+
+ def tearDown(self):
+ # If the test indicated it might leave some server-side connections
+ # around, clean them up.
+ connections = self.wrapper.protocols.keys()
+ # If there are fewer server-side connections than requested,
+ # that's okay. Some might have noticed that the client closed
+ # the connection and cleaned up after themselves.
+ for n in range(min(len(connections), self.cleanupServerConnections)):
+ proto = connections.pop()
+ msg("Closing %r" % (proto,))
+ proto.transport.loseConnection()
+ if connections:
+ msg("Some left-over connections; this test is probably buggy.")
+ return self.port.stopListening()
+
+ def getURL(self, path):
+ return "http://127.0.0.1:%d/%s" % (self.portno, path)
+
+ def testPayload(self):
+ s = "0123456789" * 10
+ return client.getPage(self.getURL("payload"), postdata=s
+ ).addCallback(self.assertEquals, s
+ )
+
+
+ def test_getPageBrokenDownload(self):
+ """
+ If the connection is closed before the number of bytes indicated by
+ I{Content-Length} have been received, the L{Deferred} returned by
+ L{getPage} fails with L{PartialDownloadError}.
+ """
+ d = client.getPage(self.getURL("broken"))
+ d = self.assertFailure(d, client.PartialDownloadError)
+ d.addCallback(lambda exc: self.assertEquals(exc.response, "abc"))
+ return d
+
+
+ def test_downloadPageBrokenDownload(self):
+ """
+ If the connection is closed before the number of bytes indicated by
+ I{Content-Length} have been received, the L{Deferred} returned by
+ L{downloadPage} fails with L{PartialDownloadError}.
+ """
+ # test what happens when download gets disconnected in the middle
+ path = FilePath(self.mktemp())
+ d = client.downloadPage(self.getURL("broken"), path.path)
+ d = self.assertFailure(d, client.PartialDownloadError)
+
+ def checkResponse(response):
+ """
+ The HTTP status code from the server is propagated through the
+ C{PartialDownloadError}.
+ """
+ self.assertEquals(response.status, "200")
+ self.assertEquals(response.message, "OK")
+ return response
+ d.addCallback(checkResponse)
+
+ def cbFailed(ignored):
+ self.assertEquals(path.getContent(), "abc")
+ d.addCallback(cbFailed)
+ return d
+
+
+ def test_downloadPageLogsFileCloseError(self):
+ """
+ If there is an exception closing the file being written to after the
+ connection is prematurely closed, that exception is logged.
+ """
+ class BrokenFile:
+ def write(self, bytes):
+ pass
+
+ def close(self):
+ raise IOError(ENOSPC, "No file left on device")
+
+ d = client.downloadPage(self.getURL("broken"), BrokenFile())
+ d = self.assertFailure(d, client.PartialDownloadError)
+ def cbFailed(ignored):
+ self.assertEquals(len(self.flushLoggedErrors(IOError)), 1)
+ d.addCallback(cbFailed)
+ return d
+
+
+ def testHostHeader(self):
+ # if we pass Host header explicitly, it should be used, otherwise
+ # it should extract from url
+ return defer.gatherResults([
+ client.getPage(self.getURL("host")).addCallback(self.assertEquals, "127.0.0.1"),
+ client.getPage(self.getURL("host"), headers={"Host": "www.example.com"}).addCallback(self.assertEquals, "www.example.com")])
+
+
+ def test_getPage(self):
+ """
+ L{client.getPage} returns a L{Deferred} which is called back with
+ the body of the response if the default method B{GET} is used.
+ """
+ d = client.getPage(self.getURL("file"))
+ d.addCallback(self.assertEquals, "0123456789")
+ return d
+
+
+ def test_getPageHEAD(self):
+ """
+ L{client.getPage} returns a L{Deferred} which is called back with
+ the empty string if the method is I{HEAD} and there is a successful
+ response code.
+ """
+ d = client.getPage(self.getURL("file"), method="HEAD")
+ d.addCallback(self.assertEquals, "")
+ return d
+
+
+
+ def test_getPageNotQuiteHEAD(self):
+ """
+ If the request method is a different casing of I{HEAD} (ie, not all
+ capitalized) then it is not a I{HEAD} request and the response body
+ is returned.
+ """
+ d = client.getPage(self.getURL("miscased-head"), method='Head')
+ d.addCallback(self.assertEquals, "miscased-head content")
+ return d
+
+
+ def test_timeoutNotTriggering(self):
+ """
+ When a non-zero timeout is passed to L{getPage} and the page is
+ retrieved before the timeout period elapses, the L{Deferred} is
+ called back with the contents of the page.
+ """
+ d = client.getPage(self.getURL("host"), timeout=100)
+ d.addCallback(self.assertEquals, "127.0.0.1")
+ return d
+
+
+ def test_timeoutTriggering(self):
+ """
+ When a non-zero timeout is passed to L{getPage} and that many
+ seconds elapse before the server responds to the request. the
+ L{Deferred} is errbacked with a L{error.TimeoutError}.
+ """
+ # This will probably leave some connections around.
+ self.cleanupServerConnections = 1
+ return self.assertFailure(
+ client.getPage(self.getURL("wait"), timeout=0.000001),
+ defer.TimeoutError)
+
+
+ def testDownloadPage(self):
+ downloads = []
+ downloadData = [("file", self.mktemp(), "0123456789"),
+ ("nolength", self.mktemp(), "nolength")]
+
+ for (url, name, data) in downloadData:
+ d = client.downloadPage(self.getURL(url), name)
+ d.addCallback(self._cbDownloadPageTest, data, name)
+ downloads.append(d)
+ return defer.gatherResults(downloads)
+
+ def _cbDownloadPageTest(self, ignored, data, name):
+ bytes = file(name, "rb").read()
+ self.assertEquals(bytes, data)
+
+ def testDownloadPageError1(self):
+ class errorfile:
+ def write(self, data):
+ raise IOError, "badness happened during write"
+ def close(self):
+ pass
+ ef = errorfile()
+ return self.assertFailure(
+ client.downloadPage(self.getURL("file"), ef),
+ IOError)
+
+ def testDownloadPageError2(self):
+ class errorfile:
+ def write(self, data):
+ pass
+ def close(self):
+ raise IOError, "badness happened during close"
+ ef = errorfile()
+ return self.assertFailure(
+ client.downloadPage(self.getURL("file"), ef),
+ IOError)
+
+ def testDownloadPageError3(self):
+ # make sure failures in open() are caught too. This is tricky.
+ # Might only work on posix.
+ tmpfile = open("unwritable", "wb")
+ tmpfile.close()
+ os.chmod("unwritable", 0) # make it unwritable (to us)
+ d = self.assertFailure(
+ client.downloadPage(self.getURL("file"), "unwritable"),
+ IOError)
+ d.addBoth(self._cleanupDownloadPageError3)
+ return d
+
+ def _cleanupDownloadPageError3(self, ignored):
+ os.chmod("unwritable", 0700)
+ os.unlink("unwritable")
+ return ignored
+
+ def _downloadTest(self, method):
+ dl = []
+ for (url, code) in [("nosuchfile", "404"), ("error", "401"),
+ ("error?showlength=1", "401")]:
+ d = method(url)
+ d = self.assertFailure(d, error.Error)
+ d.addCallback(lambda exc, code=code: self.assertEquals(exc.args[0], code))
+ dl.append(d)
+ return defer.DeferredList(dl, fireOnOneErrback=True)
+
+ def testServerError(self):
+ return self._downloadTest(lambda url: client.getPage(self.getURL(url)))
+
+ def testDownloadServerError(self):
+ return self._downloadTest(lambda url: client.downloadPage(self.getURL(url), url.split('?')[0]))
+
+ def testFactoryInfo(self):
+ url = self.getURL('file')
+ scheme, host, port, path = client._parse(url)
+ factory = client.HTTPClientFactory(url)
+ reactor.connectTCP(host, port, factory)
+ return factory.deferred.addCallback(self._cbFactoryInfo, factory)
+
+ def _cbFactoryInfo(self, ignoredResult, factory):
+ self.assertEquals(factory.status, '200')
+ self.assert_(factory.version.startswith('HTTP/'))
+ self.assertEquals(factory.message, 'OK')
+ self.assertEquals(factory.response_headers['content-length'][0], '10')
+
+
+ def testRedirect(self):
+ return client.getPage(self.getURL("redirect")).addCallback(self._cbRedirect)
+
+ def _cbRedirect(self, pageData):
+ self.assertEquals(pageData, "0123456789")
+ d = self.assertFailure(
+ client.getPage(self.getURL("redirect"), followRedirect=0),
+ error.PageRedirect)
+ d.addCallback(self._cbCheckLocation)
+ return d
+
+ def _cbCheckLocation(self, exc):
+ self.assertEquals(exc.location, "/file")
+
+
+ def test_infiniteRedirection(self):
+ """
+ When more than C{redirectLimit} HTTP redirects are encountered, the
+ page request fails with L{InfiniteRedirection}.
+ """
+ def checkRedirectCount(*a):
+ self.assertEquals(f._redirectCount, 13)
+ self.assertEquals(self.infiniteRedirectResource.count, 13)
+
+ f = client._makeGetterFactory(
+ self.getURL('infiniteRedirect'),
+ client.HTTPClientFactory,
+ redirectLimit=13)
+ d = self.assertFailure(f.deferred, error.InfiniteRedirection)
+ d.addCallback(checkRedirectCount)
+ return d
+
+
+ def test_isolatedFollowRedirect(self):
+ """
+ C{client.HTTPPagerGetter} instances each obey the C{followRedirect}
+ value passed to the L{client.getPage} call which created them.
+ """
+ d1 = client.getPage(self.getURL('redirect'), followRedirect=True)
+ d2 = client.getPage(self.getURL('redirect'), followRedirect=False)
+
+ d = self.assertFailure(d2, error.PageRedirect
+ ).addCallback(lambda dummy: d1)
+ return d
+
+
+ def test_afterFoundGet(self):
+ """
+ Enabling unsafe redirection behaviour overwrites the method of
+ redirected C{POST} requests with C{GET}.
+ """
+ url = self.getURL('extendedRedirect?code=302')
+ f = client.HTTPClientFactory(url, followRedirect=True, method="POST")
+ self.assertFalse(
+ f.afterFoundGet,
+ "By default, afterFoundGet must be disabled")
+
+ def gotPage(page):
+ self.assertEquals(
+ self.extendedRedirect.lastMethod,
+ "GET",
+ "With afterFoundGet, the HTTP method must change to GET")
+
+ d = client.getPage(
+ url, followRedirect=True, afterFoundGet=True, method="POST")
+ d.addCallback(gotPage)
+ return d
+
+
+ def testPartial(self):
+ name = self.mktemp()
+ f = open(name, "wb")
+ f.write("abcd")
+ f.close()
+
+ partialDownload = [(True, "abcd456789"),
+ (True, "abcd456789"),
+ (False, "0123456789")]
+
+ d = defer.succeed(None)
+ for (partial, expectedData) in partialDownload:
+ d.addCallback(self._cbRunPartial, name, partial)
+ d.addCallback(self._cbPartialTest, expectedData, name)
+
+ return d
+
+ testPartial.skip = "Cannot test until webserver can serve partial data properly"
+
+ def _cbRunPartial(self, ignored, name, partial):
+ return client.downloadPage(self.getURL("file"), name, supportPartial=partial)
+
+ def _cbPartialTest(self, ignored, expectedData, filename):
+ bytes = file(filename, "rb").read()
+ self.assertEquals(bytes, expectedData)
+
+
+ def test_downloadTimeout(self):
+ """
+ If the timeout indicated by the C{timeout} parameter to
+ L{client.HTTPDownloader.__init__} elapses without the complete response
+ being received, the L{defer.Deferred} returned by
+ L{client.downloadPage} fires with a L{Failure} wrapping a
+ L{defer.TimeoutError}.
+ """
+ self.cleanupServerConnections = 2
+ # Verify the behavior if no bytes are ever written.
+ first = client.downloadPage(
+ self.getURL("wait"),
+ self.mktemp(), timeout=0.01)
+
+ # Verify the behavior if some bytes are written but then the request
+ # never completes.
+ second = client.downloadPage(
+ self.getURL("write-then-wait"),
+ self.mktemp(), timeout=0.01)
+
+ return defer.gatherResults([
+ self.assertFailure(first, defer.TimeoutError),
+ self.assertFailure(second, defer.TimeoutError)])
+
+
+ def test_downloadHeaders(self):
+ """
+ After L{client.HTTPDownloader.deferred} fires, the
+ L{client.HTTPDownloader} instance's C{status} and C{response_headers}
+ attributes are populated with the values from the response.
+ """
+ def checkHeaders(factory):
+ self.assertEquals(factory.status, '200')
+ self.assertEquals(factory.response_headers['content-type'][0], 'text/html')
+ self.assertEquals(factory.response_headers['content-length'][0], '10')
+ os.unlink(factory.fileName)
+ factory = client._makeGetterFactory(
+ self.getURL('file'),
+ client.HTTPDownloader,
+ fileOrName=self.mktemp())
+ return factory.deferred.addCallback(lambda _: checkHeaders(factory))
+
+
+ def test_downloadCookies(self):
+ """
+ The C{cookies} dict passed to the L{client.HTTPDownloader}
+ initializer is used to populate the I{Cookie} header included in the
+ request sent to the server.
+ """
+ output = self.mktemp()
+ factory = client._makeGetterFactory(
+ self.getURL('cookiemirror'),
+ client.HTTPDownloader,
+ fileOrName=output,
+ cookies={'foo': 'bar'})
+ def cbFinished(ignored):
+ self.assertEqual(
+ FilePath(output).getContent(),
+ "[('foo', 'bar')]")
+ factory.deferred.addCallback(cbFinished)
+ return factory.deferred
+
+
+ def test_downloadRedirectLimit(self):
+ """
+ When more than C{redirectLimit} HTTP redirects are encountered, the
+ page request fails with L{InfiniteRedirection}.
+ """
+ def checkRedirectCount(*a):
+ self.assertEquals(f._redirectCount, 7)
+ self.assertEquals(self.infiniteRedirectResource.count, 7)
+
+ f = client._makeGetterFactory(
+ self.getURL('infiniteRedirect'),
+ client.HTTPDownloader,
+ fileOrName=self.mktemp(),
+ redirectLimit=7)
+ d = self.assertFailure(f.deferred, error.InfiniteRedirection)
+ d.addCallback(checkRedirectCount)
+ return d
+
+
+
+class WebClientSSLTestCase(WebClientTestCase):
+ def _listen(self, site):
+ from twisted import test
+ return reactor.listenSSL(0, site,
+ contextFactory=ssl.DefaultOpenSSLContextFactory(
+ FilePath(test.__file__).sibling('server.pem').path,
+ FilePath(test.__file__).sibling('server.pem').path,
+ ),
+ interface="127.0.0.1")
+
+ def getURL(self, path):
+ return "https://127.0.0.1:%d/%s" % (self.portno, path)
+
+ def testFactoryInfo(self):
+ url = self.getURL('file')
+ scheme, host, port, path = client._parse(url)
+ factory = client.HTTPClientFactory(url)
+ reactor.connectSSL(host, port, factory, ssl.ClientContextFactory())
+ # The base class defines _cbFactoryInfo correctly for this
+ return factory.deferred.addCallback(self._cbFactoryInfo, factory)
+
+class WebClientRedirectBetweenSSLandPlainText(unittest.TestCase):
+ def getHTTPS(self, path):
+ return "https://127.0.0.1:%d/%s" % (self.tlsPortno, path)
+
+ def getHTTP(self, path):
+ return "http://127.0.0.1:%d/%s" % (self.plainPortno, path)
+
+ def setUp(self):
+ plainRoot = static.Data('not me', 'text/plain')
+ tlsRoot = static.Data('me neither', 'text/plain')
+
+ plainSite = server.Site(plainRoot, timeout=None)
+ tlsSite = server.Site(tlsRoot, timeout=None)
+
+ from twisted import test
+ self.tlsPort = reactor.listenSSL(0, tlsSite,
+ contextFactory=ssl.DefaultOpenSSLContextFactory(
+ FilePath(test.__file__).sibling('server.pem').path,
+ FilePath(test.__file__).sibling('server.pem').path,
+ ),
+ interface="127.0.0.1")
+ self.plainPort = reactor.listenTCP(0, plainSite, interface="127.0.0.1")
+
+ self.plainPortno = self.plainPort.getHost().port
+ self.tlsPortno = self.tlsPort.getHost().port
+
+ plainRoot.putChild('one', util.Redirect(self.getHTTPS('two')))
+ tlsRoot.putChild('two', util.Redirect(self.getHTTP('three')))
+ plainRoot.putChild('three', util.Redirect(self.getHTTPS('four')))
+ tlsRoot.putChild('four', static.Data('FOUND IT!', 'text/plain'))
+
+ def tearDown(self):
+ ds = map(defer.maybeDeferred,
+ [self.plainPort.stopListening, self.tlsPort.stopListening])
+ return defer.gatherResults(ds)
+
+ def testHoppingAround(self):
+ return client.getPage(self.getHTTP("one")
+ ).addCallback(self.assertEquals, "FOUND IT!"
+ )
+
+class FakeTransport:
+ disconnecting = False
+ def __init__(self):
+ self.data = []
+ def write(self, stuff):
+ self.data.append(stuff)
+
+class CookieTestCase(unittest.TestCase):
+ def _listen(self, site):
+ return reactor.listenTCP(0, site, interface="127.0.0.1")
+
+ def setUp(self):
+ root = static.Data('El toro!', 'text/plain')
+ root.putChild("cookiemirror", CookieMirrorResource())
+ root.putChild("rawcookiemirror", RawCookieMirrorResource())
+ site = server.Site(root, timeout=None)
+ self.port = self._listen(site)
+ self.portno = self.port.getHost().port
+
+ def tearDown(self):
+ return self.port.stopListening()
+
+ def getHTTP(self, path):
+ return "http://127.0.0.1:%d/%s" % (self.portno, path)
+
+ def testNoCookies(self):
+ return client.getPage(self.getHTTP("cookiemirror")
+ ).addCallback(self.assertEquals, "[]"
+ )
+
+ def testSomeCookies(self):
+ cookies = {'foo': 'bar', 'baz': 'quux'}
+ return client.getPage(self.getHTTP("cookiemirror"), cookies=cookies
+ ).addCallback(self.assertEquals, "[('baz', 'quux'), ('foo', 'bar')]"
+ )
+
+ def testRawNoCookies(self):
+ return client.getPage(self.getHTTP("rawcookiemirror")
+ ).addCallback(self.assertEquals, "None"
+ )
+
+ def testRawSomeCookies(self):
+ cookies = {'foo': 'bar', 'baz': 'quux'}
+ return client.getPage(self.getHTTP("rawcookiemirror"), cookies=cookies
+ ).addCallback(self.assertEquals, "'foo=bar; baz=quux'"
+ )
+
+ def testCookieHeaderParsing(self):
+ factory = client.HTTPClientFactory('http://foo.example.com/')
+ proto = factory.buildProtocol('127.42.42.42')
+ proto.transport = FakeTransport()
+ proto.connectionMade()
+ for line in [
+ '200 Ok',
+ 'Squash: yes',
+ 'Hands: stolen',
+ 'Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT',
+ 'Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/',
+ 'Set-Cookie: SHIPPING=FEDEX; path=/foo',
+ '',
+ 'body',
+ 'more body',
+ ]:
+ proto.dataReceived(line + '\r\n')
+ self.assertEquals(proto.transport.data,
+ ['GET / HTTP/1.0\r\n',
+ 'Host: foo.example.com\r\n',
+ 'User-Agent: Twisted PageGetter\r\n',
+ '\r\n'])
+ self.assertEquals(factory.cookies,
+ {
+ 'CUSTOMER': 'WILE_E_COYOTE',
+ 'PART_NUMBER': 'ROCKET_LAUNCHER_0001',
+ 'SHIPPING': 'FEDEX',
+ })
+
+
+
+class StubHTTPProtocol(Protocol):
+ """
+ A protocol like L{HTTP11ClientProtocol} but which does not actually know
+ HTTP/1.1 and only collects requests in a list.
+
+ @ivar requests: A C{list} of two-tuples. Each time a request is made, a
+ tuple consisting of the request and the L{Deferred} returned from the
+ request method is appended to this list.
+ """
+ def __init__(self):
+ self.requests = []
+
+
+ def request(self, request):
+ """
+ Capture the given request for later inspection.
+
+ @return: A L{Deferred} which this code will never fire.
+ """
+ result = Deferred()
+ self.requests.append((request, result))
+ return result
+
+
+
+class AgentTests(unittest.TestCase):
+ """
+ Tests for the new HTTP client API provided by L{Agent}.
+ """
+ def setUp(self):
+ """
+ Create an L{Agent} wrapped around a fake reactor.
+ """
+ class Reactor(MemoryReactor, Clock):
+ def __init__(self):
+ MemoryReactor.__init__(self)
+ Clock.__init__(self)
+
+ self.reactor = Reactor()
+ self.agent = client.Agent(self.reactor)
+
+
+ def completeConnection(self):
+ """
+ Do whitebox stuff to finish any outstanding connection attempts the
+ agent may have initiated.
+
+ This spins the fake reactor clock just enough to get L{ClientCreator},
+ which agent is implemented in terms of, to fire its Deferreds.
+ """
+ self.reactor.advance(0)
+
+
+ def _verifyAndCompleteConnectionTo(self, host, port):
+ """
+ Assert that the destination of the oldest unverified TCP connection
+ attempt is the given host and port. Then pop it, create a protocol,
+ connect it to a L{StringTransport}, and return the protocol.
+ """
+ # Grab the connection attempt, make sure it goes to the right place,
+ # and cause it to succeed.
+ host, port, factory = self.reactor.tcpClients.pop()[:3]
+ self.assertEquals(host, host)
+ self.assertEquals(port, port)
+
+ protocol = factory.buildProtocol(IPv4Address('TCP', '10.0.0.3', 1234))
+ transport = StringTransport()
+ protocol.makeConnection(transport)
+ self.completeConnection()
+ return protocol
+
+
+ def test_unsupportedScheme(self):
+ """
+ L{Agent.request} returns a L{Deferred} which fails with
+ L{SchemeNotSupported} if the scheme of the URI passed to it is not
+ C{'http'}.
+ """
+ return self.assertFailure(
+ self.agent.request('GET', 'mailto:alice@example.com'),
+ SchemeNotSupported)
+
+
+ def test_connectionFailed(self):
+ """
+ The L{Deferred} returned by L{Agent.request} fires with a L{Failure} if
+ the TCP connection attempt fails.
+ """
+ result = self.agent.request('GET', 'http://foo/')
+
+ # Cause the connection to be refused
+ host, port, factory = self.reactor.tcpClients.pop()[:3]
+ factory.clientConnectionFailed(None, ConnectionRefusedError())
+ self.completeConnection()
+
+ return self.assertFailure(result, ConnectionRefusedError)
+
+
+ def test_request(self):
+ """
+ L{Agent.request} establishes a new connection to the host indicated by
+ the host part of the URI passed to it and issues a request using the
+ method, the path portion of the URI, the headers, and the body producer
+ passed to it. It returns a L{Deferred} which fires with a L{Response}
+ from the server.
+ """
+ self.agent._protocol = StubHTTPProtocol
+
+ headers = http_headers.Headers({'foo': ['bar']})
+ # Just going to check the body for identity, so it doesn't need to be
+ # real.
+ body = object()
+ self.agent.request(
+ 'GET', 'http://example.com:1234/foo?bar', headers, body)
+
+ protocol = self._verifyAndCompleteConnectionTo('example.com', 1234)
+
+ # The request should be issued.
+ self.assertEquals(len(protocol.requests), 1)
+ req, res = protocol.requests.pop()
+ self.assertTrue(isinstance(req, Request))
+ self.assertEquals(req.method, 'GET')
+ self.assertEquals(req.uri, '/foo?bar')
+ self.assertEquals(
+ req.headers,
+ http_headers.Headers({'foo': ['bar'],
+ 'host': ['example.com:1234']}))
+ self.assertIdentical(req.bodyProducer, body)
+
+
+ def test_hostProvided(self):
+ """
+ If C{None} is passed to L{Agent.request} for the C{headers}
+ parameter, a L{Headers} instance is created for the request and a
+ I{Host} header added to it.
+ """
+ self.agent._protocol = StubHTTPProtocol
+
+ self.agent.request('GET', 'http://example.com/foo')
+
+ protocol = self._verifyAndCompleteConnectionTo('example.com', 80)
+
+ # The request should have been issued with a host header based on
+ # the request URL.
+ self.assertEquals(len(protocol.requests), 1)
+ req, res = protocol.requests.pop()
+ self.assertEquals(req.headers.getRawHeaders('host'), ['example.com'])
+
+
+ def test_hostOverride(self):
+ """
+ If the headers passed to L{Agent.request} includes a value for the
+ I{Host} header, that value takes precedence over the one which would
+ otherwise be automatically provided.
+ """
+ self.agent._protocol = StubHTTPProtocol
+
+ headers = http_headers.Headers({'foo': ['bar'], 'host': ['quux']})
+ body = object()
+ self.agent.request(
+ 'GET', 'http://example.com/baz', headers, body)
+
+ protocol = self._verifyAndCompleteConnectionTo('example.com', 80)
+
+ # The request should have been issued with the host header specified
+ # above, not one based on the request URI.
+ self.assertEquals(len(protocol.requests), 1)
+ req, res = protocol.requests.pop()
+ self.assertEquals(req.headers.getRawHeaders('host'), ['quux'])
+
+
+ def test_headersUnmodified(self):
+ """
+ If a I{Host} header must be added to the request, the L{Headers}
+ instance passed to L{Agent.request} is not modified.
+ """
+ self.agent._protocol = StubHTTPProtocol
+
+ headers = http_headers.Headers()
+ body = object()
+ self.agent.request(
+ 'GET', 'http://example.com/foo', headers, body)
+
+ protocol = self._verifyAndCompleteConnectionTo('example.com', 80)
+
+ # The request should have been issued.
+ self.assertEquals(len(protocol.requests), 1)
+ # And the headers object passed in should not have changed.
+ self.assertEquals(headers, http_headers.Headers())
+
+
+ def test_hostValue(self):
+ """
+ L{Agent._computeHostValue} returns just the hostname it is passed if
+ the port number it is passed is the default for the scheme it is
+ passed, otherwise it returns a string containing both the host and port
+ separated by C{":"}.
+ """
+ self.assertEquals(
+ self.agent._computeHostValue('http', 'example.com', 80),
+ 'example.com')
+
+ self.assertEquals(
+ self.agent._computeHostValue('http', 'example.com', 54321),
+ 'example.com:54321')
+
+
+
+if ssl is None or not hasattr(ssl, 'DefaultOpenSSLContextFactory'):
+ for case in [WebClientSSLTestCase, WebClientRedirectBetweenSSLandPlainText]:
+ case.skip = "OpenSSL not present"
+
+if not interfaces.IReactorSSL(reactor, None):
+ for case in [WebClientSSLTestCase, WebClientRedirectBetweenSSLandPlainText]:
+ case.skip = "Reactor doesn't support SSL"
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_wsgi.py b/vendor/Twisted-10.0.0/twisted/web/test/test_wsgi.py
new file mode 100644
index 0000000000..f3bc25d3ad
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_wsgi.py
@@ -0,0 +1,1572 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.web.wsgi}.
+"""
+
+__metaclass__ = type
+
+from sys import exc_info
+from urllib import quote
+from thread import get_ident
+import StringIO, cStringIO, tempfile
+
+from zope.interface.verify import verifyObject
+
+from twisted.python.compat import set
+from twisted.python.log import addObserver, removeObserver, err
+from twisted.python.failure import Failure
+from twisted.python.threadpool import ThreadPool
+from twisted.internet.defer import Deferred, gatherResults
+from twisted.internet import reactor
+from twisted.internet.error import ConnectionLost
+from twisted.trial.unittest import TestCase
+from twisted.web import http
+from twisted.web.resource import IResource, Resource
+from twisted.web.server import Request, Site, version
+from twisted.web.wsgi import WSGIResource
+from twisted.web.test.test_web import DummyChannel
+
+
+class SynchronousThreadPool:
+ """
+ A single-threaded implementation of part of the L{ThreadPool} interface.
+ This implementation calls functions synchronously rather than running
+ them in a thread pool. It is used to make the tests which are not
+ directly for thread-related behavior deterministic.
+ """
+ def callInThread(self, f, *a, **kw):
+ """
+ Call C{f(*a, **kw)} in this thread rather than scheduling it to be
+ called in a thread.
+ """
+ try:
+ f(*a, **kw)
+ except:
+ # callInThread doesn't let exceptions propagate to the caller.
+ # None is always returned and any exception raised gets logged
+ # later on.
+ err(None, "Callable passed to SynchronousThreadPool.callInThread failed")
+
+
+
+class SynchronousReactorThreads:
+ """
+ A single-threaded implementation of part of the L{IReactorThreads}
+ interface. This implementation assumes that it will only be invoked
+ from the reactor thread, so it calls functions synchronously rather than
+ trying to schedule them to run in the reactor thread. It is used in
+ conjunction with L{SynchronousThreadPool} to make the tests which are
+ not directly for thread-related behavior deterministic.
+ """
+ def callFromThread(self, f, *a, **kw):
+ """
+ Call C{f(*a, **kw)} in this thread which should also be the reactor
+ thread.
+ """
+ f(*a, **kw)
+
+
+
+class WSGIResourceTests(TestCase):
+ def setUp(self):
+ """
+ Create a L{WSGIResource} with synchronous threading objects and a no-op
+ application object. This is useful for testing certain things about
+ the resource implementation which are unrelated to WSGI.
+ """
+ self.resource = WSGIResource(
+ SynchronousReactorThreads(), SynchronousThreadPool(),
+ lambda environ, startResponse: None)
+
+
+ def test_interfaces(self):
+ """
+ L{WSGIResource} implements L{IResource} and stops resource traversal.
+ """
+ verifyObject(IResource, self.resource)
+ self.assertTrue(self.resource.isLeaf)
+
+
+ def test_unsupported(self):
+ """
+ A L{WSGIResource} cannot have L{IResource} children. Its
+ C{getChildWithDefault} and C{putChild} methods raise L{RuntimeError}.
+ """
+ self.assertRaises(
+ RuntimeError,
+ self.resource.getChildWithDefault,
+ "foo", Request(DummyChannel(), False))
+ self.assertRaises(
+ RuntimeError,
+ self.resource.putChild,
+ "foo", Resource())
+
+
+class WSGITestsMixin:
+ """
+ @ivar channelFactory: A no-argument callable which will be invoked to
+ create a new HTTP channel to associate with request objects.
+ """
+ channelFactory = DummyChannel
+
+ def setUp(self):
+ self.threadpool = SynchronousThreadPool()
+ self.reactor = SynchronousReactorThreads()
+
+
+ def lowLevelRender(
+ self, requestFactory, applicationFactory, channelFactory, method,
+ version, resourceSegments, requestSegments, query=None, headers=[],
+ body=None, safe=''):
+ """
+ @param method: A C{str} giving the request method to use.
+
+ @param version: A C{str} like C{'1.1'} giving the request version.
+
+ @param resourceSegments: A C{list} of unencoded path segments which
+ specifies the location in the resource hierarchy at which the
+ L{WSGIResource} will be placed, eg C{['']} for I{/}, C{['foo',
+ 'bar', '']} for I{/foo/bar/}, etc.
+
+ @param requestSegments: A C{list} of unencoded path segments giving the
+ request URI.
+
+ @param query: A C{list} of two-tuples of C{str} giving unencoded query
+ argument keys and values.
+
+ @param headers: A C{list} of two-tuples of C{str} giving request header
+ names and corresponding values.
+
+ @param safe: A C{str} giving the bytes which are to be considered
+ I{safe} for inclusion in the request URI and not quoted.
+
+ @return: A L{Deferred} which will be called back with a two-tuple of
+ the arguments passed which would be passed to the WSGI application
+ object for this configuration and request (ie, the environment and
+ start_response callable).
+ """
+ root = WSGIResource(
+ self.reactor, self.threadpool, applicationFactory())
+ resourceSegments.reverse()
+ for seg in resourceSegments:
+ tmp = Resource()
+ tmp.putChild(seg, root)
+ root = tmp
+
+ channel = channelFactory()
+ channel.site = Site(root)
+ request = requestFactory(channel, False)
+ for k, v in headers:
+ request.requestHeaders.addRawHeader(k, v)
+ request.gotLength(0)
+ if body:
+ request.content.write(body)
+ request.content.seek(0)
+ uri = '/' + '/'.join([quote(seg, safe) for seg in requestSegments])
+ if query is not None:
+ uri += '?' + '&'.join(['='.join([quote(k, safe), quote(v, safe)])
+ for (k, v) in query])
+ request.requestReceived(method, uri, 'HTTP/' + version)
+ return request
+
+
+ def render(self, *a, **kw):
+ result = Deferred()
+ def applicationFactory():
+ def application(*args):
+ environ, startResponse = args
+ result.callback(args)
+ startResponse('200 OK', [])
+ return iter(())
+ return application
+ self.lowLevelRender(
+ Request, applicationFactory, self.channelFactory, *a, **kw)
+ return result
+
+
+ def requestFactoryFactory(self, requestClass=Request):
+ d = Deferred()
+ def requestFactory(*a, **kw):
+ request = requestClass(*a, **kw)
+ # If notifyFinish is called after lowLevelRender returns, it won't
+ # do the right thing, because the request will have already
+ # finished. One might argue that this is a bug in
+ # Request.notifyFinish.
+ request.notifyFinish().chainDeferred(d)
+ return request
+ return d, requestFactory
+
+
+ def getContentFromResponse(self, response):
+ return response.split('\r\n\r\n', 1)[1]
+
+
+
+class EnvironTests(WSGITestsMixin, TestCase):
+ """
+ Tests for the values in the C{environ} C{dict} passed to the application
+ object by L{twisted.web.wsgi.WSGIResource}.
+ """
+ def environKeyEqual(self, key, value):
+ def assertEnvironKeyEqual((environ, startResponse)):
+ self.assertEqual(environ[key], value)
+ return assertEnvironKeyEqual
+
+
+ def test_environIsDict(self):
+ """
+ L{WSGIResource} calls the application object with an C{environ}
+ parameter which is exactly of type C{dict}.
+ """
+ d = self.render('GET', '1.1', [], [''])
+ def cbRendered((environ, startResponse)):
+ self.assertIdentical(type(environ), dict)
+ d.addCallback(cbRendered)
+ return d
+
+
+ def test_requestMethod(self):
+ """
+ The C{'REQUEST_METHOD'} key of the C{environ} C{dict} passed to the
+ application contains the HTTP method in the request (RFC 3875, section
+ 4.1.12).
+ """
+ get = self.render('GET', '1.1', [], [''])
+ get.addCallback(self.environKeyEqual('REQUEST_METHOD', 'GET'))
+
+ # Also make sure a different request method shows up as a different
+ # value in the environ dict.
+ post = self.render('POST', '1.1', [], [''])
+ post.addCallback(self.environKeyEqual('REQUEST_METHOD', 'POST'))
+
+ return gatherResults([get, post])
+
+
+ def test_scriptName(self):
+ """
+ The C{'SCRIPT_NAME'} key of the C{environ} C{dict} passed to the
+ application contains the I{abs_path} (RFC 2396, section 3) to this
+ resource (RFC 3875, section 4.1.13).
+ """
+ root = self.render('GET', '1.1', [], [''])
+ root.addCallback(self.environKeyEqual('SCRIPT_NAME', ''))
+
+ emptyChild = self.render('GET', '1.1', [''], [''])
+ emptyChild.addCallback(self.environKeyEqual('SCRIPT_NAME', '/'))
+
+ leaf = self.render('GET', '1.1', ['foo'], ['foo'])
+ leaf.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo'))
+
+ container = self.render('GET', '1.1', ['foo', ''], ['foo', ''])
+ container.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo/'))
+
+ internal = self.render('GET', '1.1', ['foo'], ['foo', 'bar'])
+ internal.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo'))
+
+ unencoded = self.render(
+ 'GET', '1.1', ['foo', '/', 'bar\xff'], ['foo', '/', 'bar\xff'])
+ # The RFC says "(not URL-encoded)", even though that makes
+ # interpretation of SCRIPT_NAME ambiguous.
+ unencoded.addCallback(
+ self.environKeyEqual('SCRIPT_NAME', '/foo///bar\xff'))
+
+ return gatherResults([
+ root, emptyChild, leaf, container, internal, unencoded])
+
+
+ def test_pathInfo(self):
+ """
+ The C{'PATH_INFO'} key of the C{environ} C{dict} passed to the
+ application contains the suffix of the request URI path which is not
+ included in the value for the C{'SCRIPT_NAME'} key (RFC 3875, section
+ 4.1.5).
+ """
+ assertKeyEmpty = self.environKeyEqual('PATH_INFO', '')
+
+ root = self.render('GET', '1.1', [], [''])
+ root.addCallback(self.environKeyEqual('PATH_INFO', '/'))
+
+ emptyChild = self.render('GET', '1.1', [''], [''])
+ emptyChild.addCallback(assertKeyEmpty)
+
+ leaf = self.render('GET', '1.1', ['foo'], ['foo'])
+ leaf.addCallback(assertKeyEmpty)
+
+ container = self.render('GET', '1.1', ['foo', ''], ['foo', ''])
+ container.addCallback(assertKeyEmpty)
+
+ internalLeaf = self.render('GET', '1.1', ['foo'], ['foo', 'bar'])
+ internalLeaf.addCallback(self.environKeyEqual('PATH_INFO', '/bar'))
+
+ internalContainer = self.render('GET', '1.1', ['foo'], ['foo', ''])
+ internalContainer.addCallback(self.environKeyEqual('PATH_INFO', '/'))
+
+ unencoded = self.render('GET', '1.1', [], ['foo', '/', 'bar\xff'])
+ unencoded.addCallback(
+ self.environKeyEqual('PATH_INFO', '/foo///bar\xff'))
+
+ return gatherResults([
+ root, leaf, container, internalLeaf,
+ internalContainer, unencoded])
+
+
+ def test_queryString(self):
+ """
+ The C{'QUERY_STRING'} key of the C{environ} C{dict} passed to the
+ application contains the portion of the request URI after the first
+ I{?} (RFC 3875, section 4.1.7).
+ """
+ missing = self.render('GET', '1.1', [], [''], None)
+ missing.addCallback(self.environKeyEqual('QUERY_STRING', ''))
+
+ empty = self.render('GET', '1.1', [], [''], [])
+ empty.addCallback(self.environKeyEqual('QUERY_STRING', ''))
+
+ present = self.render('GET', '1.1', [], [''], [('foo', 'bar')])
+ present.addCallback(self.environKeyEqual('QUERY_STRING', 'foo=bar'))
+
+ unencoded = self.render('GET', '1.1', [], [''], [('/', '/')])
+ unencoded.addCallback(self.environKeyEqual('QUERY_STRING', '%2F=%2F'))
+
+ # "?" is reserved in the <searchpart> portion of a URL. However, it
+ # seems to be a common mistake of clients to forget to quote it. So,
+ # make sure we handle that invalid case.
+ doubleQuestion = self.render(
+ 'GET', '1.1', [], [''], [('foo', '?bar')], safe='?')
+ doubleQuestion.addCallback(
+ self.environKeyEqual('QUERY_STRING', 'foo=?bar'))
+
+ return gatherResults([
+ missing, empty, present, unencoded, doubleQuestion])
+
+
+ def test_contentType(self):
+ """
+ The C{'CONTENT_TYPE'} key of the C{environ} C{dict} passed to the
+ application contains the value of the I{Content-Type} request header
+ (RFC 3875, section 4.1.3).
+ """
+ missing = self.render('GET', '1.1', [], [''])
+ missing.addCallback(self.environKeyEqual('CONTENT_TYPE', ''))
+
+ present = self.render(
+ 'GET', '1.1', [], [''], None, [('content-type', 'x-foo/bar')])
+ present.addCallback(self.environKeyEqual('CONTENT_TYPE', 'x-foo/bar'))
+
+ return gatherResults([missing, present])
+
+
+ def test_contentLength(self):
+ """
+ The C{'CONTENT_LENGTH'} key of the C{environ} C{dict} passed to the
+ application contains the value of the I{Content-Length} request header
+ (RFC 3875, section 4.1.2).
+ """
+ missing = self.render('GET', '1.1', [], [''])
+ missing.addCallback(self.environKeyEqual('CONTENT_LENGTH', ''))
+
+ present = self.render(
+ 'GET', '1.1', [], [''], None, [('content-length', '1234')])
+ present.addCallback(self.environKeyEqual('CONTENT_LENGTH', '1234'))
+
+ return gatherResults([missing, present])
+
+
+ def test_serverName(self):
+ """
+ The C{'SERVER_NAME'} key of the C{environ} C{dict} passed to the
+ application contains the best determination of the server hostname
+ possible, using either the value of the I{Host} header in the request
+ or the address the server is listening on if that header is not
+ present (RFC 3875, section 4.1.14).
+ """
+ missing = self.render('GET', '1.1', [], [''])
+ # 10.0.0.1 value comes from a bit far away -
+ # twisted.test.test_web.DummyChannel.transport.getHost().host
+ missing.addCallback(self.environKeyEqual('SERVER_NAME', '10.0.0.1'))
+
+ present = self.render(
+ 'GET', '1.1', [], [''], None, [('host', 'example.org')])
+ present.addCallback(self.environKeyEqual('SERVER_NAME', 'example.org'))
+
+ return gatherResults([missing, present])
+
+
+ def test_serverPort(self):
+ """
+ The C{'SERVER_PORT'} key of the C{environ} C{dict} passed to the
+ application contains the port number of the server which received the
+ request (RFC 3875, section 4.1.15).
+ """
+ portNumber = 12354
+ def makeChannel():
+ channel = DummyChannel()
+ channel.transport = DummyChannel.TCP()
+ channel.transport.port = portNumber
+ return channel
+ self.channelFactory = makeChannel
+
+ d = self.render('GET', '1.1', [], [''])
+ d.addCallback(self.environKeyEqual('SERVER_PORT', str(portNumber)))
+ return d
+
+
+ def test_serverProtocol(self):
+ """
+ The C{'SERVER_PROTOCOL'} key of the C{environ} C{dict} passed to the
+ application contains the HTTP version number received in the request
+ (RFC 3875, section 4.1.16).
+ """
+ old = self.render('GET', '1.0', [], [''])
+ old.addCallback(self.environKeyEqual('SERVER_PROTOCOL', 'HTTP/1.0'))
+
+ new = self.render('GET', '1.1', [], [''])
+ new.addCallback(self.environKeyEqual('SERVER_PROTOCOL', 'HTTP/1.1'))
+
+ return gatherResults([old, new])
+
+
+ def test_remoteAddr(self):
+ """
+ The C{'REMOTE_ADDR'} key of the C{environ} C{dict} passed to the
+ application contains the address of the client making the request.
+ """
+ d = self.render('GET', '1.1', [], [''])
+ d.addCallback(self.environKeyEqual('REMOTE_ADDR', '192.168.1.1'))
+
+ return d
+
+ def test_headers(self):
+ """
+ HTTP request headers are copied into the C{environ} C{dict} passed to
+ the application with a C{HTTP_} prefix added to their names.
+ """
+ singleValue = self.render(
+ 'GET', '1.1', [], [''], None, [('foo', 'bar'), ('baz', 'quux')])
+ def cbRendered((environ, startResponse)):
+ self.assertEqual(environ['HTTP_FOO'], 'bar')
+ self.assertEqual(environ['HTTP_BAZ'], 'quux')
+ singleValue.addCallback(cbRendered)
+
+ multiValue = self.render(
+ 'GET', '1.1', [], [''], None, [('foo', 'bar'), ('foo', 'baz')])
+ multiValue.addCallback(self.environKeyEqual('HTTP_FOO', 'bar,baz'))
+
+ withHyphen = self.render(
+ 'GET', '1.1', [], [''], None, [('foo-bar', 'baz')])
+ withHyphen.addCallback(self.environKeyEqual('HTTP_FOO_BAR', 'baz'))
+
+ multiLine = self.render(
+ 'GET', '1.1', [], [''], None, [('foo', 'bar\n\tbaz')])
+ multiLine.addCallback(self.environKeyEqual('HTTP_FOO', 'bar \tbaz'))
+
+ return gatherResults([singleValue, multiValue, withHyphen, multiLine])
+
+
+ def test_wsgiVersion(self):
+ """
+ The C{'wsgi.version'} key of the C{environ} C{dict} passed to the
+ application has the value C{(1, 0)} indicating that this is a WSGI 1.0
+ container.
+ """
+ versionDeferred = self.render('GET', '1.1', [], [''])
+ versionDeferred.addCallback(self.environKeyEqual('wsgi.version', (1, 0)))
+ return versionDeferred
+
+
+ def test_wsgiRunOnce(self):
+ """
+ The C{'wsgi.run_once'} key of the C{environ} C{dict} passed to the
+ application is set to C{False}.
+ """
+ once = self.render('GET', '1.1', [], [''])
+ once.addCallback(self.environKeyEqual('wsgi.run_once', False))
+ return once
+
+
+ def test_wsgiMultithread(self):
+ """
+ The C{'wsgi.multithread'} key of the C{environ} C{dict} passed to the
+ application is set to C{True}.
+ """
+ thread = self.render('GET', '1.1', [], [''])
+ thread.addCallback(self.environKeyEqual('wsgi.multithread', True))
+ return thread
+
+
+ def test_wsgiMultiprocess(self):
+ """
+ The C{'wsgi.multiprocess'} key of the C{environ} C{dict} passed to the
+ application is set to C{False}.
+ """
+ process = self.render('GET', '1.1', [], [''])
+ process.addCallback(self.environKeyEqual('wsgi.multiprocess', False))
+ return process
+
+
+ def test_wsgiURLScheme(self):
+ """
+ The C{'wsgi.url_scheme'} key of the C{environ} C{dict} passed to the
+ application has the request URL scheme.
+ """
+ # XXX Does this need to be different if the request is for an absolute
+ # URL?
+ def channelFactory():
+ channel = DummyChannel()
+ channel.transport = DummyChannel.SSL()
+ return channel
+
+ self.channelFactory = DummyChannel
+ httpDeferred = self.render('GET', '1.1', [], [''])
+ httpDeferred.addCallback(self.environKeyEqual('wsgi.url_scheme', 'http'))
+
+ self.channelFactory = channelFactory
+ httpsDeferred = self.render('GET', '1.1', [], [''])
+ httpsDeferred.addCallback(self.environKeyEqual('wsgi.url_scheme', 'https'))
+
+ return gatherResults([httpDeferred, httpsDeferred])
+
+
+ def test_wsgiErrors(self):
+ """
+ The C{'wsgi.errors'} key of the C{environ} C{dict} passed to the
+ application is a file-like object (as defined in the U{Input and Errors
+ Streams<http://www.python.org/dev/peps/pep-0333/#input-and-error-streams>}
+ section of PEP 333) which converts bytes written to it into events for
+ the logging system.
+ """
+ events = []
+ addObserver(events.append)
+ self.addCleanup(removeObserver, events.append)
+
+ errors = self.render('GET', '1.1', [], [''])
+ def cbErrors((environ, startApplication)):
+ errors = environ['wsgi.errors']
+ errors.write('some message\n')
+ errors.writelines(['another\nmessage\n'])
+ errors.flush()
+ self.assertEqual(events[0]['message'], ('some message\n',))
+ self.assertEqual(events[0]['system'], 'wsgi')
+ self.assertTrue(events[0]['isError'])
+ self.assertEqual(events[1]['message'], ('another\nmessage\n',))
+ self.assertEqual(events[1]['system'], 'wsgi')
+ self.assertTrue(events[1]['isError'])
+ self.assertEqual(len(events), 2)
+ errors.addCallback(cbErrors)
+ return errors
+
+
+class InputStreamTestMixin(WSGITestsMixin):
+ """
+ A mixin for L{TestCase} subclasses which defines a number of tests against
+ L{_InputStream}. The subclass is expected to create a file-like object to
+ be wrapped by an L{_InputStream} under test.
+ """
+ def getFileType(self):
+ raise NotImplementedError(
+ "%s.getFile must be implemented" % (self.__class__.__name__,))
+
+
+ def _renderAndReturnReaderResult(self, reader, content):
+ contentType = self.getFileType()
+ class CustomizedRequest(Request):
+ def gotLength(self, length):
+ # Always allocate a file of the specified type, instead of
+ # using the base behavior of selecting one depending on the
+ # length.
+ self.content = contentType()
+
+ def appFactoryFactory(reader):
+ result = Deferred()
+ def applicationFactory():
+ def application(*args):
+ environ, startResponse = args
+ result.callback(reader(environ['wsgi.input']))
+ startResponse('200 OK', [])
+ return iter(())
+ return application
+ return result, applicationFactory
+ d, appFactory = appFactoryFactory(reader)
+ self.lowLevelRender(
+ CustomizedRequest, appFactory, DummyChannel,
+ 'PUT', '1.1', [], [''], None, [],
+ content)
+ return d
+
+
+ def test_readAll(self):
+ """
+ Calling L{_InputStream.read} with no arguments returns the entire input
+ stream.
+ """
+ bytes = "some bytes are here"
+ d = self._renderAndReturnReaderResult(lambda input: input.read(), bytes)
+ d.addCallback(self.assertEquals, bytes)
+ return d
+
+
+ def test_readSome(self):
+ """
+ Calling L{_InputStream.read} with an integer returns that many bytes
+ from the input stream, as long as it is less than or equal to the total
+ number of bytes available.
+ """
+ bytes = "hello, world."
+ d = self._renderAndReturnReaderResult(lambda input: input.read(3), bytes)
+ d.addCallback(self.assertEquals, "hel")
+ return d
+
+
+ def test_readMoreThan(self):
+ """
+ Calling L{_InputStream.read} with an integer that is greater than the
+ total number of bytes in the input stream returns all bytes in the
+ input stream.
+ """
+ bytes = "some bytes are here"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.read(len(bytes) + 3), bytes)
+ d.addCallback(self.assertEquals, bytes)
+ return d
+
+
+ def test_readTwice(self):
+ """
+ Calling L{_InputStream.read} a second time returns bytes starting from
+ the position after the last byte returned by the previous read.
+ """
+ bytes = "some bytes, hello"
+ def read(input):
+ input.read(3)
+ return input.read()
+ d = self._renderAndReturnReaderResult(read, bytes)
+ d.addCallback(self.assertEquals, bytes[3:])
+ return d
+
+
+ def test_readNone(self):
+ """
+ Calling L{_InputStream.read} with C{None} as an argument returns all
+ bytes in the input stream.
+ """
+ bytes = "the entire stream"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.read(None), bytes)
+ d.addCallback(self.assertEquals, bytes)
+ return d
+
+
+ def test_readNegative(self):
+ """
+ Calling L{_InputStream.read} with a negative integer as an argument
+ returns all bytes in the input stream.
+ """
+ bytes = "all of the input"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.read(-1), bytes)
+ d.addCallback(self.assertEquals, bytes)
+ return d
+
+
+ def test_readline(self):
+ """
+ Calling L{_InputStream.readline} with no argument returns one line from
+ the input stream.
+ """
+ bytes = "hello\nworld"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.readline(), bytes)
+ d.addCallback(self.assertEquals, "hello\n")
+ return d
+
+
+ def test_readlineSome(self):
+ """
+ Calling L{_InputStream.readline} with an integer returns at most that
+ many bytes, even if it is not enough to make up a complete line.
+
+ COMPATIBILITY NOTE: the size argument is excluded from the WSGI
+ specification, but is provided here anyhow, because useful libraries
+ such as python stdlib's cgi.py assume their input file-like-object
+ supports readline with a size argument. If you use it, be aware your
+ application may not be portable to other conformant WSGI servers.
+ """
+ bytes = "goodbye\nworld"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.readline(3), bytes)
+ d.addCallback(self.assertEquals, "goo")
+ return d
+
+
+ def test_readlineMoreThan(self):
+ """
+ Calling L{_InputStream.readline} with an integer which is greater than
+ the number of bytes in the next line returns only the next line.
+ """
+ bytes = "some lines\nof text"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.readline(20), bytes)
+ d.addCallback(self.assertEquals, "some lines\n")
+ return d
+
+
+ def test_readlineTwice(self):
+ """
+ Calling L{_InputStream.readline} a second time returns the line
+ following the line returned by the first call.
+ """
+ bytes = "first line\nsecond line\nlast line"
+ def readline(input):
+ input.readline()
+ return input.readline()
+ d = self._renderAndReturnReaderResult(readline, bytes)
+ d.addCallback(self.assertEquals, "second line\n")
+ return d
+
+
+ def test_readlineNone(self):
+ """
+ Calling L{_InputStream.readline} with C{None} as an argument returns
+ one line from the input stream.
+ """
+ bytes = "this is one line\nthis is another line"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.readline(None), bytes)
+ d.addCallback(self.assertEquals, "this is one line\n")
+ return d
+
+
+ def test_readlineNegative(self):
+ """
+ Calling L{_InputStream.readline} with a negative integer as an argument
+ returns one line from the input stream.
+ """
+ bytes = "input stream line one\nline two"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.readline(-1), bytes)
+ d.addCallback(self.assertEquals, "input stream line one\n")
+ return d
+
+
+ def test_readlines(self):
+ """
+ Calling L{_InputStream.readlines} with no arguments returns a list of
+ all lines from the input stream.
+ """
+ bytes = "alice\nbob\ncarol"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.readlines(), bytes)
+ d.addCallback(self.assertEquals, ["alice\n", "bob\n", "carol"])
+ return d
+
+
+ def test_readlinesSome(self):
+ """
+ Calling L{_InputStream.readlines} with an integer as an argument
+ returns a list of lines from the input stream with the argument serving
+ as an approximate bound on the total number of bytes to read.
+ """
+ bytes = "123\n456\n789\n0"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.readlines(5), bytes)
+ def cbLines(lines):
+ # Make sure we got enough lines to make 5 bytes. Anything beyond
+ # that is fine too.
+ self.assertEquals(lines[:2], ["123\n", "456\n"])
+ d.addCallback(cbLines)
+ return d
+
+
+ def test_readlinesMoreThan(self):
+ """
+ Calling L{_InputStream.readlines} with an integer which is greater than
+ the total number of bytes in the input stream returns a list of all
+ lines from the input.
+ """
+ bytes = "one potato\ntwo potato\nthree potato"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.readlines(100), bytes)
+ d.addCallback(
+ self.assertEquals,
+ ["one potato\n", "two potato\n", "three potato"])
+ return d
+
+
+ def test_readlinesAfterRead(self):
+ """
+ Calling L{_InputStream.readlines} after a call to L{_InputStream.read}
+ returns lines starting at the byte after the last byte returned by the
+ C{read} call.
+ """
+ bytes = "hello\nworld\nfoo"
+ def readlines(input):
+ input.read(7)
+ return input.readlines()
+ d = self._renderAndReturnReaderResult(readlines, bytes)
+ d.addCallback(self.assertEquals, ["orld\n", "foo"])
+ return d
+
+
+ def test_readlinesNone(self):
+ """
+ Calling L{_InputStream.readlines} with C{None} as an argument returns
+ all lines from the input.
+ """
+ bytes = "one fish\ntwo fish\n"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.readlines(None), bytes)
+ d.addCallback(self.assertEquals, ["one fish\n", "two fish\n"])
+ return d
+
+
+ def test_readlinesNegative(self):
+ """
+ Calling L{_InputStream.readlines} with a negative integer as an
+ argument returns a list of all lines from the input.
+ """
+ bytes = "red fish\nblue fish\n"
+ d = self._renderAndReturnReaderResult(
+ lambda input: input.readlines(-1), bytes)
+ d.addCallback(self.assertEquals, ["red fish\n", "blue fish\n"])
+ return d
+
+
+ def test_iterable(self):
+ """
+ Iterating over L{_InputStream} produces lines from the input stream.
+ """
+ bytes = "green eggs\nand ham\n"
+ d = self._renderAndReturnReaderResult(lambda input: list(input), bytes)
+ d.addCallback(self.assertEquals, ["green eggs\n", "and ham\n"])
+ return d
+
+
+ def test_iterableAfterRead(self):
+ """
+ Iterating over L{_InputStream} after calling L{_InputStream.read}
+ produces lines from the input stream starting from the first byte after
+ the last byte returned by the C{read} call.
+ """
+ bytes = "green eggs\nand ham\n"
+ def iterate(input):
+ input.read(3)
+ return list(input)
+ d = self._renderAndReturnReaderResult(iterate, bytes)
+ d.addCallback(self.assertEquals, ["en eggs\n", "and ham\n"])
+ return d
+
+
+
+class InputStreamStringIOTests(InputStreamTestMixin, TestCase):
+ """
+ Tests for L{_InputStream} when it is wrapped around a L{StringIO.StringIO}.
+ """
+ def getFileType(self):
+ return StringIO.StringIO
+
+
+
+class InputStreamCStringIOTests(InputStreamTestMixin, TestCase):
+ """
+ Tests for L{_InputStream} when it is wrapped around a
+ L{cStringIO.StringIO}.
+ """
+ def getFileType(self):
+ return cStringIO.StringIO
+
+
+
+class InputStreamTemporaryFileTests(InputStreamTestMixin, TestCase):
+ """
+ Tests for L{_InputStream} when it is wrapped around a L{tempfile.TemporaryFile}.
+ """
+ def getFileType(self):
+ return tempfile.TemporaryFile
+
+
+
+class StartResponseTests(WSGITestsMixin, TestCase):
+ """
+ Tests for the I{start_response} parameter passed to the application object
+ by L{WSGIResource}.
+ """
+ def test_status(self):
+ """
+ The response status passed to the I{start_response} callable is written
+ as the status of the response to the request.
+ """
+ channel = DummyChannel()
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('107 Strange message', [])
+ return iter(())
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ self.assertTrue(
+ channel.transport.written.getvalue().startswith(
+ 'HTTP/1.1 107 Strange message'))
+ d.addCallback(cbRendered)
+
+ request = self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+
+ return d
+
+
+ def _headersTest(self, appHeaders, expectedHeaders):
+ """
+ Verify that if the response headers given by C{appHeaders} are passed
+ to the I{start_response} callable, then the response header lines given
+ by C{expectedHeaders} plus I{Server} and I{Date} header lines are
+ included in the response.
+ """
+ # Make the Date header value deterministic
+ self.patch(http, 'datetimeToString', lambda: 'Tuesday')
+
+ channel = DummyChannel()
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('200 OK', appHeaders)
+ return iter(())
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ response = channel.transport.written.getvalue()
+ headers, rest = response.split('\r\n\r\n', 1)
+ headerLines = headers.split('\r\n')[1:]
+ headerLines.sort()
+ allExpectedHeaders = expectedHeaders + [
+ 'Date: Tuesday',
+ 'Server: ' + version,
+ 'Transfer-Encoding: chunked']
+ allExpectedHeaders.sort()
+ self.assertEqual(headerLines, allExpectedHeaders)
+
+ d.addCallback(cbRendered)
+
+ request = self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+ return d
+
+
+ def test_headers(self):
+ """
+ The headers passed to the I{start_response} callable are included in
+ the response as are the required I{Date} and I{Server} headers and the
+ necessary connection (hop to hop) header I{Transfer-Encoding}.
+ """
+ return self._headersTest(
+ [('foo', 'bar'), ('baz', 'quux')],
+ ['Baz: quux', 'Foo: bar'])
+
+
+ def test_applicationProvidedContentType(self):
+ """
+ If I{Content-Type} is included in the headers passed to the
+ I{start_response} callable, one I{Content-Type} header is included in
+ the response.
+ """
+ return self._headersTest(
+ [('content-type', 'monkeys are great')],
+ ['Content-Type: monkeys are great'])
+
+
+ def test_applicationProvidedServerAndDate(self):
+ """
+ If either I{Server} or I{Date} is included in the headers passed to the
+ I{start_response} callable, they are disregarded.
+ """
+ return self._headersTest(
+ [('server', 'foo'), ('Server', 'foo'),
+ ('date', 'bar'), ('dATE', 'bar')],
+ [])
+
+
+ def test_delayedUntilReturn(self):
+ """
+ Nothing is written in response to a request when the I{start_response}
+ callable is invoked. If the iterator returned by the application
+ object produces only empty strings, the response is written after the
+ last element is produced.
+ """
+ channel = DummyChannel()
+
+ intermediateValues = []
+ def record():
+ intermediateValues.append(channel.transport.written.getvalue())
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('200 OK', [('foo', 'bar'), ('baz', 'quux')])
+ yield ''
+ record()
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ self.assertEqual(intermediateValues, [''])
+ d.addCallback(cbRendered)
+
+ request = self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+
+ return d
+
+
+ def test_delayedUntilContent(self):
+ """
+ Nothing is written in response to a request when the I{start_response}
+ callable is invoked. Once a non-empty string has been produced by the
+ iterator returned by the application object, the response status and
+ headers are written.
+ """
+ channel = DummyChannel()
+
+ intermediateValues = []
+ def record():
+ intermediateValues.append(channel.transport.written.getvalue())
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('200 OK', [('foo', 'bar')])
+ yield ''
+ record()
+ yield 'foo'
+ record()
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ self.assertFalse(intermediateValues[0])
+ self.assertTrue(intermediateValues[1])
+ d.addCallback(cbRendered)
+
+ request = self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+
+ return d
+
+
+ def test_content(self):
+ """
+ Content produced by the iterator returned by the application object is
+ written to the request as it is produced.
+ """
+ channel = DummyChannel()
+
+ intermediateValues = []
+ def record():
+ intermediateValues.append(channel.transport.written.getvalue())
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('200 OK', [('content-length', '6')])
+ yield 'foo'
+ record()
+ yield 'bar'
+ record()
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ self.assertEqual(
+ self.getContentFromResponse(intermediateValues[0]),
+ 'foo')
+ self.assertEqual(
+ self.getContentFromResponse(intermediateValues[1]),
+ 'foobar')
+ d.addCallback(cbRendered)
+
+ request = self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+
+ return d
+
+
+ def test_multipleStartResponse(self):
+ """
+ If the I{start_response} callable is invoked multiple times before a
+ data for the response body is produced, the values from the last call
+ are used.
+ """
+ channel = DummyChannel()
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('100 Foo', [])
+ startResponse('200 Bar', [])
+ return iter(())
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ self.assertTrue(
+ channel.transport.written.getvalue().startswith(
+ 'HTTP/1.1 200 Bar\r\n'))
+ d.addCallback(cbRendered)
+
+ request = self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+
+ return d
+
+
+ def test_startResponseWithException(self):
+ """
+ If the I{start_response} callable is invoked with a third positional
+ argument before the status and headers have been written to the
+ response, the status and headers become the newly supplied values.
+ """
+ channel = DummyChannel()
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('100 Foo', [], (Exception, Exception("foo"), None))
+ return iter(())
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ self.assertTrue(
+ channel.transport.written.getvalue().startswith(
+ 'HTTP/1.1 100 Foo\r\n'))
+ d.addCallback(cbRendered)
+
+ request = self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+
+ return d
+
+
+ def test_startResponseWithExceptionTooLate(self):
+ """
+ If the I{start_response} callable is invoked with a third positional
+ argument after the status and headers have been written to the
+ response, the supplied I{exc_info} values are re-raised to the
+ application.
+ """
+ channel = DummyChannel()
+
+ class SomeException(Exception):
+ pass
+
+ try:
+ raise SomeException()
+ except:
+ excInfo = exc_info()
+
+ reraised = []
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('200 OK', [])
+ yield 'foo'
+ try:
+ startResponse('500 ERR', [], excInfo)
+ except:
+ reraised.append(exc_info())
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ self.assertTrue(
+ channel.transport.written.getvalue().startswith(
+ 'HTTP/1.1 200 OK\r\n'))
+ self.assertEqual(reraised[0][0], excInfo[0])
+ self.assertEqual(reraised[0][1], excInfo[1])
+ self.assertEqual(reraised[0][2].tb_next, excInfo[2])
+
+ d.addCallback(cbRendered)
+
+ request = self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+
+ return d
+
+
+ def test_write(self):
+ """
+ I{start_response} returns the I{write} callable which can be used to
+ write bytes to the response body without buffering.
+ """
+ channel = DummyChannel()
+
+ intermediateValues = []
+ def record():
+ intermediateValues.append(channel.transport.written.getvalue())
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ write = startResponse('100 Foo', [('content-length', '6')])
+ write('foo')
+ record()
+ write('bar')
+ record()
+ return iter(())
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ self.assertEqual(
+ self.getContentFromResponse(intermediateValues[0]),
+ 'foo')
+ self.assertEqual(
+ self.getContentFromResponse(intermediateValues[1]),
+ 'foobar')
+ d.addCallback(cbRendered)
+
+ request = self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+
+ return d
+
+
+
+class ApplicationTests(WSGITestsMixin, TestCase):
+ """
+ Tests for things which are done to the application object and the iterator
+ it returns.
+ """
+ def enableThreads(self):
+ self.reactor = reactor
+ self.threadpool = ThreadPool()
+ self.threadpool.start()
+ self.addCleanup(self.threadpool.stop)
+
+
+ def test_close(self):
+ """
+ If the application object returns an iterator which also has a I{close}
+ method, that method is called after iteration is complete.
+ """
+ channel = DummyChannel()
+
+ class Result:
+ def __init__(self):
+ self.open = True
+
+ def __iter__(self):
+ for i in range(3):
+ if self.open:
+ yield str(i)
+
+ def close(self):
+ self.open = False
+
+ result = Result()
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('200 OK', [('content-length', '3')])
+ return result
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ self.assertEqual(
+ self.getContentFromResponse(
+ channel.transport.written.getvalue()),
+ '012')
+ self.assertFalse(result.open)
+ d.addCallback(cbRendered)
+
+ self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''])
+
+ return d
+
+
+ def test_applicationCalledInThread(self):
+ """
+ The application object is invoked and iterated in a thread which is not
+ the reactor thread.
+ """
+ self.enableThreads()
+ invoked = []
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ def result():
+ for i in range(3):
+ invoked.append(get_ident())
+ yield str(i)
+ invoked.append(get_ident())
+ startResponse('200 OK', [('content-length', '3')])
+ return result()
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ self.assertNotIn(get_ident(), invoked)
+ self.assertEqual(len(set(invoked)), 1)
+ d.addCallback(cbRendered)
+
+ self.lowLevelRender(
+ requestFactory, applicationFactory,
+ DummyChannel, 'GET', '1.1', [], [''])
+
+ return d
+
+
+ def test_writeCalledFromThread(self):
+ """
+ The I{write} callable returned by I{start_response} calls the request's
+ C{write} method in the reactor thread.
+ """
+ self.enableThreads()
+ invoked = []
+
+ class ThreadVerifier(Request):
+ def write(self, bytes):
+ invoked.append(get_ident())
+ return Request.write(self, bytes)
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ write = startResponse('200 OK', [])
+ write('foo')
+ return iter(())
+ return application
+
+ d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
+ def cbRendered(ignored):
+ self.assertEqual(set(invoked), set([get_ident()]))
+ d.addCallback(cbRendered)
+
+ self.lowLevelRender(
+ requestFactory, applicationFactory, DummyChannel,
+ 'GET', '1.1', [], [''])
+
+ return d
+
+
+ def test_iteratedValuesWrittenFromThread(self):
+ """
+ Strings produced by the iterator returned by the application object are
+ written to the request in the reactor thread.
+ """
+ self.enableThreads()
+ invoked = []
+
+ class ThreadVerifier(Request):
+ def write(self, bytes):
+ invoked.append(get_ident())
+ return Request.write(self, bytes)
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('200 OK', [])
+ yield 'foo'
+ return application
+
+ d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
+ def cbRendered(ignored):
+ self.assertEqual(set(invoked), set([get_ident()]))
+ d.addCallback(cbRendered)
+
+ self.lowLevelRender(
+ requestFactory, applicationFactory, DummyChannel,
+ 'GET', '1.1', [], [''])
+
+ return d
+
+
+ def test_statusWrittenFromThread(self):
+ """
+ The response status is set on the request object in the reactor thread.
+ """
+ self.enableThreads()
+ invoked = []
+
+ class ThreadVerifier(Request):
+ def setResponseCode(self, code, message):
+ invoked.append(get_ident())
+ return Request.setResponseCode(self, code, message)
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('200 OK', [])
+ return iter(())
+ return application
+
+ d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
+ def cbRendered(ignored):
+ self.assertEqual(set(invoked), set([get_ident()]))
+ d.addCallback(cbRendered)
+
+ self.lowLevelRender(
+ requestFactory, applicationFactory, DummyChannel,
+ 'GET', '1.1', [], [''])
+
+ return d
+
+
+ def test_connectionClosedDuringIteration(self):
+ """
+ If the request connection is lost while the application object is being
+ iterated, iteration is stopped.
+ """
+ class UnreliableConnection(Request):
+ """
+ This is a request which pretends its connection is lost immediately
+ after the first write is done to it.
+ """
+ def write(self, bytes):
+ self.connectionLost(Failure(ConnectionLost("No more connection")))
+
+ self.badIter = False
+ def appIter():
+ yield "foo"
+ self.badIter = True
+ raise Exception("Should not have gotten here")
+
+ def applicationFactory():
+ def application(environ, startResponse):
+ startResponse('200 OK', [])
+ return appIter()
+ return application
+
+ d, requestFactory = self.requestFactoryFactory(UnreliableConnection)
+ def cbRendered(ignored):
+ self.assertFalse(self.badIter, "Should not have resumed iteration")
+ d.addCallback(cbRendered)
+
+ self.lowLevelRender(
+ requestFactory, applicationFactory, DummyChannel,
+ 'GET', '1.1', [], [''])
+
+ return self.assertFailure(d, ConnectionLost)
+
+
+ def _internalServerErrorTest(self, application):
+ channel = DummyChannel()
+
+ def applicationFactory():
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+ def cbRendered(ignored):
+ errors = self.flushLoggedErrors(RuntimeError)
+ self.assertEquals(len(errors), 1)
+
+ self.assertTrue(
+ channel.transport.written.getvalue().startswith(
+ 'HTTP/1.1 500 Internal Server Error'))
+ d.addCallback(cbRendered)
+
+ request = self.lowLevelRender(
+ requestFactory, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+
+ return d
+
+
+ def test_applicationExceptionBeforeStartResponse(self):
+ """
+ If the application raises an exception before calling I{start_response}
+ then the response status is I{500} and the exception is logged.
+ """
+ def application(environ, startResponse):
+ raise RuntimeError("This application had some error.")
+ return self._internalServerErrorTest(application)
+
+
+ def test_applicationExceptionAfterStartResponse(self):
+ """
+ If the application calls I{start_response} but then raises an exception
+ before any data is written to the response then the response status is
+ I{500} and the exception is logged.
+ """
+ def application(environ, startResponse):
+ startResponse('200 OK', [])
+ raise RuntimeError("This application had some error.")
+ return self._internalServerErrorTest(application)
+
+
+ def _connectionClosedTest(self, application, responseContent):
+ channel = DummyChannel()
+
+ def applicationFactory():
+ return application
+
+ d, requestFactory = self.requestFactoryFactory()
+
+ # Capture the request so we can disconnect it later on.
+ requests = []
+ def requestFactoryWrapper(*a, **kw):
+ requests.append(requestFactory(*a, **kw))
+ return requests[-1]
+
+ def ebRendered(ignored):
+ errors = self.flushLoggedErrors(RuntimeError)
+ self.assertEquals(len(errors), 1)
+
+ response = channel.transport.written.getvalue()
+ self.assertTrue(response.startswith('HTTP/1.1 200 OK'))
+ # Chunked transfer-encoding makes this a little messy.
+ self.assertIn(responseContent, response)
+ d.addErrback(ebRendered)
+
+ request = self.lowLevelRender(
+ requestFactoryWrapper, applicationFactory,
+ lambda: channel, 'GET', '1.1', [], [''], None, [])
+
+ # By now the connection should be closed.
+ self.assertTrue(channel.transport.disconnected)
+ # Give it a little push to go the rest of the way.
+ requests[0].connectionLost(Failure(ConnectionLost("All gone")))
+
+ return d
+
+
+ def test_applicationExceptionAfterWrite(self):
+ """
+ If the application raises an exception after the response status has
+ already been sent then the connection is closed and the exception is
+ logged.
+ """
+ responseContent = (
+ 'Some bytes, triggering the server to start sending the response')
+
+ def application(environ, startResponse):
+ startResponse('200 OK', [])
+ yield responseContent
+ raise RuntimeError("This application had some error.")
+ return self._connectionClosedTest(application, responseContent)
+
+
+ def test_applicationCloseException(self):
+ """
+ If the application returns a closeable iterator and the C{close} method
+ raises an exception when called then the connection is still closed and
+ the exception is logged.
+ """
+ responseContent = 'foo'
+
+ class Application(object):
+ def __init__(self, environ, startResponse):
+ startResponse('200 OK', [])
+
+ def __iter__(self):
+ yield responseContent
+
+ def close(self):
+ raise RuntimeError("This application had some error.")
+
+ return self._connectionClosedTest(Application, responseContent)
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_xml.py b/vendor/Twisted-10.0.0/twisted/web/test/test_xml.py
new file mode 100644
index 0000000000..3a0067b3be
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_xml.py
@@ -0,0 +1,1105 @@
+# -*- test-case-name: twisted.web.test.test_xml -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Some fairly inadequate testcases for Twisted XML support.
+"""
+
+from twisted.trial.unittest import TestCase
+from twisted.web import sux
+from twisted.web import microdom
+from twisted.web import domhelpers
+
+
+class Sux0r(sux.XMLParser):
+ def __init__(self):
+ self.tokens = []
+
+ def getTagStarts(self):
+ return [token for token in self.tokens if token[0] == 'start']
+
+ def gotTagStart(self, name, attrs):
+ self.tokens.append(("start", name, attrs))
+
+ def gotText(self, text):
+ self.tokens.append(("text", text))
+
+class SUXTest(TestCase):
+
+ def testBork(self):
+ s = "<bork><bork><bork>"
+ ms = Sux0r()
+ ms.connectionMade()
+ ms.dataReceived(s)
+ self.failUnlessEqual(len(ms.getTagStarts()),3)
+
+
+class MicroDOMTest(TestCase):
+
+ def test_leadingTextDropping(self):
+ """
+ Make sure that if there's no top-level node lenient-mode won't
+ drop leading text that's outside of any elements.
+ """
+ s = "Hi orders! <br>Well. <br>"
+ d = microdom.parseString(s, beExtremelyLenient=True)
+ self.assertEquals(d.firstChild().toxml(),
+ '<html>Hi orders! <br />Well. <br /></html>')
+
+ def test_trailingTextDropping(self):
+ """
+ Ensure that no *trailing* text in a mal-formed
+ no-top-level-element document(s) will not be dropped.
+ """
+ s = "<br>Hi orders!"
+ d = microdom.parseString(s, beExtremelyLenient=True)
+ self.assertEquals(d.firstChild().toxml(),
+ '<html><br />Hi orders!</html>')
+
+
+ def test_noTags(self):
+ """
+ A string with nothing that looks like a tag at all should just
+ be parsed as body text.
+ """
+ s = "Hi orders!"
+ d = microdom.parseString(s, beExtremelyLenient=True)
+ self.assertEquals(d.firstChild().toxml(),
+ "<html>Hi orders!</html>")
+
+
+ def test_surroundingCrap(self):
+ """
+ If a document is surrounded by non-xml text, the text should
+ be remain in the XML.
+ """
+ s = "Hi<br> orders!"
+ d = microdom.parseString(s, beExtremelyLenient=True)
+ self.assertEquals(d.firstChild().toxml(),
+ "<html>Hi<br /> orders!</html>")
+
+
+ def testCaseSensitiveSoonCloser(self):
+ s = """
+ <HTML><BODY>
+ <P ALIGN="CENTER">
+ <A HREF="http://www.apache.org/"><IMG SRC="/icons/apache_pb.gif"></A>
+ </P>
+
+ <P>
+ This is an insane set of text nodes that should NOT be gathered under
+ the A tag above.
+ </P>
+ </BODY></HTML>
+ """
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ l = domhelpers.findNodesNamed(d.documentElement, 'a')
+ n = domhelpers.gatherTextNodes(l[0],1).replace('&nbsp;',' ')
+ self.assertEquals(n.find('insane'), -1)
+
+
+ def test_lenientParenting(self):
+ """
+ Test that C{parentNode} attributes are set to meaningful values when
+ we are parsing HTML that lacks a root node.
+ """
+ # Spare the rod, ruin the child.
+ s = "<br/><br/>"
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ self.assertIdentical(d.documentElement,
+ d.documentElement.firstChild().parentNode)
+
+
+ def test_lenientParentSingle(self):
+ """
+ Test that the C{parentNode} attribute is set to a meaningful value
+ when we parse an HTML document that has a non-Element root node.
+ """
+ s = "Hello"
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ self.assertIdentical(d.documentElement,
+ d.documentElement.firstChild().parentNode)
+
+
+ def testUnEntities(self):
+ s = """
+ <HTML>
+ This HTML goes between Stupid <=CrAzY!=> Dumb.
+ </HTML>
+ """
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ n = domhelpers.gatherTextNodes(d)
+ self.assertNotEquals(n.find('>'), -1)
+
+ def testEmptyError(self):
+ self.assertRaises(sux.ParseError, microdom.parseString, "")
+
+ def testTameDocument(self):
+ s = """
+ <test>
+ <it>
+ <is>
+ <a>
+ test
+ </a>
+ </is>
+ </it>
+ </test>
+ """
+ d = microdom.parseString(s)
+ self.assertEquals(
+ domhelpers.gatherTextNodes(d.documentElement).strip() ,'test')
+
+ def testAwfulTagSoup(self):
+ s = """
+ <html>
+ <head><title> I send you this message to have your advice!!!!</titl e
+ </headd>
+
+ <body bgcolor alink hlink vlink>
+
+ <h1><BLINK>SALE</blINK> TWENTY MILLION EMAILS & FUR COAT NOW
+ FREE WITH `ENLARGER'</h1>
+
+ YES THIS WONDERFUL AWFER IS NOW HERER!!!
+
+ <script LANGUAGE="javascript">
+function give_answers() {
+if (score < 70) {
+alert("I hate you");
+}}
+ </script><a href=/foo.com/lalal name=foo>lalal</a>
+ </body>
+ </HTML>
+ """
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ l = domhelpers.findNodesNamed(d.documentElement, 'blink')
+ self.assertEquals(len(l), 1)
+
+ def testScriptLeniency(self):
+ s = """
+ <script>(foo < bar) and (bar > foo)</script>
+ <script language="javascript">foo </scrip bar </script>
+ <script src="foo">
+ <script src="foo">baz</script>
+ <script /><script></script>
+ """
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ self.assertEquals(d.firstChild().firstChild().firstChild().data,
+ "(foo < bar) and (bar > foo)")
+ self.assertEquals(
+ d.firstChild().getElementsByTagName("script")[1].firstChild().data,
+ "foo </scrip bar ")
+
+ def testScriptLeniencyIntelligence(self):
+ # if there is comment or CDATA in script, the autoquoting in bEL mode
+ # should not happen
+ s = """<script><!-- lalal --></script>"""
+ self.assertEquals(
+ microdom.parseString(s, beExtremelyLenient=1).firstChild().toxml(), s)
+ s = """<script><![CDATA[lalal]]></script>"""
+ self.assertEquals(
+ microdom.parseString(s, beExtremelyLenient=1).firstChild().toxml(), s)
+ s = """<script> // <![CDATA[
+ lalal
+ //]]></script>"""
+ self.assertEquals(
+ microdom.parseString(s, beExtremelyLenient=1).firstChild().toxml(), s)
+
+ def testPreserveCase(self):
+ s = '<eNcApSuLaTe><sUxor></sUxor><bOrk><w00T>TeXt</W00t></BoRk></EnCaPsUlAtE>'
+ s2 = s.lower().replace('text', 'TeXt')
+ # these are the only two option permutations that *can* parse the above
+ d = microdom.parseString(s, caseInsensitive=1, preserveCase=1)
+ d2 = microdom.parseString(s, caseInsensitive=1, preserveCase=0)
+ # caseInsensitive=0 preserveCase=0 is not valid, it's converted to
+ # caseInsensitive=0 preserveCase=1
+ d3 = microdom.parseString(s2, caseInsensitive=0, preserveCase=1)
+ d4 = microdom.parseString(s2, caseInsensitive=1, preserveCase=0)
+ d5 = microdom.parseString(s2, caseInsensitive=1, preserveCase=1)
+ # this is slightly contrived, toxml() doesn't need to be identical
+ # for the documents to be equivalent (i.e. <b></b> to <b/>),
+ # however this assertion tests preserving case for start and
+ # end tags while still matching stuff like <bOrk></BoRk>
+ self.assertEquals(d.documentElement.toxml(), s)
+ self.assert_(d.isEqualToDocument(d2), "%r != %r" % (d.toxml(), d2.toxml()))
+ self.assert_(d2.isEqualToDocument(d3), "%r != %r" % (d2.toxml(), d3.toxml()))
+ # caseInsensitive=0 on the left, NOT perserveCase=1 on the right
+ ## XXX THIS TEST IS TURNED OFF UNTIL SOMEONE WHO CARES ABOUT FIXING IT DOES
+ #self.failIf(d3.isEqualToDocument(d2), "%r == %r" % (d3.toxml(), d2.toxml()))
+ self.assert_(d3.isEqualToDocument(d4), "%r != %r" % (d3.toxml(), d4.toxml()))
+ self.assert_(d4.isEqualToDocument(d5), "%r != %r" % (d4.toxml(), d5.toxml()))
+
+ def testDifferentQuotes(self):
+ s = '<test a="a" b=\'b\' />'
+ d = microdom.parseString(s)
+ e = d.documentElement
+ self.assertEquals(e.getAttribute('a'), 'a')
+ self.assertEquals(e.getAttribute('b'), 'b')
+
+ def testLinebreaks(self):
+ s = '<test \na="a"\n\tb="#b" />'
+ d = microdom.parseString(s)
+ e = d.documentElement
+ self.assertEquals(e.getAttribute('a'), 'a')
+ self.assertEquals(e.getAttribute('b'), '#b')
+
+ def testMismatchedTags(self):
+ for s in '<test>', '<test> </tset>', '</test>':
+ self.assertRaises(microdom.MismatchedTags, microdom.parseString, s)
+
+ def testComment(self):
+ s = "<bar><!--<foo />--></bar>"
+ d = microdom.parseString(s)
+ e = d.documentElement
+ self.assertEquals(e.nodeName, "bar")
+ c = e.childNodes[0]
+ self.assert_(isinstance(c, microdom.Comment))
+ self.assertEquals(c.value, "<foo />")
+ c2 = c.cloneNode()
+ self.assert_(c is not c2)
+ self.assertEquals(c2.toxml(), "<!--<foo />-->")
+
+ def testText(self):
+ d = microdom.parseString("<bar>xxxx</bar>").documentElement
+ text = d.childNodes[0]
+ self.assert_(isinstance(text, microdom.Text))
+ self.assertEquals(text.value, "xxxx")
+ clone = text.cloneNode()
+ self.assert_(clone is not text)
+ self.assertEquals(clone.toxml(), "xxxx")
+
+ def testEntities(self):
+ nodes = microdom.parseString("<b>&amp;&#12AB;</b>").documentElement.childNodes
+ self.assertEquals(len(nodes), 2)
+ self.assertEquals(nodes[0].data, "&amp;")
+ self.assertEquals(nodes[1].data, "&#12AB;")
+ self.assertEquals(nodes[0].cloneNode().toxml(), "&amp;")
+ for n in nodes:
+ self.assert_(isinstance(n, microdom.EntityReference))
+
+ def testCData(self):
+ s = '<x><![CDATA[</x>\r\n & foo]]></x>'
+ cdata = microdom.parseString(s).documentElement.childNodes[0]
+ self.assert_(isinstance(cdata, microdom.CDATASection))
+ self.assertEquals(cdata.data, "</x>\r\n & foo")
+ self.assertEquals(cdata.cloneNode().toxml(), "<![CDATA[</x>\r\n & foo]]>")
+
+ def testSingletons(self):
+ s = "<foo><b/><b /><b\n/></foo>"
+ s2 = "<foo><b/><b/><b/></foo>"
+ nodes = microdom.parseString(s).documentElement.childNodes
+ nodes2 = microdom.parseString(s2).documentElement.childNodes
+ self.assertEquals(len(nodes), 3)
+ for (n, n2) in zip(nodes, nodes2):
+ self.assert_(isinstance(n, microdom.Element))
+ self.assertEquals(n.nodeName, "b")
+ self.assert_(n.isEqualToNode(n2))
+
+ def testAttributes(self):
+ s = '<foo a="b" />'
+ node = microdom.parseString(s).documentElement
+
+ self.assertEquals(node.getAttribute("a"), "b")
+ self.assertEquals(node.getAttribute("c"), None)
+ self.assert_(node.hasAttribute("a"))
+ self.assert_(not node.hasAttribute("c"))
+ a = node.getAttributeNode("a")
+ self.assertEquals(a.value, "b")
+
+ node.setAttribute("foo", "bar")
+ self.assertEquals(node.getAttribute("foo"), "bar")
+
+ def testChildren(self):
+ s = "<foo><bar /><baz /><bax>foo</bax></foo>"
+ d = microdom.parseString(s).documentElement
+ self.assertEquals([n.nodeName for n in d.childNodes], ["bar", "baz", "bax"])
+ self.assertEquals(d.lastChild().nodeName, "bax")
+ self.assertEquals(d.firstChild().nodeName, "bar")
+ self.assert_(d.hasChildNodes())
+ self.assert_(not d.firstChild().hasChildNodes())
+
+ def testMutate(self):
+ s = "<foo />"
+ s1 = '<foo a="b"><bar/><foo/></foo>'
+ s2 = '<foo a="b">foo</foo>'
+ d = microdom.parseString(s).documentElement
+ d1 = microdom.parseString(s1).documentElement
+ d2 = microdom.parseString(s2).documentElement
+
+ d.appendChild(d.cloneNode())
+ d.setAttribute("a", "b")
+ child = d.childNodes[0]
+ self.assertEquals(child.getAttribute("a"), None)
+ self.assertEquals(child.nodeName, "foo")
+
+ d.insertBefore(microdom.Element("bar"), child)
+ self.assertEquals(d.childNodes[0].nodeName, "bar")
+ self.assertEquals(d.childNodes[1], child)
+ for n in d.childNodes:
+ self.assertEquals(n.parentNode, d)
+ self.assert_(d.isEqualToNode(d1))
+
+ d.removeChild(child)
+ self.assertEquals(len(d.childNodes), 1)
+ self.assertEquals(d.childNodes[0].nodeName, "bar")
+
+ t = microdom.Text("foo")
+ d.replaceChild(t, d.firstChild())
+ self.assertEquals(d.firstChild(), t)
+ self.assert_(d.isEqualToNode(d2))
+
+
+ def test_replaceNonChild(self):
+ """
+ L{Node.replaceChild} raises L{ValueError} if the node given to be
+ replaced is not a child of the node C{replaceChild} is called on.
+ """
+ parent = microdom.parseString('<foo />')
+ orphan = microdom.parseString('<bar />')
+ replacement = microdom.parseString('<baz />')
+
+ self.assertRaises(
+ ValueError, parent.replaceChild, replacement, orphan)
+
+
+ def testSearch(self):
+ s = "<foo><bar id='me' /><baz><foo /></baz></foo>"
+ s2 = "<fOo><bAr id='me' /><bAz><fOO /></bAz></fOo>"
+ d = microdom.parseString(s)
+ d2 = microdom.parseString(s2, caseInsensitive=0, preserveCase=1)
+ d3 = microdom.parseString(s2, caseInsensitive=1, preserveCase=1)
+
+ root = d.documentElement
+ self.assertEquals(root.firstChild(), d.getElementById('me'))
+ self.assertEquals(d.getElementsByTagName("foo"),
+ [root, root.lastChild().firstChild()])
+
+ root = d2.documentElement
+ self.assertEquals(root.firstChild(), d2.getElementById('me'))
+ self.assertEquals(d2.getElementsByTagName('fOo'), [root])
+ self.assertEquals(d2.getElementsByTagName('fOO'),
+ [root.lastChild().firstChild()])
+ self.assertEquals(d2.getElementsByTagName('foo'), [])
+
+ root = d3.documentElement
+ self.assertEquals(root.firstChild(), d3.getElementById('me'))
+ self.assertEquals(d3.getElementsByTagName('FOO'),
+ [root, root.lastChild().firstChild()])
+ self.assertEquals(d3.getElementsByTagName('fOo'),
+ [root, root.lastChild().firstChild()])
+
+ def testDoctype(self):
+ s = ('<?xml version="1.0"?>'
+ '<!DOCTYPE foo PUBLIC "baz" "http://www.example.com/example.dtd">'
+ '<foo></foo>')
+ s2 = '<foo/>'
+ d = microdom.parseString(s)
+ d2 = microdom.parseString(s2)
+ self.assertEquals(d.doctype,
+ 'foo PUBLIC "baz" "http://www.example.com/example.dtd"')
+ self.assertEquals(d.toxml(), s)
+ self.failIf(d.isEqualToDocument(d2))
+ self.failUnless(d.documentElement.isEqualToNode(d2.documentElement))
+
+ samples = [("<img/>", "<img />"),
+ ("<foo A='b'>x</foo>", '<foo A="b">x</foo>'),
+ ("<foo><BAR /></foo>", "<foo><BAR></BAR></foo>"),
+ ("<foo>hello there &amp; yoyoy</foo>",
+ "<foo>hello there &amp; yoyoy</foo>"),
+ ]
+
+ def testOutput(self):
+ for s, out in self.samples:
+ d = microdom.parseString(s, caseInsensitive=0)
+ d2 = microdom.parseString(out, caseInsensitive=0)
+ testOut = d.documentElement.toxml()
+ self.assertEquals(out, testOut)
+ self.assert_(d.isEqualToDocument(d2))
+
+ def testErrors(self):
+ for s in ["<foo>&am</foo>", "<foo", "<f>&</f>", "<() />"]:
+ self.assertRaises(Exception, microdom.parseString, s)
+
+ def testCaseInsensitive(self):
+ s = "<foo a='b'><BAx>x</bax></FOO>"
+ s2 = '<foo a="b"><bax>x</bax></foo>'
+ s3 = "<FOO a='b'><BAx>x</BAx></FOO>"
+ s4 = "<foo A='b'>x</foo>"
+ d = microdom.parseString(s)
+ d2 = microdom.parseString(s2)
+ d3 = microdom.parseString(s3, caseInsensitive=1)
+ d4 = microdom.parseString(s4, caseInsensitive=1, preserveCase=1)
+ d5 = microdom.parseString(s4, caseInsensitive=1, preserveCase=0)
+ d6 = microdom.parseString(s4, caseInsensitive=0, preserveCase=0)
+ out = microdom.parseString(s).documentElement.toxml()
+ self.assertRaises(microdom.MismatchedTags, microdom.parseString,
+ s, caseInsensitive=0)
+ self.assertEquals(out, s2)
+ self.failUnless(d.isEqualToDocument(d2))
+ self.failUnless(d.isEqualToDocument(d3))
+ self.failUnless(d4.documentElement.hasAttribute('a'))
+ self.failIf(d6.documentElement.hasAttribute('a'))
+ self.assertEquals(d4.documentElement.toxml(), '<foo A="b">x</foo>')
+ self.assertEquals(d5.documentElement.toxml(), '<foo a="b">x</foo>')
+ def testEatingWhitespace(self):
+ s = """<hello>
+ </hello>"""
+ d = microdom.parseString(s)
+ self.failUnless(not d.documentElement.hasChildNodes(),
+ d.documentElement.childNodes)
+ self.failUnless(d.isEqualToDocument(microdom.parseString('<hello></hello>')))
+
+ def testLenientAmpersand(self):
+ prefix = "<?xml version='1.0'?>"
+ # we use <pre> so space will be preserved
+ for i, o in [("&", "&amp;"),
+ ("& ", "&amp; "),
+ ("&amp;", "&amp;"),
+ ("&hello monkey", "&amp;hello monkey")]:
+ d = microdom.parseString("%s<pre>%s</pre>"
+ % (prefix, i), beExtremelyLenient=1)
+ self.assertEquals(d.documentElement.toxml(), "<pre>%s</pre>" % o)
+ # non-space preserving
+ d = microdom.parseString("<t>hello & there</t>", beExtremelyLenient=1)
+ self.assertEquals(d.documentElement.toxml(), "<t>hello &amp; there</t>")
+
+ def testInsensitiveLenient(self):
+ # testing issue #537
+ d = microdom.parseString(
+ "<?xml version='1.0'?><bar><xA><y>c</Xa> <foo></bar>",
+ beExtremelyLenient=1)
+ self.assertEquals(d.documentElement.firstChild().toxml(), "<xa><y>c</y></xa>")
+
+ def testLaterCloserSimple(self):
+ s = "<ul><li>foo<li>bar<li>baz</ul>"
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ expected = "<ul><li>foo</li><li>bar</li><li>baz</li></ul>"
+ actual = d.documentElement.toxml()
+ self.assertEquals(expected, actual)
+
+ def testLaterCloserCaseInsensitive(self):
+ s = "<DL><p><DT>foo<DD>bar</DL>"
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ expected = "<dl><p></p><dt>foo</dt><dd>bar</dd></dl>"
+ actual = d.documentElement.toxml()
+ self.assertEquals(expected, actual)
+
+ def testLaterCloserTable(self):
+ s = ("<table>"
+ "<tr><th>name<th>value<th>comment"
+ "<tr><th>this<td>tag<td>soup"
+ "<tr><th>must<td>be<td>handled"
+ "</table>")
+ expected = ("<table>"
+ "<tr><th>name</th><th>value</th><th>comment</th></tr>"
+ "<tr><th>this</th><td>tag</td><td>soup</td></tr>"
+ "<tr><th>must</th><td>be</td><td>handled</td></tr>"
+ "</table>")
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ actual = d.documentElement.toxml()
+ self.assertEquals(expected, actual)
+ testLaterCloserTable.todo = "Table parsing needs to be fixed."
+
+ def testLaterCloserDL(self):
+ s = ("<dl>"
+ "<dt>word<dd>definition"
+ "<dt>word<dt>word<dd>definition<dd>definition"
+ "</dl>")
+ expected = ("<dl>"
+ "<dt>word</dt><dd>definition</dd>"
+ "<dt>word</dt><dt>word</dt><dd>definition</dd><dd>definition</dd>"
+ "</dl>")
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ actual = d.documentElement.toxml()
+ self.assertEquals(expected, actual)
+
+ def testLaterCloserDL2(self):
+ s = ("<dl>"
+ "<dt>word<dd>definition<p>more definition"
+ "<dt>word"
+ "</dl>")
+ expected = ("<dl>"
+ "<dt>word</dt><dd>definition<p>more definition</p></dd>"
+ "<dt>word</dt>"
+ "</dl>")
+ d = microdom.parseString(s, beExtremelyLenient=1)
+ actual = d.documentElement.toxml()
+ self.assertEquals(expected, actual)
+
+ testLaterCloserDL2.todo = "unclosed <p> messes it up."
+
+ def testUnicodeTolerance(self):
+ import struct
+ s = '<foo><bar><baz /></bar></foo>'
+ j =(u'<?xml version="1.0" encoding="UCS-2" ?>\r\n<JAPANESE>\r\n'
+ u'<TITLE>\u5c02\u9580\u5bb6\u30ea\u30b9\u30c8 </TITLE></JAPANESE>')
+ j2=('\xff\xfe<\x00?\x00x\x00m\x00l\x00 \x00v\x00e\x00r\x00s\x00i\x00o'
+ '\x00n\x00=\x00"\x001\x00.\x000\x00"\x00 \x00e\x00n\x00c\x00o\x00d'
+ '\x00i\x00n\x00g\x00=\x00"\x00U\x00C\x00S\x00-\x002\x00"\x00 \x00?'
+ '\x00>\x00\r\x00\n\x00<\x00J\x00A\x00P\x00A\x00N\x00E\x00S\x00E'
+ '\x00>\x00\r\x00\n\x00<\x00T\x00I\x00T\x00L\x00E\x00>\x00\x02\\'
+ '\x80\x95\xb6[\xea0\xb90\xc80 \x00<\x00/\x00T\x00I\x00T\x00L\x00E'
+ '\x00>\x00<\x00/\x00J\x00A\x00P\x00A\x00N\x00E\x00S\x00E\x00>\x00')
+ def reverseBytes(s):
+ fmt = str(len(s) / 2) + 'H'
+ return struct.pack('<' + fmt, *struct.unpack('>' + fmt, s))
+ urd = microdom.parseString(reverseBytes(s.encode('UTF-16')))
+ ud = microdom.parseString(s.encode('UTF-16'))
+ sd = microdom.parseString(s)
+ self.assert_(ud.isEqualToDocument(sd))
+ self.assert_(ud.isEqualToDocument(urd))
+ ud = microdom.parseString(j)
+ urd = microdom.parseString(reverseBytes(j2))
+ sd = microdom.parseString(j2)
+ self.assert_(ud.isEqualToDocument(sd))
+ self.assert_(ud.isEqualToDocument(urd))
+
+ # test that raw text still gets encoded
+ # test that comments get encoded
+ j3=microdom.parseString(u'<foo/>')
+ hdr='<?xml version="1.0"?>'
+ div=microdom.lmx().text(u'\u221a', raw=1).node
+ de=j3.documentElement
+ de.appendChild(div)
+ de.appendChild(j3.createComment(u'\u221a'))
+ self.assertEquals(j3.toxml(), hdr+
+ u'<foo><div>\u221a</div><!--\u221a--></foo>'.encode('utf8'))
+
+ def testNamedChildren(self):
+ tests = {"<foo><bar /><bar unf='1' /><bar>asdfadsf</bar>"
+ "<bam/></foo>" : 3,
+ '<foo>asdf</foo>' : 0,
+ '<foo><bar><bar></bar></bar></foo>' : 1,
+ }
+ for t in tests.keys():
+ node = microdom.parseString(t).documentElement
+ result = domhelpers.namedChildren(node, 'bar')
+ self.assertEquals(len(result), tests[t])
+ if result:
+ self.assert_(hasattr(result[0], 'tagName'))
+
+ def testCloneNode(self):
+ s = '<foo a="b"><bax>x</bax></foo>'
+ node = microdom.parseString(s).documentElement
+ clone = node.cloneNode(deep=1)
+ self.failIfEquals(node, clone)
+ self.assertEquals(len(node.childNodes), len(clone.childNodes))
+ c1, c2 = node.firstChild(), clone.firstChild()
+ self.failIfEquals(c1, c2)
+ self.assertEquals(len(c1.childNodes), len(c2.childNodes))
+ self.failIfEquals(c1.firstChild(), c2.firstChild())
+ self.assertEquals(s, clone.toxml())
+ self.assertEquals(node.namespace, clone.namespace)
+
+ def testCloneDocument(self):
+ s = ('<?xml version="1.0"?>'
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
+ '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><foo></foo>')
+
+ node = microdom.parseString(s)
+ clone = node.cloneNode(deep=1)
+ self.failIfEquals(node, clone)
+ self.assertEquals(len(node.childNodes), len(clone.childNodes))
+ self.assertEquals(s, clone.toxml())
+
+ self.failUnless(clone.isEqualToDocument(node))
+ self.failUnless(node.isEqualToDocument(clone))
+
+
+ def testLMX(self):
+ n = microdom.Element("p")
+ lmx = microdom.lmx(n)
+ lmx.text("foo")
+ b = lmx.b(a="c")
+ b.foo()["z"] = "foo"
+ b.foo()
+ b.add("bar", c="y")
+
+ s = '<p>foo<b a="c"><foo z="foo"></foo><foo></foo><bar c="y"></bar></b></p>'
+ self.assertEquals(s, n.toxml())
+
+ def testDict(self):
+ n = microdom.Element("p")
+ d = {n : 1} # will fail if Element is unhashable
+
+ def testEscaping(self):
+ # issue 590
+ raw = "&'some \"stuff\"', <what up?>"
+ cooked = "&amp;'some &quot;stuff&quot;', &lt;what up?&gt;"
+ esc1 = microdom.escape(raw)
+ self.assertEquals(esc1, cooked)
+ self.assertEquals(microdom.unescape(esc1), raw)
+
+ def testNamespaces(self):
+ s = '''
+ <x xmlns="base">
+ <y />
+ <y q="1" x:q="2" y:q="3" />
+ <y:y xml:space="1">here is some space </y:y>
+ <y:y />
+ <x:y />
+ </x>
+ '''
+ d = microdom.parseString(s)
+ # at least make sure it doesn't traceback
+ s2 = d.toprettyxml()
+ self.assertEquals(d.documentElement.namespace,
+ "base")
+ self.assertEquals(d.documentElement.getElementsByTagName("y")[0].namespace,
+ "base")
+ self.assertEquals(
+ d.documentElement.getElementsByTagName("y")[1].getAttributeNS('base','q'),
+ '1')
+
+ d2 = microdom.parseString(s2)
+ self.assertEquals(d2.documentElement.namespace,
+ "base")
+ self.assertEquals(d2.documentElement.getElementsByTagName("y")[0].namespace,
+ "base")
+ self.assertEquals(
+ d2.documentElement.getElementsByTagName("y")[1].getAttributeNS('base','q'),
+ '1')
+
+ def testNamespaceDelete(self):
+ """
+ Test that C{toxml} can support xml structures that remove namespaces.
+ """
+ s1 = ('<?xml version="1.0"?><html xmlns="http://www.w3.org/TR/REC-html40">'
+ '<body xmlns=""></body></html>')
+ s2 = microdom.parseString(s1).toxml()
+ self.assertEquals(s1, s2)
+
+ def testNamespaceInheritance(self):
+ """
+ Check that unspecified namespace is a thing separate from undefined
+ namespace. This test added after discovering some weirdness in Lore.
+ """
+ # will only work if childNodes is mutated. not sure why.
+ child = microdom.Element('ol')
+ parent = microdom.Element('div', namespace='http://www.w3.org/1999/xhtml')
+ parent.childNodes = [child]
+ self.assertEquals(parent.toxml(),
+ '<div xmlns="http://www.w3.org/1999/xhtml"><ol></ol></div>')
+
+ def test_prefixedTags(self):
+ """
+ XML elements with a prefixed name as per upper level tag definition
+ have a start-tag of C{"<prefix:tag>"} and an end-tag of
+ C{"</prefix:tag>"}.
+
+ Refer to U{http://www.w3.org/TR/xml-names/#ns-using} for details.
+ """
+ outerNamespace = "http://example.com/outer"
+ innerNamespace = "http://example.com/inner"
+
+ document = microdom.Document()
+ # Create the root in one namespace. Microdom will probably make this
+ # the default namespace.
+ root = document.createElement("root", namespace=outerNamespace)
+
+ # Give the root some prefixes to use.
+ root.addPrefixes({innerNamespace: "inner"})
+
+ # Append a child to the root from the namespace that prefix is bound
+ # to.
+ tag = document.createElement("tag", namespace=innerNamespace)
+
+ # Give that tag a child too. This way we test rendering of tags with
+ # children and without children.
+ child = document.createElement("child", namespace=innerNamespace)
+
+ tag.appendChild(child)
+ root.appendChild(tag)
+ document.appendChild(root)
+
+ # ok, the xml should appear like this
+ xmlOk = (
+ '<?xml version="1.0"?>'
+ '<root xmlns="http://example.com/outer" '
+ 'xmlns:inner="http://example.com/inner">'
+ '<inner:tag><inner:child></inner:child></inner:tag>'
+ '</root>')
+
+ xmlOut = document.toxml()
+ self.assertEquals(xmlOut, xmlOk)
+
+
+ def test_prefixPropagation(self):
+ """
+ Children of prefixed tags respect the default namespace at the point
+ where they are rendered. Specifically, they are not influenced by the
+ prefix of their parent as that prefix has no bearing on them.
+
+ See U{http://www.w3.org/TR/xml-names/#scoping} for details.
+
+ To further clarify the matter, the following::
+
+ <root xmlns="http://example.com/ns/test">
+ <mytag xmlns="http://example.com/ns/mytags">
+ <mysubtag xmlns="http://example.com/ns/mytags">
+ <element xmlns="http://example.com/ns/test"></element>
+ </mysubtag>
+ </mytag>
+ </root>
+
+ Should become this after all the namespace declarations have been
+ I{moved up}::
+
+ <root xmlns="http://example.com/ns/test"
+ xmlns:mytags="http://example.com/ns/mytags">
+ <mytags:mytag>
+ <mytags:mysubtag>
+ <element></element>
+ </mytags:mysubtag>
+ </mytags:mytag>
+ </root>
+ """
+ outerNamespace = "http://example.com/outer"
+ innerNamespace = "http://example.com/inner"
+
+ document = microdom.Document()
+ # creates a root element
+ root = document.createElement("root", namespace=outerNamespace)
+ document.appendChild(root)
+
+ # Create a child with a specific namespace with a prefix bound to it.
+ root.addPrefixes({innerNamespace: "inner"})
+ mytag = document.createElement("mytag",namespace=innerNamespace)
+ root.appendChild(mytag)
+
+ # Create a child of that which has the outer namespace.
+ mysubtag = document.createElement("mysubtag", namespace=outerNamespace)
+ mytag.appendChild(mysubtag)
+
+ xmlOk = (
+ '<?xml version="1.0"?>'
+ '<root xmlns="http://example.com/outer" '
+ 'xmlns:inner="http://example.com/inner">'
+ '<inner:mytag>'
+ '<mysubtag></mysubtag>'
+ '</inner:mytag>'
+ '</root>'
+ )
+ xmlOut = document.toxml()
+ self.assertEquals(xmlOut, xmlOk)
+
+
+
+class TestBrokenHTML(TestCase):
+ """
+ Tests for when microdom encounters very bad HTML and C{beExtremelyLenient}
+ is enabled. These tests are inspired by some HTML generated in by a mailer,
+ which breaks up very long lines by splitting them with '!\n '. The expected
+ behaviour is loosely modelled on the way Firefox treats very bad HTML.
+ """
+
+ def checkParsed(self, input, expected, beExtremelyLenient=1):
+ """
+ Check that C{input}, when parsed, produces a DOM where the XML
+ of the document element is equal to C{expected}.
+ """
+ output = microdom.parseString(input,
+ beExtremelyLenient=beExtremelyLenient)
+ self.assertEquals(output.documentElement.toxml(), expected)
+
+
+ def test_brokenAttributeName(self):
+ """
+ Check that microdom does its best to handle broken attribute names.
+ The important thing is that it doesn't raise an exception.
+ """
+ input = '<body><h1><div al!\n ign="center">Foo</div></h1></body>'
+ expected = ('<body><h1><div ign="center" al="True">'
+ 'Foo</div></h1></body>')
+ self.checkParsed(input, expected)
+
+
+ def test_brokenAttributeValue(self):
+ """
+ Check that microdom encompasses broken attribute values.
+ """
+ input = '<body><h1><div align="cen!\n ter">Foo</div></h1></body>'
+ expected = '<body><h1><div align="cen!\n ter">Foo</div></h1></body>'
+ self.checkParsed(input, expected)
+
+
+ def test_brokenOpeningTag(self):
+ """
+ Check that microdom does its best to handle broken opening tags.
+ The important thing is that it doesn't raise an exception.
+ """
+ input = '<body><h1><sp!\n an>Hello World!</span></h1></body>'
+ expected = '<body><h1><sp an="True">Hello World!</sp></h1></body>'
+ self.checkParsed(input, expected)
+
+
+ def test_brokenSelfClosingTag(self):
+ """
+ Check that microdom does its best to handle broken self-closing tags
+ The important thing is that it doesn't raise an exception.
+ """
+ self.checkParsed('<body><span /!\n></body>',
+ '<body><span></span></body>')
+ self.checkParsed('<span!\n />', '<span></span>')
+
+
+ def test_brokenClosingTag(self):
+ """
+ Check that microdom does its best to handle broken closing tags.
+ The important thing is that it doesn't raise an exception.
+ """
+ input = '<body><h1><span>Hello World!</sp!\nan></h1></body>'
+ expected = '<body><h1><span>Hello World!</span></h1></body>'
+ self.checkParsed(input, expected)
+ input = '<body><h1><span>Hello World!</!\nspan></h1></body>'
+ self.checkParsed(input, expected)
+ input = '<body><h1><span>Hello World!</span!\n></h1></body>'
+ self.checkParsed(input, expected)
+ input = '<body><h1><span>Hello World!<!\n/span></h1></body>'
+ expected = '<body><h1><span>Hello World!<!></!></span></h1></body>'
+ self.checkParsed(input, expected)
+
+
+
+
+class NodeTests(TestCase):
+ """
+ Tests for L{Node}.
+ """
+ def test_isNodeEqualTo(self):
+ """
+ L{Node.isEqualToNode} returns C{True} if and only if passed a L{Node}
+ with the same children.
+ """
+ # A node is equal to itself
+ node = microdom.Node(object())
+ self.assertTrue(node.isEqualToNode(node))
+ another = microdom.Node(object())
+ # Two nodes with no children are equal
+ self.assertTrue(node.isEqualToNode(another))
+ node.appendChild(microdom.Node(object()))
+ # A node with no children is not equal to a node with a child
+ self.assertFalse(node.isEqualToNode(another))
+ another.appendChild(microdom.Node(object()))
+ # A node with a child and no grandchildren is equal to another node
+ # with a child and no grandchildren.
+ self.assertTrue(node.isEqualToNode(another))
+ # A node with a child and a grandchild is not equal to another node
+ # with a child and no grandchildren.
+ node.firstChild().appendChild(microdom.Node(object()))
+ self.assertFalse(node.isEqualToNode(another))
+ # A node with a child and a grandchild is equal to another node with a
+ # child and a grandchild.
+ another.firstChild().appendChild(microdom.Node(object()))
+ self.assertTrue(node.isEqualToNode(another))
+
+ def test_validChildInstance(self):
+ """
+ Children of L{Node} instances must also be L{Node} instances.
+ """
+ node = microdom.Node()
+ child = microdom.Node()
+ # Node.appendChild() only accepts Node instances.
+ node.appendChild(child)
+ self.assertRaises(TypeError, node.appendChild, None)
+ # Node.insertBefore() only accepts Node instances.
+ self.assertRaises(TypeError, node.insertBefore, child, None)
+ self.assertRaises(TypeError, node.insertBefore, None, child)
+ self.assertRaises(TypeError, node.insertBefore, None, None)
+ # Node.removeChild() only accepts Node instances.
+ node.removeChild(child)
+ self.assertRaises(TypeError, node.removeChild, None)
+ # Node.replaceChild() only accepts Node instances.
+ self.assertRaises(TypeError, node.replaceChild, child, None)
+ self.assertRaises(TypeError, node.replaceChild, None, child)
+ self.assertRaises(TypeError, node.replaceChild, None, None)
+
+
+class DocumentTests(TestCase):
+ """
+ Tests for L{Document}.
+ """
+ doctype = 'foo PUBLIC "baz" "http://www.example.com/example.dtd"'
+
+ def test_isEqualToNode(self):
+ """
+ L{Document.isEqualToNode} returns C{True} if and only if passed a
+ L{Document} with the same C{doctype} and C{documentElement}.
+ """
+ # A document is equal to itself
+ document = microdom.Document()
+ self.assertTrue(document.isEqualToNode(document))
+ # A document without a doctype or documentElement is equal to another
+ # document without a doctype or documentElement.
+ another = microdom.Document()
+ self.assertTrue(document.isEqualToNode(another))
+ # A document with a doctype is not equal to a document without a
+ # doctype.
+ document.doctype = self.doctype
+ self.assertFalse(document.isEqualToNode(another))
+ # Two documents with the same doctype are equal
+ another.doctype = self.doctype
+ self.assertTrue(document.isEqualToNode(another))
+ # A document with a documentElement is not equal to a document without
+ # a documentElement
+ document.appendChild(microdom.Node(object()))
+ self.assertFalse(document.isEqualToNode(another))
+ # Two documents with equal documentElements are equal.
+ another.appendChild(microdom.Node(object()))
+ self.assertTrue(document.isEqualToNode(another))
+ # Two documents with documentElements which are not equal are not
+ # equal.
+ document.documentElement.appendChild(microdom.Node(object()))
+ self.assertFalse(document.isEqualToNode(another))
+
+
+ def test_childRestriction(self):
+ """
+ L{Document.appendChild} raises L{ValueError} if the document already
+ has a child.
+ """
+ document = microdom.Document()
+ child = microdom.Node()
+ another = microdom.Node()
+ document.appendChild(child)
+ self.assertRaises(ValueError, document.appendChild, another)
+
+
+
+class EntityReferenceTests(TestCase):
+ """
+ Tests for L{EntityReference}.
+ """
+ def test_isEqualToNode(self):
+ """
+ L{EntityReference.isEqualToNode} returns C{True} if and only if passed
+ a L{EntityReference} with the same C{eref}.
+ """
+ self.assertTrue(
+ microdom.EntityReference('quot').isEqualToNode(
+ microdom.EntityReference('quot')))
+ self.assertFalse(
+ microdom.EntityReference('quot').isEqualToNode(
+ microdom.EntityReference('apos')))
+
+
+
+class CharacterDataTests(TestCase):
+ """
+ Tests for L{CharacterData}.
+ """
+ def test_isEqualToNode(self):
+ """
+ L{CharacterData.isEqualToNode} returns C{True} if and only if passed a
+ L{CharacterData} with the same value.
+ """
+ self.assertTrue(
+ microdom.CharacterData('foo').isEqualToNode(
+ microdom.CharacterData('foo')))
+ self.assertFalse(
+ microdom.CharacterData('foo').isEqualToNode(
+ microdom.CharacterData('bar')))
+
+
+
+class CommentTests(TestCase):
+ """
+ Tests for L{Comment}.
+ """
+ def test_isEqualToNode(self):
+ """
+ L{Comment.isEqualToNode} returns C{True} if and only if passed a
+ L{Comment} with the same value.
+ """
+ self.assertTrue(
+ microdom.Comment('foo').isEqualToNode(
+ microdom.Comment('foo')))
+ self.assertFalse(
+ microdom.Comment('foo').isEqualToNode(
+ microdom.Comment('bar')))
+
+
+
+class TextTests(TestCase):
+ """
+ Tests for L{Text}.
+ """
+ def test_isEqualToNode(self):
+ """
+ L{Text.isEqualToNode} returns C{True} if and only if passed a L{Text}
+ which represents the same data.
+ """
+ self.assertTrue(
+ microdom.Text('foo', raw=True).isEqualToNode(
+ microdom.Text('foo', raw=True)))
+ self.assertFalse(
+ microdom.Text('foo', raw=True).isEqualToNode(
+ microdom.Text('foo', raw=False)))
+ self.assertFalse(
+ microdom.Text('foo', raw=True).isEqualToNode(
+ microdom.Text('bar', raw=True)))
+
+
+
+class CDATASectionTests(TestCase):
+ """
+ Tests for L{CDATASection}.
+ """
+ def test_isEqualToNode(self):
+ """
+ L{CDATASection.isEqualToNode} returns C{True} if and only if passed a
+ L{CDATASection} which represents the same data.
+ """
+ self.assertTrue(
+ microdom.CDATASection('foo').isEqualToNode(
+ microdom.CDATASection('foo')))
+ self.assertFalse(
+ microdom.CDATASection('foo').isEqualToNode(
+ microdom.CDATASection('bar')))
+
+
+
+class ElementTests(TestCase):
+ """
+ Tests for L{Element}.
+ """
+ def test_isEqualToNode(self):
+ """
+ L{Element.isEqualToNode} returns C{True} if and only if passed a
+ L{Element} with the same C{nodeName}, C{namespace}, C{childNodes}, and
+ C{attributes}.
+ """
+ self.assertTrue(
+ microdom.Element(
+ 'foo', {'a': 'b'}, object(), namespace='bar').isEqualToNode(
+ microdom.Element(
+ 'foo', {'a': 'b'}, object(), namespace='bar')))
+
+ # Elements with different nodeName values do not compare equal.
+ self.assertFalse(
+ microdom.Element(
+ 'foo', {'a': 'b'}, object(), namespace='bar').isEqualToNode(
+ microdom.Element(
+ 'bar', {'a': 'b'}, object(), namespace='bar')))
+
+ # Elements with different namespaces do not compare equal.
+ self.assertFalse(
+ microdom.Element(
+ 'foo', {'a': 'b'}, object(), namespace='bar').isEqualToNode(
+ microdom.Element(
+ 'foo', {'a': 'b'}, object(), namespace='baz')))
+
+ # Elements with different childNodes do not compare equal.
+ one = microdom.Element('foo', {'a': 'b'}, object(), namespace='bar')
+ two = microdom.Element('foo', {'a': 'b'}, object(), namespace='bar')
+ two.appendChild(microdom.Node(object()))
+ self.assertFalse(one.isEqualToNode(two))
+
+ # Elements with different attributes do not compare equal.
+ self.assertFalse(
+ microdom.Element(
+ 'foo', {'a': 'b'}, object(), namespace='bar').isEqualToNode(
+ microdom.Element(
+ 'foo', {'a': 'c'}, object(), namespace='bar')))
diff --git a/vendor/Twisted-10.0.0/twisted/web/test/test_xmlrpc.py b/vendor/Twisted-10.0.0/twisted/web/test/test_xmlrpc.py
new file mode 100644
index 0000000000..0d1b3618e3
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/test/test_xmlrpc.py
@@ -0,0 +1,510 @@
+# -*- test-case-name: twisted.web.test.test_xmlrpc -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for XML-RPC support in L{twisted.web.xmlrpc}.
+"""
+
+import xmlrpclib
+
+from twisted.trial import unittest
+from twisted.web import xmlrpc
+from twisted.web.xmlrpc import XMLRPC, addIntrospection, _QueryFactory
+from twisted.web import server, static, client, error, http
+from twisted.internet import reactor, defer
+from twisted.internet.error import ConnectionDone
+from twisted.python import failure
+
+
+class TestRuntimeError(RuntimeError):
+ pass
+
+class TestValueError(ValueError):
+ pass
+
+
+
+class Test(XMLRPC):
+
+ # If you add xmlrpc_ methods to this class, go change test_listMethods
+ # below.
+
+ FAILURE = 666
+ NOT_FOUND = 23
+ SESSION_EXPIRED = 42
+
+ def xmlrpc_echo(self, arg):
+ return arg
+
+ # the doc string is part of the test
+ def xmlrpc_add(self, a, b):
+ """
+ This function add two numbers.
+ """
+ return a + b
+
+ xmlrpc_add.signature = [['int', 'int', 'int'],
+ ['double', 'double', 'double']]
+
+ # the doc string is part of the test
+ def xmlrpc_pair(self, string, num):
+ """
+ This function puts the two arguments in an array.
+ """
+ return [string, num]
+
+ xmlrpc_pair.signature = [['array', 'string', 'int']]
+
+ # the doc string is part of the test
+ def xmlrpc_defer(self, x):
+ """Help for defer."""
+ return defer.succeed(x)
+
+ def xmlrpc_deferFail(self):
+ return defer.fail(TestValueError())
+
+ # don't add a doc string, it's part of the test
+ def xmlrpc_fail(self):
+ raise TestRuntimeError
+
+ def xmlrpc_fault(self):
+ return xmlrpc.Fault(12, "hello")
+
+ def xmlrpc_deferFault(self):
+ return defer.fail(xmlrpc.Fault(17, "hi"))
+
+ def xmlrpc_complex(self):
+ return {"a": ["b", "c", 12, []], "D": "foo"}
+
+ def xmlrpc_dict(self, map, key):
+ return map[key]
+ xmlrpc_dict.help = 'Help for dict.'
+
+ def _getFunction(self, functionPath):
+ try:
+ return XMLRPC._getFunction(self, functionPath)
+ except xmlrpc.NoSuchFunction:
+ if functionPath.startswith("SESSION"):
+ raise xmlrpc.Fault(self.SESSION_EXPIRED,
+ "Session non-existant/expired.")
+ else:
+ raise
+
+
+class TestAuthHeader(Test):
+ """
+ This is used to get the header info so that we can test
+ authentication.
+ """
+ def __init__(self):
+ Test.__init__(self)
+ self.request = None
+
+ def render(self, request):
+ self.request = request
+ return Test.render(self, request)
+
+ def xmlrpc_authinfo(self):
+ return self.request.getUser(), self.request.getPassword()
+
+
+class TestQueryProtocol(xmlrpc.QueryProtocol):
+ """
+ QueryProtocol for tests that saves headers received inside the factory.
+ """
+ def handleHeader(self, key, val):
+ self.factory.headers[key.lower()] = val
+
+
+class TestQueryFactory(xmlrpc._QueryFactory):
+ """
+ QueryFactory using L{TestQueryProtocol} for saving headers.
+ """
+ protocol = TestQueryProtocol
+
+ def __init__(self, *args, **kwargs):
+ self.headers = {}
+ xmlrpc._QueryFactory.__init__(self, *args, **kwargs)
+
+
+class XMLRPCTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.p = reactor.listenTCP(0, server.Site(Test()),
+ interface="127.0.0.1")
+ self.port = self.p.getHost().port
+ self.factories = []
+
+ def tearDown(self):
+ self.factories = []
+ return self.p.stopListening()
+
+ def queryFactory(self, *args, **kwargs):
+ """
+ Specific queryFactory for proxy that uses our custom
+ L{TestQueryFactory}, and save factories.
+ """
+ factory = TestQueryFactory(*args, **kwargs)
+ self.factories.append(factory)
+ return factory
+
+ def proxy(self):
+ p = xmlrpc.Proxy("http://127.0.0.1:%d/" % self.port)
+ p.queryFactory = self.queryFactory
+ return p
+
+ def test_results(self):
+ inputOutput = [
+ ("add", (2, 3), 5),
+ ("defer", ("a",), "a"),
+ ("dict", ({"a": 1}, "a"), 1),
+ ("pair", ("a", 1), ["a", 1]),
+ ("complex", (), {"a": ["b", "c", 12, []], "D": "foo"})]
+
+ dl = []
+ for meth, args, outp in inputOutput:
+ d = self.proxy().callRemote(meth, *args)
+ d.addCallback(self.assertEquals, outp)
+ dl.append(d)
+ return defer.DeferredList(dl, fireOnOneErrback=True)
+
+ def test_errors(self):
+ """
+ Verify that for each way a method exposed via XML-RPC can fail, the
+ correct 'Content-type' header is set in the response and that the
+ client-side Deferred is errbacked with an appropriate C{Fault}
+ instance.
+ """
+ dl = []
+ for code, methodName in [(666, "fail"), (666, "deferFail"),
+ (12, "fault"), (23, "noSuchMethod"),
+ (17, "deferFault"), (42, "SESSION_TEST")]:
+ d = self.proxy().callRemote(methodName)
+ d = self.assertFailure(d, xmlrpc.Fault)
+ d.addCallback(lambda exc, code=code:
+ self.assertEquals(exc.faultCode, code))
+ dl.append(d)
+ d = defer.DeferredList(dl, fireOnOneErrback=True)
+ def cb(ign):
+ for factory in self.factories:
+ self.assertEquals(factory.headers['content-type'],
+ 'text/xml')
+ self.flushLoggedErrors(TestRuntimeError, TestValueError)
+ d.addCallback(cb)
+ return d
+
+ def test_errorGet(self):
+ """
+ A classic GET on the xml server should return a NOT_ALLOWED.
+ """
+ d = client.getPage("http://127.0.0.1:%d/" % (self.port,))
+ d = self.assertFailure(d, error.Error)
+ d.addCallback(
+ lambda exc: self.assertEquals(int(exc.args[0]), http.NOT_ALLOWED))
+ return d
+
+ def test_errorXMLContent(self):
+ """
+ Test that an invalid XML input returns an L{xmlrpc.Fault}.
+ """
+ d = client.getPage("http://127.0.0.1:%d/" % (self.port,),
+ method="POST", postdata="foo")
+ def cb(result):
+ self.assertRaises(xmlrpc.Fault, xmlrpclib.loads, result)
+ d.addCallback(cb)
+ return d
+
+
+ def test_datetimeRoundtrip(self):
+ """
+ If an L{xmlrpclib.DateTime} is passed as an argument to an XML-RPC
+ call and then returned by the server unmodified, the result should
+ be equal to the original object.
+ """
+ when = xmlrpclib.DateTime()
+ d = self.proxy().callRemote("echo", when)
+ d.addCallback(self.assertEqual, when)
+ return d
+
+
+ def test_doubleEncodingError(self):
+ """
+ If it is not possible to encode a response to the request (for example,
+ because L{xmlrpclib.dumps} raises an exception when encoding a
+ L{Fault}) the exception which prevents the response from being
+ generated is logged and the request object is finished anyway.
+ """
+ d = self.proxy().callRemote("echo", "")
+
+ # *Now* break xmlrpclib.dumps. Hopefully the client already used it.
+ def fakeDumps(*args, **kwargs):
+ raise RuntimeError("Cannot encode anything at all!")
+ self.patch(xmlrpclib, 'dumps', fakeDumps)
+
+ # It doesn't matter how it fails, so long as it does. Also, it happens
+ # to fail with an implementation detail exception right now, not
+ # something suitable as part of a public interface.
+ d = self.assertFailure(d, Exception)
+
+ def cbFailed(ignored):
+ # The fakeDumps exception should have been logged.
+ self.assertEqual(len(self.flushLoggedErrors(RuntimeError)), 1)
+ d.addCallback(cbFailed)
+ return d
+
+
+
+class XMLRPCTestCase2(XMLRPCTestCase):
+ """
+ Test with proxy that doesn't add a slash.
+ """
+
+ def proxy(self):
+ p = xmlrpc.Proxy("http://127.0.0.1:%d" % self.port)
+ p.queryFactory = self.queryFactory
+ return p
+
+
+
+class XMLRPCAllowNoneTestCase(unittest.TestCase):
+ """
+ Test with allowNone set to True.
+
+ These are not meant to be exhaustive serialization tests, since
+ L{xmlrpclib} does all of the actual serialization work. They are just
+ meant to exercise a few codepaths to make sure we are calling into
+ xmlrpclib correctly.
+ """
+
+ def setUp(self):
+ self.p = reactor.listenTCP(
+ 0, server.Site(Test(allowNone=True)), interface="127.0.0.1")
+ self.port = self.p.getHost().port
+
+
+ def tearDown(self):
+ return self.p.stopListening()
+
+
+ def proxy(self):
+ return xmlrpc.Proxy("http://127.0.0.1:%d" % (self.port,),
+ allowNone=True)
+
+
+ def test_deferredNone(self):
+ """
+ Test that passing a C{None} as an argument to a remote method and
+ returning a L{Deferred} which fires with C{None} properly passes
+ </nil> over the network if allowNone is set to True.
+ """
+ d = self.proxy().callRemote('defer', None)
+ d.addCallback(self.assertEquals, None)
+ return d
+
+
+ def test_dictWithNoneValue(self):
+ """
+ Test that return a C{dict} with C{None} as a value works properly.
+ """
+ d = self.proxy().callRemote('defer', {'a': None})
+ d.addCallback(self.assertEquals, {'a': None})
+ return d
+
+
+
+class XMLRPCTestAuthenticated(XMLRPCTestCase):
+ """
+ Test with authenticated proxy. We run this with the same inout/ouput as
+ above.
+ """
+ user = "username"
+ password = "asecret"
+
+ def setUp(self):
+ self.p = reactor.listenTCP(0, server.Site(TestAuthHeader()),
+ interface="127.0.0.1")
+ self.port = self.p.getHost().port
+ self.factories = []
+
+
+ def test_authInfoInURL(self):
+ p = xmlrpc.Proxy("http://%s:%s@127.0.0.1:%d/" % (
+ self.user, self.password, self.port))
+ d = p.callRemote("authinfo")
+ d.addCallback(self.assertEquals, [self.user, self.password])
+ return d
+
+
+ def test_explicitAuthInfo(self):
+ p = xmlrpc.Proxy("http://127.0.0.1:%d/" % (
+ self.port,), self.user, self.password)
+ d = p.callRemote("authinfo")
+ d.addCallback(self.assertEquals, [self.user, self.password])
+ return d
+
+
+ def test_explicitAuthInfoOverride(self):
+ p = xmlrpc.Proxy("http://wrong:info@127.0.0.1:%d/" % (
+ self.port,), self.user, self.password)
+ d = p.callRemote("authinfo")
+ d.addCallback(self.assertEquals, [self.user, self.password])
+ return d
+
+
+class XMLRPCTestIntrospection(XMLRPCTestCase):
+
+ def setUp(self):
+ xmlrpc = Test()
+ addIntrospection(xmlrpc)
+ self.p = reactor.listenTCP(0, server.Site(xmlrpc),interface="127.0.0.1")
+ self.port = self.p.getHost().port
+ self.factories = []
+
+ def test_listMethods(self):
+
+ def cbMethods(meths):
+ meths.sort()
+ self.assertEqual(
+ meths,
+ ['add', 'complex', 'defer', 'deferFail',
+ 'deferFault', 'dict', 'echo', 'fail', 'fault',
+ 'pair', 'system.listMethods',
+ 'system.methodHelp',
+ 'system.methodSignature'])
+
+ d = self.proxy().callRemote("system.listMethods")
+ d.addCallback(cbMethods)
+ return d
+
+ def test_methodHelp(self):
+ inputOutputs = [
+ ("defer", "Help for defer."),
+ ("fail", ""),
+ ("dict", "Help for dict.")]
+
+ dl = []
+ for meth, expected in inputOutputs:
+ d = self.proxy().callRemote("system.methodHelp", meth)
+ d.addCallback(self.assertEquals, expected)
+ dl.append(d)
+ return defer.DeferredList(dl, fireOnOneErrback=True)
+
+ def test_methodSignature(self):
+ inputOutputs = [
+ ("defer", ""),
+ ("add", [['int', 'int', 'int'],
+ ['double', 'double', 'double']]),
+ ("pair", [['array', 'string', 'int']])]
+
+ dl = []
+ for meth, expected in inputOutputs:
+ d = self.proxy().callRemote("system.methodSignature", meth)
+ d.addCallback(self.assertEquals, expected)
+ dl.append(d)
+ return defer.DeferredList(dl, fireOnOneErrback=True)
+
+
+class XMLRPCClientErrorHandling(unittest.TestCase):
+ """
+ Test error handling on the xmlrpc client.
+ """
+ def setUp(self):
+ self.resource = static.Data(
+ "This text is not a valid XML-RPC response.",
+ "text/plain")
+ self.resource.isLeaf = True
+ self.port = reactor.listenTCP(0, server.Site(self.resource),
+ interface='127.0.0.1')
+
+ def tearDown(self):
+ return self.port.stopListening()
+
+ def test_erroneousResponse(self):
+ """
+ Test that calling the xmlrpc client on a static http server raises
+ an exception.
+ """
+ proxy = xmlrpc.Proxy("http://127.0.0.1:%d/" %
+ (self.port.getHost().port,))
+ return self.assertFailure(proxy.callRemote("someMethod"), Exception)
+
+
+
+class TestQueryFactoryParseResponse(unittest.TestCase):
+ """
+ Test the behaviour of L{_QueryFactory.parseResponse}.
+ """
+
+ def setUp(self):
+ # The _QueryFactory that we are testing. We don't care about any
+ # of the constructor parameters.
+ self.queryFactory = _QueryFactory(
+ path=None, host=None, method='POST', user=None, password=None,
+ allowNone=False, args=())
+ # An XML-RPC response that will parse without raising an error.
+ self.goodContents = xmlrpclib.dumps(('',))
+ # An 'XML-RPC response' that will raise a parsing error.
+ self.badContents = 'invalid xml'
+ # A dummy 'reason' to pass to clientConnectionLost. We don't care
+ # what it is.
+ self.reason = failure.Failure(ConnectionDone())
+
+
+ def test_parseResponseCallbackSafety(self):
+ """
+ We can safely call L{_QueryFactory.clientConnectionLost} as a callback
+ of L{_QueryFactory.parseResponse}.
+ """
+ d = self.queryFactory.deferred
+ # The failure mode is that this callback raises an AlreadyCalled
+ # error. We have to add it now so that it gets called synchronously
+ # and triggers the race condition.
+ d.addCallback(self.queryFactory.clientConnectionLost, self.reason)
+ self.queryFactory.parseResponse(self.goodContents)
+ return d
+
+
+ def test_parseResponseErrbackSafety(self):
+ """
+ We can safely call L{_QueryFactory.clientConnectionLost} as an errback
+ of L{_QueryFactory.parseResponse}.
+ """
+ d = self.queryFactory.deferred
+ # The failure mode is that this callback raises an AlreadyCalled
+ # error. We have to add it now so that it gets called synchronously
+ # and triggers the race condition.
+ d.addErrback(self.queryFactory.clientConnectionLost, self.reason)
+ self.queryFactory.parseResponse(self.badContents)
+ return d
+
+
+ def test_badStatusErrbackSafety(self):
+ """
+ We can safely call L{_QueryFactory.clientConnectionLost} as an errback
+ of L{_QueryFactory.badStatus}.
+ """
+ d = self.queryFactory.deferred
+ # The failure mode is that this callback raises an AlreadyCalled
+ # error. We have to add it now so that it gets called synchronously
+ # and triggers the race condition.
+ d.addErrback(self.queryFactory.clientConnectionLost, self.reason)
+ self.queryFactory.badStatus('status', 'message')
+ return d
+
+ def test_parseResponseWithoutData(self):
+ """
+ Some server can send a response without any data:
+ L{_QueryFactory.parseResponse} should catch the error and call the
+ result errback.
+ """
+ content = """
+<methodResponse>
+ <params>
+ <param>
+ </param>
+ </params>
+</methodResponse>"""
+ d = self.queryFactory.deferred
+ self.queryFactory.parseResponse(content)
+ return self.assertFailure(d, IndexError)
diff --git a/vendor/Twisted-10.0.0/twisted/web/topfiles/NEWS b/vendor/Twisted-10.0.0/twisted/web/topfiles/NEWS
new file mode 100644
index 0000000000..e0cd0dc5dc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/topfiles/NEWS
@@ -0,0 +1,309 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/<number>
+
+Twisted Web 10.0.0 (2010-03-01)
+===============================
+
+Features
+--------
+ - Twisted Web in 60 Seconds, a series of short tutorials with self-
+ contained examples on a range of common web topics, is now a part
+ of the Twisted Web howto documentation. (#4192)
+
+Bugfixes
+--------
+ - Data and File from twisted.web.static and
+ twisted.web.distrib.UserDirectory will now only generate a 200
+ response for GET or HEAD requests.
+ twisted.web.client.HTTPPageGetter will no longer ignore the case of
+ a request method when considering whether to apply special HEAD
+ processing to a response. (#446)
+
+ - twisted.web.http.HTTPClient now supports multi-line headers.
+ (#2062)
+
+ - Resources served via twisted.web.distrib will no longer encounter a
+ Banana error when writing more than 640kB at once to the request
+ object. (#3212)
+
+ - The Error, PageRedirect, and InfiniteRedirection exception in
+ twisted.web now initialize an empty message parameter by mapping
+ the HTTP status code parameter to a descriptive string. Previously
+ the lookup would always fail, leaving message empty. (#3806)
+
+ - The 'wsgi.input' WSGI environment object now supports -1 and None
+ as arguments to the read and readlines methods. (#4114)
+
+ - twisted.web.wsgi doesn't unquote QUERY_STRING anymore, thus
+ complying with the WSGI reference implementation. (#4143)
+
+ - The HTTP proxy will no longer pass on keep-alive request headers
+ from the client, preventing pages from loading then "hanging"
+ (leaving the connection open with no hope of termination). (#4179)
+
+Deprecations and Removals
+-------------------------
+ - Remove '--static' option from twistd web, that served as an alias
+ for the '--path' option. (#3907)
+
+Other
+-----
+ - #3784, #4216, #4242
+
+
+Twisted Web 9.0.0 (2009-11-24)
+==============================
+
+Features
+--------
+ - There is now an iweb.IRequest interface which specifies the interface that
+ request objects provide (#3416)
+ - downloadPage now supports the same cookie, redirect, and timeout features
+ that getPage supports (#2971)
+ - A chapter about WSGI has been added to the twisted.web documentation (#3510)
+ - The HTTP auth support in the web server now allows anonymous sessions by
+ logging in with ANONYMOUS credentials when no Authorization header is
+ provided in a request (#3924, #3936)
+ - HTTPClientFactory now accepts a parameter to enable a common deviation from
+ the HTTP 1.1 standard by responding to redirects in a POSTed request with a
+ GET instead of another POST (#3624)
+ - A new basic HTTP/1.1 client API is included in twisted.web.client.Agent
+ (#886, #3987)
+
+Fixes
+-----
+ - Requests for "insecure" children of a static.File (such as paths containing
+ encoded directory separators) will now result in a 404 instead of a 500
+ (#3549, #3469)
+ - When specifying a followRedirect argument to the getPage function, the state
+ of redirect-following for other getPage calls should now be unaffected. It
+ was previously overwriting a class attribute which would affect outstanding
+ getPage calls (#3192)
+ - Downloading an URL of the form "http://example.com:/" will now work,
+ ignoring the extraneous colon (#2402)
+ - microdom's appendChild method will no longer issue a spurious warning, and
+ microdom's methods in general should now issue more meaningful exceptions
+ when invalid parameters are passed (#3421)
+ - WSGI applications will no longer have spurious Content-Type headers added to
+ their responses by the twisted.web server. In addition, WSGI applications
+ will no longer be able to specify the server-restricted headers Server and
+ Date (#3569)
+ - http_headers.Headers now normalizes the case of raw headers passed directly
+ to it in the same way that it normalizes the headers passed to setRawHeaders
+ (#3557)
+ - The distrib module no longer relies on the deprecated woven package (#3559)
+ - twisted.web.domhelpers now works with both microdom and minidom (#3600)
+ - twisted.web servers will now ignore invalid If-Modified-Since headers instead
+ of returning a 500 error (#3601)
+ - Certain request-bound memory and file resources are cleaned up slightly
+ sooner by the request when the connection is lost (#1621, #3176)
+ - xmlrpclib.DateTime objects should now correctly round-trip over twisted.web's
+ XMLRPC support in all supported versions of Python, and errors during error
+ serialization will no longer hang a twisted.web XMLRPC response (#2446)
+ - request.content should now always be seeked to the beginning when
+ request.process is called, so application code should never need to seek
+ back manually (#3585)
+ - Fetching a child of static.File with a double-slash in the URL (such as
+ "example//foo.html") should now return a 404 instead of a traceback and
+ 500 error (#3631)
+ - downloadPage will now fire a Failure on its returned Deferred instead of
+ indicating success when the connection is prematurely lost (#3645)
+ - static.File will now provide a 404 instead of a 500 error when it was
+ constructed with a non-existent file (#3634)
+ - microdom should now serialize namespaces correctly (#3672)
+ - The HTTP Auth support resource wrapper should no longer corrupt requests and
+ cause them to skip a segment in the request path (#3679)
+ - The twisted.web WSGI support should now include leading slashes in PATH_INFO,
+ and SCRIPT_NAME will be empty if the application is at the root of the
+ resource tree. This means that WSGI applications should no longer generate
+ URLs with double-slashes in them even if they naively concatenate the values
+ (#3721)
+ - WSGI applications should now receive the requesting client's IP in the
+ REMOTE_ADDR environment variable (#3730)
+ - The distrib module should work again. It was unfortunately broken with the
+ refactoring of twisted.web's header support (#3697)
+ - static.File now supports multiple ranges specified in the Range header
+ (#3574)
+ - static.File should now generate a correct Content-Length value when the
+ requested Range value doesn't fit entirely within the file's contents (#3814)
+ - Attempting to call request.finish() after the connection has been lost will
+ now immediately raise a RuntimeError (#4013)
+ - An HTTP-auth resource should now be able to directly render the wrapped
+ avatar, whereas before it would only allow retrieval of child resources
+ (#4014)
+ - twisted.web's wsgi support should no longer attempt to call request.finish
+ twice, which would cause errors in certain cases (#4025)
+ - WSGI applications should now be able to handle requests with large bodies
+ (#4029)
+ - Exceptions raised from WSGI applications should now more reliably be turned
+ into 500 errors on the HTTP level (#4019)
+ - DeferredResource now correctly passes through exceptions raised from the
+ wrapped resource, instead of turning them all into 500 errors (#3932)
+ - Agent.request now generates a Host header when no headers are passed at
+ (#4131)
+
+Deprecations and Removals
+-------------------------
+ - The unmaintained and untested twisted.web.monitor module was removed (#2763)
+ - The twisted.web.woven package has been removed (#1522)
+ - All of the error resources in twisted.web.error are now in
+ twisted.web.resource, and accessing them through twisted.web.error is now
+ deprecated (#3035)
+ - To facilitate a simplification of the timeout logic in server.Session,
+ various things have been deprecated (#3457)
+ - the loopFactory attribute is now ignored
+ - the checkExpired method now does nothing
+ - the lifetime parameter to startCheckingExpiration is now ignored
+ - The twisted.web.trp module is now deprecated (#2030)
+
+Other
+-----
+ - #2763, #3540, #3575, #3610, #3605, #1176, #3539, #3750, #3761, #3779, #2677,
+ #3782, #3904, #3919, #3418, #3990, #1404, #4050
+
+
+Web 8.2.0 (2008-12-16)
+======================
+
+Features
+--------
+ - The web server can now deal with multi-value headers in the new attributes of
+ Request, requestHeaders and responseHeaders (#165)
+ - There is now a resource-wrapper which implements HTTP Basic and Digest auth
+ in terms of twisted.cred (#696)
+ - It's now possible to limit the number of redirects that client.getPage will
+ follow (#2412)
+ - The directory-listing code no longer uses Woven (#3257)
+ - static.File now supports Range headers with a single range (#1493)
+ - twisted.web now has a rudimentary WSGI container (#2753)
+ - The web server now supports chunked encoding in requests (#3385)
+
+Fixes
+-----
+ - The xmlrpc client now raises an error when the server sends an empty
+ response (#3399)
+ - HTTPPageGetter no longer duplicates default headers when they're explicitly
+ overridden in the headers parameter (#1382)
+ - The server will no longer timeout clients which are still sending request
+ data (#1903)
+ - microdom's isEqualToNode now returns False when the nodes aren't equal
+ (#2542)
+
+Deprecations and Removals
+-------------------------
+
+ - Request.headers and Request.received_headers are not quite deprecated, but
+ they are discouraged in favor of requestHeaders and responseHeaders (#165)
+
+Other
+-----
+ - #909, #687, #2938, #1152, #2930, #2025, #2683, #3471
+
+
+8.1.0 (2008-05-18)
+==================
+
+Fixes
+-----
+
+ - Fixed an XMLRPC bug whereby sometimes a callRemote Deferred would
+ accidentally be fired twice when a connection was lost during the handling of
+ a response (#3152)
+ - Fixed a bug in the "Using Twisted Web" document which prevented an example
+ resource from being renderable (#3147)
+ - The deprecated mktap API is no longer used (#3127)
+
+
+8.0.0 (2008-03-17)
+==================
+
+Features
+--------
+ - Add support to twisted.web.client.getPage for the HTTP HEAD method. (#2750)
+
+Fixes
+-----
+ - Set content-type in xmlrpc responses to "text/xml" (#2430)
+ - Add more error checking in the xmlrpc.XMLRPC render method, and enforce
+ POST requests. (#2505)
+ - Reject unicode input to twisted.web.client._parse to reject invalid
+ unicode URLs early. (#2628)
+ - Correctly re-quote URL path segments when generating an URL string to
+ return from Request.prePathURL. (#2934)
+ - Make twisted.web.proxy.ProxyClientFactory close the connection when
+ reporting a 501 error. (#1089)
+ - Fix twisted.web.proxy.ReverseProxyResource to specify the port in the
+ host header if different from 80. (#1117)
+ - Change twisted.web.proxy.ReverseProxyResource so that it correctly encodes
+ the request URI it sends on to the server for which it is a proxy. (#3013)
+ - Make "twistd web --personal" use PBServerFactory (#2681)
+
+Misc
+----
+ - #1996, #2382, #2211, #2633, #2634, #2640, #2752, #238, #2905
+
+
+0.7.0 (2007-01-02)
+==================
+
+Features
+--------
+ - Python 2.5 is now supported (#1867)
+ - twisted.web.xmlrpc now supports the <nil/> xml-rpc extension type
+ in both the server and the client (#469)
+
+Fixes
+-----
+ - Microdom and SUX now manages certain malformed XML more resiliently
+ (#1984, #2225, #2298)
+ - twisted.web.client.getPage can now deal with an URL of the form
+ "http://example.com" (no trailing slash) (#1080)
+ - The HTTP server now allows (invalid) URLs with multiple question
+ marks (#1550)
+ - '=' can now be in the value of a cookie (#1051)
+ - Microdom now correctly handles xmlns="" (#2184)
+
+Deprecations and Removals
+-------------------------
+ - websetroot was removed, because it wasn't working anyway (#945)
+ - woven.guard no longer supports the old twisted.cred API (#1440)
+
+Other
+-----
+The following changes are minor or closely related to other changes.
+
+ - #1636, #1637, #1638, #1936, #1883, #447
+
+
+0.6.0 (2006-05-21)
+==================
+
+Features
+--------
+ - Basic auth support for the XMLRPC client (#1474).
+
+Fixes
+-----
+ - More correct datetime parsing.
+ - Efficiency improvements (#974)
+ - Handle popular non-RFC compliant formats for If-Modified-Since
+ headers (#976).
+ - Improve support for certain buggy CGI scripts.
+ - CONTENT_LENGTH is now available to CGI scripts.
+ - Support for even worse HTML in microdom (#1358).
+ - Trying to view a user's home page when the user doesn't have a
+ ~/public_html no longer displays a traceback (#551).
+ - Misc: #543, #1011, #1005, #1287, #1337, #1383, #1079, #1492, #1189,
+ #737, #872.
+
+
+0.5.0
+=====
+ - Client properly reports timeouts as error
+ - "Socially deprecate" woven
+ - Fix memory leak in _c_urlarg library
+ - Stop using _c_urlarg library
+ - Fix 'gzip' and 'bzip2' content-encodings
+ - Escape log entries so remote user cannot corrupt the log
+ - Commented out range support because it's broken
+ - Fix HEAD responses without content-length
diff --git a/vendor/Twisted-10.0.0/twisted/web/topfiles/README b/vendor/Twisted-10.0.0/twisted/web/topfiles/README
new file mode 100644
index 0000000000..c245997f72
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/topfiles/README
@@ -0,0 +1 @@
+Twisted Web 10.0.0
diff --git a/vendor/Twisted-10.0.0/twisted/web/topfiles/setup.py b/vendor/Twisted-10.0.0/twisted/web/topfiles/setup.py
new file mode 100644
index 0000000000..15eefc0ae0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/topfiles/setup.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+
+try:
+ from twisted.python import dist
+except ImportError:
+ raise SystemExit("twisted.python.dist module not found. Make sure you "
+ "have installed the Twisted core package before "
+ "attempting to install any other Twisted projects.")
+
+if __name__ == '__main__':
+ dist.setup(
+ twisted_subproject="web",
+ scripts=dist.getScripts("web"),
+ # metadata
+ name="Twisted Web",
+ description="Twisted web server, programmable in Python.",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="James Knight",
+ url="http://twistedmatrix.com/trac/wiki/TwistedWeb",
+ license="MIT",
+ long_description="""\
+Twisted Web is a complete web server, aimed at hosting web
+applications using Twisted and Python, but fully able to serve static
+pages, also.
+""",
+ )
diff --git a/vendor/Twisted-10.0.0/twisted/web/trp.py b/vendor/Twisted-10.0.0/twisted/web/trp.py
new file mode 100644
index 0000000000..e9cfcd3fd5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/trp.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+I contain ResourceUnpickler, which will unpickle any python object
+named with the file extension .trp.
+"""
+
+import warnings
+from pickle import Unpickler
+
+_msg = ("is deprecated as of Twisted 9.0. Resource persistence "
+ "is beyond the scope of Twisted Web.")
+
+warnings.warn("twisted.web.trp " + _msg , DeprecationWarning, stacklevel=2)
+
+def ResourceUnpickler(path, registry = None):
+ warnings.warn(
+ "twisted.web.trp.ResourceUnpickler " + _msg ,
+ DeprecationWarning, stacklevel=2)
+ fl = open(path)
+ result = Unpickler(fl).load()
+ return result
diff --git a/vendor/Twisted-10.0.0/twisted/web/twcgi.py b/vendor/Twisted-10.0.0/twisted/web/twcgi.py
new file mode 100644
index 0000000000..c2868985c8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/twcgi.py
@@ -0,0 +1,253 @@
+# -*- test-case-name: twisted.web.test.test_cgi -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+I hold resource classes and helper classes that deal with CGI scripts.
+"""
+
+# System Imports
+import string
+import os
+import sys
+import urllib
+
+# Twisted Imports
+from twisted.web import http
+from twisted.internet import reactor, protocol
+from twisted.spread import pb
+from twisted.python import log, filepath
+from twisted.web import resource, server, static
+
+
+class CGIDirectory(resource.Resource, filepath.FilePath):
+ def __init__(self, pathname):
+ resource.Resource.__init__(self)
+ filepath.FilePath.__init__(self, pathname)
+
+ def getChild(self, path, request):
+ fnp = self.child(path)
+ if not fnp.exists():
+ return static.File.childNotFound
+ elif fnp.isdir():
+ return CGIDirectory(fnp.path)
+ else:
+ return CGIScript(fnp.path)
+ return resource.NoResource()
+
+ def render(self, request):
+ notFound = resource.NoResource(
+ "CGI directories do not support directory listing.")
+ return notFound.render(request)
+
+class CGIScript(resource.Resource):
+ """I represent a CGI script.
+
+ My implementation is complex due to the fact that it requires asynchronous
+ IPC with an external process with an unpleasant protocol.
+ """
+ isLeaf = 1
+ def __init__(self, filename, registry=None):
+ """Initialize, with the name of a CGI script file.
+ """
+ self.filename = filename
+
+ def render(self, request):
+ """Do various things to conform to the CGI specification.
+
+ I will set up the usual slew of environment variables, then spin off a
+ process.
+ """
+ script_name = "/"+string.join(request.prepath, '/')
+ python_path = string.join(sys.path, os.pathsep)
+ serverName = string.split(request.getRequestHostname(), ':')[0]
+ env = {"SERVER_SOFTWARE": server.version,
+ "SERVER_NAME": serverName,
+ "GATEWAY_INTERFACE": "CGI/1.1",
+ "SERVER_PROTOCOL": request.clientproto,
+ "SERVER_PORT": str(request.getHost().port),
+ "REQUEST_METHOD": request.method,
+ "SCRIPT_NAME": script_name, # XXX
+ "SCRIPT_FILENAME": self.filename,
+ "REQUEST_URI": request.uri,
+ }
+
+ client = request.getClient()
+ if client is not None:
+ env['REMOTE_HOST'] = client
+ ip = request.getClientIP()
+ if ip is not None:
+ env['REMOTE_ADDR'] = ip
+ pp = request.postpath
+ if pp:
+ env["PATH_INFO"] = "/"+string.join(pp, '/')
+
+ if hasattr(request, "content"):
+ # request.content is either a StringIO or a TemporaryFile, and
+ # the file pointer is sitting at the beginning (seek(0,0))
+ request.content.seek(0,2)
+ length = request.content.tell()
+ request.content.seek(0,0)
+ env['CONTENT_LENGTH'] = str(length)
+
+ qindex = string.find(request.uri, '?')
+ if qindex != -1:
+ qs = env['QUERY_STRING'] = request.uri[qindex+1:]
+ if '=' in qs:
+ qargs = []
+ else:
+ qargs = [urllib.unquote(x) for x in qs.split('+')]
+ else:
+ env['QUERY_STRING'] = ''
+ qargs = []
+
+ # Propogate HTTP headers
+ for title, header in request.getAllHeaders().items():
+ envname = string.upper(string.replace(title, '-', '_'))
+ if title not in ('content-type', 'content-length'):
+ envname = "HTTP_" + envname
+ env[envname] = header
+ # Propogate our environment
+ for key, value in os.environ.items():
+ if not env.has_key(key):
+ env[key] = value
+ # And they're off!
+ self.runProcess(env, request, qargs)
+ return server.NOT_DONE_YET
+
+ def runProcess(self, env, request, qargs=[]):
+ p = CGIProcessProtocol(request)
+ reactor.spawnProcess(p, self.filename, [self.filename]+qargs, env, os.path.dirname(self.filename))
+
+
+class FilteredScript(CGIScript):
+ """I am a special version of a CGI script, that uses a specific executable.
+
+ This is useful for interfacing with other scripting languages that adhere
+ to the CGI standard (cf. PHPScript). My 'filter' attribute specifies what
+ executable to run, and my 'filename' init parameter describes which script
+ to pass to the first argument of that script.
+ """
+
+ filter = '/usr/bin/cat'
+
+ def runProcess(self, env, request, qargs=[]):
+ p = CGIProcessProtocol(request)
+ reactor.spawnProcess(p, self.filter, [self.filter, self.filename]+qargs, env, os.path.dirname(self.filename))
+
+
+class PHP3Script(FilteredScript):
+ """I am a FilteredScript that uses the default PHP3 command on most systems.
+ """
+
+ filter = '/usr/bin/php3'
+
+
+class PHPScript(FilteredScript):
+ """I am a FilteredScript that uses the PHP command on most systems.
+ Sometimes, php wants the path to itself as argv[0]. This is that time.
+ """
+
+ filter = '/usr/bin/php4'
+
+
+class CGIProcessProtocol(protocol.ProcessProtocol, pb.Viewable):
+ handling_headers = 1
+ headers_written = 0
+ headertext = ''
+ errortext = ''
+
+ # Remotely relay producer interface.
+
+ def view_resumeProducing(self, issuer):
+ self.resumeProducing()
+
+ def view_pauseProducing(self, issuer):
+ self.pauseProducing()
+
+ def view_stopProducing(self, issuer):
+ self.stopProducing()
+
+ def resumeProducing(self):
+ self.transport.resumeProducing()
+
+ def pauseProducing(self):
+ self.transport.pauseProducing()
+
+ def stopProducing(self):
+ self.transport.loseConnection()
+
+ def __init__(self, request):
+ self.request = request
+
+ def connectionMade(self):
+ self.request.registerProducer(self, 1)
+ self.request.content.seek(0, 0)
+ content = self.request.content.read()
+ if content:
+ self.transport.write(content)
+ self.transport.closeStdin()
+
+ def errReceived(self, error):
+ self.errortext = self.errortext + error
+
+ def outReceived(self, output):
+ """
+ Handle a chunk of input
+ """
+ # First, make sure that the headers from the script are sorted
+ # out (we'll want to do some parsing on these later.)
+ if self.handling_headers:
+ text = self.headertext + output
+ headerEnds = []
+ for delimiter in '\n\n','\r\n\r\n','\r\r', '\n\r\n':
+ headerend = string.find(text,delimiter)
+ if headerend != -1:
+ headerEnds.append((headerend, delimiter))
+ if headerEnds:
+ headerEnds.sort()
+ headerend, delimiter = headerEnds[0]
+ self.headertext = text[:headerend]
+ # This is a final version of the header text.
+ linebreak = delimiter[:len(delimiter)/2]
+ headers = string.split(self.headertext, linebreak)
+ for header in headers:
+ br = string.find(header,': ')
+ if br == -1:
+ log.msg( 'ignoring malformed CGI header: %s' % header )
+ else:
+ headerName = string.lower(header[:br])
+ headerText = header[br+2:]
+ if headerName == 'location':
+ self.request.setResponseCode(http.FOUND)
+ if headerName == 'status':
+ try:
+ statusNum = int(headerText[:3]) #"XXX <description>" sometimes happens.
+ except:
+ log.msg( "malformed status header" )
+ else:
+ self.request.setResponseCode(statusNum)
+ else:
+ self.request.setHeader(headerName,headerText)
+ output = text[headerend+len(delimiter):]
+ self.handling_headers = 0
+ if self.handling_headers:
+ self.headertext = text
+ if not self.handling_headers:
+ self.request.write(output)
+
+ def processEnded(self, reason):
+ if reason.value.exitCode != 0:
+ log.msg("CGI %s exited with exit code %s" %
+ (self.request.uri, reason.value.exitCode))
+ if self.errortext:
+ log.msg("Errors from CGI %s: %s" % (self.request.uri, self.errortext))
+ if self.handling_headers:
+ log.msg("Premature end of headers in %s: %s" % (self.request.uri, self.headertext))
+ self.request.write(
+ resource.ErrorPage(http.INTERNAL_SERVER_ERROR,
+ "CGI Script Error",
+ "Premature end of script headers.").render(self.request))
+ self.request.unregisterProducer()
+ self.request.finish()
diff --git a/vendor/Twisted-10.0.0/twisted/web/util.py b/vendor/Twisted-10.0.0/twisted/web/util.py
new file mode 100644
index 0000000000..6b4b222748
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/util.py
@@ -0,0 +1,380 @@
+# -*- test-case-name: twisted.web.test.test_web -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from cStringIO import StringIO
+import linecache
+import string, re
+import types
+
+from twisted.python import failure
+
+from twisted.web import html, resource
+
+
+def redirectTo(URL, request):
+ request.redirect(URL)
+ return """
+<html>
+ <head>
+ <meta http-equiv=\"refresh\" content=\"0;URL=%(url)s\">
+ </head>
+ <body bgcolor=\"#FFFFFF\" text=\"#000000\">
+ <a href=\"%(url)s\">click here</a>
+ </body>
+</html>
+""" % {'url': URL}
+
+class Redirect(resource.Resource):
+
+ isLeaf = 1
+
+ def __init__(self, url):
+ resource.Resource.__init__(self)
+ self.url = url
+
+ def render(self, request):
+ return redirectTo(self.url, request)
+
+ def getChild(self, name, request):
+ return self
+
+class ChildRedirector(Redirect):
+ isLeaf = 0
+ def __init__(self, url):
+ # XXX is this enough?
+ if ((url.find('://') == -1)
+ and (not url.startswith('..'))
+ and (not url.startswith('/'))):
+ raise ValueError("It seems you've given me a redirect (%s) that is a child of myself! That's not good, it'll cause an infinite redirect." % url)
+ Redirect.__init__(self, url)
+
+ def getChild(self, name, request):
+ newUrl = self.url
+ if not newUrl.endswith('/'):
+ newUrl += '/'
+ newUrl += name
+ return ChildRedirector(newUrl)
+
+
+from twisted.python import urlpath
+
+class ParentRedirect(resource.Resource):
+ """
+ I redirect to URLPath.here().
+ """
+ isLeaf = 1
+ def render(self, request):
+ return redirectTo(urlpath.URLPath.fromRequest(request).here(), request)
+
+ def getChild(self, request):
+ return self
+
+
+class DeferredResource(resource.Resource):
+ """
+ I wrap up a Deferred that will eventually result in a Resource
+ object.
+ """
+ isLeaf = 1
+
+ def __init__(self, d):
+ resource.Resource.__init__(self)
+ self.d = d
+
+ def getChild(self, name, request):
+ return self
+
+ def render(self, request):
+ self.d.addCallback(self._cbChild, request).addErrback(
+ self._ebChild,request)
+ from twisted.web.server import NOT_DONE_YET
+ return NOT_DONE_YET
+
+ def _cbChild(self, child, request):
+ request.render(resource.getChildForRequest(child, request))
+
+ def _ebChild(self, reason, request):
+ request.processingFailed(reason)
+ return reason
+
+
+stylesheet = """
+<style type="text/css">
+ p.error {
+ color: red;
+ font-family: Verdana, Arial, helvetica, sans-serif;
+ font-weight: bold;
+ }
+
+ div {
+ font-family: Verdana, Arial, helvetica, sans-serif;
+ }
+
+ div.stackTrace {
+ }
+
+ div.frame {
+ padding: 1em;
+ background: white;
+ border-bottom: thin black dashed;
+ }
+
+ div.firstFrame {
+ padding: 1em;
+ background: white;
+ border-top: thin black dashed;
+ border-bottom: thin black dashed;
+ }
+
+ div.location {
+ }
+
+ div.snippet {
+ margin-bottom: 0.5em;
+ margin-left: 1em;
+ background: #FFFFDD;
+ }
+
+ div.snippetHighlightLine {
+ color: red;
+ }
+
+ span.code {
+ font-family: "Courier New", courier, monotype;
+ }
+
+ span.function {
+ font-weight: bold;
+ font-family: "Courier New", courier, monotype;
+ }
+
+ table.variables {
+ border-collapse: collapse;
+ margin-left: 1em;
+ }
+
+ td.varName {
+ vertical-align: top;
+ font-weight: bold;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ }
+
+ td.varValue {
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ }
+
+ div.variables {
+ margin-bottom: 0.5em;
+ }
+
+ span.heading {
+ font-weight: bold;
+ }
+
+ div.dict {
+ background: #cccc99;
+ padding: 2px;
+ float: left;
+ }
+
+ td.dictKey {
+ background: #ffff99;
+ font-weight: bold;
+ }
+
+ td.dictValue {
+ background: #ffff99;
+ }
+
+ div.list {
+ background: #7777cc;
+ padding: 2px;
+ float: left;
+ }
+
+ div.listItem {
+ background: #9999ff;
+ }
+
+ div.instance {
+ background: #cc7777;
+ padding: 2px;
+ float: left;
+ }
+
+ span.instanceName {
+ font-weight: bold;
+ display: block;
+ }
+
+ span.instanceRepr {
+ background: #ff9999;
+ font-family: "Courier New", courier, monotype;
+ }
+
+ div.function {
+ background: orange;
+ font-weight: bold;
+ float: left;
+ }
+</style>
+"""
+
+
+def htmlrepr(x):
+ return htmlReprTypes.get(type(x), htmlUnknown)(x)
+
+def saferepr(x):
+ try:
+ rx = repr(x)
+ except:
+ rx = "<repr failed! %s instance at %s>" % (x.__class__, id(x))
+ return rx
+
+def htmlUnknown(x):
+ return '<code>'+html.escape(saferepr(x))+'</code>'
+
+def htmlDict(d):
+ io = StringIO()
+ w = io.write
+ w('<div class="dict"><span class="heading">Dictionary instance @ %s</span>' % hex(id(d)))
+ w('<table class="dict">')
+ for k, v in d.items():
+
+ if k == '__builtins__':
+ v = 'builtin dictionary'
+ w('<tr><td class="dictKey">%s</td><td class="dictValue">%s</td></tr>' % (htmlrepr(k), htmlrepr(v)))
+ w('</table></div>')
+ return io.getvalue()
+
+def htmlList(l):
+ io = StringIO()
+ w = io.write
+ w('<div class="list"><span class="heading">List instance @ %s</span>' % hex(id(l)))
+ for i in l:
+ w('<div class="listItem">%s</div>' % htmlrepr(i))
+ w('</div>')
+ return io.getvalue()
+
+def htmlInst(i):
+ if hasattr(i, "__html__"):
+ s = i.__html__()
+ else:
+ s = html.escape(saferepr(i))
+ return '''<div class="instance"><span class="instanceName">%s instance @ %s</span>
+ <span class="instanceRepr">%s</span></div>
+ ''' % (i.__class__, hex(id(i)), s)
+
+def htmlString(s):
+ return html.escape(saferepr(s))
+
+def htmlFunc(f):
+ return ('<div class="function">' +
+ html.escape("function %s in file %s at line %s" %
+ (f.__name__, f.func_code.co_filename,
+ f.func_code.co_firstlineno))+
+ '</div>')
+
+htmlReprTypes = {types.DictType: htmlDict,
+ types.ListType: htmlList,
+ types.InstanceType: htmlInst,
+ types.StringType: htmlString,
+ types.FunctionType: htmlFunc}
+
+
+
+def htmlIndent(snippetLine):
+ ret = string.replace(string.replace(html.escape(string.rstrip(snippetLine)),
+ ' ', '&nbsp;'),
+ '\t', '&nbsp; &nbsp; &nbsp; &nbsp; ')
+ return ret
+
+def formatFailure(myFailure):
+
+ exceptionHTML = """
+<p class="error">%s: %s</p>
+"""
+
+ frameHTML = """
+<div class="location">%s, line %s in <span class="function">%s</span></div>
+"""
+
+ snippetLineHTML = """
+<div class="snippetLine"><span class="lineno">%s</span><span class="code">%s</span></div>
+"""
+
+ snippetHighlightLineHTML = """
+<div class="snippetHighlightLine"><span class="lineno">%s</span><span class="code">%s</span></div>
+"""
+
+ variableHTML = """
+<tr class="varRow"><td class="varName">%s</td><td class="varValue">%s</td></tr>
+"""
+
+ if not isinstance(myFailure, failure.Failure):
+ return html.PRE(str(myFailure))
+ io = StringIO()
+ w = io.write
+ w(stylesheet)
+ w('<a href="#tbend">')
+ w(exceptionHTML % (html.escape(str(myFailure.type)),
+ html.escape(str(myFailure.value))))
+ w('</a>')
+ w('<div class="stackTrace">')
+ first = 1
+ for method, filename, lineno, localVars, globalVars in myFailure.frames:
+ if filename == '<string>':
+ continue
+ if first:
+ w('<div class="firstFrame">')
+ first = 0
+ else:
+ w('<div class="frame">')
+ w(frameHTML % (filename, lineno, method))
+
+ w('<div class="snippet">')
+ textSnippet = ''
+ for snipLineNo in range(lineno-2, lineno+2):
+ snipLine = linecache.getline(filename, snipLineNo)
+ textSnippet += snipLine
+ snipLine = htmlIndent(snipLine)
+ if snipLineNo == lineno:
+ w(snippetHighlightLineHTML % (snipLineNo, snipLine))
+ else:
+ w(snippetLineHTML % (snipLineNo, snipLine))
+ w('</div>')
+
+ # Instance variables
+ for name, var in localVars:
+ if name == 'self' and hasattr(var, '__dict__'):
+ usedVars = [ (key, value) for (key, value) in var.__dict__.items()
+ if re.search(r'\W'+'self.'+key+r'\W', textSnippet) ]
+ if usedVars:
+ w('<div class="variables"><b>Self</b>')
+ w('<table class="variables">')
+ for key, value in usedVars:
+ w(variableHTML % (key, htmlrepr(value)))
+ w('</table></div>')
+ break
+
+ # Local and global vars
+ for nm, varList in ('Locals', localVars), ('Globals', globalVars):
+ usedVars = [ (name, var) for (name, var) in varList
+ if re.search(r'\W'+name+r'\W', textSnippet) ]
+ if usedVars:
+ w('<div class="variables"><b>%s</b><table class="variables">' % nm)
+ for name, var in usedVars:
+ w(variableHTML % (name, htmlrepr(var)))
+ w('</table></div>')
+
+ w('</div>') # frame
+ w('</div>') # stacktrace
+ w('<a name="tbend"> </a>')
+ w(exceptionHTML % (html.escape(str(myFailure.type)),
+ html.escape(str(myFailure.value))))
+
+ return io.getvalue()
diff --git a/vendor/Twisted-10.0.0/twisted/web/vhost.py b/vendor/Twisted-10.0.0/twisted/web/vhost.py
new file mode 100644
index 0000000000..6cf83e124f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/vhost.py
@@ -0,0 +1,135 @@
+# -*- test-case-name: twisted.web.
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+I am a virtual hosts implementation.
+"""
+
+# Twisted Imports
+from twisted.python import roots
+from twisted.web import resource
+
+
+class VirtualHostCollection(roots.Homogenous):
+ """Wrapper for virtual hosts collection.
+
+ This exists for configuration purposes.
+ """
+ entityType = resource.Resource
+
+ def __init__(self, nvh):
+ self.nvh = nvh
+
+ def listStaticEntities(self):
+ return self.nvh.hosts.items()
+
+ def getStaticEntity(self, name):
+ return self.nvh.hosts.get(self)
+
+ def reallyPutEntity(self, name, entity):
+ self.nvh.addHost(name, entity)
+
+ def delEntity(self, name):
+ self.nvh.removeHost(name)
+
+
+class NameVirtualHost(resource.Resource):
+ """I am a resource which represents named virtual hosts.
+ """
+
+ default = None
+
+ def __init__(self):
+ """Initialize.
+ """
+ resource.Resource.__init__(self)
+ self.hosts = {}
+
+ def listStaticEntities(self):
+ return resource.Resource.listStaticEntities(self) + [("Virtual Hosts", VirtualHostCollection(self))]
+
+ def getStaticEntity(self, name):
+ if name == "Virtual Hosts":
+ return VirtualHostCollection(self)
+ else:
+ return resource.Resource.getStaticEntity(self, name)
+
+ def addHost(self, name, resrc):
+ """Add a host to this virtual host.
+
+ This will take a host named `name', and map it to a resource
+ `resrc'. For example, a setup for our virtual hosts would be::
+
+ nvh.addHost('divunal.com', divunalDirectory)
+ nvh.addHost('www.divunal.com', divunalDirectory)
+ nvh.addHost('twistedmatrix.com', twistedMatrixDirectory)
+ nvh.addHost('www.twistedmatrix.com', twistedMatrixDirectory)
+ """
+ self.hosts[name] = resrc
+
+ def removeHost(self, name):
+ """Remove a host."""
+ del self.hosts[name]
+
+ def _getResourceForRequest(self, request):
+ """(Internal) Get the appropriate resource for the given host.
+ """
+ hostHeader = request.getHeader('host')
+ if hostHeader == None:
+ return self.default or resource.NoResource()
+ else:
+ host = hostHeader.lower().split(':', 1)[0]
+ return (self.hosts.get(host, self.default)
+ or resource.NoResource("host %s not in vhost map" % repr(host)))
+
+ def render(self, request):
+ """Implementation of resource.Resource's render method.
+ """
+ resrc = self._getResourceForRequest(request)
+ return resrc.render(request)
+
+ def getChild(self, path, request):
+ """Implementation of resource.Resource's getChild method.
+ """
+ resrc = self._getResourceForRequest(request)
+ if resrc.isLeaf:
+ request.postpath.insert(0,request.prepath.pop(-1))
+ return resrc
+ else:
+ return resrc.getChildWithDefault(path, request)
+
+class _HostResource(resource.Resource):
+
+ def getChild(self, path, request):
+ if ':' in path:
+ host, port = path.split(':', 1)
+ port = int(port)
+ else:
+ host, port = path, 80
+ request.setHost(host, port)
+ prefixLen = 3+request.isSecure()+4+len(path)+len(request.prepath[-3])
+ request.path = '/'+'/'.join(request.postpath)
+ request.uri = request.uri[prefixLen:]
+ del request.prepath[:3]
+ return request.site.getResourceFor(request)
+
+
+class VHostMonsterResource(resource.Resource):
+
+ """
+ Use this to be able to record the hostname and method (http vs. https)
+ in the URL without disturbing your web site. If you put this resource
+ in a URL http://foo.com/bar then requests to
+ http://foo.com/bar/http/baz.com/something will be equivalent to
+ http://foo.com/something, except that the hostname the request will
+ appear to be accessing will be "baz.com". So if "baz.com" is redirecting
+ all requests for to foo.com, while foo.com is inaccessible from the outside,
+ then redirect and url generation will work correctly
+ """
+ def getChild(self, path, request):
+ if path == 'http':
+ request.isSecure = lambda: 0
+ elif path == 'https':
+ request.isSecure = lambda: 1
+ return _HostResource()
diff --git a/vendor/Twisted-10.0.0/twisted/web/wsgi.py b/vendor/Twisted-10.0.0/twisted/web/wsgi.py
new file mode 100644
index 0000000000..cb18de379b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/wsgi.py
@@ -0,0 +1,401 @@
+# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An implementation of
+U{Web Resource Gateway Interface<http://www.python.org/dev/peps/pep-0333/>}.
+"""
+
+__metaclass__ = type
+
+from sys import exc_info
+
+from zope.interface import implements
+
+from twisted.python.log import msg, err
+from twisted.python.failure import Failure
+from twisted.web.resource import IResource
+from twisted.web.server import NOT_DONE_YET
+from twisted.web.http import INTERNAL_SERVER_ERROR
+
+
+class _ErrorStream:
+ """
+ File-like object instances of which are used as the value for the
+ C{'wsgi.errors'} key in the C{environ} dictionary passed to the application
+ object.
+
+ This simply passes writes on to L{logging<twisted.python.log>} system as
+ error events from the C{'wsgi'} system. In the future, it may be desirable
+ to expose more information in the events it logs, such as the application
+ object which generated the message.
+ """
+ def write(self, bytes):
+ """
+ Generate an event for the logging system with the given bytes as the
+ message.
+
+ This is called in a WSGI application thread, not the I/O thread.
+ """
+ msg(bytes, system='wsgi', isError=True)
+
+
+ def writelines(self, iovec):
+ """
+ Join the given lines and pass them to C{write} to be handled in the
+ usual way.
+
+ This is called in a WSGI application thread, not the I/O thread.
+
+ @param iovec: A C{list} of C{'\\n'}-terminated C{str} which will be
+ logged.
+ """
+ self.write(''.join(iovec))
+
+
+ def flush(self):
+ """
+ Nothing is buffered, so flushing does nothing. This method is required
+ to exist by PEP 333, though.
+
+ This is called in a WSGI application thread, not the I/O thread.
+ """
+
+
+
+class _InputStream:
+ """
+ File-like object instances of which are used as the value for the
+ C{'wsgi.input'} key in the C{environ} dictionary passed to the application
+ object.
+
+ This only exists to make the handling of C{readline(-1)} consistent across
+ different possible underlying file-like object implementations. The other
+ supported methods pass through directly to the wrapped object.
+ """
+ def __init__(self, input):
+ """
+ Initialize the instance.
+
+ This is called in the I/O thread, not a WSGI application thread.
+ """
+ self._wrapped = input
+
+
+ def read(self, size=None):
+ """
+ Pass through to the underlying C{read}.
+
+ This is called in a WSGI application thread, not the I/O thread.
+ """
+ # Avoid passing None because cStringIO and file don't like it.
+ if size is None:
+ return self._wrapped.read()
+ return self._wrapped.read(size)
+
+
+ def readline(self, size=None):
+ """
+ Pass through to the underlying C{readline}, with a size of C{-1} replaced
+ with a size of C{None}.
+
+ This is called in a WSGI application thread, not the I/O thread.
+ """
+ # Check for -1 because StringIO doesn't handle it correctly. Check for
+ # None because files and tempfiles don't accept that.
+ if size == -1 or size is None:
+ return self._wrapped.readline()
+ return self._wrapped.readline(size)
+
+
+ def readlines(self, size=None):
+ """
+ Pass through to the underlying C{readlines}.
+
+ This is called in a WSGI application thread, not the I/O thread.
+ """
+ # Avoid passing None because cStringIO and file don't like it.
+ if size is None:
+ return self._wrapped.readlines()
+ return self._wrapped.readlines(size)
+
+
+ def __iter__(self):
+ """
+ Pass through to the underlying C{__iter__}.
+
+ This is called in a WSGI application thread, not the I/O thread.
+ """
+ return iter(self._wrapped)
+
+
+
+class _WSGIResponse:
+ """
+ Helper for L{WSGIResource} which drives the WSGI application using a
+ threadpool and hooks it up to the L{Request}.
+
+ @ivar started: A C{bool} indicating whether or not the response status and
+ headers have been written to the request yet. This may only be read or
+ written in the WSGI application thread.
+
+ @ivar reactor: An L{IReactorThreads} provider which is used to call methods
+ on the request in the I/O thread.
+
+ @ivar threadpool: A L{ThreadPool} which is used to call the WSGI
+ application object in a non-I/O thread.
+
+ @ivar application: The WSGI application object.
+
+ @ivar request: The L{Request} upon which the WSGI environment is based and
+ to which the application's output will be sent.
+
+ @ivar environ: The WSGI environment C{dict}.
+
+ @ivar status: The HTTP response status C{str} supplied to the WSGI
+ I{start_response} callable by the application.
+
+ @ivar headers: A list of HTTP response headers supplied to the WSGI
+ I{start_response} callable by the application.
+
+ @ivar _requestFinished: A flag which indicates whether it is possible to
+ generate more response data or not. This is C{False} until
+ L{Request.notifyFinish} tells us the request is done, then C{True}.
+ """
+
+ _requestFinished = False
+
+ def __init__(self, reactor, threadpool, application, request):
+ self.started = False
+ self.reactor = reactor
+ self.threadpool = threadpool
+ self.application = application
+ self.request = request
+ self.request.notifyFinish().addBoth(self._finished)
+
+ if request.prepath:
+ scriptName = '/' + '/'.join(request.prepath)
+ else:
+ scriptName = ''
+
+ if request.postpath:
+ pathInfo = '/' + '/'.join(request.postpath)
+ else:
+ pathInfo = ''
+
+ parts = request.uri.split('?', 1)
+ if len(parts) == 1:
+ queryString = ''
+ else:
+ queryString = parts[1]
+
+ self.environ = {
+ 'REQUEST_METHOD': request.method,
+ 'REMOTE_ADDR': request.getClientIP(),
+ 'SCRIPT_NAME': scriptName,
+ 'PATH_INFO': pathInfo,
+ 'QUERY_STRING': queryString,
+ 'CONTENT_TYPE': request.getHeader('content-type') or '',
+ 'CONTENT_LENGTH': request.getHeader('content-length') or '',
+ 'SERVER_NAME': request.getRequestHostname(),
+ 'SERVER_PORT': str(request.getHost().port),
+ 'SERVER_PROTOCOL': request.clientproto}
+
+ for name, values in request.requestHeaders.getAllRawHeaders():
+ name = 'HTTP_' + name.upper().replace('-', '_')
+ # It might be preferable for http.HTTPChannel to clear out
+ # newlines.
+ self.environ[name] = ','.join([
+ v.replace('\n', ' ') for v in values])
+
+ self.environ.update({
+ 'wsgi.version': (1, 0),
+ 'wsgi.url_scheme': request.isSecure() and 'https' or 'http',
+ 'wsgi.run_once': False,
+ 'wsgi.multithread': True,
+ 'wsgi.multiprocess': False,
+ 'wsgi.errors': _ErrorStream(),
+ # Attend: request.content was owned by the I/O thread up until
+ # this point. By wrapping it and putting the result into the
+ # environment dictionary, it is effectively being given to
+ # another thread. This means that whatever it is, it has to be
+ # safe to access it from two different threads. The access
+ # *should* all be serialized (first the I/O thread writes to
+ # it, then the WSGI thread reads from it, then the I/O thread
+ # closes it). However, since the request is made available to
+ # arbitrary application code during resource traversal, it's
+ # possible that some other code might decide to use it in the
+ # I/O thread concurrently with its use in the WSGI thread.
+ # More likely than not, this will break. This seems like an
+ # unlikely possibility to me, but if it is to be allowed,
+ # something here needs to change. -exarkun
+ 'wsgi.input': _InputStream(request.content)})
+
+
+ def _finished(self, ignored):
+ """
+ Record the end of the response generation for the request being
+ serviced.
+ """
+ self._requestFinished = True
+
+
+ def startResponse(self, status, headers, excInfo=None):
+ """
+ The WSGI I{start_response} callable. The given values are saved until
+ they are needed to generate the response.
+
+ This will be called in a non-I/O thread.
+ """
+ if self.started and excInfo is not None:
+ raise excInfo[0], excInfo[1], excInfo[2]
+ self.status = status
+ self.headers = headers
+ return self.write
+
+
+ def write(self, bytes):
+ """
+ The WSGI I{write} callable returned by the I{start_response} callable.
+ The given bytes will be written to the response body, possibly flushing
+ the status and headers first.
+
+ This will be called in a non-I/O thread.
+ """
+ def wsgiWrite(started):
+ if not started:
+ self._sendResponseHeaders()
+ self.request.write(bytes)
+ self.reactor.callFromThread(wsgiWrite, self.started)
+ self.started = True
+
+
+ def _sendResponseHeaders(self):
+ """
+ Set the response code and response headers on the request object, but
+ do not flush them. The caller is responsible for doing a write in
+ order for anything to actually be written out in response to the
+ request.
+
+ This must be called in the I/O thread.
+ """
+ code, message = self.status.split(None, 1)
+ code = int(code)
+ self.request.setResponseCode(code, message)
+
+ # twisted.web.server.Request.process always addes a content-type
+ # response header. That's not appropriate for us.
+ self.request.responseHeaders.removeHeader('content-type')
+
+ for name, value in self.headers:
+ # Don't allow the application to control these required headers.
+ if name.lower() not in ('server', 'date'):
+ self.request.responseHeaders.addRawHeader(name, value)
+
+
+ def start(self):
+ """
+ Start the WSGI application in the threadpool.
+
+ This must be called in the I/O thread.
+ """
+ self.threadpool.callInThread(self.run)
+
+
+ def run(self):
+ """
+ Call the WSGI application object, iterate it, and handle its output.
+
+ This must be called in a non-I/O thread (ie, a WSGI application
+ thread).
+ """
+ try:
+ appIterator = self.application(self.environ, self.startResponse)
+ for elem in appIterator:
+ if elem:
+ self.write(elem)
+ if self._requestFinished:
+ break
+ close = getattr(appIterator, 'close', None)
+ if close is not None:
+ close()
+ except:
+ def wsgiError(started, type, value, traceback):
+ err(Failure(value, type, traceback), "WSGI application error")
+ if started:
+ self.request.transport.loseConnection()
+ else:
+ self.request.setResponseCode(INTERNAL_SERVER_ERROR)
+ self.request.finish()
+ self.reactor.callFromThread(wsgiError, self.started, *exc_info())
+ else:
+ def wsgiFinish(started):
+ if not self._requestFinished:
+ if not started:
+ self._sendResponseHeaders()
+ self.request.finish()
+ self.reactor.callFromThread(wsgiFinish, self.started)
+ self.started = True
+
+
+
+class WSGIResource:
+ """
+ An L{IResource} implementation which delegates responsibility for all
+ resources hierarchically inferior to it to a WSGI application.
+
+ @ivar _reactor: An L{IReactorThreads} provider which will be passed on to
+ L{_WSGIResponse} to schedule calls in the I/O thread.
+
+ @ivar _threadpool: A L{ThreadPool} which will be passed on to
+ L{_WSGIResponse} to run the WSGI application object.
+
+ @ivar _application: The WSGI application object.
+ """
+ implements(IResource)
+
+ # Further resource segments are left up to the WSGI application object to
+ # handle.
+ isLeaf = True
+
+ def __init__(self, reactor, threadpool, application):
+ self._reactor = reactor
+ self._threadpool = threadpool
+ self._application = application
+
+
+ def render(self, request):
+ """
+ Turn the request into the appropriate C{environ} C{dict} suitable to be
+ passed to the WSGI application object and then pass it on.
+
+ The WSGI application object is given almost complete control of the
+ rendering process. C{NOT_DONE_YET} will always be returned in order
+ and response completion will be dictated by the application object, as
+ will the status, headers, and the response body.
+ """
+ response = _WSGIResponse(
+ self._reactor, self._threadpool, self._application, request)
+ response.start()
+ return NOT_DONE_YET
+
+
+ def getChildWithDefault(self, name, request):
+ """
+ Reject attempts to retrieve a child resource. All path segments beyond
+ the one which refers to this resource are handled by the WSGI
+ application object.
+ """
+ raise RuntimeError("Cannot get IResource children from WSGIResource")
+
+
+ def putChild(self, path, child):
+ """
+ Reject attempts to add a child resource to this resource. The WSGI
+ application object handles all path segments beneath this resource, so
+ L{IResource} children can never be found.
+ """
+ raise RuntimeError("Cannot put IResource children under WSGIResource")
+
+
+__all__ = ['WSGIResource']
diff --git a/vendor/Twisted-10.0.0/twisted/web/xmlrpc.py b/vendor/Twisted-10.0.0/twisted/web/xmlrpc.py
new file mode 100644
index 0000000000..cbe8bf36d2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/web/xmlrpc.py
@@ -0,0 +1,427 @@
+# -*- test-case-name: twisted.web.test.test_xmlrpc -*-
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A generic resource for publishing objects via XML-RPC.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+# System Imports
+import sys, xmlrpclib, urlparse
+
+# Sibling Imports
+from twisted.web import resource, server, http
+from twisted.internet import defer, protocol, reactor
+from twisted.python import log, reflect, failure
+
+# These are deprecated, use the class level definitions
+NOT_FOUND = 8001
+FAILURE = 8002
+
+
+# Useful so people don't need to import xmlrpclib directly
+Fault = xmlrpclib.Fault
+Binary = xmlrpclib.Binary
+Boolean = xmlrpclib.Boolean
+DateTime = xmlrpclib.DateTime
+
+# On Python 2.4 and earlier, DateTime.decode returns unicode.
+if sys.version_info[:2] < (2, 5):
+ _decode = DateTime.decode
+ DateTime.decode = lambda self, value: _decode(self, value.encode('ascii'))
+
+
+class NoSuchFunction(Fault):
+ """
+ There is no function by the given name.
+ """
+
+
+class Handler:
+ """
+ Handle a XML-RPC request and store the state for a request in progress.
+
+ Override the run() method and return result using self.result,
+ a Deferred.
+
+ We require this class since we're not using threads, so we can't
+ encapsulate state in a running function if we're going to have
+ to wait for results.
+
+ For example, lets say we want to authenticate against twisted.cred,
+ run a LDAP query and then pass its result to a database query, all
+ as a result of a single XML-RPC command. We'd use a Handler instance
+ to store the state of the running command.
+ """
+
+ def __init__(self, resource, *args):
+ self.resource = resource # the XML-RPC resource we are connected to
+ self.result = defer.Deferred()
+ self.run(*args)
+
+ def run(self, *args):
+ # event driven equivalent of 'raise UnimplementedError'
+ self.result.errback(
+ NotImplementedError("Implement run() in subclasses"))
+
+
+class XMLRPC(resource.Resource):
+ """
+ A resource that implements XML-RPC.
+
+ You probably want to connect this to '/RPC2'.
+
+ Methods published can return XML-RPC serializable results, Faults,
+ Binary, Boolean, DateTime, Deferreds, or Handler instances.
+
+ By default methods beginning with 'xmlrpc_' are published.
+
+ Sub-handlers for prefixed methods (e.g., system.listMethods)
+ can be added with putSubHandler. By default, prefixes are
+ separated with a '.'. Override self.separator to change this.
+ """
+
+ # Error codes for Twisted, if they conflict with yours then
+ # modify them at runtime.
+ NOT_FOUND = 8001
+ FAILURE = 8002
+
+ isLeaf = 1
+ separator = '.'
+ allowedMethods = ('POST',)
+
+ def __init__(self, allowNone=False):
+ resource.Resource.__init__(self)
+ self.subHandlers = {}
+ self.allowNone = allowNone
+
+ def putSubHandler(self, prefix, handler):
+ self.subHandlers[prefix] = handler
+
+ def getSubHandler(self, prefix):
+ return self.subHandlers.get(prefix, None)
+
+ def getSubHandlerPrefixes(self):
+ return self.subHandlers.keys()
+
+ def render_POST(self, request):
+ request.content.seek(0, 0)
+ request.setHeader("content-type", "text/xml")
+ try:
+ args, functionPath = xmlrpclib.loads(request.content.read())
+ except Exception, e:
+ f = Fault(self.FAILURE, "Can't deserialize input: %s" % (e,))
+ self._cbRender(f, request)
+ else:
+ try:
+ function = self._getFunction(functionPath)
+ except Fault, f:
+ self._cbRender(f, request)
+ else:
+ d = defer.maybeDeferred(function, *args)
+ d.addErrback(self._ebRender)
+ d.addCallback(self._cbRender, request)
+ return server.NOT_DONE_YET
+
+
+ def _cbRender(self, result, request):
+ if isinstance(result, Handler):
+ result = result.result
+ if not isinstance(result, Fault):
+ result = (result,)
+ try:
+ try:
+ content = xmlrpclib.dumps(
+ result, methodresponse=True,
+ allow_none=self.allowNone)
+ except Exception, e:
+ f = Fault(self.FAILURE, "Can't serialize output: %s" % (e,))
+ content = xmlrpclib.dumps(f, methodresponse=True,
+ allow_none=self.allowNone)
+
+ request.setHeader("content-length", str(len(content)))
+ request.write(content)
+ except:
+ log.err()
+ request.finish()
+
+
+ def _ebRender(self, failure):
+ if isinstance(failure.value, Fault):
+ return failure.value
+ log.err(failure)
+ return Fault(self.FAILURE, "error")
+
+ def _getFunction(self, functionPath):
+ """
+ Given a string, return a function, or raise NoSuchFunction.
+
+ This returned function will be called, and should return the result
+ of the call, a Deferred, or a Fault instance.
+
+ Override in subclasses if you want your own policy. The default
+ policy is that given functionPath 'foo', return the method at
+ self.xmlrpc_foo, i.e. getattr(self, "xmlrpc_" + functionPath).
+ If functionPath contains self.separator, the sub-handler for
+ the initial prefix is used to search for the remaining path.
+ """
+ if functionPath.find(self.separator) != -1:
+ prefix, functionPath = functionPath.split(self.separator, 1)
+ handler = self.getSubHandler(prefix)
+ if handler is None:
+ raise NoSuchFunction(self.NOT_FOUND,
+ "no such subHandler %s" % prefix)
+ return handler._getFunction(functionPath)
+
+ f = getattr(self, "xmlrpc_%s" % functionPath, None)
+ if not f:
+ raise NoSuchFunction(self.NOT_FOUND,
+ "function %s not found" % functionPath)
+ elif not callable(f):
+ raise NoSuchFunction(self.NOT_FOUND,
+ "function %s not callable" % functionPath)
+ else:
+ return f
+
+ def _listFunctions(self):
+ """
+ Return a list of the names of all xmlrpc methods.
+ """
+ return reflect.prefixedMethodNames(self.__class__, 'xmlrpc_')
+
+
+class XMLRPCIntrospection(XMLRPC):
+ """
+ Implement the XML-RPC Introspection API.
+
+ By default, the methodHelp method returns the 'help' method attribute,
+ if it exists, otherwise the __doc__ method attribute, if it exists,
+ otherwise the empty string.
+
+ To enable the methodSignature method, add a 'signature' method attribute
+ containing a list of lists. See methodSignature's documentation for the
+ format. Note the type strings should be XML-RPC types, not Python types.
+ """
+
+ def __init__(self, parent):
+ """
+ Implement Introspection support for an XMLRPC server.
+
+ @param parent: the XMLRPC server to add Introspection support to.
+ """
+
+ XMLRPC.__init__(self)
+ self._xmlrpc_parent = parent
+
+ def xmlrpc_listMethods(self):
+ """
+ Return a list of the method names implemented by this server.
+ """
+ functions = []
+ todo = [(self._xmlrpc_parent, '')]
+ while todo:
+ obj, prefix = todo.pop(0)
+ functions.extend([prefix + name for name in obj._listFunctions()])
+ todo.extend([ (obj.getSubHandler(name),
+ prefix + name + obj.separator)
+ for name in obj.getSubHandlerPrefixes() ])
+ return functions
+
+ xmlrpc_listMethods.signature = [['array']]
+
+ def xmlrpc_methodHelp(self, method):
+ """
+ Return a documentation string describing the use of the given method.
+ """
+ method = self._xmlrpc_parent._getFunction(method)
+ return (getattr(method, 'help', None)
+ or getattr(method, '__doc__', None) or '')
+
+ xmlrpc_methodHelp.signature = [['string', 'string']]
+
+ def xmlrpc_methodSignature(self, method):
+ """
+ Return a list of type signatures.
+
+ Each type signature is a list of the form [rtype, type1, type2, ...]
+ where rtype is the return type and typeN is the type of the Nth
+ argument. If no signature information is available, the empty
+ string is returned.
+ """
+ method = self._xmlrpc_parent._getFunction(method)
+ return getattr(method, 'signature', None) or ''
+
+ xmlrpc_methodSignature.signature = [['array', 'string'],
+ ['string', 'string']]
+
+
+def addIntrospection(xmlrpc):
+ """
+ Add Introspection support to an XMLRPC server.
+
+ @param xmlrpc: The xmlrpc server to add Introspection support to.
+ """
+ xmlrpc.putSubHandler('system', XMLRPCIntrospection(xmlrpc))
+
+
+class QueryProtocol(http.HTTPClient):
+
+ def connectionMade(self):
+ self.sendCommand('POST', self.factory.path)
+ self.sendHeader('User-Agent', 'Twisted/XMLRPClib')
+ self.sendHeader('Host', self.factory.host)
+ self.sendHeader('Content-type', 'text/xml')
+ self.sendHeader('Content-length', str(len(self.factory.payload)))
+ if self.factory.user:
+ auth = '%s:%s' % (self.factory.user, self.factory.password)
+ auth = auth.encode('base64').strip()
+ self.sendHeader('Authorization', 'Basic %s' % (auth,))
+ self.endHeaders()
+ self.transport.write(self.factory.payload)
+
+ def handleStatus(self, version, status, message):
+ if status != '200':
+ self.factory.badStatus(status, message)
+
+ def handleResponse(self, contents):
+ self.factory.parseResponse(contents)
+
+
+payloadTemplate = """<?xml version="1.0"?>
+<methodCall>
+<methodName>%s</methodName>
+%s
+</methodCall>
+"""
+
+
+class _QueryFactory(protocol.ClientFactory):
+
+ deferred = None
+ protocol = QueryProtocol
+
+ def __init__(self, path, host, method, user=None, password=None,
+ allowNone=False, args=()):
+ self.path, self.host = path, host
+ self.user, self.password = user, password
+ self.payload = payloadTemplate % (method,
+ xmlrpclib.dumps(args, allow_none=allowNone))
+ self.deferred = defer.Deferred()
+
+ def parseResponse(self, contents):
+ if not self.deferred:
+ return
+ try:
+ response = xmlrpclib.loads(contents)[0][0]
+ except:
+ deferred, self.deferred = self.deferred, None
+ deferred.errback(failure.Failure())
+ else:
+ deferred, self.deferred = self.deferred, None
+ deferred.callback(response)
+
+ def clientConnectionLost(self, _, reason):
+ if self.deferred is not None:
+ deferred, self.deferred = self.deferred, None
+ deferred.errback(reason)
+
+ clientConnectionFailed = clientConnectionLost
+
+ def badStatus(self, status, message):
+ deferred, self.deferred = self.deferred, None
+ deferred.errback(ValueError(status, message))
+
+
+
+class Proxy:
+ """
+ A Proxy for making remote XML-RPC calls.
+
+ Pass the URL of the remote XML-RPC server to the constructor.
+
+ Use proxy.callRemote('foobar', *args) to call remote method
+ 'foobar' with *args.
+
+ @ivar queryFactory: object returning a factory for XML-RPC protocol. Mainly
+ useful for tests.
+ """
+ queryFactory = _QueryFactory
+
+ def __init__(self, url, user=None, password=None, allowNone=False):
+ """
+ @type url: C{str}
+ @param url: The URL to which to post method calls. Calls will be made
+ over SSL if the scheme is HTTPS. If netloc contains username or
+ password information, these will be used to authenticate, as long as
+ the C{user} and C{password} arguments are not specified.
+
+ @type user: C{str} or None
+ @param user: The username with which to authenticate with the server
+ when making calls. If specified, overrides any username information
+ embedded in C{url}. If not specified, a value may be taken from C{url}
+ if present.
+
+ @type password: C{str} or None
+ @param password: The password with which to authenticate with the
+ server when making calls. If specified, overrides any password
+ information embedded in C{url}. If not specified, a value may be taken
+ from C{url} if present.
+
+ @type allowNone: C{bool} or None
+ @param allowNone: allow the use of None values in parameters. It's
+ passed to the underlying xmlrpclib implementation. Default to False.
+ """
+ scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
+ netlocParts = netloc.split('@')
+ if len(netlocParts) == 2:
+ userpass = netlocParts.pop(0).split(':')
+ self.user = userpass.pop(0)
+ try:
+ self.password = userpass.pop(0)
+ except:
+ self.password = None
+ else:
+ self.user = self.password = None
+ hostport = netlocParts[0].split(':')
+ self.host = hostport.pop(0)
+ try:
+ self.port = int(hostport.pop(0))
+ except:
+ self.port = None
+ self.path = path
+ if self.path in ['', None]:
+ self.path = '/'
+ self.secure = (scheme == 'https')
+ if user is not None:
+ self.user = user
+ if password is not None:
+ self.password = password
+ self.allowNone = allowNone
+
+ def callRemote(self, method, *args):
+ """
+ Call remote XML-RPC C{method} with given arguments.
+
+ @return: a L{defer.Deferred} that will fire with the method response,
+ or a failure if the method failed. Generally, the failure type will
+ be L{Fault}, but you can also have an C{IndexError} on some buggy
+ servers giving empty responses.
+ """
+ factory = self.queryFactory(
+ self.path, self.host, method, self.user,
+ self.password, self.allowNone, args)
+ if self.secure:
+ from twisted.internet import ssl
+ reactor.connectSSL(self.host, self.port or 443,
+ factory, ssl.ClientContextFactory())
+ else:
+ reactor.connectTCP(self.host, self.port or 80, factory)
+ return factory.deferred
+
+
+__all__ = [
+ "XMLRPC", "Handler", "NoSuchFunction", "Proxy",
+
+ "Fault", "Binary", "Boolean", "DateTime"]
diff --git a/vendor/Twisted-10.0.0/twisted/words/__init__.py b/vendor/Twisted-10.0.0/twisted/words/__init__.py
new file mode 100644
index 0000000000..725af4c939
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/__init__.py
@@ -0,0 +1,10 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Twisted Words: a Twisted Chat service.
+"""
+
+from twisted.words._version import version
+__version__ = version.short()
diff --git a/vendor/Twisted-10.0.0/twisted/words/_version.py b/vendor/Twisted-10.0.0/twisted/words/_version.py
new file mode 100644
index 0000000000..ccfa3efdcc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/_version.py
@@ -0,0 +1,3 @@
+# This is an auto-generated file. Do not edit it.
+from twisted.python import versions
+version = versions.Version('twisted.words', 10, 0, 0)
diff --git a/vendor/Twisted-10.0.0/twisted/words/ewords.py b/vendor/Twisted-10.0.0/twisted/words/ewords.py
new file mode 100644
index 0000000000..5aa93450f2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/ewords.py
@@ -0,0 +1,34 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""Exception definitions for Words
+"""
+
+class WordsError(Exception):
+ def __str__(self):
+ return self.__class__.__name__ + ': ' + Exception.__str__(self)
+
+class NoSuchUser(WordsError):
+ pass
+
+
+class DuplicateUser(WordsError):
+ pass
+
+
+class NoSuchGroup(WordsError):
+ pass
+
+
+class DuplicateGroup(WordsError):
+ pass
+
+
+class AlreadyLoggedIn(WordsError):
+ pass
+
+__all__ = [
+ 'WordsError', 'NoSuchUser', 'DuplicateUser',
+ 'NoSuchGroup', 'DuplicateGroup', 'AlreadyLoggedIn',
+ ]
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/__init__.py b/vendor/Twisted-10.0.0/twisted/words/im/__init__.py
new file mode 100644
index 0000000000..9f511b7c8d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/__init__.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Instance Messenger, Pan-protocol chat client."""
+
+import warnings
+warnings.warn("twisted.im will be undergoing a rewrite at some point in the future.")
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/baseaccount.py b/vendor/Twisted-10.0.0/twisted/words/im/baseaccount.py
new file mode 100644
index 0000000000..4cbe669048
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/baseaccount.py
@@ -0,0 +1,62 @@
+# -*- Python -*-
+#
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+
+class AccountManager:
+ """I am responsible for managing a user's accounts.
+
+ That is, remembering what accounts are available, their settings,
+ adding and removal of accounts, etc.
+
+ @ivar accounts: A collection of available accounts.
+ @type accounts: mapping of strings to L{Account<interfaces.IAccount>}s.
+ """
+ def __init__(self):
+ self.accounts = {}
+
+ def getSnapShot(self):
+ """A snapshot of all the accounts and their status.
+
+ @returns: A list of tuples, each of the form
+ (string:accountName, boolean:isOnline,
+ boolean:autoLogin, string:gatewayType)
+ """
+ data = []
+ for account in self.accounts.values():
+ data.append((account.accountName, account.isOnline(),
+ account.autoLogin, account.gatewayType))
+ return data
+
+ def isEmpty(self):
+ return len(self.accounts) == 0
+
+ def getConnectionInfo(self):
+ connectioninfo = []
+ for account in self.accounts.values():
+ connectioninfo.append(account.isOnline())
+ return connectioninfo
+
+ def addAccount(self, account):
+ self.accounts[account.accountName] = account
+
+ def delAccount(self, accountName):
+ del self.accounts[accountName]
+
+ def connect(self, accountName, chatui):
+ """
+ @returntype: Deferred L{interfaces.IClient}
+ """
+ return self.accounts[accountName].logOn(chatui)
+
+ def disconnect(self, accountName):
+ pass
+ #self.accounts[accountName].logOff() - not yet implemented
+
+ def quit(self):
+ pass
+ #for account in self.accounts.values():
+ # account.logOff() - not yet implemented
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/basechat.py b/vendor/Twisted-10.0.0/twisted/words/im/basechat.py
new file mode 100644
index 0000000000..4555bef224
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/basechat.py
@@ -0,0 +1,316 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""Base classes for Instance Messenger clients."""
+
+from twisted.words.im.locals import OFFLINE, ONLINE, AWAY
+
+class ContactsList:
+ """A GUI object that displays a contacts list"""
+ def __init__(self, chatui):
+ """
+ @param chatui: ???
+ @type chatui: L{ChatUI}
+ """
+ self.chatui = chatui
+ self.contacts = {}
+ self.onlineContacts = {}
+ self.clients = []
+
+ def setContactStatus(self, person):
+ """Inform the user that a person's status has changed.
+
+ @type person: L{Person<interfaces.IPerson>}
+ """
+ if not self.contacts.has_key(person.name):
+ self.contacts[person.name] = person
+ if not self.onlineContacts.has_key(person.name) and \
+ (person.status == ONLINE or person.status == AWAY):
+ self.onlineContacts[person.name] = person
+ if self.onlineContacts.has_key(person.name) and \
+ person.status == OFFLINE:
+ del self.onlineContacts[person.name]
+
+ def registerAccountClient(self, client):
+ """Notify the user that an account client has been signed on to.
+
+ @type client: L{Client<interfaces.IClient>}
+ """
+ if not client in self.clients:
+ self.clients.append(client)
+
+ def unregisterAccountClient(self, client):
+ """Notify the user that an account client has been signed off
+ or disconnected from.
+
+ @type client: L{Client<interfaces.IClient>}
+ """
+ if client in self.clients:
+ self.clients.remove(client)
+
+ def contactChangedNick(self, person, newnick):
+ oldname = person.name
+ if self.contacts.has_key(oldname):
+ del self.contacts[oldname]
+ person.name = newnick
+ self.contacts[newnick] = person
+ if self.onlineContacts.has_key(oldname):
+ del self.onlineContacts[oldname]
+ self.onlineContacts[newnick] = person
+
+
+class Conversation:
+ """A GUI window of a conversation with a specific person"""
+ def __init__(self, person, chatui):
+ """
+ @type person: L{Person<interfaces.IPerson>}
+ @type chatui: L{ChatUI}
+ """
+ self.chatui = chatui
+ self.person = person
+
+ def show(self):
+ """Displays the ConversationWindow"""
+ raise NotImplementedError("Subclasses must implement this method")
+
+ def hide(self):
+ """Hides the ConversationWindow"""
+ raise NotImplementedError("Subclasses must implement this method")
+
+ def sendText(self, text):
+ """Sends text to the person with whom the user is conversing.
+
+ @returntype: L{Deferred<twisted.internet.defer.Deferred>}
+ """
+ self.person.sendMessage(text, None)
+
+ def showMessage(self, text, metadata=None):
+ """Display a message sent from the person with whom she is conversing
+
+ @type text: string
+ @type metadata: dict
+ """
+ raise NotImplementedError("Subclasses must implement this method")
+
+ def contactChangedNick(self, person, newnick):
+ """Change a person's name.
+
+ @type person: L{Person<interfaces.IPerson>}
+ @type newnick: string
+ """
+ self.person.name = newnick
+
+
+class GroupConversation:
+ """A conversation with a group of people."""
+ def __init__(self, group, chatui):
+ """
+ @type group: L{Group<interfaces.IGroup>}
+ @param chatui: ???
+ @type chatui: L{ChatUI}
+ """
+ self.chatui = chatui
+ self.group = group
+ self.members = []
+
+ def show(self):
+ """Displays the GroupConversationWindow."""
+ raise NotImplementedError("Subclasses must implement this method")
+
+ def hide(self):
+ """Hides the GroupConversationWindow."""
+ raise NotImplementedError("Subclasses must implement this method")
+
+ def sendText(self, text):
+ """Sends text to the group.
+
+ @type text: string
+ @returntype: L{Deferred<twisted.internet.defer.Deferred>}
+ """
+ self.group.sendGroupMessage(text, None)
+
+ def showGroupMessage(self, sender, text, metadata=None):
+ """Displays to the user a message sent to this group from the given sender
+ @type sender: string (XXX: Not Person?)
+ @type text: string
+ @type metadata: dict
+ """
+ raise NotImplementedError("Subclasses must implement this method")
+
+ def setGroupMembers(self, members):
+ """Sets the list of members in the group and displays it to the user
+ """
+ self.members = members
+
+ def setTopic(self, topic, author):
+ """Displays the topic (from the server) for the group conversation window
+
+ @type topic: string
+ @type author: string (XXX: Not Person?)
+ """
+ raise NotImplementedError("Subclasses must implement this method")
+
+ def memberJoined(self, member):
+ """Adds the given member to the list of members in the group conversation
+ and displays this to the user
+
+ @type member: string (XXX: Not Person?)
+ """
+ if not member in self.members:
+ self.members.append(member)
+
+ def memberChangedNick(self, oldnick, newnick):
+ """Changes the oldnick in the list of members to newnick and displays this
+ change to the user
+
+ @type oldnick: string
+ @type newnick: string
+ """
+ if oldnick in self.members:
+ self.members.remove(oldnick)
+ self.members.append(newnick)
+ #self.chatui.contactChangedNick(oldnick, newnick)
+
+ def memberLeft(self, member):
+ """Deletes the given member from the list of members in the group
+ conversation and displays the change to the user
+
+ @type member: string
+ """
+ if member in self.members:
+ self.members.remove(member)
+
+
+class ChatUI:
+ """A GUI chat client"""
+ def __init__(self):
+ self.conversations = {} # cache of all direct windows
+ self.groupConversations = {} # cache of all group windows
+ self.persons = {} # keys are (name, client)
+ self.groups = {} # cache of all groups
+ self.onlineClients = [] # list of message sources currently online
+ self.contactsList = ContactsList(self)
+
+ def registerAccountClient(self, client):
+ """Notifies user that an account has been signed on to.
+
+ @type client: L{Client<interfaces.IClient>}
+ @returns: client, so that I may be used in a callback chain
+ """
+ print "signing onto", client.accountName
+ self.onlineClients.append(client)
+ self.contactsList.registerAccountClient(client)
+ return client
+
+ def unregisterAccountClient(self, client):
+ """Notifies user that an account has been signed off or disconnected
+
+ @type client: L{Client<interfaces.IClient>}
+ """
+ print "signing off from", client.accountName
+ self.onlineClients.remove(client)
+ self.contactsList.unregisterAccountClient(client)
+
+ def getContactsList(self):
+ """
+ @returntype: L{ContactsList}
+ """
+ return self.contactsList
+
+ def getConversation(self, person, Class=Conversation, stayHidden=0):
+ """For the given person object, returns the conversation window
+ or creates and returns a new conversation window if one does not exist.
+
+ @type person: L{Person<interfaces.IPerson>}
+ @type Class: L{Conversation<interfaces.IConversation>} class
+ @type stayHidden: boolean
+
+ @returntype: L{Conversation<interfaces.IConversation>}
+ """
+ conv = self.conversations.get(person)
+ if not conv:
+ conv = Class(person, self)
+ self.conversations[person] = conv
+ if stayHidden:
+ conv.hide()
+ else:
+ conv.show()
+ return conv
+
+ def getGroupConversation(self,group,Class=GroupConversation,stayHidden=0):
+ """For the given group object, returns the group conversation window or
+ creates and returns a new group conversation window if it doesn't exist
+
+ @type group: L{Group<interfaces.IGroup>}
+ @type Class: L{Conversation<interfaces.IConversation>} class
+ @type stayHidden: boolean
+
+ @returntype: L{GroupConversation<interfaces.IGroupConversation>}
+ """
+ conv = self.groupConversations.get(group)
+ if not conv:
+ conv = Class(group, self)
+ self.groupConversations[group] = conv
+ if stayHidden:
+ conv.hide()
+ else:
+ conv.show()
+ return conv
+
+ def getPerson(self, name, client):
+ """For the given name and account client, returns the instance of the
+ AbstractPerson subclass, or creates and returns a new AbstractPerson
+ subclass of the type Class
+
+ @type name: string
+ @type client: L{Client<interfaces.IClient>}
+
+ @returntype: L{Person<interfaces.IPerson>}
+ """
+ account = client.account
+ p = self.persons.get((name, account))
+ if not p:
+ p = account.getPerson(name)
+ self.persons[name, account] = p
+ return p
+
+ def getGroup(self, name, client):
+ """For the given name and account client, returns the instance of the
+ AbstractGroup subclass, or creates and returns a new AbstractGroup
+ subclass of the type Class
+
+ @type name: string
+ @type client: L{Client<interfaces.IClient>}
+
+ @returntype: L{Group<interfaces.IGroup>}
+ """
+ # I accept 'client' instead of 'account' in my signature for
+ # backwards compatibility. (Groups changed to be Account-oriented
+ # in CVS revision 1.8.)
+ account = client.account
+ g = self.groups.get((name, account))
+ if not g:
+ g = account.getGroup(name)
+ self.groups[name, account] = g
+ return g
+
+ def contactChangedNick(self, oldnick, newnick):
+ """For the given person, changes the person's name to newnick, and
+ tells the contact list and any conversation windows with that person
+ to change as well.
+
+ @type oldnick: string
+ @type newnick: string
+ """
+ if self.persons.has_key((person.name, person.account)):
+ conv = self.conversations.get(person)
+ if conv:
+ conv.contactChangedNick(person, newnick)
+
+ self.contactsList.contactChangedNick(person, newnick)
+
+ del self.persons[person.name, person.account]
+ person.name = newnick
+ self.persons[person.name, person.account] = person
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/basesupport.py b/vendor/Twisted-10.0.0/twisted/words/im/basesupport.py
new file mode 100644
index 0000000000..1b2a2a946a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/basesupport.py
@@ -0,0 +1,270 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+"""Instance Messenger base classes for protocol support.
+
+You will find these useful if you're adding a new protocol to IM.
+"""
+
+# Abstract representation of chat "model" classes
+
+from twisted.words.im.locals import ONLINE, OFFLINE, OfflineError
+from twisted.words.im import interfaces
+
+from twisted.internet.protocol import Protocol
+
+from twisted.python.reflect import prefixedMethods
+from twisted.persisted import styles
+
+from twisted.internet import error
+
+class AbstractGroup:
+ def __init__(self, name, account):
+ self.name = name
+ self.account = account
+
+ def getGroupCommands(self):
+ """finds group commands
+
+ these commands are methods on me that start with imgroup_; they are
+ called with no arguments
+ """
+ return prefixedMethods(self, "imgroup_")
+
+ def getTargetCommands(self, target):
+ """finds group commands
+
+ these commands are methods on me that start with imgroup_; they are
+ called with a user present within this room as an argument
+
+ you may want to override this in your group in order to filter for
+ appropriate commands on the given user
+ """
+ return prefixedMethods(self, "imtarget_")
+
+ def join(self):
+ if not self.account.client:
+ raise OfflineError
+ self.account.client.joinGroup(self.name)
+
+ def leave(self):
+ if not self.account.client:
+ raise OfflineError
+ self.account.client.leaveGroup(self.name)
+
+ def __repr__(self):
+ return '<%s %r>' % (self.__class__, self.name)
+
+ def __str__(self):
+ return '%s@%s' % (self.name, self.account.accountName)
+
+class AbstractPerson:
+ def __init__(self, name, baseAccount):
+ self.name = name
+ self.account = baseAccount
+ self.status = OFFLINE
+
+ def getPersonCommands(self):
+ """finds person commands
+
+ these commands are methods on me that start with imperson_; they are
+ called with no arguments
+ """
+ return prefixedMethods(self, "imperson_")
+
+ def getIdleTime(self):
+ """
+ Returns a string.
+ """
+ return '--'
+
+ def __repr__(self):
+ return '<%s %r/%s>' % (self.__class__, self.name, self.status)
+
+ def __str__(self):
+ return '%s@%s' % (self.name, self.account.accountName)
+
+class AbstractClientMixin:
+ """Designed to be mixed in to a Protocol implementing class.
+
+ Inherit from me first.
+
+ @ivar _logonDeferred: Fired when I am done logging in.
+ """
+ def __init__(self, account, chatui, logonDeferred):
+ for base in self.__class__.__bases__:
+ if issubclass(base, Protocol):
+ self.__class__._protoBase = base
+ break
+ else:
+ pass
+ self.account = account
+ self.chat = chatui
+ self._logonDeferred = logonDeferred
+
+ def connectionMade(self):
+ self._protoBase.connectionMade(self)
+
+ def connectionLost(self, reason):
+ self.account._clientLost(self, reason)
+ self.unregisterAsAccountClient()
+ return self._protoBase.connectionLost(self, reason)
+
+ def unregisterAsAccountClient(self):
+ """Tell the chat UI that I have `signed off'.
+ """
+ self.chat.unregisterAccountClient(self)
+
+
+class AbstractAccount(styles.Versioned):
+ """Base class for Accounts.
+
+ I am the start of an implementation of L{IAccount<interfaces.IAccount>}, I
+ implement L{isOnline} and most of L{logOn}, though you'll need to implement
+ L{_startLogOn} in a subclass.
+
+ @cvar _groupFactory: A Callable that will return a L{IGroup} appropriate
+ for this account type.
+ @cvar _personFactory: A Callable that will return a L{IPerson} appropriate
+ for this account type.
+
+ @type _isConnecting: boolean
+ @ivar _isConnecting: Whether I am in the process of establishing a
+ connection to the server.
+ @type _isOnline: boolean
+ @ivar _isOnline: Whether I am currently on-line with the server.
+
+ @ivar accountName:
+ @ivar autoLogin:
+ @ivar username:
+ @ivar password:
+ @ivar host:
+ @ivar port:
+ """
+
+ _isOnline = 0
+ _isConnecting = 0
+ client = None
+
+ _groupFactory = AbstractGroup
+ _personFactory = AbstractPerson
+
+ persistanceVersion = 2
+
+ def __init__(self, accountName, autoLogin, username, password, host, port):
+ self.accountName = accountName
+ self.autoLogin = autoLogin
+ self.username = username
+ self.password = password
+ self.host = host
+ self.port = port
+
+ self._groups = {}
+ self._persons = {}
+
+ def upgrateToVersion2(self):
+ # Added in CVS revision 1.16.
+ for k in ('_groups', '_persons'):
+ if not hasattr(self, k):
+ setattr(self, k, {})
+
+ def __getstate__(self):
+ state = styles.Versioned.__getstate__(self)
+ for k in ('client', '_isOnline', '_isConnecting'):
+ try:
+ del state[k]
+ except KeyError:
+ pass
+ return state
+
+ def isOnline(self):
+ return self._isOnline
+
+ def logOn(self, chatui):
+ """Log on to this account.
+
+ Takes care to not start a connection if a connection is
+ already in progress. You will need to implement
+ L{_startLogOn} for this to work, and it would be a good idea
+ to override L{_loginFailed} too.
+
+ @returntype: Deferred L{interfaces.IClient}
+ """
+ if (not self._isConnecting) and (not self._isOnline):
+ self._isConnecting = 1
+ d = self._startLogOn(chatui)
+ d.addCallback(self._cb_logOn)
+ # if chatui is not None:
+ # (I don't particularly like having to pass chatUI to this function,
+ # but we haven't factored it out yet.)
+ d.addCallback(chatui.registerAccountClient)
+ d.addErrback(self._loginFailed)
+ return d
+ else:
+ raise error.ConnectError("Connection in progress")
+
+ def getGroup(self, name):
+ """Group factory.
+
+ @param name: Name of the group on this account.
+ @type name: string
+ """
+ group = self._groups.get(name)
+ if group is None:
+ group = self._groupFactory(name, self)
+ self._groups[name] = group
+ return group
+
+ def getPerson(self, name):
+ """Person factory.
+
+ @param name: Name of the person on this account.
+ @type name: string
+ """
+ person = self._persons.get(name)
+ if person is None:
+ person = self._personFactory(name, self)
+ self._persons[name] = person
+ return person
+
+ def _startLogOn(self, chatui):
+ """Start the sign on process.
+
+ Factored out of L{logOn}.
+
+ @returntype: Deferred L{interfaces.IClient}
+ """
+ raise NotImplementedError()
+
+ def _cb_logOn(self, client):
+ self._isConnecting = 0
+ self._isOnline = 1
+ self.client = client
+ return client
+
+ def _loginFailed(self, reason):
+ """Errorback for L{logOn}.
+
+ @type reason: Failure
+
+ @returns: I{reason}, for further processing in the callback chain.
+ @returntype: Failure
+ """
+ self._isConnecting = 0
+ self._isOnline = 0 # just in case
+ return reason
+
+ def _clientLost(self, client, reason):
+ self.client = None
+ self._isConnecting = 0
+ self._isOnline = 0
+ return reason
+
+ def __repr__(self):
+ return "<%s: %s (%s@%s:%s)>" % (self.__class__,
+ self.accountName,
+ self.username,
+ self.host,
+ self.port)
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/instancemessenger.glade b/vendor/Twisted-10.0.0/twisted/words/im/instancemessenger.glade
new file mode 100644
index 0000000000..33ffaa2779
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/instancemessenger.glade
@@ -0,0 +1,3165 @@
+<?xml version="1.0"?>
+<GTK-Interface>
+
+<project>
+ <name>InstanceMessenger</name>
+ <program_name>instancemessenger</program_name>
+ <directory></directory>
+ <source_directory>src</source_directory>
+ <pixmaps_directory>pixmaps</pixmaps_directory>
+ <language>C</language>
+ <gnome_support>True</gnome_support>
+ <gettext_support>True</gettext_support>
+ <use_widget_names>True</use_widget_names>
+</project>
+
+<widget>
+ <class>GtkWindow</class>
+ <name>UnseenConversationWindow</name>
+ <visible>False</visible>
+ <title>Unseen Conversation Window</title>
+ <type>GTK_WINDOW_TOPLEVEL</type>
+ <position>GTK_WIN_POS_NONE</position>
+ <modal>False</modal>
+ <allow_shrink>False</allow_shrink>
+ <allow_grow>True</allow_grow>
+ <auto_shrink>False</auto_shrink>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>ConversationWidget</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkVPaned</class>
+ <name>vpaned1</name>
+ <handle_size>10</handle_size>
+ <gutter_size>6</gutter_size>
+ <position>0</position>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow10</name>
+ <hscrollbar_policy>GTK_POLICY_NEVER</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_ALWAYS</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <shrink>False</shrink>
+ <resize>True</resize>
+ </child>
+
+ <widget>
+ <class>GtkText</class>
+ <name>ConversationOutput</name>
+ <editable>False</editable>
+ <text></text>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow11</name>
+ <hscrollbar_policy>GTK_POLICY_NEVER</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_AUTOMATIC</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <shrink>True</shrink>
+ <resize>False</resize>
+ </child>
+
+ <widget>
+ <class>GtkText</class>
+ <name>ConversationMessageEntry</name>
+ <can_focus>True</can_focus>
+ <has_focus>True</has_focus>
+ <signal>
+ <name>key_press_event</name>
+ <handler>handle_key_press_event</handler>
+ <last_modification_time>Tue, 29 Jan 2002 12:42:58 GMT</last_modification_time>
+ </signal>
+ <editable>True</editable>
+ <text></text>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox9</name>
+ <homogeneous>True</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>3</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button42</name>
+ <can_focus>True</can_focus>
+ <label> Send Message </label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>3</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>AddRemoveContact</name>
+ <can_focus>True</can_focus>
+ <label> Add Contact </label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>3</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>CloseContact</name>
+ <can_focus>True</can_focus>
+ <label> Close </label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>3</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+</widget>
+
+<widget>
+ <class>GtkWindow</class>
+ <name>MainIMWindow</name>
+ <signal>
+ <name>destroy</name>
+ <handler>on_MainIMWindow_destroy</handler>
+ <last_modification_time>Sun, 21 Jul 2002 08:16:08 GMT</last_modification_time>
+ </signal>
+ <title>Instance Messenger</title>
+ <type>GTK_WINDOW_TOPLEVEL</type>
+ <position>GTK_WIN_POS_NONE</position>
+ <modal>False</modal>
+ <allow_shrink>True</allow_shrink>
+ <allow_grow>True</allow_grow>
+ <auto_shrink>False</auto_shrink>
+
+ <widget>
+ <class>GtkNotebook</class>
+ <name>ContactsNotebook</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>key_press_event</name>
+ <handler>on_ContactsWidget_key_press_event</handler>
+ <last_modification_time>Tue, 07 May 2002 03:02:33 GMT</last_modification_time>
+ </signal>
+ <show_tabs>True</show_tabs>
+ <show_border>True</show_border>
+ <tab_pos>GTK_POS_TOP</tab_pos>
+ <scrollable>False</scrollable>
+ <tab_hborder>2</tab_hborder>
+ <tab_vborder>2</tab_vborder>
+ <popup_enable>False</popup_enable>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox11</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>OnlineCount</name>
+ <label>Online: %d</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow14</name>
+ <hscrollbar_policy>GTK_POLICY_AUTOMATIC</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_AUTOMATIC</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkCTree</class>
+ <name>OnlineContactsTree</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>tree_select_row</name>
+ <handler>on_OnlineContactsTree_tree_select_row</handler>
+ <last_modification_time>Tue, 07 May 2002 03:06:32 GMT</last_modification_time>
+ </signal>
+ <signal>
+ <name>select_row</name>
+ <handler>on_OnlineContactsTree_select_row</handler>
+ <last_modification_time>Tue, 07 May 2002 04:36:10 GMT</last_modification_time>
+ </signal>
+ <columns>4</columns>
+ <column_widths>109,35,23,80</column_widths>
+ <selection_mode>GTK_SELECTION_SINGLE</selection_mode>
+ <show_titles>True</show_titles>
+ <shadow_type>GTK_SHADOW_IN</shadow_type>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CTree:title</child_name>
+ <name>label77</name>
+ <label>Alias</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CTree:title</child_name>
+ <name>label78</name>
+ <label>Status</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CTree:title</child_name>
+ <name>label79</name>
+ <label>Idle</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CTree:title</child_name>
+ <name>label80</name>
+ <label>Account</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox30</name>
+ <homogeneous>False</homogeneous>
+ <spacing>2</spacing>
+ <child>
+ <padding>1</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>ContactNameEntry</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>activate</name>
+ <handler>on_ContactNameEntry_activate</handler>
+ <last_modification_time>Tue, 07 May 2002 04:07:25 GMT</last_modification_time>
+ </signal>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkOptionMenu</class>
+ <name>AccountsListPopup</name>
+ <can_focus>True</can_focus>
+ <items>Nothing
+To
+Speak
+Of
+</items>
+ <initial_choice>1</initial_choice>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox7</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>PlainSendIM</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_PlainSendIM_clicked</handler>
+ <last_modification_time>Tue, 29 Jan 2002 03:17:35 GMT</last_modification_time>
+ </signal>
+ <label> Send IM </label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>PlainGetInfo</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_PlainGetInfo_clicked</handler>
+ <last_modification_time>Tue, 07 May 2002 04:06:59 GMT</last_modification_time>
+ </signal>
+ <label> Get Info </label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>PlainJoinChat</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_PlainJoinChat_clicked</handler>
+ <last_modification_time>Tue, 29 Jan 2002 13:04:49 GMT</last_modification_time>
+ </signal>
+ <label> Join Group </label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>PlainGoAway</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_PlainGoAway_clicked</handler>
+ <last_modification_time>Tue, 07 May 2002 04:06:53 GMT</last_modification_time>
+ </signal>
+ <label> Go Away </label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox8</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>AddContactButton</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_AddContactButton_clicked</handler>
+ <last_modification_time>Tue, 07 May 2002 04:06:33 GMT</last_modification_time>
+ </signal>
+ <label> Add Contact </label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>RemoveContactButton</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_RemoveContactButton_clicked</handler>
+ <last_modification_time>Tue, 07 May 2002 04:06:28 GMT</last_modification_time>
+ </signal>
+ <label> Remove Contact </label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>Notebook:tab</child_name>
+ <name>label35</name>
+ <label> Online Contacts </label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox14</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>OfflineContactsScroll</name>
+ <hscrollbar_policy>GTK_POLICY_AUTOMATIC</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_ALWAYS</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkCList</class>
+ <name>OfflineContactsList</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>select_row</name>
+ <handler>on_OfflineContactsList_select_row</handler>
+ <last_modification_time>Tue, 07 May 2002 03:00:07 GMT</last_modification_time>
+ </signal>
+ <columns>4</columns>
+ <column_widths>66,80,80,80</column_widths>
+ <selection_mode>GTK_SELECTION_SINGLE</selection_mode>
+ <show_titles>True</show_titles>
+ <shadow_type>GTK_SHADOW_IN</shadow_type>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label41</name>
+ <label>Contact</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label42</name>
+ <label>Account</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label43</name>
+ <label>Alias</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label44</name>
+ <label>Group</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>Notebook:tab</child_name>
+ <name>label36</name>
+ <label> All Contacts </label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>AccountManWidget</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow12</name>
+ <hscrollbar_policy>GTK_POLICY_AUTOMATIC</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_ALWAYS</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkCList</class>
+ <name>accountsList</name>
+ <can_focus>True</can_focus>
+ <columns>4</columns>
+ <column_widths>80,36,34,80</column_widths>
+ <selection_mode>GTK_SELECTION_SINGLE</selection_mode>
+ <show_titles>True</show_titles>
+ <shadow_type>GTK_SHADOW_IN</shadow_type>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label45</name>
+ <label>Service Name</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label46</name>
+ <label>Online</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label47</name>
+ <label>Auto</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label48</name>
+ <label>Gateway</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkTable</class>
+ <name>table5</name>
+ <rows>2</rows>
+ <columns>3</columns>
+ <homogeneous>False</homogeneous>
+ <row_spacing>0</row_spacing>
+ <column_spacing>0</column_spacing>
+ <child>
+ <padding>3</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>NewAccountButton</name>
+ <can_default>True</can_default>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_NewAccountButton_clicked</handler>
+ <last_modification_time>Sun, 27 Jan 2002 10:32:20 GMT</last_modification_time>
+ </signal>
+ <label>New Account</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button46</name>
+ <sensitive>False</sensitive>
+ <can_default>True</can_default>
+ <label>Modify Account</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>LogOnButton</name>
+ <can_default>True</can_default>
+ <has_default>True</has_default>
+ <can_focus>True</can_focus>
+ <has_focus>True</has_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_LogOnButton_clicked</handler>
+ <last_modification_time>Mon, 28 Jan 2002 04:06:23 GMT</last_modification_time>
+ </signal>
+ <label>Logon</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <left_attach>2</left_attach>
+ <right_attach>3</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>DeleteAccountButton</name>
+ <can_default>True</can_default>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_DeleteAccountButton_clicked</handler>
+ <last_modification_time>Mon, 28 Jan 2002 00:18:22 GMT</last_modification_time>
+ </signal>
+ <label>Delete Account</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <left_attach>2</left_attach>
+ <right_attach>3</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>ConsoleButton</name>
+ <can_default>True</can_default>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_ConsoleButton_clicked</handler>
+ <last_modification_time>Mon, 29 Apr 2002 09:13:32 GMT</last_modification_time>
+ </signal>
+ <label>Console</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button75</name>
+ <can_default>True</can_default>
+ <can_focus>True</can_focus>
+ <label>Quit</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>True</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>True</yfill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>Notebook:tab</child_name>
+ <name>label107</name>
+ <label>Accounts</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+ </widget>
+</widget>
+
+<widget>
+ <class>GtkWindow</class>
+ <name>UnseenGroupWindow</name>
+ <visible>False</visible>
+ <title>Unseen Group Window</title>
+ <type>GTK_WINDOW_TOPLEVEL</type>
+ <position>GTK_WIN_POS_NONE</position>
+ <modal>False</modal>
+ <allow_shrink>False</allow_shrink>
+ <allow_grow>True</allow_grow>
+ <auto_shrink>False</auto_shrink>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>GroupChatBox</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox5</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>TopicEntry</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>activate</name>
+ <handler>on_TopicEntry_activate</handler>
+ <last_modification_time>Sat, 23 Feb 2002 02:57:41 GMT</last_modification_time>
+ </signal>
+ <signal>
+ <name>focus_out_event</name>
+ <handler>on_TopicEntry_focus_out_event</handler>
+ <last_modification_time>Sun, 21 Jul 2002 09:36:54 GMT</last_modification_time>
+ </signal>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text>&lt;TOPIC NOT RECEIVED&gt;</text>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>AuthorLabel</name>
+ <label>&lt;nobody&gt;</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>HideButton</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_HideButton_clicked</handler>
+ <last_modification_time>Tue, 29 Jan 2002 14:10:00 GMT</last_modification_time>
+ </signal>
+ <label>&lt;</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkVPaned</class>
+ <name>vpaned2</name>
+ <handle_size>10</handle_size>
+ <gutter_size>6</gutter_size>
+ <position>0</position>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkHPaned</class>
+ <name>GroupHPaned</name>
+ <handle_size>6</handle_size>
+ <gutter_size>6</gutter_size>
+ <child>
+ <shrink>False</shrink>
+ <resize>True</resize>
+ </child>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow4</name>
+ <hscrollbar_policy>GTK_POLICY_NEVER</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_ALWAYS</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <shrink>False</shrink>
+ <resize>True</resize>
+ </child>
+
+ <widget>
+ <class>GtkText</class>
+ <name>GroupOutput</name>
+ <can_focus>True</can_focus>
+ <editable>False</editable>
+ <text></text>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>actionvbox</name>
+ <width>110</width>
+ <homogeneous>False</homogeneous>
+ <spacing>1</spacing>
+ <child>
+ <shrink>True</shrink>
+ <resize>False</resize>
+ </child>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow5</name>
+ <hscrollbar_policy>GTK_POLICY_NEVER</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_ALWAYS</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkCList</class>
+ <name>ParticipantList</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>select_row</name>
+ <handler>on_ParticipantList_select_row</handler>
+ <last_modification_time>Sat, 13 Jul 2002 08:11:12 GMT</last_modification_time>
+ </signal>
+ <signal>
+ <name>unselect_row</name>
+ <handler>on_ParticipantList_unselect_row</handler>
+ <last_modification_time>Sat, 13 Jul 2002 08:23:25 GMT</last_modification_time>
+ </signal>
+ <columns>1</columns>
+ <column_widths>80</column_widths>
+ <selection_mode>GTK_SELECTION_SINGLE</selection_mode>
+ <show_titles>False</show_titles>
+ <shadow_type>GTK_SHADOW_IN</shadow_type>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label18</name>
+ <label>Users</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkFrame</class>
+ <name>frame10</name>
+ <label>Group</label>
+ <label_xalign>0</label_xalign>
+ <shadow_type>GTK_SHADOW_ETCHED_IN</shadow_type>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>GroupActionsBox</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>Placeholder</class>
+ </widget>
+
+ <widget>
+ <class>Placeholder</class>
+ </widget>
+
+ <widget>
+ <class>Placeholder</class>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkFrame</class>
+ <name>PersonFrame</name>
+ <label>Person</label>
+ <label_xalign>0</label_xalign>
+ <shadow_type>GTK_SHADOW_ETCHED_IN</shadow_type>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>PersonActionsBox</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>Placeholder</class>
+ </widget>
+
+ <widget>
+ <class>Placeholder</class>
+ </widget>
+
+ <widget>
+ <class>Placeholder</class>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox6</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <shrink>True</shrink>
+ <resize>False</resize>
+ </child>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>NickLabel</name>
+ <label>&lt;no nick&gt;</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <padding>4</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow9</name>
+ <hscrollbar_policy>GTK_POLICY_NEVER</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_AUTOMATIC</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkText</class>
+ <name>GroupInput</name>
+ <can_focus>True</can_focus>
+ <has_focus>True</has_focus>
+ <signal>
+ <name>key_press_event</name>
+ <handler>handle_key_press_event</handler>
+ <last_modification_time>Tue, 29 Jan 2002 12:41:03 GMT</last_modification_time>
+ </signal>
+ <editable>True</editable>
+ <text></text>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+</widget>
+
+<widget>
+ <class>GtkWindow</class>
+ <name>NewAccountWindow</name>
+ <border_width>3</border_width>
+ <visible>False</visible>
+ <signal>
+ <name>destroy</name>
+ <handler>on_NewAccountWindow_destroy</handler>
+ <last_modification_time>Sun, 27 Jan 2002 10:35:19 GMT</last_modification_time>
+ </signal>
+ <title>New Account</title>
+ <type>GTK_WINDOW_TOPLEVEL</type>
+ <position>GTK_WIN_POS_NONE</position>
+ <modal>False</modal>
+ <allow_shrink>False</allow_shrink>
+ <allow_grow>True</allow_grow>
+ <auto_shrink>True</auto_shrink>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox17</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox11</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>3</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label49</name>
+ <label>Gateway:</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkOptionMenu</class>
+ <name>GatewayOptionMenu</name>
+ <can_focus>True</can_focus>
+ <items>Twisted (Perspective Broker)
+Internet Relay Chat
+AIM (TOC)
+AIM (OSCAR)
+</items>
+ <initial_choice>0</initial_choice>
+ <child>
+ <padding>4</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkFrame</class>
+ <name>GatewayFrame</name>
+ <border_width>3</border_width>
+ <label>Gateway Options</label>
+ <label_xalign>0</label_xalign>
+ <shadow_type>GTK_SHADOW_ETCHED_IN</shadow_type>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>Placeholder</class>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkFrame</class>
+ <name>frame2</name>
+ <border_width>3</border_width>
+ <label>Standard Options</label>
+ <label_xalign>0</label_xalign>
+ <shadow_type>GTK_SHADOW_ETCHED_IN</shadow_type>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkTable</class>
+ <name>table1</name>
+ <border_width>3</border_width>
+ <rows>2</rows>
+ <columns>2</columns>
+ <homogeneous>False</homogeneous>
+ <row_spacing>0</row_spacing>
+ <column_spacing>0</column_spacing>
+
+ <widget>
+ <class>GtkCheckButton</class>
+ <name>AutoLogin</name>
+ <can_focus>True</can_focus>
+ <label>Automatically Log In</label>
+ <active>False</active>
+ <draw_indicator>True</draw_indicator>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>True</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>accountName</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>True</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label50</name>
+ <label> Auto-Login: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>True</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>True</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label51</name>
+ <label>Account Name: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>True</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>True</yfill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkHButtonBox</class>
+ <name>hbuttonbox2</name>
+ <layout_style>GTK_BUTTONBOX_SPREAD</layout_style>
+ <spacing>30</spacing>
+ <child_min_width>85</child_min_width>
+ <child_min_height>27</child_min_height>
+ <child_ipad_x>7</child_ipad_x>
+ <child_ipad_y>0</child_ipad_y>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button50</name>
+ <can_default>True</can_default>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>createAccount</handler>
+ <last_modification_time>Sun, 27 Jan 2002 11:25:05 GMT</last_modification_time>
+ </signal>
+ <label>OK</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button51</name>
+ <can_default>True</can_default>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>destroyMe</handler>
+ <last_modification_time>Sun, 27 Jan 2002 11:27:12 GMT</last_modification_time>
+ </signal>
+ <label>Cancel</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ </widget>
+ </widget>
+ </widget>
+</widget>
+
+<widget>
+ <class>GtkWindow</class>
+ <name>PBAccountWindow</name>
+ <visible>False</visible>
+ <title>PB Account Window</title>
+ <type>GTK_WINDOW_TOPLEVEL</type>
+ <position>GTK_WIN_POS_NONE</position>
+ <modal>False</modal>
+ <allow_shrink>False</allow_shrink>
+ <allow_grow>True</allow_grow>
+ <auto_shrink>False</auto_shrink>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>PBAccountWidget</name>
+ <border_width>4</border_width>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkTable</class>
+ <name>table3</name>
+ <rows>4</rows>
+ <columns>2</columns>
+ <homogeneous>False</homogeneous>
+ <row_spacing>0</row_spacing>
+ <column_spacing>0</column_spacing>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>hostname</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text>twistedmatrix.com</text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>2</top_attach>
+ <bottom_attach>3</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>identity</name>
+ <can_focus>True</can_focus>
+ <has_focus>True</has_focus>
+ <signal>
+ <name>changed</name>
+ <handler>on_identity_changed</handler>
+ <last_modification_time>Sun, 27 Jan 2002 11:52:17 GMT</last_modification_time>
+ </signal>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label52</name>
+ <label> Hostname: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>2</top_attach>
+ <bottom_attach>3</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label54</name>
+ <label>Identity Name: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>password</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>False</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>portno</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text>8787</text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>3</top_attach>
+ <bottom_attach>4</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label55</name>
+ <label> Password: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label53</name>
+ <label> Port Number: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>3</top_attach>
+ <bottom_attach>4</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkFrame</class>
+ <name>frame3</name>
+ <label>Perspectives</label>
+ <label_xalign>0</label_xalign>
+ <shadow_type>GTK_SHADOW_ETCHED_IN</shadow_type>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox19</name>
+ <border_width>3</border_width>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow13</name>
+ <hscrollbar_policy>GTK_POLICY_AUTOMATIC</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_ALWAYS</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkCList</class>
+ <name>serviceList</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>select_row</name>
+ <handler>on_serviceList_select_row</handler>
+ <last_modification_time>Sun, 27 Jan 2002 12:04:38 GMT</last_modification_time>
+ </signal>
+ <columns>3</columns>
+ <column_widths>80,80,80</column_widths>
+ <selection_mode>GTK_SELECTION_SINGLE</selection_mode>
+ <show_titles>True</show_titles>
+ <shadow_type>GTK_SHADOW_IN</shadow_type>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label60</name>
+ <label>Service Type</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label61</name>
+ <label>Service Name</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label62</name>
+ <label>Perspective Name</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkTable</class>
+ <name>table4</name>
+ <rows>3</rows>
+ <columns>2</columns>
+ <homogeneous>False</homogeneous>
+ <row_spacing>0</row_spacing>
+ <column_spacing>0</column_spacing>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label63</name>
+ <label>Perspective Name: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>2</top_attach>
+ <bottom_attach>3</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label59</name>
+ <label> Service Type: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkCombo</class>
+ <name>serviceCombo</name>
+ <value_in_list>False</value_in_list>
+ <ok_if_empty>True</ok_if_empty>
+ <case_sensitive>False</case_sensitive>
+ <use_arrows>True</use_arrows>
+ <use_arrows_always>False</use_arrows_always>
+ <items>twisted.words
+twisted.reality
+twisted.manhole
+</items>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+
+ <widget>
+ <class>GtkEntry</class>
+ <child_name>GtkCombo:entry</child_name>
+ <name>serviceType</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>changed</name>
+ <handler>on_serviceType_changed</handler>
+ <last_modification_time>Sun, 27 Jan 2002 11:49:07 GMT</last_modification_time>
+ </signal>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text>twisted.words</text>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label64</name>
+ <label> Service Name: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>serviceName</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>perspectiveName</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>2</top_attach>
+ <bottom_attach>3</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox13</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button53</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>addPerspective</handler>
+ <last_modification_time>Mon, 28 Jan 2002 01:07:15 GMT</last_modification_time>
+ </signal>
+ <label> Add Perspective </label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button54</name>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>removePerspective</handler>
+ <last_modification_time>Sun, 27 Jan 2002 11:34:36 GMT</last_modification_time>
+ </signal>
+ <label>Remove Perspective</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+</widget>
+
+<widget>
+ <class>GtkWindow</class>
+ <name>IRCAccountWindow</name>
+ <title>IRC Account Window</title>
+ <type>GTK_WINDOW_TOPLEVEL</type>
+ <position>GTK_WIN_POS_NONE</position>
+ <modal>False</modal>
+ <allow_shrink>False</allow_shrink>
+ <allow_grow>True</allow_grow>
+ <auto_shrink>False</auto_shrink>
+
+ <widget>
+ <class>GtkTable</class>
+ <name>IRCAccountWidget</name>
+ <rows>5</rows>
+ <columns>2</columns>
+ <homogeneous>False</homogeneous>
+ <row_spacing>0</row_spacing>
+ <column_spacing>0</column_spacing>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label65</name>
+ <label> Nickname: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label66</name>
+ <label> Server: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label67</name>
+ <label> Port: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>2</top_attach>
+ <bottom_attach>3</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label68</name>
+ <label> Channels: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>3</top_attach>
+ <bottom_attach>4</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label69</name>
+ <label> Password: </label>
+ <justify>GTK_JUSTIFY_RIGHT</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>4</top_attach>
+ <bottom_attach>5</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>ircNick</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>ircServer</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>ircPort</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text>6667</text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>2</top_attach>
+ <bottom_attach>3</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>ircChannels</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>3</top_attach>
+ <bottom_attach>4</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>ircPassword</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>4</top_attach>
+ <bottom_attach>5</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+ </widget>
+</widget>
+
+<widget>
+ <class>GtkWindow</class>
+ <name>TOCAccountWindow</name>
+ <title>TOC Account Window</title>
+ <type>GTK_WINDOW_TOPLEVEL</type>
+ <position>GTK_WIN_POS_NONE</position>
+ <modal>False</modal>
+ <allow_shrink>False</allow_shrink>
+ <allow_grow>True</allow_grow>
+ <auto_shrink>False</auto_shrink>
+
+ <widget>
+ <class>GtkTable</class>
+ <name>TOCAccountWidget</name>
+ <rows>4</rows>
+ <columns>2</columns>
+ <homogeneous>False</homogeneous>
+ <row_spacing>0</row_spacing>
+ <column_spacing>0</column_spacing>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label70</name>
+ <label> Screen Name: </label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label71</name>
+ <label> Password: </label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label72</name>
+ <label> Host: </label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>2</top_attach>
+ <bottom_attach>3</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label73</name>
+ <label> Port: </label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <left_attach>0</left_attach>
+ <right_attach>1</right_attach>
+ <top_attach>3</top_attach>
+ <bottom_attach>4</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>False</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>TOCName</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>0</top_attach>
+ <bottom_attach>1</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>TOCPass</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>False</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>1</top_attach>
+ <bottom_attach>2</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>TOCHost</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text>toc.oscar.aol.com</text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>2</top_attach>
+ <bottom_attach>3</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>TOCPort</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text>9898</text>
+ <child>
+ <left_attach>1</left_attach>
+ <right_attach>2</right_attach>
+ <top_attach>3</top_attach>
+ <bottom_attach>4</bottom_attach>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <xexpand>True</xexpand>
+ <yexpand>False</yexpand>
+ <xshrink>False</xshrink>
+ <yshrink>False</yshrink>
+ <xfill>True</xfill>
+ <yfill>False</yfill>
+ </child>
+ </widget>
+ </widget>
+</widget>
+
+<widget>
+ <class>GtkWindow</class>
+ <name>JoinGroupWindow</name>
+ <border_width>5</border_width>
+ <visible>False</visible>
+ <title>Group to Join</title>
+ <type>GTK_WINDOW_TOPLEVEL</type>
+ <position>GTK_WIN_POS_NONE</position>
+ <modal>False</modal>
+ <allow_shrink>False</allow_shrink>
+ <allow_grow>True</allow_grow>
+ <auto_shrink>False</auto_shrink>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox20</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkOptionMenu</class>
+ <name>AccountSelector</name>
+ <can_focus>True</can_focus>
+ <items>None
+In
+Particular
+</items>
+ <initial_choice>0</initial_choice>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox15</name>
+ <homogeneous>False</homogeneous>
+ <spacing>5</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>GroupNameEntry</name>
+ <can_focus>True</can_focus>
+ <has_focus>True</has_focus>
+ <signal>
+ <name>activate</name>
+ <handler>on_GroupJoinButton_clicked</handler>
+ <last_modification_time>Tue, 29 Jan 2002 13:27:18 GMT</last_modification_time>
+ </signal>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>GroupJoinButton</name>
+ <can_default>True</can_default>
+ <has_default>True</has_default>
+ <can_focus>True</can_focus>
+ <signal>
+ <name>clicked</name>
+ <handler>on_GroupJoinButton_clicked</handler>
+ <last_modification_time>Tue, 29 Jan 2002 13:16:50 GMT</last_modification_time>
+ </signal>
+ <label>Join</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+</widget>
+
+<widget>
+ <class>GtkWindow</class>
+ <name>UnifiedWindow</name>
+ <title>Twisted Instance Messenger</title>
+ <type>GTK_WINDOW_TOPLEVEL</type>
+ <position>GTK_WIN_POS_NONE</position>
+ <modal>False</modal>
+ <allow_shrink>False</allow_shrink>
+ <allow_grow>True</allow_grow>
+ <auto_shrink>False</auto_shrink>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox25</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox28</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button74</name>
+ <can_focus>True</can_focus>
+ <label>&gt;</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkEntry</class>
+ <name>entry3</name>
+ <can_focus>True</can_focus>
+ <editable>True</editable>
+ <text_visible>True</text_visible>
+ <text_max_length>0</text_max_length>
+ <text></text>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkOptionMenu</class>
+ <name>optionmenu3</name>
+ <items>List
+Of
+Online
+Accounts
+</items>
+ <initial_choice>0</initial_choice>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkOptionMenu</class>
+ <name>optionmenu4</name>
+ <can_focus>True</can_focus>
+ <items>Contact
+Person
+Group
+Account
+</items>
+ <initial_choice>0</initial_choice>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkHPaned</class>
+ <name>hpaned1</name>
+ <handle_size>10</handle_size>
+ <gutter_size>6</gutter_size>
+ <position>0</position>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox26</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+ <child>
+ <shrink>True</shrink>
+ <resize>False</resize>
+ </child>
+
+ <widget>
+ <class>GtkFrame</class>
+ <name>frame7</name>
+ <border_width>2</border_width>
+ <label>Accounts</label>
+ <label_xalign>0</label_xalign>
+ <shadow_type>GTK_SHADOW_ETCHED_IN</shadow_type>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox27</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow18</name>
+ <hscrollbar_policy>GTK_POLICY_AUTOMATIC</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_AUTOMATIC</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkCList</class>
+ <name>clist4</name>
+ <columns>4</columns>
+ <column_widths>18,25,25,80</column_widths>
+ <selection_mode>GTK_SELECTION_SINGLE</selection_mode>
+ <show_titles>False</show_titles>
+ <shadow_type>GTK_SHADOW_IN</shadow_type>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label95</name>
+ <label>label87</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label96</name>
+ <label>label88</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label97</name>
+ <label>label89</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label98</name>
+ <label>label90</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox23</name>
+ <homogeneous>True</homogeneous>
+ <spacing>2</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button65</name>
+ <label>New</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button66</name>
+ <label>Delete</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button67</name>
+ <label>Connect</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkFrame</class>
+ <name>frame8</name>
+ <border_width>2</border_width>
+ <label>Contacts</label>
+ <label_xalign>0</label_xalign>
+ <shadow_type>GTK_SHADOW_ETCHED_IN</shadow_type>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox28</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow19</name>
+ <hscrollbar_policy>GTK_POLICY_AUTOMATIC</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_AUTOMATIC</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkCList</class>
+ <name>clist5</name>
+ <columns>3</columns>
+ <column_widths>18,17,80</column_widths>
+ <selection_mode>GTK_SELECTION_SINGLE</selection_mode>
+ <show_titles>False</show_titles>
+ <shadow_type>GTK_SHADOW_IN</shadow_type>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label99</name>
+ <label>label84</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label100</name>
+ <label>label85</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label101</name>
+ <label>label86</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox24</name>
+ <homogeneous>True</homogeneous>
+ <spacing>2</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button68</name>
+ <can_focus>True</can_focus>
+ <label>Talk</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button69</name>
+ <can_focus>True</can_focus>
+ <label>Info</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button70</name>
+ <can_focus>True</can_focus>
+ <label>Add</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button71</name>
+ <can_focus>True</can_focus>
+ <label>Remove</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkFrame</class>
+ <name>frame9</name>
+ <border_width>2</border_width>
+ <label>Groups</label>
+ <label_xalign>0</label_xalign>
+ <shadow_type>GTK_SHADOW_ETCHED_IN</shadow_type>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkVBox</class>
+ <name>vbox29</name>
+ <homogeneous>False</homogeneous>
+ <spacing>0</spacing>
+
+ <widget>
+ <class>GtkScrolledWindow</class>
+ <name>scrolledwindow20</name>
+ <hscrollbar_policy>GTK_POLICY_AUTOMATIC</hscrollbar_policy>
+ <vscrollbar_policy>GTK_POLICY_AUTOMATIC</vscrollbar_policy>
+ <hupdate_policy>GTK_UPDATE_CONTINUOUS</hupdate_policy>
+ <vupdate_policy>GTK_UPDATE_CONTINUOUS</vupdate_policy>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkCList</class>
+ <name>clist6</name>
+ <columns>3</columns>
+ <column_widths>21,75,80</column_widths>
+ <selection_mode>GTK_SELECTION_SINGLE</selection_mode>
+ <show_titles>False</show_titles>
+ <shadow_type>GTK_SHADOW_IN</shadow_type>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label102</name>
+ <label>label91</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label103</name>
+ <label>label92</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <child_name>CList:title</child_name>
+ <name>label104</name>
+ <label>label93</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkHBox</class>
+ <name>hbox27</name>
+ <homogeneous>True</homogeneous>
+ <spacing>2</spacing>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>True</fill>
+ </child>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button72</name>
+ <label>Join</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkButton</class>
+ <name>button73</name>
+ <label>Leave</label>
+ <relief>GTK_RELIEF_NORMAL</relief>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkHSeparator</class>
+ <name>hseparator2</name>
+ <child>
+ <padding>0</padding>
+ <expand>True</expand>
+ <fill>True</fill>
+ </child>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label105</name>
+ <label>Twisted IM V. %s</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>3</ypad>
+ <child>
+ <padding>0</padding>
+ <expand>False</expand>
+ <fill>False</fill>
+ </child>
+ </widget>
+ </widget>
+
+ <widget>
+ <class>GtkLabel</class>
+ <name>label106</name>
+ <label>This
+Space
+Left
+Intentionally
+Blank
+(Here is where the UI for the currently
+selected element
+for interaction
+will go.)</label>
+ <justify>GTK_JUSTIFY_CENTER</justify>
+ <wrap>False</wrap>
+ <xalign>0.5</xalign>
+ <yalign>0.5</yalign>
+ <xpad>0</xpad>
+ <ypad>0</ypad>
+ <child>
+ <shrink>True</shrink>
+ <resize>True</resize>
+ </child>
+ </widget>
+ </widget>
+ </widget>
+</widget>
+
+</GTK-Interface>
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/interfaces.py b/vendor/Twisted-10.0.0/twisted/words/im/interfaces.py
new file mode 100644
index 0000000000..b616674a5a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/interfaces.py
@@ -0,0 +1,364 @@
+# -*- Python -*-
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Pan-protocol chat client.
+"""
+
+from zope.interface import Interface, Attribute
+
+from twisted.words.im import locals
+
+# (Random musings, may not reflect on current state of code:)
+#
+# Accounts have Protocol components (clients)
+# Persons have Conversation components
+# Groups have GroupConversation components
+# Persons and Groups are associated with specific Accounts
+# At run-time, Clients/Accounts are slaved to a User Interface
+# (Note: User may be a bot, so don't assume all UIs are built on gui toolkits)
+
+
+class IAccount(Interface):
+ """
+ I represent a user's account with a chat service.
+ """
+
+ client = Attribute('The L{IClient} currently connecting to this account, if any.')
+ gatewayType = Attribute('A C{str} that identifies the protocol used by this account.')
+
+ def __init__(accountName, autoLogin, username, password, host, port):
+ """
+ @type accountName: string
+ @param accountName: A name to refer to the account by locally.
+ @type autoLogin: boolean
+ @type username: string
+ @type password: string
+ @type host: string
+ @type port: integer
+ """
+
+ def isOnline():
+ """
+ Am I online?
+
+ @rtype: boolean
+ """
+
+ def logOn(chatui):
+ """
+ Go on-line.
+
+ @type chatui: Implementor of C{IChatUI}
+
+ @rtype: L{Deferred} L{Client}
+ """
+
+ def logOff():
+ """
+ Sign off.
+ """
+
+ def getGroup(groupName):
+ """
+ @rtype: L{Group<IGroup>}
+ """
+
+ def getPerson(personName):
+ """
+ @rtype: L{Person<IPerson>}
+ """
+
+class IClient(Interface):
+
+ account = Attribute('The L{IAccount} I am a Client for')
+
+ def __init__(account, chatui, logonDeferred):
+ """
+ @type account: L{IAccount}
+ @type chatui: L{IChatUI}
+ @param logonDeferred: Will be called back once I am logged on.
+ @type logonDeferred: L{Deferred<twisted.internet.defer.Deferred>}
+ """
+
+ def joinGroup(groupName):
+ """
+ @param groupName: The name of the group to join.
+ @type groupName: string
+ """
+
+ def leaveGroup(groupName):
+ """
+ @param groupName: The name of the group to leave.
+ @type groupName: string
+ """
+
+ def getGroupConversation(name, hide=0):
+ pass
+
+ def getPerson(name):
+ pass
+
+
+class IPerson(Interface):
+
+ def __init__(name, account):
+ """
+ Initialize me.
+
+ @param name: My name, as the server knows me.
+ @type name: string
+ @param account: The account I am accessed through.
+ @type account: I{Account}
+ """
+
+ def isOnline():
+ """
+ Am I online right now?
+
+ @rtype: boolean
+ """
+
+ def getStatus():
+ """
+ What is my on-line status?
+
+ @return: L{locals.StatusEnum}
+ """
+
+ def getIdleTime():
+ """
+ @rtype: string (XXX: How about a scalar?)
+ """
+
+ def sendMessage(text, metadata=None):
+ """
+ Send a message to this person.
+
+ @type text: string
+ @type metadata: dict
+ """
+
+
+class IGroup(Interface):
+ """
+ A group which you may have a conversation with.
+
+ Groups generally have a loosely-defined set of members, who may
+ leave and join at any time.
+ """
+
+ name = Attribute('My C{str} name, as the server knows me.')
+ account = Attribute('The L{Account<IAccount>} I am accessed through.')
+
+ def __init__(name, account):
+ """
+ Initialize me.
+
+ @param name: My name, as the server knows me.
+ @type name: str
+ @param account: The account I am accessed through.
+ @type account: L{Account<IAccount>}
+ """
+
+ def setTopic(text):
+ """
+ Set this Groups topic on the server.
+
+ @type text: string
+ """
+
+ def sendGroupMessage(text, metadata=None):
+ """
+ Send a message to this group.
+
+ @type text: str
+
+ @type metadata: dict
+ @param metadata: Valid keys for this dictionary include:
+
+ - C{'style'}: associated with one of:
+ - C{'emote'}: indicates this is an action
+ """
+
+ def join():
+ """
+ Join this group.
+ """
+
+ def leave():
+ """
+ Depart this group.
+ """
+
+
+class IConversation(Interface):
+ """
+ A conversation with a specific person.
+ """
+
+ def __init__(person, chatui):
+ """
+ @type person: L{IPerson}
+ """
+
+ def show():
+ """
+ doesn't seem like it belongs in this interface.
+ """
+
+ def hide():
+ """
+ nor this neither.
+ """
+
+ def sendText(text, metadata):
+ pass
+
+ def showMessage(text, metadata):
+ pass
+
+ def changedNick(person, newnick):
+ """
+ @param person: XXX Shouldn't this always be Conversation.person?
+ """
+
+class IGroupConversation(Interface):
+
+ def show():
+ """
+ doesn't seem like it belongs in this interface.
+ """
+
+ def hide():
+ """
+ nor this neither.
+ """
+
+ def sendText(text, metadata):
+ pass
+
+ def showGroupMessage(sender, text, metadata):
+ pass
+
+ def setGroupMembers(members):
+ """
+ Sets the list of members in the group and displays it to the user.
+ """
+
+ def setTopic(topic, author):
+ """
+ Displays the topic (from the server) for the group conversation window.
+
+ @type topic: string
+ @type author: string (XXX: Not Person?)
+ """
+
+ def memberJoined(member):
+ """
+ Adds the given member to the list of members in the group conversation
+ and displays this to the user,
+
+ @type member: string (XXX: Not Person?)
+ """
+
+ def memberChangedNick(oldnick, newnick):
+ """
+ Changes the oldnick in the list of members to C{newnick} and displays this
+ change to the user,
+
+ @type oldnick: string (XXX: Not Person?)
+ @type newnick: string
+ """
+
+ def memberLeft(member):
+ """
+ Deletes the given member from the list of members in the group
+ conversation and displays the change to the user.
+
+ @type member: string (XXX: Not Person?)
+ """
+
+
+class IChatUI(Interface):
+
+ def registerAccountClient(client):
+ """
+ Notifies user that an account has been signed on to.
+
+ @type client: L{Client<IClient>}
+ """
+
+ def unregisterAccountClient(client):
+ """
+ Notifies user that an account has been signed off or disconnected.
+
+ @type client: L{Client<IClient>}
+ """
+
+ def getContactsList():
+ """
+ @rtype: L{ContactsList}
+ """
+
+ # WARNING: You'll want to be polymorphed into something with
+ # intrinsic stoning resistance before continuing.
+
+ def getConversation(person, Class, stayHidden=0):
+ """
+ For the given person object, returns the conversation window
+ or creates and returns a new conversation window if one does not exist.
+
+ @type person: L{Person<IPerson>}
+ @type Class: L{Conversation<IConversation>} class
+ @type stayHidden: boolean
+
+ @rtype: L{Conversation<IConversation>}
+ """
+
+ def getGroupConversation(group, Class, stayHidden=0):
+ """
+ For the given group object, returns the group conversation window or
+ creates and returns a new group conversation window if it doesn't exist.
+
+ @type group: L{Group<interfaces.IGroup>}
+ @type Class: L{Conversation<interfaces.IConversation>} class
+ @type stayHidden: boolean
+
+ @rtype: L{GroupConversation<interfaces.IGroupConversation>}
+ """
+
+ def getPerson(name, client):
+ """
+ Get a Person for a client.
+
+ Duplicates L{IAccount.getPerson}.
+
+ @type name: string
+ @type client: L{Client<IClient>}
+
+ @rtype: L{Person<IPerson>}
+ """
+
+ def getGroup(name, client):
+ """
+ Get a Group for a client.
+
+ Duplicates L{IAccount.getGroup}.
+
+ @type name: string
+ @type client: L{Client<IClient>}
+
+ @rtype: L{Group<IGroup>}
+ """
+
+ def contactChangedNick(oldnick, newnick):
+ """
+ For the given person, changes the person's name to newnick, and
+ tells the contact list and any conversation windows with that person
+ to change as well.
+
+ @type oldnick: string
+ @type newnick: string
+ """
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/ircsupport.py b/vendor/Twisted-10.0.0/twisted/words/im/ircsupport.py
new file mode 100644
index 0000000000..01db52a0e6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/ircsupport.py
@@ -0,0 +1,261 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""IRC support for Instance Messenger."""
+
+import string
+
+from twisted.words.protocols import irc
+from twisted.words.im.locals import ONLINE
+from twisted.internet import defer, reactor, protocol
+from twisted.internet.defer import succeed
+from twisted.words.im import basesupport, interfaces, locals
+from zope.interface import implements
+
+
+class IRCPerson(basesupport.AbstractPerson):
+
+ def imperson_whois(self):
+ if self.account.client is None:
+ raise locals.OfflineError
+ self.account.client.sendLine("WHOIS %s" % self.name)
+
+ ### interface impl
+
+ def isOnline(self):
+ return ONLINE
+
+ def getStatus(self):
+ return ONLINE
+
+ def setStatus(self,status):
+ self.status=status
+ self.chat.getContactsList().setContactStatus(self)
+
+ def sendMessage(self, text, meta=None):
+ if self.account.client is None:
+ raise locals.OfflineError
+ for line in string.split(text, '\n'):
+ if meta and meta.get("style", None) == "emote":
+ self.account.client.ctcpMakeQuery(self.name,[('ACTION', line)])
+ else:
+ self.account.client.msg(self.name, line)
+ return succeed(text)
+
+class IRCGroup(basesupport.AbstractGroup):
+
+ implements(interfaces.IGroup)
+
+ def imgroup_testAction(self):
+ pass
+
+ def imtarget_kick(self, target):
+ if self.account.client is None:
+ raise locals.OfflineError
+ reason = "for great justice!"
+ self.account.client.sendLine("KICK #%s %s :%s" % (
+ self.name, target.name, reason))
+
+ ### Interface Implementation
+
+ def setTopic(self, topic):
+ if self.account.client is None:
+ raise locals.OfflineError
+ self.account.client.topic(self.name, topic)
+
+ def sendGroupMessage(self, text, meta={}):
+ if self.account.client is None:
+ raise locals.OfflineError
+ if meta and meta.get("style", None) == "emote":
+ self.account.client.me(self.name,text)
+ return succeed(text)
+ #standard shmandard, clients don't support plain escaped newlines!
+ for line in string.split(text, '\n'):
+ self.account.client.say(self.name, line)
+ return succeed(text)
+
+ def leave(self):
+ if self.account.client is None:
+ raise locals.OfflineError
+ self.account.client.leave(self.name)
+ self.account.client.getGroupConversation(self.name,1)
+
+
+class IRCProto(basesupport.AbstractClientMixin, irc.IRCClient):
+ def __init__(self, account, chatui, logonDeferred=None):
+ basesupport.AbstractClientMixin.__init__(self, account, chatui,
+ logonDeferred)
+ self._namreplies={}
+ self._ingroups={}
+ self._groups={}
+ self._topics={}
+
+ def getGroupConversation(self, name, hide=0):
+ name=string.lower(name)
+ return self.chat.getGroupConversation(self.chat.getGroup(name, self),
+ stayHidden=hide)
+
+ def getPerson(self,name):
+ return self.chat.getPerson(name, self)
+
+ def connectionMade(self):
+ # XXX: Why do I duplicate code in IRCClient.register?
+ try:
+ if self.account.password:
+ self.sendLine("PASS :%s" % self.account.password)
+ self.setNick(self.account.username)
+ self.sendLine("USER %s foo bar :Twisted-IM user" % (self.nickname,))
+ for channel in self.account.channels:
+ self.joinGroup(channel)
+ self.account._isOnline=1
+ if self._logonDeferred is not None:
+ self._logonDeferred.callback(self)
+ self.chat.getContactsList()
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def setNick(self,nick):
+ self.name=nick
+ self.accountName="%s (IRC)"%nick
+ irc.IRCClient.setNick(self,nick)
+
+ def kickedFrom(self, channel, kicker, message):
+ """
+ Called when I am kicked from a channel.
+ """
+ return self.chat.getGroupConversation(
+ self.chat.getGroup(channel[1:], self), 1)
+
+ def userKicked(self, kickee, channel, kicker, message):
+ pass
+
+ def noticed(self, username, channel, message):
+ self.privmsg(username, channel, message, {"dontAutoRespond": 1})
+
+ def privmsg(self, username, channel, message, metadata=None):
+ if metadata is None:
+ metadata = {}
+ username=string.split(username,'!',1)[0]
+ if username==self.name: return
+ if channel[0]=='#':
+ group=channel[1:]
+ self.getGroupConversation(group).showGroupMessage(username, message, metadata)
+ return
+ self.chat.getConversation(self.getPerson(username)).showMessage(message, metadata)
+
+ def action(self,username,channel,emote):
+ username=string.split(username,'!',1)[0]
+ if username==self.name: return
+ meta={'style':'emote'}
+ if channel[0]=='#':
+ group=channel[1:]
+ self.getGroupConversation(group).showGroupMessage(username, emote, meta)
+ return
+ self.chat.getConversation(self.getPerson(username)).showMessage(emote,meta)
+
+ def irc_RPL_NAMREPLY(self,prefix,params):
+ """
+ RPL_NAMREPLY
+ >> NAMES #bnl
+ << :Arlington.VA.US.Undernet.Org 353 z3p = #bnl :pSwede Dan-- SkOyg AG
+ """
+ group=string.lower(params[2][1:])
+ users=string.split(params[3])
+ for ui in range(len(users)):
+ while users[ui][0] in ["@","+"]: # channel modes
+ users[ui]=users[ui][1:]
+ if not self._namreplies.has_key(group):
+ self._namreplies[group]=[]
+ self._namreplies[group].extend(users)
+ for nickname in users:
+ try:
+ self._ingroups[nickname].append(group)
+ except:
+ self._ingroups[nickname]=[group]
+
+ def irc_RPL_ENDOFNAMES(self,prefix,params):
+ group=params[1][1:]
+ self.getGroupConversation(group).setGroupMembers(self._namreplies[string.lower(group)])
+ del self._namreplies[string.lower(group)]
+
+ def irc_RPL_TOPIC(self,prefix,params):
+ self._topics[params[1][1:]]=params[2]
+
+ def irc_333(self,prefix,params):
+ group=params[1][1:]
+ self.getGroupConversation(group).setTopic(self._topics[group],params[2])
+ del self._topics[group]
+
+ def irc_TOPIC(self,prefix,params):
+ nickname = string.split(prefix,"!")[0]
+ group = params[0][1:]
+ topic = params[1]
+ self.getGroupConversation(group).setTopic(topic,nickname)
+
+ def irc_JOIN(self,prefix,params):
+ nickname=string.split(prefix,"!")[0]
+ group=string.lower(params[0][1:])
+ if nickname!=self.nickname:
+ try:
+ self._ingroups[nickname].append(group)
+ except:
+ self._ingroups[nickname]=[group]
+ self.getGroupConversation(group).memberJoined(nickname)
+
+ def irc_PART(self,prefix,params):
+ nickname=string.split(prefix,"!")[0]
+ group=string.lower(params[0][1:])
+ if nickname!=self.nickname:
+ if group in self._ingroups[nickname]:
+ self._ingroups[nickname].remove(group)
+ self.getGroupConversation(group).memberLeft(nickname)
+
+ def irc_QUIT(self,prefix,params):
+ nickname=string.split(prefix,"!")[0]
+ if self._ingroups.has_key(nickname):
+ for group in self._ingroups[nickname]:
+ self.getGroupConversation(group).memberLeft(nickname)
+ self._ingroups[nickname]=[]
+
+ def irc_NICK(self, prefix, params):
+ fromNick = string.split(prefix, "!")[0]
+ toNick = params[0]
+ if not self._ingroups.has_key(fromNick):
+ return
+ for group in self._ingroups[fromNick]:
+ self.getGroupConversation(group).memberChangedNick(fromNick, toNick)
+ self._ingroups[toNick] = self._ingroups[fromNick]
+ del self._ingroups[fromNick]
+
+ def irc_unknown(self, prefix, command, params):
+ pass
+
+ # GTKIM calls
+ def joinGroup(self,name):
+ self.join(name)
+ self.getGroupConversation(name)
+
+class IRCAccount(basesupport.AbstractAccount):
+ implements(interfaces.IAccount)
+ gatewayType = "IRC"
+
+ _groupFactory = IRCGroup
+ _personFactory = IRCPerson
+
+ def __init__(self, accountName, autoLogin, username, password, host, port,
+ channels=''):
+ basesupport.AbstractAccount.__init__(self, accountName, autoLogin,
+ username, password, host, port)
+ self.channels = map(string.strip,string.split(channels,','))
+ if self.channels == ['']:
+ self.channels = []
+
+ def _startLogOn(self, chatui):
+ logonDeferred = defer.Deferred()
+ cc = protocol.ClientCreator(reactor, IRCProto, self, chatui,
+ logonDeferred)
+ d = cc.connectTCP(self.host, self.port)
+ d.addErrback(logonDeferred.errback)
+ return logonDeferred
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/locals.py b/vendor/Twisted-10.0.0/twisted/words/im/locals.py
new file mode 100644
index 0000000000..02025f928d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/locals.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+class Enum:
+ group = None
+
+ def __init__(self, label):
+ self.label = label
+
+ def __repr__(self):
+ return '<%s: %s>' % (self.group, self.label)
+
+ def __str__(self):
+ return self.label
+
+
+class StatusEnum(Enum):
+ group = 'Status'
+
+OFFLINE = Enum('Offline')
+ONLINE = Enum('Online')
+AWAY = Enum('Away')
+
+class OfflineError(Exception):
+ """The requested action can't happen while offline."""
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/pbsupport.py b/vendor/Twisted-10.0.0/twisted/words/im/pbsupport.py
new file mode 100644
index 0000000000..7e750b7306
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/pbsupport.py
@@ -0,0 +1,260 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""L{twisted.words} support for Instance Messenger."""
+
+from __future__ import nested_scopes
+
+from twisted.internet import defer
+from twisted.internet import error
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.spread import pb
+
+from twisted.words.im.locals import ONLINE, OFFLINE, AWAY
+
+from twisted.words.im import basesupport, interfaces
+from zope.interface import implements
+
+
+class TwistedWordsPerson(basesupport.AbstractPerson):
+ """I a facade for a person you can talk to through a twisted.words service.
+ """
+ def __init__(self, name, wordsAccount):
+ basesupport.AbstractPerson.__init__(self, name, wordsAccount)
+ self.status = OFFLINE
+
+ def isOnline(self):
+ return ((self.status == ONLINE) or
+ (self.status == AWAY))
+
+ def getStatus(self):
+ return self.status
+
+ def sendMessage(self, text, metadata):
+ """Return a deferred...
+ """
+ if metadata:
+ d=self.account.client.perspective.directMessage(self.name,
+ text, metadata)
+ d.addErrback(self.metadataFailed, "* "+text)
+ return d
+ else:
+ return self.account.client.perspective.callRemote('directMessage',self.name, text)
+
+ def metadataFailed(self, result, text):
+ print "result:",result,"text:",text
+ return self.account.client.perspective.directMessage(self.name, text)
+
+ def setStatus(self, status):
+ self.status = status
+ self.chat.getContactsList().setContactStatus(self)
+
+class TwistedWordsGroup(basesupport.AbstractGroup):
+ implements(interfaces.IGroup)
+ def __init__(self, name, wordsClient):
+ basesupport.AbstractGroup.__init__(self, name, wordsClient)
+ self.joined = 0
+
+ def sendGroupMessage(self, text, metadata=None):
+ """Return a deferred.
+ """
+ #for backwards compatibility with older twisted.words servers.
+ if metadata:
+ d=self.account.client.perspective.callRemote(
+ 'groupMessage', self.name, text, metadata)
+ d.addErrback(self.metadataFailed, "* "+text)
+ return d
+ else:
+ return self.account.client.perspective.callRemote('groupMessage',
+ self.name, text)
+
+ def setTopic(self, text):
+ self.account.client.perspective.callRemote(
+ 'setGroupMetadata',
+ {'topic': text, 'topic_author': self.client.name},
+ self.name)
+
+ def metadataFailed(self, result, text):
+ print "result:",result,"text:",text
+ return self.account.client.perspective.callRemote('groupMessage',
+ self.name, text)
+
+ def joining(self):
+ self.joined = 1
+
+ def leaving(self):
+ self.joined = 0
+
+ def leave(self):
+ return self.account.client.perspective.callRemote('leaveGroup',
+ self.name)
+
+
+
+class TwistedWordsClient(pb.Referenceable, basesupport.AbstractClientMixin):
+ """In some cases, this acts as an Account, since it a source of text
+ messages (multiple Words instances may be on a single PB connection)
+ """
+ def __init__(self, acct, serviceName, perspectiveName, chatui,
+ _logonDeferred=None):
+ self.accountName = "%s (%s:%s)" % (acct.accountName, serviceName, perspectiveName)
+ self.name = perspectiveName
+ print "HELLO I AM A PB SERVICE", serviceName, perspectiveName
+ self.chat = chatui
+ self.account = acct
+ self._logonDeferred = _logonDeferred
+
+ def getPerson(self, name):
+ return self.chat.getPerson(name, self)
+
+ def getGroup(self, name):
+ return self.chat.getGroup(name, self)
+
+ def getGroupConversation(self, name):
+ return self.chat.getGroupConversation(self.getGroup(name))
+
+ def addContact(self, name):
+ self.perspective.callRemote('addContact', name)
+
+ def remote_receiveGroupMembers(self, names, group):
+ print 'received group members:', names, group
+ self.getGroupConversation(group).setGroupMembers(names)
+
+ def remote_receiveGroupMessage(self, sender, group, message, metadata=None):
+ print 'received a group message', sender, group, message, metadata
+ self.getGroupConversation(group).showGroupMessage(sender, message, metadata)
+
+ def remote_memberJoined(self, member, group):
+ print 'member joined', member, group
+ self.getGroupConversation(group).memberJoined(member)
+
+ def remote_memberLeft(self, member, group):
+ print 'member left'
+ self.getGroupConversation(group).memberLeft(member)
+
+ def remote_notifyStatusChanged(self, name, status):
+ self.chat.getPerson(name, self).setStatus(status)
+
+ def remote_receiveDirectMessage(self, name, message, metadata=None):
+ self.chat.getConversation(self.chat.getPerson(name, self)).showMessage(message, metadata)
+
+ def remote_receiveContactList(self, clist):
+ for name, status in clist:
+ self.chat.getPerson(name, self).setStatus(status)
+
+ def remote_setGroupMetadata(self, dict_, groupName):
+ if dict_.has_key("topic"):
+ self.getGroupConversation(groupName).setTopic(dict_["topic"], dict_.get("topic_author", None))
+
+ def joinGroup(self, name):
+ self.getGroup(name).joining()
+ return self.perspective.callRemote('joinGroup', name).addCallback(self._cbGroupJoined, name)
+
+ def leaveGroup(self, name):
+ self.getGroup(name).leaving()
+ return self.perspective.callRemote('leaveGroup', name).addCallback(self._cbGroupLeft, name)
+
+ def _cbGroupJoined(self, result, name):
+ groupConv = self.chat.getGroupConversation(self.getGroup(name))
+ groupConv.showGroupMessage("sys", "you joined")
+ self.perspective.callRemote('getGroupMembers', name)
+
+ def _cbGroupLeft(self, result, name):
+ print 'left',name
+ groupConv = self.chat.getGroupConversation(self.getGroup(name), 1)
+ groupConv.showGroupMessage("sys", "you left")
+
+ def connected(self, perspective):
+ print 'Connected Words Client!', perspective
+ if self._logonDeferred is not None:
+ self._logonDeferred.callback(self)
+ self.perspective = perspective
+ self.chat.getContactsList()
+
+
+pbFrontEnds = {
+ "twisted.words": TwistedWordsClient,
+ "twisted.reality": None
+ }
+
+
+class PBAccount(basesupport.AbstractAccount):
+ implements(interfaces.IAccount)
+ gatewayType = "PB"
+ _groupFactory = TwistedWordsGroup
+ _personFactory = TwistedWordsPerson
+
+ def __init__(self, accountName, autoLogin, username, password, host, port,
+ services=None):
+ """
+ @param username: The name of your PB Identity.
+ @type username: string
+ """
+ basesupport.AbstractAccount.__init__(self, accountName, autoLogin,
+ username, password, host, port)
+ self.services = []
+ if not services:
+ services = [('twisted.words', 'twisted.words', username)]
+ for serviceType, serviceName, perspectiveName in services:
+ self.services.append([pbFrontEnds[serviceType], serviceName,
+ perspectiveName])
+
+ def logOn(self, chatui):
+ """
+ @returns: this breaks with L{interfaces.IAccount}
+ @returntype: DeferredList of L{interfaces.IClient}s
+ """
+ # Overriding basesupport's implementation on account of the
+ # fact that _startLogOn tends to return a deferredList rather
+ # than a simple Deferred, and we need to do registerAccountClient.
+ if (not self._isConnecting) and (not self._isOnline):
+ self._isConnecting = 1
+ d = self._startLogOn(chatui)
+ d.addErrback(self._loginFailed)
+ def registerMany(results):
+ for success, result in results:
+ if success:
+ chatui.registerAccountClient(result)
+ self._cb_logOn(result)
+ else:
+ log.err(result)
+ d.addCallback(registerMany)
+ return d
+ else:
+ raise error.ConnectionError("Connection in progress")
+
+
+ def _startLogOn(self, chatui):
+ print 'Connecting...',
+ d = pb.getObjectAt(self.host, self.port)
+ d.addCallbacks(self._cbConnected, self._ebConnected,
+ callbackArgs=(chatui,))
+ return d
+
+ def _cbConnected(self, root, chatui):
+ print 'Connected!'
+ print 'Identifying...',
+ d = pb.authIdentity(root, self.username, self.password)
+ d.addCallbacks(self._cbIdent, self._ebConnected,
+ callbackArgs=(chatui,))
+ return d
+
+ def _cbIdent(self, ident, chatui):
+ if not ident:
+ print 'falsely identified.'
+ return self._ebConnected(Failure(Exception("username or password incorrect")))
+ print 'Identified!'
+ dl = []
+ for handlerClass, sname, pname in self.services:
+ d = defer.Deferred()
+ dl.append(d)
+ handler = handlerClass(self, sname, pname, chatui, d)
+ ident.callRemote('attach', sname, pname, handler).addCallback(handler.connected)
+ return defer.DeferredList(dl)
+
+ def _ebConnected(self, error):
+ print 'Not connected.'
+ return error
+
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/proxyui.py b/vendor/Twisted-10.0.0/twisted/words/im/proxyui.py
new file mode 100644
index 0000000000..de11bf7560
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/proxyui.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+from twisted.words.protocols.irc import IRC
+from twisted.python import log
+from twisted.internet.protocol import Factory
+
+class IRCUserInterface(IRC):
+ def connectionLost(self):
+ del self.factory.ircui
+
+class IRCUIFactory(Factory):
+ ircui = None
+ def buildProtocol(self):
+ if self.ircui:
+ log.msg("already logged in")
+ return None
+ i = IRCUserInterface()
+ i.factory = self
+ self.ircui = i
+ return i
+
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/tap.py b/vendor/Twisted-10.0.0/twisted/words/im/tap.py
new file mode 100644
index 0000000000..64bddce4ab
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/tap.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+
+from twisted.words.im.proxyui import IRCUIFactory
+from twisted.python import usage
+
+class Options(usage.Options):
+ optParameters = [["ircport", "p", "6667",
+ "Port to start the IRC server on."]]
+
+def updateApplication(app, config):
+ factory = IRCUIFactory()
+ app.listenTCP(int(config.opts['ircport']), IRCUIFactory())
diff --git a/vendor/Twisted-10.0.0/twisted/words/im/tocsupport.py b/vendor/Twisted-10.0.0/twisted/words/im/tocsupport.py
new file mode 100644
index 0000000000..36ac2cd9b2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/im/tocsupport.py
@@ -0,0 +1,220 @@
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""TOC (i.e. AIM) support for Instance Messenger."""
+
+# System Imports
+import string, re
+from zope.interface import implements
+
+# Twisted Imports
+from twisted.words.protocols import toc
+from twisted.words.im.locals import ONLINE, OFFLINE, AWAY
+from twisted.internet import defer, reactor, protocol
+from twisted.internet.defer import succeed
+
+# Sibling Imports
+from twisted.words.im import basesupport, interfaces, locals
+
+def dehtml(text):
+ text=string.replace(text,"<br>","\n")
+ text=string.replace(text,"<BR>","\n")
+ text=string.replace(text,"<Br>","\n") # XXX make this a regexp
+ text=string.replace(text,"<bR>","\n")
+ text=re.sub('<.*?>','',text)
+ text=string.replace(text,'&gt;','>')
+ text=string.replace(text,'&lt;','<')
+ text=string.replace(text,'&amp;','&')
+ text=string.replace(text,'&nbsp;',' ')
+ text=string.replace(text,'&#34;','"')
+ return text
+
+def html(text):
+ text=string.replace(text,'"','&#34;')
+ text=string.replace(text,'&amp;','&')
+ text=string.replace(text,'&lt;','<')
+ text=string.replace(text,'&gt;','>')
+ text=string.replace(text,"\n","<br>")
+ return '<font color="#000000" back="#ffffff" size=3>%s</font>'%text
+
+class TOCPerson(basesupport.AbstractPerson):
+ def isOnline(self):
+ return self.status != OFFLINE
+
+ def getStatus(self):
+ return self.status
+
+ def getIdleTime(self):
+ return str(self.idletime)
+
+ def setStatusAndIdle(self, status, idletime):
+ if self.account.client is None:
+ raise locals.OfflineError
+ self.status = status
+ self.idletime = idletime
+ self.account.client.chat.getContactsList().setContactStatus(self)
+
+ def sendMessage(self, text, meta=None):
+ if self.account.client is None:
+ raise locals.OfflineError
+ if meta:
+ if meta.get("style", None) == "emote":
+ text="* "+text+"* "
+ self.account.client.say(self.name,html(text))
+ return succeed(text)
+
+class TOCGroup(basesupport.AbstractGroup):
+ implements(interfaces.IGroup)
+ def __init__(self, name, tocAccount):
+ basesupport.AbstractGroup.__init__(self, name, tocAccount)
+ self.roomID = self.client.roomID[self.name]
+
+ def sendGroupMessage(self, text, meta=None):
+ if self.account.client is None:
+ raise locals.OfflineError
+ if meta:
+ if meta.get("style", None) == "emote":
+ text="* "+text+"* "
+ self.account.client.chat_say(self.roomID,html(text))
+ return succeed(text)
+
+ def leave(self):
+ if self.account.client is None:
+ raise locals.OfflineError
+ self.account.client.chat_leave(self.roomID)
+
+
+class TOCProto(basesupport.AbstractClientMixin, toc.TOCClient):
+ def __init__(self, account, chatui, logonDeferred):
+ toc.TOCClient.__init__(self, account.username, account.password)
+ basesupport.AbstractClientMixin.__init__(self, account, chatui,
+ logonDeferred)
+ self.roomID = {}
+ self.roomIDreverse = {}
+
+ def _debug(self, m):
+ pass #print '<toc debug>', repr(m)
+
+ def getGroupConversation(self, name, hide=0):
+ return self.chat.getGroupConversation(
+ self.chat.getGroup(name, self), hide)
+
+ def addContact(self, name):
+ self.add_buddy([name])
+ if not self._buddylist.has_key('TwistedIM'):
+ self._buddylist['TwistedIM'] = []
+ if name in self._buddylist['TwistedIM']:
+ # whoops, don't add again
+ return
+ self._buddylist['TwistedIM'].append(name)
+ self.set_config(self._config_mode, self._buddylist, self._permit, self._deny)
+
+ def getPerson(self,name):
+ return self.chat.getPerson(name, self)
+
+ def onLine(self):
+ self.account._isOnline = 1
+ #print '$$!&*$&!(@$*& TOC ONLINE *!#@&$(!*%&'
+
+ def gotConfig(self, mode, buddylist, permit, deny):
+ #print 'got toc config', repr(mode), repr(buddylist), repr(permit), repr(deny)
+ self._config_mode = mode
+ self._buddylist = buddylist
+ self._permit = permit
+ self._deny = deny
+ if permit:
+ self._debug('adding permit')
+ self.add_permit(permit)
+ if deny:
+ self._debug('adding deny')
+ self.add_deny(deny)
+ clist=[]
+ for k in buddylist.keys():
+ self.add_buddy(buddylist[k])
+ for name in buddylist[k]:
+ self.getPerson(name).setStatusAndIdle(OFFLINE, '--')
+ self.signon()
+ name = None
+ def tocNICK(self,data):
+ if not self.name:
+ print 'Waiting for second NICK', data
+ self.name=data[0]
+ self.accountName = '%s (TOC)' % self.name
+ self.chat.getContactsList()
+ else:
+ print 'reregistering...?', data
+ self.name=data[0]
+ # self.accountName = "%s (TOC)"%data[0]
+ if self._logonDeferred is not None:
+ self._logonDeferred.callback(self)
+ self._logonDeferred = None
+
+ ### Error Messages
+ def hearError(self, code, args):
+ print '*** TOC ERROR ***', repr(code), repr(args)
+ def hearWarning(self, newamount, username):
+ print '*** TOC WARNING ***', repr(newamount), repr(username)
+ ### Buddy Messages
+ def hearMessage(self,username,message,autoreply):
+ if autoreply:
+ message='<AUTO-REPLY>: '+message
+ self.chat.getConversation(self.getPerson(username)
+ ).showMessage(dehtml(message))
+ def updateBuddy(self,username,online,evilness,signontime,idletime,userclass,away):
+ if away:
+ status=AWAY
+ elif online:
+ status=ONLINE
+ else:
+ status=OFFLINE
+ self.getPerson(username).setStatusAndIdle(status, idletime)
+
+ ### Group Chat
+ def chatJoined(self, roomid, roomname, users):
+ self.roomID[roomname]=roomid
+ self.roomIDreverse[roomid]=roomname
+ self.getGroupConversation(roomname).setGroupMembers(users)
+ def chatUpdate(self,roomid,member,inroom):
+ group=self.roomIDreverse[roomid]
+ if inroom:
+ self.getGroupConversation(group).memberJoined(member)
+ else:
+ self.getGroupConversation(group).memberLeft(member)
+ def chatHearMessage(self, roomid, username, message):
+ if toc.normalize(username) == toc.normalize(self.name):
+ return # ignore the message
+ group=self.roomIDreverse[roomid]
+ self.getGroupConversation(group).showGroupMessage(username, dehtml(message))
+ def chatHearWhisper(self, roomid, username, message):
+ print '*** user whispered *** ', roomid, username, message
+ def chatInvited(self, roomid, roomname, username, message):
+ print '*** user invited us to chat *** ',roomid, roomname, username, message
+ def chatLeft(self, roomid):
+ group=self.roomIDreverse[roomid]
+ self.getGroupConversation(group,1)
+ del self.roomID[group]
+ del self.roomIDreverse[roomid]
+ def rvousProposal(self,type,cookie,user,vip,port,**kw):
+ print '*** rendezvous. ***', type, cookie, user, vip, port, kw
+ def receiveBytes(self, user, file, chunk, sofar, total):
+ print '*** File transfer! ***', user, file, chunk, sofar, total
+
+ def joinGroup(self,name):
+ self.chat_join(4,toc.normalize(name))
+
+class TOCAccount(basesupport.AbstractAccount):
+ implements(interfaces.IAccount)
+ gatewayType = "AIM (TOC)"
+
+ _groupFactory = TOCGroup
+ _personFactory = TOCPerson
+
+ def _startLogOn(self, chatui):
+ logonDeferred = defer.Deferred()
+ cc = protocol.ClientCreator(reactor, TOCProto, self, chatui,
+ logonDeferred)
+ d = cc.connectTCP(self.host, self.port)
+ d.addErrback(logonDeferred.errback)
+ return logonDeferred
+
diff --git a/vendor/Twisted-10.0.0/twisted/words/iwords.py b/vendor/Twisted-10.0.0/twisted/words/iwords.py
new file mode 100644
index 0000000000..1b9da0769a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/iwords.py
@@ -0,0 +1,266 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import Interface, Attribute, implements
+
+class IProtocolPlugin(Interface):
+ """Interface for plugins providing an interface to a Words service
+ """
+
+ name = Attribute("A single word describing what kind of interface this is (eg, irc or web)")
+
+ def getFactory(realm, portal):
+ """Retrieve a C{twisted.internet.interfaces.IServerFactory} provider
+
+ @param realm: An object providing C{twisted.cred.portal.IRealm} and
+ C{IChatService}, with which service information should be looked up.
+
+ @param portal: An object providing C{twisted.cred.portal.IPortal},
+ through which logins should be performed.
+ """
+
+
+class IGroup(Interface):
+ name = Attribute("A short string, unique among groups.")
+
+ def add(user):
+ """Include the given user in this group.
+
+ @type user: L{IUser}
+ """
+
+ def remove(user, reason=None):
+ """Remove the given user from this group.
+
+ @type user: L{IUser}
+ @type reason: C{unicode}
+ """
+
+ def size():
+ """Return the number of participants in this group.
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with an C{int} representing the the
+ number of participants in this group.
+ """
+
+ def receive(sender, recipient, message):
+ """
+ Broadcast the given message from the given sender to other
+ users in group.
+
+ The message is not re-transmitted to the sender.
+
+ @param sender: L{IUser}
+
+ @type recipient: L{IGroup}
+ @param recipient: This is probably a wart. Maybe it will be removed
+ in the future. For now, it should be the group object the message
+ is being delivered to.
+
+ @param message: C{dict}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with None when delivery has been
+ attempted for all users.
+ """
+
+ def setMetadata(meta):
+ """Change the metadata associated with this group.
+
+ @type meta: C{dict}
+ """
+
+ def iterusers():
+ """Return an iterator of all users in this group.
+ """
+
+
+class IChatClient(Interface):
+ """Interface through which IChatService interacts with clients.
+ """
+
+ name = Attribute("A short string, unique among users. This will be set by the L{IChatService} at login time.")
+
+ def receive(sender, recipient, message):
+ """
+ Callback notifying this user of the given message sent by the
+ given user.
+
+ This will be invoked whenever another user sends a message to a
+ group this user is participating in, or whenever another user sends
+ a message directly to this user. In the former case, C{recipient}
+ will be the group to which the message was sent; in the latter, it
+ will be the same object as the user who is receiving the message.
+
+ @type sender: L{IUser}
+ @type recipient: L{IUser} or L{IGroup}
+ @type message: C{dict}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires when the message has been delivered,
+ or which fails in some way. If the Deferred fails and the message
+ was directed at a group, this user will be removed from that group.
+ """
+
+ def groupMetaUpdate(group, meta):
+ """
+ Callback notifying this user that the metadata for the given
+ group has changed.
+
+ @type group: L{IGroup}
+ @type meta: C{dict}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ """
+
+ def userJoined(group, user):
+ """
+ Callback notifying this user that the given user has joined
+ the given group.
+
+ @type group: L{IGroup}
+ @type user: L{IUser}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ """
+
+ def userLeft(group, user, reason=None):
+ """
+ Callback notifying this user that the given user has left the
+ given group for the given reason.
+
+ @type group: L{IGroup}
+ @type user: L{IUser}
+ @type reason: C{unicode}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ """
+
+
+class IUser(Interface):
+ """Interface through which clients interact with IChatService.
+ """
+
+ realm = Attribute("A reference to the Realm to which this user belongs. Set if and only if the user is logged in.")
+ mind = Attribute("A reference to the mind which logged in to this user. Set if and only if the user is logged in.")
+ name = Attribute("A short string, unique among users.")
+
+ lastMessage = Attribute("A POSIX timestamp indicating the time of the last message received from this user.")
+ signOn = Attribute("A POSIX timestamp indicating this user's most recent sign on time.")
+
+ def loggedIn(realm, mind):
+ """Invoked by the associated L{IChatService} when login occurs.
+
+ @param realm: The L{IChatService} through which login is occurring.
+ @param mind: The mind object used for cred login.
+ """
+
+ def send(recipient, message):
+ """Send the given message to the given user or group.
+
+ @type recipient: Either L{IUser} or L{IGroup}
+ @type message: C{dict}
+ """
+
+ def join(group):
+ """Attempt to join the given group.
+
+ @type group: L{IGroup}
+ @rtype: L{twisted.internet.defer.Deferred}
+ """
+
+ def leave(group):
+ """Discontinue participation in the given group.
+
+ @type group: L{IGroup}
+ @rtype: L{twisted.internet.defer.Deferred}
+ """
+
+ def itergroups():
+ """
+ Return an iterator of all groups of which this user is a
+ member.
+ """
+
+
+class IChatService(Interface):
+ name = Attribute("A short string identifying this chat service (eg, a hostname)")
+
+ createGroupOnRequest = Attribute(
+ "A boolean indicating whether L{getGroup} should implicitly "
+ "create groups which are requested but which do not yet exist.")
+
+ createUserOnRequest = Attribute(
+ "A boolean indicating whether L{getUser} should implicitly "
+ "create users which are requested but which do not yet exist.")
+
+ def itergroups():
+ """Return all groups available on this service.
+
+ @rtype: C{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with a list of C{IGroup} providers.
+ """
+
+ def getGroup(name):
+ """Retrieve the group by the given name.
+
+ @type name: C{str}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with the group with the given
+ name if one exists (or if one is created due to the setting of
+ L{createGroupOnRequest}, or which fails with
+ L{twisted.words.ewords.NoSuchGroup} if no such group exists.
+ """
+
+ def createGroup(name):
+ """Create a new group with the given name.
+
+ @type name: C{str}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with the created group, or
+ with fails with L{twisted.words.ewords.DuplicateGroup} if a
+ group by that name exists already.
+ """
+
+ def lookupGroup(name):
+ """Retrieve a group by name.
+
+ Unlike C{getGroup}, this will never implicitly create a group.
+
+ @type name: C{str}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with the group by the given
+ name, or which fails with L{twisted.words.ewords.NoSuchGroup}.
+ """
+
+ def getUser(name):
+ """Retrieve the user by the given name.
+
+ @type name: C{str}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with the user with the given
+ name if one exists (or if one is created due to the setting of
+ L{createUserOnRequest}, or which fails with
+ L{twisted.words.ewords.NoSuchUser} if no such user exists.
+ """
+
+ def createUser(name):
+ """Create a new user with the given name.
+
+ @type name: C{str}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with the created user, or
+ with fails with L{twisted.words.ewords.DuplicateUser} if a
+ user by that name exists already.
+ """
+
+__all__ = [
+ 'IChatInterface', 'IGroup', 'IChatClient', 'IUser', 'IChatService',
+ ]
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/__init__.py b/vendor/Twisted-10.0.0/twisted/words/protocols/__init__.py
new file mode 100644
index 0000000000..5b4f7e5087
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/__init__.py
@@ -0,0 +1 @@
+"Chat protocols"
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/irc.py b/vendor/Twisted-10.0.0/twisted/words/protocols/irc.py
new file mode 100644
index 0000000000..a96da2c224
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/irc.py
@@ -0,0 +1,3166 @@
+# -*- test-case-name: twisted.words.test.test_irc -*-
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Internet Relay Chat Protocol for client and server.
+
+Future Plans
+============
+
+The way the IRCClient class works here encourages people to implement
+IRC clients by subclassing the ephemeral protocol class, and it tends
+to end up with way more state than it should for an object which will
+be destroyed as soon as the TCP transport drops. Someone oughta do
+something about that, ya know?
+
+The DCC support needs to have more hooks for the client for it to be
+able to ask the user things like "Do you want to accept this session?"
+and "Transfer #2 is 67% done." and otherwise manage the DCC sessions.
+
+Test coverage needs to be better.
+
+@author: Kevin Turner
+
+@see: RFC 1459: Internet Relay Chat Protocol
+@see: RFC 2812: Internet Relay Chat: Client Protocol
+@see: U{The Client-To-Client-Protocol
+<http://www.irchelp.org/irchelp/rfc/ctcpspec.html>}
+"""
+
+import errno, os, random, re, stat, struct, sys, time, types, traceback
+import string, socket
+import warnings
+from os import path
+
+from twisted.internet import reactor, protocol
+from twisted.persisted import styles
+from twisted.protocols import basic
+from twisted.python import log, reflect, text
+
+NUL = chr(0)
+CR = chr(015)
+NL = chr(012)
+LF = NL
+SPC = chr(040)
+
+CHANNEL_PREFIXES = '&#!+'
+
+class IRCBadMessage(Exception):
+ pass
+
+class IRCPasswordMismatch(Exception):
+ pass
+
+
+
+class IRCBadModes(ValueError):
+ """
+ A malformed mode was encountered while attempting to parse a mode string.
+ """
+
+
+
+def parsemsg(s):
+ """Breaks a message from an IRC server into its prefix, command, and arguments.
+ """
+ prefix = ''
+ trailing = []
+ if not s:
+ raise IRCBadMessage("Empty line.")
+ if s[0] == ':':
+ prefix, s = s[1:].split(' ', 1)
+ if s.find(' :') != -1:
+ s, trailing = s.split(' :', 1)
+ args = s.split()
+ args.append(trailing)
+ else:
+ args = s.split()
+ command = args.pop(0)
+ return prefix, command, args
+
+
+def split(str, length = 80):
+ """I break a message into multiple lines.
+
+ I prefer to break at whitespace near str[length]. I also break at \\n.
+
+ @returns: list of strings
+ """
+ if length <= 0:
+ raise ValueError("Length must be a number greater than zero")
+ r = []
+ while len(str) > length:
+ w, n = str[:length].rfind(' '), str[:length].find('\n')
+ if w == -1 and n == -1:
+ line, str = str[:length], str[length:]
+ else:
+ if n == -1:
+ i = w
+ else:
+ i = n
+ if i == 0: # just skip the space or newline. don't append any output.
+ str = str[1:]
+ continue
+ line, str = str[:i], str[i+1:]
+ r.append(line)
+ if len(str):
+ r.extend(str.split('\n'))
+ return r
+
+
+
+def _intOrDefault(value, default=None):
+ """
+ Convert a value to an integer if possible.
+
+ @rtype: C{int} or type of L{default}
+ @return: An integer when C{value} can be converted to an integer,
+ otherwise return C{default}
+ """
+ if value:
+ try:
+ return int(value)
+ except (TypeError, ValueError):
+ pass
+ return default
+
+
+
+class UnhandledCommand(RuntimeError):
+ """
+ A command dispatcher could not locate an appropriate command handler.
+ """
+
+
+
+class _CommandDispatcherMixin(object):
+ """
+ Dispatch commands to handlers based on their name.
+
+ Command handler names should be of the form C{prefix_commandName},
+ where C{prefix} is the value specified by L{prefix}, and must
+ accept the parameters as given to L{dispatch}.
+
+ Attempting to mix this in more than once for a single class will cause
+ strange behaviour, due to L{prefix} being overwritten.
+
+ @type prefix: C{str}
+ @ivar prefix: Command handler prefix, used to locate handler attributes
+ """
+ prefix = None
+
+ def dispatch(self, commandName, *args):
+ """
+ Perform actual command dispatch.
+ """
+ def _getMethodName(command):
+ return '%s_%s' % (self.prefix, command)
+
+ def _getMethod(name):
+ return getattr(self, _getMethodName(name), None)
+
+ method = _getMethod(commandName)
+ if method is not None:
+ return method(*args)
+
+ method = _getMethod('unknown')
+ if method is None:
+ raise UnhandledCommand("No handler for %r could be found" % (_getMethodName(commandName),))
+ return method(commandName, *args)
+
+
+
+
+
+def parseModes(modes, params, paramModes=('', '')):
+ """
+ Parse an IRC mode string.
+
+ The mode string is parsed into two lists of mode changes (added and
+ removed), with each mode change represented as C{(mode, param)} where mode
+ is the mode character, and param is the parameter passed for that mode, or
+ C{None} if no parameter is required.
+
+ @type modes: C{str}
+ @param modes: Modes string to parse.
+
+ @type params: C{list}
+ @param params: Parameters specified along with L{modes}.
+
+ @type paramModes: C{(str, str)}
+ @param paramModes: A pair of strings (C{(add, remove)}) that indicate which modes take
+ parameters when added or removed.
+
+ @returns: Two lists of mode changes, one for modes added and the other for
+ modes removed respectively, mode changes in each list are represented as
+ C{(mode, param)}.
+ """
+ if len(modes) == 0:
+ raise IRCBadModes('Empty mode string')
+
+ if modes[0] not in '+-':
+ raise IRCBadModes('Malformed modes string: %r' % (modes,))
+
+ changes = ([], [])
+
+ direction = None
+ count = -1
+ for ch in modes:
+ if ch in '+-':
+ if count == 0:
+ raise IRCBadModes('Empty mode sequence: %r' % (modes,))
+ direction = '+-'.index(ch)
+ count = 0
+ else:
+ param = None
+ if ch in paramModes[direction]:
+ try:
+ param = params.pop(0)
+ except IndexError:
+ raise IRCBadModes('Not enough parameters: %r' % (ch,))
+ changes[direction].append((ch, param))
+ count += 1
+
+ if len(params) > 0:
+ raise IRCBadModes('Too many parameters: %r %r' % (modes, params))
+
+ if count == 0:
+ raise IRCBadModes('Empty mode sequence: %r' % (modes,))
+
+ return changes
+
+
+
+class IRC(protocol.Protocol):
+ """
+ Internet Relay Chat server protocol.
+ """
+
+ buffer = ""
+ hostname = None
+
+ encoding = None
+
+ def connectionMade(self):
+ self.channels = []
+ if self.hostname is None:
+ self.hostname = socket.getfqdn()
+
+
+ def sendLine(self, line):
+ if self.encoding is not None:
+ if isinstance(line, unicode):
+ line = line.encode(self.encoding)
+ self.transport.write("%s%s%s" % (line, CR, LF))
+
+
+ def sendMessage(self, command, *parameter_list, **prefix):
+ """
+ Send a line formatted as an IRC message.
+
+ First argument is the command, all subsequent arguments are parameters
+ to that command. If a prefix is desired, it may be specified with the
+ keyword argument 'prefix'.
+ """
+
+ if not command:
+ raise ValueError, "IRC message requires a command."
+
+ if ' ' in command or command[0] == ':':
+ # Not the ONLY way to screw up, but provides a little
+ # sanity checking to catch likely dumb mistakes.
+ raise ValueError, "Somebody screwed up, 'cuz this doesn't" \
+ " look like a command to me: %s" % command
+
+ line = string.join([command] + list(parameter_list))
+ if prefix.has_key('prefix'):
+ line = ":%s %s" % (prefix['prefix'], line)
+ self.sendLine(line)
+
+ if len(parameter_list) > 15:
+ log.msg("Message has %d parameters (RFC allows 15):\n%s" %
+ (len(parameter_list), line))
+
+
+ def dataReceived(self, data):
+ """
+ This hack is to support mIRC, which sends LF only, even though the RFC
+ says CRLF. (Also, the flexibility of LineReceiver to turn "line mode"
+ on and off was not required.)
+ """
+ lines = (self.buffer + data).split(LF)
+ # Put the (possibly empty) element after the last LF back in the
+ # buffer
+ self.buffer = lines.pop()
+
+ for line in lines:
+ if len(line) <= 2:
+ # This is a blank line, at best.
+ continue
+ if line[-1] == CR:
+ line = line[:-1]
+ prefix, command, params = parsemsg(line)
+ # mIRC is a big pile of doo-doo
+ command = command.upper()
+ # DEBUG: log.msg( "%s %s %s" % (prefix, command, params))
+
+ self.handleCommand(command, prefix, params)
+
+
+ def handleCommand(self, command, prefix, params):
+ """
+ Determine the function to call for the given command and call it with
+ the given arguments.
+ """
+ method = getattr(self, "irc_%s" % command, None)
+ try:
+ if method is not None:
+ method(prefix, params)
+ else:
+ self.irc_unknown(prefix, command, params)
+ except:
+ log.deferr()
+
+
+ def irc_unknown(self, prefix, command, params):
+ """
+ Called by L{handleCommand} on a command that doesn't have a defined
+ handler. Subclasses should override this method.
+ """
+ raise NotImplementedError(command, prefix, params)
+
+
+ # Helper methods
+ def privmsg(self, sender, recip, message):
+ """
+ Send a message to a channel or user
+
+ @type sender: C{str} or C{unicode}
+ @param sender: Who is sending this message. Should be of the form
+ username!ident@hostmask (unless you know better!).
+
+ @type recip: C{str} or C{unicode}
+ @param recip: The recipient of this message. If a channel, it must
+ start with a channel prefix.
+
+ @type message: C{str} or C{unicode}
+ @param message: The message being sent.
+ """
+ self.sendLine(":%s PRIVMSG %s :%s" % (sender, recip, lowQuote(message)))
+
+
+ def notice(self, sender, recip, message):
+ """
+ Send a "notice" to a channel or user.
+
+ Notices differ from privmsgs in that the RFC claims they are different.
+ Robots are supposed to send notices and not respond to them. Clients
+ typically display notices differently from privmsgs.
+
+ @type sender: C{str} or C{unicode}
+ @param sender: Who is sending this message. Should be of the form
+ username!ident@hostmask (unless you know better!).
+
+ @type recip: C{str} or C{unicode}
+ @param recip: The recipient of this message. If a channel, it must
+ start with a channel prefix.
+
+ @type message: C{str} or C{unicode}
+ @param message: The message being sent.
+ """
+ self.sendLine(":%s NOTICE %s :%s" % (sender, recip, message))
+
+
+ def action(self, sender, recip, message):
+ """
+ Send an action to a channel or user.
+
+ @type sender: C{str} or C{unicode}
+ @param sender: Who is sending this message. Should be of the form
+ username!ident@hostmask (unless you know better!).
+
+ @type recip: C{str} or C{unicode}
+ @param recip: The recipient of this message. If a channel, it must
+ start with a channel prefix.
+
+ @type message: C{str} or C{unicode}
+ @param message: The action being sent.
+ """
+ self.sendLine(":%s ACTION %s :%s" % (sender, recip, message))
+
+
+ def topic(self, user, channel, topic, author=None):
+ """
+ Send the topic to a user.
+
+ @type user: C{str} or C{unicode}
+ @param user: The user receiving the topic. Only their nick name, not
+ the full hostmask.
+
+ @type channel: C{str} or C{unicode}
+ @param channel: The channel for which this is the topic.
+
+ @type topic: C{str} or C{unicode} or C{None}
+ @param topic: The topic string, unquoted, or None if there is no topic.
+
+ @type author: C{str} or C{unicode}
+ @param author: If the topic is being changed, the full username and
+ hostmask of the person changing it.
+ """
+ if author is None:
+ if topic is None:
+ self.sendLine(':%s %s %s %s :%s' % (
+ self.hostname, RPL_NOTOPIC, user, channel, 'No topic is set.'))
+ else:
+ self.sendLine(":%s %s %s %s :%s" % (
+ self.hostname, RPL_TOPIC, user, channel, lowQuote(topic)))
+ else:
+ self.sendLine(":%s TOPIC %s :%s" % (author, channel, lowQuote(topic)))
+
+
+ def topicAuthor(self, user, channel, author, date):
+ """
+ Send the author of and time at which a topic was set for the given
+ channel.
+
+ This sends a 333 reply message, which is not part of the IRC RFC.
+
+ @type user: C{str} or C{unicode}
+ @param user: The user receiving the topic. Only their nick name, not
+ the full hostmask.
+
+ @type channel: C{str} or C{unicode}
+ @param channel: The channel for which this information is relevant.
+
+ @type author: C{str} or C{unicode}
+ @param author: The nickname (without hostmask) of the user who last set
+ the topic.
+
+ @type date: C{int}
+ @param date: A POSIX timestamp (number of seconds since the epoch) at
+ which the topic was last set.
+ """
+ self.sendLine(':%s %d %s %s %s %d' % (
+ self.hostname, 333, user, channel, author, date))
+
+
+ def names(self, user, channel, names):
+ """
+ Send the names of a channel's participants to a user.
+
+ @type user: C{str} or C{unicode}
+ @param user: The user receiving the name list. Only their nick name,
+ not the full hostmask.
+
+ @type channel: C{str} or C{unicode}
+ @param channel: The channel for which this is the namelist.
+
+ @type names: C{list} of C{str} or C{unicode}
+ @param names: The names to send.
+ """
+ # XXX If unicode is given, these limits are not quite correct
+ prefixLength = len(channel) + len(user) + 10
+ namesLength = 512 - prefixLength
+
+ L = []
+ count = 0
+ for n in names:
+ if count + len(n) + 1 > namesLength:
+ self.sendLine(":%s %s %s = %s :%s" % (
+ self.hostname, RPL_NAMREPLY, user, channel, ' '.join(L)))
+ L = [n]
+ count = len(n)
+ else:
+ L.append(n)
+ count += len(n) + 1
+ if L:
+ self.sendLine(":%s %s %s = %s :%s" % (
+ self.hostname, RPL_NAMREPLY, user, channel, ' '.join(L)))
+ self.sendLine(":%s %s %s %s :End of /NAMES list" % (
+ self.hostname, RPL_ENDOFNAMES, user, channel))
+
+
+ def who(self, user, channel, memberInfo):
+ """
+ Send a list of users participating in a channel.
+
+ @type user: C{str} or C{unicode}
+ @param user: The user receiving this member information. Only their
+ nick name, not the full hostmask.
+
+ @type channel: C{str} or C{unicode}
+ @param channel: The channel for which this is the member information.
+
+ @type memberInfo: C{list} of C{tuples}
+ @param memberInfo: For each member of the given channel, a 7-tuple
+ containing their username, their hostmask, the server to which they
+ are connected, their nickname, the letter "H" or "G" (standing for
+ "Here" or "Gone"), the hopcount from C{user} to this member, and
+ this member's real name.
+ """
+ for info in memberInfo:
+ (username, hostmask, server, nickname, flag, hops, realName) = info
+ assert flag in ("H", "G")
+ self.sendLine(":%s %s %s %s %s %s %s %s %s :%d %s" % (
+ self.hostname, RPL_WHOREPLY, user, channel,
+ username, hostmask, server, nickname, flag, hops, realName))
+
+ self.sendLine(":%s %s %s %s :End of /WHO list." % (
+ self.hostname, RPL_ENDOFWHO, user, channel))
+
+
+ def whois(self, user, nick, username, hostname, realName, server, serverInfo, oper, idle, signOn, channels):
+ """
+ Send information about the state of a particular user.
+
+ @type user: C{str} or C{unicode}
+ @param user: The user receiving this information. Only their nick name,
+ not the full hostmask.
+
+ @type nick: C{str} or C{unicode}
+ @param nick: The nickname of the user this information describes.
+
+ @type username: C{str} or C{unicode}
+ @param username: The user's username (eg, ident response)
+
+ @type hostname: C{str}
+ @param hostname: The user's hostmask
+
+ @type realName: C{str} or C{unicode}
+ @param realName: The user's real name
+
+ @type server: C{str} or C{unicode}
+ @param server: The name of the server to which the user is connected
+
+ @type serverInfo: C{str} or C{unicode}
+ @param serverInfo: A descriptive string about that server
+
+ @type oper: C{bool}
+ @param oper: Indicates whether the user is an IRC operator
+
+ @type idle: C{int}
+ @param idle: The number of seconds since the user last sent a message
+
+ @type signOn: C{int}
+ @param signOn: A POSIX timestamp (number of seconds since the epoch)
+ indicating the time the user signed on
+
+ @type channels: C{list} of C{str} or C{unicode}
+ @param channels: A list of the channels which the user is participating in
+ """
+ self.sendLine(":%s %s %s %s %s %s * :%s" % (
+ self.hostname, RPL_WHOISUSER, user, nick, username, hostname, realName))
+ self.sendLine(":%s %s %s %s %s :%s" % (
+ self.hostname, RPL_WHOISSERVER, user, nick, server, serverInfo))
+ if oper:
+ self.sendLine(":%s %s %s %s :is an IRC operator" % (
+ self.hostname, RPL_WHOISOPERATOR, user, nick))
+ self.sendLine(":%s %s %s %s %d %d :seconds idle, signon time" % (
+ self.hostname, RPL_WHOISIDLE, user, nick, idle, signOn))
+ self.sendLine(":%s %s %s %s :%s" % (
+ self.hostname, RPL_WHOISCHANNELS, user, nick, ' '.join(channels)))
+ self.sendLine(":%s %s %s %s :End of WHOIS list." % (
+ self.hostname, RPL_ENDOFWHOIS, user, nick))
+
+
+ def join(self, who, where):
+ """
+ Send a join message.
+
+ @type who: C{str} or C{unicode}
+ @param who: The name of the user joining. Should be of the form
+ username!ident@hostmask (unless you know better!).
+
+ @type where: C{str} or C{unicode}
+ @param where: The channel the user is joining.
+ """
+ self.sendLine(":%s JOIN %s" % (who, where))
+
+
+ def part(self, who, where, reason=None):
+ """
+ Send a part message.
+
+ @type who: C{str} or C{unicode}
+ @param who: The name of the user joining. Should be of the form
+ username!ident@hostmask (unless you know better!).
+
+ @type where: C{str} or C{unicode}
+ @param where: The channel the user is joining.
+
+ @type reason: C{str} or C{unicode}
+ @param reason: A string describing the misery which caused this poor
+ soul to depart.
+ """
+ if reason:
+ self.sendLine(":%s PART %s :%s" % (who, where, reason))
+ else:
+ self.sendLine(":%s PART %s" % (who, where))
+
+
+ def channelMode(self, user, channel, mode, *args):
+ """
+ Send information about the mode of a channel.
+
+ @type user: C{str} or C{unicode}
+ @param user: The user receiving the name list. Only their nick name,
+ not the full hostmask.
+
+ @type channel: C{str} or C{unicode}
+ @param channel: The channel for which this is the namelist.
+
+ @type mode: C{str}
+ @param mode: A string describing this channel's modes.
+
+ @param args: Any additional arguments required by the modes.
+ """
+ self.sendLine(":%s %s %s %s %s %s" % (
+ self.hostname, RPL_CHANNELMODEIS, user, channel, mode, ' '.join(args)))
+
+
+
+class ServerSupportedFeatures(_CommandDispatcherMixin):
+ """
+ Handle ISUPPORT messages.
+
+ Feature names match those in the ISUPPORT RFC draft identically.
+
+ Information regarding the specifics of ISUPPORT was gleaned from
+ <http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt>.
+ """
+ prefix = 'isupport'
+
+ def __init__(self):
+ self._features = {
+ 'CHANNELLEN': 200,
+ 'CHANTYPES': tuple('#&'),
+ 'MODES': 3,
+ 'NICKLEN': 9,
+ 'PREFIX': self._parsePrefixParam('(ovh)@+%'),
+ # The ISUPPORT draft explicitly says that there is no default for
+ # CHANMODES, but we're defaulting it here to handle the case where
+ # the IRC server doesn't send us any ISUPPORT information, since
+ # IRCClient.getChannelModeParams relies on this value.
+ 'CHANMODES': self._parseChanModesParam(['b', '', 'lk'])}
+
+
+ def _splitParamArgs(cls, params, valueProcessor=None):
+ """
+ Split ISUPPORT parameter arguments.
+
+ Values can optionally be processed by C{valueProcessor}.
+
+ For example::
+
+ >>> ServerSupportedFeatures._splitParamArgs(['A:1', 'B:2'])
+ (('A', '1'), ('B', '2'))
+
+ @type params: C{iterable} of C{str}
+
+ @type valueProcessor: C{callable} taking {str}
+ @param valueProcessor: Callable to process argument values, or C{None}
+ to perform no processing
+
+ @rtype: C{list} of C{(str, object)}
+ @return: Sequence of C{(name, processedValue)}
+ """
+ if valueProcessor is None:
+ valueProcessor = lambda x: x
+
+ def _parse():
+ for param in params:
+ if ':' not in param:
+ param += ':'
+ a, b = param.split(':', 1)
+ yield a, valueProcessor(b)
+ return list(_parse())
+ _splitParamArgs = classmethod(_splitParamArgs)
+
+
+ def _unescapeParamValue(cls, value):
+ """
+ Unescape an ISUPPORT parameter.
+
+ The only form of supported escape is C{\\xHH}, where HH must be a valid
+ 2-digit hexadecimal number.
+
+ @rtype: C{str}
+ """
+ def _unescape():
+ parts = value.split('\\x')
+ # The first part can never be preceeded by the escape.
+ yield parts.pop(0)
+ for s in parts:
+ octet, rest = s[:2], s[2:]
+ try:
+ octet = int(octet, 16)
+ except ValueError:
+ raise ValueError('Invalid hex octet: %r' % (octet,))
+ yield chr(octet) + rest
+
+ if '\\x' not in value:
+ return value
+ return ''.join(_unescape())
+ _unescapeParamValue = classmethod(_unescapeParamValue)
+
+
+ def _splitParam(cls, param):
+ """
+ Split an ISUPPORT parameter.
+
+ @type param: C{str}
+
+ @rtype: C{(str, list)}
+ @return C{(key, arguments)}
+ """
+ if '=' not in param:
+ param += '='
+ key, value = param.split('=', 1)
+ return key, map(cls._unescapeParamValue, value.split(','))
+ _splitParam = classmethod(_splitParam)
+
+
+ def _parsePrefixParam(cls, prefix):
+ """
+ Parse the ISUPPORT "PREFIX" parameter.
+
+ The order in which the parameter arguments appear is significant, the
+ earlier a mode appears the more privileges it gives.
+
+ @rtype: C{dict} mapping C{str} to C{(str, int)}
+ @return: A dictionary mapping a mode character to a two-tuple of
+ C({symbol, priority)}, the lower a priority (the lowest being
+ C{0}) the more privileges it gives
+ """
+ if not prefix:
+ return None
+ if prefix[0] != '(' and ')' not in prefix:
+ raise ValueError('Malformed PREFIX parameter')
+ modes, symbols = prefix.split(')', 1)
+ symbols = zip(symbols, xrange(len(symbols)))
+ modes = modes[1:]
+ return dict(zip(modes, symbols))
+ _parsePrefixParam = classmethod(_parsePrefixParam)
+
+
+ def _parseChanModesParam(self, params):
+ """
+ Parse the ISUPPORT "CHANMODES" parameter.
+
+ See L{isupport_CHANMODES} for a detailed explanation of this parameter.
+ """
+ names = ('addressModes', 'param', 'setParam', 'noParam')
+ if len(params) > len(names):
+ raise ValueError(
+ 'Expecting a maximum of %d channel mode parameters, got %d' % (
+ len(names), len(params)))
+ items = map(lambda key, value: (key, value or ''), names, params)
+ return dict(items)
+ _parseChanModesParam = classmethod(_parseChanModesParam)
+
+
+ def getFeature(self, feature, default=None):
+ """
+ Get a server supported feature's value.
+
+ A feature with the value C{None} is equivalent to the feature being
+ unsupported.
+
+ @type feature: C{str}
+ @param feature: Feature name
+
+ @type default: C{object}
+ @param default: The value to default to, assuming that C{feature}
+ is not supported
+
+ @return: Feature value
+ """
+ return self._features.get(feature, default)
+
+
+ def hasFeature(self, feature):
+ """
+ Determine whether a feature is supported or not.
+
+ @rtype: C{bool}
+ """
+ return self.getFeature(feature) is not None
+
+
+ def parse(self, params):
+ """
+ Parse ISUPPORT parameters.
+
+ If an unknown parameter is encountered, it is simply added to the
+ dictionary, keyed by its name, as a tuple of the parameters provided.
+
+ @type params: C{iterable} of C{str}
+ @param params: Iterable of ISUPPORT parameters to parse
+ """
+ for param in params:
+ key, value = self._splitParam(param)
+ if key.startswith('-'):
+ self._features.pop(key[1:], None)
+ else:
+ self._features[key] = self.dispatch(key, value)
+
+
+ def isupport_unknown(self, command, params):
+ """
+ Unknown ISUPPORT parameter.
+ """
+ return tuple(params)
+
+
+ def isupport_CHANLIMIT(self, params):
+ """
+ The maximum number of each channel type a user may join.
+ """
+ return self._splitParamArgs(params, _intOrDefault)
+
+
+ def isupport_CHANMODES(self, params):
+ """
+ Available channel modes.
+
+ There are 4 categories of channel mode::
+
+ addressModes - Modes that add or remove an address to or from a
+ list, these modes always take a parameter.
+
+ param - Modes that change a setting on a channel, these modes
+ always take a parameter.
+
+ setParam - Modes that change a setting on a channel, these modes
+ only take a parameter when being set.
+
+ noParam - Modes that change a setting on a channel, these modes
+ never take a parameter.
+ """
+ try:
+ return self._parseChanModesParam(params)
+ except ValueError:
+ return self.getFeature('CHANMODES')
+
+
+ def isupport_CHANNELLEN(self, params):
+ """
+ Maximum length of a channel name a client may create.
+ """
+ return _intOrDefault(params[0], self.getFeature('CHANNELLEN'))
+
+
+ def isupport_CHANTYPES(self, params):
+ """
+ Valid channel prefixes.
+ """
+ return tuple(params[0])
+
+
+ def isupport_EXCEPTS(self, params):
+ """
+ Mode character for "ban exceptions".
+
+ The presence of this parameter indicates that the server supports
+ this functionality.
+ """
+ return params[0] or 'e'
+
+
+ def isupport_IDCHAN(self, params):
+ """
+ Safe channel identifiers.
+
+ The presence of this parameter indicates that the server supports
+ this functionality.
+ """
+ return self._splitParamArgs(params)
+
+
+ def isupport_INVEX(self, params):
+ """
+ Mode character for "invite exceptions".
+
+ The presence of this parameter indicates that the server supports
+ this functionality.
+ """
+ return params[0] or 'I'
+
+
+ def isupport_KICKLEN(self, params):
+ """
+ Maximum length of a kick message a client may provide.
+ """
+ return _intOrDefault(params[0])
+
+
+ def isupport_MAXLIST(self, params):
+ """
+ Maximum number of "list modes" a client may set on a channel at once.
+
+ List modes are identified by the "addressModes" key in CHANMODES.
+ """
+ return self._splitParamArgs(params, _intOrDefault)
+
+
+ def isupport_MODES(self, params):
+ """
+ Maximum number of modes accepting parameters that may be sent, by a
+ client, in a single MODE command.
+ """
+ return _intOrDefault(params[0])
+
+
+ def isupport_NETWORK(self, params):
+ """
+ IRC network name.
+ """
+ return params[0]
+
+
+ def isupport_NICKLEN(self, params):
+ """
+ Maximum length of a nickname the client may use.
+ """
+ return _intOrDefault(params[0], self.getFeature('NICKLEN'))
+
+
+ def isupport_PREFIX(self, params):
+ """
+ Mapping of channel modes that clients may have to status flags.
+ """
+ try:
+ return self._parsePrefixParam(params[0])
+ except ValueError:
+ return self.getFeature('PREFIX')
+
+
+ def isupport_SAFELIST(self, params):
+ """
+ Flag indicating that a client may request a LIST without being
+ disconnected due to the large amount of data generated.
+ """
+ return True
+
+
+ def isupport_STATUSMSG(self, params):
+ """
+ The server supports sending messages to only to clients on a channel
+ with a specific status.
+ """
+ return params[0]
+
+
+ def isupport_TARGMAX(self, params):
+ """
+ Maximum number of targets allowable for commands that accept multiple
+ targets.
+ """
+ return dict(self._splitParamArgs(params, _intOrDefault))
+
+
+ def isupport_TOPICLEN(self, params):
+ """
+ Maximum length of a topic that may be set.
+ """
+ return _intOrDefault(params[0])
+
+
+
+class IRCClient(basic.LineReceiver):
+ """Internet Relay Chat client protocol, with sprinkles.
+
+ In addition to providing an interface for an IRC client protocol,
+ this class also contains reasonable implementations of many common
+ CTCP methods.
+
+ TODO
+ ====
+ - Limit the length of messages sent (because the IRC server probably
+ does).
+ - Add flood protection/rate limiting for my CTCP replies.
+ - NickServ cooperation. (a mix-in?)
+ - Heartbeat. The transport may die in such a way that it does not realize
+ it is dead until it is written to. Sending something (like "PING
+ this.irc-host.net") during idle peroids would alleviate that. If
+ you're concerned with the stability of the host as well as that of the
+ transport, you might care to watch for the corresponding PONG.
+
+ @ivar nickname: Nickname the client will use.
+ @ivar password: Password used to log on to the server. May be C{None}.
+ @ivar realname: Supplied to the server during login as the "Real name"
+ or "ircname". May be C{None}.
+ @ivar username: Supplied to the server during login as the "User name".
+ May be C{None}
+
+ @ivar userinfo: Sent in reply to a C{USERINFO} CTCP query. If C{None}, no
+ USERINFO reply will be sent.
+ "This is used to transmit a string which is settable by
+ the user (and never should be set by the client)."
+ @ivar fingerReply: Sent in reply to a C{FINGER} CTCP query. If C{None}, no
+ FINGER reply will be sent.
+ @type fingerReply: Callable or String
+
+ @ivar versionName: CTCP VERSION reply, client name. If C{None}, no VERSION
+ reply will be sent.
+ @type versionName: C{str}, or None.
+ @ivar versionNum: CTCP VERSION reply, client version.
+ @type versionNum: C{str}, or None.
+ @ivar versionEnv: CTCP VERSION reply, environment the client is running in.
+ @type versionEnv: C{str}, or None.
+
+ @ivar sourceURL: CTCP SOURCE reply, a URL where the source code of this
+ client may be found. If C{None}, no SOURCE reply will be sent.
+
+ @ivar lineRate: Minimum delay between lines sent to the server. If
+ C{None}, no delay will be imposed.
+ @type lineRate: Number of Seconds.
+
+ @ivar motd: Either L{None} or, between receipt of I{RPL_MOTDSTART} and
+ I{RPL_ENDOFMOTD}, a L{list} of L{str}, each of which is the content
+ of an I{RPL_MOTD} message.
+
+ @ivar erroneousNickFallback: Default nickname assigned when an unregistered
+ client triggers an C{ERR_ERRONEUSNICKNAME} while trying to register
+ with an illegal nickname.
+ @type erroneousNickFallback: C{str}
+
+ @ivar _registered: Whether or not the user is registered. It becomes True
+ once a welcome has been received from the server.
+ @type _registered: C{bool}
+
+ @ivar _attemptedNick: The nickname that will try to get registered. It may
+ change if it is illegal or already taken. L{nickname} becomes the
+ L{_attemptedNick} that is successfully registered.
+ @type _attemptedNick: C{str}
+
+ @type supported: L{ServerSupportedFeatures}
+ @ivar supported: Available ISUPPORT features on the server
+ """
+ motd = None
+ nickname = 'irc'
+ password = None
+ realname = None
+ username = None
+ ### Responses to various CTCP queries.
+
+ userinfo = None
+ # fingerReply is a callable returning a string, or a str()able object.
+ fingerReply = None
+ versionName = None
+ versionNum = None
+ versionEnv = None
+
+ sourceURL = "http://twistedmatrix.com/downloads/"
+
+ dcc_destdir = '.'
+ dcc_sessions = None
+
+ # If this is false, no attempt will be made to identify
+ # ourself to the server.
+ performLogin = 1
+
+ lineRate = None
+ _queue = None
+ _queueEmptying = None
+
+ delimiter = '\n' # '\r\n' will also work (see dataReceived)
+
+ __pychecker__ = 'unusednames=params,prefix,channel'
+
+ _registered = False
+ _attemptedNick = ''
+ erroneousNickFallback = 'defaultnick'
+
+ def _reallySendLine(self, line):
+ return basic.LineReceiver.sendLine(self, lowQuote(line) + '\r')
+
+ def sendLine(self, line):
+ if self.lineRate is None:
+ self._reallySendLine(line)
+ else:
+ self._queue.append(line)
+ if not self._queueEmptying:
+ self._sendLine()
+
+ def _sendLine(self):
+ if self._queue:
+ self._reallySendLine(self._queue.pop(0))
+ self._queueEmptying = reactor.callLater(self.lineRate,
+ self._sendLine)
+ else:
+ self._queueEmptying = None
+
+
+ ### Interface level client->user output methods
+ ###
+ ### You'll want to override these.
+
+ ### Methods relating to the server itself
+
+ def created(self, when):
+ """Called with creation date information about the server, usually at logon.
+
+ @type when: C{str}
+ @param when: A string describing when the server was created, probably.
+ """
+
+ def yourHost(self, info):
+ """Called with daemon information about the server, usually at logon.
+
+ @type info: C{str}
+ @param when: A string describing what software the server is running, probably.
+ """
+
+ def myInfo(self, servername, version, umodes, cmodes):
+ """Called with information about the server, usually at logon.
+
+ @type servername: C{str}
+ @param servername: The hostname of this server.
+
+ @type version: C{str}
+ @param version: A description of what software this server runs.
+
+ @type umodes: C{str}
+ @param umodes: All the available user modes.
+
+ @type cmodes: C{str}
+ @param cmodes: All the available channel modes.
+ """
+
+ def luserClient(self, info):
+ """Called with information about the number of connections, usually at logon.
+
+ @type info: C{str}
+ @param info: A description of the number of clients and servers
+ connected to the network, probably.
+ """
+
+ def bounce(self, info):
+ """Called with information about where the client should reconnect.
+
+ @type info: C{str}
+ @param info: A plaintext description of the address that should be
+ connected to.
+ """
+
+ def isupport(self, options):
+ """Called with various information about what the server supports.
+
+ @type options: C{list} of C{str}
+ @param options: Descriptions of features or limits of the server, possibly
+ in the form "NAME=VALUE".
+ """
+
+ def luserChannels(self, channels):
+ """Called with the number of channels existant on the server.
+
+ @type channels: C{int}
+ """
+
+ def luserOp(self, ops):
+ """Called with the number of ops logged on to the server.
+
+ @type ops: C{int}
+ """
+
+ def luserMe(self, info):
+ """Called with information about the server connected to.
+
+ @type info: C{str}
+ @param info: A plaintext string describing the number of users and servers
+ connected to this server.
+ """
+
+ ### Methods involving me directly
+
+ def privmsg(self, user, channel, message):
+ """Called when I have a message from a user to me or a channel.
+ """
+ pass
+
+ def joined(self, channel):
+ """
+ Called when I finish joining a channel.
+
+ channel has the starting character (C{'#'}, C{'&'}, C{'!'}, or C{'+'})
+ intact.
+ """
+
+ def left(self, channel):
+ """
+ Called when I have left a channel.
+
+ channel has the starting character (C{'#'}, C{'&'}, C{'!'}, or C{'+'})
+ intact.
+ """
+
+ def noticed(self, user, channel, message):
+ """Called when I have a notice from a user to me or a channel.
+
+ By default, this is equivalent to IRCClient.privmsg, but if your
+ client makes any automated replies, you must override this!
+ From the RFC::
+
+ The difference between NOTICE and PRIVMSG is that
+ automatic replies MUST NEVER be sent in response to a
+ NOTICE message. [...] The object of this rule is to avoid
+ loops between clients automatically sending something in
+ response to something it received.
+ """
+ self.privmsg(user, channel, message)
+
+ def modeChanged(self, user, channel, set, modes, args):
+ """Called when users or channel's modes are changed.
+
+ @type user: C{str}
+ @param user: The user and hostmask which instigated this change.
+
+ @type channel: C{str}
+ @param channel: The channel where the modes are changed. If args is
+ empty the channel for which the modes are changing. If the changes are
+ at server level it could be equal to C{user}.
+
+ @type set: C{bool} or C{int}
+ @param set: True if the mode(s) is being added, False if it is being
+ removed. If some modes are added and others removed at the same time
+ this function will be called twice, the first time with all the added
+ modes, the second with the removed ones. (To change this behaviour
+ override the irc_MODE method)
+
+ @type modes: C{str}
+ @param modes: The mode or modes which are being changed.
+
+ @type args: C{tuple}
+ @param args: Any additional information required for the mode
+ change.
+ """
+
+ def pong(self, user, secs):
+ """Called with the results of a CTCP PING query.
+ """
+ pass
+
+ def signedOn(self):
+ """Called after sucessfully signing on to the server.
+ """
+ pass
+
+ def kickedFrom(self, channel, kicker, message):
+ """Called when I am kicked from a channel.
+ """
+ pass
+
+ def nickChanged(self, nick):
+ """Called when my nick has been changed.
+ """
+ self.nickname = nick
+
+
+ ### Things I observe other people doing in a channel.
+
+ def userJoined(self, user, channel):
+ """Called when I see another user joining a channel.
+ """
+ pass
+
+ def userLeft(self, user, channel):
+ """Called when I see another user leaving a channel.
+ """
+ pass
+
+ def userQuit(self, user, quitMessage):
+ """Called when I see another user disconnect from the network.
+ """
+ pass
+
+ def userKicked(self, kickee, channel, kicker, message):
+ """Called when I observe someone else being kicked from a channel.
+ """
+ pass
+
+ def action(self, user, channel, data):
+ """Called when I see a user perform an ACTION on a channel.
+ """
+ pass
+
+ def topicUpdated(self, user, channel, newTopic):
+ """In channel, user changed the topic to newTopic.
+
+ Also called when first joining a channel.
+ """
+ pass
+
+ def userRenamed(self, oldname, newname):
+ """A user changed their name from oldname to newname.
+ """
+ pass
+
+ ### Information from the server.
+
+ def receivedMOTD(self, motd):
+ """I received a message-of-the-day banner from the server.
+
+ motd is a list of strings, where each string was sent as a seperate
+ message from the server. To display, you might want to use::
+
+ '\\n'.join(motd)
+
+ to get a nicely formatted string.
+ """
+ pass
+
+ ### user input commands, client->server
+ ### Your client will want to invoke these.
+
+ def join(self, channel, key=None):
+ """
+ Join a channel.
+
+ @type channel: C{str}
+ @param channel: The name of the channel to join. If it has no prefix,
+ C{'#'} will be prepended to it.
+ @type key: C{str}
+ @param key: If specified, the key used to join the channel.
+ """
+ if channel[0] not in CHANNEL_PREFIXES:
+ channel = '#' + channel
+ if key:
+ self.sendLine("JOIN %s %s" % (channel, key))
+ else:
+ self.sendLine("JOIN %s" % (channel,))
+
+ def leave(self, channel, reason=None):
+ """
+ Leave a channel.
+
+ @type channel: C{str}
+ @param channel: The name of the channel to leave. If it has no prefix,
+ C{'#'} will be prepended to it.
+ @type reason: C{str}
+ @param reason: If given, the reason for leaving.
+ """
+ if channel[0] not in CHANNEL_PREFIXES:
+ channel = '#' + channel
+ if reason:
+ self.sendLine("PART %s :%s" % (channel, reason))
+ else:
+ self.sendLine("PART %s" % (channel,))
+
+ def kick(self, channel, user, reason=None):
+ """
+ Attempt to kick a user from a channel.
+
+ @type channel: C{str}
+ @param channel: The name of the channel to kick the user from. If it has
+ no prefix, C{'#'} will be prepended to it.
+ @type user: C{str}
+ @param user: The nick of the user to kick.
+ @type reason: C{str}
+ @param reason: If given, the reason for kicking the user.
+ """
+ if channel[0] not in CHANNEL_PREFIXES:
+ channel = '#' + channel
+ if reason:
+ self.sendLine("KICK %s %s :%s" % (channel, user, reason))
+ else:
+ self.sendLine("KICK %s %s" % (channel, user))
+
+ part = leave
+
+ def topic(self, channel, topic=None):
+ """
+ Attempt to set the topic of the given channel, or ask what it is.
+
+ If topic is None, then I sent a topic query instead of trying to set the
+ topic. The server should respond with a TOPIC message containing the
+ current topic of the given channel.
+
+ @type channel: C{str}
+ @param channel: The name of the channel to change the topic on. If it
+ has no prefix, C{'#'} will be prepended to it.
+ @type topic: C{str}
+ @param topic: If specified, what to set the topic to.
+ """
+ # << TOPIC #xtestx :fff
+ if channel[0] not in CHANNEL_PREFIXES:
+ channel = '#' + channel
+ if topic != None:
+ self.sendLine("TOPIC %s :%s" % (channel, topic))
+ else:
+ self.sendLine("TOPIC %s" % (channel,))
+
+ def mode(self, chan, set, modes, limit = None, user = None, mask = None):
+ """
+ Change the modes on a user or channel.
+
+ The C{limit}, C{user}, and C{mask} parameters are mutually exclusive.
+
+ @type chan: C{str}
+ @param chan: The name of the channel to operate on.
+ @type set: C{bool}
+ @param set: True to give the user or channel permissions and False to
+ remove them.
+ @type modes: C{str}
+ @param modes: The mode flags to set on the user or channel.
+ @type limit: C{int}
+ @param limit: In conjuction with the C{'l'} mode flag, limits the
+ number of users on the channel.
+ @type user: C{str}
+ @param user: The user to change the mode on.
+ @type mask: C{str}
+ @param mask: In conjuction with the C{'b'} mode flag, sets a mask of
+ users to be banned from the channel.
+ """
+ if set:
+ line = 'MODE %s +%s' % (chan, modes)
+ else:
+ line = 'MODE %s -%s' % (chan, modes)
+ if limit is not None:
+ line = '%s %d' % (line, limit)
+ elif user is not None:
+ line = '%s %s' % (line, user)
+ elif mask is not None:
+ line = '%s %s' % (line, mask)
+ self.sendLine(line)
+
+
+ def say(self, channel, message, length = None):
+ """
+ Send a message to a channel
+
+ @type channel: C{str}
+ @param channel: The channel to say the message on. If it has no prefix,
+ C{'#'} will be prepended to it.
+ @type message: C{str}
+ @param message: The message to say.
+ @type length: C{int}
+ @param length: The maximum number of octets to send at a time. This has
+ the effect of turning a single call to C{msg()} into multiple
+ commands to the server. This is useful when long messages may be
+ sent that would otherwise cause the server to kick us off or
+ silently truncate the text we are sending. If None is passed, the
+ entire message is always send in one command.
+ """
+ if channel[0] not in CHANNEL_PREFIXES:
+ channel = '#' + channel
+ self.msg(channel, message, length)
+
+
+ def msg(self, user, message, length = None):
+ """Send a message to a user or channel.
+
+ @type user: C{str}
+ @param user: The username or channel name to which to direct the
+ message.
+
+ @type message: C{str}
+ @param message: The text to send
+
+ @type length: C{int}
+ @param length: The maximum number of octets to send at a time. This
+ has the effect of turning a single call to msg() into multiple
+ commands to the server. This is useful when long messages may be
+ sent that would otherwise cause the server to kick us off or silently
+ truncate the text we are sending. If None is passed, the entire
+ message is always send in one command.
+ """
+
+ fmt = "PRIVMSG %s :%%s" % (user,)
+
+ if length is None:
+ self.sendLine(fmt % (message,))
+ else:
+ # NOTE: minimumLength really equals len(fmt) - 2 (for '%s') + n
+ # where n is how many bytes sendLine sends to end the line.
+ # n was magic numbered to 2, I think incorrectly
+ minimumLength = len(fmt)
+ if length <= minimumLength:
+ raise ValueError("Maximum length must exceed %d for message "
+ "to %s" % (minimumLength, user))
+ lines = split(message, length - minimumLength)
+ map(lambda line, self=self, fmt=fmt: self.sendLine(fmt % line),
+ lines)
+
+ def notice(self, user, message):
+ """
+ Send a notice to a user.
+
+ Notices are like normal message, but should never get automated
+ replies.
+
+ @type user: C{str}
+ @param user: The user to send a notice to.
+ @type message: C{str}
+ @param message: The contents of the notice to send.
+ """
+ self.sendLine("NOTICE %s :%s" % (user, message))
+
+ def away(self, message=''):
+ """
+ Mark this client as away.
+
+ @type message: C{str}
+ @param message: If specified, the away message.
+ """
+ self.sendLine("AWAY :%s" % message)
+
+
+
+ def back(self):
+ """
+ Clear the away status.
+ """
+ # An empty away marks us as back
+ self.away()
+
+
+ def whois(self, nickname, server=None):
+ """
+ Retrieve user information about the given nick name.
+
+ @type nickname: C{str}
+ @param nickname: The nick name about which to retrieve information.
+
+ @since: 8.2
+ """
+ if server is None:
+ self.sendLine('WHOIS ' + nickname)
+ else:
+ self.sendLine('WHOIS %s %s' % (server, nickname))
+
+
+ def register(self, nickname, hostname='foo', servername='bar'):
+ """
+ Login to the server.
+
+ @type nickname: C{str}
+ @param nickname: The nickname to register.
+ @type hostname: C{str}
+ @param hostname: If specified, the hostname to logon as.
+ @type servername: C{str}
+ @param servername: If specified, the servername to logon as.
+ """
+ if self.password is not None:
+ self.sendLine("PASS %s" % self.password)
+ self.setNick(nickname)
+ if self.username is None:
+ self.username = nickname
+ self.sendLine("USER %s %s %s :%s" % (self.username, hostname, servername, self.realname))
+
+ def setNick(self, nickname):
+ """
+ Set this client's nickname.
+
+ @type nickname: C{str}
+ @param nickname: The nickname to change to.
+ """
+ self._attemptedNick = nickname
+ self.sendLine("NICK %s" % nickname)
+
+ def quit(self, message = ''):
+ """
+ Disconnect from the server
+
+ @type message: C{str}
+
+ @param message: If specified, the message to give when quitting the
+ server.
+ """
+ self.sendLine("QUIT :%s" % message)
+
+ ### user input commands, client->client
+
+ def describe(self, channel, action):
+ """
+ Strike a pose.
+
+ @type channel: C{str}
+ @param channel: The name of the channel to have an action on. If it
+ has no prefix, it is sent to the user of that name.
+ @type action: C{str}
+ @param action: The action to preform.
+ @since: 9.0
+ """
+ self.ctcpMakeQuery(channel, [('ACTION', action)])
+
+
+ def me(self, channel, action):
+ """
+ Strike a pose.
+
+ This function is deprecated since Twisted 9.0. Use describe().
+
+ @type channel: C{str}
+ @param channel: The name of the channel to have an action on. If it
+ has no prefix, C{'#'} will be prepended to it.
+ @type action: C{str}
+ @param action: The action to preform.
+ """
+ warnings.warn("me() is deprecated since Twisted 9.0. Use IRCClient.describe().",
+ DeprecationWarning, stacklevel=2)
+
+ if channel[0] not in CHANNEL_PREFIXES:
+ channel = '#' + channel
+ self.describe(channel, action)
+
+
+ _pings = None
+ _MAX_PINGRING = 12
+
+ def ping(self, user, text = None):
+ """
+ Measure round-trip delay to another IRC client.
+ """
+ if self._pings is None:
+ self._pings = {}
+
+ if text is None:
+ chars = string.letters + string.digits + string.punctuation
+ key = ''.join([random.choice(chars) for i in range(12)])
+ else:
+ key = str(text)
+ self._pings[(user, key)] = time.time()
+ self.ctcpMakeQuery(user, [('PING', key)])
+
+ if len(self._pings) > self._MAX_PINGRING:
+ # Remove some of the oldest entries.
+ byValue = [(v, k) for (k, v) in self._pings.items()]
+ byValue.sort()
+ excess = self._MAX_PINGRING - len(self._pings)
+ for i in xrange(excess):
+ del self._pings[byValue[i][1]]
+
+ def dccSend(self, user, file):
+ if type(file) == types.StringType:
+ file = open(file, 'r')
+
+ size = fileSize(file)
+
+ name = getattr(file, "name", "file@%s" % (id(file),))
+
+ factory = DccSendFactory(file)
+ port = reactor.listenTCP(0, factory, 1)
+
+ raise NotImplementedError,(
+ "XXX!!! Help! I need to bind a socket, have it listen, and tell me its address. "
+ "(and stop accepting once we've made a single connection.)")
+
+ my_address = struct.pack("!I", my_address)
+
+ args = ['SEND', name, my_address, str(port)]
+
+ if not (size is None):
+ args.append(size)
+
+ args = string.join(args, ' ')
+
+ self.ctcpMakeQuery(user, [('DCC', args)])
+
+ def dccResume(self, user, fileName, port, resumePos):
+ """Send a DCC RESUME request to another user."""
+ self.ctcpMakeQuery(user, [
+ ('DCC', ['RESUME', fileName, port, resumePos])])
+
+ def dccAcceptResume(self, user, fileName, port, resumePos):
+ """Send a DCC ACCEPT response to clients who have requested a resume.
+ """
+ self.ctcpMakeQuery(user, [
+ ('DCC', ['ACCEPT', fileName, port, resumePos])])
+
+ ### server->client messages
+ ### You might want to fiddle with these,
+ ### but it is safe to leave them alone.
+
+ def irc_ERR_NICKNAMEINUSE(self, prefix, params):
+ """
+ Called when we try to register or change to a nickname that is already
+ taken.
+ """
+ self._attemptedNick = self.alterCollidedNick(self._attemptedNick)
+ self.setNick(self._attemptedNick)
+
+
+ def alterCollidedNick(self, nickname):
+ """
+ Generate an altered version of a nickname that caused a collision in an
+ effort to create an unused related name for subsequent registration.
+
+ @param nickname: The nickname a user is attempting to register.
+ @type nickname: C{str}
+
+ @returns: A string that is in some way different from the nickname.
+ @rtype: C{str}
+ """
+ return nickname + '_'
+
+
+ def irc_ERR_ERRONEUSNICKNAME(self, prefix, params):
+ """
+ Called when we try to register or change to an illegal nickname.
+
+ The server should send this reply when the nickname contains any
+ disallowed characters. The bot will stall, waiting for RPL_WELCOME, if
+ we don't handle this during sign-on.
+
+ @note: The method uses the spelling I{erroneus}, as it appears in
+ the RFC, section 6.1.
+ """
+ if not self._registered:
+ self.setNick(self.erroneousNickFallback)
+
+
+ def irc_ERR_PASSWDMISMATCH(self, prefix, params):
+ """
+ Called when the login was incorrect.
+ """
+ raise IRCPasswordMismatch("Password Incorrect.")
+
+ def irc_RPL_WELCOME(self, prefix, params):
+ """
+ Called when we have received the welcome from the server.
+ """
+ self._registered = True
+ self.nickname = self._attemptedNick
+ self.signedOn()
+
+ def irc_JOIN(self, prefix, params):
+ """
+ Called when a user joins a channel.
+ """
+ nick = string.split(prefix,'!')[0]
+ channel = params[-1]
+ if nick == self.nickname:
+ self.joined(channel)
+ else:
+ self.userJoined(nick, channel)
+
+ def irc_PART(self, prefix, params):
+ """
+ Called when a user leaves a channel.
+ """
+ nick = string.split(prefix,'!')[0]
+ channel = params[0]
+ if nick == self.nickname:
+ self.left(channel)
+ else:
+ self.userLeft(nick, channel)
+
+ def irc_QUIT(self, prefix, params):
+ """
+ Called when a user has quit.
+ """
+ nick = string.split(prefix,'!')[0]
+ self.userQuit(nick, params[0])
+
+
+ def irc_MODE(self, user, params):
+ """
+ Parse a server mode change message.
+ """
+ channel, modes, args = params[0], params[1], params[2:]
+
+ if modes[0] not in '-+':
+ modes = '+' + modes
+
+ if channel == self.nickname:
+ # This is a mode change to our individual user, not a channel mode
+ # that involves us.
+ paramModes = self.getUserModeParams()
+ else:
+ paramModes = self.getChannelModeParams()
+
+ try:
+ added, removed = parseModes(modes, args, paramModes)
+ except IRCBadModes:
+ log.err(None, 'An error occured while parsing the following '
+ 'MODE message: MODE %s' % (' '.join(params),))
+ else:
+ if added:
+ modes, params = zip(*added)
+ self.modeChanged(user, channel, True, ''.join(modes), params)
+
+ if removed:
+ modes, params = zip(*removed)
+ self.modeChanged(user, channel, False, ''.join(modes), params)
+
+
+ def irc_PING(self, prefix, params):
+ """
+ Called when some has pinged us.
+ """
+ self.sendLine("PONG %s" % params[-1])
+
+ def irc_PRIVMSG(self, prefix, params):
+ """
+ Called when we get a message.
+ """
+ user = prefix
+ channel = params[0]
+ message = params[-1]
+
+ if not message: return # don't raise an exception if some idiot sends us a blank message
+
+ if message[0]==X_DELIM:
+ m = ctcpExtract(message)
+ if m['extended']:
+ self.ctcpQuery(user, channel, m['extended'])
+
+ if not m['normal']:
+ return
+
+ message = string.join(m['normal'], ' ')
+
+ self.privmsg(user, channel, message)
+
+ def irc_NOTICE(self, prefix, params):
+ """
+ Called when a user gets a notice.
+ """
+ user = prefix
+ channel = params[0]
+ message = params[-1]
+
+ if message[0]==X_DELIM:
+ m = ctcpExtract(message)
+ if m['extended']:
+ self.ctcpReply(user, channel, m['extended'])
+
+ if not m['normal']:
+ return
+
+ message = string.join(m['normal'], ' ')
+
+ self.noticed(user, channel, message)
+
+ def irc_NICK(self, prefix, params):
+ """
+ Called when a user changes their nickname.
+ """
+ nick = string.split(prefix,'!', 1)[0]
+ if nick == self.nickname:
+ self.nickChanged(params[0])
+ else:
+ self.userRenamed(nick, params[0])
+
+ def irc_KICK(self, prefix, params):
+ """
+ Called when a user is kicked from a channel.
+ """
+ kicker = string.split(prefix,'!')[0]
+ channel = params[0]
+ kicked = params[1]
+ message = params[-1]
+ if string.lower(kicked) == string.lower(self.nickname):
+ # Yikes!
+ self.kickedFrom(channel, kicker, message)
+ else:
+ self.userKicked(kicked, channel, kicker, message)
+
+ def irc_TOPIC(self, prefix, params):
+ """
+ Someone in the channel set the topic.
+ """
+ user = string.split(prefix, '!')[0]
+ channel = params[0]
+ newtopic = params[1]
+ self.topicUpdated(user, channel, newtopic)
+
+ def irc_RPL_TOPIC(self, prefix, params):
+ """
+ Called when the topic for a channel is initially reported or when it
+ subsequently changes.
+ """
+ user = string.split(prefix, '!')[0]
+ channel = params[1]
+ newtopic = params[2]
+ self.topicUpdated(user, channel, newtopic)
+
+ def irc_RPL_NOTOPIC(self, prefix, params):
+ user = string.split(prefix, '!')[0]
+ channel = params[1]
+ newtopic = ""
+ self.topicUpdated(user, channel, newtopic)
+
+ def irc_RPL_MOTDSTART(self, prefix, params):
+ if params[-1].startswith("- "):
+ params[-1] = params[-1][2:]
+ self.motd = [params[-1]]
+
+ def irc_RPL_MOTD(self, prefix, params):
+ if params[-1].startswith("- "):
+ params[-1] = params[-1][2:]
+ if self.motd is None:
+ self.motd = []
+ self.motd.append(params[-1])
+
+
+ def irc_RPL_ENDOFMOTD(self, prefix, params):
+ """
+ I{RPL_ENDOFMOTD} indicates the end of the message of the day
+ messages. Deliver the accumulated lines to C{receivedMOTD}.
+ """
+ motd = self.motd
+ self.motd = None
+ self.receivedMOTD(motd)
+
+
+ def irc_RPL_CREATED(self, prefix, params):
+ self.created(params[1])
+
+ def irc_RPL_YOURHOST(self, prefix, params):
+ self.yourHost(params[1])
+
+ def irc_RPL_MYINFO(self, prefix, params):
+ info = params[1].split(None, 3)
+ while len(info) < 4:
+ info.append(None)
+ self.myInfo(*info)
+
+ def irc_RPL_BOUNCE(self, prefix, params):
+ self.bounce(params[1])
+
+ def irc_RPL_ISUPPORT(self, prefix, params):
+ args = params[1:-1]
+ # Several ISUPPORT messages, in no particular order, may be sent
+ # to the client at any given point in time (usually only on connect,
+ # though.) For this reason, ServerSupportedFeatures.parse is intended
+ # to mutate the supported feature list.
+ self.supported.parse(args)
+ self.isupport(args)
+
+ def irc_RPL_LUSERCLIENT(self, prefix, params):
+ self.luserClient(params[1])
+
+ def irc_RPL_LUSEROP(self, prefix, params):
+ try:
+ self.luserOp(int(params[1]))
+ except ValueError:
+ pass
+
+ def irc_RPL_LUSERCHANNELS(self, prefix, params):
+ try:
+ self.luserChannels(int(params[1]))
+ except ValueError:
+ pass
+
+ def irc_RPL_LUSERME(self, prefix, params):
+ self.luserMe(params[1])
+
+ def irc_unknown(self, prefix, command, params):
+ pass
+
+ ### Receiving a CTCP query from another party
+ ### It is safe to leave these alone.
+
+ def ctcpQuery(self, user, channel, messages):
+ """Dispatch method for any CTCP queries received.
+ """
+ for m in messages:
+ method = getattr(self, "ctcpQuery_%s" % m[0], None)
+ if method:
+ method(user, channel, m[1])
+ else:
+ self.ctcpUnknownQuery(user, channel, m[0], m[1])
+
+ def ctcpQuery_ACTION(self, user, channel, data):
+ self.action(user, channel, data)
+
+ def ctcpQuery_PING(self, user, channel, data):
+ nick = string.split(user,"!")[0]
+ self.ctcpMakeReply(nick, [("PING", data)])
+
+ def ctcpQuery_FINGER(self, user, channel, data):
+ if data is not None:
+ self.quirkyMessage("Why did %s send '%s' with a FINGER query?"
+ % (user, data))
+ if not self.fingerReply:
+ return
+
+ if callable(self.fingerReply):
+ reply = self.fingerReply()
+ else:
+ reply = str(self.fingerReply)
+
+ nick = string.split(user,"!")[0]
+ self.ctcpMakeReply(nick, [('FINGER', reply)])
+
+ def ctcpQuery_VERSION(self, user, channel, data):
+ if data is not None:
+ self.quirkyMessage("Why did %s send '%s' with a VERSION query?"
+ % (user, data))
+
+ if self.versionName:
+ nick = string.split(user,"!")[0]
+ self.ctcpMakeReply(nick, [('VERSION', '%s:%s:%s' %
+ (self.versionName,
+ self.versionNum or '',
+ self.versionEnv or ''))])
+
+ def ctcpQuery_SOURCE(self, user, channel, data):
+ if data is not None:
+ self.quirkyMessage("Why did %s send '%s' with a SOURCE query?"
+ % (user, data))
+ if self.sourceURL:
+ nick = string.split(user,"!")[0]
+ # The CTCP document (Zeuge, Rollo, Mesander 1994) says that SOURCE
+ # replies should be responded to with the location of an anonymous
+ # FTP server in host:directory:file format. I'm taking the liberty
+ # of bringing it into the 21st century by sending a URL instead.
+ self.ctcpMakeReply(nick, [('SOURCE', self.sourceURL),
+ ('SOURCE', None)])
+
+ def ctcpQuery_USERINFO(self, user, channel, data):
+ if data is not None:
+ self.quirkyMessage("Why did %s send '%s' with a USERINFO query?"
+ % (user, data))
+ if self.userinfo:
+ nick = string.split(user,"!")[0]
+ self.ctcpMakeReply(nick, [('USERINFO', self.userinfo)])
+
+ def ctcpQuery_CLIENTINFO(self, user, channel, data):
+ """A master index of what CTCP tags this client knows.
+
+ If no arguments are provided, respond with a list of known tags.
+ If an argument is provided, provide human-readable help on
+ the usage of that tag.
+ """
+
+ nick = string.split(user,"!")[0]
+ if not data:
+ # XXX: prefixedMethodNames gets methods from my *class*,
+ # but it's entirely possible that this *instance* has more
+ # methods.
+ names = reflect.prefixedMethodNames(self.__class__,
+ 'ctcpQuery_')
+
+ self.ctcpMakeReply(nick, [('CLIENTINFO',
+ string.join(names, ' '))])
+ else:
+ args = string.split(data)
+ method = getattr(self, 'ctcpQuery_%s' % (args[0],), None)
+ if not method:
+ self.ctcpMakeReply(nick, [('ERRMSG',
+ "CLIENTINFO %s :"
+ "Unknown query '%s'"
+ % (data, args[0]))])
+ return
+ doc = getattr(method, '__doc__', '')
+ self.ctcpMakeReply(nick, [('CLIENTINFO', doc)])
+
+
+ def ctcpQuery_ERRMSG(self, user, channel, data):
+ # Yeah, this seems strange, but that's what the spec says to do
+ # when faced with an ERRMSG query (not a reply).
+ nick = string.split(user,"!")[0]
+ self.ctcpMakeReply(nick, [('ERRMSG',
+ "%s :No error has occoured." % data)])
+
+ def ctcpQuery_TIME(self, user, channel, data):
+ if data is not None:
+ self.quirkyMessage("Why did %s send '%s' with a TIME query?"
+ % (user, data))
+ nick = string.split(user,"!")[0]
+ self.ctcpMakeReply(nick,
+ [('TIME', ':%s' %
+ time.asctime(time.localtime(time.time())))])
+
+ def ctcpQuery_DCC(self, user, channel, data):
+ """Initiate a Direct Client Connection
+ """
+
+ if not data: return
+ dcctype = data.split(None, 1)[0].upper()
+ handler = getattr(self, "dcc_" + dcctype, None)
+ if handler:
+ if self.dcc_sessions is None:
+ self.dcc_sessions = []
+ data = data[len(dcctype)+1:]
+ handler(user, channel, data)
+ else:
+ nick = string.split(user,"!")[0]
+ self.ctcpMakeReply(nick, [('ERRMSG',
+ "DCC %s :Unknown DCC type '%s'"
+ % (data, dcctype))])
+ self.quirkyMessage("%s offered unknown DCC type %s"
+ % (user, dcctype))
+
+ def dcc_SEND(self, user, channel, data):
+ # Use splitQuoted for those who send files with spaces in the names.
+ data = text.splitQuoted(data)
+ if len(data) < 3:
+ raise IRCBadMessage, "malformed DCC SEND request: %r" % (data,)
+
+ (filename, address, port) = data[:3]
+
+ address = dccParseAddress(address)
+ try:
+ port = int(port)
+ except ValueError:
+ raise IRCBadMessage, "Indecipherable port %r" % (port,)
+
+ size = -1
+ if len(data) >= 4:
+ try:
+ size = int(data[3])
+ except ValueError:
+ pass
+
+ # XXX Should we bother passing this data?
+ self.dccDoSend(user, address, port, filename, size, data)
+
+ def dcc_ACCEPT(self, user, channel, data):
+ data = text.splitQuoted(data)
+ if len(data) < 3:
+ raise IRCBadMessage, "malformed DCC SEND ACCEPT request: %r" % (data,)
+ (filename, port, resumePos) = data[:3]
+ try:
+ port = int(port)
+ resumePos = int(resumePos)
+ except ValueError:
+ return
+
+ self.dccDoAcceptResume(user, filename, port, resumePos)
+
+ def dcc_RESUME(self, user, channel, data):
+ data = text.splitQuoted(data)
+ if len(data) < 3:
+ raise IRCBadMessage, "malformed DCC SEND RESUME request: %r" % (data,)
+ (filename, port, resumePos) = data[:3]
+ try:
+ port = int(port)
+ resumePos = int(resumePos)
+ except ValueError:
+ return
+ self.dccDoResume(user, filename, port, resumePos)
+
+ def dcc_CHAT(self, user, channel, data):
+ data = text.splitQuoted(data)
+ if len(data) < 3:
+ raise IRCBadMessage, "malformed DCC CHAT request: %r" % (data,)
+
+ (filename, address, port) = data[:3]
+
+ address = dccParseAddress(address)
+ try:
+ port = int(port)
+ except ValueError:
+ raise IRCBadMessage, "Indecipherable port %r" % (port,)
+
+ self.dccDoChat(user, channel, address, port, data)
+
+ ### The dccDo methods are the slightly higher-level siblings of
+ ### common dcc_ methods; the arguments have been parsed for them.
+
+ def dccDoSend(self, user, address, port, fileName, size, data):
+ """Called when I receive a DCC SEND offer from a client.
+
+ By default, I do nothing here."""
+ ## filename = path.basename(arg)
+ ## protocol = DccFileReceive(filename, size,
+ ## (user,channel,data),self.dcc_destdir)
+ ## reactor.clientTCP(address, port, protocol)
+ ## self.dcc_sessions.append(protocol)
+ pass
+
+ def dccDoResume(self, user, file, port, resumePos):
+ """Called when a client is trying to resume an offered file
+ via DCC send. It should be either replied to with a DCC
+ ACCEPT or ignored (default)."""
+ pass
+
+ def dccDoAcceptResume(self, user, file, port, resumePos):
+ """Called when a client has verified and accepted a DCC resume
+ request made by us. By default it will do nothing."""
+ pass
+
+ def dccDoChat(self, user, channel, address, port, data):
+ pass
+ #factory = DccChatFactory(self, queryData=(user, channel, data))
+ #reactor.connectTCP(address, port, factory)
+ #self.dcc_sessions.append(factory)
+
+ #def ctcpQuery_SED(self, user, data):
+ # """Simple Encryption Doodoo
+ #
+ # Feel free to implement this, but no specification is available.
+ # """
+ # raise NotImplementedError
+
+ def ctcpUnknownQuery(self, user, channel, tag, data):
+ nick = string.split(user,"!")[0]
+ self.ctcpMakeReply(nick, [('ERRMSG',
+ "%s %s: Unknown query '%s'"
+ % (tag, data, tag))])
+
+ log.msg("Unknown CTCP query from %s: %s %s\n"
+ % (user, tag, data))
+
+ def ctcpMakeReply(self, user, messages):
+ """
+ Send one or more C{extended messages} as a CTCP reply.
+
+ @type messages: a list of extended messages. An extended
+ message is a (tag, data) tuple, where 'data' may be C{None}.
+ """
+ self.notice(user, ctcpStringify(messages))
+
+ ### client CTCP query commands
+
+ def ctcpMakeQuery(self, user, messages):
+ """
+ Send one or more C{extended messages} as a CTCP query.
+
+ @type messages: a list of extended messages. An extended
+ message is a (tag, data) tuple, where 'data' may be C{None}.
+ """
+ self.msg(user, ctcpStringify(messages))
+
+ ### Receiving a response to a CTCP query (presumably to one we made)
+ ### You may want to add methods here, or override UnknownReply.
+
+ def ctcpReply(self, user, channel, messages):
+ """
+ Dispatch method for any CTCP replies received.
+ """
+ for m in messages:
+ method = getattr(self, "ctcpReply_%s" % m[0], None)
+ if method:
+ method(user, channel, m[1])
+ else:
+ self.ctcpUnknownReply(user, channel, m[0], m[1])
+
+ def ctcpReply_PING(self, user, channel, data):
+ nick = user.split('!', 1)[0]
+ if (not self._pings) or (not self._pings.has_key((nick, data))):
+ raise IRCBadMessage,\
+ "Bogus PING response from %s: %s" % (user, data)
+
+ t0 = self._pings[(nick, data)]
+ self.pong(user, time.time() - t0)
+
+ def ctcpUnknownReply(self, user, channel, tag, data):
+ """Called when a fitting ctcpReply_ method is not found.
+
+ XXX: If the client makes arbitrary CTCP queries,
+ this method should probably show the responses to
+ them instead of treating them as anomolies.
+ """
+ log.msg("Unknown CTCP reply from %s: %s %s\n"
+ % (user, tag, data))
+
+ ### Error handlers
+ ### You may override these with something more appropriate to your UI.
+
+ def badMessage(self, line, excType, excValue, tb):
+ """When I get a message that's so broken I can't use it.
+ """
+ log.msg(line)
+ log.msg(string.join(traceback.format_exception(excType,
+ excValue,
+ tb),''))
+
+ def quirkyMessage(self, s):
+ """This is called when I receive a message which is peculiar,
+ but not wholly indecipherable.
+ """
+ log.msg(s + '\n')
+
+ ### Protocool methods
+
+ def connectionMade(self):
+ self.supported = ServerSupportedFeatures()
+ self._queue = []
+ if self.performLogin:
+ self.register(self.nickname)
+
+ def dataReceived(self, data):
+ basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
+
+ def lineReceived(self, line):
+ line = lowDequote(line)
+ try:
+ prefix, command, params = parsemsg(line)
+ if numeric_to_symbolic.has_key(command):
+ command = numeric_to_symbolic[command]
+ self.handleCommand(command, prefix, params)
+ except IRCBadMessage:
+ self.badMessage(line, *sys.exc_info())
+
+
+ def getUserModeParams(self):
+ """
+ Get user modes that require parameters for correct parsing.
+
+ @rtype: C{[str, str]}
+ @return C{[add, remove]}
+ """
+ return ['', '']
+
+
+ def getChannelModeParams(self):
+ """
+ Get channel modes that require parameters for correct parsing.
+
+ @rtype: C{[str, str]}
+ @return C{[add, remove]}
+ """
+ # PREFIX modes are treated as "type B" CHANMODES, they always take
+ # parameter.
+ params = ['', '']
+ prefixes = self.supported.getFeature('PREFIX', {})
+ params[0] = params[1] = ''.join(prefixes.iterkeys())
+
+ chanmodes = self.supported.getFeature('CHANMODES')
+ if chanmodes is not None:
+ params[0] += chanmodes.get('addressModes', '')
+ params[0] += chanmodes.get('param', '')
+ params[1] = params[0]
+ params[0] += chanmodes.get('setParam', '')
+ return params
+
+
+ def handleCommand(self, command, prefix, params):
+ """Determine the function to call for the given command and call
+ it with the given arguments.
+ """
+ method = getattr(self, "irc_%s" % command, None)
+ try:
+ if method is not None:
+ method(prefix, params)
+ else:
+ self.irc_unknown(prefix, command, params)
+ except:
+ log.deferr()
+
+
+ def __getstate__(self):
+ dct = self.__dict__.copy()
+ dct['dcc_sessions'] = None
+ dct['_pings'] = None
+ return dct
+
+
+def dccParseAddress(address):
+ if '.' in address:
+ pass
+ else:
+ try:
+ address = long(address)
+ except ValueError:
+ raise IRCBadMessage,\
+ "Indecipherable address %r" % (address,)
+ else:
+ address = (
+ (address >> 24) & 0xFF,
+ (address >> 16) & 0xFF,
+ (address >> 8) & 0xFF,
+ address & 0xFF,
+ )
+ address = '.'.join(map(str,address))
+ return address
+
+
+class DccFileReceiveBasic(protocol.Protocol, styles.Ephemeral):
+ """Bare protocol to receive a Direct Client Connection SEND stream.
+
+ This does enough to keep the other guy talking, but you'll want to
+ extend my dataReceived method to *do* something with the data I get.
+ """
+
+ bytesReceived = 0
+
+ def __init__(self, resumeOffset=0):
+ self.bytesReceived = resumeOffset
+ self.resume = (resumeOffset != 0)
+
+ def dataReceived(self, data):
+ """Called when data is received.
+
+ Warning: This just acknowledges to the remote host that the
+ data has been received; it doesn't *do* anything with the
+ data, so you'll want to override this.
+ """
+ self.bytesReceived = self.bytesReceived + len(data)
+ self.transport.write(struct.pack('!i', self.bytesReceived))
+
+
+class DccSendProtocol(protocol.Protocol, styles.Ephemeral):
+ """Protocol for an outgoing Direct Client Connection SEND.
+ """
+
+ blocksize = 1024
+ file = None
+ bytesSent = 0
+ completed = 0
+ connected = 0
+
+ def __init__(self, file):
+ if type(file) is types.StringType:
+ self.file = open(file, 'r')
+
+ def connectionMade(self):
+ self.connected = 1
+ self.sendBlock()
+
+ def dataReceived(self, data):
+ # XXX: Do we need to check to see if len(data) != fmtsize?
+
+ bytesShesGot = struct.unpack("!I", data)
+ if bytesShesGot < self.bytesSent:
+ # Wait for her.
+ # XXX? Add some checks to see if we've stalled out?
+ return
+ elif bytesShesGot > self.bytesSent:
+ # self.transport.log("DCC SEND %s: She says she has %d bytes "
+ # "but I've only sent %d. I'm stopping "
+ # "this screwy transfer."
+ # % (self.file,
+ # bytesShesGot, self.bytesSent))
+ self.transport.loseConnection()
+ return
+
+ self.sendBlock()
+
+ def sendBlock(self):
+ block = self.file.read(self.blocksize)
+ if block:
+ self.transport.write(block)
+ self.bytesSent = self.bytesSent + len(block)
+ else:
+ # Nothing more to send, transfer complete.
+ self.transport.loseConnection()
+ self.completed = 1
+
+ def connectionLost(self, reason):
+ self.connected = 0
+ if hasattr(self.file, "close"):
+ self.file.close()
+
+
+class DccSendFactory(protocol.Factory):
+ protocol = DccSendProtocol
+ def __init__(self, file):
+ self.file = file
+
+ def buildProtocol(self, connection):
+ p = self.protocol(self.file)
+ p.factory = self
+ return p
+
+
+def fileSize(file):
+ """I'll try my damndest to determine the size of this file object.
+ """
+ size = None
+ if hasattr(file, "fileno"):
+ fileno = file.fileno()
+ try:
+ stat_ = os.fstat(fileno)
+ size = stat_[stat.ST_SIZE]
+ except:
+ pass
+ else:
+ return size
+
+ if hasattr(file, "name") and path.exists(file.name):
+ try:
+ size = path.getsize(file.name)
+ except:
+ pass
+ else:
+ return size
+
+ if hasattr(file, "seek") and hasattr(file, "tell"):
+ try:
+ try:
+ file.seek(0, 2)
+ size = file.tell()
+ finally:
+ file.seek(0, 0)
+ except:
+ pass
+ else:
+ return size
+
+ return size
+
+class DccChat(basic.LineReceiver, styles.Ephemeral):
+ """Direct Client Connection protocol type CHAT.
+
+ DCC CHAT is really just your run o' the mill basic.LineReceiver
+ protocol. This class only varies from that slightly, accepting
+ either LF or CR LF for a line delimeter for incoming messages
+ while always using CR LF for outgoing.
+
+ The lineReceived method implemented here uses the DCC connection's
+ 'client' attribute (provided upon construction) to deliver incoming
+ lines from the DCC chat via IRCClient's normal privmsg interface.
+ That's something of a spoof, which you may well want to override.
+ """
+
+ queryData = None
+ delimiter = CR + NL
+ client = None
+ remoteParty = None
+ buffer = ""
+
+ def __init__(self, client, queryData=None):
+ """Initialize a new DCC CHAT session.
+
+ queryData is a 3-tuple of
+ (fromUser, targetUserOrChannel, data)
+ as received by the CTCP query.
+
+ (To be honest, fromUser is the only thing that's currently
+ used here. targetUserOrChannel is potentially useful, while
+ the 'data' argument is soley for informational purposes.)
+ """
+ self.client = client
+ if queryData:
+ self.queryData = queryData
+ self.remoteParty = self.queryData[0]
+
+ def dataReceived(self, data):
+ self.buffer = self.buffer + data
+ lines = string.split(self.buffer, LF)
+ # Put the (possibly empty) element after the last LF back in the
+ # buffer
+ self.buffer = lines.pop()
+
+ for line in lines:
+ if line[-1] == CR:
+ line = line[:-1]
+ self.lineReceived(line)
+
+ def lineReceived(self, line):
+ log.msg("DCC CHAT<%s> %s" % (self.remoteParty, line))
+ self.client.privmsg(self.remoteParty,
+ self.client.nickname, line)
+
+
+class DccChatFactory(protocol.ClientFactory):
+ protocol = DccChat
+ noisy = 0
+ def __init__(self, client, queryData):
+ self.client = client
+ self.queryData = queryData
+
+ def buildProtocol(self, addr):
+ p = self.protocol(client=self.client, queryData=self.queryData)
+ p.factory = self
+
+ def clientConnectionFailed(self, unused_connector, unused_reason):
+ self.client.dcc_sessions.remove(self)
+
+ def clientConnectionLost(self, unused_connector, unused_reason):
+ self.client.dcc_sessions.remove(self)
+
+
+def dccDescribe(data):
+ """Given the data chunk from a DCC query, return a descriptive string.
+ """
+
+ orig_data = data
+ data = string.split(data)
+ if len(data) < 4:
+ return orig_data
+
+ (dcctype, arg, address, port) = data[:4]
+
+ if '.' in address:
+ pass
+ else:
+ try:
+ address = long(address)
+ except ValueError:
+ pass
+ else:
+ address = (
+ (address >> 24) & 0xFF,
+ (address >> 16) & 0xFF,
+ (address >> 8) & 0xFF,
+ address & 0xFF,
+ )
+ # The mapping to 'int' is to get rid of those accursed
+ # "L"s which python 1.5.2 puts on the end of longs.
+ address = string.join(map(str,map(int,address)), ".")
+
+ if dcctype == 'SEND':
+ filename = arg
+
+ size_txt = ''
+ if len(data) >= 5:
+ try:
+ size = int(data[4])
+ size_txt = ' of size %d bytes' % (size,)
+ except ValueError:
+ pass
+
+ dcc_text = ("SEND for file '%s'%s at host %s, port %s"
+ % (filename, size_txt, address, port))
+ elif dcctype == 'CHAT':
+ dcc_text = ("CHAT for host %s, port %s"
+ % (address, port))
+ else:
+ dcc_text = orig_data
+
+ return dcc_text
+
+
+class DccFileReceive(DccFileReceiveBasic):
+ """Higher-level coverage for getting a file from DCC SEND.
+
+ I allow you to change the file's name and destination directory.
+ I won't overwrite an existing file unless I've been told it's okay
+ to do so. If passed the resumeOffset keyword argument I will attempt to
+ resume the file from that amount of bytes.
+
+ XXX: I need to let the client know when I am finished.
+ XXX: I need to decide how to keep a progress indicator updated.
+ XXX: Client needs a way to tell me "Do not finish until I say so."
+ XXX: I need to make sure the client understands if the file cannot be written.
+ """
+
+ filename = 'dcc'
+ fileSize = -1
+ destDir = '.'
+ overwrite = 0
+ fromUser = None
+ queryData = None
+
+ def __init__(self, filename, fileSize=-1, queryData=None,
+ destDir='.', resumeOffset=0):
+ DccFileReceiveBasic.__init__(self, resumeOffset=resumeOffset)
+ self.filename = filename
+ self.destDir = destDir
+ self.fileSize = fileSize
+
+ if queryData:
+ self.queryData = queryData
+ self.fromUser = self.queryData[0]
+
+ def set_directory(self, directory):
+ """Set the directory where the downloaded file will be placed.
+
+ May raise OSError if the supplied directory path is not suitable.
+ """
+ if not path.exists(directory):
+ raise OSError(errno.ENOENT, "You see no directory there.",
+ directory)
+ if not path.isdir(directory):
+ raise OSError(errno.ENOTDIR, "You cannot put a file into "
+ "something which is not a directory.",
+ directory)
+ if not os.access(directory, os.X_OK | os.W_OK):
+ raise OSError(errno.EACCES,
+ "This directory is too hard to write in to.",
+ directory)
+ self.destDir = directory
+
+ def set_filename(self, filename):
+ """Change the name of the file being transferred.
+
+ This replaces the file name provided by the sender.
+ """
+ self.filename = filename
+
+ def set_overwrite(self, boolean):
+ """May I overwrite existing files?
+ """
+ self.overwrite = boolean
+
+
+ # Protocol-level methods.
+
+ def connectionMade(self):
+ dst = path.abspath(path.join(self.destDir,self.filename))
+ exists = path.exists(dst)
+ if self.resume and exists:
+ # I have been told I want to resume, and a file already
+ # exists - Here we go
+ self.file = open(dst, 'ab')
+ log.msg("Attempting to resume %s - starting from %d bytes" %
+ (self.file, self.file.tell()))
+ elif self.overwrite or not exists:
+ self.file = open(dst, 'wb')
+ else:
+ raise OSError(errno.EEXIST,
+ "There's a file in the way. "
+ "Perhaps that's why you cannot open it.",
+ dst)
+
+ def dataReceived(self, data):
+ self.file.write(data)
+ DccFileReceiveBasic.dataReceived(self, data)
+
+ # XXX: update a progress indicator here?
+
+ def connectionLost(self, reason):
+ """When the connection is lost, I close the file.
+ """
+ self.connected = 0
+ logmsg = ("%s closed." % (self,))
+ if self.fileSize > 0:
+ logmsg = ("%s %d/%d bytes received"
+ % (logmsg, self.bytesReceived, self.fileSize))
+ if self.bytesReceived == self.fileSize:
+ pass # Hooray!
+ elif self.bytesReceived < self.fileSize:
+ logmsg = ("%s (Warning: %d bytes short)"
+ % (logmsg, self.fileSize - self.bytesReceived))
+ else:
+ logmsg = ("%s (file larger than expected)"
+ % (logmsg,))
+ else:
+ logmsg = ("%s %d bytes received"
+ % (logmsg, self.bytesReceived))
+
+ if hasattr(self, 'file'):
+ logmsg = "%s and written to %s.\n" % (logmsg, self.file.name)
+ if hasattr(self.file, 'close'): self.file.close()
+
+ # self.transport.log(logmsg)
+
+ def __str__(self):
+ if not self.connected:
+ return "<Unconnected DccFileReceive object at %x>" % (id(self),)
+ from_ = self.transport.getPeer()
+ if self.fromUser:
+ from_ = "%s (%s)" % (self.fromUser, from_)
+
+ s = ("DCC transfer of '%s' from %s" % (self.filename, from_))
+ return s
+
+ def __repr__(self):
+ s = ("<%s at %x: GET %s>"
+ % (self.__class__, id(self), self.filename))
+ return s
+
+
+# CTCP constants and helper functions
+
+X_DELIM = chr(001)
+
+def ctcpExtract(message):
+ """Extract CTCP data from a string.
+
+ Returns a dictionary with two items:
+
+ - C{'extended'}: a list of CTCP (tag, data) tuples
+ - C{'normal'}: a list of strings which were not inside a CTCP delimeter
+ """
+
+ extended_messages = []
+ normal_messages = []
+ retval = {'extended': extended_messages,
+ 'normal': normal_messages }
+
+ messages = string.split(message, X_DELIM)
+ odd = 0
+
+ # X1 extended data X2 nomal data X3 extended data X4 normal...
+ while messages:
+ if odd:
+ extended_messages.append(messages.pop(0))
+ else:
+ normal_messages.append(messages.pop(0))
+ odd = not odd
+
+ extended_messages[:] = filter(None, extended_messages)
+ normal_messages[:] = filter(None, normal_messages)
+
+ extended_messages[:] = map(ctcpDequote, extended_messages)
+ for i in xrange(len(extended_messages)):
+ m = string.split(extended_messages[i], SPC, 1)
+ tag = m[0]
+ if len(m) > 1:
+ data = m[1]
+ else:
+ data = None
+
+ extended_messages[i] = (tag, data)
+
+ return retval
+
+# CTCP escaping
+
+M_QUOTE= chr(020)
+
+mQuoteTable = {
+ NUL: M_QUOTE + '0',
+ NL: M_QUOTE + 'n',
+ CR: M_QUOTE + 'r',
+ M_QUOTE: M_QUOTE + M_QUOTE
+ }
+
+mDequoteTable = {}
+for k, v in mQuoteTable.items():
+ mDequoteTable[v[-1]] = k
+del k, v
+
+mEscape_re = re.compile('%s.' % (re.escape(M_QUOTE),), re.DOTALL)
+
+def lowQuote(s):
+ for c in (M_QUOTE, NUL, NL, CR):
+ s = string.replace(s, c, mQuoteTable[c])
+ return s
+
+def lowDequote(s):
+ def sub(matchobj, mDequoteTable=mDequoteTable):
+ s = matchobj.group()[1]
+ try:
+ s = mDequoteTable[s]
+ except KeyError:
+ s = s
+ return s
+
+ return mEscape_re.sub(sub, s)
+
+X_QUOTE = '\\'
+
+xQuoteTable = {
+ X_DELIM: X_QUOTE + 'a',
+ X_QUOTE: X_QUOTE + X_QUOTE
+ }
+
+xDequoteTable = {}
+
+for k, v in xQuoteTable.items():
+ xDequoteTable[v[-1]] = k
+
+xEscape_re = re.compile('%s.' % (re.escape(X_QUOTE),), re.DOTALL)
+
+def ctcpQuote(s):
+ for c in (X_QUOTE, X_DELIM):
+ s = string.replace(s, c, xQuoteTable[c])
+ return s
+
+def ctcpDequote(s):
+ def sub(matchobj, xDequoteTable=xDequoteTable):
+ s = matchobj.group()[1]
+ try:
+ s = xDequoteTable[s]
+ except KeyError:
+ s = s
+ return s
+
+ return xEscape_re.sub(sub, s)
+
+def ctcpStringify(messages):
+ """
+ @type messages: a list of extended messages. An extended
+ message is a (tag, data) tuple, where 'data' may be C{None}, a
+ string, or a list of strings to be joined with whitespace.
+
+ @returns: String
+ """
+ coded_messages = []
+ for (tag, data) in messages:
+ if data:
+ if not isinstance(data, types.StringType):
+ try:
+ # data as list-of-strings
+ data = " ".join(map(str, data))
+ except TypeError:
+ # No? Then use it's %s representation.
+ pass
+ m = "%s %s" % (tag, data)
+ else:
+ m = str(tag)
+ m = ctcpQuote(m)
+ m = "%s%s%s" % (X_DELIM, m, X_DELIM)
+ coded_messages.append(m)
+
+ line = string.join(coded_messages, '')
+ return line
+
+
+# Constants (from RFC 2812)
+RPL_WELCOME = '001'
+RPL_YOURHOST = '002'
+RPL_CREATED = '003'
+RPL_MYINFO = '004'
+RPL_ISUPPORT = '005'
+RPL_BOUNCE = '010'
+RPL_USERHOST = '302'
+RPL_ISON = '303'
+RPL_AWAY = '301'
+RPL_UNAWAY = '305'
+RPL_NOWAWAY = '306'
+RPL_WHOISUSER = '311'
+RPL_WHOISSERVER = '312'
+RPL_WHOISOPERATOR = '313'
+RPL_WHOISIDLE = '317'
+RPL_ENDOFWHOIS = '318'
+RPL_WHOISCHANNELS = '319'
+RPL_WHOWASUSER = '314'
+RPL_ENDOFWHOWAS = '369'
+RPL_LISTSTART = '321'
+RPL_LIST = '322'
+RPL_LISTEND = '323'
+RPL_UNIQOPIS = '325'
+RPL_CHANNELMODEIS = '324'
+RPL_NOTOPIC = '331'
+RPL_TOPIC = '332'
+RPL_INVITING = '341'
+RPL_SUMMONING = '342'
+RPL_INVITELIST = '346'
+RPL_ENDOFINVITELIST = '347'
+RPL_EXCEPTLIST = '348'
+RPL_ENDOFEXCEPTLIST = '349'
+RPL_VERSION = '351'
+RPL_WHOREPLY = '352'
+RPL_ENDOFWHO = '315'
+RPL_NAMREPLY = '353'
+RPL_ENDOFNAMES = '366'
+RPL_LINKS = '364'
+RPL_ENDOFLINKS = '365'
+RPL_BANLIST = '367'
+RPL_ENDOFBANLIST = '368'
+RPL_INFO = '371'
+RPL_ENDOFINFO = '374'
+RPL_MOTDSTART = '375'
+RPL_MOTD = '372'
+RPL_ENDOFMOTD = '376'
+RPL_YOUREOPER = '381'
+RPL_REHASHING = '382'
+RPL_YOURESERVICE = '383'
+RPL_TIME = '391'
+RPL_USERSSTART = '392'
+RPL_USERS = '393'
+RPL_ENDOFUSERS = '394'
+RPL_NOUSERS = '395'
+RPL_TRACELINK = '200'
+RPL_TRACECONNECTING = '201'
+RPL_TRACEHANDSHAKE = '202'
+RPL_TRACEUNKNOWN = '203'
+RPL_TRACEOPERATOR = '204'
+RPL_TRACEUSER = '205'
+RPL_TRACESERVER = '206'
+RPL_TRACESERVICE = '207'
+RPL_TRACENEWTYPE = '208'
+RPL_TRACECLASS = '209'
+RPL_TRACERECONNECT = '210'
+RPL_TRACELOG = '261'
+RPL_TRACEEND = '262'
+RPL_STATSLINKINFO = '211'
+RPL_STATSCOMMANDS = '212'
+RPL_ENDOFSTATS = '219'
+RPL_STATSUPTIME = '242'
+RPL_STATSOLINE = '243'
+RPL_UMODEIS = '221'
+RPL_SERVLIST = '234'
+RPL_SERVLISTEND = '235'
+RPL_LUSERCLIENT = '251'
+RPL_LUSEROP = '252'
+RPL_LUSERUNKNOWN = '253'
+RPL_LUSERCHANNELS = '254'
+RPL_LUSERME = '255'
+RPL_ADMINME = '256'
+RPL_ADMINLOC = '257'
+RPL_ADMINLOC = '258'
+RPL_ADMINEMAIL = '259'
+RPL_TRYAGAIN = '263'
+ERR_NOSUCHNICK = '401'
+ERR_NOSUCHSERVER = '402'
+ERR_NOSUCHCHANNEL = '403'
+ERR_CANNOTSENDTOCHAN = '404'
+ERR_TOOMANYCHANNELS = '405'
+ERR_WASNOSUCHNICK = '406'
+ERR_TOOMANYTARGETS = '407'
+ERR_NOSUCHSERVICE = '408'
+ERR_NOORIGIN = '409'
+ERR_NORECIPIENT = '411'
+ERR_NOTEXTTOSEND = '412'
+ERR_NOTOPLEVEL = '413'
+ERR_WILDTOPLEVEL = '414'
+ERR_BADMASK = '415'
+ERR_UNKNOWNCOMMAND = '421'
+ERR_NOMOTD = '422'
+ERR_NOADMININFO = '423'
+ERR_FILEERROR = '424'
+ERR_NONICKNAMEGIVEN = '431'
+ERR_ERRONEUSNICKNAME = '432'
+ERR_NICKNAMEINUSE = '433'
+ERR_NICKCOLLISION = '436'
+ERR_UNAVAILRESOURCE = '437'
+ERR_USERNOTINCHANNEL = '441'
+ERR_NOTONCHANNEL = '442'
+ERR_USERONCHANNEL = '443'
+ERR_NOLOGIN = '444'
+ERR_SUMMONDISABLED = '445'
+ERR_USERSDISABLED = '446'
+ERR_NOTREGISTERED = '451'
+ERR_NEEDMOREPARAMS = '461'
+ERR_ALREADYREGISTRED = '462'
+ERR_NOPERMFORHOST = '463'
+ERR_PASSWDMISMATCH = '464'
+ERR_YOUREBANNEDCREEP = '465'
+ERR_YOUWILLBEBANNED = '466'
+ERR_KEYSET = '467'
+ERR_CHANNELISFULL = '471'
+ERR_UNKNOWNMODE = '472'
+ERR_INVITEONLYCHAN = '473'
+ERR_BANNEDFROMCHAN = '474'
+ERR_BADCHANNELKEY = '475'
+ERR_BADCHANMASK = '476'
+ERR_NOCHANMODES = '477'
+ERR_BANLISTFULL = '478'
+ERR_NOPRIVILEGES = '481'
+ERR_CHANOPRIVSNEEDED = '482'
+ERR_CANTKILLSERVER = '483'
+ERR_RESTRICTED = '484'
+ERR_UNIQOPPRIVSNEEDED = '485'
+ERR_NOOPERHOST = '491'
+ERR_NOSERVICEHOST = '492'
+ERR_UMODEUNKNOWNFLAG = '501'
+ERR_USERSDONTMATCH = '502'
+
+# And hey, as long as the strings are already intern'd...
+symbolic_to_numeric = {
+ "RPL_WELCOME": '001',
+ "RPL_YOURHOST": '002',
+ "RPL_CREATED": '003',
+ "RPL_MYINFO": '004',
+ "RPL_ISUPPORT": '005',
+ "RPL_BOUNCE": '010',
+ "RPL_USERHOST": '302',
+ "RPL_ISON": '303',
+ "RPL_AWAY": '301',
+ "RPL_UNAWAY": '305',
+ "RPL_NOWAWAY": '306',
+ "RPL_WHOISUSER": '311',
+ "RPL_WHOISSERVER": '312',
+ "RPL_WHOISOPERATOR": '313',
+ "RPL_WHOISIDLE": '317',
+ "RPL_ENDOFWHOIS": '318',
+ "RPL_WHOISCHANNELS": '319',
+ "RPL_WHOWASUSER": '314',
+ "RPL_ENDOFWHOWAS": '369',
+ "RPL_LISTSTART": '321',
+ "RPL_LIST": '322',
+ "RPL_LISTEND": '323',
+ "RPL_UNIQOPIS": '325',
+ "RPL_CHANNELMODEIS": '324',
+ "RPL_NOTOPIC": '331',
+ "RPL_TOPIC": '332',
+ "RPL_INVITING": '341',
+ "RPL_SUMMONING": '342',
+ "RPL_INVITELIST": '346',
+ "RPL_ENDOFINVITELIST": '347',
+ "RPL_EXCEPTLIST": '348',
+ "RPL_ENDOFEXCEPTLIST": '349',
+ "RPL_VERSION": '351',
+ "RPL_WHOREPLY": '352',
+ "RPL_ENDOFWHO": '315',
+ "RPL_NAMREPLY": '353',
+ "RPL_ENDOFNAMES": '366',
+ "RPL_LINKS": '364',
+ "RPL_ENDOFLINKS": '365',
+ "RPL_BANLIST": '367',
+ "RPL_ENDOFBANLIST": '368',
+ "RPL_INFO": '371',
+ "RPL_ENDOFINFO": '374',
+ "RPL_MOTDSTART": '375',
+ "RPL_MOTD": '372',
+ "RPL_ENDOFMOTD": '376',
+ "RPL_YOUREOPER": '381',
+ "RPL_REHASHING": '382',
+ "RPL_YOURESERVICE": '383',
+ "RPL_TIME": '391',
+ "RPL_USERSSTART": '392',
+ "RPL_USERS": '393',
+ "RPL_ENDOFUSERS": '394',
+ "RPL_NOUSERS": '395',
+ "RPL_TRACELINK": '200',
+ "RPL_TRACECONNECTING": '201',
+ "RPL_TRACEHANDSHAKE": '202',
+ "RPL_TRACEUNKNOWN": '203',
+ "RPL_TRACEOPERATOR": '204',
+ "RPL_TRACEUSER": '205',
+ "RPL_TRACESERVER": '206',
+ "RPL_TRACESERVICE": '207',
+ "RPL_TRACENEWTYPE": '208',
+ "RPL_TRACECLASS": '209',
+ "RPL_TRACERECONNECT": '210',
+ "RPL_TRACELOG": '261',
+ "RPL_TRACEEND": '262',
+ "RPL_STATSLINKINFO": '211',
+ "RPL_STATSCOMMANDS": '212',
+ "RPL_ENDOFSTATS": '219',
+ "RPL_STATSUPTIME": '242',
+ "RPL_STATSOLINE": '243',
+ "RPL_UMODEIS": '221',
+ "RPL_SERVLIST": '234',
+ "RPL_SERVLISTEND": '235',
+ "RPL_LUSERCLIENT": '251',
+ "RPL_LUSEROP": '252',
+ "RPL_LUSERUNKNOWN": '253',
+ "RPL_LUSERCHANNELS": '254',
+ "RPL_LUSERME": '255',
+ "RPL_ADMINME": '256',
+ "RPL_ADMINLOC": '257',
+ "RPL_ADMINLOC": '258',
+ "RPL_ADMINEMAIL": '259',
+ "RPL_TRYAGAIN": '263',
+ "ERR_NOSUCHNICK": '401',
+ "ERR_NOSUCHSERVER": '402',
+ "ERR_NOSUCHCHANNEL": '403',
+ "ERR_CANNOTSENDTOCHAN": '404',
+ "ERR_TOOMANYCHANNELS": '405',
+ "ERR_WASNOSUCHNICK": '406',
+ "ERR_TOOMANYTARGETS": '407',
+ "ERR_NOSUCHSERVICE": '408',
+ "ERR_NOORIGIN": '409',
+ "ERR_NORECIPIENT": '411',
+ "ERR_NOTEXTTOSEND": '412',
+ "ERR_NOTOPLEVEL": '413',
+ "ERR_WILDTOPLEVEL": '414',
+ "ERR_BADMASK": '415',
+ "ERR_UNKNOWNCOMMAND": '421',
+ "ERR_NOMOTD": '422',
+ "ERR_NOADMININFO": '423',
+ "ERR_FILEERROR": '424',
+ "ERR_NONICKNAMEGIVEN": '431',
+ "ERR_ERRONEUSNICKNAME": '432',
+ "ERR_NICKNAMEINUSE": '433',
+ "ERR_NICKCOLLISION": '436',
+ "ERR_UNAVAILRESOURCE": '437',
+ "ERR_USERNOTINCHANNEL": '441',
+ "ERR_NOTONCHANNEL": '442',
+ "ERR_USERONCHANNEL": '443',
+ "ERR_NOLOGIN": '444',
+ "ERR_SUMMONDISABLED": '445',
+ "ERR_USERSDISABLED": '446',
+ "ERR_NOTREGISTERED": '451',
+ "ERR_NEEDMOREPARAMS": '461',
+ "ERR_ALREADYREGISTRED": '462',
+ "ERR_NOPERMFORHOST": '463',
+ "ERR_PASSWDMISMATCH": '464',
+ "ERR_YOUREBANNEDCREEP": '465',
+ "ERR_YOUWILLBEBANNED": '466',
+ "ERR_KEYSET": '467',
+ "ERR_CHANNELISFULL": '471',
+ "ERR_UNKNOWNMODE": '472',
+ "ERR_INVITEONLYCHAN": '473',
+ "ERR_BANNEDFROMCHAN": '474',
+ "ERR_BADCHANNELKEY": '475',
+ "ERR_BADCHANMASK": '476',
+ "ERR_NOCHANMODES": '477',
+ "ERR_BANLISTFULL": '478',
+ "ERR_NOPRIVILEGES": '481',
+ "ERR_CHANOPRIVSNEEDED": '482',
+ "ERR_CANTKILLSERVER": '483',
+ "ERR_RESTRICTED": '484',
+ "ERR_UNIQOPPRIVSNEEDED": '485',
+ "ERR_NOOPERHOST": '491',
+ "ERR_NOSERVICEHOST": '492',
+ "ERR_UMODEUNKNOWNFLAG": '501',
+ "ERR_USERSDONTMATCH": '502',
+}
+
+numeric_to_symbolic = {}
+for k, v in symbolic_to_numeric.items():
+ numeric_to_symbolic[v] = k
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/__init__.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/__init__.py
new file mode 100644
index 0000000000..53c765c2cc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/__init__.py
@@ -0,0 +1,8 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Twisted Jabber: Jabber Protocol Helpers
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/client.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/client.py
new file mode 100644
index 0000000000..f26354e919
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/client.py
@@ -0,0 +1,369 @@
+# -*- test-case-name: twisted.words.test.test_jabberclient -*-
+#
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import defer
+from twisted.words.xish import domish, xpath, utility
+from twisted.words.protocols.jabber import xmlstream, sasl, error
+from twisted.words.protocols.jabber.jid import JID
+
+NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams'
+NS_XMPP_BIND = 'urn:ietf:params:xml:ns:xmpp-bind'
+NS_XMPP_SESSION = 'urn:ietf:params:xml:ns:xmpp-session'
+NS_IQ_AUTH_FEATURE = 'http://jabber.org/features/iq-auth'
+
+DigestAuthQry = xpath.internQuery("/iq/query/digest")
+PlaintextAuthQry = xpath.internQuery("/iq/query/password")
+
+def basicClientFactory(jid, secret):
+ a = BasicAuthenticator(jid, secret)
+ return xmlstream.XmlStreamFactory(a)
+
+class IQ(domish.Element):
+ """
+ Wrapper for a Info/Query packet.
+
+ This provides the necessary functionality to send IQs and get notified when
+ a result comes back. It's a subclass from L{domish.Element}, so you can use
+ the standard DOM manipulation calls to add data to the outbound request.
+
+ @type callbacks: L{utility.CallbackList}
+ @cvar callbacks: Callback list to be notified when response comes back
+
+ """
+ def __init__(self, xmlstream, type = "set"):
+ """
+ @type xmlstream: L{xmlstream.XmlStream}
+ @param xmlstream: XmlStream to use for transmission of this IQ
+
+ @type type: L{str}
+ @param type: IQ type identifier ('get' or 'set')
+ """
+
+ domish.Element.__init__(self, ("jabber:client", "iq"))
+ self.addUniqueId()
+ self["type"] = type
+ self._xmlstream = xmlstream
+ self.callbacks = utility.CallbackList()
+
+ def addCallback(self, fn, *args, **kwargs):
+ """
+ Register a callback for notification when the IQ result is available.
+ """
+
+ self.callbacks.addCallback(True, fn, *args, **kwargs)
+
+ def send(self, to = None):
+ """
+ Call this method to send this IQ request via the associated XmlStream.
+
+ @param to: Jabber ID of the entity to send the request to
+ @type to: L{str}
+
+ @returns: Callback list for this IQ. Any callbacks added to this list
+ will be fired when the result comes back.
+ """
+ if to != None:
+ self["to"] = to
+ self._xmlstream.addOnetimeObserver("/iq[@id='%s']" % self["id"], \
+ self._resultEvent)
+ self._xmlstream.send(self)
+
+ def _resultEvent(self, iq):
+ self.callbacks.callback(iq)
+ self.callbacks = None
+
+
+
+class IQAuthInitializer(object):
+ """
+ Non-SASL Authentication initializer for the initiating entity.
+
+ This protocol is defined in
+ U{JEP-0078<http://www.jabber.org/jeps/jep-0078.html>} and mainly serves for
+ compatibility with pre-XMPP-1.0 server implementations.
+ """
+
+ INVALID_USER_EVENT = "//event/client/basicauth/invaliduser"
+ AUTH_FAILED_EVENT = "//event/client/basicauth/authfailed"
+
+ def __init__(self, xs):
+ self.xmlstream = xs
+
+
+ def initialize(self):
+ # Send request for auth fields
+ iq = xmlstream.IQ(self.xmlstream, "get")
+ iq.addElement(("jabber:iq:auth", "query"))
+ jid = self.xmlstream.authenticator.jid
+ iq.query.addElement("username", content = jid.user)
+
+ d = iq.send()
+ d.addCallbacks(self._cbAuthQuery, self._ebAuthQuery)
+ return d
+
+
+ def _cbAuthQuery(self, iq):
+ jid = self.xmlstream.authenticator.jid
+ password = self.xmlstream.authenticator.password
+
+ # Construct auth request
+ reply = xmlstream.IQ(self.xmlstream, "set")
+ reply.addElement(("jabber:iq:auth", "query"))
+ reply.query.addElement("username", content = jid.user)
+ reply.query.addElement("resource", content = jid.resource)
+
+ # Prefer digest over plaintext
+ if DigestAuthQry.matches(iq):
+ digest = xmlstream.hashPassword(self.xmlstream.sid, unicode(password))
+ reply.query.addElement("digest", content = digest)
+ else:
+ reply.query.addElement("password", content = password)
+
+ d = reply.send()
+ d.addCallbacks(self._cbAuth, self._ebAuth)
+ return d
+
+
+ def _ebAuthQuery(self, failure):
+ failure.trap(error.StanzaError)
+ e = failure.value
+ if e.condition == 'not-authorized':
+ self.xmlstream.dispatch(e.stanza, self.INVALID_USER_EVENT)
+ else:
+ self.xmlstream.dispatch(e.stanza, self.AUTH_FAILED_EVENT)
+
+ return failure
+
+
+ def _cbAuth(self, iq):
+ pass
+
+
+ def _ebAuth(self, failure):
+ failure.trap(error.StanzaError)
+ self.xmlstream.dispatch(failure.value.stanza, self.AUTH_FAILED_EVENT)
+ return failure
+
+
+
+class BasicAuthenticator(xmlstream.ConnectAuthenticator):
+ """
+ Authenticates an XmlStream against a Jabber server as a Client.
+
+ This only implements non-SASL authentication, per
+ U{JEP-0078<http://www.jabber.org/jeps/jep-0078.html>}. Additionally, this
+ authenticator provides the ability to perform inline registration, per
+ U{JEP-0077<http://www.jabber.org/jeps/jep-0077.html>}.
+
+ Under normal circumstances, the BasicAuthenticator generates the
+ L{xmlstream.STREAM_AUTHD_EVENT} once the stream has authenticated. However,
+ it can also generate other events, such as:
+ - L{INVALID_USER_EVENT} : Authentication failed, due to invalid username
+ - L{AUTH_FAILED_EVENT} : Authentication failed, due to invalid password
+ - L{REGISTER_FAILED_EVENT} : Registration failed
+
+ If authentication fails for any reason, you can attempt to register by
+ calling the L{registerAccount} method. If the registration succeeds, a
+ L{xmlstream.STREAM_AUTHD_EVENT} will be fired. Otherwise, one of the above
+ errors will be generated (again).
+ """
+
+ namespace = "jabber:client"
+
+ INVALID_USER_EVENT = IQAuthInitializer.INVALID_USER_EVENT
+ AUTH_FAILED_EVENT = IQAuthInitializer.AUTH_FAILED_EVENT
+ REGISTER_FAILED_EVENT = "//event/client/basicauth/registerfailed"
+
+ def __init__(self, jid, password):
+ xmlstream.ConnectAuthenticator.__init__(self, jid.host)
+ self.jid = jid
+ self.password = password
+
+ def associateWithStream(self, xs):
+ xs.version = (0, 0)
+ xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
+
+ inits = [ (xmlstream.TLSInitiatingInitializer, False),
+ (IQAuthInitializer, True),
+ ]
+
+ for initClass, required in inits:
+ init = initClass(xs)
+ init.required = required
+ xs.initializers.append(init)
+
+ # TODO: move registration into an Initializer?
+
+ def registerAccount(self, username = None, password = None):
+ if username:
+ self.jid.user = username
+ if password:
+ self.password = password
+
+ iq = IQ(self.xmlstream, "set")
+ iq.addElement(("jabber:iq:register", "query"))
+ iq.query.addElement("username", content = self.jid.user)
+ iq.query.addElement("password", content = self.password)
+
+ iq.addCallback(self._registerResultEvent)
+
+ iq.send()
+
+ def _registerResultEvent(self, iq):
+ if iq["type"] == "result":
+ # Registration succeeded -- go ahead and auth
+ self.streamStarted()
+ else:
+ # Registration failed
+ self.xmlstream.dispatch(iq, self.REGISTER_FAILED_EVENT)
+
+
+
+class CheckVersionInitializer(object):
+ """
+ Initializer that checks if the minimum common stream version number is 1.0.
+ """
+
+ def __init__(self, xs):
+ self.xmlstream = xs
+
+
+ def initialize(self):
+ if self.xmlstream.version < (1, 0):
+ raise error.StreamError('unsupported-version')
+
+
+
+class BindInitializer(xmlstream.BaseFeatureInitiatingInitializer):
+ """
+ Initializer that implements Resource Binding for the initiating entity.
+
+ This protocol is documented in U{RFC 3920, section
+ 7<http://www.xmpp.org/specs/rfc3920.html#bind>}.
+ """
+
+ feature = (NS_XMPP_BIND, 'bind')
+
+ def start(self):
+ iq = xmlstream.IQ(self.xmlstream, 'set')
+ bind = iq.addElement((NS_XMPP_BIND, 'bind'))
+ resource = self.xmlstream.authenticator.jid.resource
+ if resource:
+ bind.addElement('resource', content=resource)
+ d = iq.send()
+ d.addCallback(self.onBind)
+ return d
+
+
+ def onBind(self, iq):
+ if iq.bind:
+ self.xmlstream.authenticator.jid = JID(unicode(iq.bind.jid))
+
+
+
+class SessionInitializer(xmlstream.BaseFeatureInitiatingInitializer):
+ """
+ Initializer that implements session establishment for the initiating
+ entity.
+
+ This protocol is defined in U{RFC 3921, section
+ 3<http://www.xmpp.org/specs/rfc3921.html#session>}.
+ """
+
+ feature = (NS_XMPP_SESSION, 'session')
+
+ def start(self):
+ iq = xmlstream.IQ(self.xmlstream, 'set')
+ session = iq.addElement((NS_XMPP_SESSION, 'session'))
+ return iq.send()
+
+
+
+def XMPPClientFactory(jid, password):
+ """
+ Client factory for XMPP 1.0 (only).
+
+ This returns a L{xmlstream.XmlStreamFactory} with an L{XMPPAuthenticator}
+ object to perform the stream initialization steps (such as authentication).
+
+ @see: The notes at L{XMPPAuthenticator} describe how the L{jid} and
+ L{password} parameters are to be used.
+
+ @param jid: Jabber ID to connect with.
+ @type jid: L{jid.JID}
+ @param password: password to authenticate with.
+ @type password: L{unicode}
+ @return: XML stream factory.
+ @rtype: L{xmlstream.XmlStreamFactory}
+ """
+ a = XMPPAuthenticator(jid, password)
+ return xmlstream.XmlStreamFactory(a)
+
+
+
+class XMPPAuthenticator(xmlstream.ConnectAuthenticator):
+ """
+ Initializes an XmlStream connecting to an XMPP server as a Client.
+
+ This authenticator performs the initialization steps needed to start
+ exchanging XML stanzas with an XMPP server as an XMPP client. It checks if
+ the server advertises XML stream version 1.0, negotiates TLS (when
+ available), performs SASL authentication, binds a resource and establishes
+ a session.
+
+ Upon successful stream initialization, the L{xmlstream.STREAM_AUTHD_EVENT}
+ event will be dispatched through the XML stream object. Otherwise, the
+ L{xmlstream.INIT_FAILED_EVENT} event will be dispatched with a failure
+ object.
+
+ After inspection of the failure, initialization can then be restarted by
+ calling L{initializeStream}. For example, in case of authentication
+ failure, a user may be given the opportunity to input the correct password.
+ By setting the L{password} instance variable and restarting initialization,
+ the stream authentication step is then retried, and subsequent steps are
+ performed if succesful.
+
+ @ivar jid: Jabber ID to authenticate with. This may contain a resource
+ part, as a suggestion to the server for resource binding. A
+ server may override this, though. If the resource part is left
+ off, the server will generate a unique resource identifier.
+ The server will always return the full Jabber ID in the
+ resource binding step, and this is stored in this instance
+ variable.
+ @type jid: L{jid.JID}
+ @ivar password: password to be used during SASL authentication.
+ @type password: L{unicode}
+ """
+
+ namespace = 'jabber:client'
+
+ def __init__(self, jid, password):
+ xmlstream.ConnectAuthenticator.__init__(self, jid.host)
+ self.jid = jid
+ self.password = password
+
+
+ def associateWithStream(self, xs):
+ """
+ Register with the XML stream.
+
+ Populates stream's list of initializers, along with their
+ requiredness. This list is used by
+ L{ConnectAuthenticator.initializeStream} to perform the initalization
+ steps.
+ """
+ xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
+
+ xs.initializers = [CheckVersionInitializer(xs)]
+ inits = [ (xmlstream.TLSInitiatingInitializer, False),
+ (sasl.SASLInitiatingInitializer, True),
+ (BindInitializer, False),
+ (SessionInitializer, False),
+ ]
+
+ for initClass, required in inits:
+ init = initClass(xs)
+ init.required = required
+ xs.initializers.append(init)
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/component.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/component.py
new file mode 100644
index 0000000000..8df5bf7845
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/component.py
@@ -0,0 +1,474 @@
+# -*- test-case-name: twisted.words.test.test_jabbercomponent -*-
+#
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+External server-side components.
+
+Most Jabber server implementations allow for add-on components that act as a
+seperate entity on the Jabber network, but use the server-to-server
+functionality of a regular Jabber IM server. These so-called 'external
+components' are connected to the Jabber server using the Jabber Component
+Protocol as defined in U{JEP-0114<http://www.jabber.org/jeps/jep-0114.html>}.
+
+This module allows for writing external server-side component by assigning one
+or more services implementing L{ijabber.IService} up to L{ServiceManager}. The
+ServiceManager connects to the Jabber server and is responsible for the
+corresponding XML stream.
+"""
+
+from zope.interface import implements
+
+from twisted.application import service
+from twisted.internet import defer
+from twisted.python import log
+from twisted.words.xish import domish
+from twisted.words.protocols.jabber import error, ijabber, jstrports, xmlstream
+from twisted.words.protocols.jabber.jid import internJID as JID
+
+NS_COMPONENT_ACCEPT = 'jabber:component:accept'
+
+def componentFactory(componentid, password):
+ """
+ XML stream factory for external server-side components.
+
+ @param componentid: JID of the component.
+ @type componentid: L{unicode}
+ @param password: password used to authenticate to the server.
+ @type password: L{str}
+ """
+ a = ConnectComponentAuthenticator(componentid, password)
+ return xmlstream.XmlStreamFactory(a)
+
+class ComponentInitiatingInitializer(object):
+ """
+ External server-side component authentication initializer for the
+ initiating entity.
+
+ @ivar xmlstream: XML stream between server and component.
+ @type xmlstream: L{xmlstream.XmlStream}
+ """
+
+ def __init__(self, xs):
+ self.xmlstream = xs
+ self._deferred = None
+
+ def initialize(self):
+ xs = self.xmlstream
+ hs = domish.Element((self.xmlstream.namespace, "handshake"))
+ hs.addContent(xmlstream.hashPassword(xs.sid,
+ unicode(xs.authenticator.password)))
+
+ # Setup observer to watch for handshake result
+ xs.addOnetimeObserver("/handshake", self._cbHandshake)
+ xs.send(hs)
+ self._deferred = defer.Deferred()
+ return self._deferred
+
+ def _cbHandshake(self, _):
+ # we have successfully shaken hands and can now consider this
+ # entity to represent the component JID.
+ self.xmlstream.thisEntity = self.xmlstream.otherEntity
+ self._deferred.callback(None)
+
+
+
+class ConnectComponentAuthenticator(xmlstream.ConnectAuthenticator):
+ """
+ Authenticator to permit an XmlStream to authenticate against a Jabber
+ server as an external component (where the Authenticator is initiating the
+ stream).
+ """
+ namespace = NS_COMPONENT_ACCEPT
+
+ def __init__(self, componentjid, password):
+ """
+ @type componentjid: L{str}
+ @param componentjid: Jabber ID that this component wishes to bind to.
+
+ @type password: L{str}
+ @param password: Password/secret this component uses to authenticate.
+ """
+ # Note that we are sending 'to' our desired component JID.
+ xmlstream.ConnectAuthenticator.__init__(self, componentjid)
+ self.password = password
+
+ def associateWithStream(self, xs):
+ xs.version = (0, 0)
+ xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
+
+ xs.initializers = [ComponentInitiatingInitializer(xs)]
+
+
+
+class ListenComponentAuthenticator(xmlstream.ListenAuthenticator):
+ """
+ Authenticator for accepting components.
+
+ @since: 8.2
+ @ivar secret: The shared secret used to authorized incoming component
+ connections.
+ @type secret: C{unicode}.
+ """
+
+ namespace = NS_COMPONENT_ACCEPT
+
+ def __init__(self, secret):
+ self.secret = secret
+ xmlstream.ListenAuthenticator.__init__(self)
+
+
+ def associateWithStream(self, xs):
+ """
+ Associate the authenticator with a stream.
+
+ This sets the stream's version to 0.0, because the XEP-0114 component
+ protocol was not designed for XMPP 1.0.
+ """
+ xs.version = (0, 0)
+ xmlstream.ListenAuthenticator.associateWithStream(self, xs)
+
+
+ def streamStarted(self, rootElement):
+ """
+ Called by the stream when it has started.
+
+ This examines the default namespace of the incoming stream and whether
+ there is a requested hostname for the component. Then it generates a
+ stream identifier, sends a response header and adds an observer for
+ the first incoming element, triggering L{onElement}.
+ """
+
+ xmlstream.ListenAuthenticator.streamStarted(self, rootElement)
+
+ if rootElement.defaultUri != self.namespace:
+ exc = error.StreamError('invalid-namespace')
+ self.xmlstream.sendStreamError(exc)
+ return
+
+ # self.xmlstream.thisEntity is set to the address the component
+ # wants to assume.
+ if not self.xmlstream.thisEntity:
+ exc = error.StreamError('improper-addressing')
+ self.xmlstream.sendStreamError(exc)
+ return
+
+ self.xmlstream.sendHeader()
+ self.xmlstream.addOnetimeObserver('/*', self.onElement)
+
+
+ def onElement(self, element):
+ """
+ Called on incoming XML Stanzas.
+
+ The very first element received should be a request for handshake.
+ Otherwise, the stream is dropped with a 'not-authorized' error. If a
+ handshake request was received, the hash is extracted and passed to
+ L{onHandshake}.
+ """
+ if (element.uri, element.name) == (self.namespace, 'handshake'):
+ self.onHandshake(unicode(element))
+ else:
+ exc = error.StreamError('not-authorized')
+ self.xmlstream.sendStreamError(exc)
+
+
+ def onHandshake(self, handshake):
+ """
+ Called upon receiving the handshake request.
+
+ This checks that the given hash in C{handshake} is equal to a
+ calculated hash, responding with a handshake reply or a stream error.
+ If the handshake was ok, the stream is authorized, and XML Stanzas may
+ be exchanged.
+ """
+ calculatedHash = xmlstream.hashPassword(self.xmlstream.sid,
+ unicode(self.secret))
+ if handshake != calculatedHash:
+ exc = error.StreamError('not-authorized', text='Invalid hash')
+ self.xmlstream.sendStreamError(exc)
+ else:
+ self.xmlstream.send('<handshake/>')
+ self.xmlstream.dispatch(self.xmlstream,
+ xmlstream.STREAM_AUTHD_EVENT)
+
+
+
+class Service(service.Service):
+ """
+ External server-side component service.
+ """
+
+ implements(ijabber.IService)
+
+ def componentConnected(self, xs):
+ pass
+
+ def componentDisconnected(self):
+ pass
+
+ def transportConnected(self, xs):
+ pass
+
+ def send(self, obj):
+ """
+ Send data over service parent's XML stream.
+
+ @note: L{ServiceManager} maintains a queue for data sent using this
+ method when there is no current established XML stream. This data is
+ then sent as soon as a new stream has been established and initialized.
+ Subsequently, L{componentConnected} will be called again. If this
+ queueing is not desired, use C{send} on the XmlStream object (passed to
+ L{componentConnected}) directly.
+
+ @param obj: data to be sent over the XML stream. This is usually an
+ object providing L{domish.IElement}, or serialized XML. See
+ L{xmlstream.XmlStream} for details.
+ """
+
+ self.parent.send(obj)
+
+class ServiceManager(service.MultiService):
+ """
+ Business logic representing a managed component connection to a Jabber
+ router.
+
+ This service maintains a single connection to a Jabber router and provides
+ facilities for packet routing and transmission. Business logic modules are
+ services implementing L{ijabber.IService} (like subclasses of L{Service}), and
+ added as sub-service.
+ """
+
+ def __init__(self, jid, password):
+ service.MultiService.__init__(self)
+
+ # Setup defaults
+ self.jabberId = jid
+ self.xmlstream = None
+
+ # Internal buffer of packets
+ self._packetQueue = []
+
+ # Setup the xmlstream factory
+ self._xsFactory = componentFactory(self.jabberId, password)
+
+ # Register some lambda functions to keep the self.xmlstream var up to
+ # date
+ self._xsFactory.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT,
+ self._connected)
+ self._xsFactory.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self._authd)
+ self._xsFactory.addBootstrap(xmlstream.STREAM_END_EVENT,
+ self._disconnected)
+
+ # Map addBootstrap and removeBootstrap to the underlying factory -- is
+ # this right? I have no clue...but it'll work for now, until i can
+ # think about it more.
+ self.addBootstrap = self._xsFactory.addBootstrap
+ self.removeBootstrap = self._xsFactory.removeBootstrap
+
+ def getFactory(self):
+ return self._xsFactory
+
+ def _connected(self, xs):
+ self.xmlstream = xs
+ for c in self:
+ if ijabber.IService.providedBy(c):
+ c.transportConnected(xs)
+
+ def _authd(self, xs):
+ # Flush all pending packets
+ for p in self._packetQueue:
+ self.xmlstream.send(p)
+ self._packetQueue = []
+
+ # Notify all child services which implement the IService interface
+ for c in self:
+ if ijabber.IService.providedBy(c):
+ c.componentConnected(xs)
+
+ def _disconnected(self, _):
+ self.xmlstream = None
+
+ # Notify all child services which implement
+ # the IService interface
+ for c in self:
+ if ijabber.IService.providedBy(c):
+ c.componentDisconnected()
+
+ def send(self, obj):
+ """
+ Send data over the XML stream.
+
+ When there is no established XML stream, the data is queued and sent
+ out when a new XML stream has been established and initialized.
+
+ @param obj: data to be sent over the XML stream. This is usually an
+ object providing L{domish.IElement}, or serialized XML. See
+ L{xmlstream.XmlStream} for details.
+ """
+
+ if self.xmlstream != None:
+ self.xmlstream.send(obj)
+ else:
+ self._packetQueue.append(obj)
+
+def buildServiceManager(jid, password, strport):
+ """
+ Constructs a pre-built L{ServiceManager}, using the specified strport
+ string.
+ """
+
+ svc = ServiceManager(jid, password)
+ client_svc = jstrports.client(strport, svc.getFactory())
+ client_svc.setServiceParent(svc)
+ return svc
+
+
+
+class Router(object):
+ """
+ XMPP Server's Router.
+
+ A router connects the different components of the XMPP service and routes
+ messages between them based on the given routing table.
+
+ Connected components are trusted to have correct addressing in the
+ stanzas they offer for routing.
+
+ A route destination of C{None} adds a default route. Traffic for which no
+ specific route exists, will be routed to this default route.
+
+ @since: 8.2
+ @ivar routes: Routes based on the host part of JIDs. Maps host names to the
+ L{EventDispatcher<utility.EventDispatcher>}s that should
+ receive the traffic. A key of C{None} means the default
+ route.
+ @type routes: C{dict}
+ """
+
+ def __init__(self):
+ self.routes = {}
+
+
+ def addRoute(self, destination, xs):
+ """
+ Add a new route.
+
+ The passed XML Stream C{xs} will have an observer for all stanzas
+ added to route its outgoing traffic. In turn, traffic for
+ C{destination} will be passed to this stream.
+
+ @param destination: Destination of the route to be added as a host name
+ or C{None} for the default route.
+ @type destination: C{str} or C{NoneType}.
+ @param xs: XML Stream to register the route for.
+ @type xs: L{EventDispatcher<utility.EventDispatcher>}.
+ """
+ self.routes[destination] = xs
+ xs.addObserver('/*', self.route)
+
+
+ def removeRoute(self, destination, xs):
+ """
+ Remove a route.
+
+ @param destination: Destination of the route that should be removed.
+ @type destination: C{str}.
+ @param xs: XML Stream to remove the route for.
+ @type xs: L{EventDispatcher<utility.EventDispatcher>}.
+ """
+ xs.removeObserver('/*', self.route)
+ if (xs == self.routes[destination]):
+ del self.routes[destination]
+
+
+ def route(self, stanza):
+ """
+ Route a stanza.
+
+ @param stanza: The stanza to be routed.
+ @type stanza: L{domish.Element}.
+ """
+ destination = JID(stanza['to'])
+
+ log.msg("Routing to %s: %r" % (destination.full(), stanza.toXml()))
+
+ if destination.host in self.routes:
+ self.routes[destination.host].send(stanza)
+ else:
+ self.routes[None].send(stanza)
+
+
+
+class XMPPComponentServerFactory(xmlstream.XmlStreamServerFactory):
+ """
+ XMPP Component Server factory.
+
+ This factory accepts XMPP external component connections and makes
+ the router service route traffic for a component's bound domain
+ to that component.
+
+ @since: 8.2
+ """
+
+ logTraffic = False
+
+ def __init__(self, router, secret='secret'):
+ self.router = router
+ self.secret = secret
+
+ def authenticatorFactory():
+ return ListenComponentAuthenticator(self.secret)
+
+ xmlstream.XmlStreamServerFactory.__init__(self, authenticatorFactory)
+ self.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT,
+ self.onConnectionMade)
+ self.addBootstrap(xmlstream.STREAM_AUTHD_EVENT,
+ self.onAuthenticated)
+
+ self.serial = 0
+
+
+ def onConnectionMade(self, xs):
+ """
+ Called when a component connection was made.
+
+ This enables traffic debugging on incoming streams.
+ """
+ xs.serial = self.serial
+ self.serial += 1
+
+ def logDataIn(buf):
+ log.msg("RECV (%d): %r" % (xs.serial, buf))
+
+ def logDataOut(buf):
+ log.msg("SEND (%d): %r" % (xs.serial, buf))
+
+ if self.logTraffic:
+ xs.rawDataInFn = logDataIn
+ xs.rawDataOutFn = logDataOut
+
+ xs.addObserver(xmlstream.STREAM_ERROR_EVENT, self.onError)
+
+
+ def onAuthenticated(self, xs):
+ """
+ Called when a component has succesfully authenticated.
+
+ Add the component to the routing table and establish a handler
+ for a closed connection.
+ """
+ destination = xs.thisEntity.host
+
+ self.router.addRoute(destination, xs)
+ xs.addObserver(xmlstream.STREAM_END_EVENT, self.onConnectionLost, 0,
+ destination, xs)
+
+
+ def onError(self, reason):
+ log.err(reason, "Stream Error")
+
+
+ def onConnectionLost(self, destination, xs, reason):
+ self.router.removeRoute(destination, xs)
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/error.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/error.py
new file mode 100644
index 0000000000..64fbe284d9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/error.py
@@ -0,0 +1,336 @@
+# -*- test-case-name: twisted.words.test.test_jabbererror -*-
+#
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+XMPP Error support.
+"""
+
+import copy
+
+from twisted.words.xish import domish
+
+NS_XML = "http://www.w3.org/XML/1998/namespace"
+NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams"
+NS_XMPP_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"
+
+STANZA_CONDITIONS = {
+ 'bad-request': {'code': '400', 'type': 'modify'},
+ 'conflict': {'code': '409', 'type': 'cancel'},
+ 'feature-not-implemented': {'code': '501', 'type': 'cancel'},
+ 'forbidden': {'code': '403', 'type': 'auth'},
+ 'gone': {'code': '302', 'type': 'modify'},
+ 'internal-server-error': {'code': '500', 'type': 'wait'},
+ 'item-not-found': {'code': '404', 'type': 'cancel'},
+ 'jid-malformed': {'code': '400', 'type': 'modify'},
+ 'not-acceptable': {'code': '406', 'type': 'modify'},
+ 'not-allowed': {'code': '405', 'type': 'cancel'},
+ 'not-authorized': {'code': '401', 'type': 'auth'},
+ 'payment-required': {'code': '402', 'type': 'auth'},
+ 'recipient-unavailable': {'code': '404', 'type': 'wait'},
+ 'redirect': {'code': '302', 'type': 'modify'},
+ 'registration-required': {'code': '407', 'type': 'auth'},
+ 'remote-server-not-found': {'code': '404', 'type': 'cancel'},
+ 'remove-server-timeout': {'code': '504', 'type': 'wait'},
+ 'resource-constraint': {'code': '500', 'type': 'wait'},
+ 'service-unavailable': {'code': '503', 'type': 'cancel'},
+ 'subscription-required': {'code': '407', 'type': 'auth'},
+ 'undefined-condition': {'code': '500', 'type': None},
+ 'unexpected-request': {'code': '400', 'type': 'wait'},
+}
+
+CODES_TO_CONDITIONS = {
+ '302': ('gone', 'modify'),
+ '400': ('bad-request', 'modify'),
+ '401': ('not-authorized', 'auth'),
+ '402': ('payment-required', 'auth'),
+ '403': ('forbidden', 'auth'),
+ '404': ('item-not-found', 'cancel'),
+ '405': ('not-allowed', 'cancel'),
+ '406': ('not-acceptable', 'modify'),
+ '407': ('registration-required', 'auth'),
+ '408': ('remote-server-timeout', 'wait'),
+ '409': ('conflict', 'cancel'),
+ '500': ('internal-server-error', 'wait'),
+ '501': ('feature-not-implemented', 'cancel'),
+ '502': ('service-unavailable', 'wait'),
+ '503': ('service-unavailable', 'cancel'),
+ '504': ('remote-server-timeout', 'wait'),
+ '510': ('service-unavailable', 'cancel'),
+}
+
+class BaseError(Exception):
+ """
+ Base class for XMPP error exceptions.
+
+ @cvar namespace: The namespace of the C{error} element generated by
+ C{getElement}.
+ @type namespace: C{str}
+ @ivar condition: The error condition. The valid values are defined by
+ subclasses of L{BaseError}.
+ @type contition: C{str}
+ @ivar text: Optional text message to supplement the condition or application
+ specific condition.
+ @type text: C{unicode}
+ @ivar textLang: Identifier of the language used for the message in C{text}.
+ Values are as described in RFC 3066.
+ @type textLang: C{str}
+ @ivar appCondition: Application specific condition element, supplementing
+ the error condition in C{condition}.
+ @type appCondition: object providing L{domish.IElement}.
+ """
+
+ namespace = None
+
+ def __init__(self, condition, text=None, textLang=None, appCondition=None):
+ Exception.__init__(self)
+ self.condition = condition
+ self.text = text
+ self.textLang = textLang
+ self.appCondition = appCondition
+
+
+ def __str__(self):
+ message = "%s with condition %r" % (self.__class__.__name__,
+ self.condition)
+
+ if self.text:
+ message += ': ' + self.text
+
+ return message
+
+
+ def getElement(self):
+ """
+ Get XML representation from self.
+
+ The method creates an L{domish} representation of the
+ error data contained in this exception.
+
+ @rtype: L{domish.Element}
+ """
+ error = domish.Element((None, 'error'))
+ error.addElement((self.namespace, self.condition))
+ if self.text:
+ text = error.addElement((self.namespace, 'text'),
+ content=self.text)
+ if self.textLang:
+ text[(NS_XML, 'lang')] = self.textLang
+ if self.appCondition:
+ error.addChild(self.appCondition)
+ return error
+
+
+
+class StreamError(BaseError):
+ """
+ Stream Error exception.
+
+ Refer to RFC 3920, section 4.7.3, for the allowed values for C{condition}.
+ """
+
+ namespace = NS_XMPP_STREAMS
+
+ def getElement(self):
+ """
+ Get XML representation from self.
+
+ Overrides the base L{BaseError.getElement} to make sure the returned
+ element is in the XML Stream namespace.
+
+ @rtype: L{domish.Element}
+ """
+ from twisted.words.protocols.jabber.xmlstream import NS_STREAMS
+
+ error = BaseError.getElement(self)
+ error.uri = NS_STREAMS
+ return error
+
+
+
+class StanzaError(BaseError):
+ """
+ Stanza Error exception.
+
+ Refer to RFC 3920, section 9.3, for the allowed values for C{condition} and
+ C{type}.
+
+ @ivar type: The stanza error type. Gives a suggestion to the recipient
+ of the error on how to proceed.
+ @type type: C{str}
+ @ivar code: A numeric identifier for the error condition for backwards
+ compatibility with pre-XMPP Jabber implementations.
+ """
+
+ namespace = NS_XMPP_STANZAS
+
+ def __init__(self, condition, type=None, text=None, textLang=None,
+ appCondition=None):
+ BaseError.__init__(self, condition, text, textLang, appCondition)
+
+ if type is None:
+ try:
+ type = STANZA_CONDITIONS[condition]['type']
+ except KeyError:
+ pass
+ self.type = type
+
+ try:
+ self.code = STANZA_CONDITIONS[condition]['code']
+ except KeyError:
+ self.code = None
+
+ self.children = []
+ self.iq = None
+
+
+ def getElement(self):
+ """
+ Get XML representation from self.
+
+ Overrides the base L{BaseError.getElement} to make sure the returned
+ element has a C{type} attribute and optionally a legacy C{code}
+ attribute.
+
+ @rtype: L{domish.Element}
+ """
+ error = BaseError.getElement(self)
+ error['type'] = self.type
+ if self.code:
+ error['code'] = self.code
+ return error
+
+
+ def toResponse(self, stanza):
+ """
+ Construct error response stanza.
+
+ The C{stanza} is transformed into an error response stanza by
+ swapping the C{to} and C{from} addresses and inserting an error
+ element.
+
+ @note: This creates a shallow copy of the list of child elements of the
+ stanza. The child elements themselves are not copied themselves,
+ and references to their parent element will still point to the
+ original stanza element.
+
+ The serialization of an element does not use the reference to
+ its parent, so the typical use case of immediately sending out
+ the constructed error response is not affected.
+
+ @param stanza: the stanza to respond to
+ @type stanza: L{domish.Element}
+ """
+ from twisted.words.protocols.jabber.xmlstream import toResponse
+ response = toResponse(stanza, stanzaType='error')
+ response.children = copy.copy(stanza.children)
+ response.addChild(self.getElement())
+ return response
+
+
+def _getText(element):
+ for child in element.children:
+ if isinstance(child, basestring):
+ return unicode(child)
+
+ return None
+
+
+
+def _parseError(error, errorNamespace):
+ """
+ Parses an error element.
+
+ @param error: The error element to be parsed
+ @type error: L{domish.Element}
+ @param errorNamespace: The namespace of the elements that hold the error
+ condition and text.
+ @type errorNamespace: C{str}
+ @return: Dictionary with extracted error information. If present, keys
+ C{condition}, C{text}, C{textLang} have a string value,
+ and C{appCondition} has an L{domish.Element} value.
+ @rtype: L{dict}
+ """
+ condition = None
+ text = None
+ textLang = None
+ appCondition = None
+
+ for element in error.elements():
+ if element.uri == errorNamespace:
+ if element.name == 'text':
+ text = _getText(element)
+ textLang = element.getAttribute((NS_XML, 'lang'))
+ else:
+ condition = element.name
+ else:
+ appCondition = element
+
+ return {
+ 'condition': condition,
+ 'text': text,
+ 'textLang': textLang,
+ 'appCondition': appCondition,
+ }
+
+
+
+def exceptionFromStreamError(element):
+ """
+ Build an exception object from a stream error.
+
+ @param element: the stream error
+ @type element: L{domish.Element}
+ @return: the generated exception object
+ @rtype: L{StreamError}
+ """
+ error = _parseError(element, NS_XMPP_STREAMS)
+
+ exception = StreamError(error['condition'],
+ error['text'],
+ error['textLang'],
+ error['appCondition'])
+
+ return exception
+
+
+
+def exceptionFromStanza(stanza):
+ """
+ Build an exception object from an error stanza.
+
+ @param stanza: the error stanza
+ @type stanza: L{domish.Element}
+ @return: the generated exception object
+ @rtype: L{StanzaError}
+ """
+ children = []
+ condition = text = textLang = appCondition = type = code = None
+
+ for element in stanza.elements():
+ if element.name == 'error' and element.uri == stanza.uri:
+ code = element.getAttribute('code')
+ type = element.getAttribute('type')
+ error = _parseError(element, NS_XMPP_STANZAS)
+ condition = error['condition']
+ text = error['text']
+ textLang = error['textLang']
+ appCondition = error['appCondition']
+
+ if not condition and code:
+ condition, type = CODES_TO_CONDITIONS[code]
+ text = _getText(stanza.error)
+ else:
+ children.append(element)
+
+ if condition is None:
+ # TODO: raise exception instead?
+ return StanzaError(None)
+
+ exception = StanzaError(condition, type, text, textLang, appCondition)
+
+ exception.children = children
+ exception.stanza = stanza
+
+ return exception
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/ijabber.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/ijabber.py
new file mode 100644
index 0000000000..1e50179b41
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/ijabber.py
@@ -0,0 +1,199 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Public Jabber Interfaces.
+"""
+
+from zope.interface import Attribute, Interface
+
+class IInitializer(Interface):
+ """
+ Interface for XML stream initializers.
+
+ Initializers perform a step in getting the XML stream ready to be
+ used for the exchange of XML stanzas.
+ """
+
+
+
+class IInitiatingInitializer(IInitializer):
+ """
+ Interface for XML stream initializers for the initiating entity.
+ """
+
+ xmlstream = Attribute("""The associated XML stream""")
+
+ def initialize():
+ """
+ Initiate the initialization step.
+
+ May return a deferred when the initialization is done asynchronously.
+ """
+
+
+
+class IIQResponseTracker(Interface):
+ """
+ IQ response tracker interface.
+
+ The XMPP stanza C{iq} has a request-response nature that fits
+ naturally with deferreds. You send out a request and when the response
+ comes back a deferred is fired.
+
+ The L{IQ} class implements a C{send} method that returns a deferred. This
+ deferred is put in a dictionary that is kept in an L{XmlStream} object,
+ keyed by the request stanzas C{id} attribute.
+
+ An object providing this interface (usually an instance of L{XmlStream}),
+ keeps the said dictionary and sets observers on the iq stanzas of type
+ C{result} and C{error} and lets the callback fire the associated deferred.
+ """
+ iqDeferreds = Attribute("Dictionary of deferreds waiting for an iq "
+ "response")
+
+
+
+class IXMPPHandler(Interface):
+ """
+ Interface for XMPP protocol handlers.
+
+ Objects that provide this interface can be added to a stream manager to
+ handle of (part of) an XMPP extension protocol.
+ """
+
+ parent = Attribute("""XML stream manager for this handler""")
+ xmlstream = Attribute("""The managed XML stream""")
+
+ def setHandlerParent(parent):
+ """
+ Set the parent of the handler.
+
+ @type parent: L{IXMPPHandlerCollection}
+ """
+
+
+ def disownHandlerParent(parent):
+ """
+ Remove the parent of the handler.
+
+ @type parent: L{IXMPPHandlerCollection}
+ """
+
+
+ def makeConnection(xs):
+ """
+ A connection over the underlying transport of the XML stream has been
+ established.
+
+ At this point, no traffic has been exchanged over the XML stream
+ given in C{xs}.
+
+ This should setup L{xmlstream} and call L{connectionMade}.
+
+ @type xs: L{XmlStream<twisted.words.protocols.jabber.XmlStream>}
+ """
+
+
+ def connectionMade():
+ """
+ Called after a connection has been established.
+
+ This method can be used to change properties of the XML Stream, its
+ authenticator or the stream manager prior to stream initialization
+ (including authentication).
+ """
+
+
+ def connectionInitialized():
+ """
+ The XML stream has been initialized.
+
+ At this point, authentication was successful, and XML stanzas can be
+ exchanged over the XML stream L{xmlstream}. This method can be
+ used to setup observers for incoming stanzas.
+ """
+
+
+ def connectionLost(reason):
+ """
+ The XML stream has been closed.
+
+ Subsequent use of L{parent.send} will result in data being queued
+ until a new connection has been established.
+
+ @type reason: L{twisted.python.failure.Failure}
+ """
+
+
+
+class IXMPPHandlerCollection(Interface):
+ """
+ Collection of handlers.
+
+ Contain several handlers and manage their connection.
+ """
+
+ def __iter__():
+ """
+ Get an iterator over all child handlers.
+ """
+
+
+ def addHandler(handler):
+ """
+ Add a child handler.
+
+ @type handler: L{IXMPPHandler}
+ """
+
+
+ def removeHandler(handler):
+ """
+ Remove a child handler.
+
+ @type handler: L{IXMPPHandler}
+ """
+
+
+
+class IService(Interface):
+ """
+ External server-side component service interface.
+
+ Services that provide this interface can be added to L{ServiceManager} to
+ implement (part of) the functionality of the server-side component.
+ """
+
+ def componentConnected(xs):
+ """
+ Parent component has established a connection.
+
+ At this point, authentication was succesful, and XML stanzas
+ can be exchanged over the XML stream L{xs}. This method can be used
+ to setup observers for incoming stanzas.
+
+ @param xs: XML Stream that represents the established connection.
+ @type xs: L{xmlstream.XmlStream}
+ """
+
+
+ def componentDisconnected():
+ """
+ Parent component has lost the connection to the Jabber server.
+
+ Subsequent use of C{self.parent.send} will result in data being
+ queued until a new connection has been established.
+ """
+
+
+ def transportConnected(xs):
+ """
+ Parent component has established a connection over the underlying
+ transport.
+
+ At this point, no traffic has been exchanged over the XML stream. This
+ method can be used to change properties of the XML Stream (in L{xs}),
+ the service manager or it's authenticator prior to stream
+ initialization (including authentication).
+ """
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/jid.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/jid.py
new file mode 100644
index 0000000000..2604685c6f
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/jid.py
@@ -0,0 +1,249 @@
+# -*- test-case-name: twisted.words.test.test_jabberjid -*-
+#
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Jabber Identifier support.
+
+This module provides an object to represent Jabber Identifiers (JIDs) and
+parse string representations into them with proper checking for illegal
+characters, case folding and canonicalisation through L{stringprep<twisted.words.protocols.jabber.xmpp_stringprep>}.
+"""
+
+from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep, resourceprep, nameprep
+
+class InvalidFormat(Exception):
+ """
+ The given string could not be parsed into a valid Jabber Identifier (JID).
+ """
+
+def parse(jidstring):
+ """
+ Parse given JID string into its respective parts and apply stringprep.
+
+ @param jidstring: string representation of a JID.
+ @type jidstring: C{unicode}
+ @return: tuple of (user, host, resource), each of type C{unicode} as
+ the parsed and stringprep'd parts of the given JID. If the
+ given string did not have a user or resource part, the respective
+ field in the tuple will hold C{None}.
+ @rtype: C{tuple}
+ """
+ user = None
+ host = None
+ resource = None
+
+ # Search for delimiters
+ user_sep = jidstring.find("@")
+ res_sep = jidstring.find("/")
+
+ if user_sep == -1:
+ if res_sep == -1:
+ # host
+ host = jidstring
+ else:
+ # host/resource
+ host = jidstring[0:res_sep]
+ resource = jidstring[res_sep + 1:] or None
+ else:
+ if res_sep == -1:
+ # user@host
+ user = jidstring[0:user_sep] or None
+ host = jidstring[user_sep + 1:]
+ else:
+ if user_sep < res_sep:
+ # user@host/resource
+ user = jidstring[0:user_sep] or None
+ host = jidstring[user_sep + 1:user_sep + (res_sep - user_sep)]
+ resource = jidstring[res_sep + 1:] or None
+ else:
+ # host/resource (with an @ in resource)
+ host = jidstring[0:res_sep]
+ resource = jidstring[res_sep + 1:] or None
+
+ return prep(user, host, resource)
+
+def prep(user, host, resource):
+ """
+ Perform stringprep on all JID fragments.
+
+ @param user: The user part of the JID.
+ @type user: C{unicode}
+ @param host: The host part of the JID.
+ @type host: C{unicode}
+ @param resource: The resource part of the JID.
+ @type resource: C{unicode}
+ @return: The given parts with stringprep applied.
+ @rtype: C{tuple}
+ """
+
+ if user:
+ try:
+ user = nodeprep.prepare(unicode(user))
+ except UnicodeError:
+ raise InvalidFormat, "Invalid character in username"
+ else:
+ user = None
+
+ if not host:
+ raise InvalidFormat, "Server address required."
+ else:
+ try:
+ host = nameprep.prepare(unicode(host))
+ except UnicodeError:
+ raise InvalidFormat, "Invalid character in hostname"
+
+ if resource:
+ try:
+ resource = resourceprep.prepare(unicode(resource))
+ except UnicodeError:
+ raise InvalidFormat, "Invalid character in resource"
+ else:
+ resource = None
+
+ return (user, host, resource)
+
+__internJIDs = {}
+
+def internJID(jidstring):
+ """
+ Return interned JID.
+
+ @rtype: L{JID}
+ """
+
+ if jidstring in __internJIDs:
+ return __internJIDs[jidstring]
+ else:
+ j = JID(jidstring)
+ __internJIDs[jidstring] = j
+ return j
+
+class JID(object):
+ """
+ Represents a stringprep'd Jabber ID.
+
+ JID objects are hashable so they can be used in sets and as keys in
+ dictionaries.
+ """
+
+ def __init__(self, str=None, tuple=None):
+ if not (str or tuple):
+ raise RuntimeError("You must provide a value for either 'str' or "
+ "'tuple' arguments.")
+
+ if str:
+ user, host, res = parse(str)
+ else:
+ user, host, res = prep(*tuple)
+
+ self.user = user
+ self.host = host
+ self.resource = res
+
+ def userhost(self):
+ """
+ Extract the bare JID as a unicode string.
+
+ A bare JID does not have a resource part, so this returns either
+ C{user@host} or just C{host}.
+
+ @rtype: C{unicode}
+ """
+ if self.user:
+ return u"%s@%s" % (self.user, self.host)
+ else:
+ return self.host
+
+ def userhostJID(self):
+ """
+ Extract the bare JID.
+
+ A bare JID does not have a resource part, so this returns a
+ L{JID} object representing either C{user@host} or just C{host}.
+
+ If the object this method is called upon doesn't have a resource
+ set, it will return itself. Otherwise, the bare JID object will
+ be created, interned using L{internJID}.
+
+ @rtype: L{JID}
+ """
+ if self.resource:
+ return internJID(self.userhost())
+ else:
+ return self
+
+ def full(self):
+ """
+ Return the string representation of this JID.
+
+ @rtype: C{unicode}
+ """
+ if self.user:
+ if self.resource:
+ return u"%s@%s/%s" % (self.user, self.host, self.resource)
+ else:
+ return u"%s@%s" % (self.user, self.host)
+ else:
+ if self.resource:
+ return u"%s/%s" % (self.host, self.resource)
+ else:
+ return self.host
+
+ def __eq__(self, other):
+ """
+ Equality comparison.
+
+ L{JID}s compare equal if their user, host and resource parts all
+ compare equal. When comparing against instances of other types, it
+ uses the default comparison.
+ """
+ if isinstance(other, JID):
+ return (self.user == other.user and
+ self.host == other.host and
+ self.resource == other.resource)
+ else:
+ return NotImplemented
+
+ def __ne__(self, other):
+ """
+ Inequality comparison.
+
+ This negates L{__eq__} for comparison with JIDs and uses the default
+ comparison for other types.
+ """
+ result = self.__eq__(other)
+ if result is NotImplemented:
+ return result
+ else:
+ return not result
+
+ def __hash__(self):
+ """
+ Calculate hash.
+
+ L{JID}s with identical constituent user, host and resource parts have
+ equal hash values. In combination with the comparison defined on JIDs,
+ this allows for using L{JID}s in sets and as dictionary keys.
+ """
+ return hash((self.user, self.host, self.resource))
+
+ def __unicode__(self):
+ """
+ Get unicode representation.
+
+ Return the string representation of this JID as a unicode string.
+ @see: L{full}
+ """
+
+ return self.full()
+
+ def __repr__(self):
+ """
+ Get object representation.
+
+ Returns a string that would create a new JID object that compares equal
+ to this one.
+ """
+ return 'JID(%r)' % self.full()
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/jstrports.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/jstrports.py
new file mode 100644
index 0000000000..dbecbdd9a5
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/jstrports.py
@@ -0,0 +1,31 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+""" A temporary placeholder for client-capable strports, until we
+sufficient use cases get identified """
+
+from twisted.application import strports
+
+def _parseTCPSSL(factory, domain, port):
+ """ For the moment, parse TCP or SSL connections the same """
+ return (domain, int(port), factory), {}
+
+def _parseUNIX(factory, address):
+ return (address, factory), {}
+
+
+_funcs = { "tcp" : _parseTCPSSL,
+ "unix" : _parseUNIX,
+ "ssl" : _parseTCPSSL }
+
+
+def parse(description, factory):
+ args, kw = strports._parse(description)
+ return (args[0].upper(),) + _funcs[args[0]](factory, *args[1:], **kw)
+
+def client(description, factory):
+ from twisted.application import internet
+ name, args, kw = parse(description, factory)
+ return getattr(internet, name + 'Client')(*args, **kw)
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/sasl.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/sasl.py
new file mode 100644
index 0000000000..eb4b6c3750
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/sasl.py
@@ -0,0 +1,243 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+XMPP-specific SASL profile.
+"""
+
+import re
+from twisted.internet import defer
+from twisted.words.protocols.jabber import sasl_mechanisms, xmlstream
+from twisted.words.xish import domish
+
+# The b64decode and b64encode functions from the base64 module are new in
+# Python 2.4. For Python 2.3 compatibility, the legacy interface is used while
+# working around MIMEisms.
+
+try:
+ from base64 import b64decode, b64encode
+except ImportError:
+ import base64
+
+ def b64encode(s):
+ return "".join(base64.encodestring(s).split("\n"))
+
+ b64decode = base64.decodestring
+
+NS_XMPP_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'
+
+def get_mechanisms(xs):
+ """
+ Parse the SASL feature to extract the available mechanism names.
+ """
+ mechanisms = []
+ for element in xs.features[(NS_XMPP_SASL, 'mechanisms')].elements():
+ if element.name == 'mechanism':
+ mechanisms.append(str(element))
+
+ return mechanisms
+
+
+class SASLError(Exception):
+ """
+ SASL base exception.
+ """
+
+
+class SASLNoAcceptableMechanism(SASLError):
+ """
+ The server did not present an acceptable SASL mechanism.
+ """
+
+
+class SASLAuthError(SASLError):
+ """
+ SASL Authentication failed.
+ """
+ def __init__(self, condition=None):
+ self.condition = condition
+
+
+ def __str__(self):
+ return "SASLAuthError with condition %r" % self.condition
+
+
+class SASLIncorrectEncodingError(SASLError):
+ """
+ SASL base64 encoding was incorrect.
+
+ RFC 3920 specifies that any characters not in the base64 alphabet
+ and padding characters present elsewhere than at the end of the string
+ MUST be rejected. See also L{fromBase64}.
+
+ This exception is raised whenever the encoded string does not adhere
+ to these additional restrictions or when the decoding itself fails.
+
+ The recommended behaviour for so-called receiving entities (like servers in
+ client-to-server connections, see RFC 3920 for terminology) is to fail the
+ SASL negotiation with a C{'incorrect-encoding'} condition. For initiating
+ entities, one should assume the receiving entity to be either buggy or
+ malevolent. The stream should be terminated and reconnecting is not
+ advised.
+ """
+
+base64Pattern = re.compile("^[0-9A-Za-z+/]*[0-9A-Za-z+/=]{,2}$")
+
+def fromBase64(s):
+ """
+ Decode base64 encoded string.
+
+ This helper performs regular decoding of a base64 encoded string, but also
+ rejects any characters that are not in the base64 alphabet and padding
+ occurring elsewhere from the last or last two characters, as specified in
+ section 14.9 of RFC 3920. This safeguards against various attack vectors
+ among which the creation of a covert channel that "leaks" information.
+ """
+
+ if base64Pattern.match(s) is None:
+ raise SASLIncorrectEncodingError()
+
+ try:
+ return b64decode(s)
+ except Exception, e:
+ raise SASLIncorrectEncodingError(str(e))
+
+
+
+class SASLInitiatingInitializer(xmlstream.BaseFeatureInitiatingInitializer):
+ """
+ Stream initializer that performs SASL authentication.
+
+ The supported mechanisms by this initializer are C{DIGEST-MD5}, C{PLAIN}
+ and C{ANONYMOUS}. The C{ANONYMOUS} SASL mechanism is used when the JID, set
+ on the authenticator, does not have a localpart (username), requesting an
+ anonymous session where the username is generated by the server.
+ Otherwise, C{DIGEST-MD5} and C{PLAIN} are attempted, in that order.
+ """
+
+ feature = (NS_XMPP_SASL, 'mechanisms')
+ _deferred = None
+
+ def setMechanism(self):
+ """
+ Select and setup authentication mechanism.
+
+ Uses the authenticator's C{jid} and C{password} attribute for the
+ authentication credentials. If no supported SASL mechanisms are
+ advertized by the receiving party, a failing deferred is returned with
+ a L{SASLNoAcceptableMechanism} exception.
+ """
+
+ jid = self.xmlstream.authenticator.jid
+ password = self.xmlstream.authenticator.password
+
+ mechanisms = get_mechanisms(self.xmlstream)
+ if jid.user is not None:
+ if 'DIGEST-MD5' in mechanisms:
+ self.mechanism = sasl_mechanisms.DigestMD5('xmpp', jid.host, None,
+ jid.user, password)
+ elif 'PLAIN' in mechanisms:
+ self.mechanism = sasl_mechanisms.Plain(None, jid.user, password)
+ else:
+ raise SASLNoAcceptableMechanism()
+ else:
+ if 'ANONYMOUS' in mechanisms:
+ self.mechanism = sasl_mechanisms.Anonymous()
+ else:
+ raise SASLNoAcceptableMechanism()
+
+
+ def start(self):
+ """
+ Start SASL authentication exchange.
+ """
+
+ self.setMechanism()
+ self._deferred = defer.Deferred()
+ self.xmlstream.addObserver('/challenge', self.onChallenge)
+ self.xmlstream.addOnetimeObserver('/success', self.onSuccess)
+ self.xmlstream.addOnetimeObserver('/failure', self.onFailure)
+ self.sendAuth(self.mechanism.getInitialResponse())
+ return self._deferred
+
+
+ def sendAuth(self, data=None):
+ """
+ Initiate authentication protocol exchange.
+
+ If an initial client response is given in C{data}, it will be
+ sent along.
+
+ @param data: initial client response.
+ @type data: L{str} or L{None}.
+ """
+
+ auth = domish.Element((NS_XMPP_SASL, 'auth'))
+ auth['mechanism'] = self.mechanism.name
+ if data is not None:
+ auth.addContent(b64encode(data) or '=')
+ self.xmlstream.send(auth)
+
+
+ def sendResponse(self, data=''):
+ """
+ Send response to a challenge.
+
+ @param data: client response.
+ @type data: L{str}.
+ """
+
+ response = domish.Element((NS_XMPP_SASL, 'response'))
+ if data:
+ response.addContent(b64encode(data))
+ self.xmlstream.send(response)
+
+
+ def onChallenge(self, element):
+ """
+ Parse challenge and send response from the mechanism.
+
+ @param element: the challenge protocol element.
+ @type element: L{domish.Element}.
+ """
+
+ try:
+ challenge = fromBase64(str(element))
+ except SASLIncorrectEncodingError:
+ self._deferred.errback()
+ else:
+ self.sendResponse(self.mechanism.getResponse(challenge))
+
+
+ def onSuccess(self, success):
+ """
+ Clean up observers, reset the XML stream and send a new header.
+
+ @param success: the success protocol element. For now unused, but
+ could hold additional data.
+ @type success: L{domish.Element}
+ """
+
+ self.xmlstream.removeObserver('/challenge', self.onChallenge)
+ self.xmlstream.removeObserver('/failure', self.onFailure)
+ self.xmlstream.reset()
+ self.xmlstream.sendHeader()
+ self._deferred.callback(xmlstream.Reset)
+
+
+ def onFailure(self, failure):
+ """
+ Clean up observers, parse the failure and errback the deferred.
+
+ @param failure: the failure protocol element. Holds details on
+ the error condition.
+ @type failure: L{domish.Element}
+ """
+
+ self.xmlstream.removeObserver('/challenge', self.onChallenge)
+ self.xmlstream.removeObserver('/success', self.onSuccess)
+ try:
+ condition = failure.firstChildElement().name
+ except AttributeError:
+ condition = None
+ self._deferred.errback(SASLAuthError(condition))
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/sasl_mechanisms.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/sasl_mechanisms.py
new file mode 100644
index 0000000000..390f6cbe22
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/sasl_mechanisms.py
@@ -0,0 +1,240 @@
+# -*- test-case-name: twisted.words.test.test_jabbersaslmechanisms -*-
+#
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Protocol agnostic implementations of SASL authentication mechanisms.
+"""
+
+import binascii, random, time, os
+
+from zope.interface import Interface, Attribute, implements
+
+from twisted.python.hashlib import md5
+
+class ISASLMechanism(Interface):
+ name = Attribute("""Common name for the SASL Mechanism.""")
+
+ def getInitialResponse():
+ """
+ Get the initial client response, if defined for this mechanism.
+
+ @return: initial client response string.
+ @rtype: L{str}.
+ """
+
+
+ def getResponse(challenge):
+ """
+ Get the response to a server challenge.
+
+ @param challenge: server challenge.
+ @type challenge: L{str}.
+ @return: client response.
+ @rtype: L{str}.
+ """
+
+
+
+class Anonymous(object):
+ """
+ Implements the ANONYMOUS SASL authentication mechanism.
+
+ This mechanism is defined in RFC 2245.
+ """
+ implements(ISASLMechanism)
+ name = 'ANONYMOUS'
+
+ def getInitialResponse(self):
+ return None
+
+
+
+class Plain(object):
+ """
+ Implements the PLAIN SASL authentication mechanism.
+
+ The PLAIN SASL authentication mechanism is defined in RFC 2595.
+ """
+ implements(ISASLMechanism)
+
+ name = 'PLAIN'
+
+ def __init__(self, authzid, authcid, password):
+ self.authzid = authzid or ''
+ self.authcid = authcid or ''
+ self.password = password or ''
+
+
+ def getInitialResponse(self):
+ return "%s\x00%s\x00%s" % (self.authzid.encode('utf-8'),
+ self.authcid.encode('utf-8'),
+ self.password.encode('utf-8'))
+
+
+
+class DigestMD5(object):
+ """
+ Implements the DIGEST-MD5 SASL authentication mechanism.
+
+ The DIGEST-MD5 SASL authentication mechanism is defined in RFC 2831.
+ """
+ implements(ISASLMechanism)
+
+ name = 'DIGEST-MD5'
+
+ def __init__(self, serv_type, host, serv_name, username, password):
+ self.username = username
+ self.password = password
+ self.defaultRealm = host
+
+ self.digest_uri = '%s/%s' % (serv_type, host)
+ if serv_name is not None:
+ self.digest_uri += '/%s' % serv_name
+
+
+ def getInitialResponse(self):
+ return None
+
+
+ def getResponse(self, challenge):
+ directives = self._parse(challenge)
+
+ # Compat for implementations that do not send this along with
+ # a succesful authentication.
+ if 'rspauth' in directives:
+ return ''
+
+ try:
+ realm = directives['realm']
+ except KeyError:
+ realm = self.defaultRealm
+
+ return self._gen_response(directives['charset'],
+ realm,
+ directives['nonce'])
+
+ def _parse(self, challenge):
+ """
+ Parses the server challenge.
+
+ Splits the challenge into a dictionary of directives with values.
+
+ @return: challenge directives and their values.
+ @rtype: L{dict} of L{str} to L{str}.
+ """
+ s = challenge
+ paramDict = {}
+ cur = 0
+ remainingParams = True
+ while remainingParams:
+ # Parse a param. We can't just split on commas, because there can
+ # be some commas inside (quoted) param values, e.g.:
+ # qop="auth,auth-int"
+
+ middle = s.index("=", cur)
+ name = s[cur:middle].lstrip()
+ middle += 1
+ if s[middle] == '"':
+ middle += 1
+ end = s.index('"', middle)
+ value = s[middle:end]
+ cur = s.find(',', end) + 1
+ if cur == 0:
+ remainingParams = False
+ else:
+ end = s.find(',', middle)
+ if end == -1:
+ value = s[middle:].rstrip()
+ remainingParams = False
+ else:
+ value = s[middle:end].rstrip()
+ cur = end + 1
+ paramDict[name] = value
+
+ for param in ('qop', 'cipher'):
+ if param in paramDict:
+ paramDict[param] = paramDict[param].split(',')
+
+ return paramDict
+
+ def _unparse(self, directives):
+ """
+ Create message string from directives.
+
+ @param directives: dictionary of directives (names to their values).
+ For certain directives, extra quotes are added, as
+ needed.
+ @type directives: L{dict} of L{str} to L{str}
+ @return: message string.
+ @rtype: L{str}.
+ """
+
+ directive_list = []
+ for name, value in directives.iteritems():
+ if name in ('username', 'realm', 'cnonce',
+ 'nonce', 'digest-uri', 'authzid', 'cipher'):
+ directive = '%s="%s"' % (name, value)
+ else:
+ directive = '%s=%s' % (name, value)
+
+ directive_list.append(directive)
+
+ return ','.join(directive_list)
+
+
+ def _gen_response(self, charset, realm, nonce):
+ """
+ Generate response-value.
+
+ Creates a response to a challenge according to section 2.1.2.1 of
+ RFC 2831 using the L{charset}, L{realm} and L{nonce} directives
+ from the challenge.
+ """
+
+ def H(s):
+ return md5(s).digest()
+
+ def HEX(n):
+ return binascii.b2a_hex(n)
+
+ def KD(k, s):
+ return H('%s:%s' % (k, s))
+
+ try:
+ username = self.username.encode(charset)
+ password = self.password.encode(charset)
+ except UnicodeError:
+ # TODO - add error checking
+ raise
+
+ nc = '%08x' % 1 # TODO: support subsequent auth.
+ cnonce = self._gen_nonce()
+ qop = 'auth'
+
+ # TODO - add support for authzid
+ a1 = "%s:%s:%s" % (H("%s:%s:%s" % (username, realm, password)),
+ nonce,
+ cnonce)
+ a2 = "AUTHENTICATE:%s" % self.digest_uri
+
+ response = HEX( KD ( HEX(H(a1)),
+ "%s:%s:%s:%s:%s" % (nonce, nc,
+ cnonce, "auth", HEX(H(a2)))))
+
+ directives = {'username': username,
+ 'realm' : realm,
+ 'nonce' : nonce,
+ 'cnonce' : cnonce,
+ 'nc' : nc,
+ 'qop' : qop,
+ 'digest-uri': self.digest_uri,
+ 'response': response,
+ 'charset': charset}
+
+ return self._unparse(directives)
+
+
+ def _gen_nonce(self):
+ return md5("%s:%s:%s" % (str(random.random()) , str(time.gmtime()),str(os.getpid()))).hexdigest()
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/xmlstream.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/xmlstream.py
new file mode 100644
index 0000000000..b5847b7204
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/xmlstream.py
@@ -0,0 +1,1136 @@
+# -*- test-case-name: twisted.words.test.test_jabberxmlstream -*-
+#
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+XMPP XML Streams
+
+Building blocks for setting up XML Streams, including helping classes for
+doing authentication on either client or server side, and working with XML
+Stanzas.
+"""
+
+from zope.interface import directlyProvides, implements
+
+from twisted.internet import defer, protocol
+from twisted.internet.error import ConnectionLost
+from twisted.python import failure, log, randbytes
+from twisted.python.hashlib import sha1
+from twisted.words.protocols.jabber import error, ijabber, jid
+from twisted.words.xish import domish, xmlstream
+from twisted.words.xish.xmlstream import STREAM_CONNECTED_EVENT
+from twisted.words.xish.xmlstream import STREAM_START_EVENT
+from twisted.words.xish.xmlstream import STREAM_END_EVENT
+from twisted.words.xish.xmlstream import STREAM_ERROR_EVENT
+
+try:
+ from twisted.internet import ssl
+except ImportError:
+ ssl = None
+if ssl and not ssl.supported:
+ ssl = None
+
+STREAM_AUTHD_EVENT = intern("//event/stream/authd")
+INIT_FAILED_EVENT = intern("//event/xmpp/initfailed")
+
+NS_STREAMS = 'http://etherx.jabber.org/streams'
+NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls'
+
+Reset = object()
+
+def hashPassword(sid, password):
+ """
+ Create a SHA1-digest string of a session identifier and password.
+
+ @param sid: The stream session identifier.
+ @type sid: C{unicode}.
+ @param password: The password to be hashed.
+ @type password: C{unicode}.
+ """
+ if not isinstance(sid, unicode):
+ raise TypeError("The session identifier must be a unicode object")
+ if not isinstance(password, unicode):
+ raise TypeError("The password must be a unicode object")
+ input = u"%s%s" % (sid, password)
+ return sha1(input.encode('utf-8')).hexdigest()
+
+
+
+class Authenticator:
+ """
+ Base class for business logic of initializing an XmlStream
+
+ Subclass this object to enable an XmlStream to initialize and authenticate
+ to different types of stream hosts (such as clients, components, etc.).
+
+ Rules:
+ 1. The Authenticator MUST dispatch a L{STREAM_AUTHD_EVENT} when the
+ stream has been completely initialized.
+ 2. The Authenticator SHOULD reset all state information when
+ L{associateWithStream} is called.
+ 3. The Authenticator SHOULD override L{streamStarted}, and start
+ initialization there.
+
+ @type xmlstream: L{XmlStream}
+ @ivar xmlstream: The XmlStream that needs authentication
+
+ @note: the term authenticator is historical. Authenticators perform
+ all steps required to prepare the stream for the exchange
+ of XML stanzas.
+ """
+
+ def __init__(self):
+ self.xmlstream = None
+
+
+ def connectionMade(self):
+ """
+ Called by the XmlStream when the underlying socket connection is
+ in place.
+
+ This allows the Authenticator to send an initial root element, if it's
+ connecting, or wait for an inbound root from the peer if it's accepting
+ the connection.
+
+ Subclasses can use self.xmlstream.send() to send any initial data to
+ the peer.
+ """
+
+
+ def streamStarted(self, rootElement):
+ """
+ Called by the XmlStream when the stream has started.
+
+ A stream is considered to have started when the start tag of the root
+ element has been received.
+
+ This examines L{rootElement} to see if there is a version attribute.
+ If absent, C{0.0} is assumed per RFC 3920. Subsequently, the
+ minimum of the version from the received stream header and the
+ value stored in L{xmlstream} is taken and put back in {xmlstream}.
+
+ Extensions of this method can extract more information from the
+ stream header and perform checks on them, optionally sending
+ stream errors and closing the stream.
+ """
+ if rootElement.hasAttribute("version"):
+ version = rootElement["version"].split(".")
+ try:
+ version = (int(version[0]), int(version[1]))
+ except (IndexError, ValueError):
+ version = (0, 0)
+ else:
+ version = (0, 0)
+
+ self.xmlstream.version = min(self.xmlstream.version, version)
+
+
+ def associateWithStream(self, xmlstream):
+ """
+ Called by the XmlStreamFactory when a connection has been made
+ to the requested peer, and an XmlStream object has been
+ instantiated.
+
+ The default implementation just saves a handle to the new
+ XmlStream.
+
+ @type xmlstream: L{XmlStream}
+ @param xmlstream: The XmlStream that will be passing events to this
+ Authenticator.
+
+ """
+ self.xmlstream = xmlstream
+
+
+
+class ConnectAuthenticator(Authenticator):
+ """
+ Authenticator for initiating entities.
+ """
+
+ namespace = None
+
+ def __init__(self, otherHost):
+ self.otherHost = otherHost
+
+
+ def connectionMade(self):
+ self.xmlstream.namespace = self.namespace
+ self.xmlstream.otherEntity = jid.internJID(self.otherHost)
+ self.xmlstream.sendHeader()
+
+
+ def initializeStream(self):
+ """
+ Perform stream initialization procedures.
+
+ An L{XmlStream} holds a list of initializer objects in its
+ C{initializers} attribute. This method calls these initializers in
+ order and dispatches the C{STREAM_AUTHD_EVENT} event when the list has
+ been successfully processed. Otherwise it dispatches the
+ C{INIT_FAILED_EVENT} event with the failure.
+
+ Initializers may return the special L{Reset} object to halt the
+ initialization processing. It signals that the current initializer was
+ successfully processed, but that the XML Stream has been reset. An
+ example is the TLSInitiatingInitializer.
+ """
+
+ def remove_first(result):
+ self.xmlstream.initializers.pop(0)
+
+ return result
+
+ def do_next(result):
+ """
+ Take the first initializer and process it.
+
+ On success, the initializer is removed from the list and
+ then next initializer will be tried.
+ """
+
+ if result is Reset:
+ return None
+
+ try:
+ init = self.xmlstream.initializers[0]
+ except IndexError:
+ self.xmlstream.dispatch(self.xmlstream, STREAM_AUTHD_EVENT)
+ return None
+ else:
+ d = defer.maybeDeferred(init.initialize)
+ d.addCallback(remove_first)
+ d.addCallback(do_next)
+ return d
+
+ d = defer.succeed(None)
+ d.addCallback(do_next)
+ d.addErrback(self.xmlstream.dispatch, INIT_FAILED_EVENT)
+
+
+ def streamStarted(self, rootElement):
+ """
+ Called by the XmlStream when the stream has started.
+
+ This extends L{Authenticator.streamStarted} to extract further stream
+ headers from L{rootElement}, optionally wait for stream features being
+ received and then call C{initializeStream}.
+ """
+
+ Authenticator.streamStarted(self, rootElement)
+
+ self.xmlstream.sid = rootElement.getAttribute("id")
+
+ if rootElement.hasAttribute("from"):
+ self.xmlstream.otherEntity = jid.internJID(rootElement["from"])
+
+ # Setup observer for stream features, if applicable
+ if self.xmlstream.version >= (1, 0):
+ def onFeatures(element):
+ features = {}
+ for feature in element.elements():
+ features[(feature.uri, feature.name)] = feature
+
+ self.xmlstream.features = features
+ self.initializeStream()
+
+ self.xmlstream.addOnetimeObserver('/features[@xmlns="%s"]' %
+ NS_STREAMS,
+ onFeatures)
+ else:
+ self.initializeStream()
+
+
+
+class ListenAuthenticator(Authenticator):
+ """
+ Authenticator for receiving entities.
+ """
+
+ namespace = None
+
+ def associateWithStream(self, xmlstream):
+ """
+ Called by the XmlStreamFactory when a connection has been made.
+
+ Extend L{Authenticator.associateWithStream} to set the L{XmlStream}
+ to be non-initiating.
+ """
+ Authenticator.associateWithStream(self, xmlstream)
+ self.xmlstream.initiating = False
+
+
+ def streamStarted(self, rootElement):
+ """
+ Called by the XmlStream when the stream has started.
+
+ This extends L{Authenticator.streamStarted} to extract further
+ information from the stream headers from L{rootElement}.
+ """
+ Authenticator.streamStarted(self, rootElement)
+
+ self.xmlstream.namespace = rootElement.defaultUri
+
+ if rootElement.hasAttribute("to"):
+ self.xmlstream.thisEntity = jid.internJID(rootElement["to"])
+
+ self.xmlstream.prefixes = {}
+ for prefix, uri in rootElement.localPrefixes.iteritems():
+ self.xmlstream.prefixes[uri] = prefix
+
+ self.xmlstream.sid = randbytes.secureRandom(8).encode('hex')
+
+
+
+class FeatureNotAdvertized(Exception):
+ """
+ Exception indicating a stream feature was not advertized, while required by
+ the initiating entity.
+ """
+
+
+
+class BaseFeatureInitiatingInitializer(object):
+ """
+ Base class for initializers with a stream feature.
+
+ This assumes the associated XmlStream represents the initiating entity
+ of the connection.
+
+ @cvar feature: tuple of (uri, name) of the stream feature root element.
+ @type feature: tuple of (L{str}, L{str})
+ @ivar required: whether the stream feature is required to be advertized
+ by the receiving entity.
+ @type required: L{bool}
+ """
+
+ implements(ijabber.IInitiatingInitializer)
+
+ feature = None
+ required = False
+
+ def __init__(self, xs):
+ self.xmlstream = xs
+
+
+ def initialize(self):
+ """
+ Initiate the initialization.
+
+ Checks if the receiving entity advertizes the stream feature. If it
+ does, the initialization is started. If it is not advertized, and the
+ C{required} instance variable is L{True}, it raises
+ L{FeatureNotAdvertized}. Otherwise, the initialization silently
+ succeeds.
+ """
+
+ if self.feature in self.xmlstream.features:
+ return self.start()
+ elif self.required:
+ raise FeatureNotAdvertized
+ else:
+ return None
+
+
+ def start(self):
+ """
+ Start the actual initialization.
+
+ May return a deferred for asynchronous initialization.
+ """
+
+
+
+class TLSError(Exception):
+ """
+ TLS base exception.
+ """
+
+
+
+class TLSFailed(TLSError):
+ """
+ Exception indicating failed TLS negotiation
+ """
+
+
+
+class TLSRequired(TLSError):
+ """
+ Exception indicating required TLS negotiation.
+
+ This exception is raised when the receiving entity requires TLS
+ negotiation and the initiating does not desire to negotiate TLS.
+ """
+
+
+
+class TLSNotSupported(TLSError):
+ """
+ Exception indicating missing TLS support.
+
+ This exception is raised when the initiating entity wants and requires to
+ negotiate TLS when the OpenSSL library is not available.
+ """
+
+
+
+class TLSInitiatingInitializer(BaseFeatureInitiatingInitializer):
+ """
+ TLS stream initializer for the initiating entity.
+
+ It is strongly required to include this initializer in the list of
+ initializers for an XMPP stream. By default it will try to negotiate TLS.
+ An XMPP server may indicate that TLS is required. If TLS is not desired,
+ set the C{wanted} attribute to False instead of removing it from the list
+ of initializers, so a proper exception L{TLSRequired} can be raised.
+
+ @cvar wanted: indicates if TLS negotiation is wanted.
+ @type wanted: L{bool}
+ """
+
+ feature = (NS_XMPP_TLS, 'starttls')
+ wanted = True
+ _deferred = None
+
+ def onProceed(self, obj):
+ """
+ Proceed with TLS negotiation and reset the XML stream.
+ """
+
+ self.xmlstream.removeObserver('/failure', self.onFailure)
+ ctx = ssl.CertificateOptions()
+ self.xmlstream.transport.startTLS(ctx)
+ self.xmlstream.reset()
+ self.xmlstream.sendHeader()
+ self._deferred.callback(Reset)
+
+
+ def onFailure(self, obj):
+ self.xmlstream.removeObserver('/proceed', self.onProceed)
+ self._deferred.errback(TLSFailed())
+
+
+ def start(self):
+ """
+ Start TLS negotiation.
+
+ This checks if the receiving entity requires TLS, the SSL library is
+ available and uses the C{required} and C{wanted} instance variables to
+ determine what to do in the various different cases.
+
+ For example, if the SSL library is not available, and wanted and
+ required by the user, it raises an exception. However if it is not
+ required by both parties, initialization silently succeeds, moving
+ on to the next step.
+ """
+ if self.wanted:
+ if ssl is None:
+ if self.required:
+ return defer.fail(TLSNotSupported())
+ else:
+ return defer.succeed(None)
+ else:
+ pass
+ elif self.xmlstream.features[self.feature].required:
+ return defer.fail(TLSRequired())
+ else:
+ return defer.succeed(None)
+
+ self._deferred = defer.Deferred()
+ self.xmlstream.addOnetimeObserver("/proceed", self.onProceed)
+ self.xmlstream.addOnetimeObserver("/failure", self.onFailure)
+ self.xmlstream.send(domish.Element((NS_XMPP_TLS, "starttls")))
+ return self._deferred
+
+
+
+class XmlStream(xmlstream.XmlStream):
+ """
+ XMPP XML Stream protocol handler.
+
+ @ivar version: XML stream version as a tuple (major, minor). Initially,
+ this is set to the minimally supported version. Upon
+ receiving the stream header of the peer, it is set to the
+ minimum of that value and the version on the received
+ header.
+ @type version: (L{int}, L{int})
+ @ivar namespace: default namespace URI for stream
+ @type namespace: L{str}
+ @ivar thisEntity: JID of this entity
+ @type thisEntity: L{JID}
+ @ivar otherEntity: JID of the peer entity
+ @type otherEntity: L{JID}
+ @ivar sid: session identifier
+ @type sid: L{str}
+ @ivar initiating: True if this is the initiating stream
+ @type initiating: L{bool}
+ @ivar features: map of (uri, name) to stream features element received from
+ the receiving entity.
+ @type features: L{dict} of (L{str}, L{str}) to L{domish.Element}.
+ @ivar prefixes: map of URI to prefixes that are to appear on stream
+ header.
+ @type prefixes: L{dict} of L{str} to L{str}
+ @ivar initializers: list of stream initializer objects
+ @type initializers: L{list} of objects that provide L{IInitializer}
+ @ivar authenticator: associated authenticator that uses C{initializers} to
+ initialize the XML stream.
+ """
+
+ version = (1, 0)
+ namespace = 'invalid'
+ thisEntity = None
+ otherEntity = None
+ sid = None
+ initiating = True
+
+ _headerSent = False # True if the stream header has been sent
+
+ def __init__(self, authenticator):
+ xmlstream.XmlStream.__init__(self)
+
+ self.prefixes = {NS_STREAMS: 'stream'}
+ self.authenticator = authenticator
+ self.initializers = []
+ self.features = {}
+
+ # Reset the authenticator
+ authenticator.associateWithStream(self)
+
+
+ def _callLater(self, *args, **kwargs):
+ from twisted.internet import reactor
+ return reactor.callLater(*args, **kwargs)
+
+
+ def reset(self):
+ """
+ Reset XML Stream.
+
+ Resets the XML Parser for incoming data. This is to be used after
+ successfully negotiating a new layer, e.g. TLS and SASL. Note that
+ registered event observers will continue to be in place.
+ """
+ self._headerSent = False
+ self._initializeStream()
+
+
+ def onStreamError(self, errelem):
+ """
+ Called when a stream:error element has been received.
+
+ Dispatches a L{STREAM_ERROR_EVENT} event with the error element to
+ allow for cleanup actions and drops the connection.
+
+ @param errelem: The received error element.
+ @type errelem: L{domish.Element}
+ """
+ self.dispatch(failure.Failure(error.exceptionFromStreamError(errelem)),
+ STREAM_ERROR_EVENT)
+ self.transport.loseConnection()
+
+
+ def sendHeader(self):
+ """
+ Send stream header.
+ """
+ # set up optional extra namespaces
+ localPrefixes = {}
+ for uri, prefix in self.prefixes.iteritems():
+ if uri != NS_STREAMS:
+ localPrefixes[prefix] = uri
+
+ rootElement = domish.Element((NS_STREAMS, 'stream'), self.namespace,
+ localPrefixes=localPrefixes)
+
+ if self.otherEntity:
+ rootElement['to'] = self.otherEntity.userhost()
+
+ if self.thisEntity:
+ rootElement['from'] = self.thisEntity.userhost()
+
+ if not self.initiating and self.sid:
+ rootElement['id'] = self.sid
+
+ if self.version >= (1, 0):
+ rootElement['version'] = "%d.%d" % self.version
+
+ self.send(rootElement.toXml(prefixes=self.prefixes, closeElement=0))
+ self._headerSent = True
+
+
+ def sendFooter(self):
+ """
+ Send stream footer.
+ """
+ self.send('</stream:stream>')
+
+
+ def sendStreamError(self, streamError):
+ """
+ Send stream level error.
+
+ If we are the receiving entity, and haven't sent the header yet,
+ we sent one first.
+
+ After sending the stream error, the stream is closed and the transport
+ connection dropped.
+
+ @param streamError: stream error instance
+ @type streamError: L{error.StreamError}
+ """
+ if not self._headerSent and not self.initiating:
+ self.sendHeader()
+
+ if self._headerSent:
+ self.send(streamError.getElement())
+ self.sendFooter()
+
+ self.transport.loseConnection()
+
+
+ def send(self, obj):
+ """
+ Send data over the stream.
+
+ This overrides L{xmlstream.Xmlstream.send} to use the default namespace
+ of the stream header when serializing L{domish.IElement}s. It is
+ assumed that if you pass an object that provides L{domish.IElement},
+ it represents a direct child of the stream's root element.
+ """
+ if domish.IElement.providedBy(obj):
+ obj = obj.toXml(prefixes=self.prefixes,
+ defaultUri=self.namespace,
+ prefixesInScope=self.prefixes.values())
+
+ xmlstream.XmlStream.send(self, obj)
+
+
+ def connectionMade(self):
+ """
+ Called when a connection is made.
+
+ Notifies the authenticator when a connection has been made.
+ """
+ xmlstream.XmlStream.connectionMade(self)
+ self.authenticator.connectionMade()
+
+
+ def onDocumentStart(self, rootElement):
+ """
+ Called when the stream header has been received.
+
+ Extracts the header's C{id} and C{version} attributes from the root
+ element. The C{id} attribute is stored in our C{sid} attribute and the
+ C{version} attribute is parsed and the minimum of the version we sent
+ and the parsed C{version} attribute is stored as a tuple (major, minor)
+ in this class' C{version} attribute. If no C{version} attribute was
+ present, we assume version 0.0.
+
+ If appropriate (we are the initiating stream and the minimum of our and
+ the other party's version is at least 1.0), a one-time observer is
+ registered for getting the stream features. The registered function is
+ C{onFeatures}.
+
+ Ultimately, the authenticator's C{streamStarted} method will be called.
+
+ @param rootElement: The root element.
+ @type rootElement: L{domish.Element}
+ """
+ xmlstream.XmlStream.onDocumentStart(self, rootElement)
+
+ # Setup observer for stream errors
+ self.addOnetimeObserver("/error[@xmlns='%s']" % NS_STREAMS,
+ self.onStreamError)
+
+ self.authenticator.streamStarted(rootElement)
+
+
+
+class XmlStreamFactory(xmlstream.XmlStreamFactory):
+ """
+ Factory for Jabber XmlStream objects as a reconnecting client.
+
+ Note that this differs from L{xmlstream.XmlStreamFactory} in that
+ it generates Jabber specific L{XmlStream} instances that have
+ authenticators.
+ """
+
+ protocol = XmlStream
+
+ def __init__(self, authenticator):
+ xmlstream.XmlStreamFactory.__init__(self, authenticator)
+ self.authenticator = authenticator
+
+
+
+class XmlStreamServerFactory(xmlstream.BootstrapMixin,
+ protocol.ServerFactory):
+ """
+ Factory for Jabber XmlStream objects as a server.
+
+ @since: 8.2.
+ @ivar authenticatorFactory: Factory callable that takes no arguments, to
+ create a fresh authenticator to be associated
+ with the XmlStream.
+ """
+
+ protocol = XmlStream
+
+ def __init__(self, authenticatorFactory):
+ xmlstream.BootstrapMixin.__init__(self)
+ self.authenticatorFactory = authenticatorFactory
+
+
+ def buildProtocol(self, addr):
+ """
+ Create an instance of XmlStream.
+
+ A new authenticator instance will be created and passed to the new
+ XmlStream. Registered bootstrap event observers are installed as well.
+ """
+ authenticator = self.authenticatorFactory()
+ xs = self.protocol(authenticator)
+ xs.factory = self
+ self.installBootstraps(xs)
+ return xs
+
+
+
+class TimeoutError(Exception):
+ """
+ Exception raised when no IQ response has been received before the
+ configured timeout.
+ """
+
+
+
+def upgradeWithIQResponseTracker(xs):
+ """
+ Enhances an XmlStream for iq response tracking.
+
+ This makes an L{XmlStream} object provide L{IIQResponseTracker}. When a
+ response is an error iq stanza, the deferred has its errback invoked with a
+ failure that holds a L{StanzaException<error.StanzaException>} that is
+ easier to examine.
+ """
+ def callback(iq):
+ """
+ Handle iq response by firing associated deferred.
+ """
+ if getattr(iq, 'handled', False):
+ return
+
+ try:
+ d = xs.iqDeferreds[iq["id"]]
+ except KeyError:
+ pass
+ else:
+ del xs.iqDeferreds[iq["id"]]
+ iq.handled = True
+ if iq['type'] == 'error':
+ d.errback(error.exceptionFromStanza(iq))
+ else:
+ d.callback(iq)
+
+
+ def disconnected(_):
+ """
+ Make sure deferreds do not linger on after disconnect.
+
+ This errbacks all deferreds of iq's for which no response has been
+ received with a L{ConnectionLost} failure. Otherwise, the deferreds
+ will never be fired.
+ """
+ iqDeferreds = xs.iqDeferreds
+ xs.iqDeferreds = {}
+ for d in iqDeferreds.itervalues():
+ d.errback(ConnectionLost())
+
+ xs.iqDeferreds = {}
+ xs.iqDefaultTimeout = getattr(xs, 'iqDefaultTimeout', None)
+ xs.addObserver(xmlstream.STREAM_END_EVENT, disconnected)
+ xs.addObserver('/iq[@type="result"]', callback)
+ xs.addObserver('/iq[@type="error"]', callback)
+ directlyProvides(xs, ijabber.IIQResponseTracker)
+
+
+
+class IQ(domish.Element):
+ """
+ Wrapper for an iq stanza.
+
+ Iq stanzas are used for communications with a request-response behaviour.
+ Each iq request is associated with an XML stream and has its own unique id
+ to be able to track the response.
+
+ @ivar timeout: if set, a timeout period after which the deferred returned
+ by C{send} will have its errback called with a
+ L{TimeoutError} failure.
+ @type timeout: C{float}
+ """
+
+ timeout = None
+
+ def __init__(self, xmlstream, stanzaType="set"):
+ """
+ @type xmlstream: L{xmlstream.XmlStream}
+ @param xmlstream: XmlStream to use for transmission of this IQ
+
+ @type stanzaType: L{str}
+ @param stanzaType: IQ type identifier ('get' or 'set')
+ """
+ domish.Element.__init__(self, (None, "iq"))
+ self.addUniqueId()
+ self["type"] = stanzaType
+ self._xmlstream = xmlstream
+
+
+ def send(self, to=None):
+ """
+ Send out this iq.
+
+ Returns a deferred that is fired when an iq response with the same id
+ is received. Result responses will be passed to the deferred callback.
+ Error responses will be transformed into a
+ L{StanzaError<error.StanzaError>} and result in the errback of the
+ deferred being invoked.
+
+ @rtype: L{defer.Deferred}
+ """
+ if to is not None:
+ self["to"] = to
+
+ if not ijabber.IIQResponseTracker.providedBy(self._xmlstream):
+ upgradeWithIQResponseTracker(self._xmlstream)
+
+ d = defer.Deferred()
+ self._xmlstream.iqDeferreds[self['id']] = d
+
+ timeout = self.timeout or self._xmlstream.iqDefaultTimeout
+ if timeout is not None:
+ def onTimeout():
+ del self._xmlstream.iqDeferreds[self['id']]
+ d.errback(TimeoutError("IQ timed out"))
+
+ call = self._xmlstream._callLater(timeout, onTimeout)
+
+ def cancelTimeout(result):
+ if call.active():
+ call.cancel()
+
+ return result
+
+ d.addBoth(cancelTimeout)
+
+ self._xmlstream.send(self)
+ return d
+
+
+
+def toResponse(stanza, stanzaType=None):
+ """
+ Create a response stanza from another stanza.
+
+ This takes the addressing and id attributes from a stanza to create a (new,
+ empty) response stanza. The addressing attributes are swapped and the id
+ copied. Optionally, the stanza type of the response can be specified.
+
+ @param stanza: the original stanza
+ @type stanza: L{domish.Element}
+ @param stanzaType: optional response stanza type
+ @type stanzaType: C{str}
+ @return: the response stanza.
+ @rtype: L{domish.Element}
+ """
+
+ toAddr = stanza.getAttribute('from')
+ fromAddr = stanza.getAttribute('to')
+ stanzaID = stanza.getAttribute('id')
+
+ response = domish.Element((None, stanza.name))
+ if toAddr:
+ response['to'] = toAddr
+ if fromAddr:
+ response['from'] = fromAddr
+ if stanzaID:
+ response['id'] = stanzaID
+ if stanzaType:
+ response['type'] = stanzaType
+
+ return response
+
+
+
+class XMPPHandler(object):
+ """
+ XMPP protocol handler.
+
+ Classes derived from this class implement (part of) one or more XMPP
+ extension protocols, and are referred to as a subprotocol implementation.
+ """
+
+ implements(ijabber.IXMPPHandler)
+
+ def __init__(self):
+ self.parent = None
+ self.xmlstream = None
+
+
+ def setHandlerParent(self, parent):
+ self.parent = parent
+ self.parent.addHandler(self)
+
+
+ def disownHandlerParent(self, parent):
+ self.parent.removeHandler(self)
+ self.parent = None
+
+
+ def makeConnection(self, xs):
+ self.xmlstream = xs
+ self.connectionMade()
+
+
+ def connectionMade(self):
+ """
+ Called after a connection has been established.
+
+ Can be overridden to perform work before stream initialization.
+ """
+
+
+ def connectionInitialized(self):
+ """
+ The XML stream has been initialized.
+
+ Can be overridden to perform work after stream initialization, e.g. to
+ set up observers and start exchanging XML stanzas.
+ """
+
+
+ def connectionLost(self, reason):
+ """
+ The XML stream has been closed.
+
+ This method can be extended to inspect the C{reason} argument and
+ act on it.
+ """
+ self.xmlstream = None
+
+
+ def send(self, obj):
+ """
+ Send data over the managed XML stream.
+
+ @note: The stream manager maintains a queue for data sent using this
+ method when there is no current initialized XML stream. This
+ data is then sent as soon as a new stream has been established
+ and initialized. Subsequently, L{connectionInitialized} will be
+ called again. If this queueing is not desired, use C{send} on
+ C{self.xmlstream}.
+
+ @param obj: data to be sent over the XML stream. This is usually an
+ object providing L{domish.IElement}, or serialized XML. See
+ L{xmlstream.XmlStream} for details.
+ """
+ self.parent.send(obj)
+
+
+
+class XMPPHandlerCollection(object):
+ """
+ Collection of XMPP subprotocol handlers.
+
+ This allows for grouping of subprotocol handlers, but is not an
+ L{XMPPHandler} itself, so this is not recursive.
+
+ @ivar handlers: List of protocol handlers.
+ @type handlers: L{list} of objects providing
+ L{IXMPPHandler}
+ """
+
+ implements(ijabber.IXMPPHandlerCollection)
+
+ def __init__(self):
+ self.handlers = []
+
+
+ def __iter__(self):
+ """
+ Act as a container for handlers.
+ """
+ return iter(self.handlers)
+
+
+ def addHandler(self, handler):
+ """
+ Add protocol handler.
+
+ Protocol handlers are expected to provide L{ijabber.IXMPPHandler}.
+ """
+ self.handlers.append(handler)
+
+
+ def removeHandler(self, handler):
+ """
+ Remove protocol handler.
+ """
+ self.handlers.remove(handler)
+
+
+
+class StreamManager(XMPPHandlerCollection):
+ """
+ Business logic representing a managed XMPP connection.
+
+ This maintains a single XMPP connection and provides facilities for packet
+ routing and transmission. Business logic modules are objects providing
+ L{ijabber.IXMPPHandler} (like subclasses of L{XMPPHandler}), and added
+ using L{addHandler}.
+
+ @ivar xmlstream: currently managed XML stream
+ @type xmlstream: L{XmlStream}
+ @ivar logTraffic: if true, log all traffic.
+ @type logTraffic: L{bool}
+ @ivar _initialized: Whether the stream represented by L{xmlstream} has
+ been initialized. This is used when caching outgoing
+ stanzas.
+ @type _initialized: C{bool}
+ @ivar _packetQueue: internal buffer of unsent data. See L{send} for details.
+ @type _packetQueue: L{list}
+ """
+
+ logTraffic = False
+
+ def __init__(self, factory):
+ XMPPHandlerCollection.__init__(self)
+ self.xmlstream = None
+ self._packetQueue = []
+ self._initialized = False
+
+ factory.addBootstrap(STREAM_CONNECTED_EVENT, self._connected)
+ factory.addBootstrap(STREAM_AUTHD_EVENT, self._authd)
+ factory.addBootstrap(INIT_FAILED_EVENT, self.initializationFailed)
+ factory.addBootstrap(STREAM_END_EVENT, self._disconnected)
+ self.factory = factory
+
+
+ def addHandler(self, handler):
+ """
+ Add protocol handler.
+
+ When an XML stream has already been established, the handler's
+ C{connectionInitialized} will be called to get it up to speed.
+ """
+ XMPPHandlerCollection.addHandler(self, handler)
+
+ # get protocol handler up to speed when a connection has already
+ # been established
+ if self.xmlstream and self._initialized:
+ handler.makeConnection(self.xmlstream)
+ handler.connectionInitialized()
+
+
+ def _connected(self, xs):
+ """
+ Called when the transport connection has been established.
+
+ Here we optionally set up traffic logging (depending on L{logTraffic})
+ and call each handler's C{makeConnection} method with the L{XmlStream}
+ instance.
+ """
+ def logDataIn(buf):
+ log.msg("RECV: %r" % buf)
+
+ def logDataOut(buf):
+ log.msg("SEND: %r" % buf)
+
+ if self.logTraffic:
+ xs.rawDataInFn = logDataIn
+ xs.rawDataOutFn = logDataOut
+
+ self.xmlstream = xs
+
+ for e in self:
+ e.makeConnection(xs)
+
+
+ def _authd(self, xs):
+ """
+ Called when the stream has been initialized.
+
+ Send out cached stanzas and call each handler's
+ C{connectionInitialized} method.
+ """
+ # Flush all pending packets
+ for p in self._packetQueue:
+ xs.send(p)
+ self._packetQueue = []
+ self._initialized = True
+
+ # Notify all child services which implement
+ # the IService interface
+ for e in self:
+ e.connectionInitialized()
+
+
+ def initializationFailed(self, reason):
+ """
+ Called when stream initialization has failed.
+
+ Stream initialization has halted, with the reason indicated by
+ C{reason}. It may be retried by calling the authenticator's
+ C{initializeStream}. See the respective authenticators for details.
+
+ @param reason: A failure instance indicating why stream initialization
+ failed.
+ @type reason: L{failure.Failure}
+ """
+
+
+ def _disconnected(self, _):
+ """
+ Called when the stream has been closed.
+
+ From this point on, the manager doesn't interact with the
+ L{XmlStream} anymore and notifies each handler that the connection
+ was lost by calling its C{connectionLost} method.
+ """
+ self.xmlstream = None
+ self._initialized = False
+
+ # Notify all child services which implement
+ # the IService interface
+ for e in self:
+ e.connectionLost(None)
+
+
+ def send(self, obj):
+ """
+ Send data over the XML stream.
+
+ When there is no established XML stream, the data is queued and sent
+ out when a new XML stream has been established and initialized.
+
+ @param obj: data to be sent over the XML stream. See
+ L{xmlstream.XmlStream.send} for details.
+ """
+ if self._initialized:
+ self.xmlstream.send(obj)
+ else:
+ self._packetQueue.append(obj)
+
+
+
+__all__ = ['Authenticator', 'BaseFeatureInitiatingInitializer',
+ 'ConnectAuthenticator', 'ConnectionLost', 'FeatureNotAdvertized',
+ 'INIT_FAILED_EVENT', 'IQ', 'ListenAuthenticator', 'NS_STREAMS',
+ 'NS_XMPP_TLS', 'Reset', 'STREAM_AUTHD_EVENT',
+ 'STREAM_CONNECTED_EVENT', 'STREAM_END_EVENT', 'STREAM_ERROR_EVENT',
+ 'STREAM_START_EVENT', 'StreamManager', 'TLSError', 'TLSFailed',
+ 'TLSInitiatingInitializer', 'TLSNotSupported', 'TLSRequired',
+ 'TimeoutError', 'XMPPHandler', 'XMPPHandlerCollection', 'XmlStream',
+ 'XmlStreamFactory', 'XmlStreamServerFactory', 'hashPassword',
+ 'toResponse', 'upgradeWithIQResponseTracker']
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/xmpp_stringprep.py b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/xmpp_stringprep.py
new file mode 100644
index 0000000000..87025fb2ef
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/jabber/xmpp_stringprep.py
@@ -0,0 +1,248 @@
+# -*- test-case-name: twisted.words.test.test_jabberxmppstringprep -*-
+#
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys, warnings
+from zope.interface import Interface, implements
+
+if sys.version_info < (2,3,2):
+ import re
+
+ class IDNA:
+ dots = re.compile(u"[\u002E\u3002\uFF0E\uFF61]")
+ def nameprep(self, label):
+ return label.lower()
+
+ idna = IDNA()
+
+ crippled = True
+
+ warnings.warn("Accented and non-Western Jabber IDs will not be properly "
+ "case-folded with this version of Python, resulting in "
+ "incorrect protocol-level behavior. It is strongly "
+ "recommended you upgrade to Python 2.3.2 or newer if you "
+ "intend to use Twisted's Jabber support.")
+
+else:
+ import stringprep
+ import unicodedata
+ from encodings import idna
+
+ crippled = False
+
+del sys, warnings
+
+class ILookupTable(Interface):
+ """ Interface for character lookup classes. """
+
+ def lookup(c):
+ """ Return whether character is in this table. """
+
+class IMappingTable(Interface):
+ """ Interface for character mapping classes. """
+
+ def map(c):
+ """ Return mapping for character. """
+
+class LookupTableFromFunction:
+
+ implements(ILookupTable)
+
+ def __init__(self, in_table_function):
+ self.lookup = in_table_function
+
+class LookupTable:
+
+ implements(ILookupTable)
+
+ def __init__(self, table):
+ self._table = table
+
+ def lookup(self, c):
+ return c in self._table
+
+class MappingTableFromFunction:
+
+ implements(IMappingTable)
+
+ def __init__(self, map_table_function):
+ self.map = map_table_function
+
+class EmptyMappingTable:
+
+ implements(IMappingTable)
+
+ def __init__(self, in_table_function):
+ self._in_table_function = in_table_function
+
+ def map(self, c):
+ if self._in_table_function(c):
+ return None
+ else:
+ return c
+
+class Profile:
+ def __init__(self, mappings=[], normalize=True, prohibiteds=[],
+ check_unassigneds=True, check_bidi=True):
+ self.mappings = mappings
+ self.normalize = normalize
+ self.prohibiteds = prohibiteds
+ self.do_check_unassigneds = check_unassigneds
+ self.do_check_bidi = check_bidi
+
+ def prepare(self, string):
+ result = self.map(string)
+ if self.normalize:
+ result = unicodedata.normalize("NFKC", result)
+ self.check_prohibiteds(result)
+ if self.do_check_unassigneds:
+ self.check_unassigneds(result)
+ if self.do_check_bidi:
+ self.check_bidirectionals(result)
+ return result
+
+ def map(self, string):
+ result = []
+
+ for c in string:
+ result_c = c
+
+ for mapping in self.mappings:
+ result_c = mapping.map(c)
+ if result_c != c:
+ break
+
+ if result_c is not None:
+ result.append(result_c)
+
+ return u"".join(result)
+
+ def check_prohibiteds(self, string):
+ for c in string:
+ for table in self.prohibiteds:
+ if table.lookup(c):
+ raise UnicodeError, "Invalid character %s" % repr(c)
+
+ def check_unassigneds(self, string):
+ for c in string:
+ if stringprep.in_table_a1(c):
+ raise UnicodeError, "Unassigned code point %s" % repr(c)
+
+ def check_bidirectionals(self, string):
+ found_LCat = False
+ found_RandALCat = False
+
+ for c in string:
+ if stringprep.in_table_d1(c):
+ found_RandALCat = True
+ if stringprep.in_table_d2(c):
+ found_LCat = True
+
+ if found_LCat and found_RandALCat:
+ raise UnicodeError, "Violation of BIDI Requirement 2"
+
+ if found_RandALCat and not (stringprep.in_table_d1(string[0]) and
+ stringprep.in_table_d1(string[-1])):
+ raise UnicodeError, "Violation of BIDI Requirement 3"
+
+
+class NamePrep:
+ """ Implements preparation of internationalized domain names.
+
+ This class implements preparing internationalized domain names using the
+ rules defined in RFC 3491, section 4 (Conversion operations).
+
+ We do not perform step 4 since we deal with unicode representations of
+ domain names and do not convert from or to ASCII representations using
+ punycode encoding. When such a conversion is needed, the L{idna} standard
+ library provides the C{ToUnicode()} and C{ToASCII()} functions. Note that
+ L{idna} itself assumes UseSTD3ASCIIRules to be false.
+
+ The following steps are performed by C{prepare()}:
+
+ - Split the domain name in labels at the dots (RFC 3490, 3.1)
+ - Apply nameprep proper on each label (RFC 3491)
+ - Enforce the restrictions on ASCII characters in host names by
+ assuming STD3ASCIIRules to be true. (STD 3)
+ - Rejoin the labels using the label separator U+002E (full stop).
+
+ """
+
+ # Prohibited characters.
+ prohibiteds = [unichr(n) for n in range(0x00, 0x2c + 1) +
+ range(0x2e, 0x2f + 1) +
+ range(0x3a, 0x40 + 1) +
+ range(0x5b, 0x60 + 1) +
+ range(0x7b, 0x7f + 1) ]
+
+ def prepare(self, string):
+ result = []
+
+ labels = idna.dots.split(string)
+
+ if labels and len(labels[-1]) == 0:
+ trailing_dot = '.'
+ del labels[-1]
+ else:
+ trailing_dot = ''
+
+ for label in labels:
+ result.append(self.nameprep(label))
+
+ return ".".join(result) + trailing_dot
+
+ def check_prohibiteds(self, string):
+ for c in string:
+ if c in self.prohibiteds:
+ raise UnicodeError, "Invalid character %s" % repr(c)
+
+ def nameprep(self, label):
+ label = idna.nameprep(label)
+ self.check_prohibiteds(label)
+ if label[0] == '-':
+ raise UnicodeError, "Invalid leading hyphen-minus"
+ if label[-1] == '-':
+ raise UnicodeError, "Invalid trailing hyphen-minus"
+ return label
+
+if crippled:
+ case_map = MappingTableFromFunction(lambda c: c.lower())
+ nodeprep = Profile(mappings=[case_map],
+ normalize=False,
+ prohibiteds=[LookupTable([u' ', u'"', u'&', u"'", u'/',
+ u':', u'<', u'>', u'@'])],
+ check_unassigneds=False,
+ check_bidi=False)
+
+ resourceprep = Profile(normalize=False,
+ check_unassigneds=False,
+ check_bidi=False)
+
+else:
+ C_11 = LookupTableFromFunction(stringprep.in_table_c11)
+ C_12 = LookupTableFromFunction(stringprep.in_table_c12)
+ C_21 = LookupTableFromFunction(stringprep.in_table_c21)
+ C_22 = LookupTableFromFunction(stringprep.in_table_c22)
+ C_3 = LookupTableFromFunction(stringprep.in_table_c3)
+ C_4 = LookupTableFromFunction(stringprep.in_table_c4)
+ C_5 = LookupTableFromFunction(stringprep.in_table_c5)
+ C_6 = LookupTableFromFunction(stringprep.in_table_c6)
+ C_7 = LookupTableFromFunction(stringprep.in_table_c7)
+ C_8 = LookupTableFromFunction(stringprep.in_table_c8)
+ C_9 = LookupTableFromFunction(stringprep.in_table_c9)
+
+ B_1 = EmptyMappingTable(stringprep.in_table_b1)
+ B_2 = MappingTableFromFunction(stringprep.map_table_b2)
+
+ nodeprep = Profile(mappings=[B_1, B_2],
+ prohibiteds=[C_11, C_12, C_21, C_22,
+ C_3, C_4, C_5, C_6, C_7, C_8, C_9,
+ LookupTable([u'"', u'&', u"'", u'/',
+ u':', u'<', u'>', u'@'])])
+
+ resourceprep = Profile(mappings=[B_1,],
+ prohibiteds=[C_12, C_21, C_22,
+ C_3, C_4, C_5, C_6, C_7, C_8, C_9])
+
+nameprep = NamePrep()
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/msn.py b/vendor/Twisted-10.0.0/twisted/words/protocols/msn.py
new file mode 100644
index 0000000000..cdc5c3c1bd
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/msn.py
@@ -0,0 +1,2449 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+MSNP8 Protocol (client only) - semi-experimental
+
+This module provides support for clients using the MSN Protocol (MSNP8).
+There are basically 3 servers involved in any MSN session:
+
+I{Dispatch server}
+
+The DispatchClient class handles connections to the
+dispatch server, which basically delegates users to a
+suitable notification server.
+
+You will want to subclass this and handle the gotNotificationReferral
+method appropriately.
+
+I{Notification Server}
+
+The NotificationClient class handles connections to the
+notification server, which acts as a session server
+(state updates, message negotiation etc...)
+
+I{Switcboard Server}
+
+The SwitchboardClient handles connections to switchboard
+servers which are used to conduct conversations with other users.
+
+There are also two classes (FileSend and FileReceive) used
+for file transfers.
+
+Clients handle events in two ways.
+
+ - each client request requiring a response will return a Deferred,
+ the callback for same will be fired when the server sends the
+ required response
+ - Events which are not in response to any client request have
+ respective methods which should be overridden and handled in
+ an adequate manner
+
+Most client request callbacks require more than one argument,
+and since Deferreds can only pass the callback one result,
+most of the time the callback argument will be a tuple of
+values (documented in the respective request method).
+To make reading/writing code easier, callbacks can be defined in
+a number of ways to handle this 'cleanly'. One way would be to
+define methods like: def callBack(self, (arg1, arg2, arg)): ...
+another way would be to do something like:
+d.addCallback(lambda result: myCallback(*result)).
+
+If the server sends an error response to a client request,
+the errback of the corresponding Deferred will be called,
+the argument being the corresponding error code.
+
+B{NOTE}:
+Due to the lack of an official spec for MSNP8, extra checking
+than may be deemed necessary often takes place considering the
+server is never 'wrong'. Thus, if gotBadLine (in any of the 3
+main clients) is called, or an MSNProtocolError is raised, it's
+probably a good idea to submit a bug report. ;)
+Use of this module requires that PyOpenSSL is installed.
+
+TODO
+====
+- check message hooks with invalid x-msgsinvite messages.
+- font handling
+- switchboard factory
+
+@author: Sam Jordan
+"""
+
+import types, operator, os
+from random import randint
+from urllib import quote, unquote
+
+from twisted.python import failure, log
+from twisted.python.hashlib import md5
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred
+from twisted.internet.protocol import ClientFactory
+try:
+ from twisted.internet.ssl import ClientContextFactory
+except ImportError:
+ ClientContextFactory = None
+from twisted.protocols.basic import LineReceiver
+from twisted.web.http import HTTPClient
+
+
+MSN_PROTOCOL_VERSION = "MSNP8 CVR0" # protocol version
+MSN_PORT = 1863 # default dispatch server port
+MSN_MAX_MESSAGE = 1664 # max message length
+MSN_CHALLENGE_STR = "Q1P7W2E4J9R8U3S5" # used for server challenges
+MSN_CVR_STR = "0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS" # :(
+
+# auth constants
+LOGIN_SUCCESS = 1
+LOGIN_FAILURE = 2
+LOGIN_REDIRECT = 3
+
+# list constants
+FORWARD_LIST = 1
+ALLOW_LIST = 2
+BLOCK_LIST = 4
+REVERSE_LIST = 8
+
+# phone constants
+HOME_PHONE = "PHH"
+WORK_PHONE = "PHW"
+MOBILE_PHONE = "PHM"
+HAS_PAGER = "MOB"
+
+# status constants
+STATUS_ONLINE = 'NLN'
+STATUS_OFFLINE = 'FLN'
+STATUS_HIDDEN = 'HDN'
+STATUS_IDLE = 'IDL'
+STATUS_AWAY = 'AWY'
+STATUS_BUSY = 'BSY'
+STATUS_BRB = 'BRB'
+STATUS_PHONE = 'PHN'
+STATUS_LUNCH = 'LUN'
+
+CR = "\r"
+LF = "\n"
+
+def checkParamLen(num, expected, cmd, error=None):
+ if error == None:
+ error = "Invalid Number of Parameters for %s" % cmd
+ if num != expected:
+ raise MSNProtocolError, error
+
+def _parseHeader(h, v):
+ """
+ Split a certin number of known
+ header values with the format:
+ field1=val,field2=val,field3=val into
+ a dict mapping fields to values.
+ @param h: the header's key
+ @param v: the header's value as a string
+ """
+
+ if h in ('passporturls','authentication-info','www-authenticate'):
+ v = v.replace('Passport1.4','').lstrip()
+ fields = {}
+ for fieldPair in v.split(','):
+ try:
+ field,value = fieldPair.split('=',1)
+ fields[field.lower()] = value
+ except ValueError:
+ fields[field.lower()] = ''
+ return fields
+ else:
+ return v
+
+def _parsePrimitiveHost(host):
+ # Ho Ho Ho
+ h,p = host.replace('https://','').split('/',1)
+ p = '/' + p
+ return h,p
+
+def _login(userHandle, passwd, nexusServer, cached=0, authData=''):
+ """
+ This function is used internally and should not ever be called
+ directly.
+ """
+ cb = Deferred()
+ def _cb(server, auth):
+ loginFac = ClientFactory()
+ loginFac.protocol = lambda : PassportLogin(cb, userHandle, passwd, server, auth)
+ reactor.connectSSL(_parsePrimitiveHost(server)[0], 443, loginFac, ClientContextFactory())
+
+ if cached:
+ _cb(nexusServer, authData)
+ else:
+ fac = ClientFactory()
+ d = Deferred()
+ d.addCallbacks(_cb, callbackArgs=(authData,))
+ d.addErrback(lambda f: cb.errback(f))
+ fac.protocol = lambda : PassportNexus(d, nexusServer)
+ reactor.connectSSL(_parsePrimitiveHost(nexusServer)[0], 443, fac, ClientContextFactory())
+ return cb
+
+
+class PassportNexus(HTTPClient):
+
+ """
+ Used to obtain the URL of a valid passport
+ login HTTPS server.
+
+ This class is used internally and should
+ not be instantiated directly -- that is,
+ The passport logging in process is handled
+ transparantly by NotificationClient.
+ """
+
+ def __init__(self, deferred, host):
+ self.deferred = deferred
+ self.host, self.path = _parsePrimitiveHost(host)
+
+ def connectionMade(self):
+ HTTPClient.connectionMade(self)
+ self.sendCommand('GET', self.path)
+ self.sendHeader('Host', self.host)
+ self.endHeaders()
+ self.headers = {}
+
+ def handleHeader(self, header, value):
+ h = header.lower()
+ self.headers[h] = _parseHeader(h, value)
+
+ def handleEndHeaders(self):
+ if self.connected:
+ self.transport.loseConnection()
+ if not self.headers.has_key('passporturls') or not self.headers['passporturls'].has_key('dalogin'):
+ self.deferred.errback(failure.Failure(failure.DefaultException("Invalid Nexus Reply")))
+ self.deferred.callback('https://' + self.headers['passporturls']['dalogin'])
+
+ def handleResponse(self, r):
+ pass
+
+class PassportLogin(HTTPClient):
+ """
+ This class is used internally to obtain
+ a login ticket from a passport HTTPS
+ server -- it should not be used directly.
+ """
+
+ _finished = 0
+
+ def __init__(self, deferred, userHandle, passwd, host, authData):
+ self.deferred = deferred
+ self.userHandle = userHandle
+ self.passwd = passwd
+ self.authData = authData
+ self.host, self.path = _parsePrimitiveHost(host)
+
+ def connectionMade(self):
+ self.sendCommand('GET', self.path)
+ self.sendHeader('Authorization', 'Passport1.4 OrgVerb=GET,OrgURL=http://messenger.msn.com,' +
+ 'sign-in=%s,pwd=%s,%s' % (quote(self.userHandle), self.passwd,self.authData))
+ self.sendHeader('Host', self.host)
+ self.endHeaders()
+ self.headers = {}
+
+ def handleHeader(self, header, value):
+ h = header.lower()
+ self.headers[h] = _parseHeader(h, value)
+
+ def handleEndHeaders(self):
+ if self._finished:
+ return
+ self._finished = 1 # I think we need this because of HTTPClient
+ if self.connected:
+ self.transport.loseConnection()
+ authHeader = 'authentication-info'
+ _interHeader = 'www-authenticate'
+ if self.headers.has_key(_interHeader):
+ authHeader = _interHeader
+ try:
+ info = self.headers[authHeader]
+ status = info['da-status']
+ handler = getattr(self, 'login_%s' % (status,), None)
+ if handler:
+ handler(info)
+ else:
+ raise Exception()
+ except Exception, e:
+ self.deferred.errback(failure.Failure(e))
+
+ def handleResponse(self, r):
+ pass
+
+ def login_success(self, info):
+ ticket = info['from-pp']
+ ticket = ticket[1:len(ticket)-1]
+ self.deferred.callback((LOGIN_SUCCESS, ticket))
+
+ def login_failed(self, info):
+ self.deferred.callback((LOGIN_FAILURE, unquote(info['cbtxt'])))
+
+ def login_redir(self, info):
+ self.deferred.callback((LOGIN_REDIRECT, self.headers['location'], self.authData))
+
+
+class MSNProtocolError(Exception):
+ """
+ This Exception is basically used for debugging
+ purposes, as the official MSN server should never
+ send anything _wrong_ and nobody in their right
+ mind would run their B{own} MSN server.
+ If it is raised by default command handlers
+ (handle_BLAH) the error will be logged.
+ """
+ pass
+
+
+class MSNCommandFailed(Exception):
+ """
+ The server said that the command failed.
+ """
+
+ def __init__(self, errorCode):
+ self.errorCode = errorCode
+
+ def __str__(self):
+ return ("Command failed: %s (error code %d)"
+ % (errorCodes[self.errorCode], self.errorCode))
+
+
+class MSNMessage:
+ """
+ I am the class used to represent an 'instant' message.
+
+ @ivar userHandle: The user handle (passport) of the sender
+ (this is only used when receiving a message)
+ @ivar screenName: The screen name of the sender (this is only used
+ when receiving a message)
+ @ivar message: The message
+ @ivar headers: The message headers
+ @type headers: dict
+ @ivar length: The message length (including headers and line endings)
+ @ivar ack: This variable is used to tell the server how to respond
+ once the message has been sent. If set to MESSAGE_ACK
+ (default) the server will respond with an ACK upon receiving
+ the message, if set to MESSAGE_NACK the server will respond
+ with a NACK upon failure to receive the message.
+ If set to MESSAGE_ACK_NONE the server will do nothing.
+ This is relevant for the return value of
+ SwitchboardClient.sendMessage (which will return
+ a Deferred if ack is set to either MESSAGE_ACK or MESSAGE_NACK
+ and will fire when the respective ACK or NACK is received).
+ If set to MESSAGE_ACK_NONE sendMessage will return None.
+ """
+ MESSAGE_ACK = 'A'
+ MESSAGE_NACK = 'N'
+ MESSAGE_ACK_NONE = 'U'
+
+ ack = MESSAGE_ACK
+
+ def __init__(self, length=0, userHandle="", screenName="", message=""):
+ self.userHandle = userHandle
+ self.screenName = screenName
+ self.message = message
+ self.headers = {'MIME-Version' : '1.0', 'Content-Type' : 'text/plain'}
+ self.length = length
+ self.readPos = 0
+
+ def _calcMessageLen(self):
+ """
+ used to calculte the number to send
+ as the message length when sending a message.
+ """
+ return reduce(operator.add, [len(x[0]) + len(x[1]) + 4 for x in self.headers.items()]) + len(self.message) + 2
+
+ def setHeader(self, header, value):
+ """ set the desired header """
+ self.headers[header] = value
+
+ def getHeader(self, header):
+ """
+ get the desired header value
+ @raise KeyError: if no such header exists.
+ """
+ return self.headers[header]
+
+ def hasHeader(self, header):
+ """ check to see if the desired header exists """
+ return self.headers.has_key(header)
+
+ def getMessage(self):
+ """ return the message - not including headers """
+ return self.message
+
+ def setMessage(self, message):
+ """ set the message text """
+ self.message = message
+
+class MSNContact:
+
+ """
+ This class represents a contact (user).
+
+ @ivar userHandle: The contact's user handle (passport).
+ @ivar screenName: The contact's screen name.
+ @ivar groups: A list of all the group IDs which this
+ contact belongs to.
+ @ivar lists: An integer representing the sum of all lists
+ that this contact belongs to.
+ @ivar status: The contact's status code.
+ @type status: str if contact's status is known, None otherwise.
+
+ @ivar homePhone: The contact's home phone number.
+ @type homePhone: str if known, otherwise None.
+ @ivar workPhone: The contact's work phone number.
+ @type workPhone: str if known, otherwise None.
+ @ivar mobilePhone: The contact's mobile phone number.
+ @type mobilePhone: str if known, otherwise None.
+ @ivar hasPager: Whether or not this user has a mobile pager
+ (true=yes, false=no)
+ """
+
+ def __init__(self, userHandle="", screenName="", lists=0, groups=[], status=None):
+ self.userHandle = userHandle
+ self.screenName = screenName
+ self.lists = lists
+ self.groups = [] # if applicable
+ self.status = status # current status
+
+ # phone details
+ self.homePhone = None
+ self.workPhone = None
+ self.mobilePhone = None
+ self.hasPager = None
+
+ def setPhone(self, phoneType, value):
+ """
+ set phone numbers/values for this specific user.
+ for phoneType check the *_PHONE constants and HAS_PAGER
+ """
+
+ t = phoneType.upper()
+ if t == HOME_PHONE:
+ self.homePhone = value
+ elif t == WORK_PHONE:
+ self.workPhone = value
+ elif t == MOBILE_PHONE:
+ self.mobilePhone = value
+ elif t == HAS_PAGER:
+ self.hasPager = value
+ else:
+ raise ValueError, "Invalid Phone Type"
+
+ def addToList(self, listType):
+ """
+ Update the lists attribute to
+ reflect being part of the
+ given list.
+ """
+ self.lists |= listType
+
+ def removeFromList(self, listType):
+ """
+ Update the lists attribute to
+ reflect being removed from the
+ given list.
+ """
+ self.lists ^= listType
+
+class MSNContactList:
+ """
+ This class represents a basic MSN contact list.
+
+ @ivar contacts: All contacts on my various lists
+ @type contacts: dict (mapping user handles to MSNContact objects)
+ @ivar version: The current contact list version (used for list syncing)
+ @ivar groups: a mapping of group ids to group names
+ (groups can only exist on the forward list)
+ @type groups: dict
+
+ B{Note}:
+ This is used only for storage and doesn't effect the
+ server's contact list.
+ """
+
+ def __init__(self):
+ self.contacts = {}
+ self.version = 0
+ self.groups = {}
+ self.autoAdd = 0
+ self.privacy = 0
+
+ def _getContactsFromList(self, listType):
+ """
+ Obtain all contacts which belong
+ to the given list type.
+ """
+ return dict([(uH,obj) for uH,obj in self.contacts.items() if obj.lists & listType])
+
+ def addContact(self, contact):
+ """
+ Add a contact
+ """
+ self.contacts[contact.userHandle] = contact
+
+ def remContact(self, userHandle):
+ """
+ Remove a contact
+ """
+ try:
+ del self.contacts[userHandle]
+ except KeyError:
+ pass
+
+ def getContact(self, userHandle):
+ """
+ Obtain the MSNContact object
+ associated with the given
+ userHandle.
+ @return: the MSNContact object if
+ the user exists, or None.
+ """
+ try:
+ return self.contacts[userHandle]
+ except KeyError:
+ return None
+
+ def getBlockedContacts(self):
+ """
+ Obtain all the contacts on my block list
+ """
+ return self._getContactsFromList(BLOCK_LIST)
+
+ def getAuthorizedContacts(self):
+ """
+ Obtain all the contacts on my auth list.
+ (These are contacts which I have verified
+ can view my state changes).
+ """
+ return self._getContactsFromList(ALLOW_LIST)
+
+ def getReverseContacts(self):
+ """
+ Get all contacts on my reverse list.
+ (These are contacts which have added me
+ to their forward list).
+ """
+ return self._getContactsFromList(REVERSE_LIST)
+
+ def getContacts(self):
+ """
+ Get all contacts on my forward list.
+ (These are the contacts which I have added
+ to my list).
+ """
+ return self._getContactsFromList(FORWARD_LIST)
+
+ def setGroup(self, id, name):
+ """
+ Keep a mapping from the given id
+ to the given name.
+ """
+ self.groups[id] = name
+
+ def remGroup(self, id):
+ """
+ Removed the stored group
+ mapping for the given id.
+ """
+ try:
+ del self.groups[id]
+ except KeyError:
+ pass
+ for c in self.contacts:
+ if id in c.groups:
+ c.groups.remove(id)
+
+
+class MSNEventBase(LineReceiver):
+ """
+ This class provides support for handling / dispatching events and is the
+ base class of the three main client protocols (DispatchClient,
+ NotificationClient, SwitchboardClient)
+ """
+
+ def __init__(self):
+ self.ids = {} # mapping of ids to Deferreds
+ self.currentID = 0
+ self.connected = 0
+ self.setLineMode()
+ self.currentMessage = None
+
+ def connectionLost(self, reason):
+ self.ids = {}
+ self.connected = 0
+
+ def connectionMade(self):
+ self.connected = 1
+
+ def _fireCallback(self, id, *args):
+ """
+ Fire the callback for the given id
+ if one exists and return 1, else return false
+ """
+ if self.ids.has_key(id):
+ self.ids[id][0].callback(args)
+ del self.ids[id]
+ return 1
+ return 0
+
+ def _nextTransactionID(self):
+ """ return a usable transaction ID """
+ self.currentID += 1
+ if self.currentID > 1000:
+ self.currentID = 1
+ return self.currentID
+
+ def _createIDMapping(self, data=None):
+ """
+ return a unique transaction ID that is mapped internally to a
+ deferred .. also store arbitrary data if it is needed
+ """
+ id = self._nextTransactionID()
+ d = Deferred()
+ self.ids[id] = (d, data)
+ return (id, d)
+
+ def checkMessage(self, message):
+ """
+ process received messages to check for file invitations and
+ typing notifications and other control type messages
+ """
+ raise NotImplementedError
+
+ def lineReceived(self, line):
+ if self.currentMessage:
+ self.currentMessage.readPos += len(line+CR+LF)
+ if line == "":
+ self.setRawMode()
+ if self.currentMessage.readPos == self.currentMessage.length:
+ self.rawDataReceived("") # :(
+ return
+ try:
+ header, value = line.split(':')
+ except ValueError:
+ raise MSNProtocolError, "Invalid Message Header"
+ self.currentMessage.setHeader(header, unquote(value).lstrip())
+ return
+ try:
+ cmd, params = line.split(' ', 1)
+ except ValueError:
+ raise MSNProtocolError, "Invalid Message, %s" % repr(line)
+
+ if len(cmd) != 3:
+ raise MSNProtocolError, "Invalid Command, %s" % repr(cmd)
+ if cmd.isdigit():
+ errorCode = int(cmd)
+ id = int(params.split()[0])
+ if id in self.ids:
+ self.ids[id][0].errback(MSNCommandFailed(errorCode))
+ del self.ids[id]
+ return
+ else: # we received an error which doesn't map to a sent command
+ self.gotError(errorCode)
+ return
+
+ handler = getattr(self, "handle_%s" % cmd.upper(), None)
+ if handler:
+ try:
+ handler(params.split())
+ except MSNProtocolError, why:
+ self.gotBadLine(line, why)
+ else:
+ self.handle_UNKNOWN(cmd, params.split())
+
+ def rawDataReceived(self, data):
+ extra = ""
+ self.currentMessage.readPos += len(data)
+ diff = self.currentMessage.readPos - self.currentMessage.length
+ if diff > 0:
+ self.currentMessage.message += data[:-diff]
+ extra = data[-diff:]
+ elif diff == 0:
+ self.currentMessage.message += data
+ else:
+ self.currentMessage += data
+ return
+ del self.currentMessage.readPos
+ m = self.currentMessage
+ self.currentMessage = None
+ self.setLineMode(extra)
+ if not self.checkMessage(m):
+ return
+ self.gotMessage(m)
+
+ ### protocol command handlers - no need to override these.
+
+ def handle_MSG(self, params):
+ checkParamLen(len(params), 3, 'MSG')
+ try:
+ messageLen = int(params[2])
+ except ValueError:
+ raise MSNProtocolError, "Invalid Parameter for MSG length argument"
+ self.currentMessage = MSNMessage(length=messageLen, userHandle=params[0], screenName=unquote(params[1]))
+
+ def handle_UNKNOWN(self, cmd, params):
+ """ implement me in subclasses if you want to handle unknown events """
+ log.msg("Received unknown command (%s), params: %s" % (cmd, params))
+
+ ### callbacks
+
+ def gotMessage(self, message):
+ """
+ called when we receive a message - override in notification
+ and switchboard clients
+ """
+ raise NotImplementedError
+
+ def gotBadLine(self, line, why):
+ """ called when a handler notifies me that this line is broken """
+ log.msg('Error in line: %s (%s)' % (line, why))
+
+ def gotError(self, errorCode):
+ """
+ called when the server sends an error which is not in
+ response to a sent command (ie. it has no matching transaction ID)
+ """
+ log.msg('Error %s' % (errorCodes[errorCode]))
+
+
+
+class DispatchClient(MSNEventBase):
+ """
+ This class provides support for clients connecting to the dispatch server
+ @ivar userHandle: your user handle (passport) needed before connecting.
+ """
+
+ # eventually this may become an attribute of the
+ # factory.
+ userHandle = ""
+
+ def connectionMade(self):
+ MSNEventBase.connectionMade(self)
+ self.sendLine('VER %s %s' % (self._nextTransactionID(), MSN_PROTOCOL_VERSION))
+
+ ### protocol command handlers ( there is no need to override these )
+
+ def handle_VER(self, params):
+ id = self._nextTransactionID()
+ self.sendLine("CVR %s %s %s" % (id, MSN_CVR_STR, self.userHandle))
+
+ def handle_CVR(self, params):
+ self.sendLine("USR %s TWN I %s" % (self._nextTransactionID(), self.userHandle))
+
+ def handle_XFR(self, params):
+ if len(params) < 4:
+ raise MSNProtocolError, "Invalid number of parameters for XFR"
+ id, refType, addr = params[:3]
+ # was addr a host:port pair?
+ try:
+ host, port = addr.split(':')
+ except ValueError:
+ host = addr
+ port = MSN_PORT
+ if refType == "NS":
+ self.gotNotificationReferral(host, int(port))
+
+ ### callbacks
+
+ def gotNotificationReferral(self, host, port):
+ """
+ called when we get a referral to the notification server.
+
+ @param host: the notification server's hostname
+ @param port: the port to connect to
+ """
+ pass
+
+
+class NotificationClient(MSNEventBase):
+ """
+ This class provides support for clients connecting
+ to the notification server.
+ """
+
+ factory = None # sssh pychecker
+
+ def __init__(self, currentID=0):
+ MSNEventBase.__init__(self)
+ self.currentID = currentID
+ self._state = ['DISCONNECTED', {}]
+
+ def _setState(self, state):
+ self._state[0] = state
+
+ def _getState(self):
+ return self._state[0]
+
+ def _getStateData(self, key):
+ return self._state[1][key]
+
+ def _setStateData(self, key, value):
+ self._state[1][key] = value
+
+ def _remStateData(self, *args):
+ for key in args:
+ del self._state[1][key]
+
+ def connectionMade(self):
+ MSNEventBase.connectionMade(self)
+ self._setState('CONNECTED')
+ self.sendLine("VER %s %s" % (self._nextTransactionID(), MSN_PROTOCOL_VERSION))
+
+ def connectionLost(self, reason):
+ self._setState('DISCONNECTED')
+ self._state[1] = {}
+ MSNEventBase.connectionLost(self, reason)
+
+ def checkMessage(self, message):
+ """ hook used for detecting specific notification messages """
+ cTypes = [s.lstrip() for s in message.getHeader('Content-Type').split(';')]
+ if 'text/x-msmsgsprofile' in cTypes:
+ self.gotProfile(message)
+ return 0
+ return 1
+
+ ### protocol command handlers - no need to override these
+
+ def handle_VER(self, params):
+ id = self._nextTransactionID()
+ self.sendLine("CVR %s %s %s" % (id, MSN_CVR_STR, self.factory.userHandle))
+
+ def handle_CVR(self, params):
+ self.sendLine("USR %s TWN I %s" % (self._nextTransactionID(), self.factory.userHandle))
+
+ def handle_USR(self, params):
+ if len(params) != 4 and len(params) != 6:
+ raise MSNProtocolError, "Invalid Number of Parameters for USR"
+
+ mechanism = params[1]
+ if mechanism == "OK":
+ self.loggedIn(params[2], unquote(params[3]), int(params[4]))
+ elif params[2].upper() == "S":
+ # we need to obtain auth from a passport server
+ f = self.factory
+ d = _login(f.userHandle, f.password, f.passportServer, authData=params[3])
+ d.addCallback(self._passportLogin)
+ d.addErrback(self._passportError)
+
+ def _passportLogin(self, result):
+ if result[0] == LOGIN_REDIRECT:
+ d = _login(self.factory.userHandle, self.factory.password,
+ result[1], cached=1, authData=result[2])
+ d.addCallback(self._passportLogin)
+ d.addErrback(self._passportError)
+ elif result[0] == LOGIN_SUCCESS:
+ self.sendLine("USR %s TWN S %s" % (self._nextTransactionID(), result[1]))
+ elif result[0] == LOGIN_FAILURE:
+ self.loginFailure(result[1])
+
+ def _passportError(self, failure):
+ self.loginFailure("Exception while authenticating: %s" % failure)
+
+ def handle_CHG(self, params):
+ checkParamLen(len(params), 3, 'CHG')
+ id = int(params[0])
+ if not self._fireCallback(id, params[1]):
+ self.statusChanged(params[1])
+
+ def handle_ILN(self, params):
+ checkParamLen(len(params), 5, 'ILN')
+ self.gotContactStatus(params[1], params[2], unquote(params[3]))
+
+ def handle_CHL(self, params):
+ checkParamLen(len(params), 2, 'CHL')
+ self.sendLine("QRY %s msmsgs@msnmsgr.com 32" % self._nextTransactionID())
+ self.transport.write(md5(params[1] + MSN_CHALLENGE_STR).hexdigest())
+
+ def handle_QRY(self, params):
+ pass
+
+ def handle_NLN(self, params):
+ checkParamLen(len(params), 4, 'NLN')
+ self.contactStatusChanged(params[0], params[1], unquote(params[2]))
+
+ def handle_FLN(self, params):
+ checkParamLen(len(params), 1, 'FLN')
+ self.contactOffline(params[0])
+
+ def handle_LST(self, params):
+ # support no longer exists for manually
+ # requesting lists - why do I feel cleaner now?
+ if self._getState() != 'SYNC':
+ return
+ contact = MSNContact(userHandle=params[0], screenName=unquote(params[1]),
+ lists=int(params[2]))
+ if contact.lists & FORWARD_LIST:
+ contact.groups.extend(map(int, params[3].split(',')))
+ self._getStateData('list').addContact(contact)
+ self._setStateData('last_contact', contact)
+ sofar = self._getStateData('lst_sofar') + 1
+ if sofar == self._getStateData('lst_reply'):
+ # this is the best place to determine that
+ # a syn realy has finished - msn _may_ send
+ # BPR information for the last contact
+ # which is unfortunate because it means
+ # that the real end of a syn is non-deterministic.
+ # to handle this we'll keep 'last_contact' hanging
+ # around in the state data and update it if we need
+ # to later.
+ self._setState('SESSION')
+ contacts = self._getStateData('list')
+ phone = self._getStateData('phone')
+ id = self._getStateData('synid')
+ self._remStateData('lst_reply', 'lsg_reply', 'lst_sofar', 'phone', 'synid', 'list')
+ self._fireCallback(id, contacts, phone)
+ else:
+ self._setStateData('lst_sofar',sofar)
+
+ def handle_BLP(self, params):
+ # check to see if this is in response to a SYN
+ if self._getState() == 'SYNC':
+ self._getStateData('list').privacy = listCodeToID[params[0].lower()]
+ else:
+ id = int(params[0])
+ self._fireCallback(id, int(params[1]), listCodeToID[params[2].lower()])
+
+ def handle_GTC(self, params):
+ # check to see if this is in response to a SYN
+ if self._getState() == 'SYNC':
+ if params[0].lower() == "a":
+ self._getStateData('list').autoAdd = 0
+ elif params[0].lower() == "n":
+ self._getStateData('list').autoAdd = 1
+ else:
+ raise MSNProtocolError, "Invalid Paramater for GTC" # debug
+ else:
+ id = int(params[0])
+ if params[1].lower() == "a":
+ self._fireCallback(id, 0)
+ elif params[1].lower() == "n":
+ self._fireCallback(id, 1)
+ else:
+ raise MSNProtocolError, "Invalid Paramater for GTC" # debug
+
+ def handle_SYN(self, params):
+ id = int(params[0])
+ if len(params) == 2:
+ self._setState('SESSION')
+ self._fireCallback(id, None, None)
+ else:
+ contacts = MSNContactList()
+ contacts.version = int(params[1])
+ self._setStateData('list', contacts)
+ self._setStateData('lst_reply', int(params[2]))
+ self._setStateData('lsg_reply', int(params[3]))
+ self._setStateData('lst_sofar', 0)
+ self._setStateData('phone', [])
+
+ def handle_LSG(self, params):
+ if self._getState() == 'SYNC':
+ self._getStateData('list').groups[int(params[0])] = unquote(params[1])
+
+ # Please see the comment above the requestListGroups / requestList methods
+ # regarding support for this
+ #
+ #else:
+ # self._getStateData('groups').append((int(params[4]), unquote(params[5])))
+ # if params[3] == params[4]: # this was the last group
+ # self._fireCallback(int(params[0]), self._getStateData('groups'), int(params[1]))
+ # self._remStateData('groups')
+
+ def handle_PRP(self, params):
+ if self._getState() == 'SYNC':
+ self._getStateData('phone').append((params[0], unquote(params[1])))
+ else:
+ self._fireCallback(int(params[0]), int(params[1]), unquote(params[3]))
+
+ def handle_BPR(self, params):
+ numParams = len(params)
+ if numParams == 2: # part of a syn
+ self._getStateData('last_contact').setPhone(params[0], unquote(params[1]))
+ elif numParams == 4:
+ self.gotPhoneNumber(int(params[0]), params[1], params[2], unquote(params[3]))
+
+ def handle_ADG(self, params):
+ checkParamLen(len(params), 5, 'ADG')
+ id = int(params[0])
+ if not self._fireCallback(id, int(params[1]), unquote(params[2]), int(params[3])):
+ raise MSNProtocolError, "ADG response does not match up to a request" # debug
+
+ def handle_RMG(self, params):
+ checkParamLen(len(params), 3, 'RMG')
+ id = int(params[0])
+ if not self._fireCallback(id, int(params[1]), int(params[2])):
+ raise MSNProtocolError, "RMG response does not match up to a request" # debug
+
+ def handle_REG(self, params):
+ checkParamLen(len(params), 5, 'REG')
+ id = int(params[0])
+ if not self._fireCallback(id, int(params[1]), int(params[2]), unquote(params[3])):
+ raise MSNProtocolError, "REG response does not match up to a request" # debug
+
+ def handle_ADD(self, params):
+ numParams = len(params)
+ if numParams < 5 or params[1].upper() not in ('AL','BL','RL','FL'):
+ raise MSNProtocolError, "Invalid Paramaters for ADD" # debug
+ id = int(params[0])
+ listType = params[1].lower()
+ listVer = int(params[2])
+ userHandle = params[3]
+ groupID = None
+ if numParams == 6: # they sent a group id
+ if params[1].upper() != "FL":
+ raise MSNProtocolError, "Only forward list can contain groups" # debug
+ groupID = int(params[5])
+ if not self._fireCallback(id, listCodeToID[listType], userHandle, listVer, groupID):
+ self.userAddedMe(userHandle, unquote(params[4]), listVer)
+
+ def handle_REM(self, params):
+ numParams = len(params)
+ if numParams < 4 or params[1].upper() not in ('AL','BL','FL','RL'):
+ raise MSNProtocolError, "Invalid Paramaters for REM" # debug
+ id = int(params[0])
+ listType = params[1].lower()
+ listVer = int(params[2])
+ userHandle = params[3]
+ groupID = None
+ if numParams == 5:
+ if params[1] != "FL":
+ raise MSNProtocolError, "Only forward list can contain groups" # debug
+ groupID = int(params[4])
+ if not self._fireCallback(id, listCodeToID[listType], userHandle, listVer, groupID):
+ if listType.upper() == "RL":
+ self.userRemovedMe(userHandle, listVer)
+
+ def handle_REA(self, params):
+ checkParamLen(len(params), 4, 'REA')
+ id = int(params[0])
+ self._fireCallback(id, int(params[1]), unquote(params[3]))
+
+ def handle_XFR(self, params):
+ checkParamLen(len(params), 5, 'XFR')
+ id = int(params[0])
+ # check to see if they sent a host/port pair
+ try:
+ host, port = params[2].split(':')
+ except ValueError:
+ host = params[2]
+ port = MSN_PORT
+
+ if not self._fireCallback(id, host, int(port), params[4]):
+ raise MSNProtocolError, "Got XFR (referral) that I didn't ask for .. should this happen?" # debug
+
+ def handle_RNG(self, params):
+ checkParamLen(len(params), 6, 'RNG')
+ # check for host:port pair
+ try:
+ host, port = params[1].split(":")
+ port = int(port)
+ except ValueError:
+ host = params[1]
+ port = MSN_PORT
+ self.gotSwitchboardInvitation(int(params[0]), host, port, params[3], params[4],
+ unquote(params[5]))
+
+ def handle_OUT(self, params):
+ checkParamLen(len(params), 1, 'OUT')
+ if params[0] == "OTH":
+ self.multipleLogin()
+ elif params[0] == "SSD":
+ self.serverGoingDown()
+ else:
+ raise MSNProtocolError, "Invalid Parameters received for OUT" # debug
+
+ # callbacks
+
+ def loggedIn(self, userHandle, screenName, verified):
+ """
+ Called when the client has logged in.
+ The default behaviour of this method is to
+ update the factory with our screenName and
+ to sync the contact list (factory.contacts).
+ When this is complete self.listSynchronized
+ will be called.
+
+ @param userHandle: our userHandle
+ @param screenName: our screenName
+ @param verified: 1 if our passport has been (verified), 0 if not.
+ (i'm not sure of the significace of this)
+ @type verified: int
+ """
+ self.factory.screenName = screenName
+ if not self.factory.contacts:
+ listVersion = 0
+ else:
+ listVersion = self.factory.contacts.version
+ self.syncList(listVersion).addCallback(self.listSynchronized)
+
+ def loginFailure(self, message):
+ """
+ Called when the client fails to login.
+
+ @param message: a message indicating the problem that was encountered
+ """
+ pass
+
+ def gotProfile(self, message):
+ """
+ Called after logging in when the server sends an initial
+ message with MSN/passport specific profile information
+ such as country, number of kids, etc.
+ Check the message headers for the specific values.
+
+ @param message: The profile message
+ """
+ pass
+
+ def listSynchronized(self, *args):
+ """
+ Lists are now synchronized by default upon logging in, this
+ method is called after the synchronization has finished
+ and the factory now has the up-to-date contacts.
+ """
+ pass
+
+ def statusChanged(self, statusCode):
+ """
+ Called when our status changes and it isn't in response to
+ a client command. By default we will update the status
+ attribute of the factory.
+
+ @param statusCode: 3-letter status code
+ """
+ self.factory.status = statusCode
+
+ def gotContactStatus(self, statusCode, userHandle, screenName):
+ """
+ Called after loggin in when the server sends status of online contacts.
+ By default we will update the status attribute of the contact stored
+ on the factory.
+
+ @param statusCode: 3-letter status code
+ @param userHandle: the contact's user handle (passport)
+ @param screenName: the contact's screen name
+ """
+ self.factory.contacts.getContact(userHandle).status = statusCode
+
+ def contactStatusChanged(self, statusCode, userHandle, screenName):
+ """
+ Called when we're notified that a contact's status has changed.
+ By default we will update the status attribute of the contact
+ stored on the factory.
+
+ @param statusCode: 3-letter status code
+ @param userHandle: the contact's user handle (passport)
+ @param screenName: the contact's screen name
+ """
+ self.factory.contacts.getContact(userHandle).status = statusCode
+
+ def contactOffline(self, userHandle):
+ """
+ Called when a contact goes offline. By default this method
+ will update the status attribute of the contact stored
+ on the factory.
+
+ @param userHandle: the contact's user handle
+ """
+ self.factory.contacts.getContact(userHandle).status = STATUS_OFFLINE
+
+ def gotPhoneNumber(self, listVersion, userHandle, phoneType, number):
+ """
+ Called when the server sends us phone details about
+ a specific user (for example after a user is added
+ the server will send their status, phone details etc.
+ By default we will update the list version for the
+ factory's contact list and update the phone details
+ for the specific user.
+
+ @param listVersion: the new list version
+ @param userHandle: the contact's user handle (passport)
+ @param phoneType: the specific phoneType
+ (*_PHONE constants or HAS_PAGER)
+ @param number: the value/phone number.
+ """
+ self.factory.contacts.version = listVersion
+ self.factory.contacts.getContact(userHandle).setPhone(phoneType, number)
+
+ def userAddedMe(self, userHandle, screenName, listVersion):
+ """
+ Called when a user adds me to their list. (ie. they have been added to
+ the reverse list. By default this method will update the version of
+ the factory's contact list -- that is, if the contact already exists
+ it will update the associated lists attribute, otherwise it will create
+ a new MSNContact object and store it.
+
+ @param userHandle: the userHandle of the user
+ @param screenName: the screen name of the user
+ @param listVersion: the new list version
+ @type listVersion: int
+ """
+ self.factory.contacts.version = listVersion
+ c = self.factory.contacts.getContact(userHandle)
+ if not c:
+ c = MSNContact(userHandle=userHandle, screenName=screenName)
+ self.factory.contacts.addContact(c)
+ c.addToList(REVERSE_LIST)
+
+ def userRemovedMe(self, userHandle, listVersion):
+ """
+ Called when a user removes us from their contact list
+ (they are no longer on our reverseContacts list.
+ By default this method will update the version of
+ the factory's contact list -- that is, the user will
+ be removed from the reverse list and if they are no longer
+ part of any lists they will be removed from the contact
+ list entirely.
+
+ @param userHandle: the contact's user handle (passport)
+ @param listVersion: the new list version
+ """
+ self.factory.contacts.version = listVersion
+ c = self.factory.contacts.getContact(userHandle)
+ c.removeFromList(REVERSE_LIST)
+ if c.lists == 0:
+ self.factory.contacts.remContact(c.userHandle)
+
+ def gotSwitchboardInvitation(self, sessionID, host, port,
+ key, userHandle, screenName):
+ """
+ Called when we get an invitation to a switchboard server.
+ This happens when a user requests a chat session with us.
+
+ @param sessionID: session ID number, must be remembered for logging in
+ @param host: the hostname of the switchboard server
+ @param port: the port to connect to
+ @param key: used for authorization when connecting
+ @param userHandle: the user handle of the person who invited us
+ @param screenName: the screen name of the person who invited us
+ """
+ pass
+
+ def multipleLogin(self):
+ """
+ Called when the server says there has been another login
+ under our account, the server should disconnect us right away.
+ """
+ pass
+
+ def serverGoingDown(self):
+ """
+ Called when the server has notified us that it is going down for
+ maintenance.
+ """
+ pass
+
+ # api calls
+
+ def changeStatus(self, status):
+ """
+ Change my current status. This method will add
+ a default callback to the returned Deferred
+ which will update the status attribute of the
+ factory.
+
+ @param status: 3-letter status code (as defined by
+ the STATUS_* constants)
+ @return: A Deferred, the callback of which will be
+ fired when the server confirms the change
+ of status. The callback argument will be
+ a tuple with the new status code as the
+ only element.
+ """
+
+ id, d = self._createIDMapping()
+ self.sendLine("CHG %s %s" % (id, status))
+ def _cb(r):
+ self.factory.status = r[0]
+ return r
+ return d.addCallback(_cb)
+
+ # I am no longer supporting the process of manually requesting
+ # lists or list groups -- as far as I can see this has no use
+ # if lists are synchronized and updated correctly, which they
+ # should be. If someone has a specific justified need for this
+ # then please contact me and i'll re-enable/fix support for it.
+
+ #def requestList(self, listType):
+ # """
+ # request the desired list type
+ #
+ # @param listType: (as defined by the *_LIST constants)
+ # @return: A Deferred, the callback of which will be
+ # fired when the list has been retrieved.
+ # The callback argument will be a tuple with
+ # the only element being a list of MSNContact
+ # objects.
+ # """
+ # # this doesn't need to ever be used if syncing of the lists takes place
+ # # i.e. please don't use it!
+ # warnings.warn("Please do not use this method - use the list syncing process instead")
+ # id, d = self._createIDMapping()
+ # self.sendLine("LST %s %s" % (id, listIDToCode[listType].upper()))
+ # self._setStateData('list',[])
+ # return d
+
+ def setPrivacyMode(self, privLevel):
+ """
+ Set my privacy mode on the server.
+
+ B{Note}:
+ This only keeps the current privacy setting on
+ the server for later retrieval, it does not
+ effect the way the server works at all.
+
+ @param privLevel: This parameter can be true, in which
+ case the server will keep the state as
+ 'al' which the official client interprets
+ as -> allow messages from only users on
+ the allow list. Alternatively it can be
+ false, in which case the server will keep
+ the state as 'bl' which the official client
+ interprets as -> allow messages from all
+ users except those on the block list.
+
+ @return: A Deferred, the callback of which will be fired when
+ the server replies with the new privacy setting.
+ The callback argument will be a tuple, the 2 elements
+ of which being the list version and either 'al'
+ or 'bl' (the new privacy setting).
+ """
+
+ id, d = self._createIDMapping()
+ if privLevel:
+ self.sendLine("BLP %s AL" % id)
+ else:
+ self.sendLine("BLP %s BL" % id)
+ return d
+
+ def syncList(self, version):
+ """
+ Used for keeping an up-to-date contact list.
+ A callback is added to the returned Deferred
+ that updates the contact list on the factory
+ and also sets my state to STATUS_ONLINE.
+
+ B{Note}:
+ This is called automatically upon signing
+ in using the version attribute of
+ factory.contacts, so you may want to persist
+ this object accordingly. Because of this there
+ is no real need to ever call this method
+ directly.
+
+ @param version: The current known list version
+
+ @return: A Deferred, the callback of which will be
+ fired when the server sends an adequate reply.
+ The callback argument will be a tuple with two
+ elements, the new list (MSNContactList) and
+ your current state (a dictionary). If the version
+ you sent _was_ the latest list version, both elements
+ will be None. To just request the list send a version of 0.
+ """
+
+ self._setState('SYNC')
+ id, d = self._createIDMapping(data=str(version))
+ self._setStateData('synid',id)
+ self.sendLine("SYN %s %s" % (id, version))
+ def _cb(r):
+ self.changeStatus(STATUS_ONLINE)
+ if r[0] is not None:
+ self.factory.contacts = r[0]
+ return r
+ return d.addCallback(_cb)
+
+
+ # I am no longer supporting the process of manually requesting
+ # lists or list groups -- as far as I can see this has no use
+ # if lists are synchronized and updated correctly, which they
+ # should be. If someone has a specific justified need for this
+ # then please contact me and i'll re-enable/fix support for it.
+
+ #def requestListGroups(self):
+ # """
+ # Request (forward) list groups.
+ #
+ # @return: A Deferred, the callback for which will be called
+ # when the server responds with the list groups.
+ # The callback argument will be a tuple with two elements,
+ # a dictionary mapping group IDs to group names and the
+ # current list version.
+ # """
+ #
+ # # this doesn't need to be used if syncing of the lists takes place (which it SHOULD!)
+ # # i.e. please don't use it!
+ # warnings.warn("Please do not use this method - use the list syncing process instead")
+ # id, d = self._createIDMapping()
+ # self.sendLine("LSG %s" % id)
+ # self._setStateData('groups',{})
+ # return d
+
+ def setPhoneDetails(self, phoneType, value):
+ """
+ Set/change my phone numbers stored on the server.
+
+ @param phoneType: phoneType can be one of the following
+ constants - HOME_PHONE, WORK_PHONE,
+ MOBILE_PHONE, HAS_PAGER.
+ These are pretty self-explanatory, except
+ maybe HAS_PAGER which refers to whether or
+ not you have a pager.
+ @param value: for all of the *_PHONE constants the value is a
+ phone number (str), for HAS_PAGER accepted values
+ are 'Y' (for yes) and 'N' (for no).
+
+ @return: A Deferred, the callback for which will be fired when
+ the server confirms the change has been made. The
+ callback argument will be a tuple with 2 elements, the
+ first being the new list version (int) and the second
+ being the new phone number value (str).
+ """
+ # XXX: Add a default callback which updates
+ # factory.contacts.version and the relevant phone
+ # number
+ id, d = self._createIDMapping()
+ self.sendLine("PRP %s %s %s" % (id, phoneType, quote(value)))
+ return d
+
+ def addListGroup(self, name):
+ """
+ Used to create a new list group.
+ A default callback is added to the
+ returned Deferred which updates the
+ contacts attribute of the factory.
+
+ @param name: The desired name of the new group.
+
+ @return: A Deferred, the callbacck for which will be called
+ when the server clarifies that the new group has been
+ created. The callback argument will be a tuple with 3
+ elements: the new list version (int), the new group name
+ (str) and the new group ID (int).
+ """
+
+ id, d = self._createIDMapping()
+ self.sendLine("ADG %s %s 0" % (id, quote(name)))
+ def _cb(r):
+ self.factory.contacts.version = r[0]
+ self.factory.contacts.setGroup(r[1], r[2])
+ return r
+ return d.addCallback(_cb)
+
+ def remListGroup(self, groupID):
+ """
+ Used to remove a list group.
+ A default callback is added to the
+ returned Deferred which updates the
+ contacts attribute of the factory.
+
+ @param groupID: the ID of the desired group to be removed.
+
+ @return: A Deferred, the callback for which will be called when
+ the server clarifies the deletion of the group.
+ The callback argument will be a tuple with 2 elements:
+ the new list version (int) and the group ID (int) of
+ the removed group.
+ """
+
+ id, d = self._createIDMapping()
+ self.sendLine("RMG %s %s" % (id, groupID))
+ def _cb(r):
+ self.factory.contacts.version = r[0]
+ self.factory.contacts.remGroup(r[1])
+ return r
+ return d.addCallback(_cb)
+
+ def renameListGroup(self, groupID, newName):
+ """
+ Used to rename an existing list group.
+ A default callback is added to the returned
+ Deferred which updates the contacts attribute
+ of the factory.
+
+ @param groupID: the ID of the desired group to rename.
+ @param newName: the desired new name for the group.
+
+ @return: A Deferred, the callback for which will be called
+ when the server clarifies the renaming.
+ The callback argument will be a tuple of 3 elements,
+ the new list version (int), the group id (int) and
+ the new group name (str).
+ """
+
+ id, d = self._createIDMapping()
+ self.sendLine("REG %s %s %s 0" % (id, groupID, quote(newName)))
+ def _cb(r):
+ self.factory.contacts.version = r[0]
+ self.factory.contacts.setGroup(r[1], r[2])
+ return r
+ return d.addCallback(_cb)
+
+ def addContact(self, listType, userHandle, groupID=0):
+ """
+ Used to add a contact to the desired list.
+ A default callback is added to the returned
+ Deferred which updates the contacts attribute of
+ the factory with the new contact information.
+ If you are adding a contact to the forward list
+ and you want to associate this contact with multiple
+ groups then you will need to call this method for each
+ group you would like to add them to, changing the groupID
+ parameter. The default callback will take care of updating
+ the group information on the factory's contact list.
+
+ @param listType: (as defined by the *_LIST constants)
+ @param userHandle: the user handle (passport) of the contact
+ that is being added
+ @param groupID: the group ID for which to associate this contact
+ with. (default 0 - default group). Groups are only
+ valid for FORWARD_LIST.
+
+ @return: A Deferred, the callback for which will be called when
+ the server has clarified that the user has been added.
+ The callback argument will be a tuple with 4 elements:
+ the list type, the contact's user handle, the new list
+ version, and the group id (if relevant, otherwise it
+ will be None)
+ """
+
+ id, d = self._createIDMapping()
+ listType = listIDToCode[listType].upper()
+ if listType == "FL":
+ self.sendLine("ADD %s FL %s %s %s" % (id, userHandle, userHandle, groupID))
+ else:
+ self.sendLine("ADD %s %s %s %s" % (id, listType, userHandle, userHandle))
+
+ def _cb(r):
+ self.factory.contacts.version = r[2]
+ c = self.factory.contacts.getContact(r[1])
+ if not c:
+ c = MSNContact(userHandle=r[1])
+ if r[3]:
+ c.groups.append(r[3])
+ c.addToList(r[0])
+ return r
+ return d.addCallback(_cb)
+
+ def remContact(self, listType, userHandle, groupID=0):
+ """
+ Used to remove a contact from the desired list.
+ A default callback is added to the returned deferred
+ which updates the contacts attribute of the factory
+ to reflect the new contact information. If you are
+ removing from the forward list then you will need to
+ supply a groupID, if the contact is in more than one
+ group then they will only be removed from this group
+ and not the entire forward list, but if this is their
+ only group they will be removed from the whole list.
+
+ @param listType: (as defined by the *_LIST constants)
+ @param userHandle: the user handle (passport) of the
+ contact being removed
+ @param groupID: the ID of the group to which this contact
+ belongs (only relevant for FORWARD_LIST,
+ default is 0)
+
+ @return: A Deferred, the callback for which will be called when
+ the server has clarified that the user has been removed.
+ The callback argument will be a tuple of 4 elements:
+ the list type, the contact's user handle, the new list
+ version, and the group id (if relevant, otherwise it will
+ be None)
+ """
+
+ id, d = self._createIDMapping()
+ listType = listIDToCode[listType].upper()
+ if listType == "FL":
+ self.sendLine("REM %s FL %s %s" % (id, userHandle, groupID))
+ else:
+ self.sendLine("REM %s %s %s" % (id, listType, userHandle))
+
+ def _cb(r):
+ l = self.factory.contacts
+ l.version = r[2]
+ c = l.getContact(r[1])
+ group = r[3]
+ shouldRemove = 1
+ if group: # they may not have been removed from the list
+ c.groups.remove(group)
+ if c.groups:
+ shouldRemove = 0
+ if shouldRemove:
+ c.removeFromList(r[0])
+ if c.lists == 0:
+ l.remContact(c.userHandle)
+ return r
+ return d.addCallback(_cb)
+
+ def changeScreenName(self, newName):
+ """
+ Used to change your current screen name.
+ A default callback is added to the returned
+ Deferred which updates the screenName attribute
+ of the factory and also updates the contact list
+ version.
+
+ @param newName: the new screen name
+
+ @return: A Deferred, the callback for which will be called
+ when the server sends an adequate reply.
+ The callback argument will be a tuple of 2 elements:
+ the new list version and the new screen name.
+ """
+
+ id, d = self._createIDMapping()
+ self.sendLine("REA %s %s %s" % (id, self.factory.userHandle, quote(newName)))
+ def _cb(r):
+ self.factory.contacts.version = r[0]
+ self.factory.screenName = r[1]
+ return r
+ return d.addCallback(_cb)
+
+ def requestSwitchboardServer(self):
+ """
+ Used to request a switchboard server to use for conversations.
+
+ @return: A Deferred, the callback for which will be called when
+ the server responds with the switchboard information.
+ The callback argument will be a tuple with 3 elements:
+ the host of the switchboard server, the port and a key
+ used for logging in.
+ """
+
+ id, d = self._createIDMapping()
+ self.sendLine("XFR %s SB" % id)
+ return d
+
+ def logOut(self):
+ """
+ Used to log out of the notification server.
+ After running the method the server is expected
+ to close the connection.
+ """
+
+ self.sendLine("OUT")
+
+class NotificationFactory(ClientFactory):
+ """
+ Factory for the NotificationClient protocol.
+ This is basically responsible for keeping
+ the state of the client and thus should be used
+ in a 1:1 situation with clients.
+
+ @ivar contacts: An MSNContactList instance reflecting
+ the current contact list -- this is
+ generally kept up to date by the default
+ command handlers.
+ @ivar userHandle: The client's userHandle, this is expected
+ to be set by the client and is used by the
+ protocol (for logging in etc).
+ @ivar screenName: The client's current screen-name -- this is
+ generally kept up to date by the default
+ command handlers.
+ @ivar password: The client's password -- this is (obviously)
+ expected to be set by the client.
+ @ivar passportServer: This must point to an msn passport server
+ (the whole URL is required)
+ @ivar status: The status of the client -- this is generally kept
+ up to date by the default command handlers
+ """
+
+ contacts = None
+ userHandle = ''
+ screenName = ''
+ password = ''
+ passportServer = 'https://nexus.passport.com/rdr/pprdr.asp'
+ status = 'FLN'
+ protocol = NotificationClient
+
+
+# XXX: A lot of the state currently kept in
+# instances of SwitchboardClient is likely to
+# be moved into a factory at some stage in the
+# future
+
+class SwitchboardClient(MSNEventBase):
+ """
+ This class provides support for clients connecting to a switchboard server.
+
+ Switchboard servers are used for conversations with other people
+ on the MSN network. This means that the number of conversations at
+ any given time will be directly proportional to the number of
+ connections to varioius switchboard servers.
+
+ MSN makes no distinction between single and group conversations,
+ so any number of users may be invited to join a specific conversation
+ taking place on a switchboard server.
+
+ @ivar key: authorization key, obtained when receiving
+ invitation / requesting switchboard server.
+ @ivar userHandle: your user handle (passport)
+ @ivar sessionID: unique session ID, used if you are replying
+ to a switchboard invitation
+ @ivar reply: set this to 1 in connectionMade or before to signifiy
+ that you are replying to a switchboard invitation.
+ """
+
+ key = 0
+ userHandle = ""
+ sessionID = ""
+ reply = 0
+
+ _iCookie = 0
+
+ def __init__(self):
+ MSNEventBase.__init__(self)
+ self.pendingUsers = {}
+ self.cookies = {'iCookies' : {}, 'external' : {}} # will maybe be moved to a factory in the future
+
+ def connectionMade(self):
+ MSNEventBase.connectionMade(self)
+ print 'sending initial stuff'
+ self._sendInit()
+
+ def connectionLost(self, reason):
+ self.cookies['iCookies'] = {}
+ self.cookies['external'] = {}
+ MSNEventBase.connectionLost(self, reason)
+
+ def _sendInit(self):
+ """
+ send initial data based on whether we are replying to an invitation
+ or starting one.
+ """
+ id = self._nextTransactionID()
+ if not self.reply:
+ self.sendLine("USR %s %s %s" % (id, self.userHandle, self.key))
+ else:
+ self.sendLine("ANS %s %s %s %s" % (id, self.userHandle, self.key, self.sessionID))
+
+ def _newInvitationCookie(self):
+ self._iCookie += 1
+ if self._iCookie > 1000:
+ self._iCookie = 1
+ return self._iCookie
+
+ def _checkTyping(self, message, cTypes):
+ """ helper method for checkMessage """
+ if 'text/x-msmsgscontrol' in cTypes and message.hasHeader('TypingUser'):
+ self.userTyping(message)
+ return 1
+
+ def _checkFileInvitation(self, message, info):
+ """ helper method for checkMessage """
+ guid = info.get('Application-GUID', '').lower()
+ name = info.get('Application-Name', '').lower()
+
+ # Both fields are required, but we'll let some lazy clients get away
+ # with only sending a name, if it is easy for us to recognize the
+ # name (the name is localized, so this check might fail for lazy,
+ # non-english clients, but I'm not about to include "file transfer"
+ # in 80 different languages here).
+
+ if name != "file transfer" and guid != classNameToGUID["file transfer"]:
+ return 0
+ try:
+ cookie = int(info['Invitation-Cookie'])
+ fileName = info['Application-File']
+ fileSize = int(info['Application-FileSize'])
+ except KeyError:
+ log.msg('Received munged file transfer request ... ignoring.')
+ return 0
+ self.gotSendRequest(fileName, fileSize, cookie, message)
+ return 1
+
+ def _checkFileResponse(self, message, info):
+ """ helper method for checkMessage """
+ try:
+ cmd = info['Invitation-Command'].upper()
+ cookie = int(info['Invitation-Cookie'])
+ except KeyError:
+ return 0
+ accept = (cmd == 'ACCEPT') and 1 or 0
+ requested = self.cookies['iCookies'].get(cookie)
+ if not requested:
+ return 1
+ requested[0].callback((accept, cookie, info))
+ del self.cookies['iCookies'][cookie]
+ return 1
+
+ def _checkFileInfo(self, message, info):
+ """ helper method for checkMessage """
+ try:
+ ip = info['IP-Address']
+ iCookie = int(info['Invitation-Cookie'])
+ aCookie = int(info['AuthCookie'])
+ cmd = info['Invitation-Command'].upper()
+ port = int(info['Port'])
+ except KeyError:
+ return 0
+ accept = (cmd == 'ACCEPT') and 1 or 0
+ requested = self.cookies['external'].get(iCookie)
+ if not requested:
+ return 1 # we didn't ask for this
+ requested[0].callback((accept, ip, port, aCookie, info))
+ del self.cookies['external'][iCookie]
+ return 1
+
+ def checkMessage(self, message):
+ """
+ hook for detecting any notification type messages
+ (e.g. file transfer)
+ """
+ cTypes = [s.lstrip() for s in message.getHeader('Content-Type').split(';')]
+ if self._checkTyping(message, cTypes):
+ return 0
+ if 'text/x-msmsgsinvite' in cTypes:
+ # header like info is sent as part of the message body.
+ info = {}
+ for line in message.message.split('\r\n'):
+ try:
+ key, val = line.split(':')
+ info[key] = val.lstrip()
+ except ValueError:
+ continue
+ if self._checkFileInvitation(message, info) or self._checkFileInfo(message, info) or self._checkFileResponse(message, info):
+ return 0
+ elif 'text/x-clientcaps' in cTypes:
+ # do something with capabilities
+ return 0
+ return 1
+
+ # negotiation
+ def handle_USR(self, params):
+ checkParamLen(len(params), 4, 'USR')
+ if params[1] == "OK":
+ self.loggedIn()
+
+ # invite a user
+ def handle_CAL(self, params):
+ checkParamLen(len(params), 3, 'CAL')
+ id = int(params[0])
+ if params[1].upper() == "RINGING":
+ self._fireCallback(id, int(params[2])) # session ID as parameter
+
+ # user joined
+ def handle_JOI(self, params):
+ checkParamLen(len(params), 2, 'JOI')
+ self.userJoined(params[0], unquote(params[1]))
+
+ # users participating in the current chat
+ def handle_IRO(self, params):
+ checkParamLen(len(params), 5, 'IRO')
+ self.pendingUsers[params[3]] = unquote(params[4])
+ if params[1] == params[2]:
+ self.gotChattingUsers(self.pendingUsers)
+ self.pendingUsers = {}
+
+ # finished listing users
+ def handle_ANS(self, params):
+ checkParamLen(len(params), 2, 'ANS')
+ if params[1] == "OK":
+ self.loggedIn()
+
+ def handle_ACK(self, params):
+ checkParamLen(len(params), 1, 'ACK')
+ self._fireCallback(int(params[0]), None)
+
+ def handle_NAK(self, params):
+ checkParamLen(len(params), 1, 'NAK')
+ self._fireCallback(int(params[0]), None)
+
+ def handle_BYE(self, params):
+ #checkParamLen(len(params), 1, 'BYE') # i've seen more than 1 param passed to this
+ self.userLeft(params[0])
+
+ # callbacks
+
+ def loggedIn(self):
+ """
+ called when all login details have been negotiated.
+ Messages can now be sent, or new users invited.
+ """
+ pass
+
+ def gotChattingUsers(self, users):
+ """
+ called after connecting to an existing chat session.
+
+ @param users: A dict mapping user handles to screen names
+ (current users taking part in the conversation)
+ """
+ pass
+
+ def userJoined(self, userHandle, screenName):
+ """
+ called when a user has joined the conversation.
+
+ @param userHandle: the user handle (passport) of the user
+ @param screenName: the screen name of the user
+ """
+ pass
+
+ def userLeft(self, userHandle):
+ """
+ called when a user has left the conversation.
+
+ @param userHandle: the user handle (passport) of the user.
+ """
+ pass
+
+ def gotMessage(self, message):
+ """
+ called when we receive a message.
+
+ @param message: the associated MSNMessage object
+ """
+ pass
+
+ def userTyping(self, message):
+ """
+ called when we receive the special type of message notifying
+ us that a user is typing a message.
+
+ @param message: the associated MSNMessage object
+ """
+ pass
+
+ def gotSendRequest(self, fileName, fileSize, iCookie, message):
+ """
+ called when a contact is trying to send us a file.
+ To accept or reject this transfer see the
+ fileInvitationReply method.
+
+ @param fileName: the name of the file
+ @param fileSize: the size of the file
+ @param iCookie: the invitation cookie, used so the client can
+ match up your reply with this request.
+ @param message: the MSNMessage object which brought about this
+ invitation (it may contain more information)
+ """
+ pass
+
+ # api calls
+
+ def inviteUser(self, userHandle):
+ """
+ used to invite a user to the current switchboard server.
+
+ @param userHandle: the user handle (passport) of the desired user.
+
+ @return: A Deferred, the callback for which will be called
+ when the server notifies us that the user has indeed
+ been invited. The callback argument will be a tuple
+ with 1 element, the sessionID given to the invited user.
+ I'm not sure if this is useful or not.
+ """
+
+ id, d = self._createIDMapping()
+ self.sendLine("CAL %s %s" % (id, userHandle))
+ return d
+
+ def sendMessage(self, message):
+ """
+ used to send a message.
+
+ @param message: the corresponding MSNMessage object.
+
+ @return: Depending on the value of message.ack.
+ If set to MSNMessage.MESSAGE_ACK or
+ MSNMessage.MESSAGE_NACK a Deferred will be returned,
+ the callback for which will be fired when an ACK or
+ NACK is received - the callback argument will be
+ (None,). If set to MSNMessage.MESSAGE_ACK_NONE then
+ the return value is None.
+ """
+
+ if message.ack not in ('A','N'):
+ id, d = self._nextTransactionID(), None
+ else:
+ id, d = self._createIDMapping()
+ if message.length == 0:
+ message.length = message._calcMessageLen()
+ self.sendLine("MSG %s %s %s" % (id, message.ack, message.length))
+ # apparently order matters with at least MIME-Version and Content-Type
+ self.sendLine('MIME-Version: %s' % message.getHeader('MIME-Version'))
+ self.sendLine('Content-Type: %s' % message.getHeader('Content-Type'))
+ # send the rest of the headers
+ for header in [h for h in message.headers.items() if h[0].lower() not in ('mime-version','content-type')]:
+ self.sendLine("%s: %s" % (header[0], header[1]))
+ self.transport.write(CR+LF)
+ self.transport.write(message.message)
+ return d
+
+ def sendTypingNotification(self):
+ """
+ used to send a typing notification. Upon receiving this
+ message the official client will display a 'user is typing'
+ message to all other users in the chat session for 10 seconds.
+ The official client sends one of these every 5 seconds (I think)
+ as long as you continue to type.
+ """
+ m = MSNMessage()
+ m.ack = m.MESSAGE_ACK_NONE
+ m.setHeader('Content-Type', 'text/x-msmsgscontrol')
+ m.setHeader('TypingUser', self.userHandle)
+ m.message = "\r\n"
+ self.sendMessage(m)
+
+ def sendFileInvitation(self, fileName, fileSize):
+ """
+ send an notification that we want to send a file.
+
+ @param fileName: the file name
+ @param fileSize: the file size
+
+ @return: A Deferred, the callback of which will be fired
+ when the user responds to this invitation with an
+ appropriate message. The callback argument will be
+ a tuple with 3 elements, the first being 1 or 0
+ depending on whether they accepted the transfer
+ (1=yes, 0=no), the second being an invitation cookie
+ to identify your follow-up responses and the third being
+ the message 'info' which is a dict of information they
+ sent in their reply (this doesn't really need to be used).
+ If you wish to proceed with the transfer see the
+ sendTransferInfo method.
+ """
+ cookie = self._newInvitationCookie()
+ d = Deferred()
+ m = MSNMessage()
+ m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
+ m.message += 'Application-Name: File Transfer\r\n'
+ m.message += 'Application-GUID: %s\r\n' % (classNameToGUID["file transfer"],)
+ m.message += 'Invitation-Command: INVITE\r\n'
+ m.message += 'Invitation-Cookie: %s\r\n' % str(cookie)
+ m.message += 'Application-File: %s\r\n' % fileName
+ m.message += 'Application-FileSize: %s\r\n\r\n' % str(fileSize)
+ m.ack = m.MESSAGE_ACK_NONE
+ self.sendMessage(m)
+ self.cookies['iCookies'][cookie] = (d, m)
+ return d
+
+ def fileInvitationReply(self, iCookie, accept=1):
+ """
+ used to reply to a file transfer invitation.
+
+ @param iCookie: the invitation cookie of the initial invitation
+ @param accept: whether or not you accept this transfer,
+ 1 = yes, 0 = no, default = 1.
+
+ @return: A Deferred, the callback for which will be fired when
+ the user responds with the transfer information.
+ The callback argument will be a tuple with 5 elements,
+ whether or not they wish to proceed with the transfer
+ (1=yes, 0=no), their ip, the port, the authentication
+ cookie (see FileReceive/FileSend) and the message
+ info (dict) (in case they send extra header-like info
+ like Internal-IP, this doesn't necessarily need to be
+ used). If you wish to proceed with the transfer see
+ FileReceive.
+ """
+ d = Deferred()
+ m = MSNMessage()
+ m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
+ m.message += 'Invitation-Command: %s\r\n' % (accept and 'ACCEPT' or 'CANCEL')
+ m.message += 'Invitation-Cookie: %s\r\n' % str(iCookie)
+ if not accept:
+ m.message += 'Cancel-Code: REJECT\r\n'
+ m.message += 'Launch-Application: FALSE\r\n'
+ m.message += 'Request-Data: IP-Address:\r\n'
+ m.message += '\r\n'
+ m.ack = m.MESSAGE_ACK_NONE
+ self.sendMessage(m)
+ self.cookies['external'][iCookie] = (d, m)
+ return d
+
+ def sendTransferInfo(self, accept, iCookie, authCookie, ip, port):
+ """
+ send information relating to a file transfer session.
+
+ @param accept: whether or not to go ahead with the transfer
+ (1=yes, 0=no)
+ @param iCookie: the invitation cookie of previous replies
+ relating to this transfer
+ @param authCookie: the authentication cookie obtained from
+ an FileSend instance
+ @param ip: your ip
+ @param port: the port on which an FileSend protocol is listening.
+ """
+ m = MSNMessage()
+ m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
+ m.message += 'Invitation-Command: %s\r\n' % (accept and 'ACCEPT' or 'CANCEL')
+ m.message += 'Invitation-Cookie: %s\r\n' % iCookie
+ m.message += 'IP-Address: %s\r\n' % ip
+ m.message += 'Port: %s\r\n' % port
+ m.message += 'AuthCookie: %s\r\n' % authCookie
+ m.message += '\r\n'
+ m.ack = m.MESSAGE_NACK
+ self.sendMessage(m)
+
+class FileReceive(LineReceiver):
+ """
+ This class provides support for receiving files from contacts.
+
+ @ivar fileSize: the size of the receiving file. (you will have to set this)
+ @ivar connected: true if a connection has been established.
+ @ivar completed: true if the transfer is complete.
+ @ivar bytesReceived: number of bytes (of the file) received.
+ This does not include header data.
+ """
+
+ def __init__(self, auth, myUserHandle, file, directory="", overwrite=0):
+ """
+ @param auth: auth string received in the file invitation.
+ @param myUserHandle: your userhandle.
+ @param file: A string or file object represnting the file
+ to save data to.
+ @param directory: optional parameter specifiying the directory.
+ Defaults to the current directory.
+ @param overwrite: if true and a file of the same name exists on
+ your system, it will be overwritten. (0 by default)
+ """
+ self.auth = auth
+ self.myUserHandle = myUserHandle
+ self.fileSize = 0
+ self.connected = 0
+ self.completed = 0
+ self.directory = directory
+ self.bytesReceived = 0
+ self.overwrite = overwrite
+
+ # used for handling current received state
+ self.state = 'CONNECTING'
+ self.segmentLength = 0
+ self.buffer = ''
+
+ if isinstance(file, types.StringType):
+ path = os.path.join(directory, file)
+ if os.path.exists(path) and not self.overwrite:
+ log.msg('File already exists...')
+ raise IOError, "File Exists" # is this all we should do here?
+ self.file = open(os.path.join(directory, file), 'wb')
+ else:
+ self.file = file
+
+ def connectionMade(self):
+ self.connected = 1
+ self.state = 'INHEADER'
+ self.sendLine('VER MSNFTP')
+
+ def connectionLost(self, reason):
+ self.connected = 0
+ self.file.close()
+
+ def parseHeader(self, header):
+ """ parse the header of each 'message' to obtain the segment length """
+
+ if ord(header[0]) != 0: # they requested that we close the connection
+ self.transport.loseConnection()
+ return
+ try:
+ extra, factor = header[1:]
+ except ValueError:
+ # munged header, ending transfer
+ self.transport.loseConnection()
+ raise
+ extra = ord(extra)
+ factor = ord(factor)
+ return factor * 256 + extra
+
+ def lineReceived(self, line):
+ temp = line.split()
+ if len(temp) == 1:
+ params = []
+ else:
+ params = temp[1:]
+ cmd = temp[0]
+ handler = getattr(self, "handle_%s" % cmd.upper(), None)
+ if handler:
+ handler(params) # try/except
+ else:
+ self.handle_UNKNOWN(cmd, params)
+
+ def rawDataReceived(self, data):
+ bufferLen = len(self.buffer)
+ if self.state == 'INHEADER':
+ delim = 3-bufferLen
+ self.buffer += data[:delim]
+ if len(self.buffer) == 3:
+ self.segmentLength = self.parseHeader(self.buffer)
+ if not self.segmentLength:
+ return # hrm
+ self.buffer = ""
+ self.state = 'INSEGMENT'
+ extra = data[delim:]
+ if len(extra) > 0:
+ self.rawDataReceived(extra)
+ return
+
+ elif self.state == 'INSEGMENT':
+ dataSeg = data[:(self.segmentLength-bufferLen)]
+ self.buffer += dataSeg
+ self.bytesReceived += len(dataSeg)
+ if len(self.buffer) == self.segmentLength:
+ self.gotSegment(self.buffer)
+ self.buffer = ""
+ if self.bytesReceived == self.fileSize:
+ self.completed = 1
+ self.buffer = ""
+ self.file.close()
+ self.sendLine("BYE 16777989")
+ return
+ self.state = 'INHEADER'
+ extra = data[(self.segmentLength-bufferLen):]
+ if len(extra) > 0:
+ self.rawDataReceived(extra)
+ return
+
+ def handle_VER(self, params):
+ checkParamLen(len(params), 1, 'VER')
+ if params[0].upper() == "MSNFTP":
+ self.sendLine("USR %s %s" % (self.myUserHandle, self.auth))
+ else:
+ log.msg('they sent the wrong version, time to quit this transfer')
+ self.transport.loseConnection()
+
+ def handle_FIL(self, params):
+ checkParamLen(len(params), 1, 'FIL')
+ try:
+ self.fileSize = int(params[0])
+ except ValueError: # they sent the wrong file size - probably want to log this
+ self.transport.loseConnection()
+ return
+ self.setRawMode()
+ self.sendLine("TFR")
+
+ def handle_UNKNOWN(self, cmd, params):
+ log.msg('received unknown command (%s), params: %s' % (cmd, params))
+
+ def gotSegment(self, data):
+ """ called when a segment (block) of data arrives. """
+ self.file.write(data)
+
+class FileSend(LineReceiver):
+ """
+ This class provides support for sending files to other contacts.
+
+ @ivar bytesSent: the number of bytes that have currently been sent.
+ @ivar completed: true if the send has completed.
+ @ivar connected: true if a connection has been established.
+ @ivar targetUser: the target user (contact).
+ @ivar segmentSize: the segment (block) size.
+ @ivar auth: the auth cookie (number) to use when sending the
+ transfer invitation
+ """
+
+ def __init__(self, file):
+ """
+ @param file: A string or file object represnting the file to send.
+ """
+
+ if isinstance(file, types.StringType):
+ self.file = open(file, 'rb')
+ else:
+ self.file = file
+
+ self.fileSize = 0
+ self.bytesSent = 0
+ self.completed = 0
+ self.connected = 0
+ self.targetUser = None
+ self.segmentSize = 2045
+ self.auth = randint(0, 2**30)
+ self._pendingSend = None # :(
+
+ def connectionMade(self):
+ self.connected = 1
+
+ def connectionLost(self, reason):
+ if self._pendingSend.active():
+ self._pendingSend.cancel()
+ self._pendingSend = None
+ if self.bytesSent == self.fileSize:
+ self.completed = 1
+ self.connected = 0
+ self.file.close()
+
+ def lineReceived(self, line):
+ temp = line.split()
+ if len(temp) == 1:
+ params = []
+ else:
+ params = temp[1:]
+ cmd = temp[0]
+ handler = getattr(self, "handle_%s" % cmd.upper(), None)
+ if handler:
+ handler(params)
+ else:
+ self.handle_UNKNOWN(cmd, params)
+
+ def handle_VER(self, params):
+ checkParamLen(len(params), 1, 'VER')
+ if params[0].upper() == "MSNFTP":
+ self.sendLine("VER MSNFTP")
+ else: # they sent some weird version during negotiation, i'm quitting.
+ self.transport.loseConnection()
+
+ def handle_USR(self, params):
+ checkParamLen(len(params), 2, 'USR')
+ self.targetUser = params[0]
+ if self.auth == int(params[1]):
+ self.sendLine("FIL %s" % (self.fileSize))
+ else: # they failed the auth test, disconnecting.
+ self.transport.loseConnection()
+
+ def handle_TFR(self, params):
+ checkParamLen(len(params), 0, 'TFR')
+ # they are ready for me to start sending
+ self.sendPart()
+
+ def handle_BYE(self, params):
+ self.completed = (self.bytesSent == self.fileSize)
+ self.transport.loseConnection()
+
+ def handle_CCL(self, params):
+ self.completed = (self.bytesSent == self.fileSize)
+ self.transport.loseConnection()
+
+ def handle_UNKNOWN(self, cmd, params):
+ log.msg('received unknown command (%s), params: %s' % (cmd, params))
+
+ def makeHeader(self, size):
+ """ make the appropriate header given a specific segment size. """
+ quotient, remainder = divmod(size, 256)
+ return chr(0) + chr(remainder) + chr(quotient)
+
+ def sendPart(self):
+ """ send a segment of data """
+ if not self.connected:
+ self._pendingSend = None
+ return # may be buggy (if handle_CCL/BYE is called but self.connected is still 1)
+ data = self.file.read(self.segmentSize)
+ if data:
+ dataSize = len(data)
+ header = self.makeHeader(dataSize)
+ self.bytesSent += dataSize
+ self.transport.write(header + data)
+ self._pendingSend = reactor.callLater(0, self.sendPart)
+ else:
+ self._pendingSend = None
+ self.completed = 1
+
+# mapping of error codes to error messages
+errorCodes = {
+
+ 200 : "Syntax error",
+ 201 : "Invalid parameter",
+ 205 : "Invalid user",
+ 206 : "Domain name missing",
+ 207 : "Already logged in",
+ 208 : "Invalid username",
+ 209 : "Invalid screen name",
+ 210 : "User list full",
+ 215 : "User already there",
+ 216 : "User already on list",
+ 217 : "User not online",
+ 218 : "Already in mode",
+ 219 : "User is in the opposite list",
+ 223 : "Too many groups",
+ 224 : "Invalid group",
+ 225 : "User not in group",
+ 229 : "Group name too long",
+ 230 : "Cannot remove group 0",
+ 231 : "Invalid group",
+ 280 : "Switchboard failed",
+ 281 : "Transfer to switchboard failed",
+
+ 300 : "Required field missing",
+ 301 : "Too many FND responses",
+ 302 : "Not logged in",
+
+ 500 : "Internal server error",
+ 501 : "Database server error",
+ 502 : "Command disabled",
+ 510 : "File operation failed",
+ 520 : "Memory allocation failed",
+ 540 : "Wrong CHL value sent to server",
+
+ 600 : "Server is busy",
+ 601 : "Server is unavaliable",
+ 602 : "Peer nameserver is down",
+ 603 : "Database connection failed",
+ 604 : "Server is going down",
+ 605 : "Server unavailable",
+
+ 707 : "Could not create connection",
+ 710 : "Invalid CVR parameters",
+ 711 : "Write is blocking",
+ 712 : "Session is overloaded",
+ 713 : "Too many active users",
+ 714 : "Too many sessions",
+ 715 : "Not expected",
+ 717 : "Bad friend file",
+ 731 : "Not expected",
+
+ 800 : "Requests too rapid",
+
+ 910 : "Server too busy",
+ 911 : "Authentication failed",
+ 912 : "Server too busy",
+ 913 : "Not allowed when offline",
+ 914 : "Server too busy",
+ 915 : "Server too busy",
+ 916 : "Server too busy",
+ 917 : "Server too busy",
+ 918 : "Server too busy",
+ 919 : "Server too busy",
+ 920 : "Not accepting new users",
+ 921 : "Server too busy",
+ 922 : "Server too busy",
+ 923 : "No parent consent",
+ 924 : "Passport account not yet verified"
+
+}
+
+# mapping of status codes to readable status format
+statusCodes = {
+
+ STATUS_ONLINE : "Online",
+ STATUS_OFFLINE : "Offline",
+ STATUS_HIDDEN : "Appear Offline",
+ STATUS_IDLE : "Idle",
+ STATUS_AWAY : "Away",
+ STATUS_BUSY : "Busy",
+ STATUS_BRB : "Be Right Back",
+ STATUS_PHONE : "On the Phone",
+ STATUS_LUNCH : "Out to Lunch"
+
+}
+
+# mapping of list ids to list codes
+listIDToCode = {
+
+ FORWARD_LIST : 'fl',
+ BLOCK_LIST : 'bl',
+ ALLOW_LIST : 'al',
+ REVERSE_LIST : 'rl'
+
+}
+
+# mapping of list codes to list ids
+listCodeToID = {}
+for id,code in listIDToCode.items():
+ listCodeToID[code] = id
+
+del id, code
+
+# Mapping of class GUIDs to simple english names
+guidToClassName = {
+ "{5D3E02AB-6190-11d3-BBBB-00C04F795683}": "file transfer",
+ }
+
+# Reverse of the above
+classNameToGUID = {}
+for guid, name in guidToClassName.iteritems():
+ classNameToGUID[name] = guid
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/oscar.py b/vendor/Twisted-10.0.0/twisted/words/protocols/oscar.py
new file mode 100644
index 0000000000..3ca4c0efe1
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/oscar.py
@@ -0,0 +1,1235 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An implementation of the OSCAR protocol, which AIM and ICQ use to communcate.
+
+Maintainer: Paul Swartz
+"""
+
+import struct
+import string
+import socket
+import random
+import types
+import re
+
+from twisted.internet import reactor, defer, protocol
+from twisted.python import log
+from twisted.python.hashlib import md5
+
+def logPacketData(data):
+ lines = len(data)/16
+ if lines*16 != len(data): lines=lines+1
+ for i in range(lines):
+ d = tuple(data[16*i:16*i+16])
+ hex = map(lambda x: "%02X"%ord(x),d)
+ text = map(lambda x: (len(repr(x))>3 and '.') or x, d)
+ log.msg(' '.join(hex)+ ' '*3*(16-len(d)) +''.join(text))
+ log.msg('')
+
+def SNAC(fam,sub,id,data,flags=[0,0]):
+ header="!HHBBL"
+ head=struct.pack(header,fam,sub,
+ flags[0],flags[1],
+ id)
+ return head+str(data)
+
+def readSNAC(data):
+ header="!HHBBL"
+ head=list(struct.unpack(header,data[:10]))
+ return head+[data[10:]]
+
+def TLV(type,value):
+ header="!HH"
+ head=struct.pack(header,type,len(value))
+ return head+str(value)
+
+def readTLVs(data,count=None):
+ header="!HH"
+ dict={}
+ while data and len(dict)!=count:
+ head=struct.unpack(header,data[:4])
+ dict[head[0]]=data[4:4+head[1]]
+ data=data[4+head[1]:]
+ if not count:
+ return dict
+ return dict,data
+
+def encryptPasswordMD5(password,key):
+ m=md5()
+ m.update(key)
+ m.update(md5(password).digest())
+ m.update("AOL Instant Messenger (SM)")
+ return m.digest()
+
+def encryptPasswordICQ(password):
+ key=[0xF3,0x26,0x81,0xC4,0x39,0x86,0xDB,0x92,0x71,0xA3,0xB9,0xE6,0x53,0x7A,0x95,0x7C]
+ bytes=map(ord,password)
+ r=""
+ for i in range(len(bytes)):
+ r=r+chr(bytes[i]^key[i%len(key)])
+ return r
+
+def dehtml(text):
+ text=string.replace(text,"<br>","\n")
+ text=string.replace(text,"<BR>","\n")
+ text=string.replace(text,"<Br>","\n") # XXX make this a regexp
+ text=string.replace(text,"<bR>","\n")
+ text=re.sub('<.*?>','',text)
+ text=string.replace(text,'&gt;','>')
+ text=string.replace(text,'&lt;','<')
+ text=string.replace(text,'&nbsp;',' ')
+ text=string.replace(text,'&#34;','"')
+ text=string.replace(text,'&amp;','&')
+ return text
+
+def html(text):
+ text=string.replace(text,'"','&#34;')
+ text=string.replace(text,'&','&amp;')
+ text=string.replace(text,'<','&lt;')
+ text=string.replace(text,'>','&gt;')
+ text=string.replace(text,"\n","<br>")
+ return '<html><body bgcolor="white"><font color="black">%s</font></body></html>'%text
+
+class OSCARUser:
+ def __init__(self, name, warn, tlvs):
+ self.name = name
+ self.warning = warn
+ self.flags = []
+ self.caps = []
+ for k,v in tlvs.items():
+ if k == 1: # user flags
+ v=struct.unpack('!H',v)[0]
+ for o, f in [(1,'trial'),
+ (2,'unknown bit 2'),
+ (4,'aol'),
+ (8,'unknown bit 4'),
+ (16,'aim'),
+ (32,'away'),
+ (1024,'activebuddy')]:
+ if v&o: self.flags.append(f)
+ elif k == 2: # member since date
+ self.memberSince = struct.unpack('!L',v)[0]
+ elif k == 3: # on-since
+ self.onSince = struct.unpack('!L',v)[0]
+ elif k == 4: # idle time
+ self.idleTime = struct.unpack('!H',v)[0]
+ elif k == 5: # unknown
+ pass
+ elif k == 6: # icq online status
+ if v[2] == '\x00':
+ self.icqStatus = 'online'
+ elif v[2] == '\x01':
+ self.icqStatus = 'away'
+ elif v[2] == '\x02':
+ self.icqStatus = 'dnd'
+ elif v[2] == '\x04':
+ self.icqStatus = 'out'
+ elif v[2] == '\x10':
+ self.icqStatus = 'busy'
+ else:
+ self.icqStatus = 'unknown'
+ elif k == 10: # icq ip address
+ self.icqIPaddy = socket.inet_ntoa(v)
+ elif k == 12: # icq random stuff
+ self.icqRandom = v
+ elif k == 13: # capabilities
+ caps=[]
+ while v:
+ c=v[:16]
+ if c==CAP_ICON: caps.append("icon")
+ elif c==CAP_IMAGE: caps.append("image")
+ elif c==CAP_VOICE: caps.append("voice")
+ elif c==CAP_CHAT: caps.append("chat")
+ elif c==CAP_GET_FILE: caps.append("getfile")
+ elif c==CAP_SEND_FILE: caps.append("sendfile")
+ elif c==CAP_SEND_LIST: caps.append("sendlist")
+ elif c==CAP_GAMES: caps.append("games")
+ else: caps.append(("unknown",c))
+ v=v[16:]
+ caps.sort()
+ self.caps=caps
+ elif k == 14: pass
+ elif k == 15: # session length (aim)
+ self.sessionLength = struct.unpack('!L',v)[0]
+ elif k == 16: # session length (aol)
+ self.sessionLength = struct.unpack('!L',v)[0]
+ elif k == 30: # no idea
+ pass
+ else:
+ log.msg("unknown tlv for user %s\nt: %s\nv: %s"%(self.name,k,repr(v)))
+
+ def __str__(self):
+ s = '<OSCARUser %s' % self.name
+ o = []
+ if self.warning!=0: o.append('warning level %s'%self.warning)
+ if hasattr(self, 'flags'): o.append('flags %s'%self.flags)
+ if hasattr(self, 'sessionLength'): o.append('online for %i minutes' % (self.sessionLength/60,))
+ if hasattr(self, 'idleTime'): o.append('idle for %i minutes' % self.idleTime)
+ if self.caps: o.append('caps %s'%self.caps)
+ if o:
+ s=s+', '+', '.join(o)
+ s=s+'>'
+ return s
+
+
+class SSIGroup:
+ def __init__(self, name, tlvs = {}):
+ self.name = name
+ #self.tlvs = []
+ #self.userIDs = []
+ self.usersToID = {}
+ self.users = []
+ #if not tlvs.has_key(0xC8): return
+ #buddyIDs = tlvs[0xC8]
+ #while buddyIDs:
+ # bid = struct.unpack('!H',buddyIDs[:2])[0]
+ # buddyIDs = buddyIDs[2:]
+ # self.users.append(bid)
+
+ def findIDFor(self, user):
+ return self.usersToID[user]
+
+ def addUser(self, buddyID, user):
+ self.usersToID[user] = buddyID
+ self.users.append(user)
+ user.group = self
+
+ def oscarRep(self, groupID, buddyID):
+ tlvData = TLV(0xc8, reduce(lambda x,y:x+y, [struct.pack('!H',self.usersToID[x]) for x in self.users]))
+ return struct.pack('!H', len(self.name)) + self.name + \
+ struct.pack('!HH', groupID, buddyID) + '\000\001' + tlvData
+
+
+class SSIBuddy:
+ def __init__(self, name, tlvs = {}):
+ self.name = name
+ self.tlvs = tlvs
+ for k,v in tlvs.items():
+ if k == 0x013c: # buddy comment
+ self.buddyComment = v
+ elif k == 0x013d: # buddy alerts
+ actionFlag = ord(v[0])
+ whenFlag = ord(v[1])
+ self.alertActions = []
+ self.alertWhen = []
+ if actionFlag&1:
+ self.alertActions.append('popup')
+ if actionFlag&2:
+ self.alertActions.append('sound')
+ if whenFlag&1:
+ self.alertWhen.append('online')
+ if whenFlag&2:
+ self.alertWhen.append('unidle')
+ if whenFlag&4:
+ self.alertWhen.append('unaway')
+ elif k == 0x013e:
+ self.alertSound = v
+
+ def oscarRep(self, groupID, buddyID):
+ tlvData = reduce(lambda x,y: x+y, map(lambda (k,v):TLV(k,v), self.tlvs.items()), '\000\000')
+ return struct.pack('!H', len(self.name)) + self.name + \
+ struct.pack('!HH', groupID, buddyID) + '\000\000' + tlvData
+
+
+class OscarConnection(protocol.Protocol):
+ def connectionMade(self):
+ self.state=""
+ self.seqnum=0
+ self.buf=''
+ self.stopKeepAliveID = None
+ self.setKeepAlive(4*60) # 4 minutes
+
+ def connectionLost(self, reason):
+ log.msg("Connection Lost! %s" % self)
+ self.stopKeepAlive()
+
+# def connectionFailed(self):
+# log.msg("Connection Failed! %s" % self)
+# self.stopKeepAlive()
+
+ def sendFLAP(self,data,channel = 0x02):
+ header="!cBHH"
+ self.seqnum=(self.seqnum+1)%0xFFFF
+ seqnum=self.seqnum
+ head=struct.pack(header,'*', channel,
+ seqnum, len(data))
+ self.transport.write(head+str(data))
+# if isinstance(self, ChatService):
+# logPacketData(head+str(data))
+
+ def readFlap(self):
+ header="!cBHH"
+ if len(self.buf)<6: return
+ flap=struct.unpack(header,self.buf[:6])
+ if len(self.buf)<6+flap[3]: return
+ data,self.buf=self.buf[6:6+flap[3]],self.buf[6+flap[3]:]
+ return [flap[1],data]
+
+ def dataReceived(self,data):
+# if isinstance(self, ChatService):
+# logPacketData(data)
+ self.buf=self.buf+data
+ flap=self.readFlap()
+ while flap:
+ func=getattr(self,"oscar_%s"%self.state,None)
+ if not func:
+ log.msg("no func for state: %s" % self.state)
+ state=func(flap)
+ if state:
+ self.state=state
+ flap=self.readFlap()
+
+ def setKeepAlive(self,t):
+ self.keepAliveDelay=t
+ self.stopKeepAlive()
+ self.stopKeepAliveID = reactor.callLater(t, self.sendKeepAlive)
+
+ def sendKeepAlive(self):
+ self.sendFLAP("",0x05)
+ self.stopKeepAliveID = reactor.callLater(self.keepAliveDelay, self.sendKeepAlive)
+
+ def stopKeepAlive(self):
+ if self.stopKeepAliveID:
+ self.stopKeepAliveID.cancel()
+ self.stopKeepAliveID = None
+
+ def disconnect(self):
+ """
+ send the disconnect flap, and sever the connection
+ """
+ self.sendFLAP('', 0x04)
+ def f(reason): pass
+ self.connectionLost = f
+ self.transport.loseConnection()
+
+
+class SNACBased(OscarConnection):
+ snacFamilies = {
+ # family : (version, toolID, toolVersion)
+ }
+ def __init__(self,cookie):
+ self.cookie=cookie
+ self.lastID=0
+ self.supportedFamilies = ()
+ self.requestCallbacks={} # request id:Deferred
+
+ def sendSNAC(self,fam,sub,data,flags=[0,0]):
+ """
+ send a snac and wait for the response by returning a Deferred.
+ """
+ reqid=self.lastID
+ self.lastID=reqid+1
+ d = defer.Deferred()
+ d.reqid = reqid
+
+ #d.addErrback(self._ebDeferredError,fam,sub,data) # XXX for testing
+
+ self.requestCallbacks[reqid] = d
+ self.sendFLAP(SNAC(fam,sub,reqid,data))
+ return d
+
+ def _ebDeferredError(self, error, fam, sub, data):
+ log.msg('ERROR IN DEFERRED %s' % error)
+ log.msg('on sending of message, family 0x%02x, subtype 0x%02x' % (fam, sub))
+ log.msg('data: %s' % repr(data))
+
+ def sendSNACnr(self,fam,sub,data,flags=[0,0]):
+ """
+ send a snac, but don't bother adding a deferred, we don't care.
+ """
+ self.sendFLAP(SNAC(fam,sub,0x10000*fam+sub,data))
+
+ def oscar_(self,data):
+ self.sendFLAP("\000\000\000\001"+TLV(6,self.cookie), 0x01)
+ return "Data"
+
+ def oscar_Data(self,data):
+ snac=readSNAC(data[1])
+ if self.requestCallbacks.has_key(snac[4]):
+ d = self.requestCallbacks[snac[4]]
+ del self.requestCallbacks[snac[4]]
+ if snac[1]!=1:
+ d.callback(snac)
+ else:
+ d.errback(snac)
+ return
+ func=getattr(self,'oscar_%02X_%02X'%(snac[0],snac[1]),None)
+ if not func:
+ self.oscar_unknown(snac)
+ else:
+ func(snac[2:])
+ return "Data"
+
+ def oscar_unknown(self,snac):
+ log.msg("unknown for %s" % self)
+ log.msg(snac)
+
+
+ def oscar_01_03(self, snac):
+ numFamilies = len(snac[3])/2
+ self.supportedFamilies = struct.unpack("!"+str(numFamilies)+'H', snac[3])
+ d = ''
+ for fam in self.supportedFamilies:
+ if self.snacFamilies.has_key(fam):
+ d=d+struct.pack('!2H',fam,self.snacFamilies[fam][0])
+ self.sendSNACnr(0x01,0x17, d)
+
+ def oscar_01_0A(self,snac):
+ """
+ change of rate information.
+ """
+ # this can be parsed, maybe we can even work it in
+ pass
+
+ def oscar_01_18(self,snac):
+ """
+ host versions, in the same format as we sent
+ """
+ self.sendSNACnr(0x01,0x06,"") #pass
+
+ def clientReady(self):
+ """
+ called when the client is ready to be online
+ """
+ d = ''
+ for fam in self.supportedFamilies:
+ if self.snacFamilies.has_key(fam):
+ version, toolID, toolVersion = self.snacFamilies[fam]
+ d = d + struct.pack('!4H',fam,version,toolID,toolVersion)
+ self.sendSNACnr(0x01,0x02,d)
+
+class BOSConnection(SNACBased):
+ snacFamilies = {
+ 0x01:(3, 0x0110, 0x059b),
+ 0x13:(3, 0x0110, 0x059b),
+ 0x02:(1, 0x0110, 0x059b),
+ 0x03:(1, 0x0110, 0x059b),
+ 0x04:(1, 0x0110, 0x059b),
+ 0x06:(1, 0x0110, 0x059b),
+ 0x08:(1, 0x0104, 0x0001),
+ 0x09:(1, 0x0110, 0x059b),
+ 0x0a:(1, 0x0110, 0x059b),
+ 0x0b:(1, 0x0104, 0x0001),
+ 0x0c:(1, 0x0104, 0x0001)
+ }
+
+ capabilities = None
+
+ def __init__(self,username,cookie):
+ SNACBased.__init__(self,cookie)
+ self.username=username
+ self.profile = None
+ self.awayMessage = None
+ self.services = {}
+
+ if not self.capabilities:
+ self.capabilities = [CAP_CHAT]
+
+ def parseUser(self,data,count=None):
+ l=ord(data[0])
+ name=data[1:1+l]
+ warn,foo=struct.unpack("!HH",data[1+l:5+l])
+ warn=int(warn/10)
+ tlvs=data[5+l:]
+ if count:
+ tlvs,rest = readTLVs(tlvs,foo)
+ else:
+ tlvs,rest = readTLVs(tlvs), None
+ u = OSCARUser(name, warn, tlvs)
+ if rest == None:
+ return u
+ else:
+ return u, rest
+
+ def oscar_01_05(self, snac, d = None):
+ """
+ data for a new service connection
+ d might be a deferred to be called back when the service is ready
+ """
+ tlvs = readTLVs(snac[3][2:])
+ service = struct.unpack('!H',tlvs[0x0d])[0]
+ ip = tlvs[5]
+ cookie = tlvs[6]
+ #c = serviceClasses[service](self, cookie, d)
+ c = protocol.ClientCreator(reactor, serviceClasses[service], self, cookie, d)
+ def addService(x):
+ self.services[service] = x
+ c.connectTCP(ip, 5190).addCallback(addService)
+ #self.services[service] = c
+
+ def oscar_01_07(self,snac):
+ """
+ rate paramaters
+ """
+ self.sendSNACnr(0x01,0x08,"\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05") # ack
+ self.initDone()
+ self.sendSNACnr(0x13,0x02,'') # SSI rights info
+ self.sendSNACnr(0x02,0x02,'') # location rights info
+ self.sendSNACnr(0x03,0x02,'') # buddy list rights
+ self.sendSNACnr(0x04,0x04,'') # ICBM parms
+ self.sendSNACnr(0x09,0x02,'') # BOS rights
+
+ def oscar_01_10(self,snac):
+ """
+ we've been warned
+ """
+ skip = struct.unpack('!H',snac[3][:2])[0]
+ newLevel = struct.unpack('!H',snac[3][2+skip:4+skip])[0]/10
+ if len(snac[3])>4+skip:
+ by = self.parseUser(snac[3][4+skip:])
+ else:
+ by = None
+ self.receiveWarning(newLevel, by)
+
+ def oscar_01_13(self,snac):
+ """
+ MOTD
+ """
+ pass # we don't care for now
+
+ def oscar_02_03(self, snac):
+ """
+ location rights response
+ """
+ tlvs = readTLVs(snac[3])
+ self.maxProfileLength = tlvs[1]
+
+ def oscar_03_03(self, snac):
+ """
+ buddy list rights response
+ """
+ tlvs = readTLVs(snac[3])
+ self.maxBuddies = tlvs[1]
+ self.maxWatchers = tlvs[2]
+
+ def oscar_03_0B(self, snac):
+ """
+ buddy update
+ """
+ self.updateBuddy(self.parseUser(snac[3]))
+
+ def oscar_03_0C(self, snac):
+ """
+ buddy offline
+ """
+ self.offlineBuddy(self.parseUser(snac[3]))
+
+# def oscar_04_03(self, snac):
+
+ def oscar_04_05(self, snac):
+ """
+ ICBM parms response
+ """
+ self.sendSNACnr(0x04,0x02,'\x00\x00\x00\x00\x00\x0b\x1f@\x03\xe7\x03\xe7\x00\x00\x00\x00') # IM rights
+
+ def oscar_04_07(self, snac):
+ """
+ ICBM message (instant message)
+ """
+ data = snac[3]
+ cookie, data = data[:8], data[8:]
+ channel = struct.unpack('!H',data[:2])[0]
+ data = data[2:]
+ user, data = self.parseUser(data, 1)
+ tlvs = readTLVs(data)
+ if channel == 1: # message
+ flags = []
+ multiparts = []
+ for k, v in tlvs.items():
+ if k == 2:
+ while v:
+ v = v[2:] # skip bad data
+ messageLength, charSet, charSubSet = struct.unpack('!3H', v[:6])
+ messageLength -= 4
+ message = [v[6:6+messageLength]]
+ if charSet == 0:
+ pass # don't add anything special
+ elif charSet == 2:
+ message.append('unicode')
+ elif charSet == 3:
+ message.append('iso-8859-1')
+ elif charSet == 0xffff:
+ message.append('none')
+ if charSubSet == 0xb:
+ message.append('macintosh')
+ if messageLength > 0: multiparts.append(tuple(message))
+ v = v[6+messageLength:]
+ elif k == 3:
+ flags.append('acknowledge')
+ elif k == 4:
+ flags.append('auto')
+ elif k == 6:
+ flags.append('offline')
+ elif k == 8:
+ iconLength, foo, iconSum, iconStamp = struct.unpack('!LHHL',v)
+ if iconLength:
+ flags.append('icon')
+ flags.append((iconLength, iconSum, iconStamp))
+ elif k == 9:
+ flags.append('buddyrequest')
+ elif k == 0xb: # unknown
+ pass
+ elif k == 0x17:
+ flags.append('extradata')
+ flags.append(v)
+ else:
+ log.msg('unknown TLV for incoming IM, %04x, %s' % (k,repr(v)))
+
+# unknown tlv for user SNewdorf
+# t: 29
+# v: '\x00\x00\x00\x05\x02\x01\xd2\x04r\x00\x01\x01\x10/\x8c\x8b\x8a\x1e\x94*\xbc\x80}\x8d\xc4;\x1dEM'
+# XXX what is this?
+ self.receiveMessage(user, multiparts, flags)
+ elif channel == 2: # rondevouz
+ status = struct.unpack('!H',tlvs[5][:2])[0]
+ requestClass = tlvs[5][10:26]
+ moreTLVs = readTLVs(tlvs[5][26:])
+ if requestClass == CAP_CHAT: # a chat request
+ exchange = struct.unpack('!H',moreTLVs[10001][:2])[0]
+ name = moreTLVs[10001][3:-2]
+ instance = struct.unpack('!H',moreTLVs[10001][-2:])[0]
+ if not self.services.has_key(SERVICE_CHATNAV):
+ self.connectService(SERVICE_CHATNAV,1).addCallback(lambda x: self.services[SERVICE_CHATNAV].getChatInfo(exchange, name, instance).\
+ addCallback(self._cbGetChatInfoForInvite, user, moreTLVs[12]))
+ else:
+ self.services[SERVICE_CHATNAV].getChatInfo(exchange, name, instance).\
+ addCallback(self._cbGetChatInfoForInvite, user, moreTLVs[12])
+ elif requestClass == CAP_SEND_FILE:
+ if moreTLVs.has_key(11): # cancel
+ log.msg('cancelled file request')
+ log.msg(status)
+ return # handle this later
+ name = moreTLVs[10001][9:-7]
+ desc = moreTLVs[12]
+ log.msg('file request from %s, %s, %s' % (user, name, desc))
+ self.receiveSendFileRequest(user, name, desc, cookie)
+ else:
+ log.msg('unsupported rondevouz: %s' % requestClass)
+ log.msg(repr(moreTLVs))
+ else:
+ log.msg('unknown channel %02x' % channel)
+ log.msg(tlvs)
+
+ def _cbGetChatInfoForInvite(self, info, user, message):
+ apply(self.receiveChatInvite, (user,message)+info)
+
+ def oscar_09_03(self, snac):
+ """
+ BOS rights response
+ """
+ tlvs = readTLVs(snac[3])
+ self.maxPermitList = tlvs[1]
+ self.maxDenyList = tlvs[2]
+
+ def oscar_0B_02(self, snac):
+ """
+ stats reporting interval
+ """
+ self.reportingInterval = struct.unpack('!H',snac[3][:2])[0]
+
+ def oscar_13_03(self, snac):
+ """
+ SSI rights response
+ """
+ #tlvs = readTLVs(snac[3])
+ pass # we don't know how to parse this
+
+ # methods to be called by the client, and their support methods
+ def requestSelfInfo(self):
+ """
+ ask for the OSCARUser for ourselves
+ """
+ d = defer.Deferred()
+ self.sendSNAC(0x01, 0x0E, '').addCallback(self._cbRequestSelfInfo, d)
+ return d
+
+ def _cbRequestSelfInfo(self, snac, d):
+ d.callback(self.parseUser(snac[5]))
+
+ def initSSI(self):
+ """
+ this sends the rate request for family 0x13 (Server Side Information)
+ so we can then use it
+ """
+ return self.sendSNAC(0x13, 0x02, '').addCallback(self._cbInitSSI)
+
+ def _cbInitSSI(self, snac, d):
+ return {} # don't even bother parsing this
+
+ def requestSSI(self, timestamp = 0, revision = 0):
+ """
+ request the server side information
+ if the deferred gets None, it means the SSI is the same
+ """
+ return self.sendSNAC(0x13, 0x05,
+ struct.pack('!LH',timestamp,revision)).addCallback(self._cbRequestSSI)
+
+ def _cbRequestSSI(self, snac, args = ()):
+ if snac[1] == 0x0f: # same SSI as we have
+ return
+ itemdata = snac[5][3:]
+ if args:
+ revision, groups, permit, deny, permitMode, visibility = args
+ else:
+ version, revision = struct.unpack('!BH', snac[5][:3])
+ groups = {}
+ permit = []
+ deny = []
+ permitMode = None
+ visibility = None
+ while len(itemdata)>4:
+ nameLength = struct.unpack('!H', itemdata[:2])[0]
+ name = itemdata[2:2+nameLength]
+ groupID, buddyID, itemType, restLength = \
+ struct.unpack('!4H', itemdata[2+nameLength:10+nameLength])
+ tlvs = readTLVs(itemdata[10+nameLength:10+nameLength+restLength])
+ itemdata = itemdata[10+nameLength+restLength:]
+ if itemType == 0: # buddies
+ groups[groupID].addUser(buddyID, SSIBuddy(name, tlvs))
+ elif itemType == 1: # group
+ g = SSIGroup(name, tlvs)
+ if groups.has_key(0): groups[0].addUser(groupID, g)
+ groups[groupID] = g
+ elif itemType == 2: # permit
+ permit.append(name)
+ elif itemType == 3: # deny
+ deny.append(name)
+ elif itemType == 4: # permit deny info
+ if not tlvs.has_key(0xcb):
+ continue # this happens with ICQ
+ permitMode = {1:'permitall',2:'denyall',3:'permitsome',4:'denysome',5:'permitbuddies'}[ord(tlvs[0xca])]
+ visibility = {'\xff\xff\xff\xff':'all','\x00\x00\x00\x04':'notaim'}[tlvs[0xcb]]
+ elif itemType == 5: # unknown (perhaps idle data)?
+ pass
+ else:
+ log.msg('%s %s %s %s %s' % (name, groupID, buddyID, itemType, tlvs))
+ timestamp = struct.unpack('!L',itemdata)[0]
+ if not timestamp: # we've got more packets coming
+ # which means add some deferred stuff
+ d = defer.Deferred()
+ self.requestCallbacks[snac[4]] = d
+ d.addCallback(self._cbRequestSSI, (revision, groups, permit, deny, permitMode, visibility))
+ return d
+ return (groups[0].users,permit,deny,permitMode,visibility,timestamp,revision)
+
+ def activateSSI(self):
+ """
+ active the data stored on the server (use buddy list, permit deny settings, etc.)
+ """
+ self.sendSNACnr(0x13,0x07,'')
+
+ def startModifySSI(self):
+ """
+ tell the OSCAR server to be on the lookout for SSI modifications
+ """
+ self.sendSNACnr(0x13,0x11,'')
+
+ def addItemSSI(self, item, groupID = None, buddyID = None):
+ """
+ add an item to the SSI server. if buddyID == 0, then this should be a group.
+ this gets a callback when it's finished, but you can probably ignore it.
+ """
+ if groupID is None:
+ if isinstance(item, SSIGroup):
+ groupID = 0
+ else:
+ groupID = item.group.group.findIDFor(item.group)
+ if buddyID is None:
+ buddyID = item.group.findIDFor(item)
+ return self.sendSNAC(0x13,0x08, item.oscarRep(groupID, buddyID))
+
+ def modifyItemSSI(self, item, groupID = None, buddyID = None):
+ if groupID is None:
+ if isinstance(item, SSIGroup):
+ groupID = 0
+ else:
+ groupID = item.group.group.findIDFor(item.group)
+ if buddyID is None:
+ buddyID = item.group.findIDFor(item)
+ return self.sendSNAC(0x13,0x09, item.oscarRep(groupID, buddyID))
+
+ def delItemSSI(self, item, groupID = None, buddyID = None):
+ if groupID is None:
+ if isinstance(item, SSIGroup):
+ groupID = 0
+ else:
+ groupID = item.group.group.findIDFor(item.group)
+ if buddyID is None:
+ buddyID = item.group.findIDFor(item)
+ return self.sendSNAC(0x13,0x0A, item.oscarRep(groupID, buddyID))
+
+ def endModifySSI(self):
+ self.sendSNACnr(0x13,0x12,'')
+
+ def setProfile(self, profile):
+ """
+ set the profile.
+ send None to not set a profile (different from '' for a blank one)
+ """
+ self.profile = profile
+ tlvs = ''
+ if self.profile is not None:
+ tlvs = TLV(1,'text/aolrtf; charset="us-ascii"') + \
+ TLV(2,self.profile)
+
+ tlvs = tlvs + TLV(5, ''.join(self.capabilities))
+ self.sendSNACnr(0x02, 0x04, tlvs)
+
+ def setAway(self, away = None):
+ """
+ set the away message, or return (if away == None)
+ """
+ self.awayMessage = away
+ tlvs = TLV(3,'text/aolrtf; charset="us-ascii"') + \
+ TLV(4,away or '')
+ self.sendSNACnr(0x02, 0x04, tlvs)
+
+ def setIdleTime(self, idleTime):
+ """
+ set our idle time. don't call more than once with a non-0 idle time.
+ """
+ self.sendSNACnr(0x01, 0x11, struct.pack('!L',idleTime))
+
+ def sendMessage(self, user, message, wantAck = 0, autoResponse = 0, offline = 0 ): \
+ #haveIcon = 0, ):
+ """
+ send a message to user (not an OSCARUseR).
+ message can be a string, or a multipart tuple.
+ if wantAck, we return a Deferred that gets a callback when the message is sent.
+ if autoResponse, this message is an autoResponse, as if from an away message.
+ if offline, this is an offline message (ICQ only, I think)
+ """
+ data = ''.join([chr(random.randrange(0, 127)) for i in range(8)]) # cookie
+ data = data + '\x00\x01' + chr(len(user)) + user
+ if not type(message) in (types.TupleType, types.ListType):
+ message = [[message,]]
+ if type(message[0][0]) == types.UnicodeType:
+ message[0].append('unicode')
+ messageData = ''
+ for part in message:
+ charSet = 0
+ if 'unicode' in part[1:]:
+ charSet = 2
+ part[0] = part[0].encode('utf-8')
+ elif 'iso-8859-1' in part[1:]:
+ charSet = 3
+ part[0] = part[0].encode('iso-8859-1')
+ elif 'none' in part[1:]:
+ charSet = 0xffff
+ if 'macintosh' in part[1:]:
+ charSubSet = 0xb
+ else:
+ charSubSet = 0
+ messageData = messageData + '\x01\x01' + \
+ struct.pack('!3H',len(part[0])+4,charSet,charSubSet)
+ messageData = messageData + part[0]
+ data = data + TLV(2, '\x05\x01\x00\x03\x01\x01\x02'+messageData)
+ if wantAck:
+ data = data + TLV(3,'')
+ if autoResponse:
+ data = data + TLV(4,'')
+ if offline:
+ data = data + TLV(6,'')
+ if wantAck:
+ return self.sendSNAC(0x04, 0x06, data).addCallback(self._cbSendMessageAck, user, message)
+ self.sendSNACnr(0x04, 0x06, data)
+
+ def _cbSendMessageAck(self, snac, user, message):
+ return user, message
+
+ def connectService(self, service, wantCallback = 0, extraData = ''):
+ """
+ connect to another service
+ if wantCallback, we return a Deferred that gets called back when the service is online.
+ if extraData, append that to our request.
+ """
+ if wantCallback:
+ d = defer.Deferred()
+ self.sendSNAC(0x01,0x04,struct.pack('!H',service) + extraData).addCallback(self._cbConnectService, d)
+ return d
+ else:
+ self.sendSNACnr(0x01,0x04,struct.pack('!H',service))
+
+ def _cbConnectService(self, snac, d):
+ self.oscar_01_05(snac[2:], d)
+
+ def createChat(self, shortName):
+ """
+ create a chat room
+ """
+ if self.services.has_key(SERVICE_CHATNAV):
+ return self.services[SERVICE_CHATNAV].createChat(shortName)
+ else:
+ return self.connectService(SERVICE_CHATNAV,1).addCallback(lambda s: s.createChat(shortName))
+
+
+ def joinChat(self, exchange, fullName, instance):
+ """
+ join a chat room
+ """
+ #d = defer.Deferred()
+ return self.connectService(0x0e, 1, TLV(0x01, struct.pack('!HB',exchange, len(fullName)) + fullName +
+ struct.pack('!H', instance))).addCallback(self._cbJoinChat) #, d)
+ #return d
+
+ def _cbJoinChat(self, chat):
+ del self.services[SERVICE_CHAT]
+ return chat
+
+ def warnUser(self, user, anon = 0):
+ return self.sendSNAC(0x04, 0x08, '\x00'+chr(anon)+chr(len(user))+user).addCallback(self._cbWarnUser)
+
+ def _cbWarnUser(self, snac):
+ oldLevel, newLevel = struct.unpack('!2H', snac[5])
+ return oldLevel, newLevel
+
+ def getInfo(self, user):
+ #if user.
+ return self.sendSNAC(0x02, 0x05, '\x00\x01'+chr(len(user))+user).addCallback(self._cbGetInfo)
+
+ def _cbGetInfo(self, snac):
+ user, rest = self.parseUser(snac[5],1)
+ tlvs = readTLVs(rest)
+ return tlvs.get(0x02,None)
+
+ def getAway(self, user):
+ return self.sendSNAC(0x02, 0x05, '\x00\x03'+chr(len(user))+user).addCallback(self._cbGetAway)
+
+ def _cbGetAway(self, snac):
+ user, rest = self.parseUser(snac[5],1)
+ tlvs = readTLVs(rest)
+ return tlvs.get(0x04,None) # return None if there is no away message
+
+ #def acceptSendFileRequest(self,
+
+ # methods to be overriden by the client
+ def initDone(self):
+ """
+ called when we get the rate information, which means we should do other init. stuff.
+ """
+ log.msg('%s initDone' % self)
+ pass
+
+ def updateBuddy(self, user):
+ """
+ called when a buddy changes status, with the OSCARUser for that buddy.
+ """
+ log.msg('%s updateBuddy %s' % (self, user))
+ pass
+
+ def offlineBuddy(self, user):
+ """
+ called when a buddy goes offline
+ """
+ log.msg('%s offlineBuddy %s' % (self, user))
+ pass
+
+ def receiveMessage(self, user, multiparts, flags):
+ """
+ called when someone sends us a message
+ """
+ pass
+
+ def receiveWarning(self, newLevel, user):
+ """
+ called when someone warns us.
+ user is either None (if it was anonymous) or an OSCARUser
+ """
+ pass
+
+ def receiveChatInvite(self, user, message, exchange, fullName, instance, shortName, inviteTime):
+ """
+ called when someone invites us to a chat room
+ """
+ pass
+
+ def chatReceiveMessage(self, chat, user, message):
+ """
+ called when someone in a chatroom sends us a message in the chat
+ """
+ pass
+
+ def chatMemberJoined(self, chat, member):
+ """
+ called when a member joins the chat
+ """
+ pass
+
+ def chatMemberLeft(self, chat, member):
+ """
+ called when a member leaves the chat
+ """
+ pass
+
+ def receiveSendFileRequest(self, user, file, description, cookie):
+ """
+ called when someone tries to send a file to us
+ """
+ pass
+
+class OSCARService(SNACBased):
+ def __init__(self, bos, cookie, d = None):
+ SNACBased.__init__(self, cookie)
+ self.bos = bos
+ self.d = d
+
+ def connectionLost(self, reason):
+ for k,v in self.bos.services.items():
+ if v == self:
+ del self.bos.services[k]
+ return
+
+ def clientReady(self):
+ SNACBased.clientReady(self)
+ if self.d:
+ self.d.callback(self)
+ self.d = None
+
+class ChatNavService(OSCARService):
+ snacFamilies = {
+ 0x01:(3, 0x0010, 0x059b),
+ 0x0d:(1, 0x0010, 0x059b)
+ }
+ def oscar_01_07(self, snac):
+ # rate info
+ self.sendSNACnr(0x01, 0x08, '\000\001\000\002\000\003\000\004\000\005')
+ self.sendSNACnr(0x0d, 0x02, '')
+
+ def oscar_0D_09(self, snac):
+ self.clientReady()
+
+ def getChatInfo(self, exchange, name, instance):
+ d = defer.Deferred()
+ self.sendSNAC(0x0d,0x04,struct.pack('!HB',exchange,len(name)) + \
+ name + struct.pack('!HB',instance,2)). \
+ addCallback(self._cbGetChatInfo, d)
+ return d
+
+ def _cbGetChatInfo(self, snac, d):
+ data = snac[5][4:]
+ exchange, length = struct.unpack('!HB',data[:3])
+ fullName = data[3:3+length]
+ instance = struct.unpack('!H',data[3+length:5+length])[0]
+ tlvs = readTLVs(data[8+length:])
+ shortName = tlvs[0x6a]
+ inviteTime = struct.unpack('!L',tlvs[0xca])[0]
+ info = (exchange,fullName,instance,shortName,inviteTime)
+ d.callback(info)
+
+ def createChat(self, shortName):
+ #d = defer.Deferred()
+ data = '\x00\x04\x06create\xff\xff\x01\x00\x03'
+ data = data + TLV(0xd7, 'en')
+ data = data + TLV(0xd6, 'us-ascii')
+ data = data + TLV(0xd3, shortName)
+ return self.sendSNAC(0x0d, 0x08, data).addCallback(self._cbCreateChat)
+ #return d
+
+ def _cbCreateChat(self, snac): #d):
+ exchange, length = struct.unpack('!HB',snac[5][4:7])
+ fullName = snac[5][7:7+length]
+ instance = struct.unpack('!H',snac[5][7+length:9+length])[0]
+ #d.callback((exchange, fullName, instance))
+ return exchange, fullName, instance
+
+class ChatService(OSCARService):
+ snacFamilies = {
+ 0x01:(3, 0x0010, 0x059b),
+ 0x0E:(1, 0x0010, 0x059b)
+ }
+ def __init__(self,bos,cookie, d = None):
+ OSCARService.__init__(self,bos,cookie,d)
+ self.exchange = None
+ self.fullName = None
+ self.instance = None
+ self.name = None
+ self.members = None
+
+ clientReady = SNACBased.clientReady # we'll do our own callback
+
+ def oscar_01_07(self,snac):
+ self.sendSNAC(0x01,0x08,"\000\001\000\002\000\003\000\004\000\005")
+ self.clientReady()
+
+ def oscar_0E_02(self, snac):
+# try: # this is EVIL
+# data = snac[3][4:]
+# self.exchange, length = struct.unpack('!HB',data[:3])
+# self.fullName = data[3:3+length]
+# self.instance = struct.unpack('!H',data[3+length:5+length])[0]
+# tlvs = readTLVs(data[8+length:])
+# self.name = tlvs[0xd3]
+# self.d.callback(self)
+# except KeyError:
+ data = snac[3]
+ self.exchange, length = struct.unpack('!HB',data[:3])
+ self.fullName = data[3:3+length]
+ self.instance = struct.unpack('!H',data[3+length:5+length])[0]
+ tlvs = readTLVs(data[8+length:])
+ self.name = tlvs[0xd3]
+ self.d.callback(self)
+
+ def oscar_0E_03(self,snac):
+ users=[]
+ rest=snac[3]
+ while rest:
+ user, rest = self.bos.parseUser(rest, 1)
+ users.append(user)
+ if not self.fullName:
+ self.members = users
+ else:
+ self.members.append(users[0])
+ self.bos.chatMemberJoined(self,users[0])
+
+ def oscar_0E_04(self,snac):
+ user=self.bos.parseUser(snac[3])
+ for u in self.members:
+ if u.name == user.name: # same person!
+ self.members.remove(u)
+ self.bos.chatMemberLeft(self,user)
+
+ def oscar_0E_06(self,snac):
+ data = snac[3]
+ user,rest=self.bos.parseUser(snac[3][14:],1)
+ tlvs = readTLVs(rest[8:])
+ message=tlvs[1]
+ self.bos.chatReceiveMessage(self,user,message)
+
+ def sendMessage(self,message):
+ tlvs=TLV(0x02,"us-ascii")+TLV(0x03,"en")+TLV(0x01,message)
+ self.sendSNAC(0x0e,0x05,
+ "\x46\x30\x38\x30\x44\x00\x63\x00\x00\x03\x00\x01\x00\x00\x00\x06\x00\x00\x00\x05"+
+ struct.pack("!H",len(tlvs))+
+ tlvs)
+
+ def leaveChat(self):
+ self.disconnect()
+
+class OscarAuthenticator(OscarConnection):
+ BOSClass = BOSConnection
+ def __init__(self,username,password,deferred=None,icq=0):
+ self.username=username
+ self.password=password
+ self.deferred=deferred
+ self.icq=icq # icq mode is disabled
+ #if icq and self.BOSClass==BOSConnection:
+ # self.BOSClass=ICQConnection
+
+ def oscar_(self,flap):
+ if not self.icq:
+ self.sendFLAP("\000\000\000\001", 0x01)
+ self.sendFLAP(SNAC(0x17,0x06,0,
+ TLV(TLV_USERNAME,self.username)+
+ TLV(0x004B,'')))
+ self.state="Key"
+ else:
+ encpass=encryptPasswordICQ(self.password)
+ self.sendFLAP('\000\000\000\001'+
+ TLV(0x01,self.username)+
+ TLV(0x02,encpass)+
+ TLV(0x03,'ICQ Inc. - Product of ICQ (TM).2001b.5.18.1.3659.85')+
+ TLV(0x16,"\x01\x0a")+
+ TLV(0x17,"\x00\x05")+
+ TLV(0x18,"\x00\x12")+
+ TLV(0x19,"\000\001")+
+ TLV(0x1a,"\x0eK")+
+ TLV(0x14,"\x00\x00\x00U")+
+ TLV(0x0f,"en")+
+ TLV(0x0e,"us"),0x01)
+ self.state="Cookie"
+
+ def oscar_Key(self,data):
+ snac=readSNAC(data[1])
+ key=snac[5][2:]
+ encpass=encryptPasswordMD5(self.password,key)
+ self.sendFLAP(SNAC(0x17,0x02,0,
+ TLV(TLV_USERNAME,self.username)+
+ TLV(TLV_PASSWORD,encpass)+
+ TLV(0x004C, '')+ # unknown
+ TLV(TLV_CLIENTNAME,"AOL Instant Messenger (SM), version 4.8.2790/WIN32")+
+ TLV(0x0016,"\x01\x09")+
+ TLV(TLV_CLIENTMAJOR,"\000\004")+
+ TLV(TLV_CLIENTMINOR,"\000\010")+
+ TLV(0x0019,"\000\000")+
+ TLV(TLV_CLIENTSUB,"\x0A\xE6")+
+ TLV(0x0014,"\x00\x00\x00\xBB")+
+ TLV(TLV_LANG,"en")+
+ TLV(TLV_COUNTRY,"us")+
+ TLV(TLV_USESSI,"\001")))
+ return "Cookie"
+
+ def oscar_Cookie(self,data):
+ snac=readSNAC(data[1])
+ if self.icq:
+ i=snac[5].find("\000")
+ snac[5]=snac[5][i:]
+ tlvs=readTLVs(snac[5])
+ if tlvs.has_key(6):
+ self.cookie=tlvs[6]
+ server,port=string.split(tlvs[5],":")
+ d = self.connectToBOS(server, int(port))
+ d.addErrback(lambda x: log.msg("Connection Failed! Reason: %s" % x))
+ if self.deferred:
+ d.chainDeferred(self.deferred)
+ self.disconnect()
+ elif tlvs.has_key(8):
+ errorcode=tlvs[8]
+ errorurl=tlvs[4]
+ if errorcode=='\000\030':
+ error="You are attempting to sign on again too soon. Please try again later."
+ elif errorcode=='\000\005':
+ error="Invalid Username or Password."
+ else: error=repr(errorcode)
+ self.error(error,errorurl)
+ else:
+ log.msg('hmm, weird tlvs for %s cookie packet' % str(self))
+ log.msg(tlvs)
+ log.msg('snac')
+ log.msg(str(snac))
+ return "None"
+
+ def oscar_None(self,data): pass
+
+ def connectToBOS(self, server, port):
+ c = protocol.ClientCreator(reactor, self.BOSClass, self.username, self.cookie)
+ return c.connectTCP(server, int(port))
+
+ def error(self,error,url):
+ log.msg("ERROR! %s %s" % (error,url))
+ if self.deferred: self.deferred.errback((error,url))
+ self.transport.loseConnection()
+
+FLAP_CHANNEL_NEW_CONNECTION = 0x01
+FLAP_CHANNEL_DATA = 0x02
+FLAP_CHANNEL_ERROR = 0x03
+FLAP_CHANNEL_CLOSE_CONNECTION = 0x04
+
+SERVICE_CHATNAV = 0x0d
+SERVICE_CHAT = 0x0e
+serviceClasses = {
+ SERVICE_CHATNAV:ChatNavService,
+ SERVICE_CHAT:ChatService
+}
+TLV_USERNAME = 0x0001
+TLV_CLIENTNAME = 0x0003
+TLV_COUNTRY = 0x000E
+TLV_LANG = 0x000F
+TLV_CLIENTMAJOR = 0x0017
+TLV_CLIENTMINOR = 0x0018
+TLV_CLIENTSUB = 0x001A
+TLV_PASSWORD = 0x0025
+TLV_USESSI = 0x004A
+
+CAP_ICON = '\011F\023FL\177\021\321\202"DEST\000\000'
+CAP_VOICE = '\011F\023AL\177\021\321\202"DEST\000\000'
+CAP_IMAGE = '\011F\023EL\177\021\321\202"DEST\000\000'
+CAP_CHAT = 't\217$ b\207\021\321\202"DEST\000\000'
+CAP_GET_FILE = '\011F\023HL\177\021\321\202"DEST\000\000'
+CAP_SEND_FILE = '\011F\023CL\177\021\321\202"DEST\000\000'
+CAP_GAMES = '\011F\023GL\177\021\321\202"DEST\000\000'
+CAP_SEND_LIST = '\011F\023KL\177\021\321\202"DEST\000\000'
+CAP_SERV_REL = '\011F\023IL\177\021\321\202"DEST\000\000'
diff --git a/vendor/Twisted-10.0.0/twisted/words/protocols/toc.py b/vendor/Twisted-10.0.0/twisted/words/protocols/toc.py
new file mode 100644
index 0000000000..4612e835c8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/protocols/toc.py
@@ -0,0 +1,1622 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implements a AOL Instant Messenger TOC server and client, using the Twisted
+framework.
+
+TODO:
+info,dir: see how gaim connects for this...it may never work if it tries to
+connect to the aim server automatically
+
+This module is deprecated.
+
+Maintainer: Paul Swartz
+"""
+
+import warnings
+warnings.warn(
+ "twisted.words.protocols.toc is deprecated since Twisted 9.0. "
+ "Use twisted.words.protocols.oscar instead.",
+ category=DeprecationWarning,
+ stacklevel=2)
+
+
+# twisted imports
+from twisted.internet import reactor, protocol
+from twisted.python import log
+
+# base imports
+import struct
+import string
+import time
+import base64
+import os
+import StringIO
+
+SIGNON,DATA,ERROR,SIGNOFF,KEEP_ALIVE=range(1,6)
+PERMITALL,DENYALL,PERMITSOME,DENYSOME=range(1,5)
+
+DUMMY_CHECKSUM = -559038737 # 0xdeadbeef
+
+def quote(s):
+ rep=['\\','$','{','}','[',']','(',')','"']
+ for r in rep:
+ s=string.replace(s,r,"\\"+r)
+ return "\""+s+"\""
+
+def unquote(s):
+ if s=="": return ""
+ if s[0]!='"': return s
+ r=string.replace
+ s=s[1:-1]
+ s=r(s,"\\\\","\\")
+ s=r(s,"\\$","$")
+ s=r(s,"\\{","{")
+ s=r(s,"\\}","}")
+ s=r(s,"\\[","[")
+ s=r(s,"\\]","]")
+ s=r(s,"\\(","(")
+ s=r(s,"\\)",")")
+ s=r(s,"\\\"","\"")
+ return s
+
+def unquotebeg(s):
+ for i in range(1,len(s)):
+ if s[i]=='"' and s[i-1]!='\\':
+ q=unquote(s[:i+1])
+ return [q,s[i+2:]]
+
+def unroast(pw):
+ roaststring="Tic/Toc"
+ pw=string.lower(pw[2:])
+ r=""
+ count=0
+ hex=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"]
+ while pw:
+ st,pw=pw[:2],pw[2:]
+ value=(16*hex.index(st[0]))+hex.index(st[1])
+ xor=ord(roaststring[count])
+ count=(count+1)%len(roaststring)
+ r=r+chr(value^xor)
+ return r
+
+def roast(pw):
+ # contributed by jemfinch on #python
+ key="Tic/Toc"
+ ro="0x"
+ i=0
+ ascii=map(ord,pw)
+ for c in ascii:
+ ro=ro+'%02x'%(c^ord(key[i%len(key)]))
+ i=i+1
+ return string.lower(ro)
+
+def checksum(b):
+ return DUMMY_CHECKSUM # do it like gaim does, since the checksum
+ # formula doesn't work
+## # used in file transfers
+## check0 = check1 = 0x00ff
+## for i in range(len(b)):
+## if i%2:
+## if ord(b[i])>check1:
+## check1=check1+0x100 # wrap
+## if check0==0:
+## check0=0x00ff
+## if check1==0x100:
+## check1=check1-1
+## else:
+## check0=check0-1
+## check1=check1-ord(b[i])
+## else:
+## if ord(b[i])>check0: # wrap
+## check0=check0+0x100
+## if check1==0:
+## check1=0x00ff
+## if check0==0x100:
+## check0=check0-1
+## else:
+## check1=check1-1
+## check0=check0-ord(b[i])
+## check0=check0 & 0xff
+## check1=check1 & 0xff
+## checksum=(long(check0)*0x1000000)+(long(check1)*0x10000)
+## return checksum
+
+def checksum_file(f):
+ return DUMMY_CHECKSUM # do it like gaim does, since the checksum
+ # formula doesn't work
+## check0=check1=0x00ff
+## i=0
+## while 1:
+## b=f.read()
+## if not b: break
+## for char in b:
+## i=not i
+## if i:
+## if ord(char)>check1:
+## check1=check1+0x100 # wrap
+## if check0==0:
+## check0=0x00ff
+## if check1==0x100:
+## check1=check1-1
+## else:
+## check0=check0-1
+## check1=check1-ord(char)
+## else:
+## if ord(char)>check0: # wrap
+## check0=check0+0x100
+## if check1==0:
+## check1=0x00ff
+## if check0==0x100:
+## check0=check0-1
+## else:
+## check1=check1-1
+## check0=check0-ord(char)
+## check0=check0 & 0xff
+## check1=check1 & 0xff
+## checksum=(long(check0)*0x1000000)+(long(check1)*0x10000)
+## return checksum
+
+def normalize(s):
+ s=string.lower(s)
+ s=string.replace(s," ","")
+ return s
+
+
+class TOCParseError(ValueError):
+ pass
+
+
+class TOC(protocol.Protocol):
+ users={}
+
+ def connectionMade(self):
+ # initialization of protocol
+ self._buf=""
+ self._ourseqnum=0L
+ self._theirseqnum=0L
+ self._mode="Flapon"
+ self._onlyflaps=0
+ self._laststatus={} # the last status for a user
+ self.username=None
+ self.permitmode=PERMITALL
+ self.permitlist=[]
+ self.denylist=[]
+ self.buddylist=[]
+ self.signontime=0
+ self.idletime=0
+ self.userinfo="<br>"
+ self.userclass=" O"
+ self.away=""
+ self.saved=None
+
+ def _debug(self,data):
+ log.msg(data)
+
+ def connectionLost(self, reason):
+ self._debug("dropped connection from %s" % self.username)
+ try:
+ del self.factory.users[self.username]
+ except:
+ pass
+ for k in self.factory.chatroom.keys():
+ try:
+ self.factory.chatroom[k].leave(self)
+ except TOCParseError:
+ pass
+ if self.saved:
+ self.factory.savedusers[self.username]=self.saved
+ self.updateUsers()
+
+ def sendFlap(self,type,data):
+ """
+ send a FLAP to the client
+ """
+ send="*"
+ self._debug(data)
+ if type==DATA:
+ data=data+"\000"
+ length=len(data)
+ send=send+struct.pack("!BHH",type,self._ourseqnum,length)
+ send=send+data
+ self._ourseqnum=self._ourseqnum+1
+ if self._ourseqnum>(256L**4):
+ self._ourseqnum=0
+ self.transport.write(send)
+
+ def dataReceived(self,data):
+ self._buf=self._buf+data
+ try:
+ func=getattr(self,"mode%s"%self._mode)
+ except:
+ return
+ self._mode=func()
+ if self._onlyflaps and self.isFlap(): self.dataReceived("")
+
+ def isFlap(self):
+ """
+ tests to see if a flap is actually on the buffer
+ """
+ if self._buf=='': return 0
+ if self._buf[0]!="*": return 0
+ if len(self._buf)<6: return 0
+ foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6])
+ if type not in range(1,6): return 0
+ if len(self._buf)<6+length: return 0
+ return 1
+
+ def readFlap(self):
+ """
+ read the first FLAP off self._buf, raising errors if it isn't in the right form.
+ the FLAP is the basic TOC message format, and is logically equivilant to a packet in TCP
+ """
+ if self._buf=='': return None
+ if self._buf[0]!="*":
+ raise TOCParseError
+ if len(self._buf)<6: return None
+ foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6])
+ if len(self._buf)<6+length: return None
+ data=self._buf[6:6+length]
+ self._buf=self._buf[6+length:]
+ if data and data[-1]=="\000":
+ data=data[:-1]
+ self._debug([type,data])
+ return [type,data]
+
+ #def modeWeb(self):
+ # try:
+ # line,rest=string.split(self._buf,"\n",1)
+ # get,username,http=string.split(line," ",2)
+ # except:
+ # return "Web" # not enough data
+ # foo,type,username=string.split(username,"/")
+ # if type=="info":
+ # user=self.factory.users[username]
+ # text="<HTML><HEAD><TITLE>User Information for %s</TITLE></HEAD><BODY>Username: <B>%s</B><br>\nWarning Level: <B>%s%</B><br>\n Online Since: <B>%s</B><br>\nIdle Minutes: <B>%s</B><br>\n<hr><br>\n%s\n<hr><br>\n"%(user.saved.nick, user.saved.nick, user.saved.evilness, time.asctime(user.signontime), int((time.time()-user.idletime)/60), user.userinfo)
+ # self.transport.write("HTTP/1.1 200 OK\n")
+ # self.transport.write("Content-Type: text/html\n")
+ # self.transport.write("Content-Length: %s\n\n"%len(text))
+ # self.transport.write(text)
+ # self.loseConnection()
+
+ def modeFlapon(self):
+ #if self._buf[:3]=="GET": self.modeWeb() # TODO: get this working
+ if len(self._buf)<10: return "Flapon" # not enough bytes
+ flapon,self._buf=self._buf[:10],self._buf[10:]
+ if flapon!="FLAPON\r\n\r\n":
+ raise TOCParseError
+ self.sendFlap(SIGNON,"\000\000\000\001")
+ self._onlyflaps=1
+ return "Signon"
+
+ def modeSignon(self):
+ flap=self.readFlap()
+ if flap==None:
+ return "Signon"
+ if flap[0]!=SIGNON: raise TOCParseError
+ version,tlv,unlength=struct.unpack("!LHH",flap[1][:8])
+ if version!=1 or tlv!=1 or unlength+8!=len(flap[1]):
+ raise TOCParseError
+ self.username=normalize(flap[1][8:])
+ if self.username in self.factory.savedusers.keys():
+ self.saved=self.factory.savedusers[self.username]
+ else:
+ self.saved=SavedUser()
+ self.saved.nick=self.username
+ return "TocSignon"
+
+ def modeTocSignon(self):
+ flap=self.readFlap()
+ if flap==None:
+ return "TocSignon"
+ if flap[0]!=DATA: raise TOCParseError
+ data=string.split(flap[1]," ")
+ if data[0]!="toc_signon": raise TOCParseError
+ for i in data:
+ if not i:data.remove(i)
+ password=unroast(data[4])
+ if not(self.authorize(data[1],int(data[2]),data[3],password)):
+ self.sendError(BAD_NICKNAME)
+ self.transport.loseConnection()
+ return
+ self.sendFlap(DATA,"SIGN_ON:TOC1.0")
+ self.sendFlap(DATA,"NICK:%s"%self.saved.nick)
+ self.sendFlap(DATA,"CONFIG:%s"%self.saved.config)
+ # sending user configuration goes here
+ return "Connected"
+
+ def authorize(self,server,port,username,password):
+ if self.saved.password=="":
+ self.saved.password=password
+ return 1
+ else:
+ return self.saved.password==password
+
+ def modeConnected(self):
+ flap=self.readFlap()
+ while flap!=None:
+ if flap[0] not in [DATA,KEEP_ALIVE]: raise TOCParseError
+ flapdata=string.split(flap[1]," ",1)
+ tocname=flapdata[0][4:]
+ if len(flapdata)==2:
+ data=flapdata[1]
+ else:
+ data=""
+ func=getattr(self,"toc_"+tocname,None)
+ if func!=None:
+ func(data)
+ else:
+ self.toc_unknown(tocname,data)
+ flap=self.readFlap()
+ return "Connected"
+
+ def toc_unknown(self,tocname,data):
+ self._debug("unknown! %s %s" % (tocname,data))
+
+ def toc_init_done(self,data):
+ """
+ called when all the setup is done.
+
+ toc_init_done
+ """
+ self.signontime=int(time.time())
+ self.factory.users[self.username]=self
+ self.updateUsers()
+
+ def toc_add_permit(self,data):
+ """
+ adds users to the permit list. if the list is null, then set the mode to DENYALL
+ """
+ if data=="":
+ self.permitmode=DENYALL
+ self.permitlist=[]
+ self.denylist=[]
+ else:
+ self.permitmode=PERMITSOME
+ self.denylist=[]
+ users=string.split(data," ")
+ map(self.permitlist.append,users)
+ self.updateUsers()
+
+ def toc_add_deny(self,data):
+ """
+ adds users to the deny list. if the list is null, then set the mode to PERMITALL
+ """
+ if data=="":
+ self.permitmode=PERMITALL
+ self.permitlist=[]
+ self.denylist=[]
+ else:
+ self.permitmode=DENYSOME
+ self.permitlist=[]
+ users=string.split(data," ")
+ map(self.denylist.append,users)
+ self.updateUsers()
+
+ def toc_evil(self,data):
+ """
+ warns a user.
+
+ toc_evil <username> <anon|norm>
+ """
+ username,nora=string.split(data," ")
+ if nora=="anon":
+ user=""
+ else:
+ user=self.saved.nick
+ if not(self.factory.users.has_key(username)):
+ self.sendError(CANT_WARN,username)
+ return
+ if self.factory.users[username].saved.evilness>=100:
+ self.sendError(CANT_WARN,username)
+ return
+ self.factory.users[username].evilFrom(user)
+
+ def toc_add_buddy(self,data):
+ """
+ adds users to the buddy list
+
+ toc_add_buddy <buddyname1> [<buddyname2>] [<buddyname3>]...
+ """
+ buddies=map(normalize,string.split(data," "))
+ for b in buddies:
+ if b not in self.buddylist:
+ self.buddylist.append(b)
+ for buddy in buddies:
+ try:
+ buddy=self.factory.users[buddy]
+ except:
+ pass
+ else:
+ self.buddyUpdate(buddy)
+
+ def toc_remove_buddy(self,data):
+ """
+ removes users from the buddy list
+
+ toc_remove_buddy <buddyname1> [<buddyname2>] [<buddyname3>]...
+ """
+ buddies=string.split(data," ")
+ for buddy in buddies:
+ try:
+ self.buddylist.remove(normalize(buddy))
+ except: pass
+
+ def toc_send_im(self,data):
+ """
+ incoming instant message
+
+ toc_send_im <screenname> <quoted message> [auto]
+ """
+ username,data=string.split(data," ",1)
+ auto=0
+ if data[-4:]=="auto":
+ auto=1
+ data=data[:-5]
+ data=unquote(data)
+ if not(self.factory.users.has_key(username)):
+ self.sendError(NOT_AVAILABLE,username)
+ return
+ user=self.factory.users[username]
+ if not(self.canContact(user)):
+ self.sendError(NOT_AVAILABLE,username)
+ return
+ user.hearWhisper(self,data,auto)
+
+ def toc_set_info(self,data):
+ """
+ set the users information, retrivable with toc_get_info
+
+ toc_set_info <user info (quoted)>
+ """
+ info=unquote(data)
+ self._userinfo=info
+
+ def toc_set_idle(self,data):
+ """
+ set/unset idle
+
+ toc_set_idle <seconds>
+ """
+ seconds=int(data)
+ self.idletime=time.time()-seconds # time when they started being idle
+ self.updateUsers()
+
+ def toc_set_away(self,data):
+ """
+ set/unset away message
+
+ toc_set_away [<away message>]
+ """
+ away=unquote(data)
+ if not self.away and away: # setting an away message
+ self.away=away
+ self.userclass=self.userclass+'U'
+ self.updateUsers()
+ elif self.away and not away: # coming back
+ self.away=""
+ self.userclass=self.userclass[:2]
+ self.updateUsers()
+ else:
+ raise TOCParseError
+
+ def toc_chat_join(self,data):
+ """
+ joins the chat room.
+
+ toc_chat_join <exchange> <room name>
+ """
+ exchange,name=string.split(data," ",1)
+ self.factory.getChatroom(int(exchange),unquote(name)).join(self)
+
+ def toc_chat_invite(self,data):
+ """
+ invite others to the room.
+
+ toc_chat_invite <room id> <invite message> <buddy 1> [<buddy2>]...
+ """
+ id,data=string.split(data," ",1)
+ id=int(id)
+ message,data=unquotebeg(data)
+ buddies=string.split(data," ")
+ for b in buddies:
+ room=self.factory.chatroom[id]
+ bud=self.factory.users[b]
+ bud.chatInvite(room,self,message)
+
+ def toc_chat_accept(self,data):
+ """
+ accept an invitation.
+
+ toc_chat_accept <room id>
+ """
+ id=int(data)
+ self.factory.chatroom[id].join(self)
+
+ def toc_chat_send(self,data):
+ """
+ send a message to the chat room.
+
+ toc_chat_send <room id> <message>
+ """
+ id,message=string.split(data," ",1)
+ id=int(id)
+ message=unquote(message)
+ self.factory.chatroom[id].say(self,message)
+
+ def toc_chat_whisper(self,data):
+ id,user,message=string.split(data," ",2)
+ id=int(id)
+ room=self.factory.chatroom[id]
+ message=unquote(message)
+ self.factory.users[user].chatWhisper(room,self,message)
+
+ def toc_chat_leave(self,data):
+ """
+ leave the room.
+
+ toc_chat_leave <room id>
+ """
+ id=int(data)
+ self.factory.chatroom[id].leave(self)
+
+ def toc_set_config(self,data):
+ """
+ set the saved config. this gets send when you log in.
+
+ toc_set_config <config>
+ """
+ self.saved.config=unquote(data)
+
+ def toc_get_info(self,data):
+ """
+ get the user info for a user
+
+ toc_get_info <username>
+ """
+ if not self.factory.users.has_key(data):
+ self.sendError(901,data)
+ return
+ self.sendFlap(2,"GOTO_URL:TIC:info/%s"%data)
+
+ def toc_format_nickname(self,data):
+ """
+ change the format of your nickname.
+
+ toc_format_nickname <new format>
+ """
+ # XXX may not work
+ nick=unquote(data)
+ if normalize(nick)==self.username:
+ self.saved.nick=nick
+ self.sendFlap(2,"ADMIN_NICK_STATUS:0")
+ else:
+ self.sendError(BAD_INPUT)
+
+ def toc_change_passwd(self,data):
+ orig,data=unquotebeg(data)
+ new=unquote(data)
+ if orig==self.saved.password:
+ self.saved.password=new
+ self.sendFlap(2,"ADMIN_PASSWD_STATUS:0")
+ else:
+ self.sendError(BAD_INPUT)
+
+ def sendError(self,code,*varargs):
+ """
+ send an error to the user. listing of error messages is below.
+ """
+ send="ERROR:%s"%code
+ for v in varargs:
+ send=send+":"+v
+ self.sendFlap(DATA,send)
+
+ def updateUsers(self):
+ """
+ Update the users who have us on their buddylist.
+ Called when the user changes anything (idle,away) so people can get updates.
+ """
+ for user in self.factory.users.values():
+ if self.username in user.buddylist and self.canContact(user):
+ user.buddyUpdate(self)
+
+ def getStatus(self,user):
+ if self.canContact(user):
+ if self in self.factory.users.values():ol='T'
+ else: ol='F'
+ idle=0
+ if self.idletime:
+ idle=int((time.time()-self.idletime)/60)
+ return (self.saved.nick,ol,self.saved.evilness,self.signontime,idle,self.userclass)
+ else:
+ return (self.saved.nick,'F',0,0,0,self.userclass)
+
+ def canContact(self,user):
+ if self.permitmode==PERMITALL: return 1
+ elif self.permitmode==DENYALL: return 0
+ elif self.permitmode==PERMITSOME:
+ if user.username in self.permitlist: return 1
+ else: return 0
+ elif self.permitmode==DENYSOME:
+ if user.username in self.denylist: return 0
+ else: return 1
+ else:
+ assert 0,"bad permitmode %s" % self.permitmode
+
+ def buddyUpdate(self,user):
+ """
+ Update the buddy. Called from updateUsers()
+ """
+ if not self.canContact(user): return
+ status=user.getStatus(self)
+ if not self._laststatus.has_key(user):
+ self._laststatus[user]=()
+ if self._laststatus[user]!=status:
+ send="UPDATE_BUDDY:%s:%s:%s:%s:%s:%s"%status
+ self.sendFlap(DATA,send)
+ self._laststatus[user]=status
+
+ def hearWhisper(self,user,data,auto=0):
+ """
+ Called when you get an IM. If auto=1, it's an autoreply from an away message.
+ """
+ if not self.canContact(user): return
+ if auto: auto='T'
+ else: auto='F'
+ send="IM_IN:%s:%s:%s"%(user.saved.nick,auto,data)
+ self.sendFlap(DATA,send)
+
+ def evilFrom(self,user):
+ if user=="":
+ percent=0.03
+ else:
+ percent=0.1
+ self.saved.evilness=self.saved.evilness+int((100-self.saved.evilness)*percent)
+ self.sendFlap(2,"EVILED:%s:%s"%(self.saved.evilness,user))
+ self.updateUsers()
+
+ def chatJoin(self,room):
+ self.sendFlap(2,"CHAT_JOIN:%s:%s"%(room.id,room.name))
+ f="CHAT_UPDATE_BUDDY:%s:T"%room.id
+ for u in room.users:
+ if u!=self:
+ u.chatUserUpdate(room,self)
+ f=f+":"+u.saved.nick
+ self.sendFlap(2,f)
+
+ def chatInvite(self,room,user,message):
+ if not self.canContact(user): return
+ self.sendFlap(2,"CHAT_INVITE:%s:%s:%s:%s"%(room.name,room.id,user.saved.nick,message))
+
+ def chatUserUpdate(self,room,user):
+ if user in room.users:
+ inroom='T'
+ else:
+ inroom='F'
+ self.sendFlap(2,"CHAT_UPDATE_BUDDY:%s:%s:%s"%(room.id,inroom,user.saved.nick))
+
+ def chatMessage(self,room,user,message):
+ if not self.canContact(user): return
+ self.sendFlap(2,"CHAT_IN:%s:%s:F:%s"%(room.id,user.saved.nick,message))
+
+ def chatWhisper(self,room,user,message):
+ if not self.canContact(user): return
+ self.sendFlap(2,"CHAT_IN:%s:%s:T:%s"%(room.id,user.saved.nick,message))
+
+ def chatLeave(self,room):
+ self.sendFlap(2,"CHAT_LEFT:%s"%(room.id))
+
+
+class Chatroom:
+ def __init__(self,fac,exchange,name,id):
+ self.exchange=exchange
+ self.name=name
+ self.id=id
+ self.factory=fac
+ self.users=[]
+
+ def join(self,user):
+ if user in self.users:
+ return
+ self.users.append(user)
+ user.chatJoin(self)
+
+ def leave(self,user):
+ if user not in self.users:
+ raise TOCParseError
+ self.users.remove(user)
+ user.chatLeave(self)
+ for u in self.users:
+ u.chatUserUpdate(self,user)
+ if len(self.users)==0:
+ self.factory.remChatroom(self)
+
+ def say(self,user,message):
+ for u in self.users:
+ u.chatMessage(self,user,message)
+
+
+class SavedUser:
+ def __init__(self):
+ self.config=""
+ self.nick=""
+ self.password=""
+ self.evilness=0
+
+
+class TOCFactory(protocol.Factory):
+ def __init__(self):
+ self.users={}
+ self.savedusers={}
+ self.chatroom={}
+ self.chatroomid=0
+
+ def buildProtocol(self,addr):
+ p=TOC()
+ p.factory=self
+ return p
+
+ def getChatroom(self,exchange,name):
+ for i in self.chatroom.values():
+ if normalize(i.name)==normalize(name):
+ return i
+ self.chatroom[self.chatroomid]=Chatroom(self,exchange,name,self.chatroomid)
+ self.chatroomid=self.chatroomid+1
+ return self.chatroom[self.chatroomid-1]
+
+ def remChatroom(self,room):
+ id=room.id
+ del self.chatroom[id]
+
+MAXARGS={}
+MAXARGS["CONFIG"]=0
+MAXARGS["NICK"]=0
+MAXARGS["IM_IN"]=2
+MAXARGS["UPDATE_BUDDY"]=5
+MAXARGS["ERROR"]=-1
+MAXARGS["EVILED"]=1
+MAXARGS["CHAT_JOIN"]=1
+MAXARGS["CHAT_IN"]=3
+MAXARGS["CHAT_UPDATE_BUDDY"]=-1
+MAXARGS["CHAT_INVITE"]=3
+MAXARGS["CHAT_LEFT"]=0
+MAXARGS["ADMIN_NICK_STATUS"]=0
+MAXARGS["ADMIN_PASSWD_STATUS"]=0
+
+
+class TOCClient(protocol.Protocol):
+ def __init__(self,username,password,authhost="login.oscar.aol.com",authport=5190):
+
+ self.username=normalize(username) # our username
+ self._password=password # our password
+ self._mode="SendNick" # current mode
+ self._ourseqnum=19071 # current sequence number (for sendFlap)
+ self._authhost=authhost # authorization host
+ self._authport=authport # authorization port
+ self._online=0 # are we online?
+ self._buddies=[] # the current buddy list
+ self._privacymode=PERMITALL # current privacy mode
+ self._permitlist=[] # list of users on the permit list
+ self._roomnames={} # the names for each of the rooms we're in
+ self._receivedchatmembers={} # have we gotten who's in our room yet?
+ self._denylist=[]
+ self._cookies={} # for file transfers
+ self._buf='' # current data buffer
+ self._awaymessage=''
+
+ def _debug(self,data):
+ log.msg(data)
+
+ def sendFlap(self,type,data):
+ if type==DATA:
+ data=data+"\000"
+ length=len(data)
+ s="*"
+ s=s+struct.pack("!BHH",type,self._ourseqnum,length)
+ s=s+data
+ self._ourseqnum=self._ourseqnum+1
+ if self._ourseqnum>(256*256+256):
+ self._ourseqnum=0
+ self._debug(data)
+ self.transport.write(s)
+
+ def isFlap(self):
+ """
+ tests to see if a flap is actually on the buffer
+ """
+ if self._buf=='': return 0
+ if self._buf[0]!="*": return 0
+ if len(self._buf)<6: return 0
+ foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6])
+ if type not in range(1,6): return 0
+ if len(self._buf)<6+length: return 0
+ return 1
+
+ def readFlap(self):
+ if self._buf=='': return None
+ if self._buf[0]!="*":
+ raise TOCParseError
+ if len(self._buf)<6: return None
+ foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6])
+ if len(self._buf)<6+length: return None
+ data=self._buf[6:6+length]
+ self._buf=self._buf[6+length:]
+ if data and data[-1]=="\000":
+ data=data[:-1]
+ return [type,data]
+
+ def connectionMade(self):
+ self._debug("connection made! %s" % self.transport)
+ self.transport.write("FLAPON\r\n\r\n")
+
+ def connectionLost(self, reason):
+ self._debug("connection lost!")
+ self._online=0
+
+ def dataReceived(self,data):
+ self._buf=self._buf+data
+ while self.isFlap():
+ flap=self.readFlap()
+ func=getattr(self,"mode%s"%self._mode)
+ func(flap)
+
+ def modeSendNick(self,flap):
+ if flap!=[1,"\000\000\000\001"]: raise TOCParseError
+ s="\000\000\000\001\000\001"+struct.pack("!H",len(self.username))+self.username
+ self.sendFlap(1,s)
+ s="toc_signon %s %s %s %s english \"penguin\""%(self._authhost,\
+ self._authport,self.username,roast(self._password))
+ self.sendFlap(2,s)
+ self._mode="Data"
+
+ def modeData(self,flap):
+ if not flap[1]:
+ return
+ if not ':' in flap[1]:
+ self._debug("bad SNAC:%s"%(flap[1]))
+ return
+ command,rest=string.split(flap[1],":",1)
+ if MAXARGS.has_key(command):
+ maxsplit=MAXARGS[command]
+ else:
+ maxsplit=-1
+ if maxsplit==-1:
+ l=tuple(string.split(rest,":"))
+ elif maxsplit==0:
+ l=(rest,)
+ else:
+ l=tuple(string.split(rest,":",maxsplit))
+ self._debug("%s %s"%(command,l))
+ try:
+ func=getattr(self,"toc%s"%command)
+ self._debug("calling %s"%func)
+ except:
+ self._debug("calling %s"%self.tocUNKNOWN)
+ self.tocUNKNOWN(command,l)
+ return
+ func(l)
+
+ def tocUNKNOWN(self,command,data):
+ pass
+
+ def tocSIGN_ON(self,data):
+ if data!=("TOC1.0",): raise TOCParseError
+ self._debug("Whee, signed on!")
+ if self._buddies: self.add_buddy(self._buddies)
+ self._online=1
+ self.onLine()
+
+ def tocNICK(self,data):
+ """
+ Handle a message that looks like::
+
+ NICK:<format of nickname>
+ """
+ self.username=data[0]
+
+ def tocCONFIG(self,data):
+ """
+ Handle a message that looks like::
+
+ CONFIG:<config>
+
+ Format of config data:
+
+ - g: group. all users until next g or end of config are in this group
+ - b: buddy
+ - p: person on the permit list
+ - d: person on the deny list
+ - m: permit/deny mode (1: permit all, 2: deny all, 3: permit some, 4: deny some)
+ """
+ data=data[0]
+ if data and data[0]=="{":data=data[1:-1]
+ lines=string.split(data,"\n")
+ buddylist={}
+ currentgroup=""
+ permit=[]
+ deny=[]
+ mode=1
+ for l in lines:
+ if l:
+ code,data=l[0],l[2:]
+ if code=='g': # group
+ currentgroup=data
+ buddylist[currentgroup]=[]
+ elif code=='b':
+ buddylist[currentgroup].append(data)
+ elif code=='p':
+ permit.append(data)
+ elif code=='d':
+ deny.append(data)
+ elif code=='m':
+ mode=int(data)
+ self.gotConfig(mode,buddylist,permit,deny)
+
+ def tocIM_IN(self,data):
+ """
+ Handle a message that looks like::
+
+ IM_IN:<user>:<autoreply T|F>:message
+ """
+ user=data[0]
+ autoreply=(data[1]=='T')
+ message=data[2]
+ self.hearMessage(user,message,autoreply)
+
+ def tocUPDATE_BUDDY(self,data):
+ """
+ Handle a message that looks like::
+
+ UPDATE_BUDDY:<username>:<online T|F>:<warning level>:<signon time>:<idle time (minutes)>:<user class>
+ """
+ data=list(data)
+ online=(data[1]=='T')
+ if len(data[5])==2:
+ data[5]=data[5]+" "
+ away=(data[5][-1]=='U')
+ if data[5][-1]=='U':
+ data[5]=data[5][:-1]
+ self.updateBuddy(data[0],online,int(data[2]),int(data[3]),int(data[4]),data[5],away)
+
+ def tocERROR(self,data):
+ """
+ Handle a message that looks like::
+
+ ERROR:<error code>:<misc. data>
+ """
+ code,args=data[0],data[1:]
+ self.hearError(int(code),args)
+
+ def tocEVILED(self,data):
+ """
+ Handle a message that looks like::
+
+ EVILED:<current warning level>:<user who warned us>
+ """
+ self.hearWarning(data[0],data[1])
+
+ def tocCHAT_JOIN(self,data):
+ """
+ Handle a message that looks like::
+
+ CHAT_JOIN:<room id>:<room name>
+ """
+ #self.chatJoined(int(data[0]),data[1])
+ self._roomnames[int(data[0])]=data[1]
+ self._receivedchatmembers[int(data[0])]=0
+
+ def tocCHAT_UPDATE_BUDDY(self,data):
+ """
+ Handle a message that looks like::
+
+ CHAT_UPDATE_BUDDY:<room id>:<in room? T/F>:<user 1>:<user 2>...
+ """
+ roomid=int(data[0])
+ inroom=(data[1]=='T')
+ if self._receivedchatmembers[roomid]:
+ for u in data[2:]:
+ self.chatUpdate(roomid,u,inroom)
+ else:
+ self._receivedchatmembers[roomid]=1
+ self.chatJoined(roomid,self._roomnames[roomid],list(data[2:]))
+
+ def tocCHAT_IN(self,data):
+ """
+ Handle a message that looks like::
+
+ CHAT_IN:<room id>:<username>:<whisper T/F>:<message>
+
+ whisper isn't used
+ """
+ whisper=(data[2]=='T')
+ if whisper:
+ self.chatHearWhisper(int(data[0]),data[1],data[3])
+ else:
+ self.chatHearMessage(int(data[0]),data[1],data[3])
+
+ def tocCHAT_INVITE(self,data):
+ """
+ Handle a message that looks like::
+
+ CHAT_INVITE:<room name>:<room id>:<username>:<message>
+ """
+ self.chatInvited(int(data[1]),data[0],data[2],data[3])
+
+ def tocCHAT_LEFT(self,data):
+ """
+ Handle a message that looks like::
+
+ CHAT_LEFT:<room id>
+ """
+ self.chatLeft(int(data[0]))
+ del self._receivedchatmembers[int(data[0])]
+ del self._roomnames[int(data[0])]
+
+ def tocRVOUS_PROPOSE(self,data):
+ """
+ Handle a message that looks like::
+
+ RVOUS_PROPOSE:<user>:<uuid>:<cookie>:<seq>:<rip>:<pip>:<vip>:<port>
+ [:tlv tag1:tlv value1[:tlv tag2:tlv value2[:...]]]
+ """
+ user,uid,cookie,seq,rip,pip,vip,port=data[:8]
+ cookie=base64.decodestring(cookie)
+ port=int(port)
+ tlvs={}
+ for i in range(8,len(data),2):
+ key=data[i]
+ value=base64.decodestring(data[i+1])
+ tlvs[key]=value
+ name=UUIDS[uid]
+ try:
+ func=getattr(self,"toc%s"%name)
+ except:
+ self._debug("no function for UID %s" % uid)
+ return
+ func(user,cookie,seq,pip,vip,port,tlvs)
+
+ def tocSEND_FILE(self,user,cookie,seq,pip,vip,port,tlvs):
+ if tlvs.has_key('12'):
+ description=tlvs['12']
+ else:
+ description=""
+ subtype,numfiles,size=struct.unpack("!HHI",tlvs['10001'][:8])
+ name=tlvs['10001'][8:-4]
+ while name[-1]=='\000':
+ name=name[:-1]
+ self._cookies[cookie]=[user,SEND_FILE_UID,pip,port,{'name':name}]
+ self.rvousProposal("send",cookie,user,vip,port,description=description,
+ name=name,files=numfiles,size=size)
+
+ def tocGET_FILE(self,user,cookie,seq,pip,vip,port,tlvs):
+ return
+ # XXX add this back in
+ #reactor.clientTCP(pip,port,GetFileTransfer(self,cookie,os.path.expanduser("~")))
+ #self.rvous_accept(user,cookie,GET_FILE_UID)
+
+ def onLine(self):
+ """
+ called when we are first online
+ """
+ pass
+
+ def gotConfig(self,mode,buddylist,permit,deny):
+ """
+ called when we get a configuration from the server
+ mode := permit/deny mode
+ buddylist := current buddylist
+ permit := permit list
+ deny := deny list
+ """
+ pass
+
+ def hearError(self,code,args):
+ """
+ called when an error is received
+ code := error code
+ args := misc. arguments (username, etc.)
+ """
+ pass
+
+ def hearWarning(self,newamount,username):
+ """
+ called when we get warned
+ newamount := the current warning level
+ username := the user who warned us, or '' if it's anonymous
+ """
+ pass
+
+ def hearMessage(self,username,message,autoreply):
+ """
+ called when you receive an IM
+ username := the user who the IM is from
+ message := the message
+ autoreply := true if the message is an autoreply from an away message
+ """
+ pass
+
+ def updateBuddy(self,username,online,evilness,signontime,idletime,userclass,away):
+ """
+ called when a buddy changes state
+ username := the user whos state changed
+ online := true if the user is online
+ evilness := the users current warning level
+ signontime := the time the user signed on (UNIX epoch)
+ idletime := the time the user has been idle (minutes)
+ away := true if the user is away
+ userclass := the class of the user (generally " O")
+ """
+ pass
+
+ def chatJoined(self,roomid,roomname,users):
+ """
+ we just joined a chat room
+ roomid := the AIM id for the room
+ roomname := the name for the room
+ users := a list of the users already in the room
+ """
+ pass
+
+ def chatUpdate(self,roomid,username,inroom):
+ """
+ a user has joined the room
+ roomid := the AIM id for the room
+ username := the username
+ inroom := true if the user is in the room
+ """
+ pass
+
+ def chatHearMessage(self,roomid,username,message):
+ """
+ a message was sent to the room
+ roomid := the AIM id for the room
+ username := the user who sent the message
+ message := the message
+ """
+ pass
+
+ def chatHearWhisper(self,roomid,username,message):
+ """
+ someone whispered to us in a chatroom
+ roomid := the AIM for the room
+ username := the user who whispered to us
+ message := the message
+ """
+ pass
+
+ def chatInvited(self,roomid,roomname,username,message):
+ """
+ we were invited to a chat room
+ roomid := the AIM id for the room
+ roomname := the name of the room
+ username := the user who invited us
+ message := the invite message
+ """
+ pass
+
+ def chatLeft(self,roomid):
+ """
+ we left the room
+ roomid := the AIM id for the room
+ """
+ pass
+
+ def rvousProposal(self,type,cookie,user,vip,port,**kw):
+ """
+ we were asked for a rondevouz
+ type := the type of rondevous. currently, one of ["send"]
+ cookie := the cookie. pass this to rvous_accept()
+ user := the user who asked us
+ vip := their verified_ip
+ port := the port they want us to conenct to
+ kw := misc. args
+ """
+ pass #self.rvous_accept(cookie)
+
+ def receiveBytes(self,user,file,chunk,sofar,total):
+ """
+ we received part of a file from a file transfer
+ file := the name of the file
+ chunk := the chunk of data
+ sofar := how much data we've gotten so far
+ total := the total amount of data
+ """
+ pass #print user,file,sofar,total
+
+ def isaway(self):
+ """
+ return our away status
+ """
+ return len(self._awaymessage)>0
+
+ def set_config(self,mode,buddylist,permit,deny):
+ """
+ set the server configuration
+ mode := permit mode
+ buddylist := buddy list
+ permit := permit list
+ deny := deny list
+ """
+ s="m %s\n"%mode
+ for g in buddylist.keys():
+ s=s+"g %s\n"%g
+ for u in buddylist[g]:
+ s=s+"b %s\n"%u
+ for p in permit:
+ s=s+"p %s\n"%p
+ for d in deny:
+ s=s+"d %s\n"%d
+ #s="{\n"+s+"\n}"
+ self.sendFlap(2,"toc_set_config %s"%quote(s))
+
+ def add_buddy(self,buddies):
+ s=""
+ if type(buddies)==type(""): buddies=[buddies]
+ for b in buddies:
+ s=s+" "+normalize(b)
+ self.sendFlap(2,"toc_add_buddy%s"%s)
+
+ def del_buddy(self,buddies):
+ s=""
+ if type(buddies)==type(""): buddies=[buddies]
+ for b in buddies:
+ s=s+" "+b
+ self.sendFlap(2,"toc_remove_buddy%s"%s)
+
+ def add_permit(self,users):
+ if type(users)==type(""): users=[users]
+ s=""
+ if self._privacymode!=PERMITSOME:
+ self._privacymode=PERMITSOME
+ self._permitlist=[]
+ for u in users:
+ u=normalize(u)
+ if u not in self._permitlist:self._permitlist.append(u)
+ s=s+" "+u
+ if not s:
+ self._privacymode=DENYALL
+ self._permitlist=[]
+ self._denylist=[]
+ self.sendFlap(2,"toc_add_permit"+s)
+
+ def del_permit(self,users):
+ if type(users)==type(""): users=[users]
+ p=self._permitlist[:]
+ for u in users:
+ u=normalize(u)
+ if u in p:
+ p.remove(u)
+ self.add_permit([])
+ self.add_permit(p)
+
+ def add_deny(self,users):
+ if type(users)==type(""): users=[users]
+ s=""
+ if self._privacymode!=DENYSOME:
+ self._privacymode=DENYSOME
+ self._denylist=[]
+ for u in users:
+ u=normalize(u)
+ if u not in self._denylist:self._denylist.append(u)
+ s=s+" "+u
+ if not s:
+ self._privacymode=PERMITALL
+ self._permitlist=[]
+ self._denylist=[]
+ self.sendFlap(2,"toc_add_deny"+s)
+
+ def del_deny(self,users):
+ if type(users)==type(""): users=[users]
+ d=self._denylist[:]
+ for u in users:
+ u=normalize(u)
+ if u in d:
+ d.remove(u)
+ self.add_deny([])
+ if d:
+ self.add_deny(d)
+
+ def signon(self):
+ """
+ called to finish the setup, and signon to the network
+ """
+ self.sendFlap(2,"toc_init_done")
+ self.sendFlap(2,"toc_set_caps %s" % (SEND_FILE_UID,)) # GET_FILE_UID)
+
+ def say(self,user,message,autoreply=0):
+ """
+ send a message
+ user := the user to send to
+ message := the message
+ autoreply := true if the message is an autoreply (good for away messages)
+ """
+ if autoreply: a=" auto"
+ else: a=''
+ self.sendFlap(2,"toc_send_im %s %s%s"%(normalize(user),quote(message),a))
+
+ def idle(self,idletime=0):
+ """
+ change idle state
+ idletime := the seconds that the user has been away, or 0 if they're back
+ """
+ self.sendFlap(2,"toc_set_idle %s" % int(idletime))
+
+ def evil(self,user,anon=0):
+ """
+ warn a user
+ user := the user to warn
+ anon := if true, an anonymous warning
+ """
+ self.sendFlap(2,"toc_evil %s %s"%(normalize(user), (not anon and "anon") or "norm"))
+
+ def away(self,message=''):
+ """
+ change away state
+ message := the message, or '' to come back from awayness
+ """
+ self._awaymessage=message
+ if message:
+ message=' '+quote(message)
+ self.sendFlap(2,"toc_set_away%s"%message)
+
+ def chat_join(self,exchange,roomname):
+ """
+ join a chat room
+ exchange := should almost always be 4
+ roomname := room name
+ """
+ roomname=string.replace(roomname," ","")
+ self.sendFlap(2,"toc_chat_join %s %s"%(int(exchange),roomname))
+
+ def chat_say(self,roomid,message):
+ """
+ send a message to a chatroom
+ roomid := the AIM id for the room
+ message := the message to send
+ """
+ self.sendFlap(2,"toc_chat_send %s %s"%(int(roomid),quote(message)))
+
+ def chat_whisper(self,roomid,user,message):
+ """
+ whisper to another user in a chatroom
+ roomid := the AIM id for the room
+ user := the user to whisper to
+ message := the message to send
+ """
+ self.sendFlap(2,"toc_chat_whisper %s %s %s"%(int(roomid),normalize(user),quote(message)))
+
+ def chat_leave(self,roomid):
+ """
+ leave a chat room.
+ roomid := the AIM id for the room
+ """
+ self.sendFlap(2,"toc_chat_leave %s" % int(roomid))
+
+ def chat_invite(self,roomid,usernames,message):
+ """
+ invite a user[s] to the chat room
+ roomid := the AIM id for the room
+ usernames := either a string (one username) or a list (more than one)
+ message := the message to invite them with
+ """
+ if type(usernames)==type(""): # a string, one username
+ users=usernames
+ else:
+ users=""
+ for u in usernames:
+ users=users+u+" "
+ users=users[:-1]
+ self.sendFlap(2,"toc_chat_invite %s %s %s" % (int(roomid),quote(message),users))
+
+ def chat_accept(self,roomid):
+ """
+ accept an invite to a chat room
+ roomid := the AIM id for the room
+ """
+ self.sendFlap(2,"toc_chat_accept %s"%int(roomid))
+
+ def rvous_accept(self,cookie):
+ user,uuid,pip,port,d=self._cookies[cookie]
+ self.sendFlap(2,"toc_rvous_accept %s %s %s" % (normalize(user),
+ cookie,uuid))
+ if uuid==SEND_FILE_UID:
+ protocol.ClientCreator(reactor, SendFileTransfer,self,cookie,user,d["name"]).connectTCP(pip,port)
+
+ def rvous_cancel(self,cookie):
+ user,uuid,pip,port,d=self._cookies[cookie]
+ self.sendFlap(2,"toc_rvous_accept %s %s %s" % (normalize(user),
+ cookie,uuid))
+ del self._cookies[cookie]
+
+
+class SendFileTransfer(protocol.Protocol):
+ header_fmt="!4s2H8s6H10I32s3c69s16s2H64s"
+
+ def __init__(self,client,cookie,user,filename):
+ self.client=client
+ self.cookie=cookie
+ self.user=user
+ self.filename=filename
+ self.hdr=[0,0,0]
+ self.sofar=0
+
+ def dataReceived(self,data):
+ if not self.hdr[2]==0x202:
+ self.hdr=list(struct.unpack(self.header_fmt,data[:256]))
+ self.hdr[2]=0x202
+ self.hdr[3]=self.cookie
+ self.hdr[4]=0
+ self.hdr[5]=0
+ self.transport.write(apply(struct.pack,[self.header_fmt]+self.hdr))
+ data=data[256:]
+ if self.hdr[6]==1:
+ self.name=self.filename
+ else:
+ self.name=self.filename+self.hdr[-1]
+ while self.name[-1]=="\000":
+ self.name=self.name[:-1]
+ if not data: return
+ self.sofar=self.sofar+len(data)
+ self.client.receiveBytes(self.user,self.name,data,self.sofar,self.hdr[11])
+ if self.sofar==self.hdr[11]: # end of this file
+ self.hdr[2]=0x204
+ self.hdr[7]=self.hdr[7]-1
+ self.hdr[9]=self.hdr[9]-1
+ self.hdr[19]=DUMMY_CHECKSUM # XXX really calculate this
+ self.hdr[18]=self.hdr[18]+1
+ self.hdr[21]="\000"
+ self.transport.write(apply(struct.pack,[self.header_fmt]+self.hdr))
+ self.sofar=0
+ if self.hdr[7]==0:
+ self.transport.loseConnection()
+
+
+class GetFileTransfer(protocol.Protocol):
+ header_fmt="!4s 2H 8s 6H 10I 32s 3c 69s 16s 2H 64s"
+ def __init__(self,client,cookie,dir):
+ self.client=client
+ self.cookie=cookie
+ self.dir=dir
+ self.buf=""
+
+ def connectionMade(self):
+ def func(f,path,names):
+ names.sort(lambda x,y:cmp(string.lower(x),string.lower(y)))
+ for n in names:
+ name=os.path.join(path,n)
+ lt=time.localtime(os.path.getmtime(name))
+ size=os.path.getsize(name)
+ f[1]=f[1]+size
+ f.append("%02d/%02d/%4d %02d:%02d %8d %s" %
+ (lt[1],lt[2],lt[0],lt[3],lt[4],size,name[f[0]:]))
+ f=[len(self.dir)+1,0]
+ os.path.walk(self.dir,func,f)
+ size=f[1]
+ self.listing=string.join(f[2:],"\r\n")+"\r\n"
+ open("\\listing.txt","w").write(self.listing)
+ hdr=["OFT2",256,0x1108,self.cookie,0,0,len(f)-2,len(f)-2,1,1,size,
+ len(self.listing),os.path.getmtime(self.dir),
+ checksum(self.listing),0,0,0,0,0,0,"OFT_Windows ICBMFT V1.1 32",
+ "\002",chr(0x1a),chr(0x10),"","",0,0,""]
+ self.transport.write(apply(struct.pack,[self.header_fmt]+hdr))
+
+ def dataReceived(self,data):
+ self.buf=self.buf+data
+ while len(self.buf)>=256:
+ hdr=list(struct.unpack(self.header_fmt,self.buf[:256]))
+ self.buf=self.buf[256:]
+ if hdr[2]==0x1209:
+ self.file=StringIO.StringIO(self.listing)
+ self.transport.registerProducer(self,0)
+ elif hdr[2]==0x120b: pass
+ elif hdr[2]==0x120c: # file request
+ file=hdr[-1]
+ for k,v in [["\000",""],["\001",os.sep]]:
+ file=string.replace(file,k,v)
+ self.name=os.path.join(self.dir,file)
+ self.file=open(self.name,'rb')
+ hdr[2]=0x0101
+ hdr[6]=hdr[7]=1
+ hdr[10]=hdr[11]=os.path.getsize(self.name)
+ hdr[12]=os.path.getmtime(self.name)
+ hdr[13]=checksum_file(self.file)
+ self.file.seek(0)
+ hdr[18]=hdr[19]=0
+ hdr[21]=chr(0x20)
+ self.transport.write(apply(struct.pack,[self.header_fmt]+hdr))
+ log.msg("got file request for %s"%file,hex(hdr[13]))
+ elif hdr[2]==0x0202:
+ log.msg("sending file")
+ self.transport.registerProducer(self,0)
+ elif hdr[2]==0x0204:
+ log.msg("real checksum: %s"%hex(hdr[19]))
+ del self.file
+ elif hdr[2]==0x0205: # resume
+ already=hdr[18]
+ if already:
+ data=self.file.read(already)
+ else:
+ data=""
+ log.msg("restarting at %s"%already)
+ hdr[2]=0x0106
+ hdr[19]=checksum(data)
+ self.transport.write(apply(struct.pack,[self.header_fmt]+hdr))
+ elif hdr[2]==0x0207:
+ self.transport.registerProducer(self,0)
+ else:
+ log.msg("don't understand 0x%04x"%hdr[2])
+ log.msg(hdr)
+
+ def resumeProducing(self):
+ data=self.file.read(4096)
+ log.msg(len(data))
+ if not data:
+ self.transport.unregisterProducer()
+ self.transport.write(data)
+
+ def pauseProducing(self): pass
+
+ def stopProducing(self): del self.file
+
+# UUIDs
+SEND_FILE_UID = "09461343-4C7F-11D1-8222-444553540000"
+GET_FILE_UID = "09461348-4C7F-11D1-8222-444553540000"
+UUIDS={
+ SEND_FILE_UID:"SEND_FILE",
+ GET_FILE_UID:"GET_FILE"
+}
+
+# ERRORS
+# general
+NOT_AVAILABLE=901
+CANT_WARN=902
+MESSAGES_TOO_FAST=903
+# admin
+BAD_INPUT=911
+BAD_ACCOUNT=912
+REQUEST_ERROR=913
+SERVICE_UNAVAILABLE=914
+# chat
+NO_CHAT_IN=950
+# im and info
+SEND_TOO_FAST=960
+MISSED_BIG_IM=961
+MISSED_FAST_IM=962
+# directory
+DIR_FAILURE=970
+TOO_MANY_MATCHES=971
+NEED_MORE_QUALIFIERS=972
+DIR_UNAVAILABLE=973
+NO_EMAIL_LOOKUP=974
+KEYWORD_IGNORED=975
+NO_KEYWORDS=976
+BAD_LANGUAGE=977
+BAD_COUNTRY=978
+DIR_FAIL_UNKNOWN=979
+# authorization
+BAD_NICKNAME=980
+SERVICE_TEMP_UNAVAILABLE=981
+WARNING_TOO_HIGH=982
+CONNECTING_TOO_QUICK=983
+UNKNOWN_SIGNON=989
+
+STD_MESSAGE={}
+STD_MESSAGE[NOT_AVAILABLE]="%s not currently available"
+STD_MESSAGE[CANT_WARN]="Warning of %s not currently available"
+STD_MESSAGE[MESSAGES_TOO_FAST]="A message has been dropped, you are exceeding the server speed limit"
+STD_MESSAGE[BAD_INPUT]="Error validating input"
+STD_MESSAGE[BAD_ACCOUNT]="Invalid account"
+STD_MESSAGE[REQUEST_ERROR]="Error encountered while processing request"
+STD_MESSAGE[SERVICE_UNAVAILABLE]="Service unavailable"
+STD_MESSAGE[NO_CHAT_IN]="Chat in %s is unavailable"
+STD_MESSAGE[SEND_TOO_FAST]="You are sending messages too fast to %s"
+STD_MESSAGE[MISSED_BIG_IM]="You missed an IM from %s because it was too big"
+STD_MESSAGE[MISSED_FAST_IM]="You missed an IM from %s because it was sent too fast"
+# skipping directory for now
+STD_MESSAGE[BAD_NICKNAME]="Incorrect nickname or password"
+STD_MESSAGE[SERVICE_TEMP_UNAVAILABLE]="The service is temporarily unavailable"
+STD_MESSAGE[WARNING_TOO_HIGH]="Your warning level is currently too high to sign on"
+STD_MESSAGE[CONNECTING_TOO_QUICK]="You have been connecting and disconnecting too frequently. Wait 10 minutes and try again. If you continue to try, you will need to wait even longer."
+STD_MESSAGE[UNKNOWN_SIGNON]="An unknown signon error has occurred %s"
diff --git a/vendor/Twisted-10.0.0/twisted/words/service.py b/vendor/Twisted-10.0.0/twisted/words/service.py
new file mode 100644
index 0000000000..d7f29ab2f4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/service.py
@@ -0,0 +1,1223 @@
+# -*- test-case-name: twisted.words.test.test_service -*-
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A module that needs a better name.
+
+Implements new cred things for words.
+
+How does this thing work?
+
+ - Network connection on some port expecting to speak some protocol
+
+ - Protocol-specific authentication, resulting in some kind of credentials object
+
+ - twisted.cred.portal login using those credentials for the interface
+ IUser and with something implementing IChatClient as the mind
+
+ - successful login results in an IUser avatar the protocol can call
+ methods on, and state added to the realm such that the mind will have
+ methods called on it as is necessary
+
+ - protocol specific actions lead to calls onto the avatar; remote events
+ lead to calls onto the mind
+
+ - protocol specific hangup, realm is notified, user is removed from active
+ play, the end.
+"""
+
+from time import time, ctime
+
+from zope.interface import implements
+
+from twisted.words import iwords, ewords
+
+from twisted.python.components import registerAdapter
+from twisted.cred import portal, credentials, error as ecred
+from twisted.spread import pb
+from twisted.words.protocols import irc
+from twisted.internet import defer, protocol
+from twisted.python import log, failure, reflect
+from twisted import copyright
+
+
+class Group(object):
+ implements(iwords.IGroup)
+
+ def __init__(self, name):
+ self.name = name
+ self.users = {}
+ self.meta = {
+ "topic": "",
+ "topic_author": "",
+ }
+
+
+ def _ebUserCall(self, err, p):
+ return failure.Failure(Exception(p, err))
+
+
+ def _cbUserCall(self, results):
+ for (success, result) in results:
+ if not success:
+ user, err = result.value # XXX
+ self.remove(user, err.getErrorMessage())
+
+
+ def add(self, user):
+ assert iwords.IChatClient.providedBy(user), "%r is not a chat client" % (user,)
+ if user.name not in self.users:
+ additions = []
+ self.users[user.name] = user
+ for p in self.users.itervalues():
+ if p is not user:
+ d = defer.maybeDeferred(p.userJoined, self, user)
+ d.addErrback(self._ebUserCall, p=p)
+ additions.append(d)
+ defer.DeferredList(additions).addCallback(self._cbUserCall)
+ return defer.succeed(None)
+
+
+ def remove(self, user, reason=None):
+ assert reason is None or isinstance(reason, unicode)
+ try:
+ del self.users[user.name]
+ except KeyError:
+ pass
+ else:
+ removals = []
+ for p in self.users.itervalues():
+ if p is not user:
+ d = defer.maybeDeferred(p.userLeft, self, user, reason)
+ d.addErrback(self._ebUserCall, p=p)
+ removals.append(d)
+ defer.DeferredList(removals).addCallback(self._cbUserCall)
+ return defer.succeed(None)
+
+
+ def size(self):
+ return defer.succeed(len(self.users))
+
+
+ def receive(self, sender, recipient, message):
+ assert recipient is self
+ receives = []
+ for p in self.users.itervalues():
+ if p is not sender:
+ d = defer.maybeDeferred(p.receive, sender, self, message)
+ d.addErrback(self._ebUserCall, p=p)
+ receives.append(d)
+ defer.DeferredList(receives).addCallback(self._cbUserCall)
+ return defer.succeed(None)
+
+
+ def setMetadata(self, meta):
+ self.meta = meta
+ sets = []
+ for p in self.users.itervalues():
+ d = defer.maybeDeferred(p.groupMetaUpdate, self, meta)
+ d.addErrback(self._ebUserCall, p=p)
+ sets.append(d)
+ defer.DeferredList(sets).addCallback(self._cbUserCall)
+ return defer.succeed(None)
+
+
+ def iterusers(self):
+ # XXX Deferred?
+ return iter(self.users.values())
+
+
+class User(object):
+ implements(iwords.IUser)
+
+ realm = None
+ mind = None
+
+ def __init__(self, name):
+ self.name = name
+ self.groups = []
+ self.lastMessage = time()
+
+
+ def loggedIn(self, realm, mind):
+ self.realm = realm
+ self.mind = mind
+ self.signOn = time()
+
+
+ def join(self, group):
+ def cbJoin(result):
+ self.groups.append(group)
+ return result
+ return group.add(self.mind).addCallback(cbJoin)
+
+
+ def leave(self, group, reason=None):
+ def cbLeave(result):
+ self.groups.remove(group)
+ return result
+ return group.remove(self.mind, reason).addCallback(cbLeave)
+
+
+ def send(self, recipient, message):
+ self.lastMessage = time()
+ return recipient.receive(self.mind, recipient, message)
+
+
+ def itergroups(self):
+ return iter(self.groups)
+
+
+ def logout(self):
+ for g in self.groups[:]:
+ self.leave(g)
+
+
+NICKSERV = 'NickServ!NickServ@services'
+
+
+class IRCUser(irc.IRC):
+ """
+ Protocol instance representing an IRC user connected to the server.
+ """
+ implements(iwords.IChatClient)
+
+ # A list of IGroups in which I am participating
+ groups = None
+
+ # A no-argument callable I should invoke when I go away
+ logout = None
+
+ # An IUser we use to interact with the chat service
+ avatar = None
+
+ # To whence I belong
+ realm = None
+
+ # How to handle unicode (TODO: Make this customizable on a per-user basis)
+ encoding = 'utf-8'
+
+ # Twisted callbacks
+ def connectionMade(self):
+ self.irc_PRIVMSG = self.irc_NICKSERV_PRIVMSG
+ self.realm = self.factory.realm
+ self.hostname = self.realm.name
+
+
+ def connectionLost(self, reason):
+ if self.logout is not None:
+ self.logout()
+ self.avatar = None
+
+
+ # Make sendMessage a bit more useful to us
+ def sendMessage(self, command, *parameter_list, **kw):
+ if not kw.has_key('prefix'):
+ kw['prefix'] = self.hostname
+ if not kw.has_key('to'):
+ kw['to'] = self.name.encode(self.encoding)
+
+ arglist = [self, command, kw['to']] + list(parameter_list)
+ irc.IRC.sendMessage(*arglist, **kw)
+
+
+ # IChatClient implementation
+ def userJoined(self, group, user):
+ self.join(
+ "%s!%s@%s" % (user.name, user.name, self.hostname),
+ '#' + group.name)
+
+
+ def userLeft(self, group, user, reason=None):
+ assert reason is None or isinstance(reason, unicode)
+ self.part(
+ "%s!%s@%s" % (user.name, user.name, self.hostname),
+ '#' + group.name,
+ (reason or u"leaving").encode(self.encoding, 'replace'))
+
+
+ def receive(self, sender, recipient, message):
+ #>> :glyph!glyph@adsl-64-123-27-108.dsl.austtx.swbell.net PRIVMSG glyph_ :hello
+
+ # omg???????????
+ if iwords.IGroup.providedBy(recipient):
+ recipientName = '#' + recipient.name
+ else:
+ recipientName = recipient.name
+
+ text = message.get('text', '<an unrepresentable message>')
+ for L in text.splitlines():
+ self.privmsg(
+ '%s!%s@%s' % (sender.name, sender.name, self.hostname),
+ recipientName,
+ L)
+
+
+ def groupMetaUpdate(self, group, meta):
+ if 'topic' in meta:
+ topic = meta['topic']
+ author = meta.get('topic_author', '')
+ self.topic(
+ self.name,
+ '#' + group.name,
+ topic,
+ '%s!%s@%s' % (author, author, self.hostname)
+ )
+
+ # irc.IRC callbacks - starting with login related stuff.
+ nickname = None
+ password = None
+
+ def irc_PASS(self, prefix, params):
+ """Password message -- Register a password.
+
+ Parameters: <password>
+
+ [REQUIRED]
+
+ Note that IRC requires the client send this *before* NICK
+ and USER.
+ """
+ self.password = params[-1]
+
+
+ def irc_NICK(self, prefix, params):
+ """Nick message -- Set your nickname.
+
+ Parameters: <nickname>
+
+ [REQUIRED]
+ """
+ try:
+ nickname = params[0].decode(self.encoding)
+ except UnicodeDecodeError:
+ self.privmsg(
+ NICKSERV,
+ nickname,
+ 'Your nickname is cannot be decoded. Please use ASCII or UTF-8.')
+ self.transport.loseConnection()
+ return
+
+ self.nickname = nickname
+ self.name = nickname
+
+ for code, text in self._motdMessages:
+ self.sendMessage(code, text % self.factory._serverInfo)
+
+ if self.password is None:
+ self.privmsg(
+ NICKSERV,
+ nickname,
+ 'Password?')
+ else:
+ password = self.password
+ self.password = None
+ self.logInAs(nickname, password)
+
+
+ def irc_USER(self, prefix, params):
+ """User message -- Set your realname.
+
+ Parameters: <user> <mode> <unused> <realname>
+ """
+ # Note: who gives a crap about this? The IUser has the real
+ # information we care about. Save it anyway, I guess, just
+ # for fun.
+ self.realname = params[-1]
+
+
+ def irc_NICKSERV_PRIVMSG(self, prefix, params):
+ """Send a (private) message.
+
+ Parameters: <msgtarget> <text to be sent>
+ """
+ target = params[0]
+ password = params[-1]
+
+ if self.nickname is None:
+ # XXX Send an error response here
+ self.transport.loseConnection()
+ elif target.lower() != "nickserv":
+ self.privmsg(
+ NICKSERV,
+ self.nickname,
+ "Denied. Please send me (NickServ) your password.")
+ else:
+ nickname = self.nickname
+ self.nickname = None
+ self.logInAs(nickname, password)
+
+
+ def logInAs(self, nickname, password):
+ d = self.factory.portal.login(
+ credentials.UsernamePassword(nickname, password),
+ self,
+ iwords.IUser)
+ d.addCallbacks(self._cbLogin, self._ebLogin, errbackArgs=(nickname,))
+
+
+ _welcomeMessages = [
+ (irc.RPL_WELCOME,
+ ":connected to Twisted IRC"),
+ (irc.RPL_YOURHOST,
+ ":Your host is %(serviceName)s, running version %(serviceVersion)s"),
+ (irc.RPL_CREATED,
+ ":This server was created on %(creationDate)s"),
+
+ # "Bummer. This server returned a worthless 004 numeric.
+ # I'll have to guess at all the values"
+ # -- epic
+ (irc.RPL_MYINFO,
+ # w and n are the currently supported channel and user modes
+ # -- specify this better
+ "%(serviceName)s %(serviceVersion)s w n")
+ ]
+
+ _motdMessages = [
+ (irc.RPL_MOTDSTART,
+ ":- %(serviceName)s Message of the Day - "),
+ (irc.RPL_ENDOFMOTD,
+ ":End of /MOTD command.")
+ ]
+
+ def _cbLogin(self, (iface, avatar, logout)):
+ assert iface is iwords.IUser, "Realm is buggy, got %r" % (iface,)
+
+ # Let them send messages to the world
+ del self.irc_PRIVMSG
+
+ self.avatar = avatar
+ self.logout = logout
+ for code, text in self._welcomeMessages:
+ self.sendMessage(code, text % self.factory._serverInfo)
+
+
+ def _ebLogin(self, err, nickname):
+ if err.check(ewords.AlreadyLoggedIn):
+ self.privmsg(
+ NICKSERV,
+ nickname,
+ "Already logged in. No pod people allowed!")
+ elif err.check(ecred.UnauthorizedLogin):
+ self.privmsg(
+ NICKSERV,
+ nickname,
+ "Login failed. Goodbye.")
+ else:
+ log.msg("Unhandled error during login:")
+ log.err(err)
+ self.privmsg(
+ NICKSERV,
+ nickname,
+ "Server error during login. Sorry.")
+ self.transport.loseConnection()
+
+
+ # Great, now that's out of the way, here's some of the interesting
+ # bits
+ def irc_PING(self, prefix, params):
+ """Ping message
+
+ Parameters: <server1> [ <server2> ]
+ """
+ if self.realm is not None:
+ self.sendMessage('PONG', self.hostname)
+
+
+ def irc_QUIT(self, prefix, params):
+ """Quit
+
+ Parameters: [ <Quit Message> ]
+ """
+ self.transport.loseConnection()
+
+
+ def _channelMode(self, group, modes=None, *args):
+ if modes:
+ self.sendMessage(
+ irc.ERR_UNKNOWNMODE,
+ ":Unknown MODE flag.")
+ else:
+ self.channelMode(self.name, '#' + group.name, '+')
+
+
+ def _userMode(self, user, modes=None):
+ if modes:
+ self.sendMessage(
+ irc.ERR_UNKNOWNMODE,
+ ":Unknown MODE flag.")
+ elif user is self.avatar:
+ self.sendMessage(
+ irc.RPL_UMODEIS,
+ "+")
+ else:
+ self.sendMessage(
+ irc.ERR_USERSDONTMATCH,
+ ":You can't look at someone else's modes.")
+
+
+ def irc_MODE(self, prefix, params):
+ """User mode message
+
+ Parameters: <nickname>
+ *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) )
+
+ """
+ try:
+ channelOrUser = params[0].decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHNICK, params[0],
+ ":No such nickname (could not decode your unicode!)")
+ return
+
+ if channelOrUser.startswith('#'):
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL, params[0],
+ ":That channel doesn't exist.")
+ d = self.realm.lookupGroup(channelOrUser[1:])
+ d.addCallbacks(
+ self._channelMode,
+ ebGroup,
+ callbackArgs=tuple(params[1:]))
+ else:
+ def ebUser(err):
+ self.sendMessage(
+ irc.ERR_NOSUCHNICK,
+ ":No such nickname.")
+
+ d = self.realm.lookupUser(channelOrUser)
+ d.addCallbacks(
+ self._userMode,
+ ebUser,
+ callbackArgs=tuple(params[1:]))
+
+
+ def irc_USERHOST(self, prefix, params):
+ """Userhost message
+
+ Parameters: <nickname> *( SPACE <nickname> )
+
+ [Optional]
+ """
+ pass
+
+
+ def irc_PRIVMSG(self, prefix, params):
+ """Send a (private) message.
+
+ Parameters: <msgtarget> <text to be sent>
+ """
+ try:
+ targetName = params[0].decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHNICK, targetName,
+ ":No such nick/channel (could not decode your unicode!)")
+ return
+
+ messageText = params[-1]
+ if targetName.startswith('#'):
+ target = self.realm.lookupGroup(targetName[1:])
+ else:
+ target = self.realm.lookupUser(targetName).addCallback(lambda user: user.mind)
+
+ def cbTarget(targ):
+ if targ is not None:
+ return self.avatar.send(targ, {"text": messageText})
+
+ def ebTarget(err):
+ self.sendMessage(
+ irc.ERR_NOSUCHNICK, targetName,
+ ":No such nick/channel.")
+
+ target.addCallbacks(cbTarget, ebTarget)
+
+
+ def irc_JOIN(self, prefix, params):
+ """Join message
+
+ Parameters: ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] )
+ """
+ try:
+ groupName = params[0].decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.IRC_NOSUCHCHANNEL, params[0],
+ ":No such channel (could not decode your unicode!)")
+ return
+
+ if groupName.startswith('#'):
+ groupName = groupName[1:]
+
+ def cbGroup(group):
+ def cbJoin(ign):
+ self.userJoined(group, self)
+ self.names(
+ self.name,
+ '#' + group.name,
+ [user.name for user in group.iterusers()])
+ self._sendTopic(group)
+ return self.avatar.join(group).addCallback(cbJoin)
+
+ def ebGroup(err):
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL, '#' + groupName,
+ ":No such channel.")
+
+ self.realm.getGroup(groupName).addCallbacks(cbGroup, ebGroup)
+
+
+ def irc_PART(self, prefix, params):
+ """Part message
+
+ Parameters: <channel> *( "," <channel> ) [ <Part Message> ]
+ """
+ try:
+ groupName = params[0].decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOTONCHANNEL, params[0],
+ ":Could not decode your unicode!")
+ return
+
+ if groupName.startswith('#'):
+ groupName = groupName[1:]
+
+ if len(params) > 1:
+ reason = params[1].decode('utf-8')
+ else:
+ reason = None
+
+ def cbGroup(group):
+ def cbLeave(result):
+ self.userLeft(group, self, reason)
+ return self.avatar.leave(group, reason).addCallback(cbLeave)
+
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ self.sendMessage(
+ irc.ERR_NOTONCHANNEL,
+ '#' + groupName,
+ ":" + err.getErrorMessage())
+
+ self.realm.lookupGroup(groupName).addCallbacks(cbGroup, ebGroup)
+
+
+ def irc_NAMES(self, prefix, params):
+ """Names message
+
+ Parameters: [ <channel> *( "," <channel> ) [ <target> ] ]
+ """
+ #<< NAMES #python
+ #>> :benford.openprojects.net 353 glyph = #python :Orban ... @glyph ... Zymurgy skreech
+ #>> :benford.openprojects.net 366 glyph #python :End of /NAMES list.
+ try:
+ channel = params[-1].decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL, params[-1],
+ ":No such channel (could not decode your unicode!)")
+ return
+
+ if channel.startswith('#'):
+ channel = channel[1:]
+
+ def cbGroup(group):
+ self.names(
+ self.name,
+ '#' + group.name,
+ [user.name for user in group.iterusers()])
+
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ # No group? Fine, no names!
+ self.names(
+ self.name,
+ '#' + channel,
+ [])
+
+ self.realm.lookupGroup(channel).addCallbacks(cbGroup, ebGroup)
+
+
+ def irc_TOPIC(self, prefix, params):
+ """Topic message
+
+ Parameters: <channel> [ <topic> ]
+ """
+ try:
+ channel = params[0].decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL,
+ ":That channel doesn't exist (could not decode your unicode!)")
+ return
+
+ if channel.startswith('#'):
+ channel = channel[1:]
+
+ if len(params) > 1:
+ self._setTopic(channel, params[1])
+ else:
+ self._getTopic(channel)
+
+
+ def _sendTopic(self, group):
+ """
+ Send the topic of the given group to this user, if it has one.
+ """
+ topic = group.meta.get("topic")
+ if topic:
+ author = group.meta.get("topic_author") or "<noone>"
+ date = group.meta.get("topic_date", 0)
+ self.topic(self.name, '#' + group.name, topic)
+ self.topicAuthor(self.name, '#' + group.name, author, date)
+
+
+ def _getTopic(self, channel):
+ #<< TOPIC #python
+ #>> :benford.openprojects.net 332 glyph #python :<churchr> I really did. I sprained all my toes.
+ #>> :benford.openprojects.net 333 glyph #python itamar|nyc 994713482
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL, '=', channel,
+ ":That channel doesn't exist.")
+
+ self.realm.lookupGroup(channel).addCallbacks(self._sendTopic, ebGroup)
+
+
+ def _setTopic(self, channel, topic):
+ #<< TOPIC #divunal :foo
+ #>> :glyph!glyph@adsl-64-123-27-108.dsl.austtx.swbell.net TOPIC #divunal :foo
+
+ def cbGroup(group):
+ newMeta = group.meta.copy()
+ newMeta['topic'] = topic
+ newMeta['topic_author'] = self.name
+ newMeta['topic_date'] = int(time())
+
+ def ebSet(err):
+ self.sendMessage(
+ irc.ERR_CHANOPRIVSNEEDED,
+ "#" + group.name,
+ ":You need to be a channel operator to do that.")
+
+ return group.setMetadata(newMeta).addErrback(ebSet)
+
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL, '=', channel,
+ ":That channel doesn't exist.")
+
+ self.realm.lookupGroup(channel).addCallbacks(cbGroup, ebGroup)
+
+
+ def list(self, channels):
+ """Send a group of LIST response lines
+
+ @type channel: C{list} of C{(str, int, str)}
+ @param channel: Information about the channels being sent:
+ their name, the number of participants, and their topic.
+ """
+ for (name, size, topic) in channels:
+ self.sendMessage(irc.RPL_LIST, name, str(size), ":" + topic)
+ self.sendMessage(irc.RPL_LISTEND, ":End of /LIST")
+
+
+ def irc_LIST(self, prefix, params):
+ """List query
+
+ Return information about the indicated channels, or about all
+ channels if none are specified.
+
+ Parameters: [ <channel> *( "," <channel> ) [ <target> ] ]
+ """
+ #<< list #python
+ #>> :orwell.freenode.net 321 exarkun Channel :Users Name
+ #>> :orwell.freenode.net 322 exarkun #python 358 :The Python programming language
+ #>> :orwell.freenode.net 323 exarkun :End of /LIST
+ if params:
+ # Return information about indicated channels
+ try:
+ channels = params[0].decode(self.encoding).split(',')
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL, params[0],
+ ":No such channel (could not decode your unicode!)")
+ return
+
+ groups = []
+ for ch in channels:
+ if ch.startswith('#'):
+ ch = ch[1:]
+ groups.append(self.realm.lookupGroup(ch))
+
+ groups = defer.DeferredList(groups, consumeErrors=True)
+ groups.addCallback(lambda gs: [r for (s, r) in gs if s])
+ else:
+ # Return information about all channels
+ groups = self.realm.itergroups()
+
+ def cbGroups(groups):
+ def gotSize(size, group):
+ return group.name, size, group.meta.get('topic')
+ d = defer.DeferredList([
+ group.size().addCallback(gotSize, group) for group in groups])
+ d.addCallback(lambda results: self.list([r for (s, r) in results if s]))
+ return d
+ groups.addCallback(cbGroups)
+
+
+ def _channelWho(self, group):
+ self.who(self.name, '#' + group.name,
+ [(m.name, self.hostname, self.realm.name, m.name, "H", 0, m.name) for m in group.iterusers()])
+
+
+ def _userWho(self, user):
+ self.sendMessage(irc.RPL_ENDOFWHO,
+ ":User /WHO not implemented")
+
+
+ def irc_WHO(self, prefix, params):
+ """Who query
+
+ Parameters: [ <mask> [ "o" ] ]
+ """
+ #<< who #python
+ #>> :x.opn 352 glyph #python aquarius pc-62-31-193-114-du.blueyonder.co.uk y.opn Aquarius H :3 Aquarius
+ # ...
+ #>> :x.opn 352 glyph #python foobar europa.tranquility.net z.opn skreech H :0 skreech
+ #>> :x.opn 315 glyph #python :End of /WHO list.
+ ### also
+ #<< who glyph
+ #>> :x.opn 352 glyph #python glyph adsl-64-123-27-108.dsl.austtx.swbell.net x.opn glyph H :0 glyph
+ #>> :x.opn 315 glyph glyph :End of /WHO list.
+ if not params:
+ self.sendMessage(irc.RPL_ENDOFWHO, ":/WHO not supported.")
+ return
+
+ try:
+ channelOrUser = params[0].decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.RPL_ENDOFWHO, params[0],
+ ":End of /WHO list (could not decode your unicode!)")
+ return
+
+ if channelOrUser.startswith('#'):
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ self.sendMessage(
+ irc.RPL_ENDOFWHO, channelOrUser,
+ ":End of /WHO list.")
+ d = self.realm.lookupGroup(channelOrUser[1:])
+ d.addCallbacks(self._channelWho, ebGroup)
+ else:
+ def ebUser(err):
+ err.trap(ewords.NoSuchUser)
+ self.sendMessage(
+ irc.RPL_ENDOFWHO, channelOrUser,
+ ":End of /WHO list.")
+ d = self.realm.lookupUser(channelOrUser)
+ d.addCallbacks(self._userWho, ebUser)
+
+
+
+ def irc_WHOIS(self, prefix, params):
+ """Whois query
+
+ Parameters: [ <target> ] <mask> *( "," <mask> )
+ """
+ def cbUser(user):
+ self.whois(
+ self.name,
+ user.name, user.name, self.realm.name,
+ user.name, self.realm.name, 'Hi mom!', False,
+ int(time() - user.lastMessage), user.signOn,
+ ['#' + group.name for group in user.itergroups()])
+
+ def ebUser(err):
+ err.trap(ewords.NoSuchUser)
+ self.sendMessage(
+ irc.ERR_NOSUCHNICK,
+ params[0],
+ ":No such nick/channel")
+
+ try:
+ user = params[0].decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHNICK,
+ params[0],
+ ":No such nick/channel")
+ return
+
+ self.realm.lookupUser(user).addCallbacks(cbUser, ebUser)
+
+
+ # Unsupported commands, here for legacy compatibility
+ def irc_OPER(self, prefix, params):
+ """Oper message
+
+ Parameters: <name> <password>
+ """
+ self.sendMessage(irc.ERR_NOOPERHOST, ":O-lines not applicable")
+
+
+class IRCFactory(protocol.ServerFactory):
+ """
+ IRC server that creates instances of the L{IRCUser} protocol.
+
+ @ivar _serverInfo: A dictionary mapping:
+ "serviceName" to the name of the server,
+ "serviceVersion" to the copyright version,
+ "creationDate" to the time that the server was started.
+ """
+ protocol = IRCUser
+
+ def __init__(self, realm, portal):
+ self.realm = realm
+ self.portal = portal
+ self._serverInfo = {
+ "serviceName": self.realm.name,
+ "serviceVersion": copyright.version,
+ "creationDate": ctime()
+ }
+
+
+
+class PBMind(pb.Referenceable):
+ def __init__(self):
+ pass
+
+ def jellyFor(self, jellier):
+ return reflect.qual(PBMind), jellier.invoker.registerReference(self)
+
+ def remote_userJoined(self, user, group):
+ pass
+
+ def remote_userLeft(self, user, group, reason):
+ pass
+
+ def remote_receive(self, sender, recipient, message):
+ pass
+
+ def remote_groupMetaUpdate(self, group, meta):
+ pass
+
+
+class PBMindReference(pb.RemoteReference):
+ implements(iwords.IChatClient)
+
+ def receive(self, sender, recipient, message):
+ if iwords.IGroup.providedBy(recipient):
+ rec = PBGroup(self.realm, self.avatar, recipient)
+ else:
+ rec = PBUser(self.realm, self.avatar, recipient)
+ return self.callRemote(
+ 'receive',
+ PBUser(self.realm, self.avatar, sender),
+ rec,
+ message)
+
+ def groupMetaUpdate(self, group, meta):
+ return self.callRemote(
+ 'groupMetaUpdate',
+ PBGroup(self.realm, self.avatar, group),
+ meta)
+
+ def userJoined(self, group, user):
+ return self.callRemote(
+ 'userJoined',
+ PBGroup(self.realm, self.avatar, group),
+ PBUser(self.realm, self.avatar, user))
+
+ def userLeft(self, group, user, reason=None):
+ assert reason is None or isinstance(reason, unicode)
+ return self.callRemote(
+ 'userLeft',
+ PBGroup(self.realm, self.avatar, group),
+ PBUser(self.realm, self.avatar, user),
+ reason)
+pb.setUnjellyableForClass(PBMind, PBMindReference)
+
+
+class PBGroup(pb.Referenceable):
+ def __init__(self, realm, avatar, group):
+ self.realm = realm
+ self.avatar = avatar
+ self.group = group
+
+
+ def processUniqueID(self):
+ return hash((self.realm.name, self.avatar.name, self.group.name))
+
+
+ def jellyFor(self, jellier):
+ return reflect.qual(self.__class__), self.group.name.encode('utf-8'), jellier.invoker.registerReference(self)
+
+
+ def remote_leave(self, reason=None):
+ return self.avatar.leave(self.group, reason)
+
+
+ def remote_send(self, message):
+ return self.avatar.send(self.group, message)
+
+
+class PBGroupReference(pb.RemoteReference):
+ implements(iwords.IGroup)
+
+ def unjellyFor(self, unjellier, unjellyList):
+ clsName, name, ref = unjellyList
+ self.name = name.decode('utf-8')
+ return pb.RemoteReference.unjellyFor(self, unjellier, [clsName, ref])
+
+ def leave(self, reason=None):
+ return self.callRemote("leave", reason)
+
+ def send(self, message):
+ return self.callRemote("send", message)
+pb.setUnjellyableForClass(PBGroup, PBGroupReference)
+
+class PBUser(pb.Referenceable):
+ def __init__(self, realm, avatar, user):
+ self.realm = realm
+ self.avatar = avatar
+ self.user = user
+
+ def processUniqueID(self):
+ return hash((self.realm.name, self.avatar.name, self.user.name))
+
+
+class ChatAvatar(pb.Referenceable):
+ implements(iwords.IChatClient)
+
+ def __init__(self, avatar):
+ self.avatar = avatar
+
+
+ def jellyFor(self, jellier):
+ return reflect.qual(self.__class__), jellier.invoker.registerReference(self)
+
+
+ def remote_join(self, groupName):
+ assert isinstance(groupName, unicode)
+ def cbGroup(group):
+ def cbJoin(ignored):
+ return PBGroup(self.avatar.realm, self.avatar, group)
+ d = self.avatar.join(group)
+ d.addCallback(cbJoin)
+ return d
+ d = self.avatar.realm.getGroup(groupName)
+ d.addCallback(cbGroup)
+ return d
+registerAdapter(ChatAvatar, iwords.IUser, pb.IPerspective)
+
+class AvatarReference(pb.RemoteReference):
+ def join(self, groupName):
+ return self.callRemote('join', groupName)
+
+ def quit(self):
+ d = defer.Deferred()
+ self.broker.notifyOnDisconnect(lambda: d.callback(None))
+ self.broker.transport.loseConnection()
+ return d
+
+pb.setUnjellyableForClass(ChatAvatar, AvatarReference)
+
+
+class WordsRealm(object):
+ implements(portal.IRealm, iwords.IChatService)
+
+ _encoding = 'utf-8'
+
+ def __init__(self, name):
+ self.name = name
+
+
+ def userFactory(self, name):
+ return User(name)
+
+
+ def groupFactory(self, name):
+ return Group(name)
+
+
+ def logoutFactory(self, avatar, facet):
+ def logout():
+ # XXX Deferred support here
+ getattr(facet, 'logout', lambda: None)()
+ avatar.realm = avatar.mind = None
+ return logout
+
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if isinstance(avatarId, str):
+ avatarId = avatarId.decode(self._encoding)
+
+ def gotAvatar(avatar):
+ if avatar.realm is not None:
+ raise ewords.AlreadyLoggedIn()
+ for iface in interfaces:
+ facet = iface(avatar, None)
+ if facet is not None:
+ avatar.loggedIn(self, mind)
+ mind.name = avatarId
+ mind.realm = self
+ mind.avatar = avatar
+ return iface, facet, self.logoutFactory(avatar, facet)
+ raise NotImplementedError(self, interfaces)
+
+ return self.getUser(avatarId).addCallback(gotAvatar)
+
+
+ # IChatService, mostly.
+ createGroupOnRequest = False
+ createUserOnRequest = True
+
+ def lookupUser(self, name):
+ raise NotImplementedError
+
+
+ def lookupGroup(self, group):
+ raise NotImplementedError
+
+
+ def addUser(self, user):
+ """Add the given user to this service.
+
+ This is an internal method intented to be overridden by
+ L{WordsRealm} subclasses, not called by external code.
+
+ @type user: L{IUser}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with C{None} when the user is
+ added, or which fails with
+ L{twisted.words.ewords.DuplicateUser} if a user with the
+ same name exists already.
+ """
+ raise NotImplementedError
+
+
+ def addGroup(self, group):
+ """Add the given group to this service.
+
+ @type group: L{IGroup}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with C{None} when the group is
+ added, or which fails with
+ L{twisted.words.ewords.DuplicateGroup} if a group with the
+ same name exists already.
+ """
+ raise NotImplementedError
+
+
+ def getGroup(self, name):
+ assert isinstance(name, unicode)
+ if self.createGroupOnRequest:
+ def ebGroup(err):
+ err.trap(ewords.DuplicateGroup)
+ return self.lookupGroup(name)
+ return self.createGroup(name).addErrback(ebGroup)
+ return self.lookupGroup(name)
+
+
+ def getUser(self, name):
+ assert isinstance(name, unicode)
+ if self.createUserOnRequest:
+ def ebUser(err):
+ err.trap(ewords.DuplicateUser)
+ return self.lookupUser(name)
+ return self.createUser(name).addErrback(ebUser)
+ return self.lookupUser(name)
+
+
+ def createUser(self, name):
+ assert isinstance(name, unicode)
+ def cbLookup(user):
+ return failure.Failure(ewords.DuplicateUser(name))
+ def ebLookup(err):
+ err.trap(ewords.NoSuchUser)
+ return self.userFactory(name)
+
+ name = name.lower()
+ d = self.lookupUser(name)
+ d.addCallbacks(cbLookup, ebLookup)
+ d.addCallback(self.addUser)
+ return d
+
+
+ def createGroup(self, name):
+ assert isinstance(name, unicode)
+ def cbLookup(group):
+ return failure.Failure(ewords.DuplicateGroup(name))
+ def ebLookup(err):
+ err.trap(ewords.NoSuchGroup)
+ return self.groupFactory(name)
+
+ name = name.lower()
+ d = self.lookupGroup(name)
+ d.addCallbacks(cbLookup, ebLookup)
+ d.addCallback(self.addGroup)
+ return d
+
+
+class InMemoryWordsRealm(WordsRealm):
+ def __init__(self, *a, **kw):
+ super(InMemoryWordsRealm, self).__init__(*a, **kw)
+ self.users = {}
+ self.groups = {}
+
+
+ def itergroups(self):
+ return defer.succeed(self.groups.itervalues())
+
+
+ def addUser(self, user):
+ if user.name in self.users:
+ return defer.fail(failure.Failure(ewords.DuplicateUser()))
+ self.users[user.name] = user
+ return defer.succeed(user)
+
+
+ def addGroup(self, group):
+ if group.name in self.groups:
+ return defer.fail(failure.Failure(ewords.DuplicateGroup()))
+ self.groups[group.name] = group
+ return defer.succeed(group)
+
+
+ def lookupUser(self, name):
+ assert isinstance(name, unicode)
+ name = name.lower()
+ try:
+ user = self.users[name]
+ except KeyError:
+ return defer.fail(failure.Failure(ewords.NoSuchUser(name)))
+ else:
+ return defer.succeed(user)
+
+
+ def lookupGroup(self, name):
+ assert isinstance(name, unicode)
+ name = name.lower()
+ try:
+ group = self.groups[name]
+ except KeyError:
+ return defer.fail(failure.Failure(ewords.NoSuchGroup(name)))
+ else:
+ return defer.succeed(group)
+
+__all__ = [
+ 'Group', 'User',
+
+ 'WordsRealm', 'InMemoryWordsRealm',
+ ]
diff --git a/vendor/Twisted-10.0.0/twisted/words/tap.py b/vendor/Twisted-10.0.0/twisted/words/tap.py
new file mode 100644
index 0000000000..2a6656ee03
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/tap.py
@@ -0,0 +1,72 @@
+# -*- test-case-name: twisted.words.test.test_tap -*-
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+"""
+Shiny new words service maker
+"""
+
+import sys, socket
+
+from twisted.application import strports
+from twisted.application.service import MultiService
+from twisted.python import usage
+from twisted import plugin
+
+from twisted.words import iwords, service
+from twisted.cred import checkers, credentials, portal, strcred
+
+class Options(usage.Options, strcred.AuthOptionMixin):
+ supportedInterfaces = [credentials.IUsernamePassword]
+ optParameters = [
+ ('hostname', None, socket.gethostname(),
+ 'Name of this server; purely an informative')]
+
+ interfacePlugins = {}
+ plg = None
+ for plg in plugin.getPlugins(iwords.IProtocolPlugin):
+ assert plg.name not in interfacePlugins
+ interfacePlugins[plg.name] = plg
+ optParameters.append((
+ plg.name + '-port',
+ None, None,
+ 'strports description of the port to bind for the ' + plg.name + ' server'))
+ del plg
+
+ def __init__(self, *a, **kw):
+ usage.Options.__init__(self, *a, **kw)
+ self['groups'] = []
+
+ def opt_group(self, name):
+ """Specify a group which should exist
+ """
+ self['groups'].append(name.decode(sys.stdin.encoding))
+
+ def opt_passwd(self, filename):
+ """
+ Name of a passwd-style file. (This is for
+ backwards-compatibility only; you should use the --auth
+ command instead.)
+ """
+ self.addChecker(checkers.FilePasswordDB(filename))
+
+def makeService(config):
+ credCheckers = config.get('credCheckers', [])
+ wordsRealm = service.InMemoryWordsRealm(config['hostname'])
+ wordsPortal = portal.Portal(wordsRealm, credCheckers)
+
+ msvc = MultiService()
+
+ # XXX Attribute lookup on config is kind of bad - hrm.
+ for plgName in config.interfacePlugins:
+ port = config.get(plgName + '-port')
+ if port is not None:
+ factory = config.interfacePlugins[plgName].getFactory(wordsRealm, wordsPortal)
+ svc = strports.service(port, factory)
+ svc.setServiceParent(msvc)
+
+ # This is bogus. createGroup is async. makeService must be
+ # allowed to return a Deferred or some crap.
+ for g in config['groups']:
+ wordsRealm.createGroup(g)
+
+ return msvc
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/__init__.py b/vendor/Twisted-10.0.0/twisted/words/test/__init__.py
new file mode 100644
index 0000000000..d599f20dc7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/__init__.py
@@ -0,0 +1 @@
+"Words tests"
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_basesupport.py b/vendor/Twisted-10.0.0/twisted/words/test/test_basesupport.py
new file mode 100644
index 0000000000..00b852b391
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_basesupport.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.trial import unittest
+from twisted.words.im import basesupport
+from twisted.internet import error, defer
+
+class DummyAccount(basesupport.AbstractAccount):
+ """
+ An account object that will do nothing when asked to start to log on.
+ """
+
+ loginHasFailed = False
+ loginCallbackCalled = False
+
+ def _startLogOn(self, *args):
+ """
+ Set self.loginDeferred to the same as the deferred returned, allowing a
+ testcase to .callback or .errback.
+
+ @return: A deferred.
+ """
+ self.loginDeferred = defer.Deferred()
+ return self.loginDeferred
+
+ def _loginFailed(self, result):
+ self.loginHasFailed = True
+ return basesupport.AbstractAccount._loginFailed(self, result)
+
+ def _cb_logOn(self, result):
+ self.loginCallbackCalled = True
+ return basesupport.AbstractAccount._cb_logOn(self, result)
+
+class DummyUI(object):
+ """
+ Provide just the interface required to be passed to AbstractAccount.logOn.
+ """
+ clientRegistered = False
+
+ def registerAccountClient(self, result):
+ self.clientRegistered = True
+
+class ClientMsgTests(unittest.TestCase):
+ def makeUI(self):
+ return DummyUI()
+
+ def makeAccount(self):
+ return DummyAccount('la', False, 'la', None, 'localhost', 6667)
+
+ def test_connect(self):
+ """
+ Test that account.logOn works, and it calls the right callback when a
+ connection is established.
+ """
+ account = self.makeAccount()
+ ui = self.makeUI()
+ d = account.logOn(ui)
+ account.loginDeferred.callback(None)
+
+ def check(result):
+ self.assert_(not account.loginHasFailed,
+ "Login shouldn't have failed")
+ self.assert_(account.loginCallbackCalled,
+ "We should be logged in")
+ d.addCallback(check)
+ return d
+
+ def test_failedConnect(self):
+ """
+ Test that account.logOn works, and it calls the right callback when a
+ connection is established.
+ """
+ account = self.makeAccount()
+ ui = self.makeUI()
+ d = account.logOn(ui)
+ account.loginDeferred.errback(Exception())
+
+ def err(reason):
+ self.assert_(account.loginHasFailed, "Login should have failed")
+ self.assert_(not account.loginCallbackCalled,
+ "We shouldn't be logged in")
+ self.assert_(not ui.clientRegistered,
+ "Client shouldn't be registered in the UI")
+ cb = lambda r: self.assert_(False, "Shouldn't get called back")
+ d.addCallbacks(cb, err)
+ return d
+
+ def test_alreadyConnecting(self):
+ """
+ Test that it can fail sensibly when someone tried to connect before
+ we did.
+ """
+ account = self.makeAccount()
+ ui = self.makeUI()
+ account.logOn(ui)
+ self.assertRaises(error.ConnectError, account.logOn, ui)
+
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_domish.py b/vendor/Twisted-10.0.0/twisted/words/test/test_domish.py
new file mode 100644
index 0000000000..fcff3ee36b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_domish.py
@@ -0,0 +1,421 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.xish.domish}, a DOM-like library for XMPP.
+"""
+
+from twisted.trial import unittest
+from twisted.words.xish import domish
+
+
+class DomishTestCase(unittest.TestCase):
+ def testEscaping(self):
+ s = "&<>'\""
+ self.assertEquals(domish.escapeToXml(s), "&amp;&lt;&gt;'\"")
+ self.assertEquals(domish.escapeToXml(s, 1), "&amp;&lt;&gt;&apos;&quot;")
+
+ def testNamespaceObject(self):
+ ns = domish.Namespace("testns")
+ self.assertEquals(ns.foo, ("testns", "foo"))
+
+ def testElementInit(self):
+ e = domish.Element((None, "foo"))
+ self.assertEquals(e.name, "foo")
+ self.assertEquals(e.uri, None)
+ self.assertEquals(e.defaultUri, None)
+ self.assertEquals(e.parent, None)
+
+ e = domish.Element(("", "foo"))
+ self.assertEquals(e.name, "foo")
+ self.assertEquals(e.uri, "")
+ self.assertEquals(e.defaultUri, "")
+ self.assertEquals(e.parent, None)
+
+ e = domish.Element(("testns", "foo"))
+ self.assertEquals(e.name, "foo")
+ self.assertEquals(e.uri, "testns")
+ self.assertEquals(e.defaultUri, "testns")
+ self.assertEquals(e.parent, None)
+
+ e = domish.Element(("testns", "foo"), "test2ns")
+ self.assertEquals(e.name, "foo")
+ self.assertEquals(e.uri, "testns")
+ self.assertEquals(e.defaultUri, "test2ns")
+
+ def testChildOps(self):
+ e = domish.Element(("testns", "foo"))
+ e.addContent("somecontent")
+ b2 = e.addElement(("testns2", "bar2"))
+ e["attrib1"] = "value1"
+ e[("testns2", "attrib2")] = "value2"
+ e.addElement("bar")
+ e.addElement("bar")
+ e.addContent("abc")
+ e.addContent("123")
+
+ # Check content merging
+ self.assertEquals(e.children[-1], "abc123")
+
+ # Check str()/content extraction
+ self.assertEquals(str(e), "somecontent")
+
+ # Check direct child accessor
+ self.assertEquals(e.bar2, b2)
+ e.bar2.addContent("subcontent")
+ e.bar2["bar2value"] = "somevalue"
+
+ # Check child ops
+ self.assertEquals(e.children[1], e.bar2)
+ self.assertEquals(e.children[2], e.bar)
+
+ # Check attribute ops
+ self.assertEquals(e["attrib1"], "value1")
+ del e["attrib1"]
+ self.assertEquals(e.hasAttribute("attrib1"), 0)
+ self.assertEquals(e.hasAttribute("attrib2"), 0)
+ self.assertEquals(e[("testns2", "attrib2")], "value2")
+
+
+ def test_elements(self):
+ """
+ Calling C{elements} without arguments on a L{domish.Element} returns
+ all child elements, whatever the qualfied name.
+ """
+ e = domish.Element((u"testns", u"foo"))
+ c1 = e.addElement(u"name")
+ c2 = e.addElement((u"testns2", u"baz"))
+ c3 = e.addElement(u"quux")
+ c4 = e.addElement((u"testns", u"name"))
+
+ elts = list(e.elements())
+
+ self.assertIn(c1, elts)
+ self.assertIn(c2, elts)
+ self.assertIn(c3, elts)
+ self.assertIn(c4, elts)
+
+
+ def test_elementsWithQN(self):
+ """
+ Calling C{elements} with a namespace and local name on a
+ L{domish.Element} returns all child elements with that qualified name.
+ """
+ e = domish.Element((u"testns", u"foo"))
+ c1 = e.addElement(u"name")
+ c2 = e.addElement((u"testns2", u"baz"))
+ c3 = e.addElement(u"quux")
+ c4 = e.addElement((u"testns", u"name"))
+
+ elts = list(e.elements(u"testns", u"name"))
+
+ self.assertIn(c1, elts)
+ self.assertNotIn(c2, elts)
+ self.assertNotIn(c3, elts)
+ self.assertIn(c4, elts)
+
+
+
+class DomishStreamTestsMixin:
+ """
+ Mixin defining tests for different stream implementations.
+
+ @ivar streamClass: A no-argument callable which will be used to create an
+ XML parser which can produce a stream of elements from incremental
+ input.
+ """
+ def setUp(self):
+ self.doc_started = False
+ self.doc_ended = False
+ self.root = None
+ self.elements = []
+ self.stream = self.streamClass()
+ self.stream.DocumentStartEvent = self._docStarted
+ self.stream.ElementEvent = self.elements.append
+ self.stream.DocumentEndEvent = self._docEnded
+
+ def _docStarted(self, root):
+ self.root = root
+ self.doc_started = True
+
+ def _docEnded(self):
+ self.doc_ended = True
+
+ def doTest(self, xml):
+ self.stream.parse(xml)
+
+ def testHarness(self):
+ xml = "<root><child/><child2/></root>"
+ self.stream.parse(xml)
+ self.assertEquals(self.doc_started, True)
+ self.assertEquals(self.root.name, 'root')
+ self.assertEquals(self.elements[0].name, 'child')
+ self.assertEquals(self.elements[1].name, 'child2')
+ self.assertEquals(self.doc_ended, True)
+
+ def testBasic(self):
+ xml = "<stream:stream xmlns:stream='etherx' xmlns='jabber'>\n" + \
+ " <message to='bar'>" + \
+ " <x xmlns='xdelay'>some&amp;data&gt;</x>" + \
+ " </message>" + \
+ "</stream:stream>"
+
+ self.stream.parse(xml)
+ self.assertEquals(self.root.name, 'stream')
+ self.assertEquals(self.root.uri, 'etherx')
+ self.assertEquals(self.elements[0].name, 'message')
+ self.assertEquals(self.elements[0].uri, 'jabber')
+ self.assertEquals(self.elements[0]['to'], 'bar')
+ self.assertEquals(self.elements[0].x.uri, 'xdelay')
+ self.assertEquals(unicode(self.elements[0].x), 'some&data>')
+
+ def testNoRootNS(self):
+ xml = "<stream><error xmlns='etherx'/></stream>"
+
+ self.stream.parse(xml)
+ self.assertEquals(self.root.uri, '')
+ self.assertEquals(self.elements[0].uri, 'etherx')
+
+ def testNoDefaultNS(self):
+ xml = "<stream:stream xmlns:stream='etherx'><error/></stream:stream>"""
+
+ self.stream.parse(xml)
+ self.assertEquals(self.root.uri, 'etherx')
+ self.assertEquals(self.root.defaultUri, '')
+ self.assertEquals(self.elements[0].uri, '')
+ self.assertEquals(self.elements[0].defaultUri, '')
+
+ def testChildDefaultNS(self):
+ xml = "<root xmlns='testns'><child/></root>"
+
+ self.stream.parse(xml)
+ self.assertEquals(self.root.uri, 'testns')
+ self.assertEquals(self.elements[0].uri, 'testns')
+
+ def testEmptyChildNS(self):
+ xml = "<root xmlns='testns'><child1><child2 xmlns=''/></child1></root>"
+
+ self.stream.parse(xml)
+ self.assertEquals(self.elements[0].child2.uri, '')
+
+ def testChildPrefix(self):
+ xml = "<root xmlns='testns' xmlns:foo='testns2'><foo:child/></root>"
+
+ self.stream.parse(xml)
+ self.assertEquals(self.root.localPrefixes['foo'], 'testns2')
+ self.assertEquals(self.elements[0].uri, 'testns2')
+
+ def testUnclosedElement(self):
+ self.assertRaises(domish.ParserError, self.stream.parse,
+ "<root><error></root>")
+
+ def test_namespaceReuse(self):
+ """
+ Test that reuse of namespaces does affect an element's serialization.
+
+ When one element uses a prefix for a certain namespace, this is
+ stored in the C{localPrefixes} attribute of the element. We want
+ to make sure that elements created after such use, won't have this
+ prefix end up in their C{localPrefixes} attribute, too.
+ """
+
+ xml = """<root>
+ <foo:child1 xmlns:foo='testns'/>
+ <child2 xmlns='testns'/>
+ </root>"""
+
+ self.stream.parse(xml)
+ self.assertEquals('child1', self.elements[0].name)
+ self.assertEquals('testns', self.elements[0].uri)
+ self.assertEquals('', self.elements[0].defaultUri)
+ self.assertEquals({'foo': 'testns'}, self.elements[0].localPrefixes)
+ self.assertEquals('child2', self.elements[1].name)
+ self.assertEquals('testns', self.elements[1].uri)
+ self.assertEquals('testns', self.elements[1].defaultUri)
+ self.assertEquals({}, self.elements[1].localPrefixes)
+
+
+
+class DomishExpatStreamTestCase(DomishStreamTestsMixin, unittest.TestCase):
+ """
+ Tests for L{domish.ExpatElementStream}, the expat-based element stream
+ implementation.
+ """
+ streamClass = domish.ExpatElementStream
+
+ try:
+ import pyexpat
+ except ImportError:
+ skip = "pyexpat is required for ExpatElementStream tests."
+
+
+
+class DomishSuxStreamTestCase(DomishStreamTestsMixin, unittest.TestCase):
+ """
+ Tests for L{domish.SuxElementStream}, the L{twisted.web.sux}-based element
+ stream implementation.
+ """
+ streamClass = domish.SuxElementStream
+
+ if domish.SuxElementStream is None:
+ skip = "twisted.web is required for SuxElementStream tests."
+
+
+
+class SerializerTests(unittest.TestCase):
+ def testNoNamespace(self):
+ e = domish.Element((None, "foo"))
+ self.assertEquals(e.toXml(), "<foo/>")
+ self.assertEquals(e.toXml(closeElement = 0), "<foo>")
+
+ def testDefaultNamespace(self):
+ e = domish.Element(("testns", "foo"))
+ self.assertEquals(e.toXml(), "<foo xmlns='testns'/>")
+
+ def testOtherNamespace(self):
+ e = domish.Element(("testns", "foo"), "testns2")
+ self.assertEquals(e.toXml({'testns': 'bar'}),
+ "<bar:foo xmlns:bar='testns' xmlns='testns2'/>")
+
+ def testChildDefaultNamespace(self):
+ e = domish.Element(("testns", "foo"))
+ e.addElement("bar")
+ self.assertEquals(e.toXml(), "<foo xmlns='testns'><bar/></foo>")
+
+ def testChildSameNamespace(self):
+ e = domish.Element(("testns", "foo"))
+ e.addElement(("testns", "bar"))
+ self.assertEquals(e.toXml(), "<foo xmlns='testns'><bar/></foo>")
+
+ def testChildSameDefaultNamespace(self):
+ e = domish.Element(("testns", "foo"))
+ e.addElement("bar", "testns")
+ self.assertEquals(e.toXml(), "<foo xmlns='testns'><bar/></foo>")
+
+ def testChildOtherDefaultNamespace(self):
+ e = domish.Element(("testns", "foo"))
+ e.addElement(("testns2", "bar"), 'testns2')
+ self.assertEquals(e.toXml(), "<foo xmlns='testns'><bar xmlns='testns2'/></foo>")
+
+ def testOnlyChildDefaultNamespace(self):
+ e = domish.Element((None, "foo"))
+ e.addElement(("ns2", "bar"), 'ns2')
+ self.assertEquals(e.toXml(), "<foo><bar xmlns='ns2'/></foo>")
+
+ def testOnlyChildDefaultNamespace2(self):
+ e = domish.Element((None, "foo"))
+ e.addElement("bar")
+ self.assertEquals(e.toXml(), "<foo><bar/></foo>")
+
+ def testChildInDefaultNamespace(self):
+ e = domish.Element(("testns", "foo"), "testns2")
+ e.addElement(("testns2", "bar"))
+ self.assertEquals(e.toXml(), "<xn0:foo xmlns:xn0='testns' xmlns='testns2'><bar/></xn0:foo>")
+
+ def testQualifiedAttribute(self):
+ e = domish.Element((None, "foo"),
+ attribs = {("testns2", "bar"): "baz"})
+ self.assertEquals(e.toXml(), "<foo xmlns:xn0='testns2' xn0:bar='baz'/>")
+
+ def testQualifiedAttributeDefaultNS(self):
+ e = domish.Element(("testns", "foo"),
+ attribs = {("testns", "bar"): "baz"})
+ self.assertEquals(e.toXml(), "<foo xmlns='testns' xmlns:xn0='testns' xn0:bar='baz'/>")
+
+ def testTwoChilds(self):
+ e = domish.Element(('', "foo"))
+ child1 = e.addElement(("testns", "bar"), "testns2")
+ child1.addElement(('testns2', 'quux'))
+ child2 = e.addElement(("testns3", "baz"), "testns4")
+ child2.addElement(('testns', 'quux'))
+ self.assertEquals(e.toXml(), "<foo><xn0:bar xmlns:xn0='testns' xmlns='testns2'><quux/></xn0:bar><xn1:baz xmlns:xn1='testns3' xmlns='testns4'><xn0:quux xmlns:xn0='testns'/></xn1:baz></foo>")
+
+ def testXMLNamespace(self):
+ e = domish.Element((None, "foo"),
+ attribs = {("http://www.w3.org/XML/1998/namespace",
+ "lang"): "en_US"})
+ self.assertEquals(e.toXml(), "<foo xml:lang='en_US'/>")
+
+ def testQualifiedAttributeGivenListOfPrefixes(self):
+ e = domish.Element((None, "foo"),
+ attribs = {("testns2", "bar"): "baz"})
+ self.assertEquals(e.toXml({"testns2": "qux"}),
+ "<foo xmlns:qux='testns2' qux:bar='baz'/>")
+
+ def testNSPrefix(self):
+ e = domish.Element((None, "foo"),
+ attribs = {("testns2", "bar"): "baz"})
+ c = e.addElement(("testns2", "qux"))
+ c[("testns2", "bar")] = "quux"
+
+ self.assertEquals(e.toXml(), "<foo xmlns:xn0='testns2' xn0:bar='baz'><xn0:qux xn0:bar='quux'/></foo>")
+
+ def testDefaultNSPrefix(self):
+ e = domish.Element((None, "foo"),
+ attribs = {("testns2", "bar"): "baz"})
+ c = e.addElement(("testns2", "qux"))
+ c[("testns2", "bar")] = "quux"
+ c.addElement('foo')
+
+ self.assertEquals(e.toXml(), "<foo xmlns:xn0='testns2' xn0:bar='baz'><xn0:qux xn0:bar='quux'><xn0:foo/></xn0:qux></foo>")
+
+ def testPrefixScope(self):
+ e = domish.Element(('testns', 'foo'))
+
+ self.assertEquals(e.toXml(prefixes={'testns': 'bar'},
+ prefixesInScope=['bar']),
+ "<bar:foo/>")
+
+ def testLocalPrefixes(self):
+ e = domish.Element(('testns', 'foo'), localPrefixes={'bar': 'testns'})
+ self.assertEquals(e.toXml(), "<bar:foo xmlns:bar='testns'/>")
+
+ def testLocalPrefixesWithChild(self):
+ e = domish.Element(('testns', 'foo'), localPrefixes={'bar': 'testns'})
+ e.addElement('baz')
+ self.assertIdentical(e.baz.defaultUri, None)
+ self.assertEquals(e.toXml(), "<bar:foo xmlns:bar='testns'><baz/></bar:foo>")
+
+ def test_prefixesReuse(self):
+ """
+ Test that prefixes passed to serialization are not modified.
+
+ This test makes sure that passing a dictionary of prefixes repeatedly
+ to C{toXml} of elements does not cause serialization errors. A
+ previous implementation changed the passed in dictionary internally,
+ causing havoc later on.
+ """
+ prefixes = {'testns': 'foo'}
+
+ # test passing of dictionary
+ s = domish.SerializerClass(prefixes=prefixes)
+ self.assertNotIdentical(prefixes, s.prefixes)
+
+ # test proper serialization on prefixes reuse
+ e = domish.Element(('testns2', 'foo'),
+ localPrefixes={'quux': 'testns2'})
+ self.assertEquals("<quux:foo xmlns:quux='testns2'/>",
+ e.toXml(prefixes=prefixes))
+ e = domish.Element(('testns2', 'foo'))
+ self.assertEquals("<foo xmlns='testns2'/>",
+ e.toXml(prefixes=prefixes))
+
+ def testRawXMLSerialization(self):
+ e = domish.Element((None, "foo"))
+ e.addRawXml("<abc123>")
+ # The testcase below should NOT generate valid XML -- that's
+ # the whole point of using the raw XML call -- it's the callers
+ # responsiblity to ensure that the data inserted is valid
+ self.assertEquals(e.toXml(), "<foo><abc123></foo>")
+
+ def testRawXMLWithUnicodeSerialization(self):
+ e = domish.Element((None, "foo"))
+ e.addRawXml(u"<degree>\u00B0</degree>")
+ self.assertEquals(e.toXml(), u"<foo><degree>\u00B0</degree></foo>")
+
+ def testUnicodeSerialization(self):
+ e = domish.Element((None, "foo"))
+ e["test"] = u"my value\u0221e"
+ e.addContent(u"A degree symbol...\u00B0")
+ self.assertEquals(e.toXml(),
+ u"<foo test='my value\u0221e'>A degree symbol...\u00B0</foo>")
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_irc.py b/vendor/Twisted-10.0.0/twisted/words/test/test_irc.py
new file mode 100644
index 0000000000..7a62ef85f2
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_irc.py
@@ -0,0 +1,1566 @@
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.protocols.irc}.
+"""
+
+import time
+
+from twisted.trial import unittest
+from twisted.trial.unittest import TestCase
+from twisted.words.protocols import irc
+from twisted.words.protocols.irc import IRCClient
+from twisted.internet import protocol
+from twisted.test.proto_helpers import StringTransport, StringIOWithoutClosing
+
+
+
+class ModeParsingTests(unittest.TestCase):
+ """
+ Tests for L{twisted.words.protocols.irc.parseModes}.
+ """
+ paramModes = ('klb', 'b')
+
+
+ def test_emptyModes(self):
+ """
+ Parsing an empty mode string raises L{irc.IRCBadModes}.
+ """
+ self.assertRaises(irc.IRCBadModes, irc.parseModes, '', [])
+
+
+ def test_emptyModeSequence(self):
+ """
+ Parsing a mode string that contains an empty sequence (either a C{+} or
+ C{-} followed directly by another C{+} or C{-}, or not followed by
+ anything at all) raises L{irc.IRCBadModes}.
+ """
+ self.assertRaises(irc.IRCBadModes, irc.parseModes, '++k', [])
+ self.assertRaises(irc.IRCBadModes, irc.parseModes, '-+k', [])
+ self.assertRaises(irc.IRCBadModes, irc.parseModes, '+', [])
+ self.assertRaises(irc.IRCBadModes, irc.parseModes, '-', [])
+
+
+ def test_malformedModes(self):
+ """
+ Parsing a mode string that does not start with C{+} or C{-} raises
+ L{irc.IRCBadModes}.
+ """
+ self.assertRaises(irc.IRCBadModes, irc.parseModes, 'foo', [])
+ self.assertRaises(irc.IRCBadModes, irc.parseModes, '%', [])
+
+
+ def test_nullModes(self):
+ """
+ Parsing a mode string that contains no mode characters raises
+ L{irc.IRCBadModes}.
+ """
+ self.assertRaises(irc.IRCBadModes, irc.parseModes, '+', [])
+ self.assertRaises(irc.IRCBadModes, irc.parseModes, '-', [])
+
+
+ def test_singleMode(self):
+ """
+ Parsing a single mode setting with no parameters results in that mode,
+ with no parameters, in the "added" direction and no modes in the
+ "removed" direction.
+ """
+ added, removed = irc.parseModes('+s', [])
+ self.assertEquals(added, [('s', None)])
+ self.assertEquals(removed, [])
+
+ added, removed = irc.parseModes('-s', [])
+ self.assertEquals(added, [])
+ self.assertEquals(removed, [('s', None)])
+
+
+ def test_singleDirection(self):
+ """
+ Parsing a single-direction mode setting with multiple modes and no
+ parameters, results in all modes falling into the same direction group.
+ """
+ added, removed = irc.parseModes('+stn', [])
+ self.assertEquals(added, [('s', None),
+ ('t', None),
+ ('n', None)])
+ self.assertEquals(removed, [])
+
+ added, removed = irc.parseModes('-nt', [])
+ self.assertEquals(added, [])
+ self.assertEquals(removed, [('n', None),
+ ('t', None)])
+
+
+ def test_multiDirection(self):
+ """
+ Parsing a multi-direction mode setting with no parameters.
+ """
+ added, removed = irc.parseModes('+s-n+ti', [])
+ self.assertEquals(added, [('s', None),
+ ('t', None),
+ ('i', None)])
+ self.assertEquals(removed, [('n', None)])
+
+
+ def test_consecutiveDirection(self):
+ """
+ Parsing a multi-direction mode setting containing two consecutive mode
+ sequences with the same direction results in the same result as if
+ there were only one mode sequence in the same direction.
+ """
+ added, removed = irc.parseModes('+sn+ti', [])
+ self.assertEquals(added, [('s', None),
+ ('n', None),
+ ('t', None),
+ ('i', None)])
+ self.assertEquals(removed, [])
+
+
+ def test_mismatchedParams(self):
+ """
+ If the number of mode parameters does not match the number of modes
+ expecting parameters, L{irc.IRCBadModes} is raised.
+ """
+ self.assertRaises(irc.IRCBadModes,
+ irc.parseModes,
+ '+k', [],
+ self.paramModes)
+ self.assertRaises(irc.IRCBadModes,
+ irc.parseModes,
+ '+kl', ['foo', '10', 'lulz_extra_param'],
+ self.paramModes)
+
+
+ def test_parameters(self):
+ """
+ Modes which require parameters are parsed and paired with their relevant
+ parameter, modes which do not require parameters do not consume any of
+ the parameters.
+ """
+ added, removed = irc.parseModes(
+ '+klbb',
+ ['somekey', '42', 'nick!user@host', 'other!*@*'],
+ self.paramModes)
+ self.assertEquals(added, [('k', 'somekey'),
+ ('l', '42'),
+ ('b', 'nick!user@host'),
+ ('b', 'other!*@*')])
+ self.assertEquals(removed, [])
+
+ added, removed = irc.parseModes(
+ '-klbb',
+ ['nick!user@host', 'other!*@*'],
+ self.paramModes)
+ self.assertEquals(added, [])
+ self.assertEquals(removed, [('k', None),
+ ('l', None),
+ ('b', 'nick!user@host'),
+ ('b', 'other!*@*')])
+
+ # Mix a no-argument mode in with argument modes.
+ added, removed = irc.parseModes(
+ '+knbb',
+ ['somekey', 'nick!user@host', 'other!*@*'],
+ self.paramModes)
+ self.assertEquals(added, [('k', 'somekey'),
+ ('n', None),
+ ('b', 'nick!user@host'),
+ ('b', 'other!*@*')])
+ self.assertEquals(removed, [])
+
+
+
+stringSubjects = [
+ "Hello, this is a nice string with no complications.",
+ "xargs%(NUL)smight%(NUL)slike%(NUL)sthis" % {'NUL': irc.NUL },
+ "embedded%(CR)snewline%(CR)s%(NL)sFUN%(NL)s" % {'CR': irc.CR,
+ 'NL': irc.NL},
+ "escape!%(X)s escape!%(M)s %(X)s%(X)sa %(M)s0" % {'X': irc.X_QUOTE,
+ 'M': irc.M_QUOTE}
+ ]
+
+
+class QuotingTest(unittest.TestCase):
+ def test_lowquoteSanity(self):
+ """Testing client-server level quote/dequote"""
+ for s in stringSubjects:
+ self.failUnlessEqual(s, irc.lowDequote(irc.lowQuote(s)))
+
+
+ def test_ctcpquoteSanity(self):
+ """Testing CTCP message level quote/dequote"""
+ for s in stringSubjects:
+ self.failUnlessEqual(s, irc.ctcpDequote(irc.ctcpQuote(s)))
+
+
+
+class Dispatcher(irc._CommandDispatcherMixin):
+ """
+ A dispatcher that exposes one known command and handles unknown commands.
+ """
+ prefix = 'disp'
+
+ def disp_working(self, a, b):
+ """
+ A known command that returns its input.
+ """
+ return a, b
+
+
+ def disp_unknown(self, name, a, b):
+ """
+ Handle unknown commands by returning their name and inputs.
+ """
+ return name, a, b
+
+
+
+class DispatcherTests(unittest.TestCase):
+ """
+ Tests for L{irc._CommandDispatcherMixin}.
+ """
+ def test_dispatch(self):
+ """
+ Dispatching a command invokes the correct handler.
+ """
+ disp = Dispatcher()
+ args = (1, 2)
+ res = disp.dispatch('working', *args)
+ self.assertEquals(res, args)
+
+
+ def test_dispatchUnknown(self):
+ """
+ Dispatching an unknown command invokes the default handler.
+ """
+ disp = Dispatcher()
+ name = 'missing'
+ args = (1, 2)
+ res = disp.dispatch(name, *args)
+ self.assertEquals(res, (name,) + args)
+
+
+ def test_dispatchMissingUnknown(self):
+ """
+ Dispatching an unknown command, when no default handler is present,
+ results in an exception being raised.
+ """
+ disp = Dispatcher()
+ disp.disp_unknown = None
+ self.assertRaises(irc.UnhandledCommand, disp.dispatch, 'bar')
+
+
+
+class ServerSupportedFeatureTests(unittest.TestCase):
+ """
+ Tests for L{ServerSupportedFeatures} and related functions.
+ """
+ def test_intOrDefault(self):
+ """
+ L{_intOrDefault} converts values to C{int} if possible, otherwise
+ returns a default value.
+ """
+ self.assertEquals(irc._intOrDefault(None), None)
+ self.assertEquals(irc._intOrDefault([]), None)
+ self.assertEquals(irc._intOrDefault(''), None)
+ self.assertEquals(irc._intOrDefault('hello', 5), 5)
+ self.assertEquals(irc._intOrDefault('123'), 123)
+ self.assertEquals(irc._intOrDefault(123), 123)
+
+
+ def test_splitParam(self):
+ """
+ L{ServerSupportedFeatures._splitParam} splits ISUPPORT parameters
+ into key and values. Parameters without a separator are split into a
+ key and a list containing only the empty string. Escaped parameters
+ are unescaped.
+ """
+ params = [('FOO', ('FOO', [''])),
+ ('FOO=', ('FOO', [''])),
+ ('FOO=1', ('FOO', ['1'])),
+ ('FOO=1,2,3', ('FOO', ['1', '2', '3'])),
+ ('FOO=A\\x20B', ('FOO', ['A B'])),
+ ('FOO=\\x5Cx', ('FOO', ['\\x'])),
+ ('FOO=\\', ('FOO', ['\\'])),
+ ('FOO=\\n', ('FOO', ['\\n']))]
+
+ _splitParam = irc.ServerSupportedFeatures._splitParam
+
+ for param, expected in params:
+ res = _splitParam(param)
+ self.assertEquals(res, expected)
+
+ self.assertRaises(ValueError, _splitParam, 'FOO=\\x')
+ self.assertRaises(ValueError, _splitParam, 'FOO=\\xNN')
+ self.assertRaises(ValueError, _splitParam, 'FOO=\\xN')
+ self.assertRaises(ValueError, _splitParam, 'FOO=\\x20\\x')
+
+
+ def test_splitParamArgs(self):
+ """
+ L{ServerSupportedFeatures._splitParamArgs} splits ISUPPORT parameter
+ arguments into key and value. Arguments without a separator are
+ split into a key and an empty string.
+ """
+ res = irc.ServerSupportedFeatures._splitParamArgs(['A:1', 'B:2', 'C:', 'D'])
+ self.assertEquals(res, [('A', '1'),
+ ('B', '2'),
+ ('C', ''),
+ ('D', '')])
+
+
+ def test_splitParamArgsProcessor(self):
+ """
+ L{ServerSupportedFeatures._splitParamArgs} uses the argument processor
+ passed to to convert ISUPPORT argument values to some more suitable
+ form.
+ """
+ res = irc.ServerSupportedFeatures._splitParamArgs(['A:1', 'B:2', 'C'],
+ irc._intOrDefault)
+ self.assertEquals(res, [('A', 1),
+ ('B', 2),
+ ('C', None)])
+
+
+ def test_parsePrefixParam(self):
+ """
+ L{ServerSupportedFeatures._parsePrefixParam} parses the ISUPPORT PREFIX
+ parameter into a mapping from modes to prefix symbols, returns
+ C{None} if there is no parseable prefix parameter or raises
+ C{ValueError} if the prefix parameter is malformed.
+ """
+ _parsePrefixParam = irc.ServerSupportedFeatures._parsePrefixParam
+ self.assertEquals(_parsePrefixParam(''), None)
+ self.assertRaises(ValueError, _parsePrefixParam, 'hello')
+ self.assertEquals(_parsePrefixParam('(ov)@+'),
+ {'o': ('@', 0),
+ 'v': ('+', 1)})
+
+
+ def test_parseChanModesParam(self):
+ """
+ L{ServerSupportedFeatures._parseChanModesParam} parses the ISUPPORT
+ CHANMODES parameter into a mapping from mode categories to mode
+ characters. Passing fewer than 4 parameters results in the empty string
+ for the relevant categories. Passing more than 4 parameters raises
+ C{ValueError}.
+ """
+ _parseChanModesParam = irc.ServerSupportedFeatures._parseChanModesParam
+ self.assertEquals(
+ _parseChanModesParam([]),
+ {'addressModes': '',
+ 'param': '',
+ 'setParam': '',
+ 'noParam': ''})
+
+ self.assertEquals(
+ _parseChanModesParam(['b', 'k', 'l', 'imnpst']),
+ {'addressModes': 'b',
+ 'param': 'k',
+ 'setParam': 'l',
+ 'noParam': 'imnpst'})
+
+ self.assertEquals(
+ _parseChanModesParam(['b', 'k', 'l']),
+ {'addressModes': 'b',
+ 'param': 'k',
+ 'setParam': 'l',
+ 'noParam': ''})
+
+ self.assertRaises(
+ ValueError,
+ _parseChanModesParam, ['a', 'b', 'c', 'd', 'e'])
+
+
+ def test_parse(self):
+ """
+ L{ServerSupportedFeatures.parse} changes the internal state of the
+ instance to reflect the features indicated by the parsed ISUPPORT
+ parameters, including unknown parameters and unsetting previously set
+ parameters.
+ """
+ supported = irc.ServerSupportedFeatures()
+ supported.parse(['MODES=4',
+ 'CHANLIMIT=#:20,&:10',
+ 'INVEX',
+ 'EXCEPTS=Z',
+ 'UNKNOWN=A,B,C'])
+
+ self.assertEquals(supported.getFeature('MODES'), 4)
+ self.assertEquals(supported.getFeature('CHANLIMIT'),
+ [('#', 20),
+ ('&', 10)])
+ self.assertEquals(supported.getFeature('INVEX'), 'I')
+ self.assertEquals(supported.getFeature('EXCEPTS'), 'Z')
+ self.assertEquals(supported.getFeature('UNKNOWN'), ('A', 'B', 'C'))
+
+ self.assertTrue(supported.hasFeature('INVEX'))
+ supported.parse(['-INVEX'])
+ self.assertFalse(supported.hasFeature('INVEX'))
+ # Unsetting a previously unset parameter should not be a problem.
+ supported.parse(['-INVEX'])
+
+
+ def _parse(self, features):
+ """
+ Parse all specified features according to the ISUPPORT specifications.
+
+ @type features: C{list} of C{(featureName, value)}
+ @param features: Feature names and values to parse
+
+ @rtype: L{irc.ServerSupportedFeatures}
+ """
+ supported = irc.ServerSupportedFeatures()
+ features = ['%s=%s' % (name, value or '')
+ for name, value in features]
+ supported.parse(features)
+ return supported
+
+
+ def _parseFeature(self, name, value=None):
+ """
+ Parse a feature, with the given name and value, according to the
+ ISUPPORT specifications and return the parsed value.
+ """
+ supported = self._parse([(name, value)])
+ return supported.getFeature(name)
+
+
+ def _testIntOrDefaultFeature(self, name, default=None):
+ """
+ Perform some common tests on a feature known to use L{_intOrDefault}.
+ """
+ self.assertEquals(
+ self._parseFeature(name, None),
+ default)
+ self.assertEquals(
+ self._parseFeature(name, 'notanint'),
+ default)
+ self.assertEquals(
+ self._parseFeature(name, '42'),
+ 42)
+
+
+ def _testFeatureDefault(self, name, features=None):
+ """
+ Features known to have default values are reported as being present by
+ L{irc.ServerSupportedFeatures.hasFeature}, and their value defaults
+ correctly, when they don't appear in an ISUPPORT message.
+ """
+ default = irc.ServerSupportedFeatures()._features[name]
+
+ if features is None:
+ features = [('DEFINITELY_NOT', 'a_feature')]
+
+ supported = self._parse(features)
+ self.assertTrue(supported.hasFeature(name))
+ self.assertEquals(supported.getFeature(name), default)
+
+
+ def test_support_CHANMODES(self):
+ """
+ The CHANMODES ISUPPORT parameter is parsed into a C{dict} giving the
+ four mode categories, C{'addressModes'}, C{'param'}, C{'setParam'}, and
+ C{'noParam'}.
+ """
+ self._testFeatureDefault('CHANMODES')
+ self._testFeatureDefault('CHANMODES', [('CHANMODES', 'b,,lk,')])
+ self._testFeatureDefault('CHANMODES', [('CHANMODES', 'b,,lk,ha,ha')])
+
+ self.assertEquals(
+ self._parseFeature('CHANMODES', ''),
+ {'addressModes': '',
+ 'param': '',
+ 'setParam': '',
+ 'noParam': ''})
+
+ self.assertEquals(
+ self._parseFeature('CHANMODES', ',A'),
+ {'addressModes': '',
+ 'param': 'A',
+ 'setParam': '',
+ 'noParam': ''})
+
+ self.assertEquals(
+ self._parseFeature('CHANMODES', 'A,Bc,Def,Ghij'),
+ {'addressModes': 'A',
+ 'param': 'Bc',
+ 'setParam': 'Def',
+ 'noParam': 'Ghij'})
+
+
+ def test_support_IDCHAN(self):
+ """
+ The IDCHAN support parameter is parsed into a sequence of two-tuples
+ giving channel prefix and ID length pairs.
+ """
+ self.assertEquals(
+ self._parseFeature('IDCHAN', '!:5'),
+ [('!', '5')])
+
+
+ def test_support_MAXLIST(self):
+ """
+ The MAXLIST support parameter is parsed into a sequence of two-tuples
+ giving modes and their limits.
+ """
+ self.assertEquals(
+ self._parseFeature('MAXLIST', 'b:25,eI:50'),
+ [('b', 25), ('eI', 50)])
+ # A non-integer parameter argument results in None.
+ self.assertEquals(
+ self._parseFeature('MAXLIST', 'b:25,eI:50,a:3.1415'),
+ [('b', 25), ('eI', 50), ('a', None)])
+ self.assertEquals(
+ self._parseFeature('MAXLIST', 'b:25,eI:50,a:notanint'),
+ [('b', 25), ('eI', 50), ('a', None)])
+
+
+ def test_support_NETWORK(self):
+ """
+ The NETWORK support parameter is parsed as the network name, as
+ specified by the server.
+ """
+ self.assertEquals(
+ self._parseFeature('NETWORK', 'IRCNet'),
+ 'IRCNet')
+
+
+ def test_support_SAFELIST(self):
+ """
+ The SAFELIST support parameter is parsed into a boolean indicating
+ whether the safe "list" command is supported or not.
+ """
+ self.assertEquals(
+ self._parseFeature('SAFELIST'),
+ True)
+
+
+ def test_support_STATUSMSG(self):
+ """
+ The STATUSMSG support parameter is parsed into a string of channel
+ status that support the exclusive channel notice method.
+ """
+ self.assertEquals(
+ self._parseFeature('STATUSMSG', '@+'),
+ '@+')
+
+
+ def test_support_TARGMAX(self):
+ """
+ The TARGMAX support parameter is parsed into a dictionary, mapping
+ strings to integers, of the maximum number of targets for a particular
+ command.
+ """
+ self.assertEquals(
+ self._parseFeature('TARGMAX', 'PRIVMSG:4,NOTICE:3'),
+ {'PRIVMSG': 4,
+ 'NOTICE': 3})
+ # A non-integer parameter argument results in None.
+ self.assertEquals(
+ self._parseFeature('TARGMAX', 'PRIVMSG:4,NOTICE:3,KICK:3.1415'),
+ {'PRIVMSG': 4,
+ 'NOTICE': 3,
+ 'KICK': None})
+ self.assertEquals(
+ self._parseFeature('TARGMAX', 'PRIVMSG:4,NOTICE:3,KICK:notanint'),
+ {'PRIVMSG': 4,
+ 'NOTICE': 3,
+ 'KICK': None})
+
+
+ def test_support_NICKLEN(self):
+ """
+ The NICKLEN support parameter is parsed into an integer value
+ indicating the maximum length of a nickname the client may use,
+ otherwise, if the parameter is missing or invalid, the default value
+ (as specified by RFC 1459) is used.
+ """
+ default = irc.ServerSupportedFeatures()._features['NICKLEN']
+ self._testIntOrDefaultFeature('NICKLEN', default)
+
+
+ def test_support_CHANNELLEN(self):
+ """
+ The CHANNELLEN support parameter is parsed into an integer value
+ indicating the maximum channel name length, otherwise, if the
+ parameter is missing or invalid, the default value (as specified by
+ RFC 1459) is used.
+ """
+ default = irc.ServerSupportedFeatures()._features['CHANNELLEN']
+ self._testIntOrDefaultFeature('CHANNELLEN', default)
+
+
+ def test_support_CHANTYPES(self):
+ """
+ The CHANTYPES support parameter is parsed into a tuple of
+ valid channel prefix characters.
+ """
+ self._testFeatureDefault('CHANTYPES')
+
+ self.assertEquals(
+ self._parseFeature('CHANTYPES', '#&%'),
+ ('#', '&', '%'))
+
+
+ def test_support_KICKLEN(self):
+ """
+ The KICKLEN support parameter is parsed into an integer value
+ indicating the maximum length of a kick message a client may use.
+ """
+ self._testIntOrDefaultFeature('KICKLEN')
+
+
+ def test_support_PREFIX(self):
+ """
+ The PREFIX support parameter is parsed into a dictionary mapping
+ modes to two-tuples of status symbol and priority.
+ """
+ self._testFeatureDefault('PREFIX')
+ self._testFeatureDefault('PREFIX', [('PREFIX', 'hello')])
+
+ self.assertEquals(
+ self._parseFeature('PREFIX', None),
+ None)
+ self.assertEquals(
+ self._parseFeature('PREFIX', '(ohv)@%+'),
+ {'o': ('@', 0),
+ 'h': ('%', 1),
+ 'v': ('+', 2)})
+ self.assertEquals(
+ self._parseFeature('PREFIX', '(hov)@%+'),
+ {'o': ('%', 1),
+ 'h': ('@', 0),
+ 'v': ('+', 2)})
+
+
+ def test_support_TOPICLEN(self):
+ """
+ The TOPICLEN support parameter is parsed into an integer value
+ indicating the maximum length of a topic a client may set.
+ """
+ self._testIntOrDefaultFeature('TOPICLEN')
+
+
+ def test_support_MODES(self):
+ """
+ The MODES support parameter is parsed into an integer value
+ indicating the maximum number of "variable" modes (defined as being
+ modes from C{addressModes}, C{param} or C{setParam} categories for
+ the C{CHANMODES} ISUPPORT parameter) which may by set on a channel
+ by a single MODE command from a client.
+ """
+ self._testIntOrDefaultFeature('MODES')
+
+
+ def test_support_EXCEPTS(self):
+ """
+ The EXCEPTS support parameter is parsed into the mode character
+ to be used for "ban exception" modes. If no parameter is specified
+ then the character C{e} is assumed.
+ """
+ self.assertEquals(
+ self._parseFeature('EXCEPTS', 'Z'),
+ 'Z')
+ self.assertEquals(
+ self._parseFeature('EXCEPTS'),
+ 'e')
+
+
+ def test_support_INVEX(self):
+ """
+ The INVEX support parameter is parsed into the mode character to be
+ used for "invite exception" modes. If no parameter is specified then
+ the character C{I} is assumed.
+ """
+ self.assertEquals(
+ self._parseFeature('INVEX', 'Z'),
+ 'Z')
+ self.assertEquals(
+ self._parseFeature('INVEX'),
+ 'I')
+
+
+
+class IRCClientWithoutLogin(irc.IRCClient):
+ performLogin = 0
+
+
+class CTCPTest(unittest.TestCase):
+ def setUp(self):
+ self.file = StringIOWithoutClosing()
+ self.transport = protocol.FileWrapper(self.file)
+ self.client = IRCClientWithoutLogin()
+ self.client.makeConnection(self.transport)
+
+
+ def test_ERRMSG(self):
+ """Testing CTCP query ERRMSG.
+
+ Not because this is this is an especially important case in the
+ field, but it does go through the entire dispatch/decode/encode
+ process.
+ """
+
+ errQuery = (":nick!guy@over.there PRIVMSG #theChan :"
+ "%(X)cERRMSG t%(X)c%(EOL)s"
+ % {'X': irc.X_DELIM,
+ 'EOL': irc.CR + irc.LF})
+
+ errReply = ("NOTICE nick :%(X)cERRMSG t :"
+ "No error has occoured.%(X)c%(EOL)s"
+ % {'X': irc.X_DELIM,
+ 'EOL': irc.CR + irc.LF})
+
+ self.client.dataReceived(errQuery)
+ reply = self.file.getvalue()
+
+ self.failUnlessEqual(errReply, reply)
+
+
+ def test_noNumbersVERSION(self):
+ """
+ If attributes for version information on L{IRCClient} are set to
+ C{None}, the parts of the CTCP VERSION response they correspond to
+ are omitted.
+ """
+ self.client.versionName = "FrobozzIRC"
+ self.client.ctcpQuery_VERSION("nick!guy@over.there", "#theChan", None)
+ versionReply = ("NOTICE nick :%(X)cVERSION %(vname)s::"
+ "%(X)c%(EOL)s"
+ % {'X': irc.X_DELIM,
+ 'EOL': irc.CR + irc.LF,
+ 'vname': self.client.versionName})
+ reply = self.file.getvalue()
+ self.assertEquals(versionReply, reply)
+
+
+ def test_fullVERSION(self):
+ """
+ The response to a CTCP VERSION query includes the version number and
+ environment information, as specified by L{IRCClient.versionNum} and
+ L{IRCClient.versionEnv}.
+ """
+ self.client.versionName = "FrobozzIRC"
+ self.client.versionNum = "1.2g"
+ self.client.versionEnv = "ZorkOS"
+ self.client.ctcpQuery_VERSION("nick!guy@over.there", "#theChan", None)
+ versionReply = ("NOTICE nick :%(X)cVERSION %(vname)s:%(vnum)s:%(venv)s"
+ "%(X)c%(EOL)s"
+ % {'X': irc.X_DELIM,
+ 'EOL': irc.CR + irc.LF,
+ 'vname': self.client.versionName,
+ 'vnum': self.client.versionNum,
+ 'venv': self.client.versionEnv})
+ reply = self.file.getvalue()
+ self.assertEquals(versionReply, reply)
+
+
+ def tearDown(self):
+ self.transport.loseConnection()
+ self.client.connectionLost()
+ del self.client
+ del self.transport
+
+class NoticingClient(IRCClientWithoutLogin, object):
+ methods = {
+ 'created': ('when',),
+ 'yourHost': ('info',),
+ 'myInfo': ('servername', 'version', 'umodes', 'cmodes'),
+ 'luserClient': ('info',),
+ 'bounce': ('info',),
+ 'isupport': ('options',),
+ 'luserChannels': ('channels',),
+ 'luserOp': ('ops',),
+ 'luserMe': ('info',),
+ 'receivedMOTD': ('motd',),
+
+ 'privmsg': ('user', 'channel', 'message'),
+ 'joined': ('channel',),
+ 'left': ('channel',),
+ 'noticed': ('user', 'channel', 'message'),
+ 'modeChanged': ('user', 'channel', 'set', 'modes', 'args'),
+ 'pong': ('user', 'secs'),
+ 'signedOn': (),
+ 'kickedFrom': ('channel', 'kicker', 'message'),
+ 'nickChanged': ('nick',),
+
+ 'userJoined': ('user', 'channel'),
+ 'userLeft': ('user', 'channel'),
+ 'userKicked': ('user', 'channel', 'kicker', 'message'),
+ 'action': ('user', 'channel', 'data'),
+ 'topicUpdated': ('user', 'channel', 'newTopic'),
+ 'userRenamed': ('oldname', 'newname')}
+
+
+ def __init__(self, *a, **kw):
+ # It is important that IRCClient.__init__ is not called since
+ # traditionally it did not exist, so it is important that nothing is
+ # initialised there that would prevent subclasses that did not (or
+ # could not) invoke the base implementation. Any protocol
+ # initialisation should happen in connectionMode.
+ self.calls = []
+
+
+ def __getattribute__(self, name):
+ if name.startswith('__') and name.endswith('__'):
+ return super(NoticingClient, self).__getattribute__(name)
+ try:
+ args = super(NoticingClient, self).__getattribute__('methods')[name]
+ except KeyError:
+ return super(NoticingClient, self).__getattribute__(name)
+ else:
+ return self.makeMethod(name, args)
+
+
+ def makeMethod(self, fname, args):
+ def method(*a, **kw):
+ if len(a) > len(args):
+ raise TypeError("TypeError: %s() takes %d arguments "
+ "(%d given)" % (fname, len(args), len(a)))
+ for (name, value) in zip(args, a):
+ if name in kw:
+ raise TypeError("TypeError: %s() got multiple values "
+ "for keyword argument '%s'" % (fname, name))
+ else:
+ kw[name] = value
+ if len(kw) != len(args):
+ raise TypeError("TypeError: %s() takes %d arguments "
+ "(%d given)" % (fname, len(args), len(a)))
+ self.calls.append((fname, kw))
+ return method
+
+
+def pop(dict, key, default):
+ try:
+ value = dict[key]
+ except KeyError:
+ return default
+ else:
+ del dict[key]
+ return value
+
+class ClientImplementationTests(unittest.TestCase):
+ def setUp(self):
+ self.file = StringIOWithoutClosing()
+ self.transport = protocol.FileWrapper(self.file)
+ self.client = NoticingClient()
+ self.client.makeConnection(self.transport)
+
+
+ def tearDown(self):
+ self.transport.loseConnection()
+ self.client.connectionLost()
+ del self.client
+ del self.transport
+
+
+ def _serverTestImpl(self, code, msg, func, **kw):
+ host = pop(kw, 'host', 'server.host')
+ nick = pop(kw, 'nick', 'nickname')
+ args = pop(kw, 'args', '')
+
+ message = (":" +
+ host + " " +
+ code + " " +
+ nick + " " +
+ args + " :" +
+ msg + "\r\n")
+
+ self.client.dataReceived(message)
+ self.assertEquals(
+ self.client.calls,
+ [(func, kw)])
+
+
+ def testYourHost(self):
+ msg = "Your host is some.host[blah.blah/6667], running version server-version-3"
+ self._serverTestImpl("002", msg, "yourHost", info=msg)
+
+
+ def testCreated(self):
+ msg = "This server was cobbled together Fri Aug 13 18:00:25 UTC 2004"
+ self._serverTestImpl("003", msg, "created", when=msg)
+
+
+ def testMyInfo(self):
+ msg = "server.host server-version abcDEF bcdEHI"
+ self._serverTestImpl("004", msg, "myInfo",
+ servername="server.host",
+ version="server-version",
+ umodes="abcDEF",
+ cmodes="bcdEHI")
+
+
+ def testLuserClient(self):
+ msg = "There are 9227 victims and 9542 hiding on 24 servers"
+ self._serverTestImpl("251", msg, "luserClient",
+ info=msg)
+
+
+ def _sendISUPPORT(self):
+ args = ("MODES=4 CHANLIMIT=#:20 NICKLEN=16 USERLEN=10 HOSTLEN=63 "
+ "TOPICLEN=450 KICKLEN=450 CHANNELLEN=30 KEYLEN=23 CHANTYPES=# "
+ "PREFIX=(ov)@+ CASEMAPPING=ascii CAPAB IRCD=dancer")
+ msg = "are available on this server"
+ self._serverTestImpl("005", msg, "isupport", args=args,
+ options=['MODES=4',
+ 'CHANLIMIT=#:20',
+ 'NICKLEN=16',
+ 'USERLEN=10',
+ 'HOSTLEN=63',
+ 'TOPICLEN=450',
+ 'KICKLEN=450',
+ 'CHANNELLEN=30',
+ 'KEYLEN=23',
+ 'CHANTYPES=#',
+ 'PREFIX=(ov)@+',
+ 'CASEMAPPING=ascii',
+ 'CAPAB',
+ 'IRCD=dancer'])
+
+
+ def test_ISUPPORT(self):
+ """
+ The client parses ISUPPORT messages sent by the server and calls
+ L{IRCClient.isupport}.
+ """
+ self._sendISUPPORT()
+
+
+ def testBounce(self):
+ msg = "Try server some.host, port 321"
+ self._serverTestImpl("010", msg, "bounce",
+ info=msg)
+
+
+ def testLuserChannels(self):
+ args = "7116"
+ msg = "channels formed"
+ self._serverTestImpl("254", msg, "luserChannels", args=args,
+ channels=int(args))
+
+
+ def testLuserOp(self):
+ args = "34"
+ msg = "flagged staff members"
+ self._serverTestImpl("252", msg, "luserOp", args=args,
+ ops=int(args))
+
+
+ def testLuserMe(self):
+ msg = "I have 1937 clients and 0 servers"
+ self._serverTestImpl("255", msg, "luserMe",
+ info=msg)
+
+
+ def test_receivedMOTD(self):
+ """
+ Lines received in I{RPL_MOTDSTART} and I{RPL_MOTD} are delivered to
+ L{IRCClient.receivedMOTD} when I{RPL_ENDOFMOTD} is received.
+ """
+ lines = [
+ ":host.name 375 nickname :- host.name Message of the Day -",
+ ":host.name 372 nickname :- Welcome to host.name",
+ ":host.name 376 nickname :End of /MOTD command."]
+ for L in lines:
+ self.assertEquals(self.client.calls, [])
+ self.client.dataReceived(L + '\r\n')
+
+ self.assertEquals(
+ self.client.calls,
+ [("receivedMOTD", {"motd": ["host.name Message of the Day -", "Welcome to host.name"]})])
+
+ # After the motd is delivered, the tracking variable should be
+ # reset.
+ self.assertIdentical(self.client.motd, None)
+
+
+ def test_withoutMOTDSTART(self):
+ """
+ If L{IRCClient} receives I{RPL_MOTD} and I{RPL_ENDOFMOTD} without
+ receiving I{RPL_MOTDSTART}, L{IRCClient.receivedMOTD} is still
+ called with a list of MOTD lines.
+ """
+ lines = [
+ ":host.name 372 nickname :- Welcome to host.name",
+ ":host.name 376 nickname :End of /MOTD command."]
+
+ for L in lines:
+ self.client.dataReceived(L + '\r\n')
+
+ self.assertEquals(
+ self.client.calls,
+ [("receivedMOTD", {"motd": ["Welcome to host.name"]})])
+
+
+ def _clientTestImpl(self, sender, group, type, msg, func, **kw):
+ ident = pop(kw, 'ident', 'ident')
+ host = pop(kw, 'host', 'host')
+
+ wholeUser = sender + '!' + ident + '@' + host
+ message = (":" +
+ wholeUser + " " +
+ type + " " +
+ group + " :" +
+ msg + "\r\n")
+ self.client.dataReceived(message)
+ self.assertEquals(
+ self.client.calls,
+ [(func, kw)])
+ self.client.calls = []
+
+
+ def testPrivmsg(self):
+ msg = "Tooty toot toot."
+ self._clientTestImpl("sender", "#group", "PRIVMSG", msg, "privmsg",
+ ident="ident", host="host",
+ # Expected results below
+ user="sender!ident@host",
+ channel="#group",
+ message=msg)
+
+ self._clientTestImpl("sender", "recipient", "PRIVMSG", msg, "privmsg",
+ ident="ident", host="host",
+ # Expected results below
+ user="sender!ident@host",
+ channel="recipient",
+ message=msg)
+
+
+ def test_getChannelModeParams(self):
+ """
+ L{IRCClient.getChannelModeParams} uses ISUPPORT information, either
+ given by the server or defaults, to determine which channel modes
+ require arguments when being added or removed.
+ """
+ add, remove = map(sorted, self.client.getChannelModeParams())
+ self.assertEquals(add, ['b', 'h', 'k', 'l', 'o', 'v'])
+ self.assertEquals(remove, ['b', 'h', 'o', 'v'])
+
+ def removeFeature(name):
+ name = '-' + name
+ msg = "are available on this server"
+ self._serverTestImpl(
+ '005', msg, 'isupport', args=name, options=[name])
+ self.assertIdentical(
+ self.client.supported.getFeature(name), None)
+ self.client.calls = []
+
+ # Remove CHANMODES feature, causing getFeature('CHANMODES') to return
+ # None.
+ removeFeature('CHANMODES')
+ add, remove = map(sorted, self.client.getChannelModeParams())
+ self.assertEquals(add, ['h', 'o', 'v'])
+ self.assertEquals(remove, ['h', 'o', 'v'])
+
+ # Remove PREFIX feature, causing getFeature('PREFIX') to return None.
+ removeFeature('PREFIX')
+ add, remove = map(sorted, self.client.getChannelModeParams())
+ self.assertEquals(add, [])
+ self.assertEquals(remove, [])
+
+ # Restore ISUPPORT features.
+ self._sendISUPPORT()
+ self.assertNotIdentical(
+ self.client.supported.getFeature('PREFIX'), None)
+
+
+ def test_getUserModeParams(self):
+ """
+ L{IRCClient.getUserModeParams} returns a list of user modes (modes that
+ the user sets on themself, outside of channel modes) that require
+ parameters when added and removed, respectively.
+ """
+ add, remove = map(sorted, self.client.getUserModeParams())
+ self.assertEquals(add, [])
+ self.assertEquals(remove, [])
+
+
+ def _sendModeChange(self, msg, args='', target=None):
+ """
+ Build a MODE string and send it to the client.
+ """
+ if target is None:
+ target = '#chan'
+ message = ":Wolf!~wolf@yok.utu.fi MODE %s %s %s\r\n" % (
+ target, msg, args)
+ self.client.dataReceived(message)
+
+
+ def _parseModeChange(self, results, target=None):
+ """
+ Parse the results, do some test and return the data to check.
+ """
+ if target is None:
+ target = '#chan'
+
+ for n, result in enumerate(results):
+ method, data = result
+ self.assertEquals(method, 'modeChanged')
+ self.assertEquals(data['user'], 'Wolf!~wolf@yok.utu.fi')
+ self.assertEquals(data['channel'], target)
+ results[n] = tuple([data[key] for key in ('set', 'modes', 'args')])
+ return results
+
+
+ def _checkModeChange(self, expected, target=None):
+ """
+ Compare the expected result with the one returned by the client.
+ """
+ result = self._parseModeChange(self.client.calls, target)
+ self.assertEquals(result, expected)
+ self.client.calls = []
+
+
+ def test_modeMissingDirection(self):
+ """
+ Mode strings that do not begin with a directional character, C{'+'} or
+ C{'-'}, have C{'+'} automatically prepended.
+ """
+ self._sendModeChange('s')
+ self._checkModeChange([(True, 's', (None,))])
+
+
+ def test_noModeParameters(self):
+ """
+ No parameters are passed to L{IRCClient.modeChanged} for modes that
+ don't take any parameters.
+ """
+ self._sendModeChange('-s')
+ self._checkModeChange([(False, 's', (None,))])
+ self._sendModeChange('+n')
+ self._checkModeChange([(True, 'n', (None,))])
+
+
+ def test_oneModeParameter(self):
+ """
+ Parameters are passed to L{IRCClient.modeChanged} for modes that take
+ parameters.
+ """
+ self._sendModeChange('+o', 'a_user')
+ self._checkModeChange([(True, 'o', ('a_user',))])
+ self._sendModeChange('-o', 'a_user')
+ self._checkModeChange([(False, 'o', ('a_user',))])
+
+
+ def test_mixedModes(self):
+ """
+ Mixing adding and removing modes that do and don't take parameters
+ invokes L{IRCClient.modeChanged} with mode characters and parameters
+ that match up.
+ """
+ self._sendModeChange('+osv', 'a_user another_user')
+ self._checkModeChange([(True, 'osv', ('a_user', None, 'another_user'))])
+ self._sendModeChange('+v-os', 'a_user another_user')
+ self._checkModeChange([(True, 'v', ('a_user',)),
+ (False, 'os', ('another_user', None))])
+
+
+ def test_tooManyModeParameters(self):
+ """
+ Passing an argument to modes that take no parameters results in
+ L{IRCClient.modeChanged} not being called and an error being logged.
+ """
+ self._sendModeChange('+s', 'wrong')
+ self._checkModeChange([])
+ errors = self.flushLoggedErrors(irc.IRCBadModes)
+ self.assertEquals(len(errors), 1)
+ self.assertSubstring(
+ 'Too many parameters', errors[0].getErrorMessage())
+
+
+ def test_tooFewModeParameters(self):
+ """
+ Passing no arguments to modes that do take parameters results in
+ L{IRCClient.modeChange} not being called and an error being logged.
+ """
+ self._sendModeChange('+o')
+ self._checkModeChange([])
+ errors = self.flushLoggedErrors(irc.IRCBadModes)
+ self.assertEquals(len(errors), 1)
+ self.assertSubstring(
+ 'Not enough parameters', errors[0].getErrorMessage())
+
+
+ def test_userMode(self):
+ """
+ A C{MODE} message whose target is our user (the nickname of our user,
+ to be precise), as opposed to a channel, will be parsed according to
+ the modes specified by L{IRCClient.getUserModeParams}.
+ """
+ target = self.client.nickname
+ # Mode "o" on channels is supposed to take a parameter, but since this
+ # is not a channel this will not cause an exception.
+ self._sendModeChange('+o', target=target)
+ self._checkModeChange([(True, 'o', (None,))], target=target)
+
+ def getUserModeParams():
+ return ['Z', '']
+
+ # Introduce our own user mode that takes an argument.
+ self.patch(self.client, 'getUserModeParams', getUserModeParams)
+
+ self._sendModeChange('+Z', 'an_arg', target=target)
+ self._checkModeChange([(True, 'Z', ('an_arg',))], target=target)
+
+
+
+class BasicServerFunctionalityTestCase(unittest.TestCase):
+ def setUp(self):
+ self.f = StringIOWithoutClosing()
+ self.t = protocol.FileWrapper(self.f)
+ self.p = irc.IRC()
+ self.p.makeConnection(self.t)
+
+
+ def check(self, s):
+ self.assertEquals(self.f.getvalue(), s)
+
+
+ def testPrivmsg(self):
+ self.p.privmsg("this-is-sender", "this-is-recip", "this is message")
+ self.check(":this-is-sender PRIVMSG this-is-recip :this is message\r\n")
+
+
+ def testNotice(self):
+ self.p.notice("this-is-sender", "this-is-recip", "this is notice")
+ self.check(":this-is-sender NOTICE this-is-recip :this is notice\r\n")
+
+
+ def testAction(self):
+ self.p.action("this-is-sender", "this-is-recip", "this is action")
+ self.check(":this-is-sender ACTION this-is-recip :this is action\r\n")
+
+
+ def testJoin(self):
+ self.p.join("this-person", "#this-channel")
+ self.check(":this-person JOIN #this-channel\r\n")
+
+
+ def testPart(self):
+ self.p.part("this-person", "#that-channel")
+ self.check(":this-person PART #that-channel\r\n")
+
+
+ def testWhois(self):
+ """
+ Verify that a whois by the client receives the right protocol actions
+ from the server.
+ """
+ timestamp = int(time.time()-100)
+ hostname = self.p.hostname
+ req = 'requesting-nick'
+ targ = 'target-nick'
+ self.p.whois(req, targ, 'target', 'host.com',
+ 'Target User', 'irc.host.com', 'A fake server', False,
+ 12, timestamp, ['#fakeusers', '#fakemisc'])
+ expected = '\r\n'.join([
+':%(hostname)s 311 %(req)s %(targ)s target host.com * :Target User',
+':%(hostname)s 312 %(req)s %(targ)s irc.host.com :A fake server',
+':%(hostname)s 317 %(req)s %(targ)s 12 %(timestamp)s :seconds idle, signon time',
+':%(hostname)s 319 %(req)s %(targ)s :#fakeusers #fakemisc',
+':%(hostname)s 318 %(req)s %(targ)s :End of WHOIS list.',
+'']) % dict(hostname=hostname, timestamp=timestamp, req=req, targ=targ)
+ self.check(expected)
+
+
+class DummyClient(irc.IRCClient):
+ def __init__(self):
+ self.lines = []
+ def sendLine(self, m):
+ self.lines.append(m)
+
+
+class ClientMsgTests(unittest.TestCase):
+ def setUp(self):
+ self.client = DummyClient()
+
+
+ def testSingleLine(self):
+ self.client.msg('foo', 'bar')
+ self.assertEquals(self.client.lines, ['PRIVMSG foo :bar'])
+
+
+ def testDodgyMaxLength(self):
+ self.assertRaises(ValueError, self.client.msg, 'foo', 'bar', 0)
+ self.assertRaises(ValueError, self.client.msg, 'foo', 'bar', 3)
+
+
+ def testMultipleLine(self):
+ maxLen = len('PRIVMSG foo :') + 3 + 2 # 2 for line endings
+ self.client.msg('foo', 'barbazbo', maxLen)
+ self.assertEquals(self.client.lines, ['PRIVMSG foo :bar',
+ 'PRIVMSG foo :baz',
+ 'PRIVMSG foo :bo'])
+
+
+ def testSufficientWidth(self):
+ msg = 'barbazbo'
+ maxLen = len('PRIVMSG foo :%s' % (msg,)) + 2
+ self.client.msg('foo', msg, maxLen)
+ self.assertEquals(self.client.lines, ['PRIVMSG foo :%s' % (msg,)])
+ self.client.lines = []
+ self.client.msg('foo', msg, maxLen-1)
+ self.assertEquals(2, len(self.client.lines))
+ self.client.lines = []
+ self.client.msg('foo', msg, maxLen+1)
+ self.assertEquals(1, len(self.client.lines))
+
+
+ def testSplitSanity(self):
+ # Whiteboxing
+ self.assertRaises(ValueError, irc.split, 'foo', -1)
+ self.assertRaises(ValueError, irc.split, 'foo', 0)
+ self.assertEquals([], irc.split('', 1))
+ self.assertEquals([], irc.split(''))
+
+
+ def test_splitDelimiters(self):
+ """
+ Test that split() skips any delimiter (space or newline) that it finds
+ at the very beginning of the string segment it is operating on.
+ Nothing should be added to the output list because of it.
+ """
+ r = irc.split("xx yyz", 2)
+ self.assertEquals(['xx', 'yy', 'z'], r)
+ r = irc.split("xx\nyyz", 2)
+ self.assertEquals(['xx', 'yy', 'z'], r)
+
+
+class ClientTests(TestCase):
+ """
+ Tests for the protocol-level behavior of IRCClient methods intended to
+ be called by application code.
+ """
+ def setUp(self):
+ """
+ Create and connect a new L{IRCClient} to a new L{StringTransport}.
+ """
+ self.transport = StringTransport()
+ self.protocol = IRCClient()
+ self.protocol.performLogin = False
+ self.protocol.makeConnection(self.transport)
+
+ # Sanity check - we don't want anything to have happened at this
+ # point, since we're not in a test yet.
+ self.assertEquals(self.transport.value(), "")
+
+
+ def getLastLine(self, transport):
+ """
+ Return the last IRC message in the transport buffer.
+ """
+ return transport.value().split('\r\n')[-2]
+
+
+ def test_away(self):
+ """
+ L{IRCCLient.away} sends an AWAY command with the specified message.
+ """
+ message = "Sorry, I'm not here."
+ self.protocol.away(message)
+ expected = [
+ 'AWAY :%s' % (message,),
+ '',
+ ]
+ self.assertEquals(self.transport.value().split('\r\n'), expected)
+
+
+ def test_back(self):
+ """
+ L{IRCClient.back} sends an AWAY command with an empty message.
+ """
+ self.protocol.back()
+ expected = [
+ 'AWAY :',
+ '',
+ ]
+ self.assertEquals(self.transport.value().split('\r\n'), expected)
+
+
+ def test_whois(self):
+ """
+ L{IRCClient.whois} sends a WHOIS message.
+ """
+ self.protocol.whois('alice')
+ self.assertEquals(
+ self.transport.value().split('\r\n'),
+ ['WHOIS alice', ''])
+
+
+ def test_whoisWithServer(self):
+ """
+ L{IRCClient.whois} sends a WHOIS message with a server name if a
+ value is passed for the C{server} parameter.
+ """
+ self.protocol.whois('alice', 'example.org')
+ self.assertEquals(
+ self.transport.value().split('\r\n'),
+ ['WHOIS example.org alice', ''])
+
+
+ def test_register(self):
+ """
+ L{IRCClient.register} sends NICK and USER commands with the
+ username, name, hostname, server name, and real name specified.
+ """
+ username = 'testuser'
+ hostname = 'testhost'
+ servername = 'testserver'
+ self.protocol.realname = 'testname'
+ self.protocol.password = None
+ self.protocol.register(username, hostname, servername)
+ expected = [
+ 'NICK %s' % (username,),
+ 'USER %s %s %s :%s' % (
+ username, hostname, servername, self.protocol.realname),
+ '']
+ self.assertEquals(self.transport.value().split('\r\n'), expected)
+
+
+ def test_registerWithPassword(self):
+ """
+ If the C{password} attribute of L{IRCClient} is not C{None}, the
+ C{register} method also sends a PASS command with it as the
+ argument.
+ """
+ username = 'testuser'
+ hostname = 'testhost'
+ servername = 'testserver'
+ self.protocol.realname = 'testname'
+ self.protocol.password = 'testpass'
+ self.protocol.register(username, hostname, servername)
+ expected = [
+ 'PASS %s' % (self.protocol.password,),
+ 'NICK %s' % (username,),
+ 'USER %s %s %s :%s' % (
+ username, hostname, servername, self.protocol.realname),
+ '']
+ self.assertEquals(self.transport.value().split('\r\n'), expected)
+
+
+ def test_registerWithTakenNick(self):
+ """
+ Verify that the client repeats the L{IRCClient.setNick} method with a
+ new value when presented with an C{ERR_NICKNAMEINUSE} while trying to
+ register.
+ """
+ username = 'testuser'
+ hostname = 'testhost'
+ servername = 'testserver'
+ self.protocol.realname = 'testname'
+ self.protocol.password = 'testpass'
+ self.protocol.register(username, hostname, servername)
+ self.protocol.irc_ERR_NICKNAMEINUSE('prefix', ['param'])
+ lastLine = self.getLastLine(self.transport)
+ self.assertNotEquals(lastLine, 'NICK %s' % (username,))
+
+ # Keep chaining underscores for each collision
+ self.protocol.irc_ERR_NICKNAMEINUSE('prefix', ['param'])
+ lastLine = self.getLastLine(self.transport)
+ self.assertEquals(lastLine, 'NICK %s' % (username + '__',))
+
+
+ def test_overrideAlterCollidedNick(self):
+ """
+ L{IRCClient.alterCollidedNick} determines how a nickname is altered upon
+ collision while a user is trying to change to that nickname.
+ """
+ nick = 'foo'
+ self.protocol.alterCollidedNick = lambda nick: nick + '***'
+ self.protocol.register(nick)
+ self.protocol.irc_ERR_NICKNAMEINUSE('prefix', ['param'])
+ lastLine = self.getLastLine(self.transport)
+ self.assertEquals(
+ lastLine, 'NICK %s' % (nick + '***',))
+
+
+ def test_nickChange(self):
+ """
+ When a NICK command is sent after signon, C{IRCClient.nickname} is set
+ to the new nickname I{after} the server sends an acknowledgement.
+ """
+ oldnick = 'foo'
+ newnick = 'bar'
+ self.protocol.register(oldnick)
+ self.protocol.irc_RPL_WELCOME('prefix', ['param'])
+ self.protocol.setNick(newnick)
+ self.assertEquals(self.protocol.nickname, oldnick)
+ self.protocol.irc_NICK('%s!quux@qux' % (oldnick,), [newnick])
+ self.assertEquals(self.protocol.nickname, newnick)
+
+
+ def test_erroneousNick(self):
+ """
+ Trying to register an illegal nickname results in the default legal
+ nickname being set, and trying to change a nickname to an illegal
+ nickname results in the old nickname being kept.
+ """
+ # Registration case: change illegal nickname to erroneousNickFallback
+ badnick = 'foo'
+ self.assertEquals(self.protocol._registered, False)
+ self.protocol.register(badnick)
+ self.protocol.irc_ERR_ERRONEUSNICKNAME('prefix', ['param'])
+ lastLine = self.getLastLine(self.transport)
+ self.assertEquals(
+ lastLine, 'NICK %s' % (self.protocol.erroneousNickFallback,))
+ self.protocol.irc_RPL_WELCOME('prefix', ['param'])
+ self.assertEquals(self.protocol._registered, True)
+ self.protocol.setNick(self.protocol.erroneousNickFallback)
+ self.assertEquals(
+ self.protocol.nickname, self.protocol.erroneousNickFallback)
+
+ # Illegal nick change attempt after registration. Fall back to the old
+ # nickname instead of erroneousNickFallback.
+ oldnick = self.protocol.nickname
+ self.protocol.setNick(badnick)
+ self.protocol.irc_ERR_ERRONEUSNICKNAME('prefix', ['param'])
+ lastLine = self.getLastLine(self.transport)
+ self.assertEquals(
+ lastLine, 'NICK %s' % (badnick,))
+ self.assertEquals(self.protocol.nickname, oldnick)
+
+
+ def test_describe(self):
+ """
+ L{IRCClient.desrcibe} sends a CTCP ACTION message to the target
+ specified.
+ """
+ target = 'foo'
+ channel = '#bar'
+ action = 'waves'
+ self.protocol.describe(target, action)
+ self.protocol.describe(channel, action)
+ expected = [
+ 'PRIVMSG %s :\01ACTION %s\01' % (target, action),
+ 'PRIVMSG %s :\01ACTION %s\01' % (channel, action),
+ '']
+ self.assertEquals(self.transport.value().split('\r\n'), expected)
+
+
+ def test_me(self):
+ """
+ L{IRCClient.me} sends a CTCP ACTION message to the target channel
+ specified.
+ If the target does not begin with a standard channel prefix,
+ '#' is prepended.
+ """
+ target = 'foo'
+ channel = '#bar'
+ action = 'waves'
+ self.protocol.me(target, action)
+ self.protocol.me(channel, action)
+ expected = [
+ 'PRIVMSG %s :\01ACTION %s\01' % ('#' + target, action),
+ 'PRIVMSG %s :\01ACTION %s\01' % (channel, action),
+ '']
+ self.assertEquals(self.transport.value().split('\r\n'), expected)
+ warnings = self.flushWarnings(
+ offendingFunctions=[self.test_me])
+ self.assertEquals(
+ warnings[0]['message'],
+ "me() is deprecated since Twisted 9.0. Use IRCClient.describe().")
+ self.assertEquals(warnings[0]['category'], DeprecationWarning)
+ self.assertEquals(len(warnings), 2)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_irc_service.py b/vendor/Twisted-10.0.0/twisted/words/test/test_irc_service.py
new file mode 100644
index 0000000000..13e809ee6e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_irc_service.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for IRC portions of L{twisted.words.service}.
+"""
+
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+from twisted.words.service import InMemoryWordsRealm, IRCFactory
+from twisted.words.protocols import irc
+from twisted.cred import checkers, portal
+
+class IRCUserTestCase(unittest.TestCase):
+ """
+ Isolated tests for L{IRCUser}
+ """
+
+ def setUp(self):
+ """
+ Sets up a Realm, Portal, Factory, IRCUser, Transport, and Connection
+ for our tests.
+ """
+ self.wordsRealm = InMemoryWordsRealm("example.com")
+ self.portal = portal.Portal(self.wordsRealm,
+ [checkers.InMemoryUsernamePasswordDatabaseDontUse(john="pass")])
+ self.factory = IRCFactory(self.wordsRealm, self.portal)
+ self.ircUser = self.factory.buildProtocol(None)
+ self.stringTransport = proto_helpers.StringTransport()
+ self.ircUser.makeConnection(self.stringTransport)
+
+
+ def test_sendMessage(self):
+ """
+ Sending a message to a user after they have sent NICK, but before they
+ have authenticated, results in a message from "example.com".
+ """
+ self.ircUser.irc_NICK("", ["mynick"])
+ self.stringTransport.clear()
+ self.ircUser.sendMessage("foo")
+ self.assertEquals(":example.com foo mynick\r\n",
+ self.stringTransport.value())
+
+
+ def response(self):
+ """
+ Grabs our responses and then clears the transport
+ """
+ response = self.ircUser.transport.value().splitlines()
+ self.ircUser.transport.clear()
+ return map(irc.parsemsg, response)
+
+
+ def scanResponse(self, response, messageType):
+ """
+ Gets messages out of a response
+
+ @param response: The parsed IRC messages of the response, as returned
+ by L{IRCServiceTestCase.response}
+
+ @param messageType: The string type of the desired messages.
+
+ @return: An iterator which yields 2-tuples of C{(index, ircMessage)}
+ """
+ for n, message in enumerate(response):
+ if (message[1] == messageType):
+ yield n, message
+
+
+ def test_sendNickSendsGreeting(self):
+ """
+ Receiving NICK without authenticating sends the MOTD Start and MOTD End
+ messages, which is required by certain popular IRC clients (such as
+ Pidgin) before a connection is considered to be fully established.
+ """
+ self.ircUser.irc_NICK("", ["mynick"])
+ response = self.response()
+ start = list(self.scanResponse(response, irc.RPL_MOTDSTART))
+ end = list(self.scanResponse(response, irc.RPL_ENDOFMOTD))
+ self.assertEquals(start,
+ [(0, ('example.com', '375', ['mynick', '- example.com Message of the Day - ']))])
+ self.assertEquals(end,
+ [(1, ('example.com', '376', ['mynick', 'End of /MOTD command.']))])
+
+
+ def test_fullLogin(self):
+ """
+ Receiving USER, PASS, NICK will log in the user, and transmit the
+ appropriate response messages.
+ """
+ self.ircUser.irc_USER("", ["john doe"])
+ self.ircUser.irc_PASS("", ["pass"])
+ self.ircUser.irc_NICK("", ["john"])
+
+ version = ('Your host is example.com, running version %s' %
+ (self.factory._serverInfo["serviceVersion"],))
+
+ creation = ('This server was created on %s' %
+ (self.factory._serverInfo["creationDate"],))
+
+ self.assertEquals(self.response(),
+ [('example.com', '375',
+ ['john', '- example.com Message of the Day - ']),
+ ('example.com', '376', ['john', 'End of /MOTD command.']),
+ ('example.com', '001', ['john', 'connected to Twisted IRC']),
+ ('example.com', '002', ['john', version]),
+ ('example.com', '003', ['john', creation]),
+ ('example.com', '004',
+ ['john', 'example.com', self.factory._serverInfo["serviceVersion"],
+ 'w', 'n'])])
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_jabberclient.py b/vendor/Twisted-10.0.0/twisted/words/test/test_jabberclient.py
new file mode 100644
index 0000000000..e90895beed
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_jabberclient.py
@@ -0,0 +1,414 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.protocols.jabber.client}
+"""
+
+from twisted.internet import defer
+from twisted.python.hashlib import sha1
+from twisted.trial import unittest
+from twisted.words.protocols.jabber import client, error, jid, xmlstream
+from twisted.words.protocols.jabber.sasl import SASLInitiatingInitializer
+from twisted.words.xish import utility
+
+IQ_AUTH_GET = '/iq[@type="get"]/query[@xmlns="jabber:iq:auth"]'
+IQ_AUTH_SET = '/iq[@type="set"]/query[@xmlns="jabber:iq:auth"]'
+NS_BIND = 'urn:ietf:params:xml:ns:xmpp-bind'
+IQ_BIND_SET = '/iq[@type="set"]/bind[@xmlns="%s"]' % NS_BIND
+NS_SESSION = 'urn:ietf:params:xml:ns:xmpp-session'
+IQ_SESSION_SET = '/iq[@type="set"]/session[@xmlns="%s"]' % NS_SESSION
+
+class CheckVersionInitializerTest(unittest.TestCase):
+ def setUp(self):
+ a = xmlstream.Authenticator()
+ xs = xmlstream.XmlStream(a)
+ self.init = client.CheckVersionInitializer(xs)
+
+
+ def testSupported(self):
+ """
+ Test supported version number 1.0
+ """
+ self.init.xmlstream.version = (1, 0)
+ self.init.initialize()
+
+
+ def testNotSupported(self):
+ """
+ Test unsupported version number 0.0, and check exception.
+ """
+ self.init.xmlstream.version = (0, 0)
+ exc = self.assertRaises(error.StreamError, self.init.initialize)
+ self.assertEquals('unsupported-version', exc.condition)
+
+
+
+class InitiatingInitializerHarness(object):
+ """
+ Testing harness for interacting with XML stream initializers.
+
+ This sets up an L{utility.XmlPipe} to create a communication channel between
+ the initializer and the stubbed receiving entity. It features a sink and
+ source side that both act similarly to a real L{xmlstream.XmlStream}. The
+ sink is augmented with an authenticator to which initializers can be added.
+
+ The harness also provides some utility methods to work with event observers
+ and deferreds.
+ """
+
+ def setUp(self):
+ self.output = []
+ self.pipe = utility.XmlPipe()
+ self.xmlstream = self.pipe.sink
+ self.authenticator = xmlstream.ConnectAuthenticator('example.org')
+ self.xmlstream.authenticator = self.authenticator
+
+
+ def waitFor(self, event, handler):
+ """
+ Observe an output event, returning a deferred.
+
+ The returned deferred will be fired when the given event has been
+ observed on the source end of the L{XmlPipe} tied to the protocol
+ under test. The handler is added as the first callback.
+
+ @param event: The event to be observed. See
+ L{utility.EventDispatcher.addOnetimeObserver}.
+ @param handler: The handler to be called with the observed event object.
+ @rtype: L{defer.Deferred}.
+ """
+ d = defer.Deferred()
+ d.addCallback(handler)
+ self.pipe.source.addOnetimeObserver(event, d.callback)
+ return d
+
+
+
+class IQAuthInitializerTest(InitiatingInitializerHarness, unittest.TestCase):
+ """
+ Tests for L{client.IQAuthInitializer}.
+ """
+
+ def setUp(self):
+ super(IQAuthInitializerTest, self).setUp()
+ self.init = client.IQAuthInitializer(self.xmlstream)
+ self.authenticator.jid = jid.JID('user@example.com/resource')
+ self.authenticator.password = 'secret'
+
+
+ def testPlainText(self):
+ """
+ Test plain-text authentication.
+
+ Act as a server supporting plain-text authentication and expect the
+ C{password} field to be filled with the password. Then act as if
+ authentication succeeds.
+ """
+
+ def onAuthGet(iq):
+ """
+ Called when the initializer sent a query for authentication methods.
+
+ The response informs the client that plain-text authentication
+ is supported.
+ """
+
+ # Create server response
+ response = xmlstream.toResponse(iq, 'result')
+ response.addElement(('jabber:iq:auth', 'query'))
+ response.query.addElement('username')
+ response.query.addElement('password')
+ response.query.addElement('resource')
+
+ # Set up an observer for the next request we expect.
+ d = self.waitFor(IQ_AUTH_SET, onAuthSet)
+
+ # Send server response
+ self.pipe.source.send(response)
+
+ return d
+
+ def onAuthSet(iq):
+ """
+ Called when the initializer sent the authentication request.
+
+ The server checks the credentials and responds with an empty result
+ signalling success.
+ """
+ self.assertEquals('user', unicode(iq.query.username))
+ self.assertEquals('secret', unicode(iq.query.password))
+ self.assertEquals('resource', unicode(iq.query.resource))
+
+ # Send server response
+ response = xmlstream.toResponse(iq, 'result')
+ self.pipe.source.send(response)
+
+ # Set up an observer for the request for authentication fields
+ d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
+
+ # Start the initializer
+ d2 = self.init.initialize()
+ return defer.gatherResults([d1, d2])
+
+
+ def testDigest(self):
+ """
+ Test digest authentication.
+
+ Act as a server supporting digest authentication and expect the
+ C{digest} field to be filled with a sha1 digest of the concatenated
+ stream session identifier and password. Then act as if authentication
+ succeeds.
+ """
+
+ def onAuthGet(iq):
+ """
+ Called when the initializer sent a query for authentication methods.
+
+ The response informs the client that digest authentication is
+ supported.
+ """
+
+ # Create server response
+ response = xmlstream.toResponse(iq, 'result')
+ response.addElement(('jabber:iq:auth', 'query'))
+ response.query.addElement('username')
+ response.query.addElement('digest')
+ response.query.addElement('resource')
+
+ # Set up an observer for the next request we expect.
+ d = self.waitFor(IQ_AUTH_SET, onAuthSet)
+
+ # Send server response
+ self.pipe.source.send(response)
+
+ return d
+
+ def onAuthSet(iq):
+ """
+ Called when the initializer sent the authentication request.
+
+ The server checks the credentials and responds with an empty result
+ signalling success.
+ """
+ self.assertEquals('user', unicode(iq.query.username))
+ self.assertEquals(sha1('12345secret').hexdigest(),
+ unicode(iq.query.digest).encode('utf-8'))
+ self.assertEquals('resource', unicode(iq.query.resource))
+
+ # Send server response
+ response = xmlstream.toResponse(iq, 'result')
+ self.pipe.source.send(response)
+
+ # Digest authentication relies on the stream session identifier. Set it.
+ self.xmlstream.sid = u'12345'
+
+ # Set up an observer for the request for authentication fields
+ d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
+
+ # Start the initializer
+ d2 = self.init.initialize()
+
+ return defer.gatherResults([d1, d2])
+
+
+ def testFailRequestFields(self):
+ """
+ Test initializer failure of request for fields for authentication.
+ """
+ def onAuthGet(iq):
+ """
+ Called when the initializer sent a query for authentication methods.
+
+ The server responds that the client is not authorized to authenticate.
+ """
+ response = error.StanzaError('not-authorized').toResponse(iq)
+ self.pipe.source.send(response)
+
+ # Set up an observer for the request for authentication fields
+ d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
+
+ # Start the initializer
+ d2 = self.init.initialize()
+
+ # The initialized should fail with a stanza error.
+ self.assertFailure(d2, error.StanzaError)
+
+ return defer.gatherResults([d1, d2])
+
+
+ def testFailAuth(self):
+ """
+ Test initializer failure to authenticate.
+ """
+
+ def onAuthGet(iq):
+ """
+ Called when the initializer sent a query for authentication methods.
+
+ The response informs the client that plain-text authentication
+ is supported.
+ """
+
+ # Send server response
+ response = xmlstream.toResponse(iq, 'result')
+ response.addElement(('jabber:iq:auth', 'query'))
+ response.query.addElement('username')
+ response.query.addElement('password')
+ response.query.addElement('resource')
+
+ # Set up an observer for the next request we expect.
+ d = self.waitFor(IQ_AUTH_SET, onAuthSet)
+
+ # Send server response
+ self.pipe.source.send(response)
+
+ return d
+
+ def onAuthSet(iq):
+ """
+ Called when the initializer sent the authentication request.
+
+ The server checks the credentials and responds with a not-authorized
+ stanza error.
+ """
+ response = error.StanzaError('not-authorized').toResponse(iq)
+ self.pipe.source.send(response)
+
+ # Set up an observer for the request for authentication fields
+ d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
+
+ # Start the initializer
+ d2 = self.init.initialize()
+
+ # The initializer should fail with a stanza error.
+ self.assertFailure(d2, error.StanzaError)
+
+ return defer.gatherResults([d1, d2])
+
+
+
+class BindInitializerTest(InitiatingInitializerHarness, unittest.TestCase):
+ """
+ Tests for L{client.BindInitializer}.
+ """
+
+ def setUp(self):
+ super(BindInitializerTest, self).setUp()
+ self.init = client.BindInitializer(self.xmlstream)
+ self.authenticator.jid = jid.JID('user@example.com/resource')
+
+
+ def testBasic(self):
+ """
+ Set up a stream, and act as if resource binding succeeds.
+ """
+ def onBind(iq):
+ response = xmlstream.toResponse(iq, 'result')
+ response.addElement((NS_BIND, 'bind'))
+ response.bind.addElement('jid',
+ content='user@example.com/other resource')
+ self.pipe.source.send(response)
+
+ def cb(result):
+ self.assertEquals(jid.JID('user@example.com/other resource'),
+ self.authenticator.jid)
+
+ d1 = self.waitFor(IQ_BIND_SET, onBind)
+ d2 = self.init.start()
+ d2.addCallback(cb)
+ return defer.gatherResults([d1, d2])
+
+
+ def testFailure(self):
+ """
+ Set up a stream, and act as if resource binding fails.
+ """
+ def onBind(iq):
+ response = error.StanzaError('conflict').toResponse(iq)
+ self.pipe.source.send(response)
+
+ d1 = self.waitFor(IQ_BIND_SET, onBind)
+ d2 = self.init.start()
+ self.assertFailure(d2, error.StanzaError)
+ return defer.gatherResults([d1, d2])
+
+
+
+class SessionInitializerTest(InitiatingInitializerHarness, unittest.TestCase):
+ """
+ Tests for L{client.SessionInitializer}.
+ """
+
+ def setUp(self):
+ super(SessionInitializerTest, self).setUp()
+ self.init = client.SessionInitializer(self.xmlstream)
+
+
+ def testSuccess(self):
+ """
+ Set up a stream, and act as if session establishment succeeds.
+ """
+
+ def onSession(iq):
+ response = xmlstream.toResponse(iq, 'result')
+ self.pipe.source.send(response)
+
+ d1 = self.waitFor(IQ_SESSION_SET, onSession)
+ d2 = self.init.start()
+ return defer.gatherResults([d1, d2])
+
+
+ def testFailure(self):
+ """
+ Set up a stream, and act as if session establishment fails.
+ """
+ def onSession(iq):
+ response = error.StanzaError('forbidden').toResponse(iq)
+ self.pipe.source.send(response)
+
+ d1 = self.waitFor(IQ_SESSION_SET, onSession)
+ d2 = self.init.start()
+ self.assertFailure(d2, error.StanzaError)
+ return defer.gatherResults([d1, d2])
+
+
+
+class XMPPAuthenticatorTest(unittest.TestCase):
+ """
+ Test for both XMPPAuthenticator and XMPPClientFactory.
+ """
+ def testBasic(self):
+ """
+ Test basic operations.
+
+ Setup an XMPPClientFactory, which sets up an XMPPAuthenticator, and let
+ it produce a protocol instance. Then inspect the instance variables of
+ the authenticator and XML stream objects.
+ """
+ self.client_jid = jid.JID('user@example.com/resource')
+
+ # Get an XmlStream instance. Note that it gets initialized with the
+ # XMPPAuthenticator (that has its associateWithXmlStream called) that
+ # is in turn initialized with the arguments to the factory.
+ xs = client.XMPPClientFactory(self.client_jid,
+ 'secret').buildProtocol(None)
+
+ # test authenticator's instance variables
+ self.assertEqual('example.com', xs.authenticator.otherHost)
+ self.assertEqual(self.client_jid, xs.authenticator.jid)
+ self.assertEqual('secret', xs.authenticator.password)
+
+ # test list of initializers
+ version, tls, sasl, bind, session = xs.initializers
+
+ self.assert_(isinstance(tls, xmlstream.TLSInitiatingInitializer))
+ self.assert_(isinstance(sasl, SASLInitiatingInitializer))
+ self.assert_(isinstance(bind, client.BindInitializer))
+ self.assert_(isinstance(session, client.SessionInitializer))
+
+ self.assertFalse(tls.required)
+ self.assertTrue(sasl.required)
+ self.assertFalse(bind.required)
+ self.assertFalse(session.required)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_jabbercomponent.py b/vendor/Twisted-10.0.0/twisted/words/test/test_jabbercomponent.py
new file mode 100644
index 0000000000..1ce6db1d12
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_jabbercomponent.py
@@ -0,0 +1,422 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.protocols.jabber.component}
+"""
+
+from twisted.python import failure
+from twisted.python.hashlib import sha1
+from twisted.trial import unittest
+from twisted.words.protocols.jabber import component, xmlstream
+from twisted.words.protocols.jabber.jid import JID
+from twisted.words.xish import domish
+from twisted.words.xish.utility import XmlPipe
+
+class DummyTransport:
+ def __init__(self, list):
+ self.list = list
+
+ def write(self, bytes):
+ self.list.append(bytes)
+
+class ComponentInitiatingInitializerTest(unittest.TestCase):
+ def setUp(self):
+ self.output = []
+
+ self.authenticator = xmlstream.Authenticator()
+ self.authenticator.password = 'secret'
+ self.xmlstream = xmlstream.XmlStream(self.authenticator)
+ self.xmlstream.namespace = 'test:component'
+ self.xmlstream.send = self.output.append
+ self.xmlstream.connectionMade()
+ self.xmlstream.dataReceived(
+ "<stream:stream xmlns='test:component' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345' version='1.0'>")
+ self.xmlstream.sid = u'12345'
+ self.init = component.ComponentInitiatingInitializer(self.xmlstream)
+
+ def testHandshake(self):
+ """
+ Test basic operations of component handshake.
+ """
+
+ d = self.init.initialize()
+
+ # the initializer should have sent the handshake request
+
+ handshake = self.output[-1]
+ self.assertEquals('handshake', handshake.name)
+ self.assertEquals('test:component', handshake.uri)
+ self.assertEquals(sha1("%s%s" % ('12345', 'secret')).hexdigest(),
+ unicode(handshake))
+
+ # successful authentication
+
+ handshake.children = []
+ self.xmlstream.dataReceived(handshake.toXml())
+
+ return d
+
+class ComponentAuthTest(unittest.TestCase):
+ def authPassed(self, stream):
+ self.authComplete = True
+
+ def testAuth(self):
+ self.authComplete = False
+ outlist = []
+
+ ca = component.ConnectComponentAuthenticator("cjid", "secret")
+ xs = xmlstream.XmlStream(ca)
+ xs.transport = DummyTransport(outlist)
+
+ xs.addObserver(xmlstream.STREAM_AUTHD_EVENT,
+ self.authPassed)
+
+ # Go...
+ xs.connectionMade()
+ xs.dataReceived("<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' from='cjid' id='12345'>")
+
+ # Calculate what we expect the handshake value to be
+ hv = sha1("%s%s" % ("12345", "secret")).hexdigest()
+
+ self.assertEquals(outlist[1], "<handshake>%s</handshake>" % (hv))
+
+ xs.dataReceived("<handshake/>")
+
+ self.assertEquals(self.authComplete, True)
+
+
+class JabberServiceHarness(component.Service):
+ def __init__(self):
+ self.componentConnectedFlag = False
+ self.componentDisconnectedFlag = False
+ self.transportConnectedFlag = False
+
+ def componentConnected(self, xmlstream):
+ self.componentConnectedFlag = True
+
+ def componentDisconnected(self):
+ self.componentDisconnectedFlag = True
+
+ def transportConnected(self, xmlstream):
+ self.transportConnectedFlag = True
+
+
+class TestJabberServiceManager(unittest.TestCase):
+ def testSM(self):
+ # Setup service manager and test harnes
+ sm = component.ServiceManager("foo", "password")
+ svc = JabberServiceHarness()
+ svc.setServiceParent(sm)
+
+ # Create a write list
+ wlist = []
+
+ # Setup a XmlStream
+ xs = sm.getFactory().buildProtocol(None)
+ xs.transport = self
+ xs.transport.write = wlist.append
+
+ # Indicate that it's connected
+ xs.connectionMade()
+
+ # Ensure the test service harness got notified
+ self.assertEquals(True, svc.transportConnectedFlag)
+
+ # Jump ahead and pretend like the stream got auth'd
+ xs.dispatch(xs, xmlstream.STREAM_AUTHD_EVENT)
+
+ # Ensure the test service harness got notified
+ self.assertEquals(True, svc.componentConnectedFlag)
+
+ # Pretend to drop the connection
+ xs.connectionLost(None)
+
+ # Ensure the test service harness got notified
+ self.assertEquals(True, svc.componentDisconnectedFlag)
+
+
+
+class RouterTest(unittest.TestCase):
+ """
+ Tests for L{component.Router}.
+ """
+
+ def test_addRoute(self):
+ """
+ Test route registration and routing on incoming stanzas.
+ """
+ router = component.Router()
+ routed = []
+ router.route = lambda element: routed.append(element)
+
+ pipe = XmlPipe()
+ router.addRoute('example.org', pipe.sink)
+ self.assertEquals(1, len(router.routes))
+ self.assertEquals(pipe.sink, router.routes['example.org'])
+
+ element = domish.Element(('testns', 'test'))
+ pipe.source.send(element)
+ self.assertEquals([element], routed)
+
+
+ def test_route(self):
+ """
+ Test routing of a message.
+ """
+ component1 = XmlPipe()
+ component2 = XmlPipe()
+ router = component.Router()
+ router.addRoute('component1.example.org', component1.sink)
+ router.addRoute('component2.example.org', component2.sink)
+
+ outgoing = []
+ component2.source.addObserver('/*',
+ lambda element: outgoing.append(element))
+ stanza = domish.Element((None, 'presence'))
+ stanza['from'] = 'component1.example.org'
+ stanza['to'] = 'component2.example.org'
+ component1.source.send(stanza)
+ self.assertEquals([stanza], outgoing)
+
+
+ def test_routeDefault(self):
+ """
+ Test routing of a message using the default route.
+
+ The default route is the one with C{None} as its key in the
+ routing table. It is taken when there is no more specific route
+ in the routing table that matches the stanza's destination.
+ """
+ component1 = XmlPipe()
+ s2s = XmlPipe()
+ router = component.Router()
+ router.addRoute('component1.example.org', component1.sink)
+ router.addRoute(None, s2s.sink)
+
+ outgoing = []
+ s2s.source.addObserver('/*', lambda element: outgoing.append(element))
+ stanza = domish.Element((None, 'presence'))
+ stanza['from'] = 'component1.example.org'
+ stanza['to'] = 'example.com'
+ component1.source.send(stanza)
+ self.assertEquals([stanza], outgoing)
+
+
+
+class ListenComponentAuthenticatorTest(unittest.TestCase):
+ """
+ Tests for L{component.ListenComponentAuthenticator}.
+ """
+
+ def setUp(self):
+ self.output = []
+ authenticator = component.ListenComponentAuthenticator('secret')
+ self.xmlstream = xmlstream.XmlStream(authenticator)
+ self.xmlstream.send = self.output.append
+
+
+ def loseConnection(self):
+ """
+ Stub loseConnection because we are a transport.
+ """
+ self.xmlstream.connectionLost("no reason")
+
+
+ def test_streamStarted(self):
+ """
+ The received stream header should set several attributes.
+ """
+ observers = []
+
+ def addOnetimeObserver(event, observerfn):
+ observers.append((event, observerfn))
+
+ xs = self.xmlstream
+ xs.addOnetimeObserver = addOnetimeObserver
+
+ xs.makeConnection(self)
+ self.assertIdentical(None, xs.sid)
+ self.assertFalse(xs._headerSent)
+
+ xs.dataReceived("<stream:stream xmlns='jabber:component:accept' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "to='component.example.org'>")
+ self.assertEqual((0, 0), xs.version)
+ self.assertNotIdentical(None, xs.sid)
+ self.assertTrue(xs._headerSent)
+ self.assertEquals(('/*', xs.authenticator.onElement), observers[-1])
+
+
+ def test_streamStartedWrongNamespace(self):
+ """
+ The received stream header should have a correct namespace.
+ """
+ streamErrors = []
+
+ xs = self.xmlstream
+ xs.sendStreamError = streamErrors.append
+ xs.makeConnection(self)
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "to='component.example.org'>")
+ self.assertEquals(1, len(streamErrors))
+ self.assertEquals('invalid-namespace', streamErrors[-1].condition)
+
+
+ def test_streamStartedNoTo(self):
+ """
+ The received stream header should have a 'to' attribute.
+ """
+ streamErrors = []
+
+ xs = self.xmlstream
+ xs.sendStreamError = streamErrors.append
+ xs.makeConnection(self)
+ xs.dataReceived("<stream:stream xmlns='jabber:component:accept' "
+ "xmlns:stream='http://etherx.jabber.org/streams'>")
+ self.assertEquals(1, len(streamErrors))
+ self.assertEquals('improper-addressing', streamErrors[-1].condition)
+
+
+ def test_onElement(self):
+ """
+ We expect a handshake element with a hash.
+ """
+ handshakes = []
+
+ xs = self.xmlstream
+ xs.authenticator.onHandshake = handshakes.append
+
+ handshake = domish.Element(('jabber:component:accept', 'handshake'))
+ handshake.addContent('1234')
+ xs.authenticator.onElement(handshake)
+ self.assertEqual('1234', handshakes[-1])
+
+ def test_onElementNotHandshake(self):
+ """
+ Reject elements that are not handshakes
+ """
+ handshakes = []
+ streamErrors = []
+
+ xs = self.xmlstream
+ xs.authenticator.onHandshake = handshakes.append
+ xs.sendStreamError = streamErrors.append
+
+ element = domish.Element(('jabber:component:accept', 'message'))
+ xs.authenticator.onElement(element)
+ self.assertFalse(handshakes)
+ self.assertEquals('not-authorized', streamErrors[-1].condition)
+
+
+ def test_onHandshake(self):
+ """
+ Receiving a handshake matching the secret authenticates the stream.
+ """
+ authd = []
+
+ def authenticated(xs):
+ authd.append(xs)
+
+ xs = self.xmlstream
+ xs.addOnetimeObserver(xmlstream.STREAM_AUTHD_EVENT, authenticated)
+ xs.sid = u'1234'
+ theHash = '32532c0f7dbf1253c095b18b18e36d38d94c1256'
+ xs.authenticator.onHandshake(theHash)
+ self.assertEqual('<handshake/>', self.output[-1])
+ self.assertEquals(1, len(authd))
+
+
+ def test_onHandshakeWrongHash(self):
+ """
+ Receiving a bad handshake should yield a stream error.
+ """
+ streamErrors = []
+ authd = []
+
+ def authenticated(xs):
+ authd.append(xs)
+
+ xs = self.xmlstream
+ xs.addOnetimeObserver(xmlstream.STREAM_AUTHD_EVENT, authenticated)
+ xs.sendStreamError = streamErrors.append
+
+ xs.sid = u'1234'
+ theHash = '1234'
+ xs.authenticator.onHandshake(theHash)
+ self.assertEquals('not-authorized', streamErrors[-1].condition)
+ self.assertEquals(0, len(authd))
+
+
+
+class XMPPComponentServerFactoryTest(unittest.TestCase):
+ """
+ Tests for L{component.XMPPComponentServerFactory}.
+ """
+
+ def setUp(self):
+ self.router = component.Router()
+ self.factory = component.XMPPComponentServerFactory(self.router,
+ 'secret')
+ self.xmlstream = self.factory.buildProtocol(None)
+ self.xmlstream.thisEntity = JID('component.example.org')
+
+
+ def test_makeConnection(self):
+ """
+ A new connection increases the stream serial count. No logs by default.
+ """
+ self.xmlstream.dispatch(self.xmlstream,
+ xmlstream.STREAM_CONNECTED_EVENT)
+ self.assertEqual(0, self.xmlstream.serial)
+ self.assertEqual(1, self.factory.serial)
+ self.assertIdentical(None, self.xmlstream.rawDataInFn)
+ self.assertIdentical(None, self.xmlstream.rawDataOutFn)
+
+
+ def test_makeConnectionLogTraffic(self):
+ """
+ Setting logTraffic should set up raw data loggers.
+ """
+ self.factory.logTraffic = True
+ self.xmlstream.dispatch(self.xmlstream,
+ xmlstream.STREAM_CONNECTED_EVENT)
+ self.assertNotIdentical(None, self.xmlstream.rawDataInFn)
+ self.assertNotIdentical(None, self.xmlstream.rawDataOutFn)
+
+
+ def test_onError(self):
+ """
+ An observer for stream errors should trigger onError to log it.
+ """
+ self.xmlstream.dispatch(self.xmlstream,
+ xmlstream.STREAM_CONNECTED_EVENT)
+
+ class TestError(Exception):
+ pass
+
+ reason = failure.Failure(TestError())
+ self.xmlstream.dispatch(reason, xmlstream.STREAM_ERROR_EVENT)
+ self.assertEqual(1, len(self.flushLoggedErrors(TestError)))
+
+
+ def test_connectionInitialized(self):
+ """
+ Make sure a new stream is added to the routing table.
+ """
+ self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
+ self.assertIn('component.example.org', self.router.routes)
+ self.assertIdentical(self.xmlstream,
+ self.router.routes['component.example.org'])
+
+
+ def test_connectionLost(self):
+ """
+ Make sure a stream is removed from the routing table on disconnect.
+ """
+ self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
+ self.xmlstream.dispatch(None, xmlstream.STREAM_END_EVENT)
+ self.assertNotIn('component.example.org', self.router.routes)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_jabbererror.py b/vendor/Twisted-10.0.0/twisted/words/test/test_jabbererror.py
new file mode 100644
index 0000000000..9b4643dcd9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_jabbererror.py
@@ -0,0 +1,308 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.protocols.jabber.error}.
+"""
+
+from twisted.trial import unittest
+
+from twisted.words.protocols.jabber import error
+from twisted.words.xish import domish
+
+NS_XML = 'http://www.w3.org/XML/1998/namespace'
+NS_STREAMS = 'http://etherx.jabber.org/streams'
+NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams'
+NS_XMPP_STANZAS = 'urn:ietf:params:xml:ns:xmpp-stanzas'
+
+class BaseErrorTest(unittest.TestCase):
+
+ def test_getElementPlain(self):
+ """
+ Test getting an element for a plain error.
+ """
+ e = error.BaseError('feature-not-implemented')
+ element = e.getElement()
+ self.assertIdentical(element.uri, None)
+ self.assertEquals(len(element.children), 1)
+
+ def test_getElementText(self):
+ """
+ Test getting an element for an error with a text.
+ """
+ e = error.BaseError('feature-not-implemented', 'text')
+ element = e.getElement()
+ self.assertEquals(len(element.children), 2)
+ self.assertEquals(unicode(element.text), 'text')
+ self.assertEquals(element.text.getAttribute((NS_XML, 'lang')), None)
+
+ def test_getElementTextLang(self):
+ """
+ Test getting an element for an error with a text and language.
+ """
+ e = error.BaseError('feature-not-implemented', 'text', 'en_US')
+ element = e.getElement()
+ self.assertEquals(len(element.children), 2)
+ self.assertEquals(unicode(element.text), 'text')
+ self.assertEquals(element.text[(NS_XML, 'lang')], 'en_US')
+
+ def test_getElementAppCondition(self):
+ """
+ Test getting an element for an error with an app specific condition.
+ """
+ ac = domish.Element(('testns', 'myerror'))
+ e = error.BaseError('feature-not-implemented', appCondition=ac)
+ element = e.getElement()
+ self.assertEquals(len(element.children), 2)
+ self.assertEquals(element.myerror, ac)
+
+class StreamErrorTest(unittest.TestCase):
+
+ def test_getElementPlain(self):
+ """
+ Test namespace of the element representation of an error.
+ """
+ e = error.StreamError('feature-not-implemented')
+ element = e.getElement()
+ self.assertEquals(element.uri, NS_STREAMS)
+
+ def test_getElementConditionNamespace(self):
+ """
+ Test that the error condition element has the correct namespace.
+ """
+ e = error.StreamError('feature-not-implemented')
+ element = e.getElement()
+ self.assertEquals(NS_XMPP_STREAMS, getattr(element, 'feature-not-implemented').uri)
+
+ def test_getElementTextNamespace(self):
+ """
+ Test that the error text element has the correct namespace.
+ """
+ e = error.StreamError('feature-not-implemented', 'text')
+ element = e.getElement()
+ self.assertEquals(NS_XMPP_STREAMS, element.text.uri)
+
+class StanzaErrorTest(unittest.TestCase):
+
+ def test_getElementPlain(self):
+ """
+ Test getting an element for a plain stanza error.
+ """
+ e = error.StanzaError('feature-not-implemented')
+ element = e.getElement()
+ self.assertEquals(element.uri, None)
+ self.assertEquals(element['type'], 'cancel')
+ self.assertEquals(element['code'], '501')
+
+ def test_getElementType(self):
+ """
+ Test getting an element for a stanza error with a given type.
+ """
+ e = error.StanzaError('feature-not-implemented', 'auth')
+ element = e.getElement()
+ self.assertEquals(element.uri, None)
+ self.assertEquals(element['type'], 'auth')
+ self.assertEquals(element['code'], '501')
+
+ def test_getElementConditionNamespace(self):
+ """
+ Test that the error condition element has the correct namespace.
+ """
+ e = error.StanzaError('feature-not-implemented')
+ element = e.getElement()
+ self.assertEquals(NS_XMPP_STANZAS, getattr(element, 'feature-not-implemented').uri)
+
+ def test_getElementTextNamespace(self):
+ """
+ Test that the error text element has the correct namespace.
+ """
+ e = error.StanzaError('feature-not-implemented', text='text')
+ element = e.getElement()
+ self.assertEquals(NS_XMPP_STANZAS, element.text.uri)
+
+ def test_toResponse(self):
+ """
+ Test an error response is generated from a stanza.
+
+ The addressing on the (new) response stanza should be reversed, an
+ error child (with proper properties) added and the type set to
+ C{'error'}.
+ """
+ stanza = domish.Element(('jabber:client', 'message'))
+ stanza['type'] = 'chat'
+ stanza['to'] = 'user1@example.com'
+ stanza['from'] = 'user2@example.com/resource'
+ e = error.StanzaError('service-unavailable')
+ response = e.toResponse(stanza)
+ self.assertNotIdentical(response, stanza)
+ self.assertEqual(response['from'], 'user1@example.com')
+ self.assertEqual(response['to'], 'user2@example.com/resource')
+ self.assertEqual(response['type'], 'error')
+ self.assertEqual(response.error.children[0].name,
+ 'service-unavailable')
+ self.assertEqual(response.error['type'], 'cancel')
+ self.assertNotEqual(stanza.children, response.children)
+
+class ParseErrorTest(unittest.TestCase):
+
+ def setUp(self):
+ self.error = domish.Element((None, 'error'))
+
+ def test_empty(self):
+ """
+ Test parsing of the empty error element.
+ """
+ result = error._parseError(self.error, 'errorns')
+ self.assertEqual({'condition': None,
+ 'text': None,
+ 'textLang': None,
+ 'appCondition': None}, result)
+
+ def test_condition(self):
+ """
+ Test parsing of an error element with a condition.
+ """
+ self.error.addElement(('errorns', 'bad-request'))
+ result = error._parseError(self.error, 'errorns')
+ self.assertEqual('bad-request', result['condition'])
+
+ def test_text(self):
+ """
+ Test parsing of an error element with a text.
+ """
+ text = self.error.addElement(('errorns', 'text'))
+ text.addContent('test')
+ result = error._parseError(self.error, 'errorns')
+ self.assertEqual('test', result['text'])
+ self.assertEqual(None, result['textLang'])
+
+ def test_textLang(self):
+ """
+ Test parsing of an error element with a text with a defined language.
+ """
+ text = self.error.addElement(('errorns', 'text'))
+ text[NS_XML, 'lang'] = 'en_US'
+ text.addContent('test')
+ result = error._parseError(self.error, 'errorns')
+ self.assertEqual('en_US', result['textLang'])
+
+ def test_textLangInherited(self):
+ """
+ Test parsing of an error element with a text with inherited language.
+ """
+ text = self.error.addElement(('errorns', 'text'))
+ self.error[NS_XML, 'lang'] = 'en_US'
+ text.addContent('test')
+ result = error._parseError(self.error, 'errorns')
+ self.assertEqual('en_US', result['textLang'])
+ test_textLangInherited.todo = "xml:lang inheritance not implemented"
+
+ def test_appCondition(self):
+ """
+ Test parsing of an error element with an app specific condition.
+ """
+ condition = self.error.addElement(('testns', 'condition'))
+ result = error._parseError(self.error, 'errorns')
+ self.assertEqual(condition, result['appCondition'])
+
+ def test_appConditionMultiple(self):
+ """
+ Test parsing of an error element with multiple app specific conditions.
+ """
+ condition = self.error.addElement(('testns', 'condition'))
+ condition2 = self.error.addElement(('testns', 'condition2'))
+ result = error._parseError(self.error, 'errorns')
+ self.assertEqual(condition2, result['appCondition'])
+
+class ExceptionFromStanzaTest(unittest.TestCase):
+
+ def test_basic(self):
+ """
+ Test basic operations of exceptionFromStanza.
+
+ Given a realistic stanza, check if a sane exception is returned.
+
+ Using this stanza::
+
+ <iq type='error'
+ from='pubsub.shakespeare.lit'
+ to='francisco@denmark.lit/barracks'
+ id='subscriptions1'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <subscriptions/>
+ </pubsub>
+ <error type='cancel'>
+ <feature-not-implemented
+ xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <unsupported xmlns='http://jabber.org/protocol/pubsub#errors'
+ feature='retrieve-subscriptions'/>
+ </error>
+ </iq>
+ """
+
+ stanza = domish.Element((None, 'stanza'))
+ p = stanza.addElement(('http://jabber.org/protocol/pubsub', 'pubsub'))
+ p.addElement('subscriptions')
+ e = stanza.addElement('error')
+ e['type'] = 'cancel'
+ e.addElement((NS_XMPP_STANZAS, 'feature-not-implemented'))
+ uc = e.addElement(('http://jabber.org/protocol/pubsub#errors',
+ 'unsupported'))
+ uc['feature'] = 'retrieve-subscriptions'
+
+ result = error.exceptionFromStanza(stanza)
+ self.assert_(isinstance(result, error.StanzaError))
+ self.assertEquals('feature-not-implemented', result.condition)
+ self.assertEquals('cancel', result.type)
+ self.assertEquals(uc, result.appCondition)
+ self.assertEquals([p], result.children)
+
+ def test_legacy(self):
+ """
+ Test legacy operations of exceptionFromStanza.
+
+ Given a realistic stanza with only legacy (pre-XMPP) error information,
+ check if a sane exception is returned.
+
+ Using this stanza::
+
+ <message type='error'
+ to='piers@pipetree.com/Home'
+ from='qmacro@jaber.org'>
+ <body>Are you there?</body>
+ <error code='502'>Unable to resolve hostname.</error>
+ </message>
+ """
+ stanza = domish.Element((None, 'stanza'))
+ p = stanza.addElement('body', content='Are you there?')
+ e = stanza.addElement('error', content='Unable to resolve hostname.')
+ e['code'] = '502'
+
+ result = error.exceptionFromStanza(stanza)
+ self.assert_(isinstance(result, error.StanzaError))
+ self.assertEquals('service-unavailable', result.condition)
+ self.assertEquals('wait', result.type)
+ self.assertEquals('Unable to resolve hostname.', result.text)
+ self.assertEquals([p], result.children)
+
+class ExceptionFromStreamErrorTest(unittest.TestCase):
+
+ def test_basic(self):
+ """
+ Test basic operations of exceptionFromStreamError.
+
+ Given a realistic stream error, check if a sane exception is returned.
+
+ Using this error::
+
+ <stream:error xmlns:stream='http://etherx.jabber.org/streams'>
+ <xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>
+ </stream:error>
+ """
+
+ e = domish.Element(('http://etherx.jabber.org/streams', 'error'))
+ e.addElement((NS_XMPP_STREAMS, 'xml-not-well-formed'))
+
+ result = error.exceptionFromStreamError(e)
+ self.assert_(isinstance(result, error.StreamError))
+ self.assertEquals('xml-not-well-formed', result.condition)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_jabberjid.py b/vendor/Twisted-10.0.0/twisted/words/test/test_jabberjid.py
new file mode 100644
index 0000000000..dbb42e5b20
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_jabberjid.py
@@ -0,0 +1,225 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.protocols.jabber.jid}.
+"""
+
+from twisted.trial import unittest
+
+from twisted.words.protocols.jabber import jid
+
+class JIDParsingTest(unittest.TestCase):
+ def test_parse(self):
+ """
+ Test different forms of JIDs.
+ """
+ # Basic forms
+ self.assertEquals(jid.parse("user@host/resource"),
+ ("user", "host", "resource"))
+ self.assertEquals(jid.parse("user@host"),
+ ("user", "host", None))
+ self.assertEquals(jid.parse("host"),
+ (None, "host", None))
+ self.assertEquals(jid.parse("host/resource"),
+ (None, "host", "resource"))
+
+ # More interesting forms
+ self.assertEquals(jid.parse("foo/bar@baz"),
+ (None, "foo", "bar@baz"))
+ self.assertEquals(jid.parse("boo@foo/bar@baz"),
+ ("boo", "foo", "bar@baz"))
+ self.assertEquals(jid.parse("boo@foo/bar/baz"),
+ ("boo", "foo", "bar/baz"))
+ self.assertEquals(jid.parse("boo/foo@bar@baz"),
+ (None, "boo", "foo@bar@baz"))
+ self.assertEquals(jid.parse("boo/foo/bar"),
+ (None, "boo", "foo/bar"))
+ self.assertEquals(jid.parse("boo//foo"),
+ (None, "boo", "/foo"))
+
+ def test_noHost(self):
+ """
+ Test for failure on no host part.
+ """
+ self.assertRaises(jid.InvalidFormat, jid.parse, "user@")
+
+ def test_doubleAt(self):
+ """
+ Test for failure on double @ signs.
+
+ This should fail because @ is not a valid character for the host
+ part of the JID.
+ """
+ self.assertRaises(jid.InvalidFormat, jid.parse, "user@@host")
+
+ def test_multipleAt(self):
+ """
+ Test for failure on two @ signs.
+
+ This should fail because @ is not a valid character for the host
+ part of the JID.
+ """
+ self.assertRaises(jid.InvalidFormat, jid.parse, "user@host@host")
+
+ # Basic tests for case mapping. These are fallback tests for the
+ # prepping done in twisted.words.protocols.jabber.xmpp_stringprep
+
+ def test_prepCaseMapUser(self):
+ """
+ Test case mapping of the user part of the JID.
+ """
+ self.assertEquals(jid.prep("UsEr", "host", "resource"),
+ ("user", "host", "resource"))
+
+ def test_prepCaseMapHost(self):
+ """
+ Test case mapping of the host part of the JID.
+ """
+ self.assertEquals(jid.prep("user", "hoST", "resource"),
+ ("user", "host", "resource"))
+
+ def test_prepNoCaseMapResource(self):
+ """
+ Test no case mapping of the resourcce part of the JID.
+ """
+ self.assertEquals(jid.prep("user", "hoST", "resource"),
+ ("user", "host", "resource"))
+ self.assertNotEquals(jid.prep("user", "host", "Resource"),
+ ("user", "host", "resource"))
+
+class JIDTest(unittest.TestCase):
+
+ def test_noneArguments(self):
+ """
+ Test that using no arguments raises an exception.
+ """
+ self.assertRaises(RuntimeError, jid.JID)
+
+ def test_attributes(self):
+ """
+ Test that the attributes correspond with the JID parts.
+ """
+ j = jid.JID("user@host/resource")
+ self.assertEquals(j.user, "user")
+ self.assertEquals(j.host, "host")
+ self.assertEquals(j.resource, "resource")
+
+ def test_userhost(self):
+ """
+ Test the extraction of the bare JID.
+ """
+ j = jid.JID("user@host/resource")
+ self.assertEquals("user@host", j.userhost())
+
+ def test_userhostOnlyHost(self):
+ """
+ Test the extraction of the bare JID of the full form host/resource.
+ """
+ j = jid.JID("host/resource")
+ self.assertEquals("host", j.userhost())
+
+ def test_userhostJID(self):
+ """
+ Test getting a JID object of the bare JID.
+ """
+ j1 = jid.JID("user@host/resource")
+ j2 = jid.internJID("user@host")
+ self.assertIdentical(j2, j1.userhostJID())
+
+ def test_userhostJIDNoResource(self):
+ """
+ Test getting a JID object of the bare JID when there was no resource.
+ """
+ j = jid.JID("user@host")
+ self.assertIdentical(j, j.userhostJID())
+
+ def test_fullHost(self):
+ """
+ Test giving a string representation of the JID with only a host part.
+ """
+ j = jid.JID(tuple=(None, 'host', None))
+ self.assertEqual('host', j.full())
+
+ def test_fullHostResource(self):
+ """
+ Test giving a string representation of the JID with host, resource.
+ """
+ j = jid.JID(tuple=(None, 'host', 'resource'))
+ self.assertEqual('host/resource', j.full())
+
+ def test_fullUserHost(self):
+ """
+ Test giving a string representation of the JID with user, host.
+ """
+ j = jid.JID(tuple=('user', 'host', None))
+ self.assertEqual('user@host', j.full())
+
+ def test_fullAll(self):
+ """
+ Test giving a string representation of the JID.
+ """
+ j = jid.JID(tuple=('user', 'host', 'resource'))
+ self.assertEqual('user@host/resource', j.full())
+
+ def test_equality(self):
+ """
+ Test JID equality.
+ """
+ j1 = jid.JID("user@host/resource")
+ j2 = jid.JID("user@host/resource")
+ self.assertNotIdentical(j1, j2)
+ self.assertEqual(j1, j2)
+
+ def test_equalityWithNonJIDs(self):
+ """
+ Test JID equality.
+ """
+ j = jid.JID("user@host/resource")
+ self.assertFalse(j == 'user@host/resource')
+
+ def test_inequality(self):
+ """
+ Test JID inequality.
+ """
+ j1 = jid.JID("user1@host/resource")
+ j2 = jid.JID("user2@host/resource")
+ self.assertNotEqual(j1, j2)
+
+ def test_inequalityWithNonJIDs(self):
+ """
+ Test JID equality.
+ """
+ j = jid.JID("user@host/resource")
+ self.assertNotEqual(j, 'user@host/resource')
+
+ def test_hashable(self):
+ """
+ Test JID hashability.
+ """
+ j1 = jid.JID("user@host/resource")
+ j2 = jid.JID("user@host/resource")
+ self.assertEqual(hash(j1), hash(j2))
+
+ def test_unicode(self):
+ """
+ Test unicode representation of JIDs.
+ """
+ j = jid.JID(tuple=('user', 'host', 'resource'))
+ self.assertEquals("user@host/resource", unicode(j))
+
+ def test_repr(self):
+ """
+ Test representation of JID objects.
+ """
+ j = jid.JID(tuple=('user', 'host', 'resource'))
+ self.assertEquals("JID(u'user@host/resource')", repr(j))
+
+class InternJIDTest(unittest.TestCase):
+ def test_identity(self):
+ """
+ Test that two interned JIDs yield the same object.
+ """
+ j1 = jid.internJID("user@host")
+ j2 = jid.internJID("user@host")
+ self.assertIdentical(j1, j2)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_jabbersasl.py b/vendor/Twisted-10.0.0/twisted/words/test/test_jabbersasl.py
new file mode 100644
index 0000000000..cd2a7d24c7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_jabbersasl.py
@@ -0,0 +1,272 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import implements
+from twisted.internet import defer
+from twisted.trial import unittest
+from twisted.words.protocols.jabber import sasl, sasl_mechanisms, xmlstream, jid
+from twisted.words.xish import domish
+
+NS_XMPP_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'
+
+class DummySASLMechanism(object):
+ """
+ Dummy SASL mechanism.
+
+ This just returns the initialResponse passed on creation, stores any
+ challenges and replies with an empty response.
+
+ @ivar challenge: Last received challenge.
+ @type challenge: C{unicode}.
+ @ivar initialResponse: Initial response to be returned when requested
+ via C{getInitialResponse} or C{None}.
+ @type initialResponse: C{unicode}
+ """
+
+ implements(sasl_mechanisms.ISASLMechanism)
+
+ challenge = None
+ name = "DUMMY"
+
+ def __init__(self, initialResponse):
+ self.initialResponse = initialResponse
+
+ def getInitialResponse(self):
+ return self.initialResponse
+
+ def getResponse(self, challenge):
+ self.challenge = challenge
+ return ""
+
+class DummySASLInitiatingInitializer(sasl.SASLInitiatingInitializer):
+ """
+ Dummy SASL Initializer for initiating entities.
+
+ This hardwires the SASL mechanism to L{DummySASLMechanism}, that is
+ instantiated with the value of C{initialResponse}.
+
+ @ivar initialResponse: The initial response to be returned by the
+ dummy SASL mechanism or C{None}.
+ @type initialResponse: C{unicode}.
+ """
+
+ initialResponse = None
+
+ def setMechanism(self):
+ self.mechanism = DummySASLMechanism(self.initialResponse)
+
+
+
+class SASLInitiatingInitializerTest(unittest.TestCase):
+ """
+ Tests for L{sasl.SASLInitiatingInitializer}
+ """
+
+ def setUp(self):
+ self.output = []
+
+ self.authenticator = xmlstream.Authenticator()
+ self.xmlstream = xmlstream.XmlStream(self.authenticator)
+ self.xmlstream.send = self.output.append
+ self.xmlstream.connectionMade()
+ self.xmlstream.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345' version='1.0'>")
+ self.init = DummySASLInitiatingInitializer(self.xmlstream)
+
+
+ def test_onFailure(self):
+ """
+ Test that the SASL error condition is correctly extracted.
+ """
+ failure = domish.Element(('urn:ietf:params:xml:ns:xmpp-sasl',
+ 'failure'))
+ failure.addElement('not-authorized')
+ self.init._deferred = defer.Deferred()
+ self.init.onFailure(failure)
+ self.assertFailure(self.init._deferred, sasl.SASLAuthError)
+ self.init._deferred.addCallback(lambda e:
+ self.assertEquals('not-authorized',
+ e.condition))
+ return self.init._deferred
+
+
+ def test_sendAuthInitialResponse(self):
+ """
+ Test starting authentication with an initial response.
+ """
+ self.init.initialResponse = "dummy"
+ self.init.start()
+ auth = self.output[0]
+ self.assertEquals(NS_XMPP_SASL, auth.uri)
+ self.assertEquals('auth', auth.name)
+ self.assertEquals('DUMMY', auth['mechanism'])
+ self.assertEquals('ZHVtbXk=', str(auth))
+
+
+ def test_sendAuthNoInitialResponse(self):
+ """
+ Test starting authentication without an initial response.
+ """
+ self.init.initialResponse = None
+ self.init.start()
+ auth = self.output[0]
+ self.assertEquals('', str(auth))
+
+
+ def test_sendAuthEmptyInitialResponse(self):
+ """
+ Test starting authentication where the initial response is empty.
+ """
+ self.init.initialResponse = ""
+ self.init.start()
+ auth = self.output[0]
+ self.assertEquals('=', str(auth))
+
+
+ def test_onChallenge(self):
+ """
+ Test receiving a challenge message.
+ """
+ d = self.init.start()
+ challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
+ challenge.addContent('bXkgY2hhbGxlbmdl')
+ self.init.onChallenge(challenge)
+ self.assertEqual('my challenge', self.init.mechanism.challenge)
+ self.init.onSuccess(None)
+ return d
+
+
+ def test_onChallengeEmpty(self):
+ """
+ Test receiving an empty challenge message.
+ """
+ d = self.init.start()
+ challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
+ self.init.onChallenge(challenge)
+ self.assertEqual('', self.init.mechanism.challenge)
+ self.init.onSuccess(None)
+ return d
+
+
+ def test_onChallengeIllegalPadding(self):
+ """
+ Test receiving a challenge message with illegal padding.
+ """
+ d = self.init.start()
+ challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
+ challenge.addContent('bXkg=Y2hhbGxlbmdl')
+ self.init.onChallenge(challenge)
+ self.assertFailure(d, sasl.SASLIncorrectEncodingError)
+ return d
+
+
+ def test_onChallengeIllegalCharacters(self):
+ """
+ Test receiving a challenge message with illegal characters.
+ """
+ d = self.init.start()
+ challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
+ challenge.addContent('bXkg*Y2hhbGxlbmdl')
+ self.init.onChallenge(challenge)
+ self.assertFailure(d, sasl.SASLIncorrectEncodingError)
+ return d
+
+
+ def test_onChallengeMalformed(self):
+ """
+ Test receiving a malformed challenge message.
+ """
+ d = self.init.start()
+ challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
+ challenge.addContent('a')
+ self.init.onChallenge(challenge)
+ self.assertFailure(d, sasl.SASLIncorrectEncodingError)
+ return d
+
+
+class SASLInitiatingInitializerSetMechanismTest(unittest.TestCase):
+ """
+ Test for L{sasl.SASLInitiatingInitializer.setMechanism}.
+ """
+
+ def setUp(self):
+ self.output = []
+
+ self.authenticator = xmlstream.Authenticator()
+ self.xmlstream = xmlstream.XmlStream(self.authenticator)
+ self.xmlstream.send = self.output.append
+ self.xmlstream.connectionMade()
+ self.xmlstream.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345' version='1.0'>")
+
+ self.init = sasl.SASLInitiatingInitializer(self.xmlstream)
+
+
+ def _setMechanism(self, name):
+ """
+ Set up the XML Stream to have a SASL feature with the given mechanism.
+ """
+ feature = domish.Element((NS_XMPP_SASL, 'mechanisms'))
+ feature.addElement('mechanism', content=name)
+ self.xmlstream.features[(feature.uri, feature.name)] = feature
+
+ self.init.setMechanism()
+ return self.init.mechanism.name
+
+
+ def test_anonymous(self):
+ """
+ Test setting ANONYMOUS as the authentication mechanism.
+ """
+ self.authenticator.jid = jid.JID('example.com')
+ self.authenticator.password = None
+ name = "ANONYMOUS"
+
+ self.assertEqual(name, self._setMechanism(name))
+
+
+ def test_plain(self):
+ """
+ Test setting PLAIN as the authentication mechanism.
+ """
+ self.authenticator.jid = jid.JID('test@example.com')
+ self.authenticator.password = 'secret'
+ name = "PLAIN"
+
+ self.assertEqual(name, self._setMechanism(name))
+
+
+ def test_digest(self):
+ """
+ Test setting DIGEST-MD5 as the authentication mechanism.
+ """
+ self.authenticator.jid = jid.JID('test@example.com')
+ self.authenticator.password = 'secret'
+ name = "DIGEST-MD5"
+
+ self.assertEqual(name, self._setMechanism(name))
+
+
+ def test_notAcceptable(self):
+ """
+ Test using an unacceptable SASL authentication mechanism.
+ """
+
+ self.authenticator.jid = jid.JID('test@example.com')
+ self.authenticator.password = 'secret'
+
+ self.assertRaises(sasl.SASLNoAcceptableMechanism,
+ self._setMechanism, 'SOMETHING_UNACCEPTABLE')
+
+
+ def test_notAcceptableWithoutUser(self):
+ """
+ Test using an unacceptable SASL authentication mechanism with no JID.
+ """
+ self.authenticator.jid = jid.JID('example.com')
+ self.authenticator.password = 'secret'
+
+ self.assertRaises(sasl.SASLNoAcceptableMechanism,
+ self._setMechanism, 'SOMETHING_UNACCEPTABLE')
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_jabbersaslmechanisms.py b/vendor/Twisted-10.0.0/twisted/words/test/test_jabbersaslmechanisms.py
new file mode 100644
index 0000000000..627d069acb
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_jabbersaslmechanisms.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.protocols.jabber.sasl_mechanisms}.
+"""
+
+from twisted.trial import unittest
+
+from twisted.words.protocols.jabber import sasl_mechanisms
+
+class PlainTest(unittest.TestCase):
+ def test_getInitialResponse(self):
+ """
+ Test the initial response.
+ """
+ m = sasl_mechanisms.Plain(None, 'test', 'secret')
+ self.assertEquals(m.getInitialResponse(), '\x00test\x00secret')
+
+
+
+class AnonymousTest(unittest.TestCase):
+ """
+ Tests for L{twisted.words.protocols.jabber.sasl_mechanisms.Anonymous}.
+ """
+ def test_getInitialResponse(self):
+ """
+ Test the initial response to be empty.
+ """
+ m = sasl_mechanisms.Anonymous()
+ self.assertEquals(m.getInitialResponse(), None)
+
+
+
+class DigestMD5Test(unittest.TestCase):
+ def setUp(self):
+ self.mechanism = sasl_mechanisms.DigestMD5('xmpp', 'example.org', None,
+ 'test', 'secret')
+
+
+ def test_getInitialResponse(self):
+ """
+ Test that no initial response is generated.
+ """
+ self.assertIdentical(self.mechanism.getInitialResponse(), None)
+
+ def test_getResponse(self):
+ """
+ Partially test challenge response.
+
+ Does not actually test the response-value, yet.
+ """
+
+ challenge = 'realm="localhost",nonce="1234",qop="auth",charset=utf-8,algorithm=md5-sess'
+ directives = self.mechanism._parse(self.mechanism.getResponse(challenge))
+ self.assertEqual(directives['username'], 'test')
+ self.assertEqual(directives['nonce'], '1234')
+ self.assertEqual(directives['nc'], '00000001')
+ self.assertEqual(directives['qop'], ['auth'])
+ self.assertEqual(directives['charset'], 'utf-8')
+ self.assertEqual(directives['digest-uri'], 'xmpp/example.org')
+ self.assertEqual(directives['realm'], 'localhost')
+
+ def test_getResponseNoRealm(self):
+ """
+ Test that we accept challenges without realm.
+
+ The realm should default to the host part of the JID.
+ """
+
+ challenge = 'nonce="1234",qop="auth",charset=utf-8,algorithm=md5-sess'
+ directives = self.mechanism._parse(self.mechanism.getResponse(challenge))
+ self.assertEqual(directives['realm'], 'example.org')
+
+ def test__parse(self):
+ """
+ Test challenge decoding.
+
+ Specifically, check for multiple values for the C{qop} and C{cipher}
+ directives.
+ """
+ challenge = 'nonce="1234",qop="auth,auth-conf",charset=utf-8,' \
+ 'algorithm=md5-sess,cipher="des,3des"'
+ directives = self.mechanism._parse(challenge)
+ self.assertEqual('1234', directives['nonce'])
+ self.assertEqual('utf-8', directives['charset'])
+ self.assertIn('auth', directives['qop'])
+ self.assertIn('auth-conf', directives['qop'])
+ self.assertIn('des', directives['cipher'])
+ self.assertIn('3des', directives['cipher'])
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_jabberxmlstream.py b/vendor/Twisted-10.0.0/twisted/words/test/test_jabberxmlstream.py
new file mode 100644
index 0000000000..6bca82456b
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_jabberxmlstream.py
@@ -0,0 +1,1287 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.protocols.jabber.xmlstream}.
+"""
+
+from twisted.trial import unittest
+
+from zope.interface.verify import verifyObject
+
+from twisted.internet import defer, task
+from twisted.internet.error import ConnectionLost
+from twisted.internet.interfaces import IProtocolFactory
+from twisted.test import proto_helpers
+from twisted.words.test.test_xmlstream import GenericXmlStreamFactoryTestsMixin
+from twisted.words.xish import domish
+from twisted.words.protocols.jabber import error, ijabber, jid, xmlstream
+
+
+
+NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls'
+
+
+
+class HashPasswordTest(unittest.TestCase):
+ """
+ Tests for L{xmlstream.hashPassword}.
+ """
+
+ def test_basic(self):
+ """
+ The sid and secret are concatenated to calculate sha1 hex digest.
+ """
+ hash = xmlstream.hashPassword(u"12345", u"secret")
+ self.assertEqual('99567ee91b2c7cabf607f10cb9f4a3634fa820e0', hash)
+
+
+ def test_sidNotUnicode(self):
+ """
+ The session identifier must be a unicode object.
+ """
+ self.assertRaises(TypeError, xmlstream.hashPassword, "\xc2\xb92345",
+ u"secret")
+
+
+ def test_passwordNotUnicode(self):
+ """
+ The password must be a unicode object.
+ """
+ self.assertRaises(TypeError, xmlstream.hashPassword, u"12345",
+ "secr\xc3\xa9t")
+
+
+ def test_unicodeSecret(self):
+ """
+ The concatenated sid and password must be encoded to UTF-8 before hashing.
+ """
+ hash = xmlstream.hashPassword(u"12345", u"secr\u00e9t")
+ self.assertEqual('659bf88d8f8e179081f7f3b4a8e7d224652d2853', hash)
+
+
+
+class IQTest(unittest.TestCase):
+ """
+ Tests both IQ and the associated IIQResponseTracker callback.
+ """
+
+ def setUp(self):
+ authenticator = xmlstream.ConnectAuthenticator('otherhost')
+ authenticator.namespace = 'testns'
+ self.xmlstream = xmlstream.XmlStream(authenticator)
+ self.clock = task.Clock()
+ self.xmlstream._callLater = self.clock.callLater
+ self.xmlstream.makeConnection(proto_helpers.StringTransport())
+ self.xmlstream.dataReceived(
+ "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "
+ "xmlns='testns' from='otherhost' version='1.0'>")
+ self.iq = xmlstream.IQ(self.xmlstream, 'get')
+
+
+ def testBasic(self):
+ self.assertEquals(self.iq['type'], 'get')
+ self.assertTrue(self.iq['id'])
+
+
+ def testSend(self):
+ self.xmlstream.transport.clear()
+ self.iq.send()
+ self.assertEquals("<iq type='get' id='%s'/>" % self.iq['id'],
+ self.xmlstream.transport.value())
+
+
+ def testResultResponse(self):
+ def cb(result):
+ self.assertEquals(result['type'], 'result')
+
+ d = self.iq.send()
+ d.addCallback(cb)
+
+ xs = self.xmlstream
+ xs.dataReceived("<iq type='result' id='%s'/>" % self.iq['id'])
+ return d
+
+
+ def testErrorResponse(self):
+ d = self.iq.send()
+ self.assertFailure(d, error.StanzaError)
+
+ xs = self.xmlstream
+ xs.dataReceived("<iq type='error' id='%s'/>" % self.iq['id'])
+ return d
+
+
+ def testNonTrackedResponse(self):
+ """
+ Test that untracked iq responses don't trigger any action.
+
+ Untracked means that the id of the incoming response iq is not
+ in the stream's C{iqDeferreds} dictionary.
+ """
+ xs = self.xmlstream
+ xmlstream.upgradeWithIQResponseTracker(xs)
+
+ # Make sure we aren't tracking any iq's.
+ self.assertFalse(xs.iqDeferreds)
+
+ # Set up a fallback handler that checks the stanza's handled attribute.
+ # If that is set to True, the iq tracker claims to have handled the
+ # response.
+ def cb(iq):
+ self.assertFalse(getattr(iq, 'handled', False))
+
+ xs.addObserver("/iq", cb, -1)
+
+ # Receive an untracked iq response
+ xs.dataReceived("<iq type='result' id='test'/>")
+
+
+ def testCleanup(self):
+ """
+ Test if the deferred associated with an iq request is removed
+ from the list kept in the L{XmlStream} object after it has
+ been fired.
+ """
+
+ d = self.iq.send()
+ xs = self.xmlstream
+ xs.dataReceived("<iq type='result' id='%s'/>" % self.iq['id'])
+ self.assertNotIn(self.iq['id'], xs.iqDeferreds)
+ return d
+
+
+ def testDisconnectCleanup(self):
+ """
+ Test if deferreds for iq's that haven't yet received a response
+ have their errback called on stream disconnect.
+ """
+
+ d = self.iq.send()
+ xs = self.xmlstream
+ xs.connectionLost("Closed by peer")
+ self.assertFailure(d, ConnectionLost)
+ return d
+
+
+ def testNoModifyingDict(self):
+ """
+ Test to make sure the errbacks cannot cause the iteration of the
+ iqDeferreds to blow up in our face.
+ """
+
+ def eb(failure):
+ d = xmlstream.IQ(self.xmlstream).send()
+ d.addErrback(eb)
+
+ d = self.iq.send()
+ d.addErrback(eb)
+ self.xmlstream.connectionLost("Closed by peer")
+ return d
+
+
+ def testRequestTimingOut(self):
+ """
+ Test that an iq request with a defined timeout times out.
+ """
+ self.iq.timeout = 60
+ d = self.iq.send()
+ self.assertFailure(d, xmlstream.TimeoutError)
+
+ self.clock.pump([1, 60])
+ self.assertFalse(self.clock.calls)
+ self.assertFalse(self.xmlstream.iqDeferreds)
+ return d
+
+
+ def testRequestNotTimingOut(self):
+ """
+ Test that an iq request with a defined timeout does not time out
+ when a response was received before the timeout period elapsed.
+ """
+ self.iq.timeout = 60
+ d = self.iq.send()
+ self.clock.callLater(1, self.xmlstream.dataReceived,
+ "<iq type='result' id='%s'/>" % self.iq['id'])
+ self.clock.pump([1, 1])
+ self.assertFalse(self.clock.calls)
+ return d
+
+
+ def testDisconnectTimeoutCancellation(self):
+ """
+ Test if timeouts for iq's that haven't yet received a response
+ are cancelled on stream disconnect.
+ """
+
+ self.iq.timeout = 60
+ d = self.iq.send()
+
+ xs = self.xmlstream
+ xs.connectionLost("Closed by peer")
+ self.assertFailure(d, ConnectionLost)
+ self.assertFalse(self.clock.calls)
+ return d
+
+
+
+class XmlStreamTest(unittest.TestCase):
+
+ def onStreamStart(self, obj):
+ self.gotStreamStart = True
+
+
+ def onStreamEnd(self, obj):
+ self.gotStreamEnd = True
+
+
+ def onStreamError(self, obj):
+ self.gotStreamError = True
+
+
+ def setUp(self):
+ """
+ Set up XmlStream and several observers.
+ """
+ self.gotStreamStart = False
+ self.gotStreamEnd = False
+ self.gotStreamError = False
+ xs = xmlstream.XmlStream(xmlstream.Authenticator())
+ xs.addObserver('//event/stream/start', self.onStreamStart)
+ xs.addObserver('//event/stream/end', self.onStreamEnd)
+ xs.addObserver('//event/stream/error', self.onStreamError)
+ xs.makeConnection(proto_helpers.StringTransportWithDisconnection())
+ xs.transport.protocol = xs
+ xs.namespace = 'testns'
+ xs.version = (1, 0)
+ self.xmlstream = xs
+
+
+ def test_sendHeaderBasic(self):
+ """
+ Basic test on the header sent by sendHeader.
+ """
+ xs = self.xmlstream
+ xs.sendHeader()
+ splitHeader = self.xmlstream.transport.value()[0:-1].split(' ')
+ self.assertIn("<stream:stream", splitHeader)
+ self.assertIn("xmlns:stream='http://etherx.jabber.org/streams'",
+ splitHeader)
+ self.assertIn("xmlns='testns'", splitHeader)
+ self.assertIn("version='1.0'", splitHeader)
+ self.assertTrue(xs._headerSent)
+
+
+ def test_sendHeaderAdditionalNamespaces(self):
+ """
+ Test for additional namespace declarations.
+ """
+ xs = self.xmlstream
+ xs.prefixes['jabber:server:dialback'] = 'db'
+ xs.sendHeader()
+ splitHeader = self.xmlstream.transport.value()[0:-1].split(' ')
+ self.assertIn("<stream:stream", splitHeader)
+ self.assertIn("xmlns:stream='http://etherx.jabber.org/streams'",
+ splitHeader)
+ self.assertIn("xmlns:db='jabber:server:dialback'", splitHeader)
+ self.assertIn("xmlns='testns'", splitHeader)
+ self.assertIn("version='1.0'", splitHeader)
+ self.assertTrue(xs._headerSent)
+
+
+ def test_sendHeaderInitiating(self):
+ """
+ Test addressing when initiating a stream.
+ """
+ xs = self.xmlstream
+ xs.thisEntity = jid.JID('thisHost')
+ xs.otherEntity = jid.JID('otherHost')
+ xs.initiating = True
+ xs.sendHeader()
+ splitHeader = xs.transport.value()[0:-1].split(' ')
+ self.assertIn("to='otherhost'", splitHeader)
+ self.assertIn("from='thishost'", splitHeader)
+
+
+ def test_sendHeaderReceiving(self):
+ """
+ Test addressing when receiving a stream.
+ """
+ xs = self.xmlstream
+ xs.thisEntity = jid.JID('thisHost')
+ xs.otherEntity = jid.JID('otherHost')
+ xs.initiating = False
+ xs.sid = 'session01'
+ xs.sendHeader()
+ splitHeader = xs.transport.value()[0:-1].split(' ')
+ self.assertIn("to='otherhost'", splitHeader)
+ self.assertIn("from='thishost'", splitHeader)
+ self.assertIn("id='session01'", splitHeader)
+
+
+ def test_receiveStreamError(self):
+ """
+ Test events when a stream error is received.
+ """
+ xs = self.xmlstream
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345' version='1.0'>")
+ xs.dataReceived("<stream:error/>")
+ self.assertTrue(self.gotStreamError)
+ self.assertTrue(self.gotStreamEnd)
+
+
+ def test_sendStreamErrorInitiating(self):
+ """
+ Test sendStreamError on an initiating xmlstream with a header sent.
+
+ An error should be sent out and the connection lost.
+ """
+ xs = self.xmlstream
+ xs.initiating = True
+ xs.sendHeader()
+ xs.transport.clear()
+ xs.sendStreamError(error.StreamError('version-unsupported'))
+ self.assertNotEqual('', xs.transport.value())
+ self.assertTrue(self.gotStreamEnd)
+
+
+ def test_sendStreamErrorInitiatingNoHeader(self):
+ """
+ Test sendStreamError on an initiating xmlstream without having sent a
+ header.
+
+ In this case, no header should be generated. Also, the error should
+ not be sent out on the stream. Just closing the connection.
+ """
+ xs = self.xmlstream
+ xs.initiating = True
+ xs.transport.clear()
+ xs.sendStreamError(error.StreamError('version-unsupported'))
+ self.assertNot(xs._headerSent)
+ self.assertEqual('', xs.transport.value())
+ self.assertTrue(self.gotStreamEnd)
+
+
+ def test_sendStreamErrorReceiving(self):
+ """
+ Test sendStreamError on a receiving xmlstream with a header sent.
+
+ An error should be sent out and the connection lost.
+ """
+ xs = self.xmlstream
+ xs.initiating = False
+ xs.sendHeader()
+ xs.transport.clear()
+ xs.sendStreamError(error.StreamError('version-unsupported'))
+ self.assertNotEqual('', xs.transport.value())
+ self.assertTrue(self.gotStreamEnd)
+
+
+ def test_sendStreamErrorReceivingNoHeader(self):
+ """
+ Test sendStreamError on a receiving xmlstream without having sent a
+ header.
+
+ In this case, a header should be generated. Then, the error should
+ be sent out on the stream followed by closing the connection.
+ """
+ xs = self.xmlstream
+ xs.initiating = False
+ xs.transport.clear()
+ xs.sendStreamError(error.StreamError('version-unsupported'))
+ self.assertTrue(xs._headerSent)
+ self.assertNotEqual('', xs.transport.value())
+ self.assertTrue(self.gotStreamEnd)
+
+
+ def test_reset(self):
+ """
+ Test resetting the XML stream to start a new layer.
+ """
+ xs = self.xmlstream
+ xs.sendHeader()
+ stream = xs.stream
+ xs.reset()
+ self.assertNotEqual(stream, xs.stream)
+ self.assertNot(xs._headerSent)
+
+
+ def test_send(self):
+ """
+ Test send with various types of objects.
+ """
+ xs = self.xmlstream
+ xs.send('<presence/>')
+ self.assertEqual(xs.transport.value(), '<presence/>')
+
+ xs.transport.clear()
+ el = domish.Element(('testns', 'presence'))
+ xs.send(el)
+ self.assertEqual(xs.transport.value(), '<presence/>')
+
+ xs.transport.clear()
+ el = domish.Element(('http://etherx.jabber.org/streams', 'features'))
+ xs.send(el)
+ self.assertEqual(xs.transport.value(), '<stream:features/>')
+
+
+ def test_authenticator(self):
+ """
+ Test that the associated authenticator is correctly called.
+ """
+ connectionMadeCalls = []
+ streamStartedCalls = []
+ associateWithStreamCalls = []
+
+ class TestAuthenticator:
+ def connectionMade(self):
+ connectionMadeCalls.append(None)
+
+ def streamStarted(self, rootElement):
+ streamStartedCalls.append(rootElement)
+
+ def associateWithStream(self, xs):
+ associateWithStreamCalls.append(xs)
+
+ a = TestAuthenticator()
+ xs = xmlstream.XmlStream(a)
+ self.assertEqual([xs], associateWithStreamCalls)
+ xs.connectionMade()
+ self.assertEqual([None], connectionMadeCalls)
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345'>")
+ self.assertEqual(1, len(streamStartedCalls))
+ xs.reset()
+ self.assertEqual([None], connectionMadeCalls)
+
+
+
+class TestError(Exception):
+ pass
+
+
+
+class AuthenticatorTest(unittest.TestCase):
+ def setUp(self):
+ self.authenticator = xmlstream.Authenticator()
+ self.xmlstream = xmlstream.XmlStream(self.authenticator)
+
+
+ def test_streamStart(self):
+ """
+ Test streamStart to fill the appropriate attributes from the
+ stream header.
+ """
+ xs = self.xmlstream
+ xs.makeConnection(proto_helpers.StringTransport())
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.org' to='example.com' id='12345' "
+ "version='1.0'>")
+ self.assertEqual((1, 0), xs.version)
+ self.assertIdentical(None, xs.sid)
+ self.assertEqual('invalid', xs.namespace)
+ self.assertIdentical(None, xs.otherEntity)
+ self.assertEqual(None, xs.thisEntity)
+
+
+ def test_streamStartLegacy(self):
+ """
+ Test streamStart to fill the appropriate attributes from the
+ stream header for a pre-XMPP-1.0 header.
+ """
+ xs = self.xmlstream
+ xs.makeConnection(proto_helpers.StringTransport())
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345'>")
+ self.assertEqual((0, 0), xs.version)
+
+
+ def test_streamBadVersionOneDigit(self):
+ """
+ Test streamStart to fill the appropriate attributes from the
+ stream header for a version with only one digit.
+ """
+ xs = self.xmlstream
+ xs.makeConnection(proto_helpers.StringTransport())
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345' version='1'>")
+ self.assertEqual((0, 0), xs.version)
+
+
+ def test_streamBadVersionNoNumber(self):
+ """
+ Test streamStart to fill the appropriate attributes from the
+ stream header for a malformed version.
+ """
+ xs = self.xmlstream
+ xs.makeConnection(proto_helpers.StringTransport())
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345' version='blah'>")
+ self.assertEqual((0, 0), xs.version)
+
+
+
+class ConnectAuthenticatorTest(unittest.TestCase):
+
+ def setUp(self):
+ self.gotAuthenticated = False
+ self.initFailure = None
+ self.authenticator = xmlstream.ConnectAuthenticator('otherHost')
+ self.xmlstream = xmlstream.XmlStream(self.authenticator)
+ self.xmlstream.addObserver('//event/stream/authd', self.onAuthenticated)
+ self.xmlstream.addObserver('//event/xmpp/initfailed', self.onInitFailed)
+
+
+ def onAuthenticated(self, obj):
+ self.gotAuthenticated = True
+
+
+ def onInitFailed(self, failure):
+ self.initFailure = failure
+
+
+ def testSucces(self):
+ """
+ Test successful completion of an initialization step.
+ """
+ class Initializer:
+ def initialize(self):
+ pass
+
+ init = Initializer()
+ self.xmlstream.initializers = [init]
+
+ self.authenticator.initializeStream()
+ self.assertEqual([], self.xmlstream.initializers)
+ self.assertTrue(self.gotAuthenticated)
+
+
+ def testFailure(self):
+ """
+ Test failure of an initialization step.
+ """
+ class Initializer:
+ def initialize(self):
+ raise TestError
+
+ init = Initializer()
+ self.xmlstream.initializers = [init]
+
+ self.authenticator.initializeStream()
+ self.assertEqual([init], self.xmlstream.initializers)
+ self.assertFalse(self.gotAuthenticated)
+ self.assertNotIdentical(None, self.initFailure)
+ self.assertTrue(self.initFailure.check(TestError))
+
+
+ def test_streamStart(self):
+ """
+ Test streamStart to fill the appropriate attributes from the
+ stream header.
+ """
+ self.authenticator.namespace = 'testns'
+ xs = self.xmlstream
+ xs.makeConnection(proto_helpers.StringTransport())
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' to='example.org' id='12345' "
+ "version='1.0'>")
+ self.assertEqual((1, 0), xs.version)
+ self.assertEqual('12345', xs.sid)
+ self.assertEqual('testns', xs.namespace)
+ self.assertEqual('example.com', xs.otherEntity.host)
+ self.assertIdentical(None, xs.thisEntity)
+ self.assertNot(self.gotAuthenticated)
+ xs.dataReceived("<stream:features>"
+ "<test xmlns='testns'/>"
+ "</stream:features>")
+ self.assertIn(('testns', 'test'), xs.features)
+ self.assertTrue(self.gotAuthenticated)
+
+
+
+class ListenAuthenticatorTest(unittest.TestCase):
+ def setUp(self):
+ self.authenticator = xmlstream.ListenAuthenticator()
+ self.xmlstream = xmlstream.XmlStream(self.authenticator)
+
+
+ def test_streamStart(self):
+ """
+ Test streamStart to fill the appropriate attributes from the
+ stream header.
+ """
+ xs = self.xmlstream
+ xs.makeConnection(proto_helpers.StringTransport())
+ self.assertIdentical(None, xs.sid)
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.org' to='example.com' id='12345' "
+ "version='1.0'>")
+ self.assertEqual((1, 0), xs.version)
+ self.assertNotIdentical(None, xs.sid)
+ self.assertNotEquals('12345', xs.sid)
+ self.assertEqual('jabber:client', xs.namespace)
+ self.assertIdentical(None, xs.otherEntity)
+ self.assertEqual('example.com', xs.thisEntity.host)
+
+
+
+class TLSInitiatingInitializerTest(unittest.TestCase):
+ def setUp(self):
+ self.output = []
+ self.done = []
+
+ self.savedSSL = xmlstream.ssl
+
+ self.authenticator = xmlstream.Authenticator()
+ self.xmlstream = xmlstream.XmlStream(self.authenticator)
+ self.xmlstream.send = self.output.append
+ self.xmlstream.connectionMade()
+ self.xmlstream.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345' version='1.0'>")
+ self.init = xmlstream.TLSInitiatingInitializer(self.xmlstream)
+
+
+ def tearDown(self):
+ xmlstream.ssl = self.savedSSL
+
+
+ def testWantedSupported(self):
+ """
+ Test start when TLS is wanted and the SSL library available.
+ """
+ self.xmlstream.transport = proto_helpers.StringTransport()
+ self.xmlstream.transport.startTLS = lambda ctx: self.done.append('TLS')
+ self.xmlstream.reset = lambda: self.done.append('reset')
+ self.xmlstream.sendHeader = lambda: self.done.append('header')
+
+ d = self.init.start()
+ d.addCallback(self.assertEquals, xmlstream.Reset)
+ starttls = self.output[0]
+ self.assertEquals('starttls', starttls.name)
+ self.assertEquals(NS_XMPP_TLS, starttls.uri)
+ self.xmlstream.dataReceived("<proceed xmlns='%s'/>" % NS_XMPP_TLS)
+ self.assertEquals(['TLS', 'reset', 'header'], self.done)
+
+ return d
+
+ if not xmlstream.ssl:
+ testWantedSupported.skip = "SSL not available"
+
+
+ def testWantedNotSupportedNotRequired(self):
+ """
+ Test start when TLS is wanted and the SSL library available.
+ """
+ xmlstream.ssl = None
+
+ d = self.init.start()
+ d.addCallback(self.assertEquals, None)
+ self.assertEquals([], self.output)
+
+ return d
+
+
+ def testWantedNotSupportedRequired(self):
+ """
+ Test start when TLS is wanted and the SSL library available.
+ """
+ xmlstream.ssl = None
+ self.init.required = True
+
+ d = self.init.start()
+ self.assertFailure(d, xmlstream.TLSNotSupported)
+ self.assertEquals([], self.output)
+
+ return d
+
+
+ def testNotWantedRequired(self):
+ """
+ Test start when TLS is not wanted, but required by the server.
+ """
+ tls = domish.Element(('urn:ietf:params:xml:ns:xmpp-tls', 'starttls'))
+ tls.addElement('required')
+ self.xmlstream.features = {(tls.uri, tls.name): tls}
+ self.init.wanted = False
+
+ d = self.init.start()
+ self.assertEquals([], self.output)
+ self.assertFailure(d, xmlstream.TLSRequired)
+
+ return d
+
+
+ def testNotWantedNotRequired(self):
+ """
+ Test start when TLS is not wanted, but required by the server.
+ """
+ tls = domish.Element(('urn:ietf:params:xml:ns:xmpp-tls', 'starttls'))
+ self.xmlstream.features = {(tls.uri, tls.name): tls}
+ self.init.wanted = False
+
+ d = self.init.start()
+ d.addCallback(self.assertEqual, None)
+ self.assertEquals([], self.output)
+ return d
+
+
+ def testFailed(self):
+ """
+ Test failed TLS negotiation.
+ """
+ # Pretend that ssl is supported, it isn't actually used when the
+ # server starts out with a failure in response to our initial
+ # C{starttls} stanza.
+ xmlstream.ssl = 1
+
+ d = self.init.start()
+ self.assertFailure(d, xmlstream.TLSFailed)
+ self.xmlstream.dataReceived("<failure xmlns='%s'/>" % NS_XMPP_TLS)
+ return d
+
+
+
+class TestFeatureInitializer(xmlstream.BaseFeatureInitiatingInitializer):
+ feature = ('testns', 'test')
+
+ def start(self):
+ return defer.succeed(None)
+
+
+
+class BaseFeatureInitiatingInitializerTest(unittest.TestCase):
+
+ def setUp(self):
+ self.xmlstream = xmlstream.XmlStream(xmlstream.Authenticator())
+ self.init = TestFeatureInitializer(self.xmlstream)
+
+
+ def testAdvertized(self):
+ """
+ Test that an advertized feature results in successful initialization.
+ """
+ self.xmlstream.features = {self.init.feature:
+ domish.Element(self.init.feature)}
+ return self.init.initialize()
+
+
+ def testNotAdvertizedRequired(self):
+ """
+ Test that when the feature is not advertized, but required by the
+ initializer, an exception is raised.
+ """
+ self.init.required = True
+ self.assertRaises(xmlstream.FeatureNotAdvertized, self.init.initialize)
+
+
+ def testNotAdvertizedNotRequired(self):
+ """
+ Test that when the feature is not advertized, and not required by the
+ initializer, the initializer silently succeeds.
+ """
+ self.init.required = False
+ self.assertIdentical(None, self.init.initialize())
+
+
+
+class ToResponseTest(unittest.TestCase):
+
+ def test_toResponse(self):
+ """
+ Test that a response stanza is generated with addressing swapped.
+ """
+ stanza = domish.Element(('jabber:client', 'iq'))
+ stanza['type'] = 'get'
+ stanza['to'] = 'user1@example.com'
+ stanza['from'] = 'user2@example.com/resource'
+ stanza['id'] = 'stanza1'
+ response = xmlstream.toResponse(stanza, 'result')
+ self.assertNotIdentical(stanza, response)
+ self.assertEqual(response['from'], 'user1@example.com')
+ self.assertEqual(response['to'], 'user2@example.com/resource')
+ self.assertEqual(response['type'], 'result')
+ self.assertEqual(response['id'], 'stanza1')
+
+
+ def test_toResponseNoFrom(self):
+ """
+ Test that a response is generated from a stanza without a from address.
+ """
+ stanza = domish.Element(('jabber:client', 'iq'))
+ stanza['type'] = 'get'
+ stanza['to'] = 'user1@example.com'
+ response = xmlstream.toResponse(stanza)
+ self.assertEqual(response['from'], 'user1@example.com')
+ self.assertFalse(response.hasAttribute('to'))
+
+
+ def test_toResponseNoTo(self):
+ """
+ Test that a response is generated from a stanza without a to address.
+ """
+ stanza = domish.Element(('jabber:client', 'iq'))
+ stanza['type'] = 'get'
+ stanza['from'] = 'user2@example.com/resource'
+ response = xmlstream.toResponse(stanza)
+ self.assertFalse(response.hasAttribute('from'))
+ self.assertEqual(response['to'], 'user2@example.com/resource')
+
+
+ def test_toResponseNoAddressing(self):
+ """
+ Test that a response is generated from a stanza without any addressing.
+ """
+ stanza = domish.Element(('jabber:client', 'message'))
+ stanza['type'] = 'chat'
+ response = xmlstream.toResponse(stanza)
+ self.assertFalse(response.hasAttribute('to'))
+ self.assertFalse(response.hasAttribute('from'))
+
+
+ def test_noID(self):
+ """
+ Test that a proper response is generated without id attribute.
+ """
+ stanza = domish.Element(('jabber:client', 'message'))
+ response = xmlstream.toResponse(stanza)
+ self.assertFalse(response.hasAttribute('id'))
+
+
+ def test_noType(self):
+ """
+ Test that a proper response is generated without type attribute.
+ """
+ stanza = domish.Element(('jabber:client', 'message'))
+ response = xmlstream.toResponse(stanza)
+ self.assertFalse(response.hasAttribute('type'))
+
+
+class DummyFactory(object):
+ """
+ Dummy XmlStream factory that only registers bootstrap observers.
+ """
+ def __init__(self):
+ self.callbacks = {}
+
+
+ def addBootstrap(self, event, callback):
+ self.callbacks[event] = callback
+
+
+
+class DummyXMPPHandler(xmlstream.XMPPHandler):
+ """
+ Dummy XMPP subprotocol handler to count the methods are called on it.
+ """
+ def __init__(self):
+ self.doneMade = 0
+ self.doneInitialized = 0
+ self.doneLost = 0
+
+
+ def makeConnection(self, xs):
+ self.connectionMade()
+
+
+ def connectionMade(self):
+ self.doneMade += 1
+
+
+ def connectionInitialized(self):
+ self.doneInitialized += 1
+
+
+ def connectionLost(self, reason):
+ self.doneLost += 1
+
+
+
+class XMPPHandlerTest(unittest.TestCase):
+ """
+ Tests for L{xmlstream.XMPPHandler}.
+ """
+
+ def test_interface(self):
+ """
+ L{xmlstream.XMPPHandler} implements L{ijabber.IXMPPHandler}.
+ """
+ verifyObject(ijabber.IXMPPHandler, xmlstream.XMPPHandler())
+
+
+ def test_send(self):
+ """
+ Test that data is passed on for sending by the stream manager.
+ """
+ class DummyStreamManager(object):
+ def __init__(self):
+ self.outlist = []
+
+ def send(self, data):
+ self.outlist.append(data)
+
+ handler = xmlstream.XMPPHandler()
+ handler.parent = DummyStreamManager()
+ handler.send('<presence/>')
+ self.assertEquals(['<presence/>'], handler.parent.outlist)
+
+
+ def test_makeConnection(self):
+ """
+ Test that makeConnection saves the XML stream and calls connectionMade.
+ """
+ class TestXMPPHandler(xmlstream.XMPPHandler):
+ def connectionMade(self):
+ self.doneMade = True
+
+ handler = TestXMPPHandler()
+ xs = xmlstream.XmlStream(xmlstream.Authenticator())
+ handler.makeConnection(xs)
+ self.assertTrue(handler.doneMade)
+ self.assertIdentical(xs, handler.xmlstream)
+
+
+ def test_connectionLost(self):
+ """
+ Test that connectionLost forgets the XML stream.
+ """
+ handler = xmlstream.XMPPHandler()
+ xs = xmlstream.XmlStream(xmlstream.Authenticator())
+ handler.makeConnection(xs)
+ handler.connectionLost(Exception())
+ self.assertIdentical(None, handler.xmlstream)
+
+
+
+class XMPPHandlerCollectionTest(unittest.TestCase):
+ """
+ Tests for L{xmlstream.XMPPHandlerCollection}.
+ """
+
+ def setUp(self):
+ self.collection = xmlstream.XMPPHandlerCollection()
+
+
+ def test_interface(self):
+ """
+ L{xmlstream.StreamManager} implements L{ijabber.IXMPPHandlerCollection}.
+ """
+ verifyObject(ijabber.IXMPPHandlerCollection, self.collection)
+
+
+ def test_addHandler(self):
+ """
+ Test the addition of a protocol handler.
+ """
+ handler = DummyXMPPHandler()
+ handler.setHandlerParent(self.collection)
+ self.assertIn(handler, self.collection)
+ self.assertIdentical(self.collection, handler.parent)
+
+
+ def test_removeHandler(self):
+ """
+ Test removal of a protocol handler.
+ """
+ handler = DummyXMPPHandler()
+ handler.setHandlerParent(self.collection)
+ handler.disownHandlerParent(self.collection)
+ self.assertNotIn(handler, self.collection)
+ self.assertIdentical(None, handler.parent)
+
+
+
+class StreamManagerTest(unittest.TestCase):
+ """
+ Tests for L{xmlstream.StreamManager}.
+ """
+
+ def setUp(self):
+ factory = DummyFactory()
+ self.streamManager = xmlstream.StreamManager(factory)
+
+
+ def test_basic(self):
+ """
+ Test correct initialization and setup of factory observers.
+ """
+ sm = self.streamManager
+ self.assertIdentical(None, sm.xmlstream)
+ self.assertEquals([], sm.handlers)
+ self.assertEquals(sm._connected,
+ sm.factory.callbacks['//event/stream/connected'])
+ self.assertEquals(sm._authd,
+ sm.factory.callbacks['//event/stream/authd'])
+ self.assertEquals(sm._disconnected,
+ sm.factory.callbacks['//event/stream/end'])
+ self.assertEquals(sm.initializationFailed,
+ sm.factory.callbacks['//event/xmpp/initfailed'])
+
+
+ def test_connected(self):
+ """
+ Test that protocol handlers have their connectionMade method called
+ when the XML stream is connected.
+ """
+ sm = self.streamManager
+ handler = DummyXMPPHandler()
+ handler.setHandlerParent(sm)
+ xs = xmlstream.XmlStream(xmlstream.Authenticator())
+ sm._connected(xs)
+ self.assertEquals(1, handler.doneMade)
+ self.assertEquals(0, handler.doneInitialized)
+ self.assertEquals(0, handler.doneLost)
+
+
+ def test_connectedLogTrafficFalse(self):
+ """
+ Test raw data functions unset when logTraffic is set to False.
+ """
+ sm = self.streamManager
+ handler = DummyXMPPHandler()
+ handler.setHandlerParent(sm)
+ xs = xmlstream.XmlStream(xmlstream.Authenticator())
+ sm._connected(xs)
+ self.assertIdentical(None, xs.rawDataInFn)
+ self.assertIdentical(None, xs.rawDataOutFn)
+
+
+ def test_connectedLogTrafficTrue(self):
+ """
+ Test raw data functions set when logTraffic is set to True.
+ """
+ sm = self.streamManager
+ sm.logTraffic = True
+ handler = DummyXMPPHandler()
+ handler.setHandlerParent(sm)
+ xs = xmlstream.XmlStream(xmlstream.Authenticator())
+ sm._connected(xs)
+ self.assertNotIdentical(None, xs.rawDataInFn)
+ self.assertNotIdentical(None, xs.rawDataOutFn)
+
+
+ def test_authd(self):
+ """
+ Test that protocol handlers have their connectionInitialized method
+ called when the XML stream is initialized.
+ """
+ sm = self.streamManager
+ handler = DummyXMPPHandler()
+ handler.setHandlerParent(sm)
+ xs = xmlstream.XmlStream(xmlstream.Authenticator())
+ sm._authd(xs)
+ self.assertEquals(0, handler.doneMade)
+ self.assertEquals(1, handler.doneInitialized)
+ self.assertEquals(0, handler.doneLost)
+
+
+ def test_disconnected(self):
+ """
+ Test that protocol handlers have their connectionLost method
+ called when the XML stream is disconnected.
+ """
+ sm = self.streamManager
+ handler = DummyXMPPHandler()
+ handler.setHandlerParent(sm)
+ xs = xmlstream.XmlStream(xmlstream.Authenticator())
+ sm._disconnected(xs)
+ self.assertEquals(0, handler.doneMade)
+ self.assertEquals(0, handler.doneInitialized)
+ self.assertEquals(1, handler.doneLost)
+
+
+ def test_addHandler(self):
+ """
+ Test the addition of a protocol handler while not connected.
+ """
+ sm = self.streamManager
+ handler = DummyXMPPHandler()
+ handler.setHandlerParent(sm)
+
+ self.assertEquals(0, handler.doneMade)
+ self.assertEquals(0, handler.doneInitialized)
+ self.assertEquals(0, handler.doneLost)
+
+
+ def test_addHandlerInitialized(self):
+ """
+ Test the addition of a protocol handler after the stream
+ have been initialized.
+
+ Make sure that the handler will have the connected stream
+ passed via C{makeConnection} and have C{connectionInitialized}
+ called.
+ """
+ sm = self.streamManager
+ xs = xmlstream.XmlStream(xmlstream.Authenticator())
+ sm._connected(xs)
+ sm._authd(xs)
+ handler = DummyXMPPHandler()
+ handler.setHandlerParent(sm)
+
+ self.assertEquals(1, handler.doneMade)
+ self.assertEquals(1, handler.doneInitialized)
+ self.assertEquals(0, handler.doneLost)
+
+
+ def test_sendInitialized(self):
+ """
+ Test send when the stream has been initialized.
+
+ The data should be sent directly over the XML stream.
+ """
+ factory = xmlstream.XmlStreamFactory(xmlstream.Authenticator())
+ sm = xmlstream.StreamManager(factory)
+ xs = factory.buildProtocol(None)
+ xs.transport = proto_helpers.StringTransport()
+ xs.connectionMade()
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345'>")
+ xs.dispatch(xs, "//event/stream/authd")
+ sm.send("<presence/>")
+ self.assertEquals("<presence/>", xs.transport.value())
+
+
+ def test_sendNotConnected(self):
+ """
+ Test send when there is no established XML stream.
+
+ The data should be cached until an XML stream has been established and
+ initialized.
+ """
+ factory = xmlstream.XmlStreamFactory(xmlstream.Authenticator())
+ sm = xmlstream.StreamManager(factory)
+ handler = DummyXMPPHandler()
+ sm.addHandler(handler)
+
+ xs = factory.buildProtocol(None)
+ xs.transport = proto_helpers.StringTransport()
+ sm.send("<presence/>")
+ self.assertEquals("", xs.transport.value())
+ self.assertEquals("<presence/>", sm._packetQueue[0])
+
+ xs.connectionMade()
+ self.assertEquals("", xs.transport.value())
+ self.assertEquals("<presence/>", sm._packetQueue[0])
+
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345'>")
+ xs.dispatch(xs, "//event/stream/authd")
+
+ self.assertEquals("<presence/>", xs.transport.value())
+ self.assertFalse(sm._packetQueue)
+
+
+ def test_sendNotInitialized(self):
+ """
+ Test send when the stream is connected but not yet initialized.
+
+ The data should be cached until the XML stream has been initialized.
+ """
+ factory = xmlstream.XmlStreamFactory(xmlstream.Authenticator())
+ sm = xmlstream.StreamManager(factory)
+ xs = factory.buildProtocol(None)
+ xs.transport = proto_helpers.StringTransport()
+ xs.connectionMade()
+ xs.dataReceived("<stream:stream xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams' "
+ "from='example.com' id='12345'>")
+ sm.send("<presence/>")
+ self.assertEquals("", xs.transport.value())
+ self.assertEquals("<presence/>", sm._packetQueue[0])
+
+
+ def test_sendDisconnected(self):
+ """
+ Test send after XML stream disconnection.
+
+ The data should be cached until a new XML stream has been established
+ and initialized.
+ """
+ factory = xmlstream.XmlStreamFactory(xmlstream.Authenticator())
+ sm = xmlstream.StreamManager(factory)
+ handler = DummyXMPPHandler()
+ sm.addHandler(handler)
+
+ xs = factory.buildProtocol(None)
+ xs.connectionMade()
+ xs.transport = proto_helpers.StringTransport()
+ xs.connectionLost(None)
+
+ sm.send("<presence/>")
+ self.assertEquals("", xs.transport.value())
+ self.assertEquals("<presence/>", sm._packetQueue[0])
+
+
+
+class XmlStreamServerFactoryTest(GenericXmlStreamFactoryTestsMixin):
+ """
+ Tests for L{xmlstream.XmlStreamServerFactory}.
+ """
+
+ def setUp(self):
+ """
+ Set up a server factory with a authenticator factory function.
+ """
+ class TestAuthenticator(object):
+ def __init__(self):
+ self.xmlstreams = []
+
+ def associateWithStream(self, xs):
+ self.xmlstreams.append(xs)
+
+ def authenticatorFactory():
+ return TestAuthenticator()
+
+ self.factory = xmlstream.XmlStreamServerFactory(authenticatorFactory)
+
+
+ def test_interface(self):
+ """
+ L{XmlStreamServerFactory} is a L{Factory}.
+ """
+ verifyObject(IProtocolFactory, self.factory)
+
+
+ def test_buildProtocolAuthenticatorInstantiation(self):
+ """
+ The authenticator factory should be used to instantiate the
+ authenticator and pass it to the protocol.
+
+ The default protocol, L{XmlStream} stores the authenticator it is
+ passed, and calls its C{associateWithStream} method. so we use that to
+ check whether our authenticator factory is used and the protocol
+ instance gets an authenticator.
+ """
+ xs = self.factory.buildProtocol(None)
+ self.assertEquals([xs], xs.authenticator.xmlstreams)
+
+
+ def test_buildProtocolXmlStream(self):
+ """
+ The protocol factory creates Jabber XML Stream protocols by default.
+ """
+ xs = self.factory.buildProtocol(None)
+ self.assertIsInstance(xs, xmlstream.XmlStream)
+
+
+ def test_buildProtocolTwice(self):
+ """
+ Subsequent calls to buildProtocol should result in different instances
+ of the protocol, as well as their authenticators.
+ """
+ xs1 = self.factory.buildProtocol(None)
+ xs2 = self.factory.buildProtocol(None)
+ self.assertNotIdentical(xs1, xs2)
+ self.assertNotIdentical(xs1.authenticator, xs2.authenticator)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_jabberxmppstringprep.py b/vendor/Twisted-10.0.0/twisted/words/test/test_jabberxmppstringprep.py
new file mode 100644
index 0000000000..e42c0d6c52
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_jabberxmppstringprep.py
@@ -0,0 +1,84 @@
+# Copyright (c) 2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.trial import unittest
+
+from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep, resourceprep, nameprep, crippled
+
+class XMPPStringPrepTest(unittest.TestCase):
+ """
+
+ The nodeprep stringprep profile is similar to the resourceprep profile,
+ but does an extra mapping of characters (table B.2) and disallows
+ more characters (table C.1.1 and eight extra punctuation characters).
+ Due to this similarity, the resourceprep tests are more extensive, and
+ the nodeprep tests only address the mappings additional restrictions.
+
+ The nameprep profile is nearly identical to the nameprep implementation in
+ L{encodings.idna}, but that implementation assumes the C{UseSTD4ASCIIRules}
+ flag to be false. This implementation assumes it to be true, and restricts
+ the allowed set of characters. The tests here only check for the
+ differences.
+
+ """
+
+ def testResourcePrep(self):
+ self.assertEquals(resourceprep.prepare(u'resource'), u'resource')
+ self.assertNotEquals(resourceprep.prepare(u'Resource'), u'resource')
+ self.assertEquals(resourceprep.prepare(u' '), u' ')
+
+ if crippled:
+ return
+
+ self.assertEquals(resourceprep.prepare(u'Henry \u2163'), u'Henry IV')
+ self.assertEquals(resourceprep.prepare(u'foo\xad\u034f\u1806\u180b'
+ u'bar\u200b\u2060'
+ u'baz\ufe00\ufe08\ufe0f\ufeff'),
+ u'foobarbaz')
+ self.assertEquals(resourceprep.prepare(u'\u00a0'), u' ')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\u1680')
+ self.assertEquals(resourceprep.prepare(u'\u2000'), u' ')
+ self.assertEquals(resourceprep.prepare(u'\u200b'), u'')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\u0010\u007f')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\u0085')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\u180e')
+ self.assertEquals(resourceprep.prepare(u'\ufeff'), u'')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\uf123')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\U000f1234')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\U0010f234')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\U0008fffe')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\U0010ffff')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\udf42')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\ufffd')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\u2ff5')
+ self.assertEquals(resourceprep.prepare(u'\u0341'), u'\u0301')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\u200e')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\u202a')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\U000e0001')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\U000e0042')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'foo\u05bebar')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'foo\ufd50bar')
+ #self.assertEquals(resourceprep.prepare(u'foo\ufb38bar'),
+ # u'foo\u064ebar')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\u06271')
+ self.assertEquals(resourceprep.prepare(u'\u06271\u0628'),
+ u'\u06271\u0628')
+ self.assertRaises(UnicodeError, resourceprep.prepare, u'\U000e0002')
+
+ def testNodePrep(self):
+ self.assertEquals(nodeprep.prepare(u'user'), u'user')
+ self.assertEquals(nodeprep.prepare(u'User'), u'user')
+ self.assertRaises(UnicodeError, nodeprep.prepare, u'us&er')
+
+ def testNamePrep(self):
+ self.assertEquals(nameprep.prepare(u'example.com'), u'example.com')
+ self.assertEquals(nameprep.prepare(u'Example.com'), u'example.com')
+ self.assertRaises(UnicodeError, nameprep.prepare, u'ex@mple.com')
+ self.assertRaises(UnicodeError, nameprep.prepare, u'-example.com')
+ self.assertRaises(UnicodeError, nameprep.prepare, u'example-.com')
+
+ if crippled:
+ return
+
+ self.assertEquals(nameprep.prepare(u'stra\u00dfe.example.com'),
+ u'strasse.example.com')
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_msn.py b/vendor/Twisted-10.0.0/twisted/words/test/test_msn.py
new file mode 100644
index 0000000000..9074310cb9
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_msn.py
@@ -0,0 +1,503 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for L{twisted.words.protocols.msn}.
+"""
+
+# System imports
+import StringIO
+
+# Twisted imports
+
+# t.w.p.msn requires an HTTP client
+try:
+ # So try to get one - do it directly instead of catching an ImportError
+ # from t.w.p.msn so that other problems which cause that module to fail
+ # to import don't cause the tests to be skipped.
+ from twisted.web import client
+except ImportError:
+ # If there isn't one, we're going to skip all the tests.
+ msn = None
+else:
+ # Otherwise importing it should work, so do it.
+ from twisted.words.protocols import msn
+
+
+from twisted.python.hashlib import md5
+from twisted.protocols import loopback
+from twisted.internet.defer import Deferred
+from twisted.trial import unittest
+from twisted.test.proto_helpers import StringTransport, StringIOWithoutClosing
+
+def printError(f):
+ print f
+
+
+class PassportTests(unittest.TestCase):
+
+ def setUp(self):
+ self.result = []
+ self.deferred = Deferred()
+ self.deferred.addCallback(lambda r: self.result.append(r))
+ self.deferred.addErrback(printError)
+
+ def test_nexus(self):
+ """
+ When L{msn.PassportNexus} receives enough information to identify the
+ address of the login server, it fires the L{Deferred} passed to its
+ initializer with that address.
+ """
+ protocol = msn.PassportNexus(self.deferred, 'https://foobar.com/somepage.quux')
+ headers = {
+ 'Content-Length' : '0',
+ 'Content-Type' : 'text/html',
+ 'PassportURLs' : 'DARealm=Passport.Net,DALogin=login.myserver.com/,DAReg=reg.myserver.com'
+ }
+ transport = StringTransport()
+ protocol.makeConnection(transport)
+ protocol.dataReceived('HTTP/1.0 200 OK\r\n')
+ for (h, v) in headers.items():
+ protocol.dataReceived('%s: %s\r\n' % (h,v))
+ protocol.dataReceived('\r\n')
+ self.assertEquals(self.result[0], "https://login.myserver.com/")
+
+
+ def _doLoginTest(self, response, headers):
+ protocol = msn.PassportLogin(self.deferred,'foo@foo.com','testpass','https://foo.com/', 'a')
+ protocol.makeConnection(StringTransport())
+ protocol.dataReceived(response)
+ for (h,v) in headers.items(): protocol.dataReceived('%s: %s\r\n' % (h,v))
+ protocol.dataReceived('\r\n')
+
+ def testPassportLoginSuccess(self):
+ headers = {
+ 'Content-Length' : '0',
+ 'Content-Type' : 'text/html',
+ 'Authentication-Info' : "Passport1.4 da-status=success,tname=MSPAuth," +
+ "tname=MSPProf,tname=MSPSec,from-PP='somekey'," +
+ "ru=http://messenger.msn.com"
+ }
+ self._doLoginTest('HTTP/1.1 200 OK\r\n', headers)
+ self.failUnless(self.result[0] == (msn.LOGIN_SUCCESS, 'somekey'))
+
+ def testPassportLoginFailure(self):
+ headers = {
+ 'Content-Type' : 'text/html',
+ 'WWW-Authenticate' : 'Passport1.4 da-status=failed,' +
+ 'srealm=Passport.NET,ts=-3,prompt,cburl=http://host.com,' +
+ 'cbtxt=the%20error%20message'
+ }
+ self._doLoginTest('HTTP/1.1 401 Unauthorized\r\n', headers)
+ self.failUnless(self.result[0] == (msn.LOGIN_FAILURE, 'the error message'))
+
+ def testPassportLoginRedirect(self):
+ headers = {
+ 'Content-Type' : 'text/html',
+ 'Authentication-Info' : 'Passport1.4 da-status=redir',
+ 'Location' : 'https://newlogin.host.com/'
+ }
+ self._doLoginTest('HTTP/1.1 302 Found\r\n', headers)
+ self.failUnless(self.result[0] == (msn.LOGIN_REDIRECT, 'https://newlogin.host.com/', 'a'))
+
+
+if msn is not None:
+ class DummySwitchboardClient(msn.SwitchboardClient):
+ def userTyping(self, message):
+ self.state = 'TYPING'
+
+ def gotSendRequest(self, fileName, fileSize, cookie, message):
+ if fileName == 'foobar.ext' and fileSize == 31337 and cookie == 1234: self.state = 'INVITATION'
+
+
+ class DummyNotificationClient(msn.NotificationClient):
+ def loggedIn(self, userHandle, screenName, verified):
+ if userHandle == 'foo@bar.com' and screenName == 'Test Screen Name' and verified:
+ self.state = 'LOGIN'
+
+ def gotProfile(self, message):
+ self.state = 'PROFILE'
+
+ def gotContactStatus(self, code, userHandle, screenName):
+ if code == msn.STATUS_AWAY and userHandle == "foo@bar.com" and screenName == "Test Screen Name":
+ self.state = 'INITSTATUS'
+
+ def contactStatusChanged(self, code, userHandle, screenName):
+ if code == msn.STATUS_LUNCH and userHandle == "foo@bar.com" and screenName == "Test Name":
+ self.state = 'NEWSTATUS'
+
+ def contactOffline(self, userHandle):
+ if userHandle == "foo@bar.com": self.state = 'OFFLINE'
+
+ def statusChanged(self, code):
+ if code == msn.STATUS_HIDDEN: self.state = 'MYSTATUS'
+
+ def listSynchronized(self, *args):
+ self.state = 'GOTLIST'
+
+ def gotPhoneNumber(self, listVersion, userHandle, phoneType, number):
+ msn.NotificationClient.gotPhoneNumber(self, listVersion, userHandle, phoneType, number)
+ self.state = 'GOTPHONE'
+
+ def userRemovedMe(self, userHandle, listVersion):
+ msn.NotificationClient.userRemovedMe(self, userHandle, listVersion)
+ c = self.factory.contacts.getContact(userHandle)
+ if not c and self.factory.contacts.version == listVersion: self.state = 'USERREMOVEDME'
+
+ def userAddedMe(self, userHandle, screenName, listVersion):
+ msn.NotificationClient.userAddedMe(self, userHandle, screenName, listVersion)
+ c = self.factory.contacts.getContact(userHandle)
+ if c and (c.lists | msn.REVERSE_LIST) and (self.factory.contacts.version == listVersion) and \
+ (screenName == 'Screen Name'):
+ self.state = 'USERADDEDME'
+
+ def gotSwitchboardInvitation(self, sessionID, host, port, key, userHandle, screenName):
+ if sessionID == 1234 and \
+ host == '192.168.1.1' and \
+ port == 1863 and \
+ key == '123.456' and \
+ userHandle == 'foo@foo.com' and \
+ screenName == 'Screen Name':
+ self.state = 'SBINVITED'
+
+
+
+class DispatchTests(unittest.TestCase):
+ """
+ Tests for L{DispatchClient}.
+ """
+ def _versionTest(self, serverVersionResponse):
+ """
+ Test L{DispatchClient} version negotiation.
+ """
+ client = msn.DispatchClient()
+ client.userHandle = "foo"
+
+ transport = StringTransport()
+ client.makeConnection(transport)
+ self.assertEquals(
+ transport.value(), "VER 1 MSNP8 CVR0\r\n")
+ transport.clear()
+
+ client.dataReceived(serverVersionResponse)
+ self.assertEquals(
+ transport.value(),
+ "CVR 2 0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS foo\r\n")
+
+
+ def test_version(self):
+ """
+ L{DispatchClient.connectionMade} greets the server with a I{VER}
+ (version) message and then L{NotificationClient.dataReceived}
+ handles the server's I{VER} response by sending a I{CVR} (client
+ version) message.
+ """
+ self._versionTest("VER 1 MSNP8 CVR0\r\n")
+
+
+ def test_versionWithoutCVR0(self):
+ """
+ If the server responds to a I{VER} command without including the
+ I{CVR0} protocol, L{DispatchClient} behaves in the same way as if
+ that protocol were included.
+
+ Starting in August 2008, CVR0 disappeared from the I{VER} response.
+ """
+ self._versionTest("VER 1 MSNP8\r\n")
+
+
+
+class NotificationTests(unittest.TestCase):
+ """ testing the various events in NotificationClient """
+
+ def setUp(self):
+ self.client = DummyNotificationClient()
+ self.client.factory = msn.NotificationFactory()
+ self.client.state = 'START'
+
+
+ def tearDown(self):
+ self.client = None
+
+
+ def _versionTest(self, serverVersionResponse):
+ """
+ Test L{NotificationClient} version negotiation.
+ """
+ self.client.factory.userHandle = "foo"
+
+ transport = StringTransport()
+ self.client.makeConnection(transport)
+ self.assertEquals(
+ transport.value(), "VER 1 MSNP8 CVR0\r\n")
+ transport.clear()
+
+ self.client.dataReceived(serverVersionResponse)
+ self.assertEquals(
+ transport.value(),
+ "CVR 2 0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS foo\r\n")
+
+
+ def test_version(self):
+ """
+ L{NotificationClient.connectionMade} greets the server with a I{VER}
+ (version) message and then L{NotificationClient.dataReceived}
+ handles the server's I{VER} response by sending a I{CVR} (client
+ version) message.
+ """
+ self._versionTest("VER 1 MSNP8 CVR0\r\n")
+
+
+ def test_versionWithoutCVR0(self):
+ """
+ If the server responds to a I{VER} command without including the
+ I{CVR0} protocol, L{NotificationClient} behaves in the same way as
+ if that protocol were included.
+
+ Starting in August 2008, CVR0 disappeared from the I{VER} response.
+ """
+ self._versionTest("VER 1 MSNP8\r\n")
+
+
+ def test_challenge(self):
+ """
+ L{NotificationClient} responds to a I{CHL} message by sending a I{QRY}
+ back which included a hash based on the parameters of the I{CHL}.
+ """
+ transport = StringTransport()
+ self.client.makeConnection(transport)
+ transport.clear()
+
+ challenge = "15570131571988941333"
+ self.client.dataReceived('CHL 0 ' + challenge + '\r\n')
+ # md5 of the challenge and a magic string defined by the protocol
+ response = "8f2f5a91b72102cd28355e9fc9000d6e"
+ # Sanity check - the response is what the comment above says it is.
+ self.assertEquals(
+ response, md5(challenge + "Q1P7W2E4J9R8U3S5").hexdigest())
+ self.assertEquals(
+ transport.value(),
+ # 2 is the next transaction identifier. 32 is the length of the
+ # response.
+ "QRY 2 msmsgs@msnmsgr.com 32\r\n" + response)
+
+
+ def testLogin(self):
+ self.client.lineReceived('USR 1 OK foo@bar.com Test%20Screen%20Name 1 0')
+ self.failUnless((self.client.state == 'LOGIN'), msg='Failed to detect successful login')
+
+
+ def testProfile(self):
+ m = 'MSG Hotmail Hotmail 353\r\nMIME-Version: 1.0\r\nContent-Type: text/x-msmsgsprofile; charset=UTF-8\r\n'
+ m += 'LoginTime: 1016941010\r\nEmailEnabled: 1\r\nMemberIdHigh: 40000\r\nMemberIdLow: -600000000\r\nlang_preference: 1033\r\n'
+ m += 'preferredEmail: foo@bar.com\r\ncountry: AU\r\nPostalCode: 90210\r\nGender: M\r\nKid: 0\r\nAge:\r\nsid: 400\r\n'
+ m += 'kv: 2\r\nMSPAuth: 2CACCBCCADMoV8ORoz64BVwmjtksIg!kmR!Rj5tBBqEaW9hc4YnPHSOQ$$\r\n\r\n'
+ map(self.client.lineReceived, m.split('\r\n')[:-1])
+ self.failUnless((self.client.state == 'PROFILE'), msg='Failed to detect initial profile')
+
+ def testStatus(self):
+ t = [('ILN 1 AWY foo@bar.com Test%20Screen%20Name 0', 'INITSTATUS', 'Failed to detect initial status report'),
+ ('NLN LUN foo@bar.com Test%20Name 0', 'NEWSTATUS', 'Failed to detect contact status change'),
+ ('FLN foo@bar.com', 'OFFLINE', 'Failed to detect contact signing off'),
+ ('CHG 1 HDN 0', 'MYSTATUS', 'Failed to detect my status changing')]
+ for i in t:
+ self.client.lineReceived(i[0])
+ self.failUnless((self.client.state == i[1]), msg=i[2])
+
+ def testListSync(self):
+ # currently this test does not take into account the fact
+ # that BPRs sent as part of the SYN reply may not be interpreted
+ # as such if they are for the last LST -- maybe I should
+ # factor this in later.
+ self.client.makeConnection(StringTransport())
+ msn.NotificationClient.loggedIn(self.client, 'foo@foo.com', 'foobar', 1)
+ lines = [
+ "SYN %s 100 1 1" % self.client.currentID,
+ "GTC A",
+ "BLP AL",
+ "LSG 0 Other%20Contacts 0",
+ "LST userHandle@email.com Some%20Name 11 0"
+ ]
+ map(self.client.lineReceived, lines)
+ contacts = self.client.factory.contacts
+ contact = contacts.getContact('userHandle@email.com')
+ self.failUnless(contacts.version == 100, "Invalid contact list version")
+ self.failUnless(contact.screenName == 'Some Name', "Invalid screen-name for user")
+ self.failUnless(contacts.groups == {0 : 'Other Contacts'}, "Did not get proper group list")
+ self.failUnless(contact.groups == [0] and contact.lists == 11, "Invalid contact list/group info")
+ self.failUnless(self.client.state == 'GOTLIST', "Failed to call list sync handler")
+
+ def testAsyncPhoneChange(self):
+ c = msn.MSNContact(userHandle='userHandle@email.com')
+ self.client.factory.contacts = msn.MSNContactList()
+ self.client.factory.contacts.addContact(c)
+ self.client.makeConnection(StringTransport())
+ self.client.lineReceived("BPR 101 userHandle@email.com PHH 123%20456")
+ c = self.client.factory.contacts.getContact('userHandle@email.com')
+ self.failUnless(self.client.state == 'GOTPHONE', "Did not fire phone change callback")
+ self.failUnless(c.homePhone == '123 456', "Did not update the contact's phone number")
+ self.failUnless(self.client.factory.contacts.version == 101, "Did not update list version")
+
+ def testLateBPR(self):
+ """
+ This test makes sure that if a BPR response that was meant
+ to be part of a SYN response (but came after the last LST)
+ is received, the correct contact is updated and all is well
+ """
+ self.client.makeConnection(StringTransport())
+ msn.NotificationClient.loggedIn(self.client, 'foo@foo.com', 'foo', 1)
+ lines = [
+ "SYN %s 100 1 1" % self.client.currentID,
+ "GTC A",
+ "BLP AL",
+ "LSG 0 Other%20Contacts 0",
+ "LST userHandle@email.com Some%20Name 11 0",
+ "BPR PHH 123%20456"
+ ]
+ map(self.client.lineReceived, lines)
+ contact = self.client.factory.contacts.getContact('userHandle@email.com')
+ self.failUnless(contact.homePhone == '123 456', "Did not update contact's phone number")
+
+ def testUserRemovedMe(self):
+ self.client.factory.contacts = msn.MSNContactList()
+ contact = msn.MSNContact(userHandle='foo@foo.com')
+ contact.addToList(msn.REVERSE_LIST)
+ self.client.factory.contacts.addContact(contact)
+ self.client.lineReceived("REM 0 RL 100 foo@foo.com")
+ self.failUnless(self.client.state == 'USERREMOVEDME', "Failed to remove user from reverse list")
+
+ def testUserAddedMe(self):
+ self.client.factory.contacts = msn.MSNContactList()
+ self.client.lineReceived("ADD 0 RL 100 foo@foo.com Screen%20Name")
+ self.failUnless(self.client.state == 'USERADDEDME', "Failed to add user to reverse lise")
+
+ def testAsyncSwitchboardInvitation(self):
+ self.client.lineReceived("RNG 1234 192.168.1.1:1863 CKI 123.456 foo@foo.com Screen%20Name")
+ self.failUnless(self.client.state == "SBINVITED")
+
+ def testCommandFailed(self):
+ """
+ Ensures that error responses from the server fires an errback with
+ MSNCommandFailed.
+ """
+ id, d = self.client._createIDMapping()
+ self.client.lineReceived("201 %s" % id)
+ d = self.assertFailure(d, msn.MSNCommandFailed)
+ def assertErrorCode(exception):
+ self.assertEqual(201, exception.errorCode)
+ return d.addCallback(assertErrorCode)
+
+
+class MessageHandlingTests(unittest.TestCase):
+ """ testing various message handling methods from SwichboardClient """
+
+ def setUp(self):
+ self.client = DummySwitchboardClient()
+ self.client.state = 'START'
+
+ def tearDown(self):
+ self.client = None
+
+ def testClientCapabilitiesCheck(self):
+ m = msn.MSNMessage()
+ m.setHeader('Content-Type', 'text/x-clientcaps')
+ self.assertEquals(self.client.checkMessage(m), 0, 'Failed to detect client capability message')
+
+ def testTypingCheck(self):
+ m = msn.MSNMessage()
+ m.setHeader('Content-Type', 'text/x-msmsgscontrol')
+ m.setHeader('TypingUser', 'foo@bar')
+ self.client.checkMessage(m)
+ self.failUnless((self.client.state == 'TYPING'), msg='Failed to detect typing notification')
+
+ def testFileInvitation(self, lazyClient=False):
+ m = msn.MSNMessage()
+ m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
+ m.message += 'Application-Name: File Transfer\r\n'
+ if not lazyClient:
+ m.message += 'Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n'
+ m.message += 'Invitation-Command: Invite\r\n'
+ m.message += 'Invitation-Cookie: 1234\r\n'
+ m.message += 'Application-File: foobar.ext\r\n'
+ m.message += 'Application-FileSize: 31337\r\n\r\n'
+ self.client.checkMessage(m)
+ self.failUnless((self.client.state == 'INVITATION'), msg='Failed to detect file transfer invitation')
+
+ def testFileInvitationMissingGUID(self):
+ return self.testFileInvitation(True)
+
+ def testFileResponse(self):
+ d = Deferred()
+ d.addCallback(self.fileResponse)
+ self.client.cookies['iCookies'][1234] = (d, None)
+ m = msn.MSNMessage()
+ m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
+ m.message += 'Invitation-Command: ACCEPT\r\n'
+ m.message += 'Invitation-Cookie: 1234\r\n\r\n'
+ self.client.checkMessage(m)
+ self.failUnless((self.client.state == 'RESPONSE'), msg='Failed to detect file transfer response')
+
+ def testFileInfo(self):
+ d = Deferred()
+ d.addCallback(self.fileInfo)
+ self.client.cookies['external'][1234] = (d, None)
+ m = msn.MSNMessage()
+ m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
+ m.message += 'Invitation-Command: ACCEPT\r\n'
+ m.message += 'Invitation-Cookie: 1234\r\n'
+ m.message += 'IP-Address: 192.168.0.1\r\n'
+ m.message += 'Port: 6891\r\n'
+ m.message += 'AuthCookie: 4321\r\n\r\n'
+ self.client.checkMessage(m)
+ self.failUnless((self.client.state == 'INFO'), msg='Failed to detect file transfer info')
+
+ def fileResponse(self, (accept, cookie, info)):
+ if accept and cookie == 1234: self.client.state = 'RESPONSE'
+
+ def fileInfo(self, (accept, ip, port, aCookie, info)):
+ if accept and ip == '192.168.0.1' and port == 6891 and aCookie == 4321: self.client.state = 'INFO'
+
+
+class FileTransferTestCase(unittest.TestCase):
+ """
+ test FileSend against FileReceive
+ """
+
+ def setUp(self):
+ self.input = 'a' * 7000
+ self.output = StringIOWithoutClosing()
+
+
+ def tearDown(self):
+ self.input = None
+ self.output = None
+
+
+ def test_fileTransfer(self):
+ """
+ Test L{FileSend} against L{FileReceive} using a loopback transport.
+ """
+ auth = 1234
+ sender = msn.FileSend(StringIO.StringIO(self.input))
+ sender.auth = auth
+ sender.fileSize = 7000
+ client = msn.FileReceive(auth, "foo@bar.com", self.output)
+ client.fileSize = 7000
+ def check(ignored):
+ self.assertTrue(
+ client.completed and sender.completed,
+ msg="send failed to complete")
+ self.assertEqual(
+ self.input, self.output.getvalue(),
+ msg="saved file does not match original")
+ d = loopback.loopbackAsync(sender, client)
+ d.addCallback(check)
+ return d
+
+
+if msn is None:
+ for testClass in [PassportTests, NotificationTests,
+ MessageHandlingTests, FileTransferTestCase]:
+ testClass.skip = (
+ "MSN requires an HTTP client but none is available, "
+ "skipping tests.")
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_oscar.py b/vendor/Twisted-10.0.0/twisted/words/test/test_oscar.py
new file mode 100644
index 0000000000..7fdcf40238
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_oscar.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.protocols.oscar}.
+"""
+
+from twisted.trial.unittest import TestCase
+
+from twisted.words.protocols.oscar import encryptPasswordMD5
+
+
+class PasswordTests(TestCase):
+ """
+ Tests for L{encryptPasswordMD5}.
+ """
+ def test_encryptPasswordMD5(self):
+ """
+ L{encryptPasswordMD5} hashes the given password and key and returns a
+ string suitable to use to authenticate against an OSCAR server.
+ """
+ self.assertEqual(
+ encryptPasswordMD5('foo', 'bar').encode('hex'),
+ 'd73475c370a7b18c6c20386bcf1339f2')
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_service.py b/vendor/Twisted-10.0.0/twisted/words/test/test_service.py
new file mode 100644
index 0000000000..aa79a64311
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_service.py
@@ -0,0 +1,992 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.service}.
+"""
+
+import time
+
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+from twisted.cred import portal, credentials, checkers
+from twisted.words import ewords, service
+from twisted.words.protocols import irc
+from twisted.spread import pb
+from twisted.internet.defer import Deferred, DeferredList, maybeDeferred, succeed
+from twisted.internet.defer import deferredGenerator as dG, waitForDeferred as wFD
+from twisted.internet import address, reactor
+
+class RealmTestCase(unittest.TestCase):
+ def _entityCreationTest(self, kind):
+ # Kind is "user" or "group"
+ realm = service.InMemoryWordsRealm("realmname")
+
+ name = u'test' + kind.lower()
+ create = getattr(realm, 'create' + kind.title())
+ get = getattr(realm, 'get' + kind.title())
+ flag = 'create' + kind.title() + 'OnRequest'
+ dupExc = getattr(ewords, 'Duplicate' + kind.title())
+ noSuchExc = getattr(ewords, 'NoSuch' + kind.title())
+
+ # Creating should succeed
+ d = wFD(create(name))
+ yield d
+ p = d.getResult()
+ self.assertEquals(p.name, name)
+
+ # Creating the same user again should not
+ d = wFD(create(name))
+ yield d
+ self.assertRaises(dupExc, d.getResult)
+
+ # Getting a non-existent user should succeed if createUserOnRequest is True
+ setattr(realm, flag, True)
+ d = wFD(get(u"new" + kind.lower()))
+ yield d
+ p = d.getResult()
+ self.assertEquals(p.name, "new" + kind.lower())
+
+ # Getting that user again should return the same object
+ d = wFD(get(u"new" + kind.lower()))
+ yield d
+ newp = d.getResult()
+ self.assertIdentical(p, newp)
+
+ # Getting a non-existent user should fail if createUserOnRequest is False
+ setattr(realm, flag, False)
+ d = wFD(get(u"another" + kind.lower()))
+ yield d
+ self.assertRaises(noSuchExc, d.getResult)
+ _entityCreationTest = dG(_entityCreationTest)
+
+
+ def testUserCreation(self):
+ return self._entityCreationTest("User")
+
+
+ def testGroupCreation(self):
+ return self._entityCreationTest("Group")
+
+
+ def testUserRetrieval(self):
+ realm = service.InMemoryWordsRealm("realmname")
+
+ # Make a user to play around with
+ d = wFD(realm.createUser(u"testuser"))
+ yield d
+ user = d.getResult()
+
+ # Make sure getting the user returns the same object
+ d = wFD(realm.getUser(u"testuser"))
+ yield d
+ retrieved = d.getResult()
+ self.assertIdentical(user, retrieved)
+
+ # Make sure looking up the user also returns the same object
+ d = wFD(realm.lookupUser(u"testuser"))
+ yield d
+ lookedUp = d.getResult()
+ self.assertIdentical(retrieved, lookedUp)
+
+ # Make sure looking up a user who does not exist fails
+ d = wFD(realm.lookupUser(u"nosuchuser"))
+ yield d
+ self.assertRaises(ewords.NoSuchUser, d.getResult)
+ testUserRetrieval = dG(testUserRetrieval)
+
+
+ def testUserAddition(self):
+ realm = service.InMemoryWordsRealm("realmname")
+
+ # Create and manually add a user to the realm
+ p = service.User("testuser")
+ d = wFD(realm.addUser(p))
+ yield d
+ user = d.getResult()
+ self.assertIdentical(p, user)
+
+ # Make sure getting that user returns the same object
+ d = wFD(realm.getUser(u"testuser"))
+ yield d
+ retrieved = d.getResult()
+ self.assertIdentical(user, retrieved)
+
+ # Make sure looking up that user returns the same object
+ d = wFD(realm.lookupUser(u"testuser"))
+ yield d
+ lookedUp = d.getResult()
+ self.assertIdentical(retrieved, lookedUp)
+ testUserAddition = dG(testUserAddition)
+
+
+ def testGroupRetrieval(self):
+ realm = service.InMemoryWordsRealm("realmname")
+
+ d = wFD(realm.createGroup(u"testgroup"))
+ yield d
+ group = d.getResult()
+
+ d = wFD(realm.getGroup(u"testgroup"))
+ yield d
+ retrieved = d.getResult()
+
+ self.assertIdentical(group, retrieved)
+
+ d = wFD(realm.getGroup(u"nosuchgroup"))
+ yield d
+ self.assertRaises(ewords.NoSuchGroup, d.getResult)
+ testGroupRetrieval = dG(testGroupRetrieval)
+
+
+ def testGroupAddition(self):
+ realm = service.InMemoryWordsRealm("realmname")
+
+ p = service.Group("testgroup")
+ d = wFD(realm.addGroup(p))
+ yield d
+ d.getResult()
+
+ d = wFD(realm.getGroup(u"testGroup"))
+ yield d
+ group = d.getResult()
+
+ self.assertIdentical(p, group)
+ testGroupAddition = dG(testGroupAddition)
+
+
+ def testGroupUsernameCollision(self):
+ """
+ Try creating a group with the same name as an existing user and
+ assert that it succeeds, since users and groups should not be in the
+ same namespace and collisions should be impossible.
+ """
+ realm = service.InMemoryWordsRealm("realmname")
+
+ d = wFD(realm.createUser(u"test"))
+ yield d
+ user = d.getResult()
+
+ d = wFD(realm.createGroup(u"test"))
+ yield d
+ group = d.getResult()
+ testGroupUsernameCollision = dG(testGroupUsernameCollision)
+
+
+ def testEnumeration(self):
+ realm = service.InMemoryWordsRealm("realmname")
+ d = wFD(realm.createGroup(u"groupone"))
+ yield d
+ d.getResult()
+
+ d = wFD(realm.createGroup(u"grouptwo"))
+ yield d
+ d.getResult()
+
+ groups = wFD(realm.itergroups())
+ yield groups
+ groups = groups.getResult()
+
+ n = [g.name for g in groups]
+ n.sort()
+ self.assertEquals(n, ["groupone", "grouptwo"])
+ testEnumeration = dG(testEnumeration)
+
+
+class TestGroup(object):
+ def __init__(self, name, size, topic):
+ self.name = name
+ self.size = lambda: size
+ self.meta = {'topic': topic}
+
+
+class TestUser(object):
+ def __init__(self, name, groups, signOn, lastMessage):
+ self.name = name
+ self.itergroups = lambda: iter([TestGroup(g, 3, 'Hello') for g in groups])
+ self.signOn = signOn
+ self.lastMessage = lastMessage
+
+
+class TestPortal(object):
+ def __init__(self):
+ self.logins = []
+
+
+ def login(self, credentials, mind, *interfaces):
+ d = Deferred()
+ self.logins.append((credentials, mind, interfaces, d))
+ return d
+
+
+class TestCaseUserAgg(object):
+ def __init__(self, user, realm, factory, address=address.IPv4Address('TCP', '127.0.0.1', 54321)):
+ self.user = user
+ self.transport = proto_helpers.StringTransportWithDisconnection()
+ self.protocol = factory.buildProtocol(address)
+ self.transport.protocol = self.protocol
+ self.user.mind = self.protocol
+ self.protocol.makeConnection(self.transport)
+
+
+ def write(self, stuff):
+ if isinstance(stuff, unicode):
+ stuff = stuff.encode('utf-8')
+ self.protocol.dataReceived(stuff)
+
+
+class IRCProtocolTestCase(unittest.TestCase):
+ STATIC_USERS = [
+ u'useruser', u'otheruser', u'someguy', u'firstuser', u'username',
+ u'userone', u'usertwo', u'userthree', u'someuser']
+
+
+ def setUp(self):
+ self.realm = service.InMemoryWordsRealm("realmname")
+ self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ self.portal = portal.Portal(self.realm, [self.checker])
+ self.factory = service.IRCFactory(self.realm, self.portal)
+
+ c = []
+ for nick in self.STATIC_USERS:
+ c.append(self.realm.createUser(nick))
+ self.checker.addUser(nick.encode('ascii'), nick + "_password")
+ return DeferredList(c)
+
+
+ def _assertGreeting(self, user):
+ """
+ The user has been greeted with the four messages that are (usually)
+ considered to start an IRC session.
+
+ Asserts that the required responses were received.
+ """
+ # Make sure we get 1-4 at least
+ response = self._response(user)
+ expected = [irc.RPL_WELCOME, irc.RPL_YOURHOST, irc.RPL_CREATED,
+ irc.RPL_MYINFO]
+ for (prefix, command, args) in response:
+ if command in expected:
+ expected.remove(command)
+ self.failIf(expected, "Missing responses for %r" % (expected,))
+
+
+ def _login(self, user, nick, password=None):
+ if password is None:
+ password = nick + "_password"
+ user.write('PASS %s\r\n' % (password,))
+ user.write('NICK %s extrainfo\r\n' % (nick,))
+
+
+ def _loggedInUser(self, name):
+ d = wFD(self.realm.lookupUser(name))
+ yield d
+ user = d.getResult()
+ agg = TestCaseUserAgg(user, self.realm, self.factory)
+ self._login(agg, name)
+ yield agg
+ _loggedInUser = dG(_loggedInUser)
+
+
+ def _response(self, user, messageType=None):
+ """
+ Extracts the user's response, and returns a list of parsed lines.
+ If messageType is defined, only messages of that type will be returned.
+ """
+ response = user.transport.value().splitlines()
+ user.transport.clear()
+ result = []
+ for message in map(irc.parsemsg, response):
+ if messageType is None or message[1] == messageType:
+ result.append(message)
+ return result
+
+
+ def testPASSLogin(self):
+ user = wFD(self._loggedInUser(u'firstuser'))
+ yield user
+ user = user.getResult()
+ self._assertGreeting(user)
+ testPASSLogin = dG(testPASSLogin)
+
+
+ def test_nickServLogin(self):
+ """
+ Sending NICK without PASS will prompt the user for their password.
+ When the user sends their password to NickServ, it will respond with a
+ Greeting.
+ """
+ firstuser = wFD(self.realm.lookupUser(u'firstuser'))
+ yield firstuser
+ firstuser = firstuser.getResult()
+
+ user = TestCaseUserAgg(firstuser, self.realm, self.factory)
+ user.write('NICK firstuser extrainfo\r\n')
+ response = self._response(user, 'PRIVMSG')
+ self.assertEquals(len(response), 1)
+ self.assertEquals(response[0][0], service.NICKSERV)
+ self.assertEquals(response[0][1], 'PRIVMSG')
+ self.assertEquals(response[0][2], ['firstuser', 'Password?'])
+ user.transport.clear()
+
+ user.write('PRIVMSG nickserv firstuser_password\r\n')
+ self._assertGreeting(user)
+ test_nickServLogin = dG(test_nickServLogin)
+
+
+ def testFailedLogin(self):
+ firstuser = wFD(self.realm.lookupUser(u'firstuser'))
+ yield firstuser
+ firstuser = firstuser.getResult()
+
+ user = TestCaseUserAgg(firstuser, self.realm, self.factory)
+ self._login(user, "firstuser", "wrongpass")
+ response = self._response(user, "PRIVMSG")
+ self.assertEquals(len(response), 1)
+ self.assertEquals(response[0][2], ['firstuser', 'Login failed. Goodbye.'])
+ testFailedLogin = dG(testFailedLogin)
+
+
+ def testLogout(self):
+ logout = []
+ firstuser = wFD(self.realm.lookupUser(u'firstuser'))
+ yield firstuser
+ firstuser = firstuser.getResult()
+
+ user = TestCaseUserAgg(firstuser, self.realm, self.factory)
+ self._login(user, "firstuser")
+ user.protocol.logout = lambda: logout.append(True)
+ user.write('QUIT\r\n')
+ self.assertEquals(logout, [True])
+ testLogout = dG(testLogout)
+
+
+ def testJoin(self):
+ firstuser = wFD(self.realm.lookupUser(u'firstuser'))
+ yield firstuser
+ firstuser = firstuser.getResult()
+
+ somechannel = wFD(self.realm.createGroup(u"somechannel"))
+ yield somechannel
+ somechannel = somechannel.getResult()
+
+ somechannel.meta['topic'] = 'some random topic'
+
+ # Bring in one user, make sure he gets into the channel sanely
+ user = TestCaseUserAgg(firstuser, self.realm, self.factory)
+ self._login(user, "firstuser")
+ user.transport.clear()
+ user.write('JOIN #somechannel\r\n')
+
+ response = self._response(user)
+ self.assertEquals(len(response), 5)
+
+ # Join message
+ self.assertEquals(response[0][0], 'firstuser!firstuser@realmname')
+ self.assertEquals(response[0][1], 'JOIN')
+ self.assertEquals(response[0][2], ['#somechannel'])
+
+ # User list
+ self.assertEquals(response[1][1], '353')
+ self.assertEquals(response[2][1], '366')
+
+ # Topic (or lack thereof, as the case may be)
+ self.assertEquals(response[3][1], '332')
+ self.assertEquals(response[4][1], '333')
+
+
+ # Hook up another client! It is a CHAT SYSTEM!!!!!!!
+ other = wFD(self._loggedInUser(u'otheruser'))
+ yield other
+ other = other.getResult()
+
+ other.transport.clear()
+ user.transport.clear()
+ other.write('JOIN #somechannel\r\n')
+
+ # At this point, both users should be in the channel
+ response = self._response(other)
+
+ event = self._response(user)
+ self.assertEquals(len(event), 1)
+ self.assertEquals(event[0][0], 'otheruser!otheruser@realmname')
+ self.assertEquals(event[0][1], 'JOIN')
+ self.assertEquals(event[0][2], ['#somechannel'])
+
+ self.assertEquals(response[1][0], 'realmname')
+ self.assertEquals(response[1][1], '353')
+ self.assertEquals(response[1][2], ['otheruser', '=', '#somechannel', 'firstuser otheruser'])
+ testJoin = dG(testJoin)
+
+
+ def test_joinTopicless(self):
+ """
+ When a user joins a group without a topic, no topic information is
+ sent to that user.
+ """
+ firstuser = wFD(self.realm.lookupUser(u'firstuser'))
+ yield firstuser
+ firstuser = firstuser.getResult()
+
+ somechannel = wFD(self.realm.createGroup(u"somechannel"))
+ yield somechannel
+ somechannel = somechannel.getResult()
+
+ # Bring in one user, make sure he gets into the channel sanely
+ user = TestCaseUserAgg(firstuser, self.realm, self.factory)
+ self._login(user, "firstuser")
+ user.transport.clear()
+ user.write('JOIN #somechannel\r\n')
+
+ response = self._response(user)
+ responseCodes = [r[1] for r in response]
+ self.assertNotIn('332', responseCodes)
+ self.assertNotIn('333', responseCodes)
+ test_joinTopicless = dG(test_joinTopicless)
+
+
+ def testLeave(self):
+ user = wFD(self._loggedInUser(u'useruser'))
+ yield user
+ user = user.getResult()
+
+ somechannel = wFD(self.realm.createGroup(u"somechannel"))
+ yield somechannel
+ somechannel = somechannel.getResult()
+
+ user.write('JOIN #somechannel\r\n')
+ user.transport.clear()
+
+ other = wFD(self._loggedInUser(u'otheruser'))
+ yield other
+ other = other.getResult()
+
+ other.write('JOIN #somechannel\r\n')
+
+ user.transport.clear()
+ other.transport.clear()
+
+ user.write('PART #somechannel\r\n')
+
+ response = self._response(user)
+ event = self._response(other)
+
+ self.assertEquals(len(response), 1)
+ self.assertEquals(response[0][0], 'useruser!useruser@realmname')
+ self.assertEquals(response[0][1], 'PART')
+ self.assertEquals(response[0][2], ['#somechannel', 'leaving'])
+ self.assertEquals(response, event)
+
+ # Now again, with a part message
+ user.write('JOIN #somechannel\r\n')
+
+ user.transport.clear()
+ other.transport.clear()
+
+ user.write('PART #somechannel :goodbye stupidheads\r\n')
+
+ response = self._response(user)
+ event = self._response(other)
+
+ self.assertEquals(len(response), 1)
+ self.assertEquals(response[0][0], 'useruser!useruser@realmname')
+ self.assertEquals(response[0][1], 'PART')
+ self.assertEquals(response[0][2], ['#somechannel', 'goodbye stupidheads'])
+ self.assertEquals(response, event)
+ testLeave = dG(testLeave)
+
+
+ def testGetTopic(self):
+ user = wFD(self._loggedInUser(u'useruser'))
+ yield user
+ user = user.getResult()
+
+ group = service.Group("somechannel")
+ group.meta["topic"] = "This is a test topic."
+ group.meta["topic_author"] = "some_fellow"
+ group.meta["topic_date"] = 77777777
+
+ add = wFD(self.realm.addGroup(group))
+ yield add
+ add.getResult()
+
+ user.transport.clear()
+ user.write("JOIN #somechannel\r\n")
+
+ response = self._response(user)
+
+ self.assertEquals(response[3][0], 'realmname')
+ self.assertEquals(response[3][1], '332')
+
+ # XXX Sigh. irc.parsemsg() is not as correct as one might hope.
+ self.assertEquals(response[3][2], ['useruser', '#somechannel', 'This is a test topic.'])
+ self.assertEquals(response[4][1], '333')
+ self.assertEquals(response[4][2], ['useruser', '#somechannel', 'some_fellow', '77777777'])
+
+ user.transport.clear()
+
+ user.write('TOPIC #somechannel\r\n')
+
+ response = self._response(user)
+
+ self.assertEquals(response[0][1], '332')
+ self.assertEquals(response[0][2], ['useruser', '#somechannel', 'This is a test topic.'])
+ self.assertEquals(response[1][1], '333')
+ self.assertEquals(response[1][2], ['useruser', '#somechannel', 'some_fellow', '77777777'])
+ testGetTopic = dG(testGetTopic)
+
+
+ def testSetTopic(self):
+ user = wFD(self._loggedInUser(u'useruser'))
+ yield user
+ user = user.getResult()
+
+ add = wFD(self.realm.createGroup(u"somechannel"))
+ yield add
+ somechannel = add.getResult()
+
+ user.write("JOIN #somechannel\r\n")
+
+ other = wFD(self._loggedInUser(u'otheruser'))
+ yield other
+ other = other.getResult()
+
+ other.write("JOIN #somechannel\r\n")
+
+ user.transport.clear()
+ other.transport.clear()
+
+ other.write('TOPIC #somechannel :This is the new topic.\r\n')
+
+ response = self._response(other)
+ event = self._response(user)
+
+ self.assertEquals(response, event)
+
+ self.assertEquals(response[0][0], 'otheruser!otheruser@realmname')
+ self.assertEquals(response[0][1], 'TOPIC')
+ self.assertEquals(response[0][2], ['#somechannel', 'This is the new topic.'])
+
+ other.transport.clear()
+
+ somechannel.meta['topic_date'] = 12345
+ other.write('TOPIC #somechannel\r\n')
+
+ response = self._response(other)
+ self.assertEquals(response[0][1], '332')
+ self.assertEquals(response[0][2], ['otheruser', '#somechannel', 'This is the new topic.'])
+ self.assertEquals(response[1][1], '333')
+ self.assertEquals(response[1][2], ['otheruser', '#somechannel', 'otheruser', '12345'])
+
+ other.transport.clear()
+ other.write('TOPIC #asdlkjasd\r\n')
+
+ response = self._response(other)
+ self.assertEquals(response[0][1], '403')
+ testSetTopic = dG(testSetTopic)
+
+
+ def testGroupMessage(self):
+ user = wFD(self._loggedInUser(u'useruser'))
+ yield user
+ user = user.getResult()
+
+ add = wFD(self.realm.createGroup(u"somechannel"))
+ yield add
+ somechannel = add.getResult()
+
+ user.write("JOIN #somechannel\r\n")
+
+ other = wFD(self._loggedInUser(u'otheruser'))
+ yield other
+ other = other.getResult()
+
+ other.write("JOIN #somechannel\r\n")
+
+ user.transport.clear()
+ other.transport.clear()
+
+ user.write('PRIVMSG #somechannel :Hello, world.\r\n')
+
+ response = self._response(user)
+ event = self._response(other)
+
+ self.failIf(response)
+ self.assertEquals(len(event), 1)
+ self.assertEquals(event[0][0], 'useruser!useruser@realmname')
+ self.assertEquals(event[0][1], 'PRIVMSG', -1)
+ self.assertEquals(event[0][2], ['#somechannel', 'Hello, world.'])
+ testGroupMessage = dG(testGroupMessage)
+
+
+ def testPrivateMessage(self):
+ user = wFD(self._loggedInUser(u'useruser'))
+ yield user
+ user = user.getResult()
+
+ other = wFD(self._loggedInUser(u'otheruser'))
+ yield other
+ other = other.getResult()
+
+ user.transport.clear()
+ other.transport.clear()
+
+ user.write('PRIVMSG otheruser :Hello, monkey.\r\n')
+
+ response = self._response(user)
+ event = self._response(other)
+
+ self.failIf(response)
+ self.assertEquals(len(event), 1)
+ self.assertEquals(event[0][0], 'useruser!useruser@realmname')
+ self.assertEquals(event[0][1], 'PRIVMSG')
+ self.assertEquals(event[0][2], ['otheruser', 'Hello, monkey.'])
+
+ user.write('PRIVMSG nousernamedthis :Hello, monkey.\r\n')
+
+ response = self._response(user)
+
+ self.assertEquals(len(response), 1)
+ self.assertEquals(response[0][0], 'realmname')
+ self.assertEquals(response[0][1], '401')
+ self.assertEquals(response[0][2], ['useruser', 'nousernamedthis', 'No such nick/channel.'])
+ testPrivateMessage = dG(testPrivateMessage)
+
+
+ def testOper(self):
+ user = wFD(self._loggedInUser(u'useruser'))
+ yield user
+ user = user.getResult()
+
+ user.transport.clear()
+ user.write('OPER user pass\r\n')
+ response = self._response(user)
+
+ self.assertEquals(len(response), 1)
+ self.assertEquals(response[0][1], '491')
+ testOper = dG(testOper)
+
+
+ def testGetUserMode(self):
+ user = wFD(self._loggedInUser(u'useruser'))
+ yield user
+ user = user.getResult()
+
+ user.transport.clear()
+ user.write('MODE useruser\r\n')
+
+ response = self._response(user)
+ self.assertEquals(len(response), 1)
+ self.assertEquals(response[0][0], 'realmname')
+ self.assertEquals(response[0][1], '221')
+ self.assertEquals(response[0][2], ['useruser', '+'])
+ testGetUserMode = dG(testGetUserMode)
+
+
+ def testSetUserMode(self):
+ user = wFD(self._loggedInUser(u'useruser'))
+ yield user
+ user = user.getResult()
+
+ user.transport.clear()
+ user.write('MODE useruser +abcd\r\n')
+
+ response = self._response(user)
+ self.assertEquals(len(response), 1)
+ self.assertEquals(response[0][1], '472')
+ testSetUserMode = dG(testSetUserMode)
+
+
+ def testGetGroupMode(self):
+ user = wFD(self._loggedInUser(u'useruser'))
+ yield user
+ user = user.getResult()
+
+ add = wFD(self.realm.createGroup(u"somechannel"))
+ yield add
+ somechannel = add.getResult()
+
+ user.write('JOIN #somechannel\r\n')
+
+ user.transport.clear()
+ user.write('MODE #somechannel\r\n')
+
+ response = self._response(user)
+ self.assertEquals(len(response), 1)
+ self.assertEquals(response[0][1], '324')
+ testGetGroupMode = dG(testGetGroupMode)
+
+
+ def testSetGroupMode(self):
+ user = wFD(self._loggedInUser(u'useruser'))
+ yield user
+ user = user.getResult()
+
+ group = wFD(self.realm.createGroup(u"groupname"))
+ yield group
+ group = group.getResult()
+
+ user.write('JOIN #groupname\r\n')
+
+ user.transport.clear()
+ user.write('MODE #groupname +abcd\r\n')
+
+ response = self._response(user)
+ self.assertEquals(len(response), 1)
+ self.assertEquals(response[0][1], '472')
+ testSetGroupMode = dG(testSetGroupMode)
+
+
+ def testWho(self):
+ group = service.Group('groupname')
+ add = wFD(self.realm.addGroup(group))
+ yield add
+ add.getResult()
+
+ users = []
+ for nick in u'userone', u'usertwo', u'userthree':
+ u = wFD(self._loggedInUser(nick))
+ yield u
+ u = u.getResult()
+ users.append(u)
+ users[-1].write('JOIN #groupname\r\n')
+ for user in users:
+ user.transport.clear()
+
+ users[0].write('WHO #groupname\r\n')
+
+ r = self._response(users[0])
+ self.failIf(self._response(users[1]))
+ self.failIf(self._response(users[2]))
+
+ wantusers = ['userone', 'usertwo', 'userthree']
+ for (prefix, code, stuff) in r[:-1]:
+ self.assertEquals(prefix, 'realmname')
+ self.assertEquals(code, '352')
+
+ (myname, group, theirname, theirhost, theirserver, theirnick, flag, extra) = stuff
+ self.assertEquals(myname, 'userone')
+ self.assertEquals(group, '#groupname')
+ self.failUnless(theirname in wantusers)
+ self.assertEquals(theirhost, 'realmname')
+ self.assertEquals(theirserver, 'realmname')
+ wantusers.remove(theirnick)
+ self.assertEquals(flag, 'H')
+ self.assertEquals(extra, '0 ' + theirnick)
+ self.failIf(wantusers)
+
+ prefix, code, stuff = r[-1]
+ self.assertEquals(prefix, 'realmname')
+ self.assertEquals(code, '315')
+ myname, channel, extra = stuff
+ self.assertEquals(myname, 'userone')
+ self.assertEquals(channel, '#groupname')
+ self.assertEquals(extra, 'End of /WHO list.')
+ testWho = dG(testWho)
+
+
+ def testList(self):
+ user = wFD(self._loggedInUser(u"someuser"))
+ yield user
+ user = user.getResult()
+ user.transport.clear()
+
+ somegroup = wFD(self.realm.createGroup(u"somegroup"))
+ yield somegroup
+ somegroup = somegroup.getResult()
+ somegroup.size = lambda: succeed(17)
+ somegroup.meta['topic'] = 'this is the topic woo'
+
+ # Test one group
+ user.write('LIST #somegroup\r\n')
+
+ r = self._response(user)
+ self.assertEquals(len(r), 2)
+ resp, end = r
+
+ self.assertEquals(resp[0], 'realmname')
+ self.assertEquals(resp[1], '322')
+ self.assertEquals(resp[2][0], 'someuser')
+ self.assertEquals(resp[2][1], 'somegroup')
+ self.assertEquals(resp[2][2], '17')
+ self.assertEquals(resp[2][3], 'this is the topic woo')
+
+ self.assertEquals(end[0], 'realmname')
+ self.assertEquals(end[1], '323')
+ self.assertEquals(end[2][0], 'someuser')
+ self.assertEquals(end[2][1], 'End of /LIST')
+
+ user.transport.clear()
+ # Test all groups
+
+ user.write('LIST\r\n')
+ r = self._response(user)
+ self.assertEquals(len(r), 2)
+
+ fg1, end = r
+
+ self.assertEquals(fg1[1], '322')
+ self.assertEquals(fg1[2][1], 'somegroup')
+ self.assertEquals(fg1[2][2], '17')
+ self.assertEquals(fg1[2][3], 'this is the topic woo')
+
+ self.assertEquals(end[1], '323')
+ testList = dG(testList)
+
+
+ def testWhois(self):
+ user = wFD(self._loggedInUser(u'someguy'))
+ yield user
+ user = user.getResult()
+
+ otherguy = service.User("otherguy")
+ otherguy.itergroups = lambda: iter([
+ service.Group('groupA'),
+ service.Group('groupB')])
+ otherguy.signOn = 10
+ otherguy.lastMessage = time.time() - 15
+
+ add = wFD(self.realm.addUser(otherguy))
+ yield add
+ add.getResult()
+
+ user.transport.clear()
+ user.write('WHOIS otherguy\r\n')
+ r = self._response(user)
+
+ self.assertEquals(len(r), 5)
+ wuser, wserver, idle, channels, end = r
+
+ self.assertEquals(wuser[0], 'realmname')
+ self.assertEquals(wuser[1], '311')
+ self.assertEquals(wuser[2][0], 'someguy')
+ self.assertEquals(wuser[2][1], 'otherguy')
+ self.assertEquals(wuser[2][2], 'otherguy')
+ self.assertEquals(wuser[2][3], 'realmname')
+ self.assertEquals(wuser[2][4], '*')
+ self.assertEquals(wuser[2][5], 'otherguy')
+
+ self.assertEquals(wserver[0], 'realmname')
+ self.assertEquals(wserver[1], '312')
+ self.assertEquals(wserver[2][0], 'someguy')
+ self.assertEquals(wserver[2][1], 'otherguy')
+ self.assertEquals(wserver[2][2], 'realmname')
+ self.assertEquals(wserver[2][3], 'Hi mom!')
+
+ self.assertEquals(idle[0], 'realmname')
+ self.assertEquals(idle[1], '317')
+ self.assertEquals(idle[2][0], 'someguy')
+ self.assertEquals(idle[2][1], 'otherguy')
+ self.assertEquals(idle[2][2], '15')
+ self.assertEquals(idle[2][3], '10')
+ self.assertEquals(idle[2][4], "seconds idle, signon time")
+
+ self.assertEquals(channels[0], 'realmname')
+ self.assertEquals(channels[1], '319')
+ self.assertEquals(channels[2][0], 'someguy')
+ self.assertEquals(channels[2][1], 'otherguy')
+ self.assertEquals(channels[2][2], '#groupA #groupB')
+
+ self.assertEquals(end[0], 'realmname')
+ self.assertEquals(end[1], '318')
+ self.assertEquals(end[2][0], 'someguy')
+ self.assertEquals(end[2][1], 'otherguy')
+ self.assertEquals(end[2][2], 'End of WHOIS list.')
+ testWhois = dG(testWhois)
+
+
+class TestMind(service.PBMind):
+ def __init__(self, *a, **kw):
+ self.joins = []
+ self.parts = []
+ self.messages = []
+ self.meta = []
+
+ def remote_userJoined(self, user, group):
+ self.joins.append((user, group))
+
+
+ def remote_userLeft(self, user, group, reason):
+ self.parts.append((user, group, reason))
+
+
+ def remote_receive(self, sender, recipient, message):
+ self.messages.append((sender, recipient, message))
+
+
+ def remote_groupMetaUpdate(self, group, meta):
+ self.meta.append((group, meta))
+pb.setUnjellyableForClass(TestMind, service.PBMindReference)
+
+
+class PBProtocolTestCase(unittest.TestCase):
+ def setUp(self):
+ self.realm = service.InMemoryWordsRealm("realmname")
+ self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ self.portal = portal.Portal(
+ self.realm, [self.checker])
+ self.serverFactory = pb.PBServerFactory(self.portal)
+ self.serverFactory.protocol = self._protocolFactory
+ self.serverFactory.unsafeTracebacks = True
+ self.clientFactory = pb.PBClientFactory()
+ self.clientFactory.unsafeTracebacks = True
+ self.serverPort = reactor.listenTCP(0, self.serverFactory)
+ self.clientConn = reactor.connectTCP(
+ '127.0.0.1',
+ self.serverPort.getHost().port,
+ self.clientFactory)
+
+
+ def _protocolFactory(self, *args, **kw):
+ self._serverProtocol = pb.Broker(0)
+ return self._serverProtocol
+
+
+ def tearDown(self):
+ d3 = Deferred()
+ self._serverProtocol.notifyOnDisconnect(lambda: d3.callback(None))
+ return DeferredList([
+ maybeDeferred(self.serverPort.stopListening),
+ maybeDeferred(self.clientConn.disconnect), d3])
+
+
+ def _loggedInAvatar(self, name, password, mind):
+ creds = credentials.UsernamePassword(name, password)
+ self.checker.addUser(name.encode('ascii'), password)
+ d = self.realm.createUser(name)
+ d.addCallback(lambda ign: self.clientFactory.login(creds, mind))
+ return d
+
+
+ def testGroups(self):
+ mindone = TestMind()
+ one = wFD(self._loggedInAvatar(u"one", "p1", mindone))
+ yield one
+ one = one.getResult()
+
+ mindtwo = TestMind()
+ two = wFD(self._loggedInAvatar(u"two", "p2", mindtwo))
+ yield two
+ two = two.getResult()
+
+ add = wFD(self.realm.createGroup(u"foobar"))
+ yield add
+ add.getResult()
+
+ groupone = wFD(one.join(u"foobar"))
+ yield groupone
+ groupone = groupone.getResult()
+
+ grouptwo = wFD(two.join(u"foobar"))
+ yield grouptwo
+ grouptwo = grouptwo.getResult()
+
+ msg = wFD(groupone.send({"text": "hello, monkeys"}))
+ yield msg
+ msg = msg.getResult()
+
+ leave = wFD(groupone.leave())
+ yield leave
+ leave = leave.getResult()
+ testGroups = dG(testGroups)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_tap.py b/vendor/Twisted-10.0.0/twisted/words/test/test_tap.py
new file mode 100644
index 0000000000..142d681df4
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_tap.py
@@ -0,0 +1,78 @@
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.cred import credentials, error
+from twisted.words import tap
+from twisted.trial import unittest
+
+
+
+class WordsTap(unittest.TestCase):
+ """
+ Ensures that the twisted.words.tap API works.
+ """
+
+ PASSWD_TEXT = "admin:admin\njoe:foo\n"
+ admin = credentials.UsernamePassword('admin', 'admin')
+ joeWrong = credentials.UsernamePassword('joe', 'bar')
+
+
+ def setUp(self):
+ """
+ Create a file with two users.
+ """
+ self.filename = self.mktemp()
+ self.file = open(self.filename, 'w')
+ self.file.write(self.PASSWD_TEXT)
+ self.file.flush()
+
+
+ def tearDown(self):
+ """
+ Close the dummy user database.
+ """
+ self.file.close()
+
+
+ def test_hostname(self):
+ """
+ Tests that the --hostname parameter gets passed to Options.
+ """
+ opt = tap.Options()
+ opt.parseOptions(['--hostname', 'myhost'])
+ self.assertEquals(opt['hostname'], 'myhost')
+
+
+ def test_passwd(self):
+ """
+ Tests the --passwd command for backwards-compatibility.
+ """
+ opt = tap.Options()
+ opt.parseOptions(['--passwd', self.file.name])
+ self._loginTest(opt)
+
+
+ def test_auth(self):
+ """
+ Tests that the --auth command generates a checker.
+ """
+ opt = tap.Options()
+ opt.parseOptions(['--auth', 'file:'+self.file.name])
+ self._loginTest(opt)
+
+
+ def _loginTest(self, opt):
+ """
+ This method executes both positive and negative authentication
+ tests against whatever credentials checker has been stored in
+ the Options class.
+
+ @param opt: An instance of L{tap.Options}.
+ """
+ self.assertEquals(len(opt['credCheckers']), 1)
+ checker = opt['credCheckers'][0]
+ self.assertFailure(checker.requestAvatarId(self.joeWrong),
+ error.UnauthorizedLogin)
+ def _gotAvatar(username):
+ self.assertEquals(username, self.admin.username)
+ return checker.requestAvatarId(self.admin).addCallback(_gotAvatar)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_toc.py b/vendor/Twisted-10.0.0/twisted/words/test/test_toc.py
new file mode 100644
index 0000000000..907db4bedc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_toc.py
@@ -0,0 +1,340 @@
+# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.trial import unittest
+
+from twisted.words.protocols import toc
+from twisted.internet import protocol, main
+from twisted.python import failure
+from twisted.test.proto_helpers import StringIOWithoutClosing
+
+from struct import pack,unpack
+
+
+class DummyTOC(toc.TOC):
+ """
+ used to override authentication, now overrides printing.
+ """
+ def _debug(self,data):
+ pass
+SEQID=1001
+def flap(type,data):
+ global SEQID
+ send="*"
+ send=send+pack("!BHH",type,SEQID,len(data))
+ send=send+data
+ SEQID=SEQID+1
+ return send
+def readFlap(data):
+ if data=="": return [None,""]
+ null,type,seqid,length=unpack("!BBHH",data[:6])
+ val=data[6:6+length]
+ return [[type,val],data[6+length:]]
+
+class TOCGeneralTestCase(unittest.TestCase):
+ """
+ general testing of TOC functions.
+ """
+ def testTOC(self):
+ self.runTest()
+ def runTest(self):
+ USERS=2
+ data=range(USERS)
+ data[0]=("FLAPON\r\n\r\n",\
+ flap(1,"\000\000\000\001\000\001\000\004test"),\
+ flap(2,"toc_signon localhost 9999 test 0x100000 english \"penguin 0.1\"\000"),\
+ flap(2,"toc_add_buddy test\000"),\
+ flap(2,"toc_init_done\000"),\
+ flap(2,"toc_send_im test \"hi\"\000"),\
+ flap(2,"toc_send_im test2 \"hello\"\000"),\
+ flap(2,"toc_set_away \"not here\"\000"),\
+ flap(2,"toc_set_idle 602\000"),\
+ flap(2,"toc_set_idle 0\000"),\
+ flap(2,"toc_set_away\000"),\
+ flap(2,"toc_evil test norm\000"),\
+ flap(2,"toc_chat_join 4 \"Test Chat\"\000"),\
+ flap(2,"toc_chat_send 0 \"hello\"\000"),\
+ #flap(2,"toc_chat_leave 0\000")) #,\
+ flap(2,"toc_chat_invite 0 \"come\" ooga\000"),\
+ #flap(2,"toc_chat_accept 0\000"),\
+ flap(5,"\000"),\
+ flap(2,"toc_chat_whisper 0 ooga \"boo ga\"\000"),\
+ flap(2,"toc_chat_leave 0"),\
+ flap(5,"\000"))
+ data[1]=("FLAPON\r\n\r\n",\
+ flap(1,"\000\000\000\001\000\001\000\004ooga"),\
+ flap(2,"toc_signon localhost 9999 ooga 0x100000 english \"penguin 0.1\"\000"),\
+ flap(2,"toc_add_buddy test\000"),\
+ flap(2,"toc_init_done\000"),\
+ flap(5,"\000"),\
+ flap(5,"\000"),\
+ #flap(5,"\000"),\
+ #flap(5,"\000"),\
+ #flap(5,"\000"),\
+ flap(5,"\000"),\
+ flap(5,"\000"),\
+ flap(5,"\000"),\
+ flap(5,"\000"),\
+ flap(5,"\000"),\
+ flap(5,"\000"),\
+ flap(5,"\000"),\
+ #flap(5,"\000"),\
+ flap(2,"toc_chat_accept 0\000"),\
+ flap(2,"toc_chat_send 0 \"hi test\"\000"),\
+ flap(5,"\000"),\
+ flap(2,"toc_chat_leave 0\000"))
+ strings=range(USERS)
+ for i in strings:
+ strings[i]=StringIOWithoutClosing()
+ fac=toc.TOCFactory()
+ dummy=range(USERS)
+ for i in dummy:
+ dummy[i]=DummyTOC()
+ dummy[i].factory=fac
+ dummy[i].makeConnection(protocol.FileWrapper(strings[i]))
+ while sum(map(lambda x: x == (), data)) != USERS:
+ for i in range(USERS):
+ d=data[i]
+ if len(d)>0:
+ k,data[i]=d[0],d[1:]
+ for j in k:
+ dummy[i].dataReceived(j) # test by doing a character at a time
+ else:
+ dummy[i].connectionLost(failure.Failure(main.CONNECTION_DONE))
+ values=range(USERS)
+ for i in values:
+ values[i]=strings[i].getvalue()
+ flaps=map(lambda x:[],range(USERS))
+ for value in values:
+ i=values.index(value)
+ f,value=readFlap(value)
+ while f:
+ flaps[i].append(f)
+ f,value=readFlap(value)
+ ts=range(USERS)
+ for t in ts:
+ ts[t]=dummy[t].signontime
+ shouldequal=range(USERS)
+ shouldequal[0]=[ \
+ [1,"\000\000\000\001"],\
+ [2,"SIGN_ON:TOC1.0\000"],\
+ [2,"NICK:test\000"],\
+ [2,"CONFIG:\00"],\
+ [2,"UPDATE_BUDDY:test:T:0:%s:0: O\000"%ts[0]],\
+ [2,"IM_IN:test:F:hi\000"],\
+ [2,"ERROR:901:test2\000"],\
+ #[2,"UPDATE_BUDDY:test:T:0:%s:0: O\000"%ts[0]],\
+ [2,"UPDATE_BUDDY:test:T:0:%s:0: OU\000"%ts[0]],\
+ [2,"UPDATE_BUDDY:test:T:0:%s:10: OU\000"%ts[0]],\
+ [2,"UPDATE_BUDDY:test:T:0:%s:0: OU\000"%ts[0]],\
+ [2,"UPDATE_BUDDY:test:T:0:%s:0: O\000"%ts[0]],\
+ [2,"EVILED:10:test\000"],\
+ [2,"UPDATE_BUDDY:test:T:10:%s:0: O\000"%ts[0]],\
+ [2,"CHAT_JOIN:0:Test Chat\000"],\
+ [2,"CHAT_UPDATE_BUDDY:0:T:test\000"],\
+ [2,"CHAT_IN:0:test:F:hello\000"],\
+ [2,"CHAT_UPDATE_BUDDY:0:T:ooga\000"],\
+ [2,"CHAT_IN:0:ooga:F:hi test\000"],\
+ [2,"CHAT_LEFT:0\000"]]
+ shouldequal[1]=[ \
+ [1,"\000\000\000\001"],\
+ [2,"SIGN_ON:TOC1.0\000"],\
+ [2,"NICK:ooga\000"],\
+ [2,"CONFIG:\000"],\
+ #[2,"UPDATE_BUDDY:test:T:0:%s:0: O\000"%ts[0]],\
+ [2,"UPDATE_BUDDY:test:T:0:%s:0: OU\000"%ts[0]],\
+ [2,"UPDATE_BUDDY:test:T:0:%s:10: OU\000"%ts[0]],\
+ [2,"UPDATE_BUDDY:test:T:0:%s:0: OU\000"%ts[0]],\
+ [2,"UPDATE_BUDDY:test:T:0:%s:0: O\000"%ts[0]],\
+ [2,"UPDATE_BUDDY:test:T:10:%s:0: O\000"%ts[0]],\
+ [2,"CHAT_INVITE:Test Chat:0:test:come\000"],\
+ [2,"CHAT_JOIN:0:Test Chat\000"],\
+ [2,"CHAT_UPDATE_BUDDY:0:T:test:ooga\000"],\
+ [2,"CHAT_IN:0:ooga:F:hi test\000"],\
+ [2,"CHAT_IN:0:test:T:boo ga\000"],\
+ [2,"CHAT_UPDATE_BUDDY:0:F:test\000"],\
+ [2,"CHAT_LEFT:0\000"]]
+ if flaps!=shouldequal:
+ for i in range(len(shouldequal)):
+ for j in range(len(shouldequal[i])):
+ if shouldequal[i][j]!=flaps[i][j]:
+ raise AssertionError("GeneralTest Failed!\nUser %s Line %s\nactual:%s\nshould be:%s"%(i,j,flaps[i][j],shouldequal[i][j]))
+ raise AssertionError("GeneralTest Failed with incorrect lengths!")
+class TOCMultiPacketTestCase(unittest.TestCase):
+ """
+ i saw this problem when using GAIM. It only read the flaps onces per dataReceived, and would basically block if it ever received two packets together in one dataReceived. this tests for that occurance.
+ """
+ def testTOC(self):
+ self.runTest()
+ def runTest(self):
+ packets=["FLAPON\r\n\r\n",\
+ flap(1,"\000\000\000\001\000\001\000\004test"),\
+ flap(2,"toc_signon null 9999 test 0x100000 english \"penguin 0.1\"\000"),\
+ flap(2,"toc_init_done\000"),\
+ flap(2,"toc_send_im test hi\000")]
+ shouldbe=[[1,"\000\000\000\001"],\
+ [2,"SIGN_ON:TOC1.0\000"],\
+ [2,"NICK:test\000"],\
+ [2,"CONFIG:\000"],\
+ [2,"IM_IN:test:F:hi\000"]]
+ data=""
+ for i in packets:
+ data=data+i
+ s=StringIOWithoutClosing()
+ d=DummyTOC()
+ fac=toc.TOCFactory()
+ d.factory=fac
+ d.makeConnection(protocol.FileWrapper(s))
+ d.dataReceived(data)
+ d.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ value=s.getvalue()
+ flaps=[]
+ f,value=readFlap(value)
+ while f:
+ flaps.append(f)
+ f,value=readFlap(value)
+ if flaps!=shouldbe:
+ for i in range(len(flaps)):
+ if flaps[i]!=shouldbe[i]:raise AssertionError("MultiPacketTest Failed!\nactual:%s\nshould be:%s"%(flaps[i],shouldbe[i]))
+ raise AssertionError("MultiPacketTest Failed with incorrect length!, printing both lists\nactual:%s\nshould be:%s"%(flaps,shouldbe))
+class TOCSavedValuesTestCase(unittest.TestCase):
+ def testTOC(self):
+ self.runTest()
+ def runTest(self):
+ password1=toc.roast("test pass")
+ password2=toc.roast("pass test")
+ beforesend=[\
+ "FLAPON\r\n\r\n",\
+ flap(1,"\000\000\000\001\000\001\000\004test"),\
+ flap(2,"toc_signon localhost 9999 test %s english \"penguin 0.1\"\000"%password1),\
+ flap(2,"toc_init_done\000"),\
+ flap(2,"toc_set_config \"{m 4}\"\000"),\
+ flap(2,"toc_format_nickname BOOGA\000"),\
+ flap(2,"toc_format_nickname \"T E S T\"\000"),\
+ flap(2,"toc_change_passwd \"testpass\" \"pass test\"\000"),\
+ flap(2,"toc_change_passwd \"test pass\" \"pass test\"\000")]
+ beforeexpect=[\
+ [1,"\000\000\000\001"],\
+ [2,"SIGN_ON:TOC1.0\000"],\
+ [2,"NICK:test\000"],\
+ [2,"CONFIG:\000"],\
+ [2,"ERROR:911\000"],\
+ [2,"ADMIN_NICK_STATUS:0\000"],\
+ [2,"ERROR:911\000"],\
+ [2,"ADMIN_PASSWD_STATUS:0\000"]]
+ badpasssend=[\
+ "FLAPON\r\n\r\n",\
+ flap(1,"\000\000\000\001\000\001\000\004test"),\
+ flap(2,"toc_signon localhost 9999 test 0x1000 english \"penguin 0.1\"\000"),\
+ flap(2,"toc_init_done")]
+ badpassexpect=[\
+ [1,"\000\00\000\001"],\
+ [2,"ERROR:980\000"]]
+ goodpasssend=[\
+ "FLAPON\r\n\r\n",\
+ flap(1,"\000\000\000\001\000\001\000\004test"),\
+ flap(2,"toc_signon localhost 9999 test %s english \"penguin 0.1\"\000"%password2),\
+ flap(2,"toc_init_done")]
+ goodpassexpect=[\
+ [1,"\000\000\000\001"],\
+ [2,"SIGN_ON:TOC1.0\000"],\
+ [2,"NICK:T E S T\000"],\
+ [2,"CONFIG:{m 4}\000"]]
+ fac=toc.TOCFactory()
+ d=DummyTOC()
+ d.factory=fac
+ s=StringIOWithoutClosing()
+ d.makeConnection(protocol.FileWrapper(s))
+ for i in beforesend:
+ d.dataReceived(i)
+ d.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ v=s.getvalue()
+ flaps=[]
+ f,v=readFlap(v)
+ while f:
+ flaps.append(f)
+ f,v=readFlap(v)
+ if flaps!=beforeexpect:
+ for i in range(len(flaps)):
+ if flaps[i]!=beforeexpect[i]:
+ raise AssertionError("SavedValuesTest Before Failed!\nactual:%s\nshould be:%s"%(flaps[i],beforeexpect[i]))
+ raise AssertionError("SavedValuesTest Before Failed with incorrect length!\nactual:%s\nshould be:%s"%(flaps,beforeexpect))
+ d=DummyTOC()
+ d.factory=fac
+ s=StringIOWithoutClosing()
+ d.makeConnection(protocol.FileWrapper(s))
+ for i in badpasssend:
+ d.dataReceived(i)
+ d.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ v=s.getvalue()
+ flaps=[]
+ f,v=readFlap(v)
+ while f:
+ flaps.append(f)
+ f,v=readFlap(v)
+ if flaps!=badpassexpect:
+ for i in range(len(flaps)):
+ if flaps[i]!=badpassexpect[i]:
+ raise AssertionError("SavedValuesTest BadPass Failed!\nactual:%s\nshould be:%s"%(flaps[i],badpassexpect[i]))
+ raise AssertionError("SavedValuesTest BadPass Failed with incorrect length!\nactual:%s\nshould be:%s"%(flaps,badpassexpect))
+ d=DummyTOC()
+ d.factory=fac
+ s=StringIOWithoutClosing()
+ d.makeConnection(protocol.FileWrapper(s))
+ for i in goodpasssend:
+ d.dataReceived(i)
+ d.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ v=s.getvalue()
+ flaps=[]
+ f,v=readFlap(v)
+ while f:
+ flaps.append(f)
+ f,v=readFlap(v)
+ if flaps!=goodpassexpect:
+ for i in range(len(flaps)):
+ if flaps[i]!=goodpassexpect[i]:
+ raise AssertionError("SavedValuesTest GoodPass Failed!\nactual:%s\nshould be:%s"%(flaps[i],goodpassexpect[i]))
+ raise AssertionError("SavedValuesTest GoodPass Failed with incorrect length!\nactual:%s\nshould be:%s"%(flaps,beforeexpect))
+class TOCPrivacyTestCase(unittest.TestCase):
+ def runTest(self):
+ sends=["FLAPON\r\n\r\n",\
+ flap(1,"\000\000\000\001\000\001\000\004test"),\
+ flap(2,"toc_signon localhost 9999 test 0x00 english penguin\000"),\
+ flap(2,"toc_init_done\000"),\
+ flap(2,"toc_add_deny\000"),\
+ flap(2,"toc_send_im test 1\000"),\
+ flap(2,"toc_add_deny test\000"),\
+ flap(2,"toc_send_im test 2\000"),\
+ flap(2,"toc_add_permit\000"),\
+ flap(2,"toc_send_im test 3\000"),\
+ flap(2,"toc_add_permit test\000"),\
+ flap(2,"toc_send_im test 4\000")]
+ expect=[[1,"\000\000\000\001"],\
+ [2,"SIGN_ON:TOC1.0\000"],\
+ [2,"NICK:test\000"],\
+ [2,"CONFIG:\000"],\
+ [2,"IM_IN:test:F:1\000"],\
+ [2,"ERROR:901:test\000"],\
+ [2,"ERROR:901:test\000"],\
+ [2,"IM_IN:test:F:4\000"]]
+ d=DummyTOC()
+ d.factory=toc.TOCFactory()
+ s=StringIOWithoutClosing()
+ d.makeConnection(protocol.FileWrapper(s))
+ for i in sends:
+ d.dataReceived(i)
+ d.connectionLost(failure.Failure(main.CONNECTION_DONE))
+ v=s.getvalue()
+ flaps=[]
+ f,v=readFlap(v)
+ while f:
+ flaps.append(f)
+ f,v=readFlap(v)
+ if flaps!=expect:
+ for i in range(len(flaps)):
+ if flaps[i]!=expect[i]:
+ raise AssertionError("PrivacyTest Before Failed!\nactual:%s\nshould be:%s"%(flaps[i],expect[i]))
+ raise AssertionError("PrivacyTest Before Failed with incorrect length!\nactual:%s\nshould be:%s"%(flaps,expect))
+testCases=[TOCGeneralTestCase,TOCMultiPacketTestCase,TOCSavedValuesTestCase,TOCPrivacyTestCase]
+
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_xishutil.py b/vendor/Twisted-10.0.0/twisted/words/test/test_xishutil.py
new file mode 100644
index 0000000000..eb0dd4fb47
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_xishutil.py
@@ -0,0 +1,345 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for twisted.words.xish.utility
+"""
+
+from twisted.trial import unittest
+
+from twisted.python.util import OrderedDict
+from twisted.words.xish import utility
+from twisted.words.xish.domish import Element
+from twisted.words.xish.utility import EventDispatcher
+
+class CallbackTracker:
+ """
+ Test helper for tracking callbacks.
+
+ Increases a counter on each call to L{call} and stores the object
+ passed in the call.
+ """
+
+ def __init__(self):
+ self.called = 0
+ self.obj = None
+
+
+ def call(self, obj):
+ self.called = self.called + 1
+ self.obj = obj
+
+
+
+class OrderedCallbackTracker:
+ """
+ Test helper for tracking callbacks and their order.
+ """
+
+ def __init__(self):
+ self.callList = []
+
+
+ def call1(self, object):
+ self.callList.append(self.call1)
+
+
+ def call2(self, object):
+ self.callList.append(self.call2)
+
+
+ def call3(self, object):
+ self.callList.append(self.call3)
+
+
+
+class EventDispatcherTest(unittest.TestCase):
+ """
+ Tests for L{EventDispatcher}.
+ """
+
+ def testStuff(self):
+ d = EventDispatcher()
+ cb1 = CallbackTracker()
+ cb2 = CallbackTracker()
+ cb3 = CallbackTracker()
+
+ d.addObserver("/message/body", cb1.call)
+ d.addObserver("/message", cb1.call)
+ d.addObserver("/presence", cb2.call)
+ d.addObserver("//event/testevent", cb3.call)
+
+ msg = Element(("ns", "message"))
+ msg.addElement("body")
+
+ pres = Element(("ns", "presence"))
+ pres.addElement("presence")
+
+ d.dispatch(msg)
+ self.assertEquals(cb1.called, 2)
+ self.assertEquals(cb1.obj, msg)
+ self.assertEquals(cb2.called, 0)
+
+ d.dispatch(pres)
+ self.assertEquals(cb1.called, 2)
+ self.assertEquals(cb2.called, 1)
+ self.assertEquals(cb2.obj, pres)
+ self.assertEquals(cb3.called, 0)
+
+ d.dispatch(d, "//event/testevent")
+ self.assertEquals(cb3.called, 1)
+ self.assertEquals(cb3.obj, d)
+
+ d.removeObserver("/presence", cb2.call)
+ d.dispatch(pres)
+ self.assertEquals(cb2.called, 1)
+
+
+ def test_addObserverTwice(self):
+ """
+ Test adding two observers for the same query.
+
+ When the event is dispath both of the observers need to be called.
+ """
+ d = EventDispatcher()
+ cb1 = CallbackTracker()
+ cb2 = CallbackTracker()
+
+ d.addObserver("//event/testevent", cb1.call)
+ d.addObserver("//event/testevent", cb2.call)
+ d.dispatch(d, "//event/testevent")
+
+ self.assertEquals(cb1.called, 1)
+ self.assertEquals(cb1.obj, d)
+ self.assertEquals(cb2.called, 1)
+ self.assertEquals(cb2.obj, d)
+
+
+ def test_addObserverInDispatch(self):
+ """
+ Test for registration of an observer during dispatch.
+ """
+ d = EventDispatcher()
+ msg = Element(("ns", "message"))
+ cb = CallbackTracker()
+
+ def onMessage(_):
+ d.addObserver("/message", cb.call)
+
+ d.addOnetimeObserver("/message", onMessage)
+
+ d.dispatch(msg)
+ self.assertEquals(cb.called, 0)
+
+ d.dispatch(msg)
+ self.assertEquals(cb.called, 1)
+
+ d.dispatch(msg)
+ self.assertEquals(cb.called, 2)
+
+
+ def test_addOnetimeObserverInDispatch(self):
+ """
+ Test for registration of a onetime observer during dispatch.
+ """
+ d = EventDispatcher()
+ msg = Element(("ns", "message"))
+ cb = CallbackTracker()
+
+ def onMessage(msg):
+ d.addOnetimeObserver("/message", cb.call)
+
+ d.addOnetimeObserver("/message", onMessage)
+
+ d.dispatch(msg)
+ self.assertEquals(cb.called, 0)
+
+ d.dispatch(msg)
+ self.assertEquals(cb.called, 1)
+
+ d.dispatch(msg)
+ self.assertEquals(cb.called, 1)
+
+
+ def testOnetimeDispatch(self):
+ d = EventDispatcher()
+ msg = Element(("ns", "message"))
+ cb = CallbackTracker()
+
+ d.addOnetimeObserver("/message", cb.call)
+ d.dispatch(msg)
+ self.assertEquals(cb.called, 1)
+ d.dispatch(msg)
+ self.assertEquals(cb.called, 1)
+
+
+ def testDispatcherResult(self):
+ d = EventDispatcher()
+ msg = Element(("ns", "message"))
+ pres = Element(("ns", "presence"))
+ cb = CallbackTracker()
+
+ d.addObserver("/presence", cb.call)
+ result = d.dispatch(msg)
+ self.assertEquals(False, result)
+
+ result = d.dispatch(pres)
+ self.assertEquals(True, result)
+
+
+ def testOrderedXPathDispatch(self):
+ d = EventDispatcher()
+ cb = OrderedCallbackTracker()
+ d.addObserver("/message/body", cb.call2)
+ d.addObserver("/message", cb.call3, -1)
+ d.addObserver("/message/body", cb.call1, 1)
+
+ msg = Element(("ns", "message"))
+ msg.addElement("body")
+ d.dispatch(msg)
+ self.assertEquals(cb.callList, [cb.call1, cb.call2, cb.call3],
+ "Calls out of order: %s" %
+ repr([c.__name__ for c in cb.callList]))
+
+
+ # Observers are put into CallbackLists that are then put into dictionaries
+ # keyed by the event trigger. Upon removal of the last observer for a
+ # particular event trigger, the (now empty) CallbackList and corresponding
+ # event trigger should be removed from those dictionaries to prevent
+ # slowdown and memory leakage.
+
+ def test_cleanUpRemoveEventObserver(self):
+ """
+ Test observer clean-up after removeObserver for named events.
+ """
+
+ d = EventDispatcher()
+ cb = CallbackTracker()
+
+ d.addObserver('//event/test', cb.call)
+ d.dispatch(None, '//event/test')
+ self.assertEqual(1, cb.called)
+ d.removeObserver('//event/test', cb.call)
+ self.assertEqual(0, len(d._eventObservers.pop(0)))
+
+
+ def test_cleanUpRemoveXPathObserver(self):
+ """
+ Test observer clean-up after removeObserver for XPath events.
+ """
+
+ d = EventDispatcher()
+ cb = CallbackTracker()
+ msg = Element((None, "message"))
+
+ d.addObserver('/message', cb.call)
+ d.dispatch(msg)
+ self.assertEqual(1, cb.called)
+ d.removeObserver('/message', cb.call)
+ self.assertEqual(0, len(d._xpathObservers.pop(0)))
+
+
+ def test_cleanUpOnetimeEventObserver(self):
+ """
+ Test observer clean-up after onetime named events.
+ """
+
+ d = EventDispatcher()
+ cb = CallbackTracker()
+
+ d.addOnetimeObserver('//event/test', cb.call)
+ d.dispatch(None, '//event/test')
+ self.assertEqual(1, cb.called)
+ self.assertEqual(0, len(d._eventObservers.pop(0)))
+
+
+ def test_cleanUpOnetimeXPathObserver(self):
+ """
+ Test observer clean-up after onetime XPath events.
+ """
+
+ d = EventDispatcher()
+ cb = CallbackTracker()
+ msg = Element((None, "message"))
+
+ d.addOnetimeObserver('/message', cb.call)
+ d.dispatch(msg)
+ self.assertEqual(1, cb.called)
+ self.assertEqual(0, len(d._xpathObservers.pop(0)))
+
+
+ def test_observerRaisingException(self):
+ """
+ Test that exceptions in observers do not bubble up to dispatch.
+
+ The exceptions raised in observers should be logged and other
+ observers should be called as if nothing happened.
+ """
+
+ class OrderedCallbackList(utility.CallbackList):
+ def __init__(self):
+ self.callbacks = OrderedDict()
+
+ class TestError(Exception):
+ pass
+
+ def raiseError(_):
+ raise TestError()
+
+ d = EventDispatcher()
+ cb = CallbackTracker()
+
+ originalCallbackList = utility.CallbackList
+
+ try:
+ utility.CallbackList = OrderedCallbackList
+
+ d.addObserver('//event/test', raiseError)
+ d.addObserver('//event/test', cb.call)
+ try:
+ d.dispatch(None, '//event/test')
+ except TestError:
+ self.fail("TestError raised. Should have been logged instead.")
+
+ self.assertEqual(1, len(self.flushLoggedErrors(TestError)))
+ self.assertEqual(1, cb.called)
+ finally:
+ utility.CallbackList = originalCallbackList
+
+
+
+class XmlPipeTest(unittest.TestCase):
+ """
+ Tests for L{twisted.words.xish.utility.XmlPipe}.
+ """
+
+ def setUp(self):
+ self.pipe = utility.XmlPipe()
+
+
+ def test_sendFromSource(self):
+ """
+ Send an element from the source and observe it from the sink.
+ """
+ def cb(obj):
+ called.append(obj)
+
+ called = []
+ self.pipe.sink.addObserver('/test[@xmlns="testns"]', cb)
+ element = Element(('testns', 'test'))
+ self.pipe.source.send(element)
+ self.assertEquals([element], called)
+
+
+ def test_sendFromSink(self):
+ """
+ Send an element from the sink and observe it from the source.
+ """
+ def cb(obj):
+ called.append(obj)
+
+ called = []
+ self.pipe.source.addObserver('/test[@xmlns="testns"]', cb)
+ element = Element(('testns', 'test'))
+ self.pipe.sink.send(element)
+ self.assertEquals([element], called)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_xmlstream.py b/vendor/Twisted-10.0.0/twisted/words/test/test_xmlstream.py
new file mode 100644
index 0000000000..743de6876d
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_xmlstream.py
@@ -0,0 +1,201 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.xish.xmlstream}.
+"""
+
+from twisted.internet import protocol
+from twisted.trial import unittest
+from twisted.words.xish import domish, utility, xmlstream
+
+class XmlStreamTest(unittest.TestCase):
+ def setUp(self):
+ self.outlist = []
+ self.xmlstream = xmlstream.XmlStream()
+ self.xmlstream.transport = self
+ self.xmlstream.transport.write = self.outlist.append
+
+
+ def loseConnection(self):
+ """
+ Stub loseConnection because we are a transport.
+ """
+ self.xmlstream.connectionLost("no reason")
+
+
+ def test_send(self):
+ """
+ Sending data should result into it being written to the transport.
+ """
+ self.xmlstream.connectionMade()
+ self.xmlstream.send("<root>")
+ self.assertEquals(self.outlist[0], "<root>")
+
+
+ def test_receiveRoot(self):
+ """
+ Receiving the starttag of the root element results in stream start.
+ """
+ streamStarted = []
+
+ def streamStartEvent(rootelem):
+ streamStarted.append(None)
+
+ self.xmlstream.addObserver(xmlstream.STREAM_START_EVENT,
+ streamStartEvent)
+ self.xmlstream.connectionMade()
+ self.xmlstream.dataReceived("<root>")
+ self.assertEquals(1, len(streamStarted))
+
+
+ def test_receiveBadXML(self):
+ """
+ Receiving malformed XML should result in in error.
+ """
+ streamError = []
+ streamEnd = []
+
+ def streamErrorEvent(reason):
+ streamError.append(reason)
+
+ def streamEndEvent(_):
+ streamEnd.append(None)
+
+ self.xmlstream.addObserver(xmlstream.STREAM_ERROR_EVENT,
+ streamErrorEvent)
+ self.xmlstream.addObserver(xmlstream.STREAM_END_EVENT,
+ streamEndEvent)
+ self.xmlstream.connectionMade()
+
+ self.xmlstream.dataReceived("<root>")
+ self.assertEquals(0, len(streamError))
+ self.assertEquals(0, len(streamEnd))
+
+ self.xmlstream.dataReceived("<child><unclosed></child>")
+ self.assertEquals(1, len(streamError))
+ self.assertTrue(streamError[0].check(domish.ParserError))
+ self.assertEquals(1, len(streamEnd))
+
+
+
+class DummyProtocol(protocol.Protocol, utility.EventDispatcher):
+ """
+ I am a protocol with an event dispatcher without further processing.
+
+ This protocol is only used for testing XmlStreamFactoryMixin to make
+ sure the bootstrap observers are added to the protocol instance.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.args = args
+ self.kwargs = kwargs
+ self.observers = []
+
+ utility.EventDispatcher.__init__(self)
+
+
+
+class BootstrapMixinTest(unittest.TestCase):
+ """
+ Tests for L{xmlstream.BootstrapMixin}.
+
+ @ivar factory: Instance of the factory or mixin under test.
+ """
+
+ def setUp(self):
+ self.factory = xmlstream.BootstrapMixin()
+
+
+ def test_installBootstraps(self):
+ """
+ Dispatching an event should fire registered bootstrap observers.
+ """
+ called = []
+
+ def cb(data):
+ called.append(data)
+
+ dispatcher = DummyProtocol()
+ self.factory.addBootstrap('//event/myevent', cb)
+ self.factory.installBootstraps(dispatcher)
+
+ dispatcher.dispatch(None, '//event/myevent')
+ self.assertEquals(1, len(called))
+
+
+ def test_addAndRemoveBootstrap(self):
+ """
+ Test addition and removal of a bootstrap event handler.
+ """
+
+ called = []
+
+ def cb(data):
+ called.append(data)
+
+ self.factory.addBootstrap('//event/myevent', cb)
+ self.factory.removeBootstrap('//event/myevent', cb)
+
+ dispatcher = DummyProtocol()
+ self.factory.installBootstraps(dispatcher)
+
+ dispatcher.dispatch(None, '//event/myevent')
+ self.assertFalse(called)
+
+
+
+class GenericXmlStreamFactoryTestsMixin(BootstrapMixinTest):
+ """
+ Generic tests for L{XmlStream} factories.
+ """
+
+ def setUp(self):
+ self.factory = xmlstream.XmlStreamFactory()
+
+
+ def test_buildProtocolInstallsBootstraps(self):
+ """
+ The protocol factory installs bootstrap event handlers on the protocol.
+ """
+ called = []
+
+ def cb(data):
+ called.append(data)
+
+ self.factory.addBootstrap('//event/myevent', cb)
+
+ xs = self.factory.buildProtocol(None)
+ xs.dispatch(None, '//event/myevent')
+
+ self.assertEquals(1, len(called))
+
+
+ def test_buildProtocolStoresFactory(self):
+ """
+ The protocol factory is saved in the protocol.
+ """
+ xs = self.factory.buildProtocol(None)
+ self.assertIdentical(self.factory, xs.factory)
+
+
+
+class XmlStreamFactoryMixinTest(GenericXmlStreamFactoryTestsMixin):
+ """
+ Tests for L{xmlstream.XmlStreamFactoryMixin}.
+ """
+
+ def setUp(self):
+ self.factory = xmlstream.XmlStreamFactoryMixin(None, test=None)
+ self.factory.protocol = DummyProtocol
+
+
+ def test_buildProtocolFactoryArguments(self):
+ """
+ Arguments passed to the factory should be passed to protocol on
+ instantiation.
+ """
+ xs = self.factory.buildProtocol(None)
+
+ self.assertEquals((None,), xs.args)
+ self.assertEquals({'test': None}, xs.kwargs)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_xmpproutertap.py b/vendor/Twisted-10.0.0/twisted/words/test/test_xmpproutertap.py
new file mode 100644
index 0000000000..7885c73345
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_xmpproutertap.py
@@ -0,0 +1,86 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.words.xmpproutertap}.
+"""
+
+from twisted.application import internet
+from twisted.trial import unittest
+from twisted.words import xmpproutertap as tap
+from twisted.words.protocols.jabber import component
+
+class XMPPRouterTapTest(unittest.TestCase):
+
+ def test_port(self):
+ """
+ The port option is recognised as a parameter.
+ """
+ opt = tap.Options()
+ opt.parseOptions(['--port', '7001'])
+ self.assertEquals(opt['port'], '7001')
+
+
+ def test_portDefault(self):
+ """
+ The port option has '5347' as default value
+ """
+ opt = tap.Options()
+ opt.parseOptions([])
+ self.assertEquals(opt['port'], 'tcp:5347:interface=127.0.0.1')
+
+
+ def test_secret(self):
+ """
+ The secret option is recognised as a parameter.
+ """
+ opt = tap.Options()
+ opt.parseOptions(['--secret', 'hushhush'])
+ self.assertEquals(opt['secret'], 'hushhush')
+
+
+ def test_secretDefault(self):
+ """
+ The secret option has 'secret' as default value
+ """
+ opt = tap.Options()
+ opt.parseOptions([])
+ self.assertEquals(opt['secret'], 'secret')
+
+
+ def test_verbose(self):
+ """
+ The verbose option is recognised as a flag.
+ """
+ opt = tap.Options()
+ opt.parseOptions(['--verbose'])
+ self.assertTrue(opt['verbose'])
+
+
+ def test_makeService(self):
+ """
+ The service gets set up with a router and factory.
+ """
+ opt = tap.Options()
+ opt.parseOptions([])
+ s = tap.makeService(opt)
+ self.assertIsInstance(s, internet.TCPServer)
+ self.assertEquals('127.0.0.1', s.kwargs['interface'])
+ self.assertEquals(2, len(s.args))
+ self.assertEquals(5347, s.args[0])
+ factory = s.args[1]
+ self.assertIsInstance(factory, component.XMPPComponentServerFactory)
+ self.assertIsInstance(factory.router, component.Router)
+ self.assertEquals('secret', factory.secret)
+ self.assertFalse(factory.logTraffic)
+
+
+ def test_makeServiceVerbose(self):
+ """
+ The verbose flag enables traffic logging.
+ """
+ opt = tap.Options()
+ opt.parseOptions(['--verbose'])
+ s = tap.makeService(opt)
+ factory = s.args[1]
+ self.assertTrue(factory.logTraffic)
diff --git a/vendor/Twisted-10.0.0/twisted/words/test/test_xpath.py b/vendor/Twisted-10.0.0/twisted/words/test/test_xpath.py
new file mode 100644
index 0000000000..ad9ef67ee6
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/test/test_xpath.py
@@ -0,0 +1,260 @@
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.trial import unittest
+import sys, os
+
+from twisted.words.xish.domish import Element
+from twisted.words.xish.xpath import XPathQuery
+from twisted.words.xish import xpath
+
+class XPathTest(unittest.TestCase):
+ def setUp(self):
+ # Build element:
+ # <foo xmlns='testns' attrib1='value1' attrib3="user@host/resource">
+ # somecontent
+ # <bar>
+ # <foo>
+ # <gar>DEF</gar>
+ # </foo>
+ # </bar>
+ # somemorecontent
+ # <bar attrib2="value2">
+ # <bar>
+ # <foo/>
+ # <gar>ABC</gar>
+ # </bar>
+ # <bar/>
+ # <bar attrib4='value4' attrib5='value5'>
+ # <foo/>
+ # <gar>JKL</gar>
+ # </bar>
+ # <bar attrib4='value4' attrib5='value4'>
+ # <foo/>
+ # <gar>MNO</gar>
+ # </bar>
+ # <bar attrib4='value4' attrib5='value6'/>
+ # </foo>
+ self.e = Element(("testns", "foo"))
+ self.e["attrib1"] = "value1"
+ self.e["attrib3"] = "user@host/resource"
+ self.e.addContent("somecontent")
+ self.bar1 = self.e.addElement("bar")
+ self.subfoo = self.bar1.addElement("foo")
+ self.gar1 = self.subfoo.addElement("gar")
+ self.gar1.addContent("DEF")
+ self.e.addContent("somemorecontent")
+ self.bar2 = self.e.addElement("bar")
+ self.bar2["attrib2"] = "value2"
+ self.bar3 = self.bar2.addElement("bar")
+ self.subfoo2 = self.bar3.addElement("foo")
+ self.gar2 = self.bar3.addElement("gar")
+ self.gar2.addContent("ABC")
+ self.bar4 = self.e.addElement("bar")
+ self.bar5 = self.e.addElement("bar")
+ self.bar5["attrib4"] = "value4"
+ self.bar5["attrib5"] = "value5"
+ self.subfoo3 = self.bar5.addElement("foo")
+ self.gar3 = self.bar5.addElement("gar")
+ self.gar3.addContent("JKL")
+ self.bar6 = self.e.addElement("bar")
+ self.bar6["attrib4"] = "value4"
+ self.bar6["attrib5"] = "value4"
+ self.subfoo4 = self.bar6.addElement("foo")
+ self.gar4 = self.bar6.addElement("gar")
+ self.gar4.addContent("MNO")
+ self.bar7 = self.e.addElement("bar")
+ self.bar7["attrib4"] = "value4"
+ self.bar7["attrib5"] = "value6"
+
+ def test_staticMethods(self):
+ """
+ Test basic operation of the static methods.
+ """
+ self.assertEquals(xpath.matches("/foo/bar", self.e),
+ True)
+ self.assertEquals(xpath.queryForNodes("/foo/bar", self.e),
+ [self.bar1, self.bar2, self.bar4,
+ self.bar5, self.bar6, self.bar7])
+ self.assertEquals(xpath.queryForString("/foo", self.e),
+ "somecontent")
+ self.assertEquals(xpath.queryForStringList("/foo", self.e),
+ ["somecontent", "somemorecontent"])
+
+ def test_locationFooBar(self):
+ """
+ Test matching foo with child bar.
+ """
+ xp = XPathQuery("/foo/bar")
+ self.assertEquals(xp.matches(self.e), 1)
+
+ def test_locationFooBarFoo(self):
+ """
+ Test finding foos at the second level.
+ """
+ xp = XPathQuery("/foo/bar/foo")
+ self.assertEquals(xp.matches(self.e), 1)
+ self.assertEquals(xp.queryForNodes(self.e), [self.subfoo,
+ self.subfoo3,
+ self.subfoo4])
+
+ def test_locationNoBar3(self):
+ """
+ Test not finding bar3.
+ """
+ xp = XPathQuery("/foo/bar3")
+ self.assertEquals(xp.matches(self.e), 0)
+
+ def test_locationAllChilds(self):
+ """
+ Test finding childs of foo.
+ """
+ xp = XPathQuery("/foo/*")
+ self.assertEquals(xp.matches(self.e), True)
+ self.assertEquals(xp.queryForNodes(self.e), [self.bar1, self.bar2,
+ self.bar4, self.bar5,
+ self.bar6, self.bar7])
+
+ def test_attribute(self):
+ """
+ Test matching foo with attribute.
+ """
+ xp = XPathQuery("/foo[@attrib1]")
+ self.assertEquals(xp.matches(self.e), True)
+
+ def test_attributeWithValueAny(self):
+ """
+ Test find nodes with attribute having value.
+ """
+ xp = XPathQuery("/foo/*[@attrib2='value2']")
+ self.assertEquals(xp.matches(self.e), True)
+ self.assertEquals(xp.queryForNodes(self.e), [self.bar2])
+
+ def test_position(self):
+ """
+ Test finding element at position.
+ """
+ xp = XPathQuery("/foo/bar[2]")
+ self.assertEquals(xp.matches(self.e), 1)
+ self.assertEquals(xp.queryForNodes(self.e), [self.bar1])
+
+ test_position.todo = "XPath queries with position are not working."
+
+ def test_namespaceFound(self):
+ """
+ Test matching node with namespace.
+ """
+ xp = XPathQuery("/foo[@xmlns='testns']/bar")
+ self.assertEquals(xp.matches(self.e), 1)
+
+ def test_namespaceNotFound(self):
+ """
+ Test not matching node with wrong namespace.
+ """
+ xp = XPathQuery("/foo[@xmlns='badns']/bar2")
+ self.assertEquals(xp.matches(self.e), 0)
+
+ def test_attributeWithValue(self):
+ """
+ Test matching node with attribute having value.
+ """
+ xp = XPathQuery("/foo[@attrib1='value1']")
+ self.assertEquals(xp.matches(self.e), 1)
+
+ def test_queryForString(self):
+ """
+ Test for queryForString and queryForStringList.
+ """
+ xp = XPathQuery("/foo")
+ self.assertEquals(xp.queryForString(self.e), "somecontent")
+ self.assertEquals(xp.queryForStringList(self.e),
+ ["somecontent", "somemorecontent"])
+
+ def test_queryForNodes(self):
+ """
+ Test finding nodes.
+ """
+ xp = XPathQuery("/foo/bar")
+ self.assertEquals(xp.queryForNodes(self.e), [self.bar1, self.bar2,
+ self.bar4, self.bar5,
+ self.bar6, self.bar7])
+
+ def test_textCondition(self):
+ """
+ Test matching a node with given text.
+ """
+ xp = XPathQuery("/foo[text() = 'somecontent']")
+ self.assertEquals(xp.matches(self.e), True)
+
+ def test_textNotOperator(self):
+ """
+ Test for not operator.
+ """
+ xp = XPathQuery("/foo[not(@nosuchattrib)]")
+ self.assertEquals(xp.matches(self.e), True)
+
+ def test_anyLocationAndText(self):
+ """
+ Test finding any nodes named gar and getting their text contents.
+ """
+ xp = XPathQuery("//gar")
+ self.assertEquals(xp.matches(self.e), True)
+ self.assertEquals(xp.queryForNodes(self.e), [self.gar1, self.gar2,
+ self.gar3, self.gar4])
+ self.assertEquals(xp.queryForStringList(self.e), ["DEF", "ABC",
+ "JKL", "MNO"])
+
+ def test_anyLocation(self):
+ """
+ Test finding any nodes named bar.
+ """
+ xp = XPathQuery("//bar")
+ self.assertEquals(xp.matches(self.e), True)
+ self.assertEquals(xp.queryForNodes(self.e), [self.bar1, self.bar2,
+ self.bar3, self.bar4,
+ self.bar5, self.bar6,
+ self.bar7])
+
+ def test_anyLocationQueryForString(self):
+ """
+ L{XPathQuery.queryForString} should raise a L{NotImplementedError}
+ for any location.
+ """
+ xp = XPathQuery("//bar")
+ self.assertRaises(NotImplementedError, xp.queryForString, None)
+
+ def test_andOperator(self):
+ """
+ Test boolean and operator in condition.
+ """
+ xp = XPathQuery("//bar[@attrib4='value4' and @attrib5='value5']")
+ self.assertEquals(xp.matches(self.e), True)
+ self.assertEquals(xp.queryForNodes(self.e), [self.bar5])
+
+ def test_orOperator(self):
+ """
+ Test boolean or operator in condition.
+ """
+ xp = XPathQuery("//bar[@attrib5='value4' or @attrib5='value5']")
+ self.assertEquals(xp.matches(self.e), True)
+ self.assertEquals(xp.queryForNodes(self.e), [self.bar5, self.bar6])
+
+ def test_booleanOperatorsParens(self):
+ """
+ Test multiple boolean operators in condition with parens.
+ """
+ xp = XPathQuery("""//bar[@attrib4='value4' and
+ (@attrib5='value4' or @attrib5='value6')]""")
+ self.assertEquals(xp.matches(self.e), True)
+ self.assertEquals(xp.queryForNodes(self.e), [self.bar6, self.bar7])
+
+ def test_booleanOperatorsNoParens(self):
+ """
+ Test multiple boolean operators in condition without parens.
+ """
+ xp = XPathQuery("""//bar[@attrib5='value4' or
+ @attrib5='value5' or
+ @attrib5='value6']""")
+ self.assertEquals(xp.matches(self.e), True)
+ self.assertEquals(xp.queryForNodes(self.e), [self.bar5, self.bar6, self.bar7])
diff --git a/vendor/Twisted-10.0.0/twisted/words/toctap.py b/vendor/Twisted-10.0.0/twisted/words/toctap.py
new file mode 100644
index 0000000000..ce79bb89d8
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/toctap.py
@@ -0,0 +1,20 @@
+
+# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Support module for making TOC servers with twistd.
+"""
+
+from twisted.words.protocols import toc
+from twisted.python import usage
+from twisted.application import strports
+
+class Options(usage.Options):
+ synopsis = "[-p <port>]"
+ optParameters = [["port", "p", "5190"]]
+ longdesc = "Makes a TOC server."
+
+def makeService(config):
+ return strports.service(config['port'], toc.TOCFactory())
diff --git a/vendor/Twisted-10.0.0/twisted/words/topfiles/NEWS b/vendor/Twisted-10.0.0/twisted/words/topfiles/NEWS
new file mode 100644
index 0000000000..aad5ef0d40
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/topfiles/NEWS
@@ -0,0 +1,230 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/<number>
+
+Twisted Words 10.0.0 (2010-03-01)
+=================================
+
+Features
+--------
+ - twisted.words.protocols.irc.IRCClient.irc_MODE now takes ISUPPORT
+ parameters into account when parsing mode messages with arguments
+ that take parameters (#3296)
+
+Bugfixes
+--------
+ - When twisted.words.protocols.irc.IRCClient's versionNum and
+ versionEnv attributes are set to None, they will no longer be
+ included in the client's response to CTCP VERSION queries. (#3660)
+
+ - twisted.words.protocols.jabber.xmlstream.hashPassword now only
+ accepts unicode as input (#3741, #3742, #3847)
+
+Other
+-----
+ - #2503, #4066, #4261
+
+
+Twisted Words 9.0.0 (2009-11-24)
+================================
+
+Features
+--------
+ - IRCClient.describe is a new method meant to replace IRCClient.me to send
+ CTCP ACTION messages with less confusing behavior (#3910)
+ - The XMPP client protocol implementation now supports ANONYMOUS SASL
+ authentication (#4067)
+ - The IRC client protocol implementation now has better support for the
+ ISUPPORT server->client message, storing the data in a new
+ ServerSupportedFeatures object accessible via IRCClient.supported (#3285)
+
+Fixes
+-----
+ - The twisted.words IRC server now always sends an MOTD, which at least makes
+ Pidgin able to successfully connect to a twisted.words IRC server (#2385)
+ - The IRC client will now dispatch "RPL MOTD" messages received before a
+ "RPL MOTD START" instead of raising an exception (#3676)
+ - The IRC client protocol implementation no longer updates its 'nickname'
+ attribute directly; instead, that attribute will be updated when the server
+ acknowledges the change (#3377)
+ - The IRC client protocol implementation now supports falling back to another
+ nickname when a nick change request fails (#3377, #4010)
+
+Deprecations and Removals
+-------------------------
+ - The TOC protocol implementation is now deprecated, since the protocol itself
+ has been deprecated and obselete for quite a long time (#3580)
+ - The gui "im" application has been removed, since it relied on GTK1, which is
+ hard to find these days (#3699, #3340)
+
+Other
+-----
+ - #2763, #3540, #3647, #3750, #3895, #3968, #4050
+
+Words 8.2.0 (2008-12-16)
+========================
+
+Feature
+-------
+ - There is now a standalone XMPP router included in twisted.words: it can be
+ used with the 'twistd xmpp-router' command line (#3407)
+ - A server factory for Jabber XML Streams has been added (#3435)
+ - Domish now allows for iterating child elements with specific qualified names
+ (#2429)
+ - IRCClient now has a 'back' method which removes the away status (#3366)
+ - IRCClient now has a 'whois' method (#3133)
+
+Fixes
+-----
+ - The IRC Client implementation can now deal with compound mode changes (#3230)
+ - The MSN protocol implementation no longer requires the CVR0 protocol to
+ be included in the VER command (#3394)
+ - In the IRC server implementation, topic messages will no longer be sent for
+ a group which has no topic (#2204)
+ - An infinite loop (which caused infinite memory usage) in irc.split has been
+ fixed. This was triggered any time a message that starts with a delimiter
+ was sent (#3446)
+ - Jabber's toResponse now generates a valid stanza even when stanzaType is not
+ specified (#3467)
+ - The lifetime of authenticator instances in XmlStreamServerFactory is no
+ longer artificially extended (#3464)
+
+Other
+-----
+ - #3365
+
+
+8.1.0 (2008-05-18)
+==================
+
+Features
+--------
+ - JID objects now have a nice __repr__ (#3156)
+ - Extending XMPP protocols is now easier (#2178)
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+ - A bug whereby one-time XMPP observers would be enabled permanently was fixed
+ (#3066)
+
+
+8.0.0 (2008-03-17)
+==================
+
+Features
+--------
+ - Provide function for creating XMPP response stanzas. (#2614, #2614)
+ - Log exceptions raised in Xish observers. (#2616)
+ - Add 'and' and 'or' operators for Xish XPath expressions. (#2502)
+ - Make JIDs hashable. (#2770)
+
+Fixes
+-----
+ - Respect the hostname and servername parameters to IRCClient.register. (#1649)
+ - Make EventDispatcher remove empty callback lists. (#1652)
+ - Use legacy base64 API to support Python 2.3 (#2461)
+ - Fix support of DIGEST-MD5 challenge parsing with multi-valued directives.
+ (#2606)
+ - Fix reuse of dict of prefixes in domish.Element.toXml (#2609)
+ - Properly process XMPP stream headers (#2615)
+ - Use proper namespace for XMPP stream errors. (#2630)
+ - Properly parse XMPP stream errors. (#2771)
+ - Fix toResponse for XMPP stanzas without an id attribute. (#2773)
+ - Move XMPP stream header procesing to authenticators. (#2772)
+
+Misc
+----
+ - #2617, #2640, #2741, #2063, #2570, #2847
+
+
+0.5.0 (2007-01-06)
+==================
+
+Features
+--------
+ - (Jabber) IQ.send now optionally has a 'timeout' parameter which
+ specifies a time at which to errback the Deferred with a
+ TimeoutError (#2218)
+ - (Jabber) SASL authentication, resource binding and session
+ establishment were added. (#1046) The following were done in
+ support of this change:
+ - Rework ConnectAuthenticator to work with initializer objects that
+ provide a stream initialization step.
+ - Reimplement iq:auth as an initializer.
+ - Reimplement TLS negotiation as an initializer.
+ - Add XMPPAuthenticator as a XMPP 1.0 client authenticator (only), along
+ with XMPPClientFactory.
+ - Add support for working with pre-XMPP-1.0 error stanzas.
+ - Remove hasFeature() from XmlStream as you can test (uri, name) in
+ xs.features.
+ - Add sendFooter() and sendStreamError() to XmlStream
+
+Fixes
+-----
+ - (Jabber) Deferreds from queries which were never resolved before
+ a lost connection are now errbacked (#2006)
+ - (Jabber) servers which didn't send a 'realm' directive in
+ authentication challenges no longer cause the Jabber client to
+ choke (#2098)
+ - (MSN) error responses are now properly turned into errbacks (#2019)
+ - (IRC) A trivial bug in IRCClient which would cause whois(oper=True)
+ to always raise an exception was fixed (#2089)
+ - (IM) Bugs in the error handling and already-connecting cases of
+ AbstractAccount.logOn were fixed (#2086)
+
+Misc
+----
+ - #1734, #1735, #1636, #1936, #1883, #1995, #2171, #2165, #2177
+
+
+0.4.0 (2006-05-21)
+==================
+
+Features
+--------
+ - Jabber:
+ - Add support for stream and stanza level errors
+ - Create new IQ stanza helper that works with deferreds
+ - Add TLS support for initiating entities to XmlStream
+ - Fix account registration
+ - Xish:
+ - Fix various namespace issues
+ - Add IElement
+ - Store namespace declarations in parsed XML for later serialization
+ - Fix user name/group collision in server service (#1655).
+ - Correctly recognize MSN capability messages (#861).
+
+Fixes
+-----
+ - Misc: #1283, #1296, #1302, #1424
+ - Fix unicode/str confusion in IRC server service.
+
+
+0.3.0:
+ - Jabber:
+
+ - Fix digest authentication in Jabber
+ - Add Jabber xmlstream module that contains the Jabber specific bits that
+ got factored out of Twisted Xish's xmlstream, and make it suitable for
+ implementing full XMPP support.
+ - Xish:
+ - Fixed serialization in _ListSerializer
+ - Removed unneeded extra whitespace generated in serialization
+ - Removed _Serializer in favour of _ListSerializer
+ - Use unicode objects for representing serialized XML, instead of utf-8
+ encoded str objects.
+ - Properly catch XML parser errors
+ - Rework and fix element stream test cases
+ - Strip xmlstream from all Jabber specifics that moved to Twisted Words
+ - Added exhaustive docstrings to xmlstream.
+ - Words Service:
+ - Complete rewrite
+ - Not backwards compatible
+
+0.1.0:
+ - Fix some miscellaneous bugs in OSCAR
+ - Add QUIT notification for IRC
+ - Fix message wrapping
+ - Misc Jabber fixes
+ - Add stringprep support for Jabber IDs
+ This only works properly on 2.3.2 or higher
diff --git a/vendor/Twisted-10.0.0/twisted/words/topfiles/README b/vendor/Twisted-10.0.0/twisted/words/topfiles/README
new file mode 100644
index 0000000000..712466cfb7
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/topfiles/README
@@ -0,0 +1,4 @@
+Twisted Words 10.0.0
+
+Twisted Words depends on Twisted Core and Twisted Web. The Twisted Web
+dependency is only necessary for MSN support.
diff --git a/vendor/Twisted-10.0.0/twisted/words/topfiles/setup.py b/vendor/Twisted-10.0.0/twisted/words/topfiles/setup.py
new file mode 100644
index 0000000000..fb33c62f88
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/topfiles/setup.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys
+
+try:
+ from twisted.python import dist
+except ImportError:
+ raise SystemExit("twisted.python.dist module not found. Make sure you "
+ "have installed the Twisted core package before "
+ "attempting to install any other Twisted projects.")
+
+if __name__ == '__main__':
+ if sys.version_info[:2] >= (2, 4):
+ extraMeta = dict(
+ classifiers=[
+ "Development Status :: 4 - Beta",
+ "Environment :: No Input/Output (Daemon)",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python",
+ "Topic :: Communications :: Chat",
+ "Topic :: Communications :: Chat :: AOL Instant Messenger",
+ "Topic :: Communications :: Chat :: ICQ",
+ "Topic :: Communications :: Chat :: Internet Relay Chat",
+ "Topic :: Internet",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ ])
+ else:
+ extraMeta = {}
+
+ dist.setup(
+ twisted_subproject="words",
+ scripts=dist.getScripts("words"),
+ # metadata
+ name="Twisted Words",
+ description="Twisted Words contains Instant Messaging implementations.",
+ author="Twisted Matrix Laboratories",
+ author_email="twisted-python@twistedmatrix.com",
+ maintainer="Jp Calderone",
+ url="http://twistedmatrix.com/trac/wiki/TwistedWords",
+ license="MIT",
+ long_description="""\
+Twisted Words contains implementations of many Instant Messaging
+protocols, including IRC, Jabber, MSN, OSCAR (AIM & ICQ), TOC (AOL),
+and some functionality for creating bots, inter-protocol gateways, and
+a client application for many of the protocols.
+
+In support of Jabber, Twisted Words also contains X-ish, a library for
+processing XML with Twisted and Python, with support for a Pythonic DOM and
+an XPath-like toolkit.
+""",
+ **extraMeta)
diff --git a/vendor/Twisted-10.0.0/twisted/words/xish/__init__.py b/vendor/Twisted-10.0.0/twisted/words/xish/__init__.py
new file mode 100644
index 0000000000..747d943010
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/xish/__init__.py
@@ -0,0 +1,10 @@
+# -*- test-case-name: twisted.words.test -*-
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+
+Twisted X-ish: XML-ish DOM and XPath-ish engine
+
+"""
diff --git a/vendor/Twisted-10.0.0/twisted/words/xish/domish.py b/vendor/Twisted-10.0.0/twisted/words/xish/domish.py
new file mode 100644
index 0000000000..cb8b5d417e
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/xish/domish.py
@@ -0,0 +1,848 @@
+# -*- test-case-name: twisted.words.test.test_domish -*-
+#
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+DOM-like XML processing support.
+
+This module provides support for parsing XML into DOM-like object structures
+and serializing such structures to an XML string representation, optimized
+for use in streaming XML applications.
+"""
+
+import types
+
+from zope.interface import implements, Interface, Attribute
+
+def _splitPrefix(name):
+ """ Internal method for splitting a prefixed Element name into its
+ respective parts """
+ ntok = name.split(":", 1)
+ if len(ntok) == 2:
+ return ntok
+ else:
+ return (None, ntok[0])
+
+# Global map of prefixes that always get injected
+# into the serializers prefix map (note, that doesn't
+# mean they're always _USED_)
+G_PREFIXES = { "http://www.w3.org/XML/1998/namespace":"xml" }
+
+class _ListSerializer:
+ """ Internal class which serializes an Element tree into a buffer """
+ def __init__(self, prefixes=None, prefixesInScope=None):
+ self.writelist = []
+ self.prefixes = {}
+ if prefixes:
+ self.prefixes.update(prefixes)
+ self.prefixes.update(G_PREFIXES)
+ self.prefixStack = [G_PREFIXES.values()] + (prefixesInScope or [])
+ self.prefixCounter = 0
+
+ def getValue(self):
+ return u"".join(self.writelist)
+
+ def getPrefix(self, uri):
+ if not self.prefixes.has_key(uri):
+ self.prefixes[uri] = "xn%d" % (self.prefixCounter)
+ self.prefixCounter = self.prefixCounter + 1
+ return self.prefixes[uri]
+
+ def prefixInScope(self, prefix):
+ stack = self.prefixStack
+ for i in range(-1, (len(self.prefixStack)+1) * -1, -1):
+ if prefix in stack[i]:
+ return True
+ return False
+
+ def serialize(self, elem, closeElement=1, defaultUri=''):
+ # Optimization shortcuts
+ write = self.writelist.append
+
+ # Shortcut, check to see if elem is actually a chunk o' serialized XML
+ if isinstance(elem, SerializedXML):
+ write(elem)
+ return
+
+ # Shortcut, check to see if elem is actually a string (aka Cdata)
+ if isinstance(elem, types.StringTypes):
+ write(escapeToXml(elem))
+ return
+
+ # Further optimizations
+ parent = elem.parent
+ name = elem.name
+ uri = elem.uri
+ defaultUri, currentDefaultUri = elem.defaultUri, defaultUri
+
+ for p, u in elem.localPrefixes.iteritems():
+ self.prefixes[u] = p
+ self.prefixStack.append(elem.localPrefixes.keys())
+
+ # Inherit the default namespace
+ if defaultUri is None:
+ defaultUri = currentDefaultUri
+
+ if uri is None:
+ uri = defaultUri
+
+ prefix = None
+ if uri != defaultUri or uri in self.prefixes:
+ prefix = self.getPrefix(uri)
+ inScope = self.prefixInScope(prefix)
+
+ # Create the starttag
+
+ if not prefix:
+ write("<%s" % (name))
+ else:
+ write("<%s:%s" % (prefix, name))
+
+ if not inScope:
+ write(" xmlns:%s='%s'" % (prefix, uri))
+ self.prefixStack[-1].append(prefix)
+ inScope = True
+
+ if defaultUri != currentDefaultUri and \
+ (uri != defaultUri or not prefix or not inScope):
+ write(" xmlns='%s'" % (defaultUri))
+
+ for p, u in elem.localPrefixes.iteritems():
+ write(" xmlns:%s='%s'" % (p, u))
+
+ # Serialize attributes
+ for k,v in elem.attributes.items():
+ # If the attribute name is a tuple, it's a qualified attribute
+ if isinstance(k, types.TupleType):
+ attr_uri, attr_name = k
+ attr_prefix = self.getPrefix(attr_uri)
+
+ if not self.prefixInScope(attr_prefix):
+ write(" xmlns:%s='%s'" % (attr_prefix, attr_uri))
+ self.prefixStack[-1].append(attr_prefix)
+
+ write(" %s:%s='%s'" % (attr_prefix, attr_name,
+ escapeToXml(v, 1)))
+ else:
+ write((" %s='%s'" % ( k, escapeToXml(v, 1))))
+
+ # Shortcut out if this is only going to return
+ # the element (i.e. no children)
+ if closeElement == 0:
+ write(">")
+ return
+
+ # Serialize children
+ if len(elem.children) > 0:
+ write(">")
+ for c in elem.children:
+ self.serialize(c, defaultUri=defaultUri)
+ # Add closing tag
+ if not prefix:
+ write("</%s>" % (name))
+ else:
+ write("</%s:%s>" % (prefix, name))
+ else:
+ write("/>")
+
+ self.prefixStack.pop()
+
+
+SerializerClass = _ListSerializer
+
+def escapeToXml(text, isattrib = 0):
+ """ Escape text to proper XML form, per section 2.3 in the XML specification.
+
+ @type text: L{str}
+ @param text: Text to escape
+
+ @type isattrib: L{bool}
+ @param isattrib: Triggers escaping of characters necessary for use as
+ attribute values
+ """
+ text = text.replace("&", "&amp;")
+ text = text.replace("<", "&lt;")
+ text = text.replace(">", "&gt;")
+ if isattrib == 1:
+ text = text.replace("'", "&apos;")
+ text = text.replace("\"", "&quot;")
+ return text
+
+def unescapeFromXml(text):
+ text = text.replace("&lt;", "<")
+ text = text.replace("&gt;", ">")
+ text = text.replace("&apos;", "'")
+ text = text.replace("&quot;", "\"")
+ text = text.replace("&amp;", "&")
+ return text
+
+def generateOnlyInterface(list, int):
+ """ Filters items in a list by class
+ """
+ for n in list:
+ if int.providedBy(n):
+ yield n
+
+def generateElementsQNamed(list, name, uri):
+ """ Filters Element items in a list with matching name and URI. """
+ for n in list:
+ if IElement.providedBy(n) and n.name == name and n.uri == uri:
+ yield n
+
+def generateElementsNamed(list, name):
+ """ Filters Element items in a list with matching name, regardless of URI.
+ """
+ for n in list:
+ if IElement.providedBy(n) and n.name == name:
+ yield n
+
+
+class SerializedXML(unicode):
+ """ Marker class for pre-serialized XML in the DOM. """
+ pass
+
+
+class Namespace:
+ """ Convenience object for tracking namespace declarations. """
+ def __init__(self, uri):
+ self._uri = uri
+ def __getattr__(self, n):
+ return (self._uri, n)
+ def __getitem__(self, n):
+ return (self._uri, n)
+
+class IElement(Interface):
+ """
+ Interface to XML element nodes.
+
+ See L{Element} for a detailed example of its general use.
+
+ Warning: this Interface is not yet complete!
+ """
+
+ uri = Attribute(""" Element's namespace URI """)
+ name = Attribute(""" Element's local name """)
+ defaultUri = Attribute(""" Default namespace URI of child elements """)
+ attributes = Attribute(""" Dictionary of element attributes """)
+ children = Attribute(""" List of child nodes """)
+ parent = Attribute(""" Reference to element's parent element """)
+ localPrefixes = Attribute(""" Dictionary of local prefixes """)
+
+ def toXml(prefixes=None, closeElement=1, defaultUri='',
+ prefixesInScope=None):
+ """ Serializes object to a (partial) XML document
+
+ @param prefixes: dictionary that maps namespace URIs to suggested
+ prefix names.
+ @type prefixes: L{dict}
+ @param closeElement: flag that determines whether to include the
+ closing tag of the element in the serialized
+ string. A value of C{0} only generates the
+ element's start tag. A value of C{1} yields a
+ complete serialization.
+ @type closeElement: L{int}
+ @param defaultUri: Initial default namespace URI. This is most useful
+ for partial rendering, where the logical parent
+ element (of which the starttag was already
+ serialized) declares a default namespace that should
+ be inherited.
+ @type defaultUri: L{str}
+ @param prefixesInScope: list of prefixes that are assumed to be
+ declared by ancestors.
+ @type prefixesInScope: L{list}
+ @return: (partial) serialized XML
+ @rtype: L{unicode}
+ """
+
+ def addElement(name, defaultUri = None, content = None):
+ """ Create an element and add as child.
+
+ The new element is added to this element as a child, and will have
+ this element as its parent.
+
+ @param name: element name. This can be either a L{unicode} object that
+ contains the local name, or a tuple of (uri, local_name)
+ for a fully qualified name. In the former case,
+ the namespace URI is inherited from this element.
+ @type name: L{unicode} or L{tuple} of (L{unicode}, L{unicode})
+ @param defaultUri: default namespace URI for child elements. If
+ C{None}, this is inherited from this element.
+ @type defaultUri: L{unicode}
+ @param content: text contained by the new element.
+ @type content: L{unicode}
+ @return: the created element
+ @rtype: object providing L{IElement}
+ """
+
+ def addChild(node):
+ """ Adds a node as child of this element.
+
+ The C{node} will be added to the list of childs of this element, and
+ will have this element set as its parent when C{node} provides
+ L{IElement}.
+
+ @param node: the child node.
+ @type node: L{unicode} or object implementing L{IElement}
+ """
+
+class Element(object):
+ """ Represents an XML element node.
+
+ An Element contains a series of attributes (name/value pairs), content
+ (character data), and other child Element objects. When building a document
+ with markup (such as HTML or XML), use this object as the starting point.
+
+ Element objects fully support XML Namespaces. The fully qualified name of
+ the XML Element it represents is stored in the C{uri} and C{name}
+ attributes, where C{uri} holds the namespace URI. There is also a default
+ namespace, for child elements. This is stored in the C{defaultUri}
+ attribute. Note that C{''} means the empty namespace.
+
+ Serialization of Elements through C{toXml()} will use these attributes
+ for generating proper serialized XML. When both C{uri} and C{defaultUri}
+ are not None in the Element and all of its descendents, serialization
+ proceeds as expected:
+
+ >>> from twisted.words.xish import domish
+ >>> root = domish.Element(('myns', 'root'))
+ >>> root.addElement('child', content='test')
+ <twisted.words.xish.domish.Element object at 0x83002ac>
+ >>> root.toXml()
+ u"<root xmlns='myns'><child>test</child></root>"
+
+ For partial serialization, needed for streaming XML, a special value for
+ namespace URIs can be used: C{None}.
+
+ Using C{None} as the value for C{uri} means: this element is in whatever
+ namespace inherited by the closest logical ancestor when the complete XML
+ document has been serialized. The serialized start tag will have a
+ non-prefixed name, and no xmlns declaration will be generated.
+
+ Similarly, C{None} for C{defaultUri} means: the default namespace for my
+ child elements is inherited from the logical ancestors of this element,
+ when the complete XML document has been serialized.
+
+ To illustrate, an example from a Jabber stream. Assume the start tag of the
+ root element of the stream has already been serialized, along with several
+ complete child elements, and sent off, looking like this::
+
+ <stream:stream xmlns:stream='http://etherx.jabber.org/streams'
+ xmlns='jabber:client' to='example.com'>
+ ...
+
+ Now suppose we want to send a complete element represented by an
+ object C{message} created like:
+
+ >>> message = domish.Element((None, 'message'))
+ >>> message['to'] = 'user@example.com'
+ >>> message.addElement('body', content='Hi!')
+ <twisted.words.xish.domish.Element object at 0x8276e8c>
+ >>> message.toXml()
+ u"<message to='user@example.com'><body>Hi!</body></message>"
+
+ As, you can see, this XML snippet has no xmlns declaration. When sent
+ off, it inherits the C{jabber:client} namespace from the root element.
+ Note that this renders the same as using C{''} instead of C{None}:
+
+ >>> presence = domish.Element(('', 'presence'))
+ >>> presence.toXml()
+ u"<presence/>"
+
+ However, if this object has a parent defined, the difference becomes
+ clear:
+
+ >>> child = message.addElement(('http://example.com/', 'envelope'))
+ >>> child.addChild(presence)
+ <twisted.words.xish.domish.Element object at 0x8276fac>
+ >>> message.toXml()
+ u"<message to='user@example.com'><body>Hi!</body><envelope xmlns='http://example.com/'><presence xmlns=''/></envelope></message>"
+
+ As, you can see, the <presence/> element is now in the empty namespace, not
+ in the default namespace of the parent or the streams'.
+
+ @type uri: L{unicode} or None
+ @ivar uri: URI of this Element's name
+
+ @type name: L{unicode}
+ @ivar name: Name of this Element
+
+ @type defaultUri: L{unicode} or None
+ @ivar defaultUri: URI this Element exists within
+
+ @type children: L{list}
+ @ivar children: List of child Elements and content
+
+ @type parent: L{Element}
+ @ivar parent: Reference to the parent Element, if any.
+
+ @type attributes: L{dict}
+ @ivar attributes: Dictionary of attributes associated with this Element.
+
+ @type localPrefixes: L{dict}
+ @ivar localPrefixes: Dictionary of namespace declarations on this
+ element. The key is the prefix to bind the
+ namespace uri to.
+ """
+
+ implements(IElement)
+
+ _idCounter = 0
+
+ def __init__(self, qname, defaultUri=None, attribs=None,
+ localPrefixes=None):
+ """
+ @param qname: Tuple of (uri, name)
+ @param defaultUri: The default URI of the element; defaults to the URI
+ specified in L{qname}
+ @param attribs: Dictionary of attributes
+ @param localPrefixes: Dictionary of namespace declarations on this
+ element. The key is the prefix to bind the
+ namespace uri to.
+ """
+ self.localPrefixes = localPrefixes or {}
+ self.uri, self.name = qname
+ if defaultUri is None and \
+ self.uri not in self.localPrefixes.itervalues():
+ self.defaultUri = self.uri
+ else:
+ self.defaultUri = defaultUri
+ self.attributes = attribs or {}
+ self.children = []
+ self.parent = None
+
+ def __getattr__(self, key):
+ # Check child list for first Element with a name matching the key
+ for n in self.children:
+ if IElement.providedBy(n) and n.name == key:
+ return n
+
+ # Tweak the behaviour so that it's more friendly about not
+ # finding elements -- we need to document this somewhere :)
+ if key.startswith('_'):
+ raise AttributeError(key)
+ else:
+ return None
+
+ def __getitem__(self, key):
+ return self.attributes[self._dqa(key)]
+
+ def __delitem__(self, key):
+ del self.attributes[self._dqa(key)];
+
+ def __setitem__(self, key, value):
+ self.attributes[self._dqa(key)] = value
+
+ def __str__(self):
+ """ Retrieve the first CData (content) node
+ """
+ for n in self.children:
+ if isinstance(n, types.StringTypes): return n
+ return ""
+
+ def _dqa(self, attr):
+ """ Dequalify an attribute key as needed """
+ if isinstance(attr, types.TupleType) and not attr[0]:
+ return attr[1]
+ else:
+ return attr
+
+ def getAttribute(self, attribname, default = None):
+ """ Retrieve the value of attribname, if it exists """
+ return self.attributes.get(attribname, default)
+
+ def hasAttribute(self, attrib):
+ """ Determine if the specified attribute exists """
+ return self.attributes.has_key(self._dqa(attrib))
+
+ def compareAttribute(self, attrib, value):
+ """ Safely compare the value of an attribute against a provided value.
+
+ C{None}-safe.
+ """
+ return self.attributes.get(self._dqa(attrib), None) == value
+
+ def swapAttributeValues(self, left, right):
+ """ Swap the values of two attribute. """
+ d = self.attributes
+ l = d[left]
+ d[left] = d[right]
+ d[right] = l
+
+ def addChild(self, node):
+ """ Add a child to this Element. """
+ if IElement.providedBy(node):
+ node.parent = self
+ self.children.append(node)
+ return self.children[-1]
+
+ def addContent(self, text):
+ """ Add some text data to this Element. """
+ c = self.children
+ if len(c) > 0 and isinstance(c[-1], types.StringTypes):
+ c[-1] = c[-1] + text
+ else:
+ c.append(text)
+ return c[-1]
+
+ def addElement(self, name, defaultUri = None, content = None):
+ result = None
+ if isinstance(name, type(())):
+ if defaultUri is None:
+ defaultUri = name[0]
+ self.children.append(Element(name, defaultUri))
+ else:
+ if defaultUri is None:
+ defaultUri = self.defaultUri
+ self.children.append(Element((defaultUri, name), defaultUri))
+
+ result = self.children[-1]
+ result.parent = self
+
+ if content:
+ result.children.append(content)
+
+ return result
+
+ def addRawXml(self, rawxmlstring):
+ """ Add a pre-serialized chunk o' XML as a child of this Element. """
+ self.children.append(SerializedXML(rawxmlstring))
+
+ def addUniqueId(self):
+ """ Add a unique (across a given Python session) id attribute to this
+ Element.
+ """
+ self.attributes["id"] = "H_%d" % Element._idCounter
+ Element._idCounter = Element._idCounter + 1
+
+
+ def elements(self, uri=None, name=None):
+ """
+ Iterate across all children of this Element that are Elements.
+
+ Returns a generator over the child elements. If both the C{uri} and
+ C{name} parameters are set, the returned generator will only yield
+ on elements matching the qualified name.
+
+ @param uri: Optional element URI.
+ @type uri: C{unicode}
+ @param name: Optional element name.
+ @type name: C{unicode}
+ @return: Iterator that yields objects implementing L{IElement}.
+ """
+ if name is None:
+ return generateOnlyInterface(self.children, IElement)
+ else:
+ return generateElementsQNamed(self.children, name, uri)
+
+
+ def toXml(self, prefixes=None, closeElement=1, defaultUri='',
+ prefixesInScope=None):
+ """ Serialize this Element and all children to a string. """
+ s = SerializerClass(prefixes=prefixes, prefixesInScope=prefixesInScope)
+ s.serialize(self, closeElement=closeElement, defaultUri=defaultUri)
+ return s.getValue()
+
+ def firstChildElement(self):
+ for c in self.children:
+ if IElement.providedBy(c):
+ return c
+ return None
+
+
+class ParserError(Exception):
+ """ Exception thrown when a parsing error occurs """
+ pass
+
+def elementStream():
+ """ Preferred method to construct an ElementStream
+
+ Uses Expat-based stream if available, and falls back to Sux if necessary.
+ """
+ try:
+ es = ExpatElementStream()
+ return es
+ except ImportError:
+ if SuxElementStream is None:
+ raise Exception("No parsers available :(")
+ es = SuxElementStream()
+ return es
+
+try:
+ from twisted.web import sux
+except:
+ SuxElementStream = None
+else:
+ class SuxElementStream(sux.XMLParser):
+ def __init__(self):
+ self.connectionMade()
+ self.DocumentStartEvent = None
+ self.ElementEvent = None
+ self.DocumentEndEvent = None
+ self.currElem = None
+ self.rootElem = None
+ self.documentStarted = False
+ self.defaultNsStack = []
+ self.prefixStack = []
+
+ def parse(self, buffer):
+ try:
+ self.dataReceived(buffer)
+ except sux.ParseError, e:
+ raise ParserError, str(e)
+
+
+ def findUri(self, prefix):
+ # Walk prefix stack backwards, looking for the uri
+ # matching the specified prefix
+ stack = self.prefixStack
+ for i in range(-1, (len(self.prefixStack)+1) * -1, -1):
+ if prefix in stack[i]:
+ return stack[i][prefix]
+ return None
+
+ def gotTagStart(self, name, attributes):
+ defaultUri = None
+ localPrefixes = {}
+ attribs = {}
+ uri = None
+
+ # Pass 1 - Identify namespace decls
+ for k, v in attributes.items():
+ if k.startswith("xmlns"):
+ x, p = _splitPrefix(k)
+ if (x is None): # I.e. default declaration
+ defaultUri = v
+ else:
+ localPrefixes[p] = v
+ del attributes[k]
+
+ # Push namespace decls onto prefix stack
+ self.prefixStack.append(localPrefixes)
+
+ # Determine default namespace for this element; if there
+ # is one
+ if defaultUri is None:
+ if len(self.defaultNsStack) > 0:
+ defaultUri = self.defaultNsStack[-1]
+ else:
+ defaultUri = ''
+
+ # Fix up name
+ prefix, name = _splitPrefix(name)
+ if prefix is None: # This element is in the default namespace
+ uri = defaultUri
+ else:
+ # Find the URI for the prefix
+ uri = self.findUri(prefix)
+
+ # Pass 2 - Fix up and escape attributes
+ for k, v in attributes.items():
+ p, n = _splitPrefix(k)
+ if p is None:
+ attribs[n] = v
+ else:
+ attribs[(self.findUri(p)), n] = unescapeFromXml(v)
+
+ # Construct the actual Element object
+ e = Element((uri, name), defaultUri, attribs, localPrefixes)
+
+ # Save current default namespace
+ self.defaultNsStack.append(defaultUri)
+
+ # Document already started
+ if self.documentStarted:
+ # Starting a new packet
+ if self.currElem is None:
+ self.currElem = e
+ # Adding to existing element
+ else:
+ self.currElem = self.currElem.addChild(e)
+ # New document
+ else:
+ self.rootElem = e
+ self.documentStarted = True
+ self.DocumentStartEvent(e)
+
+ def gotText(self, data):
+ if self.currElem != None:
+ self.currElem.addContent(data)
+
+ def gotCData(self, data):
+ if self.currElem != None:
+ self.currElem.addContent(data)
+
+ def gotComment(self, data):
+ # Ignore comments for the moment
+ pass
+
+ entities = { "amp" : "&",
+ "lt" : "<",
+ "gt" : ">",
+ "apos": "'",
+ "quot": "\"" }
+
+ def gotEntityReference(self, entityRef):
+ # If this is an entity we know about, add it as content
+ # to the current element
+ if entityRef in SuxElementStream.entities:
+ self.currElem.addContent(SuxElementStream.entities[entityRef])
+
+ def gotTagEnd(self, name):
+ # Ensure the document hasn't already ended
+ if self.rootElem is None:
+ # XXX: Write more legible explanation
+ raise ParserError, "Element closed after end of document."
+
+ # Fix up name
+ prefix, name = _splitPrefix(name)
+ if prefix is None:
+ uri = self.defaultNsStack[-1]
+ else:
+ uri = self.findUri(prefix)
+
+ # End of document
+ if self.currElem is None:
+ # Ensure element name and uri matches
+ if self.rootElem.name != name or self.rootElem.uri != uri:
+ raise ParserError, "Mismatched root elements"
+ self.DocumentEndEvent()
+ self.rootElem = None
+
+ # Other elements
+ else:
+ # Ensure the tag being closed matches the name of the current
+ # element
+ if self.currElem.name != name or self.currElem.uri != uri:
+ # XXX: Write more legible explanation
+ raise ParserError, "Malformed element close"
+
+ # Pop prefix and default NS stack
+ self.prefixStack.pop()
+ self.defaultNsStack.pop()
+
+ # Check for parent null parent of current elem;
+ # that's the top of the stack
+ if self.currElem.parent is None:
+ self.currElem.parent = self.rootElem
+ self.ElementEvent(self.currElem)
+ self.currElem = None
+
+ # Anything else is just some element wrapping up
+ else:
+ self.currElem = self.currElem.parent
+
+
+class ExpatElementStream:
+ def __init__(self):
+ import pyexpat
+ self.DocumentStartEvent = None
+ self.ElementEvent = None
+ self.DocumentEndEvent = None
+ self.error = pyexpat.error
+ self.parser = pyexpat.ParserCreate("UTF-8", " ")
+ self.parser.StartElementHandler = self._onStartElement
+ self.parser.EndElementHandler = self._onEndElement
+ self.parser.CharacterDataHandler = self._onCdata
+ self.parser.StartNamespaceDeclHandler = self._onStartNamespace
+ self.parser.EndNamespaceDeclHandler = self._onEndNamespace
+ self.currElem = None
+ self.defaultNsStack = ['']
+ self.documentStarted = 0
+ self.localPrefixes = {}
+
+ def parse(self, buffer):
+ try:
+ self.parser.Parse(buffer)
+ except self.error, e:
+ raise ParserError, str(e)
+
+ def _onStartElement(self, name, attrs):
+ # Generate a qname tuple from the provided name
+ qname = name.split(" ")
+ if len(qname) == 1:
+ qname = ('', name)
+
+ # Process attributes
+ for k, v in attrs.items():
+ if k.find(" ") != -1:
+ aqname = k.split(" ")
+ attrs[(aqname[0], aqname[1])] = v
+ del attrs[k]
+
+ # Construct the new element
+ e = Element(qname, self.defaultNsStack[-1], attrs, self.localPrefixes)
+ self.localPrefixes = {}
+
+ # Document already started
+ if self.documentStarted == 1:
+ if self.currElem != None:
+ self.currElem.children.append(e)
+ e.parent = self.currElem
+ self.currElem = e
+
+ # New document
+ else:
+ self.documentStarted = 1
+ self.DocumentStartEvent(e)
+
+ def _onEndElement(self, _):
+ # Check for null current elem; end of doc
+ if self.currElem is None:
+ self.DocumentEndEvent()
+
+ # Check for parent that is None; that's
+ # the top of the stack
+ elif self.currElem.parent is None:
+ self.ElementEvent(self.currElem)
+ self.currElem = None
+
+ # Anything else is just some element in the current
+ # packet wrapping up
+ else:
+ self.currElem = self.currElem.parent
+
+ def _onCdata(self, data):
+ if self.currElem != None:
+ self.currElem.addContent(data)
+
+ def _onStartNamespace(self, prefix, uri):
+ # If this is the default namespace, put
+ # it on the stack
+ if prefix is None:
+ self.defaultNsStack.append(uri)
+ else:
+ self.localPrefixes[prefix] = uri
+
+ def _onEndNamespace(self, prefix):
+ # Remove last element on the stack
+ if prefix is None:
+ self.defaultNsStack.pop()
+
+## class FileParser(ElementStream):
+## def __init__(self):
+## ElementStream.__init__(self)
+## self.DocumentStartEvent = self.docStart
+## self.ElementEvent = self.elem
+## self.DocumentEndEvent = self.docEnd
+## self.done = 0
+
+## def docStart(self, elem):
+## self.document = elem
+
+## def elem(self, elem):
+## self.document.addChild(elem)
+
+## def docEnd(self):
+## self.done = 1
+
+## def parse(self, filename):
+## for l in open(filename).readlines():
+## self.parser.Parse(l)
+## assert self.done == 1
+## return self.document
+
+## def parseFile(filename):
+## return FileParser().parse(filename)
+
+
diff --git a/vendor/Twisted-10.0.0/twisted/words/xish/utility.py b/vendor/Twisted-10.0.0/twisted/words/xish/utility.py
new file mode 100644
index 0000000000..aae0cfce8a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/xish/utility.py
@@ -0,0 +1,372 @@
+# -*- test-case-name: twisted.words.test.test_xishutil -*-
+#
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Event Dispatching and Callback utilities.
+"""
+
+from twisted.python import log
+from twisted.words.xish import xpath
+
+class _MethodWrapper(object):
+ """
+ Internal class for tracking method calls.
+ """
+ def __init__(self, method, *args, **kwargs):
+ self.method = method
+ self.args = args
+ self.kwargs = kwargs
+
+
+ def __call__(self, *args, **kwargs):
+ nargs = self.args + args
+ nkwargs = self.kwargs.copy()
+ nkwargs.update(kwargs)
+ self.method(*nargs, **nkwargs)
+
+
+
+class CallbackList:
+ """
+ Container for callbacks.
+
+ Event queries are linked to lists of callables. When a matching event
+ occurs, these callables are called in sequence. One-time callbacks
+ are removed from the list after the first time the event was triggered.
+
+ Arguments to callbacks are split spread across two sets. The first set,
+ callback specific, is passed to C{addCallback} and is used for all
+ subsequent event triggers. The second set is passed to C{callback} and is
+ event specific. Positional arguments in the second set come after the
+ positional arguments of the first set. Keyword arguments in the second set
+ override those in the first set.
+
+ @ivar callbacks: The registered callbacks as mapping from the callable to a
+ tuple of a wrapper for that callable that keeps the
+ callback specific arguments and a boolean that signifies
+ if it is to be called only once.
+ @type callbacks: C{dict}
+ """
+
+ def __init__(self):
+ self.callbacks = {}
+
+
+ def addCallback(self, onetime, method, *args, **kwargs):
+ """
+ Add callback.
+
+ The arguments passed are used as callback specific arguments.
+
+ @param onetime: If C{True}, this callback is called at most once.
+ @type onetime: C{bool}
+ @param method: The callback callable to be added.
+ @param args: Positional arguments to the callable.
+ @type args: C{list}
+ @param kwargs: Keyword arguments to the callable.
+ @type kwargs: C{dict}
+ """
+
+ if not method in self.callbacks:
+ self.callbacks[method] = (_MethodWrapper(method, *args, **kwargs),
+ onetime)
+
+
+ def removeCallback(self, method):
+ """
+ Remove callback.
+
+ @param method: The callable to be removed.
+ """
+
+ if method in self.callbacks:
+ del self.callbacks[method]
+
+
+ def callback(self, *args, **kwargs):
+ """
+ Call all registered callbacks.
+
+ The passed arguments are event specific and augment and override
+ the callback specific arguments as described above.
+
+ @note: Exceptions raised by callbacks are trapped and logged. They will
+ not propagate up to make sure other callbacks will still be
+ called, and the event dispatching allways succeeds.
+
+ @param args: Positional arguments to the callable.
+ @type args: C{list}
+ @param kwargs: Keyword arguments to the callable.
+ @type kwargs: C{dict}
+ """
+
+ for key, (methodwrapper, onetime) in self.callbacks.items():
+ try:
+ methodwrapper(*args, **kwargs)
+ except:
+ log.err()
+
+ if onetime:
+ del self.callbacks[key]
+
+
+ def isEmpty(self):
+ """
+ Return if list of registered callbacks is empty.
+
+ @rtype: C{bool}
+ """
+
+ return len(self.callbacks) == 0
+
+
+
+class EventDispatcher:
+ """
+ Event dispatching service.
+
+ The C{EventDispatcher} allows observers to be registered for certain events
+ that are dispatched. There are two types of events: XPath events and Named
+ events.
+
+ Every dispatch is triggered by calling L{dispatch} with a data object and,
+ for named events, the name of the event.
+
+ When an XPath type event is dispatched, the associated object is assumed to
+ be an L{Element<twisted.words.xish.domish.Element>} instance, which is
+ matched against all registered XPath queries. For every match, the
+ respective observer will be called with the data object.
+
+ A named event will simply call each registered observer for that particular
+ event name, with the data object. Unlike XPath type events, the data object
+ is not restricted to L{Element<twisted.words.xish.domish.Element>}, but can
+ be anything.
+
+ When registering observers, the event that is to be observed is specified
+ using an L{xpath.XPathQuery} instance or a string. In the latter case, the
+ string can also contain the string representation of an XPath expression.
+ To distinguish these from named events, each named event should start with
+ a special prefix that is stored in C{self.prefix}. It defaults to
+ C{//event/}.
+
+ Observers registered using L{addObserver} are persistent: after the
+ observer has been triggered by a dispatch, it remains registered for a
+ possible next dispatch. If instead L{addOnetimeObserver} was used to
+ observe an event, the observer is removed from the list of observers after
+ the first observed event.
+
+ Obsevers can also prioritized, by providing an optional C{priority}
+ parameter to the L{addObserver} and L{addOnetimeObserver} methods. Higher
+ priority observers are then called before lower priority observers.
+
+ Finally, observers can be unregistered by using L{removeObserver}.
+ """
+
+ def __init__(self, eventprefix="//event/"):
+ self.prefix = eventprefix
+ self._eventObservers = {}
+ self._xpathObservers = {}
+ self._dispatchDepth = 0 # Flag indicating levels of dispatching
+ # in progress
+ self._updateQueue = [] # Queued updates for observer ops
+
+
+ def _getEventAndObservers(self, event):
+ if isinstance(event, xpath.XPathQuery):
+ # Treat as xpath
+ observers = self._xpathObservers
+ else:
+ if self.prefix == event[:len(self.prefix)]:
+ # Treat as event
+ observers = self._eventObservers
+ else:
+ # Treat as xpath
+ event = xpath.internQuery(event)
+ observers = self._xpathObservers
+
+ return event, observers
+
+
+ def addOnetimeObserver(self, event, observerfn, priority=0, *args, **kwargs):
+ """
+ Register a one-time observer for an event.
+
+ Like L{addObserver}, but is only triggered at most once. See there
+ for a description of the parameters.
+ """
+ self._addObserver(True, event, observerfn, priority, *args, **kwargs)
+
+
+ def addObserver(self, event, observerfn, priority=0, *args, **kwargs):
+ """
+ Register an observer for an event.
+
+ Each observer will be registered with a certain priority. Higher
+ priority observers get called before lower priority observers.
+
+ @param event: Name or XPath query for the event to be monitored.
+ @type event: C{str} or L{xpath.XPathQuery}.
+ @param observerfn: Function to be called when the specified event
+ has been triggered. This callable takes
+ one parameter: the data object that triggered
+ the event. When specified, the C{*args} and
+ C{**kwargs} parameters to addObserver are being used
+ as additional parameters to the registered observer
+ callable.
+ @param priority: (Optional) priority of this observer in relation to
+ other observer that match the same event. Defaults to
+ C{0}.
+ @type priority: C{int}
+ """
+ self._addObserver(False, event, observerfn, priority, *args, **kwargs)
+
+
+ def _addObserver(self, onetime, event, observerfn, priority, *args, **kwargs):
+ # If this is happening in the middle of the dispatch, queue
+ # it up for processing after the dispatch completes
+ if self._dispatchDepth > 0:
+ self._updateQueue.append(lambda:self._addObserver(onetime, event, observerfn, priority, *args, **kwargs))
+ return
+
+ event, observers = self._getEventAndObservers(event)
+
+ if priority not in observers:
+ cbl = CallbackList()
+ observers[priority] = {event: cbl}
+ else:
+ priorityObservers = observers[priority]
+ if event not in priorityObservers:
+ cbl = CallbackList()
+ observers[priority][event] = cbl
+ else:
+ cbl = priorityObservers[event]
+
+ cbl.addCallback(onetime, observerfn, *args, **kwargs)
+
+
+ def removeObserver(self, event, observerfn):
+ """
+ Remove callable as observer for an event.
+
+ The observer callable is removed for all priority levels for the
+ specified event.
+
+ @param event: Event for which the observer callable was registered.
+ @type event: C{str} or L{xpath.XPathQuery}
+ @param observerfn: Observer callable to be unregistered.
+ """
+
+ # If this is happening in the middle of the dispatch, queue
+ # it up for processing after the dispatch completes
+ if self._dispatchDepth > 0:
+ self._updateQueue.append(lambda:self.removeObserver(event, observerfn))
+ return
+
+ event, observers = self._getEventAndObservers(event)
+
+ emptyLists = []
+ for priority, priorityObservers in observers.iteritems():
+ for query, callbacklist in priorityObservers.iteritems():
+ if event == query:
+ callbacklist.removeCallback(observerfn)
+ if callbacklist.isEmpty():
+ emptyLists.append((priority, query))
+
+ for priority, query in emptyLists:
+ del observers[priority][query]
+
+
+ def dispatch(self, obj, event=None):
+ """
+ Dispatch an event.
+
+ When C{event} is C{None}, an XPath type event is triggered, and
+ C{obj} is assumed to be an instance of
+ L{Element<twisted.words.xish.domish.Element>}. Otherwise, C{event}
+ holds the name of the named event being triggered. In the latter case,
+ C{obj} can be anything.
+
+ @param obj: The object to be dispatched.
+ @param event: Optional event name.
+ @type event: C{str}
+ """
+
+ foundTarget = False
+
+ self._dispatchDepth += 1
+
+ if event != None:
+ # Named event
+ observers = self._eventObservers
+ match = lambda query, obj: query == event
+ else:
+ # XPath event
+ observers = self._xpathObservers
+ match = lambda query, obj: query.matches(obj)
+
+ priorities = observers.keys()
+ priorities.sort()
+ priorities.reverse()
+
+ emptyLists = []
+ for priority in priorities:
+ for query, callbacklist in observers[priority].iteritems():
+ if match(query, obj):
+ callbacklist.callback(obj)
+ foundTarget = True
+ if callbacklist.isEmpty():
+ emptyLists.append((priority, query))
+
+ for priority, query in emptyLists:
+ del observers[priority][query]
+
+ self._dispatchDepth -= 1
+
+ # If this is a dispatch within a dispatch, don't
+ # do anything with the updateQueue -- it needs to
+ # wait until we've back all the way out of the stack
+ if self._dispatchDepth == 0:
+ # Deal with pending update operations
+ for f in self._updateQueue:
+ f()
+ self._updateQueue = []
+
+ return foundTarget
+
+
+
+class XmlPipe(object):
+ """
+ XML stream pipe.
+
+ Connects two objects that communicate stanzas through an XML stream like
+ interface. Each of the ends of the pipe (sink and source) can be used to
+ send XML stanzas to the other side, or add observers to process XML stanzas
+ that were sent from the other side.
+
+ XML pipes are usually used in place of regular XML streams that are
+ transported over TCP. This is the reason for the use of the names source
+ and sink for both ends of the pipe. The source side corresponds with the
+ entity that initiated the TCP connection, whereas the sink corresponds with
+ the entity that accepts that connection. In this object, though, the source
+ and sink are treated equally.
+
+ Unlike Jabber
+ L{XmlStream<twisted.words.protocols.jabber.xmlstream.XmlStream>}s, the sink
+ and source objects are assumed to represent an eternal connected and
+ initialized XML stream. As such, events corresponding to connection,
+ disconnection, initialization and stream errors are not dispatched or
+ processed.
+
+ @since: 8.2
+ @ivar source: Source XML stream.
+ @ivar sink: Sink XML stream.
+ """
+
+ def __init__(self):
+ self.source = EventDispatcher()
+ self.sink = EventDispatcher()
+ self.source.send = lambda obj: self.sink.dispatch(obj)
+ self.sink.send = lambda obj: self.source.dispatch(obj)
diff --git a/vendor/Twisted-10.0.0/twisted/words/xish/xmlstream.py b/vendor/Twisted-10.0.0/twisted/words/xish/xmlstream.py
new file mode 100644
index 0000000000..90f4c8e5bc
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/xish/xmlstream.py
@@ -0,0 +1,261 @@
+# -*- test-case-name: twisted.words.test.test_xmlstream -*-
+#
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+XML Stream processing.
+
+An XML Stream is defined as a connection over which two XML documents are
+exchanged during the lifetime of the connection, one for each direction. The
+unit of interaction is a direct child element of the root element (stanza).
+
+The most prominent use of XML Streams is Jabber, but this module is generically
+usable. See Twisted Words for Jabber specific protocol support.
+
+Maintainer: Ralph Meijer
+"""
+
+from twisted.python import failure
+from twisted.internet import protocol
+from twisted.words.xish import domish, utility
+
+STREAM_CONNECTED_EVENT = intern("//event/stream/connected")
+STREAM_START_EVENT = intern("//event/stream/start")
+STREAM_END_EVENT = intern("//event/stream/end")
+STREAM_ERROR_EVENT = intern("//event/stream/error")
+
+class XmlStream(protocol.Protocol, utility.EventDispatcher):
+ """ Generic Streaming XML protocol handler.
+
+ This protocol handler will parse incoming data as XML and dispatch events
+ accordingly. Incoming stanzas can be handled by registering observers using
+ XPath-like expressions that are matched against each stanza. See
+ L{utility.EventDispatcher} for details.
+ """
+ def __init__(self):
+ utility.EventDispatcher.__init__(self)
+ self.stream = None
+ self.rawDataOutFn = None
+ self.rawDataInFn = None
+
+ def _initializeStream(self):
+ """ Sets up XML Parser. """
+ self.stream = domish.elementStream()
+ self.stream.DocumentStartEvent = self.onDocumentStart
+ self.stream.ElementEvent = self.onElement
+ self.stream.DocumentEndEvent = self.onDocumentEnd
+
+ ### --------------------------------------------------------------
+ ###
+ ### Protocol events
+ ###
+ ### --------------------------------------------------------------
+
+ def connectionMade(self):
+ """ Called when a connection is made.
+
+ Sets up the XML parser and dispatches the L{STREAM_CONNECTED_EVENT}
+ event indicating the connection has been established.
+ """
+ self._initializeStream()
+ self.dispatch(self, STREAM_CONNECTED_EVENT)
+
+ def dataReceived(self, data):
+ """ Called whenever data is received.
+
+ Passes the data to the XML parser. This can result in calls to the
+ DOM handlers. If a parse error occurs, the L{STREAM_ERROR_EVENT} event
+ is called to allow for cleanup actions, followed by dropping the
+ connection.
+ """
+ try:
+ if self.rawDataInFn:
+ self.rawDataInFn(data)
+ self.stream.parse(data)
+ except domish.ParserError:
+ self.dispatch(failure.Failure(), STREAM_ERROR_EVENT)
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ """ Called when the connection is shut down.
+
+ Dispatches the L{STREAM_END_EVENT}.
+ """
+ self.dispatch(self, STREAM_END_EVENT)
+ self.stream = None
+
+ ### --------------------------------------------------------------
+ ###
+ ### DOM events
+ ###
+ ### --------------------------------------------------------------
+
+ def onDocumentStart(self, rootElement):
+ """ Called whenever the start tag of a root element has been received.
+
+ Dispatches the L{STREAM_START_EVENT}.
+ """
+ self.dispatch(self, STREAM_START_EVENT)
+
+ def onElement(self, element):
+ """ Called whenever a direct child element of the root element has
+ been received.
+
+ Dispatches the received element.
+ """
+ self.dispatch(element)
+
+ def onDocumentEnd(self):
+ """ Called whenever the end tag of the root element has been received.
+
+ Closes the connection. This causes C{connectionLost} being called.
+ """
+ self.transport.loseConnection()
+
+ def setDispatchFn(self, fn):
+ """ Set another function to handle elements. """
+ self.stream.ElementEvent = fn
+
+ def resetDispatchFn(self):
+ """ Set the default function (C{onElement}) to handle elements. """
+ self.stream.ElementEvent = self.onElement
+
+ def send(self, obj):
+ """ Send data over the stream.
+
+ Sends the given C{obj} over the connection. C{obj} may be instances of
+ L{domish.Element}, L{unicode} and L{str}. The first two will be
+ properly serialized and/or encoded. L{str} objects must be in UTF-8
+ encoding.
+
+ Note: because it is easy to make mistakes in maintaining a properly
+ encoded L{str} object, it is advised to use L{unicode} objects
+ everywhere when dealing with XML Streams.
+
+ @param obj: Object to be sent over the stream.
+ @type obj: L{domish.Element}, L{domish} or L{str}
+
+ """
+ if domish.IElement.providedBy(obj):
+ obj = obj.toXml()
+
+ if isinstance(obj, unicode):
+ obj = obj.encode('utf-8')
+
+ if self.rawDataOutFn:
+ self.rawDataOutFn(obj)
+
+ self.transport.write(obj)
+
+
+
+class BootstrapMixin(object):
+ """
+ XmlStream factory mixin to install bootstrap event observers.
+
+ This mixin is for factories providing
+ L{IProtocolFactory<twisted.internet.interfaces.IProtocolFactory>} to make
+ sure bootstrap event observers are set up on protocols, before incoming
+ data is processed. Such protocols typically derive from
+ L{utility.EventDispatcher}, like L{XmlStream}.
+
+ You can set up bootstrap event observers using C{addBootstrap}. The
+ C{event} and C{fn} parameters correspond with the C{event} and
+ C{observerfn} arguments to L{utility.EventDispatcher.addObserver}.
+
+ @since: 8.2.
+ @ivar bootstraps: The list of registered bootstrap event observers.
+ @type bootstrap: C{list}
+ """
+
+ def __init__(self):
+ self.bootstraps = []
+
+
+ def installBootstraps(self, dispatcher):
+ """
+ Install registered bootstrap observers.
+
+ @param dispatcher: Event dispatcher to add the observers to.
+ @type dispatcher: L{utility.EventDispatcher}
+ """
+ for event, fn in self.bootstraps:
+ dispatcher.addObserver(event, fn)
+
+
+ def addBootstrap(self, event, fn):
+ """
+ Add a bootstrap event handler.
+
+ @param event: The event to register an observer for.
+ @type event: C{str} or L{xpath.XPathQuery}
+ @param fn: The observer callable to be registered.
+ """
+ self.bootstraps.append((event, fn))
+
+
+ def removeBootstrap(self, event, fn):
+ """
+ Remove a bootstrap event handler.
+
+ @param event: The event the observer is registered for.
+ @type event: C{str} or L{xpath.XPathQuery}
+ @param fn: The registered observer callable.
+ """
+ self.bootstraps.remove((event, fn))
+
+
+
+class XmlStreamFactoryMixin(BootstrapMixin):
+ """
+ XmlStream factory mixin that takes care of event handlers.
+
+ All positional and keyword arguments passed to create this factory are
+ passed on as-is to the protocol.
+
+ @ivar args: Positional arguments passed to the protocol upon instantiation.
+ @type args: C{tuple}.
+ @ivar kwargs: Keyword arguments passed to the protocol upon instantiation.
+ @type kwargs: C{dict}.
+ """
+
+ def __init__(self, *args, **kwargs):
+ BootstrapMixin.__init__(self)
+ self.args = args
+ self.kwargs = kwargs
+
+
+ def buildProtocol(self, addr):
+ """
+ Create an instance of XmlStream.
+
+ The returned instance will have bootstrap event observers registered
+ and will proceed to handle input on an incoming connection.
+ """
+ xs = self.protocol(*self.args, **self.kwargs)
+ xs.factory = self
+ self.installBootstraps(xs)
+ return xs
+
+
+
+class XmlStreamFactory(XmlStreamFactoryMixin,
+ protocol.ReconnectingClientFactory):
+ """
+ Factory for XmlStream protocol objects as a reconnection client.
+ """
+
+ protocol = XmlStream
+
+ def buildProtocol(self, addr):
+ """
+ Create a protocol instance.
+
+ Overrides L{XmlStreamFactoryMixin.buildProtocol} to work with
+ a L{ReconnectingClientFactory}. As this is called upon having an
+ connection established, we are resetting the delay for reconnection
+ attempts when the connection is lost again.
+ """
+ self.resetDelay()
+ return XmlStreamFactoryMixin.buildProtocol(self, addr)
diff --git a/vendor/Twisted-10.0.0/twisted/words/xish/xpath.py b/vendor/Twisted-10.0.0/twisted/words/xish/xpath.py
new file mode 100644
index 0000000000..9505c38d93
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/xish/xpath.py
@@ -0,0 +1,333 @@
+# -*- test-case-name: twisted.words.test.test_xpath -*-
+#
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+XPath query support.
+
+This module provides L{XPathQuery} to match
+L{domish.Element<twisted.words.xish.domish.Element>} instances against
+XPath-like expressions.
+"""
+
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+class LiteralValue(str):
+ def value(self, elem):
+ return self
+
+
+class IndexValue:
+ def __init__(self, index):
+ self.index = int(index) - 1
+
+ def value(self, elem):
+ return elem.children[self.index]
+
+
+class AttribValue:
+ def __init__(self, attribname):
+ self.attribname = attribname
+ if self.attribname == "xmlns":
+ self.value = self.value_ns
+
+ def value_ns(self, elem):
+ return elem.uri
+
+ def value(self, elem):
+ if self.attribname in elem.attributes:
+ return elem.attributes[self.attribname]
+ else:
+ return None
+
+
+class CompareValue:
+ def __init__(self, lhs, op, rhs):
+ self.lhs = lhs
+ self.rhs = rhs
+ if op == "=":
+ self.value = self._compareEqual
+ else:
+ self.value = self._compareNotEqual
+
+ def _compareEqual(self, elem):
+ return self.lhs.value(elem) == self.rhs.value(elem)
+
+ def _compareNotEqual(self, elem):
+ return self.lhs.value(elem) != self.rhs.value(elem)
+
+
+class BooleanValue:
+ """
+ Provide boolean XPath expression operators.
+
+ @ivar lhs: Left hand side expression of the operator.
+ @ivar op: The operator. One of C{'and'}, C{'or'}.
+ @ivar rhs: Right hand side expression of the operator.
+ @ivar value: Reference to the method that will calculate the value of
+ this expression given an element.
+ """
+ def __init__(self, lhs, op, rhs):
+ self.lhs = lhs
+ self.rhs = rhs
+ if op == "and":
+ self.value = self._booleanAnd
+ else:
+ self.value = self._booleanOr
+
+ def _booleanAnd(self, elem):
+ """
+ Calculate boolean and of the given expressions given an element.
+
+ @param elem: The element to calculate the value of the expression from.
+ """
+ return self.lhs.value(elem) and self.rhs.value(elem)
+
+ def _booleanOr(self, elem):
+ """
+ Calculate boolean or of the given expressions given an element.
+
+ @param elem: The element to calculate the value of the expression from.
+ """
+ return self.lhs.value(elem) or self.rhs.value(elem)
+
+
+def Function(fname):
+ """
+ Internal method which selects the function object
+ """
+ klassname = "_%s_Function" % fname
+ c = globals()[klassname]()
+ return c
+
+
+class _not_Function:
+ def __init__(self):
+ self.baseValue = None
+
+ def setParams(self, baseValue):
+ self.baseValue = baseValue
+
+ def value(self, elem):
+ return not self.baseValue.value(elem)
+
+
+class _text_Function:
+ def setParams(self):
+ pass
+
+ def value(self, elem):
+ return str(elem)
+
+
+class _Location:
+ def __init__(self):
+ self.predicates = []
+ self.elementName = None
+ self.childLocation = None
+
+ def matchesPredicates(self, elem):
+ if self.elementName != None and self.elementName != elem.name:
+ return 0
+
+ for p in self.predicates:
+ if not p.value(elem):
+ return 0
+
+ return 1
+
+ def matches(self, elem):
+ if not self.matchesPredicates(elem):
+ return 0
+
+ if self.childLocation != None:
+ for c in elem.elements():
+ if self.childLocation.matches(c):
+ return 1
+ else:
+ return 1
+
+ return 0
+
+ def queryForString(self, elem, resultbuf):
+ if not self.matchesPredicates(elem):
+ return
+
+ if self.childLocation != None:
+ for c in elem.elements():
+ self.childLocation.queryForString(c, resultbuf)
+ else:
+ resultbuf.write(str(elem))
+
+ def queryForNodes(self, elem, resultlist):
+ if not self.matchesPredicates(elem):
+ return
+
+ if self.childLocation != None:
+ for c in elem.elements():
+ self.childLocation.queryForNodes(c, resultlist)
+ else:
+ resultlist.append(elem)
+
+ def queryForStringList(self, elem, resultlist):
+ if not self.matchesPredicates(elem):
+ return
+
+ if self.childLocation != None:
+ for c in elem.elements():
+ self.childLocation.queryForStringList(c, resultlist)
+ else:
+ for c in elem.children:
+ if isinstance(c, (str, unicode)):
+ resultlist.append(c)
+
+
+class _AnyLocation:
+ def __init__(self):
+ self.predicates = []
+ self.elementName = None
+ self.childLocation = None
+
+ def matchesPredicates(self, elem):
+ for p in self.predicates:
+ if not p.value(elem):
+ return 0
+ return 1
+
+ def listParents(self, elem, parentlist):
+ if elem.parent != None:
+ self.listParents(elem.parent, parentlist)
+ parentlist.append(elem.name)
+
+ def isRootMatch(self, elem):
+ if (self.elementName == None or self.elementName == elem.name) and \
+ self.matchesPredicates(elem):
+ if self.childLocation != None:
+ for c in elem.elements():
+ if self.childLocation.matches(c):
+ return True
+ else:
+ return True
+ return False
+
+ def findFirstRootMatch(self, elem):
+ if (self.elementName == None or self.elementName == elem.name) and \
+ self.matchesPredicates(elem):
+ # Thus far, the name matches and the predicates match,
+ # now check into the children and find the first one
+ # that matches the rest of the structure
+ # the rest of the structure
+ if self.childLocation != None:
+ for c in elem.elements():
+ if self.childLocation.matches(c):
+ return c
+ return None
+ else:
+ # No children locations; this is a match!
+ return elem
+ else:
+ # Ok, predicates or name didn't match, so we need to start
+ # down each child and treat it as the root and try
+ # again
+ for c in elem.elements():
+ if self.matches(c):
+ return c
+ # No children matched...
+ return None
+
+ def matches(self, elem):
+ if self.isRootMatch(elem):
+ return True
+ else:
+ # Ok, initial element isn't an exact match, walk
+ # down each child and treat it as the root and try
+ # again
+ for c in elem.elements():
+ if self.matches(c):
+ return True
+ # No children matched...
+ return False
+
+ def queryForString(self, elem, resultbuf):
+ raise NotImplementedError(
+ "queryForString is not implemented for any location")
+
+ def queryForNodes(self, elem, resultlist):
+ # First check to see if _this_ element is a root
+ if self.isRootMatch(elem):
+ resultlist.append(elem)
+
+ # Now check each child
+ for c in elem.elements():
+ self.queryForNodes(c, resultlist)
+
+
+ def queryForStringList(self, elem, resultlist):
+ if self.isRootMatch(elem):
+ for c in elem.children:
+ if isinstance(c, (str, unicode)):
+ resultlist.append(c)
+ for c in elem.elements():
+ self.queryForStringList(c, resultlist)
+
+
+class XPathQuery:
+ def __init__(self, queryStr):
+ self.queryStr = queryStr
+ from twisted.words.xish.xpathparser import parse
+ self.baseLocation = parse('XPATH', queryStr)
+
+ def __hash__(self):
+ return self.queryStr.__hash__()
+
+ def matches(self, elem):
+ return self.baseLocation.matches(elem)
+
+ def queryForString(self, elem):
+ result = StringIO.StringIO()
+ self.baseLocation.queryForString(elem, result)
+ return result.getvalue()
+
+ def queryForNodes(self, elem):
+ result = []
+ self.baseLocation.queryForNodes(elem, result)
+ if len(result) == 0:
+ return None
+ else:
+ return result
+
+ def queryForStringList(self, elem):
+ result = []
+ self.baseLocation.queryForStringList(elem, result)
+ if len(result) == 0:
+ return None
+ else:
+ return result
+
+
+__internedQueries = {}
+
+def internQuery(queryString):
+ if queryString not in __internedQueries:
+ __internedQueries[queryString] = XPathQuery(queryString)
+ return __internedQueries[queryString]
+
+
+def matches(xpathstr, elem):
+ return internQuery(xpathstr).matches(elem)
+
+
+def queryForStringList(xpathstr, elem):
+ return internQuery(xpathstr).queryForStringList(elem)
+
+
+def queryForString(xpathstr, elem):
+ return internQuery(xpathstr).queryForString(elem)
+
+
+def queryForNodes(xpathstr, elem):
+ return internQuery(xpathstr).queryForNodes(elem)
diff --git a/vendor/Twisted-10.0.0/twisted/words/xish/xpathparser.g b/vendor/Twisted-10.0.0/twisted/words/xish/xpathparser.g
new file mode 100644
index 0000000000..4e51636655
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/xish/xpathparser.g
@@ -0,0 +1,375 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# DO NOT EDIT xpathparser.py!
+#
+# It is generated from xpathparser.g using Yapps. Make needed changes there.
+# This also means that the generated Python may not conform to Twisted's coding
+# standards.
+
+# HOWTO Generate me:
+#
+# 1.) Grab a copy of yapps2, version 2.1.1:
+# http://theory.stanford.edu/~amitp/Yapps/
+#
+# Note: Do NOT use the package in debian/ubuntu as it has incompatible
+# modifications.
+#
+# 2.) Generate the grammar:
+#
+# yapps2 xpathparser.g xpathparser.py.proto
+#
+# 3.) Edit the output to depend on the embedded runtime, not yappsrt.
+#
+# sed -e '/^import yapps/d' -e '/^[^#]/s/yappsrt\.//g' \
+# xpathparser.py.proto > xpathparser.py
+
+"""
+XPath Parser.
+
+Besides the parser code produced by Yapps, this module also defines the
+parse-time exception classes, a scanner class, a base class for parsers
+produced by Yapps, and a context class that keeps track of the parse stack.
+These have been copied from the Yapps runtime.
+"""
+
+import sys, re
+
+class SyntaxError(Exception):
+ """When we run into an unexpected token, this is the exception to use"""
+ def __init__(self, charpos=-1, msg="Bad Token", context=None):
+ Exception.__init__(self)
+ self.charpos = charpos
+ self.msg = msg
+ self.context = context
+
+ def __str__(self):
+ if self.charpos < 0: return 'SyntaxError'
+ else: return 'SyntaxError@char%s(%s)' % (repr(self.charpos), self.msg)
+
+class NoMoreTokens(Exception):
+ """Another exception object, for when we run out of tokens"""
+ pass
+
+class Scanner:
+ """Yapps scanner.
+
+ The Yapps scanner can work in context sensitive or context
+ insensitive modes. The token(i) method is used to retrieve the
+ i-th token. It takes a restrict set that limits the set of tokens
+ it is allowed to return. In context sensitive mode, this restrict
+ set guides the scanner. In context insensitive mode, there is no
+ restriction (the set is always the full set of tokens).
+
+ """
+
+ def __init__(self, patterns, ignore, input):
+ """Initialize the scanner.
+
+ @param patterns: [(terminal, uncompiled regex), ...] or C{None}
+ @param ignore: [terminal,...]
+ @param input: string
+
+ If patterns is C{None}, we assume that the subclass has defined
+ C{self.patterns} : [(terminal, compiled regex), ...]. Note that the
+ patterns parameter expects uncompiled regexes, whereas the
+ C{self.patterns} field expects compiled regexes.
+ """
+ self.tokens = [] # [(begin char pos, end char pos, token name, matched text), ...]
+ self.restrictions = []
+ self.input = input
+ self.pos = 0
+ self.ignore = ignore
+ self.first_line_number = 1
+
+ if patterns is not None:
+ # Compile the regex strings into regex objects
+ self.patterns = []
+ for terminal, regex in patterns:
+ self.patterns.append( (terminal, re.compile(regex)) )
+
+ def get_token_pos(self):
+ """Get the current token position in the input text."""
+ return len(self.tokens)
+
+ def get_char_pos(self):
+ """Get the current char position in the input text."""
+ return self.pos
+
+ def get_prev_char_pos(self, i=None):
+ """Get the previous position (one token back) in the input text."""
+ if self.pos == 0: return 0
+ if i is None: i = -1
+ return self.tokens[i][0]
+
+ def get_line_number(self):
+ """Get the line number of the current position in the input text."""
+ # TODO: make this work at any token/char position
+ return self.first_line_number + self.get_input_scanned().count('\n')
+
+ def get_column_number(self):
+ """Get the column number of the current position in the input text."""
+ s = self.get_input_scanned()
+ i = s.rfind('\n') # may be -1, but that's okay in this case
+ return len(s) - (i+1)
+
+ def get_input_scanned(self):
+ """Get the portion of the input that has been tokenized."""
+ return self.input[:self.pos]
+
+ def get_input_unscanned(self):
+ """Get the portion of the input that has not yet been tokenized."""
+ return self.input[self.pos:]
+
+ def token(self, i, restrict=None):
+ """Get the i'th token in the input.
+
+ If C{i} is one past the end, then scan for another token.
+
+ @param i: token index
+
+ @param restrict: [token, ...] or C{None}; if restrict is
+ C{None}, then any token is allowed. You may call
+ token(i) more than once. However, the restrict set
+ may never be larger than what was passed in on the
+ first call to token(i).
+ """
+ if i == len(self.tokens):
+ self.scan(restrict)
+ if i < len(self.tokens):
+ # Make sure the restriction is more restricted. This
+ # invariant is needed to avoid ruining tokenization at
+ # position i+1 and higher.
+ if restrict and self.restrictions[i]:
+ for r in restrict:
+ if r not in self.restrictions[i]:
+ raise NotImplementedError("Unimplemented: restriction set changed")
+ return self.tokens[i]
+ raise NoMoreTokens()
+
+ def __repr__(self):
+ """Print the last 10 tokens that have been scanned in"""
+ output = ''
+ for t in self.tokens[-10:]:
+ output = '%s\n (@%s) %s = %s' % (output,t[0],t[2],repr(t[3]))
+ return output
+
+ def scan(self, restrict):
+ """Should scan another token and add it to the list, self.tokens,
+ and add the restriction to self.restrictions"""
+ # Keep looking for a token, ignoring any in self.ignore
+ while 1:
+ # Search the patterns for the longest match, with earlier
+ # tokens in the list having preference
+ best_match = -1
+ best_pat = '(error)'
+ for p, regexp in self.patterns:
+ # First check to see if we're ignoring this token
+ if restrict and p not in restrict and p not in self.ignore:
+ continue
+ m = regexp.match(self.input, self.pos)
+ if m and len(m.group(0)) > best_match:
+ # We got a match that's better than the previous one
+ best_pat = p
+ best_match = len(m.group(0))
+
+ # If we didn't find anything, raise an error
+ if best_pat == '(error)' and best_match < 0:
+ msg = 'Bad Token'
+ if restrict:
+ msg = 'Trying to find one of '+', '.join(restrict)
+ raise SyntaxError(self.pos, msg)
+
+ # If we found something that isn't to be ignored, return it
+ if best_pat not in self.ignore:
+ # Create a token with this data
+ token = (self.pos, self.pos+best_match, best_pat,
+ self.input[self.pos:self.pos+best_match])
+ self.pos = self.pos + best_match
+ # Only add this token if it's not in the list
+ # (to prevent looping)
+ if not self.tokens or token != self.tokens[-1]:
+ self.tokens.append(token)
+ self.restrictions.append(restrict)
+ return
+ else:
+ # This token should be ignored ..
+ self.pos = self.pos + best_match
+
+class Parser:
+ """Base class for Yapps-generated parsers.
+
+ """
+
+ def __init__(self, scanner):
+ self._scanner = scanner
+ self._pos = 0
+
+ def _peek(self, *types):
+ """Returns the token type for lookahead; if there are any args
+ then the list of args is the set of token types to allow"""
+ tok = self._scanner.token(self._pos, types)
+ return tok[2]
+
+ def _scan(self, type):
+ """Returns the matched text, and moves to the next token"""
+ tok = self._scanner.token(self._pos, [type])
+ if tok[2] != type:
+ raise SyntaxError(tok[0], 'Trying to find '+type+' :'+ ' ,'.join(self._scanner.restrictions[self._pos]))
+ self._pos = 1 + self._pos
+ return tok[3]
+
+class Context:
+ """Class to represent the parser's call stack.
+
+ Every rule creates a Context that links to its parent rule. The
+ contexts can be used for debugging.
+
+ """
+
+ def __init__(self, parent, scanner, tokenpos, rule, args=()):
+ """Create a new context.
+
+ @param parent: Context object or C{None}
+ @param scanner: Scanner object
+ @param tokenpos: scanner token position
+ @type tokenpos: L{int}
+ @param rule: name of the rule
+ @type rule: L{str}
+ @param args: tuple listing parameters to the rule
+
+ """
+ self.parent = parent
+ self.scanner = scanner
+ self.tokenpos = tokenpos
+ self.rule = rule
+ self.args = args
+
+ def __str__(self):
+ output = ''
+ if self.parent: output = str(self.parent) + ' > '
+ output += self.rule
+ return output
+
+def print_line_with_pointer(text, p):
+ """Print the line of 'text' that includes position 'p',
+ along with a second line with a single caret (^) at position p"""
+
+ # TODO: separate out the logic for determining the line/character
+ # location from the logic for determining how to display an
+ # 80-column line to stderr.
+
+ # Now try printing part of the line
+ text = text[max(p-80, 0):p+80]
+ p = p - max(p-80, 0)
+
+ # Strip to the left
+ i = text[:p].rfind('\n')
+ j = text[:p].rfind('\r')
+ if i < 0 or (0 <= j < i): i = j
+ if 0 <= i < p:
+ p = p - i - 1
+ text = text[i+1:]
+
+ # Strip to the right
+ i = text.find('\n', p)
+ j = text.find('\r', p)
+ if i < 0 or (0 <= j < i): i = j
+ if i >= 0:
+ text = text[:i]
+
+ # Now shorten the text
+ while len(text) > 70 and p > 60:
+ # Cut off 10 chars
+ text = "..." + text[10:]
+ p = p - 7
+
+ # Now print the string, along with an indicator
+ print >>sys.stderr, '> ',text
+ print >>sys.stderr, '> ',' '*p + '^'
+
+def print_error(input, err, scanner):
+ """Print error messages, the parser stack, and the input text -- for human-readable error messages."""
+ # NOTE: this function assumes 80 columns :-(
+ # Figure out the line number
+ line_number = scanner.get_line_number()
+ column_number = scanner.get_column_number()
+ print >>sys.stderr, '%d:%d: %s' % (line_number, column_number, err.msg)
+
+ context = err.context
+ if not context:
+ print_line_with_pointer(input, err.charpos)
+
+ while context:
+ # TODO: add line number
+ print >>sys.stderr, 'while parsing %s%s:' % (context.rule, tuple(context.args))
+ print_line_with_pointer(input, context.scanner.get_prev_char_pos(context.tokenpos))
+ context = context.parent
+
+def wrap_error_reporter(parser, rule):
+ try:
+ return getattr(parser, rule)()
+ except SyntaxError, e:
+ input = parser._scanner.input
+ print_error(input, e, parser._scanner)
+ except NoMoreTokens:
+ print >>sys.stderr, 'Could not complete parsing; stopped around here:'
+ print >>sys.stderr, parser._scanner
+
+
+from twisted.words.xish.xpath import AttribValue, BooleanValue, CompareValue
+from twisted.words.xish.xpath import Function, IndexValue, LiteralValue
+from twisted.words.xish.xpath import _AnyLocation, _Location
+
+%%
+parser XPathParser:
+ ignore: "\\s+"
+ token INDEX: "[0-9]+"
+ token WILDCARD: "\*"
+ token IDENTIFIER: "[a-zA-Z][a-zA-Z0-9_\-]*"
+ token ATTRIBUTE: "\@[a-zA-Z][a-zA-Z0-9_\-]*"
+ token FUNCNAME: "[a-zA-Z][a-zA-Z0-9_]*"
+ token CMP_EQ: "\="
+ token CMP_NE: "\!\="
+ token STR_DQ: '"([^"]|(\\"))*?"'
+ token STR_SQ: "'([^']|(\\'))*?'"
+ token OP_AND: "and"
+ token OP_OR: "or"
+ token END: "$"
+
+ rule XPATH: PATH {{ result = PATH; current = result }}
+ ( PATH {{ current.childLocation = PATH; current = current.childLocation }} ) * END
+ {{ return result }}
+
+ rule PATH: ("/" {{ result = _Location() }} | "//" {{ result = _AnyLocation() }} )
+ ( IDENTIFIER {{ result.elementName = IDENTIFIER }} | WILDCARD {{ result.elementName = None }} )
+ ( "\[" PREDICATE {{ result.predicates.append(PREDICATE) }} "\]")*
+ {{ return result }}
+
+ rule PREDICATE: EXPR {{ return EXPR }} |
+ INDEX {{ return IndexValue(INDEX) }}
+
+ rule EXPR: FACTOR {{ e = FACTOR }}
+ ( BOOLOP FACTOR {{ e = BooleanValue(e, BOOLOP, FACTOR) }} )*
+ {{ return e }}
+
+ rule BOOLOP: ( OP_AND {{ return OP_AND }} | OP_OR {{ return OP_OR }} )
+
+ rule FACTOR: TERM {{ return TERM }}
+ | "\(" EXPR "\)" {{ return EXPR }}
+
+ rule TERM: VALUE {{ t = VALUE }}
+ [ CMP VALUE {{ t = CompareValue(t, CMP, VALUE) }} ]
+ {{ return t }}
+
+ rule VALUE: "@" IDENTIFIER {{ return AttribValue(IDENTIFIER) }} |
+ FUNCNAME {{ f = Function(FUNCNAME); args = [] }}
+ "\(" [ VALUE {{ args.append(VALUE) }}
+ (
+ "," VALUE {{ args.append(VALUE) }}
+ )*
+ ] "\)" {{ f.setParams(*args); return f }} |
+ STR {{ return LiteralValue(STR[1:len(STR)-1]) }}
+
+ rule CMP: (CMP_EQ {{ return CMP_EQ }} | CMP_NE {{ return CMP_NE }})
+ rule STR: (STR_DQ {{ return STR_DQ }} | STR_SQ {{ return STR_SQ }})
diff --git a/vendor/Twisted-10.0.0/twisted/words/xish/xpathparser.py b/vendor/Twisted-10.0.0/twisted/words/xish/xpathparser.py
new file mode 100644
index 0000000000..c6b235c48a
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/xish/xpathparser.py
@@ -0,0 +1,508 @@
+# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# DO NOT EDIT xpathparser.py!
+#
+# It is generated from xpathparser.g using Yapps. Make needed changes there.
+# This also means that the generated Python may not conform to Twisted's coding
+# standards.
+
+# HOWTO Generate me:
+#
+# 1.) Grab a copy of yapps2, version 2.1.1:
+# http://theory.stanford.edu/~amitp/Yapps/
+#
+# Note: Do NOT use the package in debian/ubuntu as it has incompatible
+# modifications.
+#
+# 2.) Generate the grammar:
+#
+# yapps2 xpathparser.g xpathparser.py.proto
+#
+# 3.) Edit the output to depend on the embedded runtime, not yappsrt.
+#
+# sed -e '/^import yapps/d' -e '/^[^#]/s/yappsrt\.//g' \
+# xpathparser.py.proto > xpathparser.py
+
+"""
+XPath Parser.
+
+Besides the parser code produced by Yapps, this module also defines the
+parse-time exception classes, a scanner class, a base class for parsers
+produced by Yapps, and a context class that keeps track of the parse stack.
+These have been copied from the Yapps runtime.
+"""
+
+import sys, re
+
+class SyntaxError(Exception):
+ """When we run into an unexpected token, this is the exception to use"""
+ def __init__(self, charpos=-1, msg="Bad Token", context=None):
+ Exception.__init__(self)
+ self.charpos = charpos
+ self.msg = msg
+ self.context = context
+
+ def __str__(self):
+ if self.charpos < 0: return 'SyntaxError'
+ else: return 'SyntaxError@char%s(%s)' % (repr(self.charpos), self.msg)
+
+class NoMoreTokens(Exception):
+ """Another exception object, for when we run out of tokens"""
+ pass
+
+class Scanner:
+ """Yapps scanner.
+
+ The Yapps scanner can work in context sensitive or context
+ insensitive modes. The token(i) method is used to retrieve the
+ i-th token. It takes a restrict set that limits the set of tokens
+ it is allowed to return. In context sensitive mode, this restrict
+ set guides the scanner. In context insensitive mode, there is no
+ restriction (the set is always the full set of tokens).
+
+ """
+
+ def __init__(self, patterns, ignore, input):
+ """Initialize the scanner.
+
+ @param patterns: [(terminal, uncompiled regex), ...] or C{None}
+ @param ignore: [terminal,...]
+ @param input: string
+
+ If patterns is C{None}, we assume that the subclass has defined
+ C{self.patterns} : [(terminal, compiled regex), ...]. Note that the
+ patterns parameter expects uncompiled regexes, whereas the
+ C{self.patterns} field expects compiled regexes.
+ """
+ self.tokens = [] # [(begin char pos, end char pos, token name, matched text), ...]
+ self.restrictions = []
+ self.input = input
+ self.pos = 0
+ self.ignore = ignore
+ self.first_line_number = 1
+
+ if patterns is not None:
+ # Compile the regex strings into regex objects
+ self.patterns = []
+ for terminal, regex in patterns:
+ self.patterns.append( (terminal, re.compile(regex)) )
+
+ def get_token_pos(self):
+ """Get the current token position in the input text."""
+ return len(self.tokens)
+
+ def get_char_pos(self):
+ """Get the current char position in the input text."""
+ return self.pos
+
+ def get_prev_char_pos(self, i=None):
+ """Get the previous position (one token back) in the input text."""
+ if self.pos == 0: return 0
+ if i is None: i = -1
+ return self.tokens[i][0]
+
+ def get_line_number(self):
+ """Get the line number of the current position in the input text."""
+ # TODO: make this work at any token/char position
+ return self.first_line_number + self.get_input_scanned().count('\n')
+
+ def get_column_number(self):
+ """Get the column number of the current position in the input text."""
+ s = self.get_input_scanned()
+ i = s.rfind('\n') # may be -1, but that's okay in this case
+ return len(s) - (i+1)
+
+ def get_input_scanned(self):
+ """Get the portion of the input that has been tokenized."""
+ return self.input[:self.pos]
+
+ def get_input_unscanned(self):
+ """Get the portion of the input that has not yet been tokenized."""
+ return self.input[self.pos:]
+
+ def token(self, i, restrict=None):
+ """Get the i'th token in the input.
+
+ If C{i} is one past the end, then scan for another token.
+
+ @param i: token index
+
+ @param restrict: [token, ...] or C{None}; if restrict is
+ C{None}, then any token is allowed. You may call
+ token(i) more than once. However, the restrict set
+ may never be larger than what was passed in on the
+ first call to token(i).
+ """
+ if i == len(self.tokens):
+ self.scan(restrict)
+ if i < len(self.tokens):
+ # Make sure the restriction is more restricted. This
+ # invariant is needed to avoid ruining tokenization at
+ # position i+1 and higher.
+ if restrict and self.restrictions[i]:
+ for r in restrict:
+ if r not in self.restrictions[i]:
+ raise NotImplementedError("Unimplemented: restriction set changed")
+ return self.tokens[i]
+ raise NoMoreTokens()
+
+ def __repr__(self):
+ """Print the last 10 tokens that have been scanned in"""
+ output = ''
+ for t in self.tokens[-10:]:
+ output = '%s\n (@%s) %s = %s' % (output,t[0],t[2],repr(t[3]))
+ return output
+
+ def scan(self, restrict):
+ """Should scan another token and add it to the list, self.tokens,
+ and add the restriction to self.restrictions"""
+ # Keep looking for a token, ignoring any in self.ignore
+ while 1:
+ # Search the patterns for the longest match, with earlier
+ # tokens in the list having preference
+ best_match = -1
+ best_pat = '(error)'
+ for p, regexp in self.patterns:
+ # First check to see if we're ignoring this token
+ if restrict and p not in restrict and p not in self.ignore:
+ continue
+ m = regexp.match(self.input, self.pos)
+ if m and len(m.group(0)) > best_match:
+ # We got a match that's better than the previous one
+ best_pat = p
+ best_match = len(m.group(0))
+
+ # If we didn't find anything, raise an error
+ if best_pat == '(error)' and best_match < 0:
+ msg = 'Bad Token'
+ if restrict:
+ msg = 'Trying to find one of '+', '.join(restrict)
+ raise SyntaxError(self.pos, msg)
+
+ # If we found something that isn't to be ignored, return it
+ if best_pat not in self.ignore:
+ # Create a token with this data
+ token = (self.pos, self.pos+best_match, best_pat,
+ self.input[self.pos:self.pos+best_match])
+ self.pos = self.pos + best_match
+ # Only add this token if it's not in the list
+ # (to prevent looping)
+ if not self.tokens or token != self.tokens[-1]:
+ self.tokens.append(token)
+ self.restrictions.append(restrict)
+ return
+ else:
+ # This token should be ignored ..
+ self.pos = self.pos + best_match
+
+class Parser:
+ """Base class for Yapps-generated parsers.
+
+ """
+
+ def __init__(self, scanner):
+ self._scanner = scanner
+ self._pos = 0
+
+ def _peek(self, *types):
+ """Returns the token type for lookahead; if there are any args
+ then the list of args is the set of token types to allow"""
+ tok = self._scanner.token(self._pos, types)
+ return tok[2]
+
+ def _scan(self, type):
+ """Returns the matched text, and moves to the next token"""
+ tok = self._scanner.token(self._pos, [type])
+ if tok[2] != type:
+ raise SyntaxError(tok[0], 'Trying to find '+type+' :'+ ' ,'.join(self._scanner.restrictions[self._pos]))
+ self._pos = 1 + self._pos
+ return tok[3]
+
+class Context:
+ """Class to represent the parser's call stack.
+
+ Every rule creates a Context that links to its parent rule. The
+ contexts can be used for debugging.
+
+ """
+
+ def __init__(self, parent, scanner, tokenpos, rule, args=()):
+ """Create a new context.
+
+ @param parent: Context object or C{None}
+ @param scanner: Scanner object
+ @param tokenpos: scanner token position
+ @type tokenpos: L{int}
+ @param rule: name of the rule
+ @type rule: L{str}
+ @param args: tuple listing parameters to the rule
+
+ """
+ self.parent = parent
+ self.scanner = scanner
+ self.tokenpos = tokenpos
+ self.rule = rule
+ self.args = args
+
+ def __str__(self):
+ output = ''
+ if self.parent: output = str(self.parent) + ' > '
+ output += self.rule
+ return output
+
+def print_line_with_pointer(text, p):
+ """Print the line of 'text' that includes position 'p',
+ along with a second line with a single caret (^) at position p"""
+
+ # TODO: separate out the logic for determining the line/character
+ # location from the logic for determining how to display an
+ # 80-column line to stderr.
+
+ # Now try printing part of the line
+ text = text[max(p-80, 0):p+80]
+ p = p - max(p-80, 0)
+
+ # Strip to the left
+ i = text[:p].rfind('\n')
+ j = text[:p].rfind('\r')
+ if i < 0 or (0 <= j < i): i = j
+ if 0 <= i < p:
+ p = p - i - 1
+ text = text[i+1:]
+
+ # Strip to the right
+ i = text.find('\n', p)
+ j = text.find('\r', p)
+ if i < 0 or (0 <= j < i): i = j
+ if i >= 0:
+ text = text[:i]
+
+ # Now shorten the text
+ while len(text) > 70 and p > 60:
+ # Cut off 10 chars
+ text = "..." + text[10:]
+ p = p - 7
+
+ # Now print the string, along with an indicator
+ print >>sys.stderr, '> ',text
+ print >>sys.stderr, '> ',' '*p + '^'
+
+def print_error(input, err, scanner):
+ """Print error messages, the parser stack, and the input text -- for human-readable error messages."""
+ # NOTE: this function assumes 80 columns :-(
+ # Figure out the line number
+ line_number = scanner.get_line_number()
+ column_number = scanner.get_column_number()
+ print >>sys.stderr, '%d:%d: %s' % (line_number, column_number, err.msg)
+
+ context = err.context
+ if not context:
+ print_line_with_pointer(input, err.charpos)
+
+ while context:
+ # TODO: add line number
+ print >>sys.stderr, 'while parsing %s%s:' % (context.rule, tuple(context.args))
+ print_line_with_pointer(input, context.scanner.get_prev_char_pos(context.tokenpos))
+ context = context.parent
+
+def wrap_error_reporter(parser, rule):
+ try:
+ return getattr(parser, rule)()
+ except SyntaxError, e:
+ input = parser._scanner.input
+ print_error(input, e, parser._scanner)
+ except NoMoreTokens:
+ print >>sys.stderr, 'Could not complete parsing; stopped around here:'
+ print >>sys.stderr, parser._scanner
+
+
+from twisted.words.xish.xpath import AttribValue, BooleanValue, CompareValue
+from twisted.words.xish.xpath import Function, IndexValue, LiteralValue
+from twisted.words.xish.xpath import _AnyLocation, _Location
+
+
+# Begin -- grammar generated by Yapps
+import sys, re
+
+class XPathParserScanner(Scanner):
+ patterns = [
+ ('","', re.compile(',')),
+ ('"@"', re.compile('@')),
+ ('"\\)"', re.compile('\\)')),
+ ('"\\("', re.compile('\\(')),
+ ('"\\]"', re.compile('\\]')),
+ ('"\\["', re.compile('\\[')),
+ ('"//"', re.compile('//')),
+ ('"/"', re.compile('/')),
+ ('\\s+', re.compile('\\s+')),
+ ('INDEX', re.compile('[0-9]+')),
+ ('WILDCARD', re.compile('\\*')),
+ ('IDENTIFIER', re.compile('[a-zA-Z][a-zA-Z0-9_\\-]*')),
+ ('ATTRIBUTE', re.compile('\\@[a-zA-Z][a-zA-Z0-9_\\-]*')),
+ ('FUNCNAME', re.compile('[a-zA-Z][a-zA-Z0-9_]*')),
+ ('CMP_EQ', re.compile('\\=')),
+ ('CMP_NE', re.compile('\\!\\=')),
+ ('STR_DQ', re.compile('"([^"]|(\\"))*?"')),
+ ('STR_SQ', re.compile("'([^']|(\\'))*?'")),
+ ('OP_AND', re.compile('and')),
+ ('OP_OR', re.compile('or')),
+ ('END', re.compile('$')),
+ ]
+ def __init__(self, str):
+ Scanner.__init__(self,None,['\\s+'],str)
+
+class XPathParser(Parser):
+ Context = Context
+ def XPATH(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, self._pos, 'XPATH', [])
+ PATH = self.PATH(_context)
+ result = PATH; current = result
+ while self._peek('END', '"/"', '"//"') != 'END':
+ PATH = self.PATH(_context)
+ current.childLocation = PATH; current = current.childLocation
+ if self._peek() not in ['END', '"/"', '"//"']:
+ raise SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['END', '"/"', '"//"']))
+ END = self._scan('END')
+ return result
+
+ def PATH(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, self._pos, 'PATH', [])
+ _token = self._peek('"/"', '"//"')
+ if _token == '"/"':
+ self._scan('"/"')
+ result = _Location()
+ else: # == '"//"'
+ self._scan('"//"')
+ result = _AnyLocation()
+ _token = self._peek('IDENTIFIER', 'WILDCARD')
+ if _token == 'IDENTIFIER':
+ IDENTIFIER = self._scan('IDENTIFIER')
+ result.elementName = IDENTIFIER
+ else: # == 'WILDCARD'
+ WILDCARD = self._scan('WILDCARD')
+ result.elementName = None
+ while self._peek('"\\["', 'END', '"/"', '"//"') == '"\\["':
+ self._scan('"\\["')
+ PREDICATE = self.PREDICATE(_context)
+ result.predicates.append(PREDICATE)
+ self._scan('"\\]"')
+ if self._peek() not in ['"\\["', 'END', '"/"', '"//"']:
+ raise SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['"\\["', 'END', '"/"', '"//"']))
+ return result
+
+ def PREDICATE(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, self._pos, 'PREDICATE', [])
+ _token = self._peek('INDEX', '"\\("', '"@"', 'FUNCNAME', 'STR_DQ', 'STR_SQ')
+ if _token != 'INDEX':
+ EXPR = self.EXPR(_context)
+ return EXPR
+ else: # == 'INDEX'
+ INDEX = self._scan('INDEX')
+ return IndexValue(INDEX)
+
+ def EXPR(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, self._pos, 'EXPR', [])
+ FACTOR = self.FACTOR(_context)
+ e = FACTOR
+ while self._peek('OP_AND', 'OP_OR', '"\\)"', '"\\]"') in ['OP_AND', 'OP_OR']:
+ BOOLOP = self.BOOLOP(_context)
+ FACTOR = self.FACTOR(_context)
+ e = BooleanValue(e, BOOLOP, FACTOR)
+ if self._peek() not in ['OP_AND', 'OP_OR', '"\\)"', '"\\]"']:
+ raise SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['OP_AND', 'OP_OR', '"\\)"', '"\\]"']))
+ return e
+
+ def BOOLOP(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, self._pos, 'BOOLOP', [])
+ _token = self._peek('OP_AND', 'OP_OR')
+ if _token == 'OP_AND':
+ OP_AND = self._scan('OP_AND')
+ return OP_AND
+ else: # == 'OP_OR'
+ OP_OR = self._scan('OP_OR')
+ return OP_OR
+
+ def FACTOR(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, self._pos, 'FACTOR', [])
+ _token = self._peek('"\\("', '"@"', 'FUNCNAME', 'STR_DQ', 'STR_SQ')
+ if _token != '"\\("':
+ TERM = self.TERM(_context)
+ return TERM
+ else: # == '"\\("'
+ self._scan('"\\("')
+ EXPR = self.EXPR(_context)
+ self._scan('"\\)"')
+ return EXPR
+
+ def TERM(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, self._pos, 'TERM', [])
+ VALUE = self.VALUE(_context)
+ t = VALUE
+ if self._peek('CMP_EQ', 'CMP_NE', 'OP_AND', 'OP_OR', '"\\)"', '"\\]"') in ['CMP_EQ', 'CMP_NE']:
+ CMP = self.CMP(_context)
+ VALUE = self.VALUE(_context)
+ t = CompareValue(t, CMP, VALUE)
+ return t
+
+ def VALUE(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, self._pos, 'VALUE', [])
+ _token = self._peek('"@"', 'FUNCNAME', 'STR_DQ', 'STR_SQ')
+ if _token == '"@"':
+ self._scan('"@"')
+ IDENTIFIER = self._scan('IDENTIFIER')
+ return AttribValue(IDENTIFIER)
+ elif _token == 'FUNCNAME':
+ FUNCNAME = self._scan('FUNCNAME')
+ f = Function(FUNCNAME); args = []
+ self._scan('"\\("')
+ if self._peek('"\\)"', '"@"', 'FUNCNAME', '","', 'STR_DQ', 'STR_SQ') not in ['"\\)"', '","']:
+ VALUE = self.VALUE(_context)
+ args.append(VALUE)
+ while self._peek('","', '"\\)"') == '","':
+ self._scan('","')
+ VALUE = self.VALUE(_context)
+ args.append(VALUE)
+ if self._peek() not in ['","', '"\\)"']:
+ raise SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['","', '"\\)"']))
+ self._scan('"\\)"')
+ f.setParams(*args); return f
+ else: # in ['STR_DQ', 'STR_SQ']
+ STR = self.STR(_context)
+ return LiteralValue(STR[1:len(STR)-1])
+
+ def CMP(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, self._pos, 'CMP', [])
+ _token = self._peek('CMP_EQ', 'CMP_NE')
+ if _token == 'CMP_EQ':
+ CMP_EQ = self._scan('CMP_EQ')
+ return CMP_EQ
+ else: # == 'CMP_NE'
+ CMP_NE = self._scan('CMP_NE')
+ return CMP_NE
+
+ def STR(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, self._pos, 'STR', [])
+ _token = self._peek('STR_DQ', 'STR_SQ')
+ if _token == 'STR_DQ':
+ STR_DQ = self._scan('STR_DQ')
+ return STR_DQ
+ else: # == 'STR_SQ'
+ STR_SQ = self._scan('STR_SQ')
+ return STR_SQ
+
+
+def parse(rule, text):
+ P = XPathParser(XPathParserScanner(text))
+ return wrap_error_reporter(P, rule)
+
+if __name__ == '__main__':
+ from sys import argv, stdin
+ if len(argv) >= 2:
+ if len(argv) >= 3:
+ f = open(argv[2],'r')
+ else:
+ f = stdin
+ print parse(argv[1], f.read())
+ else: print >>sys.stderr, 'Args: <rule> [<filename>]'
+# End -- grammar generated by Yapps
diff --git a/vendor/Twisted-10.0.0/twisted/words/xmpproutertap.py b/vendor/Twisted-10.0.0/twisted/words/xmpproutertap.py
new file mode 100644
index 0000000000..2c66c2a9f0
--- /dev/null
+++ b/vendor/Twisted-10.0.0/twisted/words/xmpproutertap.py
@@ -0,0 +1,30 @@
+# -*- test-case-name: twisted.words.test.test_xmpproutertap -*-
+#
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application import strports
+from twisted.python import usage
+from twisted.words.protocols.jabber import component
+
+class Options(usage.Options):
+ optParameters = [
+ ('port', None, 'tcp:5347:interface=127.0.0.1',
+ 'Port components connect to'),
+ ('secret', None, 'secret', 'Router secret'),
+ ]
+
+ optFlags = [
+ ('verbose', 'v', 'Log traffic'),
+ ]
+
+
+
+def makeService(config):
+ router = component.Router()
+ factory = component.XMPPComponentServerFactory(router, config['secret'])
+
+ if config['verbose']:
+ factory.logTraffic = True
+
+ return strports.service(config['port'], factory)
diff --git a/vendor/amqplib/__init__.py b/vendor/amqplib/__init__.py
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/vendor/amqplib/__init__.py
@@ -0,0 +1 @@
+
diff --git a/vendor/amqplib/client_0_8/__init__.py b/vendor/amqplib/client_0_8/__init__.py
new file mode 100644
index 0000000000..4306dea326
--- /dev/null
+++ b/vendor/amqplib/client_0_8/__init__.py
@@ -0,0 +1,35 @@
+"""
+AMQP Client implementing the 0-8 spec.
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+#
+# Pull in the public items from the various sub-modules
+#
+from basic_message import *
+from connection import *
+from exceptions import *
+
+__all__ = [
+ 'Connection',
+ 'Channel', # here mainly so it shows in in pydoc
+ 'Message',
+ 'AMQPException',
+ 'AMQPConnectionException',
+ 'AMQPChannelException',
+ ]
diff --git a/vendor/amqplib/client_0_8/abstract_channel.py b/vendor/amqplib/client_0_8/abstract_channel.py
new file mode 100644
index 0000000000..74c8b3fa1d
--- /dev/null
+++ b/vendor/amqplib/client_0_8/abstract_channel.py
@@ -0,0 +1,114 @@
+"""
+Code common to Connection and Channel objects.
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+from serialization import AMQPWriter
+
+__all__ = [
+ 'AbstractChannel',
+ ]
+
+
+class AbstractChannel(object):
+ """
+ Superclass for both the Connection, which is treated
+ as channel 0, and other user-created Channel objects.
+
+ The subclasses must have a _METHOD_MAP class property, mapping
+ between AMQP method signatures and Python methods.
+
+ """
+ def __init__(self, connection, channel_id):
+ self.connection = connection
+ self.channel_id = channel_id
+ connection.channels[channel_id] = self
+ self.method_queue = [] # Higher level queue for methods
+ self.auto_decode = False
+
+
+ def __enter__(self):
+ """
+ Support for Python >= 2.5 'with' statements.
+
+ """
+ return self
+
+
+ def __exit__(self, type, value, traceback):
+ """
+ Support for Python >= 2.5 'with' statements.
+
+ """
+ self.close()
+
+
+ def _send_method(self, method_sig, args='', content=None):
+ """
+ Send a method for our channel.
+
+ """
+ if isinstance(args, AMQPWriter):
+ args = args.getvalue()
+
+ self.connection.method_writer.write_method(self.channel_id,
+ method_sig, args, content)
+
+
+ def close(self):
+ """
+ Close this Channel or Connection
+
+ """
+ raise NotImplementedError('Must be overriden in subclass')
+
+
+
+ def wait(self, allowed_methods=None):
+ """
+ Wait for a method that matches our allowed_methods parameter (the
+ default value of None means match any method), and dispatch to it.
+
+ """
+ method_sig, args, content = self.connection._wait_method(
+ self.channel_id, allowed_methods)
+
+ if content \
+ and self.auto_decode \
+ and hasattr(content, 'content_encoding'):
+ try:
+ content.body = content.body.decode(content.content_encoding)
+ except Exception:
+ pass
+
+ amqp_method = self._METHOD_MAP.get(method_sig, None)
+
+ if amqp_method is None:
+ raise Exception('Unknown AMQP method (%d, %d)' % method_sig)
+
+ if content is None:
+ return amqp_method(self, args)
+ else:
+ return amqp_method(self, args, content)
+
+
+ #
+ # Placeholder, the concrete implementations will have to
+ # supply their own versions of _METHOD_MAP
+ #
+ _METHOD_MAP = {}
diff --git a/vendor/amqplib/client_0_8/basic_message.py b/vendor/amqplib/client_0_8/basic_message.py
new file mode 100644
index 0000000000..c970634e63
--- /dev/null
+++ b/vendor/amqplib/client_0_8/basic_message.py
@@ -0,0 +1,137 @@
+"""
+Messages for AMQP
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+
+from serialization import GenericContent
+
+__all__ = [
+ 'Message',
+ ]
+
+
+class Message(GenericContent):
+ """
+ A Message for use with the Channnel.basic_* methods.
+
+ """
+ #
+ # Instances of this class have these attributes, which
+ # are passed back and forth as message properties between
+ # client and server
+ #
+ PROPERTIES = [
+ ('content_type', 'shortstr'),
+ ('content_encoding', 'shortstr'),
+ ('application_headers', 'table'),
+ ('delivery_mode', 'octet'),
+ ('priority', 'octet'),
+ ('correlation_id', 'shortstr'),
+ ('reply_to', 'shortstr'),
+ ('expiration', 'shortstr'),
+ ('message_id', 'shortstr'),
+ ('timestamp', 'timestamp'),
+ ('type', 'shortstr'),
+ ('user_id', 'shortstr'),
+ ('app_id', 'shortstr'),
+ ('cluster_id', 'shortstr')
+ ]
+
+ def __init__(self, body='', children=None, **properties):
+ """
+ Expected arg types
+
+ body: string
+ children: (not supported)
+
+ Keyword properties may include:
+
+ content_type: shortstr
+ MIME content type
+
+ content_encoding: shortstr
+ MIME content encoding
+
+ application_headers: table
+ Message header field table, a dict with string keys,
+ and string | int | Decimal | datetime | dict values.
+
+ delivery_mode: octet
+ Non-persistent (1) or persistent (2)
+
+ priority: octet
+ The message priority, 0 to 9
+
+ correlation_id: shortstr
+ The application correlation identifier
+
+ reply_to: shortstr
+ The destination to reply to
+
+ expiration: shortstr
+ Message expiration specification
+
+ message_id: shortstr
+ The application message identifier
+
+ timestamp: datetime.datetime
+ The message timestamp
+
+ type: shortstr
+ The message type name
+
+ user_id: shortstr
+ The creating user id
+
+ app_id: shortstr
+ The creating application id
+
+ cluster_id: shortstr
+ Intra-cluster routing identifier
+
+ Unicode bodies are encoded according to the 'content_encoding'
+ argument. If that's None, it's set to 'UTF-8' automatically.
+
+ example:
+
+ msg = Message('hello world',
+ content_type='text/plain',
+ application_headers={'foo': 7})
+
+ """
+ if isinstance(body, unicode):
+ if properties.get('content_encoding', None) is None:
+ properties['content_encoding'] = 'UTF-8'
+ self.body = body.encode(properties['content_encoding'])
+ else:
+ self.body = body
+
+ super(Message, self).__init__(**properties)
+
+
+ def __eq__(self, other):
+ """
+ Check if the properties and bodies of this Message and another
+ Message are the same.
+
+ Received messages may contain a 'delivery_info' attribute,
+ which isn't compared.
+
+ """
+ return super(Message, self).__eq__(other) and (self.body == other.body)
diff --git a/vendor/amqplib/client_0_8/channel.py b/vendor/amqplib/client_0_8/channel.py
new file mode 100644
index 0000000000..8de0b220ac
--- /dev/null
+++ b/vendor/amqplib/client_0_8/channel.py
@@ -0,0 +1,2602 @@
+"""
+AMQP 0-8 Channels
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+import logging
+from Queue import Queue
+
+from abstract_channel import AbstractChannel
+from exceptions import *
+from serialization import AMQPWriter
+
+__all__ = [
+ 'Channel', # here mainly so it shows in in pydoc
+ ]
+
+AMQP_LOGGER = logging.getLogger('amqplib')
+
+
+class Channel(AbstractChannel):
+ """
+ work with channels
+
+ The channel class provides methods for a client to establish a
+ virtual connection - a channel - to a server and for both peers to
+ operate the virtual connection thereafter.
+
+ GRAMMAR:
+
+ channel = open-channel *use-channel close-channel
+ open-channel = C:OPEN S:OPEN-OK
+ use-channel = C:FLOW S:FLOW-OK
+ / S:FLOW C:FLOW-OK
+ / S:ALERT
+ / functional-class
+ close-channel = C:CLOSE S:CLOSE-OK
+ / S:CLOSE C:CLOSE-OK
+
+ """
+ def __init__(self, connection, channel_id=None, auto_decode=True):
+ """
+ Create a channel bound to a connection and using the specified
+ numeric channel_id, and open on the server.
+
+ The 'auto_decode' parameter (defaults to True), indicates
+ whether the library should attempt to decode the body
+ of Messages to a Unicode string if there's a 'content_encoding'
+ property for the message. If there's no 'content_encoding'
+ property, or the decode raises an Exception, the plain string
+ is left as the message body.
+
+ """
+ if channel_id is None:
+ channel_id = connection._get_free_channel_id()
+ AMQP_LOGGER.debug('using channel_id: %d' % channel_id)
+
+ super(Channel, self).__init__(connection, channel_id)
+
+ self.default_ticket = 0
+ self.is_open = False
+ self.active = True # Flow control
+ self.alerts = Queue()
+ self.returned_messages = Queue()
+ self.callbacks = {}
+ self.auto_decode = auto_decode
+
+ self._x_open()
+
+
+ def _do_close(self):
+ """
+ Tear down this object, after we've agreed to close with the server.
+
+ """
+ AMQP_LOGGER.debug('Closed channel #%d' % self.channel_id)
+ self.is_open = False
+ del self.connection.channels[self.channel_id]
+ self.channel_id = self.connection = None
+ self.callbacks = {}
+
+
+ #################
+
+ def _alert(self, args):
+ """
+ This method allows the server to send a non-fatal warning to
+ the client. This is used for methods that are normally
+ asynchronous and thus do not have confirmations, and for which
+ the server may detect errors that need to be reported. Fatal
+ errors are handled as channel or connection exceptions; non-
+ fatal errors are sent through this method.
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ details: table
+
+ detailed information for warning
+
+ A set of fields that provide more information about
+ the problem. The meaning of these fields are defined
+ on a per-reply-code basis (TO BE DEFINED).
+
+ """
+ reply_code = args.read_short()
+ reply_text = args.read_shortstr()
+ details = args.read_table()
+
+ self.alerts.put((reply_code, reply_text, details))
+
+
+ def close(self, reply_code=0, reply_text='', method_sig=(0, 0)):
+ """
+ request a channel close
+
+ This method indicates that the sender wants to close the
+ channel. This may be due to internal conditions (e.g. a forced
+ shut-down) or due to an error handling a specific method, i.e.
+ an exception. When a close is due to an exception, the sender
+ provides the class and method id of the method which caused
+ the exception.
+
+ RULE:
+
+ After sending this method any received method except
+ Channel.Close-OK MUST be discarded.
+
+ RULE:
+
+ The peer sending this method MAY use a counter or timeout
+ to detect failure of the other peer to respond correctly
+ with Channel.Close-OK..
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ class_id: short
+
+ failing method class
+
+ When the close is provoked by a method exception, this
+ is the class of the method.
+
+ method_id: short
+
+ failing method ID
+
+ When the close is provoked by a method exception, this
+ is the ID of the method.
+
+ """
+ if not self.is_open:
+ # already closed
+ return
+
+ args = AMQPWriter()
+ args.write_short(reply_code)
+ args.write_shortstr(reply_text)
+ args.write_short(method_sig[0]) # class_id
+ args.write_short(method_sig[1]) # method_id
+ self._send_method((20, 40), args)
+ return self.wait(allowed_methods=[
+ (20, 41), # Channel.close_ok
+ ])
+
+
+ def _close(self, args):
+ """
+ request a channel close
+
+ This method indicates that the sender wants to close the
+ channel. This may be due to internal conditions (e.g. a forced
+ shut-down) or due to an error handling a specific method, i.e.
+ an exception. When a close is due to an exception, the sender
+ provides the class and method id of the method which caused
+ the exception.
+
+ RULE:
+
+ After sending this method any received method except
+ Channel.Close-OK MUST be discarded.
+
+ RULE:
+
+ The peer sending this method MAY use a counter or timeout
+ to detect failure of the other peer to respond correctly
+ with Channel.Close-OK..
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ class_id: short
+
+ failing method class
+
+ When the close is provoked by a method exception, this
+ is the class of the method.
+
+ method_id: short
+
+ failing method ID
+
+ When the close is provoked by a method exception, this
+ is the ID of the method.
+
+ """
+ reply_code = args.read_short()
+ reply_text = args.read_shortstr()
+ class_id = args.read_short()
+ method_id = args.read_short()
+
+# self.close_ok()
+
+
+# def close_ok(self):
+# """
+# confirm a channel close
+#
+# This method confirms a Channel.Close method and tells the
+# recipient that it is safe to release resources for the channel
+# and close the socket.
+#
+# RULE:
+#
+# A peer that detects a socket closure without having
+# received a Channel.Close-Ok handshake method SHOULD log
+# the error.
+#
+# """
+ self._send_method((20, 41))
+ self._do_close()
+
+ raise AMQPChannelException(reply_code, reply_text,
+ (class_id, method_id))
+
+
+ def _close_ok(self, args):
+ """
+ confirm a channel close
+
+ This method confirms a Channel.Close method and tells the
+ recipient that it is safe to release resources for the channel
+ and close the socket.
+
+ RULE:
+
+ A peer that detects a socket closure without having
+ received a Channel.Close-Ok handshake method SHOULD log
+ the error.
+
+ """
+ self._do_close()
+
+
+ def flow(self, active):
+ """
+ enable/disable flow from peer
+
+ This method asks the peer to pause or restart the flow of
+ content data. This is a simple flow-control mechanism that a
+ peer can use to avoid oveflowing its queues or otherwise
+ finding itself receiving more messages than it can process.
+ Note that this method is not intended for window control. The
+ peer that receives a request to stop sending content should
+ finish sending the current content, if any, and then wait
+ until it receives a Flow restart method.
+
+ RULE:
+
+ When a new channel is opened, it is active. Some
+ applications assume that channels are inactive until
+ started. To emulate this behaviour a client MAY open the
+ channel, then pause it.
+
+ RULE:
+
+ When sending content data in multiple frames, a peer
+ SHOULD monitor the channel for incoming methods and
+ respond to a Channel.Flow as rapidly as possible.
+
+ RULE:
+
+ A peer MAY use the Channel.Flow method to throttle
+ incoming content data for internal reasons, for example,
+ when exchangeing data over a slower connection.
+
+ RULE:
+
+ The peer that requests a Channel.Flow method MAY
+ disconnect and/or ban a peer that does not respect the
+ request.
+
+ PARAMETERS:
+ active: boolean
+
+ start/stop content frames
+
+ If True, the peer starts sending content frames. If
+ False, the peer stops sending content frames.
+
+ """
+ args = AMQPWriter()
+ args.write_bit(active)
+ self._send_method((20, 20), args)
+ return self.wait(allowed_methods=[
+ (20, 21), # Channel.flow_ok
+ ])
+
+
+ def _flow(self, args):
+ """
+ enable/disable flow from peer
+
+ This method asks the peer to pause or restart the flow of
+ content data. This is a simple flow-control mechanism that a
+ peer can use to avoid oveflowing its queues or otherwise
+ finding itself receiving more messages than it can process.
+ Note that this method is not intended for window control. The
+ peer that receives a request to stop sending content should
+ finish sending the current content, if any, and then wait
+ until it receives a Flow restart method.
+
+ RULE:
+
+ When a new channel is opened, it is active. Some
+ applications assume that channels are inactive until
+ started. To emulate this behaviour a client MAY open the
+ channel, then pause it.
+
+ RULE:
+
+ When sending content data in multiple frames, a peer
+ SHOULD monitor the channel for incoming methods and
+ respond to a Channel.Flow as rapidly as possible.
+
+ RULE:
+
+ A peer MAY use the Channel.Flow method to throttle
+ incoming content data for internal reasons, for example,
+ when exchangeing data over a slower connection.
+
+ RULE:
+
+ The peer that requests a Channel.Flow method MAY
+ disconnect and/or ban a peer that does not respect the
+ request.
+
+ PARAMETERS:
+ active: boolean
+
+ start/stop content frames
+
+ If True, the peer starts sending content frames. If
+ False, the peer stops sending content frames.
+
+ """
+ self.active = args.read_bit()
+
+ self._x_flow_ok(self.active)
+
+
+ def _x_flow_ok(self, active):
+ """
+ confirm a flow method
+
+ Confirms to the peer that a flow command was received and
+ processed.
+
+ PARAMETERS:
+ active: boolean
+
+ current flow setting
+
+ Confirms the setting of the processed flow method:
+ True means the peer will start sending or continue
+ to send content frames; False means it will not.
+
+ """
+ args = AMQPWriter()
+ args.write_bit(active)
+ self._send_method((20, 21), args)
+
+
+ def _flow_ok(self, args):
+ """
+ confirm a flow method
+
+ Confirms to the peer that a flow command was received and
+ processed.
+
+ PARAMETERS:
+ active: boolean
+
+ current flow setting
+
+ Confirms the setting of the processed flow method:
+ True means the peer will start sending or continue
+ to send content frames; False means it will not.
+
+ """
+ return args.read_bit()
+
+
+ def _x_open(self, out_of_band=''):
+ """
+ open a channel for use
+
+ This method opens a virtual connection (a channel).
+
+ RULE:
+
+ This method MUST NOT be called when the channel is already
+ open.
+
+ PARAMETERS:
+ out_of_band: shortstr
+
+ out-of-band settings
+
+ Configures out-of-band transfers on this channel. The
+ syntax and meaning of this field will be formally
+ defined at a later date.
+
+ """
+ if self.is_open:
+ return
+
+ args = AMQPWriter()
+ args.write_shortstr(out_of_band)
+ self._send_method((20, 10), args)
+ return self.wait(allowed_methods=[
+ (20, 11), # Channel.open_ok
+ ])
+
+
+ def _open_ok(self, args):
+ """
+ signal that the channel is ready
+
+ This method signals to the client that the channel is ready
+ for use.
+
+ """
+ self.is_open = True
+ AMQP_LOGGER.debug('Channel open')
+
+
+ #############
+ #
+ # Access
+ #
+ #
+ # work with access tickets
+ #
+ # The protocol control access to server resources using access
+ # tickets. A client must explicitly request access tickets before
+ # doing work. An access ticket grants a client the right to use a
+ # specific set of resources - called a "realm" - in specific ways.
+ #
+ # GRAMMAR:
+ #
+ # access = C:REQUEST S:REQUEST-OK
+ #
+ #
+
+ def access_request(self, realm, exclusive=False,
+ passive=False, active=False, write=False, read=False):
+ """
+ request an access ticket
+
+ This method requests an access ticket for an access realm. The
+ server responds by granting the access ticket. If the client
+ does not have access rights to the requested realm this causes
+ a connection exception. Access tickets are a per-channel
+ resource.
+
+ RULE:
+
+ The realm name MUST start with either "/data" (for
+ application resources) or "/admin" (for server
+ administration resources). If the realm starts with any
+ other path, the server MUST raise a connection exception
+ with reply code 403 (access refused).
+
+ RULE:
+
+ The server MUST implement the /data realm and MAY
+ implement the /admin realm. The mapping of resources to
+ realms is not defined in the protocol - this is a server-
+ side configuration issue.
+
+ PARAMETERS:
+ realm: shortstr
+
+ name of requested realm
+
+ RULE:
+
+ If the specified realm is not known to the server,
+ the server must raise a channel exception with
+ reply code 402 (invalid path).
+
+ exclusive: boolean
+
+ request exclusive access
+
+ Request exclusive access to the realm. If the server
+ cannot grant this - because there are other active
+ tickets for the realm - it raises a channel exception.
+
+ passive: boolean
+
+ request passive access
+
+ Request message passive access to the specified access
+ realm. Passive access lets a client get information
+ about resources in the realm but not to make any
+ changes to them.
+
+ active: boolean
+
+ request active access
+
+ Request message active access to the specified access
+ realm. Acvtive access lets a client get create and
+ delete resources in the realm.
+
+ write: boolean
+
+ request write access
+
+ Request write access to the specified access realm.
+ Write access lets a client publish messages to all
+ exchanges in the realm.
+
+ read: boolean
+
+ request read access
+
+ Request read access to the specified access realm.
+ Read access lets a client consume messages from queues
+ in the realm.
+
+ The most recently requested ticket is used as the channel's
+ default ticket for any method that requires a ticket.
+
+ """
+ args = AMQPWriter()
+ args.write_shortstr(realm)
+ args.write_bit(exclusive)
+ args.write_bit(passive)
+ args.write_bit(active)
+ args.write_bit(write)
+ args.write_bit(read)
+ self._send_method((30, 10), args)
+ return self.wait(allowed_methods=[
+ (30, 11), # Channel.access_request_ok
+ ])
+
+
+ def _access_request_ok(self, args):
+ """
+ grant access to server resources
+
+ This method provides the client with an access ticket. The
+ access ticket is valid within the current channel and for the
+ lifespan of the channel.
+
+ RULE:
+
+ The client MUST NOT use access tickets except within the
+ same channel as originally granted.
+
+ RULE:
+
+ The server MUST isolate access tickets per channel and
+ treat an attempt by a client to mix these as a connection
+ exception.
+
+ PARAMETERS:
+ ticket: short
+
+ """
+ self.default_ticket = args.read_short()
+ return self.default_ticket
+
+
+ #############
+ #
+ # Exchange
+ #
+ #
+ # work with exchanges
+ #
+ # Exchanges match and distribute messages across queues.
+ # Exchanges can be configured in the server or created at runtime.
+ #
+ # GRAMMAR:
+ #
+ # exchange = C:DECLARE S:DECLARE-OK
+ # / C:DELETE S:DELETE-OK
+ #
+ # RULE:
+ #
+ # The server MUST implement the direct and fanout exchange
+ # types, and predeclare the corresponding exchanges named
+ # amq.direct and amq.fanout in each virtual host. The server
+ # MUST also predeclare a direct exchange to act as the default
+ # exchange for content Publish methods and for default queue
+ # bindings.
+ #
+ # RULE:
+ #
+ # The server SHOULD implement the topic exchange type, and
+ # predeclare the corresponding exchange named amq.topic in
+ # each virtual host.
+ #
+ # RULE:
+ #
+ # The server MAY implement the system exchange type, and
+ # predeclare the corresponding exchanges named amq.system in
+ # each virtual host. If the client attempts to bind a queue to
+ # the system exchange, the server MUST raise a connection
+ # exception with reply code 507 (not allowed).
+ #
+ # RULE:
+ #
+ # The default exchange MUST be defined as internal, and be
+ # inaccessible to the client except by specifying an empty
+ # exchange name in a content Publish method. That is, the
+ # server MUST NOT let clients make explicit bindings to this
+ # exchange.
+ #
+ #
+
+ def exchange_declare(self, exchange, type, passive=False, durable=False,
+ auto_delete=True, internal=False, nowait=False,
+ arguments=None, ticket=None):
+ """
+ declare exchange, create if needed
+
+ This method creates an exchange if it does not already exist,
+ and if the exchange exists, verifies that it is of the correct
+ and expected class.
+
+ RULE:
+
+ The server SHOULD support a minimum of 16 exchanges per
+ virtual host and ideally, impose no limit except as
+ defined by available resources.
+
+ PARAMETERS:
+ exchange: shortstr
+
+ RULE:
+
+ Exchange names starting with "amq." are reserved
+ for predeclared and standardised exchanges. If
+ the client attempts to create an exchange starting
+ with "amq.", the server MUST raise a channel
+ exception with reply code 403 (access refused).
+
+ type: shortstr
+
+ exchange type
+
+ Each exchange belongs to one of a set of exchange
+ types implemented by the server. The exchange types
+ define the functionality of the exchange - i.e. how
+ messages are routed through it. It is not valid or
+ meaningful to attempt to change the type of an
+ existing exchange.
+
+ RULE:
+
+ If the exchange already exists with a different
+ type, the server MUST raise a connection exception
+ with a reply code 507 (not allowed).
+
+ RULE:
+
+ If the server does not support the requested
+ exchange type it MUST raise a connection exception
+ with a reply code 503 (command invalid).
+
+ passive: boolean
+
+ do not create exchange
+
+ If set, the server will not create the exchange. The
+ client can use this to check whether an exchange
+ exists without modifying the server state.
+
+ RULE:
+
+ If set, and the exchange does not already exist,
+ the server MUST raise a channel exception with
+ reply code 404 (not found).
+
+ durable: boolean
+
+ request a durable exchange
+
+ If set when creating a new exchange, the exchange will
+ be marked as durable. Durable exchanges remain active
+ when a server restarts. Non-durable exchanges
+ (transient exchanges) are purged if/when a server
+ restarts.
+
+ RULE:
+
+ The server MUST support both durable and transient
+ exchanges.
+
+ RULE:
+
+ The server MUST ignore the durable field if the
+ exchange already exists.
+
+ auto_delete: boolean
+
+ auto-delete when unused
+
+ If set, the exchange is deleted when all queues have
+ finished using it.
+
+ RULE:
+
+ The server SHOULD allow for a reasonable delay
+ between the point when it determines that an
+ exchange is not being used (or no longer used),
+ and the point when it deletes the exchange. At
+ the least it must allow a client to create an
+ exchange and then bind a queue to it, with a small
+ but non-zero delay between these two actions.
+
+ RULE:
+
+ The server MUST ignore the auto-delete field if
+ the exchange already exists.
+
+ internal: boolean
+
+ create internal exchange
+
+ If set, the exchange may not be used directly by
+ publishers, but only when bound to other exchanges.
+ Internal exchanges are used to construct wiring that
+ is not visible to applications.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ arguments: table
+
+ arguments for declaration
+
+ A set of arguments for the declaration. The syntax and
+ semantics of these arguments depends on the server
+ implementation. This field is ignored if passive is
+ True.
+
+ ticket: short
+
+ When a client defines a new exchange, this belongs to
+ the access realm of the ticket used. All further work
+ done with that exchange must be done with an access
+ ticket for the same realm.
+
+ RULE:
+
+ The client MUST provide a valid access ticket
+ giving "active" access to the realm in which the
+ exchange exists or will be created, or "passive"
+ access if the if-exists flag is set.
+
+ """
+ if arguments is None:
+ arguments = {}
+
+ args = AMQPWriter()
+ if ticket is not None:
+ args.write_short(ticket)
+ else:
+ args.write_short(self.default_ticket)
+ args.write_shortstr(exchange)
+ args.write_shortstr(type)
+ args.write_bit(passive)
+ args.write_bit(durable)
+ args.write_bit(auto_delete)
+ args.write_bit(internal)
+ args.write_bit(nowait)
+ args.write_table(arguments)
+ self._send_method((40, 10), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (40, 11), # Channel.exchange_declare_ok
+ ])
+
+
+ def _exchange_declare_ok(self, args):
+ """
+ confirms an exchange declaration
+
+ This method confirms a Declare method and confirms the name of
+ the exchange, essential for automatically-named exchanges.
+
+ """
+ pass
+
+
+ def exchange_delete(self, exchange, if_unused=False,
+ nowait=False, ticket=None):
+ """
+ delete an exchange
+
+ This method deletes an exchange. When an exchange is deleted
+ all queue bindings on the exchange are cancelled.
+
+ PARAMETERS:
+ exchange: shortstr
+
+ RULE:
+
+ The exchange MUST exist. Attempting to delete a
+ non-existing exchange causes a channel exception.
+
+ if_unused: boolean
+
+ delete only if unused
+
+ If set, the server will only delete the exchange if it
+ has no queue bindings. If the exchange has queue
+ bindings the server does not delete it but raises a
+ channel exception instead.
+
+ RULE:
+
+ If set, the server SHOULD delete the exchange but
+ only if it has no queue bindings.
+
+ RULE:
+
+ If set, the server SHOULD raise a channel
+ exception if the exchange is in use.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ ticket: short
+
+ RULE:
+
+ The client MUST provide a valid access ticket
+ giving "active" access rights to the exchange's
+ access realm.
+
+ """
+ args = AMQPWriter()
+ if ticket is not None:
+ args.write_short(ticket)
+ else:
+ args.write_short(self.default_ticket)
+ args.write_shortstr(exchange)
+ args.write_bit(if_unused)
+ args.write_bit(nowait)
+ self._send_method((40, 20), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (40, 21), # Channel.exchange_delete_ok
+ ])
+
+
+ def _exchange_delete_ok(self, args):
+ """
+ confirm deletion of an exchange
+
+ This method confirms the deletion of an exchange.
+
+ """
+ pass
+
+
+ #############
+ #
+ # Queue
+ #
+ #
+ # work with queues
+ #
+ # Queues store and forward messages. Queues can be configured in
+ # the server or created at runtime. Queues must be attached to at
+ # least one exchange in order to receive messages from publishers.
+ #
+ # GRAMMAR:
+ #
+ # queue = C:DECLARE S:DECLARE-OK
+ # / C:BIND S:BIND-OK
+ # / C:PURGE S:PURGE-OK
+ # / C:DELETE S:DELETE-OK
+ #
+ # RULE:
+ #
+ # A server MUST allow any content class to be sent to any
+ # queue, in any mix, and queue and delivery these content
+ # classes independently. Note that all methods that fetch
+ # content off queues are specific to a given content class.
+ #
+ #
+
+ def queue_bind(self, queue, exchange, routing_key='',
+ nowait=False, arguments=None, ticket=None):
+ """
+ bind queue to an exchange
+
+ This method binds a queue to an exchange. Until a queue is
+ bound it will not receive any messages. In a classic
+ messaging model, store-and-forward queues are bound to a dest
+ exchange and subscription queues are bound to a dest_wild
+ exchange.
+
+ RULE:
+
+ A server MUST allow ignore duplicate bindings - that is,
+ two or more bind methods for a specific queue, with
+ identical arguments - without treating these as an error.
+
+ RULE:
+
+ If a bind fails, the server MUST raise a connection
+ exception.
+
+ RULE:
+
+ The server MUST NOT allow a durable queue to bind to a
+ transient exchange. If the client attempts this the server
+ MUST raise a channel exception.
+
+ RULE:
+
+ Bindings for durable queues are automatically durable and
+ the server SHOULD restore such bindings after a server
+ restart.
+
+ RULE:
+
+ If the client attempts to an exchange that was declared as
+ internal, the server MUST raise a connection exception
+ with reply code 530 (not allowed).
+
+ RULE:
+
+ The server SHOULD support at least 4 bindings per queue,
+ and ideally, impose no limit except as defined by
+ available resources.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to bind. If the queue
+ name is empty, refers to the current queue for the
+ channel, which is the last declared queue.
+
+ RULE:
+
+ If the client did not previously declare a queue,
+ and the queue name in this method is empty, the
+ server MUST raise a connection exception with
+ reply code 530 (not allowed).
+
+ RULE:
+
+ If the queue does not exist the server MUST raise
+ a channel exception with reply code 404 (not
+ found).
+
+ exchange: shortstr
+
+ The name of the exchange to bind to.
+
+ RULE:
+
+ If the exchange does not exist the server MUST
+ raise a channel exception with reply code 404 (not
+ found).
+
+ routing_key: shortstr
+
+ message routing key
+
+ Specifies the routing key for the binding. The
+ routing key is used for routing messages depending on
+ the exchange configuration. Not all exchanges use a
+ routing key - refer to the specific exchange
+ documentation. If the routing key is empty and the
+ queue name is empty, the routing key will be the
+ current queue for the channel, which is the last
+ declared queue.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ arguments: table
+
+ arguments for binding
+
+ A set of arguments for the binding. The syntax and
+ semantics of these arguments depends on the exchange
+ class.
+
+ ticket: short
+
+ The client provides a valid access ticket giving
+ "active" access rights to the queue's access realm.
+
+ """
+ if arguments is None:
+ arguments = {}
+
+ args = AMQPWriter()
+ if ticket is not None:
+ args.write_short(ticket)
+ else:
+ args.write_short(self.default_ticket)
+ args.write_shortstr(queue)
+ args.write_shortstr(exchange)
+ args.write_shortstr(routing_key)
+ args.write_bit(nowait)
+ args.write_table(arguments)
+ self._send_method((50, 20), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (50, 21), # Channel.queue_bind_ok
+ ])
+
+
+ def _queue_bind_ok(self, args):
+ """
+ confirm bind successful
+
+ This method confirms that the bind was successful.
+
+ """
+ pass
+
+
+ def queue_declare(self, queue='', passive=False, durable=False,
+ exclusive=False, auto_delete=True, nowait=False,
+ arguments=None, ticket=None):
+ """
+ declare queue, create if needed
+
+ This method creates or checks a queue. When creating a new
+ queue the client can specify various properties that control
+ the durability of the queue and its contents, and the level of
+ sharing for the queue.
+
+ RULE:
+
+ The server MUST create a default binding for a newly-
+ created queue to the default exchange, which is an
+ exchange of type 'direct'.
+
+ RULE:
+
+ The server SHOULD support a minimum of 256 queues per
+ virtual host and ideally, impose no limit except as
+ defined by available resources.
+
+ PARAMETERS:
+ queue: shortstr
+
+ RULE:
+
+ The queue name MAY be empty, in which case the
+ server MUST create a new queue with a unique
+ generated name and return this to the client in
+ the Declare-Ok method.
+
+ RULE:
+
+ Queue names starting with "amq." are reserved for
+ predeclared and standardised server queues. If
+ the queue name starts with "amq." and the passive
+ option is False, the server MUST raise a connection
+ exception with reply code 403 (access refused).
+
+ passive: boolean
+
+ do not create queue
+
+ If set, the server will not create the queue. The
+ client can use this to check whether a queue exists
+ without modifying the server state.
+
+ RULE:
+
+ If set, and the queue does not already exist, the
+ server MUST respond with a reply code 404 (not
+ found) and raise a channel exception.
+
+ durable: boolean
+
+ request a durable queue
+
+ If set when creating a new queue, the queue will be
+ marked as durable. Durable queues remain active when
+ a server restarts. Non-durable queues (transient
+ queues) are purged if/when a server restarts. Note
+ that durable queues do not necessarily hold persistent
+ messages, although it does not make sense to send
+ persistent messages to a transient queue.
+
+ RULE:
+
+ The server MUST recreate the durable queue after a
+ restart.
+
+ RULE:
+
+ The server MUST support both durable and transient
+ queues.
+
+ RULE:
+
+ The server MUST ignore the durable field if the
+ queue already exists.
+
+ exclusive: boolean
+
+ request an exclusive queue
+
+ Exclusive queues may only be consumed from by the
+ current connection. Setting the 'exclusive' flag
+ always implies 'auto-delete'.
+
+ RULE:
+
+ The server MUST support both exclusive (private)
+ and non-exclusive (shared) queues.
+
+ RULE:
+
+ The server MUST raise a channel exception if
+ 'exclusive' is specified and the queue already
+ exists and is owned by a different connection.
+
+ auto_delete: boolean
+
+ auto-delete queue when unused
+
+ If set, the queue is deleted when all consumers have
+ finished using it. Last consumer can be cancelled
+ either explicitly or because its channel is closed. If
+ there was no consumer ever on the queue, it won't be
+ deleted.
+
+ RULE:
+
+ The server SHOULD allow for a reasonable delay
+ between the point when it determines that a queue
+ is not being used (or no longer used), and the
+ point when it deletes the queue. At the least it
+ must allow a client to create a queue and then
+ create a consumer to read from it, with a small
+ but non-zero delay between these two actions. The
+ server should equally allow for clients that may
+ be disconnected prematurely, and wish to re-
+ consume from the same queue without losing
+ messages. We would recommend a configurable
+ timeout, with a suitable default value being one
+ minute.
+
+ RULE:
+
+ The server MUST ignore the auto-delete field if
+ the queue already exists.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ arguments: table
+
+ arguments for declaration
+
+ A set of arguments for the declaration. The syntax and
+ semantics of these arguments depends on the server
+ implementation. This field is ignored if passive is
+ True.
+
+ ticket: short
+
+ When a client defines a new queue, this belongs to the
+ access realm of the ticket used. All further work
+ done with that queue must be done with an access
+ ticket for the same realm.
+
+ The client provides a valid access ticket giving
+ "active" access to the realm in which the queue exists
+ or will be created, or "passive" access if the if-
+ exists flag is set.
+
+ Returns a tuple containing 3 items:
+ the name of the queue (essential for automatically-named queues)
+ message count
+ consumer count
+
+ """
+ if arguments is None:
+ arguments = {}
+
+ args = AMQPWriter()
+ if ticket is not None:
+ args.write_short(ticket)
+ else:
+ args.write_short(self.default_ticket)
+ args.write_shortstr(queue)
+ args.write_bit(passive)
+ args.write_bit(durable)
+ args.write_bit(exclusive)
+ args.write_bit(auto_delete)
+ args.write_bit(nowait)
+ args.write_table(arguments)
+ self._send_method((50, 10), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (50, 11), # Channel.queue_declare_ok
+ ])
+
+
+ def _queue_declare_ok(self, args):
+ """
+ confirms a queue definition
+
+ This method confirms a Declare method and confirms the name of
+ the queue, essential for automatically-named queues.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Reports the name of the queue. If the server generated
+ a queue name, this field contains that name.
+
+ message_count: long
+
+ number of messages in queue
+
+ Reports the number of messages in the queue, which
+ will be zero for newly-created queues.
+
+ consumer_count: long
+
+ number of consumers
+
+ Reports the number of active consumers for the queue.
+ Note that consumers can suspend activity
+ (Channel.Flow) in which case they do not appear in
+ this count.
+
+ """
+ queue = args.read_shortstr()
+ message_count = args.read_long()
+ consumer_count = args.read_long()
+
+ return queue, message_count, consumer_count
+
+
+ def queue_delete(self, queue='', if_unused=False, if_empty=False,
+ nowait=False, ticket=None):
+ """
+ delete a queue
+
+ This method deletes a queue. When a queue is deleted any
+ pending messages are sent to a dead-letter queue if this is
+ defined in the server configuration, and all consumers on the
+ queue are cancelled.
+
+ RULE:
+
+ The server SHOULD use a dead-letter queue to hold messages
+ that were pending on a deleted queue, and MAY provide
+ facilities for a system administrator to move these
+ messages back to an active queue.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to delete. If the
+ queue name is empty, refers to the current queue for
+ the channel, which is the last declared queue.
+
+ RULE:
+
+ If the client did not previously declare a queue,
+ and the queue name in this method is empty, the
+ server MUST raise a connection exception with
+ reply code 530 (not allowed).
+
+ RULE:
+
+ The queue must exist. Attempting to delete a non-
+ existing queue causes a channel exception.
+
+ if_unused: boolean
+
+ delete only if unused
+
+ If set, the server will only delete the queue if it
+ has no consumers. If the queue has consumers the
+ server does does not delete it but raises a channel
+ exception instead.
+
+ RULE:
+
+ The server MUST respect the if-unused flag when
+ deleting a queue.
+
+ if_empty: boolean
+
+ delete only if empty
+
+ If set, the server will only delete the queue if it
+ has no messages. If the queue is not empty the server
+ raises a channel exception.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ ticket: short
+
+ The client provides a valid access ticket giving
+ "active" access rights to the queue's access realm.
+
+ """
+ args = AMQPWriter()
+ if ticket is not None:
+ args.write_short(ticket)
+ else:
+ args.write_short(self.default_ticket)
+
+ args.write_shortstr(queue)
+ args.write_bit(if_unused)
+ args.write_bit(if_empty)
+ args.write_bit(nowait)
+ self._send_method((50, 40), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (50, 41), # Channel.queue_delete_ok
+ ])
+
+
+ def _queue_delete_ok(self, args):
+ """
+ confirm deletion of a queue
+
+ This method confirms the deletion of a queue.
+
+ PARAMETERS:
+ message_count: long
+
+ number of messages purged
+
+ Reports the number of messages purged.
+
+ """
+ return args.read_long()
+
+
+ def queue_purge(self, queue='', nowait=False, ticket=None):
+ """
+ purge a queue
+
+ This method removes all messages from a queue. It does not
+ cancel consumers. Purged messages are deleted without any
+ formal "undo" mechanism.
+
+ RULE:
+
+ A call to purge MUST result in an empty queue.
+
+ RULE:
+
+ On transacted channels the server MUST not purge messages
+ that have already been sent to a client but not yet
+ acknowledged.
+
+ RULE:
+
+ The server MAY implement a purge queue or log that allows
+ system administrators to recover accidentally-purged
+ messages. The server SHOULD NOT keep purged messages in
+ the same storage spaces as the live messages since the
+ volumes of purged messages may get very large.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to purge. If the
+ queue name is empty, refers to the current queue for
+ the channel, which is the last declared queue.
+
+ RULE:
+
+ If the client did not previously declare a queue,
+ and the queue name in this method is empty, the
+ server MUST raise a connection exception with
+ reply code 530 (not allowed).
+
+ RULE:
+
+ The queue must exist. Attempting to purge a non-
+ existing queue causes a channel exception.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ ticket: short
+
+ The access ticket must be for the access realm that
+ holds the queue.
+
+ RULE:
+
+ The client MUST provide a valid access ticket
+ giving "read" access rights to the queue's access
+ realm. Note that purging a queue is equivalent to
+ reading all messages and discarding them.
+
+ if nowait is False, returns a message_count
+
+ """
+ args = AMQPWriter()
+ if ticket is not None:
+ args.write_short(ticket)
+ else:
+ args.write_short(self.default_ticket)
+ args.write_shortstr(queue)
+ args.write_bit(nowait)
+ self._send_method((50, 30), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (50, 31), # Channel.queue_purge_ok
+ ])
+
+
+ def _queue_purge_ok(self, args):
+ """
+ confirms a queue purge
+
+ This method confirms the purge of a queue.
+
+ PARAMETERS:
+ message_count: long
+
+ number of messages purged
+
+ Reports the number of messages purged.
+
+ """
+ return args.read_long()
+
+
+ #############
+ #
+ # Basic
+ #
+ #
+ # work with basic content
+ #
+ # The Basic class provides methods that support an industry-
+ # standard messaging model.
+ #
+ # GRAMMAR:
+ #
+ # basic = C:QOS S:QOS-OK
+ # / C:CONSUME S:CONSUME-OK
+ # / C:CANCEL S:CANCEL-OK
+ # / C:PUBLISH content
+ # / S:RETURN content
+ # / S:DELIVER content
+ # / C:GET ( S:GET-OK content / S:GET-EMPTY )
+ # / C:ACK
+ # / C:REJECT
+ #
+ # RULE:
+ #
+ # The server SHOULD respect the persistent property of basic
+ # messages and SHOULD make a best-effort to hold persistent
+ # basic messages on a reliable storage mechanism.
+ #
+ # RULE:
+ #
+ # The server MUST NOT discard a persistent basic message in
+ # case of a queue overflow. The server MAY use the
+ # Channel.Flow method to slow or stop a basic message
+ # publisher when necessary.
+ #
+ # RULE:
+ #
+ # The server MAY overflow non-persistent basic messages to
+ # persistent storage and MAY discard or dead-letter non-
+ # persistent basic messages on a priority basis if the queue
+ # size exceeds some configured limit.
+ #
+ # RULE:
+ #
+ # The server MUST implement at least 2 priority levels for
+ # basic messages, where priorities 0-4 and 5-9 are treated as
+ # two distinct levels. The server MAY implement up to 10
+ # priority levels.
+ #
+ # RULE:
+ #
+ # The server MUST deliver messages of the same priority in
+ # order irrespective of their individual persistence.
+ #
+ # RULE:
+ #
+ # The server MUST support both automatic and explicit
+ # acknowledgements on Basic content.
+ #
+
+ def basic_ack(self, delivery_tag, multiple=False):
+ """
+ acknowledge one or more messages
+
+ This method acknowledges one or more messages delivered via
+ the Deliver or Get-Ok methods. The client can ask to confirm
+ a single message or a set of messages up to and including a
+ specific message.
+
+ PARAMETERS:
+ delivery_tag: longlong
+
+ server-assigned delivery tag
+
+ The server-assigned and channel-specific delivery tag
+
+ RULE:
+
+ The delivery tag is valid only within the channel
+ from which the message was received. I.e. a client
+ MUST NOT receive a message on one channel and then
+ acknowledge it on another.
+
+ RULE:
+
+ The server MUST NOT use a zero value for delivery
+ tags. Zero is reserved for client use, meaning "all
+ messages so far received".
+
+ multiple: boolean
+
+ acknowledge multiple messages
+
+ If set to True, the delivery tag is treated as "up to
+ and including", so that the client can acknowledge
+ multiple messages with a single method. If set to
+ False, the delivery tag refers to a single message.
+ If the multiple field is True, and the delivery tag
+ is zero, tells the server to acknowledge all
+ outstanding mesages.
+
+ RULE:
+
+ The server MUST validate that a non-zero delivery-
+ tag refers to an delivered message, and raise a
+ channel exception if this is not the case.
+
+ """
+ args = AMQPWriter()
+ args.write_longlong(delivery_tag)
+ args.write_bit(multiple)
+ self._send_method((60, 80), args)
+
+
+ def basic_cancel(self, consumer_tag, nowait=False):
+ """
+ end a queue consumer
+
+ This method cancels a consumer. This does not affect already
+ delivered messages, but it does mean the server will not send
+ any more messages for that consumer. The client may receive
+ an abitrary number of messages in between sending the cancel
+ method and receiving the cancel-ok reply.
+
+ RULE:
+
+ If the queue no longer exists when the client sends a
+ cancel command, or the consumer has been cancelled for
+ other reasons, this command has no effect.
+
+ PARAMETERS:
+ consumer_tag: shortstr
+
+ consumer tag
+
+ Identifier for the consumer, valid within the current
+ connection.
+
+ RULE:
+
+ The consumer tag is valid only within the channel
+ from which the consumer was created. I.e. a client
+ MUST NOT create a consumer in one channel and then
+ use it in another.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ """
+ args = AMQPWriter()
+ args.write_shortstr(consumer_tag)
+ args.write_bit(nowait)
+ self._send_method((60, 30), args)
+ return self.wait(allowed_methods=[
+ (60, 31), # Channel.basic_cancel_ok
+ ])
+
+
+ def _basic_cancel_ok(self, args):
+ """
+ confirm a cancelled consumer
+
+ This method confirms that the cancellation was completed.
+
+ PARAMETERS:
+ consumer_tag: shortstr
+
+ consumer tag
+
+ Identifier for the consumer, valid within the current
+ connection.
+
+ RULE:
+
+ The consumer tag is valid only within the channel
+ from which the consumer was created. I.e. a client
+ MUST NOT create a consumer in one channel and then
+ use it in another.
+
+ """
+ consumer_tag = args.read_shortstr()
+ del self.callbacks[consumer_tag]
+
+
+ def basic_consume(self, queue='', consumer_tag='', no_local=False,
+ no_ack=False, exclusive=False, nowait=False,
+ callback=None, ticket=None):
+ """
+ start a queue consumer
+
+ This method asks the server to start a "consumer", which is a
+ transient request for messages from a specific queue.
+ Consumers last as long as the channel they were created on, or
+ until the client cancels them.
+
+ RULE:
+
+ The server SHOULD support at least 16 consumers per queue,
+ unless the queue was declared as private, and ideally,
+ impose no limit except as defined by available resources.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to consume from. If
+ the queue name is null, refers to the current queue
+ for the channel, which is the last declared queue.
+
+ RULE:
+
+ If the client did not previously declare a queue,
+ and the queue name in this method is empty, the
+ server MUST raise a connection exception with
+ reply code 530 (not allowed).
+
+ consumer_tag: shortstr
+
+ Specifies the identifier for the consumer. The
+ consumer tag is local to a connection, so two clients
+ can use the same consumer tags. If this field is empty
+ the server will generate a unique tag.
+
+ RULE:
+
+ The tag MUST NOT refer to an existing consumer. If
+ the client attempts to create two consumers with
+ the same non-empty tag the server MUST raise a
+ connection exception with reply code 530 (not
+ allowed).
+
+ no_local: boolean
+
+ do not deliver own messages
+
+ If the no-local field is set the server will not send
+ messages to the client that published them.
+
+ no_ack: boolean
+
+ no acknowledgement needed
+
+ If this field is set the server does not expect
+ acknowledgments for messages. That is, when a message
+ is delivered to the client the server automatically and
+ silently acknowledges it on behalf of the client. This
+ functionality increases performance but at the cost of
+ reliability. Messages can get lost if a client dies
+ before it can deliver them to the application.
+
+ exclusive: boolean
+
+ request exclusive access
+
+ Request exclusive consumer access, meaning only this
+ consumer can access the queue.
+
+ RULE:
+
+ If the server cannot grant exclusive access to the
+ queue when asked, - because there are other
+ consumers active - it MUST raise a channel
+ exception with return code 403 (access refused).
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ callback: Python callable
+
+ function/method called with each delivered message
+
+ For each message delivered by the broker, the
+ callable will be called with a Message object
+ as the single argument. If no callable is specified,
+ messages are quietly discarded, no_ack should probably
+ be set to True in that case.
+
+ ticket: short
+
+ RULE:
+
+ The client MUST provide a valid access ticket
+ giving "read" access rights to the realm for the
+ queue.
+
+ """
+ args = AMQPWriter()
+ if ticket is not None:
+ args.write_short(ticket)
+ else:
+ args.write_short(self.default_ticket)
+ args.write_shortstr(queue)
+ args.write_shortstr(consumer_tag)
+ args.write_bit(no_local)
+ args.write_bit(no_ack)
+ args.write_bit(exclusive)
+ args.write_bit(nowait)
+ self._send_method((60, 20), args)
+
+ if not nowait:
+ consumer_tag = self.wait(allowed_methods=[
+ (60, 21), # Channel.basic_consume_ok
+ ])
+
+ self.callbacks[consumer_tag] = callback
+
+ return consumer_tag
+
+
+ def _basic_consume_ok(self, args):
+ """
+ confirm a new consumer
+
+ The server provides the client with a consumer tag, which is
+ used by the client for methods called on the consumer at a
+ later stage.
+
+ PARAMETERS:
+ consumer_tag: shortstr
+
+ Holds the consumer tag specified by the client or
+ provided by the server.
+
+ """
+ return args.read_shortstr()
+
+
+ def _basic_deliver(self, args, msg):
+ """
+ notify the client of a consumer message
+
+ This method delivers a message to the client, via a consumer.
+ In the asynchronous message delivery model, the client starts
+ a consumer using the Consume method, then the server responds
+ with Deliver methods as and when messages arrive for that
+ consumer.
+
+ RULE:
+
+ The server SHOULD track the number of times a message has
+ been delivered to clients and when a message is
+ redelivered a certain number of times - e.g. 5 times -
+ without being acknowledged, the server SHOULD consider the
+ message to be unprocessable (possibly causing client
+ applications to abort), and move the message to a dead
+ letter queue.
+
+ PARAMETERS:
+ consumer_tag: shortstr
+
+ consumer tag
+
+ Identifier for the consumer, valid within the current
+ connection.
+
+ RULE:
+
+ The consumer tag is valid only within the channel
+ from which the consumer was created. I.e. a client
+ MUST NOT create a consumer in one channel and then
+ use it in another.
+
+ delivery_tag: longlong
+
+ server-assigned delivery tag
+
+ The server-assigned and channel-specific delivery tag
+
+ RULE:
+
+ The delivery tag is valid only within the channel
+ from which the message was received. I.e. a client
+ MUST NOT receive a message on one channel and then
+ acknowledge it on another.
+
+ RULE:
+
+ The server MUST NOT use a zero value for delivery
+ tags. Zero is reserved for client use, meaning "all
+ messages so far received".
+
+ redelivered: boolean
+
+ message is being redelivered
+
+ This indicates that the message has been previously
+ delivered to this or another client.
+
+ exchange: shortstr
+
+ Specifies the name of the exchange that the message
+ was originally published to.
+
+ routing_key: shortstr
+
+ Message routing key
+
+ Specifies the routing key name specified when the
+ message was published.
+
+ """
+ consumer_tag = args.read_shortstr()
+ delivery_tag = args.read_longlong()
+ redelivered = args.read_bit()
+ exchange = args.read_shortstr()
+ routing_key = args.read_shortstr()
+
+ msg.delivery_info = {
+ 'channel': self,
+ 'consumer_tag': consumer_tag,
+ 'delivery_tag': delivery_tag,
+ 'redelivered': redelivered,
+ 'exchange': exchange,
+ 'routing_key': routing_key,
+ }
+
+ func = self.callbacks.get(consumer_tag, None)
+ if func is not None:
+ func(msg)
+
+
+ def basic_get(self, queue='', no_ack=False, ticket=None):
+ """
+ direct access to a queue
+
+ This method provides a direct access to the messages in a
+ queue using a synchronous dialogue that is designed for
+ specific types of application where synchronous functionality
+ is more important than performance.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to consume from. If
+ the queue name is null, refers to the current queue
+ for the channel, which is the last declared queue.
+
+ RULE:
+
+ If the client did not previously declare a queue,
+ and the queue name in this method is empty, the
+ server MUST raise a connection exception with
+ reply code 530 (not allowed).
+
+ no_ack: boolean
+
+ no acknowledgement needed
+
+ If this field is set the server does not expect
+ acknowledgments for messages. That is, when a message
+ is delivered to the client the server automatically and
+ silently acknowledges it on behalf of the client. This
+ functionality increases performance but at the cost of
+ reliability. Messages can get lost if a client dies
+ before it can deliver them to the application.
+
+ ticket: short
+
+ RULE:
+
+ The client MUST provide a valid access ticket
+ giving "read" access rights to the realm for the
+ queue.
+
+ Non-blocking, returns a message object, or None.
+
+ """
+ args = AMQPWriter()
+ if ticket is not None:
+ args.write_short(ticket)
+ else:
+ args.write_short(self.default_ticket)
+ args.write_shortstr(queue)
+ args.write_bit(no_ack)
+ self._send_method((60, 70), args)
+ return self.wait(allowed_methods=[
+ (60, 71), # Channel.basic_get_ok
+ (60, 72), # Channel.basic_get_empty
+ ])
+
+
+ def _basic_get_empty(self, args):
+ """
+ indicate no messages available
+
+ This method tells the client that the queue has no messages
+ available for the client.
+
+ PARAMETERS:
+ cluster_id: shortstr
+
+ Cluster id
+
+ For use by cluster applications, should not be used by
+ client applications.
+
+ """
+ cluster_id = args.read_shortstr()
+
+
+ def _basic_get_ok(self, args, msg):
+ """
+ provide client with a message
+
+ This method delivers a message to the client following a get
+ method. A message delivered by 'get-ok' must be acknowledged
+ unless the no-ack option was set in the get method.
+
+ PARAMETERS:
+ delivery_tag: longlong
+
+ server-assigned delivery tag
+
+ The server-assigned and channel-specific delivery tag
+
+ RULE:
+
+ The delivery tag is valid only within the channel
+ from which the message was received. I.e. a client
+ MUST NOT receive a message on one channel and then
+ acknowledge it on another.
+
+ RULE:
+
+ The server MUST NOT use a zero value for delivery
+ tags. Zero is reserved for client use, meaning "all
+ messages so far received".
+
+ redelivered: boolean
+
+ message is being redelivered
+
+ This indicates that the message has been previously
+ delivered to this or another client.
+
+ exchange: shortstr
+
+ Specifies the name of the exchange that the message
+ was originally published to. If empty, the message
+ was published to the default exchange.
+
+ routing_key: shortstr
+
+ Message routing key
+
+ Specifies the routing key name specified when the
+ message was published.
+
+ message_count: long
+
+ number of messages pending
+
+ This field reports the number of messages pending on
+ the queue, excluding the message being delivered.
+ Note that this figure is indicative, not reliable, and
+ can change arbitrarily as messages are added to the
+ queue and removed by other clients.
+
+ """
+ delivery_tag = args.read_longlong()
+ redelivered = args.read_bit()
+ exchange = args.read_shortstr()
+ routing_key = args.read_shortstr()
+ message_count = args.read_long()
+
+ msg.delivery_info = {
+ 'delivery_tag': delivery_tag,
+ 'redelivered': redelivered,
+ 'exchange': exchange,
+ 'routing_key': routing_key,
+ 'message_count': message_count
+ }
+
+ return msg
+
+
+ def basic_publish(self, msg, exchange='', routing_key='',
+ mandatory=False, immediate=False, ticket=None):
+ """
+ publish a message
+
+ This method publishes a message to a specific exchange. The
+ message will be routed to queues as defined by the exchange
+ configuration and distributed to any active consumers when the
+ transaction, if any, is committed.
+
+ PARAMETERS:
+ exchange: shortstr
+
+ Specifies the name of the exchange to publish to. The
+ exchange name can be empty, meaning the default
+ exchange. If the exchange name is specified, and that
+ exchange does not exist, the server will raise a
+ channel exception.
+
+ RULE:
+
+ The server MUST accept a blank exchange name to
+ mean the default exchange.
+
+ RULE:
+
+ If the exchange was declared as an internal
+ exchange, the server MUST raise a channel
+ exception with a reply code 403 (access refused).
+
+ RULE:
+
+ The exchange MAY refuse basic content in which
+ case it MUST raise a channel exception with reply
+ code 540 (not implemented).
+
+ routing_key: shortstr
+
+ Message routing key
+
+ Specifies the routing key for the message. The
+ routing key is used for routing messages depending on
+ the exchange configuration.
+
+ mandatory: boolean
+
+ indicate mandatory routing
+
+ This flag tells the server how to react if the message
+ cannot be routed to a queue. If this flag is True, the
+ server will return an unroutable message with a Return
+ method. If this flag is False, the server silently
+ drops the message.
+
+ RULE:
+
+ The server SHOULD implement the mandatory flag.
+
+ immediate: boolean
+
+ request immediate delivery
+
+ This flag tells the server how to react if the message
+ cannot be routed to a queue consumer immediately. If
+ this flag is set, the server will return an
+ undeliverable message with a Return method. If this
+ flag is zero, the server will queue the message, but
+ with no guarantee that it will ever be consumed.
+
+ RULE:
+
+ The server SHOULD implement the immediate flag.
+
+ ticket: short
+
+ RULE:
+
+ The client MUST provide a valid access ticket
+ giving "write" access rights to the access realm
+ for the exchange.
+
+ """
+ args = AMQPWriter()
+ if ticket is not None:
+ args.write_short(ticket)
+ else:
+ args.write_short(self.default_ticket)
+ args.write_shortstr(exchange)
+ args.write_shortstr(routing_key)
+ args.write_bit(mandatory)
+ args.write_bit(immediate)
+
+ self._send_method((60, 40), args, msg)
+
+
+ def basic_qos(self, prefetch_size, prefetch_count, a_global):
+ """
+ specify quality of service
+
+ This method requests a specific quality of service. The QoS
+ can be specified for the current channel or for all channels
+ on the connection. The particular properties and semantics of
+ a qos method always depend on the content class semantics.
+ Though the qos method could in principle apply to both peers,
+ it is currently meaningful only for the server.
+
+ PARAMETERS:
+ prefetch_size: long
+
+ prefetch window in octets
+
+ The client can request that messages be sent in
+ advance so that when the client finishes processing a
+ message, the following message is already held
+ locally, rather than needing to be sent down the
+ channel. Prefetching gives a performance improvement.
+ This field specifies the prefetch window size in
+ octets. The server will send a message in advance if
+ it is equal to or smaller in size than the available
+ prefetch size (and also falls into other prefetch
+ limits). May be set to zero, meaning "no specific
+ limit", although other prefetch limits may still
+ apply. The prefetch-size is ignored if the no-ack
+ option is set.
+
+ RULE:
+
+ The server MUST ignore this setting when the
+ client is not processing any messages - i.e. the
+ prefetch size does not limit the transfer of
+ single messages to a client, only the sending in
+ advance of more messages while the client still
+ has one or more unacknowledged messages.
+
+ prefetch_count: short
+
+ prefetch window in messages
+
+ Specifies a prefetch window in terms of whole
+ messages. This field may be used in combination with
+ the prefetch-size field; a message will only be sent
+ in advance if both prefetch windows (and those at the
+ channel and connection level) allow it. The prefetch-
+ count is ignored if the no-ack option is set.
+
+ RULE:
+
+ The server MAY send less data in advance than
+ allowed by the client's specified prefetch windows
+ but it MUST NOT send more.
+
+ a_global: boolean
+
+ apply to entire connection
+
+ By default the QoS settings apply to the current
+ channel only. If this field is set, they are applied
+ to the entire connection.
+
+ """
+ args = AMQPWriter()
+ args.write_long(prefetch_size)
+ args.write_short(prefetch_count)
+ args.write_bit(a_global)
+ self._send_method((60, 10), args)
+ return self.wait(allowed_methods=[
+ (60, 11), # Channel.basic_qos_ok
+ ])
+
+
+ def _basic_qos_ok(self, args):
+ """
+ confirm the requested qos
+
+ This method tells the client that the requested QoS levels
+ could be handled by the server. The requested QoS applies to
+ all active consumers until a new QoS is defined.
+
+ """
+ pass
+
+
+ def basic_recover(self, requeue=False):
+ """
+ redeliver unacknowledged messages
+
+ This method asks the broker to redeliver all unacknowledged
+ messages on a specified channel. Zero or more messages may be
+ redelivered. This method is only allowed on non-transacted
+ channels.
+
+ RULE:
+
+ The server MUST set the redelivered flag on all messages
+ that are resent.
+
+ RULE:
+
+ The server MUST raise a channel exception if this is
+ called on a transacted channel.
+
+ PARAMETERS:
+ requeue: boolean
+
+ requeue the message
+
+ If this field is False, the message will be redelivered
+ to the original recipient. If this field is True, the
+ server will attempt to requeue the message,
+ potentially then delivering it to an alternative
+ subscriber.
+
+ """
+ args = AMQPWriter()
+ args.write_bit(requeue)
+ self._send_method((60, 100), args)
+
+
+ def basic_reject(self, delivery_tag, requeue):
+ """
+ reject an incoming message
+
+ This method allows a client to reject a message. It can be
+ used to interrupt and cancel large incoming messages, or
+ return untreatable messages to their original queue.
+
+ RULE:
+
+ The server SHOULD be capable of accepting and process the
+ Reject method while sending message content with a Deliver
+ or Get-Ok method. I.e. the server should read and process
+ incoming methods while sending output frames. To cancel a
+ partially-send content, the server sends a content body
+ frame of size 1 (i.e. with no data except the frame-end
+ octet).
+
+ RULE:
+
+ The server SHOULD interpret this method as meaning that
+ the client is unable to process the message at this time.
+
+ RULE:
+
+ A client MUST NOT use this method as a means of selecting
+ messages to process. A rejected message MAY be discarded
+ or dead-lettered, not necessarily passed to another
+ client.
+
+ PARAMETERS:
+ delivery_tag: longlong
+
+ server-assigned delivery tag
+
+ The server-assigned and channel-specific delivery tag
+
+ RULE:
+
+ The delivery tag is valid only within the channel
+ from which the message was received. I.e. a client
+ MUST NOT receive a message on one channel and then
+ acknowledge it on another.
+
+ RULE:
+
+ The server MUST NOT use a zero value for delivery
+ tags. Zero is reserved for client use, meaning "all
+ messages so far received".
+
+ requeue: boolean
+
+ requeue the message
+
+ If this field is False, the message will be discarded.
+ If this field is True, the server will attempt to
+ requeue the message.
+
+ RULE:
+
+ The server MUST NOT deliver the message to the
+ same client within the context of the current
+ channel. The recommended strategy is to attempt
+ to deliver the message to an alternative consumer,
+ and if that is not possible, to move the message
+ to a dead-letter queue. The server MAY use more
+ sophisticated tracking to hold the message on the
+ queue and redeliver it to the same client at a
+ later stage.
+
+ """
+ args = AMQPWriter()
+ args.write_longlong(delivery_tag)
+ args.write_bit(requeue)
+ self._send_method((60, 90), args)
+
+
+ def _basic_return(self, args, msg):
+ """
+ return a failed message
+
+ This method returns an undeliverable message that was
+ published with the "immediate" flag set, or an unroutable
+ message published with the "mandatory" flag set. The reply
+ code and text provide information about the reason that the
+ message was undeliverable.
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ exchange: shortstr
+
+ Specifies the name of the exchange that the message
+ was originally published to.
+
+ routing_key: shortstr
+
+ Message routing key
+
+ Specifies the routing key name specified when the
+ message was published.
+
+ """
+ reply_code = args.read_short()
+ reply_text = args.read_shortstr()
+ exchange = args.read_shortstr()
+ routing_key = args.read_shortstr()
+
+ self.returned_messages.put(
+ (reply_code, reply_text, exchange, routing_key, msg)
+ )
+
+
+ #############
+ #
+ # Tx
+ #
+ #
+ # work with standard transactions
+ #
+ # Standard transactions provide so-called "1.5 phase commit". We
+ # can ensure that work is never lost, but there is a chance of
+ # confirmations being lost, so that messages may be resent.
+ # Applications that use standard transactions must be able to
+ # detect and ignore duplicate messages.
+ #
+ # GRAMMAR:
+ #
+ # tx = C:SELECT S:SELECT-OK
+ # / C:COMMIT S:COMMIT-OK
+ # / C:ROLLBACK S:ROLLBACK-OK
+ #
+ # RULE:
+ #
+ # An client using standard transactions SHOULD be able to
+ # track all messages received within a reasonable period, and
+ # thus detect and reject duplicates of the same message. It
+ # SHOULD NOT pass these to the application layer.
+ #
+ #
+
+ def tx_commit(self):
+ """
+ commit the current transaction
+
+ This method commits all messages published and acknowledged in
+ the current transaction. A new transaction starts immediately
+ after a commit.
+
+ """
+ self._send_method((90, 20))
+ return self.wait(allowed_methods=[
+ (90, 21), # Channel.tx_commit_ok
+ ])
+
+
+ def _tx_commit_ok(self, args):
+ """
+ confirm a successful commit
+
+ This method confirms to the client that the commit succeeded.
+ Note that if a commit fails, the server raises a channel
+ exception.
+
+ """
+ pass
+
+
+ def tx_rollback(self):
+ """
+ abandon the current transaction
+
+ This method abandons all messages published and acknowledged
+ in the current transaction. A new transaction starts
+ immediately after a rollback.
+
+ """
+ self._send_method((90, 30))
+ return self.wait(allowed_methods=[
+ (90, 31), # Channel.tx_rollback_ok
+ ])
+
+
+ def _tx_rollback_ok(self, args):
+ """
+ confirm a successful rollback
+
+ This method confirms to the client that the rollback
+ succeeded. Note that if an rollback fails, the server raises a
+ channel exception.
+
+ """
+ pass
+
+
+ def tx_select(self):
+ """
+ select standard transaction mode
+
+ This method sets the channel to use standard transactions.
+ The client must use this method at least once on a channel
+ before using the Commit or Rollback methods.
+
+ """
+ self._send_method((90, 10))
+ return self.wait(allowed_methods=[
+ (90, 11), # Channel.tx_select_ok
+ ])
+
+
+ def _tx_select_ok(self, args):
+ """
+ confirm transaction mode
+
+ This method confirms to the client that the channel was
+ successfully set to use standard transactions.
+
+ """
+ pass
+
+
+ _METHOD_MAP = {
+ (20, 11): _open_ok,
+ (20, 20): _flow,
+ (20, 21): _flow_ok,
+ (20, 30): _alert,
+ (20, 40): _close,
+ (20, 41): _close_ok,
+ (30, 11): _access_request_ok,
+ (40, 11): _exchange_declare_ok,
+ (40, 21): _exchange_delete_ok,
+ (50, 11): _queue_declare_ok,
+ (50, 21): _queue_bind_ok,
+ (50, 31): _queue_purge_ok,
+ (50, 41): _queue_delete_ok,
+ (60, 11): _basic_qos_ok,
+ (60, 21): _basic_consume_ok,
+ (60, 31): _basic_cancel_ok,
+ (60, 50): _basic_return,
+ (60, 60): _basic_deliver,
+ (60, 71): _basic_get_ok,
+ (60, 72): _basic_get_empty,
+ (90, 11): _tx_select_ok,
+ (90, 21): _tx_commit_ok,
+ (90, 31): _tx_rollback_ok,
+ }
diff --git a/vendor/amqplib/client_0_8/connection.py b/vendor/amqplib/client_0_8/connection.py
new file mode 100644
index 0000000000..f41587566c
--- /dev/null
+++ b/vendor/amqplib/client_0_8/connection.py
@@ -0,0 +1,826 @@
+"""
+AMQP 0-8 Connections
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+import logging
+
+from abstract_channel import AbstractChannel
+from channel import Channel
+from exceptions import *
+from method_framing import MethodReader, MethodWriter
+from serialization import AMQPReader, AMQPWriter
+from transport import create_transport
+
+__all__ = [
+ 'Connection',
+ ]
+
+#
+# Client property info that gets sent to the server on connection startup
+#
+LIBRARY_PROPERTIES = {
+ 'library': 'Python amqplib',
+ 'library_version': '0.6.1',
+ }
+
+AMQP_LOGGER = logging.getLogger('amqplib')
+
+
+class Connection(AbstractChannel):
+ """
+ The connection class provides methods for a client to establish a
+ network connection to a server, and for both peers to operate the
+ connection thereafter.
+
+ GRAMMAR:
+
+ connection = open-connection *use-connection close-connection
+ open-connection = C:protocol-header
+ S:START C:START-OK
+ *challenge
+ S:TUNE C:TUNE-OK
+ C:OPEN S:OPEN-OK | S:REDIRECT
+ challenge = S:SECURE C:SECURE-OK
+ use-connection = *channel
+ close-connection = C:CLOSE S:CLOSE-OK
+ / S:CLOSE C:CLOSE-OK
+
+ """
+ def __init__(self,
+ host='localhost',
+ userid='guest',
+ password='guest',
+ login_method='AMQPLAIN',
+ login_response=None,
+ virtual_host='/',
+ locale='en_US',
+ client_properties=None,
+ ssl=False,
+ insist=False,
+ connect_timeout=None,
+ **kwargs):
+ """
+ Create a connection to the specified host, which should be
+ a 'host[:port]', such as 'localhost', or '1.2.3.4:5672'
+ (defaults to 'localhost', if a port is not specified then
+ 5672 is used)
+
+ If login_response is not specified, one is built up for you from
+ userid and password if they are present.
+
+ """
+ if (login_response is None) \
+ and (userid is not None) \
+ and (password is not None):
+ login_response = AMQPWriter()
+ login_response.write_table({'LOGIN': userid, 'PASSWORD': password})
+ login_response = login_response.getvalue()[4:] #Skip the length
+ #at the beginning
+
+ d = {}
+ d.update(LIBRARY_PROPERTIES)
+ if client_properties:
+ d.update(client_properties)
+
+ self.known_hosts = ''
+
+ while True:
+ self.channels = {}
+ # The connection object itself is treated as channel 0
+ super(Connection, self).__init__(self, 0)
+
+ self.transport = None
+
+ # Properties set in the Tune method
+ self.channel_max = 65535
+ self.frame_max = 131072
+ self.heartbeat = 0
+
+ # Properties set in the Start method
+ self.version_major = 0
+ self.version_minor = 0
+ self.server_properties = {}
+ self.mechanisms = []
+ self.locales = []
+
+ # Let the transport.py module setup the actual
+ # socket connection to the broker.
+ #
+ self.transport = create_transport(host, connect_timeout, ssl)
+
+ self.method_reader = MethodReader(self.transport)
+ self.method_writer = MethodWriter(self.transport, self.frame_max)
+
+ self.wait(allowed_methods=[
+ (10, 10), # start
+ ])
+
+ self._x_start_ok(d, login_method, login_response, locale)
+
+ self._wait_tune_ok = True
+ while self._wait_tune_ok:
+ self.wait(allowed_methods=[
+ (10, 20), # secure
+ (10, 30), # tune
+ ])
+
+ host = self._x_open(virtual_host, insist=insist)
+ if host is None:
+ # we weren't redirected
+ return
+
+ # we were redirected, close the socket, loop and try again
+ try:
+ self.close()
+ except Exception:
+ pass
+
+
+ def _do_close(self):
+ self.transport.close()
+ self.transport = None
+
+ temp_list = [x for x in self.channels.values() if x is not self]
+ for ch in temp_list:
+ ch._do_close()
+
+ self.connection = self.channels = None
+
+
+ def _get_free_channel_id(self):
+ for i in xrange(1, self.channel_max+1):
+ if i not in self.channels:
+ return i
+ raise AMQPException('No free channel ids, current=%d, channel_max=%d'
+ % (len(self.channels), self.channel_max))
+
+
+ def _wait_method(self, channel_id, allowed_methods):
+ """
+ Wait for a method from the server destined for
+ a particular channel.
+
+ """
+ #
+ # Check the channel's deferred methods
+ #
+ method_queue = self.channels[channel_id].method_queue
+
+ for queued_method in method_queue:
+ method_sig = queued_method[0]
+ if (allowed_methods is None) \
+ or (method_sig in allowed_methods) \
+ or (method_sig == (20, 40)):
+ method_queue.remove(queued_method)
+ return queued_method
+
+ #
+ # Nothing queued, need to wait for a method from the peer
+ #
+ while True:
+ channel, method_sig, args, content = \
+ self.method_reader.read_method()
+
+ if (channel == channel_id) \
+ and ((allowed_methods is None) \
+ or (method_sig in allowed_methods) \
+ or (method_sig == (20, 40))):
+ return method_sig, args, content
+
+ #
+ # Not the channel and/or method we were looking for. Queue
+ # this method for later
+ #
+ self.channels[channel].method_queue.append((method_sig, args, content))
+
+ #
+ # If we just queued up a method for channel 0 (the Connection
+ # itself) it's probably a close method in reaction to some
+ # error, so deal with it right away.
+ #
+ if channel == 0:
+ self.wait()
+
+
+ def channel(self, channel_id=None):
+ """
+ Fetch a Channel object identified by the numeric channel_id, or
+ create that object if it doesn't already exist.
+
+ """
+ if channel_id in self.channels:
+ return self.channels[channel_id]
+
+ return Channel(self, channel_id)
+
+
+ #################
+
+ def close(self, reply_code=0, reply_text='', method_sig=(0, 0)):
+ """
+ request a connection close
+
+ This method indicates that the sender wants to close the
+ connection. This may be due to internal conditions (e.g. a
+ forced shut-down) or due to an error handling a specific
+ method, i.e. an exception. When a close is due to an
+ exception, the sender provides the class and method id of the
+ method which caused the exception.
+
+ RULE:
+
+ After sending this method any received method except the
+ Close-OK method MUST be discarded.
+
+ RULE:
+
+ The peer sending this method MAY use a counter or timeout
+ to detect failure of the other peer to respond correctly
+ with the Close-OK method.
+
+ RULE:
+
+ When a server receives the Close method from a client it
+ MUST delete all server-side resources associated with the
+ client's context. A client CANNOT reconnect to a context
+ after sending or receiving a Close method.
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ class_id: short
+
+ failing method class
+
+ When the close is provoked by a method exception, this
+ is the class of the method.
+
+ method_id: short
+
+ failing method ID
+
+ When the close is provoked by a method exception, this
+ is the ID of the method.
+
+ """
+ if self.transport is None:
+ # already closed
+ return
+
+ args = AMQPWriter()
+ args.write_short(reply_code)
+ args.write_shortstr(reply_text)
+ args.write_short(method_sig[0]) # class_id
+ args.write_short(method_sig[1]) # method_id
+ self._send_method((10, 60), args)
+ return self.wait(allowed_methods=[
+ (10, 61), # Connection.close_ok
+ ])
+
+
+ def _close(self, args):
+ """
+ request a connection close
+
+ This method indicates that the sender wants to close the
+ connection. This may be due to internal conditions (e.g. a
+ forced shut-down) or due to an error handling a specific
+ method, i.e. an exception. When a close is due to an
+ exception, the sender provides the class and method id of the
+ method which caused the exception.
+
+ RULE:
+
+ After sending this method any received method except the
+ Close-OK method MUST be discarded.
+
+ RULE:
+
+ The peer sending this method MAY use a counter or timeout
+ to detect failure of the other peer to respond correctly
+ with the Close-OK method.
+
+ RULE:
+
+ When a server receives the Close method from a client it
+ MUST delete all server-side resources associated with the
+ client's context. A client CANNOT reconnect to a context
+ after sending or receiving a Close method.
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ class_id: short
+
+ failing method class
+
+ When the close is provoked by a method exception, this
+ is the class of the method.
+
+ method_id: short
+
+ failing method ID
+
+ When the close is provoked by a method exception, this
+ is the ID of the method.
+
+ """
+ reply_code = args.read_short()
+ reply_text = args.read_shortstr()
+ class_id = args.read_short()
+ method_id = args.read_short()
+
+ self._x_close_ok()
+
+ raise AMQPConnectionException(reply_code, reply_text, (class_id, method_id))
+
+
+ def _x_close_ok(self):
+ """
+ confirm a connection close
+
+ This method confirms a Connection.Close method and tells the
+ recipient that it is safe to release resources for the
+ connection and close the socket.
+
+ RULE:
+
+ A peer that detects a socket closure without having
+ received a Close-Ok handshake method SHOULD log the error.
+
+ """
+ self._send_method((10, 61))
+ self._do_close()
+
+
+ def _close_ok(self, args):
+ """
+ confirm a connection close
+
+ This method confirms a Connection.Close method and tells the
+ recipient that it is safe to release resources for the
+ connection and close the socket.
+
+ RULE:
+
+ A peer that detects a socket closure without having
+ received a Close-Ok handshake method SHOULD log the error.
+
+ """
+ self._do_close()
+
+
+ def _x_open(self, virtual_host, capabilities='', insist=False):
+ """
+ open connection to virtual host
+
+ This method opens a connection to a virtual host, which is a
+ collection of resources, and acts to separate multiple
+ application domains within a server.
+
+ RULE:
+
+ The client MUST open the context before doing any work on
+ the connection.
+
+ PARAMETERS:
+ virtual_host: shortstr
+
+ virtual host name
+
+ The name of the virtual host to work with.
+
+ RULE:
+
+ If the server supports multiple virtual hosts, it
+ MUST enforce a full separation of exchanges,
+ queues, and all associated entities per virtual
+ host. An application, connected to a specific
+ virtual host, MUST NOT be able to access resources
+ of another virtual host.
+
+ RULE:
+
+ The server SHOULD verify that the client has
+ permission to access the specified virtual host.
+
+ RULE:
+
+ The server MAY configure arbitrary limits per
+ virtual host, such as the number of each type of
+ entity that may be used, per connection and/or in
+ total.
+
+ capabilities: shortstr
+
+ required capabilities
+
+ The client may specify a number of capability names,
+ delimited by spaces. The server can use this string
+ to how to process the client's connection request.
+
+ insist: boolean
+
+ insist on connecting to server
+
+ In a configuration with multiple load-sharing servers,
+ the server may respond to a Connection.Open method
+ with a Connection.Redirect. The insist option tells
+ the server that the client is insisting on a
+ connection to the specified server.
+
+ RULE:
+
+ When the client uses the insist option, the server
+ SHOULD accept the client connection unless it is
+ technically unable to do so.
+
+ """
+ args = AMQPWriter()
+ args.write_shortstr(virtual_host)
+ args.write_shortstr(capabilities)
+ args.write_bit(insist)
+ self._send_method((10, 40), args)
+ return self.wait(allowed_methods=[
+ (10, 41), # Connection.open_ok
+ (10, 50), # Connection.redirect
+ ])
+
+
+ def _open_ok(self, args):
+ """
+ signal that the connection is ready
+
+ This method signals to the client that the connection is ready
+ for use.
+
+ PARAMETERS:
+ known_hosts: shortstr
+
+ """
+ self.known_hosts = args.read_shortstr()
+ AMQP_LOGGER.debug('Open OK! known_hosts [%s]' % self.known_hosts)
+ return None
+
+
+ def _redirect(self, args):
+ """
+ asks the client to use a different server
+
+ This method redirects the client to another server, based on
+ the requested virtual host and/or capabilities.
+
+ RULE:
+
+ When getting the Connection.Redirect method, the client
+ SHOULD reconnect to the host specified, and if that host
+ is not present, to any of the hosts specified in the
+ known-hosts list.
+
+ PARAMETERS:
+ host: shortstr
+
+ server to connect to
+
+ Specifies the server to connect to. This is an IP
+ address or a DNS name, optionally followed by a colon
+ and a port number. If no port number is specified, the
+ client should use the default port number for the
+ protocol.
+
+ known_hosts: shortstr
+
+ """
+ host = args.read_shortstr()
+ self.known_hosts = args.read_shortstr()
+ AMQP_LOGGER.debug('Redirected to [%s], known_hosts [%s]' % (host, self.known_hosts))
+ return host
+
+
+ def _secure(self, args):
+ """
+ security mechanism challenge
+
+ The SASL protocol works by exchanging challenges and responses
+ until both peers have received sufficient information to
+ authenticate each other. This method challenges the client to
+ provide more information.
+
+ PARAMETERS:
+ challenge: longstr
+
+ security challenge data
+
+ Challenge information, a block of opaque binary data
+ passed to the security mechanism.
+
+ """
+ challenge = args.read_longstr()
+
+
+ def _x_secure_ok(self, response):
+ """
+ security mechanism response
+
+ This method attempts to authenticate, passing a block of SASL
+ data for the security mechanism at the server side.
+
+ PARAMETERS:
+ response: longstr
+
+ security response data
+
+ A block of opaque data passed to the security
+ mechanism. The contents of this data are defined by
+ the SASL security mechanism.
+
+ """
+ args = AMQPWriter()
+ args.write_longstr(response)
+ self._send_method((10, 21), args)
+
+
+ def _start(self, args):
+ """
+ start connection negotiation
+
+ This method starts the connection negotiation process by
+ telling the client the protocol version that the server
+ proposes, along with a list of security mechanisms which the
+ client can use for authentication.
+
+ RULE:
+
+ If the client cannot handle the protocol version suggested
+ by the server it MUST close the socket connection.
+
+ RULE:
+
+ The server MUST provide a protocol version that is lower
+ than or equal to that requested by the client in the
+ protocol header. If the server cannot support the
+ specified protocol it MUST NOT send this method, but MUST
+ close the socket connection.
+
+ PARAMETERS:
+ version_major: octet
+
+ protocol major version
+
+ The protocol major version that the server agrees to
+ use, which cannot be higher than the client's major
+ version.
+
+ version_minor: octet
+
+ protocol major version
+
+ The protocol minor version that the server agrees to
+ use, which cannot be higher than the client's minor
+ version.
+
+ server_properties: table
+
+ server properties
+
+ mechanisms: longstr
+
+ available security mechanisms
+
+ A list of the security mechanisms that the server
+ supports, delimited by spaces. Currently ASL supports
+ these mechanisms: PLAIN.
+
+ locales: longstr
+
+ available message locales
+
+ A list of the message locales that the server
+ supports, delimited by spaces. The locale defines the
+ language in which the server will send reply texts.
+
+ RULE:
+
+ All servers MUST support at least the en_US
+ locale.
+
+ """
+ self.version_major = args.read_octet()
+ self.version_minor = args.read_octet()
+ self.server_properties = args.read_table()
+ self.mechanisms = args.read_longstr().split(' ')
+ self.locales = args.read_longstr().split(' ')
+
+ AMQP_LOGGER.debug('Start from server, version: %d.%d, properties: %s, mechanisms: %s, locales: %s'
+ % (self.version_major, self.version_minor,
+ str(self.server_properties), self.mechanisms, self.locales))
+
+
+ def _x_start_ok(self, client_properties, mechanism, response, locale):
+ """
+ select security mechanism and locale
+
+ This method selects a SASL security mechanism. ASL uses SASL
+ (RFC2222) to negotiate authentication and encryption.
+
+ PARAMETERS:
+ client_properties: table
+
+ client properties
+
+ mechanism: shortstr
+
+ selected security mechanism
+
+ A single security mechanisms selected by the client,
+ which must be one of those specified by the server.
+
+ RULE:
+
+ The client SHOULD authenticate using the highest-
+ level security profile it can handle from the list
+ provided by the server.
+
+ RULE:
+
+ The mechanism field MUST contain one of the
+ security mechanisms proposed by the server in the
+ Start method. If it doesn't, the server MUST close
+ the socket.
+
+ response: longstr
+
+ security response data
+
+ A block of opaque data passed to the security
+ mechanism. The contents of this data are defined by
+ the SASL security mechanism. For the PLAIN security
+ mechanism this is defined as a field table holding two
+ fields, LOGIN and PASSWORD.
+
+ locale: shortstr
+
+ selected message locale
+
+ A single message local selected by the client, which
+ must be one of those specified by the server.
+
+ """
+ args = AMQPWriter()
+ args.write_table(client_properties)
+ args.write_shortstr(mechanism)
+ args.write_longstr(response)
+ args.write_shortstr(locale)
+ self._send_method((10, 11), args)
+
+
+ def _tune(self, args):
+ """
+ propose connection tuning parameters
+
+ This method proposes a set of connection configuration values
+ to the client. The client can accept and/or adjust these.
+
+ PARAMETERS:
+ channel_max: short
+
+ proposed maximum channels
+
+ The maximum total number of channels that the server
+ allows per connection. Zero means that the server does
+ not impose a fixed limit, but the number of allowed
+ channels may be limited by available server resources.
+
+ frame_max: long
+
+ proposed maximum frame size
+
+ The largest frame size that the server proposes for
+ the connection. The client can negotiate a lower
+ value. Zero means that the server does not impose any
+ specific limit but may reject very large frames if it
+ cannot allocate resources for them.
+
+ RULE:
+
+ Until the frame-max has been negotiated, both
+ peers MUST accept frames of up to 4096 octets
+ large. The minimum non-zero value for the frame-
+ max field is 4096.
+
+ heartbeat: short
+
+ desired heartbeat delay
+
+ The delay, in seconds, of the connection heartbeat
+ that the server wants. Zero means the server does not
+ want a heartbeat.
+
+ """
+ self.channel_max = args.read_short() or self.channel_max
+ self.frame_max = args.read_long() or self.frame_max
+ self.method_writer.frame_max = self.frame_max
+ self.heartbeat = args.read_short()
+
+ self._x_tune_ok(self.channel_max, self.frame_max, 0)
+
+
+ def _x_tune_ok(self, channel_max, frame_max, heartbeat):
+ """
+ negotiate connection tuning parameters
+
+ This method sends the client's connection tuning parameters to
+ the server. Certain fields are negotiated, others provide
+ capability information.
+
+ PARAMETERS:
+ channel_max: short
+
+ negotiated maximum channels
+
+ The maximum total number of channels that the client
+ will use per connection. May not be higher than the
+ value specified by the server.
+
+ RULE:
+
+ The server MAY ignore the channel-max value or MAY
+ use it for tuning its resource allocation.
+
+ frame_max: long
+
+ negotiated maximum frame size
+
+ The largest frame size that the client and server will
+ use for the connection. Zero means that the client
+ does not impose any specific limit but may reject very
+ large frames if it cannot allocate resources for them.
+ Note that the frame-max limit applies principally to
+ content frames, where large contents can be broken
+ into frames of arbitrary size.
+
+ RULE:
+
+ Until the frame-max has been negotiated, both
+ peers must accept frames of up to 4096 octets
+ large. The minimum non-zero value for the frame-
+ max field is 4096.
+
+ heartbeat: short
+
+ desired heartbeat delay
+
+ The delay, in seconds, of the connection heartbeat
+ that the client wants. Zero means the client does not
+ want a heartbeat.
+
+ """
+ args = AMQPWriter()
+ args.write_short(channel_max)
+ args.write_long(frame_max)
+ args.write_short(heartbeat)
+ self._send_method((10, 31), args)
+ self._wait_tune_ok = False
+
+
+ _METHOD_MAP = {
+ (10, 10): _start,
+ (10, 20): _secure,
+ (10, 30): _tune,
+ (10, 41): _open_ok,
+ (10, 50): _redirect,
+ (10, 60): _close,
+ (10, 61): _close_ok,
+ }
diff --git a/vendor/amqplib/client_0_8/exceptions.py b/vendor/amqplib/client_0_8/exceptions.py
new file mode 100644
index 0000000000..58d0b5f7ef
--- /dev/null
+++ b/vendor/amqplib/client_0_8/exceptions.py
@@ -0,0 +1,105 @@
+"""
+Exceptions used by amqplib.client_0_8
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+
+__all__ = [
+ 'AMQPException',
+ 'AMQPConnectionException',
+ 'AMQPChannelException',
+ ]
+
+
+class AMQPException(Exception):
+ def __init__(self, reply_code, reply_text, method_sig):
+ Exception.__init__(self)
+ self.amqp_reply_code = reply_code
+ self.amqp_reply_text = reply_text
+ self.amqp_method_sig = method_sig
+ self.args = (
+ reply_code,
+ reply_text,
+ method_sig,
+ METHOD_NAME_MAP.get(method_sig, '')
+ )
+
+
+class AMQPConnectionException(AMQPException):
+ pass
+
+
+class AMQPChannelException(AMQPException):
+ pass
+
+
+METHOD_NAME_MAP = {
+ (10, 10): 'Connection.start',
+ (10, 11): 'Connection.start_ok',
+ (10, 20): 'Connection.secure',
+ (10, 21): 'Connection.secure_ok',
+ (10, 30): 'Connection.tune',
+ (10, 31): 'Connection.tune_ok',
+ (10, 40): 'Connection.open',
+ (10, 41): 'Connection.open_ok',
+ (10, 50): 'Connection.redirect',
+ (10, 60): 'Connection.close',
+ (10, 61): 'Connection.close_ok',
+ (20, 10): 'Channel.open',
+ (20, 11): 'Channel.open_ok',
+ (20, 20): 'Channel.flow',
+ (20, 21): 'Channel.flow_ok',
+ (20, 30): 'Channel.alert',
+ (20, 40): 'Channel.close',
+ (20, 41): 'Channel.close_ok',
+ (30, 10): 'Channel.access_request',
+ (30, 11): 'Channel.access_request_ok',
+ (40, 10): 'Channel.exchange_declare',
+ (40, 11): 'Channel.exchange_declare_ok',
+ (40, 20): 'Channel.exchange_delete',
+ (40, 21): 'Channel.exchange_delete_ok',
+ (50, 10): 'Channel.queue_declare',
+ (50, 11): 'Channel.queue_declare_ok',
+ (50, 20): 'Channel.queue_bind',
+ (50, 21): 'Channel.queue_bind_ok',
+ (50, 30): 'Channel.queue_purge',
+ (50, 31): 'Channel.queue_purge_ok',
+ (50, 40): 'Channel.queue_delete',
+ (50, 41): 'Channel.queue_delete_ok',
+ (60, 10): 'Channel.basic_qos',
+ (60, 11): 'Channel.basic_qos_ok',
+ (60, 20): 'Channel.basic_consume',
+ (60, 21): 'Channel.basic_consume_ok',
+ (60, 30): 'Channel.basic_cancel',
+ (60, 31): 'Channel.basic_cancel_ok',
+ (60, 40): 'Channel.basic_publish',
+ (60, 50): 'Channel.basic_return',
+ (60, 60): 'Channel.basic_deliver',
+ (60, 70): 'Channel.basic_get',
+ (60, 71): 'Channel.basic_get_ok',
+ (60, 72): 'Channel.basic_get_empty',
+ (60, 80): 'Channel.basic_ack',
+ (60, 90): 'Channel.basic_reject',
+ (60, 100): 'Channel.basic_recover',
+ (90, 10): 'Channel.tx_select',
+ (90, 11): 'Channel.tx_select_ok',
+ (90, 20): 'Channel.tx_commit',
+ (90, 21): 'Channel.tx_commit_ok',
+ (90, 30): 'Channel.tx_rollback',
+ (90, 31): 'Channel.tx_rollback_ok',
+}
diff --git a/vendor/amqplib/client_0_8/method_framing.py b/vendor/amqplib/client_0_8/method_framing.py
new file mode 100644
index 0000000000..9f69ee50c7
--- /dev/null
+++ b/vendor/amqplib/client_0_8/method_framing.py
@@ -0,0 +1,244 @@
+"""
+Convert between frames and higher-level AMQP methods
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+from Queue import Empty, Queue
+from struct import pack, unpack
+
+try:
+ from collections import defaultdict
+except:
+ class defaultdict(dict):
+ """
+ Mini-implementation of collections.defaultdict that
+ appears in Python 2.5 and up.
+
+ """
+ def __init__(self, default_factory):
+ dict.__init__(self)
+ self.default_factory = default_factory
+
+ def __getitem__(self, key):
+ try:
+ return dict.__getitem__(self, key)
+ except KeyError:
+ result = self.default_factory()
+ dict.__setitem__(self, key, result)
+ return result
+
+
+from basic_message import Message
+from exceptions import *
+from serialization import AMQPReader
+
+__all__ = [
+ 'MethodReader',
+ ]
+
+#
+# MethodReader needs to know which methods are supposed
+# to be followed by content headers and bodies.
+#
+_CONTENT_METHODS = [
+ (60, 50), # Basic.return
+ (60, 60), # Basic.deliver
+ (60, 71), # Basic.get_ok
+ ]
+
+
+class _PartialMessage(object):
+ """
+ Helper class to build up a multi-frame method.
+
+ """
+ def __init__(self, method_sig, args):
+ self.method_sig = method_sig
+ self.args = args
+ self.msg = Message()
+ self.body_parts = []
+ self.body_received = 0
+ self.body_size = None
+ self.complete = False
+
+
+ def add_header(self, payload):
+ class_id, weight, self.body_size = unpack('>HHQ', payload[:12])
+ self.msg._load_properties(payload[12:])
+ self.complete = (self.body_size == 0)
+
+
+ def add_payload(self, payload):
+ self.body_parts.append(payload)
+ self.body_received += len(payload)
+
+ if self.body_received == self.body_size:
+ self.msg.body = ''.join(self.body_parts)
+ self.complete = True
+
+
+class MethodReader(object):
+ """
+ Helper class to receive frames from the broker, combine them if
+ necessary with content-headers and content-bodies into complete methods.
+
+ Normally a method is represented as a tuple containing
+ (channel, method_sig, args, content).
+
+ In the case of a framing error, an AMQPConnectionException is placed
+ in the queue.
+
+ In the case of unexpected frames, a tuple made up of
+ (channel, AMQPChannelException) is placed in the queue.
+
+ """
+ def __init__(self, source):
+ self.source = source
+ self.queue = Queue()
+ self.running = False
+ self.partial_messages = {}
+ # For each channel, which type is expected next
+ self.expected_types = defaultdict(lambda:1)
+
+
+ def _next_method(self):
+ """
+ Read the next method from the source, once one complete method has
+ been assembled it is placed in the internal queue.
+
+ """
+ while self.queue.empty():
+ try:
+ frame_type, channel, payload = self.source.read_frame()
+ except Exception, e:
+ #
+ # Connection was closed? Framing Error?
+ #
+ self.queue.put(e)
+ break
+
+ if self.expected_types[channel] != frame_type:
+ self.queue.put((
+ channel,
+ Exception('Received frame type %s while expecting type: %s' %
+ (frame_type, self.expected_types[channel])
+ )
+ ))
+ elif frame_type == 1:
+ self._process_method_frame(channel, payload)
+ elif frame_type == 2:
+ self._process_content_header(channel, payload)
+ elif frame_type == 3:
+ self._process_content_body(channel, payload)
+
+
+ def _process_method_frame(self, channel, payload):
+ """
+ Process Method frames
+
+ """
+ method_sig = unpack('>HH', payload[:4])
+ args = AMQPReader(payload[4:])
+
+ if method_sig in _CONTENT_METHODS:
+ #
+ # Save what we've got so far and wait for the content-header
+ #
+ self.partial_messages[channel] = _PartialMessage(method_sig, args)
+ self.expected_types[channel] = 2
+ else:
+ self.queue.put((channel, method_sig, args, None))
+
+
+ def _process_content_header(self, channel, payload):
+ """
+ Process Content Header frames
+
+ """
+ partial = self.partial_messages[channel]
+ partial.add_header(payload)
+
+ if partial.complete:
+ #
+ # a bodyless message, we're done
+ #
+ self.queue.put((channel, partial.method_sig, partial.args, partial.msg))
+ del self.partial_messages[channel]
+ self.expected_types[channel] = 1
+ else:
+ #
+ # wait for the content-body
+ #
+ self.expected_types[channel] = 3
+
+
+ def _process_content_body(self, channel, payload):
+ """
+ Process Content Body frames
+
+ """
+ partial = self.partial_messages[channel]
+ partial.add_payload(payload)
+ if partial.complete:
+ #
+ # Stick the message in the queue and go back to
+ # waiting for method frames
+ #
+ self.queue.put((channel, partial.method_sig, partial.args, partial.msg))
+ del self.partial_messages[channel]
+ self.expected_types[channel] = 1
+
+
+ def read_method(self):
+ """
+ Read a method from the peer.
+
+ """
+ self._next_method()
+ m = self.queue.get()
+ if isinstance(m, Exception):
+ raise m
+ return m
+
+
+class MethodWriter(object):
+ """
+ Convert AMQP methods into AMQP frames and send them out
+ to the peer.
+
+ """
+ def __init__(self, dest, frame_max):
+ self.dest = dest
+ self.frame_max = frame_max
+
+
+ def write_method(self, channel, method_sig, args, content=None):
+ payload = pack('>HH', method_sig[0], method_sig[1]) + args
+
+ self.dest.write_frame(1, channel, payload)
+
+ if content:
+ body = content.body
+ payload = pack('>HHQ', method_sig[0], 0, len(body)) + \
+ content._serialize_properties()
+
+ self.dest.write_frame(2, channel, payload)
+
+ while body:
+ payload, body = body[:self.frame_max - 8], body[self.frame_max -8:]
+ self.dest.write_frame(3, channel, payload)
diff --git a/vendor/amqplib/client_0_8/serialization.py b/vendor/amqplib/client_0_8/serialization.py
new file mode 100644
index 0000000000..3936dcd7a8
--- /dev/null
+++ b/vendor/amqplib/client_0_8/serialization.py
@@ -0,0 +1,530 @@
+"""
+Convert between bytestreams and higher-level AMQP types.
+
+2007-11-05 Barry Pederson <bp@barryp.org>
+
+"""
+# Copyright (C) 2007 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+import string
+from datetime import datetime
+from decimal import Decimal
+from struct import pack, unpack
+from time import mktime
+
+try:
+ from cStringIO import StringIO
+except:
+ from StringIO import StringIO
+
+
+DUMP_CHARS = string.letters + string.digits + string.punctuation
+
+def _hexdump(s):
+ """
+ Present just for debugging help.
+
+ """
+ while s:
+ x, s = s[:16], s[16:]
+
+ hex = ['%02x' % ord(ch) for ch in x]
+ hex = ' '.join(hex).ljust(50)
+
+ char_dump = []
+ for ch in x:
+ if ch in DUMP_CHARS:
+ char_dump.append(ch)
+ else:
+ char_dump.append('.')
+
+ print hex + ''.join(char_dump)
+
+
+class AMQPReader(object):
+ """
+ Read higher-level AMQP types from a bytestream.
+
+ """
+ def __init__(self, source):
+ """
+ Source should be either a file-like object with a read() method, or
+ a plain (non-unicode) string.
+
+ """
+ if isinstance(source, str):
+ self.input = StringIO(source)
+ elif hasattr(source, 'read'):
+ self.input = source
+ else:
+ raise ValueError('AMQPReader needs a file-like object or plain string')
+
+ self.bitcount = self.bits = 0
+
+
+ def close(self):
+ self.input.close()
+
+
+ def read(self, n):
+ """
+ Read n bytes.
+
+ """
+ self.bitcount = self.bits = 0
+ return self.input.read(n)
+
+
+ def read_bit(self):
+ """
+ Read a single boolean value.
+
+ """
+ if not self.bitcount:
+ self.bits = ord(self.input.read(1))
+ self.bitcount = 8
+ result = (self.bits & 1) == 1
+ self.bits >>= 1
+ self.bitcount -= 1
+ return result
+
+
+ def read_octet(self):
+ """
+ Read one byte, return as an integer
+
+ """
+ self.bitcount = self.bits = 0
+ return unpack('B', self.input.read(1))[0]
+
+
+ def read_short(self):
+ """
+ Read an unsigned 16-bit integer
+
+ """
+ self.bitcount = self.bits = 0
+ return unpack('>H', self.input.read(2))[0]
+
+
+ def read_long(self):
+ """
+ Read an unsigned 32-bit integer
+
+ """
+ self.bitcount = self.bits = 0
+ return unpack('>I', self.input.read(4))[0]
+
+
+ def read_longlong(self):
+ """
+ Read an unsigned 64-bit integer
+
+ """
+ self.bitcount = self.bits = 0
+ return unpack('>Q', self.input.read(8))[0]
+
+
+ def read_shortstr(self):
+ """
+ Read a utf-8 encoded string that's stored in up to
+ 255 bytes. Return it decoded as a Python unicode object.
+
+ """
+ self.bitcount = self.bits = 0
+ slen = unpack('B', self.input.read(1))[0]
+ return self.input.read(slen).decode('utf-8')
+
+
+ def read_longstr(self):
+ """
+ Read a string that's up to 2**32 bytes, the encoding
+ isn't specified in the AMQP spec, so just return it as
+ a plain Python string.
+
+ """
+ self.bitcount = self.bits = 0
+ slen = unpack('>I', self.input.read(4))[0]
+ return self.input.read(slen)
+
+
+ def read_table(self):
+ """
+ Read an AMQP table, and return as a Python dictionary.
+
+ """
+ self.bitcount = self.bits = 0
+ tlen = unpack('>I', self.input.read(4))[0]
+ table_data = AMQPReader(self.input.read(tlen))
+ result = {}
+ while table_data.input.tell() < tlen:
+ name = table_data.read_shortstr()
+ ftype = table_data.input.read(1)
+ if ftype == 'S':
+ val = table_data.read_longstr()
+ elif ftype == 'I':
+ val = unpack('>i', table_data.input.read(4))[0]
+ elif ftype == 'D':
+ d = table_data.read_octet()
+ n = unpack('>i', table_data.input.read(4))[0]
+ val = Decimal(n) / Decimal(10 ** d)
+ elif ftype == 'T':
+ val = table_data.read_timestamp()
+ elif ftype == 'F':
+ val = table_data.read_table() # recurse
+ result[name] = val
+ return result
+
+
+ def read_timestamp(self):
+ """
+ Read and AMQP timestamp, which is a 64-bit integer representing
+ seconds since the Unix epoch in 1-second resolution. Return as
+ a Python datetime.datetime object, expressed as localtime.
+
+ """
+ return datetime.fromtimestamp(self.read_longlong())
+
+
+class AMQPWriter(object):
+ """
+ Convert higher-level AMQP types to bytestreams.
+
+ """
+ def __init__(self, dest=None):
+ """
+ dest may be a file-type object (with a write() method). If None
+ then a StringIO is created, and the contents can be accessed with
+ this class's getvalue() method.
+
+ """
+ if dest is None:
+ self.out = StringIO()
+ else:
+ self.out = dest
+
+ self.bits = []
+ self.bitcount = 0
+
+
+ def _flushbits(self):
+ if self.bits:
+ for b in self.bits:
+ self.out.write(pack('B', b))
+ self.bits = []
+ self.bitcount = 0
+
+
+ def close(self):
+ """
+ Pass through if possible to any file-like destinations.
+
+ """
+ if hasattr(self.out, 'close'):
+ self.out.close()
+
+
+ def flush(self):
+ """
+ Pass through if possible to any file-like destinations.
+
+ """
+ if hasattr(self.out, 'flush'):
+ self.out.flush()
+
+
+ def getvalue(self):
+ """
+ Get what's been encoded so far if we're working with a StringIO.
+
+ """
+ self._flushbits()
+ return self.out.getvalue()
+
+
+ def write(self, s):
+ """
+ Write a plain Python string, with no special encoding.
+
+ """
+ self._flushbits()
+ self.out.write(s)
+
+
+ def write_bit(self, b):
+ """
+ Write a boolean value.
+
+ """
+ if b:
+ b = 1
+ else:
+ b = 0
+ shift = self.bitcount % 8
+ if shift == 0:
+ self.bits.append(0)
+ self.bits[-1] |= (b << shift)
+ self.bitcount += 1
+
+
+ def write_octet(self, n):
+ """
+ Write an integer as an unsigned 8-bit value.
+
+ """
+ if (n < 0) or (n > 255):
+ raise ValueError('Octet out of range 0..255')
+ self._flushbits()
+ self.out.write(pack('B', n))
+
+
+ def write_short(self, n):
+ """
+ Write an integer as an unsigned 16-bit value.
+
+ """
+ if (n < 0) or (n > 65535):
+ raise ValueError('Octet out of range 0..65535')
+ self._flushbits()
+ self.out.write(pack('>H', n))
+
+
+ def write_long(self, n):
+ """
+ Write an integer as an unsigned2 32-bit value.
+
+ """
+ if (n < 0) or (n >= (2**32)):
+ raise ValueError('Octet out of range 0..2**31-1')
+ self._flushbits()
+ self.out.write(pack('>I', n))
+
+
+ def write_longlong(self, n):
+ """
+ Write an integer as an unsigned 64-bit value.
+
+ """
+ if (n < 0) or (n >= (2**64)):
+ raise ValueError('Octet out of range 0..2**64-1')
+ self._flushbits()
+ self.out.write(pack('>Q', n))
+
+
+ def write_shortstr(self, s):
+ """
+ Write a string up to 255 bytes long after encoding. If passed
+ a unicode string, encode as UTF-8.
+
+ """
+ self._flushbits()
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ if len(s) > 255:
+ raise ValueError('String too long')
+ self.write_octet(len(s))
+ self.out.write(s)
+
+
+ def write_longstr(self, s):
+ """
+ Write a string up to 2**32 bytes long after encoding. If passed
+ a unicode string, encode as UTF-8.
+
+ """
+ self._flushbits()
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ self.write_long(len(s))
+ self.out.write(s)
+
+
+ def write_table(self, d):
+ """
+ Write out a Python dictionary made of up string keys, and values
+ that are strings, signed integers, Decimal, datetime.datetime, or
+ sub-dictionaries following the same constraints.
+
+ """
+ self._flushbits()
+ table_data = AMQPWriter()
+ for k, v in d.items():
+ table_data.write_shortstr(k)
+ if isinstance(v, basestring):
+ if isinstance(v, unicode):
+ v = v.encode('utf-8')
+ table_data.write('S')
+ table_data.write_longstr(v)
+ elif isinstance(v, (int, long)):
+ table_data.write('I')
+ table_data.write(pack('>i', v))
+ elif isinstance(v, Decimal):
+ table_data.write('D')
+ sign, digits, exponent = v.as_tuple()
+ v = 0
+ for d in digits:
+ v = (v * 10) + d
+ if sign:
+ v = -v
+ table_data.write_octet(-exponent)
+ table_data.write(pack('>i', v))
+ elif isinstance(v, datetime):
+ table_data.write('T')
+ table_data.write_timestamp(v)
+ ## FIXME: timezone ?
+ elif isinstance(v, dict):
+ table_data.write('F')
+ table_data.write_table(v)
+ table_data = table_data.getvalue()
+ self.write_long(len(table_data))
+ self.out.write(table_data)
+
+
+ def write_timestamp(self, v):
+ """
+ Write out a Python datetime.datetime object as a 64-bit integer
+ representing seconds since the Unix epoch.
+
+ """
+ self.out.write(pack('>q', long(mktime(v.timetuple()))))
+
+
+class GenericContent(object):
+ """
+ Abstract base class for AMQP content. Subclasses should
+ override the PROPERTIES attribute.
+
+ """
+ PROPERTIES = [
+ ('dummy', 'shortstr'),
+ ]
+
+ def __init__(self, **props):
+ """
+ Save the properties appropriate to this AMQP content type
+ in a 'properties' dictionary.
+
+ """
+ d = {}
+ for propname, _ in self.PROPERTIES:
+ if propname in props:
+ d[propname] = props[propname]
+ # FIXME: should we ignore unknown properties?
+
+ self.properties = d
+
+
+ def __eq__(self, other):
+ """
+ Check if this object has the same properties as another
+ content object.
+
+ """
+ return (self.properties == other.properties)
+
+
+ def __getattr__(self, name):
+ """
+ Look for additional properties in the 'properties'
+ dictionary, and if present - the 'delivery_info'
+ dictionary.
+
+ """
+ if name in self.properties:
+ return self.properties[name]
+
+ if ('delivery_info' in self.__dict__) \
+ and (name in self.delivery_info):
+ return self.delivery_info[name]
+
+ raise AttributeError(name)
+
+
+ def __ne__(self, other):
+ """
+ Just return the opposite of __eq__
+
+ """
+ return not self.__eq__(other)
+
+
+ def _load_properties(self, raw_bytes):
+ """
+ Given the raw bytes containing the property-flags and property-list
+ from a content-frame-header, parse and insert into a dictionary
+ stored in this object as an attribute named 'properties'.
+
+ """
+ r = AMQPReader(raw_bytes)
+
+ #
+ # Read 16-bit shorts until we get one with a low bit set to zero
+ #
+ flags = []
+ while True:
+ flag_bits = r.read_short()
+ flags.append(flag_bits)
+ if flag_bits & 1 == 0:
+ break
+
+ shift = 0
+ d = {}
+ for key, proptype in self.PROPERTIES:
+ if shift == 0:
+ if not flags:
+ break
+ flag_bits, flags = flags[0], flags[1:]
+ shift = 15
+ if flag_bits & (1 << shift):
+ d[key] = getattr(r, 'read_' + proptype)()
+ shift -= 1
+
+ self.properties = d
+
+
+ def _serialize_properties(self):
+ """
+ serialize the 'properties' attribute (a dictionary) into
+ the raw bytes making up a set of property flags and a
+ property list, suitable for putting into a content frame header.
+
+ """
+ shift = 15
+ flag_bits = 0
+ flags = []
+ raw_bytes = AMQPWriter()
+ for key, proptype in self.PROPERTIES:
+ val = self.properties.get(key, None)
+ if val is not None:
+ if shift == 0:
+ flags.append(flag_bits)
+ flag_bits = 0
+ shift = 15
+
+ flag_bits |= (1 << shift)
+ if proptype != 'bit':
+ getattr(raw_bytes, 'write_' + proptype)(val)
+
+ shift -= 1
+
+ flags.append(flag_bits)
+ result = AMQPWriter()
+ for flag_bits in flags:
+ result.write_short(flag_bits)
+ result.write(raw_bytes.getvalue())
+
+ return result.getvalue()
diff --git a/vendor/amqplib/client_0_8/transport.py b/vendor/amqplib/client_0_8/transport.py
new file mode 100644
index 0000000000..3c82f456b1
--- /dev/null
+++ b/vendor/amqplib/client_0_8/transport.py
@@ -0,0 +1,220 @@
+"""
+Read/Write AMQP frames over network transports.
+
+2009-01-14 Barry Pederson <bp@barryp.org>
+
+"""
+# Copyright (C) 2009 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+import socket
+
+#
+# See if Python 2.6+ SSL support is available
+#
+try:
+ import ssl
+ HAVE_PY26_SSL = True
+except:
+ HAVE_PY26_SSL = False
+
+from struct import pack, unpack
+
+AMQP_PORT = 5672
+
+# Yes, Advanced Message Queuing Protocol Protocol is redundant
+AMQP_PROTOCOL_HEADER = 'AMQP\x01\x01\x09\x01'
+
+
+class _AbstractTransport(object):
+ """
+ Common superclass for TCP and SSL transports
+
+ """
+ def __init__(self, host, connect_timeout):
+ if ':' in host:
+ host, port = host.split(':', 1)
+ port = int(port)
+ else:
+ port = AMQP_PORT
+
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.settimeout(connect_timeout)
+
+ try:
+ self.sock.connect((host, port))
+ except socket.error:
+ self.sock.close()
+ raise
+ self.sock.settimeout(None)
+
+ self._setup_transport()
+
+ self._write(AMQP_PROTOCOL_HEADER)
+
+
+ def __del__(self):
+ self.close()
+
+
+ def _read(self, n):
+ """
+ Read exactly n bytes from the peer
+
+ """
+ raise NotImplementedError('Must be overriden in subclass')
+
+
+ def _setup_transport(self):
+ """
+ Do any additional initialization of the class (used
+ by the subclasses).
+
+ """
+ pass
+
+
+ def _write(self, s):
+ """
+ Completely write a string to the peer.
+
+ """
+ raise NotImplementedError('Must be overriden in subclass')
+
+
+ def close(self):
+ if self.sock is not None:
+ self.sock.close()
+ self.sock = None
+
+
+ def read_frame(self):
+ """
+ Read an AMQP frame.
+
+ """
+ frame_type, channel, size = unpack('>BHI', self._read(7))
+ payload = self._read(size)
+ ch = self._read(1)
+ if ch == '\xce':
+ return frame_type, channel, payload
+ else:
+ raise Exception('Framing Error, received 0x%02x while expecting 0xce' % ord(ch))
+
+
+ def write_frame(self, frame_type, channel, payload):
+ """
+ Write out an AMQP frame.
+
+ """
+ size = len(payload)
+ self._write(pack('>BHI%dsB' % size,
+ frame_type, channel, size, payload, 0xce))
+
+
+class SSLTransport(_AbstractTransport):
+ """
+ Transport that works over SSL
+
+ """
+ def _setup_transport(self):
+ """
+ Wrap the socket in an SSL object, either the
+ new Python 2.6 version, or the older Python 2.5 and
+ lower version.
+
+ """
+ if HAVE_PY26_SSL:
+ self.sslobj = ssl.wrap_socket(self.sock)
+ self.sslobj.do_handshake()
+ else:
+ self.sslobj = socket.ssl(self.sock)
+
+
+ def _read(self, n):
+ """
+ It seems that SSL Objects read() method may not supply as much
+ as you're asking for, at least with extremely large messages.
+ somewhere > 16K - found this in the test_channel.py test_large
+ unittest.
+
+ """
+ result = self.sslobj.read(n)
+
+ while len(result) < n:
+ s = self.sslobj.read(n - len(result))
+ if not s:
+ raise IOError('Socket closed')
+ result += s
+
+ return result
+
+
+ def _write(self, s):
+ """
+ Write a string out to the SSL socket fully.
+
+ """
+ while s:
+ n = self.sslobj.write(s)
+ if not n:
+ raise IOError('Socket closed')
+ s = s[n:]
+
+
+
+class TCPTransport(_AbstractTransport):
+ """
+ Transport that deals directly with TCP socket.
+
+ """
+ def _setup_transport(self):
+ """
+ Setup to _write() directly to the socket, and
+ do our own buffered reads.
+
+ """
+ self._write = self.sock.sendall
+ self._read_buffer = ''
+
+
+ def _read(self, n):
+ """
+ Read exactly n bytes from the socket
+
+ """
+ while len(self._read_buffer) < n:
+ s = self.sock.recv(65536)
+ if not s:
+ raise IOError('Socket closed')
+ self._read_buffer += s
+
+ result = self._read_buffer[:n]
+ self._read_buffer = self._read_buffer[n:]
+
+ return result
+
+
+def create_transport(host, connect_timeout, ssl=False):
+ """
+ Given a few parameters from the Connection constructor,
+ select and create a subclass of _AbstractTransport.
+
+ """
+ if ssl:
+ return SSLTransport(host, connect_timeout)
+ else:
+ return TCPTransport(host, connect_timeout)
diff --git a/vendor/anyjson/__init__.py b/vendor/anyjson/__init__.py
new file mode 100644
index 0000000000..fdfeece63c
--- /dev/null
+++ b/vendor/anyjson/__init__.py
@@ -0,0 +1,124 @@
+"""Wraps the best available JSON implementation available in a common
+interface"""
+
+import sys
+
+__version__ = "0.2.2"
+__author__ = "Rune Halvorsen <runefh@gmail.com>"
+__homepage__ = "http://bitbucket.org/runeh/anyjson/"
+__docformat__ = "restructuredtext"
+
+implementation = None
+
+"""
+.. function:: serialize(obj)
+
+ Serialize the object to JSON.
+
+.. function:: deserialize(str)
+
+ Deserialize JSON-encoded object to a Python object.
+
+.. function:: force_implementation(name)
+
+ Load a specific json module. This is useful for testing and not much else
+
+.. attribute:: implementation
+
+ The json implementation object. This is probably not useful to you,
+ except to get the name of the implementation in use. The name is
+ available through `implementation.name`.
+
+.. data:: _modules
+
+ List of known json modules, and the names of their serialize/unserialize
+ methods, as well as the exception they throw. Exception can be either
+ an exception class or a string.
+"""
+_modules = [("cjson", "encode", "EncodeError", "decode", "DecodeError"),
+ ("jsonlib2", "write", "WriteError", "read", "ReadError"),
+ ("jsonlib", "write", "WriteError", "read", "ReadError"),
+ ("simplejson", "dumps", TypeError, "loads", ValueError),
+ ("json", "dumps", TypeError, "loads", ValueError),
+ ("django.utils.simplejson", "dumps", TypeError, "loads",
+ ValueError)]
+_fields = ("modname", "encoder", "encerror", "decoder", "decerror")
+
+
+class _JsonImplementation(object):
+ """Incapsulates a JSON implementation"""
+
+ def __init__(self, modspec):
+ modinfo = dict(zip(_fields, modspec))
+
+ # No try block. We want importerror to end up at caller
+ module = self._attempt_load(modinfo["modname"])
+
+ self.implementation = modinfo["modname"]
+ self._encode = getattr(module, modinfo["encoder"])
+ self._decode = getattr(module, modinfo["decoder"])
+ self._encode_error = modinfo["encerror"]
+ self._decode_error = modinfo["decerror"]
+
+ if isinstance(modinfo["encerror"], basestring):
+ self._encode_error = getattr(module, modinfo["encerror"])
+ if isinstance(modinfo["decerror"], basestring):
+ self._decode_error = getattr(module, modinfo["decerror"])
+
+ self.name = modinfo["modname"]
+
+ def __str__(self):
+ return "<_JsonImplementation instance using %s>" % self.name
+
+ def _attempt_load(self, modname):
+ """Attempt to load module name modname, returning it on success,
+ throwing ImportError if module couldn't be imported"""
+ __import__(modname)
+ return sys.modules[modname]
+
+ def serialize(self, data):
+ """Serialize the datastructure to json. Returns a string. Raises
+ TypeError if the object could not be serialized."""
+ try:
+ return self._encode(data)
+ except self._encode_error, exc:
+ raise TypeError(*exc.args)
+
+ def deserialize(self, s):
+ """deserialize the string to python data types. Raises
+ ValueError if the string vould not be parsed."""
+ try:
+ return self._decode(s)
+ except self._decode_error, exc:
+ raise ValueError(*exc.args)
+
+
+def force_implementation(modname):
+ """Forces anyjson to use a specific json module if it's available"""
+ global implementation
+ for name, spec in [(e[0], e) for e in _modules]:
+ if name == modname:
+ implementation = _JsonImplementation(spec)
+ return
+ raise ImportError("No module named: %s" % modname)
+
+
+if __name__ == "__main__":
+ # If run as a script, we do nothing but print an error message.
+ # We do NOT try to load a compatible module because that may throw an
+ # exception, which renders the package uninstallable with easy_install
+ # (It trys to execfile the script when installing, to make sure it works)
+ print "Running anyjson as a stand alone script is not supported"
+ sys.exit(1)
+else:
+ for modspec in _modules:
+ try:
+ implementation = _JsonImplementation(modspec)
+ break
+ except ImportError:
+ pass
+ else:
+ raise ImportError("No supported JSON module found")
+
+ serialize = lambda value: implementation.serialize(value)
+ deserialize = lambda value: implementation.deserialize(value)
diff --git a/vendor/boto/README b/vendor/boto/README
new file mode 100644
index 0000000000..48f68490ac
--- /dev/null
+++ b/vendor/boto/README
@@ -0,0 +1,53 @@
+boto 1.9a
+22-Dec-2009
+
+Copyright (c) 2006-2009 Mitch Garnaat <mitch@garnaat.org>
+
+http://code.google.com/p/boto
+
+Boto is a Python package that provides interfaces to Amazon Web Services.
+At the moment, boto supports:
+
+ * S3 (Simple Storage Service) via the REST API
+ * SQS (SimpleQueue Service) via the Query API
+ * EC2 (Elastic Compute Cloud) via the Query API
+ * Mechanical Turk via the Query API
+ * SimpleDB via the Query API.
+ * CloudFront via the REST API
+ * CloudWatch via the Query API
+ * AutoScale via the Query API
+ * Elastic Load Balancer via the Query API
+
+The intent is to support additional services in the future.
+
+The goal of boto is to provide a very simple, easy to use, lightweight
+wrapper around the Amazon services. Not all features supported by the
+Amazon Web Services will be supported in boto. Basically, those
+features I need to do what I want to do are supported first. Other
+features and requests are welcome and will be accomodated to the best
+of my ability. Patches and contributions are welcome!
+
+Boto was written using Python 2.5.1 on Mac OSX. It has also been tested
+on Linux Ubuntu using Python 2.5.1. Boto requires no additional
+libraries or packages other than those that are distributed with Python 2.5.1.
+Efforts are made to keep boto compatible with Python 2.4.x but no
+guarantees are made. Boto should also run on Python 2.6, albeit with
+a few deprecation warnings.
+
+There is some documentation for boto, mainly in the form of tutorials.
+Check in the doc directory of the distribution. You can also check out
+the unit tests in the tests directory of the distribution for examples of use.
+
+You AWS credentials can be passed into the methods that create S3 and SQS
+connections. Alternatively, boto will check for the existance of the
+following environment variables to ascertain your credentials:
+
+AWS_ACCESS_KEY_ID - Your AWS Access Key ID
+AWS_SECRET_ACCESS_KEY - Your AWS Secret Access Key
+
+Changes
+
+Rather than list changes in the README file, I have decided to refer people to the
+excellent subversion browsing available on googlecode.
+
+http://code.google.com/p/boto/source/browse
diff --git a/vendor/boto/bin/bundle_image b/vendor/boto/bin/bundle_image
new file mode 100755
index 0000000000..7096979082
--- /dev/null
+++ b/vendor/boto/bin/bundle_image
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+from boto.manage.server import Server
+if __name__ == "__main__":
+ from optparse import OptionParser
+ parser = OptionParser(version="%prog 1.0", usage="Usage: %prog [options] instance-id [instance-id-2]")
+
+ # Commands
+ parser.add_option("-b", "--bucket", help="Destination Bucket", dest="bucket", default=None)
+ parser.add_option("-p", "--prefix", help="AMI Prefix", dest="prefix", default=None)
+ parser.add_option("-k", "--key", help="Private Key File", dest="key_file", default=None)
+ parser.add_option("-c", "--cert", help="Public Certificate File", dest="cert_file", default=None)
+ parser.add_option("-s", "--size", help="AMI Size", dest="size", default=None)
+ parser.add_option("-i", "--ssh-key", help="SSH Keyfile", dest="ssh_key", default=None)
+ parser.add_option("-u", "--user-name", help="SSH Username", dest="uname", default="root")
+ parser.add_option("-n", "--name", help="Name of Image", dest="name")
+ (options, args) = parser.parse_args()
+
+ for instance_id in args:
+ try:
+ s = Server.find(instance_id=instance_id).next()
+ print "Found old server object"
+ except StopIteration:
+ print "New Server Object Created"
+ s = Server.create_from_instance_id(instance_id, options.name)
+ assert(s.hostname is not None)
+ b = s.get_bundler(uname=options.uname)
+ b.bundle(bucket=options.bucket,prefix=options.prefix,key_file=options.key_file,cert_file=options.cert_file,size=int(options.size),ssh_key=options.ssh_key)
diff --git a/vendor/boto/bin/cfadmin b/vendor/boto/bin/cfadmin
new file mode 100644
index 0000000000..d44e7405e8
--- /dev/null
+++ b/vendor/boto/bin/cfadmin
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# Author: Chris Moyer
+#
+# cfadmin is similar to sdbadmin for CloudFront, it's a simple
+# console utility to perform the most frequent tasks with CloudFront
+#
+def _print_distributions(dists):
+ """Internal function to print out all the distributions provided"""
+ print "%-12s %-50s %s" % ("Status", "Domain Name", "Origin")
+ print "-"*80
+ for d in dists:
+ print "%-12s %-50s %-30s" % (d.status, d.domain_name, d.origin)
+ for cname in d.cnames:
+ print " "*12, "CNAME => %s" % cname
+ print ""
+
+def help(cf, fnc=None):
+ """Print help message, optionally about a specific function"""
+ import inspect
+ self = sys.modules['__main__']
+ if fnc:
+ try:
+ cmd = getattr(self, fnc)
+ except:
+ cmd = None
+ if not inspect.isfunction(cmd):
+ print "No function named: %s found" % fnc
+ sys.exit(2)
+ (args, varargs, varkw, defaults) = inspect.getargspec(cmd)
+ print cmd.__doc__
+ print "Usage: %s %s" % (fnc, " ".join([ "[%s]" % a for a in args[1:]]))
+ else:
+ print "Usage: cfadmin [command]"
+ for cname in dir(self):
+ if not cname.startswith("_"):
+ cmd = getattr(self, cname)
+ if inspect.isfunction(cmd):
+ doc = cmd.__doc__
+ print "\t%s - %s" % (cname, doc)
+ sys.exit(1)
+
+def ls(cf):
+ """List all distributions and streaming distributions"""
+ print "Standard Distributions"
+ _print_distributions(cf.get_all_distributions())
+ print "Streaming Distributions"
+ _print_distributions(cf.get_all_streaming_distributions())
+
+
+if __name__ == "__main__":
+ import boto
+ import sys
+ cf = boto.connect_cloudfront()
+ self = sys.modules['__main__']
+ if len(sys.argv) >= 2:
+ try:
+ cmd = getattr(self, sys.argv[1])
+ except:
+ cmd = None
+ args = sys.argv[2:]
+ else:
+ cmd = help
+ args = []
+ if not cmd:
+ cmd = help
+ try:
+ cmd(cf, *args)
+ except TypeError, e:
+ print e
+ help(cf, cmd.__name__)
diff --git a/vendor/boto/bin/elbadmin b/vendor/boto/bin/elbadmin
new file mode 100755
index 0000000000..5c139eece3
--- /dev/null
+++ b/vendor/boto/bin/elbadmin
@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+# Copyright (c) 2009 Chris Moyer http://coredumped.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+
+#
+# Elastic Load Balancer Tool
+#
+VERSION="0.1"
+usage = """%prog [options] [command]
+Commands:
+ list|ls List all Elastic Load Balancers
+ delete <name> Delete ELB <name>
+ get <name> Get all instances associated with <name>
+ create <name> Create an ELB
+ add <name> <instance> Add <instance> in ELB <name>
+ remove|rm <name> <instance> Remove <instance> from ELB <name>
+ enable|en <name> <zone> Enable Zone <zone> for ELB <name>
+ disable <name> <zone> Disable Zone <zone> for ELB <name>
+"""
+
+def list(elb):
+ """List all ELBs"""
+ print "%-20s %s" % ("Name", "DNS Name")
+ print "-"*80
+ for b in elb.get_all_load_balancers():
+ print "%-20s %s" % (b.name, b.dns_name)
+
+def get(elb, name):
+ """Get details about ELB <name>"""
+ b = elb.get_all_load_balancers(name)
+ if len(b) < 1:
+ print "No load balancer by the name of %s found" % name
+ return
+ b = b[0]
+
+ print "Name: %s" % b.name
+ print "DNS Name: %s" % b.dns_name
+
+ print
+
+ print "Listeners"
+ print "---------"
+ print "%-8s %-8s %s" % ("IN", "OUT", "PROTO")
+ for l in b.listeners:
+ print "%-8s %-8s %s" % (l[0], l[1], l[2])
+
+ print
+
+ print " Zones "
+ print "---------"
+ for z in b.availability_zones:
+ print z
+
+ print
+
+ print "Instances"
+ print "---------"
+ for i in b.instances:
+ print i.id
+
+ print
+
+def create(elb, name, zones, listeners):
+ """Create an ELB named <name>"""
+ l_list = []
+ for l in listeners:
+ l = l.split(",")
+ l_list.append((int(l[0]), int(l[1]), l[2]))
+
+ b = elb.create_load_balancer(name, zones, l_list)
+ return get(elb, name)
+
+def delete(elb, name):
+ """Delete this ELB"""
+ b = elb.get_all_load_balancers(name)
+ if len(b) < 1:
+ print "No load balancer by the name of %s found" % name
+ return
+ b = b[0]
+ b.delete()
+ print "Load Balancer %s deleted" % name
+
+def add_instance(elb, name, instance):
+ """Add <instance> to ELB <name>"""
+ b = elb.get_all_load_balancers(name)
+ if len(b) < 1:
+ print "No load balancer by the name of %s found" % name
+ return
+ b = b[0]
+ b.register_instances([instance])
+ return get(elb, name)
+
+
+def remove_instance(elb, name, instance):
+ """Remove instance from elb <name>"""
+ b = elb.get_all_load_balancers(name)
+ if len(b) < 1:
+ print "No load balancer by the name of %s found" % name
+ return
+ b = b[0]
+ b.deregister_instances([instance])
+ return get(elb, name)
+
+def enable_zone(elb, name, zone):
+ """Enable <zone> for elb"""
+ b = elb.get_all_load_balancers(name)
+ if len(b) < 1:
+ print "No load balancer by the name of %s found" % name
+ return
+ b = b[0]
+ b.enable_zones([zone])
+ return get(elb, name)
+
+def disable_zone(elb, name, zone):
+ """Disable <zone> for elb"""
+ b = elb.get_all_load_balancers(name)
+ if len(b) < 1:
+ print "No load balancer by the name of %s found" % name
+ return
+ b = b[0]
+ b.disable_zones([zone])
+ return get(elb, name)
+
+
+
+if __name__ == "__main__":
+ try:
+ import readline
+ except ImportError:
+ pass
+ import boto
+ import sys
+ from optparse import OptionParser
+ from boto.mashups.iobject import IObject
+ parser = OptionParser(version=VERSION, usage=usage)
+ parser.add_option("-z", "--zone", help="Operate on zone", action="append", default=[], dest="zones")
+ parser.add_option("-l", "--listener", help="Specify Listener in,out,proto", action="append", default=[], dest="listeners")
+
+ (options, args) = parser.parse_args()
+
+ if len(args) < 1:
+ parser.print_help()
+ sys.exit(1)
+
+ elb = boto.connect_elb()
+ command = args[0].lower()
+ if command in ("ls", "list"):
+ list(elb)
+ elif command == "get":
+ get(elb, args[1])
+ elif command == "create":
+ create(elb, args[1], options.zones, options.listeners)
+ elif command == "delete":
+ delete(elb, args[1])
+ elif command in ("add", "put"):
+ add_instance(elb, args[1], args[2])
+ elif command in ("rm", "remove"):
+ remove_instance(elb, args[1], args[2])
+ elif command in ("en", "enable"):
+ enable_zone(elb, args[1], args[2])
+ elif command == "disable":
+ disable_zone(elb, args[1], args[2])
diff --git a/vendor/boto/bin/fetch_file b/vendor/boto/bin/fetch_file
new file mode 100755
index 0000000000..6b8c4da90f
--- /dev/null
+++ b/vendor/boto/bin/fetch_file
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# Copyright (c) 2009 Chris Moyer http://coredumped.org
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+if __name__ == "__main__":
+ from optparse import OptionParser
+ parser = OptionParser(version="0.1", usage="Usage: %prog [options] url")
+ parser.add_option("-o", "--out-file", help="Output file", dest="outfile")
+
+ (options, args) = parser.parse_args()
+ if len(args) < 1:
+ parser.print_help()
+ exit(1)
+ from boto.utils import fetch_file
+ f = fetch_file(args[0])
+ if options.outfile:
+ open(options.outfile, "w").write(f.read())
+ else:
+ print f.read()
diff --git a/vendor/boto/bin/kill_instance b/vendor/boto/bin/kill_instance
new file mode 100644
index 0000000000..7418d46189
--- /dev/null
+++ b/vendor/boto/bin/kill_instance
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+def kill_instance(instance_id):
+ """Kill an instance given it's instance ID"""
+ import boto
+ ec2 = boto.connect_ec2()
+ print "Stopping instance: %s" % instance_id
+ ec2.terminate_instances([instance_id])
+
+
+if __name__ == "__main__":
+ import sys
+ kill_instance(sys.argv[1])
diff --git a/vendor/boto/bin/launch_instance b/vendor/boto/bin/launch_instance
new file mode 100755
index 0000000000..69a9f81b15
--- /dev/null
+++ b/vendor/boto/bin/launch_instance
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+# Copyright (c) 2009 Chris Moyer http://coredumped.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+
+#
+# Utility to launch an EC2 Instance
+#
+VERSION="0.1"
+
+import boto.pyami.config
+from boto.utils import fetch_file
+import re, os
+import ConfigParser
+
+class Config(boto.pyami.config.Config):
+ """A special config class that also adds import abilities
+ Directly in the config file. To have a config file import
+ another config file, simply use "#import <path>" where <path>
+ is either a relative path or a full URL to another config
+ """
+
+ def __init__(self):
+ ConfigParser.SafeConfigParser.__init__(self, {'working_dir' : '/mnt/pyami', 'debug' : '0'})
+
+ def add_config(self, file_url):
+ """Add a config file to this configuration
+ :param file_url: URL for the file to add, or a local path
+ :type file_url: str
+ """
+ if not re.match("^([a-zA-Z0-9]*:\/\/)(.*)", file_url):
+ if not file_url.startswith("/"):
+ file_url = os.path.join(os.getcwd(), file_url)
+ file_url = "file://%s" % file_url
+ (base_url, file_name) = file_url.rsplit("/", 1)
+ base_config = fetch_file(file_url)
+ base_config.seek(0)
+ for line in base_config.readlines():
+ match = re.match("^#import[\s\t]*([^\s^\t]*)[\s\t]*$", line)
+ if match:
+ self.add_config("%s/%s" % (base_url, match.group(1)))
+ base_config.seek(0)
+ self.readfp(base_config)
+
+ def add_creds(self, ec2):
+ """Add the credentials to this config if they don't already exist"""
+ if not self.has_section('Credentials'):
+ self.add_section('Credentials')
+ self.set('Credentials', 'aws_access_key_id', ec2.aws_access_key_id)
+ self.set('Credentials', 'aws_secret_access_key', ec2.aws_secret_access_key)
+
+
+ def __str__(self):
+ """Get config as string"""
+ from StringIO import StringIO
+ s = StringIO()
+ self.write(s)
+ return s.getvalue()
+
+
+if __name__ == "__main__":
+ try:
+ import readline
+ except ImportError:
+ pass
+ import boto
+ from optparse import OptionParser
+ from boto.mashups.iobject import IObject
+ parser = OptionParser(version=VERSION, usage="%prog [options] config_url")
+ parser.add_option("-c", "--max-count", help="Maximum number of this type of instance to launch", dest="max_count", default="1")
+ parser.add_option("--min-count", help="Minimum number of this type of instance to launch", dest="min_count", default="1")
+ parser.add_option("-g", "--groups", help="Security Groups to add this instance to", action="append", dest="groups")
+ parser.add_option("-a", "--ami", help="AMI to launch", dest="ami_id")
+ parser.add_option("-t", "--type", help="Type of Instance (default m1.small)", dest="type", default="m1.small")
+ parser.add_option("-k", "--key", help="Keypair", dest="key_name")
+ parser.add_option("-z", "--zone", help="Zone (default us-east-1a)", dest="zone", default="us-east-1a")
+ parser.add_option("-i", "--ip", help="Elastic IP", dest="elastic_ip")
+ parser.add_option("-n", "--no-add-cred", help="Don't add a credentials section", default=False, action="store_true", dest="nocred")
+
+ (options, args) = parser.parse_args()
+
+ if len(args) < 1:
+ import sys
+ parser.print_help()
+ sys.exit(1)
+ file_url = os.path.expanduser(args[0])
+ ec2 = boto.connect_ec2()
+
+ cfg = Config()
+ cfg.add_config(file_url)
+ if not options.nocred:
+ cfg.add_creds(ec2)
+
+ iobj = IObject()
+ if options.ami_id:
+ ami = ec2.get_image(options.ami_id)
+ else:
+ ami_id = options.ami_id
+ l = [(a, a.id, a.location) for a in ec2.get_all_images()]
+ ami = iobj.choose_from_list(l, prompt='Choose AMI')
+
+ if options.key_name:
+ key_name = options.key_name
+ else:
+ l = [(k, k.name, '') for k in ec2.get_all_key_pairs()]
+ key_name = iobj.choose_from_list(l, prompt='Choose Keypair').name
+
+ if options.groups:
+ groups = options.groups
+ else:
+ groups = []
+ l = [(g, g.name, g.description) for g in ec2.get_all_security_groups()]
+ g = iobj.choose_from_list(l, prompt='Choose Primary Security Group')
+ while g != None:
+ groups.append(g)
+ l.remove((g, g.name, g.description))
+ g = iobj.choose_from_list(l, prompt='Choose Additional Security Group (0 to quit)')
+
+ r = ami.run(min_count=int(options.min_count), max_count=int(options.max_count),
+ key_name=key_name, user_data=str(cfg),
+ security_groups=groups, instance_type=options.type,
+ placement=options.zone)
diff --git a/vendor/boto/bin/list_instances b/vendor/boto/bin/list_instances
new file mode 100755
index 0000000000..19e1c9b1d2
--- /dev/null
+++ b/vendor/boto/bin/list_instances
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import boto
+ec2 = boto.connect_ec2()
+
+print "%-15s %-15s %-30s %s" % ("ID", 'Zone', "Groups", "Hostname")
+print "-"*105
+for r in ec2.get_all_instances():
+ groups = [g.id for g in r.groups]
+ for i in r.instances:
+ print "%-15s %-15s %-30s %s" % (i.id, i.placement, ','.join(groups), i.public_dns_name)
diff --git a/vendor/boto/bin/pyami_sendmail b/vendor/boto/bin/pyami_sendmail
new file mode 100755
index 0000000000..78e30039b7
--- /dev/null
+++ b/vendor/boto/bin/pyami_sendmail
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+# Copyright (c) 2010 Chris Moyer http://coredumped.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+
+#
+# Send Mail from a PYAMI instance, or anything that has a boto.cfg
+# properly set up
+#
+VERSION="0.1"
+usage = """%prog [options]
+Sends whatever is on stdin to the recipient specified by your boto.cfg
+or whoevery you specify in the options here.
+"""
+
+if __name__ == "__main__":
+ from boto.utils import notify
+ import sys
+ from optparse import OptionParser
+ parser = OptionParser(version=VERSION, usage=usage)
+ parser.add_option("-t", "--to", help="Optional to address to send to (default from your boto.cfg)", action="store", default=None, dest="to")
+ parser.add_option("-s", "--subject", help="Optional Subject to send this report as", action="store", default="Report", dest="subject")
+ parser.add_option("-f", "--file", help="Optionally, read from a file instead of STDIN", action="store", default=None, dest="file")
+
+ (options, args) = parser.parse_args()
+ if options.file:
+ body = open(options.file, 'r').read()
+ else:
+ body = sys.stdin.read()
+
+ notify(options.subject, body=body, to_string=options.to)
diff --git a/vendor/boto/bin/s3put b/vendor/boto/bin/s3put
new file mode 100755
index 0000000000..b5467d96b2
--- /dev/null
+++ b/vendor/boto/bin/s3put
@@ -0,0 +1,196 @@
+#!/usr/bin/env python
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+import getopt, sys, os
+import boto
+from boto.exception import S3ResponseError
+
+usage_string = """
+SYNOPSIS
+ s3put [-a/--access_key <access_key>] [-s/--secret_key <secret_key>]
+ -b/--bucket <bucket_name> [-c/--callback <num_cb>]
+ [-d/--debug <debug_level>] [-i/--ignore <ignore_dirs>]
+ [-n/--no_op] [-p/--prefix <prefix>] [-q/--quiet]
+ [-g/--grant grant] [-w/--no_overwrite] path
+
+ Where
+ access_key - Your AWS Access Key ID. If not supplied, boto will
+ use the value of the environment variable
+ AWS_ACCESS_KEY_ID
+ secret_key - Your AWS Secret Access Key. If not supplied, boto
+ will use the value of the environment variable
+ AWS_SECRET_ACCESS_KEY
+ bucket_name - The name of the S3 bucket the file(s) should be
+ copied to.
+ path - A path to a directory or file that represents the items
+ to be uploaded. If the path points to an individual file,
+ that file will be uploaded to the specified bucket. If the
+ path points to a directory, s3_it will recursively traverse
+ the directory and upload all files to the specified bucket.
+ debug_level - 0 means no debug output (default), 1 means normal
+ debug output from boto, and 2 means boto debug output
+ plus request/response output from httplib
+ ignore_dirs - a comma-separated list of directory names that will
+ be ignored and not uploaded to S3.
+ num_cb - The number of progress callbacks to display. The default
+ is zero which means no callbacks. If you supplied a value
+ of "-c 10" for example, the progress callback would be
+ called 10 times for each file transferred.
+ prefix - A file path prefix that will be stripped from the full
+ path of the file when determining the key name in S3.
+ For example, if the full path of a file is:
+ /home/foo/bar/fie.baz
+ and the prefix is specified as "-p /home/foo/" the
+ resulting key name in S3 will be:
+ /bar/fie.baz
+ The prefix must end in a trailing separator and if it
+ does not then one will be added.
+ grant - A canned ACL policy that will be granted on each file
+ transferred to S3. The value of provided must be one
+ of the "canned" ACL policies supported by S3:
+ private|public-read|public-read-write|authenticated-read
+ no_overwrite - No files will be overwritten on S3, if the file/key
+ exists on s3 it will be kept. This is useful for
+ resuming interrupted transfers. Note this is not a
+ sync, even if the file has been updated locally if
+ the key exists on s3 the file on s3 will not be
+ updated.
+
+ If the -n option is provided, no files will be transferred to S3 but
+ informational messages will be printed about what would happen.
+"""
+def usage():
+ print usage_string
+ sys.exit()
+
+def submit_cb(bytes_so_far, total_bytes):
+ print '%d bytes transferred / %d bytes total' % (bytes_so_far, total_bytes)
+
+def get_key_name(fullpath, prefix):
+ key_name = fullpath[len(prefix):]
+ l = key_name.split(os.sep)
+ return '/'.join(l)
+
+def main():
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'a:b:c::d:g:hi:np:qs:vw',
+ ['access_key', 'bucket', 'callback', 'debug', 'help', 'grant',
+ 'ignore', 'no_op', 'prefix', 'quiet', 'secret_key', 'no_overwrite'])
+ except:
+ usage()
+ ignore_dirs = []
+ aws_access_key_id = None
+ aws_secret_access_key = None
+ bucket_name = ''
+ total = 0
+ debug = 0
+ cb = None
+ num_cb = 0
+ quiet = False
+ no_op = False
+ prefix = '/'
+ grant = None
+ no_overwrite = False
+ for o, a in opts:
+ if o in ('-h', '--help'):
+ usage()
+ sys.exit()
+ if o in ('-a', '--access_key'):
+ aws_access_key_id = a
+ if o in ('-b', '--bucket'):
+ bucket_name = a
+ if o in ('-c', '--callback'):
+ num_cb = int(a)
+ cb = submit_cb
+ if o in ('-d', '--debug'):
+ debug = int(a)
+ if o in ('-g', '--grant'):
+ grant = a
+ if o in ('-i', '--ignore'):
+ ignore_dirs = a.split(',')
+ if o in ('-n', '--no_op'):
+ no_op = True
+ if o in ('w', '--no_overwrite'):
+ no_overwrite = True
+ if o in ('-p', '--prefix'):
+ prefix = a
+ if prefix[-1] != os.sep:
+ prefix = prefix + os.sep
+ if o in ('-q', '--quiet'):
+ quiet = True
+ if o in ('-s', '--secret_key'):
+ aws_secret_access_key = a
+ if len(args) != 1:
+ print usage()
+ path = os.path.expanduser(args[0])
+ path = os.path.expandvars(path)
+ path = os.path.abspath(path)
+ if bucket_name:
+ c = boto.connect_s3(aws_access_key_id=aws_access_key_id,
+ aws_secret_access_key=aws_secret_access_key)
+ c.debug = debug
+ b = c.get_bucket(bucket_name)
+ if os.path.isdir(path):
+ if no_overwrite:
+ if not quiet:
+ print 'Getting list of existing keys to check against'
+ keys = []
+ for key in b.list():
+ keys.append(key.name)
+ for root, dirs, files in os.walk(path):
+ for ignore in ignore_dirs:
+ if ignore in dirs:
+ dirs.remove(ignore)
+ for file in files:
+ fullpath = os.path.join(root, file)
+ key_name = get_key_name(fullpath, prefix)
+ copy_file = True
+ if no_overwrite:
+ if key_name in keys:
+ copy_file = False
+ if not quiet:
+ print 'Skipping %s as it exists in s3' % file
+ if copy_file:
+ if not quiet:
+ print 'Copying %s to %s/%s' % (file, bucket_name, key_name)
+ if not no_op:
+ k = b.new_key(key_name)
+ k.set_contents_from_filename(fullpath, cb=cb,
+ num_cb=num_cb, policy=grant)
+ total += 1
+ elif os.path.isfile(path):
+ key_name = os.path.split(path)[1]
+ copy_file = True
+ if no_overwrite:
+ if b.get_key(key_name):
+ copy_file = False
+ if not quiet:
+ print 'Skipping %s as it exists in s3' % path
+ if copy_file:
+ k = b.new_key(key_name)
+ k.set_contents_from_filename(path, cb=cb, num_cb=num_cb, policy=grant)
+ else:
+ print usage()
+
+if __name__ == "__main__":
+ main()
+
diff --git a/vendor/boto/bin/sdbadmin b/vendor/boto/bin/sdbadmin
new file mode 100755
index 0000000000..e8ff9b52bd
--- /dev/null
+++ b/vendor/boto/bin/sdbadmin
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+# Copyright (c) 2009 Chris Moyer http://kopertop.blogspot.com/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+
+#
+# Tools to dump and recover an SDB domain
+#
+VERSION = "%prog version 1.0"
+import boto
+import time
+from boto import sdb
+
+def choice_input(options, default=None, title=None):
+ """
+ Choice input
+ """
+ if title == None:
+ title = "Please choose"
+ print title
+ objects = []
+ for n, obj in enumerate(options):
+ print "%s: %s" % (n, obj)
+ objects.append(obj)
+ choice = int(raw_input(">>> "))
+ try:
+ choice = objects[choice]
+ except:
+ choice = default
+ return choice
+
+def confirm(message="Are you sure?"):
+ choice = raw_input("%s [yN] " % message)
+ return choice and len(choice) > 0 and choice[0].lower() == "y"
+
+
+def dump_db(domain, file_name):
+ """
+ Dump SDB domain to file
+ """
+ doc = domain.to_xml(open(file_name, "w"))
+
+def empty_db(domain):
+ """
+ Remove all entries from domain
+ """
+ for item in domain:
+ item.delete()
+
+def load_db(domain, file):
+ """
+ Load a domain from a file, this doesn't overwrite any existing
+ data in the file so if you want to do a full recovery and restore
+ you need to call empty_db before calling this
+
+ :param domain: The SDB Domain object to load to
+ :param file: The File to load the DB from
+ """
+ domain.from_xml(file)
+
+def create_db(domain_name, region_name):
+ """Create a new DB
+
+ :param domain: Name of the domain to create
+ :type domain: str
+ """
+ sdb = boto.sdb.connect_to_region(region_name)
+ return sdb.create_domain(domain_name)
+
+if __name__ == "__main__":
+ from optparse import OptionParser
+ parser = OptionParser(version=VERSION, usage="Usage: %prog [--dump|--load|--empty|--list|-l] [options]")
+
+ # Commands
+ parser.add_option("--dump", help="Dump domain to file", dest="dump", default=False, action="store_true")
+ parser.add_option("--load", help="Load domain contents from file", dest="load", default=False, action="store_true")
+ parser.add_option("--empty", help="Empty all contents of domain", dest="empty", default=False, action="store_true")
+ parser.add_option("-l", "--list", help="List All domains", dest="list", default=False, action="store_true")
+ parser.add_option("-c", "--create", help="Create domain", dest="create", default=False, action="store_true")
+
+ parser.add_option("-a", "--all-domains", help="Operate on all domains", action="store_true", default=False, dest="all_domains")
+ parser.add_option("-d", "--domain", help="Do functions on domain (may be more then one)", action="append", dest="domains")
+ parser.add_option("-f", "--file", help="Input/Output file we're operating on", dest="file_name")
+ parser.add_option("-r", "--region", help="Region (e.g. us-east-1[default] or eu-west-1)", default="us-east-1", dest="region_name")
+ (options, args) = parser.parse_args()
+
+ if options.create:
+ for domain_name in options.domains:
+ create_db(domain_name, options.region_name)
+ exit()
+
+ sdb = boto.sdb.connect_to_region(options.region_name)
+ if options.list:
+ for db in sdb.get_all_domains():
+ print db
+ exit()
+
+ if not options.dump and not options.load and not options.empty:
+ parser.print_help()
+ exit()
+
+
+
+
+ #
+ # Setup
+ #
+ if options.domains:
+ domains = []
+ for domain_name in options.domains:
+ domains.append(sdb.get_domain(domain_name))
+ elif options.all_domains:
+ domains = sdb.get_all_domains()
+ else:
+ domains = [choice_input(options=sdb.get_all_domains(), title="No domain specified, please choose one")]
+
+
+ #
+ # Execute the commands
+ #
+ stime = time.time()
+ if options.empty:
+ if confirm("WARNING!!! Are you sure you want to empty the following domains?: %s" % domains):
+ stime = time.time()
+ for domain in domains:
+ print "--------> Emptying %s <--------" % domain.name
+ empty_db(domain)
+ else:
+ print "Canceling operations"
+ exit()
+
+ if options.dump:
+ for domain in domains:
+ print "--------> Dumping %s <---------" % domain.name
+ if options.file_name:
+ file_name = options.file_name
+ else:
+ file_name = "%s.db" % domain.name
+ dump_db(domain, file_name)
+
+ if options.load:
+ for domain in domains:
+ print "---------> Loading %s <----------" % domain.name
+ if options.file_name:
+ file_name = options.file_name
+ else:
+ file_name = "%s.db" % domain.name
+ load_db(domain, open(file_name, "rb"))
+
+
+ total_time = round(time.time() - stime, 2)
+ print "--------> Finished in %s <--------" % total_time
diff --git a/vendor/boto/bin/taskadmin b/vendor/boto/bin/taskadmin
new file mode 100755
index 0000000000..5d5302adc9
--- /dev/null
+++ b/vendor/boto/bin/taskadmin
@@ -0,0 +1,116 @@
+#!/usr/bin/env python
+# Copyright (c) 2009 Chris Moyer http://coredumped.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+
+#
+# Task/Job Administration utility
+#
+VERSION="0.1"
+__version__ = VERSION
+usage = """%prog [options] [command]
+Commands:
+ list|ls List all Tasks in SDB
+ delete <id> Delete Task with id <id>
+ get <name> Get Task <name>
+ create|mk <name> <hour> <command> Create a new Task <name> with command <command> running every <hour>
+"""
+
+def list():
+ """List all Tasks in SDB"""
+ from boto.manage.task import Task
+ print "%-8s %-40s %s" % ("Hour", "Name", "Command")
+ print "-"*100
+ for t in Task.all():
+ print "%-8s %-40s %s" % (t.hour, t.name, t.command)
+
+def get(name):
+ """Get a task
+ :param name: The name of the task to fetch
+ :type name: str
+ """
+ from boto.manage.task import Task
+ q = Task.find()
+ q.filter("name like", "%s%%" % name)
+ for t in q:
+ print "="*80
+ print "| ", t.id
+ print "|%s" % ("-"*79)
+ print "| Name: ", t.name
+ print "| Hour: ", t.hour
+ print "| Command: ", t.command
+ if t.last_executed:
+ print "| Last Run: ", t.last_executed.ctime()
+ print "| Last Status: ", t.last_status
+ print "| Last Run Log: ", t.last_output
+ print "="*80
+
+def delete(id):
+ from boto.manage.task import Task
+ t = Task.get_by_id(id)
+ print "Deleting task: %s" % t.name
+ if raw_input("Are you sure? ").lower() in ["y", "yes"]:
+ t.delete()
+ print "Deleted"
+ else:
+ print "Canceled"
+
+def create(name, hour, command):
+ """Create a new task
+ :param name: Name of the task to create
+ :type name: str
+ :param hour: What hour to run it at, "*" for every hour
+ :type hour: str
+ :param command: The command to execute
+ :type command: str
+ """
+ from boto.manage.task import Task
+ t = Task()
+ t.name = name
+ t.hour = hour
+ t.command = command
+ t.put()
+ print "Created task: %s" % t.id
+
+if __name__ == "__main__":
+ try:
+ import readline
+ except ImportError:
+ pass
+ import boto
+ import sys
+ from optparse import OptionParser
+ from boto.mashups.iobject import IObject
+ parser = OptionParser(version=__version__, usage=usage)
+
+ (options, args) = parser.parse_args()
+
+ if len(args) < 1:
+ parser.print_help()
+ sys.exit(1)
+
+ command = args[0].lower()
+ if command in ("ls", "list"):
+ list()
+ elif command == "get":
+ get(args[1])
+ elif command == "create":
+ create(args[1], args[2], args[3])
+ elif command == "delete":
+ delete(args[1])
diff --git a/vendor/boto/boto/__init__.py b/vendor/boto/boto/__init__.py
new file mode 100644
index 0000000000..051c903653
--- /dev/null
+++ b/vendor/boto/boto/__init__.py
@@ -0,0 +1,292 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+from boto.pyami.config import Config, BotoConfigLocations
+import os, sys
+import logging
+import logging.config
+
+Version = '1.9b'
+UserAgent = 'Boto/%s (%s)' % (Version, sys.platform)
+config = Config()
+
+def init_logging():
+ for file in BotoConfigLocations:
+ try:
+ logging.config.fileConfig(os.path.expanduser(file))
+ except:
+ pass
+
+class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+
+log = logging.getLogger('boto')
+log.addHandler(NullHandler())
+init_logging()
+
+# convenience function to set logging to a particular file
+def set_file_logger(name, filepath, level=logging.INFO, format_string=None):
+ global log
+ if not format_string:
+ format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s"
+ logger = logging.getLogger(name)
+ logger.setLevel(level)
+ fh = logging.FileHandler(filepath)
+ fh.setLevel(level)
+ formatter = logging.Formatter(format_string)
+ fh.setFormatter(formatter)
+ logger.addHandler(fh)
+ log = logger
+
+def set_stream_logger(name, level=logging.DEBUG, format_string=None):
+ global log
+ if not format_string:
+ format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s"
+ logger = logging.getLogger(name)
+ logger.setLevel(level)
+ fh = logging.StreamHandler()
+ fh.setLevel(level)
+ formatter = logging.Formatter(format_string)
+ fh.setFormatter(formatter)
+ logger.addHandler(fh)
+ log = logger
+
+def connect_sqs(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.sqs.connection.SQSConnection`
+ :return: A connection to Amazon's SQS
+ """
+ from boto.sqs.connection import SQSConnection
+ return SQSConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_s3(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.s3.connection.S3Connection`
+ :return: A connection to Amazon's S3
+ """
+ from boto.s3.connection import S3Connection
+ return S3Connection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_ec2(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.ec2.connection.EC2Connection`
+ :return: A connection to Amazon's EC2
+ """
+ from boto.ec2.connection import EC2Connection
+ return EC2Connection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_elb(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.ec2.elb.ELBConnection`
+ :return: A connection to Amazon's Load Balancing Service
+ """
+ from boto.ec2.elb import ELBConnection
+ return ELBConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_autoscale(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.ec2.autoscale.AutoScaleConnection`
+ :return: A connection to Amazon's Auto Scaling Service
+ """
+ from boto.ec2.autoscale import AutoScaleConnection
+ return AutoScaleConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_cloudwatch(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.ec2.cloudwatch.CloudWatchConnection`
+ :return: A connection to Amazon's EC2 Monitoring service
+ """
+ from boto.ec2.cloudwatch import CloudWatchConnection
+ return CloudWatchConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_sdb(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.sdb.connection.SDBConnection`
+ :return: A connection to Amazon's SDB
+ """
+ from boto.sdb.connection import SDBConnection
+ return SDBConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_fps(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.fps.connection.FPSConnection`
+ :return: A connection to FPS
+ """
+ from boto.fps.connection import FPSConnection
+ return FPSConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_cloudfront(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.fps.connection.FPSConnection`
+ :return: A connection to FPS
+ """
+ from boto.cloudfront import CloudFrontConnection
+ return CloudFrontConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_vpc(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.vpc.VPCConnection`
+ :return: A connection to VPC
+ """
+ from boto.vpc import VPCConnection
+ return VPCConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_rds(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.rds.RDSConnection`
+ :return: A connection to RDS
+ """
+ from boto.rds import RDSConnection
+ return RDSConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_emr(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.emr.EmrConnection`
+ :return: A connection to Elastic mapreduce
+ """
+ from boto.emr import EmrConnection
+ return EmrConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+def connect_sns(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
+ """
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ :rtype: :class:`boto.sns.SNSConnection`
+ :return: A connection to Amazon's SNS
+ """
+ from boto.sns import SNSConnection
+ return SNSConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
+
+
+def check_extensions(module_name, module_path):
+ """
+ This function checks for extensions to boto modules. It should be called in the
+ __init__.py file of all boto modules. See:
+ http://code.google.com/p/boto/wiki/ExtendModules
+
+ for details.
+ """
+ option_name = '%s_extend' % module_name
+ version = config.get('Boto', option_name, None)
+ if version:
+ dirname = module_path[0]
+ path = os.path.join(dirname, version)
+ if os.path.isdir(path):
+ log.info('extending module %s with: %s' % (module_name, path))
+ module_path.insert(0, path)
+
+_aws_cache = {}
+
+def _get_aws_conn(service):
+ global _aws_cache
+ conn = _aws_cache.get(service)
+ if not conn:
+ meth = getattr(sys.modules[__name__], 'connect_'+service)
+ conn = meth()
+ _aws_cache[service] = conn
+ return conn
+
+def lookup(service, name):
+ global _aws_cache
+ conn = _get_aws_conn(service)
+ obj = _aws_cache.get('.'.join((service,name)), None)
+ if not obj:
+ obj = conn.lookup(name)
+ _aws_cache['.'.join((service,name))] = obj
+ return obj
+
diff --git a/vendor/boto/boto/cloudfront/__init__.py b/vendor/boto/boto/cloudfront/__init__.py
new file mode 100644
index 0000000000..28309ff935
--- /dev/null
+++ b/vendor/boto/boto/cloudfront/__init__.py
@@ -0,0 +1,223 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+import xml.sax
+import base64
+import time
+from boto.connection import AWSAuthConnection
+from boto import handler
+from boto.cloudfront.distribution import Distribution, DistributionSummary, DistributionConfig
+from boto.cloudfront.distribution import StreamingDistribution, StreamingDistributionSummary, StreamingDistributionConfig
+from boto.cloudfront.identity import OriginAccessIdentity
+from boto.cloudfront.identity import OriginAccessIdentitySummary
+from boto.cloudfront.identity import OriginAccessIdentityConfig
+from boto.resultset import ResultSet
+from boto.cloudfront.exception import CloudFrontServerError
+
+class CloudFrontConnection(AWSAuthConnection):
+
+ DefaultHost = 'cloudfront.amazonaws.com'
+ Version = '2009-12-01'
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ port=None, proxy=None, proxy_port=None,
+ host=DefaultHost, debug=0):
+ AWSAuthConnection.__init__(self, host,
+ aws_access_key_id, aws_secret_access_key,
+ True, port, proxy, proxy_port, debug=debug)
+
+ def get_etag(self, response):
+ response_headers = response.msg
+ for key in response_headers.keys():
+ if key.lower() == 'etag':
+ return response_headers[key]
+ return None
+
+ def add_aws_auth_header(self, headers, method, path):
+ if not headers.has_key('Date'):
+ headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
+ time.gmtime())
+
+ hmac = self.hmac.copy()
+ hmac.update(headers['Date'])
+ b64_hmac = base64.encodestring(hmac.digest()).strip()
+ headers['Authorization'] = "AWS %s:%s" % (self.aws_access_key_id, b64_hmac)
+
+ # Generics
+
+ def _get_all_objects(self, resource, tags):
+ if not tags:
+ tags=[('DistributionSummary', DistributionSummary)]
+ response = self.make_request('GET', '/%s/%s' % (self.Version, resource))
+ body = response.read()
+ if response.status >= 300:
+ raise CloudFrontServerError(response.status, response.reason, body)
+ rs = ResultSet(tags)
+ h = handler.XmlHandler(rs, self)
+ xml.sax.parseString(body, h)
+ return rs
+
+ def _get_info(self, id, resource, dist_class):
+ uri = '/%s/%s/%s' % (self.Version, resource, id)
+ response = self.make_request('GET', uri)
+ body = response.read()
+ if response.status >= 300:
+ raise CloudFrontServerError(response.status, response.reason, body)
+ d = dist_class(connection=self)
+ response_headers = response.msg
+ for key in response_headers.keys():
+ if key.lower() == 'etag':
+ d.etag = response_headers[key]
+ h = handler.XmlHandler(d, self)
+ xml.sax.parseString(body, h)
+ return d
+
+ def _get_config(self, id, resource, config_class):
+ uri = '/%s/%s/%s/config' % (self.Version, resource, id)
+ response = self.make_request('GET', uri)
+ body = response.read()
+ if response.status >= 300:
+ raise CloudFrontServerError(response.status, response.reason, body)
+ d = config_class(connection=self)
+ d.etag = self.get_etag(response)
+ h = handler.XmlHandler(d, self)
+ xml.sax.parseString(body, h)
+ return d
+
+ def _set_config(self, distribution_id, etag, config):
+ if isinstance(config, StreamingDistributionConfig):
+ resource = 'streaming-distribution'
+ else:
+ resource = 'distribution'
+ uri = '/%s/%s/%s/config' % (self.Version, resource, distribution_id)
+ headers = {'If-Match' : etag, 'Content-Type' : 'text/xml'}
+ response = self.make_request('PUT', uri, headers, config.to_xml())
+ body = response.read()
+ return self.get_etag(response)
+ if response.status != 200:
+ raise CloudFrontServerError(response.status, response.reason, body)
+
+ def _create_object(self, config, resource, dist_class):
+ response = self.make_request('POST', '/%s/%s' % (self.Version, resource),
+ {'Content-Type' : 'text/xml'}, data=config.to_xml())
+ body = response.read()
+ if response.status == 201:
+ d = dist_class(connection=self)
+ h = handler.XmlHandler(d, self)
+ xml.sax.parseString(body, h)
+ return d
+ else:
+ raise CloudFrontServerError(response.status, response.reason, body)
+
+ def _delete_object(self, id, etag, resource):
+ uri = '/%s/%s/%s' % (self.Version, resource, id)
+ response = self.make_request('DELETE', uri, {'If-Match' : etag})
+ body = response.read()
+ if response.status != 204:
+ raise CloudFrontServerError(response.status, response.reason, body)
+
+ # Distributions
+
+ def get_all_distributions(self):
+ tags=[('DistributionSummary', DistributionSummary)]
+ return self._get_all_objects('distribution', tags)
+
+ def get_distribution_info(self, distribution_id):
+ return self._get_info(distribution_id, 'distribution', Distribution)
+
+ def get_distribution_config(self, distribution_id):
+ return self._get_config(distribution_id, 'distribution',
+ DistributionConfig)
+
+ def set_distribution_config(self, distribution_id, etag, config):
+ return self._set_config(distribution_id, etag, config)
+
+ def create_distribution(self, origin, enabled, caller_reference='',
+ cnames=None, comment=''):
+ config = DistributionConfig(origin=origin, enabled=enabled,
+ caller_reference=caller_reference,
+ cnames=cnames, comment=comment)
+ return self._create_object(config, 'distribution', Distribution)
+
+ def delete_distribution(self, distribution_id, etag):
+ return self._delete_object(distribution_id, etag, 'distribution')
+
+ # Streaming Distributions
+
+ def get_all_streaming_distributions(self):
+ tags=[('StreamingDistributionSummary', StreamingDistributionSummary)]
+ return self._get_all_objects('streaming-distribution', tags)
+
+ def get_streaming_distribution_info(self, distribution_id):
+ return self._get_info(distribution_id, 'streaming-distribution',
+ StreamingDistribution)
+
+ def get_streaming_distribution_config(self, distribution_id):
+ return self._get_config(distribution_id, 'streaming-distribution',
+ StreamingDistributionConfig)
+
+ def set_streaming_distribution_config(self, distribution_id, etag, config):
+ return self._set_config(distribution_id, etag, config)
+
+ def create_streaming_distribution(self, origin, enabled,
+ caller_reference='',
+ cnames=None, comment=''):
+ config = StreamingDistributionConfig(origin=origin, enabled=enabled,
+ caller_reference=caller_reference,
+ cnames=cnames, comment=comment)
+ return self._create_object(config, 'streaming-distribution',
+ StreamingDistribution)
+
+ def delete_streaming_distribution(self, distribution_id, etag):
+ return self._delete_object(distribution_id, etag, 'streaming-distribution')
+
+ # Origin Access Identity
+
+ def get_all_origin_access_identity(self):
+ tags=[('CloudFrontOriginAccessIdentitySummary',
+ OriginAccessIdentitySummary)]
+ return self._get_all_objects('origin-access-identity/cloudfront', tags)
+
+ def get_origin_access_identity_info(self, access_id):
+ return self._get_info(access_id, 'origin-access-identity/cloudfront',
+ OriginAccessIdentity)
+
+ def get_origin_access_identity_config(self, access_id):
+ return self._get_config(access_id,
+ 'origin-access-identity/cloudfront',
+ OriginAccessIdentityConfig)
+
+ def set_origin_access_identity_config(self, access_id,
+ etag, config):
+ return self._set_config(access_id, etag, config)
+
+ def create_origin_access_identity(self, caller_reference='', comment=''):
+ config = OriginAccessIdentityConfig(caller_reference=caller_reference,
+ comment=comment)
+ return self._create_object(config, 'origin-access-identity/cloudfront',
+ OriginAccessIdentity)
+
+ def delete_origin_access_identity(self, access_id, etag):
+ return self._delete_object(access_id, etag,
+ 'origin-access-identity/cloudfront')
+
+
diff --git a/vendor/boto/boto/cloudfront/distribution.py b/vendor/boto/boto/cloudfront/distribution.py
new file mode 100644
index 0000000000..ead6e36235
--- /dev/null
+++ b/vendor/boto/boto/cloudfront/distribution.py
@@ -0,0 +1,470 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import uuid
+from boto.cloudfront.identity import OriginAccessIdentity
+from boto.cloudfront.object import Object, StreamingObject
+from boto.cloudfront.signers import ActiveTrustedSigners, TrustedSigners
+from boto.cloudfront.logging import LoggingInfo
+from boto.s3.acl import ACL
+
+class DistributionConfig:
+
+ def __init__(self, connection=None, origin='', enabled=False,
+ caller_reference='', cnames=None, comment='',
+ origin_access_identity=None, trusted_signers=None):
+ self.connection = connection
+ self.origin = origin
+ self.enabled = enabled
+ if caller_reference:
+ self.caller_reference = caller_reference
+ else:
+ self.caller_reference = str(uuid.uuid4())
+ self.cnames = []
+ if cnames:
+ self.cnames = cnames
+ self.comment = comment
+ self.origin_access_identity = origin_access_identity
+ self.trusted_signers = trusted_signers
+ self.logging = None
+
+ def get_oai_value(self):
+ if isinstance(self.origin_access_identity, OriginAccessIdentity):
+ return self.origin_access_identity.uri()
+ else:
+ return self.origin_access_identity
+
+ def to_xml(self):
+ s = '<?xml version="1.0" encoding="UTF-8"?>\n'
+ s += '<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2008-12-01/">\n'
+ s += ' <Origin>%s</Origin>\n' % self.origin
+ s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference
+ for cname in self.cnames:
+ s += ' <CNAME>%s</CNAME>\n' % cname
+ if self.comment:
+ s += ' <Comment>%s</Comment>\n' % self.comment
+ s += ' <Enabled>'
+ if self.enabled:
+ s += 'true'
+ else:
+ s += 'false'
+ s += '</Enabled>\n'
+ if self.origin_access_identity:
+ val = self.get_oai_value()
+ s += '<OriginAccessIdentity>%s</OriginAccessIdentity>\n' % val
+ if self.trusted_signers:
+ s += '<TrustedSigners>\n'
+ for signer in self.trusted_signers:
+ if signer == 'Self':
+ s += ' <Self></Self>\n'
+ else:
+ s += ' <AwsAccountNumber>%s</AwsAccountNumber>\n' % signer
+ s += '</TrustedSigners>\n'
+ if self.logging:
+ s += '<Logging>\n'
+ s += ' <Bucket>%s</Bucket>\n' % self.logging.bucket
+ s += ' <Prefix>%s</Prefix>\n' % self.logging.prefix
+ s += '</Logging>\n'
+ s += '</DistributionConfig>\n'
+ return s
+
+ def startElement(self, name, attrs, connection):
+ if name == 'TrustedSigners':
+ self.trusted_signers = TrustedSigners()
+ return self.trusted_signers
+ elif name == 'Logging':
+ self.logging = LoggingInfo()
+ return self.logging
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'CNAME':
+ self.cnames.append(value)
+ elif name == 'Origin':
+ self.origin = value
+ elif name == 'Comment':
+ self.comment = value
+ elif name == 'Enabled':
+ if value.lower() == 'true':
+ self.enabled = True
+ else:
+ self.enabled = False
+ elif name == 'CallerReference':
+ self.caller_reference = value
+ elif name == 'OriginAccessIdentity':
+ self.origin_access_identity = value
+ else:
+ setattr(self, name, value)
+
+class StreamingDistributionConfig(DistributionConfig):
+
+ def __init__(self, connection=None, origin='', enabled=False,
+ caller_reference='', cnames=None, comment=''):
+ DistributionConfig.__init__(self, connection, origin,
+ enabled, caller_reference,
+ cnames, comment)
+
+ def to_xml(self):
+ s = '<?xml version="1.0" encoding="UTF-8"?>\n'
+ s += '<StreamingDistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2009-12-01/">\n'
+ s += ' <Origin>%s</Origin>\n' % self.origin
+ s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference
+ for cname in self.cnames:
+ s += ' <CNAME>%s</CNAME>\n' % cname
+ if self.comment:
+ s += ' <Comment>%s</Comment>\n' % self.comment
+ s += ' <Enabled>'
+ if self.enabled:
+ s += 'true'
+ else:
+ s += 'false'
+ s += '</Enabled>\n'
+ s += '</StreamingDistributionConfig>\n'
+ return s
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+class DistributionSummary:
+
+ def __init__(self, connection=None, domain_name='', id='',
+ last_modified_time=None, status='', origin='',
+ cname='', comment='', enabled=False):
+ self.connection = connection
+ self.domain_name = domain_name
+ self.id = id
+ self.last_modified_time = last_modified_time
+ self.status = status
+ self.origin = origin
+ self.enabled = enabled
+ self.cnames = []
+ if cname:
+ self.cnames.append(cname)
+ self.comment = comment
+ self.trusted_signers = None
+ self.etag = None
+ self.streaming = False
+
+ def startElement(self, name, attrs, connection):
+ if name == 'TrustedSigners':
+ self.trusted_signers = TrustedSigners()
+ return self.trusted_signers
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Id':
+ self.id = value
+ elif name == 'Status':
+ self.status = value
+ elif name == 'LastModifiedTime':
+ self.last_modified_time = value
+ elif name == 'DomainName':
+ self.domain_name = value
+ elif name == 'Origin':
+ self.origin = value
+ elif name == 'CNAME':
+ self.cnames.append(value)
+ elif name == 'Comment':
+ self.comment = value
+ elif name == 'Enabled':
+ if value.lower() == 'true':
+ self.enabled = True
+ else:
+ self.enabled = False
+ elif name == 'StreamingDistributionSummary':
+ self.streaming = True
+ else:
+ setattr(self, name, value)
+
+ def get_distribution(self):
+ return self.connection.get_distribution_info(self.id)
+
+class StreamingDistributionSummary(DistributionSummary):
+
+ def get_distribution(self):
+ return self.connection.get_streaming_distribution_info(self.id)
+
+class Distribution:
+
+ def __init__(self, connection=None, config=None, domain_name='',
+ id='', last_modified_time=None, status=''):
+ self.connection = connection
+ self.config = config
+ self.domain_name = domain_name
+ self.id = id
+ self.last_modified_time = last_modified_time
+ self.status = status
+ self.active_signers = None
+ self.etag = None
+ self._bucket = None
+
+ def startElement(self, name, attrs, connection):
+ if name == 'DistributionConfig':
+ self.config = DistributionConfig()
+ return self.config
+ elif name == 'ActiveTrustedSigners':
+ self.active_signers = ActiveTrustedSigners()
+ return self.active_signers
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Id':
+ self.id = value
+ elif name == 'LastModifiedTime':
+ self.last_modified_time = value
+ elif name == 'Status':
+ self.status = value
+ elif name == 'DomainName':
+ self.domain_name = value
+ else:
+ setattr(self, name, value)
+
+ def update(self, enabled=None, cnames=None, comment=None,
+ origin_access_identity=None,
+ trusted_signers=None):
+ """
+ Update the configuration of the Distribution.
+
+ :type enabled: bool
+ :param enabled: Whether the Distribution is active or not.
+
+ :type cnames: list of str
+ :param cnames: The DNS CNAME's associated with this
+ Distribution. Maximum of 10 values.
+
+ :type comment: str or unicode
+ :param comment: The comment associated with the Distribution.
+
+ :type origin_access_identity: :class:`boto.cloudfront.identity.OriginAccessIdentity`
+ :param origin_access_identity: The CloudFront origin access identity
+ associated with the distribution. This
+ must be provided if you want the
+ distribution to serve private content.
+
+ :type trusted_signers: :class:`boto.cloudfront.signers.TrustedSigner`
+ :param trusted_signers: The AWS users who are authorized to sign
+ URL's for private content in this Distribution.
+
+ """
+ new_config = DistributionConfig(self.connection, self.config.origin,
+ self.config.enabled, self.config.caller_reference,
+ self.config.cnames, self.config.comment,
+ self.config.origin_access_identity,
+ self.config.trusted_signers)
+ if enabled != None:
+ new_config.enabled = enabled
+ if cnames != None:
+ new_config.cnames = cnames
+ if comment != None:
+ new_config.comment = comment
+ if origin_access_identity != None:
+ new_config.origin_access_identity = origin_access_identity
+ if trusted_signers:
+ new_config.trusted_signers = trusted_signers
+ self.etag = self.connection.set_distribution_config(self.id, self.etag, new_config)
+ self.config = new_config
+ self._object_class = Object
+
+ def enable(self):
+ """
+ Deactivate the Distribution. A convenience wrapper around
+ the update method.
+ """
+ self.update(enabled=True)
+
+ def disable(self):
+ """
+ Activate the Distribution. A convenience wrapper around
+ the update method.
+ """
+ self.update(enabled=False)
+
+ def delete(self):
+ """
+ Delete this CloudFront Distribution. The content
+ associated with the Distribution is not deleted from
+ the underlying Origin bucket in S3.
+ """
+ self.connection.delete_distribution(self.id, self.etag)
+
+ def _get_bucket(self):
+ if not self._bucket:
+ bucket_name = self.config.origin.split('.')[0]
+ from boto.s3.connection import S3Connection
+ s3 = S3Connection(self.connection.aws_access_key_id,
+ self.connection.aws_secret_access_key,
+ proxy=self.connection.proxy,
+ proxy_port=self.connection.proxy_port,
+ proxy_user=self.connection.proxy_user,
+ proxy_pass=self.connection.proxy_pass)
+ self._bucket = s3.get_bucket(bucket_name)
+ self._bucket.distribution = self
+ self._bucket.set_key_class(self._object_class)
+ return self._bucket
+
+ def get_objects(self):
+ """
+ Return a list of all content objects in this distribution.
+
+ :rtype: list of :class:`boto.cloudfront.object.Object`
+ :return: The content objects
+ """
+ bucket = self._get_bucket()
+ objs = []
+ for key in bucket:
+ objs.append(key)
+ return objs
+
+ def set_permissions(self, object, replace=False):
+ """
+ Sets the S3 ACL grants for the given object to the appropriate
+ value based on the type of Distribution. If the Distribution
+ is serving private content the ACL will be set to include the
+ Origin Access Identity associated with the Distribution. If
+ the Distribution is serving public content the content will
+ be set up with "public-read".
+
+ :type object: :class:`boto.cloudfront.object.Object`
+ :param enabled: The Object whose ACL is being set
+
+ :type replace: bool
+ :param replace: If False, the Origin Access Identity will be
+ appended to the existing ACL for the object.
+ If True, the ACL for the object will be
+ completely replaced with one that grants
+ READ permission to the Origin Access Identity.
+
+ """
+ if self.config.origin_access_identity:
+ id = self.config.origin_access_identity.split('/')[-1]
+ oai = self.connection.get_origin_access_identity_info(id)
+ policy = object.get_acl()
+ if replace:
+ policy.acl = ACL()
+ policy.acl.add_user_grant('READ', oai.s3_user_id)
+ object.set_acl(policy)
+ else:
+ object.set_canned_acl('public-read')
+
+ def set_permissions_all(self, replace=False):
+ """
+ Sets the S3 ACL grants for all objects in the Distribution
+ to the appropriate value based on the type of Distribution.
+
+ :type replace: bool
+ :param replace: If False, the Origin Access Identity will be
+ appended to the existing ACL for the object.
+ If True, the ACL for the object will be
+ completely replaced with one that grants
+ READ permission to the Origin Access Identity.
+
+ """
+ bucket = self._get_bucket()
+ for key in bucket:
+ self.set_permissions(key)
+
+ def add_object(self, name, content, headers=None, replace=True):
+ """
+ Adds a new content object to the Distribution. The content
+ for the object will be copied to a new Key in the S3 Bucket
+ and the permissions will be set appropriately for the type
+ of Distribution.
+
+ :type name: str or unicode
+ :param name: The name or key of the new object.
+
+ :type content: file-like object
+ :param content: A file-like object that contains the content
+ for the new object.
+
+ :type headers: dict
+ :param headers: A dictionary containing additional headers
+ you would like associated with the new
+ object in S3.
+
+ :rtype: :class:`boto.cloudfront.object.Object`
+ :return: The newly created object.
+ """
+ if self.config.origin_access_identity:
+ policy = 'private'
+ else:
+ policy = 'public-read'
+ bucket = self._get_bucket()
+ object = bucket.new_key(name)
+ object.set_contents_from_file(content, headers=headers, policy=policy)
+ if self.config.origin_access_identity:
+ self.set_permissions(object, replace)
+ return object
+
+class StreamingDistribution(Distribution):
+
+ def __init__(self, connection=None, config=None, domain_name='',
+ id='', last_modified_time=None, status=''):
+ Distribution.__init__(self, connection, config, domain_name,
+ id, last_modified_time, status)
+ self._object_class = StreamingObject
+
+ def startElement(self, name, attrs, connection):
+ if name == 'StreamingDistributionConfig':
+ self.config = StreamingDistributionConfig()
+ return self.config
+ else:
+ return None
+
+ def update(self, enabled=None, cnames=None, comment=None):
+ """
+ Update the configuration of the Distribution.
+
+ :type enabled: bool
+ :param enabled: Whether the Distribution is active or not.
+
+ :type cnames: list of str
+ :param cnames: The DNS CNAME's associated with this
+ Distribution. Maximum of 10 values.
+
+ :type comment: str or unicode
+ :param comment: The comment associated with the Distribution.
+
+ """
+ new_config = StreamingDistributionConfig(self.connection,
+ self.config.origin,
+ self.config.enabled,
+ self.config.caller_reference,
+ self.config.cnames,
+ self.config.comment)
+ if enabled != None:
+ new_config.enabled = enabled
+ if cnames != None:
+ new_config.cnames = cnames
+ if comment != None:
+ new_config.comment = comment
+
+ self.etag = self.connection.set_streaming_distribution_config(self.id,
+ self.etag,
+ new_config)
+ self.config = new_config
+
+ def delete(self):
+ self.connection.delete_streaming_distribution(self.id, self.etag)
+
+
diff --git a/vendor/boto/boto/cloudfront/exception.py b/vendor/boto/boto/cloudfront/exception.py
new file mode 100644
index 0000000000..768064210c
--- /dev/null
+++ b/vendor/boto/boto/cloudfront/exception.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.exception import BotoServerError
+
+class CloudFrontServerError(BotoServerError):
+
+ pass
diff --git a/vendor/boto/boto/cloudfront/identity.py b/vendor/boto/boto/cloudfront/identity.py
new file mode 100644
index 0000000000..1571e87a0a
--- /dev/null
+++ b/vendor/boto/boto/cloudfront/identity.py
@@ -0,0 +1,122 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import uuid
+
+class OriginAccessIdentity:
+
+ def __init__(self, connection=None, config=None, id='',
+ s3_user_id='', comment=''):
+ self.connection = connection
+ self.config = config
+ self.id = id
+ self.s3_user_id = s3_user_id
+ self.comment = comment
+ self.etag = None
+
+ def startElement(self, name, attrs, connection):
+ if name == 'CloudFrontOriginAccessIdentityConfig':
+ self.config = OriginAccessIdentityConfig()
+ return self.config
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Id':
+ self.id = value
+ elif name == 'S3CanonicalUserId':
+ self.s3_user_id = value
+ elif name == 'Comment':
+ self.comment = value
+ else:
+ setattr(self, name, value)
+
+ def update(self, comment=None):
+ new_config = OriginAccessIdentityConfig(self.connection,
+ self.config.caller_reference,
+ self.config.comment)
+ if comment != None:
+ new_config.comment = comment
+ self.etag = self.connection.set_origin_identity_config(self.id, self.etag, new_config)
+ self.config = new_config
+
+ def delete(self):
+ return self.connection.delete_origin_access_identity(self.id, self.etag)
+
+ def uri(self):
+ return 'origin-access-identity/cloudfront/%s' % self.id
+
+class OriginAccessIdentityConfig:
+
+ def __init__(self, connection=None, caller_reference='', comment=''):
+ self.connection = connection
+ if caller_reference:
+ self.caller_reference = caller_reference
+ else:
+ self.caller_reference = str(uuid.uuid4())
+ self.comment = comment
+
+ def to_xml(self):
+ s = '<?xml version="1.0" encoding="UTF-8"?>\n'
+ s += '<CloudFrontOriginAccessIdentityConfig xmlns="http://cloudfront.amazonaws.com/doc/2009-09-09/">\n'
+ s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference
+ if self.comment:
+ s += ' <Comment>%s</Comment>\n' % self.comment
+ s += '</CloudFrontOriginAccessIdentityConfig>\n'
+ return s
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Comment':
+ self.comment = value
+ elif name == 'CallerReference':
+ self.caller_reference = value
+ else:
+ setattr(self, name, value)
+
+class OriginAccessIdentitySummary:
+
+ def __init__(self, connection=None, id='',
+ s3_user_id='', comment=''):
+ self.connection = connection
+ self.id = id
+ self.s3_user_id = s3_user_id
+ self.comment = comment
+ self.etag = None
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Id':
+ self.id = value
+ elif name == 'S3CanonicalUserId':
+ self.s3_user_id = value
+ elif name == 'Comment':
+ self.comment = value
+ else:
+ setattr(self, name, value)
+
+ def get_origin_access_identity(self):
+ return self.connection.get_origin_access_identity_info(self.id)
+
diff --git a/vendor/boto/boto/cloudfront/logging.py b/vendor/boto/boto/cloudfront/logging.py
new file mode 100644
index 0000000000..6c2f4fde2f
--- /dev/null
+++ b/vendor/boto/boto/cloudfront/logging.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class LoggingInfo(object):
+
+ def __init__(self, bucket='', prefix=''):
+ self.bucket = bucket
+ self.prefix = prefix
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Bucket':
+ self.bucket = value
+ elif name == 'Prefix':
+ self.prefix = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/cloudfront/object.py b/vendor/boto/boto/cloudfront/object.py
new file mode 100644
index 0000000000..3574d13634
--- /dev/null
+++ b/vendor/boto/boto/cloudfront/object.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.s3.key import Key
+
+class Object(Key):
+
+ def __init__(self, bucket, name=None):
+ Key.__init__(self, bucket, name=name)
+ self.distribution = bucket.distribution
+
+ def __repr__(self):
+ return '<Object: %s/%s>' % (self.distribution.config.origin, self.name)
+
+ def url(self, scheme='http'):
+ url = '%s://' % scheme
+ url += self.distribution.domain_name
+ if scheme.lower().startswith('rtmp'):
+ url += '/cfx/st/'
+ else:
+ url += '/'
+ url += self.name
+ return url
+
+class StreamingObject(Object):
+
+ def url(self, scheme='rtmp'):
+ return Object.url(self, scheme)
+
+
diff --git a/vendor/boto/boto/cloudfront/signers.py b/vendor/boto/boto/cloudfront/signers.py
new file mode 100644
index 0000000000..0b0cd50a76
--- /dev/null
+++ b/vendor/boto/boto/cloudfront/signers.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Signer:
+
+ def __init__(self):
+ self.id = None
+ self.key_pair_ids = []
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Self':
+ self.id = 'Self'
+ elif name == 'AwsAccountNumber':
+ self.id = value
+ elif name == 'KeyPairId':
+ self.key_pair_ids.append(value)
+
+class ActiveTrustedSigners(list):
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Signer':
+ s = Signer()
+ self.append(s)
+ return s
+
+ def endElement(self, name, value, connection):
+ pass
+
+class TrustedSigners(list):
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Self':
+ self.append(name)
+ elif name == 'AwsAccountNumber':
+ self.append(value)
+
diff --git a/vendor/boto/boto/connection.py b/vendor/boto/boto/connection.py
new file mode 100644
index 0000000000..41a3c771ae
--- /dev/null
+++ b/vendor/boto/boto/connection.py
@@ -0,0 +1,644 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2008 rPath, Inc.
+# Copyright (c) 2009 The Echo Nest Corporation
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+#
+# Parts of this code were copied or derived from sample code supplied by AWS.
+# The following notice applies to that code.
+#
+# This software code is made available "AS IS" without warranties of any
+# kind. You may copy, display, modify and redistribute the software
+# code either by itself or as incorporated into your code; provided that
+# you do not remove any proprietary notices. Your use of this software
+# code is at your own risk and you waive any claim against Amazon
+# Digital Services, Inc. or its affiliates with respect to your use of
+# this software code. (c) 2006 Amazon Digital Services, Inc. or its
+# affiliates.
+
+"""
+Handles basic connections to AWS
+"""
+
+import base64
+import hmac
+import httplib
+import socket, errno
+import re
+import sys
+import time
+import urllib, urlparse
+import os
+import xml.sax
+import Queue
+import boto
+from boto.exception import BotoClientError, BotoServerError
+from boto.resultset import ResultSet
+import boto.utils
+from boto import config, UserAgent, handler
+
+#
+# the following is necessary because of the incompatibilities
+# between Python 2.4, 2.5, and 2.6 as well as the fact that some
+# people running 2.4 have installed hashlib as a separate module
+# this fix was provided by boto user mccormix.
+# see: http://code.google.com/p/boto/issues/detail?id=172
+# for more details.
+#
+try:
+ from hashlib import sha1 as sha
+ from hashlib import sha256 as sha256
+
+ if sys.version[:3] == "2.4":
+ # we are using an hmac that expects a .new() method.
+ class Faker:
+ def __init__(self, which):
+ self.which = which
+ self.digest_size = self.which().digest_size
+
+ def new(self, *args, **kwargs):
+ return self.which(*args, **kwargs)
+
+ sha = Faker(sha)
+ sha256 = Faker(sha256)
+
+except ImportError:
+ import sha
+ sha256 = None
+
+PORTS_BY_SECURITY = { True: 443, False: 80 }
+
+class ConnectionPool:
+ def __init__(self, hosts, connections_per_host):
+ self._hosts = boto.utils.LRUCache(hosts)
+ self.connections_per_host = connections_per_host
+
+ def __getitem__(self, key):
+ if key not in self._hosts:
+ self._hosts[key] = Queue.Queue(self.connections_per_host)
+ return self._hosts[key]
+
+ def __repr__(self):
+ return 'ConnectionPool:%s' % ','.join(self._hosts._dict.keys())
+
+class AWSAuthConnection:
+ def __init__(self, host, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, debug=0,
+ https_connection_factory=None, path='/'):
+ """
+ :type host: string
+ :param host: The host to make the connection to
+
+ :type aws_access_key_id: string
+ :param aws_access_key_id: AWS Access Key ID (provided by Amazon)
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Secret Access Key (provided by Amazon)
+
+ :type is_secure: boolean
+ :param is_secure: Whether the connection is over SSL
+
+ :type https_connection_factory: list or tuple
+ :param https_connection_factory: A pair of an HTTP connection
+ factory and the exceptions to catch.
+ The factory should have a similar
+ interface to L{httplib.HTTPSConnection}.
+
+ :type proxy:
+ :param proxy:
+
+ :type proxy_port: int
+ :param proxy_port: The port to use when connecting over a proxy
+
+ :type proxy_user: string
+ :param proxy_user: The username to connect with on the proxy
+
+ :type proxy_pass: string
+ :param proxy_pass: The password to use when connection over a proxy.
+
+ :type port: integer
+ :param port: The port to use to connect
+ """
+
+ self.num_retries = 5
+ self.is_secure = is_secure
+ self.handle_proxy(proxy, proxy_port, proxy_user, proxy_pass)
+ # define exceptions from httplib that we want to catch and retry
+ self.http_exceptions = (httplib.HTTPException, socket.error, socket.gaierror)
+ # define values in socket exceptions we don't want to catch
+ self.socket_exception_values = (errno.EINTR,)
+ if https_connection_factory is not None:
+ self.https_connection_factory = https_connection_factory[0]
+ self.http_exceptions += https_connection_factory[1]
+ else:
+ self.https_connection_factory = None
+ if (is_secure):
+ self.protocol = 'https'
+ else:
+ self.protocol = 'http'
+ self.host = host
+ self.path = path
+ if debug:
+ self.debug = debug
+ else:
+ self.debug = config.getint('Boto', 'debug', debug)
+ if port:
+ self.port = port
+ else:
+ self.port = PORTS_BY_SECURITY[is_secure]
+
+ if aws_access_key_id:
+ self.aws_access_key_id = aws_access_key_id
+ elif os.environ.has_key('AWS_ACCESS_KEY_ID'):
+ self.aws_access_key_id = os.environ['AWS_ACCESS_KEY_ID']
+ elif config.has_option('Credentials', 'aws_access_key_id'):
+ self.aws_access_key_id = config.get('Credentials', 'aws_access_key_id')
+
+ if aws_secret_access_key:
+ self.aws_secret_access_key = aws_secret_access_key
+ elif os.environ.has_key('AWS_SECRET_ACCESS_KEY'):
+ self.aws_secret_access_key = os.environ['AWS_SECRET_ACCESS_KEY']
+ elif config.has_option('Credentials', 'aws_secret_access_key'):
+ self.aws_secret_access_key = config.get('Credentials', 'aws_secret_access_key')
+
+ # initialize an HMAC for signatures, make copies with each request
+ self.hmac = hmac.new(self.aws_secret_access_key, digestmod=sha)
+ if sha256:
+ self.hmac_256 = hmac.new(self.aws_secret_access_key, digestmod=sha256)
+ else:
+ self.hmac_256 = None
+
+ # cache up to 20 connections per host, up to 20 hosts
+ self._pool = ConnectionPool(20, 20)
+ self._connection = (self.server_name(), self.is_secure)
+ self._last_rs = None
+
+ def __repr__(self):
+ return '%s:%s' % (self.__class__.__name__, self.host)
+
+ def _cached_name(self, host, is_secure):
+ if host is None:
+ host = self.server_name()
+ cached_name = is_secure and 'https://' or 'http://'
+ cached_name += host
+ return cached_name
+
+ def connection(self):
+ return self.get_http_connection(*self._connection)
+
+ connection = property(connection)
+
+ def get_path(self, path='/'):
+ pos = path.find('?')
+ if pos >= 0:
+ params = path[pos:]
+ path = path[:pos]
+ else:
+ params = None
+ if path[-1] == '/':
+ need_trailing = True
+ else:
+ need_trailing = False
+ path_elements = self.path.split('/')
+ path_elements.extend(path.split('/'))
+ path_elements = [p for p in path_elements if p]
+ path = '/' + '/'.join(path_elements)
+ if path[-1] != '/' and need_trailing:
+ path += '/'
+ if params:
+ path = path + params
+ return path
+
+ def server_name(self, port=None):
+ if not port:
+ port = self.port
+ if port == 80:
+ signature_host = self.host
+ else:
+ # This unfortunate little hack can be attributed to
+ # a difference in the 2.6 version of httplib. In old
+ # versions, it would append ":443" to the hostname sent
+ # in the Host header and so we needed to make sure we
+ # did the same when calculating the V2 signature. In 2.6
+ # it no longer does that. Hence, this kludge.
+ if sys.version[:3] == "2.6" and port == 443:
+ signature_host = self.host
+ else:
+ signature_host = '%s:%d' % (self.host, port)
+ return signature_host
+
+ def handle_proxy(self, proxy, proxy_port, proxy_user, proxy_pass):
+ self.proxy = proxy
+ self.proxy_port = proxy_port
+ self.proxy_user = proxy_user
+ self.proxy_pass = proxy_pass
+ if os.environ.has_key('http_proxy') and not self.proxy:
+ pattern = re.compile(
+ '(?:http://)?' \
+ '(?:(?P<user>\w+):(?P<pass>.*)@)?' \
+ '(?P<host>[\w\-\.]+)' \
+ '(?::(?P<port>\d+))?'
+ )
+ match = pattern.match(os.environ['http_proxy'])
+ if match:
+ self.proxy = match.group('host')
+ self.proxy_port = match.group('port')
+ self.proxy_user = match.group('user')
+ self.proxy_pass = match.group('pass')
+ else:
+ if not self.proxy:
+ self.proxy = config.get_value('Boto', 'proxy', None)
+ if not self.proxy_port:
+ self.proxy_port = config.get_value('Boto', 'proxy_port', None)
+ if not self.proxy_user:
+ self.proxy_user = config.get_value('Boto', 'proxy_user', None)
+ if not self.proxy_pass:
+ self.proxy_pass = config.get_value('Boto', 'proxy_pass', None)
+
+ if not self.proxy_port and self.proxy:
+ print "http_proxy environment variable does not specify " \
+ "a port, using default"
+ self.proxy_port = self.port
+ self.use_proxy = (self.proxy != None)
+
+ def get_http_connection(self, host, is_secure):
+ queue = self._pool[self._cached_name(host, is_secure)]
+ try:
+ return queue.get_nowait()
+ except Queue.Empty:
+ return self.new_http_connection(host, is_secure)
+
+ def new_http_connection(self, host, is_secure):
+ if self.use_proxy:
+ host = '%s:%d' % (self.proxy, int(self.proxy_port))
+ if host is None:
+ host = self.server_name()
+ boto.log.debug('establishing HTTP connection')
+ if is_secure:
+ if self.use_proxy:
+ connection = self.proxy_ssl()
+ elif self.https_connection_factory:
+ connection = self.https_connection_factory(host)
+ else:
+ connection = httplib.HTTPSConnection(host)
+ else:
+ connection = httplib.HTTPConnection(host)
+ if self.debug > 1:
+ connection.set_debuglevel(self.debug)
+ # self.connection must be maintained for backwards-compatibility
+ # however, it must be dynamically pulled from the connection pool
+ # set a private variable which will enable that
+ if host.split(':')[0] == self.host and is_secure == self.is_secure:
+ self._connection = (host, is_secure)
+ return connection
+
+ def put_http_connection(self, host, is_secure, connection):
+ try:
+ self._pool[self._cached_name(host, is_secure)].put_nowait(connection)
+ except Queue.Full:
+ # gracefully fail in case of pool overflow
+ connection.close()
+
+ def proxy_ssl(self):
+ host = '%s:%d' % (self.host, self.port)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ sock.connect((self.proxy, int(self.proxy_port)))
+ except:
+ raise
+ sock.sendall("CONNECT %s HTTP/1.0\r\n" % host)
+ sock.sendall("User-Agent: %s\r\n" % UserAgent)
+ if self.proxy_user and self.proxy_pass:
+ for k, v in self.get_proxy_auth_header().items():
+ sock.sendall("%s: %s\r\n" % (k, v))
+ sock.sendall("\r\n")
+ resp = httplib.HTTPResponse(sock, strict=True)
+ resp.begin()
+
+ if resp.status != 200:
+ # Fake a socket error, use a code that make it obvious it hasn't
+ # been generated by the socket library
+ raise socket.error(-71,
+ "Error talking to HTTP proxy %s:%s: %s (%s)" %
+ (self.proxy, self.proxy_port, resp.status, resp.reason))
+
+ # We can safely close the response, it duped the original socket
+ resp.close()
+
+ h = httplib.HTTPConnection(host)
+
+ # Wrap the socket in an SSL socket
+ if hasattr(httplib, 'ssl'):
+ sslSock = httplib.ssl.SSLSocket(sock)
+ else: # Old Python, no ssl module
+ sslSock = socket.ssl(sock, None, None)
+ sslSock = httplib.FakeSocket(sock, sslSock)
+ # This is a bit unclean
+ h.sock = sslSock
+ return h
+
+ def prefix_proxy_to_path(self, path, host=None):
+ path = self.protocol + '://' + (host or self.server_name()) + path
+ return path
+
+ def get_proxy_auth_header(self):
+ auth = base64.encodestring(self.proxy_user+':'+self.proxy_pass)
+ return {'Proxy-Authorization': 'Basic %s' % auth}
+
+ def _mexe(self, method, path, data, headers, host=None, sender=None):
+ """
+ mexe - Multi-execute inside a loop, retrying multiple times to handle
+ transient Internet errors by simply trying again.
+ Also handles redirects.
+
+ This code was inspired by the S3Utils classes posted to the boto-users
+ Google group by Larry Bates. Thanks!
+ """
+ boto.log.debug('Method: %s' % method)
+ boto.log.debug('Path: %s' % path)
+ boto.log.debug('Data: %s' % data)
+ boto.log.debug('Headers: %s' % headers)
+ boto.log.debug('Host: %s' % host)
+ response = None
+ body = None
+ e = None
+ num_retries = config.getint('Boto', 'num_retries', self.num_retries)
+ i = 0
+ connection = self.get_http_connection(host, self.is_secure)
+ while i <= num_retries:
+ try:
+ if callable(sender):
+ response = sender(connection, method, path, data, headers)
+ else:
+ connection.request(method, path, data, headers)
+ response = connection.getresponse()
+ location = response.getheader('location')
+ # -- gross hack --
+ # httplib gets confused with chunked responses to HEAD requests
+ # so I have to fake it out
+ if method == 'HEAD' and getattr(response, 'chunked', False):
+ response.chunked = 0
+ if response.status == 500 or response.status == 503:
+ boto.log.debug('received %d response, retrying in %d seconds' % (response.status, 2**i))
+ body = response.read()
+ elif response.status == 408:
+ body = response.read()
+ print '-------------------------'
+ print ' 4 0 8 '
+ print 'path=%s' % path
+ print body
+ print '-------------------------'
+ elif response.status < 300 or response.status >= 400 or \
+ not location:
+ self.put_http_connection(host, self.is_secure, connection)
+ return response
+ else:
+ scheme, host, path, params, query, fragment = \
+ urlparse.urlparse(location)
+ if query:
+ path += '?' + query
+ boto.log.debug('Redirecting: %s' % scheme + '://' + host + path)
+ connection = self.get_http_connection(host,
+ scheme == 'https')
+ continue
+ except KeyboardInterrupt:
+ sys.exit('Keyboard Interrupt')
+ except self.http_exceptions, e:
+ boto.log.debug('encountered %s exception, reconnecting' % \
+ e.__class__.__name__)
+ connection = self.new_http_connection(host, self.is_secure)
+ time.sleep(2**i)
+ i += 1
+ # If we made it here, it's because we have exhausted our retries and stil haven't
+ # succeeded. So, if we have a response object, use it to raise an exception.
+ # Otherwise, raise the exception that must have already happened.
+ if response:
+ raise BotoServerError(response.status, response.reason, body)
+ elif e:
+ raise e
+ else:
+ raise BotoClientError('Please report this exception as a Boto Issue!')
+
+ def make_request(self, method, path, headers=None, data='', host=None,
+ auth_path=None, sender=None):
+ path = self.get_path(path)
+ if headers == None:
+ headers = {}
+ else:
+ headers = headers.copy()
+ headers['User-Agent'] = UserAgent
+ if not headers.has_key('Content-Length'):
+ headers['Content-Length'] = str(len(data))
+ if self.use_proxy:
+ path = self.prefix_proxy_to_path(path, host)
+ if self.proxy_user and self.proxy_pass and not self.is_secure:
+ # If is_secure, we don't have to set the proxy authentication
+ # header here, we did that in the CONNECT to the proxy.
+ headers.update(self.get_proxy_auth_header())
+ request_string = auth_path or path
+ self.add_aws_auth_header(headers, method, request_string)
+ return self._mexe(method, path, data, headers, host, sender)
+
+ def add_aws_auth_header(self, headers, method, path):
+ path = self.get_path(path)
+ if not headers.has_key('Date'):
+ headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
+ time.gmtime())
+
+ c_string = boto.utils.canonical_string(method, path, headers)
+ boto.log.debug('Canonical: %s' % c_string)
+ hmac = self.hmac.copy()
+ hmac.update(c_string)
+ b64_hmac = base64.encodestring(hmac.digest()).strip()
+ headers['Authorization'] = "AWS %s:%s" % (self.aws_access_key_id, b64_hmac)
+
+ def close(self):
+ """(Optional) Close any open HTTP connections. This is non-destructive,
+ and making a new request will open a connection again."""
+
+ boto.log.debug('closing all HTTP connections')
+ self.connection = None # compat field
+
+
+class AWSQueryConnection(AWSAuthConnection):
+
+ APIVersion = ''
+ SignatureVersion = '1'
+ ResponseError = BotoServerError
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, host=None, debug=0,
+ https_connection_factory=None, path='/'):
+ AWSAuthConnection.__init__(self, host, aws_access_key_id, aws_secret_access_key,
+ is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
+ debug, https_connection_factory, path)
+
+ def get_utf8_value(self, value):
+ if not isinstance(value, str) and not isinstance(value, unicode):
+ value = str(value)
+ if isinstance(value, unicode):
+ return value.encode('utf-8')
+ else:
+ return value
+
+ def calc_signature_0(self, params):
+ boto.log.debug('using calc_signature_0')
+ hmac = self.hmac.copy()
+ s = params['Action'] + params['Timestamp']
+ hmac.update(s)
+ keys = params.keys()
+ keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
+ pairs = []
+ for key in keys:
+ val = self.get_utf8_value(params[key])
+ pairs.append(key + '=' + urllib.quote(val))
+ qs = '&'.join(pairs)
+ return (qs, base64.b64encode(hmac.digest()))
+
+ def calc_signature_1(self, params):
+ boto.log.debug('using calc_signature_1')
+ hmac = self.hmac.copy()
+ keys = params.keys()
+ keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
+ pairs = []
+ for key in keys:
+ hmac.update(key)
+ val = self.get_utf8_value(params[key])
+ hmac.update(val)
+ pairs.append(key + '=' + urllib.quote(val))
+ qs = '&'.join(pairs)
+ return (qs, base64.b64encode(hmac.digest()))
+
+ def calc_signature_2(self, params, verb, path):
+ boto.log.debug('using calc_signature_2')
+ string_to_sign = '%s\n%s\n%s\n' % (verb, self.server_name().lower(), path)
+ if self.hmac_256:
+ hmac = self.hmac_256.copy()
+ params['SignatureMethod'] = 'HmacSHA256'
+ else:
+ hmac = self.hmac.copy()
+ params['SignatureMethod'] = 'HmacSHA1'
+ keys = params.keys()
+ keys.sort()
+ pairs = []
+ for key in keys:
+ val = self.get_utf8_value(params[key])
+ pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~'))
+ qs = '&'.join(pairs)
+ boto.log.debug('query string: %s' % qs)
+ string_to_sign += qs
+ boto.log.debug('string_to_sign: %s' % string_to_sign)
+ hmac.update(string_to_sign)
+ b64 = base64.b64encode(hmac.digest())
+ boto.log.debug('len(b64)=%d' % len(b64))
+ boto.log.debug('base64 encoded digest: %s' % b64)
+ return (qs, b64)
+
+ def get_signature(self, params, verb, path):
+ if self.SignatureVersion == '0':
+ t = self.calc_signature_0(params)
+ elif self.SignatureVersion == '1':
+ t = self.calc_signature_1(params)
+ elif self.SignatureVersion == '2':
+ t = self.calc_signature_2(params, verb, path)
+ else:
+ raise BotoClientError('Unknown Signature Version: %s' % self.SignatureVersion)
+ return t
+
+ def make_request(self, action, params=None, path='/', verb='GET'):
+ headers = {}
+ if params == None:
+ params = {}
+ params['Action'] = action
+ params['Version'] = self.APIVersion
+ params['AWSAccessKeyId'] = self.aws_access_key_id
+ params['SignatureVersion'] = self.SignatureVersion
+ params['Timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
+ qs, signature = self.get_signature(params, verb, self.get_path(path))
+ if verb == 'POST':
+ headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
+ request_body = qs + '&Signature=' + urllib.quote(signature)
+ qs = path
+ else:
+ request_body = ''
+ qs = path + '?' + qs + '&Signature=' + urllib.quote(signature)
+ return AWSAuthConnection.make_request(self, verb, qs,
+ data=request_body,
+ headers=headers)
+
+ def build_list_params(self, params, items, label):
+ if isinstance(items, str):
+ items = [items]
+ for i in range(1, len(items)+1):
+ params['%s.%d' % (label, i)] = items[i-1]
+
+ # generics
+
+ def get_list(self, action, params, markers, path='/', parent=None, verb='GET'):
+ if not parent:
+ parent = self
+ response = self.make_request(action, params, path, verb)
+ body = response.read()
+ boto.log.debug(body)
+ if response.status == 200:
+ rs = ResultSet(markers)
+ h = handler.XmlHandler(rs, parent)
+ xml.sax.parseString(body, h)
+ return rs
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def get_object(self, action, params, cls, path='/', parent=None, verb='GET'):
+ if not parent:
+ parent = self
+ response = self.make_request(action, params, path, verb)
+ body = response.read()
+ boto.log.debug(body)
+ if response.status == 200:
+ obj = cls(parent)
+ h = handler.XmlHandler(obj, parent)
+ xml.sax.parseString(body, h)
+ return obj
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def get_status(self, action, params, path='/', parent=None, verb='GET'):
+ if not parent:
+ parent = self
+ response = self.make_request(action, params, path, verb)
+ body = response.read()
+ boto.log.debug(body)
+ if response.status == 200:
+ rs = ResultSet()
+ h = handler.XmlHandler(rs, parent)
+ xml.sax.parseString(body, h)
+ return rs.status
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
diff --git a/vendor/boto/boto/contrib/__init__.py b/vendor/boto/boto/contrib/__init__.py
new file mode 100644
index 0000000000..303dbb66c9
--- /dev/null
+++ b/vendor/boto/boto/contrib/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
diff --git a/vendor/boto/boto/contrib/m2helpers.py b/vendor/boto/boto/contrib/m2helpers.py
new file mode 100644
index 0000000000..82d2730515
--- /dev/null
+++ b/vendor/boto/boto/contrib/m2helpers.py
@@ -0,0 +1,52 @@
+# Copyright (c) 2006,2007 Jon Colverson
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+This module was contributed by Jon Colverson. It provides a couple of helper
+functions that allow you to use M2Crypto's implementation of HTTPSConnection
+rather than the default version in httplib.py. The main benefit is that
+M2Crypto's version verifies the certificate of the server.
+
+To use this feature, do something like this:
+
+from boto.ec2.connection import EC2Connection
+
+ec2 = EC2Connection(ACCESS_KEY_ID, SECRET_ACCESS_KEY,
+ https_connection_factory=https_connection_factory(cafile=CA_FILE))
+
+See http://code.google.com/p/boto/issues/detail?id=57 for more details.
+"""
+from M2Crypto import SSL
+from M2Crypto.httpslib import HTTPSConnection
+
+def secure_context(cafile=None, capath=None):
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9)
+ if ctx.load_verify_locations(cafile=cafile, capath=capath) != 1:
+ raise Exception("Couldn't load certificates")
+ return ctx
+
+def https_connection_factory(cafile=None, capath=None):
+ def factory(*args, **kwargs):
+ return HTTPSConnection(
+ ssl_context=secure_context(cafile=cafile, capath=capath),
+ *args, **kwargs)
+ return (factory, (SSL.SSLError,))
diff --git a/vendor/boto/boto/contrib/ymlmessage.py b/vendor/boto/boto/contrib/ymlmessage.py
new file mode 100644
index 0000000000..b9a2c93262
--- /dev/null
+++ b/vendor/boto/boto/contrib/ymlmessage.py
@@ -0,0 +1,52 @@
+# Copyright (c) 2006,2007 Chris Moyer
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+This module was contributed by Chris Moyer. It provides a subclass of the
+SQS Message class that supports YAML as the body of the message.
+
+This module requires the yaml module.
+"""
+from boto.sqs.message import Message
+import yaml
+
+class YAMLMessage(Message):
+ """
+ The YAMLMessage class provides a YAML compatible message. Encoding and
+ decoding are handled automaticaly.
+
+ Access this message data like such:
+
+ m.data = [ 1, 2, 3]
+ m.data[0] # Returns 1
+
+ This depends on the PyYAML package
+ """
+
+ def __init__(self, queue=None, body='', xml_attrs=None):
+ self.data = None
+ Message.__init__(self, queue, body)
+
+ def set_body(self, body):
+ self.data = yaml.load(body)
+
+ def get_body(self):
+ return yaml.dump(self.data)
diff --git a/vendor/boto/boto/ec2/__init__.py b/vendor/boto/boto/ec2/__init__.py
new file mode 100644
index 0000000000..8bb3f53790
--- /dev/null
+++ b/vendor/boto/boto/ec2/__init__.py
@@ -0,0 +1,52 @@
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+"""
+This module provides an interface to the Elastic Compute Cloud (EC2)
+service from AWS.
+"""
+from boto.ec2.connection import EC2Connection
+
+def regions(**kw_params):
+ """
+ Get all available regions for the EC2 service.
+ You may pass any of the arguments accepted by the EC2Connection
+ object's constructor as keyword arguments and they will be
+ passed along to the EC2Connection object.
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.regioninfo.RegionInfo`
+ """
+ c = EC2Connection(**kw_params)
+ return c.get_all_regions()
+
+def connect_to_region(region_name, **kw_params):
+ for region in regions(**kw_params):
+ if region.name == region_name:
+ return region.connect(**kw_params)
+ return None
+
+def get_region(region_name, **kw_params):
+ for region in regions(**kw_params):
+ if region.name == region_name:
+ return region
+ return None
+
diff --git a/vendor/boto/boto/ec2/address.py b/vendor/boto/boto/ec2/address.py
new file mode 100644
index 0000000000..60ed40675f
--- /dev/null
+++ b/vendor/boto/boto/ec2/address.py
@@ -0,0 +1,58 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Elastic IP Address
+"""
+
+from boto.ec2.ec2object import EC2Object
+
+class Address(EC2Object):
+
+ def __init__(self, connection=None, public_ip=None, instance_id=None):
+ EC2Object.__init__(self, connection)
+ self.connection = connection
+ self.public_ip = public_ip
+ self.instance_id = instance_id
+
+ def __repr__(self):
+ return 'Address:%s' % self.public_ip
+
+ def endElement(self, name, value, connection):
+ if name == 'publicIp':
+ self.public_ip = value
+ elif name == 'instanceId':
+ self.instance_id = value
+ else:
+ setattr(self, name, value)
+
+ def release(self):
+ return self.connection.release_address(self.public_ip)
+
+ delete = release
+
+ def associate(self, instance_id):
+ return self.connection.associate_address(instance_id, self.public_ip)
+
+ def disassociate(self):
+ return self.connection.disassociate_address(self.public_ip)
+
+
diff --git a/vendor/boto/boto/ec2/autoscale/__init__.py b/vendor/boto/boto/ec2/autoscale/__init__.py
new file mode 100644
index 0000000000..a06781f1d8
--- /dev/null
+++ b/vendor/boto/boto/ec2/autoscale/__init__.py
@@ -0,0 +1,203 @@
+# Copyright (c) 2009 Reza Lotun http://reza.lotun.name/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+This module provides an interface to the Elastic Compute Cloud (EC2)
+Auto Scaling service.
+"""
+
+import boto
+from boto.connection import AWSQueryConnection
+from boto.ec2.autoscale.request import Request
+from boto.ec2.autoscale.trigger import Trigger
+from boto.ec2.autoscale.launchconfig import LaunchConfiguration
+from boto.ec2.autoscale.group import AutoScalingGroup
+from boto.ec2.autoscale.activity import Activity
+
+
+class AutoScaleConnection(AWSQueryConnection):
+ APIVersion = boto.config.get('Boto', 'autoscale_version', '2009-05-15')
+ Endpoint = boto.config.get('Boto', 'autoscale_endpoint',
+ 'autoscaling.amazonaws.com')
+ SignatureVersion = '2'
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, host=Endpoint, debug=1,
+ https_connection_factory=None, region=None, path='/'):
+ """
+ Init method to create a new connection to the AutoScaling service.
+
+ B{Note:} The host argument is overridden by the host specified in the
+ boto configuration file.
+ """
+ AWSQueryConnection.__init__(self, aws_access_key_id,
+ aws_secret_access_key, is_secure, port, proxy, proxy_port,
+ proxy_user, proxy_pass, host, debug,
+ https_connection_factory, path=path)
+
+ def build_list_params(self, params, items, label):
+ """ items is a list of dictionaries or strings:
+ [{'Protocol' : 'HTTP',
+ 'LoadBalancerPort' : '80',
+ 'InstancePort' : '80'},..] etc.
+ or
+ ['us-east-1b',...]
+ """
+ # different from EC2 list params
+ for i in xrange(1, len(items)+1):
+ if isinstance(items[i-1], dict):
+ for k, v in items[i-1].iteritems():
+ params['%s.member.%d.%s' % (label, i, k)] = v
+ elif isinstance(items[i-1], basestring):
+ params['%s.member.%d' % (label, i)] = items[i-1]
+
+ def _update_group(self, op, as_group):
+ params = {
+ 'AutoScalingGroupName' : as_group.name,
+ 'Cooldown' : as_group.cooldown,
+ 'LaunchConfigurationName' : as_group.launch_config_name,
+ 'MinSize' : as_group.min_size,
+ 'MaxSize' : as_group.max_size,
+ }
+ if op.startswith('Create'):
+ if as_group.availability_zones:
+ zones = self.availability_zones
+ else:
+ zones = [as_group.availability_zone]
+ self.build_list_params(params, as_group.load_balancers,
+ 'LoadBalancerNames')
+ self.build_list_params(params, zones,
+ 'AvailabilityZones')
+ return self.get_object(op, params, Request)
+
+ def create_auto_scaling_group(self, as_group):
+ """
+ Create auto scaling group.
+ """
+ return self._update_group('CreateAutoScalingGroup', as_group)
+
+ def create_launch_configuration(self, launch_config):
+ """
+ Creates a new Launch Configuration.
+
+ :type launch_config: boto.ec2.autoscale.launchconfig.LaunchConfiguration
+ :param launch_config: LaunchConfiguraiton object.
+
+ """
+ params = {
+ 'ImageId' : launch_config.image_id,
+ 'KeyName' : launch_config.key_name,
+ 'LaunchConfigurationName' : launch_config.name,
+ 'InstanceType' : launch_config.instance_type,
+ }
+ if launch_config.user_data:
+ params['UserData'] = launch_config.user_data
+ if launch_config.kernel_id:
+ params['KernelId'] = launch_config.kernel_id
+ if launch_config.ramdisk_id:
+ params['RamdiskId'] = launch_config.ramdisk_id
+ if launch_config.block_device_mappings:
+ self.build_list_params(params, launch_config.block_device_mappings,
+ 'BlockDeviceMappings')
+ self.build_list_params(params, launch_config.security_groups,
+ 'SecurityGroups')
+ return self.get_object('CreateLaunchConfiguration', params,
+ Request)
+
+ def create_trigger(self, trigger):
+ """
+
+ """
+ params = {'TriggerName' : trigger.name,
+ 'AutoScalingGroupName' : trigger.autoscale_group.name,
+ 'MeasureName' : trigger.measure_name,
+ 'Statistic' : trigger.statistic,
+ 'Period' : trigger.period,
+ 'Unit' : trigger.unit,
+ 'LowerThreshold' : trigger.lower_threshold,
+ 'LowerBreachScaleIncrement' : trigger.lower_breach_scale_increment,
+ 'UpperThreshold' : trigger.upper_threshold,
+ 'UpperBreachScaleIncrement' : trigger.upper_breach_scale_increment,
+ 'BreachDuration' : trigger.breach_duration}
+ # dimensions should be a list of tuples
+ dimensions = []
+ for dim in trigger.dimensions:
+ name, value = dim
+ dimensions.append(dict(Name=name, Value=value))
+ self.build_list_params(params, dimensions, 'Dimensions')
+
+ req = self.get_object('CreateOrUpdateScalingTrigger', params,
+ Request)
+ return req
+
+ def get_all_groups(self, names=None):
+ """
+ """
+ params = {}
+ if names:
+ self.build_list_params(params, names, 'AutoScalingGroupNames')
+ return self.get_list('DescribeAutoScalingGroups', params,
+ [('member', AutoScalingGroup)])
+
+ def get_all_launch_configurations(self, names=None):
+ """
+ """
+ params = {}
+ if names:
+ self.build_list_params(params, names, 'LaunchConfigurationNames')
+ return self.get_list('DescribeLaunchConfigurations', params,
+ [('member', LaunchConfiguration)])
+
+ def get_all_activities(self, autoscale_group,
+ activity_ids=None,
+ max_records=100):
+ """
+ Get all activities for the given autoscaling group.
+
+ :type autoscale_group: str or AutoScalingGroup object
+ :param autoscale_group: The auto scaling group to get activities on.
+
+ @max_records: int
+ :param max_records: Maximum amount of activities to return.
+ """
+ name = autoscale_group
+ if isinstance(autoscale_group, AutoScalingGroup):
+ name = autoscale_group.name
+ params = {'AutoScalingGroupName' : name}
+ if activity_ids:
+ self.build_list_params(params, activity_ids, 'ActivityIds')
+ return self.get_list('DescribeScalingActivities', params,
+ [('member', Activity)])
+
+ def get_all_triggers(self, autoscale_group):
+ params = {'AutoScalingGroupName' : autoscale_group}
+ return self.get_list('DescribeTriggers', params,
+ [('member', Trigger)])
+
+ def terminate_instance(self, instance_id, decrement_capacity=True):
+ params = {
+ 'InstanceId' : instance_id,
+ 'ShouldDecrementDesiredCapacity' : decrement_capacity
+ }
+ return self.get_object('TerminateInstanceInAutoScalingGroup', params,
+ Activity)
+
diff --git a/vendor/boto/boto/ec2/autoscale/activity.py b/vendor/boto/boto/ec2/autoscale/activity.py
new file mode 100644
index 0000000000..f895d65e9a
--- /dev/null
+++ b/vendor/boto/boto/ec2/autoscale/activity.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2009 Reza Lotun http://reza.lotun.name/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+
+class Activity(object):
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.start_time = None
+ self.activity_id = None
+ self.progress = None
+ self.status_code = None
+ self.cause = None
+ self.description = None
+
+ def __repr__(self):
+ return 'Activity:%s status:%s progress:%s' % (self.description,
+ self.status_code,
+ self.progress)
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'ActivityId':
+ self.activity_id = value
+ elif name == 'StartTime':
+ self.start_time = value
+ elif name == 'Progress':
+ self.progress = value
+ elif name == 'Cause':
+ self.cause = value
+ elif name == 'Description':
+ self.description = value
+ elif name == 'StatusCode':
+ self.status_code = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/ec2/autoscale/group.py b/vendor/boto/boto/ec2/autoscale/group.py
new file mode 100644
index 0000000000..3fa6d68f53
--- /dev/null
+++ b/vendor/boto/boto/ec2/autoscale/group.py
@@ -0,0 +1,189 @@
+# Copyright (c) 2009 Reza Lotun http://reza.lotun.name/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import weakref
+
+from boto.ec2.elb.listelement import ListElement
+from boto.resultset import ResultSet
+from boto.ec2.autoscale.trigger import Trigger
+from boto.ec2.autoscale.request import Request
+
+class Instance(object):
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.instance_id = ''
+
+ def __repr__(self):
+ return 'Instance:%s' % self.instance_id
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'InstanceId':
+ self.instance_id = value
+ else:
+ setattr(self, name, value)
+
+
+class AutoScalingGroup(object):
+ def __init__(self, connection=None, group_name=None,
+ availability_zone=None, launch_config=None,
+ availability_zones=None,
+ load_balancers=None, cooldown=0,
+ min_size=None, max_size=None):
+ """
+ Creates a new AutoScalingGroup with the specified name.
+
+ You must not have already used up your entire quota of
+ AutoScalingGroups in order for this call to be successful. Once the
+ creation request is completed, the AutoScalingGroup is ready to be
+ used in other calls.
+
+ :type name: str
+ :param name: Name of autoscaling group.
+
+ :type availability_zone: str
+ :param availability_zone: An availability zone. DEPRECATED - use the
+ availability_zones parameter, which expects
+ a list of availability zone
+ strings
+
+ :type availability_zone: list
+ :param availability_zone: List of availability zones.
+
+ :type launch_config: str
+ :param launch_config: Name of launch configuration name.
+
+ :type load_balancers: list
+ :param load_balancers: List of load balancers.
+
+ :type minsize: int
+ :param minsize: Minimum size of group
+
+ :type maxsize: int
+ :param maxsize: Maximum size of group
+
+ :type cooldown: int
+ :param cooldown: Amount of time after a Scaling Activity completes
+ before any further scaling activities can start.
+
+ :rtype: tuple
+ :return: Updated healthcheck for the instances.
+ """
+ self.name = group_name
+ self.connection = connection
+ self.min_size = min_size
+ self.max_size = max_size
+ self.created_time = None
+ self.cooldown = cooldown
+ self.launch_config = launch_config
+ if self.launch_config:
+ self.launch_config_name = self.launch_config.name
+ else:
+ self.launch_config_name = None
+ self.desired_capacity = None
+ lbs = load_balancers or []
+ self.load_balancers = ListElement(lbs)
+ zones = availability_zones or []
+ self.availability_zone = availability_zone
+ self.availability_zones = ListElement(zones)
+ self.instances = None
+
+ def __repr__(self):
+ return 'AutoScalingGroup:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Instances':
+ self.instances = ResultSet([('member', Instance)])
+ return self.instances
+ elif name == 'LoadBalancerNames':
+ return self.load_balancers
+ elif name == 'AvailabilityZones':
+ return self.availability_zones
+ else:
+ return
+
+ def endElement(self, name, value, connection):
+ if name == 'MinSize':
+ self.min_size = value
+ elif name == 'CreatedTime':
+ self.created_time = value
+ elif name == 'Cooldown':
+ self.cooldown = value
+ elif name == 'LaunchConfigurationName':
+ self.launch_config_name = value
+ elif name == 'DesiredCapacity':
+ self.desired_capacity = value
+ elif name == 'MaxSize':
+ self.max_size = value
+ elif name == 'AutoScalingGroupName':
+ self.name = value
+ else:
+ setattr(self, name, value)
+
+ def set_capacity(self, capacity):
+ """ Set the desired capacity for the group. """
+ params = {
+ 'AutoScalingGroupName' : self.name,
+ 'DesiredCapacity' : capacity,
+ }
+ req = self.connection.get_object('SetDesiredCapacity', params,
+ Request)
+ self.connection.last_request = req
+ return req
+
+ def update(self):
+ """ Sync local changes with AutoScaling group. """
+ return self.connection._update_group('UpdateAutoScalingGroup', self)
+
+ def shutdown_instances(self):
+ """ Convenience method which shuts down all instances associated with
+ this group.
+ """
+ self.min_size = 0
+ self.max_size = 0
+ self.update()
+
+ def get_all_triggers(self):
+ """ Get all triggers for this auto scaling group. """
+ params = {'AutoScalingGroupName' : self.name}
+ triggers = self.connection.get_list('DescribeTriggers', params,
+ [('member', Trigger)])
+
+ # allow triggers to be able to access the autoscale group
+ for tr in triggers:
+ tr.autoscale_group = weakref.proxy(self)
+
+ return triggers
+
+ def delete(self):
+ """ Delete this auto-scaling group. """
+ params = {'AutoScalingGroupName' : self.name}
+ return self.connection.get_object('DeleteAutoScalingGroup', params,
+ Request)
+
+ def get_activities(self, activity_ids=None, max_records=100):
+ """
+ Get all activies for this group.
+ """
+ return self.connection.get_all_activities(self, activity_ids, max_records)
+
diff --git a/vendor/boto/boto/ec2/autoscale/instance.py b/vendor/boto/boto/ec2/autoscale/instance.py
new file mode 100644
index 0000000000..2e9ae465e4
--- /dev/null
+++ b/vendor/boto/boto/ec2/autoscale/instance.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2009 Reza Lotun http://reza.lotun.name/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+
+class Instance(object):
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.instance_id = ''
+ self.lifecycle_state = None
+ self.availability_zone = ''
+
+ def __repr__(self):
+ return 'Instance:%s' % self.instance_id
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'InstanceId':
+ self.instance_id = value
+ elif name == 'LifecycleState':
+ self.lifecycle_state = value
+ elif name == 'AvailabilityZone':
+ self.availability_zone = value
+ else:
+ setattr(self, name, value)
+
+ # BUG: self.get_object is not defined
+ # BUG: Request is not defined
+ # def terminate(self):
+ # """ Terminate this instance. """
+ # params = {'LaunchConfigurationName' : self.instance_id}
+ # return self.get_object('DeleteLaunchConfiguration', params,
+ # Request)
+
diff --git a/vendor/boto/boto/ec2/autoscale/launchconfig.py b/vendor/boto/boto/ec2/autoscale/launchconfig.py
new file mode 100644
index 0000000000..7587cb64f5
--- /dev/null
+++ b/vendor/boto/boto/ec2/autoscale/launchconfig.py
@@ -0,0 +1,98 @@
+# Copyright (c) 2009 Reza Lotun http://reza.lotun.name/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+
+from boto.ec2.autoscale.request import Request
+from boto.ec2.elb.listelement import ListElement
+
+
+class LaunchConfiguration(object):
+ def __init__(self, connection=None, name=None, image_id=None,
+ key_name=None, security_groups=None, user_data=None,
+ instance_type='m1.small', kernel_id=None,
+ ramdisk_id=None, block_device_mappings=None):
+ """
+ A launch configuration.
+
+ :type name: str
+ :param name: Name of the launch configuration to create.
+
+ :type image_id: str
+ :param image_id: Unique ID of the Amazon Machine Image (AMI) which was
+ assigned during registration.
+
+ :type key_name: str
+ :param key_name: The name of the EC2 key pair.
+
+ :type security_groups: list
+ :param security_groups: Names of the security groups with which to
+ associate the EC2 instances.
+
+ """
+ self.connection = connection
+ self.name = name
+ self.instance_type = instance_type
+ self.block_device_mappings = block_device_mappings
+ self.key_name = key_name
+ sec_groups = security_groups or []
+ self.security_groups = ListElement(sec_groups)
+ self.image_id = image_id
+ self.ramdisk_id = ramdisk_id
+ self.created_time = None
+ self.kernel_id = kernel_id
+ self.user_data = user_data
+ self.created_time = None
+
+ def __repr__(self):
+ return 'LaunchConfiguration:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ if name == 'SecurityGroups':
+ return self.security_groups
+ else:
+ return
+
+ def endElement(self, name, value, connection):
+ if name == 'InstanceType':
+ self.instance_type = value
+ elif name == 'LaunchConfigurationName':
+ self.name = value
+ elif name == 'KeyName':
+ self.key_name = value
+ elif name == 'ImageId':
+ self.image_id = value
+ elif name == 'CreatedTime':
+ self.created_time = value
+ elif name == 'KernelId':
+ self.kernel_id = value
+ elif name == 'RamdiskId':
+ self.ramdisk_id = value
+ elif name == 'UserData':
+ self.user_data = value
+ else:
+ setattr(self, name, value)
+
+ def delete(self):
+ """ Delete this launch configuration. """
+ params = {'LaunchConfigurationName' : self.name}
+ return self.connection.get_object('DeleteLaunchConfiguration', params,
+ Request)
+
diff --git a/vendor/boto/boto/ec2/autoscale/request.py b/vendor/boto/boto/ec2/autoscale/request.py
new file mode 100644
index 0000000000..c066dff5be
--- /dev/null
+++ b/vendor/boto/boto/ec2/autoscale/request.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2009 Reza Lotun http://reza.lotun.name/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Request(object):
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.request_id = ''
+
+ def __repr__(self):
+ return 'Request:%s' % self.request_id
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'RequestId':
+ self.request_id = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/ec2/autoscale/trigger.py b/vendor/boto/boto/ec2/autoscale/trigger.py
new file mode 100644
index 0000000000..197803d1e3
--- /dev/null
+++ b/vendor/boto/boto/ec2/autoscale/trigger.py
@@ -0,0 +1,137 @@
+# Copyright (c) 2009 Reza Lotun http://reza.lotun.name/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import weakref
+
+from boto.ec2.autoscale.request import Request
+
+
+class Trigger(object):
+ """
+ An auto scaling trigger.
+ """
+
+ def __init__(self, connection=None, name=None, autoscale_group=None,
+ dimensions=None, measure_name=None,
+ statistic=None, unit=None, period=60,
+ lower_threshold=None,
+ lower_breach_scale_increment=None,
+ upper_threshold=None,
+ upper_breach_scale_increment=None,
+ breach_duration=None):
+ """
+ Initialize an auto-scaling trigger object.
+
+ :type name: str
+ :param name: The name for this trigger
+
+ :type autoscale_group: str
+ :param autoscale_group: The name of the AutoScalingGroup that will be
+ associated with the trigger. The AutoScalingGroup
+ that will be affected by the trigger when it is
+ activated.
+
+ :type dimensions: list
+ :param dimensions: List of tuples, i.e.
+ ('ImageId', 'i-13lasde') etc.
+
+ :type measure_name: str
+ :param measure_name: The measure name associated with the metric used by
+ the trigger to determine when to activate, for
+ example, CPU, network I/O, or disk I/O.
+
+ :type statistic: str
+ :param statistic: The particular statistic used by the trigger when
+ fetching metric statistics to examine.
+
+ :type period: int
+ :param period: The period associated with the metric statistics in
+ seconds. Valid Values: 60 or a multiple of 60.
+
+ :type unit:
+ :param unit
+
+ :type lower_threshold:
+ :param lower_threshold
+ """
+ self.name = name
+ self.connection = connection
+ self.dimensions = dimensions
+ self.breach_duration = breach_duration
+ self.upper_breach_scale_increment = upper_breach_scale_increment
+ self.created_time = None
+ self.upper_threshold = upper_threshold
+ self.status = None
+ self.lower_threshold = lower_threshold
+ self.period = period
+ self.lower_breach_scale_increment = lower_breach_scale_increment
+ self.statistic = statistic
+ self.unit = unit
+ self.namespace = None
+ if autoscale_group:
+ self.autoscale_group = weakref.proxy(autoscale_group)
+ else:
+ self.autoscale_group = None
+ self.measure_name = measure_name
+
+ def __repr__(self):
+ return 'Trigger:%s' % (self.name)
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'BreachDuration':
+ self.breach_duration = value
+ elif name == 'TriggerName':
+ self.name = value
+ elif name == 'Period':
+ self.period = value
+ elif name == 'CreatedTime':
+ self.created_time = value
+ elif name == 'Statistic':
+ self.statistic = value
+ elif name == 'Unit':
+ self.unit = value
+ elif name == 'Namespace':
+ self.namespace = value
+ elif name == 'AutoScalingGroupName':
+ self.autoscale_group_name = value
+ elif name == 'MeasureName':
+ self.measure_name = value
+ else:
+ setattr(self, name, value)
+
+ def update(self):
+ """ Write out differences to trigger. """
+ self.connection.create_trigger(self)
+
+ def delete(self):
+ """ Delete this trigger. """
+ params = {
+ 'TriggerName' : self.name,
+ 'AutoScalingGroupName' : self.autoscale_group_name,
+ }
+ req =self.connection.get_object('DeleteTrigger', params,
+ Request)
+ self.connection.last_request = req
+ return req
+
diff --git a/vendor/boto/boto/ec2/blockdevicemapping.py b/vendor/boto/boto/ec2/blockdevicemapping.py
new file mode 100644
index 0000000000..f315fe9837
--- /dev/null
+++ b/vendor/boto/boto/ec2/blockdevicemapping.py
@@ -0,0 +1,98 @@
+# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+class BlockDeviceType(object):
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.ephemeral_name = None
+ self.volume_id = None
+ self.snapshot_id = None
+ self.status = None
+ self.attach_time = None
+ self.delete_on_termination = False
+ self.size = None
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name =='volumeId':
+ self.volume_id = value
+ elif name == 'virtualName':
+ self.ephemeral_name = value
+ elif name =='snapshotId':
+ self.snapshot_id = value
+ elif name == 'volumeSize':
+ self.size = int(value)
+ elif name == 'status':
+ self.status = value
+ elif name == 'attachTime':
+ self.attach_time = value
+ elif name == 'deleteOnTermination':
+ if value == 'true':
+ self.delete_on_termination = True
+ else:
+ self.delete_on_termination = False
+ else:
+ setattr(self, name, value)
+
+# for backwards compatibility
+EBSBlockDeviceType = BlockDeviceType
+
+class BlockDeviceMapping(dict):
+
+ def __init__(self, connection=None):
+ dict.__init__(self)
+ self.connection = connection
+ self.current_name = None
+ self.current_value = None
+
+ def startElement(self, name, attrs, connection):
+ if name == 'ebs':
+ self.current_value = BlockDeviceType(self)
+ return self.current_value
+
+ def endElement(self, name, value, connection):
+ if name == 'device' or name == 'deviceName':
+ self.current_name = value
+ elif name == 'item':
+ self[self.current_name] = self.current_value
+
+ def build_list_params(self, params, prefix=''):
+ i = 1
+ for dev_name in self:
+ pre = '%sBlockDeviceMapping.%d' % (prefix, i)
+ params['%s.DeviceName' % pre] = dev_name
+ block_dev = self[dev_name]
+ if block_dev.ephemeral_name:
+ params['%s.VirtualName' % pre] = block_dev.ephemeral_name
+ else:
+ if block_dev.snapshot_id:
+ params['%s.Ebs.SnapshotId' % pre] = block_dev.snapshot_id
+ if block_dev.size:
+ params['%s.Ebs.VolumeSize' % pre] = block_dev.size
+ if block_dev.delete_on_termination:
+ params['%s.Ebs.DeleteOnTermination' % pre] = 'true'
+ else:
+ params['%s.Ebs.DeleteOnTermination' % pre] = 'false'
+ i += 1
diff --git a/vendor/boto/boto/ec2/bundleinstance.py b/vendor/boto/boto/ec2/bundleinstance.py
new file mode 100644
index 0000000000..96519921e4
--- /dev/null
+++ b/vendor/boto/boto/ec2/bundleinstance.py
@@ -0,0 +1,78 @@
+# Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Bundle Task
+"""
+
+from boto.ec2.ec2object import EC2Object
+
+class BundleInstanceTask(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.instance_id = None
+ self.progress = None
+ self.start_time = None
+ self.state = None
+ self.bucket = None
+ self.prefix = None
+ self.upload_policy = None
+ self.upload_policy_signature = None
+ self.update_time = None
+ self.code = None
+ self.message = None
+
+ def __repr__(self):
+ return 'BundleInstanceTask:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'bundleId':
+ self.id = value
+ elif name == 'instanceId':
+ self.instance_id = value
+ elif name == 'progress':
+ self.progress = value
+ elif name == 'startTime':
+ self.start_time = value
+ elif name == 'state':
+ self.state = value
+ elif name == 'bucket':
+ self.bucket = value
+ elif name == 'prefix':
+ self.prefix = value
+ elif name == 'uploadPolicy':
+ self.upload_policy = value
+ elif name == 'uploadPolicySignature':
+ self.upload_policy_signature = value
+ elif name == 'updateTime':
+ self.update_time = value
+ elif name == 'code':
+ self.code = value
+ elif name == 'message':
+ self.message = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/ec2/buyreservation.py b/vendor/boto/boto/ec2/buyreservation.py
new file mode 100644
index 0000000000..45b21a1424
--- /dev/null
+++ b/vendor/boto/boto/ec2/buyreservation.py
@@ -0,0 +1,81 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import boto.ec2
+from boto.sdb.db.property import StringProperty, IntegerProperty
+from boto.manage import propget
+
+InstanceTypes = ['m1.small', 'm1.large', 'm1.xlarge', 'c1.medium', 'c1.xlarge']
+
+class BuyReservation(object):
+
+ def get_region(self, params):
+ if not params.get('region', None):
+ prop = StringProperty(name='region', verbose_name='EC2 Region',
+ choices=boto.ec2.regions)
+ params['region'] = propget.get(prop, choices=boto.ec2.regions)
+
+ def get_instance_type(self, params):
+ if not params.get('instance_type', None):
+ prop = StringProperty(name='instance_type', verbose_name='Instance Type',
+ choices=InstanceTypes)
+ params['instance_type'] = propget.get(prop)
+
+ def get_quantity(self, params):
+ if not params.get('quantity', None):
+ prop = IntegerProperty(name='quantity', verbose_name='Number of Instances')
+ params['quantity'] = propget.get(prop)
+
+ def get_zone(self, params):
+ if not params.get('zone', None):
+ prop = StringProperty(name='zone', verbose_name='EC2 Availability Zone',
+ choices=self.ec2.get_all_zones)
+ params['zone'] = propget.get(prop)
+
+ def get(self, params):
+ self.get_region(params)
+ self.ec2 = params['region'].connect()
+ self.get_instance_type(params)
+ self.get_zone(params)
+ self.get_quantity(params)
+
+if __name__ == "__main__":
+ obj = BuyReservation()
+ params = {}
+ obj.get(params)
+ offerings = obj.ec2.get_all_reserved_instances_offerings(instance_type=params['instance_type'],
+ availability_zone=params['zone'].name)
+ print '\nThe following Reserved Instances Offerings are available:\n'
+ for offering in offerings:
+ offering.describe()
+ prop = StringProperty(name='offering', verbose_name='Offering',
+ choices=offerings)
+ offering = propget.get(prop)
+ print '\nYou have chosen this offering:'
+ offering.describe()
+ unit_price = float(offering.fixed_price)
+ total_price = unit_price * params['quantity']
+ print '!!! You are about to purchase %d of these offerings for a total of $%.2f !!!' % (params['quantity'], total_price)
+ answer = raw_input('Are you sure you want to do this? If so, enter YES: ')
+ if answer.strip().lower() == 'yes':
+ offering.purchase(params['quantity'])
+ else:
+ print 'Purchase cancelled'
diff --git a/vendor/boto/boto/ec2/cloudwatch/__init__.py b/vendor/boto/boto/ec2/cloudwatch/__init__.py
new file mode 100644
index 0000000000..1cb8719e72
--- /dev/null
+++ b/vendor/boto/boto/ec2/cloudwatch/__init__.py
@@ -0,0 +1,213 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+"""
+This module provides an interface to the Elastic Compute Cloud (EC2)
+CloudWatch service from AWS.
+
+The 5 Minute How-To Guide
+-------------------------
+First, make sure you have something to monitor. You can either create a
+LoadBalancer or enable monitoring on an existing EC2 instance. To enable
+monitoring, you can either call the monitor_instance method on the
+EC2Connection object or call the monitor method on the Instance object.
+
+It takes a while for the monitoring data to start accumulating but once
+it does, you can do this:
+
+>>> import boto
+>>> c = boto.connect_cloudwatch()
+>>> metrics = c.list_metrics()
+>>> metrics
+[Metric:NetworkIn,
+ Metric:NetworkOut,
+ Metric:NetworkOut(InstanceType,m1.small),
+ Metric:NetworkIn(InstanceId,i-e573e68c),
+ Metric:CPUUtilization(InstanceId,i-e573e68c),
+ Metric:DiskWriteBytes(InstanceType,m1.small),
+ Metric:DiskWriteBytes(ImageId,ami-a1ffb63),
+ Metric:NetworkOut(ImageId,ami-a1ffb63),
+ Metric:DiskWriteOps(InstanceType,m1.small),
+ Metric:DiskReadBytes(InstanceType,m1.small),
+ Metric:DiskReadOps(ImageId,ami-a1ffb63),
+ Metric:CPUUtilization(InstanceType,m1.small),
+ Metric:NetworkIn(ImageId,ami-a1ffb63),
+ Metric:DiskReadOps(InstanceType,m1.small),
+ Metric:DiskReadBytes,
+ Metric:CPUUtilization,
+ Metric:DiskWriteBytes(InstanceId,i-e573e68c),
+ Metric:DiskWriteOps(InstanceId,i-e573e68c),
+ Metric:DiskWriteOps,
+ Metric:DiskReadOps,
+ Metric:CPUUtilization(ImageId,ami-a1ffb63),
+ Metric:DiskReadOps(InstanceId,i-e573e68c),
+ Metric:NetworkOut(InstanceId,i-e573e68c),
+ Metric:DiskReadBytes(ImageId,ami-a1ffb63),
+ Metric:DiskReadBytes(InstanceId,i-e573e68c),
+ Metric:DiskWriteBytes,
+ Metric:NetworkIn(InstanceType,m1.small),
+ Metric:DiskWriteOps(ImageId,ami-a1ffb63)]
+
+The list_metrics call will return a list of all of the available metrics
+that you can query against. Each entry in the list is a Metric object.
+As you can see from the list above, some of the metrics are generic metrics
+and some have Dimensions associated with them (e.g. InstanceType=m1.small).
+The Dimension can be used to refine your query. So, for example, I could
+query the metric Metric:CPUUtilization which would create the desired statistic
+by aggregating cpu utilization data across all sources of information available
+or I could refine that by querying the metric
+Metric:CPUUtilization(InstanceId,i-e573e68c) which would use only the data
+associated with the instance identified by the instance ID i-e573e68c.
+
+Because for this example, I'm only monitoring a single instance, the set
+of metrics available to me are fairly limited. If I was monitoring many
+instances, using many different instance types and AMI's and also several
+load balancers, the list of available metrics would grow considerably.
+
+Once you have the list of available metrics, you can actually
+query the CloudWatch system for that metric. Let's choose the CPU utilization
+metric for our instance.
+
+>>> m = metrics[5]
+>>> m
+Metric:CPUUtilization(InstanceId,i-e573e68c)
+
+The Metric object has a query method that lets us actually perform
+the query against the collected data in CloudWatch. To call that,
+we need a start time and end time to control the time span of data
+that we are interested in. For this example, let's say we want the
+data for the previous hour:
+
+>>> import datetime
+>>> end = datetime.datetime.now()
+>>> start = end - datetime.timedelta(hours=1)
+
+We also need to supply the Statistic that we want reported and
+the Units to use for the results. The Statistic can be one of these
+values:
+
+['Minimum', 'Maximum', 'Sum', 'Average', 'Samples']
+
+And Units must be one of the following:
+
+['Seconds', 'Percent', 'Bytes', 'Bits', 'Count',
+'Bytes/Second', 'Bits/Second', 'Count/Second']
+
+The query method also takes an optional parameter, period. This
+parameter controls the granularity (in seconds) of the data returned.
+The smallest period is 60 seconds and the value must be a multiple
+of 60 seconds. So, let's ask for the average as a percent:
+
+>>> datapoints = m.query(start, end, 'Average', 'Percent')
+>>> len(datapoints)
+60
+
+Our period was 60 seconds and our duration was one hour so
+we should get 60 data points back and we can see that we did.
+Each element in the datapoints list is a DataPoint object
+which is a simple subclass of a Python dict object. Each
+Datapoint object contains all of the information available
+about that particular data point.
+
+>>> d = datapoints[0]
+>>> d
+{u'Average': 0.0,
+ u'Samples': 1.0,
+ u'Timestamp': u'2009-05-21T19:55:00Z',
+ u'Unit': u'Percent'}
+
+My server obviously isn't very busy right now!
+"""
+from boto.connection import AWSQueryConnection
+from boto.ec2.cloudwatch.metric import Metric
+from boto.ec2.cloudwatch.datapoint import Datapoint
+import boto
+
+class CloudWatchConnection(AWSQueryConnection):
+
+ APIVersion = boto.config.get('Boto', 'cloudwatch_version', '2009-05-15')
+ Endpoint = boto.config.get('Boto', 'cloudwatch_endpoint', 'monitoring.amazonaws.com')
+ SignatureVersion = '2'
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, host=Endpoint, debug=0,
+ https_connection_factory=None, path='/'):
+ """
+ Init method to create a new connection to EC2 Monitoring Service.
+
+ B{Note:} The host argument is overridden by the host specified in the boto configuration file.
+ """
+ AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_key,
+ is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
+ host, debug, https_connection_factory, path)
+
+ def build_list_params(self, params, items, label):
+ if isinstance(items, str):
+ items = [items]
+ for i in range(1, len(items)+1):
+ params[label % i] = items[i-1]
+
+ def get_metric_statistics(self, period, start_time, end_time, measure_name,
+ namespace, statistics=None, dimensions=None, unit=None):
+ """
+ Get time-series data for one or more statistics of a given metric.
+
+ :type measure_name: string
+ :param measure_name: CPUUtilization|NetworkIO-in|NetworkIO-out|DiskIO-ALL-read|
+ DiskIO-ALL-write|DiskIO-ALL-read-bytes|DiskIO-ALL-write-bytes
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.image.Image`
+ """
+ params = {'Period' : period,
+ 'MeasureName' : measure_name,
+ 'Namespace' : namespace,
+ 'StartTime' : start_time.isoformat(),
+ 'EndTime' : end_time.isoformat()}
+ if dimensions:
+ i = 1
+ for name in dimensions:
+ params['Dimensions.member.%d.Name' % i] = name
+ params['Dimensions.member.%d.Value' % i] = dimensions[name]
+ i += 1
+ if statistics:
+ self.build_list_params(params, statistics, 'Statistics.member.%d')
+ return self.get_list('GetMetricStatistics', params, [('member', Datapoint)])
+
+ def list_metrics(self, next_token=None):
+ """
+ Returns a list of the valid metrics for which there is recorded data available.
+
+ :type next_token: string
+ :param next_token: A maximum of 500 metrics will be returned at one time.
+ If more results are available, the ResultSet returned
+ will contain a non-Null next_token attribute. Passing
+ that token as a parameter to list_metrics will retrieve
+ the next page of metrics.
+ """
+ params = {}
+ if next_token:
+ params['NextToken'] = next_token
+ return self.get_list('ListMetrics', params, [('member', Metric)])
+
+
+
diff --git a/vendor/boto/boto/ec2/cloudwatch/datapoint.py b/vendor/boto/boto/ec2/cloudwatch/datapoint.py
new file mode 100644
index 0000000000..1860f4a44a
--- /dev/null
+++ b/vendor/boto/boto/ec2/cloudwatch/datapoint.py
@@ -0,0 +1,37 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+class Datapoint(dict):
+
+ def __init__(self, connection=None):
+ dict.__init__(self)
+ self.connection = connection
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name in ['Average', 'Maximum', 'Minimum', 'Samples', 'Sum']:
+ self[name] = float(value)
+ elif name != 'member':
+ self[name] = value
+
diff --git a/vendor/boto/boto/ec2/cloudwatch/metric.py b/vendor/boto/boto/ec2/cloudwatch/metric.py
new file mode 100644
index 0000000000..e4661f43ee
--- /dev/null
+++ b/vendor/boto/boto/ec2/cloudwatch/metric.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+class Dimensions(dict):
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'Name':
+ self._name = value
+ elif name == 'Value':
+ self[self._name] = value
+ elif name != 'Dimensions' and name != 'member':
+ self[name] = value
+
+class Metric(object):
+
+ Statistics = ['Minimum', 'Maximum', 'Sum', 'Average', 'Samples']
+ Units = ['Seconds', 'Percent', 'Bytes', 'Bits', 'Count',
+ 'Bytes/Second', 'Bits/Second', 'Count/Second']
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.name = None
+ self.namespace = None
+ self.dimensions = None
+
+ def __repr__(self):
+ s = 'Metric:%s' % self.name
+ if self.dimensions:
+ for name,value in self.dimensions.items():
+ s += '(%s,%s)' % (name, value)
+ return s
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Dimensions':
+ self.dimensions = Dimensions()
+ return self.dimensions
+
+ def endElement(self, name, value, connection):
+ if name == 'MeasureName':
+ self.name = value
+ elif name == 'Namespace':
+ self.namespace = value
+ else:
+ setattr(self, name, value)
+
+ def query(self, start_time, end_time, statistic, unit, period=60):
+ return self.connection.get_metric_statistics(period, start_time, end_time,
+ self.name, self.namespace, [statistic],
+ self.dimensions, unit)
diff --git a/vendor/boto/boto/ec2/connection.py b/vendor/boto/boto/ec2/connection.py
new file mode 100644
index 0000000000..a1ddf16739
--- /dev/null
+++ b/vendor/boto/boto/ec2/connection.py
@@ -0,0 +1,1605 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a connection to the EC2 service.
+"""
+
+import urllib
+import base64
+import hmac
+import boto
+from hashlib import sha1 as sha
+from boto.connection import AWSQueryConnection
+from boto.resultset import ResultSet
+from boto.ec2.image import Image, ImageAttribute
+from boto.ec2.instance import Reservation, Instance, ConsoleOutput, InstanceAttribute
+from boto.ec2.keypair import KeyPair
+from boto.ec2.address import Address
+from boto.ec2.volume import Volume
+from boto.ec2.snapshot import Snapshot
+from boto.ec2.snapshot import SnapshotAttribute
+from boto.ec2.zone import Zone
+from boto.ec2.securitygroup import SecurityGroup
+from boto.ec2.regioninfo import RegionInfo
+from boto.ec2.instanceinfo import InstanceInfo
+from boto.ec2.reservedinstance import ReservedInstancesOffering, ReservedInstance
+from boto.ec2.spotinstancerequest import SpotInstanceRequest
+from boto.ec2.spotpricehistory import SpotPriceHistory
+from boto.ec2.spotdatafeedsubscription import SpotDatafeedSubscription
+from boto.ec2.bundleinstance import BundleInstanceTask
+from boto.exception import EC2ResponseError
+
+#boto.set_stream_logger('ec2')
+
+class EC2Connection(AWSQueryConnection):
+
+ APIVersion = boto.config.get('Boto', 'ec2_version', '2009-11-30')
+ DefaultRegionName = boto.config.get('Boto', 'ec2_region_name', 'us-east-1')
+ DefaultRegionEndpoint = boto.config.get('Boto', 'ec2_region_endpoint',
+ 'ec2.amazonaws.com')
+ SignatureVersion = '2'
+ ResponseError = EC2ResponseError
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, host=None, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, debug=0,
+ https_connection_factory=None, region=None, path='/'):
+ """
+ Init method to create a new connection to EC2.
+
+ B{Note:} The host argument is overridden by the host specified in the boto configuration file.
+ """
+ if not region:
+ region = RegionInfo(self, self.DefaultRegionName, self.DefaultRegionEndpoint)
+ self.region = region
+ AWSQueryConnection.__init__(self, aws_access_key_id,
+ aws_secret_access_key,
+ is_secure, port, proxy, proxy_port,
+ proxy_user, proxy_pass,
+ self.region.endpoint, debug,
+ https_connection_factory, path)
+
+ def get_params(self):
+ """
+ Returns a dictionary containing the value of of all of the keyword
+ arguments passed when constructing this connection.
+ """
+ param_names = ['aws_access_key_id', 'aws_secret_access_key', 'is_secure',
+ 'port', 'proxy', 'proxy_port', 'proxy_user', 'proxy_pass',
+ 'debug', 'https_connection_factory']
+ params = {}
+ for name in param_names:
+ params[name] = getattr(self, name)
+ return params
+
+ # Image methods
+
+ def get_all_images(self, image_ids=None, owners=None, executable_by=None):
+ """
+ Retrieve all the EC2 images available on your account.
+
+ :type image_ids: list
+ :param image_ids: A list of strings with the image IDs wanted
+
+ :type owners: list
+ :param owners: A list of owner IDs
+
+ :type executable_by:
+ :param executable_by:
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.image.Image`
+ """
+ params = {}
+ if image_ids:
+ self.build_list_params(params, image_ids, 'ImageId')
+ if owners:
+ self.build_list_params(params, owners, 'Owner')
+ if executable_by:
+ self.build_list_params(params, executable_by, 'ExecutableBy')
+ return self.get_list('DescribeImages', params, [('item', Image)])
+
+ def get_all_kernels(self, kernel_ids=None, owners=None):
+ """
+ Retrieve all the EC2 kernels available on your account. Simply filters the list returned
+ by get_all_images because EC2 does not provide a way to filter server-side.
+
+ :type kernel_ids: list
+ :param kernel_ids: A list of strings with the image IDs wanted
+
+ :type owners: list
+ :param owners: A list of owner IDs
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.image.Image`
+ """
+ rs = self.get_all_images(kernel_ids, owners)
+ kernels = []
+ for image in rs:
+ if image.type == 'kernel':
+ kernels.append(image)
+ return kernels
+
+ def get_all_ramdisks(self, ramdisk_ids=None, owners=None):
+ """
+ Retrieve all the EC2 ramdisks available on your account.
+ Simply filters the list returned by get_all_images because
+ EC2 does not provide a way to filter server-side.
+
+ :type ramdisk_ids: list
+ :param ramdisk_ids: A list of strings with the image IDs wanted
+
+ :type owners: list
+ :param owners: A list of owner IDs
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.image.Image`
+ """
+ rs = self.get_all_images(ramdisk_ids, owners)
+ ramdisks = []
+ for image in rs:
+ if image.type == 'ramdisk':
+ ramdisks.append(image)
+ return ramdisks
+
+ def get_image(self, image_id):
+ """
+ Shortcut method to retrieve a specific image (AMI).
+
+ :type image_id: string
+ :param image_id: the ID of the Image to retrieve
+
+ :rtype: :class:`boto.ec2.image.Image`
+ :return: The EC2 Image specified or None if the image is not found
+ """
+ try:
+ return self.get_all_images(image_ids=[image_id])[0]
+ except IndexError: # None of those images available
+ return None
+
+ def register_image(self, name=None, description=None, image_location=None,
+ architecture=None, kernel_id=None, ramdisk_id=None,
+ root_device_name=None, block_device_map=None):
+ """
+ Register an image.
+
+ :type name: string
+ :param name: The name of the AMI. Valid only for EBS-based images.
+
+ :type description: string
+ :param description: The description of the AMI.
+
+ :type image_location: string
+ :param image_location: Full path to your AMI manifest in Amazon S3 storage.
+ Only used for S3-based AMI's.
+
+ :type architecture: string
+ :param architecture: The architecture of the AMI. Valid choices are:
+ i386 | x86_64
+
+ :type kernel_id: string
+ :param kernel_id: The ID of the kernel with which to launch the instances
+
+ :type root_device_name: string
+ :param root_device_name: The root device name (e.g. /dev/sdh)
+
+ :type block_device_map: :class:`boto.ec2.blockdevicemapping.BlockDeviceMapping`
+ :param block_device_map: A BlockDeviceMapping data structure
+ describing the EBS volumes associated
+ with the Image.
+
+ :rtype: string
+ :return: The new image id
+ """
+ params = {}
+ if name:
+ params['Name'] = name
+ if description:
+ params['Description'] = description
+ if architecture:
+ params['Architecture'] = architecture
+ if kernel_id:
+ params['KernelId'] = kernel_id
+ if ramdisk_id:
+ params['RamdiskId'] = ramdisk_id
+ if image_location:
+ params['ImageLocation'] = image_location
+ if root_device_name:
+ params['RootDeviceName'] = root_device_name
+ if block_device_map:
+ block_device_map.build_list_params(params)
+ rs = self.get_object('RegisterImage', params, ResultSet)
+ image_id = getattr(rs, 'imageId', None)
+ return image_id
+
+ def deregister_image(self, image_id):
+ """
+ Unregister an AMI.
+
+ :type image_id: string
+ :param image_id: the ID of the Image to unregister
+
+ :rtype: bool
+ :return: True if successful
+ """
+ return self.get_status('DeregisterImage', {'ImageId':image_id})
+
+ def create_image(self, instance_id, name, description=None, no_reboot=False):
+ """
+ Will create an AMI from the instance in the running or stopped
+ state.
+
+ :type instance_id: string
+ :param instance_id: the ID of the instance to image.
+
+ :type name: string
+ :param name: The name of the new image
+
+ :type description: string
+ :param description: An optional human-readable string describing
+ the contents and purpose of the AMI.
+
+ :type no_reboot: bool
+ :param no_reboot: An optional flag indicating that the bundling process
+ should not attempt to shutdown the instance before
+ bundling. If this flag is True, the responsibility
+ of maintaining file system integrity is left to the
+ owner of the instance.
+
+ :rtype: string
+ :return: The new image id
+ """
+ params = {'InstanceId' : instance_id,
+ 'Name' : name}
+ if description:
+ params['Description'] = description
+ if no_reboot:
+ params['NoReboot'] = 'true'
+ rs = self.get_object('CreateImage', params, Image)
+ image_id = getattr(rs, 'imageId', None)
+ if not image_id:
+ image_id = getattr(rs, 'ImageId', None)
+ return image_id
+
+ # ImageAttribute methods
+
+ def get_image_attribute(self, image_id, attribute='launchPermission'):
+ """
+ Gets an attribute from an image.
+ See http://docs.amazonwebservices.com/AWSEC2/2008-02-01/DeveloperGuide/ApiReference-Query-DescribeImageAttribute.html
+
+ :type image_id: string
+ :param image_id: The Amazon image id for which you want info about
+
+ :type attribute: string
+ :param attribute: The attribute you need information about.
+ Valid choices are:
+ * launchPermission
+ * productCodes
+ * blockDeviceMapping
+
+ :rtype: :class:`boto.ec2.image.ImageAttribute`
+ :return: An ImageAttribute object representing the value of the attribute requested
+ """
+ params = {'ImageId' : image_id,
+ 'Attribute' : attribute}
+ return self.get_object('DescribeImageAttribute', params, ImageAttribute)
+
+ def modify_image_attribute(self, image_id, attribute='launchPermission',
+ operation='add', user_ids=None, groups=None,
+ product_codes=None):
+ """
+ Changes an attribute of an image.
+ See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-ModifyImageAttribute.html
+
+ :type image_id: string
+ :param image_id: The image id you wish to change
+
+ :type attribute: string
+ :param attribute: The attribute you wish to change
+
+ :type operation: string
+ :param operation: Either add or remove (this is required for changing launchPermissions)
+
+ :type user_ids: list
+ :param user_ids: The Amazon IDs of users to add/remove attributes
+
+ :type groups: list
+ :param groups: The groups to add/remove attributes
+
+ :type product_codes: list
+ :param product_codes: Amazon DevPay product code. Currently only one
+ product code can be associated with an AMI. Once
+ set, the product code cannot be changed or reset.
+ """
+ params = {'ImageId' : image_id,
+ 'Attribute' : attribute,
+ 'OperationType' : operation}
+ if user_ids:
+ self.build_list_params(params, user_ids, 'UserId')
+ if groups:
+ self.build_list_params(params, groups, 'UserGroup')
+ if product_codes:
+ self.build_list_params(params, product_codes, 'ProductCode')
+ return self.get_status('ModifyImageAttribute', params)
+
+ def reset_image_attribute(self, image_id, attribute='launchPermission'):
+ """
+ Resets an attribute of an AMI to its default value.
+ See http://docs.amazonwebservices.com/AWSEC2/2008-02-01/DeveloperGuide/ApiReference-Query-ResetImageAttribute.html
+
+ :type image_id: string
+ :param image_id: ID of the AMI for which an attribute will be described
+
+ :type attribute: string
+ :param attribute: The attribute to reset
+
+ :rtype: bool
+ :return: Whether the operation succeeded or not
+ """
+ params = {'ImageId' : image_id,
+ 'Attribute' : attribute}
+ return self.get_status('ResetImageAttribute', params)
+
+ # Instance methods
+
+ def get_all_instances(self, instance_ids=None):
+ """
+ Retrieve all the instances associated with your account.
+
+ :type instance_ids: list
+ :param instance_ids: A list of strings of instance IDs
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.instance.Reservation`
+ """
+ params = {}
+ if instance_ids:
+ self.build_list_params(params, instance_ids, 'InstanceId')
+ return self.get_list('DescribeInstances', params, [('item', Reservation)])
+
+ def run_instances(self, image_id, min_count=1, max_count=1,
+ key_name=None, security_groups=None,
+ user_data=None, addressing_type=None,
+ instance_type='m1.small', placement=None,
+ kernel_id=None, ramdisk_id=None,
+ monitoring_enabled=False, subnet_id=None,
+ block_device_map=None,
+ instance_initiated_shutdown_behavior=None):
+ """
+ Runs an image on EC2.
+
+ :type image_id: string
+ :param image_id: The ID of the image to run
+
+ :type min_count: int
+ :param min_count: The minimum number of instances to launch
+
+ :type max_count: int
+ :param max_count: The maximum number of instances to launch
+
+ :type key_name: string
+ :param key_name: The name of the key pair with which to launch instances
+
+ :type security_groups: list of strings
+ :param security_groups: The names of the security groups with which to associate instances
+
+ :type user_data: string
+ :param user_data: The user data passed to the launched instances
+
+ :type instance_type: string
+ :param instance_type: The type of instance to run (m1.small, m1.large, m1.xlarge)
+
+ :type placement: string
+ :param placement: The availability zone in which to launch the instances
+
+ :type kernel_id: string
+ :param kernel_id: The ID of the kernel with which to launch the instances
+
+ :type ramdisk_id: string
+ :param ramdisk_id: The ID of the RAM disk with which to launch the instances
+
+ :type monitoring_enabled: bool
+ :param monitoring_enabled: Enable CloudWatch monitoring on the instance.
+
+ :type subnet_id: string
+ :param subnet_id: The subnet ID within which to launch the instances for VPC.
+
+ :type block_device_map: :class:`boto.ec2.blockdevicemapping.BlockDeviceMapping`
+ :param block_device_map: A BlockDeviceMapping data structure
+ describing the EBS volumes associated
+ with the Image.
+
+ :type instance_initiated_shutdown_behavior: string
+ :param instance_initiated_shutdown_behavior: Specifies whether the instance's
+ EBS volues are stopped (i.e. detached)
+ or terminated (i.e. deleted) when
+ the instance is shutdown by the
+ owner. Valid values are:
+ stop | terminate
+
+ :rtype: Reservation
+ :return: The :class:`boto.ec2.instance.Reservation` associated with the request for machines
+ """
+ params = {'ImageId':image_id,
+ 'MinCount':min_count,
+ 'MaxCount': max_count}
+ if key_name:
+ params['KeyName'] = key_name
+ if security_groups:
+ l = []
+ for group in security_groups:
+ if isinstance(group, SecurityGroup):
+ l.append(group.name)
+ else:
+ l.append(group)
+ self.build_list_params(params, l, 'SecurityGroup')
+ if user_data:
+ params['UserData'] = base64.b64encode(user_data)
+ if addressing_type:
+ params['AddressingType'] = addressing_type
+ if instance_type:
+ params['InstanceType'] = instance_type
+ if placement:
+ params['Placement.AvailabilityZone'] = placement
+ if kernel_id:
+ params['KernelId'] = kernel_id
+ if ramdisk_id:
+ params['RamdiskId'] = ramdisk_id
+ if monitoring_enabled:
+ params['Monitoring.Enabled'] = 'true'
+ if subnet_id:
+ params['SubnetId'] = subnet_id
+ if block_device_map:
+ block_device_map.build_list_params(params)
+ if instance_initiated_shutdown_behavior:
+ val = instance_initiated_shutdown_behavior
+ params['InstanceInitiatedShutdownBehavior'] = val
+ return self.get_object('RunInstances', params, Reservation, verb='POST')
+
+ def terminate_instances(self, instance_ids=None):
+ """
+ Terminate the instances specified
+
+ :type instance_ids: list
+ :param instance_ids: A list of strings of the Instance IDs to terminate
+
+ :rtype: list
+ :return: A list of the instances terminated
+ """
+ params = {}
+ if instance_ids:
+ self.build_list_params(params, instance_ids, 'InstanceId')
+ return self.get_list('TerminateInstances', params, [('item', Instance)])
+
+ def stop_instances(self, instance_ids=None, force=False):
+ """
+ Stop the instances specified
+
+ :type instance_ids: list
+ :param instance_ids: A list of strings of the Instance IDs to stop
+
+ :type force: bool
+ :param force: Forces the instance to stop
+
+ :rtype: list
+ :return: A list of the instances stopped
+ """
+ params = {}
+ if force:
+ params['Force'] = 'true'
+ if instance_ids:
+ self.build_list_params(params, instance_ids, 'InstanceId')
+ return self.get_list('StopInstances', params, [('item', Instance)])
+
+ def start_instances(self, instance_ids=None):
+ """
+ Start the instances specified
+
+ :type instance_ids: list
+ :param instance_ids: A list of strings of the Instance IDs to start
+
+ :rtype: list
+ :return: A list of the instances started
+ """
+ params = {}
+ if instance_ids:
+ self.build_list_params(params, instance_ids, 'InstanceId')
+ return self.get_list('StartInstances', params, [('item', Instance)])
+
+ def get_console_output(self, instance_id):
+ """
+ Retrieves the console output for the specified instance.
+ See http://docs.amazonwebservices.com/AWSEC2/2008-02-01/DeveloperGuide/ApiReference-Query-GetConsoleOutput.html
+
+ :type instance_id: string
+ :param instance_id: The instance ID of a running instance on the cloud.
+
+ :rtype: :class:`boto.ec2.instance.ConsoleOutput`
+ :return: The console output as a ConsoleOutput object
+ """
+ params = {}
+ self.build_list_params(params, [instance_id], 'InstanceId')
+ return self.get_object('GetConsoleOutput', params, ConsoleOutput)
+
+ def reboot_instances(self, instance_ids=None):
+ """
+ Reboot the specified instances.
+
+ :type instance_ids: list
+ :param instance_ids: The instances to terminate and reboot
+ """
+ params = {}
+ if instance_ids:
+ self.build_list_params(params, instance_ids, 'InstanceId')
+ return self.get_status('RebootInstances', params)
+
+ def confirm_product_instance(self, product_code, instance_id):
+ params = {'ProductCode' : product_code,
+ 'InstanceId' : instance_id}
+ rs = self.get_object('ConfirmProductInstance', params, ResultSet)
+ return (rs.status, rs.ownerId)
+
+ # InstanceAttribute methods
+
+ def get_instance_attribute(self, instance_id, attribute):
+ """
+ Gets an attribute from an instance.
+
+ :type instance_id: string
+ :param instance_id: The Amazon id of the instance
+
+ :type attribute: string
+ :param attribute: The attribute you need information about
+ Valid choices are:
+ instanceType|kernel|ramdisk|userData|
+ disableApiTermination|
+ instanceInitiatedShutdownBehavior|
+ rootDeviceName|blockDeviceMapping
+
+ :rtype: :class:`boto.ec2.image.ImageAttribute`
+ :return: An ImageAttribute object representing the value of the attribute requested
+ """
+ params = {'InstanceId' : instance_id}
+ if attribute:
+ params['Attribute'] = attribute
+ return self.get_object('DescribeInstanceAttribute', params, InstanceAttribute)
+
+ def modify_instance_attribute(self, instance_id, attribute, value):
+ """
+ Changes an attribute of an instance
+
+ :type instance_id: string
+ :param instance_id: The instance id you wish to change
+
+ :type attribute: string
+ :param attribute: The attribute you wish to change.
+ AttributeName - Expected value (default)
+ instanceType - A valid instance type (m1.small)
+ kernel - Kernel ID (None)
+ ramdisk - Ramdisk ID (None)
+ userData - Base64 encoded String (None)
+ disableApiTermination - Boolean (true)
+ instanceInitiatedShutdownBehavior - stop|terminate
+ rootDeviceName - device name (None)
+
+ :type value: string
+ :param value: The new value for the attribute
+
+ :rtype: bool
+ :return: Whether the operation succeeded or not
+ """
+ params = {'InstanceId' : instance_id,
+ 'Attribute' : attribute,
+ 'Value' : value}
+ return self.get_status('ModifyInstanceAttribute', params)
+
+ def reset_instance_attribute(self, instance_id, attribute):
+ """
+ Resets an attribute of an instance to its default value.
+
+ :type instance_id: string
+ :param instance_id: ID of the instance
+
+ :type attribute: string
+ :param attribute: The attribute to reset. Valid values are:
+ kernel|ramdisk
+
+ :rtype: bool
+ :return: Whether the operation succeeded or not
+ """
+ params = {'InstanceId' : instance_id,
+ 'Attribute' : attribute}
+ return self.get_status('ResetInstanceAttribute', params)
+
+ # Spot Instances
+
+ def get_all_spot_instance_requests(self, request_ids=None):
+ """
+ Retrieve all the spot instances requests associated with your account.
+
+ @type request_ids: list
+ @param request_ids: A list of strings of spot instance request IDs
+
+ @rtype: list
+ @return: A list of
+ :class:`boto.ec2.spotinstancerequest.SpotInstanceRequest`
+ """
+ params = {}
+ if request_ids:
+ self.build_list_params(params, request_ids, 'SpotInstanceRequestId')
+ return self.get_list('DescribeSpotInstanceRequests', params,
+ [('item', SpotInstanceRequest)])
+
+ def get_spot_price_history(self, start_time=None, end_time=None,
+ instance_type=None, product_description=None):
+ """
+ Retrieve the recent history of spot instances pricing.
+
+ @type start_time: str
+ @param start_time: An indication of how far back to provide price
+ changes for. An ISO8601 DateTime string.
+
+ @type end_time: str
+ @param end_time: An indication of how far forward to provide price
+ changes for. An ISO8601 DateTime string.
+
+ @type instance_type: str
+ @param instance_type: Filter responses to a particular instance type.
+
+ @type product_description: str
+ @param product_descripton: Filter responses to a particular platform.
+ Valid values are currently: Linux
+
+ @rtype: list
+ @return: A list tuples containing price and timestamp.
+ """
+ params = {}
+ if start_time:
+ params['StartTime'] = start_time
+ if end_time:
+ params['EndTime'] = end_time
+ if instance_type:
+ params['InstanceType'] = instance_type
+ if product_description:
+ params['ProductDescription'] = product_description
+ return self.get_list('DescribeSpotPriceHistory', params, [('item', SpotPriceHistory)])
+
+ def request_spot_instances(self, price, image_id, count=1, type=None,
+ valid_from=None, valid_until=None,
+ launch_group=None, availability_zone_group=None,
+ key_name=None, security_groups=None,
+ user_data=None, addressing_type=None,
+ instance_type='m1.small', placement=None,
+ kernel_id=None, ramdisk_id=None,
+ monitoring_enabled=False, subnet_id=None,
+ block_device_map=None):
+ """
+ Request instances on the spot market at a particular price.
+
+ :type price: str
+ :param price: The maximum price of your bid
+
+ :type image_id: string
+ :param image_id: The ID of the image to run
+
+ :type count: int
+ :param count: The of instances to requested
+
+ :type type: str
+ :param type: Type of request. Can be 'one-time' or 'persistent'.
+ Default is one-time.
+
+ :type valid_from: str
+ :param valid_from: Start date of the request. An ISO8601 time string.
+
+ :type valid_until: str
+ :param valid_until: End date of the request. An ISO8601 time string.
+
+ :type launch_group: str
+ :param launch_group: If supplied, all requests will be fulfilled
+ as a group.
+
+ :type availability_zone_group: str
+ :param availability_zone_group: If supplied, all requests will be fulfilled
+ within a single availability zone.
+
+ :type key_name: string
+ :param key_name: The name of the key pair with which to launch instances
+
+ :type security_groups: list of strings
+ :param security_groups: The names of the security groups with which to associate instances
+
+ :type user_data: string
+ :param user_data: The user data passed to the launched instances
+
+ :type instance_type: string
+ :param instance_type: The type of instance to run (m1.small, m1.large, m1.xlarge)
+
+ :type placement: string
+ :param placement: The availability zone in which to launch the instances
+
+ :type kernel_id: string
+ :param kernel_id: The ID of the kernel with which to launch the instances
+
+ :type ramdisk_id: string
+ :param ramdisk_id: The ID of the RAM disk with which to launch the instances
+
+ :type monitoring_enabled: bool
+ :param monitoring_enabled: Enable CloudWatch monitoring on the instance.
+
+ :type subnet_id: string
+ :param subnet_id: The subnet ID within which to launch the instances for VPC.
+
+ :type block_device_map: :class:`boto.ec2.blockdevicemapping.BlockDeviceMapping`
+ :param block_device_map: A BlockDeviceMapping data structure
+ describing the EBS volumes associated
+ with the Image.
+
+ :rtype: Reservation
+ :return: The :class:`boto.ec2.instance.Reservation` associated with the request for machines
+ """
+ params = {'LaunchSpecification.ImageId':image_id,
+ 'SpotPrice' : price}
+ if count:
+ params['InstanceCount'] = count
+ if valid_from:
+ params['ValidFrom'] = valid_from
+ if valid_until:
+ params['ValidUntil'] = valid_until
+ if launch_group:
+ params['LaunchGroup'] = launch_group
+ if availability_zone_group:
+ params['AvailabilityZoneGroup'] = availability_zone_group
+ if key_name:
+ params['LaunchSpecification.KeyName'] = key_name
+ if security_groups:
+ l = []
+ for group in security_groups:
+ if isinstance(group, SecurityGroup):
+ l.append(group.name)
+ else:
+ l.append(group)
+ self.build_list_params(params, l,
+ 'LaunchSpecification.SecurityGroup')
+ if user_data:
+ params['LaunchSpecification.UserData'] = base64.b64encode(user_data)
+ if addressing_type:
+ params['LaunchSpecification.AddressingType'] = addressing_type
+ if instance_type:
+ params['LaunchSpecification.InstanceType'] = instance_type
+ if placement:
+ params['LaunchSpecification.Placement.AvailabilityZone'] = placement
+ if kernel_id:
+ params['LaunchSpecification.KernelId'] = kernel_id
+ if ramdisk_id:
+ params['LaunchSpecification.RamdiskId'] = ramdisk_id
+ if monitoring_enabled:
+ params['LaunchSpecification.Monitoring.Enabled'] = 'true'
+ if subnet_id:
+ params['LaunchSpecification.SubnetId'] = subnet_id
+ if block_device_map:
+ block_device_map.build_list_params(params, 'LaunchSpecification.')
+ return self.get_list('RequestSpotInstances', params,
+ [('item', SpotInstanceRequest)],
+ verb='POST')
+
+
+ def cancel_spot_instance_requests(self, request_ids):
+ """
+ Cancel the specified Spot Instance Requests.
+
+ :type request_ids: list
+ :param request_ids: A list of strings of the Request IDs to terminate
+
+ :rtype: list
+ :return: A list of the instances terminated
+ """
+ params = {}
+ if request_ids:
+ self.build_list_params(params, request_ids, 'SpotInstanceRequestId')
+ return self.get_list('CancelSpotInstanceRequests', params, [('item', Instance)])
+
+ def get_spot_datafeed_subscription(self):
+ """
+ Return the current spot instance data feed subscription
+ associated with this account, if any.
+
+ :rtype: :class:`boto.ec2.spotdatafeedsubscription.SpotDatafeedSubscription`
+ :return: The datafeed subscription object or None
+ """
+ return self.get_object('DescribeSpotDatafeedSubscription',
+ None, SpotDatafeedSubscription)
+
+ def create_spot_datafeed_subscription(self, bucket, prefix):
+ """
+ Create a spot instance datafeed subscription for this account.
+
+ :type bucket: str or unicode
+ :param bucket: The name of the bucket where spot instance data
+ will be written. The account issuing this request
+ must have FULL_CONTROL access to the bucket
+ specified in the request.
+
+ :type prefix: str or unicode
+ :param prefix: An optional prefix that will be pre-pended to all
+ data files written to the bucket.
+
+ :rtype: :class:`boto.ec2.spotdatafeedsubscription.SpotDatafeedSubscription`
+ :return: The datafeed subscription object or None
+ """
+ params = {'Bucket' : bucket}
+ if prefix:
+ params['Prefix'] = prefix
+ return self.get_object('CreateSpotDatafeedSubscription',
+ params, SpotDatafeedSubscription)
+
+ def delete_spot_datafeed_subscription(self):
+ """
+ Delete the current spot instance data feed subscription
+ associated with this account
+
+ :rtype: bool
+ :return: True if successful
+ """
+ return self.get_status('DeleteSpotDatafeedSubscription', None)
+
+ # Zone methods
+
+ def get_all_zones(self, zones=None):
+ """
+ Get all Availability Zones associated with the current region.
+
+ :type zones: list
+ :param zones: Optional list of zones. If this list is present,
+ only the Zones associated with these zone names
+ will be returned.
+
+ :rtype: list of L{boto.ec2.zone.Zone}
+ :return: The requested Zone objects
+ """
+ params = {}
+ if zones:
+ self.build_list_params(params, zones, 'ZoneName')
+ return self.get_list('DescribeAvailabilityZones', params, [('item', Zone)])
+
+ # Address methods
+
+ def get_all_addresses(self, addresses=None):
+ """
+ Get all EIP's associated with the current credentials.
+
+ :type addresses: list
+ :param addresses: Optional list of addresses. If this list is present,
+ only the Addresses associated with these addresses
+ will be returned.
+
+ :rtype: list of L{boto.ec2.address.Address}
+ :return: The requested Address objects
+ """
+ params = {}
+ if addresses:
+ self.build_list_params(params, addresses, 'PublicIp')
+ return self.get_list('DescribeAddresses', params, [('item', Address)])
+
+ def allocate_address(self):
+ """
+ Allocate a new Elastic IP address and associate it with your account.
+
+ :rtype: L{boto.ec2.address.Address}
+ :return: The newly allocated Address
+ """
+ return self.get_object('AllocateAddress', None, Address)
+
+ def associate_address(self, instance_id, public_ip):
+ """
+ Associate an Elastic IP address with a currently running instance.
+
+ :type instance_id: string
+ :param instance_id: The ID of the instance
+
+ :type public_ip: string
+ :param public_ip: The public IP address
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'InstanceId' : instance_id, 'PublicIp' : public_ip}
+ return self.get_status('AssociateAddress', params)
+
+ def disassociate_address(self, public_ip):
+ """
+ Disassociate an Elastic IP address from a currently running instance.
+
+ :type public_ip: string
+ :param public_ip: The public IP address
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'PublicIp' : public_ip}
+ return self.get_status('DisassociateAddress', params)
+
+ def release_address(self, public_ip):
+ """
+ Free up an Elastic IP address
+
+ :type public_ip: string
+ :param public_ip: The public IP address
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'PublicIp' : public_ip}
+ return self.get_status('ReleaseAddress', params)
+
+ # Volume methods
+
+ def get_all_volumes(self, volume_ids=None):
+ """
+ Get all Volumes associated with the current credentials.
+
+ :type volume_ids: list
+ :param volume_ids: Optional list of volume ids. If this list is present,
+ only the volumes associated with these volume ids
+ will be returned.
+
+ :rtype: list of L{boto.ec2.volume.Volume}
+ :return: The requested Volume objects
+ """
+ params = {}
+ if volume_ids:
+ self.build_list_params(params, volume_ids, 'VolumeId')
+ return self.get_list('DescribeVolumes', params, [('item', Volume)])
+
+ def create_volume(self, size, zone, snapshot=None):
+ """
+ Create a new EBS Volume.
+
+ :type size: int
+ :param size: The size of the new volume, in GiB
+
+ :type zone: string or L{boto.ec2.zone.Zone}
+ :param zone: The availability zone in which the Volume will be created.
+
+ :type snapshot: string or L{boto.ec2.snapshot.Snapshot}
+ :param snapshot: The snapshot from which the new Volume will be created.
+ """
+ if isinstance(zone, Zone):
+ zone = zone.name
+ params = {'AvailabilityZone' : zone}
+ if size:
+ params['Size'] = size
+ if snapshot:
+ if isinstance(snapshot, Snapshot):
+ snapshot = snapshot.id
+ params['SnapshotId'] = snapshot
+ return self.get_object('CreateVolume', params, Volume)
+
+ def delete_volume(self, volume_id):
+ """
+ Delete an EBS volume.
+
+ :type volume_id: str
+ :param volume_id: The ID of the volume to be delete.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'VolumeId': volume_id}
+ return self.get_status('DeleteVolume', params)
+
+ def attach_volume(self, volume_id, instance_id, device):
+ """
+ Attach an EBS volume to an EC2 instance.
+
+ :type volume_id: str
+ :param volume_id: The ID of the EBS volume to be attached.
+
+ :type instance_id: str
+ :param instance_id: The ID of the EC2 instance to which it will
+ be attached.
+
+ :type device: str
+ :param device: The device on the instance through which the
+ volume will be exposted (e.g. /dev/sdh)
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'InstanceId' : instance_id,
+ 'VolumeId' : volume_id,
+ 'Device' : device}
+ return self.get_status('AttachVolume', params)
+
+ def detach_volume(self, volume_id, instance_id=None, device=None, force=False):
+ """
+ Detach an EBS volume from an EC2 instance.
+
+ :type volume_id: str
+ :param volume_id: The ID of the EBS volume to be attached.
+
+ :type instance_id: str
+ :param instance_id: The ID of the EC2 instance from which it will
+ be detached.
+
+ :type device: str
+ :param device: The device on the instance through which the
+ volume is exposted (e.g. /dev/sdh)
+
+ :type force: bool
+ :param force: Forces detachment if the previous detachment attempt did
+ not occur cleanly. This option can lead to data loss or
+ a corrupted file system. Use this option only as a last
+ resort to detach a volume from a failed instance. The
+ instance will not have an opportunity to flush file system
+ caches nor file system meta data. If you use this option,
+ you must perform file system check and repair procedures.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'VolumeId' : volume_id}
+ if instance_id:
+ params['InstanceId'] = instance_id
+ if device:
+ params['Device'] = device
+ if force:
+ params['Force'] = 'true'
+ return self.get_status('DetachVolume', params)
+
+ # Snapshot methods
+
+ def get_all_snapshots(self, snapshot_ids=None, owner=None, restorable_by=None):
+ """
+ Get all EBS Snapshots associated with the current credentials.
+
+ :type snapshot_ids: list
+ :param snapshot_ids: Optional list of snapshot ids. If this list is present,
+ only the Snapshots associated with these snapshot ids
+ will be returned.
+
+ :type owner: str
+ :param owner: If present, only the snapshots owned by the specified user
+ will be returned. Valid values are:
+ self | amazon | AWS Account ID
+
+ :type restorable_by: str
+ :param restorable_by: If present, only the snapshots that are restorable
+ by the specified account id will be returned.
+
+ :rtype: list of L{boto.ec2.snapshot.Snapshot}
+ :return: The requested Snapshot objects
+ """
+ params = {}
+ if snapshot_ids:
+ self.build_list_params(params, snapshot_ids, 'SnapshotId')
+ if owner:
+ params['Owner'] = owner
+ if restorable_by:
+ params['RestorableBy'] = restorable_by
+ return self.get_list('DescribeSnapshots', params, [('item', Snapshot)])
+
+ def create_snapshot(self, volume_id, description=None):
+ """
+ Create a snapshot of an existing EBS Volume.
+
+ :type volume_id: str
+ :param volume_id: The ID of the volume to be snapshot'ed
+
+ :type description: str
+ :param description: A description of the snapshot. Limited to 255 characters.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'VolumeId' : volume_id}
+ if description:
+ params['Description'] = description[0:255]
+ return self.get_object('CreateSnapshot', params, Snapshot)
+
+ def delete_snapshot(self, snapshot_id):
+ params = {'SnapshotId': snapshot_id}
+ return self.get_status('DeleteSnapshot', params)
+
+ def get_snapshot_attribute(self, snapshot_id, attribute='createVolumePermission'):
+ """
+ Get information about an attribute of a snapshot. Only one attribute can be
+ specified per call.
+
+ :type snapshot_id: str
+ :param snapshot_id: The ID of the snapshot.
+
+ :type attribute: str
+ :param attribute: The requested attribute. Valid values are:
+ createVolumePermission
+
+ :rtype: list of L{boto.ec2.snapshotattribute.SnapshotAttribute}
+ :return: The requested Snapshot attribute
+ """
+ params = {'Attribute' : attribute}
+ if snapshot_id:
+ params['SnapshotId'] = snapshot_id
+ return self.get_object('DescribeSnapshotAttribute', params, SnapshotAttribute)
+
+ def modify_snapshot_attribute(self, snapshot_id, attribute='createVolumePermission',
+ operation='add', user_ids=None, groups=None):
+ """
+ Changes an attribute of an image.
+
+ :type snapshot_id: string
+ :param snapshot_id: The snapshot id you wish to change
+
+ :type attribute: string
+ :param attribute: The attribute you wish to change. Valid values are:
+ createVolumePermission
+
+ :type operation: string
+ :param operation: Either add or remove (this is required for changing
+ snapshot ermissions)
+
+ :type user_ids: list
+ :param user_ids: The Amazon IDs of users to add/remove attributes
+
+ :type groups: list
+ :param groups: The groups to add/remove attributes. The only valid
+ value at this time is 'all'.
+
+ """
+ params = {'SnapshotId' : snapshot_id,
+ 'Attribute' : attribute,
+ 'OperationType' : operation}
+ if user_ids:
+ self.build_list_params(params, user_ids, 'UserId')
+ if groups:
+ self.build_list_params(params, groups, 'UserGroup')
+ return self.get_status('ModifySnapshotAttribute', params)
+
+ def reset_snapshot_attribute(self, snapshot_id, attribute='createVolumePermission'):
+ """
+ Resets an attribute of a snapshot to its default value.
+
+ :type snapshot_id: string
+ :param snapshot_id: ID of the snapshot
+
+ :type attribute: string
+ :param attribute: The attribute to reset
+
+ :rtype: bool
+ :return: Whether the operation succeeded or not
+ """
+ params = {'SnapshotId' : snapshot_id,
+ 'Attribute' : attribute}
+ return self.get_status('ResetSnapshotAttribute', params)
+
+ # Keypair methods
+
+ def get_all_key_pairs(self, keynames=None):
+ """
+ Get all key pairs associated with your account.
+
+ :type keynames: list
+ :param keynames: A list of the names of keypairs to retrieve.
+ If not provided, all key pairs will be returned.
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.keypair.KeyPair`
+ """
+ params = {}
+ if keynames:
+ self.build_list_params(params, keynames, 'KeyName')
+ return self.get_list('DescribeKeyPairs', params, [('item', KeyPair)])
+
+ def get_key_pair(self, keyname):
+ """
+ Convenience method to retrieve a specific keypair (KeyPair).
+
+ :type image_id: string
+ :param image_id: the ID of the Image to retrieve
+
+ :rtype: :class:`boto.ec2.keypair.KeyPair`
+ :return: The KeyPair specified or None if it is not found
+ """
+ try:
+ return self.get_all_key_pairs(keynames=[keyname])[0]
+ except IndexError: # None of those key pairs available
+ return None
+
+ def create_key_pair(self, key_name):
+ """
+ Create a new key pair for your account.
+ This will create the key pair within the region you
+ are currently connected to.
+
+ :type key_name: string
+ :param key_name: The name of the new keypair
+
+ :rtype: :class:`boto.ec2.keypair.KeyPair`
+ :return: The newly created :class:`boto.ec2.keypair.KeyPair`.
+ The material attribute of the new KeyPair object
+ will contain the the unencrypted PEM encoded RSA private key.
+ """
+ params = {'KeyName':key_name}
+ return self.get_object('CreateKeyPair', params, KeyPair)
+
+ def delete_key_pair(self, key_name):
+ """
+ Delete a key pair from your account.
+
+ :type key_name: string
+ :param key_name: The name of the keypair to delete
+ """
+ params = {'KeyName':key_name}
+ return self.get_status('DeleteKeyPair', params)
+
+ # SecurityGroup methods
+
+ def get_all_security_groups(self, groupnames=None):
+ """
+ Get all security groups associated with your account in a region.
+
+ :type groupnames: list
+ :param groupnames: A list of the names of security groups to retrieve.
+ If not provided, all security groups will be returned.
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.securitygroup.SecurityGroup`
+ """
+ params = {}
+ if groupnames:
+ self.build_list_params(params, groupnames, 'GroupName')
+ return self.get_list('DescribeSecurityGroups', params, [('item', SecurityGroup)])
+
+ def create_security_group(self, name, description):
+ """
+ Create a new security group for your account.
+ This will create the security group within the region you
+ are currently connected to.
+
+ :type name: string
+ :param name: The name of the new security group
+
+ :type description: string
+ :param description: The description of the new security group
+
+ :rtype: :class:`boto.ec2.securitygroup.SecurityGroup`
+ :return: The newly created :class:`boto.ec2.keypair.KeyPair`.
+ """
+ params = {'GroupName':name, 'GroupDescription':description}
+ group = self.get_object('CreateSecurityGroup', params, SecurityGroup)
+ group.name = name
+ group.description = description
+ return group
+
+ def delete_security_group(self, name):
+ """
+ Delete a security group from your account.
+
+ :type key_name: string
+ :param key_name: The name of the keypair to delete
+ """
+ params = {'GroupName':name}
+ return self.get_status('DeleteSecurityGroup', params)
+
+ def authorize_security_group(self, group_name, src_security_group_name=None,
+ src_security_group_owner_id=None,
+ ip_protocol=None, from_port=None, to_port=None,
+ cidr_ip=None):
+ """
+ Add a new rule to an existing security group.
+ You need to pass in either src_security_group_name and
+ src_security_group_owner_id OR ip_protocol, from_port, to_port,
+ and cidr_ip. In other words, either you are authorizing another
+ group or you are authorizing some ip-based rule.
+
+ :type group_name: string
+ :param group_name: The name of the security group you are adding
+ the rule to.
+
+ :type src_security_group_name: string
+ :param src_security_group_name: The name of the security group you are
+ granting access to.
+
+ :type src_security_group_owner_id: string
+ :param src_security_group_owner_id: The ID of the owner of the security group you are
+ granting access to.
+
+ :type ip_protocol: string
+ :param ip_protocol: Either tcp | udp | icmp
+
+ :type from_port: int
+ :param from_port: The beginning port number you are enabling
+
+ :type to_port: int
+ :param to_port: The ending port number you are enabling
+
+ :type to_port: string
+ :param to_port: The CIDR block you are providing access to.
+ See http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
+
+ :rtype: bool
+ :return: True if successful.
+ """
+ params = {'GroupName':group_name}
+ if src_security_group_name:
+ params['SourceSecurityGroupName'] = src_security_group_name
+ if src_security_group_owner_id:
+ params['SourceSecurityGroupOwnerId'] = src_security_group_owner_id
+ if ip_protocol:
+ params['IpProtocol'] = ip_protocol
+ if from_port:
+ params['FromPort'] = from_port
+ if to_port:
+ params['ToPort'] = to_port
+ if cidr_ip:
+ params['CidrIp'] = urllib.quote(cidr_ip)
+ return self.get_status('AuthorizeSecurityGroupIngress', params)
+
+ def revoke_security_group(self, group_name, src_security_group_name=None,
+ src_security_group_owner_id=None,
+ ip_protocol=None, from_port=None, to_port=None,
+ cidr_ip=None):
+ """
+ Remove an existing rule from an existing security group.
+ You need to pass in either src_security_group_name and
+ src_security_group_owner_id OR ip_protocol, from_port, to_port,
+ and cidr_ip. In other words, either you are revoking another
+ group or you are revoking some ip-based rule.
+
+ :type group_name: string
+ :param group_name: The name of the security group you are removing
+ the rule from.
+
+ :type src_security_group_name: string
+ :param src_security_group_name: The name of the security group you are
+ revoking access to.
+
+ :type src_security_group_owner_id: string
+ :param src_security_group_owner_id: The ID of the owner of the security group you are
+ revoking access to.
+
+ :type ip_protocol: string
+ :param ip_protocol: Either tcp | udp | icmp
+
+ :type from_port: int
+ :param from_port: The beginning port number you are disabling
+
+ :type to_port: int
+ :param to_port: The ending port number you are disabling
+
+ :type to_port: string
+ :param to_port: The CIDR block you are revoking access to.
+ See http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
+
+ :rtype: bool
+ :return: True if successful.
+ """
+ params = {'GroupName':group_name}
+ if src_security_group_name:
+ params['SourceSecurityGroupName'] = src_security_group_name
+ if src_security_group_owner_id:
+ params['SourceSecurityGroupOwnerId'] = src_security_group_owner_id
+ if ip_protocol:
+ params['IpProtocol'] = ip_protocol
+ if from_port:
+ params['FromPort'] = from_port
+ if to_port:
+ params['ToPort'] = to_port
+ if cidr_ip:
+ params['CidrIp'] = cidr_ip
+ return self.get_status('RevokeSecurityGroupIngress', params)
+
+ #
+ # Regions
+ #
+
+ def get_all_regions(self):
+ """
+ Get all available regions for the EC2 service.
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.regioninfo.RegionInfo`
+ """
+ return self.get_list('DescribeRegions', None, [('item', RegionInfo)])
+
+ #
+ # Reservation methods
+ #
+
+ def get_all_reserved_instances_offerings(self, reserved_instances_id=None,
+ instance_type=None,
+ availability_zone=None,
+ product_description=None):
+ """
+ Describes Reserved Instance offerings that are available for purchase.
+
+ :type reserved_instances_id: str
+ :param reserved_instances_id: Displays Reserved Instances with the specified offering IDs.
+
+ :type instance_type: str
+ :param instance_type: Displays Reserved Instances of the specified instance type.
+
+ :type availability_zone: str
+ :param availability_zone: Displays Reserved Instances within the specified Availability Zone.
+
+ :type product_description: str
+ :param product_description: Displays Reserved Instances with the specified product description.
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.reservedinstance.ReservedInstancesOffering`
+ """
+ params = {}
+ if reserved_instances_id:
+ params['ReservedInstancesId'] = reserved_instances_id
+ if instance_type:
+ params['InstanceType'] = instance_type
+ if availability_zone:
+ params['AvailabilityZone'] = availability_zone
+ if product_description:
+ params['ProductDescription'] = product_description
+
+ return self.get_list('DescribeReservedInstancesOfferings',
+ params, [('item', ReservedInstancesOffering)])
+
+ def get_all_reserved_instances(self, reserved_instances_id=None):
+ """
+ Describes Reserved Instance offerings that are available for purchase.
+
+ :type reserved_instance_ids: list
+ :param reserved_instance_ids: A list of the reserved instance ids that will be returned.
+ If not provided, all reserved instances will be returned.
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.reservedinstance.ReservedInstance`
+ """
+ params = {}
+ if reserved_instances_id:
+ self.build_list_params(params, reserved_instances_id, 'ReservedInstancesId')
+ return self.get_list('DescribeReservedInstances',
+ params, [('item', ReservedInstance)])
+
+ def purchase_reserved_instance_offering(self, reserved_instances_offering_id,
+ instance_count=1):
+ """
+ Purchase a Reserved Instance for use with your account.
+ ** CAUTION **
+ This request can result in large amounts of money being charged to your
+ AWS account. Use with caution!
+
+ :type reserved_instances_offering_id: string
+ :param reserved_instances_offering_id: The offering ID of the Reserved
+ Instance to purchase
+
+ :type instance_count: int
+ :param instance_count: The number of Reserved Instances to purchase.
+ Default value is 1.
+
+ :rtype: :class:`boto.ec2.reservedinstance.ReservedInstance`
+ :return: The newly created Reserved Instance
+ """
+ params = {'ReservedInstancesOfferingId' : reserved_instances_offering_id,
+ 'InstanceCount' : instance_count}
+ return self.get_object('PurchaseReservedInstancesOffering', params, ReservedInstance)
+
+ #
+ # Monitoring
+ #
+
+ def monitor_instance(self, instance_id):
+ """
+ Enable CloudWatch monitoring for the supplied instance.
+
+ :type instance_id: string
+ :param instance_id: The instance id
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.instanceinfo.InstanceInfo`
+ """
+ params = {'InstanceId' : instance_id}
+ return self.get_list('MonitorInstances', params, [('item', InstanceInfo)])
+
+ def unmonitor_instance(self, instance_id):
+ """
+ Disable CloudWatch monitoring for the supplied instance.
+
+ :type instance_id: string
+ :param instance_id: The instance id
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.instanceinfo.InstanceInfo`
+ """
+ params = {'InstanceId' : instance_id}
+ return self.get_list('UnmonitorInstances', params, [('item', InstanceInfo)])
+
+ #
+ # Bundle Windows Instances
+ #
+
+ def bundle_instance(self, instance_id,
+ s3_bucket,
+ s3_prefix,
+ s3_upload_policy):
+ """
+ Bundle Windows instance.
+
+ :type instance_id: string
+ :param instance_id: The instance id
+
+ :type s3_bucket: string
+ :param s3_bucket: The bucket in which the AMI should be stored.
+
+ :type s3_prefix: string
+ :param s3_prefix: The beginning of the file name for the AMI.
+
+ :type s3_upload_policy: string
+ :param s3_upload_policy: Base64 encoded policy that specifies condition and permissions
+ for Amazon EC2 to upload the user's image into Amazon S3.
+ """
+
+ params = {'InstanceId' : instance_id,
+ 'Storage.S3.Bucket' : s3_bucket,
+ 'Storage.S3.Prefix' : s3_prefix,
+ 'Storage.S3.UploadPolicy' : s3_upload_policy}
+ params['Storage.S3.AWSAccessKeyId'] = self.aws_access_key_id
+ local_hmac = self.hmac.copy()
+ local_hmac.update(s3_upload_policy)
+ s3_upload_policy_signature = base64.b64encode(local_hmac.digest())
+ params['Storage.S3.UploadPolicySignature'] = s3_upload_policy_signature
+ return self.get_object('BundleInstance', params, BundleInstanceTask)
+
+ def get_all_bundle_tasks(self, bundle_ids=None):
+ """
+ Retrieve current bundling tasks. If no bundle id is specified, all tasks are retrieved.
+
+ :type bundle_ids: list
+ :param bundle_ids: A list of strings containing identifiers for
+ previously created bundling tasks.
+ """
+
+ params = {}
+ if bundle_ids:
+ self.build_list_params(params, bundle_ids, 'BundleId')
+ return self.get_list('DescribeBundleTasks', params, [('item', BundleInstanceTask)])
+
+ def cancel_bundle_task(self, bundle_id):
+ """
+ Cancel a previously submitted bundle task
+
+ :type bundle_id: string
+ :param bundle_id: The identifier of the bundle task to cancel.
+ """
+
+ params = {'BundleId' : bundle_id}
+ return self.get_object('CancelBundleTask', params, BundleInstanceTask)
+
+ def get_password_data(self, instance_id):
+ """
+ Get encrypted administrator password for a Windows instance.
+
+ :type instance_id: string
+ :param instance_id: The identifier of the instance to retrieve the password for.
+ """
+
+ params = {'InstanceId' : instance_id}
+ rs = self.get_object('GetPasswordData', params, ResultSet)
+ return rs.passwordData
+
diff --git a/vendor/boto/boto/ec2/ec2object.py b/vendor/boto/boto/ec2/ec2object.py
new file mode 100644
index 0000000000..9ffab5d995
--- /dev/null
+++ b/vendor/boto/boto/ec2/ec2object.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Object
+"""
+
+class EC2Object(object):
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ if self.connection and hasattr(self.connection, 'region'):
+ self.region = connection.region
+ else:
+ self.region = None
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ setattr(self, name, value)
+
+
diff --git a/vendor/boto/boto/ec2/elb/__init__.py b/vendor/boto/boto/ec2/elb/__init__.py
new file mode 100644
index 0000000000..55e846f081
--- /dev/null
+++ b/vendor/boto/boto/ec2/elb/__init__.py
@@ -0,0 +1,238 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+"""
+This module provides an interface to the Elastic Compute Cloud (EC2)
+load balancing service from AWS.
+"""
+from boto.connection import AWSQueryConnection
+from boto.ec2.instanceinfo import InstanceInfo
+from boto.ec2.elb.loadbalancer import LoadBalancer
+from boto.ec2.elb.instancestate import InstanceState
+from boto.ec2.elb.healthcheck import HealthCheck
+import boto
+
+class ELBConnection(AWSQueryConnection):
+
+ APIVersion = boto.config.get('Boto', 'elb_version', '2009-05-15')
+ Endpoint = boto.config.get('Boto', 'elb_endpoint', 'elasticloadbalancing.amazonaws.com')
+ SignatureVersion = '1'
+ #ResponseError = EC2ResponseError
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=False, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, host=Endpoint, debug=0,
+ https_connection_factory=None, path='/'):
+ """
+ Init method to create a new connection to EC2 Load Balancing Service.
+
+ B{Note:} The host argument is overridden by the host specified in the boto configuration file.
+ """
+ AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_key,
+ is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
+ host, debug, https_connection_factory, path)
+
+ def build_list_params(self, params, items, label):
+ if isinstance(items, str):
+ items = [items]
+ for i in range(1, len(items)+1):
+ params[label % i] = items[i-1]
+
+ def get_all_load_balancers(self, load_balancer_name=None):
+ """
+ Retrieve all load balancers associated with your account.
+
+ :type load_balancer_names: str
+ :param load_balancer_names: An optional filter string to get only one ELB
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.elb.loadbalancer.LoadBalancer`
+ """
+ params = {}
+ if load_balancer_name:
+ #self.build_list_params(params, load_balancer_names, 'LoadBalancerName.%d')
+ params['LoadBalancerName'] = load_balancer_name
+ return self.get_list('DescribeLoadBalancers', params, [('member', LoadBalancer)])
+
+
+ def create_load_balancer(self, name, zones, listeners):
+ """
+ Create a new load balancer for your account.
+
+ :type name: string
+ :param name: The mnemonic name associated with the new load balancer
+
+ :type zones: List of strings
+ :param zones: The names of the availability zone(s) to add.
+
+ :type listeners: List of tuples
+ :param listeners: Each tuple contains three values.
+ (LoadBalancerPortNumber, InstancePortNumber, Protocol)
+ where LoadBalancerPortNumber and InstancePortNumber are
+ integer values between 1 and 65535 and Protocol is a
+ string containing either 'TCP' or 'HTTP'.
+
+ :rtype: :class:`boto.ec2.elb.loadbalancer.LoadBalancer`
+ :return: The newly created :class:`boto.ec2.elb.loadbalancer.LoadBalancer`
+ """
+ params = {'LoadBalancerName' : name}
+ for i in range(0, len(listeners)):
+ params['Listeners.member.%d.LoadBalancerPort' % (i+1)] = listeners[i][0]
+ params['Listeners.member.%d.InstancePort' % (i+1)] = listeners[i][1]
+ params['Listeners.member.%d.Protocol' % (i+1)] = listeners[i][2]
+ self.build_list_params(params, zones, 'AvailabilityZones.member.%d')
+ load_balancer = self.get_object('CreateLoadBalancer', params, LoadBalancer)
+ load_balancer.name = name
+ load_balancer.listeners = listeners
+ load_balancer.availability_zones = zones
+ return load_balancer
+
+ def delete_load_balancer(self, name):
+ """
+ Delete a Load Balancer from your account.
+
+ :type name: string
+ :param name: The name of the Load Balancer to delete
+ """
+ params = {'LoadBalancerName': name}
+ return self.get_status('DeleteLoadBalancer', params)
+
+ def enable_availability_zones(self, load_balancer_name, zones_to_add):
+ """
+ Add availability zones to an existing Load Balancer
+ All zones must be in the same region as the Load Balancer
+ Adding zones that are already registered with the Load Balancer
+ has no effect.
+
+ :type load_balancer_name: string
+ :param load_balancer_name: The name of the Load Balancer
+
+ :type zones: List of strings
+ :param zones: The name of the zone(s) to add.
+
+ :rtype: List of strings
+ :return: An updated list of zones for this Load Balancer.
+
+ """
+ params = {'LoadBalancerName' : load_balancer_name}
+ self.build_list_params(params, zones_to_add, 'AvailabilityZones.member.%d')
+ return self.get_list('EnableAvailabilityZonesForLoadBalancer', params, None)
+
+ def disable_availability_zones(self, load_balancer_name, zones_to_remove):
+ """
+ Remove availability zones from an existing Load Balancer.
+ All zones must be in the same region as the Load Balancer.
+ Removing zones that are not registered with the Load Balancer
+ has no effect.
+ You cannot remove all zones from an Load Balancer.
+
+ :type load_balancer_name: string
+ :param load_balancer_name: The name of the Load Balancer
+
+ :type zones: List of strings
+ :param zones: The name of the zone(s) to remove.
+
+ :rtype: List of strings
+ :return: An updated list of zones for this Load Balancer.
+
+ """
+ params = {'LoadBalancerName' : load_balancer_name}
+ self.build_list_params(params, zones_to_remove, 'AvailabilityZones.member.%d')
+ return self.get_list('DisableAvailabilityZonesForLoadBalancer', params, None)
+
+ def register_instances(self, load_balancer_name, instances):
+ """
+ Add new Instances to an existing Load Balancer.
+
+ :type load_balancer_name: string
+ :param load_balancer_name: The name of the Load Balancer
+
+ :type instances: List of strings
+ :param instances: The instance ID's of the EC2 instances to add.
+
+ :rtype: List of strings
+ :return: An updated list of instances for this Load Balancer.
+
+ """
+ params = {'LoadBalancerName' : load_balancer_name}
+ self.build_list_params(params, instances, 'Instances.member.%d.InstanceId')
+ return self.get_list('RegisterInstancesWithLoadBalancer', params, [('member', InstanceInfo)])
+
+ def deregister_instances(self, load_balancer_name, instances):
+ """
+ Remove Instances from an existing Load Balancer.
+
+ :type load_balancer_name: string
+ :param load_balancer_name: The name of the Load Balancer
+
+ :type instances: List of strings
+ :param instances: The instance ID's of the EC2 instances to remove.
+
+ :rtype: List of strings
+ :return: An updated list of instances for this Load Balancer.
+
+ """
+ params = {'LoadBalancerName' : load_balancer_name}
+ self.build_list_params(params, instances, 'Instances.member.%d.InstanceId')
+ return self.get_list('DeregisterInstancesFromLoadBalancer', params, [('member', InstanceInfo)])
+
+ def describe_instance_health(self, load_balancer_name, instances=None):
+ """
+ Get current state of all Instances registered to an Load Balancer.
+
+ :type load_balancer_name: string
+ :param load_balancer_name: The name of the Load Balancer
+
+ :type instances: List of strings
+ :param instances: The instance ID's of the EC2 instances
+ to return status for. If not provided,
+ the state of all instances will be returned.
+
+ :rtype: List of :class:`boto.ec2.elb.instancestate.InstanceState`
+ :return: list of state info for instances in this Load Balancer.
+
+ """
+ params = {'LoadBalancerName' : load_balancer_name}
+ if instances:
+ self.build_list_params(params, instances, 'instances.member.%d')
+ return self.get_list('DescribeInstanceHealth', params, [('member', InstanceState)])
+
+ def configure_health_check(self, name, health_check):
+ """
+ Define a health check for the EndPoints.
+
+ :type name: string
+ :param name: The mnemonic name associated with the new access point
+
+ :type health_check: :class:`boto.ec2.elb.healthcheck.HealthCheck`
+ :param health_check: A HealthCheck object populated with the desired
+ values.
+
+ :rtype: :class:`boto.ec2.elb.healthcheck.HealthCheck`
+ :return: The updated :class:`boto.ec2.elb.healthcheck.HealthCheck`
+ """
+ params = {'LoadBalancerName' : name,
+ 'HealthCheck.Timeout' : health_check.timeout,
+ 'HealthCheck.Target' : health_check.target,
+ 'HealthCheck.Interval' : health_check.interval,
+ 'HealthCheck.UnhealthyThreshold' : health_check.unhealthy_threshold,
+ 'HealthCheck.HealthyThreshold' : health_check.healthy_threshold}
+ return self.get_object('ConfigureHealthCheck', params, HealthCheck)
diff --git a/vendor/boto/boto/ec2/elb/healthcheck.py b/vendor/boto/boto/ec2/elb/healthcheck.py
new file mode 100644
index 0000000000..5a3edbc639
--- /dev/null
+++ b/vendor/boto/boto/ec2/elb/healthcheck.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class HealthCheck(object):
+ """
+ Represents an EC2 Access Point Health Check
+ """
+
+ def __init__(self, access_point=None, interval=30, target=None,
+ healthy_threshold=3, timeout=5, unhealthy_threshold=5):
+ self.access_point = access_point
+ self.interval = interval
+ self.target = target
+ self.healthy_threshold = healthy_threshold
+ self.timeout = timeout
+ self.unhealthy_threshold = unhealthy_threshold
+
+ def __repr__(self):
+ return 'HealthCheck:%s' % self.target
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Interval':
+ self.interval = int(value)
+ elif name == 'Target':
+ self.target = value
+ elif name == 'HealthyThreshold':
+ self.healthy_threshold = int(value)
+ elif name == 'Timeout':
+ self.timeout = int(value)
+ elif name == 'UnhealthyThreshold':
+ self.unhealthy_threshold = int(value)
+ else:
+ setattr(self, name, value)
+
+ def update(self):
+ if not self.access_point:
+ return
+
+ new_hc = self.connection.configure_health_check(self.access_point,
+ self)
+ self.interval = new_hc.interval
+ self.target = new_hc.target
+ self.healthy_threshold = new_hc.healthy_threshold
+ self.unhealthy_threshold = new_hc.unhealthy_threshold
+ self.timeout = new_hc.timeout
+
+
diff --git a/vendor/boto/boto/ec2/elb/instancestate.py b/vendor/boto/boto/ec2/elb/instancestate.py
new file mode 100644
index 0000000000..4a9b0d479a
--- /dev/null
+++ b/vendor/boto/boto/ec2/elb/instancestate.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class InstanceState(object):
+ """
+ Represents the state of an EC2 Load Balancer Instance
+ """
+
+ def __init__(self, load_balancer=None, description=None,
+ state=None, instance_id=None, reason_code=None):
+ self.load_balancer = load_balancer
+ self.description = description
+ self.state = state
+ self.instance_id = instance_id
+ self.reason_code = reason_code
+
+ def __repr__(self):
+ return 'InstanceState:(%s,%s)' % (self.instance_id, self.state)
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Description':
+ self.description = value
+ elif name == 'State':
+ self.state = value
+ elif name == 'InstanceId':
+ self.instance_id = value
+ elif name == 'ReasonCode':
+ self.reason_code = value
+ else:
+ setattr(self, name, value)
+
+
+
diff --git a/vendor/boto/boto/ec2/elb/listelement.py b/vendor/boto/boto/ec2/elb/listelement.py
new file mode 100644
index 0000000000..5be45992a0
--- /dev/null
+++ b/vendor/boto/boto/ec2/elb/listelement.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class ListElement(list):
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'member':
+ self.append(value)
+
+
diff --git a/vendor/boto/boto/ec2/elb/listener.py b/vendor/boto/boto/ec2/elb/listener.py
new file mode 100644
index 0000000000..ab482c2e76
--- /dev/null
+++ b/vendor/boto/boto/ec2/elb/listener.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Listener(object):
+ """
+ Represents an EC2 Load Balancer Listener tuple
+ """
+
+ def __init__(self, load_balancer=None, load_balancer_port=0,
+ instance_port=0, protocol=''):
+ self.load_balancer = load_balancer
+ self.load_balancer_port = load_balancer_port
+ self.instance_port = instance_port
+ self.protocol = protocol
+
+ def __repr__(self):
+ return "(%d, %d, '%s')" % (self.load_balancer_port, self.instance_port, self.protocol)
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'LoadBalancerPort':
+ self.load_balancer_port = int(value)
+ elif name == 'InstancePort':
+ self.instance_port = int(value)
+ elif name == 'Protocol':
+ self.protocol = value
+ else:
+ setattr(self, name, value)
+
+ def get_tuple(self):
+ return self.load_balancer_port, self.instance_port, self.protocol
+
+ def __getitem__(self, key):
+ if key == 0:
+ return self.load_balancer_port
+ if key == 1:
+ return self.instance_port
+ if key == 2:
+ return self.protocol
+ raise KeyError
+
+
+
+
diff --git a/vendor/boto/boto/ec2/elb/loadbalancer.py b/vendor/boto/boto/ec2/elb/loadbalancer.py
new file mode 100644
index 0000000000..0a90389cf6
--- /dev/null
+++ b/vendor/boto/boto/ec2/elb/loadbalancer.py
@@ -0,0 +1,142 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.ec2.elb.healthcheck import HealthCheck
+from boto.ec2.elb.listener import Listener
+from boto.ec2.elb.listelement import ListElement
+from boto.ec2.instanceinfo import InstanceInfo
+from boto.resultset import ResultSet
+
+class LoadBalancer(object):
+ """
+ Represents an EC2 Load Balancer
+ """
+
+ def __init__(self, connection=None, name=None, endpoints=None):
+ self.connection = connection
+ self.name = name
+ self.listeners = None
+ self.health_check = None
+ self.dns_name = None
+ self.created_time = None
+ self.instances = None
+ self.availability_zones = ListElement()
+
+ def __repr__(self):
+ return 'LoadBalancer:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ if name == 'HealthCheck':
+ self.health_check = HealthCheck(self)
+ return self.health_check
+ elif name == 'Listeners':
+ self.listeners = ResultSet([('member', Listener)])
+ return self.listeners
+ elif name == 'AvailabilityZones':
+ return self.availability_zones
+ elif name == 'Instances':
+ self.instances = ResultSet([('member', InstanceInfo)])
+ return self.instances
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'LoadBalancerName':
+ self.name = value
+ elif name == 'DNSName':
+ self.dns_name = value
+ elif name == 'CreatedTime':
+ self.created_time = value
+ elif name == 'InstanceId':
+ self.instances.append(value)
+ else:
+ setattr(self, name, value)
+
+ def enable_zones(self, zones):
+ """
+ Enable availability zones to this Access Point.
+ All zones must be in the same region as the Access Point.
+
+ :type zones: string or List of strings
+ :param zones: The name of the zone(s) to add.
+
+ """
+ if isinstance(zones, str) or isinstance(zones, unicode):
+ zones = [zones]
+ new_zones = self.connection.enable_availability_zones(self.name, zones)
+ self.availability_zones = new_zones
+
+ def disable_zones(self, zones):
+ """
+ Disable availability zones from this Access Point.
+
+ :type zones: string or List of strings
+ :param zones: The name of the zone(s) to add.
+
+ """
+ if isinstance(zones, str) or isinstance(zones, unicode):
+ zones = [zones]
+ new_zones = self.connection.disable_availability_zones(self.name, zones)
+ self.availability_zones = new_zones
+
+ def register_instances(self, instances):
+ """
+ Add instances to this Load Balancer
+ All instances must be in the same region as the Load Balancer.
+ Adding endpoints that are already registered with the Load Balancer
+ has no effect.
+
+ :type zones: string or List of instance id's
+ :param zones: The name of the endpoint(s) to add.
+
+ """
+ if isinstance(instances, str) or isinstance(instances, unicode):
+ instances = [instances]
+ new_instances = self.connection.register_instances(self.name, instances)
+ self.instances = new_instances
+
+ def deregister_instances(self, instances):
+ """
+ Remove instances from this Load Balancer.
+ Removing instances that are not registered with the Load Balancer
+ has no effect.
+
+ :type zones: string or List of instance id's
+ :param zones: The name of the endpoint(s) to add.
+
+ """
+ if isinstance(instances, str) or isinstance(instances, unicode):
+ instances = [instances]
+ new_instances = self.connection.deregister_instances(self.name, instances)
+ self.instances = new_instances
+
+ def delete(self):
+ """
+ Delete this load balancer
+ """
+ return self.connection.delete_load_balancer(self.name)
+
+ def configure_health_check(self, health_check):
+ self.connection.configure_health_check(self.name, health_check)
+
+ def get_instance_health(self, instances=None):
+ self.connection.describe_instance_health(self.name, instances)
+
diff --git a/vendor/boto/boto/ec2/image.py b/vendor/boto/boto/ec2/image.py
new file mode 100644
index 0000000000..c9b7fec725
--- /dev/null
+++ b/vendor/boto/boto/ec2/image.py
@@ -0,0 +1,250 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.ec2.ec2object import EC2Object
+from boto.ec2.blockdevicemapping import BlockDeviceMapping
+
+class ProductCodes(list):
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'productCode':
+ self.append(value)
+
+class Image(EC2Object):
+ """
+ Represents an EC2 Image
+ """
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.location = None
+ self.state = None
+ self.ownerId = None
+ self.owner_alias = None
+ self.is_public = False
+ self.architecture = None
+ self.platform = None
+ self.type = None
+ self.kernel_id = None
+ self.ramdisk_id = None
+ self.name = None
+ self.description = None
+ self.product_codes = ProductCodes()
+ self.block_device_mapping = None
+ self.root_device_type = None
+ self.root_device_name = None
+
+ def __repr__(self):
+ return 'Image:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ if name == 'blockDeviceMapping':
+ self.block_device_mapping = BlockDeviceMapping()
+ return self.block_device_mapping
+ elif name == 'productCodes':
+ return self.product_codes
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'imageId':
+ self.id = value
+ elif name == 'imageLocation':
+ self.location = value
+ elif name == 'imageState':
+ self.state = value
+ elif name == 'imageOwnerId':
+ self.ownerId = value
+ elif name == 'isPublic':
+ if value == 'false':
+ self.is_public = False
+ elif value == 'true':
+ self.is_public = True
+ else:
+ raise Exception(
+ 'Unexpected value of isPublic %s for image %s'%(
+ value,
+ self.id
+ )
+ )
+ elif name == 'architecture':
+ self.architecture = value
+ elif name == 'imageType':
+ self.type = value
+ elif name == 'kernelId':
+ self.kernel_id = value
+ elif name == 'ramdiskId':
+ self.ramdisk_id = value
+ elif name == 'imageOwnerAlias':
+ self.owner_alias = value
+ elif name == 'platform':
+ self.platform = value
+ elif name == 'name':
+ self.name = value
+ elif name == 'description':
+ self.description = value
+ elif name == 'rootDeviceType':
+ self.root_device_type = value
+ elif name == 'rootDeviceName':
+ self.root_device_name = value
+ else:
+ setattr(self, name, value)
+
+ def run(self, min_count=1, max_count=1, key_name=None,
+ security_groups=None, user_data=None,
+ addressing_type=None, instance_type='m1.small', placement=None,
+ kernel_id=None, ramdisk_id=None,
+ monitoring_enabled=False, subnet_id=None,
+ block_device_map=None):
+ """
+ Runs this instance.
+
+ :type min_count: int
+ :param min_count: The minimum number of instances to start
+
+ :type max_count: int
+ :param max_count: The maximum number of instances to start
+
+ :type key_name: string
+ :param key_name: The keypair to run this instance with.
+
+ :type security_groups:
+ :param security_groups:
+
+ :type user_data:
+ :param user_data:
+
+ :type addressing_type:
+ :param daddressing_type:
+
+ :type instance_type: string
+ :param instance_type: The type of instance to run (m1.small, m1.large, m1.xlarge)
+
+ :type placement:
+ :param placement:
+
+ :type kernel_id: string
+ :param kernel_id: The ID of the kernel with which to launch the instances
+
+ :type ramdisk_id: string
+ :param ramdisk_id: The ID of the RAM disk with which to launch the instances
+
+ :type monitoring_enabled: bool
+ :param monitoring_enabled: Enable CloudWatch monitoring on the instance.
+
+ :type subnet_id: string
+ :param subnet_id: The subnet ID within which to launch the instances for VPC.
+
+ :type block_device_map: :class:`boto.ec2.blockdevicemapping.BlockDeviceMapping`
+ :param block_device_map: A BlockDeviceMapping data structure
+ describing the EBS volumes associated
+ with the Image.
+
+ :rtype: Reservation
+ :return: The :class:`boto.ec2.instance.Reservation` associated with the request for machines
+ """
+ return self.connection.run_instances(self.id, min_count, max_count,
+ key_name, security_groups,
+ user_data, addressing_type,
+ instance_type, placement,
+ kernel_id, ramdisk_id,
+ monitoring_enabled, subnet_id,
+ block_device_map)
+
+ def deregister(self):
+ return self.connection.deregister_image(self.id)
+
+ def get_launch_permissions(self):
+ img_attrs = self.connection.get_image_attribute(self.id,
+ 'launchPermission')
+ return img_attrs.attrs
+
+ def set_launch_permissions(self, user_ids=None, group_names=None):
+ return self.connection.modify_image_attribute(self.id,
+ 'launchPermission',
+ 'add',
+ user_ids,
+ group_names)
+
+ def remove_launch_permissions(self, user_ids=None, group_names=None):
+ return self.connection.modify_image_attribute(self.id,
+ 'launchPermission',
+ 'remove',
+ user_ids,
+ group_names)
+
+ def reset_launch_attributes(self):
+ return self.connection.reset_image_attribute(self.id,
+ 'launchPermission')
+
+ def get_kernel(self):
+ img_attrs =self.connection.get_image_attribute(self.id, 'kernel')
+ return img_attrs.kernel
+
+ def get_ramdisk(self):
+ img_attrs = self.connection.get_image_attribute(self.id, 'ramdisk')
+ return img_attrs.ramdisk
+
+class ImageAttribute:
+
+ def __init__(self, parent=None):
+ self.name = None
+ self.kernel = None
+ self.ramdisk = None
+ self.attrs = {}
+
+ def startElement(self, name, attrs, connection):
+ if name == 'blockDeviceMapping':
+ self.attrs['block_device_mapping'] = BlockDeviceMapping()
+ return self.attrs['block_device_mapping']
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'launchPermission':
+ self.name = 'launch_permission'
+ elif name == 'group':
+ if self.attrs.has_key('groups'):
+ self.attrs['groups'].append(value)
+ else:
+ self.attrs['groups'] = [value]
+ elif name == 'userId':
+ if self.attrs.has_key('user_ids'):
+ self.attrs['user_ids'].append(value)
+ else:
+ self.attrs['user_ids'] = [value]
+ elif name == 'productCode':
+ if self.attrs.has_key('product_codes'):
+ self.attrs['product_codes'].append(value)
+ else:
+ self.attrs['product_codes'] = [value]
+ elif name == 'imageId':
+ self.image_id = value
+ elif name == 'kernel':
+ self.kernel = value
+ elif name == 'ramdisk':
+ self.ramdisk = value
+ else:
+ setattr(self, name, value)
diff --git a/vendor/boto/boto/ec2/instance.py b/vendor/boto/boto/ec2/instance.py
new file mode 100644
index 0000000000..78bf55d5d4
--- /dev/null
+++ b/vendor/boto/boto/ec2/instance.py
@@ -0,0 +1,294 @@
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Instance
+"""
+import boto
+from boto.ec2.ec2object import EC2Object
+from boto.resultset import ResultSet
+from boto.ec2.address import Address
+from boto.ec2.blockdevicemapping import BlockDeviceMapping
+from boto.ec2.image import ProductCodes
+import base64
+
+class Reservation(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.owner_id = None
+ self.groups = []
+ self.instances = []
+
+ def __repr__(self):
+ return 'Reservation:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ if name == 'instancesSet':
+ self.instances = ResultSet([('item', Instance)])
+ return self.instances
+ elif name == 'groupSet':
+ self.groups = ResultSet([('item', Group)])
+ return self.groups
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'reservationId':
+ self.id = value
+ elif name == 'ownerId':
+ self.owner_id = value
+ else:
+ setattr(self, name, value)
+
+ def stop_all(self):
+ for instance in self.instances:
+ instance.stop()
+
+class Instance(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.dns_name = None
+ self.public_dns_name = None
+ self.private_dns_name = None
+ self.state = None
+ self.state_code = None
+ self.key_name = None
+ self.shutdown_state = None
+ self.previous_state = None
+ self.instance_type = None
+ self.instance_class = None
+ self.launch_time = None
+ self.image_id = None
+ self.placement = None
+ self.kernel = None
+ self.ramdisk = None
+ self.product_codes = ProductCodes()
+ self.ami_launch_index = None
+ self.monitored = False
+ self.instance_class = None
+ self.spot_instance_request_id = None
+ self.subnet_id = None
+ self.vpc_id = None
+ self.private_ip_address = None
+ self.ip_address = None
+ self.requester_id = None
+ self._in_monitoring_element = False
+ self.persistent = False
+ self.root_device_name = None
+ self.root_device_type = None
+ self.block_device_mapping = None
+ self.state_reason = None
+
+ def __repr__(self):
+ return 'Instance:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ if name == 'monitoring':
+ self._in_monitoring_element = True
+ elif name == 'blockDeviceMapping':
+ self.block_device_mapping = BlockDeviceMapping()
+ return self.block_device_mapping
+ elif name == 'productCodes':
+ return self.product_codes
+ elif name == 'stateReason':
+ self.state_reason = StateReason()
+ return self.state_reason
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'instanceId':
+ self.id = value
+ elif name == 'imageId':
+ self.image_id = value
+ elif name == 'dnsName' or name == 'publicDnsName':
+ self.dns_name = value # backwards compatibility
+ self.public_dns_name = value
+ elif name == 'privateDnsName':
+ self.private_dns_name = value
+ elif name == 'keyName':
+ self.key_name = value
+ elif name == 'amiLaunchIndex':
+ self.ami_launch_index = value
+ elif name == 'shutdownState':
+ self.shutdown_state = value
+ elif name == 'previousState':
+ self.previous_state = value
+ elif name == 'name':
+ self.state = value
+ elif name == 'code':
+ try:
+ self.state_code = int(value)
+ except ValueError:
+ boto.log.warning('Error converting code (%s) to int' % value)
+ self.state_code = value
+ elif name == 'instanceType':
+ self.instance_type = value
+ elif name == 'instanceClass':
+ self.instance_class = value
+ elif name == 'rootDeviceName':
+ self.root_device_name = value
+ elif name == 'rootDeviceType':
+ self.root_device_type = value
+ elif name == 'launchTime':
+ self.launch_time = value
+ elif name == 'availabilityZone':
+ self.placement = value
+ elif name == 'placement':
+ pass
+ elif name == 'kernelId':
+ self.kernel = value
+ elif name == 'ramdiskId':
+ self.ramdisk = value
+ elif name == 'state':
+ if self._in_monitoring_element:
+ if value == 'enabled':
+ self.monitored = True
+ self._in_monitoring_element = False
+ elif name == 'instanceClass':
+ self.instance_class = value
+ elif name == 'spotInstanceRequestId':
+ self.spot_instance_request_id = value
+ elif name == 'subnetId':
+ self.subnet_id = value
+ elif name == 'vpcId':
+ self.vpc_id = value
+ elif name == 'privateIpAddress':
+ self.private_ip_address = value
+ elif name == 'ipAddress':
+ self.ip_address = value
+ elif name == 'requesterId':
+ self.requester_id = value
+ elif name == 'persistent':
+ if value == 'true':
+ self.persistent = True
+ else:
+ self.persistent = False
+ else:
+ setattr(self, name, value)
+
+ def _update(self, updated):
+ self.__dict__.update(updated.__dict__)
+
+ def update(self):
+ rs = self.connection.get_all_instances([self.id])
+ if len(rs) > 0:
+ r = rs[0]
+ for i in r.instances:
+ if i.id == self.id:
+ self._update(i)
+ return self.state
+
+ def terminate(self):
+ rs = self.connection.terminate_instances([self.id])
+ self._update(rs[0])
+
+ def stop(self):
+ rs = self.connection.stop_instances([self.id])
+ self._update(rs[0])
+
+ def start(self):
+ rs = self.connection.start_instances([self.id])
+ self._update(rs[0])
+
+ def reboot(self):
+ return self.connection.reboot_instances([self.id])
+
+ def get_console_output(self):
+ return self.connection.get_console_output(self.id)
+
+ def confirm_product(self, product_code):
+ return self.connection.confirm_product_instance(self.id, product_code)
+
+ def use_ip(self, ip_address):
+ if isinstance(ip_address, Address):
+ ip_address = ip_address.public_ip
+ return self.connection.associate_address(self.id, ip_address)
+
+ def monitor(self):
+ return self.connection.monitor_instance(self.id)
+
+ def unmonitor(self):
+ return self.connection.unmonitor_instance(self.id)
+
+class Group:
+
+ def __init__(self, parent=None):
+ self.id = None
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'groupId':
+ self.id = value
+ else:
+ setattr(self, name, value)
+
+class ConsoleOutput:
+
+ def __init__(self, parent=None):
+ self.parent = parent
+ self.instance_id = None
+ self.timestamp = None
+ self.comment = None
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'instanceId':
+ self.instance_id = value
+ elif name == 'output':
+ self.output = base64.b64decode(value)
+ else:
+ setattr(self, name, value)
+
+class InstanceAttribute(dict):
+
+ def __init__(self, parent=None):
+ dict.__init__(self)
+ self._current_value = None
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'value':
+ self._current_value = value
+ else:
+ self[name] = self._current_value
+
+class StateReason(dict):
+
+ def __init__(self, parent=None):
+ dict.__init__(self)
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name != 'stateReason':
+ self[name] = value
+
diff --git a/vendor/boto/boto/ec2/instanceinfo.py b/vendor/boto/boto/ec2/instanceinfo.py
new file mode 100644
index 0000000000..6efbaed3bc
--- /dev/null
+++ b/vendor/boto/boto/ec2/instanceinfo.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class InstanceInfo(object):
+ """
+ Represents an EC2 Instance status response from CloudWatch
+ """
+
+ def __init__(self, connection=None, id=None, state=None):
+ self.connection = connection
+ self.id = id
+ self.state = state
+
+ def __repr__(self):
+ return 'InstanceInfo:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'instanceId' or name == 'InstanceId':
+ self.id = value
+ elif name == 'state':
+ self.state = value
+ else:
+ setattr(self, name, value)
+
+
+
diff --git a/vendor/boto/boto/ec2/keypair.py b/vendor/boto/boto/ec2/keypair.py
new file mode 100644
index 0000000000..d08e5ce3b4
--- /dev/null
+++ b/vendor/boto/boto/ec2/keypair.py
@@ -0,0 +1,111 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Keypair
+"""
+
+import os
+from boto.ec2.ec2object import EC2Object
+from boto.exception import BotoClientError
+
+class KeyPair(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.name = None
+ self.fingerprint = None
+ self.material = None
+
+ def __repr__(self):
+ return 'KeyPair:%s' % self.name
+
+ def endElement(self, name, value, connection):
+ if name == 'keyName':
+ self.name = value
+ elif name == 'keyFingerprint':
+ self.fingerprint = value
+ elif name == 'keyMaterial':
+ self.material = value
+ else:
+ setattr(self, name, value)
+
+ def delete(self):
+ """
+ Delete the KeyPair.
+
+ :rtype: bool
+ :return: True if successful, otherwise False.
+ """
+ return self.connection.delete_key_pair(self.name)
+
+ def save(self, directory_path):
+ """
+ Save the material (the unencrypted PEM encoded RSA private key)
+ of a newly created KeyPair to a local file.
+
+ :type directory_path: string
+ :param directory_path: The fully qualified path to the directory
+ in which the keypair will be saved. The
+ keypair file will be named using the name
+ of the keypair as the base name and .pem
+ for the file extension. If a file of that
+ name already exists in the directory, an
+ exception will be raised and the old file
+ will not be overwritten.
+
+ :rtype: bool
+ :return: True if successful.
+ """
+ if self.material:
+ file_path = os.path.join(directory_path, '%s.pem' % self.name)
+ if os.path.exists(file_path):
+ raise BotoClientError('%s already exists, it will not be overwritten' % file_path)
+ fp = open(file_path, 'wb')
+ fp.write(self.material)
+ fp.close()
+ return True
+ else:
+ raise BotoClientError('KeyPair contains no material')
+
+ def copy_to_region(self, region):
+ """
+ Create a new key pair of the same new in another region.
+ Note that the new key pair will use a different ssh
+ cert than the this key pair. After doing the copy,
+ you will need to save the material associated with the
+ new key pair (use the save method) to a local file.
+
+ :type region: :class:`boto.ec2.regioninfo.RegionInfo`
+ :param region: The region to which this security group will be copied.
+
+ :rtype: :class:`boto.ec2.keypair.KeyPair`
+ :return: The new key pair
+ """
+ if region.name == self.region:
+ raise BotoClientError('Unable to copy to the same Region')
+ conn_params = self.connection.get_params()
+ rconn = region.connect(**conn_params)
+ kp = rconn.create_key_pair(self.name)
+ return kp
+
+
+
diff --git a/vendor/boto/boto/ec2/launchspecification.py b/vendor/boto/boto/ec2/launchspecification.py
new file mode 100644
index 0000000000..a574a3825e
--- /dev/null
+++ b/vendor/boto/boto/ec2/launchspecification.py
@@ -0,0 +1,96 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a launch specification for Spot instances.
+"""
+
+from boto.ec2.ec2object import EC2Object
+from boto.resultset import ResultSet
+from boto.ec2.blockdevicemapping import BlockDeviceMapping
+from boto.ec2.instance import Group
+
+class GroupList(list):
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'groupId':
+ self.append(value)
+
+class LaunchSpecification(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.key_name = None
+ self.instance_type = None
+ self.image_id = None
+ self.groups = []
+ self.placement = None
+ self.kernel = None
+ self.ramdisk = None
+ self.monitored = False
+ self.subnet_id = None
+ self._in_monitoring_element = False
+ self.block_device_mapping = None
+
+ def __repr__(self):
+ return 'LaunchSpecification(%s)' % self.image_id
+
+ def startElement(self, name, attrs, connection):
+ if name == 'groupSet':
+ self.groups = ResultSet([('item', Group)])
+ return self.groups
+ elif name == 'monitoring':
+ self._in_monitoring_element = True
+ elif name == 'blockDeviceMapping':
+ self.block_device_mapping = BlockDeviceMapping()
+ return self.block_device_mapping
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'imageId':
+ self.image_id = value
+ elif name == 'keyName':
+ self.key_name = value
+ elif name == 'instanceType':
+ self.instance_type = value
+ elif name == 'availabilityZone':
+ self.placement = value
+ elif name == 'placement':
+ pass
+ elif name == 'kernelId':
+ self.kernel = value
+ elif name == 'ramdiskId':
+ self.ramdisk = value
+ elif name == 'subnetId':
+ self.subnet_id = value
+ elif name == 'state':
+ if self._in_monitoring_element:
+ if value == 'enabled':
+ self.monitored = True
+ self._in_monitoring_element = False
+ else:
+ setattr(self, name, value)
+
+
diff --git a/vendor/boto/boto/ec2/regioninfo.py b/vendor/boto/boto/ec2/regioninfo.py
new file mode 100644
index 0000000000..ab61703f37
--- /dev/null
+++ b/vendor/boto/boto/ec2/regioninfo.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class RegionInfo(object):
+ """
+ Represents an EC2 Region
+ """
+
+ def __init__(self, connection=None, name=None, endpoint=None):
+ self.connection = connection
+ self.name = name
+ self.endpoint = endpoint
+
+ def __repr__(self):
+ return 'RegionInfo:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'regionName':
+ self.name = value
+ elif name == 'regionEndpoint':
+ self.endpoint = value
+ else:
+ setattr(self, name, value)
+
+ def connect(self, **kw_params):
+ """
+ Connect to this Region's endpoint. Returns an EC2Connection
+ object pointing to the endpoint associated with this region.
+ You may pass any of the arguments accepted by the EC2Connection
+ object's constructor as keyword arguments and they will be
+ passed along to the EC2Connection object.
+
+ :rtype: :class:`boto.ec2.connection.EC2Connection`
+ :return: The connection to this regions endpoint
+ """
+ from boto.ec2.connection import EC2Connection
+ return EC2Connection(region=self, **kw_params)
+
+
diff --git a/vendor/boto/boto/ec2/reservedinstance.py b/vendor/boto/boto/ec2/reservedinstance.py
new file mode 100644
index 0000000000..1d35c1df06
--- /dev/null
+++ b/vendor/boto/boto/ec2/reservedinstance.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.ec2.ec2object import EC2Object
+
+class ReservedInstancesOffering(EC2Object):
+
+ def __init__(self, connection=None, id=None, instance_type=None,
+ availability_zone=None, duration=None, fixed_price=None,
+ usage_price=None, description=None):
+ EC2Object.__init__(self, connection)
+ self.id = id
+ self.instance_type = instance_type
+ self.availability_zone = availability_zone
+ self.duration = duration
+ self.fixed_price = fixed_price
+ self.usage_price = usage_price
+ self.description = description
+
+ def __repr__(self):
+ return 'ReservedInstanceOffering:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'reservedInstancesOfferingId':
+ self.id = value
+ elif name == 'instanceType':
+ self.instance_type = value
+ elif name == 'availabilityZone':
+ self.availability_zone = value
+ elif name == 'duration':
+ self.duration = value
+ elif name == 'fixedPrice':
+ self.fixed_price = value
+ elif name == 'usagePrice':
+ self.usage_price = value
+ elif name == 'productDescription':
+ self.description = value
+ else:
+ setattr(self, name, value)
+
+ def describe(self):
+ print 'ID=%s' % self.id
+ print '\tInstance Type=%s' % self.instance_type
+ print '\tZone=%s' % self.availability_zone
+ print '\tDuration=%s' % self.duration
+ print '\tFixed Price=%s' % self.fixed_price
+ print '\tUsage Price=%s' % self.usage_price
+ print '\tDescription=%s' % self.description
+
+ def purchase(self, instance_count=1):
+ return self.connection.purchase_reserved_instance_offering(self.id, instance_count)
+
+class ReservedInstance(ReservedInstancesOffering):
+
+ def __init__(self, connection=None, id=None, instance_type=None,
+ availability_zone=None, duration=None, fixed_price=None,
+ usage_price=None, description=None,
+ instance_count=None, state=None):
+ ReservedInstancesOffering.__init__(self, connection, id, instance_type,
+ availability_zone, duration, fixed_price,
+ usage_price, description)
+ self.instance_count = instance_count
+ self.state = state
+
+ def __repr__(self):
+ return 'ReservedInstance:%s' % self.id
+
+ def endElement(self, name, value, connection):
+ if name == 'reservedInstancesId':
+ self.id = value
+ if name == 'instanceCount':
+ self.instance_count = int(value)
+ elif name == 'state':
+ self.state = value
+ else:
+ ReservedInstancesOffering.endElement(self, name, value, connection)
diff --git a/vendor/boto/boto/ec2/securitygroup.py b/vendor/boto/boto/ec2/securitygroup.py
new file mode 100644
index 0000000000..61b0a00fdf
--- /dev/null
+++ b/vendor/boto/boto/ec2/securitygroup.py
@@ -0,0 +1,282 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Security Group
+"""
+from boto.ec2.ec2object import EC2Object
+from boto.exception import BotoClientError
+
+class SecurityGroup(EC2Object):
+
+ def __init__(self, connection=None, owner_id=None,
+ name=None, description=None):
+ EC2Object.__init__(self, connection)
+ self.owner_id = owner_id
+ self.name = name
+ self.description = description
+ self.rules = []
+
+ def __repr__(self):
+ return 'SecurityGroup:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ if name == 'item':
+ self.rules.append(IPPermissions(self))
+ return self.rules[-1]
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'ownerId':
+ self.owner_id = value
+ elif name == 'groupName':
+ self.name = value
+ elif name == 'groupDescription':
+ self.description = value
+ elif name == 'ipRanges':
+ pass
+ elif name == 'return':
+ if value == 'false':
+ self.status = False
+ elif value == 'true':
+ self.status = True
+ else:
+ raise Exception(
+ 'Unexpected value of status %s for group %s'%(
+ value,
+ self.name
+ )
+ )
+ else:
+ setattr(self, name, value)
+
+ def delete(self):
+ return self.connection.delete_security_group(self.name)
+
+ def add_rule(self, ip_protocol, from_port, to_port,
+ src_group_name, src_group_owner_id, cidr_ip):
+ rule = IPPermissions(self)
+ rule.ip_protocol = ip_protocol
+ rule.from_port = from_port
+ rule.to_port = to_port
+ self.rules.append(rule)
+ rule.add_grant(src_group_name, src_group_owner_id, cidr_ip)
+
+ def remove_rule(self, ip_protocol, from_port, to_port,
+ src_group_name, src_group_owner_id, cidr_ip):
+ target_rule = None
+ for rule in self.rules:
+ if rule.ip_protocol == ip_protocol:
+ if rule.from_port == from_port:
+ if rule.to_port == to_port:
+ target_rule = rule
+ target_grant = None
+ for grant in rule.grants:
+ if grant.name == src_group_name:
+ if grant.owner_id == src_group_owner_id:
+ if grant.cidr_ip == cidr_ip:
+ target_grant = grant
+ if target_grant:
+ rule.grants.remove(target_grant)
+ if len(rule.grants) == 0:
+ self.rules.remove(target_rule)
+
+ def authorize(self, ip_protocol=None, from_port=None, to_port=None,
+ cidr_ip=None, src_group=None):
+ """
+ Add a new rule to this security group.
+ You need to pass in either src_group_name
+ OR ip_protocol, from_port, to_port,
+ and cidr_ip. In other words, either you are authorizing another
+ group or you are authorizing some ip-based rule.
+
+ :type ip_protocol: string
+ :param ip_protocol: Either tcp | udp | icmp
+
+ :type from_port: int
+ :param from_port: The beginning port number you are enabling
+
+ :type to_port: int
+ :param to_port: The ending port number you are enabling
+
+ :type to_port: string
+ :param to_port: The CIDR block you are providing access to.
+ See http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
+
+ :type src_group: :class:`boto.ec2.securitygroup.SecurityGroup` or
+ :class:`boto.ec2.securitygroup.GroupOrCIDR`
+
+ :rtype: bool
+ :return: True if successful.
+ """
+ if src_group:
+ from_port = None
+ to_port = None
+ cidr_ip = None
+ ip_protocol = None
+ src_group_name = src_group.name
+ src_group_owner_id = src_group.owner_id
+ else:
+ src_group_name = None
+ src_group_owner_id = None
+ status = self.connection.authorize_security_group(self.name,
+ src_group_name,
+ src_group_owner_id,
+ ip_protocol,
+ from_port,
+ to_port,
+ cidr_ip)
+ if status:
+ self.add_rule(ip_protocol, from_port, to_port, src_group_name,
+ src_group_owner_id, cidr_ip)
+ return status
+
+ def revoke(self, ip_protocol=None, from_port=None, to_port=None,
+ cidr_ip=None, src_group=None):
+ if src_group:
+ from_port=None
+ to_port=None
+ cidr_ip=None
+ ip_protocol = None
+ src_group_name = src_group.name
+ src_group_owner_id = src_group.owner_id
+ else:
+ src_group_name = None
+ src_group_owner_id = None
+ status = self.connection.revoke_security_group(self.name,
+ src_group_name,
+ src_group_owner_id,
+ ip_protocol,
+ from_port,
+ to_port,
+ cidr_ip)
+ if status:
+ self.remove_rule(ip_protocol, from_port, to_port, src_group_name,
+ src_group_owner_id, cidr_ip)
+ return status
+
+ def copy_to_region(self, region, name=None):
+ """
+ Create a copy of this security group in another region.
+ Note that the new security group will be a separate entity
+ and will not stay in sync automatically after the copy
+ operation.
+
+ :type region: :class:`boto.ec2.regioninfo.RegionInfo`
+ :param region: The region to which this security group will be copied.
+
+ :type name: string
+ :param name: The name of the copy. If not supplied, the copy
+ will have the same name as this security group.
+
+ :rtype: :class:`boto.ec2.securitygroup.SecurityGroup`
+ :return: The new security group.
+ """
+ if region.name == self.region:
+ raise BotoClientError('Unable to copy to the same Region')
+ conn_params = self.connection.get_params()
+ rconn = region.connect(**conn_params)
+ sg = rconn.create_security_group(name or self.name, self.description)
+ source_groups = []
+ for rule in self.rules:
+ grant = rule.grants[0]
+ if grant.name:
+ if grant.name not in source_groups:
+ source_groups.append(grant.name)
+ sg.authorize(None, None, None, None, grant)
+ else:
+ sg.authorize(rule.ip_protocol, rule.from_port, rule.to_port,
+ grant.cidr_ip)
+ return sg
+
+ def instances(self):
+ instances = []
+ rs = self.connection.get_all_instances()
+ for reservation in rs:
+ uses_group = [g.id for g in reservation.groups if g.id == self.name]
+ if uses_group:
+ instances.extend(reservation.instances)
+ return instances
+
+class IPPermissions:
+
+ def __init__(self, parent=None):
+ self.parent = parent
+ self.ip_protocol = None
+ self.from_port = None
+ self.to_port = None
+ self.grants = []
+
+ def __repr__(self):
+ return 'IPPermissions:%s(%s-%s)' % (self.ip_protocol,
+ self.from_port, self.to_port)
+
+ def startElement(self, name, attrs, connection):
+ if name == 'item':
+ self.grants.append(GroupOrCIDR(self))
+ return self.grants[-1]
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'ipProtocol':
+ self.ip_protocol = value
+ elif name == 'fromPort':
+ self.from_port = value
+ elif name == 'toPort':
+ self.to_port = value
+ else:
+ setattr(self, name, value)
+
+ def add_grant(self, name=None, owner_id=None, cidr_ip=None):
+ grant = GroupOrCIDR(self)
+ grant.owner_id = owner_id
+ grant.name = name
+ grant.cidr_ip = cidr_ip
+ self.grants.append(grant)
+ return grant
+
+class GroupOrCIDR:
+
+ def __init__(self, parent=None):
+ self.owner_id = None
+ self.name = None
+ self.cidr_ip = None
+
+ def __repr__(self):
+ if self.cidr_ip:
+ return '%s' % self.cidr_ip
+ else:
+ return '%s-%s' % (self.name, self.owner_id)
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'userId':
+ self.owner_id = value
+ elif name == 'groupName':
+ self.name = value
+ if name == 'cidrIp':
+ self.cidr_ip = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/ec2/snapshot.py b/vendor/boto/boto/ec2/snapshot.py
new file mode 100644
index 0000000000..3d4398ed39
--- /dev/null
+++ b/vendor/boto/boto/ec2/snapshot.py
@@ -0,0 +1,127 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Elastic IP Snapshot
+"""
+from boto.ec2.ec2object import EC2Object
+
+class Snapshot(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.volume_id = None
+ self.status = None
+ self.progress = None
+ self.start_time = None
+ self.owner_id = None
+ self.volume_size = None
+ self.description = None
+
+ def __repr__(self):
+ return 'Snapshot:%s' % self.id
+
+ def endElement(self, name, value, connection):
+ if name == 'snapshotId':
+ self.id = value
+ elif name == 'volumeId':
+ self.volume_id = value
+ elif name == 'status':
+ self.status = value
+ elif name == 'startTime':
+ self.start_time = value
+ elif name == 'ownerId':
+ self.owner_id = value
+ elif name == 'volumeSize':
+ try:
+ self.volume_size = int(value)
+ except:
+ self.volume_size = value
+ elif name == 'description':
+ self.description = value
+ else:
+ setattr(self, name, value)
+
+ def _update(self, updated):
+ self.progress = updated.progress
+ self.status = updated.status
+
+ def update(self):
+ rs = self.connection.get_all_snapshots([self.id])
+ if len(rs) > 0:
+ self._update(rs[0])
+ return self.progress
+
+ def delete(self):
+ return self.connection.delete_snapshot(self.id)
+
+ def get_permissions(self):
+ attrs = self.connection.get_snapshot_attribute(self.id,
+ attribute='createVolumePermission')
+ return attrs.attrs
+
+ def share(self, user_ids=None, groups=None):
+ return self.connection.modify_snapshot_attribute(self.id,
+ 'createVolumePermission',
+ 'add',
+ user_ids,
+ groups)
+
+ def unshare(self, user_ids=None, groups=None):
+ return self.connection.modify_snapshot_attribute(self.id,
+ 'createVolumePermission',
+ 'remove',
+ user_ids,
+ groups)
+
+ def reset_permissions(self):
+ return self.connection.reset_snapshot_attribute(self.id, 'createVolumePermission')
+
+class SnapshotAttribute:
+
+ def __init__(self, parent=None):
+ self.snapshot_id = None
+ self.attrs = {}
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'createVolumePermission':
+ self.name = 'create_volume_permission'
+ elif name == 'group':
+ if self.attrs.has_key('groups'):
+ self.attrs['groups'].append(value)
+ else:
+ self.attrs['groups'] = [value]
+ elif name == 'userId':
+ if self.attrs.has_key('user_ids'):
+ self.attrs['user_ids'].append(value)
+ else:
+ self.attrs['user_ids'] = [value]
+ elif name == 'snapshotId':
+ self.snapshot_id = value
+ else:
+ setattr(self, name, value)
+
+
+
diff --git a/vendor/boto/boto/ec2/spotdatafeedsubscription.py b/vendor/boto/boto/ec2/spotdatafeedsubscription.py
new file mode 100644
index 0000000000..9b820a3e09
--- /dev/null
+++ b/vendor/boto/boto/ec2/spotdatafeedsubscription.py
@@ -0,0 +1,63 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Spot Instance Datafeed Subscription
+"""
+from boto.ec2.ec2object import EC2Object
+from boto.ec2.spotinstancerequest import SpotInstanceStateFault
+
+class SpotDatafeedSubscription(EC2Object):
+
+ def __init__(self, connection=None, owner_id=None,
+ bucket=None, prefix=None, state=None,fault=None):
+ EC2Object.__init__(self, connection)
+ self.owner_id = owner_id
+ self.bucket = bucket
+ self.prefix = prefix
+ self.state = state
+ self.fault = fault
+
+ def __repr__(self):
+ return 'SpotDatafeedSubscription:%s' % self.bucket
+
+ def startElement(self, name, attrs, connection):
+ if name == 'fault':
+ self.fault = SpotInstanceStateFault()
+ return self.fault
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'ownerId':
+ self.owner_id = value
+ elif name == 'bucket':
+ self.bucket = value
+ elif name == 'prefix':
+ self.prefix = value
+ elif name == 'state':
+ self.state = value
+ else:
+ setattr(self, name, value)
+
+ def delete(self):
+ return self.connection.delete_spot_datafeed_subscription()
+
diff --git a/vendor/boto/boto/ec2/spotinstancerequest.py b/vendor/boto/boto/ec2/spotinstancerequest.py
new file mode 100644
index 0000000000..3014c7a683
--- /dev/null
+++ b/vendor/boto/boto/ec2/spotinstancerequest.py
@@ -0,0 +1,109 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Spot Instance Request
+"""
+
+from boto.ec2.ec2object import EC2Object
+from boto.ec2.launchspecification import LaunchSpecification
+
+class SpotInstanceStateFault(object):
+
+ def __init__(self, code=None, message=None):
+ self.code = code
+ self.message = message
+
+ def __repr__(self):
+ return '(%s, %s)' % (self.code, self.message)
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'code':
+ self.code = value
+ elif name == 'message':
+ self.message = value
+ setattr(self, name, value)
+
+class SpotInstanceRequest(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.price = None
+ self.type = None
+ self.state = None
+ self.fault = None
+ self.valid_from = None
+ self.valid_until = None
+ self.launch_group = None
+ self.product_description = None
+ self.availability_zone_group = None
+ self.create_time = None
+ self.launch_specification = None
+ self.instance_id = None
+
+ def __repr__(self):
+ return 'SpotInstanceRequest:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ if name == 'launchSpecification':
+ self.launch_specification = LaunchSpecification(connection)
+ return self.launch_specification
+ elif name == 'fault':
+ self.fault = SpotInstanceStateFault()
+ return self.fault
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'spotInstanceRequestId':
+ self.id = value
+ elif name == 'spotPrice':
+ self.price = float(value)
+ elif name == 'type':
+ self.type = value
+ elif name == 'state':
+ self.state = value
+ elif name == 'productDescription':
+ self.product_description = value
+ elif name == 'validFrom':
+ self.valid_from = value
+ elif name == 'validUntil':
+ self.valid_until = value
+ elif name == 'launchGroup':
+ self.launch_group = value
+ elif name == 'availabilityZoneGroup':
+ self.availability_zone_group = value
+ elif name == 'createTime':
+ self.create_time = value
+ elif name == 'instanceId':
+ self.instance_id = value
+ else:
+ setattr(self, name, value)
+
+ def cancel(self):
+ self.connection.cancel_spot_instance_requests([self.id])
+
+
+
diff --git a/vendor/boto/boto/ec2/spotpricehistory.py b/vendor/boto/boto/ec2/spotpricehistory.py
new file mode 100644
index 0000000000..d4e171102b
--- /dev/null
+++ b/vendor/boto/boto/ec2/spotpricehistory.py
@@ -0,0 +1,52 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Spot Instance Request
+"""
+
+from boto.ec2.ec2object import EC2Object
+
+class SpotPriceHistory(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.price = 0.0
+ self.instance_type = None
+ self.product_description = None
+ self.timestamp = None
+
+ def __repr__(self):
+ return 'SpotPriceHistory(%s):%2f' % (self.instance_type, self.price)
+
+ def endElement(self, name, value, connection):
+ if name == 'instanceType':
+ self.instance_type = value
+ elif name == 'spotPrice':
+ self.price = float(value)
+ elif name == 'productDescription':
+ self.product_description = value
+ elif name == 'timestamp':
+ self.timestamp = value
+ else:
+ setattr(self, name, value)
+
+
diff --git a/vendor/boto/boto/ec2/volume.py b/vendor/boto/boto/ec2/volume.py
new file mode 100644
index 0000000000..b07f83b994
--- /dev/null
+++ b/vendor/boto/boto/ec2/volume.py
@@ -0,0 +1,208 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Elastic Block Storage Volume
+"""
+from boto.ec2.ec2object import EC2Object
+
+class Volume(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.create_time = None
+ self.status = None
+ self.size = None
+ self.snapshot_id = None
+ self.attach_data = None
+ self.zone = None
+
+ def __repr__(self):
+ return 'Volume:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ if name == 'attachmentSet':
+ self.attach_data = AttachmentSet()
+ return self.attach_data
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'volumeId':
+ self.id = value
+ elif name == 'createTime':
+ self.create_time = value
+ elif name == 'status':
+ if value != '':
+ self.status = value
+ elif name == 'size':
+ self.size = int(value)
+ elif name == 'snapshotId':
+ self.snapshot_id = value
+ elif name == 'availabilityZone':
+ self.zone = value
+ else:
+ setattr(self, name, value)
+
+ def _update(self, updated):
+ self.__dict__.update(updated.__dict__)
+
+ def update(self):
+ rs = self.connection.get_all_volumes([self.id])
+ if len(rs) > 0:
+ self._update(rs[0])
+ return self.status
+
+ def delete(self):
+ """
+ Delete this EBS volume.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ return self.connection.delete_volume(self.id)
+
+ def attach(self, instance_id, device):
+ """
+ Attach this EBS volume to an EC2 instance.
+
+ :type instance_id: str
+ :param instance_id: The ID of the EC2 instance to which it will
+ be attached.
+
+ :type device: str
+ :param device: The device on the instance through which the
+ volume will be exposted (e.g. /dev/sdh)
+
+ :rtype: bool
+ :return: True if successful
+ """
+ return self.connection.attach_volume(self.id, instance_id, device)
+
+ def detach(self, force=False):
+ """
+ Detach this EBS volume from an EC2 instance.
+
+ :type force: bool
+ :param force: Forces detachment if the previous detachment attempt did
+ not occur cleanly. This option can lead to data loss or
+ a corrupted file system. Use this option only as a last
+ resort to detach a volume from a failed instance. The
+ instance will not have an opportunity to flush file system
+ caches nor file system meta data. If you use this option,
+ you must perform file system check and repair procedures.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ instance_id = None
+ if self.attach_data:
+ instance_id = self.attach_data.instance_id
+ device = None
+ if self.attach_data:
+ device = self.attach_data.device
+ return self.connection.detach_volume(self.id, instance_id, device, force)
+
+ def create_snapshot(self, description=None):
+ """
+ Create a snapshot of this EBS Volume.
+
+ :type description: str
+ :param description: A description of the snapshot. Limited to 256 characters.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ return self.connection.create_snapshot(self.id, description)
+
+ def volume_state(self):
+ """
+ Returns the state of the volume. Same value as the status attribute.
+ """
+ return self.status
+
+ def attachment_state(self):
+ """
+ Get the attachment state.
+ """
+ state = None
+ if self.attach_data:
+ state = self.attach_data.status
+ return state
+
+ def snapshots(self, owner=None, restorable_by=None):
+ """
+ Get all snapshots related to this volume. Note that this requires
+ that all available snapshots for the account be retrieved from EC2
+ first and then the list is filtered client-side to contain only
+ those for this volume.
+
+ :type owner: str
+ :param owner: If present, only the snapshots owned by the specified user
+ will be returned. Valid values are:
+ self | amazon | AWS Account ID
+
+ :type restorable_by: str
+ :param restorable_by: If present, only the snapshots that are restorable
+ by the specified account id will be returned.
+
+ :rtype: list of L{boto.ec2.snapshot.Snapshot}
+ :return: The requested Snapshot objects
+
+ """
+ rs = self.connection.get_all_snapshots(owner=owner,
+ restorable_by=restorable_by)
+ mine = []
+ for snap in rs:
+ if snap.volume_id == self.id:
+ mine.append(snap)
+ return mine
+
+class AttachmentSet(object):
+
+ def __init__(self):
+ self.id = None
+ self.instance_id = None
+ self.status = None
+ self.attach_time = None
+ self.device = None
+
+ def __repr__(self):
+ return 'AttachmentSet:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'volumeId':
+ self.id = value
+ elif name == 'instanceId':
+ self.instance_id = value
+ elif name == 'status':
+ self.status = value
+ elif name == 'attachTime':
+ self.attach_time = value
+ elif name == 'device':
+ self.device = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/ec2/zone.py b/vendor/boto/boto/ec2/zone.py
new file mode 100644
index 0000000000..aec79b2c40
--- /dev/null
+++ b/vendor/boto/boto/ec2/zone.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an EC2 Availability Zone
+"""
+from boto.ec2.ec2object import EC2Object
+
+class Zone(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.name = None
+ self.state = None
+
+ def __repr__(self):
+ return 'Zone:%s' % self.name
+
+ def endElement(self, name, value, connection):
+ if name == 'zoneName':
+ self.name = value
+ elif name == 'zoneState':
+ self.state = value
+ else:
+ setattr(self, name, value)
+
+
+
+
diff --git a/vendor/boto/boto/emr/__init__.py b/vendor/boto/boto/emr/__init__.py
new file mode 100644
index 0000000000..970b4b80f0
--- /dev/null
+++ b/vendor/boto/boto/emr/__init__.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2010 Spotify AB
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+This module provies an interface to the Elastic MapReduce (EMR)
+service from AWS.
+"""
+from connection import EmrConnection
+from step import Step, StreamingStep, JarStep
+
+
diff --git a/vendor/boto/boto/emr/connection.py b/vendor/boto/boto/emr/connection.py
new file mode 100644
index 0000000000..c6d454a256
--- /dev/null
+++ b/vendor/boto/boto/emr/connection.py
@@ -0,0 +1,236 @@
+# Copyright (c) 2010 Spotify AB
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a connection to the EMR service
+"""
+import types
+
+import boto
+from boto.ec2.regioninfo import RegionInfo
+from boto.emr.jobflow import JobFlow, RunJobFlowResponse
+from boto.emr.step import JarStep
+from boto.connection import AWSQueryConnection
+from boto.exception import EmrResponseError
+
+class EmrConnection(AWSQueryConnection):
+
+ APIVersion = boto.config.get('Boto', 'emr_version', '2009-03-31')
+ DefaultRegionName = boto.config.get('Boto', 'emr_region_name', 'us-east-1')
+ DefaultRegionEndpoint = boto.config.get('Boto', 'emr_region_endpoint',
+ 'elasticmapreduce.amazonaws.com')
+ ResponseError = EmrResponseError
+
+ # Constants for AWS Console debugging
+ DebuggingJar = 's3n://us-east-1.elasticmapreduce/libs/script-runner/script-runner.jar'
+ DebuggingArgs = 's3n://us-east-1.elasticmapreduce/libs/state-pusher/0.1/fetch'
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, host=None, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, debug=0,
+ https_connection_factory=None, region=None, path='/'):
+ if not region:
+ region = RegionInfo(self, self.DefaultRegionName, self.DefaultRegionEndpoint)
+ self.region = region
+ AWSQueryConnection.__init__(self, aws_access_key_id,
+ aws_secret_access_key,
+ is_secure, port, proxy, proxy_port,
+ proxy_user, proxy_pass,
+ self.region.endpoint, debug,
+ https_connection_factory, path)
+
+ def describe_jobflow(self, jobflow_id):
+ """
+ Describes a single Elastic MapReduce job flow
+
+ :type jobflow_id: str
+ :param jobflow_id: The job flow id of interest
+ """
+ jobflows = self.describe_jobflows(jobflow_ids=[jobflow_id])
+ if jobflows:
+ return jobflows[0]
+
+ def describe_jobflows(self, states=None, jobflow_ids=None,
+ created_after=None, created_before=None):
+ """
+ Retrieve all the Elastic MapReduce job flows on your account
+
+ :type states: list
+ :param states: A list of strings with job flow states wanted
+
+ :type jobflow_ids: list
+ :param jobflow_ids: A list of job flow IDs
+ :type created_after: datetime
+ :param created_after: Bound on job flow creation time
+
+ :type created_before: datetime
+ :param created_before: Bound on job flow creation time
+ """
+ params = {}
+
+ if states:
+ self.build_list_params(params, states, 'JobFlowStates.member')
+ if jobflow_ids:
+ self.build_list_params(params, jobflow_ids, 'JobFlowIds.member')
+ if created_after:
+ params['CreatedAfter'] = created_after.strftime('%Y-%m-%dT%H:%M:%S')
+ if created_before:
+ params['CreatedBefore'] = created_before.strftime('%Y-%m-%dT%H:%M:%S')
+
+ return self.get_list('DescribeJobFlows', params, [('member', JobFlow)])
+
+ def terminate_jobflows(self, jobflow_ids):
+ """
+ Terminate an Elastic MapReduce job flow
+
+ :type jobflow_ids: list
+ :param jobflow_ids: A list of job flow IDs
+ """
+ params = {}
+ self.build_list_params(params, jobflow_ids, 'JobFlowIds.member')
+ return self.get_status('TerminateJobFlows', params)
+
+ def add_jobflow_steps(self, jobflow_id, steps):
+ """
+ Adds steps to a jobflow
+
+ :type jobflow_id: str
+ :param jobflow_id: The job flow id
+ :type steps: list(boto.emr.Step)
+ :param steps: A list of steps to add to the job
+ """
+ if type(steps) != types.ListType:
+ steps = [steps]
+ params = {}
+ params['JobFlowId'] = jobflow_id
+
+ # Step args
+ step_args = [self._build_step_args(step) for step in steps]
+ params.update(self._build_step_list(step_args))
+
+ return self.get_object('AddJobFlowSteps', params, RunJobFlowResponse)
+
+ def run_jobflow(self, name, log_uri, ec2_keyname=None, availability_zone=None,
+ master_instance_type='m1.small',
+ slave_instance_type='m1.small', num_instances=1,
+ action_on_failure='TERMINATE_JOB_FLOW', keep_alive=False,
+ enable_debugging=False,
+ steps=[]):
+ """
+ Runs a job flow
+
+ :type name: str
+ :param name: Name of the job flow
+ :type log_uri: str
+ :param log_uri: URI of the S3 bucket to place logs
+ :type ec2_keyname: str
+ :param ec2_keyname: EC2 key used for the instances
+ :type availability_zone: str
+ :param availability_zone: EC2 availability zone of the cluster
+ :type master_instance_type: str
+ :param master_instance_type: EC2 instance type of the master
+ :type slave_instance_type: str
+ :param slave_instance_type: EC2 instance type of the slave nodes
+ :type num_instances: int
+ :param num_instances: Number of instances in the Hadoop cluster
+ :type action_on_failure: str
+ :param action_on_failure: Action to take if a step terminates
+ :type keep_alive: bool
+ :param keep_alive: Denotes whether the cluster should stay alive upon completion
+ :type enable_debugging: bool
+ :param enable_debugging: Denotes whether AWS console debugging should be enabled.
+ :type steps: list(boto.emr.Step)
+ :param steps: List of steps to add with the job
+
+ :rtype: str
+ :return: The jobflow id
+ """
+ params = {}
+ if action_on_failure:
+ params['ActionOnFailure'] = action_on_failure
+ params['Name'] = name
+ params['LogUri'] = log_uri
+
+ # Instance args
+ instance_params = self._build_instance_args(ec2_keyname, availability_zone,
+ master_instance_type, slave_instance_type,
+ num_instances, keep_alive)
+ params.update(instance_params)
+
+ # Debugging step from EMR API docs
+ if enable_debugging:
+ debugging_step = JarStep(name='Setup Hadoop Debugging',
+ action_on_failure='TERMINATE_JOB_FLOW',
+ main_class=None,
+ jar=self.DebuggingJar,
+ step_args=self.DebuggingArgs)
+ steps.insert(0, debugging_step)
+
+ # Step args
+ if steps:
+ step_args = [self._build_step_args(step) for step in steps]
+ params.update(self._build_step_list(step_args))
+
+ response = self.get_object('RunJobFlow', params, RunJobFlowResponse)
+ return response.jobflowid
+
+ def _build_step_args(self, step):
+ step_params = {}
+ step_params['ActionOnFailure'] = step.action_on_failure
+ step_params['HadoopJarStep.Jar'] = step.jar()
+
+ main_class = step.main_class()
+ if main_class:
+ step_params['HadoopJarStep.MainClass'] = main_class
+
+ args = step.args()
+ if args:
+ self.build_list_params(step_params, args, 'HadoopJarStep.Args.member')
+
+ step_params['Name'] = step.name
+ return step_params
+
+ def _build_step_list(self, steps):
+ if type(steps) != types.ListType:
+ steps = [steps]
+
+ params = {}
+ for i, step in enumerate(steps):
+ for key, value in step.iteritems():
+ params['Steps.memeber.%s.%s' % (i+1, key)] = value
+ return params
+
+ def _build_instance_args(self, ec2_keyname, availability_zone, master_instance_type,
+ slave_instance_type, num_instances, keep_alive):
+ params = {
+ 'Instances.MasterInstanceType' : master_instance_type,
+ 'Instances.SlaveInstanceType' : slave_instance_type,
+ 'Instances.InstanceCount' : num_instances,
+ 'Instances.KeepJobFlowAliveWhenNoSteps' : str(keep_alive).lower()
+ }
+
+ if ec2_keyname:
+ params['Instances.Ec2KeyName'] = ec2_keyname
+ if availability_zone:
+ params['Placement'] = availability_zone
+
+ return params
+
diff --git a/vendor/boto/boto/emr/emrobject.py b/vendor/boto/boto/emr/emrobject.py
new file mode 100644
index 0000000000..4e2bd7cde9
--- /dev/null
+++ b/vendor/boto/boto/emr/emrobject.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2010 Spotify AB
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+
+class EmrObject(object):
+ Fields = set()
+
+ def __init__(self, connection=None):
+ self.connection = connection
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name in self.Fields:
+ setattr(self, name.lower(), value)
diff --git a/vendor/boto/boto/emr/jobflow.py b/vendor/boto/boto/emr/jobflow.py
new file mode 100644
index 0000000000..d2b3ab0170
--- /dev/null
+++ b/vendor/boto/boto/emr/jobflow.py
@@ -0,0 +1,89 @@
+# Copyright (c) 2010 Spotify AB
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.emr.emrobject import EmrObject
+from boto.resultset import ResultSet
+
+class RunJobFlowResponse(EmrObject):
+ Fields = set(['JobFlowId'])
+
+class Arg(EmrObject):
+ def __init__(self, connection=None):
+ self.value = None
+
+ def endElement(self, name, value, connection):
+ self.value = value
+
+
+class Step(EmrObject):
+ Fields = set(['Name',
+ 'ActionOnFailure',
+ 'CreationDateTime',
+ 'StartDateTime',
+ 'EndDateTime',
+ 'LastStateChangeReason',
+ 'State'])
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.args = None
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Args':
+ self.args = ResultSet([('member', Arg)])
+ return self.args
+
+
+class JobFlow(EmrObject):
+ Fields = set(['CreationDateTime',
+ 'StartDateTime',
+ 'State',
+ 'EndDateTime',
+ 'Id',
+ 'InstanceCount',
+ 'JobFlowId',
+ 'KeepJobAliveWhenNoSteps',
+ 'LogURI',
+ 'MasterPublicDnsName',
+ 'MasterInstanceId',
+ 'Name',
+ 'Placement',
+ 'RequestId',
+ 'Type',
+ 'Value',
+ 'AvailabilityZone',
+ 'SlaveInstanceType',
+ 'MasterInstanceType',
+ 'Ec2KeyName',
+ 'InstanceCount',
+ 'KeepJobFlowAliveWhenNoSteps'])
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.steps = None
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Steps':
+ self.steps = ResultSet([('member', Step)])
+ return self.steps
+ else:
+ return None
+
diff --git a/vendor/boto/boto/emr/step.py b/vendor/boto/boto/emr/step.py
new file mode 100644
index 0000000000..b6c73fcbed
--- /dev/null
+++ b/vendor/boto/boto/emr/step.py
@@ -0,0 +1,168 @@
+# Copyright (c) 2010 Spotify AB
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Step(object):
+ """
+ Jobflow Step base class
+ """
+ def jar(self):
+ """
+ :rtype: str
+ :return: URI to the jar
+ """
+ raise NotImplemented()
+
+ def args(self):
+ """
+ :rtype: list(str)
+ :return: List of arguments for the step
+ """
+ raise NotImplemented()
+
+ def main_class(self):
+ """
+ :rtype: str
+ :return: The main class name
+ """
+ raise NotImplemented()
+
+
+class JarStep(Step):
+ """
+ Custom jar step
+ """
+ def __init__(self, name, jar, main_class,
+ action_on_failure='TERMINATE_JOB_FLOW', step_args=None):
+ """
+ A elastic mapreduce step that executes a jar
+
+ :type name: str
+ :param name: The name of the step
+ :type jar: str
+ :param jar: S3 URI to the Jar file
+ :type main_class: str
+ :param main_class: The class to execute in the jar
+ :type action_on_failure: str
+ :param action_on_failure: An action, defined in the EMR docs to take on failure.
+ :type step_args: list(str)
+ :param step_args: A list of arguments to pass to the step
+ """
+ self.name = name
+ self._jar = jar
+ self._main_class = main_class
+ self.action_on_failure = action_on_failure
+
+ if isinstance(step_args, basestring):
+ step_args = [step_args]
+
+ self.step_args = step_args
+
+ def jar(self):
+ return self._jar
+
+ def args(self):
+ args = []
+
+ if self.step_args:
+ args.extend(self.step_args)
+
+ return args
+
+ def main_class(self):
+ return self._main_class
+
+
+class StreamingStep(Step):
+ """
+ Hadoop streaming step
+ """
+ def __init__(self, name, mapper, reducer,
+ action_on_failure='TERMINATE_JOB_FLOW',
+ cache_files=None, cache_archives=None,
+ step_args=None, input=None, output=None):
+ """
+ A hadoop streaming elastic mapreduce step
+
+ :type name: str
+ :param name: The name of the step
+ :type mapper: str
+ :param mapper: The mapper URI
+ :type reducer: str
+ :param reducer: The reducer URI
+ :type action_on_failure: str
+ :param action_on_failure: An action, defined in the EMR docs to take on failure.
+ :type cache_files: list(str)
+ :param cache_files: A list of cache files to be bundled with the job
+ :type cache_archives: list(str)
+ :param cache_archives: A list of jar archives to be bundled with the job
+ :type step_args: list(str)
+ :param step_args: A list of arguments to pass to the step
+ :type input: str
+ :param input: The input uri
+ :type output: str
+ :param output: The output uri
+ """
+ self.name = name
+ self.mapper = mapper
+ self.reducer = reducer
+ self.action_on_failure = action_on_failure
+ self.cache_files = cache_files
+ self.cache_archives = cache_archives
+ self.input = input
+ self.output = output
+
+ if isinstance(step_args, basestring):
+ step_args = [step_args]
+
+ self.step_args = step_args
+
+ def jar(self):
+ return '/home/hadoop/contrib/streaming/hadoop-0.18-streaming.jar'
+
+ def main_class(self):
+ return None
+
+ def args(self):
+ args = ['-mapper', self.mapper,
+ '-reducer', self.reducer]
+
+ if self.input:
+ if isinstance(self.input, list):
+ for input in self.input:
+ args.extend(('-input', input))
+ else:
+ args.extend(('-input', self.input))
+ if self.output:
+ args.extend(('-output', self.output))
+
+ if self.cache_files:
+ for cache_file in self.cache_files:
+ args.extend(('-cacheFile', cache_file))
+
+ if self.cache_archives:
+ for cache_archive in self.cache_archives:
+ args.extend(('-cacheArchive', cache_archive))
+
+ if self.step_args:
+ args.extend(self.step_args)
+
+ return args
+
diff --git a/vendor/boto/boto/exception.py b/vendor/boto/boto/exception.py
new file mode 100644
index 0000000000..503f43d1cb
--- /dev/null
+++ b/vendor/boto/boto/exception.py
@@ -0,0 +1,293 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Exception classes - Subclassing allows you to check for specific errors
+"""
+import base64
+import xml.sax
+from boto import handler
+from boto.resultset import ResultSet
+
+
+class BotoClientError(StandardError):
+ """
+ General Boto Client error (error accessing AWS)
+ """
+
+ def __init__(self, reason):
+ StandardError.__init__(self)
+ self.reason = reason
+
+ def __repr__(self):
+ return 'S3Error: %s' % self.reason
+
+ def __str__(self):
+ return 'S3Error: %s' % self.reason
+
+class SDBPersistenceError(StandardError):
+
+ pass
+
+class S3PermissionsError(BotoClientError):
+ """
+ Permissions error when accessing a bucket or key on S3.
+ """
+ pass
+
+class BotoServerError(StandardError):
+
+ def __init__(self, status, reason, body=None):
+ StandardError.__init__(self)
+ self.status = status
+ self.reason = reason
+ self.body = body or ''
+ self.request_id = None
+ self.error_code = None
+ self.error_message = None
+ self.box_usage = None
+
+ # Attempt to parse the error response. If body isn't present,
+ # then just ignore the error response.
+ if self.body:
+ try:
+ h = handler.XmlHandler(self, self)
+ xml.sax.parseString(self.body, h)
+ except xml.sax.SAXParseException, pe:
+ # Go ahead and clean up anything that may have
+ # managed to get into the error data so we
+ # don't get partial garbage.
+ print "Warning: failed to parse error message from AWS: %s" % pe
+ self._cleanupParsedProperties()
+
+ def __getattr__(self, name):
+ if name == 'message':
+ return self.error_message
+ if name == 'code':
+ return self.error_code
+ raise AttributeError
+
+ def __repr__(self):
+ return '%s: %s %s\n%s' % (self.__class__.__name__,
+ self.status, self.reason, self.body)
+
+ def __str__(self):
+ return '%s: %s %s\n%s' % (self.__class__.__name__,
+ self.status, self.reason, self.body)
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name in ('RequestId', 'RequestID'):
+ self.request_id = value
+ elif name == 'Code':
+ self.error_code = value
+ elif name == 'Message':
+ self.error_message = value
+ elif name == 'BoxUsage':
+ self.box_usage = value
+ return None
+
+ def _cleanupParsedProperties(self):
+ self.request_id = None
+ self.error_code = None
+ self.error_message = None
+ self.box_usage = None
+
+class ConsoleOutput:
+
+ def __init__(self, parent=None):
+ self.parent = parent
+ self.instance_id = None
+ self.timestamp = None
+ self.comment = None
+ self.output = None
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'instanceId':
+ self.instance_id = value
+ elif name == 'output':
+ self.output = base64.b64decode(value)
+ else:
+ setattr(self, name, value)
+
+class S3CreateError(BotoServerError):
+ """
+ Error creating a bucket or key on S3.
+ """
+ def __init__(self, status, reason, body=None):
+ self.bucket = None
+ BotoServerError.__init__(self, status, reason, body)
+
+ def endElement(self, name, value, connection):
+ if name == 'BucketName':
+ self.bucket = value
+ else:
+ return BotoServerError.endElement(self, name, value, connection)
+
+class S3CopyError(BotoServerError):
+ """
+ Error copying a key on S3.
+ """
+ pass
+
+class SQSError(BotoServerError):
+ """
+ General Error on Simple Queue Service.
+ """
+ def __init__(self, status, reason, body=None):
+ self.detail = None
+ self.type = None
+ BotoServerError.__init__(self, status, reason, body)
+
+ def startElement(self, name, attrs, connection):
+ return BotoServerError.startElement(self, name, attrs, connection)
+
+ def endElement(self, name, value, connection):
+ if name == 'Detail':
+ self.detail = value
+ elif name == 'Type':
+ self.type = value
+ else:
+ return BotoServerError.endElement(self, name, value, connection)
+
+ def _cleanupParsedProperties(self):
+ BotoServerError._cleanupParsedProperties(self)
+ for p in ('detail', 'type'):
+ setattr(self, p, None)
+
+class SQSDecodeError(BotoClientError):
+ """
+ Error when decoding an SQS message.
+ """
+ def __init__(self, reason, message):
+ BotoClientError.__init__(self, reason)
+ self.message = message
+
+ def __repr__(self):
+ return 'SQSDecodeError: %s' % self.reason
+
+ def __str__(self):
+ return 'SQSDecodeError: %s' % self.reason
+
+class S3ResponseError(BotoServerError):
+ """
+ Error in response from S3.
+ """
+ def __init__(self, status, reason, body=None):
+ self.resource = None
+ BotoServerError.__init__(self, status, reason, body)
+
+ def startElement(self, name, attrs, connection):
+ return BotoServerError.startElement(self, name, attrs, connection)
+
+ def endElement(self, name, value, connection):
+ if name == 'Resource':
+ self.resource = value
+ else:
+ return BotoServerError.endElement(self, name, value, connection)
+
+ def _cleanupParsedProperties(self):
+ BotoServerError._cleanupParsedProperties(self)
+ for p in ('resource'):
+ setattr(self, p, None)
+
+class EC2ResponseError(BotoServerError):
+ """
+ Error in response from EC2.
+ """
+
+ def __init__(self, status, reason, body=None):
+ self.errors = None
+ self._errorResultSet = []
+ BotoServerError.__init__(self, status, reason, body)
+ self.errors = [ (e.error_code, e.error_message) \
+ for e in self._errorResultSet ]
+ if len(self.errors):
+ self.error_code, self.error_message = self.errors[0]
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Errors':
+ self._errorResultSet = ResultSet([('Error', _EC2Error)])
+ return self._errorResultSet
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'RequestID':
+ self.request_id = value
+ else:
+ return None # don't call subclass here
+
+ def _cleanupParsedProperties(self):
+ BotoServerError._cleanupParsedProperties(self)
+ self._errorResultSet = []
+ for p in ('errors'):
+ setattr(self, p, None)
+
+class EmrResponseError(BotoServerError):
+ """
+ Error in response from EMR
+ """
+ pass
+
+class _EC2Error:
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.error_code = None
+ self.error_message = None
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Code':
+ self.error_code = value
+ elif name == 'Message':
+ self.error_message = value
+ else:
+ return None
+
+class SDBResponseError(BotoServerError):
+ """
+ Error in respones from SDB.
+ """
+ pass
+
+class AWSConnectionError(BotoClientError):
+ """
+ General error connecting to Amazon Web Services.
+ """
+ pass
+
+class S3DataError(BotoClientError):
+ """
+ Error receiving data from S3.
+ """
+ pass
+
+class FPSResponseError(BotoServerError):
+ pass
diff --git a/vendor/boto/boto/fps/__init__.py b/vendor/boto/boto/fps/__init__.py
new file mode 100644
index 0000000000..2f44483df6
--- /dev/null
+++ b/vendor/boto/boto/fps/__init__.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2008, Chris Moyer http://coredumped.org
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+
diff --git a/vendor/boto/boto/fps/connection.py b/vendor/boto/boto/fps/connection.py
new file mode 100644
index 0000000000..0e0d4e8e27
--- /dev/null
+++ b/vendor/boto/boto/fps/connection.py
@@ -0,0 +1,172 @@
+# Copyright (c) 2008 Chris Moyer http://coredumped.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import urllib
+import xml.sax
+import uuid
+import boto
+import boto.utils
+from boto import handler
+from boto.connection import AWSQueryConnection
+from boto.resultset import ResultSet
+from boto.exception import FPSResponseError
+
+class FPSConnection(AWSQueryConnection):
+
+ APIVersion = '2007-01-08'
+ SignatureVersion = '1'
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, port=None, proxy=None, proxy_port=None,
+ host='fps.sandbox.amazonaws.com', debug=0,
+ https_connection_factory=None):
+ AWSQueryConnection.__init__(self, aws_access_key_id,
+ aws_secret_access_key,
+ is_secure, port, proxy, proxy_port,
+ host, debug, https_connection_factory)
+
+ def install_payment_instruction(self, instruction, token_type="Unrestricted", transaction_id=None):
+ """
+ InstallPaymentInstruction
+ instruction: The PaymentInstruction to send, for example:
+
+ MyRole=='Caller' orSay 'Roles do not match';
+
+ token_type: Defaults to "Unrestricted"
+ transaction_id: Defaults to a new ID
+ """
+
+ if(transaction_id == None):
+ transaction_id = uuid.uuid4()
+ params = {}
+ params['PaymentInstruction'] = instruction
+ params['TokenType'] = token_type
+ params['CallerReference'] = transaction_id
+ response = self.make_request("InstallPaymentInstruction", params)
+ return response
+
+ def install_caller_instruction(self, token_type="Unrestricted", transaction_id=None):
+ """
+ Set us up as a caller
+ This will install a new caller_token into the FPS section.
+ This should really only be called to regenerate the caller token.
+ """
+ response = self.install_payment_instruction("MyRole=='Caller';", token_type=token_type, transaction_id=transaction_id)
+ body = response.read()
+ if(response.status == 200):
+ rs = ResultSet()
+ h = handler.XmlHandler(rs, self)
+ xml.sax.parseString(body, h)
+ caller_token = rs.TokenId
+ try:
+ boto.config.save_system_option("FPS", "caller_token", caller_token)
+ except(IOError):
+ boto.config.save_user_option("FPS", "caller_token", caller_token)
+ return caller_token
+ else:
+ raise FPSResponseError(response.status, response.reason, body)
+
+ def install_recipient_instruction(self, token_type="Unrestricted", transaction_id=None):
+ """
+ Set us up as a Recipient
+ This will install a new caller_token into the FPS section.
+ This should really only be called to regenerate the recipient token.
+ """
+ response = self.install_payment_instruction("MyRole=='Recipient';", token_type=token_type, transaction_id=transaction_id)
+ body = response.read()
+ if(response.status == 200):
+ rs = ResultSet()
+ h = handler.XmlHandler(rs, self)
+ xml.sax.parseString(body, h)
+ recipient_token = rs.TokenId
+ try:
+ boto.config.save_system_option("FPS", "recipient_token", recipient_token)
+ except(IOError):
+ boto.config.save_user_option("FPS", "recipient_token", recipient_token)
+
+ return recipient_token
+ else:
+ raise FPSResponseError(response.status, response.reason, body)
+
+ def make_url(self, returnURL, paymentReason, pipelineName, **params):
+ """
+ Generate the URL with the signature required for a transaction
+ """
+ params['callerKey'] = str(self.aws_access_key_id)
+ params['returnURL'] = str(returnURL)
+ params['paymentReason'] = str(paymentReason)
+ params['pipelineName'] = pipelineName
+
+ if(not params.has_key('callerReference')):
+ params['callerReference'] = str(uuid.uuid4())
+
+ url = ""
+ keys = params.keys()
+ keys.sort()
+ for k in keys:
+ url += "&%s=%s" % (k, urllib.quote_plus(str(params[k])))
+
+ url = "/cobranded-ui/actions/start?%s" % ( url[1:])
+ signature= boto.utils.encode(self.aws_secret_access_key, url, True)
+ return "https://authorize.payments-sandbox.amazon.com%s&awsSignature=%s" % (url, signature)
+
+ def make_payment(self, amount, sender_token, charge_fee_to="Recipient", reference=None, senderReference=None, recipientReference=None, senderDescription=None, recipientDescription=None, callerDescription=None, metadata=None, transactionDate=None):
+ """
+ Make a payment transaction
+ You must specify the amount and the sender token.
+ """
+ params = {}
+ params['RecipientTokenId'] = boto.config.get("FPS", "recipient_token")
+ params['CallerTokenId'] = boto.config.get("FPS", "caller_token")
+ params['SenderTokenId'] = sender_token
+ params['TransactionAmount.Amount'] = str(amount)
+ params['TransactionAmount.CurrencyCode'] = "USD"
+ params['ChargeFeeTo'] = charge_fee_to
+
+ if(transactionDate != None):
+ params['TransactionDate'] = transactionDate
+ if(senderReference != None):
+ params['SenderReference'] = senderReference
+ if(recipientReference != None):
+ params['RecipientReference'] = recipientReference
+ if(senderDescription != None):
+ params['SenderDescription'] = senderDescription
+ if(recipientDescription != None):
+ params['RecipientDescription'] = recipientDescription
+ if(callerDescription != None):
+ params['CallerDescription'] = callerDescription
+ if(metadata != None):
+ params['MetaData'] = metadata
+ if(transactionDate != None):
+ params['TransactionDate'] = transactionDate
+ if(reference == None):
+ reference = uuid.uuid4()
+ params['CallerReference'] = reference
+
+ response = self.make_request("Pay", params)
+ body = response.read()
+ if(response.status == 200):
+ rs = ResultSet()
+ h = handler.XmlHandler(rs, self)
+ xml.sax.parseString(body, h)
+ return rs
+ else:
+ raise FPSResponseError(response.status, response.reason, body)
diff --git a/vendor/boto/boto/handler.py b/vendor/boto/boto/handler.py
new file mode 100644
index 0000000000..525f9c9a6c
--- /dev/null
+++ b/vendor/boto/boto/handler.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import xml.sax
+
+class XmlHandler(xml.sax.ContentHandler):
+
+ def __init__(self, root_node, connection):
+ self.connection = connection
+ self.nodes = [('root', root_node)]
+ self.current_text = ''
+
+ def startElement(self, name, attrs):
+ self.current_text = ''
+ new_node = self.nodes[-1][1].startElement(name, attrs, self.connection)
+ if new_node != None:
+ self.nodes.append((name, new_node))
+
+ def endElement(self, name):
+ self.nodes[-1][1].endElement(name, self.current_text, self.connection)
+ if self.nodes[-1][0] == name:
+ self.nodes.pop()
+ self.current_text = ''
+
+ def characters(self, content):
+ self.current_text += content
+
+
diff --git a/vendor/boto/boto/manage/__init__.py b/vendor/boto/boto/manage/__init__.py
new file mode 100644
index 0000000000..49d029ba2c
--- /dev/null
+++ b/vendor/boto/boto/manage/__init__.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+
diff --git a/vendor/boto/boto/manage/cmdshell.py b/vendor/boto/boto/manage/cmdshell.py
new file mode 100644
index 0000000000..5d287ab7c9
--- /dev/null
+++ b/vendor/boto/boto/manage/cmdshell.py
@@ -0,0 +1,169 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.mashups.interactive import interactive_shell
+import boto
+import os
+import time
+import shutil
+import StringIO
+import paramiko
+import socket
+import subprocess
+
+
+class SSHClient(object):
+
+ def __init__(self, server, host_key_file='~/.ssh/known_hosts', uname='root'):
+ self.server = server
+ self.host_key_file = host_key_file
+ self.uname = uname
+ self._pkey = paramiko.RSAKey.from_private_key_file(server.ssh_key_file)
+ self._ssh_client = paramiko.SSHClient()
+ self._ssh_client.load_system_host_keys()
+ self._ssh_client.load_host_keys(os.path.expanduser(host_key_file))
+ self._ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ self.connect()
+
+ def connect(self):
+ retry = 0
+ while retry < 5:
+ try:
+ self._ssh_client.connect(self.server.hostname, username=self.uname, pkey=self._pkey)
+ return
+ except socket.error, (value,message):
+ if value == 61:
+ print 'SSH Connection refused, will retry in 5 seconds'
+ time.sleep(5)
+ retry += 1
+ else:
+ raise
+ except paramiko.BadHostKeyException:
+ print "%s has an entry in ~/.ssh/known_hosts and it doesn't match" % self.server.hostname
+ print 'Edit that file to remove the entry and then hit return to try again'
+ raw_input('Hit Enter when ready')
+ retry += 1
+ except EOFError:
+ print 'Unexpected Error from SSH Connection, retry in 5 seconds'
+ time.sleep(5)
+ retry += 1
+ print 'Could not establish SSH connection'
+
+ def get_file(self, src, dst):
+ sftp_client = self._ssh_client.open_sftp()
+ sftp_client.get(src, dst)
+
+ def put_file(self, src, dst):
+ sftp_client = self._ssh_client.open_sftp()
+ sftp_client.put(src, dst)
+
+ def listdir(self, path):
+ sftp_client = self._ssh_client.open_sftp()
+ return sftp_client.listdir(path)
+
+ def open_sftp(self):
+ return self._ssh_client.open_sftp()
+
+ def isdir(self, path):
+ status = self.run('[ -d %s ] || echo "FALSE"' % path)
+ if status[1].startswith('FALSE'):
+ return 0
+ return 1
+
+ def exists(self, path):
+ status = self.run('[ -a %s ] || echo "FALSE"' % path)
+ if status[1].startswith('FALSE'):
+ return 0
+ return 1
+
+ def shell(self):
+ channel = self._ssh_client.invoke_shell()
+ interactive_shell(channel)
+
+ def run(self, command):
+ boto.log.info('running:%s on %s' % (command, self.server.instance_id))
+ log_fp = StringIO.StringIO()
+ status = 0
+ try:
+ t = self._ssh_client.exec_command(command)
+ except paramiko.SSHException:
+ status = 1
+ log_fp.write(t[1].read())
+ log_fp.write(t[2].read())
+ t[0].close()
+ t[1].close()
+ t[2].close()
+ boto.log.info('output: %s' % log_fp.getvalue())
+ return (status, log_fp.getvalue())
+
+ def close(self):
+ transport = self._ssh_client.get_transport()
+ transport.close()
+ self.server.reset_cmdshell()
+
+class LocalClient(object):
+
+ def __init__(self, server, host_key_file=None, uname='root'):
+ self.server = server
+ self.host_key_file = host_key_file
+ self.uname = uname
+
+ def get_file(self, src, dst):
+ shutil.copyfile(src, dst)
+
+ def put_file(self, src, dst):
+ shutil.copyfile(src, dst)
+
+ def listdir(self, path):
+ return os.listdir(path)
+
+ def isdir(self, path):
+ return os.path.isdir(path)
+
+ def exists(self, path):
+ return os.path.exists(path)
+
+ def shell(self):
+ raise NotImplementedError, 'shell not supported with LocalClient'
+
+ def run(self):
+ boto.log.info('running:%s' % self.command)
+ log_fp = StringIO.StringIO()
+ process = subprocess.Popen(self.command, shell=True, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ while process.poll() == None:
+ time.sleep(1)
+ t = process.communicate()
+ log_fp.write(t[0])
+ log_fp.write(t[1])
+ boto.log.info(log_fp.getvalue())
+ boto.log.info('output: %s' % log_fp.getvalue())
+ return (process.returncode, log_fp.getvalue())
+
+ def close(self):
+ pass
+
+def start(server):
+ instance_id = boto.config.get('Instance', 'instance-id', None)
+ if instance_id == server.instance_id:
+ return LocalClient(server)
+ else:
+ return SSHClient(server)
diff --git a/vendor/boto/boto/manage/propget.py b/vendor/boto/boto/manage/propget.py
new file mode 100644
index 0000000000..45b2ff2221
--- /dev/null
+++ b/vendor/boto/boto/manage/propget.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+
+def get(prop, choices=None):
+ prompt = prop.verbose_name
+ if not prompt:
+ prompt = prop.name
+ if choices:
+ if callable(choices):
+ choices = choices()
+ else:
+ choices = prop.get_choices()
+ valid = False
+ while not valid:
+ if choices:
+ min = 1
+ max = len(choices)
+ for i in range(min, max+1):
+ value = choices[i-1]
+ if isinstance(value, tuple):
+ value = value[0]
+ print '[%d] %s' % (i, value)
+ value = raw_input('%s [%d-%d]: ' % (prompt, min, max))
+ try:
+ int_value = int(value)
+ value = choices[int_value-1]
+ if isinstance(value, tuple):
+ value = value[1]
+ valid = True
+ except ValueError:
+ print '%s is not a valid choice' % value
+ except IndexError:
+ print '%s is not within the range[%d-%d]' % (min, max)
+ else:
+ value = raw_input('%s: ' % prompt)
+ try:
+ value = prop.validate(value)
+ if prop.empty(value) and prop.required:
+ print 'A value is required'
+ else:
+ valid = True
+ except:
+ print 'Invalid value: %s' % value
+ return value
+
diff --git a/vendor/boto/boto/manage/server.py b/vendor/boto/boto/manage/server.py
new file mode 100644
index 0000000000..9a1e280cce
--- /dev/null
+++ b/vendor/boto/boto/manage/server.py
@@ -0,0 +1,548 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010 Chris Moyer http://coredumped.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+High-level abstraction of an EC2 server
+"""
+from __future__ import with_statement
+import boto.ec2
+from boto.mashups.iobject import IObject
+from boto.pyami.config import BotoConfigPath, Config
+from boto.sdb.db.model import Model
+from boto.sdb.db.property import StringProperty, IntegerProperty, BooleanProperty, CalculatedProperty
+from boto.manage import propget
+from boto.ec2.zone import Zone
+from boto.ec2.keypair import KeyPair
+import os, time, StringIO
+from contextlib import closing
+from boto.exception import EC2ResponseError
+
+InstanceTypes = ['m1.small', 'm1.large', 'm1.xlarge',
+ 'c1.medium', 'c1.xlarge',
+ 'm2.2xlarge', 'm2.4xlarge']
+
+class Bundler(object):
+
+ def __init__(self, server, uname='root'):
+ from boto.manage.cmdshell import SSHClient
+ self.server = server
+ self.uname = uname
+ self.ssh_client = SSHClient(server, uname=uname)
+
+ def copy_x509(self, key_file, cert_file):
+ print '\tcopying cert and pk over to /mnt directory on server'
+ self.ssh_client.open_sftp()
+ path, name = os.path.split(key_file)
+ self.remote_key_file = '/mnt/%s' % name
+ self.ssh_client.put_file(key_file, self.remote_key_file)
+ path, name = os.path.split(cert_file)
+ self.remote_cert_file = '/mnt/%s' % name
+ self.ssh_client.put_file(cert_file, self.remote_cert_file)
+ print '...complete!'
+
+ def bundle_image(self, prefix, size, ssh_key):
+ command = ""
+ if self.uname != 'root':
+ command = "sudo "
+ command += 'ec2-bundle-vol '
+ command += '-c %s -k %s ' % (self.remote_cert_file, self.remote_key_file)
+ command += '-u %s ' % self.server._reservation.owner_id
+ command += '-p %s ' % prefix
+ command += '-s %d ' % size
+ command += '-d /mnt '
+ if self.server.instance_type == 'm1.small' or self.server.instance_type == 'c1.medium':
+ command += '-r i386'
+ else:
+ command += '-r x86_64'
+ return command
+
+ def upload_bundle(self, bucket, prefix, ssh_key):
+ command = ""
+ if self.uname != 'root':
+ command = "sudo "
+ command += 'ec2-upload-bundle '
+ command += '-m /mnt/%s.manifest.xml ' % prefix
+ command += '-b %s ' % bucket
+ command += '-a %s ' % self.server.ec2.aws_access_key_id
+ command += '-s %s ' % self.server.ec2.aws_secret_access_key
+ return command
+
+ def bundle(self, bucket=None, prefix=None, key_file=None, cert_file=None,
+ size=None, ssh_key=None, fp=None, clear_history=True):
+ iobject = IObject()
+ if not bucket:
+ bucket = iobject.get_string('Name of S3 bucket')
+ if not prefix:
+ prefix = iobject.get_string('Prefix for AMI file')
+ if not key_file:
+ key_file = iobject.get_filename('Path to RSA private key file')
+ if not cert_file:
+ cert_file = iobject.get_filename('Path to RSA public cert file')
+ if not size:
+ size = iobject.get_int('Size (in MB) of bundled image')
+ if not ssh_key:
+ ssh_key = self.server.get_ssh_key_file()
+ self.copy_x509(key_file, cert_file)
+ if not fp:
+ fp = StringIO.StringIO()
+ fp.write('sudo mv %s /mnt/boto.cfg; ' % BotoConfigPath)
+ fp.write('mv ~/.ssh/authorized_keys /mnt/authorized_keys; ')
+ if clear_history:
+ fp.write('history -c; ')
+ fp.write(self.bundle_image(prefix, size, ssh_key))
+ fp.write('; ')
+ fp.write(self.upload_bundle(bucket, prefix, ssh_key))
+ fp.write('; ')
+ fp.write('sudo mv /mnt/boto.cfg %s; ' % BotoConfigPath)
+ fp.write('mv /mnt/authorized_keys ~/.ssh/authorized_keys')
+ command = fp.getvalue()
+ print 'running the following command on the remote server:'
+ print command
+ t = self.ssh_client.run(command)
+ print '\t%s' % t[0]
+ print '\t%s' % t[1]
+ print '...complete!'
+ print 'registering image...'
+ self.image_id = self.server.ec2.register_image(name=prefix, image_location='%s/%s.manifest.xml' % (bucket, prefix))
+ return self.image_id
+
+class CommandLineGetter(object):
+
+ def get_ami_list(self):
+ my_amis = []
+ for ami in self.ec2.get_all_images():
+ # hack alert, need a better way to do this!
+ if ami.location.find('pyami') >= 0:
+ my_amis.append((ami.location, ami))
+ return my_amis
+
+ def get_region(self, params):
+ region = params.get('region', None)
+ if isinstance(region, str) or isinstance(region, unicode):
+ region = boto.ec2.get_region(region)
+ params['region'] = region
+ if not region:
+ prop = self.cls.find_property('region_name')
+ params['region'] = propget.get(prop, choices=boto.ec2.regions)
+
+ def get_name(self, params):
+ if not params.get('name', None):
+ prop = self.cls.find_property('name')
+ params['name'] = propget.get(prop)
+
+ def get_description(self, params):
+ if not params.get('description', None):
+ prop = self.cls.find_property('description')
+ params['description'] = propget.get(prop)
+
+ def get_instance_type(self, params):
+ if not params.get('instance_type', None):
+ prop = StringProperty(name='instance_type', verbose_name='Instance Type',
+ choices=InstanceTypes)
+ params['instance_type'] = propget.get(prop)
+
+ def get_quantity(self, params):
+ if not params.get('quantity', None):
+ prop = IntegerProperty(name='quantity', verbose_name='Number of Instances')
+ params['quantity'] = propget.get(prop)
+
+ def get_zone(self, params):
+ if not params.get('zone', None):
+ prop = StringProperty(name='zone', verbose_name='EC2 Availability Zone',
+ choices=self.ec2.get_all_zones)
+ params['zone'] = propget.get(prop)
+
+ def get_ami_id(self, params):
+ ami = params.get('ami', None)
+ if isinstance(ami, str) or isinstance(ami, unicode):
+ for a in self.ec2.get_all_images():
+ if a.id == ami:
+ params['ami'] = a
+ if not params.get('ami', None):
+ prop = StringProperty(name='ami', verbose_name='AMI',
+ choices=self.get_ami_list)
+ params['ami'] = propget.get(prop)
+
+ def get_group(self, params):
+ group = params.get('group', None)
+ if isinstance(group, str) or isinstance(group, unicode):
+ group_list = self.ec2.get_all_security_groups()
+ for g in group_list:
+ if g.name == group:
+ group = g
+ params['group'] = g
+ if not group:
+ prop = StringProperty(name='group', verbose_name='EC2 Security Group',
+ choices=self.ec2.get_all_security_groups)
+ params['group'] = propget.get(prop)
+
+ def get_key(self, params):
+ keypair = params.get('keypair', None)
+ if isinstance(keypair, str) or isinstance(keypair, unicode):
+ key_list = self.ec2.get_all_key_pairs()
+ for k in key_list:
+ if k.name == keypair:
+ keypair = k.name
+ params['keypair'] = k.name
+ if not keypair:
+ prop = StringProperty(name='keypair', verbose_name='EC2 KeyPair',
+ choices=self.ec2.get_all_key_pairs)
+ params['keypair'] = propget.get(prop).name
+
+ def get(self, cls, params):
+ self.cls = cls
+ self.get_region(params)
+ self.ec2 = params['region'].connect()
+ self.get_name(params)
+ self.get_description(params)
+ self.get_instance_type(params)
+ self.get_zone(params)
+ self.get_quantity(params)
+ self.get_ami_id(params)
+ self.get_group(params)
+ self.get_key(params)
+
+class Server(Model):
+
+ #
+ # The properties of this object consists of real properties for data that
+ # is not already stored in EC2 somewhere (e.g. name, description) plus
+ # calculated properties for all of the properties that are already in
+ # EC2 (e.g. hostname, security groups, etc.)
+ #
+ name = StringProperty(unique=True, verbose_name="Name")
+ description = StringProperty(verbose_name="Description")
+ region_name = StringProperty(verbose_name="EC2 Region Name")
+ instance_id = StringProperty(verbose_name="EC2 Instance ID")
+ elastic_ip = StringProperty(verbose_name="EC2 Elastic IP Address")
+ production = BooleanProperty(verbose_name="Is This Server Production", default=False)
+ ami_id = CalculatedProperty(verbose_name="AMI ID", calculated_type=str, use_method=True)
+ zone = CalculatedProperty(verbose_name="Availability Zone Name", calculated_type=str, use_method=True)
+ hostname = CalculatedProperty(verbose_name="Public DNS Name", calculated_type=str, use_method=True)
+ private_hostname = CalculatedProperty(verbose_name="Private DNS Name", calculated_type=str, use_method=True)
+ groups = CalculatedProperty(verbose_name="Security Groups", calculated_type=list, use_method=True)
+ security_group = CalculatedProperty(verbose_name="Primary Security Group Name", calculated_type=str, use_method=True)
+ key_name = CalculatedProperty(verbose_name="Key Name", calculated_type=str, use_method=True)
+ instance_type = CalculatedProperty(verbose_name="Instance Type", calculated_type=str, use_method=True)
+ status = CalculatedProperty(verbose_name="Current Status", calculated_type=str, use_method=True)
+ launch_time = CalculatedProperty(verbose_name="Server Launch Time", calculated_type=str, use_method=True)
+ console_output = CalculatedProperty(verbose_name="Console Output", calculated_type=file, use_method=True)
+
+ packages = []
+ plugins = []
+
+ @classmethod
+ def add_credentials(cls, cfg, aws_access_key_id, aws_secret_access_key):
+ if not cfg.has_section('Credentials'):
+ cfg.add_section('Credentials')
+ cfg.set('Credentials', 'aws_access_key_id', aws_access_key_id)
+ cfg.set('Credentials', 'aws_secret_access_key', aws_secret_access_key)
+ if not cfg.has_section('DB_Server'):
+ cfg.add_section('DB_Server')
+ cfg.set('DB_Server', 'db_type', 'SimpleDB')
+ cfg.set('DB_Server', 'db_name', cls._manager.domain.name)
+
+ '''
+ Create a new instance based on the specified configuration file or the specified
+ configuration and the passed in parameters.
+
+ If the config_file argument is not None, the configuration is read from there.
+ Otherwise, the cfg argument is used.
+
+ The config file may include other config files with a #import reference. The included
+ config files must reside in the same directory as the specified file.
+
+ The logical_volume argument, if supplied, will be used to get the current physical
+ volume ID and use that as an override of the value specified in the config file. This
+ may be useful for debugging purposes when you want to debug with a production config
+ file but a test Volume.
+
+ The dictionary argument may be used to override any EC2 configuration values in the
+ config file.
+ '''
+ @classmethod
+ def create(cls, config_file=None, logical_volume = None, cfg = None, **params):
+ if config_file:
+ cfg = Config(path=config_file)
+ if cfg.has_section('EC2'):
+ # include any EC2 configuration values that aren't specified in params:
+ for option in cfg.options('EC2'):
+ if option not in params:
+ params[option] = cfg.get('EC2', option)
+ getter = CommandLineGetter()
+ getter.get(cls, params)
+ region = params.get('region')
+ ec2 = region.connect()
+ cls.add_credentials(cfg, ec2.aws_access_key_id, ec2.aws_secret_access_key)
+ ami = params.get('ami')
+ kp = params.get('keypair')
+ group = params.get('group')
+ zone = params.get('zone')
+ # deal with possibly passed in logical volume:
+ if logical_volume != None:
+ cfg.set('EBS', 'logical_volume_name', logical_volume.name)
+ cfg_fp = StringIO.StringIO()
+ cfg.write(cfg_fp)
+ # deal with the possibility that zone and/or keypair are strings read from the config file:
+ if isinstance(zone, Zone):
+ zone = zone.name
+ if isinstance(kp, KeyPair):
+ kp = kp.name
+ reservation = ami.run(min_count=1,
+ max_count=params.get('quantity', 1),
+ key_name=kp,
+ security_groups=[group],
+ instance_type=params.get('instance_type'),
+ placement = zone,
+ user_data = cfg_fp.getvalue())
+ l = []
+ i = 0
+ elastic_ip = params.get('elastic_ip')
+ instances = reservation.instances
+ if elastic_ip != None and instances.__len__() > 0:
+ instance = instances[0]
+ print 'Waiting for instance to start so we can set its elastic IP address...'
+ while instance.update() != 'running':
+ time.sleep(1)
+ instance.use_ip(elastic_ip)
+ print 'set the elastic IP of the first instance to %s' % elastic_ip
+ for instance in instances:
+ s = cls()
+ s.ec2 = ec2
+ s.name = params.get('name') + '' if i==0 else str(i)
+ s.description = params.get('description')
+ s.region_name = region.name
+ s.instance_id = instance.id
+ if elastic_ip and i == 0:
+ s.elastic_ip = elastic_ip
+ s.put()
+ l.append(s)
+ i += 1
+ return l
+
+ @classmethod
+ def create_from_instance_id(cls, instance_id, name, description=''):
+ regions = boto.ec2.regions()
+ for region in regions:
+ ec2 = region.connect()
+ try:
+ rs = ec2.get_all_instances([instance_id])
+ except:
+ rs = []
+ if len(rs) == 1:
+ s = cls()
+ s.ec2 = ec2
+ s.name = name
+ s.description = description
+ s.region_name = region.name
+ s.instance_id = instance_id
+ s._reservation = rs[0]
+ for instance in s._reservation.instances:
+ if instance.id == instance_id:
+ s._instance = instance
+ s.put()
+ return s
+ return None
+
+ @classmethod
+ def create_from_current_instances(cls):
+ servers = []
+ regions = boto.ec2.regions()
+ for region in regions:
+ ec2 = region.connect()
+ rs = ec2.get_all_instances()
+ for reservation in rs:
+ for instance in reservation.instances:
+ try:
+ Server.find(instance_id=instance.id).next()
+ boto.log.info('Server for %s already exists' % instance.id)
+ except StopIteration:
+ s = cls()
+ s.ec2 = ec2
+ s.name = instance.id
+ s.region_name = region.name
+ s.instance_id = instance.id
+ s._reservation = reservation
+ s.put()
+ servers.append(s)
+ return servers
+
+ def __init__(self, id=None, **kw):
+ Model.__init__(self, id, **kw)
+ self.ssh_key_file = None
+ self.ec2 = None
+ self._cmdshell = None
+ self._reservation = None
+ self._instance = None
+ self._setup_ec2()
+
+ def _setup_ec2(self):
+ if self.ec2 and self._instance and self._reservation:
+ return
+ if self.id:
+ if self.region_name:
+ for region in boto.ec2.regions():
+ if region.name == self.region_name:
+ self.ec2 = region.connect()
+ if self.instance_id and not self._instance:
+ try:
+ rs = self.ec2.get_all_instances([self.instance_id])
+ if len(rs) >= 1:
+ for instance in rs[0].instances:
+ if instance.id == self.instance_id:
+ self._reservation = rs[0]
+ self._instance = instance
+ except EC2ResponseError:
+ pass
+
+ def _status(self):
+ status = ''
+ if self._instance:
+ self._instance.update()
+ status = self._instance.state
+ return status
+
+ def _hostname(self):
+ hostname = ''
+ if self._instance:
+ hostname = self._instance.public_dns_name
+ return hostname
+
+ def _private_hostname(self):
+ hostname = ''
+ if self._instance:
+ hostname = self._instance.private_dns_name
+ return hostname
+
+ def _instance_type(self):
+ it = ''
+ if self._instance:
+ it = self._instance.instance_type
+ return it
+
+ def _launch_time(self):
+ lt = ''
+ if self._instance:
+ lt = self._instance.launch_time
+ return lt
+
+ def _console_output(self):
+ co = ''
+ if self._instance:
+ co = self._instance.get_console_output()
+ return co
+
+ def _groups(self):
+ gn = []
+ if self._reservation:
+ gn = self._reservation.groups
+ return gn
+
+ def _security_group(self):
+ groups = self._groups()
+ if len(groups) >= 1:
+ return groups[0].id
+ return ""
+
+ def _zone(self):
+ zone = None
+ if self._instance:
+ zone = self._instance.placement
+ return zone
+
+ def _key_name(self):
+ kn = None
+ if self._instance:
+ kn = self._instance.key_name
+ return kn
+
+ def put(self):
+ Model.put(self)
+ self._setup_ec2()
+
+ def delete(self):
+ if self.production:
+ raise ValueError, "Can't delete a production server"
+ #self.stop()
+ Model.delete(self)
+
+ def stop(self):
+ if self.production:
+ raise ValueError, "Can't delete a production server"
+ if self._instance:
+ self._instance.stop()
+
+ def terminate(self):
+ if self.production:
+ raise ValueError, "Can't delete a production server"
+ if self._instance:
+ self._instance.terminate()
+
+ def reboot(self):
+ if self._instance:
+ self._instance.reboot()
+
+ def wait(self):
+ while self.status != 'running':
+ time.sleep(5)
+
+ def get_ssh_key_file(self):
+ if not self.ssh_key_file:
+ ssh_dir = os.path.expanduser('~/.ssh')
+ if os.path.isdir(ssh_dir):
+ ssh_file = os.path.join(ssh_dir, '%s.pem' % self.key_name)
+ if os.path.isfile(ssh_file):
+ self.ssh_key_file = ssh_file
+ if not self.ssh_key_file:
+ iobject = IObject()
+ self.ssh_key_file = iobject.get_filename('Path to OpenSSH Key file')
+ return self.ssh_key_file
+
+ def get_cmdshell(self):
+ if not self._cmdshell:
+ import cmdshell
+ self.get_ssh_key_file()
+ self._cmdshell = cmdshell.start(self)
+ return self._cmdshell
+
+ def reset_cmdshell(self):
+ self._cmdshell = None
+
+ def run(self, command):
+ with closing(self.get_cmdshell()) as cmd:
+ status = cmd.run(command)
+ return status
+
+ def get_bundler(self, uname='root'):
+ self.get_ssh_key_file()
+ return Bundler(self, uname)
+
+ def get_ssh_client(self, uname='root'):
+ from boto.manage.cmdshell import SSHClient
+ self.get_ssh_key_file()
+ return SSHClient(self, uname=uname)
+
+ def install(self, pkg):
+ return self.run('apt-get -y install %s' % pkg)
+
+
+
diff --git a/vendor/boto/boto/manage/task.py b/vendor/boto/boto/manage/task.py
new file mode 100644
index 0000000000..2f9d7d0017
--- /dev/null
+++ b/vendor/boto/boto/manage/task.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+import boto
+from boto.sdb.db.property import StringProperty, DateTimeProperty, IntegerProperty
+from boto.sdb.db.model import Model
+import datetime, subprocess, StringIO, time
+
+def check_hour(val):
+ if val == '*':
+ return
+ if int(val) < 0 or int(val) > 23:
+ raise ValueError
+
+class Task(Model):
+
+ """
+ A scheduled, repeating task that can be executed by any participating servers.
+ The scheduling is similar to cron jobs. Each task has an hour attribute.
+ The allowable values for hour are [0-23|*].
+
+ To keep the operation reasonably efficient and not cause excessive polling,
+ the minimum granularity of a Task is hourly. Some examples:
+
+ hour='*' - the task would be executed each hour
+ hour='3' - the task would be executed at 3AM GMT each day.
+
+ """
+ name = StringProperty()
+ hour = StringProperty(required=True, validator=check_hour, default='*')
+ command = StringProperty(required=True)
+ last_executed = DateTimeProperty()
+ last_status = IntegerProperty()
+ last_output = StringProperty()
+ message_id = StringProperty()
+
+ @classmethod
+ def start_all(cls, queue_name):
+ for task in cls.all():
+ task.start(queue_name)
+
+ def __init__(self, id=None, **kw):
+ Model.__init__(self, id, **kw)
+ self.hourly = self.hour == '*'
+ self.daily = self.hour != '*'
+ self.now = datetime.datetime.utcnow()
+
+ def check(self):
+ """
+ Determine how long until the next scheduled time for a Task.
+ Returns the number of seconds until the next scheduled time or zero
+ if the task needs to be run immediately.
+ If it's an hourly task and it's never been run, run it now.
+ If it's a daily task and it's never been run and the hour is right, run it now.
+ """
+ boto.log.info('checking Task[%s]-now=%s, last=%s' % (self.name, self.now, self.last_executed))
+
+ if self.hourly and not self.last_executed:
+ return 0
+
+ if self.daily and not self.last_executed:
+ if int(self.hour) == self.now.hour:
+ return 0
+ else:
+ return max( (int(self.hour)-self.now.hour), (self.now.hour-int(self.hour)) )*60*60
+
+ delta = self.now - self.last_executed
+ if self.hourly:
+ if delta.seconds >= 60*60:
+ return 0
+ else:
+ return 60*60 - delta.seconds
+ else:
+ if int(self.hour) == self.now.hour:
+ if delta.days >= 1:
+ return 0
+ else:
+ return 82800 # 23 hours, just to be safe
+ else:
+ return max( (int(self.hour)-self.now.hour), (self.now.hour-int(self.hour)) )*60*60
+
+ def _run(self, msg, vtimeout):
+ boto.log.info('Task[%s] - running:%s' % (self.name, self.command))
+ log_fp = StringIO.StringIO()
+ process = subprocess.Popen(self.command, shell=True, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ nsecs = 5
+ current_timeout = vtimeout
+ while process.poll() == None:
+ boto.log.info('nsecs=%s, timeout=%s' % (nsecs, current_timeout))
+ if nsecs >= current_timeout:
+ current_timeout += vtimeout
+ boto.log.info('Task[%s] - setting timeout to %d seconds' % (self.name, current_timeout))
+ if msg:
+ msg.change_visibility(current_timeout)
+ time.sleep(5)
+ nsecs += 5
+ t = process.communicate()
+ log_fp.write(t[0])
+ log_fp.write(t[1])
+ boto.log.info('Task[%s] - output: %s' % (self.name, log_fp.getvalue()))
+ self.last_executed = self.now
+ self.last_status = process.returncode
+ self.last_output = log_fp.getvalue()[0:1023]
+
+ def run(self, msg, vtimeout=60):
+ delay = self.check()
+ boto.log.info('Task[%s] - delay=%s seconds' % (self.name, delay))
+ if delay == 0:
+ self._run(msg, vtimeout)
+ queue = msg.queue
+ new_msg = queue.new_message(self.id)
+ new_msg = queue.write(new_msg)
+ self.message_id = new_msg.id
+ self.put()
+ boto.log.info('Task[%s] - new message id=%s' % (self.name, new_msg.id))
+ msg.delete()
+ boto.log.info('Task[%s] - deleted message %s' % (self.name, msg.id))
+ else:
+ boto.log.info('new_vtimeout: %d' % delay)
+ msg.change_visibility(delay)
+
+ def start(self, queue_name):
+ boto.log.info('Task[%s] - starting with queue: %s' % (self.name, queue_name))
+ queue = boto.lookup('sqs', queue_name)
+ msg = queue.new_message(self.id)
+ msg = queue.write(msg)
+ self.message_id = msg.id
+ self.put()
+ boto.log.info('Task[%s] - start successful' % self.name)
+
+class TaskPoller(object):
+
+ def __init__(self, queue_name):
+ self.sqs = boto.connect_sqs()
+ self.queue = self.sqs.lookup(queue_name)
+
+ def poll(self, wait=60, vtimeout=60):
+ while 1:
+ m = self.queue.read(vtimeout)
+ if m:
+ task = Task.get_by_id(m.get_body())
+ if task:
+ if not task.message_id or m.id == task.message_id:
+ boto.log.info('Task[%s] - read message %s' % (task.name, m.id))
+ task.run(m, vtimeout)
+ else:
+ boto.log.info('Task[%s] - found extraneous message, ignoring' % task.name)
+ else:
+ time.sleep(wait)
+
+
+
+
+
+
diff --git a/vendor/boto/boto/manage/test_manage.py b/vendor/boto/boto/manage/test_manage.py
new file mode 100644
index 0000000000..e0b032a9b8
--- /dev/null
+++ b/vendor/boto/boto/manage/test_manage.py
@@ -0,0 +1,34 @@
+from boto.manage.server import Server
+from boto.manage.volume import Volume
+import time
+
+print '--> Creating New Volume'
+volume = Volume.create()
+print volume
+
+print '--> Creating New Server'
+server_list = Server.create()
+server = server_list[0]
+print server
+
+print '----> Waiting for Server to start up'
+while server.status != 'running':
+ print '*'
+ time.sleep(10)
+print '----> Server is running'
+
+print '--> Run "df -k" on Server'
+status = server.run('df -k')
+print status[1]
+
+print '--> Now run volume.make_ready to make the volume ready to use on server'
+volume.make_ready(server)
+
+print '--> Run "df -k" on Server'
+status = server.run('df -k')
+print status[1]
+
+print '--> Do an "ls -al" on the new filesystem'
+status = server.run('ls -al %s' % volume.mount_point)
+print status[1]
+
diff --git a/vendor/boto/boto/manage/volume.py b/vendor/boto/boto/manage/volume.py
new file mode 100644
index 0000000000..66a458f67f
--- /dev/null
+++ b/vendor/boto/boto/manage/volume.py
@@ -0,0 +1,420 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from __future__ import with_statement
+from boto.sdb.db.model import Model
+from boto.sdb.db.property import StringProperty, IntegerProperty, ListProperty, ReferenceProperty, CalculatedProperty
+from boto.manage.server import Server
+from boto.manage import propget
+import boto.ec2
+import time
+import traceback
+from contextlib import closing
+import dateutil.parser
+import datetime
+
+
+class CommandLineGetter(object):
+
+ def get_region(self, params):
+ if not params.get('region', None):
+ prop = self.cls.find_property('region_name')
+ params['region'] = propget.get(prop, choices=boto.ec2.regions)
+
+ def get_zone(self, params):
+ if not params.get('zone', None):
+ prop = StringProperty(name='zone', verbose_name='EC2 Availability Zone',
+ choices=self.ec2.get_all_zones)
+ params['zone'] = propget.get(prop)
+
+ def get_name(self, params):
+ if not params.get('name', None):
+ prop = self.cls.find_property('name')
+ params['name'] = propget.get(prop)
+
+ def get_size(self, params):
+ if not params.get('size', None):
+ prop = IntegerProperty(name='size', verbose_name='Size (GB)')
+ params['size'] = propget.get(prop)
+
+ def get_mount_point(self, params):
+ if not params.get('mount_point', None):
+ prop = self.cls.find_property('mount_point')
+ params['mount_point'] = propget.get(prop)
+
+ def get_device(self, params):
+ if not params.get('device', None):
+ prop = self.cls.find_property('device')
+ params['device'] = propget.get(prop)
+
+ def get(self, cls, params):
+ self.cls = cls
+ self.get_region(params)
+ self.ec2 = params['region'].connect()
+ self.get_zone(params)
+ self.get_name(params)
+ self.get_size(params)
+ self.get_mount_point(params)
+ self.get_device(params)
+
+class Volume(Model):
+
+ name = StringProperty(required=True, unique=True, verbose_name='Name')
+ region_name = StringProperty(required=True, verbose_name='EC2 Region')
+ zone_name = StringProperty(required=True, verbose_name='EC2 Zone')
+ mount_point = StringProperty(verbose_name='Mount Point')
+ device = StringProperty(verbose_name="Device Name", default='/dev/sdp')
+ volume_id = StringProperty(required=True)
+ past_volume_ids = ListProperty(item_type=str)
+ server = ReferenceProperty(Server, collection_name='volumes',
+ verbose_name='Server Attached To')
+ volume_state = CalculatedProperty(verbose_name="Volume State",
+ calculated_type=str, use_method=True)
+ attachment_state = CalculatedProperty(verbose_name="Attachment State",
+ calculated_type=str, use_method=True)
+ size = CalculatedProperty(verbose_name="Size (GB)",
+ calculated_type=int, use_method=True)
+
+ @classmethod
+ def create(cls, **params):
+ getter = CommandLineGetter()
+ getter.get(cls, params)
+ region = params.get('region')
+ ec2 = region.connect()
+ zone = params.get('zone')
+ size = params.get('size')
+ ebs_volume = ec2.create_volume(size, zone.name)
+ v = cls()
+ v.ec2 = ec2
+ v.volume_id = ebs_volume.id
+ v.name = params.get('name')
+ v.mount_point = params.get('mount_point')
+ v.device = params.get('device')
+ v.region_name = region.name
+ v.zone_name = zone.name
+ v.put()
+ return v
+
+ @classmethod
+ def create_from_volume_id(cls, region_name, volume_id, name):
+ vol = None
+ ec2 = boto.ec2.connect_to_region(region_name)
+ rs = ec2.get_all_volumes([volume_id])
+ if len(rs) == 1:
+ v = rs[0]
+ vol = cls()
+ vol.volume_id = v.id
+ vol.name = name
+ vol.region_name = v.region.name
+ vol.zone_name = v.zone
+ vol.put()
+ return vol
+
+ def create_from_latest_snapshot(self, name, size=None):
+ snapshot = self.get_snapshots()[-1]
+ return self.create_from_snapshot(name, snapshot, size)
+
+ def create_from_snapshot(self, name, snapshot, size=None):
+ if size < self.size:
+ size = self.size
+ ec2 = self.get_ec2_connection()
+ if self.zone_name == None or self.zone_name == '':
+ # deal with the migration case where the zone is not set in the logical volume:
+ current_volume = ec2.get_all_volumes([self.volume_id])[0]
+ self.zone_name = current_volume.zone
+ ebs_volume = ec2.create_volume(size, self.zone_name, snapshot)
+ v = Volume()
+ v.ec2 = self.ec2
+ v.volume_id = ebs_volume.id
+ v.name = name
+ v.mount_point = self.mount_point
+ v.device = self.device
+ v.region_name = self.region_name
+ v.zone_name = self.zone_name
+ v.put()
+ return v
+
+ def get_ec2_connection(self):
+ if self.server:
+ return self.server.ec2
+ if not hasattr(self, 'ec2') or self.ec2 == None:
+ self.ec2 = boto.ec2.connect_to_region(self.region_name)
+ return self.ec2
+
+ def _volume_state(self):
+ ec2 = self.get_ec2_connection()
+ rs = ec2.get_all_volumes([self.volume_id])
+ return rs[0].volume_state()
+
+ def _attachment_state(self):
+ ec2 = self.get_ec2_connection()
+ rs = ec2.get_all_volumes([self.volume_id])
+ return rs[0].attachment_state()
+
+ def _size(self):
+ if not hasattr(self, '__size'):
+ ec2 = self.get_ec2_connection()
+ rs = ec2.get_all_volumes([self.volume_id])
+ self.__size = rs[0].size
+ return self.__size
+
+ def install_xfs(self):
+ if self.server:
+ self.server.install('xfsprogs xfsdump')
+
+ def get_snapshots(self):
+ """
+ Returns a list of all completed snapshots for this volume ID.
+ """
+ ec2 = self.get_ec2_connection()
+ rs = ec2.get_all_snapshots()
+ all_vols = [self.volume_id] + self.past_volume_ids
+ snaps = []
+ for snapshot in rs:
+ if snapshot.volume_id in all_vols:
+ if snapshot.progress == '100%':
+ snapshot.date = dateutil.parser.parse(snapshot.start_time)
+ snapshot.keep = True
+ snaps.append(snapshot)
+ snaps.sort(cmp=lambda x,y: cmp(x.date, y.date))
+ return snaps
+
+ def attach(self, server=None):
+ if self.attachment_state == 'attached':
+ print 'already attached'
+ return None
+ if server:
+ self.server = server
+ self.put()
+ ec2 = self.get_ec2_connection()
+ ec2.attach_volume(self.volume_id, self.server.instance_id, self.device)
+
+ def detach(self, force=False):
+ state = self.attachment_state
+ if state == 'available' or state == None or state == 'detaching':
+ print 'already detached'
+ return None
+ ec2 = self.get_ec2_connection()
+ ec2.detach_volume(self.volume_id, self.server.instance_id, self.device, force)
+ self.server = None
+ self.put()
+
+ def checkfs(self, use_cmd=None):
+ if self.server == None:
+ raise ValueError, 'server attribute must be set to run this command'
+ # detemine state of file system on volume, only works if attached
+ if use_cmd:
+ cmd = use_cmd
+ else:
+ cmd = self.server.get_cmdshell()
+ status = cmd.run('xfs_check %s' % self.device)
+ if not use_cmd:
+ cmd.close()
+ if status[1].startswith('bad superblock magic number 0'):
+ return False
+ return True
+
+ def wait(self):
+ if self.server == None:
+ raise ValueError, 'server attribute must be set to run this command'
+ with closing(self.server.get_cmdshell()) as cmd:
+ # wait for the volume device to appear
+ cmd = self.server.get_cmdshell()
+ while not cmd.exists(self.device):
+ boto.log.info('%s still does not exist, waiting 10 seconds' % self.device)
+ time.sleep(10)
+
+ def format(self):
+ if self.server == None:
+ raise ValueError, 'server attribute must be set to run this command'
+ status = None
+ with closing(self.server.get_cmdshell()) as cmd:
+ if not self.checkfs(cmd):
+ boto.log.info('make_fs...')
+ status = cmd.run('mkfs -t xfs %s' % self.device)
+ return status
+
+ def mount(self):
+ if self.server == None:
+ raise ValueError, 'server attribute must be set to run this command'
+ boto.log.info('handle_mount_point')
+ with closing(self.server.get_cmdshell()) as cmd:
+ cmd = self.server.get_cmdshell()
+ if not cmd.isdir(self.mount_point):
+ boto.log.info('making directory')
+ # mount directory doesn't exist so create it
+ cmd.run("mkdir %s" % self.mount_point)
+ else:
+ boto.log.info('directory exists already')
+ status = cmd.run('mount -l')
+ lines = status[1].split('\n')
+ for line in lines:
+ t = line.split()
+ if t and t[2] == self.mount_point:
+ # something is already mounted at the mount point
+ # unmount that and mount it as /tmp
+ if t[0] != self.device:
+ cmd.run('umount %s' % self.mount_point)
+ cmd.run('mount %s /tmp' % t[0])
+ cmd.run('chmod 777 /tmp')
+ break
+ # Mount up our new EBS volume onto mount_point
+ cmd.run("mount %s %s" % (self.device, self.mount_point))
+ cmd.run('xfs_growfs %s' % self.mount_point)
+
+ def make_ready(self, server):
+ self.server = server
+ self.put()
+ self.install_xfs()
+ self.attach()
+ self.wait()
+ self.format()
+ self.mount()
+
+ def freeze(self):
+ if self.server:
+ return self.server.run("/usr/sbin/xfs_freeze -f %s" % self.mount_point)
+
+ def unfreeze(self):
+ if self.server:
+ return self.server.run("/usr/sbin/xfs_freeze -u %s" % self.mount_point)
+
+ def snapshot(self):
+ # if this volume is attached to a server
+ # we need to freeze the XFS file system
+ try:
+ self.freeze()
+ if self.server == None:
+ snapshot = self.get_ec2_connection().create_snapshot(self.volume_id)
+ else:
+ snapshot = self.server.ec2.create_snapshot(self.volume_id)
+ boto.log.info('Snapshot of Volume %s created: %s' % (self.name, snapshot))
+ except Exception:
+ boto.log.info('Snapshot error')
+ boto.log.info(traceback.format_exc())
+ finally:
+ status = self.unfreeze()
+ return status
+
+ def get_snapshot_range(self, snaps, start_date=None, end_date=None):
+ l = []
+ for snap in snaps:
+ if start_date and end_date:
+ if snap.date >= start_date and snap.date <= end_date:
+ l.append(snap)
+ elif start_date:
+ if snap.date >= start_date:
+ l.append(snap)
+ elif end_date:
+ if snap.date <= end_date:
+ l.append(snap)
+ else:
+ l.append(snap)
+ return l
+
+ def trim_snapshots(self, delete=False):
+ """
+ Trim the number of snapshots for this volume. This method always
+ keeps the oldest snapshot. It then uses the parameters passed in
+ to determine how many others should be kept.
+
+ The algorithm is to keep all snapshots from the current day. Then
+ it will keep the first snapshot of the day for the previous seven days.
+ Then, it will keep the first snapshot of the week for the previous
+ four weeks. After than, it will keep the first snapshot of the month
+ for as many months as there are.
+
+ """
+ snaps = self.get_snapshots()
+ # Always keep the oldest and the newest
+ if len(snaps) <= 2:
+ return snaps
+ snaps = snaps[1:-1]
+ now = datetime.datetime.now(snaps[0].date.tzinfo)
+ midnight = datetime.datetime(year=now.year, month=now.month,
+ day=now.day, tzinfo=now.tzinfo)
+ # Keep the first snapshot from each day of the previous week
+ one_week = datetime.timedelta(days=7, seconds=60*60)
+ print midnight-one_week, midnight
+ previous_week = self.get_snapshot_range(snaps, midnight-one_week, midnight)
+ print previous_week
+ if not previous_week:
+ return snaps
+ current_day = None
+ for snap in previous_week:
+ if current_day and current_day == snap.date.day:
+ snap.keep = False
+ else:
+ current_day = snap.date.day
+ # Get ourselves onto the next full week boundary
+ if previous_week:
+ week_boundary = previous_week[0].date
+ if week_boundary.weekday() != 0:
+ delta = datetime.timedelta(days=week_boundary.weekday())
+ week_boundary = week_boundary - delta
+ # Keep one within this partial week
+ partial_week = self.get_snapshot_range(snaps, week_boundary, previous_week[0].date)
+ if len(partial_week) > 1:
+ for snap in partial_week[1:]:
+ snap.keep = False
+ # Keep the first snapshot of each week for the previous 4 weeks
+ for i in range(0,4):
+ weeks_worth = self.get_snapshot_range(snaps, week_boundary-one_week, week_boundary)
+ if len(weeks_worth) > 1:
+ for snap in weeks_worth[1:]:
+ snap.keep = False
+ week_boundary = week_boundary - one_week
+ # Now look through all remaining snaps and keep one per month
+ remainder = self.get_snapshot_range(snaps, end_date=week_boundary)
+ current_month = None
+ for snap in remainder:
+ if current_month and current_month == snap.date.month:
+ snap.keep = False
+ else:
+ current_month = snap.date.month
+ if delete:
+ for snap in snaps:
+ if not snap.keep:
+ boto.log.info('Deleting %s(%s) for %s' % (snap, snap.date, self.name))
+ snap.delete()
+ return snaps
+
+ def grow(self, size):
+ pass
+
+ def copy(self, snapshot):
+ pass
+
+ def get_snapshot_from_date(self, date):
+ pass
+
+ def delete(self, delete_ebs_volume=False):
+ if delete_ebs_volume:
+ self.detach()
+ ec2 = self.get_ec2_connection()
+ ec2.delete_volume(self.volume_id)
+ Model.delete(self)
+
+ def archive(self):
+ # snapshot volume, trim snaps, delete volume-id
+ pass
+
+
diff --git a/vendor/boto/boto/mapreduce/__init__.py b/vendor/boto/boto/mapreduce/__init__.py
new file mode 100644
index 0000000000..ac3ddc4474
--- /dev/null
+++ b/vendor/boto/boto/mapreduce/__init__.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+
diff --git a/vendor/boto/boto/mapreduce/lqs.py b/vendor/boto/boto/mapreduce/lqs.py
new file mode 100644
index 0000000000..fc76e50f82
--- /dev/null
+++ b/vendor/boto/boto/mapreduce/lqs.py
@@ -0,0 +1,152 @@
+import SocketServer, os, datetime, sys, random, time
+import simplejson
+
+class LQSCommand:
+
+ def __init__(self, line):
+ self.raw_line = line
+ self.line = self.raw_line.strip()
+ l = self.line.split(' ')
+ self.name = l[0]
+ if len(l) > 1:
+ self.args = [arg for arg in l[1:] if arg]
+ else:
+ self.args = []
+
+class LQSMessage(dict):
+
+ def __init__(self, item=None, args=None, jsonvalue=None):
+ dict.__init__(self)
+ if jsonvalue:
+ self.decode(jsonvalue)
+ else:
+ self['id'] = '%d_%d' % (int(time.time()), int(random.random()*1000000))
+ self['item'] = item
+ self['args'] = args
+
+ def encode(self):
+ return simplejson.dumps(self)
+
+ def decode(self, value):
+ self.update(simplejson.loads(value))
+
+ def is_empty(self):
+ if self['item'] == None:
+ return True
+ return False
+
+class LQSServer(SocketServer.UDPServer):
+
+ PORT = 5151
+ TIMEOUT = 30
+ MAXSIZE = 8192
+
+ def __init__(self, server_address, RequestHandlerClass, iterator, args=None):
+ server_address = (server_address, self.PORT)
+ SocketServer.UDPServer.__init__(self, server_address, RequestHandlerClass)
+ self.count = 0
+ self.iterator = iterator
+ self.args = args
+ self.start = datetime.datetime.now()
+ self.end = None
+ self.extant = []
+
+class LQSHandler(SocketServer.DatagramRequestHandler):
+
+ def get_cmd(self):
+ return LQSCommand(self.rfile.readline())
+
+ def build_msg(self):
+ if not self.server.iterator:
+ return LQSMessage(None)
+ try:
+ item = self.server.iterator.next()
+ msg = LQSMessage(item, self.server.args)
+ return msg
+ except StopIteration:
+ self.server.iterator = None
+ return LQSMessage(None)
+
+ def respond(self, msg):
+ self.wfile.write(msg.encode())
+
+ def check_extant(self):
+ if len(self.server.extant) == 0 and not self.server.iterator:
+ self.server.end = datetime.datetime.now()
+ delta = self.server.end - self.server.start
+ print 'Total Processing Time: %s' % delta
+ print 'Total Messages Processed: %d' % self.server.count
+
+ def do_debug(self, cmd):
+ args = {'extant' : self.server.extant,
+ 'count' : self.server.count}
+ msg = LQSMessage('debug', args)
+ self.respond(msg)
+
+ def do_next(self, cmd):
+ out_msg = self.build_msg()
+ if not out_msg.is_empty():
+ self.server.count += 1
+ self.server.extant.append(out_msg['id'])
+ self.respond(out_msg)
+
+ def do_delete(self, cmd):
+ if len(cmd.args) != 1:
+ self.error(cmd, 'delete command requires message id')
+ else:
+ mid = cmd.args[0]
+ try:
+ self.server.extant.remove(mid)
+ except ValueError:
+ self.error(cmd, 'message id not found')
+ args = {'deleted' : True}
+ msg = LQSMessage(mid, args)
+ self.respond(msg)
+ self.check_extant()
+
+ def error(self, cmd, error_msg=None):
+ args = {'error_msg' : error_msg,
+ 'cmd_name' : cmd.name,
+ 'cmd_args' : cmd.args}
+ msg = LQSMessage('error', args)
+ self.respond(msg)
+
+ def do_stop(self, cmd):
+ sys.exit(0)
+
+ def handle(self):
+ cmd = self.get_cmd()
+ if hasattr(self, 'do_%s' % cmd.name):
+ method = getattr(self, 'do_%s' % cmd.name)
+ method(cmd)
+ else:
+ self.error(cmd, 'unrecognized command')
+
+class PersistHandler(LQSHandler):
+
+ def build_msg(self):
+ if not self.server.iterator:
+ return LQSMessage(None)
+ try:
+ obj = self.server.iterator.next()
+ msg = LQSMessage(obj.id, self.server.args)
+ return msg
+ except StopIteration:
+ self.server.iterator = None
+ return LQSMessage(None)
+
+def test_file(path, args=None):
+ l = os.listdir(path)
+ if not args:
+ args = {}
+ args['path'] = path
+ s = LQSServer('', LQSHandler, iter(l), args)
+ print "Awaiting UDP messages on port %d" % s.PORT
+ s.serve_forever()
+
+def test_simple(n):
+ l = range(0, n)
+ s = LQSServer('', LQSHandler, iter(l), None)
+ print "Awaiting UDP messages on port %d" % s.PORT
+ s.serve_forever()
+
diff --git a/vendor/boto/boto/mapreduce/partitiondb.py b/vendor/boto/boto/mapreduce/partitiondb.py
new file mode 100644
index 0000000000..25cf135367
--- /dev/null
+++ b/vendor/boto/boto/mapreduce/partitiondb.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+import random
+import os
+import datetime
+from boto.sdb.persist.object import SDBObject
+from boto.sdb.persist.property import StringProperty, ObjectProperty, DateTimeProperty, ObjectListProperty, S3KeyProperty
+
+
+HEX_DIGITS = '0123456789abcdef'
+
+class Identifier(object):
+
+ @staticmethod
+ def gen(prefix):
+ suffix = ''
+ for i in range(0,8):
+ suffix += random.choice(HEX_DIGITS)
+ return prefix + '-' + suffix
+
+
+class Version(SDBObject):
+
+ name = StringProperty()
+ pdb = ObjectProperty(ref_class=SDBObject)
+ date = DateTimeProperty()
+
+ def __init__(self, id=None, manager=None):
+ SDBObject.__init__(self, id, manager)
+ if id == None:
+ self.name = Identifier.gen('v')
+ self.date = datetime.datetime.now()
+ print 'created Version %s' % self.name
+
+ def partitions(self):
+ """
+ Return an iterator containing all Partition objects related to this Version.
+
+ :rtype: iterator of :class:`boto.mapreduce.partitiondb.Partition`
+ :return: The Partitions in this Version
+ """
+ return self.get_related_objects('version', Partition)
+
+ def add_partition(self, name=None):
+ """
+ Add a new Partition to this Version.
+
+ :type name: string
+ :param name: The name of the new Partition (optional)
+
+ :rtype: :class:`boto.mapreduce.partitiondb.Partition`
+ :return: The new Partition object
+ """
+ p = Partition(manager=self.manager, name=name)
+ p.version = self
+ p.pdb = self.pdb
+ p.save()
+ return p
+
+ def get_s3_prefix(self):
+ if not self.pdb:
+ raise ValueError, 'pdb attribute must be set to compute S3 prefix'
+ return self.pdb.get_s3_prefix() + self.name + '/'
+
+class PartitionDB(SDBObject):
+
+ name = StringProperty()
+ bucket_name = StringProperty()
+ versions = ObjectListProperty(ref_class=Version)
+
+ def __init__(self, id=None, manager=None, name='', bucket_name=''):
+ SDBObject.__init__(self, id, manager)
+ if id == None:
+ self.name = name
+ self.bucket_name = bucket_name
+
+ def get_s3_prefix(self):
+ return self.name + '/'
+
+ def add_version(self):
+ """
+ Add a new Version to this PartitionDB. The newly added version becomes the
+ current version.
+
+ :rtype: :class:`boto.mapreduce.partitiondb.Version`
+ :return: The newly created Version object.
+ """
+ v = Version()
+ v.pdb = self
+ v.save()
+ self.versions.append(v)
+ return v
+
+ def revert(self):
+ """
+ Revert to the previous version of this PartitionDB. The current version is removed from the
+ list of Versions and the Version immediately preceeding it becomes the current version.
+ Note that this method does not delete the Version object or any Partitions related to the
+ Version object.
+
+ :rtype: :class:`boto.mapreduce.partitiondb.Version`
+ :return: The previous current Version object.
+ """
+ v = self.current_version()
+ if v:
+ self.versions.remove(v)
+ return v
+
+ def current_version(self):
+ """
+ Get the currently active Version of this PartitionDB object.
+
+ :rtype: :class:`boto.mapreduce.partitiondb.Version`
+ :return: The current Version object or None if there are no Versions associated
+ with this PartitionDB object.
+ """
+ if self.versions:
+ if len(self.versions) > 0:
+ return self.versions[-1]
+ return None
+
+class Partition(SDBObject):
+
+ def __init__(self, id=None, manager=None, name=None):
+ SDBObject.__init__(self, id, manager)
+ if id == None:
+ self.name = name
+
+ name = StringProperty()
+ version = ObjectProperty(ref_class=Version)
+ pdb = ObjectProperty(ref_class=PartitionDB)
+ data = S3KeyProperty()
+
+ def get_key_name(self):
+ return self.version.get_s3_prefix() + self.name
+
+ def upload(self, path, bucket_name=None):
+ if not bucket_name:
+ bucket_name = self.version.pdb.bucket_name
+ s3 = self.manager.get_s3_connection()
+ bucket = s3.lookup(bucket_name)
+ directory, filename = os.path.split(path)
+ self.name = filename
+ key = bucket.new_key(self.get_key_name())
+ key.set_contents_from_filename(path)
+ self.data = key
+ self.save()
+
+ def delete(self):
+ if self.data:
+ self.data.delete()
+ SDBObject.delete(self)
+
+
+
diff --git a/vendor/boto/boto/mapreduce/pdb_delete b/vendor/boto/boto/mapreduce/pdb_delete
new file mode 100644
index 0000000000..b7af9cc06a
--- /dev/null
+++ b/vendor/boto/boto/mapreduce/pdb_delete
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+import queuetools, os, signal, sys
+import subprocess
+import time
+from optparse import OptionParser
+from boto.mapreduce.partitiondb import PartitionDB, Partition, Version
+from lqs import LQSServer, PersistHandler
+from boto.exception import SDBPersistenceError
+from boto.sdb.persist import get_manager, revive_object_from_id
+
+USAGE = """
+ SYNOPSIS
+ %prog [options] [command]
+ DESCRIPTION
+ Delete a PartitionDB and all related data in SimpleDB and S3.
+"""
+class Client:
+
+ def __init__(self, queue_name):
+ self.q = queuetools.get_queue(queue_name)
+ self.q.connect()
+ self.manager = get_manager()
+ self.process()
+
+ def process(self):
+ m = self.q.get()
+ while m['item']:
+ print 'Deleting: %s' % m['item']
+ obj = revive_object_from_id(m['item'], manager=self.manager)
+ obj.delete()
+ self.q.delete(m)
+ m = self.q.get()
+ print 'client processing complete'
+
+class Server:
+
+ def __init__(self, pdb_name, domain_name=None):
+ self.pdb_name = pdb_name
+ self.manager = get_manager(domain_name)
+ self.pdb = PartitionDB.get(name=self.pdb_name)
+ self.serve()
+
+ def serve(self):
+ args = {'pdb_id' : self.pdb.id}
+ rs = self.pdb.get_related_objects('pdb')
+ self.pdb.delete()
+ s = LQSServer('', PersistHandler, rs, args)
+ s.serve_forever()
+
+class Delete:
+
+ Commands = {'client' : 'Start a Delete client',
+ 'server' : 'Start a Delete server'}
+
+ def __init__(self):
+ self.parser = OptionParser(usage=USAGE)
+ self.parser.add_option("--help-commands", action="store_true", dest="help_commands",
+ help="provides help on the available commands")
+ self.parser.add_option('-d', '--domain-name', action='store', type='string',
+ help='name of the SimpleDB domain where PDB objects are stored')
+ self.parser.add_option('-n', '--num-processes', action='store', type='int', dest='num_processes',
+ help='the number of client processes launched')
+ self.parser.set_defaults(num_processes=5)
+ self.parser.add_option('-p', '--pdb-name', action='store', type='string',
+ help='name of the PDB in which to store files (will create if necessary)')
+ self.options, self.args = self.parser.parse_args()
+ self.prog_name = sys.argv[0]
+
+ def print_command_help(self):
+ print '\nCommands:'
+ for key in self.Commands.keys():
+ print ' %s\t\t%s' % (key, self.Commands[key])
+
+ def do_server(self):
+ if not self.options.pdb_name:
+ self.parser.error('No PDB name provided')
+ s = Server(self.options.pdb_name, self.options.domain_name)
+
+ def do_client(self):
+ c = Client('localhost')
+
+ def main(self):
+ if self.options.help_commands:
+ self.print_command_help()
+ sys.exit(0)
+ if len(self.args) == 0:
+ if not self.options.pdb_name:
+ self.parser.error('No PDB name provided')
+ server_command = '%s -p %s ' % (self.prog_name, self.options.pdb_name)
+ server_command += ' server'
+ client_command = '%s client' % self.prog_name
+ server = subprocess.Popen(server_command, shell=True)
+ print 'server pid: %s' % server.pid
+ time.sleep(5)
+ clients = []
+ for i in range(0, self.options.num_processes):
+ client = subprocess.Popen(client_command, shell=True)
+ clients.append(client)
+ print 'waiting for clients to finish'
+ for client in clients:
+ client.wait()
+ os.kill(server.pid, signal.SIGTERM)
+ elif len(self.args) == 1:
+ self.command = self.args[0]
+ if hasattr(self, 'do_%s' % self.command):
+ method = getattr(self, 'do_%s' % self.command)
+ method()
+ else:
+ self.parser.error('command (%s) not recognized' % self.command)
+ else:
+ self.parser.error('unrecognized commands')
+
+if __name__ == "__main__":
+ delete = Delete()
+ delete.main()
diff --git a/vendor/boto/boto/mapreduce/pdb_describe b/vendor/boto/boto/mapreduce/pdb_describe
new file mode 100755
index 0000000000..d0fa86c63d
--- /dev/null
+++ b/vendor/boto/boto/mapreduce/pdb_describe
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+import sys
+from optparse import OptionParser
+from boto.mapreduce.partitiondb import PartitionDB, Partition, Version
+from boto.exception import SDBPersistenceError
+from boto.sdb.persist import get_manager, get_domain
+
+USAGE = """
+ SYNOPSIS
+ %prog [options]
+ DESCRIPTION
+ List and describe your PartitionDBs.
+ Called with no options, all PartitionDB objects defined in your default
+ domain (as specified in the "default_domain" option in the "[Persist]"
+ section of your boto config file) will be listed.
+ When called with a particular PartitionDB name (using -p option) all
+ Version objects of that PartitionDB object will be listed.
+ When called with the -p option and a particular Version name specified
+ (using the -v option) all Partitions in that Version object will be listed.
+"""
+class Describe:
+
+ def __init__(self):
+ self.parser = OptionParser(usage=USAGE)
+ self.parser.add_option('-d', '--domain-name', action='store', type='string',
+ help='name of the SimpleDB domain where PDB objects are stored')
+ self.parser.add_option('-n', '--num-entries', action='store', type='int',
+ help='maximum number of entries to print (default 100)')
+ self.parser.set_defaults(num_entries=100)
+ self.parser.add_option('-p', '--pdb-name', action='store', type='string',
+ help='name of the PDB to describe')
+ self.parser.add_option('-v', '--version-name', action='store', type='string',
+ help='name of the PDB Version to describe')
+ self.options, self.args = self.parser.parse_args()
+ self.prog_name = sys.argv[0]
+
+ def describe_all(self):
+ print 'Using SimpleDB Domain: %s' % get_domain()
+ print 'PDBs:'
+ rs = PartitionDB.list()
+ i = 0
+ for pdb in rs:
+ print '%s\t%s\t%s' % (pdb.id, pdb.name, pdb.bucket_name)
+ i += 1
+ if i == self.options.num_entries:
+ break
+
+ def describe_pdb(self, pdb_name):
+ print 'Using SimpleDB Domain: %s' % get_domain()
+ print 'PDB: %s' % pdb_name
+ print 'Versions:'
+ try:
+ pdb = PartitionDB.get(name=pdb_name)
+ i = 0
+ for v in pdb.versions:
+ if v.date:
+ ds = v.date.isoformat()
+ else:
+ ds = 'unknown'
+ print '%s\t%s\t%s' % (v.id, v.name, ds)
+ i += 1
+ if i == self.options.num_entries:
+ break
+ cv = pdb.current_version()
+ if cv:
+ print 'Current Version: %s' % cv.name
+ else:
+ print 'Current Version: None'
+ except SDBPersistenceError:
+ self.parser.error('pdb_name (%s) unknown' % pdb_name)
+
+ def describe_version(self, pdb_name, version_name):
+ print 'Using SimpleDB Domain: %s' % get_domain()
+ print 'PDB: %s' % pdb_name
+ print 'Version: %s' % version_name
+ print 'Partitions:'
+ try:
+ pdb = PartitionDB.get(name=pdb_name)
+ for v in pdb.versions:
+ if v.name == version_name:
+ i = 0
+ for p in v.partitions():
+ print '%s\t%s' % (p.id, p.name)
+ i += 1
+ if i == self.options.num_entries:
+ break
+ except SDBPersistenceError:
+ self.parser.error('pdb_name (%s) unknown' % pdb_name)
+
+ def main(self):
+ self.options, self.args = self.parser.parse_args()
+ self.manager = get_manager(self.options.domain_name)
+
+ if self.options.pdb_name:
+ if self.options.version_name:
+ self.describe_version(self.options.pdb_name, self.options.version_name)
+ else:
+ self.describe_pdb(self.options.pdb_name)
+ else:
+ self.describe_all()
+
+if __name__ == "__main__":
+ describe = Describe()
+ describe.main()
diff --git a/vendor/boto/boto/mapreduce/pdb_revert b/vendor/boto/boto/mapreduce/pdb_revert
new file mode 100755
index 0000000000..daffeef1ed
--- /dev/null
+++ b/vendor/boto/boto/mapreduce/pdb_revert
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+import queuetools, os, signal, sys
+import subprocess
+import time
+from optparse import OptionParser
+from boto.mapreduce.partitiondb import PartitionDB, Partition, Version
+from lqs import LQSServer, PersistHandler
+from boto.exception import SDBPersistenceError
+from boto.sdb.persist import get_manager
+
+USAGE = """
+ SYNOPSIS
+ %prog [options] [command]
+ DESCRIPTION
+ Revert to the previous Version in a PartitionDB.
+"""
+class Client:
+
+ def __init__(self, queue_name):
+ self.q = queuetools.get_queue(queue_name)
+ self.q.connect()
+ self.manager = get_manager()
+ self.process()
+
+ def process(self):
+ m = self.q.get()
+ while m['item']:
+ print 'Deleting: %s' % m['item']
+ p = Partition(id=m['item'], manager=self.manager)
+ p.delete()
+ self.q.delete(m)
+ m = self.q.get()
+ print 'client processing complete'
+
+class Server:
+
+ def __init__(self, pdb_name, domain_name=None):
+ self.pdb_name = pdb_name
+ self.manager = get_manager(domain_name)
+ self.pdb = PartitionDB.get(name=self.pdb_name)
+ self.serve()
+
+ def serve(self):
+ v = self.pdb.revert()
+ args = {'v_id' : v.id}
+ rs = v.partitions()
+ s = LQSServer('', PersistHandler, rs, args)
+ s.serve_forever()
+
+class Revert:
+
+ Commands = {'client' : 'Start a Revert client',
+ 'server' : 'Start a Revert server'}
+
+ def __init__(self):
+ self.parser = OptionParser(usage=USAGE)
+ self.parser.add_option("--help-commands", action="store_true", dest="help_commands",
+ help="provides help on the available commands")
+ self.parser.add_option('-d', '--domain-name', action='store', type='string',
+ help='name of the SimpleDB domain where PDB objects are stored')
+ self.parser.add_option('-n', '--num-processes', action='store', type='int', dest='num_processes',
+ help='the number of client processes launched')
+ self.parser.set_defaults(num_processes=5)
+ self.parser.add_option('-p', '--pdb-name', action='store', type='string',
+ help='name of the PDB in which to store files (will create if necessary)')
+ self.options, self.args = self.parser.parse_args()
+ self.prog_name = sys.argv[0]
+
+ def print_command_help(self):
+ print '\nCommands:'
+ for key in self.Commands.keys():
+ print ' %s\t\t%s' % (key, self.Commands[key])
+
+ def do_server(self):
+ if not self.options.pdb_name:
+ self.parser.error('No PDB name provided')
+ s = Server(self.options.pdb_name, self.options.domain_name)
+
+ def do_client(self):
+ c = Client('localhost')
+
+ def main(self):
+ if self.options.help_commands:
+ self.print_command_help()
+ sys.exit(0)
+ if len(self.args) == 0:
+ if not self.options.pdb_name:
+ self.parser.error('No PDB name provided')
+ server_command = '%s -p %s ' % (self.prog_name, self.options.pdb_name)
+ server_command += ' server'
+ client_command = '%s client' % self.prog_name
+ server = subprocess.Popen(server_command, shell=True)
+ print 'server pid: %s' % server.pid
+ time.sleep(5)
+ clients = []
+ for i in range(0, self.options.num_processes):
+ client = subprocess.Popen(client_command, shell=True)
+ clients.append(client)
+ print 'waiting for clients to finish'
+ for client in clients:
+ client.wait()
+ os.kill(server.pid, signal.SIGTERM)
+ elif len(self.args) == 1:
+ self.command = self.args[0]
+ if hasattr(self, 'do_%s' % self.command):
+ method = getattr(self, 'do_%s' % self.command)
+ method()
+ else:
+ self.parser.error('command (%s) not recognized' % self.command)
+ else:
+ self.parser.error('unrecognized commands')
+
+if __name__ == "__main__":
+ revert = Revert()
+ revert.main()
diff --git a/vendor/boto/boto/mapreduce/pdb_upload b/vendor/boto/boto/mapreduce/pdb_upload
new file mode 100755
index 0000000000..1ca2b6d398
--- /dev/null
+++ b/vendor/boto/boto/mapreduce/pdb_upload
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+import queuetools, os, signal, sys
+import subprocess
+import time
+from optparse import OptionParser
+from boto.mapreduce.partitiondb import PartitionDB, Partition, Version
+from lqs import LQSServer, LQSHandler
+from boto.exception import SDBPersistenceError
+from boto.sdb.persist import get_manager
+
+USAGE = """
+ SYNOPSIS
+ %prog [options]
+ DESCRIPTION
+ Upload partition files to a PartitionDB.
+ Called with no options, all PartitionDB objects defined in your default
+ domain (as specified in the "default_domain" option in the "[Persist]"
+ section of your boto config file) will be listed.
+ When called with a particular PartitionDB name (using -p option) all
+ Version objects of that PartitionDB object will be listed.
+ When called with the -p option and a particular Version name specified
+ (using the -v option) all Partitions in that Version object will be listed.
+"""
+class Client:
+
+ def __init__(self, queue_name):
+ self.q = queuetools.get_queue(queue_name)
+ self.q.connect()
+ self.manager = get_manager()
+ self.process()
+
+ def process(self):
+ m = self.q.get()
+ if m['item']:
+ v = Version(m['args']['v_id'], self.manager)
+ bucket_name = v.pdb.bucket_name
+ while m['item']:
+ print 'Uploading: %s' % m['item']
+ p = v.add_partition(name=m['item'])
+ p.upload(os.path.join(m['args']['path'], m['item']), bucket_name)
+ self.q.delete(m)
+ m = self.q.get()
+ print 'client processing complete'
+
+class Server:
+
+ def __init__(self, path, pdb_name, bucket_name=None, domain_name=None):
+ self.path = path
+ self.pdb_name = pdb_name
+ self.bucket_name = bucket_name
+ self.manager = get_manager(domain_name)
+ self.get_pdb()
+ self.serve()
+
+ def get_pdb(self):
+ try:
+ self.pdb = PartitionDB.get(name=self.pdb_name)
+ except SDBPersistenceError:
+ self.pdb = PartitionDB(manager=self.manager, name=self.pdb_name, bucket_name=self.bucket_name)
+ self.pdb.save()
+
+ def serve(self):
+ v = self.pdb.add_version()
+ args = {'path' : self.path,
+ 'v_id' : v.id}
+ l = os.listdir(self.path)
+ s = LQSServer('', LQSHandler, iter(l), args)
+ s.serve_forever()
+
+class Upload:
+
+ Usage = "usage: %prog [options] command"
+
+ Commands = {'client' : 'Start an Upload client',
+ 'server' : 'Start an Upload server'}
+
+ def __init__(self):
+ self.parser = OptionParser(usage=self.Usage)
+ self.parser.add_option("--help-commands", action="store_true", dest="help_commands",
+ help="provides help on the available commands")
+ self.parser.add_option('-d', '--domain-name', action='store', type='string',
+ help='name of the SimpleDB domain where PDB objects are stored')
+ self.parser.add_option('-n', '--num-processes', action='store', type='int', dest='num_processes',
+ help='the number of client processes launched')
+ self.parser.set_defaults(num_processes=2)
+ self.parser.add_option('-i', '--input-path', action='store', type='string',
+ help='the path to directory to upload')
+ self.parser.add_option('-p', '--pdb-name', action='store', type='string',
+ help='name of the PDB in which to store files (will create if necessary)')
+ self.parser.add_option('-b', '--bucket-name', action='store', type='string',
+ help='name of S3 bucket (only needed if creating new PDB)')
+ self.options, self.args = self.parser.parse_args()
+ self.prog_name = sys.argv[0]
+
+ def print_command_help(self):
+ print '\nCommands:'
+ for key in self.Commands.keys():
+ print ' %s\t\t%s' % (key, self.Commands[key])
+
+ def do_server(self):
+ if not self.options.input_path:
+ self.parser.error('No path provided')
+ if not os.path.isdir(self.options.input_path):
+ self.parser.error('Invalid path (%s)' % self.options.input_path)
+ if not self.options.pdb_name:
+ self.parser.error('No PDB name provided')
+ s = Server(self.options.input_path, self.options.pdb_name,
+ self.options.bucket_name, self.options.domain_name)
+
+ def do_client(self):
+ c = Client('localhost')
+
+ def main(self):
+ if self.options.help_commands:
+ self.print_command_help()
+ sys.exit(0)
+ if len(self.args) == 0:
+ if not self.options.input_path:
+ self.parser.error('No path provided')
+ if not os.path.isdir(self.options.input_path):
+ self.parser.error('Invalid path (%s)' % self.options.input_path)
+ if not self.options.pdb_name:
+ self.parser.error('No PDB name provided')
+ server_command = '%s -p %s -i %s' % (self.prog_name, self.options.pdb_name, self.options.input_path)
+ if self.options.bucket_name:
+ server_command += ' -b %s' % self.options.bucket_name
+ server_command += ' server'
+ client_command = '%s client' % self.prog_name
+ server = subprocess.Popen(server_command, shell=True)
+ print 'server pid: %s' % server.pid
+ time.sleep(5)
+ clients = []
+ for i in range(0, self.options.num_processes):
+ client = subprocess.Popen(client_command, shell=True)
+ clients.append(client)
+ print 'waiting for clients to finish'
+ for client in clients:
+ client.wait()
+ os.kill(server.pid, signal.SIGTERM)
+ elif len(self.args) == 1:
+ self.command = self.args[0]
+ if hasattr(self, 'do_%s' % self.command):
+ method = getattr(self, 'do_%s' % self.command)
+ method()
+ else:
+ self.parser.error('command (%s) not recognized' % self.command)
+ else:
+ self.parser.error('unrecognized commands')
+
+if __name__ == "__main__":
+ upload = Upload()
+ upload.main()
diff --git a/vendor/boto/boto/mapreduce/queuetools.py b/vendor/boto/boto/mapreduce/queuetools.py
new file mode 100644
index 0000000000..db1e495c6d
--- /dev/null
+++ b/vendor/boto/boto/mapreduce/queuetools.py
@@ -0,0 +1,66 @@
+#!/usr/bin/python
+import socket
+from lqs import LQSServer, LQSMessage
+import boto
+from boto.sqs.jsonmessage import JSONMessage
+
+class LQSClient:
+
+ def __init__(self, host):
+ self.host = host
+ self.port = LQSServer.PORT
+ self.timeout = LQSServer.TIMEOUT
+ self.max_len = LQSServer.MAXSIZE
+ self.sock = None
+
+ def connect(self):
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ self.sock.settimeout(self.timeout)
+ self.sock.connect((self.host, self.port))
+
+ def decode(self, jsonstr):
+ return LQSMessage(jsonvalue=jsonstr)
+
+ def get(self):
+ self.sock.send('next')
+ try:
+ jsonstr = self.sock.recv(self.max_len)
+ msg = LQSMessage(jsonvalue=jsonstr)
+ return msg
+ except:
+ print "recv from %s failed" % self.host
+
+ def delete(self, msg):
+ self.sock.send('delete %s' % msg['id'])
+ try:
+ jsonstr = self.sock.recv(self.max_len)
+ msg = LQSMessage(jsonvalue=jsonstr)
+ return msg
+ except:
+ print "recv from %s failed" % self.host
+
+ def close(self):
+ self.sock.close()
+
+class SQSClient:
+
+ def __init__(self, queue_name):
+ self.queue_name = queue_name
+
+ def connect(self):
+ self.queue = boto.lookup('sqs', self.queue_name)
+ self.queue.set_mesasge_class(JSONMessage)
+
+ def get(self):
+ m = self.queue.read()
+ return m.get_body()
+
+ def close(self):
+ pass
+
+def get_queue(name):
+ if name == 'localhost':
+ return LQSClient(name)
+ else:
+ return SQSClient(name)
+
diff --git a/vendor/boto/boto/mashups/__init__.py b/vendor/boto/boto/mashups/__init__.py
new file mode 100644
index 0000000000..449bd162a8
--- /dev/null
+++ b/vendor/boto/boto/mashups/__init__.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+
diff --git a/vendor/boto/boto/mashups/interactive.py b/vendor/boto/boto/mashups/interactive.py
new file mode 100644
index 0000000000..b80e661e5f
--- /dev/null
+++ b/vendor/boto/boto/mashups/interactive.py
@@ -0,0 +1,97 @@
+# Copyright (C) 2003-2007 Robey Pointer <robey@lag.net>
+#
+# This file is part of paramiko.
+#
+# Paramiko is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+
+import socket
+import sys
+
+# windows does not have termios...
+try:
+ import termios
+ import tty
+ has_termios = True
+except ImportError:
+ has_termios = False
+
+
+def interactive_shell(chan):
+ if has_termios:
+ posix_shell(chan)
+ else:
+ windows_shell(chan)
+
+
+def posix_shell(chan):
+ import select
+
+ oldtty = termios.tcgetattr(sys.stdin)
+ try:
+ tty.setraw(sys.stdin.fileno())
+ tty.setcbreak(sys.stdin.fileno())
+ chan.settimeout(0.0)
+
+ while True:
+ r, w, e = select.select([chan, sys.stdin], [], [])
+ if chan in r:
+ try:
+ x = chan.recv(1024)
+ if len(x) == 0:
+ print '\r\n*** EOF\r\n',
+ break
+ sys.stdout.write(x)
+ sys.stdout.flush()
+ except socket.timeout:
+ pass
+ if sys.stdin in r:
+ x = sys.stdin.read(1)
+ if len(x) == 0:
+ break
+ chan.send(x)
+
+ finally:
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
+
+
+# thanks to Mike Looijmans for this code
+def windows_shell(chan):
+ import threading
+
+ sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n")
+
+ def writeall(sock):
+ while True:
+ data = sock.recv(256)
+ if not data:
+ sys.stdout.write('\r\n*** EOF ***\r\n\r\n')
+ sys.stdout.flush()
+ break
+ sys.stdout.write(data)
+ sys.stdout.flush()
+
+ writer = threading.Thread(target=writeall, args=(chan,))
+ writer.start()
+
+ try:
+ while True:
+ d = sys.stdin.read(1)
+ if not d:
+ break
+ chan.send(d)
+ except EOFError:
+ # user hit ^Z or F6
+ pass
diff --git a/vendor/boto/boto/mashups/iobject.py b/vendor/boto/boto/mashups/iobject.py
new file mode 100644
index 0000000000..a226b5ca6a
--- /dev/null
+++ b/vendor/boto/boto/mashups/iobject.py
@@ -0,0 +1,115 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import os
+
+def int_val_fn(v):
+ try:
+ int(v)
+ return True
+ except:
+ return False
+
+class IObject(object):
+
+ def choose_from_list(self, item_list, search_str='',
+ prompt='Enter Selection'):
+ if not item_list:
+ print 'No Choices Available'
+ return
+ choice = None
+ while not choice:
+ n = 1
+ choices = []
+ for item in item_list:
+ if isinstance(item, str):
+ print '[%d] %s' % (n, item)
+ choices.append(item)
+ n += 1
+ else:
+ obj, id, desc = item
+ if desc:
+ if desc.find(search_str) >= 0:
+ print '[%d] %s - %s' % (n, id, desc)
+ choices.append(obj)
+ n += 1
+ else:
+ if id.find(search_str) >= 0:
+ print '[%d] %s' % (n, id)
+ choices.append(obj)
+ n += 1
+ if choices:
+ val = raw_input('%s[1-%d]: ' % (prompt, len(choices)))
+ if val.startswith('/'):
+ search_str = val[1:]
+ else:
+ try:
+ int_val = int(val)
+ if int_val == 0:
+ return None
+ choice = choices[int_val-1]
+ except ValueError:
+ print '%s is not a valid choice' % val
+ except IndexError:
+ print '%s is not within the range[1-%d]' % (val,
+ len(choices))
+ else:
+ print "No objects matched your pattern"
+ search_str = ''
+ return choice
+
+ def get_string(self, prompt, validation_fn=None):
+ okay = False
+ while not okay:
+ val = raw_input('%s: ' % prompt)
+ if validation_fn:
+ okay = validation_fn(val)
+ if not okay:
+ print 'Invalid value: %s' % val
+ else:
+ okay = True
+ return val
+
+ def get_filename(self, prompt):
+ okay = False
+ val = ''
+ while not okay:
+ val = raw_input('%s: %s' % (prompt, val))
+ val = os.path.expanduser(val)
+ if os.path.isfile(val):
+ okay = True
+ elif os.path.isdir(val):
+ path = val
+ val = self.choose_from_list(os.listdir(path))
+ if val:
+ val = os.path.join(path, val)
+ okay = True
+ else:
+ val = ''
+ else:
+ print 'Invalid value: %s' % val
+ val = ''
+ return val
+
+ def get_int(self, prompt):
+ s = self.get_string(prompt, int_val_fn)
+ return int(s)
+
diff --git a/vendor/boto/boto/mashups/order.py b/vendor/boto/boto/mashups/order.py
new file mode 100644
index 0000000000..6efdc3ecab
--- /dev/null
+++ b/vendor/boto/boto/mashups/order.py
@@ -0,0 +1,211 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+High-level abstraction of an EC2 order for servers
+"""
+
+import boto
+import boto.ec2
+from boto.mashups.server import Server, ServerSet
+from boto.mashups.iobject import IObject
+from boto.pyami.config import Config
+from boto.sdb.persist import get_domain, set_domain
+import time, StringIO
+
+InstanceTypes = ['m1.small', 'm1.large', 'm1.xlarge', 'c1.medium', 'c1.xlarge']
+
+class Item(IObject):
+
+ def __init__(self):
+ self.region = None
+ self.name = None
+ self.instance_type = None
+ self.quantity = 0
+ self.zone = None
+ self.ami = None
+ self.groups = []
+ self.key = None
+ self.ec2 = None
+ self.config = None
+
+ def set_userdata(self, key, value):
+ self.userdata[key] = value
+
+ def get_userdata(self, key):
+ return self.userdata[key]
+
+ def set_region(self, region=None):
+ if region:
+ self.region = region
+ else:
+ l = [(r, r.name, r.endpoint) for r in boto.ec2.regions()]
+ self.region = self.choose_from_list(l, prompt='Choose Region')
+
+ def set_name(self, name=None):
+ if name:
+ self.name = name
+ else:
+ self.name = self.get_string('Name')
+
+ def set_instance_type(self, instance_type=None):
+ if instance_type:
+ self.instance_type = instance_type
+ else:
+ self.instance_type = self.choose_from_list(InstanceTypes, 'Instance Type')
+
+ def set_quantity(self, n=0):
+ if n > 0:
+ self.quantity = n
+ else:
+ self.quantity = self.get_int('Quantity')
+
+ def set_zone(self, zone=None):
+ if zone:
+ self.zone = zone
+ else:
+ l = [(z, z.name, z.state) for z in self.ec2.get_all_zones()]
+ self.zone = self.choose_from_list(l, prompt='Choose Availability Zone')
+
+ def set_ami(self, ami=None):
+ if ami:
+ self.ami = ami
+ else:
+ l = [(a, a.id, a.location) for a in self.ec2.get_all_images()]
+ self.ami = self.choose_from_list(l, prompt='Choose AMI')
+
+ def add_group(self, group=None):
+ if group:
+ self.groups.append(group)
+ else:
+ l = [(s, s.name, s.description) for s in self.ec2.get_all_security_groups()]
+ self.groups.append(self.choose_from_list(l, prompt='Choose Security Group'))
+
+ def set_key(self, key=None):
+ if key:
+ self.key = key
+ else:
+ l = [(k, k.name, '') for k in self.ec2.get_all_key_pairs()]
+ self.key = self.choose_from_list(l, prompt='Choose Keypair')
+
+ def update_config(self):
+ if not self.config.has_section('Credentials'):
+ self.config.add_section('Credentials')
+ self.config.set('Credentials', 'aws_access_key_id', self.ec2.aws_access_key_id)
+ self.config.set('Credentials', 'aws_secret_access_key', self.ec2.aws_secret_access_key)
+ if not self.config.has_section('Pyami'):
+ self.config.add_section('Pyami')
+ sdb_domain = get_domain()
+ if sdb_domain:
+ self.config.set('Pyami', 'server_sdb_domain', sdb_domain)
+ self.config.set('Pyami', 'server_sdb_name', self.name)
+
+ def set_config(self, config_path=None):
+ if not config_path:
+ config_path = self.get_filename('Specify Config file')
+ self.config = Config(path=config_path)
+
+ def get_userdata_string(self):
+ s = StringIO.StringIO()
+ self.config.write(s)
+ return s.getvalue()
+
+ def enter(self, **params):
+ self.region = params.get('region', self.region)
+ if not self.region:
+ self.set_region()
+ self.ec2 = self.region.connect()
+ self.name = params.get('name', self.name)
+ if not self.name:
+ self.set_name()
+ self.instance_type = params.get('instance_type', self.instance_type)
+ if not self.instance_type:
+ self.set_instance_type()
+ self.zone = params.get('zone', self.zone)
+ if not self.zone:
+ self.set_zone()
+ self.quantity = params.get('quantity', self.quantity)
+ if not self.quantity:
+ self.set_quantity()
+ self.ami = params.get('ami', self.ami)
+ if not self.ami:
+ self.set_ami()
+ self.groups = params.get('groups', self.groups)
+ if not self.groups:
+ self.add_group()
+ self.key = params.get('key', self.key)
+ if not self.key:
+ self.set_key()
+ self.config = params.get('config', self.config)
+ if not self.config:
+ self.set_config()
+ self.update_config()
+
+class Order(IObject):
+
+ def __init__(self):
+ self.items = []
+ self.reservation = None
+
+ def add_item(self, **params):
+ item = Item()
+ item.enter(**params)
+ self.items.append(item)
+
+ def display(self):
+ print 'This Order consists of the following items'
+ print
+ print 'QTY\tNAME\tTYPE\nAMI\t\tGroups\t\t\tKeyPair'
+ for item in self.items:
+ print '%s\t%s\t%s\t%s\t%s\t%s' % (item.quantity, item.name, item.instance_type,
+ item.ami.id, item.groups, item.key.name)
+
+ def place(self, block=True):
+ if get_domain() == None:
+ print 'SDB Persistence Domain not set'
+ domain_name = self.get_string('Specify SDB Domain')
+ set_domain(domain_name)
+ s = ServerSet()
+ for item in self.items:
+ r = item.ami.run(min_count=1, max_count=item.quantity,
+ key_name=item.key.name, user_data=item.get_userdata_string(),
+ security_groups=item.groups, instance_type=item.instance_type,
+ placement=item.zone.name)
+ if block:
+ states = [i.state for i in r.instances]
+ if states.count('running') != len(states):
+ print states
+ time.sleep(15)
+ states = [i.update() for i in r.instances]
+ for i in r.instances:
+ server = Server()
+ server.name = item.name
+ server.instance_id = i.id
+ server.reservation = r
+ server.save()
+ s.append(server)
+ if len(s) == 1:
+ return s[0]
+ else:
+ return s
+
+
+
diff --git a/vendor/boto/boto/mashups/server.py b/vendor/boto/boto/mashups/server.py
new file mode 100644
index 0000000000..6cea106c05
--- /dev/null
+++ b/vendor/boto/boto/mashups/server.py
@@ -0,0 +1,395 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+High-level abstraction of an EC2 server
+"""
+import boto
+import boto.utils
+from boto.mashups.iobject import IObject
+from boto.pyami.config import Config, BotoConfigPath
+from boto.mashups.interactive import interactive_shell
+from boto.sdb.db.model import Model
+from boto.sdb.db.property import StringProperty
+import os
+import StringIO
+
+
+class ServerSet(list):
+
+ def __getattr__(self, name):
+ results = []
+ is_callable = False
+ for server in self:
+ try:
+ val = getattr(server, name)
+ if callable(val):
+ is_callable = True
+ results.append(val)
+ except:
+ results.append(None)
+ if is_callable:
+ self.map_list = results
+ return self.map
+ return results
+
+ def map(self, *args):
+ results = []
+ for fn in self.map_list:
+ results.append(fn(*args))
+ return results
+
+class Server(Model):
+
+ @property
+ def ec2(self):
+ if self._ec2 is None:
+ self._ec2 = boto.connect_ec2()
+ return self._ec2
+
+ @classmethod
+ def Inventory(cls):
+ """
+ Returns a list of Server instances, one for each Server object
+ persisted in the db
+ """
+ l = ServerSet()
+ rs = cls.find()
+ for server in rs:
+ l.append(server)
+ return l
+
+ @classmethod
+ def Register(cls, name, instance_id, description=''):
+ s = cls()
+ s.name = name
+ s.instance_id = instance_id
+ s.description = description
+ s.save()
+ return s
+
+ def __init__(self, id=None, **kw):
+ Model.__init__(self, id, **kw)
+ self._reservation = None
+ self._instance = None
+ self._ssh_client = None
+ self._pkey = None
+ self._config = None
+ self._ec2 = None
+
+ name = StringProperty(unique=True, verbose_name="Name")
+ instance_id = StringProperty(verbose_name="Instance ID")
+ config_uri = StringProperty()
+ ami_id = StringProperty(verbose_name="AMI ID")
+ zone = StringProperty(verbose_name="Availability Zone")
+ security_group = StringProperty(verbose_name="Security Group", default="default")
+ key_name = StringProperty(verbose_name="Key Name")
+ elastic_ip = StringProperty(verbose_name="Elastic IP")
+ instance_type = StringProperty(verbose_name="Instance Type")
+ description = StringProperty(verbose_name="Description")
+ log = StringProperty()
+
+ def setReadOnly(self, value):
+ raise AttributeError
+
+ def getInstance(self):
+ if not self._instance:
+ if self.instance_id:
+ try:
+ rs = self.ec2.get_all_instances([self.instance_id])
+ except:
+ return None
+ if len(rs) > 0:
+ self._reservation = rs[0]
+ self._instance = self._reservation.instances[0]
+ return self._instance
+
+ instance = property(getInstance, setReadOnly, None, 'The Instance for the server')
+
+ def getAMI(self):
+ if self.instance:
+ return self.instance.image_id
+
+ ami = property(getAMI, setReadOnly, None, 'The AMI for the server')
+
+ def getStatus(self):
+ if self.instance:
+ self.instance.update()
+ return self.instance.state
+
+ status = property(getStatus, setReadOnly, None,
+ 'The status of the server')
+
+ def getHostname(self):
+ if self.instance:
+ return self.instance.public_dns_name
+
+ hostname = property(getHostname, setReadOnly, None,
+ 'The public DNS name of the server')
+
+ def getPrivateHostname(self):
+ if self.instance:
+ return self.instance.private_dns_name
+
+ private_hostname = property(getPrivateHostname, setReadOnly, None,
+ 'The private DNS name of the server')
+
+ def getLaunchTime(self):
+ if self.instance:
+ return self.instance.launch_time
+
+ launch_time = property(getLaunchTime, setReadOnly, None,
+ 'The time the Server was started')
+
+ def getConsoleOutput(self):
+ if self.instance:
+ return self.instance.get_console_output()
+
+ console_output = property(getConsoleOutput, setReadOnly, None,
+ 'Retrieve the console output for server')
+
+ def getGroups(self):
+ if self._reservation:
+ return self._reservation.groups
+ else:
+ return None
+
+ groups = property(getGroups, setReadOnly, None,
+ 'The Security Groups controlling access to this server')
+
+ def getConfig(self):
+ if not self._config:
+ remote_file = BotoConfigPath
+ local_file = '%s.ini' % self.instance.id
+ self.get_file(remote_file, local_file)
+ self._config = Config(local_file)
+ return self._config
+
+ def setConfig(self, config):
+ local_file = '%s.ini' % self.instance.id
+ fp = open(local_file)
+ config.write(fp)
+ fp.close()
+ self.put_file(local_file, BotoConfigPath)
+ self._config = config
+
+ config = property(getConfig, setConfig, None,
+ 'The instance data for this server')
+
+ def set_config(self, config):
+ """
+ Set SDB based config
+ """
+ self._config = config
+ self._config.dump_to_sdb("botoConfigs", self.id)
+
+ def load_config(self):
+ self._config = Config(do_load=False)
+ self._config.load_from_sdb("botoConfigs", self.id)
+
+ def stop(self):
+ if self.instance:
+ self.instance.stop()
+
+ def start(self):
+ self.stop()
+ ec2 = boto.connect_ec2()
+ ami = ec2.get_all_images(image_ids = [str(self.ami_id)])[0]
+ groups = ec2.get_all_security_groups(groupnames=[str(self.security_group)])
+ if not self._config:
+ self.load_config()
+ if not self._config.has_section("Credentials"):
+ self._config.add_section("Credentials")
+ self._config.set("Credentials", "aws_access_key_id", ec2.aws_access_key_id)
+ self._config.set("Credentials", "aws_secret_access_key", ec2.aws_secret_access_key)
+
+ if not self._config.has_section("Pyami"):
+ self._config.add_section("Pyami")
+
+ if self._manager.domain:
+ self._config.set('Pyami', 'server_sdb_domain', self._manager.domain.name)
+ self._config.set("Pyami", 'server_sdb_name', self.name)
+
+ cfg = StringIO.StringIO()
+ self._config.write(cfg)
+ cfg = cfg.getvalue()
+ r = ami.run(min_count=1,
+ max_count=1,
+ key_name=self.key_name,
+ security_groups = groups,
+ instance_type = self.instance_type,
+ placement = self.zone,
+ user_data = cfg)
+ i = r.instances[0]
+ self.instance_id = i.id
+ self.put()
+ if self.elastic_ip:
+ ec2.associate_address(self.instance_id, self.elastic_ip)
+
+ def reboot(self):
+ if self.instance:
+ self.instance.reboot()
+
+ def get_ssh_client(self, key_file=None, host_key_file='~/.ssh/known_hosts',
+ uname='root'):
+ import paramiko
+ if not self.instance:
+ print 'No instance yet!'
+ return
+ if not self._ssh_client:
+ if not key_file:
+ iobject = IObject()
+ key_file = iobject.get_filename('Path to OpenSSH Key file')
+ self._pkey = paramiko.RSAKey.from_private_key_file(key_file)
+ self._ssh_client = paramiko.SSHClient()
+ self._ssh_client.load_system_host_keys()
+ self._ssh_client.load_host_keys(os.path.expanduser(host_key_file))
+ self._ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ self._ssh_client.connect(self.instance.public_dns_name,
+ username=uname, pkey=self._pkey)
+ return self._ssh_client
+
+ def get_file(self, remotepath, localpath):
+ ssh_client = self.get_ssh_client()
+ sftp_client = ssh_client.open_sftp()
+ sftp_client.get(remotepath, localpath)
+
+ def put_file(self, localpath, remotepath):
+ ssh_client = self.get_ssh_client()
+ sftp_client = ssh_client.open_sftp()
+ sftp_client.put(localpath, remotepath)
+
+ def listdir(self, remotepath):
+ ssh_client = self.get_ssh_client()
+ sftp_client = ssh_client.open_sftp()
+ return sftp_client.listdir(remotepath)
+
+ def shell(self, key_file=None):
+ ssh_client = self.get_ssh_client(key_file)
+ channel = ssh_client.invoke_shell()
+ interactive_shell(channel)
+
+ def bundle_image(self, prefix, key_file, cert_file, size):
+ print 'bundling image...'
+ print '\tcopying cert and pk over to /mnt directory on server'
+ ssh_client = self.get_ssh_client()
+ sftp_client = ssh_client.open_sftp()
+ path, name = os.path.split(key_file)
+ remote_key_file = '/mnt/%s' % name
+ self.put_file(key_file, remote_key_file)
+ path, name = os.path.split(cert_file)
+ remote_cert_file = '/mnt/%s' % name
+ self.put_file(cert_file, remote_cert_file)
+ print '\tdeleting %s' % BotoConfigPath
+ # delete the metadata.ini file if it exists
+ try:
+ sftp_client.remove(BotoConfigPath)
+ except:
+ pass
+ command = 'sudo ec2-bundle-vol '
+ command += '-c %s -k %s ' % (remote_cert_file, remote_key_file)
+ command += '-u %s ' % self._reservation.owner_id
+ command += '-p %s ' % prefix
+ command += '-s %d ' % size
+ command += '-d /mnt '
+ if self.instance.instance_type == 'm1.small' or self.instance_type == 'c1.medium':
+ command += '-r i386'
+ else:
+ command += '-r x86_64'
+ print '\t%s' % command
+ t = ssh_client.exec_command(command)
+ response = t[1].read()
+ print '\t%s' % response
+ print '\t%s' % t[2].read()
+ print '...complete!'
+
+ def upload_bundle(self, bucket, prefix):
+ print 'uploading bundle...'
+ command = 'ec2-upload-bundle '
+ command += '-m /mnt/%s.manifest.xml ' % prefix
+ command += '-b %s ' % bucket
+ command += '-a %s ' % self.ec2.aws_access_key_id
+ command += '-s %s ' % self.ec2.aws_secret_access_key
+ print '\t%s' % command
+ ssh_client = self.get_ssh_client()
+ t = ssh_client.exec_command(command)
+ response = t[1].read()
+ print '\t%s' % response
+ print '\t%s' % t[2].read()
+ print '...complete!'
+
+ def create_image(self, bucket=None, prefix=None, key_file=None, cert_file=None, size=None):
+ iobject = IObject()
+ if not bucket:
+ bucket = iobject.get_string('Name of S3 bucket')
+ if not prefix:
+ prefix = iobject.get_string('Prefix for AMI file')
+ if not key_file:
+ key_file = iobject.get_filename('Path to RSA private key file')
+ if not cert_file:
+ cert_file = iobject.get_filename('Path to RSA public cert file')
+ if not size:
+ size = iobject.get_int('Size (in MB) of bundled image')
+ self.bundle_image(prefix, key_file, cert_file, size)
+ self.upload_bundle(bucket, prefix)
+ print 'registering image...'
+ self.image_id = self.ec2.register_image('%s/%s.manifest.xml' % (bucket, prefix))
+ return self.image_id
+
+ def attach_volume(self, volume, device="/dev/sdp"):
+ """
+ Attach an EBS volume to this server
+
+ :param volume: EBS Volume to attach
+ :type volume: boto.ec2.volume.Volume
+
+ :param device: Device to attach to (default to /dev/sdp)
+ :type device: string
+ """
+ if hasattr(volume, "id"):
+ volume_id = volume.id
+ else:
+ volume_id = volume
+ return self.ec2.attach_volume(volume_id=volume_id, instance_id=self.instance_id, device=device)
+
+ def detach_volume(self, volume):
+ """
+ Detach an EBS volume from this server
+
+ :param volume: EBS Volume to detach
+ :type volume: boto.ec2.volume.Volume
+ """
+ if hasattr(volume, "id"):
+ volume_id = volume.id
+ else:
+ volume_id = volume
+ return self.ec2.detach_volume(volume_id=volume_id, instance_id=self.instance_id)
+
+ def install_package(self, package_name):
+ print 'installing %s...' % package_name
+ command = 'yum -y install %s' % package_name
+ print '\t%s' % command
+ ssh_client = self.get_ssh_client()
+ t = ssh_client.exec_command(command)
+ response = t[1].read()
+ print '\t%s' % response
+ print '\t%s' % t[2].read()
+ print '...complete!'
diff --git a/vendor/boto/boto/mturk/__init__.py b/vendor/boto/boto/mturk/__init__.py
new file mode 100644
index 0000000000..449bd162a8
--- /dev/null
+++ b/vendor/boto/boto/mturk/__init__.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+
diff --git a/vendor/boto/boto/mturk/connection.py b/vendor/boto/boto/mturk/connection.py
new file mode 100644
index 0000000000..f064554518
--- /dev/null
+++ b/vendor/boto/boto/mturk/connection.py
@@ -0,0 +1,515 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import xml.sax
+import datetime
+
+from boto import handler
+from boto.mturk.price import Price
+import boto.mturk.notification
+from boto.connection import AWSQueryConnection
+from boto.exception import EC2ResponseError
+from boto.resultset import ResultSet
+
+class MTurkConnection(AWSQueryConnection):
+
+ APIVersion = '2008-08-02'
+ SignatureVersion = '1'
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=False, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, host='mechanicalturk.amazonaws.com', debug=0,
+ https_connection_factory=None):
+ AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_key,
+ is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
+ host, debug, https_connection_factory)
+
+ def get_account_balance(self):
+ """
+ """
+ params = {}
+ return self._process_request('GetAccountBalance', params, [('AvailableBalance', Price),
+ ('OnHoldBalance', Price)])
+
+ def register_hit_type(self, title, description, reward, duration,
+ keywords=None, approval_delay=None, qual_req=None):
+ """
+ Register a new HIT Type
+ \ttitle, description are strings
+ \treward is a Price object
+ \tduration can be an integer or string
+ """
+ params = {'Title' : title,
+ 'Description' : description,
+ 'AssignmentDurationInSeconds' : duration}
+ params.update(MTurkConnection.get_price_as_price(reward).get_as_params('Reward'))
+
+ if keywords:
+ params['Keywords'] = keywords
+
+ if approval_delay is not None:
+ params['AutoApprovalDelayInSeconds']= approval_delay
+
+ return self._process_request('RegisterHITType', params)
+
+ def set_email_notification(self, hit_type, email, event_types=None):
+ """
+ Performs a SetHITTypeNotification operation to set email notification for a specified HIT type
+ """
+ return self._set_notification(hit_type, 'Email', email, event_types)
+
+ def set_rest_notification(self, hit_type, url, event_types=None):
+ """
+ Performs a SetHITTypeNotification operation to set REST notification for a specified HIT type
+ """
+ return self._set_notification(hit_type, 'REST', url, event_types)
+
+ def _set_notification(self, hit_type, transport, destination, event_types=None):
+ """
+ Common SetHITTypeNotification operation to set notification for a specified HIT type
+ """
+ assert type(hit_type) is str, "hit_type argument should be a string."
+
+ params = {'HITTypeId': hit_type}
+
+ # from the Developer Guide:
+ # The 'Active' parameter is optional. If omitted, the active status of the HIT type's
+ # notification specification is unchanged. All HIT types begin with their
+ # notification specifications in the "inactive" status.
+ notification_params = {'Destination': destination,
+ 'Transport': transport,
+ 'Version': boto.mturk.notification.NotificationMessage.NOTIFICATION_VERSION,
+ 'Active': True,
+ }
+
+ # add specific event types if required
+ if event_types:
+ self.build_list_params(notification_params, event_types, 'EventType')
+
+ # Set up dict of 'Notification.1.Transport' etc. values
+ notification_rest_params = {}
+ num = 1
+ for key in notification_params:
+ notification_rest_params['Notification.%d.%s' % (num, key)] = notification_params[key]
+
+ # Update main params dict
+ params.update(notification_rest_params)
+
+ # Execute operation
+ return self._process_request('SetHITTypeNotification', params)
+
+ def create_hit(self, hit_type=None, question=None, lifetime=60*60*24*7, max_assignments=1,
+ title=None, description=None, keywords=None, reward=None,
+ duration=60*60*24*7, approval_delay=None, annotation=None, qual_req=None,
+ questions=None, qualifications=None, response_groups=None):
+ """
+ Creates a new HIT.
+ Returns a ResultSet
+ See: http://docs.amazonwebservices.com/AWSMechanicalTurkRequester/2006-10-31/ApiReference_CreateHITOperation.html
+ """
+
+ # handle single or multiple questions
+ if question is not None and questions is not None:
+ raise ValueError("Must specify either question (single Question instance) or questions (list), but not both")
+ if question is not None and questions is None:
+ questions = [question]
+
+
+ # Handle basic required arguments and set up params dict
+ params = {'Question': question.get_as_xml(),
+ 'LifetimeInSeconds' : lifetime,
+ 'MaxAssignments' : max_assignments,
+ }
+
+ # if hit type specified then add it
+ # else add the additional required parameters
+ if hit_type:
+ params['HITTypeId'] = hit_type
+ else:
+ # Handle keywords
+ final_keywords = MTurkConnection.get_keywords_as_string(keywords)
+
+ # Handle price argument
+ final_price = MTurkConnection.get_price_as_price(reward)
+
+ additional_params = {'Title': title,
+ 'Description' : description,
+ 'Keywords': final_keywords,
+ 'AssignmentDurationInSeconds' : duration,
+ }
+ additional_params.update(final_price.get_as_params('Reward'))
+
+ if approval_delay is not None:
+ additional_params['AutoApprovalDelayInSeconds'] = approval_delay
+
+ # add these params to the others
+ params.update(additional_params)
+
+ # add the annotation if specified
+ if annotation is not None:
+ params['RequesterAnnotation'] = annotation
+
+ # Add the Qualifications if specified
+ if qualifications is not None:
+ params.update(qualifications.get_as_params())
+
+ # Handle optional response groups argument
+ if response_groups:
+ self.build_list_params(params, response_groups, 'ResponseGroup')
+
+ # Submit
+ return self._process_request('CreateHIT', params, [('HIT', HIT),])
+
+ def get_reviewable_hits(self, hit_type=None, status='Reviewable',
+ sort_by='Expiration', sort_direction='Ascending',
+ page_size=10, page_number=1):
+ """
+ Retrieve the HITs that have a status of Reviewable, or HITs that
+ have a status of Reviewing, and that belong to the Requester calling the operation.
+ """
+ params = {'Status' : status,
+ 'SortProperty' : sort_by,
+ 'SortDirection' : sort_direction,
+ 'PageSize' : page_size,
+ 'PageNumber' : page_number}
+
+ # Handle optional hit_type argument
+ if hit_type is not None:
+ params.update({'HITTypeId': hit_type})
+
+ return self._process_request('GetReviewableHITs', params, [('HIT', HIT),])
+
+ def search_hits(self, sort_by='CreationTime', sort_direction='Ascending',
+ page_size=10, page_number=1):
+ """
+ Return all of a Requester's HITs, on behalf of the Requester.
+ The operation returns HITs of any status, except for HITs that have been disposed
+ with the DisposeHIT operation.
+ Note:
+ The SearchHITs operation does not accept any search parameters that filter the results.
+ """
+ params = {'SortProperty' : sort_by,
+ 'SortDirection' : sort_direction,
+ 'PageSize' : page_size,
+ 'PageNumber' : page_number}
+
+ return self._process_request('SearchHITs', params, [('HIT', HIT),])
+
+ def get_assignments(self, hit_id, status=None,
+ sort_by='SubmitTime', sort_direction='Ascending',
+ page_size=10, page_number=1):
+ """
+ Retrieves completed assignments for a HIT.
+ Use this operation to retrieve the results for a HIT.
+
+ The returned ResultSet will have the following attributes:
+
+ NumResults
+ The number of assignments on the page in the filtered results list,
+ equivalent to the number of assignments being returned by this call.
+ A non-negative integer
+ PageNumber
+ The number of the page in the filtered results list being returned.
+ A positive integer
+ TotalNumResults
+ The total number of HITs in the filtered results list based on this call.
+ A non-negative integer
+
+ The ResultSet will contain zero or more Assignment objects
+
+ """
+ params = {'HITId' : hit_id,
+ 'SortProperty' : sort_by,
+ 'SortDirection' : sort_direction,
+ 'PageSize' : page_size,
+ 'PageNumber' : page_number}
+
+ if status is not None:
+ params['AssignmentStatus'] = status
+
+ return self._process_request('GetAssignmentsForHIT', params, [('Assignment', Assignment),])
+
+ def approve_assignment(self, assignment_id, feedback=None):
+ """
+ """
+ params = {'AssignmentId' : assignment_id,}
+ if feedback:
+ params['RequesterFeedback'] = feedback
+ return self._process_request('ApproveAssignment', params)
+
+ def reject_assignment(self, assignment_id, feedback=None):
+ """
+ """
+ params = {'AssignmentId' : assignment_id,}
+ if feedback:
+ params['RequesterFeedback'] = feedback
+ return self._process_request('RejectAssignment', params)
+
+ def get_hit(self, hit_id):
+ """
+ """
+ params = {'HITId' : hit_id,}
+ return self._process_request('GetHIT', params, [('HIT', HIT),])
+
+ def set_reviewing(self, hit_id, revert=None):
+ """
+ Update a HIT with a status of Reviewable to have a status of Reviewing,
+ or reverts a Reviewing HIT back to the Reviewable status.
+
+ Only HITs with a status of Reviewable can be updated with a status of Reviewing.
+ Similarly, only Reviewing HITs can be reverted back to a status of Reviewable.
+ """
+ params = {'HITId' : hit_id,}
+ if revert:
+ params['Revert'] = revert
+ return self._process_request('SetHITAsReviewing', params)
+
+ def disable_hit(self, hit_id):
+ """
+ Remove a HIT from the Mechanical Turk marketplace, approves all submitted assignments
+ that have not already been approved or rejected, and disposes of the HIT and all
+ assignment data.
+
+ Assignments for the HIT that have already been submitted, but not yet approved or rejected, will be
+ automatically approved. Assignments in progress at the time of the call to DisableHIT will be
+ approved once the assignments are submitted. You will be charged for approval of these assignments.
+ DisableHIT completely disposes of the HIT and all submitted assignment data. Assignment results
+ data cannot be retrieved for a HIT that has been disposed.
+
+ It is not possible to re-enable a HIT once it has been disabled. To make the work from a disabled HIT
+ available again, create a new HIT.
+ """
+ params = {'HITId' : hit_id,}
+ return self._process_request('DisableHIT', params)
+
+ def dispose_hit(self, hit_id):
+ """
+ Dispose of a HIT that is no longer needed.
+
+ Only HITs in the "reviewable" state, with all submitted assignments approved or rejected,
+ can be disposed. A Requester can call GetReviewableHITs to determine which HITs are
+ reviewable, then call GetAssignmentsForHIT to retrieve the assignments.
+ Disposing of a HIT removes the HIT from the results of a call to GetReviewableHITs.
+ """
+ params = {'HITId' : hit_id,}
+ return self._process_request('DisposeHIT', params)
+
+ def expire_hit(self, hit_id):
+
+ """
+ Expire a HIT that is no longer needed.
+
+ The effect is identical to the HIT expiring on its own. The HIT no longer appears on the
+ Mechanical Turk web site, and no new Workers are allowed to accept the HIT. Workers who
+ have accepted the HIT prior to expiration are allowed to complete it or return it, or
+ allow the assignment duration to elapse (abandon the HIT). Once all remaining assignments
+ have been submitted, the expired HIT becomes "reviewable", and will be returned by a call
+ to GetReviewableHITs.
+ """
+ params = {'HITId' : hit_id,}
+ return self._process_request('ForceExpireHIT', params)
+
+ def extend_hit(self, hit_id, assignments_increment=None, expiration_increment=None):
+ """
+ Increase the maximum number of assignments, or extend the expiration date, of an existing HIT.
+
+ NOTE: If a HIT has a status of Reviewable and the HIT is extended to make it Available, the
+ HIT will not be returned by GetReviewableHITs, and its submitted assignments will not
+ be returned by GetAssignmentsForHIT, until the HIT is Reviewable again.
+ Assignment auto-approval will still happen on its original schedule, even if the HIT has
+ been extended. Be sure to retrieve and approve (or reject) submitted assignments before
+ extending the HIT, if so desired.
+ """
+ # must provide assignment *or* expiration increment
+ if (assignments_increment is None and expiration_increment is None) or \
+ (assignments_increment is not None and expiration_increment is not None):
+ raise ValueError("Must specify either assignments_increment or expiration_increment, but not both")
+
+ params = {'HITId' : hit_id,}
+ if assignments_increment:
+ params['MaxAssignmentsIncrement'] = assignments_increment
+ if expiration_increment:
+ params['ExpirationIncrementInSeconds'] = expiration_increment
+
+ return self._process_request('ExtendHIT', params)
+
+ def get_help(self, about, help_type='Operation'):
+ """
+ Return information about the Mechanical Turk Service operations and response group
+ NOTE - this is basically useless as it just returns the URL of the documentation
+
+ help_type: either 'Operation' or 'ResponseGroup'
+ """
+ params = {'About': about, 'HelpType': help_type,}
+ return self._process_request('Help', params)
+
+ def grant_bonus(self, worker_id, assignment_id, bonus_price, reason):
+ """
+ Issues a payment of money from your account to a Worker.
+ To be eligible for a bonus, the Worker must have submitted results for one of your
+ HITs, and have had those results approved or rejected. This payment happens separately
+ from the reward you pay to the Worker when you approve the Worker's assignment.
+ The Bonus must be passed in as an instance of the Price object.
+ """
+ params = bonus_price.get_as_params('BonusAmount', 1)
+ params['WorkerId'] = worker_id
+ params['AssignmentId'] = assignment_id
+ params['Reason'] = reason
+
+ return self._process_request('GrantBonus', params)
+
+ def _process_request(self, request_type, params, marker_elems=None):
+ """
+ Helper to process the xml response from AWS
+ """
+ response = self.make_request(request_type, params)
+ return self._process_response(response, marker_elems)
+
+ def _process_response(self, response, marker_elems=None):
+ """
+ Helper to process the xml response from AWS
+ """
+ body = response.read()
+ #print body
+ if '<Errors>' not in body:
+ rs = ResultSet(marker_elems)
+ h = handler.XmlHandler(rs, self)
+ xml.sax.parseString(body, h)
+ return rs
+ else:
+ raise EC2ResponseError(response.status, response.reason, body)
+
+ @staticmethod
+ def get_keywords_as_string(keywords):
+ """
+ Returns a comma+space-separated string of keywords from either a list or a string
+ """
+ if type(keywords) is list:
+ final_keywords = ', '.join(keywords)
+ elif type(keywords) is str:
+ final_keywords = keywords
+ elif type(keywords) is unicode:
+ final_keywords = keywords.encode('utf-8')
+ elif keywords is None:
+ final_keywords = ""
+ else:
+ raise TypeError("keywords argument must be a string or a list of strings; got a %s" % type(keywords))
+ return final_keywords
+
+ @staticmethod
+ def get_price_as_price(reward):
+ """
+ Returns a Price data structure from either a float or a Price
+ """
+ if isinstance(reward, Price):
+ final_price = reward
+ else:
+ final_price = Price(reward)
+ return final_price
+
+class BaseAutoResultElement:
+ """
+ Base class to automatically add attributes when parsing XML
+ """
+ def __init__(self, connection):
+ self.connection = connection
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ setattr(self, name, value)
+
+class HIT(BaseAutoResultElement):
+ """
+ Class to extract a HIT structure from a response (used in ResultSet)
+
+ Will have attributes named as per the Developer Guide,
+ e.g. HITId, HITTypeId, CreationTime
+ """
+
+ # property helper to determine if HIT has expired
+ def _has_expired(self):
+ """ Has this HIT expired yet? """
+ expired = False
+ if hasattr(self, 'Expiration'):
+ now = datetime.datetime.utcnow()
+ expiration = datetime.datetime.strptime(self.Expiration, '%Y-%m-%dT%H:%M:%SZ')
+ expired = (now >= expiration)
+ else:
+ raise ValueError("ERROR: Request for expired property, but no Expiration in HIT!")
+ return expired
+
+ # are we there yet?
+ expired = property(_has_expired)
+
+class Assignment(BaseAutoResultElement):
+ """
+ Class to extract an Assignment structure from a response (used in ResultSet)
+
+ Will have attributes named as per the Developer Guide,
+ e.g. AssignmentId, WorkerId, HITId, Answer, etc
+ """
+
+ def __init__(self, connection):
+ BaseAutoResultElement.__init__(self, connection)
+ self.answers = []
+
+ def endElement(self, name, value, connection):
+ # the answer consists of embedded XML, so it needs to be parsed independantly
+ if name == 'Answer':
+ answer_rs = ResultSet([('Answer', QuestionFormAnswer),])
+ h = handler.XmlHandler(answer_rs, connection)
+ value = self.connection.get_utf8_value(value)
+ xml.sax.parseString(value, h)
+ self.answers.append(answer_rs)
+ else:
+ BaseAutoResultElement.endElement(self, name, value, connection)
+
+class QuestionFormAnswer(BaseAutoResultElement):
+ """
+ Class to extract Answers from inside the embedded XML QuestionFormAnswers element inside the
+ Answer element which is part of the Assignment structure
+
+ A QuestionFormAnswers element contains an Answer element for each question in the HIT or
+ Qualification test for which the Worker provided an answer. Each Answer contains a
+ QuestionIdentifier element whose value corresponds to the QuestionIdentifier of a
+ Question in the QuestionForm. See the QuestionForm data structure for more information about
+ questions and answer specifications.
+
+ If the question expects a free-text answer, the Answer element contains a FreeText element. This
+ element contains the Worker's answer
+
+ *NOTE* - currently really only supports free-text answers
+ """
+
+ def __init__(self, connection):
+ BaseAutoResultElement.__init__(self, connection)
+ self.fields = []
+ self.qid = None
+
+ def endElement(self, name, value, connection):
+ if name == 'QuestionIdentifier':
+ self.qid = value
+ elif name == 'FreeText' and self.qid:
+ self.fields.append((self.qid,value))
+ elif name == 'Answer':
+ self.qid = None
diff --git a/vendor/boto/boto/mturk/notification.py b/vendor/boto/boto/mturk/notification.py
new file mode 100644
index 0000000000..4904a99825
--- /dev/null
+++ b/vendor/boto/boto/mturk/notification.py
@@ -0,0 +1,95 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Provides NotificationMessage and Event classes, with utility methods, for
+implementations of the Mechanical Turk Notification API.
+"""
+
+import hmac
+try:
+ from hashlib import sha1 as sha
+except ImportError:
+ import sha
+import base64
+import re
+
+class NotificationMessage:
+
+ NOTIFICATION_WSDL = "http://mechanicalturk.amazonaws.com/AWSMechanicalTurk/2006-05-05/AWSMechanicalTurkRequesterNotification.wsdl"
+ NOTIFICATION_VERSION = '2006-05-05'
+
+ SERVICE_NAME = "AWSMechanicalTurkRequesterNotification"
+ OPERATION_NAME = "Notify"
+
+ EVENT_PATTERN = r"Event\.(?P<n>\d+)\.(?P<param>\w+)"
+ EVENT_RE = re.compile(EVENT_PATTERN)
+
+ def __init__(self, d):
+ """
+ Constructor; expects parameter d to be a dict of string parameters from a REST transport notification message
+ """
+ self.signature = d['Signature'] # vH6ZbE0NhkF/hfNyxz2OgmzXYKs=
+ self.timestamp = d['Timestamp'] # 2006-05-23T23:22:30Z
+ self.version = d['Version'] # 2006-05-05
+ assert d['method'] == NotificationMessage.OPERATION_NAME, "Method should be '%s'" % NotificationMessage.OPERATION_NAME
+
+ # Build Events
+ self.events = []
+ events_dict = {}
+ if 'Event' in d:
+ # TurboGears surprised me by 'doing the right thing' and making { 'Event': { '1': { 'EventType': ... } } } etc.
+ events_dict = d['Event']
+ else:
+ for k in d:
+ v = d[k]
+ if k.startswith('Event.'):
+ ed = NotificationMessage.EVENT_RE.search(k).groupdict()
+ n = int(ed['n'])
+ param = str(ed['param'])
+ if n not in events_dict:
+ events_dict[n] = {}
+ events_dict[n][param] = v
+ for n in events_dict:
+ self.events.append(Event(events_dict[n]))
+
+ def verify(self, secret_key):
+ """
+ Verifies the authenticity of a notification message.
+ """
+ verification_input = NotificationMessage.SERVICE_NAME + NotificationMessage.OPERATION_NAME + self.timestamp
+ h = hmac.new(key=secret_key, digestmod=sha)
+ h.update(verification_input)
+ signature_calc = base64.b64encode(h.digest())
+ return self.signature == signature_calc
+
+class Event:
+ def __init__(self, d):
+ self.event_type = d['EventType']
+ self.event_time_str = d['EventTime']
+ self.hit_type = d['HITTypeId']
+ self.hit_id = d['HITId']
+ self.assignment_id = d['AssignmentId']
+
+ #TODO: build self.event_time datetime from string self.event_time_str
+
+ def __repr__(self):
+ return "<boto.mturk.notification.Event: %s for HIT # %s>" % (self.event_type, self.hit_id)
diff --git a/vendor/boto/boto/mturk/price.py b/vendor/boto/boto/mturk/price.py
new file mode 100644
index 0000000000..3c88a96549
--- /dev/null
+++ b/vendor/boto/boto/mturk/price.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Price:
+
+ def __init__(self, amount=0.0, currency_code='USD'):
+ self.amount = amount
+ self.currency_code = currency_code
+ self.formatted_price = ''
+
+ def __repr__(self):
+ if self.formatted_price:
+ return self.formatted_price
+ else:
+ return str(self.amount)
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Amount':
+ self.amount = float(value)
+ elif name == 'CurrencyCode':
+ self.currency_code = value
+ elif name == 'FormattedPrice':
+ self.formatted_price = value
+
+ def get_as_params(self, label, ord=1):
+ return {'%s.%d.Amount'%(label, ord) : str(self.amount),
+ '%s.%d.CurrencyCode'%(label, ord) : self.currency_code}
diff --git a/vendor/boto/boto/mturk/qualification.py b/vendor/boto/boto/mturk/qualification.py
new file mode 100644
index 0000000000..ed02087f01
--- /dev/null
+++ b/vendor/boto/boto/mturk/qualification.py
@@ -0,0 +1,118 @@
+# Copyright (c) 2008 Chris Moyer http://coredumped.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Qualifications:
+
+ def __init__(self, requirements = []):
+ self.requirements = requirements
+
+ def add(self, req):
+ self.requirements.append(req)
+
+ def get_as_params(self):
+ params = {}
+ assert(len(self.requirements) <= 10)
+ for n, req in enumerate(self.requirements):
+ reqparams = req.get_as_params()
+ for rp in reqparams:
+ params['QualificationRequirement.%s.%s' % ((n+1),rp) ] = reqparams[rp]
+ return params
+
+
+class Requirement(object):
+ """
+ Representation of a single requirement
+ """
+
+ def __init__(self, qualification_type_id, comparator, integer_value, required_to_preview=False):
+ self.qualification_type_id = qualification_type_id
+ self.comparator = comparator
+ self.integer_value = integer_value
+ self.required_to_preview = required_to_preview
+
+ def get_as_params(self):
+ params = {
+ "QualificationTypeId": self.qualification_type_id,
+ "Comparator": self.comparator,
+ "IntegerValue": self.integer_value,
+ }
+ if self.required_to_preview:
+ params['RequiredToPreview'] = "true"
+ return params
+
+class PercentAssignmentsSubmittedRequirement(Requirement):
+ """
+ The percentage of assignments the Worker has submitted, over all assignments the Worker has accepted. The value is an integer between 0 and 100.
+ """
+
+ def __init__(self, comparator, integer_value, required_to_preview=False):
+ Requirement.__init__(self, qualification_type_id="00000000000000000000", comparator=comparator, integer_value=integer_value, required_to_preview=required_to_preview)
+
+class PercentAssignmentsAbandonedRequirement(Requirement):
+ """
+ The percentage of assignments the Worker has abandoned (allowed the deadline to elapse), over all assignments the Worker has accepted. The value is an integer between 0 and 100.
+ """
+
+ def __init__(self, comparator, integer_value, required_to_preview=False):
+ Requirement.__init__(self, qualification_type_id="00000000000000000070", comparator=comparator, integer_value=integer_value, required_to_preview=required_to_preview)
+
+class PercentAssignmentsReturnedRequirement(Requirement):
+ """
+ The percentage of assignments the Worker has returned, over all assignments the Worker has accepted. The value is an integer between 0 and 100.
+ """
+
+ def __init__(self, comparator, integer_value, required_to_preview=False):
+ Requirement.__init__(self, qualification_type_id="000000000000000000E0", comparator=comparator, integer_value=integer_value, required_to_preview=required_to_preview)
+
+class PercentAssignmentsApprovedRequirement(Requirement):
+ """
+ The percentage of assignments the Worker has submitted that were subsequently approved by the Requester, over all assignments the Worker has submitted. The value is an integer between 0 and 100.
+ """
+
+ def __init__(self, comparator, integer_value, required_to_preview=False):
+ Requirement.__init__(self, qualification_type_id="000000000000000000L0", comparator=comparator, integer_value=integer_value, required_to_preview=required_to_preview)
+
+class PercentAssignmentsRejectedRequirement(Requirement):
+ """
+ The percentage of assignments the Worker has submitted that were subsequently rejected by the Requester, over all assignments the Worker has submitted. The value is an integer between 0 and 100.
+ """
+
+ def __init__(self, comparator, integer_value, required_to_preview=False):
+ Requirement.__init__(self, qualification_type_id="000000000000000000S0", comparator=comparator, integer_value=integer_value, required_to_preview=required_to_preview)
+
+class LocaleRequirement(Requirement):
+ """
+ A Qualification requirement based on the Worker's location. The Worker's location is specified by the Worker to Mechanical Turk when the Worker creates his account.
+ """
+
+ def __init__(self, comparator, locale, required_to_preview=False):
+ Requirement.__init__(self, qualification_type_id="00000000000000000071", comparator=comparator, integer_value=None, required_to_preview=required_to_preview)
+ self.locale = locale
+
+ def get_as_params(self):
+ params = {
+ "QualificationTypeId": self.qualification_type_id,
+ "Comparator": self.comparator,
+ 'LocaleValue.Country': self.locale,
+ }
+ if self.required_to_preview:
+ params['RequiredToPreview'] = "true"
+ return params
diff --git a/vendor/boto/boto/mturk/question.py b/vendor/boto/boto/mturk/question.py
new file mode 100644
index 0000000000..d4d9734f9a
--- /dev/null
+++ b/vendor/boto/boto/mturk/question.py
@@ -0,0 +1,336 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Question(object):
+
+ QUESTION_XML_TEMPLATE = """<Question><QuestionIdentifier>%s</QuestionIdentifier>%s<IsRequired>%s</IsRequired>%s%s</Question>"""
+ DISPLAY_NAME_XML_TEMPLATE = """<DisplayName>%s</DisplayName>"""
+
+ def __init__(self, identifier, content, answer_spec, is_required=False, display_name=None): #amount=0.0, currency_code='USD'):
+ self.identifier = identifier
+ self.content = content
+ self.answer_spec = answer_spec
+ self.is_required = is_required
+ self.display_name = display_name
+
+ def get_as_params(self, label='Question', identifier=None):
+
+ if identifier is None:
+ raise ValueError("identifier (QuestionIdentifier) is required per MTurk spec.")
+
+ return { label : self.get_as_xml() }
+
+ def get_as_xml(self):
+ # add the display name if required
+ display_name_xml = ''
+ if self.display_name:
+ display_name_xml = self.DISPLAY_NAME_XML_TEMPLATE %(self.display_name)
+
+ ret = Question.QUESTION_XML_TEMPLATE % (self.identifier,
+ display_name_xml,
+ str(self.is_required).lower(),
+ self.content.get_as_xml(),
+ self.answer_spec.get_as_xml())
+
+ return ret
+
+class ExternalQuestion(object):
+
+ EXTERNAL_QUESTIONFORM_SCHEMA_LOCATION = "http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd"
+ EXTERNAL_QUESTION_XML_TEMPLATE = """<ExternalQuestion xmlns="%s"><ExternalURL>%s</ExternalURL><FrameHeight>%s</FrameHeight></ExternalQuestion>"""
+
+ def __init__(self, external_url, frame_height):
+ self.external_url = external_url
+ self.frame_height = frame_height
+
+ def get_as_params(self, label='ExternalQuestion'):
+ return { label : self.get_as_xml() }
+
+ def get_as_xml(self):
+ ret = ExternalQuestion.EXTERNAL_QUESTION_XML_TEMPLATE % (ExternalQuestion.EXTERNAL_QUESTIONFORM_SCHEMA_LOCATION,
+ self.external_url,
+ self.frame_height)
+ return ret
+
+class OrderedContent(object):
+ def __init__(self):
+ self.items = []
+
+ def append(self, field, value):
+ "Expects field type and value"
+ self.items.append((field, value))
+
+ def get_binary_xml(self, field, value):
+ return """
+<Binary>
+ <MimeType>
+ <Type>%s</Type>
+ <SubType>%s</SubType>
+ </MimeType>
+ <DataURL>%s</DataURL>
+ <AltText>%s</AltText>
+</Binary>""" % (value['type'],
+ value['subtype'],
+ value['dataurl'],
+ value['alttext'])
+
+ def get_application_xml(self, field, value):
+ raise NotImplementedError("Application question content is not yet supported.")
+
+ def get_as_xml(self):
+ default_handler = lambda f,v: '<%s>%s</%s>' % (f,v,f)
+ bulleted_list_handler = lambda _,list: '<List>%s</List>' % ''.join([('<ListItem>%s</ListItem>' % item) for item in list])
+ formatted_content_handler = lambda _,content: "<FormattedContent><![CDATA[%s]]></FormattedContent>" % content
+ application_handler = self.get_application_xml
+ binary_handler = self.get_binary_xml
+
+ children = ''
+ for (field,value) in self.items:
+ handler = default_handler
+ if field == 'List':
+ handler = bulleted_list_handler
+ elif field == 'Application':
+ handler = application_handler
+ elif field == 'Binary':
+ handler = binary_handler
+ elif field == 'FormattedContent':
+ handler = formatted_content_handler
+ children = children + handler(field, value)
+
+ return children
+
+class Overview(object):
+ OVERVIEW_XML_TEMPLATE = """<Overview>%s</Overview>"""
+
+ def __init__(self):
+ self.ordered_content = OrderedContent()
+
+ def append(self, field, value):
+ self.ordered_content.append(field,value)
+
+ def get_as_params(self, label='Overview'):
+ return { label : self.get_as_xml() }
+
+ def get_as_xml(self):
+ ret = Overview.OVERVIEW_XML_TEMPLATE % (self.ordered_content.get_as_xml())
+
+ return ret
+
+
+class QuestionForm(object):
+
+ QUESTIONFORM_SCHEMA_LOCATION = "http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionForm.xsd"
+ QUESTIONFORM_XML_TEMPLATE = """<QuestionForm xmlns="%s">%s</QuestionForm>"""
+
+ def __init__(self):
+ self.items = []
+
+ def append(self, item):
+ "Expects field type and value"
+ self.items.append(item)
+
+ def get_as_xml(self):
+ xml = ''
+ for item in self.items:
+ xml = xml + item.get_as_xml()
+ return QuestionForm.QUESTIONFORM_XML_TEMPLATE % (QuestionForm.QUESTIONFORM_SCHEMA_LOCATION, xml)
+
+class QuestionContent(object):
+ QUESTIONCONTENT_XML_TEMPLATE = """<QuestionContent>%s</QuestionContent>"""
+
+ def __init__(self):
+ self.ordered_content = OrderedContent()
+
+ def append(self, field, value):
+ self.ordered_content.append(field,value)
+
+ def get_as_xml(self):
+ ret = QuestionContent.QUESTIONCONTENT_XML_TEMPLATE % (self.ordered_content.get_as_xml())
+
+ return ret
+
+
+class AnswerSpecification(object):
+
+ ANSWERSPECIFICATION_XML_TEMPLATE = """<AnswerSpecification>%s</AnswerSpecification>"""
+
+ def __init__(self, spec):
+ self.spec = spec
+ def get_as_xml(self):
+ values = () # TODO
+ return AnswerSpecification.ANSWERSPECIFICATION_XML_TEMPLATE % self.spec.get_as_xml()
+
+class FreeTextAnswer(object):
+
+ FREETEXTANSWER_XML_TEMPLATE = """<FreeTextAnswer>%s%s</FreeTextAnswer>""" # (constraints, default)
+ FREETEXTANSWER_CONSTRAINTS_XML_TEMPLATE = """<Constraints>%s%s%s</Constraints>""" # (is_numeric_xml, length_xml, regex_xml)
+ FREETEXTANSWER_LENGTH_XML_TEMPLATE = """<Length %s %s />""" # (min_length_attr, max_length_attr)
+ FREETEXTANSWER_ISNUMERIC_XML_TEMPLATE = """<IsNumeric %s %s />""" # (min_value_attr, max_value_attr)
+ FREETEXTANSWER_DEFAULTTEXT_XML_TEMPLATE = """<DefaultText>%s</DefaultText>""" # (default)
+
+ def __init__(self, default=None, min_length=None, max_length=None, is_numeric=False, min_value=None, max_value=None, format_regex=None):
+ self.default = default
+ self.min_length = min_length
+ self.max_length = max_length
+ self.is_numeric = is_numeric
+ self.min_value = min_value
+ self.max_value = max_value
+ self.format_regex = format_regex
+
+ def get_as_xml(self):
+ is_numeric_xml = ""
+ if self.is_numeric:
+ min_value_attr = ""
+ max_value_attr = ""
+ if self.min_value:
+ min_value_attr = """minValue="%d" """ % self.min_value
+ if self.max_value:
+ max_value_attr = """maxValue="%d" """ % self.max_value
+ is_numeric_xml = FreeTextAnswer.FREETEXTANSWER_ISNUMERIC_XML_TEMPLATE % (min_value_attr, max_value_attr)
+
+ length_xml = ""
+ if self.min_length or self.max_length:
+ min_length_attr = ""
+ max_length_attr = ""
+ if self.min_length:
+ min_length_attr = """minLength="%d" """
+ if self.max_length:
+ max_length_attr = """maxLength="%d" """
+ length_xml = FreeTextAnswer.FREETEXTANSWER_LENGTH_XML_TEMPLATE % (min_length_attr, max_length_attr)
+
+ regex_xml = ""
+ if self.format_regex:
+ format_regex_attribs = '''regex="%s"''' %self.format_regex['regex']
+
+ error_text = self.format_regex.get('error_text', None)
+ if error_text:
+ format_regex_attribs += ' errorText="%s"' %error_text
+
+ flags = self.format_regex.get('flags', None)
+ if flags:
+ format_regex_attribs += ' flags="%s"' %flags
+
+ regex_xml = """<AnswerFormatRegex %s/>""" %format_regex_attribs
+
+ constraints_xml = ""
+ if is_numeric_xml or length_xml or regex_xml:
+ constraints_xml = FreeTextAnswer.FREETEXTANSWER_CONSTRAINTS_XML_TEMPLATE % (is_numeric_xml, length_xml, regex_xml)
+
+ default_xml = ""
+ if self.default is not None:
+ default_xml = FreeTextAnswer.FREETEXTANSWER_DEFAULTTEXT_XML_TEMPLATE % self.default
+
+ return FreeTextAnswer.FREETEXTANSWER_XML_TEMPLATE % (constraints_xml, default_xml)
+
+class FileUploadAnswer(object):
+ FILEUPLOADANSWER_XML_TEMLPATE = """<FileUploadAnswer><MinFileSizeInBytes>%d</MinFileSizeInBytes><MaxFileSizeInBytes>%d</MaxFileSizeInBytes></FileUploadAnswer>""" # (min, max)
+ DEFAULT_MIN_SIZE = 1024 # 1K (completely arbitrary!)
+ DEFAULT_MAX_SIZE = 5 * 1024 * 1024 # 5MB (completely arbitrary!)
+
+ def __init__(self, min=None, max=None):
+ self.min = min
+ self.max = max
+ if self.min is None:
+ self.min = FileUploadAnswer.DEFAULT_MIN_SIZE
+ if self.max is None:
+ self.max = FileUploadAnswer.DEFAULT_MAX_SIZE
+
+ def get_as_xml(self):
+ return FileUploadAnswer.FILEUPLOADANSWER_XML_TEMLPATE % (self.min, self.max)
+
+class SelectionAnswer(object):
+ """
+ A class to generate SelectionAnswer XML data structures.
+ Does not yet implement Binary selection options.
+ """
+ SELECTIONANSWER_XML_TEMPLATE = """<SelectionAnswer>%s%s<Selections>%s</Selections></SelectionAnswer>""" # % (count_xml, style_xml, selections_xml)
+ SELECTION_XML_TEMPLATE = """<Selection><SelectionIdentifier>%s</SelectionIdentifier>%s</Selection>""" # (identifier, value_xml)
+ SELECTION_VALUE_XML_TEMPLATE = """<%s>%s</%s>""" # (type, value, type)
+ STYLE_XML_TEMPLATE = """<StyleSuggestion>%s</StyleSuggestion>""" # (style)
+ MIN_SELECTION_COUNT_XML_TEMPLATE = """<MinSelectionCount>%s</MinSelectionCount>""" # count
+ MAX_SELECTION_COUNT_XML_TEMPLATE = """<MaxSelectionCount>%s</MaxSelectionCount>""" # count
+ ACCEPTED_STYLES = ['radiobutton', 'dropdown', 'checkbox', 'list', 'combobox', 'multichooser']
+ OTHER_SELECTION_ELEMENT_NAME = 'OtherSelection'
+
+ def __init__(self, min=1, max=1, style=None, selections=None, type='text', other=False):
+
+ if style is not None:
+ if style in SelectionAnswer.ACCEPTED_STYLES:
+ self.style_suggestion = style
+ else:
+ raise ValueError("style '%s' not recognized; should be one of %s" % (style, ', '.join(SelectionAnswer.ACCEPTED_STYLES)))
+ else:
+ self.style_suggestion = None
+
+ if selections is None:
+ raise ValueError("SelectionAnswer.__init__(): selections must be a non-empty list of (content, identifier) tuples")
+ else:
+ self.selections = selections
+
+ self.min_selections = min
+ self.max_selections = max
+
+ assert len(selections) >= self.min_selections, "# of selections is less than minimum of %d" % self.min_selections
+ #assert len(selections) <= self.max_selections, "# of selections exceeds maximum of %d" % self.max_selections
+
+ self.type = type
+
+ self.other = other
+
+ def get_as_xml(self):
+ if self.type == 'text':
+ TYPE_TAG = "Text"
+ elif self.type == 'binary':
+ TYPE_TAG = "Binary"
+ else:
+ raise ValueError("illegal type: %s; must be either 'text' or 'binary'" % str(self.type))
+
+ # build list of <Selection> elements
+ selections_xml = ""
+ for tpl in self.selections:
+ value_xml = SelectionAnswer.SELECTION_VALUE_XML_TEMPLATE % (TYPE_TAG, tpl[0], TYPE_TAG)
+ selection_xml = SelectionAnswer.SELECTION_XML_TEMPLATE % (tpl[1], value_xml)
+ selections_xml += selection_xml
+
+ if self.other:
+ # add OtherSelection element as xml if available
+ if hasattr(self.other, 'get_as_xml'):
+ assert type(self.other) == FreeTextAnswer, 'OtherSelection can only be a FreeTextAnswer'
+ selections_xml += self.other.get_as_xml().replace('FreeTextAnswer', 'OtherSelection')
+ else:
+ selections_xml += "<OtherSelection />"
+
+ if self.style_suggestion is not None:
+ style_xml = SelectionAnswer.STYLE_XML_TEMPLATE % self.style_suggestion
+ else:
+ style_xml = ""
+
+ if self.style_suggestion != 'radiobutton':
+ count_xml = SelectionAnswer.MIN_SELECTION_COUNT_XML_TEMPLATE %self.min_selections
+ count_xml += SelectionAnswer.MAX_SELECTION_COUNT_XML_TEMPLATE %self.max_selections
+ else:
+ count_xml = ""
+
+ ret = SelectionAnswer.SELECTIONANSWER_XML_TEMPLATE % (count_xml, style_xml, selections_xml)
+
+ # return XML
+ return ret
+
diff --git a/vendor/boto/boto/mturk/test/all_tests.py b/vendor/boto/boto/mturk/test/all_tests.py
new file mode 100644
index 0000000000..a8f291a6a9
--- /dev/null
+++ b/vendor/boto/boto/mturk/test/all_tests.py
@@ -0,0 +1,8 @@
+import doctest
+
+# doctest.testfile("create_hit.doctest")
+# doctest.testfile("create_hit_binary.doctest")
+doctest.testfile("create_free_text_question_regex.doctest")
+# doctest.testfile("create_hit_from_hit_type.doctest")
+# doctest.testfile("search_hits.doctest")
+# doctest.testfile("reviewable_hits.doctest")
diff --git a/vendor/boto/boto/mturk/test/cleanup_tests.py b/vendor/boto/boto/mturk/test/cleanup_tests.py
new file mode 100644
index 0000000000..7bdff90c74
--- /dev/null
+++ b/vendor/boto/boto/mturk/test/cleanup_tests.py
@@ -0,0 +1,67 @@
+from boto.mturk.connection import MTurkConnection
+
+def cleanup():
+ """Remove any boto test related HIT's"""
+
+ conn = MTurkConnection(host='mechanicalturk.sandbox.amazonaws.com')
+ current_page = 1
+ page_size = 10
+ total_disabled = 0
+ ignored = []
+
+ while True:
+ # reset the total for this loop
+ disabled_count = 0
+
+ # search all the hits in the sandbox
+ search_rs = conn.search_hits(page_size=page_size, page_number=current_page)
+
+ # success?
+ if search_rs.status:
+ for hit in search_rs:
+ # delete any with Boto in the description
+ print 'hit id:%s Status:%s, desc:%s' %(hit.HITId, hit.HITStatus, hit.Description)
+ if hit.Description.find('Boto') != -1:
+ if hit.HITStatus != 'Reviewable':
+ print 'Disabling hit id:%s %s' %(hit.HITId, hit.Description)
+ disable_rs = conn.disable_hit(hit.HITId)
+ if disable_rs.status:
+ disabled_count += 1
+ # update the running total
+ total_disabled += 1
+ else:
+ print 'Error when disabling, code:%s, message:%s' %(disable_rs.Code, disable_rs.Message)
+ else:
+ print 'Disposing hit id:%s %s' %(hit.HITId, hit.Description)
+ dispose_rs = conn.dispose_hit(hit.HITId)
+ if dispose_rs.status:
+ disabled_count += 1
+ # update the running total
+ total_disabled += 1
+ else:
+ print 'Error when disposing, code:%s, message:%s' %(dispose_rs.Code, dispose_rs.Message)
+
+ else:
+ if hit.HITId not in ignored:
+ print 'ignored:%s' %hit.HITId
+ ignored.append(hit.HITId)
+
+ # any more results?
+ if int(search_rs.TotalNumResults) > current_page*page_size:
+ # if we have disabled any HITs on this page
+ # then we don't need to go to a new page
+ # otherwise we do
+ if not disabled_count:
+ current_page += 1
+ else:
+ # no, we're done
+ break
+ else:
+ print 'Error performing search, code:%s, message:%s' %(search_rs.Code, search_rs.Message)
+ break
+
+ total_ignored = len(ignored)
+ print 'Processed: %d HITs, disabled/disposed: %d, ignored: %d' %(total_ignored + total_disabled, total_disabled, total_ignored)
+
+if __name__ == '__main__':
+ cleanup()
diff --git a/vendor/boto/boto/mturk/test/create_free_text_question_regex.doctest b/vendor/boto/boto/mturk/test/create_free_text_question_regex.doctest
new file mode 100644
index 0000000000..a10b7ed1b8
--- /dev/null
+++ b/vendor/boto/boto/mturk/test/create_free_text_question_regex.doctest
@@ -0,0 +1,92 @@
+>>> import uuid
+>>> import datetime
+>>> from boto.mturk.connection import MTurkConnection
+>>> from boto.mturk.question import Question, QuestionContent, AnswerSpecification, FreeTextAnswer
+
+>>> conn = MTurkConnection(host='mechanicalturk.sandbox.amazonaws.com')
+
+# create content for a question
+>>> qn_content = QuestionContent(title='Boto no hit type question content',
+... text='What is a boto no hit type?')
+
+# create a free text answer that is not quite so free!
+>>> ft_answer = FreeTextAnswer(format_regex=dict(regex="^[12][0-9]{3}-[01]?\d-[0-3]?\d$",
+... error_text="You must enter a date with the format yyyy-mm-dd.",
+... flags="i"),
+... default="This is not a valid format")
+
+# create the question specification
+>>> qn = Question(identifier=str(uuid.uuid4()),
+... content=qn_content,
+... answer_spec=AnswerSpecification(ft_answer))
+
+# now, create the actual HIT for the question without using a HIT type
+# NOTE - the response_groups are specified to get back additional information for testing
+>>> keywords=['boto', 'test', 'doctest']
+>>> create_hit_rs = conn.create_hit(question=qn,
+... lifetime=60*65,
+... max_assignments=2,
+... title='Boto create_hit title',
+... description='Boto create_hit description',
+... keywords=keywords,
+... reward=0.23,
+... duration=60*6,
+... approval_delay=60*60,
+... annotation='An annotation from boto create_hit test',
+... response_groups=['Minimal',
+... 'HITDetail',
+... 'HITQuestion',
+... 'HITAssignmentSummary',])
+
+# this is a valid request
+>>> create_hit_rs.status
+True
+
+# for the requested hit type id
+# the HIT Type Id is a unicode string
+>>> hit_type_id = create_hit_rs.HITTypeId
+>>> hit_type_id # doctest: +ELLIPSIS
+u'...'
+
+>>> create_hit_rs.MaxAssignments
+u'2'
+
+>>> create_hit_rs.AutoApprovalDelayInSeconds
+u'3600'
+
+# expiration should be very close to now + the lifetime in seconds
+>>> expected_datetime = datetime.datetime.utcnow() + datetime.timedelta(seconds=3900)
+>>> expiration_datetime = datetime.datetime.strptime(create_hit_rs.Expiration, '%Y-%m-%dT%H:%M:%SZ')
+>>> delta = expected_datetime - expiration_datetime
+>>> delta.seconds < 5
+True
+
+# duration is as specified for the HIT type
+>>> create_hit_rs.AssignmentDurationInSeconds
+u'360'
+
+# the reward has been set correctly (allow for float error here)
+>>> int(create_hit_rs[0].amount * 100)
+23
+
+>>> create_hit_rs[0].formatted_price
+u'$0.23'
+
+# only US currency supported at present
+>>> create_hit_rs[0].currency_code
+u'USD'
+
+# title is the HIT type title
+>>> create_hit_rs.Title
+u'Boto create_hit title'
+
+# title is the HIT type description
+>>> create_hit_rs.Description
+u'Boto create_hit description'
+
+# annotation is correct
+>>> create_hit_rs.RequesterAnnotation
+u'An annotation from boto create_hit test'
+
+>>> create_hit_rs.HITReviewStatus
+u'NotReviewed'
diff --git a/vendor/boto/boto/mturk/test/create_hit.doctest b/vendor/boto/boto/mturk/test/create_hit.doctest
new file mode 100644
index 0000000000..22209d6702
--- /dev/null
+++ b/vendor/boto/boto/mturk/test/create_hit.doctest
@@ -0,0 +1,86 @@
+>>> import uuid
+>>> import datetime
+>>> from boto.mturk.connection import MTurkConnection
+>>> from boto.mturk.question import Question, QuestionContent, AnswerSpecification, FreeTextAnswer
+
+>>> conn = MTurkConnection(host='mechanicalturk.sandbox.amazonaws.com')
+
+# create content for a question
+>>> qn_content = QuestionContent(title='Boto no hit type question content',
+... text='What is a boto no hit type?')
+
+# create the question specification
+>>> qn = Question(identifier=str(uuid.uuid4()),
+... content=qn_content,
+... answer_spec=AnswerSpecification(FreeTextAnswer()))
+
+# now, create the actual HIT for the question without using a HIT type
+# NOTE - the response_groups are specified to get back additional information for testing
+>>> keywords=['boto', 'test', 'doctest']
+>>> create_hit_rs = conn.create_hit(question=qn,
+... lifetime=60*65,
+... max_assignments=2,
+... title='Boto create_hit title',
+... description='Boto create_hit description',
+... keywords=keywords,
+... reward=0.23,
+... duration=60*6,
+... approval_delay=60*60,
+... annotation='An annotation from boto create_hit test',
+... response_groups=['Minimal',
+... 'HITDetail',
+... 'HITQuestion',
+... 'HITAssignmentSummary',])
+
+# this is a valid request
+>>> create_hit_rs.status
+True
+
+# for the requested hit type id
+# the HIT Type Id is a unicode string
+>>> hit_type_id = create_hit_rs.HITTypeId
+>>> hit_type_id # doctest: +ELLIPSIS
+u'...'
+
+>>> create_hit_rs.MaxAssignments
+u'2'
+
+>>> create_hit_rs.AutoApprovalDelayInSeconds
+u'3600'
+
+# expiration should be very close to now + the lifetime in seconds
+>>> expected_datetime = datetime.datetime.utcnow() + datetime.timedelta(seconds=3900)
+>>> expiration_datetime = datetime.datetime.strptime(create_hit_rs.Expiration, '%Y-%m-%dT%H:%M:%SZ')
+>>> delta = expected_datetime - expiration_datetime
+>>> delta.seconds < 5
+True
+
+# duration is as specified for the HIT type
+>>> create_hit_rs.AssignmentDurationInSeconds
+u'360'
+
+# the reward has been set correctly (allow for float error here)
+>>> int(create_hit_rs[0].amount * 100)
+23
+
+>>> create_hit_rs[0].formatted_price
+u'$0.23'
+
+# only US currency supported at present
+>>> create_hit_rs[0].currency_code
+u'USD'
+
+# title is the HIT type title
+>>> create_hit_rs.Title
+u'Boto create_hit title'
+
+# title is the HIT type description
+>>> create_hit_rs.Description
+u'Boto create_hit description'
+
+# annotation is correct
+>>> create_hit_rs.RequesterAnnotation
+u'An annotation from boto create_hit test'
+
+>>> create_hit_rs.HITReviewStatus
+u'NotReviewed'
diff --git a/vendor/boto/boto/mturk/test/create_hit_binary.doctest b/vendor/boto/boto/mturk/test/create_hit_binary.doctest
new file mode 100644
index 0000000000..309608320c
--- /dev/null
+++ b/vendor/boto/boto/mturk/test/create_hit_binary.doctest
@@ -0,0 +1,87 @@
+>>> import uuid
+>>> import datetime
+>>> from boto.mturk.connection import MTurkConnection
+>>> from boto.mturk.question import Question, QuestionContent, AnswerSpecification, FreeTextAnswer
+
+>>> conn = MTurkConnection(host='mechanicalturk.sandbox.amazonaws.com')
+
+# create content for a question
+>>> qn_content = QuestionContent(title='Boto no hit type question content',
+... text='What is a boto no hit type?',
+... binary='http://www.example.com/test1.jpg')
+
+# create the question specification
+>>> qn = Question(identifier=str(uuid.uuid4()),
+... content=qn_content,
+... answer_spec=AnswerSpecification(FreeTextAnswer()))
+
+# now, create the actual HIT for the question without using a HIT type
+# NOTE - the response_groups are specified to get back additional information for testing
+>>> keywords=['boto', 'test', 'doctest']
+>>> create_hit_rs = conn.create_hit(question=qn,
+... lifetime=60*65,
+... max_assignments=2,
+... title='Boto create_hit title',
+... description='Boto create_hit description',
+... keywords=keywords,
+... reward=0.23,
+... duration=60*6,
+... approval_delay=60*60,
+... annotation='An annotation from boto create_hit test',
+... response_groups=['Minimal',
+... 'HITDetail',
+... 'HITQuestion',
+... 'HITAssignmentSummary',])
+
+# this is a valid request
+>>> create_hit_rs.status
+True
+
+# for the requested hit type id
+# the HIT Type Id is a unicode string
+>>> hit_type_id = create_hit_rs.HITTypeId
+>>> hit_type_id # doctest: +ELLIPSIS
+u'...'
+
+>>> create_hit_rs.MaxAssignments
+u'2'
+
+>>> create_hit_rs.AutoApprovalDelayInSeconds
+u'3600'
+
+# expiration should be very close to now + the lifetime in seconds
+>>> expected_datetime = datetime.datetime.utcnow() + datetime.timedelta(seconds=3900)
+>>> expiration_datetime = datetime.datetime.strptime(create_hit_rs.Expiration, '%Y-%m-%dT%H:%M:%SZ')
+>>> delta = expected_datetime - expiration_datetime
+>>> delta.seconds < 5
+True
+
+# duration is as specified for the HIT type
+>>> create_hit_rs.AssignmentDurationInSeconds
+u'360'
+
+# the reward has been set correctly (allow for float error here)
+>>> int(create_hit_rs[0].amount * 100)
+23
+
+>>> create_hit_rs[0].formatted_price
+u'$0.23'
+
+# only US currency supported at present
+>>> create_hit_rs[0].currency_code
+u'USD'
+
+# title is the HIT type title
+>>> create_hit_rs.Title
+u'Boto create_hit title'
+
+# title is the HIT type description
+>>> create_hit_rs.Description
+u'Boto create_hit description'
+
+# annotation is correct
+>>> create_hit_rs.RequesterAnnotation
+u'An annotation from boto create_hit test'
+
+>>> create_hit_rs.HITReviewStatus
+u'NotReviewed'
diff --git a/vendor/boto/boto/mturk/test/create_hit_external.py b/vendor/boto/boto/mturk/test/create_hit_external.py
new file mode 100644
index 0000000000..e7425d665d
--- /dev/null
+++ b/vendor/boto/boto/mturk/test/create_hit_external.py
@@ -0,0 +1,14 @@
+import uuid
+import datetime
+from boto.mturk.connection import MTurkConnection
+from boto.mturk.question import ExternalQuestion
+
+def test():
+ q = ExternalQuestion(external_url="http://websort.net/s/F3481C", frame_height=800)
+ conn = MTurkConnection(host='mechanicalturk.sandbox.amazonaws.com')
+ keywords=['boto', 'test', 'doctest']
+ create_hit_rs = conn.create_hit(question=q, lifetime=60*65,max_assignments=2,title="Boto External Question Test", keywords=keywords,reward = 0.05, duration=60*6,approval_delay=60*60, annotation='An annotation from boto external question test', response_groups=['Minimal','HITDetail','HITQuestion','HITAssignmentSummary',])
+ assert(create_hit_rs.status == True)
+
+if __name__ == "__main__":
+ test()
diff --git a/vendor/boto/boto/mturk/test/create_hit_from_hit_type.doctest b/vendor/boto/boto/mturk/test/create_hit_from_hit_type.doctest
new file mode 100644
index 0000000000..144a677f6d
--- /dev/null
+++ b/vendor/boto/boto/mturk/test/create_hit_from_hit_type.doctest
@@ -0,0 +1,97 @@
+>>> import uuid
+>>> import datetime
+>>> from boto.mturk.connection import MTurkConnection
+>>> from boto.mturk.question import Question, QuestionContent, AnswerSpecification, FreeTextAnswer
+>>>
+>>> conn = MTurkConnection(host='mechanicalturk.sandbox.amazonaws.com')
+>>> keywords=['boto', 'test', 'doctest']
+>>> hit_type_rs = conn.register_hit_type('Boto Test HIT type',
+... 'HIT Type for testing Boto',
+... 0.12,
+... 60*6,
+... keywords=keywords,
+... approval_delay=60*60)
+
+# this was a valid request
+>>> hit_type_rs.status
+True
+
+# the HIT Type Id is a unicode string
+>>> hit_type_id = hit_type_rs.HITTypeId
+>>> hit_type_id # doctest: +ELLIPSIS
+u'...'
+
+# create content for a question
+>>> qn_content = QuestionContent(title='Boto question content create_hit_from_hit_type',
+... text='What is a boto create_hit_from_hit_type?')
+
+# create the question specification
+>>> qn = Question(identifier=str(uuid.uuid4()),
+... content=qn_content,
+... answer_spec=AnswerSpecification(FreeTextAnswer()))
+
+# now, create the actual HIT for the question using the HIT type
+# NOTE - the response_groups are specified to get back additional information for testing
+>>> create_hit_rs = conn.create_hit(hit_type=hit_type_rs.HITTypeId,
+... question=qn,
+... lifetime=60*65,
+... max_assignments=2,
+... annotation='An annotation from boto create_hit_from_hit_type test',
+... response_groups=['Minimal',
+... 'HITDetail',
+... 'HITQuestion',
+... 'HITAssignmentSummary',])
+
+# this is a valid request
+>>> create_hit_rs.status
+True
+
+# for the requested hit type id
+>>> create_hit_rs.HITTypeId == hit_type_id
+True
+
+# with the correct number of maximum assignments
+>>> create_hit_rs.MaxAssignments
+u'2'
+
+# and the approval delay
+>>> create_hit_rs.AutoApprovalDelayInSeconds
+u'3600'
+
+# expiration should be very close to now + the lifetime in seconds
+>>> expected_datetime = datetime.datetime.utcnow() + datetime.timedelta(seconds=3900)
+>>> expiration_datetime = datetime.datetime.strptime(create_hit_rs.Expiration, '%Y-%m-%dT%H:%M:%SZ')
+>>> delta = expected_datetime - expiration_datetime
+>>> delta.seconds < 5
+True
+
+# duration is as specified for the HIT type
+>>> create_hit_rs.AssignmentDurationInSeconds
+u'360'
+
+# the reward has been set correctly
+>>> create_hit_rs[0].amount
+0.12
+
+>>> create_hit_rs[0].formatted_price
+u'$0.12'
+
+# only US currency supported at present
+>>> create_hit_rs[0].currency_code
+u'USD'
+
+# title is the HIT type title
+>>> create_hit_rs.Title
+u'Boto Test HIT type'
+
+# title is the HIT type description
+>>> create_hit_rs.Description
+u'HIT Type for testing Boto'
+
+# annotation is correct
+>>> create_hit_rs.RequesterAnnotation
+u'An annotation from boto create_hit_from_hit_type test'
+
+# not reviewed yet
+>>> create_hit_rs.HITReviewStatus
+u'NotReviewed'
diff --git a/vendor/boto/boto/mturk/test/create_hit_with_qualifications.py b/vendor/boto/boto/mturk/test/create_hit_with_qualifications.py
new file mode 100644
index 0000000000..f2149eebe2
--- /dev/null
+++ b/vendor/boto/boto/mturk/test/create_hit_with_qualifications.py
@@ -0,0 +1,18 @@
+import uuid
+import datetime
+from boto.mturk.connection import MTurkConnection
+from boto.mturk.question import ExternalQuestion
+from boto.mturk.qualification import Qualifications, PercentAssignmentsApprovedRequirement
+
+def test():
+ q = ExternalQuestion(external_url="http://websort.net/s/F3481C", frame_height=800)
+ conn = MTurkConnection(host='mechanicalturk.sandbox.amazonaws.com')
+ keywords=['boto', 'test', 'doctest']
+ qualifications = Qualifications()
+ qualifications.add(PercentAssignmentsApprovedRequirement(comparator="GreaterThan", integer_value="95"))
+ create_hit_rs = conn.create_hit(question=q, lifetime=60*65,max_assignments=2,title="Boto External Question Test", keywords=keywords,reward = 0.05, duration=60*6,approval_delay=60*60, annotation='An annotation from boto external question test', qualifications=qualifications)
+ assert(create_hit_rs.status == True)
+ print create_hit_rs.HITTypeId
+
+if __name__ == "__main__":
+ test()
diff --git a/vendor/boto/boto/mturk/test/reviewable_hits.doctest b/vendor/boto/boto/mturk/test/reviewable_hits.doctest
new file mode 100644
index 0000000000..0305901098
--- /dev/null
+++ b/vendor/boto/boto/mturk/test/reviewable_hits.doctest
@@ -0,0 +1,71 @@
+>>> from boto.mturk.connection import MTurkConnection
+>>> conn = MTurkConnection(host='mechanicalturk.sandbox.amazonaws.com')
+
+# should have some reviewable HIT's returned, especially if returning all HIT type's
+# NOTE: but only if your account has existing HIT's in the reviewable state
+>>> reviewable_rs = conn.get_reviewable_hits()
+
+# this is a valid request
+>>> reviewable_rs.status
+True
+
+>>> len(reviewable_rs) > 1
+True
+
+# should contain at least one HIT object
+>>> reviewable_rs # doctest: +ELLIPSIS
+[<boto.mturk.connection.HIT instance at ...]
+
+>>> hit_id = reviewable_rs[0].HITId
+
+# check that we can retrieve the assignments for a HIT
+>>> assignments_rs = conn.get_assignments(hit_id)
+
+# this is a valid request
+>>> assignments_rs.status
+True
+
+>>> assignments_rs.NumResults >= 1
+True
+
+>>> len(assignments_rs) == int(assignments_rs.NumResults)
+True
+
+>>> assignments_rs.PageNumber
+u'1'
+
+>>> assignments_rs.TotalNumResults >= 1
+True
+
+# should contain at least one Assignment object
+>>> assignments_rs # doctest: +ELLIPSIS
+[<boto.mturk.connection.Assignment instance at ...]
+
+# should have returned assignments for the requested HIT id
+>>> assignment = assignments_rs[0]
+
+>>> assignment.HITId == hit_id
+True
+
+# should have a valid status
+>>> assignment.AssignmentStatus in ['Submitted', 'Approved', 'Rejected']
+True
+
+# should have returned at least one answer
+>>> len(assignment.answers) > 0
+True
+
+# should contain at least one set of QuestionFormAnswer objects
+>>> assignment.answers # doctest: +ELLIPSIS
+[[<boto.mturk.connection.QuestionFormAnswer instance at ...]]
+
+>>> answer = assignment.answers[0][0]
+
+# answer should be a FreeTextAnswer
+>>> answer.FreeText # doctest: +ELLIPSIS
+u'...'
+
+# question identifier should be a unicode string
+>>> answer.QuestionIdentifier # doctest: +ELLIPSIS
+u'...'
+
diff --git a/vendor/boto/boto/mturk/test/search_hits.doctest b/vendor/boto/boto/mturk/test/search_hits.doctest
new file mode 100644
index 0000000000..a2547ea14b
--- /dev/null
+++ b/vendor/boto/boto/mturk/test/search_hits.doctest
@@ -0,0 +1,16 @@
+>>> from boto.mturk.connection import MTurkConnection
+>>> conn = MTurkConnection(host='mechanicalturk.sandbox.amazonaws.com')
+
+# should have some HIT's returned by a search (but only if your account has existing HIT's)
+>>> search_rs = conn.search_hits()
+
+# this is a valid request
+>>> search_rs.status
+True
+
+>>> len(search_rs) > 1
+True
+
+>>> search_rs # doctest: +ELLIPSIS
+[<boto.mturk.connection.HIT instance at ...]
+
diff --git a/vendor/boto/boto/pyami/__init__.py b/vendor/boto/boto/pyami/__init__.py
new file mode 100644
index 0000000000..303dbb66c9
--- /dev/null
+++ b/vendor/boto/boto/pyami/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
diff --git a/vendor/boto/boto/pyami/bootstrap.py b/vendor/boto/boto/pyami/bootstrap.py
new file mode 100644
index 0000000000..44bcc2d99b
--- /dev/null
+++ b/vendor/boto/boto/pyami/bootstrap.py
@@ -0,0 +1,121 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+import os
+import boto
+from boto.utils import get_instance_metadata, get_instance_userdata
+from boto.pyami.config import Config, BotoConfigPath
+from boto.pyami.scriptbase import ScriptBase
+
+class Bootstrap(ScriptBase):
+ """
+ The Bootstrap class is instantiated and run as part of the PyAMI
+ instance initialization process. The methods in this class will
+ be run from the rc.local script of the instance and will be run
+ as the root user.
+
+ The main purpose of this class is to make sure the boto distribution
+ on the instance is the one required.
+ """
+
+ def __init__(self):
+ self.working_dir = '/mnt/pyami'
+ self.write_metadata()
+ ScriptBase.__init__(self)
+
+ def write_metadata(self):
+ fp = open(os.path.expanduser(BotoConfigPath), 'w')
+ fp.write('[Instance]\n')
+ inst_data = get_instance_metadata()
+ for key in inst_data:
+ fp.write('%s = %s\n' % (key, inst_data[key]))
+ user_data = get_instance_userdata()
+ fp.write('\n%s\n' % user_data)
+ fp.write('[Pyami]\n')
+ fp.write('working_dir = %s\n' % self.working_dir)
+ fp.close()
+ # This file has the AWS credentials, should we lock it down?
+ # os.chmod(BotoConfigPath, stat.S_IREAD | stat.S_IWRITE)
+ # now that we have written the file, read it into a pyami Config object
+ boto.config = Config()
+ boto.init_logging()
+
+ def create_working_dir(self):
+ boto.log.info('Working directory: %s' % self.working_dir)
+ if not os.path.exists(self.working_dir):
+ os.mkdir(self.working_dir)
+
+ def load_boto(self):
+ update = boto.config.get('Boto', 'boto_update', 'svn:HEAD')
+ if update.startswith('svn'):
+ if update.find(':') >= 0:
+ method, version = update.split(':')
+ version = '-r%s' % version
+ else:
+ version = '-rHEAD'
+ location = boto.config.get('Boto', 'boto_location', '/usr/local/boto')
+ self.run('svn update %s %s' % (version, location))
+ else:
+ # first remove the symlink needed when running from subversion
+ self.run('rm /usr/local/lib/python2.5/site-packages/boto')
+ self.run('easy_install %s' % update)
+
+ def fetch_s3_file(self, s3_file):
+ try:
+ if s3_file.startswith('s3:'):
+ bucket_name, key_name = s3_file[len('s3:'):].split('/')
+ c = boto.connect_s3()
+ bucket = c.get_bucket(bucket_name)
+ key = bucket.get_key(key_name)
+ boto.log.info('Fetching %s/%s' % (bucket.name, key.name))
+ path = os.path.join(self.working_dir, key.name)
+ key.get_contents_to_filename(path)
+ except:
+ boto.log.exception('Problem Retrieving file: %s' % s3_file)
+ path = None
+ return path
+
+ def load_packages(self):
+ package_str = boto.config.get('Pyami', 'packages')
+ if package_str:
+ packages = package_str.split(',')
+ for package in packages:
+ package = package.strip()
+ if package.startswith('s3:'):
+ package = self.fetch_s3_file(package)
+ if package:
+ # if the "package" is really a .py file, it doesn't have to
+ # be installed, just being in the working dir is enough
+ if not package.endswith('.py'):
+ self.run('easy_install -Z %s' % package, exit_on_error=False)
+
+ def main(self):
+ self.create_working_dir()
+ self.load_boto()
+ self.load_packages()
+ self.notify('Bootstrap Completed for %s' % boto.config.get_instance('instance-id'))
+
+if __name__ == "__main__":
+ # because bootstrap starts before any logging configuration can be loaded from
+ # the boto config files, we will manually enable logging to /var/log/boto.log
+ boto.set_file_logger('bootstrap', '/var/log/boto.log')
+ bs = Bootstrap()
+ bs.main()
diff --git a/vendor/boto/boto/pyami/config.py b/vendor/boto/boto/pyami/config.py
new file mode 100644
index 0000000000..ea0c3a1a5e
--- /dev/null
+++ b/vendor/boto/boto/pyami/config.py
@@ -0,0 +1,203 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+import StringIO, os, re
+import ConfigParser
+import boto
+
+BotoConfigPath = '/etc/boto.cfg'
+BotoConfigLocations = [BotoConfigPath]
+if 'HOME' in os.environ:
+ UserConfigPath = os.path.expanduser('~/.boto')
+ BotoConfigLocations.append(UserConfigPath)
+else:
+ UserConfigPath = None
+if 'BOTO_CONFIG' in os.environ:
+ BotoConfigLocations.append(os.path.expanduser(os.environ['BOTO_CONFIG']))
+
+class Config(ConfigParser.SafeConfigParser):
+
+ def __init__(self, path=None, fp=None, do_load=True):
+ ConfigParser.SafeConfigParser.__init__(self, {'working_dir' : '/mnt/pyami',
+ 'debug' : '0'})
+ if do_load:
+ if path:
+ self.load_from_path(path)
+ elif fp:
+ self.readfp(fp)
+ else:
+ self.read(BotoConfigLocations)
+ if "AWS_CREDENTIAL_FILE" in os.environ:
+ self.load_credential_file(os.path.expanduser(os.environ['AWS_CREDENTIAL_FILE']))
+
+ def load_credential_file(self, path):
+ """Load a credential file as is setup like the Java utilities"""
+ c_data = StringIO.StringIO()
+ c_data.write("[Credentials]\n")
+ for line in open(path, "r").readlines():
+ c_data.write(line.replace("AWSAccessKeyId", "aws_access_key_id").replace("AWSSecretKey", "aws_secret_access_key"))
+ c_data.seek(0)
+ self.readfp(c_data)
+
+ def load_from_path(self, path):
+ file = open(path)
+ for line in file.readlines():
+ match = re.match("^#import[\s\t]*([^\s^\t]*)[\s\t]*$", line)
+ if match:
+ extended_file = match.group(1)
+ (dir, file) = os.path.split(path)
+ self.load_from_path(os.path.join(dir, extended_file))
+ self.read(path)
+
+ def save_option(self, path, section, option, value):
+ """
+ Write the specified Section.Option to the config file specified by path.
+ Replace any previous value. If the path doesn't exist, create it.
+ Also add the option the the in-memory config.
+ """
+ config = ConfigParser.SafeConfigParser()
+ config.read(path)
+ if not config.has_section(section):
+ config.add_section(section)
+ config.set(section, option, value)
+ fp = open(path, 'w')
+ config.write(fp)
+ fp.close()
+ if not self.has_section(section):
+ self.add_section(section)
+ self.set(section, option, value)
+
+ def save_user_option(self, section, option, value):
+ self.save_option(UserConfigPath, section, option, value)
+
+ def save_system_option(self, section, option, value):
+ self.save_option(BotoConfigPath, section, option, value)
+
+ def get_instance(self, name, default=None):
+ try:
+ val = self.get('Instance', name)
+ except:
+ val = default
+ return val
+
+ def get_user(self, name, default=None):
+ try:
+ val = self.get('User', name)
+ except:
+ val = default
+ return val
+
+ def getint_user(self, name, default=0):
+ try:
+ val = self.getint('User', name)
+ except:
+ val = default
+ return val
+
+ def get_value(self, section, name, default=None):
+ return self.get(section, name, default)
+
+ def get(self, section, name, default=None):
+ try:
+ val = ConfigParser.SafeConfigParser.get(self, section, name)
+ except:
+ val = default
+ return val
+
+ def getint(self, section, name, default=0):
+ try:
+ val = ConfigParser.SafeConfigParser.getint(self, section, name)
+ except:
+ val = int(default)
+ return val
+
+ def getfloat(self, section, name, default=0.0):
+ try:
+ val = ConfigParser.SafeConfigParser.getfloat(self, section, name)
+ except:
+ val = float(default)
+ return val
+
+ def getbool(self, section, name, default=False):
+ if self.has_option(section, name):
+ val = self.get(section, name)
+ if val.lower() == 'true':
+ val = True
+ else:
+ val = False
+ else:
+ val = default
+ return val
+
+ def setbool(self, section, name, value):
+ if value:
+ self.set(section, name, 'true')
+ else:
+ self.set(section, name, 'false')
+
+ def dump(self):
+ s = StringIO.StringIO()
+ self.write(s)
+ print s.getvalue()
+
+ def dump_safe(self, fp=None):
+ if not fp:
+ fp = StringIO.StringIO()
+ for section in self.sections():
+ fp.write('[%s]\n' % section)
+ for option in self.options(section):
+ if option == 'aws_secret_access_key':
+ fp.write('%s = xxxxxxxxxxxxxxxxxx\n' % option)
+ else:
+ fp.write('%s = %s\n' % (option, self.get(section, option)))
+
+ def dump_to_sdb(self, domain_name, item_name):
+ import simplejson
+ sdb = boto.connect_sdb()
+ domain = sdb.lookup(domain_name)
+ if not domain:
+ domain = sdb.create_domain(domain_name)
+ item = domain.new_item(item_name)
+ item.active = False
+ for section in self.sections():
+ d = {}
+ for option in self.options(section):
+ d[option] = self.get(section, option)
+ item[section] = simplejson.dumps(d)
+ item.save()
+
+ def load_from_sdb(self, domain_name, item_name):
+ import simplejson
+ sdb = boto.connect_sdb()
+ domain = sdb.lookup(domain_name)
+ item = domain.get_item(item_name)
+ for section in item.keys():
+ if not self.has_section(section):
+ self.add_section(section)
+ d = simplejson.loads(item[section])
+ for attr_name in d.keys():
+ attr_value = d[attr_name]
+ if attr_value == None:
+ attr_value = 'None'
+ if isinstance(attr_value, bool):
+ self.setbool(section, attr_name, attr_value)
+ else:
+ self.set(section, attr_name, attr_value)
diff --git a/vendor/boto/boto/pyami/copybot.cfg b/vendor/boto/boto/pyami/copybot.cfg
new file mode 100644
index 0000000000..cbfdc5ad19
--- /dev/null
+++ b/vendor/boto/boto/pyami/copybot.cfg
@@ -0,0 +1,60 @@
+#
+# Your AWS Credentials
+#
+[Credentials]
+aws_access_key_id = <AWS Access Key Here>
+aws_secret_access_key = <AWS Secret Key Here>
+
+#
+# If you want to use a separate set of credentials when writing
+# to the destination bucket, put them here
+#dest_aws_access_key_id = <AWS Access Key Here>
+#dest_aws_secret_access_key = <AWS Secret Key Here>
+
+#
+# Fill out this section if you want emails from CopyBot
+# when it starts and stops
+#
+[Notification]
+#smtp_host = <your smtp host>
+#smtp_user = <your smtp username, if necessary>
+#smtp_pass = <your smtp password, if necessary>
+#smtp_from = <email address for From: field>
+#smtp_to = <email address for To: field>
+
+#
+# If you leave this section as is, it will automatically
+# update boto from subversion upon start up.
+# If you don't want that to happen, comment this out
+#
+[Boto]
+boto_location = /usr/local/boto
+boto_update = svn:HEAD
+
+#
+# This tells the Pyami code in boto what scripts
+# to run during startup
+#
+[Pyami]
+scripts = boto.pyami.copybot.CopyBot
+
+#
+# Source bucket and Destination Bucket, obviously.
+# If the Destination bucket does not exist, it will
+# attempt to create it.
+# If exit_on_completion is false, the instance
+# will keep running after the copy operation is
+# complete which might be handy for debugging.
+# If copy_acls is false, the ACL's will not be
+# copied with the objects to the new bucket.
+# If replace_dst is false, copybot will not
+# will only store the source file in the dest if
+# that file does not already exist. If it's true
+# it will replace it even if it does exist.
+#
+[CopyBot]
+src_bucket = <your source bucket name>
+dst_bucket = <your destination bucket name>
+exit_on_completion = true
+copy_acls = true
+replace_dst = true
diff --git a/vendor/boto/boto/pyami/copybot.py b/vendor/boto/boto/pyami/copybot.py
new file mode 100644
index 0000000000..ed397cb761
--- /dev/null
+++ b/vendor/boto/boto/pyami/copybot.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+import boto
+from boto.pyami.scriptbase import ScriptBase
+import os, StringIO
+
+class CopyBot(ScriptBase):
+
+ def __init__(self):
+ ScriptBase.__init__(self)
+ self.wdir = boto.config.get('Pyami', 'working_dir')
+ self.log_file = '%s.log' % self.instance_id
+ self.log_path = os.path.join(self.wdir, self.log_file)
+ boto.set_file_logger(self.name, self.log_path)
+ self.src_name = boto.config.get(self.name, 'src_bucket')
+ self.dst_name = boto.config.get(self.name, 'dst_bucket')
+ self.replace = boto.config.getbool(self.name, 'replace_dst', True)
+ s3 = boto.connect_s3()
+ self.src = s3.lookup(self.src_name)
+ if not self.src:
+ boto.log.error('Source bucket does not exist: %s' % self.src_name)
+ dest_access_key = boto.config.get(self.name, 'dest_aws_access_key_id', None)
+ if dest_access_key:
+ dest_secret_key = boto.config.get(self.name, 'dest_aws_secret_access_key', None)
+ s3 = boto.connect(dest_access_key, dest_secret_key)
+ self.dst = s3.lookup(self.dst_name)
+ if not self.dst:
+ self.dst = s3.create_bucket(self.dst_name)
+
+ def copy_bucket_acl(self):
+ if boto.config.get(self.name, 'copy_acls', True):
+ acl = self.src.get_xml_acl()
+ self.dst.set_xml_acl(acl)
+
+ def copy_key_acl(self, src, dst):
+ if boto.config.get(self.name, 'copy_acls', True):
+ acl = src.get_xml_acl()
+ dst.set_xml_acl(acl)
+
+ def copy_keys(self):
+ boto.log.info('src=%s' % self.src.name)
+ boto.log.info('dst=%s' % self.dst.name)
+ try:
+ for key in self.src:
+ if not self.replace:
+ exists = self.dst.lookup(key.name)
+ if exists:
+ boto.log.info('key=%s already exists in %s, skipping' % (key.name, self.dst.name))
+ continue
+ boto.log.info('copying %d bytes from key=%s' % (key.size, key.name))
+ prefix, base = os.path.split(key.name)
+ path = os.path.join(self.wdir, base)
+ key.get_contents_to_filename(path)
+ new_key = self.dst.new_key(key.name)
+ new_key.set_contents_from_filename(path)
+ self.copy_key_acl(key, new_key)
+ os.unlink(path)
+ except:
+ boto.log.exception('Error copying key: %s' % key.name)
+
+ def copy_log(self):
+ key = self.dst.new_key(self.log_file)
+ key.set_contents_from_filename(self.log_path)
+
+ def main(self):
+ fp = StringIO.StringIO()
+ boto.config.dump_safe(fp)
+ self.notify('%s (%s) Starting' % (self.name, self.instance_id), fp.getvalue())
+ if self.src and self.dst:
+ self.copy_keys()
+ if self.dst:
+ self.copy_log()
+ self.notify('%s (%s) Stopping' % (self.name, self.instance_id),
+ 'Copy Operation Complete')
+ if boto.config.getbool(self.name, 'exit_on_completion', True):
+ ec2 = boto.connect_ec2()
+ ec2.terminate_instances([self.instance_id])
+
diff --git a/vendor/boto/boto/pyami/helloworld.py b/vendor/boto/boto/pyami/helloworld.py
new file mode 100644
index 0000000000..680873ce17
--- /dev/null
+++ b/vendor/boto/boto/pyami/helloworld.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+from boto.pyami.scriptbase import ScriptBase
+
+class HelloWorld(ScriptBase):
+
+ def main(self):
+ self.log('Hello World!!!')
+
diff --git a/vendor/boto/boto/pyami/installers/__init__.py b/vendor/boto/boto/pyami/installers/__init__.py
new file mode 100644
index 0000000000..cc689264bc
--- /dev/null
+++ b/vendor/boto/boto/pyami/installers/__init__.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+from boto.pyami.scriptbase import ScriptBase
+
+
+class Installer(ScriptBase):
+ """
+ Abstract base class for installers
+ """
+
+ def add_cron(self, name, minute, hour, mday, month, wday, who, command, env=None):
+ """
+ Add an entry to the system crontab.
+ """
+ raise NotImplementedError
+
+ def add_init_script(self, file):
+ """
+ Add this file to the init.d directory
+ """
+
+ def add_env(self, key, value):
+ """
+ Add an environemnt variable
+ """
+ raise NotImplementedError
+
+ def stop(self, service_name):
+ """
+ Stop a service.
+ """
+ raise NotImplementedError
+
+ def start(self, service_name):
+ """
+ Start a service.
+ """
+ raise NotImplementedError
+
+ def install(self):
+ """
+ Do whatever is necessary to "install" the package.
+ """
+ raise NotImplementedError
+
diff --git a/vendor/boto/boto/pyami/installers/ubuntu/__init__.py b/vendor/boto/boto/pyami/installers/ubuntu/__init__.py
new file mode 100644
index 0000000000..60ee658e34
--- /dev/null
+++ b/vendor/boto/boto/pyami/installers/ubuntu/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
diff --git a/vendor/boto/boto/pyami/installers/ubuntu/apache.py b/vendor/boto/boto/pyami/installers/ubuntu/apache.py
new file mode 100644
index 0000000000..febc2dfa25
--- /dev/null
+++ b/vendor/boto/boto/pyami/installers/ubuntu/apache.py
@@ -0,0 +1,43 @@
+# Copyright (c) 2008 Chris Moyer http://coredumped.org
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+from boto.pyami.installers.ubuntu.installer import Installer
+
+class Apache(Installer):
+ """
+ Install apache2, mod_python, and libapache2-svn
+ """
+
+ def install(self):
+ self.run("apt-get update")
+ self.run('apt-get -y install apache2', notify=True, exit_on_error=True)
+ self.run('apt-get -y install libapache2-mod-python', notify=True, exit_on_error=True)
+ self.run('a2enmod rewrite', notify=True, exit_on_error=True)
+ self.run('a2enmod ssl', notify=True, exit_on_error=True)
+ self.run('a2enmod proxy', notify=True, exit_on_error=True)
+ self.run('a2enmod proxy_ajp', notify=True, exit_on_error=True)
+
+ # Hard reboot the apache2 server to enable these module
+ self.stop("apache2")
+ self.start("apache2")
+
+ def main(self):
+ self.install()
diff --git a/vendor/boto/boto/pyami/installers/ubuntu/ebs.py b/vendor/boto/boto/pyami/installers/ubuntu/ebs.py
new file mode 100644
index 0000000000..5486add99b
--- /dev/null
+++ b/vendor/boto/boto/pyami/installers/ubuntu/ebs.py
@@ -0,0 +1,206 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+"""
+Automated installer to attach, format and mount an EBS volume.
+This installer assumes that you want the volume formatted as
+an XFS file system. To drive this installer, you need the
+following section in the boto config passed to the new instance.
+You also need to install dateutil by listing python-dateutil
+in the list of packages to be installed in the Pyami seciont
+of your boto config file.
+
+If there is already a device mounted at the specified mount point,
+the installer assumes that it is the ephemeral drive and unmounts
+it, remounts it as /tmp and chmods it to 777.
+
+Config file section::
+
+ [EBS]
+ volume_id = <the id of the EBS volume, should look like vol-xxxxxxxx>
+ logical_volume_name = <the name of the logical volume that contaings
+ a reference to the physical volume to be mounted. If this parameter
+ is supplied, it overrides the volume_id setting.>
+ device = <the linux device the EBS volume should be mounted on>
+ mount_point = <directory to mount device, defaults to /ebs>
+
+"""
+import boto
+from boto.manage.volume import Volume
+import os, time
+from boto.pyami.installers.ubuntu.installer import Installer
+from string import Template
+
+BackupScriptTemplate = """#!/usr/bin/env python
+# Backup EBS volume
+import boto
+from boto.pyami.scriptbase import ScriptBase
+import traceback
+
+class Backup(ScriptBase):
+
+ def main(self):
+ try:
+ ec2 = boto.connect_ec2()
+ self.run("/usr/sbin/xfs_freeze -f ${mount_point}")
+ snapshot = ec2.create_snapshot('${volume_id}')
+ boto.log.info("Snapshot created: %s " % snapshot)
+ except Exception, e:
+ self.notify(subject="${instance_id} Backup Failed", body=traceback.format_exc())
+ boto.log.info("Snapshot created: ${volume_id}")
+ except Exception, e:
+ self.notify(subject="${instance_id} Backup Failed", body=traceback.format_exc())
+ finally:
+ self.run("/usr/sbin/xfs_freeze -u ${mount_point}")
+
+if __name__ == "__main__":
+ b = Backup()
+ b.main()
+"""
+
+BackupCleanupScript= """#!/usr/bin/env python
+import boto
+from boto.manage.volume import Volume
+
+# Cleans Backups of EBS volumes
+
+for v in Volume.all():
+ v.trim_snapshots(True)
+"""
+
+class EBSInstaller(Installer):
+ """
+ Set up the EBS stuff
+ """
+
+ def __init__(self, config_file=None):
+ Installer.__init__(self, config_file)
+ self.instance_id = boto.config.get('Instance', 'instance-id')
+ self.device = boto.config.get('EBS', 'device', '/dev/sdp')
+ self.volume_id = boto.config.get('EBS', 'volume_id')
+ self.logical_volume_name = boto.config.get('EBS', 'logical_volume_name')
+ self.mount_point = boto.config.get('EBS', 'mount_point', '/ebs')
+
+ def attach(self):
+ ec2 = boto.connect_ec2()
+ if self.logical_volume_name:
+ # if a logical volume was specified, override the specified volume_id
+ # (if there was one) with the current AWS volume for the logical volume:
+ logical_volume = Volume.find(name = self.logical_volume_name).next()
+ self.volume_id = logical_volume._volume_id
+ volume = ec2.get_all_volumes([self.volume_id])[0]
+ # wait for the volume to be available. The volume may still be being created
+ # from a snapshot.
+ while volume.update() != 'available':
+ boto.log.info('Volume %s not yet available. Current status = %s.' % (volume.id, volume.status))
+ time.sleep(5)
+ ec2.attach_volume(self.volume_id, self.instance_id, self.device)
+ # now wait for the volume device to appear
+ while not os.path.exists(self.device):
+ boto.log.info('%s still does not exist, waiting 10 seconds' % self.device)
+ time.sleep(10)
+
+ def make_fs(self):
+ boto.log.info('make_fs...')
+ has_fs = self.run('fsck %s' % self.device)
+ if has_fs != 0:
+ self.run('mkfs -t xfs %s' % self.device)
+
+ def create_backup_script(self):
+ t = Template(BackupScriptTemplate)
+ s = t.substitute(volume_id=self.volume_id, instance_id=self.instance_id,
+ mount_point=self.mount_point)
+ fp = open('/usr/local/bin/ebs_backup', 'w')
+ fp.write(s)
+ fp.close()
+ self.run('chmod +x /usr/local/bin/ebs_backup')
+
+ def create_backup_cleanup_script(self):
+ fp = open('/usr/local/bin/ebs_backup_cleanup', 'w')
+ fp.write(BackupCleanupScript)
+ fp.close()
+ self.run('chmod +x /usr/local/bin/ebs_backup_cleanup')
+
+ def handle_mount_point(self):
+ boto.log.info('handle_mount_point')
+ if not os.path.isdir(self.mount_point):
+ boto.log.info('making directory')
+ # mount directory doesn't exist so create it
+ self.run("mkdir %s" % self.mount_point)
+ else:
+ boto.log.info('directory exists already')
+ self.run('mount -l')
+ lines = self.last_command.output.split('\n')
+ for line in lines:
+ t = line.split()
+ if t and t[2] == self.mount_point:
+ # something is already mounted at the mount point
+ # unmount that and mount it as /tmp
+ if t[0] != self.device:
+ self.run('umount %s' % self.mount_point)
+ self.run('mount %s /tmp' % t[0])
+ self.run('chmod 777 /tmp')
+ break
+ # Mount up our new EBS volume onto mount_point
+ self.run("mount %s %s" % (self.device, self.mount_point))
+ self.run('xfs_growfs %s' % self.mount_point)
+
+ def update_fstab(self):
+ f = open("/etc/fstab", "a")
+ f.write('%s\t%s\txfs\tdefaults 0 0\n' % (self.mount_point, self.device))
+ f.close()
+
+ def install(self):
+ # First, find and attach the volume
+ self.attach()
+
+ # Install the xfs tools
+ self.run('apt-get -y install xfsprogs xfsdump')
+
+ # Check to see if the filesystem was created or not
+ self.make_fs()
+
+ # create the /ebs directory for mounting
+ self.handle_mount_point()
+
+ # create the backup script
+ self.create_backup_script()
+
+ # Set up the backup script
+ minute = boto.config.get('EBS', 'backup_cron_minute', '0')
+ hour = boto.config.get('EBS', 'backup_cron_hour', '4,16')
+ self.add_cron("ebs_backup", "/usr/local/bin/ebs_backup", minute=minute, hour=hour)
+
+ # Set up the backup cleanup script
+ minute = boto.config.get('EBS', 'backup_cleanup_cron_minute')
+ hour = boto.config.get('EBS', 'backup_cleanup_cron_hour')
+ if (minute != None) and (hour != None):
+ self.create_backup_cleanup_script();
+ self.add_cron("ebs_backup_cleanup", "/usr/local/bin/ebs_backup_cleanup", minute=minute, hour=hour)
+
+ # Set up the fstab
+ self.update_fstab()
+
+ def main(self):
+ if not os.path.exists(self.device):
+ self.install()
+ else:
+ boto.log.info("Device %s is already attached, skipping EBS Installer" % self.device)
diff --git a/vendor/boto/boto/pyami/installers/ubuntu/installer.py b/vendor/boto/boto/pyami/installers/ubuntu/installer.py
new file mode 100644
index 0000000000..370d63fd7b
--- /dev/null
+++ b/vendor/boto/boto/pyami/installers/ubuntu/installer.py
@@ -0,0 +1,96 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+import boto.pyami.installers
+import os
+import os.path
+import stat
+import boto
+import random
+from pwd import getpwnam
+
+class Installer(boto.pyami.installers.Installer):
+ """
+ Base Installer class for Ubuntu-based AMI's
+ """
+ def add_cron(self, name, command, minute="*", hour="*", mday="*", month="*", wday="*", who="root", env=None):
+ """
+ Write a file to /etc/cron.d to schedule a command
+ env is a dict containing environment variables you want to set in the file
+ name will be used as the name of the file
+ """
+ if minute == 'random':
+ minute = str(random.randrange(60))
+ if hour == 'random':
+ hour = str(random.randrange(24))
+ fp = open('/etc/cron.d/%s' % name, "w")
+ if env:
+ for key, value in env.items():
+ fp.write('%s=%s\n' % (key, value))
+ fp.write('%s %s %s %s %s %s %s\n' % (minute, hour, mday, month, wday, who, command))
+ fp.close()
+
+ def add_init_script(self, file, name):
+ """
+ Add this file to the init.d directory
+ """
+ f_path = os.path.join("/etc/init.d", name)
+ f = open(f_path, "w")
+ f.write(file)
+ f.close()
+ os.chmod(f_path, stat.S_IREAD| stat.S_IWRITE | stat.S_IEXEC)
+ self.run("/usr/sbin/update-rc.d %s defaults" % name)
+
+ def add_env(self, key, value):
+ """
+ Add an environemnt variable
+ For Ubuntu, the best place is /etc/environment. Values placed here do
+ not need to be exported.
+ """
+ boto.log.info('Adding env variable: %s=%s' % (key, value))
+ if not os.path.exists("/etc/environment.orig"):
+ self.run('cp /etc/environment /etc/environment.orig', notify=False, exit_on_error=False)
+ fp = open('/etc/environment', 'a')
+ fp.write('\n%s="%s"' % (key, value))
+ fp.close()
+ os.environ[key] = value
+
+ def stop(self, service_name):
+ self.run('/etc/init.d/%s stop' % service_name)
+
+ def start(self, service_name):
+ self.run('/etc/init.d/%s start' % service_name)
+
+ def create_user(self, user):
+ """
+ Create a user on the local system
+ """
+ self.run("useradd -m %s" % user)
+ usr = getpwnam(user)
+ return usr
+
+
+ def install(self):
+ """
+ This is the only method you need to override
+ """
+ raise NotImplementedError
+
diff --git a/vendor/boto/boto/pyami/installers/ubuntu/mysql.py b/vendor/boto/boto/pyami/installers/ubuntu/mysql.py
new file mode 100644
index 0000000000..490e5dbb4f
--- /dev/null
+++ b/vendor/boto/boto/pyami/installers/ubuntu/mysql.py
@@ -0,0 +1,109 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+"""
+This installer will install mysql-server on an Ubuntu machine.
+In addition to the normal installation done by apt-get, it will
+also configure the new MySQL server to store it's data files in
+a different location. By default, this is /mnt but that can be
+configured in the [MySQL] section of the boto config file passed
+to the instance.
+"""
+from boto.pyami.installers.ubuntu.installer import Installer
+import os
+import boto
+from boto.utils import ShellCommand
+from ConfigParser import SafeConfigParser
+import time
+
+ConfigSection = """
+[MySQL]
+root_password = <will be used as MySQL root password, default none>
+data_dir = <new data dir for MySQL, default is /mnt>
+"""
+
+class MySQL(Installer):
+
+ def install(self):
+ self.run('apt-get update')
+ self.run('apt-get -y install mysql-server', notify=True, exit_on_error=True)
+
+# def set_root_password(self, password=None):
+# if not password:
+# password = boto.config.get('MySQL', 'root_password')
+# if password:
+# self.run('mysqladmin -u root password %s' % password)
+# return password
+
+ def change_data_dir(self, password=None):
+ data_dir = boto.config.get('MySQL', 'data_dir', '/mnt')
+ fresh_install = False;
+ is_mysql_running_command = ShellCommand('mysqladmin ping') # exit status 0 if mysql is running
+ is_mysql_running_command.run()
+ if is_mysql_running_command.getStatus() == 0:
+ # mysql is running. This is the state apt-get will leave it in. If it isn't running,
+ # that means mysql was already installed on the AMI and there's no need to stop it,
+ # saving 40 seconds on instance startup.
+ time.sleep(10) #trying to stop mysql immediately after installing it fails
+ # We need to wait until mysql creates the root account before we kill it
+ # or bad things will happen
+ i = 0
+ while self.run("echo 'quit' | mysql -u root") != 0 and i<5:
+ time.sleep(5)
+ i = i + 1
+ self.run('/etc/init.d/mysql stop')
+ self.run("pkill -9 mysql")
+
+ mysql_path = os.path.join(data_dir, 'mysql')
+ if not os.path.exists(mysql_path):
+ self.run('mkdir %s' % mysql_path)
+ fresh_install = True;
+ self.run('chown -R mysql:mysql %s' % mysql_path)
+ fp = open('/etc/mysql/conf.d/use_mnt.cnf', 'w')
+ fp.write('# created by pyami\n')
+ fp.write('# use the %s volume for data\n' % data_dir)
+ fp.write('[mysqld]\n')
+ fp.write('datadir = %s\n' % mysql_path)
+ fp.write('log_bin = %s\n' % os.path.join(mysql_path, 'mysql-bin.log'))
+ fp.close()
+ if fresh_install:
+ self.run('cp -pr /var/lib/mysql/* %s/' % mysql_path)
+ self.start('mysql')
+ else:
+ #get the password ubuntu expects to use:
+ config_parser = SafeConfigParser()
+ config_parser.read('/etc/mysql/debian.cnf')
+ password = config_parser.get('client', 'password')
+ # start the mysql deamon, then mysql with the required grant statement piped into it:
+ self.start('mysql')
+ time.sleep(10) #time for mysql to start
+ grant_command = "echo \"GRANT ALL PRIVILEGES ON *.* TO 'debian-sys-maint'@'localhost' IDENTIFIED BY '%s' WITH GRANT OPTION;\" | mysql" % password
+ while self.run(grant_command) != 0:
+ time.sleep(5)
+ # leave mysqld running
+
+ def main(self):
+ self.install()
+ # change_data_dir runs 'mysql -u root' which assumes there is no mysql password, i
+ # and changing that is too ugly to be worth it:
+ #self.set_root_password()
+ self.change_data_dir()
+
diff --git a/vendor/boto/boto/pyami/installers/ubuntu/trac.py b/vendor/boto/boto/pyami/installers/ubuntu/trac.py
new file mode 100644
index 0000000000..ef83af7aac
--- /dev/null
+++ b/vendor/boto/boto/pyami/installers/ubuntu/trac.py
@@ -0,0 +1,139 @@
+# Copyright (c) 2008 Chris Moyer http://coredumped.org
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+from boto.pyami.installers.ubuntu.installer import Installer
+import boto
+import os
+
+class Trac(Installer):
+ """
+ Install Trac and DAV-SVN
+ Sets up a Vhost pointing to [Trac]->home
+ Using the config parameter [Trac]->hostname
+ Sets up a trac environment for every directory found under [Trac]->data_dir
+
+ [Trac]
+ name = My Foo Server
+ hostname = trac.foo.com
+ home = /mnt/sites/trac
+ data_dir = /mnt/trac
+ svn_dir = /mnt/subversion
+ server_admin = root@foo.com
+ sdb_auth_domain = users
+ # Optional
+ SSLCertificateFile = /mnt/ssl/foo.crt
+ SSLCertificateKeyFile = /mnt/ssl/foo.key
+ SSLCertificateChainFile = /mnt/ssl/FooCA.crt
+
+ """
+
+ def install(self):
+ self.run('apt-get -y install trac', notify=True, exit_on_error=True)
+ self.run('apt-get -y install libapache2-svn', notify=True, exit_on_error=True)
+ self.run("a2enmod ssl")
+ self.run("a2enmod mod_python")
+ self.run("a2enmod dav_svn")
+ self.run("a2enmod rewrite")
+ # Make sure that boto.log is writable by everyone so that subversion post-commit hooks can
+ # write to it.
+ self.run("touch /var/log/boto.log")
+ self.run("chmod a+w /var/log/boto.log")
+
+ def setup_vhost(self):
+ domain = boto.config.get("Trac", "hostname").strip()
+ if domain:
+ domain_info = domain.split('.')
+ cnf = open("/etc/apache2/sites-available/%s" % domain_info[0], "w")
+ cnf.write("NameVirtualHost *:80\n")
+ if boto.config.get("Trac", "SSLCertificateFile"):
+ cnf.write("NameVirtualHost *:443\n\n")
+ cnf.write("<VirtualHost *:80>\n")
+ cnf.write("\tServerAdmin %s\n" % boto.config.get("Trac", "server_admin").strip())
+ cnf.write("\tServerName %s\n" % domain)
+ cnf.write("\tRewriteEngine On\n")
+ cnf.write("\tRewriteRule ^(.*)$ https://%s$1\n" % domain)
+ cnf.write("</VirtualHost>\n\n")
+
+ cnf.write("<VirtualHost *:443>\n")
+ else:
+ cnf.write("<VirtualHost *:80>\n")
+
+ cnf.write("\tServerAdmin %s\n" % boto.config.get("Trac", "server_admin").strip())
+ cnf.write("\tServerName %s\n" % domain)
+ cnf.write("\tDocumentRoot %s\n" % boto.config.get("Trac", "home").strip())
+
+ cnf.write("\t<Directory %s>\n" % boto.config.get("Trac", "home").strip())
+ cnf.write("\t\tOptions FollowSymLinks Indexes MultiViews\n")
+ cnf.write("\t\tAllowOverride All\n")
+ cnf.write("\t\tOrder allow,deny\n")
+ cnf.write("\t\tallow from all\n")
+ cnf.write("\t</Directory>\n")
+
+ cnf.write("\t<Location />\n")
+ cnf.write("\t\tAuthType Basic\n")
+ cnf.write("\t\tAuthName \"%s\"\n" % boto.config.get("Trac", "name"))
+ cnf.write("\t\tRequire valid-user\n")
+ cnf.write("\t\tAuthUserFile /mnt/apache/passwd/passwords\n")
+ cnf.write("\t</Location>\n")
+
+ data_dir = boto.config.get("Trac", "data_dir")
+ for env in os.listdir(data_dir):
+ if(env[0] != "."):
+ cnf.write("\t<Location /trac/%s>\n" % env)
+ cnf.write("\t\tSetHandler mod_python\n")
+ cnf.write("\t\tPythonInterpreter main_interpreter\n")
+ cnf.write("\t\tPythonHandler trac.web.modpython_frontend\n")
+ cnf.write("\t\tPythonOption TracEnv %s/%s\n" % (data_dir, env))
+ cnf.write("\t\tPythonOption TracUriRoot /trac/%s\n" % env)
+ cnf.write("\t</Location>\n")
+
+ svn_dir = boto.config.get("Trac", "svn_dir")
+ for env in os.listdir(svn_dir):
+ if(env[0] != "."):
+ cnf.write("\t<Location /svn/%s>\n" % env)
+ cnf.write("\t\tDAV svn\n")
+ cnf.write("\t\tSVNPath %s/%s\n" % (svn_dir, env))
+ cnf.write("\t</Location>\n")
+
+ cnf.write("\tErrorLog /var/log/apache2/error.log\n")
+ cnf.write("\tLogLevel warn\n")
+ cnf.write("\tCustomLog /var/log/apache2/access.log combined\n")
+ cnf.write("\tServerSignature On\n")
+ SSLCertificateFile = boto.config.get("Trac", "SSLCertificateFile")
+ if SSLCertificateFile:
+ cnf.write("\tSSLEngine On\n")
+ cnf.write("\tSSLCertificateFile %s\n" % SSLCertificateFile)
+
+ SSLCertificateKeyFile = boto.config.get("Trac", "SSLCertificateKeyFile")
+ if SSLCertificateKeyFile:
+ cnf.write("\tSSLCertificateKeyFile %s\n" % SSLCertificateKeyFile)
+
+ SSLCertificateChainFile = boto.config.get("Trac", "SSLCertificateChainFile")
+ if SSLCertificateChainFile:
+ cnf.write("\tSSLCertificateChainFile %s\n" % SSLCertificateChainFile)
+ cnf.write("</VirtualHost>\n")
+ cnf.close()
+ self.run("a2ensite %s" % domain_info[0])
+ self.run("/etc/init.d/apache2 force-reload")
+
+ def main(self):
+ self.install()
+ self.setup_vhost()
diff --git a/vendor/boto/boto/pyami/launch_ami.py b/vendor/boto/boto/pyami/launch_ami.py
new file mode 100755
index 0000000000..243d56d2eb
--- /dev/null
+++ b/vendor/boto/boto/pyami/launch_ami.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+import getopt
+import sys
+import imp
+import time
+import boto
+
+usage_string = """
+SYNOPSIS
+ launch_ami.py -a ami_id [-b script_bucket] [-s script_name]
+ [-m module] [-c class_name] [-r]
+ [-g group] [-k key_name] [-n num_instances]
+ [-w] [extra_data]
+ Where:
+ ami_id - the id of the AMI you wish to launch
+ module - The name of the Python module containing the class you
+ want to run when the instance is started. If you use this
+ option the Python module must already be stored on the
+ instance in a location that is on the Python path.
+ script_file - The name of a local Python module that you would like
+ to have copied to S3 and then run on the instance
+ when it is started. The specified module must be
+ import'able (i.e. in your local Python path). It
+ will then be copied to the specified bucket in S3
+ (see the -b option). Once the new instance(s)
+ start up the script will be copied from S3 and then
+ run locally on the instance.
+ class_name - The name of the class to be instantiated within the
+ module or script file specified.
+ script_bucket - the name of the bucket in which the script will be
+ stored
+ group - the name of the security group the instance will run in
+ key_name - the name of the keypair to use when launching the AMI
+ num_instances - how many instances of the AMI to launch (default 1)
+ input_queue_name - Name of SQS to read input messages from
+ output_queue_name - Name of SQS to write output messages to
+ extra_data - additional name-value pairs that will be passed as
+ userdata to the newly launched instance. These should
+ be of the form "name=value"
+ The -r option reloads the Python module to S3 without launching
+ another instance. This can be useful during debugging to allow
+ you to test a new version of your script without shutting down
+ your instance and starting up another one.
+ The -w option tells the script to run synchronously, meaning to
+ wait until the instance is actually up and running. It then prints
+ the IP address and internal and external DNS names before exiting.
+"""
+
+def usage():
+ print usage_string
+ sys.exit()
+
+def main():
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'a:b:c:g:hi:k:m:n:o:rs:w',
+ ['ami', 'bucket', 'class', 'group', 'help',
+ 'inputqueue', 'keypair', 'module',
+ 'numinstances', 'outputqueue',
+ 'reload', 'script_name', 'wait'])
+ except:
+ usage()
+ params = {'module_name' : None,
+ 'script_name' : None,
+ 'class_name' : None,
+ 'script_bucket' : None,
+ 'group' : 'default',
+ 'keypair' : None,
+ 'ami' : None,
+ 'num_instances' : 1,
+ 'input_queue_name' : None,
+ 'output_queue_name' : None}
+ reload = None
+ wait = None
+ for o, a in opts:
+ if o in ('-a', '--ami'):
+ params['ami'] = a
+ if o in ('-b', '--bucket'):
+ params['script_bucket'] = a
+ if o in ('-c', '--class'):
+ params['class_name'] = a
+ if o in ('-g', '--group'):
+ params['group'] = a
+ if o in ('-h', '--help'):
+ usage()
+ if o in ('-i', '--inputqueue'):
+ params['input_queue_name'] = a
+ if o in ('-k', '--keypair'):
+ params['keypair'] = a
+ if o in ('-m', '--module'):
+ params['module_name'] = a
+ if o in ('-n', '--num_instances'):
+ params['num_instances'] = int(a)
+ if o in ('-o', '--outputqueue'):
+ params['output_queue_name'] = a
+ if o in ('-r', '--reload'):
+ reload = True
+ if o in ('-s', '--script'):
+ params['script_name'] = a
+ if o in ('-w', '--wait'):
+ wait = True
+
+ # check required fields
+ required = ['ami']
+ for pname in required:
+ if not params.get(pname, None):
+ print '%s is required' % pname
+ usage()
+ if params['script_name']:
+ # first copy the desired module file to S3 bucket
+ if reload:
+ print 'Reloading module %s to S3' % params['script_name']
+ else:
+ print 'Copying module %s to S3' % params['script_name']
+ l = imp.find_module(params['script_name'])
+ c = boto.connect_s3()
+ bucket = c.get_bucket(params['script_bucket'])
+ key = bucket.new_key(params['script_name']+'.py')
+ key.set_contents_from_file(l[0])
+ params['script_md5'] = key.md5
+ # we have everything we need, now build userdata string
+ l = []
+ for k, v in params.items():
+ if v:
+ l.append('%s=%s' % (k, v))
+ c = boto.connect_ec2()
+ l.append('aws_access_key_id=%s' % c.aws_access_key_id)
+ l.append('aws_secret_access_key=%s' % c.aws_secret_access_key)
+ for kv in args:
+ l.append(kv)
+ s = '|'.join(l)
+ if not reload:
+ rs = c.get_all_images([params['ami']])
+ img = rs[0]
+ r = img.run(user_data=s, key_name=params['keypair'],
+ security_groups=[params['group']],
+ max_count=params.get('num_instances', 1))
+ print 'AMI: %s - %s (Started)' % (params['ami'], img.location)
+ print 'Reservation %s contains the following instances:' % r.id
+ for i in r.instances:
+ print '\t%s' % i.id
+ if wait:
+ running = False
+ while not running:
+ time.sleep(30)
+ [i.update() for i in r.instances]
+ status = [i.state for i in r.instances]
+ print status
+ if status.count('running') == len(r.instances):
+ running = True
+ for i in r.instances:
+ print 'Instance: %s' % i.ami_launch_index
+ print 'Public DNS Name: %s' % i.public_dns_name
+ print 'Private DNS Name: %s' % i.private_dns_name
+
+if __name__ == "__main__":
+ main()
+
diff --git a/vendor/boto/boto/pyami/scriptbase.py b/vendor/boto/boto/pyami/scriptbase.py
new file mode 100644
index 0000000000..ef8bd28f1e
--- /dev/null
+++ b/vendor/boto/boto/pyami/scriptbase.py
@@ -0,0 +1,44 @@
+import os
+import sys
+from boto.utils import ShellCommand, get_ts
+import boto
+import boto.utils
+
+class ScriptBase:
+
+ def __init__(self, config_file=None):
+ self.instance_id = boto.config.get('Instance', 'instance-id', 'default')
+ self.name = self.__class__.__name__
+ self.ts = get_ts()
+ if config_file:
+ boto.config.read(config_file)
+
+ def notify(self, subject, body=''):
+ boto.utils.notify(subject, body)
+
+ def mkdir(self, path):
+ if not os.path.isdir(path):
+ try:
+ os.mkdir(path)
+ except:
+ boto.log.error('Error creating directory: %s' % path)
+
+ def umount(self, path):
+ if os.path.ismount(path):
+ self.run('umount %s' % path)
+
+ def run(self, command, notify=True, exit_on_error=False):
+ self.last_command = ShellCommand(command)
+ if self.last_command.status != 0:
+ boto.log.error('Error running command: "%s". Output: "%s"' % (command, self.last_command.output))
+ if notify:
+ self.notify('Error encountered', \
+ 'Error running the following command:\n\t%s\n\nCommand output:\n\t%s' % \
+ (command, self.last_command.output))
+ if exit_on_error:
+ sys.exit(-1)
+ return self.last_command.status
+
+ def main(self):
+ pass
+
diff --git a/vendor/boto/boto/pyami/startup.py b/vendor/boto/boto/pyami/startup.py
new file mode 100644
index 0000000000..8443bff122
--- /dev/null
+++ b/vendor/boto/boto/pyami/startup.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+import sys
+import boto
+from boto.utils import find_class
+from boto import config
+from boto.pyami.scriptbase import ScriptBase
+
+
+class Startup(ScriptBase):
+
+ def run_scripts(self):
+ scripts = config.get('Pyami', 'scripts')
+ if scripts:
+ for script in scripts.split(','):
+ script = script.strip(" ")
+ try:
+ pos = script.rfind('.')
+ if pos > 0:
+ mod_name = script[0:pos]
+ cls_name = script[pos+1:]
+ cls = find_class(mod_name, cls_name)
+ boto.log.info('Running Script: %s' % script)
+ s = cls()
+ s.main()
+ else:
+ boto.log.warning('Trouble parsing script: %s' % script)
+ except Exception:
+ boto.log.exception('Problem Running Script: %s' % script)
+
+ def main(self):
+ self.run_scripts()
+ self.notify('Startup Completed for %s' % config.get('Instance', 'instance-id'))
+
+if __name__ == "__main__":
+ if not config.has_section('loggers'):
+ boto.set_file_logger('startup', '/var/log/boto.log')
+ sys.path.append(config.get('Pyami', 'working_dir'))
+ su = Startup()
+ su.main()
diff --git a/vendor/boto/boto/rds/__init__.py b/vendor/boto/boto/rds/__init__.py
new file mode 100644
index 0000000000..2283e2ce4a
--- /dev/null
+++ b/vendor/boto/boto/rds/__init__.py
@@ -0,0 +1,810 @@
+# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+import boto.utils
+import urllib
+from boto.connection import AWSQueryConnection
+from boto.rds.dbinstance import DBInstance
+from boto.rds.dbsecuritygroup import DBSecurityGroup
+from boto.rds.parametergroup import ParameterGroup
+from boto.rds.dbsnapshot import DBSnapshot
+from boto.rds.event import Event
+
+#boto.set_stream_logger('rds')
+
+class RDSConnection(AWSQueryConnection):
+
+ DefaultHost = 'rds.amazonaws.com'
+ APIVersion = '2009-10-16'
+ SignatureVersion = '2'
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, host=DefaultHost, debug=0,
+ https_connection_factory=None, path='/'):
+ AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_key,
+ is_secure, port, proxy, proxy_port, proxy_user,
+ proxy_pass, self.DefaultHost, debug,
+ https_connection_factory, path)
+
+ # DB Instance methods
+
+ def get_all_dbinstances(self, instance_id=None, max_records=None,
+ marker=None):
+ """
+ Retrieve all the DBInstances in your account.
+
+ :type instance_id: str
+ :param instance_id: DB Instance identifier. If supplied, only information
+ this instance will be returned. Otherwise, info
+ about all DB Instances will be returned.
+
+ :type max_records: int
+ :param max_records: The maximum number of records to be returned.
+ If more results are available, a MoreToken will
+ be returned in the response that can be used to
+ retrieve additional records. Default is 100.
+
+ :type marker: str
+ :param marker: The marker provided by a previous request.
+
+ :rtype: list
+ :return: A list of :class:`boto.rds.dbinstance.DBInstance`
+ """
+ params = {}
+ if instance_id:
+ params['DBInstanceIdentifier'] = instance_id
+ if max_records:
+ params['MaxRecords'] = max_records
+ if marker:
+ params['Marker'] = marker
+ return self.get_list('DescribeDBInstances', params, [('DBInstance', DBInstance)])
+
+ def create_dbinstance(self, id, allocated_storage, instance_class,
+ master_username, master_password, port=3306,
+ engine='MySQL5.1', db_name=None, param_group=None,
+ security_groups=None, availability_zone=None,
+ preferred_maintenance_window=None,
+ backup_retention_period=None,
+ preferred_backup_window=None):
+ """
+ Create a new DBInstance.
+
+ :type id: str
+ :param id: Unique identifier for the new instance.
+ Must contain 1-63 alphanumeric characters.
+ First character must be a letter.
+ May not end with a hyphen or contain two consecutive hyphens
+
+ :type allocated_storage: int
+ :param allocated_storage: Initially allocated storage size, in GBs.
+ Valid values are [5-1024]
+
+ :type instance_class: str
+ :param instance_class: The compute and memory capacity of the DBInstance.
+ Valid values are:
+ db.m1.small | db.m1.large | db.m1.xlarge |
+ db.m2.2xlarge | db.m2.4xlarge
+
+ :type engine: str
+ :param engine: Name of database engine. Must be MySQL5.1 for now.
+
+ :type master_username: str
+ :param master_username: Name of master user for the DBInstance.
+ Must be 1-15 alphanumeric characters, first must be
+ a letter.
+
+ :type master_password: str
+ :param master_password: Password of master user for the DBInstance.
+ Must be 4-16 alphanumeric characters.
+
+ :type port: int
+ :param port: Port number on which database accepts connections.
+ Valid values [1115-65535]. Defaults to 3306.
+
+ :type db_name: str
+ :param db_name: Name of a database to create when the DBInstance
+ is created. Default is to create no databases.
+
+ :type param_group: str
+ :param param_group: Name of DBParameterGroup to associate with
+ this DBInstance. If no groups are specified
+ no parameter groups will be used.
+
+ :type security_groups: list of str or list of DBSecurityGroup objects
+ :param security_groups: List of names of DBSecurityGroup to authorize on
+ this DBInstance.
+
+ :type availability_zone: str
+ :param availability_zone: Name of the availability zone to place
+ DBInstance into.
+
+ :type preferred_maintenance_window: str
+ :param preferred_maintenance_window: The weekly time range (in UTC) during
+ which maintenance can occur.
+ Default is Sun:05:00-Sun:09:00
+
+ :type backup_retention_period: int
+ :param backup_retention_period: The number of days for which automated
+ backups are retained. Setting this to
+ zero disables automated backups.
+
+ :type preferred_backup_window: str
+ :param preferred_backup_window: The daily time range during which
+ automated backups are created (if
+ enabled). Must be in h24:mi-hh24:mi
+ format (UTC).
+
+ :rtype: :class:`boto.rds.dbinstance.DBInstance`
+ :return: The new db instance.
+ """
+ params = {'DBInstanceIdentifier' : id,
+ 'AllocatedStorage' : allocated_storage,
+ 'DBInstanceClass' : instance_class,
+ 'Engine' : engine,
+ 'MasterUsername' : master_username,
+ 'MasterUserPassword' : master_password}
+ if port:
+ params['Port'] = port
+ if db_name:
+ params['DBName'] = db_name
+ if param_group:
+ params['DBParameterGroup'] = param_group
+ if security_groups:
+ l = []
+ for group in security_groups:
+ if isinstance(group, DBSecurityGroup):
+ l.append(group.name)
+ else:
+ l.append(group)
+ self.build_list_params(params, l, 'DBSecurityGroups.member')
+ if availability_zone:
+ params['AvailabilityZone'] = availability_zone
+ if preferred_maintenance_window:
+ params['PreferredMaintenanceWindow'] = preferred_maintenance_window
+ if backup_retention_period:
+ params['BackupRetentionPeriod'] = backup_retention_period
+ if preferred_backup_window:
+ params['PreferredBackupWindow'] = preferred_backup_window
+
+ return self.get_object('CreateDBInstance', params, DBInstance)
+
+ def modify_dbinstance(self, id, param_group=None, security_groups=None,
+ preferred_maintenance_window=None,
+ master_password=None, allocated_storage=None,
+ instance_class=None,
+ backup_retention_period=None,
+ preferred_backup_window=None,
+ apply_immediately=False):
+ """
+ Modify an existing DBInstance.
+
+ :type id: str
+ :param id: Unique identifier for the new instance.
+
+ :type security_groups: list of str or list of DBSecurityGroup objects
+ :param security_groups: List of names of DBSecurityGroup to authorize on
+ this DBInstance.
+
+ :type preferred_maintenance_window: str
+ :param preferred_maintenance_window: The weekly time range (in UTC) during
+ which maintenance can occur.
+ Default is Sun:05:00-Sun:09:00
+
+ :type master_password: str
+ :param master_password: Password of master user for the DBInstance.
+ Must be 4-15 alphanumeric characters.
+
+ :type allocated_storage: int
+ :param allocated_storage: The new allocated storage size, in GBs.
+ Valid values are [5-1024]
+
+ :type instance_class: str
+ :param instance_class: The compute and memory capacity of the DBInstance.
+ Changes will be applied at next maintenance
+ window unless apply_immediately is True.
+ Valid values are:
+ db.m1.small | db.m1.large | db.m1.xlarge |
+ db.m2.2xlarge | db.m2.4xlarge
+
+ :type apply_immediately: bool
+ :param apply_immediately: If true, the modifications will be applied
+ as soon as possible rather than waiting for
+ the next preferred maintenance window.
+
+ :type backup_retention_period: int
+ :param backup_retention_period: The number of days for which automated
+ backups are retained. Setting this to
+ zero disables automated backups.
+
+ :type preferred_backup_window: str
+ :param preferred_backup_window: The daily time range during which
+ automated backups are created (if
+ enabled). Must be in h24:mi-hh24:mi
+ format (UTC).
+
+ :rtype: :class:`boto.rds.dbinstance.DBInstance`
+ :return: The modified db instance.
+ """
+ params = {'DBInstanceIdentifier' : id}
+ if param_group:
+ params['DBParameterGroupName'] = param_group
+ if security_groups:
+ l = []
+ for group in security_groups:
+ if isinstance(group, DBSecurityGroup):
+ l.append(group.name)
+ else:
+ l.append(group)
+ self.build_list_params(params, l, 'DBSecurityGroups.member')
+ if preferred_maintenance_window:
+ params['PreferredMaintenanceWindow'] = preferred_maintenance_window
+ if master_password:
+ params['MasterUserPassword'] = master_password
+ if allocated_storage:
+ params['AllocatedStorage'] = allocated_storage
+ if instance_class:
+ params['DBInstanceClass'] = instance_class
+ if backup_retention_period:
+ params['BackupRetentionPeriod'] = backup_retention_period
+ if preferred_backup_window:
+ params['PreferredBackupWindow'] = preferred_backup_window
+ if apply_immediately:
+ params['ApplyImmediately'] = 'true'
+
+ return self.get_object('ModifyDBInstance', params, DBInstance)
+
+ def delete_dbinstance(self, id, skip_final_snapshot=False,
+ final_snapshot_id=''):
+ """
+ Delete an existing DBInstance.
+
+ :type id: str
+ :param id: Unique identifier for the new instance.
+
+ :type skip_final_snapshot: bool
+ :param skip_final_snapshot: This parameter determines whether a final
+ db snapshot is created before the instance
+ is deleted. If True, no snapshot is created.
+ If False, a snapshot is created before
+ deleting the instance.
+
+ :type final_snapshot_id: str
+ :param final_snapshot_id: If a final snapshot is requested, this
+ is the identifier used for that snapshot.
+
+ :rtype: :class:`boto.rds.dbinstance.DBInstance`
+ :return: The deleted db instance.
+ """
+ params = {'DBInstanceIdentifier' : id}
+ if skip_final_snapshot:
+ params['SkipFinalSnapshot'] = 'true'
+ else:
+ params['SkipFinalSnapshot'] = 'false'
+ params['FinalDBSnapshotIdentifier'] = final_snapshot_id
+ return self.get_object('DeleteDBInstance', params, DBInstance)
+
+ # DBParameterGroup methods
+
+ def get_all_dbparameter_groups(self, groupname=None, max_records=None,
+ marker=None):
+ """
+ Get all parameter groups associated with your account in a region.
+
+ :type groupname: str
+ :param groupname: The name of the DBParameter group to retrieve.
+ If not provided, all DBParameter groups will be returned.
+
+ :type max_records: int
+ :param max_records: The maximum number of records to be returned.
+ If more results are available, a MoreToken will
+ be returned in the response that can be used to
+ retrieve additional records. Default is 100.
+
+ :type marker: str
+ :param marker: The marker provided by a previous request.
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.parametergroup.ParameterGroup`
+ """
+ params = {}
+ if groupname:
+ params['DBParameterGroupName'] = groupname
+ if max_records:
+ params['MaxRecords'] = max_records
+ if marker:
+ params['Marker'] = marker
+ return self.get_list('DescribeDBParameterGroups', params,
+ [('DBParameterGroup', ParameterGroup)])
+
+ def get_all_dbparameters(self, groupname, source=None,
+ max_records=None, marker=None):
+ """
+ Get all parameters associated with a ParameterGroup
+
+ :type groupname: str
+ :param groupname: The name of the DBParameter group to retrieve.
+
+ :type source: str
+ :param source: Specifies which parameters to return.
+ If not specified, all parameters will be returned.
+ Valid values are: user|system|engine-default
+
+ :type max_records: int
+ :param max_records: The maximum number of records to be returned.
+ If more results are available, a MoreToken will
+ be returned in the response that can be used to
+ retrieve additional records. Default is 100.
+
+ :type marker: str
+ :param marker: The marker provided by a previous request.
+
+ :rtype: :class:`boto.ec2.parametergroup.ParameterGroup`
+ :return: The ParameterGroup
+ """
+ params = {'DBParameterGroupName' : groupname}
+ if source:
+ params['Source'] = source
+ if max_records:
+ params['MaxRecords'] = max_records
+ if marker:
+ params['Marker'] = marker
+ pg = self.get_object('DescribeDBParameters', params, ParameterGroup)
+ pg.name = groupname
+ return pg
+
+ def create_parameter_group(self, name, engine='MySQL5.1', description=''):
+ """
+ Create a new dbparameter group for your account.
+
+ :type name: string
+ :param name: The name of the new dbparameter group
+
+ :type engine: str
+ :param engine: Name of database engine. Must be MySQL5.1 for now.
+
+ :type description: string
+ :param description: The description of the new security group
+
+ :rtype: :class:`boto.rds.dbsecuritygroup.DBSecurityGroup`
+ :return: The newly created DBSecurityGroup
+ """
+ params = {'DBParameterGroupName': name,
+ 'Engine': engine,
+ 'Description' : description}
+ return self.get_object('CreateDBParameterGroup', params, ParameterGroup)
+
+ def modify_parameter_group(self, name, parameters=None):
+ """
+ Modify a parameter group for your account.
+
+ :type name: string
+ :param name: The name of the new parameter group
+
+ :type parameters: list of :class:`boto.rds.parametergroup.Parameter`
+ :param parameters: The new parameters
+
+ :rtype: :class:`boto.rds.parametergroup.ParameterGroup`
+ :return: The newly created ParameterGroup
+ """
+ params = {'DBParameterGroupName': name}
+ for i in range(0, len(parameters)):
+ parameter = parameters[i]
+ parameter.merge(params, i+1)
+ return self.get_list('ModifyDBParameterGroup', params, ParameterGroup)
+
+ def reset_parameter_group(self, name, reset_all_params=False, parameters=None):
+ """
+ Resets some or all of the parameters of a ParameterGroup to the
+ default value
+
+ :type key_name: string
+ :param key_name: The name of the ParameterGroup to reset
+
+ :type parameters: list of :class:`boto.rds.parametergroup.Parameter`
+ :param parameters: The parameters to reset. If not supplied, all parameters
+ will be reset.
+ """
+ params = {'DBParameterGroupName':name}
+ if reset_all_params:
+ params['ResetAllParameters'] = 'true'
+ else:
+ params['ResetAllParameters'] = 'false'
+ for i in range(0, len(parameters)):
+ parameter = parameters[i]
+ parameter.merge(params, i+1)
+ return self.get_status('ResetDBParameterGroup', params)
+
+ def delete_parameter_group(self, name):
+ """
+ Delete a DBSecurityGroup from your account.
+
+ :type key_name: string
+ :param key_name: The name of the DBSecurityGroup to delete
+ """
+ params = {'DBParameterGroupName':name}
+ return self.get_status('DeleteDBParameterGroup', params)
+
+ # DBSecurityGroup methods
+
+ def get_all_dbsecurity_groups(self, groupname=None, max_records=None,
+ marker=None):
+ """
+ Get all security groups associated with your account in a region.
+
+ :type groupnames: list
+ :param groupnames: A list of the names of security groups to retrieve.
+ If not provided, all security groups will be returned.
+
+ :type max_records: int
+ :param max_records: The maximum number of records to be returned.
+ If more results are available, a MoreToken will
+ be returned in the response that can be used to
+ retrieve additional records. Default is 100.
+
+ :type marker: str
+ :param marker: The marker provided by a previous request.
+
+ :rtype: list
+ :return: A list of :class:`boto.rds.dbsecuritygroup.DBSecurityGroup`
+ """
+ params = {}
+ if groupname:
+ params['DBSecurityGroupName'] = groupname
+ if max_records:
+ params['MaxRecords'] = max_records
+ if marker:
+ params['Marker'] = marker
+ return self.get_list('DescribeDBSecurityGroups', params,
+ [('DBSecurityGroup', DBSecurityGroup)])
+
+ def create_dbsecurity_group(self, name, description=None):
+ """
+ Create a new security group for your account.
+ This will create the security group within the region you
+ are currently connected to.
+
+ :type name: string
+ :param name: The name of the new security group
+
+ :type description: string
+ :param description: The description of the new security group
+
+ :rtype: :class:`boto.rds.dbsecuritygroup.DBSecurityGroup`
+ :return: The newly created DBSecurityGroup
+ """
+ params = {'DBSecurityGroupName':name}
+ if description:
+ params['DBSecurityGroupDescription'] = description
+ group = self.get_object('CreateDBSecurityGroup', params, DBSecurityGroup)
+ group.name = name
+ group.description = description
+ return group
+
+ def delete_dbsecurity_group(self, name):
+ """
+ Delete a DBSecurityGroup from your account.
+
+ :type key_name: string
+ :param key_name: The name of the DBSecurityGroup to delete
+ """
+ params = {'DBSecurityGroupName':name}
+ return self.get_status('DeleteDBSecurityGroup', params)
+
+ def authorize_dbsecurity_group(self, group_name, cidr_ip=None,
+ ec2_security_group_name=None,
+ ec2_security_group_owner_id=None):
+ """
+ Add a new rule to an existing security group.
+ You need to pass in either src_security_group_name and
+ src_security_group_owner_id OR a CIDR block but not both.
+
+ :type group_name: string
+ :param group_name: The name of the security group you are adding
+ the rule to.
+
+ :type ec2_security_group_name: string
+ :param ec2_security_group_name: The name of the EC2 security group you are
+ granting access to.
+
+ :type ec2_security_group_owner_id: string
+ :param ec2_security_group_owner_id: The ID of the owner of the EC2 security
+ group you are granting access to.
+
+ :type cidr_ip: string
+ :param cidr_ip: The CIDR block you are providing access to.
+ See http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
+
+ :rtype: bool
+ :return: True if successful.
+ """
+ params = {'DBSecurityGroupName':group_name}
+ if ec2_security_group_name:
+ params['EC2SecurityGroupName'] = ec2_security_group_name
+ if ec2_security_group_owner_id:
+ params['EC2SecurityGroupOwnerId'] = ec2_security_group_owner_id
+ if cidr_ip:
+ params['CIDRIP'] = urllib.quote(cidr_ip)
+ return self.get_object('AuthorizeDBSecurityGroupIngress', params, DBSecurityGroup)
+
+ def revoke_security_group(self, group_name, ec2_security_group_name=None,
+ ec2_security_group_owner_id=None, cidr_ip=None):
+ """
+ Remove an existing rule from an existing security group.
+ You need to pass in either ec2_security_group_name and
+ ec2_security_group_owner_id OR a CIDR block.
+
+ :type group_name: string
+ :param group_name: The name of the security group you are removing
+ the rule from.
+
+ :type ec2_security_group_name: string
+ :param ec2_security_group_name: The name of the EC2 security group you are
+ granting access to.
+
+ :type ec2_security_group_owner_id: string
+ :param ec2_security_group_owner_id: The ID of the owner of the EC2 security
+ group you are granting access to.
+
+ :type cidr_ip: string
+ :param cidr_ip: The CIDR block you are providing access to.
+ See http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
+
+ :rtype: bool
+ :return: True if successful.
+ """
+ params = {'DBSecurityGroupName':group_name}
+ if ec2_security_group_name:
+ params['EC2SecurityGroupName'] = ec2_security_group_name
+ if ec2_security_group_owner_id:
+ params['EC2SecurityGroupOwnerId'] = ec2_security_group_owner_id
+ if cidr_ip:
+ params['CIDRIP'] = cidr_ip
+ return self.get_object('RevokeDBSecurityGroupIngress', params, DBSecurityGroup)
+
+ # DBSnapshot methods
+
+ def get_all_dbsnapshots(self, snapshot_id=None, instance_id=None,
+ max_records=None, marker=None):
+ """
+ Get information about DB Snapshots.
+
+ :type snapshot_id: str
+ :param snapshot_id: The unique identifier of an RDS snapshot.
+ If not provided, all RDS snapshots will be returned.
+
+ :type instance_id: str
+ :param instance_id: The identifier of a DBInstance. If provided,
+ only the DBSnapshots related to that instance will
+ be returned.
+ If not provided, all RDS snapshots will be returned.
+
+ :type max_records: int
+ :param max_records: The maximum number of records to be returned.
+ If more results are available, a MoreToken will
+ be returned in the response that can be used to
+ retrieve additional records. Default is 100.
+
+ :type marker: str
+ :param marker: The marker provided by a previous request.
+
+ :rtype: list
+ :return: A list of :class:`boto.rds.dbsnapshot.DBSnapshot`
+ """
+ params = {}
+ if snapshot_id:
+ params['DBSnapshotIdentifier'] = snapshot_id
+ if instance_id:
+ params['DBInstanceIdentifier'] = instance_id
+ if max_records:
+ params['MaxRecords'] = max_records
+ if marker:
+ params['Marker'] = marker
+ return self.get_list('DescribeDBSnapshots', params,
+ [('DBSnapshot', DBSnapshot)])
+
+ def create_dbsnapshot(self, snapshot_id, dbinstance_id):
+ """
+ Create a new DB snapshot.
+
+ :type snapshot_id: string
+ :param snapshot_id: The identifier for the DBSnapshot
+
+ :type dbinstance_id: string
+ :param dbinstance_id: The source identifier for the RDS instance from
+ which the snapshot is created.
+
+ :rtype: :class:`boto.rds.dbsnapshot.DBSnapshot`
+ :return: The newly created DBSnapshot
+ """
+ params = {'DBSnapshotIdentifier' : snapshot_id,
+ 'DBInstanceIdentifier' : dbinstance_id}
+ return self.get_object('CreateDBSnapshot', params, DBSnapshot)
+
+ def delete_dbsnapshot(self, identifier):
+ """
+ Delete a DBSnapshot
+
+ :type identifier: string
+ :param identifier: The identifier of the DBSnapshot to delete
+ """
+ params = {'DBSnapshotIdentifier' : identifier}
+ return self.get_object('DeleteDBSnapshot', params, DBSnapshot)
+
+ def restore_dbinstance_from_dbsnapshot(self, identifier, instance_id,
+ instance_class, port=None,
+ availability_zone=None):
+
+ """
+ Create a new DBInstance from a DB snapshot.
+
+ :type identifier: string
+ :param identifier: The identifier for the DBSnapshot
+
+ :type instance_id: string
+ :param instance_id: The source identifier for the RDS instance from
+ which the snapshot is created.
+
+ :type instance_class: str
+ :param instance_class: The compute and memory capacity of the DBInstance.
+ Valid values are:
+ db.m1.small | db.m1.large | db.m1.xlarge |
+ db.m2.2xlarge | db.m2.4xlarge
+
+ :type port: int
+ :param port: Port number on which database accepts connections.
+ Valid values [1115-65535]. Defaults to 3306.
+
+ :type availability_zone: str
+ :param availability_zone: Name of the availability zone to place
+ DBInstance into.
+
+ :rtype: :class:`boto.rds.dbinstance.DBInstance`
+ :return: The newly created DBInstance
+ """
+ params = {'DBSnapshotIdentifier' : identifier,
+ 'DBInstanceIdentifier' : instance_id,
+ 'DBInstanceClass' : instance_class}
+ if port:
+ params['Port'] = port
+ if availability_zone:
+ params['AvailabilityZone'] = availability_zone
+ return self.get_object('RestoreDBInstanceFromDBSnapshot',
+ params, DBInstance)
+
+ def restore_dbinstance_from_point_in_time(self, source_instance_id,
+ target_instance_id,
+ use_latest=False,
+ restore_time=None,
+ dbinstance_class=None,
+ port=None,
+ availability_zone=None):
+
+ """
+ Create a new DBInstance from a point in time.
+
+ :type source_instance_id: string
+ :param source_instance_id: The identifier for the source DBInstance.
+
+ :type target_instance_id: string
+ :param target_instance_id: The identifier of the new DBInstance.
+
+ :type use_latest: bool
+ :param use_latest: If True, the latest snapshot availabile will
+ be used.
+
+ :type restore_time: datetime
+ :param restore_time: The date and time to restore from. Only
+ used if use_latest is False.
+
+ :type instance_class: str
+ :param instance_class: The compute and memory capacity of the DBInstance.
+ Valid values are:
+ db.m1.small | db.m1.large | db.m1.xlarge |
+ db.m2.2xlarge | db.m2.4xlarge
+
+ :type port: int
+ :param port: Port number on which database accepts connections.
+ Valid values [1115-65535]. Defaults to 3306.
+
+ :type availability_zone: str
+ :param availability_zone: Name of the availability zone to place
+ DBInstance into.
+
+ :rtype: :class:`boto.rds.dbinstance.DBInstance`
+ :return: The newly created DBInstance
+ """
+ params = {'SourceDBInstanceIdentifier' : source_instance_id,
+ 'TargetDBInstanceIdentifier' : target_instance_id}
+ if use_latest:
+ params['UseLatestRestorableTime'] = 'true'
+ elif restore_time:
+ params['RestoreTime'] = restore_time.isoformat()
+ if dbinstance_class:
+ params['DBInstanceClass'] = dbinstance_class
+ if port:
+ params['Port'] = port
+ if availability_zone:
+ params['AvailabilityZone'] = availability_zone
+ return self.get_object('RestoreDBInstanceToPointInTime',
+ params, DBInstance)
+
+ # Events
+
+ def get_all_events(self, source_identifier=None, source_type=None,
+ start_time=None, end_time=None,
+ max_records=None, marker=None):
+ """
+ Get information about events related to your DBInstances,
+ DBSecurityGroups and DBParameterGroups.
+
+ :type source_identifier: str
+ :param source_identifier: If supplied, the events returned will be
+ limited to those that apply to the identified
+ source. The value of this parameter depends
+ on the value of source_type. If neither
+ parameter is specified, all events in the time
+ span will be returned.
+
+ :type source_type: str
+ :param source_type: Specifies how the source_identifier should
+ be interpreted. Valid values are:
+ b-instance | db-security-group |
+ db-parameter-group | db-snapshot
+
+ :type start_time: datetime
+ :param start_time: The beginning of the time interval for events.
+ If not supplied, all available events will
+ be returned.
+
+ :type end_time: datetime
+ :param end_time: The ending of the time interval for events.
+ If not supplied, all available events will
+ be returned.
+
+ :type max_records: int
+ :param max_records: The maximum number of records to be returned.
+ If more results are available, a MoreToken will
+ be returned in the response that can be used to
+ retrieve additional records. Default is 100.
+
+ :type marker: str
+ :param marker: The marker provided by a previous request.
+
+ :rtype: list
+ :return: A list of class:`boto.rds.event.Event`
+ """
+ params = {}
+ if source_identifier and source_type:
+ params['SourceIdentifier'] = source_identifier
+ params['SourceType'] = source_type
+ if start_time:
+ params['StartTime'] = start_time.isoformat()
+ if end_time:
+ params['EndTime'] = end_time.isoformat()
+ if max_records:
+ params['MaxRecords'] = max_records
+ if marker:
+ params['Marker'] = marker
+ return self.get_list('DescribeEvents', params, [('Event', Event)])
+
+
diff --git a/vendor/boto/boto/rds/dbinstance.py b/vendor/boto/boto/rds/dbinstance.py
new file mode 100644
index 0000000000..23e1c984a4
--- /dev/null
+++ b/vendor/boto/boto/rds/dbinstance.py
@@ -0,0 +1,136 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.rds.dbsecuritygroup import DBSecurityGroup
+from boto.rds.parametergroup import ParameterGroup
+
+class DBInstance(object):
+ """
+ Represents a RDS DBInstance
+ """
+
+ def __init__(self, connection=None, id=None):
+ self.connection = connection
+ self.id = id
+ self.create_time = None
+ self.engine = None
+ self.status = None
+ self.allocated_storage = None
+ self.endpoint = None
+ self.instance_class = None
+ self.master_username = None
+ self.parameter_group = None
+ self.security_group = None
+ self.availability_zone = None
+ self.backup_retention_period = None
+ self.preferred_backup_window = None
+ self.preferred_maintenance_window = None
+ self.latest_restorable_time = None
+ self._in_endpoint = False
+ self._port = None
+ self._address = None
+
+ def __repr__(self):
+ return 'DBInstance:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Endpoint':
+ self._in_endpoint = True
+ elif name == 'DBParameterGroup':
+ self.parameter_group = ParameterGroup(self.connection)
+ return self.parameter_group
+ elif name == 'DBSecurityGroup':
+ self.security_group = DBSecurityGroup(self.connection)
+ return self.security_group
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'DBInstanceIdentifier':
+ self.id = value
+ elif name == 'DBInstanceStatus':
+ self.status = value
+ elif name == 'InstanceCreateTime':
+ self.create_time = value
+ elif name == 'Engine':
+ self.engine = value
+ elif name == 'DBInstanceStatus':
+ self.status = value
+ elif name == 'AllocatedStorage':
+ self.allocated_storage = int(value)
+ elif name == 'DBInstanceClass':
+ self.instance_class = value
+ elif name == 'MasterUsername':
+ self.master_username = value
+ elif name == 'Port':
+ if self._in_endpoint:
+ self._port = int(value)
+ elif name == 'Address':
+ if self._in_endpoint:
+ self._address = value
+ elif name == 'Endpoint':
+ self.endpoint = (self._address, self._port)
+ self._in_endpoint = False
+ elif name == 'AvailabilityZone':
+ self.availability_zone = value
+ elif name == 'BackupRetentionPeriod':
+ self.backup_retention_period = value
+ elif name == 'LatestRestorableTime':
+ self.latest_restorable_time = value
+ elif name == 'PreferredMaintenanceWindow':
+ self.preferred_maintenance_window = value
+ elif name == 'PreferredBackupWindow':
+ self.preferred_backup_window = value
+ else:
+ setattr(self, name, value)
+
+ def snapshot(self, snapshot_id):
+ """
+ Create a new DB snapshot of this DBInstance.
+
+ :type identifier: string
+ :param identifier: The identifier for the DBSnapshot
+
+ :rtype: :class:`boto.rds.dbsnapshot.DBSnapshot`
+ :return: The newly created DBSnapshot
+ """
+ return self.connection.create_dbsnapshot(snapshot_id, self.id)
+
+ def stop(self, skip_final_snapshot, final_snapshot_id):
+ """
+ Delete this DBInstance.
+
+ :type skip_final_snapshot: bool
+ :param skip_final_snapshot: This parameter determines whether a final
+ db snapshot is created before the instance
+ is deleted. If True, no snapshot is created.
+ If False, a snapshot is created before
+ deleting the instance.
+
+ :type final_snapshot_id: str
+ :param final_snapshot_id: If a final snapshot is requested, this
+ is the identifier used for that snapshot.
+
+ :rtype: :class:`boto.rds.dbinstance.DBInstance`
+ :return: The deleted db instance.
+ """
+ return self.connection.delete_dbinstance(self.id,
+ skip_final_snapshot,
+ final_snapshot_id)
diff --git a/vendor/boto/boto/rds/dbsecuritygroup.py b/vendor/boto/boto/rds/dbsecuritygroup.py
new file mode 100644
index 0000000000..24cdad2da5
--- /dev/null
+++ b/vendor/boto/boto/rds/dbsecuritygroup.py
@@ -0,0 +1,159 @@
+# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an DBSecurityGroup
+"""
+from boto.ec2.securitygroup import SecurityGroup
+
+class DBSecurityGroup(object):
+
+ def __init__(self, connection=None, owner_id=None,
+ name=None, description=None):
+ self.connection = connection
+ self.owner_id = owner_id
+ self.name = name
+ self.description = description
+ self.ec2_groups = []
+ self.ip_ranges = []
+
+ def __repr__(self):
+ return 'DBSecurityGroup:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ if name == 'IPRange':
+ cidr = IPRange(self)
+ self.ip_ranges.append(cidr)
+ return cidr
+ elif name == 'EC2SecurityGroup':
+ ec2_grp = EC2SecurityGroup(self)
+ self.ec2_groups.append(ec2_grp)
+ return ec2_grp
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'OwnerId':
+ self.owner_id = value
+ elif name == 'DBSecurityGroupName':
+ self.name = value
+ elif name == 'DBSecurityGroupDescription':
+ self.description = value
+ elif name == 'IPRanges':
+ pass
+ else:
+ setattr(self, name, value)
+
+ def delete(self):
+ return self.connection.delete_dbsecurity_group(self.name)
+
+ def authorize(self, cidr_ip=None, ec2_group=None):
+ """
+ Add a new rule to this DBSecurity group.
+ You need to pass in either a CIDR block to authorize or
+ and EC2 SecurityGroup.
+
+ @type cidr_ip: string
+ @param cidr_ip: A valid CIDR IP range to authorize
+
+ @type ec2_group: :class:`boto.ec2.securitygroup.SecurityGroup>`
+
+ @rtype: bool
+ @return: True if successful.
+ """
+ if isinstance(ec2_group, SecurityGroup):
+ group_name = ec2_group.name
+ group_owner_id = ec2_group.owner_id
+ else:
+ group_name = None
+ group_owner_id = None
+ return self.connection.authorize_dbsecurity_group(self.name,
+ cidr_ip,
+ group_name,
+ group_owner_id)
+
+ def revoke(self, cidr_ip=None, ec2_group=None):
+ """
+ Revoke access to a CIDR range or EC2 SecurityGroup
+ You need to pass in either a CIDR block to authorize or
+ and EC2 SecurityGroup.
+
+ @type cidr_ip: string
+ @param cidr_ip: A valid CIDR IP range to authorize
+
+ @type ec2_group: :class:`boto.ec2.securitygroup.SecurityGroup>`
+
+ @rtype: bool
+ @return: True if successful.
+ """
+ if isinstance(ec2_group, SecurityGroup):
+ group_name = ec2_group.name
+ group_owner_id = ec2_group.owner_id
+ else:
+ group_name = None
+ group_owner_id = None
+ return self.connection.revoke_dbsecurity_group(self.name,
+ cidr_ip,
+ group_name,
+ group_owner_id)
+
+class IPRange(object):
+
+ def __init__(self, parent=None):
+ self.parent = parent
+ self.cidr_ip = None
+ self.status = None
+
+ def __repr__(self):
+ return 'IPRange:%s' % self.cidr_ip
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'CIDRIP':
+ self.cidr_ip = value
+ elif name == 'Status':
+ self.status = value
+ else:
+ setattr(self, name, value)
+
+class EC2SecurityGroup(object):
+
+ def __init__(self, parent=None):
+ self.parent = parent
+ self.name = None
+ self.owner_id = None
+
+ def __repr__(self):
+ return 'EC2SecurityGroup:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'EC2SecurityGroupName':
+ self.name = value
+ elif name == 'EC2SecurityGroupOwnerId':
+ self.owner_id = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/rds/dbsnapshot.py b/vendor/boto/boto/rds/dbsnapshot.py
new file mode 100644
index 0000000000..78d0230c21
--- /dev/null
+++ b/vendor/boto/boto/rds/dbsnapshot.py
@@ -0,0 +1,74 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class DBSnapshot(object):
+ """
+ Represents a RDS DB Snapshot
+ """
+
+ def __init__(self, connection=None, id=None):
+ self.connection = connection
+ self.id = id
+ self.engine = None
+ self.snapshot_create_time = None
+ self.instance_create_time = None
+ self.port = None
+ self.status = None
+ self.availability_zone = None
+ self.master_username = None
+ self.allocated_storage = None
+ self.instance_id = None
+ self.availability_zone = None
+
+ def __repr__(self):
+ return 'DBSnapshot:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'Engine':
+ self.engine = value
+ elif name == 'InstanceCreateTime':
+ self.instance_create_time = value
+ elif name == 'SnapshotCreateTime':
+ self.snapshot_create_time = value
+ elif name == 'DBInstanceIdentifier':
+ self.instance_id = value
+ elif name == 'DBSnapshotIdentifier':
+ self.id = value
+ elif name == 'Port':
+ self.port = int(value)
+ elif name == 'Status':
+ self.status = value
+ elif name == 'AvailabilityZone':
+ self.availability_zone = value
+ elif name == 'MasterUsername':
+ self.master_username = value
+ elif name == 'AllocatedStorage':
+ self.allocated_storage = int(value)
+ elif name == 'SnapshotTime':
+ self.time = value
+ else:
+ setattr(self, name, value)
+
+
+
diff --git a/vendor/boto/boto/rds/event.py b/vendor/boto/boto/rds/event.py
new file mode 100644
index 0000000000..a91f8f08a5
--- /dev/null
+++ b/vendor/boto/boto/rds/event.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Event(object):
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.message = None
+ self.source_identifier = None
+ self.source_type = None
+ self.engine = None
+ self.date = None
+
+ def __repr__(self):
+ return '"%s"' % self.message
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'SourceIdentifier':
+ self.source_identifier = value
+ elif name == 'SourceType':
+ self.source_type = value
+ elif name == 'Message':
+ self.message = value
+ elif name == 'Date':
+ self.date = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/rds/parametergroup.py b/vendor/boto/boto/rds/parametergroup.py
new file mode 100644
index 0000000000..081e263575
--- /dev/null
+++ b/vendor/boto/boto/rds/parametergroup.py
@@ -0,0 +1,201 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class ParameterGroup(dict):
+
+ def __init__(self, connection=None):
+ dict.__init__(self)
+ self.connection = connection
+ self.name = None
+ self.description = None
+ self.engine = None
+ self._current_param = None
+
+ def __repr__(self):
+ return 'ParameterGroup:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Parameter':
+ if self._current_param:
+ self[self._current_param.name] = self._current_param
+ self._current_param = Parameter(self)
+ return self._current_param
+
+ def endElement(self, name, value, connection):
+ if name == 'DBParameterGroupName':
+ self.name = value
+ elif name == 'Description':
+ self.description = value
+ elif name == 'Engine':
+ self.engine = value
+ else:
+ setattr(self, name, value)
+
+ def modifiable(self):
+ mod = []
+ for key in self:
+ p = self[key]
+ if p.is_modifiable:
+ mod.append(p)
+ return mod
+
+ def get_params(self):
+ pg = self.connection.get_all_dbparameters(self.name)
+ self.update(pg)
+
+ def add_param(self, name, value, apply_method):
+ param = Parameter()
+ param.name = name
+ param.value = value
+ param.apply_method = apply_method
+ self.params.append(param)
+
+class Parameter(object):
+ """
+ Represents a RDS Parameter
+ """
+
+ ValidTypes = {'integer' : int,
+ 'string' : str,
+ 'boolean' : bool}
+ ValidSources = ['user', 'system', 'engine-default']
+ ValidApplyTypes = ['static', 'dynamic']
+ ValidApplyMethods = ['immediate', 'pending-reboot']
+
+ def __init__(self, group=None, name=None):
+ self.group = group
+ self.name = name
+ self._value = None
+ self.type = str
+ self.source = None
+ self.is_modifiable = True
+ self.description = None
+ self.apply_method = None
+ self.allowed_values = None
+
+ def __repr__(self):
+ return 'Parameter:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'ParameterName':
+ self.name = value
+ elif name == 'ParameterValue':
+ self._value = value
+ elif name == 'DataType':
+ if value in self.ValidTypes:
+ self.type = value
+ elif name == 'Source':
+ if value in self.ValidSources:
+ self.source = value
+ elif name == 'IsModifiable':
+ if value.lower() == 'true':
+ self.is_modifiable = True
+ else:
+ self.is_modifiable = False
+ elif name == 'Description':
+ self.description = value
+ elif name == 'ApplyType':
+ if value in self.ValidApplyTypes:
+ self.apply_type = value
+ elif name == 'AllowedValues':
+ self.allowed_values = value
+ else:
+ setattr(self, name, value)
+
+ def merge(self, d, i):
+ prefix = 'Parameters.member.%d.' % i
+ if self.name:
+ d[prefix+'ParameterName'] = self.name
+ if self._value:
+ d[prefix+'ParameterValue'] = self._value
+ if self.apply_type:
+ d[prefix+'ApplyMethod'] = self.apply_method
+
+ def _set_string_value(self, value):
+ if not isinstance(value, str) or isinstance(value, unicode):
+ raise ValueError, 'value must be of type str'
+ if self.allowed_values:
+ choices = self.allowed_values.split(',')
+ if value not in choices:
+ raise ValueError, 'value must be in %s' % self.allowed_values
+ set._value = value
+
+ def _set_integer_value(self, value):
+ if isinstance(value, str) or isinstance(value, unicode):
+ value = int(value)
+ if isinstance(value, int) or isinstance(value, long):
+ if self.allowed_values:
+ min, max = self.allowed_values.split('-')
+ if value < int(min) or value > int(max):
+ raise ValueError, 'range is %s' % self.allowed_values
+ self._value = value
+ else:
+ raise ValueError, 'value must be integer'
+
+ def _set_boolean_value(self, value):
+ if isinstance(value, bool):
+ self._value = value
+ elif isinstance(value, str) or isinstance(value, unicode):
+ if value.lower() == 'true':
+ self._value = True
+ else:
+ self._value = False
+ else:
+ raise ValueError, 'value must be boolean'
+
+ def set_value(self, value):
+ if self.type == 'string':
+ self._set_string_value(value)
+ elif self.type == 'integer':
+ self._set_integer_value(value)
+ elif self.type == 'boolean':
+ self._set_boolean_value(value)
+ else:
+ raise TypeError, 'unknown type (%s)' % self.type
+
+ def get_value(self):
+ if self._value == None:
+ return self._value
+ if self.type == 'string':
+ return self._value
+ elif self.type == 'integer':
+ if not isinstance(self._value, int) and not isinstance(self._value, long):
+ self._set_integer_value(self._value)
+ return self._value
+ elif self.type == 'boolean':
+ if not isinstance(self._value, bool):
+ self._set_boolean_value(self._value)
+ return self._value
+ else:
+ raise TypeError, 'unknown type (%s)' % self.type
+
+ value = property(get_value, set_value, 'The value of the parameter')
+
+ def apply(self, immediate=False):
+ if immediate:
+ self.apply_method = 'immediate'
+ else:
+ self.apply_method = 'pending-reboot'
+ self.group.connection.modify_parameter_group(self.group.name, [self])
+
diff --git a/vendor/boto/boto/resultset.py b/vendor/boto/boto/resultset.py
new file mode 100644
index 0000000000..cf6f1fdcf7
--- /dev/null
+++ b/vendor/boto/boto/resultset.py
@@ -0,0 +1,136 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class ResultSet(list):
+ """
+ The ResultSet is used to pass results back from the Amazon services
+ to the client. It has an ugly but workable mechanism for parsing
+ the XML results from AWS. Because I don't really want any dependencies
+ on external libraries, I'm using the standard SAX parser that comes
+ with Python. The good news is that it's quite fast and efficient but
+ it makes some things rather difficult.
+
+ You can pass in, as the marker_elem parameter, a list of tuples.
+ Each tuple contains a string as the first element which represents
+ the XML element that the resultset needs to be on the lookout for
+ and a Python class as the second element of the tuple. Each time the
+ specified element is found in the XML, a new instance of the class
+ will be created and popped onto the stack.
+
+ """
+
+ def __init__(self, marker_elem=None):
+ list.__init__(self)
+ if isinstance(marker_elem, list):
+ self.markers = marker_elem
+ else:
+ self.markers = []
+ self.marker = None
+ self.key_marker = None
+ self.version_id_marker = None
+ self.is_truncated = False
+ self.next_token = None
+ self.status = True
+
+ def startElement(self, name, attrs, connection):
+ for t in self.markers:
+ if name == t[0]:
+ obj = t[1](connection)
+ self.append(obj)
+ return obj
+ return None
+
+ def to_boolean(self, value, true_value='true'):
+ if value == true_value:
+ return True
+ else:
+ return False
+
+ def endElement(self, name, value, connection):
+ if name == 'IsTruncated':
+ self.is_truncated = self.to_boolean(value)
+ elif name == 'Marker':
+ self.marker = value
+ elif name == 'KeyMarker':
+ self.key_marker = value
+ elif name == 'VersionIdMarker':
+ self.version_id_marker = value
+ elif name == 'Prefix':
+ self.prefix = value
+ elif name == 'return':
+ self.status = self.to_boolean(value)
+ elif name == 'StatusCode':
+ self.status = self.to_boolean(value, 'Success')
+ elif name == 'ItemName':
+ self.append(value)
+ elif name == 'NextToken':
+ self.next_token = value
+ elif name == 'BoxUsage':
+ try:
+ connection.box_usage += float(value)
+ except:
+ pass
+ elif name == 'IsValid':
+ self.status = self.to_boolean(value, 'True')
+ else:
+ setattr(self, name, value)
+
+class BooleanResult(object):
+
+ def __init__(self, marker_elem=None):
+ self.status = True
+ self.request_id = None
+ self.box_usage = None
+
+ def __repr__(self):
+ if self.status:
+ return 'True'
+ else:
+ return 'False'
+
+ def __nonzero__(self):
+ return self.status
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def to_boolean(self, value, true_value='true'):
+ if value == true_value:
+ return True
+ else:
+ return False
+
+ def endElement(self, name, value, connection):
+ if name == 'return':
+ self.status = self.to_boolean(value)
+ elif name == 'StatusCode':
+ self.status = self.to_boolean(value, 'Success')
+ elif name == 'IsValid':
+ self.status = self.to_boolean(value, 'True')
+ elif name == 'RequestId':
+ self.request_id = value
+ elif name == 'requestId':
+ self.request_id = value
+ elif name == 'BoxUsage':
+ self.request_id = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/s3/__init__.py b/vendor/boto/boto/s3/__init__.py
new file mode 100644
index 0000000000..be2de1d511
--- /dev/null
+++ b/vendor/boto/boto/s3/__init__.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+import boto
+
+boto.check_extensions(__name__, __path__)
+
+from connection import S3Connection as Connection
+from key import Key
+from bucket import Bucket
+
+__all__ = ['Connection', 'Key', 'Bucket']
diff --git a/vendor/boto/boto/s3/acl.py b/vendor/boto/boto/s3/acl.py
new file mode 100644
index 0000000000..59d3687bcb
--- /dev/null
+++ b/vendor/boto/boto/s3/acl.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.s3.user import User
+
+
+CannedACLStrings = ['private', 'public-read',
+ 'public-read-write', 'authenticated-read']
+
+
+class Policy:
+
+ def __init__(self, parent=None):
+ self.parent = parent
+ self.acl = None
+
+ def __repr__(self):
+ grants = []
+ for g in self.acl.grants:
+ if g.id == self.owner.id:
+ grants.append("%s (owner) = %s" % (g.display_name, g.permission))
+ else:
+ if g.type == 'CanonicalUser':
+ u = g.display_name
+ elif g.type == 'Group':
+ u = g.uri
+ else:
+ u = g.email
+ grants.append("%s = %s" % (u, g.permission))
+ return "<Policy: %s>" % ", ".join(grants)
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Owner':
+ self.owner = User(self)
+ return self.owner
+ elif name == 'AccessControlList':
+ self.acl = ACL(self)
+ return self.acl
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Owner':
+ pass
+ elif name == 'AccessControlList':
+ pass
+ else:
+ setattr(self, name, value)
+
+ def to_xml(self):
+ s = '<AccessControlPolicy>'
+ s += self.owner.to_xml()
+ s += self.acl.to_xml()
+ s += '</AccessControlPolicy>'
+ return s
+
+class ACL:
+
+ def __init__(self, policy=None):
+ self.policy = policy
+ self.grants = []
+
+ def add_grant(self, grant):
+ self.grants.append(grant)
+
+ def add_email_grant(self, permission, email_address):
+ grant = Grant(permission=permission, type='AmazonCustomerByEmail',
+ email_address=email_address)
+ self.grants.append(grant)
+
+ def add_user_grant(self, permission, user_id):
+ grant = Grant(permission=permission, type='CanonicalUser', id=user_id)
+ self.grants.append(grant)
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Grant':
+ self.grants.append(Grant(self))
+ return self.grants[-1]
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Grant':
+ pass
+ else:
+ setattr(self, name, value)
+
+ def to_xml(self):
+ s = '<AccessControlList>'
+ for grant in self.grants:
+ s += grant.to_xml()
+ s += '</AccessControlList>'
+ return s
+
+class Grant:
+
+ NameSpace = 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
+
+ def __init__(self, permission=None, type=None, id=None,
+ display_name=None, uri=None, email_address=None):
+ self.permission = permission
+ self.id = id
+ self.display_name = display_name
+ self.uri = uri
+ self.email_address = email_address
+ self.type = type
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Grantee':
+ self.type = attrs['xsi:type']
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'ID':
+ self.id = value
+ elif name == 'DisplayName':
+ self.display_name = value
+ elif name == 'URI':
+ self.uri = value
+ elif name == 'EmailAddress':
+ self.email_address = value
+ elif name == 'Grantee':
+ pass
+ elif name == 'Permission':
+ self.permission = value
+ else:
+ setattr(self, name, value)
+
+ def to_xml(self):
+ s = '<Grant>'
+ s += '<Grantee %s xsi:type="%s">' % (self.NameSpace, self.type)
+ if self.type == 'CanonicalUser':
+ s += '<ID>%s</ID>' % self.id
+ s += '<DisplayName>%s</DisplayName>' % self.display_name
+ elif self.type == 'Group':
+ s += '<URI>%s</URI>' % self.uri
+ else:
+ s += '<EmailAddress>%s</EmailAddress>' % self.email_address
+ s += '</Grantee>'
+ s += '<Permission>%s</Permission>' % self.permission
+ s += '</Grant>'
+ return s
+
+
diff --git a/vendor/boto/boto/s3/bucket.py b/vendor/boto/boto/s3/bucket.py
new file mode 100644
index 0000000000..42c32f8daf
--- /dev/null
+++ b/vendor/boto/boto/s3/bucket.py
@@ -0,0 +1,721 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import boto
+from boto import handler
+from boto.resultset import ResultSet
+from boto.s3.acl import Policy, CannedACLStrings, Grant
+from boto.s3.key import Key
+from boto.s3.prefix import Prefix
+from boto.s3.deletemarker import DeleteMarker
+from boto.exception import S3ResponseError, S3PermissionsError, S3CopyError
+from boto.s3.bucketlistresultset import BucketListResultSet
+from boto.s3.bucketlistresultset import VersionedBucketListResultSet
+import boto.utils
+import xml.sax
+import urllib
+import re
+
+S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL']
+
+class Bucket:
+
+ BucketLoggingBody = """<?xml version="1.0" encoding="UTF-8"?>
+ <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
+ <LoggingEnabled>
+ <TargetBucket>%s</TargetBucket>
+ <TargetPrefix>%s</TargetPrefix>
+ </LoggingEnabled>
+ </BucketLoggingStatus>"""
+
+ EmptyBucketLoggingBody = """<?xml version="1.0" encoding="UTF-8"?>
+ <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
+ </BucketLoggingStatus>"""
+
+ LoggingGroup = 'http://acs.amazonaws.com/groups/s3/LogDelivery'
+
+ BucketPaymentBody = """<?xml version="1.0" encoding="UTF-8"?>
+ <RequestPaymentConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
+ <Payer>%s</Payer>
+ </RequestPaymentConfiguration>"""
+
+ VersioningBody = """<?xml version="1.0" encoding="UTF-8"?>
+ <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
+ <Status>%s</Status>
+ <MfaDelete>%s</MfaDelete>
+ </VersioningConfiguration>"""
+
+ VersionRE = '<Status>([A-Za-z]+)</Status>'
+ MFADeleteRE = '<MfaDelete>([A-Za-z]+)</MfaDelete>'
+
+ def __init__(self, connection=None, name=None, key_class=Key):
+ self.name = name
+ self.connection = connection
+ self.key_class = key_class
+
+ def __repr__(self):
+ return '<Bucket: %s>' % self.name
+
+ def __iter__(self):
+ return iter(BucketListResultSet(self))
+
+ def __contains__(self, key_name):
+ return not (self.get_key(key_name) is None)
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Name':
+ self.name = value
+ elif name == 'CreationDate':
+ self.creation_date = value
+ else:
+ setattr(self, name, value)
+
+ def set_key_class(self, key_class):
+ """
+ Set the Key class associated with this bucket. By default, this
+ would be the boto.s3.key.Key class but if you want to subclass that
+ for some reason this allows you to associate your new class with a
+ bucket so that when you call bucket.new_key() or when you get a listing
+ of keys in the bucket you will get an instances of your key class
+ rather than the default.
+
+ :type key_class: class
+ :param key_class: A subclass of Key that can be more specific
+ """
+ self.key_class = key_class
+
+ def lookup(self, key_name, headers=None):
+ """
+ Deprecated: Please use get_key method.
+
+ :type key_name: string
+ :param key_name: The name of the key to retrieve
+
+ :rtype: :class:`boto.s3.key.Key`
+ :returns: A Key object from this bucket.
+ """
+ return self.get_key(key_name, headers=headers)
+
+ def get_key(self, key_name, headers=None, version_id=None):
+ """
+ Check to see if a particular key exists within the bucket. This
+ method uses a HEAD request to check for the existance of the key.
+ Returns: An instance of a Key object or None
+
+ :type key_name: string
+ :param key_name: The name of the key to retrieve
+
+ :rtype: :class:`boto.s3.key.Key`
+ :returns: A Key object from this bucket.
+ """
+ if version_id:
+ query_args = 'versionId=%s' % version_id
+ else:
+ query_args = None
+ response = self.connection.make_request('HEAD', self.name, key_name,
+ headers=headers,
+ query_args=query_args)
+ if response.status == 200:
+ response.read()
+ k = self.key_class(self)
+ k.metadata = boto.utils.get_aws_metadata(response.msg)
+ k.etag = response.getheader('etag')
+ k.content_type = response.getheader('content-type')
+ k.content_encoding = response.getheader('content-encoding')
+ k.last_modified = response.getheader('last-modified')
+ k.size = int(response.getheader('content-length'))
+ k.name = key_name
+ k.handle_version_headers(response)
+ return k
+ else:
+ if response.status == 404:
+ response.read()
+ return None
+ else:
+ raise S3ResponseError(response.status, response.reason, '')
+
+ def list(self, prefix='', delimiter='', marker='', headers=None):
+ """
+ List key objects within a bucket. This returns an instance of an
+ BucketListResultSet that automatically handles all of the result
+ paging, etc. from S3. You just need to keep iterating until
+ there are no more results.
+ Called with no arguments, this will return an iterator object across
+ all keys within the bucket.
+
+ :type prefix: string
+ :param prefix: allows you to limit the listing to a particular
+ prefix. For example, if you call the method with
+ prefix='/foo/' then the iterator will only cycle
+ through the keys that begin with the string '/foo/'.
+
+ :type delimiter: string
+ :param delimiter: can be used in conjunction with the prefix
+ to allow you to organize and browse your keys
+ hierarchically. See:
+ http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
+ for more details.
+
+ :type marker: string
+ :param marker: The "marker" of where you are in the result set
+
+ :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet`
+ :return: an instance of a BucketListResultSet that handles paging, etc
+ """
+ return BucketListResultSet(self, prefix, delimiter, marker, headers)
+
+ def list_versions(self, prefix='', delimiter='', key_marker='',
+ version_id_marker='', headers=None):
+ """
+ List key objects within a bucket. This returns an instance of an
+ BucketListResultSet that automatically handles all of the result
+ paging, etc. from S3. You just need to keep iterating until
+ there are no more results.
+ Called with no arguments, this will return an iterator object across
+ all keys within the bucket.
+
+ :type prefix: string
+ :param prefix: allows you to limit the listing to a particular
+ prefix. For example, if you call the method with
+ prefix='/foo/' then the iterator will only cycle
+ through the keys that begin with the string '/foo/'.
+
+ :type delimiter: string
+ :param delimiter: can be used in conjunction with the prefix
+ to allow you to organize and browse your keys
+ hierarchically. See:
+ http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
+ for more details.
+
+ :type marker: string
+ :param marker: The "marker" of where you are in the result set
+
+ :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet`
+ :return: an instance of a BucketListResultSet that handles paging, etc
+ """
+ return VersionedBucketListResultSet(self, prefix, delimiter, key_marker,
+ version_id_marker, headers)
+
+ def _get_all(self, element_map, initial_query_string='',
+ headers=None, **params):
+ l = []
+ for k,v in params.items():
+ k = k.replace('_', '-')
+ if k == 'maxkeys':
+ k = 'max-keys'
+ if isinstance(v, unicode):
+ v = v.encode('utf-8')
+ if v is not None and v != '':
+ l.append('%s=%s' % (urllib.quote(k), urllib.quote(str(v))))
+ if len(l):
+ s = initial_query_string + '&' + '&'.join(l)
+ else:
+ s = initial_query_string
+ response = self.connection.make_request('GET', self.name,
+ headers=headers, query_args=s)
+ body = response.read()
+ boto.log.debug(body)
+ if response.status == 200:
+ rs = ResultSet(element_map)
+ h = handler.XmlHandler(rs, self)
+ xml.sax.parseString(body, h)
+ return rs
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def get_all_keys(self, headers=None, **params):
+ """
+ A lower-level method for listing contents of a bucket.
+ This closely models the actual S3 API and requires you to manually
+ handle the paging of results. For a higher-level method
+ that handles the details of paging for you, you can use the list method.
+
+ :type max_keys: int
+ :param max_keys: The maximum number of keys to retrieve
+
+ :type prefix: string
+ :param prefix: The prefix of the keys you want to retrieve
+
+ :type marker: string
+ :param marker: The "marker" of where you are in the result set
+
+ :type delimiter: string
+ :param delimiter: If this optional, Unicode string parameter
+ is included with your request, then keys that
+ contain the same string between the prefix and
+ the first occurrence of the delimiter will be
+ rolled up into a single result element in the
+ CommonPrefixes collection. These rolled-up keys
+ are not returned elsewhere in the response.
+
+ :rtype: ResultSet
+ :return: The result from S3 listing the keys requested
+
+ """
+ return self._get_all([('Contents', self.key_class),
+ ('CommonPrefixes', Prefix)],
+ '', headers, **params)
+
+ def get_all_versions(self, headers=None, **params):
+ """
+ A lower-level, version-aware method for listing contents of a bucket.
+ This closely models the actual S3 API and requires you to manually
+ handle the paging of results. For a higher-level method
+ that handles the details of paging for you, you can use the list method.
+
+ :type max_keys: int
+ :param max_keys: The maximum number of keys to retrieve
+
+ :type prefix: string
+ :param prefix: The prefix of the keys you want to retrieve
+
+ :type key_marker: string
+ :param key_marker: The "marker" of where you are in the result set
+ with respect to keys.
+
+ :type version_id_marker: string
+ :param version_id_marker: The "marker" of where you are in the result
+ set with respect to version-id's.
+
+ :type delimiter: string
+ :param delimiter: If this optional, Unicode string parameter
+ is included with your request, then keys that
+ contain the same string between the prefix and
+ the first occurrence of the delimiter will be
+ rolled up into a single result element in the
+ CommonPrefixes collection. These rolled-up keys
+ are not returned elsewhere in the response.
+
+ :rtype: ResultSet
+ :return: The result from S3 listing the keys requested
+
+ """
+ return self._get_all([('Version', self.key_class),
+ ('CommonPrefixes', Prefix),
+ ('DeleteMarker', DeleteMarker)],
+ 'versions', headers, **params)
+
+ def new_key(self, key_name=None):
+ """
+ Creates a new key
+
+ :type key_name: string
+ :param key_name: The name of the key to create
+
+ :rtype: :class:`boto.s3.key.Key` or subclass
+ :returns: An instance of the newly created key object
+ """
+ return self.key_class(self, key_name)
+
+ def generate_url(self, expires_in, method='GET',
+ headers=None, force_http=False):
+ return self.connection.generate_url(expires_in, method, self.name,
+ headers=headers,
+ force_http=force_http)
+
+ def delete_key(self, key_name, headers=None,
+ version_id=None, mfa_token=None):
+ """
+ Deletes a key from the bucket. If a version_id is provided,
+ only that version of the key will be deleted.
+
+ :type key_name: string
+ :param key_name: The key name to delete
+
+ :type version_id: string
+ :param version_id: The version ID (optional)
+
+ :type mfa_token: tuple or list of strings
+ :param mfa_token: A tuple or list consisting of the serial number
+ from the MFA device and the current value of
+ the six-digit token associated with the device.
+ This value is required anytime you are
+ deleting versioned objects from a bucket
+ that has the MFADelete option on the bucket.
+ """
+ if version_id:
+ query_args = 'versionId=%s' % version_id
+ else:
+ query_args = None
+ if mfa_token:
+ if not headers:
+ headers = {}
+ headers['x-amz-mfa'] = ' '.join(mfa_token)
+ response = self.connection.make_request('DELETE', self.name, key_name,
+ headers=headers,
+ query_args=query_args)
+ body = response.read()
+ if response.status != 204:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def copy_key(self, new_key_name, src_bucket_name,
+ src_key_name, metadata=None, src_version_id=None):
+ """
+ Create a new key in the bucket by copying another existing key.
+
+ :type new_key_name: string
+ :param new_key_name: The name of the new key
+
+ :type src_bucket_name: string
+ :param src_bucket_name: The name of the source bucket
+
+ :type src_key_name: string
+ :param src_key_name: The name of the source key
+
+ :type src_version_id: string
+ :param src_version_id: The version id for the key. This param
+ is optional. If not specified, the newest
+ version of the key will be copied.
+
+ :type metadata: dict
+ :param metadata: Metadata to be associated with new key.
+ If metadata is supplied, it will replace the
+ metadata of the source key being copied.
+ If no metadata is supplied, the source key's
+ metadata will be copied to the new key.
+
+ :rtype: :class:`boto.s3.key.Key` or subclass
+ :returns: An instance of the newly created key object
+ """
+ src = '%s/%s' % (src_bucket_name, urllib.quote(src_key_name))
+ if src_version_id:
+ src += '?version_id=%s' % src_version_id
+ if metadata:
+ headers = {'x-amz-copy-source' : src,
+ 'x-amz-metadata-directive' : 'REPLACE'}
+ headers = boto.utils.merge_meta(headers, metadata)
+ else:
+ headers = {'x-amz-copy-source' : src,
+ 'x-amz-metadata-directive' : 'COPY'}
+ response = self.connection.make_request('PUT', self.name, new_key_name,
+ headers=headers)
+ body = response.read()
+ if response.status == 200:
+ key = self.new_key(new_key_name)
+ h = handler.XmlHandler(key, self)
+ xml.sax.parseString(body, h)
+ if hasattr(key, 'Error'):
+ raise S3CopyError(key.Code, key.Message, body)
+ key.handle_version_headers(response)
+ return key
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def set_canned_acl(self, acl_str, key_name='', headers=None,
+ version_id=None):
+ assert acl_str in CannedACLStrings
+
+ if headers:
+ headers['x-amz-acl'] = acl_str
+ else:
+ headers={'x-amz-acl': acl_str}
+
+ query_args='acl'
+ if version_id:
+ query_args += '&versionId=%s' % version_id
+ response = self.connection.make_request('PUT', self.name, key_name,
+ headers=headers, query_args=query_args)
+ body = response.read()
+ if response.status != 200:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def get_xml_acl(self, key_name='', headers=None, version_id=None):
+ query_args = 'acl'
+ if version_id:
+ query_args += '&versionId=%s' % version_id
+ response = self.connection.make_request('GET', self.name, key_name,
+ query_args=query_args,
+ headers=headers)
+ body = response.read()
+ if response.status != 200:
+ raise S3ResponseError(response.status, response.reason, body)
+ return body
+
+ def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None):
+ query_args = 'acl'
+ if version_id:
+ query_args += '&versionId=%s' % version_id
+ response = self.connection.make_request('PUT', self.name, key_name,
+ data=acl_str,
+ query_args=query_args,
+ headers=headers)
+ body = response.read()
+ if response.status != 200:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None):
+ if isinstance(acl_or_str, Policy):
+ self.set_xml_acl(acl_or_str.to_xml(), key_name,
+ headers, version_id)
+ else:
+ self.set_canned_acl(acl_or_str, key_name,
+ headers, version_id)
+
+ def get_acl(self, key_name='', headers=None, version_id=None):
+ query_args = 'acl'
+ if version_id:
+ query_args += '&versionId=%s' % version_id
+ response = self.connection.make_request('GET', self.name, key_name,
+ query_args=query_args,
+ headers=headers)
+ body = response.read()
+ if response.status == 200:
+ policy = Policy(self)
+ h = handler.XmlHandler(policy, self)
+ xml.sax.parseString(body, h)
+ return policy
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def make_public(self, recursive=False, headers=None):
+ self.set_canned_acl('public-read', headers=headers)
+ if recursive:
+ for key in self:
+ self.set_canned_acl('public-read', key.name, headers=headers)
+
+ def add_email_grant(self, permission, email_address,
+ recursive=False, headers=None):
+ """
+ Convenience method that provides a quick way to add an email grant
+ to a bucket. This method retrieves the current ACL, creates a new
+ grant based on the parameters passed in, adds that grant to the ACL
+ and then PUT's the new ACL back to S3.
+
+ :type permission: string
+ :param permission: The permission being granted. Should be one of:
+ (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL).
+
+ :type email_address: string
+ :param email_address: The email address associated with the AWS
+ account your are granting the permission to.
+
+ :type recursive: boolean
+ :param recursive: A boolean value to controls whether the command
+ will apply the grant to all keys within the bucket
+ or not. The default value is False. By passing a
+ True value, the call will iterate through all keys
+ in the bucket and apply the same grant to each key.
+ CAUTION: If you have a lot of keys, this could take
+ a long time!
+ """
+ if permission not in S3Permissions:
+ raise S3PermissionsError('Unknown Permission: %s' % permission)
+ policy = self.get_acl(headers=headers)
+ policy.acl.add_email_grant(permission, email_address)
+ self.set_acl(policy, headers=headers)
+ if recursive:
+ for key in self:
+ key.add_email_grant(permission, email_address, headers=headers)
+
+ def add_user_grant(self, permission, user_id, recursive=False, headers=None):
+ """
+ Convenience method that provides a quick way to add a canonical user grant to a bucket.
+ This method retrieves the current ACL, creates a new grant based on the parameters
+ passed in, adds that grant to the ACL and then PUT's the new ACL back to S3.
+
+ :type permission: string
+ :param permission: The permission being granted. Should be one of:
+ (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL).
+
+ :type user_id: string
+ :param user_id: The canonical user id associated with the AWS account your are granting
+ the permission to.
+
+ :type recursive: boolean
+ :param recursive: A boolean value to controls whether the command
+ will apply the grant to all keys within the bucket
+ or not. The default value is False. By passing a
+ True value, the call will iterate through all keys
+ in the bucket and apply the same grant to each key.
+ CAUTION: If you have a lot of keys, this could take
+ a long time!
+ """
+ if permission not in S3Permissions:
+ raise S3PermissionsError('Unknown Permission: %s' % permission)
+ policy = self.get_acl(headers=headers)
+ policy.acl.add_user_grant(permission, user_id)
+ self.set_acl(policy, headers=headers)
+ if recursive:
+ for key in self:
+ key.add_user_grant(permission, user_id, headers=headers)
+
+ def list_grants(self, headers=None):
+ policy = self.get_acl(headers=headers)
+ return policy.acl.grants
+
+ def get_location(self):
+ """
+ Returns the LocationConstraint for the bucket.
+
+ :rtype: str
+ :return: The LocationConstraint for the bucket or the empty string if
+ no constraint was specified when bucket was created.
+ """
+ response = self.connection.make_request('GET', self.name,
+ query_args='location')
+ body = response.read()
+ if response.status == 200:
+ rs = ResultSet(self)
+ h = handler.XmlHandler(rs, self)
+ xml.sax.parseString(body, h)
+ return rs.LocationConstraint
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def enable_logging(self, target_bucket, target_prefix='', headers=None):
+ if isinstance(target_bucket, Bucket):
+ target_bucket = target_bucket.name
+ body = self.BucketLoggingBody % (target_bucket, target_prefix)
+ response = self.connection.make_request('PUT', self.name, data=body,
+ query_args='logging', headers=headers)
+ body = response.read()
+ if response.status == 200:
+ return True
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def disable_logging(self, headers=None):
+ body = self.EmptyBucketLoggingBody
+ response = self.connection.make_request('PUT', self.name, data=body,
+ query_args='logging', headers=headers)
+ body = response.read()
+ if response.status == 200:
+ return True
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def get_logging_status(self, headers=None):
+ response = self.connection.make_request('GET', self.name,
+ query_args='logging', headers=headers)
+ body = response.read()
+ if response.status == 200:
+ return body
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def set_as_logging_target(self, headers=None):
+ policy = self.get_acl(headers=headers)
+ g1 = Grant(permission='WRITE', type='Group', uri=self.LoggingGroup)
+ g2 = Grant(permission='READ_ACP', type='Group', uri=self.LoggingGroup)
+ policy.acl.add_grant(g1)
+ policy.acl.add_grant(g2)
+ self.set_acl(policy, headers=headers)
+
+ def get_request_payment(self, headers=None):
+ response = self.connection.make_request('GET', self.name,
+ query_args='requestPayment', headers=headers)
+ body = response.read()
+ if response.status == 200:
+ return body
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def set_request_payment(self, payer='BucketOwner', headers=None):
+ body = self.BucketPaymentBody % payer
+ response = self.connection.make_request('PUT', self.name, data=body,
+ query_args='requestPayment', headers=headers)
+ body = response.read()
+ if response.status == 200:
+ return True
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def configure_versioning(self, versioning, mfa_delete=False,
+ mfa_token=None, headers=None):
+ """
+ Configure versioning for this bucket.
+ Note: This feature is currently in beta release and is available
+ only in the Northern California region.
+
+ :type versioning: bool
+ :param versioning: A boolean indicating whether version is
+ enabled (True) or disabled (False).
+
+ :type mfa_delete: bool
+ :param mfa_delete: A boolean indicating whether the Multi-Factor
+ Authentication Delete feature is enabled (True)
+ or disabled (False). If mfa_delete is enabled
+ then all Delete operations will require the
+ token from your MFA device to be passed in
+ the request.
+
+ :type mfa_token: tuple or list of strings
+ :param mfa_token: A tuple or list consisting of the serial number
+ from the MFA device and the current value of
+ the six-digit token associated with the device.
+ This value is required when you are changing
+ the status of the MfaDelete property of
+ the bucket.
+ """
+ if versioning:
+ ver = 'Enabled'
+ else:
+ ver = 'Suspended'
+ if mfa_delete:
+ mfa = 'Enabled'
+ else:
+ mfa = 'Disabled'
+ body = self.VersioningBody % (ver, mfa)
+ if mfa_token:
+ if not headers:
+ headers = {}
+ headers['x-amz-mfa'] = ' '.join(mfa_token)
+ response = self.connection.make_request('PUT', self.name, data=body,
+ query_args='versioning', headers=headers)
+ body = response.read()
+ if response.status == 200:
+ return True
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def get_versioning_status(self, headers=None):
+ """
+ Returns the current status of versioning on the bucket.
+
+ :rtype: dict
+ :returns: A dictionary containing a key named 'Versioning'
+ that can have a value of either Enabled, Disabled,
+ or Suspended. Also, if MFADelete has ever been enabled
+ on the bucket, the dictionary will contain a key
+ named 'MFADelete' which will have a value of either
+ Enabled or Suspended.
+ """
+ response = self.connection.make_request('GET', self.name,
+ query_args='versioning', headers=headers)
+ body = response.read()
+ boto.log.debug(body)
+ if response.status == 200:
+ d = {}
+ ver = re.search(self.VersionRE, body)
+ if ver:
+ d['Versioning'] = ver.group(1)
+ mfa = re.search(self.MFADeleteRE, body)
+ if mfa:
+ d['MfaDelete'] = mfa.group(1)
+ return d
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def delete(self, headers=None):
+ return self.connection.delete_bucket(self.name, headers=headers)
diff --git a/vendor/boto/boto/s3/bucketlistresultset.py b/vendor/boto/boto/s3/bucketlistresultset.py
new file mode 100644
index 0000000000..9fc79bdf31
--- /dev/null
+++ b/vendor/boto/boto/s3/bucketlistresultset.py
@@ -0,0 +1,99 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+def bucket_lister(bucket, prefix='', delimiter='', marker='', headers=None):
+ """
+ A generator function for listing keys in a bucket.
+ """
+ more_results = True
+ k = None
+ while more_results:
+ rs = bucket.get_all_keys(prefix=prefix, marker=marker,
+ delimiter=delimiter, headers=headers)
+ for k in rs:
+ yield k
+ if k:
+ marker = k.name
+ more_results= rs.is_truncated
+
+class BucketListResultSet:
+ """
+ A resultset for listing keys within a bucket. Uses the bucket_lister
+ generator function and implements the iterator interface. This
+ transparently handles the results paging from S3 so even if you have
+ many thousands of keys within the bucket you can iterate over all
+ keys in a reasonably efficient manner.
+ """
+
+ def __init__(self, bucket=None, prefix='', delimiter='', marker='', headers=None):
+ self.bucket = bucket
+ self.prefix = prefix
+ self.delimiter = delimiter
+ self.marker = marker
+ self.headers = headers
+
+ def __iter__(self):
+ return bucket_lister(self.bucket, prefix=self.prefix,
+ delimiter=self.delimiter, marker=self.marker, headers=self.headers)
+
+def versioned_bucket_lister(bucket, prefix='', delimiter='',
+ key_marker='', version_id_marker='', headers=None):
+ """
+ A generator function for listing versions in a bucket.
+ """
+ more_results = True
+ k = None
+ while more_results:
+ rs = bucket.get_all_versions(prefix=prefix, key_marker=key_marker,
+ version_id_marker=version_id_marker,
+ delimiter=delimiter, headers=headers)
+ for k in rs:
+ yield k
+ key_marker = rs.key_marker
+ version_id_marker = rs.version_id_marker
+ more_results= rs.is_truncated
+
+class VersionedBucketListResultSet:
+ """
+ A resultset for listing versions within a bucket. Uses the bucket_lister
+ generator function and implements the iterator interface. This
+ transparently handles the results paging from S3 so even if you have
+ many thousands of keys within the bucket you can iterate over all
+ keys in a reasonably efficient manner.
+ """
+
+ def __init__(self, bucket=None, prefix='', delimiter='', key_marker='',
+ version_id_marker='', headers=None):
+ self.bucket = bucket
+ self.prefix = prefix
+ self.delimiter = delimiter
+ self.key_marker = key_marker
+ self.version_id_marker = version_id_marker
+ self.headers = headers
+
+ def __iter__(self):
+ return versioned_bucket_lister(self.bucket, prefix=self.prefix,
+ delimiter=self.delimiter,
+ key_marker=self.key_marker,
+ version_id_marker=self.version_id_marker,
+ headers=self.headers)
+
+
diff --git a/vendor/boto/boto/s3/connection.py b/vendor/boto/boto/s3/connection.py
new file mode 100644
index 0000000000..614de0b861
--- /dev/null
+++ b/vendor/boto/boto/s3/connection.py
@@ -0,0 +1,350 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import xml.sax
+import urllib, base64
+import time
+import boto.utils
+from boto.connection import AWSAuthConnection
+from boto import handler
+from boto.s3.bucket import Bucket
+from boto.s3.key import Key
+from boto.resultset import ResultSet
+from boto.exception import S3ResponseError, S3CreateError, BotoClientError
+
+def assert_case_insensitive(f):
+ def wrapper(*args, **kwargs):
+ if len(args) == 3 and not (args[2].islower() or args[2].isalnum()):
+ raise BotoClientError("Bucket names cannot contain upper-case " \
+ "characters when using either the sub-domain or virtual " \
+ "hosting calling format.")
+ return f(*args, **kwargs)
+ return wrapper
+
+class _CallingFormat:
+ def build_url_base(self, protocol, server, bucket, key=''):
+ url_base = '%s://' % protocol
+ url_base += self.build_host(server, bucket)
+ url_base += self.build_path_base(bucket, key)
+ return url_base
+
+ def build_host(self, server, bucket):
+ if bucket == '':
+ return server
+ else:
+ return self.get_bucket_server(server, bucket)
+
+ def build_auth_path(self, bucket, key=''):
+ path = ''
+ if bucket != '':
+ path = '/' + bucket
+ return path + '/%s' % urllib.quote(key)
+
+ def build_path_base(self, bucket, key=''):
+ return '/%s' % urllib.quote(key)
+
+class SubdomainCallingFormat(_CallingFormat):
+ @assert_case_insensitive
+ def get_bucket_server(self, server, bucket):
+ return '%s.%s' % (bucket, server)
+
+class VHostCallingFormat(_CallingFormat):
+ @assert_case_insensitive
+ def get_bucket_server(self, server, bucket):
+ return bucket
+
+class OrdinaryCallingFormat(_CallingFormat):
+ def get_bucket_server(self, server, bucket):
+ return server
+
+ def build_path_base(self, bucket, key=''):
+ path_base = '/'
+ if bucket:
+ path_base += "%s/" % bucket
+ return path_base + urllib.quote(key)
+
+class Location:
+ DEFAULT = ''
+ EU = 'EU'
+ USWest = 'us-west-1'
+
+#boto.set_stream_logger('s3')
+
+class S3Connection(AWSAuthConnection):
+
+ DefaultHost = 's3.amazonaws.com'
+ QueryString = 'Signature=%s&Expires=%d&AWSAccessKeyId=%s'
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None,
+ host=DefaultHost, debug=0, https_connection_factory=None,
+ calling_format=SubdomainCallingFormat(), path='/'):
+ self.calling_format = calling_format
+ AWSAuthConnection.__init__(self, host,
+ aws_access_key_id, aws_secret_access_key,
+ is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
+ debug=debug, https_connection_factory=https_connection_factory,
+ path=path)
+
+ def __iter__(self):
+ return self.get_all_buckets()
+
+ def __contains__(self, bucket_name):
+ return not (self.lookup(bucket_name) is None)
+
+ def build_post_policy(self, expiration_time, conditions):
+ """
+ Taken from the AWS book Python examples and modified for use with boto
+ """
+ if type(expiration_time) != time.struct_time:
+ raise 'Policy document must include a valid expiration Time object'
+
+ # Convert conditions object mappings to condition statements
+
+ return '{"expiration": "%s",\n"conditions": [%s]}' % \
+ (time.strftime(boto.utils.ISO8601, expiration_time), ",".join(conditions))
+
+
+ def build_post_form_args(self, bucket_name, key, expires_in = 6000,
+ acl = None, success_action_redirect = None, max_content_length = None,
+ http_method = "http", fields=None, conditions=None):
+ """
+ Taken from the AWS book Python examples and modified for use with boto
+ This only returns the arguments required for the post form, not the actual form
+ This does not return the file input field which also needs to be added
+
+ :param bucket_name: Bucket to submit to
+ :type bucket_name: string
+
+ :param key: Key name, optionally add ${filename} to the end to attach the submitted filename
+ :type key: string
+
+ :param expires_in: Time (in seconds) before this expires, defaults to 6000
+ :type expires_in: integer
+
+ :param acl: ACL rule to use, if any
+ :type acl: :class:`boto.s3.acl.ACL`
+
+ :param success_action_redirect: URL to redirect to on success
+ :type success_action_redirect: string
+
+ :param max_content_length: Maximum size for this file
+ :type max_content_length: integer
+
+ :type http_method: string
+ :param http_method: HTTP Method to use, "http" or "https"
+
+
+ :rtype: dict
+ :return: A dictionary containing field names/values as well as a url to POST to
+
+ .. code-block:: python
+
+ {
+ "action": action_url_to_post_to,
+ "fields": [
+ {
+ "name": field_name,
+ "value": field_value
+ },
+ {
+ "name": field_name2,
+ "value": field_value2
+ }
+ ]
+ }
+
+ """
+ if fields == None:
+ fields = []
+ if conditions == None:
+ conditions = []
+ expiration = time.gmtime(int(time.time() + expires_in))
+
+ # Generate policy document
+ conditions.append('{"bucket": "%s"}' % bucket_name)
+ if key.endswith("${filename}"):
+ conditions.append('["starts-with", "$key", "%s"]' % key[:-len("${filename}")])
+ else:
+ conditions.append('{"key": "%s"}' % key)
+ if acl:
+ conditions.append('{"acl": "%s"}' % acl)
+ fields.append({ "name": "acl", "value": acl})
+ if success_action_redirect:
+ conditions.append('{"success_action_redirect": "%s"}' % success_action_redirect)
+ fields.append({ "name": "success_action_redirect", "value": success_action_redirect})
+ if max_content_length:
+ conditions.append('["content-length-range", 0, %i]' % max_content_length)
+ fields.append({"name":'content-length-range', "value": "0,%i" % max_content_length})
+
+ policy = self.build_post_policy(expiration, conditions)
+
+ # Add the base64-encoded policy document as the 'policy' field
+ policy_b64 = base64.b64encode(policy)
+ fields.append({"name": "policy", "value": policy_b64})
+
+ # Add the AWS access key as the 'AWSAccessKeyId' field
+ fields.append({"name": "AWSAccessKeyId", "value": self.aws_access_key_id})
+
+ # Add signature for encoded policy document as the 'AWSAccessKeyId' field
+ hmac_copy = self.hmac.copy()
+ hmac_copy.update(policy_b64)
+ signature = base64.encodestring(hmac_copy.digest()).strip()
+ fields.append({"name": "signature", "value": signature})
+ fields.append({"name": "key", "value": key})
+
+ # HTTPS protocol will be used if the secure HTTP option is enabled.
+ url = '%s://%s.s3.amazonaws.com/' % (http_method, bucket_name)
+
+ return {"action": url, "fields": fields}
+
+
+ def generate_url(self, expires_in, method, bucket='', key='',
+ headers=None, query_auth=True, force_http=False):
+ if not headers:
+ headers = {}
+ expires = int(time.time() + expires_in)
+ auth_path = self.calling_format.build_auth_path(bucket, key)
+ canonical_str = boto.utils.canonical_string(method, auth_path,
+ headers, expires)
+ hmac_copy = self.hmac.copy()
+ hmac_copy.update(canonical_str)
+ b64_hmac = base64.encodestring(hmac_copy.digest()).strip()
+ encoded_canonical = urllib.quote_plus(b64_hmac)
+ self.calling_format.build_path_base(bucket, key)
+ if query_auth:
+ query_part = '?' + self.QueryString % (encoded_canonical, expires,
+ self.aws_access_key_id)
+ if 'x-amz-security-token' in headers:
+ query_part += '&x-amz-security-token=%s' % urllib.quote(headers['x-amz-security-token']);
+ else:
+ query_part = ''
+ if force_http:
+ protocol = 'http'
+ port = 80
+ else:
+ protocol = self.protocol
+ port = self.port
+ return self.calling_format.build_url_base(protocol, self.server_name(port),
+ bucket, key) + query_part
+
+ def get_all_buckets(self, headers=None):
+ response = self.make_request('GET')
+ body = response.read()
+ if response.status > 300:
+ raise S3ResponseError(response.status, response.reason, body, headers=headers)
+ rs = ResultSet([('Bucket', Bucket)])
+ h = handler.XmlHandler(rs, self)
+ xml.sax.parseString(body, h)
+ return rs
+
+ def get_canonical_user_id(self, headers=None):
+ """
+ Convenience method that returns the "CanonicalUserID" of the user who's credentials
+ are associated with the connection. The only way to get this value is to do a GET
+ request on the service which returns all buckets associated with the account. As part
+ of that response, the canonical userid is returned. This method simply does all of
+ that and then returns just the user id.
+
+ :rtype: string
+ :return: A string containing the canonical user id.
+ """
+ rs = self.get_all_buckets(headers=headers)
+ return rs.ID
+
+ def get_bucket(self, bucket_name, validate=True, headers=None):
+ bucket = Bucket(self, bucket_name)
+ if validate:
+ bucket.get_all_keys(headers, maxkeys=0)
+ return bucket
+
+ def lookup(self, bucket_name, validate=True, headers=None):
+ try:
+ bucket = self.get_bucket(bucket_name, validate, headers=headers)
+ except:
+ bucket = None
+ return bucket
+
+ def create_bucket(self, bucket_name, headers=None,
+ location=Location.DEFAULT, policy=None):
+ """
+ Creates a new located bucket. By default it's in the USA. You can pass
+ Location.EU to create an European bucket.
+
+ :type bucket_name: string
+ :param bucket_name: The name of the new bucket
+
+ :type headers: dict
+ :param headers: Additional headers to pass along with the request to AWS.
+
+ :type location: :class:`boto.s3.connection.Location`
+ :param location: The location of the new bucket
+
+ :type policy: :class:`boto.s3.acl.CannedACLStrings`
+ :param policy: A canned ACL policy that will be applied to the new key in S3.
+
+ """
+ # TODO: Not sure what Exception Type from boto.exception to use.
+ if not bucket_name.islower():
+ raise Exception("Bucket names must be lower case.")
+
+ if policy:
+ if headers:
+ headers['x-amz-acl'] = policy
+ else:
+ headers = {'x-amz-acl' : policy}
+ if location == Location.DEFAULT:
+ data = ''
+ else:
+ data = '<CreateBucketConstraint><LocationConstraint>' + \
+ location + '</LocationConstraint></CreateBucketConstraint>'
+ response = self.make_request('PUT', bucket_name, headers=headers,
+ data=data)
+ body = response.read()
+ if response.status == 409:
+ raise S3CreateError(response.status, response.reason, body)
+ if response.status == 200:
+ return Bucket(self, bucket_name)
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def delete_bucket(self, bucket, headers=None):
+ response = self.make_request('DELETE', bucket, headers=headers)
+ body = response.read()
+ if response.status != 204:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ def make_request(self, method, bucket='', key='', headers=None, data='',
+ query_args=None, sender=None):
+ if isinstance(bucket, Bucket):
+ bucket = bucket.name
+ if isinstance(key, Key):
+ key = key.name
+ path = self.calling_format.build_path_base(bucket, key)
+ auth_path = self.calling_format.build_auth_path(bucket, key)
+ host = self.calling_format.build_host(self.server_name(), bucket)
+ if query_args:
+ path += '?' + query_args
+ auth_path += '?' + query_args
+ return AWSAuthConnection.make_request(self, method, path, headers,
+ data, host, auth_path, sender)
+
diff --git a/vendor/boto/boto/s3/deletemarker.py b/vendor/boto/boto/s3/deletemarker.py
new file mode 100644
index 0000000000..3462d42eeb
--- /dev/null
+++ b/vendor/boto/boto/s3/deletemarker.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.s3.user import User
+
+class DeleteMarker:
+ def __init__(self, bucket=None, name=None):
+ self.bucket = bucket
+ self.name = name
+ self.is_latest = False
+ self.last_modified = None
+ self.owner = None
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Owner':
+ self.owner = User(self)
+ return self.owner
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Key':
+ self.name = value.encode('utf-8')
+ elif name == 'IsLatest':
+ if value == 'true':
+ self.is_lastest = True
+ else:
+ self.is_latest = False
+ elif name == 'LastModified':
+ self.last_modified = value
+ elif name == 'Owner':
+ pass
+ elif name == 'VersionId':
+ self.version_id = value
+ else:
+ setattr(self, name, value)
+
+
diff --git a/vendor/boto/boto/s3/key.py b/vendor/boto/boto/s3/key.py
new file mode 100644
index 0000000000..a0bf840d0b
--- /dev/null
+++ b/vendor/boto/boto/s3/key.py
@@ -0,0 +1,804 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import mimetypes
+import os
+import rfc822
+import StringIO
+import base64
+import boto.utils
+from boto.exception import S3ResponseError, S3DataError, BotoClientError
+from boto.s3.user import User
+from boto import UserAgent
+
+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import md5
+
+
+class Key(object):
+
+ DefaultContentType = 'application/octet-stream'
+
+ BufferSize = 8192
+
+ def __init__(self, bucket=None, name=None):
+ self.bucket = bucket
+ self.name = name
+ self.metadata = {}
+ self.content_type = self.DefaultContentType
+ self.content_encoding = None
+ self.filename = None
+ self.etag = None
+ self.last_modified = None
+ self.owner = None
+ self.storage_class = None
+ self.md5 = None
+ self.base64md5 = None
+ self.path = None
+ self.resp = None
+ self.mode = None
+ self.size = None
+ self.version_id = None
+ self.source_version_id = None
+ self.delete_marker = False
+
+ def __repr__(self):
+ if self.bucket:
+ return '<Key: %s,%s>' % (self.bucket.name, self.name)
+ else:
+ return '<Key: None,%s>' % self.name
+
+ def __getattr__(self, name):
+ if name == 'key':
+ return self.name
+ else:
+ raise AttributeError
+
+ def __setattr__(self, name, value):
+ if name == 'key':
+ self.__dict__['name'] = value
+ else:
+ self.__dict__[name] = value
+
+ def __iter__(self):
+ return self
+
+ def handle_version_headers(self, resp):
+ self.version_id = resp.getheader('x-amz-version-id', None)
+ self.source_version_id = resp.getheader('x-amz-copy-source-version-id', None)
+ if resp.getheader('x-amz-delete-marker', 'false') == 'true':
+ self.delete_marker = True
+ else:
+ self.delete_marker = False
+
+ def open_read(self, headers=None, query_args=None):
+ """
+ Open this key for reading
+
+ :type headers: dict
+ :param headers: Headers to pass in the web request
+
+ :type query_args: string
+ :param query_args: Arguments to pass in the query string (ie, 'torrent')
+ """
+ if self.resp == None:
+ self.mode = 'r'
+
+ self.resp = self.bucket.connection.make_request('GET',
+ self.bucket.name,
+ self.name, headers,
+ query_args=query_args)
+ if self.resp.status < 199 or self.resp.status > 299:
+ body = self.resp.read()
+ raise S3ResponseError(self.resp.status, self.resp.reason, body)
+ response_headers = self.resp.msg
+ self.metadata = boto.utils.get_aws_metadata(response_headers)
+ for name,value in response_headers.items():
+ if name.lower() == 'content-length':
+ self.size = int(value)
+ elif name.lower() == 'etag':
+ self.etag = value
+ elif name.lower() == 'content-type':
+ self.content_type = value
+ elif name.lower() == 'content-encoding':
+ self.content_encoding = value
+ elif name.lower() == 'last-modified':
+ self.last_modified = value
+ self.handle_version_headers(self.resp)
+
+ def open_write(self, headers=None):
+ """
+ Open this key for writing.
+ Not yet implemented
+
+ :type headers: dict
+ :param headers: Headers to pass in the write request
+ """
+ raise BotoClientError('Not Implemented')
+
+ def open(self, mode='r', headers=None, query_args=None):
+ if mode == 'r':
+ self.mode = 'r'
+ self.open_read(headers=headers, query_args=query_args)
+ elif mode == 'w':
+ self.mode = 'w'
+ self.open_write(headers=headers)
+ else:
+ raise BotoClientError('Invalid mode: %s' % mode)
+
+ closed = False
+ def close(self):
+ if self.resp:
+ self.resp.read()
+ self.resp = None
+ self.mode = None
+ self.closed = True
+
+ def next(self):
+ """
+ By providing a next method, the key object supports use as an iterator.
+ For example, you can now say:
+
+ for bytes in key:
+ write bytes to a file or whatever
+
+ All of the HTTP connection stuff is handled for you.
+ """
+ self.open_read()
+ data = self.resp.read(self.BufferSize)
+ if not data:
+ self.close()
+ raise StopIteration
+ return data
+
+ def read(self, size=0):
+ if size == 0:
+ size = self.BufferSize
+ self.open_read()
+ data = self.resp.read(size)
+ if not data:
+ self.close()
+ return data
+
+ def copy(self, dst_bucket, dst_key, metadata=None):
+ """
+ Copy this Key to another bucket.
+
+ :type dst_bucket: string
+ :param dst_bucket: The name of the destination bucket
+
+ :type dst_key: string
+ :param dst_key: The name of the destinatino key
+
+ :type metadata: dict
+ :param metadata: Metadata to be associated with new key.
+ If metadata is supplied, it will replace the
+ metadata of the source key being copied.
+ If no metadata is supplied, the source key's
+ metadata will be copied to the new key.
+
+ :rtype: :class:`boto.s3.key.Key` or subclass
+ :returns: An instance of the newly created key object
+ """
+ dst_bucket = self.bucket.connection.lookup(dst_bucket)
+ return dst_bucket.copy_key(dst_key, self.bucket.name, self.name, metadata)
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Owner':
+ self.owner = User(self)
+ return self.owner
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Key':
+ self.name = value.encode('utf-8')
+ elif name == 'ETag':
+ self.etag = value
+ elif name == 'LastModified':
+ self.last_modified = value
+ elif name == 'Size':
+ self.size = int(value)
+ elif name == 'StorageClass':
+ self.storage_class = value
+ elif name == 'Owner':
+ pass
+ elif name == 'VersionId':
+ self.version_id = value
+ else:
+ setattr(self, name, value)
+
+ def exists(self):
+ """
+ Returns True if the key exists
+
+ :rtype: bool
+ :return: Whether the key exists on S3
+ """
+ return bool(self.bucket.lookup(self.name))
+
+ def delete(self):
+ """
+ Delete this key from S3
+ """
+ return self.bucket.delete_key(self.name)
+
+ def get_metadata(self, name):
+ return self.metadata.get(name)
+
+ def set_metadata(self, name, value):
+ self.metadata[name] = value
+
+ def update_metadata(self, d):
+ self.metadata.update(d)
+
+ # convenience methods for setting/getting ACL
+ def set_acl(self, acl_str, headers=None):
+ if self.bucket != None:
+ self.bucket.set_acl(acl_str, self.name, headers=headers)
+
+ def get_acl(self, headers=None):
+ if self.bucket != None:
+ return self.bucket.get_acl(self.name, headers=headers)
+
+ def get_xml_acl(self, headers=None):
+ if self.bucket != None:
+ return self.bucket.get_xml_acl(self.name, headers=headers)
+
+ def set_xml_acl(self, acl_str, headers=None):
+ if self.bucket != None:
+ return self.bucket.set_xml_acl(acl_str, self.name, headers=headers)
+
+ def set_canned_acl(self, acl_str, headers=None):
+ return self.bucket.set_canned_acl(acl_str, self.name, headers)
+
+ def make_public(self, headers=None):
+ return self.bucket.set_canned_acl('public-read', self.name, headers)
+
+ def generate_url(self, expires_in, method='GET', headers=None,
+ query_auth=True, force_http=False):
+ """
+ Generate a URL to access this key.
+
+ :type expires_in: int
+ :param expires_in: How long the url is valid for, in seconds
+
+ :type method: string
+ :param method: The method to use for retrieving the file (default is GET)
+
+ :type headers: dict
+ :param headers: Any headers to pass along in the request
+
+ :type query_auth: bool
+ :param query_auth:
+
+ :rtype: string
+ :return: The URL to access the key
+ """
+ return self.bucket.connection.generate_url(expires_in, method,
+ self.bucket.name, self.name,
+ headers, query_auth, force_http)
+
+ def send_file(self, fp, headers=None, cb=None, num_cb=10):
+ """
+ Upload a file to a key into a bucket on S3.
+
+ :type fp: file
+ :param fp: The file pointer to upload
+
+ :type headers: dict
+ :param headers: The headers to pass along with the PUT request
+
+ :type cb: function
+ :param cb: a callback function that will be called to report
+ progress on the upload. The callback should accept two integer
+ parameters, the first representing the number of bytes that have
+ been successfully transmitted to S3 and the second representing
+ the total number of bytes that need to be transmitted.
+
+ :type cb: int
+ :param num_cb: (optional) If a callback is specified with the cb parameter
+ this parameter determines the granularity of the callback by defining
+ the maximum number of times the callback will be called during the file transfer.
+
+ """
+ def sender(http_conn, method, path, data, headers):
+ http_conn.putrequest(method, path)
+ for key in headers:
+ http_conn.putheader(key, headers[key])
+ http_conn.endheaders()
+ fp.seek(0)
+ save_debug = self.bucket.connection.debug
+ self.bucket.connection.debug = 0
+ if cb:
+ if num_cb > 2:
+ cb_count = self.size / self.BufferSize / (num_cb-2)
+ else:
+ cb_count = 0
+ i = total_bytes = 0
+ cb(total_bytes, self.size)
+ l = fp.read(self.BufferSize)
+ while len(l) > 0:
+ http_conn.send(l)
+ if cb:
+ total_bytes += len(l)
+ i += 1
+ if i == cb_count:
+ cb(total_bytes, self.size)
+ i = 0
+ l = fp.read(self.BufferSize)
+ if cb:
+ cb(total_bytes, self.size)
+ response = http_conn.getresponse()
+ body = response.read()
+ fp.seek(0)
+ self.bucket.connection.debug = save_debug
+ if response.status == 500 or response.status == 503 or \
+ response.getheader('location'):
+ # we'll try again
+ return response
+ elif response.status >= 200 and response.status <= 299:
+ self.etag = response.getheader('etag')
+ if self.etag != '"%s"' % self.md5:
+ raise S3DataError('ETag from S3 did not match computed MD5')
+ return response
+ else:
+ raise S3ResponseError(response.status, response.reason, body)
+
+ if not headers:
+ headers = {}
+ else:
+ headers = headers.copy()
+ headers['User-Agent'] = UserAgent
+ headers['Content-MD5'] = self.base64md5
+ if headers.has_key('Content-Type'):
+ self.content_type = headers['Content-Type']
+ elif self.path:
+ self.content_type = mimetypes.guess_type(self.path)[0]
+ if self.content_type == None:
+ self.content_type = self.DefaultContentType
+ headers['Content-Type'] = self.content_type
+ else:
+ headers['Content-Type'] = self.content_type
+ headers['Content-Length'] = str(self.size)
+ headers['Expect'] = '100-Continue'
+ headers = boto.utils.merge_meta(headers, self.metadata)
+ resp = self.bucket.connection.make_request('PUT', self.bucket.name,
+ self.name, headers,
+ sender=sender)
+ self.handle_version_headers(resp)
+
+ def compute_md5(self, fp):
+ """
+ :type fp: file
+ :param fp: File pointer to the file to MD5 hash. The file pointer will be
+ reset to the beginning of the file before the method returns.
+
+ :rtype: tuple
+ :return: A tuple containing the hex digest version of the MD5 hash
+ as the first element and the base64 encoded version of the
+ plain digest as the second element.
+ """
+ m = md5()
+ fp.seek(0)
+ s = fp.read(self.BufferSize)
+ while s:
+ m.update(s)
+ s = fp.read(self.BufferSize)
+ hex_md5 = m.hexdigest()
+ base64md5 = base64.encodestring(m.digest())
+ if base64md5[-1] == '\n':
+ base64md5 = base64md5[0:-1]
+ self.size = fp.tell()
+ fp.seek(0)
+ return (hex_md5, base64md5)
+
+ def set_contents_from_file(self, fp, headers=None, replace=True, cb=None, num_cb=10,
+ policy=None, md5=None):
+ """
+ Store an object in S3 using the name of the Key object as the
+ key in S3 and the contents of the file pointed to by 'fp' as the
+ contents.
+
+ :type fp: file
+ :param fp: the file whose contents to upload
+
+ :type headers: dict
+ :param headers: additional HTTP headers that will be sent with the PUT request.
+
+ :type replace: bool
+ :param replace: If this parameter is False, the method
+ will first check to see if an object exists in the
+ bucket with the same key. If it does, it won't
+ overwrite it. The default value is True which will
+ overwrite the object.
+
+ :type cb: function
+ :param cb: a callback function that will be called to report
+ progress on the upload. The callback should accept two integer
+ parameters, the first representing the number of bytes that have
+ been successfully transmitted to S3 and the second representing
+ the total number of bytes that need to be transmitted.
+
+ :type cb: int
+ :param num_cb: (optional) If a callback is specified with the cb parameter
+ this parameter determines the granularity of the callback by defining
+ the maximum number of times the callback will be called during the file transfer.
+
+ :type policy: :class:`boto.s3.acl.CannedACLStrings`
+ :param policy: A canned ACL policy that will be applied to the new key in S3.
+
+ :type md5: A tuple containing the hexdigest version of the MD5 checksum of the
+ file as the first element and the Base64-encoded version of the plain
+ checksum as the second element. This is the same format returned by
+ the compute_md5 method.
+ :param md5: If you need to compute the MD5 for any reason prior to upload,
+ it's silly to have to do it twice so this param, if present, will be
+ used as the MD5 values of the file. Otherwise, the checksum will be computed.
+ """
+ if policy:
+ if headers:
+ headers['x-amz-acl'] = policy
+ else:
+ headers = {'x-amz-acl' : policy}
+ if hasattr(fp, 'name'):
+ self.path = fp.name
+ if self.bucket != None:
+ if not md5:
+ md5 = self.compute_md5(fp)
+ self.md5 = md5[0]
+ self.base64md5 = md5[1]
+ if self.name == None:
+ self.name = self.md5
+ if not replace:
+ k = self.bucket.lookup(self.name)
+ if k:
+ return
+ self.send_file(fp, headers, cb, num_cb)
+
+ def set_contents_from_filename(self, filename, headers=None, replace=True, cb=None, num_cb=10,
+ policy=None, md5=None):
+ """
+ Store an object in S3 using the name of the Key object as the
+ key in S3 and the contents of the file named by 'filename'.
+ See set_contents_from_file method for details about the
+ parameters.
+
+ :type filename: string
+ :param filename: The name of the file that you want to put onto S3
+
+ :type headers: dict
+ :param headers: Additional headers to pass along with the request to AWS.
+
+ :type replace: bool
+ :param replace: If True, replaces the contents of the file if it already exists.
+
+ :type cb: function
+ :param cb: (optional) a callback function that will be called to report
+ progress on the download. The callback should accept two integer
+ parameters, the first representing the number of bytes that have
+ been successfully transmitted from S3 and the second representing
+ the total number of bytes that need to be transmitted.
+
+ :type cb: int
+ :param num_cb: (optional) If a callback is specified with the cb parameter
+ this parameter determines the granularity of the callback by defining
+ the maximum number of times the callback will be called during the file transfer.
+
+ :type policy: :class:`boto.s3.acl.CannedACLStrings`
+ :param policy: A canned ACL policy that will be applied to the new key in S3.
+
+ :type md5: A tuple containing the hexdigest version of the MD5 checksum of the
+ file as the first element and the Base64-encoded version of the plain
+ checksum as the second element. This is the same format returned by
+ the compute_md5 method.
+ :param md5: If you need to compute the MD5 for any reason prior to upload,
+ it's silly to have to do it twice so this param, if present, will be
+ used as the MD5 values of the file. Otherwise, the checksum will be computed.
+ """
+ fp = open(filename, 'rb')
+ self.set_contents_from_file(fp, headers, replace, cb, num_cb, policy)
+ fp.close()
+
+ def set_contents_from_string(self, s, headers=None, replace=True, cb=None, num_cb=10,
+ policy=None, md5=None):
+ """
+ Store an object in S3 using the name of the Key object as the
+ key in S3 and the string 's' as the contents.
+ See set_contents_from_file method for details about the
+ parameters.
+
+ :type headers: dict
+ :param headers: Additional headers to pass along with the request to AWS.
+
+ :type replace: bool
+ :param replace: If True, replaces the contents of the file if it already exists.
+
+ :type cb: function
+ :param cb: (optional) a callback function that will be called to report
+ progress on the download. The callback should accept two integer
+ parameters, the first representing the number of bytes that have
+ been successfully transmitted from S3 and the second representing
+ the total number of bytes that need to be transmitted.
+
+ :type cb: int
+ :param num_cb: (optional) If a callback is specified with the cb parameter
+ this parameter determines the granularity of the callback by defining
+ the maximum number of times the callback will be called during the file transfer.
+
+ :type policy: :class:`boto.s3.acl.CannedACLStrings`
+ :param policy: A canned ACL policy that will be applied to the new key in S3.
+
+ :type md5: A tuple containing the hexdigest version of the MD5 checksum of the
+ file as the first element and the Base64-encoded version of the plain
+ checksum as the second element. This is the same format returned by
+ the compute_md5 method.
+ :param md5: If you need to compute the MD5 for any reason prior to upload,
+ it's silly to have to do it twice so this param, if present, will be
+ used as the MD5 values of the file. Otherwise, the checksum will be computed.
+ """
+ fp = StringIO.StringIO(s)
+ r = self.set_contents_from_file(fp, headers, replace, cb, num_cb, policy)
+ fp.close()
+ return r
+
+ def get_file(self, fp, headers=None, cb=None, num_cb=10,
+ torrent=False, version_id=None):
+ """
+ Retrieves a file from an S3 Key
+
+ :type fp: file
+ :param fp: File pointer to put the data into
+
+ :type headers: string
+ :param: headers to send when retrieving the files
+
+ :type cb: function
+ :param cb: (optional) a callback function that will be called to report
+ progress on the download. The callback should accept two integer
+ parameters, the first representing the number of bytes that have
+ been successfully transmitted from S3 and the second representing
+ the total number of bytes that need to be transmitted.
+
+
+ :type cb: int
+ :param num_cb: (optional) If a callback is specified with the cb parameter
+ this parameter determines the granularity of the callback by defining
+ the maximum number of times the callback will be called during the file transfer.
+
+ :type torrent: bool
+ :param torrent: Flag for whether to get a torrent for the file
+ """
+ if cb:
+ if num_cb > 2:
+ cb_count = self.size / self.BufferSize / (num_cb-2)
+ else:
+ cb_count = 0
+ i = total_bytes = 0
+ cb(total_bytes, self.size)
+ save_debug = self.bucket.connection.debug
+ if self.bucket.connection.debug == 1:
+ self.bucket.connection.debug = 0
+
+ query_args = ''
+ if torrent:
+ query_args = 'torrent'
+ elif version_id:
+ query_args = 'versionId=%s' % version_id
+ self.open('r', headers, query_args=query_args)
+ for bytes in self:
+ fp.write(bytes)
+ if cb:
+ total_bytes += len(bytes)
+ i += 1
+ if i == cb_count:
+ cb(total_bytes, self.size)
+ i = 0
+ if cb:
+ cb(total_bytes, self.size)
+ self.close()
+ self.bucket.connection.debug = save_debug
+
+ def get_torrent_file(self, fp, headers=None, cb=None, num_cb=10):
+ """
+ Get a torrent file (see to get_file)
+
+ :type fp: file
+ :param fp: The file pointer of where to put the torrent
+
+ :type headers: dict
+ :param headers: Headers to be passed
+
+ :type cb: function
+ :param cb: Callback function to call on retrieved data
+
+ :type cb: int
+ :param num_cb: (optional) If a callback is specified with the cb parameter
+ this parameter determines the granularity of the callback by defining
+ the maximum number of times the callback will be called during the file transfer.
+
+ """
+ return self.get_file(fp, headers, cb, num_cb, torrent=True)
+
+ def get_contents_to_file(self, fp, headers=None,
+ cb=None, num_cb=10,
+ torrent=False,
+ version_id=None):
+ """
+ Retrieve an object from S3 using the name of the Key object as the
+ key in S3. Write the contents of the object to the file pointed
+ to by 'fp'.
+
+ :type fp: File -like object
+ :param fp:
+
+ :type headers: dict
+ :param headers: additional HTTP headers that will be sent with the GET request.
+
+ :type cb: function
+ :param cb: (optional) a callback function that will be called to report
+ progress on the download. The callback should accept two integer
+ parameters, the first representing the number of bytes that have
+ been successfully transmitted from S3 and the second representing
+ the total number of bytes that need to be transmitted.
+
+
+ :type cb: int
+ :param num_cb: (optional) If a callback is specified with the cb parameter
+ this parameter determines the granularity of the callback by defining
+ the maximum number of times the callback will be called during the file transfer.
+
+ :type torrent: bool
+ :param torrent: If True, returns the contents of a torrent file as a string.
+
+ """
+ if self.bucket != None:
+ self.get_file(fp, headers, cb, num_cb, torrent=torrent,
+ version_id=version_id)
+
+ def get_contents_to_filename(self, filename, headers=None,
+ cb=None, num_cb=10,
+ torrent=False,
+ version_id=None):
+ """
+ Retrieve an object from S3 using the name of the Key object as the
+ key in S3. Store contents of the object to a file named by 'filename'.
+ See get_contents_to_file method for details about the
+ parameters.
+
+ :type filename: string
+ :param filename: The filename of where to put the file contents
+
+ :type headers: dict
+ :param headers: Any additional headers to send in the request
+
+ :type cb: function
+ :param cb: (optional) a callback function that will be called to report
+ progress on the download. The callback should accept two integer
+ parameters, the first representing the number of bytes that have
+ been successfully transmitted from S3 and the second representing
+ the total number of bytes that need to be transmitted.
+
+
+ :type cb: int
+ :param num_cb: (optional) If a callback is specified with the cb parameter
+ this parameter determines the granularity of the callback by defining
+ the maximum number of times the callback will be called during the file transfer.
+
+ :type torrent: bool
+ :param torrent: If True, returns the contents of a torrent file as a string.
+
+ """
+ fp = open(filename, 'wb')
+ self.get_contents_to_file(fp, headers, cb, num_cb, torrent=torrent,
+ version_id=version_id)
+ fp.close()
+ # if last_modified date was sent from s3, try to set file's timestamp
+ if self.last_modified != None:
+ try:
+ modified_tuple = rfc822.parsedate_tz(self.last_modified)
+ modified_stamp = int(rfc822.mktime_tz(modified_tuple))
+ os.utime(fp.name, (modified_stamp, modified_stamp))
+ except Exception: pass
+
+ def get_contents_as_string(self, headers=None,
+ cb=None, num_cb=10,
+ torrent=False,
+ version_id=None):
+ """
+ Retrieve an object from S3 using the name of the Key object as the
+ key in S3. Return the contents of the object as a string.
+ See get_contents_to_file method for details about the
+ parameters.
+
+ :type headers: dict
+ :param headers: Any additional headers to send in the request
+
+ :type cb: function
+ :param cb: (optional) a callback function that will be called to report
+ progress on the download. The callback should accept two integer
+ parameters, the first representing the number of bytes that have
+ been successfully transmitted from S3 and the second representing
+ the total number of bytes that need to be transmitted.
+
+ :type cb: int
+ :param num_cb: (optional) If a callback is specified with the cb parameter
+ this parameter determines the granularity of the callback by defining
+ the maximum number of times the callback will be called during the file transfer.
+
+
+ :type cb: int
+ :param num_cb: (optional) If a callback is specified with the cb parameter
+ this parameter determines the granularity of the callback by defining
+ the maximum number of times the callback will be called during the file transfer.
+
+ :type torrent: bool
+ :param torrent: If True, returns the contents of a torrent file as a string.
+
+ :rtype: string
+ :returns: The contents of the file as a string
+ """
+ fp = StringIO.StringIO()
+ self.get_contents_to_file(fp, headers, cb, num_cb, torrent=torrent,
+ version_id=version_id)
+ return fp.getvalue()
+
+ def add_email_grant(self, permission, email_address):
+ """
+ Convenience method that provides a quick way to add an email grant to a key.
+ This method retrieves the current ACL, creates a new grant based on the parameters
+ passed in, adds that grant to the ACL and then PUT's the new ACL back to S3.
+
+ :type permission: string
+ :param permission: The permission being granted. Should be one of:
+ READ|WRITE|READ_ACP|WRITE_ACP|FULL_CONTROL
+ See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingAuthAccess.html
+ for more details on permissions.
+
+ :type email_address: string
+ :param email_address: The email address associated with the AWS account your are granting
+ the permission to.
+ """
+ policy = self.get_acl()
+ policy.acl.add_email_grant(permission, email_address)
+ self.set_acl(policy)
+
+ def add_user_grant(self, permission, user_id):
+ """
+ Convenience method that provides a quick way to add a canonical user grant to a key.
+ This method retrieves the current ACL, creates a new grant based on the parameters
+ passed in, adds that grant to the ACL and then PUT's the new ACL back to S3.
+
+ :type permission: string
+ :param permission: The permission being granted. Should be one of:
+ READ|WRITE|READ_ACP|WRITE_ACP|FULL_CONTROL
+ See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingAuthAccess.html
+ for more details on permissions.
+
+ :type user_id: string
+ :param user_id: The canonical user id associated with the AWS account your are granting
+ the permission to.
+ """
+ policy = self.get_acl()
+ policy.acl.add_user_grant(permission, user_id)
+ self.set_acl(policy)
diff --git a/vendor/boto/boto/s3/prefix.py b/vendor/boto/boto/s3/prefix.py
new file mode 100644
index 0000000000..fc0f26ab58
--- /dev/null
+++ b/vendor/boto/boto/s3/prefix.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Prefix:
+ def __init__(self, bucket=None, name=None):
+ self.bucket = bucket
+ self.name = name
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Prefix':
+ self.name = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/s3/user.py b/vendor/boto/boto/s3/user.py
new file mode 100644
index 0000000000..f45f038130
--- /dev/null
+++ b/vendor/boto/boto/s3/user.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class User:
+ def __init__(self, parent=None, id='', display_name=''):
+ if parent:
+ parent.owner = self
+ self.type = None
+ self.id = id
+ self.display_name = display_name
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'DisplayName':
+ self.display_name = value
+ elif name == 'ID':
+ self.id = value
+ else:
+ setattr(self, name, value)
+
+ def to_xml(self, element_name='Owner'):
+ if self.type:
+ s = '<%s xsi:type="%s">' % (element_name, self.type)
+ else:
+ s = '<%s>' % element_name
+ s += '<ID>%s</ID>' % self.id
+ s += '<DisplayName>%s</DisplayName>' % self.display_name
+ s += '</%s>' % element_name
+ return s
diff --git a/vendor/boto/boto/sdb/__init__.py b/vendor/boto/boto/sdb/__init__.py
new file mode 100644
index 0000000000..df1f95b3e2
--- /dev/null
+++ b/vendor/boto/boto/sdb/__init__.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+from regioninfo import SDBRegionInfo
+
+
+def regions():
+ """
+ Get all available regions for the SDB service.
+
+ :rtype: list
+ :return: A list of :class:`boto.sdb.regioninfo.RegionInfo`
+ """
+ return [SDBRegionInfo(name='us-east-1', endpoint='sdb.amazonaws.com'),
+ SDBRegionInfo(name='eu-west-1', endpoint='sdb.eu-west-1.amazonaws.com'),
+ SDBRegionInfo(name='us-west-1', endpoint='sdb.us-west-1.amazonaws.com')]
+
+def connect_to_region(region_name):
+ for region in regions():
+ if region.name == region_name:
+ return region.connect()
+ return None
diff --git a/vendor/boto/boto/sdb/connection.py b/vendor/boto/boto/sdb/connection.py
new file mode 100644
index 0000000000..0824f5cb48
--- /dev/null
+++ b/vendor/boto/boto/sdb/connection.py
@@ -0,0 +1,441 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import xml.sax
+import threading
+from boto import handler
+from boto.connection import AWSQueryConnection
+from boto.sdb.domain import Domain, DomainMetaData
+from boto.sdb.item import Item
+from boto.sdb.regioninfo import SDBRegionInfo
+from boto.exception import SDBResponseError
+from boto.resultset import ResultSet
+import warnings
+
+
+class ItemThread(threading.Thread):
+
+ def __init__(self, name, domain_name, item_names):
+ threading.Thread.__init__(self, name=name)
+ print 'starting %s with %d items' % (name, len(item_names))
+ self.domain_name = domain_name
+ self.conn = SDBConnection()
+ self.item_names = item_names
+ self.items = []
+
+ def run(self):
+ for item_name in self.item_names:
+ item = self.conn.get_attributes(self.domain_name, item_name)
+ self.items.append(item)
+
+#boto.set_stream_logger('sdb')
+
+class SDBConnection(AWSQueryConnection):
+
+ DefaultRegionName = 'us-east-1'
+ DefaultRegionEndpoint = 'sdb.amazonaws.com'
+ APIVersion = '2009-04-15'
+ SignatureVersion = '2'
+ ResponseError = SDBResponseError
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, debug=0,
+ https_connection_factory=None, region=None, path='/', converter=None):
+ if not region:
+ region = SDBRegionInfo(self, self.DefaultRegionName, self.DefaultRegionEndpoint)
+ self.region = region
+ AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_key,
+ is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
+ self.region.endpoint, debug, https_connection_factory, path)
+ self.box_usage = 0.0
+ self.converter = converter
+ self.item_cls = Item
+
+ def set_item_cls(self, cls):
+ self.item_cls = cls
+
+ def build_name_value_list(self, params, attributes, replace=False,
+ label='Attribute'):
+ keys = attributes.keys()
+ keys.sort()
+ i = 1
+ for key in keys:
+ value = attributes[key]
+ if isinstance(value, list):
+ for v in value:
+ params['%s.%d.Name'%(label,i)] = key
+ if self.converter:
+ v = self.converter.encode(v)
+ params['%s.%d.Value'%(label,i)] = v
+ if replace:
+ params['%s.%d.Replace'%(label,i)] = 'true'
+ i += 1
+ else:
+ params['%s.%d.Name'%(label,i)] = key
+ if self.converter:
+ value = self.converter.encode(value)
+ params['%s.%d.Value'%(label,i)] = value
+ if replace:
+ params['%s.%d.Replace'%(label,i)] = 'true'
+ i += 1
+
+ def build_expected_value(self, params, expected_value):
+ params['Expected.1.Name'] = expected_value[0]
+ if expected_value[1] == True:
+ params['Expected.1.Exists'] = 'true'
+ elif expected_value[1] == False:
+ params['Expected.1.Exists'] = 'false'
+ else:
+ params['Expected.1.Value'] = expected_value[1]
+
+
+ def build_batch_list(self, params, items, replace=False):
+ item_names = items.keys()
+ i = 0
+ for item_name in item_names:
+ j = 0
+ item = items[item_name]
+ attr_names = item.keys()
+ params['Item.%d.ItemName' % i] = item_name
+ for attr_name in attr_names:
+ value = item[attr_name]
+ if isinstance(value, list):
+ for v in value:
+ if self.converter:
+ v = self.converter.encode(v)
+ params['Item.%d.Attribute.%d.Name' % (i,j)] = attr_name
+ params['Item.%d.Attribute.%d.Value' % (i,j)] = v
+ if replace:
+ params['Item.%d.Attribute.%d.Replace' % (i,j)] = 'true'
+ j += 1
+ else:
+ params['Item.%d.Attribute.%d.Name' % (i,j)] = attr_name
+ if self.converter:
+ value = self.converter.encode(value)
+ params['Item.%d.Attribute.%d.Value' % (i,j)] = value
+ if replace:
+ params['Item.%d.Attribute.%d.Replace' % (i,j)] = 'true'
+ j += 1
+ i += 1
+
+ def build_name_list(self, params, attribute_names):
+ i = 1
+ attribute_names.sort()
+ for name in attribute_names:
+ params['Attribute.%d.Name'%i] = name
+ i += 1
+
+ def get_usage(self):
+ """
+ Returns the BoxUsage accumulated on this SDBConnection object.
+
+ :rtype: float
+ :return: The accumulated BoxUsage of all requests made on the connection.
+ """
+ return self.box_usage
+
+ def print_usage(self):
+ """
+ Print the BoxUsage and approximate costs of all requests made on this connection.
+ """
+ print 'Total Usage: %f compute seconds' % self.box_usage
+ cost = self.box_usage * 0.14
+ print 'Approximate Cost: $%f' % cost
+
+ def get_domain(self, domain_name, validate=True):
+ domain = Domain(self, domain_name)
+ if validate:
+ self.select(domain, """select * from `%s` limit 1""" % domain_name)
+ return domain
+
+ def lookup(self, domain_name, validate=True):
+ """
+ Lookup an existing SimpleDB domain
+
+ :type domain_name: string
+ :param domain_name: The name of the new domain
+
+ :rtype: :class:`boto.sdb.domain.Domain` object or None
+ :return: The Domain object or None if the domain does not exist.
+ """
+ try:
+ domain = self.get_domain(domain_name, validate)
+ except:
+ domain = None
+ return domain
+
+ def get_all_domains(self, max_domains=None, next_token=None):
+ params = {}
+ if max_domains:
+ params['MaxNumberOfDomains'] = max_domains
+ if next_token:
+ params['NextToken'] = next_token
+ return self.get_list('ListDomains', params, [('DomainName', Domain)])
+
+ def create_domain(self, domain_name):
+ """
+ Create a SimpleDB domain.
+
+ :type domain_name: string
+ :param domain_name: The name of the new domain
+
+ :rtype: :class:`boto.sdb.domain.Domain` object
+ :return: The newly created domain
+ """
+ params = {'DomainName':domain_name}
+ d = self.get_object('CreateDomain', params, Domain)
+ d.name = domain_name
+ return d
+
+ def get_domain_and_name(self, domain_or_name):
+ if (isinstance(domain_or_name, Domain)):
+ return (domain_or_name, domain_or_name.name)
+ else:
+ return (self.get_domain(domain_or_name), domain_or_name)
+
+ def delete_domain(self, domain_or_name):
+ """
+ Delete a SimpleDB domain.
+
+ :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
+ :param domain_or_name: Either the name of a domain or a Domain object
+
+ :rtype: bool
+ :return: True if successful
+
+ B{Note:} This will delete the domain and all items within the domain.
+ """
+ domain, domain_name = self.get_domain_and_name(domain_or_name)
+ params = {'DomainName':domain_name}
+ return self.get_status('DeleteDomain', params)
+
+ def domain_metadata(self, domain_or_name):
+ """
+ Get the Metadata for a SimpleDB domain.
+
+ :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
+ :param domain_or_name: Either the name of a domain or a Domain object
+
+ :rtype: :class:`boto.sdb.domain.DomainMetaData` object
+ :return: The newly created domain metadata object
+ """
+ domain, domain_name = self.get_domain_and_name(domain_or_name)
+ params = {'DomainName':domain_name}
+ d = self.get_object('DomainMetadata', params, DomainMetaData)
+ d.domain = domain
+ return d
+
+ def put_attributes(self, domain_or_name, item_name, attributes,
+ replace=True, expected_value=None):
+ """
+ Store attributes for a given item in a domain.
+
+ :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
+ :param domain_or_name: Either the name of a domain or a Domain object
+
+ :type item_name: string
+ :param item_name: The name of the item whose attributes are being stored.
+
+ :type attribute_names: dict or dict-like object
+ :param attribute_names: The name/value pairs to store as attributes
+
+ :type expected_value: list
+ :param expected_value: If supplied, this is a list or tuple consisting
+ of a single attribute name and expected value.
+ The list can be of the form:
+ * ['name', 'value']
+ In which case the call will first verify
+ that the attribute "name" of this item has
+ a value of "value". If it does, the delete
+ will proceed, otherwise a ConditionalCheckFailed
+ error will be returned.
+ The list can also be of the form:
+ * ['name', True|False]
+ which will simply check for the existence (True)
+ or non-existencve (False) of the attribute.
+
+ :type replace: bool
+ :param replace: Whether the attribute values passed in will replace
+ existing values or will be added as addition values.
+ Defaults to True.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ domain, domain_name = self.get_domain_and_name(domain_or_name)
+ params = {'DomainName' : domain_name,
+ 'ItemName' : item_name}
+ self.build_name_value_list(params, attributes, replace)
+ if expected_value:
+ self.build_expected_value(params, expected_value)
+ return self.get_status('PutAttributes', params)
+
+ def batch_put_attributes(self, domain_or_name, items, replace=True):
+ """
+ Store attributes for multiple items in a domain.
+
+ :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
+ :param domain_or_name: Either the name of a domain or a Domain object
+
+ :type items: dict or dict-like object
+ :param items: A dictionary-like object. The keys of the dictionary are
+ the item names and the values are themselves dictionaries
+ of attribute names/values, exactly the same as the
+ attribute_names parameter of the scalar put_attributes
+ call.
+
+ :type replace: bool
+ :param replace: Whether the attribute values passed in will replace
+ existing values or will be added as addition values.
+ Defaults to True.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ domain, domain_name = self.get_domain_and_name(domain_or_name)
+ params = {'DomainName' : domain_name}
+ self.build_batch_list(params, items, replace)
+ return self.get_status('BatchPutAttributes', params, verb='POST')
+
+ def get_attributes(self, domain_or_name, item_name, attribute_names=None,
+ consistent_read=False, item=None):
+ """
+ Retrieve attributes for a given item in a domain.
+
+ :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
+ :param domain_or_name: Either the name of a domain or a Domain object
+
+ :type item_name: string
+ :param item_name: The name of the item whose attributes are being retrieved.
+
+ :type attribute_names: string or list of strings
+ :param attribute_names: An attribute name or list of attribute names. This
+ parameter is optional. If not supplied, all attributes
+ will be retrieved for the item.
+
+ :type consistent_read: bool
+ :param consistent_read: When set to true, ensures that the most recent
+ data is returned.
+
+ :rtype: :class:`boto.sdb.item.Item`
+ :return: An Item mapping type containing the requested attribute name/values
+ """
+ domain, domain_name = self.get_domain_and_name(domain_or_name)
+ params = {'DomainName' : domain_name,
+ 'ItemName' : item_name}
+ if consistent_read:
+ params['ConsistentRead'] = 'true'
+ if attribute_names:
+ if not isinstance(attribute_names, list):
+ attribute_names = [attribute_names]
+ self.build_list_params(params, attribute_names, 'AttributeName')
+ response = self.make_request('GetAttributes', params)
+ body = response.read()
+ if response.status == 200:
+ if item == None:
+ item = self.item_cls(domain, item_name)
+ h = handler.XmlHandler(item, self)
+ xml.sax.parseString(body, h)
+ return item
+ else:
+ raise SDBResponseError(response.status, response.reason, body)
+
+ def delete_attributes(self, domain_or_name, item_name, attr_names=None,
+ expected_value=None):
+ """
+ Delete attributes from a given item in a domain.
+
+ :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
+ :param domain_or_name: Either the name of a domain or a Domain object
+
+ :type item_name: string
+ :param item_name: The name of the item whose attributes are being deleted.
+
+ :type attributes: dict, list or :class:`boto.sdb.item.Item`
+ :param attributes: Either a list containing attribute names which will cause
+ all values associated with that attribute name to be deleted or
+ a dict or Item containing the attribute names and keys and list
+ of values to delete as the value. If no value is supplied,
+ all attribute name/values for the item will be deleted.
+
+ :type expected_value: list
+ :param expected_value: If supplied, this is a list or tuple consisting
+ of a single attribute name and expected value.
+ The list can be of the form:
+ * ['name', 'value']
+ In which case the call will first verify
+ that the attribute "name" of this item has
+ a value of "value". If it does, the delete
+ will proceed, otherwise a ConditionalCheckFailed
+ error will be returned.
+ The list can also be of the form:
+ * ['name', True|False]
+ which will simply check for the existence (True)
+ or non-existencve (False) of the attribute.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ domain, domain_name = self.get_domain_and_name(domain_or_name)
+ params = {'DomainName':domain_name,
+ 'ItemName' : item_name}
+ if attr_names:
+ if isinstance(attr_names, list):
+ self.build_name_list(params, attr_names)
+ elif isinstance(attr_names, dict) or isinstance(attr_names, self.item_cls):
+ self.build_name_value_list(params, attr_names)
+ if expected_value:
+ self.build_expected_value(params, expected_value)
+ return self.get_status('DeleteAttributes', params)
+
+ def select(self, domain_or_name, query='', next_token=None,
+ consistent_read=False):
+ """
+ Returns a set of Attributes for item names within domain_name that match the query.
+ The query must be expressed in using the SELECT style syntax rather than the
+ original SimpleDB query language.
+ Even though the select request does not require a domain object, a domain
+ object must be passed into this method so the Item objects returned can
+ point to the appropriate domain.
+
+ :type domain_or_name: string or :class:`boto.sdb.domain.Domain` object.
+ :param domain_or_name: Either the name of a domain or a Domain object
+
+ :type query: string
+ :param query: The SimpleDB query to be performed.
+
+ :type consistent_read: bool
+ :param consistent_read: When set to true, ensures that the most recent
+ data is returned.
+
+ :rtype: ResultSet
+ :return: An iterator containing the results.
+ """
+ domain, domain_name = self.get_domain_and_name(domain_or_name)
+ params = {'SelectExpression' : query}
+ if consistent_read:
+ params['ConsistentRead'] = 'true'
+ if next_token:
+ params['NextToken'] = next_token
+ return self.get_list('Select', params, [('Item', self.item_cls)],
+ parent=domain)
+
diff --git a/vendor/boto/boto/sdb/db/__init__.py b/vendor/boto/boto/sdb/db/__init__.py
new file mode 100644
index 0000000000..86044ed61b
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/__init__.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
diff --git a/vendor/boto/boto/sdb/db/blob.py b/vendor/boto/boto/sdb/db/blob.py
new file mode 100644
index 0000000000..8c0b66e14c
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/blob.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+
+class Blob(object):
+ """Blob object"""
+ def __init__(self, value=None, file=None, id=None):
+ self._file = file
+ self.id = id
+ self.value = value
+
+ @property
+ def file(self):
+ from StringIO import StringIO
+ if self._file:
+ f = self._file
+ else:
+ f = StringIO(self.value)
+ return f
+
+ def __str__(self):
+ if hasattr(self.file, "get_contents_as_string"):
+ return str(self.file.get_contents_as_string())
+ else:
+ return str(self.file.getvalue())
+
+ def read(self):
+ return self.file.read()
+
+ def readline(self):
+ return self.file.readline()
+
+ def next(self):
+ return self.file.next()
+
+ def __iter__(self):
+ return iter(self.file)
+
+ @property
+ def size(self):
+ if self._file:
+ return self._file.size
+ elif self.value:
+ return len(self.value)
+ else:
+ return 0
diff --git a/vendor/boto/boto/sdb/db/key.py b/vendor/boto/boto/sdb/db/key.py
new file mode 100644
index 0000000000..42a9d8dae7
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/key.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Key(object):
+
+ @classmethod
+ def from_path(cls, *args, **kwds):
+ raise NotImplementedError, "Paths are not currently supported"
+
+ def __init__(self, encoded=None, obj=None):
+ self.name = None
+ if obj:
+ self.id = obj.id
+ self.kind = obj.kind()
+ else:
+ self.id = None
+ self.kind = None
+
+ def app(self):
+ raise NotImplementedError, "Applications are not currently supported"
+
+ def kind(self):
+ return self.kind
+
+ def id(self):
+ return self.id
+
+ def name(self):
+ raise NotImplementedError, "Key Names are not currently supported"
+
+ def id_or_name(self):
+ return self.id
+
+ def has_id_or_name(self):
+ return self.id != None
+
+ def parent(self):
+ raise NotImplementedError, "Key parents are not currently supported"
+
+ def __str__(self):
+ return self.id_or_name()
diff --git a/vendor/boto/boto/sdb/db/manager/__init__.py b/vendor/boto/boto/sdb/db/manager/__init__.py
new file mode 100644
index 0000000000..0777796600
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/manager/__init__.py
@@ -0,0 +1,88 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+import boto
+
+def get_manager(cls):
+ """
+ Returns the appropriate Manager class for a given Model class. It does this by
+ looking in the boto config for a section like this::
+
+ [DB]
+ db_type = SimpleDB
+ db_user = <aws access key id>
+ db_passwd = <aws secret access key>
+ db_name = my_domain
+ [DB_TestBasic]
+ db_type = SimpleDB
+ db_user = <another aws access key id>
+ db_passwd = <another aws secret access key>
+ db_name = basic_domain
+ db_port = 1111
+
+ The values in the DB section are "generic values" that will be used if nothing more
+ specific is found. You can also create a section for a specific Model class that
+ gives the db info for that class. In the example above, TestBasic is a Model subclass.
+ """
+ db_user = boto.config.get('DB', 'db_user', None)
+ db_passwd = boto.config.get('DB', 'db_passwd', None)
+ db_type = boto.config.get('DB', 'db_type', 'SimpleDB')
+ db_name = boto.config.get('DB', 'db_name', None)
+ db_table = boto.config.get('DB', 'db_table', None)
+ db_host = boto.config.get('DB', 'db_host', "sdb.amazonaws.com")
+ db_port = boto.config.getint('DB', 'db_port', 443)
+ enable_ssl = boto.config.getbool('DB', 'enable_ssl', True)
+ sql_dir = boto.config.get('DB', 'sql_dir', None)
+ debug = boto.config.getint('DB', 'debug', 0)
+ # first see if there is a fully qualified section name in the Boto config file
+ module_name = cls.__module__.replace('.', '_')
+ db_section = 'DB_' + module_name + '_' + cls.__name__
+ if not boto.config.has_section(db_section):
+ db_section = 'DB_' + cls.__name__
+ if boto.config.has_section(db_section):
+ db_user = boto.config.get(db_section, 'db_user', db_user)
+ db_passwd = boto.config.get(db_section, 'db_passwd', db_passwd)
+ db_type = boto.config.get(db_section, 'db_type', db_type)
+ db_name = boto.config.get(db_section, 'db_name', db_name)
+ db_table = boto.config.get(db_section, 'db_table', db_table)
+ db_host = boto.config.get(db_section, 'db_host', db_host)
+ db_port = boto.config.getint(db_section, 'db_port', db_port)
+ enable_ssl = boto.config.getint(db_section, 'enable_ssl', enable_ssl)
+ debug = boto.config.getint(db_section, 'debug', debug)
+ elif hasattr(cls.__bases__[0], "_manager"):
+ return cls.__bases__[0]._manager
+ if db_type == 'SimpleDB':
+ from sdbmanager import SDBManager
+ return SDBManager(cls, db_name, db_user, db_passwd,
+ db_host, db_port, db_table, sql_dir, enable_ssl)
+ elif db_type == 'PostgreSQL':
+ from pgmanager import PGManager
+ if db_table:
+ return PGManager(cls, db_name, db_user, db_passwd,
+ db_host, db_port, db_table, sql_dir, enable_ssl)
+ else:
+ return None
+ elif db_type == 'XML':
+ from xmlmanager import XMLManager
+ return XMLManager(cls, db_name, db_user, db_passwd,
+ db_host, db_port, db_table, sql_dir, enable_ssl)
+ else:
+ raise ValueError, 'Unknown db_type: %s' % db_type
+
diff --git a/vendor/boto/boto/sdb/db/manager/pgmanager.py b/vendor/boto/boto/sdb/db/manager/pgmanager.py
new file mode 100644
index 0000000000..73a93f0ec5
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/manager/pgmanager.py
@@ -0,0 +1,389 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+from boto.sdb.db.key import Key
+from boto.sdb.db.model import Model
+import psycopg2
+import psycopg2.extensions
+import uuid
+import os
+import string
+from boto.exception import SDBPersistenceError
+
+psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
+
+class PGConverter:
+
+ def __init__(self, manager):
+ self.manager = manager
+ self.type_map = {Key : (self.encode_reference, self.decode_reference),
+ Model : (self.encode_reference, self.decode_reference)}
+
+ def encode(self, type, value):
+ if type in self.type_map:
+ encode = self.type_map[type][0]
+ return encode(value)
+ return value
+
+ def decode(self, type, value):
+ if type in self.type_map:
+ decode = self.type_map[type][1]
+ return decode(value)
+ return value
+
+ def encode_prop(self, prop, value):
+ if isinstance(value, list):
+ if hasattr(prop, 'item_type'):
+ s = "{"
+ new_value = []
+ for v in value:
+ item_type = getattr(prop, 'item_type')
+ if Model in item_type.mro():
+ item_type = Model
+ new_value.append('%s' % self.encode(item_type, v))
+ s += ','.join(new_value)
+ s += "}"
+ return s
+ else:
+ return value
+ return self.encode(prop.data_type, value)
+
+ def decode_prop(self, prop, value):
+ if prop.data_type == list:
+ if value != None:
+ if not isinstance(value, list):
+ value = [value]
+ if hasattr(prop, 'item_type'):
+ item_type = getattr(prop, "item_type")
+ if Model in item_type.mro():
+ if item_type != self.manager.cls:
+ return item_type._manager.decode_value(prop, value)
+ else:
+ item_type = Model
+ return [self.decode(item_type, v) for v in value]
+ return value
+ elif hasattr(prop, 'reference_class'):
+ ref_class = getattr(prop, 'reference_class')
+ if ref_class != self.manager.cls:
+ return ref_class._manager.decode_value(prop, value)
+ else:
+ return self.decode(prop.data_type, value)
+ elif hasattr(prop, 'calculated_type'):
+ calc_type = getattr(prop, 'calculated_type')
+ return self.decode(calc_type, value)
+ else:
+ return self.decode(prop.data_type, value)
+
+ def encode_reference(self, value):
+ if isinstance(value, str) or isinstance(value, unicode):
+ return value
+ if value == None:
+ return ''
+ else:
+ return value.id
+
+ def decode_reference(self, value):
+ if not value:
+ return None
+ try:
+ return self.manager.get_object_from_id(value)
+ except:
+ raise ValueError, 'Unable to convert %s to Object' % value
+
+class PGManager(object):
+
+ def __init__(self, cls, db_name, db_user, db_passwd,
+ db_host, db_port, db_table, sql_dir, enable_ssl):
+ self.cls = cls
+ self.db_name = db_name
+ self.db_user = db_user
+ self.db_passwd = db_passwd
+ self.db_host = db_host
+ self.db_port = db_port
+ self.db_table = db_table
+ self.sql_dir = sql_dir
+ self.in_transaction = False
+ self.converter = PGConverter(self)
+ self._connect()
+
+ def _build_connect_string(self):
+ cs = 'dbname=%s user=%s password=%s host=%s port=%d'
+ return cs % (self.db_name, self.db_user, self.db_passwd,
+ self.db_host, self.db_port)
+
+ def _connect(self):
+ self.connection = psycopg2.connect(self._build_connect_string())
+ self.connection.set_client_encoding('UTF8')
+ self.cursor = self.connection.cursor()
+
+ def _object_lister(self, cursor):
+ try:
+ for row in cursor:
+ yield self._object_from_row(row, cursor.description)
+ except StopIteration:
+ cursor.close()
+ raise StopIteration
+
+ def _dict_from_row(self, row, description):
+ d = {}
+ for i in range(0, len(row)):
+ d[description[i][0]] = row[i]
+ return d
+
+ def _object_from_row(self, row, description=None):
+ if not description:
+ description = self.cursor.description
+ d = self._dict_from_row(row, description)
+ obj = self.cls(d['id'])
+ obj._manager = self
+ obj._auto_update = False
+ for prop in obj.properties(hidden=False):
+ if prop.data_type != Key:
+ v = self.decode_value(prop, d[prop.name])
+ v = prop.make_value_from_datastore(v)
+ if hasattr(prop, 'calculated_type'):
+ prop._set_direct(obj, v)
+ elif not prop.empty(v):
+ setattr(obj, prop.name, v)
+ else:
+ setattr(obj, prop.name, prop.default_value())
+ return obj
+
+ def _build_insert_qs(self, obj, calculated):
+ fields = []
+ values = []
+ templs = []
+ id_calculated = [p for p in calculated if p.name == 'id']
+ for prop in obj.properties(hidden=False):
+ if prop not in calculated:
+ value = prop.get_value_for_datastore(obj)
+ if value != prop.default_value() or prop.required:
+ value = self.encode_value(prop, value)
+ values.append(value)
+ fields.append('"%s"' % prop.name)
+ templs.append('%s')
+ qs = 'INSERT INTO "%s" (' % self.db_table
+ if len(id_calculated) == 0:
+ qs += '"id",'
+ qs += ','.join(fields)
+ qs += ") VALUES ("
+ if len(id_calculated) == 0:
+ qs += "'%s'," % obj.id
+ qs += ','.join(templs)
+ qs += ')'
+ if calculated:
+ qs += ' RETURNING '
+ calc_values = ['"%s"' % p.name for p in calculated]
+ qs += ','.join(calc_values)
+ qs += ';'
+ return qs, values
+
+ def _build_update_qs(self, obj, calculated):
+ fields = []
+ values = []
+ for prop in obj.properties(hidden=False):
+ if prop not in calculated:
+ value = prop.get_value_for_datastore(obj)
+ if value != prop.default_value() or prop.required:
+ value = self.encode_value(prop, value)
+ values.append(value)
+ field = '"%s"=' % prop.name
+ field += '%s'
+ fields.append(field)
+ qs = 'UPDATE "%s" SET ' % self.db_table
+ qs += ','.join(fields)
+ qs += """ WHERE "id" = '%s'""" % obj.id
+ if calculated:
+ qs += ' RETURNING '
+ calc_values = ['"%s"' % p.name for p in calculated]
+ qs += ','.join(calc_values)
+ qs += ';'
+ return qs, values
+
+ def _get_sql(self, mapping=None):
+ print '_get_sql'
+ sql = None
+ if self.sql_dir:
+ path = os.path.join(self.sql_dir, self.cls.__name__ + '.sql')
+ print path
+ if os.path.isfile(path):
+ fp = open(path)
+ sql = fp.read()
+ fp.close()
+ t = string.Template(sql)
+ sql = t.safe_substitute(mapping)
+ return sql
+
+ def start_transaction(self):
+ print 'start_transaction'
+ self.in_transaction = True
+
+ def end_transaction(self):
+ print 'end_transaction'
+ self.in_transaction = False
+ self.commit()
+
+ def commit(self):
+ if not self.in_transaction:
+ print '!!commit on %s' % self.db_table
+ try:
+ self.connection.commit()
+
+ except psycopg2.ProgrammingError, err:
+ self.connection.rollback()
+ raise err
+
+ def rollback(self):
+ print '!!rollback on %s' % self.db_table
+ self.connection.rollback()
+
+ def delete_table(self):
+ self.cursor.execute('DROP TABLE "%s";' % self.db_table)
+ self.commit()
+
+ def create_table(self, mapping=None):
+ self.cursor.execute(self._get_sql(mapping))
+ self.commit()
+
+ def encode_value(self, prop, value):
+ return self.converter.encode_prop(prop, value)
+
+ def decode_value(self, prop, value):
+ return self.converter.decode_prop(prop, value)
+
+ def execute_sql(self, query):
+ self.cursor.execute(query, None)
+ self.commit()
+
+ def query_sql(self, query, vars=None):
+ self.cursor.execute(query, vars)
+ return self.cursor.fetchall()
+
+ def lookup(self, cls, name, value):
+ values = []
+ qs = 'SELECT * FROM "%s" WHERE ' % self.db_table
+ found = False
+ for property in cls.properties(hidden=False):
+ if property.name == name:
+ found = True
+ value = self.encode_value(property, value)
+ values.append(value)
+ qs += "%s=" % name
+ qs += "%s"
+ if not found:
+ raise SDBPersistenceError('%s is not a valid field' % name)
+ qs += ';'
+ print qs
+ self.cursor.execute(qs, values)
+ if self.cursor.rowcount == 1:
+ row = self.cursor.fetchone()
+ return self._object_from_row(row, self.cursor.description)
+ elif self.cursor.rowcount == 0:
+ raise KeyError, 'Object not found'
+ else:
+ raise LookupError, 'Multiple Objects Found'
+
+ def query(self, cls, filters, limit=None, order_by=None):
+ parts = []
+ qs = 'SELECT * FROM "%s"' % self.db_table
+ if filters:
+ qs += ' WHERE '
+ properties = cls.properties(hidden=False)
+ for filter, value in filters:
+ name, op = filter.strip().split()
+ found = False
+ for property in properties:
+ if property.name == name:
+ found = True
+ value = self.encode_value(property, value)
+ parts.append(""""%s"%s'%s'""" % (name, op, value))
+ if not found:
+ raise SDBPersistenceError('%s is not a valid field' % name)
+ qs += ','.join(parts)
+ qs += ';'
+ print qs
+ cursor = self.connection.cursor()
+ cursor.execute(qs)
+ return self._object_lister(cursor)
+
+ def get_property(self, prop, obj, name):
+ qs = """SELECT "%s" FROM "%s" WHERE id='%s';""" % (name, self.db_table, obj.id)
+ print qs
+ self.cursor.execute(qs, None)
+ if self.cursor.rowcount == 1:
+ rs = self.cursor.fetchone()
+ for prop in obj.properties(hidden=False):
+ if prop.name == name:
+ v = self.decode_value(prop, rs[0])
+ return v
+ raise AttributeError, '%s not found' % name
+
+ def set_property(self, prop, obj, name, value):
+ pass
+ value = self.encode_value(prop, value)
+ qs = 'UPDATE "%s" SET ' % self.db_table
+ qs += "%s='%s'" % (name, self.encode_value(prop, value))
+ qs += " WHERE id='%s'" % obj.id
+ qs += ';'
+ print qs
+ self.cursor.execute(qs)
+ self.commit()
+
+ def get_object(self, cls, id):
+ qs = """SELECT * FROM "%s" WHERE id='%s';""" % (self.db_table, id)
+ self.cursor.execute(qs, None)
+ if self.cursor.rowcount == 1:
+ row = self.cursor.fetchone()
+ return self._object_from_row(row, self.cursor.description)
+ else:
+ raise SDBPersistenceError('%s object with id=%s does not exist' % (cls.__name__, id))
+
+ def get_object_from_id(self, id):
+ return self.get_object(self.cls, id)
+
+ def _find_calculated_props(self, obj):
+ return [p for p in obj.properties() if hasattr(p, 'calculated_type')]
+
+ def save_object(self, obj):
+ obj._auto_update = False
+ calculated = self._find_calculated_props(obj)
+ if not obj.id:
+ obj.id = str(uuid.uuid4())
+ qs, values = self._build_insert_qs(obj, calculated)
+ else:
+ qs, values = self._build_update_qs(obj, calculated)
+ print qs
+ self.cursor.execute(qs, values)
+ if calculated:
+ calc_values = self.cursor.fetchone()
+ print calculated
+ print calc_values
+ for i in range(0, len(calculated)):
+ prop = calculated[i]
+ prop._set_direct(obj, calc_values[i])
+ self.commit()
+
+ def delete_object(self, obj):
+ qs = """DELETE FROM "%s" WHERE id='%s';""" % (self.db_table, obj.id)
+ print qs
+ self.cursor.execute(qs)
+ self.commit()
+
+
diff --git a/vendor/boto/boto/sdb/db/manager/sdbmanager.py b/vendor/boto/boto/sdb/db/manager/sdbmanager.py
new file mode 100644
index 0000000000..fec24273fa
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/manager/sdbmanager.py
@@ -0,0 +1,599 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010 Chris Moyer http://coredumped.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+import boto
+import re
+from boto.utils import find_class
+import uuid
+from boto.sdb.db.key import Key
+from boto.sdb.db.model import Model
+from boto.sdb.db.blob import Blob
+from boto.sdb.db.property import ListProperty, MapProperty
+from datetime import datetime
+from boto.exception import SDBPersistenceError
+
+ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
+
+
+class SDBConverter:
+ """
+ Responsible for converting base Python types to format compatible with underlying
+ database. For SimpleDB, that means everything needs to be converted to a string
+ when stored in SimpleDB and from a string when retrieved.
+
+ To convert a value, pass it to the encode or decode method. The encode method
+ will take a Python native value and convert to DB format. The decode method will
+ take a DB format value and convert it to Python native format. To find the appropriate
+ method to call, the generic encode/decode methods will look for the type-specific
+ method by searching for a method called "encode_<type name>" or "decode_<type name>".
+ """
+ def __init__(self, manager):
+ self.manager = manager
+ self.type_map = { bool : (self.encode_bool, self.decode_bool),
+ int : (self.encode_int, self.decode_int),
+ long : (self.encode_long, self.decode_long),
+ float : (self.encode_float, self.decode_float),
+ Model : (self.encode_reference, self.decode_reference),
+ Key : (self.encode_reference, self.decode_reference),
+ datetime : (self.encode_datetime, self.decode_datetime),
+ Blob: (self.encode_blob, self.decode_blob),
+ }
+
+ def encode(self, item_type, value):
+ try:
+ if Model in item_type.mro():
+ item_type = Model
+ except:
+ pass
+ if item_type in self.type_map:
+ encode = self.type_map[item_type][0]
+ return encode(value)
+ return value
+
+ def decode(self, item_type, value):
+ if item_type in self.type_map:
+ decode = self.type_map[item_type][1]
+ return decode(value)
+ return value
+
+ def encode_list(self, prop, value):
+ if value == None:
+ return None
+ if not isinstance(value, list):
+ # This is a little trick to avoid encoding when it's just a single value,
+ # since that most likely means it's from a query
+ item_type = getattr(prop, "item_type")
+ return self.encode(item_type, value)
+ # Just enumerate(value) won't work here because
+ # we need to add in some zero padding
+ # We support lists up to 1,000 attributes, since
+ # SDB technically only supports 1024 attributes anyway
+ values = {}
+ for k,v in enumerate(value):
+ values["%03d" % k] = v
+ return self.encode_map(prop, values)
+
+ def encode_map(self, prop, value):
+ if value == None:
+ return None
+ if not isinstance(value, dict):
+ raise ValueError, 'Expected a dict value, got %s' % type(value)
+ new_value = []
+ for key in value:
+ item_type = getattr(prop, "item_type")
+ if Model in item_type.mro():
+ item_type = Model
+ encoded_value = self.encode(item_type, value[key])
+ if encoded_value != None and encoded_value != "None":
+ new_value.append('%s:%s' % (key, encoded_value))
+ return new_value
+
+ def encode_prop(self, prop, value):
+ if isinstance(prop, ListProperty):
+ return self.encode_list(prop, value)
+ elif isinstance(prop, MapProperty):
+ return self.encode_map(prop, value)
+ else:
+ return self.encode(prop.data_type, value)
+
+ def decode_list(self, prop, value):
+ if not isinstance(value, list):
+ value = [value]
+ if hasattr(prop, 'item_type'):
+ item_type = getattr(prop, "item_type")
+ dec_val = {}
+ for val in value:
+ k,v = self.decode_map_element(item_type, val)
+ try:
+ k = int(k)
+ except:
+ k = v
+ dec_val[k] = v
+ value = dec_val.values()
+ return value
+
+ def decode_map(self, prop, value):
+ if not isinstance(value, list):
+ value = [value]
+ ret_value = {}
+ item_type = getattr(prop, "item_type")
+ for val in value:
+ k,v = self.decode_map_element(item_type, val)
+ ret_value[k] = v
+ return ret_value
+
+ def decode_map_element(self, item_type, value):
+ """Decode a single element for a map"""
+ key = value
+ if ":" in value:
+ key, value = value.split(':',1)
+ if Model in item_type.mro():
+ value = item_type(id=value)
+ else:
+ value = self.decode(item_type, value)
+ return (key, value)
+
+ def decode_prop(self, prop, value):
+ if isinstance(prop, ListProperty):
+ return self.decode_list(prop, value)
+ elif isinstance(prop, MapProperty):
+ return self.decode_map(prop, value)
+ else:
+ return self.decode(prop.data_type, value)
+
+ def encode_int(self, value):
+ value = int(value)
+ value += 2147483648
+ return '%010d' % value
+
+ def decode_int(self, value):
+ try:
+ value = int(value)
+ except:
+ boto.log.error("Error, %s is not an integer" % value)
+ value = 0
+ value = int(value)
+ value -= 2147483648
+ return int(value)
+
+ def encode_long(self, value):
+ value = long(value)
+ value += 9223372036854775808
+ return '%020d' % value
+
+ def decode_long(self, value):
+ value = long(value)
+ value -= 9223372036854775808
+ return value
+
+ def encode_bool(self, value):
+ if value == True:
+ return 'true'
+ else:
+ return 'false'
+
+ def decode_bool(self, value):
+ if value.lower() == 'true':
+ return True
+ else:
+ return False
+
+ def encode_float(self, value):
+ """
+ See http://tools.ietf.org/html/draft-wood-ldapext-float-00.
+ """
+ s = '%e' % value
+ l = s.split('e')
+ mantissa = l[0].ljust(18, '0')
+ exponent = l[1]
+ if value == 0.0:
+ case = '3'
+ exponent = '000'
+ elif mantissa[0] != '-' and exponent[0] == '+':
+ case = '5'
+ exponent = exponent[1:].rjust(3, '0')
+ elif mantissa[0] != '-' and exponent[0] == '-':
+ case = '4'
+ exponent = 999 + int(exponent)
+ exponent = '%03d' % exponent
+ elif mantissa[0] == '-' and exponent[0] == '-':
+ case = '2'
+ mantissa = '%f' % (10 + float(mantissa))
+ mantissa = mantissa.ljust(18, '0')
+ exponent = exponent[1:].rjust(3, '0')
+ else:
+ case = '1'
+ mantissa = '%f' % (10 + float(mantissa))
+ mantissa = mantissa.ljust(18, '0')
+ exponent = 999 - int(exponent)
+ exponent = '%03d' % exponent
+ return '%s %s %s' % (case, exponent, mantissa)
+
+ def decode_float(self, value):
+ case = value[0]
+ exponent = value[2:5]
+ mantissa = value[6:]
+ if case == '3':
+ return 0.0
+ elif case == '5':
+ pass
+ elif case == '4':
+ exponent = '%03d' % (int(exponent) - 999)
+ elif case == '2':
+ mantissa = '%f' % (float(mantissa) - 10)
+ exponent = '-' + exponent
+ else:
+ mantissa = '%f' % (float(mantissa) - 10)
+ exponent = '%03d' % abs((int(exponent) - 999))
+ return float(mantissa + 'e' + exponent)
+
+ def encode_datetime(self, value):
+ if isinstance(value, str) or isinstance(value, unicode):
+ return value
+ return value.strftime(ISO8601)
+
+ def decode_datetime(self, value):
+ try:
+ return datetime.strptime(value, ISO8601)
+ except:
+ return None
+
+ def encode_reference(self, value):
+ if isinstance(value, str) or isinstance(value, unicode):
+ return value
+ if value == None:
+ return ''
+ else:
+ return value.id
+
+ def decode_reference(self, value):
+ if not value or value == "None":
+ return None
+ return value
+
+ def encode_blob(self, value):
+ if not value:
+ return None
+
+ if not value.id:
+ bucket = self.manager.get_blob_bucket()
+ key = bucket.new_key(str(uuid.uuid4()))
+ value.id = "s3://%s/%s" % (key.bucket.name, key.name)
+ else:
+ match = re.match("^s3:\/\/([^\/]*)\/(.*)$", value.id)
+ if match:
+ s3 = self.manager.get_s3_connection()
+ bucket = s3.get_bucket(match.group(1), validate=False)
+ key = bucket.get_key(match.group(2))
+ else:
+ raise SDBPersistenceError("Invalid Blob ID: %s" % value.id)
+
+ if value.value != None:
+ key.set_contents_from_string(value.value)
+ return value.id
+
+
+ def decode_blob(self, value):
+ if not value:
+ return None
+ match = re.match("^s3:\/\/([^\/]*)\/(.*)$", value)
+ if match:
+ s3 = self.manager.get_s3_connection()
+ bucket = s3.get_bucket(match.group(1), validate=False)
+ key = bucket.get_key(match.group(2))
+ else:
+ return None
+ if key:
+ return Blob(file=key, id="s3://%s/%s" % (key.bucket.name, key.name))
+ else:
+ return None
+
+class SDBManager(object):
+
+ def __init__(self, cls, db_name, db_user, db_passwd,
+ db_host, db_port, db_table, ddl_dir, enable_ssl, consistent=None):
+ self.cls = cls
+ self.db_name = db_name
+ self.db_user = db_user
+ self.db_passwd = db_passwd
+ self.db_host = db_host
+ self.db_port = db_port
+ self.db_table = db_table
+ self.ddl_dir = ddl_dir
+ self.enable_ssl = enable_ssl
+ self.s3 = None
+ self.bucket = None
+ self.converter = SDBConverter(self)
+ self._sdb = None
+ self._domain = None
+ if consistent == None and hasattr(cls, "__consistent"):
+ consistent = cls.__consistent__
+ self.consistent = consistent
+
+ @property
+ def sdb(self):
+ if self._sdb is None:
+ self._connect()
+ return self._sdb
+
+ @property
+ def domain(self):
+ if self._domain is None:
+ self._connect()
+ return self._domain
+
+ def _connect(self):
+ self._sdb = boto.connect_sdb(aws_access_key_id=self.db_user,
+ aws_secret_access_key=self.db_passwd,
+ is_secure=self.enable_ssl)
+ # This assumes that the domain has already been created
+ # It's much more efficient to do it this way rather than
+ # having this make a roundtrip each time to validate.
+ # The downside is that if the domain doesn't exist, it breaks
+ self._domain = self._sdb.lookup(self.db_name, validate=False)
+ if not self._domain:
+ self._domain = self._sdb.create_domain(self.db_name)
+
+ def _object_lister(self, cls, query_lister):
+ for item in query_lister:
+ obj = self.get_object(cls, item.name, item)
+ if obj:
+ yield obj
+
+ def encode_value(self, prop, value):
+ if value == None:
+ return None
+ if not prop:
+ return str(value)
+ return self.converter.encode_prop(prop, value)
+
+ def decode_value(self, prop, value):
+ return self.converter.decode_prop(prop, value)
+
+ def get_s3_connection(self):
+ if not self.s3:
+ self.s3 = boto.connect_s3(self.db_user, self.db_passwd)
+ return self.s3
+
+ def get_blob_bucket(self, bucket_name=None):
+ s3 = self.get_s3_connection()
+ bucket_name = "%s-%s" % (s3.aws_access_key_id, self.domain.name)
+ bucket_name = bucket_name.lower()
+ try:
+ self.bucket = s3.get_bucket(bucket_name)
+ except:
+ self.bucket = s3.create_bucket(bucket_name)
+ return self.bucket
+
+ def load_object(self, obj):
+ if not obj._loaded:
+ a = self.domain.get_attributes(obj.id,consistent_read=self.consistent)
+ if a.has_key('__type__'):
+ for prop in obj.properties(hidden=False):
+ if a.has_key(prop.name):
+ value = self.decode_value(prop, a[prop.name])
+ value = prop.make_value_from_datastore(value)
+ try:
+ setattr(obj, prop.name, value)
+ except Exception, e:
+ boto.log.exception(e)
+ obj._loaded = True
+
+ def get_object(self, cls, id, a=None):
+ obj = None
+ if not a:
+ a = self.domain.get_attributes(id,consistent_read=self.consistent)
+ if a.has_key('__type__'):
+ if not cls or a['__type__'] != cls.__name__:
+ cls = find_class(a['__module__'], a['__type__'])
+ if cls:
+ params = {}
+ for prop in cls.properties(hidden=False):
+ if a.has_key(prop.name):
+ value = self.decode_value(prop, a[prop.name])
+ value = prop.make_value_from_datastore(value)
+ params[prop.name] = value
+ obj = cls(id, **params)
+ obj._loaded = True
+ else:
+ s = '(%s) class %s.%s not found' % (id, a['__module__'], a['__type__'])
+ boto.log.info('sdbmanager: %s' % s)
+ return obj
+
+ def get_object_from_id(self, id):
+ return self.get_object(None, id)
+
+ def query(self, query):
+ query_str = "select * from `%s` %s" % (self.domain.name, self._build_filter_part(query.model_class, query.filters, query.sort_by))
+ if query.limit:
+ query_str += " limit %s" % query.limit
+ rs = self.domain.select(query_str, max_items=query.limit, next_token = query.next_token)
+ query.rs = rs
+ return self._object_lister(query.model_class, rs)
+
+ def count(self, cls, filters):
+ """
+ Get the number of results that would
+ be returned in this query
+ """
+ query = "select count(*) from `%s` %s" % (self.domain.name, self._build_filter_part(cls, filters))
+ count = int(self.domain.select(query).next()["Count"])
+ return count
+
+
+ def _build_filter(self, property, name, op, val):
+ if val == None:
+ if op in ('is','='):
+ return "`%s` is null" % name
+ elif op in ('is not', '!='):
+ return "`%s` is not null" % name
+ else:
+ val = ""
+ if property.__class__ == ListProperty:
+ if op in ("is", "="):
+ op = "like"
+ elif op in ("!=", "not"):
+ op = "not like"
+ if not(op == "like" and val.startswith("%")):
+ val = "%%:%s" % val
+ return "`%s` %s '%s'" % (name, op, val.replace("'", "''"))
+
+ def _build_filter_part(self, cls, filters, order_by=None):
+ """
+ Build the filter part
+ """
+ import types
+ query_parts = []
+ order_by_filtered = False
+ if order_by:
+ if order_by[0] == "-":
+ order_by_method = "desc";
+ order_by = order_by[1:]
+ else:
+ order_by_method = "asc";
+
+ for filter in filters:
+ filter_parts = []
+ filter_props = filter[0]
+ if type(filter_props) != list:
+ filter_props = [filter_props]
+ for filter_prop in filter_props:
+ (name, op) = filter_prop.strip().split(" ", 1)
+ value = filter[1]
+ property = cls.find_property(name)
+ if name == order_by:
+ order_by_filtered = True
+ if types.TypeType(value) == types.ListType:
+ filter_parts_sub = []
+ for val in value:
+ val = self.encode_value(property, val)
+ if isinstance(val, list):
+ for v in val:
+ filter_parts_sub.append(self._build_filter(property, name, op, v))
+ else:
+ filter_parts_sub.append(self._build_filter(property, name, op, val))
+ filter_parts.append("(%s)" % (" or ".join(filter_parts_sub)))
+ else:
+ val = self.encode_value(property, value)
+ if isinstance(val, list):
+ for v in val:
+ filter_parts.append(self._build_filter(property, name, op, v))
+ else:
+ filter_parts.append(self._build_filter(property, name, op, val))
+ query_parts.append("(%s)" % (" or ".join(filter_parts)))
+
+
+ type_query = "(`__type__` = '%s'" % cls.__name__
+ for subclass in self._get_all_decendents(cls).keys():
+ type_query += " or `__type__` = '%s'" % subclass
+ type_query +=")"
+ query_parts.append(type_query)
+
+ order_by_query = ""
+ if order_by:
+ if not order_by_filtered:
+ query_parts.append("`%s` like '%%'" % order_by)
+ order_by_query = " order by `%s` %s" % (order_by, order_by_method)
+
+ if len(query_parts) > 0:
+ return "where %s %s" % (" and ".join(query_parts), order_by_query)
+ else:
+ return ""
+
+
+ def _get_all_decendents(self, cls):
+ """Get all decendents for a given class"""
+ decendents = {}
+ for sc in cls.__sub_classes__:
+ decendents[sc.__name__] = sc
+ decendents.update(self._get_all_decendents(sc))
+ return decendents
+
+ def query_gql(self, query_string, *args, **kwds):
+ raise NotImplementedError, "GQL queries not supported in SimpleDB"
+
+ def save_object(self, obj):
+ if not obj.id:
+ obj.id = str(uuid.uuid4())
+
+ attrs = {'__type__' : obj.__class__.__name__,
+ '__module__' : obj.__class__.__module__,
+ '__lineage__' : obj.get_lineage()}
+ for property in obj.properties(hidden=False):
+ value = property.get_value_for_datastore(obj)
+ if value is not None:
+ value = self.encode_value(property, value)
+ if value == []:
+ value = None
+ attrs[property.name] = value
+ if property.unique:
+ try:
+ args = {property.name: value}
+ obj2 = obj.find(**args).next()
+ if obj2.id != obj.id:
+ raise SDBPersistenceError("Error: %s must be unique!" % property.name)
+ except(StopIteration):
+ pass
+ self.domain.put_attributes(obj.id, attrs, replace=True)
+
+ def delete_object(self, obj):
+ self.domain.delete_attributes(obj.id)
+
+ def set_property(self, prop, obj, name, value):
+ value = prop.get_value_for_datastore(obj)
+ value = self.encode_value(prop, value)
+ if prop.unique:
+ try:
+ args = {prop.name: value}
+ obj2 = obj.find(**args).next()
+ if obj2.id != obj.id:
+ raise SDBPersistenceError("Error: %s must be unique!" % prop.name)
+ except(StopIteration):
+ pass
+ self.domain.put_attributes(obj.id, {name : value}, replace=True)
+
+ def get_property(self, prop, obj, name):
+ a = self.domain.get_attributes(obj.id,consistent_read=self.consistent)
+
+ # try to get the attribute value from SDB
+ if name in a:
+ value = self.decode_value(prop, a[name])
+ value = prop.make_value_from_datastore(value)
+ setattr(obj, prop.name, value)
+ return value
+ raise AttributeError, '%s not found' % name
+
+ def set_key_value(self, obj, name, value):
+ self.domain.put_attributes(obj.id, {name : value}, replace=True)
+
+ def delete_key_value(self, obj, name):
+ self.domain.delete_attributes(obj.id, name)
+
+ def get_key_value(self, obj, name):
+ a = self.domain.get_attributes(obj.id, name,consistent_read=self.consistent)
+ if a.has_key(name):
+ return a[name]
+ else:
+ return None
+
+ def get_raw_item(self, obj):
+ return self.domain.get_item(obj.id)
+
diff --git a/vendor/boto/boto/sdb/db/manager/xmlmanager.py b/vendor/boto/boto/sdb/db/manager/xmlmanager.py
new file mode 100644
index 0000000000..a3f8074e7a
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/manager/xmlmanager.py
@@ -0,0 +1,517 @@
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+import boto
+from boto.utils import find_class, Password
+from boto.sdb.db.key import Key
+from boto.sdb.db.model import Model
+from datetime import datetime
+from xml.dom.minidom import getDOMImplementation, parse, parseString, Node
+
+ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
+
+class XMLConverter:
+ """
+ Responsible for converting base Python types to format compatible with underlying
+ database. For SimpleDB, that means everything needs to be converted to a string
+ when stored in SimpleDB and from a string when retrieved.
+
+ To convert a value, pass it to the encode or decode method. The encode method
+ will take a Python native value and convert to DB format. The decode method will
+ take a DB format value and convert it to Python native format. To find the appropriate
+ method to call, the generic encode/decode methods will look for the type-specific
+ method by searching for a method called "encode_<type name>" or "decode_<type name>".
+ """
+ def __init__(self, manager):
+ self.manager = manager
+ self.type_map = { bool : (self.encode_bool, self.decode_bool),
+ int : (self.encode_int, self.decode_int),
+ long : (self.encode_long, self.decode_long),
+ Model : (self.encode_reference, self.decode_reference),
+ Key : (self.encode_reference, self.decode_reference),
+ Password : (self.encode_password, self.decode_password),
+ datetime : (self.encode_datetime, self.decode_datetime)}
+
+ def get_text_value(self, parent_node):
+ value = ''
+ for node in parent_node.childNodes:
+ if node.nodeType == node.TEXT_NODE:
+ value += node.data
+ return value
+
+ def encode(self, item_type, value):
+ if item_type in self.type_map:
+ encode = self.type_map[item_type][0]
+ return encode(value)
+ return value
+
+ def decode(self, item_type, value):
+ if item_type in self.type_map:
+ decode = self.type_map[item_type][1]
+ return decode(value)
+ else:
+ value = self.get_text_value(value)
+ return value
+
+ def encode_prop(self, prop, value):
+ if isinstance(value, list):
+ if hasattr(prop, 'item_type'):
+ new_value = []
+ for v in value:
+ item_type = getattr(prop, "item_type")
+ if Model in item_type.mro():
+ item_type = Model
+ new_value.append(self.encode(item_type, v))
+ return new_value
+ else:
+ return value
+ else:
+ return self.encode(prop.data_type, value)
+
+ def decode_prop(self, prop, value):
+ if prop.data_type == list:
+ if hasattr(prop, 'item_type'):
+ item_type = getattr(prop, "item_type")
+ if Model in item_type.mro():
+ item_type = Model
+ values = []
+ for item_node in value.getElementsByTagName('item'):
+ value = self.decode(item_type, item_node)
+ values.append(value)
+ return values
+ else:
+ return self.get_text_value(value)
+ else:
+ return self.decode(prop.data_type, value)
+
+ def encode_int(self, value):
+ value = int(value)
+ return '%d' % value
+
+ def decode_int(self, value):
+ value = self.get_text_value(value)
+ if value:
+ value = int(value)
+ else:
+ value = None
+ return value
+
+ def encode_long(self, value):
+ value = long(value)
+ return '%d' % value
+
+ def decode_long(self, value):
+ value = self.get_text_value(value)
+ return long(value)
+
+ def encode_bool(self, value):
+ if value == True:
+ return 'true'
+ else:
+ return 'false'
+
+ def decode_bool(self, value):
+ value = self.get_text_value(value)
+ if value.lower() == 'true':
+ return True
+ else:
+ return False
+
+ def encode_datetime(self, value):
+ return value.strftime(ISO8601)
+
+ def decode_datetime(self, value):
+ value = self.get_text_value(value)
+ try:
+ return datetime.strptime(value, ISO8601)
+ except:
+ return None
+
+ def encode_reference(self, value):
+ if isinstance(value, str) or isinstance(value, unicode):
+ return value
+ if value == None:
+ return ''
+ else:
+ val_node = self.manager.doc.createElement("object")
+ val_node.setAttribute('id', value.id)
+ val_node.setAttribute('class', '%s.%s' % (value.__class__.__module__, value.__class__.__name__))
+ return val_node
+
+ def decode_reference(self, value):
+ if not value:
+ return None
+ try:
+ value = value.childNodes[0]
+ class_name = value.getAttribute("class")
+ id = value.getAttribute("id")
+ cls = find_class(class_name)
+ return cls.get_by_ids(id)
+ except:
+ return None
+
+ def encode_password(self, value):
+ if value and len(value) > 0:
+ return str(value)
+ else:
+ return None
+
+ def decode_password(self, value):
+ value = self.get_text_value(value)
+ return Password(value)
+
+
+class XMLManager(object):
+
+ def __init__(self, cls, db_name, db_user, db_passwd,
+ db_host, db_port, db_table, ddl_dir, enable_ssl):
+ self.cls = cls
+ if not db_name:
+ db_name = cls.__name__.lower()
+ self.db_name = db_name
+ self.db_user = db_user
+ self.db_passwd = db_passwd
+ self.db_host = db_host
+ self.db_port = db_port
+ self.db_table = db_table
+ self.ddl_dir = ddl_dir
+ self.s3 = None
+ self.converter = XMLConverter(self)
+ self.impl = getDOMImplementation()
+ self.doc = self.impl.createDocument(None, 'objects', None)
+
+ self.connection = None
+ self.enable_ssl = enable_ssl
+ self.auth_header = None
+ if self.db_user:
+ import base64
+ base64string = base64.encodestring('%s:%s' % (self.db_user, self.db_passwd))[:-1]
+ authheader = "Basic %s" % base64string
+ self.auth_header = authheader
+
+ def _connect(self):
+ if self.db_host:
+ if self.enable_ssl:
+ from httplib import HTTPSConnection as Connection
+ else:
+ from httplib import HTTPConnection as Connection
+
+ self.connection = Connection(self.db_host, self.db_port)
+
+ def _make_request(self, method, url, post_data=None, body=None):
+ """
+ Make a request on this connection
+ """
+ if not self.connection:
+ self._connect()
+ try:
+ self.connection.close()
+ except:
+ pass
+ self.connection.connect()
+ headers = {}
+ if self.auth_header:
+ headers["Authorization"] = self.auth_header
+ self.connection.request(method, url, body, headers)
+ resp = self.connection.getresponse()
+ return resp
+
+ def new_doc(self):
+ return self.impl.createDocument(None, 'objects', None)
+
+ def _object_lister(self, cls, doc):
+ for obj_node in doc.getElementsByTagName('object'):
+ if not cls:
+ class_name = obj_node.getAttribute('class')
+ cls = find_class(class_name)
+ id = obj_node.getAttribute('id')
+ obj = cls(id)
+ for prop_node in obj_node.getElementsByTagName('property'):
+ prop_name = prop_node.getAttribute('name')
+ prop = obj.find_property(prop_name)
+ if prop:
+ if hasattr(prop, 'item_type'):
+ value = self.get_list(prop_node, prop.item_type)
+ else:
+ value = self.decode_value(prop, prop_node)
+ value = prop.make_value_from_datastore(value)
+ setattr(obj, prop.name, value)
+ yield obj
+
+ def reset(self):
+ self._connect()
+
+ def get_doc(self):
+ return self.doc
+
+ def encode_value(self, prop, value):
+ return self.converter.encode_prop(prop, value)
+
+ def decode_value(self, prop, value):
+ return self.converter.decode_prop(prop, value)
+
+ def get_s3_connection(self):
+ if not self.s3:
+ self.s3 = boto.connect_s3(self.aws_access_key_id, self.aws_secret_access_key)
+ return self.s3
+
+ def get_list(self, prop_node, item_type):
+ values = []
+ try:
+ items_node = prop_node.getElementsByTagName('items')[0]
+ except:
+ return []
+ for item_node in items_node.getElementsByTagName('item'):
+ value = self.converter.decode(item_type, item_node)
+ values.append(value)
+ return values
+
+ def get_object_from_doc(self, cls, id, doc):
+ obj_node = doc.getElementsByTagName('object')[0]
+ if not cls:
+ class_name = obj_node.getAttribute('class')
+ cls = find_class(class_name)
+ if not id:
+ id = obj_node.getAttribute('id')
+ obj = cls(id)
+ for prop_node in obj_node.getElementsByTagName('property'):
+ prop_name = prop_node.getAttribute('name')
+ prop = obj.find_property(prop_name)
+ value = self.decode_value(prop, prop_node)
+ value = prop.make_value_from_datastore(value)
+ if value != None:
+ try:
+ setattr(obj, prop.name, value)
+ except:
+ pass
+ return obj
+
+ def get_props_from_doc(self, cls, id, doc):
+ """
+ Pull out the properties from this document
+ Returns the class, the properties in a hash, and the id if provided as a tuple
+ :return: (cls, props, id)
+ """
+ obj_node = doc.getElementsByTagName('object')[0]
+ if not cls:
+ class_name = obj_node.getAttribute('class')
+ cls = find_class(class_name)
+ if not id:
+ id = obj_node.getAttribute('id')
+ props = {}
+ for prop_node in obj_node.getElementsByTagName('property'):
+ prop_name = prop_node.getAttribute('name')
+ prop = cls.find_property(prop_name)
+ value = self.decode_value(prop, prop_node)
+ value = prop.make_value_from_datastore(value)
+ if value != None:
+ props[prop.name] = value
+ return (cls, props, id)
+
+
+ def get_object(self, cls, id):
+ if not self.connection:
+ self._connect()
+
+ if not self.connection:
+ raise NotImplementedError("Can't query without a database connection")
+ url = "/%s/%s" % (self.db_name, id)
+ resp = self._make_request('GET', url)
+ if resp.status == 200:
+ doc = parse(resp)
+ else:
+ raise Exception("Error: %s" % resp.status)
+ return self.get_object_from_doc(cls, id, doc)
+
+ def query(self, cls, filters, limit=None, order_by=None):
+ if not self.connection:
+ self._connect()
+
+ if not self.connection:
+ raise NotImplementedError("Can't query without a database connection")
+
+ from urllib import urlencode
+
+ query = str(self._build_query(cls, filters, limit, order_by))
+ if query:
+ url = "/%s?%s" % (self.db_name, urlencode({"query": query}))
+ else:
+ url = "/%s" % self.db_name
+ resp = self._make_request('GET', url)
+ if resp.status == 200:
+ doc = parse(resp)
+ else:
+ raise Exception("Error: %s" % resp.status)
+ return self._object_lister(cls, doc)
+
+ def _build_query(self, cls, filters, limit, order_by):
+ import types
+ if len(filters) > 4:
+ raise Exception('Too many filters, max is 4')
+ parts = []
+ properties = cls.properties(hidden=False)
+ for filter, value in filters:
+ name, op = filter.strip().split()
+ found = False
+ for property in properties:
+ if property.name == name:
+ found = True
+ if types.TypeType(value) == types.ListType:
+ filter_parts = []
+ for val in value:
+ val = self.encode_value(property, val)
+ filter_parts.append("'%s' %s '%s'" % (name, op, val))
+ parts.append("[%s]" % " OR ".join(filter_parts))
+ else:
+ value = self.encode_value(property, value)
+ parts.append("['%s' %s '%s']" % (name, op, value))
+ if not found:
+ raise Exception('%s is not a valid field' % name)
+ if order_by:
+ if order_by.startswith("-"):
+ key = order_by[1:]
+ type = "desc"
+ else:
+ key = order_by
+ type = "asc"
+ parts.append("['%s' starts-with ''] sort '%s' %s" % (key, key, type))
+ return ' intersection '.join(parts)
+
+ def query_gql(self, query_string, *args, **kwds):
+ raise NotImplementedError, "GQL queries not supported in XML"
+
+ def save_list(self, doc, items, prop_node):
+ items_node = doc.createElement('items')
+ prop_node.appendChild(items_node)
+ for item in items:
+ item_node = doc.createElement('item')
+ items_node.appendChild(item_node)
+ if isinstance(item, Node):
+ item_node.appendChild(item)
+ else:
+ text_node = doc.createTextNode(item)
+ item_node.appendChild(text_node)
+
+ def save_object(self, obj):
+ """
+ Marshal the object and do a PUT
+ """
+ doc = self.marshal_object(obj)
+ if obj.id:
+ url = "/%s/%s" % (self.db_name, obj.id)
+ else:
+ url = "/%s" % (self.db_name)
+ resp = self._make_request("PUT", url, body=doc.toxml())
+ new_obj = self.get_object_from_doc(obj.__class__, None, parse(resp))
+ obj.id = new_obj.id
+ for prop in obj.properties():
+ try:
+ propname = prop.name
+ except AttributeError:
+ propname = None
+ if propname:
+ value = getattr(new_obj, prop.name)
+ if value:
+ setattr(obj, prop.name, value)
+ return obj
+
+
+ def marshal_object(self, obj, doc=None):
+ if not doc:
+ doc = self.new_doc()
+ if not doc:
+ doc = self.doc
+ obj_node = doc.createElement('object')
+
+ if obj.id:
+ obj_node.setAttribute('id', obj.id)
+
+ obj_node.setAttribute('class', '%s.%s' % (obj.__class__.__module__,
+ obj.__class__.__name__))
+ root = doc.documentElement
+ root.appendChild(obj_node)
+ for property in obj.properties(hidden=False):
+ prop_node = doc.createElement('property')
+ prop_node.setAttribute('name', property.name)
+ prop_node.setAttribute('type', property.type_name)
+ value = property.get_value_for_datastore(obj)
+ if value is not None:
+ value = self.encode_value(property, value)
+ if isinstance(value, list):
+ self.save_list(doc, value, prop_node)
+ elif isinstance(value, Node):
+ prop_node.appendChild(value)
+ else:
+ text_node = doc.createTextNode(str(value))
+ prop_node.appendChild(text_node)
+ obj_node.appendChild(prop_node)
+
+ return doc
+
+ def unmarshal_object(self, fp, cls=None, id=None):
+ if isinstance(fp, str) or isinstance(fp, unicode):
+ doc = parseString(fp)
+ else:
+ doc = parse(fp)
+ return self.get_object_from_doc(cls, id, doc)
+
+ def unmarshal_props(self, fp, cls=None, id=None):
+ """
+ Same as unmarshalling an object, except it returns
+ from "get_props_from_doc"
+ """
+ if isinstance(fp, str) or isinstance(fp, unicode):
+ doc = parseString(fp)
+ else:
+ doc = parse(fp)
+ return self.get_props_from_doc(cls, id, doc)
+
+ def delete_object(self, obj):
+ url = "/%s/%s" % (self.db_name, obj.id)
+ return self._make_request("DELETE", url)
+
+ def set_key_value(self, obj, name, value):
+ self.domain.put_attributes(obj.id, {name : value}, replace=True)
+
+ def delete_key_value(self, obj, name):
+ self.domain.delete_attributes(obj.id, name)
+
+ def get_key_value(self, obj, name):
+ a = self.domain.get_attributes(obj.id, name)
+ if a.has_key(name):
+ return a[name]
+ else:
+ return None
+
+ def get_raw_item(self, obj):
+ return self.domain.get_item(obj.id)
+
+ def set_property(self, prop, obj, name, value):
+ pass
+
+ def get_property(self, prop, obj, name):
+ pass
+
+ def load_object(self, obj):
+ if not obj._loaded:
+ obj = obj.get_by_id(obj.id)
+ obj._loaded = True
+ return obj
+
diff --git a/vendor/boto/boto/sdb/db/model.py b/vendor/boto/boto/sdb/db/model.py
new file mode 100644
index 0000000000..069294729e
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/model.py
@@ -0,0 +1,234 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.sdb.db.manager import get_manager
+from boto.sdb.db.property import Property
+from boto.sdb.db.key import Key
+from boto.sdb.db.query import Query
+import boto
+
+class ModelMeta(type):
+ "Metaclass for all Models"
+
+ def __init__(cls, name, bases, dict):
+ super(ModelMeta, cls).__init__(name, bases, dict)
+ # Make sure this is a subclass of Model - mainly copied from django ModelBase (thanks!)
+ cls.__sub_classes__ = []
+ try:
+ if filter(lambda b: issubclass(b, Model), bases):
+ for base in bases:
+ base.__sub_classes__.append(cls)
+ cls._manager = get_manager(cls)
+ # look for all of the Properties and set their names
+ for key in dict.keys():
+ if isinstance(dict[key], Property):
+ property = dict[key]
+ property.__property_config__(cls, key)
+ prop_names = []
+ props = cls.properties()
+ for prop in props:
+ if not prop.__class__.__name__.startswith('_'):
+ prop_names.append(prop.name)
+ setattr(cls, '_prop_names', prop_names)
+ except NameError:
+ # 'Model' isn't defined yet, meaning we're looking at our own
+ # Model class, defined below.
+ pass
+
+class Model(object):
+ __metaclass__ = ModelMeta
+ __consistent__ = False # Consistent is set off by default
+ id = None
+
+ @classmethod
+ def get_lineage(cls):
+ l = [c.__name__ for c in cls.mro()]
+ l.reverse()
+ return '.'.join(l)
+
+ @classmethod
+ def kind(cls):
+ return cls.__name__
+
+ @classmethod
+ def _get_by_id(cls, id, manager=None):
+ if not manager:
+ manager = cls._manager
+ return manager.get_object(cls, id)
+
+ @classmethod
+ def get_by_id(cls, ids=None, parent=None):
+ if isinstance(ids, list):
+ objs = [cls._get_by_id(id) for id in ids]
+ return objs
+ else:
+ return cls._get_by_id(ids)
+
+ get_by_ids = get_by_id
+
+ @classmethod
+ def get_by_key_name(cls, key_names, parent=None):
+ raise NotImplementedError, "Key Names are not currently supported"
+
+ @classmethod
+ def find(cls, limit=None, next_token=None, **params):
+ q = Query(cls, limit=limit, next_token=next_token)
+ for key, value in params.items():
+ q.filter('%s =' % key, value)
+ return q
+
+ @classmethod
+ def lookup(cls, name, value):
+ return cls._manager.lookup(cls, name, value)
+
+ @classmethod
+ def all(cls, limit=None, next_token=None):
+ return cls.find(limit=limit, next_token=next_token)
+
+ @classmethod
+ def get_or_insert(key_name, **kw):
+ raise NotImplementedError, "get_or_insert not currently supported"
+
+ @classmethod
+ def properties(cls, hidden=True):
+ properties = []
+ while cls:
+ for key in cls.__dict__.keys():
+ prop = cls.__dict__[key]
+ if isinstance(prop, Property):
+ if hidden or not prop.__class__.__name__.startswith('_'):
+ properties.append(prop)
+ if len(cls.__bases__) > 0:
+ cls = cls.__bases__[0]
+ else:
+ cls = None
+ return properties
+
+ @classmethod
+ def find_property(cls, prop_name):
+ property = None
+ while cls:
+ for key in cls.__dict__.keys():
+ prop = cls.__dict__[key]
+ if isinstance(prop, Property):
+ if not prop.__class__.__name__.startswith('_') and prop_name == prop.name:
+ property = prop
+ if len(cls.__bases__) > 0:
+ cls = cls.__bases__[0]
+ else:
+ cls = None
+ return property
+
+ @classmethod
+ def get_xmlmanager(cls):
+ if not hasattr(cls, '_xmlmanager'):
+ from boto.sdb.db.manager.xmlmanager import XMLManager
+ cls._xmlmanager = XMLManager(cls, None, None, None,
+ None, None, None, None, False)
+ return cls._xmlmanager
+
+ @classmethod
+ def from_xml(cls, fp):
+ xmlmanager = cls.get_xmlmanager()
+ return xmlmanager.unmarshal_object(fp)
+
+ def __init__(self, id=None, **kw):
+ self._loaded = False
+ # first initialize all properties to their default values
+ for prop in self.properties(hidden=False):
+ setattr(self, prop.name, prop.default_value())
+ if kw.has_key('manager'):
+ self._manager = kw['manager']
+ self.id = id
+ for key in kw:
+ if key != 'manager':
+ # We don't want any errors populating up when loading an object,
+ # so if it fails we just revert to it's default value
+ try:
+ setattr(self, key, kw[key])
+ except Exception, e:
+ boto.log.exception(e)
+
+ def __repr__(self):
+ return '%s<%s>' % (self.__class__.__name__, self.id)
+
+ def __str__(self):
+ return str(self.id)
+
+ def __eq__(self, other):
+ return other and isinstance(other, Model) and self.id == other.id
+
+ def _get_raw_item(self):
+ return self._manager.get_raw_item(self)
+
+ def load(self):
+ if self.id and not self._loaded:
+ self._manager.load_object(self)
+
+ def put(self):
+ self._manager.save_object(self)
+
+ save = put
+
+ def delete(self):
+ self._manager.delete_object(self)
+
+ def key(self):
+ return Key(obj=self)
+
+ def set_manager(self, manager):
+ self._manager = manager
+
+ def to_dict(self):
+ props = {}
+ for prop in self.properties(hidden=False):
+ props[prop.name] = getattr(self, prop.name)
+ obj = {'properties' : props,
+ 'id' : self.id}
+ return {self.__class__.__name__ : obj}
+
+ def to_xml(self, doc=None):
+ xmlmanager = self.get_xmlmanager()
+ doc = xmlmanager.marshal_object(self, doc)
+ return doc
+
+class Expando(Model):
+
+ def __setattr__(self, name, value):
+ if name in self._prop_names:
+ object.__setattr__(self, name, value)
+ elif name.startswith('_'):
+ object.__setattr__(self, name, value)
+ elif name == 'id':
+ object.__setattr__(self, name, value)
+ else:
+ self._manager.set_key_value(self, name, value)
+ object.__setattr__(self, name, value)
+
+ def __getattr__(self, name):
+ if not name.startswith('_'):
+ value = self._manager.get_key_value(self, name)
+ if value:
+ object.__setattr__(self, name, value)
+ return value
+ raise AttributeError
+
+
diff --git a/vendor/boto/boto/sdb/db/property.py b/vendor/boto/boto/sdb/db/property.py
new file mode 100644
index 0000000000..c7993ae7cf
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/property.py
@@ -0,0 +1,556 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import datetime
+from key import Key
+from boto.utils import Password
+from boto.sdb.db.query import Query
+import re
+import boto
+import boto.s3.key
+from boto.sdb.db.blob import Blob
+
+class Property(object):
+
+ data_type = str
+ type_name = ''
+ name = ''
+ verbose_name = ''
+
+ def __init__(self, verbose_name=None, name=None, default=None, required=False,
+ validator=None, choices=None, unique=False):
+ self.verbose_name = verbose_name
+ self.name = name
+ self.default = default
+ self.required = required
+ self.validator = validator
+ self.choices = choices
+ self.slot_name = '_'
+ self.unique = unique
+
+ def __get__(self, obj, objtype):
+ if obj:
+ obj.load()
+ return getattr(obj, self.slot_name)
+ else:
+ return None
+
+ def __set__(self, obj, value):
+ self.validate(value)
+
+ # Fire off any on_set functions
+ try:
+ if obj._loaded and hasattr(obj, "on_set_%s" % self.name):
+ fnc = getattr(obj, "on_set_%s" % self.name)
+ value = fnc(value)
+ except Exception:
+ boto.log.exception("Exception running on_set_%s" % self.name)
+
+ setattr(obj, self.slot_name, value)
+
+ def __property_config__(self, model_class, property_name):
+ self.model_class = model_class
+ self.name = property_name
+ self.slot_name = '_' + self.name
+
+ def default_validator(self, value):
+ if value == self.default_value():
+ return
+ if not isinstance(value, self.data_type):
+ raise TypeError, 'Validation Error, expecting %s, got %s' % (self.data_type, type(value))
+
+ def default_value(self):
+ return self.default
+
+ def validate(self, value):
+ if self.required and value==None:
+ raise ValueError, '%s is a required property' % self.name
+ if self.choices and value and not value in self.choices:
+ raise ValueError, '%s not a valid choice for %s.%s' % (value, self.model_class.__name__, self.name)
+ if self.validator:
+ self.validator(value)
+ else:
+ self.default_validator(value)
+ return value
+
+ def empty(self, value):
+ return not value
+
+ def get_value_for_datastore(self, model_instance):
+ return getattr(model_instance, self.name)
+
+ def make_value_from_datastore(self, value):
+ return value
+
+ def get_choices(self):
+ if callable(self.choices):
+ return self.choices()
+ return self.choices
+
+def validate_string(value):
+ if isinstance(value, str) or isinstance(value, unicode):
+ if len(value) > 1024:
+ raise ValueError, 'Length of value greater than maxlength'
+ else:
+ raise TypeError, 'Expecting String, got %s' % type(value)
+
+class StringProperty(Property):
+
+ type_name = 'String'
+
+ def __init__(self, verbose_name=None, name=None, default='', required=False,
+ validator=validate_string, choices=None, unique=False):
+ Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
+
+class TextProperty(Property):
+
+ type_name = 'Text'
+
+ def __init__(self, verbose_name=None, name=None, default='', required=False,
+ validator=None, choices=None, unique=False, max_length=None):
+ Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
+ self.max_length = max_length
+
+ def validate(self, value):
+ if not isinstance(value, str) and not isinstance(value, unicode):
+ raise TypeError, 'Expecting Text, got %s' % type(value)
+ if self.max_length and len(value) > self.max_length:
+ raise ValueError, 'Length of value greater than maxlength %s' % self.max_length
+
+class PasswordProperty(StringProperty):
+ """
+ Hashed property who's original value can not be
+ retrieved, but still can be compaired.
+ """
+ data_type = Password
+ type_name = 'Password'
+
+ def __init__(self, verbose_name=None, name=None, default='', required=False,
+ validator=None, choices=None, unique=False):
+ StringProperty.__init__(self, verbose_name, name, default, required, validator, choices, unique)
+
+ def make_value_from_datastore(self, value):
+ p = Password(value)
+ return p
+
+ def get_value_for_datastore(self, model_instance):
+ value = StringProperty.get_value_for_datastore(self, model_instance)
+ if value and len(value):
+ return str(value)
+ else:
+ return None
+
+ def __set__(self, obj, value):
+ if not isinstance(value, Password):
+ p = Password()
+ p.set(value)
+ value = p
+ Property.__set__(self, obj, value)
+
+ def __get__(self, obj, objtype):
+ return Password(StringProperty.__get__(self, obj, objtype))
+
+ def validate(self, value):
+ value = Property.validate(self, value)
+ if isinstance(value, Password):
+ if len(value) > 1024:
+ raise ValueError, 'Length of value greater than maxlength'
+ else:
+ raise TypeError, 'Expecting Password, got %s' % type(value)
+
+class BlobProperty(Property):
+ data_type = Blob
+ type_name = "blob"
+
+ def __set__(self, obj, value):
+ if value != self.default_value():
+ if not isinstance(value, Blob):
+ oldb = self.__get__(obj, type(obj))
+ id = None
+ if oldb:
+ id = oldb.id
+ b = Blob(value=value, id=id)
+ value = b
+ Property.__set__(self, obj, value)
+
+class S3KeyProperty(Property):
+
+ data_type = boto.s3.key.Key
+ type_name = 'S3Key'
+ validate_regex = "^s3:\/\/([^\/]*)\/(.*)$"
+
+ def __init__(self, verbose_name=None, name=None, default=None,
+ required=False, validator=None, choices=None, unique=False):
+ Property.__init__(self, verbose_name, name, default, required,
+ validator, choices, unique)
+
+ def validate(self, value):
+ if value == self.default_value() or value == str(self.default_value()):
+ return self.default_value()
+ if isinstance(value, self.data_type):
+ return
+ match = re.match(self.validate_regex, value)
+ if match:
+ return
+ raise TypeError, 'Validation Error, expecting %s, got %s' % (self.data_type, type(value))
+
+ def __get__(self, obj, objtype):
+ value = Property.__get__(self, obj, objtype)
+ if value:
+ if isinstance(value, self.data_type):
+ return value
+ match = re.match(self.validate_regex, value)
+ if match:
+ s3 = obj._manager.get_s3_connection()
+ bucket = s3.get_bucket(match.group(1), validate=False)
+ k = bucket.get_key(match.group(2))
+ if not k:
+ k = bucket.new_key(match.group(2))
+ k.set_contents_from_string("")
+ return k
+ else:
+ return value
+
+ def get_value_for_datastore(self, model_instance):
+ value = Property.get_value_for_datastore(self, model_instance)
+ if value:
+ return "s3://%s/%s" % (value.bucket.name, value.name)
+ else:
+ return None
+
+class IntegerProperty(Property):
+
+ data_type = int
+ type_name = 'Integer'
+
+ def __init__(self, verbose_name=None, name=None, default=0, required=False,
+ validator=None, choices=None, unique=False, max=2147483647, min=-2147483648):
+ Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
+ self.max = max
+ self.min = min
+
+ def validate(self, value):
+ value = int(value)
+ value = Property.validate(self, value)
+ if value > self.max:
+ raise ValueError, 'Maximum value is %d' % self.max
+ if value < self.min:
+ raise ValueError, 'Minimum value is %d' % self.min
+ return value
+
+ def empty(self, value):
+ return value is None
+
+class LongProperty(Property):
+
+ data_type = long
+ type_name = 'Long'
+
+ def __init__(self, verbose_name=None, name=None, default=0, required=False,
+ validator=None, choices=None, unique=False):
+ Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
+
+ def validate(self, value):
+ value = long(value)
+ value = Property.validate(self, value)
+ min = -9223372036854775808
+ max = 9223372036854775807
+ if value > max:
+ raise ValueError, 'Maximum value is %d' % max
+ if value < min:
+ raise ValueError, 'Minimum value is %d' % min
+ return value
+
+ def empty(self, value):
+ return value is None
+
+class BooleanProperty(Property):
+
+ data_type = bool
+ type_name = 'Boolean'
+
+ def __init__(self, verbose_name=None, name=None, default=False, required=False,
+ validator=None, choices=None, unique=False):
+ Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
+
+ def empty(self, value):
+ return value is None
+
+class FloatProperty(Property):
+
+ data_type = float
+ type_name = 'Float'
+
+ def __init__(self, verbose_name=None, name=None, default=0.0, required=False,
+ validator=None, choices=None, unique=False):
+ Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
+
+ def validate(self, value):
+ value = float(value)
+ value = Property.validate(self, value)
+ return value
+
+ def empty(self, value):
+ return value is None
+
+class DateTimeProperty(Property):
+
+ data_type = datetime.datetime
+ type_name = 'DateTime'
+
+ def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, name=None,
+ default=None, required=False, validator=None, choices=None, unique=False):
+ Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
+ self.auto_now = auto_now
+ self.auto_now_add = auto_now_add
+
+ def default_value(self):
+ if self.auto_now or self.auto_now_add:
+ return self.now()
+ return Property.default_value(self)
+
+ def validate(self, value):
+ if value == None:
+ return
+ if not isinstance(value, self.data_type):
+ raise TypeError, 'Validation Error, expecting %s, got %s' % (self.data_type, type(value))
+
+ def get_value_for_datastore(self, model_instance):
+ if self.auto_now:
+ setattr(model_instance, self.name, self.now())
+ return Property.get_value_for_datastore(self, model_instance)
+
+ def now(self):
+ return datetime.datetime.utcnow()
+
+class ReferenceProperty(Property):
+
+ data_type = Key
+ type_name = 'Reference'
+
+ def __init__(self, reference_class=None, collection_name=None,
+ verbose_name=None, name=None, default=None, required=False, validator=None, choices=None, unique=False):
+ Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
+ self.reference_class = reference_class
+ self.collection_name = collection_name
+
+ def __get__(self, obj, objtype):
+ if obj:
+ value = getattr(obj, self.slot_name)
+ if value == self.default_value():
+ return value
+ # If the value is still the UUID for the referenced object, we need to create
+ # the object now that is the attribute has actually been accessed. This lazy
+ # instantiation saves unnecessary roundtrips to SimpleDB
+ if isinstance(value, str) or isinstance(value, unicode):
+ value = self.reference_class(value)
+ setattr(obj, self.name, value)
+ return value
+
+ def __property_config__(self, model_class, property_name):
+ Property.__property_config__(self, model_class, property_name)
+ if self.collection_name is None:
+ self.collection_name = '%s_%s_set' % (model_class.__name__.lower(), self.name)
+ if hasattr(self.reference_class, self.collection_name):
+ raise ValueError, 'duplicate property: %s' % self.collection_name
+ setattr(self.reference_class, self.collection_name,
+ _ReverseReferenceProperty(model_class, property_name, self.collection_name))
+
+ def check_uuid(self, value):
+ # This does a bit of hand waving to "type check" the string
+ t = value.split('-')
+ if len(t) != 5:
+ raise ValueError
+
+ def check_instance(self, value):
+ try:
+ obj_lineage = value.get_lineage()
+ cls_lineage = self.reference_class.get_lineage()
+ if obj_lineage.startswith(cls_lineage):
+ return
+ raise TypeError, '%s not instance of %s' % (obj_lineage, cls_lineage)
+ except:
+ raise ValueError, '%s is not a Model' % value
+
+ def validate(self, value):
+ if self.required and value==None:
+ raise ValueError, '%s is a required property' % self.name
+ if value == self.default_value():
+ return
+ if not isinstance(value, str) and not isinstance(value, unicode):
+ self.check_instance(value)
+
+class _ReverseReferenceProperty(Property):
+ data_type = Query
+ type_name = 'query'
+
+ def __init__(self, model, prop, name):
+ self.__model = model
+ self.__property = prop
+ self.collection_name = prop
+ self.name = name
+ self.item_type = model
+
+ def __get__(self, model_instance, model_class):
+ """Fetches collection of model instances of this collection property."""
+ if model_instance is not None:
+ query = Query(self.__model)
+ if type(self.__property) == list:
+ props = []
+ for prop in self.__property:
+ props.append("%s =" % prop)
+ return query.filter(props, model_instance)
+ else:
+ return query.filter(self.__property + ' =', model_instance)
+ else:
+ return self
+
+ def __set__(self, model_instance, value):
+ """Not possible to set a new collection."""
+ raise ValueError, 'Virtual property is read-only'
+
+
+class CalculatedProperty(Property):
+
+ def __init__(self, verbose_name=None, name=None, default=None,
+ required=False, validator=None, choices=None,
+ calculated_type=int, unique=False, use_method=False):
+ Property.__init__(self, verbose_name, name, default, required,
+ validator, choices, unique)
+ self.calculated_type = calculated_type
+ self.use_method = use_method
+
+ def __get__(self, obj, objtype):
+ value = self.default_value()
+ if obj:
+ try:
+ value = getattr(obj, self.slot_name)
+ if self.use_method:
+ value = value()
+ except AttributeError:
+ pass
+ return value
+
+ def __set__(self, obj, value):
+ """Not possible to set a new AutoID."""
+ pass
+
+ def _set_direct(self, obj, value):
+ if not self.use_method:
+ setattr(obj, self.slot_name, value)
+
+ def get_value_for_datastore(self, model_instance):
+ if self.calculated_type in [str, int, bool]:
+ value = self.__get__(model_instance, model_instance.__class__)
+ return value
+ else:
+ return None
+
+class ListProperty(Property):
+
+ data_type = list
+ type_name = 'List'
+
+ def __init__(self, item_type, verbose_name=None, name=None, default=None, **kwds):
+ if default is None:
+ default = []
+ self.item_type = item_type
+ Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
+
+ def validate(self, value):
+ if value is not None:
+ if not isinstance(value, list):
+ value = [value]
+
+ if self.item_type in (int, long):
+ item_type = (int, long)
+ elif self.item_type in (str, unicode):
+ item_type = (str, unicode)
+ else:
+ item_type = self.item_type
+
+ for item in value:
+ if not isinstance(item, item_type):
+ if item_type == (int, long):
+ raise ValueError, 'Items in the %s list must all be integers.' % self.name
+ else:
+ raise ValueError('Items in the %s list must all be %s instances' %
+ (self.name, self.item_type.__name__))
+ return value
+
+ def empty(self, value):
+ return value is None
+
+ def default_value(self):
+ return list(super(ListProperty, self).default_value())
+
+ def __set__(self, obj, value):
+ """Override the set method to allow them to set the property to an instance of the item_type instead of requiring a list to be passed in"""
+ if self.item_type in (int, long):
+ item_type = (int, long)
+ elif self.item_type in (str, unicode):
+ item_type = (str, unicode)
+ else:
+ item_type = self.item_type
+ if isinstance(value, item_type):
+ value = [value]
+ elif value == None: # Override to allow them to set this to "None" to remove everything
+ value = []
+ return super(ListProperty, self).__set__(obj,value)
+
+
+class MapProperty(Property):
+
+ data_type = dict
+ type_name = 'Map'
+
+ def __init__(self, item_type=str, verbose_name=None, name=None, default=None, **kwds):
+ if default is None:
+ default = {}
+ self.item_type = item_type
+ Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
+
+ def validate(self, value):
+ if value is not None:
+ if not isinstance(value, dict):
+ raise ValueError, 'Value must of type dict'
+
+ if self.item_type in (int, long):
+ item_type = (int, long)
+ elif self.item_type in (str, unicode):
+ item_type = (str, unicode)
+ else:
+ item_type = self.item_type
+
+ for key in value:
+ if not isinstance(value[key], item_type):
+ if item_type == (int, long):
+ raise ValueError, 'Values in the %s Map must all be integers.' % self.name
+ else:
+ raise ValueError('Values in the %s Map must all be %s instances' %
+ (self.name, self.item_type.__name__))
+ return value
+
+ def empty(self, value):
+ return value is None
+
+ def default_value(self):
+ return {}
diff --git a/vendor/boto/boto/sdb/db/query.py b/vendor/boto/boto/sdb/db/query.py
new file mode 100644
index 0000000000..27987a311b
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/query.py
@@ -0,0 +1,79 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+class Query(object):
+ __local_iter__ = None
+ def __init__(self, model_class, limit=None, next_token=None, manager=None):
+ self.model_class = model_class
+ self.limit = limit
+ if manager:
+ self.manager = manager
+ else:
+ self.manager = self.model_class._manager
+ self.filters = []
+ self.sort_by = None
+ self.rs = None
+ self.next_token = next_token
+
+ def __iter__(self):
+ return iter(self.manager.query(self))
+
+ def next(self):
+ if self.__local_iter__ == None:
+ self.__local_iter__ = self.__iter__()
+ return self.__local_iter__.next()
+
+ def filter(self, property_operator, value):
+ self.filters.append((property_operator, value))
+ return self
+
+ def fetch(self, limit, offset=0):
+ raise NotImplementedError, "fetch mode is not currently supported"
+
+ def count(self):
+ return self.manager.count(self.model_class, self.filters)
+
+ def get_query(self):
+ return self.manager._build_filter_part(self.model_class, self.filters, self.sort_by)
+
+ def order(self, key):
+ self.sort_by = key
+ return self
+
+ def to_xml(self, doc=None):
+ if not doc:
+ xmlmanager = self.model_class.get_xmlmanager()
+ doc = xmlmanager.new_doc()
+ for obj in self:
+ obj.to_xml(doc)
+ return doc
+
+ def get_next_token(self):
+ if self.rs:
+ return self.rs.next_token
+ if self._next_token:
+ return self._next_token
+ return None
+
+ def set_next_token(self, token):
+ self._next_token = token
+
+ next_token = property(get_next_token, set_next_token)
diff --git a/vendor/boto/boto/sdb/db/sequence.py b/vendor/boto/boto/sdb/db/sequence.py
new file mode 100644
index 0000000000..0a2ad326b8
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/sequence.py
@@ -0,0 +1,224 @@
+# Copyright (c) 2010 Chris Moyer http://coredumped.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.exception import SDBResponseError
+
+class SequenceGenerator(object):
+ """Generic Sequence Generator object, this takes a single
+ string as the "sequence" and uses that to figure out
+ what the next value in a string is. For example
+ if you give "ABC" and pass in "A" it will give you "B",
+ and if you give it "C" it will give you "AA".
+
+ If you set "rollover" to True in the above example, passing
+ in "C" would give you "A" again.
+
+ The Sequence string can be a string or any iterable
+ that has the "index" function and is indexable.
+ """
+ __name__ = "SequenceGenerator"
+
+ def __init__(self, sequence_string, rollover=False):
+ """Create a new SequenceGenerator using the sequence_string
+ as how to generate the next item.
+
+ :param sequence_string: The string or list that explains
+ how to generate the next item in the sequence
+ :type sequence_string: str,iterable
+
+ :param rollover: Rollover instead of incrementing when
+ we hit the end of the sequence
+ :type rollover: bool
+ """
+ self.sequence_string = sequence_string
+ self.sequence_length = len(sequence_string[0])
+ self.rollover = rollover
+ self.last_item = sequence_string[-1]
+ self.__name__ = "%s('%s')" % (self.__class__.__name__, sequence_string)
+
+ def __call__(self, val, last=None):
+ """Get the next value in the sequence"""
+ # If they pass us in a string that's not at least
+ # the lenght of our sequence, then return the
+ # first element in our sequence
+ if val == None or len(val) < self.sequence_length:
+ return self.sequence_string[0]
+ last_value = val[-self.sequence_length:]
+ if (not self.rollover) and (last_value == self.last_item):
+ val = "%s%s" % (self(val[:-self.sequence_length]), self._inc(last_value))
+ else:
+ val = "%s%s" % (val[:-self.sequence_length], self._inc(last_value))
+ return val
+
+ def _inc(self, val):
+ """Increment a single value"""
+ assert(len(val) == self.sequence_length)
+ return self.sequence_string[(self.sequence_string.index(val)+1) % len(self.sequence_string)]
+
+
+
+#
+# Simple Sequence Functions
+#
+def increment_by_one(cv=None, lv=None):
+ if cv == None:
+ return 0
+ return cv + 1
+
+def double(cv=None, lv=None):
+ if cv == None:
+ return 1
+ return cv * 2
+
+def fib(cv=1, lv=0):
+ """The fibonacci sequence, this incrementer uses the
+ last value"""
+ if cv == None:
+ cv = 1
+ if lv == None:
+ lv = 0
+ return cv + lv
+
+increment_string = SequenceGenerator("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+
+
+class Sequence(object):
+ """A simple Sequence using the new SDB "Consistent" features
+ Based largly off of the "Counter" example from mitch garnaat:
+ http://bitbucket.org/mitch/stupidbototricks/src/tip/counter.py"""
+
+
+ def __init__(self, id=None, domain_name=None, fnc=increment_by_one, init_val=None):
+ """Create a new Sequence, using an optional function to
+ increment to the next number, by default we just increment by one.
+ Every parameter here is optional, if you don't specify any options
+ then you'll get a new SequenceGenerator with a random ID stored in the
+ default domain that increments by one and uses the default botoweb
+ environment
+
+ :param id: Optional ID (name) for this counter
+ :type id: str
+
+ :param domain_name: Optional domain name to use, by default we get this out of the
+ environment configuration
+ :type domain_name:str
+
+ :param fnc: Optional function to use for the incrementation, by default we just increment by one
+ There are several functions defined in this module.
+ Your function must accept "None" to get the initial value
+ :type fnc: function, str
+
+ :param init_val: Initial value, by default this is the first element in your sequence,
+ but you can pass in any value, even a string if you pass in a function that uses
+ strings instead of ints to increment
+ """
+ self._db = None
+ self._value = None
+ self.last_value = None
+ self.domain_name = domain_name
+ self.id = id
+ if self.id == None:
+ import uuid
+ self.id = str(uuid.uuid4())
+ if init_val == None:
+ init_val = fnc(init_val)
+ self.val = init_val
+
+ self.item_type = type(fnc(None))
+ self.timestamp = None
+ # Allow us to pass in a full name to a function
+ if type(fnc) == str:
+ from boto.utils import find_class
+ fnc = find_class(fnc)
+ self.fnc = fnc
+
+ def set(self, val):
+ """Set the value"""
+ import time
+ now = time.time()
+ expected_values = []
+ new_val = {}
+ new_val['timestamp'] = now
+ if self._value != None:
+ new_val['last_value'] = self._value
+ expected_values = ['current_value', str(self._value)]
+ new_val['current_value'] = val
+ try:
+ self.db.put_attributes(self.id, new_val, expected_values=expected_values)
+ self.timestamp = new_val['timestamp']
+ except SDBResponseError, e:
+ if e.status == 409:
+ raise ValueError, "Sequence out of sync"
+ else:
+ raise
+
+
+ def get(self):
+ """Get the value"""
+ val = self.db.get_attributes(self.id, consistent_read=True)
+ if val and val.has_key('timestamp'):
+ self.timestamp = val['timestamp']
+ if val and val.has_key('current_value'):
+ self._value = self.item_type(val['current_value'])
+ if val.has_key("last_value") and val['last_value'] != None:
+ self.last_value = self.item_type(val['last_value'])
+ return self._value
+
+ val = property(get, set)
+
+ def __repr__(self):
+ return "%s('%s', '%s', '%s.%s', '%s')" % (
+ self.__class__.__name__,
+ self.id,
+ self.domain_name,
+ self.fnc.__module__, self.fnc.__name__,
+ self.val)
+
+
+ def _connect(self):
+ """Connect to our domain"""
+ if not self._db:
+ if not self.domain_name:
+ import boto
+ sdb = boto.connect_sdb()
+ self.domain_name = boto.config.get("DB", "sequence_db", boto.config.get("DB", "db_name", "default"))
+ try:
+ self._db = sdb.get_domain(self.domain_name)
+ except SDBResponseError, e:
+ if e.status == 400:
+ self._db = sdb.create_domain(self.domain_name)
+ else:
+ raise
+ return self._db
+
+ db = property(_connect)
+
+ def next(self):
+ self.val = self.fnc(self.val, self.last_value)
+ return self.val
+
+ def delete(self):
+ """Remove this sequence"""
+ self.db.delete_attributes(self.id)
+
+ def __del__(self):
+ self.delete()
diff --git a/vendor/boto/boto/sdb/db/test_db.py b/vendor/boto/boto/sdb/db/test_db.py
new file mode 100644
index 0000000000..0c345abd4a
--- /dev/null
+++ b/vendor/boto/boto/sdb/db/test_db.py
@@ -0,0 +1,225 @@
+from boto.sdb.db.model import Model
+from boto.sdb.db.property import StringProperty, IntegerProperty, BooleanProperty
+from boto.sdb.db.property import DateTimeProperty, FloatProperty, ReferenceProperty
+from boto.sdb.db.property import PasswordProperty, ListProperty, MapProperty
+from datetime import datetime
+import time
+from boto.exception import SDBPersistenceError
+
+_objects = {}
+
+#
+# This will eventually be moved to the boto.tests module and become a real unit test
+# but for now it will live here. It shows examples of each of the Property types in
+# use and tests the basic operations.
+#
+class TestBasic(Model):
+
+ name = StringProperty()
+ size = IntegerProperty()
+ foo = BooleanProperty()
+ date = DateTimeProperty()
+
+class TestFloat(Model):
+
+ name = StringProperty()
+ value = FloatProperty()
+
+class TestRequired(Model):
+
+ req = StringProperty(required=True, default='foo')
+
+class TestReference(Model):
+
+ ref = ReferenceProperty(reference_class=TestBasic, collection_name='refs')
+
+class TestSubClass(TestBasic):
+
+ answer = IntegerProperty()
+
+class TestPassword(Model):
+ password = PasswordProperty()
+
+class TestList(Model):
+
+ name = StringProperty()
+ nums = ListProperty(int)
+
+class TestMap(Model):
+
+ name = StringProperty()
+ map = MapProperty()
+
+class TestListReference(Model):
+
+ name = StringProperty()
+ basics = ListProperty(TestBasic)
+
+class TestAutoNow(Model):
+
+ create_date = DateTimeProperty(auto_now_add=True)
+ modified_date = DateTimeProperty(auto_now=True)
+
+class TestUnique(Model):
+ name = StringProperty(unique=True)
+
+def test_basic():
+ global _objects
+ t = TestBasic()
+ t.name = 'simple'
+ t.size = -42
+ t.foo = True
+ t.date = datetime.now()
+ print 'saving object'
+ t.put()
+ _objects['test_basic_t'] = t
+ time.sleep(5)
+ print 'now try retrieving it'
+ tt = TestBasic.get_by_id(t.id)
+ _objects['test_basic_tt'] = tt
+ assert tt.id == t.id
+ l = TestBasic.get_by_id([t.id])
+ assert len(l) == 1
+ assert l[0].id == t.id
+ assert t.size == tt.size
+ assert t.foo == tt.foo
+ assert t.name == tt.name
+ #assert t.date == tt.date
+ return t
+
+def test_float():
+ global _objects
+ t = TestFloat()
+ t.name = 'float object'
+ t.value = 98.6
+ print 'saving object'
+ t.save()
+ _objects['test_float_t'] = t
+ time.sleep(5)
+ print 'now try retrieving it'
+ tt = TestFloat.get_by_id(t.id)
+ _objects['test_float_tt'] = tt
+ assert tt.id == t.id
+ assert tt.name == t.name
+ assert tt.value == t.value
+ return t
+
+def test_required():
+ global _objects
+ t = TestRequired()
+ _objects['test_required_t'] = t
+ t.put()
+ return t
+
+def test_reference(t=None):
+ global _objects
+ if not t:
+ t = test_basic()
+ tt = TestReference()
+ tt.ref = t
+ tt.put()
+ time.sleep(10)
+ tt = TestReference.get_by_id(tt.id)
+ _objects['test_reference_tt'] = tt
+ assert tt.ref.id == t.id
+ for o in t.refs:
+ print o
+
+def test_subclass():
+ global _objects
+ t = TestSubClass()
+ _objects['test_subclass_t'] = t
+ t.name = 'a subclass'
+ t.size = -489
+ t.save()
+
+def test_password():
+ global _objects
+ t = TestPassword()
+ _objects['test_password_t'] = t
+ t.password = "foo"
+ t.save()
+ time.sleep(5)
+ # Make sure it stored ok
+ tt = TestPassword.get_by_id(t.id)
+ _objects['test_password_tt'] = tt
+ #Testing password equality
+ assert tt.password == "foo"
+ #Testing password not stored as string
+ assert str(tt.password) != "foo"
+
+def test_list():
+ global _objects
+ t = TestList()
+ _objects['test_list_t'] = t
+ t.name = 'a list of ints'
+ t.nums = [1,2,3,4,5]
+ t.put()
+ tt = TestList.get_by_id(t.id)
+ _objects['test_list_tt'] = tt
+ assert tt.name == t.name
+ for n in tt.nums:
+ assert isinstance(n, int)
+
+def test_list_reference():
+ global _objects
+ t = TestBasic()
+ t.put()
+ _objects['test_list_ref_t'] = t
+ tt = TestListReference()
+ tt.name = "foo"
+ tt.basics = [t]
+ tt.put()
+ time.sleep(5)
+ _objects['test_list_ref_tt'] = tt
+ ttt = TestListReference.get_by_id(tt.id)
+ assert ttt.basics[0].id == t.id
+
+def test_unique():
+ global _objects
+ t = TestUnique()
+ name = 'foo' + str(int(time.time()))
+ t.name = name
+ t.put()
+ _objects['test_unique_t'] = t
+ time.sleep(10)
+ tt = TestUnique()
+ _objects['test_unique_tt'] = tt
+ tt.name = name
+ try:
+ tt.put()
+ assert False
+ except(SDBPersistenceError):
+ pass
+
+def test_datetime():
+ global _objects
+ t = TestAutoNow()
+ t.put()
+ _objects['test_datetime_t'] = t
+ time.sleep(5)
+ tt = TestAutoNow.get_by_id(t.id)
+ assert tt.create_date.timetuple() == t.create_date.timetuple()
+
+def test():
+ print 'test_basic'
+ t1 = test_basic()
+ print 'test_required'
+ test_required()
+ print 'test_reference'
+ test_reference(t1)
+ print 'test_subclass'
+ test_subclass()
+ print 'test_password'
+ test_password()
+ print 'test_list'
+ test_list()
+ print 'test_list_reference'
+ test_list_reference()
+ print "test_datetime"
+ test_datetime()
+ print 'test_unique'
+ test_unique()
+
+if __name__ == "__main__":
+ test()
diff --git a/vendor/boto/boto/sdb/domain.py b/vendor/boto/boto/sdb/domain.py
new file mode 100644
index 0000000000..17739eead0
--- /dev/null
+++ b/vendor/boto/boto/sdb/domain.py
@@ -0,0 +1,337 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an SDB Domain
+"""
+from boto.sdb.queryresultset import QueryResultSet, SelectResultSet
+
+class Domain:
+
+ def __init__(self, connection=None, name=None):
+ self.connection = connection
+ self.name = name
+ self._metadata = None
+
+ def __repr__(self):
+ return 'Domain:%s' % self.name
+
+ def __iter__(self):
+ return iter(self.select("SELECT * FROM `%s`" % self.name))
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'DomainName':
+ self.name = value
+ else:
+ setattr(self, name, value)
+
+ def get_metadata(self):
+ if not self._metadata:
+ self._metadata = self.connection.domain_metadata(self)
+ return self._metadata
+
+ def put_attributes(self, item_name, attributes,
+ replace=True, expected_values=None):
+ """
+ Store attributes for a given item.
+
+ :type item_name: string
+ :param item_name: The name of the item whose attributes are being stored.
+
+ :type attribute_names: dict or dict-like object
+ :param attribute_names: The name/value pairs to store as attributes
+
+ :type expected_value: list
+ :param expected_value: If supplied, this is a list or tuple consisting
+ of a single attribute name and expected value.
+ The list can be of the form:
+ * ['name', 'value']
+ In which case the call will first verify
+ that the attribute "name" of this item has
+ a value of "value". If it does, the delete
+ will proceed, otherwise a ConditionalCheckFailed
+ error will be returned.
+ The list can also be of the form:
+ * ['name', True|False]
+ which will simply check for the existence (True)
+ or non-existencve (False) of the attribute.
+
+ :type replace: bool
+ :param replace: Whether the attribute values passed in will replace
+ existing values or will be added as addition values.
+ Defaults to True.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ return self.connection.put_attributes(self, item_name, attributes,
+ replace, expected_values)
+
+ def batch_put_attributes(self, items, replace=True):
+ """
+ Store attributes for multiple items.
+
+ :type items: dict or dict-like object
+ :param items: A dictionary-like object. The keys of the dictionary are
+ the item names and the values are themselves dictionaries
+ of attribute names/values, exactly the same as the
+ attribute_names parameter of the scalar put_attributes
+ call.
+
+ :type replace: bool
+ :param replace: Whether the attribute values passed in will replace
+ existing values or will be added as addition values.
+ Defaults to True.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ return self.connection.batch_put_attributes(self, items, replace)
+
+ def get_attributes(self, item_name, attribute_name=None,
+ consistent_read=False, item=None):
+ """
+ Retrieve attributes for a given item.
+
+ :type item_name: string
+ :param item_name: The name of the item whose attributes are being retrieved.
+
+ :type attribute_names: string or list of strings
+ :param attribute_names: An attribute name or list of attribute names. This
+ parameter is optional. If not supplied, all attributes
+ will be retrieved for the item.
+
+ :rtype: :class:`boto.sdb.item.Item`
+ :return: An Item mapping type containing the requested attribute name/values
+ """
+ return self.connection.get_attributes(self, item_name, attribute_name,
+ consistent_read, item)
+
+ def delete_attributes(self, item_name, attributes=None,
+ expected_values=None):
+ """
+ Delete attributes from a given item.
+
+ :type item_name: string
+ :param item_name: The name of the item whose attributes are being deleted.
+
+ :type attributes: dict, list or :class:`boto.sdb.item.Item`
+ :param attributes: Either a list containing attribute names which will cause
+ all values associated with that attribute name to be deleted or
+ a dict or Item containing the attribute names and keys and list
+ of values to delete as the value. If no value is supplied,
+ all attribute name/values for the item will be deleted.
+
+ :type expected_value: list
+ :param expected_value: If supplied, this is a list or tuple consisting
+ of a single attribute name and expected value.
+ The list can be of the form:
+ * ['name', 'value']
+ In which case the call will first verify
+ that the attribute "name" of this item has
+ a value of "value". If it does, the delete
+ will proceed, otherwise a ConditionalCheckFailed
+ error will be returned.
+ The list can also be of the form:
+ * ['name', True|False]
+ which will simply check for the existence (True)
+ or non-existencve (False) of the attribute.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ return self.connection.delete_attributes(self, item_name, attributes,
+ expected_values)
+
+ def select(self, query='', next_token=None, consistent_read=False, max_items=None):
+ """
+ Returns a set of Attributes for item names within domain_name that match the query.
+ The query must be expressed in using the SELECT style syntax rather than the
+ original SimpleDB query language.
+
+ :type query: string
+ :param query: The SimpleDB query to be performed.
+
+ :rtype: iter
+ :return: An iterator containing the results. This is actually a generator
+ function that will iterate across all search results, not just the
+ first page.
+ """
+ return SelectResultSet(self, query, max_items = max_items, next_token=next_token,
+ consistent_read=consistent_read)
+
+ def get_item(self, item_name):
+ item = self.get_attributes(item_name)
+ if item:
+ item.domain = self
+ return item
+ else:
+ return None
+
+ def new_item(self, item_name):
+ return self.connection.item_cls(self, item_name)
+
+ def delete_item(self, item):
+ self.delete_attributes(item.name)
+
+ def to_xml(self, f=None):
+ """Get this domain as an XML DOM Document
+ :param f: Optional File to dump directly to
+ :type f: File or Stream
+
+ :return: File object where the XML has been dumped to
+ :rtype: file
+ """
+ if not f:
+ from tempfile import TemporaryFile
+ f = TemporaryFile()
+ print >>f, '<?xml version="1.0" encoding="UTF-8"?>'
+ print >>f, '<Domain id="%s">' % self.name
+ for item in self:
+ print >>f, '\t<Item id="%s">' % item.name
+ for k in item:
+ print >>f, '\t\t<attribute id="%s">' % k
+ values = item[k]
+ if not isinstance(values, list):
+ values = [values]
+ for value in values:
+ print >>f, '\t\t\t<value><![CDATA[',
+ if isinstance(value, unicode):
+ value = value.encode('utf-8', 'replace')
+ else:
+ value = unicode(value, errors='replace').encode('utf-8', 'replace')
+ f.write(value)
+ print >>f, ']]></value>'
+ print >>f, '\t\t</attribute>'
+ print >>f, '\t</Item>'
+ print >>f, '</Domain>'
+ f.flush()
+ f.seek(0)
+ return f
+
+
+ def from_xml(self, doc):
+ """Load this domain based on an XML document"""
+ import xml.sax
+ handler = DomainDumpParser(self)
+ xml.sax.parse(doc, handler)
+ return handler
+
+
+class DomainMetaData:
+
+ def __init__(self, domain=None):
+ self.domain = domain
+ self.item_count = None
+ self.item_names_size = None
+ self.attr_name_count = None
+ self.attr_names_size = None
+ self.attr_value_count = None
+ self.attr_values_size = None
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'ItemCount':
+ self.item_count = int(value)
+ elif name == 'ItemNamesSizeBytes':
+ self.item_names_size = int(value)
+ elif name == 'AttributeNameCount':
+ self.attr_name_count = int(value)
+ elif name == 'AttributeNamesSizeBytes':
+ self.attr_names_size = int(value)
+ elif name == 'AttributeValueCount':
+ self.attr_value_count = int(value)
+ elif name == 'AttributeValuesSizeBytes':
+ self.attr_values_size = int(value)
+ elif name == 'Timestamp':
+ self.timestamp = value
+ else:
+ setattr(self, name, value)
+
+import sys
+from xml.sax.handler import ContentHandler
+class DomainDumpParser(ContentHandler):
+ """
+ SAX parser for a domain that has been dumped
+ """
+
+ def __init__(self, domain):
+ self.uploader = UploaderThread(domain)
+ self.item_id = None
+ self.attrs = {}
+ self.attribute = None
+ self.value = ""
+ self.domain = domain
+
+ def startElement(self, name, attrs):
+ if name == "Item":
+ self.item_id = attrs['id']
+ self.attrs = {}
+ elif name == "attribute":
+ self.attribute = attrs['id']
+ elif name == "value":
+ self.value = ""
+
+ def characters(self, ch):
+ self.value += ch
+
+ def endElement(self, name):
+ if name == "value":
+ if self.value and self.attribute:
+ value = self.value.strip()
+ attr_name = self.attribute.strip()
+ if self.attrs.has_key(attr_name):
+ self.attrs[attr_name].append(value)
+ else:
+ self.attrs[attr_name] = [value]
+ elif name == "Item":
+ self.uploader.items[self.item_id] = self.attrs
+ # Every 20 items we spawn off the uploader
+ if len(self.uploader.items) >= 20:
+ self.uploader.start()
+ self.uploader = UploaderThread(self.domain)
+ elif name == "Domain":
+ # If we're done, spawn off our last Uploader Thread
+ self.uploader.start()
+
+from threading import Thread
+class UploaderThread(Thread):
+ """Uploader Thread"""
+
+ def __init__(self, domain):
+ self.db = domain
+ self.items = {}
+ Thread.__init__(self)
+
+ def run(self):
+ try:
+ self.db.batch_put_attributes(self.items)
+ except:
+ print "Exception using batch put, trying regular put instead"
+ for item_name in self.items:
+ self.db.put_attributes(item_name, self.items[item_name])
+ print ".",
+ sys.stdout.flush()
diff --git a/vendor/boto/boto/sdb/item.py b/vendor/boto/boto/sdb/item.py
new file mode 100644
index 0000000000..d6a56a95b9
--- /dev/null
+++ b/vendor/boto/boto/sdb/item.py
@@ -0,0 +1,105 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an SDB Item
+"""
+
+import base64
+
+class Item(dict):
+
+ def __init__(self, domain, name='', active=False):
+ dict.__init__(self)
+ self.domain = domain
+ self.name = name
+ self.active = active
+ self.request_id = None
+ self.encoding = None
+ self.in_attribute = False
+ self.converter = self.domain.connection.converter
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Attribute':
+ self.in_attribute = True
+ self.encoding = attrs.get('encoding', None)
+ return None
+
+ def decode_value(self, value):
+ if self.encoding == 'base64':
+ self.encoding = None
+ return base64.decodestring(value)
+ else:
+ return value
+
+ def endElement(self, name, value, connection):
+ if name == 'ItemName':
+ self.name = self.decode_value(value)
+ elif name == 'Name':
+ if self.in_attribute:
+ self.last_key = self.decode_value(value)
+ else:
+ self.name = self.decode_value(value)
+ elif name == 'Value':
+ if self.has_key(self.last_key):
+ if not isinstance(self[self.last_key], list):
+ self[self.last_key] = [self[self.last_key]]
+ value = self.decode_value(value)
+ if self.converter:
+ value = self.converter.decode(value)
+ self[self.last_key].append(value)
+ else:
+ value = self.decode_value(value)
+ if self.converter:
+ value = self.converter.decode(value)
+ self[self.last_key] = value
+ elif name == 'BoxUsage':
+ try:
+ connection.box_usage += float(value)
+ except:
+ pass
+ elif name == 'RequestId':
+ self.request_id = value
+ elif name == 'Attribute':
+ self.in_attribute = False
+ else:
+ setattr(self, name, value)
+
+ def load(self):
+ self.domain.get_attributes(self.name, item=self)
+
+ def save(self, replace=True):
+ self.domain.put_attributes(self.name, self, replace)
+
+ def add_value(self, key, value):
+ if key in self:
+ if not isinstance(self[key], list):
+ self[key] = [self[key]]
+ self[key].append(value)
+ else:
+ self[key] = value
+
+ def delete(self):
+ self.domain.delete_item(self)
+
+
+
+
diff --git a/vendor/boto/boto/sdb/persist/__init__.py b/vendor/boto/boto/sdb/persist/__init__.py
new file mode 100644
index 0000000000..2f2b0c1d68
--- /dev/null
+++ b/vendor/boto/boto/sdb/persist/__init__.py
@@ -0,0 +1,83 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import boto
+from boto.utils import find_class
+
+class Manager(object):
+
+ DefaultDomainName = boto.config.get('Persist', 'default_domain', None)
+
+ def __init__(self, domain_name=None, aws_access_key_id=None, aws_secret_access_key=None, debug=0):
+ self.domain_name = domain_name
+ self.aws_access_key_id = aws_access_key_id
+ self.aws_secret_access_key = aws_secret_access_key
+ self.domain = None
+ self.sdb = None
+ self.s3 = None
+ if not self.domain_name:
+ self.domain_name = self.DefaultDomainName
+ if self.domain_name:
+ boto.log.info('No SimpleDB domain set, using default_domain: %s' % self.domain_name)
+ else:
+ boto.log.warning('No SimpleDB domain set, persistance is disabled')
+ if self.domain_name:
+ self.sdb = boto.connect_sdb(aws_access_key_id=self.aws_access_key_id,
+ aws_secret_access_key=self.aws_secret_access_key,
+ debug=debug)
+ self.domain = self.sdb.lookup(self.domain_name)
+ if not self.domain:
+ self.domain = self.sdb.create_domain(self.domain_name)
+
+ def get_s3_connection(self):
+ if not self.s3:
+ self.s3 = boto.connect_s3(self.aws_access_key_id, self.aws_secret_access_key)
+ return self.s3
+
+def get_manager(domain_name=None, aws_access_key_id=None, aws_secret_access_key=None, debug=0):
+ return Manager(domain_name, aws_access_key_id, aws_secret_access_key, debug=debug)
+
+def set_domain(domain_name):
+ Manager.DefaultDomainName = domain_name
+
+def get_domain():
+ return Manager.DefaultDomainName
+
+def revive_object_from_id(id, manager):
+ if not manager.domain:
+ return None
+ attrs = manager.domain.get_attributes(id, ['__module__', '__type__', '__lineage__'])
+ try:
+ cls = find_class(attrs['__module__'], attrs['__type__'])
+ return cls(id, manager=manager)
+ except ImportError:
+ return None
+
+def object_lister(cls, query_lister, manager):
+ for item in query_lister:
+ if cls:
+ yield cls(item.name)
+ else:
+ o = revive_object_from_id(item.name, manager)
+ if o:
+ yield o
+
+
diff --git a/vendor/boto/boto/sdb/persist/checker.py b/vendor/boto/boto/sdb/persist/checker.py
new file mode 100644
index 0000000000..e2146c9732
--- /dev/null
+++ b/vendor/boto/boto/sdb/persist/checker.py
@@ -0,0 +1,302 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from datetime import datetime
+from boto.s3.key import Key
+from boto.s3.bucket import Bucket
+from boto.sdb.persist import revive_object_from_id
+from boto.exception import SDBPersistenceError
+from boto.utils import Password
+
+ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
+
+class ValueChecker:
+
+ def check(self, value):
+ """
+ Checks a value to see if it is of the right type.
+
+ Should raise a TypeError exception if an in appropriate value is passed in.
+ """
+ raise TypeError
+
+ def from_string(self, str_value, obj):
+ """
+ Takes a string as input and returns the type-specific value represented by that string.
+
+ Should raise a ValueError if the value cannot be converted to the appropriate type.
+ """
+ raise ValueError
+
+ def to_string(self, value):
+ """
+ Convert a value to it's string representation.
+
+ Should raise a ValueError if the value cannot be converted to a string representation.
+ """
+ raise ValueError
+
+class StringChecker(ValueChecker):
+
+ def __init__(self, **params):
+ if params.has_key('maxlength'):
+ self.maxlength = params['maxlength']
+ else:
+ self.maxlength = 1024
+ if params.has_key('default'):
+ self.check(params['default'])
+ self.default = params['default']
+ else:
+ self.default = ''
+
+ def check(self, value):
+ if isinstance(value, str) or isinstance(value, unicode):
+ if len(value) > self.maxlength:
+ raise ValueError, 'Length of value greater than maxlength'
+ else:
+ raise TypeError, 'Expecting String, got %s' % type(value)
+
+ def from_string(self, str_value, obj):
+ return str_value
+
+ def to_string(self, value):
+ self.check(value)
+ return value
+
+class PasswordChecker(StringChecker):
+ def check(self, value):
+ if isinstance(value, str) or isinstance(value, unicode) or isinstance(value, Password):
+ if len(value) > self.maxlength:
+ raise ValueError, 'Length of value greater than maxlength'
+ else:
+ raise TypeError, 'Expecting String, got %s' % type(value)
+
+class IntegerChecker(ValueChecker):
+
+ __sizes__ = { 'small' : (65535, 32767, -32768, 5),
+ 'medium' : (4294967295, 2147483647, -2147483648, 10),
+ 'large' : (18446744073709551615, 9223372036854775807, -9223372036854775808, 20)}
+
+ def __init__(self, **params):
+ self.size = params.get('size', 'medium')
+ if self.size not in self.__sizes__.keys():
+ raise ValueError, 'size must be one of %s' % self.__sizes__.keys()
+ self.signed = params.get('signed', True)
+ self.default = params.get('default', 0)
+ self.format_string = '%%0%dd' % self.__sizes__[self.size][-1]
+
+ def check(self, value):
+ if not isinstance(value, int) and not isinstance(value, long):
+ raise TypeError, 'Expecting int or long, got %s' % type(value)
+ if self.signed:
+ min = self.__sizes__[self.size][2]
+ max = self.__sizes__[self.size][1]
+ else:
+ min = 0
+ max = self.__sizes__[self.size][0]
+ if value > max:
+ raise ValueError, 'Maximum value is %d' % max
+ if value < min:
+ raise ValueError, 'Minimum value is %d' % min
+
+ def from_string(self, str_value, obj):
+ val = int(str_value)
+ if self.signed:
+ val = val + self.__sizes__[self.size][2]
+ return val
+
+ def to_string(self, value):
+ self.check(value)
+ if self.signed:
+ value += -self.__sizes__[self.size][2]
+ return self.format_string % value
+
+class BooleanChecker(ValueChecker):
+
+ def __init__(self, **params):
+ if params.has_key('default'):
+ self.default = params['default']
+ else:
+ self.default = False
+
+ def check(self, value):
+ if not isinstance(value, bool):
+ raise TypeError, 'Expecting bool, got %s' % type(value)
+
+ def from_string(self, str_value, obj):
+ if str_value.lower() == 'true':
+ return True
+ else:
+ return False
+
+ def to_string(self, value):
+ self.check(value)
+ if value == True:
+ return 'true'
+ else:
+ return 'false'
+
+class DateTimeChecker(ValueChecker):
+
+ def __init__(self, **params):
+ if params.has_key('maxlength'):
+ self.maxlength = params['maxlength']
+ else:
+ self.maxlength = 1024
+ if params.has_key('default'):
+ self.default = params['default']
+ else:
+ self.default = datetime.now()
+
+ def check(self, value):
+ if not isinstance(value, datetime):
+ raise TypeError, 'Expecting datetime, got %s' % type(value)
+
+ def from_string(self, str_value, obj):
+ try:
+ return datetime.strptime(str_value, ISO8601)
+ except:
+ raise ValueError, 'Unable to convert %s to DateTime' % str_value
+
+ def to_string(self, value):
+ self.check(value)
+ return value.strftime(ISO8601)
+
+class ObjectChecker(ValueChecker):
+
+ def __init__(self, **params):
+ self.default = None
+ self.ref_class = params.get('ref_class', None)
+ if self.ref_class == None:
+ raise SDBPersistenceError('ref_class parameter is required')
+
+ def check(self, value):
+ if value == None:
+ return
+ if isinstance(value, str) or isinstance(value, unicode):
+ # ugly little hack - sometimes I want to just stick a UUID string
+ # in here rather than instantiate an object.
+ # This does a bit of hand waving to "type check" the string
+ t = value.split('-')
+ if len(t) != 5:
+ raise ValueError
+ else:
+ try:
+ obj_lineage = value.get_lineage()
+ cls_lineage = self.ref_class.get_lineage()
+ if obj_lineage.startswith(cls_lineage):
+ return
+ raise TypeError, '%s not instance of %s' % (obj_lineage, cls_lineage)
+ except:
+ raise ValueError, '%s is not an SDBObject' % value
+
+ def from_string(self, str_value, obj):
+ if not str_value:
+ return None
+ try:
+ return revive_object_from_id(str_value, obj._manager)
+ except:
+ raise ValueError, 'Unable to convert %s to Object' % str_value
+
+ def to_string(self, value):
+ self.check(value)
+ if isinstance(value, str) or isinstance(value, unicode):
+ return value
+ if value == None:
+ return ''
+ else:
+ return value.id
+
+class S3KeyChecker(ValueChecker):
+
+ def __init__(self, **params):
+ self.default = None
+
+ def check(self, value):
+ if value == None:
+ return
+ if isinstance(value, str) or isinstance(value, unicode):
+ try:
+ bucket_name, key_name = value.split('/', 1)
+ except:
+ raise ValueError
+ elif not isinstance(value, Key):
+ raise TypeError, 'Expecting Key, got %s' % type(value)
+
+ def from_string(self, str_value, obj):
+ if not str_value:
+ return None
+ if str_value == 'None':
+ return None
+ try:
+ bucket_name, key_name = str_value.split('/', 1)
+ if obj:
+ s3 = obj._manager.get_s3_connection()
+ bucket = s3.get_bucket(bucket_name)
+ key = bucket.get_key(key_name)
+ if not key:
+ key = bucket.new_key(key_name)
+ return key
+ except:
+ raise ValueError, 'Unable to convert %s to S3Key' % str_value
+
+ def to_string(self, value):
+ self.check(value)
+ if isinstance(value, str) or isinstance(value, unicode):
+ return value
+ if value == None:
+ return ''
+ else:
+ return '%s/%s' % (value.bucket.name, value.name)
+
+class S3BucketChecker(ValueChecker):
+
+ def __init__(self, **params):
+ self.default = None
+
+ def check(self, value):
+ if value == None:
+ return
+ if isinstance(value, str) or isinstance(value, unicode):
+ return
+ elif not isinstance(value, Bucket):
+ raise TypeError, 'Expecting Bucket, got %s' % type(value)
+
+ def from_string(self, str_value, obj):
+ if not str_value:
+ return None
+ if str_value == 'None':
+ return None
+ try:
+ if obj:
+ s3 = obj._manager.get_s3_connection()
+ bucket = s3.get_bucket(str_value)
+ return bucket
+ except:
+ raise ValueError, 'Unable to convert %s to S3Bucket' % str_value
+
+ def to_string(self, value):
+ self.check(value)
+ if value == None:
+ return ''
+ else:
+ return '%s' % value.name
+
diff --git a/vendor/boto/boto/sdb/persist/object.py b/vendor/boto/boto/sdb/persist/object.py
new file mode 100644
index 0000000000..993df1ee83
--- /dev/null
+++ b/vendor/boto/boto/sdb/persist/object.py
@@ -0,0 +1,207 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.exception import SDBPersistenceError
+from boto.sdb.persist import get_manager, object_lister
+from boto.sdb.persist.property import Property, ScalarProperty
+import uuid
+
+class SDBBase(type):
+ "Metaclass for all SDBObjects"
+ def __init__(cls, name, bases, dict):
+ super(SDBBase, cls).__init__(name, bases, dict)
+ # Make sure this is a subclass of SDBObject - mainly copied from django ModelBase (thanks!)
+ try:
+ if filter(lambda b: issubclass(b, SDBObject), bases):
+ # look for all of the Properties and set their names
+ for key in dict.keys():
+ if isinstance(dict[key], Property):
+ property = dict[key]
+ property.set_name(key)
+ prop_names = []
+ props = cls.properties()
+ for prop in props:
+ prop_names.append(prop.name)
+ setattr(cls, '_prop_names', prop_names)
+ except NameError:
+ # 'SDBObject' isn't defined yet, meaning we're looking at our own
+ # SDBObject class, defined below.
+ pass
+
+class SDBObject(object):
+ __metaclass__ = SDBBase
+
+ _manager = get_manager()
+
+ @classmethod
+ def get_lineage(cls):
+ l = [c.__name__ for c in cls.mro()]
+ l.reverse()
+ return '.'.join(l)
+
+ @classmethod
+ def get(cls, id=None, **params):
+ if params.has_key('manager'):
+ manager = params['manager']
+ else:
+ manager = cls._manager
+ if manager.domain and id:
+ a = cls._manager.domain.get_attributes(id, '__type__')
+ if a.has_key('__type__'):
+ return cls(id, manager)
+ else:
+ raise SDBPersistenceError('%s object with id=%s does not exist' % (cls.__name__, id))
+ else:
+ rs = cls.find(**params)
+ try:
+ obj = rs.next()
+ except StopIteration:
+ raise SDBPersistenceError('%s object matching query does not exist' % cls.__name__)
+ try:
+ rs.next()
+ except StopIteration:
+ return obj
+ raise SDBPersistenceError('Query matched more than 1 item')
+
+ @classmethod
+ def find(cls, **params):
+ if params.has_key('manager'):
+ manager = params['manager']
+ del params['manager']
+ else:
+ manager = cls._manager
+ keys = params.keys()
+ if len(keys) > 4:
+ raise SDBPersistenceError('Too many fields, max is 4')
+ parts = ["['__type__'='%s'] union ['__lineage__'starts-with'%s']" % (cls.__name__, cls.get_lineage())]
+ properties = cls.properties()
+ for key in keys:
+ found = False
+ for property in properties:
+ if property.name == key:
+ found = True
+ if isinstance(property, ScalarProperty):
+ checker = property.checker
+ parts.append("['%s' = '%s']" % (key, checker.to_string(params[key])))
+ else:
+ raise SDBPersistenceError('%s is not a searchable field' % key)
+ if not found:
+ raise SDBPersistenceError('%s is not a valid field' % key)
+ query = ' intersection '.join(parts)
+ if manager.domain:
+ rs = manager.domain.query(query)
+ else:
+ rs = []
+ return object_lister(None, rs, manager)
+
+ @classmethod
+ def list(cls, max_items=None, manager=None):
+ if not manager:
+ manager = cls._manager
+ if manager.domain:
+ rs = manager.domain.query("['__type__' = '%s']" % cls.__name__, max_items=max_items)
+ else:
+ rs = []
+ return object_lister(cls, rs, manager)
+
+ @classmethod
+ def properties(cls):
+ properties = []
+ while cls:
+ for key in cls.__dict__.keys():
+ if isinstance(cls.__dict__[key], Property):
+ properties.append(cls.__dict__[key])
+ if len(cls.__bases__) > 0:
+ cls = cls.__bases__[0]
+ else:
+ cls = None
+ return properties
+
+ # for backwards compatibility
+ find_properties = properties
+
+ def __init__(self, id=None, manager=None):
+ if manager:
+ self._manager = manager
+ self.id = id
+ if self.id:
+ self._auto_update = True
+ if self._manager.domain:
+ attrs = self._manager.domain.get_attributes(self.id, '__type__')
+ if len(attrs.keys()) == 0:
+ raise SDBPersistenceError('Object %s: not found' % self.id)
+ else:
+ self.id = str(uuid.uuid4())
+ self._auto_update = False
+
+ def __setattr__(self, name, value):
+ if name in self._prop_names:
+ object.__setattr__(self, name, value)
+ elif name.startswith('_'):
+ object.__setattr__(self, name, value)
+ elif name == 'id':
+ object.__setattr__(self, name, value)
+ else:
+ self._persist_attribute(name, value)
+ object.__setattr__(self, name, value)
+
+ def __getattr__(self, name):
+ if not name.startswith('_'):
+ a = self._manager.domain.get_attributes(self.id, name)
+ if a.has_key(name):
+ object.__setattr__(self, name, a[name])
+ return a[name]
+ raise AttributeError
+
+ def __repr__(self):
+ return '%s<%s>' % (self.__class__.__name__, self.id)
+
+ def _persist_attribute(self, name, value):
+ if self.id:
+ self._manager.domain.put_attributes(self.id, {name : value}, replace=True)
+
+ def _get_sdb_item(self):
+ return self._manager.domain.get_item(self.id)
+
+ def save(self):
+ attrs = {'__type__' : self.__class__.__name__,
+ '__module__' : self.__class__.__module__,
+ '__lineage__' : self.get_lineage()}
+ for property in self.properties():
+ attrs[property.name] = property.to_string(self)
+ if self._manager.domain:
+ self._manager.domain.put_attributes(self.id, attrs, replace=True)
+ self._auto_update = True
+
+ def delete(self):
+ if self._manager.domain:
+ self._manager.domain.delete_attributes(self.id)
+
+ def get_related_objects(self, ref_name, ref_cls=None):
+ if self._manager.domain:
+ query = "['%s' = '%s']" % (ref_name, self.id)
+ if ref_cls:
+ query += " intersection ['__type__'='%s']" % ref_cls.__name__
+ rs = self._manager.domain.query(query)
+ else:
+ rs = []
+ return object_lister(ref_cls, rs, self._manager)
+
diff --git a/vendor/boto/boto/sdb/persist/property.py b/vendor/boto/boto/sdb/persist/property.py
new file mode 100644
index 0000000000..4776d35d6d
--- /dev/null
+++ b/vendor/boto/boto/sdb/persist/property.py
@@ -0,0 +1,371 @@
+# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.exception import SDBPersistenceError
+from boto.sdb.persist.checker import StringChecker, PasswordChecker, IntegerChecker, BooleanChecker
+from boto.sdb.persist.checker import DateTimeChecker, ObjectChecker, S3KeyChecker, S3BucketChecker
+from boto.utils import Password
+
+class Property(object):
+
+ def __init__(self, checker_class, **params):
+ self.name = ''
+ self.checker = checker_class(**params)
+ self.slot_name = '__'
+
+ def set_name(self, name):
+ self.name = name
+ self.slot_name = '__' + self.name
+
+class ScalarProperty(Property):
+
+ def save(self, obj):
+ domain = obj._manager.domain
+ domain.put_attributes(obj.id, {self.name : self.to_string(obj)}, replace=True)
+
+ def to_string(self, obj):
+ return self.checker.to_string(getattr(obj, self.name))
+
+ def load(self, obj):
+ domain = obj._manager.domain
+ a = domain.get_attributes(obj.id, self.name)
+ # try to get the attribute value from SDB
+ if self.name in a:
+ value = self.checker.from_string(a[self.name], obj)
+ setattr(obj, self.slot_name, value)
+ # if it's not there, set the value to the default value
+ else:
+ self.__set__(obj, self.checker.default)
+
+ def __get__(self, obj, objtype):
+ if obj:
+ try:
+ value = getattr(obj, self.slot_name)
+ except AttributeError:
+ if obj._auto_update:
+ self.load(obj)
+ value = getattr(obj, self.slot_name)
+ else:
+ value = self.checker.default
+ setattr(obj, self.slot_name, self.checker.default)
+ return value
+
+ def __set__(self, obj, value):
+ self.checker.check(value)
+ try:
+ old_value = getattr(obj, self.slot_name)
+ except:
+ old_value = self.checker.default
+ setattr(obj, self.slot_name, value)
+ if obj._auto_update:
+ try:
+ self.save(obj)
+ except:
+ setattr(obj, self.slot_name, old_value)
+ raise
+
+class StringProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ ScalarProperty.__init__(self, StringChecker, **params)
+
+class PasswordProperty(ScalarProperty):
+ """
+ Hashed password
+ """
+
+ def __init__(self, **params):
+ ScalarProperty.__init__(self, PasswordChecker, **params)
+
+ def __set__(self, obj, value):
+ p = Password()
+ p.set(value)
+ ScalarProperty.__set__(self, obj, p)
+
+ def __get__(self, obj, objtype):
+ return Password(ScalarProperty.__get__(self, obj, objtype))
+
+class SmallPositiveIntegerProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'small'
+ params['signed'] = False
+ ScalarProperty.__init__(self, IntegerChecker, **params)
+
+class SmallIntegerProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'small'
+ params['signed'] = True
+ ScalarProperty.__init__(self, IntegerChecker, **params)
+
+class PositiveIntegerProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'medium'
+ params['signed'] = False
+ ScalarProperty.__init__(self, IntegerChecker, **params)
+
+class IntegerProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'medium'
+ params['signed'] = True
+ ScalarProperty.__init__(self, IntegerChecker, **params)
+
+class LargePositiveIntegerProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'large'
+ params['signed'] = False
+ ScalarProperty.__init__(self, IntegerChecker, **params)
+
+class LargeIntegerProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'large'
+ params['signed'] = True
+ ScalarProperty.__init__(self, IntegerChecker, **params)
+
+class BooleanProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ ScalarProperty.__init__(self, BooleanChecker, **params)
+
+class DateTimeProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ ScalarProperty.__init__(self, DateTimeChecker, **params)
+
+class ObjectProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ ScalarProperty.__init__(self, ObjectChecker, **params)
+
+class S3KeyProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ ScalarProperty.__init__(self, S3KeyChecker, **params)
+
+ def __set__(self, obj, value):
+ self.checker.check(value)
+ try:
+ old_value = getattr(obj, self.slot_name)
+ except:
+ old_value = self.checker.default
+ if isinstance(value, str):
+ value = self.checker.from_string(value, obj)
+ setattr(obj, self.slot_name, value)
+ if obj._auto_update:
+ try:
+ self.save(obj)
+ except:
+ setattr(obj, self.slot_name, old_value)
+ raise
+
+class S3BucketProperty(ScalarProperty):
+
+ def __init__(self, **params):
+ ScalarProperty.__init__(self, S3BucketChecker, **params)
+
+ def __set__(self, obj, value):
+ self.checker.check(value)
+ try:
+ old_value = getattr(obj, self.slot_name)
+ except:
+ old_value = self.checker.default
+ if isinstance(value, str):
+ value = self.checker.from_string(value, obj)
+ setattr(obj, self.slot_name, value)
+ if obj._auto_update:
+ try:
+ self.save(obj)
+ except:
+ setattr(obj, self.slot_name, old_value)
+ raise
+
+class MultiValueProperty(Property):
+
+ def __init__(self, checker_class, **params):
+ Property.__init__(self, checker_class, **params)
+
+ def __get__(self, obj, objtype):
+ if obj:
+ try:
+ value = getattr(obj, self.slot_name)
+ except AttributeError:
+ if obj._auto_update:
+ self.load(obj)
+ value = getattr(obj, self.slot_name)
+ else:
+ value = MultiValue(self, obj, [])
+ setattr(obj, self.slot_name, value)
+ return value
+
+ def load(self, obj):
+ if obj != None:
+ _list = []
+ domain = obj._manager.domain
+ a = domain.get_attributes(obj.id, self.name)
+ if self.name in a:
+ lst = a[self.name]
+ if not isinstance(lst, list):
+ lst = [lst]
+ for value in lst:
+ value = self.checker.from_string(value, obj)
+ _list.append(value)
+ setattr(obj, self.slot_name, MultiValue(self, obj, _list))
+
+ def __set__(self, obj, value):
+ if not isinstance(value, list):
+ raise SDBPersistenceError('Value must be a list')
+ setattr(obj, self.slot_name, MultiValue(self, obj, value))
+ str_list = self.to_string(obj)
+ domain = obj._manager.domain
+ if obj._auto_update:
+ if len(str_list) == 1:
+ domain.put_attributes(obj.id, {self.name : str_list[0]}, replace=True)
+ else:
+ try:
+ self.__delete__(obj)
+ except:
+ pass
+ domain.put_attributes(obj.id, {self.name : str_list}, replace=True)
+ setattr(obj, self.slot_name, MultiValue(self, obj, value))
+
+ def __delete__(self, obj):
+ if obj._auto_update:
+ domain = obj._manager.domain
+ domain.delete_attributes(obj.id, [self.name])
+ setattr(obj, self.slot_name, MultiValue(self, obj, []))
+
+ def to_string(self, obj):
+ str_list = []
+ for value in self.__get__(obj, type(obj)):
+ str_list.append(self.checker.to_string(value))
+ return str_list
+
+class StringListProperty(MultiValueProperty):
+
+ def __init__(self, **params):
+ MultiValueProperty.__init__(self, StringChecker, **params)
+
+class SmallIntegerListProperty(MultiValueProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'small'
+ params['signed'] = True
+ MultiValueProperty.__init__(self, IntegerChecker, **params)
+
+class SmallPositiveIntegerListProperty(MultiValueProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'small'
+ params['signed'] = False
+ MultiValueProperty.__init__(self, IntegerChecker, **params)
+
+class IntegerListProperty(MultiValueProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'medium'
+ params['signed'] = True
+ MultiValueProperty.__init__(self, IntegerChecker, **params)
+
+class PositiveIntegerListProperty(MultiValueProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'medium'
+ params['signed'] = False
+ MultiValueProperty.__init__(self, IntegerChecker, **params)
+
+class LargeIntegerListProperty(MultiValueProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'large'
+ params['signed'] = True
+ MultiValueProperty.__init__(self, IntegerChecker, **params)
+
+class LargePositiveIntegerListProperty(MultiValueProperty):
+
+ def __init__(self, **params):
+ params['size'] = 'large'
+ params['signed'] = False
+ MultiValueProperty.__init__(self, IntegerChecker, **params)
+
+class BooleanListProperty(MultiValueProperty):
+
+ def __init__(self, **params):
+ MultiValueProperty.__init__(self, BooleanChecker, **params)
+
+class ObjectListProperty(MultiValueProperty):
+
+ def __init__(self, **params):
+ MultiValueProperty.__init__(self, ObjectChecker, **params)
+
+class HasManyProperty(Property):
+
+ def set_name(self, name):
+ self.name = name
+ self.slot_name = '__' + self.name
+
+ def __get__(self, obj, objtype):
+ return self
+
+
+class MultiValue:
+ """
+ Special Multi Value for boto persistence layer to allow us to do
+ obj.list.append(foo)
+ """
+ def __init__(self, property, obj, _list):
+ self.checker = property.checker
+ self.name = property.name
+ self.object = obj
+ self._list = _list
+
+ def __repr__(self):
+ return repr(self._list)
+
+ def __getitem__(self, key):
+ return self._list.__getitem__(key)
+
+ def __delitem__(self, key):
+ item = self[key]
+ self._list.__delitem__(key)
+ domain = self.object._manager.domain
+ domain.delete_attributes(self.object.id, {self.name: [self.checker.to_string(item)]})
+
+ def __len__(self):
+ return len(self._list)
+
+ def append(self, value):
+ self.checker.check(value)
+ self._list.append(value)
+ domain = self.object._manager.domain
+ domain.put_attributes(self.object.id, {self.name: self.checker.to_string(value)}, replace=False)
+
+ def index(self, value):
+ for x in self._list:
+ if x.id == value.id:
+ return self._list.index(x)
+
+ def remove(self, value):
+ del(self[self.index(value)])
diff --git a/vendor/boto/boto/sdb/persist/test_persist.py b/vendor/boto/boto/sdb/persist/test_persist.py
new file mode 100644
index 0000000000..080935d312
--- /dev/null
+++ b/vendor/boto/boto/sdb/persist/test_persist.py
@@ -0,0 +1,141 @@
+from boto.sdb.persist.object import SDBObject
+from boto.sdb.persist.property import StringProperty, PositiveIntegerProperty, IntegerProperty
+from boto.sdb.persist.property import BooleanProperty, DateTimeProperty, S3KeyProperty
+from boto.sdb.persist.property import ObjectProperty, StringListProperty
+from boto.sdb.persist.property import PositiveIntegerListProperty, BooleanListProperty, ObjectListProperty
+from boto.sdb.persist import Manager
+from datetime import datetime
+import time
+
+#
+# This will eventually be moved to the boto.tests module and become a real unit test
+# but for now it will live here. It shows examples of each of the Property types in
+# use and tests the basic operations.
+#
+class TestScalar(SDBObject):
+
+ name = StringProperty()
+ description = StringProperty()
+ size = PositiveIntegerProperty()
+ offset = IntegerProperty()
+ foo = BooleanProperty()
+ date = DateTimeProperty()
+ file = S3KeyProperty()
+
+class TestRef(SDBObject):
+
+ name = StringProperty()
+ ref = ObjectProperty(ref_class=TestScalar)
+
+class TestSubClass1(TestRef):
+
+ answer = PositiveIntegerProperty()
+
+class TestSubClass2(TestScalar):
+
+ flag = BooleanProperty()
+
+class TestList(SDBObject):
+
+ names = StringListProperty()
+ numbers = PositiveIntegerListProperty()
+ bools = BooleanListProperty()
+ objects = ObjectListProperty(ref_class=TestScalar)
+
+def test1():
+ s = TestScalar()
+ s.name = 'foo'
+ s.description = 'This is foo'
+ s.size = 42
+ s.offset = -100
+ s.foo = True
+ s.date = datetime.now()
+ s.save()
+ return s
+
+def test2(ref_name):
+ s = TestRef()
+ s.name = 'testref'
+ rs = TestScalar.find(name=ref_name)
+ s.ref = rs.next()
+ s.save()
+ return s
+
+def test3():
+ s = TestScalar()
+ s.name = 'bar'
+ s.description = 'This is bar'
+ s.size = 24
+ s.foo = False
+ s.date = datetime.now()
+ s.save()
+ return s
+
+def test4(ref1, ref2):
+ s = TestList()
+ s.names.append(ref1.name)
+ s.names.append(ref2.name)
+ s.numbers.append(ref1.size)
+ s.numbers.append(ref2.size)
+ s.bools.append(ref1.foo)
+ s.bools.append(ref2.foo)
+ s.objects.append(ref1)
+ s.objects.append(ref2)
+ s.save()
+ return s
+
+def test5(ref):
+ s = TestSubClass1()
+ s.answer = 42
+ s.ref = ref
+ s.save()
+ # test out free form attribute
+ s.fiddlefaddle = 'this is fiddlefaddle'
+ s._fiddlefaddle = 'this is not fiddlefaddle'
+ return s
+
+def test6():
+ s = TestSubClass2()
+ s.name = 'fie'
+ s.description = 'This is fie'
+ s.size = 4200
+ s.offset = -820
+ s.foo = False
+ s.date = datetime.now()
+ s.flag = True
+ s.save()
+ return s
+
+def test(domain_name):
+ print 'Initialize the Persistance system'
+ Manager.DefaultDomainName = domain_name
+ print 'Call test1'
+ s1 = test1()
+ # now create a new instance and read the saved data from SDB
+ print 'Now sleep to wait for things to converge'
+ time.sleep(5)
+ print 'Now lookup the object and compare the fields'
+ s2 = TestScalar(s1.id)
+ assert s1.name == s2.name
+ assert s1.description == s2.description
+ assert s1.size == s2.size
+ assert s1.offset == s2.offset
+ assert s1.foo == s2.foo
+ #assert s1.date == s2.date
+ print 'Call test2'
+ s2 = test2(s1.name)
+ print 'Call test3'
+ s3 = test3()
+ print 'Call test4'
+ s4 = test4(s1, s3)
+ print 'Call test5'
+ s6 = test6()
+ s5 = test5(s6)
+ domain = s5._manager.domain
+ item1 = domain.get_item(s1.id)
+ item2 = domain.get_item(s2.id)
+ item3 = domain.get_item(s3.id)
+ item4 = domain.get_item(s4.id)
+ item5 = domain.get_item(s5.id)
+ item6 = domain.get_item(s6.id)
+ return [(s1, item1), (s2, item2), (s3, item3), (s4, item4), (s5, item5), (s6, item6)]
diff --git a/vendor/boto/boto/sdb/queryresultset.py b/vendor/boto/boto/sdb/queryresultset.py
new file mode 100644
index 0000000000..10bafd1c92
--- /dev/null
+++ b/vendor/boto/boto/sdb/queryresultset.py
@@ -0,0 +1,92 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+def query_lister(domain, query='', max_items=None, attr_names=None):
+ more_results = True
+ num_results = 0
+ next_token = None
+ while more_results:
+ rs = domain.connection.query_with_attributes(domain, query, attr_names,
+ next_token=next_token)
+ for item in rs:
+ if max_items:
+ if num_results == max_items:
+ raise StopIteration
+ yield item
+ num_results += 1
+ next_token = rs.next_token
+ more_results = next_token != None
+
+class QueryResultSet:
+
+ def __init__(self, domain=None, query='', max_items=None, attr_names=None):
+ self.max_items = max_items
+ self.domain = domain
+ self.query = query
+ self.attr_names = attr_names
+
+ def __iter__(self):
+ return query_lister(self.domain, self.query, self.max_items, self.attr_names)
+
+def select_lister(domain, query='', max_items=None):
+ more_results = True
+ num_results = 0
+ next_token = None
+ while more_results:
+ rs = domain.connection.select(domain, query, next_token=next_token)
+ for item in rs:
+ if max_items:
+ if num_results == max_items:
+ raise StopIteration
+ yield item
+ num_results += 1
+ next_token = rs.next_token
+ more_results = next_token != None
+
+class SelectResultSet(object):
+
+ def __init__(self, domain=None, query='', max_items=None,
+ next_token=None, consistent_read=False):
+ self.domain = domain
+ self.query = query
+ self.consistent_read = consistent_read
+ self.max_items = max_items
+ self.next_token = next_token
+
+ def __iter__(self):
+ more_results = True
+ num_results = 0
+ while more_results:
+ rs = self.domain.connection.select(self.domain, self.query,
+ next_token=self.next_token,
+ consistent_read=self.consistent_read)
+ for item in rs:
+ if self.max_items and num_results >= self.max_items:
+ raise StopIteration
+ yield item
+ num_results += 1
+ self.next_token = rs.next_token
+ if self.max_items and num_results >= self.max_items:
+ raise StopIteration
+ more_results = self.next_token != None
+
+ def next(self):
+ return self.__iter__().next()
diff --git a/vendor/boto/boto/sdb/regioninfo.py b/vendor/boto/boto/sdb/regioninfo.py
new file mode 100644
index 0000000000..bff9dea61d
--- /dev/null
+++ b/vendor/boto/boto/sdb/regioninfo.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+from boto.ec2.regioninfo import RegionInfo
+
+class SDBRegionInfo(RegionInfo):
+
+ def connect(self, **kw_params):
+ """
+ Connect to this Region's endpoint. Returns an SDBConnection
+ object pointing to the endpoint associated with this region.
+ You may pass any of the arguments accepted by the SDBConnection
+ object's constructor as keyword arguments and they will be
+ passed along to the SDBConnection object.
+
+ :rtype: :class:`boto.sdb.connection.SDBConnection`
+ :return: The connection to this regions endpoint
+ """
+ from boto.sdb.connection import SDBConnection
+ return SDBConnection(region=self, **kw_params)
+
diff --git a/vendor/boto/boto/services/__init__.py b/vendor/boto/boto/services/__init__.py
new file mode 100644
index 0000000000..449bd162a8
--- /dev/null
+++ b/vendor/boto/boto/services/__init__.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+
diff --git a/vendor/boto/boto/services/bs.py b/vendor/boto/boto/services/bs.py
new file mode 100755
index 0000000000..3d700315db
--- /dev/null
+++ b/vendor/boto/boto/services/bs.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+from optparse import OptionParser
+from boto.services.servicedef import ServiceDef
+from boto.services.submit import Submitter
+from boto.services.result import ResultProcessor
+import boto
+import sys, os, StringIO
+
+class BS(object):
+
+ Usage = "usage: %prog [options] config_file command"
+
+ Commands = {'reset' : 'Clear input queue and output bucket',
+ 'submit' : 'Submit local files to the service',
+ 'start' : 'Start the service',
+ 'status' : 'Report on the status of the service buckets and queues',
+ 'retrieve' : 'Retrieve output generated by a batch',
+ 'batches' : 'List all batches stored in current output_domain'}
+
+ def __init__(self):
+ self.service_name = None
+ self.parser = OptionParser(usage=self.Usage)
+ self.parser.add_option("--help-commands", action="store_true", dest="help_commands",
+ help="provides help on the available commands")
+ self.parser.add_option("-a", "--access-key", action="store", type="string",
+ help="your AWS Access Key")
+ self.parser.add_option("-s", "--secret-key", action="store", type="string",
+ help="your AWS Secret Access Key")
+ self.parser.add_option("-p", "--path", action="store", type="string", dest="path",
+ help="the path to local directory for submit and retrieve")
+ self.parser.add_option("-k", "--keypair", action="store", type="string", dest="keypair",
+ help="the SSH keypair used with launched instance(s)")
+ self.parser.add_option("-l", "--leave", action="store_true", dest="leave",
+ help="leave the files (don't retrieve) files during retrieve command")
+ self.parser.set_defaults(leave=False)
+ self.parser.add_option("-n", "--num-instances", action="store", type="string", dest="num_instances",
+ help="the number of launched instance(s)")
+ self.parser.set_defaults(num_instances=1)
+ self.parser.add_option("-i", "--ignore-dirs", action="append", type="string", dest="ignore",
+ help="directories that should be ignored by submit command")
+ self.parser.add_option("-b", "--batch-id", action="store", type="string", dest="batch",
+ help="batch identifier required by the retrieve command")
+
+ def print_command_help(self):
+ print '\nCommands:'
+ for key in self.Commands.keys():
+ print ' %s\t\t%s' % (key, self.Commands[key])
+
+ def do_reset(self):
+ iq = self.sd.get_obj('input_queue')
+ if iq:
+ print 'clearing out input queue'
+ i = 0
+ m = iq.read()
+ while m:
+ i += 1
+ iq.delete_message(m)
+ m = iq.read()
+ print 'deleted %d messages' % i
+ ob = self.sd.get_obj('output_bucket')
+ ib = self.sd.get_obj('input_bucket')
+ if ob:
+ if ib and ob.name == ib.name:
+ return
+ print 'delete generated files in output bucket'
+ i = 0
+ for k in ob:
+ i += 1
+ k.delete()
+ print 'deleted %d keys' % i
+
+ def do_submit(self):
+ if not self.options.path:
+ self.parser.error('No path provided')
+ if not os.path.exists(self.options.path):
+ self.parser.error('Invalid path (%s)' % self.options.path)
+ s = Submitter(self.sd)
+ t = s.submit_path(self.options.path, None, self.options.ignore, None,
+ None, True, self.options.path)
+ print 'A total of %d files were submitted' % t[1]
+ print 'Batch Identifier: %s' % t[0]
+
+ def do_start(self):
+ ami_id = self.sd.get('ami_id')
+ instance_type = self.sd.get('instance_type', 'm1.small')
+ security_group = self.sd.get('security_group', 'default')
+ if not ami_id:
+ self.parser.error('ami_id option is required when starting the service')
+ ec2 = boto.connect_ec2()
+ if not self.sd.has_section('Credentials'):
+ self.sd.add_section('Credentials')
+ self.sd.set('Credentials', 'aws_access_key_id', ec2.aws_access_key_id)
+ self.sd.set('Credentials', 'aws_secret_access_key', ec2.aws_secret_access_key)
+ s = StringIO.StringIO()
+ self.sd.write(s)
+ rs = ec2.get_all_images([ami_id])
+ img = rs[0]
+ r = img.run(user_data=s.getvalue(), key_name=self.options.keypair,
+ max_count=self.options.num_instances,
+ instance_type=instance_type,
+ security_groups=[security_group])
+ print 'Starting AMI: %s' % ami_id
+ print 'Reservation %s contains the following instances:' % r.id
+ for i in r.instances:
+ print '\t%s' % i.id
+
+ def do_status(self):
+ iq = self.sd.get_obj('input_queue')
+ if iq:
+ print 'The input_queue (%s) contains approximately %s messages' % (iq.id, iq.count())
+ ob = self.sd.get_obj('output_bucket')
+ ib = self.sd.get_obj('input_bucket')
+ if ob:
+ if ib and ob.name == ib.name:
+ return
+ total = 0
+ for k in ob:
+ total += 1
+ print 'The output_bucket (%s) contains %d keys' % (ob.name, total)
+
+ def do_retrieve(self):
+ if not self.options.path:
+ self.parser.error('No path provided')
+ if not os.path.exists(self.options.path):
+ self.parser.error('Invalid path (%s)' % self.options.path)
+ if not self.options.batch:
+ self.parser.error('batch identifier is required for retrieve command')
+ s = ResultProcessor(self.options.batch, self.sd)
+ s.get_results(self.options.path, get_file=(not self.options.leave))
+
+ def do_batches(self):
+ d = self.sd.get_obj('output_domain')
+ if d:
+ print 'Available Batches:'
+ rs = d.query("['type'='Batch']")
+ for item in rs:
+ print ' %s' % item.name
+ else:
+ self.parser.error('No output_domain specified for service')
+
+ def main(self):
+ self.options, self.args = self.parser.parse_args()
+ if self.options.help_commands:
+ self.print_command_help()
+ sys.exit(0)
+ if len(self.args) != 2:
+ self.parser.error("config_file and command are required")
+ self.config_file = self.args[0]
+ self.sd = ServiceDef(self.config_file)
+ self.command = self.args[1]
+ if hasattr(self, 'do_%s' % self.command):
+ method = getattr(self, 'do_%s' % self.command)
+ method()
+ else:
+ self.parser.error('command (%s) not recognized' % self.command)
+
+if __name__ == "__main__":
+ bs = BS()
+ bs.main()
diff --git a/vendor/boto/boto/services/message.py b/vendor/boto/boto/services/message.py
new file mode 100644
index 0000000000..79f6d19f66
--- /dev/null
+++ b/vendor/boto/boto/services/message.py
@@ -0,0 +1,58 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.sqs.message import MHMessage
+from boto.utils import get_ts
+from socket import gethostname
+import os, mimetypes, time
+
+class ServiceMessage(MHMessage):
+
+ def for_key(self, key, params=None, bucket_name=None):
+ if params:
+ self.update(params)
+ if key.path:
+ t = os.path.split(key.path)
+ self['OriginalLocation'] = t[0]
+ self['OriginalFileName'] = t[1]
+ mime_type = mimetypes.guess_type(t[1])[0]
+ if mime_type == None:
+ mime_type = 'application/octet-stream'
+ self['Content-Type'] = mime_type
+ s = os.stat(key.path)
+ t = time.gmtime(s[7])
+ self['FileAccessedDate'] = get_ts(t)
+ t = time.gmtime(s[8])
+ self['FileModifiedDate'] = get_ts(t)
+ t = time.gmtime(s[9])
+ self['FileCreateDate'] = get_ts(t)
+ else:
+ self['OriginalFileName'] = key.name
+ self['OriginalLocation'] = key.bucket.name
+ self['ContentType'] = key.content_type
+ self['Host'] = gethostname()
+ if bucket_name:
+ self['Bucket'] = bucket_name
+ else:
+ self['Bucket'] = key.bucket.name
+ self['InputKey'] = key.name
+ self['Size'] = key.size
+
diff --git a/vendor/boto/boto/services/result.py b/vendor/boto/boto/services/result.py
new file mode 100644
index 0000000000..f6c440719b
--- /dev/null
+++ b/vendor/boto/boto/services/result.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import os
+from datetime import datetime, timedelta
+from boto.utils import parse_ts
+import boto
+
+class ResultProcessor:
+
+ LogFileName = 'log.csv'
+
+ def __init__(self, batch_name, sd, mimetype_files=None):
+ self.sd = sd
+ self.batch = batch_name
+ self.log_fp = None
+ self.num_files = 0
+ self.total_time = 0
+ self.min_time = timedelta.max
+ self.max_time = timedelta.min
+ self.earliest_time = datetime.max
+ self.latest_time = datetime.min
+ self.queue = self.sd.get_obj('output_queue')
+ self.domain = self.sd.get_obj('output_domain')
+
+ def calculate_stats(self, msg):
+ start_time = parse_ts(msg['Service-Read'])
+ end_time = parse_ts(msg['Service-Write'])
+ elapsed_time = end_time - start_time
+ if elapsed_time > self.max_time:
+ self.max_time = elapsed_time
+ if elapsed_time < self.min_time:
+ self.min_time = elapsed_time
+ self.total_time += elapsed_time.seconds
+ if start_time < self.earliest_time:
+ self.earliest_time = start_time
+ if end_time > self.latest_time:
+ self.latest_time = end_time
+
+ def log_message(self, msg, path):
+ keys = msg.keys()
+ keys.sort()
+ if not self.log_fp:
+ self.log_fp = open(os.path.join(path, self.LogFileName), 'w')
+ line = ','.join(keys)
+ self.log_fp.write(line+'\n')
+ values = []
+ for key in keys:
+ value = msg[key]
+ if value.find(',') > 0:
+ value = '"%s"' % value
+ values.append(value)
+ line = ','.join(values)
+ self.log_fp.write(line+'\n')
+
+ def process_record(self, record, path, get_file=True):
+ self.log_message(record, path)
+ self.calculate_stats(record)
+ outputs = record['OutputKey'].split(',')
+ if record.has_key('OutputBucket'):
+ bucket = boto.lookup('s3', record['OutputBucket'])
+ else:
+ bucket = boto.lookup('s3', record['Bucket'])
+ for output in outputs:
+ if get_file:
+ key_name = output.split(';')[0]
+ key = bucket.lookup(key_name)
+ file_name = os.path.join(path, key_name)
+ print 'retrieving file: %s to %s' % (key_name, file_name)
+ key.get_contents_to_filename(file_name)
+ self.num_files += 1
+
+ def get_results_from_queue(self, path, get_file=True, delete_msg=True):
+ m = self.queue.read()
+ while m:
+ if m.has_key('Batch') and m['Batch'] == self.batch:
+ self.process_record(m, path, get_file)
+ if delete_msg:
+ self.queue.delete_message(m)
+ m = self.queue.read()
+
+ def get_results_from_domain(self, path, get_file=True):
+ rs = self.domain.query("['Batch'='%s']" % self.batch)
+ for item in rs:
+ self.process_record(item, path, get_file)
+
+ def get_results_from_bucket(self, path):
+ bucket = self.sd.get_obj('output_bucket')
+ if bucket:
+ print 'No output queue or domain, just retrieving files from output_bucket'
+ for key in bucket:
+ file_name = os.path.join(path, key)
+ print 'retrieving file: %s to %s' % (key, file_name)
+ key.get_contents_to_filename(file_name)
+ self.num_files + 1
+
+ def get_results(self, path, get_file=True, delete_msg=True):
+ if not os.path.isdir(path):
+ os.mkdir(path)
+ if self.queue:
+ self.get_results_from_queue(path, get_file)
+ elif self.domain:
+ self.get_results_from_domain(path, get_file)
+ else:
+ self.get_results_from_bucket(path)
+ if self.log_fp:
+ self.log_fp.close()
+ print '%d results successfully retrieved.' % self.num_files
+ if self.num_files > 0:
+ self.avg_time = float(self.total_time)/self.num_files
+ print 'Minimum Processing Time: %d' % self.min_time.seconds
+ print 'Maximum Processing Time: %d' % self.max_time.seconds
+ print 'Average Processing Time: %f' % self.avg_time
+ self.elapsed_time = self.latest_time-self.earliest_time
+ print 'Elapsed Time: %d' % self.elapsed_time.seconds
+ tput = 1.0 / ((self.elapsed_time.seconds/60.0) / self.num_files)
+ print 'Throughput: %f transactions / minute' % tput
+
diff --git a/vendor/boto/boto/services/service.py b/vendor/boto/boto/services/service.py
new file mode 100644
index 0000000000..8ee1a8beed
--- /dev/null
+++ b/vendor/boto/boto/services/service.py
@@ -0,0 +1,161 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import boto
+from boto.services.message import ServiceMessage
+from boto.services.servicedef import ServiceDef
+from boto.pyami.scriptbase import ScriptBase
+from boto.utils import get_ts
+import time
+import os
+import mimetypes
+
+
+class Service(ScriptBase):
+
+ # Time required to process a transaction
+ ProcessingTime = 60
+
+ def __init__(self, config_file=None, mimetype_files=None):
+ ScriptBase.__init__(self, config_file)
+ self.name = self.__class__.__name__
+ self.working_dir = boto.config.get('Pyami', 'working_dir')
+ self.sd = ServiceDef(config_file)
+ self.retry_count = self.sd.getint('retry_count', 5)
+ self.loop_delay = self.sd.getint('loop_delay', 30)
+ self.processing_time = self.sd.getint('processing_time', 60)
+ self.input_queue = self.sd.get_obj('input_queue')
+ self.output_queue = self.sd.get_obj('output_queue')
+ self.output_domain = self.sd.get_obj('output_domain')
+ if mimetype_files:
+ mimetypes.init(mimetype_files)
+
+ def split_key(key):
+ if key.find(';') < 0:
+ t = (key, '')
+ else:
+ key, type = key.split(';')
+ label, mtype = type.split('=')
+ t = (key, mtype)
+ return t
+
+ def read_message(self):
+ boto.log.info('read_message')
+ message = self.input_queue.read(self.processing_time)
+ if message:
+ boto.log.info(message.get_body())
+ key = 'Service-Read'
+ message[key] = get_ts()
+ return message
+
+ # retrieve the source file from S3
+ def get_file(self, message):
+ bucket_name = message['Bucket']
+ key_name = message['InputKey']
+ file_name = os.path.join(self.working_dir, message.get('OriginalFileName', 'in_file'))
+ boto.log.info('get_file: %s/%s to %s' % (bucket_name, key_name, file_name))
+ bucket = boto.lookup('s3', bucket_name)
+ key = bucket.new_key(key_name)
+ key.get_contents_to_filename(os.path.join(self.working_dir, file_name))
+ return file_name
+
+ # process source file, return list of output files
+ def process_file(self, in_file_name, msg):
+ return []
+
+ # store result file in S3
+ def put_file(self, bucket_name, file_path, key_name=None):
+ boto.log.info('putting file %s as %s.%s' % (file_path, bucket_name, key_name))
+ bucket = boto.lookup('s3', bucket_name)
+ key = bucket.new_key(key_name)
+ key.set_contents_from_filename(file_path)
+ return key
+
+ def save_results(self, results, input_message, output_message):
+ output_keys = []
+ for file, type in results:
+ if input_message.has_key('OutputBucket'):
+ output_bucket = input_message['OutputBucket']
+ else:
+ output_bucket = input_message['Bucket']
+ key_name = os.path.split(file)[1]
+ key = self.put_file(output_bucket, file, key_name)
+ output_keys.append('%s;type=%s' % (key.name, type))
+ output_message['OutputKey'] = ','.join(output_keys)
+
+ # write message to each output queue
+ def write_message(self, message):
+ message['Service-Write'] = get_ts()
+ message['Server'] = self.name
+ if os.environ.has_key('HOSTNAME'):
+ message['Host'] = os.environ['HOSTNAME']
+ else:
+ message['Host'] = 'unknown'
+ message['Instance-ID'] = self.instance_id
+ if self.output_queue:
+ boto.log.info('Writing message to SQS queue: %s' % self.output_queue.id)
+ self.output_queue.write(message)
+ if self.output_domain:
+ boto.log.info('Writing message to SDB domain: %s' % self.output_domain.name)
+ item_name = '/'.join([message['Service-Write'], message['Bucket'], message['InputKey']])
+ self.output_domain.put_attributes(item_name, message)
+
+ # delete message from input queue
+ def delete_message(self, message):
+ boto.log.info('deleting message from %s' % self.input_queue.id)
+ self.input_queue.delete_message(message)
+
+ # to clean up any files, etc. after each iteration
+ def cleanup(self):
+ pass
+
+ def shutdown(self):
+ on_completion = self.sd.get('on_completion', 'shutdown')
+ if on_completion == 'shutdown':
+ if self.instance_id:
+ time.sleep(60)
+ c = boto.connect_ec2()
+ c.terminate_instances([self.instance_id])
+
+ def main(self, notify=False):
+ self.notify('Service: %s Starting' % self.name)
+ empty_reads = 0
+ while self.retry_count < 0 or empty_reads < self.retry_count:
+ try:
+ input_message = self.read_message()
+ if input_message:
+ empty_reads = 0
+ output_message = ServiceMessage(None, input_message.get_body())
+ input_file = self.get_file(input_message)
+ results = self.process_file(input_file, output_message)
+ self.save_results(results, input_message, output_message)
+ self.write_message(output_message)
+ self.delete_message(input_message)
+ self.cleanup()
+ else:
+ empty_reads += 1
+ time.sleep(self.loop_delay)
+ except Exception:
+ boto.log.exception('Service Failed')
+ empty_reads += 1
+ self.notify('Service: %s Shutting Down' % self.name)
+ self.shutdown()
+
diff --git a/vendor/boto/boto/services/servicedef.py b/vendor/boto/boto/services/servicedef.py
new file mode 100644
index 0000000000..1cb01aa754
--- /dev/null
+++ b/vendor/boto/boto/services/servicedef.py
@@ -0,0 +1,91 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.pyami.config import Config
+from boto.services.message import ServiceMessage
+import boto
+
+class ServiceDef(Config):
+
+ def __init__(self, config_file, aws_access_key_id=None, aws_secret_access_key=None):
+ Config.__init__(self, config_file)
+ self.aws_access_key_id = aws_access_key_id
+ self.aws_secret_access_key = aws_secret_access_key
+ script = Config.get(self, 'Pyami', 'scripts')
+ if script:
+ self.name = script.split('.')[-1]
+ else:
+ self.name = None
+
+
+ def get(self, name, default=None):
+ return Config.get(self, self.name, name, default)
+
+ def has_option(self, option):
+ return Config.has_option(self, self.name, option)
+
+ def getint(self, option, default=0):
+ try:
+ val = Config.get(self, self.name, option)
+ val = int(val)
+ except:
+ val = int(default)
+ return val
+
+ def getbool(self, option, default=False):
+ try:
+ val = Config.get(self, self.name, option)
+ if val.lower() == 'true':
+ val = True
+ else:
+ val = False
+ except:
+ val = default
+ return val
+
+ def get_obj(self, name):
+ """
+ Returns the AWS object associated with a given option.
+
+ The heuristics used are a bit lame. If the option name contains
+ the word 'bucket' it is assumed to be an S3 bucket, if the name
+ contains the word 'queue' it is assumed to be an SQS queue and
+ if it contains the word 'domain' it is assumed to be a SimpleDB
+ domain. If the option name specified does not exist in the
+ config file or if the AWS object cannot be retrieved this
+ returns None.
+ """
+ val = self.get(name)
+ if not val:
+ return None
+ if name.find('queue') >= 0:
+ obj = boto.lookup('sqs', val)
+ if obj:
+ obj.set_message_class(ServiceMessage)
+ elif name.find('bucket') >= 0:
+ obj = boto.lookup('s3', val)
+ elif name.find('domain') >= 0:
+ obj = boto.lookup('sdb', val)
+ else:
+ obj = None
+ return obj
+
+
diff --git a/vendor/boto/boto/services/sonofmmm.cfg b/vendor/boto/boto/services/sonofmmm.cfg
new file mode 100644
index 0000000000..d70d3794d5
--- /dev/null
+++ b/vendor/boto/boto/services/sonofmmm.cfg
@@ -0,0 +1,43 @@
+#
+# Your AWS Credentials
+# You only need to supply these in this file if you are not using
+# the boto tools to start your service
+#
+#[Credentials]
+#aws_access_key_id = <AWS Access Key Here>
+#aws_secret_access_key = <AWS Secret Key Here>
+
+#
+# Fill out this section if you want emails from the service
+# when it starts and stops
+#
+#[Notification]
+#smtp_host = <your smtp host>
+#smtp_user = <your smtp username, if necessary>
+#smtp_pass = <your smtp password, if necessary>
+#smtp_from = <email address for From: field>
+#smtp_to = <email address for To: field>
+
+[Pyami]
+scripts = boto.services.sonofmmm.SonOfMMM
+
+[SonOfMMM]
+# id of the AMI to be launched
+ami_id = ami-dc799cb5
+# number of times service will read an empty queue before exiting
+# a negative value will cause the service to run forever
+retry_count = 5
+# seconds to wait after empty queue read before reading again
+loop_delay = 10
+# average time it takes to process a transaction
+# controls invisibility timeout of messages
+processing_time = 60
+ffmpeg_args = -y -i %%s -f mov -r 29.97 -b 1200kb -mbd 2 -flags +4mv+trell -aic 2 -cmp 2 -subcmp 2 -ar 48000 -ab 19200 -s 320x240 -vcodec mpeg4 -acodec libfaac %%s
+output_mimetype = video/quicktime
+output_ext = .mov
+input_bucket = <S3 bucket where source videos live>
+output_bucket = <S3 bucket where converted videos should be stored>
+output_domain = <SimpleDB domain to store results - optional>
+output_queue = <SQS queue to store results - optional>
+input_queue = <SQS queue where work to be done will be queued up>
+
diff --git a/vendor/boto/boto/services/sonofmmm.py b/vendor/boto/boto/services/sonofmmm.py
new file mode 100644
index 0000000000..acb7e61067
--- /dev/null
+++ b/vendor/boto/boto/services/sonofmmm.py
@@ -0,0 +1,81 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import boto
+from boto.services.service import Service
+from boto.services.message import ServiceMessage
+import os
+import mimetypes
+
+class SonOfMMM(Service):
+
+ def __init__(self, config_file=None):
+ Service.__init__(self, config_file)
+ self.log_file = '%s.log' % self.instance_id
+ self.log_path = os.path.join(self.working_dir, self.log_file)
+ boto.set_file_logger(self.name, self.log_path)
+ if self.sd.has_option('ffmpeg_args'):
+ self.command = '/usr/local/bin/ffmpeg ' + self.sd.get('ffmpeg_args')
+ else:
+ self.command = '/usr/local/bin/ffmpeg -y -i %s %s'
+ self.output_mimetype = self.sd.get('output_mimetype')
+ if self.sd.has_option('output_ext'):
+ self.output_ext = self.sd.get('output_ext')
+ else:
+ self.output_ext = mimetypes.guess_extension(self.output_mimetype)
+ self.output_bucket = self.sd.get_obj('output_bucket')
+ self.input_bucket = self.sd.get_obj('input_bucket')
+ # check to see if there are any messages queue
+ # if not, create messages for all files in input_bucket
+ m = self.input_queue.read(1)
+ if not m:
+ self.queue_files()
+
+ def queue_files(self):
+ boto.log.info('Queueing files from %s' % self.input_bucket.name)
+ for key in self.input_bucket:
+ boto.log.info('Queueing %s' % key.name)
+ m = ServiceMessage()
+ if self.output_bucket:
+ d = {'OutputBucket' : self.output_bucket.name}
+ else:
+ d = None
+ m.for_key(key, d)
+ self.input_queue.write(m)
+
+ def process_file(self, in_file_name, msg):
+ base, ext = os.path.splitext(in_file_name)
+ out_file_name = os.path.join(self.working_dir,
+ base+self.output_ext)
+ command = self.command % (in_file_name, out_file_name)
+ boto.log.info('running:\n%s' % command)
+ status = self.run(command)
+ if status == 0:
+ return [(out_file_name, self.output_mimetype)]
+ else:
+ return []
+
+ def shutdown(self):
+ if os.path.isfile(self.log_path):
+ if self.output_bucket:
+ key = self.output_bucket.new_key(self.log_file)
+ key.set_contents_from_filename(self.log_path)
+ Service.shutdown(self)
diff --git a/vendor/boto/boto/services/submit.py b/vendor/boto/boto/services/submit.py
new file mode 100644
index 0000000000..89c439c525
--- /dev/null
+++ b/vendor/boto/boto/services/submit.py
@@ -0,0 +1,88 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import time
+import os
+
+
+class Submitter:
+
+ def __init__(self, sd):
+ self.sd = sd
+ self.input_bucket = self.sd.get_obj('input_bucket')
+ self.output_bucket = self.sd.get_obj('output_bucket')
+ self.output_domain = self.sd.get_obj('output_domain')
+ self.queue = self.sd.get_obj('input_queue')
+
+ def get_key_name(self, fullpath, prefix):
+ key_name = fullpath[len(prefix):]
+ l = key_name.split(os.sep)
+ return '/'.join(l)
+
+ def write_message(self, key, metadata):
+ if self.queue:
+ m = self.queue.new_message()
+ m.for_key(key, metadata)
+ if self.output_bucket:
+ m['OutputBucket'] = self.output_bucket.name
+ self.queue.write(m)
+
+ def submit_file(self, path, metadata=None, cb=None, num_cb=0, prefix='/'):
+ if not metadata:
+ metadata = {}
+ key_name = self.get_key_name(path, prefix)
+ k = self.input_bucket.new_key(key_name)
+ k.update_metadata(metadata)
+ k.set_contents_from_filename(path, replace=False, cb=cb, num_cb=num_cb)
+ self.write_message(k, metadata)
+
+ def submit_path(self, path, tags=None, ignore_dirs=None, cb=None, num_cb=0, status=False, prefix='/'):
+ path = os.path.expanduser(path)
+ path = os.path.expandvars(path)
+ path = os.path.abspath(path)
+ total = 0
+ metadata = {}
+ if tags:
+ metadata['Tags'] = tags
+ l = []
+ for t in time.gmtime():
+ l.append(str(t))
+ metadata['Batch'] = '_'.join(l)
+ if self.output_domain:
+ self.output_domain.put_attributes(metadata['Batch'], {'type' : 'Batch'})
+ if os.path.isdir(path):
+ for root, dirs, files in os.walk(path):
+ if ignore_dirs:
+ for ignore in ignore_dirs:
+ if ignore in dirs:
+ dirs.remove(ignore)
+ for file in files:
+ fullpath = os.path.join(root, file)
+ if status:
+ print 'Submitting %s' % fullpath
+ self.submit_file(fullpath, metadata, cb, num_cb, prefix)
+ total += 1
+ elif os.path.isfile(path):
+ self.submit_file(path, metadata, cb, num_cb)
+ total += 1
+ else:
+ print 'problem with %s' % path
+ return (metadata['Batch'], total)
diff --git a/vendor/boto/boto/sns/__init__.py b/vendor/boto/boto/sns/__init__.py
new file mode 100644
index 0000000000..6075fe6f76
--- /dev/null
+++ b/vendor/boto/boto/sns/__init__.py
@@ -0,0 +1,353 @@
+# Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.connection import AWSQueryConnection
+from boto.sdb.regioninfo import SDBRegionInfo
+import boto
+try:
+ import json
+except ImportError:
+ import simplejson as json
+
+#boto.set_stream_logger('sns')
+
+class SNSConnection(AWSQueryConnection):
+
+ DefaultRegionName = 'us-east-1'
+ DefaultRegionEndpoint = 'sns.us-east-1.amazonaws.com'
+ APIVersion = '2010-03-31'
+ SignatureVersion = '2'
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, debug=0,
+ https_connection_factory=None, region=None, path='/', converter=None):
+ if not region:
+ region = SDBRegionInfo(self, self.DefaultRegionName, self.DefaultRegionEndpoint)
+ self.region = region
+ AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_key,
+ is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
+ self.region.endpoint, debug, https_connection_factory, path)
+
+ def get_all_topics(self, next_token=None):
+ """
+ :type next_token: string
+ :param next_token: Token returned by the previous call to
+ this method.
+
+ """
+ params = {'ContentType' : 'JSON'}
+ if next_token:
+ params['NextToken'] = next_token
+ response = self.make_request('ListTopics', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def get_topic_attributes(self, topic):
+ """
+ Get attributes of a Topic
+
+ :type topic: string
+ :param topic: The ARN of the topic.
+
+ """
+ params = {'ContentType' : 'JSON',
+ 'TopicArn' : topic}
+ response = self.make_request('GetTopicAttributes', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def add_permission(self, topic, label, account_ids, actions):
+ """
+ Adds a statement to a topic's access control policy, granting
+ access for the specified AWS accounts to the specified actions.
+
+ :type topic: string
+ :param topic: The ARN of the topic.
+
+ :type label: string
+ :param label: A unique identifier for the new policy statement.
+
+ :type account_ids: list of strings
+ :param account_ids: The AWS account ids of the users who will be
+ give access to the specified actions.
+
+ :type actions: list of strings
+ :param actions: The actions you want to allow for each of the
+ specified principal(s).
+
+ """
+ params = {'ContentType' : 'JSON',
+ 'TopicArn' : topic,
+ 'Label' : label}
+ self.build_list_params(params, account_ids, 'AWSAccountId')
+ self.build_list_params(params, actions, 'ActionName')
+ response = self.make_request('AddPermission', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def remove_permission(self, topic, label):
+ """
+ Removes a statement from a topic's access control policy.
+
+ :type topic: string
+ :param topic: The ARN of the topic.
+
+ :type label: string
+ :param label: A unique identifier for the policy statement
+ to be removed.
+
+ """
+ params = {'ContentType' : 'JSON',
+ 'TopicArn' : topic,
+ 'Label' : label}
+ response = self.make_request('RemovePermission', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def create_topic(self, topic):
+ """
+ Create a new Topic.
+
+ :type topic: string
+ :param topic: The name of the new topic.
+
+ """
+ params = {'ContentType' : 'JSON',
+ 'Name' : topic}
+ response = self.make_request('CreateTopic', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def delete_topic(self, topic):
+ """
+ Delete an existing topic
+
+ :type topic: string
+ :param topic: The ARN of the topic
+
+ """
+ params = {'ContentType' : 'JSON',
+ 'TopicArn' : topic}
+ response = self.make_request('DeleteTopic', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+
+
+ def publish(self, topic, message, subject=None):
+ """
+ Get properties of a Topic
+
+ :type topic: string
+ :param topic: The ARN of the new topic.
+
+ :type message: string
+ :param message: The message you want to send to the topic.
+ Messages must be UTF-8 encoded strings and
+ be at most 4KB in size.
+
+ :type subject: string
+ :param subject: Optional parameter to be used as the "Subject"
+ line of the email notifications.
+
+ """
+ params = {'ContentType' : 'JSON',
+ 'TopicArn' : topic,
+ 'Message' : message}
+ if subject:
+ params['Subject'] = subject
+ response = self.make_request('Publish', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def subscribe(self, topic, protocol, endpoint):
+ """
+ Subscribe to a Topic.
+
+ :type topic: string
+ :param topic: The name of the new topic.
+
+ :type protocol: string
+ :param protocol: The protocol used to communicate with
+ the subscriber. Current choices are:
+ email|email-json|http|https|sqs
+
+ :type endpoint: string
+ :param endpoint: The location of the endpoint for
+ the subscriber.
+ * For email, this would be a valid email address
+ * For email-json, this would be a valid email address
+ * For http, this would be a URL beginning with http
+ * For https, this would be a URL beginning with https
+ * For sqs, this would be the ARN of an SQS Queue
+
+ :rtype: :class:`boto.sdb.domain.Domain` object
+ :return: The newly created domain
+ """
+ params = {'ContentType' : 'JSON',
+ 'TopicArn' : topic,
+ 'Protocol' : protocol,
+ 'Endpoint' : endpoint}
+ response = self.make_request('Subscribe', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def confirm_subscription(self, topic, token,
+ authenticate_on_unsubscribe=False):
+ """
+ Get properties of a Topic
+
+ :type topic: string
+ :param topic: The ARN of the new topic.
+
+ :type token: string
+ :param token: Short-lived token sent to and endpoint during
+ the Subscribe operation.
+
+ :type authenticate_on_unsubscribe: bool
+ :param authenticate_on_unsubscribe: Optional parameter indicating
+ that you wish to disable
+ unauthenticated unsubscription
+ of the subscription.
+
+ """
+ params = {'ContentType' : 'JSON',
+ 'TopicArn' : topic,
+ 'Token' : token}
+ if authenticate_on_unsubscribe:
+ params['AuthenticateOnUnsubscribe'] = 'true'
+ response = self.make_request('ConfirmSubscription', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def unsubscribe(self, subscription):
+ """
+ Allows endpoint owner to delete subscription.
+ Confirmation message will be delivered.
+
+ :type subscription: string
+ :param subscription: The ARN of the subscription to be deleted.
+
+ """
+ params = {'ContentType' : 'JSON',
+ 'SubscriptionArn' : subscription}
+ response = self.make_request('Unsubscribe', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def get_all_subscriptions(self, next_token=None):
+ """
+ Get list of all subscriptions.
+
+ :type next_token: string
+ :param next_token: Token returned by the previous call to
+ this method.
+
+ """
+ params = {'ContentType' : 'JSON'}
+ if next_token:
+ params['NextToken'] = next_token
+ response = self.make_request('ListSubscriptions', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
+ def get_all_subscriptions_by_topic(self, topic, next_token=None):
+ """
+ Get list of all subscriptions to a specific topic.
+
+ :type topic: string
+ :param topic: The ARN of the topic for which you wish to
+ find subscriptions.
+
+ :type next_token: string
+ :param next_token: Token returned by the previous call to
+ this method.
+
+ """
+ params = {'ContentType' : 'JSON',
+ 'TopicArn' : topic}
+ if next_token:
+ params['NextToken'] = next_token
+ response = self.make_request('ListSubscriptions', params, '/', 'GET')
+ body = response.read()
+ if response.status == 200:
+ return json.loads(body)
+ else:
+ boto.log.error('%s %s' % (response.status, response.reason))
+ boto.log.error('%s' % body)
+ raise self.ResponseError(response.status, response.reason, body)
+
diff --git a/vendor/boto/boto/sqs/__init__.py b/vendor/boto/boto/sqs/__init__.py
new file mode 100644
index 0000000000..2817191338
--- /dev/null
+++ b/vendor/boto/boto/sqs/__init__.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+import boto
+
+from regioninfo import SQSRegionInfo
+
+def regions():
+ """
+ Get all available regions for the SQS service.
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.regioninfo.RegionInfo`
+ """
+ return [SQSRegionInfo(name='us-east-1', endpoint='queue.amazonaws.com'),
+ SQSRegionInfo(name='eu-west-1', endpoint='eu-west-1.queue.amazonaws.com'),
+ SQSRegionInfo(name='us-west-1', endpoint='us-west-1.queue.amazonaws.com')]
+
+def connect_to_region(region_name):
+ for region in regions():
+ if region.name == region_name:
+ return region.connect()
+ return None
diff --git a/vendor/boto/boto/sqs/attributes.py b/vendor/boto/boto/sqs/attributes.py
new file mode 100644
index 0000000000..26c720416f
--- /dev/null
+++ b/vendor/boto/boto/sqs/attributes.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an SQS Attribute Name/Value set
+"""
+
+class Attributes(dict):
+
+ def __init__(self, parent):
+ self.parent = parent
+ self.current_key = None
+ self.current_value = None
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'Attribute':
+ self[self.current_key] = self.current_value
+ elif name == 'Name':
+ self.current_key = value
+ elif name == 'Value':
+ self.current_value = value
+ else:
+ setattr(self, name, value)
+
+
diff --git a/vendor/boto/boto/sqs/connection.py b/vendor/boto/boto/sqs/connection.py
new file mode 100644
index 0000000000..848679e30b
--- /dev/null
+++ b/vendor/boto/boto/sqs/connection.py
@@ -0,0 +1,286 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.connection import AWSQueryConnection
+from boto.sqs.regioninfo import SQSRegionInfo
+from boto.sqs.queue import Queue
+from boto.sqs.message import Message
+from boto.sqs.attributes import Attributes
+from boto.exception import SQSError
+
+
+class SQSConnection(AWSQueryConnection):
+ """
+ A Connection to the SQS Service.
+ """
+ DefaultRegionName = 'us-east-1'
+ DefaultRegionEndpoint = 'queue.amazonaws.com'
+ APIVersion = '2009-02-01'
+ SignatureVersion = '2'
+ DefaultContentType = 'text/plain'
+ ResponseError = SQSError
+
+ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
+ is_secure=True, port=None, proxy=None, proxy_port=None,
+ proxy_user=None, proxy_pass=None, debug=0,
+ https_connection_factory=None, region=None, path='/'):
+ if not region:
+ region = SQSRegionInfo(self, self.DefaultRegionName, self.DefaultRegionEndpoint)
+ self.region = region
+ AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_key,
+ is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
+ self.region.endpoint, debug, https_connection_factory, path)
+
+ def create_queue(self, queue_name, visibility_timeout=None):
+ """
+ Create an SQS Queue.
+
+ :type queue_name: str or unicode
+ :param queue_name: The name of the new queue. Names are scoped to an account and need to
+ be unique within that account. Calling this method on an existing
+ queue name will not return an error from SQS unless the value for
+ visibility_timeout is different than the value of the existing queue
+ of that name. This is still an expensive operation, though, and not
+ the preferred way to check for the existence of a queue. See the
+ :func:`boto.sqs.connection.SQSConnection.lookup` method.
+
+ :type visibility_timeout: int
+ :param visibility_timeout: The default visibility timeout for all messages written in the
+ queue. This can be overridden on a per-message.
+
+ :rtype: :class:`boto.sqs.queue.Queue`
+ :return: The newly created queue.
+
+ """
+ params = {'QueueName': queue_name}
+ if visibility_timeout:
+ params['DefaultVisibilityTimeout'] = '%d' % (visibility_timeout,)
+ return self.get_object('CreateQueue', params, Queue)
+
+ def delete_queue(self, queue, force_deletion=False):
+ """
+ Delete an SQS Queue.
+
+ :type queue: A Queue object
+ :param queue: The SQS queue to be deleted
+
+ :type force_deletion: Boolean
+ :param force_deletion: Normally, SQS will not delete a queue that contains messages.
+ However, if the force_deletion argument is True, the
+ queue will be deleted regardless of whether there are messages in
+ the queue or not. USE WITH CAUTION. This will delete all
+ messages in the queue as well.
+
+ :rtype: bool
+ :return: True if the command succeeded, False otherwise
+ """
+ return self.get_status('DeleteQueue', None, queue.id)
+
+ def get_queue_attributes(self, queue, attribute='All'):
+ """
+ Gets one or all attributes of a Queue
+
+ :type queue: A Queue object
+ :param queue: The SQS queue to be deleted
+
+ :type attribute: str
+ :type attribute: The specific attribute requested. If not supplied, the default
+ is to return all attributes. Valid attributes are:
+ ApproximateNumberOfMessages,
+ ApproximateNumberOfMessagesNotVisible,
+ VisibilityTimeout,
+ CreatedTimestamp,
+ LastModifiedTimestamp,
+ Policy
+
+ :rtype: :class:`boto.sqs.attributes.Attributes`
+ :return: An Attributes object containing request value(s).
+ """
+ params = {'AttributeName' : attribute}
+ return self.get_object('GetQueueAttributes', params, Attributes, queue.id)
+
+ def set_queue_attribute(self, queue, attribute, value):
+ params = {'Attribute.Name' : attribute, 'Attribute.Value' : value}
+ return self.get_status('SetQueueAttributes', params, queue.id)
+
+ def receive_message(self, queue, number_messages=1, visibility_timeout=None,
+ attributes=None):
+ """
+ Read messages from an SQS Queue.
+
+ :type queue: A Queue object
+ :param queue: The Queue from which messages are read.
+
+ :type number_messages: int
+ :param number_messages: The maximum number of messages to read (default=1)
+
+ :type visibility_timeout: int
+ :param visibility_timeout: The number of seconds the message should remain invisible
+ to other queue readers (default=None which uses the Queues default)
+
+ :type attributes: str
+ :param attributes: The name of additional attribute to return with response
+ or All if you want all attributes. The default is to
+ return no additional attributes. Valid values:
+ All
+ SenderId
+ SentTimestamp
+ ApproximateReceiveCount
+ ApproximateFirstReceiveTimestamp
+
+ :rtype: list
+ :return: A list of :class:`boto.sqs.message.Message` objects.
+ """
+ params = {'MaxNumberOfMessages' : number_messages}
+ if visibility_timeout:
+ params['VisibilityTimeout'] = visibility_timeout
+ if attributes:
+ self.build_list_params(params, attributes, 'AttributeName')
+ return self.get_list('ReceiveMessage', params, [('Message', queue.message_class)],
+ queue.id, queue)
+
+ def delete_message(self, queue, message):
+ """
+ Delete a message from a queue.
+
+ :type queue: A :class:`boto.sqs.queue.Queue` object
+ :param queue: The Queue from which messages are read.
+
+ :type message: A :class:`boto.sqs.message.Message` object
+ :param message: The Message to be deleted
+
+ :rtype: bool
+ :return: True if successful, False otherwise.
+ """
+ params = {'ReceiptHandle' : message.receipt_handle}
+ return self.get_status('DeleteMessage', params, queue.id)
+
+ def delete_message_from_handle(self, queue, receipt_handle):
+ """
+ Delete a message from a queue, given a receipt handle.
+
+ :type queue: A :class:`boto.sqs.queue.Queue` object
+ :param queue: The Queue from which messages are read.
+
+ :type receipt_handle: str
+ :param receipt_handle: The receipt handle for the message
+
+ :rtype: bool
+ :return: True if successful, False otherwise.
+ """
+ params = {'ReceiptHandle' : receipt_handle}
+ return self.get_status('DeleteMessage', params, queue.id)
+
+ def send_message(self, queue, message_content):
+ params = {'MessageBody' : message_content}
+ return self.get_object('SendMessage', params, Message, queue.id, verb='POST')
+
+ def change_message_visibility(self, queue, receipt_handle, visibility_timeout):
+ """
+ Extends the read lock timeout for the specified message from the specified queue
+ to the specified value.
+
+ :type queue: A :class:`boto.sqs.queue.Queue` object
+ :param queue: The Queue from which messages are read.
+
+ :type receipt_handle: str
+ :param queue: The receipt handle associated with the message whose
+ visibility timeout will be changed.
+
+ :type visibility_timeout: int
+ :param visibility_timeout: The new value of the message's visibility timeout
+ in seconds.
+ """
+ params = {'ReceiptHandle' : receipt_handle,
+ 'VisibilityTimeout' : visibility_timeout}
+ return self.get_status('ChangeMessageVisibility', params, queue.id)
+
+ def get_all_queues(self, prefix=''):
+ params = {}
+ if prefix:
+ params['QueueNamePrefix'] = prefix
+ return self.get_list('ListQueues', params, [('QueueUrl', Queue)])
+
+ def get_queue(self, queue_name):
+ rs = self.get_all_queues(queue_name)
+ for q in rs:
+ if q.url.endswith(queue_name):
+ return q
+ return None
+
+ lookup = get_queue
+
+ #
+ # Permissions methods
+ #
+
+ def add_permission(self, queue, label, aws_account_id, action_name):
+ """
+ Add a permission to a queue.
+
+ :type queue: :class:`boto.sqs.queue.Queue`
+ :param queue: The queue object
+
+ :type label: str or unicode
+ :param label: A unique identification of the permission you are setting.
+ Maximum of 80 characters ``[0-9a-zA-Z_-]``
+ Example, AliceSendMessage
+
+ :type aws_account_id: str or unicode
+ :param principal_id: The AWS account number of the principal who will be given
+ permission. The principal must have an AWS account, but
+ does not need to be signed up for Amazon SQS. For information
+ about locating the AWS account identification.
+
+ :type action_name: str or unicode
+ :param action_name: The action. Valid choices are:
+ \*|SendMessage|ReceiveMessage|DeleteMessage|
+ ChangeMessageVisibility|GetQueueAttributes
+
+ :rtype: bool
+ :return: True if successful, False otherwise.
+
+ """
+ params = {'Label': label,
+ 'AWSAccountId' : aws_account_id,
+ 'ActionName' : action_name}
+ return self.get_status('AddPermission', params, queue.id)
+
+ def remove_permission(self, queue, label):
+ """
+ Remove a permission from a queue.
+
+ :type queue: :class:`boto.sqs.queue.Queue`
+ :param queue: The queue object
+
+ :type label: str or unicode
+ :param label: The unique label associated with the permission being removed.
+
+ :rtype: bool
+ :return: True if successful, False otherwise.
+ """
+ params = {'Label': label}
+ return self.get_status('RemovePermission', params, queue.id)
+
+
+
+
+
diff --git a/vendor/boto/boto/sqs/jsonmessage.py b/vendor/boto/boto/sqs/jsonmessage.py
new file mode 100644
index 0000000000..ab05a60172
--- /dev/null
+++ b/vendor/boto/boto/sqs/jsonmessage.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from boto.sqs.message import MHMessage
+from boto.exception import SQSDecodeError
+import base64
+import simplejson
+
+class JSONMessage(MHMessage):
+ """
+ Acts like a dictionary but encodes it's data as a Base64 encoded JSON payload.
+ """
+
+ def decode(self, value):
+ try:
+ value = base64.b64decode(value)
+ value = simplejson.loads(value)
+ except:
+ raise SQSDecodeError('Unable to decode message', self)
+ return value
+
+ def encode(self, value):
+ value = simplejson.dumps(value)
+ return base64.b64encode(value)
diff --git a/vendor/boto/boto/sqs/message.py b/vendor/boto/boto/sqs/message.py
new file mode 100644
index 0000000000..da1ba68e6f
--- /dev/null
+++ b/vendor/boto/boto/sqs/message.py
@@ -0,0 +1,251 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+SQS Message
+
+A Message represents the data stored in an SQS queue. The rules for what is allowed within an SQS
+Message are here:
+
+ http://docs.amazonwebservices.com/AWSSimpleQueueService/2008-01-01/SQSDeveloperGuide/Query_QuerySendMessage.html
+
+So, at it's simplest level a Message just needs to allow a developer to store bytes in it and get the bytes
+back out. However, to allow messages to have richer semantics, the Message class must support the
+following interfaces:
+
+The constructor for the Message class must accept a keyword parameter "queue" which is an instance of a
+boto Queue object and represents the queue that the message will be stored in. The default value for
+this parameter is None.
+
+The constructor for the Message class must accept a keyword parameter "body" which represents the
+content or body of the message. The format of this parameter will depend on the behavior of the
+particular Message subclass. For example, if the Message subclass provides dictionary-like behavior to the
+user the body passed to the constructor should be a dict-like object that can be used to populate
+the initial state of the message.
+
+The Message class must provide an encode method that accepts a value of the same type as the body
+parameter of the constructor and returns a string of characters that are able to be stored in an
+SQS message body (see rules above).
+
+The Message class must provide a decode method that accepts a string of characters that can be
+stored (and probably were stored!) in an SQS message and return an object of a type that is consistent
+with the "body" parameter accepted on the class constructor.
+
+The Message class must provide a __len__ method that will return the size of the encoded message
+that would be stored in SQS based on the current state of the Message object.
+
+The Message class must provide a get_body method that will return the body of the message in the
+same format accepted in the constructor of the class.
+
+The Message class must provide a set_body method that accepts a message body in the same format
+accepted by the constructor of the class. This method should alter to the internal state of the
+Message object to reflect the state represented in the message body parameter.
+
+The Message class must provide a get_body_encoded method that returns the current body of the message
+in the format in which it would be stored in SQS.
+"""
+
+import base64
+import StringIO
+from boto.sqs.attributes import Attributes
+from boto.exception import SQSDecodeError
+
+class RawMessage:
+ """
+ Base class for SQS messages. RawMessage does not encode the message
+ in any way. Whatever you store in the body of the message is what
+ will be written to SQS and whatever is returned from SQS is stored
+ directly into the body of the message.
+ """
+
+ def __init__(self, queue=None, body=''):
+ self.queue = queue
+ self.set_body(body)
+ self.id = None
+ self.receipt_handle = None
+ self.md5 = None
+ self.attributes = Attributes(self)
+
+ def __len__(self):
+ return len(self.encode(self._body))
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Attribute':
+ return self.attributes
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Body':
+ self.set_body(self.decode(value))
+ elif name == 'MessageId':
+ self.id = value
+ elif name == 'ReceiptHandle':
+ self.receipt_handle = value
+ elif name == 'MD5OfMessageBody':
+ self.md5 = value
+ else:
+ setattr(self, name, value)
+
+ def encode(self, value):
+ """Transform body object into serialized byte array format."""
+ return value
+
+ def decode(self, value):
+ """Transform seralized byte array into any object."""
+ return value
+
+ def set_body(self, body):
+ """Override the current body for this object, using decoded format."""
+ self._body = body
+
+ def get_body(self):
+ return self._body
+
+ def get_body_encoded(self):
+ """
+ This method is really a semi-private method used by the Queue.write
+ method when writing the contents of the message to SQS.
+ You probably shouldn't need to call this method in the normal course of events.
+ """
+ return self.encode(self.get_body())
+
+ def delete(self):
+ if self.queue:
+ return self.queue.delete_message(self)
+
+ def change_visibility(self, visibility_timeout):
+ if self.queue:
+ self.queue.connection.change_message_visibility(self.queue,
+ self.receipt_handle,
+ visibility_timeout)
+
+class Message(RawMessage):
+ """
+ The default Message class used for SQS queues. This class automatically
+ encodes/decodes the message body using Base64 encoding to avoid any
+ illegal characters in the message body. See:
+
+ http://developer.amazonwebservices.com/connect/thread.jspa?messageID=49680%EC%88%90
+
+ for details on why this is a good idea. The encode/decode is meant to
+ be transparent to the end-user.
+ """
+
+ def encode(self, value):
+ return base64.b64encode(value)
+
+ def decode(self, value):
+ try:
+ value = base64.b64decode(value)
+ except:
+ raise SQSDecodeError('Unable to decode message', self)
+ return value
+
+class MHMessage(Message):
+ """
+ The MHMessage class provides a message that provides RFC821-like
+ headers like this:
+
+ HeaderName: HeaderValue
+
+ The encoding/decoding of this is handled automatically and after
+ the message body has been read, the message instance can be treated
+ like a mapping object, i.e. m['HeaderName'] would return 'HeaderValue'.
+ """
+
+ def __init__(self, queue=None, body=None, xml_attrs=None):
+ if body == None or body == '':
+ body = {}
+ Message.__init__(self, queue, body)
+
+ def decode(self, value):
+ try:
+ msg = {}
+ fp = StringIO.StringIO(value)
+ line = fp.readline()
+ while line:
+ delim = line.find(':')
+ key = line[0:delim]
+ value = line[delim+1:].strip()
+ msg[key.strip()] = value.strip()
+ line = fp.readline()
+ except:
+ raise SQSDecodeError('Unable to decode message', self)
+ return msg
+
+ def encode(self, value):
+ s = ''
+ for item in value.items():
+ s = s + '%s: %s\n' % (item[0], item[1])
+ return s
+
+ def __getitem__(self, key):
+ if self._body.has_key(key):
+ return self._body[key]
+ else:
+ raise KeyError(key)
+
+ def __setitem__(self, key, value):
+ self._body[key] = value
+ self.set_body(self._body)
+
+ def keys(self):
+ return self._body.keys()
+
+ def values(self):
+ return self._body.values()
+
+ def items(self):
+ return self._body.items()
+
+ def has_key(self, key):
+ return self._body.has_key(key)
+
+ def update(self, d):
+ self._body.update(d)
+ self.set_body(self._body)
+
+ def get(self, key, default=None):
+ return self._body.get(key, default)
+
+class EncodedMHMessage(MHMessage):
+ """
+ The EncodedMHMessage class provides a message that provides RFC821-like
+ headers like this:
+
+ HeaderName: HeaderValue
+
+ This variation encodes/decodes the body of the message in base64 automatically.
+ The message instance can be treated like a mapping object,
+ i.e. m['HeaderName'] would return 'HeaderValue'.
+ """
+
+ def decode(self, value):
+ try:
+ value = base64.b64decode(value)
+ except:
+ raise SQSDecodeError('Unable to decode message', self)
+ return MHMessage.decode(self, value)
+
+ def encode(self, value):
+ value = MHMessage.encode(value)
+ return base64.b64encode(self, value)
+
diff --git a/vendor/boto/boto/sqs/queue.py b/vendor/boto/boto/sqs/queue.py
new file mode 100644
index 0000000000..91b319ba14
--- /dev/null
+++ b/vendor/boto/boto/sqs/queue.py
@@ -0,0 +1,414 @@
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents an SQS Queue
+"""
+
+import urlparse
+from boto.sqs.message import Message
+
+
+class Queue:
+
+ def __init__(self, connection=None, url=None, message_class=Message):
+ self.connection = connection
+ self.url = url
+ self.message_class = message_class
+ self.visibility_timeout = None
+
+ def _id(self):
+ if self.url:
+ val = urlparse.urlparse(self.url)[2]
+ else:
+ val = self.url
+ return val
+ id = property(_id)
+
+ def _name(self):
+ if self.url:
+ val = urlparse.urlparse(self.url)[2].split('/')[2]
+ else:
+ val = self.url
+ return val
+ name = property(_name)
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'QueueUrl':
+ self.url = value
+ elif name == 'VisibilityTimeout':
+ self.visibility_timeout = int(value)
+ else:
+ setattr(self, name, value)
+
+ def set_message_class(self, message_class):
+ """
+ Set the message class that should be used when instantiating messages read
+ from the queue. By default, the class boto.sqs.message.Message is used but
+ this can be overriden with any class that behaves like a message.
+
+ :type message_class: Message-like class
+ :param message_class: The new Message class
+ """
+ self.message_class = message_class
+
+ def get_attributes(self, attributes='All'):
+ """
+ Retrieves attributes about this queue object and returns
+ them in an Attribute instance (subclass of a Dictionary).
+
+ :type attributes: string
+ :param attributes: String containing one of:
+ ApproximateNumberOfMessages,
+ ApproximateNumberOfMessagesNotVisible,
+ VisibilityTimeout,
+ CreatedTimestamp,
+ LastModifiedTimestamp,
+ Policy
+ :rtype: Attribute object
+ :return: An Attribute object which is a mapping type holding the
+ requested name/value pairs
+ """
+ return self.connection.get_queue_attributes(self, attributes)
+
+ def set_attribute(self, attribute, value):
+ """
+ Set a new value for an attribute of the Queue.
+
+ :type attribute: String
+ :param attribute: The name of the attribute you want to set. The
+ only valid value at this time is: VisibilityTimeout
+ :type value: int
+ :param value: The new value for the attribute.
+ For VisibilityTimeout the value must be an
+ integer number of seconds from 0 to 86400.
+
+ :rtype: bool
+ :return: True if successful, otherwise False.
+ """
+ return self.connection.set_queue_attribute(self, attribute, value)
+
+ def get_timeout(self):
+ """
+ Get the visibility timeout for the queue.
+
+ :rtype: int
+ :return: The number of seconds as an integer.
+ """
+ a = self.get_attributes('VisibilityTimeout')
+ return int(a['VisibilityTimeout'])
+
+ def set_timeout(self, visibility_timeout):
+ """
+ Set the visibility timeout for the queue.
+
+ :type visibility_timeout: int
+ :param visibility_timeout: The desired timeout in seconds
+ """
+ retval = self.set_attribute('VisibilityTimeout', visibility_timeout)
+ if retval:
+ self.visibility_timeout = visibility_timeout
+ return retval
+
+ def add_permission(self, label, aws_account_id, action_name):
+ """
+ Add a permission to a queue.
+
+ :type label: str or unicode
+ :param label: A unique identification of the permission you are setting.
+ Maximum of 80 characters ``[0-9a-zA-Z_-]``
+ Example, AliceSendMessage
+
+ :type aws_account_id: str or unicode
+ :param principal_id: The AWS account number of the principal who will be given
+ permission. The principal must have an AWS account, but
+ does not need to be signed up for Amazon SQS. For information
+ about locating the AWS account identification.
+
+ :type action_name: str or unicode
+ :param action_name: The action. Valid choices are:
+ \*|SendMessage|ReceiveMessage|DeleteMessage|
+ ChangeMessageVisibility|GetQueueAttributes
+
+ :rtype: bool
+ :return: True if successful, False otherwise.
+
+ """
+ return self.connection.add_permission(self, label, aws_account_id, action_name)
+
+ def remove_permission(self, label):
+ """
+ Remove a permission from a queue.
+
+ :type label: str or unicode
+ :param label: The unique label associated with the permission being removed.
+
+ :rtype: bool
+ :return: True if successful, False otherwise.
+ """
+ return self.connection.remove_permission(self, label)
+
+ def read(self, visibility_timeout=None):
+ """
+ Read a single message from the queue.
+
+ :type visibility_timeout: int
+ :param visibility_timeout: The timeout for this message in seconds
+
+ :rtype: :class:`boto.sqs.message.Message`
+ :return: A single message or None if queue is empty
+ """
+ rs = self.get_messages(1, visibility_timeout)
+ if len(rs) == 1:
+ return rs[0]
+ else:
+ return None
+
+ def write(self, message):
+ """
+ Add a single message to the queue.
+
+ :type message: Message
+ :param message: The message to be written to the queue
+
+ :rtype: :class:`boto.sqs.message.Message`
+ :return: The :class:`boto.sqs.message.Message` object that was written.
+ """
+ new_msg = self.connection.send_message(self, message.get_body_encoded())
+ message.id = new_msg.id
+ message.md5 = new_msg.md5
+ return message
+
+ def new_message(self, body=''):
+ """
+ Create new message of appropriate class.
+
+ :type body: message body
+ :param body: The body of the newly created message (optional).
+
+ :rtype: :class:`boto.sqs.message.Message`
+ :return: A new Message object
+ """
+ m = self.message_class(self, body)
+ m.queue = self
+ return m
+
+ # get a variable number of messages, returns a list of messages
+ def get_messages(self, num_messages=1, visibility_timeout=None,
+ attributes=None):
+ """
+ Get a variable number of messages.
+
+ :type num_messages: int
+ :param num_messages: The maximum number of messages to read from the queue.
+
+ :type visibility_timeout: int
+ :param visibility_timeout: The VisibilityTimeout for the messages read.
+
+ :type attributes: str
+ :param attributes: The name of additional attribute to return with response
+ or All if you want all attributes. The default is to
+ return no additional attributes. Valid values:
+ All
+ SenderId
+ SentTimestamp
+ ApproximateReceiveCount
+ ApproximateFirstReceiveTimestamp
+
+ :rtype: list
+ :return: A list of :class:`boto.sqs.message.Message` objects.
+ """
+ return self.connection.receive_message(self, number_messages=num_messages,
+ visibility_timeout=visibility_timeout,
+ attributes=attributes)
+
+ def delete_message(self, message):
+ """
+ Delete a message from the queue.
+
+ :type message: :class:`boto.sqs.message.Message`
+ :param message: The :class:`boto.sqs.message.Message` object to delete.
+
+ :rtype: bool
+ :return: True if successful, False otherwise
+ """
+ return self.connection.delete_message(self, message)
+
+ def delete(self):
+ """
+ Delete the queue.
+ """
+ return self.connection.delete_queue(self)
+
+ def clear(self, page_size=10, vtimeout=10):
+ """Utility function to remove all messages from a queue"""
+ n = 0
+ l = self.get_messages(page_size, vtimeout)
+ while l:
+ for m in l:
+ self.delete_message(m)
+ n += 1
+ l = self.get_messages(page_size, vtimeout)
+ return n
+
+ def count(self, page_size=10, vtimeout=10):
+ """
+ Utility function to count the number of messages in a queue.
+ Note: This function now calls GetQueueAttributes to obtain
+ an 'approximate' count of the number of messages in a queue.
+ """
+ a = self.get_attributes('ApproximateNumberOfMessages')
+ return int(a['ApproximateNumberOfMessages'])
+
+ def count_slow(self, page_size=10, vtimeout=10):
+ """
+ Deprecated. This is the old 'count' method that actually counts
+ the messages by reading them all. This gives an accurate count but
+ is very slow for queues with non-trivial number of messasges.
+ Instead, use get_attribute('ApproximateNumberOfMessages') to take
+ advantage of the new SQS capability. This is retained only for
+ the unit tests.
+ """
+ n = 0
+ l = self.get_messages(page_size, vtimeout)
+ while l:
+ for m in l:
+ n += 1
+ l = self.get_messages(page_size, vtimeout)
+ return n
+
+ def dump_(self, file_name, page_size=10, vtimeout=10, sep='\n'):
+ """Utility function to dump the messages in a queue to a file
+ NOTE: Page size must be < 10 else SQS errors"""
+ fp = open(file_name, 'wb')
+ n = 0
+ l = self.get_messages(page_size, vtimeout)
+ while l:
+ for m in l:
+ fp.write(m.get_body())
+ if sep:
+ fp.write(sep)
+ n += 1
+ l = self.get_messages(page_size, vtimeout)
+ fp.close()
+ return n
+
+ def save_to_file(self, fp, sep='\n'):
+ """
+ Read all messages from the queue and persist them to file-like object.
+ Messages are written to the file and the 'sep' string is written
+ in between messages. Messages are deleted from the queue after
+ being written to the file.
+ Returns the number of messages saved.
+ """
+ n = 0
+ m = self.read()
+ while m:
+ n += 1
+ fp.write(m.get_body())
+ if sep:
+ fp.write(sep)
+ self.delete_message(m)
+ m = self.read()
+ return n
+
+ def save_to_filename(self, file_name, sep='\n'):
+ """
+ Read all messages from the queue and persist them to local file.
+ Messages are written to the file and the 'sep' string is written
+ in between messages. Messages are deleted from the queue after
+ being written to the file.
+ Returns the number of messages saved.
+ """
+ fp = open(file_name, 'wb')
+ n = self.save_to_file(fp, sep)
+ fp.close()
+ return n
+
+ # for backwards compatibility
+ save = save_to_filename
+
+ def save_to_s3(self, bucket):
+ """
+ Read all messages from the queue and persist them to S3.
+ Messages are stored in the S3 bucket using a naming scheme of::
+
+ <queue_id>/<message_id>
+
+ Messages are deleted from the queue after being saved to S3.
+ Returns the number of messages saved.
+ """
+ n = 0
+ m = self.read()
+ while m:
+ n += 1
+ key = bucket.new_key('%s/%s' % (self.id, m.id))
+ key.set_contents_from_string(m.get_body())
+ self.delete_message(m)
+ m = self.read()
+ return n
+
+ def load_from_s3(self, bucket, prefix=None):
+ """
+ Load messages previously saved to S3.
+ """
+ n = 0
+ if prefix:
+ prefix = '%s/' % prefix
+ else:
+ prefix = '%s/' % self.id[1:]
+ rs = bucket.list(prefix=prefix)
+ for key in rs:
+ n += 1
+ m = self.new_message(key.get_contents_as_string())
+ self.write(m)
+ return n
+
+ def load_from_file(self, fp, sep='\n'):
+ """Utility function to load messages from a file-like object to a queue"""
+ n = 0
+ body = ''
+ l = fp.readline()
+ while l:
+ if l == sep:
+ m = Message(self, body)
+ self.write(m)
+ n += 1
+ print 'writing message %d' % n
+ body = ''
+ else:
+ body = body + l
+ l = fp.readline()
+ return n
+
+ def load_from_filename(self, file_name, sep='\n'):
+ """Utility function to load messages from a local filename to a queue"""
+ fp = open(file_name, 'rb')
+ n = self.load_file_file(fp, sep)
+ fp.close()
+ return n
+
+ # for backward compatibility
+ load = load_from_filename
+
diff --git a/vendor/boto/boto/sqs/regioninfo.py b/vendor/boto/boto/sqs/regioninfo.py
new file mode 100644
index 0000000000..1d13a4005c
--- /dev/null
+++ b/vendor/boto/boto/sqs/regioninfo.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+from boto.ec2.regioninfo import RegionInfo
+
+class SQSRegionInfo(RegionInfo):
+
+ def connect(self, **kw_params):
+ """
+ Connect to this Region's endpoint. Returns an SQSConnection
+ object pointing to the endpoint associated with this region.
+ You may pass any of the arguments accepted by the SQSConnection
+ object's constructor as keyword arguments and they will be
+ passed along to the SQSConnection object.
+
+ :rtype: :class:`boto.sqs.connection.SQSConnection`
+ :return: The connection to this regions endpoint
+ """
+ from boto.sqs.connection import SQSConnection
+ return SQSConnection(region=self, **kw_params)
+
diff --git a/vendor/boto/boto/tests/__init__.py b/vendor/boto/boto/tests/__init__.py
new file mode 100644
index 0000000000..449bd162a8
--- /dev/null
+++ b/vendor/boto/boto/tests/__init__.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+
+
diff --git a/vendor/boto/boto/tests/devpay_s3.py b/vendor/boto/boto/tests/devpay_s3.py
new file mode 100644
index 0000000000..bb91125bf8
--- /dev/null
+++ b/vendor/boto/boto/tests/devpay_s3.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Some unit tests for the S3Connection
+"""
+
+import time
+import os
+import urllib
+
+from boto.s3.connection import S3Connection
+from boto.exception import S3PermissionsError
+
+# this test requires a devpay product and user token to run:
+
+AMAZON_USER_TOKEN = '{UserToken}...your token here...'
+DEVPAY_HEADERS = { 'x-amz-security-token': AMAZON_USER_TOKEN }
+
+print '--- running S3Connection tests (DevPay) ---'
+c = S3Connection()
+# create a new, empty bucket
+bucket_name = 'test-%d' % int(time.time())
+bucket = c.create_bucket(bucket_name, headers=DEVPAY_HEADERS)
+# now try a get_bucket call and see if it's really there
+bucket = c.get_bucket(bucket_name, headers=DEVPAY_HEADERS)
+# test logging
+logging_bucket = c.create_bucket(bucket_name + '-log', headers=DEVPAY_HEADERS)
+logging_bucket.set_as_logging_target(headers=DEVPAY_HEADERS)
+bucket.enable_logging(target_bucket=logging_bucket, target_prefix=bucket.name, headers=DEVPAY_HEADERS)
+bucket.disable_logging(headers=DEVPAY_HEADERS)
+c.delete_bucket(logging_bucket, headers=DEVPAY_HEADERS)
+# create a new key and store it's content from a string
+k = bucket.new_key()
+k.name = 'foobar'
+s1 = 'This is a test of file upload and download'
+s2 = 'This is a second string to test file upload and download'
+k.set_contents_from_string(s1, headers=DEVPAY_HEADERS)
+fp = open('foobar', 'wb')
+# now get the contents from s3 to a local file
+k.get_contents_to_file(fp, headers=DEVPAY_HEADERS)
+fp.close()
+fp = open('foobar')
+# check to make sure content read from s3 is identical to original
+assert s1 == fp.read(), 'corrupted file'
+fp.close()
+# test generated URLs
+url = k.generate_url(3600, headers=DEVPAY_HEADERS)
+file = urllib.urlopen(url)
+assert s1 == file.read(), 'invalid URL %s' % url
+url = k.generate_url(3600, force_http=True, headers=DEVPAY_HEADERS)
+file = urllib.urlopen(url)
+assert s1 == file.read(), 'invalid URL %s' % url
+bucket.delete_key(k, headers=DEVPAY_HEADERS)
+# test a few variations on get_all_keys - first load some data
+# for the first one, let's override the content type
+phony_mimetype = 'application/x-boto-test'
+headers = {'Content-Type': phony_mimetype}
+headers.update(DEVPAY_HEADERS)
+k.name = 'foo/bar'
+k.set_contents_from_string(s1, headers)
+k.name = 'foo/bas'
+k.set_contents_from_filename('foobar', headers=DEVPAY_HEADERS)
+k.name = 'foo/bat'
+k.set_contents_from_string(s1, headers=DEVPAY_HEADERS)
+k.name = 'fie/bar'
+k.set_contents_from_string(s1, headers=DEVPAY_HEADERS)
+k.name = 'fie/bas'
+k.set_contents_from_string(s1, headers=DEVPAY_HEADERS)
+k.name = 'fie/bat'
+k.set_contents_from_string(s1, headers=DEVPAY_HEADERS)
+# try resetting the contents to another value
+md5 = k.md5
+k.set_contents_from_string(s2, headers=DEVPAY_HEADERS)
+assert k.md5 != md5
+os.unlink('foobar')
+all = bucket.get_all_keys(headers=DEVPAY_HEADERS)
+assert len(all) == 6
+rs = bucket.get_all_keys(prefix='foo', headers=DEVPAY_HEADERS)
+assert len(rs) == 3
+rs = bucket.get_all_keys(prefix='', delimiter='/', headers=DEVPAY_HEADERS)
+assert len(rs) == 2
+rs = bucket.get_all_keys(maxkeys=5, headers=DEVPAY_HEADERS)
+assert len(rs) == 5
+# test the lookup method
+k = bucket.lookup('foo/bar', headers=DEVPAY_HEADERS)
+assert isinstance(k, bucket.key_class)
+assert k.content_type == phony_mimetype
+k = bucket.lookup('notthere', headers=DEVPAY_HEADERS)
+assert k == None
+# try some metadata stuff
+k = bucket.new_key()
+k.name = 'has_metadata'
+mdkey1 = 'meta1'
+mdval1 = 'This is the first metadata value'
+k.set_metadata(mdkey1, mdval1)
+mdkey2 = 'meta2'
+mdval2 = 'This is the second metadata value'
+k.set_metadata(mdkey2, mdval2)
+k.set_contents_from_string(s1, headers=DEVPAY_HEADERS)
+k = bucket.lookup('has_metadata', headers=DEVPAY_HEADERS)
+assert k.get_metadata(mdkey1) == mdval1
+assert k.get_metadata(mdkey2) == mdval2
+k = bucket.new_key()
+k.name = 'has_metadata'
+k.get_contents_as_string(headers=DEVPAY_HEADERS)
+assert k.get_metadata(mdkey1) == mdval1
+assert k.get_metadata(mdkey2) == mdval2
+bucket.delete_key(k, headers=DEVPAY_HEADERS)
+# test list and iterator
+rs1 = bucket.list(headers=DEVPAY_HEADERS)
+num_iter = 0
+for r in rs1:
+ num_iter = num_iter + 1
+rs = bucket.get_all_keys(headers=DEVPAY_HEADERS)
+num_keys = len(rs)
+assert num_iter == num_keys
+# try a key with a funny character
+k = bucket.new_key()
+k.name = 'testnewline\n'
+k.set_contents_from_string('This is a test', headers=DEVPAY_HEADERS)
+rs = bucket.get_all_keys(headers=DEVPAY_HEADERS)
+assert len(rs) == num_keys + 1
+bucket.delete_key(k, headers=DEVPAY_HEADERS)
+rs = bucket.get_all_keys(headers=DEVPAY_HEADERS)
+assert len(rs) == num_keys
+# try some acl stuff
+bucket.set_acl('public-read', headers=DEVPAY_HEADERS)
+policy = bucket.get_acl(headers=DEVPAY_HEADERS)
+assert len(policy.acl.grants) == 2
+bucket.set_acl('private', headers=DEVPAY_HEADERS)
+policy = bucket.get_acl(headers=DEVPAY_HEADERS)
+assert len(policy.acl.grants) == 1
+k = bucket.lookup('foo/bar', headers=DEVPAY_HEADERS)
+k.set_acl('public-read', headers=DEVPAY_HEADERS)
+policy = k.get_acl(headers=DEVPAY_HEADERS)
+assert len(policy.acl.grants) == 2
+k.set_acl('private', headers=DEVPAY_HEADERS)
+policy = k.get_acl(headers=DEVPAY_HEADERS)
+assert len(policy.acl.grants) == 1
+# try the convenience methods for grants
+# this doesn't work with devpay
+#bucket.add_user_grant('FULL_CONTROL',
+# 'c1e724fbfa0979a4448393c59a8c055011f739b6d102fb37a65f26414653cd67',
+# headers=DEVPAY_HEADERS)
+try:
+ bucket.add_email_grant('foobar', 'foo@bar.com', headers=DEVPAY_HEADERS)
+except S3PermissionsError:
+ pass
+# now delete all keys in bucket
+for k in all:
+ bucket.delete_key(k, headers=DEVPAY_HEADERS)
+# now delete bucket
+
+c.delete_bucket(bucket, headers=DEVPAY_HEADERS)
+
+print '--- tests completed ---'
diff --git a/vendor/boto/boto/tests/test.py b/vendor/boto/boto/tests/test.py
new file mode 100755
index 0000000000..e3c3ce7977
--- /dev/null
+++ b/vendor/boto/boto/tests/test.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+do the unit tests!
+"""
+
+import sys, os, unittest
+import getopt, sys
+import boto
+
+from boto.tests.test_sqsconnection import SQSConnectionTest
+from boto.tests.test_s3connection import S3ConnectionTest
+from boto.tests.test_s3versioning import S3VersionTest
+from boto.tests.test_ec2connection import EC2ConnectionTest
+from boto.tests.test_sdbconnection import SDBConnectionTest
+
+def usage():
+ print 'test.py [-t testsuite] [-v verbosity]'
+ print ' -t run specific testsuite (s3|sqs|ec2|sdb|all)'
+ print ' -v verbosity (0|1|2)'
+
+def main():
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'ht:v:',
+ ['help', 'testsuite', 'verbosity'])
+ except:
+ usage()
+ sys.exit(2)
+ testsuite = 'all'
+ verbosity = 1
+ for o, a in opts:
+ if o in ('-h', '--help'):
+ usage()
+ sys.exit()
+ if o in ('-t', '--testsuite'):
+ testsuite = a
+ if o in ('-v', '--verbosity'):
+ verbosity = int(a)
+ if len(args) != 0:
+ usage()
+ sys.exit()
+ suite = unittest.TestSuite()
+ if testsuite == 'all':
+ suite.addTest(unittest.makeSuite(SQSConnectionTest))
+ suite.addTest(unittest.makeSuite(S3ConnectionTest))
+ suite.addTest(unittest.makeSuite(EC2ConnectionTest))
+ suite.addTest(unittest.makeSuite(SDBConnectionTest))
+ elif testsuite == 's3':
+ suite.addTest(unittest.makeSuite(S3ConnectionTest))
+ suite.addTest(unittest.makeSuite(S3VersionTest))
+ elif testsuite == 's3ver':
+ suite.addTest(unittest.makeSuite(S3VersionTest))
+ elif testsuite == 'sqs':
+ suite.addTest(unittest.makeSuite(SQSConnectionTest))
+ elif testsuite == 'ec2':
+ suite.addTest(unittest.makeSuite(EC2ConnectionTest))
+ elif testsuite == 'sdb':
+ suite.addTest(unittest.makeSuite(SDBConnectionTest))
+ else:
+ usage()
+ sys.exit()
+ unittest.TextTestRunner(verbosity=verbosity).run(suite)
+
+if __name__ == "__main__":
+ main()
diff --git a/vendor/boto/boto/tests/test_ec2connection.py b/vendor/boto/boto/tests/test_ec2connection.py
new file mode 100644
index 0000000000..db6e2af788
--- /dev/null
+++ b/vendor/boto/boto/tests/test_ec2connection.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Some unit tests for the EC2Connection
+"""
+
+import unittest
+import time
+import os
+from boto.ec2.connection import EC2Connection
+import telnetlib
+import socket
+
+class EC2ConnectionTest (unittest.TestCase):
+
+ def test_1_basic(self):
+ # this is my user_id, if you want to run these tests you should
+ # replace this with yours or they won't work
+ user_id = '963068290131'
+ print '--- running EC2Connection tests ---'
+ c = EC2Connection()
+ # get list of private AMI's
+ rs = c.get_all_images(owners=[user_id])
+ assert len(rs) > 0
+ # now pick the first one
+ image = rs[0]
+ # temporarily make this image runnable by everyone
+ status = image.set_launch_permissions(group_names=['all'])
+ assert status
+ d = image.get_launch_permissions()
+ assert d.has_key('groups')
+ assert len(d['groups']) > 0
+ # now remove that permission
+ status = image.remove_launch_permissions(group_names=['all'])
+ assert status
+ d = image.get_launch_permissions()
+ assert not d.has_key('groups')
+
+ # create a new security group
+ group_name = 'test-%d' % int(time.time())
+ group_desc = 'This is a security group created during unit testing'
+ group = c.create_security_group(group_name, group_desc)
+ # now get a listing of all security groups and look for our new one
+ rs = c.get_all_security_groups()
+ found = False
+ for g in rs:
+ if g.name == group_name:
+ found = True
+ assert found
+ # now pass arg to filter results to only our new group
+ rs = c.get_all_security_groups([group_name])
+ assert len(rs) == 1
+ group = rs[0]
+ #
+ # now delete the security group
+ status = c.delete_security_group(group_name)
+ # now make sure it's really gone
+ rs = c.get_all_security_groups()
+ found = False
+ for g in rs:
+ if g.name == group_name:
+ found = True
+ assert not found
+ # now create it again for use with the instance test
+ group = c.create_security_group(group_name, group_desc)
+
+ # now try to launch apache image with our new security group
+ rs = c.get_all_images()
+ img_loc = 'ec2-public-images/fedora-core4-apache.manifest.xml'
+ for image in rs:
+ if image.location == img_loc:
+ break
+ reservation = image.run(security_groups=[group.name])
+ instance = reservation.instances[0]
+ while instance.state != 'running':
+ print '\tinstance is %s' % instance.state
+ time.sleep(30)
+ instance.update()
+ # instance in now running, try to telnet to port 80
+ t = telnetlib.Telnet()
+ try:
+ t.open(instance.dns_name, 80)
+ except socket.error:
+ pass
+ # now open up port 80 and try again, it should work
+ group.authorize('tcp', 80, 80, '0.0.0.0/0')
+ t.open(instance.dns_name, 80)
+ t.close()
+ # now revoke authorization and try again
+ group.revoke('tcp', 80, 80, '0.0.0.0/0')
+ try:
+ t.open(instance.dns_name, 80)
+ except socket.error:
+ pass
+ # now kill the instance and delete the security group
+ instance.stop()
+ # unfortunately, I can't delete the sg within this script
+ #sg.delete()
+
+ # create a new key pair
+ key_name = 'test-%d' % int(time.time())
+ status = c.create_key_pair(key_name)
+ assert status
+ # now get a listing of all key pairs and look for our new one
+ rs = c.get_all_key_pairs()
+ found = False
+ for k in rs:
+ if k.name == key_name:
+ found = True
+ assert found
+ # now pass arg to filter results to only our new key pair
+ rs = c.get_all_key_pairs([key_name])
+ assert len(rs) == 1
+ key_pair = rs[0]
+ # now delete the key pair
+ status = c.delete_key_pair(key_name)
+ # now make sure it's really gone
+ rs = c.get_all_key_pairs()
+ found = False
+ for k in rs:
+ if k.name == key_name:
+ found = True
+ assert not found
+
+ # short test around Paid AMI capability
+ demo_paid_ami_id = 'ami-bd9d78d4'
+ demo_paid_ami_product_code = 'A79EC0DB'
+ l = c.get_all_images([demo_paid_ami_id])
+ assert len(l) == 1
+ assert len(l[0].product_codes) == 1
+ assert l[0].product_codes[0] == demo_paid_ami_product_code
+
+ print '--- tests completed ---'
diff --git a/vendor/boto/boto/tests/test_s3connection.py b/vendor/boto/boto/tests/test_s3connection.py
new file mode 100644
index 0000000000..a952d65d64
--- /dev/null
+++ b/vendor/boto/boto/tests/test_s3connection.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Some unit tests for the S3Connection
+"""
+
+import unittest
+import time
+import os
+import urllib
+from boto.s3.connection import S3Connection
+from boto.exception import S3PermissionsError
+
+class S3ConnectionTest (unittest.TestCase):
+
+ def test_1_basic(self):
+ print '--- running S3Connection tests ---'
+ c = S3Connection()
+ # create a new, empty bucket
+ bucket_name = 'test-%d' % int(time.time())
+ bucket = c.create_bucket(bucket_name)
+ # now try a get_bucket call and see if it's really there
+ bucket = c.get_bucket(bucket_name)
+ # test logging
+ logging_bucket = c.create_bucket(bucket_name + '-log')
+ logging_bucket.set_as_logging_target()
+ bucket.enable_logging(target_bucket=logging_bucket, target_prefix=bucket.name)
+ bucket.disable_logging()
+ c.delete_bucket(logging_bucket)
+ k = bucket.new_key()
+ k.name = 'foobar'
+ s1 = 'This is a test of file upload and download'
+ s2 = 'This is a second string to test file upload and download'
+ k.set_contents_from_string(s1)
+ fp = open('foobar', 'wb')
+ # now get the contents from s3 to a local file
+ k.get_contents_to_file(fp)
+ fp.close()
+ fp = open('foobar')
+ # check to make sure content read from s3 is identical to original
+ assert s1 == fp.read(), 'corrupted file'
+ fp.close()
+ # test generated URLs
+ url = k.generate_url(3600)
+ file = urllib.urlopen(url)
+ assert s1 == file.read(), 'invalid URL %s' % url
+ url = k.generate_url(3600, force_http=True)
+ file = urllib.urlopen(url)
+ assert s1 == file.read(), 'invalid URL %s' % url
+ bucket.delete_key(k)
+ # test a few variations on get_all_keys - first load some data
+ # for the first one, let's override the content type
+ phony_mimetype = 'application/x-boto-test'
+ headers = {'Content-Type': phony_mimetype}
+ k.name = 'foo/bar'
+ k.set_contents_from_string(s1, headers)
+ k.name = 'foo/bas'
+ k.set_contents_from_filename('foobar')
+ k.name = 'foo/bat'
+ k.set_contents_from_string(s1)
+ k.name = 'fie/bar'
+ k.set_contents_from_string(s1)
+ k.name = 'fie/bas'
+ k.set_contents_from_string(s1)
+ k.name = 'fie/bat'
+ k.set_contents_from_string(s1)
+ # try resetting the contents to another value
+ md5 = k.md5
+ k.set_contents_from_string(s2)
+ assert k.md5 != md5
+ os.unlink('foobar')
+ all = bucket.get_all_keys()
+ assert len(all) == 6
+ rs = bucket.get_all_keys(prefix='foo')
+ assert len(rs) == 3
+ rs = bucket.get_all_keys(prefix='', delimiter='/')
+ assert len(rs) == 2
+ rs = bucket.get_all_keys(maxkeys=5)
+ assert len(rs) == 5
+ # test the lookup method
+ k = bucket.lookup('foo/bar')
+ assert isinstance(k, bucket.key_class)
+ assert k.content_type == phony_mimetype
+ k = bucket.lookup('notthere')
+ assert k == None
+ # try some metadata stuff
+ k = bucket.new_key()
+ k.name = 'has_metadata'
+ mdkey1 = 'meta1'
+ mdval1 = 'This is the first metadata value'
+ k.set_metadata(mdkey1, mdval1)
+ mdkey2 = 'meta2'
+ mdval2 = 'This is the second metadata value'
+ k.set_metadata(mdkey2, mdval2)
+ # try a unicode metadata value
+ mdval3 = u'föö'
+ mdkey3 = 'meta3'
+ k.set_metadata(mdkey3, mdval3)
+ k.set_contents_from_string(s1)
+ k = bucket.lookup('has_metadata')
+ assert k.get_metadata(mdkey1) == mdval1
+ assert k.get_metadata(mdkey2) == mdval2
+ assert k.get_metadata(mdkey3) == mdval3
+ k = bucket.new_key()
+ k.name = 'has_metadata'
+ k.get_contents_as_string()
+ assert k.get_metadata(mdkey1) == mdval1
+ assert k.get_metadata(mdkey2) == mdval2
+ assert k.get_metadata(mdkey3) == mdval3
+ bucket.delete_key(k)
+ # test list and iterator
+ rs1 = bucket.list()
+ num_iter = 0
+ for r in rs1:
+ num_iter = num_iter + 1
+ rs = bucket.get_all_keys()
+ num_keys = len(rs)
+ assert num_iter == num_keys
+ # try a key with a funny character
+ k = bucket.new_key()
+ k.name = 'testnewline\n'
+ k.set_contents_from_string('This is a test')
+ rs = bucket.get_all_keys()
+ assert len(rs) == num_keys + 1
+ bucket.delete_key(k)
+ rs = bucket.get_all_keys()
+ assert len(rs) == num_keys
+ # try some acl stuff
+ bucket.set_acl('public-read')
+ policy = bucket.get_acl()
+ assert len(policy.acl.grants) == 2
+ bucket.set_acl('private')
+ policy = bucket.get_acl()
+ assert len(policy.acl.grants) == 1
+ k = bucket.lookup('foo/bar')
+ k.set_acl('public-read')
+ policy = k.get_acl()
+ assert len(policy.acl.grants) == 2
+ k.set_acl('private')
+ policy = k.get_acl()
+ assert len(policy.acl.grants) == 1
+ # try the convenience methods for grants
+ bucket.add_user_grant('FULL_CONTROL',
+ 'c1e724fbfa0979a4448393c59a8c055011f739b6d102fb37a65f26414653cd67')
+ try:
+ bucket.add_email_grant('foobar', 'foo@bar.com')
+ except S3PermissionsError:
+ pass
+ # now delete all keys in bucket
+ for k in all:
+ bucket.delete_key(k)
+ # now delete bucket
+ c.delete_bucket(bucket)
+ print '--- tests completed ---'
diff --git a/vendor/boto/boto/tests/test_s3versioning.py b/vendor/boto/boto/tests/test_s3versioning.py
new file mode 100644
index 0000000000..02abedd305
--- /dev/null
+++ b/vendor/boto/boto/tests/test_s3versioning.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Some unit tests for the S3 Versioning and MfaDelete
+"""
+
+import unittest
+import time
+import os
+import urllib
+from boto.s3.connection import S3Connection
+from boto.exception import S3ResponseError
+from boto.s3.deletemarker import DeleteMarker
+
+class S3VersionTest (unittest.TestCase):
+
+ def test_1_versions(self):
+ print '--- running S3Version tests ---'
+ c = S3Connection()
+ # create a new, empty bucket
+ bucket_name = 'version-%d' % int(time.time())
+ bucket = c.create_bucket(bucket_name)
+
+ # now try a get_bucket call and see if it's really there
+ bucket = c.get_bucket(bucket_name)
+
+ # enable versions
+ d = bucket.get_versioning_status()
+ assert not d.has_key('Versioning')
+ bucket.configure_versioning(versioning=True)
+ time.sleep(5)
+ d = bucket.get_versioning_status()
+ assert d['Versioning'] == 'Enabled'
+
+ # create a new key in the versioned bucket
+ k = bucket.new_key()
+ k.name = 'foobar'
+ s1 = 'This is a test of s3 versioning'
+ s2 = 'This is the second test of s3 versioning'
+ k.set_contents_from_string(s1)
+ time.sleep(5)
+
+ # remember the version id of this object
+ v1 = k.version_id
+
+ # now get the contents from s3
+ o1 = k.get_contents_as_string()
+
+ # check to make sure content read from s3 is identical to original
+ assert o1 == s1
+
+ # now overwrite that same key with new data
+ k.set_contents_from_string(s2)
+ v2 = k.version_id
+ time.sleep(5)
+
+ # now retrieve the contents as a string and compare
+ s3 = k.get_contents_as_string()
+ assert s3 == s2
+
+ # Now list all versions and compare to what we have
+ rs = bucket.get_all_versions()
+ assert rs[0].version_id == v2
+ assert rs[1].version_id == v1
+
+ # Now do a regular list command and make sure only the new key shows up
+ rs = bucket.get_all_keys()
+ assert len(rs) == 1
+
+ # Now do regular delete
+ bucket.delete_key('foobar')
+ time.sleep(5)
+
+ # Now list versions and make sure old versions are there
+ # plus the DeleteMarker
+ rs = bucket.get_all_versions()
+ assert len(rs) == 3
+ assert isinstance(rs[0], DeleteMarker)
+
+ # Now delete v1 of the key
+ bucket.delete_key('foobar', version_id=v1)
+ time.sleep(5)
+
+ # Now list versions again and make sure v1 is not there
+ rs = bucket.get_all_versions()
+ versions = [k.version_id for k in rs]
+ assert v1 not in versions
+ assert v2 in versions
+
+ # Now try to enable MfaDelete
+ mfa_sn = raw_input('MFA S/N: ')
+ mfa_code = raw_input('MFA Code: ')
+ bucket.configure_versioning(True, mfa_delete=True, mfa_token=(mfa_sn, mfa_code))
+ time.sleep(5)
+ d = bucket.get_versioning_status()
+ assert d['Versioning'] == 'Enabled'
+ assert d['MfaDelete'] == 'Enabled'
+
+ # Now try to delete v2 without the MFA token
+ try:
+ bucket.delete_key('foobar', version_id=v2)
+ except S3ResponseError:
+ pass
+
+ # Now try to delete v2 with the MFA token
+ mfa_code = raw_input('MFA Code: ')
+ bucket.delete_key('foobar', version_id=v2, mfa_token=(mfa_sn, mfa_code))
+
+ # Now disable MfaDelete on the bucket
+ mfa_code = raw_input('MFA Code: ')
+ bucket.configure_versioning(True, mfa_delete=False, mfa_token=(mfa_sn, mfa_code))
+
+ # Now suspend Versioning on the bucket
+ bucket.configure_versioning(False)
+
+ # now delete all keys and deletemarkers in bucket
+ for k in bucket.list_versions():
+ bucket.delete_key(k.name, version_id=k.version_id)
+
+ # now delete bucket
+ c.delete_bucket(bucket)
+ print '--- tests completed ---'
diff --git a/vendor/boto/boto/tests/test_sdbconnection.py b/vendor/boto/boto/tests/test_sdbconnection.py
new file mode 100644
index 0000000000..c2bb74e398
--- /dev/null
+++ b/vendor/boto/boto/tests/test_sdbconnection.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Some unit tests for the SDBConnection
+"""
+
+import unittest
+import time
+from boto.sdb.connection import SDBConnection
+from boto.exception import SDBResponseError
+
+class SDBConnectionTest (unittest.TestCase):
+
+ def test_1_basic(self):
+ print '--- running SDBConnection tests ---'
+ c = SDBConnection()
+ rs = c.get_all_domains()
+ num_domains = len(rs)
+
+ # try illegal name
+ try:
+ domain = c.create_domain('bad:domain:name')
+ except SDBResponseError:
+ pass
+
+ # now create one that should work and should be unique (i.e. a new one)
+ domain_name = 'test%d' % int(time.time())
+ domain = c.create_domain(domain_name)
+ rs = c.get_all_domains()
+ assert len(rs) == num_domains+1
+
+ # now let's a couple of items and attributes
+ item_1 = 'item1'
+ same_value = 'same_value'
+ attrs_1 = {'name1' : same_value, 'name2' : 'diff_value_1'}
+ domain.put_attributes(item_1, attrs_1)
+ item_2 = 'item2'
+ attrs_2 = {'name1' : same_value, 'name2' : 'diff_value_2'}
+ domain.put_attributes(item_2, attrs_2)
+ time.sleep(10)
+
+ # try to get the attributes and see if they match
+ item = domain.get_attributes(item_1)
+ assert len(item.keys()) == len(attrs_1.keys())
+ assert item['name1'] == attrs_1['name1']
+ assert item['name2'] == attrs_1['name2']
+
+ # try a search or two
+ rs = domain.query("['name1'='%s']" % same_value)
+ n = 0
+ for item in rs:
+ n += 1
+ assert n == 2
+ rs = domain.query("['name2'='diff_value_2']")
+ n = 0
+ for item in rs:
+ n += 1
+ assert n == 1
+
+ # delete all attributes associated with item_1
+ stat = domain.delete_attributes(item_1)
+ assert stat
+
+ # now try a batch put operation on the domain
+ item3 = {'name3_1' : 'value3_1',
+ 'name3_2' : 'value3_2',
+ 'name3_3' : ['value3_3_1', 'value3_3_2']}
+
+ item4 = {'name4_1' : 'value4_1',
+ 'name4_2' : ['value4_2_1', 'value4_2_2'],
+ 'name4_3' : 'value4_3'}
+ items = {'item3' : item3, 'item4' : item4}
+ domain.batch_put_attributes(items)
+ time.sleep(10)
+ item = domain.get_attributes('item3')
+ assert item['name3_2'] == 'value3_2'
+
+ # now delete the domain
+ stat = c.delete_domain(domain)
+ assert stat
+
+ print '--- tests completed ---'
+
diff --git a/vendor/boto/boto/tests/test_sqsconnection.py b/vendor/boto/boto/tests/test_sqsconnection.py
new file mode 100644
index 0000000000..0fbd1f1afa
--- /dev/null
+++ b/vendor/boto/boto/tests/test_sqsconnection.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Some unit tests for the SQSConnection
+"""
+
+import unittest
+import time
+from boto.sqs.connection import SQSConnection
+from boto.sqs.message import MHMessage
+from boto.exception import SQSError
+
+class SQSConnectionTest (unittest.TestCase):
+
+ def test_1_basic(self):
+ print '--- running SQSConnection tests ---'
+ c = SQSConnection()
+ rs = c.get_all_queues()
+ num_queues = 0
+ for q in rs:
+ num_queues += 1
+
+ # try illegal name
+ try:
+ queue = c.create_queue('bad_queue_name')
+ except SQSError:
+ pass
+
+ # now create one that should work and should be unique (i.e. a new one)
+ queue_name = 'test%d' % int(time.time())
+ timeout = 60
+ queue = c.create_queue(queue_name, timeout)
+ time.sleep(60)
+ rs = c.get_all_queues()
+ i = 0
+ for q in rs:
+ i += 1
+ assert i == num_queues+1
+ assert queue.count_slow() == 0
+
+ # check the visibility timeout
+ t = queue.get_timeout()
+ assert t == timeout, '%d != %d' % (t, timeout)
+
+ # now try to get queue attributes
+ a = q.get_attributes()
+ assert a.has_key('ApproximateNumberOfMessages')
+ assert a.has_key('VisibilityTimeout')
+ a = q.get_attributes('ApproximateNumberOfMessages')
+ assert a.has_key('ApproximateNumberOfMessages')
+ assert not a.has_key('VisibilityTimeout')
+ a = q.get_attributes('VisibilityTimeout')
+ assert not a.has_key('ApproximateNumberOfMessages')
+ assert a.has_key('VisibilityTimeout')
+
+ # now change the visibility timeout
+ timeout = 45
+ queue.set_timeout(timeout)
+ time.sleep(60)
+ t = queue.get_timeout()
+ assert t == timeout, '%d != %d' % (t, timeout)
+
+ # now add a message
+ message_body = 'This is a test\n'
+ message = queue.new_message(message_body)
+ queue.write(message)
+ time.sleep(30)
+ assert queue.count_slow() == 1
+ time.sleep(30)
+
+ # now read the message from the queue with a 10 second timeout
+ message = queue.read(visibility_timeout=10)
+ assert message
+ assert message.get_body() == message_body
+
+ # now immediately try another read, shouldn't find anything
+ message = queue.read()
+ assert message == None
+
+ # now wait 30 seconds and try again
+ time.sleep(30)
+ message = queue.read()
+ assert message
+
+ if c.APIVersion == '2007-05-01':
+ # now terminate the visibility timeout for this message
+ message.change_visibility(0)
+ # now see if we can read it in the queue
+ message = queue.read()
+ assert message
+
+ # now delete the message
+ queue.delete_message(message)
+ time.sleep(30)
+ assert queue.count_slow() == 0
+
+ # create another queue so we can test force deletion
+ # we will also test MHMessage with this queue
+ queue_name = 'test%d' % int(time.time())
+ timeout = 60
+ queue = c.create_queue(queue_name, timeout)
+ queue.set_message_class(MHMessage)
+ time.sleep(30)
+
+ # now add a couple of messages
+ message = queue.new_message()
+ message['foo'] = 'bar'
+ queue.write(message)
+ message_body = {'fie' : 'baz', 'foo' : 'bar'}
+ message = queue.new_message(body=message_body)
+ queue.write(message)
+ time.sleep(30)
+
+ m = queue.read()
+ assert m['foo'] == 'bar'
+
+ # now delete that queue and messages
+ c.delete_queue(queue, True)
+
+ print '--- tests completed ---'
+
diff --git a/vendor/boto/boto/utils.py b/vendor/boto/boto/utils.py
new file mode 100644
index 0000000000..255d42f641
--- /dev/null
+++ b/vendor/boto/boto/utils.py
@@ -0,0 +1,561 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+#
+# Parts of this code were copied or derived from sample code supplied by AWS.
+# The following notice applies to that code.
+#
+# This software code is made available "AS IS" without warranties of any
+# kind. You may copy, display, modify and redistribute the software
+# code either by itself or as incorporated into your code; provided that
+# you do not remove any proprietary notices. Your use of this software
+# code is at your own risk and you waive any claim against Amazon
+# Digital Services, Inc. or its affiliates with respect to your use of
+# this software code. (c) 2006 Amazon Digital Services, Inc. or its
+# affiliates.
+
+"""
+Some handy utility functions used by several classes.
+"""
+
+import re
+import urllib
+import urllib2
+import subprocess
+import StringIO
+import time
+import logging.handlers
+import boto
+import tempfile
+import smtplib
+import datetime
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEBase import MIMEBase
+from email.MIMEText import MIMEText
+from email.Utils import formatdate
+from email import Encoders
+
+try:
+ import hashlib
+ _hashfn = hashlib.sha512
+except ImportError:
+ import md5
+ _hashfn = md5.md5
+
+METADATA_PREFIX = 'x-amz-meta-'
+AMAZON_HEADER_PREFIX = 'x-amz-'
+
+# generates the aws canonical string for the given parameters
+def canonical_string(method, path, headers, expires=None):
+ interesting_headers = {}
+ for key in headers:
+ lk = key.lower()
+ if lk in ['content-md5', 'content-type', 'date'] or lk.startswith(AMAZON_HEADER_PREFIX):
+ interesting_headers[lk] = headers[key].strip()
+
+ # these keys get empty strings if they don't exist
+ if not interesting_headers.has_key('content-type'):
+ interesting_headers['content-type'] = ''
+ if not interesting_headers.has_key('content-md5'):
+ interesting_headers['content-md5'] = ''
+
+ # just in case someone used this. it's not necessary in this lib.
+ if interesting_headers.has_key('x-amz-date'):
+ interesting_headers['date'] = ''
+
+ # if you're using expires for query string auth, then it trumps date
+ # (and x-amz-date)
+ if expires:
+ interesting_headers['date'] = str(expires)
+
+ sorted_header_keys = interesting_headers.keys()
+ sorted_header_keys.sort()
+
+ buf = "%s\n" % method
+ for key in sorted_header_keys:
+ val = interesting_headers[key]
+ if key.startswith(AMAZON_HEADER_PREFIX):
+ buf += "%s:%s\n" % (key, val)
+ else:
+ buf += "%s\n" % val
+
+ # don't include anything after the first ? in the resource...
+ buf += "%s" % path.split('?')[0]
+
+ # ...unless there is an acl or torrent parameter
+ if re.search("[&?]acl($|=|&)", path):
+ buf += "?acl"
+ elif re.search("[&?]logging($|=|&)", path):
+ buf += "?logging"
+ elif re.search("[&?]torrent($|=|&)", path):
+ buf += "?torrent"
+ elif re.search("[&?]location($|=|&)", path):
+ buf += "?location"
+ elif re.search("[&?]requestPayment($|=|&)", path):
+ buf += "?requestPayment"
+ elif re.search("[&?]versions($|=|&)", path):
+ buf += "?versions"
+ elif re.search("[&?]versioning($|=|&)", path):
+ buf += "?versioning"
+ else:
+ m = re.search("[&?]versionId=([^&]+)($|=|&)", path)
+ if m:
+ buf += '?versionId=' + m.group(1)
+
+ return buf
+
+def merge_meta(headers, metadata):
+ final_headers = headers.copy()
+ for k in metadata.keys():
+ if k.lower() in ['cache-control', 'content-md5', 'content-type',
+ 'content-encoding', 'content-disposition',
+ 'date', 'expires']:
+ final_headers[k] = metadata[k]
+ else:
+ final_headers[METADATA_PREFIX + k] = metadata[k]
+
+ return final_headers
+
+def get_aws_metadata(headers):
+ metadata = {}
+ for hkey in headers.keys():
+ if hkey.lower().startswith(METADATA_PREFIX):
+ val = urllib.unquote_plus(headers[hkey])
+ metadata[hkey[len(METADATA_PREFIX):]] = unicode(val, 'utf-8')
+ del headers[hkey]
+ return metadata
+
+def retry_url(url, retry_on_404=True):
+ for i in range(0, 10):
+ try:
+ req = urllib2.Request(url)
+ resp = urllib2.urlopen(req)
+ return resp.read()
+ except urllib2.HTTPError, e:
+ # in 2.6 you use getcode(), in 2.5 and earlier you use code
+ if hasattr(e, 'getcode'):
+ code = e.getcode()
+ else:
+ code = e.code
+ if code == 404 and not retry_on_404:
+ return ''
+ except:
+ pass
+ boto.log.exception('Caught exception reading instance data')
+ time.sleep(2**i)
+ boto.log.error('Unable to read instance data, giving up')
+ return ''
+
+def _get_instance_metadata(url):
+ d = {}
+ data = retry_url(url)
+ if data:
+ fields = data.split('\n')
+ for field in fields:
+ if field.endswith('/'):
+ d[field[0:-1]] = _get_instance_metadata(url + field)
+ else:
+ p = field.find('=')
+ if p > 0:
+ key = field[p+1:]
+ resource = field[0:p] + '/openssh-key'
+ else:
+ key = resource = field
+ val = retry_url(url + resource)
+ p = val.find('\n')
+ if p > 0:
+ val = val.split('\n')
+ d[key] = val
+ return d
+
+def get_instance_metadata(version='latest'):
+ """
+ Returns the instance metadata as a nested Python dictionary.
+ Simple values (e.g. local_hostname, hostname, etc.) will be
+ stored as string values. Values such as ancestor-ami-ids will
+ be stored in the dict as a list of string values. More complex
+ fields such as public-keys and will be stored as nested dicts.
+ """
+ url = 'http://169.254.169.254/%s/meta-data/' % version
+ return _get_instance_metadata(url)
+
+def get_instance_userdata(version='latest', sep=None):
+ url = 'http://169.254.169.254/%s/user-data' % version
+ user_data = retry_url(url, retry_on_404=False)
+ if user_data:
+ if sep:
+ l = user_data.split(sep)
+ user_data = {}
+ for nvpair in l:
+ t = nvpair.split('=')
+ user_data[t[0].strip()] = t[1].strip()
+ return user_data
+
+ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
+
+def get_ts(ts=None):
+ if not ts:
+ ts = time.gmtime()
+ return time.strftime(ISO8601, ts)
+
+def parse_ts(ts):
+ return datetime.datetime.strptime(ts, ISO8601)
+
+def find_class(module_name, class_name=None):
+ if class_name:
+ module_name = "%s.%s" % (module_name, class_name)
+ modules = module_name.split('.')
+ c = None
+
+ try:
+ for m in modules[1:]:
+ if c:
+ c = getattr(c, m)
+ else:
+ c = getattr(__import__(".".join(modules[0:-1])), m)
+ return c
+ except:
+ return None
+
+def update_dme(username, password, dme_id, ip_address):
+ """
+ Update your Dynamic DNS record with DNSMadeEasy.com
+ """
+ dme_url = 'https://www.dnsmadeeasy.com/servlet/updateip'
+ dme_url += '?username=%s&password=%s&id=%s&ip=%s'
+ s = urllib2.urlopen(dme_url % (username, password, dme_id, ip_address))
+ return s.read()
+
+def fetch_file(uri, file=None, username=None, password=None):
+ """
+ Fetch a file based on the URI provided. If you do not pass in a file pointer
+ a tempfile.NamedTemporaryFile, or None if the file could not be
+ retrieved is returned.
+ The URI can be either an HTTP url, or "s3://bucket_name/key_name"
+ """
+ boto.log.info('Fetching %s' % uri)
+ if file == None:
+ file = tempfile.NamedTemporaryFile()
+ try:
+ if uri.startswith('s3://'):
+ bucket_name, key_name = uri[len('s3://'):].split('/', 1)
+ c = boto.connect_s3()
+ bucket = c.get_bucket(bucket_name)
+ key = bucket.get_key(key_name)
+ key.get_contents_to_file(file)
+ else:
+ if username and password:
+ passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
+ passman.add_password(None, uri, username, password)
+ authhandler = urllib2.HTTPBasicAuthHandler(passman)
+ opener = urllib2.build_opener(authhandler)
+ urllib2.install_opener(opener)
+ s = urllib2.urlopen(uri)
+ file.write(s.read())
+ file.seek(0)
+ except:
+ raise
+ boto.log.exception('Problem Retrieving file: %s' % uri)
+ file = None
+ return file
+
+class ShellCommand(object):
+
+ def __init__(self, command, wait=True):
+ self.exit_code = 0
+ self.command = command
+ self.log_fp = StringIO.StringIO()
+ self.wait = wait
+ self.run()
+
+ def run(self):
+ boto.log.info('running:%s' % self.command)
+ self.process = subprocess.Popen(self.command, shell=True, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ if(self.wait):
+ while self.process.poll() == None:
+ time.sleep(1)
+ t = self.process.communicate()
+ self.log_fp.write(t[0])
+ self.log_fp.write(t[1])
+ boto.log.info(self.log_fp.getvalue())
+ self.exit_code = self.process.returncode
+ return self.exit_code
+
+ def setReadOnly(self, value):
+ raise AttributeError
+
+ def getStatus(self):
+ return self.exit_code
+
+ status = property(getStatus, setReadOnly, None, 'The exit code for the command')
+
+ def getOutput(self):
+ return self.log_fp.getvalue()
+
+ output = property(getOutput, setReadOnly, None, 'The STDIN and STDERR output of the command')
+
+class AuthSMTPHandler(logging.handlers.SMTPHandler):
+ """
+ This class extends the SMTPHandler in the standard Python logging module
+ to accept a username and password on the constructor and to then use those
+ credentials to authenticate with the SMTP server. To use this, you could
+ add something like this in your boto config file:
+
+ [handler_hand07]
+ class=boto.utils.AuthSMTPHandler
+ level=WARN
+ formatter=form07
+ args=('localhost', 'username', 'password', 'from@abc', ['user1@abc', 'user2@xyz'], 'Logger Subject')
+ """
+
+ def __init__(self, mailhost, username, password, fromaddr, toaddrs, subject):
+ """
+ Initialize the handler.
+
+ We have extended the constructor to accept a username/password
+ for SMTP authentication.
+ """
+ logging.handlers.SMTPHandler.__init__(self, mailhost, fromaddr, toaddrs, subject)
+ self.username = username
+ self.password = password
+
+ def emit(self, record):
+ """
+ Emit a record.
+
+ Format the record and send it to the specified addressees.
+ It would be really nice if I could add authorization to this class
+ without having to resort to cut and paste inheritance but, no.
+ """
+ try:
+ port = self.mailport
+ if not port:
+ port = smtplib.SMTP_PORT
+ smtp = smtplib.SMTP(self.mailhost, port)
+ smtp.login(self.username, self.password)
+ msg = self.format(record)
+ msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % (
+ self.fromaddr,
+ ','.join(self.toaddrs),
+ self.getSubject(record),
+ formatdate(), msg)
+ smtp.sendmail(self.fromaddr, self.toaddrs, msg)
+ smtp.quit()
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ self.handleError(record)
+
+class LRUCache(dict):
+ """A dictionary-like object that stores only a certain number of items, and
+ discards its least recently used item when full.
+
+ >>> cache = LRUCache(3)
+ >>> cache['A'] = 0
+ >>> cache['B'] = 1
+ >>> cache['C'] = 2
+ >>> len(cache)
+ 3
+
+ >>> cache['A']
+ 0
+
+ Adding new items to the cache does not increase its size. Instead, the least
+ recently used item is dropped:
+
+ >>> cache['D'] = 3
+ >>> len(cache)
+ 3
+ >>> 'B' in cache
+ False
+
+ Iterating over the cache returns the keys, starting with the most recently
+ used:
+
+ >>> for key in cache:
+ ... print key
+ D
+ A
+ C
+
+ This code is based on the LRUCache class from Genshi which is based on
+ Mighty's LRUCache from ``myghtyutils.util``, written
+ by Mike Bayer and released under the MIT license (Genshi uses the
+ BSD License). See:
+
+ http://svn.myghty.org/myghtyutils/trunk/lib/myghtyutils/util.py
+ """
+
+ class _Item(object):
+ def __init__(self, key, value):
+ self.previous = self.next = None
+ self.key = key
+ self.value = value
+ def __repr__(self):
+ return repr(self.value)
+
+ def __init__(self, capacity):
+ self._dict = dict()
+ self.capacity = capacity
+ self.head = None
+ self.tail = None
+
+ def __contains__(self, key):
+ return key in self._dict
+
+ def __iter__(self):
+ cur = self.head
+ while cur:
+ yield cur.key
+ cur = cur.next
+
+ def __len__(self):
+ return len(self._dict)
+
+ def __getitem__(self, key):
+ item = self._dict[key]
+ self._update_item(item)
+ return item.value
+
+ def __setitem__(self, key, value):
+ item = self._dict.get(key)
+ if item is None:
+ item = self._Item(key, value)
+ self._dict[key] = item
+ self._insert_item(item)
+ else:
+ item.value = value
+ self._update_item(item)
+ self._manage_size()
+
+ def __repr__(self):
+ return repr(self._dict)
+
+ def _insert_item(self, item):
+ item.previous = None
+ item.next = self.head
+ if self.head is not None:
+ self.head.previous = item
+ else:
+ self.tail = item
+ self.head = item
+ self._manage_size()
+
+ def _manage_size(self):
+ while len(self._dict) > self.capacity:
+ del self._dict[self.tail.key]
+ if self.tail != self.head:
+ self.tail = self.tail.previous
+ self.tail.next = None
+ else:
+ self.head = self.tail = None
+
+ def _update_item(self, item):
+ if self.head == item:
+ return
+
+ previous = item.previous
+ previous.next = item.next
+ if item.next is not None:
+ item.next.previous = previous
+ else:
+ self.tail = previous
+
+ item.previous = None
+ item.next = self.head
+ self.head.previous = self.head = item
+
+class Password(object):
+ """
+ Password object that stores itself as SHA512 hashed.
+ """
+ def __init__(self, str=None):
+ """
+ Load the string from an initial value, this should be the raw SHA512 hashed password
+ """
+ self.str = str
+
+ def set(self, value):
+ self.str = _hashfn(value).hexdigest()
+
+ def __str__(self):
+ return str(self.str)
+
+ def __eq__(self, other):
+ if other == None:
+ return False
+ return str(_hashfn(other).hexdigest()) == str(self.str)
+
+ def __len__(self):
+ if self.str:
+ return len(self.str)
+ else:
+ return 0
+
+def notify(subject, body=None, html_body=None, to_string=None, attachments=[], append_instance_id=True):
+ if append_instance_id:
+ subject = "[%s] %s" % (boto.config.get_value("Instance", "instance-id"), subject)
+ if not to_string:
+ to_string = boto.config.get_value('Notification', 'smtp_to', None)
+ if to_string:
+ try:
+ from_string = boto.config.get_value('Notification', 'smtp_from', 'boto')
+ msg = MIMEMultipart()
+ msg['From'] = from_string
+ msg['To'] = to_string
+ msg['Date'] = formatdate(localtime=True)
+ msg['Subject'] = subject
+
+ if body:
+ msg.attach(MIMEText(body))
+
+ if html_body:
+ part = MIMEBase('text', 'html')
+ part.set_payload(html_body)
+ Encoders.encode_base64(part)
+ msg.attach(part)
+
+ for part in attachments:
+ msg.attach(part)
+
+ smtp_host = boto.config.get_value('Notification', 'smtp_host', 'localhost')
+
+ # Alternate port support
+ if boto.config.get_value("Notification", "smtp_port"):
+ server = smtplib.SMTP(smtp_host, int(boto.config.get_value("Notification", "smtp_port")))
+ else:
+ server = smtplib.SMTP(smtp_host)
+
+ # TLS support
+ if boto.config.getbool("Notification", "smtp_tls"):
+ server.ehlo()
+ server.starttls()
+ server.ehlo()
+ smtp_user = boto.config.get_value('Notification', 'smtp_user', '')
+ smtp_pass = boto.config.get_value('Notification', 'smtp_pass', '')
+ if smtp_user:
+ server.login(smtp_user, smtp_pass)
+ server.sendmail(from_string, to_string, msg.as_string())
+ server.quit()
+ except:
+ boto.log.exception('notify failed')
+
diff --git a/vendor/boto/boto/vpc/__init__.py b/vendor/boto/boto/vpc/__init__.py
new file mode 100644
index 0000000000..16c420d530
--- /dev/null
+++ b/vendor/boto/boto/vpc/__init__.py
@@ -0,0 +1,473 @@
+# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a connection to the EC2 service.
+"""
+
+from boto.ec2.connection import EC2Connection
+from boto.vpc.vpc import VPC
+from boto.vpc.customergateway import CustomerGateway
+from boto.vpc.vpngateway import VpnGateway, Attachment
+from boto.vpc.dhcpoptions import DhcpOptions
+from boto.vpc.subnet import Subnet
+from boto.vpc.vpnconnection import VpnConnection
+
+class VPCConnection(EC2Connection):
+
+ # VPC methods
+
+ def get_all_vpcs(self, vpc_ids=None, filters=None):
+ """
+ Retrieve information about your VPCs. You can filter results to
+ return information only about those VPCs that match your search
+ parameters. Otherwise, all VPCs associated with your account
+ are returned.
+
+ :type vpc_ids: list
+ :param vpc_ids: A list of strings with the desired VPC ID's
+
+ :type filters: list of tuples
+ :param filters: A list of tuples containing filters. Each tuple
+ consists of a filter key and a filter value.
+ Possible filter keys are:
+
+ - *state*, the state of the VPC (pending or available)
+ - *cidrBlock*, CIDR block of the VPC
+ - *dhcpOptionsId*, the ID of a set of DHCP options
+
+ :rtype: list
+ :return: A list of :class:`boto.vpc.vpc.VPC`
+ """
+ params = {}
+ if vpc_ids:
+ self.build_list_params(params, vpc_ids, 'VpcId')
+ if filters:
+ i = 1
+ for filter in filters:
+ params[('Filter.%d.Key' % i)] = filter[0]
+ params[('Filter.%d.Value.1')] = filter[1]
+ i += 1
+ return self.get_list('DescribeVpcs', params, [('item', VPC)])
+
+ def create_vpc(self, cidr_block):
+ """
+ Create a new Virtual Private Cloud.
+
+ :type cidr_block: str
+ :param cidr_block: A valid CIDR block
+
+ :rtype: The newly created VPC
+ :return: A :class:`boto.vpc.vpc.VPC` object
+ """
+ params = {'CidrBlock' : cidr_block}
+ return self.get_object('CreateVpc', params, VPC)
+
+ def delete_vpc(self, vpc_id):
+ """
+ Delete a Virtual Private Cloud.
+
+ :type vpc_id: str
+ :param vpc_id: The ID of the vpc to be deleted.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'VpcId': vpc_id}
+ return self.get_status('DeleteVpc', params)
+
+ # Customer Gateways
+
+ def get_all_customer_gateways(self, customer_gateway_ids=None, filters=None):
+ """
+ Retrieve information about your CustomerGateways. You can filter results to
+ return information only about those CustomerGateways that match your search
+ parameters. Otherwise, all CustomerGateways associated with your account
+ are returned.
+
+ :type customer_gateway_ids: list
+ :param customer_gateway_ids: A list of strings with the desired CustomerGateway ID's
+
+ :type filters: list of tuples
+ :param filters: A list of tuples containing filters. Each tuple
+ consists of a filter key and a filter value.
+ Possible filter keys are:
+
+ - *state*, the state of the CustomerGateway
+ (pending,available,deleting,deleted)
+ - *type*, the type of customer gateway (ipsec.1)
+ - *ipAddress* the IP address of customer gateway's
+ internet-routable external inteface
+
+ :rtype: list
+ :return: A list of :class:`boto.vpc.customergateway.CustomerGateway`
+ """
+ params = {}
+ if customer_gateway_ids:
+ self.build_list_params(params, customer_gateway_ids, 'CustomerGatewayId')
+ if filters:
+ i = 1
+ for filter in filters:
+ params[('Filter.%d.Key' % i)] = filter[0]
+ params[('Filter.%d.Value.1')] = filter[1]
+ i += 1
+ return self.get_list('DescribeCustomerGateways', params, [('item', CustomerGateway)])
+
+ def create_customer_gateway(self, type, ip_address, bgp_asn):
+ """
+ Create a new Customer Gateway
+
+ :type type: str
+ :param type: Type of VPN Connection. Only valid valid currently is 'ipsec.1'
+
+ :type ip_address: str
+ :param ip_address: Internet-routable IP address for customer's gateway.
+ Must be a static address.
+
+ :type bgp_asn: str
+ :param bgp_asn: Customer gateway's Border Gateway Protocol (BGP)
+ Autonomous System Number (ASN)
+
+ :rtype: The newly created CustomerGateway
+ :return: A :class:`boto.vpc.customergateway.CustomerGateway` object
+ """
+ params = {'Type' : type,
+ 'IpAddress' : ip_address,
+ 'BgpAsn' : bgp_asn}
+ return self.get_object('CreateCustomerGateway', params, CustomerGateway)
+
+ def delete_customer_gateway(self, customer_gateway_id):
+ """
+ Delete a Customer Gateway.
+
+ :type customer_gateway_id: str
+ :param customer_gateway_id: The ID of the customer_gateway to be deleted.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'CustomerGatewayId': customer_gateway_id}
+ return self.get_status('DeleteCustomerGateway', params)
+
+ # VPN Gateways
+
+ def get_all_vpn_gateways(self, vpn_gateway_ids=None, filters=None):
+ """
+ Retrieve information about your VpnGateways. You can filter results to
+ return information only about those VpnGateways that match your search
+ parameters. Otherwise, all VpnGateways associated with your account
+ are returned.
+
+ :type vpn_gateway_ids: list
+ :param vpn_gateway_ids: A list of strings with the desired VpnGateway ID's
+
+ :type filters: list of tuples
+ :param filters: A list of tuples containing filters. Each tuple
+ consists of a filter key and a filter value.
+ Possible filter keys are:
+
+ - *state*, the state of the VpnGateway
+ (pending,available,deleting,deleted)
+ - *type*, the type of customer gateway (ipsec.1)
+ - *availabilityZone*, the Availability zone the
+ VPN gateway is in.
+
+ :rtype: list
+ :return: A list of :class:`boto.vpc.customergateway.VpnGateway`
+ """
+ params = {}
+ if vpn_gateway_ids:
+ self.build_list_params(params, vpn_gateway_ids, 'VpnGatewayId')
+ if filters:
+ i = 1
+ for filter in filters:
+ params[('Filter.%d.Key' % i)] = filter[0]
+ params[('Filter.%d.Value.1')] = filter[1]
+ i += 1
+ return self.get_list('DescribeVpnGateways', params, [('item', VpnGateway)])
+
+ def create_vpn_gateway(self, type, availability_zone=None):
+ """
+ Create a new Vpn Gateway
+
+ :type type: str
+ :param type: Type of VPN Connection. Only valid valid currently is 'ipsec.1'
+
+ :type availability_zone: str
+ :param availability_zone: The Availability Zone where you want the VPN gateway.
+
+ :rtype: The newly created VpnGateway
+ :return: A :class:`boto.vpc.vpngateway.VpnGateway` object
+ """
+ params = {'Type' : type}
+ if availability_zone:
+ params['AvailabilityZone'] = availability_zone
+ return self.get_object('CreateVpnGateway', params, VpnGateway)
+
+ def delete_vpn_gateway(self, vpn_gateway_id):
+ """
+ Delete a Vpn Gateway.
+
+ :type vpn_gateway_id: str
+ :param vpn_gateway_id: The ID of the vpn_gateway to be deleted.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'VpnGatewayId': vpn_gateway_id}
+ return self.get_status('DeleteVpnGateway', params)
+
+ def attach_vpn_gateway(self, vpn_gateway_id, vpc_id):
+ """
+ Attaches a VPN gateway to a VPC.
+
+ :type vpn_gateway_id: str
+ :param vpn_gateway_id: The ID of the vpn_gateway to attach
+
+ :type vpc_id: str
+ :param vpc_id: The ID of the VPC you want to attach the gateway to.
+
+ :rtype: An attachment
+ :return: a :class:`boto.vpc.vpngateway.Attachment`
+ """
+ params = {'VpnGatewayId': vpn_gateway_id,
+ 'VpcId' : vpc_id}
+ return self.get_object('AttachVpnGateway', params, Attachment)
+
+ # Subnets
+
+ def get_all_subnets(self, subnet_ids=None, filters=None):
+ """
+ Retrieve information about your Subnets. You can filter results to
+ return information only about those Subnets that match your search
+ parameters. Otherwise, all Subnets associated with your account
+ are returned.
+
+ :type subnet_ids: list
+ :param subnet_ids: A list of strings with the desired Subnet ID's
+
+ :type filters: list of tuples
+ :param filters: A list of tuples containing filters. Each tuple
+ consists of a filter key and a filter value.
+ Possible filter keys are:
+
+ - *state*, the state of the Subnet
+ (pending,available)
+ - *vpdId*, the ID of teh VPC the subnet is in.
+ - *cidrBlock*, CIDR block of the subnet
+ - *availabilityZone*, the Availability Zone
+ the subnet is in.
+
+
+ :rtype: list
+ :return: A list of :class:`boto.vpc.subnet.Subnet`
+ """
+ params = {}
+ if subnet_ids:
+ self.build_list_params(params, subnet_ids, 'SubnetId')
+ if filters:
+ i = 1
+ for filter in filters:
+ params[('Filter.%d.Key' % i)] = filter[0]
+ params[('Filter.%d.Value.1')] = filter[1]
+ i += 1
+ return self.get_list('DescribeSubnets', params, [('item', Subnet)])
+
+ def create_subnet(self, vpc_id, cidr_block, availability_zone=None):
+ """
+ Create a new Subnet
+
+ :type vpc_id: str
+ :param vpc_id: The ID of the VPC where you want to create the subnet.
+
+ :type cidr_block: str
+ :param cidr_block: The CIDR block you want the subnet to cover.
+
+ :type availability_zone: str
+ :param availability_zone: The AZ you want the subnet in
+
+ :rtype: The newly created Subnet
+ :return: A :class:`boto.vpc.customergateway.Subnet` object
+ """
+ params = {'VpcId' : vpc_id,
+ 'CidrBlock' : cidr_block}
+ if availability_zone:
+ params['AvailabilityZone'] = availability_zone
+ return self.get_object('CreateSubnet', params, Subnet)
+
+ def delete_subnet(self, subnet_id):
+ """
+ Delete a subnet.
+
+ :type subnet_id: str
+ :param subnet_id: The ID of the subnet to be deleted.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'SubnetId': subnet_id}
+ return self.get_status('DeleteSubnet', params)
+
+
+ # DHCP Options
+
+ def get_all_dhcp_options(self, dhcp_options_ids=None):
+ """
+ Retrieve information about your DhcpOptions.
+
+ :type dhcp_options_ids: list
+ :param dhcp_options_ids: A list of strings with the desired DhcpOption ID's
+
+ :rtype: list
+ :return: A list of :class:`boto.vpc.dhcpoptions.DhcpOptions`
+ """
+ params = {}
+ if dhcp_options_ids:
+ self.build_list_params(params, dhcp_options_ids, 'DhcpOptionsId')
+ return self.get_list('DescribeDhcpOptions', params, [('item', DhcpOptions)])
+
+ def create_dhcp_options(self, vpc_id, cidr_block, availability_zone=None):
+ """
+ Create a new DhcpOption
+
+ :type vpc_id: str
+ :param vpc_id: The ID of the VPC where you want to create the subnet.
+
+ :type cidr_block: str
+ :param cidr_block: The CIDR block you want the subnet to cover.
+
+ :type availability_zone: str
+ :param availability_zone: The AZ you want the subnet in
+
+ :rtype: The newly created DhcpOption
+ :return: A :class:`boto.vpc.customergateway.DhcpOption` object
+ """
+ params = {'VpcId' : vpc_id,
+ 'CidrBlock' : cidr_block}
+ if availability_zone:
+ params['AvailabilityZone'] = availability_zone
+ return self.get_object('CreateDhcpOption', params, DhcpOptions)
+
+ def delete_dhcp_options(self, dhcp_options_id):
+ """
+ Delete a DHCP Options
+
+ :type dhcp_options_id: str
+ :param dhcp_options_id: The ID of the DHCP Options to be deleted.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'DhcpOptionsId': dhcp_options_id}
+ return self.get_status('DeleteDhcpOptions', params)
+
+ def associate_dhcp_options(self, dhcp_options_id, vpc_id):
+ """
+ Associate a set of Dhcp Options with a VPC.
+
+ :type dhcp_options_id: str
+ :param dhcp_options_id: The ID of the Dhcp Options
+
+ :type vpc_id: str
+ :param vpc_id: The ID of the VPC.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'DhcpOptionsId': dhcp_options_id,
+ 'VpcId' : vpc_id}
+ return self.get_status('AssociateDhcpOptions', params)
+
+ # VPN Connection
+
+ def get_all_vpn_connections(self, vpn_connection_ids=None, filters=None):
+ """
+ Retrieve information about your VPN_CONNECTIONs. You can filter results to
+ return information only about those VPN_CONNECTIONs that match your search
+ parameters. Otherwise, all VPN_CONNECTIONs associated with your account
+ are returned.
+
+ :type vpn_connection_ids: list
+ :param vpn_connection_ids: A list of strings with the desired VPN_CONNECTION ID's
+
+ :type filters: list of tuples
+ :param filters: A list of tuples containing filters. Each tuple
+ consists of a filter key and a filter value.
+ Possible filter keys are:
+
+ - *state*, the state of the VPN_CONNECTION
+ pending,available,deleting,deleted
+ - *type*, the type of connection, currently 'ipsec.1'
+ - *customerGatewayId*, the ID of the customer gateway
+ associated with the VPN
+ - *vpnGatewayId*, the ID of the VPN gateway associated
+ with the VPN connection
+
+ :rtype: list
+ :return: A list of :class:`boto.vpn_connection.vpnconnection.VpnConnection`
+ """
+ params = {}
+ if vpn_connection_ids:
+ self.build_list_params(params, vpn_connection_ids, 'Vpn_ConnectionId')
+ if filters:
+ i = 1
+ for filter in filters:
+ params[('Filter.%d.Key' % i)] = filter[0]
+ params[('Filter.%d.Value.1')] = filter[1]
+ i += 1
+ return self.get_list('DescribeVpnConnections', params, [('item', VpnConnection)])
+
+ def create_vpn_connection(self, type, customer_gateway_id, vpn_gateway_id):
+ """
+ Create a new VPN Connection.
+
+ :type type: str
+ :param type: The type of VPN Connection. Currently only 'ipsec.1'
+ is supported
+
+ :type customer_gateway_id: str
+ :param customer_gateway_id: The ID of the customer gateway.
+
+ :type vpn_gateway_id: str
+ :param vpn_gateway_id: The ID of the VPN gateway.
+
+ :rtype: The newly created VpnConnection
+ :return: A :class:`boto.vpc.vpnconnection.VpnConnection` object
+ """
+ params = {'Type' : type,
+ 'CustomerGatewayId' : customer_gateway_id,
+ 'VpnGatewayId' : vpn_gateway_id}
+ return self.get_object('CreateVpnConnection', params, VpnConnection)
+
+ def delete_vpn_connection(self, vpn_connection_id):
+ """
+ Delete a VPN Connection.
+
+ :type vpn_connection_id: str
+ :param vpn_connection_id: The ID of the vpn_connection to be deleted.
+
+ :rtype: bool
+ :return: True if successful
+ """
+ params = {'VpnConnectionId': vpn_connection_id}
+ return self.get_status('DeleteVpnConnection', params)
+
+
diff --git a/vendor/boto/boto/vpc/customergateway.py b/vendor/boto/boto/vpc/customergateway.py
new file mode 100644
index 0000000000..c50a616d5e
--- /dev/null
+++ b/vendor/boto/boto/vpc/customergateway.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a Customer Gateway
+"""
+
+from boto.ec2.ec2object import EC2Object
+
+class CustomerGateway(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.type = None
+ self.state = None
+ self.ip_address = None
+ self.bgp_asn = None
+
+ def __repr__(self):
+ return 'CustomerGateway:%s' % self.id
+
+ def endElement(self, name, value, connection):
+ if name == 'customerGatewayId':
+ self.id = value
+ elif name == 'ipAddress':
+ self.ip_address = value
+ elif name == 'type':
+ self.type = value
+ elif name == 'state':
+ self.state = value
+ elif name == 'bgpAsn':
+ self.bgp_asn = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/vpc/dhcpoptions.py b/vendor/boto/boto/vpc/dhcpoptions.py
new file mode 100644
index 0000000000..4fce7dc90f
--- /dev/null
+++ b/vendor/boto/boto/vpc/dhcpoptions.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a DHCP Options set
+"""
+
+from boto.ec2.ec2object import EC2Object
+
+class DhcpValueSet(list):
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'value':
+ self.append(value)
+
+class DhcpConfigSet(dict):
+
+ def startElement(self, name, attrs, connection):
+ if name == 'valueSet':
+ if not self.has_key(self._name):
+ self[self._name] = DhcpValueSet()
+ return self[self._name]
+
+ def endElement(self, name, value, connection):
+ if name == 'key':
+ self._name = value
+
+class DhcpOptions(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.options = None
+
+ def __repr__(self):
+ return 'DhcpOptions:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ if name == 'dhcpConfigurationSet':
+ self.options = DhcpConfigSet()
+ return self.options
+
+ def endElement(self, name, value, connection):
+ if name == 'dhcpOptionsId':
+ self.id = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/vpc/subnet.py b/vendor/boto/boto/vpc/subnet.py
new file mode 100644
index 0000000000..de8a959e07
--- /dev/null
+++ b/vendor/boto/boto/vpc/subnet.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a Subnet
+"""
+
+from boto.ec2.ec2object import EC2Object
+
+class Subnet(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.state = None
+ self.cidr_block = None
+ self.available_ip_address_count = 0
+ self.availability_zone = None
+
+ def __repr__(self):
+ return 'Subnet:%s' % self.id
+
+ def endElement(self, name, value, connection):
+ if name == 'subnetId':
+ self.id = value
+ elif name == 'state':
+ self.state = value
+ elif name == 'cidrBlock':
+ self.cidr_block = value
+ elif name == 'availableIpAddressCount':
+ self.available_ip_address_count = int(value)
+ elif name == 'availabilityZone':
+ self.availability_zone = value
+ else:
+ setattr(self, name, value)
+
diff --git a/vendor/boto/boto/vpc/vpc.py b/vendor/boto/boto/vpc/vpc.py
new file mode 100644
index 0000000000..152cff3ece
--- /dev/null
+++ b/vendor/boto/boto/vpc/vpc.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a Virtual Private Cloud.
+"""
+
+from boto.ec2.ec2object import EC2Object
+
+class VPC(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.dhcp_options_id = None
+ self.state = None
+ self.cidr_block = None
+
+ def __repr__(self):
+ return 'VPC:%s' % self.id
+
+ def endElement(self, name, value, connection):
+ if name == 'vpcId':
+ self.id = value
+ elif name == 'dhcpOptionsId':
+ self.dhcp_options_id = value
+ elif name == 'state':
+ self.state = value
+ elif name == 'cidrBlock':
+ self.cidr_block = value
+ else:
+ setattr(self, name, value)
+
+ def delete(self):
+ return self.connection.delete_vpc(self.id)
+
diff --git a/vendor/boto/boto/vpc/vpnconnection.py b/vendor/boto/boto/vpc/vpnconnection.py
new file mode 100644
index 0000000000..c02789b533
--- /dev/null
+++ b/vendor/boto/boto/vpc/vpnconnection.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a VPN Connectionn
+"""
+
+from boto.ec2.ec2object import EC2Object
+
+class VpnConnection(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.state = None
+ self.customer_gateway_configuration = None
+ self.type = None
+ self.customer_gateway_id = None
+ self.vpn_gateway_id = None
+
+ def __repr__(self):
+ return 'VpnConnection:%s' % self.id
+
+ def endElement(self, name, value, connection):
+ if name == 'vpnConnectionId':
+ self.id = value
+ elif name == 'state':
+ self.state = value
+ elif name == 'CustomerGatewayConfiguration':
+ self.customer_gateway_configuration = value
+ elif name == 'type':
+ self.type = value
+ elif name == 'customerGatewayId':
+ self.customer_gateway_id = value
+ elif name == 'vpnGatewayId':
+ self.vpn_gateway_id = value
+ else:
+ setattr(self, name, value)
+
+ def delete(self):
+ return self.connection.delete_vpn_connection(self.id)
+
diff --git a/vendor/boto/boto/vpc/vpngateway.py b/vendor/boto/boto/vpc/vpngateway.py
new file mode 100644
index 0000000000..0fa0a9efd6
--- /dev/null
+++ b/vendor/boto/boto/vpc/vpngateway.py
@@ -0,0 +1,80 @@
+# Copyright (c) 2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a Vpn Gateway
+"""
+
+from boto.ec2.ec2object import EC2Object
+
+class Attachment(object):
+
+ def __init__(self, connection=None):
+ self.vpc_id = None
+ self.state = None
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'vpcId':
+ self.vpc_id = value
+ elif name == 'state':
+ self.state = value
+ else:
+ setattr(self, name, value)
+
+class VpnGateway(EC2Object):
+
+ def __init__(self, connection=None):
+ EC2Object.__init__(self, connection)
+ self.id = None
+ self.type = None
+ self.state = None
+ self.availability_zone = None
+ self.attachments = []
+
+ def __repr__(self):
+ return 'VpnGateway:%s' % self.id
+
+ def startElement(self, name, attrs, connection):
+ if name == 'item':
+ att = Attachment()
+ self.attachments.append(att)
+ return att
+
+ def endElement(self, name, value, connection):
+ if name == 'vpnGatewayId':
+ self.id = value
+ elif name == 'type':
+ self.type = value
+ elif name == 'state':
+ self.state = value
+ elif name == 'availabilityZone':
+ self.availability_zone = value
+ elif name == 'attachments':
+ pass
+ else:
+ setattr(self, name, value)
+
+ def attach(self, vpc_id):
+ return self.connection.attach_vpn_gateway(self.id, vpc_id)
+
diff --git a/vendor/boto/cq.py b/vendor/boto/cq.py
new file mode 100755
index 0000000000..241515697e
--- /dev/null
+++ b/vendor/boto/cq.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+import getopt, sys
+from boto.sqs.connection import SQSConnection
+from boto.exception import SQSError
+
+def usage():
+ print 'cq.py [-c] [-q queue_name] [-o output_file] [-t timeout]'
+
+def main():
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'hcq:o:t:',
+ ['help', 'clear', 'queue',
+ 'output', 'timeout'])
+ except:
+ usage()
+ sys.exit(2)
+ queue_name = ''
+ output_file = ''
+ timeout = 30
+ clear = False
+ for o, a in opts:
+ if o in ('-h', '--help'):
+ usage()
+ sys.exit()
+ if o in ('-q', '--queue'):
+ queue_name = a
+ if o in ('-o', '--output'):
+ output_file = a
+ if o in ('-c', '--clear'):
+ clear = True
+ if o in ('-t', '--timeout'):
+ timeout = int(a)
+ c = SQSConnection()
+ if queue_name:
+ try:
+ rs = [c.create_queue(queue_name)]
+ except SQSError, e:
+ print 'An Error Occurred:'
+ print '%s: %s' % (e.status, e.reason)
+ print e.body
+ sys.exit()
+ else:
+ try:
+ rs = c.get_all_queues()
+ except SQSError, e:
+ print 'An Error Occurred:'
+ print '%s: %s' % (e.status, e.reason)
+ print e.body
+ sys.exit()
+ for q in rs:
+ if clear:
+ n = q.clear()
+ print 'clearing %d messages from %s' % (n, q.id)
+ elif output_file:
+ q.dump(output_file)
+ else:
+ print q.id, q.count(vtimeout=timeout)
+
+if __name__ == "__main__":
+ main()
+
diff --git a/vendor/boto/docs/Makefile b/vendor/boto/docs/Makefile
new file mode 100644
index 0000000000..5fd1f92028
--- /dev/null
+++ b/vendor/boto/docs/Makefile
@@ -0,0 +1,89 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/boto.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/boto.qhc"
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+ "run these through (pdf)latex."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/vendor/boto/docs/make.bat b/vendor/boto/docs/make.bat
new file mode 100644
index 0000000000..d6b0b7b6a5
--- /dev/null
+++ b/vendor/boto/docs/make.bat
@@ -0,0 +1,113 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+set SPHINXBUILD=sphinx-build
+set BUILDDIR=build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^<target^>` where ^<target^> is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\boto.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\boto.ghc
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+:end
diff --git a/vendor/boto/docs/source/_templates/layout.html b/vendor/boto/docs/source/_templates/layout.html
new file mode 100644
index 0000000000..cdf85bbf0a
--- /dev/null
+++ b/vendor/boto/docs/source/_templates/layout.html
@@ -0,0 +1,3 @@
+{% extends '!layout.html' %}
+
+{% block sidebarsearch %}{{ super() }}<div><a href="boto.pdf">PDF Version</a></div>{% endblock %}
diff --git a/vendor/boto/docs/source/autoscale_tut.rst b/vendor/boto/docs/source/autoscale_tut.rst
new file mode 100644
index 0000000000..9f9d39940d
--- /dev/null
+++ b/vendor/boto/docs/source/autoscale_tut.rst
@@ -0,0 +1,140 @@
+.. _autoscale_tut:
+
+=============================================
+An Introduction to boto's Autoscale interface
+=============================================
+
+This tutorial focuses on the boto interface to the Autoscale service. This
+assumes you are familiar with boto's EC2 interface and concepts.
+
+Autoscale Concepts
+------------------
+
+The AWS Autoscale service is comprised of three core concepts:
+
+ #. *Autoscale Group (AG):* An AG can be viewed as a collection of criteria for
+ maintaining or scaling a set of EC2 instances over one or more availability
+ zones. An AG is limited to a single region.
+ #. *Launch Configuration (LC):* An LC is the set of information needed by the
+ AG to launch new instances - this can encompass image ids, startup data,
+ security groups and keys. Only one LC is attached to an AG.
+ #. *Triggers*: A trigger is essentially a set of rules for determining when to
+ scale an AG up or down. These rules can encompass a set of metrics such as
+ average CPU usage across instances, or incoming requests, a threshold for
+ when an action will take place, as well as parameters to control how long
+ to wait after a threshold is crossed.
+
+Creating a Connection
+---------------------
+The first step in accessing autoscaling is to create a connection to the service.
+There are two ways to do this in boto. The first is:
+
+>>> from boto.ec2.autoscale import AutoScaleConnection
+>>> conn = AutoScaleConnection('<aws access key>', '<aws secret key>')
+
+Alternatively, you can use the shortcut:
+
+>>> conn = boto.connect_autoscale()
+
+A Note About Regions and Endpoints
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Like EC2 the Autoscale service has a different endpoint for each region. By
+default the US endpoint is used. To choose a specific region, instantiate the
+AutoScaleConnection object with that region's endpoint.
+
+>>> ec2 = boto.connect_autoscale(host='eu-west-1.autoscaling.amazonaws.com')
+
+Alternatively, edit your boto.cfg with the default Autoscale endpoint to use::
+
+ [Boto]
+ autoscale_endpoint = eu-west-1.autoscaling.amazonaws.com
+
+Getting Existing AutoScale Groups
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To retrieve existing autoscale groups:
+
+>>> conn.get_all_groups()
+
+You will get back a list of AutoScale group objects, one for each AG you have.
+
+Creating Autoscaling Groups
+---------------------------
+An Autoscaling group has a number of parameters associated with it.
+
+ #. *Name*: The name of the AG.
+ #. *Availability Zones*: The list of availability zones it is defined over.
+ #. *Minimum Size*: Minimum number of instances running at one time.
+ #. *Maximum Size*: Maximum number of instances running at one time.
+ #. *Launch Configuration (LC)*: A set of instructions on how to launch an instance.
+ #. *Load Balancer*: An optional ELB load balancer to use. See the ELB tutorial
+ for information on how to create a load balancer.
+
+For the purposes of this tutorial, let's assume we want to create one autoscale
+group over the us-east-1a and us-east-1b availability zones. We want to have
+two instances in each availability zone, thus a minimum size of 4. For now we
+won't worry about scaling up or down - we'll introduce that later when we talk
+about triggers. Thus we'll set a maximum size of 4 as well. We'll also associate
+the AG with a load balancer which we assume we've already created, called 'my_lb'.
+
+Our LC tells us how to start an instance. This will at least include the image
+id to use, security_group, and key information. We assume the image id, key
+name and security groups have already been defined elsewhere - see the EC2
+tutorial for information on how to create these.
+
+>>> from boto.ec2.autoscale import LaunchConfiguration
+>>> from boto.ec2.autoscale import AutoScalingGroup
+>>> lc = LaunchConfiguration(name='my-launch_config', image_id='my-ami',
+ key_name='my_key_name',
+ security_groups=['my_security_groups'])
+>>> conn.create_launch_configuration(lc)
+
+We now have created a launch configuration called 'my-launch-config'. We are now
+ready to associate it with our new autoscale group.
+
+>>> ag = AutoScalingGroup(group_name='my_group', load_balancers=['my-lb'],
+ availability_zones=['us-east-1a', 'us-east-1b'],
+ launch_config=lc, min_size=4, max_size=4)
+>>> conn.create_auto_scaling_group(ag)
+
+We now have a new autoscaling group defined! At this point instances should be
+starting to launch. To view activity on an autoscale group:
+
+>>> ag.get_activities()
+ [Activity:Launching a new EC2 instance status:Successful progress:100,
+ ...]
+
+or alternatively:
+
+>>> conn.get_all_activities(ag)
+
+This autoscale group is fairly useful in that it will maintain the minimum size without
+breaching the maximum size defined. That means if one instance crashes, the autoscale
+group will use the launch configuration to start a new one in an attempt to maintain
+its minimum defined size. It knows instance health using the health check defined on
+its associated load balancer.
+
+Scaling a Group Up or Down
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+It might be more useful to also define means to scale a group up or down
+depending on certain criteria. For example, if the average CPU utilization of
+all your instances goes above 60%, you may want to scale up a number of
+instances to deal with demand - likewise you might want to scale down if usage
+drops. These criteria are defined in *triggers*.
+
+For example, let's modify our above group to have a maxsize of 8 and define means
+of scaling up based on CPU utilization. We'll say we should scale up if the average
+CPU usage goes above 80% and scale down if it goes below 40%.
+
+>>> from boto.ec2.autoscale import Trigger
+>>> tr = Trigger(name='my_trigger', autoscale_group=ag,
+ measure_name='CPUUtilization', statistic='Average',
+ unit='Percent',
+ dimensions=[('AutoScalingGroupName', ag.name)],
+ period=60, lower_threshold=40,
+ lower_breach_scale_increment='-5',
+ upper_threshold=80,
+ upper_breach_scale_increment='10',
+ breach_duration=360)
+>> conn.create_trigger(tr)
+
diff --git a/vendor/boto/docs/source/boto_theme/static/boto.css_t b/vendor/boto/docs/source/boto_theme/static/boto.css_t
new file mode 100644
index 0000000000..932e5183db
--- /dev/null
+++ b/vendor/boto/docs/source/boto_theme/static/boto.css_t
@@ -0,0 +1,239 @@
+/**
+ * Sphinx stylesheet -- default theme
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+@import url("basic.css");
+
+/* -- page layout ----------------------------------------------------------- */
+
+body {
+ font-family: 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, Arial, sans-serif;
+ font-size: 100%;
+ background-color: #111111;
+ color: #555555;
+ margin: 0;
+ padding: 0;
+}
+
+div.documentwrapper {
+ float: left;
+ width: 100%;
+}
+
+div.bodywrapper {
+ margin: 0 0 0 300px;
+}
+
+hr{
+ border: 1px solid #B1B4B6;
+}
+
+div.document {
+ background-color: #fafafa;
+}
+
+div.body {
+ background-color: #ffffff;
+ color: #3E4349;
+ padding: 1em 30px 30px 30px;
+ font-size: 0.9em;
+}
+
+div.footer {
+ color: #555;
+ width: 100%;
+ padding: 13px 0;
+ text-align: center;
+ font-size: 75%;
+}
+
+div.footer a {
+ color: #444444;
+}
+
+div.related {
+ background-color: #6F6555; /*#6BA81E;*/
+ line-height: 36px;
+ color: #CCCCCC;
+ text-shadow: 0px 1px 0 #444444;
+ font-size: 1.1em;
+}
+
+div.related a {
+ color: #D9C5A7;
+}
+
+div.related .right {
+ font-size: 0.9em;
+}
+
+div.sphinxsidebar {
+ font-size: 0.9em;
+ line-height: 1.5em;
+ width: 300px
+}
+
+div.sphinxsidebarwrapper{
+ padding: 20px 0;
+}
+
+div.sphinxsidebar h3,
+div.sphinxsidebar h4 {
+ font-family: 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, Arial, sans-serif;
+ color: #222222;
+ font-size: 1.2em;
+ font-weight: bold;
+ margin: 0;
+ padding: 5px 10px;
+ text-shadow: 1px 1px 0 white
+}
+
+div.sphinxsidebar h3 a {
+ color: #444444;
+}
+
+div.sphinxsidebar p {
+ color: #888888;
+ padding: 5px 20px;
+ margin: 0.5em 0px;
+}
+
+div.sphinxsidebar p.topless {
+}
+
+div.sphinxsidebar ul {
+ margin: 10px 10px 10px 20px;
+ padding: 0;
+ color: #000000;
+}
+
+div.sphinxsidebar a {
+ color: #444444;
+}
+
+div.sphinxsidebar a:hover {
+ color: #E32E00;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #cccccc;
+ font-family: sans-serif;
+ font-size: 1.1em;
+ padding: 0.15em 0.3em;
+}
+
+div.sphinxsidebar input[type=text]{
+ margin-left: 20px;
+}
+
+/* -- body styles ----------------------------------------------------------- */
+
+a {
+ color: #005B81;
+ text-decoration: none;
+}
+
+a:hover {
+ color: #E32E00;
+}
+
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+ font-family: 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, Arial, sans-serif;
+ font-weight: bold;
+ color: #069;
+ margin: 30px 0px 10px 0px;
+ padding: 5px 0 5px 0px;
+ text-shadow: 0px 1px 0 white;
+ border-bottom: 1px solid #C8D5E3;
+}
+
+div.body h1 { margin-top: 0; font-size: 165%; }
+div.body h2 { font-size: 135%; }
+div.body h3 { font-size: 120%; }
+div.body h4 { font-size: 110%; }
+div.body h5 { font-size: 100%; }
+div.body h6 { font-size: 100%; }
+
+a.headerlink {
+ color: #c60f0f;
+ font-size: 0.8em;
+ padding: 0 4px 0 4px;
+ text-decoration: none;
+}
+
+a.headerlink:hover {
+ background-color: #c60f0f;
+ color: white;
+}
+
+div.body p, div.body dd, div.body li {
+ line-height: 1.5em;
+}
+
+div.admonition p.admonition-title + p {
+ display: inline;
+}
+
+div.highlight{
+ background-color: white;
+}
+
+div.note {
+ background-color: #eeeeee;
+ border: 1px solid #cccccc;
+}
+
+div.seealso {
+ background-color: #ffffcc;
+ border: 1px solid #ffff66;
+}
+
+div.topic {
+ background-color: #fafafa;
+ border-width: 0;
+}
+
+div.warning {
+ background-color: #ffe4e4;
+ border: 1px solid #ff6666;
+}
+
+
+p.admonition-title {
+ display: inline;
+}
+
+p.admonition-title:after {
+ content: ":";
+}
+
+pre {
+ padding: 10px;
+ background-color: #fafafa;
+ color: #222222;
+ line-height: 1.5em;
+ font-size: 1.1em;
+ margin: 1.5em 0 1.5em 0;
+ -webkit-box-shadow: 0px 0px 4px #d8d8d8;
+ -moz-box-shadow: 0px 0px 4px #d8d8d8;
+ box-shadow: 0px 0px 4px #d8d8d8;
+}
+
+tt {
+ color: #222222;
+ padding: 1px 2px;
+ font-size: 1.2em;
+ font-family: monospace;
+}
+
+#table-of-contents ul {
+ padding-left: 2em;
+}
+
+div.sphinxsidebarwrapper div a {margin: 0.7em;} \ No newline at end of file
diff --git a/vendor/boto/docs/source/boto_theme/static/pygments.css b/vendor/boto/docs/source/boto_theme/static/pygments.css
new file mode 100644
index 0000000000..1f2d2b6187
--- /dev/null
+++ b/vendor/boto/docs/source/boto_theme/static/pygments.css
@@ -0,0 +1,61 @@
+.hll { background-color: #ffffcc }
+.c { color: #408090; font-style: italic } /* Comment */
+.err { border: 1px solid #FF0000 } /* Error */
+.k { color: #007020; font-weight: bold } /* Keyword */
+.o { color: #666666 } /* Operator */
+.cm { color: #408090; font-style: italic } /* Comment.Multiline */
+.cp { color: #007020 } /* Comment.Preproc */
+.c1 { color: #408090; font-style: italic } /* Comment.Single */
+.cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
+.gd { color: #A00000 } /* Generic.Deleted */
+.ge { font-style: italic } /* Generic.Emph */
+.gr { color: #FF0000 } /* Generic.Error */
+.gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.gi { color: #00A000 } /* Generic.Inserted */
+.go { color: #303030 } /* Generic.Output */
+.gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
+.gs { font-weight: bold } /* Generic.Strong */
+.gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.gt { color: #0040D0 } /* Generic.Traceback */
+.kc { color: #007020; font-weight: bold } /* Keyword.Constant */
+.kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
+.kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
+.kp { color: #007020 } /* Keyword.Pseudo */
+.kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
+.kt { color: #902000 } /* Keyword.Type */
+.m { color: #208050 } /* Literal.Number */
+.s { color: #4070a0 } /* Literal.String */
+.na { color: #4070a0 } /* Name.Attribute */
+.nb { color: #007020 } /* Name.Builtin */
+.nc { color: #0e84b5; font-weight: bold } /* Name.Class */
+.no { color: #60add5 } /* Name.Constant */
+.nd { color: #555555; font-weight: bold } /* Name.Decorator */
+.ni { color: #d55537; font-weight: bold } /* Name.Entity */
+.ne { color: #007020 } /* Name.Exception */
+.nf { color: #06287e } /* Name.Function */
+.nl { color: #002070; font-weight: bold } /* Name.Label */
+.nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
+.nt { color: #062873; font-weight: bold } /* Name.Tag */
+.nv { color: #bb60d5 } /* Name.Variable */
+.ow { color: #007020; font-weight: bold } /* Operator.Word */
+.w { color: #bbbbbb } /* Text.Whitespace */
+.mf { color: #208050 } /* Literal.Number.Float */
+.mh { color: #208050 } /* Literal.Number.Hex */
+.mi { color: #208050 } /* Literal.Number.Integer */
+.mo { color: #208050 } /* Literal.Number.Oct */
+.sb { color: #4070a0 } /* Literal.String.Backtick */
+.sc { color: #4070a0 } /* Literal.String.Char */
+.sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
+.s2 { color: #4070a0 } /* Literal.String.Double */
+.se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
+.sh { color: #4070a0 } /* Literal.String.Heredoc */
+.si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
+.sx { color: #c65d09 } /* Literal.String.Other */
+.sr { color: #235388 } /* Literal.String.Regex */
+.s1 { color: #4070a0 } /* Literal.String.Single */
+.ss { color: #517918 } /* Literal.String.Symbol */
+.bp { color: #007020 } /* Name.Builtin.Pseudo */
+.vc { color: #bb60d5 } /* Name.Variable.Class */
+.vg { color: #bb60d5 } /* Name.Variable.Global */
+.vi { color: #bb60d5 } /* Name.Variable.Instance */
+.il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file
diff --git a/vendor/boto/docs/source/boto_theme/theme.conf b/vendor/boto/docs/source/boto_theme/theme.conf
new file mode 100644
index 0000000000..7d09085abb
--- /dev/null
+++ b/vendor/boto/docs/source/boto_theme/theme.conf
@@ -0,0 +1,3 @@
+[theme]
+inherit = basic
+stylesheet = boto.css \ No newline at end of file
diff --git a/vendor/boto/docs/source/conf.py b/vendor/boto/docs/source/conf.py
new file mode 100644
index 0000000000..57b1221b0a
--- /dev/null
+++ b/vendor/boto/docs/source/conf.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+import sys, os
+
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo']
+templates_path = ['_templates']
+source_suffix = '.rst'
+master_doc = 'index'
+project = u'boto'
+copyright = u'2009,2010, Mitch Garnaat'
+version = '1.9'
+exclude_trees = []
+pygments_style = 'sphinx'
+html_theme = 'boto_theme'
+html_theme_path = ["."]
+html_static_path = ['_static']
+htmlhelp_basename = 'botodoc'
+latex_documents = [
+ ('index', 'boto.tex', u'boto Documentation',
+ u'Mitch Garnaat', 'manual'),
+]
+intersphinx_mapping = {'http://docs.python.org/': None}
+
+try:
+ release = os.environ.get('SVN_REVISION', 'HEAD')
+ print release
+except Exception, e:
+ print e
+
+html_title = "boto v%s (r%s)" % (version, release)
diff --git a/vendor/boto/docs/source/documentation.rst b/vendor/boto/docs/source/documentation.rst
new file mode 100644
index 0000000000..d4999d99da
--- /dev/null
+++ b/vendor/boto/docs/source/documentation.rst
@@ -0,0 +1,59 @@
+.. _documentation:
+
+=======================
+About the Documentation
+=======================
+
+boto's documentation uses the Sphinx__ documentation system, which in turn is
+based on docutils__. The basic idea is that lightly-formatted plain-text
+documentation is transformed into HTML, PDF, and any other output format.
+
+__ http://sphinx.pocoo.org/
+__ http://docutils.sf.net/
+
+To actually build the documentation locally, you'll currently need to install
+Sphinx -- ``easy_install Sphinx`` should do the trick.
+
+Then, building the html is easy; just ``make html`` from the ``docs`` directory.
+
+To get started contributing, you'll want to read the `ReStructuredText
+Primer`__. After that, you'll want to read about the `Sphinx-specific markup`__
+that's used to manage metadata, indexing, and cross-references.
+
+__ http://sphinx.pocoo.org/rest.html
+__ http://sphinx.pocoo.org/markup/
+
+The main thing to keep in mind as you write and edit docs is that the more
+semantic markup you can add the better. So::
+
+ Import ``boto`` to your script...
+
+Isn't nearly as helpful as::
+
+ Add :mod:`boto` to your script...
+
+This is because Sphinx will generate a proper link for the latter, which greatly
+helps readers. There's basically no limit to the amount of useful markup you can
+add.
+
+
+The fabfile
+-----------
+
+There is a Fabric__ file that can be used to build and deploy the documentation
+to a webserver that you ssh access to.
+
+__ http://fabfile.org
+
+To build and deploy::
+
+ cd docs/
+ fab deploy:remote_path='/var/www/folder/whatever' --hosts=user@host
+
+This will get the latest code from subversion, add the revision number to the
+docs conf.py file, call ``make html`` to build the documentation, then it will
+tarball it up and scp up to the host you specified and untarball it in the
+folder you specified creating a symbolic link from the untarballed versioned
+folder to ``{remote_path}/boto-docs``.
+
+
diff --git a/vendor/boto/docs/source/ec2_tut.rst b/vendor/boto/docs/source/ec2_tut.rst
new file mode 100644
index 0000000000..6326243ce2
--- /dev/null
+++ b/vendor/boto/docs/source/ec2_tut.rst
@@ -0,0 +1,420 @@
+.. _ec2_tut:
+
+=======================================
+An Introduction to boto's EC2 interface
+=======================================
+
+This tutorial focuses on the boto interface to the Elastic Compute Cloud
+from Amazon Web Services. This tutorial assumes that you have already
+downloaded and installed boto.
+
+Creating a Connection
+---------------------
+The first step in accessing EC2 is to create a connection to the service.
+There are two ways to do this in boto. The first is:
+
+>>> from boto.ec2.connection import EC2Connection
+>>> conn = EC2Connection('<aws access key>', '<aws secret key>')
+
+At this point the variable conn will point to an EC2Connection object. In
+this example, the AWS access key and AWS secret key are passed in to the
+method explicitely. Alternatively, you can set the environment variables:
+
+AWS_ACCESS_KEY_ID - Your AWS Access Key ID
+AWS_SECRET_ACCESS_KEY - Your AWS Secret Access Key
+
+and then call the constructor without any arguments, like this:
+
+>>> conn = EC2Connection()
+
+There is also a shortcut function in the boto package, called connect_ec2
+that may provide a slightly easier means of creating a connection:
+
+>>> import boto
+>>> conn = boto.connect_ec2()
+
+In either case, conn will point to an EC2Connection object which we will
+use throughout the remainder of this tutorial.
+
+A Note About Regions
+--------------------
+The 2008-12-01 version of the EC2 API introduced the idea of Regions.
+A Region is geographically distinct and is completely isolated from
+other EC2 Regions. At the time of the launch of the 2008-12-01 API
+there were two available regions, us-east-1 and eu-west-1. Each
+Region has it's own service endpoint and therefore would require
+it's own EC2Connection object in boto.
+
+The default behavior in boto, as shown above, is to connect you with
+the us-east-1 region which is exactly the same as the behavior prior
+to the introduction of Regions.
+
+However, if you would like to connect to a region other than us-east-1,
+there are a couple of ways to accomplish that. The first way, is to
+as EC2 to provide a list of currently supported regions. You can do
+that using the regions function in the boto.ec2 module:
+
+>>> import boto.ec2
+>>> regions = boto.ec2.regions()
+>>> regions
+[RegionInfo:eu-west-1, RegionInfo:us-east-1]
+>>>
+
+As you can see, a list of available regions is returned. Each region
+is represented by a RegionInfo object. A RegionInfo object has two
+attributes; a name and an endpoint.
+
+>>> eu = regions[0]
+>>> eu.name
+u'eu-west-1'
+>>> eu.endpoint
+u'eu-west-1.ec2.amazonaws.com'
+>>>
+
+You can easily create a connection to a region by using the connect
+method of the RegionInfo object:
+
+>>> conn_eu = eu.connect()
+>>> conn_eu
+<boto.ec2.connection.EC2Connection instance at 0xccaaa8>
+>>>
+
+The variable conn_eu is now bound to an EC2Connection object connected
+to the endpoint of the eu-west-1 region and all operations performed via
+that connection and all objects created by that connection will be scoped
+to the eu-west-1 region. You can always tell which region a connection
+is associated with by accessing it's region attribute:
+
+>>> conn_eu.region
+RegionInfo:eu-west-1
+>>>
+
+Supporting EC2 objects such as SecurityGroups, KeyPairs, Addresses,
+Volumes, Images and SnapShots are local to a particular region. So
+don't expect to find the security groups you created in the us-east-1
+region to be available in the eu-west-1 region.
+
+Some objects in boto, such as SecurityGroup, have a new method called
+copy_to_region which will attempt to create a copy of the object in
+another region. For example:
+
+>>> regions
+[RegionInfo:eu-west-1, RegionInfo:us-east-1]
+>>> conn_us = regions[1].connect()
+>>> groups = conn_us.get_all_security_groups()
+>>> groups
+[SecurityGroup:alfresco, SecurityGroup:apache, SecurityGroup:vnc,
+SecurityGroup:appserver2, SecurityGroup:FTP, SecurityGroup:webserver,
+SecurityGroup:default, SecurityGroup:test-1228851996]
+>>> us_group = groups[0]
+>>> us_group
+SecurityGroup:alfresco
+>>> us_group.rules
+[IPPermissions:tcp(22-22), IPPermissions:tcp(80-80), IPPermissions:tcp(1445-1445)]
+>>> eu_group = us_group.copy_to_region(eu)
+>>> eu_group.rules
+[IPPermissions:tcp(22-22), IPPermissions:tcp(80-80), IPPermissions:tcp(1445-1445)]
+
+In the above example, we chose one of the security groups available
+in the us-east-1 region (the group alfresco) and copied that security
+group to the eu-west-1 region. All of the rules associated with the
+original security group will be copied as well.
+
+If you would like your default region to be something other than
+us-east-1, you can override that default in your boto config file
+(either ~/.boto for personal settings or /etc/boto.cfg for system-wide
+settings). For example:
+
+[Boto]
+ec2_region_name = eu-west-1
+ec2_region_endpoint = eu-west-1.ec2.amazonaws.com
+
+The above lines added to either boto config file would set the default
+region to be eu-west-1.
+
+Images & Instances
+------------------
+
+An Image object represents an Amazon Machine Image (AMI) which is an
+encrypted machine image stored in Amazon S3. It contains all of the
+information necessary to boot instances of your software in EC2.
+
+To get a listing of all available Images:
+
+>>> images = conn.get_all_images()
+>>> images
+[Image:ami-20b65349, Image:ami-22b6534b, Image:ami-23b6534a, Image:ami-25b6534c, Image:ami-26b6534f, Image:ami-2bb65342, Image:ami-78b15411, Image:ami-a4aa4fcd, Image:ami-c3b550aa, Image:ami-e4b6538d, Image:ami-f1b05598]
+>>> for image in images:
+... print image.location
+ec2-public-images/fedora-core4-base.manifest.xml
+ec2-public-images/fedora-core4-mysql.manifest.xml
+ec2-public-images/fedora-core4-apache.manifest.xml
+ec2-public-images/fedora-core4-apache-mysql.manifest.xml
+ec2-public-images/developer-image.manifest.xml
+ec2-public-images/getting-started.manifest.xml
+marcins_cool_public_images/fedora-core-6.manifest.xml
+khaz_fc6_win2003/image.manifest
+aes-images/django.manifest
+marcins_cool_public_images/ubuntu-6.10.manifest.xml
+ckk_public_ec2_images/centos-base-4.4.manifest.xml
+
+The most useful thing you can do with an Image is to actually run it, so let's
+run a new instance of the base Fedora image:
+
+>>> image = images[0]
+>>> image.location
+ec2-public-images/fedora-core4-base.manifest.xml
+>>> reservation = image.run()
+
+This will begin the boot process for a new EC2 instance. The run method
+returns a Reservation object which represents a collection of instances
+that are all started at the same time. In this case, we only started one
+but you can check the instances attribute of the Reservation object to see
+all of the instances associated with this reservation:
+
+>>> reservation.instances
+[Instance:i-6761850e]
+>>> instance = reservation.instances[0]
+>>> instance.state
+u'pending'
+>>>
+
+So, we have an instance booting up that is still in the pending state. We
+can call the update method on the instance to get a refreshed view of it's
+state:
+
+>>> instance.update()
+>>> instance.state
+u'pending'
+>>> # wait a few minutes
+>>> instance.update()
+>>> instance.state
+u'running'
+
+So, now our instance is running. The time it takes to boot a new instance
+varies based on a number of different factors but usually it takes less than
+five minutes.
+
+Now the instance is up and running you can find out its DNS name like this:
+
+>>> instance.dns_name
+u'ec2-72-44-40-153.z-2.compute-1.amazonaws.com'
+
+This provides the public DNS name for your instance. Since the 2007--3-22
+release of the EC2 service, the default addressing scheme for instances
+uses NAT-addresses which means your instance has both a public IP address and a
+non-routable private IP address. You can access each of these addresses
+like this:
+
+>>> instance.public_dns_name
+u'ec2-72-44-40-153.z-2.compute-1.amazonaws.com'
+>>> instance.private_dns_name
+u'domU-12-31-35-00-42-33.z-2.compute-1.internal'
+
+Even though your instance has a public DNS name, you won't be able to
+access it yet because you need to set up some security rules which are
+described later in this tutorial.
+
+Since you are now being charged for that instance we just created, you will
+probably want to know how to terminate the instance, as well. The simplest
+way is to use the stop method of the Instance object:
+
+>>> instance.stop()
+>>> instance.update()
+>>> instance.state
+u'shutting-down'
+>>> # wait a minute
+>>> instance.update()
+>>> instance.state
+u'terminated'
+>>>
+
+When we created our new instance, we didn't pass any args to the run method
+so we got all of the default values. The full set of possible parameters
+to the run method are:
+
+min_count - The minimum number of instances to launch.
+max_count - The maximum number of instances to launch.
+keypair - Keypair to launch instances with (either a KeyPair object or a string with the name of the desired keypair.
+security_groups - A list of security groups to associate with the instance. This can either be a list of SecurityGroup objects or a list of strings with the names of the desired security groups.
+user_data - Data to be made available to the launched instances. This should be base64 encoded according to the EC2 documentation.
+
+So, if I wanted to create two instances of the base image and launch them
+with my keypair, called gsg-keypair, I would to this:
+
+>>> reservation.image.run(2,2,'gsg-keypair')
+>>> reservation.instances
+[Instance:i-5f618536, Instance:i-5e618537]
+>>> for i in reservation.instances:
+... print i.status
+u'pending'
+u'pending'
+>>>
+
+Later, when you are finished with the instances you can either stop each
+individually or you can call the stop_all method on the Reservation object:
+
+>>> reservation.stop_all()
+>>>
+
+If you just want to get a list of all of your running instances, use
+the get_all_instances method of the connection object. Note that the
+list returned is actually a list of Reservation objects (which contain
+the Instances) and that the list may include recently terminated instances
+for a small period of time subsequent to their termination.
+
+>>> instances = conn.get_all_instances()
+>>> instances
+[Reservation:r-a76085ce, Reservation:r-a66085cf, Reservation:r-8c6085e5]
+>>> r = instances[0]
+>>> for inst in r.instances:
+... print inst.state
+u'terminated'
+>>>
+
+A recent addition to the EC2 api's is to allow other EC2 users to launch
+your images. There are a couple of ways of accessing this capability in
+boto but I'll show you the simplest way here. First of all, you need to
+know the Amazon ID for the user in question. The Amazon Id is a twelve
+digit number that appears on your Account Activity page at AWS. It looks
+like this:
+
+1234-5678-9012
+
+To use this number in API calls, you need to remove the dashes so in our
+example the user ID would be 12345678912. To allow the user associated
+with this ID to launch one of your images, let's assume that the variable
+image represents the Image you want to share. So:
+
+>>> image.get_launch_permissions()
+{}
+>>>
+
+The get_launch_permissions method returns a dictionary object two possible
+entries; user_ids or groups. In our case we haven't yet given anyone
+permission to launch our image so the dictionary is empty. To add our
+EC2 user:
+
+>>> image.set_launch_permissions(['123456789012'])
+True
+>>> image.get_launch_permissions()
+{'user_ids': [u'123456789012']}
+>>>
+
+We have now added the desired user to the launch permissions for the Image
+so that user will now be able to access and launch our Image. You can add
+multiple users at one time by adding them all to the list you pass in as
+a parameter to the method. To revoke the user's launch permissions:
+
+>>> image.remove_launch_permissions(['123456789012'])
+True
+>>> image.get_launch_permissions()
+{}
+>>>
+
+It is possible to pass a list of group names to the set_launch_permissions
+method, as well. The only group available at the moment is the group "all"
+which would allow any valid EC2 user to launch your image.
+
+Finally, you can completely reset the launch permissions for an Image with:
+
+>>> image.reset_launch_permissions()
+True
+>>>
+
+This will remove all users and groups from the launch permission list and
+makes the Image private, again.
+
+Security Groups
+----------------
+
+Amazon defines a security group as:
+
+"A security group is a named collection of access rules. These access rules
+ specify which ingress, i.e. incoming, network traffic should be delivered
+ to your instance."
+
+To get a listing of all currently defined security groups:
+
+>>> rs = conn.get_all_security_groups()
+>>> print rs
+[SecurityGroup:appserver, SecurityGroup:default, SecurityGroup:vnc, SecurityGroup:webserver]
+>>>
+
+Each security group can have an arbitrary number of rules which represent
+different network ports which are being enabled. To find the rules for a
+particular security group, use the rules attribute:
+
+>>> sg = rs[1]
+>>> sg.name
+u'default'
+>>> sg.rules
+[IPPermissions:tcp(0-65535),
+ IPPermissions:udp(0-65535),
+ IPPermissions:icmp(-1--1),
+ IPPermissions:tcp(22-22),
+ IPPermissions:tcp(80-80)]
+>>>
+
+In addition to listing the available security groups you can also create
+a new security group. I'll follow through the "Three Tier Web Service"
+example included in the EC2 Developer's Guide for an example of how to
+create security groups and add rules to them.
+
+First, let's create a group for our Apache web servers that allows HTTP
+access to the world:
+
+>>> web = conn.create_security_group('apache', 'Our Apache Group')
+>>> web
+SecurityGroup:apache
+>>> web.authorize('tcp', 80, 80, '0.0.0.0/0')
+True
+>>>
+
+The first argument is the ip protocol which can be one of; tcp, udp or icmp.
+The second argument is the FromPort or the beginning port in the range, the
+third argument is the ToPort or the ending port in the range and the last
+argument is the CIDR IP range to authorize access to.
+
+Next we create another group for the app servers:
+
+>>> app = conn.create_security_group('appserver', 'The application tier')
+>>>
+
+We then want to grant access between the web server group and the app
+server group. So, rather than specifying an IP address as we did in the
+last example, this time we will specify another SecurityGroup object.
+
+>>> app.authorize(src_group=web)
+True
+>>>
+
+Now, to verify that the web group now has access to the app servers, we want to
+temporarily allow SSH access to the web servers from our computer. Let's
+say that our IP address is 192.168.1.130 as it is in the EC2 Developer
+Guide. To enable that access:
+
+>>> web.authorize(ip_protocol='tcp', from_port=22, to_port=22, cidr_ip='192.168.1.130/32')
+True
+>>>
+
+Now that this access is authorized, we could ssh into an instance running in
+the web group and then try to telnet to specific ports on servers in the
+appserver group, as shown in the EC2 Developer's Guide. When this testing is
+complete, we would want to revoke SSH access to the web server group, like this:
+
+>>> web.rules
+[IPPermissions:tcp(80-80),
+ IPPermissions:tcp(22-22)]
+>>> web.revoke('tcp', 22, 22, cidr_ip='192.168.1.130/32')
+True
+>>> web.rules
+[IPPermissions:tcp(80-80)]
+>>>
+
+
+
+
+
+
+
diff --git a/vendor/boto/docs/source/elb_tut.rst b/vendor/boto/docs/source/elb_tut.rst
new file mode 100644
index 0000000000..b8735781f9
--- /dev/null
+++ b/vendor/boto/docs/source/elb_tut.rst
@@ -0,0 +1,202 @@
+.. _elb_tut:
+
+==========================================================
+An Introduction to boto's Elastic Load Balancing interface
+==========================================================
+
+This tutorial focuses on the boto interface for Elastic Load Balancing
+from Amazon Web Services. This tutorial assumes that you have already
+downloaded and installed boto, and are familiar with the boto ec2 interface.
+
+Elastic Load Balancing Concepts
+-------------------------------
+Elastic Load Balancing (ELB) is intimately connected with Amazon's Elastic
+Compute Cloud (EC2) service. Using the ELB service allows you to create a load
+balancer - a DNS endpoint and set of ports that distributes incoming requests
+to a set of ec2 instances. The advantages of using a load balancer is that it
+allows you to truly scale up or down a set of backend instances without
+disrupting service. Before the ELB service you had to do this manually by
+launching an EC2 instance and installing load balancer software on it (nginx,
+haproxy, perlbal, etc.) to distribute traffic to other EC2 instances.
+
+Recall that the ec2 service is split into Regions and Availability Zones (AZ).
+At the time of writing, there are two Regions - US and Europe, and each region
+is divided into a number of AZs (for example, us-east-1a, us-east-1b, etc.).
+You can think of AZs as data centers - each runs off a different set of ISP
+backbones and power providers. ELB load balancers can span multiple AZs but
+cannot span multiple regions. That means that if you'd like to create a set of
+instances spanning both the US and Europe Regions you'd have to create two load
+balancers and have some sort of other means of distributing requests between
+the two loadbalancers. An example of this could be using GeoIP techniques to
+choose the correct load balancer, or perhaps DNS round robin. Keep in mind also
+that traffic is distributed equally over all AZs the ELB balancer spans. This
+means you should have an equal number of instances in each AZ if you want to
+equally distribute load amongst all your instances.
+
+Creating a Connection
+---------------------
+The first step in accessing ELB is to create a connection to the service.
+There are two ways to do this in boto. The first is:
+
+>>> from boto.ec2.elb import ELBConnection
+>>> conn = ELBConnection('<aws access key>', '<aws secret key>')
+
+There is also a shortcut function in the boto package, called connect_elb
+that may provide a slightly easier means of creating a connection:
+
+>>> import boto
+>>> conn = boto.connect_elb()
+
+In either case, conn will point to an ELBConnection object which we will
+use throughout the remainder of this tutorial.
+
+A Note About Regions and Endpoints
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Like EC2 the ELB service has a different endpoint for each region. By default
+the US endpoint is used. To choose a specific region, instantiate the
+ELBConnection object with that region's endpoint.
+
+>>> ec2 = boto.connect_elb(host='eu-west-1.elasticloadbalancing.amazonaws.com')
+
+Alternatively, edit your boto.cfg with the default ELB endpoint to use::
+
+ [Boto]
+ elb_endpoint = eu-west-1.elasticloadbalancing.amazonaws.com
+
+Getting Existing Load Balancers
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To retrieve any exiting load balancers:
+
+>>> conn.get_all_load_balancers()
+
+You will get back a list of LoadBalancer objects.
+
+Creating a Load Balancer
+------------------------
+To create a load balancer you need the following:
+ #. The specific **ports and protocols** you want to load balancer over, and what port
+ you want to connect to all instances.
+ #. A **health check** - the ELB concept of a *heart beat* or *ping*. ELB will use this health
+ check to see whether your instances are up or down. If they go down, the load balancer
+ will no longer send requests to them.
+ #. A **list of Availability Zones** you'd like to create your load balancer over.
+
+Ports and Protocols
+^^^^^^^^^^^^^^^^^^^
+An incoming connection to your load balancer will come on one or more ports -
+for example 80 (HTTP) and 443 (HTTPS). Each can be using a protocol -
+currently, the supported protocols are TCP and HTTP. We also need to tell the
+load balancer which port to route connects *to* on each instance. For example,
+to create a load balancer for a website that accepts connections on 80 and 443,
+and that routes connections to port 8080 and 8443 on each instance, you would
+specify that the load balancer ports and protocols are:
+
+ * 80, 8080, HTTP
+ * 443, 8443, TCP
+
+This says that the load balancer will listen on two ports - 80 and 443.
+Connections on 80 will use an HTTP load balancer to forward connections to port
+8080 on instances. Likewise, the load balancer will listen on 443 to forward
+connections to 8443 on each instance using the TCP balancer. We need to
+use TCP for the HTTPS port because it is encrypted at the application
+layer. Of course, we could specify the load balancer use TCP for port 80,
+however specifying HTTP allows you to let ELB handle some work for you -
+for example HTTP header parsing.
+
+
+Configuring a Health Check
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+A health check allows ELB to determine which instances are alive and able to
+respond to requests. A health check is essentially a tuple consisting of:
+
+ * *target*: What to check on an instance. For a TCP check this is comprised of::
+
+ TCP:PORT_TO_CHECK
+
+ Which attempts to open a connection on PORT_TO_CHECK. If the connection opens
+ successfully, that specific instance is deemed healthy, otherwise it is marked
+ temporarily as unhealthy. For HTTP, the situation is slightly different::
+
+ HTTP:PORT_TO_CHECK/RESOURCE
+
+ This means that the health check will connect to the resource /RESOURCE on
+ PORT_TO_CHECK. If an HTTP 200 status is returned the instance is deemed healthy.
+ * *interval*: How often the check is made. This is given in seconds and defaults to 30.
+ The valid range of intervals goes from 5 seconds to 600 seconds.
+ * *timeout*: The number of seconds the load balancer will wait for a check to return a
+ result.
+ * *UnhealthyThreshold*: The number of consecutive failed checks to deem the instance
+ as being dead. The default is 5, and the range of valid values lies from 2 to 10.
+
+The following example creates a health check called *instance_health* that simply checks
+instances every 20 seconds on port 80 over HTTP at the resource /health for 200 successes.
+
+>>> import boto
+>>> from boto.ec2.elb import HealthCheck
+>>> conn = boto.connect_elb()
+>>> hc = HealthCheck('instance_health', interval=20, target='HTTP:8080/health')
+
+Putting It All Together
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Finally, let's create a load balancer in the US region that listens on ports 80 and 443
+and distributes requests to instances on 8080 and 8443 over HTTP and TCP. We want the
+load balancer to span the availability zones *us-east-1a* and *us-east-1b*:
+
+>>> lb = conn.create_load_balancer('my_lb', ['us-east-1a', 'us-east-1b'],
+ [(80, 8080, 'http'), (443, 8443, 'tcp')])
+>>> lb.configure_health_check(hc)
+
+The load balancer has been created. To see where you can actually connect to it, do:
+
+>>> print lb.dns_name
+my_elb-123456789.us-east-1.elb.amazonaws.com
+
+You can then CNAME map a better name, i.e. www.MYWEBSITE.com to the above address.
+
+Adding Instances To a Load Balancer
+-----------------------------------
+
+Now that the load balancer has been created, there are two ways to add instances to it:
+
+ #. Manually, adding each instance in turn.
+ #. Mapping an autoscale group to the load balancer. Please see the Autoscale
+ tutorial for information on how to do this.
+
+Manually Adding and Removing Instances
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Assuming you have a list of instance ids, you can add them to the load balancer
+
+>>> instance_ids = ['i-4f8cf126', 'i-0bb7ca62']
+>>> lb.register_instances(instance_ids)
+
+Keep in mind that these instances should be in Security Groups that match the
+internal ports of the load balancer you just created (for this example, they
+should allow incoming connections on 8080 and 8443).
+
+To remove instances:
+
+>>> lb.degregister_instances(instance_ids)
+
+Modifying Availability Zones for a Load Balancer
+------------------------------------------------
+
+If you wanted to disable one or more zones from an existing load balancer:
+
+>>> lb.disable_zones(['us-east-1a'])
+
+You can then terminate each instance in the disabled zone and then deregister then from your load
+balancer.
+
+To enable zones:
+
+>>> lb.enable_zones(['us-east-1c'])
+
+Deleting a Load Balancer
+------------------------
+
+>>> lb.delete()
+
+
diff --git a/vendor/boto/docs/source/index.rst b/vendor/boto/docs/source/index.rst
new file mode 100644
index 0000000000..24b6ba058d
--- /dev/null
+++ b/vendor/boto/docs/source/index.rst
@@ -0,0 +1,52 @@
+.. _index:
+
+===============================================
+boto: A Python interface to Amazon Web Services
+===============================================
+
+An integrated interface to current and future infrastructural services
+offered by Amazon Web Services.
+
+Currently, this includes:
+
+- Simple Storage Service (S3)
+- Simple Queue Service (SQS)
+- Elastic Compute Cloud (EC2)
+
+ * Elastic Load Balancer (ELB)
+ * CloudWatch
+ * AutoScale
+
+- Mechanical Turk
+- SimpleDB (SDB) - See SimpleDbPage for details
+- CloudFront
+- Virtual Private Cloud (VPC)
+
+Follow project updates on Twitter (http://twitter.com/pythonboto).
+
+Follow Mitch on Twitter (http://twitter.com/garnaat).
+
+
+Documentation Contents
+----------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ sqs_tut
+ s3_tut
+ ec2_tut
+ elb_tut
+ autoscale_tut
+ vpc_tut
+ ref/index
+ documentation
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/vendor/boto/docs/source/ref/boto.rst b/vendor/boto/docs/source/ref/boto.rst
new file mode 100644
index 0000000000..5a241b3460
--- /dev/null
+++ b/vendor/boto/docs/source/ref/boto.rst
@@ -0,0 +1,47 @@
+.. _ref-boto:
+
+====
+boto
+====
+
+boto
+----
+
+.. automodule:: boto
+ :members:
+ :undoc-members:
+
+boto.connection
+---------------
+
+.. automodule:: boto.connection
+ :members:
+ :undoc-members:
+
+boto.exception
+--------------
+
+.. automodule:: boto.exception
+ :members:
+ :undoc-members:
+
+boto.handler
+------------
+
+.. automodule:: boto.handler
+ :members:
+ :undoc-members:
+
+boto.resultset
+--------------
+
+.. automodule:: boto.resultset
+ :members:
+ :undoc-members:
+
+boto.utils
+----------
+
+.. automodule:: boto.utils
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/ref/cloudfront.rst b/vendor/boto/docs/source/ref/cloudfront.rst
new file mode 100644
index 0000000000..5cb80beb23
--- /dev/null
+++ b/vendor/boto/docs/source/ref/cloudfront.rst
@@ -0,0 +1,108 @@
+.. ref-cloudfront
+
+==========
+cloudfront
+==========
+
+A Crash Course in CloudFront in Boto
+------------------------------------
+
+This new boto module provides an interface to Amazon's new Content Service, CloudFront.
+
+Caveats:
+
+This module is not well tested. Paging of distributions is not yet
+supported. CNAME support is completely untested. Use with caution.
+Feedback and bug reports are greatly appreciated.
+
+The following shows the main features of the cloudfront module from an interactive shell:
+
+Create an cloudfront connection:
+
+>>> from boto.cloudfront import CloudFrontConnection
+>>> c = CloudFrontConnection()
+
+Create a new :class:`boto.cloudfront.distribution.Distribution`:
+
+>>> distro = c.create_distribution(origin='mybucket.s3.amazonaws.com', enabled=False, comment='My new Distribution')
+>>> d.domain_name
+u'd2oxf3980lnb8l.cloudfront.net'
+>>> d.id
+u'ECH69MOIW7613'
+>>> d.status
+u'InProgress'
+>>> d.config.comment
+u'My new distribution'
+>>> d.config.origin
+u'mybucket.s3.amazonaws.com'
+>>> d.config.caller_reference
+u'31b8d9cf-a623-4a28-b062-a91856fac6d0'
+>>> d.config.enabled
+False
+
+Note that a new caller reference is created automatically, using
+uuid.uuid4(). The :class:`boto.cloudfront.distribution.Distribution`, :class:`boto.cloudfront.distribution.DistributionConfig` and
+:class:`boto.cloudfront.distribution.DistributionSummary` objects are defined in the :mod:`boto.cloudfront.distribution`
+module.
+
+To get a listing of all current distributions:
+
+>>> rs = c.get_all_distributions()
+>>> rs
+[<boto.cloudfront.distribution.DistributionSummary instance at 0xe8d4e0>,
+ <boto.cloudfront.distribution.DistributionSummary instance at 0xe8d788>]
+
+This returns a list of :class:`boto.cloudfront.distribution.DistributionSummary` objects. Note that paging
+is not yet supported! To get a :class:`boto.cloudfront.distribution.DistributionObject` from a
+:class:`boto.cloudfront.distribution.DistributionSummary` object:
+
+>>> ds = rs[1]
+>>> distro = ds.get_distribution()
+>>> distro.domain_name
+u'd2oxf3980lnb8l.cloudfront.net'
+
+To change a property of a distribution object:
+
+>>> distro.comment
+u'My new distribution'
+>>> distro.update(comment='This is a much better comment')
+>>> distro.comment
+'This is a much better comment'
+
+You can also enable/disable a distribution using the following
+convenience methods:
+
+>>> distro.enable() # just calls distro.update(enabled=True)
+
+or
+
+>>> distro.disable() # just calls distro.update(enabled=False)
+
+The only attributes that can be updated for a Distribution are
+comment, enabled and cnames.
+
+To delete a :class:`boto.cloudfront.distribution.Distribution`:
+
+>>> distro.delete()
+
+
+boto.cloudfront
+---------------
+
+.. automodule:: boto.cloudfront
+ :members:
+ :undoc-members:
+
+boto.cloudfront.distribution
+----------------------------
+
+.. automodule:: boto.cloudfront.distribution
+ :members:
+ :undoc-members:
+
+boto.cloudfront.exception
+-------------------------
+
+.. automodule:: boto.cloudfront.exception
+ :members:
+ :undoc-members: \ No newline at end of file
diff --git a/vendor/boto/docs/source/ref/contrib.rst b/vendor/boto/docs/source/ref/contrib.rst
new file mode 100644
index 0000000000..9262a0dc81
--- /dev/null
+++ b/vendor/boto/docs/source/ref/contrib.rst
@@ -0,0 +1,32 @@
+.. ref-contrib
+
+=======
+contrib
+=======
+
+boto.contrib
+------------
+
+.. automodule:: boto.contrib
+ :members:
+ :undoc-members:
+
+boto.contrib.m2helpers
+----------------------
+
+.. note::
+
+ This module requires installation of M2Crypto__ in your Python path.
+
+ __ http://sandbox.rulemaker.net/ngps/m2/
+
+.. automodule:: boto.contrib.m2helpers
+ :members:
+ :undoc-members:
+
+boto.contrib.ymlmessage
+-----------------------
+
+.. automodule:: boto.contrib.ymlmessage
+ :members:
+ :undoc-members: \ No newline at end of file
diff --git a/vendor/boto/docs/source/ref/ec2.rst b/vendor/boto/docs/source/ref/ec2.rst
new file mode 100644
index 0000000000..e6215d7ab3
--- /dev/null
+++ b/vendor/boto/docs/source/ref/ec2.rst
@@ -0,0 +1,223 @@
+.. ref-ec2
+
+===
+EC2
+===
+
+boto.ec2
+--------
+
+.. automodule:: boto.ec2
+ :members:
+ :undoc-members:
+
+boto.ec2.address
+----------------
+
+.. automodule:: boto.ec2.address
+ :members:
+ :undoc-members:
+
+boto.ec2.autoscale
+------------------
+
+.. automodule:: boto.ec2.autoscale
+ :members:
+ :undoc-members:
+
+boto.ec2.autoscale.activity
+---------------------------
+
+.. automodule:: boto.ec2.autoscale.activity
+ :members:
+ :undoc-members:
+
+boto.ec2.autoscale.group
+------------------------
+
+.. automodule:: boto.ec2.autoscale.group
+ :members:
+ :undoc-members:
+
+
+boto.ec2.autoscale.instance
+---------------------------
+
+.. automodule:: boto.ec2.autoscale.instance
+ :members:
+ :undoc-members:
+
+boto.ec2.autoscale.launchconfig
+-------------------------------
+
+.. automodule:: boto.ec2.autoscale.launchconfig
+ :members:
+ :undoc-members:
+
+boto.ec2.autoscale.request
+--------------------------
+
+.. automodule:: boto.ec2.autoscale.request
+ :members:
+ :undoc-members:
+
+boto.ec2.autoscale.trigger
+--------------------------
+
+.. automodule:: boto.ec2.autoscale.trigger
+ :members:
+ :undoc-members:
+
+boto.ec2.buyreservation
+-----------------------
+
+.. automodule:: boto.ec2.buyreservation
+ :members:
+ :undoc-members:
+
+boto.ec2.cloudwatch
+-------------------
+
+.. automodule:: boto.ec2.cloudwatch
+ :members:
+ :undoc-members:
+
+boto.ec2.cloudwatch.datapoint
+-----------------------------
+
+.. automodule:: boto.ec2.cloudwatch.datapoint
+ :members:
+ :undoc-members:
+
+boto.ec2.cloudwatch.metric
+--------------------------
+
+.. automodule:: boto.ec2.cloudwatch.metric
+ :members:
+ :undoc-members:
+
+boto.ec2.connection
+-------------------
+
+.. automodule:: boto.ec2.connection
+ :members:
+ :undoc-members:
+
+boto.ec2.ec2object
+------------------
+
+.. automodule:: boto.ec2.ec2object
+ :members:
+ :undoc-members:
+
+boto.ec2.elb
+------------
+
+.. automodule:: boto.ec2.elb
+ :members:
+ :undoc-members:
+
+boto.ec2.elb.healthcheck
+------------------------
+
+.. automodule:: boto.ec2.elb.healthcheck
+ :members:
+ :undoc-members:
+
+boto.ec2.elb.instancestate
+--------------------------
+
+.. automodule:: boto.ec2.elb.instancestate
+ :members:
+ :undoc-members:
+
+boto.ec2.elb.listelement
+------------------------
+
+.. automodule:: boto.ec2.elb.listelement
+ :members:
+ :undoc-members:
+
+boto.ec2.elb.listener
+---------------------
+
+.. automodule:: boto.ec2.elb.listener
+ :members:
+ :undoc-members:
+
+boto.ec2.elb.loadbalancer
+-------------------------
+
+.. automodule:: boto.ec2.elb.loadbalancer
+ :members:
+ :undoc-members:
+
+boto.ec2.image
+--------------
+
+.. automodule:: boto.ec2.image
+ :members:
+ :undoc-members:
+
+boto.ec2.instance
+-----------------
+
+.. automodule:: boto.ec2.instance
+ :members:
+ :undoc-members:
+
+boto.ec2.instanceinfo
+---------------------
+
+.. automodule:: boto.ec2.instanceinfo
+ :members:
+ :undoc-members:
+
+boto.ec2.keypair
+----------------
+
+.. automodule:: boto.ec2.keypair
+ :members:
+ :undoc-members:
+
+boto.ec2.regioninfo
+-------------------
+
+.. automodule:: boto.ec2.regioninfo
+ :members:
+ :undoc-members:
+
+boto.ec2.reservedinstance
+-------------------------
+
+.. automodule:: boto.ec2.reservedinstance
+ :members:
+ :undoc-members:
+
+boto.ec2.securitygroup
+----------------------
+
+.. automodule:: boto.ec2.securitygroup
+ :members:
+ :undoc-members:
+
+boto.ec2.snapshot
+-----------------
+
+.. automodule:: boto.ec2.snapshot
+ :members:
+ :undoc-members:
+
+boto.ec2.volume
+---------------
+
+.. automodule:: boto.ec2.volume
+ :members:
+ :undoc-members:
+
+boto.ec2.zone
+-------------
+
+.. automodule:: boto.ec2.zone
+ :members:
+ :undoc-members: \ No newline at end of file
diff --git a/vendor/boto/docs/source/ref/fps.rst b/vendor/boto/docs/source/ref/fps.rst
new file mode 100644
index 0000000000..c160eee059
--- /dev/null
+++ b/vendor/boto/docs/source/ref/fps.rst
@@ -0,0 +1,19 @@
+.. ref-fps
+
+===
+fps
+===
+
+boto.fps
+--------
+
+.. automodule:: boto.fps
+ :members:
+ :undoc-members:
+
+boto.fps.connection
+-------------------
+
+.. automodule:: boto.fps.connection
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/ref/index.rst b/vendor/boto/docs/source/ref/index.rst
new file mode 100644
index 0000000000..ca1c93029c
--- /dev/null
+++ b/vendor/boto/docs/source/ref/index.rst
@@ -0,0 +1,25 @@
+.. _ref-index:
+
+=============
+API Reference
+=============
+
+.. toctree::
+ :maxdepth: 4
+
+ boto
+ cloudfront
+ contrib
+ ec2
+ fps
+ manage
+ mapreduce
+ mashups
+ mturk
+ pyami
+ rds
+ s3
+ sdb
+ services
+ sqs
+ vpc \ No newline at end of file
diff --git a/vendor/boto/docs/source/ref/manage.rst b/vendor/boto/docs/source/ref/manage.rst
new file mode 100644
index 0000000000..a175d88b99
--- /dev/null
+++ b/vendor/boto/docs/source/ref/manage.rst
@@ -0,0 +1,47 @@
+.. ref-manage
+
+======
+manage
+======
+
+boto.manage
+-----------
+
+.. automodule:: boto.manage
+ :members:
+ :undoc-members:
+
+boto.manage.cmdshell
+--------------------
+
+.. automodule:: boto.manage.cmdshell
+ :members:
+ :undoc-members:
+
+boto.manage.propget
+-------------------
+
+.. automodule:: boto.manage.propget
+ :members:
+ :undoc-members:
+
+boto.manage.server
+------------------
+
+.. automodule:: boto.manage.server
+ :members:
+ :undoc-members:
+
+boto.manage.task
+----------------
+
+.. automodule:: boto.manage.task
+ :members:
+ :undoc-members:
+
+boto.manage.volume
+------------------
+
+.. automodule:: boto.manage.volume
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/ref/mapreduce.rst b/vendor/boto/docs/source/ref/mapreduce.rst
new file mode 100644
index 0000000000..97aa56cc3b
--- /dev/null
+++ b/vendor/boto/docs/source/ref/mapreduce.rst
@@ -0,0 +1,38 @@
+.. ref-mapreduce
+
+=========
+mapreduce
+=========
+
+.. note::
+
+ I am not sure why pdb_delete, pdb_revert, pdb_describe, and pdb_upload are not available for import.
+
+
+boto.mapreduce
+--------------
+
+.. automodule:: boto.mapreduce
+ :members:
+ :undoc-members:
+
+boto.mapreduce.lqs
+------------------
+
+.. automodule:: boto.mapreduce.lqs
+ :members:
+ :undoc-members:
+
+boto.mapreduce.partitiondb
+--------------------------
+
+.. automodule:: boto.mapreduce.partitiondb
+ :members:
+ :undoc-members:
+
+boto.mapreduce.queuetools
+-------------------------
+
+.. automodule:: boto.mapreduce.queuetools
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/ref/mashups.rst b/vendor/boto/docs/source/ref/mashups.rst
new file mode 100644
index 0000000000..5eca84675a
--- /dev/null
+++ b/vendor/boto/docs/source/ref/mashups.rst
@@ -0,0 +1,40 @@
+.. ref-mashups
+
+=======
+mashups
+=======
+
+boto.mashups
+------------
+
+.. automodule:: boto.mashups
+ :members:
+ :undoc-members:
+
+boto.mashups.interactive
+------------------------
+
+.. automodule:: boto.mashups.interactive
+ :members:
+ :undoc-members:
+
+boto.mashups.iobject
+--------------------
+
+.. automodule:: boto.mashups.iobject
+ :members:
+ :undoc-members:
+
+boto.mashups.order
+------------------
+
+.. automodule:: boto.mashups.order
+ :members:
+ :undoc-members:
+
+boto.mashups.server
+-------------------
+
+.. automodule:: boto.mashups.server
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/ref/mturk.rst b/vendor/boto/docs/source/ref/mturk.rst
new file mode 100644
index 0000000000..1c8429b32c
--- /dev/null
+++ b/vendor/boto/docs/source/ref/mturk.rst
@@ -0,0 +1,47 @@
+.. ref-mturk
+
+=====
+mturk
+=====
+
+boto.mturk
+------------
+
+.. automodule:: boto.mturk
+ :members:
+ :undoc-members:
+
+boto.mturk.connection
+---------------------
+
+.. automodule:: boto.mturk.connection
+ :members:
+ :undoc-members:
+
+boto.mturk.notification
+-----------------------
+
+.. automodule:: boto.mturk.notification
+ :members:
+ :undoc-members:
+
+boto.mturk.price
+----------------
+
+.. automodule:: boto.mturk.price
+ :members:
+ :undoc-members:
+
+boto.mturk.qualification
+------------------------
+
+.. automodule:: boto.mturk.qualification
+ :members:
+ :undoc-members:
+
+boto.mturk.question
+-------------------
+
+.. automodule:: boto.mturk.question
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/ref/pyami.rst b/vendor/boto/docs/source/ref/pyami.rst
new file mode 100644
index 0000000000..e573b34dca
--- /dev/null
+++ b/vendor/boto/docs/source/ref/pyami.rst
@@ -0,0 +1,103 @@
+.. ref-pyami
+
+=====
+pyami
+=====
+
+boto.pyami
+--------------
+
+.. automodule:: boto.pyami
+ :members:
+ :undoc-members:
+
+boto.pyami.bootstrap
+--------------------
+
+.. automodule:: boto.pyami.bootstrap
+ :members:
+ :undoc-members:
+
+boto.pyami.config
+-----------------
+
+.. automodule:: boto.pyami.config
+ :members:
+ :undoc-members:
+
+boto.pyami.copybot
+------------------
+
+.. automodule:: boto.pyami.copybot
+ :members:
+ :undoc-members:
+
+boto.pyami.installers
+---------------------
+
+.. automodule:: boto.pyami.installers
+ :members:
+ :undoc-members:
+
+boto.pyami.installers.ubuntu
+----------------------------
+
+.. automodule:: boto.pyami.installers.ubuntu
+ :members:
+ :undoc-members:
+
+boto.pyami.installers.ubuntu.apache
+-----------------------------------
+
+.. automodule:: boto.pyami.installers.ubuntu.apache
+ :members:
+ :undoc-members:
+
+boto.pyami.installers.ubuntu.ebs
+--------------------------------
+
+.. automodule:: boto.pyami.installers.ubuntu.ebs
+ :members:
+ :undoc-members:
+
+boto.pyami.installers.ubuntu.installer
+--------------------------------------
+
+.. automodule:: boto.pyami.installers.ubuntu.installer
+ :members:
+ :undoc-members:
+
+boto.pyami.installers.ubuntu.mysql
+----------------------------------
+
+.. automodule:: boto.pyami.installers.ubuntu.mysql
+ :members:
+ :undoc-members:
+
+boto.pyami.installers.ubuntu.trac
+---------------------------------
+
+.. automodule:: boto.pyami.installers.ubuntu.trac
+ :members:
+ :undoc-members:
+
+boto.pyami.launch_ami
+---------------------
+
+.. automodule:: boto.pyami.launch_ami
+ :members:
+ :undoc-members:
+
+boto.pyami.scriptbase
+---------------------
+
+.. automodule:: boto.pyami.scriptbase
+ :members:
+ :undoc-members:
+
+boto.pyami.startup
+------------------
+
+.. automodule:: boto.pyami.startup
+ :members:
+ :undoc-members: \ No newline at end of file
diff --git a/vendor/boto/docs/source/ref/rds.rst b/vendor/boto/docs/source/ref/rds.rst
new file mode 100644
index 0000000000..7f02d33254
--- /dev/null
+++ b/vendor/boto/docs/source/ref/rds.rst
@@ -0,0 +1,47 @@
+.. ref-rds
+
+===
+RDS
+===
+
+boto.rds
+--------
+
+.. automodule:: boto.rds
+ :members:
+ :undoc-members:
+
+boto.rds.dbinstance
+-------------------
+
+.. automodule:: boto.rds.dbinstance
+ :members:
+ :undoc-members:
+
+boto.rds.dbsecuritygroup
+------------------------
+
+.. automodule:: boto.rds.dbsecuritygroup
+ :members:
+ :undoc-members:
+
+boto.rds.dbsnapshot
+-------------------
+
+.. automodule:: boto.rds.dbsnapshot
+ :members:
+ :undoc-members:
+
+boto.rds.event
+--------------
+
+.. automodule:: boto.rds.event
+ :members:
+ :undoc-members:
+
+boto.rds.parametergroup
+-----------------------
+
+.. automodule:: boto.rds.parametergroup
+ :members:
+ :undoc-members: \ No newline at end of file
diff --git a/vendor/boto/docs/source/ref/s3.rst b/vendor/boto/docs/source/ref/s3.rst
new file mode 100644
index 0000000000..e9b0b39f3d
--- /dev/null
+++ b/vendor/boto/docs/source/ref/s3.rst
@@ -0,0 +1,54 @@
+.. ref-s3:
+
+===
+S3
+===
+
+boto.s3.acl
+-----------
+
+.. automodule:: boto.s3.acl
+ :members:
+ :undoc-members:
+
+boto.s3.bucket
+--------------
+
+.. automodule:: boto.s3.bucket
+ :members:
+ :undoc-members:
+
+boto.s3.bucketlistresultset
+---------------------------
+
+.. automodule:: boto.s3.bucketlistresultset
+ :members:
+ :undoc-members:
+
+boto.s3.connection
+------------------
+
+.. automodule:: boto.s3.connection
+ :members:
+ :undoc-members:
+
+boto.s3.key
+-----------
+
+.. automodule:: boto.s3.key
+ :members:
+ :undoc-members:
+
+boto.s3.prefix
+--------------
+
+.. automodule:: boto.s3.prefix
+ :members:
+ :undoc-members:
+
+boto.s3.user
+------------
+
+.. automodule:: boto.s3.user
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/ref/sdb.rst b/vendor/boto/docs/source/ref/sdb.rst
new file mode 100644
index 0000000000..8b96d00fff
--- /dev/null
+++ b/vendor/boto/docs/source/ref/sdb.rst
@@ -0,0 +1,144 @@
+.. ref-sdb
+
+===
+sdb
+===
+
+boto.sdb
+--------
+
+.. automodule:: boto.sdb
+ :members:
+ :undoc-members:
+
+boto.sdb.connection
+-------------------
+
+.. automodule:: boto.sdb.connection
+ :members:
+ :undoc-members:
+
+boto.sdb.db
+-----------
+
+.. automodule:: boto.sdb.db
+ :members:
+ :undoc-members:
+
+boto.sdb.db.blob
+----------------
+
+.. automodule:: boto.sdb.db.blob
+ :members:
+ :undoc-members:
+
+boto.sdb.db.key
+---------------
+
+.. automodule:: boto.sdb.db.key
+ :members:
+ :undoc-members:
+
+boto.sdb.db.manager
+-------------------
+
+.. automodule:: boto.sdb.db.manager
+ :members:
+ :undoc-members:
+
+boto.sdb.db.manager.pgmanager
+-----------------------------
+
+.. note::
+
+ This module requires psycopg2__ to be installed in the Python path.
+
+ __ http://initd.org/
+
+.. automodule:: boto.sdb.db.manager.pgmanager
+ :members:
+ :undoc-members:
+
+boto.sdb.db.manager.sdbmanager
+------------------------------
+
+.. automodule:: boto.sdb.db.manager.sdbmanager
+ :members:
+ :undoc-members:
+
+boto.sdb.db.manager.xmlmanager
+------------------------------
+
+.. automodule:: boto.sdb.db.manager.xmlmanager
+ :members:
+ :undoc-members:
+
+boto.sdb.db.model
+-----------------
+
+.. automodule:: boto.sdb.db.model
+ :members:
+ :undoc-members:
+
+boto.sdb.db.property
+--------------------
+
+.. automodule:: boto.sdb.db.property
+ :members:
+ :undoc-members:
+
+boto.sdb.db.query
+-----------------
+
+.. automodule:: boto.sdb.db.query
+ :members:
+ :undoc-members:
+
+boto.sdb.domain
+---------------
+
+.. automodule:: boto.sdb.domain
+ :members:
+ :undoc-members:
+
+boto.sdb.item
+-------------
+
+.. automodule:: boto.sdb.item
+ :members:
+ :undoc-members:
+
+boto.sdb.persist
+----------------
+
+.. automodule:: boto.sdb.persist
+ :members:
+ :undoc-members:
+
+boto.sdb.persist.checker
+------------------------
+
+.. automodule:: boto.sdb.persist.checker
+ :members:
+ :undoc-members:
+
+boto.sdb.persist.object
+-----------------------
+
+.. automodule:: boto.sdb.persist.object
+ :members:
+ :undoc-members:
+
+boto.sdb.persist.property
+-------------------------
+
+.. automodule:: boto.sdb.persist.property
+ :members:
+ :undoc-members:
+
+boto.sdb.queryresultset
+-----------------------
+
+.. automodule:: boto.sdb.queryresultset
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/ref/services.rst b/vendor/boto/docs/source/ref/services.rst
new file mode 100644
index 0000000000..aa73dcc274
--- /dev/null
+++ b/vendor/boto/docs/source/ref/services.rst
@@ -0,0 +1,61 @@
+.. ref-services
+
+========
+services
+========
+
+boto.services
+-------------
+
+.. automodule:: boto.services
+ :members:
+ :undoc-members:
+
+boto.services.bs
+----------------
+
+.. automodule:: boto.services.bs
+ :members:
+ :undoc-members:
+
+boto.services.message
+---------------------
+
+.. automodule:: boto.services.message
+ :members:
+ :undoc-members:
+
+boto.services.result
+--------------------
+
+.. automodule:: boto.services.result
+ :members:
+ :undoc-members:
+
+boto.services.service
+---------------------
+
+.. automodule:: boto.services.service
+ :members:
+ :undoc-members:
+
+boto.services.servicedef
+------------------------
+
+.. automodule:: boto.services.servicedef
+ :members:
+ :undoc-members:
+
+boto.services.sonofmmm
+----------------------
+
+.. automodule:: boto.services.sonofmmm
+ :members:
+ :undoc-members:
+
+boto.services.submit
+--------------------
+
+.. automodule:: boto.services.submit
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/ref/sqs.rst b/vendor/boto/docs/source/ref/sqs.rst
new file mode 100644
index 0000000000..1d7b84e9a1
--- /dev/null
+++ b/vendor/boto/docs/source/ref/sqs.rst
@@ -0,0 +1,54 @@
+.. _ref-sqs:
+
+====
+SQS
+====
+
+boto.sqs
+--------
+
+.. automodule:: boto.sqs
+ :members:
+ :undoc-members:
+
+boto.sqs.attributes
+-------------------
+
+.. automodule:: boto.sqs.attributes
+ :members:
+ :undoc-members:
+
+boto.sqs.connection
+-------------------
+
+.. automodule:: boto.sqs.connection
+ :members:
+ :undoc-members:
+
+boto.sqs.jsonmessage
+--------------------
+
+.. automodule:: boto.sqs.jsonmessage
+ :members:
+ :undoc-members:
+
+boto.sqs.message
+----------------
+
+.. automodule:: boto.sqs.message
+ :members:
+ :undoc-members:
+
+boto.sqs.queue
+--------------
+
+.. automodule:: boto.sqs.queue
+ :members:
+ :undoc-members:
+
+boto.sqs.regioninfo
+-------------------
+
+.. automodule:: boto.sqs.regioninfo
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/ref/vpc.rst b/vendor/boto/docs/source/ref/vpc.rst
new file mode 100644
index 0000000000..dfa4c91313
--- /dev/null
+++ b/vendor/boto/docs/source/ref/vpc.rst
@@ -0,0 +1,54 @@
+.. _ref-vpc:
+
+====
+VPC
+====
+
+boto.vpc
+--------
+
+.. automodule:: boto.vpc
+ :members:
+ :undoc-members:
+
+boto.vpc.customergateway
+------------------------
+
+.. automodule:: boto.vpc.customergateway
+ :members:
+ :undoc-members:
+
+boto.vpc.dhcpoptions
+--------------------
+
+.. automodule:: boto.vpc.dhcpoptions
+ :members:
+ :undoc-members:
+
+boto.vpc.subnet
+---------------
+
+.. automodule:: boto.vpc.subnet
+ :members:
+ :undoc-members:
+
+boto.vpc.vpc
+------------
+
+.. automodule:: boto.vpc.vpc
+ :members:
+ :undoc-members:
+
+boto.vpc.vpnconnection
+----------------------
+
+.. automodule:: boto.vpc.vpnconnection
+ :members:
+ :undoc-members:
+
+boto.vpc.vpngateway
+-------------------
+
+.. automodule:: boto.vpc.vpngateway
+ :members:
+ :undoc-members:
diff --git a/vendor/boto/docs/source/s3_tut.rst b/vendor/boto/docs/source/s3_tut.rst
new file mode 100644
index 0000000000..460706c5bb
--- /dev/null
+++ b/vendor/boto/docs/source/s3_tut.rst
@@ -0,0 +1,213 @@
+.. _s3_tut:
+
+======================================
+An Introduction to boto's S3 interface
+======================================
+
+This tutorial focuses on the boto interface to the Simple Storage Service
+from Amazon Web Services. This tutorial assumes that you have already
+downloaded and installed boto.
+
+Creating a Connection
+---------------------
+The first step in accessing S3 is to create a connection to the service.
+There are two ways to do this in boto. The first is:
+
+>>> from boto.s3.connection import S3Connection
+>>> conn = S3Connection('<aws access key>', '<aws secret key>')
+
+At this point the variable conn will point to an S3Connection object. In
+this example, the AWS access key and AWS secret key are passed in to the
+method explicitely. Alternatively, you can set the environment variables:
+
+AWS_ACCESS_KEY_ID - Your AWS Access Key ID
+AWS_SECRET_ACCESS_KEY - Your AWS Secret Access Key
+
+and then call the constructor without any arguments, like this:
+
+>>> conn = S3Connection()
+
+There is also a shortcut function in the boto package, called connect_s3
+that may provide a slightly easier means of creating a connection:
+
+>>> import boto
+>>> conn = boto.connect_s3()
+
+In either case, conn will point to an S3Connection object which we will
+use throughout the remainder of this tutorial.
+
+Creating a Bucket
+-----------------
+
+Once you have a connection established with S3, you will probably want to
+create a bucket. A bucket is a container used to store key/value pairs
+in S3. A bucket can hold un unlimited about of data so you could potentially
+have just one bucket in S3 for all of your information. Or, you could create
+separate buckets for different types of data. You can figure all of that out
+later, first let's just create a bucket. That can be accomplished like this:
+
+>>> bucket = conn.create_bucket('mybucket')
+Traceback (most recent call last):
+ File "<stdin>", line 1, in ?
+ File "boto/connection.py", line 285, in create_bucket
+ raise S3CreateError(response.status, response.reason)
+boto.exception.S3CreateError: S3Error[409]: Conflict
+
+Whoa. What happended there? Well, the thing you have to know about
+buckets is that they are kind of like domain names. It's one flat name
+space that everyone who uses S3 shares. So, someone has already create
+a bucket called "mybucket" in S3 and that means no one else can grab that
+bucket name. So, you have to come up with a name that hasn't been taken yet.
+For example, something that uses a unique string as a prefix. Your
+AWS_ACCESS_KEY (NOT YOUR SECRET KEY!) could work but I'll leave it to
+your imagination to come up with something. I'll just assume that you
+found an acceptable name.
+
+The create_bucket method will create the requested bucket if it does not
+exist or will return the existing bucket if it does exist.
+
+Storing Data
+----------------
+
+Once you have a bucket, presumably you will want to store some data
+in it. S3 doesn't care what kind of information you store in your objects
+or what format you use to store it. All you need is a key that is unique
+within your bucket.
+
+The Key object is used in boto to keep track of data stored in S3. To store
+new data in S3, start by creating a new Key object:
+
+>>> from boto.s3.key import Key
+>>> k = Key(bucket)
+>>> k.key = 'foobar'
+>>> k.set_contents_from_string('This is a test of S3')
+
+The net effect of these statements is to create a new object in S3 with a
+key of "foobar" and a value of "This is a test of S3". To validate that
+this worked, quit out of the interpreter and start it up again. Then:
+
+>>> import boto
+>>> c = boto.connect_s3()
+>>> b = c.create_bucket('mybucket') # substitute your bucket name here
+>>> from boto.s3.key import Key
+>>> k = Key(b)
+>>> k.key = 'foobar'
+>>> k.get_contents_as_string()
+'This is a test of S3'
+
+So, we can definitely store and retrieve strings. A more interesting
+example may be to store the contents of a local file in S3 and then retrieve
+the contents to another local file.
+
+>>> k = Key(b)
+>>> k.key = 'myfile'
+>>> k.set_contents_from_filename('foo.jpg')
+>>> k.get_contents_to_filename('bar.jpg')
+
+There are a couple of things to note about this. When you send data to
+S3 from a file or filename, boto will attempt to determine the correct
+mime type for that file and send it as a Content-Type header. The boto
+package uses the standard mimetypes package in Python to do the mime type
+guessing. The other thing to note is that boto does stream the content
+to and from S3 so you should be able to send and receive large files without
+any problem.
+
+Listing All Available Buckets
+-----------------------------
+In addition to accessing specific buckets via the create_bucket method
+you can also get a list of all available buckets that you have created.
+
+>>> rs = conn.get_all_buckets()
+
+This returns a ResultSet object (see the SQS Tutorial for more info on
+ResultSet objects). The ResultSet can be used as a sequence or list type
+object to retrieve Bucket objects.
+
+>>> len(rs)
+11
+>>> for b in rs:
+... print b.name
+...
+<listing of available buckets>
+>>> b = rs[0]
+
+Setting / Getting the Access Control List for Buckets and Keys
+--------------------------------------------------------------
+The S3 service provides the ability to control access to buckets and keys
+within s3 via the Access Control List (ACL) associated with each object in
+S3. There are two ways to set the ACL for an object:
+
+1. Create a custom ACL that grants specific rights to specific users. At the
+ moment, the users that are specified within grants have to be registered
+ users of Amazon Web Services so this isn't as useful or as general as it
+ could be.
+
+2. Use a "canned" access control policy. There are four canned policies
+ defined:
+ a. private: Owner gets FULL_CONTROL. No one else has any access rights.
+ b. public-read: Owners gets FULL_CONTROL and the anonymous principal is granted READ access.
+ c. public-read-write: Owner gets FULL_CONTROL and the anonymous principal is granted READ and WRITE access.
+ d. authenticated-read: Owner gets FULL_CONTROL and any principal authenticated as a registered Amazon S3 user is granted READ access.
+
+Currently, boto only supports the second method using canned access control
+policies. A future version may allow setting of arbitrary ACL's if there
+is sufficient demand.
+
+To set the ACL for a bucket, use the set_acl method of the Bucket object.
+The argument passed to this method must be one of the four permissable
+canned policies named in the list CannedACLStrings contained in acl.py.
+For example, to make a bucket readable by anyone:
+
+>>> b.set_acl('public-read')
+
+You can also set the ACL for Key objects, either by passing an additional
+argument to the above method:
+
+>>> b.set_acl('public-read', 'foobar')
+
+where 'foobar' is the key of some object within the bucket b or you can
+call the set_acl method of the Key object:
+
+>>> k.set_acl('public-read')
+
+You can also retrieve the current ACL for a Bucket or Key object using the
+get_acl object. This method parses the AccessControlPolicy response sent
+by S3 and creates a set of Python objects that represent the ACL.
+
+>>> acp = b.get_acl()
+>>> acp
+<boto.acl.Policy instance at 0x2e6940>
+>>> acp.acl
+<boto.acl.ACL instance at 0x2e69e0>
+>>> acp.acl.grants
+[<boto.acl.Grant instance at 0x2e6a08>]
+>>> for grant in acp.acl.grants:
+... print grant.permission, grant.grantee
+...
+FULL_CONTROL <boto.user.User instance at 0x2e6a30>
+
+The Python objects representing the ACL can be found in the acl.py module
+of boto.
+
+Setting/Getting Metadata Values on Key Objects
+----------------------------------------------
+S3 allows arbitrary user metadata to be assigned to objects within a bucket.
+To take advantage of this S3 feature, you should use the set_metadata and
+get_metadata methods of the Key object to set and retrieve metadata associated
+with an S3 object. For example:
+
+>>> k = Key(b)
+>>> k.key = 'has_metadata'
+>>> k.set_metadata('meta1', 'This is the first metadata value')
+>>> k.set_metadata('meta2', 'This is the second metadata value')
+>>> k.set_contents_from_filename('foo.txt')
+
+This code associates two metadata key/value pairs with the Key k. To retrieve
+those values later:
+
+>>> k = b.get_key('has_metadata)
+>>> k.get_metadata('meta1')
+'This is the first metadata value'
+>>> k.get_metadata('meta2')
+'This is the second metadata value'
+>>>
diff --git a/vendor/boto/docs/source/sqs_tut.rst b/vendor/boto/docs/source/sqs_tut.rst
new file mode 100644
index 0000000000..d05cc53c2c
--- /dev/null
+++ b/vendor/boto/docs/source/sqs_tut.rst
@@ -0,0 +1,230 @@
+.. _sqs_tut:
+
+=======================================
+An Introduction to boto's SQS interface
+=======================================
+
+This tutorial focuses on the boto interface to the Simple Queue Service
+from Amazon Web Services. This tutorial assumes that you have already
+downloaded and installed boto.
+
+Creating a Connection
+---------------------
+The first step in accessing SQS is to create a connection to the service.
+There are two ways to do this in boto. The first is:
+
+>>> from boto.sqs.connection import SQSConnection
+>>> conn = SQSConnection('<aws access key>', '<aws secret key>')
+
+At this point the variable conn will point to an SQSConnection object. In
+this example, the AWS access key and AWS secret key are passed in to the
+method explicitely. Alternatively, you can set the environment variables:
+
+AWS_ACCESS_KEY_ID - Your AWS Access Key ID
+AWS_SECRET_ACCESS_KEY - Your AWS Secret Access Key
+
+and then call the constructor without any arguments, like this:
+
+>>> conn = SQSConnection()
+
+There is also a shortcut function in the boto package, called connect_sqs
+that may provide a slightly easier means of creating a connection:
+
+>>> import boto
+>>> conn = boto.connect_sqs()
+
+In either case, conn will point to an SQSConnection object which we will
+use throughout the remainder of this tutorial.
+
+Creating a Queue
+----------------
+
+Once you have a connection established with SQS, you will probably want to
+create a queue. That can be accomplished like this:
+
+>>> q = conn.create_queue('myqueue')
+
+The create_queue method will create the requested queue if it does not
+exist or will return the existing queue if it does exist. There is an
+optional parameter to create_queue called visibility_timeout. This basically
+controls how long a message will remain invisible to other queue readers
+once it has been read (see SQS documentation for more detailed explanation).
+If this is not explicitly specified the queue will be created with whatever
+default value SQS provides (currently 30 seconds). If you would like to
+specify another value, you could do so like this:
+
+>>> q = conn.create_queue('myqueue', 120)
+
+This would establish a default visibility timeout for this queue of 120
+seconds. As you will see later on, this default value for the queue can
+also be overridden each time a message is read from the queue. If you want
+to check what the default visibility timeout is for a queue:
+
+>>> q.get_timeout()
+30
+>>>
+
+Writing Messages
+----------------
+
+Once you have a queue, presumably you will want to write some messages
+to it. SQS doesn't care what kind of information you store in your messages
+or what format you use to store it. As long as the amount of data per
+message is less than or equal to 256Kb, it's happy.
+
+However, you may have a lot of specific requirements around the format of
+that data. For example, you may want to store one big string or you might
+want to store something that looks more like RFC822 messages or you might want
+to store a binary payload such as pickled Python objects.
+
+The way boto deals with this is to define a simple Message object that
+treats the message data as one big string which you can set and get. If that
+Message object meets your needs, you're good to go. However, if you need to
+incorporate different behavior in your message or handle different types of
+data you can create your own Message class. You just need to register that
+class with the queue so that it knows that when you read a message from the
+queue that it should create one of your message objects rather than the
+default boto Message object. To register your message class, you would:
+
+>>> q.set_message_class(MyMessage)
+
+where MyMessage is the class definition for your message class. Your
+message class should subclass the boto Message because there is a small
+bit of Python magic happening in the __setattr__ method of the boto Message
+class.
+
+For this tutorial, let's just assume that we are using the boto Message
+class. So, first we need to create a Message object:
+
+>>> from boto.sqs.message import Message
+>>> m = Message()
+>>> m.set_body('This is my first message.')
+>>> status = q.write(m)
+
+The write method returns a True if everything went well. If the write
+didn't succeed it will either return a False (meaning SQS simply chose
+not to write the message for some reason) or an exception if there was
+some sort of problem with the request.
+
+Reading Messages
+----------------
+
+So, now we have a message in our queue. How would we go about reading it?
+Here's one way:
+
+>>> rs = q.get_messages()
+>>> len(rs)
+1
+>>> m = rs[0]
+>>> m.get_body()
+u'This is my first message'
+
+The get_messages method also returns a ResultSet object as described
+above. In addition to the special attributes that we already talked
+about the ResultSet object also contains any results returned by the
+request. To get at the results you can treat the ResultSet as a
+sequence object (e.g. a list). We can check the length (how many results)
+and access particular items within the list using the slice notation
+familiar to Python programmers.
+
+At this point, we have read the message from the queue and SQS will make
+sure that this message remains invisible to other readers of the queue
+until the visibility timeout period for the queue expires. If I delete
+the message before the timeout period expires then no one will ever see
+the message again. However, if I don't delete it (maybe because I crashed
+or failed in some way, for example) it will magically reappear in my queue
+for someone else to read. If you aren't happy with the default visibility
+timeout defined for the queue, you can override it when you read a message:
+
+>>> q.get_messages(visibility_timeout=60)
+
+This means that regardless of what the default visibility timeout is for
+the queue, this message will remain invisible to other readers for 60
+seconds.
+
+The get_messages method can also return more than a single message. By
+passing a num_messages parameter (defaults to 1) you can control the maximum
+number of messages that will be returned by the method. To show this
+feature off, first let's load up a few more messages.
+
+>>> for i in range(1, 11):
+... m = Message('This is message %d' % i)
+... q.write(m)
+...
+>>> rs = q.get_messages(10)
+>>> len(rs)
+10
+
+Don't be alarmed if the length of the result set returned by the get_messages
+call is less than 10. Sometimes it takes some time for new messages to become
+visible in the queue. Give it a minute or two and they will all show up.
+
+If you want a slightly simpler way to read messages from a queue, you
+can use the read method. It will either return the message read or
+it will return None if no messages were available. You can also pass
+a visibility_timeout parameter to read, if you desire:
+
+>>> m = q.read(60)
+>>> m.get_body()
+u'This is my first message'
+
+Deleting Messages and Queues
+----------------------------
+
+Note that the first message we put in the queue is still there, even though
+we have read it a number of times. That's because we never deleted it. To
+remove a message from a queue:
+
+>>> q.delete_message(m)
+[]
+
+If I want to delete the entire queue, I would use:
+
+>>> conn.delete_queue(q)
+
+However, this won't succeed unless the queue is empty.
+
+Listing All Available Queues
+----------------------------
+In addition to accessing specific queues via the create_queue method
+you can also get a list of all available queues that you have created.
+
+>>> rs = conn.get_all_queues()
+
+This returns a ResultSet object, as described above. The ResultSet
+can be used as a sequence or list type object to retrieve Queue objects.
+
+>>> len(rs)
+11
+>>> for q in rs:
+... print q.id
+...
+<listing of available queues>
+>>> q = rs[0]
+
+Other Stuff
+-----------
+
+That covers the basic operations of creating queues, writing messages,
+reading messages, deleting messages, and deleting queues. There are a
+few utility methods in boto that might be useful as well. For example,
+to count the number of messages in a queue:
+
+>>> q.count()
+10
+
+This can be handy but is command as well as the other two utility methods
+I'll describe in a minute are inefficient and should be used with caution
+on queues with lots of messages (e.g. many hundreds or more). Similarly,
+you can clear (delete) all messages in a queue with:
+
+>>> q.clear()
+
+Be REAL careful with that one! Finally, if you want to dump all of the
+messages in a queue to a local file:
+
+>>> q.dump('messages.txt', sep='\n------------------\n')
+
+This will read all of the messages in the queue and write the bodies of
+each of the messages to the file messages.txt. The option sep argument
+is a separator that will be printed between each message body in the file.
diff --git a/vendor/boto/docs/source/vpc_tut.rst b/vendor/boto/docs/source/vpc_tut.rst
new file mode 100644
index 0000000000..0040866f89
--- /dev/null
+++ b/vendor/boto/docs/source/vpc_tut.rst
@@ -0,0 +1,88 @@
+.. _vpc_tut:
+
+=======================================
+An Introduction to boto's VPC interface
+=======================================
+
+This tutorial is based on the examples in the Amazon Virtual Private
+Cloud Getting Started Guide (http://docs.amazonwebservices.com/AmazonVPC/latest/GettingStartedGuide/).
+In each example, it tries to show the boto request that correspond to
+the AWS command line tools.
+
+Creating a VPC connection
+-------------------------
+First, we need to create a new VPC connection:
+
+>>> from boto.vpc import VPCConnection
+>>> c = VPCConnection()
+
+To create a VPC
+---------------
+Now that we have a VPC connection, we can create our first VPC.
+
+>>> vpc = c.create_vpc('10.0.0.0/24')
+>>> vpc
+VPC:vpc-6b1fe402
+>>> vpc.id
+u'vpc-6b1fe402'
+>>> vpc.state
+u'pending'
+>>> vpc.cidr_block
+u'10.0.0.0/24'
+>>> vpc.dhcp_options_id
+u'default'
+>>>
+
+To create a subnet
+------------------
+The next step is to create a subnet to associate with your VPC.
+
+>>> subnet = c.create_subnet(vpc.id, '10.0.0.0/25')
+>>> subnet.id
+u'subnet-6a1fe403'
+>>> subnet.state
+u'pending'
+>>> subnet.cidr_block
+u'10.0.0.0/25'
+>>> subnet.available_ip_address_count
+123
+>>> subnet.availability_zone
+u'us-east-1b'
+>>>
+
+To create a customer gateway
+----------------------------
+Next, we create a customer gateway.
+
+>>> cg = c.create_customer_gateway('ipsec.1', '12.1.2.3', 65534)
+>>> cg.id
+u'cgw-b6a247df'
+>>> cg.type
+u'ipsec.1'
+>>> cg.state
+u'available'
+>>> cg.ip_address
+u'12.1.2.3'
+>>> cg.bgp_asn
+u'65534'
+>>>
+
+To create a VPN gateway
+-----------------------
+
+>>> vg = c.create_vpn_gateway('ipsec.1')
+>>> vg.id
+u'vgw-44ad482d'
+>>> vg.type
+u'ipsec.1'
+>>> vg.state
+u'pending'
+>>> vg.availability_zone
+u'us-east-1b'
+>>>
+
+Attaching a VPN Gateway to a VPC
+--------------------------------
+
+>>> vg.attach(vpc.id)
+>>>
diff --git a/vendor/boto/pylintrc b/vendor/boto/pylintrc
new file mode 100644
index 0000000000..44ed07796f
--- /dev/null
+++ b/vendor/boto/pylintrc
@@ -0,0 +1,305 @@
+# lint Python modules using external checkers.
+#
+# This is the main checker controlling the other ones and the reports
+# generation. It is itself both a raw checker and an astng checker in order
+# to:
+# * handle message activation / deactivation at the module level
+# * handle some basic but necessary stats'data (number of classes, methods...)
+#
+[MASTER]
+
+
+# Specify a configuration file.
+#rcfile=
+
+# Profiled execution.
+profile=no
+
+# Add <file or directory> to the black list. It should be a base name, not a
+# path. You may set this option multiple times.
+ignore=.svn
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Set the cache size for astng objects.
+cache-size=500
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable only checker(s) with the given id(s). This option conflict with the
+# disable-checker option
+#enable-checker=
+
+# Enable all checker(s) except those with the given id(s). This option conflict
+# with the disable-checker option
+#disable-checker=
+
+# Enable all messages in the listed categories.
+#enable-msg-cat=
+
+# Disable all messages in the listed categories.
+#disable-msg-cat=
+
+# Enable the message(s) with the given id(s).
+#enable-msg=
+
+# Disable the message(s) with the given id(s).
+# disable-msg=C0323,W0142,C0301,C0103,C0111,E0213,C0302,C0203,W0703,R0201
+disable-msg=C0111,C0103,W0703,W0702
+
+[REPORTS]
+
+# set the output format. Available formats are text, parseable, colorized and
+# html
+output-format=colorized
+
+# Include message's id in output
+include-ids=yes
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells wether to display a full report or only the messages
+reports=yes
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note).You have access to the variables errors warning, statement which
+# respectivly contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (R0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (R0004).
+comment=no
+
+# Enable the report(s) with the given id(s).
+#enable-report=
+
+# Disable the report(s) with the given id(s).
+#disable-report=
+
+# checks for
+# * unused variables / imports
+# * undefined variables
+# * redefinition of variable from builtins or from an outer scope
+# * use of variable before assigment
+#
+[VARIABLES]
+
+# Tells wether we should check for unused import in __init__ files.
+init-import=yes
+
+# A regular expression matching names used for dummy variables (i.e. not used).
+dummy-variables-rgx=_|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+# try to find bugs in the code using type inference
+#
+[TYPECHECK]
+
+# Tells wether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# When zope mode is activated, consider the acquired-members option to ignore
+# access to some undefined attributes.
+zope=no
+
+# List of members which are usually get through zope's acquisition mecanism and
+# so shouldn't trigger E0201 when accessed (need zope=yes to be considered).
+acquired-members=REQUEST,acl_users,aq_parent
+
+
+# checks for :
+# * doc strings
+# * modules / classes / functions / methods / arguments / variables name
+# * number of arguments, local variables, branches, returns and statements in
+# functions, methods
+# * required module attributes
+# * dangerous default values as arguments
+# * redefinition of function / method / class
+# * uses of the global statement
+#
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=apply,input
+
+
+# checks for sign of poor/misdesign:
+# * number of methods, attributes, local variables...
+# * size, complexity of functions, methods
+#
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=12
+
+# Maximum number of locals for function / method body
+max-locals=30
+
+# Maximum number of return / yield for function / method body
+max-returns=12
+
+# Maximum number of branch for function / method body
+max-branchs=30
+
+# Maximum number of statements in function / method body
+max-statements=60
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=20
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=0
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+# checks for
+# * external modules dependencies
+# * relative / wildcard imports
+# * cyclic imports
+# * uses of deprecated modules
+#
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report R0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report R0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report R0402 must
+# not be disabled)
+int-import-graph=
+
+
+# checks for :
+# * methods without self as first argument
+# * overridden methods signature
+# * access only to existant members via self
+# * attributes not defined in the __init__ method
+# * supported interfaces implementation
+# * unreachable code
+#
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+# ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+
+# checks for similarities and duplicated code. This computation may be
+# memory / CPU intensive, so you should disable it if you experiments some
+# problems.
+#
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=5
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+
+# checks for:
+# * warning notes in the code like FIXME, XXX
+# * PEP 263: source code with non ascii character but no encoding declaration
+#
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO,BUG:
+
+
+# checks for :
+# * unauthorized constructions
+# * strict indentation
+# * line length
+# * use of <> instead of !=
+#
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+[MESSAGES CONTROL]
+disable-msg=C0301,C0111,C0103,R0201,W0702,C0324
diff --git a/vendor/boto/setup.py b/vendor/boto/setup.py
new file mode 100644
index 0000000000..bbfbf4a1ab
--- /dev/null
+++ b/vendor/boto/setup.py
@@ -0,0 +1,56 @@
+#!/usr/bin/python
+
+# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+
+from boto import Version
+
+setup(name = "boto",
+ version = Version,
+ description = "Amazon Web Services Library",
+ long_description="Python interface to Amazon's Web Services.",
+ author = "Mitch Garnaat",
+ author_email = "mitch@garnaat.com",
+ scripts = ["bin/sdbadmin", "bin/elbadmin", "bin/cfadmin",
+ "bin/s3put", "bin/fetch_file", "bin/launch_instance",
+ "bin/list_instances", "bin/taskadmin", "bin/kill_instance",
+ "bin/bundle_image", "bin/pyami_sendmail"],
+ url = "http://code.google.com/p/boto/",
+ packages = [ 'boto', 'boto.sqs', 'boto.s3',
+ 'boto.ec2', 'boto.ec2.cloudwatch', 'boto.ec2.autoscale', 'boto.ec2.elb',
+ 'boto.sdb', 'boto.sdb.persist', 'boto.sdb.db', 'boto.sdb.db.manager',
+ 'boto.mturk', 'boto.pyami', 'boto.mashups', 'boto.contrib', 'boto.manage',
+ 'boto.services', 'boto.tests', 'boto.cloudfront', 'boto.rds', 'boto.vpc',
+ 'boto.fps', 'boto.emr'],
+ license = 'MIT',
+ platforms = 'Posix; MacOS X; Windows',
+ classifiers = [ 'Development Status :: 3 - Alpha',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: OS Independent',
+ 'Topic :: Internet',
+ ],
+ )
diff --git a/vendor/carrot/__init__.py b/vendor/carrot/__init__.py
new file mode 100644
index 0000000000..d40099b670
--- /dev/null
+++ b/vendor/carrot/__init__.py
@@ -0,0 +1,7 @@
+"""AMQP Messaging Framework for Python"""
+VERSION = (0, 10, 2)
+__version__ = ".".join(map(str, VERSION))
+__author__ = "Ask Solem"
+__contact__ = "askh@opera.com"
+__homepage__ = "http://github.com/ask/carrot/"
+__docformat__ = "restructuredtext"
diff --git a/vendor/carrot/backends/__init__.py b/vendor/carrot/backends/__init__.py
new file mode 100644
index 0000000000..1f1ebfb6cb
--- /dev/null
+++ b/vendor/carrot/backends/__init__.py
@@ -0,0 +1,54 @@
+"""
+
+Working with Backends.
+
+"""
+import sys
+
+from carrot.utils import rpartition
+
+DEFAULT_BACKEND = "carrot.backends.pyamqplib.Backend"
+
+BACKEND_ALIASES = {
+ "amqp": "carrot.backends.pyamqplib.Backend",
+ "amqplib": "carrot.backends.pyamqplib.Backend",
+ "stomp": "carrot.backends.pystomp.Backend",
+ "stompy": "carrot.backends.pystomp.Backend",
+ "memory": "carrot.backends.queue.Backend",
+ "mem": "carrot.backends.queue.Backend",
+ "pika": "carrot.backends.pikachu.AsyncoreBackend",
+ "pikachu": "carrot.backends.pikachu.AsyncoreBackend",
+ "syncpika": "carrot.backends.pikachu.SyncBackend",
+}
+
+_backend_cache = {}
+
+
+def resolve_backend(backend=None):
+ backend = BACKEND_ALIASES.get(backend, backend)
+ backend_module_name, _, backend_cls_name = rpartition(backend, ".")
+ return backend_module_name, backend_cls_name
+
+
+def _get_backend_cls(backend=None):
+ backend_module_name, backend_cls_name = resolve_backend(backend)
+ __import__(backend_module_name)
+ backend_module = sys.modules[backend_module_name]
+ return getattr(backend_module, backend_cls_name)
+
+
+def get_backend_cls(backend=None):
+ """Get backend class by name.
+
+ The backend string is the full path to a backend class, e.g.::
+
+ "carrot.backends.pyamqplib.Backend"
+
+ If the name does not include "``.``" (is not fully qualified),
+ the alias table will be consulted.
+
+ """
+ backend = backend or DEFAULT_BACKEND
+ if backend not in _backend_cache:
+ _backend_cache[backend] = _get_backend_cls(backend)
+ return _backend_cache[backend]
diff --git a/vendor/carrot/backends/base.py b/vendor/carrot/backends/base.py
new file mode 100644
index 0000000000..7f3cc92fd1
--- /dev/null
+++ b/vendor/carrot/backends/base.py
@@ -0,0 +1,185 @@
+"""
+
+Backend base classes.
+
+"""
+from carrot import serialization
+
+ACKNOWLEDGED_STATES = frozenset(["ACK", "REJECTED", "REQUEUED"])
+
+
+class MessageStateError(Exception):
+ """The message has already been acknowledged."""
+
+
+class BaseMessage(object):
+ """Base class for received messages."""
+ _state = None
+
+ MessageStateError = MessageStateError
+
+ def __init__(self, backend, **kwargs):
+ self.backend = backend
+ self.body = kwargs.get("body")
+ self.delivery_tag = kwargs.get("delivery_tag")
+ self.content_type = kwargs.get("content_type")
+ self.content_encoding = kwargs.get("content_encoding")
+ self.delivery_info = kwargs.get("delivery_info", {})
+ self._decoded_cache = None
+ self._state = "RECEIVED"
+
+ def decode(self):
+ """Deserialize the message body, returning the original
+ python structure sent by the publisher."""
+ return serialization.decode(self.body, self.content_type,
+ self.content_encoding)
+
+ @property
+ def payload(self):
+ """The decoded message."""
+ if not self._decoded_cache:
+ self._decoded_cache = self.decode()
+ return self._decoded_cache
+
+ def ack(self):
+ """Acknowledge this message as being processed.,
+ This will remove the message from the queue.
+
+ :raises MessageStateError: If the message has already been
+ acknowledged/requeued/rejected.
+
+ """
+ if self.acknowledged:
+ raise self.MessageStateError(
+ "Message already acknowledged with state: %s" % self._state)
+ self.backend.ack(self.delivery_tag)
+ self._state = "ACK"
+
+ def reject(self):
+ """Reject this message.
+
+ The message will be discarded by the server.
+
+ :raises MessageStateError: If the message has already been
+ acknowledged/requeued/rejected.
+
+ """
+ if self.acknowledged:
+ raise self.MessageStateError(
+ "Message already acknowledged with state: %s" % self._state)
+ self.backend.reject(self.delivery_tag)
+ self._state = "REJECTED"
+
+ def requeue(self):
+ """Reject this message and put it back on the queue.
+
+ You must not use this method as a means of selecting messages
+ to process.
+
+ :raises MessageStateError: If the message has already been
+ acknowledged/requeued/rejected.
+
+ """
+ if self.acknowledged:
+ raise self.MessageStateError(
+ "Message already acknowledged with state: %s" % self._state)
+ self.backend.requeue(self.delivery_tag)
+ self._state = "REQUEUED"
+
+ @property
+ def acknowledged(self):
+ return self._state in ACKNOWLEDGED_STATES
+
+
+class BaseBackend(object):
+ """Base class for backends."""
+ default_port = None
+ extra_options = None
+
+ def __init__(self, connection, **kwargs):
+ self.connection = connection
+ self.extra_options = kwargs.get("extra_options")
+
+ def queue_declare(self, *args, **kwargs):
+ """Declare a queue by name."""
+ pass
+
+ def queue_delete(self, *args, **kwargs):
+ """Delete a queue by name."""
+ pass
+
+ def exchange_declare(self, *args, **kwargs):
+ """Declare an exchange by name."""
+ pass
+
+ def queue_bind(self, *args, **kwargs):
+ """Bind a queue to an exchange."""
+ pass
+
+ def get(self, *args, **kwargs):
+ """Pop a message off the queue."""
+ pass
+
+ def declare_consumer(self, *args, **kwargs):
+ pass
+
+ def consume(self, *args, **kwargs):
+ """Iterate over the declared consumers."""
+ pass
+
+ def cancel(self, *args, **kwargs):
+ """Cancel the consumer."""
+ pass
+
+ def ack(self, delivery_tag):
+ """Acknowledge the message."""
+ pass
+
+ def queue_purge(self, queue, **kwargs):
+ """Discard all messages in the queue. This will delete the messages
+ and results in an empty queue."""
+ return 0
+
+ def reject(self, delivery_tag):
+ """Reject the message."""
+ pass
+
+ def requeue(self, delivery_tag):
+ """Requeue the message."""
+ pass
+
+ def purge(self, queue, **kwargs):
+ """Discard all messages in the queue."""
+ pass
+
+ def message_to_python(self, raw_message):
+ """Convert received message body to a python datastructure."""
+ return raw_message
+
+ def prepare_message(self, message_data, delivery_mode, **kwargs):
+ """Prepare message for sending."""
+ return message_data
+
+ def publish(self, message, exchange, routing_key, **kwargs):
+ """Publish a message."""
+ pass
+
+ def close(self):
+ """Close the backend."""
+ pass
+
+ def establish_connection(self):
+ """Establish a connection to the backend."""
+ pass
+
+ def close_connection(self, connection):
+ """Close the connection."""
+ pass
+
+ def flow(self, active):
+ """Enable/disable flow from peer."""
+ pass
+
+ def qos(self, prefetch_size, prefetch_count, apply_global=False):
+ """Request specific Quality of Service."""
+ pass
diff --git a/vendor/carrot/backends/pikachu.py b/vendor/carrot/backends/pikachu.py
new file mode 100644
index 0000000000..d47e1c01a6
--- /dev/null
+++ b/vendor/carrot/backends/pikachu.py
@@ -0,0 +1,209 @@
+import asyncore
+import weakref
+import functools
+import itertools
+
+import pika
+
+from carrot.backends.base import BaseMessage, BaseBackend
+
+DEFAULT_PORT = 5672
+
+
+class Message(BaseMessage):
+
+ def __init__(self, backend, amqp_message, **kwargs):
+ channel, method, header, body = amqp_message
+ self._channel = channel
+ self._method = method
+ self._header = header
+ self.backend = backend
+
+ kwargs.update({"body": body,
+ "delivery_tag": method.delivery_tag,
+ "content_type": header.content_type,
+ "content_encoding": header.content_encoding,
+ "delivery_info": dict(
+ consumer_tag=method.consumer_tag,
+ routing_key=method.routing_key,
+ delivery_tag=method.delivery_tag,
+ exchange=method.exchange)})
+
+ super(Message, self).__init__(backend, **kwargs)
+
+
+class SyncBackend(BaseBackend):
+ default_port = DEFAULT_PORT
+ _connection_cls = pika.BlockingConnection
+
+ Message = Message
+
+ def __init__(self, connection, **kwargs):
+ self.connection = connection
+ self.default_port = kwargs.get("default_port", self.default_port)
+ self._channel_ref = None
+
+ @property
+ def _channel(self):
+ return callable(self._channel_ref) and self._channel_ref()
+
+ @property
+ def channel(self):
+ """If no channel exists, a new one is requested."""
+ if not self._channel:
+ self._channel_ref = weakref.ref(self.connection.get_channel())
+ return self._channel
+
+ def establish_connection(self):
+ """Establish connection to the AMQP broker."""
+ conninfo = self.connection
+ if not conninfo.port:
+ conninfo.port = self.default_port
+ credentials = pika.PlainCredentials(conninfo.userid,
+ conninfo.password)
+ return self._connection_cls(pika.ConnectionParameters(
+ conninfo.hostname,
+ port=conninfo.port,
+ virtual_host=conninfo.virtual_host,
+ credentials=credentials))
+
+ def close_connection(self, connection):
+ """Close the AMQP broker connection."""
+ connection.close()
+
+ def queue_exists(self, queue):
+ return False # FIXME
+
+ def queue_delete(self, queue, if_unused=False, if_empty=False):
+ """Delete queue by name."""
+ return self.channel.queue_delete(queue=queue, if_unused=if_unused,
+ if_empty=if_empty)
+
+ def queue_purge(self, queue, **kwargs):
+ """Discard all messages in the queue. This will delete the messages
+ and results in an empty queue."""
+ return self.channel.queue_purge(queue=queue)
+
+ def queue_declare(self, queue, durable, exclusive, auto_delete,
+ warn_if_exists=False):
+ """Declare a named queue."""
+
+ return self.channel.queue_declare(queue=queue,
+ durable=durable,
+ exclusive=exclusive,
+ auto_delete=auto_delete)
+
+ def exchange_declare(self, exchange, type, durable, auto_delete):
+ """Declare an named exchange."""
+ return self.channel.exchange_declare(exchange=exchange,
+ type=type,
+ durable=durable,
+ auto_delete=auto_delete)
+
+ def queue_bind(self, queue, exchange, routing_key, arguments=None):
+ """Bind queue to an exchange using a routing key."""
+ return self.channel.queue_bind(queue=queue,
+ exchange=exchange,
+ routing_key=routing_key,
+ arguments=arguments)
+
+ def message_to_python(self, raw_message):
+ """Convert encoded message body back to a Python value."""
+ return self.Message(backend=self, amqp_message=raw_message)
+
+ def get(self, queue, no_ack=False):
+ """Receive a message from a declared queue by name.
+
+ :returns: A :class:`Message` object if a message was received,
+ ``None`` otherwise. If ``None`` was returned, it probably means
+ there was no messages waiting on the queue.
+
+ """
+ raw_message = self.channel.basic_get(queue, no_ack=no_ack)
+ if not raw_message:
+ return None
+ return self.message_to_python(raw_message)
+
+ def declare_consumer(self, queue, no_ack, callback, consumer_tag,
+ nowait=False):
+ """Declare a consumer."""
+
+ @functools.wraps(callback)
+ def _callback_decode(channel, method, header, body):
+ return callback((channel, method, header, body))
+
+ return self.channel.basic_consume(_callback_decode,
+ queue=queue,
+ no_ack=no_ack,
+ consumer_tag=consumer_tag)
+
+ def consume(self, limit=None):
+ """Returns an iterator that waits for one message at a time."""
+ for total_message_count in itertools.count():
+ if limit and total_message_count >= limit:
+ raise StopIteration
+ self.connection.connection.drain_events()
+ yield True
+
+ def cancel(self, consumer_tag):
+ """Cancel a channel by consumer tag."""
+ if not self._channel:
+ return
+ self.channel.basic_cancel(consumer_tag)
+
+ def close(self):
+ """Close the channel if open."""
+ if self._channel and not self._channel.handler.channel_close:
+ self._channel.close()
+ self._channel_ref = None
+
+ def ack(self, delivery_tag):
+ """Acknowledge a message by delivery tag."""
+ return self.channel.basic_ack(delivery_tag)
+
+ def reject(self, delivery_tag):
+ """Reject a message by deliver tag."""
+ return self.channel.basic_reject(delivery_tag, requeue=False)
+
+ def requeue(self, delivery_tag):
+ """Reject and requeue a message by delivery tag."""
+ return self.channel.basic_reject(delivery_tag, requeue=True)
+
+ def prepare_message(self, message_data, delivery_mode, priority=None,
+ content_type=None, content_encoding=None):
+ """Encapsulate data into a AMQP message."""
+ properties = pika.BasicProperties(priority=priority,
+ content_type=content_type,
+ content_encoding=content_encoding,
+ delivery_mode=delivery_mode)
+ return message_data, properties
+
+ def publish(self, message, exchange, routing_key, mandatory=None,
+ immediate=None, headers=None):
+ """Publish a message to a named exchange."""
+ body, properties = message
+
+ if headers:
+ properties.headers = headers
+
+ ret = self.channel.basic_publish(body=body,
+ properties=properties,
+ exchange=exchange,
+ routing_key=routing_key,
+ mandatory=mandatory,
+ immediate=immediate)
+ if mandatory or immediate:
+ self.close()
+
+ def qos(self, prefetch_size, prefetch_count, apply_global=False):
+ """Request specific Quality of Service."""
+ self.channel.basic_qos(prefetch_size, prefetch_count,
+ apply_global)
+
+ def flow(self, active):
+ """Enable/disable flow from peer."""
+ self.channel.flow(active)
+
+
+class AsyncoreBackend(SyncBackend):
+ _connection_cls = pika.AsyncoreConnection
diff --git a/vendor/carrot/backends/pyamqplib.py b/vendor/carrot/backends/pyamqplib.py
new file mode 100644
index 0000000000..0e97f08e34
--- /dev/null
+++ b/vendor/carrot/backends/pyamqplib.py
@@ -0,0 +1,328 @@
+"""
+
+`amqplib`_ backend for carrot.
+
+.. _`amqplib`: http://barryp.org/software/py-amqplib/
+
+"""
+from amqplib import client_0_8 as amqp
+from amqplib.client_0_8.exceptions import AMQPChannelException
+from amqplib.client_0_8.serialization import AMQPReader, AMQPWriter
+from carrot.backends.base import BaseMessage, BaseBackend
+from itertools import count
+import warnings
+import weakref
+
+DEFAULT_PORT = 5672
+
+
+class Connection(amqp.Connection):
+
+ def drain_events(self, allowed_methods=None):
+ """Wait for an event on any channel."""
+ return self.wait_multi(self.channels.values())
+
+ def wait_multi(self, channels, allowed_methods=None):
+ """Wait for an event on a channel."""
+ chanmap = dict((chan.channel_id, chan) for chan in channels)
+ chanid, method_sig, args, content = self._wait_multiple(
+ chanmap.keys(), allowed_methods)
+
+ channel = chanmap[chanid]
+
+ if content \
+ and channel.auto_decode \
+ and hasattr(content, 'content_encoding'):
+ try:
+ content.body = content.body.decode(content.content_encoding)
+ except Exception:
+ pass
+
+ amqp_method = channel._METHOD_MAP.get(method_sig, None)
+
+ if amqp_method is None:
+ raise Exception('Unknown AMQP method (%d, %d)' % method_sig)
+
+ if content is None:
+ return amqp_method(channel, args)
+ else:
+ return amqp_method(channel, args, content)
+
+ def _wait_multiple(self, channel_ids, allowed_methods):
+ for channel_id in channel_ids:
+ method_queue = self.channels[channel_id].method_queue
+ for queued_method in method_queue:
+ method_sig = queued_method[0]
+ if (allowed_methods is None) \
+ or (method_sig in allowed_methods) \
+ or (method_sig == (20, 40)):
+ method_queue.remove(queued_method)
+ method_sig, args, content = queued_method
+ return channel_id, method_sig, args, content
+
+ # Nothing queued, need to wait for a method from the peer
+ while True:
+ channel, method_sig, args, content = \
+ self.method_reader.read_method()
+
+ if (channel in channel_ids) \
+ and ((allowed_methods is None) \
+ or (method_sig in allowed_methods) \
+ or (method_sig == (20, 40))):
+ return channel, method_sig, args, content
+
+ # Not the channel and/or method we were looking for. Queue
+ # this method for later
+ self.channels[channel].method_queue.append((method_sig,
+ args,
+ content))
+
+ #
+ # If we just queued up a method for channel 0 (the Connection
+ # itself) it's probably a close method in reaction to some
+ # error, so deal with it right away.
+ #
+ if channel == 0:
+ self.wait()
+
+
+class QueueAlreadyExistsWarning(UserWarning):
+ """A queue with that name already exists, so a recently changed
+ ``routing_key`` or other settings might be ignored unless you
+ rename the queue or restart the broker."""
+
+
+class Message(BaseMessage):
+ """A message received by the broker.
+
+ Usually you don't insantiate message objects yourself, but receive
+ them using a :class:`carrot.messaging.Consumer`.
+
+ :param backend: see :attr:`backend`.
+ :param amqp_message: see :attr:`_amqp_message`.
+
+
+ .. attribute:: body
+
+ The message body.
+
+ .. attribute:: delivery_tag
+
+ The message delivery tag, uniquely identifying this message.
+
+ .. attribute:: backend
+
+ The message backend used.
+ A subclass of :class:`carrot.backends.base.BaseBackend`.
+
+ .. attribute:: _amqp_message
+
+ A :class:`amqplib.client_0_8.basic_message.Message` instance.
+ This is a private attribute and should not be accessed by
+ production code.
+
+ """
+
+ def __init__(self, backend, amqp_message, **kwargs):
+ self._amqp_message = amqp_message
+ self.backend = backend
+
+ for attr_name in ("body",
+ "delivery_tag",
+ "content_type",
+ "content_encoding",
+ "delivery_info"):
+ kwargs[attr_name] = getattr(amqp_message, attr_name, None)
+
+ super(Message, self).__init__(backend, **kwargs)
+
+
+class Backend(BaseBackend):
+ """amqplib backend
+
+ :param connection: see :attr:`connection`.
+
+
+ .. attribute:: connection
+
+ A :class:`carrot.connection.BrokerConnection` instance. An established
+ connection to the broker.
+
+ """
+ default_port = DEFAULT_PORT
+
+ Message = Message
+
+ def __init__(self, connection, **kwargs):
+ self.connection = connection
+ self.default_port = kwargs.get("default_port", self.default_port)
+ self._channel_ref = None
+
+ @property
+ def _channel(self):
+ return callable(self._channel_ref) and self._channel_ref()
+
+ @property
+ def channel(self):
+ """If no channel exists, a new one is requested."""
+ if not self._channel:
+ self._channel_ref = weakref.ref(self.connection.get_channel())
+ return self._channel
+
+ def establish_connection(self):
+ """Establish connection to the AMQP broker."""
+ conninfo = self.connection
+ if not conninfo.port:
+ conninfo.port = self.default_port
+ return Connection(host=conninfo.host,
+ userid=conninfo.userid,
+ password=conninfo.password,
+ virtual_host=conninfo.virtual_host,
+ insist=conninfo.insist,
+ ssl=conninfo.ssl,
+ connect_timeout=conninfo.connect_timeout)
+
+ def close_connection(self, connection):
+ """Close the AMQP broker connection."""
+ connection.close()
+
+ def queue_exists(self, queue):
+ """Check if a queue has been declared.
+
+ :rtype bool:
+
+ """
+ try:
+ self.channel.queue_declare(queue=queue, passive=True)
+ except AMQPChannelException, e:
+ if e.amqp_reply_code == 404:
+ return False
+ raise e
+ else:
+ return True
+
+ def queue_delete(self, queue, if_unused=False, if_empty=False):
+ """Delete queue by name."""
+ return self.channel.queue_delete(queue, if_unused, if_empty)
+
+ def queue_purge(self, queue, **kwargs):
+ """Discard all messages in the queue. This will delete the messages
+ and results in an empty queue."""
+ return self.channel.queue_purge(queue=queue)
+
+ def queue_declare(self, queue, durable, exclusive, auto_delete,
+ warn_if_exists=False):
+ """Declare a named queue."""
+
+ if warn_if_exists and self.queue_exists(queue):
+ warnings.warn(QueueAlreadyExistsWarning(
+ QueueAlreadyExistsWarning.__doc__))
+
+ return self.channel.queue_declare(queue=queue,
+ durable=durable,
+ exclusive=exclusive,
+ auto_delete=auto_delete)
+
+ def exchange_declare(self, exchange, type, durable, auto_delete):
+ """Declare an named exchange."""
+ return self.channel.exchange_declare(exchange=exchange,
+ type=type,
+ durable=durable,
+ auto_delete=auto_delete)
+
+ def queue_bind(self, queue, exchange, routing_key, arguments=None):
+ """Bind queue to an exchange using a routing key."""
+ return self.channel.queue_bind(queue=queue,
+ exchange=exchange,
+ routing_key=routing_key,
+ arguments=arguments)
+
+ def message_to_python(self, raw_message):
+ """Convert encoded message body back to a Python value."""
+ return self.Message(backend=self, amqp_message=raw_message)
+
+ def get(self, queue, no_ack=False):
+ """Receive a message from a declared queue by name.
+
+ :returns: A :class:`Message` object if a message was received,
+ ``None`` otherwise. If ``None`` was returned, it probably means
+ there was no messages waiting on the queue.
+
+ """
+ raw_message = self.channel.basic_get(queue, no_ack=no_ack)
+ if not raw_message:
+ return None
+ return self.message_to_python(raw_message)
+
+ def declare_consumer(self, queue, no_ack, callback, consumer_tag,
+ nowait=False):
+ """Declare a consumer."""
+ return self.channel.basic_consume(queue=queue,
+ no_ack=no_ack,
+ callback=callback,
+ consumer_tag=consumer_tag,
+ nowait=nowait)
+
+ def consume(self, limit=None):
+ """Returns an iterator that waits for one message at a time."""
+ for total_message_count in count():
+ if limit and total_message_count >= limit:
+ raise StopIteration
+ self.channel.wait()
+ yield True
+
+ def cancel(self, consumer_tag):
+ """Cancel a channel by consumer tag."""
+ if not self.channel.connection:
+ return
+ self.channel.basic_cancel(consumer_tag)
+
+ def close(self):
+ """Close the channel if open."""
+ if self._channel and self._channel.is_open:
+ self._channel.close()
+ self._channel_ref = None
+
+ def ack(self, delivery_tag):
+ """Acknowledge a message by delivery tag."""
+ return self.channel.basic_ack(delivery_tag)
+
+ def reject(self, delivery_tag):
+ """Reject a message by deliver tag."""
+ return self.channel.basic_reject(delivery_tag, requeue=False)
+
+ def requeue(self, delivery_tag):
+ """Reject and requeue a message by delivery tag."""
+ return self.channel.basic_reject(delivery_tag, requeue=True)
+
+ def prepare_message(self, message_data, delivery_mode, priority=None,
+ content_type=None, content_encoding=None):
+ """Encapsulate data into a AMQP message."""
+ message = amqp.Message(message_data, priority=priority,
+ content_type=content_type,
+ content_encoding=content_encoding)
+ message.properties["delivery_mode"] = delivery_mode
+ return message
+
+ def publish(self, message, exchange, routing_key, mandatory=None,
+ immediate=None, headers=None):
+ """Publish a message to a named exchange."""
+
+ if headers:
+ message.properties["headers"] = headers
+
+ ret = self.channel.basic_publish(message, exchange=exchange,
+ routing_key=routing_key,
+ mandatory=mandatory,
+ immediate=immediate)
+ if mandatory or immediate:
+ self.close()
+
+ def qos(self, prefetch_size, prefetch_count, apply_global=False):
+ """Request specific Quality of Service."""
+ self.channel.basic_qos(prefetch_size, prefetch_count,
+ apply_global)
+
+ def flow(self, active):
+ """Enable/disable flow from peer."""
+ self.channel.flow(active)
diff --git a/vendor/carrot/backends/pystomp.py b/vendor/carrot/backends/pystomp.py
new file mode 100644
index 0000000000..b3156db84d
--- /dev/null
+++ b/vendor/carrot/backends/pystomp.py
@@ -0,0 +1,192 @@
+from stompy import Client
+from stompy import Empty as QueueEmpty
+from carrot.backends.base import BaseMessage, BaseBackend
+from itertools import count
+import socket
+
+DEFAULT_PORT = 61613
+
+
+class Message(BaseMessage):
+ """A message received by the STOMP broker.
+
+ Usually you don't insantiate message objects yourself, but receive
+ them using a :class:`carrot.messaging.Consumer`.
+
+ :param backend: see :attr:`backend`.
+ :param frame: see :attr:`_frame`.
+
+ .. attribute:: body
+
+ The message body.
+
+ .. attribute:: delivery_tag
+
+ The message delivery tag, uniquely identifying this message.
+
+ .. attribute:: backend
+
+ The message backend used.
+ A subclass of :class:`carrot.backends.base.BaseBackend`.
+
+ .. attribute:: _frame
+
+ The frame received by the STOMP client. This is considered a private
+ variable and should never be used in production code.
+
+ """
+
+ def __init__(self, backend, frame, **kwargs):
+ self._frame = frame
+ self.backend = backend
+
+ kwargs["body"] = frame.body
+ kwargs["delivery_tag"] = frame.headers["message-id"]
+ kwargs["content_type"] = frame.headers.get("content-type")
+ kwargs["content_encoding"] = frame.headers.get("content-encoding")
+ kwargs["priority"] = frame.headers.get("priority")
+
+ super(Message, self).__init__(backend, **kwargs)
+
+ def ack(self):
+ """Acknowledge this message as being processed.,
+ This will remove the message from the queue.
+
+ :raises MessageStateError: If the message has already been
+ acknowledged/requeued/rejected.
+
+ """
+ if self.acknowledged:
+ raise self.MessageStateError(
+ "Message already acknowledged with state: %s" % self._state)
+ self.backend.ack(self._frame)
+ self._state = "ACK"
+
+ def reject(self):
+ raise NotImplementedError(
+ "The STOMP backend does not implement basic.reject")
+
+ def requeue(self):
+ raise NotImplementedError(
+ "The STOMP backend does not implement requeue")
+
+
+class Backend(BaseBackend):
+ Stomp = Client
+ Message = Message
+ default_port = DEFAULT_PORT
+
+ def __init__(self, connection, **kwargs):
+ self.connection = connection
+ self.default_port = kwargs.get("default_port", self.default_port)
+ self._channel = None
+ self._consumers = {} # open consumers by consumer tag
+ self._callbacks = {}
+
+ def establish_connection(self):
+ conninfo = self.connection
+ if not conninfo.port:
+ conninfo.port = self.default_port
+ stomp = self.Stomp(conninfo.hostname, conninfo.port)
+ stomp.connect()
+ return stomp
+
+ def close_connection(self, connection):
+ try:
+ connection.disconnect()
+ except socket.error:
+ pass
+
+ def queue_exists(self, queue):
+ return True
+
+ def queue_purge(self, queue, **kwargs):
+ for purge_count in count(0):
+ try:
+ frame = self.channel.get_nowait()
+ except QueueEmpty:
+ return purge_count
+ else:
+ self.channel.ack(frame)
+
+ def declare_consumer(self, queue, no_ack, callback, consumer_tag,
+ **kwargs):
+ ack = no_ack and "auto" or "client"
+ self.channel.subscribe(queue, ack=ack)
+ self._consumers[consumer_tag] = queue
+ self._callbacks[queue] = callback
+
+ def consume(self, limit=None):
+ """Returns an iterator that waits for one message at a time."""
+ for total_message_count in count():
+ if limit and total_message_count >= limit:
+ raise StopIteration
+ while True:
+ frame = self.channel.get()
+ if frame:
+ break
+ queue = frame.headers.get("destination")
+
+ if not queue or queue not in self._callbacks:
+ continue
+
+ self._callbacks[queue](frame)
+
+ yield True
+
+ def queue_declare(self, queue, *args, **kwargs):
+ self.channel.subscribe(queue, ack="client")
+
+ def get(self, queue, no_ack=False):
+ try:
+ frame = self.channel.get_nowait()
+ except QueueEmpty:
+ return None
+ else:
+ return self.message_to_python(frame)
+
+ def ack(self, frame):
+ self.channel.ack(frame)
+
+ def message_to_python(self, raw_message):
+ """Convert encoded message body back to a Python value."""
+ return self.Message(backend=self, frame=raw_message)
+
+ def prepare_message(self, message_data, delivery_mode, priority=0,
+ content_type=None, content_encoding=None):
+ persistent = "false"
+ if delivery_mode == 2:
+ persistent = "true"
+ priority = priority or 0
+ return {"body": message_data,
+ "persistent": persistent,
+ "priority": priority,
+ "content-encoding": content_encoding,
+ "content-type": content_type}
+
+ def publish(self, message, exchange, routing_key, **kwargs):
+ message["destination"] = exchange
+ self.channel.stomp.send(message)
+
+ def cancel(self, consumer_tag):
+ if not self._channel or consumer_tag not in self._consumers:
+ return
+ queue = self._consumers.pop(consumer_tag)
+ self.channel.unsubscribe(queue)
+
+ def close(self):
+ for consumer_tag in self._consumers.keys():
+ self.cancel(consumer_tag)
+ if self._channel:
+ try:
+ self._channel.disconnect()
+ except socket.error:
+ pass
+
+ @property
+ def channel(self):
+ if not self._channel:
+ # Sorry, but the python-stomp library needs one connection
+ # for each channel.
+ self._channel = self.establish_connection()
+ return self._channel
diff --git a/vendor/carrot/backends/queue.py b/vendor/carrot/backends/queue.py
new file mode 100644
index 0000000000..4a6ad1799b
--- /dev/null
+++ b/vendor/carrot/backends/queue.py
@@ -0,0 +1,76 @@
+"""
+
+ Backend for unit-tests, using the Python :mod:`Queue` module.
+
+"""
+from Queue import Queue
+from carrot.backends.base import BaseMessage, BaseBackend
+import time
+import itertools
+
+mqueue = Queue()
+
+
+class Message(BaseMessage):
+ """Message received from the backend.
+
+ See :class:`carrot.backends.base.BaseMessage`.
+
+ """
+
+
+class Backend(BaseBackend):
+ """Backend using the Python :mod:`Queue` library. Usually only
+ used while executing unit tests.
+
+ Please not that this backend does not support queues, exchanges
+ or routing keys, so *all messages will be sent to all consumers*.
+
+ """
+
+ Message = Message
+
+ def get(self, *args, **kwargs):
+ """Get the next waiting message from the queue.
+
+ :returns: A :class:`Message` instance, or ``None`` if there is
+ no messages waiting.
+
+ """
+ if not mqueue.qsize():
+ return None
+ message_data, content_type, content_encoding = mqueue.get()
+ return self.Message(backend=self, body=message_data,
+ content_type=content_type,
+ content_encoding=content_encoding)
+
+ def declare_consumer(self, queue, no_ack, callback, consumer_tag,
+ nowait=False):
+ """Declare a consumer."""
+ self.callback = callback
+
+ def consume(self, limit=None):
+ """Go into consume mode."""
+ for total_message_count in itertools.count():
+ if limit and total_message_count >= limit:
+ raise StopIteration
+
+ message = self.get()
+ if message:
+ self.callback(message.decode(), message)
+ yield True
+ else:
+ time.sleep(0.1)
+
+ def purge(self, queue, **kwargs):
+ """Discard all messages in the queue."""
+ mqueue = Queue()
+
+ def prepare_message(self, message_data, delivery_mode,
+ content_type, content_encoding, **kwargs):
+ """Prepare message for sending."""
+ return (message_data, content_type, content_encoding)
+
+ def publish(self, message, exchange, routing_key, **kwargs):
+ """Publish a message to the queue."""
+ mqueue.put(message)
diff --git a/vendor/carrot/connection.py b/vendor/carrot/connection.py
new file mode 100644
index 0000000000..e392ec1d7f
--- /dev/null
+++ b/vendor/carrot/connection.py
@@ -0,0 +1,229 @@
+"""
+
+Getting a connection to the AMQP server.
+
+"""
+from amqplib.client_0_8.connection import AMQPConnectionException
+from carrot.backends import get_backend_cls
+import warnings
+import socket
+
+DEFAULT_CONNECT_TIMEOUT = 5 # seconds
+SETTING_PREFIX = "BROKER"
+COMPAT_SETTING_PREFIX = "AMQP"
+ARG_TO_DJANGO_SETTING = {
+ "hostname": "HOST",
+ "userid": "USER",
+ "password": "PASSWORD",
+ "virtual_host": "VHOST",
+ "port": "PORT",
+}
+SETTING_DEPRECATED_FMT = "Setting %s has been renamed to %s and is " \
+ "scheduled for removal in version 1.0."
+
+
+class BrokerConnection(object):
+ """A network/socket connection to an AMQP message broker.
+
+ :param hostname: see :attr:`hostname`.
+ :param userid: see :attr:`userid`.
+ :param password: see :attr:`password`.
+
+ :keyword virtual_host: see :attr:`virtual_host`.
+ :keyword port: see :attr:`port`.
+ :keyword insist: see :attr:`insist`.
+ :keyword connect_timeout: see :attr:`connect_timeout`.
+ :keyword ssl: see :attr:`ssl`.
+
+ .. attribute:: hostname
+
+ The hostname to the AMQP server
+
+ .. attribute:: userid
+
+ A valid username used to authenticate to the server.
+
+ .. attribute:: password
+
+ The password used to authenticate to the server.
+
+ .. attribute:: virtual_host
+
+ The name of the virtual host to work with. This virtual host must
+ exist on the server, and the user must have access to it. Consult
+ your brokers manual for help with creating, and mapping
+ users to virtual hosts.
+ Default is ``"/"``.
+
+ .. attribute:: port
+
+ The port of the AMQP server. Default is ``5672`` (amqp).
+
+ .. attribute:: insist
+
+ Insist on connecting to a server. In a configuration with multiple
+ load-sharing servers, the insist option tells the server that the
+ client is insisting on a connection to the specified server.
+ Default is ``False``.
+
+ .. attribute:: connect_timeout
+
+ The timeout in seconds before we give up connecting to the server.
+ The default is no timeout.
+
+ .. attribute:: ssl
+
+ Use SSL to connect to the server.
+ The default is ``False``.
+
+ .. attribute:: backend_cls
+
+ The messaging backend class used. Defaults to the ``pyamqplib``
+ backend.
+
+ """
+ virtual_host = "/"
+ port = None
+ insist = False
+ connect_timeout = DEFAULT_CONNECT_TIMEOUT
+ ssl = False
+ _closed = True
+ backend_cls = None
+
+ ConnectionException = AMQPConnectionException
+
+ @property
+ def host(self):
+ """The host as a hostname/port pair separated by colon."""
+ return ":".join([self.hostname, str(self.port)])
+
+ def __init__(self, hostname=None, userid=None, password=None,
+ virtual_host=None, port=None, **kwargs):
+ self.hostname = hostname
+ self.userid = userid
+ self.password = password
+ self.virtual_host = virtual_host or self.virtual_host
+ self.port = port or self.port
+ self.insist = kwargs.get("insist", self.insist)
+ self.connect_timeout = kwargs.get("connect_timeout",
+ self.connect_timeout)
+ self.ssl = kwargs.get("ssl", self.ssl)
+ self.backend_cls = kwargs.get("backend_cls", None)
+ self._closed = None
+ self._connection = None
+
+ @property
+ def connection(self):
+ if self._closed == True:
+ return
+ if not self._connection:
+ self._connection = self._establish_connection()
+ self._closed = False
+ return self._connection
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, e_type, e_value, e_trace):
+ if e_type:
+ raise e_type(e_value)
+ self.close()
+
+ def _establish_connection(self):
+ return self.create_backend().establish_connection()
+
+ def get_backend_cls(self):
+ """Get the currently used backend class."""
+ backend_cls = self.backend_cls
+ if not backend_cls or isinstance(backend_cls, basestring):
+ backend_cls = get_backend_cls(backend_cls)
+ return backend_cls
+
+ def create_backend(self):
+ """Create a new instance of the current backend in
+ :attr:`backend_cls`."""
+ backend_cls = self.get_backend_cls()
+ return backend_cls(connection=self)
+
+ def get_channel(self):
+ """Request a new AMQP channel."""
+ return self.connection.channel()
+
+ def connect(self):
+ """Establish a connection to the AMQP server."""
+ self._closed = False
+ return self.connection
+
+ def close(self):
+ """Close the currently open connection."""
+ try:
+ if self._connection:
+ backend = self.create_backend()
+ backend.close_connection(self._connection)
+ except socket.error:
+ pass
+ self._closed = True
+
+# For backwards compatability.
+AMQPConnection = BrokerConnection
+
+
+def get_django_conninfo():
+ # FIXME can't wait to remove this mess in 1.0 [askh]
+ ci = {}
+ from django.conf import settings as django_settings
+
+ ci["backend_cls"] = getattr(django_settings, "CARROT_BACKEND", None)
+
+ for arg_name, setting_name in ARG_TO_DJANGO_SETTING.items():
+ setting = "%s_%s" % (SETTING_PREFIX, setting_name)
+ compat_setting = "%s_%s" % (COMPAT_SETTING_PREFIX, setting_name)
+ if hasattr(django_settings, setting):
+ ci[arg_name] = getattr(django_settings, setting, None)
+ elif hasattr(django_settings, compat_setting):
+ ci[arg_name] = getattr(django_settings, compat_setting, None)
+ warnings.warn(DeprecationWarning(SETTING_DEPRECATED_FMT % (
+ compat_setting, setting)))
+
+ if "hostname" not in ci:
+ if hasattr(django_settings, "AMQP_SERVER"):
+ ci["hostname"] = django_settings.AMQP_SERVER
+ warnings.warn(DeprecationWarning(
+ "AMQP_SERVER has been renamed to BROKER_HOST and is"
+ "scheduled for removal in version 1.0."))
+
+ return ci
+
+
+class DjangoBrokerConnection(BrokerConnection):
+ """A version of :class:`BrokerConnection` that takes configuration
+ from the Django ``settings.py`` module.
+
+ :keyword hostname: The hostname of the AMQP server to connect to,
+ if not provided this is taken from ``settings.BROKER_HOST``.
+
+ :keyword userid: The username of the user to authenticate to the server
+ as. If not provided this is taken from ``settings.BROKER_USER``.
+
+ :keyword password: The users password. If not provided this is taken
+ from ``settings.BROKER_PASSWORD``.
+
+ :keyword virtual_host: The name of the virtual host to work with.
+ This virtual host must exist on the server, and the user must
+ have access to it. Consult your brokers manual for help with
+ creating, and mapping users to virtual hosts. If not provided
+ this is taken from ``settings.BROKER_VHOST``.
+
+ :keyword port: The port the AMQP server is running on. If not provided
+ this is taken from ``settings.BROKER_PORT``, or if that is not set,
+ the default is ``5672`` (amqp).
+
+ """
+
+
+ def __init__(self, *args, **kwargs):
+ kwargs = dict(get_django_conninfo(), **kwargs)
+ super(DjangoBrokerConnection, self).__init__(*args, **kwargs)
+
+# For backwards compatability.
+DjangoAMQPConnection = DjangoBrokerConnection
diff --git a/vendor/carrot/messaging.py b/vendor/carrot/messaging.py
new file mode 100644
index 0000000000..d82a85110c
--- /dev/null
+++ b/vendor/carrot/messaging.py
@@ -0,0 +1,981 @@
+"""
+
+Sending/Receiving Messages.
+
+"""
+from itertools import count
+from carrot.utils import gen_unique_id
+import warnings
+
+from carrot import serialization
+
+
+class Consumer(object):
+ """Message consumer.
+
+ :param connection: see :attr:`connection`.
+ :param queue: see :attr:`queue`.
+ :param exchange: see :attr:`exchange`.
+ :param routing_key: see :attr:`routing_key`.
+
+ :keyword durable: see :attr:`durable`.
+ :keyword auto_delete: see :attr:`auto_delete`.
+ :keyword exclusive: see :attr:`exclusive`.
+ :keyword exchange_type: see :attr:`exchange_type`.
+ :keyword auto_ack: see :attr:`auto_ack`.
+ :keyword no_ack: see :attr:`no_ack`.
+ :keyword auto_declare: see :attr:`auto_declare`.
+
+
+ .. attribute:: connection
+
+ The connection to the broker.
+ A :class:`carrot.connection.BrokerConnection` instance.
+
+ .. attribute:: queue
+
+ Name of the queue.
+
+ .. attribute:: exchange
+
+ Name of the exchange the queue binds to.
+
+ .. attribute:: routing_key
+
+ The routing key (if any). The interpretation of the routing key
+ depends on the value of the :attr:`exchange_type` attribute:
+
+ * direct exchange
+
+ Matches if the routing key property of the message and
+ the :attr:`routing_key` attribute are identical.
+
+ * fanout exchange
+
+ Always matches, even if the binding does not have a key.
+
+ * topic exchange
+
+ Matches the routing key property of the message by a primitive
+ pattern matching scheme. The message routing key then consists
+ of words separated by dots (``"."``, like domain names), and
+ two special characters are available; star (``"*"``) and hash
+ (``"#"``). The star matches any word, and the hash matches
+ zero or more words. For example ``"*.stock.#"`` matches the
+ routing keys ``"usd.stock"`` and ``"eur.stock.db"`` but not
+ ``"stock.nasdaq"``.
+
+ .. attribute:: durable
+
+ Durable exchanges remain active when a server restarts. Non-durable
+ exchanges (transient exchanges) are purged when a server restarts.
+ Default is ``True``.
+
+ .. attribute:: auto_delete
+
+ If set, the exchange is deleted when all queues have finished
+ using it. Default is ``False``.
+
+ .. attribute:: exclusive
+
+ Exclusive queues may only be consumed from by the current connection.
+ When :attr:`exclusive` is on, this also implies :attr:`auto_delete`.
+ Default is ``False``.
+
+ .. attribute:: exchange_type
+
+ AMQP defines four default exchange types (routing algorithms) that
+ covers most of the common messaging use cases. An AMQP broker can
+ also define additional exchange types, so see your message brokers
+ manual for more information about available exchange types.
+
+ * Direct
+
+ Direct match between the routing key in the message, and the
+ routing criteria used when a queue is bound to this exchange.
+
+ * Topic
+
+ Wildcard match between the routing key and the routing pattern
+ specified in the binding. The routing key is treated as zero
+ or more words delimited by ``"."`` and supports special
+ wildcard characters. ``"*"`` matches a single word and ``"#"``
+ matches zero or more words.
+
+ * Fanout
+
+ Queues are bound to this exchange with no arguments. Hence any
+ message sent to this exchange will be forwarded to all queues
+ bound to this exchange.
+
+ * Headers
+
+ Queues are bound to this exchange with a table of arguments
+ containing headers and values (optional). A special argument
+ named "x-match" determines the matching algorithm, where
+ ``"all"`` implies an ``AND`` (all pairs must match) and
+ ``"any"`` implies ``OR`` (at least one pair must match).
+
+ Use the :attr:`routing_key`` is used to specify the arguments,
+ the same when sending messages.
+
+ This description of AMQP exchange types was shamelessly stolen
+ from the blog post `AMQP in 10 minutes: Part 4`_ by
+ Rajith Attapattu. Recommended reading.
+
+ .. _`AMQP in 10 minutes: Part 4`:
+ http://bit.ly/amqp-exchange-types
+
+ .. attribute:: callbacks
+
+ List of registered callbacks to trigger when a message is received
+ by :meth:`wait`, :meth:`process_next` or :meth:`iterqueue`.
+
+ .. attribute:: warn_if_exists
+
+ Emit a warning if the queue has already been declared. If a queue
+ already exists, and you try to redeclare the queue with new settings,
+ the new settings will be silently ignored, so this can be
+ useful if you've recently changed the :attr:`routing_key` attribute
+ or other settings.
+
+ .. attribute:: auto_ack
+
+ Acknowledgement is handled automatically once messages are received.
+ This means that the :meth:`carrot.backends.base.BaseMessage.ack` and
+ :meth:`carrot.backends.base.BaseMessage.reject` methods
+ on the message object are no longer valid.
+ By default :attr:`auto_ack` is set to ``False``, and the receiver is
+ required to manually handle acknowledgment.
+
+ .. attribute:: no_ack
+
+ Disable acknowledgement on the server-side. This is different from
+ :attr:`auto_ack` in that acknowledgement is turned off altogether.
+ This functionality increases performance but at the cost of
+ reliability. Messages can get lost if a client dies before it can
+ deliver them to the application.
+
+ .. attribute auto_declare
+
+ If this is ``True`` the following will be automatically declared:
+
+ * The queue if :attr:`queue` is set.
+ * The exchange if :attr:`exchange` is set.
+ * The :attr:`queue` will be bound to the :attr:`exchange`.
+
+ This is the default behaviour.
+
+
+ :raises `amqplib.client_0_8.channel.AMQPChannelException`: if the queue is
+ exclusive and the queue already exists and is owned by another
+ connection.
+
+
+ Example Usage
+
+ >>> consumer = Consumer(connection=DjangoBrokerConnection(),
+ ... queue="foo", exchange="foo", routing_key="foo")
+ >>> def process_message(message_data, message):
+ ... print("Got message %s: %s" % (
+ ... message.delivery_tag, message_data))
+ >>> consumer.register_callback(process_message)
+ >>> consumer.wait() # Go into receive loop
+
+ """
+ queue = ""
+ exchange = ""
+ routing_key = ""
+ durable = True
+ exclusive = False
+ auto_delete = False
+ exchange_type = "direct"
+ channel_open = False
+ warn_if_exists = False
+ auto_declare = True
+ auto_ack = False
+ no_ack = False
+ _closed = True
+
+ def __init__(self, connection, queue=None, exchange=None,
+ routing_key=None, **kwargs):
+ self.connection = connection
+ self.backend = kwargs.get("backend", None)
+ if not self.backend:
+ self.backend = self.connection.create_backend()
+ self.queue = queue or self.queue
+
+ # Binding.
+ self.queue = queue or self.queue
+ self.exchange = exchange or self.exchange
+ self.routing_key = routing_key or self.routing_key
+ self.callbacks = []
+
+ # Options
+ self.durable = kwargs.get("durable", self.durable)
+ self.exclusive = kwargs.get("exclusive", self.exclusive)
+ self.auto_delete = kwargs.get("auto_delete", self.auto_delete)
+ self.exchange_type = kwargs.get("exchange_type", self.exchange_type)
+ self.warn_if_exists = kwargs.get("warn_if_exists",
+ self.warn_if_exists)
+ self.auto_ack = kwargs.get("auto_ack", self.auto_ack)
+ self.auto_declare = kwargs.get("auto_declare", self.auto_declare)
+
+ # exclusive implies auto-delete.
+ if self.exclusive:
+ self.auto_delete = True
+
+ self.consumer_tag = self._generate_consumer_tag()
+
+ if self.auto_declare:
+ self.declare()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, e_type, e_value, e_trace):
+ if e_type:
+ raise e_type(e_value)
+ self.close()
+
+ def __iter__(self):
+ """iter(Consumer) -> Consumer.iterqueue(infinite=True)"""
+ return self.iterqueue(infinite=True)
+
+ def _generate_consumer_tag(self):
+ """Generate a unique consumer tag.
+
+ :rtype string:
+
+ """
+ return "%s.%s-%s" % (
+ self.__class__.__module__,
+ self.__class__.__name__,
+ gen_unique_id())
+
+ def declare(self):
+ """Declares the queue, the exchange and binds the queue to
+ the exchange."""
+ arguments = None
+ routing_key = self.routing_key
+ if self.exchange_type == "headers":
+ arguments, routing_key = routing_key, ""
+
+ if self.queue:
+ self.backend.queue_declare(queue=self.queue, durable=self.durable,
+ exclusive=self.exclusive,
+ auto_delete=self.auto_delete,
+ warn_if_exists=self.warn_if_exists)
+ if self.exchange:
+ self.backend.exchange_declare(exchange=self.exchange,
+ type=self.exchange_type,
+ durable=self.durable,
+ auto_delete=self.auto_delete)
+ if self.queue:
+ self.backend.queue_bind(queue=self.queue,
+ exchange=self.exchange,
+ routing_key=routing_key,
+ arguments=arguments)
+ self._closed = False
+ return self
+
+ def _receive_callback(self, raw_message):
+ """Internal method used when a message is received in consume mode."""
+ message = self.backend.message_to_python(raw_message)
+
+ if self.auto_ack and not message.acknowledged:
+ message.ack()
+ self.receive(message.payload, message)
+
+ def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False):
+ """Receive the next message waiting on the queue.
+
+ :returns: A :class:`carrot.backends.base.BaseMessage` instance,
+ or ``None`` if there's no messages to be received.
+
+ :keyword enable_callbacks: Enable callbacks. The message will be
+ processed with all registered callbacks. Default is disabled.
+ :keyword auto_ack: Override the default :attr:`auto_ack` setting.
+ :keyword no_ack: Override the default :attr:`no_ack` setting.
+
+ """
+ no_ack = no_ack or self.no_ack
+ auto_ack = auto_ack or self.auto_ack
+ message = self.backend.get(self.queue, no_ack=no_ack)
+ if message:
+ if auto_ack and not message.acknowledged:
+ message.ack()
+ if enable_callbacks:
+ self.receive(message.payload, message)
+ return message
+
+ def process_next(self):
+ """**DEPRECATED** Use :meth:`fetch` like this instead:
+
+ >>> message = self.fetch(enable_callbacks=True)
+
+ """
+ warnings.warn(DeprecationWarning(
+ "Consumer.process_next has been deprecated in favor of \
+ Consumer.fetch(enable_callbacks=True)"))
+ return self.fetch(enable_callbacks=True)
+
+ def receive(self, message_data, message):
+ """This method is called when a new message is received by
+ running :meth:`wait`, :meth:`process_next` or :meth:`iterqueue`.
+
+ When a message is received, it passes the message on to the
+ callbacks listed in the :attr:`callbacks` attribute.
+ You can register callbacks using :meth:`register_callback`.
+
+ :param message_data: The deserialized message data.
+
+ :param message: The :class:`carrot.backends.base.BaseMessage` instance.
+
+ :raises NotImplementedError: If no callbacks has been registered.
+
+ """
+ if not self.callbacks:
+ raise NotImplementedError("No consumer callbacks registered")
+ for callback in self.callbacks:
+ callback(message_data, message)
+
+ def register_callback(self, callback):
+ """Register a callback function to be triggered by :meth:`receive`.
+
+ The ``callback`` function must take two arguments:
+
+ * message_data
+
+ The deserialized message data
+
+ * message
+
+ The :class:`carrot.backends.base.BaseMessage` instance.
+ """
+ self.callbacks.append(callback)
+
+ def discard_all(self, filterfunc=None):
+ """Discard all waiting messages.
+
+ :param filterfunc: A filter function to only discard the messages this
+ filter returns.
+
+ :returns: the number of messages discarded.
+
+ *WARNING*: All incoming messages will be ignored and not processed.
+
+ Example using filter:
+
+ >>> def waiting_feeds_only(message):
+ ... try:
+ ... message_data = message.decode()
+ ... except: # Should probably be more specific.
+ ... pass
+ ...
+ ... if message_data.get("type") == "feed":
+ ... return True
+ ... else:
+ ... return False
+ """
+ if not filterfunc:
+ return self.backend.queue_purge(self.queue)
+
+ if self.no_ack or self.auto_ack:
+ raise Exception("discard_all: Can't use filter with auto/no-ack.")
+
+ discarded_count = 0
+ while True:
+ message = self.fetch()
+ if message is None:
+ return discarded_count
+
+ if filterfunc(message):
+ message.ack()
+ discarded_count += 1
+
+ def iterconsume(self, limit=None, no_ack=None):
+ """Iterator processing new messages as they arrive.
+ Every new message will be passed to the callbacks, and the iterator
+ returns ``True``. The iterator is infinite unless the ``limit``
+ argument is specified or someone closes the consumer.
+
+ :meth:`iterconsume` uses transient requests for messages on the
+ server, while :meth:`iterequeue` uses synchronous access. In most
+ cases you want :meth:`iterconsume`, but if your environment does not
+ support this behaviour you can resort to using :meth:`iterqueue`
+ instead.
+
+ Also, :meth:`iterconsume` does not return the message
+ at each step, something which :meth:`iterqueue` does.
+
+ :keyword limit: Maximum number of messages to process.
+
+ :raises StopIteration: if limit is set and the message limit has been
+ reached.
+
+ """
+ no_ack = no_ack or self.no_ack
+ self.backend.declare_consumer(queue=self.queue, no_ack=no_ack,
+ callback=self._receive_callback,
+ consumer_tag=self.consumer_tag,
+ nowait=True)
+ self.channel_open = True
+ return self.backend.consume(limit=limit)
+
+ def wait(self, limit=None):
+ """Go into consume mode.
+
+ Mostly for testing purposes and simple programs, you probably
+ want :meth:`iterconsume` or :meth:`iterqueue` instead.
+
+ This runs an infinite loop, processing all incoming messages
+ using :meth:`receive` to apply the message to all registered
+ callbacks.
+
+ """
+ it = self.iterconsume(limit)
+ while True:
+ it.next()
+
+ def iterqueue(self, limit=None, infinite=False):
+ """Infinite iterator yielding pending messages, by using
+ synchronous direct access to the queue (``basic_get``).
+
+ :meth:`iterqueue` is used where synchronous functionality is more
+ important than performance. If you can, use :meth:`iterconsume`
+ instead.
+
+ :keyword limit: If set, the iterator stops when it has processed
+ this number of messages in total.
+
+ :keyword infinite: Don't raise :exc:`StopIteration` if there is no
+ messages waiting, but return ``None`` instead. If infinite you
+ obviously shouldn't consume the whole iterator at once without
+ using a ``limit``.
+
+ :raises StopIteration: If there is no messages waiting, and the
+ iterator is not infinite.
+
+ """
+ for items_since_start in count():
+ item = self.fetch()
+ if (not infinite and item is None) or \
+ (limit and items_since_start >= limit):
+ raise StopIteration
+ yield item
+
+ def cancel(self):
+ """Cancel a running :meth:`iterconsume` session."""
+ if self.channel_open:
+ try:
+ self.backend.cancel(self.consumer_tag)
+ except KeyError:
+ pass
+
+ def close(self):
+ """Close the channel to the queue."""
+ self.cancel()
+ self.backend.close()
+ self._closed = True
+
+ def flow(self, active):
+ """This method asks the peer to pause or restart the flow of
+ content data.
+
+ This is a simple flow-control mechanism that a
+ peer can use to avoid oveflowing its queues or otherwise
+ finding itself receiving more messages than it can process.
+ Note that this method is not intended for window control. The
+ peer that receives a request to stop sending content should
+ finish sending the current content, if any, and then wait
+ until it receives the ``flow(active=True)`` restart method.
+
+ """
+ self.backend.flow(active)
+
+ def qos(self, prefetch_size=0, prefetch_count=0, apply_global=False):
+ """Request specific Quality of Service.
+
+ This method requests a specific quality of service. The QoS
+ can be specified for the current channel or for all channels
+ on the connection. The particular properties and semantics of
+ a qos method always depend on the content class semantics.
+ Though the qos method could in principle apply to both peers,
+ it is currently meaningful only for the server.
+
+ :param prefetch_size: Prefetch window in octets.
+ The client can request that messages be sent in
+ advance so that when the client finishes processing a
+ message, the following message is already held
+ locally, rather than needing to be sent down the
+ channel. Prefetching gives a performance improvement.
+ This field specifies the prefetch window size in
+ octets. The server will send a message in advance if
+ it is equal to or smaller in size than the available
+ prefetch size (and also falls into other prefetch
+ limits). May be set to zero, meaning "no specific
+ limit", although other prefetch limits may still
+ apply. The ``prefetch_size`` is ignored if the
+ :attr:`no_ack` option is set.
+
+ :param prefetch_count: Specifies a prefetch window in terms of whole
+ messages. This field may be used in combination with
+ ``prefetch_size``; A message will only be sent
+ in advance if both prefetch windows (and those at the
+ channel and connection level) allow it. The prefetch-
+ count is ignored if the :attr:`no_ack` option is set.
+
+ :keyword apply_global: By default the QoS settings apply to the
+ current channel only. If this is set, they are applied
+ to the entire connection.
+
+ """
+ return self.backend.qos(prefetch_size, prefetch_count, apply_global)
+
+
+class Publisher(object):
+ """Message publisher.
+
+ :param connection: see :attr:`connection`.
+ :param exchange: see :attr:`exchange`.
+ :param routing_key: see :attr:`routing_key`.
+
+ :keyword exchange_type: see :attr:`Consumer.exchange_type`.
+ :keyword durable: see :attr:`Consumer.durable`.
+ :keyword auto_delete: see :attr:`Consumer.auto_delete`.
+ :keyword serializer: see :attr:`serializer`.
+ :keyword auto_declare: See :attr:`auto_declare`.
+
+
+ .. attribute:: connection
+
+ The connection to the broker.
+ A :class:`carrot.connection.BrokerConnection` instance.
+
+ .. attribute:: exchange
+
+ Name of the exchange we send messages to.
+
+ .. attribute:: routing_key
+
+ The default routing key for messages sent using this publisher.
+ See :attr:`Consumer.routing_key` for more information.
+ You can override the routing key by passing an explicit
+ ``routing_key`` argument to :meth:`send`.
+
+ .. attribute:: delivery_mode
+
+ The default delivery mode used for messages. The value is an integer.
+ The following delivery modes are supported by (at least) RabbitMQ:
+
+ * 1 or "non-persistent"
+
+ The message is non-persistent. Which means it is stored in
+ memory only, and is lost if the server dies or restarts.
+
+ * 2 or "persistent"
+ The message is persistent. Which means the message is
+ stored both in-memory, and on disk, and therefore
+ preserved if the server dies or restarts.
+
+ The default value is ``2`` (persistent).
+
+ .. attribute:: exchange_type
+
+ See :attr:`Consumer.exchange_type`.
+
+ .. attribute:: durable
+
+ See :attr:`Consumer.durable`.
+
+ .. attribute:: auto_delete
+
+ See :attr:`Consumer.auto_delete`.
+
+ .. attribute:: auto_declare
+
+ If this is ``True`` and the :attr:`exchange` name is set, the exchange
+ will be automatically declared at instantiation.
+ You can manually the declare the exchange by using the :meth:`declare`
+ method.
+
+ Auto declare is on by default.
+
+ .. attribute:: serializer
+
+ A string identifying the default serialization method to use.
+ Defaults to ``json``. Can be ``json`` (default), ``raw``,
+ ``pickle``, ``hessian``, ``yaml``, or any custom serialization
+ methods that have been registered with
+ :mod:`carrot.serialization.registry`.
+
+ """
+
+ NONE_PERSISTENT_DELIVERY_MODE = 1
+ PERSISTENT_DELIVERY_MODE = 2
+ DELIVERY_MODES = {
+ "non-persistent": NONE_PERSISTENT_DELIVERY_MODE,
+ "persistent": PERSISTENT_DELIVERY_MODE,
+ }
+
+ exchange = ""
+ routing_key = ""
+ delivery_mode = PERSISTENT_DELIVERY_MODE
+ _closed = True
+ exchange_type = "direct"
+ durable = True
+ auto_delete = False
+ auto_declare = True
+ serializer = None
+
+ def __init__(self, connection, exchange=None, routing_key=None, **kwargs):
+ self.connection = connection
+ self.backend = self.connection.create_backend()
+ self.exchange = exchange or self.exchange
+ self.routing_key = routing_key or self.routing_key
+ self.delivery_mode = kwargs.get("delivery_mode", self.delivery_mode)
+ self.delivery_mode = self.DELIVERY_MODES.get(self.delivery_mode,
+ self.delivery_mode)
+ self.exchange_type = kwargs.get("exchange_type", self.exchange_type)
+ self.durable = kwargs.get("durable", self.durable)
+ self.auto_delete = kwargs.get("auto_delete", self.auto_delete)
+ self.serializer = kwargs.get("serializer", self.serializer)
+ self.auto_declare = kwargs.get("auto_declare", self.auto_declare)
+ self._closed = False
+
+ if self.auto_declare and self.exchange:
+ self.declare()
+
+ def declare(self):
+ """Declare the exchange.
+
+ Creates the exchange on the broker.
+
+ """
+ self.backend.exchange_declare(exchange=self.exchange,
+ type=self.exchange_type,
+ durable=self.durable,
+ auto_delete=self.auto_delete)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, e_type, e_value, e_trace):
+ if e_type:
+ raise e_type(e_value)
+ self.close()
+
+ def create_message(self, message_data, delivery_mode=None, priority=None,
+ content_type=None, content_encoding=None,
+ serializer=None):
+ """With any data, serialize it and encapsulate it in a AMQP
+ message with the proper headers set."""
+
+ delivery_mode = delivery_mode or self.delivery_mode
+
+ # No content_type? Then we're serializing the data internally.
+ if not content_type:
+ serializer = serializer or self.serializer
+ (content_type, content_encoding,
+ message_data) = serialization.encode(message_data,
+ serializer=serializer)
+ else:
+ # If the programmer doesn't want us to serialize,
+ # make sure content_encoding is set.
+ if isinstance(message_data, unicode):
+ if not content_encoding:
+ content_encoding = 'utf-8'
+ message_data = message_data.encode(content_encoding)
+
+ # If they passed in a string, we can't know anything
+ # about it. So assume it's binary data.
+ elif not content_encoding:
+ content_encoding = 'binary'
+
+ return self.backend.prepare_message(message_data, delivery_mode,
+ priority=priority,
+ content_type=content_type,
+ content_encoding=content_encoding)
+
+ def send(self, message_data, routing_key=None, delivery_mode=None,
+ mandatory=False, immediate=False, priority=0, content_type=None,
+ content_encoding=None, serializer=None):
+ """Send a message.
+
+ :param message_data: The message data to send. Can be a list,
+ dictionary or a string.
+
+ :keyword routing_key: A custom routing key for the message.
+ If not set, the default routing key set in the :attr:`routing_key`
+ attribute is used.
+
+ :keyword mandatory: If set, the message has mandatory routing.
+ By default the message is silently dropped by the server if it
+ can't be routed to a queue. However - If the message is mandatory,
+ an exception will be raised instead.
+
+ :keyword immediate: Request immediate delivery.
+ If the message cannot be routed to a queue consumer immediately,
+ an exception will be raised. This is instead of the default
+ behaviour, where the server will accept and queue the message,
+ but with no guarantee that the message will ever be consumed.
+
+ :keyword delivery_mode: Override the default :attr:`delivery_mode`.
+
+ :keyword priority: The message priority, ``0`` to ``9``.
+
+ :keyword content_type: The messages content_type. If content_type
+ is set, no serialization occurs as it is assumed this is either
+ a binary object, or you've done your own serialization.
+ Leave blank if using built-in serialization as our library
+ properly sets content_type.
+
+ :keyword content_encoding: The character set in which this object
+ is encoded. Use "binary" if sending in raw binary objects.
+ Leave blank if using built-in serialization as our library
+ properly sets content_encoding.
+
+ :keyword serializer: Override the default :attr:`serializer`.
+
+ """
+ headers = None
+ routing_key = routing_key or self.routing_key
+
+ if self.exchange_type == "headers":
+ headers, routing_key = routing_key, ""
+
+
+ message = self.create_message(message_data, priority=priority,
+ delivery_mode=delivery_mode,
+ content_type=content_type,
+ content_encoding=content_encoding,
+ serializer=serializer)
+ self.backend.publish(message,
+ exchange=self.exchange, routing_key=routing_key,
+ mandatory=mandatory, immediate=immediate,
+ headers=headers)
+
+ def close(self):
+ """Close connection to queue."""
+ self.backend.close()
+ self._closed = True
+
+
+class Messaging(object):
+ """A combined message publisher and consumer."""
+ queue = ""
+ exchange = ""
+ routing_key = ""
+ publisher_cls = Publisher
+ consumer_cls = Consumer
+ _closed = True
+
+ def __init__(self, connection, **kwargs):
+ self.connection = connection
+ self.exchange = kwargs.get("exchange", self.exchange)
+ self.queue = kwargs.get("queue", self.queue)
+ self.routing_key = kwargs.get("routing_key", self.routing_key)
+ self.publisher = self.publisher_cls(connection,
+ exchange=self.exchange, routing_key=self.routing_key)
+ self.consumer = self.consumer_cls(connection, queue=self.queue,
+ exchange=self.exchange, routing_key=self.routing_key)
+ self.consumer.register_callback(self.receive)
+ self.callbacks = []
+ self._closed = False
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, e_type, e_value, e_trace):
+ if e_type:
+ raise e_type(e_value)
+ self.close()
+
+ def register_callback(self, callback):
+ """See :meth:`Consumer.register_callback`"""
+ self.callbacks.append(callback)
+
+ def receive(self, message_data, message):
+ """See :meth:`Consumer.receive`"""
+ if not self.callbacks:
+ raise NotImplementedError("No consumer callbacks registered")
+ for callback in self.callbacks:
+ callback(message_data, message)
+
+ def send(self, message_data, delivery_mode=None):
+ """See :meth:`Publisher.send`"""
+ self.publisher.send(message_data, delivery_mode=delivery_mode)
+
+ def fetch(self, **kwargs):
+ """See :meth:`Consumer.fetch`"""
+ return self.consumer.fetch(**kwargs)
+
+ def close(self):
+ """Close any open channels."""
+ self.consumer.close()
+ self.publisher.close()
+ self._closed = True
+
+
+class ConsumerSet(object):
+ """Receive messages from multiple consumers.
+
+ :param connection: see :attr:`connection`.
+ :param from_dict: see :attr:`from_dict`.
+ :param consumers: see :attr:`consumers`.
+ :param callbacks: see :attr:`callbacks`.
+
+ .. attribute:: connection
+
+ The connection to the broker.
+ A :class:`carrot.connection.BrokerConnection` instance.
+
+ .. attribute:: callbacks
+
+ A list of callbacks to be called when a message is received.
+ See :class:`Consumer.register_callback`.
+
+ .. attribute:: from_dict
+
+ Add consumers from a dictionary configuration::
+
+ {
+ "webshot": {
+ "exchange": "link_exchange",
+ "exchange_type": "topic",
+ "binding_key": "links.webshot",
+ "default_routing_key": "links.webshot",
+ },
+ "retrieve": {
+ "exchange": "link_exchange",
+ "exchange_type" = "topic",
+ "binding_key": "links.*",
+ "default_routing_key": "links.retrieve",
+ "auto_delete": True,
+ # ...
+ },
+ }
+
+ .. attribute:: consumers
+
+ Add consumers from a list of :class:`Consumer` instances.
+
+ .. attribute:: auto_ack
+
+ Default value for the :attr:`Consumer.auto_ack` attribute.
+
+ """
+ auto_ack = False
+
+ def __init__(self, connection, from_dict=None, consumers=None,
+ callbacks=None, **options):
+ self.connection = connection
+ self.options = options
+ self.from_dict = from_dict or {}
+ self.consumers = consumers or []
+ self.callbacks = callbacks or []
+ self._open_consumers = []
+
+ self.backend = self.connection.create_backend()
+
+ self.auto_ack = options.get("auto_ack", self.auto_ack)
+
+ [self.add_consumer_from_dict(queue_name, **queue_options)
+ for queue_name, queue_options in self.from_dict.items()]
+
+ def _receive_callback(self, raw_message):
+ """Internal method used when a message is received in consume mode."""
+ message = self.backend.message_to_python(raw_message)
+ if self.auto_ack and not message.acknowledged:
+ message.ack()
+ self.receive(message.decode(), message)
+
+ def add_consumer_from_dict(self, queue, **options):
+ """Add another consumer from dictionary configuration."""
+ consumer = Consumer(self.connection, queue=queue,
+ backend=self.backend, **options)
+ self.consumers.append(consumer)
+
+ def add_consumer(self, consumer):
+ """Add another consumer from a :class:`Consumer` instance."""
+ consumer.backend = self.backend
+ self.consumers.append(consumer)
+
+ def register_callback(self, callback):
+ """Register new callback to be called when a message is received.
+ See :meth:`Consumer.register_callback`"""
+ self.callbacks.append(callback)
+
+ def receive(self, message_data, message):
+ """What to do when a message is received.
+ See :meth:`Consumer.receive`."""
+ if not self.callbacks:
+ raise NotImplementedError("No consumer callbacks registered")
+ for callback in self.callbacks:
+ callback(message_data, message)
+
+ def _declare_consumer(self, consumer, nowait=False):
+ """Declare consumer so messages can be received from it using
+ :meth:`iterconsume`."""
+ # Use the ConsumerSet's consumer by default, but if the
+ # child consumer has a callback, honor it.
+ callback = consumer.callbacks and \
+ consumer._receive_callback or self._receive_callback
+ self.backend.declare_consumer(queue=consumer.queue,
+ no_ack=consumer.no_ack,
+ nowait=nowait,
+ callback=callback,
+ consumer_tag=consumer.consumer_tag)
+ self._open_consumers.append(consumer.consumer_tag)
+
+ def iterconsume(self, limit=None):
+ """Cycle between all consumers in consume mode.
+
+ See :meth:`Consumer.iterconsume`.
+ """
+ head = self.consumers[:-1]
+ tail = self.consumers[-1]
+ [self._declare_consumer(consumer, nowait=True)
+ for consumer in head]
+ self._declare_consumer(tail, nowait=False)
+
+ return self.backend.consume(limit=limit)
+
+ def discard_all(self):
+ """Discard all messages. Does not support filtering.
+ See :meth:`Consumer.discard_all`."""
+ return sum([consumer.discard_all()
+ for consumer in self.consumers])
+
+ def flow(self, active):
+ """This method asks the peer to pause or restart the flow of
+ content data.
+
+ See :meth:`Consumer.flow`.
+
+ """
+ self.backend.flow(active)
+
+ def qos(self, prefetch_size=0, prefetch_count=0, apply_global=False):
+ """Request specific Quality of Service.
+
+ See :meth:`Consumer.cos`.
+
+ """
+ self.backend.qos(prefetch_size, prefetch_count, apply_global)
+
+ def cancel(self):
+ """Cancel a running :meth:`iterconsume` session."""
+ for consumer_tag in self._open_consumers:
+ try:
+ self.backend.cancel(consumer_tag)
+ except KeyError:
+ pass
+ self._open_consumers = []
+
+ def close(self):
+ """Close all consumers."""
+ self.cancel()
+ for consumer in self.consumers:
+ consumer.close()
diff --git a/vendor/carrot/serialization.py b/vendor/carrot/serialization.py
new file mode 100644
index 0000000000..508836fe89
--- /dev/null
+++ b/vendor/carrot/serialization.py
@@ -0,0 +1,253 @@
+"""
+Centralized support for encoding/decoding of data structures.
+Requires a json library (`cjson`_, `simplejson`_, or `Python 2.6+`_).
+
+Optionally installs support for ``YAML`` if the necessary
+PyYAML is installed.
+
+.. _`cjson`: http://pypi.python.org/pypi/python-cjson/
+.. _`simplejson`: http://code.google.com/p/simplejson/
+.. _`Python 2.6+`: http://docs.python.org/library/json.html
+.. _`PyYAML`: http://pyyaml.org/
+
+"""
+
+import codecs
+
+__all__ = ['SerializerNotInstalled', 'registry']
+
+
+class SerializerNotInstalled(StandardError):
+ """Support for the requested serialization type is not installed"""
+
+
+class SerializerRegistry(object):
+ """The registry keeps track of serialization methods."""
+
+ def __init__(self):
+ self._encoders = {}
+ self._decoders = {}
+ self._default_encode = None
+ self._default_content_type = None
+ self._default_content_encoding = None
+
+ def register(self, name, encoder, decoder, content_type,
+ content_encoding='utf-8'):
+ """Register a new encoder/decoder.
+
+ :param name: A convenience name for the serialization method.
+
+ :param encoder: A method that will be passed a python data structure
+ and should return a string representing the serialized data.
+ If ``None``, then only a decoder will be registered. Encoding
+ will not be possible.
+
+ :param decoder: A method that will be passed a string representing
+ serialized data and should return a python data structure.
+ If ``None``, then only an encoder will be registered.
+ Decoding will not be possible.
+
+ :param content_type: The mime-type describing the serialized
+ structure.
+
+ :param content_encoding: The content encoding (character set) that
+ the :param:`decoder` method will be returning. Will usually be
+ ``utf-8``, ``us-ascii``, or ``binary``.
+
+ """
+ if encoder:
+ self._encoders[name] = (content_type, content_encoding, encoder)
+ if decoder:
+ self._decoders[content_type] = decoder
+
+ def _set_default_serializer(self, name):
+ """
+ Set the default serialization method used by this library.
+
+ :param name: The name of the registered serialization method.
+ For example, ``json`` (default), ``pickle``, ``yaml``,
+ or any custom methods registered using :meth:`register`.
+
+ :raises SerializerNotInstalled: If the serialization method
+ requested is not available.
+ """
+ try:
+ (self._default_content_type, self._default_content_encoding,
+ self._default_encode) = self._encoders[name]
+ except KeyError:
+ raise SerializerNotInstalled(
+ "No encoder installed for %s" % name)
+
+ def encode(self, data, serializer=None):
+ """
+ Serialize a data structure into a string suitable for sending
+ as an AMQP message body.
+
+ :param data: The message data to send. Can be a list,
+ dictionary or a string.
+
+ :keyword serializer: An optional string representing
+ the serialization method you want the data marshalled
+ into. (For example, ``json``, ``raw``, or ``pickle``).
+
+ If ``None`` (default), then `JSON`_ will be used, unless
+ ``data`` is a ``str`` or ``unicode`` object. In this
+ latter case, no serialization occurs as it would be
+ unnecessary.
+
+ Note that if ``serializer`` is specified, then that
+ serialization method will be used even if a ``str``
+ or ``unicode`` object is passed in.
+
+ :returns: A three-item tuple containing the content type
+ (e.g., ``application/json``), content encoding, (e.g.,
+ ``utf-8``) and a string containing the serialized
+ data.
+
+ :raises SerializerNotInstalled: If the serialization method
+ requested is not available.
+ """
+ if serializer == "raw":
+ return raw_encode(data)
+ if serializer and not self._encoders.get(serializer):
+ raise SerializerNotInstalled(
+ "No encoder installed for %s" % serializer)
+
+ # If a raw string was sent, assume binary encoding
+ # (it's likely either ASCII or a raw binary file, but 'binary'
+ # charset will encompass both, even if not ideal.
+ if not serializer and isinstance(data, str):
+ # In Python 3+, this would be "bytes"; allow binary data to be
+ # sent as a message without getting encoder errors
+ return "application/data", "binary", data
+
+ # For unicode objects, force it into a string
+ if not serializer and isinstance(data, unicode):
+ payload = data.encode("utf-8")
+ return "text/plain", "utf-8", payload
+
+ if serializer:
+ content_type, content_encoding, encoder = \
+ self._encoders[serializer]
+ else:
+ encoder = self._default_encode
+ content_type = self._default_content_type
+ content_encoding = self._default_content_encoding
+
+ payload = encoder(data)
+ return content_type, content_encoding, payload
+
+ def decode(self, data, content_type, content_encoding):
+ """Deserialize a data stream as serialized using ``encode``
+ based on :param:`content_type`.
+
+ :param data: The message data to deserialize.
+
+ :param content_type: The content-type of the data.
+ (e.g., ``application/json``).
+
+ :param content_encoding: The content-encoding of the data.
+ (e.g., ``utf-8``, ``binary``, or ``us-ascii``).
+
+ :returns: The unserialized data.
+ """
+ content_type = content_type or 'application/data'
+ content_encoding = (content_encoding or 'utf-8').lower()
+
+ # Don't decode 8-bit strings or unicode objects
+ if content_encoding not in ('binary', 'ascii-8bit') and \
+ not isinstance(data, unicode):
+ data = codecs.decode(data, content_encoding)
+
+ try:
+ decoder = self._decoders[content_type]
+ except KeyError:
+ return data
+
+ return decoder(data)
+
+
+"""
+.. data:: registry
+
+Global registry of serializers/deserializers.
+
+"""
+registry = SerializerRegistry()
+
+"""
+.. function:: encode(data, serializer=default_serializer)
+
+Encode data using the registry's default encoder.
+
+"""
+encode = registry.encode
+
+"""
+.. function:: decode(data, content_type, content_encoding):
+
+Decode data using the registry's default decoder.
+
+"""
+decode = registry.decode
+
+
+def raw_encode(data):
+ """Special case serializer."""
+ content_type = 'application/data'
+ payload = data
+ if isinstance(payload, unicode):
+ content_encoding = 'utf-8'
+ payload = payload.encode(content_encoding)
+ else:
+ content_encoding = 'binary'
+ return content_type, content_encoding, payload
+
+
+def register_json():
+ """Register a encoder/decoder for JSON serialization."""
+ from anyjson import serialize as json_serialize
+ from anyjson import deserialize as json_deserialize
+
+ registry.register('json', json_serialize, json_deserialize,
+ content_type='application/json',
+ content_encoding='utf-8')
+
+
+def register_yaml():
+ """Register a encoder/decoder for YAML serialization.
+
+ It is slower than JSON, but allows for more data types
+ to be serialized. Useful if you need to send data such as dates"""
+ try:
+ import yaml
+ registry.register('yaml', yaml.safe_dump, yaml.safe_load,
+ content_type='application/x-yaml',
+ content_encoding='utf-8')
+ except ImportError:
+
+ def not_available(*args, **kwargs):
+ """In case a client receives a yaml message, but yaml
+ isn't installed."""
+ raise SerializerNotInstalled(
+ "No decoder installed for YAML. Install the PyYAML library")
+ registry.register('yaml', None, not_available, 'application/x-yaml')
+
+
+def register_pickle():
+ """The fastest serialization method, but restricts
+ you to python clients."""
+ import cPickle
+ registry.register('pickle', cPickle.dumps, cPickle.loads,
+ content_type='application/x-python-serialize',
+ content_encoding='binary')
+
+
+# Register the base serialization methods.
+register_json()
+register_pickle()
+register_yaml()
+
+# JSON is assumed to always be available, so is the default.
+# (this matches the historical use of carrot.)
+registry._set_default_serializer('json')
diff --git a/vendor/carrot/utils.py b/vendor/carrot/utils.py
new file mode 100644
index 0000000000..686578bc72
--- /dev/null
+++ b/vendor/carrot/utils.py
@@ -0,0 +1,56 @@
+from uuid import UUID, uuid4, _uuid_generate_random
+try:
+ import ctypes
+except ImportError:
+ ctypes = None
+
+
+def gen_unique_id():
+ """Generate a unique id, having - hopefully - a very small chance of
+ collission.
+
+ For now this is provided by :func:`uuid.uuid4`.
+ """
+ # Workaround for http://bugs.python.org/issue4607
+ if ctypes and _uuid_generate_random:
+ buffer = ctypes.create_string_buffer(16)
+ _uuid_generate_random(buffer)
+ return str(UUID(bytes=buffer.raw))
+ return str(uuid4())
+
+
+def _compat_rl_partition(S, sep, direction=str.split):
+ items = direction(S, sep, 1)
+ if len(items) == 1:
+ return items[0], sep, ''
+ return items[0], sep, items[1]
+
+
+def _compat_partition(S, sep):
+ """``partition(S, sep) -> (head, sep, tail)``
+
+ Search for the separator ``sep`` in ``S``, and return the part before
+ it, the separator itself, and the part after it. If the separator is not
+ found, return ``S`` and two empty strings.
+
+ """
+ return _compat_rl_partition(S, sep, direction=str.split)
+
+
+def _compat_rpartition(S, sep):
+ """``rpartition(S, sep) -> (tail, sep, head)``
+
+ Search for the separator ``sep`` in ``S``, starting at the end of ``S``,
+ and return the part before it, the separator itself, and the part
+ after it. If the separator is not found, return two empty
+ strings and ``S``.
+
+ """
+ return _compat_rl_partition(S, sep, direction=str.rsplit)
+
+try:
+ partition = str.partition
+ rpartition = str.rpartition
+except AttributeError: # Python <= 2.4
+ partition = _compat_partition
+ rpartition = _compat_rpartition
diff --git a/vendor/lockfile/2.4.diff b/vendor/lockfile/2.4.diff
new file mode 100644
index 0000000000..72318d5085
--- /dev/null
+++ b/vendor/lockfile/2.4.diff
@@ -0,0 +1,99 @@
+Index: lockfile/sqlitelockfile.py
+===================================================================
+--- lockfile/sqlitelockfile.py (revision 93)
++++ lockfile/sqlitelockfile.py (working copy)
+@@ -1,9 +1,7 @@
+-from __future__ import absolute_import, division
+-
+ import time
+ import os
+
+-from . import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
++from lockfile import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
+
+ class SQLiteLockFile(LockBase):
+ "Demonstrate SQL-based locking."
+Index: lockfile/__init__.py
+===================================================================
+--- lockfile/__init__.py (revision 93)
++++ lockfile/__init__.py (working copy)
+@@ -24,16 +24,14 @@
+ >>> lock = LockFile('somefile')
+ >>> print lock.is_locked()
+ False
+->>> with lock:
+-... print lock.is_locked()
+-True
+->>> print lock.is_locked()
+-False
+
+ >>> lock = LockFile('somefile')
+ >>> # It is okay to lock twice from the same thread...
+->>> with lock:
+-... lock.acquire()
++>>> lock.acquire()
++>>> try:
++... lock.acquire()
++... finally:
++... lock.release()
+ ...
+ >>> # Though no counter is kept, so you can't unlock multiple times...
+ >>> print lock.is_locked()
+Index: lockfile/mkdirlockfile.py
+===================================================================
+--- lockfile/mkdirlockfile.py (revision 93)
++++ lockfile/mkdirlockfile.py (working copy)
+@@ -1,12 +1,10 @@
+-from __future__ import absolute_import, division
+-
+ import time
+ import os
+ import sys
+ import errno
+
+-from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
+- AlreadyLocked)
++from lockfile import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
++ AlreadyLocked)
+
+ class MkdirLockFile(LockBase):
+ """Lock file by creating a directory."""
+Index: lockfile/pidlockfile.py
+===================================================================
+--- lockfile/pidlockfile.py (revision 96)
++++ lockfile/pidlockfile.py (working copy)
+@@ -12,15 +12,13 @@
+ """ Lockfile behaviour implemented via Unix PID files.
+ """
+
+-from __future__ import absolute_import
+-
+ import os
+ import sys
+ import errno
+ import time
+
+-from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock,
+- LockTimeout)
++from lockfile import (LockBase, AlreadyLocked, LockFailed, NotLocked,
++ NotMyLock, LockTimeout)
+
+
+ class PIDLockFile(LockBase):
+Index: lockfile/linklockfile.py
+===================================================================
+--- lockfile/linklockfile.py (revision 93)
++++ lockfile/linklockfile.py (working copy)
+@@ -1,10 +1,8 @@
+-from __future__ import absolute_import
+-
+ import time
+ import os
+
+-from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
+- AlreadyLocked)
++from lockfile import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
++ AlreadyLocked)
+
+ class LinkLockFile(LockBase):
+ """Lock access to a file using atomic property of link(2).
diff --git a/vendor/lockfile/ACKS b/vendor/lockfile/ACKS
new file mode 100644
index 0000000000..44519d17f9
--- /dev/null
+++ b/vendor/lockfile/ACKS
@@ -0,0 +1,6 @@
+Thanks to the following people for help with lockfile.
+
+ Scott Dial
+ Ben Finney
+ Frank Niessink
+ Konstantin Veretennicov
diff --git a/vendor/lockfile/LICENSE b/vendor/lockfile/LICENSE
new file mode 100644
index 0000000000..610c0793f7
--- /dev/null
+++ b/vendor/lockfile/LICENSE
@@ -0,0 +1,21 @@
+This is the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+Copyright (c) 2007 Skip Montanaro.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/vendor/lockfile/MANIFEST b/vendor/lockfile/MANIFEST
new file mode 100644
index 0000000000..d302eef2fd
--- /dev/null
+++ b/vendor/lockfile/MANIFEST
@@ -0,0 +1,19 @@
+2.4.diff
+ACKS
+LICENSE
+MANIFEST
+README
+RELEASE-NOTES
+setup.py
+doc/Makefile
+doc/conf.py
+doc/glossary.rst
+doc/index.rst
+doc/lockfile.rst
+lockfile/__init__.py
+lockfile/linklockfile.py
+lockfile/mkdirlockfile.py
+lockfile/pidlockfile.py
+lockfile/sqlitelockfile.py
+test/compliancetest.py
+test/test_lockfile.py
diff --git a/vendor/lockfile/PKG-INFO b/vendor/lockfile/PKG-INFO
new file mode 100644
index 0000000000..0a4907b01b
--- /dev/null
+++ b/vendor/lockfile/PKG-INFO
@@ -0,0 +1,47 @@
+Metadata-Version: 1.0
+Name: lockfile
+Version: 0.9
+Summary: Platform-independent file locking module
+Home-page: http://smontanaro.dyndns.org/python/
+Author: Skip Montanaro
+Author-email: skip@pobox.com
+License: MIT License
+Download-URL: http://smontanaro.dyndns.org/python/lockfile-0.9.tar.gz
+Description: The lockfile package exports a LockFile class which provides a simple API for
+ locking files. Unlike the Windows msvcrt.locking function, the fcntl.lockf
+ and flock functions, and the deprecated posixfile module, the API is
+ identical across both Unix (including Linux and Mac) and Windows platforms.
+ The lock mechanism relies on the atomic nature of the link (on Unix) and
+ mkdir (on Windows) system calls. An implementation based on SQLite is also
+ provided, more as a demonstration of the possibilities it provides than as
+ production-quality code.
+
+ Note: In version 0.9 the API changed in two significant ways:
+
+ * It changed from a module defining several classes to a package containing
+ several modules, each defining a single class.
+
+ * Where classes had been named SomethingFileLock before the last two words
+ have been reversed, so that class is now SomethingLockFile.
+
+ The previous module-level definitions of LinkFileLock, MkdirFileLock and
+ SQLiteFileLock will be retained until the 1.0 release.
+
+ To install:
+
+ python setup.py install
+
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: MacOS
+Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.4
+Classifier: Programming Language :: Python :: 2.5
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.0
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/vendor/lockfile/README b/vendor/lockfile/README
new file mode 100644
index 0000000000..8ef7587e64
--- /dev/null
+++ b/vendor/lockfile/README
@@ -0,0 +1,23 @@
+The lockfile package exports a LockFile class which provides a simple API for
+locking files. Unlike the Windows msvcrt.locking function, the fcntl.lockf
+and flock functions, and the deprecated posixfile module, the API is
+identical across both Unix (including Linux and Mac) and Windows platforms.
+The lock mechanism relies on the atomic nature of the link (on Unix) and
+mkdir (on Windows) system calls. An implementation based on SQLite is also
+provided, more as a demonstration of the possibilities it provides than as
+production-quality code.
+
+Note: In version 0.9 the API changed in two significant ways:
+
+ * It changed from a module defining several classes to a package containing
+ several modules, each defining a single class.
+
+ * Where classes had been named SomethingFileLock before the last two words
+ have been reversed, so that class is now SomethingLockFile.
+
+The previous module-level definitions of LinkFileLock, MkdirFileLock and
+SQLiteFileLock will be retained until the 1.0 release.
+
+To install:
+
+ python setup.py install
diff --git a/vendor/lockfile/RELEASE-NOTES b/vendor/lockfile/RELEASE-NOTES
new file mode 100644
index 0000000000..7240c26358
--- /dev/null
+++ b/vendor/lockfile/RELEASE-NOTES
@@ -0,0 +1,42 @@
+Version 0.9
+===========
+
+* The lockfile module was reorganized into a package.
+
+* The names of the three main classes have changed as follows:
+
+ LinkFileLock -> LinkLockFile
+ MkdirFileLock -> MkdirLockFile
+ SQLiteFileLock -> SQLiteLockFile
+
+* A PIDLockFile class was added.
+
+Version 0.3
+===========
+
+* Fix 2.4.diff file error.
+
+* More documentation updates.
+
+Version 0.2
+===========
+
+* Added 2.4.diff file to patch lockfile to work with Python 2.4 (removes use
+ of with statement).
+
+* Renamed _FileLock base class to LockBase to expose it (and its docstrings)
+ to pydoc.
+
+* Got rid of time.sleep() calls in tests (thanks to Konstantin
+ Veretennicov).
+
+* Use thread.get_ident() as the thread discriminator.
+
+* Updated documentation a bit.
+
+* Added RELEASE-NOTES.
+
+Version 0.1
+===========
+
+* First release - All basic functionality there.
diff --git a/vendor/lockfile/doc/Makefile b/vendor/lockfile/doc/Makefile
new file mode 100644
index 0000000000..1b1e8d28e7
--- /dev/null
+++ b/vendor/lockfile/doc/Makefile
@@ -0,0 +1,73 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " pickle to make pickle files (usable by e.g. sphinx-web)"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " changes to make an overview over all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+
+clean:
+ -rm -rf .build/*
+
+html:
+ mkdir -p .build/html .build/doctrees
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
+ @echo
+ @echo "Build finished. The HTML pages are in .build/html."
+
+pickle:
+ mkdir -p .build/pickle .build/doctrees
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files or run"
+ @echo " sphinx-web .build/pickle"
+ @echo "to start the sphinx-web server."
+
+web: pickle
+
+htmlhelp:
+ mkdir -p .build/htmlhelp .build/doctrees
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in .build/htmlhelp."
+
+latex:
+ mkdir -p .build/latex .build/doctrees
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in .build/latex."
+ @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+ "run these through (pdf)latex."
+
+changes:
+ mkdir -p .build/changes .build/doctrees
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
+ @echo
+ @echo "The overview file is in .build/changes."
+
+linkcheck:
+ mkdir -p .build/linkcheck .build/doctrees
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in .build/linkcheck/output.txt."
+
+html.zip: html
+ (cd .build/html ; zip -r ../../$@ *)
diff --git a/vendor/lockfile/doc/conf.py b/vendor/lockfile/doc/conf.py
new file mode 100644
index 0000000000..64678e91d1
--- /dev/null
+++ b/vendor/lockfile/doc/conf.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+#
+# lockfile documentation build configuration file, created by
+# sphinx-quickstart on Sat Sep 13 17:54:17 2008.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# The contents of this file are pickled, so don't put values in the namespace
+# that aren't pickleable (module imports are okay, they're removed automatically).
+#
+# All configuration values have a default value; values that are commented out
+# serve to show the default value.
+
+import sys, os
+
+# If your extensions are in another directory, add it here. If the directory
+# is relative to the documentation root, use os.path.abspath to make it
+# absolute, like shown here.
+#sys.path.append(os.path.abspath('some/directory'))
+
+# General configuration
+# ---------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['.templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'lockfile'
+
+# General substitutions.
+project = 'lockfile'
+copyright = '2008, Skip Montanaro'
+
+# The default replacements for |version| and |release|, also used in various
+# other places throughout the built documents.
+#
+# The short X.Y version.
+version = '0.3'
+# The full version, including alpha/beta/rc tags.
+release = '0.3'
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directories, that shouldn't be searched
+# for source files.
+#exclude_dirs = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# Options for HTML output
+# -----------------------
+
+# The style sheet to use for HTML and HTML Help pages. A file of that name
+# must exist either in Sphinx' static/ path, or in one of the custom paths
+# given in html_static_path.
+html_style = 'default.css'
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (within the static path) to place at the top of
+# the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['.static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, the reST sources are included in the HTML build as _sources/<name>.
+#html_copy_source = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'lockfiledoc'
+
+
+# Options for LaTeX output
+# ------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, document class [howto/manual]).
+latex_documents = [
+ ('lockfile', 'lockfile.tex', 'lockfile Documentation',
+ 'Skip Montanaro', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
diff --git a/vendor/lockfile/doc/glossary.rst b/vendor/lockfile/doc/glossary.rst
new file mode 100644
index 0000000000..9401d488f4
--- /dev/null
+++ b/vendor/lockfile/doc/glossary.rst
@@ -0,0 +1,15 @@
+.. _glossary:
+
+********
+Glossary
+********
+
+.. if you add new entries, keep the alphabetical sorting!
+
+.. glossary::
+
+ context manager
+ An object which controls the environment seen in a :keyword:`with`
+ statement by defining :meth:`__enter__` and :meth:`__exit__` methods.
+ See :pep:`343`.
+
diff --git a/vendor/lockfile/doc/index.rst b/vendor/lockfile/doc/index.rst
new file mode 100644
index 0000000000..9718a68799
--- /dev/null
+++ b/vendor/lockfile/doc/index.rst
@@ -0,0 +1,22 @@
+.. lockfile documentation master file, created by sphinx-quickstart on Sat Sep 13 17:54:17 2008.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Welcome to lockfile's documentation!
+====================================
+
+Contents:
+
+.. toctree::
+ :maxdepth: 2
+
+ lockfile.rst
+ glossary.rst
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/vendor/lockfile/doc/lockfile.rst b/vendor/lockfile/doc/lockfile.rst
new file mode 100644
index 0000000000..4bc0a3e31c
--- /dev/null
+++ b/vendor/lockfile/doc/lockfile.rst
@@ -0,0 +1,257 @@
+
+:mod:`lockfile` --- Platform-independent file locking
+=====================================================
+
+.. module:: lockfile
+ :synopsis: Platform-independent file locking
+.. moduleauthor:: Skip Montanaro <skip@pobox.com>
+.. sectionauthor:: Skip Montanaro <skip@pobox.com>
+
+
+.. note::
+
+ This package is pre-release software. Between versions 0.8 and 0.9 it
+ was changed from a module to a package. It is quite possible that the
+ API and implementation will change again in important ways as people test
+ it and provide feedback and bug fixes. In particular, if the mkdir-based
+ locking scheme is sufficient for both Windows and Unix platforms, the
+ link-based scheme may be deleted so that only a single locking scheme is
+ used, providing cross-platform lockfile cooperation.
+
+.. note::
+
+ The implementation uses the :keyword:`with` statement, both in the
+ tests and in the main code, so will only work out-of-the-box with Python
+ 2.5 or later. However, the use of the :keyword:`with` statement is
+ minimal, so if you apply the patch in the included 2.4.diff file you can
+ use it with Python 2.4. It's possible that it will work in Python 2.3
+ with that patch applied as well, though the doctest code relies on APIs
+ new in 2.4, so will have to be rewritten somewhat to allow testing on
+ 2.3. As they say, patches welcome. ``;-)``
+
+The :mod:`lockfile` package exports a :class:`LockFile` class which provides
+a simple API for locking files. Unlike the Windows :func:`msvcrt.locking`
+function, the Unix :func:`fcntl.flock`, :func:`fcntl.lockf` and the
+deprecated :mod:`posixfile` module, the API is identical across both Unix
+(including Linux and Mac) and Windows platforms. The lock mechanism relies
+on the atomic nature of the :func:`link` (on Unix) and :func:`mkdir` (On
+Windows) system calls. It also contains several lock-method-specific
+modules: :mod:`lockfile.linklockfile`, :mod:`lockfile.mkdirlockfile`, and
+:mod:`lockfile.sqlitelockfile`, each one exporting a single class. For
+backwards compatibility with versions before 0.9 the :class:`LinkFileLock`,
+:class:`MkdirFileLock` and :class:`SQLiteFileLock` objects are exposed as
+attributes of the top-level lockfile package, though this use was deprecated
+starting with version 0.9 and will be removed in version 1.0.
+
+.. note::
+
+ The current implementation uses :func:`os.link` on Unix, but since that
+ function is unavailable on Windows it uses :func:`os.mkdir` there. At
+ this point it's not clear that using the :func:`os.mkdir` method would be
+ insufficient on Unix systems. If it proves to be adequate on Unix then
+ the implementation could be simplified and truly cross-platform locking
+ would be possible.
+
+.. note::
+
+ The current implementation doesn't provide for shared vs. exclusive
+ locks. It should be possible for multiple reader processes to hold the
+ lock at the same time.
+
+The module defines the following exceptions:
+
+.. exception:: Error
+
+ This is the base class for all exceptions raised by the :class:`LockFile`
+ class.
+
+.. exception:: LockError
+
+ This is the base class for all exceptions raised when attempting to lock
+ a file.
+
+.. exception:: UnlockError
+
+ This is the base class for all exceptions raised when attempting to
+ unlock a file.
+
+.. exception:: LockTimeout
+
+ This exception is raised if the :func:`LockFile.acquire` method is
+ called with a timeout which expires before an existing lock is released.
+
+.. exception:: AlreadyLocked
+
+ This exception is raised if the :func:`LockFile.acquire` detects a
+ file is already locked when in non-blocking mode.
+
+.. exception:: LockFailed
+
+ This exception is raised if the :func:`LockFile.acquire` detects some
+ other condition (such as a non-writable directory) which prevents it from
+ creating its lock file.
+
+.. exception:: NotLocked
+
+ This exception is raised if the file is not locked when
+ :func:`LockFile.release` is called.
+
+.. exception:: NotMyLock
+
+ This exception is raised if the file is locked by another thread or
+ process when :func:`LockFile.release` is called.
+
+The following classes are provided:
+
+.. class:: linklockfile.LinkLockFile(path, threaded=True)
+
+ This class uses the :func:`link(2)` system call as the basic lock
+ mechanism. *path* is an object in the file system to be locked. It need
+ not exist, but its directory must exist and be writable at the time the
+ :func:`acquire` and :func:`release` methods are called. *threaded* is
+ optional, but when set to :const:`True` locks will be distinguished
+ between threads in the same process.
+
+.. class:: mkdirlockfile.MkdirLockFile(path, threaded=True)
+
+ This class uses the :func:`mkdir(2)` system call as the basic lock
+ mechanism. The parameters have the same meaning as for the
+ :class:`LinkLockFile` class.
+
+.. class:: sqlitelockfile.SQLiteLockFile(path, threaded=True)
+
+ This class uses the :mod:`sqlite3` module to implement the lock
+ mechanism. The parameters have the same meaning as for the
+ :class:`LinkLockFile` class.
+
+.. class:: LockBase(path, threaded=True)
+
+ This is the base class for all concrete implementations and is available
+ at the lockfile package level so programmers can implement other locking
+ schemes.
+
+By default, the :const:`LockFile` object refers to the
+:class:`mkdirlockfile.MkdirLockFile` class on Windows. On all other
+platforms it refers to the :class:`linklockfile.LinkLockFile` class.
+
+When locking a file the :class:`linklockfile.LinkLockFile` class creates a
+uniquely named hard link to an empty lock file. That hard link contains the
+hostname, process id, and if locks between threads are distinguished, the
+thread identifier. For example, if you want to lock access to a file named
+"README", the lock file is named "README.lock". With per-thread locks
+enabled the hard link is named HOSTNAME-THREADID-PID. With only per-process
+locks enabled the hard link is named HOSTNAME--PID.
+
+When using the :class:`mkdirlockfile.MkdirLockFile` class the lock file is a
+directory. Referring to the example above, README.lock will be a directory
+and HOSTNAME-THREADID-PID will be an empty file within that directory.
+
+.. seealso::
+
+ Module :mod:`msvcrt`
+ Provides the :func:`locking` function, the standard Windows way of
+ locking (parts of) a file.
+
+ Module :mod:`posixfile`
+ The deprecated (since Python 1.5) way of locking files on Posix systems.
+
+ Module :mod:`fcntl`
+ Provides the current best way to lock files on Unix systems
+ (:func:`lockf` and :func:`flock`).
+
+LockFile Objects
+----------------
+
+:class:`LockFile` objects support the :term:`context manager` protocol used
+by the statement:`with` statement. The timeout option is not supported when
+used in this fashion. While support for timeouts could be implemented,
+there is no support for handling the eventual :exc:`Timeout` exceptions
+raised by the :func:`__enter__` method, so you would have to protect the
+:keyword:`with` statement with a :keyword:`try` statement. The resulting
+construct would not be any simpler than just using a :keyword:`try`
+statement in the first place.
+
+:class:`LockFile` has the following user-visible methods:
+
+.. method:: LockFile.acquire(timeout=None)
+
+ Lock the file associated with the :class:`LockFile` object. If the
+ *timeout* is omitted or :const:`None` the caller will block until the
+ file is unlocked by the object currently holding the lock. If the
+ *timeout* is zero or a negative number the :exc:`AlreadyLocked` exception
+ will be raised if the file is currently locked by another process or
+ thread. If the *timeout* is positive, the caller will block for that
+ many seconds waiting for the lock to be released. If the lock is not
+ released within that period the :exc:`LockTimeout` exception will be
+ raised.
+
+.. method:: LockFile.release()
+
+ Unlock the file associated with the :class:`LockFile` object. If the
+ file is not currently locked, the :exc:`NotLocked` exception is raised.
+ If the file is locked by another thread or process the :exc:`NotMyLock`
+ exception is raised.
+
+.. method:: is_locked()
+
+ Return the status of the lock on the current file. If any process or
+ thread (including the current one) is locking the file, :const:`True` is
+ returned, otherwise :const:`False` is returned.
+
+.. method:: break_lock()
+
+ If the file is currently locked, break it.
+
+.. method:: i_am_locking()
+
+ Returns true if the caller holds the lock.
+
+Examples
+--------
+
+This example is the "hello world" for the :mod:`lockfile` package::
+
+ from lockfile import LockFile
+ lock = LockFile("/some/file/or/other")
+ with lock:
+ print lock.path, 'is locked.'
+
+To use this with Python 2.4, you can execute::
+
+ frm lockfile import LockFile
+ lock = LockFile("/some/file/or/other")
+ lock.acquire()
+ print lock.path, 'is locked.'
+ lock.release()
+
+If you don't want to wait forever, you might try::
+
+ from lockfile import LockFile
+ lock = LockFile("/some/file/or/other")
+ while not lock.i_am_locking():
+ try:
+ lock.acquire(timeout=60) # wait up to 60 seconds
+ except LockTimeout:
+ lock.break_lock()
+ lock.acquire()
+ print "I locked", lock.path
+ lock.release()
+
+Other Libraries
+---------------
+
+The idea of implementing advisory locking with a standard API is not new
+with :mod:`lockfile`. There are a number of other libraries available:
+
+* locknix - http://pypi.python.org/pypi/locknix - Unix only
+* mx.MiscLockFile - from Marc André Lemburg, part of the mx.Base
+ distribution - cross-platform.
+* Twisted - http://twistedmatrix.com/trac/browser/trunk/twisted/python/lockfile.py
+* zc.lockfile - http://pypi.python.org/pypi/zc.lockfile
+
+
+Contacting the Author
+---------------------
+
+If you encounter any problems with ``lockfile``, would like help or want to
+submit a patch, contact me directly: Skip Montanaro (skip@pobox.com).
diff --git a/vendor/lockfile/lockfile/__init__.py b/vendor/lockfile/lockfile/__init__.py
new file mode 100644
index 0000000000..33356ad8e0
--- /dev/null
+++ b/vendor/lockfile/lockfile/__init__.py
@@ -0,0 +1,286 @@
+
+"""
+lockfile.py - Platform-independent advisory file locks.
+
+Requires Python 2.5 unless you apply 2.4.diff
+Locking is done on a per-thread basis instead of a per-process basis.
+
+Usage:
+
+>>> lock = LockFile('somefile')
+>>> try:
+... lock.acquire()
+... except AlreadyLocked:
+... print 'somefile', 'is locked already.'
+... except LockFailed:
+... print 'somefile', 'can\\'t be locked.'
+... else:
+... print 'got lock'
+got lock
+>>> print lock.is_locked()
+True
+>>> lock.release()
+
+>>> lock = LockFile('somefile')
+>>> print lock.is_locked()
+False
+>>> with lock:
+... print lock.is_locked()
+True
+>>> print lock.is_locked()
+False
+
+>>> lock = LockFile('somefile')
+>>> # It is okay to lock twice from the same thread...
+>>> with lock:
+... lock.acquire()
+...
+>>> # Though no counter is kept, so you can't unlock multiple times...
+>>> print lock.is_locked()
+False
+
+Exceptions:
+
+ Error - base class for other exceptions
+ LockError - base class for all locking exceptions
+ AlreadyLocked - Another thread or process already holds the lock
+ LockFailed - Lock failed for some other reason
+ UnlockError - base class for all unlocking exceptions
+ AlreadyUnlocked - File was not locked.
+ NotMyLock - File was locked but not by the current thread/process
+"""
+
+import sys
+import socket
+import os
+import threading
+import time
+import urllib
+import warnings
+
+# Work with PEP8 and non-PEP8 versions of threading module.
+if not hasattr(threading, "current_thread"):
+ threading.current_thread = threading.currentThread
+if not hasattr(threading.Thread, "get_name"):
+ threading.Thread.get_name = threading.Thread.getName
+
+__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
+ 'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
+ 'LinkLockFile', 'MkdirLockFile', 'SQLiteLockFile',
+ 'LockBase']
+
+class Error(Exception):
+ """
+ Base class for other exceptions.
+
+ >>> try:
+ ... raise Error
+ ... except Exception:
+ ... pass
+ """
+ pass
+
+class LockError(Error):
+ """
+ Base class for error arising from attempts to acquire the lock.
+
+ >>> try:
+ ... raise LockError
+ ... except Error:
+ ... pass
+ """
+ pass
+
+class LockTimeout(LockError):
+ """Raised when lock creation fails within a user-defined period of time.
+
+ >>> try:
+ ... raise LockTimeout
+ ... except LockError:
+ ... pass
+ """
+ pass
+
+class AlreadyLocked(LockError):
+ """Some other thread/process is locking the file.
+
+ >>> try:
+ ... raise AlreadyLocked
+ ... except LockError:
+ ... pass
+ """
+ pass
+
+class LockFailed(LockError):
+ """Lock file creation failed for some other reason.
+
+ >>> try:
+ ... raise LockFailed
+ ... except LockError:
+ ... pass
+ """
+ pass
+
+class UnlockError(Error):
+ """
+ Base class for errors arising from attempts to release the lock.
+
+ >>> try:
+ ... raise UnlockError
+ ... except Error:
+ ... pass
+ """
+ pass
+
+class NotLocked(UnlockError):
+ """Raised when an attempt is made to unlock an unlocked file.
+
+ >>> try:
+ ... raise NotLocked
+ ... except UnlockError:
+ ... pass
+ """
+ pass
+
+class NotMyLock(UnlockError):
+ """Raised when an attempt is made to unlock a file someone else locked.
+
+ >>> try:
+ ... raise NotMyLock
+ ... except UnlockError:
+ ... pass
+ """
+ pass
+
+class LockBase:
+ """Base class for platform-specific lock classes."""
+ def __init__(self, path, threaded=True):
+ """
+ >>> lock = LockBase('somefile')
+ >>> lock = LockBase('somefile', threaded=False)
+ """
+ self.path = path
+ self.lock_file = os.path.abspath(path) + ".lock"
+ self.hostname = socket.gethostname()
+ self.pid = os.getpid()
+ if threaded:
+ t = threading.current_thread()
+ # Thread objects in Python 2.4 and earlier do not have ident
+ # attrs. Worm around that.
+ ident = getattr(t, "ident", hash(t))
+ self.tname = "%x-" % (ident & 0xffffffff)
+ else:
+ self.tname = ""
+ dirname = os.path.dirname(self.lock_file)
+ self.unique_name = os.path.join(dirname,
+ "%s.%s%s" % (self.hostname,
+ self.tname,
+ self.pid))
+
+ def acquire(self, timeout=None):
+ """
+ Acquire the lock.
+
+ * If timeout is omitted (or None), wait forever trying to lock the
+ file.
+
+ * If timeout > 0, try to acquire the lock for that many seconds. If
+ the lock period expires and the file is still locked, raise
+ LockTimeout.
+
+ * If timeout <= 0, raise AlreadyLocked immediately if the file is
+ already locked.
+ """
+ raise NotImplemented("implement in subclass")
+
+ def release(self):
+ """
+ Release the lock.
+
+ If the file is not locked, raise NotLocked.
+ """
+ raise NotImplemented("implement in subclass")
+
+ def is_locked(self):
+ """
+ Tell whether or not the file is locked.
+ """
+ raise NotImplemented("implement in subclass")
+
+ def i_am_locking(self):
+ """
+ Return True if this object is locking the file.
+ """
+ raise NotImplemented("implement in subclass")
+
+ def break_lock(self):
+ """
+ Remove a lock. Useful if a locking thread failed to unlock.
+ """
+ raise NotImplemented("implement in subclass")
+
+ def __enter__(self):
+ """
+ Context manager support.
+ """
+ self.acquire()
+ return self
+
+ def __exit__(self, *_exc):
+ """
+ Context manager support.
+ """
+ self.release()
+
+def _fl_helper(cls, mod, *args, **kwds):
+ warnings.warn("Import from %s module instead of lockfile package" % mod,
+ DeprecationWarning, stacklevel=2)
+ # This is a bit funky, but it's only for awhile. The way the unit tests
+ # are constructed this function winds up as an unbound method, so it
+ # actually takes three args, not two. We want to toss out self.
+ if not isinstance(args[0], str):
+ # We are testing, avoid the first arg
+ args = args[1:]
+ if len(args) == 1 and not kwds:
+ kwds["threaded"] = True
+ return cls(*args, **kwds)
+
+def LinkFileLock(*args, **kwds):
+ """Factory function provided for backwards compatibility.
+
+ Do not use in new code. Instead, import LinkLockFile from the
+ lockfile.linklockfile module.
+ """
+ import linklockfile
+ return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile",
+ *args, **kwds)
+
+def MkdirFileLock(*args, **kwds):
+ """Factory function provided for backwards compatibility.
+
+ Do not use in new code. Instead, import MkdirLockFile from the
+ lockfile.mkdirlockfile module.
+ """
+ import mkdirlockfile
+ return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile",
+ *args, **kwds)
+
+def SQLiteFileLock(*args, **kwds):
+ """Factory function provided for backwards compatibility.
+
+ Do not use in new code. Instead, import SQLiteLockFile from the
+ lockfile.mkdirlockfile module.
+ """
+ import sqlitelockfile
+ return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile",
+ *args, **kwds)
+
+if hasattr(os, "link"):
+ import linklockfile as _llf
+ LockFile = _llf.LinkLockFile
+else:
+ import mkdirlockfile as _mlf
+ LockFile = _mlf.MkdirLockFile
+
+FileLock = LockFile
+
diff --git a/vendor/lockfile/lockfile/linklockfile.py b/vendor/lockfile/lockfile/linklockfile.py
new file mode 100644
index 0000000000..f8aeaefcfc
--- /dev/null
+++ b/vendor/lockfile/lockfile/linklockfile.py
@@ -0,0 +1,71 @@
+from __future__ import absolute_import
+
+import time
+import os
+
+from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
+ AlreadyLocked)
+
+class LinkLockFile(LockBase):
+ """Lock access to a file using atomic property of link(2).
+
+ >>> lock = LinkLockFile('somefile')
+ >>> lock = LinkLockFile('somefile', threaded=False)
+ """
+
+ def acquire(self, timeout=None):
+ try:
+ open(self.unique_name, "wb").close()
+ except IOError:
+ raise LockFailed("failed to create %s" % self.unique_name)
+
+ end_time = time.time()
+ if timeout is not None and timeout > 0:
+ end_time += timeout
+
+ while True:
+ # Try and create a hard link to it.
+ try:
+ print 'making a hard link %s to %s' % (self.unique_name,
+ self.lock_file)
+ os.link(self.unique_name, self.lock_file)
+ except OSError:
+ # Link creation failed. Maybe we've double-locked?
+ nlinks = os.stat(self.unique_name).st_nlink
+ if nlinks == 2:
+ # The original link plus the one I created == 2. We're
+ # good to go.
+ return
+ else:
+ # Otherwise the lock creation failed.
+ if timeout is not None and time.time() > end_time:
+ os.unlink(self.unique_name)
+ if timeout > 0:
+ raise LockTimeout
+ else:
+ raise AlreadyLocked
+ time.sleep(timeout is not None and timeout/10 or 0.1)
+ else:
+ # Link creation succeeded. We're good to go.
+ return
+
+ def release(self):
+ if not self.is_locked():
+ raise NotLocked
+ elif not os.path.exists(self.unique_name):
+ raise NotMyLock
+ os.unlink(self.unique_name)
+ os.unlink(self.lock_file)
+
+ def is_locked(self):
+ return os.path.exists(self.lock_file)
+
+ def i_am_locking(self):
+ return (self.is_locked() and
+ os.path.exists(self.unique_name) and
+ os.stat(self.unique_name).st_nlink == 2)
+
+ def break_lock(self):
+ if os.path.exists(self.lock_file):
+ os.unlink(self.lock_file)
+
diff --git a/vendor/lockfile/lockfile/mkdirlockfile.py b/vendor/lockfile/lockfile/mkdirlockfile.py
new file mode 100644
index 0000000000..fb78902d51
--- /dev/null
+++ b/vendor/lockfile/lockfile/mkdirlockfile.py
@@ -0,0 +1,79 @@
+from __future__ import absolute_import, division
+
+import time
+import os
+import sys
+import errno
+
+from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
+ AlreadyLocked)
+
+class MkdirLockFile(LockBase):
+ """Lock file by creating a directory."""
+ def __init__(self, path, threaded=True):
+ """
+ >>> lock = MkdirLockFile('somefile')
+ >>> lock = MkdirLockFile('somefile', threaded=False)
+ """
+ LockBase.__init__(self, path, threaded)
+ # Lock file itself is a directory. Place the unique file name into
+ # it.
+ self.unique_name = os.path.join(self.lock_file,
+ "%s.%s%s" % (self.hostname,
+ self.tname,
+ self.pid))
+
+ def acquire(self, timeout=None):
+ end_time = time.time()
+ if timeout is not None and timeout > 0:
+ end_time += timeout
+
+ if timeout is None:
+ wait = 0.1
+ else:
+ wait = max(0, timeout / 10)
+
+ while True:
+ try:
+ os.mkdir(self.lock_file)
+ except OSError:
+ err = sys.exc_info()[1]
+ if err.errno == errno.EEXIST:
+ # Already locked.
+ if os.path.exists(self.unique_name):
+ # Already locked by me.
+ return
+ if timeout is not None and time.time() > end_time:
+ if timeout > 0:
+ raise LockTimeout
+ else:
+ # Someone else has the lock.
+ raise AlreadyLocked
+ time.sleep(wait)
+ else:
+ # Couldn't create the lock for some other reason
+ raise LockFailed("failed to create %s" % self.lock_file)
+ else:
+ open(self.unique_name, "wb").close()
+ return
+
+ def release(self):
+ if not self.is_locked():
+ raise NotLocked
+ elif not os.path.exists(self.unique_name):
+ raise NotMyLock
+ os.unlink(self.unique_name)
+ os.rmdir(self.lock_file)
+
+ def is_locked(self):
+ return os.path.exists(self.lock_file)
+
+ def i_am_locking(self):
+ return (self.is_locked() and
+ os.path.exists(self.unique_name))
+
+ def break_lock(self):
+ if os.path.exists(self.lock_file):
+ for name in os.listdir(self.lock_file):
+ os.unlink(os.path.join(self.lock_file, name))
+ os.rmdir(self.lock_file)
diff --git a/vendor/lockfile/lockfile/pidlockfile.py b/vendor/lockfile/lockfile/pidlockfile.py
new file mode 100644
index 0000000000..3fa279c14b
--- /dev/null
+++ b/vendor/lockfile/lockfile/pidlockfile.py
@@ -0,0 +1,181 @@
+# -*- coding: utf-8 -*-
+
+# pidlockfile.py
+#
+# Copyright © 2008–2009 Ben Finney <ben+python@benfinney.id.au>
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Python Software Foundation License, version 2 or
+# later as published by the Python Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+
+""" Lockfile behaviour implemented via Unix PID files.
+ """
+
+from __future__ import absolute_import
+
+import os
+import sys
+import errno
+import time
+
+from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock,
+ LockTimeout)
+
+
+class PIDLockFile(LockBase):
+ """ Lockfile implemented as a Unix PID file.
+
+ The lock file is a normal file named by the attribute `path`.
+ A lock's PID file contains a single line of text, containing
+ the process ID (PID) of the process that acquired the lock.
+
+ >>> lock = PIDLockFile('somefile')
+ >>> lock = PIDLockFile('somefile', threaded=False)
+ """
+
+ def read_pid(self):
+ """ Get the PID from the lock file.
+ """
+ return read_pid_from_pidfile(self.path)
+
+ def is_locked(self):
+ """ Test if the lock is currently held.
+
+ The lock is held if the PID file for this lock exists.
+
+ """
+ return os.path.exists(self.path)
+
+ def i_am_locking(self):
+ """ Test if the lock is held by the current process.
+
+ Returns ``True`` if the current process ID matches the
+ number stored in the PID file.
+ """
+ return self.is_locked() and os.getpid() == self.read_pid()
+
+ def acquire(self, timeout=None):
+ """ Acquire the lock.
+
+ Creates the PID file for this lock, or raises an error if
+ the lock could not be acquired.
+ """
+
+ end_time = time.time()
+ if timeout is not None and timeout > 0:
+ end_time += timeout
+
+ while True:
+ try:
+ write_pid_to_pidfile(self.path)
+ except OSError, exc:
+ if exc.errno == errno.EEXIST:
+ # The lock creation failed. Maybe sleep a bit.
+ if timeout is not None and time.time() > end_time:
+ if timeout > 0:
+ raise LockTimeout
+ else:
+ raise AlreadyLocked
+ time.sleep(timeout is not None and timeout/10 or 0.1)
+ else:
+ raise LockFailed
+ else:
+ return
+
+ def release(self):
+ """ Release the lock.
+
+ Removes the PID file to release the lock, or raises an
+ error if the current process does not hold the lock.
+
+ """
+ if not self.is_locked():
+ raise NotLocked
+ if not self.i_am_locking():
+ raise NotMyLock
+ remove_existing_pidfile(self.path)
+
+ def break_lock(self):
+ """ Break an existing lock.
+
+ Removes the PID file if it already exists, otherwise does
+ nothing.
+
+ """
+ remove_existing_pidfile(self.path)
+
+def read_pid_from_pidfile(pidfile_path):
+ """ Read the PID recorded in the named PID file.
+
+ Read and return the numeric PID recorded as text in the named
+ PID file. If the PID file cannot be read, or if the content is
+ not a valid PID, return ``None``.
+
+ """
+ pid = None
+ try:
+ pidfile = open(pidfile_path, 'r')
+ except IOError:
+ pass
+ else:
+ # According to the FHS 2.3 section on PID files in /var/run:
+ #
+ # The file must consist of the process identifier in
+ # ASCII-encoded decimal, followed by a newline character.
+ #
+ # Programs that read PID files should be somewhat flexible
+ # in what they accept; i.e., they should ignore extra
+ # whitespace, leading zeroes, absence of the trailing
+ # newline, or additional lines in the PID file.
+
+ line = pidfile.readline().strip()
+ try:
+ pid = int(line)
+ except ValueError:
+ pass
+ pidfile.close()
+
+ return pid
+
+
+def write_pid_to_pidfile(pidfile_path):
+ """ Write the PID in the named PID file.
+
+ Get the numeric process ID (“PIDâ€) of the current process
+ and write it to the named file as a line of text.
+
+ """
+ open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
+ open_mode = 0x644
+ pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
+ pidfile = os.fdopen(pidfile_fd, 'w')
+
+ # According to the FHS 2.3 section on PID files in /var/run:
+ #
+ # The file must consist of the process identifier in
+ # ASCII-encoded decimal, followed by a newline character. For
+ # example, if crond was process number 25, /var/run/crond.pid
+ # would contain three characters: two, five, and newline.
+
+ pid = os.getpid()
+ line = "%(pid)d\n" % vars()
+ pidfile.write(line)
+ pidfile.close()
+
+
+def remove_existing_pidfile(pidfile_path):
+ """ Remove the named PID file if it exists.
+
+ Removing a PID file that doesn't already exist puts us in the
+ desired state, so we ignore the condition if the file does not
+ exist.
+
+ """
+ try:
+ os.remove(pidfile_path)
+ except OSError, exc:
+ if exc.errno == errno.ENOENT:
+ pass
+ else:
+ raise
diff --git a/vendor/lockfile/lockfile/sqlitelockfile.py b/vendor/lockfile/lockfile/sqlitelockfile.py
new file mode 100644
index 0000000000..9df57b04c6
--- /dev/null
+++ b/vendor/lockfile/lockfile/sqlitelockfile.py
@@ -0,0 +1,142 @@
+from __future__ import absolute_import, division
+
+import time
+import os
+
+from . import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
+
+class SQLiteLockFile(LockBase):
+ "Demonstrate SQL-based locking."
+
+ import tempfile
+ _fd, testdb = tempfile.mkstemp()
+ os.close(_fd)
+ os.unlink(testdb)
+ del _fd, tempfile
+
+ def __init__(self, path, threaded=True):
+ """
+ >>> lock = SQLiteLockFile('somefile')
+ >>> lock = SQLiteLockFile('somefile', threaded=False)
+ """
+ LockBase.__init__(self, path, threaded)
+ self.lock_file = unicode(self.lock_file)
+ self.unique_name = unicode(self.unique_name)
+
+ import sqlite3
+ self.connection = sqlite3.connect(SQLiteLockFile.testdb)
+
+ c = self.connection.cursor()
+ try:
+ c.execute("create table locks"
+ "("
+ " lock_file varchar(32),"
+ " unique_name varchar(32)"
+ ")")
+ except sqlite3.OperationalError:
+ pass
+ else:
+ self.connection.commit()
+ import atexit
+ atexit.register(os.unlink, SQLiteLockFile.testdb)
+
+ def acquire(self, timeout=None):
+ end_time = time.time()
+ if timeout is not None and timeout > 0:
+ end_time += timeout
+
+ if timeout is None:
+ wait = 0.1
+ elif timeout <= 0:
+ wait = 0
+ else:
+ wait = timeout / 10
+
+ cursor = self.connection.cursor()
+
+ while True:
+ if not self.is_locked():
+ # Not locked. Try to lock it.
+ cursor.execute("insert into locks"
+ " (lock_file, unique_name)"
+ " values"
+ " (?, ?)",
+ (self.lock_file, self.unique_name))
+ self.connection.commit()
+
+ # Check to see if we are the only lock holder.
+ cursor.execute("select * from locks"
+ " where unique_name = ?",
+ (self.unique_name,))
+ rows = cursor.fetchall()
+ if len(rows) > 1:
+ # Nope. Someone else got there. Remove our lock.
+ cursor.execute("delete from locks"
+ " where unique_name = ?",
+ (self.unique_name,))
+ self.connection.commit()
+ else:
+ # Yup. We're done, so go home.
+ return
+ else:
+ # Check to see if we are the only lock holder.
+ cursor.execute("select * from locks"
+ " where unique_name = ?",
+ (self.unique_name,))
+ rows = cursor.fetchall()
+ if len(rows) == 1:
+ # We're the locker, so go home.
+ return
+
+ # Maybe we should wait a bit longer.
+ if timeout is not None and time.time() > end_time:
+ if timeout > 0:
+ # No more waiting.
+ raise LockTimeout
+ else:
+ # Someone else has the lock and we are impatient..
+ raise AlreadyLocked
+
+ # Well, okay. We'll give it a bit longer.
+ time.sleep(wait)
+
+ def release(self):
+ if not self.is_locked():
+ raise NotLocked
+ if not self.i_am_locking():
+ raise NotMyLock((self._who_is_locking(), self.unique_name))
+ cursor = self.connection.cursor()
+ cursor.execute("delete from locks"
+ " where unique_name = ?",
+ (self.unique_name,))
+ self.connection.commit()
+
+ def _who_is_locking(self):
+ cursor = self.connection.cursor()
+ cursor.execute("select unique_name from locks"
+ " where lock_file = ?",
+ (self.lock_file,))
+ return cursor.fetchone()[0]
+
+ def is_locked(self):
+ cursor = self.connection.cursor()
+ cursor.execute("select * from locks"
+ " where lock_file = ?",
+ (self.lock_file,))
+ rows = cursor.fetchall()
+ return not not rows
+
+ def i_am_locking(self):
+ cursor = self.connection.cursor()
+ cursor.execute("select * from locks"
+ " where lock_file = ?"
+ " and unique_name = ?",
+ (self.lock_file, self.unique_name))
+ return not not cursor.fetchall()
+
+ def break_lock(self):
+ cursor = self.connection.cursor()
+ cursor.execute("delete from locks"
+ " where lock_file = ?",
+ (self.lock_file,))
+ self.connection.commit()
diff --git a/vendor/lockfile/setup.py b/vendor/lockfile/setup.py
new file mode 100644
index 0000000000..3e407f57ae
--- /dev/null
+++ b/vendor/lockfile/setup.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+V = "0.9"
+
+from distutils.core import setup
+setup(name='lockfile',
+ author='Skip Montanaro',
+ author_email='skip@pobox.com',
+ url='http://smontanaro.dyndns.org/python/',
+ download_url=('http://smontanaro.dyndns.org/python/lockfile-%s.tar.gz' %
+ V),
+ version=V,
+ description="Platform-independent file locking module",
+ long_description=open("README").read(),
+ packages=['lockfile'],
+ license='MIT License',
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: MacOS',
+ 'Operating System :: Microsoft :: Windows :: Windows NT/2000',
+ 'Operating System :: POSIX',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2.4',
+ 'Programming Language :: Python :: 2.5',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.0',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ ]
+ )
diff --git a/vendor/lockfile/test/compliancetest.py b/vendor/lockfile/test/compliancetest.py
new file mode 100644
index 0000000000..f1f221075e
--- /dev/null
+++ b/vendor/lockfile/test/compliancetest.py
@@ -0,0 +1,228 @@
+import os
+import threading
+import shutil
+
+import lockfile
+
+class ComplianceTest(object):
+ def __init__(self):
+ self.saved_class = lockfile.LockFile
+
+ def _testfile(self):
+ """Return platform-appropriate file. Helper for tests."""
+ import tempfile
+ return os.path.join(tempfile.gettempdir(), 'trash-%s' % os.getpid())
+
+ def setup(self):
+ lockfile.LockFile = self.class_to_test
+
+ def teardown(self):
+ tf = self._testfile()
+ if os.path.isdir(tf):
+ shutil.rmtree(tf)
+ elif os.path.isfile(tf):
+ os.unlink(tf)
+ lockfile.LockFile = self.saved_class
+
+ def _test_acquire_helper(self, tbool):
+ # As simple as it gets.
+ lock = lockfile.LockFile(self._testfile(), threaded=tbool)
+ lock.acquire()
+ assert lock.is_locked()
+ lock.release()
+ assert not lock.is_locked()
+
+ def test_acquire_basic_threaded(self):
+ self._test_acquire_helper(True)
+
+ def test_acquire_basic_unthreaded(self):
+ self._test_acquire_helper(False)
+
+ def _test_acquire_no_timeout_helper(self, tbool):
+ # No timeout test
+ e1, e2 = threading.Event(), threading.Event()
+ t = _in_thread(self._lock_wait_unlock, e1, e2)
+ e1.wait() # wait for thread t to acquire lock
+ lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
+ assert lock2.is_locked()
+ assert not lock2.i_am_locking()
+
+ try:
+ lock2.acquire(timeout=-1)
+ except lockfile.AlreadyLocked:
+ pass
+ else:
+ lock2.release()
+ raise AssertionError("did not raise AlreadyLocked in"
+ " thread %s" %
+ threading.current_thread().get_name())
+
+ e2.set() # tell thread t to release lock
+ t.join()
+
+ def test_acquire_no_timeout_threaded(self):
+ self._test_acquire_no_timeout_helper(True)
+
+ def test_acquire_no_timeout_unthreaded(self):
+ self._test_acquire_no_timeout_helper(False)
+
+ def _test_acquire_timeout_helper(self, tbool):
+ # Timeout test
+ e1, e2 = threading.Event(), threading.Event()
+ t = _in_thread(self._lock_wait_unlock, e1, e2)
+ e1.wait() # wait for thread t to acquire lock
+ lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
+ assert lock2.is_locked()
+ try:
+ lock2.acquire(timeout=0.1)
+ except lockfile.LockTimeout:
+ pass
+ else:
+ lock2.release()
+ raise AssertionError("did not raise LockTimeout in thread %s" %
+ threading.current_thread().get_name())
+
+ e2.set()
+ t.join()
+
+ def test_acquire_timeout_threaded(self):
+ self._test_acquire_timeout_helper(True)
+
+ def test_acquire_timeout_unthreaded(self):
+ self._test_acquire_timeout_helper(False)
+
+ def _test_release_basic_helper(self, tbool):
+ lock = lockfile.LockFile(self._testfile(), threaded=tbool)
+ lock.acquire()
+ assert lock.is_locked()
+ lock.release()
+ assert not lock.is_locked()
+ assert not lock.i_am_locking()
+ try:
+ lock.release()
+ except lockfile.NotLocked:
+ pass
+ except lockfile.NotMyLock:
+ raise AssertionError('unexpected exception: %s' %
+ lockfile.NotMyLock)
+ else:
+ raise AssertionError('erroneously unlocked file')
+
+ def test_release_basic_threaded(self):
+ self._test_release_basic_helper(True)
+
+ def test_release_basic_unthreaded(self):
+ self._test_release_basic_helper(False)
+
+ def _test_release_from_thread_helper(self, tbool):
+ e1, e2 = threading.Event(), threading.Event()
+ t = _in_thread(self._lock_wait_unlock, e1, e2)
+ e1.wait()
+ lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
+ assert lock2.is_locked()
+ assert not lock2.i_am_locking()
+ try:
+ lock2.release()
+ except lockfile.NotMyLock:
+ pass
+ else:
+ raise AssertionError('erroneously unlocked a file locked'
+ ' by another thread.')
+ e2.set()
+ t.join()
+
+ def test_release_from_thread_threaded(self):
+ self._test_release_from_thread_helper(True)
+
+ def test_release_from_thread_unthreaded(self):
+ self._test_release_from_thread_helper(False)
+
+ def _test_is_locked_helper(self, tbool):
+ lock = lockfile.LockFile(self._testfile(), threaded=tbool)
+ lock.acquire()
+ assert lock.is_locked()
+ lock.release()
+ assert not lock.is_locked()
+
+ def test_is_locked_threaded(self):
+ self._test_is_locked_helper(True)
+
+ def test_is_locked_unthreaded(self):
+ self._test_is_locked_helper(False)
+
+ def test_i_am_locking(self):
+ lock1 = lockfile.LockFile(self._testfile(), threaded=False)
+ lock1.acquire()
+ try:
+ assert lock1.is_locked()
+ lock2 = lockfile.LockFile(self._testfile())
+ try:
+ assert lock1.i_am_locking()
+ assert not lock2.i_am_locking()
+ try:
+ lock2.acquire(timeout=2)
+ except lockfile.LockTimeout:
+ lock2.break_lock()
+ assert not lock2.is_locked()
+ assert not lock1.is_locked()
+ lock2.acquire()
+ else:
+ raise AssertionError('expected LockTimeout...')
+ assert not lock1.i_am_locking()
+ assert lock2.i_am_locking()
+ finally:
+ if lock2.i_am_locking():
+ lock2.release()
+ finally:
+ if lock1.i_am_locking():
+ lock1.release()
+
+ def _test_break_lock_helper(self, tbool):
+ lock = lockfile.LockFile(self._testfile(), threaded=tbool)
+ lock.acquire()
+ assert lock.is_locked()
+ lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
+ assert lock2.is_locked()
+ lock2.break_lock()
+ assert not lock2.is_locked()
+ try:
+ lock.release()
+ except lockfile.NotLocked:
+ pass
+ else:
+ raise AssertionError('break lock failed')
+
+ def test_break_lock_threaded(self):
+ self._test_break_lock_helper(True)
+
+ def test_break_lock_unthreaded(self):
+ self._test_break_lock_helper(False)
+
+ def _lock_wait_unlock(self, event1, event2):
+ """Lock from another thread. Helper for tests."""
+ l = lockfile.LockFile(self._testfile())
+ l.acquire()
+ try:
+ event1.set() # we're in,
+ event2.wait() # wait for boss's permission to leave
+ finally:
+ l.release()
+
+ def test_enter(self):
+ lock = lockfile.LockFile(self._testfile())
+ lock.acquire()
+ try:
+ assert lock.is_locked(), "Not locked after acquire!"
+ finally:
+ lock.release()
+ assert not lock.is_locked(), "still locked after release!"
+
+def _in_thread(func, *args, **kwargs):
+ """Execute func(*args, **kwargs) after dt seconds. Helper for tests."""
+ def _f():
+ func(*args, **kwargs)
+ t = threading.Thread(target=_f, name='/*/*')
+ t.setDaemon(True)
+ t.start()
+ return t
+
diff --git a/vendor/lockfile/test/test_lockfile.py b/vendor/lockfile/test/test_lockfile.py
new file mode 100644
index 0000000000..3b70cddd31
--- /dev/null
+++ b/vendor/lockfile/test/test_lockfile.py
@@ -0,0 +1,30 @@
+import sys
+
+import lockfile.linklockfile, lockfile.mkdirlockfile, lockfile.pidlockfile
+
+from compliancetest import ComplianceTest
+
+class TestLinkLockFile(ComplianceTest):
+ class_to_test = lockfile.linklockfile.LinkLockFile
+
+class TestMkdirLockFile(ComplianceTest):
+ class_to_test = lockfile.mkdirlockfile.MkdirLockFile
+
+class TestPIDLockFile(ComplianceTest):
+ class_to_test = lockfile.pidlockfile.PIDLockFile
+
+# Check backwards compatibility
+class TestLinkFileLock(ComplianceTest):
+ class_to_test = lockfile.LinkFileLock
+
+class TestMkdirFileLock(ComplianceTest):
+ class_to_test = lockfile.MkdirFileLock
+
+try:
+ import sqlite3
+except ImportError:
+ pass
+else:
+ import lockfile.sqlitelockfile
+ class TestSQLiteLockFile(ComplianceTest):
+ class_to_test = lockfile.sqlitelockfile.SQLiteLockFile
diff --git a/vendor/pymox/COPYING b/vendor/pymox/COPYING
new file mode 100644
index 0000000000..d645695673
--- /dev/null
+++ b/vendor/pymox/COPYING
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/pymox/MANIFEST.in b/vendor/pymox/MANIFEST.in
new file mode 100644
index 0000000000..326bc88dc9
--- /dev/null
+++ b/vendor/pymox/MANIFEST.in
@@ -0,0 +1,5 @@
+include COPYING
+include mox_test.py
+include mox_test_helper.py
+include stubout_test.py
+include stubout_testee.py
diff --git a/vendor/pymox/README b/vendor/pymox/README
new file mode 100644
index 0000000000..5e1fe7827f
--- /dev/null
+++ b/vendor/pymox/README
@@ -0,0 +1,56 @@
+Mox is an open source mock object framework for Python, inspired by
+the Java library EasyMock.
+
+To install:
+
+ $ python setup.py install
+
+To run Mox's internal tests:
+
+ $ python mox_test.py
+
+Basic usage:
+
+ import unittest
+ import mox
+
+ class PersonTest(mox.MoxTestBase):
+
+ def testUsingMox(self):
+ # Create a mock Person
+ mock_person = self.mox.CreateMock(Person)
+
+ test_person = ...
+ test_primary_key = ...
+ unknown_person = ...
+
+ # Expect InsertPerson to be called with test_person; return
+ # test_primary_key at that point
+ mock_person.InsertPerson(test_person).AndReturn(test_primary_key)
+
+ # Raise an exception when this is called
+ mock_person.DeletePerson(unknown_person).AndRaise(UnknownPersonError())
+
+ # Switch from record mode to replay mode
+ self.mox.ReplayAll()
+
+ # Run the test
+ ret_pk = mock_person.InsertPerson(test_person)
+ self.assertEquals(test_primary_key, ret_pk)
+ self.assertRaises(UnknownPersonError, mock_person, unknown_person)
+
+For more documentation, see:
+
+ http://code.google.com/p/pymox/wiki/MoxDocumentation
+
+For more information, see:
+
+ http://code.google.com/p/pymox/
+
+Our user and developer discussion group is:
+
+ http://groups.google.com/group/mox-discuss
+
+Mox is Copyright 2008 Google Inc, and licensed under the Apache
+License, Version 2.0; see the file COPYING for details. If you would
+like to help us improve Mox, join the group.
diff --git a/vendor/pymox/mox.py b/vendor/pymox/mox.py
new file mode 100755
index 0000000000..ba1e09560c
--- /dev/null
+++ b/vendor/pymox/mox.py
@@ -0,0 +1,1729 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Mox, an object-mocking framework for Python.
+
+Mox works in the record-replay-verify paradigm. When you first create
+a mock object, it is in record mode. You then programmatically set
+the expected behavior of the mock object (what methods are to be
+called on it, with what parameters, what they should return, and in
+what order).
+
+Once you have set up the expected mock behavior, you put it in replay
+mode. Now the mock responds to method calls just as you told it to.
+If an unexpected method (or an expected method with unexpected
+parameters) is called, then an exception will be raised.
+
+Once you are done interacting with the mock, you need to verify that
+all the expected interactions occured. (Maybe your code exited
+prematurely without calling some cleanup method!) The verify phase
+ensures that every expected method was called; otherwise, an exception
+will be raised.
+
+WARNING! Mock objects created by Mox are not thread-safe. If you are
+call a mock in multiple threads, it should be guarded by a mutex.
+
+TODO(stevepm): Add the option to make mocks thread-safe!
+
+Suggested usage / workflow:
+
+ # Create Mox factory
+ my_mox = Mox()
+
+ # Create a mock data access object
+ mock_dao = my_mox.CreateMock(DAOClass)
+
+ # Set up expected behavior
+ mock_dao.RetrievePersonWithIdentifier('1').AndReturn(person)
+ mock_dao.DeletePerson(person)
+
+ # Put mocks in replay mode
+ my_mox.ReplayAll()
+
+ # Inject mock object and run test
+ controller.SetDao(mock_dao)
+ controller.DeletePersonById('1')
+
+ # Verify all methods were called as expected
+ my_mox.VerifyAll()
+"""
+
+from collections import deque
+import difflib
+import inspect
+import re
+import types
+import unittest
+
+import stubout
+
+class Error(AssertionError):
+ """Base exception for this module."""
+
+ pass
+
+
+class ExpectedMethodCallsError(Error):
+ """Raised when Verify() is called before all expected methods have been called
+ """
+
+ def __init__(self, expected_methods):
+ """Init exception.
+
+ Args:
+ # expected_methods: A sequence of MockMethod objects that should have been
+ # called.
+ expected_methods: [MockMethod]
+
+ Raises:
+ ValueError: if expected_methods contains no methods.
+ """
+
+ if not expected_methods:
+ raise ValueError("There must be at least one expected method")
+ Error.__init__(self)
+ self._expected_methods = expected_methods
+
+ def __str__(self):
+ calls = "\n".join(["%3d. %s" % (i, m)
+ for i, m in enumerate(self._expected_methods)])
+ return "Verify: Expected methods never called:\n%s" % (calls,)
+
+
+class UnexpectedMethodCallError(Error):
+ """Raised when an unexpected method is called.
+
+ This can occur if a method is called with incorrect parameters, or out of the
+ specified order.
+ """
+
+ def __init__(self, unexpected_method, expected):
+ """Init exception.
+
+ Args:
+ # unexpected_method: MockMethod that was called but was not at the head of
+ # the expected_method queue.
+ # expected: MockMethod or UnorderedGroup the method should have
+ # been in.
+ unexpected_method: MockMethod
+ expected: MockMethod or UnorderedGroup
+ """
+
+ Error.__init__(self)
+ if expected is None:
+ self._str = "Unexpected method call %s" % (unexpected_method,)
+ else:
+ differ = difflib.Differ()
+ diff = differ.compare(str(unexpected_method).splitlines(True),
+ str(expected).splitlines(True))
+ self._str = ("Unexpected method call. unexpected:- expected:+\n%s"
+ % ("\n".join(diff),))
+
+ def __str__(self):
+ return self._str
+
+
+class UnknownMethodCallError(Error):
+ """Raised if an unknown method is requested of the mock object."""
+
+ def __init__(self, unknown_method_name):
+ """Init exception.
+
+ Args:
+ # unknown_method_name: Method call that is not part of the mocked class's
+ # public interface.
+ unknown_method_name: str
+ """
+
+ Error.__init__(self)
+ self._unknown_method_name = unknown_method_name
+
+ def __str__(self):
+ return "Method called is not a member of the object: %s" % \
+ self._unknown_method_name
+
+
+class PrivateAttributeError(Error):
+ """
+ Raised if a MockObject is passed a private additional attribute name.
+ """
+
+ def __init__(self, attr):
+ Error.__init__(self)
+ self._attr = attr
+
+ def __str__(self):
+ return ("Attribute '%s' is private and should not be available in a mock "
+ "object." % attr)
+
+class Mox(object):
+ """Mox: a factory for creating mock objects."""
+
+ # A list of types that should be stubbed out with MockObjects (as
+ # opposed to MockAnythings).
+ _USE_MOCK_OBJECT = [types.ClassType, types.InstanceType, types.ModuleType,
+ types.ObjectType, types.TypeType]
+
+ def __init__(self):
+ """Initialize a new Mox."""
+
+ self._mock_objects = []
+ self.stubs = stubout.StubOutForTesting()
+
+ def CreateMock(self, class_to_mock, attrs={}):
+ """Create a new mock object.
+
+ Args:
+ # class_to_mock: the class to be mocked
+ class_to_mock: class
+ attrs: dict of attribute names to values that will be set on the mock
+ object. Only public attributes may be set.
+
+ Returns:
+ MockObject that can be used as the class_to_mock would be.
+ """
+ new_mock = MockObject(class_to_mock, attrs=attrs)
+ self._mock_objects.append(new_mock)
+ return new_mock
+
+ def CreateMockAnything(self, description=None):
+ """Create a mock that will accept any method calls.
+
+ This does not enforce an interface.
+
+ Args:
+ description: str. Optionally, a descriptive name for the mock object being
+ created, for debugging output purposes.
+ """
+ new_mock = MockAnything(description=description)
+ self._mock_objects.append(new_mock)
+ return new_mock
+
+ def ReplayAll(self):
+ """Set all mock objects to replay mode."""
+
+ for mock_obj in self._mock_objects:
+ mock_obj._Replay()
+
+
+ def VerifyAll(self):
+ """Call verify on all mock objects created."""
+
+ for mock_obj in self._mock_objects:
+ mock_obj._Verify()
+
+ def ResetAll(self):
+ """Call reset on all mock objects. This does not unset stubs."""
+
+ for mock_obj in self._mock_objects:
+ mock_obj._Reset()
+
+ def StubOutWithMock(self, obj, attr_name, use_mock_anything=False):
+ """Replace a method, attribute, etc. with a Mock.
+
+ This will replace a class or module with a MockObject, and everything else
+ (method, function, etc) with a MockAnything. This can be overridden to
+ always use a MockAnything by setting use_mock_anything to True.
+
+ Args:
+ obj: A Python object (class, module, instance, callable).
+ attr_name: str. The name of the attribute to replace with a mock.
+ use_mock_anything: bool. True if a MockAnything should be used regardless
+ of the type of attribute.
+ """
+
+ attr_to_replace = getattr(obj, attr_name)
+
+ # Check for a MockAnything. This could cause confusing problems later on.
+ if attr_to_replace == MockAnything():
+ raise TypeError('Cannot mock a MockAnything! Did you remember to '
+ 'call UnsetStubs in your previous test?')
+
+ if type(attr_to_replace) in self._USE_MOCK_OBJECT and not use_mock_anything:
+ stub = self.CreateMock(attr_to_replace)
+ else:
+ stub = self.CreateMockAnything(description='Stub for %s' % attr_to_replace)
+
+ self.stubs.Set(obj, attr_name, stub)
+
+ def UnsetStubs(self):
+ """Restore stubs to their original state."""
+
+ self.stubs.UnsetAll()
+
+def Replay(*args):
+ """Put mocks into Replay mode.
+
+ Args:
+ # args is any number of mocks to put into replay mode.
+ """
+
+ for mock in args:
+ mock._Replay()
+
+
+def Verify(*args):
+ """Verify mocks.
+
+ Args:
+ # args is any number of mocks to be verified.
+ """
+
+ for mock in args:
+ mock._Verify()
+
+
+def Reset(*args):
+ """Reset mocks.
+
+ Args:
+ # args is any number of mocks to be reset.
+ """
+
+ for mock in args:
+ mock._Reset()
+
+
+class MockAnything:
+ """A mock that can be used to mock anything.
+
+ This is helpful for mocking classes that do not provide a public interface.
+ """
+
+ def __init__(self, description=None):
+ """Initialize a new MockAnything.
+
+ Args:
+ description: str. Optionally, a descriptive name for the mock object being
+ created, for debugging output purposes.
+ """
+ self._description = description
+ self._Reset()
+
+ def __str__(self):
+ return "<MockAnything instance at %s>" % id(self)
+
+ def __repr__(self):
+ return '<MockAnything instance>'
+
+ def __getattr__(self, method_name):
+ """Intercept method calls on this object.
+
+ A new MockMethod is returned that is aware of the MockAnything's
+ state (record or replay). The call will be recorded or replayed
+ by the MockMethod's __call__.
+
+ Args:
+ # method name: the name of the method being called.
+ method_name: str
+
+ Returns:
+ A new MockMethod aware of MockAnything's state (record or replay).
+ """
+
+ return self._CreateMockMethod(method_name)
+
+ def _CreateMockMethod(self, method_name, method_to_mock=None):
+ """Create a new mock method call and return it.
+
+ Args:
+ # method_name: the name of the method being called.
+ # method_to_mock: The actual method being mocked, used for introspection.
+ method_name: str
+ method_to_mock: a method object
+
+ Returns:
+ A new MockMethod aware of MockAnything's state (record or replay).
+ """
+
+ return MockMethod(method_name, self._expected_calls_queue,
+ self._replay_mode, method_to_mock=method_to_mock,
+ description=self._description)
+
+ def __nonzero__(self):
+ """Return 1 for nonzero so the mock can be used as a conditional."""
+
+ return 1
+
+ def __eq__(self, rhs):
+ """Provide custom logic to compare objects."""
+
+ return (isinstance(rhs, MockAnything) and
+ self._replay_mode == rhs._replay_mode and
+ self._expected_calls_queue == rhs._expected_calls_queue)
+
+ def __ne__(self, rhs):
+ """Provide custom logic to compare objects."""
+
+ return not self == rhs
+
+ def _Replay(self):
+ """Start replaying expected method calls."""
+
+ self._replay_mode = True
+
+ def _Verify(self):
+ """Verify that all of the expected calls have been made.
+
+ Raises:
+ ExpectedMethodCallsError: if there are still more method calls in the
+ expected queue.
+ """
+
+ # If the list of expected calls is not empty, raise an exception
+ if self._expected_calls_queue:
+ # The last MultipleTimesGroup is not popped from the queue.
+ if (len(self._expected_calls_queue) == 1 and
+ isinstance(self._expected_calls_queue[0], MultipleTimesGroup) and
+ self._expected_calls_queue[0].IsSatisfied()):
+ pass
+ else:
+ raise ExpectedMethodCallsError(self._expected_calls_queue)
+
+ def _Reset(self):
+ """Reset the state of this mock to record mode with an empty queue."""
+
+ # Maintain a list of method calls we are expecting
+ self._expected_calls_queue = deque()
+
+ # Make sure we are in setup mode, not replay mode
+ self._replay_mode = False
+
+
+class MockObject(MockAnything, object):
+ """A mock object that simulates the public/protected interface of a class."""
+
+ def __init__(self, class_to_mock, attrs={}):
+ """Initialize a mock object.
+
+ This determines the methods and properties of the class and stores them.
+
+ Args:
+ # class_to_mock: class to be mocked
+ class_to_mock: class
+ attrs: dict of attribute names to values that will be set on the mock
+ object. Only public attributes may be set.
+
+ Raises:
+ PrivateAttributeError: if a supplied attribute is not public.
+ ValueError: if an attribute would mask an existing method.
+ """
+
+ # This is used to hack around the mixin/inheritance of MockAnything, which
+ # is not a proper object (it can be anything. :-)
+ MockAnything.__dict__['__init__'](self)
+
+ # Get a list of all the public and special methods we should mock.
+ self._known_methods = set()
+ self._known_vars = set()
+ self._class_to_mock = class_to_mock
+ try:
+ self._description = class_to_mock.__name__
+ # If class_to_mock is a mock itself, then we'll get an UnknownMethodCall
+ # error here from the underlying call to __getattr__('__name__')
+ except (UnknownMethodCallError, AttributeError):
+ try:
+ self._description = type(class_to_mock).__name__
+ except AttributeError:
+ pass
+
+ for method in dir(class_to_mock):
+ if callable(getattr(class_to_mock, method)):
+ self._known_methods.add(method)
+ else:
+ self._known_vars.add(method)
+
+ # Set additional attributes at instantiation time; this is quicker
+ # than manually setting attributes that are normally created in
+ # __init__.
+ for attr, value in attrs.items():
+ if attr.startswith("_"):
+ raise PrivateAttributeError(attr)
+ elif attr in self._known_methods:
+ raise ValueError("'%s' is a method of '%s' objects." % (attr,
+ class_to_mock))
+ else:
+ setattr(self, attr, value)
+
+ def __getattr__(self, name):
+ """Intercept attribute request on this object.
+
+ If the attribute is a public class variable, it will be returned and not
+ recorded as a call.
+
+ If the attribute is not a variable, it is handled like a method
+ call. The method name is checked against the set of mockable
+ methods, and a new MockMethod is returned that is aware of the
+ MockObject's state (record or replay). The call will be recorded
+ or replayed by the MockMethod's __call__.
+
+ Args:
+ # name: the name of the attribute being requested.
+ name: str
+
+ Returns:
+ Either a class variable or a new MockMethod that is aware of the state
+ of the mock (record or replay).
+
+ Raises:
+ UnknownMethodCallError if the MockObject does not mock the requested
+ method.
+ """
+
+ if name in self._known_vars:
+ return getattr(self._class_to_mock, name)
+
+ if name in self._known_methods:
+ return self._CreateMockMethod(
+ name,
+ method_to_mock=getattr(self._class_to_mock, name))
+
+ raise UnknownMethodCallError(name)
+
+ def __eq__(self, rhs):
+ """Provide custom logic to compare objects."""
+
+ return (isinstance(rhs, MockObject) and
+ self._class_to_mock == rhs._class_to_mock and
+ self._replay_mode == rhs._replay_mode and
+ self._expected_calls_queue == rhs._expected_calls_queue)
+
+ def __setitem__(self, key, value):
+ """Provide custom logic for mocking classes that support item assignment.
+
+ Args:
+ key: Key to set the value for.
+ value: Value to set.
+
+ Returns:
+ Expected return value in replay mode. A MockMethod object for the
+ __setitem__ method that has already been called if not in replay mode.
+
+ Raises:
+ TypeError if the underlying class does not support item assignment.
+ UnexpectedMethodCallError if the object does not expect the call to
+ __setitem__.
+
+ """
+ # Verify the class supports item assignment.
+ if '__setitem__' not in dir(self._class_to_mock):
+ raise TypeError('object does not support item assignment')
+
+ # If we are in replay mode then simply call the mock __setitem__ method.
+ if self._replay_mode:
+ return MockMethod('__setitem__', self._expected_calls_queue,
+ self._replay_mode)(key, value)
+
+
+ # Otherwise, create a mock method __setitem__.
+ return self._CreateMockMethod('__setitem__')(key, value)
+
+ def __getitem__(self, key):
+ """Provide custom logic for mocking classes that are subscriptable.
+
+ Args:
+ key: Key to return the value for.
+
+ Returns:
+ Expected return value in replay mode. A MockMethod object for the
+ __getitem__ method that has already been called if not in replay mode.
+
+ Raises:
+ TypeError if the underlying class is not subscriptable.
+ UnexpectedMethodCallError if the object does not expect the call to
+ __getitem__.
+
+ """
+ # Verify the class supports item assignment.
+ if '__getitem__' not in dir(self._class_to_mock):
+ raise TypeError('unsubscriptable object')
+
+ # If we are in replay mode then simply call the mock __getitem__ method.
+ if self._replay_mode:
+ return MockMethod('__getitem__', self._expected_calls_queue,
+ self._replay_mode)(key)
+
+
+ # Otherwise, create a mock method __getitem__.
+ return self._CreateMockMethod('__getitem__')(key)
+
+ def __iter__(self):
+ """Provide custom logic for mocking classes that are iterable.
+
+ Returns:
+ Expected return value in replay mode. A MockMethod object for the
+ __iter__ method that has already been called if not in replay mode.
+
+ Raises:
+ TypeError if the underlying class is not iterable.
+ UnexpectedMethodCallError if the object does not expect the call to
+ __iter__.
+
+ """
+ methods = dir(self._class_to_mock)
+
+ # Verify the class supports iteration.
+ if '__iter__' not in methods:
+ # If it doesn't have iter method and we are in replay method, then try to
+ # iterate using subscripts.
+ if '__getitem__' not in methods or not self._replay_mode:
+ raise TypeError('not iterable object')
+ else:
+ results = []
+ index = 0
+ try:
+ while True:
+ results.append(self[index])
+ index += 1
+ except IndexError:
+ return iter(results)
+
+ # If we are in replay mode then simply call the mock __iter__ method.
+ if self._replay_mode:
+ return MockMethod('__iter__', self._expected_calls_queue,
+ self._replay_mode)()
+
+
+ # Otherwise, create a mock method __iter__.
+ return self._CreateMockMethod('__iter__')()
+
+
+ def __contains__(self, key):
+ """Provide custom logic for mocking classes that contain items.
+
+ Args:
+ key: Key to look in container for.
+
+ Returns:
+ Expected return value in replay mode. A MockMethod object for the
+ __contains__ method that has already been called if not in replay mode.
+
+ Raises:
+ TypeError if the underlying class does not implement __contains__
+ UnexpectedMethodCaller if the object does not expect the call to
+ __contains__.
+
+ """
+ contains = self._class_to_mock.__dict__.get('__contains__', None)
+
+ if contains is None:
+ raise TypeError('unsubscriptable object')
+
+ if self._replay_mode:
+ return MockMethod('__contains__', self._expected_calls_queue,
+ self._replay_mode)(key)
+
+ return self._CreateMockMethod('__contains__')(key)
+
+ def __call__(self, *params, **named_params):
+ """Provide custom logic for mocking classes that are callable."""
+
+ # Verify the class we are mocking is callable.
+ callable = hasattr(self._class_to_mock, '__call__')
+ if not callable:
+ raise TypeError('Not callable')
+
+ # Because the call is happening directly on this object instead of a method,
+ # the call on the mock method is made right here
+ mock_method = self._CreateMockMethod('__call__')
+ return mock_method(*params, **named_params)
+
+ @property
+ def __class__(self):
+ """Return the class that is being mocked."""
+
+ return self._class_to_mock
+
+
+class MethodCallChecker(object):
+ """Ensures that methods are called correctly."""
+
+ _NEEDED, _DEFAULT, _GIVEN = range(3)
+
+ def __init__(self, method):
+ """Creates a checker.
+
+ Args:
+ # method: A method to check.
+ method: function
+
+ Raises:
+ ValueError: method could not be inspected, so checks aren't possible.
+ Some methods and functions like built-ins can't be inspected.
+ """
+ try:
+ self._args, varargs, varkw, defaults = inspect.getargspec(method)
+ except TypeError:
+ raise ValueError('Could not get argument specification for %r'
+ % (method,))
+ if inspect.ismethod(method):
+ self._args = self._args[1:] # Skip 'self'.
+ self._method = method
+
+ self._has_varargs = varargs is not None
+ self._has_varkw = varkw is not None
+ if defaults is None:
+ self._required_args = self._args
+ self._default_args = []
+ else:
+ self._required_args = self._args[:-len(defaults)]
+ self._default_args = self._args[-len(defaults):]
+
+ def _RecordArgumentGiven(self, arg_name, arg_status):
+ """Mark an argument as being given.
+
+ Args:
+ # arg_name: The name of the argument to mark in arg_status.
+ # arg_status: Maps argument names to one of _NEEDED, _DEFAULT, _GIVEN.
+ arg_name: string
+ arg_status: dict
+
+ Raises:
+ AttributeError: arg_name is already marked as _GIVEN.
+ """
+ if arg_status.get(arg_name, None) == MethodCallChecker._GIVEN:
+ raise AttributeError('%s provided more than once' % (arg_name,))
+ arg_status[arg_name] = MethodCallChecker._GIVEN
+
+ def Check(self, params, named_params):
+ """Ensures that the parameters used while recording a call are valid.
+
+ Args:
+ # params: A list of positional parameters.
+ # named_params: A dict of named parameters.
+ params: list
+ named_params: dict
+
+ Raises:
+ AttributeError: the given parameters don't work with the given method.
+ """
+ arg_status = dict((a, MethodCallChecker._NEEDED)
+ for a in self._required_args)
+ for arg in self._default_args:
+ arg_status[arg] = MethodCallChecker._DEFAULT
+
+ # Check that each positional param is valid.
+ for i in range(len(params)):
+ try:
+ arg_name = self._args[i]
+ except IndexError:
+ if not self._has_varargs:
+ raise AttributeError('%s does not take %d or more positional '
+ 'arguments' % (self._method.__name__, i))
+ else:
+ self._RecordArgumentGiven(arg_name, arg_status)
+
+ # Check each keyword argument.
+ for arg_name in named_params:
+ if arg_name not in arg_status and not self._has_varkw:
+ raise AttributeError('%s is not expecting keyword argument %s'
+ % (self._method.__name__, arg_name))
+ self._RecordArgumentGiven(arg_name, arg_status)
+
+ # Ensure all the required arguments have been given.
+ still_needed = [k for k, v in arg_status.iteritems()
+ if v == MethodCallChecker._NEEDED]
+ if still_needed:
+ raise AttributeError('No values given for arguments %s'
+ % (' '.join(sorted(still_needed))))
+
+
+class MockMethod(object):
+ """Callable mock method.
+
+ A MockMethod should act exactly like the method it mocks, accepting parameters
+ and returning a value, or throwing an exception (as specified). When this
+ method is called, it can optionally verify whether the called method (name and
+ signature) matches the expected method.
+ """
+
+ def __init__(self, method_name, call_queue, replay_mode,
+ method_to_mock=None, description=None):
+ """Construct a new mock method.
+
+ Args:
+ # method_name: the name of the method
+ # call_queue: deque of calls, verify this call against the head, or add
+ # this call to the queue.
+ # replay_mode: False if we are recording, True if we are verifying calls
+ # against the call queue.
+ # method_to_mock: The actual method being mocked, used for introspection.
+ # description: optionally, a descriptive name for this method. Typically
+ # this is equal to the descriptive name of the method's class.
+ method_name: str
+ call_queue: list or deque
+ replay_mode: bool
+ method_to_mock: a method object
+ description: str or None
+ """
+
+ self._name = method_name
+ self.__name__ = method_name
+ self._call_queue = call_queue
+ if not isinstance(call_queue, deque):
+ self._call_queue = deque(self._call_queue)
+ self._replay_mode = replay_mode
+ self._description = description
+
+ self._params = None
+ self._named_params = None
+ self._return_value = None
+ self._exception = None
+ self._side_effects = None
+
+ try:
+ self._checker = MethodCallChecker(method_to_mock)
+ except ValueError:
+ self._checker = None
+
+ def __call__(self, *params, **named_params):
+ """Log parameters and return the specified return value.
+
+ If the Mock(Anything/Object) associated with this call is in record mode,
+ this MockMethod will be pushed onto the expected call queue. If the mock
+ is in replay mode, this will pop a MockMethod off the top of the queue and
+ verify this call is equal to the expected call.
+
+ Raises:
+ UnexpectedMethodCall if this call is supposed to match an expected method
+ call and it does not.
+ """
+
+ self._params = params
+ self._named_params = named_params
+
+ if not self._replay_mode:
+ if self._checker is not None:
+ self._checker.Check(params, named_params)
+ self._call_queue.append(self)
+ return self
+
+ expected_method = self._VerifyMethodCall()
+
+ if expected_method._side_effects:
+ result = expected_method._side_effects(*params, **named_params)
+ if expected_method._return_value is None:
+ expected_method._return_value = result
+
+ if expected_method._exception:
+ raise expected_method._exception
+
+ return expected_method._return_value
+
+ def __getattr__(self, name):
+ """Raise an AttributeError with a helpful message."""
+
+ raise AttributeError('MockMethod has no attribute "%s". '
+ 'Did you remember to put your mocks in replay mode?' % name)
+
+ def __iter__(self):
+ """Raise a TypeError with a helpful message."""
+ raise TypeError('MockMethod cannot be iterated. '
+ 'Did you remember to put your mocks in replay mode?')
+
+ def next(self):
+ """Raise a TypeError with a helpful message."""
+ raise TypeError('MockMethod cannot be iterated. '
+ 'Did you remember to put your mocks in replay mode?')
+
+ def _PopNextMethod(self):
+ """Pop the next method from our call queue."""
+ try:
+ return self._call_queue.popleft()
+ except IndexError:
+ raise UnexpectedMethodCallError(self, None)
+
+ def _VerifyMethodCall(self):
+ """Verify the called method is expected.
+
+ This can be an ordered method, or part of an unordered set.
+
+ Returns:
+ The expected mock method.
+
+ Raises:
+ UnexpectedMethodCall if the method called was not expected.
+ """
+
+ expected = self._PopNextMethod()
+
+ # Loop here, because we might have a MethodGroup followed by another
+ # group.
+ while isinstance(expected, MethodGroup):
+ expected, method = expected.MethodCalled(self)
+ if method is not None:
+ return method
+
+ # This is a mock method, so just check equality.
+ if expected != self:
+ raise UnexpectedMethodCallError(self, expected)
+
+ return expected
+
+ def __str__(self):
+ params = ', '.join(
+ [repr(p) for p in self._params or []] +
+ ['%s=%r' % x for x in sorted((self._named_params or {}).items())])
+ full_desc = "%s(%s) -> %r" % (self._name, params, self._return_value)
+ if self._description:
+ full_desc = "%s.%s" % (self._description, full_desc)
+ return full_desc
+
+ def __eq__(self, rhs):
+ """Test whether this MockMethod is equivalent to another MockMethod.
+
+ Args:
+ # rhs: the right hand side of the test
+ rhs: MockMethod
+ """
+
+ return (isinstance(rhs, MockMethod) and
+ self._name == rhs._name and
+ self._params == rhs._params and
+ self._named_params == rhs._named_params)
+
+ def __ne__(self, rhs):
+ """Test whether this MockMethod is not equivalent to another MockMethod.
+
+ Args:
+ # rhs: the right hand side of the test
+ rhs: MockMethod
+ """
+
+ return not self == rhs
+
+ def GetPossibleGroup(self):
+ """Returns a possible group from the end of the call queue or None if no
+ other methods are on the stack.
+ """
+
+ # Remove this method from the tail of the queue so we can add it to a group.
+ this_method = self._call_queue.pop()
+ assert this_method == self
+
+ # Determine if the tail of the queue is a group, or just a regular ordered
+ # mock method.
+ group = None
+ try:
+ group = self._call_queue[-1]
+ except IndexError:
+ pass
+
+ return group
+
+ def _CheckAndCreateNewGroup(self, group_name, group_class):
+ """Checks if the last method (a possible group) is an instance of our
+ group_class. Adds the current method to this group or creates a new one.
+
+ Args:
+
+ group_name: the name of the group.
+ group_class: the class used to create instance of this new group
+ """
+ group = self.GetPossibleGroup()
+
+ # If this is a group, and it is the correct group, add the method.
+ if isinstance(group, group_class) and group.group_name() == group_name:
+ group.AddMethod(self)
+ return self
+
+ # Create a new group and add the method.
+ new_group = group_class(group_name)
+ new_group.AddMethod(self)
+ self._call_queue.append(new_group)
+ return self
+
+ def InAnyOrder(self, group_name="default"):
+ """Move this method into a group of unordered calls.
+
+ A group of unordered calls must be defined together, and must be executed
+ in full before the next expected method can be called. There can be
+ multiple groups that are expected serially, if they are given
+ different group names. The same group name can be reused if there is a
+ standard method call, or a group with a different name, spliced between
+ usages.
+
+ Args:
+ group_name: the name of the unordered group.
+
+ Returns:
+ self
+ """
+ return self._CheckAndCreateNewGroup(group_name, UnorderedGroup)
+
+ def MultipleTimes(self, group_name="default"):
+ """Move this method into group of calls which may be called multiple times.
+
+ A group of repeating calls must be defined together, and must be executed in
+ full before the next expected mehtod can be called.
+
+ Args:
+ group_name: the name of the unordered group.
+
+ Returns:
+ self
+ """
+ return self._CheckAndCreateNewGroup(group_name, MultipleTimesGroup)
+
+ def AndReturn(self, return_value):
+ """Set the value to return when this method is called.
+
+ Args:
+ # return_value can be anything.
+ """
+
+ self._return_value = return_value
+ return return_value
+
+ def AndRaise(self, exception):
+ """Set the exception to raise when this method is called.
+
+ Args:
+ # exception: the exception to raise when this method is called.
+ exception: Exception
+ """
+
+ self._exception = exception
+
+ def WithSideEffects(self, side_effects):
+ """Set the side effects that are simulated when this method is called.
+
+ Args:
+ side_effects: A callable which modifies the parameters or other relevant
+ state which a given test case depends on.
+
+ Returns:
+ Self for chaining with AndReturn and AndRaise.
+ """
+ self._side_effects = side_effects
+ return self
+
+class Comparator:
+ """Base class for all Mox comparators.
+
+ A Comparator can be used as a parameter to a mocked method when the exact
+ value is not known. For example, the code you are testing might build up a
+ long SQL string that is passed to your mock DAO. You're only interested that
+ the IN clause contains the proper primary keys, so you can set your mock
+ up as follows:
+
+ mock_dao.RunQuery(StrContains('IN (1, 2, 4, 5)')).AndReturn(mock_result)
+
+ Now whatever query is passed in must contain the string 'IN (1, 2, 4, 5)'.
+
+ A Comparator may replace one or more parameters, for example:
+ # return at most 10 rows
+ mock_dao.RunQuery(StrContains('SELECT'), 10)
+
+ or
+
+ # Return some non-deterministic number of rows
+ mock_dao.RunQuery(StrContains('SELECT'), IsA(int))
+ """
+
+ def equals(self, rhs):
+ """Special equals method that all comparators must implement.
+
+ Args:
+ rhs: any python object
+ """
+
+ raise NotImplementedError, 'method must be implemented by a subclass.'
+
+ def __eq__(self, rhs):
+ return self.equals(rhs)
+
+ def __ne__(self, rhs):
+ return not self.equals(rhs)
+
+
+class IsA(Comparator):
+ """This class wraps a basic Python type or class. It is used to verify
+ that a parameter is of the given type or class.
+
+ Example:
+ mock_dao.Connect(IsA(DbConnectInfo))
+ """
+
+ def __init__(self, class_name):
+ """Initialize IsA
+
+ Args:
+ class_name: basic python type or a class
+ """
+
+ self._class_name = class_name
+
+ def equals(self, rhs):
+ """Check to see if the RHS is an instance of class_name.
+
+ Args:
+ # rhs: the right hand side of the test
+ rhs: object
+
+ Returns:
+ bool
+ """
+
+ try:
+ return isinstance(rhs, self._class_name)
+ except TypeError:
+ # Check raw types if there was a type error. This is helpful for
+ # things like cStringIO.StringIO.
+ return type(rhs) == type(self._class_name)
+
+ def __repr__(self):
+ return str(self._class_name)
+
+class IsAlmost(Comparator):
+ """Comparison class used to check whether a parameter is nearly equal
+ to a given value. Generally useful for floating point numbers.
+
+ Example mock_dao.SetTimeout((IsAlmost(3.9)))
+ """
+
+ def __init__(self, float_value, places=7):
+ """Initialize IsAlmost.
+
+ Args:
+ float_value: The value for making the comparison.
+ places: The number of decimal places to round to.
+ """
+
+ self._float_value = float_value
+ self._places = places
+
+ def equals(self, rhs):
+ """Check to see if RHS is almost equal to float_value
+
+ Args:
+ rhs: the value to compare to float_value
+
+ Returns:
+ bool
+ """
+
+ try:
+ return round(rhs-self._float_value, self._places) == 0
+ except TypeError:
+ # This is probably because either float_value or rhs is not a number.
+ return False
+
+ def __repr__(self):
+ return str(self._float_value)
+
+class StrContains(Comparator):
+ """Comparison class used to check whether a substring exists in a
+ string parameter. This can be useful in mocking a database with SQL
+ passed in as a string parameter, for example.
+
+ Example:
+ mock_dao.RunQuery(StrContains('IN (1, 2, 4, 5)')).AndReturn(mock_result)
+ """
+
+ def __init__(self, search_string):
+ """Initialize.
+
+ Args:
+ # search_string: the string you are searching for
+ search_string: str
+ """
+
+ self._search_string = search_string
+
+ def equals(self, rhs):
+ """Check to see if the search_string is contained in the rhs string.
+
+ Args:
+ # rhs: the right hand side of the test
+ rhs: object
+
+ Returns:
+ bool
+ """
+
+ try:
+ return rhs.find(self._search_string) > -1
+ except Exception:
+ return False
+
+ def __repr__(self):
+ return '<str containing \'%s\'>' % self._search_string
+
+
+class Regex(Comparator):
+ """Checks if a string matches a regular expression.
+
+ This uses a given regular expression to determine equality.
+ """
+
+ def __init__(self, pattern, flags=0):
+ """Initialize.
+
+ Args:
+ # pattern is the regular expression to search for
+ pattern: str
+ # flags passed to re.compile function as the second argument
+ flags: int
+ """
+
+ self.regex = re.compile(pattern, flags=flags)
+
+ def equals(self, rhs):
+ """Check to see if rhs matches regular expression pattern.
+
+ Returns:
+ bool
+ """
+
+ return self.regex.search(rhs) is not None
+
+ def __repr__(self):
+ s = '<regular expression \'%s\'' % self.regex.pattern
+ if self.regex.flags:
+ s += ', flags=%d' % self.regex.flags
+ s += '>'
+ return s
+
+
+class In(Comparator):
+ """Checks whether an item (or key) is in a list (or dict) parameter.
+
+ Example:
+ mock_dao.GetUsersInfo(In('expectedUserName')).AndReturn(mock_result)
+ """
+
+ def __init__(self, key):
+ """Initialize.
+
+ Args:
+ # key is any thing that could be in a list or a key in a dict
+ """
+
+ self._key = key
+
+ def equals(self, rhs):
+ """Check to see whether key is in rhs.
+
+ Args:
+ rhs: dict
+
+ Returns:
+ bool
+ """
+
+ return self._key in rhs
+
+ def __repr__(self):
+ return '<sequence or map containing \'%s\'>' % self._key
+
+
+class Not(Comparator):
+ """Checks whether a predicates is False.
+
+ Example:
+ mock_dao.UpdateUsers(Not(ContainsKeyValue('stevepm', stevepm_user_info)))
+ """
+
+ def __init__(self, predicate):
+ """Initialize.
+
+ Args:
+ # predicate: a Comparator instance.
+ """
+
+ assert isinstance(predicate, Comparator), ("predicate %r must be a"
+ " Comparator." % predicate)
+ self._predicate = predicate
+
+ def equals(self, rhs):
+ """Check to see whether the predicate is False.
+
+ Args:
+ rhs: A value that will be given in argument of the predicate.
+
+ Returns:
+ bool
+ """
+
+ return not self._predicate.equals(rhs)
+
+ def __repr__(self):
+ return '<not \'%s\'>' % self._predicate
+
+
+class ContainsKeyValue(Comparator):
+ """Checks whether a key/value pair is in a dict parameter.
+
+ Example:
+ mock_dao.UpdateUsers(ContainsKeyValue('stevepm', stevepm_user_info))
+ """
+
+ def __init__(self, key, value):
+ """Initialize.
+
+ Args:
+ # key: a key in a dict
+ # value: the corresponding value
+ """
+
+ self._key = key
+ self._value = value
+
+ def equals(self, rhs):
+ """Check whether the given key/value pair is in the rhs dict.
+
+ Returns:
+ bool
+ """
+
+ try:
+ return rhs[self._key] == self._value
+ except Exception:
+ return False
+
+ def __repr__(self):
+ return '<map containing the entry \'%s: %s\'>' % (self._key, self._value)
+
+
+class ContainsAttributeValue(Comparator):
+ """Checks whether a passed parameter contains attributes with a given value.
+
+ Example:
+ mock_dao.UpdateSomething(ContainsAttribute('stevepm', stevepm_user_info))
+ """
+
+ def __init__(self, key, value):
+ """Initialize.
+
+ Args:
+ # key: an attribute name of an object
+ # value: the corresponding value
+ """
+
+ self._key = key
+ self._value = value
+
+ def equals(self, rhs):
+ """Check whether the given attribute has a matching value in the rhs object.
+
+ Returns:
+ bool
+ """
+
+ try:
+ return getattr(rhs, self._key) == self._value
+ except Exception:
+ return False
+
+
+class SameElementsAs(Comparator):
+ """Checks whether iterables contain the same elements (ignoring order).
+
+ Example:
+ mock_dao.ProcessUsers(SameElementsAs('stevepm', 'salomaki'))
+ """
+
+ def __init__(self, expected_seq):
+ """Initialize.
+
+ Args:
+ expected_seq: a sequence
+ """
+
+ self._expected_seq = expected_seq
+
+ def equals(self, actual_seq):
+ """Check to see whether actual_seq has same elements as expected_seq.
+
+ Args:
+ actual_seq: sequence
+
+ Returns:
+ bool
+ """
+
+ try:
+ expected = dict([(element, None) for element in self._expected_seq])
+ actual = dict([(element, None) for element in actual_seq])
+ except TypeError:
+ # Fall back to slower list-compare if any of the objects are unhashable.
+ expected = list(self._expected_seq)
+ actual = list(actual_seq)
+ expected.sort()
+ actual.sort()
+ return expected == actual
+
+ def __repr__(self):
+ return '<sequence with same elements as \'%s\'>' % self._expected_seq
+
+
+class And(Comparator):
+ """Evaluates one or more Comparators on RHS and returns an AND of the results.
+ """
+
+ def __init__(self, *args):
+ """Initialize.
+
+ Args:
+ *args: One or more Comparator
+ """
+
+ self._comparators = args
+
+ def equals(self, rhs):
+ """Checks whether all Comparators are equal to rhs.
+
+ Args:
+ # rhs: can be anything
+
+ Returns:
+ bool
+ """
+
+ for comparator in self._comparators:
+ if not comparator.equals(rhs):
+ return False
+
+ return True
+
+ def __repr__(self):
+ return '<AND %s>' % str(self._comparators)
+
+
+class Or(Comparator):
+ """Evaluates one or more Comparators on RHS and returns an OR of the results.
+ """
+
+ def __init__(self, *args):
+ """Initialize.
+
+ Args:
+ *args: One or more Mox comparators
+ """
+
+ self._comparators = args
+
+ def equals(self, rhs):
+ """Checks whether any Comparator is equal to rhs.
+
+ Args:
+ # rhs: can be anything
+
+ Returns:
+ bool
+ """
+
+ for comparator in self._comparators:
+ if comparator.equals(rhs):
+ return True
+
+ return False
+
+ def __repr__(self):
+ return '<OR %s>' % str(self._comparators)
+
+
+class Func(Comparator):
+ """Call a function that should verify the parameter passed in is correct.
+
+ You may need the ability to perform more advanced operations on the parameter
+ in order to validate it. You can use this to have a callable validate any
+ parameter. The callable should return either True or False.
+
+
+ Example:
+
+ def myParamValidator(param):
+ # Advanced logic here
+ return True
+
+ mock_dao.DoSomething(Func(myParamValidator), true)
+ """
+
+ def __init__(self, func):
+ """Initialize.
+
+ Args:
+ func: callable that takes one parameter and returns a bool
+ """
+
+ self._func = func
+
+ def equals(self, rhs):
+ """Test whether rhs passes the function test.
+
+ rhs is passed into func.
+
+ Args:
+ rhs: any python object
+
+ Returns:
+ the result of func(rhs)
+ """
+
+ return self._func(rhs)
+
+ def __repr__(self):
+ return str(self._func)
+
+
+class IgnoreArg(Comparator):
+ """Ignore an argument.
+
+ This can be used when we don't care about an argument of a method call.
+
+ Example:
+ # Check if CastMagic is called with 3 as first arg and 'disappear' as third.
+ mymock.CastMagic(3, IgnoreArg(), 'disappear')
+ """
+
+ def equals(self, unused_rhs):
+ """Ignores arguments and returns True.
+
+ Args:
+ unused_rhs: any python object
+
+ Returns:
+ always returns True
+ """
+
+ return True
+
+ def __repr__(self):
+ return '<IgnoreArg>'
+
+
+class MethodGroup(object):
+ """Base class containing common behaviour for MethodGroups."""
+
+ def __init__(self, group_name):
+ self._group_name = group_name
+
+ def group_name(self):
+ return self._group_name
+
+ def __str__(self):
+ return '<%s "%s">' % (self.__class__.__name__, self._group_name)
+
+ def AddMethod(self, mock_method):
+ raise NotImplementedError
+
+ def MethodCalled(self, mock_method):
+ raise NotImplementedError
+
+ def IsSatisfied(self):
+ raise NotImplementedError
+
+class UnorderedGroup(MethodGroup):
+ """UnorderedGroup holds a set of method calls that may occur in any order.
+
+ This construct is helpful for non-deterministic events, such as iterating
+ over the keys of a dict.
+ """
+
+ def __init__(self, group_name):
+ super(UnorderedGroup, self).__init__(group_name)
+ self._methods = []
+
+ def AddMethod(self, mock_method):
+ """Add a method to this group.
+
+ Args:
+ mock_method: A mock method to be added to this group.
+ """
+
+ self._methods.append(mock_method)
+
+ def MethodCalled(self, mock_method):
+ """Remove a method call from the group.
+
+ If the method is not in the set, an UnexpectedMethodCallError will be
+ raised.
+
+ Args:
+ mock_method: a mock method that should be equal to a method in the group.
+
+ Returns:
+ The mock method from the group
+
+ Raises:
+ UnexpectedMethodCallError if the mock_method was not in the group.
+ """
+
+ # Check to see if this method exists, and if so, remove it from the set
+ # and return it.
+ for method in self._methods:
+ if method == mock_method:
+ # Remove the called mock_method instead of the method in the group.
+ # The called method will match any comparators when equality is checked
+ # during removal. The method in the group could pass a comparator to
+ # another comparator during the equality check.
+ self._methods.remove(mock_method)
+
+ # If this group is not empty, put it back at the head of the queue.
+ if not self.IsSatisfied():
+ mock_method._call_queue.appendleft(self)
+
+ return self, method
+
+ raise UnexpectedMethodCallError(mock_method, self)
+
+ def IsSatisfied(self):
+ """Return True if there are not any methods in this group."""
+
+ return len(self._methods) == 0
+
+
+class MultipleTimesGroup(MethodGroup):
+ """MultipleTimesGroup holds methods that may be called any number of times.
+
+ Note: Each method must be called at least once.
+
+ This is helpful, if you don't know or care how many times a method is called.
+ """
+
+ def __init__(self, group_name):
+ super(MultipleTimesGroup, self).__init__(group_name)
+ self._methods = set()
+ self._methods_left = set()
+
+ def AddMethod(self, mock_method):
+ """Add a method to this group.
+
+ Args:
+ mock_method: A mock method to be added to this group.
+ """
+
+ self._methods.add(mock_method)
+ self._methods_left.add(mock_method)
+
+ def MethodCalled(self, mock_method):
+ """Remove a method call from the group.
+
+ If the method is not in the set, an UnexpectedMethodCallError will be
+ raised.
+
+ Args:
+ mock_method: a mock method that should be equal to a method in the group.
+
+ Returns:
+ The mock method from the group
+
+ Raises:
+ UnexpectedMethodCallError if the mock_method was not in the group.
+ """
+
+ # Check to see if this method exists, and if so add it to the set of
+ # called methods.
+ for method in self._methods:
+ if method == mock_method:
+ self._methods_left.discard(method)
+ # Always put this group back on top of the queue, because we don't know
+ # when we are done.
+ mock_method._call_queue.appendleft(self)
+ return self, method
+
+ if self.IsSatisfied():
+ next_method = mock_method._PopNextMethod();
+ return next_method, None
+ else:
+ raise UnexpectedMethodCallError(mock_method, self)
+
+ def IsSatisfied(self):
+ """Return True if all methods in this group are called at least once."""
+ return len(self._methods_left) == 0
+
+
+class MoxMetaTestBase(type):
+ """Metaclass to add mox cleanup and verification to every test.
+
+ As the mox unit testing class is being constructed (MoxTestBase or a
+ subclass), this metaclass will modify all test functions to call the
+ CleanUpMox method of the test class after they finish. This means that
+ unstubbing and verifying will happen for every test with no additional code,
+ and any failures will result in test failures as opposed to errors.
+ """
+
+ def __init__(cls, name, bases, d):
+ type.__init__(cls, name, bases, d)
+
+ # also get all the attributes from the base classes to account
+ # for a case when test class is not the immediate child of MoxTestBase
+ for base in bases:
+ for attr_name in dir(base):
+ if attr_name not in d:
+ d[attr_name] = getattr(base, attr_name)
+
+ for func_name, func in d.items():
+ if func_name.startswith('test') and callable(func):
+ setattr(cls, func_name, MoxMetaTestBase.CleanUpTest(cls, func))
+
+ @staticmethod
+ def CleanUpTest(cls, func):
+ """Adds Mox cleanup code to any MoxTestBase method.
+
+ Always unsets stubs after a test. Will verify all mocks for tests that
+ otherwise pass.
+
+ Args:
+ cls: MoxTestBase or subclass; the class whose test method we are altering.
+ func: method; the method of the MoxTestBase test class we wish to alter.
+
+ Returns:
+ The modified method.
+ """
+ def new_method(self, *args, **kwargs):
+ mox_obj = getattr(self, 'mox', None)
+ stubout_obj = getattr(self, 'stubs', None)
+ cleanup_mox = False
+ cleanup_stubout = False
+ if mox_obj and isinstance(mox_obj, Mox):
+ cleanup_mox = True
+ if stubout_obj and isinstance(stubout_obj, stubout.StubOutForTesting):
+ cleanup_stubout = True
+ try:
+ func(self, *args, **kwargs)
+ finally:
+ if cleanup_mox:
+ mox_obj.UnsetStubs()
+ if cleanup_stubout:
+ stubout_obj.UnsetAll()
+ stubout_obj.SmartUnsetAll()
+ if cleanup_mox:
+ mox_obj.VerifyAll()
+ new_method.__name__ = func.__name__
+ new_method.__doc__ = func.__doc__
+ new_method.__module__ = func.__module__
+ return new_method
+
+
+class MoxTestBase(unittest.TestCase):
+ """Convenience test class to make stubbing easier.
+
+ Sets up a "mox" attribute which is an instance of Mox (any mox tests will
+ want this), and a "stubs" attribute that is an instance of StubOutForTesting
+ (needed at times). Also automatically unsets any stubs and verifies that all
+ mock methods have been called at the end of each test, eliminating boilerplate
+ code.
+ """
+
+ __metaclass__ = MoxMetaTestBase
+
+ def setUp(self):
+ super(MoxTestBase, self).setUp()
+ self.mox = Mox()
+ self.stubs = stubout.StubOutForTesting()
diff --git a/vendor/pymox/mox_test.py b/vendor/pymox/mox_test.py
new file mode 100755
index 0000000000..ea12176ba0
--- /dev/null
+++ b/vendor/pymox/mox_test.py
@@ -0,0 +1,1853 @@
+#!/usr/bin/python2.4
+#
+# Unit tests for Mox.
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import cStringIO
+import unittest
+import re
+
+import mox
+
+import mox_test_helper
+
+OS_LISTDIR = mox_test_helper.os.listdir
+
+class ExpectedMethodCallsErrorTest(unittest.TestCase):
+ """Test creation and string conversion of ExpectedMethodCallsError."""
+
+ def testAtLeastOneMethod(self):
+ self.assertRaises(ValueError, mox.ExpectedMethodCallsError, [])
+
+ def testOneError(self):
+ method = mox.MockMethod("testMethod", [], False)
+ method(1, 2).AndReturn('output')
+ e = mox.ExpectedMethodCallsError([method])
+ self.assertEqual(
+ "Verify: Expected methods never called:\n"
+ " 0. testMethod(1, 2) -> 'output'",
+ str(e))
+
+ def testManyErrors(self):
+ method1 = mox.MockMethod("testMethod", [], False)
+ method1(1, 2).AndReturn('output')
+ method2 = mox.MockMethod("testMethod", [], False)
+ method2(a=1, b=2, c="only named")
+ method3 = mox.MockMethod("testMethod2", [], False)
+ method3().AndReturn(44)
+ method4 = mox.MockMethod("testMethod", [], False)
+ method4(1, 2).AndReturn('output')
+ e = mox.ExpectedMethodCallsError([method1, method2, method3, method4])
+ self.assertEqual(
+ "Verify: Expected methods never called:\n"
+ " 0. testMethod(1, 2) -> 'output'\n"
+ " 1. testMethod(a=1, b=2, c='only named') -> None\n"
+ " 2. testMethod2() -> 44\n"
+ " 3. testMethod(1, 2) -> 'output'",
+ str(e))
+
+
+class OrTest(unittest.TestCase):
+ """Test Or correctly chains Comparators."""
+
+ def testValidOr(self):
+ """Or should be True if either Comparator returns True."""
+ self.assert_(mox.Or(mox.IsA(dict), mox.IsA(str)) == {})
+ self.assert_(mox.Or(mox.IsA(dict), mox.IsA(str)) == 'test')
+ self.assert_(mox.Or(mox.IsA(str), mox.IsA(str)) == 'test')
+
+ def testInvalidOr(self):
+ """Or should be False if both Comparators return False."""
+ self.failIf(mox.Or(mox.IsA(dict), mox.IsA(str)) == 0)
+
+
+class AndTest(unittest.TestCase):
+ """Test And correctly chains Comparators."""
+
+ def testValidAnd(self):
+ """And should be True if both Comparators return True."""
+ self.assert_(mox.And(mox.IsA(str), mox.IsA(str)) == '1')
+
+ def testClauseOneFails(self):
+ """And should be False if the first Comparator returns False."""
+
+ self.failIf(mox.And(mox.IsA(dict), mox.IsA(str)) == '1')
+
+ def testAdvancedUsage(self):
+ """And should work with other Comparators.
+
+ Note: this test is reliant on In and ContainsKeyValue.
+ """
+ test_dict = {"mock" : "obj", "testing" : "isCOOL"}
+ self.assert_(mox.And(mox.In("testing"),
+ mox.ContainsKeyValue("mock", "obj")) == test_dict)
+
+ def testAdvancedUsageFails(self):
+ """Note: this test is reliant on In and ContainsKeyValue."""
+ test_dict = {"mock" : "obj", "testing" : "isCOOL"}
+ self.failIf(mox.And(mox.In("NOTFOUND"),
+ mox.ContainsKeyValue("mock", "obj")) == test_dict)
+
+
+class SameElementsAsTest(unittest.TestCase):
+ """Test SameElementsAs correctly identifies sequences with same elements."""
+
+ def testSortedLists(self):
+ """Should return True if two lists are exactly equal."""
+ self.assert_(mox.SameElementsAs([1, 2.0, 'c']) == [1, 2.0, 'c'])
+
+ def testUnsortedLists(self):
+ """Should return True if two lists are unequal but have same elements."""
+ self.assert_(mox.SameElementsAs([1, 2.0, 'c']) == [2.0, 'c', 1])
+
+ def testUnhashableLists(self):
+ """Should return True if two lists have the same unhashable elements."""
+ self.assert_(mox.SameElementsAs([{'a': 1}, {2: 'b'}]) ==
+ [{2: 'b'}, {'a': 1}])
+
+ def testEmptyLists(self):
+ """Should return True for two empty lists."""
+ self.assert_(mox.SameElementsAs([]) == [])
+
+ def testUnequalLists(self):
+ """Should return False if the lists are not equal."""
+ self.failIf(mox.SameElementsAs([1, 2.0, 'c']) == [2.0, 'c'])
+
+ def testUnequalUnhashableLists(self):
+ """Should return False if two lists with unhashable elements are unequal."""
+ self.failIf(mox.SameElementsAs([{'a': 1}, {2: 'b'}]) == [{2: 'b'}])
+
+
+class ContainsKeyValueTest(unittest.TestCase):
+ """Test ContainsKeyValue correctly identifies key/value pairs in a dict.
+ """
+
+ def testValidPair(self):
+ """Should return True if the key value is in the dict."""
+ self.assert_(mox.ContainsKeyValue("key", 1) == {"key": 1})
+
+ def testInvalidValue(self):
+ """Should return False if the value is not correct."""
+ self.failIf(mox.ContainsKeyValue("key", 1) == {"key": 2})
+
+ def testInvalidKey(self):
+ """Should return False if they key is not in the dict."""
+ self.failIf(mox.ContainsKeyValue("qux", 1) == {"key": 2})
+
+
+class ContainsAttributeValueTest(unittest.TestCase):
+ """Test ContainsAttributeValue correctly identifies properties in an object.
+ """
+
+ def setUp(self):
+ """Create an object to test with."""
+
+
+ class TestObject(object):
+ key = 1
+
+ self.test_object = TestObject()
+
+ def testValidPair(self):
+ """Should return True if the object has the key attribute and it matches."""
+ self.assert_(mox.ContainsAttributeValue("key", 1) == self.test_object)
+
+ def testInvalidValue(self):
+ """Should return False if the value is not correct."""
+ self.failIf(mox.ContainsKeyValue("key", 2) == self.test_object)
+
+ def testInvalidKey(self):
+ """Should return False if they the object doesn't have the property."""
+ self.failIf(mox.ContainsKeyValue("qux", 1) == self.test_object)
+
+
+class InTest(unittest.TestCase):
+ """Test In correctly identifies a key in a list/dict"""
+
+ def testItemInList(self):
+ """Should return True if the item is in the list."""
+ self.assert_(mox.In(1) == [1, 2, 3])
+
+ def testKeyInDict(self):
+ """Should return True if the item is a key in a dict."""
+ self.assert_(mox.In("test") == {"test" : "module"})
+
+
+class NotTest(unittest.TestCase):
+ """Test Not correctly identifies False predicates."""
+
+ def testItemInList(self):
+ """Should return True if the item is NOT in the list."""
+ self.assert_(mox.Not(mox.In(42)) == [1, 2, 3])
+
+ def testKeyInDict(self):
+ """Should return True if the item is NOT a key in a dict."""
+ self.assert_(mox.Not(mox.In("foo")) == {"key" : 42})
+
+ def testInvalidKeyWithNot(self):
+ """Should return False if they key is NOT in the dict."""
+ self.assert_(mox.Not(mox.ContainsKeyValue("qux", 1)) == {"key": 2})
+
+
+class StrContainsTest(unittest.TestCase):
+ """Test StrContains correctly checks for substring occurrence of a parameter.
+ """
+
+ def testValidSubstringAtStart(self):
+ """Should return True if the substring is at the start of the string."""
+ self.assert_(mox.StrContains("hello") == "hello world")
+
+ def testValidSubstringInMiddle(self):
+ """Should return True if the substring is in the middle of the string."""
+ self.assert_(mox.StrContains("lo wo") == "hello world")
+
+ def testValidSubstringAtEnd(self):
+ """Should return True if the substring is at the end of the string."""
+ self.assert_(mox.StrContains("ld") == "hello world")
+
+ def testInvaildSubstring(self):
+ """Should return False if the substring is not in the string."""
+ self.failIf(mox.StrContains("AAA") == "hello world")
+
+ def testMultipleMatches(self):
+ """Should return True if there are multiple occurances of substring."""
+ self.assert_(mox.StrContains("abc") == "ababcabcabcababc")
+
+
+class RegexTest(unittest.TestCase):
+ """Test Regex correctly matches regular expressions."""
+
+ def testIdentifyBadSyntaxDuringInit(self):
+ """The user should know immediately if a regex has bad syntax."""
+ self.assertRaises(re.error, mox.Regex, '(a|b')
+
+ def testPatternInMiddle(self):
+ """Should return True if the pattern matches at the middle of the string.
+
+ This ensures that re.search is used (instead of re.find).
+ """
+ self.assert_(mox.Regex(r"a\s+b") == "x y z a b c")
+
+ def testNonMatchPattern(self):
+ """Should return False if the pattern does not match the string."""
+ self.failIf(mox.Regex(r"a\s+b") == "x y z")
+
+ def testFlagsPassedCorrectly(self):
+ """Should return True as we pass IGNORECASE flag."""
+ self.assert_(mox.Regex(r"A", re.IGNORECASE) == "a")
+
+ def testReprWithoutFlags(self):
+ """repr should return the regular expression pattern."""
+ self.assert_(repr(mox.Regex(r"a\s+b")) == "<regular expression 'a\s+b'>")
+
+ def testReprWithFlags(self):
+ """repr should return the regular expression pattern and flags."""
+ self.assert_(repr(mox.Regex(r"a\s+b", flags=4)) ==
+ "<regular expression 'a\s+b', flags=4>")
+
+
+class IsATest(unittest.TestCase):
+ """Verify IsA correctly checks equality based upon class type, not value."""
+
+ def testEqualityValid(self):
+ """Verify that == correctly identifies objects of the same type."""
+ self.assert_(mox.IsA(str) == 'test')
+
+ def testEqualityInvalid(self):
+ """Verify that == correctly identifies objects of different types."""
+ self.failIf(mox.IsA(str) == 10)
+
+ def testInequalityValid(self):
+ """Verify that != identifies objects of different type."""
+ self.assert_(mox.IsA(str) != 10)
+
+ def testInequalityInvalid(self):
+ """Verify that != correctly identifies objects of the same type."""
+ self.failIf(mox.IsA(str) != "test")
+
+ def testEqualityInListValid(self):
+ """Verify list contents are properly compared."""
+ isa_list = [mox.IsA(str), mox.IsA(str)]
+ str_list = ["abc", "def"]
+ self.assert_(isa_list == str_list)
+
+ def testEquailtyInListInvalid(self):
+ """Verify list contents are properly compared."""
+ isa_list = [mox.IsA(str),mox.IsA(str)]
+ mixed_list = ["abc", 123]
+ self.failIf(isa_list == mixed_list)
+
+ def testSpecialTypes(self):
+ """Verify that IsA can handle objects like cStringIO.StringIO."""
+ isA = mox.IsA(cStringIO.StringIO())
+ stringIO = cStringIO.StringIO()
+ self.assert_(isA == stringIO)
+
+
+class IsAlmostTest(unittest.TestCase):
+ """Verify IsAlmost correctly checks equality of floating point numbers."""
+
+ def testEqualityValid(self):
+ """Verify that == correctly identifies nearly equivalent floats."""
+ self.assertEquals(mox.IsAlmost(1.8999999999), 1.9)
+
+ def testEqualityInvalid(self):
+ """Verify that == correctly identifies non-equivalent floats."""
+ self.assertNotEquals(mox.IsAlmost(1.899), 1.9)
+
+ def testEqualityWithPlaces(self):
+ """Verify that specifying places has the desired effect."""
+ self.assertNotEquals(mox.IsAlmost(1.899), 1.9)
+ self.assertEquals(mox.IsAlmost(1.899, places=2), 1.9)
+
+ def testNonNumericTypes(self):
+ """Verify that IsAlmost handles non-numeric types properly."""
+
+ self.assertNotEquals(mox.IsAlmost(1.8999999999), '1.9')
+ self.assertNotEquals(mox.IsAlmost('1.8999999999'), 1.9)
+ self.assertNotEquals(mox.IsAlmost('1.8999999999'), '1.9')
+
+
+class MockMethodTest(unittest.TestCase):
+ """Test class to verify that the MockMethod class is working correctly."""
+
+ def setUp(self):
+ self.expected_method = mox.MockMethod("testMethod", [], False)(['original'])
+ self.mock_method = mox.MockMethod("testMethod", [self.expected_method],
+ True)
+
+ def testNameAttribute(self):
+ """Should provide a __name__ attribute."""
+ self.assertEquals('testMethod', self.mock_method.__name__)
+
+ def testAndReturnNoneByDefault(self):
+ """Should return None by default."""
+ return_value = self.mock_method(['original'])
+ self.assert_(return_value == None)
+
+ def testAndReturnValue(self):
+ """Should return a specificed return value."""
+ expected_return_value = "test"
+ self.expected_method.AndReturn(expected_return_value)
+ return_value = self.mock_method(['original'])
+ self.assert_(return_value == expected_return_value)
+
+ def testAndRaiseException(self):
+ """Should raise a specified exception."""
+ expected_exception = Exception('test exception')
+ self.expected_method.AndRaise(expected_exception)
+ self.assertRaises(Exception, self.mock_method)
+
+ def testWithSideEffects(self):
+ """Should call state modifier."""
+ local_list = ['original']
+ def modifier(mutable_list):
+ self.assertTrue(local_list is mutable_list)
+ mutable_list[0] = 'mutation'
+ self.expected_method.WithSideEffects(modifier).AndReturn(1)
+ self.mock_method(local_list)
+ self.assertEquals('mutation', local_list[0])
+
+ def testWithReturningSideEffects(self):
+ """Should call state modifier and propagate its return value."""
+ local_list = ['original']
+ expected_return = 'expected_return'
+ def modifier_with_return(mutable_list):
+ self.assertTrue(local_list is mutable_list)
+ mutable_list[0] = 'mutation'
+ return expected_return
+ self.expected_method.WithSideEffects(modifier_with_return)
+ actual_return = self.mock_method(local_list)
+ self.assertEquals('mutation', local_list[0])
+ self.assertEquals(expected_return, actual_return)
+
+ def testWithReturningSideEffectsWithAndReturn(self):
+ """Should call state modifier and ignore its return value."""
+ local_list = ['original']
+ expected_return = 'expected_return'
+ unexpected_return = 'unexpected_return'
+ def modifier_with_return(mutable_list):
+ self.assertTrue(local_list is mutable_list)
+ mutable_list[0] = 'mutation'
+ return unexpected_return
+ self.expected_method.WithSideEffects(modifier_with_return).AndReturn(
+ expected_return)
+ actual_return = self.mock_method(local_list)
+ self.assertEquals('mutation', local_list[0])
+ self.assertEquals(expected_return, actual_return)
+
+ def testEqualityNoParamsEqual(self):
+ """Methods with the same name and without params should be equal."""
+ expected_method = mox.MockMethod("testMethod", [], False)
+ self.assertEqual(self.mock_method, expected_method)
+
+ def testEqualityNoParamsNotEqual(self):
+ """Methods with different names and without params should not be equal."""
+ expected_method = mox.MockMethod("otherMethod", [], False)
+ self.failIfEqual(self.mock_method, expected_method)
+
+ def testEqualityParamsEqual(self):
+ """Methods with the same name and parameters should be equal."""
+ params = [1, 2, 3]
+ expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method._params = params
+
+ self.mock_method._params = params
+ self.assertEqual(self.mock_method, expected_method)
+
+ def testEqualityParamsNotEqual(self):
+ """Methods with the same name and different params should not be equal."""
+ expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method._params = [1, 2, 3]
+
+ self.mock_method._params = ['a', 'b', 'c']
+ self.failIfEqual(self.mock_method, expected_method)
+
+ def testEqualityNamedParamsEqual(self):
+ """Methods with the same name and same named params should be equal."""
+ named_params = {"input1": "test", "input2": "params"}
+ expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method._named_params = named_params
+
+ self.mock_method._named_params = named_params
+ self.assertEqual(self.mock_method, expected_method)
+
+ def testEqualityNamedParamsNotEqual(self):
+ """Methods with the same name and diffnamed params should not be equal."""
+ expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method._named_params = {"input1": "test", "input2": "params"}
+
+ self.mock_method._named_params = {"input1": "test2", "input2": "params2"}
+ self.failIfEqual(self.mock_method, expected_method)
+
+ def testEqualityWrongType(self):
+ """Method should not be equal to an object of a different type."""
+ self.failIfEqual(self.mock_method, "string?")
+
+ def testObjectEquality(self):
+ """Equality of objects should work without a Comparator"""
+ instA = TestClass();
+ instB = TestClass();
+
+ params = [instA, ]
+ expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method._params = params
+
+ self.mock_method._params = [instB, ]
+ self.assertEqual(self.mock_method, expected_method)
+
+ def testStrConversion(self):
+ method = mox.MockMethod("f", [], False)
+ method(1, 2, "st", n1=8, n2="st2")
+ self.assertEqual(str(method), ("f(1, 2, 'st', n1=8, n2='st2') -> None"))
+
+ method = mox.MockMethod("testMethod", [], False)
+ method(1, 2, "only positional")
+ self.assertEqual(str(method), "testMethod(1, 2, 'only positional') -> None")
+
+ method = mox.MockMethod("testMethod", [], False)
+ method(a=1, b=2, c="only named")
+ self.assertEqual(str(method),
+ "testMethod(a=1, b=2, c='only named') -> None")
+
+ method = mox.MockMethod("testMethod", [], False)
+ method()
+ self.assertEqual(str(method), "testMethod() -> None")
+
+ method = mox.MockMethod("testMethod", [], False)
+ method(x="only 1 parameter")
+ self.assertEqual(str(method), "testMethod(x='only 1 parameter') -> None")
+
+ method = mox.MockMethod("testMethod", [], False)
+ method().AndReturn('return_value')
+ self.assertEqual(str(method), "testMethod() -> 'return_value'")
+
+ method = mox.MockMethod("testMethod", [], False)
+ method().AndReturn(('a', {1: 2}))
+ self.assertEqual(str(method), "testMethod() -> ('a', {1: 2})")
+
+
+class MockAnythingTest(unittest.TestCase):
+ """Verify that the MockAnything class works as expected."""
+
+ def setUp(self):
+ self.mock_object = mox.MockAnything()
+
+ def testRepr(self):
+ """Calling repr on a MockAnything instance must work."""
+ self.assertEqual('<MockAnything instance>', repr(self.mock_object))
+
+ def testSetupMode(self):
+ """Verify the mock will accept any call."""
+ self.mock_object.NonsenseCall()
+ self.assert_(len(self.mock_object._expected_calls_queue) == 1)
+
+ def testReplayWithExpectedCall(self):
+ """Verify the mock replays method calls as expected."""
+ self.mock_object.ValidCall() # setup method call
+ self.mock_object._Replay() # start replay mode
+ self.mock_object.ValidCall() # make method call
+
+ def testReplayWithUnexpectedCall(self):
+ """Unexpected method calls should raise UnexpectedMethodCallError."""
+ self.mock_object.ValidCall() # setup method call
+ self.mock_object._Replay() # start replay mode
+ self.assertRaises(mox.UnexpectedMethodCallError,
+ self.mock_object.OtherValidCall)
+
+ def testVerifyWithCompleteReplay(self):
+ """Verify should not raise an exception for a valid replay."""
+ self.mock_object.ValidCall() # setup method call
+ self.mock_object._Replay() # start replay mode
+ self.mock_object.ValidCall() # make method call
+ self.mock_object._Verify()
+
+ def testVerifyWithIncompleteReplay(self):
+ """Verify should raise an exception if the replay was not complete."""
+ self.mock_object.ValidCall() # setup method call
+ self.mock_object._Replay() # start replay mode
+ # ValidCall() is never made
+ self.assertRaises(mox.ExpectedMethodCallsError, self.mock_object._Verify)
+
+ def testSpecialClassMethod(self):
+ """Verify should not raise an exception when special methods are used."""
+ self.mock_object[1].AndReturn(True)
+ self.mock_object._Replay()
+ returned_val = self.mock_object[1]
+ self.assert_(returned_val)
+ self.mock_object._Verify()
+
+ def testNonzero(self):
+ """You should be able to use the mock object in an if."""
+ self.mock_object._Replay()
+ if self.mock_object:
+ pass
+
+ def testNotNone(self):
+ """Mock should be comparable to None."""
+ self.mock_object._Replay()
+ if self.mock_object is not None:
+ pass
+
+ if self.mock_object is None:
+ pass
+
+ def testEquals(self):
+ """A mock should be able to compare itself to another object."""
+ self.mock_object._Replay()
+ self.assertEquals(self.mock_object, self.mock_object)
+
+ def testEqualsMockFailure(self):
+ """Verify equals identifies unequal objects."""
+ self.mock_object.SillyCall()
+ self.mock_object._Replay()
+ self.assertNotEquals(self.mock_object, mox.MockAnything())
+
+ def testEqualsInstanceFailure(self):
+ """Verify equals identifies that objects are different instances."""
+ self.mock_object._Replay()
+ self.assertNotEquals(self.mock_object, TestClass())
+
+ def testNotEquals(self):
+ """Verify not equals works."""
+ self.mock_object._Replay()
+ self.assertFalse(self.mock_object != self.mock_object)
+
+ def testNestedMockCallsRecordedSerially(self):
+ """Test that nested calls work when recorded serially."""
+ self.mock_object.CallInner().AndReturn(1)
+ self.mock_object.CallOuter(1)
+ self.mock_object._Replay()
+
+ self.mock_object.CallOuter(self.mock_object.CallInner())
+
+ self.mock_object._Verify()
+
+ def testNestedMockCallsRecordedNested(self):
+ """Test that nested cals work when recorded in a nested fashion."""
+ self.mock_object.CallOuter(self.mock_object.CallInner().AndReturn(1))
+ self.mock_object._Replay()
+
+ self.mock_object.CallOuter(self.mock_object.CallInner())
+
+ self.mock_object._Verify()
+
+ def testIsCallable(self):
+ """Test that MockAnything can even mock a simple callable.
+
+ This is handy for "stubbing out" a method in a module with a mock, and
+ verifying that it was called.
+ """
+ self.mock_object().AndReturn('mox0rd')
+ self.mock_object._Replay()
+
+ self.assertEquals('mox0rd', self.mock_object())
+
+ self.mock_object._Verify()
+
+ def testIsReprable(self):
+ """Test that MockAnythings can be repr'd without causing a failure."""
+ self.failUnless('MockAnything' in repr(self.mock_object))
+
+
+class MethodCheckerTest(unittest.TestCase):
+ """Tests MockMethod's use of MethodChecker method."""
+
+ def testNoParameters(self):
+ method = mox.MockMethod('NoParameters', [], False,
+ CheckCallTestClass.NoParameters)
+ method()
+ self.assertRaises(AttributeError, method, 1)
+ self.assertRaises(AttributeError, method, 1, 2)
+ self.assertRaises(AttributeError, method, a=1)
+ self.assertRaises(AttributeError, method, 1, b=2)
+
+ def testOneParameter(self):
+ method = mox.MockMethod('OneParameter', [], False,
+ CheckCallTestClass.OneParameter)
+ self.assertRaises(AttributeError, method)
+ method(1)
+ method(a=1)
+ self.assertRaises(AttributeError, method, b=1)
+ self.assertRaises(AttributeError, method, 1, 2)
+ self.assertRaises(AttributeError, method, 1, a=2)
+ self.assertRaises(AttributeError, method, 1, b=2)
+
+ def testTwoParameters(self):
+ method = mox.MockMethod('TwoParameters', [], False,
+ CheckCallTestClass.TwoParameters)
+ self.assertRaises(AttributeError, method)
+ self.assertRaises(AttributeError, method, 1)
+ self.assertRaises(AttributeError, method, a=1)
+ self.assertRaises(AttributeError, method, b=1)
+ method(1, 2)
+ method(1, b=2)
+ method(a=1, b=2)
+ method(b=2, a=1)
+ self.assertRaises(AttributeError, method, b=2, c=3)
+ self.assertRaises(AttributeError, method, a=1, b=2, c=3)
+ self.assertRaises(AttributeError, method, 1, 2, 3)
+ self.assertRaises(AttributeError, method, 1, 2, 3, 4)
+ self.assertRaises(AttributeError, method, 3, a=1, b=2)
+
+ def testOneDefaultValue(self):
+ method = mox.MockMethod('OneDefaultValue', [], False,
+ CheckCallTestClass.OneDefaultValue)
+ method()
+ method(1)
+ method(a=1)
+ self.assertRaises(AttributeError, method, b=1)
+ self.assertRaises(AttributeError, method, 1, 2)
+ self.assertRaises(AttributeError, method, 1, a=2)
+ self.assertRaises(AttributeError, method, 1, b=2)
+
+ def testTwoDefaultValues(self):
+ method = mox.MockMethod('TwoDefaultValues', [], False,
+ CheckCallTestClass.TwoDefaultValues)
+ self.assertRaises(AttributeError, method)
+ self.assertRaises(AttributeError, method, c=3)
+ self.assertRaises(AttributeError, method, 1)
+ self.assertRaises(AttributeError, method, 1, d=4)
+ self.assertRaises(AttributeError, method, 1, d=4, c=3)
+ method(1, 2)
+ method(a=1, b=2)
+ method(1, 2, 3)
+ method(1, 2, 3, 4)
+ method(1, 2, c=3)
+ method(1, 2, c=3, d=4)
+ method(1, 2, d=4, c=3)
+ method(d=4, c=3, a=1, b=2)
+ self.assertRaises(AttributeError, method, 1, 2, 3, 4, 5)
+ self.assertRaises(AttributeError, method, 1, 2, e=9)
+ self.assertRaises(AttributeError, method, a=1, b=2, e=9)
+
+ def testArgs(self):
+ method = mox.MockMethod('Args', [], False, CheckCallTestClass.Args)
+ self.assertRaises(AttributeError, method)
+ self.assertRaises(AttributeError, method, 1)
+ method(1, 2)
+ method(a=1, b=2)
+ method(1, 2, 3)
+ method(1, 2, 3, 4)
+ self.assertRaises(AttributeError, method, 1, 2, a=3)
+ self.assertRaises(AttributeError, method, 1, 2, c=3)
+
+ def testKwargs(self):
+ method = mox.MockMethod('Kwargs', [], False, CheckCallTestClass.Kwargs)
+ self.assertRaises(AttributeError, method)
+ method(1)
+ method(1, 2)
+ method(a=1, b=2)
+ method(b=2, a=1)
+ self.assertRaises(AttributeError, method, 1, 2, 3)
+ self.assertRaises(AttributeError, method, 1, 2, a=3)
+ method(1, 2, c=3)
+ method(a=1, b=2, c=3)
+ method(c=3, a=1, b=2)
+ method(a=1, b=2, c=3, d=4)
+ self.assertRaises(AttributeError, method, 1, 2, 3, 4)
+
+ def testArgsAndKwargs(self):
+ method = mox.MockMethod('ArgsAndKwargs', [], False,
+ CheckCallTestClass.ArgsAndKwargs)
+ self.assertRaises(AttributeError, method)
+ method(1)
+ method(1, 2)
+ method(1, 2, 3)
+ method(a=1)
+ method(1, b=2)
+ self.assertRaises(AttributeError, method, 1, a=2)
+ method(b=2, a=1)
+ method(c=3, b=2, a=1)
+ method(1, 2, c=3)
+
+
+class CheckCallTestClass(object):
+ def NoParameters(self):
+ pass
+
+ def OneParameter(self, a):
+ pass
+
+ def TwoParameters(self, a, b):
+ pass
+
+ def OneDefaultValue(self, a=1):
+ pass
+
+ def TwoDefaultValues(self, a, b, c=1, d=2):
+ pass
+
+ def Args(self, a, b, *args):
+ pass
+
+ def Kwargs(self, a, b=2, **kwargs):
+ pass
+
+ def ArgsAndKwargs(self, a, *args, **kwargs):
+ pass
+
+
+class MockObjectTest(unittest.TestCase):
+ """Verify that the MockObject class works as exepcted."""
+
+ def setUp(self):
+ self.mock_object = mox.MockObject(TestClass)
+
+ def testSetupModeWithValidCall(self):
+ """Verify the mock object properly mocks a basic method call."""
+ self.mock_object.ValidCall()
+ self.assert_(len(self.mock_object._expected_calls_queue) == 1)
+
+ def testSetupModeWithInvalidCall(self):
+ """UnknownMethodCallError should be raised if a non-member method is called.
+ """
+ # Note: assertRaises does not catch exceptions thrown by MockObject's
+ # __getattr__
+ try:
+ self.mock_object.InvalidCall()
+ self.fail("No exception thrown, expected UnknownMethodCallError")
+ except mox.UnknownMethodCallError:
+ pass
+ except Exception:
+ self.fail("Wrong exception type thrown, expected UnknownMethodCallError")
+
+ def testReplayWithInvalidCall(self):
+ """UnknownMethodCallError should be raised if a non-member method is called.
+ """
+ self.mock_object.ValidCall() # setup method call
+ self.mock_object._Replay() # start replay mode
+ # Note: assertRaises does not catch exceptions thrown by MockObject's
+ # __getattr__
+ try:
+ self.mock_object.InvalidCall()
+ self.fail("No exception thrown, expected UnknownMethodCallError")
+ except mox.UnknownMethodCallError:
+ pass
+ except Exception:
+ self.fail("Wrong exception type thrown, expected UnknownMethodCallError")
+
+ def testIsInstance(self):
+ """Mock should be able to pass as an instance of the mocked class."""
+ self.assert_(isinstance(self.mock_object, TestClass))
+
+ def testFindValidMethods(self):
+ """Mock should be able to mock all public methods."""
+ self.assert_('ValidCall' in self.mock_object._known_methods)
+ self.assert_('OtherValidCall' in self.mock_object._known_methods)
+ self.assert_('MyClassMethod' in self.mock_object._known_methods)
+ self.assert_('MyStaticMethod' in self.mock_object._known_methods)
+ self.assert_('_ProtectedCall' in self.mock_object._known_methods)
+ self.assert_('__PrivateCall' not in self.mock_object._known_methods)
+ self.assert_('_TestClass__PrivateCall' in self.mock_object._known_methods)
+
+ def testFindsSuperclassMethods(self):
+ """Mock should be able to mock superclasses methods."""
+ self.mock_object = mox.MockObject(ChildClass)
+ self.assert_('ValidCall' in self.mock_object._known_methods)
+ self.assert_('OtherValidCall' in self.mock_object._known_methods)
+ self.assert_('MyClassMethod' in self.mock_object._known_methods)
+ self.assert_('ChildValidCall' in self.mock_object._known_methods)
+
+ def testAccessClassVariables(self):
+ """Class variables should be accessible through the mock."""
+ self.assert_('SOME_CLASS_VAR' in self.mock_object._known_vars)
+ self.assert_('_PROTECTED_CLASS_VAR' in self.mock_object._known_vars)
+ self.assertEquals('test_value', self.mock_object.SOME_CLASS_VAR)
+
+ def testEquals(self):
+ """A mock should be able to compare itself to another object."""
+ self.mock_object._Replay()
+ self.assertEquals(self.mock_object, self.mock_object)
+
+ def testEqualsMockFailure(self):
+ """Verify equals identifies unequal objects."""
+ self.mock_object.ValidCall()
+ self.mock_object._Replay()
+ self.assertNotEquals(self.mock_object, mox.MockObject(TestClass))
+
+ def testEqualsInstanceFailure(self):
+ """Verify equals identifies that objects are different instances."""
+ self.mock_object._Replay()
+ self.assertNotEquals(self.mock_object, TestClass())
+
+ def testNotEquals(self):
+ """Verify not equals works."""
+ self.mock_object._Replay()
+ self.assertFalse(self.mock_object != self.mock_object)
+
+ def testMockSetItem_ExpectedSetItem_Success(self):
+ """Test that __setitem__() gets mocked in Dummy.
+
+ In this test, _Verify() succeeds.
+ """
+ dummy = mox.MockObject(TestClass)
+ dummy['X'] = 'Y'
+
+ dummy._Replay()
+
+ dummy['X'] = 'Y'
+
+ dummy._Verify()
+
+ def testMockSetItem_ExpectedSetItem_NoSuccess(self):
+ """Test that __setitem__() gets mocked in Dummy.
+
+ In this test, _Verify() fails.
+ """
+ dummy = mox.MockObject(TestClass)
+ dummy['X'] = 'Y'
+
+ dummy._Replay()
+
+ # NOT doing dummy['X'] = 'Y'
+
+ self.assertRaises(mox.ExpectedMethodCallsError, dummy._Verify)
+
+ def testMockSetItem_ExpectedNoSetItem_Success(self):
+ """Test that __setitem__() gets mocked in Dummy."""
+ dummy = mox.MockObject(TestClass)
+ # NOT doing dummy['X'] = 'Y'
+
+ dummy._Replay()
+
+ def call(): dummy['X'] = 'Y'
+ self.assertRaises(mox.UnexpectedMethodCallError, call)
+
+ def testMockSetItem_ExpectedNoSetItem_NoSuccess(self):
+ """Test that __setitem__() gets mocked in Dummy.
+
+ In this test, _Verify() fails.
+ """
+ dummy = mox.MockObject(TestClass)
+ # NOT doing dummy['X'] = 'Y'
+
+ dummy._Replay()
+
+ # NOT doing dummy['X'] = 'Y'
+
+ dummy._Verify()
+
+ def testMockSetItem_ExpectedSetItem_NonmatchingParameters(self):
+ """Test that __setitem__() fails if other parameters are expected."""
+ dummy = mox.MockObject(TestClass)
+ dummy['X'] = 'Y'
+
+ dummy._Replay()
+
+ def call(): dummy['wrong'] = 'Y'
+
+ self.assertRaises(mox.UnexpectedMethodCallError, call)
+
+ dummy._Verify()
+
+ def testMockSetItem_WithSubClassOfNewStyleClass(self):
+ class NewStyleTestClass(object):
+ def __init__(self):
+ self.my_dict = {}
+
+ def __setitem__(self, key, value):
+ self.my_dict[key], value
+
+ class TestSubClass(NewStyleTestClass):
+ pass
+
+ dummy = mox.MockObject(TestSubClass)
+ dummy[1] = 2
+ dummy._Replay()
+ dummy[1] = 2
+ dummy._Verify()
+
+ def testMockGetItem_ExpectedGetItem_Success(self):
+ """Test that __getitem__() gets mocked in Dummy.
+
+ In this test, _Verify() succeeds.
+ """
+ dummy = mox.MockObject(TestClass)
+ dummy['X'].AndReturn('value')
+
+ dummy._Replay()
+
+ self.assertEqual(dummy['X'], 'value')
+
+ dummy._Verify()
+
+ def testMockGetItem_ExpectedGetItem_NoSuccess(self):
+ """Test that __getitem__() gets mocked in Dummy.
+
+ In this test, _Verify() fails.
+ """
+ dummy = mox.MockObject(TestClass)
+ dummy['X'].AndReturn('value')
+
+ dummy._Replay()
+
+ # NOT doing dummy['X']
+
+ self.assertRaises(mox.ExpectedMethodCallsError, dummy._Verify)
+
+ def testMockGetItem_ExpectedNoGetItem_NoSuccess(self):
+ """Test that __getitem__() gets mocked in Dummy."""
+ dummy = mox.MockObject(TestClass)
+ # NOT doing dummy['X']
+
+ dummy._Replay()
+
+ def call(): return dummy['X']
+ self.assertRaises(mox.UnexpectedMethodCallError, call)
+
+ def testMockGetItem_ExpectedGetItem_NonmatchingParameters(self):
+ """Test that __getitem__() fails if other parameters are expected."""
+ dummy = mox.MockObject(TestClass)
+ dummy['X'].AndReturn('value')
+
+ dummy._Replay()
+
+ def call(): return dummy['wrong']
+
+ self.assertRaises(mox.UnexpectedMethodCallError, call)
+
+ dummy._Verify()
+
+ def testMockGetItem_WithSubClassOfNewStyleClass(self):
+ class NewStyleTestClass(object):
+ def __getitem__(self, key):
+ return {1: '1', 2: '2'}[key]
+
+ class TestSubClass(NewStyleTestClass):
+ pass
+
+ dummy = mox.MockObject(TestSubClass)
+ dummy[1].AndReturn('3')
+
+ dummy._Replay()
+ self.assertEquals('3', dummy.__getitem__(1))
+ dummy._Verify()
+
+ def testMockIter_ExpectedIter_Success(self):
+ """Test that __iter__() gets mocked in Dummy.
+
+ In this test, _Verify() succeeds.
+ """
+ dummy = mox.MockObject(TestClass)
+ iter(dummy).AndReturn(iter(['X', 'Y']))
+
+ dummy._Replay()
+
+ self.assertEqual([x for x in dummy], ['X', 'Y'])
+
+ dummy._Verify()
+
+ def testMockContains_ExpectedContains_Success(self):
+ """Test that __contains__ gets mocked in Dummy.
+
+ In this test, _Verify() succeeds.
+ """
+ dummy = mox.MockObject(TestClass)
+ dummy.__contains__('X').AndReturn(True)
+
+ dummy._Replay()
+
+ self.failUnless('X' in dummy)
+
+ dummy._Verify()
+
+ def testMockContains_ExpectedContains_NoSuccess(self):
+ """Test that __contains__() gets mocked in Dummy.
+
+ In this test, _Verify() fails.
+ """
+ dummy = mox.MockObject(TestClass)
+ dummy.__contains__('X').AndReturn('True')
+
+ dummy._Replay()
+
+ # NOT doing 'X' in dummy
+
+ self.assertRaises(mox.ExpectedMethodCallsError, dummy._Verify)
+
+ def testMockContains_ExpectedContains_NonmatchingParameter(self):
+ """Test that __contains__ fails if other parameters are expected."""
+ dummy = mox.MockObject(TestClass)
+ dummy.__contains__('X').AndReturn(True)
+
+ dummy._Replay()
+
+ def call(): return 'Y' in dummy
+
+ self.assertRaises(mox.UnexpectedMethodCallError, call)
+
+ dummy._Verify()
+
+ def testMockIter_ExpectedIter_NoSuccess(self):
+ """Test that __iter__() gets mocked in Dummy.
+
+ In this test, _Verify() fails.
+ """
+ dummy = mox.MockObject(TestClass)
+ iter(dummy).AndReturn(iter(['X', 'Y']))
+
+ dummy._Replay()
+
+ # NOT doing self.assertEqual([x for x in dummy], ['X', 'Y'])
+
+ self.assertRaises(mox.ExpectedMethodCallsError, dummy._Verify)
+
+ def testMockIter_ExpectedNoIter_NoSuccess(self):
+ """Test that __iter__() gets mocked in Dummy."""
+ dummy = mox.MockObject(TestClass)
+ # NOT doing iter(dummy)
+
+ dummy._Replay()
+
+ def call(): return [x for x in dummy]
+ self.assertRaises(mox.UnexpectedMethodCallError, call)
+
+ def testMockIter_ExpectedGetItem_Success(self):
+ """Test that __iter__() gets mocked in Dummy using getitem."""
+ dummy = mox.MockObject(SubscribtableNonIterableClass)
+ dummy[0].AndReturn('a')
+ dummy[1].AndReturn('b')
+ dummy[2].AndRaise(IndexError)
+
+ dummy._Replay()
+ self.assertEquals(['a', 'b'], [x for x in dummy])
+ dummy._Verify()
+
+ def testMockIter_ExpectedNoGetItem_NoSuccess(self):
+ """Test that __iter__() gets mocked in Dummy using getitem."""
+ dummy = mox.MockObject(SubscribtableNonIterableClass)
+ # NOT doing dummy[index]
+
+ dummy._Replay()
+ function = lambda: [x for x in dummy]
+ self.assertRaises(mox.UnexpectedMethodCallError, function)
+
+ def testMockGetIter_WithSubClassOfNewStyleClass(self):
+ class NewStyleTestClass(object):
+ def __iter__(self):
+ return iter([1, 2, 3])
+
+ class TestSubClass(NewStyleTestClass):
+ pass
+
+ dummy = mox.MockObject(TestSubClass)
+ iter(dummy).AndReturn(iter(['a', 'b']))
+ dummy._Replay()
+ self.assertEquals(['a', 'b'], [x for x in dummy])
+ dummy._Verify()
+
+ def testInstantiationWithAdditionalAttributes(self):
+ mock_object = mox.MockObject(TestClass, attrs={"attr1": "value"})
+ self.assertEquals(mock_object.attr1, "value")
+
+ def testCantOverrideMethodsWithAttributes(self):
+ self.assertRaises(ValueError, mox.MockObject, TestClass,
+ attrs={"ValidCall": "value"})
+
+ def testCantMockNonPublicAttributes(self):
+ self.assertRaises(mox.PrivateAttributeError, mox.MockObject, TestClass,
+ attrs={"_protected": "value"})
+ self.assertRaises(mox.PrivateAttributeError, mox.MockObject, TestClass,
+ attrs={"__private": "value"})
+
+
+class MoxTest(unittest.TestCase):
+ """Verify Mox works correctly."""
+
+ def setUp(self):
+ self.mox = mox.Mox()
+
+ def testCreateObject(self):
+ """Mox should create a mock object."""
+ mock_obj = self.mox.CreateMock(TestClass)
+
+ def testVerifyObjectWithCompleteReplay(self):
+ """Mox should replay and verify all objects it created."""
+ mock_obj = self.mox.CreateMock(TestClass)
+ mock_obj.ValidCall()
+ mock_obj.ValidCallWithArgs(mox.IsA(TestClass))
+ self.mox.ReplayAll()
+ mock_obj.ValidCall()
+ mock_obj.ValidCallWithArgs(TestClass("some_value"))
+ self.mox.VerifyAll()
+
+ def testVerifyObjectWithIncompleteReplay(self):
+ """Mox should raise an exception if a mock didn't replay completely."""
+ mock_obj = self.mox.CreateMock(TestClass)
+ mock_obj.ValidCall()
+ self.mox.ReplayAll()
+ # ValidCall() is never made
+ self.assertRaises(mox.ExpectedMethodCallsError, self.mox.VerifyAll)
+
+ def testEntireWorkflow(self):
+ """Test the whole work flow."""
+ mock_obj = self.mox.CreateMock(TestClass)
+ mock_obj.ValidCall().AndReturn("yes")
+ self.mox.ReplayAll()
+
+ ret_val = mock_obj.ValidCall()
+ self.assertEquals("yes", ret_val)
+ self.mox.VerifyAll()
+
+ def testCallableObject(self):
+ """Test recording calls to a callable object works."""
+ mock_obj = self.mox.CreateMock(CallableClass)
+ mock_obj("foo").AndReturn("qux")
+ self.mox.ReplayAll()
+
+ ret_val = mock_obj("foo")
+ self.assertEquals("qux", ret_val)
+ self.mox.VerifyAll()
+
+ def testInheritedCallableObject(self):
+ """Test recording calls to an object inheriting from a callable object."""
+ mock_obj = self.mox.CreateMock(InheritsFromCallable)
+ mock_obj("foo").AndReturn("qux")
+ self.mox.ReplayAll()
+
+ ret_val = mock_obj("foo")
+ self.assertEquals("qux", ret_val)
+ self.mox.VerifyAll()
+
+ def testCallOnNonCallableObject(self):
+ """Test that you cannot call a non-callable object."""
+ mock_obj = self.mox.CreateMock(TestClass)
+ self.assertRaises(TypeError, mock_obj)
+
+ def testCallableObjectWithBadCall(self):
+ """Test verifying calls to a callable object works."""
+ mock_obj = self.mox.CreateMock(CallableClass)
+ mock_obj("foo").AndReturn("qux")
+ self.mox.ReplayAll()
+
+ self.assertRaises(mox.UnexpectedMethodCallError, mock_obj, "ZOOBAZ")
+
+ def testUnorderedGroup(self):
+ """Test that using one unordered group works."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Method(1).InAnyOrder()
+ mock_obj.Method(2).InAnyOrder()
+ self.mox.ReplayAll()
+
+ mock_obj.Method(2)
+ mock_obj.Method(1)
+
+ self.mox.VerifyAll()
+
+ def testUnorderedGroupsInline(self):
+ """Unordered groups should work in the context of ordered calls."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open()
+ mock_obj.Method(1).InAnyOrder()
+ mock_obj.Method(2).InAnyOrder()
+ mock_obj.Close()
+ self.mox.ReplayAll()
+
+ mock_obj.Open()
+ mock_obj.Method(2)
+ mock_obj.Method(1)
+ mock_obj.Close()
+
+ self.mox.VerifyAll()
+
+ def testMultipleUnorderdGroups(self):
+ """Multiple unoreded groups should work."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Method(1).InAnyOrder()
+ mock_obj.Method(2).InAnyOrder()
+ mock_obj.Foo().InAnyOrder('group2')
+ mock_obj.Bar().InAnyOrder('group2')
+ self.mox.ReplayAll()
+
+ mock_obj.Method(2)
+ mock_obj.Method(1)
+ mock_obj.Bar()
+ mock_obj.Foo()
+
+ self.mox.VerifyAll()
+
+ def testMultipleUnorderdGroupsOutOfOrder(self):
+ """Multiple unordered groups should maintain external order"""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Method(1).InAnyOrder()
+ mock_obj.Method(2).InAnyOrder()
+ mock_obj.Foo().InAnyOrder('group2')
+ mock_obj.Bar().InAnyOrder('group2')
+ self.mox.ReplayAll()
+
+ mock_obj.Method(2)
+ self.assertRaises(mox.UnexpectedMethodCallError, mock_obj.Bar)
+
+ def testUnorderedGroupWithReturnValue(self):
+ """Unordered groups should work with return values."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open()
+ mock_obj.Method(1).InAnyOrder().AndReturn(9)
+ mock_obj.Method(2).InAnyOrder().AndReturn(10)
+ mock_obj.Close()
+ self.mox.ReplayAll()
+
+ mock_obj.Open()
+ actual_two = mock_obj.Method(2)
+ actual_one = mock_obj.Method(1)
+ mock_obj.Close()
+
+ self.assertEquals(9, actual_one)
+ self.assertEquals(10, actual_two)
+
+ self.mox.VerifyAll()
+
+ def testUnorderedGroupWithComparator(self):
+ """Unordered groups should work with comparators"""
+
+ def VerifyOne(cmd):
+ if not isinstance(cmd, str):
+ self.fail('Unexpected type passed to comparator: ' + str(cmd))
+ return cmd == 'test'
+
+ def VerifyTwo(cmd):
+ return True
+
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Foo(['test'], mox.Func(VerifyOne), bar=1).InAnyOrder().\
+ AndReturn('yes test')
+ mock_obj.Foo(['test'], mox.Func(VerifyTwo), bar=1).InAnyOrder().\
+ AndReturn('anything')
+
+ self.mox.ReplayAll()
+
+ mock_obj.Foo(['test'], 'anything', bar=1)
+ mock_obj.Foo(['test'], 'test', bar=1)
+
+ self.mox.VerifyAll()
+
+ def testMultipleTimes(self):
+ """Test if MultipleTimesGroup works."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Method(1).MultipleTimes().AndReturn(9)
+ mock_obj.Method(2).AndReturn(10)
+ mock_obj.Method(3).MultipleTimes().AndReturn(42)
+ self.mox.ReplayAll()
+
+ actual_one = mock_obj.Method(1)
+ second_one = mock_obj.Method(1) # This tests MultipleTimes.
+ actual_two = mock_obj.Method(2)
+ actual_three = mock_obj.Method(3)
+ mock_obj.Method(3)
+ mock_obj.Method(3)
+
+ self.mox.VerifyAll()
+
+ self.assertEquals(9, actual_one)
+ self.assertEquals(9, second_one) # Repeated calls should return same number.
+ self.assertEquals(10, actual_two)
+ self.assertEquals(42, actual_three)
+
+ def testMultipleTimesUsingIsAParameter(self):
+ """Test if MultipleTimesGroup works with a IsA parameter."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open()
+ mock_obj.Method(mox.IsA(str)).MultipleTimes("IsA").AndReturn(9)
+ mock_obj.Close()
+ self.mox.ReplayAll()
+
+ mock_obj.Open()
+ actual_one = mock_obj.Method("1")
+ second_one = mock_obj.Method("2") # This tests MultipleTimes.
+ mock_obj.Close()
+
+ self.mox.VerifyAll()
+
+ self.assertEquals(9, actual_one)
+ self.assertEquals(9, second_one) # Repeated calls should return same number.
+
+ def testMutlipleTimesUsingFunc(self):
+ """Test that the Func is not evaluated more times than necessary.
+
+ If a Func() has side effects, it can cause a passing test to fail.
+ """
+
+ self.counter = 0
+ def MyFunc(actual_str):
+ """Increment the counter if actual_str == 'foo'."""
+ if actual_str == 'foo':
+ self.counter += 1
+ return True
+
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open()
+ mock_obj.Method(mox.Func(MyFunc)).MultipleTimes()
+ mock_obj.Close()
+ self.mox.ReplayAll()
+
+ mock_obj.Open()
+ mock_obj.Method('foo')
+ mock_obj.Method('foo')
+ mock_obj.Method('not-foo')
+ mock_obj.Close()
+
+ self.mox.VerifyAll()
+
+ self.assertEquals(2, self.counter)
+
+ def testMultipleTimesThreeMethods(self):
+ """Test if MultipleTimesGroup works with three or more methods."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open()
+ mock_obj.Method(1).MultipleTimes().AndReturn(9)
+ mock_obj.Method(2).MultipleTimes().AndReturn(8)
+ mock_obj.Method(3).MultipleTimes().AndReturn(7)
+ mock_obj.Method(4).AndReturn(10)
+ mock_obj.Close()
+ self.mox.ReplayAll()
+
+ mock_obj.Open()
+ actual_three = mock_obj.Method(3)
+ mock_obj.Method(1)
+ actual_two = mock_obj.Method(2)
+ mock_obj.Method(3)
+ actual_one = mock_obj.Method(1)
+ actual_four = mock_obj.Method(4)
+ mock_obj.Close()
+
+ self.assertEquals(9, actual_one)
+ self.assertEquals(8, actual_two)
+ self.assertEquals(7, actual_three)
+ self.assertEquals(10, actual_four)
+
+ self.mox.VerifyAll()
+
+ def testMultipleTimesMissingOne(self):
+ """Test if MultipleTimesGroup fails if one method is missing."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open()
+ mock_obj.Method(1).MultipleTimes().AndReturn(9)
+ mock_obj.Method(2).MultipleTimes().AndReturn(8)
+ mock_obj.Method(3).MultipleTimes().AndReturn(7)
+ mock_obj.Method(4).AndReturn(10)
+ mock_obj.Close()
+ self.mox.ReplayAll()
+
+ mock_obj.Open()
+ mock_obj.Method(3)
+ mock_obj.Method(2)
+ mock_obj.Method(3)
+ mock_obj.Method(3)
+ mock_obj.Method(2)
+
+ self.assertRaises(mox.UnexpectedMethodCallError, mock_obj.Method, 4)
+
+ def testMultipleTimesTwoGroups(self):
+ """Test if MultipleTimesGroup works with a group after a
+ MultipleTimesGroup.
+ """
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open()
+ mock_obj.Method(1).MultipleTimes().AndReturn(9)
+ mock_obj.Method(3).MultipleTimes("nr2").AndReturn(42)
+ mock_obj.Close()
+ self.mox.ReplayAll()
+
+ mock_obj.Open()
+ actual_one = mock_obj.Method(1)
+ mock_obj.Method(1)
+ actual_three = mock_obj.Method(3)
+ mock_obj.Method(3)
+ mock_obj.Close()
+
+ self.assertEquals(9, actual_one)
+ self.assertEquals(42, actual_three)
+
+ self.mox.VerifyAll()
+
+ def testMultipleTimesTwoGroupsFailure(self):
+ """Test if MultipleTimesGroup fails with a group after a
+ MultipleTimesGroup.
+ """
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open()
+ mock_obj.Method(1).MultipleTimes().AndReturn(9)
+ mock_obj.Method(3).MultipleTimes("nr2").AndReturn(42)
+ mock_obj.Close()
+ self.mox.ReplayAll()
+
+ mock_obj.Open()
+ actual_one = mock_obj.Method(1)
+ mock_obj.Method(1)
+ actual_three = mock_obj.Method(3)
+
+ self.assertRaises(mox.UnexpectedMethodCallError, mock_obj.Method, 1)
+
+ def testWithSideEffects(self):
+ """Test side effect operations actually modify their target objects."""
+ def modifier(mutable_list):
+ mutable_list[0] = 'mutated'
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.ConfigureInOutParameter(['original']).WithSideEffects(modifier)
+ mock_obj.WorkWithParameter(['mutated'])
+ self.mox.ReplayAll()
+
+ local_list = ['original']
+ mock_obj.ConfigureInOutParameter(local_list)
+ mock_obj.WorkWithParameter(local_list)
+
+ self.mox.VerifyAll()
+
+ def testWithSideEffectsException(self):
+ """Test side effect operations actually modify their target objects."""
+ def modifier(mutable_list):
+ mutable_list[0] = 'mutated'
+ mock_obj = self.mox.CreateMockAnything()
+ method = mock_obj.ConfigureInOutParameter(['original'])
+ method.WithSideEffects(modifier).AndRaise(Exception('exception'))
+ mock_obj.WorkWithParameter(['mutated'])
+ self.mox.ReplayAll()
+
+ local_list = ['original']
+ self.failUnlessRaises(Exception,
+ mock_obj.ConfigureInOutParameter,
+ local_list)
+ mock_obj.WorkWithParameter(local_list)
+
+ self.mox.VerifyAll()
+
+ def testStubOutMethod(self):
+ """Test that a method is replaced with a MockAnything."""
+ test_obj = TestClass()
+ # Replace OtherValidCall with a mock.
+ self.mox.StubOutWithMock(test_obj, 'OtherValidCall')
+ self.assert_(isinstance(test_obj.OtherValidCall, mox.MockAnything))
+ test_obj.OtherValidCall().AndReturn('foo')
+ self.mox.ReplayAll()
+
+ actual = test_obj.OtherValidCall()
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+ self.assertEquals('foo', actual)
+ self.failIf(isinstance(test_obj.OtherValidCall, mox.MockAnything))
+
+ def testStubOutClass(self):
+ """Test a mocked class whose __init__ returns a Mock."""
+ self.mox.StubOutWithMock(mox_test_helper, 'TestClassFromAnotherModule')
+ self.assert_(isinstance(mox_test_helper.TestClassFromAnotherModule,
+ mox.MockObject))
+
+ mock_instance = self.mox.CreateMock(
+ mox_test_helper.TestClassFromAnotherModule)
+ mox_test_helper.TestClassFromAnotherModule().AndReturn(mock_instance)
+ mock_instance.Value().AndReturn('mock instance')
+
+ self.mox.ReplayAll()
+
+ a_mock = mox_test_helper.TestClassFromAnotherModule()
+ actual = a_mock.Value()
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+ self.assertEquals('mock instance', actual)
+
+ def testWarnsUserIfMockingMock(self):
+ """Test that user is warned if they try to stub out a MockAnything."""
+ self.mox.StubOutWithMock(TestClass, 'MyStaticMethod')
+ self.assertRaises(TypeError, self.mox.StubOutWithMock, TestClass,
+ 'MyStaticMethod')
+
+ def testStubOutObject(self):
+ """Test than object is replaced with a Mock."""
+
+ class Foo(object):
+ def __init__(self):
+ self.obj = TestClass()
+
+ foo = Foo()
+ self.mox.StubOutWithMock(foo, "obj")
+ self.assert_(isinstance(foo.obj, mox.MockObject))
+ foo.obj.ValidCall()
+ self.mox.ReplayAll()
+
+ foo.obj.ValidCall()
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+ self.failIf(isinstance(foo.obj, mox.MockObject))
+
+ def testForgotReplayHelpfulMessage(self):
+ """If there is an AttributeError on a MockMethod, give users a helpful msg.
+ """
+ foo = self.mox.CreateMockAnything()
+ bar = self.mox.CreateMockAnything()
+ foo.GetBar().AndReturn(bar)
+ bar.ShowMeTheMoney()
+ # Forgot to replay!
+ try:
+ foo.GetBar().ShowMeTheMoney()
+ except AttributeError, e:
+ self.assertEquals('MockMethod has no attribute "ShowMeTheMoney". '
+ 'Did you remember to put your mocks in replay mode?', str(e))
+
+
+class ReplayTest(unittest.TestCase):
+ """Verify Replay works properly."""
+
+ def testReplay(self):
+ """Replay should put objects into replay mode."""
+ mock_obj = mox.MockObject(TestClass)
+ self.assertFalse(mock_obj._replay_mode)
+ mox.Replay(mock_obj)
+ self.assertTrue(mock_obj._replay_mode)
+
+
+class MoxTestBaseTest(unittest.TestCase):
+ """Verify that all tests in a class derived from MoxTestBase are wrapped."""
+
+ def setUp(self):
+ self.mox = mox.Mox()
+ self.test_mox = mox.Mox()
+ self.test_stubs = mox.stubout.StubOutForTesting()
+ self.result = unittest.TestResult()
+
+ def tearDown(self):
+ self.mox.UnsetStubs()
+ self.test_mox.UnsetStubs()
+ self.test_stubs.UnsetAll()
+ self.test_stubs.SmartUnsetAll()
+
+ def _setUpTestClass(self):
+ """Replacement for setUp in the test class instance.
+
+ Assigns a mox.Mox instance as the mox attribute of the test class instance.
+ This replacement Mox instance is under our control before setUp is called
+ in the test class instance.
+ """
+ self.test.mox = self.test_mox
+ self.test.stubs = self.test_stubs
+
+ def _CreateTest(self, test_name):
+ """Create a test from our example mox class.
+
+ The created test instance is assigned to this instances test attribute.
+ """
+ self.test = mox_test_helper.ExampleMoxTest(test_name)
+ self.mox.stubs.Set(self.test, 'setUp', self._setUpTestClass)
+
+ def _VerifySuccess(self):
+ """Run the checks to confirm test method completed successfully."""
+ self.mox.StubOutWithMock(self.test_mox, 'UnsetStubs')
+ self.mox.StubOutWithMock(self.test_mox, 'VerifyAll')
+ self.mox.StubOutWithMock(self.test_stubs, 'UnsetAll')
+ self.mox.StubOutWithMock(self.test_stubs, 'SmartUnsetAll')
+ self.test_mox.UnsetStubs()
+ self.test_mox.VerifyAll()
+ self.test_stubs.UnsetAll()
+ self.test_stubs.SmartUnsetAll()
+ self.mox.ReplayAll()
+ self.test.run(result=self.result)
+ self.assertTrue(self.result.wasSuccessful())
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs() # Needed to call the real VerifyAll() below.
+ self.test_mox.VerifyAll()
+
+ def testSuccess(self):
+ """Successful test method execution test."""
+ self._CreateTest('testSuccess')
+ self._VerifySuccess()
+
+ def testSuccessNoMocks(self):
+ """Let testSuccess() unset all the mocks, and verify they've been unset."""
+ self._CreateTest('testSuccess')
+ self.test.run(result=self.result)
+ self.assertTrue(self.result.wasSuccessful())
+ self.assertEqual(OS_LISTDIR, mox_test_helper.os.listdir)
+
+ def testStubs(self):
+ """Test that "self.stubs" is provided as is useful."""
+ self._CreateTest('testHasStubs')
+ self._VerifySuccess()
+
+ def testStubsNoMocks(self):
+ """Let testHasStubs() unset the stubs by itself."""
+ self._CreateTest('testHasStubs')
+ self.test.run(result=self.result)
+ self.assertTrue(self.result.wasSuccessful())
+ self.assertEqual(OS_LISTDIR, mox_test_helper.os.listdir)
+
+ def testExpectedNotCalled(self):
+ """Stubbed out method is not called."""
+ self._CreateTest('testExpectedNotCalled')
+ self.mox.StubOutWithMock(self.test_mox, 'UnsetStubs')
+ self.mox.StubOutWithMock(self.test_stubs, 'UnsetAll')
+ self.mox.StubOutWithMock(self.test_stubs, 'SmartUnsetAll')
+ # Don't stub out VerifyAll - that's what causes the test to fail
+ self.test_mox.UnsetStubs()
+ self.test_stubs.UnsetAll()
+ self.test_stubs.SmartUnsetAll()
+ self.mox.ReplayAll()
+ self.test.run(result=self.result)
+ self.failIf(self.result.wasSuccessful())
+ self.mox.VerifyAll()
+
+ def testExpectedNotCalledNoMocks(self):
+ """Let testExpectedNotCalled() unset all the mocks by itself."""
+ self._CreateTest('testExpectedNotCalled')
+ self.test.run(result=self.result)
+ self.failIf(self.result.wasSuccessful())
+ self.assertEqual(OS_LISTDIR, mox_test_helper.os.listdir)
+
+ def testUnexpectedCall(self):
+ """Stubbed out method is called with unexpected arguments."""
+ self._CreateTest('testUnexpectedCall')
+ self.mox.StubOutWithMock(self.test_mox, 'UnsetStubs')
+ self.mox.StubOutWithMock(self.test_stubs, 'UnsetAll')
+ self.mox.StubOutWithMock(self.test_stubs, 'SmartUnsetAll')
+ # Ensure no calls are made to VerifyAll()
+ self.mox.StubOutWithMock(self.test_mox, 'VerifyAll')
+ self.test_mox.UnsetStubs()
+ self.test_stubs.UnsetAll()
+ self.test_stubs.SmartUnsetAll()
+ self.mox.ReplayAll()
+ self.test.run(result=self.result)
+ self.failIf(self.result.wasSuccessful())
+ self.mox.VerifyAll()
+
+ def testFailure(self):
+ """Failing assertion in test method."""
+ self._CreateTest('testFailure')
+ self.mox.StubOutWithMock(self.test_mox, 'UnsetStubs')
+ self.mox.StubOutWithMock(self.test_stubs, 'UnsetAll')
+ self.mox.StubOutWithMock(self.test_stubs, 'SmartUnsetAll')
+ # Ensure no calls are made to VerifyAll()
+ self.mox.StubOutWithMock(self.test_mox, 'VerifyAll')
+ self.test_mox.UnsetStubs()
+ self.test_stubs.UnsetAll()
+ self.test_stubs.SmartUnsetAll()
+ self.mox.ReplayAll()
+ self.test.run(result=self.result)
+ self.failIf(self.result.wasSuccessful())
+ self.mox.VerifyAll()
+
+ def testMixin(self):
+ """Run test from mix-in test class, ensure it passes."""
+ self._CreateTest('testStat')
+ self._VerifySuccess()
+
+ def testMixinAgain(self):
+ """Run same test as above but from the current test class.
+
+ This ensures metaclass properly wrapped test methods from all base classes.
+ If unsetting of stubs doesn't happen, this will fail.
+ """
+ self._CreateTest('testStatOther')
+ self._VerifySuccess()
+
+
+class VerifyTest(unittest.TestCase):
+ """Verify Verify works properly."""
+
+ def testVerify(self):
+ """Verify should be called for all objects.
+
+ This should throw an exception because the expected behavior did not occur.
+ """
+ mock_obj = mox.MockObject(TestClass)
+ mock_obj.ValidCall()
+ mock_obj._Replay()
+ self.assertRaises(mox.ExpectedMethodCallsError, mox.Verify, mock_obj)
+
+
+class ResetTest(unittest.TestCase):
+ """Verify Reset works properly."""
+
+ def testReset(self):
+ """Should empty all queues and put mocks in record mode."""
+ mock_obj = mox.MockObject(TestClass)
+ mock_obj.ValidCall()
+ self.assertFalse(mock_obj._replay_mode)
+ mock_obj._Replay()
+ self.assertTrue(mock_obj._replay_mode)
+ self.assertEquals(1, len(mock_obj._expected_calls_queue))
+
+ mox.Reset(mock_obj)
+ self.assertFalse(mock_obj._replay_mode)
+ self.assertEquals(0, len(mock_obj._expected_calls_queue))
+
+
+class MyTestCase(unittest.TestCase):
+ """Simulate the use of a fake wrapper around Python's unittest library."""
+
+ def setUp(self):
+ super(MyTestCase, self).setUp()
+ self.critical_variable = 42
+ self.another_critical_variable = 42
+
+ def testMethodOverride(self):
+ """Should be properly overriden in a derived class."""
+ self.assertEquals(42, self.another_critical_variable)
+ self.another_critical_variable += 1
+
+
+class MoxTestBaseMultipleInheritanceTest(mox.MoxTestBase, MyTestCase):
+ """Test that multiple inheritance can be used with MoxTestBase."""
+
+ def setUp(self):
+ super(MoxTestBaseMultipleInheritanceTest, self).setUp()
+ self.another_critical_variable = 99
+
+ def testMultipleInheritance(self):
+ """Should be able to access members created by all parent setUp()."""
+ self.assert_(isinstance(self.mox, mox.Mox))
+ self.assertEquals(42, self.critical_variable)
+
+ def testMethodOverride(self):
+ """Should run before MyTestCase.testMethodOverride."""
+ self.assertEquals(99, self.another_critical_variable)
+ self.another_critical_variable = 42
+ super(MoxTestBaseMultipleInheritanceTest, self).testMethodOverride()
+ self.assertEquals(43, self.another_critical_variable)
+
+
+class TestClass:
+ """This class is used only for testing the mock framework"""
+
+ SOME_CLASS_VAR = "test_value"
+ _PROTECTED_CLASS_VAR = "protected value"
+
+ def __init__(self, ivar=None):
+ self.__ivar = ivar
+
+ def __eq__(self, rhs):
+ return self.__ivar == rhs
+
+ def __ne__(self, rhs):
+ return not self.__eq__(rhs)
+
+ def ValidCall(self):
+ pass
+
+ def OtherValidCall(self):
+ pass
+
+ def ValidCallWithArgs(self, *args, **kwargs):
+ pass
+
+ @classmethod
+ def MyClassMethod(cls):
+ pass
+
+ @staticmethod
+ def MyStaticMethod():
+ pass
+
+ def _ProtectedCall(self):
+ pass
+
+ def __PrivateCall(self):
+ pass
+
+ def __getitem__(self, key):
+ pass
+
+ def __DoNotMock(self):
+ pass
+
+ def __getitem__(self, key):
+ """Return the value for key."""
+ return self.d[key]
+
+ def __setitem__(self, key, value):
+ """Set the value for key to value."""
+ self.d[key] = value
+
+ def __contains__(self, key):
+ """Returns True if d contains the key."""
+ return key in self.d
+
+ def __iter__(self):
+ pass
+
+class ChildClass(TestClass):
+ """This inherits from TestClass."""
+ def __init__(self):
+ TestClass.__init__(self)
+
+ def ChildValidCall(self):
+ pass
+
+
+class CallableClass(object):
+ """This class is callable, and that should be mockable!"""
+
+ def __init__(self):
+ pass
+
+ def __call__(self, param):
+ return param
+
+
+class SubscribtableNonIterableClass(object):
+ def __getitem__(self, index):
+ raise IndexError
+
+
+class InheritsFromCallable(CallableClass):
+ """This class should also be mockable; it inherits from a callable class."""
+
+ pass
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/vendor/pymox/mox_test_helper.py b/vendor/pymox/mox_test_helper.py
new file mode 100755
index 0000000000..b4bfdec6c9
--- /dev/null
+++ b/vendor/pymox/mox_test_helper.py
@@ -0,0 +1,95 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""A very basic test class derived from mox.MoxTestBase, used by mox_test.py.
+
+The class defined in this module is used to test the features of
+MoxTestBase and is not intended to be a standalone test. It needs to
+be in a separate module, because otherwise the tests in this class
+(which should not all pass) would be executed as part of the
+mox_test.py test suite.
+
+See mox_test.MoxTestBaseTest for how this class is actually used.
+"""
+
+import os
+
+import mox
+
+class ExampleMoxTestMixin(object):
+ """Mix-in class for mox test case class.
+
+ It stubs out the same function as one of the test methods in
+ the example test case. Both tests must pass as meta class wraps
+ test methods in all base classes.
+ """
+
+ def testStat(self):
+ self.mox.StubOutWithMock(os, 'stat')
+ os.stat(self.DIR_PATH)
+ self.mox.ReplayAll()
+ os.stat(self.DIR_PATH)
+
+
+class ExampleMoxTest(mox.MoxTestBase, ExampleMoxTestMixin):
+
+ DIR_PATH = '/path/to/some/directory'
+
+ def testSuccess(self):
+ self.mox.StubOutWithMock(os, 'listdir')
+ os.listdir(self.DIR_PATH)
+ self.mox.ReplayAll()
+ os.listdir(self.DIR_PATH)
+
+ def testExpectedNotCalled(self):
+ self.mox.StubOutWithMock(os, 'listdir')
+ os.listdir(self.DIR_PATH)
+ self.mox.ReplayAll()
+
+ def testUnexpectedCall(self):
+ self.mox.StubOutWithMock(os, 'listdir')
+ os.listdir(self.DIR_PATH)
+ self.mox.ReplayAll()
+ os.listdir('/path/to/some/other/directory')
+ os.listdir(self.DIR_PATH)
+
+ def testFailure(self):
+ self.assertTrue(False)
+
+ def testStatOther(self):
+ self.mox.StubOutWithMock(os, 'stat')
+ os.stat(self.DIR_PATH)
+ self.mox.ReplayAll()
+ os.stat(self.DIR_PATH)
+
+ def testHasStubs(self):
+ listdir_list = []
+
+ def MockListdir(directory):
+ listdir_list.append(directory)
+
+ self.stubs.Set(os, 'listdir', MockListdir)
+ os.listdir(self.DIR_PATH)
+ self.assertEqual([self.DIR_PATH], listdir_list)
+
+
+class TestClassFromAnotherModule(object):
+
+ def __init__():
+ return None
+
+ def Value():
+ return "Not mock"
diff --git a/vendor/pymox/setup.py b/vendor/pymox/setup.py
new file mode 100755
index 0000000000..0a981ad2d2
--- /dev/null
+++ b/vendor/pymox/setup.py
@@ -0,0 +1,14 @@
+#!/usr/bin/python2.4
+from distutils.core import setup
+
+setup(name='mox',
+ version='0.5.2',
+ py_modules=['mox', 'stubout'],
+ url='http://code.google.com/p/pymox/',
+ maintainer='pymox maintainers',
+ maintainer_email='mox-discuss@googlegroups.com',
+ license='Apache License, Version 2.0',
+ description='Mock object framework',
+ long_description='''Mox is a mock object framework for Python based on the
+Java mock object framework EasyMock.''',
+ )
diff --git a/vendor/pymox/stubout.py b/vendor/pymox/stubout.py
new file mode 100644
index 0000000000..a45224158e
--- /dev/null
+++ b/vendor/pymox/stubout.py
@@ -0,0 +1,142 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import inspect
+
+class StubOutForTesting:
+ """Sample Usage:
+ You want os.path.exists() to always return true during testing.
+
+ stubs = StubOutForTesting()
+ stubs.Set(os.path, 'exists', lambda x: 1)
+ ...
+ stubs.UnsetAll()
+
+ The above changes os.path.exists into a lambda that returns 1. Once
+ the ... part of the code finishes, the UnsetAll() looks up the old value
+ of os.path.exists and restores it.
+
+ """
+ def __init__(self):
+ self.cache = []
+ self.stubs = []
+
+ def __del__(self):
+ self.SmartUnsetAll()
+ self.UnsetAll()
+
+ def SmartSet(self, obj, attr_name, new_attr):
+ """Replace obj.attr_name with new_attr. This method is smart and works
+ at the module, class, and instance level while preserving proper
+ inheritance. It will not stub out C types however unless that has been
+ explicitly allowed by the type.
+
+ This method supports the case where attr_name is a staticmethod or a
+ classmethod of obj.
+
+ Notes:
+ - If obj is an instance, then it is its class that will actually be
+ stubbed. Note that the method Set() does not do that: if obj is
+ an instance, it (and not its class) will be stubbed.
+ - The stubbing is using the builtin getattr and setattr. So, the __get__
+ and __set__ will be called when stubbing (TODO: A better idea would
+ probably be to manipulate obj.__dict__ instead of getattr() and
+ setattr()).
+
+ Raises AttributeError if the attribute cannot be found.
+ """
+ if (inspect.ismodule(obj) or
+ (not inspect.isclass(obj) and obj.__dict__.has_key(attr_name))):
+ orig_obj = obj
+ orig_attr = getattr(obj, attr_name)
+
+ else:
+ if not inspect.isclass(obj):
+ mro = list(inspect.getmro(obj.__class__))
+ else:
+ mro = list(inspect.getmro(obj))
+
+ mro.reverse()
+
+ orig_attr = None
+
+ for cls in mro:
+ try:
+ orig_obj = cls
+ orig_attr = getattr(obj, attr_name)
+ except AttributeError:
+ continue
+
+ if orig_attr is None:
+ raise AttributeError("Attribute not found.")
+
+ # Calling getattr() on a staticmethod transforms it to a 'normal' function.
+ # We need to ensure that we put it back as a staticmethod.
+ old_attribute = obj.__dict__.get(attr_name)
+ if old_attribute is not None and isinstance(old_attribute, staticmethod):
+ orig_attr = staticmethod(orig_attr)
+
+ self.stubs.append((orig_obj, attr_name, orig_attr))
+ setattr(orig_obj, attr_name, new_attr)
+
+ def SmartUnsetAll(self):
+ """Reverses all the SmartSet() calls, restoring things to their original
+ definition. Its okay to call SmartUnsetAll() repeatedly, as later calls
+ have no effect if no SmartSet() calls have been made.
+
+ """
+ self.stubs.reverse()
+
+ for args in self.stubs:
+ setattr(*args)
+
+ self.stubs = []
+
+ def Set(self, parent, child_name, new_child):
+ """Replace child_name's old definition with new_child, in the context
+ of the given parent. The parent could be a module when the child is a
+ function at module scope. Or the parent could be a class when a class'
+ method is being replaced. The named child is set to new_child, while
+ the prior definition is saved away for later, when UnsetAll() is called.
+
+ This method supports the case where child_name is a staticmethod or a
+ classmethod of parent.
+ """
+ old_child = getattr(parent, child_name)
+
+ old_attribute = parent.__dict__.get(child_name)
+ if old_attribute is not None:
+ if isinstance(old_attribute, staticmethod):
+ old_child = staticmethod(old_child)
+ elif isinstance(old_attribute, classmethod):
+ old_child = classmethod(old_child.im_func)
+
+ self.cache.append((parent, old_child, child_name))
+ setattr(parent, child_name, new_child)
+
+ def UnsetAll(self):
+ """Reverses all the Set() calls, restoring things to their original
+ definition. Its okay to call UnsetAll() repeatedly, as later calls have
+ no effect if no Set() calls have been made.
+
+ """
+ # Undo calls to Set() in reverse order, in case Set() was called on the
+ # same arguments repeatedly (want the original call to be last one undone)
+ self.cache.reverse()
+
+ for (parent, old_child, child_name) in self.cache:
+ setattr(parent, child_name, old_child)
+ self.cache = []
diff --git a/vendor/pymox/stubout_test.py b/vendor/pymox/stubout_test.py
new file mode 100644
index 0000000000..a062b4840a
--- /dev/null
+++ b/vendor/pymox/stubout_test.py
@@ -0,0 +1,47 @@
+#!/usr/bin/python2.4
+#
+# Unit tests for stubout.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+import mox
+import stubout
+import stubout_testee
+
+
+class StubOutForTestingTest(unittest.TestCase):
+ def setUp(self):
+ self.mox = mox.Mox()
+ self.sample_function_backup = stubout_testee.SampleFunction
+
+ def tearDown(self):
+ stubout_testee.SampleFunction = self.sample_function_backup
+
+ def testSmartSetOnModule(self):
+ mock_function = self.mox.CreateMockAnything()
+ mock_function()
+
+ stubber = stubout.StubOutForTesting()
+ stubber.SmartSet(stubout_testee, 'SampleFunction', mock_function)
+
+ self.mox.ReplayAll()
+
+ stubout_testee.SampleFunction()
+
+ self.mox.VerifyAll()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/vendor/pymox/stubout_testee.py b/vendor/pymox/stubout_testee.py
new file mode 100644
index 0000000000..9cbdef60e8
--- /dev/null
+++ b/vendor/pymox/stubout_testee.py
@@ -0,0 +1,2 @@
+def SampleFunction():
+ raise Exception('I should never be called!')
diff --git a/vendor/python-daemon/ChangeLog b/vendor/python-daemon/ChangeLog
new file mode 100644
index 0000000000..d96fad7ca7
--- /dev/null
+++ b/vendor/python-daemon/ChangeLog
@@ -0,0 +1,187 @@
+2010-03-02 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.5.5 released.
+
+ * Stop using ‘pkg_resources’ and revert to pre-1.5.3 version-string
+ handling, until a better way that doesn't break everyone else's
+ installation can be found.
+
+2010-02-27 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.5.4 released.
+
+ * MANIFEST.in: Explicitly include version data file, otherwise
+ everything breaks for users of the sdist.
+
+2010-02-26 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.5.3 released.
+
+ * daemon/daemon.py: Invoke the pidfile context manager's ‘__exit__’
+ method with the correct arguments (as per
+ <URL:http://docs.python.org/library/stdtypes.html#typecontextmanager>).
+ Thanks to Ludvig Ericson for the bug report.
+ * version: New plain-text data file to store project version string.
+ * setup.py: Read version string from data file.
+ * daemon/version/__init__.py: Query version string with ‘pkg_resources’.
+
+2010-01-20 Ben Finney <ben+python@benfinney.id.au>
+
+ * Add ‘pylint’ configuration for this project.
+ * Update copyright notices.
+
+2009-10-24 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.5.2 released.
+
+2009-10-19 Ben Finney <ben+python@benfinney.id.au>
+
+ * Ensure we only prevent core dumps if ‘prevent_core’ is true.
+ Thanks to Denis Bilenko for reporting the lacking implementation of
+ this documented option.
+
+2009-09-28 Ben Finney <ben+python@benfinney.id.au>
+
+ * Add initial Frequently Asked Questions document.
+
+2009-09-26 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.5.1 released.
+
+ * Make a separate collection of DaemonRunner test scenarios.
+ * Handle a start request with a timeout on the PID file lock acquire.
+
+2009-09-24 Ben Finney <ben+python@benfinney.id.au>
+
+ * Implement ‘TimeoutPIDLockFile’ to specify a timeout in advance of
+ lock acquisition.
+ * Use lock with timeout for ‘DaemonRunner’.
+
+2009-09-24 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.5 released.
+
+ * Make a separate collection of PIDLockFile test scenarios.
+
+2009-09-23 Ben Finney <ben+python@benfinney.id.au>
+
+ * Raise specific errors on ‘DaemonRunner’ failures.
+ * Distinguish different conditions on reading and parsing PID file.
+ * Refactor code to ‘_terminate_daemon_process’ method.
+ * Improve explanations in comments and docstrings.
+ * Don't set pidfile at all if no path specified to constructor.
+ * Write the PID file using correct OS locking and permissions.
+ * Close the PID file after writing.
+ * Implement ‘PIDLockFile’ as subclass of ‘lockfile.LinkFileLock’.
+ * Remove redundant checks for file existence.
+
+2009-09-18 Ben Finney <ben+python@benfinney.id.au>
+
+ * Manage the excluded file descriptors as a set (not a list).
+ * Only inspect the file descriptor of streams if they actually have
+ one (via a ‘fileno’ method) when determining which file descriptors
+ to close. Thanks to Ask Solem for revealing this bug.
+
+2009-09-17 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.4.8 released.
+
+ * Remove child-exit signal (‘SIGCLD’, ‘SIGCHLD’) from default signal
+ map. Thanks to Joel Martin for pinpointing this issue.
+ * Document requirement for ensuring any operating-system specific
+ signal handlers are considered.
+ * Refactor ‘fork_then_exit_parent’ functionality to avoid duplicate
+ code.
+ * Remove redundant imports.
+ * Remove unused code from unit test suite scaffold.
+ * Add specific license terms for unit test suite scaffold.
+
+2009-09-03 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.4.7 released.
+
+2009-09-02 Ben Finney <ben+python@benfinney.id.au>
+
+ * Fix keywords argument for distribution setup.
+ * Exclude ‘test’ package from distribution installation.
+
+2009-06-21 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.4.6 released.
+
+ * Update documentation for changes from latest PEP 3143 revision.
+ * Implement DaemonContext.is_open method.
+
+2009-05-17 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.4.5 released.
+
+ * Register DaemonContext.close method for atexit processing.
+ * Move PID file cleanup to close method.
+ * Improve docstrings by reference to, and copy from, PEP 3143.
+ * Use mock checking capabilities of newer ‘MiniMock’ library.
+ * Automate building a versioned distribution tarball.
+ * Include developer documentation files in source distribution.
+
+2009-03-26 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.4.4 released.
+
+ * Conform to current PEP version, now released as PEP 3143 “Standard
+ daemon process libraryâ€.
+ * Ensure UID and GID are set in correct order.
+ * Delay closing all open files until just before re-binding standard
+ streams.
+ * Redirect standard streams to null device by default.
+
+2009-03-19 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.4.3 released.
+
+ * Close the PID file context on exit.
+
+2009-03-18 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.4.2 released.
+
+ * Context manager methods for DaemonContext.
+
+2009-03-18 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.4.1 released.
+
+ * Improvements to docstrings.
+ * Further conformance with draft PEP.
+
+2009-03-17 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.4 released.
+
+ * Implement the interface from a draft PEP for process daemonisation.
+ * Complete statement coverage from unit test suite.
+
+2009-03-12 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.3 released.
+
+ * Separate controller (now ‘DaemonRunner’) from daemon process
+ context (now ‘DaemonContext’).
+ * Fix many corner cases and bugs.
+ * Huge increase in unit test suite.
+
+2009-01-27 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.2 released.
+
+ * Initial release of this project forked from ‘bda.daemon’. Thanks,
+ Robert Niederreiter.
+ * Refactor some functionality out to helper functions.
+ * Begin unit test suite.
+
+
+Local variables:
+mode: change-log
+coding: utf-8
+left-margin: 4
+indent-tabs-mode: nil
+End:
diff --git a/vendor/python-daemon/LICENSE.GPL-2 b/vendor/python-daemon/LICENSE.GPL-2
new file mode 100644
index 0000000000..d511905c16
--- /dev/null
+++ b/vendor/python-daemon/LICENSE.GPL-2
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/vendor/python-daemon/LICENSE.PSF-2 b/vendor/python-daemon/LICENSE.PSF-2
new file mode 100644
index 0000000000..28533b6c53
--- /dev/null
+++ b/vendor/python-daemon/LICENSE.PSF-2
@@ -0,0 +1,48 @@
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python
+alone or in any derivative version, provided, however, that PSF's
+License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
+2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation;
+All Rights Reserved" are retained in Python alone or in any derivative
+version prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
diff --git a/vendor/python-daemon/MANIFEST.in b/vendor/python-daemon/MANIFEST.in
new file mode 100644
index 0000000000..ef71641df8
--- /dev/null
+++ b/vendor/python-daemon/MANIFEST.in
@@ -0,0 +1,4 @@
+include MANIFEST.in
+include LICENSE.*
+include ChangeLog
+include TODO
diff --git a/vendor/python-daemon/PKG-INFO b/vendor/python-daemon/PKG-INFO
new file mode 100644
index 0000000000..df8f5531b2
--- /dev/null
+++ b/vendor/python-daemon/PKG-INFO
@@ -0,0 +1,37 @@
+Metadata-Version: 1.0
+Name: python-daemon
+Version: 1.5.5
+Summary: Library to implement a well-behaved Unix daemon process.
+Home-page: http://pypi.python.org/pypi/python-daemon/
+Author: Ben Finney
+Author-email: ben+python@benfinney.id.au
+License: PSF-2+
+Description: This library implements the well-behaved daemon specification of
+ :pep:`3143`, "Standard daemon process library".
+
+ A well-behaved Unix daemon process is tricky to get right, but the
+ required steps are much the same for every daemon program. A
+ `DaemonContext` instance holds the behaviour and configured
+ process environment for the program; use the instance as a context
+ manager to enter a daemon state.
+
+ Simple example of usage::
+
+ import daemon
+
+ from spam import do_main_program
+
+ with daemon.DaemonContext():
+ do_main_program()
+
+ Customisation of the steps to become a daemon is available by
+ setting options on the `DaemonContext` instance; see the
+ documentation for that class for each option.
+Keywords: daemon,fork,unix
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python
+Classifier: Intended Audience :: Developers
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/vendor/python-daemon/README.nova b/vendor/python-daemon/README.nova
new file mode 100644
index 0000000000..57cefd857f
--- /dev/null
+++ b/vendor/python-daemon/README.nova
@@ -0,0 +1,4 @@
+
+
+NOTE(termie): using LinkFileLock in pidlockfile resulted in a bug,
+ modified the code by replacing it with FileLock
diff --git a/vendor/python-daemon/daemon/__init__.py b/vendor/python-daemon/daemon/__init__.py
new file mode 100644
index 0000000000..d8dc171a26
--- /dev/null
+++ b/vendor/python-daemon/daemon/__init__.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+# daemon/__init__.py
+# Part of python-daemon, an implementation of PEP 3143.
+#
+# Copyright © 2009–2010 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2006 Robert Niederreiter
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Python Software Foundation License, version 2 or
+# later as published by the Python Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+
+""" Library to implement a well-behaved Unix daemon process.
+
+ This library implements the well-behaved daemon specification of
+ :pep:`3143`, "Standard daemon process library".
+
+ A well-behaved Unix daemon process is tricky to get right, but the
+ required steps are much the same for every daemon program. A
+ `DaemonContext` instance holds the behaviour and configured
+ process environment for the program; use the instance as a context
+ manager to enter a daemon state.
+
+ Simple example of usage::
+
+ import daemon
+
+ from spam import do_main_program
+
+ with daemon.DaemonContext():
+ do_main_program()
+
+ Customisation of the steps to become a daemon is available by
+ setting options on the `DaemonContext` instance; see the
+ documentation for that class for each option.
+
+ """
+
+import version
+from daemon import DaemonContext
+
+
+_version = version.version
+_copyright = version.copyright
+_license = version.license
+_url = "http://pypi.python.org/pypi/python-daemon/"
diff --git a/vendor/python-daemon/daemon/daemon.py b/vendor/python-daemon/daemon/daemon.py
new file mode 100644
index 0000000000..28db69574f
--- /dev/null
+++ b/vendor/python-daemon/daemon/daemon.py
@@ -0,0 +1,776 @@
+# -*- coding: utf-8 -*-
+
+# daemon/daemon.py
+# Part of python-daemon, an implementation of PEP 3143.
+#
+# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2007–2008 Robert Niederreiter, Jens Klein
+# Copyright © 2004–2005 Chad J. Schroeder
+# Copyright © 2003 Clark Evans
+# Copyright © 2002 Noah Spurrier
+# Copyright © 2001 Jürgen Hermann
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Python Software Foundation License, version 2 or
+# later as published by the Python Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+
+""" Daemon process behaviour.
+ """
+
+import os
+import sys
+import resource
+import errno
+import signal
+import socket
+import atexit
+
+
+class DaemonError(Exception):
+ """ Base exception class for errors from this module. """
+
+
+class DaemonOSEnvironmentError(DaemonError, OSError):
+ """ Exception raised when daemon OS environment setup receives error. """
+
+
+class DaemonProcessDetachError(DaemonError, OSError):
+ """ Exception raised when process detach fails. """
+
+
+class DaemonContext(object):
+ """ Context for turning the current program into a daemon process.
+
+ A `DaemonContext` instance represents the behaviour settings and
+ process context for the program when it becomes a daemon. The
+ behaviour and environment is customised by setting options on the
+ instance, before calling the `open` method.
+
+ Each option can be passed as a keyword argument to the `DaemonContext`
+ constructor, or subsequently altered by assigning to an attribute on
+ the instance at any time prior to calling `open`. That is, for
+ options named `wibble` and `wubble`, the following invocation::
+
+ foo = daemon.DaemonContext(wibble=bar, wubble=baz)
+ foo.open()
+
+ is equivalent to::
+
+ foo = daemon.DaemonContext()
+ foo.wibble = bar
+ foo.wubble = baz
+ foo.open()
+
+ The following options are defined.
+
+ `files_preserve`
+ :Default: ``None``
+
+ List of files that should *not* be closed when starting the
+ daemon. If ``None``, all open file descriptors will be closed.
+
+ Elements of the list are file descriptors (as returned by a file
+ object's `fileno()` method) or Python `file` objects. Each
+ specifies a file that is not to be closed during daemon start.
+
+ `chroot_directory`
+ :Default: ``None``
+
+ Full path to a directory to set as the effective root directory of
+ the process. If ``None``, specifies that the root directory is not
+ to be changed.
+
+ `working_directory`
+ :Default: ``'/'``
+
+ Full path of the working directory to which the process should
+ change on daemon start.
+
+ Since a filesystem cannot be unmounted if a process has its
+ current working directory on that filesystem, this should either
+ be left at default or set to a directory that is a sensible “home
+ directory†for the daemon while it is running.
+
+ `umask`
+ :Default: ``0``
+
+ File access creation mask (“umaskâ€) to set for the process on
+ daemon start.
+
+ Since a process inherits its umask from its parent process,
+ starting the daemon will reset the umask to this value so that
+ files are created by the daemon with access modes as it expects.
+
+ `pidfile`
+ :Default: ``None``
+
+ Context manager for a PID lock file. When the daemon context opens
+ and closes, it enters and exits the `pidfile` context manager.
+
+ `detach_process`
+ :Default: ``None``
+
+ If ``True``, detach the process context when opening the daemon
+ context; if ``False``, do not detach.
+
+ If unspecified (``None``) during initialisation of the instance,
+ this will be set to ``True`` by default, and ``False`` only if
+ detaching the process is determined to be redundant; for example,
+ in the case when the process was started by `init`, by `initd`, or
+ by `inetd`.
+
+ `signal_map`
+ :Default: system-dependent
+
+ Mapping from operating system signals to callback actions.
+
+ The mapping is used when the daemon context opens, and determines
+ the action for each signal's signal handler:
+
+ * A value of ``None`` will ignore the signal (by setting the
+ signal action to ``signal.SIG_IGN``).
+
+ * A string value will be used as the name of an attribute on the
+ ``DaemonContext`` instance. The attribute's value will be used
+ as the action for the signal handler.
+
+ * Any other value will be used as the action for the
+ signal handler. See the ``signal.signal`` documentation
+ for details of the signal handler interface.
+
+ The default value depends on which signals are defined on the
+ running system. Each item from the list below whose signal is
+ actually defined in the ``signal`` module will appear in the
+ default map:
+
+ * ``signal.SIGTTIN``: ``None``
+
+ * ``signal.SIGTTOU``: ``None``
+
+ * ``signal.SIGTSTP``: ``None``
+
+ * ``signal.SIGTERM``: ``'terminate'``
+
+ Depending on how the program will interact with its child
+ processes, it may need to specify a signal map that
+ includes the ``signal.SIGCHLD`` signal (received when a
+ child process exits). See the specific operating system's
+ documentation for more detail on how to determine what
+ circumstances dictate the need for signal handlers.
+
+ `uid`
+ :Default: ``os.getuid()``
+
+ `gid`
+ :Default: ``os.getgid()``
+
+ The user ID (“UIDâ€) value and group ID (“GIDâ€) value to switch
+ the process to on daemon start.
+
+ The default values, the real UID and GID of the process, will
+ relinquish any effective privilege elevation inherited by the
+ process.
+
+ `prevent_core`
+ :Default: ``True``
+
+ If true, prevents the generation of core files, in order to avoid
+ leaking sensitive information from daemons run as `root`.
+
+ `stdin`
+ :Default: ``None``
+
+ `stdout`
+ :Default: ``None``
+
+ `stderr`
+ :Default: ``None``
+
+ Each of `stdin`, `stdout`, and `stderr` is a file-like object
+ which will be used as the new file for the standard I/O stream
+ `sys.stdin`, `sys.stdout`, and `sys.stderr` respectively. The file
+ should therefore be open, with a minimum of mode 'r' in the case
+ of `stdin`, and mode 'w+' in the case of `stdout` and `stderr`.
+
+ If the object has a `fileno()` method that returns a file
+ descriptor, the corresponding file will be excluded from being
+ closed during daemon start (that is, it will be treated as though
+ it were listed in `files_preserve`).
+
+ If ``None``, the corresponding system stream is re-bound to the
+ file named by `os.devnull`.
+
+ """
+
+ def __init__(
+ self,
+ chroot_directory=None,
+ working_directory='/',
+ umask=0,
+ uid=None,
+ gid=None,
+ prevent_core=True,
+ detach_process=None,
+ files_preserve=None,
+ pidfile=None,
+ stdin=None,
+ stdout=None,
+ stderr=None,
+ signal_map=None,
+ ):
+ """ Set up a new instance. """
+ self.chroot_directory = chroot_directory
+ self.working_directory = working_directory
+ self.umask = umask
+ self.prevent_core = prevent_core
+ self.files_preserve = files_preserve
+ self.pidfile = pidfile
+ self.stdin = stdin
+ self.stdout = stdout
+ self.stderr = stderr
+
+ if uid is None:
+ uid = os.getuid()
+ self.uid = uid
+ if gid is None:
+ gid = os.getgid()
+ self.gid = gid
+
+ if detach_process is None:
+ detach_process = is_detach_process_context_required()
+ self.detach_process = detach_process
+
+ if signal_map is None:
+ signal_map = make_default_signal_map()
+ self.signal_map = signal_map
+
+ self._is_open = False
+
+ @property
+ def is_open(self):
+ """ ``True`` if the instance is currently open. """
+ return self._is_open
+
+ def open(self):
+ """ Become a daemon process.
+ :Return: ``None``
+
+ Open the daemon context, turning the current program into a daemon
+ process. This performs the following steps:
+
+ * If this instance's `is_open` property is true, return
+ immediately. This makes it safe to call `open` multiple times on
+ an instance.
+
+ * If the `prevent_core` attribute is true, set the resource limits
+ for the process to prevent any core dump from the process.
+
+ * If the `chroot_directory` attribute is not ``None``, set the
+ effective root directory of the process to that directory (via
+ `os.chroot`).
+
+ This allows running the daemon process inside a “chroot gaolâ€
+ as a means of limiting the system's exposure to rogue behaviour
+ by the process. Note that the specified directory needs to
+ already be set up for this purpose.
+
+ * Set the process UID and GID to the `uid` and `gid` attribute
+ values.
+
+ * Close all open file descriptors. This excludes those listed in
+ the `files_preserve` attribute, and those that correspond to the
+ `stdin`, `stdout`, or `stderr` attributes.
+
+ * Change current working directory to the path specified by the
+ `working_directory` attribute.
+
+ * Reset the file access creation mask to the value specified by
+ the `umask` attribute.
+
+ * If the `detach_process` option is true, detach the current
+ process into its own process group, and disassociate from any
+ controlling terminal.
+
+ * Set signal handlers as specified by the `signal_map` attribute.
+
+ * If any of the attributes `stdin`, `stdout`, `stderr` are not
+ ``None``, bind the system streams `sys.stdin`, `sys.stdout`,
+ and/or `sys.stderr` to the files represented by the
+ corresponding attributes. Where the attribute has a file
+ descriptor, the descriptor is duplicated (instead of re-binding
+ the name).
+
+ * If the `pidfile` attribute is not ``None``, enter its context
+ manager.
+
+ * Mark this instance as open (for the purpose of future `open` and
+ `close` calls).
+
+ * Register the `close` method to be called during Python's exit
+ processing.
+
+ When the function returns, the running program is a daemon
+ process.
+
+ """
+ if self.is_open:
+ return
+
+ if self.chroot_directory is not None:
+ change_root_directory(self.chroot_directory)
+
+ if self.prevent_core:
+ prevent_core_dump()
+
+ change_file_creation_mask(self.umask)
+ change_working_directory(self.working_directory)
+ change_process_owner(self.uid, self.gid)
+
+ if self.detach_process:
+ detach_process_context()
+
+ signal_handler_map = self._make_signal_handler_map()
+ set_signal_handlers(signal_handler_map)
+
+ exclude_fds = self._get_exclude_file_descriptors()
+ close_all_open_files(exclude=exclude_fds)
+
+ redirect_stream(sys.stdin, self.stdin)
+ redirect_stream(sys.stdout, self.stdout)
+ redirect_stream(sys.stderr, self.stderr)
+
+ if self.pidfile is not None:
+ self.pidfile.__enter__()
+
+ self._is_open = True
+
+ register_atexit_function(self.close)
+
+ def __enter__(self):
+ """ Context manager entry point. """
+ self.open()
+ return self
+
+ def close(self):
+ """ Exit the daemon process context.
+ :Return: ``None``
+
+ Close the daemon context. This performs the following steps:
+
+ * If this instance's `is_open` property is false, return
+ immediately. This makes it safe to call `close` multiple times
+ on an instance.
+
+ * If the `pidfile` attribute is not ``None``, exit its context
+ manager.
+
+ * Mark this instance as closed (for the purpose of future `open`
+ and `close` calls).
+
+ """
+ if not self.is_open:
+ return
+
+ if self.pidfile is not None:
+ # Follow the interface for telling a context manager to exit,
+ # <URL:http://docs.python.org/library/stdtypes.html#typecontextmanager>.
+ self.pidfile.__exit__(None, None, None)
+
+ self._is_open = False
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ """ Context manager exit point. """
+ self.close()
+
+ def terminate(self, signal_number, stack_frame):
+ """ Signal handler for end-process signals.
+ :Return: ``None``
+
+ Signal handler for the ``signal.SIGTERM`` signal. Performs the
+ following step:
+
+ * Raise a ``SystemExit`` exception explaining the signal.
+
+ """
+ exception = SystemExit(
+ "Terminating on signal %(signal_number)r"
+ % vars())
+ raise exception
+
+ def _get_exclude_file_descriptors(self):
+ """ Return the set of file descriptors to exclude closing.
+
+ Returns a set containing the file descriptors for the
+ items in `files_preserve`, and also each of `stdin`,
+ `stdout`, and `stderr`:
+
+ * If the item is ``None``, it is omitted from the return
+ set.
+
+ * If the item has a ``fileno()`` method, that method's
+ return value is in the return set.
+
+ * Otherwise, the item is in the return set verbatim.
+
+ """
+ files_preserve = self.files_preserve
+ if files_preserve is None:
+ files_preserve = []
+ files_preserve.extend(
+ item for item in [self.stdin, self.stdout, self.stderr]
+ if hasattr(item, 'fileno'))
+ exclude_descriptors = set()
+ for item in files_preserve:
+ if item is None:
+ continue
+ if hasattr(item, 'fileno'):
+ exclude_descriptors.add(item.fileno())
+ else:
+ exclude_descriptors.add(item)
+ return exclude_descriptors
+
+ def _make_signal_handler(self, target):
+ """ Make the signal handler for a specified target object.
+
+ If `target` is ``None``, returns ``signal.SIG_IGN``. If
+ `target` is a string, returns the attribute of this
+ instance named by that string. Otherwise, returns `target`
+ itself.
+
+ """
+ if target is None:
+ result = signal.SIG_IGN
+ elif isinstance(target, basestring):
+ name = target
+ result = getattr(self, name)
+ else:
+ result = target
+
+ return result
+
+ def _make_signal_handler_map(self):
+ """ Make the map from signals to handlers for this instance.
+
+ Constructs a map from signal numbers to handlers for this
+ context instance, suitable for passing to
+ `set_signal_handlers`.
+
+ """
+ signal_handler_map = dict(
+ (signal_number, self._make_signal_handler(target))
+ for (signal_number, target) in self.signal_map.items())
+ return signal_handler_map
+
+
+def change_working_directory(directory):
+ """ Change the working directory of this process.
+ """
+ try:
+ os.chdir(directory)
+ except Exception, exc:
+ error = DaemonOSEnvironmentError(
+ "Unable to change working directory (%(exc)s)"
+ % vars())
+ raise error
+
+
+def change_root_directory(directory):
+ """ Change the root directory of this process.
+
+ Sets the current working directory, then the process root
+ directory, to the specified `directory`. Requires appropriate
+ OS privileges for this process.
+
+ """
+ try:
+ os.chdir(directory)
+ os.chroot(directory)
+ except Exception, exc:
+ error = DaemonOSEnvironmentError(
+ "Unable to change root directory (%(exc)s)"
+ % vars())
+ raise error
+
+
+def change_file_creation_mask(mask):
+ """ Change the file creation mask for this process.
+ """
+ try:
+ os.umask(mask)
+ except Exception, exc:
+ error = DaemonOSEnvironmentError(
+ "Unable to change file creation mask (%(exc)s)"
+ % vars())
+ raise error
+
+
+def change_process_owner(uid, gid):
+ """ Change the owning UID and GID of this process.
+
+ Sets the GID then the UID of the process (in that order, to
+ avoid permission errors) to the specified `gid` and `uid`
+ values. Requires appropriate OS privileges for this process.
+
+ """
+ try:
+ os.setgid(gid)
+ os.setuid(uid)
+ except Exception, exc:
+ error = DaemonOSEnvironmentError(
+ "Unable to change file creation mask (%(exc)s)"
+ % vars())
+ raise error
+
+
+def prevent_core_dump():
+ """ Prevent this process from generating a core dump.
+
+ Sets the soft and hard limits for core dump size to zero. On
+ Unix, this prevents the process from creating core dump
+ altogether.
+
+ """
+ core_resource = resource.RLIMIT_CORE
+
+ try:
+ # Ensure the resource limit exists on this platform, by requesting
+ # its current value
+ core_limit_prev = resource.getrlimit(core_resource)
+ except ValueError, exc:
+ error = DaemonOSEnvironmentError(
+ "System does not support RLIMIT_CORE resource limit (%(exc)s)"
+ % vars())
+ raise error
+
+ # Set hard and soft limits to zero, i.e. no core dump at all
+ core_limit = (0, 0)
+ resource.setrlimit(core_resource, core_limit)
+
+
+def detach_process_context():
+ """ Detach the process context from parent and session.
+
+ Detach from the parent process and session group, allowing the
+ parent to exit while this process continues running.
+
+ Reference: “Advanced Programming in the Unix Environmentâ€,
+ section 13.3, by W. Richard Stevens, published 1993 by
+ Addison-Wesley.
+
+ """
+
+ def fork_then_exit_parent(error_message):
+ """ Fork a child process, then exit the parent process.
+
+ If the fork fails, raise a ``DaemonProcessDetachError``
+ with ``error_message``.
+
+ """
+ try:
+ pid = os.fork()
+ if pid > 0:
+ os._exit(0)
+ except OSError, exc:
+ exc_errno = exc.errno
+ exc_strerror = exc.strerror
+ error = DaemonProcessDetachError(
+ "%(error_message)s: [%(exc_errno)d] %(exc_strerror)s" % vars())
+ raise error
+
+ fork_then_exit_parent(error_message="Failed first fork")
+ os.setsid()
+ fork_then_exit_parent(error_message="Failed second fork")
+
+
+def is_process_started_by_init():
+ """ Determine if the current process is started by `init`.
+
+ The `init` process has the process ID of 1; if that is our
+ parent process ID, return ``True``, otherwise ``False``.
+
+ """
+ result = False
+
+ init_pid = 1
+ if os.getppid() == init_pid:
+ result = True
+
+ return result
+
+
+def is_socket(fd):
+ """ Determine if the file descriptor is a socket.
+
+ Return ``False`` if querying the socket type of `fd` raises an
+ error; otherwise return ``True``.
+
+ """
+ result = False
+
+ file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW)
+
+ try:
+ socket_type = file_socket.getsockopt(
+ socket.SOL_SOCKET, socket.SO_TYPE)
+ except socket.error, exc:
+ exc_errno = exc.args[0]
+ if exc_errno == errno.ENOTSOCK:
+ # Socket operation on non-socket
+ pass
+ else:
+ # Some other socket error
+ result = True
+ else:
+ # No error getting socket type
+ result = True
+
+ return result
+
+
+def is_process_started_by_superserver():
+ """ Determine if the current process is started by the superserver.
+
+ The internet superserver creates a network socket, and
+ attaches it to the standard streams of the child process. If
+ that is the case for this process, return ``True``, otherwise
+ ``False``.
+
+ """
+ result = False
+
+ stdin_fd = sys.__stdin__.fileno()
+ if is_socket(stdin_fd):
+ result = True
+
+ return result
+
+
+def is_detach_process_context_required():
+ """ Determine whether detaching process context is required.
+
+ Return ``True`` if the process environment indicates the
+ process is already detached:
+
+ * Process was started by `init`; or
+
+ * Process was started by `inetd`.
+
+ """
+ result = True
+ if is_process_started_by_init() or is_process_started_by_superserver():
+ result = False
+
+ return result
+
+
+def close_file_descriptor_if_open(fd):
+ """ Close a file descriptor if already open.
+
+ Close the file descriptor `fd`, suppressing an error in the
+ case the file was not open.
+
+ """
+ try:
+ os.close(fd)
+ except OSError, exc:
+ if exc.errno == errno.EBADF:
+ # File descriptor was not open
+ pass
+ else:
+ error = DaemonOSEnvironmentError(
+ "Failed to close file descriptor %(fd)d"
+ " (%(exc)s)"
+ % vars())
+ raise error
+
+
+MAXFD = 2048
+
+def get_maximum_file_descriptors():
+ """ Return the maximum number of open file descriptors for this process.
+
+ Return the process hard resource limit of maximum number of
+ open file descriptors. If the limit is “infinityâ€, a default
+ value of ``MAXFD`` is returned.
+
+ """
+ limits = resource.getrlimit(resource.RLIMIT_NOFILE)
+ result = limits[1]
+ if result == resource.RLIM_INFINITY:
+ result = MAXFD
+ return result
+
+
+def close_all_open_files(exclude=set()):
+ """ Close all open file descriptors.
+
+ Closes every file descriptor (if open) of this process. If
+ specified, `exclude` is a set of file descriptors to *not*
+ close.
+
+ """
+ maxfd = get_maximum_file_descriptors()
+ for fd in reversed(range(maxfd)):
+ if fd not in exclude:
+ close_file_descriptor_if_open(fd)
+
+
+def redirect_stream(system_stream, target_stream):
+ """ Redirect a system stream to a specified file.
+
+ `system_stream` is a standard system stream such as
+ ``sys.stdout``. `target_stream` is an open file object that
+ should replace the corresponding system stream object.
+
+ If `target_stream` is ``None``, defaults to opening the
+ operating system's null device and using its file descriptor.
+
+ """
+ if target_stream is None:
+ target_fd = os.open(os.devnull, os.O_RDWR)
+ else:
+ target_fd = target_stream.fileno()
+ os.dup2(target_fd, system_stream.fileno())
+
+
+def make_default_signal_map():
+ """ Make the default signal map for this system.
+
+ The signals available differ by system. The map will not
+ contain any signals not defined on the running system.
+
+ """
+ name_map = {
+ 'SIGTSTP': None,
+ 'SIGTTIN': None,
+ 'SIGTTOU': None,
+ 'SIGTERM': 'terminate',
+ }
+ signal_map = dict(
+ (getattr(signal, name), target)
+ for (name, target) in name_map.items()
+ if hasattr(signal, name))
+
+ return signal_map
+
+
+def set_signal_handlers(signal_handler_map):
+ """ Set the signal handlers as specified.
+
+ The `signal_handler_map` argument is a map from signal number
+ to signal handler. See the `signal` module for details.
+
+ """
+ for (signal_number, handler) in signal_handler_map.items():
+ signal.signal(signal_number, handler)
+
+
+def register_atexit_function(func):
+ """ Register a function for processing at program exit.
+
+ The function `func` is registered for a call with no arguments
+ at program exit.
+
+ """
+ atexit.register(func)
diff --git a/vendor/python-daemon/daemon/pidlockfile.py b/vendor/python-daemon/daemon/pidlockfile.py
new file mode 100644
index 0000000000..2eb334abb2
--- /dev/null
+++ b/vendor/python-daemon/daemon/pidlockfile.py
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+
+# daemon/pidlockfile.py
+# Part of python-daemon, an implementation of PEP 3143.
+#
+# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Python Software Foundation License, version 2 or
+# later as published by the Python Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+
+
+""" Lockfile behaviour implemented via Unix PID files.
+ """
+
+import os
+import errno
+
+from lockfile import (
+ FileLock,
+ AlreadyLocked, LockFailed,
+ NotLocked, NotMyLock,
+ )
+
+
+class PIDFileError(Exception):
+ """ Abstract base class for errors specific to PID files. """
+
+class PIDFileParseError(ValueError, PIDFileError):
+ """ Raised when parsing contents of PID file fails. """
+
+
+class PIDLockFile(FileLock, object):
+ """ Lockfile implemented as a Unix PID file.
+
+ The PID file is named by the attribute `path`. When locked,
+ the file will be created with a single line of text,
+ containing the process ID (PID) of the process that acquired
+ the lock.
+
+ The lock is acquired and maintained as per `LinkFileLock`.
+
+ """
+
+ def read_pid(self):
+ """ Get the PID from the lock file.
+ """
+ result = read_pid_from_pidfile(self.path)
+ return result
+
+ def acquire(self, *args, **kwargs):
+ """ Acquire the lock.
+
+ Locks the PID file then creates the PID file for this
+ lock. The `timeout` parameter is used as for the
+ `LinkFileLock` class.
+
+ """
+ super(PIDLockFile, self).acquire(*args, **kwargs)
+ try:
+ write_pid_to_pidfile(self.path)
+ except OSError, exc:
+ error = LockFailed("%(exc)s" % vars())
+ raise error
+
+ def release(self):
+ """ Release the lock.
+
+ Removes the PID file then releases the lock, or raises an
+ error if the current process does not hold the lock.
+
+ """
+ if self.i_am_locking():
+ remove_existing_pidfile(self.path)
+ super(PIDLockFile, self).release()
+
+ def break_lock(self):
+ """ Break an existing lock.
+
+ If the lock is held, breaks the lock and removes the PID
+ file.
+
+ """
+ super(PIDLockFile, self).break_lock()
+ remove_existing_pidfile(self.path)
+
+
+class TimeoutPIDLockFile(PIDLockFile):
+ """ Lockfile with default timeout, implemented as a Unix PID file.
+
+ This uses the ``PIDLockFile`` implementation, with the
+ following changes:
+
+ * The `acquire_timeout` parameter to the initialiser will be
+ used as the default `timeout` parameter for the `acquire`
+ method.
+
+ """
+
+ def __init__(self, path, acquire_timeout=None, *args, **kwargs):
+ """ Set up the parameters of a DaemonRunnerLock. """
+ self.acquire_timeout = acquire_timeout
+ super(TimeoutPIDLockFile, self).__init__(path, *args, **kwargs)
+
+ def acquire(self, timeout=None, *args, **kwargs):
+ """ Acquire the lock. """
+ if timeout is None:
+ timeout = self.acquire_timeout
+ super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
+
+
+def read_pid_from_pidfile(pidfile_path):
+ """ Read the PID recorded in the named PID file.
+
+ Read and return the numeric PID recorded as text in the named
+ PID file. If the PID file does not exist, return ``None``. If
+ the content is not a valid PID, raise ``PIDFileParseError``.
+
+ """
+ pid = None
+ pidfile = None
+ try:
+ pidfile = open(pidfile_path, 'r')
+ except IOError, exc:
+ if exc.errno == errno.ENOENT:
+ pass
+ else:
+ raise
+
+ if pidfile:
+ # According to the FHS 2.3 section on PID files in ‘/var/run’:
+ #
+ # The file must consist of the process identifier in
+ # ASCII-encoded decimal, followed by a newline character. …
+ #
+ # Programs that read PID files should be somewhat flexible
+ # in what they accept; i.e., they should ignore extra
+ # whitespace, leading zeroes, absence of the trailing
+ # newline, or additional lines in the PID file.
+
+ line = pidfile.readline().strip()
+ try:
+ pid = int(line)
+ except ValueError:
+ raise PIDFileParseError(
+ "PID file %(pidfile_path)r contents invalid" % vars())
+ pidfile.close()
+
+ return pid
+
+
+def write_pid_to_pidfile(pidfile_path):
+ """ Write the PID in the named PID file.
+
+ Get the numeric process ID (“PIDâ€) of the current process
+ and write it to the named file as a line of text.
+
+ """
+ open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
+ open_mode = (
+ ((os.R_OK | os.W_OK) << 6) |
+ ((os.R_OK) << 3) |
+ ((os.R_OK)))
+ pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
+ pidfile = os.fdopen(pidfile_fd, 'w')
+
+ # According to the FHS 2.3 section on PID files in ‘/var/run’:
+ #
+ # The file must consist of the process identifier in
+ # ASCII-encoded decimal, followed by a newline character. For
+ # example, if crond was process number 25, /var/run/crond.pid
+ # would contain three characters: two, five, and newline.
+
+ pid = os.getpid()
+ line = "%(pid)d\n" % vars()
+ pidfile.write(line)
+ pidfile.close()
+
+
+def remove_existing_pidfile(pidfile_path):
+ """ Remove the named PID file if it exists.
+
+ Remove the named PID file. Ignore the condition if the file
+ does not exist, since that only means we are already in the
+ desired state.
+
+ """
+ try:
+ os.remove(pidfile_path)
+ except OSError, exc:
+ if exc.errno == errno.ENOENT:
+ pass
+ else:
+ raise
diff --git a/vendor/python-daemon/daemon/runner.py b/vendor/python-daemon/daemon/runner.py
new file mode 100644
index 0000000000..0642695b00
--- /dev/null
+++ b/vendor/python-daemon/daemon/runner.py
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+
+# daemon/runner.py
+# Part of python-daemon, an implementation of PEP 3143.
+#
+# Copyright © 2009–2010 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2007–2008 Robert Niederreiter, Jens Klein
+# Copyright © 2003 Clark Evans
+# Copyright © 2002 Noah Spurrier
+# Copyright © 2001 Jürgen Hermann
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Python Software Foundation License, version 2 or
+# later as published by the Python Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+
+""" Daemon runner library.
+ """
+
+import sys
+import os
+import signal
+import errno
+
+import pidlockfile
+
+from daemon import DaemonContext
+
+
+class DaemonRunnerError(Exception):
+ """ Abstract base class for errors from DaemonRunner. """
+
+class DaemonRunnerInvalidActionError(ValueError, DaemonRunnerError):
+ """ Raised when specified action for DaemonRunner is invalid. """
+
+class DaemonRunnerStartFailureError(RuntimeError, DaemonRunnerError):
+ """ Raised when failure starting DaemonRunner. """
+
+class DaemonRunnerStopFailureError(RuntimeError, DaemonRunnerError):
+ """ Raised when failure stopping DaemonRunner. """
+
+
+class DaemonRunner(object):
+ """ Controller for a callable running in a separate background process.
+
+ The first command-line argument is the action to take:
+
+ * 'start': Become a daemon and call `app.run()`.
+ * 'stop': Exit the daemon process specified in the PID file.
+ * 'restart': Stop, then start.
+
+ """
+
+ start_message = "started with pid %(pid)d"
+
+ def __init__(self, app):
+ """ Set up the parameters of a new runner.
+
+ The `app` argument must have the following attributes:
+
+ * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem
+ paths to open and replace the existing `sys.stdin`,
+ `sys.stdout`, `sys.stderr`.
+
+ * `pidfile_path`: Absolute filesystem path to a file that
+ will be used as the PID file for the daemon. If
+ ``None``, no PID file will be used.
+
+ * `pidfile_timeout`: Used as the default acquisition
+ timeout value supplied to the runner's PID lock file.
+
+ * `run`: Callable that will be invoked when the daemon is
+ started.
+
+ """
+ self.parse_args()
+ self.app = app
+ self.daemon_context = DaemonContext()
+ self.daemon_context.stdin = open(app.stdin_path, 'r')
+ self.daemon_context.stdout = open(app.stdout_path, 'w+')
+ self.daemon_context.stderr = open(
+ app.stderr_path, 'w+', buffering=0)
+
+ self.pidfile = None
+ if app.pidfile_path is not None:
+ self.pidfile = make_pidlockfile(
+ app.pidfile_path, app.pidfile_timeout)
+ self.daemon_context.pidfile = self.pidfile
+
+ def _usage_exit(self, argv):
+ """ Emit a usage message, then exit.
+ """
+ progname = os.path.basename(argv[0])
+ usage_exit_code = 2
+ action_usage = "|".join(self.action_funcs.keys())
+ message = "usage: %(progname)s %(action_usage)s" % vars()
+ emit_message(message)
+ sys.exit(usage_exit_code)
+
+ def parse_args(self, argv=None):
+ """ Parse command-line arguments.
+ """
+ if argv is None:
+ argv = sys.argv
+
+ min_args = 2
+ if len(argv) < min_args:
+ self._usage_exit(argv)
+
+ self.action = argv[1]
+ if self.action not in self.action_funcs:
+ self._usage_exit(argv)
+
+ def _start(self):
+ """ Open the daemon context and run the application.
+ """
+ if is_pidfile_stale(self.pidfile):
+ self.pidfile.break_lock()
+
+ try:
+ self.daemon_context.open()
+ except pidlockfile.AlreadyLocked:
+ pidfile_path = self.pidfile.path
+ raise DaemonRunnerStartFailureError(
+ "PID file %(pidfile_path)r already locked" % vars())
+
+ pid = os.getpid()
+ message = self.start_message % vars()
+ emit_message(message)
+
+ self.app.run()
+
+ def _terminate_daemon_process(self):
+ """ Terminate the daemon process specified in the current PID file.
+ """
+ pid = self.pidfile.read_pid()
+ try:
+ os.kill(pid, signal.SIGTERM)
+ except OSError, exc:
+ raise DaemonRunnerStopFailureError(
+ "Failed to terminate %(pid)d: %(exc)s" % vars())
+
+ def _stop(self):
+ """ Exit the daemon process specified in the current PID file.
+ """
+ if not self.pidfile.is_locked():
+ pidfile_path = self.pidfile.path
+ raise DaemonRunnerStopFailureError(
+ "PID file %(pidfile_path)r not locked" % vars())
+
+ if is_pidfile_stale(self.pidfile):
+ self.pidfile.break_lock()
+ else:
+ self._terminate_daemon_process()
+
+ def _restart(self):
+ """ Stop, then start.
+ """
+ self._stop()
+ self._start()
+
+ action_funcs = {
+ 'start': _start,
+ 'stop': _stop,
+ 'restart': _restart,
+ }
+
+ def _get_action_func(self):
+ """ Return the function for the specified action.
+
+ Raises ``DaemonRunnerInvalidActionError`` if the action is
+ unknown.
+
+ """
+ try:
+ func = self.action_funcs[self.action]
+ except KeyError:
+ raise DaemonRunnerInvalidActionError(
+ "Unknown action: %(action)r" % vars(self))
+ return func
+
+ def do_action(self):
+ """ Perform the requested action.
+ """
+ func = self._get_action_func()
+ func(self)
+
+
+def emit_message(message, stream=None):
+ """ Emit a message to the specified stream (default `sys.stderr`). """
+ if stream is None:
+ stream = sys.stderr
+ stream.write("%(message)s\n" % vars())
+ stream.flush()
+
+
+def make_pidlockfile(path, acquire_timeout):
+ """ Make a PIDLockFile instance with the given filesystem path. """
+ if not isinstance(path, basestring):
+ error = ValueError("Not a filesystem path: %(path)r" % vars())
+ raise error
+ if not os.path.isabs(path):
+ error = ValueError("Not an absolute path: %(path)r" % vars())
+ raise error
+ lockfile = pidlockfile.TimeoutPIDLockFile(path, acquire_timeout)
+
+ return lockfile
+
+
+def is_pidfile_stale(pidfile):
+ """ Determine whether a PID file is stale.
+
+ Return ``True`` (“staleâ€) if the contents of the PID file are
+ valid but do not match the PID of a currently-running process;
+ otherwise return ``False``.
+
+ """
+ result = False
+
+ pidfile_pid = pidfile.read_pid()
+ if pidfile_pid is not None:
+ try:
+ os.kill(pidfile_pid, signal.SIG_DFL)
+ except OSError, exc:
+ if exc.errno == errno.ESRCH:
+ # The specified PID does not exist
+ result = True
+
+ return result
diff --git a/vendor/python-daemon/daemon/version/__init__.py b/vendor/python-daemon/daemon/version/__init__.py
new file mode 100644
index 0000000000..d2eafa6a99
--- /dev/null
+++ b/vendor/python-daemon/daemon/version/__init__.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+# daemon/version/__init__.py
+# Part of python-daemon, an implementation of PEP 3143.
+#
+# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Python Software Foundation License, version 2 or
+# later as published by the Python Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+
+""" Version information for the python-daemon distribution. """
+
+from version_info import version_info
+
+version_info['version_string'] = u"1.5.5"
+
+version_short = u"%(version_string)s" % version_info
+version_full = u"%(version_string)s.r%(revno)s" % version_info
+version = version_short
+
+author_name = u"Ben Finney"
+author_email = u"ben+python@benfinney.id.au"
+author = u"%(author_name)s <%(author_email)s>" % vars()
+
+copyright_year_begin = u"2001"
+date = version_info['date'].split(' ', 1)[0]
+copyright_year = date.split('-')[0]
+copyright_year_range = copyright_year_begin
+if copyright_year > copyright_year_begin:
+ copyright_year_range += u"–%(copyright_year)s" % vars()
+
+copyright = (
+ u"Copyright © %(copyright_year_range)s %(author)s and others"
+ ) % vars()
+license = u"PSF-2+"
diff --git a/vendor/python-daemon/daemon/version/version_info.py b/vendor/python-daemon/daemon/version/version_info.py
new file mode 100644
index 0000000000..cdbf280a83
--- /dev/null
+++ b/vendor/python-daemon/daemon/version/version_info.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+"""This file is automatically generated by generate_version_info
+It uses the current working tree to determine the revision.
+So don't edit it. :)
+"""
+
+version_info = {'branch_nick': u'python-daemon.devel',
+ 'build_date': '2009-05-22 19:50:06 +1000',
+ 'clean': None,
+ 'date': '2009-05-22 19:47:30 +1000',
+ 'revision_id': 'ben+python@benfinney.id.au-20090522094730-p4vsa0reh7ktt4e1',
+ 'revno': 145}
+
+revisions = {}
+
+file_revisions = {}
+
+
+
+if __name__ == '__main__':
+ print 'revision: %(revno)d' % version_info
+ print 'nick: %(branch_nick)s' % version_info
+ print 'revision id: %(revision_id)s' % version_info
diff --git a/vendor/python-daemon/python_daemon.egg-info/PKG-INFO b/vendor/python-daemon/python_daemon.egg-info/PKG-INFO
new file mode 100644
index 0000000000..df8f5531b2
--- /dev/null
+++ b/vendor/python-daemon/python_daemon.egg-info/PKG-INFO
@@ -0,0 +1,37 @@
+Metadata-Version: 1.0
+Name: python-daemon
+Version: 1.5.5
+Summary: Library to implement a well-behaved Unix daemon process.
+Home-page: http://pypi.python.org/pypi/python-daemon/
+Author: Ben Finney
+Author-email: ben+python@benfinney.id.au
+License: PSF-2+
+Description: This library implements the well-behaved daemon specification of
+ :pep:`3143`, "Standard daemon process library".
+
+ A well-behaved Unix daemon process is tricky to get right, but the
+ required steps are much the same for every daemon program. A
+ `DaemonContext` instance holds the behaviour and configured
+ process environment for the program; use the instance as a context
+ manager to enter a daemon state.
+
+ Simple example of usage::
+
+ import daemon
+
+ from spam import do_main_program
+
+ with daemon.DaemonContext():
+ do_main_program()
+
+ Customisation of the steps to become a daemon is available by
+ setting options on the `DaemonContext` instance; see the
+ documentation for that class for each option.
+Keywords: daemon,fork,unix
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python
+Classifier: Intended Audience :: Developers
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/vendor/python-daemon/python_daemon.egg-info/SOURCES.txt b/vendor/python-daemon/python_daemon.egg-info/SOURCES.txt
new file mode 100644
index 0000000000..ab2b52368e
--- /dev/null
+++ b/vendor/python-daemon/python_daemon.egg-info/SOURCES.txt
@@ -0,0 +1,22 @@
+ChangeLog
+LICENSE.GPL-2
+LICENSE.PSF-2
+MANIFEST.in
+setup.py
+daemon/__init__.py
+daemon/daemon.py
+daemon/pidlockfile.py
+daemon/runner.py
+daemon/version/__init__.py
+daemon/version/version_info.py
+python_daemon.egg-info/PKG-INFO
+python_daemon.egg-info/SOURCES.txt
+python_daemon.egg-info/dependency_links.txt
+python_daemon.egg-info/not-zip-safe
+python_daemon.egg-info/requires.txt
+python_daemon.egg-info/top_level.txt
+test/__init__.py
+test/scaffold.py
+test/test_daemon.py
+test/test_pidlockfile.py
+test/test_runner.py \ No newline at end of file
diff --git a/vendor/python-daemon/python_daemon.egg-info/dependency_links.txt b/vendor/python-daemon/python_daemon.egg-info/dependency_links.txt
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/vendor/python-daemon/python_daemon.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/vendor/python-daemon/python_daemon.egg-info/not-zip-safe b/vendor/python-daemon/python_daemon.egg-info/not-zip-safe
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/vendor/python-daemon/python_daemon.egg-info/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/vendor/python-daemon/python_daemon.egg-info/requires.txt b/vendor/python-daemon/python_daemon.egg-info/requires.txt
new file mode 100644
index 0000000000..1c7ae21661
--- /dev/null
+++ b/vendor/python-daemon/python_daemon.egg-info/requires.txt
@@ -0,0 +1,2 @@
+setuptools
+lockfile >=0.7 \ No newline at end of file
diff --git a/vendor/python-daemon/python_daemon.egg-info/top_level.txt b/vendor/python-daemon/python_daemon.egg-info/top_level.txt
new file mode 100644
index 0000000000..28e3ee0c0b
--- /dev/null
+++ b/vendor/python-daemon/python_daemon.egg-info/top_level.txt
@@ -0,0 +1 @@
+daemon
diff --git a/vendor/python-daemon/setup.cfg b/vendor/python-daemon/setup.cfg
new file mode 100644
index 0000000000..861a9f5542
--- /dev/null
+++ b/vendor/python-daemon/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/vendor/python-daemon/setup.py b/vendor/python-daemon/setup.py
new file mode 100644
index 0000000000..8570c8ae2a
--- /dev/null
+++ b/vendor/python-daemon/setup.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+# setup.py
+# Part of python-daemon, an implementation of PEP 3143.
+#
+# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2008 Robert Niederreiter, Jens Klein
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Python Software Foundation License, version 2 or
+# later as published by the Python Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+
+""" Distribution setup for python-daemon library.
+ """
+
+import textwrap
+from setuptools import setup, find_packages
+
+distribution_name = "python-daemon"
+main_module_name = 'daemon'
+main_module = __import__(main_module_name, fromlist=['version'])
+version = main_module.version
+
+short_description, long_description = (
+ textwrap.dedent(d).strip()
+ for d in main_module.__doc__.split(u'\n\n', 1)
+ )
+
+
+setup(
+ name=distribution_name,
+ version=version.version,
+ packages=find_packages(exclude=["test"]),
+
+ # setuptools metadata
+ zip_safe=False,
+ test_suite="test.suite",
+ tests_require=[
+ "MiniMock >=1.2.2",
+ ],
+ install_requires=[
+ "setuptools",
+ "lockfile >=0.7",
+ ],
+
+ # PyPI metadata
+ author=version.author_name,
+ author_email=version.author_email,
+ description=short_description,
+ license=version.license,
+ keywords=u"daemon fork unix".split(),
+ url=main_module._url,
+ long_description=long_description,
+ classifiers=[
+ # Reference: http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ "Development Status :: 4 - Beta",
+ "License :: OSI Approved :: Python Software Foundation License",
+ "Operating System :: POSIX",
+ "Programming Language :: Python",
+ "Intended Audience :: Developers",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ ],
+ )
diff --git a/vendor/python-gflags/AUTHORS b/vendor/python-gflags/AUTHORS
new file mode 100644
index 0000000000..ee92be88dc
--- /dev/null
+++ b/vendor/python-gflags/AUTHORS
@@ -0,0 +1,2 @@
+opensource@google.com
+
diff --git a/vendor/python-gflags/COPYING b/vendor/python-gflags/COPYING
new file mode 100644
index 0000000000..d15b0c2413
--- /dev/null
+++ b/vendor/python-gflags/COPYING
@@ -0,0 +1,28 @@
+Copyright (c) 2006, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/python-gflags/ChangeLog b/vendor/python-gflags/ChangeLog
new file mode 100644
index 0000000000..16a5eca0d6
--- /dev/null
+++ b/vendor/python-gflags/ChangeLog
@@ -0,0 +1,5 @@
+Mon Jan 4 18:46:29 2010 Tim 'mithro' Ansell <mithro@mithis.com>
+
+ * python-gflags: version 1.3
+ * Fork from the C++ package (google-gflags 1.3)
+ * Add debian packaging
diff --git a/vendor/python-gflags/README b/vendor/python-gflags/README
new file mode 100644
index 0000000000..81daa7ab49
--- /dev/null
+++ b/vendor/python-gflags/README
@@ -0,0 +1,23 @@
+This repository contains a python implementation of the Google commandline
+flags module.
+
+ GFlags defines a *distributed* command line system, replacing systems like
+ getopt(), optparse and manual argument processing. Rather than an application
+ having to define all flags in or near main(), each python module defines flags
+ that are useful to it. When one python module imports another, it gains
+ access to the other's flags.
+
+ It includes the ability to define flag types (boolean, float, interger, list),
+ autogeneration of help (in both human and machine readable format) and reading
+ arguments from a file. It also includes the ability to automatically generate
+ man pages from the help flags.
+
+Documentation for implementation is at the top of gflags.py file.
+
+To install the python module, run
+ python ./setup.py install
+
+When you install this library, you also get a helper application,
+gflags2man.py, installed into /usr/local/bin. You can run gflags2man.py to
+create an instant man page, with all the commandline flags and their docs, for
+any C++ or python program you've written using the gflags library.
diff --git a/vendor/python-gflags/debian/README b/vendor/python-gflags/debian/README
new file mode 100644
index 0000000000..57becfda75
--- /dev/null
+++ b/vendor/python-gflags/debian/README
@@ -0,0 +1,7 @@
+The list of files here isn't complete. For a step-by-step guide on
+how to set this package up correctly, check out
+ http://www.debian.org/doc/maint-guide/
+
+Most of the files that are in this directory are boilerplate.
+However, you may need to change the list of binary-arch dependencies
+in 'rules'.
diff --git a/vendor/python-gflags/debian/changelog b/vendor/python-gflags/debian/changelog
new file mode 100644
index 0000000000..6f9aa6c991
--- /dev/null
+++ b/vendor/python-gflags/debian/changelog
@@ -0,0 +1,11 @@
+python-gflags (1.3-2) unstable; urgency=low
+
+ * Fixed man-page generation.
+
+ -- Tim 'mithro' Ansell <mithro@mithis.com> Mon, 07 Jan 2010 13:46:10 +1100
+python-gflags (1.3-1) unstable; urgency=low
+
+ * Initial release.
+ * Packaging based on gflags 1.3
+
+ -- Tim 'mithro' Ansell <mithro@mithis.com> Mon, 04 Jan 2010 18:46:10 -0800
diff --git a/vendor/python-gflags/debian/compat b/vendor/python-gflags/debian/compat
new file mode 100644
index 0000000000..7ed6ff82de
--- /dev/null
+++ b/vendor/python-gflags/debian/compat
@@ -0,0 +1 @@
+5
diff --git a/vendor/python-gflags/debian/control b/vendor/python-gflags/debian/control
new file mode 100644
index 0000000000..fb502d3bba
--- /dev/null
+++ b/vendor/python-gflags/debian/control
@@ -0,0 +1,26 @@
+Source: python-gflags
+Section: python
+XS-Python-Version: all
+Priority: optional
+Maintainer: Tim 'mithro' Ansell <mithro@mithis.com>
+Build-Depends-Indep: python-central (>= 0.5.6), python-setuptools (>= 0.6b3-1), python-all
+Build-Depends: debhelper (>= 5.0.38)
+Standards-Version: 3.7.2
+
+Package: python-gflags
+Architecture: all
+Depends: ${python:Depends}
+XB-Python-Version: ${python:Versions}
+Description: A Python implementation of the Google commandline flags module
+ .
+ GFlags defines a *distributed* command line system, replacing systems like
+ getopt(), optparse and manual argument processing. Rather than an application
+ having to define all flags in or near main(), each Python module defines flags
+ that are useful to it. When one Python module imports another, it gains
+ access to the other's flags.
+ .
+ It includes the ability to define flag types (boolean, float, interger, list),
+ autogeneration of help (in both human and machine readable format) and reading
+ arguments from a file. It also includes the ability to automatically generate
+ man pages from the help flags.
+
diff --git a/vendor/python-gflags/debian/copyright b/vendor/python-gflags/debian/copyright
new file mode 100644
index 0000000000..5100c02e6d
--- /dev/null
+++ b/vendor/python-gflags/debian/copyright
@@ -0,0 +1,41 @@
+This package was debianized by Tim 'mithro' Ansell <mithro@mithis.com> on
+Thu, 12 Nov 2009 11:25:53 +1100.
+
+It was downloaded from http://code.google.com/p/google-gflags/downloads/list
+
+Upstream Author: Google Inc. <opensource@google.com>
+Copyright: Google Inc. <opensource@google.com>
+
+License:
+
+Copyright (c) 2006, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The Debian packaging is (C) 2009, Tim 'mithro' Ansell <mithro@mithis.com> and
+is licensed under the above.
diff --git a/vendor/python-gflags/debian/docs b/vendor/python-gflags/debian/docs
new file mode 100644
index 0000000000..6f12db5084
--- /dev/null
+++ b/vendor/python-gflags/debian/docs
@@ -0,0 +1,2 @@
+AUTHORS
+README
diff --git a/vendor/python-gflags/debian/rules b/vendor/python-gflags/debian/rules
new file mode 100755
index 0000000000..e29f988381
--- /dev/null
+++ b/vendor/python-gflags/debian/rules
@@ -0,0 +1,62 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+PYTHON := /usr/bin/python
+#PYVER := $(shell $(PYTHON) -c 'import sys; print sys.version[:3]')
+PYVERS = $(shell pyversions -vr)
+
+build: $(PYVERS:%=build-python%)
+ touch $@
+
+build-python%:
+ dh_testdir
+ python$* setup.py build
+ touch $@
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-python*
+ rm -rf build
+ -find . -name '*.py[co]' | xargs rm -f
+ dh_clean
+
+install: build $(PYVERS:%=install-python%)
+
+install-python%:
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+ python$* setup.py install --root=$(CURDIR)/debian/python-gflags
+ # Scripts should not have a .py on the end of them
+ mv $(CURDIR)/debian/python-gflags/usr/bin/gflags2man.py $(CURDIR)/debian/python-gflags/usr/bin/gflags2man
+ # Generate a man file for gflags2man
+ mkdir -p $(CURDIR)/debian/python-gflags/usr/share/man/man1
+ PYTHONPATH=$(CURDIR)/debian/.. python$* gflags2man.py --dest_dir $(CURDIR)/debian/python-gflags/usr/share/man/man1 $(CURDIR)/debian/python-gflags/usr/bin/gflags2man
+
+# Build architecture-independent files here.
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs -k ChangeLog
+ dh_installdocs
+ dh_pycentral
+ dh_compress -X.py
+ dh_fixperms
+ dh_installdeb
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+# Build architecture-dependent files here.
+binary-arch: build install
+# We have nothing to do by default.
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/vendor/python-gflags/gflags.py b/vendor/python-gflags/gflags.py
new file mode 100644
index 0000000000..1e4659e328
--- /dev/null
+++ b/vendor/python-gflags/gflags.py
@@ -0,0 +1,2340 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2007, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# ---
+# Author: Chad Lester
+# Design and style contributions by:
+# Amit Patel, Bogdan Cocosel, Daniel Dulitz, Eric Tiedemann,
+# Eric Veach, Laurence Gonsalves, Matthew Springer
+# Code reorganized a bit by Craig Silverstein
+
+"""This module is used to define and parse command line flags.
+
+This module defines a *distributed* flag-definition policy: rather than
+an application having to define all flags in or near main(), each python
+module defines flags that are useful to it. When one python module
+imports another, it gains access to the other's flags. (This is
+implemented by having all modules share a common, global registry object
+containing all the flag information.)
+
+Flags are defined through the use of one of the DEFINE_xxx functions.
+The specific function used determines how the flag is parsed, checked,
+and optionally type-converted, when it's seen on the command line.
+
+
+IMPLEMENTATION: DEFINE_* creates a 'Flag' object and registers it with a
+'FlagValues' object (typically the global FlagValues FLAGS, defined
+here). The 'FlagValues' object can scan the command line arguments and
+pass flag arguments to the corresponding 'Flag' objects for
+value-checking and type conversion. The converted flag values are
+available as attributes of the 'FlagValues' object.
+
+Code can access the flag through a FlagValues object, for instance
+gflags.FLAGS.myflag. Typically, the __main__ module passes the
+command line arguments to gflags.FLAGS for parsing.
+
+At bottom, this module calls getopt(), so getopt functionality is
+supported, including short- and long-style flags, and the use of -- to
+terminate flags.
+
+Methods defined by the flag module will throw 'FlagsError' exceptions.
+The exception argument will be a human-readable string.
+
+
+FLAG TYPES: This is a list of the DEFINE_*'s that you can do. All flags
+take a name, default value, help-string, and optional 'short' name
+(one-letter name). Some flags have other arguments, which are described
+with the flag.
+
+DEFINE_string: takes any input, and interprets it as a string.
+
+DEFINE_bool or
+DEFINE_boolean: typically does not take an argument: say --myflag to
+ set FLAGS.myflag to true, or --nomyflag to set
+ FLAGS.myflag to false. Alternately, you can say
+ --myflag=true or --myflag=t or --myflag=1 or
+ --myflag=false or --myflag=f or --myflag=0
+
+DEFINE_float: takes an input and interprets it as a floating point
+ number. Takes optional args lower_bound and upper_bound;
+ if the number specified on the command line is out of
+ range, it will raise a FlagError.
+
+DEFINE_integer: takes an input and interprets it as an integer. Takes
+ optional args lower_bound and upper_bound as for floats.
+
+DEFINE_enum: takes a list of strings which represents legal values. If
+ the command-line value is not in this list, raise a flag
+ error. Otherwise, assign to FLAGS.flag as a string.
+
+DEFINE_list: Takes a comma-separated list of strings on the commandline.
+ Stores them in a python list object.
+
+DEFINE_spaceseplist: Takes a space-separated list of strings on the
+ commandline. Stores them in a python list object.
+ Example: --myspacesepflag "foo bar baz"
+
+DEFINE_multistring: The same as DEFINE_string, except the flag can be
+ specified more than once on the commandline. The
+ result is a python list object (list of strings),
+ even if the flag is only on the command line once.
+
+DEFINE_multi_int: The same as DEFINE_integer, except the flag can be
+ specified more than once on the commandline. The
+ result is a python list object (list of ints), even if
+ the flag is only on the command line once.
+
+
+SPECIAL FLAGS: There are a few flags that have special meaning:
+ --help prints a list of all the flags in a human-readable fashion
+ --helpshort prints a list of all key flags (see below).
+ --helpxml prints a list of all flags, in XML format. DO NOT parse
+ the output of --help and --helpshort. Instead, parse
+ the output of --helpxml. As we add new flags, we may
+ add new XML elements. Hence, make sure your parser
+ does not crash when it encounters new XML elements.
+ --flagfile=foo read flags from foo.
+ --undefok=f1,f2 ignore unrecognized option errors for f1,f2.
+ For boolean flags, you should use --undefok=boolflag, and
+ --boolflag and --noboolflag will be accepted. Do not use
+ --undefok=noboolflag.
+ -- as in getopt(), terminates flag-processing
+
+
+NOTE ON --flagfile:
+
+Flags may be loaded from text files in addition to being specified on
+the commandline.
+
+Any flags you don't feel like typing, throw them in a file, one flag per
+line, for instance:
+ --myflag=myvalue
+ --nomyboolean_flag
+You then specify your file with the special flag '--flagfile=somefile'.
+You CAN recursively nest flagfile= tokens OR use multiple files on the
+command line. Lines beginning with a single hash '#' or a double slash
+'//' are comments in your flagfile.
+
+Any flagfile=<file> will be interpreted as having a relative path from
+the current working directory rather than from the place the file was
+included from:
+ myPythonScript.py --flagfile=config/somefile.cfg
+
+If somefile.cfg includes further --flagfile= directives, these will be
+referenced relative to the original CWD, not from the directory the
+including flagfile was found in!
+
+The caveat applies to people who are including a series of nested files
+in a different dir than they are executing out of. Relative path names
+are always from CWD, not from the directory of the parent include
+flagfile. We do now support '~' expanded directory names.
+
+Absolute path names ALWAYS work!
+
+
+EXAMPLE USAGE:
+
+ import gflags
+ FLAGS = gflags.FLAGS
+
+ # Flag names are globally defined! So in general, we need to be
+ # careful to pick names that are unlikely to be used by other libraries.
+ # If there is a conflict, we'll get an error at import time.
+ gflags.DEFINE_string('name', 'Mr. President', 'your name')
+ gflags.DEFINE_integer('age', None, 'your age in years', lower_bound=0)
+ gflags.DEFINE_boolean('debug', False, 'produces debugging output')
+ gflags.DEFINE_enum('gender', 'male', ['male', 'female'], 'your gender')
+
+ def main(argv):
+ try:
+ argv = FLAGS(argv) # parse flags
+ except gflags.FlagsError, e:
+ print '%s\\nUsage: %s ARGS\\n%s' % (e, sys.argv[0], FLAGS)
+ sys.exit(1)
+ if FLAGS.debug: print 'non-flag arguments:', argv
+ print 'Happy Birthday', FLAGS.name
+ if FLAGS.age is not None:
+ print 'You are a %s, who is %d years old' % (FLAGS.gender, FLAGS.age)
+
+ if __name__ == '__main__':
+ main(sys.argv)
+
+
+KEY FLAGS:
+
+As we already explained, each module gains access to all flags defined
+by all the other modules it transitively imports. In the case of
+non-trivial scripts, this means a lot of flags ... For documentation
+purposes, it is good to identify the flags that are key (i.e., really
+important) to a module. Clearly, the concept of "key flag" is a
+subjective one. When trying to determine whether a flag is key to a
+module or not, assume that you are trying to explain your module to a
+potential user: which flags would you really like to mention first?
+
+We'll describe shortly how to declare which flags are key to a module.
+For the moment, assume we know the set of key flags for each module.
+Then, if you use the app.py module, you can use the --helpshort flag to
+print only the help for the flags that are key to the main module, in a
+human-readable format.
+
+NOTE: If you need to parse the flag help, do NOT use the output of
+--help / --helpshort. That output is meant for human consumption, and
+may be changed in the future. Instead, use --helpxml; flags that are
+key for the main module are marked there with a <key>yes</key> element.
+
+The set of key flags for a module M is composed of:
+
+1. Flags defined by module M by calling a DEFINE_* function.
+
+2. Flags that module M explictly declares as key by using the function
+
+ DECLARE_key_flag(<flag_name>)
+
+3. Key flags of other modules that M specifies by using the function
+
+ ADOPT_module_key_flags(<other_module>)
+
+ This is a "bulk" declaration of key flags: each flag that is key for
+ <other_module> becomes key for the current module too.
+
+Notice that if you do not use the functions described at points 2 and 3
+above, then --helpshort prints information only about the flags defined
+by the main module of our script. In many cases, this behavior is good
+enough. But if you move part of the main module code (together with the
+related flags) into a different module, then it is nice to use
+DECLARE_key_flag / ADOPT_module_key_flags and make sure --helpshort
+lists all relevant flags (otherwise, your code refactoring may confuse
+your users).
+
+Note: each of DECLARE_key_flag / ADOPT_module_key_flags has its own
+pluses and minuses: DECLARE_key_flag is more targeted and may lead a
+more focused --helpshort documentation. ADOPT_module_key_flags is good
+for cases when an entire module is considered key to the current script.
+Also, it does not require updates to client scripts when a new flag is
+added to the module.
+
+
+EXAMPLE USAGE 2 (WITH KEY FLAGS):
+
+Consider an application that contains the following three files (two
+auxiliary modules and a main module):
+
+File libfoo.py:
+
+ import gflags
+
+ gflags.DEFINE_integer('num_replicas', 3, 'Number of replicas to start')
+ gflags.DEFINE_boolean('rpc2', True, 'Turn on the usage of RPC2.')
+
+ ... some code ...
+
+File libbar.py:
+
+ import gflags
+
+ gflags.DEFINE_string('bar_gfs_path', '/gfs/path',
+ 'Path to the GFS files for libbar.')
+ gflags.DEFINE_string('email_for_bar_errors', 'bar-team@google.com',
+ 'Email address for bug reports about module libbar.')
+ gflags.DEFINE_boolean('bar_risky_hack', False,
+ 'Turn on an experimental and buggy optimization.')
+
+ ... some code ...
+
+File myscript.py:
+
+ import gflags
+ import libfoo
+ import libbar
+
+ gflags.DEFINE_integer('num_iterations', 0, 'Number of iterations.')
+
+ # Declare that all flags that are key for libfoo are
+ # key for this module too.
+ gflags.ADOPT_module_key_flags(libfoo)
+
+ # Declare that the flag --bar_gfs_path (defined in libbar) is key
+ # for this module.
+ gflags.DECLARE_key_flag('bar_gfs_path')
+
+ ... some code ...
+
+When myscript is invoked with the flag --helpshort, the resulted help
+message lists information about all the key flags for myscript:
+--num_iterations, --num_replicas, --rpc2, and --bar_gfs_path (in
+addition to the special flags --help and --helpshort).
+
+Of course, myscript uses all the flags declared by it (in this case,
+just --num_replicas) or by any of the modules it transitively imports
+(e.g., the modules libfoo, libbar). E.g., it can access the value of
+FLAGS.bar_risky_hack, even if --bar_risky_hack is not declared as a key
+flag for myscript.
+"""
+
+import cgi
+import getopt
+import os
+import re
+import string
+import sys
+
+# Are we running at least python 2.2?
+try:
+ if tuple(sys.version_info[:3]) < (2,2,0):
+ raise NotImplementedError("requires python 2.2.0 or later")
+except AttributeError: # a very old python, that lacks sys.version_info
+ raise NotImplementedError("requires python 2.2.0 or later")
+
+# If we're not running at least python 2.2.1, define True, False, and bool.
+# Thanks, Guido, for the code.
+try:
+ True, False, bool
+except NameError:
+ False = 0
+ True = 1
+ def bool(x):
+ if x:
+ return True
+ else:
+ return False
+
+# Are we running under pychecker?
+_RUNNING_PYCHECKER = 'pychecker.python' in sys.modules
+
+
+def _GetCallingModule():
+ """Returns the name of the module that's calling into this module.
+
+ We generally use this function to get the name of the module calling a
+ DEFINE_foo... function.
+ """
+ # Walk down the stack to find the first globals dict that's not ours.
+ for depth in range(1, sys.getrecursionlimit()):
+ if not sys._getframe(depth).f_globals is globals():
+ module_name = __GetModuleName(sys._getframe(depth).f_globals)
+ if module_name is not None:
+ return module_name
+ raise AssertionError("No module was found")
+
+
+# module exceptions:
+class FlagsError(Exception):
+ """The base class for all flags errors."""
+ pass
+
+
+class DuplicateFlag(FlagsError):
+ """Raised if there is a flag naming conflict."""
+ pass
+
+
+# A DuplicateFlagError conveys more information than a
+# DuplicateFlag. Since there are external modules that create
+# DuplicateFlags, the interface to DuplicateFlag shouldn't change.
+class DuplicateFlagError(DuplicateFlag):
+
+ def __init__(self, flagname, flag_values):
+ self.flagname = flagname
+ message = "The flag '%s' is defined twice." % self.flagname
+ flags_by_module = flag_values.FlagsByModuleDict()
+ for module in flags_by_module:
+ for flag in flags_by_module[module]:
+ if flag.name == flagname or flag.short_name == flagname:
+ message = message + " First from " + module + ","
+ break
+ message = message + " Second from " + _GetCallingModule()
+ DuplicateFlag.__init__(self, message)
+
+
+class IllegalFlagValue(FlagsError):
+ """The flag command line argument is illegal."""
+ pass
+
+
+class UnrecognizedFlag(FlagsError):
+ """Raised if a flag is unrecognized."""
+ pass
+
+
+# An UnrecognizedFlagError conveys more information than an
+# UnrecognizedFlag. Since there are external modules that create
+# DuplicateFlags, the interface to DuplicateFlag shouldn't change.
+class UnrecognizedFlagError(UnrecognizedFlag):
+ def __init__(self, flagname):
+ self.flagname = flagname
+ UnrecognizedFlag.__init__(
+ self, "Unknown command line flag '%s'" % flagname)
+
+
+# Global variable used by expvar
+_exported_flags = {}
+_help_width = 80 # width of help output
+
+
+def GetHelpWidth():
+ """Returns: an integer, the width of help lines that is used in TextWrap."""
+ return _help_width
+
+
+def CutCommonSpacePrefix(text):
+ """Removes a common space prefix from the lines of a multiline text.
+
+ If the first line does not start with a space, it is left as it is and
+ only in the remaining lines a common space prefix is being searched
+ for. That means the first line will stay untouched. This is especially
+ useful to turn doc strings into help texts. This is because some
+ people prefer to have the doc comment start already after the
+ apostrophy and then align the following lines while others have the
+ apostrophies on a seperately line.
+
+ The function also drops trailing empty lines and ignores empty lines
+ following the initial content line while calculating the initial
+ common whitespace.
+
+ Args:
+ text: text to work on
+
+ Returns:
+ the resulting text
+ """
+ text_lines = text.splitlines()
+ # Drop trailing empty lines
+ while text_lines and not text_lines[-1]:
+ text_lines = text_lines[:-1]
+ if text_lines:
+ # We got some content, is the first line starting with a space?
+ if text_lines[0] and text_lines[0][0].isspace():
+ text_first_line = []
+ else:
+ text_first_line = [text_lines.pop(0)]
+ # Calculate length of common leading whitesppace (only over content lines)
+ common_prefix = os.path.commonprefix([line for line in text_lines if line])
+ space_prefix_len = len(common_prefix) - len(common_prefix.lstrip())
+ # If we have a common space prefix, drop it from all lines
+ if space_prefix_len:
+ for index in xrange(len(text_lines)):
+ if text_lines[index]:
+ text_lines[index] = text_lines[index][space_prefix_len:]
+ return '\n'.join(text_first_line + text_lines)
+ return ''
+
+
+def TextWrap(text, length=None, indent='', firstline_indent=None, tabs=' '):
+ """Wraps a given text to a maximum line length and returns it.
+
+ We turn lines that only contain whitespaces into empty lines. We keep
+ new lines and tabs (e.g., we do not treat tabs as spaces).
+
+ Args:
+ text: text to wrap
+ length: maximum length of a line, includes indentation
+ if this is None then use GetHelpWidth()
+ indent: indent for all but first line
+ firstline_indent: indent for first line; if None, fall back to indent
+ tabs: replacement for tabs
+
+ Returns:
+ wrapped text
+
+ Raises:
+ FlagsError: if indent not shorter than length
+ FlagsError: if firstline_indent not shorter than length
+ """
+ # Get defaults where callee used None
+ if length is None:
+ length = GetHelpWidth()
+ if indent is None:
+ indent = ''
+ if len(indent) >= length:
+ raise FlagsError('Indent must be shorter than length')
+ # In line we will be holding the current line which is to be started
+ # with indent (or firstline_indent if available) and then appended
+ # with words.
+ if firstline_indent is None:
+ firstline_indent = ''
+ line = indent
+ else:
+ line = firstline_indent
+ if len(firstline_indent) >= length:
+ raise FlagsError('First iline indent must be shorter than length')
+
+ # If the callee does not care about tabs we simply convert them to
+ # spaces If callee wanted tabs to be single space then we do that
+ # already here.
+ if not tabs or tabs == ' ':
+ text = text.replace('\t', ' ')
+ else:
+ tabs_are_whitespace = not tabs.strip()
+
+ line_regex = re.compile('([ ]*)(\t*)([^ \t]+)', re.MULTILINE)
+
+ # Split the text into lines and the lines with the regex above. The
+ # resulting lines are collected in result[]. For each split we get the
+ # spaces, the tabs and the next non white space (e.g. next word).
+ result = []
+ for text_line in text.splitlines():
+ # Store result length so we can find out whether processing the next
+ # line gave any new content
+ old_result_len = len(result)
+ # Process next line with line_regex. For optimization we do an rstrip().
+ # - process tabs (changes either line or word, see below)
+ # - process word (first try to squeeze on line, then wrap or force wrap)
+ # Spaces found on the line are ignored, they get added while wrapping as
+ # needed.
+ for spaces, current_tabs, word in line_regex.findall(text_line.rstrip()):
+ # If tabs weren't converted to spaces, handle them now
+ if current_tabs:
+ # If the last thing we added was a space anyway then drop
+ # it. But let's not get rid of the indentation.
+ if (((result and line != indent) or
+ (not result and line != firstline_indent)) and line[-1] == ' '):
+ line = line[:-1]
+ # Add the tabs, if that means adding whitespace, just add it at
+ # the line, the rstrip() code while shorten the line down if
+ # necessary
+ if tabs_are_whitespace:
+ line += tabs * len(current_tabs)
+ else:
+ # if not all tab replacement is whitespace we prepend it to the word
+ word = tabs * len(current_tabs) + word
+ # Handle the case where word cannot be squeezed onto current last line
+ if len(line) + len(word) > length and len(indent) + len(word) <= length:
+ result.append(line.rstrip())
+ line = indent + word
+ word = ''
+ # No space left on line or can we append a space?
+ if len(line) + 1 >= length:
+ result.append(line.rstrip())
+ line = indent
+ else:
+ line += ' '
+ # Add word and shorten it up to allowed line length. Restart next
+ # line with indent and repeat, or add a space if we're done (word
+ # finished) This deals with words that caanot fit on one line
+ # (e.g. indent + word longer than allowed line length).
+ while len(line) + len(word) >= length:
+ line += word
+ result.append(line[:length])
+ word = line[length:]
+ line = indent
+ # Default case, simply append the word and a space
+ if word:
+ line += word + ' '
+ # End of input line. If we have content we finish the line. If the
+ # current line is just the indent but we had content in during this
+ # original line then we need to add an emoty line.
+ if (result and line != indent) or (not result and line != firstline_indent):
+ result.append(line.rstrip())
+ elif len(result) == old_result_len:
+ result.append('')
+ line = indent
+
+ return '\n'.join(result)
+
+
+def DocToHelp(doc):
+ """Takes a __doc__ string and reformats it as help."""
+
+ # Get rid of starting and ending white space. Using lstrip() or even
+ # strip() could drop more than maximum of first line and right space
+ # of last line.
+ doc = doc.strip()
+
+ # Get rid of all empty lines
+ whitespace_only_line = re.compile('^[ \t]+$', re.M)
+ doc = whitespace_only_line.sub('', doc)
+
+ # Cut out common space at line beginnings
+ doc = CutCommonSpacePrefix(doc)
+
+ # Just like this module's comment, comments tend to be aligned somehow.
+ # In other words they all start with the same amount of white space
+ # 1) keep double new lines
+ # 2) keep ws after new lines if not empty line
+ # 3) all other new lines shall be changed to a space
+ # Solution: Match new lines between non white space and replace with space.
+ doc = re.sub('(?<=\S)\n(?=\S)', ' ', doc, re.M)
+
+ return doc
+
+
+def __GetModuleName(globals_dict):
+ """Given a globals dict, returns the name of the module that defines it.
+
+ Args:
+ globals_dict: A dictionary that should correspond to an environment
+ providing the values of the globals.
+
+ Returns:
+ A string (the name of the module) or None (if the module could not
+ be identified.
+ """
+ for name, module in sys.modules.iteritems():
+ if getattr(module, '__dict__', None) is globals_dict:
+ if name == '__main__':
+ return sys.argv[0]
+ return name
+ return None
+
+
+def _GetMainModule():
+ """Returns the name of the module from which execution started."""
+ for depth in range(1, sys.getrecursionlimit()):
+ try:
+ globals_of_main = sys._getframe(depth).f_globals
+ except ValueError:
+ return __GetModuleName(globals_of_main)
+ raise AssertionError("No module was found")
+
+
+class FlagValues:
+ """Registry of 'Flag' objects.
+
+ A 'FlagValues' can then scan command line arguments, passing flag
+ arguments through to the 'Flag' objects that it owns. It also
+ provides easy access to the flag values. Typically only one
+ 'FlagValues' object is needed by an application: gflags.FLAGS
+
+ This class is heavily overloaded:
+
+ 'Flag' objects are registered via __setitem__:
+ FLAGS['longname'] = x # register a new flag
+
+ The .value attribute of the registered 'Flag' objects can be accessed
+ as attributes of this 'FlagValues' object, through __getattr__. Both
+ the long and short name of the original 'Flag' objects can be used to
+ access its value:
+ FLAGS.longname # parsed flag value
+ FLAGS.x # parsed flag value (short name)
+
+ Command line arguments are scanned and passed to the registered 'Flag'
+ objects through the __call__ method. Unparsed arguments, including
+ argv[0] (e.g. the program name) are returned.
+ argv = FLAGS(sys.argv) # scan command line arguments
+
+ The original registered Flag objects can be retrieved through the use
+ of the dictionary-like operator, __getitem__:
+ x = FLAGS['longname'] # access the registered Flag object
+
+ The str() operator of a 'FlagValues' object provides help for all of
+ the registered 'Flag' objects.
+ """
+
+ def __init__(self):
+ # Since everything in this class is so heavily overloaded, the only
+ # way of defining and using fields is to access __dict__ directly.
+
+ # Dictionary: flag name (string) -> Flag object.
+ self.__dict__['__flags'] = {}
+ # Dictionary: module name (string) -> list of Flag objects that are defined
+ # by that module.
+ self.__dict__['__flags_by_module'] = {}
+ # Dictionary: module name (string) -> list of Flag objects that are
+ # key for that module.
+ self.__dict__['__key_flags_by_module'] = {}
+
+ # Set if we should use new style gnu_getopt rather than getopt when parsing
+ # the args. Only possible with Python 2.3+
+ self.UseGnuGetOpt(False)
+
+ def UseGnuGetOpt(self, use_gnu_getopt=True):
+ self.__dict__['__use_gnu_getopt'] = use_gnu_getopt
+
+ def IsGnuGetOpt(self):
+ return self.__dict__['__use_gnu_getopt']
+
+ def FlagDict(self):
+ return self.__dict__['__flags']
+
+ def FlagsByModuleDict(self):
+ """Returns the dictionary of module_name -> list of defined flags.
+
+ Returns:
+ A dictionary. Its keys are module names (strings). Its values
+ are lists of Flag objects.
+ """
+ return self.__dict__['__flags_by_module']
+
+ def KeyFlagsByModuleDict(self):
+ """Returns the dictionary of module_name -> list of key flags.
+
+ Returns:
+ A dictionary. Its keys are module names (strings). Its values
+ are lists of Flag objects.
+ """
+ return self.__dict__['__key_flags_by_module']
+
+ def _RegisterFlagByModule(self, module_name, flag):
+ """Records the module that defines a specific flag.
+
+ We keep track of which flag is defined by which module so that we
+ can later sort the flags by module.
+
+ Args:
+ module_name: A string, the name of a Python module.
+ flag: A Flag object, a flag that is key to the module.
+ """
+ flags_by_module = self.FlagsByModuleDict()
+ flags_by_module.setdefault(module_name, []).append(flag)
+
+ def _RegisterKeyFlagForModule(self, module_name, flag):
+ """Specifies that a flag is a key flag for a module.
+
+ Args:
+ module_name: A string, the name of a Python module.
+ flag: A Flag object, a flag that is key to the module.
+ """
+ key_flags_by_module = self.KeyFlagsByModuleDict()
+ # The list of key flags for the module named module_name.
+ key_flags = key_flags_by_module.setdefault(module_name, [])
+ # Add flag, but avoid duplicates.
+ if flag not in key_flags:
+ key_flags.append(flag)
+
+ def _GetFlagsDefinedByModule(self, module):
+ """Returns the list of flags defined by a module.
+
+ Args:
+ module: A module object or a module name (a string).
+
+ Returns:
+ A new list of Flag objects. Caller may update this list as he
+ wishes: none of those changes will affect the internals of this
+ FlagValue object.
+ """
+ if not isinstance(module, str):
+ module = module.__name__
+
+ return list(self.FlagsByModuleDict().get(module, []))
+
+ def _GetKeyFlagsForModule(self, module):
+ """Returns the list of key flags for a module.
+
+ Args:
+ module: A module object or a module name (a string)
+
+ Returns:
+ A new list of Flag objects. Caller may update this list as he
+ wishes: none of those changes will affect the internals of this
+ FlagValue object.
+ """
+ if not isinstance(module, str):
+ module = module.__name__
+
+ # Any flag is a key flag for the module that defined it. NOTE:
+ # key_flags is a fresh list: we can update it without affecting the
+ # internals of this FlagValues object.
+ key_flags = self._GetFlagsDefinedByModule(module)
+
+ # Take into account flags explicitly declared as key for a module.
+ for flag in self.KeyFlagsByModuleDict().get(module, []):
+ if flag not in key_flags:
+ key_flags.append(flag)
+ return key_flags
+
+ def AppendFlagValues(self, flag_values):
+ """Appends flags registered in another FlagValues instance.
+
+ Args:
+ flag_values: registry to copy from
+ """
+ for flag_name, flag in flag_values.FlagDict().iteritems():
+ # Each flags with shortname appears here twice (once under its
+ # normal name, and again with its short name). To prevent
+ # problems (DuplicateFlagError) with double flag registration, we
+ # perform a check to make sure that the entry we're looking at is
+ # for its normal name.
+ if flag_name == flag.name:
+ self[flag_name] = flag
+
+ def __setitem__(self, name, flag):
+ """Registers a new flag variable."""
+ fl = self.FlagDict()
+ if not isinstance(flag, Flag):
+ raise IllegalFlagValue(flag)
+ if not isinstance(name, type("")):
+ raise FlagsError("Flag name must be a string")
+ if len(name) == 0:
+ raise FlagsError("Flag name cannot be empty")
+ # If running under pychecker, duplicate keys are likely to be
+ # defined. Disable check for duplicate keys when pycheck'ing.
+ if (fl.has_key(name) and not flag.allow_override and
+ not fl[name].allow_override and not _RUNNING_PYCHECKER):
+ raise DuplicateFlagError(name, self)
+ short_name = flag.short_name
+ if short_name is not None:
+ if (fl.has_key(short_name) and not flag.allow_override and
+ not fl[short_name].allow_override and not _RUNNING_PYCHECKER):
+ raise DuplicateFlagError(short_name, self)
+ fl[short_name] = flag
+ fl[name] = flag
+ global _exported_flags
+ _exported_flags[name] = flag
+
+ def __getitem__(self, name):
+ """Retrieves the Flag object for the flag --name."""
+ return self.FlagDict()[name]
+
+ def __getattr__(self, name):
+ """Retrieves the 'value' attribute of the flag --name."""
+ fl = self.FlagDict()
+ if not fl.has_key(name):
+ raise AttributeError(name)
+ return fl[name].value
+
+ def __setattr__(self, name, value):
+ """Sets the 'value' attribute of the flag --name."""
+ fl = self.FlagDict()
+ fl[name].value = value
+ return value
+
+ def _FlagIsRegistered(self, flag_obj):
+ """Checks whether a Flag object is registered under some name.
+
+ Note: this is non trivial: in addition to its normal name, a flag
+ may have a short name too. In self.FlagDict(), both the normal and
+ the short name are mapped to the same flag object. E.g., calling
+ only "del FLAGS.short_name" is not unregistering the corresponding
+ Flag object (it is still registered under the longer name).
+
+ Args:
+ flag_obj: A Flag object.
+
+ Returns:
+ A boolean: True iff flag_obj is registered under some name.
+ """
+ flag_dict = self.FlagDict()
+ # Check whether flag_obj is registered under its long name.
+ name = flag_obj.name
+ if flag_dict.get(name, None) == flag_obj:
+ return True
+ # Check whether flag_obj is registered under its short name.
+ short_name = flag_obj.short_name
+ if (short_name is not None and
+ flag_dict.get(short_name, None) == flag_obj):
+ return True
+ # The flag cannot be registered under any other name, so we do not
+ # need to do a full search through the values of self.FlagDict().
+ return False
+
+ def __delattr__(self, flag_name):
+ """Deletes a previously-defined flag from a flag object.
+
+ This method makes sure we can delete a flag by using
+
+ del flag_values_object.<flag_name>
+
+ E.g.,
+
+ flags.DEFINE_integer('foo', 1, 'Integer flag.')
+ del flags.FLAGS.foo
+
+ Args:
+ flag_name: A string, the name of the flag to be deleted.
+
+ Raises:
+ AttributeError: When there is no registered flag named flag_name.
+ """
+ fl = self.FlagDict()
+ if flag_name not in fl:
+ raise AttributeError(flag_name)
+
+ flag_obj = fl[flag_name]
+ del fl[flag_name]
+
+ if not self._FlagIsRegistered(flag_obj):
+ # If the Flag object indicated by flag_name is no longer
+ # registered (please see the docstring of _FlagIsRegistered), then
+ # we delete the occurences of the flag object in all our internal
+ # dictionaries.
+ self.__RemoveFlagFromDictByModule(self.FlagsByModuleDict(), flag_obj)
+ self.__RemoveFlagFromDictByModule(self.KeyFlagsByModuleDict(), flag_obj)
+
+ def __RemoveFlagFromDictByModule(self, flags_by_module_dict, flag_obj):
+ """Removes a flag object from a module -> list of flags dictionary.
+
+ Args:
+ flags_by_module_dict: A dictionary that maps module names to lists of
+ flags.
+ flag_obj: A flag object.
+ """
+ for unused_module, flags_in_module in flags_by_module_dict.iteritems():
+ # while (as opposed to if) takes care of multiple occurences of a
+ # flag in the list for the same module.
+ while flag_obj in flags_in_module:
+ flags_in_module.remove(flag_obj)
+
+ def SetDefault(self, name, value):
+ """Changes the default value of the named flag object."""
+ fl = self.FlagDict()
+ if not fl.has_key(name):
+ raise AttributeError(name)
+ fl[name].SetDefault(value)
+
+ def __contains__(self, name):
+ """Returns True if name is a value (flag) in the dict."""
+ return name in self.FlagDict()
+
+ has_key = __contains__ # a synonym for __contains__()
+
+ def __iter__(self):
+ return self.FlagDict().iterkeys()
+
+ def __call__(self, argv):
+ """Parses flags from argv; stores parsed flags into this FlagValues object.
+
+ All unparsed arguments are returned. Flags are parsed using the GNU
+ Program Argument Syntax Conventions, using getopt:
+
+ http://www.gnu.org/software/libc/manual/html_mono/libc.html#Getopt
+
+ Args:
+ argv: argument list. Can be of any type that may be converted to a list.
+
+ Returns:
+ The list of arguments not parsed as options, including argv[0]
+
+ Raises:
+ FlagsError: on any parsing error
+ """
+ # Support any sequence type that can be converted to a list
+ argv = list(argv)
+
+ shortopts = ""
+ longopts = []
+
+ fl = self.FlagDict()
+
+ # This pre parses the argv list for --flagfile=<> options.
+ argv = argv[:1] + self.ReadFlagsFromFiles(argv[1:], force_gnu=False)
+
+ # Correct the argv to support the google style of passing boolean
+ # parameters. Boolean parameters may be passed by using --mybool,
+ # --nomybool, --mybool=(true|false|1|0). getopt does not support
+ # having options that may or may not have a parameter. We replace
+ # instances of the short form --mybool and --nomybool with their
+ # full forms: --mybool=(true|false).
+ original_argv = list(argv) # list() makes a copy
+ shortest_matches = None
+ for name, flag in fl.items():
+ if not flag.boolean:
+ continue
+ if shortest_matches is None:
+ # Determine the smallest allowable prefix for all flag names
+ shortest_matches = self.ShortestUniquePrefixes(fl)
+ no_name = 'no' + name
+ prefix = shortest_matches[name]
+ no_prefix = shortest_matches[no_name]
+
+ # Replace all occurences of this boolean with extended forms
+ for arg_idx in range(1, len(argv)):
+ arg = argv[arg_idx]
+ if arg.find('=') >= 0: continue
+ if arg.startswith('--'+prefix) and ('--'+name).startswith(arg):
+ argv[arg_idx] = ('--%s=true' % name)
+ elif arg.startswith('--'+no_prefix) and ('--'+no_name).startswith(arg):
+ argv[arg_idx] = ('--%s=false' % name)
+
+ # Loop over all of the flags, building up the lists of short options
+ # and long options that will be passed to getopt. Short options are
+ # specified as a string of letters, each letter followed by a colon
+ # if it takes an argument. Long options are stored in an array of
+ # strings. Each string ends with an '=' if it takes an argument.
+ for name, flag in fl.items():
+ longopts.append(name + "=")
+ if len(name) == 1: # one-letter option: allow short flag type also
+ shortopts += name
+ if not flag.boolean:
+ shortopts += ":"
+
+ longopts.append('undefok=')
+ undefok_flags = []
+
+ # In case --undefok is specified, loop to pick up unrecognized
+ # options one by one.
+ unrecognized_opts = []
+ args = argv[1:]
+ while True:
+ try:
+ if self.__dict__['__use_gnu_getopt']:
+ optlist, unparsed_args = getopt.gnu_getopt(args, shortopts, longopts)
+ else:
+ optlist, unparsed_args = getopt.getopt(args, shortopts, longopts)
+ break
+ except getopt.GetoptError, e:
+ if not e.opt or e.opt in fl:
+ # Not an unrecognized option, reraise the exception as a FlagsError
+ raise FlagsError(e)
+ # Handle an unrecognized option.
+ unrecognized_opts.append(e.opt)
+ # Remove offender from args and try again
+ for arg_index in range(len(args)):
+ if ((args[arg_index] == '--' + e.opt) or
+ (args[arg_index] == '-' + e.opt) or
+ args[arg_index].startswith('--' + e.opt + '=')):
+ args = args[0:arg_index] + args[arg_index+1:]
+ break
+ else:
+ # We should have found the option, so we don't expect to get
+ # here. We could assert, but raising the original exception
+ # might work better.
+ raise FlagsError(e)
+
+ for name, arg in optlist:
+ if name == '--undefok':
+ flag_names = arg.split(',')
+ undefok_flags.extend(flag_names)
+ # For boolean flags, if --undefok=boolflag is specified, then we should
+ # also accept --noboolflag, in addition to --boolflag.
+ # Since we don't know the type of the undefok'd flag, this will affect
+ # non-boolean flags as well.
+ # NOTE: You shouldn't use --undefok=noboolflag, because then we will
+ # accept --nonoboolflag here. We are choosing not to do the conversion
+ # from noboolflag -> boolflag because of the ambiguity that flag names
+ # can start with 'no'.
+ undefok_flags.extend('no' + name for name in flag_names)
+ continue
+ if name.startswith('--'):
+ # long option
+ name = name[2:]
+ short_option = 0
+ else:
+ # short option
+ name = name[1:]
+ short_option = 1
+ if fl.has_key(name):
+ flag = fl[name]
+ if flag.boolean and short_option: arg = 1
+ flag.Parse(arg)
+
+ # If there were unrecognized options, raise an exception unless
+ # the options were named via --undefok.
+ for opt in unrecognized_opts:
+ if opt not in undefok_flags:
+ raise UnrecognizedFlagError(opt)
+
+ if unparsed_args:
+ if self.__dict__['__use_gnu_getopt']:
+ # if using gnu_getopt just return the program name + remainder of argv.
+ return argv[:1] + unparsed_args
+ else:
+ # unparsed_args becomes the first non-flag detected by getopt to
+ # the end of argv. Because argv may have been modified above,
+ # return original_argv for this region.
+ return argv[:1] + original_argv[-len(unparsed_args):]
+ else:
+ return argv[:1]
+
+ def Reset(self):
+ """Resets the values to the point before FLAGS(argv) was called."""
+ for f in self.FlagDict().values():
+ f.Unparse()
+
+ def RegisteredFlags(self):
+ """Returns: a list of the names and short names of all registered flags."""
+ return self.FlagDict().keys()
+
+ def FlagValuesDict(self):
+ """Returns: a dictionary that maps flag names to flag values."""
+ flag_values = {}
+
+ for flag_name in self.RegisteredFlags():
+ flag = self.FlagDict()[flag_name]
+ flag_values[flag_name] = flag.value
+
+ return flag_values
+
+ def __str__(self):
+ """Generates a help string for all known flags."""
+ return self.GetHelp()
+
+ def GetHelp(self, prefix=''):
+ """Generates a help string for all known flags."""
+ helplist = []
+
+ flags_by_module = self.FlagsByModuleDict()
+ if flags_by_module:
+
+ modules = flags_by_module.keys()
+ modules.sort()
+
+ # Print the help for the main module first, if possible.
+ main_module = _GetMainModule()
+ if main_module in modules:
+ modules.remove(main_module)
+ modules = [main_module] + modules
+
+ for module in modules:
+ self.__RenderOurModuleFlags(module, helplist)
+
+ self.__RenderModuleFlags('gflags',
+ _SPECIAL_FLAGS.FlagDict().values(),
+ helplist)
+
+ else:
+ # Just print one long list of flags.
+ self.__RenderFlagList(
+ self.FlagDict().values() + _SPECIAL_FLAGS.FlagDict().values(),
+ helplist, prefix)
+
+ return '\n'.join(helplist)
+
+ def __RenderModuleFlags(self, module, flags, output_lines, prefix=""):
+ """Generates a help string for a given module."""
+ if not isinstance(module, str):
+ module = module.__name__
+ output_lines.append('\n%s%s:' % (prefix, module))
+ self.__RenderFlagList(flags, output_lines, prefix + " ")
+
+ def __RenderOurModuleFlags(self, module, output_lines, prefix=""):
+ """Generates a help string for a given module."""
+ flags = self._GetFlagsDefinedByModule(module)
+ if flags:
+ self.__RenderModuleFlags(module, flags, output_lines, prefix)
+
+ def __RenderOurModuleKeyFlags(self, module, output_lines, prefix=""):
+ """Generates a help string for the key flags of a given module.
+
+ Args:
+ module: A module object or a module name (a string).
+ output_lines: A list of strings. The generated help message
+ lines will be appended to this list.
+ prefix: A string that is prepended to each generated help line.
+ """
+ key_flags = self._GetKeyFlagsForModule(module)
+ if key_flags:
+ self.__RenderModuleFlags(module, key_flags, output_lines, prefix)
+
+ def ModuleHelp(self, module):
+ """Describe the key flags of a module.
+
+ Args:
+ module: A module object or a module name (a string).
+
+ Returns:
+ string describing the key flags of a module.
+ """
+ helplist = []
+ self.__RenderOurModuleKeyFlags(module, helplist)
+ return '\n'.join(helplist)
+
+ def MainModuleHelp(self):
+ """Describe the key flags of the main module.
+
+ Returns:
+ string describing the key flags of a module.
+ """
+ return self.ModuleHelp(_GetMainModule())
+
+ def __RenderFlagList(self, flaglist, output_lines, prefix=" "):
+ fl = self.FlagDict()
+ special_fl = _SPECIAL_FLAGS.FlagDict()
+ flaglist = [(flag.name, flag) for flag in flaglist]
+ flaglist.sort()
+ flagset = {}
+ for (name, flag) in flaglist:
+ # It's possible this flag got deleted or overridden since being
+ # registered in the per-module flaglist. Check now against the
+ # canonical source of current flag information, the FlagDict.
+ if fl.get(name, None) != flag and special_fl.get(name, None) != flag:
+ # a different flag is using this name now
+ continue
+ # only print help once
+ if flagset.has_key(flag): continue
+ flagset[flag] = 1
+ flaghelp = ""
+ if flag.short_name: flaghelp += "-%s," % flag.short_name
+ if flag.boolean:
+ flaghelp += "--[no]%s" % flag.name + ":"
+ else:
+ flaghelp += "--%s" % flag.name + ":"
+ flaghelp += " "
+ if flag.help:
+ flaghelp += flag.help
+ flaghelp = TextWrap(flaghelp, indent=prefix+" ",
+ firstline_indent=prefix)
+ if flag.default_as_str:
+ flaghelp += "\n"
+ flaghelp += TextWrap("(default: %s)" % flag.default_as_str,
+ indent=prefix+" ")
+ if flag.parser.syntactic_help:
+ flaghelp += "\n"
+ flaghelp += TextWrap("(%s)" % flag.parser.syntactic_help,
+ indent=prefix+" ")
+ output_lines.append(flaghelp)
+
+ def get(self, name, default):
+ """Returns the value of a flag (if not None) or a default value.
+
+ Args:
+ name: A string, the name of a flag.
+ default: Default value to use if the flag value is None.
+ """
+
+ value = self.__getattr__(name)
+ if value is not None: # Can't do if not value, b/c value might be '0' or ""
+ return value
+ else:
+ return default
+
+ def ShortestUniquePrefixes(self, fl):
+ """Returns: dictionary; maps flag names to their shortest unique prefix."""
+ # Sort the list of flag names
+ sorted_flags = []
+ for name, flag in fl.items():
+ sorted_flags.append(name)
+ if flag.boolean:
+ sorted_flags.append('no%s' % name)
+ sorted_flags.sort()
+
+ # For each name in the sorted list, determine the shortest unique
+ # prefix by comparing itself to the next name and to the previous
+ # name (the latter check uses cached info from the previous loop).
+ shortest_matches = {}
+ prev_idx = 0
+ for flag_idx in range(len(sorted_flags)):
+ curr = sorted_flags[flag_idx]
+ if flag_idx == (len(sorted_flags) - 1):
+ next = None
+ else:
+ next = sorted_flags[flag_idx+1]
+ next_len = len(next)
+ for curr_idx in range(len(curr)):
+ if (next is None
+ or curr_idx >= next_len
+ or curr[curr_idx] != next[curr_idx]):
+ # curr longer than next or no more chars in common
+ shortest_matches[curr] = curr[:max(prev_idx, curr_idx) + 1]
+ prev_idx = curr_idx
+ break
+ else:
+ # curr shorter than (or equal to) next
+ shortest_matches[curr] = curr
+ prev_idx = curr_idx + 1 # next will need at least one more char
+ return shortest_matches
+
+ def __IsFlagFileDirective(self, flag_string):
+ """Checks whether flag_string contain a --flagfile=<foo> directive."""
+ if isinstance(flag_string, type("")):
+ if flag_string.startswith('--flagfile='):
+ return 1
+ elif flag_string == '--flagfile':
+ return 1
+ elif flag_string.startswith('-flagfile='):
+ return 1
+ elif flag_string == '-flagfile':
+ return 1
+ else:
+ return 0
+ return 0
+
+ def ExtractFilename(self, flagfile_str):
+ """Returns filename from a flagfile_str of form -[-]flagfile=filename.
+
+ The cases of --flagfile foo and -flagfile foo shouldn't be hitting
+ this function, as they are dealt with in the level above this
+ function.
+ """
+ if flagfile_str.startswith('--flagfile='):
+ return os.path.expanduser((flagfile_str[(len('--flagfile=')):]).strip())
+ elif flagfile_str.startswith('-flagfile='):
+ return os.path.expanduser((flagfile_str[(len('-flagfile=')):]).strip())
+ else:
+ raise FlagsError('Hit illegal --flagfile type: %s' % flagfile_str)
+
+ def __GetFlagFileLines(self, filename, parsed_file_list):
+ """Returns the useful (!=comments, etc) lines from a file with flags.
+
+ Args:
+ filename: A string, the name of the flag file.
+ parsed_file_list: A list of the names of the files we have
+ already read. MUTATED BY THIS FUNCTION.
+
+ Returns:
+ List of strings. See the note below.
+
+ NOTE(springer): This function checks for a nested --flagfile=<foo>
+ tag and handles the lower file recursively. It returns a list of
+ all the lines that _could_ contain command flags. This is
+ EVERYTHING except whitespace lines and comments (lines starting
+ with '#' or '//').
+ """
+ line_list = [] # All line from flagfile.
+ flag_line_list = [] # Subset of lines w/o comments, blanks, flagfile= tags.
+ try:
+ file_obj = open(filename, 'r')
+ except IOError, e_msg:
+ print e_msg
+ print 'ERROR:: Unable to open flagfile: %s' % (filename)
+ return flag_line_list
+
+ line_list = file_obj.readlines()
+ file_obj.close()
+ parsed_file_list.append(filename)
+
+ # This is where we check each line in the file we just read.
+ for line in line_list:
+ if line.isspace():
+ pass
+ # Checks for comment (a line that starts with '#').
+ elif line.startswith('#') or line.startswith('//'):
+ pass
+ # Checks for a nested "--flagfile=<bar>" flag in the current file.
+ # If we find one, recursively parse down into that file.
+ elif self.__IsFlagFileDirective(line):
+ sub_filename = self.ExtractFilename(line)
+ # We do a little safety check for reparsing a file we've already done.
+ if not sub_filename in parsed_file_list:
+ included_flags = self.__GetFlagFileLines(sub_filename,
+ parsed_file_list)
+ flag_line_list.extend(included_flags)
+ else: # Case of hitting a circularly included file.
+ print >>sys.stderr, ('Warning: Hit circular flagfile dependency: %s'
+ % sub_filename)
+ else:
+ # Any line that's not a comment or a nested flagfile should get
+ # copied into 2nd position. This leaves earlier arguements
+ # further back in the list, thus giving them higher priority.
+ flag_line_list.append(line.strip())
+ return flag_line_list
+
+ def ReadFlagsFromFiles(self, argv, force_gnu=True):
+ """Processes command line args, but also allow args to be read from file.
+ Args:
+ argv: A list of strings, usually sys.argv[1:], which may contain one or
+ more flagfile directives of the form --flagfile="./filename".
+ Note that the name of the program (sys.argv[0]) should be omitted.
+ force_gnu: If False, --flagfile parsing obeys normal flag semantics.
+ If True, --flagfile parsing instead follows gnu_getopt semantics.
+ *** WARNING *** force_gnu=False may become the future default!
+
+ Returns:
+
+ A new list which has the original list combined with what we read
+ from any flagfile(s).
+
+ References: Global gflags.FLAG class instance.
+
+ This function should be called before the normal FLAGS(argv) call.
+ This function scans the input list for a flag that looks like:
+ --flagfile=<somefile>. Then it opens <somefile>, reads all valid key
+ and value pairs and inserts them into the input list between the
+ first item of the list and any subsequent items in the list.
+
+ Note that your application's flags are still defined the usual way
+ using gflags DEFINE_flag() type functions.
+
+ Notes (assuming we're getting a commandline of some sort as our input):
+ --> Flags from the command line argv _should_ always take precedence!
+ --> A further "--flagfile=<otherfile.cfg>" CAN be nested in a flagfile.
+ It will be processed after the parent flag file is done.
+ --> For duplicate flags, first one we hit should "win".
+ --> In a flagfile, a line beginning with # or // is a comment.
+ --> Entirely blank lines _should_ be ignored.
+ """
+ parsed_file_list = []
+ rest_of_args = argv
+ new_argv = []
+ while rest_of_args:
+ current_arg = rest_of_args[0]
+ rest_of_args = rest_of_args[1:]
+ if self.__IsFlagFileDirective(current_arg):
+ # This handles the case of -(-)flagfile foo. In this case the
+ # next arg really is part of this one.
+ if current_arg == '--flagfile' or current_arg == '-flagfile':
+ if not rest_of_args:
+ raise IllegalFlagValue('--flagfile with no argument')
+ flag_filename = os.path.expanduser(rest_of_args[0])
+ rest_of_args = rest_of_args[1:]
+ else:
+ # This handles the case of (-)-flagfile=foo.
+ flag_filename = self.ExtractFilename(current_arg)
+ new_argv[0:0] = self.__GetFlagFileLines(flag_filename, parsed_file_list)
+ else:
+ new_argv.append(current_arg)
+ # Stop parsing after '--', like getopt and gnu_getopt.
+ if current_arg == '--':
+ break
+ # Stop parsing after a non-flag, like getopt.
+ if not current_arg.startswith('-'):
+ if not force_gnu and not self.__dict__['__use_gnu_getopt']:
+ break
+
+ if rest_of_args:
+ new_argv.extend(rest_of_args)
+
+ return new_argv
+
+ def FlagsIntoString(self):
+ """Returns a string with the flags assignments from this FlagValues object.
+
+ This function ignores flags whose value is None. Each flag
+ assignment is separated by a newline.
+
+ NOTE: MUST mirror the behavior of the C++ function
+ CommandlineFlagsIntoString from google3/base/commandlineflags.cc.
+ """
+ s = ''
+ for flag in self.FlagDict().values():
+ if flag.value is not None:
+ s += flag.Serialize() + '\n'
+ return s
+
+ def AppendFlagsIntoFile(self, filename):
+ """Appends all flags assignments from this FlagInfo object to a file.
+
+ Output will be in the format of a flagfile.
+
+ NOTE: MUST mirror the behavior of the C++ version of
+ AppendFlagsIntoFile from google3/base/commandlineflags.cc.
+ """
+ out_file = open(filename, 'a')
+ out_file.write(self.FlagsIntoString())
+ out_file.close()
+
+ def WriteHelpInXMLFormat(self, outfile=None):
+ """Outputs flag documentation in XML format.
+
+ NOTE: We use element names that are consistent with those used by
+ the C++ command-line flag library, from
+ google3/base/commandlineflags_reporting.cc. We also use a few new
+ elements (e.g., <key>), but we do not interfere / overlap with
+ existing XML elements used by the C++ library. Please maintain this
+ consistency.
+
+ Args:
+ outfile: File object we write to. Default None means sys.stdout.
+ """
+ outfile = outfile or sys.stdout
+
+ outfile.write('<?xml version=\"1.0\"?>\n')
+ outfile.write('<AllFlags>\n')
+ indent = ' '
+ _WriteSimpleXMLElement(outfile, 'program', os.path.basename(sys.argv[0]),
+ indent)
+
+ usage_doc = sys.modules['__main__'].__doc__
+ if not usage_doc:
+ usage_doc = '\nUSAGE: %s [flags]\n' % sys.argv[0]
+ else:
+ usage_doc = usage_doc.replace('%s', sys.argv[0])
+ _WriteSimpleXMLElement(outfile, 'usage', usage_doc, indent)
+
+ # Get list of key flags for the main module.
+ key_flags = self._GetKeyFlagsForModule(_GetMainModule())
+
+ # Sort flags by declaring module name and next by flag name.
+ flags_by_module = self.FlagsByModuleDict()
+ all_module_names = list(flags_by_module.keys())
+ all_module_names.sort()
+ for module_name in all_module_names:
+ flag_list = [(f.name, f) for f in flags_by_module[module_name]]
+ flag_list.sort()
+ for unused_flag_name, flag in flag_list:
+ is_key = flag in key_flags
+ flag.WriteInfoInXMLFormat(outfile, module_name,
+ is_key=is_key, indent=indent)
+
+ outfile.write('</AllFlags>\n')
+ outfile.flush()
+# end of FlagValues definition
+
+
+# The global FlagValues instance
+FLAGS = FlagValues()
+
+
+def _MakeXMLSafe(s):
+ """Escapes <, >, and & from s, and removes XML 1.0-illegal chars."""
+ s = cgi.escape(s) # Escape <, >, and &
+ # Remove characters that cannot appear in an XML 1.0 document
+ # (http://www.w3.org/TR/REC-xml/#charsets).
+ #
+ # NOTE: if there are problems with current solution, one may move to
+ # XML 1.1, which allows such chars, if they're entity-escaped (&#xHH;).
+ s = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f]', '', s)
+ return s
+
+
+def _WriteSimpleXMLElement(outfile, name, value, indent):
+ """Writes a simple XML element.
+
+ Args:
+ outfile: File object we write the XML element to.
+ name: A string, the name of XML element.
+ value: A Python object, whose string representation will be used
+ as the value of the XML element.
+ indent: A string, prepended to each line of generated output.
+ """
+ value_str = str(value)
+ if isinstance(value, bool):
+ # Display boolean values as the C++ flag library does: no caps.
+ value_str = value_str.lower()
+ outfile.write('%s<%s>%s</%s>\n' %
+ (indent, name, _MakeXMLSafe(value_str), name))
+
+
+class Flag:
+ """Information about a command-line flag.
+
+ 'Flag' objects define the following fields:
+ .name - the name for this flag
+ .default - the default value for this flag
+ .default_as_str - default value as repr'd string, e.g., "'true'" (or None)
+ .value - the most recent parsed value of this flag; set by Parse()
+ .help - a help string or None if no help is available
+ .short_name - the single letter alias for this flag (or None)
+ .boolean - if 'true', this flag does not accept arguments
+ .present - true if this flag was parsed from command line flags.
+ .parser - an ArgumentParser object
+ .serializer - an ArgumentSerializer object
+ .allow_override - the flag may be redefined without raising an error
+
+ The only public method of a 'Flag' object is Parse(), but it is
+ typically only called by a 'FlagValues' object. The Parse() method is
+ a thin wrapper around the 'ArgumentParser' Parse() method. The parsed
+ value is saved in .value, and the .present attribute is updated. If
+ this flag was already present, a FlagsError is raised.
+
+ Parse() is also called during __init__ to parse the default value and
+ initialize the .value attribute. This enables other python modules to
+ safely use flags even if the __main__ module neglects to parse the
+ command line arguments. The .present attribute is cleared after
+ __init__ parsing. If the default value is set to None, then the
+ __init__ parsing step is skipped and the .value attribute is
+ initialized to None.
+
+ Note: The default value is also presented to the user in the help
+ string, so it is important that it be a legal value for this flag.
+ """
+
+ def __init__(self, parser, serializer, name, default, help_string,
+ short_name=None, boolean=0, allow_override=0):
+ self.name = name
+
+ if not help_string:
+ help_string = '(no help available)'
+
+ self.help = help_string
+ self.short_name = short_name
+ self.boolean = boolean
+ self.present = 0
+ self.parser = parser
+ self.serializer = serializer
+ self.allow_override = allow_override
+ self.value = None
+
+ self.SetDefault(default)
+
+ def __GetParsedValueAsString(self, value):
+ if value is None:
+ return None
+ if self.serializer:
+ return repr(self.serializer.Serialize(value))
+ if self.boolean:
+ if value:
+ return repr('true')
+ else:
+ return repr('false')
+ return repr(str(value))
+
+ def Parse(self, argument):
+ try:
+ self.value = self.parser.Parse(argument)
+ except ValueError, e: # recast ValueError as IllegalFlagValue
+ raise IllegalFlagValue("flag --%s=%s: %s" % (self.name, argument, e))
+ self.present += 1
+
+ def Unparse(self):
+ if self.default is None:
+ self.value = None
+ else:
+ self.Parse(self.default)
+ self.present = 0
+
+ def Serialize(self):
+ if self.value is None:
+ return ''
+ if self.boolean:
+ if self.value:
+ return "--%s" % self.name
+ else:
+ return "--no%s" % self.name
+ else:
+ if not self.serializer:
+ raise FlagsError("Serializer not present for flag %s" % self.name)
+ return "--%s=%s" % (self.name, self.serializer.Serialize(self.value))
+
+ def SetDefault(self, value):
+ """Changes the default value (and current value too) for this Flag."""
+ # We can't allow a None override because it may end up not being
+ # passed to C++ code when we're overriding C++ flags. So we
+ # cowardly bail out until someone fixes the semantics of trying to
+ # pass None to a C++ flag. See swig_flags.Init() for details on
+ # this behavior.
+ if value is None and self.allow_override:
+ raise DuplicateFlag(self.name)
+
+ self.default = value
+ self.Unparse()
+ self.default_as_str = self.__GetParsedValueAsString(self.value)
+
+ def Type(self):
+ """Returns: a string that describes the type of this Flag."""
+ # NOTE: we use strings, and not the types.*Type constants because
+ # our flags can have more exotic types, e.g., 'comma separated list
+ # of strings', 'whitespace separated list of strings', etc.
+ return self.parser.Type()
+
+ def WriteInfoInXMLFormat(self, outfile, module_name, is_key=False, indent=''):
+ """Writes common info about this flag, in XML format.
+
+ This is information that is relevant to all flags (e.g., name,
+ meaning, etc.). If you defined a flag that has some other pieces of
+ info, then please override _WriteCustomInfoInXMLFormat.
+
+ Please do NOT override this method.
+
+ Args:
+ outfile: File object we write to.
+ module_name: A string, the name of the module that defines this flag.
+ is_key: A boolean, True iff this flag is key for main module.
+ indent: A string that is prepended to each generated line.
+ """
+ outfile.write(indent + '<flag>\n')
+ inner_indent = indent + ' '
+ if is_key:
+ _WriteSimpleXMLElement(outfile, 'key', 'yes', inner_indent)
+ _WriteSimpleXMLElement(outfile, 'file', module_name, inner_indent)
+ # Print flag features that are relevant for all flags.
+ _WriteSimpleXMLElement(outfile, 'name', self.name, inner_indent)
+ if self.short_name:
+ _WriteSimpleXMLElement(outfile, 'short_name', self.short_name,
+ inner_indent)
+ if self.help:
+ _WriteSimpleXMLElement(outfile, 'meaning', self.help, inner_indent)
+ _WriteSimpleXMLElement(outfile, 'default', self.default, inner_indent)
+ _WriteSimpleXMLElement(outfile, 'current', self.value, inner_indent)
+ _WriteSimpleXMLElement(outfile, 'type', self.Type(), inner_indent)
+ # Print extra flag features this flag may have.
+ self._WriteCustomInfoInXMLFormat(outfile, inner_indent)
+ outfile.write(indent + '</flag>\n')
+
+ def _WriteCustomInfoInXMLFormat(self, outfile, indent):
+ """Writes extra info about this flag, in XML format.
+
+ "Extra" means "not already printed by WriteInfoInXMLFormat above."
+
+ Args:
+ outfile: File object we write to.
+ indent: A string that is prepended to each generated line.
+ """
+ # Usually, the parser knows the extra details about the flag, so
+ # we just forward the call to it.
+ self.parser.WriteCustomInfoInXMLFormat(outfile, indent)
+# End of Flag definition
+
+
+class ArgumentParser:
+ """Base class used to parse and convert arguments.
+
+ The Parse() method checks to make sure that the string argument is a
+ legal value and convert it to a native type. If the value cannot be
+ converted, it should throw a 'ValueError' exception with a human
+ readable explanation of why the value is illegal.
+
+ Subclasses should also define a syntactic_help string which may be
+ presented to the user to describe the form of the legal values.
+ """
+ syntactic_help = ""
+
+ def Parse(self, argument):
+ """Default implementation: always returns its argument unmodified."""
+ return argument
+
+ def Type(self):
+ return 'string'
+
+ def WriteCustomInfoInXMLFormat(self, outfile, indent):
+ pass
+
+
+class ArgumentSerializer:
+ """Base class for generating string representations of a flag value."""
+
+ def Serialize(self, value):
+ return str(value)
+
+
+class ListSerializer(ArgumentSerializer):
+
+ def __init__(self, list_sep):
+ self.list_sep = list_sep
+
+ def Serialize(self, value):
+ return self.list_sep.join([str(x) for x in value])
+
+
+# The DEFINE functions are explained in mode details in the module doc string.
+
+
+def DEFINE(parser, name, default, help, flag_values=FLAGS, serializer=None,
+ **args):
+ """Registers a generic Flag object.
+
+ NOTE: in the docstrings of all DEFINE* functions, "registers" is short
+ for "creates a new flag and registers it".
+
+ Auxiliary function: clients should use the specialized DEFINE_<type>
+ function instead.
+
+ Args:
+ parser: ArgumentParser that is used to parse the flag arguments.
+ name: A string, the flag name.
+ default: The default value of the flag.
+ help: A help string.
+ flag_values: FlagValues object the flag will be registered with.
+ serializer: ArgumentSerializer that serializes the flag value.
+ args: Dictionary with extra keyword args that are passes to the
+ Flag __init__.
+ """
+ DEFINE_flag(Flag(parser, serializer, name, default, help, **args),
+ flag_values)
+
+
+def DEFINE_flag(flag, flag_values=FLAGS):
+ """Registers a 'Flag' object with a 'FlagValues' object.
+
+ By default, the global FLAGS 'FlagValue' object is used.
+
+ Typical users will use one of the more specialized DEFINE_xxx
+ functions, such as DEFINE_string or DEFINE_integer. But developers
+ who need to create Flag objects themselves should use this function
+ to register their flags.
+ """
+ # copying the reference to flag_values prevents pychecker warnings
+ fv = flag_values
+ fv[flag.name] = flag
+ # Tell flag_values who's defining the flag.
+ if isinstance(flag_values, FlagValues):
+ # Regarding the above isinstance test: some users pass funny
+ # values of flag_values (e.g., {}) in order to avoid the flag
+ # registration (in the past, there used to be a flag_values ==
+ # FLAGS test here) and redefine flags with the same name (e.g.,
+ # debug). To avoid breaking their code, we perform the
+ # registration only if flag_values is a real FlagValues object.
+ flag_values._RegisterFlagByModule(_GetCallingModule(), flag)
+
+
+def _InternalDeclareKeyFlags(flag_names, flag_values=FLAGS):
+ """Declares a flag as key for the calling module.
+
+ Internal function. User code should call DECLARE_key_flag or
+ ADOPT_module_key_flags instead.
+
+ Args:
+ flag_names: A list of strings that are names of already-registered
+ Flag objects.
+ flag_values: A FlagValues object. This should almost never need
+ to be overridden.
+
+ Raises:
+ UnrecognizedFlagError: when we refer to a flag that was not
+ defined yet.
+ """
+ module = _GetCallingModule()
+
+ for flag_name in flag_names:
+ if flag_name not in flag_values:
+ raise UnrecognizedFlagError(flag_name)
+ flag = flag_values.FlagDict()[flag_name]
+ flag_values._RegisterKeyFlagForModule(module, flag)
+
+
+def DECLARE_key_flag(flag_name, flag_values=FLAGS):
+ """Declares one flag as key to the current module.
+
+ Key flags are flags that are deemed really important for a module.
+ They are important when listing help messages; e.g., if the
+ --helpshort command-line flag is used, then only the key flags of the
+ main module are listed (instead of all flags, as in the case of
+ --help).
+
+ Sample usage:
+
+ flags.DECLARED_key_flag('flag_1')
+
+ Args:
+ flag_name: A string, the name of an already declared flag.
+ (Redeclaring flags as key, including flags implicitly key
+ because they were declared in this module, is a no-op.)
+ flag_values: A FlagValues object. This should almost never
+ need to be overridden.
+ """
+ _InternalDeclareKeyFlags([flag_name], flag_values=flag_values)
+
+
+def ADOPT_module_key_flags(module, flag_values=FLAGS):
+ """Declares that all flags key to a module are key to the current module.
+
+ Args:
+ module: A module object.
+ flag_values: A FlagValues object. This should almost never need
+ to be overridden.
+
+ Raises:
+ FlagsError: When given an argument that is a module name (a
+ string), instead of a module object.
+ """
+ # NOTE(salcianu): an even better test would be if not
+ # isinstance(module, types.ModuleType) but I didn't want to import
+ # types for such a tiny use.
+ if isinstance(module, str):
+ raise FlagsError('Received module name %s; expected a module object.'
+ % module)
+ _InternalDeclareKeyFlags(
+ [f.name for f in flag_values._GetKeyFlagsForModule(module.__name__)],
+ flag_values=flag_values)
+
+
+#
+# STRING FLAGS
+#
+
+
+def DEFINE_string(name, default, help, flag_values=FLAGS, **args):
+ """Registers a flag whose value can be any string."""
+ parser = ArgumentParser()
+ serializer = ArgumentSerializer()
+ DEFINE(parser, name, default, help, flag_values, serializer, **args)
+
+
+#
+# BOOLEAN FLAGS
+#
+# and the special HELP flags.
+
+class BooleanParser(ArgumentParser):
+ """Parser of boolean values."""
+
+ def Convert(self, argument):
+ """Converts the argument to a boolean; raise ValueError on errors."""
+ if type(argument) == str:
+ if argument.lower() in ['true', 't', '1']:
+ return True
+ elif argument.lower() in ['false', 'f', '0']:
+ return False
+
+ bool_argument = bool(argument)
+ if argument == bool_argument:
+ # The argument is a valid boolean (True, False, 0, or 1), and not just
+ # something that always converts to bool (list, string, int, etc.).
+ return bool_argument
+
+ raise ValueError('Non-boolean argument to boolean flag', argument)
+
+ def Parse(self, argument):
+ val = self.Convert(argument)
+ return val
+
+ def Type(self):
+ return 'bool'
+
+
+class BooleanFlag(Flag):
+ """Basic boolean flag.
+
+ Boolean flags do not take any arguments, and their value is either
+ True (1) or False (0). The false value is specified on the command
+ line by prepending the word 'no' to either the long or the short flag
+ name.
+
+ For example, if a Boolean flag was created whose long name was
+ 'update' and whose short name was 'x', then this flag could be
+ explicitly unset through either --noupdate or --nox.
+ """
+
+ def __init__(self, name, default, help, short_name=None, **args):
+ p = BooleanParser()
+ Flag.__init__(self, p, None, name, default, help, short_name, 1, **args)
+ if not self.help: self.help = "a boolean value"
+
+
+def DEFINE_boolean(name, default, help, flag_values=FLAGS, **args):
+ """Registers a boolean flag.
+
+ Such a boolean flag does not take an argument. If a user wants to
+ specify a false value explicitly, the long option beginning with 'no'
+ must be used: i.e. --noflag
+
+ This flag will have a value of None, True or False. None is possible
+ if default=None and the user does not specify the flag on the command
+ line.
+ """
+ DEFINE_flag(BooleanFlag(name, default, help, **args), flag_values)
+
+# Match C++ API to unconfuse C++ people.
+DEFINE_bool = DEFINE_boolean
+
+class HelpFlag(BooleanFlag):
+ """
+ HelpFlag is a special boolean flag that prints usage information and
+ raises a SystemExit exception if it is ever found in the command
+ line arguments. Note this is called with allow_override=1, so other
+ apps can define their own --help flag, replacing this one, if they want.
+ """
+ def __init__(self):
+ BooleanFlag.__init__(self, "help", 0, "show this help",
+ short_name="?", allow_override=1)
+ def Parse(self, arg):
+ if arg:
+ doc = sys.modules["__main__"].__doc__
+ flags = str(FLAGS)
+ print doc or ("\nUSAGE: %s [flags]\n" % sys.argv[0])
+ if flags:
+ print "flags:"
+ print flags
+ sys.exit(1)
+
+
+class HelpXMLFlag(BooleanFlag):
+ """Similar to HelpFlag, but generates output in XML format."""
+
+ def __init__(self):
+ BooleanFlag.__init__(self, 'helpxml', False,
+ 'like --help, but generates XML output',
+ allow_override=1)
+
+ def Parse(self, arg):
+ if arg:
+ FLAGS.WriteHelpInXMLFormat(sys.stdout)
+ sys.exit(1)
+
+
+class HelpshortFlag(BooleanFlag):
+ """
+ HelpshortFlag is a special boolean flag that prints usage
+ information for the "main" module, and rasies a SystemExit exception
+ if it is ever found in the command line arguments. Note this is
+ called with allow_override=1, so other apps can define their own
+ --helpshort flag, replacing this one, if they want.
+ """
+ def __init__(self):
+ BooleanFlag.__init__(self, "helpshort", 0,
+ "show usage only for this module", allow_override=1)
+ def Parse(self, arg):
+ if arg:
+ doc = sys.modules["__main__"].__doc__
+ flags = FLAGS.MainModuleHelp()
+ print doc or ("\nUSAGE: %s [flags]\n" % sys.argv[0])
+ if flags:
+ print "flags:"
+ print flags
+ sys.exit(1)
+
+
+#
+# FLOAT FLAGS
+#
+
+class FloatParser(ArgumentParser):
+ """Parser of floating point values.
+
+ Parsed value may be bounded to a given upper and lower bound.
+ """
+ number_article = "a"
+ number_name = "number"
+ syntactic_help = " ".join((number_article, number_name))
+
+ def __init__(self, lower_bound=None, upper_bound=None):
+ self.lower_bound = lower_bound
+ self.upper_bound = upper_bound
+ sh = self.syntactic_help
+ if lower_bound != None and upper_bound != None:
+ sh = ("%s in the range [%s, %s]" % (sh, lower_bound, upper_bound))
+ elif lower_bound == 1:
+ sh = "a positive %s" % self.number_name
+ elif upper_bound == -1:
+ sh = "a negative %s" % self.number_name
+ elif lower_bound == 0:
+ sh = "a non-negative %s" % self.number_name
+ elif upper_bound != None:
+ sh = "%s <= %s" % (self.number_name, upper_bound)
+ elif lower_bound != None:
+ sh = "%s >= %s" % (self.number_name, lower_bound)
+ self.syntactic_help = sh
+
+ def Convert(self, argument):
+ """Converts argument to a float; raises ValueError on errors."""
+ return float(argument)
+
+ def Parse(self, argument):
+ val = self.Convert(argument)
+ if ((self.lower_bound != None and val < self.lower_bound) or
+ (self.upper_bound != None and val > self.upper_bound)):
+ raise ValueError("%s is not %s" % (val, self.syntactic_help))
+ return val
+
+ def Type(self):
+ return 'float'
+
+ def WriteCustomInfoInXMLFormat(self, outfile, indent):
+ if self.lower_bound is not None:
+ _WriteSimpleXMLElement(outfile, 'lower_bound', self.lower_bound, indent)
+ if self.upper_bound is not None:
+ _WriteSimpleXMLElement(outfile, 'upper_bound', self.upper_bound, indent)
+# End of FloatParser
+
+
+def DEFINE_float(name, default, help, lower_bound=None, upper_bound=None,
+ flag_values=FLAGS, **args):
+ """Registers a flag whose value must be a float.
+
+ If lower_bound or upper_bound are set, then this flag must be
+ within the given range.
+ """
+ parser = FloatParser(lower_bound, upper_bound)
+ serializer = ArgumentSerializer()
+ DEFINE(parser, name, default, help, flag_values, serializer, **args)
+
+
+#
+# INTEGER FLAGS
+#
+
+
+class IntegerParser(FloatParser):
+ """Parser of an integer value.
+
+ Parsed value may be bounded to a given upper and lower bound.
+ """
+ number_article = "an"
+ number_name = "integer"
+ syntactic_help = " ".join((number_article, number_name))
+
+ def Convert(self, argument):
+ __pychecker__ = 'no-returnvalues'
+ if type(argument) == str:
+ base = 10
+ if len(argument) > 2 and argument[0] == "0" and argument[1] == "x":
+ base = 16
+ try:
+ return int(argument, base)
+ # ValueError is thrown when argument is a string, and overflows an int.
+ except ValueError:
+ return long(argument, base)
+ else:
+ try:
+ return int(argument)
+ # OverflowError is thrown when argument is numeric, and overflows an int.
+ except OverflowError:
+ return long(argument)
+
+ def Type(self):
+ return 'int'
+
+
+def DEFINE_integer(name, default, help, lower_bound=None, upper_bound=None,
+ flag_values=FLAGS, **args):
+ """Registers a flag whose value must be an integer.
+
+ If lower_bound, or upper_bound are set, then this flag must be
+ within the given range.
+ """
+ parser = IntegerParser(lower_bound, upper_bound)
+ serializer = ArgumentSerializer()
+ DEFINE(parser, name, default, help, flag_values, serializer, **args)
+
+
+#
+# ENUM FLAGS
+#
+
+
+class EnumParser(ArgumentParser):
+ """Parser of a string enum value (a string value from a given set).
+
+ If enum_values (see below) is not specified, any string is allowed.
+ """
+
+ def __init__(self, enum_values=None):
+ self.enum_values = enum_values
+
+ def Parse(self, argument):
+ if self.enum_values and argument not in self.enum_values:
+ raise ValueError("value should be one of <%s>" %
+ "|".join(self.enum_values))
+ return argument
+
+ def Type(self):
+ return 'string enum'
+
+
+class EnumFlag(Flag):
+ """Basic enum flag; its value can be any string from list of enum_values."""
+
+ def __init__(self, name, default, help, enum_values=None,
+ short_name=None, **args):
+ enum_values = enum_values or []
+ p = EnumParser(enum_values)
+ g = ArgumentSerializer()
+ Flag.__init__(self, p, g, name, default, help, short_name, **args)
+ if not self.help: self.help = "an enum string"
+ self.help = "<%s>: %s" % ("|".join(enum_values), self.help)
+
+ def _WriteCustomInfoInXMLFormat(self, outfile, indent):
+ for enum_value in self.parser.enum_values:
+ _WriteSimpleXMLElement(outfile, 'enum_value', enum_value, indent)
+
+
+def DEFINE_enum(name, default, enum_values, help, flag_values=FLAGS,
+ **args):
+ """Registers a flag whose value can be any string from enum_values."""
+ DEFINE_flag(EnumFlag(name, default, help, enum_values, ** args),
+ flag_values)
+
+
+#
+# LIST FLAGS
+#
+
+
+class BaseListParser(ArgumentParser):
+ """Base class for a parser of lists of strings.
+
+ To extend, inherit from this class; from the subclass __init__, call
+
+ BaseListParser.__init__(self, token, name)
+
+ where token is a character used to tokenize, and name is a description
+ of the separator.
+ """
+
+ def __init__(self, token=None, name=None):
+ assert name
+ self._token = token
+ self._name = name
+ self.syntactic_help = "a %s separated list" % self._name
+
+ def Parse(self, argument):
+ if isinstance(argument, list):
+ return argument
+ elif argument == '':
+ return []
+ else:
+ return [s.strip() for s in argument.split(self._token)]
+
+ def Type(self):
+ return '%s separated list of strings' % self._name
+
+
+class ListParser(BaseListParser):
+ """Parser for a comma-separated list of strings."""
+
+ def __init__(self):
+ BaseListParser.__init__(self, ',', 'comma')
+
+ def WriteCustomInfoInXMLFormat(self, outfile, indent):
+ BaseListParser.WriteCustomInfoInXMLFormat(self, outfile, indent)
+ _WriteSimpleXMLElement(outfile, 'list_separator', repr(','), indent)
+
+
+class WhitespaceSeparatedListParser(BaseListParser):
+ """Parser for a whitespace-separated list of strings."""
+
+ def __init__(self):
+ BaseListParser.__init__(self, None, 'whitespace')
+
+ def WriteCustomInfoInXMLFormat(self, outfile, indent):
+ BaseListParser.WriteCustomInfoInXMLFormat(self, outfile, indent)
+ separators = list(string.whitespace)
+ separators.sort()
+ for ws_char in string.whitespace:
+ _WriteSimpleXMLElement(outfile, 'list_separator', repr(ws_char), indent)
+
+
+def DEFINE_list(name, default, help, flag_values=FLAGS, **args):
+ """Registers a flag whose value is a comma-separated list of strings."""
+ parser = ListParser()
+ serializer = ListSerializer(',')
+ DEFINE(parser, name, default, help, flag_values, serializer, **args)
+
+
+def DEFINE_spaceseplist(name, default, help, flag_values=FLAGS, **args):
+ """Registers a flag whose value is a whitespace-separated list of strings.
+
+ Any whitespace can be used as a separator.
+ """
+ parser = WhitespaceSeparatedListParser()
+ serializer = ListSerializer(' ')
+ DEFINE(parser, name, default, help, flag_values, serializer, **args)
+
+
+#
+# MULTI FLAGS
+#
+
+
+class MultiFlag(Flag):
+ """A flag that can appear multiple time on the command-line.
+
+ The value of such a flag is a list that contains the individual values
+ from all the appearances of that flag on the command-line.
+
+ See the __doc__ for Flag for most behavior of this class. Only
+ differences in behavior are described here:
+
+ * The default value may be either a single value or a list of values.
+ A single value is interpreted as the [value] singleton list.
+
+ * The value of the flag is always a list, even if the option was
+ only supplied once, and even if the default value is a single
+ value
+ """
+
+ def __init__(self, *args, **kwargs):
+ Flag.__init__(self, *args, **kwargs)
+ self.help += ';\n repeat this option to specify a list of values'
+
+ def Parse(self, arguments):
+ """Parses one or more arguments with the installed parser.
+
+ Args:
+ arguments: a single argument or a list of arguments (typically a
+ list of default values); a single argument is converted
+ internally into a list containing one item.
+ """
+ if not isinstance(arguments, list):
+ # Default value may be a list of values. Most other arguments
+ # will not be, so convert them into a single-item list to make
+ # processing simpler below.
+ arguments = [arguments]
+
+ if self.present:
+ # keep a backup reference to list of previously supplied option values
+ values = self.value
+ else:
+ # "erase" the defaults with an empty list
+ values = []
+
+ for item in arguments:
+ # have Flag superclass parse argument, overwriting self.value reference
+ Flag.Parse(self, item) # also increments self.present
+ values.append(self.value)
+
+ # put list of option values back in the 'value' attribute
+ self.value = values
+
+ def Serialize(self):
+ if not self.serializer:
+ raise FlagsError("Serializer not present for flag %s" % self.name)
+ if self.value is None:
+ return ''
+
+ s = ''
+
+ multi_value = self.value
+
+ for self.value in multi_value:
+ if s: s += ' '
+ s += Flag.Serialize(self)
+
+ self.value = multi_value
+
+ return s
+
+ def Type(self):
+ return 'multi ' + self.parser.Type()
+
+
+def DEFINE_multi(parser, serializer, name, default, help, flag_values=FLAGS,
+ **args):
+ """Registers a generic MultiFlag that parses its args with a given parser.
+
+ Auxiliary function. Normal users should NOT use it directly.
+
+ Developers who need to create their own 'Parser' classes for options
+ which can appear multiple times can call this module function to
+ register their flags.
+ """
+ DEFINE_flag(MultiFlag(parser, serializer, name, default, help, **args),
+ flag_values)
+
+
+def DEFINE_multistring(name, default, help, flag_values=FLAGS, **args):
+ """Registers a flag whose value can be a list of any strings.
+
+ Use the flag on the command line multiple times to place multiple
+ string values into the list. The 'default' may be a single string
+ (which will be converted into a single-element list) or a list of
+ strings.
+ """
+ parser = ArgumentParser()
+ serializer = ArgumentSerializer()
+ DEFINE_multi(parser, serializer, name, default, help, flag_values, **args)
+
+
+def DEFINE_multi_int(name, default, help, lower_bound=None, upper_bound=None,
+ flag_values=FLAGS, **args):
+ """Registers a flag whose value can be a list of arbitrary integers.
+
+ Use the flag on the command line multiple times to place multiple
+ integer values into the list. The 'default' may be a single integer
+ (which will be converted into a single-element list) or a list of
+ integers.
+ """
+ parser = IntegerParser(lower_bound, upper_bound)
+ serializer = ArgumentSerializer()
+ DEFINE_multi(parser, serializer, name, default, help, flag_values, **args)
+
+
+# Now register the flags that we want to exist in all applications.
+# These are all defined with allow_override=1, so user-apps can use
+# these flagnames for their own purposes, if they want.
+DEFINE_flag(HelpFlag())
+DEFINE_flag(HelpshortFlag())
+DEFINE_flag(HelpXMLFlag())
+
+# Define special flags here so that help may be generated for them.
+_SPECIAL_FLAGS = FlagValues()
+
+
+DEFINE_string(
+ 'flagfile', "",
+ "Insert flag definitions from the given file into the command line.",
+ _SPECIAL_FLAGS)
+
+DEFINE_string(
+ 'undefok', "",
+ "comma-separated list of flag names that it is okay to specify "
+ "on the command line even if the program does not define a flag "
+ "with that name. IMPORTANT: flags in this list that have "
+ "arguments MUST use the --flag=value format.", _SPECIAL_FLAGS)
diff --git a/vendor/python-gflags/gflags2man.py b/vendor/python-gflags/gflags2man.py
new file mode 100755
index 0000000000..f346564301
--- /dev/null
+++ b/vendor/python-gflags/gflags2man.py
@@ -0,0 +1,536 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2007, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""gflags2man runs a Google flags base program and generates a man page.
+
+Run the program, parse the output, and then format that into a man
+page.
+
+Usage:
+ gflags2man <program> [program] ...
+"""
+
+# TODO(csilvers): work with windows paths (\) as well as unix (/)
+
+# This may seem a bit of an end run, but it: doesn't bloat flags, can
+# support python/java/C++, supports older executables, and can be
+# extended to other document formats.
+# Inspired by help2man.
+
+__author__ = 'Dan Christian'
+
+import os
+import re
+import sys
+import stat
+import time
+
+import gflags
+
+_VERSION = '0.1'
+
+
+def _GetDefaultDestDir():
+ home = os.environ.get('HOME', '')
+ homeman = os.path.join(home, 'man', 'man1')
+ if home and os.path.exists(homeman):
+ return homeman
+ else:
+ return os.environ.get('TMPDIR', '/tmp')
+
+FLAGS = gflags.FLAGS
+gflags.DEFINE_string('dest_dir', _GetDefaultDestDir(),
+ 'Directory to write resulting manpage to.'
+ ' Specify \'-\' for stdout')
+gflags.DEFINE_string('help_flag', '--help',
+ 'Option to pass to target program in to get help')
+gflags.DEFINE_integer('v', 0, 'verbosity level to use for output')
+
+_MIN_VALID_USAGE_MSG = 9 # if fewer lines than this, help is suspect
+
+
+class Logging:
+ """A super-simple logging class"""
+ def error(self, msg): print >>sys.stderr, "ERROR: ", msg
+ def warn(self, msg): print >>sys.stderr, "WARNING: ", msg
+ def info(self, msg): print msg
+ def debug(self, msg): self.vlog(1, msg)
+ def vlog(self, level, msg):
+ if FLAGS.v >= level: print msg
+logging = Logging()
+
+
+def GetRealPath(filename):
+ """Given an executable filename, find in the PATH or find absolute path.
+ Args:
+ filename An executable filename (string)
+ Returns:
+ Absolute version of filename.
+ None if filename could not be found locally, absolutely, or in PATH
+ """
+ if os.path.isabs(filename): # already absolute
+ return filename
+
+ if filename.startswith('./') or filename.startswith('../'): # relative
+ return os.path.abspath(filename)
+
+ path = os.getenv('PATH', '')
+ for directory in path.split(':'):
+ tryname = os.path.join(directory, filename)
+ if os.path.exists(tryname):
+ if not os.path.isabs(directory): # relative directory
+ return os.path.abspath(tryname)
+ return tryname
+ if os.path.exists(filename):
+ return os.path.abspath(filename)
+ return None # could not determine
+
+class Flag(object):
+ """The information about a single flag."""
+
+ def __init__(self, flag_desc, help):
+ """Create the flag object.
+ Args:
+ flag_desc The command line forms this could take. (string)
+ help The help text (string)
+ """
+ self.desc = flag_desc # the command line forms
+ self.help = help # the help text
+ self.default = '' # default value
+ self.tips = '' # parsing/syntax tips
+
+
+class ProgramInfo(object):
+ """All the information gleaned from running a program with --help."""
+
+ # Match a module block start, for python scripts --help
+ # "goopy.logging:"
+ module_py_re = re.compile(r'(\S.+):$')
+ # match the start of a flag listing
+ # " -v,--verbosity: Logging verbosity"
+ flag_py_re = re.compile(r'\s+(-\S+):\s+(.*)$')
+ # " (default: '0')"
+ flag_default_py_re = re.compile(r'\s+\(default:\s+\'(.*)\'\)$')
+ # " (an integer)"
+ flag_tips_py_re = re.compile(r'\s+\((.*)\)$')
+
+ # Match a module block start, for c++ programs --help
+ # "google/base/commandlineflags"
+ module_c_re = re.compile(r'\s+Flags from (\S.+):$')
+ # match the start of a flag listing
+ # " -v,--verbosity: Logging verbosity"
+ flag_c_re = re.compile(r'\s+(-\S+)\s+(.*)$')
+
+ # Match a module block start, for java programs --help
+ # "com.google.common.flags"
+ module_java_re = re.compile(r'\s+Flags for (\S.+):$')
+ # match the start of a flag listing
+ # " -v,--verbosity: Logging verbosity"
+ flag_java_re = re.compile(r'\s+(-\S+)\s+(.*)$')
+
+ def __init__(self, executable):
+ """Create object with executable.
+ Args:
+ executable Program to execute (string)
+ """
+ self.long_name = executable
+ self.name = os.path.basename(executable) # name
+ # Get name without extension (PAR files)
+ (self.short_name, self.ext) = os.path.splitext(self.name)
+ self.executable = GetRealPath(executable) # name of the program
+ self.output = [] # output from the program. List of lines.
+ self.desc = [] # top level description. List of lines
+ self.modules = {} # { section_name(string), [ flags ] }
+ self.module_list = [] # list of module names in their original order
+ self.date = time.localtime(time.time()) # default date info
+
+ def Run(self):
+ """Run it and collect output.
+
+ Returns:
+ 1 (true) If everything went well.
+ 0 (false) If there were problems.
+ """
+ if not self.executable:
+ logging.error('Could not locate "%s"' % self.long_name)
+ return 0
+
+ finfo = os.stat(self.executable)
+ self.date = time.localtime(finfo[stat.ST_MTIME])
+
+ logging.info('Running: %s %s </dev/null 2>&1'
+ % (self.executable, FLAGS.help_flag))
+ # --help output is often routed to stderr, so we combine with stdout.
+ # Re-direct stdin to /dev/null to encourage programs that
+ # don't understand --help to exit.
+ (child_stdin, child_stdout_and_stderr) = os.popen4(
+ [self.executable, FLAGS.help_flag])
+ child_stdin.close() # '</dev/null'
+ self.output = child_stdout_and_stderr.readlines()
+ child_stdout_and_stderr.close()
+ if len(self.output) < _MIN_VALID_USAGE_MSG:
+ logging.error('Error: "%s %s" returned only %d lines: %s'
+ % (self.name, FLAGS.help_flag,
+ len(self.output), self.output))
+ return 0
+ return 1
+
+ def Parse(self):
+ """Parse program output."""
+ (start_line, lang) = self.ParseDesc()
+ if start_line < 0:
+ return
+ if 'python' == lang:
+ self.ParsePythonFlags(start_line)
+ elif 'c' == lang:
+ self.ParseCFlags(start_line)
+ elif 'java' == lang:
+ self.ParseJavaFlags(start_line)
+
+ def ParseDesc(self, start_line=0):
+ """Parse the initial description.
+
+ This could be Python or C++.
+
+ Returns:
+ (start_line, lang_type)
+ start_line Line to start parsing flags on (int)
+ lang_type Either 'python' or 'c'
+ (-1, '') if the flags start could not be found
+ """
+ exec_mod_start = self.executable + ':'
+
+ after_blank = 0
+ start_line = 0 # ignore the passed-in arg for now (?)
+ for start_line in range(start_line, len(self.output)): # collect top description
+ line = self.output[start_line].rstrip()
+ # Python flags start with 'flags:\n'
+ if ('flags:' == line
+ and len(self.output) > start_line+1
+ and '' == self.output[start_line+1].rstrip()):
+ start_line += 2
+ logging.debug('Flags start (python): %s' % line)
+ return (start_line, 'python')
+ # SWIG flags just have the module name followed by colon.
+ if exec_mod_start == line:
+ logging.debug('Flags start (swig): %s' % line)
+ return (start_line, 'python')
+ # C++ flags begin after a blank line and with a constant string
+ if after_blank and line.startswith(' Flags from '):
+ logging.debug('Flags start (c): %s' % line)
+ return (start_line, 'c')
+ # java flags begin with a constant string
+ if line == 'where flags are':
+ logging.debug('Flags start (java): %s' % line)
+ start_line += 2 # skip "Standard flags:"
+ return (start_line, 'java')
+
+ logging.debug('Desc: %s' % line)
+ self.desc.append(line)
+ after_blank = (line == '')
+ else:
+ logging.warn('Never found the start of the flags section for "%s"!'
+ % self.long_name)
+ return (-1, '')
+
+ def ParsePythonFlags(self, start_line=0):
+ """Parse python/swig style flags."""
+ modname = None # name of current module
+ modlist = []
+ flag = None
+ for line_num in range(start_line, len(self.output)): # collect flags
+ line = self.output[line_num].rstrip()
+ if not line: # blank
+ continue
+
+ mobj = self.module_py_re.match(line)
+ if mobj: # start of a new module
+ modname = mobj.group(1)
+ logging.debug('Module: %s' % line)
+ if flag:
+ modlist.append(flag)
+ self.module_list.append(modname)
+ self.modules.setdefault(modname, [])
+ modlist = self.modules[modname]
+ flag = None
+ continue
+
+ mobj = self.flag_py_re.match(line)
+ if mobj: # start of a new flag
+ if flag:
+ modlist.append(flag)
+ logging.debug('Flag: %s' % line)
+ flag = Flag(mobj.group(1), mobj.group(2))
+ continue
+
+ if not flag: # continuation of a flag
+ logging.error('Flag info, but no current flag "%s"' % line)
+ mobj = self.flag_default_py_re.match(line)
+ if mobj: # (default: '...')
+ flag.default = mobj.group(1)
+ logging.debug('Fdef: %s' % line)
+ continue
+ mobj = self.flag_tips_py_re.match(line)
+ if mobj: # (tips)
+ flag.tips = mobj.group(1)
+ logging.debug('Ftip: %s' % line)
+ continue
+ if flag and flag.help:
+ flag.help += line # multiflags tack on an extra line
+ else:
+ logging.info('Extra: %s' % line)
+ if flag:
+ modlist.append(flag)
+
+ def ParseCFlags(self, start_line=0):
+ """Parse C style flags."""
+ modname = None # name of current module
+ modlist = []
+ flag = None
+ for line_num in range(start_line, len(self.output)): # collect flags
+ line = self.output[line_num].rstrip()
+ if not line: # blank lines terminate flags
+ if flag: # save last flag
+ modlist.append(flag)
+ flag = None
+ continue
+
+ mobj = self.module_c_re.match(line)
+ if mobj: # start of a new module
+ modname = mobj.group(1)
+ logging.debug('Module: %s' % line)
+ if flag:
+ modlist.append(flag)
+ self.module_list.append(modname)
+ self.modules.setdefault(modname, [])
+ modlist = self.modules[modname]
+ flag = None
+ continue
+
+ mobj = self.flag_c_re.match(line)
+ if mobj: # start of a new flag
+ if flag: # save last flag
+ modlist.append(flag)
+ logging.debug('Flag: %s' % line)
+ flag = Flag(mobj.group(1), mobj.group(2))
+ continue
+
+ # append to flag help. type and default are part of the main text
+ if flag:
+ flag.help += ' ' + line.strip()
+ else:
+ logging.info('Extra: %s' % line)
+ if flag:
+ modlist.append(flag)
+
+ def ParseJavaFlags(self, start_line=0):
+ """Parse Java style flags (com.google.common.flags)."""
+ # The java flags prints starts with a "Standard flags" "module"
+ # that doesn't follow the standard module syntax.
+ modname = 'Standard flags' # name of current module
+ self.module_list.append(modname)
+ self.modules.setdefault(modname, [])
+ modlist = self.modules[modname]
+ flag = None
+
+ for line_num in range(start_line, len(self.output)): # collect flags
+ line = self.output[line_num].rstrip()
+ logging.vlog(2, 'Line: "%s"' % line)
+ if not line: # blank lines terminate module
+ if flag: # save last flag
+ modlist.append(flag)
+ flag = None
+ continue
+
+ mobj = self.module_java_re.match(line)
+ if mobj: # start of a new module
+ modname = mobj.group(1)
+ logging.debug('Module: %s' % line)
+ if flag:
+ modlist.append(flag)
+ self.module_list.append(modname)
+ self.modules.setdefault(modname, [])
+ modlist = self.modules[modname]
+ flag = None
+ continue
+
+ mobj = self.flag_java_re.match(line)
+ if mobj: # start of a new flag
+ if flag: # save last flag
+ modlist.append(flag)
+ logging.debug('Flag: %s' % line)
+ flag = Flag(mobj.group(1), mobj.group(2))
+ continue
+
+ # append to flag help. type and default are part of the main text
+ if flag:
+ flag.help += ' ' + line.strip()
+ else:
+ logging.info('Extra: %s' % line)
+ if flag:
+ modlist.append(flag)
+
+ def Filter(self):
+ """Filter parsed data to create derived fields."""
+ if not self.desc:
+ self.short_desc = ''
+ return
+
+ for i in range(len(self.desc)): # replace full path with name
+ if self.desc[i].find(self.executable) >= 0:
+ self.desc[i] = self.desc[i].replace(self.executable, self.name)
+
+ self.short_desc = self.desc[0]
+ word_list = self.short_desc.split(' ')
+ all_names = [ self.name, self.short_name, ]
+ # Since the short_desc is always listed right after the name,
+ # trim it from the short_desc
+ while word_list and (word_list[0] in all_names
+ or word_list[0].lower() in all_names):
+ del word_list[0]
+ self.short_desc = '' # signal need to reconstruct
+ if not self.short_desc and word_list:
+ self.short_desc = ' '.join(word_list)
+
+
+class GenerateDoc(object):
+ """Base class to output flags information."""
+
+ def __init__(self, proginfo, directory='.'):
+ """Create base object.
+ Args:
+ proginfo A ProgramInfo object
+ directory Directory to write output into
+ """
+ self.info = proginfo
+ self.dirname = directory
+
+ def Output(self):
+ """Output all sections of the page."""
+ self.Open()
+ self.Header()
+ self.Body()
+ self.Footer()
+
+ def Open(self): raise NotImplementedError # define in subclass
+ def Header(self): raise NotImplementedError # define in subclass
+ def Body(self): raise NotImplementedError # define in subclass
+ def Footer(self): raise NotImplementedError # define in subclass
+
+
+class GenerateMan(GenerateDoc):
+ """Output a man page."""
+
+ def __init__(self, proginfo, directory='.'):
+ """Create base object.
+ Args:
+ proginfo A ProgramInfo object
+ directory Directory to write output into
+ """
+ GenerateDoc.__init__(self, proginfo, directory)
+
+ def Open(self):
+ if self.dirname == '-':
+ logging.info('Writing to stdout')
+ self.fp = sys.stdout
+ else:
+ self.file_path = '%s.1' % os.path.join(self.dirname, self.info.name)
+ logging.info('Writing: %s' % self.file_path)
+ self.fp = open(self.file_path, 'w')
+
+ def Header(self):
+ self.fp.write(
+ '.\\" DO NOT MODIFY THIS FILE! It was generated by gflags2man %s\n'
+ % _VERSION)
+ self.fp.write(
+ '.TH %s "1" "%s" "%s" "User Commands"\n'
+ % (self.info.name, time.strftime('%x', self.info.date), self.info.name))
+ self.fp.write(
+ '.SH NAME\n%s \\- %s\n' % (self.info.name, self.info.short_desc))
+ self.fp.write(
+ '.SH SYNOPSIS\n.B %s\n[\\fIFLAGS\\fR]...\n' % self.info.name)
+
+ def Body(self):
+ self.fp.write(
+ '.SH DESCRIPTION\n.\\" Add any additional description here\n.PP\n')
+ for ln in self.info.desc:
+ self.fp.write('%s\n' % ln)
+ self.fp.write(
+ '.SH OPTIONS\n')
+ # This shows flags in the original order
+ for modname in self.info.module_list:
+ if modname.find(self.info.executable) >= 0:
+ mod = modname.replace(self.info.executable, self.info.name)
+ else:
+ mod = modname
+ self.fp.write('\n.P\n.I %s\n' % mod)
+ for flag in self.info.modules[modname]:
+ help_string = flag.help
+ if flag.default or flag.tips:
+ help_string += '\n.br\n'
+ if flag.default:
+ help_string += ' (default: \'%s\')' % flag.default
+ if flag.tips:
+ help_string += ' (%s)' % flag.tips
+ self.fp.write(
+ '.TP\n%s\n%s\n' % (flag.desc, help_string))
+
+ def Footer(self):
+ self.fp.write(
+ '.SH COPYRIGHT\nCopyright \(co %s Google.\n'
+ % time.strftime('%Y', self.info.date))
+ self.fp.write('Gflags2man created this page from "%s %s" output.\n'
+ % (self.info.name, FLAGS.help_flag))
+ self.fp.write('\nGflags2man was written by Dan Christian. '
+ ' Note that the date on this'
+ ' page is the modification date of %s.\n' % self.info.name)
+
+
+def main(argv):
+ argv = FLAGS(argv) # handles help as well
+ if len(argv) <= 1:
+ print >>sys.stderr, __doc__
+ print >>sys.stderr, "flags:"
+ print >>sys.stderr, str(FLAGS)
+ return 1
+
+ for arg in argv[1:]:
+ prog = ProgramInfo(arg)
+ if not prog.Run():
+ continue
+ prog.Parse()
+ prog.Filter()
+ doc = GenerateMan(prog, FLAGS.dest_dir)
+ doc.Output()
+ return 0
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/vendor/python-gflags/gflags_helpxml_test.py b/vendor/python-gflags/gflags_helpxml_test.py
new file mode 100755
index 0000000000..aea2ffbdb2
--- /dev/null
+++ b/vendor/python-gflags/gflags_helpxml_test.py
@@ -0,0 +1,563 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Unit tests for the XML-format help generated by the gflags.py module."""
+
+__author__ = 'Alex Salcianu'
+
+
+import string
+import StringIO
+import sys
+import unittest
+import xml.dom.minidom
+import xml.sax.saxutils
+
+# We use the name 'flags' internally in this test, for historical reasons.
+# Don't do this yourself! :-) Just do 'import gflags; FLAGS=gflags.FLAGS; etc'
+import gflags as flags
+
+# For historic reasons, we use the name module_bar instead of test_module_bar
+import test_module_bar as module_bar
+
+def MultiLineEqual(expected_help, help):
+ """Returns True if expected_help == help. Otherwise returns False
+ and logs the difference in a human-readable way.
+ """
+ if help == expected_help:
+ return True
+
+ print "Error: FLAGS.MainModuleHelp() didn't return the expected result."
+ print "Got:"
+ print help
+ print "[End of got]"
+
+ help_lines = help.split('\n')
+ expected_help_lines = expected_help.split('\n')
+
+ num_help_lines = len(help_lines)
+ num_expected_help_lines = len(expected_help_lines)
+
+ if num_help_lines != num_expected_help_lines:
+ print "Number of help lines = %d, expected %d" % (
+ num_help_lines, num_expected_help_lines)
+
+ num_to_match = min(num_help_lines, num_expected_help_lines)
+
+ for i in range(num_to_match):
+ if help_lines[i] != expected_help_lines[i]:
+ print "One discrepancy: Got:"
+ print help_lines[i]
+ print "Expected:"
+ print expected_help_lines[i]
+ break
+ else:
+ # If we got here, found no discrepancy, print first new line.
+ if num_help_lines > num_expected_help_lines:
+ print "New help line:"
+ print help_lines[num_expected_help_lines]
+ elif num_expected_help_lines > num_help_lines:
+ print "Missing expected help line:"
+ print expected_help_lines[num_help_lines]
+ else:
+ print "Bug in this test -- discrepancy detected but not found."
+
+ return False
+
+
+class _MakeXMLSafeTest(unittest.TestCase):
+
+ def _Check(self, s, expected_output):
+ self.assertEqual(flags._MakeXMLSafe(s), expected_output)
+
+ def testMakeXMLSafe(self):
+ self._Check('plain text', 'plain text')
+ self._Check('(x < y) && (a >= b)',
+ '(x &lt; y) &amp;&amp; (a &gt;= b)')
+ # Some characters with ASCII code < 32 are illegal in XML 1.0 and
+ # are removed by us. However, '\n', '\t', and '\r' are legal.
+ self._Check('\x09\x0btext \x02 with\x0dsome \x08 good & bad chars',
+ '\ttext with\rsome good &amp; bad chars')
+
+
+def _ListSeparatorsInXMLFormat(separators, indent=''):
+ """Generates XML encoding of a list of list separators.
+
+ Args:
+ separators: A list of list separators. Usually, this should be a
+ string whose characters are the valid list separators, e.g., ','
+ means that both comma (',') and space (' ') are valid list
+ separators.
+ indent: A string that is added at the beginning of each generated
+ XML element.
+
+ Returns:
+ A string.
+ """
+ result = ''
+ separators = list(separators)
+ separators.sort()
+ for sep_char in separators:
+ result += ('%s<list_separator>%s</list_separator>\n' %
+ (indent, repr(sep_char)))
+ return result
+
+
+class WriteFlagHelpInXMLFormatTest(unittest.TestCase):
+ """Test the XML-format help for a single flag at a time.
+
+ There is one test* method for each kind of DEFINE_* declaration.
+ """
+
+ def setUp(self):
+ # self.fv is a FlagValues object, just like flags.FLAGS. Each
+ # test registers one flag with this FlagValues.
+ self.fv = flags.FlagValues()
+
+ def assertMultiLineEqual(self, expected, actual):
+ self.assert_(MultiLineEqual(expected, actual))
+
+ def _CheckFlagHelpInXML(self, flag_name, module_name,
+ expected_output, is_key=False):
+ # StringIO.StringIO is a file object that writes into a memory string.
+ sio = StringIO.StringIO()
+ flag_obj = self.fv[flag_name]
+ flag_obj.WriteInfoInXMLFormat(sio, module_name, is_key=is_key, indent=' ')
+ self.assertMultiLineEqual(sio.getvalue(), expected_output)
+ sio.close()
+
+ def testFlagHelpInXML_Int(self):
+ flags.DEFINE_integer('index', 17, 'An integer flag', flag_values=self.fv)
+ expected_output_pattern = (
+ ' <flag>\n'
+ ' <file>module.name</file>\n'
+ ' <name>index</name>\n'
+ ' <meaning>An integer flag</meaning>\n'
+ ' <default>17</default>\n'
+ ' <current>%d</current>\n'
+ ' <type>int</type>\n'
+ ' </flag>\n')
+ self._CheckFlagHelpInXML('index', 'module.name',
+ expected_output_pattern % 17)
+ # Check that the output is correct even when the current value of
+ # a flag is different from the default one.
+ self.fv['index'].value = 20
+ self._CheckFlagHelpInXML('index', 'module.name',
+ expected_output_pattern % 20)
+
+ def testFlagHelpInXML_IntWithBounds(self):
+ flags.DEFINE_integer('nb_iters', 17, 'An integer flag',
+ lower_bound=5, upper_bound=27,
+ flag_values=self.fv)
+ expected_output = (
+ ' <flag>\n'
+ ' <key>yes</key>\n'
+ ' <file>module.name</file>\n'
+ ' <name>nb_iters</name>\n'
+ ' <meaning>An integer flag</meaning>\n'
+ ' <default>17</default>\n'
+ ' <current>17</current>\n'
+ ' <type>int</type>\n'
+ ' <lower_bound>5</lower_bound>\n'
+ ' <upper_bound>27</upper_bound>\n'
+ ' </flag>\n')
+ self._CheckFlagHelpInXML('nb_iters', 'module.name',
+ expected_output, is_key=True)
+
+ def testFlagHelpInXML_String(self):
+ flags.DEFINE_string('file_path', '/path/to/my/dir', 'A test string flag.',
+ flag_values=self.fv)
+ expected_output = (
+ ' <flag>\n'
+ ' <file>simple_module</file>\n'
+ ' <name>file_path</name>\n'
+ ' <meaning>A test string flag.</meaning>\n'
+ ' <default>/path/to/my/dir</default>\n'
+ ' <current>/path/to/my/dir</current>\n'
+ ' <type>string</type>\n'
+ ' </flag>\n')
+ self._CheckFlagHelpInXML('file_path', 'simple_module',
+ expected_output)
+
+ def testFlagHelpInXML_StringWithXMLIllegalChars(self):
+ flags.DEFINE_string('file_path', '/path/to/\x08my/dir',
+ 'A test string flag.', flag_values=self.fv)
+ # '\x08' is not a legal character in XML 1.0 documents. Our
+ # current code purges such characters from the generated XML.
+ expected_output = (
+ ' <flag>\n'
+ ' <file>simple_module</file>\n'
+ ' <name>file_path</name>\n'
+ ' <meaning>A test string flag.</meaning>\n'
+ ' <default>/path/to/my/dir</default>\n'
+ ' <current>/path/to/my/dir</current>\n'
+ ' <type>string</type>\n'
+ ' </flag>\n')
+ self._CheckFlagHelpInXML('file_path', 'simple_module',
+ expected_output)
+
+ def testFlagHelpInXML_Boolean(self):
+ flags.DEFINE_boolean('use_hack', False, 'Use performance hack',
+ flag_values=self.fv)
+ expected_output = (
+ ' <flag>\n'
+ ' <key>yes</key>\n'
+ ' <file>a_module</file>\n'
+ ' <name>use_hack</name>\n'
+ ' <meaning>Use performance hack</meaning>\n'
+ ' <default>false</default>\n'
+ ' <current>false</current>\n'
+ ' <type>bool</type>\n'
+ ' </flag>\n')
+ self._CheckFlagHelpInXML('use_hack', 'a_module',
+ expected_output, is_key=True)
+
+ def testFlagHelpInXML_Enum(self):
+ flags.DEFINE_enum('cc_version', 'stable', ['stable', 'experimental'],
+ 'Compiler version to use.', flag_values=self.fv)
+ expected_output = (
+ ' <flag>\n'
+ ' <file>tool</file>\n'
+ ' <name>cc_version</name>\n'
+ ' <meaning>&lt;stable|experimental&gt;: '
+ 'Compiler version to use.</meaning>\n'
+ ' <default>stable</default>\n'
+ ' <current>stable</current>\n'
+ ' <type>string enum</type>\n'
+ ' <enum_value>stable</enum_value>\n'
+ ' <enum_value>experimental</enum_value>\n'
+ ' </flag>\n')
+ self._CheckFlagHelpInXML('cc_version', 'tool', expected_output)
+
+ def testFlagHelpInXML_CommaSeparatedList(self):
+ flags.DEFINE_list('files', 'a.cc,a.h,archive/old.zip',
+ 'Files to process.', flag_values=self.fv)
+ expected_output = (
+ ' <flag>\n'
+ ' <file>tool</file>\n'
+ ' <name>files</name>\n'
+ ' <meaning>Files to process.</meaning>\n'
+ ' <default>a.cc,a.h,archive/old.zip</default>\n'
+ ' <current>[\'a.cc\', \'a.h\', \'archive/old.zip\']</current>\n'
+ ' <type>comma separated list of strings</type>\n'
+ ' <list_separator>\',\'</list_separator>\n'
+ ' </flag>\n')
+ self._CheckFlagHelpInXML('files', 'tool', expected_output)
+
+ def testFlagHelpInXML_SpaceSeparatedList(self):
+ flags.DEFINE_spaceseplist('dirs', 'src libs bin',
+ 'Directories to search.', flag_values=self.fv)
+ expected_output = (
+ ' <flag>\n'
+ ' <file>tool</file>\n'
+ ' <name>dirs</name>\n'
+ ' <meaning>Directories to search.</meaning>\n'
+ ' <default>src libs bin</default>\n'
+ ' <current>[\'src\', \'libs\', \'bin\']</current>\n'
+ ' <type>whitespace separated list of strings</type>\n'
+ 'LIST_SEPARATORS'
+ ' </flag>\n').replace('LIST_SEPARATORS',
+ _ListSeparatorsInXMLFormat(string.whitespace,
+ indent=' '))
+ self._CheckFlagHelpInXML('dirs', 'tool', expected_output)
+
+ def testFlagHelpInXML_MultiString(self):
+ flags.DEFINE_multistring('to_delete', ['a.cc', 'b.h'],
+ 'Files to delete', flag_values=self.fv)
+ expected_output = (
+ ' <flag>\n'
+ ' <file>tool</file>\n'
+ ' <name>to_delete</name>\n'
+ ' <meaning>Files to delete;\n '
+ 'repeat this option to specify a list of values</meaning>\n'
+ ' <default>[\'a.cc\', \'b.h\']</default>\n'
+ ' <current>[\'a.cc\', \'b.h\']</current>\n'
+ ' <type>multi string</type>\n'
+ ' </flag>\n')
+ self._CheckFlagHelpInXML('to_delete', 'tool', expected_output)
+
+ def testFlagHelpInXML_MultiInt(self):
+ flags.DEFINE_multi_int('cols', [5, 7, 23],
+ 'Columns to select', flag_values=self.fv)
+ expected_output = (
+ ' <flag>\n'
+ ' <file>tool</file>\n'
+ ' <name>cols</name>\n'
+ ' <meaning>Columns to select;\n '
+ 'repeat this option to specify a list of values</meaning>\n'
+ ' <default>[5, 7, 23]</default>\n'
+ ' <current>[5, 7, 23]</current>\n'
+ ' <type>multi int</type>\n'
+ ' </flag>\n')
+ self._CheckFlagHelpInXML('cols', 'tool', expected_output)
+
+
+# The next EXPECTED_HELP_XML_* constants are parts of a template for
+# the expected XML output from WriteHelpInXMLFormatTest below. When
+# we assemble these parts into a single big string, we'll take into
+# account the ordering between the name of the main module and the
+# name of module_bar. Next, we'll fill in the docstring for this
+# module (%(usage_doc)s), the name of the main module
+# (%(main_module_name)s) and the name of the module module_bar
+# (%(module_bar_name)s). See WriteHelpInXMLFormatTest below.
+#
+# NOTE: given the current implementation of _GetMainModule(), we
+# already know the ordering between the main module and module_bar.
+# However, there is no guarantee that _GetMainModule will never be
+# changed in the future (especially since it's far from perfect).
+EXPECTED_HELP_XML_START = """\
+<?xml version="1.0"?>
+<AllFlags>
+ <program>gflags_helpxml_test.py</program>
+ <usage>%(usage_doc)s</usage>
+"""
+
+EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE = """\
+ <flag>
+ <key>yes</key>
+ <file>%(main_module_name)s</file>
+ <name>cc_version</name>
+ <meaning>&lt;stable|experimental&gt;: Compiler version to use.</meaning>
+ <default>stable</default>
+ <current>stable</current>
+ <type>string enum</type>
+ <enum_value>stable</enum_value>
+ <enum_value>experimental</enum_value>
+ </flag>
+ <flag>
+ <key>yes</key>
+ <file>%(main_module_name)s</file>
+ <name>cols</name>
+ <meaning>Columns to select;
+ repeat this option to specify a list of values</meaning>
+ <default>[5, 7, 23]</default>
+ <current>[5, 7, 23]</current>
+ <type>multi int</type>
+ </flag>
+ <flag>
+ <key>yes</key>
+ <file>%(main_module_name)s</file>
+ <name>dirs</name>
+ <meaning>Directories to create.</meaning>
+ <default>src libs bins</default>
+ <current>['src', 'libs', 'bins']</current>
+ <type>whitespace separated list of strings</type>
+%(whitespace_separators)s </flag>
+ <flag>
+ <key>yes</key>
+ <file>%(main_module_name)s</file>
+ <name>file_path</name>
+ <meaning>A test string flag.</meaning>
+ <default>/path/to/my/dir</default>
+ <current>/path/to/my/dir</current>
+ <type>string</type>
+ </flag>
+ <flag>
+ <key>yes</key>
+ <file>%(main_module_name)s</file>
+ <name>files</name>
+ <meaning>Files to process.</meaning>
+ <default>a.cc,a.h,archive/old.zip</default>
+ <current>['a.cc', 'a.h', 'archive/old.zip']</current>
+ <type>comma separated list of strings</type>
+ <list_separator>\',\'</list_separator>
+ </flag>
+ <flag>
+ <key>yes</key>
+ <file>%(main_module_name)s</file>
+ <name>index</name>
+ <meaning>An integer flag</meaning>
+ <default>17</default>
+ <current>17</current>
+ <type>int</type>
+ </flag>
+ <flag>
+ <key>yes</key>
+ <file>%(main_module_name)s</file>
+ <name>nb_iters</name>
+ <meaning>An integer flag</meaning>
+ <default>17</default>
+ <current>17</current>
+ <type>int</type>
+ <lower_bound>5</lower_bound>
+ <upper_bound>27</upper_bound>
+ </flag>
+ <flag>
+ <key>yes</key>
+ <file>%(main_module_name)s</file>
+ <name>to_delete</name>
+ <meaning>Files to delete;
+ repeat this option to specify a list of values</meaning>
+ <default>['a.cc', 'b.h']</default>
+ <current>['a.cc', 'b.h']</current>
+ <type>multi string</type>
+ </flag>
+ <flag>
+ <key>yes</key>
+ <file>%(main_module_name)s</file>
+ <name>use_hack</name>
+ <meaning>Use performance hack</meaning>
+ <default>false</default>
+ <current>false</current>
+ <type>bool</type>
+ </flag>
+"""
+
+EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR = """\
+ <flag>
+ <file>%(module_bar_name)s</file>
+ <name>tmod_bar_t</name>
+ <meaning>Sample int flag.</meaning>
+ <default>4</default>
+ <current>4</current>
+ <type>int</type>
+ </flag>
+ <flag>
+ <key>yes</key>
+ <file>%(module_bar_name)s</file>
+ <name>tmod_bar_u</name>
+ <meaning>Sample int flag.</meaning>
+ <default>5</default>
+ <current>5</current>
+ <type>int</type>
+ </flag>
+ <flag>
+ <file>%(module_bar_name)s</file>
+ <name>tmod_bar_v</name>
+ <meaning>Sample int flag.</meaning>
+ <default>6</default>
+ <current>6</current>
+ <type>int</type>
+ </flag>
+ <flag>
+ <file>%(module_bar_name)s</file>
+ <name>tmod_bar_x</name>
+ <meaning>Boolean flag.</meaning>
+ <default>true</default>
+ <current>true</current>
+ <type>bool</type>
+ </flag>
+ <flag>
+ <file>%(module_bar_name)s</file>
+ <name>tmod_bar_y</name>
+ <meaning>String flag.</meaning>
+ <default>default</default>
+ <current>default</current>
+ <type>string</type>
+ </flag>
+ <flag>
+ <key>yes</key>
+ <file>%(module_bar_name)s</file>
+ <name>tmod_bar_z</name>
+ <meaning>Another boolean flag from module bar.</meaning>
+ <default>false</default>
+ <current>false</current>
+ <type>bool</type>
+ </flag>
+"""
+
+EXPECTED_HELP_XML_END = """\
+</AllFlags>
+"""
+
+
+class WriteHelpInXMLFormatTest(unittest.TestCase):
+ """Big test of FlagValues.WriteHelpInXMLFormat, with several flags."""
+
+ def assertMultiLineEqual(self, expected, actual):
+ self.assert_(MultiLineEqual(expected, actual))
+
+ def testWriteHelpInXMLFormat(self):
+ fv = flags.FlagValues()
+ # Since these flags are defined by the top module, they are all key.
+ flags.DEFINE_integer('index', 17, 'An integer flag', flag_values=fv)
+ flags.DEFINE_integer('nb_iters', 17, 'An integer flag',
+ lower_bound=5, upper_bound=27, flag_values=fv)
+ flags.DEFINE_string('file_path', '/path/to/my/dir', 'A test string flag.',
+ flag_values=fv)
+ flags.DEFINE_boolean('use_hack', False, 'Use performance hack',
+ flag_values=fv)
+ flags.DEFINE_enum('cc_version', 'stable', ['stable', 'experimental'],
+ 'Compiler version to use.', flag_values=fv)
+ flags.DEFINE_list('files', 'a.cc,a.h,archive/old.zip',
+ 'Files to process.', flag_values=fv)
+ flags.DEFINE_spaceseplist('dirs', 'src libs bins',
+ 'Directories to create.', flag_values=fv)
+ flags.DEFINE_multistring('to_delete', ['a.cc', 'b.h'],
+ 'Files to delete', flag_values=fv)
+ flags.DEFINE_multi_int('cols', [5, 7, 23],
+ 'Columns to select', flag_values=fv)
+ # Define a few flags in a different module.
+ module_bar.DefineFlags(flag_values=fv)
+ # And declare only a few of them to be key. This way, we have
+ # different kinds of flags, defined in different modules, and not
+ # all of them are key flags.
+ flags.DECLARE_key_flag('tmod_bar_z', flag_values=fv)
+ flags.DECLARE_key_flag('tmod_bar_u', flag_values=fv)
+
+ # Generate flag help in XML format in the StringIO sio.
+ sio = StringIO.StringIO()
+ fv.WriteHelpInXMLFormat(sio)
+
+ # Check that we got the expected result.
+ expected_output_template = EXPECTED_HELP_XML_START
+ main_module_name = flags._GetMainModule()
+ module_bar_name = module_bar.__name__
+
+ if main_module_name < module_bar_name:
+ expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE
+ expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR
+ else:
+ expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MODULE_BAR
+ expected_output_template += EXPECTED_HELP_XML_FOR_FLAGS_FROM_MAIN_MODULE
+
+ expected_output_template += EXPECTED_HELP_XML_END
+
+ # XML representation of the whitespace list separators.
+ whitespace_separators = _ListSeparatorsInXMLFormat(string.whitespace,
+ indent=' ')
+ expected_output = (
+ expected_output_template %
+ {'usage_doc': sys.modules['__main__'].__doc__,
+ 'main_module_name': main_module_name,
+ 'module_bar_name': module_bar_name,
+ 'whitespace_separators': whitespace_separators})
+
+ actual_output = sio.getvalue()
+ self.assertMultiLineEqual(actual_output, expected_output)
+
+ # Also check that our result is valid XML. minidom.parseString
+ # throws an xml.parsers.expat.ExpatError in case of an error.
+ xml.dom.minidom.parseString(actual_output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/vendor/python-gflags/gflags_unittest.py b/vendor/python-gflags/gflags_unittest.py
new file mode 100755
index 0000000000..07420c03f7
--- /dev/null
+++ b/vendor/python-gflags/gflags_unittest.py
@@ -0,0 +1,1679 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2007, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"Unittest for gflags.py module"
+
+__pychecker__ = "no-local" # for unittest
+
+
+import sys
+import os
+import shutil
+import unittest
+
+# We use the name 'flags' internally in this test, for historical reasons.
+# Don't do this yourself! :-) Just do 'import gflags; FLAGS=gflags.FLAGS; etc'
+import gflags as flags
+FLAGS=flags.FLAGS
+
+# For historic reasons, we use the name module_foo instead of
+# test_module_foo, and module_bar instead of test_module_bar.
+import test_module_foo as module_foo
+import test_module_bar as module_bar
+import test_module_baz as module_baz
+
+def MultiLineEqual(expected_help, help):
+ """Returns True if expected_help == help. Otherwise returns False
+ and logs the difference in a human-readable way.
+ """
+ if help == expected_help:
+ return True
+
+ print "Error: FLAGS.MainModuleHelp() didn't return the expected result."
+ print "Got:"
+ print help
+ print "[End of got]"
+
+ help_lines = help.split('\n')
+ expected_help_lines = expected_help.split('\n')
+
+ num_help_lines = len(help_lines)
+ num_expected_help_lines = len(expected_help_lines)
+
+ if num_help_lines != num_expected_help_lines:
+ print "Number of help lines = %d, expected %d" % (
+ num_help_lines, num_expected_help_lines)
+
+ num_to_match = min(num_help_lines, num_expected_help_lines)
+
+ for i in range(num_to_match):
+ if help_lines[i] != expected_help_lines[i]:
+ print "One discrepancy: Got:"
+ print help_lines[i]
+ print "Expected:"
+ print expected_help_lines[i]
+ break
+ else:
+ # If we got here, found no discrepancy, print first new line.
+ if num_help_lines > num_expected_help_lines:
+ print "New help line:"
+ print help_lines[num_expected_help_lines]
+ elif num_expected_help_lines > num_help_lines:
+ print "Missing expected help line:"
+ print expected_help_lines[num_help_lines]
+ else:
+ print "Bug in this test -- discrepancy detected but not found."
+
+ return False
+
+
+class FlagsUnitTest(unittest.TestCase):
+ "Flags Unit Test"
+
+ def setUp(self):
+ # make sure we are using the old, stupid way of parsing flags.
+ FLAGS.UseGnuGetOpt(False)
+
+ def assertListEqual(self, list1, list2):
+ """Asserts that, when sorted, list1 and list2 are identical."""
+ sorted_list1 = list1[:]
+ sorted_list2 = list2[:]
+ sorted_list1.sort()
+ sorted_list2.sort()
+ self.assertEqual(sorted_list1, sorted_list2)
+
+ def assertMultiLineEqual(self, expected, actual):
+ self.assert_(MultiLineEqual(expected, actual))
+
+
+ def test_flags(self):
+
+ ##############################################
+ # Test normal usage with no (expected) errors.
+
+ # Define flags
+ number_test_framework_flags = len(FLAGS.RegisteredFlags())
+ repeatHelp = "how many times to repeat (0-5)"
+ flags.DEFINE_integer("repeat", 4, repeatHelp,
+ lower_bound=0, short_name='r')
+ flags.DEFINE_string("name", "Bob", "namehelp")
+ flags.DEFINE_boolean("debug", 0, "debughelp")
+ flags.DEFINE_boolean("q", 1, "quiet mode")
+ flags.DEFINE_boolean("quack", 0, "superstring of 'q'")
+ flags.DEFINE_boolean("noexec", 1, "boolean flag with no as prefix")
+ flags.DEFINE_integer("x", 3, "how eXtreme to be")
+ flags.DEFINE_integer("l", 0x7fffffff00000000L, "how long to be")
+ flags.DEFINE_list('letters', 'a,b,c', "a list of letters")
+ flags.DEFINE_list('numbers', [1, 2, 3], "a list of numbers")
+ flags.DEFINE_enum("kwery", None, ['who', 'what', 'why', 'where', 'when'],
+ "?")
+
+ # Specify number of flags defined above. The short_name defined
+ # for 'repeat' counts as an extra flag.
+ number_defined_flags = 11 + 1
+ self.assertEqual(len(FLAGS.RegisteredFlags()),
+ number_defined_flags + number_test_framework_flags)
+
+ assert FLAGS.repeat == 4, "integer default values not set:" + FLAGS.repeat
+ assert FLAGS.name == 'Bob', "default values not set:" + FLAGS.name
+ assert FLAGS.debug == 0, "boolean default values not set:" + FLAGS.debug
+ assert FLAGS.q == 1, "boolean default values not set:" + FLAGS.q
+ assert FLAGS.x == 3, "integer default values not set:" + FLAGS.x
+ assert FLAGS.l == 0x7fffffff00000000L, ("integer default values not set:"
+ + FLAGS.l)
+ assert FLAGS.letters == ['a', 'b', 'c'], ("list default values not set:"
+ + FLAGS.letters)
+ assert FLAGS.numbers == [1, 2, 3], ("list default values not set:"
+ + FLAGS.numbers)
+ assert FLAGS.kwery is None, ("enum default None value not set:"
+ + FLAGS.kwery)
+
+ flag_values = FLAGS.FlagValuesDict()
+ assert flag_values['repeat'] == 4
+ assert flag_values['name'] == 'Bob'
+ assert flag_values['debug'] == 0
+ assert flag_values['r'] == 4 # short for of repeat
+ assert flag_values['q'] == 1
+ assert flag_values['quack'] == 0
+ assert flag_values['x'] == 3
+ assert flag_values['l'] == 0x7fffffff00000000L
+ assert flag_values['letters'] == ['a', 'b', 'c']
+ assert flag_values['numbers'] == [1, 2, 3]
+ assert flag_values['kwery'] is None
+
+ # Verify string form of defaults
+ assert FLAGS['repeat'].default_as_str == "'4'"
+ assert FLAGS['name'].default_as_str == "'Bob'"
+ assert FLAGS['debug'].default_as_str == "'false'"
+ assert FLAGS['q'].default_as_str == "'true'"
+ assert FLAGS['quack'].default_as_str == "'false'"
+ assert FLAGS['noexec'].default_as_str == "'true'"
+ assert FLAGS['x'].default_as_str == "'3'"
+ assert FLAGS['l'].default_as_str == "'9223372032559808512'"
+ assert FLAGS['letters'].default_as_str == "'a,b,c'"
+ assert FLAGS['numbers'].default_as_str == "'1,2,3'"
+
+ # Verify that the iterator for flags yields all the keys
+ keys = list(FLAGS)
+ keys.sort()
+ reg_flags = FLAGS.RegisteredFlags()
+ reg_flags.sort()
+ self.assertEqual(keys, reg_flags)
+
+ # Parse flags
+ # .. empty command line
+ argv = ('./program',)
+ argv = FLAGS(argv)
+ assert len(argv) == 1, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+
+ # .. non-empty command line
+ argv = ('./program', '--debug', '--name=Bob', '-q', '--x=8')
+ argv = FLAGS(argv)
+ assert len(argv) == 1, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert FLAGS['debug'].present == 1
+ FLAGS['debug'].present = 0 # Reset
+ assert FLAGS['name'].present == 1
+ FLAGS['name'].present = 0 # Reset
+ assert FLAGS['q'].present == 1
+ FLAGS['q'].present = 0 # Reset
+ assert FLAGS['x'].present == 1
+ FLAGS['x'].present = 0 # Reset
+
+ # Flags list
+ self.assertEqual(len(FLAGS.RegisteredFlags()),
+ number_defined_flags + number_test_framework_flags)
+ assert 'name' in FLAGS.RegisteredFlags()
+ assert 'debug' in FLAGS.RegisteredFlags()
+ assert 'repeat' in FLAGS.RegisteredFlags()
+ assert 'r' in FLAGS.RegisteredFlags()
+ assert 'q' in FLAGS.RegisteredFlags()
+ assert 'quack' in FLAGS.RegisteredFlags()
+ assert 'x' in FLAGS.RegisteredFlags()
+ assert 'l' in FLAGS.RegisteredFlags()
+ assert 'letters' in FLAGS.RegisteredFlags()
+ assert 'numbers' in FLAGS.RegisteredFlags()
+
+ # has_key
+ assert FLAGS.has_key('name')
+ assert not FLAGS.has_key('name2')
+ assert 'name' in FLAGS
+ assert 'name2' not in FLAGS
+
+ # try deleting a flag
+ del FLAGS.r
+ self.assertEqual(len(FLAGS.RegisteredFlags()),
+ number_defined_flags - 1 + number_test_framework_flags)
+ assert not 'r' in FLAGS.RegisteredFlags()
+
+ # .. command line with extra stuff
+ argv = ('./program', '--debug', '--name=Bob', 'extra')
+ argv = FLAGS(argv)
+ assert len(argv) == 2, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert argv[1]=='extra', "extra argument not preserved"
+ assert FLAGS['debug'].present == 1
+ FLAGS['debug'].present = 0 # Reset
+ assert FLAGS['name'].present == 1
+ FLAGS['name'].present = 0 # Reset
+
+ # Test reset
+ argv = ('./program', '--debug')
+ argv = FLAGS(argv)
+ assert len(argv) == 1, "wrong number of arguments pulled"
+ assert argv[0] == './program', "program name not preserved"
+ assert FLAGS['debug'].present == 1
+ assert FLAGS['debug'].value
+ FLAGS.Reset()
+ assert FLAGS['debug'].present == 0
+ assert not FLAGS['debug'].value
+
+ # Test that reset restores default value when default value is None.
+ argv = ('./program', '--kwery=who')
+ argv = FLAGS(argv)
+ assert len(argv) == 1, "wrong number of arguments pulled"
+ assert argv[0] == './program', "program name not preserved"
+ assert FLAGS['kwery'].present == 1
+ assert FLAGS['kwery'].value == 'who'
+ FLAGS.Reset()
+ assert FLAGS['kwery'].present == 0
+ assert FLAGS['kwery'].value == None
+
+ # Test integer argument passing
+ argv = ('./program', '--x', '0x12345')
+ argv = FLAGS(argv)
+ self.assertEquals(FLAGS.x, 0x12345)
+ self.assertEquals(type(FLAGS.x), int)
+
+ argv = ('./program', '--x', '0x1234567890ABCDEF1234567890ABCDEF')
+ argv = FLAGS(argv)
+ self.assertEquals(FLAGS.x, 0x1234567890ABCDEF1234567890ABCDEF)
+ self.assertEquals(type(FLAGS.x), long)
+
+ # Treat 0-prefixed parameters as base-10, not base-8
+ argv = ('./program', '--x', '012345')
+ argv = FLAGS(argv)
+ self.assertEquals(FLAGS.x, 12345)
+ self.assertEquals(type(FLAGS.x), int)
+
+ argv = ('./program', '--x', '0123459')
+ argv = FLAGS(argv)
+ self.assertEquals(FLAGS.x, 123459)
+ self.assertEquals(type(FLAGS.x), int)
+
+ argv = ('./program', '--x', '0x123efg')
+ try:
+ argv = FLAGS(argv)
+ raise AssertionError("failed to detect invalid hex argument")
+ except flags.IllegalFlagValue:
+ pass
+
+ argv = ('./program', '--x', '0X123efg')
+ try:
+ argv = FLAGS(argv)
+ raise AssertionError("failed to detect invalid hex argument")
+ except flags.IllegalFlagValue:
+ pass
+
+ # Test boolean argument parsing
+ flags.DEFINE_boolean("test0", None, "test boolean parsing")
+ argv = ('./program', '--notest0')
+ argv = FLAGS(argv)
+ assert FLAGS.test0 == 0
+
+ flags.DEFINE_boolean("test1", None, "test boolean parsing")
+ argv = ('./program', '--test1')
+ argv = FLAGS(argv)
+ assert FLAGS.test1 == 1
+
+ FLAGS.test0 = None
+ argv = ('./program', '--test0=false')
+ argv = FLAGS(argv)
+ assert FLAGS.test0 == 0
+
+ FLAGS.test1 = None
+ argv = ('./program', '--test1=true')
+ argv = FLAGS(argv)
+ assert FLAGS.test1 == 1
+
+ FLAGS.test0 = None
+ argv = ('./program', '--test0=0')
+ argv = FLAGS(argv)
+ assert FLAGS.test0 == 0
+
+ FLAGS.test1 = None
+ argv = ('./program', '--test1=1')
+ argv = FLAGS(argv)
+ assert FLAGS.test1 == 1
+
+ # Test booleans that already have 'no' as a prefix
+ FLAGS.noexec = None
+ argv = ('./program', '--nonoexec', '--name', 'Bob')
+ argv = FLAGS(argv)
+ assert FLAGS.noexec == 0
+
+ FLAGS.noexec = None
+ argv = ('./program', '--name', 'Bob', '--noexec')
+ argv = FLAGS(argv)
+ assert FLAGS.noexec == 1
+
+ # Test unassigned booleans
+ flags.DEFINE_boolean("testnone", None, "test boolean parsing")
+ argv = ('./program',)
+ argv = FLAGS(argv)
+ assert FLAGS.testnone == None
+
+ # Test get with default
+ flags.DEFINE_boolean("testget1", None, "test parsing with defaults")
+ flags.DEFINE_boolean("testget2", None, "test parsing with defaults")
+ flags.DEFINE_boolean("testget3", None, "test parsing with defaults")
+ flags.DEFINE_integer("testget4", None, "test parsing with defaults")
+ argv = ('./program','--testget1','--notestget2')
+ argv = FLAGS(argv)
+ assert FLAGS.get('testget1', 'foo') == 1
+ assert FLAGS.get('testget2', 'foo') == 0
+ assert FLAGS.get('testget3', 'foo') == 'foo'
+ assert FLAGS.get('testget4', 'foo') == 'foo'
+
+ # test list code
+ lists = [['hello','moo','boo','1'],
+ [],]
+
+ flags.DEFINE_list('testlist', '', 'test lists parsing')
+ flags.DEFINE_spaceseplist('testspacelist', '', 'tests space lists parsing')
+
+ for name, sep in (('testlist', ','), ('testspacelist', ' '),
+ ('testspacelist', '\n')):
+ for lst in lists:
+ argv = ('./program', '--%s=%s' % (name, sep.join(lst)))
+ argv = FLAGS(argv)
+ self.assertEquals(getattr(FLAGS, name), lst)
+
+ # Test help text
+ flagsHelp = str(FLAGS)
+ assert flagsHelp.find("repeat") != -1, "cannot find flag in help"
+ assert flagsHelp.find(repeatHelp) != -1, "cannot find help string in help"
+
+ # Test flag specified twice
+ argv = ('./program', '--repeat=4', '--repeat=2', '--debug', '--nodebug')
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.get('repeat', None), 2)
+ self.assertEqual(FLAGS.get('debug', None), 0)
+
+ # Test MultiFlag with single default value
+ flags.DEFINE_multistring('s_str', 'sing1',
+ 'string option that can occur multiple times',
+ short_name='s')
+ self.assertEqual(FLAGS.get('s_str', None), [ 'sing1', ])
+
+ # Test MultiFlag with list of default values
+ multi_string_defs = [ 'def1', 'def2', ]
+ flags.DEFINE_multistring('m_str', multi_string_defs,
+ 'string option that can occur multiple times',
+ short_name='m')
+ self.assertEqual(FLAGS.get('m_str', None), multi_string_defs)
+
+ # Test flag specified multiple times with a MultiFlag
+ argv = ('./program', '--m_str=str1', '-m', 'str2')
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.get('m_str', None), [ 'str1', 'str2', ])
+
+ # Test single-letter flags; should support both single and double dash
+ argv = ('./program', '-q', '-x8')
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.get('q', None), 1)
+ self.assertEqual(FLAGS.get('x', None), 8)
+
+ argv = ('./program', '--q', '--x', '9', '--noqu')
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.get('q', None), 1)
+ self.assertEqual(FLAGS.get('x', None), 9)
+ # --noqu should match '--noquack since it's a unique prefix
+ self.assertEqual(FLAGS.get('quack', None), 0)
+
+ argv = ('./program', '--noq', '--x=10', '--qu')
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.get('q', None), 0)
+ self.assertEqual(FLAGS.get('x', None), 10)
+ self.assertEqual(FLAGS.get('quack', None), 1)
+
+ ####################################
+ # Test flag serialization code:
+
+ oldtestlist = FLAGS.testlist
+ oldtestspacelist = FLAGS.testspacelist
+
+ argv = ('./program',
+ FLAGS['test0'].Serialize(),
+ FLAGS['test1'].Serialize(),
+ FLAGS['testnone'].Serialize(),
+ FLAGS['s_str'].Serialize())
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS['test0'].Serialize(), '--notest0')
+ self.assertEqual(FLAGS['test1'].Serialize(), '--test1')
+ self.assertEqual(FLAGS['testnone'].Serialize(), '')
+ self.assertEqual(FLAGS['s_str'].Serialize(), '--s_str=sing1')
+
+ testlist1 = ['aa', 'bb']
+ testspacelist1 = ['aa', 'bb', 'cc']
+ FLAGS.testlist = list(testlist1)
+ FLAGS.testspacelist = list(testspacelist1)
+ argv = ('./program',
+ FLAGS['testlist'].Serialize(),
+ FLAGS['testspacelist'].Serialize())
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.testlist, testlist1)
+ self.assertEqual(FLAGS.testspacelist, testspacelist1)
+
+ testlist1 = ['aa some spaces', 'bb']
+ testspacelist1 = ['aa', 'bb,some,commas,', 'cc']
+ FLAGS.testlist = list(testlist1)
+ FLAGS.testspacelist = list(testspacelist1)
+ argv = ('./program',
+ FLAGS['testlist'].Serialize(),
+ FLAGS['testspacelist'].Serialize())
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.testlist, testlist1)
+ self.assertEqual(FLAGS.testspacelist, testspacelist1)
+
+ FLAGS.testlist = oldtestlist
+ FLAGS.testspacelist = oldtestspacelist
+
+ ####################################
+ # Test flag-update:
+
+ def ArgsString():
+ flagnames = FLAGS.RegisteredFlags()
+
+ flagnames.sort()
+ nonbool_flags = ['--%s %s' % (name, FLAGS.get(name, None))
+ for name in flagnames
+ if not isinstance(FLAGS[name], flags.BooleanFlag)]
+
+ truebool_flags = ['--%s' % (name)
+ for name in flagnames
+ if isinstance(FLAGS[name], flags.BooleanFlag) and
+ FLAGS.get(name, None)]
+ falsebool_flags = ['--no%s' % (name)
+ for name in flagnames
+ if isinstance(FLAGS[name], flags.BooleanFlag) and
+ not FLAGS.get(name, None)]
+ return ' '.join(nonbool_flags + truebool_flags + falsebool_flags)
+
+ argv = ('./program', '--repeat=3', '--name=giants', '--nodebug')
+
+ FLAGS(argv)
+ self.assertEqual(FLAGS.get('repeat', None), 3)
+ self.assertEqual(FLAGS.get('name', None), 'giants')
+ self.assertEqual(FLAGS.get('debug', None), 0)
+ self.assertEqual(ArgsString(),
+ "--kwery None "
+ "--l 9223372032559808512 "
+ "--letters ['a', 'b', 'c'] "
+ "--m ['str1', 'str2'] --m_str ['str1', 'str2'] "
+ "--name giants "
+ "--numbers [1, 2, 3] "
+ "--repeat 3 "
+ "--s ['sing1'] --s_str ['sing1'] "
+ "--testget4 None --testlist [] "
+ "--testspacelist [] --x 10 "
+ "--noexec --quack "
+ "--test1 "
+ "--testget1 --tmod_baz_x --no? --nodebug --nohelp --nohelpshort --nohelpxml "
+ "--noq --notest0 --notestget2 "
+ "--notestget3 --notestnone")
+
+ argv = ('./program', '--debug', '--m_str=upd1', '-s', 'upd2')
+ FLAGS(argv)
+ self.assertEqual(FLAGS.get('repeat', None), 3)
+ self.assertEqual(FLAGS.get('name', None), 'giants')
+ self.assertEqual(FLAGS.get('debug', None), 1)
+
+ # items appended to existing non-default value lists for --m/--m_str
+ # new value overwrites default value (not appended to it) for --s/--s_str
+ self.assertEqual(ArgsString(),
+ "--kwery None "
+ "--l 9223372032559808512 "
+ "--letters ['a', 'b', 'c'] "
+ "--m ['str1', 'str2', 'upd1'] "
+ "--m_str ['str1', 'str2', 'upd1'] "
+ "--name giants "
+ "--numbers [1, 2, 3] "
+ "--repeat 3 "
+ "--s ['upd2'] --s_str ['upd2'] "
+ "--testget4 None --testlist [] "
+ "--testspacelist [] --x 10 "
+ "--debug --noexec --quack "
+ "--test1 "
+ "--testget1 --tmod_baz_x --no? --nohelp --nohelpshort --nohelpxml "
+ "--noq --notest0 --notestget2 "
+ "--notestget3 --notestnone")
+
+
+ ####################################
+ # Test all kind of error conditions.
+
+ # Duplicate flag detection
+ try:
+ flags.DEFINE_boolean("run", 0, "runhelp", short_name='q')
+ raise AssertionError("duplicate flag detection failed")
+ except flags.DuplicateFlag, e:
+ pass
+
+ # Duplicate short flag detection
+ try:
+ flags.DEFINE_boolean("zoom1", 0, "runhelp z1", short_name='z')
+ flags.DEFINE_boolean("zoom2", 0, "runhelp z2", short_name='z')
+ raise AssertionError("duplicate short flag detection failed")
+ except flags.DuplicateFlag, e:
+ self.assertTrue("The flag 'z' is defined twice. " in e.args[0])
+ self.assertTrue("First from" in e.args[0])
+ self.assertTrue(", Second from" in e.args[0])
+
+ # Duplicate mixed flag detection
+ try:
+ flags.DEFINE_boolean("short1", 0, "runhelp s1", short_name='s')
+ flags.DEFINE_boolean("s", 0, "runhelp s2")
+ raise AssertionError("duplicate mixed flag detection failed")
+ except flags.DuplicateFlag, e:
+ self.assertTrue("The flag 's' is defined twice. " in e.args[0])
+ self.assertTrue("First from" in e.args[0])
+ self.assertTrue(", Second from" in e.args[0])
+
+ # Make sure allow_override works
+ try:
+ flags.DEFINE_boolean("dup1", 0, "runhelp d11", short_name='u',
+ allow_override=0)
+ flag = FLAGS.FlagDict()['dup1']
+ self.assertEqual(flag.default, 0)
+
+ flags.DEFINE_boolean("dup1", 1, "runhelp d12", short_name='u',
+ allow_override=1)
+ flag = FLAGS.FlagDict()['dup1']
+ self.assertEqual(flag.default, 1)
+ except flags.DuplicateFlag, e:
+ raise AssertionError("allow_override did not permit a flag duplication")
+
+ # Make sure allow_override works
+ try:
+ flags.DEFINE_boolean("dup2", 0, "runhelp d21", short_name='u',
+ allow_override=1)
+ flag = FLAGS.FlagDict()['dup2']
+ self.assertEqual(flag.default, 0)
+
+ flags.DEFINE_boolean("dup2", 1, "runhelp d22", short_name='u',
+ allow_override=0)
+ flag = FLAGS.FlagDict()['dup2']
+ self.assertEqual(flag.default, 1)
+ except flags.DuplicateFlag, e:
+ raise AssertionError("allow_override did not permit a flag duplication")
+
+ # Make sure allow_override doesn't work with None default
+ try:
+ flags.DEFINE_boolean("dup3", 0, "runhelp d31", short_name='u',
+ allow_override=0)
+ flag = FLAGS.FlagDict()['dup3']
+ self.assertEqual(flag.default, 0)
+
+ flags.DEFINE_boolean("dup3", None, "runhelp d32", short_name='u',
+ allow_override=1)
+ raise AssertionError('Cannot override a flag with a default of None')
+ except flags.DuplicateFlag, e:
+ pass
+
+ # Make sure that when we override, the help string gets updated correctly
+ flags.DEFINE_boolean("dup3", 0, "runhelp d31", short_name='u',
+ allow_override=1)
+ flags.DEFINE_boolean("dup3", 1, "runhelp d32", short_name='u',
+ allow_override=1)
+ self.assert_(str(FLAGS).find('runhelp d31') == -1)
+ self.assert_(str(FLAGS).find('runhelp d32') != -1)
+
+ # Make sure AppendFlagValues works
+ new_flags = flags.FlagValues()
+ flags.DEFINE_boolean("new1", 0, "runhelp n1", flag_values=new_flags)
+ flags.DEFINE_boolean("new2", 0, "runhelp n2", flag_values=new_flags)
+ self.assertEqual(len(new_flags.FlagDict()), 2)
+ old_len = len(FLAGS.FlagDict())
+ FLAGS.AppendFlagValues(new_flags)
+ self.assertEqual(len(FLAGS.FlagDict())-old_len, 2)
+ self.assertEqual("new1" in FLAGS.FlagDict(), True)
+ self.assertEqual("new2" in FLAGS.FlagDict(), True)
+
+ # Make sure AppendFlagValues works with flags with shortnames.
+ new_flags = flags.FlagValues()
+ flags.DEFINE_boolean("new3", 0, "runhelp n3", flag_values=new_flags)
+ flags.DEFINE_boolean("new4", 0, "runhelp n4", flag_values=new_flags,
+ short_name="n4")
+ self.assertEqual(len(new_flags.FlagDict()), 3)
+ old_len = len(FLAGS.FlagDict())
+ FLAGS.AppendFlagValues(new_flags)
+ self.assertEqual(len(FLAGS.FlagDict())-old_len, 3)
+ self.assertTrue("new3" in FLAGS.FlagDict())
+ self.assertTrue("new4" in FLAGS.FlagDict())
+ self.assertTrue("n4" in FLAGS.FlagDict())
+ self.assertEqual(FLAGS.FlagDict()['n4'], FLAGS.FlagDict()['new4'])
+
+ # Make sure AppendFlagValues fails on duplicates
+ flags.DEFINE_boolean("dup4", 0, "runhelp d41")
+ new_flags = flags.FlagValues()
+ flags.DEFINE_boolean("dup4", 0, "runhelp d42", flag_values=new_flags)
+ try:
+ FLAGS.AppendFlagValues(new_flags)
+ raise AssertionError("ignore_copy was not set but caused no exception")
+ except flags.DuplicateFlag, e:
+ pass
+
+ # Integer out of bounds
+ try:
+ argv = ('./program', '--repeat=-4')
+ FLAGS(argv)
+ raise AssertionError('integer bounds exception not raised:'
+ + str(FLAGS.repeat))
+ except flags.IllegalFlagValue:
+ pass
+
+ # Non-integer
+ try:
+ argv = ('./program', '--repeat=2.5')
+ FLAGS(argv)
+ raise AssertionError("malformed integer value exception not raised")
+ except flags.IllegalFlagValue:
+ pass
+
+ # Missing required arugment
+ try:
+ argv = ('./program', '--name')
+ FLAGS(argv)
+ raise AssertionError("Flag argument required exception not raised")
+ except flags.FlagsError:
+ pass
+
+ # Non-boolean arguments for boolean
+ try:
+ argv = ('./program', '--debug=goofup')
+ FLAGS(argv)
+ raise AssertionError("Illegal flag value exception not raised")
+ except flags.IllegalFlagValue:
+ pass
+
+ try:
+ argv = ('./program', '--debug=42')
+ FLAGS(argv)
+ raise AssertionError("Illegal flag value exception not raised")
+ except flags.IllegalFlagValue:
+ pass
+
+
+ # Non-numeric argument for integer flag --repeat
+ try:
+ argv = ('./program', '--repeat', 'Bob', 'extra')
+ FLAGS(argv)
+ raise AssertionError("Illegal flag value exception not raised")
+ except flags.IllegalFlagValue:
+ pass
+
+ ################################################
+ # Code to test the flagfile=<> loading behavior
+ ################################################
+ def _SetupTestFiles(self):
+ """ Creates and sets up some dummy flagfile files with bogus flags"""
+
+ # Figure out where to create temporary files
+ tmp_path = '/tmp/flags_unittest'
+ if os.path.exists(tmp_path):
+ shutil.rmtree(tmp_path)
+ os.makedirs(tmp_path)
+
+ try:
+ tmp_flag_file_1 = open((tmp_path + '/UnitTestFile1.tst'), 'w')
+ tmp_flag_file_2 = open((tmp_path + '/UnitTestFile2.tst'), 'w')
+ tmp_flag_file_3 = open((tmp_path + '/UnitTestFile3.tst'), 'w')
+ except IOError, e_msg:
+ print e_msg
+ print 'FAIL\n File Creation problem in Unit Test'
+ sys.exit(1)
+
+ # put some dummy flags in our test files
+ tmp_flag_file_1.write('#A Fake Comment\n')
+ tmp_flag_file_1.write('--UnitTestMessage1=tempFile1!\n')
+ tmp_flag_file_1.write('\n')
+ tmp_flag_file_1.write('--UnitTestNumber=54321\n')
+ tmp_flag_file_1.write('--noUnitTestBoolFlag\n')
+ file_list = [tmp_flag_file_1.name]
+ # this one includes test file 1
+ tmp_flag_file_2.write('//A Different Fake Comment\n')
+ tmp_flag_file_2.write('--flagfile=%s\n' % tmp_flag_file_1.name)
+ tmp_flag_file_2.write('--UnitTestMessage2=setFromTempFile2\n')
+ tmp_flag_file_2.write('\t\t\n')
+ tmp_flag_file_2.write('--UnitTestNumber=6789a\n')
+ file_list.append(tmp_flag_file_2.name)
+ # this file points to itself
+ tmp_flag_file_3.write('--flagfile=%s\n' % tmp_flag_file_3.name)
+ tmp_flag_file_3.write('--UnitTestMessage1=setFromTempFile3\n')
+ tmp_flag_file_3.write('#YAFC\n')
+ tmp_flag_file_3.write('--UnitTestBoolFlag\n')
+ file_list.append(tmp_flag_file_3.name)
+
+ tmp_flag_file_1.close()
+ tmp_flag_file_2.close()
+ tmp_flag_file_3.close()
+
+ return file_list # these are just the file names
+ # end SetupFiles def
+
+ def _RemoveTestFiles(self, tmp_file_list):
+ """Closes the files we just created. tempfile deletes them for us """
+ for file_name in tmp_file_list:
+ try:
+ os.remove(file_name)
+ except OSError, e_msg:
+ print '%s\n, Problem deleting test file' % e_msg
+ #end RemoveTestFiles def
+
+ def __DeclareSomeFlags(self):
+ flags.DEFINE_string('UnitTestMessage1', 'Foo!', 'You Add Here.')
+ flags.DEFINE_string('UnitTestMessage2', 'Bar!', 'Hello, Sailor!')
+ flags.DEFINE_boolean('UnitTestBoolFlag', 0, 'Some Boolean thing')
+ flags.DEFINE_integer('UnitTestNumber', 12345, 'Some integer',
+ lower_bound=0)
+ flags.DEFINE_list('UnitTestList', "1,2,3", 'Some list')
+
+ def _UndeclareSomeFlags(self):
+ FLAGS.__delattr__('UnitTestMessage1')
+ FLAGS.__delattr__('UnitTestMessage2')
+ FLAGS.__delattr__('UnitTestBoolFlag')
+ FLAGS.__delattr__('UnitTestNumber')
+ FLAGS.__delattr__('UnitTestList')
+
+ def _ReadFlagsFromFiles(self, argv, force_gnu):
+ return argv[:1] + FLAGS.ReadFlagsFromFiles(argv[1:], force_gnu=force_gnu)
+
+ #### Flagfile Unit Tests ####
+ def testMethod_flagfiles_1(self):
+ """ Test trivial case with no flagfile based options. """
+ self.__DeclareSomeFlags()
+ try:
+ fake_cmd_line = 'fooScript --UnitTestBoolFlag'
+ fake_argv = fake_cmd_line.split(' ')
+ FLAGS(fake_argv)
+ self.assertEqual( FLAGS.UnitTestBoolFlag, 1)
+ self.assertEqual( fake_argv, self._ReadFlagsFromFiles(fake_argv, False))
+ finally:
+ self._UndeclareSomeFlags()
+ # end testMethodOne
+
+ def testMethod_flagfiles_2(self):
+ """Tests parsing one file + arguments off simulated argv"""
+ self.__DeclareSomeFlags()
+ try:
+ tmp_files = self._SetupTestFiles()
+ # specify our temp file on the fake cmd line
+ fake_cmd_line = 'fooScript --q --flagfile=%s' % tmp_files[0]
+ fake_argv = fake_cmd_line.split(' ')
+
+ # We should see the original cmd line with the file's contents spliced in.
+ # Note that these will be in REVERSE order from order encountered in file
+ # This is done so arguements we encounter sooner will have priority.
+ expected_results = ['fooScript',
+ '--UnitTestMessage1=tempFile1!',
+ '--UnitTestNumber=54321',
+ '--noUnitTestBoolFlag',
+ '--q']
+ test_results = self._ReadFlagsFromFiles(fake_argv, False)
+ self.assertEqual(expected_results, test_results)
+ finally:
+ self._RemoveTestFiles(tmp_files)
+ self._UndeclareSomeFlags()
+ # end testTwo def
+
+ def testMethod_flagfiles_3(self):
+ """Tests parsing nested files + arguments of simulated argv"""
+ self.__DeclareSomeFlags()
+ try:
+ tmp_files = self._SetupTestFiles()
+ # specify our temp file on the fake cmd line
+ fake_cmd_line = ('fooScript --UnitTestNumber=77 --flagfile=%s'
+ % tmp_files[1])
+ fake_argv = fake_cmd_line.split(' ')
+
+ expected_results = ['fooScript',
+ '--UnitTestMessage1=tempFile1!',
+ '--UnitTestNumber=54321',
+ '--noUnitTestBoolFlag',
+ '--UnitTestMessage2=setFromTempFile2',
+ '--UnitTestNumber=6789a',
+ '--UnitTestNumber=77']
+ test_results = self._ReadFlagsFromFiles(fake_argv, False)
+ self.assertEqual(expected_results, test_results)
+ finally:
+ self._RemoveTestFiles(tmp_files)
+ self._UndeclareSomeFlags()
+ # end testThree def
+
+ def testMethod_flagfiles_4(self):
+ """Tests parsing self-referential files + arguments of simulated argv.
+ This test should print a warning to stderr of some sort.
+ """
+ self.__DeclareSomeFlags()
+ try:
+ tmp_files = self._SetupTestFiles()
+ # specify our temp file on the fake cmd line
+ fake_cmd_line = ('fooScript --flagfile=%s --noUnitTestBoolFlag'
+ % tmp_files[2])
+ fake_argv = fake_cmd_line.split(' ')
+ expected_results = ['fooScript',
+ '--UnitTestMessage1=setFromTempFile3',
+ '--UnitTestBoolFlag',
+ '--noUnitTestBoolFlag' ]
+
+ test_results = self._ReadFlagsFromFiles(fake_argv, False)
+ self.assertEqual(expected_results, test_results)
+ finally:
+ self._RemoveTestFiles(tmp_files)
+ self._UndeclareSomeFlags()
+
+ def testMethod_flagfiles_5(self):
+ """Test that --flagfile parsing respects the '--' end-of-options marker."""
+ self.__DeclareSomeFlags()
+ try:
+ tmp_files = self._SetupTestFiles()
+ # specify our temp file on the fake cmd line
+ fake_cmd_line = 'fooScript --SomeFlag -- --flagfile=%s' % tmp_files[0]
+ fake_argv = fake_cmd_line.split(' ')
+ expected_results = ['fooScript',
+ '--SomeFlag',
+ '--',
+ '--flagfile=%s' % tmp_files[0]]
+
+ test_results = self._ReadFlagsFromFiles(fake_argv, False)
+ self.assertEqual(expected_results, test_results)
+ finally:
+ self._RemoveTestFiles(tmp_files)
+ self._UndeclareSomeFlags()
+
+ def testMethod_flagfiles_6(self):
+ """Test that --flagfile parsing stops at non-options (non-GNU behavior)."""
+ self.__DeclareSomeFlags()
+ try:
+ tmp_files = self._SetupTestFiles()
+ # specify our temp file on the fake cmd line
+ fake_cmd_line = ('fooScript --SomeFlag some_arg --flagfile=%s'
+ % tmp_files[0])
+ fake_argv = fake_cmd_line.split(' ')
+ expected_results = ['fooScript',
+ '--SomeFlag',
+ 'some_arg',
+ '--flagfile=%s' % tmp_files[0]]
+
+ test_results = self._ReadFlagsFromFiles(fake_argv, False)
+ self.assertEqual(expected_results, test_results)
+ finally:
+ self._RemoveTestFiles(tmp_files)
+ self._UndeclareSomeFlags()
+
+ def testMethod_flagfiles_7(self):
+ """Test that --flagfile parsing skips over a non-option (GNU behavior)."""
+ self.__DeclareSomeFlags()
+ try:
+ FLAGS.UseGnuGetOpt()
+ tmp_files = self._SetupTestFiles()
+ # specify our temp file on the fake cmd line
+ fake_cmd_line = ('fooScript --SomeFlag some_arg --flagfile=%s'
+ % tmp_files[0])
+ fake_argv = fake_cmd_line.split(' ')
+ expected_results = ['fooScript',
+ '--UnitTestMessage1=tempFile1!',
+ '--UnitTestNumber=54321',
+ '--noUnitTestBoolFlag',
+ '--SomeFlag',
+ 'some_arg']
+
+ test_results = self._ReadFlagsFromFiles(fake_argv, False)
+ self.assertEqual(expected_results, test_results)
+ finally:
+ self._RemoveTestFiles(tmp_files)
+ self._UndeclareSomeFlags()
+
+ def testMethod_flagfiles_8(self):
+ """Test that --flagfile parsing respects force_gnu=True."""
+ self.__DeclareSomeFlags()
+ try:
+ tmp_files = self._SetupTestFiles()
+ # specify our temp file on the fake cmd line
+ fake_cmd_line = ('fooScript --SomeFlag some_arg --flagfile=%s'
+ % tmp_files[0])
+ fake_argv = fake_cmd_line.split(' ')
+ expected_results = ['fooScript',
+ '--UnitTestMessage1=tempFile1!',
+ '--UnitTestNumber=54321',
+ '--noUnitTestBoolFlag',
+ '--SomeFlag',
+ 'some_arg']
+
+ test_results = self._ReadFlagsFromFiles(fake_argv, True)
+ self.assertEqual(expected_results, test_results)
+ finally:
+ self._RemoveTestFiles(tmp_files)
+ self._UndeclareSomeFlags()
+
+ def test_flagfiles_user_path_expansion(self):
+ """Test that user directory referenced paths (ie. ~/foo) are correctly
+ expanded. This test depends on whatever account's running the unit test
+ to have read/write access to their own home directory, otherwise it'll
+ FAIL.
+ """
+ self.__DeclareSomeFlags()
+ fake_flagfile_item_style_1 = '--flagfile=~/foo.file'
+ fake_flagfile_item_style_2 = '-flagfile=~/foo.file'
+
+ expected_results = os.path.expanduser('~/foo.file')
+
+ test_results = FLAGS.ExtractFilename(fake_flagfile_item_style_1)
+ self.assertEqual(expected_results, test_results)
+
+ test_results = FLAGS.ExtractFilename(fake_flagfile_item_style_2)
+ self.assertEqual(expected_results, test_results)
+
+ self._UndeclareSomeFlags()
+
+ # end testFour def
+
+ def test_no_touchy_non_flags(self):
+ """
+ Test that the flags parser does not mutilate arguments which are
+ not supposed to be flags
+ """
+ self.__DeclareSomeFlags()
+ fake_argv = ['fooScript', '--UnitTestBoolFlag',
+ 'command', '--command_arg1', '--UnitTestBoom', '--UnitTestB']
+ argv = FLAGS(fake_argv)
+ self.assertEqual(argv, fake_argv[:1] + fake_argv[2:])
+ self._UndeclareSomeFlags()
+
+ def test_parse_flags_after_args_if_using_gnu_getopt(self):
+ """
+ Test that flags given after arguments are parsed if using gnu_getopt.
+ """
+ self.__DeclareSomeFlags()
+ FLAGS.UseGnuGetOpt()
+ fake_argv = ['fooScript', '--UnitTestBoolFlag',
+ 'command', '--UnitTestB']
+ argv = FLAGS(fake_argv)
+ self.assertEqual(argv, ['fooScript', 'command'])
+ self._UndeclareSomeFlags()
+
+ def test_SetDefault(self):
+ """
+ Test changing flag defaults.
+ """
+ self.__DeclareSomeFlags()
+ # Test that SetDefault changes both the default and the value,
+ # and that the value is changed when one is given as an option.
+ FLAGS['UnitTestMessage1'].SetDefault('New value')
+ self.assertEqual(FLAGS.UnitTestMessage1, 'New value')
+ self.assertEqual(FLAGS['UnitTestMessage1'].default_as_str,"'New value'")
+ FLAGS([ 'dummyscript', '--UnitTestMessage1=Newer value' ])
+ self.assertEqual(FLAGS.UnitTestMessage1, 'Newer value')
+
+ # Test that setting the default to None works correctly.
+ FLAGS['UnitTestNumber'].SetDefault(None)
+ self.assertEqual(FLAGS.UnitTestNumber, None)
+ self.assertEqual(FLAGS['UnitTestNumber'].default_as_str, None)
+ FLAGS([ 'dummyscript', '--UnitTestNumber=56' ])
+ self.assertEqual(FLAGS.UnitTestNumber, 56)
+
+ # Test that setting the default to zero works correctly.
+ FLAGS['UnitTestNumber'].SetDefault(0)
+ self.assertEqual(FLAGS.UnitTestNumber, 0)
+ self.assertEqual(FLAGS['UnitTestNumber'].default_as_str, "'0'")
+ FLAGS([ 'dummyscript', '--UnitTestNumber=56' ])
+ self.assertEqual(FLAGS.UnitTestNumber, 56)
+
+ # Test that setting the default to "" works correctly.
+ FLAGS['UnitTestMessage1'].SetDefault("")
+ self.assertEqual(FLAGS.UnitTestMessage1, "")
+ self.assertEqual(FLAGS['UnitTestMessage1'].default_as_str, "''")
+ FLAGS([ 'dummyscript', '--UnitTestMessage1=fifty-six' ])
+ self.assertEqual(FLAGS.UnitTestMessage1, "fifty-six")
+
+ # Test that setting the default to false works correctly.
+ FLAGS['UnitTestBoolFlag'].SetDefault(False)
+ self.assertEqual(FLAGS.UnitTestBoolFlag, False)
+ self.assertEqual(FLAGS['UnitTestBoolFlag'].default_as_str, "'false'")
+ FLAGS([ 'dummyscript', '--UnitTestBoolFlag=true' ])
+ self.assertEqual(FLAGS.UnitTestBoolFlag, True)
+
+ # Test that setting a list default works correctly.
+ FLAGS['UnitTestList'].SetDefault('4,5,6')
+ self.assertEqual(FLAGS.UnitTestList, ['4', '5', '6'])
+ self.assertEqual(FLAGS['UnitTestList'].default_as_str, "'4,5,6'")
+ FLAGS([ 'dummyscript', '--UnitTestList=7,8,9' ])
+ self.assertEqual(FLAGS.UnitTestList, ['7', '8', '9'])
+
+ # Test that setting invalid defaults raises exceptions
+ self.assertRaises(flags.IllegalFlagValue,
+ FLAGS['UnitTestNumber'].SetDefault, 'oops')
+ self.assertRaises(flags.IllegalFlagValue,
+ FLAGS['UnitTestNumber'].SetDefault, -1)
+ self.assertRaises(flags.IllegalFlagValue,
+ FLAGS['UnitTestBoolFlag'].SetDefault, 'oops')
+
+ self._UndeclareSomeFlags()
+
+ def testMethod_ShortestUniquePrefixes(self):
+ """
+ Test FlagValues.ShortestUniquePrefixes
+ """
+ flags.DEFINE_string('a', '', '')
+ flags.DEFINE_string('abc', '', '')
+ flags.DEFINE_string('common_a_string', '', '')
+ flags.DEFINE_boolean('common_b_boolean', 0, '')
+ flags.DEFINE_boolean('common_c_boolean', 0, '')
+ flags.DEFINE_boolean('common', 0, '')
+ flags.DEFINE_integer('commonly', 0, '')
+ flags.DEFINE_boolean('zz', 0, '')
+ flags.DEFINE_integer('nozz', 0, '')
+
+ shorter_flags = FLAGS.ShortestUniquePrefixes(FLAGS.FlagDict())
+
+ expected_results = {'nocommon_b_boolean': 'nocommon_b',
+ 'common_c_boolean': 'common_c',
+ 'common_b_boolean': 'common_b',
+ 'a': 'a',
+ 'abc': 'ab',
+ 'zz': 'z',
+ 'nozz': 'nozz',
+ 'common_a_string': 'common_a',
+ 'commonly': 'commonl',
+ 'nocommon_c_boolean': 'nocommon_c',
+ 'nocommon': 'nocommon',
+ 'common': 'common'}
+
+ for name, shorter in expected_results.iteritems():
+ self.assertEquals(shorter_flags[name], shorter)
+
+ FLAGS.__delattr__('a')
+ FLAGS.__delattr__('abc')
+ FLAGS.__delattr__('common_a_string')
+ FLAGS.__delattr__('common_b_boolean')
+ FLAGS.__delattr__('common_c_boolean')
+ FLAGS.__delattr__('common')
+ FLAGS.__delattr__('commonly')
+ FLAGS.__delattr__('zz')
+ FLAGS.__delattr__('nozz')
+
+ def test_twodasharg_first(self):
+ flags.DEFINE_string("twodash_name", "Bob", "namehelp")
+ flags.DEFINE_string("twodash_blame", "Rob", "blamehelp")
+ argv = ('./program',
+ '--',
+ '--twodash_name=Harry')
+ argv = FLAGS(argv)
+ self.assertEqual('Bob', FLAGS.twodash_name)
+ self.assertEqual(argv[1], '--twodash_name=Harry')
+
+ def test_twodasharg_middle(self):
+ flags.DEFINE_string("twodash2_name", "Bob", "namehelp")
+ flags.DEFINE_string("twodash2_blame", "Rob", "blamehelp")
+ argv = ('./program',
+ '--twodash2_blame=Larry',
+ '--',
+ '--twodash2_name=Harry')
+ argv = FLAGS(argv)
+ self.assertEqual('Bob', FLAGS.twodash2_name)
+ self.assertEqual('Larry', FLAGS.twodash2_blame)
+ self.assertEqual(argv[1], '--twodash2_name=Harry')
+
+ def test_onedasharg_first(self):
+ flags.DEFINE_string("onedash_name", "Bob", "namehelp")
+ flags.DEFINE_string("onedash_blame", "Rob", "blamehelp")
+ argv = ('./program',
+ '-',
+ '--onedash_name=Harry')
+ argv = FLAGS(argv)
+ self.assertEqual(argv[1], '-')
+ # TODO(csilvers): we should still parse --onedash_name=Harry as a
+ # flag, but currently we don't (we stop flag processing as soon as
+ # we see the first non-flag).
+ # - This requires gnu_getopt from Python 2.3+ see FLAGS.UseGnuGetOpt()
+
+ def test_unrecognized_flags(self):
+ # Unknown flag --nosuchflag
+ try:
+ argv = ('./program', '--nosuchflag', '--name=Bob', 'extra')
+ FLAGS(argv)
+ raise AssertionError("Unknown flag exception not raised")
+ except flags.UnrecognizedFlag, e:
+ assert e.flagname == 'nosuchflag'
+
+ # Unknown flag -w (short option)
+ try:
+ argv = ('./program', '-w', '--name=Bob', 'extra')
+ FLAGS(argv)
+ raise AssertionError("Unknown flag exception not raised")
+ except flags.UnrecognizedFlag, e:
+ assert e.flagname == 'w'
+
+ # Unknown flag --nosuchflagwithparam=foo
+ try:
+ argv = ('./program', '--nosuchflagwithparam=foo', '--name=Bob', 'extra')
+ FLAGS(argv)
+ raise AssertionError("Unknown flag exception not raised")
+ except flags.UnrecognizedFlag, e:
+ assert e.flagname == 'nosuchflagwithparam'
+
+ # Allow unknown flag --nosuchflag if specified with undefok
+ argv = ('./program', '--nosuchflag', '--name=Bob',
+ '--undefok=nosuchflag', 'extra')
+ argv = FLAGS(argv)
+ assert len(argv) == 2, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert argv[1]=='extra', "extra argument not preserved"
+
+ # Allow unknown flag --noboolflag if undefok=boolflag is specified
+ argv = ('./program', '--noboolflag', '--name=Bob',
+ '--undefok=boolflag', 'extra')
+ argv = FLAGS(argv)
+ assert len(argv) == 2, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert argv[1]=='extra', "extra argument not preserved"
+
+ # But not if the flagname is misspelled:
+ try:
+ argv = ('./program', '--nosuchflag', '--name=Bob',
+ '--undefok=nosuchfla', 'extra')
+ FLAGS(argv)
+ raise AssertionError("Unknown flag exception not raised")
+ except flags.UnrecognizedFlag, e:
+ assert e.flagname == 'nosuchflag'
+
+ try:
+ argv = ('./program', '--nosuchflag', '--name=Bob',
+ '--undefok=nosuchflagg', 'extra')
+ FLAGS(argv)
+ raise AssertionError("Unknown flag exception not raised")
+ except flags.UnrecognizedFlag:
+ assert e.flagname == 'nosuchflag'
+
+ # Allow unknown short flag -w if specified with undefok
+ argv = ('./program', '-w', '--name=Bob', '--undefok=w', 'extra')
+ argv = FLAGS(argv)
+ assert len(argv) == 2, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert argv[1]=='extra', "extra argument not preserved"
+
+ # Allow unknown flag --nosuchflagwithparam=foo if specified
+ # with undefok
+ argv = ('./program', '--nosuchflagwithparam=foo', '--name=Bob',
+ '--undefok=nosuchflagwithparam', 'extra')
+ argv = FLAGS(argv)
+ assert len(argv) == 2, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert argv[1]=='extra', "extra argument not preserved"
+
+ # Even if undefok specifies multiple flags
+ argv = ('./program', '--nosuchflag', '-w', '--nosuchflagwithparam=foo',
+ '--name=Bob',
+ '--undefok=nosuchflag,w,nosuchflagwithparam',
+ 'extra')
+ argv = FLAGS(argv)
+ assert len(argv) == 2, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert argv[1]=='extra', "extra argument not preserved"
+
+ # However, not if undefok doesn't specify the flag
+ try:
+ argv = ('./program', '--nosuchflag', '--name=Bob',
+ '--undefok=another_such', 'extra')
+ FLAGS(argv)
+ raise AssertionError("Unknown flag exception not raised")
+ except flags.UnrecognizedFlag, e:
+ assert e.flagname == 'nosuchflag'
+
+ # Make sure --undefok doesn't mask other option errors.
+ try:
+ # Provide an option requiring a parameter but not giving it one.
+ argv = ('./program', '--undefok=name', '--name')
+ FLAGS(argv)
+ raise AssertionError("Missing option parameter exception not raised")
+ except flags.UnrecognizedFlag:
+ raise AssertionError("Wrong kind of error exception raised")
+ except flags.FlagsError:
+ pass
+
+ # Test --undefok <list>
+ argv = ('./program', '--nosuchflag', '-w', '--nosuchflagwithparam=foo',
+ '--name=Bob',
+ '--undefok',
+ 'nosuchflag,w,nosuchflagwithparam',
+ 'extra')
+ argv = FLAGS(argv)
+ assert len(argv) == 2, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert argv[1]=='extra', "extra argument not preserved"
+
+ def test_nonglobal_flags(self):
+ """Test use of non-global FlagValues"""
+ nonglobal_flags = flags.FlagValues()
+ flags.DEFINE_string("nonglobal_flag", "Bob", "flaghelp", nonglobal_flags)
+ argv = ('./program',
+ '--nonglobal_flag=Mary',
+ 'extra')
+ argv = nonglobal_flags(argv)
+ assert len(argv) == 2, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert argv[1]=='extra', "extra argument not preserved"
+ assert nonglobal_flags['nonglobal_flag'].value == 'Mary'
+
+ def test_unrecognized_nonglobal_flags(self):
+ """Test unrecognized non-global flags"""
+ nonglobal_flags = flags.FlagValues()
+ argv = ('./program',
+ '--nosuchflag')
+ try:
+ argv = nonglobal_flags(argv)
+ raise AssertionError("Unknown flag exception not raised")
+ except flags.UnrecognizedFlag, e:
+ assert e.flagname == 'nosuchflag'
+ pass
+
+ argv = ('./program',
+ '--nosuchflag',
+ '--undefok=nosuchflag')
+
+ argv = nonglobal_flags(argv)
+ assert len(argv) == 1, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+
+ def test_module_help(self):
+ """Test ModuleHelp()."""
+ helpstr = FLAGS.ModuleHelp(module_baz)
+
+ expected_help = "\n" + module_baz.__name__ + ":" + """
+ --[no]tmod_baz_x: Boolean flag.
+ (default: 'true')"""
+
+ self.assertMultiLineEqual(expected_help, helpstr)
+
+ def test_main_module_help(self):
+ """Test MainModuleHelp()."""
+ helpstr = FLAGS.MainModuleHelp()
+
+ # When this test is invoked on behalf of flags_unittest_2_2,
+ # the main module has not defined any flags. Since there's
+ # no easy way to run this script in our test environment
+ # directly from python2.2, don't bother to test the output
+ # of MainModuleHelp() in that scenario.
+ if sys.version.startswith('2.2.'):
+ return
+
+ expected_help = "\n" + sys.argv[0] + ':' + """
+ --[no]debug: debughelp
+ (default: 'false')
+ -u,--[no]dup1: runhelp d12
+ (default: 'true')
+ -u,--[no]dup2: runhelp d22
+ (default: 'true')
+ -u,--[no]dup3: runhelp d32
+ (default: 'true')
+ --[no]dup4: runhelp d41
+ (default: 'false')
+ -?,--[no]help: show this help
+ --[no]helpshort: show usage only for this module
+ --[no]helpxml: like --help, but generates XML output
+ --kwery: <who|what|why|where|when>: ?
+ --l: how long to be
+ (default: '9223372032559808512')
+ (an integer)
+ --letters: a list of letters
+ (default: 'a,b,c')
+ (a comma separated list)
+ -m,--m_str: string option that can occur multiple times;
+ repeat this option to specify a list of values
+ (default: "['def1', 'def2']")
+ --name: namehelp
+ (default: 'Bob')
+ --[no]noexec: boolean flag with no as prefix
+ (default: 'true')
+ --numbers: a list of numbers
+ (default: '1,2,3')
+ (a comma separated list)
+ --[no]q: quiet mode
+ (default: 'true')
+ --[no]quack: superstring of 'q'
+ (default: 'false')
+ -r,--repeat: how many times to repeat (0-5)
+ (default: '4')
+ (a non-negative integer)
+ -s,--s_str: string option that can occur multiple times;
+ repeat this option to specify a list of values
+ (default: "['sing1']")
+ --[no]test0: test boolean parsing
+ --[no]test1: test boolean parsing
+ --[no]testget1: test parsing with defaults
+ --[no]testget2: test parsing with defaults
+ --[no]testget3: test parsing with defaults
+ --testget4: test parsing with defaults
+ (an integer)
+ --testlist: test lists parsing
+ (default: '')
+ (a comma separated list)
+ --[no]testnone: test boolean parsing
+ --testspacelist: tests space lists parsing
+ (default: '')
+ (a whitespace separated list)
+ --x: how eXtreme to be
+ (default: '3')
+ (an integer)
+ -z,--[no]zoom1: runhelp z1
+ (default: 'false')"""
+
+ if not MultiLineEqual(expected_help, helpstr):
+ self.fail()
+
+ def test_create_flag_errors(self):
+ # Since the exception classes are exposed, nothing stops users
+ # from creating their own instances. This test makes sure that
+ # people modifying the flags module understand that the external
+ # mechanisms for creating the exceptions should continue to work.
+ e = flags.FlagsError()
+ e = flags.FlagsError("message")
+ e = flags.DuplicateFlag()
+ e = flags.DuplicateFlag("message")
+ e = flags.IllegalFlagValue()
+ e = flags.IllegalFlagValue("message")
+ e = flags.UnrecognizedFlag()
+ e = flags.UnrecognizedFlag("message")
+
+ def testFlagValuesDelAttr(self):
+ """Checks that del FLAGS.flag_id works."""
+ default_value = 'default value for testFlagValuesDelAttr'
+ # 1. Declare and delete a flag with no short name.
+ flags.DEFINE_string('delattr_foo', default_value, 'A simple flag.')
+ self.assertEquals(FLAGS.delattr_foo, default_value)
+ flag_obj = FLAGS['delattr_foo']
+ # We also check that _FlagIsRegistered works as expected :)
+ self.assertTrue(FLAGS._FlagIsRegistered(flag_obj))
+ del FLAGS.delattr_foo
+ self.assertFalse('delattr_foo' in FLAGS.FlagDict())
+ self.assertFalse(FLAGS._FlagIsRegistered(flag_obj))
+ # If the previous del FLAGS.delattr_foo did not work properly, the
+ # next definition will trigger a redefinition error.
+ flags.DEFINE_integer('delattr_foo', 3, 'A simple flag.')
+ del FLAGS.delattr_foo
+
+ self.assertFalse('delattr_foo' in FLAGS.RegisteredFlags())
+
+ # 2. Declare and delete a flag with a short name.
+ flags.DEFINE_string('delattr_bar', default_value, 'flag with short name',
+ short_name='x5')
+ flag_obj = FLAGS['delattr_bar']
+ self.assertTrue(FLAGS._FlagIsRegistered(flag_obj))
+ del FLAGS.x5
+ self.assertTrue(FLAGS._FlagIsRegistered(flag_obj))
+ del FLAGS.delattr_bar
+ self.assertFalse(FLAGS._FlagIsRegistered(flag_obj))
+
+ # 3. Just like 2, but del FLAGS.name last
+ flags.DEFINE_string('delattr_bar', default_value, 'flag with short name',
+ short_name='x5')
+ flag_obj = FLAGS['delattr_bar']
+ self.assertTrue(FLAGS._FlagIsRegistered(flag_obj))
+ del FLAGS.delattr_bar
+ self.assertTrue(FLAGS._FlagIsRegistered(flag_obj))
+ del FLAGS.x5
+ self.assertFalse(FLAGS._FlagIsRegistered(flag_obj))
+
+ self.assertFalse('delattr_bar' in FLAGS.RegisteredFlags())
+ self.assertFalse('x5' in FLAGS.RegisteredFlags())
+
+ def _GetNamesOfDefinedFlags(self, module, flag_values=FLAGS):
+ """Returns the list of names of flags defined by a module.
+
+ Auxiliary for the testKeyFlags* methods.
+
+ Args:
+ module: A module object or a string module name.
+ flag_values: A FlagValues object.
+
+ Returns:
+ A list of strings.
+ """
+ return [f.name for f in flag_values._GetFlagsDefinedByModule(module)]
+
+ def _GetNamesOfKeyFlags(self, module, flag_values=FLAGS):
+ """Returns the list of names of key flags for a module.
+
+ Auxiliary for the testKeyFlags* methods.
+
+ Args:
+ module: A module object or a string module name.
+ flag_values: A FlagValues object.
+
+ Returns:
+ A list of strings.
+ """
+ return [f.name for f in flag_values._GetKeyFlagsForModule(module)]
+
+ def testKeyFlags(self):
+ # Before starting any testing, make sure no flags are already
+ # defined for module_foo and module_bar.
+ self.assertListEqual(self._GetNamesOfKeyFlags(module_foo), [])
+ self.assertListEqual(self._GetNamesOfKeyFlags(module_bar), [])
+ self.assertListEqual(self._GetNamesOfDefinedFlags(module_foo), [])
+ self.assertListEqual(self._GetNamesOfDefinedFlags(module_bar), [])
+
+ try:
+ # Defines a few flags in module_foo and module_bar.
+ module_foo.DefineFlags()
+
+ # Part 1. Check that all flags defined by module_foo are key for
+ # that module, and similarly for module_bar.
+ for module in [module_foo, module_bar]:
+ self.assertListEqual(FLAGS._GetFlagsDefinedByModule(module),
+ FLAGS._GetKeyFlagsForModule(module))
+ # Also check that each module defined the expected flags.
+ self.assertListEqual(self._GetNamesOfDefinedFlags(module),
+ module.NamesOfDefinedFlags())
+
+ # Part 2. Check that flags.DECLARE_key_flag works fine.
+ # Declare that some flags from module_bar are key for
+ # module_foo.
+ module_foo.DeclareKeyFlags()
+
+ # Check that module_foo has the expected list of defined flags.
+ self.assertListEqual(self._GetNamesOfDefinedFlags(module_foo),
+ module_foo.NamesOfDefinedFlags())
+
+ # Check that module_foo has the expected list of key flags.
+ self.assertListEqual(self._GetNamesOfKeyFlags(module_foo),
+ module_foo.NamesOfDeclaredKeyFlags())
+
+ # Part 3. Check that flags.ADOPT_module_key_flags works fine.
+ # Trigger a call to flags.ADOPT_module_key_flags(module_bar)
+ # inside module_foo. This should declare a few more key
+ # flags in module_foo.
+ module_foo.DeclareExtraKeyFlags()
+
+ # Check that module_foo has the expected list of key flags.
+ self.assertListEqual(self._GetNamesOfKeyFlags(module_foo),
+ module_foo.NamesOfDeclaredKeyFlags() +
+ module_foo.NamesOfDeclaredExtraKeyFlags())
+ finally:
+ module_foo.RemoveFlags()
+
+ def testKeyFlagsWithNonDefaultFlagValuesObject(self):
+ # Check that key flags work even when we use a FlagValues object
+ # that is not the default flags.FLAGS object. Otherwise, this
+ # test is similar to testKeyFlags, but it uses only module_bar.
+ # The other test module (module_foo) uses only the default values
+ # for the flag_values keyword arguments. This way, testKeyFlags
+ # and this method test both the default FlagValues, the explicitly
+ # specified one, and a mixed usage of the two.
+
+ # A brand-new FlagValues object, to use instead of flags.FLAGS.
+ fv = flags.FlagValues()
+
+ # Before starting any testing, make sure no flags are already
+ # defined for module_foo and module_bar.
+ self.assertListEqual(
+ self._GetNamesOfKeyFlags(module_bar, flag_values=fv),
+ [])
+ self.assertListEqual(
+ self._GetNamesOfDefinedFlags(module_bar, flag_values=fv),
+ [])
+
+ module_bar.DefineFlags(flag_values=fv)
+
+ # Check that all flags defined by module_bar are key for that
+ # module, and that module_bar defined the expected flags.
+ self.assertListEqual(fv._GetFlagsDefinedByModule(module_bar),
+ fv._GetKeyFlagsForModule(module_bar))
+ self.assertListEqual(
+ self._GetNamesOfDefinedFlags(module_bar, flag_values=fv),
+ module_bar.NamesOfDefinedFlags())
+
+ # Pick two flags from module_bar, declare them as key for the
+ # current (i.e., main) module (via flags.DECLARE_key_flag), and
+ # check that we get the expected effect. The important thing is
+ # that we always use flags_values=fv (instead of the default
+ # FLAGS).
+ main_module = flags._GetMainModule()
+ names_of_flags_defined_by_bar = module_bar.NamesOfDefinedFlags()
+ flag_name_0 = names_of_flags_defined_by_bar[0]
+ flag_name_2 = names_of_flags_defined_by_bar[2]
+
+ flags.DECLARE_key_flag(flag_name_0, flag_values=fv)
+ self.assertListEqual(
+ self._GetNamesOfKeyFlags(main_module, flag_values=fv),
+ [flag_name_0])
+
+ flags.DECLARE_key_flag(flag_name_2, flag_values=fv)
+ self.assertListEqual(
+ self._GetNamesOfKeyFlags(main_module, flag_values=fv),
+ [flag_name_0, flag_name_2])
+
+ flags.ADOPT_module_key_flags(module_bar, flag_values=fv)
+ key_flags = self._GetNamesOfKeyFlags(main_module, flag_values=fv)
+ # Order is irrelevant; hence, we sort both lists before comparison.
+ key_flags.sort()
+ names_of_flags_defined_by_bar.sort()
+ self.assertListEqual(key_flags, names_of_flags_defined_by_bar)
+
+ def testMainModuleHelpWithKeyFlags(self):
+ # Similar to test_main_module_help, but this time we make sure to
+ # declare some key flags.
+ try:
+ help_flag_help = (
+ " -?,--[no]help: show this help\n"
+ " --[no]helpshort: show usage only for this module\n"
+ " --[no]helpxml: like --help, but generates XML output"
+ )
+
+ expected_help = "\n%s:\n%s" % (sys.argv[0], help_flag_help)
+
+ # Safety check that the main module does not declare any flags
+ # at the beginning of this test.
+ self.assertMultiLineEqual(expected_help, FLAGS.MainModuleHelp())
+
+ # Define one flag in this main module and some flags in modules
+ # a and b. Also declare one flag from module a and one flag
+ # from module b as key flags for the main module.
+ flags.DEFINE_integer('main_module_int_fg', 1,
+ 'Integer flag in the main module.')
+
+ main_module_int_fg_help = (
+ " --main_module_int_fg: Integer flag in the main module.\n"
+ " (default: '1')\n"
+ " (an integer)")
+
+ expected_help += "\n" + main_module_int_fg_help
+ self.assertMultiLineEqual(expected_help, FLAGS.MainModuleHelp())
+
+ # The following call should be a no-op: any flag declared by a
+ # module is automatically key for that module.
+ flags.DECLARE_key_flag('main_module_int_fg')
+ self.assertMultiLineEqual(expected_help, FLAGS.MainModuleHelp())
+
+ # The definition of a few flags in an imported module should not
+ # change the main module help.
+ module_foo.DefineFlags()
+ self.assertMultiLineEqual(expected_help, FLAGS.MainModuleHelp())
+
+ flags.DECLARE_key_flag('tmod_foo_bool')
+ tmod_foo_bool_help = (
+ " --[no]tmod_foo_bool: Boolean flag from module foo.\n"
+ " (default: 'true')")
+ expected_help += "\n" + tmod_foo_bool_help
+ self.assertMultiLineEqual(expected_help, FLAGS.MainModuleHelp())
+
+ flags.DECLARE_key_flag('tmod_bar_z')
+ tmod_bar_z_help = (
+ " --[no]tmod_bar_z: Another boolean flag from module bar.\n"
+ " (default: 'false')")
+ # Unfortunately, there is some flag sorting inside
+ # MainModuleHelp, so we can't keep incrementally extending
+ # the expected_help string ...
+ expected_help = ("\n%s:\n%s\n%s\n%s\n%s" %
+ (sys.argv[0],
+ help_flag_help,
+ main_module_int_fg_help,
+ tmod_bar_z_help,
+ tmod_foo_bool_help))
+ self.assertMultiLineEqual(FLAGS.MainModuleHelp(), expected_help)
+
+ finally:
+ # At the end, delete all the flag information we created.
+ FLAGS.__delattr__('main_module_int_fg')
+ module_foo.RemoveFlags()
+
+ def test_ADOPT_module_key_flags(self):
+ # Check that ADOPT_module_key_flags raises an exception when
+ # called with a module name (as opposed to a module object).
+ self.assertRaises(flags.FlagsError,
+ flags.ADOPT_module_key_flags,
+ 'google3.pyglib.app')
+
+ def test_GetCallingModule(self):
+ self.assertEqual(flags._GetCallingModule(), sys.argv[0])
+ self.assertEqual(
+ module_foo.GetModuleName(),
+ 'test_module_foo')
+ self.assertEqual(
+ module_bar.GetModuleName(),
+ 'test_module_bar')
+
+ # We execute the following exec statements for their side-effect
+ # (i.e., not raising an error). They emphasize the case that not
+ # all code resides in one of the imported modules: Python is a
+ # really dynamic language, where we can dynamically construct some
+ # code and execute it.
+ code = ("import gflags\n"
+ "module_name = gflags._GetCallingModule()")
+ exec code
+
+ # Next two exec statements executes code with a global environment
+ # that is different from the global environment of any imported
+ # module.
+ exec code in {}
+ # vars(self) returns a dictionary corresponding to the symbol
+ # table of the self object. dict(...) makes a distinct copy of
+ # this dictionary, such that any new symbol definition by the
+ # exec-ed code (e.g., import flags, module_name = ...) does not
+ # affect the symbol table of self.
+ exec code in dict(vars(self))
+
+ # Next test is actually more involved: it checks not only that
+ # _GetCallingModule does not crash inside exec code, it also checks
+ # that it returns the expected value: the code executed via exec
+ # code is treated as being executed by the current module. We
+ # check it twice: first time by executing exec from the main
+ # module, second time by executing it from module_bar.
+ global_dict = {}
+ exec code in global_dict
+ self.assertEqual(global_dict['module_name'],
+ sys.argv[0])
+
+ global_dict = {}
+ module_bar.ExecuteCode(code, global_dict)
+ self.assertEqual(
+ global_dict['module_name'],
+ 'test_module_bar')
+
+
+def main():
+ unittest.main()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/vendor/python-gflags/setup.py b/vendor/python-gflags/setup.py
new file mode 100755
index 0000000000..26820a6277
--- /dev/null
+++ b/vendor/python-gflags/setup.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2007, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from setuptools import setup
+
+setup(name='python-gflags',
+ version='1.3',
+ description='Google Commandline Flags Module',
+ license='BSD',
+ author='Google Inc.',
+ author_email='opensource@google.com',
+ url='http://code.google.com/p/python-gflags',
+ py_modules=["gflags"],
+ data_files=[("bin", ["gflags2man.py"])],
+ include_package_data=True,
+ )
diff --git a/vendor/python-gflags/test_module_bar.py b/vendor/python-gflags/test_module_bar.py
new file mode 100755
index 0000000000..55541ff762
--- /dev/null
+++ b/vendor/python-gflags/test_module_bar.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Auxiliary module for testing flags.py.
+
+The purpose of this module is to define a few flags. We want to make
+sure the unit tests for flags.py involve more than one module.
+"""
+
+__author__ = 'Alex Salcianu'
+
+__pychecker__ = "no-local" # for unittest
+
+# We use the name 'flags' internally in this test, for historical reasons.
+# Don't do this yourself! :-) Just do 'import gflags; FLAGS=gflags.FLAGS; etc'
+import gflags as flags
+FLAGS = flags.FLAGS
+
+
+def DefineFlags(flag_values=FLAGS):
+ """Defines some flags.
+
+ Args:
+ flag_values: The FlagValues object we want to register the flags
+ with.
+ """
+ # The 'tmod_bar_' prefix (short for 'test_module_bar') ensures there
+ # is no name clash with the existing flags.
+ flags.DEFINE_boolean('tmod_bar_x', True, 'Boolean flag.',
+ flag_values=flag_values)
+ flags.DEFINE_string('tmod_bar_y', 'default', 'String flag.',
+ flag_values=flag_values)
+ flags.DEFINE_boolean('tmod_bar_z', False,
+ 'Another boolean flag from module bar.',
+ flag_values=flag_values)
+ flags.DEFINE_integer('tmod_bar_t', 4, 'Sample int flag.',
+ flag_values=flag_values)
+ flags.DEFINE_integer('tmod_bar_u', 5, 'Sample int flag.',
+ flag_values=flag_values)
+ flags.DEFINE_integer('tmod_bar_v', 6, 'Sample int flag.',
+ flag_values=flag_values)
+
+
+def RemoveOneFlag(flag_name, flag_values=FLAGS):
+ """Removes the definition of one flag from flags.FLAGS.
+
+ Note: if the flag is not defined in flags.FLAGS, this function does
+ not do anything (in particular, it does not raise any exception).
+
+ Motivation: We use this function for cleanup *after* a test: if
+ there was a failure during a test and not all flags were declared,
+ we do not want the cleanup code to crash.
+
+ Args:
+ flag_name: A string, the name of the flag to delete.
+ flag_values: The FlagValues object we remove the flag from.
+ """
+ if flag_name in flag_values.FlagDict():
+ flag_values.__delattr__(flag_name)
+
+
+def NamesOfDefinedFlags():
+ """Returns: List of names of the flags declared in this module."""
+ return ['tmod_bar_x',
+ 'tmod_bar_y',
+ 'tmod_bar_z',
+ 'tmod_bar_t',
+ 'tmod_bar_u',
+ 'tmod_bar_v']
+
+
+def RemoveFlags(flag_values=FLAGS):
+ """Deletes the flag definitions done by the above DefineFlags().
+
+ Args:
+ flag_values: The FlagValues object we remove the flags from.
+ """
+ for flag_name in NamesOfDefinedFlags():
+ RemoveOneFlag(flag_name, flag_values=flag_values)
+
+
+def GetModuleName():
+ """Uses flags._GetCallingModule() to return the name of this module.
+
+ For checking that _GetCallingModule works as expected.
+
+ Returns:
+ A string, the name of this module.
+ """
+ # Calling the protected _GetCallingModule generates a lint warning,
+ # but we do not have any other alternative to test that function.
+ return flags._GetCallingModule()
+
+
+def ExecuteCode(code, global_dict):
+ """Executes some code in a given global environment.
+
+ For testing of _GetCallingModule.
+
+ Args:
+ code: A string, the code to be executed.
+ global_dict: A dictionary, the global environment that code should
+ be executed in.
+ """
+ # Indeed, using exec generates a lint warning. But some user code
+ # actually uses exec, and we have to test for it ...
+ exec code in global_dict
diff --git a/vendor/python-gflags/test_module_foo.py b/vendor/python-gflags/test_module_foo.py
new file mode 100755
index 0000000000..9f2ab49e15
--- /dev/null
+++ b/vendor/python-gflags/test_module_foo.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Auxiliary module for testing flags.py.
+
+The purpose of this module is to define a few flags, and declare some
+other flags as being important. We want to make sure the unit tests
+for flags.py involve more than one module.
+"""
+
+__author__ = 'Alex Salcianu'
+
+__pychecker__ = "no-local" # for unittest
+
+# We use the name 'flags' internally in this test, for historical reasons.
+# Don't do this yourself! :-) Just do 'import gflags; FLAGS=gflags.FLAGS; etc'
+import gflags as flags
+FLAGS = flags.FLAGS
+
+# For historical reasons we use the name module_bar instead of test_module_bar.
+import test_module_bar as module_bar
+
+DECLARED_KEY_FLAGS = ['tmod_bar_x', 'tmod_bar_z', 'tmod_bar_t']
+
+
+def DefineFlags():
+ """Defines a few flags."""
+ module_bar.DefineFlags()
+ # The 'tmod_foo_' prefix (short for 'test_module_foo') ensures that we
+ # have no name clash with existing flags.
+ flags.DEFINE_boolean('tmod_foo_bool', True, 'Boolean flag from module foo.')
+ flags.DEFINE_string('tmod_foo_str', 'default', 'String flag.')
+ flags.DEFINE_integer('tmod_foo_int', 3, 'Sample int flag.')
+
+
+def DeclareKeyFlags():
+ """Declares a few key flags."""
+ for flag_name in DECLARED_KEY_FLAGS:
+ flags.DECLARE_key_flag(flag_name)
+
+
+def DeclareExtraKeyFlags():
+ """Declares some extra key flags."""
+ flags.ADOPT_module_key_flags(module_bar)
+
+
+def NamesOfDefinedFlags():
+ """Returns: list of names of flags defined by this module."""
+ return ['tmod_foo_bool', 'tmod_foo_str', 'tmod_foo_int']
+
+
+def NamesOfDeclaredKeyFlags():
+ """Returns: list of names of key flags for this module."""
+ return NamesOfDefinedFlags() + DECLARED_KEY_FLAGS
+
+
+def NamesOfDeclaredExtraKeyFlags():
+ """Returns the list of names of additional key flags for this module.
+
+ These are the flags that became key for this module only as a result
+ of a call to DeclareExtraKeyFlags() above. I.e., the flags declared
+ by module_bar, that were not already declared as key for this
+ module.
+
+ Returns:
+ The list of names of additional key flags for this module.
+ """
+ names_of_extra_key_flags = list(module_bar.NamesOfDefinedFlags())
+ for flag_name in NamesOfDeclaredKeyFlags():
+ while flag_name in names_of_extra_key_flags:
+ names_of_extra_key_flags.remove(flag_name)
+ return names_of_extra_key_flags
+
+
+def RemoveFlags():
+ """Deletes the flag definitions done by the above DefineFlags()."""
+ for flag_name in NamesOfDefinedFlags():
+ module_bar.RemoveOneFlag(flag_name)
+ module_bar.RemoveFlags()
+
+
+def GetModuleName():
+ """Uses flags._GetCallingModule() to return the name of this module.
+
+ For checking that _GetCallingModule works as expected.
+
+ Returns:
+ A string, the name of this module.
+ """
+ # Calling the protected _GetCallingModule generates a lint warning,
+ # but we do not have any other alternative to test that function.
+ return flags._GetCallingModule()
diff --git a/vendor/redis-py/.gitignore b/vendor/redis-py/.gitignore
new file mode 100755
index 0000000000..0ed6db73c1
--- /dev/null
+++ b/vendor/redis-py/.gitignore
@@ -0,0 +1,5 @@
+*.pyc
+redis.egg-info
+build/
+dist/
+dump.rdb
diff --git a/vendor/redis-py/CHANGES b/vendor/redis-py/CHANGES
new file mode 100755
index 0000000000..0d1955b5ee
--- /dev/null
+++ b/vendor/redis-py/CHANGES
@@ -0,0 +1,58 @@
+* 1.3.6
+ * Implementation of all Hash commands
+ * Pipelines now wrap their execution with MULTI and EXEC commands to
+ process all commands atomically.
+ * Connections can now set timeout. If command execution exceeds the
+ timeout, an exception is raised.
+ * Numerous bug fixes and more tests.
+* 1.3.4
+ * Skipped version numbers ahead so that the client version matches the
+ Redis version it is feature-compatible with. Going forward, the client
+ will stay in sync with Redis version numbers when client updates are
+ made.
+ * Completely refactored the client library. It's now trivial to maintain
+ and add new commands. The library is also much more consistent.
+ * With the exception of "Response value type inference" (see below), the
+ client should be backwards compatible with 0.6.1. Some older, less
+ consistent methods will emit DeprecationWarnings, indicating that you
+ should use another command or option, but these should continue to
+ work as expected for the next few releases.
+ * WARNING: BACKWARDS INCOMPATIBLE CHANGE: "Response value type inference"
+ Previously, all values returned from Redis went through a decoding
+ process. In this process, if the response was numeric, it would be
+ automatically converted to an int or float type prior to being returned.
+ Otherwise the response would be decoded as a unicode string. This meant
+ that storing the string "123" would actually return an integer 123, and
+ that the string "foo" would be returned as the unicode object u"foo".
+ This fundamentally breaks the retrieval of binary data (byte strings) and
+ values that might accidentally look like a number (a hash value). After
+ discussing this in detail with a number of users and on the Redis mailing
+ list (http://groups.google.com/group/redis-db/browse_thread/thread/9888eb9ff383c90c/ec44fe80b6400f7b#ec44fe80b6400f7b)
+ *ALL* values returned from methods such as get() now return raw
+ Python strings. It is now your responsibility to convert that data to
+ whatever datatype you need. Other methods that *always* return integer
+ or float values, such as INCR, DECR, LLEN, ZSCORE, etc., will continue
+ returning values of the appropriate type. This resolves issue #2, #8
+ and #11:
+ http://github.com/andymccurdy/redis-py/issues#issue/2
+ http://github.com/andymccurdy/redis-py/issues#issue/8
+ http://github.com/andymccurdy/redis-py/issues#issue/11
+ * The "select" method now takes a "host" and "port" argument in addition
+ to the database. Behind the scenes, select() swaps out the underlying
+ socket connection. This resolves issue #4:
+ http://github.com/andymccurdy/redis-py/issues#issue/4
+ * The client now supports pipelining of Redis commands. Use the pipeline()
+ method to create a new Pipeline object. Each command called on the
+ pipeline object will be buffered until the pipeline if executed.
+ A list of each command's results will be returned by execution. Use
+ this for batch processing in order to eliminate multiple request/response
+ cycles.
+
+* 0.6.1
+ * Added support for ZINCRBY via the `zincr` command
+ * Swapped score and member parameters to zadd to make it more similar to other commands.
+ * Added support for Python 2.4 (thanks David Moss)
+* 0.6.0 Changed to Andy McCurdy's codebase on github
+* 0.5.5 Patch from David Moss, SHUTDOWN and doctest bugfix
+* 0.5.1-4 Bugfixes, no code changes, just packaging, 10/2/09
+* 0.5 Initial release, redis.py version 1.0.1, 10/2/09
diff --git a/vendor/redis-py/INSTALL b/vendor/redis-py/INSTALL
new file mode 100755
index 0000000000..951f7dea8a
--- /dev/null
+++ b/vendor/redis-py/INSTALL
@@ -0,0 +1,6 @@
+
+Please use
+ python setup.py install
+
+and report errors to Andy McCurdy (sedrik@gmail.com)
+
diff --git a/vendor/redis-py/LICENSE b/vendor/redis-py/LICENSE
new file mode 100755
index 0000000000..073b05cec3
--- /dev/null
+++ b/vendor/redis-py/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2010 Andy McCurdy
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/redis-py/MANIFEST.in b/vendor/redis-py/MANIFEST.in
new file mode 100755
index 0000000000..1b2fcd9432
--- /dev/null
+++ b/vendor/redis-py/MANIFEST.in
@@ -0,0 +1,4 @@
+include CHANGES
+include INSTALL
+include LICENSE
+include README.md
diff --git a/vendor/redis-py/README.md b/vendor/redis-py/README.md
new file mode 100755
index 0000000000..5a086e6126
--- /dev/null
+++ b/vendor/redis-py/README.md
@@ -0,0 +1,33 @@
+redis-py
+========
+
+This is the Python interface to the Redis key-value store.
+
+
+Usage
+-----
+
+ >>> import redis
+ >>> r = redis.Redis(host='localhost', port=6379, db=0)
+ >>> r.set('foo', 'bar') # or r['foo'] = 'bar'
+ True
+ >>> r.get('foo') # or r['foo']
+ 'bar'
+
+For a complete list of commands, check out the list of Redis commands here:
+http://code.google.com/p/redis/wiki/CommandReference
+
+
+Author
+------
+
+redis-py is developed and maintained by Andy McCurdy (sedrik@gmail.com).
+It can be found here: http://github.com/andymccurdy/redis-py
+
+Special thanks to:
+
+* Ludovico Magnocavallo, author of the original Python Redis client, from
+ which some of the socket code is still used.
+* Alexander Solovyov for ideas on the generic response callback system.
+* Paul Hubbard for initial packaging support.
+
diff --git a/vendor/redis-py/redis/__init__.py b/vendor/redis-py/redis/__init__.py
new file mode 100755
index 0000000000..93155fb4dd
--- /dev/null
+++ b/vendor/redis-py/redis/__init__.py
@@ -0,0 +1,10 @@
+# legacy imports
+from redis.client import Redis, ConnectionPool
+from redis.exceptions import RedisError, ConnectionError, AuthenticationError
+from redis.exceptions import ResponseError, InvalidResponse, InvalidData
+
+__all__ = [
+ 'Redis', 'ConnectionPool',
+ 'RedisError', 'ConnectionError', 'ResponseError', 'AuthenticationError'
+ 'InvalidResponse', 'InvalidData',
+ ]
diff --git a/vendor/redis-py/redis/client.py b/vendor/redis-py/redis/client.py
new file mode 100755
index 0000000000..d6932d1d3f
--- /dev/null
+++ b/vendor/redis-py/redis/client.py
@@ -0,0 +1,1259 @@
+import datetime
+import errno
+import socket
+import threading
+import time
+import warnings
+from itertools import chain
+from redis.exceptions import ConnectionError, ResponseError, InvalidResponse
+from redis.exceptions import RedisError, AuthenticationError
+
+
+class ConnectionPool(threading.local):
+ "Manages a list of connections on the local thread"
+ def __init__(self):
+ self.connections = {}
+
+ def make_connection_key(self, host, port, db):
+ "Create a unique key for the specified host, port and db"
+ return '%s:%s:%s' % (host, port, db)
+
+ def get_connection(self, host, port, db, password, socket_timeout):
+ "Return a specific connection for the specified host, port and db"
+ key = self.make_connection_key(host, port, db)
+ if key not in self.connections:
+ self.connections[key] = Connection(
+ host, port, db, password, socket_timeout)
+ return self.connections[key]
+
+ def get_all_connections(self):
+ "Return a list of all connection objects the manager knows about"
+ return self.connections.values()
+
+
+class Connection(object):
+ "Manages TCP communication to and from a Redis server"
+ def __init__(self, host='localhost', port=6379, db=0, password=None,
+ socket_timeout=None):
+ self.host = host
+ self.port = port
+ self.db = db
+ self.password = password
+ self.socket_timeout = socket_timeout
+ self._sock = None
+ self._fp = None
+
+ def connect(self, redis_instance):
+ "Connects to the Redis server if not already connected"
+ if self._sock:
+ return
+ try:
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect((self.host, self.port))
+ except socket.error, e:
+ # args for socket.error can either be (errno, "message")
+ # or just "message"
+ if len(e.args) == 1:
+ error_message = "Error connecting to %s:%s. %s." % \
+ (self.host, self.port, e.args[0])
+ else:
+ error_message = "Error %s connecting %s:%s. %s." % \
+ (e.args[0], self.host, self.port, e.args[1])
+ raise ConnectionError(error_message)
+ sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
+ sock.settimeout(self.socket_timeout)
+ self._sock = sock
+ self._fp = sock.makefile('r')
+ redis_instance._setup_connection()
+
+ def disconnect(self):
+ "Disconnects from the Redis server"
+ if self._sock is None:
+ return
+ try:
+ self._sock.close()
+ except socket.error:
+ pass
+ self._sock = None
+ self._fp = None
+
+ def send(self, command, redis_instance):
+ "Send ``command`` to the Redis server. Return the result."
+ self.connect(redis_instance)
+ try:
+ self._sock.sendall(command)
+ except socket.error, e:
+ if e.args[0] == errno.EPIPE:
+ self.disconnect()
+ raise ConnectionError("Error %s while writing to socket. %s." % \
+ e.args)
+
+ def read(self, length=None):
+ """
+ Read a line from the socket is length is None,
+ otherwise read ``length`` bytes
+ """
+ try:
+ if length is not None:
+ return self._fp.read(length)
+ return self._fp.readline()
+ except socket.error, e:
+ self.disconnect()
+ if e.args and e.args[0] == errno.EAGAIN:
+ raise ConnectionError("Error while reading from socket: %s" % \
+ e.args[1])
+ return ''
+
+def list_or_args(command, keys, args):
+ # returns a single list combining keys and args
+ # if keys is not a list or args has items, issue a
+ # deprecation warning
+ oldapi = bool(args)
+ try:
+ i = iter(keys)
+ # a string can be iterated, but indicates
+ # keys wasn't passed as a list
+ if isinstance(keys, basestring):
+ oldapi = True
+ except TypeError:
+ oldapi = True
+ keys = [keys]
+ if oldapi:
+ warnings.warn(DeprecationWarning(
+ "Passing *args to Redis.%s has been deprecated. "
+ "Pass an iterable to ``keys`` instead" % command
+ ))
+ keys.extend(args)
+ return keys
+
+def timestamp_to_datetime(response):
+ "Converts a unix timestamp to a Python datetime object"
+ if not response:
+ return None
+ try:
+ response = int(response)
+ except ValueError:
+ return None
+ return datetime.datetime.fromtimestamp(response)
+
+def string_keys_to_dict(key_string, callback):
+ return dict([(key, callback) for key in key_string.split()])
+
+def dict_merge(*dicts):
+ merged = {}
+ [merged.update(d) for d in dicts]
+ return merged
+
+def parse_info(response):
+ "Parse the result of Redis's INFO command into a Python dict"
+ info = {}
+ def get_value(value):
+ if ',' not in value:
+ return value
+ sub_dict = {}
+ for item in value.split(','):
+ k, v = item.split('=')
+ try:
+ sub_dict[k] = int(v)
+ except ValueError:
+ sub_dict[k] = v
+ return sub_dict
+ for line in response.splitlines():
+ key, value = line.split(':')
+ try:
+ info[key] = int(value)
+ except ValueError:
+ info[key] = get_value(value)
+ return info
+
+def pairs_to_dict(response):
+ "Create a dict given a list of key/value pairs"
+ return dict(zip(response[::2], response[1::2]))
+
+def zset_score_pairs(response, **options):
+ """
+ If ``withscores`` is specified in the options, return the response as
+ a list of (value, score) pairs
+ """
+ if not response or not options['withscores']:
+ return response
+ return zip(response[::2], map(float, response[1::2]))
+
+def int_or_none(response):
+ if response is None:
+ return None
+ return int(response)
+
+def float_or_none(response):
+ if response is None:
+ return None
+ return float(response)
+
+
+class Redis(threading.local):
+ """
+ Implementation of the Redis protocol.
+
+ This abstract class provides a Python interface to all Redis commands
+ and an implementation of the Redis protocol.
+
+ Connection and Pipeline derive from this, implementing how
+ the commands are sent and received to the Redis server
+ """
+ RESPONSE_CALLBACKS = dict_merge(
+ string_keys_to_dict(
+ 'AUTH DEL EXISTS EXPIRE EXPIREAT HDEL HEXISTS HMSET MOVE MSETNX '
+ 'RENAMENX SADD SISMEMBER SMOVE SETEX SETNX SREM ZADD ZREM',
+ bool
+ ),
+ string_keys_to_dict(
+ 'DECRBY HLEN INCRBY LLEN SCARD SDIFFSTORE SINTERSTORE '
+ 'SUNIONSTORE ZCARD ZREMRANGEBYSCORE ZREVRANK',
+ int
+ ),
+ string_keys_to_dict(
+ # these return OK, or int if redis-server is >=1.3.4
+ 'LPUSH RPUSH',
+ lambda r: isinstance(r, int) and r or r == 'OK'
+ ),
+ string_keys_to_dict('ZSCORE ZINCRBY', float_or_none),
+ string_keys_to_dict(
+ 'FLUSHALL FLUSHDB LSET LTRIM MSET RENAME '
+ 'SAVE SELECT SET SHUTDOWN',
+ lambda r: r == 'OK'
+ ),
+ string_keys_to_dict('SDIFF SINTER SMEMBERS SUNION',
+ lambda r: set(r)
+ ),
+ string_keys_to_dict('ZRANGE ZRANGEBYSCORE ZREVRANGE', zset_score_pairs),
+ {
+ 'BGREWRITEAOF': lambda r: \
+ r == 'Background rewriting of AOF file started',
+ 'BGSAVE': lambda r: r == 'Background saving started',
+ 'HGETALL': lambda r: r and pairs_to_dict(r) or {},
+ 'INFO': parse_info,
+ 'LASTSAVE': timestamp_to_datetime,
+ 'PING': lambda r: r == 'PONG',
+ 'RANDOMKEY': lambda r: r and r or None,
+ 'TTL': lambda r: r != -1 and r or None,
+ 'ZRANK': int_or_none,
+ }
+ )
+
+ # commands that should NOT pull data off the network buffer when executed
+ SUBSCRIPTION_COMMANDS = set(['SUBSCRIBE', 'UNSUBSCRIBE'])
+
+ def __init__(self, host='localhost', port=6379,
+ db=0, password=None, socket_timeout=None,
+ connection_pool=None,
+ charset='utf-8', errors='strict'):
+ self.encoding = charset
+ self.errors = errors
+ self.connection = None
+ self.subscribed = False
+ self.connection_pool = connection_pool and connection_pool or ConnectionPool()
+ self.select(db, host, port, password, socket_timeout)
+
+ #### Legacty accessors of connection information ####
+ def _get_host(self):
+ return self.connection.host
+ host = property(_get_host)
+
+ def _get_port(self):
+ return self.connection.port
+ port = property(_get_port)
+
+ def _get_db(self):
+ return self.connection.db
+ db = property(_get_db)
+
+ def pipeline(self, transaction=True):
+ """
+ Return a new pipeline object that can queue multiple commands for
+ later execution. ``transaction`` indicates whether all commands
+ should be executed atomically. Apart from multiple atomic operations,
+ pipelines are useful for batch loading of data as they reduce the
+ number of back and forth network operations between client and server.
+ """
+ return Pipeline(
+ self.connection,
+ transaction,
+ self.encoding,
+ self.errors
+ )
+
+
+ #### COMMAND EXECUTION AND PROTOCOL PARSING ####
+ def _execute_command(self, command_name, command, **options):
+ subscription_command = command_name in self.SUBSCRIPTION_COMMANDS
+ if self.subscribed and not subscription_command:
+ raise RedisError("Cannot issue commands other than SUBSCRIBE and "
+ "UNSUBSCRIBE while channels are open")
+ try:
+ self.connection.send(command, self)
+ if subscription_command:
+ return None
+ return self.parse_response(command_name, **options)
+ except ConnectionError:
+ self.connection.disconnect()
+ self.connection.send(command, self)
+ if subscription_command:
+ return None
+ return self.parse_response(command_name, **options)
+
+ def execute_command(self, *args, **options):
+ "Sends the command to the redis server and returns it's response"
+ cmd_count = len(args)
+ cmds = []
+ for i in args:
+ enc_value = self.encode(i)
+ cmds.append('$%s\r\n%s\r\n' % (len(enc_value), enc_value))
+ return self._execute_command(
+ args[0],
+ '*%s\r\n%s' % (cmd_count, ''.join(cmds)),
+ **options
+ )
+
+ def _parse_response(self, command_name, catch_errors):
+ conn = self.connection
+ response = conn.read()[:-2] # strip last two characters (\r\n)
+ if not response:
+ self.connection.disconnect()
+ raise ConnectionError("Socket closed on remote end")
+
+ # server returned a null value
+ if response in ('$-1', '*-1'):
+ return None
+ byte, response = response[0], response[1:]
+
+ # server returned an error
+ if byte == '-':
+ if response.startswith('ERR '):
+ response = response[4:]
+ raise ResponseError(response)
+ # single value
+ elif byte == '+':
+ return response
+ # int value
+ elif byte == ':':
+ return int(response)
+ # bulk response
+ elif byte == '$':
+ length = int(response)
+ if length == -1:
+ return None
+ response = length and conn.read(length) or ''
+ conn.read(2) # read the \r\n delimiter
+ return response
+ # multi-bulk response
+ elif byte == '*':
+ length = int(response)
+ if length == -1:
+ return None
+ if not catch_errors:
+ return [self._parse_response(command_name, catch_errors)
+ for i in range(length)]
+ else:
+ # for pipelines, we need to read everything,
+ # including response errors. otherwise we'd
+ # completely mess up the receive buffer
+ data = []
+ for i in range(length):
+ try:
+ data.append(
+ self._parse_response(command_name, catch_errors)
+ )
+ except Exception, e:
+ data.append(e)
+ return data
+
+ raise InvalidResponse("Unknown response type for: %s" % command_name)
+
+ def parse_response(self, command_name, catch_errors=False, **options):
+ "Parses a response from the Redis server"
+ response = self._parse_response(command_name, catch_errors)
+ if command_name in self.RESPONSE_CALLBACKS:
+ return self.RESPONSE_CALLBACKS[command_name](response, **options)
+ return response
+
+ def encode(self, value):
+ "Encode ``value`` using the instance's charset"
+ if isinstance(value, str):
+ return value
+ if isinstance(value, unicode):
+ return value.encode(self.encoding, self.errors)
+ # not a string or unicode, attempt to convert to a string
+ return str(value)
+
+ #### CONNECTION HANDLING ####
+ def get_connection(self, host, port, db, password, socket_timeout):
+ "Returns a connection object"
+ conn = self.connection_pool.get_connection(
+ host, port, db, password, socket_timeout)
+ # if for whatever reason the connection gets a bad password, make
+ # sure a subsequent attempt with the right password makes its way
+ # to the connection
+ conn.password = password
+ return conn
+
+ def _setup_connection(self):
+ """
+ After successfully opening a socket to the Redis server, the
+ connection object calls this method to authenticate and select
+ the appropriate database.
+ """
+ if self.connection.password:
+ if not self.execute_command('AUTH', self.connection.password):
+ raise AuthenticationError("Invalid Password")
+ self.execute_command('SELECT', self.connection.db)
+
+ def select(self, db, host=None, port=None, password=None,
+ socket_timeout=None):
+ """
+ Switch to a different Redis connection.
+
+ If the host and port aren't provided and there's an existing
+ connection, use the existing connection's host and port instead.
+
+ Note this method actually replaces the underlying connection object
+ prior to issuing the SELECT command. This makes sure we protect
+ the thread-safe connections
+ """
+ if host is None:
+ if self.connection is None:
+ raise RedisError("A valid hostname or IP address "
+ "must be specified")
+ host = self.connection.host
+ if port is None:
+ if self.connection is None:
+ raise RedisError("A valid port must be specified")
+ port = self.connection.port
+
+ self.connection = self.get_connection(
+ host, port, db, password, socket_timeout)
+
+
+ #### SERVER INFORMATION ####
+ def bgrewriteaof(self):
+ "Tell the Redis server to rewrite the AOF file from data in memory."
+ return self.execute_command('BGREWRITEAOF')
+
+ def bgsave(self):
+ """
+ Tell the Redis server to save its data to disk. Unlike save(),
+ this method is asynchronous and returns immediately.
+ """
+ return self.execute_command('BGSAVE')
+
+ def dbsize(self):
+ "Returns the number of keys in the current database"
+ return self.execute_command('DBSIZE')
+
+ def delete(self, *names):
+ "Delete one or more keys specified by ``names``"
+ return self.execute_command('DEL', *names)
+ __delitem__ = delete
+
+ def flush(self, all_dbs=False):
+ warnings.warn(DeprecationWarning(
+ "'flush' has been deprecated. "
+ "Use Redis.flushdb() or Redis.flushall() instead"))
+ if all_dbs:
+ return self.flushall()
+ return self.flushdb()
+
+ def flushall(self):
+ "Delete all keys in all databases on the current host"
+ return self.execute_command('FLUSHALL')
+
+ def flushdb(self):
+ "Delete all keys in the current database"
+ return self.execute_command('FLUSHDB')
+
+ def info(self):
+ "Returns a dictionary containing information about the Redis server"
+ return self.execute_command('INFO')
+
+ def lastsave(self):
+ """
+ Return a Python datetime object representing the last time the
+ Redis database was saved to disk
+ """
+ return self.execute_command('LASTSAVE')
+
+ def ping(self):
+ "Ping the Redis server"
+ return self.execute_command('PING')
+
+ def save(self):
+ """
+ Tell the Redis server to save its data to disk,
+ blocking until the save is complete
+ """
+ return self.execute_command('SAVE')
+
+ #### BASIC KEY COMMANDS ####
+ def append(self, key, value):
+ """
+ Appends the string ``value`` to the value at ``key``. If ``key``
+ doesn't already exist, create it with a value of ``value``.
+ Returns the new length of the value at ``key``.
+ """
+ return self.execute_command('APPEND', key, value)
+
+ def decr(self, name, amount=1):
+ """
+ Decrements the value of ``key`` by ``amount``. If no key exists,
+ the value will be initialized as 0 - ``amount``
+ """
+ return self.execute_command('DECRBY', name, amount)
+
+ def exists(self, name):
+ "Returns a boolean indicating whether key ``name`` exists"
+ return self.execute_command('EXISTS', name)
+ __contains__ = exists
+
+ def expire(self, name, time):
+ "Set an expire flag on key ``name`` for ``time`` seconds"
+ return self.execute_command('EXPIRE', name, time)
+
+ def expireat(self, name, when):
+ """
+ Set an expire flag on key ``name``. ``when`` can be represented
+ as an integer indicating unix time or a Python datetime object.
+ """
+ if isinstance(when, datetime.datetime):
+ when = int(time.mktime(when.timetuple()))
+ return self.execute_command('EXPIREAT', name, when)
+
+ def get(self, name):
+ """
+ Return the value at key ``name``, or None of the key doesn't exist
+ """
+ return self.execute_command('GET', name)
+ __getitem__ = get
+
+ def getset(self, name, value):
+ """
+ Set the value at key ``name`` to ``value`` if key doesn't exist
+ Return the value at key ``name`` atomically
+ """
+ return self.execute_command('GETSET', name, value)
+
+ def incr(self, name, amount=1):
+ """
+ Increments the value of ``key`` by ``amount``. If no key exists,
+ the value will be initialized as ``amount``
+ """
+ return self.execute_command('INCRBY', name, amount)
+
+ def keys(self, pattern='*'):
+ "Returns a list of keys matching ``pattern``"
+ return self.execute_command('KEYS', pattern)
+
+ def mget(self, keys, *args):
+ """
+ Returns a list of values ordered identically to ``keys``
+
+ * Passing *args to this method has been deprecated *
+ """
+ keys = list_or_args('mget', keys, args)
+ return self.execute_command('MGET', *keys)
+
+ def mset(self, mapping):
+ "Sets each key in the ``mapping`` dict to its corresponding value"
+ items = []
+ [items.extend(pair) for pair in mapping.iteritems()]
+ return self.execute_command('MSET', *items)
+
+ def msetnx(self, mapping):
+ """
+ Sets each key in the ``mapping`` dict to its corresponding value if
+ none of the keys are already set
+ """
+ items = []
+ [items.extend(pair) for pair in mapping.iteritems()]
+ return self.execute_command('MSETNX', *items)
+
+ def move(self, name, db):
+ "Moves the key ``name`` to a different Redis database ``db``"
+ return self.execute_command('MOVE', name, db)
+
+ def randomkey(self):
+ "Returns the name of a random key"
+ return self.execute_command('RANDOMKEY')
+
+ def rename(self, src, dst, **kwargs):
+ """
+ Rename key ``src`` to ``dst``
+
+ * The following flags have been deprecated *
+ If ``preserve`` is True, rename the key only if the destination name
+ doesn't already exist
+ """
+ if kwargs:
+ if 'preserve' in kwargs:
+ warnings.warn(DeprecationWarning(
+ "preserve option to 'rename' is deprecated, "
+ "use Redis.renamenx instead"))
+ if kwargs['preserve']:
+ return self.renamenx(src, dst)
+ return self.execute_command('RENAME', src, dst)
+
+ def renamenx(self, src, dst):
+ "Rename key ``src`` to ``dst`` if ``dst`` doesn't already exist"
+ return self.execute_command('RENAMENX', src, dst)
+
+
+ def set(self, name, value, **kwargs):
+ """
+ Set the value at key ``name`` to ``value``
+
+ * The following flags have been deprecated *
+ If ``preserve`` is True, set the value only if key doesn't already
+ exist
+ If ``getset`` is True, set the value only if key doesn't already exist
+ and return the resulting value of key
+ """
+ if kwargs:
+ if 'getset' in kwargs:
+ warnings.warn(DeprecationWarning(
+ "getset option to 'set' is deprecated, "
+ "use Redis.getset() instead"))
+ if kwargs['getset']:
+ return self.getset(name, value)
+ if 'preserve' in kwargs:
+ warnings.warn(DeprecationWarning(
+ "preserve option to 'set' is deprecated, "
+ "use Redis.setnx() instead"))
+ if kwargs['preserve']:
+ return self.setnx(name, value)
+ return self.execute_command('SET', name, value)
+ __setitem__ = set
+
+ def setex(self, name, value, time):
+ """
+ Set the value of key ``name`` to ``value``
+ that expires in ``time`` seconds
+ """
+ return self.execute_command('SETEX', name, time, value)
+
+ def setnx(self, name, value):
+ "Set the value of key ``name`` to ``value`` if key doesn't exist"
+ return self.execute_command('SETNX', name, value)
+
+ def substr(self, name, start, end=-1):
+ """
+ Return a substring of the string at key ``name``. ``start`` and ``end``
+ are 0-based integers specifying the portion of the string to return.
+ """
+ return self.execute_command('SUBSTR', name, start, end)
+
+ def ttl(self, name):
+ "Returns the number of seconds until the key ``name`` will expire"
+ return self.execute_command('TTL', name)
+
+ def type(self, name):
+ "Returns the type of key ``name``"
+ return self.execute_command('TYPE', name)
+
+
+ #### LIST COMMANDS ####
+ def blpop(self, keys, timeout=0):
+ """
+ LPOP a value off of the first non-empty list
+ named in the ``keys`` list.
+
+ If none of the lists in ``keys`` has a value to LPOP, then block
+ for ``timeout`` seconds, or until a value gets pushed on to one
+ of the lists.
+
+ If timeout is 0, then block indefinitely.
+ """
+ keys = list(keys)
+ keys.append(timeout)
+ return self.execute_command('BLPOP', *keys)
+
+ def brpop(self, keys, timeout=0):
+ """
+ RPOP a value off of the first non-empty list
+ named in the ``keys`` list.
+
+ If none of the lists in ``keys`` has a value to LPOP, then block
+ for ``timeout`` seconds, or until a value gets pushed on to one
+ of the lists.
+
+ If timeout is 0, then block indefinitely.
+ """
+ keys = list(keys)
+ keys.append(timeout)
+ return self.execute_command('BRPOP', *keys)
+
+ def lindex(self, name, index):
+ """
+ Return the item from list ``name`` at position ``index``
+
+ Negative indexes are supported and will return an item at the
+ end of the list
+ """
+ return self.execute_command('LINDEX', name, index)
+
+ def llen(self, name):
+ "Return the length of the list ``name``"
+ return self.execute_command('LLEN', name)
+
+ def lpop(self, name):
+ "Remove and return the first item of the list ``name``"
+ return self.execute_command('LPOP', name)
+
+ def lpush(self, name, value):
+ "Push ``value`` onto the head of the list ``name``"
+ return self.execute_command('LPUSH', name, value)
+
+ def lrange(self, name, start, end):
+ """
+ Return a slice of the list ``name`` between
+ position ``start`` and ``end``
+
+ ``start`` and ``end`` can be negative numbers just like
+ Python slicing notation
+ """
+ return self.execute_command('LRANGE', name, start, end)
+
+ def lrem(self, name, value, num=0):
+ """
+ Remove the first ``num`` occurrences of ``value`` from list ``name``
+
+ If ``num`` is 0, then all occurrences will be removed
+ """
+ return self.execute_command('LREM', name, num, value)
+
+ def lset(self, name, index, value):
+ "Set ``position`` of list ``name`` to ``value``"
+ return self.execute_command('LSET', name, index, value)
+
+ def ltrim(self, name, start, end):
+ """
+ Trim the list ``name``, removing all values not within the slice
+ between ``start`` and ``end``
+
+ ``start`` and ``end`` can be negative numbers just like
+ Python slicing notation
+ """
+ return self.execute_command('LTRIM', name, start, end)
+
+ def pop(self, name, tail=False):
+ """
+ Pop and return the first or last element of list ``name``
+
+ * This method has been deprecated,
+ use Redis.lpop or Redis.rpop instead *
+ """
+ warnings.warn(DeprecationWarning(
+ "Redis.pop has been deprecated, "
+ "use Redis.lpop or Redis.rpop instead"))
+ if tail:
+ return self.rpop(name)
+ return self.lpop(name)
+
+ def push(self, name, value, head=False):
+ """
+ Push ``value`` onto list ``name``.
+
+ * This method has been deprecated,
+ use Redis.lpush or Redis.rpush instead *
+ """
+ warnings.warn(DeprecationWarning(
+ "Redis.push has been deprecated, "
+ "use Redis.lpush or Redis.rpush instead"))
+ if head:
+ return self.lpush(name, value)
+ return self.rpush(name, value)
+
+ def rpop(self, name):
+ "Remove and return the last item of the list ``name``"
+ return self.execute_command('RPOP', name)
+
+ def rpoplpush(self, src, dst):
+ """
+ RPOP a value off of the ``src`` list and atomically LPUSH it
+ on to the ``dst`` list. Returns the value.
+ """
+ return self.execute_command('RPOPLPUSH', src, dst)
+
+ def rpush(self, name, value):
+ "Push ``value`` onto the tail of the list ``name``"
+ return self.execute_command('RPUSH', name, value)
+
+ def sort(self, name, start=None, num=None, by=None, get=None,
+ desc=False, alpha=False, store=None):
+ """
+ Sort and return the list, set or sorted set at ``name``.
+
+ ``start`` and ``num`` allow for paging through the sorted data
+
+ ``by`` allows using an external key to weight and sort the items.
+ Use an "*" to indicate where in the key the item value is located
+
+ ``get`` allows for returning items from external keys rather than the
+ sorted data itself. Use an "*" to indicate where int he key
+ the item value is located
+
+ ``desc`` allows for reversing the sort
+
+ ``alpha`` allows for sorting lexicographically rather than numerically
+
+ ``store`` allows for storing the result of the sort into
+ the key ``store``
+ """
+ if (start is not None and num is None) or \
+ (num is not None and start is None):
+ raise RedisError("``start`` and ``num`` must both be specified")
+
+ pieces = [name]
+ if by is not None:
+ pieces.append('BY')
+ pieces.append(by)
+ if start is not None and num is not None:
+ pieces.append('LIMIT')
+ pieces.append(start)
+ pieces.append(num)
+ if get is not None:
+ pieces.append('GET')
+ pieces.append(get)
+ if desc:
+ pieces.append('DESC')
+ if alpha:
+ pieces.append('ALPHA')
+ if store is not None:
+ pieces.append('STORE')
+ pieces.append(store)
+ return self.execute_command('SORT', *pieces)
+
+
+ #### SET COMMANDS ####
+ def sadd(self, name, value):
+ "Add ``value`` to set ``name``"
+ return self.execute_command('SADD', name, value)
+
+ def scard(self, name):
+ "Return the number of elements in set ``name``"
+ return self.execute_command('SCARD', name)
+
+ def sdiff(self, keys, *args):
+ "Return the difference of sets specified by ``keys``"
+ keys = list_or_args('sdiff', keys, args)
+ return self.execute_command('SDIFF', *keys)
+
+ def sdiffstore(self, dest, keys, *args):
+ """
+ Store the difference of sets specified by ``keys`` into a new
+ set named ``dest``. Returns the number of keys in the new set.
+ """
+ keys = list_or_args('sdiffstore', keys, args)
+ return self.execute_command('SDIFFSTORE', dest, *keys)
+
+ def sinter(self, keys, *args):
+ "Return the intersection of sets specified by ``keys``"
+ keys = list_or_args('sinter', keys, args)
+ return self.execute_command('SINTER', *keys)
+
+ def sinterstore(self, dest, keys, *args):
+ """
+ Store the intersection of sets specified by ``keys`` into a new
+ set named ``dest``. Returns the number of keys in the new set.
+ """
+ keys = list_or_args('sinterstore', keys, args)
+ return self.execute_command('SINTERSTORE', dest, *keys)
+
+ def sismember(self, name, value):
+ "Return a boolean indicating if ``value`` is a member of set ``name``"
+ return self.execute_command('SISMEMBER', name, value)
+
+ def smembers(self, name):
+ "Return all members of the set ``name``"
+ return self.execute_command('SMEMBERS', name)
+
+ def smove(self, src, dst, value):
+ "Move ``value`` from set ``src`` to set ``dst`` atomically"
+ return self.execute_command('SMOVE', src, dst, value)
+
+ def spop(self, name):
+ "Remove and return a random member of set ``name``"
+ return self.execute_command('SPOP', name)
+
+ def srandmember(self, name):
+ "Return a random member of set ``name``"
+ return self.execute_command('SRANDMEMBER', name)
+
+ def srem(self, name, value):
+ "Remove ``value`` from set ``name``"
+ return self.execute_command('SREM', name, value)
+
+ def sunion(self, keys, *args):
+ "Return the union of sets specifiued by ``keys``"
+ keys = list_or_args('sunion', keys, args)
+ return self.execute_command('SUNION', *keys)
+
+ def sunionstore(self, dest, keys, *args):
+ """
+ Store the union of sets specified by ``keys`` into a new
+ set named ``dest``. Returns the number of keys in the new set.
+ """
+ keys = list_or_args('sunionstore', keys, args)
+ return self.execute_command('SUNIONSTORE', dest, *keys)
+
+
+ #### SORTED SET COMMANDS ####
+ def zadd(self, name, value, score):
+ "Add member ``value`` with score ``score`` to sorted set ``name``"
+ return self.execute_command('ZADD', name, score, value)
+
+ def zcard(self, name):
+ "Return the number of elements in the sorted set ``name``"
+ return self.execute_command('ZCARD', name)
+
+ def zincr(self, key, member, value=1):
+ "This has been deprecated, use zincrby instead"
+ warnings.warn(DeprecationWarning(
+ "Redis.zincr has been deprecated, use Redis.zincrby instead"
+ ))
+ return self.zincrby(key, member, value)
+
+ def zincrby(self, name, value, amount=1):
+ "Increment the score of ``value`` in sorted set ``name`` by ``amount``"
+ return self.execute_command('ZINCRBY', name, amount, value)
+
+ def zinter(self, dest, keys, aggregate=None):
+ """
+ Intersect multiple sorted sets specified by ``keys`` into
+ a new sorted set, ``dest``. Scores in the destination will be
+ aggregated based on the ``aggregate``, or SUM if none is provided.
+ """
+ return self._zaggregate('ZINTER', dest, keys, aggregate)
+
+ def zrange(self, name, start, end, desc=False, withscores=False):
+ """
+ Return a range of values from sorted set ``name`` between
+ ``start`` and ``end`` sorted in ascending order.
+
+ ``start`` and ``end`` can be negative, indicating the end of the range.
+
+ ``desc`` indicates to sort in descending order.
+
+ ``withscores`` indicates to return the scores along with the values.
+ The return type is a list of (value, score) pairs
+ """
+ if desc:
+ return self.zrevrange(name, start, end, withscores)
+ pieces = ['ZRANGE', name, start, end]
+ if withscores:
+ pieces.append('withscores')
+ return self.execute_command(*pieces, **{'withscores': withscores})
+
+ def zrangebyscore(self, name, min, max,
+ start=None, num=None, withscores=False):
+ """
+ Return a range of values from the sorted set ``name`` with scores
+ between ``min`` and ``max``.
+
+ If ``start`` and ``num`` are specified, then return a slice of the range.
+
+ ``withscores`` indicates to return the scores along with the values.
+ The return type is a list of (value, score) pairs
+ """
+ if (start is not None and num is None) or \
+ (num is not None and start is None):
+ raise RedisError("``start`` and ``num`` must both be specified")
+ pieces = ['ZRANGEBYSCORE', name, min, max]
+ if start is not None and num is not None:
+ pieces.extend(['LIMIT', start, num])
+ if withscores:
+ pieces.append('withscores')
+ return self.execute_command(*pieces, **{'withscores': withscores})
+
+ def zrank(self, name, value):
+ """
+ Returns a 0-based value indicating the rank of ``value`` in sorted set
+ ``name``
+ """
+ return self.execute_command('ZRANK', name, value)
+
+ def zrem(self, name, value):
+ "Remove member ``value`` from sorted set ``name``"
+ return self.execute_command('ZREM', name, value)
+
+ def zremrangebyscore(self, name, min, max):
+ """
+ Remove all elements in the sorted set ``name`` with scores
+ between ``min`` and ``max``
+ """
+ return self.execute_command('ZREMRANGEBYSCORE', name, min, max)
+
+ def zrevrange(self, name, start, num, withscores=False):
+ """
+ Return a range of values from sorted set ``name`` between
+ ``start`` and ``num`` sorted in descending order.
+
+ ``start`` and ``num`` can be negative, indicating the end of the range.
+
+ ``withscores`` indicates to return the scores along with the values
+ as a dictionary of value => score
+ """
+ pieces = ['ZREVRANGE', name, start, num]
+ if withscores:
+ pieces.append('withscores')
+ return self.execute_command(*pieces, **{'withscores': withscores})
+
+ def zrevrank(self, name, value):
+ """
+ Returns a 0-based value indicating the descending rank of
+ ``value`` in sorted set ``name``
+ """
+ return self.execute_command('ZREVRANK', name, value)
+
+ def zscore(self, name, value):
+ "Return the score of element ``value`` in sorted set ``name``"
+ return self.execute_command('ZSCORE', name, value)
+
+ def zunion(self, dest, keys, aggregate=None):
+ """
+ Union multiple sorted sets specified by ``keys`` into
+ a new sorted set, ``dest``. Scores in the destination will be
+ aggregated based on the ``aggregate``, or SUM if none is provided.
+ """
+ return self._zaggregate('ZUNION', dest, keys, aggregate)
+
+ def _zaggregate(self, command, dest, keys, aggregate=None):
+ pieces = [command, dest, len(keys)]
+ if isinstance(keys, dict):
+ items = keys.items()
+ keys = [i[0] for i in items]
+ weights = [i[1] for i in items]
+ else:
+ weights = None
+ pieces.extend(keys)
+ if weights:
+ pieces.append('WEIGHTS')
+ pieces.extend(weights)
+ if aggregate:
+ pieces.append('AGGREGATE')
+ pieces.append(aggregate)
+ return self.execute_command(*pieces)
+
+ #### HASH COMMANDS ####
+ def hdel(self, name, key):
+ "Delete ``key`` from hash ``name``"
+ return self.execute_command('HDEL', name, key)
+
+ def hexists(self, name, key):
+ "Returns a boolean indicating if ``key`` exists within hash ``name``"
+ return self.execute_command('HEXISTS', name, key)
+
+ def hget(self, name, key):
+ "Return the value of ``key`` within the hash ``name``"
+ return self.execute_command('HGET', name, key)
+
+ def hgetall(self, name):
+ "Return a Python dict of the hash's name/value pairs"
+ return self.execute_command('HGETALL', name)
+
+ def hincrby(self, name, key, amount=1):
+ "Increment the value of ``key`` in hash ``name`` by ``amount``"
+ return self.execute_command('HINCRBY', name, key, amount)
+
+ def hkeys(self, name):
+ "Return the list of keys within hash ``name``"
+ return self.execute_command('HKEYS', name)
+
+ def hlen(self, name):
+ "Return the number of elements in hash ``name``"
+ return self.execute_command('HLEN', name)
+
+ def hset(self, name, key, value):
+ """
+ Set ``key`` to ``value`` within hash ``name``
+ Returns 1 if HSET created a new field, otherwise 0
+ """
+ return self.execute_command('HSET', name, key, value)
+
+ def hmset(self, name, mapping):
+ """
+ Sets each key in the ``mapping`` dict to its corresponding value
+ in the hash ``name``
+ """
+ items = []
+ [items.extend(pair) for pair in mapping.iteritems()]
+ return self.execute_command('HMSET', name, *items)
+
+ def hmget(self, name, keys):
+ "Returns a list of values ordered identically to ``keys``"
+ return self.execute_command('HMGET', name, *keys)
+
+ def hvals(self, name):
+ "Return the list of values within hash ``name``"
+ return self.execute_command('HVALS', name)
+
+
+ # channels
+ def psubscribe(self, patterns):
+ "Subscribe to all channels matching any pattern in ``patterns``"
+ if isinstance(patterns, basestring):
+ patterns = [patterns]
+ response = self.execute_command('PSUBSCRIBE', *patterns)
+ # this is *after* the SUBSCRIBE in order to allow for lazy and broken
+ # connections that need to issue AUTH and SELECT commands
+ self.subscribed = True
+ return response
+
+ def punsubscribe(self, patterns=[]):
+ """
+ Unsubscribe from any channel matching any pattern in ``patterns``.
+ If empty, unsubscribe from all channels.
+ """
+ if isinstance(patterns, basestring):
+ patterns = [patterns]
+ return self.execute_command('PUNSUBSCRIBE', *patterns)
+
+ def subscribe(self, channels):
+ "Subscribe to ``channels``, waiting for messages to be published"
+ if isinstance(channels, basestring):
+ channels = [channels]
+ response = self.execute_command('SUBSCRIBE', *channels)
+ # this is *after* the SUBSCRIBE in order to allow for lazy and broken
+ # connections that need to issue AUTH and SELECT commands
+ self.subscribed = True
+ return response
+
+ def unsubscribe(self, channels=[]):
+ """
+ Unsubscribe from ``channels``. If empty, unsubscribe
+ from all channels
+ """
+ if isinstance(channels, basestring):
+ channels = [channels]
+ return self.execute_command('UNSUBSCRIBE', *channels)
+
+ def publish(self, channel, message):
+ """
+ Publish ``message`` on ``channel``.
+ Returns the number of subscribers the message was delivered to.
+ """
+ return self.execute_command('PUBLISH', channel, message)
+
+ def listen(self):
+ "Listen for messages on channels this client has been subscribed to"
+ while self.subscribed:
+ r = self.parse_response('LISTEN')
+ message_type, channel, message = r[0], r[1], r[2]
+ yield (message_type, channel, message)
+ if message_type == 'unsubscribe' and message == 0:
+ self.subscribed = False
+
+
+class Pipeline(Redis):
+ """
+ Pipelines provide a way to transmit multiple commands to the Redis server
+ in one transmission. This is convenient for batch processing, such as
+ saving all the values in a list to Redis.
+
+ All commands executed within a pipeline are wrapped with MULTI and EXEC
+ calls. This guarantees all commands executed in the pipeline will be
+ executed atomically.
+
+ Any command raising an exception does *not* halt the execution of
+ subsequent commands in the pipeline. Instead, the exception is caught
+ and its instance is placed into the response list returned by execute().
+ Code iterating over the response list should be able to deal with an
+ instance of an exception as a potential value. In general, these will be
+ ResponseError exceptions, such as those raised when issuing a command
+ on a key of a different datatype.
+ """
+ def __init__(self, connection, transaction, charset, errors):
+ self.connection = connection
+ self.transaction = transaction
+ self.encoding = charset
+ self.errors = errors
+ self.subscribed = False # NOTE not in use, but necessary
+ self.reset()
+
+ def reset(self):
+ self.command_stack = []
+
+ def _execute_command(self, command_name, command, **options):
+ """
+ Stage a command to be executed when execute() is next called
+
+ Returns the current Pipeline object back so commands can be
+ chained together, such as:
+
+ pipe = pipe.set('foo', 'bar').incr('baz').decr('bang')
+
+ At some other point, you can then run: pipe.execute(),
+ which will execute all commands queued in the pipe.
+ """
+ # if the command_name is 'AUTH' or 'SELECT', then this command
+ # must have originated after a socket connection and a call to
+ # _setup_connection(). run these commands immediately without
+ # buffering them.
+ if command_name in ('AUTH', 'SELECT'):
+ return super(Pipeline, self)._execute_command(
+ command_name, command, **options)
+ else:
+ self.command_stack.append((command_name, command, options))
+ return self
+
+ def _execute_transaction(self, commands):
+ # wrap the commands in MULTI ... EXEC statements to indicate an
+ # atomic operation
+ all_cmds = ''.join([c for _1, c, _2 in chain(
+ (('', 'MULTI\r\n', ''),),
+ commands,
+ (('', 'EXEC\r\n', ''),)
+ )])
+ self.connection.send(all_cmds, self)
+ # parse off the response for MULTI and all commands prior to EXEC
+ for i in range(len(commands)+1):
+ _ = self.parse_response('_')
+ # parse the EXEC. we want errors returned as items in the response
+ response = self.parse_response('_', catch_errors=True)
+ if len(response) != len(commands):
+ raise ResponseError("Wrong number of response items from "
+ "pipeline execution")
+ # Run any callbacks for the commands run in the pipeline
+ data = []
+ for r, cmd in zip(response, commands):
+ if not isinstance(r, Exception):
+ if cmd[0] in self.RESPONSE_CALLBACKS:
+ r = self.RESPONSE_CALLBACKS[cmd[0]](r, **cmd[2])
+ data.append(r)
+ return data
+
+ def _execute_pipeline(self, commands):
+ # build up all commands into a single request to increase network perf
+ all_cmds = ''.join([c for _1, c, _2 in commands])
+ self.connection.send(all_cmds, self)
+ data = []
+ for command_name, _, options in commands:
+ data.append(
+ self.parse_response(command_name, catch_errors=True, **options)
+ )
+ return data
+
+ def execute(self):
+ "Execute all the commands in the current pipeline"
+ stack = self.command_stack
+ self.reset()
+ if self.transaction:
+ execute = self._execute_transaction
+ else:
+ execute = self._execute_pipeline
+ try:
+ return execute(stack)
+ except ConnectionError:
+ self.connection.disconnect()
+ return execute(stack)
+
+ def select(self, *args, **kwargs):
+ raise RedisError("Cannot select a different database from a pipeline")
+
diff --git a/vendor/redis-py/redis/exceptions.py b/vendor/redis-py/redis/exceptions.py
new file mode 100755
index 0000000000..d3449b6641
--- /dev/null
+++ b/vendor/redis-py/redis/exceptions.py
@@ -0,0 +1,20 @@
+"Core exceptions raised by the Redis client"
+
+class RedisError(Exception):
+ pass
+
+class AuthenticationError(RedisError):
+ pass
+
+class ConnectionError(RedisError):
+ pass
+
+class ResponseError(RedisError):
+ pass
+
+class InvalidResponse(RedisError):
+ pass
+
+class InvalidData(RedisError):
+ pass
+ \ No newline at end of file
diff --git a/vendor/redis-py/setup.py b/vendor/redis-py/setup.py
new file mode 100755
index 0000000000..0660371dfc
--- /dev/null
+++ b/vendor/redis-py/setup.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+"""
+@file setup.py
+@author Andy McCurdy
+@date 2/12/2010
+@brief Setuptools configuration for redis client
+"""
+
+version = '1.36'
+
+sdict = {
+ 'name' : 'redis',
+ 'version' : version,
+ 'description' : 'Python client for Redis key-value store',
+ 'long_description' : 'Python client for Redis key-value store',
+ 'url': 'http://github.com/andymccurdy/redis-py',
+ 'download_url' : 'http://cloud.github.com/downloads/andymccurdy/redis-py/redis-%s.tar.gz' % version,
+ 'author' : 'Andy McCurdy',
+ 'author_email' : 'sedrik@gmail.com',
+ 'maintainer' : 'Andy McCurdy',
+ 'maintainer_email' : 'sedrik@gmail.com',
+ 'keywords' : ['Redis', 'key-value store'],
+ 'license' : 'MIT',
+ 'packages' : ['redis'],
+ 'test_suite' : 'tests.all_tests',
+ 'classifiers' : [
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python'],
+}
+
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+
+setup(**sdict)
+
diff --git a/vendor/redis-py/tests/__init__.py b/vendor/redis-py/tests/__init__.py
new file mode 100755
index 0000000000..45e55b079e
--- /dev/null
+++ b/vendor/redis-py/tests/__init__.py
@@ -0,0 +1,11 @@
+import unittest
+from server_commands import ServerCommandsTestCase
+from connection_pool import ConnectionPoolTestCase
+from pipeline import PipelineTestCase
+
+def all_tests():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ServerCommandsTestCase))
+ suite.addTest(unittest.makeSuite(ConnectionPoolTestCase))
+ suite.addTest(unittest.makeSuite(PipelineTestCase))
+ return suite
diff --git a/vendor/redis-py/tests/connection_pool.py b/vendor/redis-py/tests/connection_pool.py
new file mode 100755
index 0000000000..56f5f43c68
--- /dev/null
+++ b/vendor/redis-py/tests/connection_pool.py
@@ -0,0 +1,53 @@
+import redis
+import threading
+import time
+import unittest
+
+class ConnectionPoolTestCase(unittest.TestCase):
+ def test_multiple_connections(self):
+ # 2 clients to the same host/port/db/pool should use the same connection
+ pool = redis.ConnectionPool()
+ r1 = redis.Redis(host='localhost', port=6379, db=9, connection_pool=pool)
+ r2 = redis.Redis(host='localhost', port=6379, db=9, connection_pool=pool)
+ self.assertEquals(r1.connection, r2.connection)
+
+ # if one of them switches, they should have
+ # separate conncetion objects
+ r2.select(db=10, host='localhost', port=6379)
+ self.assertNotEqual(r1.connection, r2.connection)
+
+ conns = [r1.connection, r2.connection]
+ conns.sort()
+
+ # but returning to the original state shares the object again
+ r2.select(db=9, host='localhost', port=6379)
+ self.assertEquals(r1.connection, r2.connection)
+
+ # the connection manager should still have just 2 connections
+ mgr_conns = pool.get_all_connections()
+ mgr_conns.sort()
+ self.assertEquals(conns, mgr_conns)
+
+ def test_threaded_workers(self):
+ r = redis.Redis(host='localhost', port=6379, db=9)
+ r.set('a', 'foo')
+ r.set('b', 'bar')
+
+ def _info_worker():
+ for i in range(50):
+ _ = r.info()
+ time.sleep(0.01)
+
+ def _keys_worker():
+ for i in range(50):
+ _ = r.keys()
+ time.sleep(0.01)
+
+ t1 = threading.Thread(target=_info_worker)
+ t2 = threading.Thread(target=_keys_worker)
+ t1.start()
+ t2.start()
+
+ for i in [t1, t2]:
+ i.join()
+
diff --git a/vendor/redis-py/tests/pipeline.py b/vendor/redis-py/tests/pipeline.py
new file mode 100755
index 0000000000..dcbfb0a301
--- /dev/null
+++ b/vendor/redis-py/tests/pipeline.py
@@ -0,0 +1,61 @@
+import redis
+import unittest
+
+class PipelineTestCase(unittest.TestCase):
+ def setUp(self):
+ self.client = redis.Redis(host='localhost', port=6379, db=9)
+ self.client.flushdb()
+
+ def tearDown(self):
+ self.client.flushdb()
+
+ def test_pipeline(self):
+ pipe = self.client.pipeline()
+ pipe.set('a', 'a1').get('a').zadd('z', 'z1', 1).zadd('z', 'z2', 4)
+ pipe.zincrby('z', 'z1').zrange('z', 0, 5, withscores=True)
+ self.assertEquals(pipe.execute(),
+ [
+ True,
+ 'a1',
+ True,
+ True,
+ 2.0,
+ [('z1', 2.0), ('z2', 4)],
+ ]
+ )
+
+ def test_invalid_command_in_pipeline(self):
+ # all commands but the invalid one should be excuted correctly
+ self.client['c'] = 'a'
+ pipe = self.client.pipeline()
+ pipe.set('a', 1).set('b', 2).lpush('c', 3).set('d', 4)
+ result = pipe.execute()
+
+ self.assertEquals(result[0], True)
+ self.assertEquals(self.client['a'], '1')
+ self.assertEquals(result[1], True)
+ self.assertEquals(self.client['b'], '2')
+ # we can't lpush to a key that's a string value, so this should
+ # be a ResponseError exception
+ self.assert_(isinstance(result[2], redis.ResponseError))
+ self.assertEquals(self.client['c'], 'a')
+ self.assertEquals(result[3], True)
+ self.assertEquals(self.client['d'], '4')
+
+ # make sure the pipe was restored to a working state
+ self.assertEquals(pipe.set('z', 'zzz').execute(), [True])
+ self.assertEquals(self.client['z'], 'zzz')
+
+ def test_pipeline_cannot_select(self):
+ pipe = self.client.pipeline()
+ self.assertRaises(redis.RedisError,
+ pipe.select, 'localhost', 6379, db=9)
+
+ def test_pipeline_no_transaction(self):
+ pipe = self.client.pipeline(transaction=False)
+ pipe.set('a', 'a1').set('b', 'b1').set('c', 'c1')
+ self.assertEquals(pipe.execute(), [True, True, True])
+ self.assertEquals(self.client['a'], 'a1')
+ self.assertEquals(self.client['b'], 'b1')
+ self.assertEquals(self.client['c'], 'c1')
+
diff --git a/vendor/redis-py/tests/server_commands.py b/vendor/redis-py/tests/server_commands.py
new file mode 100755
index 0000000000..056188aff5
--- /dev/null
+++ b/vendor/redis-py/tests/server_commands.py
@@ -0,0 +1,1092 @@
+import redis
+import unittest
+import datetime
+import threading
+import time
+from distutils.version import StrictVersion
+
+class ServerCommandsTestCase(unittest.TestCase):
+
+ def get_client(self):
+ return redis.Redis(host='localhost', port=6379, db=9)
+
+ def setUp(self):
+ self.client = self.get_client()
+ self.client.flushdb()
+
+ def tearDown(self):
+ self.client.flushdb()
+
+ # GENERAL SERVER COMMANDS
+ def test_dbsize(self):
+ self.client['a'] = 'foo'
+ self.client['b'] = 'bar'
+ self.assertEquals(self.client.dbsize(), 2)
+
+ def test_get_and_set(self):
+ # get and set can't be tested independently of each other
+ self.assertEquals(self.client.get('a'), None)
+ byte_string = 'value'
+ integer = 5
+ unicode_string = unichr(3456) + u'abcd' + unichr(3421)
+ self.assert_(self.client.set('byte_string', byte_string))
+ self.assert_(self.client.set('integer', 5))
+ self.assert_(self.client.set('unicode_string', unicode_string))
+ self.assertEquals(self.client.get('byte_string'), byte_string)
+ self.assertEquals(self.client.get('integer'), str(integer))
+ self.assertEquals(self.client.get('unicode_string').decode('utf-8'), unicode_string)
+
+ def test_getitem_and_setitem(self):
+ self.client['a'] = 'bar'
+ self.assertEquals(self.client['a'], 'bar')
+
+ def test_delete(self):
+ self.assertEquals(self.client.delete('a'), False)
+ self.client['a'] = 'foo'
+ self.assertEquals(self.client.delete('a'), True)
+
+ def test_delitem(self):
+ self.client['a'] = 'foo'
+ del self.client['a']
+ self.assertEquals(self.client['a'], None)
+
+ def test_info(self):
+ self.client['a'] = 'foo'
+ self.client['b'] = 'bar'
+ info = self.client.info()
+ self.assert_(isinstance(info, dict))
+ self.assertEquals(info['db9']['keys'], 2)
+
+ def test_lastsave(self):
+ self.assert_(isinstance(self.client.lastsave(), datetime.datetime))
+
+ def test_ping(self):
+ self.assertEquals(self.client.ping(), True)
+
+
+ # KEYS
+ def test_append(self):
+ # invalid key type
+ self.client.rpush('a', 'a1')
+ self.assertRaises(redis.ResponseError, self.client.append, 'a', 'a1')
+ del self.client['a']
+ # real logic
+ self.assertEquals(self.client.append('a', 'a1'), 2)
+ self.assertEquals(self.client['a'], 'a1')
+ self.assert_(self.client.append('a', 'a2'), 4)
+ self.assertEquals(self.client['a'], 'a1a2')
+
+ def test_decr(self):
+ self.assertEquals(self.client.decr('a'), -1)
+ self.assertEquals(self.client['a'], '-1')
+ self.assertEquals(self.client.decr('a'), -2)
+ self.assertEquals(self.client['a'], '-2')
+ self.assertEquals(self.client.decr('a', amount=5), -7)
+ self.assertEquals(self.client['a'], '-7')
+
+ def test_exists(self):
+ self.assertEquals(self.client.exists('a'), False)
+ self.client['a'] = 'foo'
+ self.assertEquals(self.client.exists('a'), True)
+
+ def test_expire_and_ttl(self):
+ self.assertEquals(self.client.expire('a', 10), False)
+ self.client['a'] = 'foo'
+ self.assertEquals(self.client.expire('a', 10), True)
+ self.assertEquals(self.client.ttl('a'), 10)
+
+ def test_expireat(self):
+ expire_at = datetime.datetime.now() + datetime.timedelta(minutes=1)
+ self.assertEquals(self.client.expireat('a', expire_at), False)
+ self.client['a'] = 'foo'
+ # expire at in unix time
+ expire_at_seconds = int(time.mktime(expire_at.timetuple()))
+ self.assertEquals(self.client.expireat('a', expire_at_seconds), True)
+ self.assertEquals(self.client.ttl('a'), 60)
+ # expire at given a datetime object
+ self.client['b'] = 'bar'
+ self.assertEquals(self.client.expireat('b', expire_at), True)
+ self.assertEquals(self.client.ttl('b'), 60)
+
+ def test_getset(self):
+ self.assertEquals(self.client.getset('a', 'foo'), None)
+ self.assertEquals(self.client.getset('a', 'bar'), 'foo')
+
+ def test_incr(self):
+ self.assertEquals(self.client.incr('a'), 1)
+ self.assertEquals(self.client['a'], '1')
+ self.assertEquals(self.client.incr('a'), 2)
+ self.assertEquals(self.client['a'], '2')
+ self.assertEquals(self.client.incr('a', amount=5), 7)
+ self.assertEquals(self.client['a'], '7')
+
+ def test_keys(self):
+ self.assertEquals(self.client.keys(), [])
+ keys = set(['test_a', 'test_b', 'testc'])
+ for key in keys:
+ self.client[key] = 1
+ self.assertEquals(set(self.client.keys(pattern='test_*')),
+ keys - set(['testc']))
+ self.assertEquals(set(self.client.keys(pattern='test*')), keys)
+
+ def test_mget(self):
+ self.assertEquals(self.client.mget(['a', 'b']), [None, None])
+ self.client['a'] = '1'
+ self.client['b'] = '2'
+ self.client['c'] = '3'
+ self.assertEquals(self.client.mget(['a', 'other', 'b', 'c']),
+ ['1', None, '2', '3'])
+
+ def test_mset(self):
+ d = {'a': '1', 'b': '2', 'c': '3'}
+ self.assert_(self.client.mset(d))
+ for k,v in d.iteritems():
+ self.assertEquals(self.client[k], v)
+
+ def test_msetnx(self):
+ d = {'a': '1', 'b': '2', 'c': '3'}
+ self.assert_(self.client.msetnx(d))
+ d2 = {'a': 'x', 'd': '4'}
+ self.assert_(not self.client.msetnx(d2))
+ for k,v in d.iteritems():
+ self.assertEquals(self.client[k], v)
+ self.assertEquals(self.client['d'], None)
+
+ def test_randomkey(self):
+ self.assertEquals(self.client.randomkey(), None)
+ self.client['a'] = '1'
+ self.client['b'] = '2'
+ self.client['c'] = '3'
+ self.assert_(self.client.randomkey() in ('a', 'b', 'c'))
+
+ def test_rename(self):
+ self.client['a'] = '1'
+ self.assert_(self.client.rename('a', 'b'))
+ self.assertEquals(self.client['a'], None)
+ self.assertEquals(self.client['b'], '1')
+
+ def test_renamenx(self):
+ self.client['a'] = '1'
+ self.client['b'] = '2'
+ self.assert_(not self.client.renamenx('a', 'b'))
+ self.assertEquals(self.client['a'], '1')
+ self.assertEquals(self.client['b'], '2')
+
+ def test_setex(self):
+ self.assertEquals(self.client.setex('a', '1', 60), True)
+ self.assertEquals(self.client['a'], '1')
+ self.assertEquals(self.client.ttl('a'), 60 )
+
+ def test_setnx(self):
+ self.assert_(self.client.setnx('a', '1'))
+ self.assertEquals(self.client['a'], '1')
+ self.assert_(not self.client.setnx('a', '2'))
+ self.assertEquals(self.client['a'], '1')
+
+ def test_substr(self):
+ # invalid key type
+ self.client.rpush('a', 'a1')
+ self.assertRaises(redis.ResponseError, self.client.substr, 'a', 0)
+ del self.client['a']
+ # real logic
+ self.client['a'] = 'abcdefghi'
+ self.assertEquals(self.client.substr('a', 0), 'abcdefghi')
+ self.assertEquals(self.client.substr('a', 2), 'cdefghi')
+ self.assertEquals(self.client.substr('a', 3, 5), 'def')
+ self.assertEquals(self.client.substr('a', 3, -2), 'defgh')
+ self.client['a'] = 123456 # does substr work with ints?
+ self.assertEquals(self.client.substr('a', 2, -2), '345')
+
+ def test_type(self):
+ self.assertEquals(self.client.type('a'), 'none')
+ self.client['a'] = '1'
+ self.assertEquals(self.client.type('a'), 'string')
+ del self.client['a']
+ self.client.lpush('a', '1')
+ self.assertEquals(self.client.type('a'), 'list')
+ del self.client['a']
+ self.client.sadd('a', '1')
+ self.assertEquals(self.client.type('a'), 'set')
+ del self.client['a']
+ self.client.zadd('a', '1', 1)
+ self.assertEquals(self.client.type('a'), 'zset')
+
+ # LISTS
+ def make_list(self, name, l):
+ for i in l:
+ self.client.rpush(name, i)
+
+ def test_blpop(self):
+ self.make_list('a', 'ab')
+ self.make_list('b', 'cd')
+ self.assertEquals(self.client.blpop(['b', 'a'], timeout=1), ['b', 'c'])
+ self.assertEquals(self.client.blpop(['b', 'a'], timeout=1), ['b', 'd'])
+ self.assertEquals(self.client.blpop(['b', 'a'], timeout=1), ['a', 'a'])
+ self.assertEquals(self.client.blpop(['b', 'a'], timeout=1), ['a', 'b'])
+ self.assertEquals(self.client.blpop(['b', 'a'], timeout=1), None)
+
+ def test_brpop(self):
+ self.make_list('a', 'ab')
+ self.make_list('b', 'cd')
+ self.assertEquals(self.client.brpop(['b', 'a'], timeout=1), ['b', 'd'])
+ self.assertEquals(self.client.brpop(['b', 'a'], timeout=1), ['b', 'c'])
+ self.assertEquals(self.client.brpop(['b', 'a'], timeout=1), ['a', 'b'])
+ self.assertEquals(self.client.brpop(['b', 'a'], timeout=1), ['a', 'a'])
+ self.assertEquals(self.client.brpop(['b', 'a'], timeout=1), None)
+
+ def test_lindex(self):
+ # no key
+ self.assertEquals(self.client.lindex('a', '0'), None)
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.lindex, 'a', '0')
+ del self.client['a']
+ # real logic
+ self.make_list('a', 'abc')
+ self.assertEquals(self.client.lindex('a', '0'), 'a')
+ self.assertEquals(self.client.lindex('a', '1'), 'b')
+ self.assertEquals(self.client.lindex('a', '2'), 'c')
+
+ def test_llen(self):
+ # no key
+ self.assertEquals(self.client.llen('a'), 0)
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.llen, 'a')
+ del self.client['a']
+ # real logic
+ self.make_list('a', 'abc')
+ self.assertEquals(self.client.llen('a'), 3)
+
+ def test_lpop(self):
+ # no key
+ self.assertEquals(self.client.lpop('a'), None)
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.lpop, 'a')
+ del self.client['a']
+ # real logic
+ self.make_list('a', 'abc')
+ self.assertEquals(self.client.lpop('a'), 'a')
+ self.assertEquals(self.client.lpop('a'), 'b')
+ self.assertEquals(self.client.lpop('a'), 'c')
+ self.assertEquals(self.client.lpop('a'), None)
+
+ def test_lpush(self):
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.lpush, 'a', 'a')
+ del self.client['a']
+ # real logic
+ version = self.client.info()['redis_version']
+ if StrictVersion(version) >= StrictVersion('1.3.4'):
+ self.assertEqual(1, self.client.lpush('a', 'b'))
+ self.assertEqual(2, self.client.lpush('a', 'a'))
+ else:
+ self.assert_(self.client.lpush('a', 'b'))
+ self.assert_(self.client.lpush('a', 'a'))
+ self.assertEquals(self.client.lindex('a', 0), 'a')
+ self.assertEquals(self.client.lindex('a', 1), 'b')
+
+ def test_lrange(self):
+ # no key
+ self.assertEquals(self.client.lrange('a', 0, 1), [])
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.lrange, 'a', 0, 1)
+ del self.client['a']
+ # real logic
+ self.make_list('a', 'abcde')
+ self.assertEquals(self.client.lrange('a', 0, 2), ['a', 'b', 'c'])
+ self.assertEquals(self.client.lrange('a', 2, 10), ['c', 'd', 'e'])
+
+ def test_lrem(self):
+ # no key
+ self.assertEquals(self.client.lrem('a', 'foo'), 0)
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.lrem, 'a', 'b')
+ del self.client['a']
+ # real logic
+ self.make_list('a', 'aaaa')
+ self.assertEquals(self.client.lrem('a', 'a', 1), 1)
+ self.assertEquals(self.client.lrange('a', 0, 3), ['a', 'a', 'a'])
+ self.assertEquals(self.client.lrem('a', 'a'), 3)
+ # remove all the elements in the list means the key is deleted
+ self.assertEquals(self.client.lrange('a', 0, 1), [])
+
+ def test_lset(self):
+ # no key
+ self.assertRaises(redis.ResponseError, self.client.lset, 'a', 1, 'b')
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.lset, 'a', 1, 'b')
+ del self.client['a']
+ # real logic
+ self.make_list('a', 'abc')
+ self.assertEquals(self.client.lrange('a', 0, 2), ['a', 'b', 'c'])
+ self.assert_(self.client.lset('a', 1, 'd'))
+ self.assertEquals(self.client.lrange('a', 0, 2), ['a', 'd', 'c'])
+
+ def test_ltrim(self):
+ # no key -- TODO: Not sure why this is actually true.
+ self.assert_(self.client.ltrim('a', 0, 2))
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.ltrim, 'a', 0, 2)
+ del self.client['a']
+ # real logic
+ self.make_list('a', 'abc')
+ self.assert_(self.client.ltrim('a', 0, 1))
+ self.assertEquals(self.client.lrange('a', 0, 5), ['a', 'b'])
+
+ def test_lpop(self):
+ # no key
+ self.assertEquals(self.client.lpop('a'), None)
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.lpop, 'a')
+ del self.client['a']
+ # real logic
+ self.make_list('a', 'abc')
+ self.assertEquals(self.client.lpop('a'), 'a')
+ self.assertEquals(self.client.lpop('a'), 'b')
+ self.assertEquals(self.client.lpop('a'), 'c')
+ self.assertEquals(self.client.lpop('a'), None)
+
+ def test_rpop(self):
+ # no key
+ self.assertEquals(self.client.rpop('a'), None)
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.rpop, 'a')
+ del self.client['a']
+ # real logic
+ self.make_list('a', 'abc')
+ self.assertEquals(self.client.rpop('a'), 'c')
+ self.assertEquals(self.client.rpop('a'), 'b')
+ self.assertEquals(self.client.rpop('a'), 'a')
+ self.assertEquals(self.client.rpop('a'), None)
+
+ def test_rpoplpush(self):
+ # no src key
+ self.make_list('b', ['b1'])
+ self.assertEquals(self.client.rpoplpush('a', 'b'), None)
+ # no dest key
+ self.assertEquals(self.client.rpoplpush('b', 'a'), 'b1')
+ self.assertEquals(self.client.lindex('a', 0), 'b1')
+ del self.client['a']
+ del self.client['b']
+ # src key is not a list
+ self.client['a'] = 'a1'
+ self.assertRaises(redis.ResponseError, self.client.rpoplpush, 'a', 'b')
+ del self.client['a']
+ # dest key is not a list
+ self.make_list('a', ['a1'])
+ self.client['b'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.rpoplpush, 'a', 'b')
+ del self.client['a']
+ del self.client['b']
+ # real logic
+ self.make_list('a', ['a1', 'a2', 'a3'])
+ self.make_list('b', ['b1', 'b2', 'b3'])
+ self.assertEquals(self.client.rpoplpush('a', 'b'), 'a3')
+ self.assertEquals(self.client.lrange('a', 0, 2), ['a1', 'a2'])
+ self.assertEquals(self.client.lrange('b', 0, 4),
+ ['a3', 'b1', 'b2', 'b3'])
+
+ def test_rpush(self):
+ # key is not a list
+ self.client['a'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.rpush, 'a', 'a')
+ del self.client['a']
+ # real logic
+ version = self.client.info()['redis_version']
+ if StrictVersion(version) >= StrictVersion('1.3.4'):
+ self.assertEqual(1, self.client.rpush('a', 'a'))
+ self.assertEqual(2, self.client.rpush('a', 'b'))
+ else:
+ self.assert_(self.client.rpush('a', 'a'))
+ self.assert_(self.client.rpush('a', 'b'))
+ self.assertEquals(self.client.lindex('a', 0), 'a')
+ self.assertEquals(self.client.lindex('a', 1), 'b')
+
+ # Set commands
+ def make_set(self, name, l):
+ for i in l:
+ self.client.sadd(name, i)
+
+ def test_sadd(self):
+ # key is not a set
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.sadd, 'a', 'a1')
+ del self.client['a']
+ # real logic
+ members = set(['a1', 'a2', 'a3'])
+ self.make_set('a', members)
+ self.assertEquals(self.client.smembers('a'), members)
+
+ def test_scard(self):
+ # key is not a set
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.scard, 'a')
+ del self.client['a']
+ # real logic
+ self.make_set('a', 'abc')
+ self.assertEquals(self.client.scard('a'), 3)
+
+ def test_sdiff(self):
+ # some key is not a set
+ self.make_set('a', ['a1', 'a2', 'a3'])
+ self.client['b'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.sdiff, ['a', 'b'])
+ del self.client['b']
+ # real logic
+ self.make_set('b', ['b1', 'a2', 'b3'])
+ self.assertEquals(self.client.sdiff(['a', 'b']), set(['a1', 'a3']))
+
+ def test_sdiffstore(self):
+ # some key is not a set
+ self.make_set('a', ['a1', 'a2', 'a3'])
+ self.client['b'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.sdiffstore,
+ 'c', ['a', 'b'])
+ del self.client['b']
+ self.make_set('b', ['b1', 'a2', 'b3'])
+ # dest key always gets overwritten, even if it's not a set, so don't
+ # test for that
+ # real logic
+ self.assertEquals(self.client.sdiffstore('c', ['a', 'b']), 2)
+ self.assertEquals(self.client.smembers('c'), set(['a1', 'a3']))
+
+ def test_sinter(self):
+ # some key is not a set
+ self.make_set('a', ['a1', 'a2', 'a3'])
+ self.client['b'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.sinter, ['a', 'b'])
+ del self.client['b']
+ # real logic
+ self.make_set('b', ['a1', 'b2', 'a3'])
+ self.assertEquals(self.client.sinter(['a', 'b']), set(['a1', 'a3']))
+
+ def test_sinterstore(self):
+ # some key is not a set
+ self.make_set('a', ['a1', 'a2', 'a3'])
+ self.client['b'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.sinterstore,
+ 'c', ['a', 'b'])
+ del self.client['b']
+ self.make_set('b', ['a1', 'b2', 'a3'])
+ # dest key always gets overwritten, even if it's not a set, so don't
+ # test for that
+ # real logic
+ self.assertEquals(self.client.sinterstore('c', ['a', 'b']), 2)
+ self.assertEquals(self.client.smembers('c'), set(['a1', 'a3']))
+
+ def test_sismember(self):
+ # key is not a set
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.sismember, 'a', 'a')
+ del self.client['a']
+ # real logic
+ self.make_set('a', 'abc')
+ self.assertEquals(self.client.sismember('a', 'a'), True)
+ self.assertEquals(self.client.sismember('a', 'b'), True)
+ self.assertEquals(self.client.sismember('a', 'c'), True)
+ self.assertEquals(self.client.sismember('a', 'd'), False)
+
+ def test_smembers(self):
+ # key is not a set
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.smembers, 'a')
+ del self.client['a']
+ # set doesn't exist
+ self.assertEquals(self.client.smembers('a'), set())
+ # real logic
+ self.make_set('a', 'abc')
+ self.assertEquals(self.client.smembers('a'), set(['a', 'b', 'c']))
+
+ def test_smove(self):
+ # src key is not set
+ self.make_set('b', ['b1', 'b2'])
+ self.assertEquals(self.client.smove('a', 'b', 'a1'), 0)
+ # src key is not a set
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.smove,
+ 'a', 'b', 'a1')
+ del self.client['a']
+ self.make_set('a', ['a1', 'a2'])
+ # dest key is not a set
+ del self.client['b']
+ self.client['b'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.smove,
+ 'a', 'b', 'a1')
+ del self.client['b']
+ self.make_set('b', ['b1', 'b2'])
+ # real logic
+ self.assert_(self.client.smove('a', 'b', 'a1'))
+ self.assertEquals(self.client.smembers('a'), set(['a2']))
+ self.assertEquals(self.client.smembers('b'), set(['b1', 'b2', 'a1']))
+
+ def test_spop(self):
+ # key is not set
+ self.assertEquals(self.client.spop('a'), None)
+ # key is not a set
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.spop, 'a')
+ del self.client['a']
+ # real logic
+ self.make_set('a', 'abc')
+ value = self.client.spop('a')
+ self.assert_(value in 'abc')
+ self.assertEquals(self.client.smembers('a'), set('abc') - set(value))
+
+ def test_srandmember(self):
+ # key is not set
+ self.assertEquals(self.client.srandmember('a'), None)
+ # key is not a set
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.srandmember, 'a')
+ del self.client['a']
+ # real logic
+ self.make_set('a', 'abc')
+ self.assert_(self.client.srandmember('a') in 'abc')
+
+ def test_srem(self):
+ # key is not set
+ self.assertEquals(self.client.srem('a', 'a'), False)
+ # key is not a set
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.srem, 'a', 'a')
+ del self.client['a']
+ # real logic
+ self.make_set('a', 'abc')
+ self.assertEquals(self.client.srem('a', 'd'), False)
+ self.assertEquals(self.client.srem('a', 'b'), True)
+ self.assertEquals(self.client.smembers('a'), set('ac'))
+
+ def test_sunion(self):
+ # some key is not a set
+ self.make_set('a', ['a1', 'a2', 'a3'])
+ self.client['b'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.sunion, ['a', 'b'])
+ del self.client['b']
+ # real logic
+ self.make_set('b', ['a1', 'b2', 'a3'])
+ self.assertEquals(self.client.sunion(['a', 'b']),
+ set(['a1', 'a2', 'a3', 'b2']))
+
+ def test_sunionstore(self):
+ # some key is not a set
+ self.make_set('a', ['a1', 'a2', 'a3'])
+ self.client['b'] = 'b'
+ self.assertRaises(redis.ResponseError, self.client.sunionstore,
+ 'c', ['a', 'b'])
+ del self.client['b']
+ self.make_set('b', ['a1', 'b2', 'a3'])
+ # dest key always gets overwritten, even if it's not a set, so don't
+ # test for that
+ # real logic
+ self.assertEquals(self.client.sunionstore('c', ['a', 'b']), 4)
+ self.assertEquals(self.client.smembers('c'),
+ set(['a1', 'a2', 'a3', 'b2']))
+
+ # SORTED SETS
+ def make_zset(self, name, d):
+ for k,v in d.items():
+ self.client.zadd(name, k, v)
+
+ def test_zadd(self):
+ self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3})
+ self.assertEquals(self.client.zrange('a', 0, 3), ['a1', 'a2', 'a3'])
+
+ def test_zcard(self):
+ # key is not a zset
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.zcard, 'a')
+ del self.client['a']
+ # real logic
+ self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3})
+ self.assertEquals(self.client.zcard('a'), 3)
+
+ def test_zincrby(self):
+ # key is not a zset
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.zincrby, 'a', 'a1')
+ del self.client['a']
+ # real logic
+ self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3})
+ self.assertEquals(self.client.zincrby('a', 'a2'), 3.0)
+ self.assertEquals(self.client.zincrby('a', 'a3', amount=5), 8.0)
+ self.assertEquals(self.client.zscore('a', 'a2'), 3.0)
+ self.assertEquals(self.client.zscore('a', 'a3'), 8.0)
+
+ def test_zinter(self):
+ self.make_zset('a', {'a1': 1, 'a2': 1, 'a3': 1})
+ self.make_zset('b', {'a1': 2, 'a3': 2, 'a4': 2})
+ self.make_zset('c', {'a1': 6, 'a3': 5, 'a4': 4})
+
+ # sum, no weight
+ self.assert_(self.client.zinter('z', ['a', 'b', 'c']))
+ self.assertEquals(
+ self.client.zrange('z', 0, -1, withscores=True),
+ [('a3', 8), ('a1', 9)]
+ )
+
+ # max, no weight
+ self.assert_(self.client.zinter('z', ['a', 'b', 'c'], aggregate='MAX'))
+ self.assertEquals(
+ self.client.zrange('z', 0, -1, withscores=True),
+ [('a3', 5), ('a1', 6)]
+ )
+
+ # with weight
+ self.assert_(self.client.zinter('z', {'a': 1, 'b': 2, 'c': 3}))
+ self.assertEquals(
+ self.client.zrange('z', 0, -1, withscores=True),
+ [('a3', 20), ('a1', 23)]
+ )
+
+
+ def test_zrange(self):
+ # key is not a zset
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.zrange, 'a', 0, 1)
+ del self.client['a']
+ # real logic
+ self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3})
+ self.assertEquals(self.client.zrange('a', 0, 1), ['a1', 'a2'])
+ self.assertEquals(self.client.zrange('a', 1, 2), ['a2', 'a3'])
+ self.assertEquals(self.client.zrange('a', 0, 1, withscores=True),
+ [('a1', 1.0), ('a2', 2.0)])
+ self.assertEquals(self.client.zrange('a', 1, 2, withscores=True),
+ [('a2', 2.0), ('a3', 3.0)])
+ # a non existant key should return empty list
+ self.assertEquals(self.client.zrange('b', 0, 1, withscores=True), [])
+
+
+ def test_zrangebyscore(self):
+ # key is not a zset
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.zrangebyscore,
+ 'a', 0, 1)
+ del self.client['a']
+ # real logic
+ self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5})
+ self.assertEquals(self.client.zrangebyscore('a', 2, 4),
+ ['a2', 'a3', 'a4'])
+ self.assertEquals(self.client.zrangebyscore('a', 2, 4, start=1, num=2),
+ ['a3', 'a4'])
+ self.assertEquals(self.client.zrangebyscore('a', 2, 4, withscores=True),
+ [('a2', 2.0), ('a3', 3.0), ('a4', 4.0)])
+ # a non existant key should return empty list
+ self.assertEquals(self.client.zrangebyscore('b', 0, 1, withscores=True), [])
+
+ def test_zrank(self):
+ # key is not a zset
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.zrank, 'a', 'a4')
+ del self.client['a']
+ # real logic
+ self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5})
+ self.assertEquals(self.client.zrank('a', 'a1'), 0)
+ self.assertEquals(self.client.zrank('a', 'a2'), 1)
+ self.assertEquals(self.client.zrank('a', 'a3'), 2)
+ self.assertEquals(self.client.zrank('a', 'a4'), 3)
+ self.assertEquals(self.client.zrank('a', 'a5'), 4)
+ # non-existent value in zset
+ self.assertEquals(self.client.zrank('a', 'a6'), None)
+
+ def test_zrem(self):
+ # key is not a zset
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.zrem, 'a', 'a1')
+ del self.client['a']
+ # real logic
+ self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3})
+ self.assertEquals(self.client.zrem('a', 'a2'), True)
+ self.assertEquals(self.client.zrange('a', 0, 5), ['a1', 'a3'])
+ self.assertEquals(self.client.zrem('a', 'b'), False)
+ self.assertEquals(self.client.zrange('a', 0, 5), ['a1', 'a3'])
+
+ def test_zremrangebyscore(self):
+ # key is not a zset
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.zremrangebyscore,
+ 'a', 0, 1)
+ del self.client['a']
+ # real logic
+ self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5})
+ self.assertEquals(self.client.zremrangebyscore('a', 2, 4), 3)
+ self.assertEquals(self.client.zrange('a', 0, 5), ['a1', 'a5'])
+ self.assertEquals(self.client.zremrangebyscore('a', 2, 4), 0)
+ self.assertEquals(self.client.zrange('a', 0, 5), ['a1', 'a5'])
+
+ def test_zrevrange(self):
+ # key is not a zset
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.zrevrange,
+ 'a', 0, 1)
+ del self.client['a']
+ # real logic
+ self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3})
+ self.assertEquals(self.client.zrevrange('a', 0, 1), ['a3', 'a2'])
+ self.assertEquals(self.client.zrevrange('a', 1, 2), ['a2', 'a1'])
+ self.assertEquals(self.client.zrevrange('a', 0, 1, withscores=True),
+ [('a3', 3.0), ('a2', 2.0)])
+ self.assertEquals(self.client.zrevrange('a', 1, 2, withscores=True),
+ [('a2', 2.0), ('a1', 1.0)])
+ # a non existant key should return empty list
+ self.assertEquals(self.client.zrange('b', 0, 1, withscores=True), [])
+
+ def test_zrevrank(self):
+ # key is not a zset
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.zrevrank, 'a', 'a4')
+ del self.client['a']
+ # real logic
+ self.make_zset('a', {'a1': 5, 'a2': 4, 'a3': 3, 'a4': 2, 'a5': 1})
+ self.assertEquals(self.client.zrevrank('a', 'a1'), 0)
+ self.assertEquals(self.client.zrevrank('a', 'a2'), 1)
+ self.assertEquals(self.client.zrevrank('a', 'a3'), 2)
+ self.assertEquals(self.client.zrevrank('a', 'a4'), 3)
+ self.assertEquals(self.client.zrevrank('a', 'a5'), 4)
+
+ def test_zscore(self):
+ # key is not a zset
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.zscore, 'a', 'a1')
+ del self.client['a']
+ # real logic
+ self.make_zset('a', {'a1': 0, 'a2': 1, 'a3': 2})
+ self.assertEquals(self.client.zscore('a', 'a1'), 0.0)
+ self.assertEquals(self.client.zscore('a', 'a2'), 1.0)
+ # test a non-existant member
+ self.assertEquals(self.client.zscore('a', 'a4'), None)
+
+ def test_zunion(self):
+ self.make_zset('a', {'a1': 1, 'a2': 1, 'a3': 1})
+ self.make_zset('b', {'a1': 2, 'a3': 2, 'a4': 2})
+ self.make_zset('c', {'a1': 6, 'a4': 5, 'a5': 4})
+
+ # sum, no weight
+ self.assert_(self.client.zunion('z', ['a', 'b', 'c']))
+ self.assertEquals(
+ self.client.zrange('z', 0, -1, withscores=True),
+ [('a2', 1), ('a3', 3), ('a5', 4), ('a4', 7), ('a1', 9)]
+ )
+
+ # max, no weight
+ self.assert_(self.client.zunion('z', ['a', 'b', 'c'], aggregate='MAX'))
+ self.assertEquals(
+ self.client.zrange('z', 0, -1, withscores=True),
+ [('a2', 1), ('a3', 2), ('a5', 4), ('a4', 5), ('a1', 6)]
+ )
+
+ # with weight
+ self.assert_(self.client.zunion('z', {'a': 1, 'b': 2, 'c': 3}))
+ self.assertEquals(
+ self.client.zrange('z', 0, -1, withscores=True),
+ [('a2', 1), ('a3', 5), ('a5', 12), ('a4', 19), ('a1', 23)]
+ )
+
+
+ # HASHES
+ def make_hash(self, key, d):
+ for k,v in d.iteritems():
+ self.client.hset(key, k, v)
+
+ def test_hget_and_hset(self):
+ # key is not a hash
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.hget, 'a', 'a1')
+ del self.client['a']
+ # no key
+ self.assertEquals(self.client.hget('a', 'a1'), None)
+ # real logic
+ self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3})
+ self.assertEquals(self.client.hget('a', 'a1'), '1')
+ self.assertEquals(self.client.hget('a', 'a2'), '2')
+ self.assertEquals(self.client.hget('a', 'a3'), '3')
+ self.assertEquals(self.client.hset('a', 'a2', 5), 0)
+ self.assertEquals(self.client.hget('a', 'a2'), '5')
+ self.assertEquals(self.client.hset('a', 'a4', 4), 1)
+ self.assertEquals(self.client.hget('a', 'a4'), '4')
+ # key inside of hash that doesn't exist returns null value
+ self.assertEquals(self.client.hget('a', 'b'), None)
+
+ def test_hmset(self):
+ d = {'a': '1', 'b': '2', 'c': '3'}
+ self.assert_(self.client.hmset('foo', d))
+ self.assertEqual(self.client.hgetall('foo'), d)
+ self.assertRaises(redis.ResponseError, self.client.hmset, 'foo', {})
+
+ def test_hmget(self):
+ d = {'a': 1, 'b': 2, 'c': 3}
+ self.assert_(self.client.hmset('foo', d))
+ self.assertEqual(self.client.hmget('foo', ['a', 'b', 'c']), ['1', '2', '3'])
+ self.assertEqual(self.client.hmget('foo', ['a', 'c']), ['1', '3'])
+
+ def test_hmget_empty(self):
+ self.assertEqual(self.client.hmget('foo', ['a', 'b']), [None, None])
+
+ def test_hmget_no_keys(self):
+ self.assertRaises(redis.ResponseError, self.client.hmget, 'foo', [])
+
+ def test_hdel(self):
+ # key is not a hash
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.hdel, 'a', 'a1')
+ del self.client['a']
+ # no key
+ self.assertEquals(self.client.hdel('a', 'a1'), False)
+ # real logic
+ self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3})
+ self.assertEquals(self.client.hget('a', 'a2'), '2')
+ self.assert_(self.client.hdel('a', 'a2'))
+ self.assertEquals(self.client.hget('a', 'a2'), None)
+
+ def test_hexists(self):
+ # key is not a hash
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.hexists, 'a', 'a1')
+ del self.client['a']
+ # no key
+ self.assertEquals(self.client.hexists('a', 'a1'), False)
+ # real logic
+ self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3})
+ self.assertEquals(self.client.hexists('a', 'a1'), True)
+ self.assertEquals(self.client.hexists('a', 'a4'), False)
+ self.client.hdel('a', 'a1')
+ self.assertEquals(self.client.hexists('a', 'a1'), False)
+
+ def test_hgetall(self):
+ # key is not a hash
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.hgetall, 'a')
+ del self.client['a']
+ # no key
+ self.assertEquals(self.client.hgetall('a'), {})
+ # real logic
+ h = {'a1': '1', 'a2': '2', 'a3': '3'}
+ self.make_hash('a', h)
+ remote_hash = self.client.hgetall('a')
+ self.assertEquals(h, remote_hash)
+
+ def test_hincrby(self):
+ # key is not a hash
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.hincrby, 'a', 'a1')
+ del self.client['a']
+ # no key should create the hash and incr the key's value to 1
+ self.assertEquals(self.client.hincrby('a', 'a1'), 1)
+ # real logic
+ self.assertEquals(self.client.hincrby('a', 'a1'), 2)
+ self.assertEquals(self.client.hincrby('a', 'a1', amount=2), 4)
+ # negative values decrement
+ self.assertEquals(self.client.hincrby('a', 'a1', amount=-3), 1)
+ # hash that exists, but key that doesn't
+ self.assertEquals(self.client.hincrby('a', 'a2', amount=3), 3)
+ # finally a key that's not an int
+ self.client.hset('a', 'a3', 'foo')
+ self.assertRaises(redis.ResponseError, self.client.hincrby, 'a', 'a3')
+
+
+ def test_hkeys(self):
+ # key is not a hash
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.hkeys, 'a')
+ del self.client['a']
+ # no key
+ self.assertEquals(self.client.hkeys('a'), [])
+ # real logic
+ h = {'a1': '1', 'a2': '2', 'a3': '3'}
+ self.make_hash('a', h)
+ keys = h.keys()
+ keys.sort()
+ remote_keys = self.client.hkeys('a')
+ remote_keys.sort()
+ self.assertEquals(keys, remote_keys)
+
+ def test_hlen(self):
+ # key is not a hash
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.hlen, 'a')
+ del self.client['a']
+ # no key
+ self.assertEquals(self.client.hlen('a'), 0)
+ # real logic
+ self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3})
+ self.assertEquals(self.client.hlen('a'), 3)
+ self.client.hdel('a', 'a3')
+ self.assertEquals(self.client.hlen('a'), 2)
+
+ def test_hvals(self):
+ # key is not a hash
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.hvals, 'a')
+ del self.client['a']
+ # no key
+ self.assertEquals(self.client.hvals('a'), [])
+ # real logic
+ h = {'a1': '1', 'a2': '2', 'a3': '3'}
+ self.make_hash('a', h)
+ vals = h.values()
+ vals.sort()
+ remote_vals = self.client.hvals('a')
+ remote_vals.sort()
+ self.assertEquals(vals, remote_vals)
+
+ # SORT
+ def test_sort_bad_key(self):
+ # key is not set
+ self.assertEquals(self.client.sort('a'), [])
+ # key is a string value
+ self.client['a'] = 'a'
+ self.assertRaises(redis.ResponseError, self.client.sort, 'a')
+ del self.client['a']
+
+ def test_sort_basic(self):
+ self.make_list('a', '3214')
+ self.assertEquals(self.client.sort('a'), ['1', '2', '3', '4'])
+
+ def test_sort_limited(self):
+ self.make_list('a', '3214')
+ self.assertEquals(self.client.sort('a', start=1, num=2), ['2', '3'])
+
+ def test_sort_by(self):
+ self.client['score:1'] = 8
+ self.client['score:2'] = 3
+ self.client['score:3'] = 5
+ self.make_list('a_values', '123')
+ self.assertEquals(self.client.sort('a_values', by='score:*'),
+ ['2', '3', '1'])
+
+ def test_sort_get(self):
+ self.client['user:1'] = 'u1'
+ self.client['user:2'] = 'u2'
+ self.client['user:3'] = 'u3'
+ self.make_list('a', '231')
+ self.assertEquals(self.client.sort('a', get='user:*'),
+ ['u1', 'u2', 'u3'])
+
+ def test_sort_desc(self):
+ self.make_list('a', '231')
+ self.assertEquals(self.client.sort('a', desc=True), ['3', '2', '1'])
+
+ def test_sort_alpha(self):
+ self.make_list('a', 'ecbda')
+ self.assertEquals(self.client.sort('a', alpha=True),
+ ['a', 'b', 'c', 'd', 'e'])
+
+ def test_sort_store(self):
+ self.make_list('a', '231')
+ self.assertEquals(self.client.sort('a', store='sorted_values'), 3)
+ self.assertEquals(self.client.lrange('sorted_values', 0, 5),
+ ['1', '2', '3'])
+
+ def test_sort_all_options(self):
+ self.client['user:1:username'] = 'zeus'
+ self.client['user:2:username'] = 'titan'
+ self.client['user:3:username'] = 'hermes'
+ self.client['user:4:username'] = 'hercules'
+ self.client['user:5:username'] = 'apollo'
+ self.client['user:6:username'] = 'athena'
+ self.client['user:7:username'] = 'hades'
+ self.client['user:8:username'] = 'dionysus'
+
+ self.client['user:1:favorite_drink'] = 'yuengling'
+ self.client['user:2:favorite_drink'] = 'rum'
+ self.client['user:3:favorite_drink'] = 'vodka'
+ self.client['user:4:favorite_drink'] = 'milk'
+ self.client['user:5:favorite_drink'] = 'pinot noir'
+ self.client['user:6:favorite_drink'] = 'water'
+ self.client['user:7:favorite_drink'] = 'gin'
+ self.client['user:8:favorite_drink'] = 'apple juice'
+
+ self.make_list('gods', '12345678')
+ num = self.client.sort('gods', start=2, num=4, by='user:*:username',
+ get='user:*:favorite_drink', desc=True, alpha=True, store='sorted')
+ self.assertEquals(num, 4)
+ self.assertEquals(self.client.lrange('sorted', 0, 10),
+ ['vodka', 'milk', 'gin', 'apple juice'])
+
+ # PUBSUB
+ def test_pubsub(self):
+ # create a new client to not polute the existing one
+ r = self.get_client()
+ channels = ('a1', 'a2', 'a3')
+ for c in channels:
+ r.subscribe(c)
+ channels_to_publish_to = channels + ('a4',)
+ messages_per_channel = 4
+ def publish():
+ for i in range(messages_per_channel):
+ for c in channels_to_publish_to:
+ self.client.publish(c, 'a message')
+ time.sleep(0.01)
+ t = threading.Thread(target=publish)
+ messages = []
+ # should receive a message for each subscribe command
+ # plus a message for each iteration of the loop * num channels
+ num_messages_to_expect = len(channels) + \
+ (messages_per_channel*len(channels))
+ thread_started = False
+ for msg in r.listen():
+ if not thread_started:
+ # start the thread delayed so that we are intermingling
+ # publish commands with pulling messsages off the socket
+ # with subscribe
+ thread_started = True
+ t.start()
+ messages.append(msg)
+ if len(messages) == num_messages_to_expect:
+ break
+ sent_types, sent_channels = {}, {}
+ for msg_type, channel, _ in messages:
+ sent_types.setdefault(msg_type, 0)
+ sent_types[msg_type] += 1
+ if msg_type == 'message':
+ sent_channels.setdefault(channel, 0)
+ sent_channels[channel] += 1
+ for channel in channels:
+ self.assertEquals(sent_channels[channel], messages_per_channel)
+ self.assert_(channel in channels)
+ self.assertEquals(sent_types['subscribe'], len(channels))
+ self.assertEquals(sent_types['message'],
+ len(channels) * messages_per_channel)
+
+ ## BINARY SAFE
+ # TODO add more tests
+ def test_binary_get_set(self):
+ self.assertTrue(self.client.set(' foo bar ', '123'))
+ self.assertEqual(self.client.get(' foo bar '), '123')
+
+ self.assertTrue(self.client.set(' foo\r\nbar\r\n ', '456'))
+ self.assertEqual(self.client.get(' foo\r\nbar\r\n '), '456')
+
+ self.assertTrue(self.client.set(' \r\n\t\x07\x13 ', '789'))
+ self.assertEqual(self.client.get(' \r\n\t\x07\x13 '), '789')
+
+ self.assertEqual(sorted(self.client.keys('*')), [' \r\n\t\x07\x13 ', ' foo\r\nbar\r\n ', ' foo bar '])
+
+ self.assertTrue(self.client.delete(' foo bar '))
+ self.assertTrue(self.client.delete(' foo\r\nbar\r\n '))
+ self.assertTrue(self.client.delete(' \r\n\t\x07\x13 '))
+
+ def test_binary_lists(self):
+ mapping = {'foo bar': '123',
+ 'foo\r\nbar\r\n': '456',
+ 'foo\tbar\x07': '789',
+ }
+ # fill in lists
+ for key, value in mapping.iteritems():
+ for c in value:
+ self.assertTrue(self.client.rpush(key, c))
+
+ # check that KEYS returns all the keys as they are
+ self.assertEqual(sorted(self.client.keys('*')), sorted(mapping.keys()))
+
+ # check that it is possible to get list content by key name
+ for key in mapping.keys():
+ self.assertEqual(self.client.lrange(key, 0, -1), list(mapping[key]))
diff --git a/vendor/tornado/MANIFEST.in b/vendor/tornado/MANIFEST.in
new file mode 100644
index 0000000000..c7a51e4094
--- /dev/null
+++ b/vendor/tornado/MANIFEST.in
@@ -0,0 +1,2 @@
+recursive-include demos *.py *.yaml *.html *.css *.png *.js *.xml *.sql README
+include tornado/epoll.c
diff --git a/vendor/tornado/README b/vendor/tornado/README
new file mode 100644
index 0000000000..d504022243
--- /dev/null
+++ b/vendor/tornado/README
@@ -0,0 +1,27 @@
+Tornado
+=======
+Tornado is an open source version of the scalable, non-blocking web server
+and and tools that power FriendFeed. Documentation and downloads are
+available at http://www.tornadoweb.org/
+
+Tornado is licensed under the Apache Licence, Version 2.0
+(http://www.apache.org/licenses/LICENSE-2.0.html).
+
+Installation
+============
+To install:
+
+ python setup.py build
+ sudo python setup.py install
+
+Tornado has been tested on Python 2.5 and 2.6. To use all of the features
+of Tornado, you need to have PycURL and a JSON library like simplejson
+installed.
+
+On Mac OS X, you can install the packages with:
+
+ sudo easy_install setuptools pycurl==7.16.2.1 simplejson
+
+On Ubuntu Linux, you can install the packages with:
+
+ sudo apt-get install python-pycurl python-simplejson
diff --git a/vendor/tornado/demos/appengine/README b/vendor/tornado/demos/appengine/README
new file mode 100644
index 0000000000..e4aead6701
--- /dev/null
+++ b/vendor/tornado/demos/appengine/README
@@ -0,0 +1,48 @@
+Running the Tornado AppEngine example
+=====================================
+This example is designed to run in Google AppEngine, so there are a couple
+of steps to get it running. You can download the Google AppEngine Python
+development environment at http://code.google.com/appengine/downloads.html.
+
+1. Link or copy the tornado code directory into this directory:
+
+ ln -s ../../tornado tornado
+
+ AppEngine doesn't use the Python modules installed on this machine.
+ You need to have the 'tornado' module copied or linked for AppEngine
+ to find it.
+
+3. Install and run dev_appserver
+
+ If you don't already have the App Engine SDK, download it from
+ http://code.google.com/appengine/downloads.html
+
+ To start the tornado demo, run the dev server on this directory:
+
+ dev_appserver.py .
+
+4. Visit http://localhost:8080/ in your browser
+
+ If you sign in as an administrator, you will be able to create and
+ edit blog posts. If you sign in as anybody else, you will only see
+ the existing blog posts.
+
+
+If you want to deploy the blog in production:
+
+1. Register a new appengine application and put its id in app.yaml
+
+ First register a new application at http://appengine.google.com/.
+ Then edit app.yaml in this directory and change the "application"
+ setting from "tornado-appenginge" to your new application id.
+
+2. Deploy to App Engine
+
+ If you registered an application id, you can now upload your new
+ Tornado blog by running this command:
+
+ appcfg update .
+
+ After that, visit application_id.appspot.com, where application_id
+ is the application you registered.
+
diff --git a/vendor/tornado/demos/appengine/app.yaml b/vendor/tornado/demos/appengine/app.yaml
new file mode 100644
index 0000000000..2d00c586dd
--- /dev/null
+++ b/vendor/tornado/demos/appengine/app.yaml
@@ -0,0 +1,11 @@
+application: tornado-appengine
+version: 1
+runtime: python
+api_version: 1
+
+handlers:
+- url: /static/
+ static_dir: static
+
+- url: /.*
+ script: blog.py
diff --git a/vendor/tornado/demos/appengine/blog.py b/vendor/tornado/demos/appengine/blog.py
new file mode 100644
index 0000000000..ccaabd5392
--- /dev/null
+++ b/vendor/tornado/demos/appengine/blog.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import functools
+import markdown
+import os.path
+import re
+import tornado.web
+import tornado.wsgi
+import unicodedata
+import wsgiref.handlers
+
+from google.appengine.api import users
+from google.appengine.ext import db
+
+
+class Entry(db.Model):
+ """A single blog entry."""
+ author = db.UserProperty()
+ title = db.StringProperty(required=True)
+ slug = db.StringProperty(required=True)
+ markdown = db.TextProperty(required=True)
+ html = db.TextProperty(required=True)
+ published = db.DateTimeProperty(auto_now_add=True)
+ updated = db.DateTimeProperty(auto_now=True)
+
+
+def administrator(method):
+ """Decorate with this method to restrict to site admins."""
+ @functools.wraps(method)
+ def wrapper(self, *args, **kwargs):
+ if not self.current_user:
+ if self.request.method == "GET":
+ self.redirect(self.get_login_url())
+ return
+ raise tornado.web.HTTPError(403)
+ elif not self.current_user.administrator:
+ if self.request.method == "GET":
+ self.redirect("/")
+ return
+ raise tornado.web.HTTPError(403)
+ else:
+ return method(self, *args, **kwargs)
+ return wrapper
+
+
+class BaseHandler(tornado.web.RequestHandler):
+ """Implements Google Accounts authentication methods."""
+ def get_current_user(self):
+ user = users.get_current_user()
+ if user: user.administrator = users.is_current_user_admin()
+ return user
+
+ def get_login_url(self):
+ return users.create_login_url(self.request.uri)
+
+ def render_string(self, template_name, **kwargs):
+ # Let the templates access the users module to generate login URLs
+ return tornado.web.RequestHandler.render_string(
+ self, template_name, users=users, **kwargs)
+
+
+class HomeHandler(BaseHandler):
+ def get(self):
+ entries = db.Query(Entry).order('-published').fetch(limit=5)
+ if not entries:
+ if not self.current_user or self.current_user.administrator:
+ self.redirect("/compose")
+ return
+ self.render("home.html", entries=entries)
+
+
+class EntryHandler(BaseHandler):
+ def get(self, slug):
+ entry = db.Query(Entry).filter("slug =", slug).get()
+ if not entry: raise tornado.web.HTTPError(404)
+ self.render("entry.html", entry=entry)
+
+
+class ArchiveHandler(BaseHandler):
+ def get(self):
+ entries = db.Query(Entry).order('-published')
+ self.render("archive.html", entries=entries)
+
+
+class FeedHandler(BaseHandler):
+ def get(self):
+ entries = db.Query(Entry).order('-published').fetch(limit=10)
+ self.set_header("Content-Type", "application/atom+xml")
+ self.render("feed.xml", entries=entries)
+
+
+class ComposeHandler(BaseHandler):
+ @administrator
+ def get(self):
+ key = self.get_argument("key", None)
+ entry = Entry.get(key) if key else None
+ self.render("compose.html", entry=entry)
+
+ @administrator
+ def post(self):
+ key = self.get_argument("key", None)
+ if key:
+ entry = Entry.get(key)
+ entry.title = self.get_argument("title")
+ entry.markdown = self.get_argument("markdown")
+ entry.html = markdown.markdown(self.get_argument("markdown"))
+ else:
+ title = self.get_argument("title")
+ slug = unicodedata.normalize("NFKD", title).encode(
+ "ascii", "ignore")
+ slug = re.sub(r"[^\w]+", " ", slug)
+ slug = "-".join(slug.lower().strip().split())
+ if not slug: slug = "entry"
+ while True:
+ existing = db.Query(Entry).filter("slug =", slug).get()
+ if not existing or str(existing.key()) == key:
+ break
+ slug += "-2"
+ entry = Entry(
+ author=self.current_user,
+ title=title,
+ slug=slug,
+ markdown=self.get_argument("markdown"),
+ html=markdown.markdown(self.get_argument("markdown")),
+ )
+ entry.put()
+ self.redirect("/entry/" + entry.slug)
+
+
+class EntryModule(tornado.web.UIModule):
+ def render(self, entry):
+ return self.render_string("modules/entry.html", entry=entry)
+
+
+settings = {
+ "blog_title": u"Tornado Blog",
+ "template_path": os.path.join(os.path.dirname(__file__), "templates"),
+ "ui_modules": {"Entry": EntryModule},
+ "xsrf_cookies": True,
+}
+application = tornado.wsgi.WSGIApplication([
+ (r"/", HomeHandler),
+ (r"/archive", ArchiveHandler),
+ (r"/feed", FeedHandler),
+ (r"/entry/([^/]+)", EntryHandler),
+ (r"/compose", ComposeHandler),
+], **settings)
+
+
+def main():
+ wsgiref.handlers.CGIHandler().run(application)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/vendor/tornado/demos/appengine/markdown.py b/vendor/tornado/demos/appengine/markdown.py
new file mode 100644
index 0000000000..59ba731bf0
--- /dev/null
+++ b/vendor/tornado/demos/appengine/markdown.py
@@ -0,0 +1,1877 @@
+#!/usr/bin/env python
+# Copyright (c) 2007-2008 ActiveState Corp.
+# License: MIT (http://www.opensource.org/licenses/mit-license.php)
+
+r"""A fast and complete Python implementation of Markdown.
+
+[from http://daringfireball.net/projects/markdown/]
+> Markdown is a text-to-HTML filter; it translates an easy-to-read /
+> easy-to-write structured text format into HTML. Markdown's text
+> format is most similar to that of plain text email, and supports
+> features such as headers, *emphasis*, code blocks, blockquotes, and
+> links.
+>
+> Markdown's syntax is designed not as a generic markup language, but
+> specifically to serve as a front-end to (X)HTML. You can use span-level
+> HTML tags anywhere in a Markdown document, and you can use block level
+> HTML tags (like <div> and <table> as well).
+
+Module usage:
+
+ >>> import markdown2
+ >>> markdown2.markdown("*boo!*") # or use `html = markdown_path(PATH)`
+ u'<p><em>boo!</em></p>\n'
+
+ >>> markdowner = Markdown()
+ >>> markdowner.convert("*boo!*")
+ u'<p><em>boo!</em></p>\n'
+ >>> markdowner.convert("**boom!**")
+ u'<p><strong>boom!</strong></p>\n'
+
+This implementation of Markdown implements the full "core" syntax plus a
+number of extras (e.g., code syntax coloring, footnotes) as described on
+<http://code.google.com/p/python-markdown2/wiki/Extras>.
+"""
+
+cmdln_desc = """A fast and complete Python implementation of Markdown, a
+text-to-HTML conversion tool for web writers.
+"""
+
+# Dev Notes:
+# - There is already a Python markdown processor
+# (http://www.freewisdom.org/projects/python-markdown/).
+# - Python's regex syntax doesn't have '\z', so I'm using '\Z'. I'm
+# not yet sure if there implications with this. Compare 'pydoc sre'
+# and 'perldoc perlre'.
+
+__version_info__ = (1, 0, 1, 14) # first three nums match Markdown.pl
+__version__ = '1.0.1.14'
+__author__ = "Trent Mick"
+
+import os
+import sys
+from pprint import pprint
+import re
+import logging
+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import md5
+import optparse
+from random import random
+import codecs
+
+
+
+#---- Python version compat
+
+if sys.version_info[:2] < (2,4):
+ from sets import Set as set
+ def reversed(sequence):
+ for i in sequence[::-1]:
+ yield i
+ def _unicode_decode(s, encoding, errors='xmlcharrefreplace'):
+ return unicode(s, encoding, errors)
+else:
+ def _unicode_decode(s, encoding, errors='strict'):
+ return s.decode(encoding, errors)
+
+
+#---- globals
+
+DEBUG = False
+log = logging.getLogger("markdown")
+
+DEFAULT_TAB_WIDTH = 4
+
+# Table of hash values for escaped characters:
+def _escape_hash(s):
+ # Lame attempt to avoid possible collision with someone actually
+ # using the MD5 hexdigest of one of these chars in there text.
+ # Other ideas: random.random(), uuid.uuid()
+ #return md5(s).hexdigest() # Markdown.pl effectively does this.
+ return 'md5-'+md5(s).hexdigest()
+g_escape_table = dict([(ch, _escape_hash(ch))
+ for ch in '\\`*_{}[]()>#+-.!'])
+
+
+
+#---- exceptions
+
+class MarkdownError(Exception):
+ pass
+
+
+
+#---- public api
+
+def markdown_path(path, encoding="utf-8",
+ html4tags=False, tab_width=DEFAULT_TAB_WIDTH,
+ safe_mode=None, extras=None, link_patterns=None,
+ use_file_vars=False):
+ text = codecs.open(path, 'r', encoding).read()
+ return Markdown(html4tags=html4tags, tab_width=tab_width,
+ safe_mode=safe_mode, extras=extras,
+ link_patterns=link_patterns,
+ use_file_vars=use_file_vars).convert(text)
+
+def markdown(text, html4tags=False, tab_width=DEFAULT_TAB_WIDTH,
+ safe_mode=None, extras=None, link_patterns=None,
+ use_file_vars=False):
+ return Markdown(html4tags=html4tags, tab_width=tab_width,
+ safe_mode=safe_mode, extras=extras,
+ link_patterns=link_patterns,
+ use_file_vars=use_file_vars).convert(text)
+
+class Markdown(object):
+ # The dict of "extras" to enable in processing -- a mapping of
+ # extra name to argument for the extra. Most extras do not have an
+ # argument, in which case the value is None.
+ #
+ # This can be set via (a) subclassing and (b) the constructor
+ # "extras" argument.
+ extras = None
+
+ urls = None
+ titles = None
+ html_blocks = None
+ html_spans = None
+ html_removed_text = "[HTML_REMOVED]" # for compat with markdown.py
+
+ # Used to track when we're inside an ordered or unordered list
+ # (see _ProcessListItems() for details):
+ list_level = 0
+
+ _ws_only_line_re = re.compile(r"^[ \t]+$", re.M)
+
+ def __init__(self, html4tags=False, tab_width=4, safe_mode=None,
+ extras=None, link_patterns=None, use_file_vars=False):
+ if html4tags:
+ self.empty_element_suffix = ">"
+ else:
+ self.empty_element_suffix = " />"
+ self.tab_width = tab_width
+
+ # For compatibility with earlier markdown2.py and with
+ # markdown.py's safe_mode being a boolean,
+ # safe_mode == True -> "replace"
+ if safe_mode is True:
+ self.safe_mode = "replace"
+ else:
+ self.safe_mode = safe_mode
+
+ if self.extras is None:
+ self.extras = {}
+ elif not isinstance(self.extras, dict):
+ self.extras = dict([(e, None) for e in self.extras])
+ if extras:
+ if not isinstance(extras, dict):
+ extras = dict([(e, None) for e in extras])
+ self.extras.update(extras)
+ assert isinstance(self.extras, dict)
+ self._instance_extras = self.extras.copy()
+ self.link_patterns = link_patterns
+ self.use_file_vars = use_file_vars
+ self._outdent_re = re.compile(r'^(\t|[ ]{1,%d})' % tab_width, re.M)
+
+ def reset(self):
+ self.urls = {}
+ self.titles = {}
+ self.html_blocks = {}
+ self.html_spans = {}
+ self.list_level = 0
+ self.extras = self._instance_extras.copy()
+ if "footnotes" in self.extras:
+ self.footnotes = {}
+ self.footnote_ids = []
+
+ def convert(self, text):
+ """Convert the given text."""
+ # Main function. The order in which other subs are called here is
+ # essential. Link and image substitutions need to happen before
+ # _EscapeSpecialChars(), so that any *'s or _'s in the <a>
+ # and <img> tags get encoded.
+
+ # Clear the global hashes. If we don't clear these, you get conflicts
+ # from other articles when generating a page which contains more than
+ # one article (e.g. an index page that shows the N most recent
+ # articles):
+ self.reset()
+
+ if not isinstance(text, unicode):
+ #TODO: perhaps shouldn't presume UTF-8 for string input?
+ text = unicode(text, 'utf-8')
+
+ if self.use_file_vars:
+ # Look for emacs-style file variable hints.
+ emacs_vars = self._get_emacs_vars(text)
+ if "markdown-extras" in emacs_vars:
+ splitter = re.compile("[ ,]+")
+ for e in splitter.split(emacs_vars["markdown-extras"]):
+ if '=' in e:
+ ename, earg = e.split('=', 1)
+ try:
+ earg = int(earg)
+ except ValueError:
+ pass
+ else:
+ ename, earg = e, None
+ self.extras[ename] = earg
+
+ # Standardize line endings:
+ text = re.sub("\r\n|\r", "\n", text)
+
+ # Make sure $text ends with a couple of newlines:
+ text += "\n\n"
+
+ # Convert all tabs to spaces.
+ text = self._detab(text)
+
+ # Strip any lines consisting only of spaces and tabs.
+ # This makes subsequent regexen easier to write, because we can
+ # match consecutive blank lines with /\n+/ instead of something
+ # contorted like /[ \t]*\n+/ .
+ text = self._ws_only_line_re.sub("", text)
+
+ if self.safe_mode:
+ text = self._hash_html_spans(text)
+
+ # Turn block-level HTML blocks into hash entries
+ text = self._hash_html_blocks(text, raw=True)
+
+ # Strip link definitions, store in hashes.
+ if "footnotes" in self.extras:
+ # Must do footnotes first because an unlucky footnote defn
+ # looks like a link defn:
+ # [^4]: this "looks like a link defn"
+ text = self._strip_footnote_definitions(text)
+ text = self._strip_link_definitions(text)
+
+ text = self._run_block_gamut(text)
+
+ if "footnotes" in self.extras:
+ text = self._add_footnotes(text)
+
+ text = self._unescape_special_chars(text)
+
+ if self.safe_mode:
+ text = self._unhash_html_spans(text)
+
+ text += "\n"
+ return text
+
+ _emacs_oneliner_vars_pat = re.compile(r"-\*-\s*([^\r\n]*?)\s*-\*-", re.UNICODE)
+ # This regular expression is intended to match blocks like this:
+ # PREFIX Local Variables: SUFFIX
+ # PREFIX mode: Tcl SUFFIX
+ # PREFIX End: SUFFIX
+ # Some notes:
+ # - "[ \t]" is used instead of "\s" to specifically exclude newlines
+ # - "(\r\n|\n|\r)" is used instead of "$" because the sre engine does
+ # not like anything other than Unix-style line terminators.
+ _emacs_local_vars_pat = re.compile(r"""^
+ (?P<prefix>(?:[^\r\n|\n|\r])*?)
+ [\ \t]*Local\ Variables:[\ \t]*
+ (?P<suffix>.*?)(?:\r\n|\n|\r)
+ (?P<content>.*?\1End:)
+ """, re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE)
+
+ def _get_emacs_vars(self, text):
+ """Return a dictionary of emacs-style local variables.
+
+ Parsing is done loosely according to this spec (and according to
+ some in-practice deviations from this):
+ http://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html#Specifying-File-Variables
+ """
+ emacs_vars = {}
+ SIZE = pow(2, 13) # 8kB
+
+ # Search near the start for a '-*-'-style one-liner of variables.
+ head = text[:SIZE]
+ if "-*-" in head:
+ match = self._emacs_oneliner_vars_pat.search(head)
+ if match:
+ emacs_vars_str = match.group(1)
+ assert '\n' not in emacs_vars_str
+ emacs_var_strs = [s.strip() for s in emacs_vars_str.split(';')
+ if s.strip()]
+ if len(emacs_var_strs) == 1 and ':' not in emacs_var_strs[0]:
+ # While not in the spec, this form is allowed by emacs:
+ # -*- Tcl -*-
+ # where the implied "variable" is "mode". This form
+ # is only allowed if there are no other variables.
+ emacs_vars["mode"] = emacs_var_strs[0].strip()
+ else:
+ for emacs_var_str in emacs_var_strs:
+ try:
+ variable, value = emacs_var_str.strip().split(':', 1)
+ except ValueError:
+ log.debug("emacs variables error: malformed -*- "
+ "line: %r", emacs_var_str)
+ continue
+ # Lowercase the variable name because Emacs allows "Mode"
+ # or "mode" or "MoDe", etc.
+ emacs_vars[variable.lower()] = value.strip()
+
+ tail = text[-SIZE:]
+ if "Local Variables" in tail:
+ match = self._emacs_local_vars_pat.search(tail)
+ if match:
+ prefix = match.group("prefix")
+ suffix = match.group("suffix")
+ lines = match.group("content").splitlines(0)
+ #print "prefix=%r, suffix=%r, content=%r, lines: %s"\
+ # % (prefix, suffix, match.group("content"), lines)
+
+ # Validate the Local Variables block: proper prefix and suffix
+ # usage.
+ for i, line in enumerate(lines):
+ if not line.startswith(prefix):
+ log.debug("emacs variables error: line '%s' "
+ "does not use proper prefix '%s'"
+ % (line, prefix))
+ return {}
+ # Don't validate suffix on last line. Emacs doesn't care,
+ # neither should we.
+ if i != len(lines)-1 and not line.endswith(suffix):
+ log.debug("emacs variables error: line '%s' "
+ "does not use proper suffix '%s'"
+ % (line, suffix))
+ return {}
+
+ # Parse out one emacs var per line.
+ continued_for = None
+ for line in lines[:-1]: # no var on the last line ("PREFIX End:")
+ if prefix: line = line[len(prefix):] # strip prefix
+ if suffix: line = line[:-len(suffix)] # strip suffix
+ line = line.strip()
+ if continued_for:
+ variable = continued_for
+ if line.endswith('\\'):
+ line = line[:-1].rstrip()
+ else:
+ continued_for = None
+ emacs_vars[variable] += ' ' + line
+ else:
+ try:
+ variable, value = line.split(':', 1)
+ except ValueError:
+ log.debug("local variables error: missing colon "
+ "in local variables entry: '%s'" % line)
+ continue
+ # Do NOT lowercase the variable name, because Emacs only
+ # allows "mode" (and not "Mode", "MoDe", etc.) in this block.
+ value = value.strip()
+ if value.endswith('\\'):
+ value = value[:-1].rstrip()
+ continued_for = variable
+ else:
+ continued_for = None
+ emacs_vars[variable] = value
+
+ # Unquote values.
+ for var, val in emacs_vars.items():
+ if len(val) > 1 and (val.startswith('"') and val.endswith('"')
+ or val.startswith('"') and val.endswith('"')):
+ emacs_vars[var] = val[1:-1]
+
+ return emacs_vars
+
+ # Cribbed from a post by Bart Lateur:
+ # <http://www.nntp.perl.org/group/perl.macperl.anyperl/154>
+ _detab_re = re.compile(r'(.*?)\t', re.M)
+ def _detab_sub(self, match):
+ g1 = match.group(1)
+ return g1 + (' ' * (self.tab_width - len(g1) % self.tab_width))
+ def _detab(self, text):
+ r"""Remove (leading?) tabs from a file.
+
+ >>> m = Markdown()
+ >>> m._detab("\tfoo")
+ ' foo'
+ >>> m._detab(" \tfoo")
+ ' foo'
+ >>> m._detab("\t foo")
+ ' foo'
+ >>> m._detab(" foo")
+ ' foo'
+ >>> m._detab(" foo\n\tbar\tblam")
+ ' foo\n bar blam'
+ """
+ if '\t' not in text:
+ return text
+ return self._detab_re.subn(self._detab_sub, text)[0]
+
+ _block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del'
+ _strict_tag_block_re = re.compile(r"""
+ ( # save in \1
+ ^ # start of line (with re.M)
+ <(%s) # start tag = \2
+ \b # word break
+ (.*\n)*? # any number of lines, minimally matching
+ </\2> # the matching end tag
+ [ \t]* # trailing spaces/tabs
+ (?=\n+|\Z) # followed by a newline or end of document
+ )
+ """ % _block_tags_a,
+ re.X | re.M)
+
+ _block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math'
+ _liberal_tag_block_re = re.compile(r"""
+ ( # save in \1
+ ^ # start of line (with re.M)
+ <(%s) # start tag = \2
+ \b # word break
+ (.*\n)*? # any number of lines, minimally matching
+ .*</\2> # the matching end tag
+ [ \t]* # trailing spaces/tabs
+ (?=\n+|\Z) # followed by a newline or end of document
+ )
+ """ % _block_tags_b,
+ re.X | re.M)
+
+ def _hash_html_block_sub(self, match, raw=False):
+ html = match.group(1)
+ if raw and self.safe_mode:
+ html = self._sanitize_html(html)
+ key = _hash_text(html)
+ self.html_blocks[key] = html
+ return "\n\n" + key + "\n\n"
+
+ def _hash_html_blocks(self, text, raw=False):
+ """Hashify HTML blocks
+
+ We only want to do this for block-level HTML tags, such as headers,
+ lists, and tables. That's because we still want to wrap <p>s around
+ "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+ phrase emphasis, and spans. The list of tags we're looking for is
+ hard-coded.
+
+ @param raw {boolean} indicates if these are raw HTML blocks in
+ the original source. It makes a difference in "safe" mode.
+ """
+ if '<' not in text:
+ return text
+
+ # Pass `raw` value into our calls to self._hash_html_block_sub.
+ hash_html_block_sub = _curry(self._hash_html_block_sub, raw=raw)
+
+ # First, look for nested blocks, e.g.:
+ # <div>
+ # <div>
+ # tags for inner block must be indented.
+ # </div>
+ # </div>
+ #
+ # The outermost tags must start at the left margin for this to match, and
+ # the inner nested divs must be indented.
+ # We need to do this before the next, more liberal match, because the next
+ # match will start at the first `<div>` and stop at the first `</div>`.
+ text = self._strict_tag_block_re.sub(hash_html_block_sub, text)
+
+ # Now match more liberally, simply from `\n<tag>` to `</tag>\n`
+ text = self._liberal_tag_block_re.sub(hash_html_block_sub, text)
+
+ # Special case just for <hr />. It was easier to make a special
+ # case than to make the other regex more complicated.
+ if "<hr" in text:
+ _hr_tag_re = _hr_tag_re_from_tab_width(self.tab_width)
+ text = _hr_tag_re.sub(hash_html_block_sub, text)
+
+ # Special case for standalone HTML comments:
+ if "<!--" in text:
+ start = 0
+ while True:
+ # Delimiters for next comment block.
+ try:
+ start_idx = text.index("<!--", start)
+ except ValueError, ex:
+ break
+ try:
+ end_idx = text.index("-->", start_idx) + 3
+ except ValueError, ex:
+ break
+
+ # Start position for next comment block search.
+ start = end_idx
+
+ # Validate whitespace before comment.
+ if start_idx:
+ # - Up to `tab_width - 1` spaces before start_idx.
+ for i in range(self.tab_width - 1):
+ if text[start_idx - 1] != ' ':
+ break
+ start_idx -= 1
+ if start_idx == 0:
+ break
+ # - Must be preceded by 2 newlines or hit the start of
+ # the document.
+ if start_idx == 0:
+ pass
+ elif start_idx == 1 and text[0] == '\n':
+ start_idx = 0 # to match minute detail of Markdown.pl regex
+ elif text[start_idx-2:start_idx] == '\n\n':
+ pass
+ else:
+ break
+
+ # Validate whitespace after comment.
+ # - Any number of spaces and tabs.
+ while end_idx < len(text):
+ if text[end_idx] not in ' \t':
+ break
+ end_idx += 1
+ # - Must be following by 2 newlines or hit end of text.
+ if text[end_idx:end_idx+2] not in ('', '\n', '\n\n'):
+ continue
+
+ # Escape and hash (must match `_hash_html_block_sub`).
+ html = text[start_idx:end_idx]
+ if raw and self.safe_mode:
+ html = self._sanitize_html(html)
+ key = _hash_text(html)
+ self.html_blocks[key] = html
+ text = text[:start_idx] + "\n\n" + key + "\n\n" + text[end_idx:]
+
+ if "xml" in self.extras:
+ # Treat XML processing instructions and namespaced one-liner
+ # tags as if they were block HTML tags. E.g., if standalone
+ # (i.e. are their own paragraph), the following do not get
+ # wrapped in a <p> tag:
+ # <?foo bar?>
+ #
+ # <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="chapter_1.md"/>
+ _xml_oneliner_re = _xml_oneliner_re_from_tab_width(self.tab_width)
+ text = _xml_oneliner_re.sub(hash_html_block_sub, text)
+
+ return text
+
+ def _strip_link_definitions(self, text):
+ # Strips link definitions from text, stores the URLs and titles in
+ # hash references.
+ less_than_tab = self.tab_width - 1
+
+ # Link defs are in the form:
+ # [id]: url "optional title"
+ _link_def_re = re.compile(r"""
+ ^[ ]{0,%d}\[(.+)\]: # id = \1
+ [ \t]*
+ \n? # maybe *one* newline
+ [ \t]*
+ <?(.+?)>? # url = \2
+ [ \t]*
+ (?:
+ \n? # maybe one newline
+ [ \t]*
+ (?<=\s) # lookbehind for whitespace
+ ['"(]
+ ([^\n]*) # title = \3
+ ['")]
+ [ \t]*
+ )? # title is optional
+ (?:\n+|\Z)
+ """ % less_than_tab, re.X | re.M | re.U)
+ return _link_def_re.sub(self._extract_link_def_sub, text)
+
+ def _extract_link_def_sub(self, match):
+ id, url, title = match.groups()
+ key = id.lower() # Link IDs are case-insensitive
+ self.urls[key] = self._encode_amps_and_angles(url)
+ if title:
+ self.titles[key] = title.replace('"', '&quot;')
+ return ""
+
+ def _extract_footnote_def_sub(self, match):
+ id, text = match.groups()
+ text = _dedent(text, skip_first_line=not text.startswith('\n')).strip()
+ normed_id = re.sub(r'\W', '-', id)
+ # Ensure footnote text ends with a couple newlines (for some
+ # block gamut matches).
+ self.footnotes[normed_id] = text + "\n\n"
+ return ""
+
+ def _strip_footnote_definitions(self, text):
+ """A footnote definition looks like this:
+
+ [^note-id]: Text of the note.
+
+ May include one or more indented paragraphs.
+
+ Where,
+ - The 'note-id' can be pretty much anything, though typically it
+ is the number of the footnote.
+ - The first paragraph may start on the next line, like so:
+
+ [^note-id]:
+ Text of the note.
+ """
+ less_than_tab = self.tab_width - 1
+ footnote_def_re = re.compile(r'''
+ ^[ ]{0,%d}\[\^(.+)\]: # id = \1
+ [ \t]*
+ ( # footnote text = \2
+ # First line need not start with the spaces.
+ (?:\s*.*\n+)
+ (?:
+ (?:[ ]{%d} | \t) # Subsequent lines must be indented.
+ .*\n+
+ )*
+ )
+ # Lookahead for non-space at line-start, or end of doc.
+ (?:(?=^[ ]{0,%d}\S)|\Z)
+ ''' % (less_than_tab, self.tab_width, self.tab_width),
+ re.X | re.M)
+ return footnote_def_re.sub(self._extract_footnote_def_sub, text)
+
+
+ _hr_res = [
+ re.compile(r"^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$", re.M),
+ re.compile(r"^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$", re.M),
+ re.compile(r"^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$", re.M),
+ ]
+
+ def _run_block_gamut(self, text):
+ # These are all the transformations that form block-level
+ # tags like paragraphs, headers, and list items.
+
+ text = self._do_headers(text)
+
+ # Do Horizontal Rules:
+ hr = "\n<hr"+self.empty_element_suffix+"\n"
+ for hr_re in self._hr_res:
+ text = hr_re.sub(hr, text)
+
+ text = self._do_lists(text)
+
+ if "pyshell" in self.extras:
+ text = self._prepare_pyshell_blocks(text)
+
+ text = self._do_code_blocks(text)
+
+ text = self._do_block_quotes(text)
+
+ # We already ran _HashHTMLBlocks() before, in Markdown(), but that
+ # was to escape raw HTML in the original Markdown source. This time,
+ # we're escaping the markup we've just created, so that we don't wrap
+ # <p> tags around block-level tags.
+ text = self._hash_html_blocks(text)
+
+ text = self._form_paragraphs(text)
+
+ return text
+
+ def _pyshell_block_sub(self, match):
+ lines = match.group(0).splitlines(0)
+ _dedentlines(lines)
+ indent = ' ' * self.tab_width
+ s = ('\n' # separate from possible cuddled paragraph
+ + indent + ('\n'+indent).join(lines)
+ + '\n\n')
+ return s
+
+ def _prepare_pyshell_blocks(self, text):
+ """Ensure that Python interactive shell sessions are put in
+ code blocks -- even if not properly indented.
+ """
+ if ">>>" not in text:
+ return text
+
+ less_than_tab = self.tab_width - 1
+ _pyshell_block_re = re.compile(r"""
+ ^([ ]{0,%d})>>>[ ].*\n # first line
+ ^(\1.*\S+.*\n)* # any number of subsequent lines
+ ^\n # ends with a blank line
+ """ % less_than_tab, re.M | re.X)
+
+ return _pyshell_block_re.sub(self._pyshell_block_sub, text)
+
+ def _run_span_gamut(self, text):
+ # These are all the transformations that occur *within* block-level
+ # tags like paragraphs, headers, and list items.
+
+ text = self._do_code_spans(text)
+
+ text = self._escape_special_chars(text)
+
+ # Process anchor and image tags.
+ text = self._do_links(text)
+
+ # Make links out of things like `<http://example.com/>`
+ # Must come after _do_links(), because you can use < and >
+ # delimiters in inline links like [this](<url>).
+ text = self._do_auto_links(text)
+
+ if "link-patterns" in self.extras:
+ text = self._do_link_patterns(text)
+
+ text = self._encode_amps_and_angles(text)
+
+ text = self._do_italics_and_bold(text)
+
+ # Do hard breaks:
+ text = re.sub(r" {2,}\n", " <br%s\n" % self.empty_element_suffix, text)
+
+ return text
+
+ # "Sorta" because auto-links are identified as "tag" tokens.
+ _sorta_html_tokenize_re = re.compile(r"""
+ (
+ # tag
+ </?
+ (?:\w+) # tag name
+ (?:\s+(?:[\w-]+:)?[\w-]+=(?:".*?"|'.*?'))* # attributes
+ \s*/?>
+ |
+ # auto-link (e.g., <http://www.activestate.com/>)
+ <\w+[^>]*>
+ |
+ <!--.*?--> # comment
+ |
+ <\?.*?\?> # processing instruction
+ )
+ """, re.X)
+
+ def _escape_special_chars(self, text):
+ # Python markdown note: the HTML tokenization here differs from
+ # that in Markdown.pl, hence the behaviour for subtle cases can
+ # differ (I believe the tokenizer here does a better job because
+ # it isn't susceptible to unmatched '<' and '>' in HTML tags).
+ # Note, however, that '>' is not allowed in an auto-link URL
+ # here.
+ escaped = []
+ is_html_markup = False
+ for token in self._sorta_html_tokenize_re.split(text):
+ if is_html_markup:
+ # Within tags/HTML-comments/auto-links, encode * and _
+ # so they don't conflict with their use in Markdown for
+ # italics and strong. We're replacing each such
+ # character with its corresponding MD5 checksum value;
+ # this is likely overkill, but it should prevent us from
+ # colliding with the escape values by accident.
+ escaped.append(token.replace('*', g_escape_table['*'])
+ .replace('_', g_escape_table['_']))
+ else:
+ escaped.append(self._encode_backslash_escapes(token))
+ is_html_markup = not is_html_markup
+ return ''.join(escaped)
+
+ def _hash_html_spans(self, text):
+ # Used for safe_mode.
+
+ def _is_auto_link(s):
+ if ':' in s and self._auto_link_re.match(s):
+ return True
+ elif '@' in s and self._auto_email_link_re.match(s):
+ return True
+ return False
+
+ tokens = []
+ is_html_markup = False
+ for token in self._sorta_html_tokenize_re.split(text):
+ if is_html_markup and not _is_auto_link(token):
+ sanitized = self._sanitize_html(token)
+ key = _hash_text(sanitized)
+ self.html_spans[key] = sanitized
+ tokens.append(key)
+ else:
+ tokens.append(token)
+ is_html_markup = not is_html_markup
+ return ''.join(tokens)
+
+ def _unhash_html_spans(self, text):
+ for key, sanitized in self.html_spans.items():
+ text = text.replace(key, sanitized)
+ return text
+
+ def _sanitize_html(self, s):
+ if self.safe_mode == "replace":
+ return self.html_removed_text
+ elif self.safe_mode == "escape":
+ replacements = [
+ ('&', '&amp;'),
+ ('<', '&lt;'),
+ ('>', '&gt;'),
+ ]
+ for before, after in replacements:
+ s = s.replace(before, after)
+ return s
+ else:
+ raise MarkdownError("invalid value for 'safe_mode': %r (must be "
+ "'escape' or 'replace')" % self.safe_mode)
+
+ _tail_of_inline_link_re = re.compile(r'''
+ # Match tail of: [text](/url/) or [text](/url/ "title")
+ \( # literal paren
+ [ \t]*
+ (?P<url> # \1
+ <.*?>
+ |
+ .*?
+ )
+ [ \t]*
+ ( # \2
+ (['"]) # quote char = \3
+ (?P<title>.*?)
+ \3 # matching quote
+ )? # title is optional
+ \)
+ ''', re.X | re.S)
+ _tail_of_reference_link_re = re.compile(r'''
+ # Match tail of: [text][id]
+ [ ]? # one optional space
+ (?:\n[ ]*)? # one optional newline followed by spaces
+ \[
+ (?P<id>.*?)
+ \]
+ ''', re.X | re.S)
+
+ def _do_links(self, text):
+ """Turn Markdown link shortcuts into XHTML <a> and <img> tags.
+
+ This is a combination of Markdown.pl's _DoAnchors() and
+ _DoImages(). They are done together because that simplified the
+ approach. It was necessary to use a different approach than
+ Markdown.pl because of the lack of atomic matching support in
+ Python's regex engine used in $g_nested_brackets.
+ """
+ MAX_LINK_TEXT_SENTINEL = 3000 # markdown2 issue 24
+
+ # `anchor_allowed_pos` is used to support img links inside
+ # anchors, but not anchors inside anchors. An anchor's start
+ # pos must be `>= anchor_allowed_pos`.
+ anchor_allowed_pos = 0
+
+ curr_pos = 0
+ while True: # Handle the next link.
+ # The next '[' is the start of:
+ # - an inline anchor: [text](url "title")
+ # - a reference anchor: [text][id]
+ # - an inline img: ![text](url "title")
+ # - a reference img: ![text][id]
+ # - a footnote ref: [^id]
+ # (Only if 'footnotes' extra enabled)
+ # - a footnote defn: [^id]: ...
+ # (Only if 'footnotes' extra enabled) These have already
+ # been stripped in _strip_footnote_definitions() so no
+ # need to watch for them.
+ # - a link definition: [id]: url "title"
+ # These have already been stripped in
+ # _strip_link_definitions() so no need to watch for them.
+ # - not markup: [...anything else...
+ try:
+ start_idx = text.index('[', curr_pos)
+ except ValueError:
+ break
+ text_length = len(text)
+
+ # Find the matching closing ']'.
+ # Markdown.pl allows *matching* brackets in link text so we
+ # will here too. Markdown.pl *doesn't* currently allow
+ # matching brackets in img alt text -- we'll differ in that
+ # regard.
+ bracket_depth = 0
+ for p in range(start_idx+1, min(start_idx+MAX_LINK_TEXT_SENTINEL,
+ text_length)):
+ ch = text[p]
+ if ch == ']':
+ bracket_depth -= 1
+ if bracket_depth < 0:
+ break
+ elif ch == '[':
+ bracket_depth += 1
+ else:
+ # Closing bracket not found within sentinel length.
+ # This isn't markup.
+ curr_pos = start_idx + 1
+ continue
+ link_text = text[start_idx+1:p]
+
+ # Possibly a footnote ref?
+ if "footnotes" in self.extras and link_text.startswith("^"):
+ normed_id = re.sub(r'\W', '-', link_text[1:])
+ if normed_id in self.footnotes:
+ self.footnote_ids.append(normed_id)
+ result = '<sup class="footnote-ref" id="fnref-%s">' \
+ '<a href="#fn-%s">%s</a></sup>' \
+ % (normed_id, normed_id, len(self.footnote_ids))
+ text = text[:start_idx] + result + text[p+1:]
+ else:
+ # This id isn't defined, leave the markup alone.
+ curr_pos = p+1
+ continue
+
+ # Now determine what this is by the remainder.
+ p += 1
+ if p == text_length:
+ return text
+
+ # Inline anchor or img?
+ if text[p] == '(': # attempt at perf improvement
+ match = self._tail_of_inline_link_re.match(text, p)
+ if match:
+ # Handle an inline anchor or img.
+ is_img = start_idx > 0 and text[start_idx-1] == "!"
+ if is_img:
+ start_idx -= 1
+
+ url, title = match.group("url"), match.group("title")
+ if url and url[0] == '<':
+ url = url[1:-1] # '<url>' -> 'url'
+ # We've got to encode these to avoid conflicting
+ # with italics/bold.
+ url = url.replace('*', g_escape_table['*']) \
+ .replace('_', g_escape_table['_'])
+ if title:
+ title_str = ' title="%s"' \
+ % title.replace('*', g_escape_table['*']) \
+ .replace('_', g_escape_table['_']) \
+ .replace('"', '&quot;')
+ else:
+ title_str = ''
+ if is_img:
+ result = '<img src="%s" alt="%s"%s%s' \
+ % (url, link_text.replace('"', '&quot;'),
+ title_str, self.empty_element_suffix)
+ curr_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[match.end():]
+ elif start_idx >= anchor_allowed_pos:
+ result_head = '<a href="%s"%s>' % (url, title_str)
+ result = '%s%s</a>' % (result_head, link_text)
+ # <img> allowed from curr_pos on, <a> from
+ # anchor_allowed_pos on.
+ curr_pos = start_idx + len(result_head)
+ anchor_allowed_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[match.end():]
+ else:
+ # Anchor not allowed here.
+ curr_pos = start_idx + 1
+ continue
+
+ # Reference anchor or img?
+ else:
+ match = self._tail_of_reference_link_re.match(text, p)
+ if match:
+ # Handle a reference-style anchor or img.
+ is_img = start_idx > 0 and text[start_idx-1] == "!"
+ if is_img:
+ start_idx -= 1
+ link_id = match.group("id").lower()
+ if not link_id:
+ link_id = link_text.lower() # for links like [this][]
+ if link_id in self.urls:
+ url = self.urls[link_id]
+ # We've got to encode these to avoid conflicting
+ # with italics/bold.
+ url = url.replace('*', g_escape_table['*']) \
+ .replace('_', g_escape_table['_'])
+ title = self.titles.get(link_id)
+ if title:
+ title = title.replace('*', g_escape_table['*']) \
+ .replace('_', g_escape_table['_'])
+ title_str = ' title="%s"' % title
+ else:
+ title_str = ''
+ if is_img:
+ result = '<img src="%s" alt="%s"%s%s' \
+ % (url, link_text.replace('"', '&quot;'),
+ title_str, self.empty_element_suffix)
+ curr_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[match.end():]
+ elif start_idx >= anchor_allowed_pos:
+ result = '<a href="%s"%s>%s</a>' \
+ % (url, title_str, link_text)
+ result_head = '<a href="%s"%s>' % (url, title_str)
+ result = '%s%s</a>' % (result_head, link_text)
+ # <img> allowed from curr_pos on, <a> from
+ # anchor_allowed_pos on.
+ curr_pos = start_idx + len(result_head)
+ anchor_allowed_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[match.end():]
+ else:
+ # Anchor not allowed here.
+ curr_pos = start_idx + 1
+ else:
+ # This id isn't defined, leave the markup alone.
+ curr_pos = match.end()
+ continue
+
+ # Otherwise, it isn't markup.
+ curr_pos = start_idx + 1
+
+ return text
+
+
+ _setext_h_re = re.compile(r'^(.+)[ \t]*\n(=+|-+)[ \t]*\n+', re.M)
+ def _setext_h_sub(self, match):
+ n = {"=": 1, "-": 2}[match.group(2)[0]]
+ demote_headers = self.extras.get("demote-headers")
+ if demote_headers:
+ n = min(n + demote_headers, 6)
+ return "<h%d>%s</h%d>\n\n" \
+ % (n, self._run_span_gamut(match.group(1)), n)
+
+ _atx_h_re = re.compile(r'''
+ ^(\#{1,6}) # \1 = string of #'s
+ [ \t]*
+ (.+?) # \2 = Header text
+ [ \t]*
+ (?<!\\) # ensure not an escaped trailing '#'
+ \#* # optional closing #'s (not counted)
+ \n+
+ ''', re.X | re.M)
+ def _atx_h_sub(self, match):
+ n = len(match.group(1))
+ demote_headers = self.extras.get("demote-headers")
+ if demote_headers:
+ n = min(n + demote_headers, 6)
+ return "<h%d>%s</h%d>\n\n" \
+ % (n, self._run_span_gamut(match.group(2)), n)
+
+ def _do_headers(self, text):
+ # Setext-style headers:
+ # Header 1
+ # ========
+ #
+ # Header 2
+ # --------
+ text = self._setext_h_re.sub(self._setext_h_sub, text)
+
+ # atx-style headers:
+ # # Header 1
+ # ## Header 2
+ # ## Header 2 with closing hashes ##
+ # ...
+ # ###### Header 6
+ text = self._atx_h_re.sub(self._atx_h_sub, text)
+
+ return text
+
+
+ _marker_ul_chars = '*+-'
+ _marker_any = r'(?:[%s]|\d+\.)' % _marker_ul_chars
+ _marker_ul = '(?:[%s])' % _marker_ul_chars
+ _marker_ol = r'(?:\d+\.)'
+
+ def _list_sub(self, match):
+ lst = match.group(1)
+ lst_type = match.group(3) in self._marker_ul_chars and "ul" or "ol"
+ result = self._process_list_items(lst)
+ if self.list_level:
+ return "<%s>\n%s</%s>\n" % (lst_type, result, lst_type)
+ else:
+ return "<%s>\n%s</%s>\n\n" % (lst_type, result, lst_type)
+
+ def _do_lists(self, text):
+ # Form HTML ordered (numbered) and unordered (bulleted) lists.
+
+ for marker_pat in (self._marker_ul, self._marker_ol):
+ # Re-usable pattern to match any entire ul or ol list:
+ less_than_tab = self.tab_width - 1
+ whole_list = r'''
+ ( # \1 = whole list
+ ( # \2
+ [ ]{0,%d}
+ (%s) # \3 = first list item marker
+ [ \t]+
+ )
+ (?:.+?)
+ ( # \4
+ \Z
+ |
+ \n{2,}
+ (?=\S)
+ (?! # Negative lookahead for another list item marker
+ [ \t]*
+ %s[ \t]+
+ )
+ )
+ )
+ ''' % (less_than_tab, marker_pat, marker_pat)
+
+ # We use a different prefix before nested lists than top-level lists.
+ # See extended comment in _process_list_items().
+ #
+ # Note: There's a bit of duplication here. My original implementation
+ # created a scalar regex pattern as the conditional result of the test on
+ # $g_list_level, and then only ran the $text =~ s{...}{...}egmx
+ # substitution once, using the scalar as the pattern. This worked,
+ # everywhere except when running under MT on my hosting account at Pair
+ # Networks. There, this caused all rebuilds to be killed by the reaper (or
+ # perhaps they crashed, but that seems incredibly unlikely given that the
+ # same script on the same server ran fine *except* under MT. I've spent
+ # more time trying to figure out why this is happening than I'd like to
+ # admit. My only guess, backed up by the fact that this workaround works,
+ # is that Perl optimizes the substition when it can figure out that the
+ # pattern will never change, and when this optimization isn't on, we run
+ # afoul of the reaper. Thus, the slightly redundant code to that uses two
+ # static s/// patterns rather than one conditional pattern.
+
+ if self.list_level:
+ sub_list_re = re.compile("^"+whole_list, re.X | re.M | re.S)
+ text = sub_list_re.sub(self._list_sub, text)
+ else:
+ list_re = re.compile(r"(?:(?<=\n\n)|\A\n?)"+whole_list,
+ re.X | re.M | re.S)
+ text = list_re.sub(self._list_sub, text)
+
+ return text
+
+ _list_item_re = re.compile(r'''
+ (\n)? # leading line = \1
+ (^[ \t]*) # leading whitespace = \2
+ (%s) [ \t]+ # list marker = \3
+ ((?:.+?) # list item text = \4
+ (\n{1,2})) # eols = \5
+ (?= \n* (\Z | \2 (%s) [ \t]+))
+ ''' % (_marker_any, _marker_any),
+ re.M | re.X | re.S)
+
+ _last_li_endswith_two_eols = False
+ def _list_item_sub(self, match):
+ item = match.group(4)
+ leading_line = match.group(1)
+ leading_space = match.group(2)
+ if leading_line or "\n\n" in item or self._last_li_endswith_two_eols:
+ item = self._run_block_gamut(self._outdent(item))
+ else:
+ # Recursion for sub-lists:
+ item = self._do_lists(self._outdent(item))
+ if item.endswith('\n'):
+ item = item[:-1]
+ item = self._run_span_gamut(item)
+ self._last_li_endswith_two_eols = (len(match.group(5)) == 2)
+ return "<li>%s</li>\n" % item
+
+ def _process_list_items(self, list_str):
+ # Process the contents of a single ordered or unordered list,
+ # splitting it into individual list items.
+
+ # The $g_list_level global keeps track of when we're inside a list.
+ # Each time we enter a list, we increment it; when we leave a list,
+ # we decrement. If it's zero, we're not in a list anymore.
+ #
+ # We do this because when we're not inside a list, we want to treat
+ # something like this:
+ #
+ # I recommend upgrading to version
+ # 8. Oops, now this line is treated
+ # as a sub-list.
+ #
+ # As a single paragraph, despite the fact that the second line starts
+ # with a digit-period-space sequence.
+ #
+ # Whereas when we're inside a list (or sub-list), that line will be
+ # treated as the start of a sub-list. What a kludge, huh? This is
+ # an aspect of Markdown's syntax that's hard to parse perfectly
+ # without resorting to mind-reading. Perhaps the solution is to
+ # change the syntax rules such that sub-lists must start with a
+ # starting cardinal number; e.g. "1." or "a.".
+ self.list_level += 1
+ self._last_li_endswith_two_eols = False
+ list_str = list_str.rstrip('\n') + '\n'
+ list_str = self._list_item_re.sub(self._list_item_sub, list_str)
+ self.list_level -= 1
+ return list_str
+
+ def _get_pygments_lexer(self, lexer_name):
+ try:
+ from pygments import lexers, util
+ except ImportError:
+ return None
+ try:
+ return lexers.get_lexer_by_name(lexer_name)
+ except util.ClassNotFound:
+ return None
+
+ def _color_with_pygments(self, codeblock, lexer, **formatter_opts):
+ import pygments
+ import pygments.formatters
+
+ class HtmlCodeFormatter(pygments.formatters.HtmlFormatter):
+ def _wrap_code(self, inner):
+ """A function for use in a Pygments Formatter which
+ wraps in <code> tags.
+ """
+ yield 0, "<code>"
+ for tup in inner:
+ yield tup
+ yield 0, "</code>"
+
+ def wrap(self, source, outfile):
+ """Return the source with a code, pre, and div."""
+ return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
+
+ formatter = HtmlCodeFormatter(cssclass="codehilite", **formatter_opts)
+ return pygments.highlight(codeblock, lexer, formatter)
+
+ def _code_block_sub(self, match):
+ codeblock = match.group(1)
+ codeblock = self._outdent(codeblock)
+ codeblock = self._detab(codeblock)
+ codeblock = codeblock.lstrip('\n') # trim leading newlines
+ codeblock = codeblock.rstrip() # trim trailing whitespace
+
+ if "code-color" in self.extras and codeblock.startswith(":::"):
+ lexer_name, rest = codeblock.split('\n', 1)
+ lexer_name = lexer_name[3:].strip()
+ lexer = self._get_pygments_lexer(lexer_name)
+ codeblock = rest.lstrip("\n") # Remove lexer declaration line.
+ if lexer:
+ formatter_opts = self.extras['code-color'] or {}
+ colored = self._color_with_pygments(codeblock, lexer,
+ **formatter_opts)
+ return "\n\n%s\n\n" % colored
+
+ codeblock = self._encode_code(codeblock)
+ return "\n\n<pre><code>%s\n</code></pre>\n\n" % codeblock
+
+ def _do_code_blocks(self, text):
+ """Process Markdown `<pre><code>` blocks."""
+ code_block_re = re.compile(r'''
+ (?:\n\n|\A)
+ ( # $1 = the code block -- one or more lines, starting with a space/tab
+ (?:
+ (?:[ ]{%d} | \t) # Lines must start with a tab or a tab-width of spaces
+ .*\n+
+ )+
+ )
+ ((?=^[ ]{0,%d}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
+ ''' % (self.tab_width, self.tab_width),
+ re.M | re.X)
+
+ return code_block_re.sub(self._code_block_sub, text)
+
+
+ # Rules for a code span:
+ # - backslash escapes are not interpreted in a code span
+ # - to include one or or a run of more backticks the delimiters must
+ # be a longer run of backticks
+ # - cannot start or end a code span with a backtick; pad with a
+ # space and that space will be removed in the emitted HTML
+ # See `test/tm-cases/escapes.text` for a number of edge-case
+ # examples.
+ _code_span_re = re.compile(r'''
+ (?<!\\)
+ (`+) # \1 = Opening run of `
+ (?!`) # See Note A test/tm-cases/escapes.text
+ (.+?) # \2 = The code block
+ (?<!`)
+ \1 # Matching closer
+ (?!`)
+ ''', re.X | re.S)
+
+ def _code_span_sub(self, match):
+ c = match.group(2).strip(" \t")
+ c = self._encode_code(c)
+ return "<code>%s</code>" % c
+
+ def _do_code_spans(self, text):
+ # * Backtick quotes are used for <code></code> spans.
+ #
+ # * You can use multiple backticks as the delimiters if you want to
+ # include literal backticks in the code span. So, this input:
+ #
+ # Just type ``foo `bar` baz`` at the prompt.
+ #
+ # Will translate to:
+ #
+ # <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
+ #
+ # There's no arbitrary limit to the number of backticks you
+ # can use as delimters. If you need three consecutive backticks
+ # in your code, use four for delimiters, etc.
+ #
+ # * You can use spaces to get literal backticks at the edges:
+ #
+ # ... type `` `bar` `` ...
+ #
+ # Turns to:
+ #
+ # ... type <code>`bar`</code> ...
+ return self._code_span_re.sub(self._code_span_sub, text)
+
+ def _encode_code(self, text):
+ """Encode/escape certain characters inside Markdown code runs.
+ The point is that in code, these characters are literals,
+ and lose their special Markdown meanings.
+ """
+ replacements = [
+ # Encode all ampersands; HTML entities are not
+ # entities within a Markdown code span.
+ ('&', '&amp;'),
+ # Do the angle bracket song and dance:
+ ('<', '&lt;'),
+ ('>', '&gt;'),
+ # Now, escape characters that are magic in Markdown:
+ ('*', g_escape_table['*']),
+ ('_', g_escape_table['_']),
+ ('{', g_escape_table['{']),
+ ('}', g_escape_table['}']),
+ ('[', g_escape_table['[']),
+ (']', g_escape_table[']']),
+ ('\\', g_escape_table['\\']),
+ ]
+ for before, after in replacements:
+ text = text.replace(before, after)
+ return text
+
+ _strong_re = re.compile(r"(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1", re.S)
+ _em_re = re.compile(r"(\*|_)(?=\S)(.+?)(?<=\S)\1", re.S)
+ _code_friendly_strong_re = re.compile(r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S)
+ _code_friendly_em_re = re.compile(r"\*(?=\S)(.+?)(?<=\S)\*", re.S)
+ def _do_italics_and_bold(self, text):
+ # <strong> must go first:
+ if "code-friendly" in self.extras:
+ text = self._code_friendly_strong_re.sub(r"<strong>\1</strong>", text)
+ text = self._code_friendly_em_re.sub(r"<em>\1</em>", text)
+ else:
+ text = self._strong_re.sub(r"<strong>\2</strong>", text)
+ text = self._em_re.sub(r"<em>\2</em>", text)
+ return text
+
+
+ _block_quote_re = re.compile(r'''
+ ( # Wrap whole match in \1
+ (
+ ^[ \t]*>[ \t]? # '>' at the start of a line
+ .+\n # rest of the first line
+ (.+\n)* # subsequent consecutive lines
+ \n* # blanks
+ )+
+ )
+ ''', re.M | re.X)
+ _bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M);
+
+ _html_pre_block_re = re.compile(r'(\s*<pre>.+?</pre>)', re.S)
+ def _dedent_two_spaces_sub(self, match):
+ return re.sub(r'(?m)^ ', '', match.group(1))
+
+ def _block_quote_sub(self, match):
+ bq = match.group(1)
+ bq = self._bq_one_level_re.sub('', bq) # trim one level of quoting
+ bq = self._ws_only_line_re.sub('', bq) # trim whitespace-only lines
+ bq = self._run_block_gamut(bq) # recurse
+
+ bq = re.sub('(?m)^', ' ', bq)
+ # These leading spaces screw with <pre> content, so we need to fix that:
+ bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq)
+
+ return "<blockquote>\n%s\n</blockquote>\n\n" % bq
+
+ def _do_block_quotes(self, text):
+ if '>' not in text:
+ return text
+ return self._block_quote_re.sub(self._block_quote_sub, text)
+
+ def _form_paragraphs(self, text):
+ # Strip leading and trailing lines:
+ text = text.strip('\n')
+
+ # Wrap <p> tags.
+ grafs = re.split(r"\n{2,}", text)
+ for i, graf in enumerate(grafs):
+ if graf in self.html_blocks:
+ # Unhashify HTML blocks
+ grafs[i] = self.html_blocks[graf]
+ else:
+ # Wrap <p> tags.
+ graf = self._run_span_gamut(graf)
+ grafs[i] = "<p>" + graf.lstrip(" \t") + "</p>"
+
+ return "\n\n".join(grafs)
+
+ def _add_footnotes(self, text):
+ if self.footnotes:
+ footer = [
+ '<div class="footnotes">',
+ '<hr' + self.empty_element_suffix,
+ '<ol>',
+ ]
+ for i, id in enumerate(self.footnote_ids):
+ if i != 0:
+ footer.append('')
+ footer.append('<li id="fn-%s">' % id)
+ footer.append(self._run_block_gamut(self.footnotes[id]))
+ backlink = ('<a href="#fnref-%s" '
+ 'class="footnoteBackLink" '
+ 'title="Jump back to footnote %d in the text.">'
+ '&#8617;</a>' % (id, i+1))
+ if footer[-1].endswith("</p>"):
+ footer[-1] = footer[-1][:-len("</p>")] \
+ + '&nbsp;' + backlink + "</p>"
+ else:
+ footer.append("\n<p>%s</p>" % backlink)
+ footer.append('</li>')
+ footer.append('</ol>')
+ footer.append('</div>')
+ return text + '\n\n' + '\n'.join(footer)
+ else:
+ return text
+
+ # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+ # http://bumppo.net/projects/amputator/
+ _ampersand_re = re.compile(r'&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)')
+ _naked_lt_re = re.compile(r'<(?![a-z/?\$!])', re.I)
+ _naked_gt_re = re.compile(r'''(?<![a-z?!/'"-])>''', re.I)
+
+ def _encode_amps_and_angles(self, text):
+ # Smart processing for ampersands and angle brackets that need
+ # to be encoded.
+ text = self._ampersand_re.sub('&amp;', text)
+
+ # Encode naked <'s
+ text = self._naked_lt_re.sub('&lt;', text)
+
+ # Encode naked >'s
+ # Note: Other markdown implementations (e.g. Markdown.pl, PHP
+ # Markdown) don't do this.
+ text = self._naked_gt_re.sub('&gt;', text)
+ return text
+
+ def _encode_backslash_escapes(self, text):
+ for ch, escape in g_escape_table.items():
+ text = text.replace("\\"+ch, escape)
+ return text
+
+ _auto_link_re = re.compile(r'<((https?|ftp):[^\'">\s]+)>', re.I)
+ def _auto_link_sub(self, match):
+ g1 = match.group(1)
+ return '<a href="%s">%s</a>' % (g1, g1)
+
+ _auto_email_link_re = re.compile(r"""
+ <
+ (?:mailto:)?
+ (
+ [-.\w]+
+ \@
+ [-\w]+(\.[-\w]+)*\.[a-z]+
+ )
+ >
+ """, re.I | re.X | re.U)
+ def _auto_email_link_sub(self, match):
+ return self._encode_email_address(
+ self._unescape_special_chars(match.group(1)))
+
+ def _do_auto_links(self, text):
+ text = self._auto_link_re.sub(self._auto_link_sub, text)
+ text = self._auto_email_link_re.sub(self._auto_email_link_sub, text)
+ return text
+
+ def _encode_email_address(self, addr):
+ # Input: an email address, e.g. "foo@example.com"
+ #
+ # Output: the email address as a mailto link, with each character
+ # of the address encoded as either a decimal or hex entity, in
+ # the hopes of foiling most address harvesting spam bots. E.g.:
+ #
+ # <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
+ # x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
+ # &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
+ #
+ # Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
+ # mailing list: <http://tinyurl.com/yu7ue>
+ chars = [_xml_encode_email_char_at_random(ch)
+ for ch in "mailto:" + addr]
+ # Strip the mailto: from the visible part.
+ addr = '<a href="%s">%s</a>' \
+ % (''.join(chars), ''.join(chars[7:]))
+ return addr
+
+ def _do_link_patterns(self, text):
+ """Caveat emptor: there isn't much guarding against link
+ patterns being formed inside other standard Markdown links, e.g.
+ inside a [link def][like this].
+
+ Dev Notes: *Could* consider prefixing regexes with a negative
+ lookbehind assertion to attempt to guard against this.
+ """
+ link_from_hash = {}
+ for regex, repl in self.link_patterns:
+ replacements = []
+ for match in regex.finditer(text):
+ if hasattr(repl, "__call__"):
+ href = repl(match)
+ else:
+ href = match.expand(repl)
+ replacements.append((match.span(), href))
+ for (start, end), href in reversed(replacements):
+ escaped_href = (
+ href.replace('"', '&quot;') # b/c of attr quote
+ # To avoid markdown <em> and <strong>:
+ .replace('*', g_escape_table['*'])
+ .replace('_', g_escape_table['_']))
+ link = '<a href="%s">%s</a>' % (escaped_href, text[start:end])
+ hash = md5(link).hexdigest()
+ link_from_hash[hash] = link
+ text = text[:start] + hash + text[end:]
+ for hash, link in link_from_hash.items():
+ text = text.replace(hash, link)
+ return text
+
+ def _unescape_special_chars(self, text):
+ # Swap back in all the special characters we've hidden.
+ for ch, hash in g_escape_table.items():
+ text = text.replace(hash, ch)
+ return text
+
+ def _outdent(self, text):
+ # Remove one level of line-leading tabs or spaces
+ return self._outdent_re.sub('', text)
+
+
+class MarkdownWithExtras(Markdown):
+ """A markdowner class that enables most extras:
+
+ - footnotes
+ - code-color (only has effect if 'pygments' Python module on path)
+
+ These are not included:
+ - pyshell (specific to Python-related documenting)
+ - code-friendly (because it *disables* part of the syntax)
+ - link-patterns (because you need to specify some actual
+ link-patterns anyway)
+ """
+ extras = ["footnotes", "code-color"]
+
+
+#---- internal support functions
+
+# From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549
+def _curry(*args, **kwargs):
+ function, args = args[0], args[1:]
+ def result(*rest, **kwrest):
+ combined = kwargs.copy()
+ combined.update(kwrest)
+ return function(*args + rest, **combined)
+ return result
+
+# Recipe: regex_from_encoded_pattern (1.0)
+def _regex_from_encoded_pattern(s):
+ """'foo' -> re.compile(re.escape('foo'))
+ '/foo/' -> re.compile('foo')
+ '/foo/i' -> re.compile('foo', re.I)
+ """
+ if s.startswith('/') and s.rfind('/') != 0:
+ # Parse it: /PATTERN/FLAGS
+ idx = s.rfind('/')
+ pattern, flags_str = s[1:idx], s[idx+1:]
+ flag_from_char = {
+ "i": re.IGNORECASE,
+ "l": re.LOCALE,
+ "s": re.DOTALL,
+ "m": re.MULTILINE,
+ "u": re.UNICODE,
+ }
+ flags = 0
+ for char in flags_str:
+ try:
+ flags |= flag_from_char[char]
+ except KeyError:
+ raise ValueError("unsupported regex flag: '%s' in '%s' "
+ "(must be one of '%s')"
+ % (char, s, ''.join(flag_from_char.keys())))
+ return re.compile(s[1:idx], flags)
+ else: # not an encoded regex
+ return re.compile(re.escape(s))
+
+# Recipe: dedent (0.1.2)
+def _dedentlines(lines, tabsize=8, skip_first_line=False):
+ """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines
+
+ "lines" is a list of lines to dedent.
+ "tabsize" is the tab width to use for indent width calculations.
+ "skip_first_line" is a boolean indicating if the first line should
+ be skipped for calculating the indent width and for dedenting.
+ This is sometimes useful for docstrings and similar.
+
+ Same as dedent() except operates on a sequence of lines. Note: the
+ lines list is modified **in-place**.
+ """
+ DEBUG = False
+ if DEBUG:
+ print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
+ % (tabsize, skip_first_line)
+ indents = []
+ margin = None
+ for i, line in enumerate(lines):
+ if i == 0 and skip_first_line: continue
+ indent = 0
+ for ch in line:
+ if ch == ' ':
+ indent += 1
+ elif ch == '\t':
+ indent += tabsize - (indent % tabsize)
+ elif ch in '\r\n':
+ continue # skip all-whitespace lines
+ else:
+ break
+ else:
+ continue # skip all-whitespace lines
+ if DEBUG: print "dedent: indent=%d: %r" % (indent, line)
+ if margin is None:
+ margin = indent
+ else:
+ margin = min(margin, indent)
+ if DEBUG: print "dedent: margin=%r" % margin
+
+ if margin is not None and margin > 0:
+ for i, line in enumerate(lines):
+ if i == 0 and skip_first_line: continue
+ removed = 0
+ for j, ch in enumerate(line):
+ if ch == ' ':
+ removed += 1
+ elif ch == '\t':
+ removed += tabsize - (removed % tabsize)
+ elif ch in '\r\n':
+ if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line
+ lines[i] = lines[i][j:]
+ break
+ else:
+ raise ValueError("unexpected non-whitespace char %r in "
+ "line %r while removing %d-space margin"
+ % (ch, line, margin))
+ if DEBUG:
+ print "dedent: %r: %r -> removed %d/%d"\
+ % (line, ch, removed, margin)
+ if removed == margin:
+ lines[i] = lines[i][j+1:]
+ break
+ elif removed > margin:
+ lines[i] = ' '*(removed-margin) + lines[i][j+1:]
+ break
+ else:
+ if removed:
+ lines[i] = lines[i][removed:]
+ return lines
+
+def _dedent(text, tabsize=8, skip_first_line=False):
+ """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text
+
+ "text" is the text to dedent.
+ "tabsize" is the tab width to use for indent width calculations.
+ "skip_first_line" is a boolean indicating if the first line should
+ be skipped for calculating the indent width and for dedenting.
+ This is sometimes useful for docstrings and similar.
+
+ textwrap.dedent(s), but don't expand tabs to spaces
+ """
+ lines = text.splitlines(1)
+ _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line)
+ return ''.join(lines)
+
+
+class _memoized(object):
+ """Decorator that caches a function's return value each time it is called.
+ If called later with the same arguments, the cached value is returned, and
+ not re-evaluated.
+
+ http://wiki.python.org/moin/PythonDecoratorLibrary
+ """
+ def __init__(self, func):
+ self.func = func
+ self.cache = {}
+ def __call__(self, *args):
+ try:
+ return self.cache[args]
+ except KeyError:
+ self.cache[args] = value = self.func(*args)
+ return value
+ except TypeError:
+ # uncachable -- for instance, passing a list as an argument.
+ # Better to not cache than to blow up entirely.
+ return self.func(*args)
+ def __repr__(self):
+ """Return the function's docstring."""
+ return self.func.__doc__
+
+
+def _xml_oneliner_re_from_tab_width(tab_width):
+ """Standalone XML processing instruction regex."""
+ return re.compile(r"""
+ (?:
+ (?<=\n\n) # Starting after a blank line
+ | # or
+ \A\n? # the beginning of the doc
+ )
+ ( # save in $1
+ [ ]{0,%d}
+ (?:
+ <\?\w+\b\s+.*?\?> # XML processing instruction
+ |
+ <\w+:\w+\b\s+.*?/> # namespaced single tag
+ )
+ [ \t]*
+ (?=\n{2,}|\Z) # followed by a blank line or end of document
+ )
+ """ % (tab_width - 1), re.X)
+_xml_oneliner_re_from_tab_width = _memoized(_xml_oneliner_re_from_tab_width)
+
+def _hr_tag_re_from_tab_width(tab_width):
+ return re.compile(r"""
+ (?:
+ (?<=\n\n) # Starting after a blank line
+ | # or
+ \A\n? # the beginning of the doc
+ )
+ ( # save in \1
+ [ ]{0,%d}
+ <(hr) # start tag = \2
+ \b # word break
+ ([^<>])*? #
+ /?> # the matching end tag
+ [ \t]*
+ (?=\n{2,}|\Z) # followed by a blank line or end of document
+ )
+ """ % (tab_width - 1), re.X)
+_hr_tag_re_from_tab_width = _memoized(_hr_tag_re_from_tab_width)
+
+
+def _xml_encode_email_char_at_random(ch):
+ r = random()
+ # Roughly 10% raw, 45% hex, 45% dec.
+ # '@' *must* be encoded. I [John Gruber] insist.
+ # Issue 26: '_' must be encoded.
+ if r > 0.9 and ch not in "@_":
+ return ch
+ elif r < 0.45:
+ # The [1:] is to drop leading '0': 0x63 -> x63
+ return '&#%s;' % hex(ord(ch))[1:]
+ else:
+ return '&#%s;' % ord(ch)
+
+def _hash_text(text):
+ return 'md5:'+md5(text.encode("utf-8")).hexdigest()
+
+
+#---- mainline
+
+class _NoReflowFormatter(optparse.IndentedHelpFormatter):
+ """An optparse formatter that does NOT reflow the description."""
+ def format_description(self, description):
+ return description or ""
+
+def _test():
+ import doctest
+ doctest.testmod()
+
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv
+ if not logging.root.handlers:
+ logging.basicConfig()
+
+ usage = "usage: %prog [PATHS...]"
+ version = "%prog "+__version__
+ parser = optparse.OptionParser(prog="markdown2", usage=usage,
+ version=version, description=cmdln_desc,
+ formatter=_NoReflowFormatter())
+ parser.add_option("-v", "--verbose", dest="log_level",
+ action="store_const", const=logging.DEBUG,
+ help="more verbose output")
+ parser.add_option("--encoding",
+ help="specify encoding of text content")
+ parser.add_option("--html4tags", action="store_true", default=False,
+ help="use HTML 4 style for empty element tags")
+ parser.add_option("-s", "--safe", metavar="MODE", dest="safe_mode",
+ help="sanitize literal HTML: 'escape' escapes "
+ "HTML meta chars, 'replace' replaces with an "
+ "[HTML_REMOVED] note")
+ parser.add_option("-x", "--extras", action="append",
+ help="Turn on specific extra features (not part of "
+ "the core Markdown spec). Supported values: "
+ "'code-friendly' disables _/__ for emphasis; "
+ "'code-color' adds code-block syntax coloring; "
+ "'link-patterns' adds auto-linking based on patterns; "
+ "'footnotes' adds the footnotes syntax;"
+ "'xml' passes one-liner processing instructions and namespaced XML tags;"
+ "'pyshell' to put unindented Python interactive shell sessions in a <code> block.")
+ parser.add_option("--use-file-vars",
+ help="Look for and use Emacs-style 'markdown-extras' "
+ "file var to turn on extras. See "
+ "<http://code.google.com/p/python-markdown2/wiki/Extras>.")
+ parser.add_option("--link-patterns-file",
+ help="path to a link pattern file")
+ parser.add_option("--self-test", action="store_true",
+ help="run internal self-tests (some doctests)")
+ parser.add_option("--compare", action="store_true",
+ help="run against Markdown.pl as well (for testing)")
+ parser.set_defaults(log_level=logging.INFO, compare=False,
+ encoding="utf-8", safe_mode=None, use_file_vars=False)
+ opts, paths = parser.parse_args()
+ log.setLevel(opts.log_level)
+
+ if opts.self_test:
+ return _test()
+
+ if opts.extras:
+ extras = {}
+ for s in opts.extras:
+ splitter = re.compile("[,;: ]+")
+ for e in splitter.split(s):
+ if '=' in e:
+ ename, earg = e.split('=', 1)
+ try:
+ earg = int(earg)
+ except ValueError:
+ pass
+ else:
+ ename, earg = e, None
+ extras[ename] = earg
+ else:
+ extras = None
+
+ if opts.link_patterns_file:
+ link_patterns = []
+ f = open(opts.link_patterns_file)
+ try:
+ for i, line in enumerate(f.readlines()):
+ if not line.strip(): continue
+ if line.lstrip().startswith("#"): continue
+ try:
+ pat, href = line.rstrip().rsplit(None, 1)
+ except ValueError:
+ raise MarkdownError("%s:%d: invalid link pattern line: %r"
+ % (opts.link_patterns_file, i+1, line))
+ link_patterns.append(
+ (_regex_from_encoded_pattern(pat), href))
+ finally:
+ f.close()
+ else:
+ link_patterns = None
+
+ from os.path import join, dirname, abspath, exists
+ markdown_pl = join(dirname(dirname(abspath(__file__))), "test",
+ "Markdown.pl")
+ for path in paths:
+ if opts.compare:
+ print "==== Markdown.pl ===="
+ perl_cmd = 'perl %s "%s"' % (markdown_pl, path)
+ o = os.popen(perl_cmd)
+ perl_html = o.read()
+ o.close()
+ sys.stdout.write(perl_html)
+ print "==== markdown2.py ===="
+ html = markdown_path(path, encoding=opts.encoding,
+ html4tags=opts.html4tags,
+ safe_mode=opts.safe_mode,
+ extras=extras, link_patterns=link_patterns,
+ use_file_vars=opts.use_file_vars)
+ sys.stdout.write(
+ html.encode(sys.stdout.encoding or "utf-8", 'xmlcharrefreplace'))
+ if opts.compare:
+ test_dir = join(dirname(dirname(abspath(__file__))), "test")
+ if exists(join(test_dir, "test_markdown2.py")):
+ sys.path.insert(0, test_dir)
+ from test_markdown2 import norm_html_from_html
+ norm_html = norm_html_from_html(html)
+ norm_perl_html = norm_html_from_html(perl_html)
+ else:
+ norm_html = html
+ norm_perl_html = perl_html
+ print "==== match? %r ====" % (norm_perl_html == norm_html)
+
+
+if __name__ == "__main__":
+ sys.exit( main(sys.argv) )
+
diff --git a/vendor/tornado/demos/appengine/static/blog.css b/vendor/tornado/demos/appengine/static/blog.css
new file mode 100644
index 0000000000..8902ec1f22
--- /dev/null
+++ b/vendor/tornado/demos/appengine/static/blog.css
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2009 Facebook
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+body {
+ background: white;
+ color: black;
+ margin: 15px;
+ margin-top: 0;
+}
+
+body,
+input,
+textarea {
+ font-family: Georgia, serif;
+ font-size: 12pt;
+}
+
+table {
+ border-collapse: collapse;
+ border: 0;
+}
+
+td {
+ border: 0;
+ padding: 0;
+}
+
+h1,
+h2,
+h3,
+h4 {
+ font-family: "Helvetica Nue", Helvetica, Arial, sans-serif;
+ margin: 0;
+}
+
+h1 {
+ font-size: 20pt;
+}
+
+pre,
+code {
+ font-family: monospace;
+ color: #060;
+}
+
+pre {
+ margin-left: 1em;
+ padding-left: 1em;
+ border-left: 1px solid silver;
+ line-height: 14pt;
+}
+
+a,
+a code {
+ color: #00c;
+}
+
+#body {
+ max-width: 800px;
+ margin: auto;
+}
+
+#header {
+ background-color: #3b5998;
+ padding: 5px;
+ padding-left: 10px;
+ padding-right: 10px;
+ margin-bottom: 1em;
+}
+
+#header,
+#header a {
+ color: white;
+}
+
+#header h1 a {
+ text-decoration: none;
+}
+
+#footer,
+#content {
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+#footer {
+ margin-top: 3em;
+}
+
+.entry h1 a {
+ color: black;
+ text-decoration: none;
+}
+
+.entry {
+ margin-bottom: 2em;
+}
+
+.entry .date {
+ margin-top: 3px;
+}
+
+.entry p {
+ margin: 0;
+ margin-bottom: 1em;
+}
+
+.entry .body {
+ margin-top: 1em;
+ line-height: 16pt;
+}
+
+.compose td {
+ vertical-align: middle;
+ padding-bottom: 5px;
+}
+
+.compose td.field {
+ padding-right: 10px;
+}
+
+.compose .title,
+.compose .submit {
+ font-family: "Helvetica Nue", Helvetica, Arial, sans-serif;
+ font-weight: bold;
+}
+
+.compose .title {
+ font-size: 20pt;
+}
+
+.compose .title,
+.compose .markdown {
+ width: 100%;
+}
+
+.compose .markdown {
+ height: 500px;
+ line-height: 16pt;
+}
diff --git a/vendor/tornado/demos/appengine/templates/archive.html b/vendor/tornado/demos/appengine/templates/archive.html
new file mode 100644
index 0000000000..dcca9511a4
--- /dev/null
+++ b/vendor/tornado/demos/appengine/templates/archive.html
@@ -0,0 +1,31 @@
+{% extends "base.html" %}
+
+{% block head %}
+ <style type="text/css">
+ ul.archive {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ }
+
+ ul.archive li {
+ margin-bottom: 1em;
+ }
+
+ ul.archive .title {
+ font-family: "Helvetica Nue", Helvetica, Arial, sans-serif;
+ font-size: 14pt;
+ }
+ </style>
+{% end %}
+
+{% block body %}
+ <ul class="archive">
+ {% for entry in entries %}
+ <li>
+ <div class="title"><a href="/entry/{{ entry.slug }}">{{ escape(entry.title) }}</a></div>
+ <div class="date">{{ locale.format_date(entry.published, full_format=True, shorter=True) }}</div>
+ </li>
+ {% end %}
+ </ul>
+{% end %}
diff --git a/vendor/tornado/demos/appengine/templates/base.html b/vendor/tornado/demos/appengine/templates/base.html
new file mode 100644
index 0000000000..0154aea8ca
--- /dev/null
+++ b/vendor/tornado/demos/appengine/templates/base.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <title>{{ escape(handler.settings["blog_title"]) }}</title>
+ <link rel="stylesheet" href="/static/blog.css" type="text/css"/>
+ <link rel="alternate" href="/feed" type="application/atom+xml" title="{{ escape(handler.settings["blog_title"]) }}"/>
+ {% block head %}{% end %}
+ </head>
+ <body>
+ <div id="body">
+ <div id="header">
+ <div style="float:right">
+ {% if not current_user %}
+ {{ _('<a href="%(url)s">Sign in</a> to compose/edit') % {"url": escape(users.create_login_url(request.uri))} }}
+ {% else %}
+ {% if current_user.administrator %}
+ <a href="/compose">{{ _("New post") }}</a> -
+ {% end %}
+ <a href="{{ escape(users.create_logout_url(request.uri)) }}">{{ _("Sign out") }}</a>
+ {% end %}
+ </div>
+ <h1><a href="/">{{ escape(handler.settings["blog_title"]) }}</a></h1>
+ </div>
+ <div id="content">{% block body %}{% end %}</div>
+ </div>
+ {% block bottom %}{% end %}
+ </body>
+</html>
diff --git a/vendor/tornado/demos/appengine/templates/compose.html b/vendor/tornado/demos/appengine/templates/compose.html
new file mode 100644
index 0000000000..5ad548307c
--- /dev/null
+++ b/vendor/tornado/demos/appengine/templates/compose.html
@@ -0,0 +1,42 @@
+{% extends "base.html" %}
+
+{% block body %}
+ <form action="{{ request.path }}" method="post" class="compose">
+ <div style="margin-bottom:5px"><input name="title" type="text" class="title" value="{{ escape(entry.title) if entry else "" }}"/></div>
+ <div style="margin-bottom:5px"><textarea name="markdown" rows="30" cols="40" class="markdown">{{ escape(entry.markdown) if entry else "" }}</textarea></div>
+ <div>
+ <div style="float:right"><a href="http://daringfireball.net/projects/markdown/syntax">{{ _("Syntax documentation") }}</a></div>
+ <input type="submit" value="{{ _("Save changes") if entry else _("Publish post") }}" class="submit"/>
+ &nbsp;<a href="{{ "/entry/" + entry.slug if entry else "/" }}">{{ _("Cancel") }}</a>
+ </div>
+ {% if entry %}
+ <input type="hidden" name="key" value="{{ str(entry.key()) }}"/>
+ {% end %}
+ {{ xsrf_form_html() }}
+ </form>
+{% end %}
+
+{% block bottom %}
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ //<![CDATA[
+
+ $(function() {
+ $("input[name=title]").select();
+ $("form.compose").submit(function() {
+ var required = ["title", "markdown"];
+ var form = $(this).get(0);
+ for (var i = 0; i < required.length; i++) {
+ if (!form[required[i]].value) {
+ $(form[required[i]]).select();
+ return false;
+ }
+ }
+ return true;
+ });
+ });
+
+ //]]>
+ </script>
+{% end %}
+
diff --git a/vendor/tornado/demos/appengine/templates/entry.html b/vendor/tornado/demos/appengine/templates/entry.html
new file mode 100644
index 0000000000..43c835dead
--- /dev/null
+++ b/vendor/tornado/demos/appengine/templates/entry.html
@@ -0,0 +1,5 @@
+{% extends "base.html" %}
+
+{% block body %}
+ {{ modules.Entry(entry) }}
+{% end %}
diff --git a/vendor/tornado/demos/appengine/templates/feed.xml b/vendor/tornado/demos/appengine/templates/feed.xml
new file mode 100644
index 0000000000..c6c368656c
--- /dev/null
+++ b/vendor/tornado/demos/appengine/templates/feed.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ {% set date_format = "%Y-%m-%dT%H:%M:%SZ" %}
+ <title>{{ escape(handler.settings["blog_title"]) }}</title>
+ {% if len(entries) > 0 %}
+ <updated>{{ max(e.updated for e in entries).strftime(date_format) }}</updated>
+ {% else %}
+ <updated>{{ datetime.datetime.utcnow().strftime(date_format) }}</updated>
+ {% end %}
+ <id>http://{{ request.host }}/</id>
+ <link rel="alternate" href="http://{{ request.host }}/" title="{{ escape(handler.settings["blog_title"]) }}" type="text/html"/>
+ <link rel="self" href="{{ request.full_url() }}" title="{{ escape(handler.settings["blog_title"]) }}" type="application/atom+xml"/>
+ <author><name>{{ escape(handler.settings["blog_title"]) }}</name></author>
+ {% for entry in entries %}
+ <entry>
+ <id>http://{{ request.host }}/entry/{{ entry.slug }}</id>
+ <title type="text">{{ escape(entry.title) }}</title>
+ <link href="http://{{ request.host }}/entry/{{ entry.slug }}" rel="alternate" type="text/html"/>
+ <updated>{{ entry.updated.strftime(date_format) }}</updated>
+ <published>{{ entry.published.strftime(date_format) }}</published>
+ <content type="xhtml" xml:base="http://{{ request.host }}/">
+ <div xmlns="http://www.w3.org/1999/xhtml">{{ entry.html }}</div>
+ </content>
+ </entry>
+ {% end %}
+</feed>
diff --git a/vendor/tornado/demos/appengine/templates/home.html b/vendor/tornado/demos/appengine/templates/home.html
new file mode 100644
index 0000000000..dd069a97f3
--- /dev/null
+++ b/vendor/tornado/demos/appengine/templates/home.html
@@ -0,0 +1,8 @@
+{% extends "base.html" %}
+
+{% block body %}
+ {% for entry in entries %}
+ {{ modules.Entry(entry) }}
+ {% end %}
+ <div><a href="/archive">{{ _("Archive") }}</a></div>
+{% end %}
diff --git a/vendor/tornado/demos/appengine/templates/modules/entry.html b/vendor/tornado/demos/appengine/templates/modules/entry.html
new file mode 100644
index 0000000000..06237657c8
--- /dev/null
+++ b/vendor/tornado/demos/appengine/templates/modules/entry.html
@@ -0,0 +1,8 @@
+<div class="entry">
+ <h1><a href="/entry/{{ entry.slug }}">{{ escape(entry.title) }}</a></h1>
+ <div class="date">{{ locale.format_date(entry.published, full_format=True, shorter=True) }}</div>
+ <div class="body">{{ entry.html }}</div>
+ {% if current_user and current_user.administrator %}
+ <div class="admin"><a href="/compose?key={{ str(entry.key()) }}">{{ _("Edit this post") }}</a></div>
+ {% end %}
+</div>
diff --git a/vendor/tornado/demos/auth/authdemo.py b/vendor/tornado/demos/auth/authdemo.py
new file mode 100755
index 0000000000..e6136d1b53
--- /dev/null
+++ b/vendor/tornado/demos/auth/authdemo.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import tornado.auth
+import tornado.escape
+import tornado.httpserver
+import tornado.ioloop
+import tornado.options
+import tornado.web
+
+from tornado.options import define, options
+
+define("port", default=8888, help="run on the given port", type=int)
+
+
+class Application(tornado.web.Application):
+ def __init__(self):
+ handlers = [
+ (r"/", MainHandler),
+ (r"/auth/login", AuthHandler),
+ ]
+ settings = dict(
+ cookie_secret="32oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+ login_url="/auth/login",
+ )
+ tornado.web.Application.__init__(self, handlers, **settings)
+
+
+class BaseHandler(tornado.web.RequestHandler):
+ def get_current_user(self):
+ user_json = self.get_secure_cookie("user")
+ if not user_json: return None
+ return tornado.escape.json_decode(user_json)
+
+
+class MainHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self):
+ name = tornado.escape.xhtml_escape(self.current_user["name"])
+ self.write("Hello, " + name)
+
+
+class AuthHandler(BaseHandler, tornado.auth.GoogleMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ if self.get_argument("openid.mode", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authenticate_redirect()
+
+ def _on_auth(self, user):
+ if not user:
+ raise tornado.web.HTTPError(500, "Google auth failed")
+ self.set_secure_cookie("user", tornado.escape.json_encode(user))
+ self.redirect("/")
+
+
+def main():
+ tornado.options.parse_command_line()
+ http_server = tornado.httpserver.HTTPServer(Application())
+ http_server.listen(options.port)
+ tornado.ioloop.IOLoop.instance().start()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/vendor/tornado/demos/blog/README b/vendor/tornado/demos/blog/README
new file mode 100644
index 0000000000..a033e7a11c
--- /dev/null
+++ b/vendor/tornado/demos/blog/README
@@ -0,0 +1,57 @@
+Running the Tornado Blog example app
+====================================
+This demo is a simple blogging engine that uses MySQL to store posts and
+Google Accounts for author authentication. Since it depends on MySQL, you
+need to set up MySQL and the database schema for the demo to run.
+
+1. Install prerequisites and build tornado
+
+ See http://www.tornadoweb.org/ for installation instructions. If you can
+ run the "helloworld" example application, your environment is set up
+ correctly.
+
+2. Install MySQL if needed
+
+ Consult the documentation for your platform. Under Ubuntu Linux you
+ can run "apt-get install mysql". Under OS X you can download the
+ MySQL PKG file from http://dev.mysql.com/downloads/mysql/
+
+3. Connect to MySQL and create a database and user for the blog.
+
+ Connect to MySQL as a user that can create databases and users:
+ mysql -u root
+
+ Create a database named "blog":
+ mysql> CREATE DATABASE blog;
+
+ Allow the "blog" user to connect with the password "blog":
+ mysql> GRANT ALL PRIVILEGES ON blog.* TO 'blog'@'localhost' IDENTIFIED BY 'blog';
+
+4. Create the tables in your new database.
+
+ You can use the provided schema.sql file by running this command:
+ mysql --user=blog --password=blog --database=blog < schema.sql
+
+ You can run the above command again later if you want to delete the
+ contents of the blog and start over after testing.
+
+5. Run the blog example
+
+ With the default user, password, and database you can just run:
+ ./blog.py
+
+ If you've changed anything, you can alter the default MySQL settings
+ with arguments on the command line, e.g.:
+ ./blog.py --mysql_user=casey --mysql_password=happiness --mysql_database=foodblog
+
+6. Visit your new blog
+
+ Open http://localhost:8888/ in your web browser. You will be redirected to
+ a Google account sign-in page because the blog uses Google accounts for
+ authentication.
+
+ Currently the first user to connect will automatically be given the
+ ability to create and edit posts.
+
+ Once you've created one blog post, subsequent users will not be
+ prompted to sign in.
diff --git a/vendor/tornado/demos/blog/blog.py b/vendor/tornado/demos/blog/blog.py
new file mode 100755
index 0000000000..808a9afc55
--- /dev/null
+++ b/vendor/tornado/demos/blog/blog.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import markdown
+import os.path
+import re
+import tornado.auth
+import tornado.database
+import tornado.httpserver
+import tornado.ioloop
+import tornado.options
+import tornado.web
+import unicodedata
+
+from tornado.options import define, options
+
+define("port", default=8888, help="run on the given port", type=int)
+define("mysql_host", default="127.0.0.1:3306", help="blog database host")
+define("mysql_database", default="blog", help="blog database name")
+define("mysql_user", default="blog", help="blog database user")
+define("mysql_password", default="blog", help="blog database password")
+
+
+class Application(tornado.web.Application):
+ def __init__(self):
+ handlers = [
+ (r"/", HomeHandler),
+ (r"/archive", ArchiveHandler),
+ (r"/feed", FeedHandler),
+ (r"/entry/([^/]+)", EntryHandler),
+ (r"/compose", ComposeHandler),
+ (r"/auth/login", AuthLoginHandler),
+ (r"/auth/logout", AuthLogoutHandler),
+ ]
+ settings = dict(
+ blog_title=u"Tornado Blog",
+ template_path=os.path.join(os.path.dirname(__file__), "templates"),
+ static_path=os.path.join(os.path.dirname(__file__), "static"),
+ ui_modules={"Entry": EntryModule},
+ xsrf_cookies=True,
+ cookie_secret="11oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+ login_url="/auth/login",
+ )
+ tornado.web.Application.__init__(self, handlers, **settings)
+
+ # Have one global connection to the blog DB across all handlers
+ self.db = tornado.database.Connection(
+ host=options.mysql_host, database=options.mysql_database,
+ user=options.mysql_user, password=options.mysql_password)
+
+
+class BaseHandler(tornado.web.RequestHandler):
+ @property
+ def db(self):
+ return self.application.db
+
+ def get_current_user(self):
+ user_id = self.get_secure_cookie("user")
+ if not user_id: return None
+ return self.db.get("SELECT * FROM authors WHERE id = %s", int(user_id))
+
+
+class HomeHandler(BaseHandler):
+ def get(self):
+ entries = self.db.query("SELECT * FROM entries ORDER BY published "
+ "DESC LIMIT 5")
+ if not entries:
+ self.redirect("/compose")
+ return
+ self.render("home.html", entries=entries)
+
+
+class EntryHandler(BaseHandler):
+ def get(self, slug):
+ entry = self.db.get("SELECT * FROM entries WHERE slug = %s", slug)
+ if not entry: raise tornado.web.HTTPError(404)
+ self.render("entry.html", entry=entry)
+
+
+class ArchiveHandler(BaseHandler):
+ def get(self):
+ entries = self.db.query("SELECT * FROM entries ORDER BY published "
+ "DESC")
+ self.render("archive.html", entries=entries)
+
+
+class FeedHandler(BaseHandler):
+ def get(self):
+ entries = self.db.query("SELECT * FROM entries ORDER BY published "
+ "DESC LIMIT 10")
+ self.set_header("Content-Type", "application/atom+xml")
+ self.render("feed.xml", entries=entries)
+
+
+class ComposeHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self):
+ id = self.get_argument("id", None)
+ entry = None
+ if id:
+ entry = self.db.get("SELECT * FROM entries WHERE id = %s", int(id))
+ self.render("compose.html", entry=entry)
+
+ @tornado.web.authenticated
+ def post(self):
+ id = self.get_argument("id", None)
+ title = self.get_argument("title")
+ text = self.get_argument("markdown")
+ html = markdown.markdown(text)
+ if id:
+ entry = self.db.get("SELECT * FROM entries WHERE id = %s", int(id))
+ if not entry: raise tornado.web.HTTPError(404)
+ slug = entry.slug
+ self.db.execute(
+ "UPDATE entries SET title = %s, markdown = %s, html = %s "
+ "WHERE id = %s", title, text, html, int(id))
+ else:
+ slug = unicodedata.normalize("NFKD", title).encode(
+ "ascii", "ignore")
+ slug = re.sub(r"[^\w]+", " ", slug)
+ slug = "-".join(slug.lower().strip().split())
+ if not slug: slug = "entry"
+ while True:
+ e = self.db.get("SELECT * FROM entries WHERE slug = %s", slug)
+ if not e: break
+ slug += "-2"
+ self.db.execute(
+ "INSERT INTO entries (author_id,title,slug,markdown,html,"
+ "published) VALUES (%s,%s,%s,%s,%s,UTC_TIMESTAMP())",
+ self.current_user.id, title, slug, text, html)
+ self.redirect("/entry/" + slug)
+
+
+class AuthLoginHandler(BaseHandler, tornado.auth.GoogleMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ if self.get_argument("openid.mode", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authenticate_redirect()
+
+ def _on_auth(self, user):
+ if not user:
+ raise tornado.web.HTTPError(500, "Google auth failed")
+ author = self.db.get("SELECT * FROM authors WHERE email = %s",
+ user["email"])
+ if not author:
+ # Auto-create first author
+ any_author = self.db.get("SELECT * FROM authors LIMIT 1")
+ if not any_author:
+ author_id = self.db.execute(
+ "INSERT INTO authors (email,name) VALUES (%s,%s)",
+ user["email"], user["name"])
+ else:
+ self.redirect("/")
+ return
+ else:
+ author_id = author["id"]
+ self.set_secure_cookie("user", str(author_id))
+ self.redirect(self.get_argument("next", "/"))
+
+
+class AuthLogoutHandler(BaseHandler):
+ def get(self):
+ self.clear_cookie("user")
+ self.redirect(self.get_argument("next", "/"))
+
+
+class EntryModule(tornado.web.UIModule):
+ def render(self, entry):
+ return self.render_string("modules/entry.html", entry=entry)
+
+
+def main():
+ tornado.options.parse_command_line()
+ http_server = tornado.httpserver.HTTPServer(Application())
+ http_server.listen(options.port)
+ tornado.ioloop.IOLoop.instance().start()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/vendor/tornado/demos/blog/markdown.py b/vendor/tornado/demos/blog/markdown.py
new file mode 100644
index 0000000000..59ba731bf0
--- /dev/null
+++ b/vendor/tornado/demos/blog/markdown.py
@@ -0,0 +1,1877 @@
+#!/usr/bin/env python
+# Copyright (c) 2007-2008 ActiveState Corp.
+# License: MIT (http://www.opensource.org/licenses/mit-license.php)
+
+r"""A fast and complete Python implementation of Markdown.
+
+[from http://daringfireball.net/projects/markdown/]
+> Markdown is a text-to-HTML filter; it translates an easy-to-read /
+> easy-to-write structured text format into HTML. Markdown's text
+> format is most similar to that of plain text email, and supports
+> features such as headers, *emphasis*, code blocks, blockquotes, and
+> links.
+>
+> Markdown's syntax is designed not as a generic markup language, but
+> specifically to serve as a front-end to (X)HTML. You can use span-level
+> HTML tags anywhere in a Markdown document, and you can use block level
+> HTML tags (like <div> and <table> as well).
+
+Module usage:
+
+ >>> import markdown2
+ >>> markdown2.markdown("*boo!*") # or use `html = markdown_path(PATH)`
+ u'<p><em>boo!</em></p>\n'
+
+ >>> markdowner = Markdown()
+ >>> markdowner.convert("*boo!*")
+ u'<p><em>boo!</em></p>\n'
+ >>> markdowner.convert("**boom!**")
+ u'<p><strong>boom!</strong></p>\n'
+
+This implementation of Markdown implements the full "core" syntax plus a
+number of extras (e.g., code syntax coloring, footnotes) as described on
+<http://code.google.com/p/python-markdown2/wiki/Extras>.
+"""
+
+cmdln_desc = """A fast and complete Python implementation of Markdown, a
+text-to-HTML conversion tool for web writers.
+"""
+
+# Dev Notes:
+# - There is already a Python markdown processor
+# (http://www.freewisdom.org/projects/python-markdown/).
+# - Python's regex syntax doesn't have '\z', so I'm using '\Z'. I'm
+# not yet sure if there implications with this. Compare 'pydoc sre'
+# and 'perldoc perlre'.
+
+__version_info__ = (1, 0, 1, 14) # first three nums match Markdown.pl
+__version__ = '1.0.1.14'
+__author__ = "Trent Mick"
+
+import os
+import sys
+from pprint import pprint
+import re
+import logging
+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import md5
+import optparse
+from random import random
+import codecs
+
+
+
+#---- Python version compat
+
+if sys.version_info[:2] < (2,4):
+ from sets import Set as set
+ def reversed(sequence):
+ for i in sequence[::-1]:
+ yield i
+ def _unicode_decode(s, encoding, errors='xmlcharrefreplace'):
+ return unicode(s, encoding, errors)
+else:
+ def _unicode_decode(s, encoding, errors='strict'):
+ return s.decode(encoding, errors)
+
+
+#---- globals
+
+DEBUG = False
+log = logging.getLogger("markdown")
+
+DEFAULT_TAB_WIDTH = 4
+
+# Table of hash values for escaped characters:
+def _escape_hash(s):
+ # Lame attempt to avoid possible collision with someone actually
+ # using the MD5 hexdigest of one of these chars in there text.
+ # Other ideas: random.random(), uuid.uuid()
+ #return md5(s).hexdigest() # Markdown.pl effectively does this.
+ return 'md5-'+md5(s).hexdigest()
+g_escape_table = dict([(ch, _escape_hash(ch))
+ for ch in '\\`*_{}[]()>#+-.!'])
+
+
+
+#---- exceptions
+
+class MarkdownError(Exception):
+ pass
+
+
+
+#---- public api
+
+def markdown_path(path, encoding="utf-8",
+ html4tags=False, tab_width=DEFAULT_TAB_WIDTH,
+ safe_mode=None, extras=None, link_patterns=None,
+ use_file_vars=False):
+ text = codecs.open(path, 'r', encoding).read()
+ return Markdown(html4tags=html4tags, tab_width=tab_width,
+ safe_mode=safe_mode, extras=extras,
+ link_patterns=link_patterns,
+ use_file_vars=use_file_vars).convert(text)
+
+def markdown(text, html4tags=False, tab_width=DEFAULT_TAB_WIDTH,
+ safe_mode=None, extras=None, link_patterns=None,
+ use_file_vars=False):
+ return Markdown(html4tags=html4tags, tab_width=tab_width,
+ safe_mode=safe_mode, extras=extras,
+ link_patterns=link_patterns,
+ use_file_vars=use_file_vars).convert(text)
+
+class Markdown(object):
+ # The dict of "extras" to enable in processing -- a mapping of
+ # extra name to argument for the extra. Most extras do not have an
+ # argument, in which case the value is None.
+ #
+ # This can be set via (a) subclassing and (b) the constructor
+ # "extras" argument.
+ extras = None
+
+ urls = None
+ titles = None
+ html_blocks = None
+ html_spans = None
+ html_removed_text = "[HTML_REMOVED]" # for compat with markdown.py
+
+ # Used to track when we're inside an ordered or unordered list
+ # (see _ProcessListItems() for details):
+ list_level = 0
+
+ _ws_only_line_re = re.compile(r"^[ \t]+$", re.M)
+
+ def __init__(self, html4tags=False, tab_width=4, safe_mode=None,
+ extras=None, link_patterns=None, use_file_vars=False):
+ if html4tags:
+ self.empty_element_suffix = ">"
+ else:
+ self.empty_element_suffix = " />"
+ self.tab_width = tab_width
+
+ # For compatibility with earlier markdown2.py and with
+ # markdown.py's safe_mode being a boolean,
+ # safe_mode == True -> "replace"
+ if safe_mode is True:
+ self.safe_mode = "replace"
+ else:
+ self.safe_mode = safe_mode
+
+ if self.extras is None:
+ self.extras = {}
+ elif not isinstance(self.extras, dict):
+ self.extras = dict([(e, None) for e in self.extras])
+ if extras:
+ if not isinstance(extras, dict):
+ extras = dict([(e, None) for e in extras])
+ self.extras.update(extras)
+ assert isinstance(self.extras, dict)
+ self._instance_extras = self.extras.copy()
+ self.link_patterns = link_patterns
+ self.use_file_vars = use_file_vars
+ self._outdent_re = re.compile(r'^(\t|[ ]{1,%d})' % tab_width, re.M)
+
+ def reset(self):
+ self.urls = {}
+ self.titles = {}
+ self.html_blocks = {}
+ self.html_spans = {}
+ self.list_level = 0
+ self.extras = self._instance_extras.copy()
+ if "footnotes" in self.extras:
+ self.footnotes = {}
+ self.footnote_ids = []
+
+ def convert(self, text):
+ """Convert the given text."""
+ # Main function. The order in which other subs are called here is
+ # essential. Link and image substitutions need to happen before
+ # _EscapeSpecialChars(), so that any *'s or _'s in the <a>
+ # and <img> tags get encoded.
+
+ # Clear the global hashes. If we don't clear these, you get conflicts
+ # from other articles when generating a page which contains more than
+ # one article (e.g. an index page that shows the N most recent
+ # articles):
+ self.reset()
+
+ if not isinstance(text, unicode):
+ #TODO: perhaps shouldn't presume UTF-8 for string input?
+ text = unicode(text, 'utf-8')
+
+ if self.use_file_vars:
+ # Look for emacs-style file variable hints.
+ emacs_vars = self._get_emacs_vars(text)
+ if "markdown-extras" in emacs_vars:
+ splitter = re.compile("[ ,]+")
+ for e in splitter.split(emacs_vars["markdown-extras"]):
+ if '=' in e:
+ ename, earg = e.split('=', 1)
+ try:
+ earg = int(earg)
+ except ValueError:
+ pass
+ else:
+ ename, earg = e, None
+ self.extras[ename] = earg
+
+ # Standardize line endings:
+ text = re.sub("\r\n|\r", "\n", text)
+
+ # Make sure $text ends with a couple of newlines:
+ text += "\n\n"
+
+ # Convert all tabs to spaces.
+ text = self._detab(text)
+
+ # Strip any lines consisting only of spaces and tabs.
+ # This makes subsequent regexen easier to write, because we can
+ # match consecutive blank lines with /\n+/ instead of something
+ # contorted like /[ \t]*\n+/ .
+ text = self._ws_only_line_re.sub("", text)
+
+ if self.safe_mode:
+ text = self._hash_html_spans(text)
+
+ # Turn block-level HTML blocks into hash entries
+ text = self._hash_html_blocks(text, raw=True)
+
+ # Strip link definitions, store in hashes.
+ if "footnotes" in self.extras:
+ # Must do footnotes first because an unlucky footnote defn
+ # looks like a link defn:
+ # [^4]: this "looks like a link defn"
+ text = self._strip_footnote_definitions(text)
+ text = self._strip_link_definitions(text)
+
+ text = self._run_block_gamut(text)
+
+ if "footnotes" in self.extras:
+ text = self._add_footnotes(text)
+
+ text = self._unescape_special_chars(text)
+
+ if self.safe_mode:
+ text = self._unhash_html_spans(text)
+
+ text += "\n"
+ return text
+
+ _emacs_oneliner_vars_pat = re.compile(r"-\*-\s*([^\r\n]*?)\s*-\*-", re.UNICODE)
+ # This regular expression is intended to match blocks like this:
+ # PREFIX Local Variables: SUFFIX
+ # PREFIX mode: Tcl SUFFIX
+ # PREFIX End: SUFFIX
+ # Some notes:
+ # - "[ \t]" is used instead of "\s" to specifically exclude newlines
+ # - "(\r\n|\n|\r)" is used instead of "$" because the sre engine does
+ # not like anything other than Unix-style line terminators.
+ _emacs_local_vars_pat = re.compile(r"""^
+ (?P<prefix>(?:[^\r\n|\n|\r])*?)
+ [\ \t]*Local\ Variables:[\ \t]*
+ (?P<suffix>.*?)(?:\r\n|\n|\r)
+ (?P<content>.*?\1End:)
+ """, re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE)
+
+ def _get_emacs_vars(self, text):
+ """Return a dictionary of emacs-style local variables.
+
+ Parsing is done loosely according to this spec (and according to
+ some in-practice deviations from this):
+ http://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html#Specifying-File-Variables
+ """
+ emacs_vars = {}
+ SIZE = pow(2, 13) # 8kB
+
+ # Search near the start for a '-*-'-style one-liner of variables.
+ head = text[:SIZE]
+ if "-*-" in head:
+ match = self._emacs_oneliner_vars_pat.search(head)
+ if match:
+ emacs_vars_str = match.group(1)
+ assert '\n' not in emacs_vars_str
+ emacs_var_strs = [s.strip() for s in emacs_vars_str.split(';')
+ if s.strip()]
+ if len(emacs_var_strs) == 1 and ':' not in emacs_var_strs[0]:
+ # While not in the spec, this form is allowed by emacs:
+ # -*- Tcl -*-
+ # where the implied "variable" is "mode". This form
+ # is only allowed if there are no other variables.
+ emacs_vars["mode"] = emacs_var_strs[0].strip()
+ else:
+ for emacs_var_str in emacs_var_strs:
+ try:
+ variable, value = emacs_var_str.strip().split(':', 1)
+ except ValueError:
+ log.debug("emacs variables error: malformed -*- "
+ "line: %r", emacs_var_str)
+ continue
+ # Lowercase the variable name because Emacs allows "Mode"
+ # or "mode" or "MoDe", etc.
+ emacs_vars[variable.lower()] = value.strip()
+
+ tail = text[-SIZE:]
+ if "Local Variables" in tail:
+ match = self._emacs_local_vars_pat.search(tail)
+ if match:
+ prefix = match.group("prefix")
+ suffix = match.group("suffix")
+ lines = match.group("content").splitlines(0)
+ #print "prefix=%r, suffix=%r, content=%r, lines: %s"\
+ # % (prefix, suffix, match.group("content"), lines)
+
+ # Validate the Local Variables block: proper prefix and suffix
+ # usage.
+ for i, line in enumerate(lines):
+ if not line.startswith(prefix):
+ log.debug("emacs variables error: line '%s' "
+ "does not use proper prefix '%s'"
+ % (line, prefix))
+ return {}
+ # Don't validate suffix on last line. Emacs doesn't care,
+ # neither should we.
+ if i != len(lines)-1 and not line.endswith(suffix):
+ log.debug("emacs variables error: line '%s' "
+ "does not use proper suffix '%s'"
+ % (line, suffix))
+ return {}
+
+ # Parse out one emacs var per line.
+ continued_for = None
+ for line in lines[:-1]: # no var on the last line ("PREFIX End:")
+ if prefix: line = line[len(prefix):] # strip prefix
+ if suffix: line = line[:-len(suffix)] # strip suffix
+ line = line.strip()
+ if continued_for:
+ variable = continued_for
+ if line.endswith('\\'):
+ line = line[:-1].rstrip()
+ else:
+ continued_for = None
+ emacs_vars[variable] += ' ' + line
+ else:
+ try:
+ variable, value = line.split(':', 1)
+ except ValueError:
+ log.debug("local variables error: missing colon "
+ "in local variables entry: '%s'" % line)
+ continue
+ # Do NOT lowercase the variable name, because Emacs only
+ # allows "mode" (and not "Mode", "MoDe", etc.) in this block.
+ value = value.strip()
+ if value.endswith('\\'):
+ value = value[:-1].rstrip()
+ continued_for = variable
+ else:
+ continued_for = None
+ emacs_vars[variable] = value
+
+ # Unquote values.
+ for var, val in emacs_vars.items():
+ if len(val) > 1 and (val.startswith('"') and val.endswith('"')
+ or val.startswith('"') and val.endswith('"')):
+ emacs_vars[var] = val[1:-1]
+
+ return emacs_vars
+
+ # Cribbed from a post by Bart Lateur:
+ # <http://www.nntp.perl.org/group/perl.macperl.anyperl/154>
+ _detab_re = re.compile(r'(.*?)\t', re.M)
+ def _detab_sub(self, match):
+ g1 = match.group(1)
+ return g1 + (' ' * (self.tab_width - len(g1) % self.tab_width))
+ def _detab(self, text):
+ r"""Remove (leading?) tabs from a file.
+
+ >>> m = Markdown()
+ >>> m._detab("\tfoo")
+ ' foo'
+ >>> m._detab(" \tfoo")
+ ' foo'
+ >>> m._detab("\t foo")
+ ' foo'
+ >>> m._detab(" foo")
+ ' foo'
+ >>> m._detab(" foo\n\tbar\tblam")
+ ' foo\n bar blam'
+ """
+ if '\t' not in text:
+ return text
+ return self._detab_re.subn(self._detab_sub, text)[0]
+
+ _block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del'
+ _strict_tag_block_re = re.compile(r"""
+ ( # save in \1
+ ^ # start of line (with re.M)
+ <(%s) # start tag = \2
+ \b # word break
+ (.*\n)*? # any number of lines, minimally matching
+ </\2> # the matching end tag
+ [ \t]* # trailing spaces/tabs
+ (?=\n+|\Z) # followed by a newline or end of document
+ )
+ """ % _block_tags_a,
+ re.X | re.M)
+
+ _block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math'
+ _liberal_tag_block_re = re.compile(r"""
+ ( # save in \1
+ ^ # start of line (with re.M)
+ <(%s) # start tag = \2
+ \b # word break
+ (.*\n)*? # any number of lines, minimally matching
+ .*</\2> # the matching end tag
+ [ \t]* # trailing spaces/tabs
+ (?=\n+|\Z) # followed by a newline or end of document
+ )
+ """ % _block_tags_b,
+ re.X | re.M)
+
+ def _hash_html_block_sub(self, match, raw=False):
+ html = match.group(1)
+ if raw and self.safe_mode:
+ html = self._sanitize_html(html)
+ key = _hash_text(html)
+ self.html_blocks[key] = html
+ return "\n\n" + key + "\n\n"
+
+ def _hash_html_blocks(self, text, raw=False):
+ """Hashify HTML blocks
+
+ We only want to do this for block-level HTML tags, such as headers,
+ lists, and tables. That's because we still want to wrap <p>s around
+ "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+ phrase emphasis, and spans. The list of tags we're looking for is
+ hard-coded.
+
+ @param raw {boolean} indicates if these are raw HTML blocks in
+ the original source. It makes a difference in "safe" mode.
+ """
+ if '<' not in text:
+ return text
+
+ # Pass `raw` value into our calls to self._hash_html_block_sub.
+ hash_html_block_sub = _curry(self._hash_html_block_sub, raw=raw)
+
+ # First, look for nested blocks, e.g.:
+ # <div>
+ # <div>
+ # tags for inner block must be indented.
+ # </div>
+ # </div>
+ #
+ # The outermost tags must start at the left margin for this to match, and
+ # the inner nested divs must be indented.
+ # We need to do this before the next, more liberal match, because the next
+ # match will start at the first `<div>` and stop at the first `</div>`.
+ text = self._strict_tag_block_re.sub(hash_html_block_sub, text)
+
+ # Now match more liberally, simply from `\n<tag>` to `</tag>\n`
+ text = self._liberal_tag_block_re.sub(hash_html_block_sub, text)
+
+ # Special case just for <hr />. It was easier to make a special
+ # case than to make the other regex more complicated.
+ if "<hr" in text:
+ _hr_tag_re = _hr_tag_re_from_tab_width(self.tab_width)
+ text = _hr_tag_re.sub(hash_html_block_sub, text)
+
+ # Special case for standalone HTML comments:
+ if "<!--" in text:
+ start = 0
+ while True:
+ # Delimiters for next comment block.
+ try:
+ start_idx = text.index("<!--", start)
+ except ValueError, ex:
+ break
+ try:
+ end_idx = text.index("-->", start_idx) + 3
+ except ValueError, ex:
+ break
+
+ # Start position for next comment block search.
+ start = end_idx
+
+ # Validate whitespace before comment.
+ if start_idx:
+ # - Up to `tab_width - 1` spaces before start_idx.
+ for i in range(self.tab_width - 1):
+ if text[start_idx - 1] != ' ':
+ break
+ start_idx -= 1
+ if start_idx == 0:
+ break
+ # - Must be preceded by 2 newlines or hit the start of
+ # the document.
+ if start_idx == 0:
+ pass
+ elif start_idx == 1 and text[0] == '\n':
+ start_idx = 0 # to match minute detail of Markdown.pl regex
+ elif text[start_idx-2:start_idx] == '\n\n':
+ pass
+ else:
+ break
+
+ # Validate whitespace after comment.
+ # - Any number of spaces and tabs.
+ while end_idx < len(text):
+ if text[end_idx] not in ' \t':
+ break
+ end_idx += 1
+ # - Must be following by 2 newlines or hit end of text.
+ if text[end_idx:end_idx+2] not in ('', '\n', '\n\n'):
+ continue
+
+ # Escape and hash (must match `_hash_html_block_sub`).
+ html = text[start_idx:end_idx]
+ if raw and self.safe_mode:
+ html = self._sanitize_html(html)
+ key = _hash_text(html)
+ self.html_blocks[key] = html
+ text = text[:start_idx] + "\n\n" + key + "\n\n" + text[end_idx:]
+
+ if "xml" in self.extras:
+ # Treat XML processing instructions and namespaced one-liner
+ # tags as if they were block HTML tags. E.g., if standalone
+ # (i.e. are their own paragraph), the following do not get
+ # wrapped in a <p> tag:
+ # <?foo bar?>
+ #
+ # <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="chapter_1.md"/>
+ _xml_oneliner_re = _xml_oneliner_re_from_tab_width(self.tab_width)
+ text = _xml_oneliner_re.sub(hash_html_block_sub, text)
+
+ return text
+
+ def _strip_link_definitions(self, text):
+ # Strips link definitions from text, stores the URLs and titles in
+ # hash references.
+ less_than_tab = self.tab_width - 1
+
+ # Link defs are in the form:
+ # [id]: url "optional title"
+ _link_def_re = re.compile(r"""
+ ^[ ]{0,%d}\[(.+)\]: # id = \1
+ [ \t]*
+ \n? # maybe *one* newline
+ [ \t]*
+ <?(.+?)>? # url = \2
+ [ \t]*
+ (?:
+ \n? # maybe one newline
+ [ \t]*
+ (?<=\s) # lookbehind for whitespace
+ ['"(]
+ ([^\n]*) # title = \3
+ ['")]
+ [ \t]*
+ )? # title is optional
+ (?:\n+|\Z)
+ """ % less_than_tab, re.X | re.M | re.U)
+ return _link_def_re.sub(self._extract_link_def_sub, text)
+
+ def _extract_link_def_sub(self, match):
+ id, url, title = match.groups()
+ key = id.lower() # Link IDs are case-insensitive
+ self.urls[key] = self._encode_amps_and_angles(url)
+ if title:
+ self.titles[key] = title.replace('"', '&quot;')
+ return ""
+
+ def _extract_footnote_def_sub(self, match):
+ id, text = match.groups()
+ text = _dedent(text, skip_first_line=not text.startswith('\n')).strip()
+ normed_id = re.sub(r'\W', '-', id)
+ # Ensure footnote text ends with a couple newlines (for some
+ # block gamut matches).
+ self.footnotes[normed_id] = text + "\n\n"
+ return ""
+
+ def _strip_footnote_definitions(self, text):
+ """A footnote definition looks like this:
+
+ [^note-id]: Text of the note.
+
+ May include one or more indented paragraphs.
+
+ Where,
+ - The 'note-id' can be pretty much anything, though typically it
+ is the number of the footnote.
+ - The first paragraph may start on the next line, like so:
+
+ [^note-id]:
+ Text of the note.
+ """
+ less_than_tab = self.tab_width - 1
+ footnote_def_re = re.compile(r'''
+ ^[ ]{0,%d}\[\^(.+)\]: # id = \1
+ [ \t]*
+ ( # footnote text = \2
+ # First line need not start with the spaces.
+ (?:\s*.*\n+)
+ (?:
+ (?:[ ]{%d} | \t) # Subsequent lines must be indented.
+ .*\n+
+ )*
+ )
+ # Lookahead for non-space at line-start, or end of doc.
+ (?:(?=^[ ]{0,%d}\S)|\Z)
+ ''' % (less_than_tab, self.tab_width, self.tab_width),
+ re.X | re.M)
+ return footnote_def_re.sub(self._extract_footnote_def_sub, text)
+
+
+ _hr_res = [
+ re.compile(r"^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$", re.M),
+ re.compile(r"^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$", re.M),
+ re.compile(r"^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$", re.M),
+ ]
+
+ def _run_block_gamut(self, text):
+ # These are all the transformations that form block-level
+ # tags like paragraphs, headers, and list items.
+
+ text = self._do_headers(text)
+
+ # Do Horizontal Rules:
+ hr = "\n<hr"+self.empty_element_suffix+"\n"
+ for hr_re in self._hr_res:
+ text = hr_re.sub(hr, text)
+
+ text = self._do_lists(text)
+
+ if "pyshell" in self.extras:
+ text = self._prepare_pyshell_blocks(text)
+
+ text = self._do_code_blocks(text)
+
+ text = self._do_block_quotes(text)
+
+ # We already ran _HashHTMLBlocks() before, in Markdown(), but that
+ # was to escape raw HTML in the original Markdown source. This time,
+ # we're escaping the markup we've just created, so that we don't wrap
+ # <p> tags around block-level tags.
+ text = self._hash_html_blocks(text)
+
+ text = self._form_paragraphs(text)
+
+ return text
+
+ def _pyshell_block_sub(self, match):
+ lines = match.group(0).splitlines(0)
+ _dedentlines(lines)
+ indent = ' ' * self.tab_width
+ s = ('\n' # separate from possible cuddled paragraph
+ + indent + ('\n'+indent).join(lines)
+ + '\n\n')
+ return s
+
+ def _prepare_pyshell_blocks(self, text):
+ """Ensure that Python interactive shell sessions are put in
+ code blocks -- even if not properly indented.
+ """
+ if ">>>" not in text:
+ return text
+
+ less_than_tab = self.tab_width - 1
+ _pyshell_block_re = re.compile(r"""
+ ^([ ]{0,%d})>>>[ ].*\n # first line
+ ^(\1.*\S+.*\n)* # any number of subsequent lines
+ ^\n # ends with a blank line
+ """ % less_than_tab, re.M | re.X)
+
+ return _pyshell_block_re.sub(self._pyshell_block_sub, text)
+
+ def _run_span_gamut(self, text):
+ # These are all the transformations that occur *within* block-level
+ # tags like paragraphs, headers, and list items.
+
+ text = self._do_code_spans(text)
+
+ text = self._escape_special_chars(text)
+
+ # Process anchor and image tags.
+ text = self._do_links(text)
+
+ # Make links out of things like `<http://example.com/>`
+ # Must come after _do_links(), because you can use < and >
+ # delimiters in inline links like [this](<url>).
+ text = self._do_auto_links(text)
+
+ if "link-patterns" in self.extras:
+ text = self._do_link_patterns(text)
+
+ text = self._encode_amps_and_angles(text)
+
+ text = self._do_italics_and_bold(text)
+
+ # Do hard breaks:
+ text = re.sub(r" {2,}\n", " <br%s\n" % self.empty_element_suffix, text)
+
+ return text
+
+ # "Sorta" because auto-links are identified as "tag" tokens.
+ _sorta_html_tokenize_re = re.compile(r"""
+ (
+ # tag
+ </?
+ (?:\w+) # tag name
+ (?:\s+(?:[\w-]+:)?[\w-]+=(?:".*?"|'.*?'))* # attributes
+ \s*/?>
+ |
+ # auto-link (e.g., <http://www.activestate.com/>)
+ <\w+[^>]*>
+ |
+ <!--.*?--> # comment
+ |
+ <\?.*?\?> # processing instruction
+ )
+ """, re.X)
+
+ def _escape_special_chars(self, text):
+ # Python markdown note: the HTML tokenization here differs from
+ # that in Markdown.pl, hence the behaviour for subtle cases can
+ # differ (I believe the tokenizer here does a better job because
+ # it isn't susceptible to unmatched '<' and '>' in HTML tags).
+ # Note, however, that '>' is not allowed in an auto-link URL
+ # here.
+ escaped = []
+ is_html_markup = False
+ for token in self._sorta_html_tokenize_re.split(text):
+ if is_html_markup:
+ # Within tags/HTML-comments/auto-links, encode * and _
+ # so they don't conflict with their use in Markdown for
+ # italics and strong. We're replacing each such
+ # character with its corresponding MD5 checksum value;
+ # this is likely overkill, but it should prevent us from
+ # colliding with the escape values by accident.
+ escaped.append(token.replace('*', g_escape_table['*'])
+ .replace('_', g_escape_table['_']))
+ else:
+ escaped.append(self._encode_backslash_escapes(token))
+ is_html_markup = not is_html_markup
+ return ''.join(escaped)
+
+ def _hash_html_spans(self, text):
+ # Used for safe_mode.
+
+ def _is_auto_link(s):
+ if ':' in s and self._auto_link_re.match(s):
+ return True
+ elif '@' in s and self._auto_email_link_re.match(s):
+ return True
+ return False
+
+ tokens = []
+ is_html_markup = False
+ for token in self._sorta_html_tokenize_re.split(text):
+ if is_html_markup and not _is_auto_link(token):
+ sanitized = self._sanitize_html(token)
+ key = _hash_text(sanitized)
+ self.html_spans[key] = sanitized
+ tokens.append(key)
+ else:
+ tokens.append(token)
+ is_html_markup = not is_html_markup
+ return ''.join(tokens)
+
+ def _unhash_html_spans(self, text):
+ for key, sanitized in self.html_spans.items():
+ text = text.replace(key, sanitized)
+ return text
+
+ def _sanitize_html(self, s):
+ if self.safe_mode == "replace":
+ return self.html_removed_text
+ elif self.safe_mode == "escape":
+ replacements = [
+ ('&', '&amp;'),
+ ('<', '&lt;'),
+ ('>', '&gt;'),
+ ]
+ for before, after in replacements:
+ s = s.replace(before, after)
+ return s
+ else:
+ raise MarkdownError("invalid value for 'safe_mode': %r (must be "
+ "'escape' or 'replace')" % self.safe_mode)
+
+ _tail_of_inline_link_re = re.compile(r'''
+ # Match tail of: [text](/url/) or [text](/url/ "title")
+ \( # literal paren
+ [ \t]*
+ (?P<url> # \1
+ <.*?>
+ |
+ .*?
+ )
+ [ \t]*
+ ( # \2
+ (['"]) # quote char = \3
+ (?P<title>.*?)
+ \3 # matching quote
+ )? # title is optional
+ \)
+ ''', re.X | re.S)
+ _tail_of_reference_link_re = re.compile(r'''
+ # Match tail of: [text][id]
+ [ ]? # one optional space
+ (?:\n[ ]*)? # one optional newline followed by spaces
+ \[
+ (?P<id>.*?)
+ \]
+ ''', re.X | re.S)
+
+ def _do_links(self, text):
+ """Turn Markdown link shortcuts into XHTML <a> and <img> tags.
+
+ This is a combination of Markdown.pl's _DoAnchors() and
+ _DoImages(). They are done together because that simplified the
+ approach. It was necessary to use a different approach than
+ Markdown.pl because of the lack of atomic matching support in
+ Python's regex engine used in $g_nested_brackets.
+ """
+ MAX_LINK_TEXT_SENTINEL = 3000 # markdown2 issue 24
+
+ # `anchor_allowed_pos` is used to support img links inside
+ # anchors, but not anchors inside anchors. An anchor's start
+ # pos must be `>= anchor_allowed_pos`.
+ anchor_allowed_pos = 0
+
+ curr_pos = 0
+ while True: # Handle the next link.
+ # The next '[' is the start of:
+ # - an inline anchor: [text](url "title")
+ # - a reference anchor: [text][id]
+ # - an inline img: ![text](url "title")
+ # - a reference img: ![text][id]
+ # - a footnote ref: [^id]
+ # (Only if 'footnotes' extra enabled)
+ # - a footnote defn: [^id]: ...
+ # (Only if 'footnotes' extra enabled) These have already
+ # been stripped in _strip_footnote_definitions() so no
+ # need to watch for them.
+ # - a link definition: [id]: url "title"
+ # These have already been stripped in
+ # _strip_link_definitions() so no need to watch for them.
+ # - not markup: [...anything else...
+ try:
+ start_idx = text.index('[', curr_pos)
+ except ValueError:
+ break
+ text_length = len(text)
+
+ # Find the matching closing ']'.
+ # Markdown.pl allows *matching* brackets in link text so we
+ # will here too. Markdown.pl *doesn't* currently allow
+ # matching brackets in img alt text -- we'll differ in that
+ # regard.
+ bracket_depth = 0
+ for p in range(start_idx+1, min(start_idx+MAX_LINK_TEXT_SENTINEL,
+ text_length)):
+ ch = text[p]
+ if ch == ']':
+ bracket_depth -= 1
+ if bracket_depth < 0:
+ break
+ elif ch == '[':
+ bracket_depth += 1
+ else:
+ # Closing bracket not found within sentinel length.
+ # This isn't markup.
+ curr_pos = start_idx + 1
+ continue
+ link_text = text[start_idx+1:p]
+
+ # Possibly a footnote ref?
+ if "footnotes" in self.extras and link_text.startswith("^"):
+ normed_id = re.sub(r'\W', '-', link_text[1:])
+ if normed_id in self.footnotes:
+ self.footnote_ids.append(normed_id)
+ result = '<sup class="footnote-ref" id="fnref-%s">' \
+ '<a href="#fn-%s">%s</a></sup>' \
+ % (normed_id, normed_id, len(self.footnote_ids))
+ text = text[:start_idx] + result + text[p+1:]
+ else:
+ # This id isn't defined, leave the markup alone.
+ curr_pos = p+1
+ continue
+
+ # Now determine what this is by the remainder.
+ p += 1
+ if p == text_length:
+ return text
+
+ # Inline anchor or img?
+ if text[p] == '(': # attempt at perf improvement
+ match = self._tail_of_inline_link_re.match(text, p)
+ if match:
+ # Handle an inline anchor or img.
+ is_img = start_idx > 0 and text[start_idx-1] == "!"
+ if is_img:
+ start_idx -= 1
+
+ url, title = match.group("url"), match.group("title")
+ if url and url[0] == '<':
+ url = url[1:-1] # '<url>' -> 'url'
+ # We've got to encode these to avoid conflicting
+ # with italics/bold.
+ url = url.replace('*', g_escape_table['*']) \
+ .replace('_', g_escape_table['_'])
+ if title:
+ title_str = ' title="%s"' \
+ % title.replace('*', g_escape_table['*']) \
+ .replace('_', g_escape_table['_']) \
+ .replace('"', '&quot;')
+ else:
+ title_str = ''
+ if is_img:
+ result = '<img src="%s" alt="%s"%s%s' \
+ % (url, link_text.replace('"', '&quot;'),
+ title_str, self.empty_element_suffix)
+ curr_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[match.end():]
+ elif start_idx >= anchor_allowed_pos:
+ result_head = '<a href="%s"%s>' % (url, title_str)
+ result = '%s%s</a>' % (result_head, link_text)
+ # <img> allowed from curr_pos on, <a> from
+ # anchor_allowed_pos on.
+ curr_pos = start_idx + len(result_head)
+ anchor_allowed_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[match.end():]
+ else:
+ # Anchor not allowed here.
+ curr_pos = start_idx + 1
+ continue
+
+ # Reference anchor or img?
+ else:
+ match = self._tail_of_reference_link_re.match(text, p)
+ if match:
+ # Handle a reference-style anchor or img.
+ is_img = start_idx > 0 and text[start_idx-1] == "!"
+ if is_img:
+ start_idx -= 1
+ link_id = match.group("id").lower()
+ if not link_id:
+ link_id = link_text.lower() # for links like [this][]
+ if link_id in self.urls:
+ url = self.urls[link_id]
+ # We've got to encode these to avoid conflicting
+ # with italics/bold.
+ url = url.replace('*', g_escape_table['*']) \
+ .replace('_', g_escape_table['_'])
+ title = self.titles.get(link_id)
+ if title:
+ title = title.replace('*', g_escape_table['*']) \
+ .replace('_', g_escape_table['_'])
+ title_str = ' title="%s"' % title
+ else:
+ title_str = ''
+ if is_img:
+ result = '<img src="%s" alt="%s"%s%s' \
+ % (url, link_text.replace('"', '&quot;'),
+ title_str, self.empty_element_suffix)
+ curr_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[match.end():]
+ elif start_idx >= anchor_allowed_pos:
+ result = '<a href="%s"%s>%s</a>' \
+ % (url, title_str, link_text)
+ result_head = '<a href="%s"%s>' % (url, title_str)
+ result = '%s%s</a>' % (result_head, link_text)
+ # <img> allowed from curr_pos on, <a> from
+ # anchor_allowed_pos on.
+ curr_pos = start_idx + len(result_head)
+ anchor_allowed_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[match.end():]
+ else:
+ # Anchor not allowed here.
+ curr_pos = start_idx + 1
+ else:
+ # This id isn't defined, leave the markup alone.
+ curr_pos = match.end()
+ continue
+
+ # Otherwise, it isn't markup.
+ curr_pos = start_idx + 1
+
+ return text
+
+
+ _setext_h_re = re.compile(r'^(.+)[ \t]*\n(=+|-+)[ \t]*\n+', re.M)
+ def _setext_h_sub(self, match):
+ n = {"=": 1, "-": 2}[match.group(2)[0]]
+ demote_headers = self.extras.get("demote-headers")
+ if demote_headers:
+ n = min(n + demote_headers, 6)
+ return "<h%d>%s</h%d>\n\n" \
+ % (n, self._run_span_gamut(match.group(1)), n)
+
+ _atx_h_re = re.compile(r'''
+ ^(\#{1,6}) # \1 = string of #'s
+ [ \t]*
+ (.+?) # \2 = Header text
+ [ \t]*
+ (?<!\\) # ensure not an escaped trailing '#'
+ \#* # optional closing #'s (not counted)
+ \n+
+ ''', re.X | re.M)
+ def _atx_h_sub(self, match):
+ n = len(match.group(1))
+ demote_headers = self.extras.get("demote-headers")
+ if demote_headers:
+ n = min(n + demote_headers, 6)
+ return "<h%d>%s</h%d>\n\n" \
+ % (n, self._run_span_gamut(match.group(2)), n)
+
+ def _do_headers(self, text):
+ # Setext-style headers:
+ # Header 1
+ # ========
+ #
+ # Header 2
+ # --------
+ text = self._setext_h_re.sub(self._setext_h_sub, text)
+
+ # atx-style headers:
+ # # Header 1
+ # ## Header 2
+ # ## Header 2 with closing hashes ##
+ # ...
+ # ###### Header 6
+ text = self._atx_h_re.sub(self._atx_h_sub, text)
+
+ return text
+
+
+ _marker_ul_chars = '*+-'
+ _marker_any = r'(?:[%s]|\d+\.)' % _marker_ul_chars
+ _marker_ul = '(?:[%s])' % _marker_ul_chars
+ _marker_ol = r'(?:\d+\.)'
+
+ def _list_sub(self, match):
+ lst = match.group(1)
+ lst_type = match.group(3) in self._marker_ul_chars and "ul" or "ol"
+ result = self._process_list_items(lst)
+ if self.list_level:
+ return "<%s>\n%s</%s>\n" % (lst_type, result, lst_type)
+ else:
+ return "<%s>\n%s</%s>\n\n" % (lst_type, result, lst_type)
+
+ def _do_lists(self, text):
+ # Form HTML ordered (numbered) and unordered (bulleted) lists.
+
+ for marker_pat in (self._marker_ul, self._marker_ol):
+ # Re-usable pattern to match any entire ul or ol list:
+ less_than_tab = self.tab_width - 1
+ whole_list = r'''
+ ( # \1 = whole list
+ ( # \2
+ [ ]{0,%d}
+ (%s) # \3 = first list item marker
+ [ \t]+
+ )
+ (?:.+?)
+ ( # \4
+ \Z
+ |
+ \n{2,}
+ (?=\S)
+ (?! # Negative lookahead for another list item marker
+ [ \t]*
+ %s[ \t]+
+ )
+ )
+ )
+ ''' % (less_than_tab, marker_pat, marker_pat)
+
+ # We use a different prefix before nested lists than top-level lists.
+ # See extended comment in _process_list_items().
+ #
+ # Note: There's a bit of duplication here. My original implementation
+ # created a scalar regex pattern as the conditional result of the test on
+ # $g_list_level, and then only ran the $text =~ s{...}{...}egmx
+ # substitution once, using the scalar as the pattern. This worked,
+ # everywhere except when running under MT on my hosting account at Pair
+ # Networks. There, this caused all rebuilds to be killed by the reaper (or
+ # perhaps they crashed, but that seems incredibly unlikely given that the
+ # same script on the same server ran fine *except* under MT. I've spent
+ # more time trying to figure out why this is happening than I'd like to
+ # admit. My only guess, backed up by the fact that this workaround works,
+ # is that Perl optimizes the substition when it can figure out that the
+ # pattern will never change, and when this optimization isn't on, we run
+ # afoul of the reaper. Thus, the slightly redundant code to that uses two
+ # static s/// patterns rather than one conditional pattern.
+
+ if self.list_level:
+ sub_list_re = re.compile("^"+whole_list, re.X | re.M | re.S)
+ text = sub_list_re.sub(self._list_sub, text)
+ else:
+ list_re = re.compile(r"(?:(?<=\n\n)|\A\n?)"+whole_list,
+ re.X | re.M | re.S)
+ text = list_re.sub(self._list_sub, text)
+
+ return text
+
+ _list_item_re = re.compile(r'''
+ (\n)? # leading line = \1
+ (^[ \t]*) # leading whitespace = \2
+ (%s) [ \t]+ # list marker = \3
+ ((?:.+?) # list item text = \4
+ (\n{1,2})) # eols = \5
+ (?= \n* (\Z | \2 (%s) [ \t]+))
+ ''' % (_marker_any, _marker_any),
+ re.M | re.X | re.S)
+
+ _last_li_endswith_two_eols = False
+ def _list_item_sub(self, match):
+ item = match.group(4)
+ leading_line = match.group(1)
+ leading_space = match.group(2)
+ if leading_line or "\n\n" in item or self._last_li_endswith_two_eols:
+ item = self._run_block_gamut(self._outdent(item))
+ else:
+ # Recursion for sub-lists:
+ item = self._do_lists(self._outdent(item))
+ if item.endswith('\n'):
+ item = item[:-1]
+ item = self._run_span_gamut(item)
+ self._last_li_endswith_two_eols = (len(match.group(5)) == 2)
+ return "<li>%s</li>\n" % item
+
+ def _process_list_items(self, list_str):
+ # Process the contents of a single ordered or unordered list,
+ # splitting it into individual list items.
+
+ # The $g_list_level global keeps track of when we're inside a list.
+ # Each time we enter a list, we increment it; when we leave a list,
+ # we decrement. If it's zero, we're not in a list anymore.
+ #
+ # We do this because when we're not inside a list, we want to treat
+ # something like this:
+ #
+ # I recommend upgrading to version
+ # 8. Oops, now this line is treated
+ # as a sub-list.
+ #
+ # As a single paragraph, despite the fact that the second line starts
+ # with a digit-period-space sequence.
+ #
+ # Whereas when we're inside a list (or sub-list), that line will be
+ # treated as the start of a sub-list. What a kludge, huh? This is
+ # an aspect of Markdown's syntax that's hard to parse perfectly
+ # without resorting to mind-reading. Perhaps the solution is to
+ # change the syntax rules such that sub-lists must start with a
+ # starting cardinal number; e.g. "1." or "a.".
+ self.list_level += 1
+ self._last_li_endswith_two_eols = False
+ list_str = list_str.rstrip('\n') + '\n'
+ list_str = self._list_item_re.sub(self._list_item_sub, list_str)
+ self.list_level -= 1
+ return list_str
+
+ def _get_pygments_lexer(self, lexer_name):
+ try:
+ from pygments import lexers, util
+ except ImportError:
+ return None
+ try:
+ return lexers.get_lexer_by_name(lexer_name)
+ except util.ClassNotFound:
+ return None
+
+ def _color_with_pygments(self, codeblock, lexer, **formatter_opts):
+ import pygments
+ import pygments.formatters
+
+ class HtmlCodeFormatter(pygments.formatters.HtmlFormatter):
+ def _wrap_code(self, inner):
+ """A function for use in a Pygments Formatter which
+ wraps in <code> tags.
+ """
+ yield 0, "<code>"
+ for tup in inner:
+ yield tup
+ yield 0, "</code>"
+
+ def wrap(self, source, outfile):
+ """Return the source with a code, pre, and div."""
+ return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
+
+ formatter = HtmlCodeFormatter(cssclass="codehilite", **formatter_opts)
+ return pygments.highlight(codeblock, lexer, formatter)
+
+ def _code_block_sub(self, match):
+ codeblock = match.group(1)
+ codeblock = self._outdent(codeblock)
+ codeblock = self._detab(codeblock)
+ codeblock = codeblock.lstrip('\n') # trim leading newlines
+ codeblock = codeblock.rstrip() # trim trailing whitespace
+
+ if "code-color" in self.extras and codeblock.startswith(":::"):
+ lexer_name, rest = codeblock.split('\n', 1)
+ lexer_name = lexer_name[3:].strip()
+ lexer = self._get_pygments_lexer(lexer_name)
+ codeblock = rest.lstrip("\n") # Remove lexer declaration line.
+ if lexer:
+ formatter_opts = self.extras['code-color'] or {}
+ colored = self._color_with_pygments(codeblock, lexer,
+ **formatter_opts)
+ return "\n\n%s\n\n" % colored
+
+ codeblock = self._encode_code(codeblock)
+ return "\n\n<pre><code>%s\n</code></pre>\n\n" % codeblock
+
+ def _do_code_blocks(self, text):
+ """Process Markdown `<pre><code>` blocks."""
+ code_block_re = re.compile(r'''
+ (?:\n\n|\A)
+ ( # $1 = the code block -- one or more lines, starting with a space/tab
+ (?:
+ (?:[ ]{%d} | \t) # Lines must start with a tab or a tab-width of spaces
+ .*\n+
+ )+
+ )
+ ((?=^[ ]{0,%d}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
+ ''' % (self.tab_width, self.tab_width),
+ re.M | re.X)
+
+ return code_block_re.sub(self._code_block_sub, text)
+
+
+ # Rules for a code span:
+ # - backslash escapes are not interpreted in a code span
+ # - to include one or or a run of more backticks the delimiters must
+ # be a longer run of backticks
+ # - cannot start or end a code span with a backtick; pad with a
+ # space and that space will be removed in the emitted HTML
+ # See `test/tm-cases/escapes.text` for a number of edge-case
+ # examples.
+ _code_span_re = re.compile(r'''
+ (?<!\\)
+ (`+) # \1 = Opening run of `
+ (?!`) # See Note A test/tm-cases/escapes.text
+ (.+?) # \2 = The code block
+ (?<!`)
+ \1 # Matching closer
+ (?!`)
+ ''', re.X | re.S)
+
+ def _code_span_sub(self, match):
+ c = match.group(2).strip(" \t")
+ c = self._encode_code(c)
+ return "<code>%s</code>" % c
+
+ def _do_code_spans(self, text):
+ # * Backtick quotes are used for <code></code> spans.
+ #
+ # * You can use multiple backticks as the delimiters if you want to
+ # include literal backticks in the code span. So, this input:
+ #
+ # Just type ``foo `bar` baz`` at the prompt.
+ #
+ # Will translate to:
+ #
+ # <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
+ #
+ # There's no arbitrary limit to the number of backticks you
+ # can use as delimters. If you need three consecutive backticks
+ # in your code, use four for delimiters, etc.
+ #
+ # * You can use spaces to get literal backticks at the edges:
+ #
+ # ... type `` `bar` `` ...
+ #
+ # Turns to:
+ #
+ # ... type <code>`bar`</code> ...
+ return self._code_span_re.sub(self._code_span_sub, text)
+
+ def _encode_code(self, text):
+ """Encode/escape certain characters inside Markdown code runs.
+ The point is that in code, these characters are literals,
+ and lose their special Markdown meanings.
+ """
+ replacements = [
+ # Encode all ampersands; HTML entities are not
+ # entities within a Markdown code span.
+ ('&', '&amp;'),
+ # Do the angle bracket song and dance:
+ ('<', '&lt;'),
+ ('>', '&gt;'),
+ # Now, escape characters that are magic in Markdown:
+ ('*', g_escape_table['*']),
+ ('_', g_escape_table['_']),
+ ('{', g_escape_table['{']),
+ ('}', g_escape_table['}']),
+ ('[', g_escape_table['[']),
+ (']', g_escape_table[']']),
+ ('\\', g_escape_table['\\']),
+ ]
+ for before, after in replacements:
+ text = text.replace(before, after)
+ return text
+
+ _strong_re = re.compile(r"(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1", re.S)
+ _em_re = re.compile(r"(\*|_)(?=\S)(.+?)(?<=\S)\1", re.S)
+ _code_friendly_strong_re = re.compile(r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S)
+ _code_friendly_em_re = re.compile(r"\*(?=\S)(.+?)(?<=\S)\*", re.S)
+ def _do_italics_and_bold(self, text):
+ # <strong> must go first:
+ if "code-friendly" in self.extras:
+ text = self._code_friendly_strong_re.sub(r"<strong>\1</strong>", text)
+ text = self._code_friendly_em_re.sub(r"<em>\1</em>", text)
+ else:
+ text = self._strong_re.sub(r"<strong>\2</strong>", text)
+ text = self._em_re.sub(r"<em>\2</em>", text)
+ return text
+
+
+ _block_quote_re = re.compile(r'''
+ ( # Wrap whole match in \1
+ (
+ ^[ \t]*>[ \t]? # '>' at the start of a line
+ .+\n # rest of the first line
+ (.+\n)* # subsequent consecutive lines
+ \n* # blanks
+ )+
+ )
+ ''', re.M | re.X)
+ _bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M);
+
+ _html_pre_block_re = re.compile(r'(\s*<pre>.+?</pre>)', re.S)
+ def _dedent_two_spaces_sub(self, match):
+ return re.sub(r'(?m)^ ', '', match.group(1))
+
+ def _block_quote_sub(self, match):
+ bq = match.group(1)
+ bq = self._bq_one_level_re.sub('', bq) # trim one level of quoting
+ bq = self._ws_only_line_re.sub('', bq) # trim whitespace-only lines
+ bq = self._run_block_gamut(bq) # recurse
+
+ bq = re.sub('(?m)^', ' ', bq)
+ # These leading spaces screw with <pre> content, so we need to fix that:
+ bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq)
+
+ return "<blockquote>\n%s\n</blockquote>\n\n" % bq
+
+ def _do_block_quotes(self, text):
+ if '>' not in text:
+ return text
+ return self._block_quote_re.sub(self._block_quote_sub, text)
+
+ def _form_paragraphs(self, text):
+ # Strip leading and trailing lines:
+ text = text.strip('\n')
+
+ # Wrap <p> tags.
+ grafs = re.split(r"\n{2,}", text)
+ for i, graf in enumerate(grafs):
+ if graf in self.html_blocks:
+ # Unhashify HTML blocks
+ grafs[i] = self.html_blocks[graf]
+ else:
+ # Wrap <p> tags.
+ graf = self._run_span_gamut(graf)
+ grafs[i] = "<p>" + graf.lstrip(" \t") + "</p>"
+
+ return "\n\n".join(grafs)
+
+ def _add_footnotes(self, text):
+ if self.footnotes:
+ footer = [
+ '<div class="footnotes">',
+ '<hr' + self.empty_element_suffix,
+ '<ol>',
+ ]
+ for i, id in enumerate(self.footnote_ids):
+ if i != 0:
+ footer.append('')
+ footer.append('<li id="fn-%s">' % id)
+ footer.append(self._run_block_gamut(self.footnotes[id]))
+ backlink = ('<a href="#fnref-%s" '
+ 'class="footnoteBackLink" '
+ 'title="Jump back to footnote %d in the text.">'
+ '&#8617;</a>' % (id, i+1))
+ if footer[-1].endswith("</p>"):
+ footer[-1] = footer[-1][:-len("</p>")] \
+ + '&nbsp;' + backlink + "</p>"
+ else:
+ footer.append("\n<p>%s</p>" % backlink)
+ footer.append('</li>')
+ footer.append('</ol>')
+ footer.append('</div>')
+ return text + '\n\n' + '\n'.join(footer)
+ else:
+ return text
+
+ # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+ # http://bumppo.net/projects/amputator/
+ _ampersand_re = re.compile(r'&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)')
+ _naked_lt_re = re.compile(r'<(?![a-z/?\$!])', re.I)
+ _naked_gt_re = re.compile(r'''(?<![a-z?!/'"-])>''', re.I)
+
+ def _encode_amps_and_angles(self, text):
+ # Smart processing for ampersands and angle brackets that need
+ # to be encoded.
+ text = self._ampersand_re.sub('&amp;', text)
+
+ # Encode naked <'s
+ text = self._naked_lt_re.sub('&lt;', text)
+
+ # Encode naked >'s
+ # Note: Other markdown implementations (e.g. Markdown.pl, PHP
+ # Markdown) don't do this.
+ text = self._naked_gt_re.sub('&gt;', text)
+ return text
+
+ def _encode_backslash_escapes(self, text):
+ for ch, escape in g_escape_table.items():
+ text = text.replace("\\"+ch, escape)
+ return text
+
+ _auto_link_re = re.compile(r'<((https?|ftp):[^\'">\s]+)>', re.I)
+ def _auto_link_sub(self, match):
+ g1 = match.group(1)
+ return '<a href="%s">%s</a>' % (g1, g1)
+
+ _auto_email_link_re = re.compile(r"""
+ <
+ (?:mailto:)?
+ (
+ [-.\w]+
+ \@
+ [-\w]+(\.[-\w]+)*\.[a-z]+
+ )
+ >
+ """, re.I | re.X | re.U)
+ def _auto_email_link_sub(self, match):
+ return self._encode_email_address(
+ self._unescape_special_chars(match.group(1)))
+
+ def _do_auto_links(self, text):
+ text = self._auto_link_re.sub(self._auto_link_sub, text)
+ text = self._auto_email_link_re.sub(self._auto_email_link_sub, text)
+ return text
+
+ def _encode_email_address(self, addr):
+ # Input: an email address, e.g. "foo@example.com"
+ #
+ # Output: the email address as a mailto link, with each character
+ # of the address encoded as either a decimal or hex entity, in
+ # the hopes of foiling most address harvesting spam bots. E.g.:
+ #
+ # <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
+ # x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
+ # &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
+ #
+ # Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
+ # mailing list: <http://tinyurl.com/yu7ue>
+ chars = [_xml_encode_email_char_at_random(ch)
+ for ch in "mailto:" + addr]
+ # Strip the mailto: from the visible part.
+ addr = '<a href="%s">%s</a>' \
+ % (''.join(chars), ''.join(chars[7:]))
+ return addr
+
+ def _do_link_patterns(self, text):
+ """Caveat emptor: there isn't much guarding against link
+ patterns being formed inside other standard Markdown links, e.g.
+ inside a [link def][like this].
+
+ Dev Notes: *Could* consider prefixing regexes with a negative
+ lookbehind assertion to attempt to guard against this.
+ """
+ link_from_hash = {}
+ for regex, repl in self.link_patterns:
+ replacements = []
+ for match in regex.finditer(text):
+ if hasattr(repl, "__call__"):
+ href = repl(match)
+ else:
+ href = match.expand(repl)
+ replacements.append((match.span(), href))
+ for (start, end), href in reversed(replacements):
+ escaped_href = (
+ href.replace('"', '&quot;') # b/c of attr quote
+ # To avoid markdown <em> and <strong>:
+ .replace('*', g_escape_table['*'])
+ .replace('_', g_escape_table['_']))
+ link = '<a href="%s">%s</a>' % (escaped_href, text[start:end])
+ hash = md5(link).hexdigest()
+ link_from_hash[hash] = link
+ text = text[:start] + hash + text[end:]
+ for hash, link in link_from_hash.items():
+ text = text.replace(hash, link)
+ return text
+
+ def _unescape_special_chars(self, text):
+ # Swap back in all the special characters we've hidden.
+ for ch, hash in g_escape_table.items():
+ text = text.replace(hash, ch)
+ return text
+
+ def _outdent(self, text):
+ # Remove one level of line-leading tabs or spaces
+ return self._outdent_re.sub('', text)
+
+
+class MarkdownWithExtras(Markdown):
+ """A markdowner class that enables most extras:
+
+ - footnotes
+ - code-color (only has effect if 'pygments' Python module on path)
+
+ These are not included:
+ - pyshell (specific to Python-related documenting)
+ - code-friendly (because it *disables* part of the syntax)
+ - link-patterns (because you need to specify some actual
+ link-patterns anyway)
+ """
+ extras = ["footnotes", "code-color"]
+
+
+#---- internal support functions
+
+# From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549
+def _curry(*args, **kwargs):
+ function, args = args[0], args[1:]
+ def result(*rest, **kwrest):
+ combined = kwargs.copy()
+ combined.update(kwrest)
+ return function(*args + rest, **combined)
+ return result
+
+# Recipe: regex_from_encoded_pattern (1.0)
+def _regex_from_encoded_pattern(s):
+ """'foo' -> re.compile(re.escape('foo'))
+ '/foo/' -> re.compile('foo')
+ '/foo/i' -> re.compile('foo', re.I)
+ """
+ if s.startswith('/') and s.rfind('/') != 0:
+ # Parse it: /PATTERN/FLAGS
+ idx = s.rfind('/')
+ pattern, flags_str = s[1:idx], s[idx+1:]
+ flag_from_char = {
+ "i": re.IGNORECASE,
+ "l": re.LOCALE,
+ "s": re.DOTALL,
+ "m": re.MULTILINE,
+ "u": re.UNICODE,
+ }
+ flags = 0
+ for char in flags_str:
+ try:
+ flags |= flag_from_char[char]
+ except KeyError:
+ raise ValueError("unsupported regex flag: '%s' in '%s' "
+ "(must be one of '%s')"
+ % (char, s, ''.join(flag_from_char.keys())))
+ return re.compile(s[1:idx], flags)
+ else: # not an encoded regex
+ return re.compile(re.escape(s))
+
+# Recipe: dedent (0.1.2)
+def _dedentlines(lines, tabsize=8, skip_first_line=False):
+ """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines
+
+ "lines" is a list of lines to dedent.
+ "tabsize" is the tab width to use for indent width calculations.
+ "skip_first_line" is a boolean indicating if the first line should
+ be skipped for calculating the indent width and for dedenting.
+ This is sometimes useful for docstrings and similar.
+
+ Same as dedent() except operates on a sequence of lines. Note: the
+ lines list is modified **in-place**.
+ """
+ DEBUG = False
+ if DEBUG:
+ print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
+ % (tabsize, skip_first_line)
+ indents = []
+ margin = None
+ for i, line in enumerate(lines):
+ if i == 0 and skip_first_line: continue
+ indent = 0
+ for ch in line:
+ if ch == ' ':
+ indent += 1
+ elif ch == '\t':
+ indent += tabsize - (indent % tabsize)
+ elif ch in '\r\n':
+ continue # skip all-whitespace lines
+ else:
+ break
+ else:
+ continue # skip all-whitespace lines
+ if DEBUG: print "dedent: indent=%d: %r" % (indent, line)
+ if margin is None:
+ margin = indent
+ else:
+ margin = min(margin, indent)
+ if DEBUG: print "dedent: margin=%r" % margin
+
+ if margin is not None and margin > 0:
+ for i, line in enumerate(lines):
+ if i == 0 and skip_first_line: continue
+ removed = 0
+ for j, ch in enumerate(line):
+ if ch == ' ':
+ removed += 1
+ elif ch == '\t':
+ removed += tabsize - (removed % tabsize)
+ elif ch in '\r\n':
+ if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line
+ lines[i] = lines[i][j:]
+ break
+ else:
+ raise ValueError("unexpected non-whitespace char %r in "
+ "line %r while removing %d-space margin"
+ % (ch, line, margin))
+ if DEBUG:
+ print "dedent: %r: %r -> removed %d/%d"\
+ % (line, ch, removed, margin)
+ if removed == margin:
+ lines[i] = lines[i][j+1:]
+ break
+ elif removed > margin:
+ lines[i] = ' '*(removed-margin) + lines[i][j+1:]
+ break
+ else:
+ if removed:
+ lines[i] = lines[i][removed:]
+ return lines
+
+def _dedent(text, tabsize=8, skip_first_line=False):
+ """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text
+
+ "text" is the text to dedent.
+ "tabsize" is the tab width to use for indent width calculations.
+ "skip_first_line" is a boolean indicating if the first line should
+ be skipped for calculating the indent width and for dedenting.
+ This is sometimes useful for docstrings and similar.
+
+ textwrap.dedent(s), but don't expand tabs to spaces
+ """
+ lines = text.splitlines(1)
+ _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line)
+ return ''.join(lines)
+
+
+class _memoized(object):
+ """Decorator that caches a function's return value each time it is called.
+ If called later with the same arguments, the cached value is returned, and
+ not re-evaluated.
+
+ http://wiki.python.org/moin/PythonDecoratorLibrary
+ """
+ def __init__(self, func):
+ self.func = func
+ self.cache = {}
+ def __call__(self, *args):
+ try:
+ return self.cache[args]
+ except KeyError:
+ self.cache[args] = value = self.func(*args)
+ return value
+ except TypeError:
+ # uncachable -- for instance, passing a list as an argument.
+ # Better to not cache than to blow up entirely.
+ return self.func(*args)
+ def __repr__(self):
+ """Return the function's docstring."""
+ return self.func.__doc__
+
+
+def _xml_oneliner_re_from_tab_width(tab_width):
+ """Standalone XML processing instruction regex."""
+ return re.compile(r"""
+ (?:
+ (?<=\n\n) # Starting after a blank line
+ | # or
+ \A\n? # the beginning of the doc
+ )
+ ( # save in $1
+ [ ]{0,%d}
+ (?:
+ <\?\w+\b\s+.*?\?> # XML processing instruction
+ |
+ <\w+:\w+\b\s+.*?/> # namespaced single tag
+ )
+ [ \t]*
+ (?=\n{2,}|\Z) # followed by a blank line or end of document
+ )
+ """ % (tab_width - 1), re.X)
+_xml_oneliner_re_from_tab_width = _memoized(_xml_oneliner_re_from_tab_width)
+
+def _hr_tag_re_from_tab_width(tab_width):
+ return re.compile(r"""
+ (?:
+ (?<=\n\n) # Starting after a blank line
+ | # or
+ \A\n? # the beginning of the doc
+ )
+ ( # save in \1
+ [ ]{0,%d}
+ <(hr) # start tag = \2
+ \b # word break
+ ([^<>])*? #
+ /?> # the matching end tag
+ [ \t]*
+ (?=\n{2,}|\Z) # followed by a blank line or end of document
+ )
+ """ % (tab_width - 1), re.X)
+_hr_tag_re_from_tab_width = _memoized(_hr_tag_re_from_tab_width)
+
+
+def _xml_encode_email_char_at_random(ch):
+ r = random()
+ # Roughly 10% raw, 45% hex, 45% dec.
+ # '@' *must* be encoded. I [John Gruber] insist.
+ # Issue 26: '_' must be encoded.
+ if r > 0.9 and ch not in "@_":
+ return ch
+ elif r < 0.45:
+ # The [1:] is to drop leading '0': 0x63 -> x63
+ return '&#%s;' % hex(ord(ch))[1:]
+ else:
+ return '&#%s;' % ord(ch)
+
+def _hash_text(text):
+ return 'md5:'+md5(text.encode("utf-8")).hexdigest()
+
+
+#---- mainline
+
+class _NoReflowFormatter(optparse.IndentedHelpFormatter):
+ """An optparse formatter that does NOT reflow the description."""
+ def format_description(self, description):
+ return description or ""
+
+def _test():
+ import doctest
+ doctest.testmod()
+
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv
+ if not logging.root.handlers:
+ logging.basicConfig()
+
+ usage = "usage: %prog [PATHS...]"
+ version = "%prog "+__version__
+ parser = optparse.OptionParser(prog="markdown2", usage=usage,
+ version=version, description=cmdln_desc,
+ formatter=_NoReflowFormatter())
+ parser.add_option("-v", "--verbose", dest="log_level",
+ action="store_const", const=logging.DEBUG,
+ help="more verbose output")
+ parser.add_option("--encoding",
+ help="specify encoding of text content")
+ parser.add_option("--html4tags", action="store_true", default=False,
+ help="use HTML 4 style for empty element tags")
+ parser.add_option("-s", "--safe", metavar="MODE", dest="safe_mode",
+ help="sanitize literal HTML: 'escape' escapes "
+ "HTML meta chars, 'replace' replaces with an "
+ "[HTML_REMOVED] note")
+ parser.add_option("-x", "--extras", action="append",
+ help="Turn on specific extra features (not part of "
+ "the core Markdown spec). Supported values: "
+ "'code-friendly' disables _/__ for emphasis; "
+ "'code-color' adds code-block syntax coloring; "
+ "'link-patterns' adds auto-linking based on patterns; "
+ "'footnotes' adds the footnotes syntax;"
+ "'xml' passes one-liner processing instructions and namespaced XML tags;"
+ "'pyshell' to put unindented Python interactive shell sessions in a <code> block.")
+ parser.add_option("--use-file-vars",
+ help="Look for and use Emacs-style 'markdown-extras' "
+ "file var to turn on extras. See "
+ "<http://code.google.com/p/python-markdown2/wiki/Extras>.")
+ parser.add_option("--link-patterns-file",
+ help="path to a link pattern file")
+ parser.add_option("--self-test", action="store_true",
+ help="run internal self-tests (some doctests)")
+ parser.add_option("--compare", action="store_true",
+ help="run against Markdown.pl as well (for testing)")
+ parser.set_defaults(log_level=logging.INFO, compare=False,
+ encoding="utf-8", safe_mode=None, use_file_vars=False)
+ opts, paths = parser.parse_args()
+ log.setLevel(opts.log_level)
+
+ if opts.self_test:
+ return _test()
+
+ if opts.extras:
+ extras = {}
+ for s in opts.extras:
+ splitter = re.compile("[,;: ]+")
+ for e in splitter.split(s):
+ if '=' in e:
+ ename, earg = e.split('=', 1)
+ try:
+ earg = int(earg)
+ except ValueError:
+ pass
+ else:
+ ename, earg = e, None
+ extras[ename] = earg
+ else:
+ extras = None
+
+ if opts.link_patterns_file:
+ link_patterns = []
+ f = open(opts.link_patterns_file)
+ try:
+ for i, line in enumerate(f.readlines()):
+ if not line.strip(): continue
+ if line.lstrip().startswith("#"): continue
+ try:
+ pat, href = line.rstrip().rsplit(None, 1)
+ except ValueError:
+ raise MarkdownError("%s:%d: invalid link pattern line: %r"
+ % (opts.link_patterns_file, i+1, line))
+ link_patterns.append(
+ (_regex_from_encoded_pattern(pat), href))
+ finally:
+ f.close()
+ else:
+ link_patterns = None
+
+ from os.path import join, dirname, abspath, exists
+ markdown_pl = join(dirname(dirname(abspath(__file__))), "test",
+ "Markdown.pl")
+ for path in paths:
+ if opts.compare:
+ print "==== Markdown.pl ===="
+ perl_cmd = 'perl %s "%s"' % (markdown_pl, path)
+ o = os.popen(perl_cmd)
+ perl_html = o.read()
+ o.close()
+ sys.stdout.write(perl_html)
+ print "==== markdown2.py ===="
+ html = markdown_path(path, encoding=opts.encoding,
+ html4tags=opts.html4tags,
+ safe_mode=opts.safe_mode,
+ extras=extras, link_patterns=link_patterns,
+ use_file_vars=opts.use_file_vars)
+ sys.stdout.write(
+ html.encode(sys.stdout.encoding or "utf-8", 'xmlcharrefreplace'))
+ if opts.compare:
+ test_dir = join(dirname(dirname(abspath(__file__))), "test")
+ if exists(join(test_dir, "test_markdown2.py")):
+ sys.path.insert(0, test_dir)
+ from test_markdown2 import norm_html_from_html
+ norm_html = norm_html_from_html(html)
+ norm_perl_html = norm_html_from_html(perl_html)
+ else:
+ norm_html = html
+ norm_perl_html = perl_html
+ print "==== match? %r ====" % (norm_perl_html == norm_html)
+
+
+if __name__ == "__main__":
+ sys.exit( main(sys.argv) )
+
diff --git a/vendor/tornado/demos/blog/schema.sql b/vendor/tornado/demos/blog/schema.sql
new file mode 100644
index 0000000000..86bff9a8ad
--- /dev/null
+++ b/vendor/tornado/demos/blog/schema.sql
@@ -0,0 +1,44 @@
+-- Copyright 2009 FriendFeed
+--
+-- Licensed under the Apache License, Version 2.0 (the "License"); you may
+-- not use this file except in compliance with the License. You may obtain
+-- a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+-- License for the specific language governing permissions and limitations
+-- under the License.
+
+-- To create the database:
+-- CREATE DATABASE blog;
+-- GRANT ALL PRIVILEGES ON blog.* TO 'blog'@'localhost' IDENTIFIED BY 'blog';
+--
+-- To reload the tables:
+-- mysql --user=blog --password=blog --database=blog < schema.sql
+
+SET SESSION storage_engine = "InnoDB";
+SET SESSION time_zone = "+0:00";
+ALTER DATABASE CHARACTER SET "utf8";
+
+DROP TABLE IF EXISTS entries;
+CREATE TABLE entries (
+ id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ author_id INT NOT NULL REFERENCES authors(id),
+ slug VARCHAR(100) NOT NULL UNIQUE,
+ title VARCHAR(512) NOT NULL,
+ markdown MEDIUMTEXT NOT NULL,
+ html MEDIUMTEXT NOT NULL,
+ published DATETIME NOT NULL,
+ updated TIMESTAMP NOT NULL,
+ KEY (published)
+);
+
+DROP TABLE IF EXISTS authors;
+CREATE TABLE authors (
+ id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ email VARCHAR(100) NOT NULL UNIQUE,
+ name VARCHAR(100) NOT NULL
+);
diff --git a/vendor/tornado/demos/blog/static/blog.css b/vendor/tornado/demos/blog/static/blog.css
new file mode 100644
index 0000000000..8902ec1f22
--- /dev/null
+++ b/vendor/tornado/demos/blog/static/blog.css
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2009 Facebook
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+body {
+ background: white;
+ color: black;
+ margin: 15px;
+ margin-top: 0;
+}
+
+body,
+input,
+textarea {
+ font-family: Georgia, serif;
+ font-size: 12pt;
+}
+
+table {
+ border-collapse: collapse;
+ border: 0;
+}
+
+td {
+ border: 0;
+ padding: 0;
+}
+
+h1,
+h2,
+h3,
+h4 {
+ font-family: "Helvetica Nue", Helvetica, Arial, sans-serif;
+ margin: 0;
+}
+
+h1 {
+ font-size: 20pt;
+}
+
+pre,
+code {
+ font-family: monospace;
+ color: #060;
+}
+
+pre {
+ margin-left: 1em;
+ padding-left: 1em;
+ border-left: 1px solid silver;
+ line-height: 14pt;
+}
+
+a,
+a code {
+ color: #00c;
+}
+
+#body {
+ max-width: 800px;
+ margin: auto;
+}
+
+#header {
+ background-color: #3b5998;
+ padding: 5px;
+ padding-left: 10px;
+ padding-right: 10px;
+ margin-bottom: 1em;
+}
+
+#header,
+#header a {
+ color: white;
+}
+
+#header h1 a {
+ text-decoration: none;
+}
+
+#footer,
+#content {
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+#footer {
+ margin-top: 3em;
+}
+
+.entry h1 a {
+ color: black;
+ text-decoration: none;
+}
+
+.entry {
+ margin-bottom: 2em;
+}
+
+.entry .date {
+ margin-top: 3px;
+}
+
+.entry p {
+ margin: 0;
+ margin-bottom: 1em;
+}
+
+.entry .body {
+ margin-top: 1em;
+ line-height: 16pt;
+}
+
+.compose td {
+ vertical-align: middle;
+ padding-bottom: 5px;
+}
+
+.compose td.field {
+ padding-right: 10px;
+}
+
+.compose .title,
+.compose .submit {
+ font-family: "Helvetica Nue", Helvetica, Arial, sans-serif;
+ font-weight: bold;
+}
+
+.compose .title {
+ font-size: 20pt;
+}
+
+.compose .title,
+.compose .markdown {
+ width: 100%;
+}
+
+.compose .markdown {
+ height: 500px;
+ line-height: 16pt;
+}
diff --git a/vendor/tornado/demos/blog/templates/archive.html b/vendor/tornado/demos/blog/templates/archive.html
new file mode 100644
index 0000000000..dcca9511a4
--- /dev/null
+++ b/vendor/tornado/demos/blog/templates/archive.html
@@ -0,0 +1,31 @@
+{% extends "base.html" %}
+
+{% block head %}
+ <style type="text/css">
+ ul.archive {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ }
+
+ ul.archive li {
+ margin-bottom: 1em;
+ }
+
+ ul.archive .title {
+ font-family: "Helvetica Nue", Helvetica, Arial, sans-serif;
+ font-size: 14pt;
+ }
+ </style>
+{% end %}
+
+{% block body %}
+ <ul class="archive">
+ {% for entry in entries %}
+ <li>
+ <div class="title"><a href="/entry/{{ entry.slug }}">{{ escape(entry.title) }}</a></div>
+ <div class="date">{{ locale.format_date(entry.published, full_format=True, shorter=True) }}</div>
+ </li>
+ {% end %}
+ </ul>
+{% end %}
diff --git a/vendor/tornado/demos/blog/templates/base.html b/vendor/tornado/demos/blog/templates/base.html
new file mode 100644
index 0000000000..038c5b3fff
--- /dev/null
+++ b/vendor/tornado/demos/blog/templates/base.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <title>{{ escape(handler.settings["blog_title"]) }}</title>
+ <link rel="stylesheet" href="{{ static_url("blog.css") }}" type="text/css"/>
+ <link rel="alternate" href="/feed" type="application/atom+xml" title="{{ escape(handler.settings["blog_title"]) }}"/>
+ {% block head %}{% end %}
+ </head>
+ <body>
+ <div id="body">
+ <div id="header">
+ <div style="float:right">
+ {% if current_user %}
+ <a href="/compose">{{ _("New post") }}</a> -
+ <a href="/auth/logout?next={{ url_escape(request.uri) }}">{{ _("Sign out") }}</a>
+ {% else %}
+ {{ _('<a href="%(url)s">Sign in</a> to compose/edit') % {"url": "/auth/login?next=" + url_escape(request.uri)} }}
+ {% end %}
+ </div>
+ <h1><a href="/">{{ escape(handler.settings["blog_title"]) }}</a></h1>
+ </div>
+ <div id="content">{% block body %}{% end %}</div>
+ </div>
+ {% block bottom %}{% end %}
+ </body>
+</html>
diff --git a/vendor/tornado/demos/blog/templates/compose.html b/vendor/tornado/demos/blog/templates/compose.html
new file mode 100644
index 0000000000..bc054b3349
--- /dev/null
+++ b/vendor/tornado/demos/blog/templates/compose.html
@@ -0,0 +1,42 @@
+{% extends "base.html" %}
+
+{% block body %}
+ <form action="{{ request.path }}" method="post" class="compose">
+ <div style="margin-bottom:5px"><input name="title" type="text" class="title" value="{{ escape(entry.title) if entry else "" }}"/></div>
+ <div style="margin-bottom:5px"><textarea name="markdown" rows="30" cols="40" class="markdown">{{ escape(entry.markdown) if entry else "" }}</textarea></div>
+ <div>
+ <div style="float:right"><a href="http://daringfireball.net/projects/markdown/syntax">{{ _("Syntax documentation") }}</a></div>
+ <input type="submit" value="{{ _("Save changes") if entry else _("Publish post") }}" class="submit"/>
+ &nbsp;<a href="{{ "/entry/" + entry.slug if entry else "/" }}">{{ _("Cancel") }}</a>
+ </div>
+ {% if entry %}
+ <input type="hidden" name="id" value="{{ entry.id }}"/>
+ {% end %}
+ {{ xsrf_form_html() }}
+ </form>
+{% end %}
+
+{% block bottom %}
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ //<![CDATA[
+
+ $(function() {
+ $("input[name=title]").select();
+ $("form.compose").submit(function() {
+ var required = ["title", "markdown"];
+ var form = $(this).get(0);
+ for (var i = 0; i < required.length; i++) {
+ if (!form[required[i]].value) {
+ $(form[required[i]]).select();
+ return false;
+ }
+ }
+ return true;
+ });
+ });
+
+ //]]>
+ </script>
+{% end %}
+
diff --git a/vendor/tornado/demos/blog/templates/entry.html b/vendor/tornado/demos/blog/templates/entry.html
new file mode 100644
index 0000000000..43c835dead
--- /dev/null
+++ b/vendor/tornado/demos/blog/templates/entry.html
@@ -0,0 +1,5 @@
+{% extends "base.html" %}
+
+{% block body %}
+ {{ modules.Entry(entry) }}
+{% end %}
diff --git a/vendor/tornado/demos/blog/templates/feed.xml b/vendor/tornado/demos/blog/templates/feed.xml
new file mode 100644
index 0000000000..c6c368656c
--- /dev/null
+++ b/vendor/tornado/demos/blog/templates/feed.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ {% set date_format = "%Y-%m-%dT%H:%M:%SZ" %}
+ <title>{{ escape(handler.settings["blog_title"]) }}</title>
+ {% if len(entries) > 0 %}
+ <updated>{{ max(e.updated for e in entries).strftime(date_format) }}</updated>
+ {% else %}
+ <updated>{{ datetime.datetime.utcnow().strftime(date_format) }}</updated>
+ {% end %}
+ <id>http://{{ request.host }}/</id>
+ <link rel="alternate" href="http://{{ request.host }}/" title="{{ escape(handler.settings["blog_title"]) }}" type="text/html"/>
+ <link rel="self" href="{{ request.full_url() }}" title="{{ escape(handler.settings["blog_title"]) }}" type="application/atom+xml"/>
+ <author><name>{{ escape(handler.settings["blog_title"]) }}</name></author>
+ {% for entry in entries %}
+ <entry>
+ <id>http://{{ request.host }}/entry/{{ entry.slug }}</id>
+ <title type="text">{{ escape(entry.title) }}</title>
+ <link href="http://{{ request.host }}/entry/{{ entry.slug }}" rel="alternate" type="text/html"/>
+ <updated>{{ entry.updated.strftime(date_format) }}</updated>
+ <published>{{ entry.published.strftime(date_format) }}</published>
+ <content type="xhtml" xml:base="http://{{ request.host }}/">
+ <div xmlns="http://www.w3.org/1999/xhtml">{{ entry.html }}</div>
+ </content>
+ </entry>
+ {% end %}
+</feed>
diff --git a/vendor/tornado/demos/blog/templates/home.html b/vendor/tornado/demos/blog/templates/home.html
new file mode 100644
index 0000000000..dd069a97f3
--- /dev/null
+++ b/vendor/tornado/demos/blog/templates/home.html
@@ -0,0 +1,8 @@
+{% extends "base.html" %}
+
+{% block body %}
+ {% for entry in entries %}
+ {{ modules.Entry(entry) }}
+ {% end %}
+ <div><a href="/archive">{{ _("Archive") }}</a></div>
+{% end %}
diff --git a/vendor/tornado/demos/blog/templates/modules/entry.html b/vendor/tornado/demos/blog/templates/modules/entry.html
new file mode 100644
index 0000000000..27ea0d76c2
--- /dev/null
+++ b/vendor/tornado/demos/blog/templates/modules/entry.html
@@ -0,0 +1,8 @@
+<div class="entry">
+ <h1><a href="/entry/{{ entry.slug }}">{{ escape(entry.title) }}</a></h1>
+ <div class="date">{{ locale.format_date(entry.published, full_format=True, shorter=True) }}</div>
+ <div class="body">{{ entry.html }}</div>
+ {% if current_user %}
+ <div class="admin"><a href="/compose?id={{ entry.id }}">{{ _("Edit this post") }}</a></div>
+ {% end %}
+</div>
diff --git a/vendor/tornado/demos/chat/chatdemo.py b/vendor/tornado/demos/chat/chatdemo.py
new file mode 100755
index 0000000000..7086592ec4
--- /dev/null
+++ b/vendor/tornado/demos/chat/chatdemo.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+import tornado.auth
+import tornado.escape
+import tornado.httpserver
+import tornado.ioloop
+import tornado.options
+import tornado.web
+import os.path
+import uuid
+
+from tornado.options import define, options
+
+define("port", default=8888, help="run on the given port", type=int)
+
+
+class Application(tornado.web.Application):
+ def __init__(self):
+ handlers = [
+ (r"/", MainHandler),
+ (r"/auth/login", AuthLoginHandler),
+ (r"/auth/logout", AuthLogoutHandler),
+ (r"/a/message/new", MessageNewHandler),
+ (r"/a/message/updates", MessageUpdatesHandler),
+ ]
+ settings = dict(
+ cookie_secret="43oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+ login_url="/auth/login",
+ template_path=os.path.join(os.path.dirname(__file__), "templates"),
+ static_path=os.path.join(os.path.dirname(__file__), "static"),
+ xsrf_cookies=True,
+ )
+ tornado.web.Application.__init__(self, handlers, **settings)
+
+
+class BaseHandler(tornado.web.RequestHandler):
+ def get_current_user(self):
+ user_json = self.get_secure_cookie("user")
+ if not user_json: return None
+ return tornado.escape.json_decode(user_json)
+
+
+class MainHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self):
+ self.render("index.html", messages=MessageMixin.cache)
+
+
+class MessageMixin(object):
+ waiters = []
+ cache = []
+ cache_size = 200
+
+ def wait_for_messages(self, callback, cursor=None):
+ cls = MessageMixin
+ if cursor:
+ index = 0
+ for i in xrange(len(cls.cache)):
+ index = len(cls.cache) - i - 1
+ if cls.cache[index]["id"] == cursor: break
+ recent = cls.cache[index + 1:]
+ if recent:
+ callback(recent)
+ return
+ cls.waiters.append(callback)
+
+ def new_messages(self, messages):
+ cls = MessageMixin
+ logging.info("Sending new message to %r listeners", len(cls.waiters))
+ for callback in cls.waiters:
+ try:
+ callback(messages)
+ except:
+ logging.error("Error in waiter callback", exc_info=True)
+ cls.waiters = []
+ cls.cache.extend(messages)
+ if len(cls.cache) > self.cache_size:
+ cls.cache = cls.cache[-self.cache_size:]
+
+
+class MessageNewHandler(BaseHandler, MessageMixin):
+ @tornado.web.authenticated
+ def post(self):
+ message = {
+ "id": str(uuid.uuid4()),
+ "from": self.current_user["first_name"],
+ "body": self.get_argument("body"),
+ }
+ message["html"] = self.render_string("message.html", message=message)
+ if self.get_argument("next", None):
+ self.redirect(self.get_argument("next"))
+ else:
+ self.write(message)
+ self.new_messages([message])
+
+
+class MessageUpdatesHandler(BaseHandler, MessageMixin):
+ @tornado.web.authenticated
+ @tornado.web.asynchronous
+ def post(self):
+ cursor = self.get_argument("cursor", None)
+ self.wait_for_messages(self.async_callback(self.on_new_messages),
+ cursor=cursor)
+
+ def on_new_messages(self, messages):
+ # Closed client connection
+ if self.request.connection.stream.closed():
+ return
+ self.finish(dict(messages=messages))
+
+
+class AuthLoginHandler(BaseHandler, tornado.auth.GoogleMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ if self.get_argument("openid.mode", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authenticate_redirect(ax_attrs=["name"])
+
+ def _on_auth(self, user):
+ if not user:
+ raise tornado.web.HTTPError(500, "Google auth failed")
+ self.set_secure_cookie("user", tornado.escape.json_encode(user))
+ self.redirect("/")
+
+
+class AuthLogoutHandler(BaseHandler):
+ def get(self):
+ self.clear_cookie("user")
+ self.write("You are now logged out")
+
+
+def main():
+ tornado.options.parse_command_line()
+ http_server = tornado.httpserver.HTTPServer(Application())
+ http_server.listen(options.port)
+ tornado.ioloop.IOLoop.instance().start()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/vendor/tornado/demos/chat/static/chat.css b/vendor/tornado/demos/chat/static/chat.css
new file mode 100644
index 0000000000..a400c32605
--- /dev/null
+++ b/vendor/tornado/demos/chat/static/chat.css
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2009 FriendFeed
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+body {
+ background: white;
+ margin: 10px;
+}
+
+body,
+input {
+ font-family: sans-serif;
+ font-size: 10pt;
+ color: black;
+}
+
+table {
+ border-collapse: collapse;
+ border: 0;
+}
+
+td {
+ border: 0;
+ padding: 0;
+}
+
+#body {
+ position: absolute;
+ bottom: 10px;
+ left: 10px;
+}
+
+#input {
+ margin-top: 0.5em;
+}
+
+#inbox .message {
+ padding-top: 0.25em;
+}
+
+#nav {
+ float: right;
+ z-index: 99;
+}
diff --git a/vendor/tornado/demos/chat/static/chat.js b/vendor/tornado/demos/chat/static/chat.js
new file mode 100644
index 0000000000..0054c710d6
--- /dev/null
+++ b/vendor/tornado/demos/chat/static/chat.js
@@ -0,0 +1,135 @@
+// Copyright 2009 FriendFeed
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+$(document).ready(function() {
+ if (!window.console) window.console = {};
+ if (!window.console.log) window.console.log = function() {};
+
+ $("#messageform").live("submit", function() {
+ newMessage($(this));
+ return false;
+ });
+ $("#messageform").live("keypress", function(e) {
+ if (e.keyCode == 13) {
+ newMessage($(this));
+ return false;
+ }
+ });
+ $("#message").select();
+ updater.poll();
+});
+
+function newMessage(form) {
+ var message = form.formToDict();
+ var disabled = form.find("input[type=submit]");
+ disabled.disable();
+ $.postJSON("/a/message/new", message, function(response) {
+ updater.showMessage(response);
+ if (message.id) {
+ form.parent().remove();
+ } else {
+ form.find("input[type=text]").val("").select();
+ disabled.enable();
+ }
+ });
+}
+
+function getCookie(name) {
+ var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
+ return r ? r[1] : undefined;
+}
+
+jQuery.postJSON = function(url, args, callback) {
+ args._xsrf = getCookie("_xsrf");
+ $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
+ success: function(response) {
+ if (callback) callback(eval("(" + response + ")"));
+ }, error: function(response) {
+ console.log("ERROR:", response)
+ }});
+};
+
+jQuery.fn.formToDict = function() {
+ var fields = this.serializeArray();
+ var json = {}
+ for (var i = 0; i < fields.length; i++) {
+ json[fields[i].name] = fields[i].value;
+ }
+ if (json.next) delete json.next;
+ return json;
+};
+
+jQuery.fn.disable = function() {
+ this.enable(false);
+ return this;
+};
+
+jQuery.fn.enable = function(opt_enable) {
+ if (arguments.length && !opt_enable) {
+ this.attr("disabled", "disabled");
+ } else {
+ this.removeAttr("disabled");
+ }
+ return this;
+};
+
+var updater = {
+ errorSleepTime: 500,
+ cursor: null,
+
+ poll: function() {
+ var args = {"_xsrf": getCookie("_xsrf")};
+ if (updater.cursor) args.cursor = updater.cursor;
+ $.ajax({url: "/a/message/updates", type: "POST", dataType: "text",
+ data: $.param(args), success: updater.onSuccess,
+ error: updater.onError});
+ },
+
+ onSuccess: function(response) {
+ try {
+ updater.newMessages(eval("(" + response + ")"));
+ } catch (e) {
+ updater.onError();
+ return;
+ }
+ updater.errorSleepTime = 500;
+ window.setTimeout(updater.poll, 0);
+ },
+
+ onError: function(response) {
+ updater.errorSleepTime *= 2;
+ console.log("Poll error; sleeping for", updater.errorSleepTime, "ms");
+ window.setTimeout(updater.poll, updater.errorSleepTime);
+ },
+
+ newMessages: function(response) {
+ if (!response.messages) return;
+ updater.cursor = response.cursor;
+ var messages = response.messages;
+ updater.cursor = messages[messages.length - 1].id;
+ console.log(messages.length, "new messages, cursor:", updater.cursor);
+ for (var i = 0; i < messages.length; i++) {
+ updater.showMessage(messages[i]);
+ }
+ },
+
+ showMessage: function(message) {
+ var existing = $("#m" + message.id);
+ if (existing.length > 0) return;
+ var node = $(message.html);
+ node.hide();
+ $("#inbox").append(node);
+ node.slideDown();
+ }
+};
diff --git a/vendor/tornado/demos/chat/templates/index.html b/vendor/tornado/demos/chat/templates/index.html
new file mode 100644
index 0000000000..de051d852b
--- /dev/null
+++ b/vendor/tornado/demos/chat/templates/index.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <title>Tornado Chat Demo</title>
+ <link rel="stylesheet" href="{{ static_url("chat.css") }}" type="text/css"/>
+ </head>
+ <body>
+ <div id="nav">
+ <b>{{ escape(current_user["name"]) }}</b> -
+ <a href="/auth/logout">{{ _("Sign out") }}</a>
+ </div>
+ <div id="body">
+ <div id="inbox">
+ {% for message in messages %}
+ {% include "message.html" %}
+ {% end %}
+ </div>
+ <div id="input">
+ <form action="/a/message/new" method="post" id="messageform">
+ <table>
+ <tr>
+ <td><input name="body" id="message" style="width:500px"/></td>
+ <td style="padding-left:5px">
+ <input type="submit" value="{{ _("Post") }}"/>
+ <input type="hidden" name="next" value="{{ request.path }}"/>
+ {{ xsrf_form_html() }}
+ </td>
+ </tr>
+ </table>
+ </form>
+ </div>
+ </div>
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>
+ <script src="{{ static_url("chat.js") }}" type="text/javascript"></script>
+ </body>
+</html>
diff --git a/vendor/tornado/demos/chat/templates/message.html b/vendor/tornado/demos/chat/templates/message.html
new file mode 100644
index 0000000000..4445cbdfaf
--- /dev/null
+++ b/vendor/tornado/demos/chat/templates/message.html
@@ -0,0 +1 @@
+<div class="message" id="m{{ message["id"] }}"><b>{{ escape(message["from"]) }}: </b>{{ escape(message["body"]) }}</div>
diff --git a/vendor/tornado/demos/facebook/README b/vendor/tornado/demos/facebook/README
new file mode 100644
index 0000000000..2f0dc28e84
--- /dev/null
+++ b/vendor/tornado/demos/facebook/README
@@ -0,0 +1,8 @@
+Running the Tornado Facebook example
+=====================================
+To work with the provided Facebook api key, this example must be
+accessed at http://localhost:8888/ to match the Connect URL set in the
+example application.
+
+To use any other domain, a new Facebook application must be registered
+with a Connect URL set to that domain.
diff --git a/vendor/tornado/demos/facebook/facebook.py b/vendor/tornado/demos/facebook/facebook.py
new file mode 100755
index 0000000000..0c984ddaa0
--- /dev/null
+++ b/vendor/tornado/demos/facebook/facebook.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+import os.path
+import tornado.auth
+import tornado.escape
+import tornado.httpserver
+import tornado.ioloop
+import tornado.options
+import tornado.web
+import uimodules
+
+from tornado.options import define, options
+
+define("port", default=8888, help="run on the given port", type=int)
+define("facebook_api_key", help="your Facebook application API key",
+ default="9e2ada1b462142c4dfcc8e894ea1e37c")
+define("facebook_secret", help="your Facebook application secret",
+ default="32fc6114554e3c53d5952594510021e2")
+
+
+class Application(tornado.web.Application):
+ def __init__(self):
+ handlers = [
+ (r"/", MainHandler),
+ (r"/auth/login", AuthLoginHandler),
+ (r"/auth/logout", AuthLogoutHandler),
+ ]
+ settings = dict(
+ cookie_secret="12oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+ login_url="/auth/login",
+ template_path=os.path.join(os.path.dirname(__file__), "templates"),
+ static_path=os.path.join(os.path.dirname(__file__), "static"),
+ xsrf_cookies=True,
+ facebook_api_key=options.facebook_api_key,
+ facebook_secret=options.facebook_secret,
+ ui_modules= {"Post": PostModule},
+ debug=True,
+ )
+ tornado.web.Application.__init__(self, handlers, **settings)
+
+
+class BaseHandler(tornado.web.RequestHandler):
+ def get_current_user(self):
+ user_json = self.get_secure_cookie("user")
+ if not user_json: return None
+ return tornado.escape.json_decode(user_json)
+
+
+class MainHandler(BaseHandler, tornado.auth.FacebookMixin):
+ @tornado.web.authenticated
+ @tornado.web.asynchronous
+ def get(self):
+ self.facebook_request(
+ method="stream.get",
+ callback=self.async_callback(self._on_stream),
+ session_key=self.current_user["session_key"])
+
+ def _on_stream(self, stream):
+ if stream is None:
+ # Session may have expired
+ self.redirect("/auth/login")
+ return
+ # Turn profiles into a dict mapping id => profile
+ stream["profiles"] = dict((p["id"], p) for p in stream["profiles"])
+ self.render("stream.html", stream=stream)
+
+
+class AuthLoginHandler(BaseHandler, tornado.auth.FacebookMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ if self.get_argument("session", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authorize_redirect("read_stream")
+
+ def _on_auth(self, user):
+ if not user:
+ raise tornado.web.HTTPError(500, "Facebook auth failed")
+ self.set_secure_cookie("user", tornado.escape.json_encode(user))
+ self.redirect(self.get_argument("next", "/"))
+
+
+class AuthLogoutHandler(BaseHandler, tornado.auth.FacebookMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ self.clear_cookie("user")
+ if not self.current_user:
+ self.redirect(self.get_argument("next", "/"))
+ return
+ self.facebook_request(
+ method="auth.revokeAuthorization",
+ callback=self.async_callback(self._on_deauthorize),
+ session_key=self.current_user["session_key"])
+
+ def _on_deauthorize(self, response):
+ self.redirect(self.get_argument("next", "/"))
+
+
+class PostModule(tornado.web.UIModule):
+ def render(self, post, actor):
+ return self.render_string("modules/post.html", post=post, actor=actor)
+
+
+def main():
+ tornado.options.parse_command_line()
+ http_server = tornado.httpserver.HTTPServer(Application())
+ http_server.listen(options.port)
+ tornado.ioloop.IOLoop.instance().start()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/vendor/tornado/demos/facebook/static/facebook.css b/vendor/tornado/demos/facebook/static/facebook.css
new file mode 100644
index 0000000000..4fee72678f
--- /dev/null
+++ b/vendor/tornado/demos/facebook/static/facebook.css
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2009 Facebook
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+body {
+ background: white;
+ color: black;
+ margin: 15px;
+}
+
+body,
+input,
+textarea {
+ font-family: "Lucida Grande", Tahoma, Verdana, sans-serif;
+ font-size: 10pt;
+}
+
+table {
+ border-collapse: collapse;
+ border: 0;
+}
+
+td {
+ border: 0;
+ padding: 0;
+}
+
+img {
+ border: 0;
+}
+
+a {
+ text-decoration: none;
+ color: #3b5998;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+.post {
+ border-bottom: 1px solid #eeeeee;
+ min-height: 50px;
+ padding-bottom: 10px;
+ margin-top: 10px;
+}
+
+.post .picture {
+ float: left;
+}
+
+.post .picture img {
+ height: 50px;
+ width: 50px;
+}
+
+.post .body {
+ margin-left: 60px;
+}
+
+.post .media img {
+ border: 1px solid #cccccc;
+ padding: 3px;
+}
+
+.post .media:hover img {
+ border: 1px solid #3b5998;
+}
+
+.post a.actor {
+ font-weight: bold;
+}
+
+.post .meta {
+ font-size: 11px;
+}
+
+.post a.permalink {
+ color: #777777;
+}
+
+#body {
+ max-width: 700px;
+ margin: auto;
+}
diff --git a/vendor/tornado/demos/facebook/static/facebook.js b/vendor/tornado/demos/facebook/static/facebook.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vendor/tornado/demos/facebook/static/facebook.js
diff --git a/vendor/tornado/demos/facebook/templates/modules/post.html b/vendor/tornado/demos/facebook/templates/modules/post.html
new file mode 100644
index 0000000000..6b50ac0f72
--- /dev/null
+++ b/vendor/tornado/demos/facebook/templates/modules/post.html
@@ -0,0 +1,29 @@
+<div class="post">
+ <div class="picture">
+ <a href="{{ actor["url"] }}"><img src="{{ actor["pic_square"] }}"/></a>
+ </div>
+ <div class="body">
+ <a href="{{ actor["url"] }}" class="actor">{{ escape(actor["name"]) }}</a>
+ {% if post["message"] %}
+ <span class="message">{{ escape(post["message"]) }}</span>
+ {% end %}
+ {% if post["attachment"] %}
+ <div class="attachment">
+ {% if post["attachment"].get("name") %}
+ <div class="name"><a href="{{ post["attachment"]["href"] }}">{{ escape(post["attachment"]["name"]) }}</a></div>
+ {% end %}
+ {% if post["attachment"].get("description") %}
+ <div class="description">{{ post["attachment"]["description"] }}</div>
+ {% end %}
+ {% for media in filter(lambda m: m.get("src") and m["type"] in ("photo", "link"), post["attachment"].get("media", [])) %}
+ <span class="media">
+ <a href="{{ media["href"] }}"><img src="{{ media["src"] }}" alt="{{ escape(media.get("alt", "")) }}"/></a>
+ </span>
+ {% end %}
+ </div>
+ {% end %}
+ <div class="meta">
+ <a href="{{ post["permalink"] }}" class="permalink">{{ locale.format_date(post["created_time"]) }}</a>
+ </div>
+ </div>
+</div>
diff --git a/vendor/tornado/demos/facebook/templates/stream.html b/vendor/tornado/demos/facebook/templates/stream.html
new file mode 100644
index 0000000000..19baa28cbf
--- /dev/null
+++ b/vendor/tornado/demos/facebook/templates/stream.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <title>Tornado Facebook Stream Demo</title>
+ <link rel="stylesheet" href="{{ static_url("facebook.css") }}" type="text/css"/>
+ </head>
+ <body>
+ <div id="body">
+ <div style="float:right">
+ <b>{{ escape(current_user["name"]) }}</b> -
+ <a href="/auth/logout">{{ _("Sign out") }}</a>
+ </div>
+ <div style="margin-bottom:1em"><a href="/">{{ _("Refresh stream") }}</a></div>
+ <div id="stream">
+ {% for post in stream["posts"] %}
+ {{ modules.Post(post, stream["profiles"][post["actor_id"]]) }}
+ {% end %}
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/vendor/tornado/demos/facebook/uimodules.py b/vendor/tornado/demos/facebook/uimodules.py
new file mode 100644
index 0000000000..1173db634e
--- /dev/null
+++ b/vendor/tornado/demos/facebook/uimodules.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import tornado.web
+
+
+class Entry(tornado.web.UIModule):
+ def render(self):
+ return '<div>ENTRY</div>'
diff --git a/vendor/tornado/demos/helloworld/helloworld.py b/vendor/tornado/demos/helloworld/helloworld.py
new file mode 100755
index 0000000000..0f1ed61ff5
--- /dev/null
+++ b/vendor/tornado/demos/helloworld/helloworld.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import tornado.httpserver
+import tornado.ioloop
+import tornado.options
+import tornado.web
+
+from tornado.options import define, options
+
+define("port", default=8888, help="run on the given port", type=int)
+
+
+class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.write("Hello, world")
+
+
+def main():
+ tornado.options.parse_command_line()
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ ])
+ http_server = tornado.httpserver.HTTPServer(application)
+ http_server.listen(options.port)
+ tornado.ioloop.IOLoop.instance().start()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/vendor/tornado/setup.py b/vendor/tornado/setup.py
new file mode 100644
index 0000000000..5cb69df2da
--- /dev/null
+++ b/vendor/tornado/setup.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import distutils.core
+import sys
+# Importing setuptools adds some features like "setup.py develop", but
+# it's optional so swallow the error if it's not there.
+try:
+ import setuptools
+except ImportError:
+ pass
+
+# Build the epoll extension for Linux systems with Python < 2.6
+extensions = []
+major, minor = sys.version_info[:2]
+python_26 = (major > 2 or (major == 2 and minor >= 6))
+if "linux" in sys.platform.lower() and not python_26:
+ extensions.append(distutils.core.Extension(
+ "tornado.epoll", ["tornado/epoll.c"]))
+
+distutils.core.setup(
+ name="tornado",
+ version="0.2",
+ packages = ["tornado"],
+ ext_modules = extensions,
+ author="Facebook",
+ author_email="python-tornado@googlegroups.com",
+ url="http://www.tornadoweb.org/",
+ license="http://www.apache.org/licenses/LICENSE-2.0",
+ description="Tornado is an open source version of the scalable, non-blocking web server and and tools that power FriendFeed",
+)
diff --git a/vendor/tornado/tornado/__init__.py b/vendor/tornado/tornado/__init__.py
new file mode 100644
index 0000000000..8f73764eb2
--- /dev/null
+++ b/vendor/tornado/tornado/__init__.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""The Tornado web server and tools."""
diff --git a/vendor/tornado/tornado/auth.py b/vendor/tornado/tornado/auth.py
new file mode 100644
index 0000000000..f67d9e5482
--- /dev/null
+++ b/vendor/tornado/tornado/auth.py
@@ -0,0 +1,883 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Implementations of various third-party authentication schemes.
+
+All the classes in this file are class Mixins designed to be used with
+web.py RequestHandler classes. The primary methods for each service are
+authenticate_redirect(), authorize_redirect(), and get_authenticated_user().
+The former should be called to redirect the user to, e.g., the OpenID
+authentication page on the third party service, and the latter should
+be called upon return to get the user data from the data returned by
+the third party service.
+
+They all take slightly different arguments due to the fact all these
+services implement authentication and authorization slightly differently.
+See the individual service classes below for complete documentation.
+
+Example usage for Google OpenID:
+
+class GoogleHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ if self.get_argument("openid.mode", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authenticate_redirect()
+
+ def _on_auth(self, user):
+ if not user:
+ raise tornado.web.HTTPError(500, "Google auth failed")
+ # Save the user with, e.g., set_secure_cookie()
+
+"""
+
+import base64
+import binascii
+import cgi
+import hashlib
+import hmac
+import httpclient
+import escape
+import logging
+import time
+import urllib
+import urlparse
+import uuid
+
+_log = logging.getLogger("tornado.auth")
+
+class OpenIdMixin(object):
+ """Abstract implementation of OpenID and Attribute Exchange.
+
+ See GoogleMixin below for example implementations.
+ """
+ def authenticate_redirect(self, callback_uri=None,
+ ax_attrs=["name","email","language","username"]):
+ """Returns the authentication URL for this service.
+
+ After authentication, the service will redirect back to the given
+ callback URI.
+
+ We request the given attributes for the authenticated user by
+ default (name, email, language, and username). If you don't need
+ all those attributes for your app, you can request fewer with
+ the ax_attrs keyword argument.
+ """
+ callback_uri = callback_uri or self.request.path
+ args = self._openid_args(callback_uri, ax_attrs=ax_attrs)
+ self.redirect(self._OPENID_ENDPOINT + "?" + urllib.urlencode(args))
+
+ def get_authenticated_user(self, callback):
+ """Fetches the authenticated user data upon redirect.
+
+ This method should be called by the handler that receives the
+ redirect from the authenticate_redirect() or authorize_redirect()
+ methods.
+ """
+ # Verify the OpenID response via direct request to the OP
+ args = dict((k, v[-1]) for k, v in self.request.arguments.iteritems())
+ args["openid.mode"] = u"check_authentication"
+ url = self._OPENID_ENDPOINT + "?" + urllib.urlencode(args)
+ http = httpclient.AsyncHTTPClient()
+ http.fetch(url, self.async_callback(
+ self._on_authentication_verified, callback))
+
+ def _openid_args(self, callback_uri, ax_attrs=[], oauth_scope=None):
+ url = urlparse.urljoin(self.request.full_url(), callback_uri)
+ args = {
+ "openid.ns": "http://specs.openid.net/auth/2.0",
+ "openid.claimed_id":
+ "http://specs.openid.net/auth/2.0/identifier_select",
+ "openid.identity":
+ "http://specs.openid.net/auth/2.0/identifier_select",
+ "openid.return_to": url,
+ "openid.realm": "http://" + self.request.host + "/",
+ "openid.mode": "checkid_setup",
+ }
+ if ax_attrs:
+ args.update({
+ "openid.ns.ax": "http://openid.net/srv/ax/1.0",
+ "openid.ax.mode": "fetch_request",
+ })
+ ax_attrs = set(ax_attrs)
+ required = []
+ if "name" in ax_attrs:
+ ax_attrs -= set(["name", "firstname", "fullname", "lastname"])
+ required += ["firstname", "fullname", "lastname"]
+ args.update({
+ "openid.ax.type.firstname":
+ "http://axschema.org/namePerson/first",
+ "openid.ax.type.fullname":
+ "http://axschema.org/namePerson",
+ "openid.ax.type.lastname":
+ "http://axschema.org/namePerson/last",
+ })
+ known_attrs = {
+ "email": "http://axschema.org/contact/email",
+ "language": "http://axschema.org/pref/language",
+ "username": "http://axschema.org/namePerson/friendly",
+ }
+ for name in ax_attrs:
+ args["openid.ax.type." + name] = known_attrs[name]
+ required.append(name)
+ args["openid.ax.required"] = ",".join(required)
+ if oauth_scope:
+ args.update({
+ "openid.ns.oauth":
+ "http://specs.openid.net/extensions/oauth/1.0",
+ "openid.oauth.consumer": self.request.host.split(":")[0],
+ "openid.oauth.scope": oauth_scope,
+ })
+ return args
+
+ def _on_authentication_verified(self, callback, response):
+ if response.error or u"is_valid:true" not in response.body:
+ _log.warning("Invalid OpenID response: %s", response.error or
+ response.body)
+ callback(None)
+ return
+
+ # Make sure we got back at least an email from attribute exchange
+ ax_ns = None
+ for name, values in self.request.arguments.iteritems():
+ if name.startswith("openid.ns.") and \
+ values[-1] == u"http://openid.net/srv/ax/1.0":
+ ax_ns = name[10:]
+ break
+ def get_ax_arg(uri):
+ if not ax_ns: return u""
+ prefix = "openid." + ax_ns + ".type."
+ ax_name = None
+ for name, values in self.request.arguments.iteritems():
+ if values[-1] == uri and name.startswith(prefix):
+ part = name[len(prefix):]
+ ax_name = "openid." + ax_ns + ".value." + part
+ break
+ if not ax_name: return u""
+ return self.get_argument(ax_name, u"")
+
+ email = get_ax_arg("http://axschema.org/contact/email")
+ name = get_ax_arg("http://axschema.org/namePerson")
+ first_name = get_ax_arg("http://axschema.org/namePerson/first")
+ last_name = get_ax_arg("http://axschema.org/namePerson/last")
+ username = get_ax_arg("http://axschema.org/namePerson/friendly")
+ locale = get_ax_arg("http://axschema.org/pref/language").lower()
+ user = dict()
+ name_parts = []
+ if first_name:
+ user["first_name"] = first_name
+ name_parts.append(first_name)
+ if last_name:
+ user["last_name"] = last_name
+ name_parts.append(last_name)
+ if name:
+ user["name"] = name
+ elif name_parts:
+ user["name"] = u" ".join(name_parts)
+ elif email:
+ user["name"] = email.split("@")[0]
+ if email: user["email"] = email
+ if locale: user["locale"] = locale
+ if username: user["username"] = username
+ callback(user)
+
+
+class OAuthMixin(object):
+ """Abstract implementation of OAuth.
+
+ See TwitterMixin and FriendFeedMixin below for example implementations.
+ """
+ def authorize_redirect(self, callback_uri=None):
+ """Redirects the user to obtain OAuth authorization for this service.
+
+ Twitter and FriendFeed both require that you register a Callback
+ URL with your application. You should call this method to log the
+ user in, and then call get_authenticated_user() in the handler
+ you registered as your Callback URL to complete the authorization
+ process.
+
+ This method sets a cookie called _oauth_request_token which is
+ subsequently used (and cleared) in get_authenticated_user for
+ security purposes.
+ """
+ if callback_uri and getattr(self, "_OAUTH_NO_CALLBACKS", False):
+ raise Exception("This service does not support oauth_callback")
+ http = httpclient.AsyncHTTPClient()
+ http.fetch(self._oauth_request_token_url(), self.async_callback(
+ self._on_request_token, self._OAUTH_AUTHORIZE_URL, callback_uri))
+
+ def get_authenticated_user(self, callback):
+ """Gets the OAuth authorized user and access token on callback.
+
+ This method should be called from the handler for your registered
+ OAuth Callback URL to complete the registration process. We call
+ callback with the authenticated user, which in addition to standard
+ attributes like 'name' includes the 'access_key' attribute, which
+ contains the OAuth access you can use to make authorized requests
+ to this service on behalf of the user.
+ """
+ request_key = self.get_argument("oauth_token")
+ request_cookie = self.get_cookie("_oauth_request_token")
+ if not request_cookie:
+ _log.warning("Missing OAuth request token cookie")
+ callback(None)
+ return
+ cookie_key, cookie_secret = request_cookie.split("|")
+ if cookie_key != request_key:
+ _log.warning("Request token does not match cookie")
+ callback(None)
+ return
+ token = dict(key=cookie_key, secret=cookie_secret)
+ http = httpclient.AsyncHTTPClient()
+ http.fetch(self._oauth_access_token_url(token), self.async_callback(
+ self._on_access_token, callback))
+
+ def _oauth_request_token_url(self):
+ consumer_token = self._oauth_consumer_token()
+ url = self._OAUTH_REQUEST_TOKEN_URL
+ args = dict(
+ oauth_consumer_key=consumer_token["key"],
+ oauth_signature_method="HMAC-SHA1",
+ oauth_timestamp=str(int(time.time())),
+ oauth_nonce=binascii.b2a_hex(uuid.uuid4().bytes),
+ oauth_version="1.0",
+ )
+ signature = _oauth_signature(consumer_token, "GET", url, args)
+ args["oauth_signature"] = signature
+ return url + "?" + urllib.urlencode(args)
+
+ def _on_request_token(self, authorize_url, callback_uri, response):
+ if response.error:
+ raise Exception("Could not get request token")
+ request_token = _oauth_parse_response(response.body)
+ data = "|".join([request_token["key"], request_token["secret"]])
+ self.set_cookie("_oauth_request_token", data)
+ args = dict(oauth_token=request_token["key"])
+ if callback_uri:
+ args["oauth_callback"] = urlparse.urljoin(
+ self.request.full_url(), callback_uri)
+ self.redirect(authorize_url + "?" + urllib.urlencode(args))
+
+ def _oauth_access_token_url(self, request_token):
+ consumer_token = self._oauth_consumer_token()
+ url = self._OAUTH_ACCESS_TOKEN_URL
+ args = dict(
+ oauth_consumer_key=consumer_token["key"],
+ oauth_token=request_token["key"],
+ oauth_signature_method="HMAC-SHA1",
+ oauth_timestamp=str(int(time.time())),
+ oauth_nonce=binascii.b2a_hex(uuid.uuid4().bytes),
+ oauth_version="1.0",
+ )
+ signature = _oauth_signature(consumer_token, "GET", url, args,
+ request_token)
+ args["oauth_signature"] = signature
+ return url + "?" + urllib.urlencode(args)
+
+ def _on_access_token(self, callback, response):
+ if response.error:
+ _log.warning("Could not fetch access token")
+ callback(None)
+ return
+ access_token = _oauth_parse_response(response.body)
+ user = self._oauth_get_user(access_token, self.async_callback(
+ self._on_oauth_get_user, access_token, callback))
+
+ def _oauth_get_user(self, access_token, callback):
+ raise NotImplementedError()
+
+ def _on_oauth_get_user(self, access_token, callback, user):
+ if not user:
+ callback(None)
+ return
+ user["access_token"] = access_token
+ callback(user)
+
+ def _oauth_request_parameters(self, url, access_token, parameters={},
+ method="GET"):
+ """Returns the OAuth parameters as a dict for the given request.
+
+ parameters should include all POST arguments and query string arguments
+ that will be sent with the request.
+ """
+ consumer_token = self._oauth_consumer_token()
+ base_args = dict(
+ oauth_consumer_key=consumer_token["key"],
+ oauth_token=access_token["key"],
+ oauth_signature_method="HMAC-SHA1",
+ oauth_timestamp=str(int(time.time())),
+ oauth_nonce=binascii.b2a_hex(uuid.uuid4().bytes),
+ oauth_version="1.0",
+ )
+ args = {}
+ args.update(base_args)
+ args.update(parameters)
+ signature = _oauth_signature(consumer_token, method, url, args,
+ access_token)
+ base_args["oauth_signature"] = signature
+ return base_args
+
+
+class TwitterMixin(OAuthMixin):
+ """Twitter OAuth authentication.
+
+ To authenticate with Twitter, register your application with
+ Twitter at http://twitter.com/apps. Then copy your Consumer Key and
+ Consumer Secret to the application settings 'twitter_consumer_key' and
+ 'twitter_consumer_secret'. Use this Mixin on the handler for the URL
+ you registered as your application's Callback URL.
+
+ When your application is set up, you can use this Mixin like this
+ to authenticate the user with Twitter and get access to their stream:
+
+ class TwitterHandler(tornado.web.RequestHandler,
+ tornado.auth.TwitterMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ if self.get_argument("oauth_token", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authorize_redirect()
+
+ def _on_auth(self, user):
+ if not user:
+ raise tornado.web.HTTPError(500, "Twitter auth failed")
+ # Save the user using, e.g., set_secure_cookie()
+
+ The user object returned by get_authenticated_user() includes the
+ attributes 'username', 'name', and all of the custom Twitter user
+ attributes describe at
+ http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-users%C2%A0show
+ in addition to 'access_token'. You should save the access token with
+ the user; it is required to make requests on behalf of the user later
+ with twitter_request().
+ """
+ _OAUTH_REQUEST_TOKEN_URL = "http://twitter.com/oauth/request_token"
+ _OAUTH_ACCESS_TOKEN_URL = "http://twitter.com/oauth/access_token"
+ _OAUTH_AUTHORIZE_URL = "http://twitter.com/oauth/authorize"
+ _OAUTH_AUTHENTICATE_URL = "http://twitter.com/oauth/authenticate"
+ _OAUTH_NO_CALLBACKS = True
+
+ def authenticate_redirect(self):
+ """Just like authorize_redirect(), but auto-redirects if authorized.
+
+ This is generally the right interface to use if you are using
+ Twitter for single-sign on.
+ """
+ http = httpclient.AsyncHTTPClient()
+ http.fetch(self._oauth_request_token_url(), self.async_callback(
+ self._on_request_token, self._OAUTH_AUTHENTICATE_URL, None))
+
+ def twitter_request(self, path, callback, access_token=None,
+ post_args=None, **args):
+ """Fetches the given API path, e.g., "/statuses/user_timeline/btaylor"
+
+ The path should not include the format (we automatically append
+ ".json" and parse the JSON output).
+
+ If the request is a POST, post_args should be provided. Query
+ string arguments should be given as keyword arguments.
+
+ All the Twitter methods are documented at
+ http://apiwiki.twitter.com/Twitter-API-Documentation.
+
+ Many methods require an OAuth access token which you can obtain
+ through authorize_redirect() and get_authenticated_user(). The
+ user returned through that process includes an 'access_token'
+ attribute that can be used to make authenticated requests via
+ this method. Example usage:
+
+ class MainHandler(tornado.web.RequestHandler,
+ tornado.auth.TwitterMixin):
+ @tornado.web.authenticated
+ @tornado.web.asynchronous
+ def get(self):
+ self.twitter_request(
+ "/statuses/update",
+ post_args={"status": "Testing Tornado Web Server"},
+ access_token=user["access_token"],
+ callback=self.async_callback(self._on_post))
+
+ def _on_post(self, new_entry):
+ if not new_entry:
+ # Call failed; perhaps missing permission?
+ self.authorize_redirect()
+ return
+ self.finish("Posted a message!")
+
+ """
+ # Add the OAuth resource request signature if we have credentials
+ url = "http://twitter.com" + path + ".json"
+ if access_token:
+ all_args = {}
+ all_args.update(args)
+ all_args.update(post_args or {})
+ consumer_token = self._oauth_consumer_token()
+ method = "POST" if post_args is not None else "GET"
+ oauth = self._oauth_request_parameters(
+ url, access_token, all_args, method=method)
+ args.update(oauth)
+ if args: url += "?" + urllib.urlencode(args)
+ callback = self.async_callback(self._on_twitter_request, callback)
+ http = httpclient.AsyncHTTPClient()
+ if post_args is not None:
+ http.fetch(url, method="POST", body=urllib.urlencode(post_args),
+ callback=callback)
+ else:
+ http.fetch(url, callback=callback)
+
+ def _on_twitter_request(self, callback, response):
+ if response.error:
+ _log.warning("Error response %s fetching %s", response.error,
+ response.request.url)
+ callback(None)
+ return
+ callback(escape.json_decode(response.body))
+
+ def _oauth_consumer_token(self):
+ self.require_setting("twitter_consumer_key", "Twitter OAuth")
+ self.require_setting("twitter_consumer_secret", "Twitter OAuth")
+ return dict(
+ key=self.settings["twitter_consumer_key"],
+ secret=self.settings["twitter_consumer_secret"])
+
+ def _oauth_get_user(self, access_token, callback):
+ callback = self.async_callback(self._parse_user_response, callback)
+ self.twitter_request(
+ "/users/show/" + access_token["screen_name"],
+ access_token=access_token, callback=callback)
+
+ def _parse_user_response(self, callback, user):
+ if user:
+ user["username"] = user["screen_name"]
+ callback(user)
+
+
+class FriendFeedMixin(OAuthMixin):
+ """FriendFeed OAuth authentication.
+
+ To authenticate with FriendFeed, register your application with
+ FriendFeed at http://friendfeed.com/api/applications. Then
+ copy your Consumer Key and Consumer Secret to the application settings
+ 'friendfeed_consumer_key' and 'friendfeed_consumer_secret'. Use
+ this Mixin on the handler for the URL you registered as your
+ application's Callback URL.
+
+ When your application is set up, you can use this Mixin like this
+ to authenticate the user with FriendFeed and get access to their feed:
+
+ class FriendFeedHandler(tornado.web.RequestHandler,
+ tornado.auth.FriendFeedMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ if self.get_argument("oauth_token", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authorize_redirect()
+
+ def _on_auth(self, user):
+ if not user:
+ raise tornado.web.HTTPError(500, "FriendFeed auth failed")
+ # Save the user using, e.g., set_secure_cookie()
+
+ The user object returned by get_authenticated_user() includes the
+ attributes 'username', 'name', and 'description' in addition to
+ 'access_token'. You should save the access token with the user;
+ it is required to make requests on behalf of the user later with
+ friendfeed_request().
+ """
+ _OAUTH_REQUEST_TOKEN_URL = "https://friendfeed.com/account/oauth/request_token"
+ _OAUTH_ACCESS_TOKEN_URL = "https://friendfeed.com/account/oauth/access_token"
+ _OAUTH_AUTHORIZE_URL = "https://friendfeed.com/account/oauth/authorize"
+ _OAUTH_NO_CALLBACKS = True
+
+ def friendfeed_request(self, path, callback, access_token=None,
+ post_args=None, **args):
+ """Fetches the given relative API path, e.g., "/bret/friends"
+
+ If the request is a POST, post_args should be provided. Query
+ string arguments should be given as keyword arguments.
+
+ All the FriendFeed methods are documented at
+ http://friendfeed.com/api/documentation.
+
+ Many methods require an OAuth access token which you can obtain
+ through authorize_redirect() and get_authenticated_user(). The
+ user returned through that process includes an 'access_token'
+ attribute that can be used to make authenticated requests via
+ this method. Example usage:
+
+ class MainHandler(tornado.web.RequestHandler,
+ tornado.auth.FriendFeedMixin):
+ @tornado.web.authenticated
+ @tornado.web.asynchronous
+ def get(self):
+ self.friendfeed_request(
+ "/entry",
+ post_args={"body": "Testing Tornado Web Server"},
+ access_token=self.current_user["access_token"],
+ callback=self.async_callback(self._on_post))
+
+ def _on_post(self, new_entry):
+ if not new_entry:
+ # Call failed; perhaps missing permission?
+ self.authorize_redirect()
+ return
+ self.finish("Posted a message!")
+
+ """
+ # Add the OAuth resource request signature if we have credentials
+ url = "http://friendfeed-api.com/v2" + path
+ if access_token:
+ all_args = {}
+ all_args.update(args)
+ all_args.update(post_args or {})
+ consumer_token = self._oauth_consumer_token()
+ method = "POST" if post_args is not None else "GET"
+ oauth = self._oauth_request_parameters(
+ url, access_token, all_args, method=method)
+ args.update(oauth)
+ if args: url += "?" + urllib.urlencode(args)
+ callback = self.async_callback(self._on_friendfeed_request, callback)
+ http = httpclient.AsyncHTTPClient()
+ if post_args is not None:
+ http.fetch(url, method="POST", body=urllib.urlencode(post_args),
+ callback=callback)
+ else:
+ http.fetch(url, callback=callback)
+
+ def _on_friendfeed_request(self, callback, response):
+ if response.error:
+ _log.warning("Error response %s fetching %s", response.error,
+ response.request.url)
+ callback(None)
+ return
+ callback(escape.json_decode(response.body))
+
+ def _oauth_consumer_token(self):
+ self.require_setting("friendfeed_consumer_key", "FriendFeed OAuth")
+ self.require_setting("friendfeed_consumer_secret", "FriendFeed OAuth")
+ return dict(
+ key=self.settings["friendfeed_consumer_key"],
+ secret=self.settings["friendfeed_consumer_secret"])
+
+ def _oauth_get_user(self, access_token, callback):
+ callback = self.async_callback(self._parse_user_response, callback)
+ self.friendfeed_request(
+ "/feedinfo/" + access_token["username"],
+ include="id,name,description", access_token=access_token,
+ callback=callback)
+
+ def _parse_user_response(self, callback, user):
+ if user:
+ user["username"] = user["id"]
+ callback(user)
+
+
+class GoogleMixin(OpenIdMixin, OAuthMixin):
+ """Google Open ID / OAuth authentication.
+
+ No application registration is necessary to use Google for authentication
+ or to access Google resources on behalf of a user. To authenticate with
+ Google, redirect with authenticate_redirect(). On return, parse the
+ response with get_authenticated_user(). We send a dict containing the
+ values for the user, including 'email', 'name', and 'locale'.
+ Example usage:
+
+ class GoogleHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ if self.get_argument("openid.mode", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authenticate_redirect()
+
+ def _on_auth(self, user):
+ if not user:
+ raise tornado.web.HTTPError(500, "Google auth failed")
+ # Save the user with, e.g., set_secure_cookie()
+
+ """
+ _OPENID_ENDPOINT = "https://www.google.com/accounts/o8/ud"
+ _OAUTH_ACCESS_TOKEN_URL = "https://www.google.com/accounts/OAuthGetAccessToken"
+
+ def authorize_redirect(self, oauth_scope, callback_uri=None,
+ ax_attrs=["name","email","language","username"]):
+ """Authenticates and authorizes for the given Google resource.
+
+ Some of the available resources are:
+
+ Gmail Contacts - http://www.google.com/m8/feeds/
+ Calendar - http://www.google.com/calendar/feeds/
+ Finance - http://finance.google.com/finance/feeds/
+
+ You can authorize multiple resources by separating the resource
+ URLs with a space.
+ """
+ callback_uri = callback_uri or self.request.path
+ args = self._openid_args(callback_uri, ax_attrs=ax_attrs,
+ oauth_scope=oauth_scope)
+ self.redirect(self._OPENID_ENDPOINT + "?" + urllib.urlencode(args))
+
+ def get_authenticated_user(self, callback):
+ """Fetches the authenticated user data upon redirect."""
+ # Look to see if we are doing combined OpenID/OAuth
+ oauth_ns = ""
+ for name, values in self.request.arguments.iteritems():
+ if name.startswith("openid.ns.") and \
+ values[-1] == u"http://specs.openid.net/extensions/oauth/1.0":
+ oauth_ns = name[10:]
+ break
+ token = self.get_argument("openid." + oauth_ns + ".request_token", "")
+ if token:
+ http = httpclient.AsyncHTTPClient()
+ token = dict(key=token, secret="")
+ http.fetch(self._oauth_access_token_url(token),
+ self.async_callback(self._on_access_token, callback))
+ else:
+ OpenIdMixin.get_authenticated_user(self, callback)
+
+ def _oauth_consumer_token(self):
+ self.require_setting("google_consumer_key", "Google OAuth")
+ self.require_setting("google_consumer_secret", "Google OAuth")
+ return dict(
+ key=self.settings["google_consumer_key"],
+ secret=self.settings["google_consumer_secret"])
+
+ def _oauth_get_user(self, access_token, callback):
+ OpenIdMixin.get_authenticated_user(self, callback)
+
+
+class FacebookMixin(object):
+ """Facebook Connect authentication.
+
+ To authenticate with Facebook, register your application with
+ Facebook at http://www.facebook.com/developers/apps.php. Then
+ copy your API Key and Application Secret to the application settings
+ 'facebook_api_key' and 'facebook_secret'.
+
+ When your application is set up, you can use this Mixin like this
+ to authenticate the user with Facebook:
+
+ class FacebookHandler(tornado.web.RequestHandler,
+ tornado.auth.FacebookMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ if self.get_argument("session", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authenticate_redirect()
+
+ def _on_auth(self, user):
+ if not user:
+ raise tornado.web.HTTPError(500, "Facebook auth failed")
+ # Save the user using, e.g., set_secure_cookie()
+
+ The user object returned by get_authenticated_user() includes the
+ attributes 'facebook_uid' and 'name' in addition to session attributes
+ like 'session_key'. You should save the session key with the user; it is
+ required to make requests on behalf of the user later with
+ facebook_request().
+ """
+ def authenticate_redirect(self, callback_uri=None, cancel_uri=None,
+ extended_permissions=None):
+ """Authenticates/installs this app for the current user."""
+ self.require_setting("facebook_api_key", "Facebook Connect")
+ callback_uri = callback_uri or self.request.path
+ args = {
+ "api_key": self.settings["facebook_api_key"],
+ "v": "1.0",
+ "fbconnect": "true",
+ "display": "page",
+ "next": urlparse.urljoin(self.request.full_url(), callback_uri),
+ "return_session": "true",
+ }
+ if cancel_uri:
+ args["cancel_url"] = urlparse.urljoin(
+ self.request.full_url(), cancel_uri)
+ if extended_permissions:
+ if isinstance(extended_permissions, basestring):
+ extended_permissions = [extended_permissions]
+ args["req_perms"] = ",".join(extended_permissions)
+ self.redirect("http://www.facebook.com/login.php?" +
+ urllib.urlencode(args))
+
+ def authorize_redirect(self, extended_permissions, callback_uri=None,
+ cancel_uri=None):
+ """Redirects to an authorization request for the given FB resource.
+
+ The available resource names are listed at
+ http://wiki.developers.facebook.com/index.php/Extended_permission.
+ The most common resource types include:
+
+ publish_stream
+ read_stream
+ email
+ sms
+
+ extended_permissions can be a single permission name or a list of
+ names. To get the session secret and session key, call
+ get_authenticated_user() just as you would with
+ authenticate_redirect().
+ """
+ self.authenticate_redirect(callback_uri, cancel_uri,
+ extended_permissions)
+
+ def get_authenticated_user(self, callback):
+ """Fetches the authenticated Facebook user.
+
+ The authenticated user includes the special Facebook attributes
+ 'session_key' and 'facebook_uid' in addition to the standard
+ user attributes like 'name'.
+ """
+ self.require_setting("facebook_api_key", "Facebook Connect")
+ session = escape.json_decode(self.get_argument("session"))
+ self.facebook_request(
+ method="facebook.users.getInfo",
+ callback=self.async_callback(
+ self._on_get_user_info, callback, session),
+ session_key=session["session_key"],
+ uids=session["uid"],
+ fields="uid,first_name,last_name,name,locale,pic_square," \
+ "profile_url,username")
+
+ def facebook_request(self, method, callback, **args):
+ """Makes a Facebook API REST request.
+
+ We automatically include the Facebook API key and signature, but
+ it is the callers responsibility to include 'session_key' and any
+ other required arguments to the method.
+
+ The available Facebook methods are documented here:
+ http://wiki.developers.facebook.com/index.php/API
+
+ Here is an example for the stream.get() method:
+
+ class MainHandler(tornado.web.RequestHandler,
+ tornado.auth.FacebookMixin):
+ @tornado.web.authenticated
+ @tornado.web.asynchronous
+ def get(self):
+ self.facebook_request(
+ method="stream.get",
+ callback=self.async_callback(self._on_stream),
+ session_key=self.current_user["session_key"])
+
+ def _on_stream(self, stream):
+ if stream is None:
+ # Not authorized to read the stream yet?
+ self.redirect(self.authorize_redirect("read_stream"))
+ return
+ self.render("stream.html", stream=stream)
+
+ """
+ self.require_setting("facebook_api_key", "Facebook Connect")
+ self.require_setting("facebook_secret", "Facebook Connect")
+ if not method.startswith("facebook."):
+ method = "facebook." + method
+ args["api_key"] = self.settings["facebook_api_key"]
+ args["v"] = "1.0"
+ args["method"] = method
+ args["call_id"] = str(long(time.time() * 1e6))
+ args["format"] = "json"
+ args["sig"] = self._signature(args)
+ url = "http://api.facebook.com/restserver.php?" + \
+ urllib.urlencode(args)
+ http = httpclient.AsyncHTTPClient()
+ http.fetch(url, callback=self.async_callback(
+ self._parse_response, callback))
+
+ def _on_get_user_info(self, callback, session, users):
+ if users is None:
+ callback(None)
+ return
+ callback({
+ "name": users[0]["name"],
+ "first_name": users[0]["first_name"],
+ "last_name": users[0]["last_name"],
+ "uid": users[0]["uid"],
+ "locale": users[0]["locale"],
+ "pic_square": users[0]["pic_square"],
+ "profile_url": users[0]["profile_url"],
+ "username": users[0].get("username"),
+ "session_key": session["session_key"],
+ "session_expires": session["expires"],
+ })
+
+ def _parse_response(self, callback, response):
+ if response.error:
+ _log.warning("HTTP error from Facebook: %s", response.error)
+ callback(None)
+ return
+ try:
+ json = escape.json_decode(response.body)
+ except:
+ _log.warning("Invalid JSON from Facebook: %r", response.body)
+ callback(None)
+ return
+ if isinstance(json, dict) and json.get("error_code"):
+ _log.warning("Facebook error: %d: %r", json["error_code"],
+ json.get("error_msg"))
+ callback(None)
+ return
+ callback(json)
+
+ def _signature(self, args):
+ parts = ["%s=%s" % (n, args[n]) for n in sorted(args.keys())]
+ body = "".join(parts) + self.settings["facebook_secret"]
+ if isinstance(body, unicode): body = body.encode("utf-8")
+ return hashlib.md5(body).hexdigest()
+
+
+def _oauth_signature(consumer_token, method, url, parameters={}, token=None):
+ """Calculates the HMAC-SHA1 OAuth signature for the given request.
+
+ See http://oauth.net/core/1.0/#signing_process
+ """
+ parts = urlparse.urlparse(url)
+ scheme, netloc, path = parts[:3]
+ normalized_url = scheme.lower() + "://" + netloc.lower() + path
+
+ base_elems = []
+ base_elems.append(method.upper())
+ base_elems.append(normalized_url)
+ base_elems.append("&".join("%s=%s" % (k, _oauth_escape(str(v)))
+ for k, v in sorted(parameters.items())))
+ base_string = "&".join(_oauth_escape(e) for e in base_elems)
+
+ key_elems = [consumer_token["secret"]]
+ key_elems.append(token["secret"] if token else "")
+ key = "&".join(key_elems)
+
+ hash = hmac.new(key, base_string, hashlib.sha1)
+ return binascii.b2a_base64(hash.digest())[:-1]
+
+
+def _oauth_escape(val):
+ if isinstance(val, unicode):
+ val = val.encode("utf-8")
+ return urllib.quote(val, safe="~")
+
+
+def _oauth_parse_response(body):
+ p = cgi.parse_qs(body, keep_blank_values=False)
+ token = dict(key=p["oauth_token"][0], secret=p["oauth_token_secret"][0])
+
+ # Add the extra parameters the Provider included to the token
+ special = ("oauth_token", "oauth_token_secret")
+ token.update((k, p[k][0]) for k in p if k not in special)
+ return token
diff --git a/vendor/tornado/tornado/autoreload.py b/vendor/tornado/tornado/autoreload.py
new file mode 100644
index 0000000000..231cfe892c
--- /dev/null
+++ b/vendor/tornado/tornado/autoreload.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""A module to automatically restart the server when a module is modified.
+
+This module depends on IOLoop, so it will not work in WSGI applications
+and Google AppEngine.
+"""
+
+import functools
+import errno
+import ioloop
+import logging
+import os
+import os.path
+import sys
+import types
+
+_log = logging.getLogger('tornado.autoreload')
+
+def start(io_loop=None, check_time=500):
+ """Restarts the process automatically when a module is modified.
+
+ We run on the I/O loop, and restarting is a destructive operation,
+ so will terminate any pending requests.
+ """
+ io_loop = io_loop or ioloop.IOLoop.instance()
+ modify_times = {}
+ callback = functools.partial(_reload_on_update, io_loop, modify_times)
+ scheduler = ioloop.PeriodicCallback(callback, check_time, io_loop=io_loop)
+ scheduler.start()
+
+
+_reload_attempted = False
+
+def _reload_on_update(io_loop, modify_times):
+ global _reload_attempted
+ if _reload_attempted:
+ # We already tried to reload and it didn't work, so don't try again.
+ return
+ for module in sys.modules.values():
+ # Some modules play games with sys.modules (e.g. email/__init__.py
+ # in the standard library), and occasionally this can cause strange
+ # failures in getattr. Just ignore anything that's not an ordinary
+ # module.
+ if not isinstance(module, types.ModuleType): continue
+ path = getattr(module, "__file__", None)
+ if not path: continue
+ if path.endswith(".pyc") or path.endswith(".pyo"):
+ path = path[:-1]
+ try:
+ modified = os.stat(path).st_mtime
+ except:
+ continue
+ if path not in modify_times:
+ modify_times[path] = modified
+ continue
+ if modify_times[path] != modified:
+ _log.info("%s modified; restarting server", path)
+ _reload_attempted = True
+ for fd in io_loop._handlers.keys():
+ try:
+ os.close(fd)
+ except:
+ pass
+ try:
+ os.execv(sys.executable, [sys.executable] + sys.argv)
+ except OSError, e:
+ # Mac OS X versions prior to 10.6 do not support execv in
+ # a process that contains multiple threads. Instead of
+ # re-executing in the current process, start a new one
+ # and cause the current process to exit. This isn't
+ # ideal since the new process is detached from the parent
+ # terminal and thus cannot easily be killed with ctrl-C,
+ # but it's better than not being able to autoreload at
+ # all.
+ # Unfortunately the errno returned in this case does not
+ # appear to be consistent, so we can't easily check for
+ # this error specifically.
+ os.spawnv(os.P_NOWAIT, sys.executable,
+ [sys.executable] + sys.argv)
+ sys.exit(0)
diff --git a/vendor/tornado/tornado/database.py b/vendor/tornado/tornado/database.py
new file mode 100644
index 0000000000..3f78e00b94
--- /dev/null
+++ b/vendor/tornado/tornado/database.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""A lightweight wrapper around MySQLdb."""
+
+import copy
+import MySQLdb
+import MySQLdb.constants
+import MySQLdb.converters
+import MySQLdb.cursors
+import itertools
+import logging
+
+_log = logging.getLogger('tornado.database')
+
+class Connection(object):
+ """A lightweight wrapper around MySQLdb DB-API connections.
+
+ The main value we provide is wrapping rows in a dict/object so that
+ columns can be accessed by name. Typical usage:
+
+ db = database.Connection("localhost", "mydatabase")
+ for article in db.query("SELECT * FROM articles"):
+ print article.title
+
+ Cursors are hidden by the implementation, but other than that, the methods
+ are very similar to the DB-API.
+
+ We explicitly set the timezone to UTC and the character encoding to
+ UTF-8 on all connections to avoid time zone and encoding errors.
+ """
+ def __init__(self, host, database, user=None, password=None):
+ self.host = host
+ self.database = database
+
+ args = dict(conv=CONVERSIONS, use_unicode=True, charset="utf8",
+ db=database, init_command='SET time_zone = "+0:00"',
+ sql_mode="TRADITIONAL")
+ if user is not None:
+ args["user"] = user
+ if password is not None:
+ args["passwd"] = password
+
+ # We accept a path to a MySQL socket file or a host(:port) string
+ if "/" in host:
+ args["unix_socket"] = host
+ else:
+ self.socket = None
+ pair = host.split(":")
+ if len(pair) == 2:
+ args["host"] = pair[0]
+ args["port"] = int(pair[1])
+ else:
+ args["host"] = host
+ args["port"] = 3306
+
+ self._db = None
+ self._db_args = args
+ try:
+ self.reconnect()
+ except:
+ _log.error("Cannot connect to MySQL on %s", self.host,
+ exc_info=True)
+
+ def __del__(self):
+ self.close()
+
+ def close(self):
+ """Closes this database connection."""
+ if getattr(self, "_db", None) is not None:
+ self._db.close()
+ self._db = None
+
+ def reconnect(self):
+ """Closes the existing database connection and re-opens it."""
+ self.close()
+ self._db = MySQLdb.connect(**self._db_args)
+ self._db.autocommit(True)
+
+ def iter(self, query, *parameters):
+ """Returns an iterator for the given query and parameters."""
+ if self._db is None: self.reconnect()
+ cursor = MySQLdb.cursors.SSCursor(self._db)
+ try:
+ self._execute(cursor, query, parameters)
+ column_names = [d[0] for d in cursor.description]
+ for row in cursor:
+ yield Row(zip(column_names, row))
+ finally:
+ cursor.close()
+
+ def query(self, query, *parameters):
+ """Returns a row list for the given query and parameters."""
+ cursor = self._cursor()
+ try:
+ self._execute(cursor, query, parameters)
+ column_names = [d[0] for d in cursor.description]
+ return [Row(itertools.izip(column_names, row)) for row in cursor]
+ finally:
+ cursor.close()
+
+ def get(self, query, *parameters):
+ """Returns the first row returned for the given query."""
+ rows = self.query(query, *parameters)
+ if not rows:
+ return None
+ elif len(rows) > 1:
+ raise Exception("Multiple rows returned for Database.get() query")
+ else:
+ return rows[0]
+
+ def execute(self, query, *parameters):
+ """Executes the given query, returning the lastrowid from the query."""
+ cursor = self._cursor()
+ try:
+ self._execute(cursor, query, parameters)
+ return cursor.lastrowid
+ finally:
+ cursor.close()
+
+ def executemany(self, query, parameters):
+ """Executes the given query against all the given param sequences.
+
+ We return the lastrowid from the query.
+ """
+ cursor = self._cursor()
+ try:
+ cursor.executemany(query, parameters)
+ return cursor.lastrowid
+ finally:
+ cursor.close()
+
+ def _cursor(self):
+ if self._db is None: self.reconnect()
+ return self._db.cursor()
+
+ def _execute(self, cursor, query, parameters):
+ try:
+ return cursor.execute(query, parameters)
+ except OperationalError:
+ _log.error("Error connecting to MySQL on %s", self.host)
+ self.close()
+ raise
+
+
+class Row(dict):
+ """A dict that allows for object-like property access syntax."""
+ def __getattr__(self, name):
+ try:
+ return self[name]
+ except KeyError:
+ raise AttributeError(name)
+
+
+# Fix the access conversions to properly recognize unicode/binary
+FIELD_TYPE = MySQLdb.constants.FIELD_TYPE
+FLAG = MySQLdb.constants.FLAG
+CONVERSIONS = copy.deepcopy(MySQLdb.converters.conversions)
+for field_type in \
+ [FIELD_TYPE.BLOB, FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING] + \
+ ([FIELD_TYPE.VARCHAR] if 'VARCHAR' in vars(FIELD_TYPE) else []):
+ CONVERSIONS[field_type].insert(0, (FLAG.BINARY, str))
+
+
+# Alias some common MySQL exceptions
+IntegrityError = MySQLdb.IntegrityError
+OperationalError = MySQLdb.OperationalError
diff --git a/vendor/tornado/tornado/epoll.c b/vendor/tornado/tornado/epoll.c
new file mode 100644
index 0000000000..9a2e3a3747
--- /dev/null
+++ b/vendor/tornado/tornado/epoll.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2009 Facebook
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "Python.h"
+#include <string.h>
+#include <sys/epoll.h>
+
+#define MAX_EVENTS 24
+
+/*
+ * Simple wrapper around epoll_create.
+ */
+static PyObject* _epoll_create(void) {
+ int fd = epoll_create(MAX_EVENTS);
+ if (fd == -1) {
+ PyErr_SetFromErrno(PyExc_Exception);
+ return NULL;
+ }
+
+ return PyInt_FromLong(fd);
+}
+
+/*
+ * Simple wrapper around epoll_ctl. We throw an exception if the call fails
+ * rather than returning the error code since it is an infrequent (and likely
+ * catastrophic) event when it does happen.
+ */
+static PyObject* _epoll_ctl(PyObject* self, PyObject* args) {
+ int epfd, op, fd, events;
+ struct epoll_event event;
+
+ if (!PyArg_ParseTuple(args, "iiiI", &epfd, &op, &fd, &events)) {
+ return NULL;
+ }
+
+ memset(&event, 0, sizeof(event));
+ event.events = events;
+ event.data.fd = fd;
+ if (epoll_ctl(epfd, op, fd, &event) == -1) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+/*
+ * Simple wrapper around epoll_wait. We return None if the call times out and
+ * throw an exception if an error occurs. Otherwise, we return a list of
+ * (fd, event) tuples.
+ */
+static PyObject* _epoll_wait(PyObject* self, PyObject* args) {
+ struct epoll_event events[MAX_EVENTS];
+ int epfd, timeout, num_events, i;
+ PyObject* list;
+ PyObject* tuple;
+
+ if (!PyArg_ParseTuple(args, "ii", &epfd, &timeout)) {
+ return NULL;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ num_events = epoll_wait(epfd, events, MAX_EVENTS, timeout);
+ Py_END_ALLOW_THREADS
+ if (num_events == -1) {
+ PyErr_SetFromErrno(PyExc_Exception);
+ return NULL;
+ }
+
+ list = PyList_New(num_events);
+ for (i = 0; i < num_events; i++) {
+ tuple = PyTuple_New(2);
+ PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(events[i].data.fd));
+ PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(events[i].events));
+ PyList_SET_ITEM(list, i, tuple);
+ }
+ return list;
+}
+
+/*
+ * Our method declararations
+ */
+static PyMethodDef kEpollMethods[] = {
+ {"epoll_create", (PyCFunction)_epoll_create, METH_NOARGS,
+ "Create an epoll file descriptor"},
+ {"epoll_ctl", _epoll_ctl, METH_VARARGS,
+ "Control an epoll file descriptor"},
+ {"epoll_wait", _epoll_wait, METH_VARARGS,
+ "Wait for events on an epoll file descriptor"},
+ {NULL, NULL, 0, NULL}
+};
+
+/*
+ * Module initialization
+ */
+PyMODINIT_FUNC initepoll(void) {
+ Py_InitModule("epoll", kEpollMethods);
+}
diff --git a/vendor/tornado/tornado/escape.py b/vendor/tornado/tornado/escape.py
new file mode 100644
index 0000000000..bacb1c51d0
--- /dev/null
+++ b/vendor/tornado/tornado/escape.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Escaping/unescaping methods for HTML, JSON, URLs, and others."""
+
+import htmlentitydefs
+import re
+import xml.sax.saxutils
+import urllib
+
+try:
+ import json
+ assert hasattr(json, "loads") and hasattr(json, "dumps")
+ _json_decode = lambda s: json.loads(s)
+ _json_encode = lambda v: json.dumps(v)
+except:
+ try:
+ import simplejson
+ _json_decode = lambda s: simplejson.loads(_unicode(s))
+ _json_encode = lambda v: simplejson.dumps(v)
+ except ImportError:
+ try:
+ # For Google AppEngine
+ from django.utils import simplejson
+ _json_decode = lambda s: simplejson.loads(_unicode(s))
+ _json_encode = lambda v: simplejson.dumps(v)
+ except ImportError:
+ raise Exception("A JSON parser is required, e.g., simplejson at "
+ "http://pypi.python.org/pypi/simplejson/")
+
+
+def xhtml_escape(value):
+ """Escapes a string so it is valid within XML or XHTML."""
+ return utf8(xml.sax.saxutils.escape(value, {'"': "&quot;"}))
+
+
+def xhtml_unescape(value):
+ """Un-escapes an XML-escaped string."""
+ return re.sub(r"&(#?)(\w+?);", _convert_entity, _unicode(value))
+
+
+def json_encode(value):
+ """JSON-encodes the given Python object."""
+ return _json_encode(value)
+
+
+def json_decode(value):
+ """Returns Python objects for the given JSON string."""
+ return _json_decode(value)
+
+
+def squeeze(value):
+ """Replace all sequences of whitespace chars with a single space."""
+ return re.sub(r"[\x00-\x20]+", " ", value).strip()
+
+
+def url_escape(value):
+ """Returns a valid URL-encoded version of the given value."""
+ return urllib.quote_plus(utf8(value))
+
+
+def url_unescape(value):
+ """Decodes the given value from a URL."""
+ return _unicode(urllib.unquote_plus(value))
+
+
+def utf8(value):
+ if isinstance(value, unicode):
+ return value.encode("utf-8")
+ assert isinstance(value, str)
+ return value
+
+
+def _unicode(value):
+ if isinstance(value, str):
+ return value.decode("utf-8")
+ assert isinstance(value, unicode)
+ return value
+
+
+def _convert_entity(m):
+ if m.group(1) == "#":
+ try:
+ return unichr(int(m.group(2)))
+ except ValueError:
+ return "&#%s;" % m.group(2)
+ try:
+ return _HTML_UNICODE_MAP[m.group(2)]
+ except KeyError:
+ return "&%s;" % m.group(2)
+
+
+def _build_unicode_map():
+ unicode_map = {}
+ for name, value in htmlentitydefs.name2codepoint.iteritems():
+ unicode_map[name] = unichr(value)
+ return unicode_map
+
+_HTML_UNICODE_MAP = _build_unicode_map()
diff --git a/vendor/tornado/tornado/httpclient.py b/vendor/tornado/tornado/httpclient.py
new file mode 100644
index 0000000000..2c9155eb9e
--- /dev/null
+++ b/vendor/tornado/tornado/httpclient.py
@@ -0,0 +1,465 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Blocking and non-blocking HTTP client implementations using pycurl."""
+
+import calendar
+import collections
+import cStringIO
+import email.utils
+import errno
+import functools
+import httplib
+import ioloop
+import logging
+import pycurl
+import time
+import weakref
+
+_log = logging.getLogger('tornado.httpclient')
+
+class HTTPClient(object):
+ """A blocking HTTP client backed with pycurl.
+
+ Typical usage looks like this:
+
+ http_client = httpclient.HTTPClient()
+ try:
+ response = http_client.fetch("http://www.google.com/")
+ print response.body
+ except httpclient.HTTPError, e:
+ print "Error:", e
+
+ fetch() can take a string URL or an HTTPRequest instance, which offers
+ more options, like executing POST/PUT/DELETE requests.
+ """
+ def __init__(self, max_simultaneous_connections=None):
+ self._curl = _curl_create(max_simultaneous_connections)
+
+ def __del__(self):
+ self._curl.close()
+
+ def fetch(self, request, **kwargs):
+ """Executes an HTTPRequest, returning an HTTPResponse.
+
+ If an error occurs during the fetch, we raise an HTTPError.
+ """
+ if not isinstance(request, HTTPRequest):
+ request = HTTPRequest(url=request, **kwargs)
+ buffer = cStringIO.StringIO()
+ headers = {}
+ try:
+ _curl_setup_request(self._curl, request, buffer, headers)
+ self._curl.perform()
+ code = self._curl.getinfo(pycurl.HTTP_CODE)
+ if code < 200 or code >= 300:
+ raise HTTPError(code)
+ effective_url = self._curl.getinfo(pycurl.EFFECTIVE_URL)
+ return HTTPResponse(
+ request=request, code=code, headers=headers,
+ body=buffer.getvalue(), effective_url=effective_url)
+ except pycurl.error, e:
+ raise CurlError(*e)
+ finally:
+ buffer.close()
+
+
+class AsyncHTTPClient(object):
+ """An non-blocking HTTP client backed with pycurl.
+
+ Example usage:
+
+ import ioloop
+
+ def handle_request(response):
+ if response.error:
+ print "Error:", response.error
+ else:
+ print response.body
+ ioloop.IOLoop.instance().stop()
+
+ http_client = httpclient.AsyncHTTPClient()
+ http_client.fetch("http://www.google.com/", handle_request)
+ ioloop.IOLoop.instance().start()
+
+ fetch() can take a string URL or an HTTPRequest instance, which offers
+ more options, like executing POST/PUT/DELETE requests.
+
+ The keyword argument max_clients to the AsyncHTTPClient constructor
+ determines the maximum number of simultaneous fetch() operations that
+ can execute in parallel on each IOLoop.
+ """
+ _ASYNC_CLIENTS = weakref.WeakKeyDictionary()
+
+ def __new__(cls, io_loop=None, max_clients=10,
+ max_simultaneous_connections=None):
+ # There is one client per IOLoop since they share curl instances
+ io_loop = io_loop or ioloop.IOLoop.instance()
+ if io_loop in cls._ASYNC_CLIENTS:
+ return cls._ASYNC_CLIENTS[io_loop]
+ else:
+ instance = super(AsyncHTTPClient, cls).__new__(cls)
+ instance.io_loop = io_loop
+ instance._multi = pycurl.CurlMulti()
+ instance._curls = [_curl_create(max_simultaneous_connections)
+ for i in xrange(max_clients)]
+ instance._free_list = instance._curls[:]
+ instance._requests = collections.deque()
+ instance._fds = {}
+ instance._events = {}
+ instance._added_perform_callback = False
+ instance._timeout = None
+ cls._ASYNC_CLIENTS[io_loop] = instance
+ return instance
+
+ def close(self):
+ """Destroys this http client, freeing any file descriptors used.
+ Not needed in normal use, but may be helpful in unittests that
+ create and destroy http clients. No other methods may be called
+ on the AsyncHTTPClient after close().
+ """
+ del AsyncHTTPClient._ASYNC_CLIENTS[self.io_loop]
+ for curl in self._curls:
+ curl.close()
+ self._multi.close()
+
+ def fetch(self, request, callback, **kwargs):
+ """Executes an HTTPRequest, calling callback with an HTTPResponse.
+
+ If an error occurs during the fetch, the HTTPResponse given to the
+ callback has a non-None error attribute that contains the exception
+ encountered during the request. You can call response.reraise() to
+ throw the exception (if any) in the callback.
+ """
+ if not isinstance(request, HTTPRequest):
+ request = HTTPRequest(url=request, **kwargs)
+ self._requests.append((request, callback))
+ self._add_perform_callback()
+
+ def _add_perform_callback(self):
+ if not self._added_perform_callback:
+ self.io_loop.add_callback(self._perform)
+ self._added_perform_callback = True
+
+ def _handle_events(self, fd, events):
+ self._events[fd] = events
+ self._add_perform_callback()
+
+ def _handle_timeout(self):
+ self._timeout = None
+ self._perform()
+
+ def _perform(self):
+ self._added_perform_callback = False
+
+ while True:
+ while True:
+ ret, num_handles = self._multi.perform()
+ if ret != pycurl.E_CALL_MULTI_PERFORM:
+ break
+
+ # Handle completed fetches
+ completed = 0
+ while True:
+ num_q, ok_list, err_list = self._multi.info_read()
+ for curl in ok_list:
+ self._finish(curl)
+ completed += 1
+ for curl, errnum, errmsg in err_list:
+ self._finish(curl, errnum, errmsg)
+ completed += 1
+ if num_q == 0:
+ break
+
+ # Start fetching new URLs
+ started = 0
+ while self._free_list and self._requests:
+ started += 1
+ curl = self._free_list.pop()
+ (request, callback) = self._requests.popleft()
+ curl.info = {
+ "headers": {},
+ "buffer": cStringIO.StringIO(),
+ "request": request,
+ "callback": callback,
+ "start_time": time.time(),
+ }
+ _curl_setup_request(curl, request, curl.info["buffer"],
+ curl.info["headers"])
+ self._multi.add_handle(curl)
+
+ if not started and not completed:
+ break
+
+ if self._timeout is not None:
+ self.io_loop.remove_timeout(self._timeout)
+ self._timeout = None
+
+ if num_handles:
+ self._timeout = self.io_loop.add_timeout(
+ time.time() + 0.2, self._handle_timeout)
+
+ # Wait for more I/O
+ fds = {}
+ (readable, writable, exceptable) = self._multi.fdset()
+ for fd in readable:
+ fds[fd] = fds.get(fd, 0) | 0x1 | 0x2
+ for fd in writable:
+ fds[fd] = fds.get(fd, 0) | 0x4
+ for fd in exceptable:
+ fds[fd] = fds.get(fd, 0) | 0x8 | 0x10
+
+ for fd in self._fds:
+ if fd not in fds:
+ try:
+ self.io_loop.remove_handler(fd)
+ except (OSError, IOError), e:
+ if e[0] != errno.ENOENT:
+ raise
+
+ for fd, events in fds.iteritems():
+ old_events = self._fds.get(fd, None)
+ if old_events is None:
+ self.io_loop.add_handler(fd, self._handle_events, events)
+ elif old_events != events:
+ try:
+ self.io_loop.update_handler(fd, events)
+ except (OSError, IOError), e:
+ if e[0] == errno.ENOENT:
+ self.io_loop.add_handler(fd, self._handle_events,
+ events)
+ else:
+ raise
+ self._fds = fds
+
+ def _finish(self, curl, curl_error=None, curl_message=None):
+ info = curl.info
+ curl.info = None
+ self._multi.remove_handle(curl)
+ self._free_list.append(curl)
+ if curl_error:
+ error = CurlError(curl_error, curl_message)
+ code = error.code
+ body = None
+ effective_url = None
+ else:
+ error = None
+ code = curl.getinfo(pycurl.HTTP_CODE)
+ body = info["buffer"].getvalue()
+ effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)
+ info["buffer"].close()
+ info["callback"](HTTPResponse(
+ request=info["request"], code=code, headers=info["headers"],
+ body=body, effective_url=effective_url, error=error,
+ request_time=time.time() - info["start_time"]))
+
+
+class HTTPRequest(object):
+ def __init__(self, url, method="GET", headers={}, body=None,
+ auth_username=None, auth_password=None,
+ connect_timeout=20.0, request_timeout=20.0,
+ if_modified_since=None, follow_redirects=True,
+ max_redirects=5, user_agent=None, use_gzip=True,
+ network_interface=None, streaming_callback=None,
+ header_callback=None, prepare_curl_callback=None):
+ if if_modified_since:
+ timestamp = calendar.timegm(if_modified_since.utctimetuple())
+ headers["If-Modified-Since"] = email.utils.formatdate(
+ timestamp, localtime=False, usegmt=True)
+ if "Pragma" not in headers:
+ headers["Pragma"] = ""
+ self.url = _utf8(url)
+ self.method = method
+ self.headers = headers
+ self.body = body
+ self.auth_username = _utf8(auth_username)
+ self.auth_password = _utf8(auth_password)
+ self.connect_timeout = connect_timeout
+ self.request_timeout = request_timeout
+ self.follow_redirects = follow_redirects
+ self.max_redirects = max_redirects
+ self.user_agent = user_agent
+ self.use_gzip = use_gzip
+ self.network_interface = network_interface
+ self.streaming_callback = streaming_callback
+ self.header_callback = header_callback
+ self.prepare_curl_callback = prepare_curl_callback
+
+
+class HTTPResponse(object):
+ def __init__(self, request, code, headers={}, body="", effective_url=None,
+ error=None, request_time=None):
+ self.request = request
+ self.code = code
+ self.headers = headers
+ self.body = body
+ if effective_url is None:
+ self.effective_url = request.url
+ else:
+ self.effective_url = effective_url
+ if error is None:
+ if self.code < 200 or self.code >= 300:
+ self.error = HTTPError(self.code)
+ else:
+ self.error = None
+ else:
+ self.error = error
+ self.request_time = request_time
+
+ def rethrow(self):
+ if self.error:
+ raise self.error
+
+ def __repr__(self):
+ args = ",".join("%s=%r" % i for i in self.__dict__.iteritems())
+ return "%s(%s)" % (self.__class__.__name__, args)
+
+
+class HTTPError(Exception):
+ def __init__(self, code, message=None):
+ self.code = code
+ message = message or httplib.responses.get(code, "Unknown")
+ Exception.__init__(self, "HTTP %d: %s" % (self.code, message))
+
+
+class CurlError(HTTPError):
+ def __init__(self, errno, message):
+ HTTPError.__init__(self, 599, message)
+ self.errno = errno
+
+
+def _curl_create(max_simultaneous_connections=None):
+ curl = pycurl.Curl()
+ if _log.isEnabledFor(logging.DEBUG):
+ curl.setopt(pycurl.VERBOSE, 1)
+ curl.setopt(pycurl.DEBUGFUNCTION, _curl_debug)
+ curl.setopt(pycurl.MAXCONNECTS, max_simultaneous_connections or 5)
+ return curl
+
+
+def _curl_setup_request(curl, request, buffer, headers):
+ curl.setopt(pycurl.URL, request.url)
+ curl.setopt(pycurl.HTTPHEADER,
+ ["%s: %s" % i for i in request.headers.iteritems()])
+ try:
+ if request.header_callback:
+ curl.setopt(pycurl.HEADERFUNCTION, request.header_callback)
+ else:
+ curl.setopt(pycurl.HEADERFUNCTION,
+ functools.partial(_curl_header_callback, headers))
+ except:
+ # Old version of curl; response will not include headers
+ pass
+ if request.streaming_callback:
+ curl.setopt(pycurl.WRITEFUNCTION, request.streaming_callback)
+ else:
+ curl.setopt(pycurl.WRITEFUNCTION, buffer.write)
+ curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)
+ curl.setopt(pycurl.MAXREDIRS, request.max_redirects)
+ curl.setopt(pycurl.CONNECTTIMEOUT, int(request.connect_timeout))
+ curl.setopt(pycurl.TIMEOUT, int(request.request_timeout))
+ if request.user_agent:
+ curl.setopt(pycurl.USERAGENT, request.user_agent)
+ else:
+ curl.setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; pycurl)")
+ if request.network_interface:
+ curl.setopt(pycurl.INTERFACE, request.network_interface)
+ if request.use_gzip:
+ curl.setopt(pycurl.ENCODING, "gzip,deflate")
+ else:
+ curl.setopt(pycurl.ENCODING, "none")
+
+ # Set the request method through curl's retarded interface which makes
+ # up names for almost every single method
+ curl_options = {
+ "GET": pycurl.HTTPGET,
+ "POST": pycurl.POST,
+ "PUT": pycurl.UPLOAD,
+ "HEAD": pycurl.NOBODY,
+ }
+ custom_methods = set(["DELETE"])
+ for o in curl_options.values():
+ curl.setopt(o, False)
+ if request.method in curl_options:
+ curl.unsetopt(pycurl.CUSTOMREQUEST)
+ curl.setopt(curl_options[request.method], True)
+ elif request.method in custom_methods:
+ curl.setopt(pycurl.CUSTOMREQUEST, request.method)
+ else:
+ raise KeyError('unknown method ' + request.method)
+
+ # Handle curl's cryptic options for every individual HTTP method
+ if request.method in ("POST", "PUT"):
+ request_buffer = cStringIO.StringIO(request.body)
+ curl.setopt(pycurl.READFUNCTION, request_buffer.read)
+ if request.method == "POST":
+ def ioctl(cmd):
+ if cmd == curl.IOCMD_RESTARTREAD:
+ request_buffer.seek(0)
+ curl.setopt(pycurl.IOCTLFUNCTION, ioctl)
+ curl.setopt(pycurl.POSTFIELDSIZE, len(request.body))
+ else:
+ curl.setopt(pycurl.INFILESIZE, len(request.body))
+
+ if request.auth_username and request.auth_password:
+ userpwd = "%s:%s" % (request.auth_username, request.auth_password)
+ curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
+ curl.setopt(pycurl.USERPWD, userpwd)
+ _log.info("%s %s (username: %r)", request.method, request.url,
+ request.auth_username)
+ else:
+ curl.unsetopt(pycurl.USERPWD)
+ _log.info("%s %s", request.method, request.url)
+ if request.prepare_curl_callback is not None:
+ request.prepare_curl_callback(curl)
+
+
+def _curl_header_callback(headers, header_line):
+ if header_line.startswith("HTTP/"):
+ headers.clear()
+ return
+ if header_line == "\r\n":
+ return
+ parts = header_line.split(": ")
+ if len(parts) != 2:
+ _log.warning("Invalid HTTP response header line %r", header_line)
+ return
+ name = parts[0].strip()
+ value = parts[1].strip()
+ if name in headers:
+ headers[name] = headers[name] + ',' + value
+ else:
+ headers[name] = value
+
+
+def _curl_debug(debug_type, debug_msg):
+ debug_types = ('I', '<', '>', '<', '>')
+ if debug_type == 0:
+ _log.debug('%s', debug_msg.strip())
+ elif debug_type in (1, 2):
+ for line in debug_msg.splitlines():
+ _log.debug('%s %s', debug_types[debug_type], line)
+ elif debug_type == 4:
+ _log.debug('%s %r', debug_types[debug_type], debug_msg)
+
+
+def _utf8(value):
+ if value is None:
+ return value
+ if isinstance(value, unicode):
+ return value.encode("utf-8")
+ assert isinstance(value, str)
+ return value
diff --git a/vendor/tornado/tornado/httpserver.py b/vendor/tornado/tornado/httpserver.py
new file mode 100644
index 0000000000..a7ec57eec4
--- /dev/null
+++ b/vendor/tornado/tornado/httpserver.py
@@ -0,0 +1,450 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""A non-blocking, single-threaded HTTP server."""
+
+import cgi
+import errno
+import functools
+import ioloop
+import iostream
+import logging
+import os
+import socket
+import time
+import urlparse
+
+try:
+ import fcntl
+except ImportError:
+ if os.name == 'nt':
+ import win32_support as fcntl
+ else:
+ raise
+
+try:
+ import ssl # Python 2.6+
+except ImportError:
+ ssl = None
+
+_log = logging.getLogger('tornado.httpserver')
+
+class HTTPServer(object):
+ """A non-blocking, single-threaded HTTP server.
+
+ A server is defined by a request callback that takes an HTTPRequest
+ instance as an argument and writes a valid HTTP response with
+ request.write(). request.finish() finishes the request (but does not
+ necessarily close the connection in the case of HTTP/1.1 keep-alive
+ requests). A simple example server that echoes back the URI you
+ requested:
+
+ import httpserver
+ import ioloop
+
+ def handle_request(request):
+ message = "You requested %s\n" % request.uri
+ request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % (
+ len(message), message))
+ request.finish()
+
+ http_server = httpserver.HTTPServer(handle_request)
+ http_server.listen(8888)
+ ioloop.IOLoop.instance().start()
+
+ HTTPServer is a very basic connection handler. Beyond parsing the
+ HTTP request body and headers, the only HTTP semantics implemented
+ in HTTPServer is HTTP/1.1 keep-alive connections. We do not, however,
+ implement chunked encoding, so the request callback must provide a
+ Content-Length header or implement chunked encoding for HTTP/1.1
+ requests for the server to run correctly for HTTP/1.1 clients. If
+ the request handler is unable to do this, you can provide the
+ no_keep_alive argument to the HTTPServer constructor, which will
+ ensure the connection is closed on every request no matter what HTTP
+ version the client is using.
+
+ If xheaders is True, we support the X-Real-Ip and X-Scheme headers,
+ which override the remote IP and HTTP scheme for all requests. These
+ headers are useful when running Tornado behind a reverse proxy or
+ load balancer.
+
+ HTTPServer can serve HTTPS (SSL) traffic with Python 2.6+ and OpenSSL.
+ To make this server serve SSL traffic, send the ssl_options dictionary
+ argument with the arguments required for the ssl.wrap_socket() method,
+ including "certfile" and "keyfile":
+
+ HTTPServer(applicaton, ssl_options={
+ "certfile": os.path.join(data_dir, "mydomain.crt"),
+ "keyfile": os.path.join(data_dir, "mydomain.key"),
+ })
+
+ By default, listen() runs in a single thread in a single process. You
+ can utilize all available CPUs on this machine by calling bind() and
+ start() instead of listen():
+
+ http_server = httpserver.HTTPServer(handle_request)
+ http_server.bind(8888)
+ http_server.start() # Forks multiple sub-processes
+ ioloop.IOLoop.instance().start()
+
+ start() detects the number of CPUs on this machine and "pre-forks" that
+ number of child processes so that we have one Tornado process per CPU,
+ all with their own IOLoop. You can also pass in the specific number of
+ child processes you want to run with if you want to override this
+ auto-detection.
+ """
+ def __init__(self, request_callback, no_keep_alive=False, io_loop=None,
+ xheaders=False, ssl_options=None):
+ """Initializes the server with the given request callback.
+
+ If you use pre-forking/start() instead of the listen() method to
+ start your server, you should not pass an IOLoop instance to this
+ constructor. Each pre-forked child process will create its own
+ IOLoop instance after the forking process.
+ """
+ self.request_callback = request_callback
+ self.no_keep_alive = no_keep_alive
+ self.io_loop = io_loop
+ self.xheaders = xheaders
+ self.ssl_options = ssl_options
+ self._socket = None
+ self._started = False
+
+ def listen(self, port, address=""):
+ """Binds to the given port and starts the server in a single process.
+
+ This method is a shortcut for:
+
+ server.bind(port, address)
+ server.start(1)
+
+ """
+ self.bind(port, address)
+ self.start(1)
+
+ def bind(self, port, address=""):
+ """Binds this server to the given port on the given IP address.
+
+ To start the server, call start(). If you want to run this server
+ in a single process, you can call listen() as a shortcut to the
+ sequence of bind() and start() calls.
+ """
+ assert not self._socket
+ self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ flags = fcntl.fcntl(self._socket.fileno(), fcntl.F_GETFD)
+ flags |= fcntl.FD_CLOEXEC
+ fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags)
+ self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self._socket.setblocking(0)
+ self._socket.bind((address, port))
+ self._socket.listen(128)
+
+ def start(self, num_processes=None):
+ """Starts this server in the IOLoop.
+
+ By default, we detect the number of cores available on this machine
+ and fork that number of child processes. If num_processes is given, we
+ fork that specific number of sub-processes.
+
+ If num_processes is 1 or we detect only 1 CPU core, we run the server
+ in this process and do not fork any additional child process.
+
+ Since we run use processes and not threads, there is no shared memory
+ between any server code.
+ """
+ assert not self._started
+ self._started = True
+ if num_processes is None:
+ # Use sysconf to detect the number of CPUs (cores)
+ try:
+ num_processes = os.sysconf("SC_NPROCESSORS_CONF")
+ except ValueError:
+ _log.error("Could not get num processors from sysconf; "
+ "running with one process")
+ num_processes = 1
+ if num_processes > 1 and ioloop.IOLoop.initialized():
+ _log.error("Cannot run in multiple processes: IOLoop instance "
+ "has already been initialized. You cannot call "
+ "IOLoop.instance() before calling start()")
+ num_processes = 1
+ if num_processes > 1:
+ _log.info("Pre-forking %d server processes", num_processes)
+ for i in range(num_processes):
+ if os.fork() == 0:
+ self.io_loop = ioloop.IOLoop.instance()
+ self.io_loop.add_handler(
+ self._socket.fileno(), self._handle_events,
+ ioloop.IOLoop.READ)
+ return
+ os.waitpid(-1, 0)
+ else:
+ if not self.io_loop:
+ self.io_loop = ioloop.IOLoop.instance()
+ self.io_loop.add_handler(self._socket.fileno(),
+ self._handle_events,
+ ioloop.IOLoop.READ)
+
+ def stop(self):
+ self.io_loop.remove_handler(self._socket.fileno())
+ self._socket.close()
+
+ def _handle_events(self, fd, events):
+ while True:
+ try:
+ connection, address = self._socket.accept()
+ except socket.error, e:
+ if e[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
+ return
+ raise
+ if self.ssl_options is not None:
+ assert ssl, "Python 2.6+ and OpenSSL required for SSL"
+ connection = ssl.wrap_socket(
+ connection, server_side=True, **self.ssl_options)
+ try:
+ stream = iostream.IOStream(connection, io_loop=self.io_loop)
+ HTTPConnection(stream, address, self.request_callback,
+ self.no_keep_alive, self.xheaders)
+ except:
+ _log.error("Error in connection callback", exc_info=True)
+
+
+class HTTPConnection(object):
+ """Handles a connection to an HTTP client, executing HTTP requests.
+
+ We parse HTTP headers and bodies, and execute the request callback
+ until the HTTP conection is closed.
+ """
+ def __init__(self, stream, address, request_callback, no_keep_alive=False,
+ xheaders=False):
+ self.stream = stream
+ self.address = address
+ self.request_callback = request_callback
+ self.no_keep_alive = no_keep_alive
+ self.xheaders = xheaders
+ self._request = None
+ self._request_finished = False
+ self.stream.read_until("\r\n\r\n", self._on_headers)
+
+ def write(self, chunk):
+ assert self._request, "Request closed"
+ if not self.stream.closed():
+ self.stream.write(chunk, self._on_write_complete)
+
+ def finish(self):
+ assert self._request, "Request closed"
+ self._request_finished = True
+ if not self.stream.writing():
+ self._finish_request()
+
+ def _on_write_complete(self):
+ if self._request_finished:
+ self._finish_request()
+
+ def _finish_request(self):
+ if self.no_keep_alive:
+ disconnect = True
+ else:
+ connection_header = self._request.headers.get("Connection")
+ if self._request.supports_http_1_1():
+ disconnect = connection_header == "close"
+ elif ("Content-Length" in self._request.headers
+ or self._request.method in ("HEAD", "GET")):
+ disconnect = connection_header != "Keep-Alive"
+ else:
+ disconnect = True
+ self._request = None
+ self._request_finished = False
+ if disconnect:
+ self.stream.close()
+ return
+ self.stream.read_until("\r\n\r\n", self._on_headers)
+
+ def _on_headers(self, data):
+ eol = data.find("\r\n")
+ start_line = data[:eol]
+ method, uri, version = start_line.split(" ")
+ if not version.startswith("HTTP/"):
+ raise Exception("Malformed HTTP version in HTTP Request-Line")
+ headers = HTTPHeaders.parse(data[eol:])
+ self._request = HTTPRequest(
+ connection=self, method=method, uri=uri, version=version,
+ headers=headers, remote_ip=self.address[0])
+
+ content_length = headers.get("Content-Length")
+ if content_length:
+ content_length = int(content_length)
+ if content_length > self.stream.max_buffer_size:
+ raise Exception("Content-Length too long")
+ if headers.get("Expect") == "100-continue":
+ self.stream.write("HTTP/1.1 100 (Continue)\r\n\r\n")
+ self.stream.read_bytes(content_length, self._on_request_body)
+ return
+
+ self.request_callback(self._request)
+
+ def _on_request_body(self, data):
+ self._request.body = data
+ content_type = self._request.headers.get("Content-Type", "")
+ if self._request.method == "POST":
+ if content_type.startswith("application/x-www-form-urlencoded"):
+ arguments = cgi.parse_qs(self._request.body)
+ for name, values in arguments.iteritems():
+ values = [v for v in values if v]
+ if values:
+ self._request.arguments.setdefault(name, []).extend(
+ values)
+ elif content_type.startswith("multipart/form-data"):
+ boundary = content_type[30:]
+ if boundary: self._parse_mime_body(boundary, data)
+ self.request_callback(self._request)
+
+ def _parse_mime_body(self, boundary, data):
+ if data.endswith("\r\n"):
+ footer_length = len(boundary) + 6
+ else:
+ footer_length = len(boundary) + 4
+ parts = data[:-footer_length].split("--" + boundary + "\r\n")
+ for part in parts:
+ if not part: continue
+ eoh = part.find("\r\n\r\n")
+ if eoh == -1:
+ _log.warning("multipart/form-data missing headers")
+ continue
+ headers = HTTPHeaders.parse(part[:eoh])
+ name_header = headers.get("Content-Disposition", "")
+ if not name_header.startswith("form-data;") or \
+ not part.endswith("\r\n"):
+ _log.warning("Invalid multipart/form-data")
+ continue
+ value = part[eoh + 4:-2]
+ name_values = {}
+ for name_part in name_header[10:].split(";"):
+ name, name_value = name_part.strip().split("=", 1)
+ name_values[name] = name_value.strip('"').decode("utf-8")
+ if not name_values.get("name"):
+ _log.warning("multipart/form-data value missing name")
+ continue
+ name = name_values["name"]
+ if name_values.get("filename"):
+ ctype = headers.get("Content-Type", "application/unknown")
+ self._request.files.setdefault(name, []).append(dict(
+ filename=name_values["filename"], body=value,
+ content_type=ctype))
+ else:
+ self._request.arguments.setdefault(name, []).append(value)
+
+
+class HTTPRequest(object):
+ """A single HTTP request.
+
+ GET/POST arguments are available in the arguments property, which
+ maps arguments names to lists of values (to support multiple values
+ for individual names). Names and values are both unicode always.
+
+ File uploads are available in the files property, which maps file
+ names to list of files. Each file is a dictionary of the form
+ {"filename":..., "content_type":..., "body":...}. The content_type
+ comes from the provided HTTP header and should not be trusted
+ outright given that it can be easily forged.
+
+ An HTTP request is attached to a single HTTP connection, which can
+ be accessed through the "connection" attribute. Since connections
+ are typically kept open in HTTP/1.1, multiple requests can be handled
+ sequentially on a single connection.
+ """
+ def __init__(self, method, uri, version="HTTP/1.0", headers=None,
+ body=None, remote_ip=None, protocol=None, host=None,
+ files=None, connection=None):
+ self.method = method
+ self.uri = uri
+ self.version = version
+ self.headers = headers or HTTPHeaders()
+ self.body = body or ""
+ if connection and connection.xheaders:
+ # Squid uses X-Forwarded-For, others use X-Real-Ip
+ self.remote_ip = self.headers.get(
+ "X-Real-Ip", self.headers.get("X-Forwarded-For", remote_ip))
+ self.protocol = self.headers.get("X-Scheme", protocol) or "http"
+ else:
+ self.remote_ip = remote_ip
+ self.protocol = protocol or "http"
+ self.host = host or self.headers.get("Host") or "127.0.0.1"
+ self.files = files or {}
+ self.connection = connection
+ self._start_time = time.time()
+ self._finish_time = None
+
+ scheme, netloc, path, query, fragment = urlparse.urlsplit(uri)
+ self.path = path
+ self.query = query
+ arguments = cgi.parse_qs(query)
+ self.arguments = {}
+ for name, values in arguments.iteritems():
+ values = [v for v in values if v]
+ if values: self.arguments[name] = values
+
+ def supports_http_1_1(self):
+ """Returns True if this request supports HTTP/1.1 semantics"""
+ return self.version == "HTTP/1.1"
+
+ def write(self, chunk):
+ """Writes the given chunk to the response stream."""
+ assert isinstance(chunk, str)
+ self.connection.write(chunk)
+
+ def finish(self):
+ """Finishes this HTTP request on the open connection."""
+ self.connection.finish()
+ self._finish_time = time.time()
+
+ def full_url(self):
+ """Reconstructs the full URL for this request."""
+ return self.protocol + "://" + self.host + self.uri
+
+ def request_time(self):
+ """Returns the amount of time it took for this request to execute."""
+ if self._finish_time is None:
+ return time.time() - self._start_time
+ else:
+ return self._finish_time - self._start_time
+
+ def __repr__(self):
+ attrs = ("protocol", "host", "method", "uri", "version", "remote_ip",
+ "remote_ip", "body")
+ args = ", ".join(["%s=%r" % (n, getattr(self, n)) for n in attrs])
+ return "%s(%s, headers=%s)" % (
+ self.__class__.__name__, args, dict(self.headers))
+
+
+class HTTPHeaders(dict):
+ """A dictionary that maintains Http-Header-Case for all keys."""
+ def __setitem__(self, name, value):
+ dict.__setitem__(self, self._normalize_name(name), value)
+
+ def __getitem__(self, name):
+ return dict.__getitem__(self, self._normalize_name(name))
+
+ def _normalize_name(self, name):
+ return "-".join([w.capitalize() for w in name.split("-")])
+
+ @classmethod
+ def parse(cls, headers_string):
+ headers = cls()
+ for line in headers_string.splitlines():
+ if line:
+ name, value = line.split(": ", 1)
+ headers[name] = value
+ return headers
diff --git a/vendor/tornado/tornado/ioloop.py b/vendor/tornado/tornado/ioloop.py
new file mode 100644
index 0000000000..e94c17372e
--- /dev/null
+++ b/vendor/tornado/tornado/ioloop.py
@@ -0,0 +1,483 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""A level-triggered I/O loop for non-blocking sockets."""
+
+import bisect
+import errno
+import os
+import logging
+import select
+import time
+
+try:
+ import fcntl
+except ImportError:
+ if os.name == 'nt':
+ import win32_support
+ import win32_support as fcntl
+ else:
+ raise
+
+_log = logging.getLogger("tornado.ioloop")
+
+class IOLoop(object):
+ """A level-triggered I/O loop.
+
+ We use epoll if it is available, or else we fall back on select(). If
+ you are implementing a system that needs to handle 1000s of simultaneous
+ connections, you should use Linux and either compile our epoll module or
+ use Python 2.6+ to get epoll support.
+
+ Example usage for a simple TCP server:
+
+ import errno
+ import functools
+ import ioloop
+ import socket
+
+ def connection_ready(sock, fd, events):
+ while True:
+ try:
+ connection, address = sock.accept()
+ except socket.error, e:
+ if e[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):
+ raise
+ return
+ connection.setblocking(0)
+ handle_connection(connection, address)
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.setblocking(0)
+ sock.bind(("", port))
+ sock.listen(128)
+
+ io_loop = ioloop.IOLoop.instance()
+ callback = functools.partial(connection_ready, sock)
+ io_loop.add_handler(sock.fileno(), callback, io_loop.READ)
+ io_loop.start()
+
+ """
+ # Constants from the epoll module
+ _EPOLLIN = 0x001
+ _EPOLLPRI = 0x002
+ _EPOLLOUT = 0x004
+ _EPOLLERR = 0x008
+ _EPOLLHUP = 0x010
+ _EPOLLRDHUP = 0x2000
+ _EPOLLONESHOT = (1 << 30)
+ _EPOLLET = (1 << 31)
+
+ # Our events map exactly to the epoll events
+ NONE = 0
+ READ = _EPOLLIN
+ WRITE = _EPOLLOUT
+ ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP
+
+ def __init__(self, impl=None):
+ self._impl = impl or _poll()
+ if hasattr(self._impl, 'fileno'):
+ self._set_close_exec(self._impl.fileno())
+ self._handlers = {}
+ self._events = {}
+ self._callbacks = set()
+ self._timeouts = []
+ self._running = False
+ self._stopped = False
+
+ # Create a pipe that we send bogus data to when we want to wake
+ # the I/O loop when it is idle
+ if os.name != 'nt':
+ r, w = os.pipe()
+ self._set_nonblocking(r)
+ self._set_nonblocking(w)
+ self._set_close_exec(r)
+ self._set_close_exec(w)
+ self._waker_reader = os.fdopen(r, "r", 0)
+ self._waker_writer = os.fdopen(w, "w", 0)
+ else:
+ self._waker_reader = self._waker_writer = win32_support.Pipe()
+ r = self._waker_writer.reader_fd
+ self.add_handler(r, self._read_waker, self.READ)
+
+ @classmethod
+ def instance(cls):
+ """Returns a global IOLoop instance.
+
+ Most single-threaded applications have a single, global IOLoop.
+ Use this method instead of passing around IOLoop instances
+ throughout your code.
+
+ A common pattern for classes that depend on IOLoops is to use
+ a default argument to enable programs with multiple IOLoops
+ but not require the argument for simpler applications:
+
+ class MyClass(object):
+ def __init__(self, io_loop=None):
+ self.io_loop = io_loop or IOLoop.instance()
+ """
+ if not hasattr(cls, "_instance"):
+ cls._instance = cls()
+ return cls._instance
+
+ @classmethod
+ def initialized(cls):
+ return hasattr(cls, "_instance")
+
+ def add_handler(self, fd, handler, events):
+ """Registers the given handler to receive the given events for fd."""
+ self._handlers[fd] = handler
+ self._impl.register(fd, events | self.ERROR)
+
+ def update_handler(self, fd, events):
+ """Changes the events we listen for fd."""
+ self._impl.modify(fd, events | self.ERROR)
+
+ def remove_handler(self, fd):
+ """Stop listening for events on fd."""
+ self._handlers.pop(fd, None)
+ self._events.pop(fd, None)
+ try:
+ self._impl.unregister(fd)
+ except (OSError, IOError):
+ _log.debug("Error deleting fd from IOLoop", exc_info=True)
+
+ def start(self):
+ """Starts the I/O loop.
+
+ The loop will run until one of the I/O handlers calls stop(), which
+ will make the loop stop after the current event iteration completes.
+ """
+ if self._stopped:
+ self._stopped = False
+ return
+ self._running = True
+ while True:
+ # Never use an infinite timeout here - it can stall epoll
+ poll_timeout = 0.2
+
+ # Prevent IO event starvation by delaying new callbacks
+ # to the next iteration of the event loop.
+ callbacks = list(self._callbacks)
+ for callback in callbacks:
+ # A callback can add or remove other callbacks
+ if callback in self._callbacks:
+ self._callbacks.remove(callback)
+ self._run_callback(callback)
+
+ if self._callbacks:
+ poll_timeout = 0.0
+
+ if self._timeouts:
+ now = time.time()
+ while self._timeouts and self._timeouts[0].deadline <= now:
+ timeout = self._timeouts.pop(0)
+ self._run_callback(timeout.callback)
+ if self._timeouts:
+ milliseconds = self._timeouts[0].deadline - now
+ poll_timeout = min(milliseconds, poll_timeout)
+
+ if not self._running:
+ break
+
+ try:
+ event_pairs = self._impl.poll(poll_timeout)
+ except Exception, e:
+ if hasattr(e, 'errno') and e.errno == errno.EINTR:
+ _log.warning("Interrupted system call", exc_info=1)
+ continue
+ else:
+ raise
+
+ # Pop one fd at a time from the set of pending fds and run
+ # its handler. Since that handler may perform actions on
+ # other file descriptors, there may be reentrant calls to
+ # this IOLoop that update self._events
+ self._events.update(event_pairs)
+ while self._events:
+ fd, events = self._events.popitem()
+ try:
+ self._handlers[fd](fd, events)
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except (OSError, IOError), e:
+ if e[0] == errno.EPIPE:
+ # Happens when the client closes the connection
+ pass
+ else:
+ _log.error("Exception in I/O handler for fd %d",
+ fd, exc_info=True)
+ except:
+ _log.error("Exception in I/O handler for fd %d",
+ fd, exc_info=True)
+ # reset the stopped flag so another start/stop pair can be issued
+ self._stopped = False
+
+ def stop(self):
+ """Stop the loop after the current event loop iteration is complete.
+ If the event loop is not currently running, the next call to start()
+ will return immediately.
+
+ To use asynchronous methods from otherwise-synchronous code (such as
+ unit tests), you can start and stop the event loop like this:
+ ioloop = IOLoop()
+ async_method(ioloop=ioloop, callback=ioloop.stop)
+ ioloop.start()
+ ioloop.start() will return after async_method has run its callback,
+ whether that callback was invoked before or after ioloop.start.
+ """
+ self._running = False
+ self._stopped = True
+ self._wake()
+
+ def running(self):
+ """Returns true if this IOLoop is currently running."""
+ return self._running
+
+ def add_timeout(self, deadline, callback):
+ """Calls the given callback at the time deadline from the I/O loop."""
+ timeout = _Timeout(deadline, callback)
+ bisect.insort(self._timeouts, timeout)
+ return timeout
+
+ def remove_timeout(self, timeout):
+ self._timeouts.remove(timeout)
+
+ def add_callback(self, callback):
+ """Calls the given callback on the next I/O loop iteration."""
+ self._callbacks.add(callback)
+ self._wake()
+
+ def remove_callback(self, callback):
+ """Removes the given callback from the next I/O loop iteration."""
+ self._callbacks.remove(callback)
+
+ def _wake(self):
+ try:
+ self._waker_writer.write("x")
+ except IOError:
+ pass
+
+ def _run_callback(self, callback):
+ try:
+ callback()
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ self.handle_callback_exception(callback)
+
+ def handle_callback_exception(self, callback):
+ """This method is called whenever a callback run by the IOLoop
+ throws an exception.
+
+ By default simply logs the exception as an error. Subclasses
+ may override this method to customize reporting of exceptions.
+
+ The exception itself is not passed explicitly, but is available
+ in sys.exc_info.
+ """
+ _log.error("Exception in callback %r", callback, exc_info=True)
+
+ def _read_waker(self, fd, events):
+ try:
+ while True:
+ self._waker_reader.read()
+ except IOError:
+ pass
+
+ def _set_nonblocking(self, fd):
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+ def _set_close_exec(self, fd):
+ flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+ fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
+
+
+class _Timeout(object):
+ """An IOLoop timeout, a UNIX timestamp and a callback"""
+
+ # Reduce memory overhead when there are lots of pending callbacks
+ __slots__ = ['deadline', 'callback']
+
+ def __init__(self, deadline, callback):
+ self.deadline = deadline
+ self.callback = callback
+
+ def __cmp__(self, other):
+ return cmp((self.deadline, id(self.callback)),
+ (other.deadline, id(other.callback)))
+
+
+class PeriodicCallback(object):
+ """Schedules the given callback to be called periodically.
+
+ The callback is called every callback_time milliseconds.
+ """
+ def __init__(self, callback, callback_time, io_loop=None):
+ self.callback = callback
+ self.callback_time = callback_time
+ self.io_loop = io_loop or IOLoop.instance()
+ self._running = True
+
+ def start(self):
+ timeout = time.time() + self.callback_time / 1000.0
+ self.io_loop.add_timeout(timeout, self._run)
+
+ def stop(self):
+ self._running = False
+
+ def _run(self):
+ if not self._running: return
+ try:
+ self.callback()
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ _log.error("Error in periodic callback", exc_info=True)
+ self.start()
+
+
+class _EPoll(object):
+ """An epoll-based event loop using our C module for Python 2.5 systems"""
+ _EPOLL_CTL_ADD = 1
+ _EPOLL_CTL_DEL = 2
+ _EPOLL_CTL_MOD = 3
+
+ def __init__(self):
+ self._epoll_fd = epoll.epoll_create()
+
+ def fileno(self):
+ return self._epoll_fd
+
+ def register(self, fd, events):
+ epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_ADD, fd, events)
+
+ def modify(self, fd, events):
+ epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_MOD, fd, events)
+
+ def unregister(self, fd):
+ epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_DEL, fd, 0)
+
+ def poll(self, timeout):
+ return epoll.epoll_wait(self._epoll_fd, int(timeout * 1000))
+
+
+class _KQueue(object):
+ """A kqueue-based event loop for BSD/Mac systems."""
+ def __init__(self):
+ self._kqueue = select.kqueue()
+ self._active = {}
+
+ def fileno(self):
+ return self._kqueue.fileno()
+
+ def register(self, fd, events):
+ self._control(fd, events, select.KQ_EV_ADD)
+ self._active[fd] = events
+
+ def modify(self, fd, events):
+ self.unregister(fd)
+ self.register(fd, events)
+
+ def unregister(self, fd):
+ events = self._active.pop(fd)
+ self._control(fd, events, select.KQ_EV_DELETE)
+
+ def _control(self, fd, events, flags):
+ kevents = []
+ if events & IOLoop.WRITE:
+ kevents.append(select.kevent(
+ fd, filter=select.KQ_FILTER_WRITE, flags=flags))
+ if events & IOLoop.READ or not kevents:
+ # Always read when there is not a write
+ kevents.append(select.kevent(
+ fd, filter=select.KQ_FILTER_READ, flags=flags))
+ # Even though control() takes a list, it seems to return EINVAL
+ # on Mac OS X (10.6) when there is more than one event in the list.
+ for kevent in kevents:
+ self._kqueue.control([kevent], 0)
+
+ def poll(self, timeout):
+ kevents = self._kqueue.control(None, 1000, timeout)
+ events = {}
+ for kevent in kevents:
+ fd = kevent.ident
+ flags = 0
+ if kevent.filter == select.KQ_FILTER_READ:
+ events[fd] = events.get(fd, 0) | IOLoop.READ
+ if kevent.filter == select.KQ_FILTER_WRITE:
+ events[fd] = events.get(fd, 0) | IOLoop.WRITE
+ if kevent.flags & select.KQ_EV_ERROR:
+ events[fd] = events.get(fd, 0) | IOLoop.ERROR
+ return events.items()
+
+
+class _Select(object):
+ """A simple, select()-based IOLoop implementation for non-Linux systems"""
+ def __init__(self):
+ self.read_fds = set()
+ self.write_fds = set()
+ self.error_fds = set()
+ self.fd_sets = (self.read_fds, self.write_fds, self.error_fds)
+
+ def register(self, fd, events):
+ if events & IOLoop.READ: self.read_fds.add(fd)
+ if events & IOLoop.WRITE: self.write_fds.add(fd)
+ if events & IOLoop.ERROR: self.error_fds.add(fd)
+
+ def modify(self, fd, events):
+ self.unregister(fd)
+ self.register(fd, events)
+
+ def unregister(self, fd):
+ self.read_fds.discard(fd)
+ self.write_fds.discard(fd)
+ self.error_fds.discard(fd)
+
+ def poll(self, timeout):
+ readable, writeable, errors = select.select(
+ self.read_fds, self.write_fds, self.error_fds, timeout)
+ events = {}
+ for fd in readable:
+ events[fd] = events.get(fd, 0) | IOLoop.READ
+ for fd in writeable:
+ events[fd] = events.get(fd, 0) | IOLoop.WRITE
+ for fd in errors:
+ events[fd] = events.get(fd, 0) | IOLoop.ERROR
+ return events.items()
+
+
+# Choose a poll implementation. Use epoll if it is available, fall back to
+# select() for non-Linux platforms
+if hasattr(select, "epoll"):
+ # Python 2.6+ on Linux
+ _poll = select.epoll
+elif hasattr(select, "kqueue"):
+ # Python 2.6+ on BSD or Mac
+ _poll = _KQueue
+else:
+ try:
+ # Linux systems with our C module installed
+ import epoll
+ _poll = _EPoll
+ except:
+ # All other systems
+ import sys
+ if "linux" in sys.platform:
+ _log.warning("epoll module not found; using select()")
+ _poll = _Select
diff --git a/vendor/tornado/tornado/iostream.py b/vendor/tornado/tornado/iostream.py
new file mode 100644
index 0000000000..af7c6edbfe
--- /dev/null
+++ b/vendor/tornado/tornado/iostream.py
@@ -0,0 +1,229 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""A utility class to write to and read from a non-blocking socket."""
+
+import errno
+import ioloop
+import logging
+import socket
+
+_log = logging.getLogger('tornado.iostream')
+
+class IOStream(object):
+ """A utility class to write to and read from a non-blocking socket.
+
+ We support three methods: write(), read_until(), and read_bytes().
+ All of the methods take callbacks (since writing and reading are
+ non-blocking and asynchronous). read_until() reads the socket until
+ a given delimiter, and read_bytes() reads until a specified number
+ of bytes have been read from the socket.
+
+ A very simple (and broken) HTTP client using this class:
+
+ import ioloop
+ import iostream
+ import socket
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ s.connect(("friendfeed.com", 80))
+ stream = IOStream(s)
+
+ def on_headers(data):
+ headers = {}
+ for line in data.split("\r\n"):
+ parts = line.split(":")
+ if len(parts) == 2:
+ headers[parts[0].strip()] = parts[1].strip()
+ stream.read_bytes(int(headers["Content-Length"]), on_body)
+
+ def on_body(data):
+ print data
+ stream.close()
+ ioloop.IOLoop.instance().stop()
+
+ stream.write("GET / HTTP/1.0\r\n\r\n")
+ stream.read_until("\r\n\r\n", on_headers)
+ ioloop.IOLoop.instance().start()
+
+ """
+ def __init__(self, socket, io_loop=None, max_buffer_size=104857600,
+ read_chunk_size=4096):
+ self.socket = socket
+ self.socket.setblocking(False)
+ self.io_loop = io_loop or ioloop.IOLoop.instance()
+ self.max_buffer_size = max_buffer_size
+ self.read_chunk_size = read_chunk_size
+ self._read_buffer = ""
+ self._write_buffer = ""
+ self._read_delimiter = None
+ self._read_bytes = None
+ self._read_callback = None
+ self._write_callback = None
+ self._close_callback = None
+ self._state = self.io_loop.ERROR
+ self.io_loop.add_handler(
+ self.socket.fileno(), self._handle_events, self._state)
+
+ def read_until(self, delimiter, callback):
+ """Call callback when we read the given delimiter."""
+ assert not self._read_callback, "Already reading"
+ loc = self._read_buffer.find(delimiter)
+ if loc != -1:
+ callback(self._consume(loc + len(delimiter)))
+ return
+ self._check_closed()
+ self._read_delimiter = delimiter
+ self._read_callback = callback
+ self._add_io_state(self.io_loop.READ)
+
+ def read_bytes(self, num_bytes, callback):
+ """Call callback when we read the given number of bytes."""
+ assert not self._read_callback, "Already reading"
+ if len(self._read_buffer) >= num_bytes:
+ callback(self._consume(num_bytes))
+ return
+ self._check_closed()
+ self._read_bytes = num_bytes
+ self._read_callback = callback
+ self._add_io_state(self.io_loop.READ)
+
+ def write(self, data, callback=None):
+ """Write the given data to this stream.
+
+ If callback is given, we call it when all of the buffered write
+ data has been successfully written to the stream. If there was
+ previously buffered write data and an old write callback, that
+ callback is simply overwritten with this new callback.
+ """
+ self._check_closed()
+ self._write_buffer += data
+ self._add_io_state(self.io_loop.WRITE)
+ self._write_callback = callback
+
+ def set_close_callback(self, callback):
+ """Call the given callback when the stream is closed."""
+ self._close_callback = callback
+
+ def close(self):
+ """Close this stream."""
+ if self.socket is not None:
+ self.io_loop.remove_handler(self.socket.fileno())
+ self.socket.close()
+ self.socket = None
+ if self._close_callback: self._close_callback()
+
+ def reading(self):
+ """Returns true if we are currently reading from the stream."""
+ return self._read_callback is not None
+
+ def writing(self):
+ """Returns true if we are currently writing to the stream."""
+ return len(self._write_buffer) > 0
+
+ def closed(self):
+ return self.socket is None
+
+ def _handle_events(self, fd, events):
+ if not self.socket:
+ _log.warning("Got events for closed stream %d", fd)
+ return
+ if events & self.io_loop.READ:
+ self._handle_read()
+ if not self.socket:
+ return
+ if events & self.io_loop.WRITE:
+ self._handle_write()
+ if not self.socket:
+ return
+ if events & self.io_loop.ERROR:
+ self.close()
+ return
+ state = self.io_loop.ERROR
+ if self._read_delimiter or self._read_bytes:
+ state |= self.io_loop.READ
+ if self._write_buffer:
+ state |= self.io_loop.WRITE
+ if state != self._state:
+ self._state = state
+ self.io_loop.update_handler(self.socket.fileno(), self._state)
+
+ def _handle_read(self):
+ try:
+ chunk = self.socket.recv(self.read_chunk_size)
+ except socket.error, e:
+ if e[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
+ return
+ else:
+ _log.warning("Read error on %d: %s",
+ self.socket.fileno(), e)
+ self.close()
+ return
+ if not chunk:
+ self.close()
+ return
+ self._read_buffer += chunk
+ if len(self._read_buffer) >= self.max_buffer_size:
+ _log.error("Reached maximum read buffer size")
+ self.close()
+ return
+ if self._read_bytes:
+ if len(self._read_buffer) >= self._read_bytes:
+ num_bytes = self._read_bytes
+ callback = self._read_callback
+ self._read_callback = None
+ self._read_bytes = None
+ callback(self._consume(num_bytes))
+ elif self._read_delimiter:
+ loc = self._read_buffer.find(self._read_delimiter)
+ if loc != -1:
+ callback = self._read_callback
+ delimiter_len = len(self._read_delimiter)
+ self._read_callback = None
+ self._read_delimiter = None
+ callback(self._consume(loc + delimiter_len))
+
+ def _handle_write(self):
+ while self._write_buffer:
+ try:
+ num_bytes = self.socket.send(self._write_buffer)
+ self._write_buffer = self._write_buffer[num_bytes:]
+ except socket.error, e:
+ if e[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
+ break
+ else:
+ _log.warning("Write error on %d: %s",
+ self.socket.fileno(), e)
+ self.close()
+ return
+ if not self._write_buffer and self._write_callback:
+ callback = self._write_callback
+ self._write_callback = None
+ callback()
+
+ def _consume(self, loc):
+ result = self._read_buffer[:loc]
+ self._read_buffer = self._read_buffer[loc:]
+ return result
+
+ def _check_closed(self):
+ if not self.socket:
+ raise IOError("Stream is closed")
+
+ def _add_io_state(self, state):
+ if not self._state & state:
+ self._state = self._state | state
+ self.io_loop.update_handler(self.socket.fileno(), self._state)
diff --git a/vendor/tornado/tornado/locale.py b/vendor/tornado/tornado/locale.py
new file mode 100644
index 0000000000..6a8537d750
--- /dev/null
+++ b/vendor/tornado/tornado/locale.py
@@ -0,0 +1,457 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Translation methods for generating localized strings.
+
+To load a locale and generate a translated string:
+
+ user_locale = locale.get("es_LA")
+ print user_locale.translate("Sign out")
+
+locale.get() returns the closest matching locale, not necessarily the
+specific locale you requested. You can support pluralization with
+additional arguments to translate(), e.g.:
+
+ people = [...]
+ message = user_locale.translate(
+ "%(list)s is online", "%(list)s are online", len(people))
+ print message % {"list": user_locale.list(people)}
+
+The first string is chosen if len(people) == 1, otherwise the second
+string is chosen.
+
+Applications should call one of load_translations (which uses a simple
+CSV format) or load_gettext_translations (which uses the .mo format
+supported by gettext and related tools). If neither method is called,
+the locale.translate method will simply return the original string.
+"""
+
+import csv
+import datetime
+import logging
+import os
+import os.path
+import re
+
+_default_locale = "en_US"
+_translations = {}
+_supported_locales = frozenset([_default_locale])
+_use_gettext = False
+
+_log = logging.getLogger('tornado.locale')
+
+def get(*locale_codes):
+ """Returns the closest match for the given locale codes.
+
+ We iterate over all given locale codes in order. If we have a tight
+ or a loose match for the code (e.g., "en" for "en_US"), we return
+ the locale. Otherwise we move to the next code in the list.
+
+ By default we return en_US if no translations are found for any of
+ the specified locales. You can change the default locale with
+ set_default_locale() below.
+ """
+ return Locale.get_closest(*locale_codes)
+
+
+def set_default_locale(code):
+ """Sets the default locale, used in get_closest_locale().
+
+ The default locale is assumed to be the language used for all strings
+ in the system. The translations loaded from disk are mappings from
+ the default locale to the destination locale. Consequently, you don't
+ need to create a translation file for the default locale.
+ """
+ global _default_locale
+ global _supported_locales
+ _default_locale = code
+ _supported_locales = frozenset(_translations.keys() + [_default_locale])
+
+
+def load_translations(directory):
+ """Loads translations from CSV files in a directory.
+
+ Translations are strings with optional Python-style named placeholders
+ (e.g., "My name is %(name)s") and their associated translations.
+
+ The directory should have translation files of the form LOCALE.csv,
+ e.g. es_GT.csv. The CSV files should have two or three columns: string,
+ translation, and an optional plural indicator. Plural indicators should
+ be one of "plural" or "singular". A given string can have both singular
+ and plural forms. For example "%(name)s liked this" may have a
+ different verb conjugation depending on whether %(name)s is one
+ name or a list of names. There should be two rows in the CSV file for
+ that string, one with plural indicator "singular", and one "plural".
+ For strings with no verbs that would change on translation, simply
+ use "unknown" or the empty string (or don't include the column at all).
+
+ Example translation es_LA.csv:
+
+ "I love you","Te amo"
+ "%(name)s liked this","A %(name)s les gust\xf3 esto","plural"
+ "%(name)s liked this","A %(name)s le gust\xf3 esto","singular"
+
+ """
+ global _translations
+ global _supported_locales
+ _translations = {}
+ for path in os.listdir(directory):
+ if not path.endswith(".csv"): continue
+ locale, extension = path.split(".")
+ if locale not in LOCALE_NAMES:
+ _log.error("Unrecognized locale %r (path: %s)", locale,
+ os.path.join(directory, path))
+ continue
+ f = open(os.path.join(directory, path), "r")
+ _translations[locale] = {}
+ for i, row in enumerate(csv.reader(f)):
+ if not row or len(row) < 2: continue
+ row = [c.decode("utf-8").strip() for c in row]
+ english, translation = row[:2]
+ if len(row) > 2:
+ plural = row[2] or "unknown"
+ else:
+ plural = "unknown"
+ if plural not in ("plural", "singular", "unknown"):
+ _log.error("Unrecognized plural indicator %r in %s line %d",
+ plural, path, i + 1)
+ continue
+ _translations[locale].setdefault(plural, {})[english] = translation
+ f.close()
+ _supported_locales = frozenset(_translations.keys() + [_default_locale])
+ _log.info("Supported locales: %s", sorted(_supported_locales))
+
+def load_gettext_translations(directory, domain):
+ """Loads translations from gettext's locale tree
+
+ Locale tree is similar to system's /usr/share/locale, like:
+
+ {directory}/{lang}/LC_MESSAGES/{domain}.mo
+
+ Three steps are required to have you app translated:
+
+ 1. Generate POT translation file
+ xgettext --language=Python --keyword=_:1,2 -d cyclone file1.py file2.html etc
+
+ 2. Merge against existing POT file:
+ msgmerge old.po cyclone.po > new.po
+
+ 3. Compile:
+ msgfmt cyclone.po -o {directory}/pt_BR/LC_MESSAGES/cyclone.mo
+ """
+ import gettext
+ global _translations
+ global _supported_locales
+ global _use_gettext
+ _translations = {}
+ for lang in os.listdir(directory):
+ if os.path.isfile(os.path.join(directory, lang)): continue
+ try:
+ os.stat(os.path.join(directory, lang, "LC_MESSAGES", domain+".mo"))
+ _translations[lang] = gettext.translation(domain, directory,
+ languages=[lang])
+ except Exception, e:
+ logging.error("Cannot load translation for '%s': %s", lang, str(e))
+ continue
+ _supported_locales = frozenset(_translations.keys() + [_default_locale])
+ _use_gettext = True
+ _log.info("Supported locales: %s", sorted(_supported_locales))
+
+
+def get_supported_locales(cls):
+ """Returns a list of all the supported locale codes."""
+ return _supported_locales
+
+
+class Locale(object):
+ @classmethod
+ def get_closest(cls, *locale_codes):
+ """Returns the closest match for the given locale code."""
+ for code in locale_codes:
+ if not code: continue
+ code = code.replace("-", "_")
+ parts = code.split("_")
+ if len(parts) > 2:
+ continue
+ elif len(parts) == 2:
+ code = parts[0].lower() + "_" + parts[1].upper()
+ if code in _supported_locales:
+ return cls.get(code)
+ if parts[0].lower() in _supported_locales:
+ return cls.get(parts[0].lower())
+ return cls.get(_default_locale)
+
+ @classmethod
+ def get(cls, code):
+ """Returns the Locale for the given locale code.
+
+ If it is not supported, we raise an exception.
+ """
+ if not hasattr(cls, "_cache"):
+ cls._cache = {}
+ if code not in cls._cache:
+ assert code in _supported_locales
+ translations = _translations.get(code, None)
+ if translations is None:
+ locale = CSVLocale(code, {})
+ elif _use_gettext:
+ locale = GettextLocale(code, translations)
+ else:
+ locale = CSVLocale(code, translations)
+ cls._cache[code] = locale
+ return cls._cache[code]
+
+ def __init__(self, code, translations):
+ self.code = code
+ self.name = LOCALE_NAMES.get(code, {}).get("name", u"Unknown")
+ self.rtl = False
+ for prefix in ["fa", "ar", "he"]:
+ if self.code.startswith(prefix):
+ self.rtl = True
+ break
+ self.translations = translations
+
+ # Initialize strings for date formatting
+ _ = self.translate
+ self._months = [
+ _("January"), _("February"), _("March"), _("April"),
+ _("May"), _("June"), _("July"), _("August"),
+ _("September"), _("October"), _("November"), _("December")]
+ self._weekdays = [
+ _("Monday"), _("Tuesday"), _("Wednesday"), _("Thursday"),
+ _("Friday"), _("Saturday"), _("Sunday")]
+
+ def translate(self, message, plural_message=None, count=None):
+ raise NotImplementedError()
+
+ def format_date(self, date, gmt_offset=0, relative=True, shorter=False,
+ full_format=False):
+ """Formats the given date (which should be GMT).
+
+ By default, we return a relative time (e.g., "2 minutes ago"). You
+ can return an absolute date string with relative=False.
+
+ You can force a full format date ("July 10, 1980") with
+ full_format=True.
+ """
+ if self.code.startswith("ru"):
+ relative = False
+ if type(date) in (int, long, float):
+ date = datetime.datetime.utcfromtimestamp(date)
+ now = datetime.datetime.utcnow()
+ # Round down to now. Due to click skew, things are somethings
+ # slightly in the future.
+ if date > now: date = now
+ local_date = date - datetime.timedelta(minutes=gmt_offset)
+ local_now = now - datetime.timedelta(minutes=gmt_offset)
+ local_yesterday = local_now - datetime.timedelta(hours=24)
+ difference = now - date
+ seconds = difference.seconds
+ days = difference.days
+
+ _ = self.translate
+ format = None
+ if not full_format:
+ if relative and days == 0:
+ if seconds < 50:
+ return _("1 second ago", "%(seconds)d seconds ago",
+ seconds) % { "seconds": seconds }
+
+ if seconds < 50 * 60:
+ minutes = round(seconds / 60.0)
+ return _("1 minute ago", "%(minutes)d minutes ago",
+ minutes) % { "minutes": minutes }
+
+ hours = round(seconds / (60.0 * 60))
+ return _("1 hour ago", "%(hours)d hours ago",
+ hours) % { "hours": hours }
+
+ if days == 0:
+ format = _("%(time)s")
+ elif days == 1 and local_date.day == local_yesterday.day and \
+ relative:
+ format = _("yesterday") if shorter else \
+ _("yesterday at %(time)s")
+ elif days < 5:
+ format = _("%(weekday)s") if shorter else \
+ _("%(weekday)s at %(time)s")
+ elif days < 334: # 11mo, since confusing for same month last year
+ format = _("%(month_name)s %(day)s") if shorter else \
+ _("%(month_name)s %(day)s at %(time)s")
+
+ if format is None:
+ format = _("%(month_name)s %(day)s, %(year)s") if shorter else \
+ _("%(month_name)s %(day)s, %(year)s at %(time)s")
+
+ tfhour_clock = self.code not in ("en", "en_US", "zh_CN")
+ if tfhour_clock:
+ str_time = "%d:%02d" % (local_date.hour, local_date.minute)
+ elif self.code == "zh_CN":
+ str_time = "%s%d:%02d" % (
+ (u'\u4e0a\u5348', u'\u4e0b\u5348')[local_date.hour >= 12],
+ local_date.hour % 12 or 12, local_date.minute)
+ else:
+ str_time = "%d:%02d %s" % (
+ local_date.hour % 12 or 12, local_date.minute,
+ ("am", "pm")[local_date.hour >= 12])
+
+ return format % {
+ "month_name": self._months[local_date.month - 1],
+ "weekday": self._weekdays[local_date.weekday()],
+ "day": str(local_date.day),
+ "year": str(local_date.year),
+ "time": str_time
+ }
+
+ def format_day(self, date, gmt_offset=0, dow=True):
+ """Formats the given date as a day of week.
+
+ Example: "Monday, January 22". You can remove the day of week with
+ dow=False.
+ """
+ local_date = date - datetime.timedelta(minutes=gmt_offset)
+ _ = self.translate
+ if dow:
+ return _("%(weekday)s, %(month_name)s %(day)s") % {
+ "month_name": self._months[local_date.month - 1],
+ "weekday": self._weekdays[local_date.weekday()],
+ "day": str(local_date.day),
+ }
+ else:
+ return _("%(month_name)s %(day)s") % {
+ "month_name": self._months[local_date.month - 1],
+ "day": str(local_date.day),
+ }
+
+ def list(self, parts):
+ """Returns a comma-separated list for the given list of parts.
+
+ The format is, e.g., "A, B and C", "A and B" or just "A" for lists
+ of size 1.
+ """
+ _ = self.translate
+ if len(parts) == 0: return ""
+ if len(parts) == 1: return parts[0]
+ comma = u' \u0648 ' if self.code.startswith("fa") else u", "
+ return _("%(commas)s and %(last)s") % {
+ "commas": comma.join(parts[:-1]),
+ "last": parts[len(parts) - 1],
+ }
+
+ def friendly_number(self, value):
+ """Returns a comma-separated number for the given integer."""
+ if self.code not in ("en", "en_US"):
+ return str(value)
+ value = str(value)
+ parts = []
+ while value:
+ parts.append(value[-3:])
+ value = value[:-3]
+ return ",".join(reversed(parts))
+
+class CSVLocale(Locale):
+ """Locale implementation using tornado's CSV translation format."""
+ def translate(self, message, plural_message=None, count=None):
+ """Returns the translation for the given message for this locale.
+
+ If plural_message is given, you must also provide count. We return
+ plural_message when count != 1, and we return the singular form
+ for the given message when count == 1.
+ """
+ if plural_message is not None:
+ assert count is not None
+ if count != 1:
+ message = plural_message
+ message_dict = self.translations.get("plural", {})
+ else:
+ message_dict = self.translations.get("singular", {})
+ else:
+ message_dict = self.translations.get("unknown", {})
+ return message_dict.get(message, message)
+
+class GettextLocale(Locale):
+ """Locale implementation using the gettext module."""
+ def translate(self, message, plural_message=None, count=None):
+ if plural_message is not None:
+ assert count is not None
+ return self.translations.ungettext(message, plural_message, count)
+ else:
+ return self.translations.ugettext(message)
+
+LOCALE_NAMES = {
+ "af_ZA": {"name_en": u"Afrikaans", "name": u"Afrikaans"},
+ "ar_AR": {"name_en": u"Arabic", "name": u"\u0627\u0644\u0639\u0631\u0628\u064a\u0629"},
+ "bg_BG": {"name_en": u"Bulgarian", "name": u"\u0411\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438"},
+ "bn_IN": {"name_en": u"Bengali", "name": u"\u09ac\u09be\u0982\u09b2\u09be"},
+ "bs_BA": {"name_en": u"Bosnian", "name": u"Bosanski"},
+ "ca_ES": {"name_en": u"Catalan", "name": u"Catal\xe0"},
+ "cs_CZ": {"name_en": u"Czech", "name": u"\u010ce\u0161tina"},
+ "cy_GB": {"name_en": u"Welsh", "name": u"Cymraeg"},
+ "da_DK": {"name_en": u"Danish", "name": u"Dansk"},
+ "de_DE": {"name_en": u"German", "name": u"Deutsch"},
+ "el_GR": {"name_en": u"Greek", "name": u"\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac"},
+ "en_GB": {"name_en": u"English (UK)", "name": u"English (UK)"},
+ "en_US": {"name_en": u"English (US)", "name": u"English (US)"},
+ "es_ES": {"name_en": u"Spanish (Spain)", "name": u"Espa\xf1ol (Espa\xf1a)"},
+ "es_LA": {"name_en": u"Spanish", "name": u"Espa\xf1ol"},
+ "et_EE": {"name_en": u"Estonian", "name": u"Eesti"},
+ "eu_ES": {"name_en": u"Basque", "name": u"Euskara"},
+ "fa_IR": {"name_en": u"Persian", "name": u"\u0641\u0627\u0631\u0633\u06cc"},
+ "fi_FI": {"name_en": u"Finnish", "name": u"Suomi"},
+ "fr_CA": {"name_en": u"French (Canada)", "name": u"Fran\xe7ais (Canada)"},
+ "fr_FR": {"name_en": u"French", "name": u"Fran\xe7ais"},
+ "ga_IE": {"name_en": u"Irish", "name": u"Gaeilge"},
+ "gl_ES": {"name_en": u"Galician", "name": u"Galego"},
+ "he_IL": {"name_en": u"Hebrew", "name": u"\u05e2\u05d1\u05e8\u05d9\u05ea"},
+ "hi_IN": {"name_en": u"Hindi", "name": u"\u0939\u093f\u0928\u094d\u0926\u0940"},
+ "hr_HR": {"name_en": u"Croatian", "name": u"Hrvatski"},
+ "hu_HU": {"name_en": u"Hungarian", "name": u"Magyar"},
+ "id_ID": {"name_en": u"Indonesian", "name": u"Bahasa Indonesia"},
+ "is_IS": {"name_en": u"Icelandic", "name": u"\xcdslenska"},
+ "it_IT": {"name_en": u"Italian", "name": u"Italiano"},
+ "ja_JP": {"name_en": u"Japanese", "name": u"\xe6\xe6\xe8"},
+ "ko_KR": {"name_en": u"Korean", "name": u"\xed\xea\xec"},
+ "lt_LT": {"name_en": u"Lithuanian", "name": u"Lietuvi\u0173"},
+ "lv_LV": {"name_en": u"Latvian", "name": u"Latvie\u0161u"},
+ "mk_MK": {"name_en": u"Macedonian", "name": u"\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438"},
+ "ml_IN": {"name_en": u"Malayalam", "name": u"\u0d2e\u0d32\u0d2f\u0d3e\u0d33\u0d02"},
+ "ms_MY": {"name_en": u"Malay", "name": u"Bahasa Melayu"},
+ "nb_NO": {"name_en": u"Norwegian (bokmal)", "name": u"Norsk (bokm\xe5l)"},
+ "nl_NL": {"name_en": u"Dutch", "name": u"Nederlands"},
+ "nn_NO": {"name_en": u"Norwegian (nynorsk)", "name": u"Norsk (nynorsk)"},
+ "pa_IN": {"name_en": u"Punjabi", "name": u"\u0a2a\u0a70\u0a1c\u0a3e\u0a2c\u0a40"},
+ "pl_PL": {"name_en": u"Polish", "name": u"Polski"},
+ "pt_BR": {"name_en": u"Portuguese (Brazil)", "name": u"Portugu\xeas (Brasil)"},
+ "pt_PT": {"name_en": u"Portuguese (Portugal)", "name": u"Portugu\xeas (Portugal)"},
+ "ro_RO": {"name_en": u"Romanian", "name": u"Rom\xe2n\u0103"},
+ "ru_RU": {"name_en": u"Russian", "name": u"\u0420\u0443\u0441\u0441\u043a\u0438\u0439"},
+ "sk_SK": {"name_en": u"Slovak", "name": u"Sloven\u010dina"},
+ "sl_SI": {"name_en": u"Slovenian", "name": u"Sloven\u0161\u010dina"},
+ "sq_AL": {"name_en": u"Albanian", "name": u"Shqip"},
+ "sr_RS": {"name_en": u"Serbian", "name": u"\u0421\u0440\u043f\u0441\u043a\u0438"},
+ "sv_SE": {"name_en": u"Swedish", "name": u"Svenska"},
+ "sw_KE": {"name_en": u"Swahili", "name": u"Kiswahili"},
+ "ta_IN": {"name_en": u"Tamil", "name": u"\u0ba4\u0bae\u0bbf\u0bb4\u0bcd"},
+ "te_IN": {"name_en": u"Telugu", "name": u"\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41"},
+ "th_TH": {"name_en": u"Thai", "name": u"\u0e20\u0e32\u0e29\u0e32\u0e44\u0e17\u0e22"},
+ "tl_PH": {"name_en": u"Filipino", "name": u"Filipino"},
+ "tr_TR": {"name_en": u"Turkish", "name": u"T\xfcrk\xe7e"},
+ "uk_UA": {"name_en": u"Ukraini ", "name": u"\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430"},
+ "vi_VN": {"name_en": u"Vietnamese", "name": u"Ti\u1ebfng Vi\u1ec7t"},
+ "zh_CN": {"name_en": u"Chinese (Simplified)", "name": u"\xe4\xe6(\xe7\xe4)"},
+ "zh_HK": {"name_en": u"Chinese (Hong Kong)", "name": u"\xe4\xe6(\xe9\xe6)"},
+ "zh_TW": {"name_en": u"Chinese (Taiwan)", "name": u"\xe4\xe6(\xe5\xe7)"},
+}
diff --git a/vendor/tornado/tornado/options.py b/vendor/tornado/tornado/options.py
new file mode 100644
index 0000000000..66bce091e7
--- /dev/null
+++ b/vendor/tornado/tornado/options.py
@@ -0,0 +1,386 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""A command line parsing module that lets modules define their own options.
+
+Each module defines its own options, e.g.,
+
+ from tornado.options import define, options
+
+ define("mysql_host", default="127.0.0.1:3306", help="Main user DB")
+ define("memcache_hosts", default="127.0.0.1:11011", multiple=True,
+ help="Main user memcache servers")
+
+ def connect():
+ db = database.Connection(options.mysql_host)
+ ...
+
+The main() method of your application does not need to be aware of all of
+the options used throughout your program; they are all automatically loaded
+when the modules are loaded. Your main() method can parse the command line
+or parse a config file with:
+
+ import tornado.options
+ tornado.options.parse_config_file("/etc/server.conf")
+ tornado.options.parse_command_line()
+
+Command line formats are what you would expect ("--myoption=myvalue").
+Config files are just Python files. Global names become options, e.g.,
+
+ myoption = "myvalue"
+ myotheroption = "myothervalue"
+
+We support datetimes, timedeltas, ints, and floats (just pass a 'type'
+kwarg to define). We also accept multi-value options. See the documentation
+for define() below.
+"""
+
+import datetime
+import logging
+import logging.handlers
+import re
+import sys
+import time
+
+# For pretty log messages, if available
+try:
+ import curses
+except:
+ curses = None
+
+
+def define(name, default=None, type=str, help=None, metavar=None,
+ multiple=False):
+ """Defines a new command line option.
+
+ If type is given (one of str, float, int, datetime, or timedelta),
+ we parse the command line arguments based on the given type. If
+ multiple is True, we accept comma-separated values, and the option
+ value is always a list.
+
+ For multi-value integers, we also accept the syntax x:y, which
+ turns into range(x, y) - very useful for long integer ranges.
+
+ help and metavar are used to construct the automatically generated
+ command line help string. The help message is formatted like:
+
+ --name=METAVAR help string
+
+ Command line option names must be unique globally. They can be parsed
+ from the command line with parse_command_line() or parsed from a
+ config file with parse_config_file.
+ """
+ if name in options:
+ raise Error("Option %r already defined in %s", name,
+ options[name].file_name)
+ frame = sys._getframe(0)
+ options_file = frame.f_code.co_filename
+ file_name = frame.f_back.f_code.co_filename
+ if file_name == options_file: file_name = ""
+ options[name] = _Option(name, file_name=file_name, default=default,
+ type=type, help=help, metavar=metavar,
+ multiple=multiple)
+
+
+def parse_command_line(args=None):
+ """Parses all options given on the command line.
+
+ We return all command line arguments that are not options as a list.
+ """
+ if args is None: args = sys.argv
+ remaining = []
+ for i in xrange(1, len(args)):
+ # All things after the last option are command line arguments
+ if not args[i].startswith("-"):
+ remaining = args[i:]
+ break
+ if args[i] == "--":
+ remaining = args[i+1:]
+ break
+ arg = args[i].lstrip("-")
+ name, equals, value = arg.partition("=")
+ name = name.replace('-', '_')
+ if not name in options:
+ print_help()
+ raise Error('Unrecognized command line option: %r' % name)
+ option = options[name]
+ if not equals:
+ if option.type == bool:
+ value = "true"
+ else:
+ raise Error('Option %r requires a value' % name)
+ option.parse(value)
+ if options.help:
+ print_help()
+ sys.exit(0)
+
+ # Set up log level and pretty console logging by default
+ if options.logging != 'none':
+ logging.getLogger().setLevel(getattr(logging, options.logging.upper()))
+ enable_pretty_logging()
+
+ return remaining
+
+
+def parse_config_file(path, overwrite=True):
+ """Parses and loads the Python config file at the given path."""
+ config = {}
+ execfile(path, config, config)
+ for name in config:
+ if name in options:
+ options[name].set(config[name])
+
+
+def print_help(file=sys.stdout):
+ """Prints all the command line options to stdout."""
+ print >> file, "Usage: %s [OPTIONS]" % sys.argv[0]
+ print >> file, ""
+ print >> file, "Options:"
+ by_file = {}
+ for option in options.itervalues():
+ by_file.setdefault(option.file_name, []).append(option)
+
+ for filename, o in sorted(by_file.items()):
+ if filename: print >> file, filename
+ o.sort(key=lambda option: option.name)
+ for option in o:
+ prefix = option.name
+ if option.metavar:
+ prefix += "=" + option.metavar
+ print >> file, " --%-30s %s" % (prefix, option.help or "")
+ print >> file
+
+
+class _Options(dict):
+ """Our global program options, an dictionary with object-like access."""
+ @classmethod
+ def instance(cls):
+ if not hasattr(cls, "_instance"):
+ cls._instance = cls()
+ return cls._instance
+
+ def __getattr__(self, name):
+ if isinstance(self.get(name), _Option):
+ return self[name].value()
+ raise AttributeError("Unrecognized option %r" % name)
+
+
+class _Option(object):
+ def __init__(self, name, default=None, type=str, help=None, metavar=None,
+ multiple=False, file_name=None):
+ if default is None and multiple:
+ default = []
+ self.name = name
+ self.type = type
+ self.help = help
+ self.metavar = metavar
+ self.multiple = multiple
+ self.file_name = file_name
+ self.default = default
+ self._value = None
+
+ def value(self):
+ return self.default if self._value is None else self._value
+
+ def parse(self, value):
+ _parse = {
+ datetime.datetime: self._parse_datetime,
+ datetime.timedelta: self._parse_timedelta,
+ bool: self._parse_bool,
+ str: self._parse_string,
+ }.get(self.type, self.type)
+ if self.multiple:
+ if self._value is None:
+ self._value = []
+ for part in value.split(","):
+ if self.type in (int, long):
+ # allow ranges of the form X:Y (inclusive at both ends)
+ lo, _, hi = part.partition(":")
+ lo = _parse(lo)
+ hi = _parse(hi) if hi else lo
+ self._value.extend(range(lo, hi+1))
+ else:
+ self._value.append(_parse(part))
+ else:
+ self._value = _parse(value)
+ return self.value()
+
+ def set(self, value):
+ if self.multiple:
+ if not isinstance(value, list):
+ raise Error("Option %r is required to be a list of %s" %
+ (self.name, self.type.__name__))
+ for item in value:
+ if item != None and not isinstance(item, self.type):
+ raise Error("Option %r is required to be a list of %s" %
+ (self.name, self.type.__name__))
+ else:
+ if value != None and not isinstance(value, self.type):
+ raise Error("Option %r is required to be a %s" %
+ (self.name, self.type.__name__))
+ self._value = value
+
+ # Supported date/time formats in our options
+ _DATETIME_FORMATS = [
+ "%a %b %d %H:%M:%S %Y",
+ "%Y-%m-%d %H:%M:%S",
+ "%Y-%m-%d %H:%M",
+ "%Y-%m-%dT%H:%M",
+ "%Y%m%d %H:%M:%S",
+ "%Y%m%d %H:%M",
+ "%Y-%m-%d",
+ "%Y%m%d",
+ "%H:%M:%S",
+ "%H:%M",
+ ]
+
+ def _parse_datetime(self, value):
+ for format in self._DATETIME_FORMATS:
+ try:
+ return datetime.datetime.strptime(value, format)
+ except ValueError:
+ pass
+ raise Error('Unrecognized date/time format: %r' % value)
+
+ _TIMEDELTA_ABBREVS = [
+ ('hours', ['h']),
+ ('minutes', ['m', 'min']),
+ ('seconds', ['s', 'sec']),
+ ('milliseconds', ['ms']),
+ ('microseconds', ['us']),
+ ('days', ['d']),
+ ('weeks', ['w']),
+ ]
+
+ _TIMEDELTA_ABBREV_DICT = dict(
+ (abbrev, full) for full, abbrevs in _TIMEDELTA_ABBREVS
+ for abbrev in abbrevs)
+
+ _FLOAT_PATTERN = r'[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?'
+
+ _TIMEDELTA_PATTERN = re.compile(
+ r'\s*(%s)\s*(\w*)\s*' % _FLOAT_PATTERN, re.IGNORECASE)
+
+ def _parse_timedelta(self, value):
+ try:
+ sum = datetime.timedelta()
+ start = 0
+ while start < len(value):
+ m = self._TIMEDELTA_PATTERN.match(value, start)
+ if not m:
+ raise Exception()
+ num = float(m.group(1))
+ units = m.group(2) or 'seconds'
+ units = self._TIMEDELTA_ABBREV_DICT.get(units, units)
+ sum += datetime.timedelta(**{units: num})
+ start = m.end()
+ return sum
+ except:
+ raise
+
+ def _parse_bool(self, value):
+ return value.lower() not in ("false", "0", "f")
+
+ def _parse_string(self, value):
+ return value.decode("utf-8")
+
+
+class Error(Exception):
+ pass
+
+
+def enable_pretty_logging():
+ """Turns on formatted logging output as configured."""
+ if (options.log_to_stderr or
+ (options.log_to_stderr is None and not options.log_file_prefix)):
+ # Set up color if we are in a tty and curses is installed
+ color = False
+ if curses and sys.stderr.isatty():
+ try:
+ curses.setupterm()
+ if curses.tigetnum("colors") > 0:
+ color = True
+ except:
+ pass
+ channel = logging.StreamHandler()
+ channel.setFormatter(_LogFormatter(color=color))
+ logging.getLogger().addHandler(channel)
+
+ if options.log_file_prefix:
+ channel = logging.handlers.RotatingFileHandler(
+ filename=options.log_file_prefix,
+ maxBytes=options.log_file_max_size,
+ backupCount=options.log_file_num_backups)
+ channel.setFormatter(_LogFormatter(color=False))
+ logging.getLogger().addHandler(channel)
+
+
+class _LogFormatter(logging.Formatter):
+ def __init__(self, color, *args, **kwargs):
+ logging.Formatter.__init__(self, *args, **kwargs)
+ self._color = color
+ if color:
+ fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or ""
+ self._colors = {
+ logging.DEBUG: curses.tparm(fg_color, 4), # Blue
+ logging.INFO: curses.tparm(fg_color, 2), # Green
+ logging.WARNING: curses.tparm(fg_color, 3), # Yellow
+ logging.ERROR: curses.tparm(fg_color, 1), # Red
+ }
+ self._normal = curses.tigetstr("sgr0")
+
+ def format(self, record):
+ try:
+ record.message = record.getMessage()
+ except Exception, e:
+ record.message = "Bad message (%r): %r" % (e, record.__dict__)
+ record.asctime = time.strftime(
+ "%y%m%d %H:%M:%S", self.converter(record.created))
+ prefix = '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]' % \
+ record.__dict__
+ if self._color:
+ prefix = (self._colors.get(record.levelno, self._normal) +
+ prefix + self._normal)
+ formatted = prefix + " " + record.message
+ if record.exc_info:
+ if not record.exc_text:
+ record.exc_text = self.formatException(record.exc_info)
+ if record.exc_text:
+ formatted = formatted.rstrip() + "\n" + record.exc_text
+ return formatted.replace("\n", "\n ")
+
+
+options = _Options.instance()
+
+
+# Default options
+define("help", type=bool, help="show this help information")
+define("logging", default="info",
+ help=("Set the Python log level. If 'none', tornado won't touch the "
+ "logging configuration."),
+ metavar="info|warning|error|none")
+define("log_to_stderr", type=bool, default=None,
+ help=("Send log output to stderr (colorized if possible). "
+ "By default use stderr if --log_file_prefix is not set."))
+define("log_file_prefix", type=str, default=None, metavar="PATH",
+ help=("Path prefix for log files. "
+ "Note that if you are running multiple tornado processes, "
+ "log_file_prefix must be different for each of them (e.g. "
+ "include the port number)"))
+define("log_file_max_size", type=int, default=100 * 1000 * 1000,
+ help="max size of log files before rollover")
+define("log_file_num_backups", type=int, default=10,
+ help="number of log files to keep")
diff --git a/vendor/tornado/tornado/s3server.py b/vendor/tornado/tornado/s3server.py
new file mode 100644
index 0000000000..2e8a97de20
--- /dev/null
+++ b/vendor/tornado/tornado/s3server.py
@@ -0,0 +1,255 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Implementation of an S3-like storage server based on local files.
+
+Useful to test features that will eventually run on S3, or if you want to
+run something locally that was once running on S3.
+
+We don't support all the features of S3, but it does work with the
+standard S3 client for the most basic semantics. To use the standard
+S3 client with this module:
+
+ c = S3.AWSAuthConnection("", "", server="localhost", port=8888,
+ is_secure=False)
+ c.create_bucket("mybucket")
+ c.put("mybucket", "mykey", "a value")
+ print c.get("mybucket", "mykey").body
+
+"""
+
+import bisect
+import datetime
+import escape
+import hashlib
+import httpserver
+import ioloop
+import os
+import os.path
+import urllib
+import web
+
+
+def start(port, root_directory="/tmp/s3", bucket_depth=0):
+ """Starts the mock S3 server on the given port at the given path."""
+ application = S3Application(root_directory, bucket_depth)
+ http_server = httpserver.HTTPServer(application)
+ http_server.listen(port)
+ ioloop.IOLoop.instance().start()
+
+
+class S3Application(web.Application):
+ """Implementation of an S3-like storage server based on local files.
+
+ If bucket depth is given, we break files up into multiple directories
+ to prevent hitting file system limits for number of files in each
+ directories. 1 means one level of directories, 2 means 2, etc.
+ """
+ def __init__(self, root_directory, bucket_depth=0):
+ web.Application.__init__(self, [
+ (r"/", RootHandler),
+ (r"/([^/]+)/(.+)", ObjectHandler),
+ (r"/([^/]+)/", BucketHandler),
+ ])
+ self.directory = os.path.abspath(root_directory)
+ if not os.path.exists(self.directory):
+ os.makedirs(self.directory)
+ self.bucket_depth = bucket_depth
+
+
+class BaseRequestHandler(web.RequestHandler):
+ SUPPORTED_METHODS = ("PUT", "GET", "DELETE")
+
+ def render_xml(self, value):
+ assert isinstance(value, dict) and len(value) == 1
+ self.set_header("Content-Type", "application/xml; charset=UTF-8")
+ name = value.keys()[0]
+ parts = []
+ parts.append('<' + escape.utf8(name) +
+ ' xmlns="http://doc.s3.amazonaws.com/2006-03-01">')
+ self._render_parts(value.values()[0], parts)
+ parts.append('</' + escape.utf8(name) + '>')
+ self.finish('<?xml version="1.0" encoding="UTF-8"?>\n' +
+ ''.join(parts))
+
+ def _render_parts(self, value, parts=[]):
+ if isinstance(value, basestring):
+ parts.append(escape.xhtml_escape(value))
+ elif isinstance(value, int) or isinstance(value, long):
+ parts.append(str(value))
+ elif isinstance(value, datetime.datetime):
+ parts.append(value.strftime("%Y-%m-%dT%H:%M:%S.000Z"))
+ elif isinstance(value, dict):
+ for name, subvalue in value.iteritems():
+ if not isinstance(subvalue, list):
+ subvalue = [subvalue]
+ for subsubvalue in subvalue:
+ parts.append('<' + escape.utf8(name) + '>')
+ self._render_parts(subsubvalue, parts)
+ parts.append('</' + escape.utf8(name) + '>')
+ else:
+ raise Exception("Unknown S3 value type %r", value)
+
+ def _object_path(self, bucket, object_name):
+ if self.application.bucket_depth < 1:
+ return os.path.abspath(os.path.join(
+ self.application.directory, bucket, object_name))
+ hash = hashlib.md5(object_name).hexdigest()
+ path = os.path.abspath(os.path.join(
+ self.application.directory, bucket))
+ for i in range(self.application.bucket_depth):
+ path = os.path.join(path, hash[:2 * (i + 1)])
+ return os.path.join(path, object_name)
+
+
+class RootHandler(BaseRequestHandler):
+ def get(self):
+ names = os.listdir(self.application.directory)
+ buckets = []
+ for name in names:
+ path = os.path.join(self.application.directory, name)
+ info = os.stat(path)
+ buckets.append({
+ "Name": name,
+ "CreationDate": datetime.datetime.utcfromtimestamp(
+ info.st_ctime),
+ })
+ self.render_xml({"ListAllMyBucketsResult": {
+ "Buckets": {"Bucket": buckets},
+ }})
+
+
+class BucketHandler(BaseRequestHandler):
+ def get(self, bucket_name):
+ prefix = self.get_argument("prefix", u"")
+ marker = self.get_argument("marker", u"")
+ max_keys = int(self.get_argument("max-keys", 50000))
+ path = os.path.abspath(os.path.join(self.application.directory,
+ bucket_name))
+ terse = int(self.get_argument("terse", 0))
+ if not path.startswith(self.application.directory) or \
+ not os.path.isdir(path):
+ raise web.HTTPError(404)
+ object_names = []
+ for root, dirs, files in os.walk(path):
+ for file_name in files:
+ object_names.append(os.path.join(root, file_name))
+ skip = len(path) + 1
+ for i in range(self.application.bucket_depth):
+ skip += 2 * (i + 1) + 1
+ object_names = [n[skip:] for n in object_names]
+ object_names.sort()
+ contents = []
+
+ start_pos = 0
+ if marker:
+ start_pos = bisect.bisect_right(object_names, marker, start_pos)
+ if prefix:
+ start_pos = bisect.bisect_left(object_names, prefix, start_pos)
+
+ truncated = False
+ for object_name in object_names[start_pos:]:
+ if not object_name.startswith(prefix):
+ break
+ if len(contents) >= max_keys:
+ truncated = True
+ break
+ object_path = self._object_path(bucket_name, object_name)
+ c = {"Key": object_name}
+ if not terse:
+ info = os.stat(object_path)
+ c.update({
+ "LastModified": datetime.datetime.utcfromtimestamp(
+ info.st_mtime),
+ "Size": info.st_size,
+ })
+ contents.append(c)
+ marker = object_name
+ self.render_xml({"ListBucketResult": {
+ "Name": bucket_name,
+ "Prefix": prefix,
+ "Marker": marker,
+ "MaxKeys": max_keys,
+ "IsTruncated": truncated,
+ "Contents": contents,
+ }})
+
+ def put(self, bucket_name):
+ path = os.path.abspath(os.path.join(
+ self.application.directory, bucket_name))
+ if not path.startswith(self.application.directory) or \
+ os.path.exists(path):
+ raise web.HTTPError(403)
+ os.makedirs(path)
+ self.finish()
+
+ def delete(self, bucket_name):
+ path = os.path.abspath(os.path.join(
+ self.application.directory, bucket_name))
+ if not path.startswith(self.application.directory) or \
+ not os.path.isdir(path):
+ raise web.HTTPError(404)
+ if len(os.listdir(path)) > 0:
+ raise web.HTTPError(403)
+ os.rmdir(path)
+ self.set_status(204)
+ self.finish()
+
+
+class ObjectHandler(BaseRequestHandler):
+ def get(self, bucket, object_name):
+ object_name = urllib.unquote(object_name)
+ path = self._object_path(bucket, object_name)
+ if not path.startswith(self.application.directory) or \
+ not os.path.isfile(path):
+ raise web.HTTPError(404)
+ info = os.stat(path)
+ self.set_header("Content-Type", "application/unknown")
+ self.set_header("Last-Modified", datetime.datetime.utcfromtimestamp(
+ info.st_mtime))
+ object_file = open(path, "r")
+ try:
+ self.finish(object_file.read())
+ finally:
+ object_file.close()
+
+ def put(self, bucket, object_name):
+ object_name = urllib.unquote(object_name)
+ bucket_dir = os.path.abspath(os.path.join(
+ self.application.directory, bucket))
+ if not bucket_dir.startswith(self.application.directory) or \
+ not os.path.isdir(bucket_dir):
+ raise web.HTTPError(404)
+ path = self._object_path(bucket, object_name)
+ if not path.startswith(bucket_dir) or os.path.isdir(path):
+ raise web.HTTPError(403)
+ directory = os.path.dirname(path)
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+ object_file = open(path, "w")
+ object_file.write(self.request.body)
+ object_file.close()
+ self.finish()
+
+ def delete(self, bucket, object_name):
+ object_name = urllib.unquote(object_name)
+ path = self._object_path(bucket, object_name)
+ if not path.startswith(self.application.directory) or \
+ not os.path.isfile(path):
+ raise web.HTTPError(404)
+ os.unlink(path)
+ self.set_status(204)
+ self.finish()
diff --git a/vendor/tornado/tornado/template.py b/vendor/tornado/tornado/template.py
new file mode 100644
index 0000000000..7ed56cfa69
--- /dev/null
+++ b/vendor/tornado/tornado/template.py
@@ -0,0 +1,576 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""A simple template system that compiles templates to Python code.
+
+Basic usage looks like:
+
+ t = template.Template("<html>{{ myvalue }}</html>")
+ print t.generate(myvalue="XXX")
+
+Loader is a class that loads templates from a root directory and caches
+the compiled templates:
+
+ loader = template.Loader("/home/btaylor")
+ print loader.load("test.html").generate(myvalue="XXX")
+
+We compile all templates to raw Python. Error-reporting is currently... uh,
+interesting. Syntax for the templates
+
+ ### base.html
+ <html>
+ <head>
+ <title>{% block title %}Default title{% end %}</title>
+ </head>
+ <body>
+ <ul>
+ {% for student in students %}
+ {% block student %}
+ <li>{{ escape(student.name) }}</li>
+ {% end %}
+ {% end %}
+ </ul>
+ </body>
+ </html>
+
+ ### bold.html
+ {% extends "base.html" %}
+
+ {% block title %}A bolder title{% end %}
+
+ {% block student %}
+ <li><span style="bold">{{ escape(student.name) }}</span></li>
+ {% block %}
+
+Unlike most other template systems, we do not put any restrictions on the
+expressions you can include in your statements. if and for blocks get
+translated exactly into Python, do you can do complex expressions like:
+
+ {% for student in [p for p in people if p.student and p.age > 23] %}
+ <li>{{ escape(student.name) }}</li>
+ {% end %}
+
+Translating directly to Python means you can apply functions to expressions
+easily, like the escape() function in the examples above. You can pass
+functions in to your template just like any other variable:
+
+ ### Python code
+ def add(x, y):
+ return x + y
+ template.execute(add=add)
+
+ ### The template
+ {{ add(1, 2) }}
+
+We provide the functions escape(), url_escape(), json_encode(), and squeeze()
+to all templates by default.
+"""
+
+from __future__ import with_statement
+
+import cStringIO
+import datetime
+import escape
+import logging
+import os.path
+import re
+
+_log = logging.getLogger('tornado.template')
+
+class Template(object):
+ """A compiled template.
+
+ We compile into Python from the given template_string. You can generate
+ the template from variables with generate().
+ """
+ def __init__(self, template_string, name="<string>", loader=None,
+ compress_whitespace=None):
+ self.name = name
+ if compress_whitespace is None:
+ compress_whitespace = name.endswith(".html") or \
+ name.endswith(".js")
+ reader = _TemplateReader(name, template_string)
+ self.file = _File(_parse(reader))
+ self.code = self._generate_python(loader, compress_whitespace)
+ try:
+ self.compiled = compile(self.code, self.name, "exec")
+ except:
+ formatted_code = _format_code(self.code).rstrip()
+ _log.error("%s code:\n%s", self.name, formatted_code)
+ raise
+
+ def generate(self, **kwargs):
+ """Generate this template with the given arguments."""
+ namespace = {
+ "escape": escape.xhtml_escape,
+ "url_escape": escape.url_escape,
+ "json_encode": escape.json_encode,
+ "squeeze": escape.squeeze,
+ "datetime": datetime,
+ }
+ namespace.update(kwargs)
+ exec self.compiled in namespace
+ execute = namespace["_execute"]
+ try:
+ return execute()
+ except:
+ formatted_code = _format_code(self.code).rstrip()
+ _log.error("%s code:\n%s", self.name, formatted_code)
+ raise
+
+ def _generate_python(self, loader, compress_whitespace):
+ buffer = cStringIO.StringIO()
+ try:
+ named_blocks = {}
+ ancestors = self._get_ancestors(loader)
+ ancestors.reverse()
+ for ancestor in ancestors:
+ ancestor.find_named_blocks(loader, named_blocks)
+ self.file.find_named_blocks(loader, named_blocks)
+ writer = _CodeWriter(buffer, named_blocks, loader, self,
+ compress_whitespace)
+ ancestors[0].generate(writer)
+ return buffer.getvalue()
+ finally:
+ buffer.close()
+
+ def _get_ancestors(self, loader):
+ ancestors = [self.file]
+ for chunk in self.file.body.chunks:
+ if isinstance(chunk, _ExtendsBlock):
+ if not loader:
+ raise ParseError("{% extends %} block found, but no "
+ "template loader")
+ template = loader.load(chunk.name, self.name)
+ ancestors.extend(template._get_ancestors(loader))
+ return ancestors
+
+
+class Loader(object):
+ """A template loader that loads from a single root directory.
+
+ You must use a template loader to use template constructs like
+ {% extends %} and {% include %}. Loader caches all templates after
+ they are loaded the first time.
+ """
+ def __init__(self, root_directory):
+ self.root = os.path.abspath(root_directory)
+ self.templates = {}
+
+ def reset(self):
+ self.templates = {}
+
+ def resolve_path(self, name, parent_path=None):
+ if parent_path and not parent_path.startswith("<") and \
+ not parent_path.startswith("/") and \
+ not name.startswith("/"):
+ current_path = os.path.join(self.root, parent_path)
+ file_dir = os.path.dirname(os.path.abspath(current_path))
+ relative_path = os.path.abspath(os.path.join(file_dir, name))
+ if relative_path.startswith(self.root):
+ name = relative_path[len(self.root) + 1:]
+ return name
+
+ def load(self, name, parent_path=None):
+ name = self.resolve_path(name, parent_path=parent_path)
+ if name not in self.templates:
+ path = os.path.join(self.root, name)
+ f = open(path, "r")
+ self.templates[name] = Template(f.read(), name=name, loader=self)
+ f.close()
+ return self.templates[name]
+
+
+class _Node(object):
+ def each_child(self):
+ return ()
+
+ def generate(self, writer):
+ raise NotImplementedError()
+
+ def find_named_blocks(self, loader, named_blocks):
+ for child in self.each_child():
+ child.find_named_blocks(loader, named_blocks)
+
+
+class _File(_Node):
+ def __init__(self, body):
+ self.body = body
+
+ def generate(self, writer):
+ writer.write_line("def _execute():")
+ with writer.indent():
+ writer.write_line("_buffer = []")
+ self.body.generate(writer)
+ writer.write_line("return ''.join(_buffer)")
+
+ def each_child(self):
+ return (self.body,)
+
+
+
+class _ChunkList(_Node):
+ def __init__(self, chunks):
+ self.chunks = chunks
+
+ def generate(self, writer):
+ for chunk in self.chunks:
+ chunk.generate(writer)
+
+ def each_child(self):
+ return self.chunks
+
+
+class _NamedBlock(_Node):
+ def __init__(self, name, body=None):
+ self.name = name
+ self.body = body
+
+ def each_child(self):
+ return (self.body,)
+
+ def generate(self, writer):
+ writer.named_blocks[self.name].generate(writer)
+
+ def find_named_blocks(self, loader, named_blocks):
+ named_blocks[self.name] = self.body
+ _Node.find_named_blocks(self, loader, named_blocks)
+
+
+class _ExtendsBlock(_Node):
+ def __init__(self, name):
+ self.name = name
+
+
+class _IncludeBlock(_Node):
+ def __init__(self, name, reader):
+ self.name = name
+ self.template_name = reader.name
+
+ def find_named_blocks(self, loader, named_blocks):
+ included = loader.load(self.name, self.template_name)
+ included.file.find_named_blocks(loader, named_blocks)
+
+ def generate(self, writer):
+ included = writer.loader.load(self.name, self.template_name)
+ old = writer.current_template
+ writer.current_template = included
+ included.file.body.generate(writer)
+ writer.current_template = old
+
+
+class _ApplyBlock(_Node):
+ def __init__(self, method, body=None):
+ self.method = method
+ self.body = body
+
+ def each_child(self):
+ return (self.body,)
+
+ def generate(self, writer):
+ method_name = "apply%d" % writer.apply_counter
+ writer.apply_counter += 1
+ writer.write_line("def %s():" % method_name)
+ with writer.indent():
+ writer.write_line("_buffer = []")
+ self.body.generate(writer)
+ writer.write_line("return ''.join(_buffer)")
+ writer.write_line("_buffer.append(%s(%s()))" % (
+ self.method, method_name))
+
+
+class _ControlBlock(_Node):
+ def __init__(self, statement, body=None):
+ self.statement = statement
+ self.body = body
+
+ def each_child(self):
+ return (self.body,)
+
+ def generate(self, writer):
+ writer.write_line("%s:" % self.statement)
+ with writer.indent():
+ self.body.generate(writer)
+
+
+class _IntermediateControlBlock(_Node):
+ def __init__(self, statement):
+ self.statement = statement
+
+ def generate(self, writer):
+ writer.write_line("%s:" % self.statement, writer.indent_size() - 1)
+
+
+class _Statement(_Node):
+ def __init__(self, statement):
+ self.statement = statement
+
+ def generate(self, writer):
+ writer.write_line(self.statement)
+
+
+class _Expression(_Node):
+ def __init__(self, expression):
+ self.expression = expression
+
+ def generate(self, writer):
+ writer.write_line("_tmp = %s" % self.expression)
+ writer.write_line("if isinstance(_tmp, str): _buffer.append(_tmp)")
+ writer.write_line("elif isinstance(_tmp, unicode): "
+ "_buffer.append(_tmp.encode('utf-8'))")
+ writer.write_line("else: _buffer.append(str(_tmp))")
+
+
+class _Text(_Node):
+ def __init__(self, value):
+ self.value = value
+
+ def generate(self, writer):
+ value = self.value
+
+ # Compress lots of white space to a single character. If the whitespace
+ # breaks a line, have it continue to break a line, but just with a
+ # single \n character
+ if writer.compress_whitespace and "<pre>" not in value:
+ value = re.sub(r"([\t ]+)", " ", value)
+ value = re.sub(r"(\s*\n\s*)", "\n", value)
+
+ if value:
+ writer.write_line('_buffer.append(%r)' % value)
+
+
+class ParseError(Exception):
+ """Raised for template syntax errors."""
+ pass
+
+
+class _CodeWriter(object):
+ def __init__(self, file, named_blocks, loader, current_template,
+ compress_whitespace):
+ self.file = file
+ self.named_blocks = named_blocks
+ self.loader = loader
+ self.current_template = current_template
+ self.compress_whitespace = compress_whitespace
+ self.apply_counter = 0
+ self._indent = 0
+
+ def indent(self):
+ return self
+
+ def indent_size(self):
+ return self._indent
+
+ def __enter__(self):
+ self._indent += 1
+ return self
+
+ def __exit__(self, *args):
+ assert self._indent > 0
+ self._indent -= 1
+
+ def write_line(self, line, indent=None):
+ if indent == None:
+ indent = self._indent
+ for i in xrange(indent):
+ self.file.write(" ")
+ print >> self.file, line
+
+
+class _TemplateReader(object):
+ def __init__(self, name, text):
+ self.name = name
+ self.text = text
+ self.line = 0
+ self.pos = 0
+
+ def find(self, needle, start=0, end=None):
+ assert start >= 0, start
+ pos = self.pos
+ start += pos
+ if end is None:
+ index = self.text.find(needle, start)
+ else:
+ end += pos
+ assert end >= start
+ index = self.text.find(needle, start, end)
+ if index != -1:
+ index -= pos
+ return index
+
+ def consume(self, count=None):
+ if count is None:
+ count = len(self.text) - self.pos
+ newpos = self.pos + count
+ self.line += self.text.count("\n", self.pos, newpos)
+ s = self.text[self.pos:newpos]
+ self.pos = newpos
+ return s
+
+ def remaining(self):
+ return len(self.text) - self.pos
+
+ def __len__(self):
+ return self.remaining()
+
+ def __getitem__(self, key):
+ if type(key) is slice:
+ size = len(self)
+ start, stop, step = slice.indices(size)
+ if start is None: start = self.pos
+ else: start += self.pos
+ if stop is not None: stop += self.pos
+ return self.text[slice(start, stop, step)]
+ elif key < 0:
+ return self.text[key]
+ else:
+ return self.text[self.pos + key]
+
+ def __str__(self):
+ return self.text[self.pos:]
+
+
+def _format_code(code):
+ lines = code.splitlines()
+ format = "%%%dd %%s\n" % len(repr(len(lines) + 1))
+ return "".join([format % (i + 1, line) for (i, line) in enumerate(lines)])
+
+
+def _parse(reader, in_block=None):
+ body = _ChunkList([])
+ while True:
+ # Find next template directive
+ curly = 0
+ while True:
+ curly = reader.find("{", curly)
+ if curly == -1 or curly + 1 == reader.remaining():
+ # EOF
+ if in_block:
+ raise ParseError("Missing {%% end %%} block for %s" %
+ in_block)
+ body.chunks.append(_Text(reader.consume()))
+ return body
+ # If the first curly brace is not the start of a special token,
+ # start searching from the character after it
+ if reader[curly + 1] not in ("{", "%"):
+ curly += 1
+ continue
+ # When there are more than 2 curlies in a row, use the
+ # innermost ones. This is useful when generating languages
+ # like latex where curlies are also meaningful
+ if (curly + 2 < reader.remaining() and
+ reader[curly + 1] == '{' and reader[curly + 2] == '{'):
+ curly += 1
+ continue
+ break
+
+ # Append any text before the special token
+ if curly > 0:
+ body.chunks.append(_Text(reader.consume(curly)))
+
+ start_brace = reader.consume(2)
+ line = reader.line
+
+ # Expression
+ if start_brace == "{{":
+ end = reader.find("}}")
+ if end == -1 or reader.find("\n", 0, end) != -1:
+ raise ParseError("Missing end expression }} on line %d" % line)
+ contents = reader.consume(end).strip()
+ reader.consume(2)
+ if not contents:
+ raise ParseError("Empty expression on line %d" % line)
+ body.chunks.append(_Expression(contents))
+ continue
+
+ # Block
+ assert start_brace == "{%", start_brace
+ end = reader.find("%}")
+ if end == -1 or reader.find("\n", 0, end) != -1:
+ raise ParseError("Missing end block %%} on line %d" % line)
+ contents = reader.consume(end).strip()
+ reader.consume(2)
+ if not contents:
+ raise ParseError("Empty block tag ({%% %%}) on line %d" % line)
+
+ operator, space, suffix = contents.partition(" ")
+ suffix = suffix.strip()
+
+ # Intermediate ("else", "elif", etc) blocks
+ intermediate_blocks = {
+ "else": set(["if", "for", "while"]),
+ "elif": set(["if"]),
+ "except": set(["try"]),
+ "finally": set(["try"]),
+ }
+ allowed_parents = intermediate_blocks.get(operator)
+ if allowed_parents is not None:
+ if not in_block:
+ raise ParseError("%s outside %s block" %
+ (operator, allowed_parents))
+ if in_block not in allowed_parents:
+ raise ParseError("%s block cannot be attached to %s block" % (operator, in_block))
+ body.chunks.append(_IntermediateControlBlock(contents))
+ continue
+
+ # End tag
+ elif operator == "end":
+ if not in_block:
+ raise ParseError("Extra {%% end %%} block on line %d" % line)
+ return body
+
+ elif operator in ("extends", "include", "set", "import", "comment"):
+ if operator == "comment":
+ continue
+ if operator == "extends":
+ suffix = suffix.strip('"').strip("'")
+ if not suffix:
+ raise ParseError("extends missing file path on line %d" % line)
+ block = _ExtendsBlock(suffix)
+ elif operator == "import":
+ if not suffix:
+ raise ParseError("import missing statement on line %d" % line)
+ block = _Statement(contents)
+ elif operator == "include":
+ suffix = suffix.strip('"').strip("'")
+ if not suffix:
+ raise ParseError("include missing file path on line %d" % line)
+ block = _IncludeBlock(suffix, reader)
+ elif operator == "set":
+ if not suffix:
+ raise ParseError("set missing statement on line %d" % line)
+ block = _Statement(suffix)
+ body.chunks.append(block)
+ continue
+
+ elif operator in ("apply", "block", "try", "if", "for", "while"):
+ # parse inner body recursively
+ block_body = _parse(reader, operator)
+ if operator == "apply":
+ if not suffix:
+ raise ParseError("apply missing method name on line %d" % line)
+ block = _ApplyBlock(suffix, block_body)
+ elif operator == "block":
+ if not suffix:
+ raise ParseError("block missing name on line %d" % line)
+ block = _NamedBlock(suffix, block_body)
+ else:
+ block = _ControlBlock(contents, block_body)
+ body.chunks.append(block)
+ continue
+
+ else:
+ raise ParseError("unknown operator: %r" % operator)
diff --git a/vendor/tornado/tornado/test/README b/vendor/tornado/tornado/test/README
new file mode 100644
index 0000000000..2d6195d807
--- /dev/null
+++ b/vendor/tornado/tornado/test/README
@@ -0,0 +1,4 @@
+Test coverage is almost non-existent, but it's a start. Be sure to
+set PYTHONPATH apprioriately (generally to the root directory of your
+tornado checkout) when running tests to make sure you're getting the
+version of the tornado package that you expect. \ No newline at end of file
diff --git a/vendor/tornado/tornado/test/test_ioloop.py b/vendor/tornado/tornado/test/test_ioloop.py
new file mode 100755
index 0000000000..2541fa87e1
--- /dev/null
+++ b/vendor/tornado/tornado/test/test_ioloop.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+import unittest
+import time
+
+from tornado import ioloop
+
+
+class TestIOLoop(unittest.TestCase):
+ def setUp(self):
+ self.loop = ioloop.IOLoop()
+
+ def tearDown(self):
+ pass
+
+ def _callback(self):
+ self.called = True
+ self.loop.stop()
+
+ def _schedule_callback(self):
+ self.loop.add_callback(self._callback)
+ # Scroll away the time so we can check if we woke up immediately
+ self._start_time = time.time()
+ self.called = False
+
+ def test_add_callback(self):
+ self.loop.add_timeout(time.time(), self._schedule_callback)
+ self.loop.start() # Set some long poll timeout so we can check wakeup
+ self.assertAlmostEqual(time.time(), self._start_time, places=2)
+ self.assertTrue(self.called)
+
+
+if __name__ == "__main__":
+ import logging
+
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s:%(msecs)03d %(levelname)-8s %(name)-8s %(message)s', datefmt='%H:%M:%S')
+
+ unittest.main()
diff --git a/vendor/tornado/tornado/web.py b/vendor/tornado/tornado/web.py
new file mode 100644
index 0000000000..7559fae8a5
--- /dev/null
+++ b/vendor/tornado/tornado/web.py
@@ -0,0 +1,1445 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""The Tornado web framework.
+
+The Tornado web framework looks a bit like web.py (http://webpy.org/) or
+Google's webapp (http://code.google.com/appengine/docs/python/tools/webapp/),
+but with additional tools and optimizations to take advantage of the
+Tornado non-blocking web server and tools.
+
+Here is the canonical "Hello, world" example app:
+
+ import tornado.httpserver
+ import tornado.ioloop
+ import tornado.web
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.write("Hello, world")
+
+ if __name__ == "__main__":
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ ])
+ http_server = tornado.httpserver.HTTPServer(application)
+ http_server.listen(8888)
+ tornado.ioloop.IOLoop.instance().start()
+
+See the Tornado walkthrough on GitHub for more details and a good
+getting started guide.
+"""
+
+import base64
+import binascii
+import calendar
+import Cookie
+import cStringIO
+import datetime
+import email.utils
+import escape
+import functools
+import gzip
+import hashlib
+import hmac
+import httplib
+import locale
+import logging
+import mimetypes
+import os.path
+import re
+import stat
+import sys
+import template
+import time
+import types
+import urllib
+import urlparse
+import uuid
+
+_log = logging.getLogger('tornado.web')
+
+class RequestHandler(object):
+ """Subclass this class and define get() or post() to make a handler.
+
+ If you want to support more methods than the standard GET/HEAD/POST, you
+ should override the class variable SUPPORTED_METHODS in your
+ RequestHandler class.
+ """
+ SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PUT")
+
+ def __init__(self, application, request, transforms=None):
+ self.application = application
+ self.request = request
+ self._headers_written = False
+ self._finished = False
+ self._auto_finish = True
+ self._transforms = transforms or []
+ self.ui = _O((n, self._ui_method(m)) for n, m in
+ application.ui_methods.iteritems())
+ self.ui["modules"] = _O((n, self._ui_module(n, m)) for n, m in
+ application.ui_modules.iteritems())
+ self.clear()
+ # Check since connection is not available in WSGI
+ if hasattr(self.request, "connection"):
+ self.request.connection.stream.set_close_callback(
+ self.on_connection_close)
+
+ @property
+ def settings(self):
+ return self.application.settings
+
+ def head(self, *args, **kwargs):
+ raise HTTPError(405)
+
+ def get(self, *args, **kwargs):
+ raise HTTPError(405)
+
+ def post(self, *args, **kwargs):
+ raise HTTPError(405)
+
+ def delete(self, *args, **kwargs):
+ raise HTTPError(405)
+
+ def put(self, *args, **kwargs):
+ raise HTTPError(405)
+
+ def prepare(self):
+ """Called before the actual handler method.
+
+ Useful to override in a handler if you want a common bottleneck for
+ all of your requests.
+ """
+ pass
+
+ def on_connection_close(self):
+ """Called in async handlers if the client closed the connection.
+
+ You may override this to clean up resources associated with
+ long-lived connections.
+
+ Note that the select()-based implementation of IOLoop does not detect
+ closed connections and so this method will not be called until
+ you try (and fail) to produce some output. The epoll- and kqueue-
+ based implementations should detect closed connections even while
+ the request is idle.
+ """
+ pass
+
+ def clear(self):
+ """Resets all headers and content for this response."""
+ self._headers = {
+ "Server": "TornadoServer/0.1",
+ "Content-Type": "text/html; charset=UTF-8",
+ }
+ if not self.request.supports_http_1_1():
+ if self.request.headers.get("Connection") == "Keep-Alive":
+ self.set_header("Connection", "Keep-Alive")
+ self._write_buffer = []
+ self._status_code = 200
+
+ def set_status(self, status_code):
+ """Sets the status code for our response."""
+ assert status_code in httplib.responses
+ self._status_code = status_code
+
+ def set_header(self, name, value):
+ """Sets the given response header name and value.
+
+ If a datetime is given, we automatically format it according to the
+ HTTP specification. If the value is not a string, we convert it to
+ a string. All header values are then encoded as UTF-8.
+ """
+ if isinstance(value, datetime.datetime):
+ t = calendar.timegm(value.utctimetuple())
+ value = email.utils.formatdate(t, localtime=False, usegmt=True)
+ elif isinstance(value, int) or isinstance(value, long):
+ value = str(value)
+ else:
+ value = _utf8(value)
+ # If \n is allowed into the header, it is possible to inject
+ # additional headers or split the request. Also cap length to
+ # prevent obviously erroneous values.
+ safe_value = re.sub(r"[\x00-\x1f]", " ", value)[:4000]
+ if safe_value != value:
+ raise ValueError("Unsafe header value %r", value)
+ self._headers[name] = value
+
+ _ARG_DEFAULT = []
+ def get_argument(self, name, default=_ARG_DEFAULT, strip=True):
+ """Returns the value of the argument with the given name.
+
+ If default is not provided, the argument is considered to be
+ required, and we throw an HTTP 404 exception if it is missing.
+
+ The returned value is always unicode.
+ """
+ values = self.request.arguments.get(name, None)
+ if values is None:
+ if default is self._ARG_DEFAULT:
+ raise HTTPError(404, "Missing argument %s" % name)
+ return default
+ # Get rid of any weird control chars
+ value = re.sub(r"[\x00-\x08\x0e-\x1f]", " ", values[-1])
+ value = _unicode(value)
+ if strip: value = value.strip()
+ return value
+
+ @property
+ def cookies(self):
+ """A dictionary of Cookie.Morsel objects."""
+ if not hasattr(self, "_cookies"):
+ self._cookies = Cookie.BaseCookie()
+ if "Cookie" in self.request.headers:
+ try:
+ self._cookies.load(self.request.headers["Cookie"])
+ except:
+ self.clear_all_cookies()
+ return self._cookies
+
+ def get_cookie(self, name, default=None):
+ """Gets the value of the cookie with the given name, else default."""
+ if name in self.cookies:
+ return self.cookies[name].value
+ return default
+
+ def set_cookie(self, name, value, domain=None, expires=None, path="/",
+ expires_days=None):
+ """Sets the given cookie name/value with the given options."""
+ name = _utf8(name)
+ value = _utf8(value)
+ if re.search(r"[\x00-\x20]", name + value):
+ # Don't let us accidentally inject bad stuff
+ raise ValueError("Invalid cookie %r: %r" % (name, value))
+ if not hasattr(self, "_new_cookies"):
+ self._new_cookies = []
+ new_cookie = Cookie.BaseCookie()
+ self._new_cookies.append(new_cookie)
+ new_cookie[name] = value
+ if domain:
+ new_cookie[name]["domain"] = domain
+ if expires_days is not None and not expires:
+ expires = datetime.datetime.utcnow() + datetime.timedelta(
+ days=expires_days)
+ if expires:
+ timestamp = calendar.timegm(expires.utctimetuple())
+ new_cookie[name]["expires"] = email.utils.formatdate(
+ timestamp, localtime=False, usegmt=True)
+ if path:
+ new_cookie[name]["path"] = path
+
+ def clear_cookie(self, name, path="/", domain=None):
+ """Deletes the cookie with the given name."""
+ expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
+ self.set_cookie(name, value="", path=path, expires=expires,
+ domain=domain)
+
+ def clear_all_cookies(self):
+ """Deletes all the cookies the user sent with this request."""
+ for name in self.cookies.iterkeys():
+ self.clear_cookie(name)
+
+ def set_secure_cookie(self, name, value, expires_days=30, **kwargs):
+ """Signs and timestamps a cookie so it cannot be forged.
+
+ You must specify the 'cookie_secret' setting in your Application
+ to use this method. It should be a long, random sequence of bytes
+ to be used as the HMAC secret for the signature.
+
+ To read a cookie set with this method, use get_secure_cookie().
+ """
+ timestamp = str(int(time.time()))
+ value = base64.b64encode(value)
+ signature = self._cookie_signature(name, value, timestamp)
+ value = "|".join([value, timestamp, signature])
+ self.set_cookie(name, value, expires_days=expires_days, **kwargs)
+
+ def get_secure_cookie(self, name, include_name=True, value=None):
+ """Returns the given signed cookie if it validates, or None.
+
+ In older versions of Tornado (0.1 and 0.2), we did not include the
+ name of the cookie in the cookie signature. To read these old-style
+ cookies, pass include_name=False to this method. Otherwise, all
+ attempts to read old-style cookies will fail (and you may log all
+ your users out whose cookies were written with a previous Tornado
+ version).
+ """
+ if value is None: value = self.get_cookie(name)
+ if not value: return None
+ parts = value.split("|")
+ if len(parts) != 3: return None
+ if include_name:
+ signature = self._cookie_signature(name, parts[0], parts[1])
+ else:
+ signature = self._cookie_signature(parts[0], parts[1])
+ if not _time_independent_equals(parts[2], signature):
+ _log.warning("Invalid cookie signature %r", value)
+ return None
+ timestamp = int(parts[1])
+ if timestamp < time.time() - 31 * 86400:
+ _log.warning("Expired cookie %r", value)
+ return None
+ try:
+ return base64.b64decode(parts[0])
+ except:
+ return None
+
+ def _cookie_signature(self, *parts):
+ self.require_setting("cookie_secret", "secure cookies")
+ hash = hmac.new(self.application.settings["cookie_secret"],
+ digestmod=hashlib.sha1)
+ for part in parts: hash.update(part)
+ return hash.hexdigest()
+
+ def redirect(self, url, permanent=False):
+ """Sends a redirect to the given (optionally relative) URL."""
+ if self._headers_written:
+ raise Exception("Cannot redirect after headers have been written")
+ self.set_status(301 if permanent else 302)
+ # Remove whitespace
+ url = re.sub(r"[\x00-\x20]+", "", _utf8(url))
+ self.set_header("Location", urlparse.urljoin(self.request.uri, url))
+ self.finish()
+
+ def write(self, chunk):
+ """Writes the given chunk to the output buffer.
+
+ To write the output to the network, use the flush() method below.
+
+ If the given chunk is a dictionary, we write it as JSON and set
+ the Content-Type of the response to be text/javascript.
+ """
+ assert not self._finished
+ if isinstance(chunk, dict):
+ chunk = escape.json_encode(chunk)
+ self.set_header("Content-Type", "text/javascript; charset=UTF-8")
+ chunk = _utf8(chunk)
+ self._write_buffer.append(chunk)
+
+ def render(self, template_name, **kwargs):
+ """Renders the template with the given arguments as the response."""
+ html = self.render_string(template_name, **kwargs)
+
+ # Insert the additional JS and CSS added by the modules on the page
+ js_embed = []
+ js_files = []
+ css_embed = []
+ css_files = []
+ html_heads = []
+ html_bodies = []
+ for module in getattr(self, "_active_modules", {}).itervalues():
+ embed_part = module.embedded_javascript()
+ if embed_part: js_embed.append(_utf8(embed_part))
+ file_part = module.javascript_files()
+ if file_part:
+ if isinstance(file_part, basestring):
+ js_files.append(file_part)
+ else:
+ js_files.extend(file_part)
+ embed_part = module.embedded_css()
+ if embed_part: css_embed.append(_utf8(embed_part))
+ file_part = module.css_files()
+ if file_part:
+ if isinstance(file_part, basestring):
+ css_files.append(file_part)
+ else:
+ css_files.extend(file_part)
+ head_part = module.html_head()
+ if head_part: html_heads.append(_utf8(head_part))
+ body_part = module.html_body()
+ if body_part: html_bodies.append(_utf8(body_part))
+ if js_files:
+ # Maintain order of JavaScript files given by modules
+ paths = []
+ unique_paths = set()
+ for path in js_files:
+ if not path.startswith("/") and not path.startswith("http:"):
+ path = self.static_url(path)
+ if path not in unique_paths:
+ paths.append(path)
+ unique_paths.add(path)
+ js = ''.join('<script src="' + escape.xhtml_escape(p) +
+ '" type="text/javascript"></script>'
+ for p in paths)
+ sloc = html.rindex('</body>')
+ html = html[:sloc] + js + '\n' + html[sloc:]
+ if js_embed:
+ js = '<script type="text/javascript">\n//<![CDATA[\n' + \
+ '\n'.join(js_embed) + '\n//]]>\n</script>'
+ sloc = html.rindex('</body>')
+ html = html[:sloc] + js + '\n' + html[sloc:]
+ if css_files:
+ paths = set()
+ for path in css_files:
+ if not path.startswith("/") and not path.startswith("http:"):
+ paths.add(self.static_url(path))
+ else:
+ paths.add(path)
+ css = ''.join('<link href="' + escape.xhtml_escape(p) + '" '
+ 'type="text/css" rel="stylesheet"/>'
+ for p in paths)
+ hloc = html.index('</head>')
+ html = html[:hloc] + css + '\n' + html[hloc:]
+ if css_embed:
+ css = '<style type="text/css">\n' + '\n'.join(css_embed) + \
+ '\n</style>'
+ hloc = html.index('</head>')
+ html = html[:hloc] + css + '\n' + html[hloc:]
+ if html_heads:
+ hloc = html.index('</head>')
+ html = html[:hloc] + ''.join(html_heads) + '\n' + html[hloc:]
+ if html_bodies:
+ hloc = html.index('</body>')
+ html = html[:hloc] + ''.join(html_bodies) + '\n' + html[hloc:]
+ self.finish(html)
+
+ def render_string(self, template_name, **kwargs):
+ """Generate the given template with the given arguments.
+
+ We return the generated string. To generate and write a template
+ as a response, use render() above.
+ """
+ # If no template_path is specified, use the path of the calling file
+ template_path = self.application.settings.get("template_path")
+ if not template_path:
+ frame = sys._getframe(0)
+ web_file = frame.f_code.co_filename
+ while frame.f_code.co_filename == web_file:
+ frame = frame.f_back
+ template_path = os.path.dirname(frame.f_code.co_filename)
+ if not getattr(RequestHandler, "_templates", None):
+ RequestHandler._templates = {}
+ if template_path not in RequestHandler._templates:
+ loader = self.application.settings.get("template_loader") or\
+ template.Loader(template_path)
+ RequestHandler._templates[template_path] = loader
+ t = RequestHandler._templates[template_path].load(template_name)
+ args = dict(
+ handler=self,
+ request=self.request,
+ current_user=self.current_user,
+ locale=self.locale,
+ _=self.locale.translate,
+ static_url=self.static_url,
+ xsrf_form_html=self.xsrf_form_html,
+ reverse_url=self.application.reverse_url
+ )
+ args.update(self.ui)
+ args.update(kwargs)
+ return t.generate(**args)
+
+ def flush(self, include_footers=False):
+ """Flushes the current output buffer to the nextwork."""
+ if self.application._wsgi:
+ raise Exception("WSGI applications do not support flush()")
+
+ chunk = "".join(self._write_buffer)
+ self._write_buffer = []
+ if not self._headers_written:
+ self._headers_written = True
+ for transform in self._transforms:
+ self._headers, chunk = transform.transform_first_chunk(
+ self._headers, chunk, include_footers)
+ headers = self._generate_headers()
+ else:
+ for transform in self._transforms:
+ chunk = transform.transform_chunk(chunk, include_footers)
+ headers = ""
+
+ # Ignore the chunk and only write the headers for HEAD requests
+ if self.request.method == "HEAD":
+ if headers: self.request.write(headers)
+ return
+
+ if headers or chunk:
+ self.request.write(headers + chunk)
+
+ def finish(self, chunk=None):
+ """Finishes this response, ending the HTTP request."""
+ assert not self._finished
+ if chunk is not None: self.write(chunk)
+
+ # Automatically support ETags and add the Content-Length header if
+ # we have not flushed any content yet.
+ if not self._headers_written:
+ if (self._status_code == 200 and self.request.method == "GET" and
+ "Etag" not in self._headers):
+ hasher = hashlib.sha1()
+ for part in self._write_buffer:
+ hasher.update(part)
+ etag = '"%s"' % hasher.hexdigest()
+ inm = self.request.headers.get("If-None-Match")
+ if inm and inm.find(etag) != -1:
+ self._write_buffer = []
+ self.set_status(304)
+ else:
+ self.set_header("Etag", etag)
+ if "Content-Length" not in self._headers:
+ content_length = sum(len(part) for part in self._write_buffer)
+ self.set_header("Content-Length", content_length)
+
+ if not self.application._wsgi:
+ self.flush(include_footers=True)
+ self.request.finish()
+ self._log()
+ self._finished = True
+
+ def send_error(self, status_code=500, **kwargs):
+ """Sends the given HTTP error code to the browser.
+
+ We also send the error HTML for the given error code as returned by
+ get_error_html. Override that method if you want custom error pages
+ for your application.
+ """
+ if self._headers_written:
+ _log.error("Cannot send error response after headers written")
+ if not self._finished:
+ self.finish()
+ return
+ self.clear()
+ self.set_status(status_code)
+ message = self.get_error_html(status_code, **kwargs)
+ self.finish(message)
+
+ def get_error_html(self, status_code, **kwargs):
+ """Override to implement custom error pages.
+
+ If this error was caused by an uncaught exception, the
+ exception object can be found in kwargs e.g. kwargs['exception']
+ """
+ return "<html><title>%(code)d: %(message)s</title>" \
+ "<body>%(code)d: %(message)s</body></html>" % {
+ "code": status_code,
+ "message": httplib.responses[status_code],
+ }
+
+ @property
+ def locale(self):
+ """The local for the current session.
+
+ Determined by either get_user_locale, which you can override to
+ set the locale based on, e.g., a user preference stored in a
+ database, or get_browser_locale, which uses the Accept-Language
+ header.
+ """
+ if not hasattr(self, "_locale"):
+ self._locale = self.get_user_locale()
+ if not self._locale:
+ self._locale = self.get_browser_locale()
+ assert self._locale
+ return self._locale
+
+ def get_user_locale(self):
+ """Override to determine the locale from the authenticated user.
+
+ If None is returned, we use the Accept-Language header.
+ """
+ return None
+
+ def get_browser_locale(self, default="en_US"):
+ """Determines the user's locale from Accept-Language header.
+
+ See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
+ """
+ if "Accept-Language" in self.request.headers:
+ languages = self.request.headers["Accept-Language"].split(",")
+ locales = []
+ for language in languages:
+ parts = language.strip().split(";")
+ if len(parts) > 1 and parts[1].startswith("q="):
+ try:
+ score = float(parts[1][2:])
+ except (ValueError, TypeError):
+ score = 0.0
+ else:
+ score = 1.0
+ locales.append((parts[0], score))
+ if locales:
+ locales.sort(key=lambda (l, s): s, reverse=True)
+ codes = [l[0] for l in locales]
+ return locale.get(*codes)
+ return locale.get(default)
+
+ @property
+ def current_user(self):
+ """The authenticated user for this request.
+
+ Determined by either get_current_user, which you can override to
+ set the user based on, e.g., a cookie. If that method is not
+ overridden, this method always returns None.
+
+ We lazy-load the current user the first time this method is called
+ and cache the result after that.
+ """
+ if not hasattr(self, "_current_user"):
+ self._current_user = self.get_current_user()
+ return self._current_user
+
+ def get_current_user(self):
+ """Override to determine the current user from, e.g., a cookie."""
+ return None
+
+ def get_login_url(self):
+ """Override to customize the login URL based on the request.
+
+ By default, we use the 'login_url' application setting.
+ """
+ self.require_setting("login_url", "@tornado.web.authenticated")
+ return self.application.settings["login_url"]
+
+ @property
+ def xsrf_token(self):
+ """The XSRF-prevention token for the current user/session.
+
+ To prevent cross-site request forgery, we set an '_xsrf' cookie
+ and include the same '_xsrf' value as an argument with all POST
+ requests. If the two do not match, we reject the form submission
+ as a potential forgery.
+
+ See http://en.wikipedia.org/wiki/Cross-site_request_forgery
+ """
+ if not hasattr(self, "_xsrf_token"):
+ token = self.get_cookie("_xsrf")
+ if not token:
+ token = binascii.b2a_hex(uuid.uuid4().bytes)
+ expires_days = 30 if self.current_user else None
+ self.set_cookie("_xsrf", token, expires_days=expires_days)
+ self._xsrf_token = token
+ return self._xsrf_token
+
+ def check_xsrf_cookie(self):
+ """Verifies that the '_xsrf' cookie matches the '_xsrf' argument.
+
+ To prevent cross-site request forgery, we set an '_xsrf' cookie
+ and include the same '_xsrf' value as an argument with all POST
+ requests. If the two do not match, we reject the form submission
+ as a potential forgery.
+
+ See http://en.wikipedia.org/wiki/Cross-site_request_forgery
+ """
+ if self.request.headers.get("X-Requested-With") == "XMLHttpRequest":
+ return
+ token = self.get_argument("_xsrf", None)
+ if not token:
+ raise HTTPError(403, "'_xsrf' argument missing from POST")
+ if self.xsrf_token != token:
+ raise HTTPError(403, "XSRF cookie does not match POST argument")
+
+ def xsrf_form_html(self):
+ """An HTML <input/> element to be included with all POST forms.
+
+ It defines the _xsrf input value, which we check on all POST
+ requests to prevent cross-site request forgery. If you have set
+ the 'xsrf_cookies' application setting, you must include this
+ HTML within all of your HTML forms.
+
+ See check_xsrf_cookie() above for more information.
+ """
+ return '<input type="hidden" name="_xsrf" value="' + \
+ escape.xhtml_escape(self.xsrf_token) + '"/>'
+
+ def static_url(self, path):
+ """Returns a static URL for the given relative static file path.
+
+ This method requires you set the 'static_path' setting in your
+ application (which specifies the root directory of your static
+ files).
+
+ We append ?v=<signature> to the returned URL, which makes our
+ static file handler set an infinite expiration header on the
+ returned content. The signature is based on the content of the
+ file.
+
+ If this handler has a "include_host" attribute, we include the
+ full host for every static URL, including the "http://". Set
+ this attribute for handlers whose output needs non-relative static
+ path names.
+ """
+ self.require_setting("static_path", "static_url")
+ if not hasattr(RequestHandler, "_static_hashes"):
+ RequestHandler._static_hashes = {}
+ hashes = RequestHandler._static_hashes
+ if path not in hashes:
+ try:
+ f = open(os.path.join(
+ self.application.settings["static_path"], path))
+ hashes[path] = hashlib.md5(f.read()).hexdigest()
+ f.close()
+ except:
+ _log.error("Could not open static file %r", path)
+ hashes[path] = None
+ base = self.request.protocol + "://" + self.request.host \
+ if getattr(self, "include_host", False) else ""
+ static_url_prefix = self.settings.get('static_url_prefix', '/static/')
+ if hashes.get(path):
+ return base + static_url_prefix + path + "?v=" + hashes[path][:5]
+ else:
+ return base + static_url_prefix + path
+
+ def async_callback(self, callback, *args, **kwargs):
+ """Wrap callbacks with this if they are used on asynchronous requests.
+
+ Catches exceptions and properly finishes the request.
+ """
+ if callback is None:
+ return None
+ if args or kwargs:
+ callback = functools.partial(callback, *args, **kwargs)
+ def wrapper(*args, **kwargs):
+ try:
+ return callback(*args, **kwargs)
+ except Exception, e:
+ if self._headers_written:
+ _log.error("Exception after headers written",
+ exc_info=True)
+ else:
+ self._handle_request_exception(e)
+ return wrapper
+
+ def require_setting(self, name, feature="this feature"):
+ """Raises an exception if the given app setting is not defined."""
+ if not self.application.settings.get(name):
+ raise Exception("You must define the '%s' setting in your "
+ "application to use %s" % (name, feature))
+
+ def reverse_url(self, name, *args):
+ return self.application.reverse_url(name, *args)
+
+ def _execute(self, transforms, *args, **kwargs):
+ """Executes this request with the given output transforms."""
+ self._transforms = transforms
+ try:
+ if self.request.method not in self.SUPPORTED_METHODS:
+ raise HTTPError(405)
+ # If XSRF cookies are turned on, reject form submissions without
+ # the proper cookie
+ if self.request.method == "POST" and \
+ self.application.settings.get("xsrf_cookies"):
+ self.check_xsrf_cookie()
+ self.prepare()
+ if not self._finished:
+ getattr(self, self.request.method.lower())(*args, **kwargs)
+ if self._auto_finish and not self._finished:
+ self.finish()
+ except Exception, e:
+ self._handle_request_exception(e)
+
+ def _generate_headers(self):
+ lines = [self.request.version + " " + str(self._status_code) + " " +
+ httplib.responses[self._status_code]]
+ lines.extend(["%s: %s" % (n, v) for n, v in self._headers.iteritems()])
+ for cookie_dict in getattr(self, "_new_cookies", []):
+ for cookie in cookie_dict.values():
+ lines.append("Set-Cookie: " + cookie.OutputString(None))
+ return "\r\n".join(lines) + "\r\n\r\n"
+
+ def _log(self):
+ if self._status_code < 400:
+ log_method = _log.info
+ elif self._status_code < 500:
+ log_method = _log.warning
+ else:
+ log_method = _log.error
+ request_time = 1000.0 * self.request.request_time()
+ log_method("%d %s %.2fms", self._status_code,
+ self._request_summary(), request_time)
+
+ def _request_summary(self):
+ return self.request.method + " " + self.request.uri + " (" + \
+ self.request.remote_ip + ")"
+
+ def _handle_request_exception(self, e):
+ if isinstance(e, HTTPError):
+ if e.log_message:
+ format = "%d %s: " + e.log_message
+ args = [e.status_code, self._request_summary()] + list(e.args)
+ _log.warning(format, *args)
+ if e.status_code not in httplib.responses:
+ _log.error("Bad HTTP status code: %d", e.status_code)
+ self.send_error(500, exception=e)
+ else:
+ self.send_error(e.status_code, exception=e)
+ else:
+ _log.error("Uncaught exception %s\n%r", self._request_summary(),
+ self.request, exc_info=e)
+ self.send_error(500, exception=e)
+
+ def _ui_module(self, name, module):
+ def render(*args, **kwargs):
+ if not hasattr(self, "_active_modules"):
+ self._active_modules = {}
+ if name not in self._active_modules:
+ self._active_modules[name] = module(self)
+ rendered = self._active_modules[name].render(*args, **kwargs)
+ return rendered
+ return render
+
+ def _ui_method(self, method):
+ return lambda *args, **kwargs: method(self, *args, **kwargs)
+
+
+def asynchronous(method):
+ """Wrap request handler methods with this if they are asynchronous.
+
+ If this decorator is given, the response is not finished when the
+ method returns. It is up to the request handler to call self.finish()
+ to finish the HTTP request. Without this decorator, the request is
+ automatically finished when the get() or post() method returns.
+
+ class MyRequestHandler(web.RequestHandler):
+ @web.asynchronous
+ def get(self):
+ http = httpclient.AsyncHTTPClient()
+ http.fetch("http://friendfeed.com/", self._on_download)
+
+ def _on_download(self, response):
+ self.write("Downloaded!")
+ self.finish()
+
+ """
+ @functools.wraps(method)
+ def wrapper(self, *args, **kwargs):
+ if self.application._wsgi:
+ raise Exception("@asynchronous is not supported for WSGI apps")
+ self._auto_finish = False
+ return method(self, *args, **kwargs)
+ return wrapper
+
+
+def removeslash(method):
+ """Use this decorator to remove trailing slashes from the request path.
+
+ For example, a request to '/foo/' would redirect to '/foo' with this
+ decorator. Your request handler mapping should use a regular expression
+ like r'/foo/*' in conjunction with using the decorator.
+ """
+ @functools.wraps(method)
+ def wrapper(self, *args, **kwargs):
+ if self.request.path.endswith("/"):
+ if self.request.method == "GET":
+ uri = self.request.path.rstrip("/")
+ if self.request.query: uri += "?" + self.request.query
+ self.redirect(uri)
+ return
+ raise HTTPError(404)
+ return method(self, *args, **kwargs)
+ return wrapper
+
+
+def addslash(method):
+ """Use this decorator to add a missing trailing slash to the request path.
+
+ For example, a request to '/foo' would redirect to '/foo/' with this
+ decorator. Your request handler mapping should use a regular expression
+ like r'/foo/?' in conjunction with using the decorator.
+ """
+ @functools.wraps(method)
+ def wrapper(self, *args, **kwargs):
+ if not self.request.path.endswith("/"):
+ if self.request.method == "GET":
+ uri = self.request.path + "/"
+ if self.request.query: uri += "?" + self.request.query
+ self.redirect(uri)
+ return
+ raise HTTPError(404)
+ return method(self, *args, **kwargs)
+ return wrapper
+
+
+class Application(object):
+ """A collection of request handlers that make up a web application.
+
+ Instances of this class are callable and can be passed directly to
+ HTTPServer to serve the application:
+
+ application = web.Application([
+ (r"/", MainPageHandler),
+ ])
+ http_server = httpserver.HTTPServer(application)
+ http_server.listen(8080)
+ ioloop.IOLoop.instance().start()
+
+ The constructor for this class takes in a list of URLSpec objects
+ or (regexp, request_class) tuples. When we receive requests, we
+ iterate over the list in order and instantiate an instance of the
+ first request class whose regexp matches the request path.
+
+ Each tuple can contain an optional third element, which should be a
+ dictionary if it is present. That dictionary is passed as keyword
+ arguments to the contructor of the handler. This pattern is used
+ for the StaticFileHandler below:
+
+ application = web.Application([
+ (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
+ ])
+
+ We support virtual hosts with the add_handlers method, which takes in
+ a host regular expression as the first argument:
+
+ application.add_handlers(r"www\.myhost\.com", [
+ (r"/article/([0-9]+)", ArticleHandler),
+ ])
+
+ You can serve static files by sending the static_path setting as a
+ keyword argument. We will serve those files from the /static/ URI
+ (this is configurable with the static_url_prefix setting),
+ and we will serve /favicon.ico and /robots.txt from the same directory.
+ """
+ def __init__(self, handlers=None, default_host="", transforms=None,
+ wsgi=False, **settings):
+ if transforms is None:
+ self.transforms = []
+ if settings.get("gzip"):
+ self.transforms.append(GZipContentEncoding)
+ self.transforms.append(ChunkedTransferEncoding)
+ else:
+ self.transforms = transforms
+ self.handlers = []
+ self.named_handlers = {}
+ self.default_host = default_host
+ self.settings = settings
+ self.ui_modules = {}
+ self.ui_methods = {}
+ self._wsgi = wsgi
+ self._load_ui_modules(settings.get("ui_modules", {}))
+ self._load_ui_methods(settings.get("ui_methods", {}))
+ if self.settings.get("static_path"):
+ path = self.settings["static_path"]
+ handlers = list(handlers or [])
+ static_url_prefix = settings.get("static_url_prefix",
+ "/static/")
+ handlers = [
+ (re.escape(static_url_prefix) + r"(.*)", StaticFileHandler,
+ dict(path=path)),
+ (r"/(favicon\.ico)", StaticFileHandler, dict(path=path)),
+ (r"/(robots\.txt)", StaticFileHandler, dict(path=path)),
+ ] + handlers
+ if handlers: self.add_handlers(".*$", handlers)
+
+ # Automatically reload modified modules
+ if self.settings.get("debug") and not wsgi:
+ import autoreload
+ autoreload.start()
+
+ def add_handlers(self, host_pattern, host_handlers):
+ """Appends the given handlers to our handler list."""
+ if not host_pattern.endswith("$"):
+ host_pattern += "$"
+ handlers = []
+ # The handlers with the wildcard host_pattern are a special
+ # case - they're added in the constructor but should have lower
+ # precedence than the more-precise handlers added later.
+ # If a wildcard handler group exists, it should always be last
+ # in the list, so insert new groups just before it.
+ if self.handlers and self.handlers[-1][0].pattern == '.*$':
+ self.handlers.insert(-1, (re.compile(host_pattern), handlers))
+ else:
+ self.handlers.append((re.compile(host_pattern), handlers))
+
+ for spec in host_handlers:
+ if type(spec) is type(()):
+ assert len(spec) in (2, 3)
+ pattern = spec[0]
+ handler = spec[1]
+ if len(spec) == 3:
+ kwargs = spec[2]
+ else:
+ kwargs = {}
+ spec = URLSpec(pattern, handler, kwargs)
+ handlers.append(spec)
+ if spec.name:
+ if spec.name in self.named_handlers:
+ _log.warning(
+ "Multiple handlers named %s; replacing previous value",
+ spec.name)
+ self.named_handlers[spec.name] = spec
+
+ def add_transform(self, transform_class):
+ """Adds the given OutputTransform to our transform list."""
+ self.transforms.append(transform_class)
+
+ def _get_host_handlers(self, request):
+ host = request.host.lower().split(':')[0]
+ for pattern, handlers in self.handlers:
+ if pattern.match(host):
+ return handlers
+ # Look for default host if not behind load balancer (for debugging)
+ if "X-Real-Ip" not in request.headers:
+ for pattern, handlers in self.handlers:
+ if pattern.match(self.default_host):
+ return handlers
+ return None
+
+ def _load_ui_methods(self, methods):
+ if type(methods) is types.ModuleType:
+ self._load_ui_methods(dict((n, getattr(methods, n))
+ for n in dir(methods)))
+ elif isinstance(methods, list):
+ for m in list: self._load_ui_methods(m)
+ else:
+ for name, fn in methods.iteritems():
+ if not name.startswith("_") and hasattr(fn, "__call__") \
+ and name[0].lower() == name[0]:
+ self.ui_methods[name] = fn
+
+ def _load_ui_modules(self, modules):
+ if type(modules) is types.ModuleType:
+ self._load_ui_modules(dict((n, getattr(modules, n))
+ for n in dir(modules)))
+ elif isinstance(modules, list):
+ for m in list: self._load_ui_modules(m)
+ else:
+ assert isinstance(modules, dict)
+ for name, cls in modules.iteritems():
+ try:
+ if issubclass(cls, UIModule):
+ self.ui_modules[name] = cls
+ except TypeError:
+ pass
+
+ def __call__(self, request):
+ """Called by HTTPServer to execute the request."""
+ transforms = [t(request) for t in self.transforms]
+ handler = None
+ args = []
+ kwargs = {}
+ handlers = self._get_host_handlers(request)
+ if not handlers:
+ handler = RedirectHandler(
+ request, "http://" + self.default_host + "/")
+ else:
+ for spec in handlers:
+ match = spec.regex.match(request.path)
+ if match:
+ handler = spec.handler_class(self, request, **spec.kwargs)
+ # Pass matched groups to the handler. Since
+ # match.groups() includes both named and unnamed groups,
+ # we want to use either groups or groupdict but not both.
+ kwargs = match.groupdict()
+ if kwargs:
+ args = []
+ else:
+ args = match.groups()
+ break
+ if not handler:
+ handler = ErrorHandler(self, request, 404)
+
+ # In debug mode, re-compile templates and reload static files on every
+ # request so you don't need to restart to see changes
+ if self.settings.get("debug"):
+ if getattr(RequestHandler, "_templates", None):
+ map(lambda loader: loader.reset(),
+ RequestHandler._templates.values())
+ RequestHandler._static_hashes = {}
+
+ handler._execute(transforms, *args, **kwargs)
+ return handler
+
+ def reverse_url(self, name, *args):
+ """Returns a URL path for handler named `name`
+
+ The handler must be added to the application as a named URLSpec
+ """
+ if name in self.named_handlers:
+ return self.named_handlers[name].reverse(*args)
+ raise KeyError("%s not found in named urls" % name)
+
+
+class HTTPError(Exception):
+ """An exception that will turn into an HTTP error response."""
+ def __init__(self, status_code, log_message=None, *args):
+ self.status_code = status_code
+ self.log_message = log_message
+ self.args = args
+
+ def __str__(self):
+ message = "HTTP %d: %s" % (
+ self.status_code, httplib.responses[self.status_code])
+ if self.log_message:
+ return message + " (" + (self.log_message % self.args) + ")"
+ else:
+ return message
+
+
+class ErrorHandler(RequestHandler):
+ """Generates an error response with status_code for all requests."""
+ def __init__(self, application, request, status_code):
+ RequestHandler.__init__(self, application, request)
+ self.set_status(status_code)
+
+ def prepare(self):
+ raise HTTPError(self._status_code)
+
+
+class RedirectHandler(RequestHandler):
+ """Redirects the client to the given URL for all GET requests.
+
+ You should provide the keyword argument "url" to the handler, e.g.:
+
+ application = web.Application([
+ (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
+ ])
+ """
+ def __init__(self, application, request, url, permanent=True):
+ RequestHandler.__init__(self, application, request)
+ self._url = url
+ self._permanent = permanent
+
+ def get(self):
+ self.redirect(self._url, permanent=self._permanent)
+
+
+class StaticFileHandler(RequestHandler):
+ """A simple handler that can serve static content from a directory.
+
+ To map a path to this handler for a static data directory /var/www,
+ you would add a line to your application like:
+
+ application = web.Application([
+ (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
+ ])
+
+ The local root directory of the content should be passed as the "path"
+ argument to the handler.
+
+ To support aggressive browser caching, if the argument "v" is given
+ with the path, we set an infinite HTTP expiration header. So, if you
+ want browsers to cache a file indefinitely, send them to, e.g.,
+ /static/images/myimage.png?v=xxx.
+ """
+ def __init__(self, application, request, path):
+ RequestHandler.__init__(self, application, request)
+ self.root = os.path.abspath(path) + os.path.sep
+
+ def head(self, path):
+ self.get(path, include_body=False)
+
+ def get(self, path, include_body=True):
+ abspath = os.path.abspath(os.path.join(self.root, path))
+ if not abspath.startswith(self.root):
+ raise HTTPError(403, "%s is not in root static directory", path)
+ if not os.path.exists(abspath):
+ raise HTTPError(404)
+ if not os.path.isfile(abspath):
+ raise HTTPError(403, "%s is not a file", path)
+
+ stat_result = os.stat(abspath)
+ modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME])
+
+ self.set_header("Last-Modified", modified)
+ if "v" in self.request.arguments:
+ self.set_header("Expires", datetime.datetime.utcnow() + \
+ datetime.timedelta(days=365*10))
+ self.set_header("Cache-Control", "max-age=" + str(86400*365*10))
+ else:
+ self.set_header("Cache-Control", "public")
+ mime_type, encoding = mimetypes.guess_type(abspath)
+ if mime_type:
+ self.set_header("Content-Type", mime_type)
+
+ # Check the If-Modified-Since, and don't send the result if the
+ # content has not been modified
+ ims_value = self.request.headers.get("If-Modified-Since")
+ if ims_value is not None:
+ date_tuple = email.utils.parsedate(ims_value)
+ if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple))
+ if if_since >= modified:
+ self.set_status(304)
+ return
+
+ if not include_body:
+ return
+ self.set_header("Content-Length", stat_result[stat.ST_SIZE])
+ file = open(abspath, "rb")
+ try:
+ self.write(file.read())
+ finally:
+ file.close()
+
+
+class FallbackHandler(RequestHandler):
+ """A RequestHandler that wraps another HTTP server callback.
+
+ The fallback is a callable object that accepts an HTTPRequest,
+ such as an Application or tornado.wsgi.WSGIContainer. This is most
+ useful to use both tornado RequestHandlers and WSGI in the same server.
+ Typical usage:
+ wsgi_app = tornado.wsgi.WSGIContainer(
+ django.core.handlers.wsgi.WSGIHandler())
+ application = tornado.web.Application([
+ (r"/foo", FooHandler),
+ (r".*", FallbackHandler, dict(fallback=wsgi_app),
+ ])
+ """
+ def __init__(self, app, request, fallback):
+ RequestHandler.__init__(self, app, request)
+ self.fallback = fallback
+
+ def prepare(self):
+ self.fallback(self.request)
+ self._finished = True
+
+
+class OutputTransform(object):
+ """A transform modifies the result of an HTTP request (e.g., GZip encoding)
+
+ A new transform instance is created for every request. See the
+ ChunkedTransferEncoding example below if you want to implement a
+ new Transform.
+ """
+ def __init__(self, request):
+ pass
+
+ def transform_first_chunk(self, headers, chunk, finishing):
+ return headers, chunk
+
+ def transform_chunk(self, chunk, finishing):
+ return chunk
+
+
+class GZipContentEncoding(OutputTransform):
+ """Applies the gzip content encoding to the response.
+
+ See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
+ """
+ CONTENT_TYPES = set([
+ "text/plain", "text/html", "text/css", "text/xml",
+ "application/x-javascript", "application/xml", "application/atom+xml",
+ "text/javascript", "application/json", "application/xhtml+xml"])
+ MIN_LENGTH = 5
+
+ def __init__(self, request):
+ self._gzipping = request.supports_http_1_1() and \
+ "gzip" in request.headers.get("Accept-Encoding", "")
+
+ def transform_first_chunk(self, headers, chunk, finishing):
+ if self._gzipping:
+ ctype = headers.get("Content-Type", "").split(";")[0]
+ self._gzipping = (ctype in self.CONTENT_TYPES) and \
+ (not finishing or len(chunk) >= self.MIN_LENGTH) and \
+ (finishing or "Content-Length" not in headers) and \
+ ("Content-Encoding" not in headers)
+ if self._gzipping:
+ headers["Content-Encoding"] = "gzip"
+ self._gzip_value = cStringIO.StringIO()
+ self._gzip_file = gzip.GzipFile(mode="w", fileobj=self._gzip_value)
+ self._gzip_pos = 0
+ chunk = self.transform_chunk(chunk, finishing)
+ if "Content-Length" in headers:
+ headers["Content-Length"] = str(len(chunk))
+ return headers, chunk
+
+ def transform_chunk(self, chunk, finishing):
+ if self._gzipping:
+ self._gzip_file.write(chunk)
+ if finishing:
+ self._gzip_file.close()
+ else:
+ self._gzip_file.flush()
+ chunk = self._gzip_value.getvalue()
+ if self._gzip_pos > 0:
+ chunk = chunk[self._gzip_pos:]
+ self._gzip_pos += len(chunk)
+ return chunk
+
+
+class ChunkedTransferEncoding(OutputTransform):
+ """Applies the chunked transfer encoding to the response.
+
+ See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
+ """
+ def __init__(self, request):
+ self._chunking = request.supports_http_1_1()
+
+ def transform_first_chunk(self, headers, chunk, finishing):
+ if self._chunking:
+ # No need to chunk the output if a Content-Length is specified
+ if "Content-Length" in headers or "Transfer-Encoding" in headers:
+ self._chunking = False
+ else:
+ headers["Transfer-Encoding"] = "chunked"
+ chunk = self.transform_chunk(chunk, finishing)
+ return headers, chunk
+
+ def transform_chunk(self, block, finishing):
+ if self._chunking:
+ # Don't write out empty chunks because that means END-OF-STREAM
+ # with chunked encoding
+ if block:
+ block = ("%x" % len(block)) + "\r\n" + block + "\r\n"
+ if finishing:
+ block += "0\r\n\r\n"
+ return block
+
+
+def authenticated(method):
+ """Decorate methods with this to require that the user be logged in."""
+ @functools.wraps(method)
+ def wrapper(self, *args, **kwargs):
+ if not self.current_user:
+ if self.request.method == "GET":
+ url = self.get_login_url()
+ if "?" not in url:
+ url += "?" + urllib.urlencode(dict(next=self.request.uri))
+ self.redirect(url)
+ return
+ raise HTTPError(403)
+ return method(self, *args, **kwargs)
+ return wrapper
+
+
+class UIModule(object):
+ """A UI re-usable, modular unit on a page.
+
+ UI modules often execute additional queries, and they can include
+ additional CSS and JavaScript that will be included in the output
+ page, which is automatically inserted on page render.
+ """
+ def __init__(self, handler):
+ self.handler = handler
+ self.request = handler.request
+ self.ui = handler.ui
+ self.current_user = handler.current_user
+ self.locale = handler.locale
+
+ def render(self, *args, **kwargs):
+ raise NotImplementedError()
+
+ def embedded_javascript(self):
+ """Returns a JavaScript string that will be embedded in the page."""
+ return None
+
+ def javascript_files(self):
+ """Returns a list of JavaScript files required by this module."""
+ return None
+
+ def embedded_css(self):
+ """Returns a CSS string that will be embedded in the page."""
+ return None
+
+ def css_files(self):
+ """Returns a list of JavaScript files required by this module."""
+ return None
+
+ def html_head(self):
+ """Returns a CSS string that will be put in the <head/> element"""
+ return None
+
+ def html_body(self):
+ """Returns an HTML string that will be put in the <body/> element"""
+ return None
+
+ def render_string(self, path, **kwargs):
+ return self.handler.render_string(path, **kwargs)
+
+class URLSpec(object):
+ """Specifies mappings between URLs and handlers."""
+ def __init__(self, pattern, handler_class, kwargs={}, name=None):
+ """Creates a URLSpec.
+
+ Parameters:
+ pattern: Regular expression to be matched. Any groups in the regex
+ will be passed in to the handler's get/post/etc methods as
+ arguments.
+ handler_class: RequestHandler subclass to be invoked.
+ kwargs (optional): A dictionary of additional arguments to be passed
+ to the handler's constructor.
+ name (optional): A name for this handler. Used by
+ Application.reverse_url.
+ """
+ if not pattern.endswith('$'):
+ pattern += '$'
+ self.regex = re.compile(pattern)
+ self.handler_class = handler_class
+ self.kwargs = kwargs
+ self.name = name
+ self._path, self._group_count = self._find_groups()
+
+ def _find_groups(self):
+ """Returns a tuple (reverse string, group count) for a url.
+
+ For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method
+ would return ('/%s/%s/', 2).
+ """
+ pattern = self.regex.pattern
+ if pattern.startswith('^'):
+ pattern = pattern[1:]
+ if pattern.endswith('$'):
+ pattern = pattern[:-1]
+
+ if self.regex.groups != pattern.count('('):
+ # The pattern is too complicated for our simplistic matching,
+ # so we can't support reversing it.
+ return (None, None)
+
+ pieces = []
+ for fragment in pattern.split('('):
+ if ')' in fragment:
+ paren_loc = fragment.index(')')
+ if paren_loc >= 0:
+ pieces.append('%s' + fragment[paren_loc + 1:])
+ else:
+ pieces.append(fragment)
+
+ return (''.join(pieces), self.regex.groups)
+
+ def reverse(self, *args):
+ assert self._path is not None, \
+ "Cannot reverse url regex " + self.regex.pattern
+ assert len(args) == self._group_count, "required number of arguments "\
+ "not found"
+ if not len(args):
+ return self._path
+ return self._path % tuple([str(a) for a in args])
+
+url = URLSpec
+
+def _utf8(s):
+ if isinstance(s, unicode):
+ return s.encode("utf-8")
+ assert isinstance(s, str)
+ return s
+
+
+def _unicode(s):
+ if isinstance(s, str):
+ try:
+ return s.decode("utf-8")
+ except UnicodeDecodeError:
+ raise HTTPError(400, "Non-utf8 argument")
+ assert isinstance(s, unicode)
+ return s
+
+
+def _time_independent_equals(a, b):
+ if len(a) != len(b):
+ return False
+ result = 0
+ for x, y in zip(a, b):
+ result |= ord(x) ^ ord(y)
+ return result == 0
+
+
+class _O(dict):
+ """Makes a dictionary behave like an object."""
+ def __getattr__(self, name):
+ try:
+ return self[name]
+ except KeyError:
+ raise AttributeError(name)
+
+ def __setattr__(self, name, value):
+ self[name] = value
diff --git a/vendor/tornado/tornado/websocket.py b/vendor/tornado/tornado/websocket.py
new file mode 100644
index 0000000000..38a58012cc
--- /dev/null
+++ b/vendor/tornado/tornado/websocket.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import functools
+import logging
+import tornado.escape
+import tornado.web
+
+_log = logging.getLogger('tornado.websocket')
+
+class WebSocketHandler(tornado.web.RequestHandler):
+ """A request handler for HTML 5 Web Sockets.
+
+ See http://www.w3.org/TR/2009/WD-websockets-20091222/ for details on the
+ JavaScript interface. We implement the protocol as specified at
+ http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-55.
+
+ Here is an example Web Socket handler that echos back all received messages
+ back to the client:
+
+ class EchoWebSocket(websocket.WebSocketHandler):
+ def open(self):
+ self.receive_message(self.on_message)
+
+ def on_message(self, message):
+ self.write_message(u"You said: " + message)
+
+ Web Sockets are not standard HTTP connections. The "handshake" is HTTP,
+ but after the handshake, the protocol is message-based. Consequently,
+ most of the Tornado HTTP facilities are not available in handlers of this
+ type. The only communication methods available to you are send_message()
+ and receive_message(). Likewise, your request handler class should
+ implement open() method rather than get() or post().
+
+ If you map the handler above to "/websocket" in your application, you can
+ invoke it in JavaScript with:
+
+ var ws = new WebSocket("ws://localhost:8888/websocket");
+ ws.onopen = function() {
+ ws.send("Hello, world");
+ };
+ ws.onmessage = function (evt) {
+ alert(evt.data);
+ };
+
+ This script pops up an alert box that says "You said: Hello, world".
+ """
+ def __init__(self, application, request):
+ tornado.web.RequestHandler.__init__(self, application, request)
+ self.stream = request.connection.stream
+
+ def _execute(self, transforms, *args, **kwargs):
+ if self.request.headers.get("Upgrade") != "WebSocket" or \
+ self.request.headers.get("Connection") != "Upgrade" or \
+ not self.request.headers.get("Origin"):
+ message = "Expected WebSocket headers"
+ self.stream.write(
+ "HTTP/1.1 403 Forbidden\r\nContent-Length: " +
+ str(len(message)) + "\r\n\r\n" + message)
+ return
+ self.stream.write(
+ "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Server: TornadoServer/0.1\r\n"
+ "WebSocket-Origin: " + self.request.headers["Origin"] + "\r\n"
+ "WebSocket-Location: ws://" + self.request.host +
+ self.request.path + "\r\n\r\n")
+ self.async_callback(self.open)(*args, **kwargs)
+
+ def write_message(self, message):
+ """Sends the given message to the client of this Web Socket."""
+ if isinstance(message, dict):
+ message = tornado.escape.json_encode(message)
+ if isinstance(message, unicode):
+ message = message.encode("utf-8")
+ assert isinstance(message, str)
+ self.stream.write("\x00" + message + "\xff")
+
+ def receive_message(self, callback):
+ """Calls callback when the browser calls send() on this Web Socket."""
+ callback = self.async_callback(callback)
+ self.stream.read_bytes(
+ 1, functools.partial(self._on_frame_type, callback))
+
+ def close(self):
+ """Closes this Web Socket.
+
+ The browser will receive the onclose event for the open web socket
+ when this method is called.
+ """
+ self.stream.close()
+
+ def async_callback(self, callback, *args, **kwargs):
+ """Wrap callbacks with this if they are used on asynchronous requests.
+
+ Catches exceptions properly and closes this Web Socket if an exception
+ is uncaught.
+ """
+ if args or kwargs:
+ callback = functools.partial(callback, *args, **kwargs)
+ def wrapper(*args, **kwargs):
+ try:
+ return callback(*args, **kwargs)
+ except Exception, e:
+ _log.error("Uncaught exception in %s",
+ self.request.path, exc_info=True)
+ self.stream.close()
+ return wrapper
+
+ def _on_frame_type(self, callback, byte):
+ if ord(byte) & 0x80 == 0x80:
+ raise Exception("Length-encoded format not yet supported")
+ self.stream.read_until(
+ "\xff", functools.partial(self._on_end_delimiter, callback))
+
+ def _on_end_delimiter(self, callback, frame):
+ callback(frame[:-1].decode("utf-8", "replace"))
+
+ def _not_supported(self, *args, **kwargs):
+ raise Exception("Method not supported for Web Sockets")
+
+for method in ["write", "redirect", "set_header", "send_error", "set_cookie",
+ "set_status", "flush", "finish"]:
+ setattr(WebSocketHandler, method, WebSocketHandler._not_supported)
diff --git a/vendor/tornado/tornado/win32_support.py b/vendor/tornado/tornado/win32_support.py
new file mode 100644
index 0000000000..f3efa8e892
--- /dev/null
+++ b/vendor/tornado/tornado/win32_support.py
@@ -0,0 +1,123 @@
+# NOTE: win32 support is currently experimental, and not recommended
+# for production use.
+
+import ctypes
+import ctypes.wintypes
+import os
+import socket
+import errno
+
+
+# See: http://msdn.microsoft.com/en-us/library/ms738573(VS.85).aspx
+ioctlsocket = ctypes.windll.ws2_32.ioctlsocket
+ioctlsocket.argtypes = (ctypes.wintypes.HANDLE, ctypes.wintypes.LONG, ctypes.wintypes.ULONG)
+ioctlsocket.restype = ctypes.c_int
+
+# See: http://msdn.microsoft.com/en-us/library/ms724935(VS.85).aspx
+SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
+SetHandleInformation.argtypes = (ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD)
+SetHandleInformation.restype = ctypes.wintypes.BOOL
+
+HANDLE_FLAG_INHERIT = 0x00000001
+
+
+F_GETFD = 1
+F_SETFD = 2
+F_GETFL = 3
+F_SETFL = 4
+
+FD_CLOEXEC = 1
+
+os.O_NONBLOCK = 2048
+
+FIONBIO = 126
+
+
+def fcntl(fd, op, arg=0):
+ if op == F_GETFD or op == F_GETFL:
+ return 0
+ elif op == F_SETFD:
+ # Check that the flag is CLOEXEC and translate
+ if arg == FD_CLOEXEC:
+ success = SetHandleInformation(fd, HANDLE_FLAG_INHERIT, arg)
+ if not success:
+ raise ctypes.GetLastError()
+ else:
+ raise ValueError("Unsupported arg")
+ #elif op == F_SETFL:
+ ## Check that the flag is NONBLOCK and translate
+ #if arg == os.O_NONBLOCK:
+ ##pass
+ #result = ioctlsocket(fd, FIONBIO, 1)
+ #if result != 0:
+ #raise ctypes.GetLastError()
+ #else:
+ #raise ValueError("Unsupported arg")
+ else:
+ raise ValueError("Unsupported op")
+
+
+class Pipe(object):
+ """Create an OS independent asynchronous pipe"""
+ def __init__(self):
+ # Based on Zope async.py: http://svn.zope.org/zc.ngi/trunk/src/zc/ngi/async.py
+
+ self.writer = socket.socket()
+ # Disable buffering -- pulling the trigger sends 1 byte,
+ # and we want that sent immediately, to wake up ASAP.
+ self.writer.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+
+ count = 0
+ while 1:
+ count += 1
+ # Bind to a local port; for efficiency, let the OS pick
+ # a free port for us.
+ # Unfortunately, stress tests showed that we may not
+ # be able to connect to that port ("Address already in
+ # use") despite that the OS picked it. This appears
+ # to be a race bug in the Windows socket implementation.
+ # So we loop until a connect() succeeds (almost always
+ # on the first try). See the long thread at
+ # http://mail.zope.org/pipermail/zope/2005-July/160433.html
+ # for hideous details.
+ a = socket.socket()
+ a.bind(("127.0.0.1", 0))
+ connect_address = a.getsockname() # assigned (host, port) pair
+ a.listen(1)
+ try:
+ self.writer.connect(connect_address)
+ break # success
+ except socket.error, detail:
+ if detail[0] != errno.WSAEADDRINUSE:
+ # "Address already in use" is the only error
+ # I've seen on two WinXP Pro SP2 boxes, under
+ # Pythons 2.3.5 and 2.4.1.
+ raise
+ # (10048, 'Address already in use')
+ # assert count <= 2 # never triggered in Tim's tests
+ if count >= 10: # I've never seen it go above 2
+ a.close()
+ self.writer.close()
+ raise socket.error("Cannot bind trigger!")
+ # Close `a` and try again. Note: I originally put a short
+ # sleep() here, but it didn't appear to help or hurt.
+ a.close()
+
+ self.reader, addr = a.accept()
+ self.reader.setblocking(0)
+ self.writer.setblocking(0)
+ a.close()
+ self.reader_fd = self.reader.fileno()
+
+ def read(self):
+ """Emulate a file descriptors read method"""
+ try:
+ return self.reader.recv(1)
+ except socket.error, ex:
+ if ex.args[0] == errno.EWOULDBLOCK:
+ raise IOError
+ raise
+
+ def write(self, data):
+ """Emulate a file descriptors write method"""
+ return self.writer.send(data)
diff --git a/vendor/tornado/tornado/wsgi.py b/vendor/tornado/tornado/wsgi.py
new file mode 100644
index 0000000000..69fa0988eb
--- /dev/null
+++ b/vendor/tornado/tornado/wsgi.py
@@ -0,0 +1,311 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""WSGI support for the Tornado web framework.
+
+We export WSGIApplication, which is very similar to web.Application, except
+no asynchronous methods are supported (since WSGI does not support
+non-blocking requests properly). If you call self.flush() or other
+asynchronous methods in your request handlers running in a WSGIApplication,
+we throw an exception.
+
+Example usage:
+
+ import tornado.web
+ import tornado.wsgi
+ import wsgiref.simple_server
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.write("Hello, world")
+
+ if __name__ == "__main__":
+ application = tornado.wsgi.WSGIApplication([
+ (r"/", MainHandler),
+ ])
+ server = wsgiref.simple_server.make_server('', 8888, application)
+ server.serve_forever()
+
+See the 'appengine' demo for an example of using this module to run
+a Tornado app on Google AppEngine.
+
+Since no asynchronous methods are available for WSGI applications, the
+httpclient and auth modules are both not available for WSGI applications.
+
+We also export WSGIContainer, which lets you run other WSGI-compatible
+frameworks on the Tornado HTTP server and I/O loop. See WSGIContainer for
+details and documentation.
+"""
+
+import cgi
+import cStringIO
+import escape
+import httplib
+import logging
+import sys
+import time
+import urllib
+import web
+
+_log = logging.getLogger('tornado.wsgi')
+
+class WSGIApplication(web.Application):
+ """A WSGI-equivalent of web.Application.
+
+ We support the same interface, but handlers running in a WSGIApplication
+ do not support flush() or asynchronous methods.
+ """
+ def __init__(self, handlers=None, default_host="", **settings):
+ web.Application.__init__(self, handlers, default_host, transforms=[],
+ wsgi=True, **settings)
+
+ def __call__(self, environ, start_response):
+ handler = web.Application.__call__(self, HTTPRequest(environ))
+ assert handler._finished
+ status = str(handler._status_code) + " " + \
+ httplib.responses[handler._status_code]
+ headers = handler._headers.items()
+ for cookie_dict in getattr(handler, "_new_cookies", []):
+ for cookie in cookie_dict.values():
+ headers.append(("Set-Cookie", cookie.OutputString(None)))
+ start_response(status, headers)
+ return handler._write_buffer
+
+
+class HTTPRequest(object):
+ """Mimics httpserver.HTTPRequest for WSGI applications."""
+ def __init__(self, environ):
+ """Parses the given WSGI environ to construct the request."""
+ self.method = environ["REQUEST_METHOD"]
+ self.path = urllib.quote(environ.get("SCRIPT_NAME", ""))
+ self.path += urllib.quote(environ.get("PATH_INFO", ""))
+ self.uri = self.path
+ self.arguments = {}
+ self.query = environ.get("QUERY_STRING", "")
+ if self.query:
+ self.uri += "?" + self.query
+ arguments = cgi.parse_qs(self.query)
+ for name, values in arguments.iteritems():
+ values = [v for v in values if v]
+ if values: self.arguments[name] = values
+ self.version = "HTTP/1.1"
+ self.headers = HTTPHeaders()
+ if environ.get("CONTENT_TYPE"):
+ self.headers["Content-Type"] = environ["CONTENT_TYPE"]
+ if environ.get("CONTENT_LENGTH"):
+ self.headers["Content-Length"] = int(environ["CONTENT_LENGTH"])
+ for key in environ:
+ if key.startswith("HTTP_"):
+ self.headers[key[5:].replace("_", "-")] = environ[key]
+ if self.headers.get("Content-Length"):
+ self.body = environ["wsgi.input"].read()
+ else:
+ self.body = ""
+ self.protocol = environ["wsgi.url_scheme"]
+ self.remote_ip = environ.get("REMOTE_ADDR", "")
+ if environ.get("HTTP_HOST"):
+ self.host = environ["HTTP_HOST"]
+ else:
+ self.host = environ["SERVER_NAME"]
+
+ # Parse request body
+ self.files = {}
+ content_type = self.headers.get("Content-Type", "")
+ if content_type.startswith("application/x-www-form-urlencoded"):
+ for name, values in cgi.parse_qs(self.body).iteritems():
+ self.arguments.setdefault(name, []).extend(values)
+ elif content_type.startswith("multipart/form-data"):
+ boundary = content_type[30:]
+ if boundary: self._parse_mime_body(boundary)
+
+ self._start_time = time.time()
+ self._finish_time = None
+
+ def supports_http_1_1(self):
+ """Returns True if this request supports HTTP/1.1 semantics"""
+ return self.version == "HTTP/1.1"
+
+ def full_url(self):
+ """Reconstructs the full URL for this request."""
+ return self.protocol + "://" + self.host + self.uri
+
+ def request_time(self):
+ """Returns the amount of time it took for this request to execute."""
+ if self._finish_time is None:
+ return time.time() - self._start_time
+ else:
+ return self._finish_time - self._start_time
+
+ def _parse_mime_body(self, boundary):
+ if self.body.endswith("\r\n"):
+ footer_length = len(boundary) + 6
+ else:
+ footer_length = len(boundary) + 4
+ parts = self.body[:-footer_length].split("--" + boundary + "\r\n")
+ for part in parts:
+ if not part: continue
+ eoh = part.find("\r\n\r\n")
+ if eoh == -1:
+ _log.warning("multipart/form-data missing headers")
+ continue
+ headers = HTTPHeaders.parse(part[:eoh])
+ name_header = headers.get("Content-Disposition", "")
+ if not name_header.startswith("form-data;") or \
+ not part.endswith("\r\n"):
+ _log.warning("Invalid multipart/form-data")
+ continue
+ value = part[eoh + 4:-2]
+ name_values = {}
+ for name_part in name_header[10:].split(";"):
+ name, name_value = name_part.strip().split("=", 1)
+ name_values[name] = name_value.strip('"').decode("utf-8")
+ if not name_values.get("name"):
+ _log.warning("multipart/form-data value missing name")
+ continue
+ name = name_values["name"]
+ if name_values.get("filename"):
+ ctype = headers.get("Content-Type", "application/unknown")
+ self.files.setdefault(name, []).append(dict(
+ filename=name_values["filename"], body=value,
+ content_type=ctype))
+ else:
+ self.arguments.setdefault(name, []).append(value)
+
+
+class WSGIContainer(object):
+ """Makes a WSGI-compatible function runnable on Tornado's HTTP server.
+
+ Wrap a WSGI function in a WSGIContainer and pass it to HTTPServer to
+ run it. For example:
+
+ def simple_app(environ, start_response):
+ status = "200 OK"
+ response_headers = [("Content-type", "text/plain")]
+ start_response(status, response_headers)
+ return ["Hello world!\n"]
+
+ container = tornado.wsgi.WSGIContainer(simple_app)
+ http_server = tornado.httpserver.HTTPServer(container)
+ http_server.listen(8888)
+ tornado.ioloop.IOLoop.instance().start()
+
+ This class is intended to let other frameworks (Django, web.py, etc)
+ run on the Tornado HTTP server and I/O loop. It has not yet been
+ thoroughly tested in production.
+ """
+ def __init__(self, wsgi_application):
+ self.wsgi_application = wsgi_application
+
+ def __call__(self, request):
+ data = {}
+ response = []
+ def start_response(status, response_headers, exc_info=None):
+ data["status"] = status
+ data["headers"] = response_headers
+ return response.append
+ response.extend(self.wsgi_application(
+ WSGIContainer.environ(request), start_response))
+ body = "".join(response)
+ if hasattr(response, "close"):
+ response.close()
+ if not data: raise Exception("WSGI app did not call start_response")
+
+ status_code = int(data["status"].split()[0])
+ headers = data["headers"]
+ header_set = set(k.lower() for (k,v) in headers)
+ body = escape.utf8(body)
+ if "content-length" not in header_set:
+ headers.append(("Content-Length", str(len(body))))
+ if "content-type" not in header_set:
+ headers.append(("Content-Type", "text/html; charset=UTF-8"))
+ if "server" not in header_set:
+ headers.append(("Server", "TornadoServer/0.1"))
+
+ parts = ["HTTP/1.1 " + data["status"] + "\r\n"]
+ for key, value in headers:
+ parts.append(escape.utf8(key) + ": " + escape.utf8(value) + "\r\n")
+ parts.append("\r\n")
+ parts.append(body)
+ request.write("".join(parts))
+ request.finish()
+ self._log(status_code, request)
+
+ @staticmethod
+ def environ(request):
+ hostport = request.host.split(":")
+ if len(hostport) == 2:
+ host = hostport[0]
+ port = int(hostport[1])
+ else:
+ host = request.host
+ port = 443 if request.protocol == "https" else 80
+ environ = {
+ "REQUEST_METHOD": request.method,
+ "SCRIPT_NAME": "",
+ "PATH_INFO": request.path,
+ "QUERY_STRING": request.query,
+ "REMOTE_ADDR": request.remote_ip,
+ "SERVER_NAME": host,
+ "SERVER_PORT": port,
+ "SERVER_PROTOCOL": request.version,
+ "wsgi.version": (1, 0),
+ "wsgi.url_scheme": request.protocol,
+ "wsgi.input": cStringIO.StringIO(request.body),
+ "wsgi.errors": sys.stderr,
+ "wsgi.multithread": False,
+ "wsgi.multiprocess": True,
+ "wsgi.run_once": False,
+ }
+ if "Content-Type" in request.headers:
+ environ["CONTENT_TYPE"] = request.headers["Content-Type"]
+ if "Content-Length" in request.headers:
+ environ["CONTENT_LENGTH"] = request.headers["Content-Length"]
+ for key, value in request.headers.iteritems():
+ environ["HTTP_" + key.replace("-", "_").upper()] = value
+ return environ
+
+ def _log(self, status_code, request):
+ if status_code < 400:
+ log_method = _log.info
+ elif status_code < 500:
+ log_method = _log.warning
+ else:
+ log_method = _log.error
+ request_time = 1000.0 * request.request_time()
+ summary = request.method + " " + request.uri + " (" + \
+ request.remote_ip + ")"
+ log_method("%d %s %.2fms", status_code, summary, request_time)
+
+
+class HTTPHeaders(dict):
+ """A dictionary that maintains Http-Header-Case for all keys."""
+ def __setitem__(self, name, value):
+ dict.__setitem__(self, self._normalize_name(name), value)
+
+ def __getitem__(self, name):
+ return dict.__getitem__(self, self._normalize_name(name))
+
+ def _normalize_name(self, name):
+ return "-".join([w.capitalize() for w in name.split("-")])
+
+ @classmethod
+ def parse(cls, headers_string):
+ headers = cls()
+ for line in headers_string.splitlines():
+ if line:
+ name, value = line.split(": ", 1)
+ headers[name] = value
+ return headers
diff --git a/vendor/tornado/website/app.yaml b/vendor/tornado/website/app.yaml
new file mode 100644
index 0000000000..8a1ff06648
--- /dev/null
+++ b/vendor/tornado/website/app.yaml
@@ -0,0 +1,15 @@
+application: python-tornado
+version: 1
+runtime: python
+api_version: 1
+
+handlers:
+- url: /static/
+ static_dir: static
+
+- url: /robots\.txt
+ static_files: static/robots.txt
+ upload: static/robots.txt
+
+- url: /.*
+ script: website.py
diff --git a/vendor/tornado/website/index.yaml b/vendor/tornado/website/index.yaml
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vendor/tornado/website/index.yaml
diff --git a/vendor/tornado/website/markdown/__init__.py b/vendor/tornado/website/markdown/__init__.py
new file mode 100644
index 0000000000..0d1c504979
--- /dev/null
+++ b/vendor/tornado/website/markdown/__init__.py
@@ -0,0 +1,603 @@
+"""
+Python Markdown
+===============
+
+Python Markdown converts Markdown to HTML and can be used as a library or
+called from the command line.
+
+## Basic usage as a module:
+
+ import markdown
+ md = Markdown()
+ html = md.convert(your_text_string)
+
+## Basic use from the command line:
+
+ python markdown.py source.txt > destination.html
+
+Run "python markdown.py --help" to see more options.
+
+## Extensions
+
+See <http://www.freewisdom.org/projects/python-markdown/> for more
+information and instructions on how to extend the functionality of
+Python Markdown. Read that before you try modifying this file.
+
+## Authors and License
+
+Started by [Manfred Stienstra](http://www.dwerg.net/). Continued and
+maintained by [Yuri Takhteyev](http://www.freewisdom.org), [Waylan
+Limberg](http://achinghead.com/) and [Artem Yunusov](http://blog.splyer.com).
+
+Contact: markdown@freewisdom.org
+
+Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later)
+Copyright 200? Django Software Foundation (OrderedDict implementation)
+Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
+Copyright 2004 Manfred Stienstra (the original version)
+
+License: BSD (see docs/LICENSE for details).
+"""
+
+version = "2.0"
+version_info = (2,0,0, "Final")
+
+import re
+import codecs
+import sys
+import warnings
+import logging
+from logging import DEBUG, INFO, WARN, ERROR, CRITICAL
+
+
+"""
+CONSTANTS
+=============================================================================
+"""
+
+"""
+Constants you might want to modify
+-----------------------------------------------------------------------------
+"""
+
+# default logging level for command-line use
+COMMAND_LINE_LOGGING_LEVEL = CRITICAL
+TAB_LENGTH = 4 # expand tabs to this many spaces
+ENABLE_ATTRIBUTES = True # @id = xyz -> <... id="xyz">
+SMART_EMPHASIS = True # this_or_that does not become this<i>or</i>that
+DEFAULT_OUTPUT_FORMAT = 'xhtml1' # xhtml or html4 output
+HTML_REMOVED_TEXT = "[HTML_REMOVED]" # text used instead of HTML in safe mode
+BLOCK_LEVEL_ELEMENTS = re.compile("p|div|h[1-6]|blockquote|pre|table|dl|ol|ul"
+ "|script|noscript|form|fieldset|iframe|math"
+ "|ins|del|hr|hr/|style|li|dt|dd|thead|tbody"
+ "|tr|th|td")
+DOC_TAG = "div" # Element used to wrap document - later removed
+
+# Placeholders
+STX = u'\u0002' # Use STX ("Start of text") for start-of-placeholder
+ETX = u'\u0003' # Use ETX ("End of text") for end-of-placeholder
+INLINE_PLACEHOLDER_PREFIX = STX+"klzzwxh:"
+INLINE_PLACEHOLDER = INLINE_PLACEHOLDER_PREFIX + "%s" + ETX
+AMP_SUBSTITUTE = STX+"amp"+ETX
+
+
+"""
+Constants you probably do not need to change
+-----------------------------------------------------------------------------
+"""
+
+RTL_BIDI_RANGES = ( (u'\u0590', u'\u07FF'),
+ # Hebrew (0590-05FF), Arabic (0600-06FF),
+ # Syriac (0700-074F), Arabic supplement (0750-077F),
+ # Thaana (0780-07BF), Nko (07C0-07FF).
+ (u'\u2D30', u'\u2D7F'), # Tifinagh
+ )
+
+
+"""
+AUXILIARY GLOBAL FUNCTIONS
+=============================================================================
+"""
+
+
+def message(level, text):
+ """ A wrapper method for logging debug messages. """
+ logger = logging.getLogger('MARKDOWN')
+ if logger.handlers:
+ # The logger is configured
+ logger.log(level, text)
+ if level > WARN:
+ sys.exit(0)
+ elif level > WARN:
+ raise MarkdownException, text
+ else:
+ warnings.warn(text, MarkdownWarning)
+
+
+def isBlockLevel(tag):
+ """Check if the tag is a block level HTML tag."""
+ return BLOCK_LEVEL_ELEMENTS.match(tag)
+
+"""
+MISC AUXILIARY CLASSES
+=============================================================================
+"""
+
+class AtomicString(unicode):
+ """A string which should not be further processed."""
+ pass
+
+
+class MarkdownException(Exception):
+ """ A Markdown Exception. """
+ pass
+
+
+class MarkdownWarning(Warning):
+ """ A Markdown Warning. """
+ pass
+
+
+"""
+OVERALL DESIGN
+=============================================================================
+
+Markdown processing takes place in four steps:
+
+1. A bunch of "preprocessors" munge the input text.
+2. BlockParser() parses the high-level structural elements of the
+ pre-processed text into an ElementTree.
+3. A bunch of "treeprocessors" are run against the ElementTree. One such
+ treeprocessor runs InlinePatterns against the ElementTree, detecting inline
+ markup.
+4. Some post-processors are run against the text after the ElementTree has
+ been serialized into text.
+5. The output is written to a string.
+
+Those steps are put together by the Markdown() class.
+
+"""
+
+import preprocessors
+import blockprocessors
+import treeprocessors
+import inlinepatterns
+import postprocessors
+import blockparser
+import etree_loader
+import odict
+
+# Extensions should use "markdown.etree" instead of "etree" (or do `from
+# markdown import etree`). Do not import it by yourself.
+
+etree = etree_loader.importETree()
+
+# Adds the ability to output html4
+import html4
+
+
+class Markdown:
+ """Convert Markdown to HTML."""
+
+ def __init__(self,
+ extensions=[],
+ extension_configs={},
+ safe_mode = False,
+ output_format=DEFAULT_OUTPUT_FORMAT):
+ """
+ Creates a new Markdown instance.
+
+ Keyword arguments:
+
+ * extensions: A list of extensions.
+ If they are of type string, the module mdx_name.py will be loaded.
+ If they are a subclass of markdown.Extension, they will be used
+ as-is.
+ * extension-configs: Configuration setting for extensions.
+ * safe_mode: Disallow raw html. One of "remove", "replace" or "escape".
+ * output_format: Format of output. Supported formats are:
+ * "xhtml1": Outputs XHTML 1.x. Default.
+ * "xhtml": Outputs latest supported version of XHTML (currently XHTML 1.1).
+ * "html4": Outputs HTML 4
+ * "html": Outputs latest supported version of HTML (currently HTML 4).
+ Note that it is suggested that the more specific formats ("xhtml1"
+ and "html4") be used as "xhtml" or "html" may change in the future
+ if it makes sense at that time.
+
+ """
+
+ self.safeMode = safe_mode
+ self.registeredExtensions = []
+ self.docType = ""
+ self.stripTopLevelTags = True
+
+ # Preprocessors
+ self.preprocessors = odict.OrderedDict()
+ self.preprocessors["html_block"] = \
+ preprocessors.HtmlBlockPreprocessor(self)
+ self.preprocessors["reference"] = \
+ preprocessors.ReferencePreprocessor(self)
+ # footnote preprocessor will be inserted with "<reference"
+
+ # Block processors - ran by the parser
+ self.parser = blockparser.BlockParser()
+ self.parser.blockprocessors['empty'] = \
+ blockprocessors.EmptyBlockProcessor(self.parser)
+ self.parser.blockprocessors['indent'] = \
+ blockprocessors.ListIndentProcessor(self.parser)
+ self.parser.blockprocessors['code'] = \
+ blockprocessors.CodeBlockProcessor(self.parser)
+ self.parser.blockprocessors['hashheader'] = \
+ blockprocessors.HashHeaderProcessor(self.parser)
+ self.parser.blockprocessors['setextheader'] = \
+ blockprocessors.SetextHeaderProcessor(self.parser)
+ self.parser.blockprocessors['hr'] = \
+ blockprocessors.HRProcessor(self.parser)
+ self.parser.blockprocessors['olist'] = \
+ blockprocessors.OListProcessor(self.parser)
+ self.parser.blockprocessors['ulist'] = \
+ blockprocessors.UListProcessor(self.parser)
+ self.parser.blockprocessors['quote'] = \
+ blockprocessors.BlockQuoteProcessor(self.parser)
+ self.parser.blockprocessors['paragraph'] = \
+ blockprocessors.ParagraphProcessor(self.parser)
+
+
+ #self.prePatterns = []
+
+ # Inline patterns - Run on the tree
+ self.inlinePatterns = odict.OrderedDict()
+ self.inlinePatterns["backtick"] = \
+ inlinepatterns.BacktickPattern(inlinepatterns.BACKTICK_RE)
+ self.inlinePatterns["escape"] = \
+ inlinepatterns.SimpleTextPattern(inlinepatterns.ESCAPE_RE)
+ self.inlinePatterns["reference"] = \
+ inlinepatterns.ReferencePattern(inlinepatterns.REFERENCE_RE, self)
+ self.inlinePatterns["link"] = \
+ inlinepatterns.LinkPattern(inlinepatterns.LINK_RE, self)
+ self.inlinePatterns["image_link"] = \
+ inlinepatterns.ImagePattern(inlinepatterns.IMAGE_LINK_RE, self)
+ self.inlinePatterns["image_reference"] = \
+ inlinepatterns.ImageReferencePattern(inlinepatterns.IMAGE_REFERENCE_RE, self)
+ self.inlinePatterns["autolink"] = \
+ inlinepatterns.AutolinkPattern(inlinepatterns.AUTOLINK_RE, self)
+ self.inlinePatterns["automail"] = \
+ inlinepatterns.AutomailPattern(inlinepatterns.AUTOMAIL_RE, self)
+ self.inlinePatterns["linebreak2"] = \
+ inlinepatterns.SubstituteTagPattern(inlinepatterns.LINE_BREAK_2_RE, 'br')
+ self.inlinePatterns["linebreak"] = \
+ inlinepatterns.SubstituteTagPattern(inlinepatterns.LINE_BREAK_RE, 'br')
+ self.inlinePatterns["html"] = \
+ inlinepatterns.HtmlPattern(inlinepatterns.HTML_RE, self)
+ self.inlinePatterns["entity"] = \
+ inlinepatterns.HtmlPattern(inlinepatterns.ENTITY_RE, self)
+ self.inlinePatterns["not_strong"] = \
+ inlinepatterns.SimpleTextPattern(inlinepatterns.NOT_STRONG_RE)
+ self.inlinePatterns["strong_em"] = \
+ inlinepatterns.DoubleTagPattern(inlinepatterns.STRONG_EM_RE, 'strong,em')
+ self.inlinePatterns["strong"] = \
+ inlinepatterns.SimpleTagPattern(inlinepatterns.STRONG_RE, 'strong')
+ self.inlinePatterns["emphasis"] = \
+ inlinepatterns.SimpleTagPattern(inlinepatterns.EMPHASIS_RE, 'em')
+ self.inlinePatterns["emphasis2"] = \
+ inlinepatterns.SimpleTagPattern(inlinepatterns.EMPHASIS_2_RE, 'em')
+ # The order of the handlers matters!!!
+
+
+ # Tree processors - run once we have a basic parse.
+ self.treeprocessors = odict.OrderedDict()
+ self.treeprocessors["inline"] = treeprocessors.InlineProcessor(self)
+ self.treeprocessors["prettify"] = \
+ treeprocessors.PrettifyTreeprocessor(self)
+
+ # Postprocessors - finishing touches.
+ self.postprocessors = odict.OrderedDict()
+ self.postprocessors["raw_html"] = \
+ postprocessors.RawHtmlPostprocessor(self)
+ self.postprocessors["amp_substitute"] = \
+ postprocessors.AndSubstitutePostprocessor()
+ # footnote postprocessor will be inserted with ">amp_substitute"
+
+ # Map format keys to serializers
+ self.output_formats = {
+ 'html' : html4.to_html_string,
+ 'html4' : html4.to_html_string,
+ 'xhtml' : etree.tostring,
+ 'xhtml1': etree.tostring,
+ }
+
+ self.references = {}
+ self.htmlStash = preprocessors.HtmlStash()
+ self.registerExtensions(extensions = extensions,
+ configs = extension_configs)
+ self.set_output_format(output_format)
+ self.reset()
+
+ def registerExtensions(self, extensions, configs):
+ """
+ Register extensions with this instance of Markdown.
+
+ Keyword aurguments:
+
+ * extensions: A list of extensions, which can either
+ be strings or objects. See the docstring on Markdown.
+ * configs: A dictionary mapping module names to config options.
+
+ """
+ for ext in extensions:
+ if isinstance(ext, basestring):
+ ext = load_extension(ext, configs.get(ext, []))
+ try:
+ ext.extendMarkdown(self, globals())
+ except AttributeError:
+ message(ERROR, "Incorrect type! Extension '%s' is "
+ "neither a string or an Extension." %(repr(ext)))
+
+
+ def registerExtension(self, extension):
+ """ This gets called by the extension """
+ self.registeredExtensions.append(extension)
+
+ def reset(self):
+ """
+ Resets all state variables so that we can start with a new text.
+ """
+ self.htmlStash.reset()
+ self.references.clear()
+
+ for extension in self.registeredExtensions:
+ extension.reset()
+
+ def set_output_format(self, format):
+ """ Set the output format for the class instance. """
+ try:
+ self.serializer = self.output_formats[format.lower()]
+ except KeyError:
+ message(CRITICAL, 'Invalid Output Format: "%s". Use one of %s.' \
+ % (format, self.output_formats.keys()))
+
+ def convert(self, source):
+ """
+ Convert markdown to serialized XHTML or HTML.
+
+ Keyword arguments:
+
+ * source: Source text as a Unicode string.
+
+ """
+
+ # Fixup the source text
+ if not source.strip():
+ return u"" # a blank unicode string
+ try:
+ source = unicode(source)
+ except UnicodeDecodeError:
+ message(CRITICAL, 'UnicodeDecodeError: Markdown only accepts unicode or ascii input.')
+ return u""
+
+ source = source.replace(STX, "").replace(ETX, "")
+ source = source.replace("\r\n", "\n").replace("\r", "\n") + "\n\n"
+ source = re.sub(r'\n\s+\n', '\n\n', source)
+ source = source.expandtabs(TAB_LENGTH)
+
+ # Split into lines and run the line preprocessors.
+ self.lines = source.split("\n")
+ for prep in self.preprocessors.values():
+ self.lines = prep.run(self.lines)
+
+ # Parse the high-level elements.
+ root = self.parser.parseDocument(self.lines).getroot()
+
+ # Run the tree-processors
+ for treeprocessor in self.treeprocessors.values():
+ newRoot = treeprocessor.run(root)
+ if newRoot:
+ root = newRoot
+
+ # Serialize _properly_. Strip top-level tags.
+ output, length = codecs.utf_8_decode(self.serializer(root, encoding="utf8"))
+ if self.stripTopLevelTags:
+ start = output.index('<%s>'%DOC_TAG)+len(DOC_TAG)+2
+ end = output.rindex('</%s>'%DOC_TAG)
+ output = output[start:end].strip()
+
+ # Run the text post-processors
+ for pp in self.postprocessors.values():
+ output = pp.run(output)
+
+ return output.strip()
+
+ def convertFile(self, input=None, output=None, encoding=None):
+ """Converts a markdown file and returns the HTML as a unicode string.
+
+ Decodes the file using the provided encoding (defaults to utf-8),
+ passes the file content to markdown, and outputs the html to either
+ the provided stream or the file with provided name, using the same
+ encoding as the source file.
+
+ **Note:** This is the only place that decoding and encoding of unicode
+ takes place in Python-Markdown. (All other code is unicode-in /
+ unicode-out.)
+
+ Keyword arguments:
+
+ * input: Name of source text file.
+ * output: Name of output file. Writes to stdout if `None`.
+ * encoding: Encoding of input and output files. Defaults to utf-8.
+
+ """
+
+ encoding = encoding or "utf-8"
+
+ # Read the source
+ input_file = codecs.open(input, mode="r", encoding=encoding)
+ text = input_file.read()
+ input_file.close()
+ text = text.lstrip(u'\ufeff') # remove the byte-order mark
+
+ # Convert
+ html = self.convert(text)
+
+ # Write to file or stdout
+ if isinstance(output, (str, unicode)):
+ output_file = codecs.open(output, "w", encoding=encoding)
+ output_file.write(html)
+ output_file.close()
+ else:
+ output.write(html.encode(encoding))
+
+
+"""
+Extensions
+-----------------------------------------------------------------------------
+"""
+
+class Extension:
+ """ Base class for extensions to subclass. """
+ def __init__(self, configs = {}):
+ """Create an instance of an Extention.
+
+ Keyword arguments:
+
+ * configs: A dict of configuration setting used by an Extension.
+ """
+ self.config = configs
+
+ def getConfig(self, key):
+ """ Return a setting for the given key or an empty string. """
+ if key in self.config:
+ return self.config[key][0]
+ else:
+ return ""
+
+ def getConfigInfo(self):
+ """ Return all config settings as a list of tuples. """
+ return [(key, self.config[key][1]) for key in self.config.keys()]
+
+ def setConfig(self, key, value):
+ """ Set a config setting for `key` with the given `value`. """
+ self.config[key][0] = value
+
+ def extendMarkdown(self, md, md_globals):
+ """
+ Add the various proccesors and patterns to the Markdown Instance.
+
+ This method must be overriden by every extension.
+
+ Keyword arguments:
+
+ * md: The Markdown instance.
+
+ * md_globals: Global variables in the markdown module namespace.
+
+ """
+ pass
+
+
+def load_extension(ext_name, configs = []):
+ """Load extension by name, then return the module.
+
+ The extension name may contain arguments as part of the string in the
+ following format: "extname(key1=value1,key2=value2)"
+
+ """
+
+ # Parse extensions config params (ignore the order)
+ configs = dict(configs)
+ pos = ext_name.find("(") # find the first "("
+ if pos > 0:
+ ext_args = ext_name[pos+1:-1]
+ ext_name = ext_name[:pos]
+ pairs = [x.split("=") for x in ext_args.split(",")]
+ configs.update([(x.strip(), y.strip()) for (x, y) in pairs])
+
+ # Setup the module names
+ ext_module = 'markdown.extensions'
+ module_name_new_style = '.'.join([ext_module, ext_name])
+ module_name_old_style = '_'.join(['mdx', ext_name])
+
+ # Try loading the extention first from one place, then another
+ try: # New style (markdown.extensons.<extension>)
+ module = __import__(module_name_new_style, {}, {}, [ext_module])
+ except ImportError:
+ try: # Old style (mdx.<extension>)
+ module = __import__(module_name_old_style)
+ except ImportError:
+ message(WARN, "Failed loading extension '%s' from '%s' or '%s'"
+ % (ext_name, module_name_new_style, module_name_old_style))
+ # Return None so we don't try to initiate none-existant extension
+ return None
+
+ # If the module is loaded successfully, we expect it to define a
+ # function called makeExtension()
+ try:
+ return module.makeExtension(configs.items())
+ except AttributeError:
+ message(CRITICAL, "Failed to initiate extension '%s'" % ext_name)
+
+
+def load_extensions(ext_names):
+ """Loads multiple extensions"""
+ extensions = []
+ for ext_name in ext_names:
+ extension = load_extension(ext_name)
+ if extension:
+ extensions.append(extension)
+ return extensions
+
+
+"""
+EXPORTED FUNCTIONS
+=============================================================================
+
+Those are the two functions we really mean to export: markdown() and
+markdownFromFile().
+"""
+
+def markdown(text,
+ extensions = [],
+ safe_mode = False,
+ output_format = DEFAULT_OUTPUT_FORMAT):
+ """Convert a markdown string to HTML and return HTML as a unicode string.
+
+ This is a shortcut function for `Markdown` class to cover the most
+ basic use case. It initializes an instance of Markdown, loads the
+ necessary extensions and runs the parser on the given text.
+
+ Keyword arguments:
+
+ * text: Markdown formatted text as Unicode or ASCII string.
+ * extensions: A list of extensions or extension names (may contain config args).
+ * safe_mode: Disallow raw html. One of "remove", "replace" or "escape".
+ * output_format: Format of output. Supported formats are:
+ * "xhtml1": Outputs XHTML 1.x. Default.
+ * "xhtml": Outputs latest supported version of XHTML (currently XHTML 1.1).
+ * "html4": Outputs HTML 4
+ * "html": Outputs latest supported version of HTML (currently HTML 4).
+ Note that it is suggested that the more specific formats ("xhtml1"
+ and "html4") be used as "xhtml" or "html" may change in the future
+ if it makes sense at that time.
+
+ Returns: An HTML document as a string.
+
+ """
+ md = Markdown(extensions=load_extensions(extensions),
+ safe_mode=safe_mode,
+ output_format=output_format)
+ return md.convert(text)
+
+
+def markdownFromFile(input = None,
+ output = None,
+ extensions = [],
+ encoding = None,
+ safe_mode = False,
+ output_format = DEFAULT_OUTPUT_FORMAT):
+ """Read markdown code from a file and write it to a file or a stream."""
+ md = Markdown(extensions=load_extensions(extensions),
+ safe_mode=safe_mode,
+ output_format=output_format)
+ md.convertFile(input, output, encoding)
+
+
+
diff --git a/vendor/tornado/website/markdown/blockparser.py b/vendor/tornado/website/markdown/blockparser.py
new file mode 100644
index 0000000000..e18b338487
--- /dev/null
+++ b/vendor/tornado/website/markdown/blockparser.py
@@ -0,0 +1,95 @@
+
+import markdown
+
+class State(list):
+ """ Track the current and nested state of the parser.
+
+ This utility class is used to track the state of the BlockParser and
+ support multiple levels if nesting. It's just a simple API wrapped around
+ a list. Each time a state is set, that state is appended to the end of the
+ list. Each time a state is reset, that state is removed from the end of
+ the list.
+
+ Therefore, each time a state is set for a nested block, that state must be
+ reset when we back out of that level of nesting or the state could be
+ corrupted.
+
+ While all the methods of a list object are available, only the three
+ defined below need be used.
+
+ """
+
+ def set(self, state):
+ """ Set a new state. """
+ self.append(state)
+
+ def reset(self):
+ """ Step back one step in nested state. """
+ self.pop()
+
+ def isstate(self, state):
+ """ Test that top (current) level is of given state. """
+ if len(self):
+ return self[-1] == state
+ else:
+ return False
+
+class BlockParser:
+ """ Parse Markdown blocks into an ElementTree object.
+
+ A wrapper class that stitches the various BlockProcessors together,
+ looping through them and creating an ElementTree object.
+ """
+
+ def __init__(self):
+ self.blockprocessors = markdown.odict.OrderedDict()
+ self.state = State()
+
+ def parseDocument(self, lines):
+ """ Parse a markdown document into an ElementTree.
+
+ Given a list of lines, an ElementTree object (not just a parent Element)
+ is created and the root element is passed to the parser as the parent.
+ The ElementTree object is returned.
+
+ This should only be called on an entire document, not pieces.
+
+ """
+ # Create a ElementTree from the lines
+ self.root = markdown.etree.Element(markdown.DOC_TAG)
+ self.parseChunk(self.root, '\n'.join(lines))
+ return markdown.etree.ElementTree(self.root)
+
+ def parseChunk(self, parent, text):
+ """ Parse a chunk of markdown text and attach to given etree node.
+
+ While the ``text`` argument is generally assumed to contain multiple
+ blocks which will be split on blank lines, it could contain only one
+ block. Generally, this method would be called by extensions when
+ block parsing is required.
+
+ The ``parent`` etree Element passed in is altered in place.
+ Nothing is returned.
+
+ """
+ self.parseBlocks(parent, text.split('\n\n'))
+
+ def parseBlocks(self, parent, blocks):
+ """ Process blocks of markdown text and attach to given etree node.
+
+ Given a list of ``blocks``, each blockprocessor is stepped through
+ until there are no blocks left. While an extension could potentially
+ call this method directly, it's generally expected to be used internally.
+
+ This is a public method as an extension may need to add/alter additional
+ BlockProcessors which call this method to recursively parse a nested
+ block.
+
+ """
+ while blocks:
+ for processor in self.blockprocessors.values():
+ if processor.test(parent, blocks[0]):
+ processor.run(parent, blocks)
+ break
+
+
diff --git a/vendor/tornado/website/markdown/blockprocessors.py b/vendor/tornado/website/markdown/blockprocessors.py
new file mode 100644
index 0000000000..79f4db93bc
--- /dev/null
+++ b/vendor/tornado/website/markdown/blockprocessors.py
@@ -0,0 +1,460 @@
+"""
+CORE MARKDOWN BLOCKPARSER
+=============================================================================
+
+This parser handles basic parsing of Markdown blocks. It doesn't concern itself
+with inline elements such as **bold** or *italics*, but rather just catches
+blocks, lists, quotes, etc.
+
+The BlockParser is made up of a bunch of BlockProssors, each handling a
+different type of block. Extensions may add/replace/remove BlockProcessors
+as they need to alter how markdown blocks are parsed.
+
+"""
+
+import re
+import markdown
+
+class BlockProcessor:
+ """ Base class for block processors.
+
+ Each subclass will provide the methods below to work with the source and
+ tree. Each processor will need to define it's own ``test`` and ``run``
+ methods. The ``test`` method should return True or False, to indicate
+ whether the current block should be processed by this processor. If the
+ test passes, the parser will call the processors ``run`` method.
+
+ """
+
+ def __init__(self, parser=None):
+ self.parser = parser
+
+ def lastChild(self, parent):
+ """ Return the last child of an etree element. """
+ if len(parent):
+ return parent[-1]
+ else:
+ return None
+
+ def detab(self, text):
+ """ Remove a tab from the front of each line of the given text. """
+ newtext = []
+ lines = text.split('\n')
+ for line in lines:
+ if line.startswith(' '*markdown.TAB_LENGTH):
+ newtext.append(line[markdown.TAB_LENGTH:])
+ elif not line.strip():
+ newtext.append('')
+ else:
+ break
+ return '\n'.join(newtext), '\n'.join(lines[len(newtext):])
+
+ def looseDetab(self, text, level=1):
+ """ Remove a tab from front of lines but allowing dedented lines. """
+ lines = text.split('\n')
+ for i in range(len(lines)):
+ if lines[i].startswith(' '*markdown.TAB_LENGTH*level):
+ lines[i] = lines[i][markdown.TAB_LENGTH*level:]
+ return '\n'.join(lines)
+
+ def test(self, parent, block):
+ """ Test for block type. Must be overridden by subclasses.
+
+ As the parser loops through processors, it will call the ``test`` method
+ on each to determine if the given block of text is of that type. This
+ method must return a boolean ``True`` or ``False``. The actual method of
+ testing is left to the needs of that particular block type. It could
+ be as simple as ``block.startswith(some_string)`` or a complex regular
+ expression. As the block type may be different depending on the parent
+ of the block (i.e. inside a list), the parent etree element is also
+ provided and may be used as part of the test.
+
+ Keywords:
+
+ * ``parent``: A etree element which will be the parent of the block.
+ * ``block``: A block of text from the source which has been split at
+ blank lines.
+ """
+ pass
+
+ def run(self, parent, blocks):
+ """ Run processor. Must be overridden by subclasses.
+
+ When the parser determines the appropriate type of a block, the parser
+ will call the corresponding processor's ``run`` method. This method
+ should parse the individual lines of the block and append them to
+ the etree.
+
+ Note that both the ``parent`` and ``etree`` keywords are pointers
+ to instances of the objects which should be edited in place. Each
+ processor must make changes to the existing objects as there is no
+ mechanism to return new/different objects to replace them.
+
+ This means that this method should be adding SubElements or adding text
+ to the parent, and should remove (``pop``) or add (``insert``) items to
+ the list of blocks.
+
+ Keywords:
+
+ * ``parent``: A etree element which is the parent of the current block.
+ * ``blocks``: A list of all remaining blocks of the document.
+ """
+ pass
+
+
+class ListIndentProcessor(BlockProcessor):
+ """ Process children of list items.
+
+ Example:
+ * a list item
+ process this part
+
+ or this part
+
+ """
+
+ INDENT_RE = re.compile(r'^(([ ]{%s})+)'% markdown.TAB_LENGTH)
+ ITEM_TYPES = ['li']
+ LIST_TYPES = ['ul', 'ol']
+
+ def test(self, parent, block):
+ return block.startswith(' '*markdown.TAB_LENGTH) and \
+ not self.parser.state.isstate('detabbed') and \
+ (parent.tag in self.ITEM_TYPES or \
+ (len(parent) and parent[-1] and \
+ (parent[-1].tag in self.LIST_TYPES)
+ )
+ )
+
+ def run(self, parent, blocks):
+ block = blocks.pop(0)
+ level, sibling = self.get_level(parent, block)
+ block = self.looseDetab(block, level)
+
+ self.parser.state.set('detabbed')
+ if parent.tag in self.ITEM_TYPES:
+ # The parent is already a li. Just parse the child block.
+ self.parser.parseBlocks(parent, [block])
+ elif sibling.tag in self.ITEM_TYPES:
+ # The sibling is a li. Use it as parent.
+ self.parser.parseBlocks(sibling, [block])
+ elif len(sibling) and sibling[-1].tag in self.ITEM_TYPES:
+ # The parent is a list (``ol`` or ``ul``) which has children.
+ # Assume the last child li is the parent of this block.
+ if sibling[-1].text:
+ # If the parent li has text, that text needs to be moved to a p
+ block = '%s\n\n%s' % (sibling[-1].text, block)
+ sibling[-1].text = ''
+ self.parser.parseChunk(sibling[-1], block)
+ else:
+ self.create_item(sibling, block)
+ self.parser.state.reset()
+
+ def create_item(self, parent, block):
+ """ Create a new li and parse the block with it as the parent. """
+ li = markdown.etree.SubElement(parent, 'li')
+ self.parser.parseBlocks(li, [block])
+
+ def get_level(self, parent, block):
+ """ Get level of indent based on list level. """
+ # Get indent level
+ m = self.INDENT_RE.match(block)
+ if m:
+ indent_level = len(m.group(1))/markdown.TAB_LENGTH
+ else:
+ indent_level = 0
+ if self.parser.state.isstate('list'):
+ # We're in a tightlist - so we already are at correct parent.
+ level = 1
+ else:
+ # We're in a looselist - so we need to find parent.
+ level = 0
+ # Step through children of tree to find matching indent level.
+ while indent_level > level:
+ child = self.lastChild(parent)
+ if child and (child.tag in self.LIST_TYPES or child.tag in self.ITEM_TYPES):
+ if child.tag in self.LIST_TYPES:
+ level += 1
+ parent = child
+ else:
+ # No more child levels. If we're short of indent_level,
+ # we have a code block. So we stop here.
+ break
+ return level, parent
+
+
+class CodeBlockProcessor(BlockProcessor):
+ """ Process code blocks. """
+
+ def test(self, parent, block):
+ return block.startswith(' '*markdown.TAB_LENGTH)
+
+ def run(self, parent, blocks):
+ sibling = self.lastChild(parent)
+ block = blocks.pop(0)
+ theRest = ''
+ if sibling and sibling.tag == "pre" and len(sibling) \
+ and sibling[0].tag == "code":
+ # The previous block was a code block. As blank lines do not start
+ # new code blocks, append this block to the previous, adding back
+ # linebreaks removed from the split into a list.
+ code = sibling[0]
+ block, theRest = self.detab(block)
+ code.text = markdown.AtomicString('%s\n%s\n' % (code.text, block.rstrip()))
+ else:
+ # This is a new codeblock. Create the elements and insert text.
+ pre = markdown.etree.SubElement(parent, 'pre')
+ code = markdown.etree.SubElement(pre, 'code')
+ block, theRest = self.detab(block)
+ code.text = markdown.AtomicString('%s\n' % block.rstrip())
+ if theRest:
+ # This block contained unindented line(s) after the first indented
+ # line. Insert these lines as the first block of the master blocks
+ # list for future processing.
+ blocks.insert(0, theRest)
+
+
+class BlockQuoteProcessor(BlockProcessor):
+
+ RE = re.compile(r'(^|\n)[ ]{0,3}>[ ]?(.*)')
+
+ def test(self, parent, block):
+ return bool(self.RE.search(block))
+
+ def run(self, parent, blocks):
+ block = blocks.pop(0)
+ m = self.RE.search(block)
+ if m:
+ before = block[:m.start()] # Lines before blockquote
+ # Pass lines before blockquote in recursively for parsing forst.
+ self.parser.parseBlocks(parent, [before])
+ # Remove ``> `` from begining of each line.
+ block = '\n'.join([self.clean(line) for line in
+ block[m.start():].split('\n')])
+ sibling = self.lastChild(parent)
+ if sibling and sibling.tag == "blockquote":
+ # Previous block was a blockquote so set that as this blocks parent
+ quote = sibling
+ else:
+ # This is a new blockquote. Create a new parent element.
+ quote = markdown.etree.SubElement(parent, 'blockquote')
+ # Recursively parse block with blockquote as parent.
+ self.parser.parseChunk(quote, block)
+
+ def clean(self, line):
+ """ Remove ``>`` from beginning of a line. """
+ m = self.RE.match(line)
+ if line.strip() == ">":
+ return ""
+ elif m:
+ return m.group(2)
+ else:
+ return line
+
+class OListProcessor(BlockProcessor):
+ """ Process ordered list blocks. """
+
+ TAG = 'ol'
+ # Detect an item (``1. item``). ``group(1)`` contains contents of item.
+ RE = re.compile(r'^[ ]{0,3}\d+\.[ ](.*)')
+ # Detect items on secondary lines. they can be of either list type.
+ CHILD_RE = re.compile(r'^[ ]{0,3}((\d+\.)|[*+-])[ ](.*)')
+ # Detect indented (nested) items of either type
+ INDENT_RE = re.compile(r'^[ ]{4,7}((\d+\.)|[*+-])[ ].*')
+
+ def test(self, parent, block):
+ return bool(self.RE.match(block))
+
+ def run(self, parent, blocks):
+ # Check fr multiple items in one block.
+ items = self.get_items(blocks.pop(0))
+ sibling = self.lastChild(parent)
+ if sibling and sibling.tag in ['ol', 'ul']:
+ # Previous block was a list item, so set that as parent
+ lst = sibling
+ # make sure previous item is in a p.
+ if len(lst) and lst[-1].text and not len(lst[-1]):
+ p = markdown.etree.SubElement(lst[-1], 'p')
+ p.text = lst[-1].text
+ lst[-1].text = ''
+ # parse first block differently as it gets wrapped in a p.
+ li = markdown.etree.SubElement(lst, 'li')
+ self.parser.state.set('looselist')
+ firstitem = items.pop(0)
+ self.parser.parseBlocks(li, [firstitem])
+ self.parser.state.reset()
+ else:
+ # This is a new list so create parent with appropriate tag.
+ lst = markdown.etree.SubElement(parent, self.TAG)
+ self.parser.state.set('list')
+ # Loop through items in block, recursively parsing each with the
+ # appropriate parent.
+ for item in items:
+ if item.startswith(' '*markdown.TAB_LENGTH):
+ # Item is indented. Parse with last item as parent
+ self.parser.parseBlocks(lst[-1], [item])
+ else:
+ # New item. Create li and parse with it as parent
+ li = markdown.etree.SubElement(lst, 'li')
+ self.parser.parseBlocks(li, [item])
+ self.parser.state.reset()
+
+ def get_items(self, block):
+ """ Break a block into list items. """
+ items = []
+ for line in block.split('\n'):
+ m = self.CHILD_RE.match(line)
+ if m:
+ # This is a new item. Append
+ items.append(m.group(3))
+ elif self.INDENT_RE.match(line):
+ # This is an indented (possibly nested) item.
+ if items[-1].startswith(' '*markdown.TAB_LENGTH):
+ # Previous item was indented. Append to that item.
+ items[-1] = '%s\n%s' % (items[-1], line)
+ else:
+ items.append(line)
+ else:
+ # This is another line of previous item. Append to that item.
+ items[-1] = '%s\n%s' % (items[-1], line)
+ return items
+
+
+class UListProcessor(OListProcessor):
+ """ Process unordered list blocks. """
+
+ TAG = 'ul'
+ RE = re.compile(r'^[ ]{0,3}[*+-][ ](.*)')
+
+
+class HashHeaderProcessor(BlockProcessor):
+ """ Process Hash Headers. """
+
+ # Detect a header at start of any line in block
+ RE = re.compile(r'(^|\n)(?P<level>#{1,6})(?P<header>.*?)#*(\n|$)')
+
+ def test(self, parent, block):
+ return bool(self.RE.search(block))
+
+ def run(self, parent, blocks):
+ block = blocks.pop(0)
+ m = self.RE.search(block)
+ if m:
+ before = block[:m.start()] # All lines before header
+ after = block[m.end():] # All lines after header
+ if before:
+ # As the header was not the first line of the block and the
+ # lines before the header must be parsed first,
+ # recursively parse this lines as a block.
+ self.parser.parseBlocks(parent, [before])
+ # Create header using named groups from RE
+ h = markdown.etree.SubElement(parent, 'h%d' % len(m.group('level')))
+ h.text = m.group('header').strip()
+ if after:
+ # Insert remaining lines as first block for future parsing.
+ blocks.insert(0, after)
+ else:
+ # This should never happen, but just in case...
+ message(CRITICAL, "We've got a problem header!")
+
+
+class SetextHeaderProcessor(BlockProcessor):
+ """ Process Setext-style Headers. """
+
+ # Detect Setext-style header. Must be first 2 lines of block.
+ RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE)
+
+ def test(self, parent, block):
+ return bool(self.RE.match(block))
+
+ def run(self, parent, blocks):
+ lines = blocks.pop(0).split('\n')
+ # Determine level. ``=`` is 1 and ``-`` is 2.
+ if lines[1].startswith('='):
+ level = 1
+ else:
+ level = 2
+ h = markdown.etree.SubElement(parent, 'h%d' % level)
+ h.text = lines[0].strip()
+ if len(lines) > 2:
+ # Block contains additional lines. Add to master blocks for later.
+ blocks.insert(0, '\n'.join(lines[2:]))
+
+
+class HRProcessor(BlockProcessor):
+ """ Process Horizontal Rules. """
+
+ RE = r'[ ]{0,3}(?P<ch>[*_-])[ ]?((?P=ch)[ ]?){2,}[ ]*'
+ # Detect hr on any line of a block.
+ SEARCH_RE = re.compile(r'(^|\n)%s(\n|$)' % RE)
+ # Match a hr on a single line of text.
+ MATCH_RE = re.compile(r'^%s$' % RE)
+
+ def test(self, parent, block):
+ return bool(self.SEARCH_RE.search(block))
+
+ def run(self, parent, blocks):
+ lines = blocks.pop(0).split('\n')
+ prelines = []
+ # Check for lines in block before hr.
+ for line in lines:
+ m = self.MATCH_RE.match(line)
+ if m:
+ break
+ else:
+ prelines.append(line)
+ if len(prelines):
+ # Recursively parse lines before hr so they get parsed first.
+ self.parser.parseBlocks(parent, ['\n'.join(prelines)])
+ # create hr
+ hr = markdown.etree.SubElement(parent, 'hr')
+ # check for lines in block after hr.
+ lines = lines[len(prelines)+1:]
+ if len(lines):
+ # Add lines after hr to master blocks for later parsing.
+ blocks.insert(0, '\n'.join(lines))
+
+
+class EmptyBlockProcessor(BlockProcessor):
+ """ Process blocks and start with an empty line. """
+
+ # Detect a block that only contains whitespace
+ # or only whitespace on the first line.
+ RE = re.compile(r'^\s*\n')
+
+ def test(self, parent, block):
+ return bool(self.RE.match(block))
+
+ def run(self, parent, blocks):
+ block = blocks.pop(0)
+ m = self.RE.match(block)
+ if m:
+ # Add remaining line to master blocks for later.
+ blocks.insert(0, block[m.end():])
+ sibling = self.lastChild(parent)
+ if sibling and sibling.tag == 'pre' and sibling[0] and \
+ sibling[0].tag == 'code':
+ # Last block is a codeblock. Append to preserve whitespace.
+ sibling[0].text = markdown.AtomicString('%s/n/n/n' % sibling[0].text )
+
+
+class ParagraphProcessor(BlockProcessor):
+ """ Process Paragraph blocks. """
+
+ def test(self, parent, block):
+ return True
+
+ def run(self, parent, blocks):
+ block = blocks.pop(0)
+ if block.strip():
+ # Not a blank block. Add to parent, otherwise throw it away.
+ if self.parser.state.isstate('list'):
+ # The parent is a tight-list. Append to parent.text
+ if parent.text:
+ parent.text = '%s\n%s' % (parent.text, block)
+ else:
+ parent.text = block.lstrip()
+ else:
+ # Create a regular paragraph
+ p = markdown.etree.SubElement(parent, 'p')
+ p.text = block.lstrip()
diff --git a/vendor/tornado/website/markdown/commandline.py b/vendor/tornado/website/markdown/commandline.py
new file mode 100644
index 0000000000..1eedc6dbb1
--- /dev/null
+++ b/vendor/tornado/website/markdown/commandline.py
@@ -0,0 +1,96 @@
+"""
+COMMAND-LINE SPECIFIC STUFF
+=============================================================================
+
+The rest of the code is specifically for handling the case where Python
+Markdown is called from the command line.
+"""
+
+import markdown
+import sys
+import logging
+from logging import DEBUG, INFO, WARN, ERROR, CRITICAL
+
+EXECUTABLE_NAME_FOR_USAGE = "python markdown.py"
+""" The name used in the usage statement displayed for python versions < 2.3.
+(With python 2.3 and higher the usage statement is generated by optparse
+and uses the actual name of the executable called.) """
+
+OPTPARSE_WARNING = """
+Python 2.3 or higher required for advanced command line options.
+For lower versions of Python use:
+
+ %s INPUT_FILE > OUTPUT_FILE
+
+""" % EXECUTABLE_NAME_FOR_USAGE
+
+def parse_options():
+ """
+ Define and parse `optparse` options for command-line usage.
+ """
+
+ try:
+ optparse = __import__("optparse")
+ except:
+ if len(sys.argv) == 2:
+ return {'input': sys.argv[1],
+ 'output': None,
+ 'safe': False,
+ 'extensions': [],
+ 'encoding': None }, CRITICAL
+ else:
+ print OPTPARSE_WARNING
+ return None, None
+
+ parser = optparse.OptionParser(usage="%prog INPUTFILE [options]")
+ parser.add_option("-f", "--file", dest="filename", default=sys.stdout,
+ help="write output to OUTPUT_FILE",
+ metavar="OUTPUT_FILE")
+ parser.add_option("-e", "--encoding", dest="encoding",
+ help="encoding for input and output files",)
+ parser.add_option("-q", "--quiet", default = CRITICAL,
+ action="store_const", const=CRITICAL+10, dest="verbose",
+ help="suppress all messages")
+ parser.add_option("-v", "--verbose",
+ action="store_const", const=INFO, dest="verbose",
+ help="print info messages")
+ parser.add_option("-s", "--safe", dest="safe", default=False,
+ metavar="SAFE_MODE",
+ help="safe mode ('replace', 'remove' or 'escape' user's HTML tag)")
+ parser.add_option("-o", "--output_format", dest="output_format",
+ default='xhtml1', metavar="OUTPUT_FORMAT",
+ help="Format of output. One of 'xhtml1' (default) or 'html4'.")
+ parser.add_option("--noisy",
+ action="store_const", const=DEBUG, dest="verbose",
+ help="print debug messages")
+ parser.add_option("-x", "--extension", action="append", dest="extensions",
+ help = "load extension EXTENSION", metavar="EXTENSION")
+
+ (options, args) = parser.parse_args()
+
+ if not len(args) == 1:
+ parser.print_help()
+ return None, None
+ else:
+ input_file = args[0]
+
+ if not options.extensions:
+ options.extensions = []
+
+ return {'input': input_file,
+ 'output': options.filename,
+ 'safe_mode': options.safe,
+ 'extensions': options.extensions,
+ 'encoding': options.encoding,
+ 'output_format': options.output_format}, options.verbose
+
+def run():
+ """Run Markdown from the command line."""
+
+ # Parse options and adjust logging level if necessary
+ options, logging_level = parse_options()
+ if not options: sys.exit(0)
+ if logging_level: logging.getLogger('MARKDOWN').setLevel(logging_level)
+
+ # Run
+ markdown.markdownFromFile(**options)
diff --git a/vendor/tornado/website/markdown/etree_loader.py b/vendor/tornado/website/markdown/etree_loader.py
new file mode 100644
index 0000000000..e2599b2cb9
--- /dev/null
+++ b/vendor/tornado/website/markdown/etree_loader.py
@@ -0,0 +1,33 @@
+
+from markdown import message, CRITICAL
+import sys
+
+## Import
+def importETree():
+ """Import the best implementation of ElementTree, return a module object."""
+ etree_in_c = None
+ try: # Is it Python 2.5+ with C implemenation of ElementTree installed?
+ import xml.etree.cElementTree as etree_in_c
+ except ImportError:
+ try: # Is it Python 2.5+ with Python implementation of ElementTree?
+ import xml.etree.ElementTree as etree
+ except ImportError:
+ try: # An earlier version of Python with cElementTree installed?
+ import cElementTree as etree_in_c
+ except ImportError:
+ try: # An earlier version of Python with Python ElementTree?
+ import elementtree.ElementTree as etree
+ except ImportError:
+ message(CRITICAL, "Failed to import ElementTree")
+ sys.exit(1)
+ if etree_in_c and etree_in_c.VERSION < "1.0":
+ message(CRITICAL, "For cElementTree version 1.0 or higher is required.")
+ sys.exit(1)
+ elif etree_in_c :
+ return etree_in_c
+ elif etree.VERSION < "1.1":
+ message(CRITICAL, "For ElementTree version 1.1 or higher is required")
+ sys.exit(1)
+ else :
+ return etree
+
diff --git a/vendor/tornado/website/markdown/extensions/__init__.py b/vendor/tornado/website/markdown/extensions/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/vendor/tornado/website/markdown/extensions/__init__.py
diff --git a/vendor/tornado/website/markdown/extensions/toc.py b/vendor/tornado/website/markdown/extensions/toc.py
new file mode 100644
index 0000000000..3afaea0488
--- /dev/null
+++ b/vendor/tornado/website/markdown/extensions/toc.py
@@ -0,0 +1,140 @@
+"""
+Table of Contents Extension for Python-Markdown
+* * *
+
+(c) 2008 [Jack Miller](http://codezen.org)
+
+Dependencies:
+* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
+
+"""
+import markdown
+from markdown import etree
+import re
+
+class TocTreeprocessor(markdown.treeprocessors.Treeprocessor):
+ # Iterator wrapper to get parent and child all at once
+ def iterparent(self, root):
+ for parent in root.getiterator():
+ for child in parent:
+ yield parent, child
+
+ def run(self, doc):
+ div = etree.Element("div")
+ div.attrib["class"] = "toc"
+ last_li = None
+
+ # Add title to the div
+ if self.config["title"][0]:
+ header = etree.SubElement(div, "span")
+ header.attrib["class"] = "toctitle"
+ header.text = self.config["title"][0]
+
+ level = 0
+ list_stack=[div]
+ header_rgx = re.compile("[Hh][123456]")
+
+ # Get a list of id attributes
+ used_ids = []
+ for c in doc.getiterator():
+ if "id" in c.attrib:
+ used_ids.append(c.attrib["id"])
+
+ for (p, c) in self.iterparent(doc):
+ if not c.text:
+ continue
+
+ # To keep the output from screwing up the
+ # validation by putting a <div> inside of a <p>
+ # we actually replace the <p> in its entirety.
+ # We do not allow the marker inside a header as that
+ # would causes an enless loop of placing a new TOC
+ # inside previously generated TOC.
+
+ if c.text.find(self.config["marker"][0]) > -1 and not header_rgx.match(c.tag):
+ for i in range(len(p)):
+ if p[i] == c:
+ p[i] = div
+ break
+
+ if header_rgx.match(c.tag):
+ tag_level = int(c.tag[-1])
+
+ # Regardless of how many levels we jumped
+ # only one list should be created, since
+ # empty lists containing lists are illegal.
+
+ if tag_level < level:
+ list_stack.pop()
+ level = tag_level
+
+ if tag_level > level:
+ newlist = etree.Element("ul")
+ if last_li:
+ last_li.append(newlist)
+ else:
+ list_stack[-1].append(newlist)
+ list_stack.append(newlist)
+ level = tag_level
+
+ # Do not override pre-existing ids
+ if not "id" in c.attrib:
+ id = self.config["slugify"][0](c.text)
+ if id in used_ids:
+ ctr = 1
+ while "%s_%d" % (id, ctr) in used_ids:
+ ctr += 1
+ id = "%s_%d" % (id, ctr)
+ used_ids.append(id)
+ c.attrib["id"] = id
+ else:
+ id = c.attrib["id"]
+
+ # List item link, to be inserted into the toc div
+ last_li = etree.Element("li")
+ link = etree.SubElement(last_li, "a")
+ link.text = c.text
+ link.attrib["href"] = '#' + id
+
+ if int(self.config["anchorlink"][0]):
+ anchor = etree.SubElement(c, "a")
+ anchor.text = c.text
+ anchor.attrib["href"] = "#" + id
+ anchor.attrib["class"] = "toclink"
+ c.text = ""
+
+ list_stack[-1].append(last_li)
+
+class TocExtension(markdown.Extension):
+ def __init__(self, configs):
+ self.config = { "marker" : ["[TOC]",
+ "Text to find and replace with Table of Contents -"
+ "Defaults to \"[TOC]\""],
+ "slugify" : [self.slugify,
+ "Function to generate anchors based on header text-"
+ "Defaults to a built in slugify function."],
+ "title" : [None,
+ "Title to insert into TOC <div> - "
+ "Defaults to None"],
+ "anchorlink" : [0,
+ "1 if header should be a self link"
+ "Defaults to 0"]}
+
+ for key, value in configs:
+ self.setConfig(key, value)
+
+ # This is exactly the same as Django's slugify
+ def slugify(self, value):
+ """ Slugify a string, to make it URL friendly. """
+ import unicodedata
+ value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
+ value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
+ return re.sub('[-\s]+','-',value)
+
+ def extendMarkdown(self, md, md_globals):
+ tocext = TocTreeprocessor(md)
+ tocext.config = self.config
+ md.treeprocessors.add("toc", tocext, "_begin")
+
+def makeExtension(configs={}):
+ return TocExtension(configs=configs)
diff --git a/vendor/tornado/website/markdown/html4.py b/vendor/tornado/website/markdown/html4.py
new file mode 100644
index 0000000000..08f241d57a
--- /dev/null
+++ b/vendor/tornado/website/markdown/html4.py
@@ -0,0 +1,274 @@
+# markdown/html4.py
+#
+# Add html4 serialization to older versions of Elementree
+# Taken from ElementTree 1.3 preview with slight modifications
+#
+# Copyright (c) 1999-2007 by Fredrik Lundh. All rights reserved.
+#
+# fredrik@pythonware.com
+# http://www.pythonware.com
+#
+# --------------------------------------------------------------------
+# The ElementTree toolkit is
+#
+# Copyright (c) 1999-2007 by Fredrik Lundh
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# Secret Labs AB or the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+
+import markdown
+ElementTree = markdown.etree.ElementTree
+QName = markdown.etree.QName
+Comment = markdown.etree.Comment
+PI = markdown.etree.PI
+ProcessingInstruction = markdown.etree.ProcessingInstruction
+
+HTML_EMPTY = ("area", "base", "basefont", "br", "col", "frame", "hr",
+ "img", "input", "isindex", "link", "meta" "param")
+
+try:
+ HTML_EMPTY = set(HTML_EMPTY)
+except NameError:
+ pass
+
+_namespace_map = {
+ # "well-known" namespace prefixes
+ "http://www.w3.org/XML/1998/namespace": "xml",
+ "http://www.w3.org/1999/xhtml": "html",
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
+ "http://schemas.xmlsoap.org/wsdl/": "wsdl",
+ # xml schema
+ "http://www.w3.org/2001/XMLSchema": "xs",
+ "http://www.w3.org/2001/XMLSchema-instance": "xsi",
+ # dublic core
+ "http://purl.org/dc/elements/1.1/": "dc",
+}
+
+
+def _raise_serialization_error(text):
+ raise TypeError(
+ "cannot serialize %r (type %s)" % (text, type(text).__name__)
+ )
+
+def _encode(text, encoding):
+ try:
+ return text.encode(encoding, "xmlcharrefreplace")
+ except (TypeError, AttributeError):
+ _raise_serialization_error(text)
+
+def _escape_cdata(text, encoding):
+ # escape character data
+ try:
+ # it's worth avoiding do-nothing calls for strings that are
+ # shorter than 500 character, or so. assume that's, by far,
+ # the most common case in most applications.
+ if "&" in text:
+ text = text.replace("&", "&amp;")
+ if "<" in text:
+ text = text.replace("<", "&lt;")
+ if ">" in text:
+ text = text.replace(">", "&gt;")
+ return text.encode(encoding, "xmlcharrefreplace")
+ except (TypeError, AttributeError):
+ _raise_serialization_error(text)
+
+
+def _escape_attrib(text, encoding):
+ # escape attribute value
+ try:
+ if "&" in text:
+ text = text.replace("&", "&amp;")
+ if "<" in text:
+ text = text.replace("<", "&lt;")
+ if ">" in text:
+ text = text.replace(">", "&gt;")
+ if "\"" in text:
+ text = text.replace("\"", "&quot;")
+ if "\n" in text:
+ text = text.replace("\n", "&#10;")
+ return text.encode(encoding, "xmlcharrefreplace")
+ except (TypeError, AttributeError):
+ _raise_serialization_error(text)
+
+def _escape_attrib_html(text, encoding):
+ # escape attribute value
+ try:
+ if "&" in text:
+ text = text.replace("&", "&amp;")
+ if ">" in text:
+ text = text.replace(">", "&gt;")
+ if "\"" in text:
+ text = text.replace("\"", "&quot;")
+ return text.encode(encoding, "xmlcharrefreplace")
+ except (TypeError, AttributeError):
+ _raise_serialization_error(text)
+
+
+def _serialize_html(write, elem, encoding, qnames, namespaces):
+ tag = elem.tag
+ text = elem.text
+ if tag is Comment:
+ write("<!--%s-->" % _escape_cdata(text, encoding))
+ elif tag is ProcessingInstruction:
+ write("<?%s?>" % _escape_cdata(text, encoding))
+ else:
+ tag = qnames[tag]
+ if tag is None:
+ if text:
+ write(_escape_cdata(text, encoding))
+ for e in elem:
+ _serialize_html(write, e, encoding, qnames, None)
+ else:
+ write("<" + tag)
+ items = elem.items()
+ if items or namespaces:
+ items.sort() # lexical order
+ for k, v in items:
+ if isinstance(k, QName):
+ k = k.text
+ if isinstance(v, QName):
+ v = qnames[v.text]
+ else:
+ v = _escape_attrib_html(v, encoding)
+ # FIXME: handle boolean attributes
+ write(" %s=\"%s\"" % (qnames[k], v))
+ if namespaces:
+ items = namespaces.items()
+ items.sort(key=lambda x: x[1]) # sort on prefix
+ for v, k in items:
+ if k:
+ k = ":" + k
+ write(" xmlns%s=\"%s\"" % (
+ k.encode(encoding),
+ _escape_attrib(v, encoding)
+ ))
+ write(">")
+ tag = tag.lower()
+ if text:
+ if tag == "script" or tag == "style":
+ write(_encode(text, encoding))
+ else:
+ write(_escape_cdata(text, encoding))
+ for e in elem:
+ _serialize_html(write, e, encoding, qnames, None)
+ if tag not in HTML_EMPTY:
+ write("</" + tag + ">")
+ if elem.tail:
+ write(_escape_cdata(elem.tail, encoding))
+
+def write_html(root, f,
+ # keyword arguments
+ encoding="us-ascii",
+ default_namespace=None):
+ assert root is not None
+ if not hasattr(f, "write"):
+ f = open(f, "wb")
+ write = f.write
+ if not encoding:
+ encoding = "us-ascii"
+ qnames, namespaces = _namespaces(
+ root, encoding, default_namespace
+ )
+ _serialize_html(
+ write, root, encoding, qnames, namespaces
+ )
+
+# --------------------------------------------------------------------
+# serialization support
+
+def _namespaces(elem, encoding, default_namespace=None):
+ # identify namespaces used in this tree
+
+ # maps qnames to *encoded* prefix:local names
+ qnames = {None: None}
+
+ # maps uri:s to prefixes
+ namespaces = {}
+ if default_namespace:
+ namespaces[default_namespace] = ""
+
+ def encode(text):
+ return text.encode(encoding)
+
+ def add_qname(qname):
+ # calculate serialized qname representation
+ try:
+ if qname[:1] == "{":
+ uri, tag = qname[1:].split("}", 1)
+ prefix = namespaces.get(uri)
+ if prefix is None:
+ prefix = _namespace_map.get(uri)
+ if prefix is None:
+ prefix = "ns%d" % len(namespaces)
+ if prefix != "xml":
+ namespaces[uri] = prefix
+ if prefix:
+ qnames[qname] = encode("%s:%s" % (prefix, tag))
+ else:
+ qnames[qname] = encode(tag) # default element
+ else:
+ if default_namespace:
+ # FIXME: can this be handled in XML 1.0?
+ raise ValueError(
+ "cannot use non-qualified names with "
+ "default_namespace option"
+ )
+ qnames[qname] = encode(qname)
+ except TypeError:
+ _raise_serialization_error(qname)
+
+ # populate qname and namespaces table
+ try:
+ iterate = elem.iter
+ except AttributeError:
+ iterate = elem.getiterator # cET compatibility
+ for elem in iterate():
+ tag = elem.tag
+ if isinstance(tag, QName) and tag.text not in qnames:
+ add_qname(tag.text)
+ elif isinstance(tag, basestring):
+ if tag not in qnames:
+ add_qname(tag)
+ elif tag is not None and tag is not Comment and tag is not PI:
+ _raise_serialization_error(tag)
+ for key, value in elem.items():
+ if isinstance(key, QName):
+ key = key.text
+ if key not in qnames:
+ add_qname(key)
+ if isinstance(value, QName) and value.text not in qnames:
+ add_qname(value.text)
+ text = elem.text
+ if isinstance(text, QName) and text.text not in qnames:
+ add_qname(text.text)
+ return qnames, namespaces
+
+def to_html_string(element, encoding=None):
+ class dummy:
+ pass
+ data = []
+ file = dummy()
+ file.write = data.append
+ write_html(ElementTree(element).getroot(),file,encoding)
+ return "".join(data)
diff --git a/vendor/tornado/website/markdown/inlinepatterns.py b/vendor/tornado/website/markdown/inlinepatterns.py
new file mode 100644
index 0000000000..89fa3b2ef4
--- /dev/null
+++ b/vendor/tornado/website/markdown/inlinepatterns.py
@@ -0,0 +1,371 @@
+"""
+INLINE PATTERNS
+=============================================================================
+
+Inline patterns such as *emphasis* are handled by means of auxiliary
+objects, one per pattern. Pattern objects must be instances of classes
+that extend markdown.Pattern. Each pattern object uses a single regular
+expression and needs support the following methods:
+
+ pattern.getCompiledRegExp() # returns a regular expression
+
+ pattern.handleMatch(m) # takes a match object and returns
+ # an ElementTree element or just plain text
+
+All of python markdown's built-in patterns subclass from Pattern,
+but you can add additional patterns that don't.
+
+Also note that all the regular expressions used by inline must
+capture the whole block. For this reason, they all start with
+'^(.*)' and end with '(.*)!'. In case with built-in expression
+Pattern takes care of adding the "^(.*)" and "(.*)!".
+
+Finally, the order in which regular expressions are applied is very
+important - e.g. if we first replace http://.../ links with <a> tags
+and _then_ try to replace inline html, we would end up with a mess.
+So, we apply the expressions in the following order:
+
+* escape and backticks have to go before everything else, so
+ that we can preempt any markdown patterns by escaping them.
+
+* then we handle auto-links (must be done before inline html)
+
+* then we handle inline HTML. At this point we will simply
+ replace all inline HTML strings with a placeholder and add
+ the actual HTML to a hash.
+
+* then inline images (must be done before links)
+
+* then bracketed links, first regular then reference-style
+
+* finally we apply strong and emphasis
+"""
+
+import markdown
+import re
+from urlparse import urlparse, urlunparse
+import sys
+if sys.version >= "3.0":
+ from html import entities as htmlentitydefs
+else:
+ import htmlentitydefs
+
+"""
+The actual regular expressions for patterns
+-----------------------------------------------------------------------------
+"""
+
+NOBRACKET = r'[^\]\[]*'
+BRK = ( r'\[('
+ + (NOBRACKET + r'(\[')*6
+ + (NOBRACKET+ r'\])*')*6
+ + NOBRACKET + r')\]' )
+NOIMG = r'(?<!\!)'
+
+BACKTICK_RE = r'(?<!\\)(`+)(.+?)(?<!`)\2(?!`)' # `e=f()` or ``e=f("`")``
+ESCAPE_RE = r'\\(.)' # \<
+EMPHASIS_RE = r'(\*)([^\*]*)\2' # *emphasis*
+STRONG_RE = r'(\*{2}|_{2})(.*?)\2' # **strong**
+STRONG_EM_RE = r'(\*{3}|_{3})(.*?)\2' # ***strong***
+
+if markdown.SMART_EMPHASIS:
+ EMPHASIS_2_RE = r'(?<!\S)(_)(\S.*?)\2' # _emphasis_
+else:
+ EMPHASIS_2_RE = r'(_)(.*?)\2' # _emphasis_
+
+LINK_RE = NOIMG + BRK + \
+r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*)\12)?\)'''
+# [text](url) or [text](<url>)
+
+IMAGE_LINK_RE = r'\!' + BRK + r'\s*\((<.*?>|([^\)]*))\)'
+# ![alttxt](http://x.com/) or ![alttxt](<http://x.com/>)
+REFERENCE_RE = NOIMG + BRK+ r'\s*\[([^\]]*)\]' # [Google][3]
+IMAGE_REFERENCE_RE = r'\!' + BRK + '\s*\[([^\]]*)\]' # ![alt text][2]
+NOT_STRONG_RE = r'( \* )' # stand-alone * or _
+AUTOLINK_RE = r'<((?:f|ht)tps?://[^>]*)>' # <http://www.123.com>
+AUTOMAIL_RE = r'<([^> \!]*@[^> ]*)>' # <me@example.com>
+
+HTML_RE = r'(\<([a-zA-Z/][^\>]*?|\!--.*?--)\>)' # <...>
+ENTITY_RE = r'(&[\#a-zA-Z0-9]*;)' # &amp;
+LINE_BREAK_RE = r' \n' # two spaces at end of line
+LINE_BREAK_2_RE = r' $' # two spaces at end of text
+
+
+def dequote(string):
+ """Remove quotes from around a string."""
+ if ( ( string.startswith('"') and string.endswith('"'))
+ or (string.startswith("'") and string.endswith("'")) ):
+ return string[1:-1]
+ else:
+ return string
+
+ATTR_RE = re.compile("\{@([^\}]*)=([^\}]*)}") # {@id=123}
+
+def handleAttributes(text, parent):
+ """Set values of an element based on attribute definitions ({@id=123})."""
+ def attributeCallback(match):
+ parent.set(match.group(1), match.group(2).replace('\n', ' '))
+ return ATTR_RE.sub(attributeCallback, text)
+
+
+"""
+The pattern classes
+-----------------------------------------------------------------------------
+"""
+
+class Pattern:
+ """Base class that inline patterns subclass. """
+
+ def __init__ (self, pattern, markdown_instance=None):
+ """
+ Create an instant of an inline pattern.
+
+ Keyword arguments:
+
+ * pattern: A regular expression that matches a pattern
+
+ """
+ self.pattern = pattern
+ self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern, re.DOTALL)
+
+ # Api for Markdown to pass safe_mode into instance
+ self.safe_mode = False
+ if markdown_instance:
+ self.markdown = markdown_instance
+
+ def getCompiledRegExp (self):
+ """ Return a compiled regular expression. """
+ return self.compiled_re
+
+ def handleMatch(self, m):
+ """Return a ElementTree element from the given match.
+
+ Subclasses should override this method.
+
+ Keyword arguments:
+
+ * m: A re match object containing a match of the pattern.
+
+ """
+ pass
+
+ def type(self):
+ """ Return class name, to define pattern type """
+ return self.__class__.__name__
+
+BasePattern = Pattern # for backward compatibility
+
+class SimpleTextPattern (Pattern):
+ """ Return a simple text of group(2) of a Pattern. """
+ def handleMatch(self, m):
+ text = m.group(2)
+ if text == markdown.INLINE_PLACEHOLDER_PREFIX:
+ return None
+ return text
+
+class SimpleTagPattern (Pattern):
+ """
+ Return element of type `tag` with a text attribute of group(3)
+ of a Pattern.
+
+ """
+ def __init__ (self, pattern, tag):
+ Pattern.__init__(self, pattern)
+ self.tag = tag
+
+ def handleMatch(self, m):
+ el = markdown.etree.Element(self.tag)
+ el.text = m.group(3)
+ return el
+
+
+class SubstituteTagPattern (SimpleTagPattern):
+ """ Return a eLement of type `tag` with no children. """
+ def handleMatch (self, m):
+ return markdown.etree.Element(self.tag)
+
+
+class BacktickPattern (Pattern):
+ """ Return a `<code>` element containing the matching text. """
+ def __init__ (self, pattern):
+ Pattern.__init__(self, pattern)
+ self.tag = "code"
+
+ def handleMatch(self, m):
+ el = markdown.etree.Element(self.tag)
+ el.text = markdown.AtomicString(m.group(3).strip())
+ return el
+
+
+class DoubleTagPattern (SimpleTagPattern):
+ """Return a ElementTree element nested in tag2 nested in tag1.
+
+ Useful for strong emphasis etc.
+
+ """
+ def handleMatch(self, m):
+ tag1, tag2 = self.tag.split(",")
+ el1 = markdown.etree.Element(tag1)
+ el2 = markdown.etree.SubElement(el1, tag2)
+ el2.text = m.group(3)
+ return el1
+
+
+class HtmlPattern (Pattern):
+ """ Store raw inline html and return a placeholder. """
+ def handleMatch (self, m):
+ rawhtml = m.group(2)
+ inline = True
+ place_holder = self.markdown.htmlStash.store(rawhtml)
+ return place_holder
+
+
+class LinkPattern (Pattern):
+ """ Return a link element from the given match. """
+ def handleMatch(self, m):
+ el = markdown.etree.Element("a")
+ el.text = m.group(2)
+ title = m.group(11)
+ href = m.group(9)
+
+ if href:
+ if href[0] == "<":
+ href = href[1:-1]
+ el.set("href", self.sanitize_url(href.strip()))
+ else:
+ el.set("href", "")
+
+ if title:
+ title = dequote(title) #.replace('"', "&quot;")
+ el.set("title", title)
+ return el
+
+ def sanitize_url(self, url):
+ """
+ Sanitize a url against xss attacks in "safe_mode".
+
+ Rather than specifically blacklisting `javascript:alert("XSS")` and all
+ its aliases (see <http://ha.ckers.org/xss.html>), we whitelist known
+ safe url formats. Most urls contain a network location, however some
+ are known not to (i.e.: mailto links). Script urls do not contain a
+ location. Additionally, for `javascript:...`, the scheme would be
+ "javascript" but some aliases will appear to `urlparse()` to have no
+ scheme. On top of that relative links (i.e.: "foo/bar.html") have no
+ scheme. Therefore we must check "path", "parameters", "query" and
+ "fragment" for any literal colons. We don't check "scheme" for colons
+ because it *should* never have any and "netloc" must allow the form:
+ `username:password@host:port`.
+
+ """
+ locless_schemes = ['', 'mailto', 'news']
+ scheme, netloc, path, params, query, fragment = url = urlparse(url)
+ safe_url = False
+ if netloc != '' or scheme in locless_schemes:
+ safe_url = True
+
+ for part in url[2:]:
+ if ":" in part:
+ safe_url = False
+
+ if self.markdown.safeMode and not safe_url:
+ return ''
+ else:
+ return urlunparse(url)
+
+class ImagePattern(LinkPattern):
+ """ Return a img element from the given match. """
+ def handleMatch(self, m):
+ el = markdown.etree.Element("img")
+ src_parts = m.group(9).split()
+ if src_parts:
+ src = src_parts[0]
+ if src[0] == "<" and src[-1] == ">":
+ src = src[1:-1]
+ el.set('src', self.sanitize_url(src))
+ else:
+ el.set('src', "")
+ if len(src_parts) > 1:
+ el.set('title', dequote(" ".join(src_parts[1:])))
+
+ if markdown.ENABLE_ATTRIBUTES:
+ truealt = handleAttributes(m.group(2), el)
+ else:
+ truealt = m.group(2)
+
+ el.set('alt', truealt)
+ return el
+
+class ReferencePattern(LinkPattern):
+ """ Match to a stored reference and return link element. """
+ def handleMatch(self, m):
+ if m.group(9):
+ id = m.group(9).lower()
+ else:
+ # if we got something like "[Google][]"
+ # we'll use "google" as the id
+ id = m.group(2).lower()
+
+ if not id in self.markdown.references: # ignore undefined refs
+ return None
+ href, title = self.markdown.references[id]
+
+ text = m.group(2)
+ return self.makeTag(href, title, text)
+
+ def makeTag(self, href, title, text):
+ el = markdown.etree.Element('a')
+
+ el.set('href', self.sanitize_url(href))
+ if title:
+ el.set('title', title)
+
+ el.text = text
+ return el
+
+
+class ImageReferencePattern (ReferencePattern):
+ """ Match to a stored reference and return img element. """
+ def makeTag(self, href, title, text):
+ el = markdown.etree.Element("img")
+ el.set("src", self.sanitize_url(href))
+ if title:
+ el.set("title", title)
+ el.set("alt", text)
+ return el
+
+
+class AutolinkPattern (Pattern):
+ """ Return a link Element given an autolink (`<http://example/com>`). """
+ def handleMatch(self, m):
+ el = markdown.etree.Element("a")
+ el.set('href', m.group(2))
+ el.text = markdown.AtomicString(m.group(2))
+ return el
+
+class AutomailPattern (Pattern):
+ """
+ Return a mailto link Element given an automail link (`<foo@example.com>`).
+ """
+ def handleMatch(self, m):
+ el = markdown.etree.Element('a')
+ email = m.group(2)
+ if email.startswith("mailto:"):
+ email = email[len("mailto:"):]
+
+ def codepoint2name(code):
+ """Return entity definition by code, or the code if not defined."""
+ entity = htmlentitydefs.codepoint2name.get(code)
+ if entity:
+ return "%s%s;" % (markdown.AMP_SUBSTITUTE, entity)
+ else:
+ return "%s#%d;" % (markdown.AMP_SUBSTITUTE, code)
+
+ letters = [codepoint2name(ord(letter)) for letter in email]
+ el.text = markdown.AtomicString(''.join(letters))
+
+ mailto = "mailto:" + email
+ mailto = "".join([markdown.AMP_SUBSTITUTE + '#%d;' %
+ ord(letter) for letter in mailto])
+ el.set('href', mailto)
+ return el
+
diff --git a/vendor/tornado/website/markdown/odict.py b/vendor/tornado/website/markdown/odict.py
new file mode 100644
index 0000000000..bf3ef07182
--- /dev/null
+++ b/vendor/tornado/website/markdown/odict.py
@@ -0,0 +1,162 @@
+class OrderedDict(dict):
+ """
+ A dictionary that keeps its keys in the order in which they're inserted.
+
+ Copied from Django's SortedDict with some modifications.
+
+ """
+ def __new__(cls, *args, **kwargs):
+ instance = super(OrderedDict, cls).__new__(cls, *args, **kwargs)
+ instance.keyOrder = []
+ return instance
+
+ def __init__(self, data=None):
+ if data is None:
+ data = {}
+ super(OrderedDict, self).__init__(data)
+ if isinstance(data, dict):
+ self.keyOrder = data.keys()
+ else:
+ self.keyOrder = []
+ for key, value in data:
+ if key not in self.keyOrder:
+ self.keyOrder.append(key)
+
+ def __deepcopy__(self, memo):
+ from copy import deepcopy
+ return self.__class__([(key, deepcopy(value, memo))
+ for key, value in self.iteritems()])
+
+ def __setitem__(self, key, value):
+ super(OrderedDict, self).__setitem__(key, value)
+ if key not in self.keyOrder:
+ self.keyOrder.append(key)
+
+ def __delitem__(self, key):
+ super(OrderedDict, self).__delitem__(key)
+ self.keyOrder.remove(key)
+
+ def __iter__(self):
+ for k in self.keyOrder:
+ yield k
+
+ def pop(self, k, *args):
+ result = super(OrderedDict, self).pop(k, *args)
+ try:
+ self.keyOrder.remove(k)
+ except ValueError:
+ # Key wasn't in the dictionary in the first place. No problem.
+ pass
+ return result
+
+ def popitem(self):
+ result = super(OrderedDict, self).popitem()
+ self.keyOrder.remove(result[0])
+ return result
+
+ def items(self):
+ return zip(self.keyOrder, self.values())
+
+ def iteritems(self):
+ for key in self.keyOrder:
+ yield key, super(OrderedDict, self).__getitem__(key)
+
+ def keys(self):
+ return self.keyOrder[:]
+
+ def iterkeys(self):
+ return iter(self.keyOrder)
+
+ def values(self):
+ return [super(OrderedDict, self).__getitem__(k) for k in self.keyOrder]
+
+ def itervalues(self):
+ for key in self.keyOrder:
+ yield super(OrderedDict, self).__getitem__(key)
+
+ def update(self, dict_):
+ for k, v in dict_.items():
+ self.__setitem__(k, v)
+
+ def setdefault(self, key, default):
+ if key not in self.keyOrder:
+ self.keyOrder.append(key)
+ return super(OrderedDict, self).setdefault(key, default)
+
+ def value_for_index(self, index):
+ """Return the value of the item at the given zero-based index."""
+ return self[self.keyOrder[index]]
+
+ def insert(self, index, key, value):
+ """Insert the key, value pair before the item with the given index."""
+ if key in self.keyOrder:
+ n = self.keyOrder.index(key)
+ del self.keyOrder[n]
+ if n < index:
+ index -= 1
+ self.keyOrder.insert(index, key)
+ super(OrderedDict, self).__setitem__(key, value)
+
+ def copy(self):
+ """Return a copy of this object."""
+ # This way of initializing the copy means it works for subclasses, too.
+ obj = self.__class__(self)
+ obj.keyOrder = self.keyOrder[:]
+ return obj
+
+ def __repr__(self):
+ """
+ Replace the normal dict.__repr__ with a version that returns the keys
+ in their sorted order.
+ """
+ return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
+
+ def clear(self):
+ super(OrderedDict, self).clear()
+ self.keyOrder = []
+
+ def index(self, key):
+ """ Return the index of a given key. """
+ return self.keyOrder.index(key)
+
+ def index_for_location(self, location):
+ """ Return index or None for a given location. """
+ if location == '_begin':
+ i = 0
+ elif location == '_end':
+ i = None
+ elif location.startswith('<') or location.startswith('>'):
+ i = self.index(location[1:])
+ if location.startswith('>'):
+ if i >= len(self):
+ # last item
+ i = None
+ else:
+ i += 1
+ else:
+ raise ValueError('Not a valid location: "%s". Location key '
+ 'must start with a ">" or "<".' % location)
+ return i
+
+ def add(self, key, value, location):
+ """ Insert by key location. """
+ i = self.index_for_location(location)
+ if i is not None:
+ self.insert(i, key, value)
+ else:
+ self.__setitem__(key, value)
+
+ def link(self, key, location):
+ """ Change location of an existing item. """
+ n = self.keyOrder.index(key)
+ del self.keyOrder[n]
+ i = self.index_for_location(location)
+ try:
+ if i is not None:
+ self.keyOrder.insert(i, key)
+ else:
+ self.keyOrder.append(key)
+ except Error:
+ # restore to prevent data loss and reraise
+ self.keyOrder.insert(n, key)
+ raise Error
diff --git a/vendor/tornado/website/markdown/postprocessors.py b/vendor/tornado/website/markdown/postprocessors.py
new file mode 100644
index 0000000000..80227bb909
--- /dev/null
+++ b/vendor/tornado/website/markdown/postprocessors.py
@@ -0,0 +1,77 @@
+"""
+POST-PROCESSORS
+=============================================================================
+
+Markdown also allows post-processors, which are similar to preprocessors in
+that they need to implement a "run" method. However, they are run after core
+processing.
+
+"""
+
+
+import markdown
+
+class Processor:
+ def __init__(self, markdown_instance=None):
+ if markdown_instance:
+ self.markdown = markdown_instance
+
+class Postprocessor(Processor):
+ """
+ Postprocessors are run after the ElementTree it converted back into text.
+
+ Each Postprocessor implements a "run" method that takes a pointer to a
+ text string, modifies it as necessary and returns a text string.
+
+ Postprocessors must extend markdown.Postprocessor.
+
+ """
+
+ def run(self, text):
+ """
+ Subclasses of Postprocessor should implement a `run` method, which
+ takes the html document as a single text string and returns a
+ (possibly modified) string.
+
+ """
+ pass
+
+
+class RawHtmlPostprocessor(Postprocessor):
+ """ Restore raw html to the document. """
+
+ def run(self, text):
+ """ Iterate over html stash and restore "safe" html. """
+ for i in range(self.markdown.htmlStash.html_counter):
+ html, safe = self.markdown.htmlStash.rawHtmlBlocks[i]
+ if self.markdown.safeMode and not safe:
+ if str(self.markdown.safeMode).lower() == 'escape':
+ html = self.escape(html)
+ elif str(self.markdown.safeMode).lower() == 'remove':
+ html = ''
+ else:
+ html = markdown.HTML_REMOVED_TEXT
+ if safe or not self.markdown.safeMode:
+ text = text.replace("<p>%s</p>" %
+ (markdown.preprocessors.HTML_PLACEHOLDER % i),
+ html + "\n")
+ text = text.replace(markdown.preprocessors.HTML_PLACEHOLDER % i,
+ html)
+ return text
+
+ def escape(self, html):
+ """ Basic html escaping """
+ html = html.replace('&', '&amp;')
+ html = html.replace('<', '&lt;')
+ html = html.replace('>', '&gt;')
+ return html.replace('"', '&quot;')
+
+
+class AndSubstitutePostprocessor(Postprocessor):
+ """ Restore valid entities """
+ def __init__(self):
+ pass
+
+ def run(self, text):
+ text = text.replace(markdown.AMP_SUBSTITUTE, "&")
+ return text
diff --git a/vendor/tornado/website/markdown/preprocessors.py b/vendor/tornado/website/markdown/preprocessors.py
new file mode 100644
index 0000000000..712a1e8755
--- /dev/null
+++ b/vendor/tornado/website/markdown/preprocessors.py
@@ -0,0 +1,214 @@
+
+"""
+PRE-PROCESSORS
+=============================================================================
+
+Preprocessors work on source text before we start doing anything too
+complicated.
+"""
+
+import re
+import markdown
+
+HTML_PLACEHOLDER_PREFIX = markdown.STX+"wzxhzdk:"
+HTML_PLACEHOLDER = HTML_PLACEHOLDER_PREFIX + "%d" + markdown.ETX
+
+class Processor:
+ def __init__(self, markdown_instance=None):
+ if markdown_instance:
+ self.markdown = markdown_instance
+
+class Preprocessor (Processor):
+ """
+ Preprocessors are run after the text is broken into lines.
+
+ Each preprocessor implements a "run" method that takes a pointer to a
+ list of lines of the document, modifies it as necessary and returns
+ either the same pointer or a pointer to a new list.
+
+ Preprocessors must extend markdown.Preprocessor.
+
+ """
+ def run(self, lines):
+ """
+ Each subclass of Preprocessor should override the `run` method, which
+ takes the document as a list of strings split by newlines and returns
+ the (possibly modified) list of lines.
+
+ """
+ pass
+
+class HtmlStash:
+ """
+ This class is used for stashing HTML objects that we extract
+ in the beginning and replace with place-holders.
+ """
+
+ def __init__ (self):
+ """ Create a HtmlStash. """
+ self.html_counter = 0 # for counting inline html segments
+ self.rawHtmlBlocks=[]
+
+ def store(self, html, safe=False):
+ """
+ Saves an HTML segment for later reinsertion. Returns a
+ placeholder string that needs to be inserted into the
+ document.
+
+ Keyword arguments:
+
+ * html: an html segment
+ * safe: label an html segment as safe for safemode
+
+ Returns : a placeholder string
+
+ """
+ self.rawHtmlBlocks.append((html, safe))
+ placeholder = HTML_PLACEHOLDER % self.html_counter
+ self.html_counter += 1
+ return placeholder
+
+ def reset(self):
+ self.html_counter = 0
+ self.rawHtmlBlocks = []
+
+
+class HtmlBlockPreprocessor(Preprocessor):
+ """Remove html blocks from the text and store them for later retrieval."""
+
+ right_tag_patterns = ["</%s>", "%s>"]
+
+ def _get_left_tag(self, block):
+ return block[1:].replace(">", " ", 1).split()[0].lower()
+
+ def _get_right_tag(self, left_tag, block):
+ for p in self.right_tag_patterns:
+ tag = p % left_tag
+ i = block.rfind(tag)
+ if i > 2:
+ return tag.lstrip("<").rstrip(">"), i + len(p)-2 + len(left_tag)
+ return block.rstrip()[-len(left_tag)-2:-1].lower(), len(block)
+
+ def _equal_tags(self, left_tag, right_tag):
+ if left_tag == 'div' or left_tag[0] in ['?', '@', '%']: # handle PHP, etc.
+ return True
+ if ("/" + left_tag) == right_tag:
+ return True
+ if (right_tag == "--" and left_tag == "--"):
+ return True
+ elif left_tag == right_tag[1:] \
+ and right_tag[0] != "<":
+ return True
+ else:
+ return False
+
+ def _is_oneliner(self, tag):
+ return (tag in ['hr', 'hr/'])
+
+ def run(self, lines):
+ text = "\n".join(lines)
+ new_blocks = []
+ text = text.split("\n\n")
+ items = []
+ left_tag = ''
+ right_tag = ''
+ in_tag = False # flag
+
+ while text:
+ block = text[0]
+ if block.startswith("\n"):
+ block = block[1:]
+ text = text[1:]
+
+ if block.startswith("\n"):
+ block = block[1:]
+
+ if not in_tag:
+ if block.startswith("<"):
+ left_tag = self._get_left_tag(block)
+ right_tag, data_index = self._get_right_tag(left_tag, block)
+
+ if data_index < len(block):
+ text.insert(0, block[data_index:])
+ block = block[:data_index]
+
+ if not (markdown.isBlockLevel(left_tag) \
+ or block[1] in ["!", "?", "@", "%"]):
+ new_blocks.append(block)
+ continue
+
+ if self._is_oneliner(left_tag):
+ new_blocks.append(block.strip())
+ continue
+
+ if block[1] == "!":
+ # is a comment block
+ left_tag = "--"
+ right_tag, data_index = self._get_right_tag(left_tag, block)
+ # keep checking conditions below and maybe just append
+
+ if block.rstrip().endswith(">") \
+ and self._equal_tags(left_tag, right_tag):
+ new_blocks.append(
+ self.markdown.htmlStash.store(block.strip()))
+ continue
+ else: #if not block[1] == "!":
+ # if is block level tag and is not complete
+
+ if markdown.isBlockLevel(left_tag) or left_tag == "--" \
+ and not block.rstrip().endswith(">"):
+ items.append(block.strip())
+ in_tag = True
+ else:
+ new_blocks.append(
+ self.markdown.htmlStash.store(block.strip()))
+
+ continue
+
+ new_blocks.append(block)
+
+ else:
+ items.append(block.strip())
+
+ right_tag, data_index = self._get_right_tag(left_tag, block)
+
+ if self._equal_tags(left_tag, right_tag):
+ # if find closing tag
+ in_tag = False
+ new_blocks.append(
+ self.markdown.htmlStash.store('\n\n'.join(items)))
+ items = []
+
+ if items:
+ new_blocks.append(self.markdown.htmlStash.store('\n\n'.join(items)))
+ new_blocks.append('\n')
+
+ new_text = "\n\n".join(new_blocks)
+ return new_text.split("\n")
+
+
+class ReferencePreprocessor(Preprocessor):
+ """ Remove reference definitions from text and store for later use. """
+
+ RE = re.compile(r'^(\ ?\ ?\ ?)\[([^\]]*)\]:\s*([^ ]*)(.*)$', re.DOTALL)
+
+ def run (self, lines):
+ new_text = [];
+ for line in lines:
+ m = self.RE.match(line)
+ if m:
+ id = m.group(2).strip().lower()
+ t = m.group(4).strip() # potential title
+ if not t:
+ self.markdown.references[id] = (m.group(3), t)
+ elif (len(t) >= 2
+ and (t[0] == t[-1] == "\""
+ or t[0] == t[-1] == "\'"
+ or (t[0] == "(" and t[-1] == ")") ) ):
+ self.markdown.references[id] = (m.group(3), t[1:-1])
+ else:
+ new_text.append(line)
+ else:
+ new_text.append(line)
+
+ return new_text #+ "\n"
diff --git a/vendor/tornado/website/markdown/treeprocessors.py b/vendor/tornado/website/markdown/treeprocessors.py
new file mode 100644
index 0000000000..1dc612a95e
--- /dev/null
+++ b/vendor/tornado/website/markdown/treeprocessors.py
@@ -0,0 +1,329 @@
+import markdown
+import re
+
+def isString(s):
+ """ Check if it's string """
+ return isinstance(s, unicode) or isinstance(s, str)
+
+class Processor:
+ def __init__(self, markdown_instance=None):
+ if markdown_instance:
+ self.markdown = markdown_instance
+
+class Treeprocessor(Processor):
+ """
+ Treeprocessors are run on the ElementTree object before serialization.
+
+ Each Treeprocessor implements a "run" method that takes a pointer to an
+ ElementTree, modifies it as necessary and returns an ElementTree
+ object.
+
+ Treeprocessors must extend markdown.Treeprocessor.
+
+ """
+ def run(self, root):
+ """
+ Subclasses of Treeprocessor should implement a `run` method, which
+ takes a root ElementTree. This method can return another ElementTree
+ object, and the existing root ElementTree will be replaced, or it can
+ modify the current tree and return None.
+ """
+ pass
+
+
+class InlineProcessor(Treeprocessor):
+ """
+ A Treeprocessor that traverses a tree, applying inline patterns.
+ """
+
+ def __init__ (self, md):
+ self.__placeholder_prefix = markdown.INLINE_PLACEHOLDER_PREFIX
+ self.__placeholder_suffix = markdown.ETX
+ self.__placeholder_length = 4 + len(self.__placeholder_prefix) \
+ + len(self.__placeholder_suffix)
+ self.__placeholder_re = re.compile(markdown.INLINE_PLACEHOLDER % r'([0-9]{4})')
+ self.markdown = md
+
+ def __makePlaceholder(self, type):
+ """ Generate a placeholder """
+ id = "%04d" % len(self.stashed_nodes)
+ hash = markdown.INLINE_PLACEHOLDER % id
+ return hash, id
+
+ def __findPlaceholder(self, data, index):
+ """
+ Extract id from data string, start from index
+
+ Keyword arguments:
+
+ * data: string
+ * index: index, from which we start search
+
+ Returns: placeholder id and string index, after the found placeholder.
+ """
+
+ m = self.__placeholder_re.search(data, index)
+ if m:
+ return m.group(1), m.end()
+ else:
+ return None, index + 1
+
+ def __stashNode(self, node, type):
+ """ Add node to stash """
+ placeholder, id = self.__makePlaceholder(type)
+ self.stashed_nodes[id] = node
+ return placeholder
+
+ def __handleInline(self, data, patternIndex=0):
+ """
+ Process string with inline patterns and replace it
+ with placeholders
+
+ Keyword arguments:
+
+ * data: A line of Markdown text
+ * patternIndex: The index of the inlinePattern to start with
+
+ Returns: String with placeholders.
+
+ """
+ if not isinstance(data, markdown.AtomicString):
+ startIndex = 0
+ while patternIndex < len(self.markdown.inlinePatterns):
+ data, matched, startIndex = self.__applyPattern(
+ self.markdown.inlinePatterns.value_for_index(patternIndex),
+ data, patternIndex, startIndex)
+ if not matched:
+ patternIndex += 1
+ return data
+
+ def __processElementText(self, node, subnode, isText=True):
+ """
+ Process placeholders in Element.text or Element.tail
+ of Elements popped from self.stashed_nodes.
+
+ Keywords arguments:
+
+ * node: parent node
+ * subnode: processing node
+ * isText: bool variable, True - it's text, False - it's tail
+
+ Returns: None
+
+ """
+ if isText:
+ text = subnode.text
+ subnode.text = None
+ else:
+ text = subnode.tail
+ subnode.tail = None
+
+ childResult = self.__processPlaceholders(text, subnode)
+
+ if not isText and node is not subnode:
+ pos = node.getchildren().index(subnode)
+ node.remove(subnode)
+ else:
+ pos = 0
+
+ childResult.reverse()
+ for newChild in childResult:
+ node.insert(pos, newChild)
+
+ def __processPlaceholders(self, data, parent):
+ """
+ Process string with placeholders and generate ElementTree tree.
+
+ Keyword arguments:
+
+ * data: string with placeholders instead of ElementTree elements.
+ * parent: Element, which contains processing inline data
+
+ Returns: list with ElementTree elements with applied inline patterns.
+ """
+ def linkText(text):
+ if text:
+ if result:
+ if result[-1].tail:
+ result[-1].tail += text
+ else:
+ result[-1].tail = text
+ else:
+ if parent.text:
+ parent.text += text
+ else:
+ parent.text = text
+
+ result = []
+ strartIndex = 0
+ while data:
+ index = data.find(self.__placeholder_prefix, strartIndex)
+ if index != -1:
+ id, phEndIndex = self.__findPlaceholder(data, index)
+
+ if id in self.stashed_nodes:
+ node = self.stashed_nodes.get(id)
+
+ if index > 0:
+ text = data[strartIndex:index]
+ linkText(text)
+
+ if not isString(node): # it's Element
+ for child in [node] + node.getchildren():
+ if child.tail:
+ if child.tail.strip():
+ self.__processElementText(node, child, False)
+ if child.text:
+ if child.text.strip():
+ self.__processElementText(child, child)
+ else: # it's just a string
+ linkText(node)
+ strartIndex = phEndIndex
+ continue
+
+ strartIndex = phEndIndex
+ result.append(node)
+
+ else: # wrong placeholder
+ end = index + len(prefix)
+ linkText(data[strartIndex:end])
+ strartIndex = end
+ else:
+ text = data[strartIndex:]
+ linkText(text)
+ data = ""
+
+ return result
+
+ def __applyPattern(self, pattern, data, patternIndex, startIndex=0):
+ """
+ Check if the line fits the pattern, create the necessary
+ elements, add it to stashed_nodes.
+
+ Keyword arguments:
+
+ * data: the text to be processed
+ * pattern: the pattern to be checked
+ * patternIndex: index of current pattern
+ * startIndex: string index, from which we starting search
+
+ Returns: String with placeholders instead of ElementTree elements.
+
+ """
+ match = pattern.getCompiledRegExp().match(data[startIndex:])
+ leftData = data[:startIndex]
+
+ if not match:
+ return data, False, 0
+
+ node = pattern.handleMatch(match)
+
+ if node is None:
+ return data, True, len(leftData) + match.span(len(match.groups()))[0]
+
+ if not isString(node):
+ if not isinstance(node.text, markdown.AtomicString):
+ # We need to process current node too
+ for child in [node] + node.getchildren():
+ if not isString(node):
+ if child.text:
+ child.text = self.__handleInline(child.text,
+ patternIndex + 1)
+ if child.tail:
+ child.tail = self.__handleInline(child.tail,
+ patternIndex)
+
+ placeholder = self.__stashNode(node, pattern.type())
+
+ return "%s%s%s%s" % (leftData,
+ match.group(1),
+ placeholder, match.groups()[-1]), True, 0
+
+ def run(self, tree):
+ """Apply inline patterns to a parsed Markdown tree.
+
+ Iterate over ElementTree, find elements with inline tag, apply inline
+ patterns and append newly created Elements to tree. If you don't
+ want process your data with inline paterns, instead of normal string,
+ use subclass AtomicString:
+
+ node.text = markdown.AtomicString("data won't be processed with inline patterns")
+
+ Arguments:
+
+ * markdownTree: ElementTree object, representing Markdown tree.
+
+ Returns: ElementTree object with applied inline patterns.
+
+ """
+ self.stashed_nodes = {}
+
+ stack = [tree]
+
+ while stack:
+ currElement = stack.pop()
+ insertQueue = []
+ for child in currElement.getchildren():
+ if child.text and not isinstance(child.text, markdown.AtomicString):
+ text = child.text
+ child.text = None
+ lst = self.__processPlaceholders(self.__handleInline(
+ text), child)
+ stack += lst
+ insertQueue.append((child, lst))
+
+ if child.getchildren():
+ stack.append(child)
+
+ for element, lst in insertQueue:
+ if element.text:
+ element.text = \
+ markdown.inlinepatterns.handleAttributes(element.text,
+ element)
+ i = 0
+ for newChild in lst:
+ # Processing attributes
+ if newChild.tail:
+ newChild.tail = \
+ markdown.inlinepatterns.handleAttributes(newChild.tail,
+ element)
+ if newChild.text:
+ newChild.text = \
+ markdown.inlinepatterns.handleAttributes(newChild.text,
+ newChild)
+ element.insert(i, newChild)
+ i += 1
+ return tree
+
+
+class PrettifyTreeprocessor(Treeprocessor):
+ """ Add linebreaks to the html document. """
+
+ def _prettifyETree(self, elem):
+ """ Recursively add linebreaks to ElementTree children. """
+
+ i = "\n"
+ if markdown.isBlockLevel(elem.tag) and elem.tag not in ['code', 'pre']:
+ if (not elem.text or not elem.text.strip()) \
+ and len(elem) and markdown.isBlockLevel(elem[0].tag):
+ elem.text = i
+ for e in elem:
+ if markdown.isBlockLevel(e.tag):
+ self._prettifyETree(e)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+
+ def run(self, root):
+ """ Add linebreaks to ElementTree root object. """
+
+ self._prettifyETree(root)
+ # Do <br />'s seperately as they are often in the middle of
+ # inline content and missed by _prettifyETree.
+ brs = root.getiterator('br')
+ for br in brs:
+ if not br.tail or not br.tail.strip():
+ br.tail = '\n'
+ else:
+ br.tail = '\n%s' % br.tail
diff --git a/vendor/tornado/website/static/base.css b/vendor/tornado/website/static/base.css
new file mode 100644
index 0000000000..543d6f24c3
--- /dev/null
+++ b/vendor/tornado/website/static/base.css
@@ -0,0 +1,120 @@
+body {
+ background: white;
+ color: black;
+ font-family: Georgia, serif;
+ font-size: 11pt;
+ margin: 10px;
+ margin-top: 15px;
+ margin-bottom: 15px;
+}
+
+h1,
+h2,
+h3,
+h4 {
+ font-family: Calibri, sans-serif;
+ margin: 0;
+}
+
+img {
+ border: 0;
+}
+
+pre,
+code {
+ color: #060;
+}
+
+a,
+a code {
+ color: #216093;
+}
+
+table {
+ border-collapse: collapse;
+ border: 0;
+}
+
+td {
+ border: 0;
+ padding: 0;
+}
+
+#body {
+ margin: auto;
+ max-width: 850px;
+}
+
+#header {
+ margin-bottom: 15px;
+ margin-right: 30px;
+}
+
+#content,
+#footer {
+ margin-left: 31px;
+ margin-right: 31px;
+}
+
+#content p,
+#content li,
+#footer {
+ line-height: 16pt;
+}
+
+#content pre {
+ line-height: 14pt;
+ margin: 17pt;
+ padding-left: 1em;
+ border-left: 1px solid #ccc;
+}
+
+#footer {
+ margin-top: 5em;
+}
+
+#header .logo {
+ line-height: 0;
+ padding-bottom: 5px;
+ padding-right: 15px;
+}
+
+#header .logo img {
+ width: 286px;
+ height: 72px;
+}
+
+#header .title {
+ vertical-align: bottom;
+}
+
+#header .title h1 {
+ font-size: 35px;
+ font-weight: normal;
+}
+
+#header .title h1,
+#header .title h1 a {
+ color: #666;
+}
+
+#content h1,
+#content h2,
+#content h3 {
+ color: #4d8cbf;
+ margin-bottom: 2pt;
+ margin-top: 17pt;
+}
+
+#content h2 {
+ font-size: 19pt;
+}
+
+#content h3 {
+ font-size: 15pt;
+}
+
+#content p {
+ margin: 0;
+ margin-bottom: 1em;
+}
diff --git a/vendor/tornado/website/static/facebook.png b/vendor/tornado/website/static/facebook.png
new file mode 100755
index 0000000000..47738323ed
--- /dev/null
+++ b/vendor/tornado/website/static/facebook.png
Binary files differ
diff --git a/vendor/tornado/website/static/friendfeed.png b/vendor/tornado/website/static/friendfeed.png
new file mode 100755
index 0000000000..ac09f4e453
--- /dev/null
+++ b/vendor/tornado/website/static/friendfeed.png
Binary files differ
diff --git a/vendor/tornado/website/static/robots.txt b/vendor/tornado/website/static/robots.txt
new file mode 100644
index 0000000000..0ad279c736
--- /dev/null
+++ b/vendor/tornado/website/static/robots.txt
@@ -0,0 +1,2 @@
+User-Agent: *
+Disallow:
diff --git a/vendor/tornado/website/static/tornado-0.1.tar.gz b/vendor/tornado/website/static/tornado-0.1.tar.gz
new file mode 100644
index 0000000000..d06c1e04ea
--- /dev/null
+++ b/vendor/tornado/website/static/tornado-0.1.tar.gz
Binary files differ
diff --git a/vendor/tornado/website/static/tornado-0.2.tar.gz b/vendor/tornado/website/static/tornado-0.2.tar.gz
new file mode 100644
index 0000000000..6aca327aee
--- /dev/null
+++ b/vendor/tornado/website/static/tornado-0.2.tar.gz
Binary files differ
diff --git a/vendor/tornado/website/static/tornado.png b/vendor/tornado/website/static/tornado.png
new file mode 100644
index 0000000000..a920aa566f
--- /dev/null
+++ b/vendor/tornado/website/static/tornado.png
Binary files differ
diff --git a/vendor/tornado/website/static/twitter.png b/vendor/tornado/website/static/twitter.png
new file mode 100755
index 0000000000..5099c62ee5
--- /dev/null
+++ b/vendor/tornado/website/static/twitter.png
Binary files differ
diff --git a/vendor/tornado/website/templates/base.html b/vendor/tornado/website/templates/base.html
new file mode 100644
index 0000000000..48da016781
--- /dev/null
+++ b/vendor/tornado/website/templates/base.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <title>{% block title %}Tornado Web Server{% end %}</title>
+ <link rel="stylesheet" href="/static/base.css" type="text/css"/>
+ {% block head %}{% end %}
+ </head>
+ <body>
+ <div id="body">
+ <div id="header">
+ <table>
+ <tr>
+ <td class="logo"><a href="/"><img src="/static/tornado.png" alt="Tornado"/></a></td>
+ <td class="title">{% block headertitle %}{% end %}</td>
+ </tr>
+ </table>
+ </div>
+ <div id="content">{% block body %}{% end %}</div>
+ <div id="footer">
+ <div>Tornado is one of <a href="http://developers.facebook.com/opensource.php">Facebook's open source technologies</a>. It is available under the <a href="http://www.apache.org/licenses/LICENSE-2.0.html">Apache Licence, Version 2.0</a>.</div>
+ <div>This web site and all documentation is licensed under <a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons 3.0</a>.</div>
+ </div>
+ </div>
+ {% block bottom %}{% end %}
+ </body>
+</html>
diff --git a/vendor/tornado/website/templates/documentation.html b/vendor/tornado/website/templates/documentation.html
new file mode 100644
index 0000000000..8c28740874
--- /dev/null
+++ b/vendor/tornado/website/templates/documentation.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+
+{% block title %}Tornado Web Server Documentation{% end %}
+
+{% block headertitle %}<h1>documentation</h1>{% end %}
+
+{% block body %}
+ {{ markdown("documentation.txt", toc=True) }}
+{% end %}
diff --git a/vendor/tornado/website/templates/documentation.txt b/vendor/tornado/website/templates/documentation.txt
new file mode 100644
index 0000000000..81262a605a
--- /dev/null
+++ b/vendor/tornado/website/templates/documentation.txt
@@ -0,0 +1,866 @@
+Overview
+--------
+[FriendFeed](http://friendfeed.com/)'s web server is a relatively simple,
+non-blocking web server written in Python. The FriendFeed application is
+written using a web framework that looks a bit like
+[web.py](http://webpy.org/) or Google's
+[webapp](http://code.google.com/appengine/docs/python/tools/webapp/),
+but with additional tools and optimizations to take advantage of the
+non-blocking web server and tools.
+
+[Tornado](http://github.com/facebook/tornado) is an open source
+version of this web server and some of the tools we use most often at
+FriendFeed. The framework is distinct from most mainstream web server
+frameworks (and certainly most Python frameworks) because it is
+non-blocking and reasonably fast. Because it is non-blocking
+and uses [epoll](http://www.kernel.org/doc/man-pages/online/pages/man4/epoll.4.html), it can handle 1000s of simultaneous standing connections,
+which means the framework is ideal for real-time web services. We built the
+web server specifically to handle FriendFeed's real-time features &mdash;
+every active user of FriendFeed maintains an open connection to the
+FriendFeed servers. (For more information on scaling servers to support
+thousands of clients, see
+[The C10K problem](http://www.kegel.com/c10k.html).)
+
+Here is the canonical "Hello, world" example app:
+
+ import tornado.httpserver
+ import tornado.ioloop
+ import tornado.web
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.write("Hello, world")
+
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ ])
+
+ if __name__ == "__main__":
+ http_server = tornado.httpserver.HTTPServer(application)
+ http_server.listen(8888)
+ tornado.ioloop.IOLoop.instance().start()
+
+See [Tornado walkthrough](#tornado-walkthrough) below for a detailed
+walkthrough of the `tornado.web` package.
+
+We attempted to clean up the code base to reduce interdependencies between
+modules, so you should (theoretically) be able to use any of the modules
+independently in your project without using the whole package.
+
+
+Download
+--------
+Download the most recent version of Tornado from GitHub:
+
+> [tornado-0.2.tar.gz](/static/tornado-0.2.tar.gz)
+
+You can also [browse the source](http://github.com/facebook/tornado) on GitHub. To install Tornado:
+
+ tar xvzf tornado-0.2.tar.gz
+ cd tornado-0.2
+ python setup.py build
+ sudo python setup.py install
+
+After installation, you should be able to run any of the demos in the `demos`
+directory included with the Tornado package.
+
+ ./demos/helloworld/helloworld.py
+
+### Prerequisites
+
+Tornado has been tested on Python 2.5 and 2.6. To use all of the features of Tornado, you need to have [PycURL](http://pycurl.sourceforge.net/) and a JSON library like [simplejson](http://pypi.python.org/pypi/simplejson/) installed. Complete installation instructions for Mac OS X and Ubuntu are included below for convenience.
+
+**Mac OS X 10.5/10.6**
+
+ sudo easy_install setuptools pycurl==7.16.2.1 simplejson
+
+**Ubuntu Linux**
+
+ sudo apt-get install python-dev python-pycurl python-simplejson
+
+
+Module index
+------------
+The most important module is [`web`](http://github.com/facebook/tornado/blob/master/tornado/web.py), which is the web framework
+that includes most of the meat of the Tornado package. The other modules
+are tools that make `web` more useful. See
+[Tornado walkthrough](#tornado-walkthrough) below for a detailed
+walkthrough of the `web` package.
+
+### Main modules
+ * [`web`](http://github.com/facebook/tornado/blob/master/tornado/web.py) - The web framework on which FriendFeed is built. `web` incorporates most of the important features of Tornado
+ * [`escape`](http://github.com/facebook/tornado/blob/master/tornado/escape.py) - XHTML, JSON, and URL encoding/decoding methods
+ * [`database`](http://github.com/facebook/tornado/blob/master/tornado/database.py) - A simple wrapper around `MySQLdb` to make MySQL easier to use
+ * [`template`](http://github.com/facebook/tornado/blob/master/tornado/template.py) - A Python-based web templating language
+ * [`httpclient`](http://github.com/facebook/tornado/blob/master/tornado/httpclient.py) - A non-blocking HTTP client designed to work with `web` and `httpserver`
+ * [`auth`](http://github.com/facebook/tornado/blob/master/tornado/auth.py) - Implementation of third party authentication and authorization schemes (Google OpenID/OAuth, Facebook Platform, Yahoo BBAuth, FriendFeed OpenID/OAuth, Twitter OAuth)
+ * [`locale`](http://github.com/facebook/tornado/blob/master/tornado/locale.py) - Localization/translation support
+ * [`options`](http://github.com/facebook/tornado/blob/master/tornado/options.py) - Command line and config file parsing, optimized for server environments
+
+### Low-level modules
+ * [`httpserver`](http://github.com/facebook/tornado/blob/master/tornado/httpserver.py) - A very simple HTTP server built on which `web` is built
+ * [`iostream`](http://github.com/facebook/tornado/blob/master/tornado/iostream.py) - A simple wrapper around non-blocking sockets to aide common reading and writing patterns
+ * [`ioloop`](http://github.com/facebook/tornado/blob/master/tornado/ioloop.py) - Core I/O loop
+
+### Random modules
+ * [`s3server`](http://github.com/facebook/tornado/blob/master/tornado/s3server.py) - A web server that implements most of the [Amazon S3](http://aws.amazon.com/s3/) interface, backed by local file storage
+
+
+Tornado walkthrough
+-------------------
+
+### Request handlers and request arguments
+
+A Tornado web application maps URLs or URL patterns to subclasses of
+`tornado.web.RequestHandler`. Those classes define `get()` or `post()`
+methods to handle HTTP `GET` or `POST` requests to that URL.
+
+This code maps the root URL `/` to `MainHandler` and the URL pattern
+`/story/([0-9]+)` to `StoryHandler`. Regular expression groups are passed
+as arguments to the `RequestHandler` methods:
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.write("You requested the main page")
+
+ class StoryHandler(tornado.web.RequestHandler):
+ def get(self, story_id):
+ self.write("You requested the story " + story_id)
+
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ (r"/story/([0-9]+)", StoryHandler),
+ ])
+
+You can get query string arguments and parse `POST` bodies with the
+`get_argument()` method:
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.write('<html><body><form action="/" method="post">'
+ '<input type="text" name="message">'
+ '<input type="submit" value="Submit">'
+ '</form></body></html>')
+
+ def post(self):
+ self.set_header("Content-Type", "text/plain")
+ self.write("You wrote " + self.get_argument("message"))
+
+If you want to send an error response to the client, e.g., 403 Unauthorized,
+you can just raise a `tornado.web.HTTPError` exception:
+
+ if not self.user_is_logged_in():
+ raise tornado.web.HTTPError(403)
+
+The request handler can access the object representing the current request
+with `self.request`. The `HTTPRequest` object includes a number of useful
+attribute, including:
+
+ * `arguments` - all of the `GET` and `POST` arguments
+ * `files` - all of the uploaded files (via `multipart/form-data` POST requests)
+ * `path` - the request path (everything before the `?`)
+ * `headers` - the request headers
+
+See the class definition for `HTTPRequest` in `httpserver` for a complete list
+of attributes.
+
+
+### Templates
+
+You can use any template language supported by Python, but Tornado ships
+with its own templating language that is a lot faster and more flexible
+than many of the most popular templating systems out there. See the
+[`template`](http://github.com/facebook/tornado/blob/master/tornado/template.py) module documentation for complete documentation.
+
+A Tornado template is just HTML (or any other text-based format) with
+Python control sequences and expressions embedded within the markup:
+
+ <html>
+ <head>
+ <title>{{ title }}</title>
+ </head>
+ <body>
+ <ul>
+ {% for item in items %}
+ <li>{{ escape(item) }}</li>
+ {% end %}
+ </ul>
+ </body>
+ </html>
+
+If you saved this template as "template.html" and put it in the same
+directory as your Python file, you could render this template with:
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ items = ["Item 1", "Item 2", "Item 3"]
+ self.render("template.html", title="My title", items=items)
+
+Tornado templates support *control statements* and *expressions*. Control
+statements are surronded by `{%` and `%}`, e.g., `{% if len(items) > 2 %}`.
+Expressions are surrounded by `{{` and `}}`, e.g., `{{ items[0] }}`.
+
+Control statements more or less map exactly to Python statements. We support
+`if`, `for`, `while`, and `try`, all of which are terminated with `{% end %}`.
+We also support *template inheritance* using the `extends` and `block`
+statements, which are described in detail in the documentation for the
+[`template` module](http://github.com/facebook/tornado/blob/master/tornado/template.py).
+
+Expressions can be any Python expression, including function calls. We
+support the functions `escape`, `url_escape`, and `json_encode` by default,
+and you can pass other functions into the template simply by passing them
+as keyword arguments to the template render function:
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.render("template.html", add=self.add)
+
+ def add(self, x, y):
+ return x + y
+
+When you are building a real application, you are going to want to use
+all of the features of Tornado templates, especially template inheritance.
+Read all about those features in the [`template` module](http://github.com/facebook/tornado/blob/master/tornado/template.py)
+section.
+
+Under the hood, Tornado templates are translated directly to Python.
+The expressions you include in your template are copied verbatim into
+a Python function representing your template. We don't try to prevent
+anything in the template language; we created it explicitly to provide
+the flexibility that other, stricter templating systems prevent.
+Consequently, if you write random stuff inside of your template expressions,
+you will get random Python errors when you execute the template.
+
+
+### Cookies and secure cookies
+
+You can set cookies in the user's browser with the `set_cookie` method:
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ if not self.get_cookie("mycookie"):
+ self.set_cookie("mycookie", "myvalue")
+ self.write("Your cookie was not set yet!")
+ else:
+ self.write("Your cookie was set!")
+
+Cookies are easily forged by malicious clients. If you need to set cookies
+to, e.g., save the user ID of the currently logged in user, you need to
+sign your cookies to prevent forgery. Tornado supports this out of the
+box with the `set_secure_cookie` and `get_secure_cookie` methods. To use
+these methods, you need to specify a secret key named `cookie_secret` when
+you create your application. You can pass in application settings as keyword
+arguments to your application:
+
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
+
+Signed cookies contain the encoded value of the cookie in addition to a
+timestamp and an [HMAC](http://en.wikipedia.org/wiki/HMAC) signature. If the
+cookie is old or if the signature doesn't match, `get_secure_cookie` will
+return `None` just as if the cookie isn't set. The secure version of the
+example above:
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ if not self.get_secure_cookie("mycookie"):
+ self.set_secure_cookie("mycookie", "myvalue")
+ self.write("Your cookie was not set yet!")
+ else:
+ self.write("Your cookie was set!")
+
+
+### User authentication
+
+The currently authenticated user is available in every request handler
+as `self.current_user`, and in every template as `current_user`. By
+default, `current_user` is `None`.
+
+To implement user authentication in your application, you need to
+override the `get_current_user()` method in your request handlers to
+determine the current user based on, e.g., the value of a cookie.
+Here is an example that lets users log into the application simply
+by specifying a nickname, which is then saved in a cookie:
+
+ class BaseHandler(tornado.web.RequestHandler):
+ def get_current_user(self):
+ return self.get_secure_cookie("user")
+
+ class MainHandler(BaseHandler):
+ def get(self):
+ if not self.current_user:
+ self.redirect("/login")
+ return
+ name = tornado.escape.xhtml_escape(self.current_user)
+ self.write("Hello, " + name)
+
+ class LoginHandler(BaseHandler):
+ def get(self):
+ self.write('<html><body><form action="/login" method="post">'
+ 'Name: <input type="text" name="name">'
+ '<input type="submit" value="Sign in">'
+ '</form></body></html>')
+
+ def post(self):
+ self.set_secure_cookie("user", self.get_argument("name"))
+ self.redirect("/")
+
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ (r"/login", LoginHandler),
+ ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
+
+You can require that the user be logged in using the
+[Python decorator](http://www.python.org/dev/peps/pep-0318/)
+`tornado.web.authenticated`. If a request goes to a method with this
+decorator, and the user is not logged in, they will be redirected to
+`login_url` (another application setting). The example above could
+be rewritten:
+
+ class MainHandler(BaseHandler):
+ @tornado.web.authenticated
+ def get(self):
+ name = tornado.escape.xhtml_escape(self.current_user)
+ self.write("Hello, " + name)
+
+ settings = {
+ "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+ "login_url": "/login",
+ }
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ (r"/login", LoginHandler),
+ ], **settings)
+
+If you decorate `post()` methods with the `authenticated` decorator, and
+the user is not logged in, the server will send a `403` response.
+
+Tornado comes with built-in support for third-party authentication schemes
+like Google OAuth. See the [`auth` module](http://github.com/facebook/tornado/blob/master/tornado/auth.py) for more details. Check
+out the Tornado Blog example application for a complete example that
+uses authentication (and stores user data in a MySQL database).
+
+
+### Cross-site request forgery protection
+
+[Cross-site request forgery](http://en.wikipedia.org/wiki/Cross-site_request_forgery), or XSRF, is a common problem for personalized web applications. See the
+[Wikipedia article](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
+for more information on how XSRF works.
+
+The generally accepted solution to prevent XSRF is to cookie every user
+with an unpredictable value and include that value as an additional
+argument with every form submission on your site. If the cookie and the
+value in the form submission do not match, then the request is likely
+forged.
+
+Tornado comes with built-in XSRF protection. To include it in your site,
+include the application setting `xsrf_cookies`:
+
+ settings = {
+ "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+ "login_url": "/login",
+ "xsrf_cookies": True,
+ }
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ (r"/login", LoginHandler),
+ ], **settings)
+
+If `xsrf_cookies` is set, the Tornado web application will set the `_xsrf`
+cookie for all users and reject all `POST` requests hat do not contain a
+correct `_xsrf` value. If you turn this setting on, you need to instrument
+all forms that submit via `POST` to contain this field. You can do this with
+the special function `xsrf_form_html()`, available in all templates:
+
+ <form action="/login" method="post">
+ {{ xsrf_form_html() }}
+ <div>Username: <input type="text" name="username"/></div>
+ <div>Password: <input type="password" name="password"/></div>
+ <div><input type="submit" value="Sign in"/></div>
+ </form>
+
+If you submit AJAX `POST` requests, you will also need to instrument your
+JavaScript to include the `_xsrf` value with each request. This is the
+[jQuery](http://jquery.com/) function we use at FriendFeed for AJAX `POST`
+requests that automatically adds the `_xsrf` value to all requests:
+
+ function getCookie(name) {
+ var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
+ return r ? r[1] : undefined;
+ }
+
+ jQuery.postJSON = function(url, args, callback) {
+ args._xsrf = getCookie("_xsrf");
+ $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
+ success: function(response) {
+ callback(eval("(" + response + ")"));
+ }});
+ };
+
+
+### Static files and aggressive file caching
+
+You can serve static files from Tornado by specifying the `static_path`
+setting in your application:
+
+ settings = {
+ "static_path": os.path.join(os.path.dirname(__file__), "static"),
+ "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
+ "login_url": "/login",
+ "xsrf_cookies": True,
+ }
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ (r"/login", LoginHandler),
+ ], **settings)
+
+This setting will automatically make all requests that start with `/static/`
+serve from that static directory, e.g., [http://localhost:8888/static/foo.png](http://localhost:8888/static/foo.png)
+will serve the file `foo.png` from the specified static directory. We
+also automatically serve `/robots.txt` and `/favicon.ico` from the static
+directory (even though they don't start with the `/static/` prefix).
+
+To improve performance, it is generally a good idea for browsers to
+cache static resources aggressively so browsers won't send unnecessary
+`If-Modified-Since` or `Etag` requests that might block the rendering of
+the page. Tornado supports this out of the box with *static content
+versioning*.
+
+To use this feature, use the `static_url()` method in your templates rather
+than typing the URL of the static file directly in your HTML:
+
+ <html>
+ <head>
+ <title>FriendFeed - {{ _("Home") }}</title>
+ </head>
+ <body>
+ <div><img src="{{ static_url("images/logo.png") }}"/></div>
+ </body>
+ </html>
+
+The `static_url()` function will translate that relative path to a URI
+that looks like `/static/images/logo.png?v=aae54`. The `v` argument is
+a hash of the content in `logo.png`, and its presence makes the Tornado
+server send cache headers to the user's browser that will make the browser
+cache the content indefinitely.
+
+Since the `v` argument is based on the content of the file, if you update
+a file and restart your server, it will start sending a new `v` value,
+so the user's browser will automatically fetch the new file. If the file's
+contents don't change, the browser will continue to use a locally cached
+copy without ever checking for updates on the server, significantly
+improving rendering performance.
+
+In production, you probably want to serve static files from a more
+optimized static file server like [nginx](http://nginx.net/). You can
+configure most any web server to support these caching semantics. Here
+is the nginx configuration we use at FriendFeed:
+
+ location /static/ {
+ root /var/friendfeed/static;
+ if ($query_string) {
+ expires max;
+ }
+ }
+
+
+### Localization
+
+The locale of the current user (whether they are logged in or not) is
+always available as `self.locale` in the request handler and as `locale`
+in templates. The name of the locale (e.g., `en_US`) is available as
+`locale.name`, and you can translate strings with the `locale.translate`
+method. Templates also have the global function call `_()` available
+for string translation. The translate function has two forms:
+
+ _("Translate this string")
+
+which translates the string directly based on the current locale, and
+
+ _("A person liked this", "%(num)d people liked this", len(people)) % {"num": len(people)}
+
+which translates a string that can be singular or plural based on the value
+of the third argument. In the example above, a translation of the first
+string will be returned if `len(people)` is `1`, or a translation of the
+second string will be returned otherwise.
+
+The most common pattern for translations is to use Python named placeholders
+for variables (the `%(num)d` in the example above) since placeholders can
+move around on translation.
+
+Here is a properly localized template:
+
+ <html>
+ <head>
+ <title>FriendFeed - {{ _("Sign in") }}</title>
+ </head>
+ <body>
+ <form action="{{ request.path }}" method="post">
+ <div>{{ _("Username") }} <input type="text" name="username"/></div>
+ <div>{{ _("Password") }} <input type="password" name="password"/></div>
+ <div><input type="submit" value="{{ _("Sign in") }}"/></div>
+ {{ xsrf_form_html() }}
+ </form>
+ </body>
+ </html>
+
+By default, we detect the user's locale using the `Accept-Language` header
+sent by the user's browser. We choose `en_US` if we can't find an appropriate
+`Accept-Language` value. If you let user's set their locale as a preference,
+you can override this default locale selection by overriding `get_user_locale`
+in your request handler:
+
+ class BaseHandler(tornado.web.RequestHandler):
+ def get_current_user(self):
+ user_id = self.get_secure_cookie("user")
+ if not user_id: return None
+ return self.backend.get_user_by_id(user_id)
+
+ def get_user_locale(self):
+ if "locale" not in self.current_user.prefs:
+ # Use the Accept-Language header
+ return None
+ return self.current_user.prefs["locale"]
+
+If `get_user_locale` returns `None`, we fall back on the `Accept-Language`
+header.
+
+You can load all the translations for your application using the
+`tornado.locale.load_translations` method. It takes in the name of the
+directory which should contain CSV files named after the locales whose
+translations they contain, e.g., `es_GT.csv` or `fr_CA.csv`. The method
+loads all the translations from those CSV files and infers the list of
+supported locales based on the presence of each CSV file. You typically
+call this method once in the `main()` method of your server:
+
+ def main():
+ tornado.locale.load_translations(
+ os.path.join(os.path.dirname(__file__), "translations"))
+ start_server()
+
+You can get the list of supported locales in your application with
+`tornado.locale.get_supported_locales()`. The user's locale is chosen to
+be the closest match based on the supported locales. For example, if the
+user's locale is `es_GT`, and the `es` locale is supported, `self.locale`
+will be `es` for that request. We fall back on `en_US` if no close match
+can be found.
+
+See the [`locale` module](http://github.com/facebook/tornado/blob/master/tornado/locale.py) documentation for detailed information
+on the CSV format and other localization methods.
+
+
+### UI modules
+
+Tornado supports *UI modules* to make it easy to support standard, reusable
+UI widgets across your application. UI modules are like special functional
+calls to render components of your page, and they can come packaged with
+their own CSS and JavaScript.
+
+For example, if you are implementing a blog, and you want to have
+blog entries appear on both the blog home page and on each blog entry page,
+you can make an `Entry` module to render them on both pages. First, create
+a Python module for your UI modules, e.g., `uimodules.py`:
+
+ class Entry(tornado.web.UIModule):
+ def render(self, entry, show_comments=False):
+ return self.render_string(
+ "module-entry.html", show_comments=show_comments)
+
+Tell Tornado to use `uimodules.py` using the `ui_modules` setting in your
+application:
+
+ class HomeHandler(tornado.web.RequestHandler):
+ def get(self):
+ entries = self.db.query("SELECT * FROM entries ORDER BY date DESC")
+ self.render("home.html", entries=entries)
+
+ class EntryHandler(tornado.web.RequestHandler):
+ def get(self, entry_id):
+ entry = self.db.get("SELECT * FROM entries WHERE id = %s", entry_id)
+ if not entry: raise tornado.web.HTTPError(404)
+ self.render("entry.html", entry=entry)
+
+ settings = {
+ "ui_modules": uimodules,
+ }
+ application = tornado.web.Application([
+ (r"/", HomeHandler),
+ (r"/entry/([0-9]+)", EntryHandler),
+ ], **settings)
+
+Within `home.html`, you reference the `Entry` module rather than printing
+the HTML directly:
+
+ {% for entry in entries %}
+ {{ modules.Entry(entry) }}
+ {% end %}
+
+Within `entry.html`, you reference the `Entry` module with the
+`show_comments` argument to show the expanded form of the entry:
+
+ {{ modules.Entry(entry, show_comments=True) }}
+
+Modules can include custom CSS and JavaScript functions by overriding
+the `embedded_css`, `embedded_javascript`, `javascript_files`, or
+`css_files` methods:
+
+ class Entry(tornado.web.UIModule):
+ def embedded_css(self):
+ return ".entry { margin-bottom: 1em; }"
+
+ def render(self, entry, show_comments=False):
+ return self.render_string(
+ "module-entry.html", show_comments=show_comments)
+
+Module CSS and JavaScript will be included once no matter how many times
+a module is used on a page. CSS is always included in the `<head>` of the
+page, and JavaScript is always included just before the `</body>` tag
+at the end of the page.
+
+
+### Non-blocking, asynchronous requests
+
+When a request handler is executed, the request is automatically finished.
+Since Tornado uses a non-blocking I/O style, you can override this default
+behavior if you want a request to remain open after the main request handler
+method returns using the `tornado.web.asynchronous` decorator.
+
+When you use this decorator, it is your responsibility to call
+`self.finish()` to finish the HTTP request, or the user's browser
+will simply hang:
+
+ class MainHandler(tornado.web.RequestHandler):
+ @tornado.web.asynchronous
+ def get(self):
+ self.write("Hello, world")
+ self.finish()
+
+Here is a real example that makes a call to the FriendFeed API using
+Tornado's built-in asynchronous HTTP client:
+
+ class MainHandler(tornado.web.RequestHandler):
+ @tornado.web.asynchronous
+ def get(self):
+ http = tornado.httpclient.AsyncHTTPClient()
+ http.fetch("http://friendfeed-api.com/v2/feed/bret",
+ callback=self.async_callback(self.on_response))
+
+ def on_response(self, response):
+ if response.error: raise tornado.web.HTTPError(500)
+ json = tornado.escape.json_decode(response.body)
+ self.write("Fetched " + str(len(json["entries"])) + " entries "
+ "from the FriendFeed API")
+ self.finish()
+
+When `get()` returns, the request has not finished. When the HTTP client
+eventually calls `on_response()`, the request is still open, and the response
+is finally flushed to the client with the call to `self.finish()`.
+
+If you make calls to asynchronous library functions that require a callback
+(like the HTTP `fetch` function above), you should always wrap your
+callbacks with `self.async_callback`. This simple wrapper ensures that if
+your callback function raises an exception or has a programming error,
+a proper HTTP error response will be sent to the browser, and the connection
+will be properly closed.
+
+For a more advanced asynchronous example, take a look at the `chat` example
+application, which implements an AJAX chat room using
+[long polling](http://en.wikipedia.org/wiki/Push_technology#Long_polling).
+
+
+### Third party authentication
+
+Tornado's `auth` module implements the authentication and authorization
+protocols for a number of the most popular sites on the web, including
+Google/Gmail, Facebook, Twitter, Yahoo, and FriendFeed. The module includes
+methods to log users in via these sites and, where applicable, methods to
+authorize access to the service so you can, e.g., download a user's address
+book or publish a Twitter message on their behalf.
+
+Here is an example handler that uses Google for authentication, saving
+the Google credentials in a cookie for later access:
+
+ class GoogleHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin):
+ @tornado.web.asynchronous
+ def get(self):
+ if self.get_argument("openid.mode", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authenticate_redirect()
+
+ def _on_auth(self, user):
+ if not user:
+ self.authenticate_redirect()
+ return
+ # Save the user with, e.g., set_secure_cookie()
+
+See the `auth` module documentation for more details.
+
+
+Performance
+-----------
+Web application performance is generally bound by architecture, not frontend
+performance. That said, Tornado is pretty fast relative to most popular
+Python web frameworks.
+
+We ran a few remedial load tests on a simple "Hello, world" application
+in each of the most popular Python web frameworks
+([Django](http://www.djangoproject.com/), [web.py](http://webpy.org/), and
+[CherryPy](http://www.cherrypy.org/)) to get the baseline performance of
+each relative to Tornado. We used Apache/mod_wsgi for Django and web.py
+and ran CherryPy as a standalone server, which was our impression of how
+each framework is typically run in production environments. We ran 4
+single-threaded Tornado frontends behind an [nginx](http://nginx.net/)
+reverse proxy, which is how we recommend running Tornado in production
+(our load test machine had four cores, and we recommend 1 frontend per
+core).
+
+We load tested each with Apache Benchmark (`ab`) on the a separate machine
+with the command
+
+ ab -n 100000 -c 25 http://10.0.1.x/
+
+The results (requests per second) on a 2.4GHz AMD Opteron processor with
+4 cores:
+
+<div style="text-align:center;margin-top:2em;margin-bottom:2em"><img src="http://chart.apis.google.com/chart?chxt=y&chd=t%3A100%2C40%2C27%2C25%2C9&chco=609bcc&chm=t+8213%2C000000%2C0%2C0%2C11%7Ct+3353%2C000000%2C0%2C1%2C11%7Ct+2223%2C000000%2C0%2C2%2C11%7Ct+2066%2C000000%2C0%2C3%2C11%7Ct+785%2C000000%2C0%2C4%2C11&chs=600x175&cht=bhs&chtt=Web+server+requests%2Fsec+%28AMD+Opteron%2C+2.4GHz%2C+4+cores%29&chxl=0%3A%7CCherryPy+%28standalone%29%7Cweb.py+%28Apache%2Fmod_wsgi%29%7CDjango+%28Apache%2Fmod_wsgi%29%7CTornado+%281+single-threaded+frontend%29%7CTornado+%28nginx%3B+4+frontends%29%7C"/></div>
+
+In our tests, Tornado consistently had 4X the throughput of the next fastest
+framework, and even a single standalone Tornado frontend got 33% more
+throughput even though it only used one of the four cores.
+
+Not very scientific, but at a high level, it should give you a sense that we
+have cared about performance as we built Tornado, and it shouldn't add too
+much latency to your apps relative to most Python web development frameworks.
+
+
+Running Tornado in production
+-----------------------------
+At FriendFeed, we use [nginx](http://nginx.net/) as a load balancer
+and static file server. We run multiple instances of the Tornado web
+server on multiple frontend machines. We typically run one Tornado frontend
+per core on the machine (sometimes more depending on utilization).
+
+This is a barebones nginx config file that is structurally similar to the
+one we use at FriendFeed. It assumes nginx and the Tornado servers
+are running on the same machine, and the four Tornado servers
+are running on ports 8000 - 8003:
+
+ user nginx;
+ worker_processes 1;
+
+ error_log /var/log/nginx/error.log;
+ pid /var/run/nginx.pid;
+
+ events {
+ worker_connections 1024;
+ use epoll;
+ }
+
+ http {
+ # Enumerate all the Tornado servers here
+ upstream frontends {
+ server 127.0.0.1:8000;
+ server 127.0.0.1:8001;
+ server 127.0.0.1:8002;
+ server 127.0.0.1:8003;
+ }
+
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ access_log /var/log/nginx/access.log;
+
+ keepalive_timeout 65;
+ proxy_read_timeout 200;
+ sendfile on;
+ tcp_nopush on;
+ tcp_nodelay on;
+ gzip on;
+ gzip_min_length 1000;
+ gzip_proxied any;
+ gzip_types text/plain text/html text/css text/xml
+ application/x-javascript application/xml
+ application/atom+xml text/javascript;
+
+ # Only retry if there was a communication error, not a timeout
+ # on the Tornado server (to avoid propagating "queries of death"
+ # to all frontends)
+ proxy_next_upstream error;
+
+ server {
+ listen 80;
+
+ # Allow file uploads
+ client_max_body_size 50M;
+
+ location ^~ /static/ {
+ root /var/www;
+ if ($query_string) {
+ expires max;
+ }
+ }
+ location = /favicon.ico {
+ rewrite (.*) /static/favicon.ico;
+ }
+ location = /robots.txt {
+ rewrite (.*) /static/robots.txt;
+ }
+
+ location / {
+ proxy_pass_header Server;
+ proxy_set_header Host $http_host;
+ proxy_redirect false;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Scheme $scheme;
+ proxy_pass http://frontends;
+ }
+ }
+ }
+
+
+WSGI and Google AppEngine
+-------------------------
+Tornado comes with limited support for [WSGI](http://wsgi.org/). However,
+since WSGI does not support non-blocking requests, you cannot use any
+of the asynchronous/non-blocking features of Tornado in your application
+if you choose to use WSGI instead of Tornado's HTTP server. Some of the
+features that are not available in WSGI applications:
+`@tornado.web.asynchronous`, the `httpclient` module, and the `auth` module.
+
+You can create a valid WSGI application from your Tornado request handlers
+by using `WSGIApplication` in the `wsgi` module instead of using
+`tornado.web.Application`. Here is an example that uses the built-in WSGI
+`CGIHandler` to make a valid
+[Google AppEngine](http://code.google.com/appengine/) application:
+
+ import tornado.web
+ import tornado.wsgi
+ import wsgiref.handlers
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.write("Hello, world")
+
+ if __name__ == "__main__":
+ application = tornado.wsgi.WSGIApplication([
+ (r"/", MainHandler),
+ ])
+ wsgiref.handlers.CGIHandler().run(application)
+
+See the `appengine` example application for a full-featured AppEngine
+app built on Tornado.
+
+
+Caveats and support
+-------------------
+Tornado was refactored from the [FriendFeed](http://friendfeed.com/)
+code base to reduce dependencies. This refactoring may have introduced
+bugs. Likewise, because the FriendFeed servers have always run
+[behind nginx](#running-tornado-in-production), Tornado has not been
+extensively tested with HTTP/1.1 clients beyond Firefox. Tornado
+currently does not attempt to handle multi-line headers and some types
+of malformed input.
+
+You can discuss Tornado and report bugs on [the Tornado developer mailing list](http://groups.google.com/group/python-tornado).
diff --git a/vendor/tornado/website/templates/index.html b/vendor/tornado/website/templates/index.html
new file mode 100644
index 0000000000..4aa716598b
--- /dev/null
+++ b/vendor/tornado/website/templates/index.html
@@ -0,0 +1,51 @@
+{% extends "base.html" %}
+
+{% block body %}
+ <p><a href="http://www.tornadoweb.org/">Tornado</a> is an open source version of the scalable, non-blocking web server and tools that power <a href="http://friendfeed.com/">FriendFeed</a>. The FriendFeed application is written using a web framework that looks a bit like <a href="http://webpy.org/">web.py</a> or <a href="http://code.google.com/appengine/docs/python/tools/webapp/">Google's webapp</a>, but with additional tools and optimizations to take advantage of the underlying non-blocking infrastructure.</p>
+ <p>The framework is distinct from most mainstream web server frameworks (and certainly most Python frameworks) because it is non-blocking and reasonably fast. Because it is non-blocking and uses <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/epoll.4.html">epoll</a>, it can handle thousands of simultaneous standing connections, which means it is ideal for real-time web services. We built the web server specifically to handle FriendFeed's real-time features &mdash; every active user of FriendFeed maintains an open connection to the FriendFeed servers. (For more information on scaling servers to support thousands of clients, see The <a href="http://www.kegel.com/c10k.html">C10K problem</a>.)</p>
+ <p>See the <a href="/documentation">Tornado documentation</a> for a detailed walkthrough of the framework.</p>
+
+ <h2>Download and install</h2>
+ <p><b>Download:</b> <a href="/static/tornado-0.2.tar.gz">tornado-0.2.tar.gz</a></p>
+ <pre><code>tar xvzf tornado-0.2.tar.gz
+cd tornado-0.2
+python setup.py build
+sudo python setup.py install</code></pre>
+ <p>The Tornado source code is <a href="http://github.com/facebook/tornado">hosted on GitHub</a>.</p>
+
+ <h3>Prerequisites</h3>
+ <p>Tornado has been tested on Python 2.5 and 2.6. To use all of the features of Tornado, you need to have <a href="http://pycurl.sourceforge.net/">PycURL</a> and a JSON library like <a href="http://pypi.python.org/pypi/simplejson/">simplejson</a> installed. Complete installation instructions for Mac OS X and Ubuntu are included below for convenience.</p>
+ <p style="font-weight:bold">Mac OS X 10.5/10.6</p>
+ <pre><code>sudo easy_install setuptools pycurl==7.16.2.1 simplejson</code></pre>
+
+ <p style="font-weight:bold">Ubuntu Linux</p>
+ <pre><code>sudo apt-get install python-dev python-pycurl python-simplejson</code></pre>
+
+ <h2>Hello, world</h2>
+ <p>Here is the canonical &quot;Hello, world&quot; example app for Tornado:</p>
+ <pre><code>import tornado.httpserver
+import tornado.ioloop
+import tornado.web
+
+class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.write("Hello, world")
+
+application = tornado.web.Application([
+ (r"/", MainHandler),
+])
+
+if __name__ == "__main__":
+ http_server = tornado.httpserver.HTTPServer(application)
+ http_server.listen(8888)
+ tornado.ioloop.IOLoop.instance().start()</code></pre>
+ <p>See the <a href="/documentation">Tornado documentation</a> for a detailed walkthrough of the framework.</p>
+
+ <h2>Discussion and support</h2>
+ <p>You can discuss Tornado and report bugs on <a href="http://groups.google.com/group/python-tornado">the Tornado developer mailing list</a>.
+
+ <h2>Updates</h2>
+ <p>Follow us on <a href="http://www.facebook.com/pages/Tornado-Web-Server/144144048921">Facebook</a>, <a href="http://twitter.com/tornadoweb">Twitter</a>, or <a href="http://friendfeed.com/tornado-web">FriendFeed</a> to get updates and announcements:</p>
+ <div style="margin-top:1em"><a href="http://www.facebook.com/pages/Tornado-Web-Server/144144048921" style="margin-right:10px"><img src="/static/facebook.png" style="width:64px;height:64px" alt="Facebook"/></a><a href="http://twitter.com/tornadoweb" style="margin-right:10px"><img src="/static/twitter.png" style="width:64px;height:64px" alt="Twitter"/></a><a href="http://friendfeed.com/tornado-web" style="margin-right:10px"><img src="/static/friendfeed.png" style="width:64px;height:64px" alt="Facebook"/></a></div>
+
+{% end %}
diff --git a/vendor/tornado/website/website.py b/vendor/tornado/website/website.py
new file mode 100644
index 0000000000..f073b67e6b
--- /dev/null
+++ b/vendor/tornado/website/website.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Bret Taylor
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import markdown
+import os
+import os.path
+import time
+import tornado.web
+import tornado.wsgi
+import wsgiref.handlers
+
+
+class ContentHandler(tornado.web.RequestHandler):
+ def get(self, path):
+ paths = ("documentation", "index")
+ if not path: path = "index"
+ if path not in paths:
+ raise tornado.web.HTTPError(404)
+ self.render(path + ".html", markdown=self.markdown)
+
+ def markdown(self, path, toc=False):
+ if not hasattr(ContentHandler, "_md") or self.settings.get("debug"):
+ ContentHandler._md = {}
+ if path not in ContentHandler._md:
+ full_path = os.path.join(self.settings["template_path"], path)
+ f = open(full_path, "r")
+ contents = f.read().decode("utf-8")
+ f.close()
+ if toc: contents = u"[TOC]\n\n" + contents
+ md = markdown.Markdown(extensions=["toc"] if toc else [])
+ ContentHandler._md[path] = md.convert(contents).encode("utf-8")
+ return ContentHandler._md[path]
+
+
+settings = {
+ "template_path": os.path.join(os.path.dirname(__file__), "templates"),
+ "xsrf_cookies": True,
+ "debug": os.environ.get("SERVER_SOFTWARE", "").startswith("Development/"),
+}
+application = tornado.wsgi.WSGIApplication([
+ (r"/([a-z]*)", ContentHandler),
+], **settings)
+
+
+def main():
+ wsgiref.handlers.CGIHandler().run(application)
+
+
+if __name__ == "__main__":
+ main()